xref: /linux/drivers/watchdog/pic32-wdt.c (revision 0898782247ae533d1f4e47a06bc5d4870931b284)
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