Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0e735eaa authored by Bruno Thomsen's avatar Bruno Thomsen Committed by Alexandre Belloni
Browse files

rtc: pcf2127: add watchdog feature support



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: default avatarBruno Thomsen <bruno.thomsen@gmail.com>
Acked-by: default avatarGuenter Roeck <linux@roeck-us.net>
Link: https://lore.kernel.org/r/20190822131936.18772-4-bruno.thomsen@gmail.com


Signed-off-by: default avatarAlexandre Belloni <alexandre.belloni@bootlin.com>
parent 7f43020e
Loading
Loading
Loading
Loading
+6 −1
Original line number Diff line number Diff line
@@ -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.
+118 −0
Original line number Diff line number Diff line
@@ -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
@@ -35,6 +39,13 @@
#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
@@ -45,9 +56,15 @@
#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);
}