]> asedeno.scripts.mit.edu Git - linux.git/commitdiff
rtc: pcf2127: add watchdog feature support
authorBruno Thomsen <bruno.thomsen@gmail.com>
Thu, 22 Aug 2019 13:19:35 +0000 (15:19 +0200)
committerAlexandre Belloni <alexandre.belloni@bootlin.com>
Tue, 27 Aug 2019 16:24:15 +0000 (18:24 +0200)
Add partial support for the watchdog functionality of
both PCF2127 and PCF2129 chips.

The programmable watchdog timer is currently using a fixed
clock source of 1Hz. This result in a selectable range of
1-255 seconds, which covers most embedded Linux use-cases.

Clock sources of 4096Hz, 64Hz and 1/60Hz is mostly useful
in MCU use-cases.

Countdown timer not available when using watchdog feature.

Signed-off-by: Bruno Thomsen <bruno.thomsen@gmail.com>
Acked-by: Guenter Roeck <linux@roeck-us.net>
Link: https://lore.kernel.org/r/20190822131936.18772-4-bruno.thomsen@gmail.com
Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
drivers/rtc/Kconfig
drivers/rtc/rtc-pcf2127.c

index 63bd72a96210a6ead351f5dfd62e4bb51bd71c80..758f7a4174700072420e522961097b6af5c5518e 100644 (file)
@@ -888,7 +888,12 @@ config RTC_DRV_PCF2127
        depends on RTC_I2C_AND_SPI
        help
          If you say yes here you get support for the NXP PCF2127/29 RTC
-         chips.
+         chips with integrated quartz crystal for industrial applications.
+         Both chips also have watchdog timer and tamper switch detection
+         features.
+
+         PCF2127 has an additional feature of 512 bytes battery backed
+         memory that's accessible using nvmem interface.
 
          This driver can also be built as a module. If so, the module
          will be called rtc-pcf2127.
index ee4921e4a47cb108c17998a33aa30a3ab174297c..8d6eda455d813e140ea71d062a851d0a5df3b290 100644 (file)
@@ -5,6 +5,9 @@
  *
  * Author: Renaud Cerrato <r.cerrato@til-technologies.fr>
  *
+ * Watchdog and tamper functions
+ * Author: Bruno Thomsen <bruno.thomsen@gmail.com>
+ *
  * based on the other drivers in this same directory.
  *
  * Datasheet: http://cache.nxp.com/documents/data_sheet/PCF2127.pdf
@@ -18,6 +21,7 @@
 #include <linux/module.h>
 #include <linux/of.h>
 #include <linux/regmap.h>
+#include <linux/watchdog.h>
 
 /* Control register 1 */
 #define PCF2127_REG_CTRL1              0x00
 #define PCF2127_REG_DW                 0x07
 #define PCF2127_REG_MO                 0x08
 #define PCF2127_REG_YR                 0x09
+/* Watchdog registers */
+#define PCF2127_REG_WD_CTL             0x10
+#define PCF2127_BIT_WD_CTL_TF0                 BIT(0)
+#define PCF2127_BIT_WD_CTL_TF1                 BIT(1)
+#define PCF2127_BIT_WD_CTL_CD0                 BIT(6)
+#define PCF2127_BIT_WD_CTL_CD1                 BIT(7)
+#define PCF2127_REG_WD_VAL             0x11
 /*
  * RAM registers
  * PCF2127 has 512 bytes general-purpose static RAM (SRAM) that is
 #define PCF2127_REG_RAM_WRT_CMD                0x1C
 #define PCF2127_REG_RAM_RD_CMD         0x1D
 
+/* Watchdog timer value constants */
+#define PCF2127_WD_VAL_STOP            0
+#define PCF2127_WD_VAL_MIN             2
+#define PCF2127_WD_VAL_MAX             255
+#define PCF2127_WD_VAL_DEFAULT         60
 
 struct pcf2127 {
        struct rtc_device *rtc;
+       struct watchdog_device wdd;
        struct regmap *regmap;
 };
 
@@ -220,6 +237,74 @@ static int pcf2127_nvmem_write(void *priv, unsigned int offset,
        return ret ?: bytes;
 }
 
+/* watchdog driver */
+
+static int pcf2127_wdt_ping(struct watchdog_device *wdd)
+{
+       struct pcf2127 *pcf2127 = watchdog_get_drvdata(wdd);
+
+       return regmap_write(pcf2127->regmap, PCF2127_REG_WD_VAL, wdd->timeout);
+}
+
+/*
+ * Restart watchdog timer if feature is active.
+ *
+ * Note: Reading CTRL2 register causes watchdog to stop which is unfortunate,
+ * since register also contain control/status flags for other features.
+ * Always call this function after reading CTRL2 register.
+ */
+static int pcf2127_wdt_active_ping(struct watchdog_device *wdd)
+{
+       int ret = 0;
+
+       if (watchdog_active(wdd)) {
+               ret = pcf2127_wdt_ping(wdd);
+               if (ret)
+                       dev_err(wdd->parent,
+                               "%s: watchdog restart failed, ret=%d\n",
+                               __func__, ret);
+       }
+
+       return ret;
+}
+
+static int pcf2127_wdt_start(struct watchdog_device *wdd)
+{
+       return pcf2127_wdt_ping(wdd);
+}
+
+static int pcf2127_wdt_stop(struct watchdog_device *wdd)
+{
+       struct pcf2127 *pcf2127 = watchdog_get_drvdata(wdd);
+
+       return regmap_write(pcf2127->regmap, PCF2127_REG_WD_VAL,
+                           PCF2127_WD_VAL_STOP);
+}
+
+static int pcf2127_wdt_set_timeout(struct watchdog_device *wdd,
+                                  unsigned int new_timeout)
+{
+       dev_dbg(wdd->parent, "new watchdog timeout: %is (old: %is)\n",
+               new_timeout, wdd->timeout);
+
+       wdd->timeout = new_timeout;
+
+       return pcf2127_wdt_active_ping(wdd);
+}
+
+static const struct watchdog_info pcf2127_wdt_info = {
+       .identity = "NXP PCF2127/PCF2129 Watchdog",
+       .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT,
+};
+
+static const struct watchdog_ops pcf2127_watchdog_ops = {
+       .owner = THIS_MODULE,
+       .start = pcf2127_wdt_start,
+       .stop = pcf2127_wdt_stop,
+       .ping = pcf2127_wdt_ping,
+       .set_timeout = pcf2127_wdt_set_timeout,
+};
+
 static int pcf2127_probe(struct device *dev, struct regmap *regmap,
                        const char *name, bool has_nvmem)
 {
@@ -242,6 +327,16 @@ static int pcf2127_probe(struct device *dev, struct regmap *regmap,
 
        pcf2127->rtc->ops = &pcf2127_rtc_ops;
 
+       pcf2127->wdd.parent = dev;
+       pcf2127->wdd.info = &pcf2127_wdt_info;
+       pcf2127->wdd.ops = &pcf2127_watchdog_ops;
+       pcf2127->wdd.min_timeout = PCF2127_WD_VAL_MIN;
+       pcf2127->wdd.max_timeout = PCF2127_WD_VAL_MAX;
+       pcf2127->wdd.timeout = PCF2127_WD_VAL_DEFAULT;
+       pcf2127->wdd.min_hw_heartbeat_ms = 500;
+
+       watchdog_set_drvdata(&pcf2127->wdd, pcf2127);
+
        if (has_nvmem) {
                struct nvmem_config nvmem_cfg = {
                        .priv = pcf2127,
@@ -253,6 +348,29 @@ static int pcf2127_probe(struct device *dev, struct regmap *regmap,
                ret = rtc_nvmem_register(pcf2127->rtc, &nvmem_cfg);
        }
 
+       /*
+        * Watchdog timer enabled and reset pin /RST activated when timed out.
+        * Select 1Hz clock source for watchdog timer.
+        * Timer is not started until WD_VAL is loaded with a valid value.
+        * Note: Countdown timer disabled and not available.
+        */
+       ret = regmap_update_bits(pcf2127->regmap, PCF2127_REG_WD_CTL,
+                                PCF2127_BIT_WD_CTL_CD1 |
+                                PCF2127_BIT_WD_CTL_CD0 |
+                                PCF2127_BIT_WD_CTL_TF1 |
+                                PCF2127_BIT_WD_CTL_TF0,
+                                PCF2127_BIT_WD_CTL_CD1 |
+                                PCF2127_BIT_WD_CTL_CD0 |
+                                PCF2127_BIT_WD_CTL_TF1);
+       if (ret) {
+               dev_err(dev, "%s: watchdog config (wd_ctl) failed\n", __func__);
+               return ret;
+       }
+
+       ret = devm_watchdog_register_device(dev, &pcf2127->wdd);
+       if (ret)
+               return ret;
+
        return rtc_register_device(pcf2127->rtc);
 }