1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * PIC32 watchdog driver 4 * 5 * Joshua Henderson <joshua.henderson@microchip.com> 6 * Copyright (c) 2016, Microchip Technology Inc. 7 */ 8 #include <linux/clk.h> 9 #include <linux/device.h> 10 #include <linux/err.h> 11 #include <linux/io.h> 12 #include <linux/kernel.h> 13 #include <linux/module.h> 14 #include <linux/of.h> 15 #include <linux/platform_device.h> 16 #include <linux/pm.h> 17 #include <linux/watchdog.h> 18 19 #include <asm/mach-pic32/pic32.h> 20 21 /* Watchdog Timer Registers */ 22 #define WDTCON_REG 0x00 23 24 /* Watchdog Timer Control Register fields */ 25 #define WDTCON_WIN_EN BIT(0) 26 #define WDTCON_RMCS_MASK 0x0003 27 #define WDTCON_RMCS_SHIFT 0x0006 28 #define WDTCON_RMPS_MASK 0x001F 29 #define WDTCON_RMPS_SHIFT 0x0008 30 #define WDTCON_ON BIT(15) 31 #define WDTCON_CLR_KEY 0x5743 32 33 /* Reset Control Register fields for watchdog */ 34 #define RESETCON_TIMEOUT_IDLE BIT(2) 35 #define RESETCON_TIMEOUT_SLEEP BIT(3) 36 #define RESETCON_WDT_TIMEOUT BIT(4) 37 38 struct pic32_wdt { 39 void __iomem *regs; 40 void __iomem *rst_base; 41 struct clk *clk; 42 }; 43 44 static inline bool pic32_wdt_is_win_enabled(struct pic32_wdt *wdt) 45 { 46 return !!(readl(wdt->regs + WDTCON_REG) & WDTCON_WIN_EN); 47 } 48 49 static inline u32 pic32_wdt_get_post_scaler(struct pic32_wdt *wdt) 50 { 51 u32 v = readl(wdt->regs + WDTCON_REG); 52 53 return (v >> WDTCON_RMPS_SHIFT) & WDTCON_RMPS_MASK; 54 } 55 56 static inline u32 pic32_wdt_get_clk_id(struct pic32_wdt *wdt) 57 { 58 u32 v = readl(wdt->regs + WDTCON_REG); 59 60 return (v >> WDTCON_RMCS_SHIFT) & WDTCON_RMCS_MASK; 61 } 62 63 static int pic32_wdt_bootstatus(struct pic32_wdt *wdt) 64 { 65 u32 v = readl(wdt->rst_base); 66 67 writel(RESETCON_WDT_TIMEOUT, PIC32_CLR(wdt->rst_base)); 68 69 return v & RESETCON_WDT_TIMEOUT; 70 } 71 72 static u32 pic32_wdt_get_timeout_secs(struct pic32_wdt *wdt, struct device *dev) 73 { 74 unsigned long rate; 75 u32 period, ps, terminal; 76 77 rate = clk_get_rate(wdt->clk); 78 79 dev_dbg(dev, "wdt: clk_id %d, clk_rate %lu (prescale)\n", 80 pic32_wdt_get_clk_id(wdt), rate); 81 82 /* default, prescaler of 32 (i.e. div-by-32) is implicit. */ 83 rate >>= 5; 84 if (!rate) 85 return 0; 86 87 /* calculate terminal count from postscaler. */ 88 ps = pic32_wdt_get_post_scaler(wdt); 89 terminal = BIT(ps); 90 91 /* find time taken (in secs) to reach terminal count */ 92 period = terminal / rate; 93 dev_dbg(dev, 94 "wdt: clk_rate %lu (postscale) / terminal %d, timeout %dsec\n", 95 rate, terminal, period); 96 97 return period; 98 } 99 100 static void pic32_wdt_keepalive(struct pic32_wdt *wdt) 101 { 102 /* write key through single half-word */ 103 writew(WDTCON_CLR_KEY, wdt->regs + WDTCON_REG + 2); 104 } 105 106 static int pic32_wdt_start(struct watchdog_device *wdd) 107 { 108 struct pic32_wdt *wdt = watchdog_get_drvdata(wdd); 109 110 writel(WDTCON_ON, PIC32_SET(wdt->regs + WDTCON_REG)); 111 pic32_wdt_keepalive(wdt); 112 113 return 0; 114 } 115 116 static int pic32_wdt_stop(struct watchdog_device *wdd) 117 { 118 struct pic32_wdt *wdt = watchdog_get_drvdata(wdd); 119 120 writel(WDTCON_ON, PIC32_CLR(wdt->regs + WDTCON_REG)); 121 122 /* 123 * Cannot touch registers in the CPU cycle following clearing the 124 * ON bit. 125 */ 126 nop(); 127 128 return 0; 129 } 130 131 static int pic32_wdt_ping(struct watchdog_device *wdd) 132 { 133 struct pic32_wdt *wdt = watchdog_get_drvdata(wdd); 134 135 pic32_wdt_keepalive(wdt); 136 137 return 0; 138 } 139 140 static const struct watchdog_ops pic32_wdt_fops = { 141 .owner = THIS_MODULE, 142 .start = pic32_wdt_start, 143 .stop = pic32_wdt_stop, 144 .ping = pic32_wdt_ping, 145 }; 146 147 static const struct watchdog_info pic32_wdt_ident = { 148 .options = WDIOF_KEEPALIVEPING | 149 WDIOF_MAGICCLOSE | WDIOF_CARDRESET, 150 .identity = "PIC32 Watchdog", 151 }; 152 153 static struct watchdog_device pic32_wdd = { 154 .info = &pic32_wdt_ident, 155 .ops = &pic32_wdt_fops, 156 }; 157 158 static const struct of_device_id pic32_wdt_dt_ids[] = { 159 { .compatible = "microchip,pic32mzda-wdt", }, 160 { /* sentinel */ } 161 }; 162 MODULE_DEVICE_TABLE(of, pic32_wdt_dt_ids); 163 164 static int pic32_wdt_drv_probe(struct platform_device *pdev) 165 { 166 struct device *dev = &pdev->dev; 167 int ret; 168 struct watchdog_device *wdd = &pic32_wdd; 169 struct pic32_wdt *wdt; 170 171 wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL); 172 if (!wdt) 173 return -ENOMEM; 174 175 wdt->regs = devm_platform_ioremap_resource(pdev, 0); 176 if (IS_ERR(wdt->regs)) 177 return PTR_ERR(wdt->regs); 178 179 wdt->rst_base = devm_ioremap(dev, PIC32_BASE_RESET, 0x10); 180 if (!wdt->rst_base) 181 return -ENOMEM; 182 183 wdt->clk = devm_clk_get_enabled(dev, NULL); 184 if (IS_ERR(wdt->clk)) { 185 dev_err(dev, "clk not found\n"); 186 return PTR_ERR(wdt->clk); 187 } 188 189 if (pic32_wdt_is_win_enabled(wdt)) { 190 dev_err(dev, "windowed-clear mode is not supported.\n"); 191 return -ENODEV; 192 } 193 194 wdd->timeout = pic32_wdt_get_timeout_secs(wdt, dev); 195 if (!wdd->timeout) { 196 dev_err(dev, "failed to read watchdog register timeout\n"); 197 return -EINVAL; 198 } 199 200 dev_info(dev, "timeout %d\n", wdd->timeout); 201 202 wdd->bootstatus = pic32_wdt_bootstatus(wdt) ? WDIOF_CARDRESET : 0; 203 204 watchdog_set_nowayout(wdd, WATCHDOG_NOWAYOUT); 205 watchdog_set_drvdata(wdd, wdt); 206 207 ret = devm_watchdog_register_device(dev, wdd); 208 if (ret) 209 return ret; 210 211 platform_set_drvdata(pdev, wdd); 212 213 return 0; 214 } 215 216 static struct platform_driver pic32_wdt_driver = { 217 .probe = pic32_wdt_drv_probe, 218 .driver = { 219 .name = "pic32-wdt", 220 .of_match_table = of_match_ptr(pic32_wdt_dt_ids), 221 } 222 }; 223 224 module_platform_driver(pic32_wdt_driver); 225 226 MODULE_AUTHOR("Joshua Henderson <joshua.henderson@microchip.com>"); 227 MODULE_DESCRIPTION("Microchip PIC32 Watchdog Timer"); 228 MODULE_LICENSE("GPL"); 229