2024-04-02 13:10:04

by Matti Vaittinen

[permalink] [raw]
Subject: [RFC PATCH 0/6] Support ROHM BD96801 scalable PMIC

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 RFC series is a product of a _long_ process. The product has been
re-designed a few times and this series has been sitting in my git
forgotten for long periods of time, then been re-worked when new design
has been available, after which it has again been forgotten. Last week I
finally received the word that this product should've been stabilized,
digged up my last set of patches (from Nov 2021 - cover letter
reminding me the driver development had been done during 3 years...)

I think this is valid information for reviewers as it's good to keep an
eye on obsoleted practices - even though I tried updating this series.

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 I
last wrote this patch in 2021 I had some naming collison in debugfs for
the IRQ domains. Back then I used:
irq_domain_update_bus_token(intb_domain, DOMAIN_BUS_WIRED);
to work-around the issue. Now, when rebasing to v6.9-rc1 the naming
collision was gone and things seemed to work. However, it'd be great if
the IRQ code in MFD driver was reviewed by greater minds :)

Rest of the series ought to be business as usual.

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 | 155 ++
.../regulator/rohm,bd96801-regulator.yaml | 69 +
MAINTAINERS | 4 +
drivers/mfd/Kconfig | 13 +
drivers/mfd/Makefile | 1 +
drivers/mfd/rohm-bd96801.c | 454 ++++
drivers/regulator/Kconfig | 12 +
drivers/regulator/Makefile | 2 +
drivers/regulator/bd96801-regulator.c | 2109 +++++++++++++++++
drivers/watchdog/Kconfig | 13 +
drivers/watchdog/Makefile | 1 +
drivers/watchdog/bd96801_wdt.c | 375 +++
include/linux/mfd/rohm-bd96801.h | 212 ++
include/linux/mfd/rohm-generic.h | 1 +
14 files changed, 3421 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 =]


Attachments:
(No filename) (3.49 kB)
signature.asc (499.00 B)
Download all attachments

2024-04-02 13:11:14

by Matti Vaittinen

[permalink] [raw]
Subject: [RFC PATCH 1/6] dt-bindings: ROHM BD96801 PMIC regulators

ROHM BD96801 is a highly configurable automotive grade PMIC. Introduce
DT bindings for the BD96801 regulators.

Signed-off-by: Matti Vaittinen <[email protected]>
---
.../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 =]


Attachments:
(No filename) (3.24 kB)
signature.asc (499.00 B)
Download all attachments

2024-04-02 13:14:45

by Matti Vaittinen

[permalink] [raw]
Subject: [RFC PATCH 6/6] MAINTAINERS: Add ROHM BD96801 'scalable PMIC' entries

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 =]


Attachments:
(No filename) (1.44 kB)
signature.asc (499.00 B)
Download all attachments

2024-04-02 13:14:47

by Matti Vaittinen

[permalink] [raw]
Subject: [RFC PATCH 3/6] mfd: support ROHM BD96801 PMIC core

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]>
---
drivers/mfd/Kconfig | 13 +
drivers/mfd/Makefile | 1 +
drivers/mfd/rohm-bd96801.c | 454 +++++++++++++++++++++++++++++++
include/linux/mfd/rohm-bd96801.h | 212 +++++++++++++++
include/linux/mfd/rohm-generic.h | 1 +
5 files changed, 681 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..947045eb3a8e 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 solutions 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..7610d0114653
--- /dev/null
+++ b/drivers/mfd/rohm-bd96801.c
@@ -0,0 +1,454 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Copyright (C) 2022 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;
+ struct regmap_irq_chip_data *intb_irq_data, *errb_irq_data;
+ struct irq_domain *intb_domain, *errb_domain;
+ const struct fwnode_handle *fwnode;
+ struct resource *regulator_res;
+ struct regmap *regmap;
+
+ fwnode = dev_fwnode(&i2c->dev);
+ if (!fwnode) {
+ dev_err(&i2c->dev, "no fwnode\n");
+ return -EINVAL;
+ }
+
+ 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;
+ }
+
+ /*
+ * 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.
+ */
+ intb_domain = regmap_irq_get_domain(intb_irq_data);
+
+ for (i = 0; i < num_intb; i++) {
+ struct resource *res = &regulator_res[i];
+
+ *res = regulator_intb_irqs[i];
+ res->start = res->end = irq_create_mapping(intb_domain,
+ res->start);
+ }
+
+ 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 = &regulator_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..47b07171dcb2
--- /dev/null
+++ b/include/linux/mfd/rohm-bd96801.h
@@ -0,0 +1,212 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2020 ROHM Semiconductors */
+
+#ifndef __LINUX_MFD_BD96801_H__
+#define __LINUX_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.
+ * For now we just handle the INTB.
+ *
+ * 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 =]


Attachments:
(No filename) (29.63 kB)
signature.asc (499.00 B)
Download all attachments

2024-04-02 13:15:13

by Matti Vaittinen

[permalink] [raw]
Subject: [RFC PATCH 4/6] regulator: bd96801: ROHM BD96801 PMIC regulators

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]>

---
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 | 2109 +++++++++++++++++++++++++
3 files changed, 2123 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..d66fefc06308
--- /dev/null
+++ b/drivers/regulator/bd96801-regulator.c
@@ -0,0 +1,2109 @@
+// 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, &reg);
+ 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, &reg);
+ 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;
+
+ *pdata = bd96801_data;
+
+ /*
+ * 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_kzalloc(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_kzalloc(&pdev->dev, 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 struct platform_driver bd96801_regulator = {
+ .driver = {
+ .name = "bd96801-pmic"
+ },
+ .probe = bd96801_probe,
+};
+
+module_platform_driver(bd96801_regulator);
+
+MODULE_AUTHOR("Matti Vaittinen <[email protected]>");
+MODULE_DESCRIPTION("BD96801 voltage regulator driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:bd96801-pmic");
--
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 =]


Attachments:
(No filename) (69.25 kB)
signature.asc (499.00 B)
Download all attachments

2024-04-02 13:46:47

by Matti Vaittinen

[permalink] [raw]
Subject: [RFC PATCH 2/6] dt-bindings: mfd: bd96801 PMIC core

ROHM BD96801 is a highly configurable automotive grade PMIC. Introduce
DT bindings for the BD96801 core.

Signed-off-by: Matti Vaittinen <[email protected]>
---
.../bindings/mfd/rohm,bd96801-pmic.yaml | 155 ++++++++++++++++++
1 file changed, 155 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..0d512fe19081
--- /dev/null
+++ b/Documentation/devicetree/bindings/mfd/rohm,bd96801-pmic.yaml
@@ -0,0 +1,155 @@
+# 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
+
+ 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 =]


Attachments:
(No filename) (6.91 kB)
signature.asc (499.00 B)
Download all attachments

2024-04-02 13:48:42

by Matti Vaittinen

[permalink] [raw]
Subject: [RFC PATCH 5/6] watchdog: ROHM BD96801 PMIC WDG driver

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]>
---
drivers/watchdog/Kconfig | 13 ++
drivers/watchdog/Makefile | 1 +
drivers/watchdog/bd96801_wdt.c | 375 +++++++++++++++++++++++++++++++++
3 files changed, 389 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..cb2b526ecc21
--- /dev/null
+++ b/drivers/watchdog/bd96801_wdt.c
@@ -0,0 +1,375 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2022 ROHM Semiconductors
+ *
+ * ROHM BD96801 watchdog driver
+ */
+
+#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;
+ bool always_running;
+ 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);
+
+ if (!w->always_running)
+ return regmap_update_bits(w->regmap, BD96801_REG_WD_CONF,
+ BD96801_WD_EN_MASK, BD96801_WD_DISABLE);
+ set_bit(WDOG_HW_RUNNING, &wdt->status);
+
+ return 0;
+}
+
+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;
+}
+
+static int bd96801_wdt_probe(struct platform_device *pdev)
+{
+ struct wdtbd96801 *w;
+ int ret;
+ 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);
+
+ w->always_running = device_property_read_bool(pdev->dev.parent,
+ "always-running");
+
+ 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);
+
+ if (w->always_running)
+ bd96801_wdt_start(&w->wdt);
+
+ return devm_watchdog_register_device(&pdev->dev, &w->wdt);
+}
+
+static struct platform_driver bd96801_wdt = {
+ .driver = {
+ .name = "bd96801-wdt"
+ },
+ .probe = bd96801_wdt_probe,
+};
+module_platform_driver(bd96801_wdt);
+
+MODULE_AUTHOR("Matti Vaittinen <[email protected]>");
+MODULE_DESCRIPTION("BD96801 watchdog driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:bd96801-wdt");
--
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 =]


Attachments:
(No filename) (12.19 kB)
signature.asc (499.00 B)
Download all attachments

2024-04-02 16:15:00

by Krzysztof Kozlowski

[permalink] [raw]
Subject: Re: [RFC PATCH 4/6] regulator: bd96801: ROHM BD96801 PMIC regulators

On 02/04/2024 15:10, Matti Vaittinen wrote:
> 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.
>

..

> +
> +static int initialize_pmic_data(struct device *dev,
> + struct bd96801_pmic_data *pdata)
> +{
> + int r, i;
> +
> + *pdata = bd96801_data;
> +
> + /*
> + * 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_kzalloc(dev, num_infos * sizeof(*new), GFP_KERNEL);

Aren't you open coding devm_kcalloc?

> + 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_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_kzalloc(&pdev->dev, sizeof(bd96801_data), GFP_KERNEL);

This and assignment in initialize_pmic_data() could be probably
devm_kmemdup() which would be a bit more obvious for the reader.

> + 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");
This does not guarantee that interrupts are properly set up. Don't you
have some state shared between parent and this device where you could
mark that interrupts are OK?

> + if (ret < 0)
> + use_errb = false;
> + else
> + use_errb = true;
> +

..

> +
> + if (use_errb)
> + return bd96801_global_errb_irqs(pdev, all_rdevs,
> + ARRAY_SIZE(all_rdevs));
> +
> + return 0;
> +}
> +
> +static struct platform_driver bd96801_regulator = {
> + .driver = {
> + .name = "bd96801-pmic"
> + },
> + .probe = bd96801_probe,
> +};
> +
> +module_platform_driver(bd96801_regulator);
> +
> +MODULE_AUTHOR("Matti Vaittinen <[email protected]>");
> +MODULE_DESCRIPTION("BD96801 voltage regulator driver");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:bd96801-pmic");

Just add platform device ID table with MODULE_DEVICE_TABLE(). You should
not need MODULE_ALIAS() in normal cases. MODULE_ALIAS() is not a
substitute for incomplete ID table.

Best regards,
Krzysztof


2024-04-02 16:45:48

by Krzysztof Kozlowski

[permalink] [raw]
Subject: Re: [RFC PATCH 5/6] watchdog: ROHM BD96801 PMIC WDG driver

On 02/04/2024 15:11, 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

..

> +
> +static struct platform_driver bd96801_wdt = {
> + .driver = {
> + .name = "bd96801-wdt"
> + },
> + .probe = bd96801_wdt_probe,
> +};
> +module_platform_driver(bd96801_wdt);
> +
> +MODULE_AUTHOR("Matti Vaittinen <[email protected]>");
> +MODULE_DESCRIPTION("BD96801 watchdog driver");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:bd96801-wdt");

Same comment on alias. Please use ID table.

Best regards,
Krzysztof


2024-04-02 17:12:30

by Guenter Roeck

[permalink] [raw]
Subject: Re: [RFC PATCH 5/6] watchdog: ROHM BD96801 PMIC WDG driver

On Tue, Apr 02, 2024 at 04:11:41PM +0300, 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]>
> ---
> drivers/watchdog/Kconfig | 13 ++
> drivers/watchdog/Makefile | 1 +
> drivers/watchdog/bd96801_wdt.c | 375 +++++++++++++++++++++++++++++++++
> 3 files changed, 389 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..cb2b526ecc21
> --- /dev/null
> +++ b/drivers/watchdog/bd96801_wdt.c
> @@ -0,0 +1,375 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2022 ROHM Semiconductors
> + *
> + * ROHM BD96801 watchdog driver
> + */
> +
> +#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;
> + bool always_running;
> + 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);
> +
> + if (!w->always_running)
> + return regmap_update_bits(w->regmap, BD96801_REG_WD_CONF,
> + BD96801_WD_EN_MASK, BD96801_WD_DISABLE);
> + set_bit(WDOG_HW_RUNNING, &wdt->status);
> +
> + return 0;
> +}
> +
> +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;
> + }

I don't see the devicetree bindings documented in the series.

I am also a bit surprised that the interrupt isn't handled in the driver.
Please explain.

> +
> + return 0;
> +}
> +
> +static int bd96801_wdt_probe(struct platform_device *pdev)
> +{
> + struct wdtbd96801 *w;
> + int ret;
> + 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);

dev_get_regmap() can return 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);
> +
> + w->always_running = device_property_read_bool(pdev->dev.parent,
> + "always-running");
> +
Without documentation, it looks like the always-running (from
linux,wdt-gpio.yaml) may be abused. Its defined meaning is
"the watchdog is always running and can not be stopped". Its
use here seems to be "start watchdog when loading the module and
prevent it from being stopped".

Oh well, looks like the abuse was introduced with bd9576_wdt. That
doesn't make it better. At the very least it needs to be documented
that the property does not have the intended (documented) meaning.

> + 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);
> +
> + if (w->always_running)
> + bd96801_wdt_start(&w->wdt);

I think this needs to set WDOG_HW_RUNNING or the watchdog will trigger
a reboot if the watchdog device is not opened and the watchdog wasn't
already running when the module was loaded.

That makes me wonder what happens if the property is set and the
watchdog daemon isn't started in the bd9576_wdt driver.

> +
> + return devm_watchdog_register_device(&pdev->dev, &w->wdt);
> +}
> +
> +static struct platform_driver bd96801_wdt = {
> + .driver = {
> + .name = "bd96801-wdt"
> + },
> + .probe = bd96801_wdt_probe,
> +};
> +module_platform_driver(bd96801_wdt);
> +
> +MODULE_AUTHOR("Matti Vaittinen <[email protected]>");
> +MODULE_DESCRIPTION("BD96801 watchdog driver");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:bd96801-wdt");
> --
> 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 =]



2024-04-03 06:34:51

by Matti Vaittinen

[permalink] [raw]
Subject: Re: [RFC PATCH 5/6] watchdog: ROHM BD96801 PMIC WDG driver

Hi Guenter,

First of all, thanks for the review. It was quick! Especially when we
speak of a RFC series. Very much appreciated.

On 4/2/24 20:11, Guenter Roeck wrote:
> On Tue, Apr 02, 2024 at 04:11:41PM +0300, Matti Vaittinen wrote >> +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;
>> + }
>
> I don't see the devicetree bindings documented in the series.

Seems like I have missed this WDG binding. But after reading your
comment below, I am wondering if I should just drop the binding and
default to "prstb" (shutdown should the feeding be skipped) - and leave
the "intb-only" case for one who actually needs such.

> I am also a bit surprised that the interrupt isn't handled in the driver.
> Please explain.

Basically, I just had no idea what the IRQ should do in the generic
case. If we get an interrupt, it means the WDG feeding has failed. My
thinking is that, what should happen is forced reset. I don't see how
that can be done in reliably manner from an IRQ handler.

When the "prstb WDG action" is set (please, see the above DT binding
handling), the PMIC shall shut down power outputs. This should get the
watchdog's job done.

With the "intb-only"-option, PMIC will not turn off the power. I'd
expect there to be some external HW connection which handles the reset
by HW.

After all this being said, I wonder if I should just unconditionally
configure the PMIC to always turn off the power (prstb option) should
the feeding fail? Or do someone have some suggestion what the IRQ
handler should do (except maybe print an error msg)?

>
>> +
>> + return 0;
>> +}
>> +
>> +static int bd96801_wdt_probe(struct platform_device *pdev)
>> +{
>> + struct wdtbd96801 *w;
>> + int ret;
>> + 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);
>
> dev_get_regmap() can return 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);
>> +
>> + w->always_running = device_property_read_bool(pdev->dev.parent,
>> + "always-running");
>> +
> Without documentation, it looks like the always-running (from
> linux,wdt-gpio.yaml) may be abused. Its defined meaning is
> "the watchdog is always running and can not be stopped". Its
> use here seems to be "start watchdog when loading the module and
> prevent it from being stopped".

Yes. You're right.

> Oh well, looks like the abuse was introduced with bd9576_wdt. That
> doesn't make it better. At the very least it needs to be documented
> that the property does not have the intended (documented) meaning.

I can raise my hand for a sign of an error here. I've been misreading
the intended meaning of the always-running. Not sure if I've picked it
from another driver (maybe GPIO watchdog), or if I've just
misinterpreted the binding docs.

Do you suggest me to add a note in the BD9576 binding doc (there is no
BD9576 specific binding doc for watchdog. I wonder whether this warrants
adding one under watchdog or if the note can be added under
Documentation/devicetree/bindings/mfd/rohm,bd9576...).

>> + 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);
>> +
>> + if (w->always_running)
>> + bd96801_wdt_start(&w->wdt);
>
> I think this needs to set WDOG_HW_RUNNING or the watchdog will trigger
> a reboot if the watchdog device is not opened and the watchdog wasn't
> already running when the module was loaded.

I believe you're right. Seems I haven't properly tested this path.

> That makes me wonder what happens if the property is set and the
> watchdog daemon isn't started in the bd9576_wdt driver.

My assumption is the dog starts barking. I'll see if I find the BD9576
break-out board from one of my boxes to wire it up and test this. If the
always-running is not working it might be justified to just drop it from
both drivers. I believe it'd be indication that no-one is really using
the always-running with the upstream driver.

Thanks a ton!

Yours,
-- Matti

--
Matti Vaittinen
Linux kernel developer at ROHM Semiconductors
Oulu Finland

~~ When things go utterly wrong vim users can always type :help! ~~


2024-04-03 07:43:36

by Matti Vaittinen

[permalink] [raw]
Subject: Re: [RFC PATCH 4/6] regulator: bd96801: ROHM BD96801 PMIC regulators

Hi dee Ho Krzysztof,

Heading to the Seattle? If so - Enjoy! It's a bummer I'm not able to
share a beer with you in ELC this time.

On 4/2/24 19:14, Krzysztof Kozlowski wrote:
> On 02/04/2024 15:10, Matti Vaittinen wrote:
>> 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.
>>
>
> ...
>
>> +
>> +static int initialize_pmic_data(struct device *dev,
>> + struct bd96801_pmic_data *pdata)
>> +{
>> + int r, i;
>> +
>> + *pdata = bd96801_data;
>> +
>> + /*
>> + * 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_kzalloc(dev, num_infos * sizeof(*new), GFP_KERNEL);
>
> Aren't you open coding devm_kcalloc?

I think yes. Thanks.

>> + 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_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_kzalloc(&pdev->dev, sizeof(bd96801_data), GFP_KERNEL);
>
> This and assignment in initialize_pmic_data() could be probably
> devm_kmemdup() which would be a bit more obvious for the reader.

I think you're right.

>> + 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");
> This does not guarantee that interrupts are properly set up.

Hmm. Yes, you're right. I'm not sure if I did think of this.

> Don't you
> have some state shared between parent and this device where you could
> mark that interrupts are OK?

There is currently no need to share/allocate any private data from the
MFD. We get the regmap using dev_get_regmap, and interrupts using the
platform_get_irq_byname(). Nothing else is shared between the MFD and
sub-devices.

Considering the use of platform_get_irq_byname() - and how failures to
get 'errb' IRQs are silently ignored in bd96801_global_errb_irqs() and
in bd96801_rdev_errb_irqs() - this check is just a slight optimization
to not even try registering the errb IRQs if they're not found from the
device tree. So, I think things do not really go south even if we go to
"errb route" when the "errb" IRQs aren't successfully registered.

Whether this warrants a comment, or if this check is just unnecessarily
complex can be pondered. Personally I think the purpose is pretty clear
and thus the complexity is not added that much - but yes, a comment
above call(s) to the platform_get_irq_byname() saying errb IRQs are not
guaranteed to be populated might be justified.

>
>> + if (ret < 0)
>> + use_errb = false;
>> + else
>> + use_errb = true;
>> +
>
> ...
>
>> +
>> + if (use_errb)
>> + return bd96801_global_errb_irqs(pdev, all_rdevs,
>> + ARRAY_SIZE(all_rdevs));
>> +
>> + return 0;
>> +}
>> +
>> +static struct platform_driver bd96801_regulator = {
>> + .driver = {
>> + .name = "bd96801-pmic"
>> + },
>> + .probe = bd96801_probe,
>> +};
>> +
>> +module_platform_driver(bd96801_regulator);
>> +
>> +MODULE_AUTHOR("Matti Vaittinen <[email protected]>");
>> +MODULE_DESCRIPTION("BD96801 voltage regulator driver");
>> +MODULE_LICENSE("GPL");
>> +MODULE_ALIAS("platform:bd96801-pmic");
>
> Just add platform device ID table with MODULE_DEVICE_TABLE(). You should
> not need MODULE_ALIAS() in normal cases. MODULE_ALIAS() is not a
> substitute for incomplete ID table.

I guess I have something to learn here. Thanks. :)

Take care
-- Matti

--
Matti Vaittinen
Linux kernel developer at ROHM Semiconductors
Oulu Finland

~~ When things go utterly wrong vim users can always type :help! ~~


2024-04-03 12:59:14

by Guenter Roeck

[permalink] [raw]
Subject: Re: [RFC PATCH 5/6] watchdog: ROHM BD96801 PMIC WDG driver

On Wed, Apr 03, 2024 at 09:34:35AM +0300, Matti Vaittinen wrote:
> Hi Guenter,
>
> First of all, thanks for the review. It was quick! Especially when we speak
> of a RFC series. Very much appreciated.
>
> On 4/2/24 20:11, Guenter Roeck wrote:
> > On Tue, Apr 02, 2024 at 04:11:41PM +0300, Matti Vaittinen wrote >> +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;
> > > + }
> >
> > I don't see the devicetree bindings documented in the series.
>
> Seems like I have missed this WDG binding. But after reading your comment
> below, I am wondering if I should just drop the binding and default to
> "prstb" (shutdown should the feeding be skipped) - and leave the "intb-only"
> case for one who actually needs such.
>
> > I am also a bit surprised that the interrupt isn't handled in the driver.
> > Please explain.
>
> Basically, I just had no idea what the IRQ should do in the generic case. If
> we get an interrupt, it means the WDG feeding has failed. My thinking is
> that, what should happen is forced reset. I don't see how that can be done
> in reliably manner from an IRQ handler.
>
> When the "prstb WDG action" is set (please, see the above DT binding
> handling), the PMIC shall shut down power outputs. This should get the
> watchdog's job done.
>
> With the "intb-only"-option, PMIC will not turn off the power. I'd expect
> there to be some external HW connection which handles the reset by HW.
>
> After all this being said, I wonder if I should just unconditionally
> configure the PMIC to always turn off the power (prstb option) should the
> feeding fail? Or do someone have some suggestion what the IRQ handler should
> do (except maybe print an error msg)?
>

Other watchdog drivers call emergency_restart() if the watchdog times out
and triggers an interrupt. Are you saying this won't work for this system ?
If so, please explain.

Thanks,
Guenter

2024-04-03 13:01:56

by Matti Vaittinen

[permalink] [raw]
Subject: Re: [RFC PATCH 5/6] watchdog: ROHM BD96801 PMIC WDG driver

On 4/3/24 15:41, Guenter Roeck wrote:
> On Wed, Apr 03, 2024 at 09:34:35AM +0300, Matti Vaittinen wrote:
>> Hi Guenter,
>>
>> First of all, thanks for the review. It was quick! Especially when we speak
>> of a RFC series. Very much appreciated.
>>
>> On 4/2/24 20:11, Guenter Roeck wrote:
>>> On Tue, Apr 02, 2024 at 04:11:41PM +0300, Matti Vaittinen wrote >> +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;
>>>> + }
>>>
>>> I don't see the devicetree bindings documented in the series.
>>
>> Seems like I have missed this WDG binding. But after reading your comment
>> below, I am wondering if I should just drop the binding and default to
>> "prstb" (shutdown should the feeding be skipped) - and leave the "intb-only"
>> case for one who actually needs such.
>>
>>> I am also a bit surprised that the interrupt isn't handled in the driver.
>>> Please explain.
>>
>> Basically, I just had no idea what the IRQ should do in the generic case. If
>> we get an interrupt, it means the WDG feeding has failed. My thinking is
>> that, what should happen is forced reset. I don't see how that can be done
>> in reliably manner from an IRQ handler.
>>
>> When the "prstb WDG action" is set (please, see the above DT binding
>> handling), the PMIC shall shut down power outputs. This should get the
>> watchdog's job done.
>>
>> With the "intb-only"-option, PMIC will not turn off the power. I'd expect
>> there to be some external HW connection which handles the reset by HW.
>>
>> After all this being said, I wonder if I should just unconditionally
>> configure the PMIC to always turn off the power (prstb option) should the
>> feeding fail? Or do someone have some suggestion what the IRQ handler should
>> do (except maybe print an error msg)?
>>
>
> Other watchdog drivers call emergency_restart() if the watchdog times out
> and triggers an interrupt. Are you saying this won't work for this system ?
> If so, please explain.
>

Thanks Guenter. If it works with systems using other devices, then it
should work (to the same extent) with systems using this PMIC. Thanks.

I'll add the IRQ handling to next version - but it may take a while as
I'm currently having some problems with the IRQs in general, and because
I'll wait for feedback from Mark to the regulator part.

Yours,
-- Matti

--
Matti Vaittinen
Linux kernel developer at ROHM Semiconductors
Oulu Finland

~~ When things go utterly wrong vim users can always type :help! ~~


2024-04-03 13:28:54

by Guenter Roeck

[permalink] [raw]
Subject: Re: [RFC PATCH 5/6] watchdog: ROHM BD96801 PMIC WDG driver

On Wed, Apr 03, 2024 at 03:47:12PM +0300, Matti Vaittinen wrote:
> >
> > Other watchdog drivers call emergency_restart() if the watchdog times out
> > and triggers an interrupt. Are you saying this won't work for this system ?
> > If so, please explain.
> >
>
> Thanks Guenter. If it works with systems using other devices, then it should
> work (to the same extent) with systems using this PMIC. Thanks.
>

You might also consider to just call panic(). What is what we do if the
pretimeout panic governor is enabled.

That makes me wonder if it would make sense to introduce watchdog timeout
governors, similar to the existing pretimeout governors. Maybe if I ever
find the time to do it ...

Guenter

> I'll add the IRQ handling to next version - but it may take a while as I'm
> currently having some problems with the IRQs in general, and because I'll
> wait for feedback from Mark to the regulator part.
>
> Yours,
> -- Matti
>
> --
> Matti Vaittinen
> Linux kernel developer at ROHM Semiconductors
> Oulu Finland
>
> ~~ When things go utterly wrong vim users can always type :help! ~~
>

2024-04-03 14:26:18

by Krzysztof Kozlowski

[permalink] [raw]
Subject: Re: [RFC PATCH 4/6] regulator: bd96801: ROHM BD96801 PMIC regulators

On 03/04/2024 09:38, Matti Vaittinen wrote:
> Hi dee Ho Krzysztof,
>
> Heading to the Seattle? If so - Enjoy! It's a bummer I'm not able to
> share a beer with you in ELC this time.

Second chance, I hope, will be Vienna in September.


..

>>> +
>>> + 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");
>> This does not guarantee that interrupts are properly set up.
>
> Hmm. Yes, you're right. I'm not sure if I did think of this.
>
>> Don't you
>> have some state shared between parent and this device where you could
>> mark that interrupts are OK?
>
> There is currently no need to share/allocate any private data from the
> MFD. We get the regmap using dev_get_regmap, and interrupts using the
> platform_get_irq_byname(). Nothing else is shared between the MFD and
> sub-devices.
>
> Considering the use of platform_get_irq_byname() - and how failures to
> get 'errb' IRQs are silently ignored in bd96801_global_errb_irqs() and
> in bd96801_rdev_errb_irqs() - this check is just a slight optimization
> to not even try registering the errb IRQs if they're not found from the
> device tree. So, I think things do not really go south even if we go to
> "errb route" when the "errb" IRQs aren't successfully registered.
>
> Whether this warrants a comment, or if this check is just unnecessarily
> complex can be pondered. Personally I think the purpose is pretty clear
> and thus the complexity is not added that much - but yes, a comment
> above call(s) to the platform_get_irq_byname() saying errb IRQs are not
> guaranteed to be populated might be justified.
>

Fine with me.

Best regards,
Krzysztof


2024-04-04 07:26:47

by Matti Vaittinen

[permalink] [raw]
Subject: Re: [RFC PATCH 0/6] Support ROHM BD96801 scalable PMIC

On 4/2/24 16:07, Matti Vaittinen wrote:
> Another "oddity" is that the PMIC has two physical IRQ lines. When I
> last wrote this patch in 2021 I had some naming collison in debugfs for
> the IRQ domains. Back then I used:
> irq_domain_update_bus_token(intb_domain, DOMAIN_BUS_WIRED);
> to work-around the issue. Now, when rebasing to v6.9-rc1 the naming
> collision was gone and things seemed to work. However, it'd be great if
> the IRQ code in MFD driver was reviewed by greater minds :)

It appears my statement "things seemed to work" is a bit too optimistic.
I am afraid my approach of having two separate IRQ domains for one
device (and DT-node) is just somehow fundamentally wrong. It'd be great
to learn what's the correct "ideology" here.

It appears the naming collision is still there. My config just had the
CONFIG_GENERIC_IRQ_DEBUGFS disabled. Enabling it shows the same naming
collison:
debugfs: File
':ocp:interconnect@48000000:segment@100000:target-module@9c000:i2c@0:pmic@60'
in directory 'domains' already present!

If I'm not mistaken the debugfs file name is generated from the
device-tree node path+name. This is a subtle hint that it is not
expected there are more than 1 IRQ-domain / device. I guess this kind of
makes sense if we can have more than 1 HWIRQ handled by a single domain
(I don't recall having to ever write such domain/IRQ-controller before,
but I think it should be possible).

I have now 3 new questions =)

1. Should we be able to have more than 1 IRQ domain / device?
2. Should regmap_irq support having more than 1 HWIRQ
3. If answer to 1 is "no" - should we protect against this somehow? (see
why below).

When CONFIG_GENERIC_IRQ_DEBUGFS is disabled, adding the two IRQ
controllers with own IRQ domains (intb and errb here) to a single device
is seemingly successful. I see no complaints / errors. Also, most of the
IRQs seem to work - but not all. In my case trying to issue:

cat /proc/interrupts

will oops. Also, looking in the /sys/kernel/irq/ lists folders for all
the "intb" and "errb" IRQs - but reading the files contained in these
directories will cause an oops for all "errb" interrupts except for the
first 16.

Finally, if I use the
irq_domain_update_bus_token(intb_domain, DOMAIN_BUS_WIRED);

to add "-1" at the end of the "intb" - domain name resulting domains:

:ocp:interconnect@48000000:segment@100000:target-module@9c000:i2c@0:pmic@60
:ocp:interconnect@48000000:segment@100000:target-module@9c000:i2c@0:pmic@60-1

then it seems that reading the IRQ information from the /proc/interrupts
works as expected. Here I am making a wild guess that the name of the
domain is used as a key for some data-lookups, and having two domains
with a same name will either overwrite something or cause wrong domain
data to be fetched. (This is just guessing for now).

Any tips, hints or thoughts on this?

Yours,
-- Matti

--
Matti Vaittinen
Linux kernel developer at ROHM Semiconductors
Oulu Finland

~~ When things go utterly wrong vim users can always type :help! ~~


2024-04-04 12:13:17

by Mark Brown

[permalink] [raw]
Subject: Re: [RFC PATCH 0/6] Support ROHM BD96801 scalable PMIC

On Thu, Apr 04, 2024 at 10:26:34AM +0300, Matti Vaittinen wrote:

> 1. Should we be able to have more than 1 IRQ domain / device?
> 2. Should regmap_irq support having more than 1 HWIRQ

I would expect each parent interrupt to show up as a separate remap_irq.

> then it seems that reading the IRQ information from the /proc/interrupts
> works as expected. Here I am making a wild guess that the name of the domain
> is used as a key for some data-lookups, and having two domains with a same
> name will either overwrite something or cause wrong domain data to be
> fetched. (This is just guessing for now).

So if we arrange to supply a name when we register multiple domains
things should work fine?


Attachments:
(No filename) (717.00 B)
signature.asc (499.00 B)
Download all attachments

2024-04-04 13:16:10

by Matti Vaittinen

[permalink] [raw]
Subject: Re: [RFC PATCH 0/6] Support ROHM BD96801 scalable PMIC

Hi Mark,

On 4/4/24 15:09, Mark Brown wrote:
> On Thu, Apr 04, 2024 at 10:26:34AM +0300, Matti Vaittinen wrote:
>
>> 1. Should we be able to have more than 1 IRQ domain / device?
>> 2. Should regmap_irq support having more than 1 HWIRQ
>
> I would expect each parent interrupt to show up as a separate remap_irq.
>
>> then it seems that reading the IRQ information from the /proc/interrupts
>> works as expected. Here I am making a wild guess that the name of the domain
>> is used as a key for some data-lookups, and having two domains with a same
>> name will either overwrite something or cause wrong domain data to be
>> fetched. (This is just guessing for now).
>
> So if we arrange to supply a name when we register multiple domains
> things should work fine?

Thanks for taking the time to look at my questions :)
I have been debugging this thing whole day today, without getting too
far :) It seems there is something beyond the name collision though.

After I tried adding '-1' to the end of the other domain name to avoid
the debugfs name collision I managed to do couple of successful runs -
after which I reported here that problem seems to be just the naming.
Soon after sending that mail I hit the oops again even though the naming
was fixed.

Further debugging shows that the desc->action->name for the last 28
'errb' IRQs get corrupted. This might point more to the IRQ requester
side - so I need to further study the BD96801 driver side as well as the
regulator_irq_helper. I'm having the creeping feeling that at the end of
the day I need to find the guilty one from the mirror :)

But yes, creating 2 regmap-IRQ controllers for one device seems to
generate naming conflict in the debugfs - so unless I'm mistaken, with
the current regmap-IRQ we can't have more than 1 regmap-IRQ entity for a
single device.

Just please give me some more time to see if I find the cause of the
corruption and I hope I can write more concrete description. For now it
was enough for me to hear having more than 1 IRQ domain / device is not
something on the "DON'T DO THIS" -list.

Yours,
-- Matti

--
Matti Vaittinen
Linux kernel developer at ROHM Semiconductors
Oulu Finland

~~ When things go utterly wrong vim users can always type :help! ~~


2024-04-05 09:27:16

by Matti Vaittinen

[permalink] [raw]
Subject: Re: [RFC PATCH 0/6] Support ROHM BD96801 scalable PMIC

On 4/4/24 16:15, Matti Vaittinen wrote:
> Hi Mark,
>
> On 4/4/24 15:09, Mark Brown wrote:
>> On Thu, Apr 04, 2024 at 10:26:34AM +0300, Matti Vaittinen wrote:
>>
>>> 1. Should we be able to have more than 1 IRQ domain / device?
>>> 2. Should regmap_irq support having more than 1 HWIRQ
>>
>> I would expect each parent interrupt to show up as a separate remap_irq.
>>
>>> then it seems that reading the IRQ information from the /proc/interrupts
>>> works as expected. Here I am making a wild guess that the name of the
>>> domain
>>> is used as a key for some data-lookups, and having two domains with a
>>> same
>>> name will either overwrite something or cause wrong domain data to be
>>> fetched. (This is just guessing for now).

This was wrong guessing.

>> So if we arrange to supply a name when we register multiple domains
>> things should work fine?

After my latest findings, yes, I think so. How to do this correctly is
beyond me though. The __irq_domain_create() seems to me that the name is
meant to be the dt-node name when the controller is backed by a real
dt-node. Naming of the irq_domain_alloc_named_fwnode() sounds to me like
it is only intended to be used when there is no real fwnode. All
suggestions appreciated. Using the:
irq_domain_update_bus_token(intb_domain, DOMAIN_BUS_WIRED);
feels like a dirty hack, and won't scale if there is more HWIRQs.

> Thanks for taking the time to look at my questions :)
> I have been debugging this thing whole day today, without getting too
> far :) It seems there is something beyond the name collision though.
>
> After I tried adding '-1' to the end of the other domain name to avoid
> the debugfs name collision I managed to do couple of successful runs -
> after which I reported here that problem seems to be just the naming.
> Soon after sending that mail I hit the oops again even though the naming
> was fixed.
>
> Further debugging shows that the desc->action->name for the last 28
> 'errb' IRQs get corrupted. This might point more to the IRQ requester
> side - so I need to further study the BD96801 driver side as well as the
> regulator_irq_helper. I'm having the creeping feeling that at the end of
> the day I need to find the guilty one from the mirror :)

I was not wrong on this one. The regulator_irq_helper() duplicates
memory for the data given in const struct regulator_irq_desc *d - but
it does not duplicate the irq name pointed from the given
regulator_irq_desc. Nor does the request_threaded_irq(). I passed some
of the IRQ names from the stack in the BD96801 driver ... a bug I
should've caught earlier.

Well, good thing is that now I can fix the regulator_irq_helper() to do:

--- a/drivers/regulator/irq_helpers.c
+++ b/drivers/regulator/irq_helpers.c
@@ -352,6 +352,9 @@ void *regulator_irq_helper(struct device *dev,

h->irq = irq;
h->desc = *d;
+ h->desc.name = devm_kstrdup(dev, d->name, GFP_KERNEL);
+ if (!h->desc.name)
+ return ERR_PTR(-ENOMEM);

ret = init_rdev_state(dev, h, rdev, common_errs, per_rdev_errs,
rdev_amount);

I'll send a patch if this sounds like a correct thing to do.



--
Matti Vaittinen
Linux kernel developer at ROHM Semiconductors
Oulu Finland

~~ When things go utterly wrong vim users can always type :help! ~~


2024-04-05 21:28:11

by Mark Brown

[permalink] [raw]
Subject: Re: [RFC PATCH 0/6] Support ROHM BD96801 scalable PMIC

On Fri, Apr 05, 2024 at 12:19:40PM +0300, Matti Vaittinen wrote:

> Well, good thing is that now I can fix the regulator_irq_helper() to do:

> --- a/drivers/regulator/irq_helpers.c
> +++ b/drivers/regulator/irq_helpers.c
> @@ -352,6 +352,9 @@ void *regulator_irq_helper(struct device *dev,
>
> h->irq = irq;
> h->desc = *d;
> + h->desc.name = devm_kstrdup(dev, d->name, GFP_KERNEL);
> + if (!h->desc.name)
> + return ERR_PTR(-ENOMEM);
>
> ret = init_rdev_state(dev, h, rdev, common_errs, per_rdev_errs,
> rdev_amount);
>
> I'll send a patch if this sounds like a correct thing to do.

Sure.


Attachments:
(No filename) (695.00 B)
signature.asc (499.00 B)
Download all attachments

2024-04-11 14:39:54

by Lee Jones

[permalink] [raw]
Subject: Re: [RFC PATCH 3/6] mfd: support ROHM BD96801 PMIC core

On Tue, 02 Apr 2024, Matti Vaittinen wrote:

> 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]>
> ---
> drivers/mfd/Kconfig | 13 +
> drivers/mfd/Makefile | 1 +
> drivers/mfd/rohm-bd96801.c | 454 +++++++++++++++++++++++++++++++
> include/linux/mfd/rohm-bd96801.h | 212 +++++++++++++++
> include/linux/mfd/rohm-generic.h | 1 +
> 5 files changed, 681 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..947045eb3a8e 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

Power Management

> + IC for industrial and automotive use. The BD96801 can be used as a
> + master PMIC in a chained PMIC solutions with suitable companion PMICs.

solution

> +
> 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..7610d0114653
> --- /dev/null
> +++ b/drivers/mfd/rohm-bd96801.c
> @@ -0,0 +1,454 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +//
> +// Copyright (C) 2022 ROHM Semiconductors

No updates for 2 years?

> +//
> +// ROHM BD96801 PMIC driver

Only the SPDX should have C++ style comments.

> +#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;
> + struct regmap_irq_chip_data *intb_irq_data, *errb_irq_data;
> + struct irq_domain *intb_domain, *errb_domain;
> + const struct fwnode_handle *fwnode;
> + struct resource *regulator_res;
> + struct regmap *regmap;
> +
> + fwnode = dev_fwnode(&i2c->dev);
> + if (!fwnode) {
> + dev_err(&i2c->dev, "no fwnode\n");
> + return -EINVAL;

Why not dev_err_probe() here for uniformity?

> + }
> +
> + 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");

This function would look nicer if you expanded to 100-chars.

> + 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;
> + }

No need to clump these together - '\n' here please.

> + 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;
> + }
> +
> + /*
> + * 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.
> + */
> + intb_domain = regmap_irq_get_domain(intb_irq_data);
> +
> + for (i = 0; i < num_intb; i++) {
> + struct resource *res = &regulator_res[i];
> +
> + *res = regulator_intb_irqs[i];
> + res->start = res->end = irq_create_mapping(intb_domain,
> + res->start);
> + }
> +
> + 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 = &regulator_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;

'\n' here.

> + 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",
> + },

Should be on a single line.

> + { }
> +};
> +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);
> +}

'\n'

> +/* 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..47b07171dcb2
> --- /dev/null
> +++ b/include/linux/mfd/rohm-bd96801.h
> @@ -0,0 +1,212 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +/* Copyright (C) 2020 ROHM Semiconductors */
> +
> +#ifndef __LINUX_MFD_BD96801_H__
> +#define __LINUX_MFD_BD96801_H__

Drop the LINUX_ part.

> +#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.
> + * For now we just handle the INTB.
> + *
> + * 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 =]



--
Lee Jones [李琼斯]

2024-04-12 05:40:38

by Matti Vaittinen

[permalink] [raw]
Subject: Re: [RFC PATCH 3/6] mfd: support ROHM BD96801 PMIC core

Hi deee Ho Lee!

Thanks a ton for taking a look at this :) I already sent the V2
yesterday, briefly before receiving your comments. I think all of the
comments are relevant for the V2 as well, I will fix them for the V3
when I get to that. If you find the time to take a look at V2, then the
major things are addition of a watchdog IRQ + a work-around for the
debugFS name collision for IRQ domains.

On 4/11/24 17:38, Lee Jones wrote:
> On Tue, 02 Apr 2024, Matti Vaittinen wrote:
>
>> 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]>
>> ---
>> drivers/mfd/Kconfig | 13 +
>> drivers/mfd/Makefile | 1 +
>> drivers/mfd/rohm-bd96801.c | 454 +++++++++++++++++++++++++++++++
>> include/linux/mfd/rohm-bd96801.h | 212 +++++++++++++++
>> include/linux/mfd/rohm-generic.h | 1 +
>> 5 files changed, 681 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..947045eb3a8e 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
>
> Power Management

Out of the curiosity, why is the "Power Management IC" written with
capitals, when speaking of a class of devices instead of a model? (I am
100% fine with the change, just curious).

>
>> + IC for industrial and automotive use. The BD96801 can be used as a
>> + master PMIC in a chained PMIC solutions with suitable companion PMICs
..

>> +// SPDX-License-Identifier: GPL-2.0-only
>> +//
>> +// Copyright (C) 2022 ROHM Semiconductors
>
> No updates for 2 years?

The year should be updated - thanks. But, now that you asked... Almost
no updates. The patches have rotten in my outbox, waiting for the
permisson to be sent out... But yeah, I've sure added some changes
before sending the series - I'll update the copyright :)

>> +
>> +static int bd96801_i2c_probe(struct i2c_client *i2c)
>> +{
>> + int i, ret, intb_irq, errb_irq, num_regu_irqs, num_intb, num_errb = 0;
>> + struct regmap_irq_chip_data *intb_irq_data, *errb_irq_data;
>> + struct irq_domain *intb_domain, *errb_domain;
>> + const struct fwnode_handle *fwnode;
>> + struct resource *regulator_res;
>> + struct regmap *regmap;
>> +
>> + fwnode = dev_fwnode(&i2c->dev);
>> + if (!fwnode) {
>> + dev_err(&i2c->dev, "no fwnode\n");
>> + return -EINVAL;
>
> Why not dev_err_probe() here for uniformity?

I can change it to dev_err_probe() if it's strongly preferred. It just
feels silly to use dev_err_probe() when the return value is hardcoded.
Intentionally writing code like

err = -EINVAL;
if (err == ...)

just makes me feel a bit sick.

>> + }
>> +
>> + 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");
>
> This function would look nicer if you expanded to 100-chars.

The reason why I still prefer the good old 80-chars for files I work
with, is that I am often having 3 terminal windows parallel on my laptop
screen. (Or, when I have my wide mofnitor connected it is 3 editor
windows + minicom). I need to keep the terminals small enough.
Besides... I hate to admit this, but the time is finally taking it's
toll. My eyes aren't quite the same they were 2 years ago...

So, same old story, I can change this if it is important enough for
others, but personally I rather work with the short lines.

..

>> diff --git a/include/linux/mfd/rohm-bd96801.h b/include/linux/mfd/rohm-bd96801.h
>> new file mode 100644
>> index 000000000000..47b07171dcb2
>> --- /dev/null
>> +++ b/include/linux/mfd/rohm-bd96801.h
>> @@ -0,0 +1,212 @@
>> +/* SPDX-License-Identifier: GPL-2.0-or-later */
>> +/* Copyright (C) 2020 ROHM Semiconductors */
>> +

..

>> +/* IRQ register area */
>> +#define BD96801_REG_INT_MAIN 0x51
>> +
>> +/*
>> + * The BD96801 has two physical IRQ lines, INTB and ERRB.
>> + * For now we just handle the INTB.

Note to self, this comment is no longer true.

Thanks for the review!

Yours,
-- Matti


--
Matti Vaittinen
Linux kernel developer at ROHM Semiconductors
Oulu Finland

~~ When things go utterly wrong vim users can always type :help! ~~


2024-04-12 05:51:02

by Matti Vaittinen

[permalink] [raw]
Subject: Re: [RFC PATCH 3/6] mfd: support ROHM BD96801 PMIC core

On 4/12/24 08:40, Matti Vaittinen wrote:
> Hi deee Ho Lee!
>
> Thanks a ton for taking a look at this :) I already sent the V2
> yesterday, briefly before receiving your comments.

It's not completely unusual I don't know what I am doing. BUT, it's not
that common I don't know what I did... Seems like the V2 is still in my
outbox, so I will add the fixes to your comments prior sending it! :)

Sorry for the added confusion!

Yours,
-- Matti

--
Matti Vaittinen
Linux kernel developer at ROHM Semiconductors
Oulu Finland

~~ When things go utterly wrong vim users can always type :help! ~~


2024-04-12 07:24:02

by Lee Jones

[permalink] [raw]
Subject: Re: [RFC PATCH 3/6] mfd: support ROHM BD96801 PMIC core

On Fri, 12 Apr 2024, Matti Vaittinen wrote:

> Hi deee Ho Lee!
>
> Thanks a ton for taking a look at this :) I already sent the V2 yesterday,
> briefly before receiving your comments. I think all of the comments are
> relevant for the V2 as well, I will fix them for the V3 when I get to that.
> If you find the time to take a look at V2, then the major things are
> addition of a watchdog IRQ + a work-around for the debugFS name collision
> for IRQ domains.
>
> On 4/11/24 17:38, Lee Jones wrote:
> > On Tue, 02 Apr 2024, Matti Vaittinen wrote:
> >
> > > 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]>
> > > ---
> > > drivers/mfd/Kconfig | 13 +
> > > drivers/mfd/Makefile | 1 +
> > > drivers/mfd/rohm-bd96801.c | 454 +++++++++++++++++++++++++++++++
> > > include/linux/mfd/rohm-bd96801.h | 212 +++++++++++++++
> > > include/linux/mfd/rohm-generic.h | 1 +
> > > 5 files changed, 681 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..947045eb3a8e 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
> >
> > Power Management
>
> Out of the curiosity, why is the "Power Management IC" written with
> capitals, when speaking of a class of devices instead of a model? (I am 100%
> fine with the change, just curious).

It's no different to how its expressed in the tristate section above.

Power Management IC or PMIC.

"provides power management capabilities" describes its function?

"is a scalable Power Management IC", describes the device?

But actually, it just looks odd when both are used in the same section.

/me likes uniformity and consistency.

> > > + IC for industrial and automotive use. The BD96801 can be used as a
> > > + master PMIC in a chained PMIC solutions with suitable companion PMICs
> ...
>
> > > +// SPDX-License-Identifier: GPL-2.0-only
> > > +//
> > > +// Copyright (C) 2022 ROHM Semiconductors
> >
> > No updates for 2 years?
>
> The year should be updated - thanks. But, now that you asked... Almost no
> updates. The patches have rotten in my outbox, waiting for the permisson to
> be sent out... But yeah, I've sure added some changes before sending the
> series - I'll update the copyright :)
>
> > > +
> > > +static int bd96801_i2c_probe(struct i2c_client *i2c)
> > > +{
> > > + int i, ret, intb_irq, errb_irq, num_regu_irqs, num_intb, num_errb = 0;
> > > + struct regmap_irq_chip_data *intb_irq_data, *errb_irq_data;
> > > + struct irq_domain *intb_domain, *errb_domain;
> > > + const struct fwnode_handle *fwnode;
> > > + struct resource *regulator_res;
> > > + struct regmap *regmap;
> > > +
> > > + fwnode = dev_fwnode(&i2c->dev);
> > > + if (!fwnode) {
> > > + dev_err(&i2c->dev, "no fwnode\n");
> > > + return -EINVAL;
> >
> > Why not dev_err_probe() here for uniformity?
>
> I can change it to dev_err_probe() if it's strongly preferred. It just feels
> silly to use dev_err_probe() when the return value is hardcoded.

Not at all:

git grep dev_err_probe | grep "\-[A-Z]"

> Intentionally writing code like
>
> err = -EINVAL;
> if (err == ...)
>
> just makes me feel a bit sick.

Why would you want to do that?

> > > + }
> > > +
> > > + 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");
> >
> > This function would look nicer if you expanded to 100-chars.
>
> The reason why I still prefer the good old 80-chars for files I work with,
> is that I am often having 3 terminal windows parallel on my laptop screen.
> (Or, when I have my wide mofnitor connected it is 3 editor windows +
> minicom). I need to keep the terminals small enough. Besides... I hate to
> admit this, but the time is finally taking it's toll. My eyes aren't quite
> the same they were 2 years ago...

Upgrade your 14" CRT monitor to something more modern. :)

I have a 32" 4k monitor with a good sized font and each of my 3
terminals (per i3 workspace) are ~150 chars wide.

> So, same old story, I can change this if it is important enough for others,
> but personally I rather work with the short lines.

It's not a showstopper.

--
Lee Jones [李琼斯]

2024-04-12 09:05:57

by Matti Vaittinen

[permalink] [raw]
Subject: Re: [RFC PATCH 3/6] mfd: support ROHM BD96801 PMIC core

On 4/12/24 10:23, Lee Jones wrote:
> On Fri, 12 Apr 2024, Matti Vaittinen wrote:
>
>> Hi deee Ho Lee!
>>
>> Thanks a ton for taking a look at this :) I already sent the V2 yesterday,
>> briefly before receiving your comments. I think all of the comments are
>> relevant for the V2 as well, I will fix them for the V3 when I get to that.
>> If you find the time to take a look at V2, then the major things are
>> addition of a watchdog IRQ + a work-around for the debugFS name collision
>> for IRQ domains.
>>
>> On 4/11/24 17:38, Lee Jones wrote:
>>> On Tue, 02 Apr 2024, Matti Vaittinen wrote:
>>>
>>>> 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]>
>>>> ---
>>>> drivers/mfd/Kconfig | 13 +
>>>> drivers/mfd/Makefile | 1 +
>>>> drivers/mfd/rohm-bd96801.c | 454 +++++++++++++++++++++++++++++++
>>>> include/linux/mfd/rohm-bd96801.h | 212 +++++++++++++++
>>>> include/linux/mfd/rohm-generic.h | 1 +
>>>> 5 files changed, 681 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..947045eb3a8e 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
>>>
>>> Power Management
>>
>> Out of the curiosity, why is the "Power Management IC" written with
>> capitals, when speaking of a class of devices instead of a model? (I am 100%
>> fine with the change, just curious).
>
> It's no different to how its expressed in the tristate section above.
>
> Power Management IC or PMIC.
>
> "provides power management capabilities" describes its function?
>
> "is a scalable Power Management IC", describes the device?
>
> But actually, it just looks odd when both are used in the same section.
>
> /me likes uniformity and consistency.

It's okay, thanks for the explanation :)

>>>> + IC for industrial and automotive use. The BD96801 can be used as a
>>>> + master PMIC in a chained PMIC solutions with suitable companion PMICs
>> ...
>>
>>>> +static int bd96801_i2c_probe(struct i2c_client *i2c)
>>>> +{
>>>> + int i, ret, intb_irq, errb_irq, num_regu_irqs, num_intb, num_errb = 0;
>>>> + struct regmap_irq_chip_data *intb_irq_data, *errb_irq_data;
>>>> + struct irq_domain *intb_domain, *errb_domain;
>>>> + const struct fwnode_handle *fwnode;
>>>> + struct resource *regulator_res;
>>>> + struct regmap *regmap;
>>>> +
>>>> + fwnode = dev_fwnode(&i2c->dev);
>>>> + if (!fwnode) {
>>>> + dev_err(&i2c->dev, "no fwnode\n");
>>>> + return -EINVAL;
>>>
>>> Why not dev_err_probe() here for uniformity?
>>
>> I can change it to dev_err_probe() if it's strongly preferred. It just feels
>> silly to use dev_err_probe() when the return value is hardcoded.
>
> Not at all:
>
> git grep dev_err_probe | grep "\-[A-Z]"

Yes, I know people do use the dev_err_probe() with hardcoded errors but
it does not make me feel any better about it :)

>> Intentionally writing code like
>>
>> err = -EINVAL;
>> if (err == ...)
>>
>> just makes me feel a bit sick.
>
> Why would you want to do that?

This is what the dev_err_probe() with a hardcoded err does, right?

int dev_err_probe(const struct device *dev, int err, const char *fmt, ...)
{
...
if (err != -EPROBE_DEFER) {
dev_err(dev, "error %pe: %pV", ERR_PTR(err), &vaf);
} else {
device_set_deferred_probe_reason(dev, &vaf);
dev_dbg(dev, "error %pe: %pV", ERR_PTR(err), &vaf);
}
...
}

>
>>>> + }
>>>> +
>>>> + 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");
>>>
>>> This function would look nicer if you expanded to 100-chars.
>>
>> The reason why I still prefer the good old 80-chars for files I work with,
>> is that I am often having 3 terminal windows parallel on my laptop screen.
>> (Or, when I have my wide mofnitor connected it is 3 editor windows +
>> minicom). I need to keep the terminals small enough. Besides... I hate to
>> admit this, but the time is finally taking it's toll. My eyes aren't quite
>> the same they were 2 years ago...
>
> Upgrade your 14" CRT monitor to something more modern. :)

But those things were built to last! And throwing away perfectly working
stuff... :)

>
> I have a 32" 4k monitor with a good sized font and each of my 3
> terminals (per i3 workspace) are ~150 chars wide.
>
>> So, same old story, I can change this if it is important enough for others,
>> but personally I rather work with the short lines.
>
> It's not a showstopper.

I'll revise the line lengths for next version. I think this one still
won't go much over 80 chars, which may still fit on my terminals. I'll
change it if it fits, keep it if it wont. Thanks for pointing it out :)

Yours,
-- Matti

--
Matti Vaittinen
Linux kernel developer at ROHM Semiconductors
Oulu Finland

~~ When things go utterly wrong vim users can always type :help! ~~


2024-04-17 12:35:30

by Lee Jones

[permalink] [raw]
Subject: Re: [RFC PATCH 3/6] mfd: support ROHM BD96801 PMIC core

On Fri, 12 Apr 2024, Matti Vaittinen wrote:

> On 4/12/24 10:23, Lee Jones wrote:
> > On Fri, 12 Apr 2024, Matti Vaittinen wrote:
> >
> > > Hi deee Ho Lee!
> > >
> > > Thanks a ton for taking a look at this :) I already sent the V2 yesterday,
> > > briefly before receiving your comments. I think all of the comments are
> > > relevant for the V2 as well, I will fix them for the V3 when I get to that.
> > > If you find the time to take a look at V2, then the major things are
> > > addition of a watchdog IRQ + a work-around for the debugFS name collision
> > > for IRQ domains.
> > >
> > > On 4/11/24 17:38, Lee Jones wrote:
> > > > On Tue, 02 Apr 2024, Matti Vaittinen wrote:
> > > >
> > > > > 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]>
> > > > > ---
> > > > > drivers/mfd/Kconfig | 13 +
> > > > > drivers/mfd/Makefile | 1 +
> > > > > drivers/mfd/rohm-bd96801.c | 454 +++++++++++++++++++++++++++++++
> > > > > include/linux/mfd/rohm-bd96801.h | 212 +++++++++++++++
> > > > > include/linux/mfd/rohm-generic.h | 1 +
> > > > > 5 files changed, 681 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..947045eb3a8e 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
> > > >
> > > > Power Management
> > >
> > > Out of the curiosity, why is the "Power Management IC" written with
> > > capitals, when speaking of a class of devices instead of a model? (I am 100%
> > > fine with the change, just curious).
> >
> > It's no different to how its expressed in the tristate section above.
> >
> > Power Management IC or PMIC.
> >
> > "provides power management capabilities" describes its function?
> >
> > "is a scalable Power Management IC", describes the device?
> >
> > But actually, it just looks odd when both are used in the same section.
> >
> > /me likes uniformity and consistency.
>
> It's okay, thanks for the explanation :)
>
> > > > > + IC for industrial and automotive use. The BD96801 can be used as a
> > > > > + master PMIC in a chained PMIC solutions with suitable companion PMICs
> > > ...
> > >
> > > > > +static int bd96801_i2c_probe(struct i2c_client *i2c)
> > > > > +{
> > > > > + int i, ret, intb_irq, errb_irq, num_regu_irqs, num_intb, num_errb = 0;
> > > > > + struct regmap_irq_chip_data *intb_irq_data, *errb_irq_data;
> > > > > + struct irq_domain *intb_domain, *errb_domain;
> > > > > + const struct fwnode_handle *fwnode;
> > > > > + struct resource *regulator_res;
> > > > > + struct regmap *regmap;
> > > > > +
> > > > > + fwnode = dev_fwnode(&i2c->dev);
> > > > > + if (!fwnode) {
> > > > > + dev_err(&i2c->dev, "no fwnode\n");
> > > > > + return -EINVAL;
> > > >
> > > > Why not dev_err_probe() here for uniformity?
> > >
> > > I can change it to dev_err_probe() if it's strongly preferred. It just feels
> > > silly to use dev_err_probe() when the return value is hardcoded.
> >
> > Not at all:
> >
> > git grep dev_err_probe | grep "\-[A-Z]"
>
> Yes, I know people do use the dev_err_probe() with hardcoded errors but it
> does not make me feel any better about it :)

<look into my swirling eyes> Uniformity within the function!

> > > Intentionally writing code like
> > >
> > > err = -EINVAL;
> > > if (err == ...)
> > >
> > > just makes me feel a bit sick.
> >
> > Why would you want to do that?
>
> This is what the dev_err_probe() with a hardcoded err does, right?
>
> int dev_err_probe(const struct device *dev, int err, const char *fmt, ...)
> {
> ...
> if (err != -EPROBE_DEFER) {
> dev_err(dev, "error %pe: %pV", ERR_PTR(err), &vaf);
> } else {
> device_set_deferred_probe_reason(dev, &vaf);
> dev_dbg(dev, "error %pe: %pV", ERR_PTR(err), &vaf);
> }
> ...
> }

Attempt to purge this info from you brain!

> > > > > + }
> > > > > +
> > > > > + 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");
> > > >
> > > > This function would look nicer if you expanded to 100-chars.
> > >
> > > The reason why I still prefer the good old 80-chars for files I work with,
> > > is that I am often having 3 terminal windows parallel on my laptop screen.
> > > (Or, when I have my wide mofnitor connected it is 3 editor windows +
> > > minicom). I need to keep the terminals small enough. Besides... I hate to
> > > admit this, but the time is finally taking it's toll. My eyes aren't quite
> > > the same they were 2 years ago...
> >
> > Upgrade your 14" CRT monitor to something more modern. :)
>
> But those things were built to last! And throwing away perfectly working
> stuff... :)

Can't argue with that! Maybe put 2 side-by-side or 4 in a matrix!

--
Lee Jones [李琼斯]

2024-04-22 10:52:42

by Matti Vaittinen

[permalink] [raw]
Subject: Re: [RFC PATCH 0/6] Support ROHM BD96801 scalable PMIC

Hi dee Ho peeps,

On 4/5/24 12:19, Matti Vaittinen wrote:
> On 4/4/24 16:15, Matti Vaittinen wrote:
>> Hi Mark,
>>
>> On 4/4/24 15:09, Mark Brown wrote:
>>> On Thu, Apr 04, 2024 at 10:26:34AM +0300, Matti Vaittinen wrote:
>>>
>>>> 1. Should we be able to have more than 1 IRQ domain / device?
>>>> 2. Should regmap_irq support having more than 1 HWIRQ
>>>
>>> I would expect each parent interrupt to show up as a separate remap_irq.

..
>>> So if we arrange to supply a name when we register multiple domains
>>> things should work fine?
>
> After my latest findings, yes, I think so. How to do this correctly is
> beyond me though. The __irq_domain_create() seems to me that the name is
> meant to be the dt-node name when the controller is backed by a real
> dt-node. Naming of the irq_domain_alloc_named_fwnode() sounds to me like
> it is only intended to be used when there is no real fwnode. All
> suggestions appreciated. Using the:
> irq_domain_update_bus_token(intb_domain, DOMAIN_BUS_WIRED);
> feels like a dirty hack, and won't scale if there is more HWIRQs.

I tried taking a look at this again.

If we wanted to support multiple HWIRQs / regmap-IRQ controller, it
would require us to duplicate almost everything in the struct
regmap_irq_chip for every new parent IRQ. The status/mask register
information, IRQ type, etc. Naturally, it would require also duplicating
lot of the data contained in the struct regmap_irq_chip_data. I am not
sure if this could be done so the change is not reflected in the
existing IRQ data initialization macros etc. Furthermore, some API
changes would be required like changes to regmap_irq_get_domain().

I am a bit afraid this change, if implemented in regmap-IRQ, would be
very intrusive and potentially impact large amount of callers. But more
importantly, looking the amount of data that should be duplicated per
new HWIRQ makes me think that an IRQ controller is really a product of a
parent IRQ, not product of the device. Hence, assuming there is only one
IRQ controller instance / device does not feel any more correct than
assuming there is an IRQ controller instance / parent IRQ. Same thinking
applies to IRQ domains.

Thus, forcing the regmap-IRQ to support multiple parents instead of
having own regmap-IRQ instance / parent IRQ feels like fitting square
item to a round hole. I am sure fixing all the bugs I caused would give
donate a lot of EXP-points though :rolleyes:

Question is, should I still try?

Another option I see, is trying to think if irq-domain name could be
changed. (This is what the RFC v3 does, [ab]using the
irq_domain_update_bus_token()). I was a bit put off by the idea of
'instantiating' multiple domains (or regmap-IRQ controllers) from a
single node, but more I think of this, more I lean towards it. Besides,
this is not something completely odd, I think MFD devices do this
anyways. (Instantiate multiple [sub]devices from single DT-node). I
would love to get an opinion from someone who knows the 'fundamentals'
of the IRQ domains, and possibly some pointer for the right approach.

Finally we might also consider adding own sub-node in DT for each parent
IRQ - but this feels very wrong to me.

All in all, I am having very hard time trying to think how to proceed.
The last option for me is to skip support for the ERRB IRQ from the
BD96801 driver, which would leave this problem to the next person
working with a device providing multiple physical IRQs.

Yours,
-- Matti

--
Matti Vaittinen
Linux kernel developer at ROHM Semiconductors
Oulu Finland

~~ When things go utterly wrong vim users can always type :help! ~~