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: 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>
This commit is contained in:
Bruno Thomsen 2019-08-22 15:19:35 +02:00 committed by Alexandre Belloni
parent 7f43020e3b
commit 0e735eaae1
2 changed files with 124 additions and 1 deletions

View 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.

View 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
@ -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);
}