diff options
author | Bruno Thomsen <bruno.thomsen@gmail.com> | 2019-08-22 09:19:35 -0400 |
---|---|---|
committer | Alexandre Belloni <alexandre.belloni@bootlin.com> | 2019-08-27 12:24:15 -0400 |
commit | 0e735eaae1650a2e26835cc776f496e06a87be9f (patch) | |
tree | d077e7192c7f1cbbdaafe3616875f31a2b273293 | |
parent | 7f43020e3bdb63d65661ed377682702f8b34d3ea (diff) |
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>
-rw-r--r-- | drivers/rtc/Kconfig | 7 | ||||
-rw-r--r-- | drivers/rtc/rtc-pcf2127.c | 118 |
2 files changed, 124 insertions, 1 deletions
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 63bd72a96210..758f7a417470 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig | |||
@@ -888,7 +888,12 @@ config RTC_DRV_PCF2127 | |||
888 | depends on RTC_I2C_AND_SPI | 888 | depends on RTC_I2C_AND_SPI |
889 | help | 889 | help |
890 | If you say yes here you get support for the NXP PCF2127/29 RTC | 890 | If you say yes here you get support for the NXP PCF2127/29 RTC |
891 | chips. | 891 | chips with integrated quartz crystal for industrial applications. |
892 | Both chips also have watchdog timer and tamper switch detection | ||
893 | features. | ||
894 | |||
895 | PCF2127 has an additional feature of 512 bytes battery backed | ||
896 | memory that's accessible using nvmem interface. | ||
892 | 897 | ||
893 | This driver can also be built as a module. If so, the module | 898 | This driver can also be built as a module. If so, the module |
894 | will be called rtc-pcf2127. | 899 | will be called rtc-pcf2127. |
diff --git a/drivers/rtc/rtc-pcf2127.c b/drivers/rtc/rtc-pcf2127.c index ee4921e4a47c..8d6eda455d81 100644 --- a/drivers/rtc/rtc-pcf2127.c +++ b/drivers/rtc/rtc-pcf2127.c | |||
@@ -5,6 +5,9 @@ | |||
5 | * | 5 | * |
6 | * Author: Renaud Cerrato <r.cerrato@til-technologies.fr> | 6 | * Author: Renaud Cerrato <r.cerrato@til-technologies.fr> |
7 | * | 7 | * |
8 | * Watchdog and tamper functions | ||
9 | * Author: Bruno Thomsen <bruno.thomsen@gmail.com> | ||
10 | * | ||
8 | * based on the other drivers in this same directory. | 11 | * based on the other drivers in this same directory. |
9 | * | 12 | * |
10 | * Datasheet: http://cache.nxp.com/documents/data_sheet/PCF2127.pdf | 13 | * Datasheet: http://cache.nxp.com/documents/data_sheet/PCF2127.pdf |
@@ -18,6 +21,7 @@ | |||
18 | #include <linux/module.h> | 21 | #include <linux/module.h> |
19 | #include <linux/of.h> | 22 | #include <linux/of.h> |
20 | #include <linux/regmap.h> | 23 | #include <linux/regmap.h> |
24 | #include <linux/watchdog.h> | ||
21 | 25 | ||
22 | /* Control register 1 */ | 26 | /* Control register 1 */ |
23 | #define PCF2127_REG_CTRL1 0x00 | 27 | #define PCF2127_REG_CTRL1 0x00 |
@@ -35,6 +39,13 @@ | |||
35 | #define PCF2127_REG_DW 0x07 | 39 | #define PCF2127_REG_DW 0x07 |
36 | #define PCF2127_REG_MO 0x08 | 40 | #define PCF2127_REG_MO 0x08 |
37 | #define PCF2127_REG_YR 0x09 | 41 | #define PCF2127_REG_YR 0x09 |
42 | /* Watchdog registers */ | ||
43 | #define PCF2127_REG_WD_CTL 0x10 | ||
44 | #define PCF2127_BIT_WD_CTL_TF0 BIT(0) | ||
45 | #define PCF2127_BIT_WD_CTL_TF1 BIT(1) | ||
46 | #define PCF2127_BIT_WD_CTL_CD0 BIT(6) | ||
47 | #define PCF2127_BIT_WD_CTL_CD1 BIT(7) | ||
48 | #define PCF2127_REG_WD_VAL 0x11 | ||
38 | /* | 49 | /* |
39 | * RAM registers | 50 | * RAM registers |
40 | * PCF2127 has 512 bytes general-purpose static RAM (SRAM) that is | 51 | * PCF2127 has 512 bytes general-purpose static RAM (SRAM) that is |
@@ -45,9 +56,15 @@ | |||
45 | #define PCF2127_REG_RAM_WRT_CMD 0x1C | 56 | #define PCF2127_REG_RAM_WRT_CMD 0x1C |
46 | #define PCF2127_REG_RAM_RD_CMD 0x1D | 57 | #define PCF2127_REG_RAM_RD_CMD 0x1D |
47 | 58 | ||
59 | /* Watchdog timer value constants */ | ||
60 | #define PCF2127_WD_VAL_STOP 0 | ||
61 | #define PCF2127_WD_VAL_MIN 2 | ||
62 | #define PCF2127_WD_VAL_MAX 255 | ||
63 | #define PCF2127_WD_VAL_DEFAULT 60 | ||
48 | 64 | ||
49 | struct pcf2127 { | 65 | struct pcf2127 { |
50 | struct rtc_device *rtc; | 66 | struct rtc_device *rtc; |
67 | struct watchdog_device wdd; | ||
51 | struct regmap *regmap; | 68 | struct regmap *regmap; |
52 | }; | 69 | }; |
53 | 70 | ||
@@ -220,6 +237,74 @@ static int pcf2127_nvmem_write(void *priv, unsigned int offset, | |||
220 | return ret ?: bytes; | 237 | return ret ?: bytes; |
221 | } | 238 | } |
222 | 239 | ||
240 | /* watchdog driver */ | ||
241 | |||
242 | static int pcf2127_wdt_ping(struct watchdog_device *wdd) | ||
243 | { | ||
244 | struct pcf2127 *pcf2127 = watchdog_get_drvdata(wdd); | ||
245 | |||
246 | return regmap_write(pcf2127->regmap, PCF2127_REG_WD_VAL, wdd->timeout); | ||
247 | } | ||
248 | |||
249 | /* | ||
250 | * Restart watchdog timer if feature is active. | ||
251 | * | ||
252 | * Note: Reading CTRL2 register causes watchdog to stop which is unfortunate, | ||
253 | * since register also contain control/status flags for other features. | ||
254 | * Always call this function after reading CTRL2 register. | ||
255 | */ | ||
256 | static int pcf2127_wdt_active_ping(struct watchdog_device *wdd) | ||
257 | { | ||
258 | int ret = 0; | ||
259 | |||
260 | if (watchdog_active(wdd)) { | ||
261 | ret = pcf2127_wdt_ping(wdd); | ||
262 | if (ret) | ||
263 | dev_err(wdd->parent, | ||
264 | "%s: watchdog restart failed, ret=%d\n", | ||
265 | __func__, ret); | ||
266 | } | ||
267 | |||
268 | return ret; | ||
269 | } | ||
270 | |||
271 | static int pcf2127_wdt_start(struct watchdog_device *wdd) | ||
272 | { | ||
273 | return pcf2127_wdt_ping(wdd); | ||
274 | } | ||
275 | |||
276 | static int pcf2127_wdt_stop(struct watchdog_device *wdd) | ||
277 | { | ||
278 | struct pcf2127 *pcf2127 = watchdog_get_drvdata(wdd); | ||
279 | |||
280 | return regmap_write(pcf2127->regmap, PCF2127_REG_WD_VAL, | ||
281 | PCF2127_WD_VAL_STOP); | ||
282 | } | ||
283 | |||
284 | static int pcf2127_wdt_set_timeout(struct watchdog_device *wdd, | ||
285 | unsigned int new_timeout) | ||
286 | { | ||
287 | dev_dbg(wdd->parent, "new watchdog timeout: %is (old: %is)\n", | ||
288 | new_timeout, wdd->timeout); | ||
289 | |||
290 | wdd->timeout = new_timeout; | ||
291 | |||
292 | return pcf2127_wdt_active_ping(wdd); | ||
293 | } | ||
294 | |||
295 | static const struct watchdog_info pcf2127_wdt_info = { | ||
296 | .identity = "NXP PCF2127/PCF2129 Watchdog", | ||
297 | .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT, | ||
298 | }; | ||
299 | |||
300 | static const struct watchdog_ops pcf2127_watchdog_ops = { | ||
301 | .owner = THIS_MODULE, | ||
302 | .start = pcf2127_wdt_start, | ||
303 | .stop = pcf2127_wdt_stop, | ||
304 | .ping = pcf2127_wdt_ping, | ||
305 | .set_timeout = pcf2127_wdt_set_timeout, | ||
306 | }; | ||
307 | |||
223 | static int pcf2127_probe(struct device *dev, struct regmap *regmap, | 308 | static int pcf2127_probe(struct device *dev, struct regmap *regmap, |
224 | const char *name, bool has_nvmem) | 309 | const char *name, bool has_nvmem) |
225 | { | 310 | { |
@@ -242,6 +327,16 @@ static int pcf2127_probe(struct device *dev, struct regmap *regmap, | |||
242 | 327 | ||
243 | pcf2127->rtc->ops = &pcf2127_rtc_ops; | 328 | pcf2127->rtc->ops = &pcf2127_rtc_ops; |
244 | 329 | ||
330 | pcf2127->wdd.parent = dev; | ||
331 | pcf2127->wdd.info = &pcf2127_wdt_info; | ||
332 | pcf2127->wdd.ops = &pcf2127_watchdog_ops; | ||
333 | pcf2127->wdd.min_timeout = PCF2127_WD_VAL_MIN; | ||
334 | pcf2127->wdd.max_timeout = PCF2127_WD_VAL_MAX; | ||
335 | pcf2127->wdd.timeout = PCF2127_WD_VAL_DEFAULT; | ||
336 | pcf2127->wdd.min_hw_heartbeat_ms = 500; | ||
337 | |||
338 | watchdog_set_drvdata(&pcf2127->wdd, pcf2127); | ||
339 | |||
245 | if (has_nvmem) { | 340 | if (has_nvmem) { |
246 | struct nvmem_config nvmem_cfg = { | 341 | struct nvmem_config nvmem_cfg = { |
247 | .priv = pcf2127, | 342 | .priv = pcf2127, |
@@ -253,6 +348,29 @@ static int pcf2127_probe(struct device *dev, struct regmap *regmap, | |||
253 | ret = rtc_nvmem_register(pcf2127->rtc, &nvmem_cfg); | 348 | ret = rtc_nvmem_register(pcf2127->rtc, &nvmem_cfg); |
254 | } | 349 | } |
255 | 350 | ||
351 | /* | ||
352 | * Watchdog timer enabled and reset pin /RST activated when timed out. | ||
353 | * Select 1Hz clock source for watchdog timer. | ||
354 | * Timer is not started until WD_VAL is loaded with a valid value. | ||
355 | * Note: Countdown timer disabled and not available. | ||
356 | */ | ||
357 | ret = regmap_update_bits(pcf2127->regmap, PCF2127_REG_WD_CTL, | ||
358 | PCF2127_BIT_WD_CTL_CD1 | | ||
359 | PCF2127_BIT_WD_CTL_CD0 | | ||
360 | PCF2127_BIT_WD_CTL_TF1 | | ||
361 | PCF2127_BIT_WD_CTL_TF0, | ||
362 | PCF2127_BIT_WD_CTL_CD1 | | ||
363 | PCF2127_BIT_WD_CTL_CD0 | | ||
364 | PCF2127_BIT_WD_CTL_TF1); | ||
365 | if (ret) { | ||
366 | dev_err(dev, "%s: watchdog config (wd_ctl) failed\n", __func__); | ||
367 | return ret; | ||
368 | } | ||
369 | |||
370 | ret = devm_watchdog_register_device(dev, &pcf2127->wdd); | ||
371 | if (ret) | ||
372 | return ret; | ||
373 | |||
256 | return rtc_register_device(pcf2127->rtc); | 374 | return rtc_register_device(pcf2127->rtc); |
257 | } | 375 | } |
258 | 376 | ||