12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
28f91fc56SJoshua Henderson /*
38f91fc56SJoshua Henderson * PIC32 watchdog driver
48f91fc56SJoshua Henderson *
58f91fc56SJoshua Henderson * Joshua Henderson <joshua.henderson@microchip.com>
68f91fc56SJoshua Henderson * Copyright (c) 2016, Microchip Technology Inc.
78f91fc56SJoshua Henderson */
88f91fc56SJoshua Henderson #include <linux/clk.h>
98f91fc56SJoshua Henderson #include <linux/device.h>
108f91fc56SJoshua Henderson #include <linux/err.h>
118f91fc56SJoshua Henderson #include <linux/io.h>
128f91fc56SJoshua Henderson #include <linux/kernel.h>
138f91fc56SJoshua Henderson #include <linux/module.h>
148f91fc56SJoshua Henderson #include <linux/of.h>
158f91fc56SJoshua Henderson #include <linux/platform_device.h>
168f91fc56SJoshua Henderson #include <linux/pm.h>
178f91fc56SJoshua Henderson #include <linux/watchdog.h>
188f91fc56SJoshua Henderson
198f91fc56SJoshua Henderson #include <asm/mach-pic32/pic32.h>
208f91fc56SJoshua Henderson
218f91fc56SJoshua Henderson /* Watchdog Timer Registers */
228f91fc56SJoshua Henderson #define WDTCON_REG 0x00
238f91fc56SJoshua Henderson
248f91fc56SJoshua Henderson /* Watchdog Timer Control Register fields */
258f91fc56SJoshua Henderson #define WDTCON_WIN_EN BIT(0)
268f91fc56SJoshua Henderson #define WDTCON_RMCS_MASK 0x0003
278f91fc56SJoshua Henderson #define WDTCON_RMCS_SHIFT 0x0006
288f91fc56SJoshua Henderson #define WDTCON_RMPS_MASK 0x001F
298f91fc56SJoshua Henderson #define WDTCON_RMPS_SHIFT 0x0008
308f91fc56SJoshua Henderson #define WDTCON_ON BIT(15)
318f91fc56SJoshua Henderson #define WDTCON_CLR_KEY 0x5743
328f91fc56SJoshua Henderson
338f91fc56SJoshua Henderson /* Reset Control Register fields for watchdog */
348f91fc56SJoshua Henderson #define RESETCON_TIMEOUT_IDLE BIT(2)
358f91fc56SJoshua Henderson #define RESETCON_TIMEOUT_SLEEP BIT(3)
368f91fc56SJoshua Henderson #define RESETCON_WDT_TIMEOUT BIT(4)
378f91fc56SJoshua Henderson
388f91fc56SJoshua Henderson struct pic32_wdt {
398f91fc56SJoshua Henderson void __iomem *regs;
408f91fc56SJoshua Henderson void __iomem *rst_base;
418f91fc56SJoshua Henderson struct clk *clk;
428f91fc56SJoshua Henderson };
438f91fc56SJoshua Henderson
pic32_wdt_is_win_enabled(struct pic32_wdt * wdt)448f91fc56SJoshua Henderson static inline bool pic32_wdt_is_win_enabled(struct pic32_wdt *wdt)
458f91fc56SJoshua Henderson {
468f91fc56SJoshua Henderson return !!(readl(wdt->regs + WDTCON_REG) & WDTCON_WIN_EN);
478f91fc56SJoshua Henderson }
488f91fc56SJoshua Henderson
pic32_wdt_get_post_scaler(struct pic32_wdt * wdt)498f91fc56SJoshua Henderson static inline u32 pic32_wdt_get_post_scaler(struct pic32_wdt *wdt)
508f91fc56SJoshua Henderson {
518f91fc56SJoshua Henderson u32 v = readl(wdt->regs + WDTCON_REG);
528f91fc56SJoshua Henderson
538f91fc56SJoshua Henderson return (v >> WDTCON_RMPS_SHIFT) & WDTCON_RMPS_MASK;
548f91fc56SJoshua Henderson }
558f91fc56SJoshua Henderson
pic32_wdt_get_clk_id(struct pic32_wdt * wdt)568f91fc56SJoshua Henderson static inline u32 pic32_wdt_get_clk_id(struct pic32_wdt *wdt)
578f91fc56SJoshua Henderson {
588f91fc56SJoshua Henderson u32 v = readl(wdt->regs + WDTCON_REG);
598f91fc56SJoshua Henderson
608f91fc56SJoshua Henderson return (v >> WDTCON_RMCS_SHIFT) & WDTCON_RMCS_MASK;
618f91fc56SJoshua Henderson }
628f91fc56SJoshua Henderson
pic32_wdt_bootstatus(struct pic32_wdt * wdt)638f91fc56SJoshua Henderson static int pic32_wdt_bootstatus(struct pic32_wdt *wdt)
648f91fc56SJoshua Henderson {
658f91fc56SJoshua Henderson u32 v = readl(wdt->rst_base);
668f91fc56SJoshua Henderson
678f91fc56SJoshua Henderson writel(RESETCON_WDT_TIMEOUT, PIC32_CLR(wdt->rst_base));
688f91fc56SJoshua Henderson
698f91fc56SJoshua Henderson return v & RESETCON_WDT_TIMEOUT;
708f91fc56SJoshua Henderson }
718f91fc56SJoshua Henderson
pic32_wdt_get_timeout_secs(struct pic32_wdt * wdt,struct device * dev)728f91fc56SJoshua Henderson static u32 pic32_wdt_get_timeout_secs(struct pic32_wdt *wdt, struct device *dev)
738f91fc56SJoshua Henderson {
748f91fc56SJoshua Henderson unsigned long rate;
758f91fc56SJoshua Henderson u32 period, ps, terminal;
768f91fc56SJoshua Henderson
778f91fc56SJoshua Henderson rate = clk_get_rate(wdt->clk);
788f91fc56SJoshua Henderson
798f91fc56SJoshua Henderson dev_dbg(dev, "wdt: clk_id %d, clk_rate %lu (prescale)\n",
808f91fc56SJoshua Henderson pic32_wdt_get_clk_id(wdt), rate);
818f91fc56SJoshua Henderson
828f91fc56SJoshua Henderson /* default, prescaler of 32 (i.e. div-by-32) is implicit. */
838f91fc56SJoshua Henderson rate >>= 5;
848f91fc56SJoshua Henderson if (!rate)
858f91fc56SJoshua Henderson return 0;
868f91fc56SJoshua Henderson
878f91fc56SJoshua Henderson /* calculate terminal count from postscaler. */
888f91fc56SJoshua Henderson ps = pic32_wdt_get_post_scaler(wdt);
898f91fc56SJoshua Henderson terminal = BIT(ps);
908f91fc56SJoshua Henderson
918f91fc56SJoshua Henderson /* find time taken (in secs) to reach terminal count */
928f91fc56SJoshua Henderson period = terminal / rate;
938f91fc56SJoshua Henderson dev_dbg(dev,
948f91fc56SJoshua Henderson "wdt: clk_rate %lu (postscale) / terminal %d, timeout %dsec\n",
958f91fc56SJoshua Henderson rate, terminal, period);
968f91fc56SJoshua Henderson
978f91fc56SJoshua Henderson return period;
988f91fc56SJoshua Henderson }
998f91fc56SJoshua Henderson
pic32_wdt_keepalive(struct pic32_wdt * wdt)1008f91fc56SJoshua Henderson static void pic32_wdt_keepalive(struct pic32_wdt *wdt)
1018f91fc56SJoshua Henderson {
1028f91fc56SJoshua Henderson /* write key through single half-word */
1038f91fc56SJoshua Henderson writew(WDTCON_CLR_KEY, wdt->regs + WDTCON_REG + 2);
1048f91fc56SJoshua Henderson }
1058f91fc56SJoshua Henderson
pic32_wdt_start(struct watchdog_device * wdd)1068f91fc56SJoshua Henderson static int pic32_wdt_start(struct watchdog_device *wdd)
1078f91fc56SJoshua Henderson {
1088f91fc56SJoshua Henderson struct pic32_wdt *wdt = watchdog_get_drvdata(wdd);
1098f91fc56SJoshua Henderson
1108f91fc56SJoshua Henderson writel(WDTCON_ON, PIC32_SET(wdt->regs + WDTCON_REG));
1118f91fc56SJoshua Henderson pic32_wdt_keepalive(wdt);
1128f91fc56SJoshua Henderson
1138f91fc56SJoshua Henderson return 0;
1148f91fc56SJoshua Henderson }
1158f91fc56SJoshua Henderson
pic32_wdt_stop(struct watchdog_device * wdd)1168f91fc56SJoshua Henderson static int pic32_wdt_stop(struct watchdog_device *wdd)
1178f91fc56SJoshua Henderson {
1188f91fc56SJoshua Henderson struct pic32_wdt *wdt = watchdog_get_drvdata(wdd);
1198f91fc56SJoshua Henderson
1208f91fc56SJoshua Henderson writel(WDTCON_ON, PIC32_CLR(wdt->regs + WDTCON_REG));
1218f91fc56SJoshua Henderson
1228f91fc56SJoshua Henderson /*
1238f91fc56SJoshua Henderson * Cannot touch registers in the CPU cycle following clearing the
1248f91fc56SJoshua Henderson * ON bit.
1258f91fc56SJoshua Henderson */
1268f91fc56SJoshua Henderson nop();
1278f91fc56SJoshua Henderson
1288f91fc56SJoshua Henderson return 0;
1298f91fc56SJoshua Henderson }
1308f91fc56SJoshua Henderson
pic32_wdt_ping(struct watchdog_device * wdd)1318f91fc56SJoshua Henderson static int pic32_wdt_ping(struct watchdog_device *wdd)
1328f91fc56SJoshua Henderson {
1338f91fc56SJoshua Henderson struct pic32_wdt *wdt = watchdog_get_drvdata(wdd);
1348f91fc56SJoshua Henderson
1358f91fc56SJoshua Henderson pic32_wdt_keepalive(wdt);
1368f91fc56SJoshua Henderson
1378f91fc56SJoshua Henderson return 0;
1388f91fc56SJoshua Henderson }
1398f91fc56SJoshua Henderson
1408f91fc56SJoshua Henderson static const struct watchdog_ops pic32_wdt_fops = {
1418f91fc56SJoshua Henderson .owner = THIS_MODULE,
1428f91fc56SJoshua Henderson .start = pic32_wdt_start,
1438f91fc56SJoshua Henderson .stop = pic32_wdt_stop,
1448f91fc56SJoshua Henderson .ping = pic32_wdt_ping,
1458f91fc56SJoshua Henderson };
1468f91fc56SJoshua Henderson
1478f91fc56SJoshua Henderson static const struct watchdog_info pic32_wdt_ident = {
1488f91fc56SJoshua Henderson .options = WDIOF_KEEPALIVEPING |
1498f91fc56SJoshua Henderson WDIOF_MAGICCLOSE | WDIOF_CARDRESET,
1508f91fc56SJoshua Henderson .identity = "PIC32 Watchdog",
1518f91fc56SJoshua Henderson };
1528f91fc56SJoshua Henderson
1538f91fc56SJoshua Henderson static struct watchdog_device pic32_wdd = {
1548f91fc56SJoshua Henderson .info = &pic32_wdt_ident,
1558f91fc56SJoshua Henderson .ops = &pic32_wdt_fops,
1568f91fc56SJoshua Henderson };
1578f91fc56SJoshua Henderson
1588f91fc56SJoshua Henderson static const struct of_device_id pic32_wdt_dt_ids[] = {
1598f91fc56SJoshua Henderson { .compatible = "microchip,pic32mzda-wdt", },
1608f91fc56SJoshua Henderson { /* sentinel */ }
1618f91fc56SJoshua Henderson };
1628f91fc56SJoshua Henderson MODULE_DEVICE_TABLE(of, pic32_wdt_dt_ids);
1638f91fc56SJoshua Henderson
pic32_wdt_drv_probe(struct platform_device * pdev)1648f91fc56SJoshua Henderson static int pic32_wdt_drv_probe(struct platform_device *pdev)
1658f91fc56SJoshua Henderson {
1661f22b8caSGuenter Roeck struct device *dev = &pdev->dev;
1678f91fc56SJoshua Henderson int ret;
1688f91fc56SJoshua Henderson struct watchdog_device *wdd = &pic32_wdd;
1698f91fc56SJoshua Henderson struct pic32_wdt *wdt;
1708f91fc56SJoshua Henderson
1711f22b8caSGuenter Roeck wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
172db6d2d0eSWei Yongjun if (!wdt)
173db6d2d0eSWei Yongjun return -ENOMEM;
1748f91fc56SJoshua Henderson
1750f0a6a28SGuenter Roeck wdt->regs = devm_platform_ioremap_resource(pdev, 0);
1768f91fc56SJoshua Henderson if (IS_ERR(wdt->regs))
1778f91fc56SJoshua Henderson return PTR_ERR(wdt->regs);
1788f91fc56SJoshua Henderson
1791f22b8caSGuenter Roeck wdt->rst_base = devm_ioremap(dev, PIC32_BASE_RESET, 0x10);
180cddd74dbSWei Yongjun if (!wdt->rst_base)
181cddd74dbSWei Yongjun return -ENOMEM;
1828f91fc56SJoshua Henderson
183*7f7f8ad0SChristophe JAILLET wdt->clk = devm_clk_get_enabled(dev, NULL);
1848f91fc56SJoshua Henderson if (IS_ERR(wdt->clk)) {
1851f22b8caSGuenter Roeck dev_err(dev, "clk not found\n");
1868f91fc56SJoshua Henderson return PTR_ERR(wdt->clk);
1878f91fc56SJoshua Henderson }
1888f91fc56SJoshua Henderson
1898f91fc56SJoshua Henderson if (pic32_wdt_is_win_enabled(wdt)) {
1901f22b8caSGuenter Roeck dev_err(dev, "windowed-clear mode is not supported.\n");
1911f22b8caSGuenter Roeck return -ENODEV;
1928f91fc56SJoshua Henderson }
1938f91fc56SJoshua Henderson
1941f22b8caSGuenter Roeck wdd->timeout = pic32_wdt_get_timeout_secs(wdt, dev);
1958f91fc56SJoshua Henderson if (!wdd->timeout) {
1961f22b8caSGuenter Roeck dev_err(dev, "failed to read watchdog register timeout\n");
1971f22b8caSGuenter Roeck return -EINVAL;
1988f91fc56SJoshua Henderson }
1998f91fc56SJoshua Henderson
2001f22b8caSGuenter Roeck dev_info(dev, "timeout %d\n", wdd->timeout);
2018f91fc56SJoshua Henderson
2028f91fc56SJoshua Henderson wdd->bootstatus = pic32_wdt_bootstatus(wdt) ? WDIOF_CARDRESET : 0;
2038f91fc56SJoshua Henderson
2048f91fc56SJoshua Henderson watchdog_set_nowayout(wdd, WATCHDOG_NOWAYOUT);
2058f91fc56SJoshua Henderson watchdog_set_drvdata(wdd, wdt);
2068f91fc56SJoshua Henderson
2071f22b8caSGuenter Roeck ret = devm_watchdog_register_device(dev, wdd);
20890984aa1SWolfram Sang if (ret)
2098f91fc56SJoshua Henderson return ret;
2108f91fc56SJoshua Henderson
2111f22b8caSGuenter Roeck platform_set_drvdata(pdev, wdd);
2128f91fc56SJoshua Henderson
2138f91fc56SJoshua Henderson return 0;
2148f91fc56SJoshua Henderson }
2158f91fc56SJoshua Henderson
2168f91fc56SJoshua Henderson static struct platform_driver pic32_wdt_driver = {
2178f91fc56SJoshua Henderson .probe = pic32_wdt_drv_probe,
2188f91fc56SJoshua Henderson .driver = {
2198f91fc56SJoshua Henderson .name = "pic32-wdt",
2208f91fc56SJoshua Henderson .of_match_table = of_match_ptr(pic32_wdt_dt_ids),
2218f91fc56SJoshua Henderson }
2228f91fc56SJoshua Henderson };
2238f91fc56SJoshua Henderson
2248f91fc56SJoshua Henderson module_platform_driver(pic32_wdt_driver);
2258f91fc56SJoshua Henderson
2268f91fc56SJoshua Henderson MODULE_AUTHOR("Joshua Henderson <joshua.henderson@microchip.com>");
2278f91fc56SJoshua Henderson MODULE_DESCRIPTION("Microchip PIC32 Watchdog Timer");
2288f91fc56SJoshua Henderson MODULE_LICENSE("GPL");
229