Merge tag 'ib-mfd-watchdog-v5.13' into ibs-for-mfd-merged
authorLee Jones <lee.jones@linaro.org>
Wed, 14 Apr 2021 15:01:10 +0000 (16:01 +0100)
committerLee Jones <lee.jones@linaro.org>
Wed, 14 Apr 2021 15:01:10 +0000 (16:01 +0100)
Immutable branch between MFD and Watchdog due for the v5.13 merge window

Documentation/devicetree/bindings/mfd/rohm,bd9576-pmic.yaml [new file with mode: 0644]
MAINTAINERS
drivers/mfd/Kconfig
drivers/mfd/Makefile
drivers/mfd/rohm-bd9576.c [new file with mode: 0644]
drivers/watchdog/Kconfig
drivers/watchdog/Makefile
drivers/watchdog/bd9576_wdt.c [new file with mode: 0644]
include/linux/mfd/rohm-bd957x.h [new file with mode: 0644]
include/linux/mfd/rohm-generic.h

diff --git a/Documentation/devicetree/bindings/mfd/rohm,bd9576-pmic.yaml b/Documentation/devicetree/bindings/mfd/rohm,bd9576-pmic.yaml
new file mode 100644 (file)
index 0000000..6483860
--- /dev/null
@@ -0,0 +1,123 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/mfd/rohm,bd9576-pmic.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: ROHM BD9576MUF and BD9573MUF Power Management Integrated Circuit bindings
+
+maintainers:
+  - Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>
+
+description: |
+  BD9576MUF and BD9573MUF are power management ICs primarily intended for
+  powering the R-Car series processors.
+  The IC provides 6 power outputs with configurable sequencing and safety
+  monitoring. A watchdog logic with slow ping/windowed modes is also included.
+
+properties:
+  compatible:
+    enum:
+      - rohm,bd9576
+      - rohm,bd9573
+
+  reg:
+    description:
+      I2C slave address.
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+
+  rohm,vout1-en-low:
+    description:
+      BD9576 and BD9573 VOUT1 regulator enable state can be individually
+      controlled by a GPIO. This is dictated by state of vout1-en pin during
+      the PMIC startup. If vout1-en is LOW during PMIC startup then the VOUT1
+      enable sate is controlled via this pin. Set this property if vout1-en
+      is wired to be down at PMIC start-up.
+    type: boolean
+
+  rohm,vout1-en-gpios:
+    description:
+      GPIO specifier to specify the GPIO connected to vout1-en for vout1 ON/OFF
+      state control.
+    maxItems: 1
+
+  rohm,ddr-sel-low:
+    description:
+      The BD9576 and BD9573 output voltage for DDR can be selected by setting
+      the ddr-sel pin low or high. Set this property if ddr-sel is grounded.
+    type: boolean
+
+  rohm,watchdog-enable-gpios:
+    description: The GPIO line used to enable the watchdog.
+    maxItems: 1
+
+  rohm,watchdog-ping-gpios:
+    description: The GPIO line used to ping the watchdog.
+    maxItems: 1
+
+  rohm,hw-timeout-ms:
+    maxItems: 2
+    description:
+      Watchog timeout in milliseconds. If single value is given it is
+      the maximum timeout. Eg. if pinging watchdog is not done within this time
+      limit the watchdog will be triggered. If two values are given watchdog
+      is configured in "window mode". Then first value is limit for short-ping
+      Eg. if watchdog is pinged sooner than that the watchdog will trigger.
+      When two values is given the second value is the maximum timeout.
+      # (HW) minimum for short timeout is 2ms, maximum 220 ms.
+      # (HW) minimum for max timeout is 4ms, maximum 4416 ms.
+
+  regulators:
+    $ref: ../regulator/rohm,bd9576-regulator.yaml
+    description:
+      List of child nodes that specify the regulators.
+
+required:
+  - compatible
+  - reg
+  - regulators
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+    #include <dt-bindings/leds/common.h>
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+        pmic: pmic@30 {
+            compatible = "rohm,bd9576";
+            reg = <0x30>;
+            rohm,vout1-en-low;
+            rohm,vout1-en-gpios = <&gpio2 6 GPIO_ACTIVE_HIGH>;
+            rohm,ddr-sel-low;
+            rohm,watchdog-enable-gpios = <&gpio2 6 GPIO_ACTIVE_HIGH>;
+            rohm,watchdog-ping-gpios = <&gpio2 7 GPIO_ACTIVE_HIGH>;
+            rohm,hw-timeout-ms = <150>, <2300>;
+
+            regulators {
+                boost1: regulator-vd50 {
+                    regulator-name = "VD50";
+                };
+                buck1: regulator-vd18 {
+                    regulator-name = "VD18";
+                };
+                buck2: regulator-vdddr {
+                    regulator-name = "VDDDR";
+                };
+                buck3: regulator-vd10 {
+                    regulator-name = "VD10";
+                };
+                ldo: regulator-voutl1 {
+                    regulator-name = "VOUTL1";
+                };
+                sw: regulator-vouts1 {
+                    regulator-name = "VOUTS1";
+                };
+            };
+        };
+    };
index 0386d7f..d453803 100644 (file)
@@ -15478,18 +15478,22 @@ F:    drivers/gpio/gpio-bd71828.c
 F:     drivers/mfd/rohm-bd70528.c
 F:     drivers/mfd/rohm-bd71828.c
 F:     drivers/mfd/rohm-bd718x7.c
+F:     drivers/mfd/rohm-bd9576.c
 F:     drivers/power/supply/bd70528-charger.c
 F:     drivers/regulator/bd70528-regulator.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/rohm-regulator.c
 F:     drivers/rtc/rtc-bd70528.c
 F:     drivers/watchdog/bd70528_wdt.c
+F:     drivers/watchdog/bd9576_wdt.c
 F:     include/linux/mfd/rohm-bd70528.h
 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-generic.h
 F:     include/linux/mfd/rohm-shared.h
 
index 7605bed..6e31210 100644 (file)
@@ -2001,6 +2001,17 @@ config MFD_ROHM_BD71828
          also a single-cell linear charger, a Coulomb counter, a real-time
          clock (RTC), GPIOs and a 32.768 kHz clock gate.
 
+config MFD_ROHM_BD957XMUF
+       tristate "ROHM BD9576MUF and BD9573MUF Power Management ICs"
+       depends on I2C=y
+       depends on OF
+       select REGMAP_I2C
+       select MFD_CORE
+       help
+         Select this option to get support for the ROHM BD9576MUF and
+         BD9573MUF Power Management ICs. BD9576 and BD9573 are primarily
+         designed to be used to power R-Car series processors.
+
 config MFD_STM32_LPTIMER
        tristate "Support for STM32 Low-Power Timer"
        depends on (ARCH_STM32 && OF) || COMPILE_TEST
index 20b7aeb..279b808 100644 (file)
@@ -262,6 +262,7 @@ obj-$(CONFIG_RAVE_SP_CORE)  += rave-sp.o
 obj-$(CONFIG_MFD_ROHM_BD70528) += rohm-bd70528.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_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-bd9576.c b/drivers/mfd/rohm-bd9576.c
new file mode 100644 (file)
index 0000000..6661a27
--- /dev/null
@@ -0,0 +1,189 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2021 ROHM Semiconductors
+ *
+ * ROHM BD9576MUF and BD9573MUF PMIC driver
+ */
+
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/ioport.h>
+#include <linux/irq.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/rohm-bd957x.h>
+#include <linux/mfd/rohm-generic.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+enum {
+       BD957X_REGULATOR_CELL,
+       BD957X_WDT_CELL,
+};
+
+/*
+ * Due to the BD9576MUF nasty IRQ behaiour we don't always populate IRQs.
+ * These will be added to regulator resources only if IRQ information for the
+ * PMIC is populated in device-tree.
+ */
+static const struct resource bd9576_regulator_irqs[] = {
+       DEFINE_RES_IRQ_NAMED(BD9576_INT_THERM, "bd9576-temp"),
+       DEFINE_RES_IRQ_NAMED(BD9576_INT_OVD, "bd9576-ovd"),
+       DEFINE_RES_IRQ_NAMED(BD9576_INT_UVD, "bd9576-uvd"),
+};
+
+static struct mfd_cell bd9573_mfd_cells[] = {
+       [BD957X_REGULATOR_CELL] = { .name = "bd9573-regulator", },
+       [BD957X_WDT_CELL]       = { .name = "bd9576-wdt", },
+};
+
+static struct mfd_cell bd9576_mfd_cells[] = {
+       [BD957X_REGULATOR_CELL] = { .name = "bd9576-regulator", },
+       [BD957X_WDT_CELL]       = { .name = "bd9576-wdt", },
+};
+
+static const struct regmap_range volatile_ranges[] = {
+       regmap_reg_range(BD957X_REG_SMRB_ASSERT, BD957X_REG_SMRB_ASSERT),
+       regmap_reg_range(BD957X_REG_PMIC_INTERNAL_STAT,
+                        BD957X_REG_PMIC_INTERNAL_STAT),
+       regmap_reg_range(BD957X_REG_INT_THERM_STAT, BD957X_REG_INT_THERM_STAT),
+       regmap_reg_range(BD957X_REG_INT_OVP_STAT, BD957X_REG_INT_SYS_STAT),
+       regmap_reg_range(BD957X_REG_INT_MAIN_STAT, BD957X_REG_INT_MAIN_STAT),
+};
+
+static const struct regmap_access_table volatile_regs = {
+       .yes_ranges = &volatile_ranges[0],
+       .n_yes_ranges = ARRAY_SIZE(volatile_ranges),
+};
+
+static struct regmap_config bd957x_regmap = {
+       .reg_bits = 8,
+       .val_bits = 8,
+       .volatile_table = &volatile_regs,
+       .max_register = BD957X_MAX_REGISTER,
+       .cache_type = REGCACHE_RBTREE,
+};
+
+static struct regmap_irq bd9576_irqs[] = {
+       REGMAP_IRQ_REG(BD9576_INT_THERM, 0, BD957X_MASK_INT_MAIN_THERM),
+       REGMAP_IRQ_REG(BD9576_INT_OVP, 0, BD957X_MASK_INT_MAIN_OVP),
+       REGMAP_IRQ_REG(BD9576_INT_SCP, 0, BD957X_MASK_INT_MAIN_SCP),
+       REGMAP_IRQ_REG(BD9576_INT_OCP, 0, BD957X_MASK_INT_MAIN_OCP),
+       REGMAP_IRQ_REG(BD9576_INT_OVD, 0, BD957X_MASK_INT_MAIN_OVD),
+       REGMAP_IRQ_REG(BD9576_INT_UVD, 0, BD957X_MASK_INT_MAIN_UVD),
+       REGMAP_IRQ_REG(BD9576_INT_UVP, 0, BD957X_MASK_INT_MAIN_UVP),
+       REGMAP_IRQ_REG(BD9576_INT_SYS, 0, BD957X_MASK_INT_MAIN_SYS),
+};
+
+static struct regmap_irq_chip bd9576_irq_chip = {
+       .name = "bd9576_irq",
+       .irqs = &bd9576_irqs[0],
+       .num_irqs = ARRAY_SIZE(bd9576_irqs),
+       .status_base = BD957X_REG_INT_MAIN_STAT,
+       .mask_base = BD957X_REG_INT_MAIN_MASK,
+       .ack_base = BD957X_REG_INT_MAIN_STAT,
+       .init_ack_masked = true,
+       .num_regs = 1,
+       .irq_reg_stride = 1,
+};
+
+static int bd957x_i2c_probe(struct i2c_client *i2c,
+                            const struct i2c_device_id *id)
+{
+       int ret;
+       struct regmap *regmap;
+       struct mfd_cell *cells;
+       int num_cells;
+       unsigned long chip_type;
+       struct irq_domain *domain;
+       bool usable_irqs;
+
+       chip_type = (unsigned long)of_device_get_match_data(&i2c->dev);
+
+       switch (chip_type) {
+       case ROHM_CHIP_TYPE_BD9576:
+               cells = bd9576_mfd_cells;
+               num_cells = ARRAY_SIZE(bd9576_mfd_cells);
+               usable_irqs = !!i2c->irq;
+               break;
+       case ROHM_CHIP_TYPE_BD9573:
+               cells = bd9573_mfd_cells;
+               num_cells = ARRAY_SIZE(bd9573_mfd_cells);
+               /*
+                * BD9573 only supports fatal IRQs which we can not handle
+                * because SoC is going to lose the power.
+                */
+               usable_irqs = false;
+               break;
+       default:
+               dev_err(&i2c->dev, "Unknown device type");
+               return -EINVAL;
+       }
+
+       regmap = devm_regmap_init_i2c(i2c, &bd957x_regmap);
+       if (IS_ERR(regmap)) {
+               dev_err(&i2c->dev, "Failed to initialize Regmap\n");
+               return PTR_ERR(regmap);
+       }
+
+       /*
+        * BD9576 behaves badly. It kepts IRQ line asserted for the whole
+        * duration of detected HW condition (like over temperature). So we
+        * don't require IRQ to be populated.
+        * If IRQ information is not given, then we mask all IRQs and do not
+        * provide IRQ resources to regulator driver - which then just omits
+        * the notifiers.
+        */
+       if (usable_irqs) {
+               struct regmap_irq_chip_data *irq_data;
+               struct mfd_cell *regulators;
+
+               regulators = &bd9576_mfd_cells[BD957X_REGULATOR_CELL];
+               regulators->resources = bd9576_regulator_irqs;
+               regulators->num_resources = ARRAY_SIZE(bd9576_regulator_irqs);
+
+               ret = devm_regmap_add_irq_chip(&i2c->dev, regmap, i2c->irq,
+                                              IRQF_ONESHOT, 0,
+                                              &bd9576_irq_chip, &irq_data);
+               if (ret) {
+                       dev_err(&i2c->dev, "Failed to add IRQ chip\n");
+                       return ret;
+               }
+               domain = regmap_irq_get_domain(irq_data);
+       } else {
+               ret = regmap_update_bits(regmap, BD957X_REG_INT_MAIN_MASK,
+                                        BD957X_MASK_INT_ALL,
+                                        BD957X_MASK_INT_ALL);
+               if (ret)
+                       return ret;
+               domain = NULL;
+       }
+
+       ret = devm_mfd_add_devices(&i2c->dev, PLATFORM_DEVID_AUTO, cells,
+                                  num_cells, NULL, 0, domain);
+       if (ret)
+               dev_err(&i2c->dev, "Failed to create subdevices\n");
+
+       return ret;
+}
+
+static const struct of_device_id bd957x_of_match[] = {
+       { .compatible = "rohm,bd9576", .data = (void *)ROHM_CHIP_TYPE_BD9576, },
+       { .compatible = "rohm,bd9573", .data = (void *)ROHM_CHIP_TYPE_BD9573, },
+       { },
+};
+MODULE_DEVICE_TABLE(of, bd957x_of_match);
+
+static struct i2c_driver bd957x_drv = {
+       .driver = {
+               .name = "rohm-bd957x",
+               .of_match_table = bd957x_of_match,
+       },
+       .probe = &bd957x_i2c_probe,
+};
+module_i2c_driver(bd957x_drv);
+
+MODULE_AUTHOR("Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>");
+MODULE_DESCRIPTION("ROHM BD9576MUF and BD9573MUF Power Management IC driver");
+MODULE_LICENSE("GPL");
index 1fe0042..355100d 100644 (file)
@@ -172,6 +172,19 @@ config BD70528_WATCHDOG
          Alternatively say M to compile the driver as a module,
          which will be called bd70528_wdt.
 
+config BD957XMUF_WATCHDOG
+       tristate "ROHM BD9576MUF and BD9573MUF PMIC Watchdog"
+       depends on MFD_ROHM_BD957XMUF
+       select WATCHDOG_CORE
+       help
+         Support for the watchdog in the ROHM BD9576 and BD9573 PMICs.
+         These PMIC ICs contain watchdog block which can be configured
+         to toggle reset line if SoC fails to ping watchdog via GPIO.
+
+         Say Y here to include support for the ROHM BD9576 or BD9573
+         watchdog. Alternatively say M to compile the driver as a module,
+         which will be called bd9576_wdt.
+
 config DA9052_WATCHDOG
        tristate "Dialog DA9052 Watchdog"
        depends on PMIC_DA9052 || COMPILE_TEST
index f3a6540..a7eade8 100644 (file)
@@ -204,6 +204,7 @@ obj-$(CONFIG_XEN_WDT) += xen_wdt.o
 
 # Architecture Independent
 obj-$(CONFIG_BD70528_WATCHDOG) += bd70528_wdt.o
+obj-$(CONFIG_BD957XMUF_WATCHDOG) += bd9576_wdt.o
 obj-$(CONFIG_DA9052_WATCHDOG) += da9052_wdt.o
 obj-$(CONFIG_DA9055_WATCHDOG) += da9055_wdt.o
 obj-$(CONFIG_DA9062_WATCHDOG) += da9062_wdt.o
diff --git a/drivers/watchdog/bd9576_wdt.c b/drivers/watchdog/bd9576_wdt.c
new file mode 100644 (file)
index 0000000..0b6999f
--- /dev/null
@@ -0,0 +1,291 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2020 ROHM Semiconductors
+ *
+ * ROHM BD9576MUF and BD9573MUF Watchdog driver
+ */
+
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/mfd/rohm-bd957x.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 HW_MARGIN_MIN 2
+#define HW_MARGIN_MAX 4416
+#define BD957X_WDT_DEFAULT_MARGIN 4416
+#define WATCHDOG_TIMEOUT 30
+
+struct bd9576_wdt_priv {
+       struct gpio_desc        *gpiod_ping;
+       struct gpio_desc        *gpiod_en;
+       struct device           *dev;
+       struct regmap           *regmap;
+       bool                    always_running;
+       struct watchdog_device  wdd;
+};
+
+static void bd9576_wdt_disable(struct bd9576_wdt_priv *priv)
+{
+       gpiod_set_value_cansleep(priv->gpiod_en, 0);
+}
+
+static int bd9576_wdt_ping(struct watchdog_device *wdd)
+{
+       struct bd9576_wdt_priv *priv = watchdog_get_drvdata(wdd);
+
+       /* Pulse */
+       gpiod_set_value_cansleep(priv->gpiod_ping, 1);
+       gpiod_set_value_cansleep(priv->gpiod_ping, 0);
+
+       return 0;
+}
+
+static int bd9576_wdt_start(struct watchdog_device *wdd)
+{
+       struct bd9576_wdt_priv *priv = watchdog_get_drvdata(wdd);
+
+       gpiod_set_value_cansleep(priv->gpiod_en, 1);
+
+       return bd9576_wdt_ping(wdd);
+}
+
+static int bd9576_wdt_stop(struct watchdog_device *wdd)
+{
+       struct bd9576_wdt_priv *priv = watchdog_get_drvdata(wdd);
+
+       if (!priv->always_running)
+               bd9576_wdt_disable(priv);
+       else
+               set_bit(WDOG_HW_RUNNING, &wdd->status);
+
+       return 0;
+}
+
+static const struct watchdog_info bd957x_wdt_ident = {
+       .options        = WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING |
+                         WDIOF_SETTIMEOUT,
+       .identity       = "BD957x Watchdog",
+};
+
+static const struct watchdog_ops bd957x_wdt_ops = {
+       .owner          = THIS_MODULE,
+       .start          = bd9576_wdt_start,
+       .stop           = bd9576_wdt_stop,
+       .ping           = bd9576_wdt_ping,
+};
+
+/* Unit is hundreds of uS */
+#define FASTNG_MIN 23
+
+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, 3, 7, 15};
+
+       for (sel = 0; sel < ARRAY_SIZE(multipliers) &&
+            multipliers[sel] * fast_val < target; sel++)
+               ;
+
+       if (sel == ARRAY_SIZE(multipliers))
+               return -EINVAL;
+
+       *slowsel = sel;
+
+       return 0;
+}
+
+static int find_closest_slow(int target, int *slow_sel, int *fast_sel)
+{
+       static const int multipliers[] = {2, 3, 7, 15};
+       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;
+
+       return 0;
+}
+
+#define BD957X_WDG_TYPE_WINDOW BIT(5)
+#define BD957X_WDG_TYPE_SLOW 0
+#define BD957X_WDG_TYPE_MASK BIT(5)
+#define BD957X_WDG_NG_RATIO_MASK 0x18
+#define BD957X_WDG_FASTNG_MASK 0x7
+
+static int bd957x_set_wdt_mode(struct bd9576_wdt_priv *priv, int hw_margin,
+                              int hw_margin_min)
+{
+       int ret, fastng, slowng, type, reg, mask;
+       struct device *dev = priv->dev;
+
+       /* convert to 100uS */
+       hw_margin *= 10;
+       hw_margin_min *= 10;
+       if (hw_margin_min) {
+               int min;
+
+               type = BD957X_WDG_TYPE_WINDOW;
+               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;
+               }
+
+       } else {
+               type = BD957X_WDG_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;
+               }
+       }
+
+       slowng <<= ffs(BD957X_WDG_NG_RATIO_MASK) - 1;
+       reg = type | slowng | fastng;
+       mask = BD957X_WDG_TYPE_MASK | BD957X_WDG_NG_RATIO_MASK |
+              BD957X_WDG_FASTNG_MASK;
+       ret = regmap_update_bits(priv->regmap, BD957X_REG_WDT_CONF,
+                                mask, reg);
+
+       return ret;
+}
+
+static int bd9576_wdt_probe(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct device_node *np = dev->parent->of_node;
+       struct bd9576_wdt_priv *priv;
+       u32 hw_margin[2];
+       u32 hw_margin_max = BD957X_WDT_DEFAULT_MARGIN, hw_margin_min = 0;
+       int ret;
+
+       priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+       if (!priv)
+               return -ENOMEM;
+
+       platform_set_drvdata(pdev, priv);
+
+       priv->dev = dev;
+       priv->regmap = dev_get_regmap(dev->parent, NULL);
+       if (!priv->regmap) {
+               dev_err(dev, "No regmap found\n");
+               return -ENODEV;
+       }
+
+       priv->gpiod_en = devm_gpiod_get_from_of_node(dev, dev->parent->of_node,
+                                                    "rohm,watchdog-enable-gpios",
+                                                    0, GPIOD_OUT_LOW,
+                                                    "watchdog-enable");
+       if (IS_ERR(priv->gpiod_en))
+               return dev_err_probe(dev, PTR_ERR(priv->gpiod_en),
+                             "getting watchdog-enable GPIO failed\n");
+
+       priv->gpiod_ping = devm_gpiod_get_from_of_node(dev, dev->parent->of_node,
+                                                    "rohm,watchdog-ping-gpios",
+                                                    0, GPIOD_OUT_LOW,
+                                                    "watchdog-ping");
+       if (IS_ERR(priv->gpiod_ping))
+               return dev_err_probe(dev, PTR_ERR(priv->gpiod_ping),
+                                    "getting watchdog-ping GPIO failed\n");
+
+       ret = of_property_read_variable_u32_array(np, "rohm,hw-timeout-ms",
+                                                 &hw_margin[0], 1, 2);
+       if (ret < 0 && ret != -EINVAL)
+               return ret;
+
+       if (ret == 1)
+               hw_margin_max = hw_margin[0];
+
+       if (ret == 2) {
+               hw_margin_max = hw_margin[1];
+               hw_margin_min = hw_margin[0];
+       }
+
+       ret = bd957x_set_wdt_mode(priv, hw_margin_max, hw_margin_min);
+       if (ret)
+               return ret;
+
+       priv->always_running = of_property_read_bool(np, "always-running");
+
+       watchdog_set_drvdata(&priv->wdd, priv);
+
+       priv->wdd.info                  = &bd957x_wdt_ident;
+       priv->wdd.ops                   = &bd957x_wdt_ops;
+       priv->wdd.min_hw_heartbeat_ms   = hw_margin_min;
+       priv->wdd.max_hw_heartbeat_ms   = hw_margin_max;
+       priv->wdd.parent                = dev;
+       priv->wdd.timeout               = WATCHDOG_TIMEOUT;
+
+       watchdog_init_timeout(&priv->wdd, 0, dev);
+       watchdog_set_nowayout(&priv->wdd, nowayout);
+
+       watchdog_stop_on_reboot(&priv->wdd);
+
+       if (priv->always_running)
+               bd9576_wdt_start(&priv->wdd);
+
+       return devm_watchdog_register_device(dev, &priv->wdd);
+}
+
+static struct platform_driver bd9576_wdt_driver = {
+       .driver = {
+               .name = "bd9576-wdt",
+       },
+       .probe  = bd9576_wdt_probe,
+};
+
+module_platform_driver(bd9576_wdt_driver);
+
+MODULE_AUTHOR("Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>");
+MODULE_DESCRIPTION("ROHM BD9576/BD9573 Watchdog driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:bd9576-wdt");
diff --git a/include/linux/mfd/rohm-bd957x.h b/include/linux/mfd/rohm-bd957x.h
new file mode 100644 (file)
index 0000000..acc920b
--- /dev/null
@@ -0,0 +1,140 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2021 ROHM Semiconductors */
+
+#ifndef __LINUX_MFD_BD957X_H__
+#define __LINUX_MFD_BD957X_H__
+
+enum {
+       BD957X_VD50,
+       BD957X_VD18,
+       BD957X_VDDDR,
+       BD957X_VD10,
+       BD957X_VOUTL1,
+       BD957X_VOUTS1,
+};
+
+/*
+ * The BD9576 has own IRQ 'blocks' for:
+ *  - I2C/thermal,
+ *  - Over voltage protection
+ *  - Short-circuit protection
+ *  - Over current protection
+ *  - Over voltage detection
+ *  - Under voltage detection
+ *  - Under voltage protection
+ *  - 'system interrupt'.
+ *
+ * Each of the blocks have a status register giving more accurate IRQ source
+ * information - for example which of the regulators have over-voltage.
+ *
+ * On top of this, there is "main IRQ" status register where each bit indicates
+ * which of sub-blocks have active IRQs. Fine. That would fit regmap-irq main
+ * status handling. Except that:
+ *  - Only some sub-IRQs can be masked.
+ *  - The IRQ informs us about fault-condition, not when fault state changes.
+ *    The IRQ line it is kept asserted until the detected condition is acked
+ *    AND cleared in HW. This is annoying for IRQs like the one informing high
+ *    temperature because if IRQ is not disabled it keeps the CPU in IRQ
+ *    handling loop.
+ *
+ * For now we do just use the main-IRQ register as source for our IRQ
+ * information and bind the regmap-irq to this. We leave fine-grained sub-IRQ
+ * register handling to handlers in sub-devices. The regulator driver shall
+ * read which regulators are source for problem - or if the detected error is
+ * regulator temperature error. The sub-drivers do also handle masking of "sub-
+ * IRQs" if this is supported/needed.
+ *
+ * To overcome the problem with HW keeping IRQ asserted we do call
+ * disable_irq_nosync() from sub-device handler and add a delayed work to
+ * re-enable IRQ roughly 1 second later. This should keep our CPU out of
+ * busy-loop.
+ */
+#define IRQS_SILENT_MS                 1000
+
+enum {
+       BD9576_INT_THERM,
+       BD9576_INT_OVP,
+       BD9576_INT_SCP,
+       BD9576_INT_OCP,
+       BD9576_INT_OVD,
+       BD9576_INT_UVD,
+       BD9576_INT_UVP,
+       BD9576_INT_SYS,
+};
+
+#define BD957X_REG_SMRB_ASSERT         0x15
+#define BD957X_REG_PMIC_INTERNAL_STAT  0x20
+#define BD957X_REG_INT_THERM_STAT      0x23
+#define BD957X_REG_INT_THERM_MASK      0x24
+#define BD957X_REG_INT_OVP_STAT                0x25
+#define BD957X_REG_INT_SCP_STAT                0x26
+#define BD957X_REG_INT_OCP_STAT                0x27
+#define BD957X_REG_INT_OVD_STAT                0x28
+#define BD957X_REG_INT_UVD_STAT                0x29
+#define BD957X_REG_INT_UVP_STAT                0x2a
+#define BD957X_REG_INT_SYS_STAT                0x2b
+#define BD957X_REG_INT_SYS_MASK                0x2c
+#define BD957X_REG_INT_MAIN_STAT       0x30
+#define BD957X_REG_INT_MAIN_MASK       0x31
+
+#define UVD_IRQ_VALID_MASK             0x6F
+#define OVD_IRQ_VALID_MASK             0x2F
+
+#define BD957X_MASK_INT_MAIN_THERM     BIT(0)
+#define BD957X_MASK_INT_MAIN_OVP       BIT(1)
+#define BD957X_MASK_INT_MAIN_SCP       BIT(2)
+#define BD957X_MASK_INT_MAIN_OCP       BIT(3)
+#define BD957X_MASK_INT_MAIN_OVD       BIT(4)
+#define BD957X_MASK_INT_MAIN_UVD       BIT(5)
+#define BD957X_MASK_INT_MAIN_UVP       BIT(6)
+#define BD957X_MASK_INT_MAIN_SYS       BIT(7)
+#define BD957X_MASK_INT_ALL            0xff
+
+#define BD957X_REG_WDT_CONF            0x16
+
+#define BD957X_REG_POW_TRIGGER1                0x41
+#define BD957X_REG_POW_TRIGGER2                0x42
+#define BD957X_REG_POW_TRIGGER3                0x43
+#define BD957X_REG_POW_TRIGGER4                0x44
+#define BD957X_REG_POW_TRIGGERL1       0x45
+#define BD957X_REG_POW_TRIGGERS1       0x46
+
+#define BD957X_REGULATOR_EN_MASK       0xff
+#define BD957X_REGULATOR_DIS_VAL       0xff
+
+#define BD957X_VSEL_REG_MASK           0xff
+
+#define BD957X_MASK_VOUT1_TUNE         0x87
+#define BD957X_MASK_VOUT2_TUNE         0x87
+#define BD957X_MASK_VOUT3_TUNE         0x1f
+#define BD957X_MASK_VOUT4_TUNE         0x1f
+#define BD957X_MASK_VOUTL1_TUNE                0x87
+
+#define BD957X_REG_VOUT1_TUNE          0x50
+#define BD957X_REG_VOUT2_TUNE          0x53
+#define BD957X_REG_VOUT3_TUNE          0x56
+#define BD957X_REG_VOUT4_TUNE          0x59
+#define BD957X_REG_VOUTL1_TUNE         0x5c
+
+#define BD9576_REG_VOUT1_OVD           0x51
+#define BD9576_REG_VOUT1_UVD           0x52
+#define BD9576_REG_VOUT2_OVD           0x54
+#define BD9576_REG_VOUT2_UVD           0x55
+#define BD9576_REG_VOUT3_OVD           0x57
+#define BD9576_REG_VOUT3_UVD           0x58
+#define BD9576_REG_VOUT4_OVD           0x5a
+#define BD9576_REG_VOUT4_UVD           0x5b
+#define BD9576_REG_VOUTL1_OVD          0x5d
+#define BD9576_REG_VOUTL1_UVD          0x5e
+
+#define BD9576_MASK_XVD                        0x7f
+
+#define BD9576_REG_VOUT1S_OCW          0x5f
+#define BD9576_REG_VOUT1S_OCP          0x60
+
+#define BD9576_MASK_VOUT1S_OCW         0x3f
+#define BD9576_MASK_VOUT1S_OCP         0x3f
+
+#define BD957X_MAX_REGISTER            0x61
+
+#endif
index a914428..35b392a 100644 (file)
@@ -9,7 +9,9 @@
 
 enum rohm_chip_type {
        ROHM_CHIP_TYPE_BD9571,
+       ROHM_CHIP_TYPE_BD9573,
        ROHM_CHIP_TYPE_BD9574,
+       ROHM_CHIP_TYPE_BD9576,
        ROHM_CHIP_TYPE_BD70528,
        ROHM_CHIP_TYPE_BD71815,
        ROHM_CHIP_TYPE_BD71828,