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 void pic32_clk_disable_unprepare(void *data) 170 { 171 clk_disable_unprepare(data); 172 } 173 174 static int pic32_wdt_drv_probe(struct platform_device *pdev) 175 { 176 struct device *dev = &pdev->dev; 177 int ret; 178 struct watchdog_device *wdd = &pic32_wdd; 179 struct pic32_wdt *wdt; 180 181 wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL); 182 if (!wdt) 183 return -ENOMEM; 184 185 wdt->regs = devm_platform_ioremap_resource(pdev, 0); 186 if (IS_ERR(wdt->regs)) 187 return PTR_ERR(wdt->regs); 188 189 wdt->rst_base = devm_ioremap(dev, PIC32_BASE_RESET, 0x10); 190 if (!wdt->rst_base) 191 return -ENOMEM; 192 193 wdt->clk = devm_clk_get(dev, NULL); 194 if (IS_ERR(wdt->clk)) { 195 dev_err(dev, "clk not found\n"); 196 return PTR_ERR(wdt->clk); 197 } 198 199 ret = clk_prepare_enable(wdt->clk); 200 if (ret) { 201 dev_err(dev, "clk enable failed\n"); 202 return ret; 203 } 204 ret = devm_add_action_or_reset(dev, pic32_clk_disable_unprepare, 205 wdt->clk); 206 if (ret) 207 return ret; 208 209 if (pic32_wdt_is_win_enabled(wdt)) { 210 dev_err(dev, "windowed-clear mode is not supported.\n"); 211 return -ENODEV; 212 } 213 214 wdd->timeout = pic32_wdt_get_timeout_secs(wdt, dev); 215 if (!wdd->timeout) { 216 dev_err(dev, "failed to read watchdog register timeout\n"); 217 return -EINVAL; 218 } 219 220 dev_info(dev, "timeout %d\n", wdd->timeout); 221 222 wdd->bootstatus = pic32_wdt_bootstatus(wdt) ? WDIOF_CARDRESET : 0; 223 224 watchdog_set_nowayout(wdd, WATCHDOG_NOWAYOUT); 225 watchdog_set_drvdata(wdd, wdt); 226 227 ret = devm_watchdog_register_device(dev, wdd); 228 if (ret) { 229 dev_err(dev, "watchdog register failed, err %d\n", ret); 230 return ret; 231 } 232 233 platform_set_drvdata(pdev, wdd); 234 235 return 0; 236 } 237 238 static struct platform_driver pic32_wdt_driver = { 239 .probe = pic32_wdt_drv_probe, 240 .driver = { 241 .name = "pic32-wdt", 242 .of_match_table = of_match_ptr(pic32_wdt_dt_ids), 243 } 244 }; 245 246 module_platform_driver(pic32_wdt_driver); 247 248 MODULE_AUTHOR("Joshua Henderson <joshua.henderson@microchip.com>"); 249 MODULE_DESCRIPTION("Microchip PIC32 Watchdog Timer"); 250 MODULE_LICENSE("GPL"); 251