Support ROHM BD96801 "scalable" PMIC.
The ROHM BD96801 is automotive grade PMIC, intended to be usable in
multiple solutions. The BD96801 can be used as a stand-alone, or together
with separate 'companion PMICs'. This modular approach aims to make this
PMIC suitable for various use-cases.
This is sent as an RFC because of the regulator features which can be
configured only when the PMIC is in STBY state. This is described more
detailed in the regulator patch.
Another "oddity" is that the PMIC has two physical IRQ lines. When using
regmap IRQ to create own IRQ controller instance for both HWIRQs, there
will be a naming collison in debugfs for the IRQ domains. As a
work-around the MFD driver uses
irq_domain_update_bus_token(intb_domain, DOMAIN_BUS_WIRED);
to append '-1' at the end of the domain name.
Rest of the series ought to be business as usual.
Revision history:
RFCv1 => RFCv2:
- Tidying code based on feedback form Krzysztof Kozlowski and
Lee Jones.
- Documented undocumented watchdog related DT properties.
- Added usage of the watchdog IRQ.
- Use irq_domain_update_bus_token() to work-around debugFS name
collision for IRQ domains.
---
Matti Vaittinen (6):
dt-bindings: ROHM BD96801 PMIC regulators
dt-bindings: mfd: bd96801 PMIC core
mfd: support ROHM BD96801 PMIC core
regulator: bd96801: ROHM BD96801 PMIC regulators
watchdog: ROHM BD96801 PMIC WDG driver
MAINTAINERS: Add ROHM BD96801 'scalable PMIC' entries
.../bindings/mfd/rohm,bd96801-pmic.yaml | 171 ++
.../regulator/rohm,bd96801-regulator.yaml | 69 +
MAINTAINERS | 4 +
drivers/mfd/Kconfig | 13 +
drivers/mfd/Makefile | 1 +
drivers/mfd/rohm-bd96801.c | 476 ++++
drivers/regulator/Kconfig | 12 +
drivers/regulator/Makefile | 2 +
drivers/regulator/bd96801-regulator.c | 2114 +++++++++++++++++
drivers/watchdog/Kconfig | 13 +
drivers/watchdog/Makefile | 1 +
drivers/watchdog/bd96801_wdt.c | 389 +++
include/linux/mfd/rohm-bd96801.h | 211 ++
include/linux/mfd/rohm-generic.h | 1 +
14 files changed, 3477 insertions(+)
create mode 100644 Documentation/devicetree/bindings/mfd/rohm,bd96801-pmic.yaml
create mode 100644 Documentation/devicetree/bindings/regulator/rohm,bd96801-regulator.yaml
create mode 100644 drivers/mfd/rohm-bd96801.c
create mode 100644 drivers/regulator/bd96801-regulator.c
create mode 100644 drivers/watchdog/bd96801_wdt.c
create mode 100644 include/linux/mfd/rohm-bd96801.h
base-commit: 4cece764965020c22cff7665b18a012006359095
--
2.43.2
--
Matti Vaittinen, Linux device drivers
ROHM Semiconductors, Finland SWDC
Kiviharjunlenkki 1E
90220 OULU
FINLAND
~~~ "I don't think so," said Rene Descartes. Just then he vanished ~~~
Simon says - in Latin please.
~~~ "non cogito me" dixit Rene Descarte, deinde evanescavit ~~~
Thanks to Simon Glass for the translation =]
ROHM BD96801 is a highly configurable automotive grade PMIC. Introduce
DT bindings for the BD96801 regulators.
Signed-off-by: Matti Vaittinen <[email protected]>
---
Revision history:
- No changes since RFCv1
.../regulator/rohm,bd96801-regulator.yaml | 69 +++++++++++++++++++
1 file changed, 69 insertions(+)
create mode 100644 Documentation/devicetree/bindings/regulator/rohm,bd96801-regulator.yaml
diff --git a/Documentation/devicetree/bindings/regulator/rohm,bd96801-regulator.yaml b/Documentation/devicetree/bindings/regulator/rohm,bd96801-regulator.yaml
new file mode 100644
index 000000000000..4015802a3d84
--- /dev/null
+++ b/Documentation/devicetree/bindings/regulator/rohm,bd96801-regulator.yaml
@@ -0,0 +1,69 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/regulator/rohm,bd96801-regulator.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: ROHM BD96801 Power Management Integrated Circuit regulators
+
+maintainers:
+ - Matti Vaittinen <[email protected]>
+
+description: |
+ This module is part of the ROHM BD96801 MFD device. For more details
+ see Documentation/devicetree/bindings/mfd/rohm,bd96801-pmic.yaml.
+
+ The regulator controller is represented as a sub-node of the PMIC node
+ on the device tree.
+
+ Regulator nodes should be named to BUCK_<number> and LDO_<number>.
+ The valid names for BD96801 regulator nodes are
+ BUCK1, BUCK2, BUCK3, BUCK4, LDO5, LDO6, LDO7
+
+patternProperties:
+ "^LDO[5-7]$":
+ type: object
+ description:
+ Properties for single LDO regulator.
+ $ref: regulator.yaml#
+
+ properties:
+ regulator-name:
+ pattern: "^ldo[5-7]$"
+ description:
+ Name of the regulator. Should be "ldo5", ..., "ldo7"
+ rohm,initial-voltage-microvolt:
+ description:
+ Initial voltage for regulator. Voltage can be tuned +/-150 mV from
+ this value. NOTE, This can be modified via I2C only when PMIC is in
+ STBY state.
+ minimum: 300000
+ maximum: 3300000
+
+ "^BUCK[1-4]$":
+ type: object
+ description:
+ Properties for single BUCK regulator.
+ $ref: regulator.yaml#
+
+ properties:
+ regulator-name:
+ pattern: "^buck[1-4]$"
+ description:
+ should be "buck1", ..., "buck4"
+ rohm,initial-voltage-microvolt:
+ description:
+ Initial voltage for regulator. Voltage can be tuned +/-150 mV from
+ this value. NOTE, This can be modified via I2C only when PMIC is in
+ STBY state.
+ minimum: 500000
+ maximum: 3300000
+ rohm,keep-on-stby:
+ description:
+ Keep the regulator powered when PMIC transitions to STBY state.
+ type: boolean
+
+ required:
+ - regulator-name
+ additionalProperties: false
+additionalProperties: false
--
2.43.2
--
Matti Vaittinen, Linux device drivers
ROHM Semiconductors, Finland SWDC
Kiviharjunlenkki 1E
90220 OULU
FINLAND
~~~ "I don't think so," said Rene Descartes. Just then he vanished ~~~
Simon says - in Latin please.
~~~ "non cogito me" dixit Rene Descarte, deinde evanescavit ~~~
Thanks to Simon Glass for the translation =]
ROHM BD96801 is a highly configurable automotive grade PMIC. Introduce
DT bindings for the BD96801 core.
Signed-off-by: Matti Vaittinen <[email protected]>
---
Revision history:
RFCv1 => RFCv2:
- Document rohm,hw-timeout-ms
- Document rohm,wdg-action
---
.../bindings/mfd/rohm,bd96801-pmic.yaml | 171 ++++++++++++++++++
1 file changed, 171 insertions(+)
create mode 100644 Documentation/devicetree/bindings/mfd/rohm,bd96801-pmic.yaml
diff --git a/Documentation/devicetree/bindings/mfd/rohm,bd96801-pmic.yaml b/Documentation/devicetree/bindings/mfd/rohm,bd96801-pmic.yaml
new file mode 100644
index 000000000000..31ef787d6a8a
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/rohm,bd96801-pmic.yaml
@@ -0,0 +1,171 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/mfd/rohm,bd96801-pmic.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: ROHM BD96801 Scalable Power Management Integrated Circuit
+
+maintainers:
+ - Matti Vaittinen <[email protected]>
+
+description: |
+ BD96801 is an automotive grade single-chip power management IC.
+ It integrates 4 buck converters and 3 LDOs with safety features like
+ over-/under voltage and over current detection and a watchdog.
+
+properties:
+ compatible:
+ const: rohm,bd96801
+
+ reg:
+ description:
+ I2C slave address.
+ maxItems: 1
+
+ interrupts:
+ description:
+ The PMIC provides intb and errb IRQ lines. The errb IRQ line is used
+ for fatal IRQs which will cause the PMIC to shut down power outputs.
+ In many systems this will shut down the SoC contolling the PMIC and
+ connecting/handling the errb can be omitted. However, there are cases
+ where the SoC is not powered by the PMIC. In that case it may be
+ useful to connect the errb and handle errb events.
+ minItems: 1
+ maxItems: 2
+
+ interrupt-names:
+ minItems: 1
+ items:
+ - enum: [intb, errb]
+ - const: errb
+
+ rohm,hw-timeout-ms:
+ description:
+ Watchdog timeout value(s). First walue is timeout limit. Second value is
+ optional value for 'too early' watchdog ping if window timeout mode is
+ to be used.
+ minItems: 1
+ maxItems: 2
+
+ rohm,wdg-action:
+ description:
+ Whether the watchdog failure must turn off the regulator power outputs or
+ just toggle the INTB line.
+ enum:
+ - prstb
+ - intb-only
+
+ regulators:
+ $ref: ../regulator/rohm,bd96801-regulator.yaml
+ description:
+ List of child nodes that specify the regulators.
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - interrupt-names
+ - regulators
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/irq.h>
+ #include <dt-bindings/leds/common.h>
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ pmic: pmic@60 {
+ reg = <0x60>;
+ compatible = "rohm,bd96801";
+ interrupt-parent = <&gpio1>;
+ interrupts = <29 IRQ_TYPE_LEVEL_LOW>, <6 IRQ_TYPE_LEVEL_LOW>;
+ interrupt-names = "intb", "errb";
+
+ regulators {
+ buck1: BUCK1 {
+ regulator-name = "buck1";
+ regulator-ramp-delay = <1250>;
+ /* 0.5V min INITIAL - 150 mV tune */
+ regulator-min-microvolt = <350000>;
+ /* 3.3V + 150mV tune */
+ regulator-max-microvolt = <3450000>;
+
+ /* These can be set only when PMIC is in STBY */
+ rohm,initial-voltage-microvolt = <500000>;
+ regulator-ov-error-microvolt = <230000>;
+ regulator-uv-error-microvolt = <230000>;
+ regulator-temp-protection-kelvin = <1>;
+ regulator-temp-warn-kelvin = <0>;
+ };
+ buck2: BUCK2 {
+ regulator-name = "buck2";
+ regulator-min-microvolt = <350000>;
+ regulator-max-microvolt = <3450000>;
+
+ rohm,initial-voltage-microvolt = <3000000>;
+ regulator-ov-error-microvolt = <18000>;
+ regulator-uv-error-microvolt = <18000>;
+ regulator-temp-protection-kelvin = <1>;
+ regulator-temp-warn-kelvin = <1>;
+ };
+ buck3: BUCK3 {
+ regulator-name = "buck3";
+ regulator-min-microvolt = <350000>;
+ regulator-max-microvolt = <3450000>;
+
+ rohm,initial-voltage-microvolt = <600000>;
+ regulator-ov-warn-microvolt = <18000>;
+ regulator-uv-warn-microvolt = <18000>;
+ regulator-temp-protection-kelvin = <1>;
+ regulator-temp-error-kelvin = <0>;
+ };
+ buck4: BUCK4 {
+ regulator-name = "buck4";
+ regulator-min-microvolt = <350000>;
+ regulator-max-microvolt = <3450000>;
+
+ rohm,initial-voltage-microvolt = <600000>;
+ regulator-ov-warn-microvolt = <18000>;
+ regulator-uv-warn-microvolt = <18000>;
+ regulator-temp-protection-kelvin = <1>;
+ regulator-temp-error-kelvin = <0>;
+ };
+ ldo5: LDO5 {
+ regulator-name = "ldo5";
+ regulator-min-microvolt = <300000>;
+ regulator-max-microvolt = <3300000>;
+
+ rohm,initial-voltage-microvolt = <500000>;
+ regulator-ov-error-microvolt = <36000>;
+ regulator-uv-error-microvolt = <34000>;
+ regulator-temp-protection-kelvin = <1>;
+ regulator-temp-warn-kelvin = <0>;
+ };
+ ldo6: LDO6 {
+ regulator-name = "ldo6";
+ regulator-min-microvolt = <300000>;
+ regulator-max-microvolt = <3300000>;
+
+ rohm,initial-voltage-microvolt = <300000>;
+ regulator-ov-error-microvolt = <36000>;
+ regulator-uv-error-microvolt = <34000>;
+ regulator-temp-protection-kelvin = <1>;
+ regulator-temp-warn-kelvin = <0>;
+ };
+ ldo7: LDO7 {
+ regulator-name = "ldo7";
+ regulator-min-microvolt = <300000>;
+ regulator-max-microvolt = <3300000>;
+
+ rohm,initial-voltage-microvolt = <500000>;
+ regulator-ov-error-microvolt = <36000>;
+ regulator-uv-error-microvolt = <34000>;
+ regulator-temp-protection-kelvin = <1>;
+ regulator-temp-warn-kelvin = <0>;
+ };
+ };
+ };
+ };
--
2.43.2
--
Matti Vaittinen, Linux device drivers
ROHM Semiconductors, Finland SWDC
Kiviharjunlenkki 1E
90220 OULU
FINLAND
~~~ "I don't think so," said Rene Descartes. Just then he vanished ~~~
Simon says - in Latin please.
~~~ "non cogito me" dixit Rene Descarte, deinde evanescavit ~~~
Thanks to Simon Glass for the translation =]
The ROHM BD96801 PMIC is highly customizable automotive grade PMIC
which integrates regulator and watchdog funtionalities.
Provide IRQ and register accesses for regulator/watchdog drivers.
Signed-off-by: Matti Vaittinen <[email protected]>
---
Changelog: RFCv1 => RFCv2
- Work-around the IRQ domain name conflict
- Add watchdog IRQ
- Various styling fixes based on review by Lee
---
drivers/mfd/Kconfig | 13 +
drivers/mfd/Makefile | 1 +
drivers/mfd/rohm-bd96801.c | 476 +++++++++++++++++++++++++++++++
include/linux/mfd/rohm-bd96801.h | 211 ++++++++++++++
include/linux/mfd/rohm-generic.h | 1 +
5 files changed, 702 insertions(+)
create mode 100644 drivers/mfd/rohm-bd96801.c
create mode 100644 include/linux/mfd/rohm-bd96801.h
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 4b023ee229cf..9e874453d94d 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -2089,6 +2089,19 @@ config MFD_ROHM_BD957XMUF
BD9573MUF Power Management ICs. BD9576 and BD9573 are primarily
designed to be used to power R-Car series processors.
+config MFD_ROHM_BD96801
+ tristate "ROHM BD96801 Power Management IC"
+ depends on I2C=y
+ depends on OF
+ select REGMAP_I2C
+ select REGMAP_IRQ
+ select MFD_CORE
+ help
+ Select this option to get support for the ROHM BD96801 Power
+ Management IC. The ROHM BD96801 is a highly scalable Power Management
+ IC for industrial and automotive use. The BD96801 can be used as a
+ master PMIC in a chained PMIC solution with suitable companion PMICs.
+
config MFD_STM32_LPTIMER
tristate "Support for STM32 Low-Power Timer"
depends on (ARCH_STM32 && OF) || COMPILE_TEST
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index c66f07edcd0e..e792892d4a8b 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -264,6 +264,7 @@ obj-$(CONFIG_RAVE_SP_CORE) += rave-sp.o
obj-$(CONFIG_MFD_ROHM_BD71828) += rohm-bd71828.o
obj-$(CONFIG_MFD_ROHM_BD718XX) += rohm-bd718x7.o
obj-$(CONFIG_MFD_ROHM_BD957XMUF) += rohm-bd9576.o
+obj-$(CONFIG_MFD_ROHM_BD96801) += rohm-bd96801.o
obj-$(CONFIG_MFD_STMFX) += stmfx.o
obj-$(CONFIG_MFD_KHADAS_MCU) += khadas-mcu.o
obj-$(CONFIG_MFD_ACER_A500_EC) += acer-ec-a500.o
diff --git a/drivers/mfd/rohm-bd96801.c b/drivers/mfd/rohm-bd96801.c
new file mode 100644
index 000000000000..5a8a74d92873
--- /dev/null
+++ b/drivers/mfd/rohm-bd96801.c
@@ -0,0 +1,476 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2024 ROHM Semiconductors
+ *
+ * ROHM BD96801 PMIC driver
+ */
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/core.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+#include <linux/mfd/rohm-bd96801.h>
+#include <linux/mfd/rohm-generic.h>
+
+static const struct resource regulator_errb_irqs[] = {
+ DEFINE_RES_IRQ_NAMED(BD96801_OTP_ERR_STAT, "bd96801-otp-err"),
+ DEFINE_RES_IRQ_NAMED(BD96801_DBIST_ERR_STAT, "bd96801-dbist-err"),
+ DEFINE_RES_IRQ_NAMED(BD96801_EEP_ERR_STAT, "bd96801-eep-err"),
+ DEFINE_RES_IRQ_NAMED(BD96801_ABIST_ERR_STAT, "bd96801-abist-err"),
+ DEFINE_RES_IRQ_NAMED(BD96801_PRSTB_ERR_STAT, "bd96801-prstb-err"),
+ DEFINE_RES_IRQ_NAMED(BD96801_DRMOS1_ERR_STAT, "bd96801-drmoserr1"),
+ DEFINE_RES_IRQ_NAMED(BD96801_DRMOS2_ERR_STAT, "bd96801-drmoserr2"),
+ DEFINE_RES_IRQ_NAMED(BD96801_SLAVE_ERR_STAT, "bd96801-slave-err"),
+ DEFINE_RES_IRQ_NAMED(BD96801_VREF_ERR_STAT, "bd96801-vref-err"),
+ DEFINE_RES_IRQ_NAMED(BD96801_TSD_ERR_STAT, "bd96801-tsd"),
+ DEFINE_RES_IRQ_NAMED(BD96801_UVLO_ERR_STAT, "bd96801-uvlo-err"),
+ DEFINE_RES_IRQ_NAMED(BD96801_OVLO_ERR_STAT, "bd96801-ovlo-err"),
+ DEFINE_RES_IRQ_NAMED(BD96801_OSC_ERR_STAT, "bd96801-osc-err"),
+ DEFINE_RES_IRQ_NAMED(BD96801_PON_ERR_STAT, "bd96801-pon-err"),
+ DEFINE_RES_IRQ_NAMED(BD96801_POFF_ERR_STAT, "bd96801-poff-err"),
+ DEFINE_RES_IRQ_NAMED(BD96801_CMD_SHDN_ERR_STAT, "bd96801-cmd-shdn-err"),
+
+ DEFINE_RES_IRQ_NAMED(BD96801_INT_PRSTB_WDT_ERR, "bd96801-prstb-wdt-err"),
+ DEFINE_RES_IRQ_NAMED(BD96801_INT_CHIP_IF_ERR, "bd96801-chip-if-err"),
+ DEFINE_RES_IRQ_NAMED(BD96801_INT_SHDN_ERR_STAT, "bd96801-int-shdn-err"),
+
+ DEFINE_RES_IRQ_NAMED(BD96801_BUCK1_PVIN_ERR_STAT, "bd96801-buck1-pvin-err"),
+ DEFINE_RES_IRQ_NAMED(BD96801_BUCK1_OVP_ERR_STAT, "bd96801-buck1-ovp-err"),
+ DEFINE_RES_IRQ_NAMED(BD96801_BUCK1_UVP_ERR_STAT, "bd96801-buck1-uvp-err"),
+ DEFINE_RES_IRQ_NAMED(BD96801_BUCK1_SHDN_ERR_STAT, "bd96801-buck1-shdn-err"),
+
+ DEFINE_RES_IRQ_NAMED(BD96801_BUCK2_PVIN_ERR_STAT, "bd96801-buck2-pvin-err"),
+ DEFINE_RES_IRQ_NAMED(BD96801_BUCK2_OVP_ERR_STAT, "bd96801-buck2-ovp-err"),
+ DEFINE_RES_IRQ_NAMED(BD96801_BUCK2_UVP_ERR_STAT, "bd96801-buck2-uvp-err"),
+ DEFINE_RES_IRQ_NAMED(BD96801_BUCK2_SHDN_ERR_STAT, "bd96801-buck2-shdn-err"),
+
+ DEFINE_RES_IRQ_NAMED(BD96801_BUCK3_PVIN_ERR_STAT, "bd96801-buck3-pvin-err"),
+ DEFINE_RES_IRQ_NAMED(BD96801_BUCK3_OVP_ERR_STAT, "bd96801-buck3-ovp-err"),
+ DEFINE_RES_IRQ_NAMED(BD96801_BUCK3_UVP_ERR_STAT, "bd96801-buck3-uvp-err"),
+ DEFINE_RES_IRQ_NAMED(BD96801_BUCK3_SHDN_ERR_STAT, "bd96801-buck3-shdn-err"),
+
+ DEFINE_RES_IRQ_NAMED(BD96801_BUCK4_PVIN_ERR_STAT, "bd96801-buck4-pvin-err"),
+ DEFINE_RES_IRQ_NAMED(BD96801_BUCK4_OVP_ERR_STAT, "bd96801-buck4-ovp-err"),
+ DEFINE_RES_IRQ_NAMED(BD96801_BUCK4_UVP_ERR_STAT, "bd96801-buck4-uvp-err"),
+ DEFINE_RES_IRQ_NAMED(BD96801_BUCK4_SHDN_ERR_STAT, "bd96801-buck4-shdn-err"),
+
+ DEFINE_RES_IRQ_NAMED(BD96801_LDO5_PVIN_ERR_STAT, "bd96801-ldo5-pvin-err"),
+ DEFINE_RES_IRQ_NAMED(BD96801_LDO5_OVP_ERR_STAT, "bd96801-ldo5-ovp-err"),
+ DEFINE_RES_IRQ_NAMED(BD96801_LDO5_UVP_ERR_STAT, "bd96801-ldo5-uvp-err"),
+ DEFINE_RES_IRQ_NAMED(BD96801_LDO5_SHDN_ERR_STAT, "bd96801-ldo5-shdn-err"),
+
+ DEFINE_RES_IRQ_NAMED(BD96801_LDO6_PVIN_ERR_STAT, "bd96801-ldo6-pvin-err"),
+ DEFINE_RES_IRQ_NAMED(BD96801_LDO6_OVP_ERR_STAT, "bd96801-ldo6-ovp-err"),
+ DEFINE_RES_IRQ_NAMED(BD96801_LDO6_UVP_ERR_STAT, "bd96801-ldo6-uvp-err"),
+ DEFINE_RES_IRQ_NAMED(BD96801_LDO6_SHDN_ERR_STAT, "bd96801-ldo6-shdn-err"),
+
+ DEFINE_RES_IRQ_NAMED(BD96801_LDO7_PVIN_ERR_STAT, "bd96801-ldo7-pvin-err"),
+ DEFINE_RES_IRQ_NAMED(BD96801_LDO7_OVP_ERR_STAT, "bd96801-ldo7-ovp-err"),
+ DEFINE_RES_IRQ_NAMED(BD96801_LDO7_UVP_ERR_STAT, "bd96801-ldo7-uvp-err"),
+ DEFINE_RES_IRQ_NAMED(BD96801_LDO7_SHDN_ERR_STAT, "bd96801-ldo7-shdn-err"),
+};
+
+static const struct resource regulator_intb_irqs[] = {
+ DEFINE_RES_IRQ_NAMED(BD96801_TW_STAT, "bd96801-core-thermal"),
+
+ DEFINE_RES_IRQ_NAMED(BD96801_BUCK1_OCPH_STAT, "bd96801-buck1-overcurr-h"),
+ DEFINE_RES_IRQ_NAMED(BD96801_BUCK1_OCPL_STAT, "bd96801-buck1-overcurr-l"),
+ DEFINE_RES_IRQ_NAMED(BD96801_BUCK1_OCPN_STAT, "bd96801-buck1-overcurr-n"),
+ DEFINE_RES_IRQ_NAMED(BD96801_BUCK1_OVD_STAT, "bd96801-buck1-overvolt"),
+ DEFINE_RES_IRQ_NAMED(BD96801_BUCK1_UVD_STAT, "bd96801-buck1-undervolt"),
+ DEFINE_RES_IRQ_NAMED(BD96801_BUCK1_TW_CH_STAT, "bd96801-buck1-thermal"),
+
+ DEFINE_RES_IRQ_NAMED(BD96801_BUCK2_OCPH_STAT, "bd96801-buck2-overcurr-h"),
+ DEFINE_RES_IRQ_NAMED(BD96801_BUCK2_OCPL_STAT, "bd96801-buck2-overcurr-l"),
+ DEFINE_RES_IRQ_NAMED(BD96801_BUCK2_OCPN_STAT, "bd96801-buck2-overcurr-n"),
+ DEFINE_RES_IRQ_NAMED(BD96801_BUCK2_OVD_STAT, "bd96801-buck2-overvolt"),
+ DEFINE_RES_IRQ_NAMED(BD96801_BUCK2_UVD_STAT, "bd96801-buck2-undervolt"),
+ DEFINE_RES_IRQ_NAMED(BD96801_BUCK2_TW_CH_STAT, "bd96801-buck2-thermal"),
+
+ DEFINE_RES_IRQ_NAMED(BD96801_BUCK3_OCPH_STAT, "bd96801-buck3-overcurr-h"),
+ DEFINE_RES_IRQ_NAMED(BD96801_BUCK3_OCPL_STAT, "bd96801-buck3-overcurr-l"),
+ DEFINE_RES_IRQ_NAMED(BD96801_BUCK3_OCPN_STAT, "bd96801-buck3-overcurr-n"),
+ DEFINE_RES_IRQ_NAMED(BD96801_BUCK3_OVD_STAT, "bd96801-buck3-overvolt"),
+ DEFINE_RES_IRQ_NAMED(BD96801_BUCK3_UVD_STAT, "bd96801-buck3-undervolt"),
+ DEFINE_RES_IRQ_NAMED(BD96801_BUCK3_TW_CH_STAT, "bd96801-buck3-thermal"),
+
+ DEFINE_RES_IRQ_NAMED(BD96801_BUCK4_OCPH_STAT, "bd96801-buck4-overcurr-h"),
+ DEFINE_RES_IRQ_NAMED(BD96801_BUCK4_OCPL_STAT, "bd96801-buck4-overcurr-l"),
+ DEFINE_RES_IRQ_NAMED(BD96801_BUCK4_OCPN_STAT, "bd96801-buck4-overcurr-n"),
+ DEFINE_RES_IRQ_NAMED(BD96801_BUCK4_OVD_STAT, "bd96801-buck4-overvolt"),
+ DEFINE_RES_IRQ_NAMED(BD96801_BUCK4_UVD_STAT, "bd96801-buck4-undervolt"),
+ DEFINE_RES_IRQ_NAMED(BD96801_BUCK4_TW_CH_STAT, "bd96801-buck4-thermal"),
+
+ DEFINE_RES_IRQ_NAMED(BD96801_LDO5_OCPH_STAT, "bd96801-ldo5-overcurr"),
+ DEFINE_RES_IRQ_NAMED(BD96801_LDO5_OVD_STAT, "bd96801-ldo5-overvolt"),
+ DEFINE_RES_IRQ_NAMED(BD96801_LDO5_UVD_STAT, "bd96801-ldo5-undervolt"),
+
+ DEFINE_RES_IRQ_NAMED(BD96801_LDO6_OCPH_STAT, "bd96801-ldo6-overcurr"),
+ DEFINE_RES_IRQ_NAMED(BD96801_LDO6_OVD_STAT, "bd96801-ldo6-overvolt"),
+ DEFINE_RES_IRQ_NAMED(BD96801_LDO6_UVD_STAT, "bd96801-ldo6-undervolt"),
+
+ DEFINE_RES_IRQ_NAMED(BD96801_LDO7_OCPH_STAT, "bd96801-ldo7-overcurr"),
+ DEFINE_RES_IRQ_NAMED(BD96801_LDO7_OVD_STAT, "bd96801-ldo7-overvolt"),
+ DEFINE_RES_IRQ_NAMED(BD96801_LDO7_UVD_STAT, "bd96801-ldo7-undervolt"),
+};
+
+enum {
+ WDG_CELL = 0,
+ REGULATOR_CELL,
+};
+
+static struct mfd_cell bd96801_mfd_cells[] = {
+ [WDG_CELL] = { .name = "bd96801-wdt", },
+ [REGULATOR_CELL] = { .name = "bd96801-pmic", },
+};
+
+static const struct regmap_range bd96801_volatile_ranges[] = {
+ /* Status regs */
+ regmap_reg_range(BD96801_REG_WD_FEED, BD96801_REG_WD_FAILCOUNT),
+ regmap_reg_range(BD96801_REG_WD_ASK, BD96801_REG_WD_ASK),
+ regmap_reg_range(BD96801_REG_WD_STATUS, BD96801_REG_WD_STATUS),
+ regmap_reg_range(BD96801_REG_PMIC_STATE, BD96801_REG_INT_LDO7_INTB),
+ /* Registers which do not update value unless PMIC is in STBY */
+ regmap_reg_range(BD96801_REG_SSCG_CTRL, BD96801_REG_SHD_INTB),
+ regmap_reg_range(BD96801_REG_BUCK_OVP, BD96801_REG_BOOT_OVERTIME),
+ /*
+ * LDO control registers have single bit (LDO MODE) which does not
+ * change when we write it unless PMIC is in STBY. It's safer to not
+ * cache it.
+ */
+ regmap_reg_range(BD96801_LDO5_VOL_LVL_REG, BD96801_LDO7_VOL_LVL_REG),
+};
+
+static const struct regmap_access_table volatile_regs = {
+ .yes_ranges = bd96801_volatile_ranges,
+ .n_yes_ranges = ARRAY_SIZE(bd96801_volatile_ranges),
+};
+
+/*
+ * For ERRB we need main register bit mapping as bit(0) indicates active IRQ
+ * in one of the first 3 sub IRQ registers, For INTB we can use default 1 to 1
+ * mapping.
+ */
+static unsigned int bit0_offsets[] = {0, 1, 2}; /* System stat, 3 registers */
+static unsigned int bit1_offsets[] = {3}; /* Buck 1 stat */
+static unsigned int bit2_offsets[] = {4}; /* Buck 2 stat */
+static unsigned int bit3_offsets[] = {5}; /* Buck 3 stat */
+static unsigned int bit4_offsets[] = {6}; /* Buck 4 stat */
+static unsigned int bit5_offsets[] = {7}; /* LDO 5 stat */
+static unsigned int bit6_offsets[] = {8}; /* LDO 6 stat */
+static unsigned int bit7_offsets[] = {9}; /* LDO 7 stat */
+
+static struct regmap_irq_sub_irq_map errb_sub_irq_offsets[] = {
+ REGMAP_IRQ_MAIN_REG_OFFSET(bit0_offsets),
+ REGMAP_IRQ_MAIN_REG_OFFSET(bit1_offsets),
+ REGMAP_IRQ_MAIN_REG_OFFSET(bit2_offsets),
+ REGMAP_IRQ_MAIN_REG_OFFSET(bit3_offsets),
+ REGMAP_IRQ_MAIN_REG_OFFSET(bit4_offsets),
+ REGMAP_IRQ_MAIN_REG_OFFSET(bit5_offsets),
+ REGMAP_IRQ_MAIN_REG_OFFSET(bit6_offsets),
+ REGMAP_IRQ_MAIN_REG_OFFSET(bit7_offsets),
+};
+
+static const struct regmap_irq bd96801_errb_irqs[] = {
+ /* Reg 0x52 Fatal ERRB1 */
+ REGMAP_IRQ_REG(BD96801_OTP_ERR_STAT, 0, BD96801_OTP_ERR_MASK),
+ REGMAP_IRQ_REG(BD96801_DBIST_ERR_STAT, 0, BD96801_DBIST_ERR_MASK),
+ REGMAP_IRQ_REG(BD96801_EEP_ERR_STAT, 0, BD96801_EEP_ERR_MASK),
+ REGMAP_IRQ_REG(BD96801_ABIST_ERR_STAT, 0, BD96801_ABIST_ERR_MASK),
+ REGMAP_IRQ_REG(BD96801_PRSTB_ERR_STAT, 0, BD96801_PRSTB_ERR_MASK),
+ REGMAP_IRQ_REG(BD96801_DRMOS1_ERR_STAT, 0, BD96801_DRMOS1_ERR_MASK),
+ REGMAP_IRQ_REG(BD96801_DRMOS2_ERR_STAT, 0, BD96801_DRMOS2_ERR_MASK),
+ REGMAP_IRQ_REG(BD96801_SLAVE_ERR_STAT, 0, BD96801_SLAVE_ERR_MASK),
+ /* 0x53 Fatal ERRB2 */
+ REGMAP_IRQ_REG(BD96801_VREF_ERR_STAT, 1, BD96801_VREF_ERR_MASK),
+ REGMAP_IRQ_REG(BD96801_TSD_ERR_STAT, 1, BD96801_TSD_ERR_MASK),
+ REGMAP_IRQ_REG(BD96801_UVLO_ERR_STAT, 1, BD96801_UVLO_ERR_MASK),
+ REGMAP_IRQ_REG(BD96801_OVLO_ERR_STAT, 1, BD96801_OVLO_ERR_MASK),
+ REGMAP_IRQ_REG(BD96801_OSC_ERR_STAT, 1, BD96801_OSC_ERR_MASK),
+ REGMAP_IRQ_REG(BD96801_PON_ERR_STAT, 1, BD96801_PON_ERR_MASK),
+ REGMAP_IRQ_REG(BD96801_POFF_ERR_STAT, 1, BD96801_POFF_ERR_MASK),
+ REGMAP_IRQ_REG(BD96801_CMD_SHDN_ERR_STAT, 1, BD96801_CMD_SHDN_ERR_MASK),
+ /* 0x54 Fatal INTB shadowed to ERRB */
+ REGMAP_IRQ_REG(BD96801_INT_PRSTB_WDT_ERR, 2, BD96801_INT_PRSTB_WDT_ERR_MASK),
+ REGMAP_IRQ_REG(BD96801_INT_CHIP_IF_ERR, 2, BD96801_INT_CHIP_IF_ERR_MASK),
+ REGMAP_IRQ_REG(BD96801_INT_SHDN_ERR_STAT, 2, BD96801_INT_SHDN_ERR_MASK),
+ /* Reg 0x55 BUCK1 ERR IRQs */
+ REGMAP_IRQ_REG(BD96801_BUCK1_PVIN_ERR_STAT, 3, BD96801_OUT_PVIN_ERR_MASK),
+ REGMAP_IRQ_REG(BD96801_BUCK1_OVP_ERR_STAT, 3, BD96801_OUT_OVP_ERR_MASK),
+ REGMAP_IRQ_REG(BD96801_BUCK1_UVP_ERR_STAT, 3, BD96801_OUT_UVP_ERR_MASK),
+ REGMAP_IRQ_REG(BD96801_BUCK1_SHDN_ERR_STAT, 3, BD96801_OUT_SHDN_ERR_MASK),
+ /* Reg 0x56 BUCK2 ERR IRQs */
+ REGMAP_IRQ_REG(BD96801_BUCK2_PVIN_ERR_STAT, 4, BD96801_OUT_PVIN_ERR_MASK),
+ REGMAP_IRQ_REG(BD96801_BUCK2_OVP_ERR_STAT, 4, BD96801_OUT_OVP_ERR_MASK),
+ REGMAP_IRQ_REG(BD96801_BUCK2_UVP_ERR_STAT, 4, BD96801_OUT_UVP_ERR_MASK),
+ REGMAP_IRQ_REG(BD96801_BUCK2_SHDN_ERR_STAT, 4, BD96801_OUT_SHDN_ERR_MASK),
+ /* Reg 0x57 BUCK3 ERR IRQs */
+ REGMAP_IRQ_REG(BD96801_BUCK3_PVIN_ERR_STAT, 5, BD96801_OUT_PVIN_ERR_MASK),
+ REGMAP_IRQ_REG(BD96801_BUCK3_OVP_ERR_STAT, 5, BD96801_OUT_OVP_ERR_MASK),
+ REGMAP_IRQ_REG(BD96801_BUCK3_UVP_ERR_STAT, 5, BD96801_OUT_UVP_ERR_MASK),
+ REGMAP_IRQ_REG(BD96801_BUCK3_SHDN_ERR_STAT, 5, BD96801_OUT_SHDN_ERR_MASK),
+ /* Reg 0x58 BUCK4 ERR IRQs */
+ REGMAP_IRQ_REG(BD96801_BUCK4_PVIN_ERR_STAT, 6, BD96801_OUT_PVIN_ERR_MASK),
+ REGMAP_IRQ_REG(BD96801_BUCK4_OVP_ERR_STAT, 6, BD96801_OUT_OVP_ERR_MASK),
+ REGMAP_IRQ_REG(BD96801_BUCK4_UVP_ERR_STAT, 6, BD96801_OUT_UVP_ERR_MASK),
+ REGMAP_IRQ_REG(BD96801_BUCK4_SHDN_ERR_STAT, 6, BD96801_OUT_SHDN_ERR_MASK),
+ /* Reg 0x59 LDO5 ERR IRQs */
+ REGMAP_IRQ_REG(BD96801_LDO5_PVIN_ERR_STAT, 7, BD96801_OUT_PVIN_ERR_MASK),
+ REGMAP_IRQ_REG(BD96801_LDO5_OVP_ERR_STAT, 7, BD96801_OUT_OVP_ERR_MASK),
+ REGMAP_IRQ_REG(BD96801_LDO5_UVP_ERR_STAT, 7, BD96801_OUT_UVP_ERR_MASK),
+ REGMAP_IRQ_REG(BD96801_LDO5_SHDN_ERR_STAT, 7, BD96801_OUT_SHDN_ERR_MASK),
+ /* Reg 0x5a LDO6 ERR IRQs */
+ REGMAP_IRQ_REG(BD96801_LDO6_PVIN_ERR_STAT, 8, BD96801_OUT_PVIN_ERR_MASK),
+ REGMAP_IRQ_REG(BD96801_LDO6_OVP_ERR_STAT, 8, BD96801_OUT_OVP_ERR_MASK),
+ REGMAP_IRQ_REG(BD96801_LDO6_UVP_ERR_STAT, 8, BD96801_OUT_UVP_ERR_MASK),
+ REGMAP_IRQ_REG(BD96801_LDO6_SHDN_ERR_STAT, 8, BD96801_OUT_SHDN_ERR_MASK),
+ /* Reg 0x5b LDO7 ERR IRQs */
+ REGMAP_IRQ_REG(BD96801_LDO7_PVIN_ERR_STAT, 9, BD96801_OUT_PVIN_ERR_MASK),
+ REGMAP_IRQ_REG(BD96801_LDO7_OVP_ERR_STAT, 9, BD96801_OUT_OVP_ERR_MASK),
+ REGMAP_IRQ_REG(BD96801_LDO7_UVP_ERR_STAT, 9, BD96801_OUT_UVP_ERR_MASK),
+ REGMAP_IRQ_REG(BD96801_LDO7_SHDN_ERR_STAT, 9, BD96801_OUT_SHDN_ERR_MASK),
+};
+
+static const struct regmap_irq bd96801_intb_irqs[] = {
+ /* STATUS SYSTEM INTB */
+ REGMAP_IRQ_REG(BD96801_TW_STAT, 0, BD96801_TW_STAT_MASK),
+ REGMAP_IRQ_REG(BD96801_WDT_ERR_STAT, 0, BD96801_WDT_ERR_STAT_MASK),
+ REGMAP_IRQ_REG(BD96801_I2C_ERR_STAT, 0, BD96801_I2C_ERR_STAT_MASK),
+ REGMAP_IRQ_REG(BD96801_CHIP_IF_ERR_STAT, 0, BD96801_CHIP_IF_ERR_STAT_MASK),
+ /* STATUS BUCK1 INTB */
+ REGMAP_IRQ_REG(BD96801_BUCK1_OCPH_STAT, 1, BD96801_BUCK_OCPH_STAT_MASK),
+ REGMAP_IRQ_REG(BD96801_BUCK1_OCPL_STAT, 1, BD96801_BUCK_OCPL_STAT_MASK),
+ REGMAP_IRQ_REG(BD96801_BUCK1_OCPN_STAT, 1, BD96801_BUCK_OCPN_STAT_MASK),
+ REGMAP_IRQ_REG(BD96801_BUCK1_OVD_STAT, 1, BD96801_BUCK_OVD_STAT_MASK),
+ REGMAP_IRQ_REG(BD96801_BUCK1_UVD_STAT, 1, BD96801_BUCK_UVD_STAT_MASK),
+ REGMAP_IRQ_REG(BD96801_BUCK1_TW_CH_STAT, 1, BD96801_BUCK_TW_CH_STAT_MASK),
+ /* BUCK 2 INTB */
+ REGMAP_IRQ_REG(BD96801_BUCK2_OCPH_STAT, 2, BD96801_BUCK_OCPH_STAT_MASK),
+ REGMAP_IRQ_REG(BD96801_BUCK2_OCPL_STAT, 2, BD96801_BUCK_OCPL_STAT_MASK),
+ REGMAP_IRQ_REG(BD96801_BUCK2_OCPN_STAT, 2, BD96801_BUCK_OCPN_STAT_MASK),
+ REGMAP_IRQ_REG(BD96801_BUCK2_OVD_STAT, 2, BD96801_BUCK_OVD_STAT_MASK),
+ REGMAP_IRQ_REG(BD96801_BUCK2_UVD_STAT, 2, BD96801_BUCK_UVD_STAT_MASK),
+ REGMAP_IRQ_REG(BD96801_BUCK2_TW_CH_STAT, 2, BD96801_BUCK_TW_CH_STAT_MASK),
+ /* BUCK 3 INTB */
+ REGMAP_IRQ_REG(BD96801_BUCK3_OCPH_STAT, 3, BD96801_BUCK_OCPH_STAT_MASK),
+ REGMAP_IRQ_REG(BD96801_BUCK3_OCPL_STAT, 3, BD96801_BUCK_OCPL_STAT_MASK),
+ REGMAP_IRQ_REG(BD96801_BUCK3_OCPN_STAT, 3, BD96801_BUCK_OCPN_STAT_MASK),
+ REGMAP_IRQ_REG(BD96801_BUCK3_OVD_STAT, 3, BD96801_BUCK_OVD_STAT_MASK),
+ REGMAP_IRQ_REG(BD96801_BUCK3_UVD_STAT, 3, BD96801_BUCK_UVD_STAT_MASK),
+ REGMAP_IRQ_REG(BD96801_BUCK3_TW_CH_STAT, 3, BD96801_BUCK_TW_CH_STAT_MASK),
+ /* BUCK 4 INTB */
+ REGMAP_IRQ_REG(BD96801_BUCK4_OCPH_STAT, 4, BD96801_BUCK_OCPH_STAT_MASK),
+ REGMAP_IRQ_REG(BD96801_BUCK4_OCPL_STAT, 4, BD96801_BUCK_OCPL_STAT_MASK),
+ REGMAP_IRQ_REG(BD96801_BUCK4_OCPN_STAT, 4, BD96801_BUCK_OCPN_STAT_MASK),
+ REGMAP_IRQ_REG(BD96801_BUCK4_OVD_STAT, 4, BD96801_BUCK_OVD_STAT_MASK),
+ REGMAP_IRQ_REG(BD96801_BUCK4_UVD_STAT, 4, BD96801_BUCK_UVD_STAT_MASK),
+ REGMAP_IRQ_REG(BD96801_BUCK4_TW_CH_STAT, 4, BD96801_BUCK_TW_CH_STAT_MASK),
+ /* LDO5 INTB */
+ REGMAP_IRQ_REG(BD96801_LDO5_OCPH_STAT, 5, BD96801_LDO_OCPH_STAT_MASK),
+ REGMAP_IRQ_REG(BD96801_LDO5_OVD_STAT, 5, BD96801_LDO_OVD_STAT_MASK),
+ REGMAP_IRQ_REG(BD96801_LDO5_UVD_STAT, 5, BD96801_LDO_UVD_STAT_MASK),
+ /* LDO6 INTB */
+ REGMAP_IRQ_REG(BD96801_LDO6_OCPH_STAT, 6, BD96801_LDO_OCPH_STAT_MASK),
+ REGMAP_IRQ_REG(BD96801_LDO6_OVD_STAT, 6, BD96801_LDO_OVD_STAT_MASK),
+ REGMAP_IRQ_REG(BD96801_LDO6_UVD_STAT, 6, BD96801_LDO_UVD_STAT_MASK),
+ /* LDO7 INTB */
+ REGMAP_IRQ_REG(BD96801_LDO7_OCPH_STAT, 7, BD96801_LDO_OCPH_STAT_MASK),
+ REGMAP_IRQ_REG(BD96801_LDO7_OVD_STAT, 7, BD96801_LDO_OVD_STAT_MASK),
+ REGMAP_IRQ_REG(BD96801_LDO7_UVD_STAT, 7, BD96801_LDO_UVD_STAT_MASK),
+};
+
+static struct regmap_irq_chip bd96801_irq_chip_errb = {
+ .name = "bd96801-irq-errb",
+ .main_status = BD96801_REG_INT_MAIN,
+ .num_main_regs = 1,
+ .irqs = &bd96801_errb_irqs[0],
+ .num_irqs = ARRAY_SIZE(bd96801_errb_irqs),
+ .status_base = BD96801_REG_INT_SYS_ERRB1,
+ .mask_base = BD96801_REG_MASK_SYS_ERRB,
+ .ack_base = BD96801_REG_INT_SYS_ERRB1,
+ .init_ack_masked = true,
+ .num_regs = 10,
+ .irq_reg_stride = 1,
+ .sub_reg_offsets = &errb_sub_irq_offsets[0],
+};
+
+static struct regmap_irq_chip bd96801_irq_chip_intb = {
+ .name = "bd96801-irq-intb",
+ .main_status = BD96801_REG_INT_MAIN,
+ .num_main_regs = 1,
+ .irqs = &bd96801_intb_irqs[0],
+ .num_irqs = ARRAY_SIZE(bd96801_intb_irqs),
+ .status_base = BD96801_REG_INT_SYS_INTB,
+ .mask_base = BD96801_REG_MASK_SYS_INTB,
+ .ack_base = BD96801_REG_INT_SYS_INTB,
+ .init_ack_masked = true,
+ .num_regs = 8,
+ .irq_reg_stride = 1,
+};
+
+static const struct regmap_config bd96801_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .volatile_table = &volatile_regs,
+ .cache_type = REGCACHE_RBTREE,
+};
+
+static int bd96801_i2c_probe(struct i2c_client *i2c)
+{
+ int i, ret, intb_irq, errb_irq, num_regu_irqs, num_intb, num_errb = 0;
+ int wdg_irq_no;
+ struct regmap_irq_chip_data *intb_irq_data, *errb_irq_data;
+ struct irq_domain *intb_domain, *errb_domain;
+ struct resource wdg_irq;
+ const struct fwnode_handle *fwnode;
+ struct resource *regulator_res;
+ struct regmap *regmap;
+
+ fwnode = dev_fwnode(&i2c->dev);
+ if (!fwnode)
+ return dev_err_probe(&i2c->dev, -EINVAL, "no fwnode\n");
+
+ intb_irq = fwnode_irq_get_byname(fwnode, "intb");
+ if (intb_irq < 0)
+ return dev_err_probe(&i2c->dev, intb_irq, "No INTB IRQ configured\n");
+
+ num_intb = ARRAY_SIZE(regulator_intb_irqs);
+
+ /* ERRB may be omitted if processor is powered by the PMIC */
+ errb_irq = fwnode_irq_get_byname(fwnode, "errb");
+ if (errb_irq < 0)
+ errb_irq = 0;
+
+ if (errb_irq)
+ num_errb = ARRAY_SIZE(regulator_errb_irqs);
+
+ num_regu_irqs = num_intb + num_errb;
+
+ regulator_res = kcalloc(num_regu_irqs, sizeof(*regulator_res), GFP_KERNEL);
+ if (!regulator_res)
+ return -ENOMEM;
+
+ regmap = devm_regmap_init_i2c(i2c, &bd96801_regmap_config);
+ if (IS_ERR(regmap)) {
+ ret = dev_err_probe(&i2c->dev, PTR_ERR(regmap),
+ "regmap initialization failed\n");
+ goto free_out;
+ }
+
+ ret = devm_regmap_add_irq_chip(&i2c->dev, regmap, intb_irq,
+ IRQF_ONESHOT, 0, &bd96801_irq_chip_intb,
+ &intb_irq_data);
+ if (ret) {
+ dev_err_probe(&i2c->dev, ret, "Failed to add INTB irq_chip\n");
+ goto free_out;
+ }
+
+ intb_domain = regmap_irq_get_domain(intb_irq_data);
+ /*
+ * Here we create two regmap IRQ controllers because the device
+ * provides two physical IRQ lines, each providing various IRQs for
+ * which the reasons can be read from the status registers. The
+ * preferred approach is to have own regmap IRQ controller / HWIRQ.
+ * The regmap IRQ creates an IRQ domain for each controller. This ends
+ * up creating two IRQ domains with the same name - because
+ * the name is derived from the device-tree node. Domain names are
+ * used to create debugfs entries, so we would end up with debugFS name
+ * collision when two IRQ domains are created for single device.
+ * Workaround here is to use the irq_domain_update_bus_token() which
+ * renames the IRQ domain by appending '-1' at the end of the domain
+ * name.
+ *
+ * FIXME: We should either find a way to create an IRQ domain with a
+ * specific name, and allow regmap IRQ to utilize this, or allow
+ * regmap IRQ controller to have multiple HWIRQs.
+ */
+ irq_domain_update_bus_token(intb_domain, DOMAIN_BUS_WIRED);
+
+ /*
+ * MFD core code is built to handle only one IRQ domain. BD96801
+ * has two domains so we do IRQ mapping here and provide the
+ * already mapped IRQ numbers to sub-devices.
+ */
+ for (i = 0; i < num_intb; i++) {
+ struct resource *res = ®ulator_res[i];
+
+ *res = regulator_intb_irqs[i];
+ res->start = res->end = irq_create_mapping(intb_domain,
+ res->start);
+ }
+
+ wdg_irq_no = irq_create_mapping(intb_domain, BD96801_WDT_ERR_STAT);
+ wdg_irq = DEFINE_RES_IRQ_NAMED(wdg_irq_no, "bd96801-wdg");
+ bd96801_mfd_cells[WDG_CELL].resources = &wdg_irq;
+ bd96801_mfd_cells[WDG_CELL].num_resources = 1;
+
+ if (num_errb) {
+ ret = devm_regmap_add_irq_chip(&i2c->dev, regmap, errb_irq,
+ IRQF_ONESHOT, 0,
+ &bd96801_irq_chip_errb,
+ &errb_irq_data);
+ if (ret) {
+ dev_err_probe(&i2c->dev, ret,
+ "Failed to add ERRB (%d) irq_chip\n",
+ errb_irq);
+ goto free_out;
+ }
+ errb_domain = regmap_irq_get_domain(errb_irq_data);
+
+ for (i = 0; i < num_errb; i++) {
+ struct resource *res = ®ulator_res[num_intb + i];
+
+ *res = regulator_errb_irqs[i];
+ res->start = res->end = irq_create_mapping(errb_domain,
+ res->start);
+ }
+ }
+
+ bd96801_mfd_cells[REGULATOR_CELL].resources = regulator_res;
+ bd96801_mfd_cells[REGULATOR_CELL].num_resources = num_regu_irqs;
+
+ ret = devm_mfd_add_devices(&i2c->dev, PLATFORM_DEVID_AUTO, bd96801_mfd_cells,
+ ARRAY_SIZE(bd96801_mfd_cells), NULL, 0, NULL);
+ if (ret)
+ dev_err_probe(&i2c->dev, ret, "Failed to create subdevices\n");
+
+free_out:
+ kfree(regulator_res);
+
+ return ret;
+}
+
+static const struct of_device_id bd96801_of_match[] = {
+ { .compatible = "rohm,bd96801", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, bd96801_of_match);
+
+static struct i2c_driver bd96801_i2c_driver = {
+ .driver = {
+ .name = "rohm-bd96801",
+ .of_match_table = bd96801_of_match,
+ },
+ .probe = bd96801_i2c_probe,
+};
+
+static int __init bd96801_i2c_init(void)
+{
+ return i2c_add_driver(&bd96801_i2c_driver);
+}
+
+/* Initialise early so consumer devices can complete system boot */
+subsys_initcall(bd96801_i2c_init);
+
+static void __exit bd96801_i2c_exit(void)
+{
+ i2c_del_driver(&bd96801_i2c_driver);
+}
+module_exit(bd96801_i2c_exit);
+
+MODULE_AUTHOR("Matti Vaittinen <[email protected]>");
+MODULE_DESCRIPTION("ROHM BD96801 Power Management IC driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/mfd/rohm-bd96801.h b/include/linux/mfd/rohm-bd96801.h
new file mode 100644
index 000000000000..26538783e7d5
--- /dev/null
+++ b/include/linux/mfd/rohm-bd96801.h
@@ -0,0 +1,211 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2024 ROHM Semiconductors */
+
+#ifndef __MFD_BD96801_H__
+#define __MFD_BD96801_H__
+
+#define BD96801_REG_SSCG_CTRL 0x09
+#define BD96801_REG_SHD_INTB 0x20
+#define BD96801_LDO5_VOL_LVL_REG 0x2c
+#define BD96801_LDO6_VOL_LVL_REG 0x2d
+#define BD96801_LDO7_VOL_LVL_REG 0x2e
+#define BD96801_REG_BUCK_OVP 0x30
+#define BD96801_REG_BUCK_OVD 0x35
+#define BD96801_REG_LDO_OVP 0x31
+#define BD96801_REG_LDO_OVD 0x36
+#define BD96801_REG_BOOT_OVERTIME 0x3a
+#define BD96801_REG_WD_TMO 0x40
+#define BD96801_REG_WD_CONF 0x41
+#define BD96801_REG_WD_FEED 0x42
+#define BD96801_REG_WD_FAILCOUNT 0x43
+#define BD96801_REG_WD_ASK 0x46
+#define BD96801_REG_WD_STATUS 0x4a
+#define BD96801_REG_PMIC_STATE 0x4f
+#define BD96801_REG_EXT_STATE 0x50
+
+#define BD96801_STATE_STBY 0x09
+
+/* IRQ register area */
+#define BD96801_REG_INT_MAIN 0x51
+
+/*
+ * The BD96801 has two physical IRQ lines, INTB and ERRB.
+ *
+ * The 'main status register' is located at 0x51.
+ * The ERRB status registers are located at 0x52 ... 0x5B
+ * INTB status registers are at range 0x5c ... 0x63
+ */
+#define BD96801_REG_INT_SYS_ERRB1 0x52
+#define BD96801_REG_INT_SYS_INTB 0x5c
+#define BD96801_REG_INT_LDO7_INTB 0x63
+
+/* MASK registers */
+#define BD96801_REG_MASK_SYS_INTB 0x73
+#define BD96801_REG_MASK_SYS_ERRB 0x69
+
+#define BD96801_MAX_REGISTER 0x7a
+
+#define BD96801_OTP_ERR_MASK BIT(0)
+#define BD96801_DBIST_ERR_MASK BIT(1)
+#define BD96801_EEP_ERR_MASK BIT(2)
+#define BD96801_ABIST_ERR_MASK BIT(3)
+#define BD96801_PRSTB_ERR_MASK BIT(4)
+#define BD96801_DRMOS1_ERR_MASK BIT(5)
+#define BD96801_DRMOS2_ERR_MASK BIT(6)
+#define BD96801_SLAVE_ERR_MASK BIT(7)
+#define BD96801_VREF_ERR_MASK BIT(0)
+#define BD96801_TSD_ERR_MASK BIT(1)
+#define BD96801_UVLO_ERR_MASK BIT(2)
+#define BD96801_OVLO_ERR_MASK BIT(3)
+#define BD96801_OSC_ERR_MASK BIT(4)
+#define BD96801_PON_ERR_MASK BIT(5)
+#define BD96801_POFF_ERR_MASK BIT(6)
+#define BD96801_CMD_SHDN_ERR_MASK BIT(7)
+#define BD96801_INT_PRSTB_WDT_ERR_MASK BIT(0)
+#define BD96801_INT_CHIP_IF_ERR_MASK BIT(3)
+#define BD96801_INT_SHDN_ERR_MASK BIT(7)
+#define BD96801_OUT_PVIN_ERR_MASK BIT(0)
+#define BD96801_OUT_OVP_ERR_MASK BIT(1)
+#define BD96801_OUT_UVP_ERR_MASK BIT(2)
+#define BD96801_OUT_SHDN_ERR_MASK BIT(7)
+
+/* ERRB IRQs */
+enum {
+ /* Reg 0x52, 0x53, 0x54 - ERRB system IRQs */
+ BD96801_OTP_ERR_STAT,
+ BD96801_DBIST_ERR_STAT,
+ BD96801_EEP_ERR_STAT,
+ BD96801_ABIST_ERR_STAT,
+ BD96801_PRSTB_ERR_STAT,
+ BD96801_DRMOS1_ERR_STAT,
+ BD96801_DRMOS2_ERR_STAT,
+ BD96801_SLAVE_ERR_STAT,
+ BD96801_VREF_ERR_STAT,
+ BD96801_TSD_ERR_STAT,
+ BD96801_UVLO_ERR_STAT,
+ BD96801_OVLO_ERR_STAT,
+ BD96801_OSC_ERR_STAT,
+ BD96801_PON_ERR_STAT,
+ BD96801_POFF_ERR_STAT,
+ BD96801_CMD_SHDN_ERR_STAT,
+ BD96801_INT_PRSTB_WDT_ERR,
+ BD96801_INT_CHIP_IF_ERR,
+ BD96801_INT_SHDN_ERR_STAT,
+
+ /* Reg 0x55 BUCK1 ERR IRQs */
+ BD96801_BUCK1_PVIN_ERR_STAT,
+ BD96801_BUCK1_OVP_ERR_STAT,
+ BD96801_BUCK1_UVP_ERR_STAT,
+ BD96801_BUCK1_SHDN_ERR_STAT,
+
+ /* Reg 0x56 BUCK2 ERR IRQs */
+ BD96801_BUCK2_PVIN_ERR_STAT,
+ BD96801_BUCK2_OVP_ERR_STAT,
+ BD96801_BUCK2_UVP_ERR_STAT,
+ BD96801_BUCK2_SHDN_ERR_STAT,
+
+ /* Reg 0x57 BUCK3 ERR IRQs */
+ BD96801_BUCK3_PVIN_ERR_STAT,
+ BD96801_BUCK3_OVP_ERR_STAT,
+ BD96801_BUCK3_UVP_ERR_STAT,
+ BD96801_BUCK3_SHDN_ERR_STAT,
+
+ /* Reg 0x58 BUCK4 ERR IRQs */
+ BD96801_BUCK4_PVIN_ERR_STAT,
+ BD96801_BUCK4_OVP_ERR_STAT,
+ BD96801_BUCK4_UVP_ERR_STAT,
+ BD96801_BUCK4_SHDN_ERR_STAT,
+
+ /* Reg 0x59 LDO5 ERR IRQs */
+ BD96801_LDO5_PVIN_ERR_STAT,
+ BD96801_LDO5_OVP_ERR_STAT,
+ BD96801_LDO5_UVP_ERR_STAT,
+ BD96801_LDO5_SHDN_ERR_STAT,
+
+ /* Reg 0x5a LDO6 ERR IRQs */
+ BD96801_LDO6_PVIN_ERR_STAT,
+ BD96801_LDO6_OVP_ERR_STAT,
+ BD96801_LDO6_UVP_ERR_STAT,
+ BD96801_LDO6_SHDN_ERR_STAT,
+
+ /* Reg 0x5b LDO7 ERR IRQs */
+ BD96801_LDO7_PVIN_ERR_STAT,
+ BD96801_LDO7_OVP_ERR_STAT,
+ BD96801_LDO7_UVP_ERR_STAT,
+ BD96801_LDO7_SHDN_ERR_STAT,
+};
+
+/* INTB IRQs */
+enum {
+ /* Reg 0x5c (System INTB) */
+ BD96801_TW_STAT,
+ BD96801_WDT_ERR_STAT,
+ BD96801_I2C_ERR_STAT,
+ BD96801_CHIP_IF_ERR_STAT,
+
+ /* Reg 0x5d (BUCK1 INTB) */
+ BD96801_BUCK1_OCPH_STAT,
+ BD96801_BUCK1_OCPL_STAT,
+ BD96801_BUCK1_OCPN_STAT,
+ BD96801_BUCK1_OVD_STAT,
+ BD96801_BUCK1_UVD_STAT,
+ BD96801_BUCK1_TW_CH_STAT,
+
+ /* Reg 0x5e (BUCK2 INTB) */
+ BD96801_BUCK2_OCPH_STAT,
+ BD96801_BUCK2_OCPL_STAT,
+ BD96801_BUCK2_OCPN_STAT,
+ BD96801_BUCK2_OVD_STAT,
+ BD96801_BUCK2_UVD_STAT,
+ BD96801_BUCK2_TW_CH_STAT,
+
+ /* Reg 0x5f (BUCK3 INTB)*/
+ BD96801_BUCK3_OCPH_STAT,
+ BD96801_BUCK3_OCPL_STAT,
+ BD96801_BUCK3_OCPN_STAT,
+ BD96801_BUCK3_OVD_STAT,
+ BD96801_BUCK3_UVD_STAT,
+ BD96801_BUCK3_TW_CH_STAT,
+
+ /* Reg 0x60 (BUCK4 INTB)*/
+ BD96801_BUCK4_OCPH_STAT,
+ BD96801_BUCK4_OCPL_STAT,
+ BD96801_BUCK4_OCPN_STAT,
+ BD96801_BUCK4_OVD_STAT,
+ BD96801_BUCK4_UVD_STAT,
+ BD96801_BUCK4_TW_CH_STAT,
+
+ /* Reg 0x61 (LDO5 INTB) */
+ BD96801_LDO5_OCPH_STAT, //bit [0]
+ BD96801_LDO5_OVD_STAT, //bit [3]
+ BD96801_LDO5_UVD_STAT, //bit [4]
+
+ /* Reg 0x62 (LDO6 INTB) */
+ BD96801_LDO6_OCPH_STAT, //bit [0]
+ BD96801_LDO6_OVD_STAT, //bit [3]
+ BD96801_LDO6_UVD_STAT, //bit [4]
+
+ /* Reg 0x63 (LDO7 INTB) */
+ BD96801_LDO7_OCPH_STAT, //bit [0]
+ BD96801_LDO7_OVD_STAT, //bit [3]
+ BD96801_LDO7_UVD_STAT, //bit [4]
+};
+
+/* IRQ MASKs */
+#define BD96801_TW_STAT_MASK BIT(0)
+#define BD96801_WDT_ERR_STAT_MASK BIT(1)
+#define BD96801_I2C_ERR_STAT_MASK BIT(2)
+#define BD96801_CHIP_IF_ERR_STAT_MASK BIT(3)
+
+#define BD96801_BUCK_OCPH_STAT_MASK BIT(0)
+#define BD96801_BUCK_OCPL_STAT_MASK BIT(1)
+#define BD96801_BUCK_OCPN_STAT_MASK BIT(2)
+#define BD96801_BUCK_OVD_STAT_MASK BIT(3)
+#define BD96801_BUCK_UVD_STAT_MASK BIT(4)
+#define BD96801_BUCK_TW_CH_STAT_MASK BIT(5)
+
+#define BD96801_LDO_OCPH_STAT_MASK BIT(0)
+#define BD96801_LDO_OVD_STAT_MASK BIT(3)
+#define BD96801_LDO_UVD_STAT_MASK BIT(4)
+
+#endif
diff --git a/include/linux/mfd/rohm-generic.h b/include/linux/mfd/rohm-generic.h
index 4eeb22876bad..e7d4e6afe388 100644
--- a/include/linux/mfd/rohm-generic.h
+++ b/include/linux/mfd/rohm-generic.h
@@ -16,6 +16,7 @@ enum rohm_chip_type {
ROHM_CHIP_TYPE_BD71828,
ROHM_CHIP_TYPE_BD71837,
ROHM_CHIP_TYPE_BD71847,
+ ROHM_CHIP_TYPE_BD96801,
ROHM_CHIP_TYPE_AMOUNT
};
--
2.43.2
--
Matti Vaittinen, Linux device drivers
ROHM Semiconductors, Finland SWDC
Kiviharjunlenkki 1E
90220 OULU
FINLAND
~~~ "I don't think so," said Rene Descartes. Just then he vanished ~~~
Simon says - in Latin please.
~~~ "non cogito me" dixit Rene Descarte, deinde evanescavit ~~~
Thanks to Simon Glass for the translation =]
The ROHM BD96801 "Scalable PMIC" is an automotive grade PMIC which can
scale to different applications by allowing chaining of PMICs. The PMIC
also supports various protection features which can be configured either
to fire IRQs - or to shut down power outputs when failure is detected.
The driver supports the basic control of BUCKs and LDOs and configuring
Over/Under-voltage, over-current, over-temperature protections via
device-tree. Following constrains are worth noting:
The voltages can be enabled/disabled and protections configured only
when the PMIC is in STANDBY state.
The protections can't be disabled. UVP limit can't be configured.
OVP and OCP limits can be configured.
OVD and UVD for a regulator have common limit.
OCP and thermal protections are implemented by turning all INTB
notifications fatal. This means that OVD/UVD can not be used together
with OCP and thermal protection.
Thermal limits can't be configured. TSD is fixed to 175 Celsius, and
thermal warning is fixed close to 140 Celsius.
LDO's do not have own temperature monitor. The LDO limits use PMIC
core temperature.
Signed-off-by: Matti Vaittinen <[email protected]>
---
Revision history:
RFCv1 => RFCv2:
- Use devm_kcalloc() instead of devm_kzalloc() where appropriate
- Use devm_kmemdup()
- Add MODULE_DEVICE_TABLE
- drop MODULE_ALIAS
This patch requires the regulator_irq_helper() IRQ name duplication
patch which is already in regulator tree 'for-6.9' -branch.
https://git.kernel.org/pub/scm/linux/kernel/git/broonie/regulator.git/commit/?h=for-6.9&id=7ab681ddedd4b6dd2b047c74af95221c5f827e1d
Note. This patch implements controls which can only be done when the
BD96801 PMIC is in a 'standby' state. These controls include the
protection settings and 'initial' voltage settings.
The PMIC has a STBY pin which is used to control the PMIC power-state.
I don't have visibility to the design process or to most of the customer
use cases - so following is speculation:
- Original idea seems to have been that, when the PMIC goes to STBY
state, controlling processor will be powered-off. In the original DS1
version all power outputs from the PMIC were turned off when PMIC
entered STBY.
- Someone somewhere needed to be able to keep the controlling processor
and some peripherials powered on when the PMIC is in STBY. A very
potential reason is to be able to do the initial voltage / protection
configurations by software. A configuration option to keep desired
power outputs enabled when PMIC is in STBY was added.
- Current PMIC state can be read from the PMIC registers.
Obvious(?) dilemma for these 'STBY only' configurations is that it is
difficult to design generic way for the PMIC driver to synchronize the
configurations with the state changes. Designing it would at least require
knowledge on:
- What is the PMIC STBY-state used for? Is it intended to be a
power-off (or other low-power) state for the system? Or, is it
intended to be used just a state where some of the configurations are
done?
- How is the hardware designed to control the STBY pin?
We *could* assume the STBY pin is controlled by a GPIO. We could grab
the GPIO here, and export functions to toggle the STBY state. We could
then 'lock' the state for the duration of config changes. But problem
is, I don't know what is the right thing to do.
So, this version of the patch just checks the PMIC state - and attempts
to write the configs if PMIC is in STBY state. This is racy unless the
entity controlling the STBY-pin is somehow (external to this driver)
synchronized. We could send a notification when configurations are
completed to help building such synchronization but this is not done as
I don't have a real-world user for it.
I am unsure if these 'STBY-only' controls should be just dropped, or if
they should be kept here to serve as an example / basis to build on. If
they are removed, it will be very hard for anyone using this PMIC to
implement those controls. OTOH, if they are kept here, it may
potentially encourage 'works on my machine' designs - although, I
believe that those who really need to do 'STBY-only' configs at run-time
will find a way to toggle the STBY-pin in a way that is synchronized
with the configs here.
---
drivers/regulator/Kconfig | 12 +
drivers/regulator/Makefile | 2 +
drivers/regulator/bd96801-regulator.c | 2114 +++++++++++++++++++++++++
3 files changed, 2128 insertions(+)
create mode 100644 drivers/regulator/bd96801-regulator.c
diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index 7db0a29b5b8d..2e8187a92952 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -268,6 +268,18 @@ config REGULATOR_BD957XMUF
This driver can also be built as a module. If so, the module
will be called bd9576-regulator.
+config REGULATOR_BD96801
+ tristate "ROHM BD96801 Power Regulator"
+ depends on MFD_ROHM_BD96801
+ select REGULATOR_ROHM
+ help
+ This driver supports voltage regulators on ROHM BD96801 PMIC.
+ This will enable support for the software controllable buck
+ and LDO regulators.
+
+ This driver can also be built as a module. If so, the module
+ will be called bd96801-regulator.
+
config REGULATOR_CPCAP
tristate "Motorola CPCAP regulator"
depends on MFD_CPCAP
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index 46fb569e6be8..ae8896e879d5 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -37,6 +37,8 @@ obj-$(CONFIG_REGULATOR_BD718XX) += bd718x7-regulator.o
obj-$(CONFIG_REGULATOR_BD9571MWV) += bd9571mwv-regulator.o
obj-$(CONFIG_REGULATOR_BD957XMUF) += bd9576-regulator.o
obj-$(CONFIG_REGULATOR_DA903X) += da903x-regulator.o
+obj-$(CONFIG_REGULATOR_BD96801) += bd96801-regulator.o
+obj-$(CONFIG_REGULATOR_DA903X) += da903x.o
obj-$(CONFIG_REGULATOR_DA9052) += da9052-regulator.o
obj-$(CONFIG_REGULATOR_DA9055) += da9055-regulator.o
obj-$(CONFIG_REGULATOR_DA9062) += da9062-regulator.o
diff --git a/drivers/regulator/bd96801-regulator.c b/drivers/regulator/bd96801-regulator.c
new file mode 100644
index 000000000000..6a43a4d1f349
--- /dev/null
+++ b/drivers/regulator/bd96801-regulator.c
@@ -0,0 +1,2114 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (C) 2022 ROHM Semiconductors
+// bd96801-regulator.c ROHM BD96801 regulator driver
+
+/*
+ * The DS2 sample does not allow controlling much of anything besides the BUCK
+ * voltage tune value unless the PMIC is in STANDBY. This means the usual case
+ * where PMIC is controlled using processor which is powered by the PMIC does
+ * not allow much of control. Eg, enable/disable status or protection limits
+ * can't be set.
+ *
+ * It is however possible that the PMIC is controlled (at least partially) from
+ * some supervisor processor which stays alive even when PMIC goes to STANDBY.
+ *
+ * This calls for following actions:
+ * - add STANDBY check also to protection setting.
+ * - consider whether the ERRB IRQ would be worth handling
+ *
+ * Right. The demand for keeping processor alive when PMIC is configured has
+ * emerged. The DS3 solves problem by allowing SW to specify power-rails which
+ * are kept powered when the PMIC goes to STANDBY. Eg, SW can (at least in
+ * theory)
+ *
+ * - Configure critical power-rails to be enabled at STANDBY
+ * - Switch PMIC to STANDBY mode
+ * - Perform the configuration
+ * - Turn the PMIC back to the ACTIVE mode.
+ *
+ * Toggling the STANDBY mode from a regulator driver does definitely not sound like
+ * "the right thing to do". That should probably be initiated by early boot
+ * - or if it is required at later stage, then maybe by a consumer driver /
+ * user-space application.
+ *
+ * Still, if STANDBY-only configuratins are needed then someone should ensure
+ * the STBY request line stays asserted until all the necessary configurations
+ * are done. Using an evaluation board this can be done toggling a swicth
+ * manually - but for any real use-case we would need a SW control for this.
+ * This driver does not in any way ensure the PMIC stays in STANDBY. It only
+ * checks if PMIC is in STANDBY when some configuration is started - and warns
+ * if the state is not correct.
+ */
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/linear_range.h>
+#include <linux/mfd/rohm-generic.h>
+#include <linux/mfd/rohm-bd96801.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/regulator/coupler.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+
+enum {
+ BD96801_BUCK1,
+ BD96801_BUCK2,
+ BD96801_BUCK3,
+ BD96801_BUCK4,
+ BD96801_LDO5,
+ BD96801_LDO6,
+ BD96801_LDO7,
+ BD96801_REGULATOR_AMOUNT,
+};
+
+enum {
+ BD96801_PROT_OVP,
+ BD96801_PROT_UVP,
+ BD96801_PROT_OCP,
+ BD96801_PROT_TEMP,
+ BD96801_NUM_PROT,
+};
+
+#define BD96801_ALWAYS_ON_REG 0x3c
+#define BD96801_REG_ENABLE 0x0b
+#define BD96801_BUCK1_EN_MASK BIT(0)
+#define BD96801_BUCK2_EN_MASK BIT(1)
+#define BD96801_BUCK3_EN_MASK BIT(2)
+#define BD96801_BUCK4_EN_MASK BIT(3)
+#define BD96801_LDO5_EN_MASK BIT(4)
+#define BD96801_LDO6_EN_MASK BIT(5)
+#define BD96801_LDO7_EN_MASK BIT(6)
+
+#define BD96801_BUCK1_VSEL_REG 0x28
+#define BD96801_BUCK2_VSEL_REG 0x29
+#define BD96801_BUCK3_VSEL_REG 0x2a
+#define BD96801_BUCK4_VSEL_REG 0x2b
+#define BD96801_LDO5_VSEL_REG 0x25
+#define BD96801_LDO6_VSEL_REG 0x26
+#define BD96801_LDO7_VSEL_REG 0x27
+#define BD96801_BUCK_VSEL_MASK 0x1F
+#define BD96801_LDO_VSEL_MASK 0xff
+
+#define BD96801_LOCK_REG 0x04
+#define BD96801_UNLOCK 0x9d
+#define BD96801_LOCK 0x00
+
+#define BD96801_MASK_RAMP_DELAY 0xc0
+#define BD96801_INT_VOUT_BASE_REG 0x21
+#define BD96801_BUCK_INT_VOUT_MASK 0xff
+
+#define BD96801_BUCK_VOLTS 256
+#define BD96801_LDO_VOLTS 256
+
+#define BD96801_OVP_MASK 0x03
+#define BD96801_MASK_BUCK1_OVP_SHIFT 0x00
+#define BD96801_MASK_BUCK2_OVP_SHIFT 0x02
+#define BD96801_MASK_BUCK3_OVP_SHIFT 0x04
+#define BD96801_MASK_BUCK4_OVP_SHIFT 0x06
+#define BD96801_MASK_LDO5_OVP_SHIFT 0x00
+#define BD96801_MASK_LDO6_OVP_SHIFT 0x02
+#define BD96801_MASK_LDO7_OVP_SHIFT 0x04
+
+#define BD96801_PROT_LIMIT_OCP_MIN 0x00
+#define BD96801_PROT_LIMIT_LOW 0x01
+#define BD96801_PROT_LIMIT_MID 0x02
+#define BD96801_PROT_LIMIT_HI 0x03
+
+#define BD96801_REG_BUCK1_OCP 0x32
+#define BD96801_REG_BUCK2_OCP 0x32
+#define BD96801_REG_BUCK3_OCP 0x33
+#define BD96801_REG_BUCK4_OCP 0x33
+
+#define BD96801_MASK_BUCK1_OCP_SHIFT 0x00
+#define BD96801_MASK_BUCK2_OCP_SHIFT 0x04
+#define BD96801_MASK_BUCK3_OCP_SHIFT 0x00
+#define BD96801_MASK_BUCK4_OCP_SHIFT 0x04
+
+#define BD96801_REG_LDO5_OCP 0x34
+#define BD96801_REG_LDO6_OCP 0x34
+#define BD96801_REG_LDO7_OCP 0x34
+
+#define BD96801_MASK_LDO5_OCP_SHIFT 0x00
+#define BD96801_MASK_LDO6_OCP_SHIFT 0x02
+#define BD96801_MASK_LDO7_OCP_SHIFT 0x04
+
+#define BD96801_MASK_SHD_INTB BIT(7)
+#define BD96801_INTB_FATAL BIT(7)
+
+#define BD96801_NUM_REGULATORS 7
+#define BD96801_NUM_LDOS 4
+
+/*
+ * Ramp rates for bucks are controlled by bits [7:6] as follows:
+ * 00 => 1 mV/uS
+ * 01 => 5 mV/uS
+ * 10 => 10 mV/uS
+ * 11 => 20 mV/uS
+ */
+static const unsigned int buck_ramp_table[] = { 1000, 5000, 10000, 20000 };
+
+/*
+ * This is a voltage range that get's appended to selected
+ * bd96801_buck_init_volts value. The range from 0x0 to 0xF is actually
+ * bd96801_buck_init_volts + 0 ... bd96801_buck_init_volts + 150mV
+ * and the range from 0x10 to 0x1f is bd96801_buck_init_volts - 150mV ...
+ * bd96801_buck_init_volts - 0. But as the members of linear_range
+ * are all unsigned I will apply offset of -150 mV to value in
+ * linear_range - which should increase these ranges with
+ * 150 mV getting all the values to >= 0.
+ */
+static const struct linear_range bd96801_tune_volts[] = {
+ REGULATOR_LINEAR_RANGE(150000, 0x00, 0xF, 10000),
+ REGULATOR_LINEAR_RANGE(0, 0x10, 0x1F, 10000),
+};
+
+static const struct linear_range bd96801_buck_init_volts[] = {
+ REGULATOR_LINEAR_RANGE(500000 - 150000, 0x00, 0xc8, 5000),
+ REGULATOR_LINEAR_RANGE(1550000 - 150000, 0xc9, 0xec, 50000),
+ REGULATOR_LINEAR_RANGE(3300000 - 150000, 0xed, 0xff, 0),
+};
+
+static const struct linear_range bd96801_ldo_int_volts[] = {
+ REGULATOR_LINEAR_RANGE(300000, 0x00, 0x78, 25000),
+ REGULATOR_LINEAR_RANGE(3300000, 0x79, 0xff, 0),
+};
+
+#define BD96801_LDO_SD_VOLT_MASK 0x1
+#define BD96801_LDO_MODE_MASK 0x6
+#define BD96801_LDO_MODE_INT 0x0
+#define BD96801_LDO_MODE_SD 0x2
+#define BD96801_LDO_MODE_DDR 0x4
+
+static int ldo_ddr_volt_table[] = {500000, 300000};
+static int ldo_sd_volt_table[] = {3300000, 1800000};
+
+/* Constant IRQ initialization data (templates) */
+struct bd96801_irqinfo {
+ int type;
+ struct regulator_irq_desc irq_desc;
+ int err_cfg;
+ int wrn_cfg;
+ const char *irq_name;
+};
+
+#define BD96801_IRQINFO(_type, _name, _irqoff_ms, _irqname) \
+{ \
+ .type = (_type), \
+ .err_cfg = -1, \
+ .wrn_cfg = -1, \
+ .irq_name = (_irqname), \
+ .irq_desc = { \
+ .name = (_name), \
+ .irq_off_ms = (_irqoff_ms), \
+ .map_event = regulator_irq_map_event_simple, \
+ }, \
+}
+
+static const struct bd96801_irqinfo buck1_irqinfo[] = {
+ BD96801_IRQINFO(BD96801_PROT_OCP, "buck1-over-curr-h", 500,
+ "bd96801-buck1-overcurr-h"),
+ BD96801_IRQINFO(BD96801_PROT_OCP, "buck1-over-curr-l", 500,
+ "bd96801-buck1-overcurr-l"),
+ BD96801_IRQINFO(BD96801_PROT_OCP, "buck1-over-curr-n", 500,
+ "bd96801-buck1-overcurr-n"),
+ BD96801_IRQINFO(BD96801_PROT_OVP, "buck1-over-voltage", 500,
+ "bd96801-buck1-overvolt"),
+ BD96801_IRQINFO(BD96801_PROT_UVP, "buck1-under-voltage", 500,
+ "bd96801-buck1-undervolt"),
+ BD96801_IRQINFO(BD96801_PROT_TEMP, "buck1-over-temp", 500,
+ "bd96801-buck1-thermal")
+};
+
+static const struct bd96801_irqinfo buck2_irqinfo[] = {
+ BD96801_IRQINFO(BD96801_PROT_OCP, "buck2-over-curr-h", 500,
+ "bd96801-buck2-overcurr-h"),
+ BD96801_IRQINFO(BD96801_PROT_OCP, "buck2-over-curr-l", 500,
+ "bd96801-buck2-overcurr-l"),
+ BD96801_IRQINFO(BD96801_PROT_OCP, "buck2-over-curr-n", 500,
+ "bd96801-buck2-overcurr-n"),
+ BD96801_IRQINFO(BD96801_PROT_OVP, "buck2-over-voltage", 500,
+ "bd96801-buck2-overvolt"),
+ BD96801_IRQINFO(BD96801_PROT_UVP, "buck2-under-voltage", 500,
+ "bd96801-buck2-undervolt"),
+ BD96801_IRQINFO(BD96801_PROT_TEMP, "buck2-over-temp", 500,
+ "bd96801-buck2-thermal")
+};
+
+static const struct bd96801_irqinfo buck3_irqinfo[] = {
+ BD96801_IRQINFO(BD96801_PROT_OCP, "buck3-over-curr-h", 500,
+ "bd96801-buck3-overcurr-h"),
+ BD96801_IRQINFO(BD96801_PROT_OCP, "buck3-over-curr-l", 500,
+ "bd96801-buck3-overcurr-l"),
+ BD96801_IRQINFO(BD96801_PROT_OCP, "buck3-over-curr-n", 500,
+ "bd96801-buck3-overcurr-n"),
+ BD96801_IRQINFO(BD96801_PROT_OVP, "buck3-over-voltage", 500,
+ "bd96801-buck3-overvolt"),
+ BD96801_IRQINFO(BD96801_PROT_UVP, "buck3-under-voltage", 500,
+ "bd96801-buck3-undervolt"),
+ BD96801_IRQINFO(BD96801_PROT_TEMP, "buck3-over-temp", 500,
+ "bd96801-buck3-thermal")
+};
+
+static const struct bd96801_irqinfo buck4_irqinfo[] = {
+ BD96801_IRQINFO(BD96801_PROT_OCP, "buck4-over-curr-h", 500,
+ "bd96801-buck4-overcurr-h"),
+ BD96801_IRQINFO(BD96801_PROT_OCP, "buck4-over-curr-l", 500,
+ "bd96801-buck4-overcurr-l"),
+ BD96801_IRQINFO(BD96801_PROT_OCP, "buck4-over-curr-n", 500,
+ "bd96801-buck4-overcurr-n"),
+ BD96801_IRQINFO(BD96801_PROT_OVP, "buck4-over-voltage", 500,
+ "bd96801-buck4-overvolt"),
+ BD96801_IRQINFO(BD96801_PROT_UVP, "buck4-under-voltage", 500,
+ "bd96801-buck4-undervolt"),
+ BD96801_IRQINFO(BD96801_PROT_TEMP, "buck4-over-temp", 500,
+ "bd96801-buck4-thermal")
+};
+
+static const struct bd96801_irqinfo ldo5_irqinfo[] = {
+ BD96801_IRQINFO(BD96801_PROT_OCP, "ldo5-overcurr", 500,
+ "bd96801-ldo5-overcurr"),
+ BD96801_IRQINFO(BD96801_PROT_OVP, "ldo5-over-voltage", 500,
+ "bd96801-ldo5-overvolt"),
+ BD96801_IRQINFO(BD96801_PROT_UVP, "ldo5-under-voltage", 500,
+ "bd96801-ldo5-undervolt"),
+};
+
+static const struct bd96801_irqinfo ldo6_irqinfo[] = {
+ BD96801_IRQINFO(BD96801_PROT_OCP, "ldo6-overcurr", 500,
+ "bd96801-ldo6-overcurr"),
+ BD96801_IRQINFO(BD96801_PROT_OVP, "ldo6-over-voltage", 500,
+ "bd96801-ldo6-overvolt"),
+ BD96801_IRQINFO(BD96801_PROT_UVP, "ldo6-under-voltage", 500,
+ "bd96801-ldo6-undervolt"),
+};
+
+static const struct bd96801_irqinfo ldo7_irqinfo[] = {
+ BD96801_IRQINFO(BD96801_PROT_OCP, "ldo7-overcurr", 500,
+ "bd96801-ldo7-overcurr"),
+ BD96801_IRQINFO(BD96801_PROT_OVP, "ldo7-over-voltage", 500,
+ "bd96801-ldo7-overvolt"),
+ BD96801_IRQINFO(BD96801_PROT_UVP, "ldo7-under-voltage", 500,
+ "bd96801-ldo7-undervolt"),
+};
+
+struct bd96801_irq_desc {
+ struct bd96801_irqinfo *irqinfo;
+ int num_irqs;
+};
+
+struct bd96801_regulator_data {
+ struct regulator_desc desc;
+ const struct linear_range *init_ranges;
+ int num_ranges;
+ struct bd96801_irq_desc irq_desc;
+ int initial_voltage;
+ int ldo_vol_lvl;
+ /* OCP tables are fixed size - four values */
+ const int *ocp_table;
+ u8 prot_reg_shift;
+ u8 ocp_shift;
+ u8 ovp_reg;
+ u8 ovd_reg;
+ u8 ocp_reg;
+ int ldo_errs;
+};
+
+struct bd96801_pmic_data {
+ struct bd96801_regulator_data regulator_data[BD96801_NUM_REGULATORS];
+ struct regmap *regmap;
+ int fatal_ind;
+};
+
+/*
+ * Return 0 if limit should be set.
+ * Return 1 if limit should not be set but we want to proceed with regulator
+ * registration.
+ * Return other error to propagate issues to regulator framework.
+ */
+static int sanity_check_ovd_uvd(struct device *dev, struct bd96801_irqinfo *new,
+ struct bd96801_irqinfo *old, int lim_uV,
+ int severity, bool enable)
+{
+ int old_err = 0, old_wrn = 0;
+ int *cfg;
+
+ if (!new) {
+ dev_warn(dev, "No protection IRQ\n");
+ return -EOPNOTSUPP;
+ }
+
+ if (severity == REGULATOR_SEVERITY_ERR)
+ cfg = &new->err_cfg;
+ else
+ cfg = &new->wrn_cfg;
+
+
+ if (!enable) {
+ *cfg = 0;
+
+ return 1;
+ }
+
+ /* Don't allow overriding ERR with WRN */
+ if (severity == REGULATOR_SEVERITY_WARN && new->err_cfg &&
+ new->err_cfg != -1) {
+ dev_warn(dev, "Both WARNING and ERROR limits given.\n");
+ return 1;
+ }
+
+ /*
+ * BD96801 has common limit for OVD and UVD.
+ * See that there is no existing settings
+ * conflicting. Warn if there is.
+ */
+ if (old) {
+ if (old->err_cfg && old->err_cfg != -1 && old->err_cfg != 1)
+ old_err = old->err_cfg;
+ if (old->wrn_cfg && old->wrn_cfg != -1 && old->wrn_cfg != 1)
+ old_wrn = old->wrn_cfg;
+
+ if (lim_uV && ((old_err && (old_err != lim_uV)) ||
+ (old_wrn && (old_wrn != lim_uV)))) {
+ dev_warn(dev, "conflicting OVD and UVD limits given\n");
+ /*
+ * If both OVD and UVD is configured, do not
+ * override ERR by WARN and don't increase already set
+ * limit.
+ */
+ if (severity == REGULATOR_SEVERITY_WARN) {
+ if (old_err || (old_wrn && old_wrn < lim_uV))
+ return -1;
+ } else {
+ /*
+ * We prefer ERROR over WARNING even though it
+ * is likely to relax the limit.
+ */
+ if (old_wrn && old_wrn < lim_uV)
+ dev_warn(dev,
+ "Increasing warning limit\n");
+
+ /*
+ * If for some reason the existing warning has
+ * strictier limit than our error - then we will
+ * just disable the warning to prevent it being
+ * errorneously sent. We won't leave warning
+ * and not send errors as we expect the errors
+ * to be much more severe.
+ */
+ if (old_wrn && old_wrn > lim_uV) {
+ dev_warn(dev,
+ "Disabling conflicting warning\n");
+ old->wrn_cfg = 0;
+ }
+
+ if (old_err && old_err < lim_uV) {
+ dev_warn(dev, "Leaving old limit %u\n",
+ old_err);
+
+ return -1;
+ }
+ dev_warn(dev, "Using new limit %u\n", lim_uV);
+ }
+ }
+ }
+
+ if (lim_uV)
+ *cfg = lim_uV;
+ else
+ *cfg = 1;
+
+ /*
+ * The BD96801 has only one OVD IRQ. We can either use it for
+ * warning or for error - not for both
+ */
+ if (new->err_cfg && new->wrn_cfg) {
+ dev_warn(dev,
+ "Both WARN and ERROR limit given. Discarding WARN\n");
+ new->wrn_cfg = 0;
+ }
+
+ return 0;
+}
+
+static int set_ovp_limit(struct regulator_dev *rdev, int lim_uV)
+{
+ int voltage, lim, set_uv, shift;
+ struct bd96801_regulator_data *rdata;
+ struct bd96801_pmic_data *pdata;
+ struct device *dev;
+
+ dev = rdev_get_dev(rdev);
+ rdata = container_of(rdev->desc, struct bd96801_regulator_data, desc);
+ pdata = rdev_get_drvdata(rdev);
+
+ /*
+ * OVP can be configured to be 9%, 15% or 20% of the set voltage.
+ *
+ * Let's compute the OVP based on the current INT_VOUT + Vtune here.
+ *
+ * This is not 100% according to the spec as the (absolute) limit
+ * value in HW will vary depending on the set voltage.
+ *
+ * We could store the desired limit and re-compute the proportional
+ * OVP/UVP values when regulator voltage is adjusted. If we're getting
+ * out of the spec we could then change the setting of limits to ensure
+ * we stay below the absolute limit value given from DT.
+ *
+ * My initial guess on the use-cases is that the INT_VOUT is only set
+ * at boot - and the impact of Vtune is so small we can ignore the
+ * potenrial limit error for now. Let's fix this only if we see actual
+ * problems.
+ */
+ voltage = regulator_get_voltage_rdev(rdev);
+ if (voltage < 0)
+ return voltage;
+
+ set_uv = voltage * 9 / 100;
+
+ if (set_uv > lim_uV) {
+ dev_err(dev, "too small OVP limit %d\n",
+ lim_uV);
+ lim = BD96801_PROT_LIMIT_LOW;
+ } else if (voltage * 15 / 100 > lim_uV) {
+ lim = BD96801_PROT_LIMIT_LOW;
+ } else if (voltage * 20 / 9 > lim_uV) {
+ set_uv = voltage * 15 / 100;
+ lim = BD96801_PROT_LIMIT_MID;
+ } else {
+ set_uv = voltage * 20 / 100;
+ lim = BD96801_PROT_LIMIT_HI;
+ }
+ dev_info(dev,
+ "OVP limit %u requested. Setting %u\n",
+ lim_uV, set_uv);
+
+ shift = rdata->prot_reg_shift;
+
+ return regmap_update_bits(pdata->regmap, rdata->ovp_reg,
+ BD96801_OVP_MASK << shift,
+ lim << shift);
+}
+
+/*
+ * Supported detection limits for BUCKs are absolute values, 9mV, 15mV and
+ * 20mV. The limits for LDOs depend on initial voltage register value. Scale
+ * limit to what is supported by HW.
+ */
+static int get_xvd_limits(struct regulator_dev *rdev, int *lim_uV, int *reg)
+{
+ int ret, regu_xvd_limits[3] = { 9000, 15000, 20000 };
+ struct bd96801_regulator_data *rdata;
+ struct bd96801_pmic_data *pdata;
+ struct device *dev;
+
+ dev = rdev_get_dev(rdev);
+ rdata = container_of(rdev->desc, struct bd96801_regulator_data, desc);
+ pdata = rdev_get_drvdata(rdev);
+
+ dev_dbg(dev, "OVD limit %u requested\n", *lim_uV);
+
+ if (rdata->ldo_vol_lvl) {
+ int val;
+
+ ret = regmap_read(pdata->regmap, rdata->ldo_vol_lvl, &val);
+ if (ret)
+ return ret;
+
+ if (val > 15) {
+ if (val < 38) {
+ regu_xvd_limits[0] = 16000;
+ regu_xvd_limits[1] = 30000;
+ regu_xvd_limits[2] = 40000;
+ } else {
+ regu_xvd_limits[0] = 36000;
+ regu_xvd_limits[1] = 60000;
+ regu_xvd_limits[2] = 80000;
+ }
+ }
+ }
+ /* LDO's have different limits */
+ if (*lim_uV < regu_xvd_limits[0]) {
+ dev_warn(dev, "Unsupported UVD limit %d\n",
+ *lim_uV);
+ *lim_uV = regu_xvd_limits[0];
+ *reg = BD96801_PROT_LIMIT_LOW;
+ } else if (*lim_uV < regu_xvd_limits[1]) {
+ *lim_uV = regu_xvd_limits[0];
+ *reg = BD96801_PROT_LIMIT_LOW;
+ } else if (*lim_uV < regu_xvd_limits[2]) {
+ *lim_uV = regu_xvd_limits[1];
+ *reg = BD96801_PROT_LIMIT_MID;
+ } else {
+ *lim_uV = regu_xvd_limits[1];
+ *reg = BD96801_PROT_LIMIT_HI;
+ }
+ dev_info(dev, "Using UVD limit %u\n", *lim_uV);
+
+ return 0;
+}
+
+static inline int bd96801_in_stby(struct regmap *rmap)
+{
+ int ret, val;
+
+ ret = regmap_read(rmap, BD96801_REG_PMIC_STATE, &val);
+ if (ret)
+ return ret;
+
+ return (val == BD96801_STATE_STBY);
+}
+
+static int bd96801_set_ovp(struct regulator_dev *rdev, int lim_uV, int severity,
+ bool enable)
+{
+ int shift, stby;
+ struct bd96801_pmic_data *pdata;
+ struct bd96801_regulator_data *rdata;
+ struct bd96801_irq_desc *idesc;
+ struct device *dev;
+ struct bd96801_irqinfo *ovp_iinfo = NULL;
+ struct bd96801_irqinfo *uvp_iinfo = NULL;
+ int reg;
+ int ret;
+ int i;
+
+ dev = rdev_get_dev(rdev);
+ rdata = container_of(rdev->desc, struct bd96801_regulator_data, desc);
+ pdata = rdev_get_drvdata(rdev);
+ idesc = &rdata->irq_desc;
+
+ if (!idesc)
+ return -EOPNOTSUPP;
+
+ stby = bd96801_in_stby(rdev->regmap);
+ if (stby < 0)
+ return stby;
+ if (!stby)
+ dev_warn(dev, "Can't set OVP. PMIC not in STANDBY\n");
+
+ if (severity == REGULATOR_SEVERITY_PROT) {
+ if (!enable) {
+ dev_err(dev, "Can't disable over voltage protection\n");
+ return -EOPNOTSUPP;
+ }
+ if (!lim_uV)
+ return 0;
+
+ return set_ovp_limit(rdev, lim_uV);
+ }
+
+ /* See the comment at bd96801_set_uvp() below */
+ if (enable && pdata->fatal_ind == 1) {
+ dev_err(dev,
+ "All errors are fatal. Can't provide notifications\n");
+ if (severity == REGULATOR_SEVERITY_WARN)
+ return -EINVAL;
+ }
+
+ if (lim_uV) {
+ ret = get_xvd_limits(rdev, &lim_uV, ®);
+ if (ret)
+ return ret;
+ }
+ for (i = 0; i < idesc->num_irqs; i++) {
+ struct bd96801_irqinfo *iinfo = &idesc->irqinfo[i];
+
+ if (iinfo->type == BD96801_PROT_OVP)
+ ovp_iinfo = iinfo;
+
+ if (iinfo->type == BD96801_PROT_UVP)
+ uvp_iinfo = iinfo;
+ }
+
+ ret = sanity_check_ovd_uvd(dev, ovp_iinfo, uvp_iinfo, lim_uV,
+ severity, enable);
+
+ if (ret) {
+ if (ret == 1)
+ return 0;
+ return ret;
+ }
+
+ shift = rdata->prot_reg_shift;
+
+ if (enable && lim_uV)
+ return regmap_update_bits(pdata->regmap, rdata->ovd_reg,
+ BD96801_OVP_MASK << shift,
+ reg << shift);
+ return 0;
+}
+
+static int bd96801_set_uvp(struct regulator_dev *rdev, int lim_uV, int severity,
+ bool enable)
+{
+ int shift, stby;
+ struct bd96801_pmic_data *pdata;
+ struct bd96801_regulator_data *rdata;
+ struct bd96801_irq_desc *idesc;
+ struct device *dev;
+ struct bd96801_irqinfo *ovp_iinfo = NULL;
+ struct bd96801_irqinfo *uvp_iinfo = NULL;
+ int reg;
+ int ret;
+ int i;
+
+ dev = rdev_get_dev(rdev);
+ rdata = container_of(rdev->desc, struct bd96801_regulator_data, desc);
+ pdata = rdev_get_drvdata(rdev);
+ idesc = &rdata->irq_desc;
+
+ if (!idesc)
+ return -EOPNOTSUPP;
+
+ stby = bd96801_in_stby(rdev->regmap);
+ if (stby < 0)
+ return stby;
+ if (!stby)
+ dev_warn(dev, "Can't set UVP. PMIC not in STANDBY\n");
+
+ if (severity == REGULATOR_SEVERITY_PROT) {
+ /* There is nothing we can do for UVP protection on BD96801 */
+ if (!enable) {
+ dev_err(dev, "Can't disable under voltage protection\n");
+ return -EOPNOTSUPP;
+ }
+ if (lim_uV)
+ dev_warn(dev,
+ "Can't set under voltage protection limit\n");
+ return 0;
+ }
+
+ /*
+ * The PMIC provides option to turn all indications fatal. The
+ * OCP (protection) does utilize this. If DT enables OCP then we
+ * can't provide warning/error notifications as these events are also
+ * causing a shutdown. If this is the case we refuse to set the WARN
+ * limit - but allow setting ERROR limit in order to prevent HW damage
+ * if someone trusts on the ERRORs. The protection shutdown won't
+ * inform software so this is not exactly a graceful thing. Thus punt
+ * error log message also for the REGULATOR_SEVERITY_ERR config.
+ */
+ if (enable && pdata->fatal_ind == 1) {
+ dev_err(dev,
+ "All errors are fatal. Can't provide notifications\n");
+ if (severity == REGULATOR_SEVERITY_WARN)
+ return -EINVAL;
+ }
+
+ if (lim_uV) {
+ ret = get_xvd_limits(rdev, &lim_uV, ®);
+ if (ret)
+ return ret;
+ }
+ for (i = 0; i < idesc->num_irqs; i++) {
+ struct bd96801_irqinfo *iinfo = &idesc->irqinfo[i];
+
+ if (iinfo->type == BD96801_PROT_OVP)
+ ovp_iinfo = iinfo;
+
+ if (iinfo->type == BD96801_PROT_UVP)
+ uvp_iinfo = iinfo;
+ }
+
+ ret = sanity_check_ovd_uvd(dev, uvp_iinfo, ovp_iinfo, lim_uV,
+ severity, enable);
+
+ if (ret) {
+ if (ret == 1)
+ return 0;
+ return ret;
+ }
+
+ shift = rdata->prot_reg_shift;
+
+ if (enable && lim_uV)
+ return regmap_update_bits(pdata->regmap, rdata->ovd_reg,
+ BD96801_OVP_MASK << shift,
+ reg << shift);
+ return 0;
+}
+
+
+/*
+ * Driver uses fixed size OCP tables. If new variant with more OCP values is
+ * added we need to handle different sizes and selectors
+ */
+
+/* 1.5 A ... 3 A step 0.5 A*/
+static const int bd96801_buck12_ocp[] = { 1500000, 2000000, 2500000, 3000000 };
+
+/* 3 A ... 6 A *step 1 A */
+static const int bd96801_buck34_ocp[] = { 3000000, 4000000, 5000000, 6000000 };
+
+/* 400 mA ... 550 mA, step 50 mA*/
+static const int bd96801_ldo_ocp[] = { 400000, 450000, 500000, 550000 };
+
+static int __drop_warns(struct bd96801_regulator_data *rdata,
+ struct regmap *regmap, struct device *dev,
+ struct bd96801_irqinfo *iinfo)
+{
+ int ret = 0;
+
+ if (!iinfo->wrn_cfg && !iinfo->err_cfg)
+ return 0;
+
+ dev_err(dev, "All errors are fatal. Can't provide notifications\n");
+
+ if (iinfo->wrn_cfg) {
+ int mask;
+ int val;
+
+ if (iinfo->type == BD96801_PROT_OVP ||
+ iinfo->type == BD96801_PROT_UVP) {
+ mask = BD96801_OVP_MASK << rdata->prot_reg_shift;
+ val = BD96801_PROT_LIMIT_HI << rdata->prot_reg_shift;
+
+ ret = regmap_update_bits(regmap, rdata->ovd_reg,
+ mask, val);
+ } else if (iinfo->type == BD96801_PROT_OCP) {
+ mask = BD96801_OVP_MASK << rdata->ocp_shift;
+ val = BD96801_PROT_LIMIT_HI << rdata->ocp_shift;
+ ret = regmap_update_bits(regmap, rdata->ocp_reg,
+ mask, val);
+ }
+ if (ret)
+ return ret;
+
+ iinfo->wrn_cfg = -1;
+ }
+
+ return 0;
+}
+
+/*
+ * When we configure INTB to cause SHDN we will also make _all_ problems fatal.
+ * This is likely to cause shutdown at too early phase if WARNs are configured.
+ * Thus we reset all already configured WARN limits.
+ *
+ * NOTE: We don't cancel both WARNs and ERRs. The typical and expected recovery
+ * for ERRs is anyways a shutdown (although graceful). We'd better to leave
+ * those limits respected even though the shutdown won't be pretty. In most
+ * cases this should still be better than allowing things to go off more than
+ * ERR boundary.
+ *
+ * For any already added WARNs we just configure the max limit. (We _could_
+ * store the HW default at startup and use it - but let's not overdo this. We
+ * do punt out a big red error message - hopefully that is read by board R&D
+ * folks and they will review limits and fix the offending DT limits...
+ */
+static int bd96801_drop_all_warns(struct device *dev,
+ struct bd96801_pmic_data *pdata)
+{
+ int i, ret;
+
+ for (i = 0; i < BD96801_NUM_REGULATORS; i++) {
+ struct bd96801_regulator_data *rdata;
+
+ rdata = &pdata->regulator_data[i];
+ for (i = 0; i < rdata->irq_desc.num_irqs; i++) {
+ struct bd96801_irqinfo *iinfo;
+
+ iinfo = &rdata->irq_desc.irqinfo[i];
+ ret = __drop_warns(rdata, pdata->regmap, dev, iinfo);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int bd96801_set_oc_det(struct device *dev,
+ struct bd96801_pmic_data *pdata,
+ struct bd96801_regulator_data *rdata, bool enable,
+ int severity)
+{
+ struct bd96801_irqinfo *iinfo;
+ bool found = false;
+ int i, *cfg;
+
+ if (enable && pdata->fatal_ind == 1) {
+ dev_err(dev, "Can't support fatal and non fatal OCP\n");
+ return -EINVAL;
+ }
+
+ /* Bucks have 3 OCP IRQs. mark them all. */
+ for (i = 0; i < rdata->irq_desc.num_irqs; i++) {
+ iinfo = &rdata->irq_desc.irqinfo[i];
+ if (iinfo->type != BD96801_PROT_OCP)
+ continue;
+
+ if (severity == REGULATOR_SEVERITY_WARN) {
+ if (enable && iinfo->err_cfg &&
+ iinfo->err_cfg != -1) {
+ dev_err(dev,
+ "Can't support both OCP WARN and ERR\n");
+ return -EINVAL;
+ }
+ cfg = &iinfo->wrn_cfg;
+ } else {
+ if (enable && iinfo->err_cfg &&
+ iinfo->err_cfg != -1) {
+ /* Print only once for this regulator's OCP */
+ if (!found)
+ dev_err(dev,
+ "Can't support both OCP WARN and ERR\n");
+ iinfo->wrn_cfg = 0;
+ }
+ cfg = &iinfo->err_cfg;
+ }
+ if (!enable)
+ *cfg = 0;
+ else
+ *cfg = 1;
+
+ found = true;
+ }
+ if (!found)
+ return -EOPNOTSUPP;
+
+ return 0;
+}
+
+static int bd96801_set_ocp(struct regulator_dev *rdev, int lim_uA,
+ int severity, bool enable)
+{
+ struct bd96801_pmic_data *pdata;
+ struct bd96801_regulator_data *rdata;
+ struct device *dev;
+ int reg, stby;
+
+ dev = rdev_get_dev(rdev);
+ rdata = container_of(rdev->desc, struct bd96801_regulator_data, desc);
+ pdata = rdev_get_drvdata(rdev);
+
+ /*
+ * Most of the configs can only be done when PMIC is in STANDBY
+ * And yes. This is racy, we don't know when PMIC state is changed.
+ * So we can't promise config works as PMIC state may change right
+ * after this check - but at least we can warn if this attempted
+ * when PMIC isn't in STANDBY.
+ */
+ stby = bd96801_in_stby(rdev->regmap);
+ if (stby < 0)
+ return stby;
+ if (!stby)
+ dev_warn(dev, "Can't set OCP. PMIC not in STANDBY\n");
+
+ if (severity == REGULATOR_SEVERITY_PROT) {
+ if (enable) {
+ if (pdata->fatal_ind == 0)
+ dev_err(dev, "Conflicting protection settings.\n");
+
+ pdata->fatal_ind = 1;
+ bd96801_drop_all_warns(dev, pdata);
+ } else {
+ if (pdata->fatal_ind == 1) {
+ dev_err(dev, "Conflicting protection settings.\n");
+ return -EINVAL;
+ }
+ pdata->fatal_ind = 0;
+ }
+ if (!lim_uA)
+ return 0;
+ } else {
+ int ret;
+
+ ret = bd96801_set_oc_det(dev, pdata, rdata, enable, severity);
+ if (ret)
+ return ret;
+
+ if (!enable || !lim_uA)
+ return 0;
+ }
+ /*
+ * zero is valid selector for OCP unlike for OVP/UVP.
+ * We only set the limit for INT OCPH. OCPL OCPN and EXT_OCP limits
+ * are not supported. Those could probably be handled using own vendor
+ * DTS property.
+ */
+ if (lim_uA > rdata->ocp_table[BD96801_PROT_LIMIT_MID]) {
+ reg = BD96801_PROT_LIMIT_HI;
+ } else if (lim_uA > rdata->ocp_table[BD96801_PROT_LIMIT_LOW]) {
+ reg = BD96801_PROT_LIMIT_MID;
+ } else if (lim_uA > rdata->ocp_table[BD96801_PROT_LIMIT_OCP_MIN]) {
+ reg = BD96801_PROT_LIMIT_LOW;
+ } else {
+ if (lim_uA < rdata->ocp_table[BD96801_PROT_LIMIT_OCP_MIN])
+ dev_warn(dev, "Can't support OCP %u, set %u\n",
+ lim_uA,
+ rdata->ocp_table[BD96801_PROT_LIMIT_OCP_MIN]);
+ reg = 0;
+ }
+
+ return regmap_update_bits(pdata->regmap, rdata->ocp_reg,
+ BD96801_OVP_MASK << rdata->ocp_shift,
+ reg << rdata->ocp_shift);
+}
+
+#define BD96801_TSD_KELVIN 448
+#define BD96801_TW_MIN_KELVIN 404
+#define BD96801_TW_MAX_KELVIN 422
+
+static int config_thermal_prot(struct bd96801_pmic_data *pdata,
+ struct device *dev, int lim, bool enable)
+{
+ if (enable) {
+ /*
+ * If limit is not given we assume the protection
+ * refers to the TSD at BD96801_TSD_KELVIN. This
+ * is always enabled so we have a no-op here.
+ *
+ * If limit is given then we try using fatal INTB
+ */
+ if (!lim)
+ return 0;
+
+ if (pdata->fatal_ind == 0)
+ dev_err(dev, "Conflicting protection settings.\n");
+
+ pdata->fatal_ind = 1;
+ bd96801_drop_all_warns(dev, pdata);
+ } else {
+ if (pdata->fatal_ind == 1) {
+ dev_err(dev,
+ "Conflicting protection settings.\n");
+ return -EINVAL;
+ }
+ pdata->fatal_ind = 0;
+ }
+
+ return 0;
+}
+
+static int bd96801_ldo_set_tw(struct regulator_dev *rdev, int lim, int severity,
+ bool enable)
+{
+ struct bd96801_regulator_data *rdata;
+ struct bd96801_pmic_data *pdata;
+ struct device *dev;
+
+ dev = rdev_get_dev(rdev);
+ rdata = container_of(rdev->desc, struct bd96801_regulator_data, desc);
+ pdata = rdev_get_drvdata(rdev);
+
+ /*
+ * Let's handle the TSD case, After this we can focus on INTB.
+ * See if given limit is the BD96801 TSD. If so, the enable request for
+ * protection is valid (TSD is always enabled and does always forcibly
+ * shut-down the PMIC. All other configurations for this temperature
+ * are unsupported.
+ */
+ if (lim == BD96801_TSD_KELVIN) {
+ if (severity == REGULATOR_SEVERITY_PROT && enable)
+ return 0;
+
+ dev_err(dev, "Unsupported TSD configuration\n");
+ return -EINVAL;
+ }
+
+ /*
+ * The PMIC provides Thermal warning IRQ with limit that is not
+ * configurable. If protection matching this limit is configured we can
+ * use the INTB IRQ either for HW protection (fatal INTB), or WARN or
+ * ERROR level notifications.
+ */
+ if (lim && (lim < BD96801_TW_MIN_KELVIN ||
+ lim > BD96801_TW_MAX_KELVIN)) {
+ dev_err(dev, "Unsupported thermal protection limit\n");
+ return -EINVAL;
+ }
+
+ if (severity == REGULATOR_SEVERITY_PROT)
+ return config_thermal_prot(pdata, dev, lim, enable);
+ if (!enable)
+ return 0;
+
+ if (rdata->ldo_errs) {
+ dev_err(dev,
+ "Multiple protection notification configs for %s\n",
+ rdev->desc->name);
+ return -EINVAL;
+ }
+ if (severity == REGULATOR_SEVERITY_ERR)
+ rdata->ldo_errs = REGULATOR_ERROR_OVER_TEMP;
+ else
+ rdata->ldo_errs = REGULATOR_ERROR_OVER_TEMP_WARN;
+
+ return 0;
+}
+
+static int ldo_map_notif(int irq, struct regulator_irq_data *rid,
+ unsigned long *dev_mask)
+{
+ int i;
+
+ for (i = 0; i < rid->num_states; i++) {
+ struct bd96801_regulator_data *rdata;
+ struct regulator_dev *rdev;
+
+ rdev = rid->states[i].rdev;
+ rdata = container_of(rdev->desc, struct bd96801_regulator_data,
+ desc);
+ rid->states[i].notifs = regulator_err2notif(rdata->ldo_errs);
+ rid->states[i].errors = rdata->ldo_errs;
+ *dev_mask |= BIT(i);
+ }
+ return 0;
+}
+
+/*
+ * Data-Sheet states that for BUCKs the IC can monitor driver MOS temperature
+ * or each internal power MOSFET temperature. Additionally there seems to be
+ * measurement of temperature at the center of the chip. These temperature
+ * detections will again generate INTB interrupt - and again, the INTB can
+ * be set to shut-down the faulting power-output (or group of outputs). Limit
+ * for thermal warning (INTB) is arounf 140 degree C. (Data-sheet says 131 to
+ * 149 but I see no configuration for the limit.
+ *
+ * Let's use the same PROTECTION (fatal INTB behaviour) as we use with the
+ * OCP.
+ *
+ * On top of this it seems the IC has TSD (thermal shut-down). This is not
+ * configurable or maskable. The temperature limit for TSD is 175 degree C
+ * but the measurement point is not mentioned (any of the measurements
+ * exceed this?)
+ */
+static int bd96801_buck_set_tw(struct regulator_dev *rdev, int lim, int severity,
+ bool enable)
+{
+ struct bd96801_regulator_data *rdata;
+ struct bd96801_pmic_data *pdata;
+ struct bd96801_irqinfo *iinfo;
+ struct device *dev;
+ int i;
+
+ dev = rdev_get_dev(rdev);
+ rdata = container_of(rdev->desc, struct bd96801_regulator_data, desc);
+ pdata = rdev_get_drvdata(rdev);
+
+ /*
+ * Let's handle the TSD case, After this we can focus on INTB.
+ * See if given limit is the BD96801 TSD. If so, the enable request for
+ * protection is valid (TSD is always enabled and does always forcibly
+ * shut-down the PMIC). All other configurations for this temperature
+ * are unsupported.
+ */
+ if (lim == BD96801_TSD_KELVIN) {
+ if (severity == REGULATOR_SEVERITY_PROT && enable)
+ return 0;
+
+ dev_err(dev, "Unsupported TSD configuration\n");
+ return -EINVAL;
+ }
+
+ for (i = 0; i < rdata->irq_desc.num_irqs; i++) {
+ iinfo = &rdata->irq_desc.irqinfo[i];
+ if (iinfo->type == BD96801_PROT_TEMP)
+ break;
+ }
+
+ if (i == rdata->irq_desc.num_irqs)
+ return -EOPNOTSUPP;
+ /*
+ * If limit is given, see it is within the detection range mentioned
+ * in BD96801 data-sheet. We can't configure the limit but we can fail
+ * if limit is given and it does not fit in typical thermal warning
+ * detection range.
+ */
+ if (lim && (lim < BD96801_TW_MIN_KELVIN ||
+ lim > BD96801_TW_MAX_KELVIN)) {
+ dev_err(dev, "Unsupported thermal protection limit\n");
+ return -EINVAL;
+ }
+
+ if (severity == REGULATOR_SEVERITY_PROT)
+ return config_thermal_prot(pdata, dev, lim, enable);
+
+ if (pdata->fatal_ind == 1) {
+ /*
+ * INTB is set fatal => there will be no warning to consumers.
+ * Let's still not fail the probe as this is not going to fry
+ * the HW - it rather will make us do protection shutdown too
+ * early. So just spill out a warning but let the boot proceed.
+ */
+ dev_warn(dev, "INTB set fatal. Notifications not supported\n");
+ return 0;
+ }
+
+ if (severity == REGULATOR_SEVERITY_ERR)
+ iinfo->err_cfg = (enable) ? 1 : 0;
+ else
+ iinfo->wrn_cfg = (enable) ? 1 : 0;
+
+ if (iinfo->wrn_cfg && iinfo->wrn_cfg != -1 && iinfo->err_cfg &&
+ iinfo->err_cfg != -1)
+ dev_warn(dev, "Both temperature WARN and ERR given\n");
+
+ return 0;
+}
+
+static int bd96801_list_voltage_lr(struct regulator_dev *rdev,
+ unsigned int selector)
+{
+ int voltage;
+ struct bd96801_regulator_data *data;
+
+ data = container_of(rdev->desc, struct bd96801_regulator_data, desc);
+
+ /*
+ * The BD096801 has voltage setting in two registers. One giving the
+ * "initial voltage" (can be changed only when regulator is disabled.
+ * This driver caches the value and sets it only at startup. The other
+ * register is voltage tuning value which applies -150 mV ... +150 mV
+ * offset to the voltage.
+ *
+ * Note that the cached initial voltage stored in regulator data is
+ * 'scaled down' by the 150 mV so that all of our tuning values are
+ * >= 0. This is done because the linear_ranges uses unsigned values.
+ *
+ * As a result, we increase the tuning voltage which we get based on
+ * the selector by the stored initial_voltage.
+ */
+ voltage = regulator_list_voltage_linear_range(rdev, selector);
+ if (voltage < 0)
+ return voltage;
+
+ return voltage + data->initial_voltage;
+}
+
+/*
+ * BD96801 does not allow controlling the output enable/disable status
+ * unless PMIC is in STANDBY state. So this may be next to useless - unless
+ * the PMIC is controlled from processor not powered by the PMIC. AFAIK
+ * this really is a potential use-case with the BD96801 - hence these
+ * controls are implemented.
+ */
+static int bd96801_enable_regmap(struct regulator_dev *rdev)
+{
+ int stby;
+
+ stby = bd96801_in_stby(rdev->regmap);
+ if (stby < 0)
+ return stby;
+ if (!stby)
+ return -EBUSY;
+
+ return regulator_enable_regmap(rdev);
+}
+
+static int bd96801_disable_regmap(struct regulator_dev *rdev)
+{
+ int stby;
+
+ stby = bd96801_in_stby(rdev->regmap);
+ if (stby < 0)
+ return stby;
+ if (!stby)
+ return -EBUSY;
+
+ return regulator_disable_regmap(rdev);
+}
+
+/*
+ * Latest data-sheet says LDO voltages can only be changed in STANDBY(?)
+ * I think the original limitation was that the LDO must not be enabled
+ * when voltage is changed..
+ */
+static int bd96801_regulator_set_voltage_sel_restricted(struct regulator_dev *rdev,
+ unsigned int sel)
+{
+ int stby;
+
+ stby = bd96801_in_stby(rdev->regmap);
+ if (stby)
+ return -EBUSY;
+
+ return rohm_regulator_set_voltage_sel_restricted(rdev, sel);
+}
+
+static const struct regulator_ops bd96801_ldo_table_ops = {
+ .enable = bd96801_enable_regmap,
+ .disable = bd96801_disable_regmap,
+ .is_enabled = regulator_is_enabled_regmap,
+ .list_voltage = regulator_list_voltage_table,
+ .set_voltage_sel = rohm_regulator_set_voltage_sel_restricted,
+ .get_voltage_sel = regulator_get_voltage_sel_regmap,
+ .set_over_voltage_protection = bd96801_set_ovp,
+ .set_under_voltage_protection = bd96801_set_uvp,
+ .set_over_current_protection = bd96801_set_ocp,
+ .set_thermal_protection = bd96801_ldo_set_tw,
+};
+
+static const struct regulator_ops bd96801_buck_ops = {
+ .enable = regulator_enable_regmap,
+ .disable = regulator_disable_regmap,
+ .is_enabled = regulator_is_enabled_regmap,
+ .list_voltage = bd96801_list_voltage_lr,
+ .set_voltage_sel = regulator_set_voltage_sel_regmap,
+ .get_voltage_sel = regulator_get_voltage_sel_regmap,
+ .set_voltage_time_sel = regulator_set_voltage_time_sel,
+ .set_ramp_delay = regulator_set_ramp_delay_regmap,
+ .set_over_voltage_protection = bd96801_set_ovp,
+ .set_under_voltage_protection = bd96801_set_uvp,
+ .set_over_current_protection = bd96801_set_ocp,
+ .set_thermal_protection = bd96801_buck_set_tw,
+};
+
+static const struct regulator_ops bd96801_ldo_ops = {
+ .enable = regulator_enable_regmap,
+ .disable = regulator_disable_regmap,
+ .is_enabled = regulator_is_enabled_regmap,
+ .list_voltage = regulator_list_voltage_linear_range,
+ .set_voltage_sel = bd96801_regulator_set_voltage_sel_restricted,
+ .get_voltage_sel = regulator_get_voltage_sel_regmap,
+ .set_over_voltage_protection = bd96801_set_ovp,
+ .set_under_voltage_protection = bd96801_set_uvp,
+ .set_over_current_protection = bd96801_set_ocp,
+ .set_thermal_protection = bd96801_ldo_set_tw,
+};
+
+static int buck_set_initial_voltage(struct regmap *regmap, struct device *dev,
+ struct bd96801_regulator_data *data,
+ struct device_node *np)
+{
+ int ret = 0;
+
+ if (data->num_ranges) {
+ int sel, val, stby;
+ bool found;
+ u32 initial_uv;
+ int reg = BD96801_INT_VOUT_BASE_REG + data->desc.id;
+
+ /* See if initial value should be configured */
+ ret = of_property_read_u32(np, "rohm,initial-voltage-microvolt",
+ &initial_uv);
+ if (ret) {
+ if (ret == EINVAL)
+ goto get_initial;
+ return ret;
+ }
+
+ /* We can only change initial voltage when PMIC is in STANDBY */
+ stby = bd96801_in_stby(regmap);
+ if (stby < 0)
+ goto get_initial;
+
+ if (!stby) {
+ dev_warn(dev,
+ "Can't set initial voltage, PMIC not in STANDBY\n");
+ goto get_initial;
+ }
+
+ /*
+ * Check if regulator is enabled - if not, then we can change
+ * the initial voltage.
+ */
+ ret = regmap_read(regmap, data->desc.enable_reg, &val);
+ if (ret)
+ return ret;
+
+ if ((val & data->desc.enable_mask) != data->desc.enable_mask) {
+ dev_warn(dev,
+ "%s: enabled. Can't set initial voltage\n",
+ data->desc.name);
+ goto get_initial;
+ }
+
+ dev_dbg(dev, "%s: Setting INITIAL voltage %u\n",
+ data->desc.name, initial_uv);
+ /*
+ * The initial uV range is scaled down by 150mV to make all
+ * tuning values positive. Hence we decrease value by 150 mV
+ * so that the set voltage will really match the one requested
+ * via DT.
+ */
+ ret = linear_range_get_selector_low_array(data->init_ranges,
+ data->num_ranges,
+ initial_uv - 150000, &sel,
+ &found);
+ if (ret) {
+ const struct linear_range *lr;
+ int max;
+
+ lr = &data->init_ranges[data->num_ranges - 1];
+ max = linear_range_get_max_value(lr);
+
+ dev_err(dev, "Unsupported initial voltage %u\n",
+ initial_uv);
+ dev_err(dev, "%u ranges, [%u .. %u]\n",
+ data->num_ranges, data->init_ranges->min, max);
+ return ret;
+ }
+
+ if (!found)
+ dev_warn(dev,
+ "Unsupported initial voltage %u requested, setting lower\n",
+ initial_uv);
+
+ ret = regmap_update_bits(regmap, reg,
+ BD96801_BUCK_INT_VOUT_MASK,
+ sel);
+ if (ret)
+ return ret;
+get_initial:
+ ret = regmap_read(regmap, reg, &sel);
+ sel &= BD96801_BUCK_INT_VOUT_MASK;
+
+ ret = linear_range_get_value_array(data->init_ranges,
+ data->num_ranges, sel,
+ &initial_uv);
+ if (ret)
+ return ret;
+
+ data->initial_voltage = initial_uv;
+ dev_dbg(dev, "Tune-scaled initial voltage %u\n",
+ data->initial_voltage);
+ }
+
+ return 0;
+}
+
+static int set_ldo_initial_voltage(struct regmap *regmap,
+ struct device *dev,
+ struct bd96801_regulator_data *data,
+ struct device_node *np)
+{
+ int ret, val, stby;
+ u32 initial_uv;
+ int cfgreg = 0;
+ int mask = BD96801_LDO_SD_VOLT_MASK | BD96801_LDO_MODE_MASK;
+
+ ret = of_property_read_u32(np, "rohm,initial-voltage-microvolt",
+ &initial_uv);
+ if (ret) {
+ if (ret == EINVAL)
+ goto get_initial;
+ return ret;
+ }
+
+ /* We can only change initial voltage when PMIC is in STANDBY */
+ stby = bd96801_in_stby(regmap);
+ if (stby < 0)
+ return stby;
+
+ if (!stby) {
+ dev_warn(dev, "Can't set initial voltage, PMIC not in STANDBY\n");
+ goto get_initial;
+ }
+
+ ret = regmap_read(regmap, data->desc.enable_reg, &val);
+ if (ret)
+ return ret;
+
+ if ((val & data->desc.enable_mask) != data->desc.enable_mask) {
+ dev_warn(dev, "%s: enabled. Can't set initial voltage\n",
+ data->desc.name);
+ goto get_initial;
+ }
+
+ /* Regulator is Disabled */
+ dev_dbg(dev, "%s: Setting INITIAL voltage %u\n", data->desc.name,
+ initial_uv);
+
+ /*
+ * Should this be two properties - one for mode (SD/DDR) and
+ * one for voltage (3.3, 1.8, 0.5, 0.3?)
+ */
+ switch (initial_uv) {
+ case 300000:
+ cfgreg |= BD96801_LDO_MODE_DDR;
+ cfgreg |= 1;
+ break;
+ case 500000:
+ cfgreg |= BD96801_LDO_MODE_DDR;
+ break;
+ case 1800000:
+ cfgreg |= BD96801_LDO_MODE_SD;
+ cfgreg |= 1;
+ break;
+ case 3300000:
+ cfgreg |= BD96801_LDO_MODE_SD;
+ break;
+ default:
+ dev_err(dev, "unsupported initial voltage for LDO\n");
+ return -EINVAL;
+ }
+ ret = regmap_update_bits(regmap, data->ldo_vol_lvl, mask,
+ cfgreg);
+ if (ret)
+ return ret;
+
+get_initial:
+ if (!cfgreg) {
+ ret = regmap_read(regmap, data->ldo_vol_lvl, &cfgreg);
+ if (ret)
+ return ret;
+ }
+ switch (cfgreg & BD96801_LDO_MODE_MASK) {
+ case BD96801_LDO_MODE_DDR:
+ data->desc.volt_table = ldo_ddr_volt_table;
+ data->desc.n_voltages = ARRAY_SIZE(ldo_ddr_volt_table);
+ break;
+ case BD96801_LDO_MODE_SD:
+ data->desc.volt_table = ldo_sd_volt_table;
+ data->desc.n_voltages = ARRAY_SIZE(ldo_sd_volt_table);
+ break;
+ default:
+ dev_info(dev, "Leaving LDO to normal mode");
+ return 0;
+ }
+
+ /* SD or DDR mode => override default ops */
+ data->desc.ops = &bd96801_ldo_table_ops,
+ data->desc.vsel_mask = 1;
+ data->desc.vsel_reg = data->ldo_vol_lvl;
+
+ return 0;
+}
+
+static int set_initial_voltage(struct device *dev, struct regmap *regmap,
+ struct bd96801_regulator_data *data,
+ struct device_node *np)
+{
+ /* BUCK */
+ if (data->desc.id <= BD96801_BUCK4)
+ return buck_set_initial_voltage(regmap, dev, data, np);
+
+ /* LDO */
+ return set_ldo_initial_voltage(regmap, dev, data, np);
+}
+
+static int bd96801_walk_regulator_dt(struct device *dev, struct regmap *regmap,
+ struct bd96801_regulator_data *data,
+ int num)
+{
+ int i, ret;
+ struct device_node *np;
+ struct device_node *nproot = dev->parent->of_node;
+
+ nproot = of_get_child_by_name(nproot, "regulators");
+ if (!nproot) {
+ dev_err(dev, "failed to find regulators node\n");
+ return -ENODEV;
+ }
+ for_each_child_of_node(nproot, np)
+ for (i = 0; i < num; i++) {
+ if (!of_node_name_eq(np, data[i].desc.of_match))
+ continue;
+ ret = set_initial_voltage(dev, regmap, &data[i], np);
+ if (ret) {
+ dev_err(dev,
+ "Initializing voltages for %s failed\n",
+ data[i].desc.name);
+ of_node_put(np);
+ of_node_put(nproot);
+
+ return ret;
+ }
+ if (of_property_read_bool(np, "rohm,keep-on-stby")) {
+ ret = regmap_set_bits(regmap,
+ BD96801_ALWAYS_ON_REG,
+ 1 << data[i].desc.id);
+ if (ret) {
+ dev_err(dev,
+ "failed to set %s on-at-stby\n",
+ data[i].desc.name);
+ of_node_put(np);
+ of_node_put(nproot);
+
+ return ret;
+ }
+ }
+ }
+ of_node_put(nproot);
+
+ return 0;
+}
+
+/*
+ * Template for regulator data. Probe will allocate dynamic / driver instance
+ * struct so we should be on a safe side even if there were multiple PMICs to
+ * control. Note that there is a plan to allow multiple PMICs to be used so
+ * systems can scale better. I am however still slightly unsure how the
+ * multi-PMIC case will be handled. I don't know if the processor will have I2C
+ * acces to all of the PMICs or only the first one. I'd guess there will be
+ * access provided to all PMICs for voltage scaling - but the errors will only
+ * be informed via the master PMIC. Eg, we should prepare to support multiple
+ * driver instances - either with or without the IRQs... Well, let's first
+ * just support the simple and clear single-PMIC setup and ponder the multi PMIC
+ * case later. What we can easly do for preparing is to not use static global
+ * data for regulators though.
+ */
+static const struct bd96801_pmic_data bd96801_data = {
+ .regulator_data = {
+ {
+ .desc = {
+ .name = "buck1",
+ .of_match = of_match_ptr("BUCK1"),
+ .regulators_node = of_match_ptr("regulators"),
+ .id = BD96801_BUCK1,
+ .ops = &bd96801_buck_ops,
+ .type = REGULATOR_VOLTAGE,
+ .linear_ranges = bd96801_tune_volts,
+ .n_linear_ranges = ARRAY_SIZE(bd96801_tune_volts),
+ .n_voltages = BD96801_BUCK_VOLTS,
+ .enable_reg = BD96801_REG_ENABLE,
+ .enable_mask = BD96801_BUCK1_EN_MASK,
+ .enable_is_inverted = true,
+ .vsel_reg = BD96801_BUCK1_VSEL_REG,
+ .vsel_mask = BD96801_BUCK_VSEL_MASK,
+ .ramp_reg = BD96801_BUCK1_VSEL_REG,
+ .ramp_mask = BD96801_MASK_RAMP_DELAY,
+ .ramp_delay_table = &buck_ramp_table[0],
+ .n_ramp_values = ARRAY_SIZE(buck_ramp_table),
+ .owner = THIS_MODULE,
+ },
+ .init_ranges = bd96801_buck_init_volts,
+ .num_ranges = ARRAY_SIZE(bd96801_buck_init_volts),
+ .irq_desc = {
+ .irqinfo = (struct bd96801_irqinfo *)&buck1_irqinfo[0],
+ .num_irqs = ARRAY_SIZE(buck1_irqinfo),
+ },
+ .prot_reg_shift = BD96801_MASK_BUCK1_OVP_SHIFT,
+ .ovp_reg = BD96801_REG_BUCK_OVP,
+ .ovd_reg = BD96801_REG_BUCK_OVD,
+ .ocp_table = bd96801_buck12_ocp,
+ .ocp_reg = BD96801_REG_BUCK1_OCP,
+ .ocp_shift = BD96801_MASK_BUCK1_OCP_SHIFT,
+ },
+ {
+ .desc = {
+ .name = "buck2",
+ .of_match = of_match_ptr("BUCK2"),
+ .regulators_node = of_match_ptr("regulators"),
+ .id = BD96801_BUCK2,
+ .ops = &bd96801_buck_ops,
+ .type = REGULATOR_VOLTAGE,
+ .linear_ranges = bd96801_tune_volts,
+ .n_linear_ranges = ARRAY_SIZE(bd96801_tune_volts),
+ .n_voltages = BD96801_BUCK_VOLTS,
+ .enable_reg = BD96801_REG_ENABLE,
+ .enable_mask = BD96801_BUCK2_EN_MASK,
+ .enable_is_inverted = true,
+ .vsel_reg = BD96801_BUCK2_VSEL_REG,
+ .vsel_mask = BD96801_BUCK_VSEL_MASK,
+ .ramp_reg = BD96801_BUCK2_VSEL_REG,
+ .ramp_mask = BD96801_MASK_RAMP_DELAY,
+ .ramp_delay_table = &buck_ramp_table[0],
+ .n_ramp_values = ARRAY_SIZE(buck_ramp_table),
+ .owner = THIS_MODULE,
+ },
+ .irq_desc = {
+ .irqinfo = (struct bd96801_irqinfo *)&buck2_irqinfo[0],
+ .num_irqs = ARRAY_SIZE(buck2_irqinfo),
+ },
+ .init_ranges = bd96801_buck_init_volts,
+ .num_ranges = ARRAY_SIZE(bd96801_buck_init_volts),
+ .prot_reg_shift = BD96801_MASK_BUCK2_OVP_SHIFT,
+ .ovp_reg = BD96801_REG_BUCK_OVP,
+ .ovd_reg = BD96801_REG_BUCK_OVD,
+ .ocp_table = bd96801_buck12_ocp,
+ .ocp_reg = BD96801_REG_BUCK2_OCP,
+ .ocp_shift = BD96801_MASK_BUCK2_OCP_SHIFT,
+ },
+ {
+ .desc = {
+ .name = "buck3",
+ .of_match = of_match_ptr("BUCK3"),
+ .regulators_node = of_match_ptr("regulators"),
+ .id = BD96801_BUCK3,
+ .ops = &bd96801_buck_ops,
+ .type = REGULATOR_VOLTAGE,
+ .linear_ranges = bd96801_tune_volts,
+ .n_linear_ranges = ARRAY_SIZE(bd96801_tune_volts),
+ .n_voltages = BD96801_BUCK_VOLTS,
+ .enable_reg = BD96801_REG_ENABLE,
+ .enable_mask = BD96801_BUCK3_EN_MASK,
+ .enable_is_inverted = true,
+ .vsel_reg = BD96801_BUCK3_VSEL_REG,
+ .vsel_mask = BD96801_BUCK_VSEL_MASK,
+ .ramp_reg = BD96801_BUCK3_VSEL_REG,
+ .ramp_mask = BD96801_MASK_RAMP_DELAY,
+ .ramp_delay_table = &buck_ramp_table[0],
+ .n_ramp_values = ARRAY_SIZE(buck_ramp_table),
+ .owner = THIS_MODULE,
+ },
+ .irq_desc = {
+ .irqinfo = (struct bd96801_irqinfo *)&buck3_irqinfo[0],
+ .num_irqs = ARRAY_SIZE(buck3_irqinfo),
+ },
+ .init_ranges = bd96801_buck_init_volts,
+ .num_ranges = ARRAY_SIZE(bd96801_buck_init_volts),
+ .prot_reg_shift = BD96801_MASK_BUCK3_OVP_SHIFT,
+ .ovp_reg = BD96801_REG_BUCK_OVP,
+ .ovd_reg = BD96801_REG_BUCK_OVD,
+ .ocp_table = bd96801_buck34_ocp,
+ .ocp_reg = BD96801_REG_BUCK3_OCP,
+ .ocp_shift = BD96801_MASK_BUCK3_OCP_SHIFT,
+ },
+ {
+ .desc = {
+ .name = "buck4",
+ .of_match = of_match_ptr("BUCK4"),
+ .regulators_node = of_match_ptr("regulators"),
+ .id = BD96801_BUCK4,
+ .ops = &bd96801_buck_ops,
+ .type = REGULATOR_VOLTAGE,
+ .linear_ranges = bd96801_tune_volts,
+ .n_linear_ranges = ARRAY_SIZE(bd96801_tune_volts),
+ .n_voltages = BD96801_BUCK_VOLTS,
+ .enable_reg = BD96801_REG_ENABLE,
+ .enable_mask = BD96801_BUCK4_EN_MASK,
+ .enable_is_inverted = true,
+ .vsel_reg = BD96801_BUCK4_VSEL_REG,
+ .vsel_mask = BD96801_BUCK_VSEL_MASK,
+ .ramp_reg = BD96801_BUCK4_VSEL_REG,
+ .ramp_mask = BD96801_MASK_RAMP_DELAY,
+ .ramp_delay_table = &buck_ramp_table[0],
+ .n_ramp_values = ARRAY_SIZE(buck_ramp_table),
+ .owner = THIS_MODULE,
+ },
+ .irq_desc = {
+ .irqinfo = (struct bd96801_irqinfo *)&buck4_irqinfo[0],
+ .num_irqs = ARRAY_SIZE(buck4_irqinfo),
+ },
+ .init_ranges = bd96801_buck_init_volts,
+ .num_ranges = ARRAY_SIZE(bd96801_buck_init_volts),
+ .prot_reg_shift = BD96801_MASK_BUCK4_OVP_SHIFT,
+ .ovp_reg = BD96801_REG_BUCK_OVP,
+ .ovd_reg = BD96801_REG_BUCK_OVD,
+ .ocp_table = bd96801_buck34_ocp,
+ .ocp_reg = BD96801_REG_BUCK4_OCP,
+ .ocp_shift = BD96801_MASK_BUCK4_OCP_SHIFT,
+ },
+ {
+ .desc = {
+ .name = "ldo5",
+ .of_match = of_match_ptr("LDO5"),
+ .regulators_node = of_match_ptr("regulators"),
+ .id = BD96801_LDO5,
+ .ops = &bd96801_ldo_ops,
+ .type = REGULATOR_VOLTAGE,
+ .linear_ranges = bd96801_ldo_int_volts,
+ .n_linear_ranges = ARRAY_SIZE(bd96801_ldo_int_volts),
+ .n_voltages = BD96801_LDO_VOLTS,
+ .enable_reg = BD96801_REG_ENABLE,
+ .enable_mask = BD96801_LDO5_EN_MASK,
+ .enable_is_inverted = true,
+ .vsel_reg = BD96801_LDO5_VSEL_REG,
+ .vsel_mask = BD96801_LDO_VSEL_MASK,
+ .owner = THIS_MODULE,
+ },
+ .irq_desc = {
+ .irqinfo = (struct bd96801_irqinfo *)&ldo5_irqinfo[0],
+ .num_irqs = ARRAY_SIZE(ldo5_irqinfo),
+ },
+ .ldo_vol_lvl = BD96801_LDO5_VOL_LVL_REG,
+ .prot_reg_shift = BD96801_MASK_LDO5_OVP_SHIFT,
+ .ovp_reg = BD96801_REG_LDO_OVP,
+ .ovd_reg = BD96801_REG_LDO_OVD,
+ .ocp_table = bd96801_ldo_ocp,
+ .ocp_reg = BD96801_REG_LDO5_OCP,
+ .ocp_shift = BD96801_MASK_LDO5_OCP_SHIFT,
+ },
+ {
+ .desc = {
+ .name = "ldo6",
+ .of_match = of_match_ptr("LDO6"),
+ .regulators_node = of_match_ptr("regulators"),
+ .id = BD96801_LDO6,
+ .ops = &bd96801_ldo_ops,
+ .type = REGULATOR_VOLTAGE,
+ .linear_ranges = bd96801_ldo_int_volts,
+ .n_linear_ranges = ARRAY_SIZE(bd96801_ldo_int_volts),
+ .n_voltages = BD96801_LDO_VOLTS,
+ .enable_reg = BD96801_REG_ENABLE,
+ .enable_mask = BD96801_LDO6_EN_MASK,
+ .enable_is_inverted = true,
+ .vsel_reg = BD96801_LDO6_VSEL_REG,
+ .vsel_mask = BD96801_LDO_VSEL_MASK,
+ .owner = THIS_MODULE,
+ },
+ .irq_desc = {
+ .irqinfo = (struct bd96801_irqinfo *)&ldo6_irqinfo[0],
+ .num_irqs = ARRAY_SIZE(ldo6_irqinfo),
+ },
+ .ldo_vol_lvl = BD96801_LDO6_VOL_LVL_REG,
+ .prot_reg_shift = BD96801_MASK_LDO6_OVP_SHIFT,
+ .ovp_reg = BD96801_REG_LDO_OVP,
+ .ovd_reg = BD96801_REG_LDO_OVD,
+ .ocp_table = bd96801_ldo_ocp,
+ .ocp_reg = BD96801_REG_LDO6_OCP,
+ .ocp_shift = BD96801_MASK_LDO6_OCP_SHIFT,
+ },
+ {
+ .desc = {
+ .name = "ldo7",
+ .of_match = of_match_ptr("LDO7"),
+ .regulators_node = of_match_ptr("regulators"),
+ .id = BD96801_LDO7,
+ .ops = &bd96801_ldo_ops,
+ .type = REGULATOR_VOLTAGE,
+ .linear_ranges = bd96801_ldo_int_volts,
+ .n_linear_ranges = ARRAY_SIZE(bd96801_ldo_int_volts),
+ .n_voltages = BD96801_LDO_VOLTS,
+ .enable_reg = BD96801_REG_ENABLE,
+ .enable_mask = BD96801_LDO7_EN_MASK,
+ .enable_is_inverted = true,
+ .vsel_reg = BD96801_LDO7_VSEL_REG,
+ .vsel_mask = BD96801_LDO_VSEL_MASK,
+ .owner = THIS_MODULE,
+ },
+ .irq_desc = {
+ .irqinfo = (struct bd96801_irqinfo *)&ldo7_irqinfo[0],
+ .num_irqs = ARRAY_SIZE(ldo7_irqinfo),
+ },
+ .ldo_vol_lvl = BD96801_LDO7_VOL_LVL_REG,
+ .prot_reg_shift = BD96801_MASK_LDO7_OVP_SHIFT,
+ .ovp_reg = BD96801_REG_LDO_OVP,
+ .ovd_reg = BD96801_REG_LDO_OVD,
+ .ocp_table = bd96801_ldo_ocp,
+ .ocp_reg = BD96801_REG_LDO7_OCP,
+ .ocp_shift = BD96801_MASK_LDO7_OCP_SHIFT,
+ },
+ },
+ .fatal_ind = -1,
+};
+
+static int initialize_pmic_data(struct device *dev,
+ struct bd96801_pmic_data *pdata)
+{
+ int r, i;
+
+ /*
+ * Allocate and initialize IRQ data for all of the regulators. We
+ * wish to modify IRQ information independently for each driver
+ * instance.
+ */
+ for (r = 0; r < BD96801_NUM_REGULATORS; r++) {
+ const struct bd96801_irqinfo *template;
+ struct bd96801_irqinfo *new;
+ int num_infos;
+
+ template = pdata->regulator_data[r].irq_desc.irqinfo;
+ num_infos = pdata->regulator_data[r].irq_desc.num_irqs;
+
+ new = devm_kcalloc(dev, num_infos, sizeof(*new), GFP_KERNEL);
+ if (!new)
+ return -ENOMEM;
+
+ pdata->regulator_data[r].irq_desc.irqinfo = new;
+
+ for (i = 0; i < num_infos; i++)
+ new[i] = template[i];
+ }
+
+ return 0;
+}
+
+static int bd96801_map_event_all(int irq, struct regulator_irq_data *rid,
+ unsigned long *dev_mask)
+{
+ int i;
+
+ for (i = 0; i < rid->num_states; i++) {
+ rid->states[i].notifs = REGULATOR_EVENT_FAIL;
+ rid->states[i].errors = REGULATOR_ERROR_FAIL;
+ *dev_mask |= BIT(i);
+ }
+
+ return 0;
+}
+
+static int bd96801_rdev_errb_irqs(struct platform_device *pdev,
+ struct regulator_dev *rdev)
+{
+ int i;
+ void *retp;
+ static const char * const single_out_errb_irqs[] = {
+ "bd96801-%s-pvin-err", "bd96801-%s-ovp-err",
+ "bd96801-%s-uvp-err", "bd96801-%s-shdn-err",
+ };
+
+ for (i = 0; i < ARRAY_SIZE(single_out_errb_irqs); i++) {
+ char tmp[255];
+ int irq;
+ struct regulator_irq_desc id = {
+ .map_event = bd96801_map_event_all,
+ .irq_off_ms = 1000,
+ };
+ struct regulator_dev *rdev_arr[1] = { rdev };
+
+ snprintf(tmp, 255, single_out_errb_irqs[i], rdev->desc->name);
+ tmp[254] = 0;
+ id.name = tmp;
+
+ irq = platform_get_irq_byname(pdev, tmp);
+ if (irq < 0)
+ continue;
+
+ retp = devm_regulator_irq_helper(&pdev->dev, &id, irq, 0,
+ REGULATOR_ERROR_FAIL, NULL,
+ rdev_arr, 1);
+ if (IS_ERR(retp))
+ return PTR_ERR(retp);
+
+ }
+ return 0;
+}
+
+static int bd96801_global_errb_irqs(struct platform_device *pdev,
+ struct regulator_dev **rdev, int num_rdev)
+{
+ int i, num_irqs;
+ void *retp;
+ static const char * const global_errb_irqs[] = {
+ "bd96801-otp-err", "bd96801-dbist-err", "bd96801-eep-err",
+ "bd96801-abist-err", "bd96801-prstb-err", "bd96801-drmoserr1",
+ "bd96801-drmoserr2", "bd96801-slave-err", "bd96801-vref-err",
+ "bd96801-tsd", "bd96801-uvlo-err", "bd96801-ovlo-err",
+ "bd96801-osc-err", "bd96801-pon-err", "bd96801-poff-err",
+ "bd96801-cmd-shdn-err", "bd96801-int-shdn-err"
+ };
+
+ num_irqs = ARRAY_SIZE(global_errb_irqs);
+ for (i = 0; i < num_irqs; i++) {
+ int irq;
+ struct regulator_irq_desc id = {
+ .name = global_errb_irqs[i],
+ .map_event = bd96801_map_event_all,
+ .irq_off_ms = 1000,
+ };
+
+ irq = platform_get_irq_byname(pdev, global_errb_irqs[i]);
+ if (irq < 0)
+ continue;
+
+ retp = devm_regulator_irq_helper(&pdev->dev, &id, irq, 0,
+ REGULATOR_ERROR_FAIL, NULL,
+ rdev, num_rdev);
+ if (IS_ERR(retp))
+ return PTR_ERR(retp);
+ }
+
+ return 0;
+}
+
+static int bd96801_probe(struct platform_device *pdev)
+{
+ struct device *parent;
+ int i, ret, irq;
+ void *retp;
+ struct regulator_config config = {};
+ struct bd96801_regulator_data *rdesc;
+ struct bd96801_pmic_data *pdata;
+ struct regulator_dev *ldo_errs_rdev_arr[BD96801_NUM_LDOS];
+ int ldo_errs_arr[BD96801_NUM_LDOS];
+ int temp_notif_ldos = 0;
+ struct regulator_dev *all_rdevs[BD96801_NUM_REGULATORS];
+ bool use_errb;
+
+ parent = pdev->dev.parent;
+
+ pdata = devm_kmemdup(&pdev->dev, &bd96801_data, sizeof(bd96801_data),
+ GFP_KERNEL);
+ if (!pdata)
+ return -ENOMEM;
+
+ if (initialize_pmic_data(&pdev->dev, pdata))
+ return -ENOMEM;
+
+ pdata->regmap = dev_get_regmap(parent, NULL);
+ if (!pdata->regmap) {
+ dev_err(&pdev->dev, "No register map found\n");
+ return -ENODEV;
+ }
+
+ rdesc = &pdata->regulator_data[0];
+
+ config.driver_data = pdata;
+ config.regmap = pdata->regmap;
+ config.dev = parent;
+
+ ret = of_property_match_string(pdev->dev.parent->of_node,
+ "interrupt-names", "errb");
+ if (ret < 0)
+ use_errb = false;
+ else
+ use_errb = true;
+
+
+ ret = regmap_write(pdata->regmap, BD96801_LOCK_REG, BD96801_UNLOCK);
+ if (ret) {
+ dev_err(&pdev->dev, "Can't unlock PMIC\n");
+ return ret;
+ }
+
+ ret = bd96801_walk_regulator_dt(&pdev->dev, pdata->regmap, rdesc,
+ BD96801_NUM_REGULATORS);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < ARRAY_SIZE(pdata->regulator_data); i++) {
+ struct regulator_dev *rdev;
+ struct regulator_dev *rdev_arr[1];
+ struct bd96801_irq_desc *idesc = &rdesc[i].irq_desc;
+ int j, stby;
+
+ rdev = devm_regulator_register(&pdev->dev,
+ &rdesc[i].desc, &config);
+ if (IS_ERR(rdev)) {
+ dev_err(&pdev->dev,
+ "failed to register %s regulator\n",
+ rdesc[i].desc.name);
+ return PTR_ERR(rdev);
+ }
+ all_rdevs[i] = rdev;
+ if (pdata->fatal_ind) {
+ stby = bd96801_in_stby(pdata->regmap);
+ if (stby < 0)
+ return stby;
+
+ if (!stby)
+ dev_warn(&pdev->dev,
+ "PMIC not in STANDBY. Can't change INTB fatality\n");
+
+ /*
+ * This means we may set the INTB fatality many times
+ * but it's better to enable it immediately after a
+ * regulator is enabled to protect early on.
+ */
+ ret = regmap_update_bits(pdata->regmap,
+ BD96801_REG_SHD_INTB,
+ BD96801_MASK_SHD_INTB,
+ BD96801_INTB_FATAL);
+ if (ret)
+ return ret;
+ }
+ /*
+ * LDOs don't have own temperature monitoring. If temperature
+ * notification was requested for this LDO from DT then we will
+ * add the regulator to be notified if central IC temperature
+ * exceeds threshold.
+ */
+ if (rdesc[i].ldo_errs) {
+ ldo_errs_rdev_arr[temp_notif_ldos] = rdev;
+ ldo_errs_arr[temp_notif_ldos] = rdesc[i].ldo_errs;
+ temp_notif_ldos++;
+ }
+ if (!idesc)
+ continue;
+ /*
+ * TODO: Can we split adding the INTB notifiers in own
+ * function ?
+ */
+ /* Register INTB handlers for configured protections */
+ for (j = 0; j < idesc->num_irqs; j++) {
+ struct bd96801_irqinfo *iinfo;
+ int err = 0;
+ int err_flags[] = {
+ [BD96801_PROT_OVP] = REGULATOR_ERROR_REGULATION_OUT,
+ [BD96801_PROT_UVP] = REGULATOR_ERROR_UNDER_VOLTAGE,
+ [BD96801_PROT_OCP] = REGULATOR_ERROR_OVER_CURRENT,
+ [BD96801_PROT_TEMP] = REGULATOR_ERROR_OVER_TEMP,
+
+ };
+ int wrn_flags[] = {
+ [BD96801_PROT_OVP] = REGULATOR_ERROR_OVER_VOLTAGE_WARN,
+ [BD96801_PROT_UVP] = REGULATOR_ERROR_UNDER_VOLTAGE_WARN,
+ [BD96801_PROT_OCP] = REGULATOR_ERROR_OVER_CURRENT_WARN,
+ [BD96801_PROT_TEMP] = REGULATOR_ERROR_OVER_TEMP_WARN,
+ };
+
+ iinfo = &idesc->irqinfo[j];
+ /*
+ * Don't install IRQ handler if both error and warning
+ * notifications are explicitly disabled
+ */
+ if (!iinfo->err_cfg && !iinfo->wrn_cfg)
+ continue;
+
+ if (WARN_ON(iinfo->type >= BD96801_NUM_PROT))
+ return -EINVAL;
+
+ if (iinfo->err_cfg)
+ err = err_flags[iinfo->type];
+ else if (iinfo->wrn_cfg)
+ err = wrn_flags[iinfo->type];
+
+ iinfo->irq_desc.data = pdata;
+ irq = platform_get_irq_byname(pdev, iinfo->irq_name);
+ if (irq < 0)
+ return irq;
+ /* Find notifications for this IRQ (WARN/ERR) */
+
+ rdev_arr[0] = rdev;
+ retp = devm_regulator_irq_helper(&pdev->dev,
+ &iinfo->irq_desc, irq,
+ 0, err, NULL, rdev_arr,
+ 1);
+ if (IS_ERR(retp))
+ return PTR_ERR(retp);
+ }
+ /* Register per regulator ERRB notifiers */
+ if (use_errb) {
+ ret = bd96801_rdev_errb_irqs(pdev, rdev);
+ if (ret)
+ return ret;
+ }
+ }
+ if (temp_notif_ldos) {
+ int irq;
+ struct regulator_irq_desc tw_desc = {
+ .name = "bd96801-core-thermal",
+ .irq_off_ms = 500,
+ .map_event = ldo_map_notif,
+ };
+
+ irq = platform_get_irq_byname(pdev, "bd96801-core-thermal");
+ if (irq < 0)
+ return irq;
+
+ retp = devm_regulator_irq_helper(&pdev->dev, &tw_desc, irq, 0,
+ 0, &ldo_errs_arr[0],
+ &ldo_errs_rdev_arr[0],
+ temp_notif_ldos);
+ if (IS_ERR(retp))
+ return PTR_ERR(retp);
+ }
+
+ if (use_errb)
+ return bd96801_global_errb_irqs(pdev, all_rdevs,
+ ARRAY_SIZE(all_rdevs));
+
+ return 0;
+}
+
+static const struct platform_device_id bd96801_pmic_id[] = {
+ { "bd96801-pmic", },
+ { }
+};
+MODULE_DEVICE_TABLE(platform, bd96801_pmic_id);
+
+static struct platform_driver bd96801_regulator = {
+ .driver = {
+ .name = "bd96801-pmic"
+ },
+ .probe = bd96801_probe,
+ .id_table = bd96801_pmic_id,
+};
+
+module_platform_driver(bd96801_regulator);
+
+MODULE_AUTHOR("Matti Vaittinen <[email protected]>");
+MODULE_DESCRIPTION("BD96801 voltage regulator driver");
+MODULE_LICENSE("GPL");
--
2.43.2
--
Matti Vaittinen, Linux device drivers
ROHM Semiconductors, Finland SWDC
Kiviharjunlenkki 1E
90220 OULU
FINLAND
~~~ "I don't think so," said Rene Descartes. Just then he vanished ~~~
Simon says - in Latin please.
~~~ "non cogito me" dixit Rene Descarte, deinde evanescavit ~~~
Thanks to Simon Glass for the translation =]
Introduce driver for WDG block on ROHM BD96801 scalable PMIC.
This driver only supports watchdog with I2C feeding and delayed
response detection. Whether the watchdog toggles PRSTB pin or
just causes an interrupt can be configured via device-tree.
The BD96801 PMIC HW supports also window watchdog (too early
feeding detection) and Q&A mode. These are not supported by
this driver.
Signed-off-by: Matti Vaittinen <[email protected]>
---
Revision history:
RFCv1 => RFCv2:
- remove always running
- add IRQ handling
- call emergency_restart()
- drop MODULE_ALIAS and add MODULE_DEVICE_TABLE
---
drivers/watchdog/Kconfig | 13 ++
drivers/watchdog/Makefile | 1 +
drivers/watchdog/bd96801_wdt.c | 389 +++++++++++++++++++++++++++++++++
3 files changed, 403 insertions(+)
create mode 100644 drivers/watchdog/bd96801_wdt.c
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index 6bee137cfbe0..d97e735e1faa 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -181,6 +181,19 @@ config BD957XMUF_WATCHDOG
watchdog. Alternatively say M to compile the driver as a module,
which will be called bd9576_wdt.
+config BD96801_WATCHDOG
+ tristate "ROHM BD96801 PMIC Watchdog"
+ depends on MFD_ROHM_BD96801
+ select WATCHDOG_CORE
+ help
+ Support for the watchdog in the ROHM BD96801 PMIC. Watchdog can be
+ configured to only generate IRQ or to trigger system reset via reset
+ pin.
+
+ Say Y here to include support for the ROHM BD96801 watchdog.
+ Alternatively say M to compile the driver as a module,
+ which will be called bd96801_wdt.
+
config CROS_EC_WATCHDOG
tristate "ChromeOS EC-based watchdog"
select WATCHDOG_CORE
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index 3710c218f05e..31bc94436c81 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -217,6 +217,7 @@ obj-$(CONFIG_XEN_WDT) += xen_wdt.o
# Architecture Independent
obj-$(CONFIG_BD957XMUF_WATCHDOG) += bd9576_wdt.o
+obj-$(CONFIG_BD96801_WATCHDOG) += bd96801_wdt.o
obj-$(CONFIG_CROS_EC_WATCHDOG) += cros_ec_wdt.o
obj-$(CONFIG_DA9052_WATCHDOG) += da9052_wdt.o
obj-$(CONFIG_DA9055_WATCHDOG) += da9055_wdt.o
diff --git a/drivers/watchdog/bd96801_wdt.c b/drivers/watchdog/bd96801_wdt.c
new file mode 100644
index 000000000000..08fab9a87aec
--- /dev/null
+++ b/drivers/watchdog/bd96801_wdt.c
@@ -0,0 +1,389 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2024 ROHM Semiconductors
+ *
+ * ROHM BD96801 watchdog driver
+ */
+
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/mfd/rohm-bd96801.h>
+#include <linux/mfd/rohm-generic.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/watchdog.h>
+
+static bool nowayout;
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout,
+ "Watchdog cannot be stopped once started (default=\"false\")");
+
+#define BD96801_WD_TMO_SHORT_MASK 0x70
+#define BD96801_WD_RATIO_MASK 0x3
+#define BD96801_WD_TYPE_MASK 0x4
+#define BD96801_WD_TYPE_SLOW 0x4
+#define BD96801_WD_TYPE_WIN 0x0
+
+#define BD96801_WD_EN_MASK 0x3
+#define BD96801_WD_IF_EN 0x1
+#define BD96801_WD_QA_EN 0x2
+#define BD96801_WD_DISABLE 0x0
+
+#define BD96801_WD_ASSERT_MASK 0x8
+#define BD96801_WD_ASSERT_RST 0x8
+#define BD96801_WD_ASSERT_IRQ 0x0
+
+#define BD96801_WD_FEED_MASK 0x1
+#define BD96801_WD_FEED 0x1
+
+/* units in uS */
+#define FASTNG_MIN 3370
+#define BD96801_WDT_DEFAULT_MARGIN 6905120
+/* Unit is seconds */
+#define DEFAULT_TIMEOUT 30
+
+/*
+ * BD96801 WDG supports window mode so the TMO consists of SHORT and LONG
+ * timeout values. SHORT time is meaningfull only in window mode where feeding
+ * period shorter than SHORT would be an error. LONG time is used to detect if
+ * feeding is not occurring within given time limit (SoC SW hangs). The LONG
+ * timeout time is a multiple of (2, 4, 8 0r 16 times) the SHORT timeout.
+ */
+
+struct wdtbd96801 {
+ struct device *dev;
+ struct regmap *regmap;
+ struct watchdog_device wdt;
+};
+
+static int bd96801_wdt_ping(struct watchdog_device *wdt)
+{
+ struct wdtbd96801 *w = watchdog_get_drvdata(wdt);
+
+ return regmap_update_bits(w->regmap, BD96801_REG_WD_FEED,
+ BD96801_WD_FEED_MASK, BD96801_WD_FEED);
+}
+
+static int bd96801_wdt_start(struct watchdog_device *wdt)
+{
+ struct wdtbd96801 *w = watchdog_get_drvdata(wdt);
+ int ret;
+
+ ret = regmap_update_bits(w->regmap, BD96801_REG_WD_CONF,
+ BD96801_WD_EN_MASK, BD96801_WD_IF_EN);
+
+ return ret;
+}
+
+static int bd96801_wdt_stop(struct watchdog_device *wdt)
+{
+ struct wdtbd96801 *w = watchdog_get_drvdata(wdt);
+
+ return regmap_update_bits(w->regmap, BD96801_REG_WD_CONF,
+ BD96801_WD_EN_MASK, BD96801_WD_DISABLE);
+}
+
+static const struct watchdog_info bd96801_wdt_info = {
+ .options = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING |
+ WDIOF_SETTIMEOUT,
+ .identity = "BD96801 Watchdog",
+};
+
+static const struct watchdog_ops bd96801_wdt_ops = {
+ .start = bd96801_wdt_start,
+ .stop = bd96801_wdt_stop,
+ .ping = bd96801_wdt_ping,
+};
+
+static int find_closest_fast(int target, int *sel, int *val)
+{
+ int i;
+ int window = FASTNG_MIN;
+
+ for (i = 0; i < 8 && window < target; i++)
+ window <<= 1;
+
+ *val = window;
+ *sel = i;
+
+ if (i == 8)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int find_closest_slow_by_fast(int fast_val, int *target, int *slowsel)
+{
+ int sel;
+ static const int multipliers[] = {2, 4, 8, 16};
+
+ for (sel = 0; sel < ARRAY_SIZE(multipliers) &&
+ multipliers[sel] * fast_val < *target; sel++)
+ ;
+
+ if (sel == ARRAY_SIZE(multipliers))
+ return -EINVAL;
+
+ *slowsel = sel;
+ *target = multipliers[sel] * fast_val;
+
+ return 0;
+}
+
+static int find_closest_slow(int *target, int *slow_sel, int *fast_sel)
+{
+ static const int multipliers[] = {2, 4, 8, 16};
+ int i, j;
+ int val = 0;
+ int window = FASTNG_MIN;
+
+ for (i = 0; i < 8; i++) {
+ for (j = 0; j < ARRAY_SIZE(multipliers); j++) {
+ int slow;
+
+ slow = window * multipliers[j];
+ if (slow >= *target && (!val || slow < val)) {
+ val = slow;
+ *fast_sel = i;
+ *slow_sel = j;
+ }
+ }
+ window <<= 1;
+ }
+ if (!val)
+ return -EINVAL;
+
+ *target = val;
+
+ return 0;
+}
+
+static int bd96801_set_wdt_mode(struct wdtbd96801 *w, int hw_margin,
+ int hw_margin_min)
+{
+ int ret, fastng, slowng, type, reg, mask;
+ struct device *dev = w->dev;
+
+ /* convert to uS */
+ hw_margin *= 1000;
+ hw_margin_min *= 1000;
+ if (hw_margin_min) {
+ int min;
+
+ type = BD96801_WD_TYPE_WIN;
+ dev_dbg(dev, "Setting type WINDOW 0x%x\n", type);
+ ret = find_closest_fast(hw_margin_min, &fastng, &min);
+ if (ret) {
+ dev_err(dev, "bad WDT window for fast timeout\n");
+ return ret;
+ }
+
+ ret = find_closest_slow_by_fast(min, &hw_margin, &slowng);
+ if (ret) {
+ dev_err(dev, "bad WDT window\n");
+ return ret;
+ }
+ w->wdt.min_hw_heartbeat_ms = min / 1000;
+ } else {
+ type = BD96801_WD_TYPE_SLOW;
+ dev_dbg(dev, "Setting type SLOW 0x%x\n", type);
+ ret = find_closest_slow(&hw_margin, &slowng, &fastng);
+ if (ret) {
+ dev_err(dev, "bad WDT window\n");
+ return ret;
+ }
+ }
+
+ w->wdt.max_hw_heartbeat_ms = hw_margin / 1000;
+
+ fastng <<= ffs(BD96801_WD_TMO_SHORT_MASK) - 1;
+
+ reg = slowng | fastng;
+ mask = BD96801_WD_RATIO_MASK | BD96801_WD_TMO_SHORT_MASK;
+ ret = regmap_update_bits(w->regmap, BD96801_REG_WD_TMO,
+ mask, reg);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(w->regmap, BD96801_REG_WD_CONF,
+ BD96801_WD_TYPE_MASK, type);
+
+ return ret;
+}
+
+static int bd96801_set_heartbeat_from_hw(struct wdtbd96801 *w,
+ unsigned int conf_reg)
+{
+ int ret;
+ unsigned int val, sel, fast;
+
+ /*
+ * The BD96801 supports a somewhat peculiar QA-mode, which we do not
+ * support in this driver. If the QA-mode is enabled then we just
+ * warn and bail-out.
+ */
+ if ((conf_reg & BD96801_WD_EN_MASK) != BD96801_WD_IF_EN) {
+ dev_warn(w->dev, "watchdog set to Q&A mode - exiting\n");
+ return -EINVAL;
+ }
+
+ ret = regmap_read(w->regmap, BD96801_REG_WD_TMO, &val);
+ if (ret)
+ return ret;
+
+ sel = val & BD96801_WD_TMO_SHORT_MASK;
+ sel >>= ffs(BD96801_WD_TMO_SHORT_MASK) - 1;
+ fast = FASTNG_MIN << sel;
+
+ sel = (val & BD96801_WD_RATIO_MASK) + 1;
+ w->wdt.max_hw_heartbeat_ms = (fast << sel) / USEC_PER_MSEC;
+
+ if ((conf_reg & BD96801_WD_TYPE_MASK) == BD96801_WD_TYPE_WIN)
+ w->wdt.min_hw_heartbeat_ms = fast / USEC_PER_MSEC;
+
+ return 0;
+}
+
+static int init_wdg_hw(struct wdtbd96801 *w)
+{
+ u32 hw_margin[2];
+ int count, ret;
+ u32 hw_margin_max = BD96801_WDT_DEFAULT_MARGIN, hw_margin_min = 0;
+
+ count = device_property_count_u32(w->dev->parent, "rohm,hw-timeout-ms");
+ if (count < 0 && count != -EINVAL)
+ return count;
+
+ if (count > 0) {
+ if (count > ARRAY_SIZE(hw_margin))
+ return -EINVAL;
+
+ ret = device_property_read_u32_array(w->dev->parent,
+ "rohm,hw-timeout-ms",
+ &hw_margin[0], count);
+ if (ret < 0)
+ return ret;
+
+ if (count == 1)
+ hw_margin_max = hw_margin[0];
+
+ if (count == 2) {
+ hw_margin_max = hw_margin[1];
+ hw_margin_min = hw_margin[0];
+ }
+ }
+
+ ret = bd96801_set_wdt_mode(w, hw_margin_max, hw_margin_min);
+ if (ret)
+ return ret;
+
+ ret = device_property_match_string(w->dev->parent, "rohm,wdg-action",
+ "prstb");
+ if (ret >= 0) {
+ ret = regmap_update_bits(w->regmap, BD96801_REG_WD_CONF,
+ BD96801_WD_ASSERT_MASK,
+ BD96801_WD_ASSERT_RST);
+ return ret;
+ }
+
+ ret = device_property_match_string(w->dev->parent, "rohm,wdg-action",
+ "intb-only");
+ if (ret >= 0) {
+ ret = regmap_update_bits(w->regmap, BD96801_REG_WD_CONF,
+ BD96801_WD_ASSERT_MASK,
+ BD96801_WD_ASSERT_IRQ);
+ return ret;
+ }
+
+ return 0;
+}
+
+extern void emergency_restart(void);
+static irqreturn_t bd96801_irq_hnd(int irq, void *data)
+{
+ emergency_restart();
+ return IRQ_NONE;
+}
+
+static int bd96801_wdt_probe(struct platform_device *pdev)
+{
+ struct wdtbd96801 *w;
+ int ret, irq;
+ unsigned int val;
+
+ w = devm_kzalloc(&pdev->dev, sizeof(*w), GFP_KERNEL);
+ if (!w)
+ return -ENOMEM;
+
+ w->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ w->dev = &pdev->dev;
+
+ w->wdt.info = &bd96801_wdt_info;
+ w->wdt.ops = &bd96801_wdt_ops;
+ w->wdt.parent = pdev->dev.parent;
+ w->wdt.timeout = DEFAULT_TIMEOUT;
+ watchdog_set_drvdata(&w->wdt, w);
+
+ ret = regmap_read(w->regmap, BD96801_REG_WD_CONF, &val);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "Failed to get the watchdog state\n");
+
+ /*
+ * If the WDG is already enabled we assume it is configured by boot.
+ * In this case we just update the hw-timeout based on values set to
+ * the timeout / mode registers and leave the hardware configs
+ * untouched.
+ */
+ if ((val & BD96801_WD_EN_MASK) != BD96801_WD_DISABLE) {
+ dev_dbg(&pdev->dev, "watchdog was running during probe\n");
+ ret = bd96801_set_heartbeat_from_hw(w, val);
+ if (ret)
+ return ret;
+
+ set_bit(WDOG_HW_RUNNING, &w->wdt.status);
+ } else {
+ /* If WDG is not running so we will initializate it */
+ ret = init_wdg_hw(w);
+ if (ret)
+ return ret;
+ }
+
+ watchdog_init_timeout(&w->wdt, 0, pdev->dev.parent);
+ watchdog_set_nowayout(&w->wdt, nowayout);
+ watchdog_stop_on_reboot(&w->wdt);
+
+ irq = platform_get_irq_byname(pdev, "bd96801-wdg");
+ if (irq > 0) {
+ ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
+ bd96801_irq_hnd,
+ IRQF_ONESHOT, "bd96801-wdg",
+ NULL);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "Failed to register IRQ\n");
+ }
+
+ return devm_watchdog_register_device(&pdev->dev, &w->wdt);
+}
+
+static const struct platform_device_id bd96801_wdt_id[] = {
+ { "bd96801-wdt", },
+ { }
+};
+MODULE_DEVICE_TABLE(platform, bd96801_wdt_id);
+
+static struct platform_driver bd96801_wdt = {
+ .driver = {
+ .name = "bd96801-wdt"
+ },
+ .probe = bd96801_wdt_probe,
+ .id_table = bd96801_wdt_id,
+};
+module_platform_driver(bd96801_wdt);
+
+MODULE_AUTHOR("Matti Vaittinen <[email protected]>");
+MODULE_DESCRIPTION("BD96801 watchdog driver");
+MODULE_LICENSE("GPL");
--
2.43.2
--
Matti Vaittinen, Linux device drivers
ROHM Semiconductors, Finland SWDC
Kiviharjunlenkki 1E
90220 OULU
FINLAND
~~~ "I don't think so," said Rene Descartes. Just then he vanished ~~~
Simon says - in Latin please.
~~~ "non cogito me" dixit Rene Descarte, deinde evanescavit ~~~
Thanks to Simon Glass for the translation =]
Add maintainer entries for ROHM BD96801 a.k.a 'scalable PMIC'
Signed-off-by: Matti Vaittinen <[email protected]>
---
MAINTAINERS | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index aa3b947fb080..da68144d51ae 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -19111,17 +19111,21 @@ F: drivers/gpio/gpio-bd71828.c
F: drivers/mfd/rohm-bd71828.c
F: drivers/mfd/rohm-bd718x7.c
F: drivers/mfd/rohm-bd9576.c
+F: drivers/mfd/rohm-bd96801.c
F: drivers/regulator/bd71815-regulator.c
F: drivers/regulator/bd71828-regulator.c
F: drivers/regulator/bd718x7-regulator.c
F: drivers/regulator/bd9576-regulator.c
+F: drivers/regulator/bd96801-regulator.c
F: drivers/regulator/rohm-regulator.c
F: drivers/rtc/rtc-bd70528.c
F: drivers/watchdog/bd9576_wdt.c
+F: drivers/watchdog/bd96801_wdt.c
F: include/linux/mfd/rohm-bd71815.h
F: include/linux/mfd/rohm-bd71828.h
F: include/linux/mfd/rohm-bd718x7.h
F: include/linux/mfd/rohm-bd957x.h
+F: include/linux/mfd/rohm-bd96801.h
F: include/linux/mfd/rohm-generic.h
F: include/linux/mfd/rohm-shared.h
--
2.43.2
--
Matti Vaittinen, Linux device drivers
ROHM Semiconductors, Finland SWDC
Kiviharjunlenkki 1E
90220 OULU
FINLAND
~~~ "I don't think so," said Rene Descartes. Just then he vanished ~~~
Simon says - in Latin please.
~~~ "non cogito me" dixit Rene Descarte, deinde evanescavit ~~~
Thanks to Simon Glass for the translation =]
On 12/04/2024 13:21, Matti Vaittinen wrote:
> ROHM BD96801 is a highly configurable automotive grade PMIC. Introduce
> DT bindings for the BD96801 regulators.
>
> Signed-off-by: Matti Vaittinen <[email protected]>
> ---
> Revision history:
> - No changes since RFCv1
Subject: missing "regulator" prefix, as first.
>
> .../regulator/rohm,bd96801-regulator.yaml | 69 +++++++++++++++++++
> 1 file changed, 69 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/regulator/rohm,bd96801-regulator.yaml
>
> diff --git a/Documentation/devicetree/bindings/regulator/rohm,bd96801-regulator.yaml b/Documentation/devicetree/bindings/regulator/rohm,bd96801-regulator.yaml
> new file mode 100644
> index 000000000000..4015802a3d84
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/regulator/rohm,bd96801-regulator.yaml
> @@ -0,0 +1,69 @@
> +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/regulator/rohm,bd96801-regulator.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: ROHM BD96801 Power Management Integrated Circuit regulators
> +
> +maintainers:
> + - Matti Vaittinen <[email protected]>
> +
> +description: |
> + This module is part of the ROHM BD96801 MFD device. For more details
> + see Documentation/devicetree/bindings/mfd/rohm,bd96801-pmic.yaml.
> +
> + The regulator controller is represented as a sub-node of the PMIC node
> + on the device tree.
> +
> + Regulator nodes should be named to BUCK_<number> and LDO_<number>.
> + The valid names for BD96801 regulator nodes are
> + BUCK1, BUCK2, BUCK3, BUCK4, LDO5, LDO6, LDO7
> +
> +patternProperties:
> + "^LDO[5-7]$":
lowercase
> + type: object
> + description:
> + Properties for single LDO regulator.
> + $ref: regulator.yaml#
Missing unevaluatedProperties: false
> +
> + properties:
> + regulator-name:
> + pattern: "^ldo[5-7]$"
> + description:
> + Name of the regulator. Should be "ldo5", ..., "ldo7"
Why do you enforce the name? The name should match board schematics, not
regulator datasheet.
> + rohm,initial-voltage-microvolt:
> + description:
> + Initial voltage for regulator. Voltage can be tuned +/-150 mV from
> + this value. NOTE, This can be modified via I2C only when PMIC is in
> + STBY state.
> + minimum: 300000
> + maximum: 3300000
Hm, regulator min/max microvolts properties don't work for you? The
initial will be just middle?
> +
> + "^BUCK[1-4]$":
lowercase
> + type: object
> + description:
> + Properties for single BUCK regulator.
> + $ref: regulator.yaml#
> +
> + properties:
> + regulator-name:
> + pattern: "^buck[1-4]$"
> + description:
> + should be "buck1", ..., "buck4"
> + rohm,initial-voltage-microvolt:
> + description:
> + Initial voltage for regulator. Voltage can be tuned +/-150 mV from
> + this value. NOTE, This can be modified via I2C only when PMIC is in
> + STBY state.
> + minimum: 500000
> + maximum: 3300000
Missing blank line
> + rohm,keep-on-stby:
> + description:
> + Keep the regulator powered when PMIC transitions to STBY state.
> + type: boolean
> +
> + required:
> + - regulator-name
> + additionalProperties: false
Blank line
> +additionalProperties: false
Best regards,
Krzysztof
On 12/04/2024 13:21, Matti Vaittinen wrote:
> ROHM BD96801 is a highly configurable automotive grade PMIC. Introduce
> DT bindings for the BD96801 core.
>
> Signed-off-by: Matti Vaittinen <[email protected]>
>
> ---
> Revision history:
> RFCv1 => RFCv2:
> - Document rohm,hw-timeout-ms
> - Document rohm,wdg-action
> ---
> .../bindings/mfd/rohm,bd96801-pmic.yaml | 171 ++++++++++++++++++
> 1 file changed, 171 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/mfd/rohm,bd96801-pmic.yaml
>
> diff --git a/Documentation/devicetree/bindings/mfd/rohm,bd96801-pmic.yaml b/Documentation/devicetree/bindings/mfd/rohm,bd96801-pmic.yaml
> new file mode 100644
> index 000000000000..31ef787d6a8a
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mfd/rohm,bd96801-pmic.yaml
> @@ -0,0 +1,171 @@
> +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/mfd/rohm,bd96801-pmic.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: ROHM BD96801 Scalable Power Management Integrated Circuit
> +
> +maintainers:
> + - Matti Vaittinen <[email protected]>
> +
> +description: |
Do not need '|' unless you need to preserve formatting.
> + BD96801 is an automotive grade single-chip power management IC.
> + It integrates 4 buck converters and 3 LDOs with safety features like
> + over-/under voltage and over current detection and a watchdog.
> +
> +properties:
> + compatible:
> + const: rohm,bd96801
> +
> + reg:
> + description:
> + I2C slave address.
Drop description, obvious.
> + maxItems: 1
> +
> + interrupts:
> + description:
> + The PMIC provides intb and errb IRQ lines. The errb IRQ line is used
> + for fatal IRQs which will cause the PMIC to shut down power outputs.
> + In many systems this will shut down the SoC contolling the PMIC and
> + connecting/handling the errb can be omitted. However, there are cases
> + where the SoC is not powered by the PMIC. In that case it may be
> + useful to connect the errb and handle errb events.
> + minItems: 1
> + maxItems: 2
> +
> + interrupt-names:
> + minItems: 1
> + items:
> + - enum: [intb, errb]
> + - const: errb
> +
> + rohm,hw-timeout-ms:
> + description:
> + Watchdog timeout value(s). First walue is timeout limit. Second value is
> + optional value for 'too early' watchdog ping if window timeout mode is
> + to be used.
Standard property timeout-sec does not work for you? It should allow two
items as well.
> + minItems: 1
> + maxItems: 2
> +
> + rohm,wdg-action:
> + description:
> + Whether the watchdog failure must turn off the regulator power outputs or
> + just toggle the INTB line.
> + enum:
> + - prstb
> + - intb-only
This is second property controlling bite behavior. The other being:
https://lore.kernel.org/all/[email protected]/
Probably we need common property in watchdog.yaml.
> +
> + regulators:
> + $ref: ../regulator/rohm,bd96801-regulator.yaml
Full path, so /schemas/regulator/....
> + description:
> + List of child nodes that specify the regulators.
> +
> +required:
> + - compatible
> + - reg
> + - interrupts
> + - interrupt-names
> + - regulators
> +
Missing allOf and $ref to watchdog.yaml
> +additionalProperties: false
> +
> +examples:
Best regards,
Krzysztof
On 13/04/2024 23:33, Krzysztof Kozlowski wrote:
>> + rohm,wdg-action:
>> + description:
>> + Whether the watchdog failure must turn off the regulator power outputs or
>> + just toggle the INTB line.
>> + enum:
>> + - prstb
>> + - intb-only
>
> This is second property controlling bite behavior. The other being:
> https://lore.kernel.org/all/[email protected]/
>
> Probably we need common property in watchdog.yaml.
No, that's not the same case. I mixed up topics.
Best regards,
Krzysztof
Morning Krzysztof,
Thanks again for the review/help!
On 4/14/24 00:33, Krzysztof Kozlowski wrote:
> On 12/04/2024 13:21, Matti Vaittinen wrote
>> +
>> + rohm,hw-timeout-ms:
>> + description:
>> + Watchdog timeout value(s). First walue is timeout limit. Second value is
>> + optional value for 'too early' watchdog ping if window timeout mode is
>> + to be used.
>
> Standard property timeout-sec does not work for you? It should allow two
> items as well.
I don't think so. We need sub-second units. Furthermore, the timeout-sec
(if I understand it correctly) updates the "timeout policy", which tells
the expected ping-interval. This can be different from the "HW
heart-beat" which tells the HW's ping expectation. Hence the "hw-" prefix.
> Missing allOf
This just about summarizes my feelings when I try write the bindings. XD
I do feel completely lost. Hence I do really appreciate someone like you
taking the time to help me through ^^;
Enjoy the Seattle!
Yours,
-- Matti
--
Matti Vaittinen
Linux kernel developer at ROHM Semiconductors
Oulu Finland
~~ When things go utterly wrong vim users can always type :help! ~~
On 4/15/24 08:50, Matti Vaittinen wrote:
> Morning Krzysztof,
>
> Thanks again for the review/help!
>
> On 4/14/24 00:33, Krzysztof Kozlowski wrote:
>> On 12/04/2024 13:21, Matti Vaittinen wrote
>>> +
>>> + rohm,hw-timeout-ms:
>>> + description:
>>> + Watchdog timeout value(s). First walue is timeout limit.
>>> Second value is
>>> + optional value for 'too early' watchdog ping if window timeout
>>> mode is
>>> + to be used.
>>
>> Standard property timeout-sec does not work for you? It should allow two
>> items as well.
>
> I don't think so. We need sub-second units. Furthermore, the timeout-sec
> (if I understand it correctly) updates the "timeout policy", which tells
> the expected ping-interval. This can be different from the "HW
> heart-beat" which tells the HW's ping expectation. Hence the "hw-" prefix.
Oh, I just found out that this is an existing property. The ROHM
BD9576/BD9573 do aleady use this. It seems I've had some discussion
about it with Rob/Guenter when adding it. Frightening thing is that I
didin't remember the discussion or that the property existed at all...
Well, luckily we have lore :)
https://lore.kernel.org/all/[email protected]/#r
(I don't see the final conclusion in this discussion, it has probably
been done on some later version of the series).
Yours,
-- Matti
--
Matti Vaittinen
Linux kernel developer at ROHM Semiconductors
Oulu Finland
~~ When things go utterly wrong vim users can always type :help! ~~
On 4/14/24 00:27, Krzysztof Kozlowski wrote:
> On 12/04/2024 13:21, Matti Vaittinen wrote:
>> ROHM BD96801 is a highly configurable automotive grade PMIC. Introduce
>> DT bindings for the BD96801 regulators.
>>
>> Signed-off-by: Matti Vaittinen <[email protected]>
>> ---
>> Revision history:
>> - No changes since RFCv1
>
> Subject: missing "regulator" prefix, as first.
>
>>
>> .../regulator/rohm,bd96801-regulator.yaml | 69 +++++++++++++++++++
>> 1 file changed, 69 insertions(+)
>> create mode 100644 Documentation/devicetree/bindings/regulator/rohm,bd96801-regulator.yaml
>>
>> diff --git a/Documentation/devicetree/bindings/regulator/rohm,bd96801-regulator.yaml b/Documentation/devicetree/bindings/regulator/rohm,bd96801-regulator.yaml
>> new file mode 100644
>> index 000000000000..4015802a3d84
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/regulator/rohm,bd96801-regulator.yaml
>> @@ -0,0 +1,69 @@
>> +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
>> +%YAML 1.2
>> +---
>> +$id: http://devicetree.org/schemas/regulator/rohm,bd96801-regulator.yaml#
>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>> +
>> +title: ROHM BD96801 Power Management Integrated Circuit regulators
>> +
>> +maintainers:
>> + - Matti Vaittinen <[email protected]>
>> +
>> +description: |
>> + This module is part of the ROHM BD96801 MFD device. For more details
>> + see Documentation/devicetree/bindings/mfd/rohm,bd96801-pmic.yaml.
>> +
>> + The regulator controller is represented as a sub-node of the PMIC node
>> + on the device tree.
>> +
>> + Regulator nodes should be named to BUCK_<number> and LDO_<number>.
>> + The valid names for BD96801 regulator nodes are
>> + BUCK1, BUCK2, BUCK3, BUCK4, LDO5, LDO6, LDO7
>> +
>> +patternProperties:
>> + "^LDO[5-7]$":
>
> lowercase
>
>> + type: object
>> + description:
>> + Properties for single LDO regulator.
>> + $ref: regulator.yaml#
>
> Missing unevaluatedProperties: false
>
>> +
>> + properties:
>> + regulator-name:
>> + pattern: "^ldo[5-7]$"
>> + description:
>> + Name of the regulator. Should be "ldo5", ..., "ldo7"
>
> Why do you enforce the name? The name should match board schematics, not
> regulator datasheet.
If my memory serves me right, the slightly peculiar thing with the
regulator core is it does matching of the regulators based on the names
of the nodes. There was the regulator-compatible property, but I think
it has been deprecated long ago.
https://elixir.bootlin.com/linux/latest/source/drivers/regulator/of_regulator.c#L380
Hence the regulators tend to have fixed names for the nodes. Unless
there has been some recent changes I am not aware of...
>> + rohm,initial-voltage-microvolt:
>> + description:
>> + Initial voltage for regulator. Voltage can be tuned +/-150 mV from
>> + this value. NOTE, This can be modified via I2C only when PMIC is in
>> + STBY state.
>> + minimum: 300000
>> + maximum: 3300000
>
> Hm, regulator min/max microvolts properties don't work for you? The
> initial will be just middle?
I had not even thought of this!
I think this is a good idea. The problem I see is if the system where
the PMIC is used will need to have 'initial power level' at start-up,
which is near the one end of the allowed voltage area. (This because the
"tuning"-range is quite narrow after the initial voltage is set). Wide
allowed voltage range may be needed if the PMIC is reconfigured using
the PMIC STBY state during the runtime.
Eg, sequence would look like:
Bootup:
PMIC STBY:
- initial value 'A' from DT
=> PMIC ACTIVE
- desired (early) voltages 'A' + 'tune'
..
Voltage state differing more than the 'tune' needed due to some runtime
use-case:
=> PMIC STBY
- initial value 'B'
=> PMIC ACTIVE
- desired voltages 'B' + 'tune'
Now, if the 'A' can be 'far' from the mid point of the 'allowed
voltages' -range.
I have no idea how valid this use-case is though. Once again, I work for
a component vendor and don't get to see the forest from the trees... But
sure I would like to enable as many possible use-cases as, well, possible :)
Yours,
-- Matti
--
Matti Vaittinen
Linux kernel developer at ROHM Semiconductors
Oulu Finland
~~ When things go utterly wrong vim users can always type :help! ~~
On 4/14/24 00:33, Krzysztof Kozlowski wrote:
> On 12/04/2024 13:21, Matti Vaittinen wrote:
>> ROHM BD96801 is a highly configurable automotive grade PMIC. Introduce
>> DT bindings for the BD96801 core.
>>
>> Signed-off-by: Matti Vaittinen <[email protected]>
>>
>> ---
>> Revision history:
>> RFCv1 => RFCv2:
>> - Document rohm,hw-timeout-ms
>> - Document rohm,wdg-action
>> ---
>> .../bindings/mfd/rohm,bd96801-pmic.yaml | 171 ++++++++++++++++++
>> 1 file changed, 171 insertions(+)
>> create mode 100644 Documentation/devicetree/bindings/mfd/rohm,bd96801-pmic.yaml
>>
>> diff --git a/Documentation/devicetree/bindings/mfd/rohm,bd96801-pmic.yaml b/Documentation/devicetree/bindings/mfd/rohm,bd96801-pmic.yaml
..
>
> Missing allOf and $ref to watchdog.yaml
Huh. The watchdog.yaml contains:
select:
properties:
$nodename:
pattern: "^watchdog(@.*|-([0-9]|[1-9][0-9]+))?$"
properties:
$nodename:
pattern: "^(timer|watchdog)(@.*|-([0-9]|[1-9][0-9]+))?$"
This means the watchdog _must_ have own sub-node inside the PMIC node,
right?
Yours,
-- Matti
--
Matti Vaittinen
Linux kernel developer at ROHM Semiconductors
Oulu Finland
~~ When things go utterly wrong vim users can always type :help! ~~
On 15/04/2024 08:24, Matti Vaittinen wrote:
> On 4/15/24 08:50, Matti Vaittinen wrote:
>> Morning Krzysztof,
>>
>> Thanks again for the review/help!
>>
>> On 4/14/24 00:33, Krzysztof Kozlowski wrote:
>>> On 12/04/2024 13:21, Matti Vaittinen wrote
>>>> +
>>>> + rohm,hw-timeout-ms:
>>>> + description:
>>>> + Watchdog timeout value(s). First walue is timeout limit.
>>>> Second value is
>>>> + optional value for 'too early' watchdog ping if window timeout
>>>> mode is
>>>> + to be used.
>>>
>>> Standard property timeout-sec does not work for you? It should allow two
>>> items as well.
>>
>> I don't think so. We need sub-second units. Furthermore, the timeout-sec
>> (if I understand it correctly) updates the "timeout policy", which tells
>> the expected ping-interval. This can be different from the "HW
>> heart-beat" which tells the HW's ping expectation. Hence the "hw-" prefix.
>
> Oh, I just found out that this is an existing property. The ROHM
> BD9576/BD9573 do aleady use this. It seems I've had some discussion
> about it with Rob/Guenter when adding it. Frightening thing is that I
> didin't remember the discussion or that the property existed at all...
> Well, luckily we have lore :)
>
> https://lore.kernel.org/all/[email protected]/#r
>
> (I don't see the final conclusion in this discussion, it has probably
> been done on some later version of the series).
>
Sure, it's fine then.
Best regards,
Krzysztof
On 15/04/2024 08:51, Matti Vaittinen wrote:
> On 4/14/24 00:27, Krzysztof Kozlowski wrote:
>> On 12/04/2024 13:21, Matti Vaittinen wrote:
>>> ROHM BD96801 is a highly configurable automotive grade PMIC. Introduce
>>> DT bindings for the BD96801 regulators.
>>>
>>> Signed-off-by: Matti Vaittinen <[email protected]>
>>> ---
>>> Revision history:
>>> - No changes since RFCv1
>>
>> Subject: missing "regulator" prefix, as first.
>>
>>>
>>> .../regulator/rohm,bd96801-regulator.yaml | 69 +++++++++++++++++++
>>> 1 file changed, 69 insertions(+)
>>> create mode 100644 Documentation/devicetree/bindings/regulator/rohm,bd96801-regulator.yaml
>>>
>>> diff --git a/Documentation/devicetree/bindings/regulator/rohm,bd96801-regulator.yaml b/Documentation/devicetree/bindings/regulator/rohm,bd96801-regulator.yaml
>>> new file mode 100644
>>> index 000000000000..4015802a3d84
>>> --- /dev/null
>>> +++ b/Documentation/devicetree/bindings/regulator/rohm,bd96801-regulator.yaml
>>> @@ -0,0 +1,69 @@
>>> +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
>>> +%YAML 1.2
>>> +---
>>> +$id: http://devicetree.org/schemas/regulator/rohm,bd96801-regulator.yaml#
>>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>>> +
>>> +title: ROHM BD96801 Power Management Integrated Circuit regulators
>>> +
>>> +maintainers:
>>> + - Matti Vaittinen <[email protected]>
>>> +
>>> +description: |
>>> + This module is part of the ROHM BD96801 MFD device. For more details
>>> + see Documentation/devicetree/bindings/mfd/rohm,bd96801-pmic.yaml.
>>> +
>>> + The regulator controller is represented as a sub-node of the PMIC node
>>> + on the device tree.
>>> +
>>> + Regulator nodes should be named to BUCK_<number> and LDO_<number>.
>>> + The valid names for BD96801 regulator nodes are
>>> + BUCK1, BUCK2, BUCK3, BUCK4, LDO5, LDO6, LDO7
>>> +
>>> +patternProperties:
>>> + "^LDO[5-7]$":
>>
>> lowercase
>>
>>> + type: object
>>> + description:
>>> + Properties for single LDO regulator.
>>> + $ref: regulator.yaml#
>>
>> Missing unevaluatedProperties: false
>>
>>> +
>>> + properties:
>>> + regulator-name:
>>> + pattern: "^ldo[5-7]$"
>>> + description:
>>> + Name of the regulator. Should be "ldo5", ..., "ldo7"
>>
>> Why do you enforce the name? The name should match board schematics, not
>> regulator datasheet.
>
> If my memory serves me right, the slightly peculiar thing with the
> regulator core is it does matching of the regulators based on the names
> of the nodes. There was the regulator-compatible property, but I think
> it has been deprecated long ago.
>
> https://elixir.bootlin.com/linux/latest/source/drivers/regulator/of_regulator.c#L380
>
> Hence the regulators tend to have fixed names for the nodes. Unless
> there has been some recent changes I am not aware of...
Yes, names of the nodes, but not "regulator-name" property.
>
>>> + rohm,initial-voltage-microvolt:
>>> + description:
>>> + Initial voltage for regulator. Voltage can be tuned +/-150 mV from
>>> + this value. NOTE, This can be modified via I2C only when PMIC is in
>>> + STBY state.
>>> + minimum: 300000
>>> + maximum: 3300000
>>
>> Hm, regulator min/max microvolts properties don't work for you? The
>> initial will be just middle?
>
> I had not even thought of this!
>
> I think this is a good idea. The problem I see is if the system where
> the PMIC is used will need to have 'initial power level' at start-up,
> which is near the one end of the allowed voltage area. (This because the
> "tuning"-range is quite narrow after the initial voltage is set). Wide
> allowed voltage range may be needed if the PMIC is reconfigured using
> the PMIC STBY state during the runtime.
>
> Eg, sequence would look like:
>
> Bootup:
> PMIC STBY:
> - initial value 'A' from DT
> => PMIC ACTIVE
> - desired (early) voltages 'A' + 'tune'
>
> ...
>
> Voltage state differing more than the 'tune' needed due to some runtime
> use-case:
> => PMIC STBY
> - initial value 'B'
> => PMIC ACTIVE
> - desired voltages 'B' + 'tune'
>
> Now, if the 'A' can be 'far' from the mid point of the 'allowed
> voltages' -range.
>
> I have no idea how valid this use-case is though. Once again, I work for
> a component vendor and don't get to see the forest from the trees... But
> sure I would like to enable as many possible use-cases as, well, possible :)
Still I think min/max microvolt solves your case. The property is
board-specific and should match what is really on the board. Therefore
when writing DTS, one must properly set min/max which in this particular
meaning would choose the starting voltage.
I am also fine with this property if somehow min/max create confusion or
aren't solving the problem.
Best regards,
Krzysztof
On 2024-04-12 at 16:52:46, Matti Vaittinen ([email protected]) wrote:
> Introduce driver for WDG block on ROHM BD96801 scalable PMIC.
>
> This driver only supports watchdog with I2C feeding and delayed
> response detection. Whether the watchdog toggles PRSTB pin or
> just causes an interrupt can be configured via device-tree.
>
> The BD96801 PMIC HW supports also window watchdog (too early
> feeding detection) and Q&A mode. These are not supported by
> this driver.
>
> Signed-off-by: Matti Vaittinen <[email protected]>
>
> ---
> Revision history:
> RFCv1 => RFCv2:
> - remove always running
> - add IRQ handling
> - call emergency_restart()
> - drop MODULE_ALIAS and add MODULE_DEVICE_TABLE
> ---
> drivers/watchdog/Kconfig | 13 ++
> drivers/watchdog/Makefile | 1 +
> drivers/watchdog/bd96801_wdt.c | 389 +++++++++++++++++++++++++++++++++
> 3 files changed, 403 insertions(+)
> create mode 100644 drivers/watchdog/bd96801_wdt.c
>
> diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
> index 6bee137cfbe0..d97e735e1faa 100644
> --- a/drivers/watchdog/Kconfig
> +++ b/drivers/watchdog/Kconfig
> @@ -181,6 +181,19 @@ config BD957XMUF_WATCHDOG
> watchdog. Alternatively say M to compile the driver as a module,
> which will be called bd9576_wdt.
>
> +config BD96801_WATCHDOG
> + tristate "ROHM BD96801 PMIC Watchdog"
> + depends on MFD_ROHM_BD96801
> + select WATCHDOG_CORE
> + help
> + Support for the watchdog in the ROHM BD96801 PMIC. Watchdog can be
> + configured to only generate IRQ or to trigger system reset via reset
> + pin.
> +
> + Say Y here to include support for the ROHM BD96801 watchdog.
> + Alternatively say M to compile the driver as a module,
> + which will be called bd96801_wdt.
> +
> config CROS_EC_WATCHDOG
> tristate "ChromeOS EC-based watchdog"
> select WATCHDOG_CORE
> diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
> index 3710c218f05e..31bc94436c81 100644
> --- a/drivers/watchdog/Makefile
> +++ b/drivers/watchdog/Makefile
> @@ -217,6 +217,7 @@ obj-$(CONFIG_XEN_WDT) += xen_wdt.o
>
> # Architecture Independent
> obj-$(CONFIG_BD957XMUF_WATCHDOG) += bd9576_wdt.o
> +obj-$(CONFIG_BD96801_WATCHDOG) += bd96801_wdt.o
> obj-$(CONFIG_CROS_EC_WATCHDOG) += cros_ec_wdt.o
> obj-$(CONFIG_DA9052_WATCHDOG) += da9052_wdt.o
> obj-$(CONFIG_DA9055_WATCHDOG) += da9055_wdt.o
> diff --git a/drivers/watchdog/bd96801_wdt.c b/drivers/watchdog/bd96801_wdt.c
> new file mode 100644
> index 000000000000..08fab9a87aec
> --- /dev/null
> +++ b/drivers/watchdog/bd96801_wdt.c
> @@ -0,0 +1,389 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2024 ROHM Semiconductors
> + *
> + * ROHM BD96801 watchdog driver
> + */
> +
> +#include <linux/interrupt.h>
> +#include <linux/kernel.h>
> +#include <linux/mfd/rohm-bd96801.h>
> +#include <linux/mfd/rohm-generic.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +#include <linux/watchdog.h>
> +
> +static bool nowayout;
> +module_param(nowayout, bool, 0);
> +MODULE_PARM_DESC(nowayout,
> + "Watchdog cannot be stopped once started (default=\"false\")");
> +
> +#define BD96801_WD_TMO_SHORT_MASK 0x70
> +#define BD96801_WD_RATIO_MASK 0x3
> +#define BD96801_WD_TYPE_MASK 0x4
> +#define BD96801_WD_TYPE_SLOW 0x4
> +#define BD96801_WD_TYPE_WIN 0x0
> +
> +#define BD96801_WD_EN_MASK 0x3
> +#define BD96801_WD_IF_EN 0x1
> +#define BD96801_WD_QA_EN 0x2
> +#define BD96801_WD_DISABLE 0x0
> +
> +#define BD96801_WD_ASSERT_MASK 0x8
> +#define BD96801_WD_ASSERT_RST 0x8
> +#define BD96801_WD_ASSERT_IRQ 0x0
> +
> +#define BD96801_WD_FEED_MASK 0x1
> +#define BD96801_WD_FEED 0x1
> +
> +/* units in uS */
> +#define FASTNG_MIN 3370
> +#define BD96801_WDT_DEFAULT_MARGIN 6905120
> +/* Unit is seconds */
> +#define DEFAULT_TIMEOUT 30
> +
> +/*
> + * BD96801 WDG supports window mode so the TMO consists of SHORT and LONG
> + * timeout values. SHORT time is meaningfull only in window mode where feeding
> + * period shorter than SHORT would be an error. LONG time is used to detect if
> + * feeding is not occurring within given time limit (SoC SW hangs). The LONG
> + * timeout time is a multiple of (2, 4, 8 0r 16 times) the SHORT timeout.
> + */
> +
> +struct wdtbd96801 {
> + struct device *dev;
> + struct regmap *regmap;
> + struct watchdog_device wdt;
> +};
> +
> +static int bd96801_wdt_ping(struct watchdog_device *wdt)
> +{
> + struct wdtbd96801 *w = watchdog_get_drvdata(wdt);
> +
> + return regmap_update_bits(w->regmap, BD96801_REG_WD_FEED,
> + BD96801_WD_FEED_MASK, BD96801_WD_FEED);
> +}
> +
> +static int bd96801_wdt_start(struct watchdog_device *wdt)
> +{
> + struct wdtbd96801 *w = watchdog_get_drvdata(wdt);
> + int ret;
> +
> + ret = regmap_update_bits(w->regmap, BD96801_REG_WD_CONF,
> + BD96801_WD_EN_MASK, BD96801_WD_IF_EN);
> +
> + return ret;
> +}
> +
> +static int bd96801_wdt_stop(struct watchdog_device *wdt)
> +{
> + struct wdtbd96801 *w = watchdog_get_drvdata(wdt);
> +
> + return regmap_update_bits(w->regmap, BD96801_REG_WD_CONF,
> + BD96801_WD_EN_MASK, BD96801_WD_DISABLE);
> +}
> +
> +static const struct watchdog_info bd96801_wdt_info = {
> + .options = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING |
> + WDIOF_SETTIMEOUT,
> + .identity = "BD96801 Watchdog",
> +};
> +
> +static const struct watchdog_ops bd96801_wdt_ops = {
> + .start = bd96801_wdt_start,
> + .stop = bd96801_wdt_stop,
> + .ping = bd96801_wdt_ping,
> +};
Is there no way to setup a timeout to the WDOG device from userspace?
> +
> +static int find_closest_fast(int target, int *sel, int *val)
> +{
> + int i;
> + int window = FASTNG_MIN;
> +
> + for (i = 0; i < 8 && window < target; i++)
> + window <<= 1;
> +
> + *val = window;
> + *sel = i;
> +
> + if (i == 8)
> + return -EINVAL;
> +
> + return 0;
> +}
> +
> +static int find_closest_slow_by_fast(int fast_val, int *target, int *slowsel)
> +{
> + int sel;
> + static const int multipliers[] = {2, 4, 8, 16};
> +
> + for (sel = 0; sel < ARRAY_SIZE(multipliers) &&
> + multipliers[sel] * fast_val < *target; sel++)
> + ;
> +
> + if (sel == ARRAY_SIZE(multipliers))
> + return -EINVAL;
> +
> + *slowsel = sel;
> + *target = multipliers[sel] * fast_val;
> +
> + return 0;
> +}
> +
> +static int find_closest_slow(int *target, int *slow_sel, int *fast_sel)
> +{
> + static const int multipliers[] = {2, 4, 8, 16};
> + int i, j;
> + int val = 0;
> + int window = FASTNG_MIN;
> +
> + for (i = 0; i < 8; i++) {
> + for (j = 0; j < ARRAY_SIZE(multipliers); j++) {
> + int slow;
> +
> + slow = window * multipliers[j];
> + if (slow >= *target && (!val || slow < val)) {
> + val = slow;
> + *fast_sel = i;
> + *slow_sel = j;
> + }
> + }
> + window <<= 1;
> + }
> + if (!val)
> + return -EINVAL;
> +
> + *target = val;
> +
> + return 0;
> +}
> +
> +static int bd96801_set_wdt_mode(struct wdtbd96801 *w, int hw_margin,
> + int hw_margin_min)
> +{
> + int ret, fastng, slowng, type, reg, mask;
> + struct device *dev = w->dev;
> +
> + /* convert to uS */
> + hw_margin *= 1000;
> + hw_margin_min *= 1000;
> + if (hw_margin_min) {
> + int min;
> +
> + type = BD96801_WD_TYPE_WIN;
> + dev_dbg(dev, "Setting type WINDOW 0x%x\n", type);
> + ret = find_closest_fast(hw_margin_min, &fastng, &min);
> + if (ret) {
> + dev_err(dev, "bad WDT window for fast timeout\n");
> + return ret;
> + }
> +
> + ret = find_closest_slow_by_fast(min, &hw_margin, &slowng);
> + if (ret) {
> + dev_err(dev, "bad WDT window\n");
> + return ret;
> + }
> + w->wdt.min_hw_heartbeat_ms = min / 1000;
> + } else {
> + type = BD96801_WD_TYPE_SLOW;
> + dev_dbg(dev, "Setting type SLOW 0x%x\n", type);
> + ret = find_closest_slow(&hw_margin, &slowng, &fastng);
> + if (ret) {
> + dev_err(dev, "bad WDT window\n");
> + return ret;
> + }
> + }
> +
> + w->wdt.max_hw_heartbeat_ms = hw_margin / 1000;
> +
> + fastng <<= ffs(BD96801_WD_TMO_SHORT_MASK) - 1;
> +
> + reg = slowng | fastng;
> + mask = BD96801_WD_RATIO_MASK | BD96801_WD_TMO_SHORT_MASK;
> + ret = regmap_update_bits(w->regmap, BD96801_REG_WD_TMO,
> + mask, reg);
> + if (ret)
> + return ret;
> +
> + ret = regmap_update_bits(w->regmap, BD96801_REG_WD_CONF,
> + BD96801_WD_TYPE_MASK, type);
> +
> + return ret;
> +}
> +
> +static int bd96801_set_heartbeat_from_hw(struct wdtbd96801 *w,
> + unsigned int conf_reg)
> +{
> + int ret;
> + unsigned int val, sel, fast;
> +
> + /*
> + * The BD96801 supports a somewhat peculiar QA-mode, which we do not
> + * support in this driver. If the QA-mode is enabled then we just
> + * warn and bail-out.
> + */
> + if ((conf_reg & BD96801_WD_EN_MASK) != BD96801_WD_IF_EN) {
> + dev_warn(w->dev, "watchdog set to Q&A mode - exiting\n");
> + return -EINVAL;
> + }
> +
> + ret = regmap_read(w->regmap, BD96801_REG_WD_TMO, &val);
> + if (ret)
> + return ret;
> +
> + sel = val & BD96801_WD_TMO_SHORT_MASK;
> + sel >>= ffs(BD96801_WD_TMO_SHORT_MASK) - 1;
> + fast = FASTNG_MIN << sel;
> +
> + sel = (val & BD96801_WD_RATIO_MASK) + 1;
> + w->wdt.max_hw_heartbeat_ms = (fast << sel) / USEC_PER_MSEC;
> +
> + if ((conf_reg & BD96801_WD_TYPE_MASK) == BD96801_WD_TYPE_WIN)
> + w->wdt.min_hw_heartbeat_ms = fast / USEC_PER_MSEC;
> +
> + return 0;
> +}
> +
> +static int init_wdg_hw(struct wdtbd96801 *w)
> +{
> + u32 hw_margin[2];
> + int count, ret;
> + u32 hw_margin_max = BD96801_WDT_DEFAULT_MARGIN, hw_margin_min = 0;
> +
> + count = device_property_count_u32(w->dev->parent, "rohm,hw-timeout-ms");
Why is that timeout need to be configured from a devicce-tree property?
set_timeout/get_timeout can't be done for this device?
> + if (count < 0 && count != -EINVAL)
> + return count;
> +
> + if (count > 0) {
> + if (count > ARRAY_SIZE(hw_margin))
> + return -EINVAL;
> +
> + ret = device_property_read_u32_array(w->dev->parent,
> + "rohm,hw-timeout-ms",
> + &hw_margin[0], count);
> + if (ret < 0)
> + return ret;
> +
> + if (count == 1)
> + hw_margin_max = hw_margin[0];
> +
> + if (count == 2) {
> + hw_margin_max = hw_margin[1];
> + hw_margin_min = hw_margin[0];
> + }
> + }
> +
> + ret = bd96801_set_wdt_mode(w, hw_margin_max, hw_margin_min);
> + if (ret)
> + return ret;
> +
> + ret = device_property_match_string(w->dev->parent, "rohm,wdg-action",
> + "prstb");
> + if (ret >= 0) {
> + ret = regmap_update_bits(w->regmap, BD96801_REG_WD_CONF,
> + BD96801_WD_ASSERT_MASK,
> + BD96801_WD_ASSERT_RST);
> + return ret;
> + }
> +
> + ret = device_property_match_string(w->dev->parent, "rohm,wdg-action",
> + "intb-only");
> + if (ret >= 0) {
> + ret = regmap_update_bits(w->regmap, BD96801_REG_WD_CONF,
> + BD96801_WD_ASSERT_MASK,
> + BD96801_WD_ASSERT_IRQ);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +extern void emergency_restart(void);
> +static irqreturn_t bd96801_irq_hnd(int irq, void *data)
> +{
> + emergency_restart();
In case of a full system hang will this function get executed?
> + return IRQ_NONE;
> +}
> +
> +static int bd96801_wdt_probe(struct platform_device *pdev)
> +{
> + struct wdtbd96801 *w;
> + int ret, irq;
> + unsigned int val;
> +
> + w = devm_kzalloc(&pdev->dev, sizeof(*w), GFP_KERNEL);
> + if (!w)
> + return -ENOMEM;
> +
> + w->regmap = dev_get_regmap(pdev->dev.parent, NULL);
> + w->dev = &pdev->dev;
> +
> + w->wdt.info = &bd96801_wdt_info;
> + w->wdt.ops = &bd96801_wdt_ops;
> + w->wdt.parent = pdev->dev.parent;
> + w->wdt.timeout = DEFAULT_TIMEOUT;
> + watchdog_set_drvdata(&w->wdt, w);
> +
> + ret = regmap_read(w->regmap, BD96801_REG_WD_CONF, &val);
> + if (ret)
> + return dev_err_probe(&pdev->dev, ret,
> + "Failed to get the watchdog state\n");
> +
> + /*
> + * If the WDG is already enabled we assume it is configured by boot.
> + * In this case we just update the hw-timeout based on values set to
> + * the timeout / mode registers and leave the hardware configs
> + * untouched.
> + */
> + if ((val & BD96801_WD_EN_MASK) != BD96801_WD_DISABLE) {
> + dev_dbg(&pdev->dev, "watchdog was running during probe\n");
> + ret = bd96801_set_heartbeat_from_hw(w, val);
> + if (ret)
> + return ret;
> +
> + set_bit(WDOG_HW_RUNNING, &w->wdt.status);
> + } else {
> + /* If WDG is not running so we will initializate it */
> + ret = init_wdg_hw(w);
> + if (ret)
> + return ret;
> + }
> +
> + watchdog_init_timeout(&w->wdt, 0, pdev->dev.parent);
> + watchdog_set_nowayout(&w->wdt, nowayout);
> + watchdog_stop_on_reboot(&w->wdt);
> +
> + irq = platform_get_irq_byname(pdev, "bd96801-wdg");
> + if (irq > 0) {
> + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
> + bd96801_irq_hnd,
> + IRQF_ONESHOT, "bd96801-wdg",
> + NULL);
> + if (ret)
> + return dev_err_probe(&pdev->dev, ret,
> + "Failed to register IRQ\n");
> + }
> +
> + return devm_watchdog_register_device(&pdev->dev, &w->wdt);
> +}
> +
> +static const struct platform_device_id bd96801_wdt_id[] = {
> + { "bd96801-wdt", },
> + { }
> +};
> +MODULE_DEVICE_TABLE(platform, bd96801_wdt_id);
> +
> +static struct platform_driver bd96801_wdt = {
> + .driver = {
> + .name = "bd96801-wdt"
> + },
> + .probe = bd96801_wdt_probe,
> + .id_table = bd96801_wdt_id,
> +};
> +module_platform_driver(bd96801_wdt);
> +
> +MODULE_AUTHOR("Matti Vaittinen <[email protected]>");
> +MODULE_DESCRIPTION("BD96801 watchdog driver");
> +MODULE_LICENSE("GPL");
> --
> 2.43.2
>
>
> --
> Matti Vaittinen, Linux device drivers
> ROHM Semiconductors, Finland SWDC
> Kiviharjunlenkki 1E
> 90220 OULU
> FINLAND
>
> ~~~ "I don't think so," said Rene Descartes. Just then he vanished ~~~
> Simon says - in Latin please.
> ~~~ "non cogito me" dixit Rene Descarte, deinde evanescavit ~~~
> Thanks to Simon Glass for the translation =]
-George
Hi George,
Thanks for the input! Reviewing is very much appreciated :)
On 4/17/24 07:29, George Cherian wrote:
> On 2024-04-12 at 16:52:46, Matti Vaittinen ([email protected]) wrote:
>> Introduce driver for WDG block on ROHM BD96801 scalable PMIC.
>>
>> This driver only supports watchdog with I2C feeding and delayed
>> response detection. Whether the watchdog toggles PRSTB pin or
>> just causes an interrupt can be configured via device-tree.
>>
>> The BD96801 PMIC HW supports also window watchdog (too early
>> feeding detection) and Q&A mode. These are not supported by
>> this driver.
>>
>> Signed-off-by: Matti Vaittinen <[email protected]>
>>
>> ---
>> Revision history:
>> RFCv1 => RFCv2:
>> - remove always running
>> - add IRQ handling
>> - call emergency_restart()
>> - drop MODULE_ALIAS and add MODULE_DEVICE_TABLE
>> ---
>> drivers/watchdog/Kconfig | 13 ++
>> drivers/watchdog/Makefile | 1 +
>> drivers/watchdog/bd96801_wdt.c | 389 +++++++++++++++++++++++++++++++++
>> 3 files changed, 403 insertions(+)
>> create mode 100644 drivers/watchdog/bd96801_wdt.c
>>4
>> +
>> +static const struct watchdog_ops bd96801_wdt_ops = {
>> + .start = bd96801_wdt_start,
>> + .stop = bd96801_wdt_stop,
>> + .ping = bd96801_wdt_ping,
>> +};
> Is there no way to setup a timeout to the WDOG device from userspace?
>
For the BD96801 hardware? Currently no. (See below)
>> +
>> +static int init_wdg_hw(struct wdtbd96801 *w)
>> +{
>> + u32 hw_margin[2];
>> + int count, ret;
>> + u32 hw_margin_max = BD96801_WDT_DEFAULT_MARGIN, hw_margin_min = 0;
>> +
>> + count = device_property_count_u32(w->dev->parent, "rohm,hw-timeout-ms");
> Why is that timeout need to be configured from a devicce-tree property?
> set_timeout/get_timeout can't be done for this device?
The BD96801 supports 32 different "slow detection" timeouts - only 1 of
which is guaranteed to be greater than 1s (1.8432 Sec). The timeout
setting interface uses seconds.
Furthermore, I think that the HW heartbeat is not all that meaningful to
the user-space. What is meaningful to user-space is how often the
userland daemon needs to write the watchdog file to keep system alive.
This is different thing. With the sub-second HW timeouts it is better
the kernel keeps feeding the watchdog based on a timer (adjusted to meet
the HW requirements) and can thus require the userspace to ping less
frequently than the HW heartbeat.
>> + if (count < 0 && count != -EINVAL)
>> + return count;
>> +
>> + if (count > 0) {
>> + if (count > ARRAY_SIZE(hw_margin))
>> + return -EINVAL;
>> +
>> + ret = device_property_read_u32_array(w->dev->parent,
>> + "rohm,hw-timeout-ms",
>> + &hw_margin[0], count);
>> + if (ret < 0)
>> + return ret;
>> +
>> + if (count == 1)
>> + hw_margin_max = hw_margin[0];
>> +
>> + if (count == 2) {
>> + hw_margin_max = hw_margin[1];
>> + hw_margin_min = hw_margin[0];
>> + }
>> + }
>> +
>> + ret = bd96801_set_wdt_mode(w, hw_margin_max, hw_margin_min);
>> + if (ret)
>> + return ret;
>> +
>> + ret = device_property_match_string(w->dev->parent, "rohm,wdg-action",
>> + "prstb");
>> + if (ret >= 0) {
>> + ret = regmap_update_bits(w->regmap, BD96801_REG_WD_CONF,
>> + BD96801_WD_ASSERT_MASK,
>> + BD96801_WD_ASSERT_RST);
>> + return ret;
>> + }
>> +
>> + ret = device_property_match_string(w->dev->parent, "rohm,wdg-action",
>> + "intb-only");
>> + if (ret >= 0) {
>> + ret = regmap_update_bits(w->regmap, BD96801_REG_WD_CONF,
>> + BD96801_WD_ASSERT_MASK,
>> + BD96801_WD_ASSERT_IRQ);
>> + return ret;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +extern void emergency_restart(void);
>> +static irqreturn_t bd96801_irq_hnd(int irq, void *data)
>> +{
>> + emergency_restart();
> In case of a full system hang will this function get executed?
Maybe, maybe not. It is probably still the best we can do if the
watchdog has not been configured to kill the power by HW. Or, do you
have some other suggestion? (The emergency_restart() was actually
suggested by Guenter during the v1 review).
>> + return IRQ_NONE;
>> +}
Yours,
-- Matti
--
Matti Vaittinen
Linux kernel developer at ROHM Semiconductors
Oulu Finland
~~ When things go utterly wrong vim users can always type :help! ~~
On 4/12/24 14:22, Matti Vaittinen wrote:
> Introduce driver for WDG block on ROHM BD96801 scalable PMIC.
>
> This driver only supports watchdog with I2C feeding and delayed
> response detection. Whether the watchdog toggles PRSTB pin or
> just causes an interrupt can be configured via device-tree.
>
> The BD96801 PMIC HW supports also window watchdog (too early
> feeding detection) and Q&A mode. These are not supported by
> this driver.
>
> Signed-off-by: Matti Vaittinen <[email protected]>
>
> ---
> Revision history:
> RFCv1 => RFCv2:
> - remove always running
> - add IRQ handling
> - call emergency_restart()
> - drop MODULE_ALIAS and add MODULE_DEVICE_TABLE
> ---
> drivers/watchdog/Kconfig | 13 ++
> drivers/watchdog/Makefile | 1 +
> drivers/watchdog/bd96801_wdt.c | 389 +++++++++++++++++++++++++++++++++
> 3 files changed, 403 insertions(+)
> create mode 100644 drivers/watchdog/bd96801_wdt.c
>
> diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
> index 6bee137cfbe0..d97e735e1faa 100644
> --- a/drivers/watchdog/Kconfig
> +++ b/drivers/watchdog/Kconfig
> @@ -181,6 +181,19 @@ config BD957XMUF_WATCHDOG
> watchdog. Alternatively say M to compile the driver as a module,
> which will be called bd9576_wdt.
>
> +config BD96801_WATCHDOG
> + tristate "ROHM BD96801 PMIC Watchdog"
> + depends on MFD_ROHM_BD96801
> + select WATCHDOG_CORE
> + help
> + Support for the watchdog in the ROHM BD96801 PMIC. Watchdog can be
> + configured to only generate IRQ or to trigger system reset via reset
> + pin.
> +
> + Say Y here to include support for the ROHM BD96801 watchdog.
> + Alternatively say M to compile the driver as a module,
> + which will be called bd96801_wdt.
> +
> config CROS_EC_WATCHDOG
> tristate "ChromeOS EC-based watchdog"
> select WATCHDOG_CORE
> diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
> index 3710c218f05e..31bc94436c81 100644
> --- a/drivers/watchdog/Makefile
> +++ b/drivers/watchdog/Makefile
> @@ -217,6 +217,7 @@ obj-$(CONFIG_XEN_WDT) += xen_wdt.o
>
> # Architecture Independent
> obj-$(CONFIG_BD957XMUF_WATCHDOG) += bd9576_wdt.o
> +obj-$(CONFIG_BD96801_WATCHDOG) += bd96801_wdt.o
> obj-$(CONFIG_CROS_EC_WATCHDOG) += cros_ec_wdt.o
> obj-$(CONFIG_DA9052_WATCHDOG) += da9052_wdt.o
> obj-$(CONFIG_DA9055_WATCHDOG) += da9055_wdt.o
> diff --git a/drivers/watchdog/bd96801_wdt.c b/drivers/watchdog/bd96801_wdt.c
> new file mode 100644
> index 000000000000..08fab9a87aec
> --- /dev/null
> +++ b/drivers/watchdog/bd96801_wdt.c
> @@ -0,0 +1,389 @@
..
> +static int find_closest_fast(int target, int *sel, int *val)
> +{
> + int i;
> + int window = FASTNG_MIN;
> +
> + for (i = 0; i < 8 && window < target; i++)
> + window <<= 1;
> +
> + *val = window;
> + *sel = i;
> +
> + if (i == 8)
> + return -EINVAL;
> +
> + return 0;
> +}
> +
> +static int find_closest_slow_by_fast(int fast_val, int *target, int *slowsel)
> +{
> + int sel;
> + static const int multipliers[] = {2, 4, 8, 16};
> +
> + for (sel = 0; sel < ARRAY_SIZE(multipliers) &&
> + multipliers[sel] * fast_val < *target; sel++)
> + ;
> +
> + if (sel == ARRAY_SIZE(multipliers))
> + return -EINVAL;
> +
> + *slowsel = sel;
> + *target = multipliers[sel] * fast_val;
> +
> + return 0;
> +}
> +
> +static int find_closest_slow(int *target, int *slow_sel, int *fast_sel)
> +{
> + static const int multipliers[] = {2, 4, 8, 16};
> + int i, j;
> + int val = 0;
> + int window = FASTNG_MIN;
> +
> + for (i = 0; i < 8; i++) {
> + for (j = 0; j < ARRAY_SIZE(multipliers); j++) {
> + int slow;
> +
> + slow = window * multipliers[j];
> + if (slow >= *target && (!val || slow < val)) {
> + val = slow;
> + *fast_sel = i;
> + *slow_sel = j;
> + }
> + }
> + window <<= 1;
> + }
> + if (!val)
> + return -EINVAL;
> +
> + *target = val;
> +
> + return 0;
> +}
Thanks to the review comments by George, I took a more careful look on
the supported watchdog time-outs. It appears the functions above don't
work as intended. I think the logic is flawed, and some of the values
correspond to an early design sample.
I will fix (and test) the timeout computations for the next version -
but it is likely to take some time since I'd rather sent the v3 without
the 'RFC'. Just wanted to warn people that it might be best to postpone
proper review to v3.
Sorry...
Yours,
-- Matti
--
Matti Vaittinen
Linux kernel developer at ROHM Semiconductors
Oulu Finland
~~ When things go utterly wrong vim users can always type :help! ~~
On 15/04/2024 10:28, Matti Vaittinen wrote:
>>
>> Missing allOf and $ref to watchdog.yaml
>
> Huh. The watchdog.yaml contains:
>
> select:
> properties:
> $nodename:
> pattern: "^watchdog(@.*|-([0-9]|[1-9][0-9]+))?$"
>
> properties:
> $nodename:
> pattern: "^(timer|watchdog)(@.*|-([0-9]|[1-9][0-9]+))?$"
>
>
> This means the watchdog _must_ have own sub-node inside the PMIC node,
Yes, that's a bit of a trouble. Then instead maybe just add timeout-sec
with maxItems: 2.
Best regards,
Krzysztof
On 4/18/24 20:28, Krzysztof Kozlowski wrote:
> On 15/04/2024 10:28, Matti Vaittinen wrote:
>>>
>>> Missing allOf and $ref to watchdog.yaml
>>
>> Huh. The watchdog.yaml contains:
>>
>> select:
>> properties:
>> $nodename:
>> pattern: "^watchdog(@.*|-([0-9]|[1-9][0-9]+))?$"
>>
>> properties:
>> $nodename:
>> pattern: "^(timer|watchdog)(@.*|-([0-9]|[1-9][0-9]+))?$"
>>
>>
>> This means the watchdog _must_ have own sub-node inside the PMIC node,
>
> Yes, that's a bit of a trouble.
Agree. I'm not 100% sure why this requirement? I guess it is a bit of a
problem for many MFD devices with a watchdog.
> Then instead maybe just add timeout-sec
> with maxItems: 2.
Nice idea. Thanks for the suggestion, I'll do just that!
Yours,
-- Matti
--
Matti Vaittinen
Linux kernel developer at ROHM Semiconductors
Oulu Finland
~~ When things go utterly wrong vim users can always type :help! ~~