Message ID | 20211111225852.3128201-8-luca@lucaceresoli.net (mailing list archive) |
---|---|
State | Changes Requested |
Headers | show |
Series | Add MAX77714 PMIC minimal driver (RTC and watchdog only) | expand |
On 11/11/21 2:58 PM, Luca Ceresoli wrote: > Add a simple driver to support the watchdog embedded in the Maxim MAX77714 > PMIC. > > Signed-off-by: Luca Ceresoli <luca@lucaceresoli.net> > I just realized that this is effectively a rewrite of drivers/watchdog/max77620_wdt.c. The only difference I can see is is the register offsets (0x91 and 0x92 vs. 1 and 2) and some implementation details. Please add support for this watchdog to the other driver or provide a _really_ good reason why that is not possible. > --- > > Changes in v3: none > > Changes in v2: > - fix Kconfig help indentation (Randy Dunlap) > - make max77714_margin_value static const (Guenter Roeck) > - fix platform module instantiation > --- > MAINTAINERS | 1 + > drivers/watchdog/Kconfig | 9 ++ > drivers/watchdog/Makefile | 1 + > drivers/watchdog/max77714_wdt.c | 179 ++++++++++++++++++++++++++++++++ > 4 files changed, 190 insertions(+) > create mode 100644 drivers/watchdog/max77714_wdt.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index 1a37b9422c5f..d182231b4bbf 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -11573,6 +11573,7 @@ M: Luca Ceresoli <luca@lucaceresoli.net> > S: Maintained > F: Documentation/devicetree/bindings/mfd/maxim,max77714.yaml > F: drivers/mfd/max77714.c > +F: drivers/watchdog/max77714_wdt.c > F: include/linux/mfd/max77714.h > > MAXIM MAX77802 PMIC REGULATOR DEVICE DRIVER > diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig > index a6d97f30325a..f5100b731927 100644 > --- a/drivers/watchdog/Kconfig > +++ b/drivers/watchdog/Kconfig > @@ -685,6 +685,15 @@ config MAX77620_WATCHDOG > MAX77620 chips. To compile this driver as a module, > choose M here: the module will be called max77620_wdt. > > +config MAX77714_WATCHDOG > + tristate "Maxim MAX77714 Watchdog Timer" > + depends on MFD_MAX77714 || COMPILE_TEST > + help > + This is the driver for watchdog timer in the MAX77714 PMIC. > + Say 'Y' here to enable the watchdog timer support for MAX77714 > + chips. To compile this driver as a module, choose M here: the > + module will be called max77714_wdt. > + > config IMX2_WDT > tristate "IMX2+ Watchdog" > depends on ARCH_MXC || ARCH_LAYERSCAPE || COMPILE_TEST > diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile > index 2ee97064145b..575be33c52b8 100644 > --- a/drivers/watchdog/Makefile > +++ b/drivers/watchdog/Makefile > @@ -214,6 +214,7 @@ obj-$(CONFIG_WM831X_WATCHDOG) += wm831x_wdt.o > obj-$(CONFIG_WM8350_WATCHDOG) += wm8350_wdt.o > obj-$(CONFIG_MAX63XX_WATCHDOG) += max63xx_wdt.o > obj-$(CONFIG_MAX77620_WATCHDOG) += max77620_wdt.o > +obj-$(CONFIG_MAX77714_WATCHDOG) += max77714_wdt.o > obj-$(CONFIG_ZIIRAVE_WATCHDOG) += ziirave_wdt.o > obj-$(CONFIG_SOFT_WATCHDOG) += softdog.o > obj-$(CONFIG_MENF21BMC_WATCHDOG) += menf21bmc_wdt.o > diff --git a/drivers/watchdog/max77714_wdt.c b/drivers/watchdog/max77714_wdt.c > new file mode 100644 > index 000000000000..cce6c13d76eb > --- /dev/null > +++ b/drivers/watchdog/max77714_wdt.c > @@ -0,0 +1,179 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * Maxim MAX77714 Watchdog Driver > + * > + * Copyright (C) 2021 Luca Ceresoli > + * Author: Luca Ceresoli <luca@lucaceresoli.net> > + */ > + > +#include <linux/err.h> > +#include <linux/kernel.h> > +#include <linux/mfd/max77714.h> > +#include <linux/mod_devicetable.h> > +#include <linux/module.h> > +#include <linux/platform_device.h> > +#include <linux/regmap.h> > +#include <linux/watchdog.h> > + > +struct max77714_wdt { > + struct device *dev; > + struct regmap *rmap; > + struct watchdog_device wd_dev; > +}; > + > +/* Timeout in seconds, indexed by TWD bits of CNFG_GLBL2 register */ > +static const unsigned int max77714_margin_value[] = { 2, 16, 64, 128 }; > + > +static int max77714_wdt_start(struct watchdog_device *wd_dev) > +{ > + struct max77714_wdt *wdt = watchdog_get_drvdata(wd_dev); > + > + return regmap_update_bits(wdt->rmap, MAX77714_CNFG_GLBL2, > + MAX77714_WDTEN, MAX77714_WDTEN); > +} > + > +static int max77714_wdt_stop(struct watchdog_device *wd_dev) > +{ > + struct max77714_wdt *wdt = watchdog_get_drvdata(wd_dev); > + > + return regmap_update_bits(wdt->rmap, MAX77714_CNFG_GLBL2, > + MAX77714_WDTEN, 0); > +} > + > +static int max77714_wdt_ping(struct watchdog_device *wd_dev) > +{ > + struct max77714_wdt *wdt = watchdog_get_drvdata(wd_dev); > + > + return regmap_update_bits(wdt->rmap, MAX77714_CNFG_GLBL3, > + MAX77714_WDTC, 1); > +} > + > +static int max77714_wdt_set_timeout(struct watchdog_device *wd_dev, > + unsigned int timeout) > +{ > + struct max77714_wdt *wdt = watchdog_get_drvdata(wd_dev); > + unsigned int new_timeout, new_twd; > + int err; > + > + for (new_twd = 0; new_twd < ARRAY_SIZE(max77714_margin_value) - 1; new_twd++) > + if (timeout <= max77714_margin_value[new_twd]) > + break; > + > + /* new_wdt is not out of bounds here due to the "- 1" in the for loop */ > + new_timeout = max77714_margin_value[new_twd]; > + > + /* > + * "If the value of TWD needs to be changed, clear the system > + * watchdog timer first [...], then change the value of TWD." > + * (MAX77714 datasheet) > + */ > + err = regmap_update_bits(wdt->rmap, MAX77714_CNFG_GLBL3, > + MAX77714_WDTC, 1); > + if (err) > + return err; > + > + err = regmap_update_bits(wdt->rmap, MAX77714_CNFG_GLBL2, > + MAX77714_TWD_MASK, new_twd); > + if (err) > + return err; > + > + wd_dev->timeout = new_timeout; > + > + dev_dbg(wdt->dev, "New timeout = %u s (WDT = 0x%x)", new_timeout, new_twd); > + > + return 0; > +} > + > +static const struct watchdog_info max77714_wdt_info = { > + .identity = "max77714-watchdog", > + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, > +}; > + > +static const struct watchdog_ops max77714_wdt_ops = { > + .start = max77714_wdt_start, > + .stop = max77714_wdt_stop, > + .ping = max77714_wdt_ping, > + .set_timeout = max77714_wdt_set_timeout, > +}; > + > +static int max77714_wdt_probe(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + struct max77714_wdt *wdt; > + struct watchdog_device *wd_dev; > + unsigned int regval; > + int err; > + > + wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL); > + if (!wdt) > + return -ENOMEM; > + > + wdt->dev = dev; > + > + wd_dev = &wdt->wd_dev; > + wd_dev->info = &max77714_wdt_info; > + wd_dev->ops = &max77714_wdt_ops; > + wd_dev->min_timeout = 2; > + wd_dev->max_timeout = 128; > + > + platform_set_drvdata(pdev, wdt); > + watchdog_set_drvdata(wd_dev, wdt); > + > + wdt->rmap = dev_get_regmap(dev->parent, NULL); > + if (!wdt->rmap) > + return dev_err_probe(wdt->dev, -ENODEV, "Failed to get parent regmap\n"); > + > + /* WD_RST_WK: if 1 wdog restarts; if 0 wdog shuts down */ > + err = regmap_update_bits(wdt->rmap, MAX77714_CNFG2_ONOFF, > + MAX77714_WD_RST_WK, MAX77714_WD_RST_WK); > + if (err) > + return dev_err_probe(wdt->dev, err, "Error updating CNFG2_ONOFF\n"); > + > + err = regmap_read(wdt->rmap, MAX77714_CNFG_GLBL2, ®val); > + if (err) > + return dev_err_probe(wdt->dev, err, "Error reading CNFG_GLBL2\n"); > + > + /* enable watchdog | enable auto-clear in sleep state */ > + regval |= (MAX77714_WDTEN | MAX77714_WDTSLPC); > + > + err = regmap_write(wdt->rmap, MAX77714_CNFG_GLBL2, regval); > + if (err) > + return dev_err_probe(wdt->dev, err, "Error writing CNFG_GLBL2\n"); > + > + wd_dev->timeout = max77714_margin_value[regval & MAX77714_TWD_MASK]; > + > + dev_dbg(wdt->dev, "Timeout = %u s (WDT = 0x%x)", > + wd_dev->timeout, regval & MAX77714_TWD_MASK); > + > + set_bit(WDOG_HW_RUNNING, &wd_dev->status); > + > + watchdog_stop_on_unregister(wd_dev); > + > + err = devm_watchdog_register_device(dev, wd_dev); > + if (err) > + return dev_err_probe(dev, err, "Cannot register watchdog device\n"); > + > + dev_info(dev, "registered as /dev/watchdog%d\n", wd_dev->id); > + > + return 0; > +} > + > +static const struct platform_device_id max77714_wdt_platform_id[] = { > + { .name = "max77714-watchdog", }, > + { }, > +}; > +MODULE_DEVICE_TABLE(platform, max77714_wdt_platform_id); > + > +static struct platform_driver max77714_wdt_driver = { > + .driver = { > + .name = "max77714-watchdog", > + }, > + .probe = max77714_wdt_probe, > + .id_table = max77714_wdt_platform_id, > +}; > + > +module_platform_driver(max77714_wdt_driver); > + > +MODULE_DESCRIPTION("MAX77714 watchdog timer driver"); > +MODULE_AUTHOR("Luca Ceresoli <luca@lucaceresoli.net>"); > +MODULE_LICENSE("GPL v2"); >
Hi Guenter, On 12/11/21 15:57, Guenter Roeck wrote: > On 11/11/21 2:58 PM, Luca Ceresoli wrote: >> Add a simple driver to support the watchdog embedded in the Maxim >> MAX77714 >> PMIC. >> >> Signed-off-by: Luca Ceresoli <luca@lucaceresoli.net> >> > > I just realized that this is effectively a rewrite of > drivers/watchdog/max77620_wdt.c. > The only difference I can see is is the register offsets (0x91 and 0x92 > vs. 1 and 2) and some implementation details. Please add support for this > watchdog to the other driver or provide a _really_ good reason why that > is not possible. I initially started developing MAX77714 watchdog support as an addition to max77620_wdt.c as the procedures look identical at least for the basic features. But the register content seems completely different. Here are the notes I took at that time: -------------------------8<------------------------- MAX77620 has reg ONOFFCNFG1 at 0x41, ONOFFCNFG2 at 0x42. MAX77714 has reg CNFG1_ONOFF at 0x93, CNFG2_ONOFF at 0x94. OK, we can handle this with a register indirection table, indexed by chip model. MAX77620 has MAX77620_REG_FPS_CFG0 register. On MAX77714 I was unable to find any such register (I haven't looked at FPS in detail though). OK, we can handle this with some if()s or entirely disable PM on the 77714 until anybody cares. MAX77620 ONOFFCNFG1 has SFT_RST in bit 7. MAX77714 CNFG1_ONOFF has SFT_RST is bit 6. Uhm, should we have a _bit_ indirection table in addition to the _register_ indirection table? MAX77620 ONOFFCNFG2 bit 5 is SLP_LPM_MSK, involved in FPS. MAX77620 ONOFFCNFG2 bit 6 is WD_RTS_WK, configures the watchdog timer. MAX77714 CNFG2_ONOFF bit 5 is WD_RTS_WK, configures the watchdog timer. On MAX77714 I haven't found SLP_LPM_MSK. MAX77620 has 6 CID registers with "ES version" in CID5. MAX77714 has 5 CID registers with "DEVICE id" in CID3. CID registers would be useful to get the chip model directly from the chip, if only they had the same structure. Almost all of the registers I have been looking into have similar differences. -------------------------8<------------------------- When I started adding indirection tables the driver started growing bigger and uglier, and that little simple driver started being big and complex. So I opted to add a new driver. Let me know whether that sounds OK.
On 12/11/2021 17:02, Luca Ceresoli wrote: > Hi Guenter, > > On 12/11/21 15:57, Guenter Roeck wrote: >> On 11/11/21 2:58 PM, Luca Ceresoli wrote: >>> Add a simple driver to support the watchdog embedded in the Maxim >>> MAX77714 >>> PMIC. >>> >>> Signed-off-by: Luca Ceresoli <luca@lucaceresoli.net> >>> >> >> I just realized that this is effectively a rewrite of >> drivers/watchdog/max77620_wdt.c. >> The only difference I can see is is the register offsets (0x91 and 0x92 >> vs. 1 and 2) and some implementation details. Please add support for this >> watchdog to the other driver or provide a _really_ good reason why that >> is not possible. > > I initially started developing MAX77714 watchdog support as an addition > to max77620_wdt.c as the procedures look identical at least for the > basic features. > > But the register content seems completely different. Here are the notes > I took at that time: > > -------------------------8<------------------------- > > MAX77620 has reg ONOFFCNFG1 at 0x41, ONOFFCNFG2 at 0x42. > MAX77714 has reg CNFG1_ONOFF at 0x93, CNFG2_ONOFF at 0x94. > OK, we can handle this with a register indirection table, indexed by > chip model. > > MAX77620 has MAX77620_REG_FPS_CFG0 register. > On MAX77714 I was unable to find any such register (I haven't looked at > FPS in detail though). > OK, we can handle this with some if()s or entirely disable PM on the > 77714 until anybody cares. > > MAX77620 ONOFFCNFG1 has SFT_RST in bit 7. > MAX77714 CNFG1_ONOFF has SFT_RST is bit 6. > Uhm, should we have a _bit_ indirection table in addition to the > _register_ indirection table? > > MAX77620 ONOFFCNFG2 bit 5 is SLP_LPM_MSK, involved in FPS. > MAX77620 ONOFFCNFG2 bit 6 is WD_RTS_WK, configures the watchdog timer. > MAX77714 CNFG2_ONOFF bit 5 is WD_RTS_WK, configures the watchdog timer. > On MAX77714 I haven't found SLP_LPM_MSK. > > MAX77620 has 6 CID registers with "ES version" in CID5. > MAX77714 has 5 CID registers with "DEVICE id" in CID3. > CID registers would be useful to get the chip model directly from the > chip, if only they had the same structure. > > Almost all of the registers I have been looking into have similar > differences. > > -------------------------8<------------------------- > > When I started adding indirection tables the driver started growing > bigger and uglier, and that little simple driver started being big and > complex. So I opted to add a new driver. > The register offset differences are trivial and we do it in several drivers. Also in rtc-max77686 used by you here. Lack of features as well - just have a variant/driver data which defines certain features (true/false) or quirk bits (see s3c2410_wdt). The second driver - s3c2410_wdt - also customizes the bits. Therefore if the generic device operating configuration is similar (same generic control flow) and differences are in bits and offsets, then it should be one driver. Best regards, Krzysztof
On 11/12/21 8:07 AM, Krzysztof Kozlowski wrote: > On 12/11/2021 17:02, Luca Ceresoli wrote: >> Hi Guenter, >> >> On 12/11/21 15:57, Guenter Roeck wrote: >>> On 11/11/21 2:58 PM, Luca Ceresoli wrote: >>>> Add a simple driver to support the watchdog embedded in the Maxim >>>> MAX77714 >>>> PMIC. >>>> >>>> Signed-off-by: Luca Ceresoli <luca@lucaceresoli.net> >>>> >>> >>> I just realized that this is effectively a rewrite of >>> drivers/watchdog/max77620_wdt.c. >>> The only difference I can see is is the register offsets (0x91 and 0x92 >>> vs. 1 and 2) and some implementation details. Please add support for this >>> watchdog to the other driver or provide a _really_ good reason why that >>> is not possible. >> >> I initially started developing MAX77714 watchdog support as an addition >> to max77620_wdt.c as the procedures look identical at least for the >> basic features. >> >> But the register content seems completely different. Here are the notes >> I took at that time: >> >> -------------------------8<------------------------- >> >> MAX77620 has reg ONOFFCNFG1 at 0x41, ONOFFCNFG2 at 0x42. >> MAX77714 has reg CNFG1_ONOFF at 0x93, CNFG2_ONOFF at 0x94. >> OK, we can handle this with a register indirection table, indexed by >> chip model. >> >> MAX77620 has MAX77620_REG_FPS_CFG0 register. >> On MAX77714 I was unable to find any such register (I haven't looked at >> FPS in detail though). >> OK, we can handle this with some if()s or entirely disable PM on the >> 77714 until anybody cares. >> >> MAX77620 ONOFFCNFG1 has SFT_RST in bit 7. >> MAX77714 CNFG1_ONOFF has SFT_RST is bit 6. >> Uhm, should we have a _bit_ indirection table in addition to the >> _register_ indirection table? >> >> MAX77620 ONOFFCNFG2 bit 5 is SLP_LPM_MSK, involved in FPS. >> MAX77620 ONOFFCNFG2 bit 6 is WD_RTS_WK, configures the watchdog timer. >> MAX77714 CNFG2_ONOFF bit 5 is WD_RTS_WK, configures the watchdog timer. >> On MAX77714 I haven't found SLP_LPM_MSK. >> >> MAX77620 has 6 CID registers with "ES version" in CID5. >> MAX77714 has 5 CID registers with "DEVICE id" in CID3. >> CID registers would be useful to get the chip model directly from the >> chip, if only they had the same structure. >> >> Almost all of the registers I have been looking into have similar >> differences. >> >> -------------------------8<------------------------- >> >> When I started adding indirection tables the driver started growing >> bigger and uglier, and that little simple driver started being big and >> complex. So I opted to add a new driver. >> > > The register offset differences are trivial and we do it in several > drivers. Also in rtc-max77686 used by you here. > Lack of features as well - just have a variant/driver data which defines > certain features (true/false) or quirk bits (see s3c2410_wdt). > > The second driver - s3c2410_wdt - also customizes the bits. > > Therefore if the generic device operating configuration is similar (same > generic control flow) and differences are in bits and offsets, then it > should be one driver. > Exactly. Thanks, Guenter
Hi Guenter, Krzysztof, On 12/11/21 20:23, Guenter Roeck wrote: > On 11/12/21 8:07 AM, Krzysztof Kozlowski wrote: >> On 12/11/2021 17:02, Luca Ceresoli wrote: >>> Hi Guenter, >>> >>> On 12/11/21 15:57, Guenter Roeck wrote: >>>> On 11/11/21 2:58 PM, Luca Ceresoli wrote: >>>>> Add a simple driver to support the watchdog embedded in the Maxim >>>>> MAX77714 >>>>> PMIC. >>>>> >>>>> Signed-off-by: Luca Ceresoli <luca@lucaceresoli.net> >>>>> >>>> >>>> I just realized that this is effectively a rewrite of >>>> drivers/watchdog/max77620_wdt.c. >>>> The only difference I can see is is the register offsets (0x91 and 0x92 >>>> vs. 1 and 2) and some implementation details. Please add support for >>>> this >>>> watchdog to the other driver or provide a _really_ good reason why that >>>> is not possible. >>> >>> I initially started developing MAX77714 watchdog support as an addition >>> to max77620_wdt.c as the procedures look identical at least for the >>> basic features. >>> >>> But the register content seems completely different. Here are the notes >>> I took at that time: >>> >>> -------------------------8<------------------------- >>> >>> MAX77620 has reg ONOFFCNFG1 at 0x41, ONOFFCNFG2 at 0x42. >>> MAX77714 has reg CNFG1_ONOFF at 0x93, CNFG2_ONOFF at 0x94. >>> OK, we can handle this with a register indirection table, indexed by >>> chip model. >>> >>> MAX77620 has MAX77620_REG_FPS_CFG0 register. >>> On MAX77714 I was unable to find any such register (I haven't looked at >>> FPS in detail though). >>> OK, we can handle this with some if()s or entirely disable PM on the >>> 77714 until anybody cares. >>> >>> MAX77620 ONOFFCNFG1 has SFT_RST in bit 7. >>> MAX77714 CNFG1_ONOFF has SFT_RST is bit 6. >>> Uhm, should we have a _bit_ indirection table in addition to the >>> _register_ indirection table? >>> >>> MAX77620 ONOFFCNFG2 bit 5 is SLP_LPM_MSK, involved in FPS. >>> MAX77620 ONOFFCNFG2 bit 6 is WD_RTS_WK, configures the watchdog timer. >>> MAX77714 CNFG2_ONOFF bit 5 is WD_RTS_WK, configures the watchdog timer. >>> On MAX77714 I haven't found SLP_LPM_MSK. >>> >>> MAX77620 has 6 CID registers with "ES version" in CID5. >>> MAX77714 has 5 CID registers with "DEVICE id" in CID3. >>> CID registers would be useful to get the chip model directly from the >>> chip, if only they had the same structure. >>> >>> Almost all of the registers I have been looking into have similar >>> differences. >>> >>> -------------------------8<------------------------- >>> >>> When I started adding indirection tables the driver started growing >>> bigger and uglier, and that little simple driver started being big and >>> complex. So I opted to add a new driver. >>> >> >> The register offset differences are trivial and we do it in several >> drivers. Also in rtc-max77686 used by you here. >> Lack of features as well - just have a variant/driver data which defines >> certain features (true/false) or quirk bits (see s3c2410_wdt). >> >> The second driver - s3c2410_wdt - also customizes the bits. >> >> Therefore if the generic device operating configuration is similar (same >> generic control flow) and differences are in bits and offsets, then it >> should be one driver. >> > > Exactly. Ok, I'll do that and send v4. Now I realize I should have mentioned from the beginning the reasons for creating a new driver, so this discussion would have been cleared much earlier. Apologies for that. Patches 1-6 + 8 are not impacted and will need no change for this issue.
diff --git a/MAINTAINERS b/MAINTAINERS index 1a37b9422c5f..d182231b4bbf 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11573,6 +11573,7 @@ M: Luca Ceresoli <luca@lucaceresoli.net> S: Maintained F: Documentation/devicetree/bindings/mfd/maxim,max77714.yaml F: drivers/mfd/max77714.c +F: drivers/watchdog/max77714_wdt.c F: include/linux/mfd/max77714.h MAXIM MAX77802 PMIC REGULATOR DEVICE DRIVER diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index a6d97f30325a..f5100b731927 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -685,6 +685,15 @@ config MAX77620_WATCHDOG MAX77620 chips. To compile this driver as a module, choose M here: the module will be called max77620_wdt. +config MAX77714_WATCHDOG + tristate "Maxim MAX77714 Watchdog Timer" + depends on MFD_MAX77714 || COMPILE_TEST + help + This is the driver for watchdog timer in the MAX77714 PMIC. + Say 'Y' here to enable the watchdog timer support for MAX77714 + chips. To compile this driver as a module, choose M here: the + module will be called max77714_wdt. + config IMX2_WDT tristate "IMX2+ Watchdog" depends on ARCH_MXC || ARCH_LAYERSCAPE || COMPILE_TEST diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 2ee97064145b..575be33c52b8 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -214,6 +214,7 @@ obj-$(CONFIG_WM831X_WATCHDOG) += wm831x_wdt.o obj-$(CONFIG_WM8350_WATCHDOG) += wm8350_wdt.o obj-$(CONFIG_MAX63XX_WATCHDOG) += max63xx_wdt.o obj-$(CONFIG_MAX77620_WATCHDOG) += max77620_wdt.o +obj-$(CONFIG_MAX77714_WATCHDOG) += max77714_wdt.o obj-$(CONFIG_ZIIRAVE_WATCHDOG) += ziirave_wdt.o obj-$(CONFIG_SOFT_WATCHDOG) += softdog.o obj-$(CONFIG_MENF21BMC_WATCHDOG) += menf21bmc_wdt.o diff --git a/drivers/watchdog/max77714_wdt.c b/drivers/watchdog/max77714_wdt.c new file mode 100644 index 000000000000..cce6c13d76eb --- /dev/null +++ b/drivers/watchdog/max77714_wdt.c @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Maxim MAX77714 Watchdog Driver + * + * Copyright (C) 2021 Luca Ceresoli + * Author: Luca Ceresoli <luca@lucaceresoli.net> + */ + +#include <linux/err.h> +#include <linux/kernel.h> +#include <linux/mfd/max77714.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/watchdog.h> + +struct max77714_wdt { + struct device *dev; + struct regmap *rmap; + struct watchdog_device wd_dev; +}; + +/* Timeout in seconds, indexed by TWD bits of CNFG_GLBL2 register */ +static const unsigned int max77714_margin_value[] = { 2, 16, 64, 128 }; + +static int max77714_wdt_start(struct watchdog_device *wd_dev) +{ + struct max77714_wdt *wdt = watchdog_get_drvdata(wd_dev); + + return regmap_update_bits(wdt->rmap, MAX77714_CNFG_GLBL2, + MAX77714_WDTEN, MAX77714_WDTEN); +} + +static int max77714_wdt_stop(struct watchdog_device *wd_dev) +{ + struct max77714_wdt *wdt = watchdog_get_drvdata(wd_dev); + + return regmap_update_bits(wdt->rmap, MAX77714_CNFG_GLBL2, + MAX77714_WDTEN, 0); +} + +static int max77714_wdt_ping(struct watchdog_device *wd_dev) +{ + struct max77714_wdt *wdt = watchdog_get_drvdata(wd_dev); + + return regmap_update_bits(wdt->rmap, MAX77714_CNFG_GLBL3, + MAX77714_WDTC, 1); +} + +static int max77714_wdt_set_timeout(struct watchdog_device *wd_dev, + unsigned int timeout) +{ + struct max77714_wdt *wdt = watchdog_get_drvdata(wd_dev); + unsigned int new_timeout, new_twd; + int err; + + for (new_twd = 0; new_twd < ARRAY_SIZE(max77714_margin_value) - 1; new_twd++) + if (timeout <= max77714_margin_value[new_twd]) + break; + + /* new_wdt is not out of bounds here due to the "- 1" in the for loop */ + new_timeout = max77714_margin_value[new_twd]; + + /* + * "If the value of TWD needs to be changed, clear the system + * watchdog timer first [...], then change the value of TWD." + * (MAX77714 datasheet) + */ + err = regmap_update_bits(wdt->rmap, MAX77714_CNFG_GLBL3, + MAX77714_WDTC, 1); + if (err) + return err; + + err = regmap_update_bits(wdt->rmap, MAX77714_CNFG_GLBL2, + MAX77714_TWD_MASK, new_twd); + if (err) + return err; + + wd_dev->timeout = new_timeout; + + dev_dbg(wdt->dev, "New timeout = %u s (WDT = 0x%x)", new_timeout, new_twd); + + return 0; +} + +static const struct watchdog_info max77714_wdt_info = { + .identity = "max77714-watchdog", + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, +}; + +static const struct watchdog_ops max77714_wdt_ops = { + .start = max77714_wdt_start, + .stop = max77714_wdt_stop, + .ping = max77714_wdt_ping, + .set_timeout = max77714_wdt_set_timeout, +}; + +static int max77714_wdt_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct max77714_wdt *wdt; + struct watchdog_device *wd_dev; + unsigned int regval; + int err; + + wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL); + if (!wdt) + return -ENOMEM; + + wdt->dev = dev; + + wd_dev = &wdt->wd_dev; + wd_dev->info = &max77714_wdt_info; + wd_dev->ops = &max77714_wdt_ops; + wd_dev->min_timeout = 2; + wd_dev->max_timeout = 128; + + platform_set_drvdata(pdev, wdt); + watchdog_set_drvdata(wd_dev, wdt); + + wdt->rmap = dev_get_regmap(dev->parent, NULL); + if (!wdt->rmap) + return dev_err_probe(wdt->dev, -ENODEV, "Failed to get parent regmap\n"); + + /* WD_RST_WK: if 1 wdog restarts; if 0 wdog shuts down */ + err = regmap_update_bits(wdt->rmap, MAX77714_CNFG2_ONOFF, + MAX77714_WD_RST_WK, MAX77714_WD_RST_WK); + if (err) + return dev_err_probe(wdt->dev, err, "Error updating CNFG2_ONOFF\n"); + + err = regmap_read(wdt->rmap, MAX77714_CNFG_GLBL2, ®val); + if (err) + return dev_err_probe(wdt->dev, err, "Error reading CNFG_GLBL2\n"); + + /* enable watchdog | enable auto-clear in sleep state */ + regval |= (MAX77714_WDTEN | MAX77714_WDTSLPC); + + err = regmap_write(wdt->rmap, MAX77714_CNFG_GLBL2, regval); + if (err) + return dev_err_probe(wdt->dev, err, "Error writing CNFG_GLBL2\n"); + + wd_dev->timeout = max77714_margin_value[regval & MAX77714_TWD_MASK]; + + dev_dbg(wdt->dev, "Timeout = %u s (WDT = 0x%x)", + wd_dev->timeout, regval & MAX77714_TWD_MASK); + + set_bit(WDOG_HW_RUNNING, &wd_dev->status); + + watchdog_stop_on_unregister(wd_dev); + + err = devm_watchdog_register_device(dev, wd_dev); + if (err) + return dev_err_probe(dev, err, "Cannot register watchdog device\n"); + + dev_info(dev, "registered as /dev/watchdog%d\n", wd_dev->id); + + return 0; +} + +static const struct platform_device_id max77714_wdt_platform_id[] = { + { .name = "max77714-watchdog", }, + { }, +}; +MODULE_DEVICE_TABLE(platform, max77714_wdt_platform_id); + +static struct platform_driver max77714_wdt_driver = { + .driver = { + .name = "max77714-watchdog", + }, + .probe = max77714_wdt_probe, + .id_table = max77714_wdt_platform_id, +}; + +module_platform_driver(max77714_wdt_driver); + +MODULE_DESCRIPTION("MAX77714 watchdog timer driver"); +MODULE_AUTHOR("Luca Ceresoli <luca@lucaceresoli.net>"); +MODULE_LICENSE("GPL v2");
Add a simple driver to support the watchdog embedded in the Maxim MAX77714 PMIC. Signed-off-by: Luca Ceresoli <luca@lucaceresoli.net> --- Changes in v3: none Changes in v2: - fix Kconfig help indentation (Randy Dunlap) - make max77714_margin_value static const (Guenter Roeck) - fix platform module instantiation --- MAINTAINERS | 1 + drivers/watchdog/Kconfig | 9 ++ drivers/watchdog/Makefile | 1 + drivers/watchdog/max77714_wdt.c | 179 ++++++++++++++++++++++++++++++++ 4 files changed, 190 insertions(+) create mode 100644 drivers/watchdog/max77714_wdt.c