]> asedeno.scripts.mit.edu Git - linux.git/blobdiff - drivers/rtc/rtc-pcf2127.c
Merge tag 'xfs-5.4-fixes-4' of git://git.kernel.org/pub/scm/fs/xfs/xfs-linux
[linux.git] / drivers / rtc / rtc-pcf2127.c
index ee4921e4a47cb108c17998a33aa30a3ab174297c..02b069caffd57c88ab0b022481d0b8c1704de19c 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
 #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_BIT_CTRL1_TSF1                 BIT(4)
 /* Control register 2 */
 #define PCF2127_REG_CTRL2              0x01
+#define PCF2127_BIT_CTRL2_TSIE                 BIT(2)
+#define PCF2127_BIT_CTRL2_TSF2                 BIT(5)
 /* Control register 3 */
 #define PCF2127_REG_CTRL3              0x02
+#define PCF2127_BIT_CTRL3_BLIE                 BIT(0)
+#define PCF2127_BIT_CTRL3_BIE                  BIT(1)
 #define PCF2127_BIT_CTRL3_BLF                  BIT(2)
+#define PCF2127_BIT_CTRL3_BF                   BIT(3)
+#define PCF2127_BIT_CTRL3_BTSE                 BIT(4)
 /* Time and date registers */
 #define PCF2127_REG_SC                 0x03
 #define PCF2127_BIT_SC_OSF                     BIT(7)
 #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
+/* Tamper timestamp registers */
+#define PCF2127_REG_TS_CTRL            0x12
+#define PCF2127_BIT_TS_CTRL_TSOFF              BIT(6)
+#define PCF2127_BIT_TS_CTRL_TSM                        BIT(7)
+#define PCF2127_REG_TS_SC              0x13
+#define PCF2127_REG_TS_MN              0x14
+#define PCF2127_REG_TS_HR              0x15
+#define PCF2127_REG_TS_DM              0x16
+#define PCF2127_REG_TS_MO              0x17
+#define PCF2127_REG_TS_YR              0x18
 /*
  * 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 +254,165 @@ 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,
+};
+
+/* sysfs interface */
+
+static ssize_t timestamp0_store(struct device *dev,
+                               struct device_attribute *attr,
+                               const char *buf, size_t count)
+{
+       struct pcf2127 *pcf2127 = dev_get_drvdata(dev->parent);
+       int ret;
+
+       ret = regmap_update_bits(pcf2127->regmap, PCF2127_REG_CTRL1,
+                                PCF2127_BIT_CTRL1_TSF1, 0);
+       if (ret) {
+               dev_err(dev, "%s: update ctrl1 ret=%d\n", __func__, ret);
+               return ret;
+       }
+
+       ret = regmap_update_bits(pcf2127->regmap, PCF2127_REG_CTRL2,
+                                PCF2127_BIT_CTRL2_TSF2, 0);
+       if (ret) {
+               dev_err(dev, "%s: update ctrl2 ret=%d\n", __func__, ret);
+               return ret;
+       }
+
+       ret = pcf2127_wdt_active_ping(&pcf2127->wdd);
+       if (ret)
+               return ret;
+
+       return count;
+};
+
+static ssize_t timestamp0_show(struct device *dev,
+                              struct device_attribute *attr, char *buf)
+{
+       struct pcf2127 *pcf2127 = dev_get_drvdata(dev->parent);
+       struct rtc_time tm;
+       int ret;
+       unsigned char data[25];
+
+       ret = regmap_bulk_read(pcf2127->regmap, PCF2127_REG_CTRL1, data,
+                              sizeof(data));
+       if (ret) {
+               dev_err(dev, "%s: read error ret=%d\n", __func__, ret);
+               return ret;
+       }
+
+       dev_dbg(dev,
+               "%s: raw data is cr1=%02x, cr2=%02x, cr3=%02x, ts_sc=%02x, "
+               "ts_mn=%02x, ts_hr=%02x, ts_dm=%02x, ts_mo=%02x, ts_yr=%02x\n",
+               __func__, data[PCF2127_REG_CTRL1], data[PCF2127_REG_CTRL2],
+               data[PCF2127_REG_CTRL3], data[PCF2127_REG_TS_SC],
+               data[PCF2127_REG_TS_MN], data[PCF2127_REG_TS_HR],
+               data[PCF2127_REG_TS_DM], data[PCF2127_REG_TS_MO],
+               data[PCF2127_REG_TS_YR]);
+
+       ret = pcf2127_wdt_active_ping(&pcf2127->wdd);
+       if (ret)
+               return ret;
+
+       if (!(data[PCF2127_REG_CTRL1] & PCF2127_BIT_CTRL1_TSF1) &&
+           !(data[PCF2127_REG_CTRL2] & PCF2127_BIT_CTRL2_TSF2))
+               return 0;
+
+       tm.tm_sec = bcd2bin(data[PCF2127_REG_TS_SC] & 0x7F);
+       tm.tm_min = bcd2bin(data[PCF2127_REG_TS_MN] & 0x7F);
+       tm.tm_hour = bcd2bin(data[PCF2127_REG_TS_HR] & 0x3F);
+       tm.tm_mday = bcd2bin(data[PCF2127_REG_TS_DM] & 0x3F);
+       /* TS_MO register (month) value range: 1-12 */
+       tm.tm_mon = bcd2bin(data[PCF2127_REG_TS_MO] & 0x1F) - 1;
+       tm.tm_year = bcd2bin(data[PCF2127_REG_TS_YR]);
+       if (tm.tm_year < 70)
+               tm.tm_year += 100; /* assume we are in 1970...2069 */
+
+       ret = rtc_valid_tm(&tm);
+       if (ret)
+               return ret;
+
+       return sprintf(buf, "%llu\n",
+                      (unsigned long long)rtc_tm_to_time64(&tm));
+};
+
+static DEVICE_ATTR_RW(timestamp0);
+
+static struct attribute *pcf2127_attrs[] = {
+       &dev_attr_timestamp0.attr,
+       NULL
+};
+
+static const struct attribute_group pcf2127_attr_group = {
+       .attrs  = pcf2127_attrs,
+};
+
 static int pcf2127_probe(struct device *dev, struct regmap *regmap,
                        const char *name, bool has_nvmem)
 {
@@ -242,6 +435,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 +456,83 @@ 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;
+       }
+
+#ifdef CONFIG_WATCHDOG
+       ret = devm_watchdog_register_device(dev, &pcf2127->wdd);
+       if (ret)
+               return ret;
+#endif /* CONFIG_WATCHDOG */
+
+       /*
+        * Disable battery low/switch-over timestamp and interrupts.
+        * Clear battery interrupt flags which can block new trigger events.
+        * Note: This is the default chip behaviour but added to ensure
+        * correct tamper timestamp and interrupt function.
+        */
+       ret = regmap_update_bits(pcf2127->regmap, PCF2127_REG_CTRL3,
+                                PCF2127_BIT_CTRL3_BTSE |
+                                PCF2127_BIT_CTRL3_BF |
+                                PCF2127_BIT_CTRL3_BIE |
+                                PCF2127_BIT_CTRL3_BLIE, 0);
+       if (ret) {
+               dev_err(dev, "%s: interrupt config (ctrl3) failed\n",
+                       __func__);
+               return ret;
+       }
+
+       /*
+        * Enable timestamp function and store timestamp of first trigger
+        * event until TSF1 and TFS2 interrupt flags are cleared.
+        */
+       ret = regmap_update_bits(pcf2127->regmap, PCF2127_REG_TS_CTRL,
+                                PCF2127_BIT_TS_CTRL_TSOFF |
+                                PCF2127_BIT_TS_CTRL_TSM,
+                                PCF2127_BIT_TS_CTRL_TSM);
+       if (ret) {
+               dev_err(dev, "%s: tamper detection config (ts_ctrl) failed\n",
+                       __func__);
+               return ret;
+       }
+
+       /*
+        * Enable interrupt generation when TSF1 or TSF2 timestamp flags
+        * are set. Interrupt signal is an open-drain output and can be
+        * left floating if unused.
+        */
+       ret = regmap_update_bits(pcf2127->regmap, PCF2127_REG_CTRL2,
+                                PCF2127_BIT_CTRL2_TSIE,
+                                PCF2127_BIT_CTRL2_TSIE);
+       if (ret) {
+               dev_err(dev, "%s: tamper detection config (ctrl2) failed\n",
+                       __func__);
+               return ret;
+       }
+
+       ret = rtc_add_group(pcf2127->rtc, &pcf2127_attr_group);
+       if (ret) {
+               dev_err(dev, "%s: tamper sysfs registering failed\n",
+                       __func__);
+               return ret;
+       }
+
        return rtc_register_device(pcf2127->rtc);
 }