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/of_device.h> 168f91fc56SJoshua Henderson #include <linux/platform_device.h> 178f91fc56SJoshua Henderson #include <linux/pm.h> 188f91fc56SJoshua Henderson #include <linux/watchdog.h> 198f91fc56SJoshua Henderson 208f91fc56SJoshua Henderson #include <asm/mach-pic32/pic32.h> 218f91fc56SJoshua Henderson 228f91fc56SJoshua Henderson /* Watchdog Timer Registers */ 238f91fc56SJoshua Henderson #define WDTCON_REG 0x00 248f91fc56SJoshua Henderson 258f91fc56SJoshua Henderson /* Watchdog Timer Control Register fields */ 268f91fc56SJoshua Henderson #define WDTCON_WIN_EN BIT(0) 278f91fc56SJoshua Henderson #define WDTCON_RMCS_MASK 0x0003 288f91fc56SJoshua Henderson #define WDTCON_RMCS_SHIFT 0x0006 298f91fc56SJoshua Henderson #define WDTCON_RMPS_MASK 0x001F 308f91fc56SJoshua Henderson #define WDTCON_RMPS_SHIFT 0x0008 318f91fc56SJoshua Henderson #define WDTCON_ON BIT(15) 328f91fc56SJoshua Henderson #define WDTCON_CLR_KEY 0x5743 338f91fc56SJoshua Henderson 348f91fc56SJoshua Henderson /* Reset Control Register fields for watchdog */ 358f91fc56SJoshua Henderson #define RESETCON_TIMEOUT_IDLE BIT(2) 368f91fc56SJoshua Henderson #define RESETCON_TIMEOUT_SLEEP BIT(3) 378f91fc56SJoshua Henderson #define RESETCON_WDT_TIMEOUT BIT(4) 388f91fc56SJoshua Henderson 398f91fc56SJoshua Henderson struct pic32_wdt { 408f91fc56SJoshua Henderson void __iomem *regs; 418f91fc56SJoshua Henderson void __iomem *rst_base; 428f91fc56SJoshua Henderson struct clk *clk; 438f91fc56SJoshua Henderson }; 448f91fc56SJoshua Henderson 458f91fc56SJoshua Henderson static inline bool pic32_wdt_is_win_enabled(struct pic32_wdt *wdt) 468f91fc56SJoshua Henderson { 478f91fc56SJoshua Henderson return !!(readl(wdt->regs + WDTCON_REG) & WDTCON_WIN_EN); 488f91fc56SJoshua Henderson } 498f91fc56SJoshua Henderson 508f91fc56SJoshua Henderson static inline u32 pic32_wdt_get_post_scaler(struct pic32_wdt *wdt) 518f91fc56SJoshua Henderson { 528f91fc56SJoshua Henderson u32 v = readl(wdt->regs + WDTCON_REG); 538f91fc56SJoshua Henderson 548f91fc56SJoshua Henderson return (v >> WDTCON_RMPS_SHIFT) & WDTCON_RMPS_MASK; 558f91fc56SJoshua Henderson } 568f91fc56SJoshua Henderson 578f91fc56SJoshua Henderson static inline u32 pic32_wdt_get_clk_id(struct pic32_wdt *wdt) 588f91fc56SJoshua Henderson { 598f91fc56SJoshua Henderson u32 v = readl(wdt->regs + WDTCON_REG); 608f91fc56SJoshua Henderson 618f91fc56SJoshua Henderson return (v >> WDTCON_RMCS_SHIFT) & WDTCON_RMCS_MASK; 628f91fc56SJoshua Henderson } 638f91fc56SJoshua Henderson 648f91fc56SJoshua Henderson static int pic32_wdt_bootstatus(struct pic32_wdt *wdt) 658f91fc56SJoshua Henderson { 668f91fc56SJoshua Henderson u32 v = readl(wdt->rst_base); 678f91fc56SJoshua Henderson 688f91fc56SJoshua Henderson writel(RESETCON_WDT_TIMEOUT, PIC32_CLR(wdt->rst_base)); 698f91fc56SJoshua Henderson 708f91fc56SJoshua Henderson return v & RESETCON_WDT_TIMEOUT; 718f91fc56SJoshua Henderson } 728f91fc56SJoshua Henderson 738f91fc56SJoshua Henderson static u32 pic32_wdt_get_timeout_secs(struct pic32_wdt *wdt, struct device *dev) 748f91fc56SJoshua Henderson { 758f91fc56SJoshua Henderson unsigned long rate; 768f91fc56SJoshua Henderson u32 period, ps, terminal; 778f91fc56SJoshua Henderson 788f91fc56SJoshua Henderson rate = clk_get_rate(wdt->clk); 798f91fc56SJoshua Henderson 808f91fc56SJoshua Henderson dev_dbg(dev, "wdt: clk_id %d, clk_rate %lu (prescale)\n", 818f91fc56SJoshua Henderson pic32_wdt_get_clk_id(wdt), rate); 828f91fc56SJoshua Henderson 838f91fc56SJoshua Henderson /* default, prescaler of 32 (i.e. div-by-32) is implicit. */ 848f91fc56SJoshua Henderson rate >>= 5; 858f91fc56SJoshua Henderson if (!rate) 868f91fc56SJoshua Henderson return 0; 878f91fc56SJoshua Henderson 888f91fc56SJoshua Henderson /* calculate terminal count from postscaler. */ 898f91fc56SJoshua Henderson ps = pic32_wdt_get_post_scaler(wdt); 908f91fc56SJoshua Henderson terminal = BIT(ps); 918f91fc56SJoshua Henderson 928f91fc56SJoshua Henderson /* find time taken (in secs) to reach terminal count */ 938f91fc56SJoshua Henderson period = terminal / rate; 948f91fc56SJoshua Henderson dev_dbg(dev, 958f91fc56SJoshua Henderson "wdt: clk_rate %lu (postscale) / terminal %d, timeout %dsec\n", 968f91fc56SJoshua Henderson rate, terminal, period); 978f91fc56SJoshua Henderson 988f91fc56SJoshua Henderson return period; 998f91fc56SJoshua Henderson } 1008f91fc56SJoshua Henderson 1018f91fc56SJoshua Henderson static void pic32_wdt_keepalive(struct pic32_wdt *wdt) 1028f91fc56SJoshua Henderson { 1038f91fc56SJoshua Henderson /* write key through single half-word */ 1048f91fc56SJoshua Henderson writew(WDTCON_CLR_KEY, wdt->regs + WDTCON_REG + 2); 1058f91fc56SJoshua Henderson } 1068f91fc56SJoshua Henderson 1078f91fc56SJoshua Henderson static int pic32_wdt_start(struct watchdog_device *wdd) 1088f91fc56SJoshua Henderson { 1098f91fc56SJoshua Henderson struct pic32_wdt *wdt = watchdog_get_drvdata(wdd); 1108f91fc56SJoshua Henderson 1118f91fc56SJoshua Henderson writel(WDTCON_ON, PIC32_SET(wdt->regs + WDTCON_REG)); 1128f91fc56SJoshua Henderson pic32_wdt_keepalive(wdt); 1138f91fc56SJoshua Henderson 1148f91fc56SJoshua Henderson return 0; 1158f91fc56SJoshua Henderson } 1168f91fc56SJoshua Henderson 1178f91fc56SJoshua Henderson static int pic32_wdt_stop(struct watchdog_device *wdd) 1188f91fc56SJoshua Henderson { 1198f91fc56SJoshua Henderson struct pic32_wdt *wdt = watchdog_get_drvdata(wdd); 1208f91fc56SJoshua Henderson 1218f91fc56SJoshua Henderson writel(WDTCON_ON, PIC32_CLR(wdt->regs + WDTCON_REG)); 1228f91fc56SJoshua Henderson 1238f91fc56SJoshua Henderson /* 1248f91fc56SJoshua Henderson * Cannot touch registers in the CPU cycle following clearing the 1258f91fc56SJoshua Henderson * ON bit. 1268f91fc56SJoshua Henderson */ 1278f91fc56SJoshua Henderson nop(); 1288f91fc56SJoshua Henderson 1298f91fc56SJoshua Henderson return 0; 1308f91fc56SJoshua Henderson } 1318f91fc56SJoshua Henderson 1328f91fc56SJoshua Henderson static int pic32_wdt_ping(struct watchdog_device *wdd) 1338f91fc56SJoshua Henderson { 1348f91fc56SJoshua Henderson struct pic32_wdt *wdt = watchdog_get_drvdata(wdd); 1358f91fc56SJoshua Henderson 1368f91fc56SJoshua Henderson pic32_wdt_keepalive(wdt); 1378f91fc56SJoshua Henderson 1388f91fc56SJoshua Henderson return 0; 1398f91fc56SJoshua Henderson } 1408f91fc56SJoshua Henderson 1418f91fc56SJoshua Henderson static const struct watchdog_ops pic32_wdt_fops = { 1428f91fc56SJoshua Henderson .owner = THIS_MODULE, 1438f91fc56SJoshua Henderson .start = pic32_wdt_start, 1448f91fc56SJoshua Henderson .stop = pic32_wdt_stop, 1458f91fc56SJoshua Henderson .ping = pic32_wdt_ping, 1468f91fc56SJoshua Henderson }; 1478f91fc56SJoshua Henderson 1488f91fc56SJoshua Henderson static const struct watchdog_info pic32_wdt_ident = { 1498f91fc56SJoshua Henderson .options = WDIOF_KEEPALIVEPING | 1508f91fc56SJoshua Henderson WDIOF_MAGICCLOSE | WDIOF_CARDRESET, 1518f91fc56SJoshua Henderson .identity = "PIC32 Watchdog", 1528f91fc56SJoshua Henderson }; 1538f91fc56SJoshua Henderson 1548f91fc56SJoshua Henderson static struct watchdog_device pic32_wdd = { 1558f91fc56SJoshua Henderson .info = &pic32_wdt_ident, 1568f91fc56SJoshua Henderson .ops = &pic32_wdt_fops, 1578f91fc56SJoshua Henderson }; 1588f91fc56SJoshua Henderson 1598f91fc56SJoshua Henderson static const struct of_device_id pic32_wdt_dt_ids[] = { 1608f91fc56SJoshua Henderson { .compatible = "microchip,pic32mzda-wdt", }, 1618f91fc56SJoshua Henderson { /* sentinel */ } 1628f91fc56SJoshua Henderson }; 1638f91fc56SJoshua Henderson MODULE_DEVICE_TABLE(of, pic32_wdt_dt_ids); 1648f91fc56SJoshua Henderson 1651f22b8caSGuenter Roeck static void pic32_clk_disable_unprepare(void *data) 1661f22b8caSGuenter Roeck { 1671f22b8caSGuenter Roeck clk_disable_unprepare(data); 1681f22b8caSGuenter Roeck } 1691f22b8caSGuenter Roeck 1708f91fc56SJoshua Henderson static int pic32_wdt_drv_probe(struct platform_device *pdev) 1718f91fc56SJoshua Henderson { 1721f22b8caSGuenter Roeck struct device *dev = &pdev->dev; 1738f91fc56SJoshua Henderson int ret; 1748f91fc56SJoshua Henderson struct watchdog_device *wdd = &pic32_wdd; 1758f91fc56SJoshua Henderson struct pic32_wdt *wdt; 1768f91fc56SJoshua Henderson 1771f22b8caSGuenter Roeck wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL); 178db6d2d0eSWei Yongjun if (!wdt) 179db6d2d0eSWei Yongjun return -ENOMEM; 1808f91fc56SJoshua Henderson 1810f0a6a28SGuenter Roeck wdt->regs = devm_platform_ioremap_resource(pdev, 0); 1828f91fc56SJoshua Henderson if (IS_ERR(wdt->regs)) 1838f91fc56SJoshua Henderson return PTR_ERR(wdt->regs); 1848f91fc56SJoshua Henderson 1851f22b8caSGuenter Roeck wdt->rst_base = devm_ioremap(dev, PIC32_BASE_RESET, 0x10); 186cddd74dbSWei Yongjun if (!wdt->rst_base) 187cddd74dbSWei Yongjun return -ENOMEM; 1888f91fc56SJoshua Henderson 1891f22b8caSGuenter Roeck wdt->clk = devm_clk_get(dev, NULL); 1908f91fc56SJoshua Henderson if (IS_ERR(wdt->clk)) { 1911f22b8caSGuenter Roeck dev_err(dev, "clk not found\n"); 1928f91fc56SJoshua Henderson return PTR_ERR(wdt->clk); 1938f91fc56SJoshua Henderson } 1948f91fc56SJoshua Henderson 1958f91fc56SJoshua Henderson ret = clk_prepare_enable(wdt->clk); 1968f91fc56SJoshua Henderson if (ret) { 1971f22b8caSGuenter Roeck dev_err(dev, "clk enable failed\n"); 1988f91fc56SJoshua Henderson return ret; 1998f91fc56SJoshua Henderson } 2001f22b8caSGuenter Roeck ret = devm_add_action_or_reset(dev, pic32_clk_disable_unprepare, 2011f22b8caSGuenter Roeck wdt->clk); 2021f22b8caSGuenter Roeck if (ret) 2031f22b8caSGuenter Roeck return ret; 2048f91fc56SJoshua Henderson 2058f91fc56SJoshua Henderson if (pic32_wdt_is_win_enabled(wdt)) { 2061f22b8caSGuenter Roeck dev_err(dev, "windowed-clear mode is not supported.\n"); 2071f22b8caSGuenter Roeck return -ENODEV; 2088f91fc56SJoshua Henderson } 2098f91fc56SJoshua Henderson 2101f22b8caSGuenter Roeck wdd->timeout = pic32_wdt_get_timeout_secs(wdt, dev); 2118f91fc56SJoshua Henderson if (!wdd->timeout) { 2121f22b8caSGuenter Roeck dev_err(dev, "failed to read watchdog register timeout\n"); 2131f22b8caSGuenter Roeck return -EINVAL; 2148f91fc56SJoshua Henderson } 2158f91fc56SJoshua Henderson 2161f22b8caSGuenter Roeck dev_info(dev, "timeout %d\n", wdd->timeout); 2178f91fc56SJoshua Henderson 2188f91fc56SJoshua Henderson wdd->bootstatus = pic32_wdt_bootstatus(wdt) ? WDIOF_CARDRESET : 0; 2198f91fc56SJoshua Henderson 2208f91fc56SJoshua Henderson watchdog_set_nowayout(wdd, WATCHDOG_NOWAYOUT); 2218f91fc56SJoshua Henderson watchdog_set_drvdata(wdd, wdt); 2228f91fc56SJoshua Henderson 2231f22b8caSGuenter Roeck ret = devm_watchdog_register_device(dev, wdd); 224*90984aa1SWolfram Sang if (ret) 2258f91fc56SJoshua Henderson return ret; 2268f91fc56SJoshua Henderson 2271f22b8caSGuenter Roeck platform_set_drvdata(pdev, wdd); 2288f91fc56SJoshua Henderson 2298f91fc56SJoshua Henderson return 0; 2308f91fc56SJoshua Henderson } 2318f91fc56SJoshua Henderson 2328f91fc56SJoshua Henderson static struct platform_driver pic32_wdt_driver = { 2338f91fc56SJoshua Henderson .probe = pic32_wdt_drv_probe, 2348f91fc56SJoshua Henderson .driver = { 2358f91fc56SJoshua Henderson .name = "pic32-wdt", 2368f91fc56SJoshua Henderson .of_match_table = of_match_ptr(pic32_wdt_dt_ids), 2378f91fc56SJoshua Henderson } 2388f91fc56SJoshua Henderson }; 2398f91fc56SJoshua Henderson 2408f91fc56SJoshua Henderson module_platform_driver(pic32_wdt_driver); 2418f91fc56SJoshua Henderson 2428f91fc56SJoshua Henderson MODULE_AUTHOR("Joshua Henderson <joshua.henderson@microchip.com>"); 2438f91fc56SJoshua Henderson MODULE_DESCRIPTION("Microchip PIC32 Watchdog Timer"); 2448f91fc56SJoshua Henderson MODULE_LICENSE("GPL"); 245