Skip to content

Commit

Permalink
rtc: pcf2127: add watchdog feature support
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Bruno Thomsen authored and Alexandre Belloni committed Aug 27, 2019
1 parent 7f43020 commit 0e735ea
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 1 deletion.
7 changes: 6 additions & 1 deletion drivers/rtc/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
118 changes: 118 additions & 0 deletions drivers/rtc/rtc-pcf2127.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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;
};

Expand Down Expand Up @@ -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)
{
Expand All @@ -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,
Expand All @@ -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);
}

Expand Down

0 comments on commit 0e735ea

Please sign in to comment.