@@ -5,6 +5,10 @@
* Driver for NXP PCF85363 real-time clock.
*
* Copyright (C) 2017 Eric Nelson
+ *
+ * Copyright 2025 NXP
+ * Added support for timestamps, battery switch-over,
+ * watchdog, offset calibration.
*/
#include <linux/module.h>
#include <linux/i2c.h>
@@ -18,6 +22,7 @@
#include <linux/of.h>
#include <linux/rtc.h>
#include <linux/regmap.h>
+#include <linux/watchdog.h>
/*
* Date/Time registers
@@ -128,6 +133,17 @@
#define OFFSET_MAXIMUM 127
#define OFFSET_MASK 0xFF
+#define WD_TIMEOUT_SHIFT 2
+#define WD_CLKSEL_MASK GENMASK(1, 0)
+#define WD_CLKSEL_0_25HZ 0x00
+#define WD_CLKSEL_1HZ 0x01
+#define WD_CLKSEL_4HZ 0x02
+#define WD_CLKSEL_16HZ 0x03
+
+#define WD_DEFAULT_TIMEOUT 10
+#define WD_TIMEOUT_MIN 1
+#define WD_TIMEOUT_MAX 0x1F
+
struct pcf85363 {
struct rtc_device *rtc;
struct regmap *regmap;
@@ -139,6 +155,14 @@ struct pcf85x63_config {
unsigned int num_nvram;
};
+struct pcf85363_watchdog {
+ struct watchdog_device wdd;
+ struct regmap *regmap;
+ struct device *dev;
+ u8 timeout_val;
+ u8 clock_sel;
+};
+
static int pcf85363_load_capacitance(struct pcf85363 *pcf85363, struct device_node *node)
{
u32 load = 7000;
@@ -324,12 +348,13 @@ static irqreturn_t pcf85363_rtc_handle_irq(int irq, void *dev_id)
return IRQ_NONE;
if (flags) {
- dev_dbg(&pcf85363->rtc->dev, "IRQ flags: 0x%02x%s%s%s%s%s\n",
+ dev_dbg(&pcf85363->rtc->dev, "IRQ flags: 0x%02x%s%s%s%s%s%s\n",
flags, (flags & FLAGS_A1F) ? " [A1F]" : "",
(flags & FLAGS_TSR1F) ? " [TSR1F]" : "",
(flags & FLAGS_TSR2F) ? " [TSR2F]" : "",
(flags & FLAGS_TSR3F) ? " [TSR3F]" : "",
- (flags & FLAGS_BSF) ? " [BSF]" : "");
+ (flags & FLAGS_BSF) ? " [BSF]" : "",
+ (flags & FLAGS_WDF) ? " [WDF]" : "");
}
if (flags & FLAGS_A1F) {
@@ -361,6 +386,11 @@ static irqreturn_t pcf85363_rtc_handle_irq(int irq, void *dev_id)
handled = true;
}
+ if (flags & FLAGS_WDF) {
+ regmap_update_bits(pcf85363->regmap, CTRL_FLAGS, FLAGS_WDF, 0);
+ handled = true;
+ }
+
return handled ? IRQ_HANDLED : IRQ_NONE;
}
@@ -504,6 +534,124 @@ static const struct pcf85x63_config pcf_85363_config = {
.num_nvram = 2
};
+static void pcf85363_wdt_select_clock(struct pcf85363_watchdog *wd)
+{
+ unsigned int t = wd->wdd.timeout;
+
+ if (t <= 2)
+ wd->clock_sel = WD_CLKSEL_16HZ;
+ else if (t <= 8)
+ wd->clock_sel = WD_CLKSEL_4HZ;
+ else if (t <= 16)
+ wd->clock_sel = WD_CLKSEL_1HZ;
+ else
+ wd->clock_sel = WD_CLKSEL_0_25HZ;
+}
+
+/*
+ * This function sets the watchdog control register based on the timeout,
+ * clock selection and repeat mode settings. It prepares the value to
+ * write into the watchdog control register (CTRL_WDOG).
+ */
+static int pcf85363_wdt_reload(struct pcf85363_watchdog *wd)
+{
+ u8 val;
+
+ val = ((wd->timeout_val & WD_TIMEOUT_MAX) << WD_TIMEOUT_SHIFT) |
+ (wd->clock_sel & WD_CLKSEL_MASK);
+
+ return regmap_write(wd->regmap, CTRL_WDOG, val);
+}
+
+static int pcf85363_wdt_start(struct watchdog_device *wdd)
+{
+ struct pcf85363_watchdog *wd = watchdog_get_drvdata(wdd);
+
+ return pcf85363_wdt_reload(wd);
+}
+
+static int pcf85363_wdt_stop(struct watchdog_device *wdd)
+{
+ struct pcf85363_watchdog *wd = watchdog_get_drvdata(wdd);
+
+ return regmap_write(wd->regmap, CTRL_WDOG, 0);
+}
+
+static int pcf85363_wdt_ping(struct watchdog_device *wdd)
+{
+ struct pcf85363_watchdog *wd = watchdog_get_drvdata(wdd);
+
+ regmap_update_bits(wd->regmap, CTRL_FLAGS, FLAGS_WDF, 0);
+
+ return pcf85363_wdt_reload(wd);
+}
+
+static int pcf85363_wdt_set_timeout(struct watchdog_device *wdd,
+ unsigned int timeout)
+{
+ struct pcf85363_watchdog *wd = watchdog_get_drvdata(wdd);
+
+ wd->timeout_val = clamp(timeout, WD_TIMEOUT_MIN, WD_TIMEOUT_MAX);
+ wdd->timeout = wd->timeout_val;
+
+ pcf85363_wdt_select_clock(wd);
+
+ return pcf85363_wdt_reload(wd);
+}
+
+static const struct watchdog_info pcf85363_wdt_info = {
+ .identity = "PCF85363 Watchdog",
+ .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT,
+};
+
+static const struct watchdog_ops pcf85363_wdt_ops = {
+ .owner = THIS_MODULE,
+ .start = pcf85363_wdt_start,
+ .stop = pcf85363_wdt_stop,
+ .ping = pcf85363_wdt_ping,
+ .set_timeout = pcf85363_wdt_set_timeout,
+};
+
+static int pcf85363_watchdog_init(struct device *dev, struct regmap *regmap)
+{
+ struct pcf85363_watchdog *wd;
+ int ret;
+
+ if (!IS_ENABLED(CONFIG_WATCHDOG))
+ return 0;
+
+ wd = devm_kzalloc(dev, sizeof(*wd), GFP_KERNEL);
+ if (!wd)
+ return -ENOMEM;
+
+ wd->regmap = regmap;
+ wd->dev = dev;
+
+ wd->wdd.info = &pcf85363_wdt_info;
+ wd->wdd.ops = &pcf85363_wdt_ops;
+ wd->wdd.min_timeout = WD_TIMEOUT_MIN;
+ wd->wdd.max_timeout = WD_TIMEOUT_MAX;
+ wd->wdd.parent = dev;
+ wd->wdd.status = WATCHDOG_NOWAYOUT_INIT_STATUS;
+
+ ret = watchdog_init_timeout(&wd->wdd, 0, dev);
+ if (ret)
+ wd->wdd.timeout = WD_DEFAULT_TIMEOUT;
+
+ wd->timeout_val = wd->wdd.timeout;
+ pcf85363_wdt_select_clock(wd);
+
+ ret = regmap_update_bits(regmap, CTRL_FLAGS, FLAGS_WDF, 0);
+ if (ret) {
+ dev_err(dev, "failed to clear WDF:%d\n", ret);
+ return ret;
+ }
+
+ watchdog_set_drvdata(&wd->wdd, wd);
+
+ return devm_watchdog_register_device(dev, &wd->wdd);
+}
+
/*
* Reads 6 bytes of timestamp data starting at the given base register,
* converts them from BCD to binary, and formats the result into a
@@ -685,6 +833,10 @@ static int pcf85363_probe(struct i2c_client *client)
PIN_IO_TSPM | PIN_IO_TSIM,
PIN_IO_TSPM | PIN_IO_TSIM);
+ ret = pcf85363_watchdog_init(dev, pcf85363->regmap);
+ if (ret)
+ dev_err_probe(dev, ret, "Watchdog init failed\n");
+
if (irq_a > 0 || wakeup_source)
device_init_wakeup(dev, true);
Add watchdog timer support to PCF85263/PCF85363 using the linux watchdog subsystem. The driver programs the hardware watchdog timeout based on the requested period. Signed-off-by: Lakshay Piplani <lakshay.piplani@nxp.com> --- V3 -> V4: - Use watchdog_init_timeout(&wd->wdd, 0, dev) to allow devicetree or module parameter overrides; fallback to WD_DEFAULT_TIMEOUT if not provided. - Centralized clock selection logic in pcf85363_wdt_select_clock() and applied dynamically whenever timeout changes. - Removed unused repeat variable and simplified timeout handling for clarity. V2 -> V3: - Split into separate patches as suggested: - Battery switch-over detection. - Timestamp recording for TS pin and battery switch-over events. - Offset calibration. - Watchdog timer (to be reviewed by watchdog maintainers). - Dropped Alarm2 support - Switched to rtc_add_group() for sysfs attributes V1 -> V2: - Watchdog related changes due to removal of vendor specific properties from device tree * remove vendor DT knobs (enable/timeout/stepsize/repeat) * use watchdog_init_timeout (with 10s default) * derive clock_sel from final timeout * default, repeat=true (repeat mode) - Fixed uninitalised warning on 'ret' (reported by kernel test robot) - Use dev_dbg instead of dev_info for debug related print messages - Minor cleanup and comments. drivers/rtc/rtc-pcf85363.c | 156 ++++++++++++++++++++++++++++++++++++- 1 file changed, 154 insertions(+), 2 deletions(-)