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