1 /* 2 * sunxi Watchdog Driver 3 * 4 * Copyright (c) 2013 Carlo Caione 5 * 2012 Henrik Nordstrom 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 * Based on xen_wdt.c 13 * (c) Copyright 2010 Novell, Inc. 14 */ 15 16 #include <linux/clk.h> 17 #include <linux/delay.h> 18 #include <linux/err.h> 19 #include <linux/init.h> 20 #include <linux/io.h> 21 #include <linux/kernel.h> 22 #include <linux/module.h> 23 #include <linux/moduleparam.h> 24 #include <linux/of.h> 25 #include <linux/of_device.h> 26 #include <linux/platform_device.h> 27 #include <linux/types.h> 28 #include <linux/watchdog.h> 29 30 #define WDT_MAX_TIMEOUT 16 31 #define WDT_MIN_TIMEOUT 1 32 #define WDT_TIMEOUT_MASK 0x0F 33 34 #define WDT_CTRL_RELOAD ((1 << 0) | (0x0a57 << 1)) 35 36 #define WDT_MODE_EN (1 << 0) 37 38 #define DRV_NAME "sunxi-wdt" 39 #define DRV_VERSION "1.0" 40 41 static bool nowayout = WATCHDOG_NOWAYOUT; 42 static unsigned int timeout = WDT_MAX_TIMEOUT; 43 44 /* 45 * This structure stores the register offsets for different variants 46 * of Allwinner's watchdog hardware. 47 */ 48 struct sunxi_wdt_reg { 49 u8 wdt_ctrl; 50 u8 wdt_cfg; 51 u8 wdt_mode; 52 u8 wdt_timeout_shift; 53 u8 wdt_reset_mask; 54 u8 wdt_reset_val; 55 }; 56 57 struct sunxi_wdt_dev { 58 struct watchdog_device wdt_dev; 59 void __iomem *wdt_base; 60 const struct sunxi_wdt_reg *wdt_regs; 61 }; 62 63 /* 64 * wdt_timeout_map maps the watchdog timer interval value in seconds to 65 * the value of the register WDT_MODE at bits .wdt_timeout_shift ~ +3 66 * 67 * [timeout seconds] = register value 68 * 69 */ 70 71 static const int wdt_timeout_map[] = { 72 [1] = 0x1, /* 1s */ 73 [2] = 0x2, /* 2s */ 74 [3] = 0x3, /* 3s */ 75 [4] = 0x4, /* 4s */ 76 [5] = 0x5, /* 5s */ 77 [6] = 0x6, /* 6s */ 78 [8] = 0x7, /* 8s */ 79 [10] = 0x8, /* 10s */ 80 [12] = 0x9, /* 12s */ 81 [14] = 0xA, /* 14s */ 82 [16] = 0xB, /* 16s */ 83 }; 84 85 86 static int sunxi_wdt_restart(struct watchdog_device *wdt_dev, 87 unsigned long action, void *data) 88 { 89 struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev); 90 void __iomem *wdt_base = sunxi_wdt->wdt_base; 91 const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs; 92 u32 val; 93 94 /* Set system reset function */ 95 val = readl(wdt_base + regs->wdt_cfg); 96 val &= ~(regs->wdt_reset_mask); 97 val |= regs->wdt_reset_val; 98 writel(val, wdt_base + regs->wdt_cfg); 99 100 /* Set lowest timeout and enable watchdog */ 101 val = readl(wdt_base + regs->wdt_mode); 102 val &= ~(WDT_TIMEOUT_MASK << regs->wdt_timeout_shift); 103 val |= WDT_MODE_EN; 104 writel(val, wdt_base + regs->wdt_mode); 105 106 /* 107 * Restart the watchdog. The default (and lowest) interval 108 * value for the watchdog is 0.5s. 109 */ 110 writel(WDT_CTRL_RELOAD, wdt_base + regs->wdt_ctrl); 111 112 while (1) { 113 mdelay(5); 114 val = readl(wdt_base + regs->wdt_mode); 115 val |= WDT_MODE_EN; 116 writel(val, wdt_base + regs->wdt_mode); 117 } 118 return 0; 119 } 120 121 static int sunxi_wdt_ping(struct watchdog_device *wdt_dev) 122 { 123 struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev); 124 void __iomem *wdt_base = sunxi_wdt->wdt_base; 125 const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs; 126 127 writel(WDT_CTRL_RELOAD, wdt_base + regs->wdt_ctrl); 128 129 return 0; 130 } 131 132 static int sunxi_wdt_set_timeout(struct watchdog_device *wdt_dev, 133 unsigned int timeout) 134 { 135 struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev); 136 void __iomem *wdt_base = sunxi_wdt->wdt_base; 137 const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs; 138 u32 reg; 139 140 if (wdt_timeout_map[timeout] == 0) 141 timeout++; 142 143 sunxi_wdt->wdt_dev.timeout = timeout; 144 145 reg = readl(wdt_base + regs->wdt_mode); 146 reg &= ~(WDT_TIMEOUT_MASK << regs->wdt_timeout_shift); 147 reg |= wdt_timeout_map[timeout] << regs->wdt_timeout_shift; 148 writel(reg, wdt_base + regs->wdt_mode); 149 150 sunxi_wdt_ping(wdt_dev); 151 152 return 0; 153 } 154 155 static int sunxi_wdt_stop(struct watchdog_device *wdt_dev) 156 { 157 struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev); 158 void __iomem *wdt_base = sunxi_wdt->wdt_base; 159 const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs; 160 161 writel(0, wdt_base + regs->wdt_mode); 162 163 return 0; 164 } 165 166 static int sunxi_wdt_start(struct watchdog_device *wdt_dev) 167 { 168 u32 reg; 169 struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev); 170 void __iomem *wdt_base = sunxi_wdt->wdt_base; 171 const struct sunxi_wdt_reg *regs = sunxi_wdt->wdt_regs; 172 int ret; 173 174 ret = sunxi_wdt_set_timeout(&sunxi_wdt->wdt_dev, 175 sunxi_wdt->wdt_dev.timeout); 176 if (ret < 0) 177 return ret; 178 179 /* Set system reset function */ 180 reg = readl(wdt_base + regs->wdt_cfg); 181 reg &= ~(regs->wdt_reset_mask); 182 reg |= regs->wdt_reset_val; 183 writel(reg, wdt_base + regs->wdt_cfg); 184 185 /* Enable watchdog */ 186 reg = readl(wdt_base + regs->wdt_mode); 187 reg |= WDT_MODE_EN; 188 writel(reg, wdt_base + regs->wdt_mode); 189 190 return 0; 191 } 192 193 static const struct watchdog_info sunxi_wdt_info = { 194 .identity = DRV_NAME, 195 .options = WDIOF_SETTIMEOUT | 196 WDIOF_KEEPALIVEPING | 197 WDIOF_MAGICCLOSE, 198 }; 199 200 static const struct watchdog_ops sunxi_wdt_ops = { 201 .owner = THIS_MODULE, 202 .start = sunxi_wdt_start, 203 .stop = sunxi_wdt_stop, 204 .ping = sunxi_wdt_ping, 205 .set_timeout = sunxi_wdt_set_timeout, 206 .restart = sunxi_wdt_restart, 207 }; 208 209 static const struct sunxi_wdt_reg sun4i_wdt_reg = { 210 .wdt_ctrl = 0x00, 211 .wdt_cfg = 0x04, 212 .wdt_mode = 0x04, 213 .wdt_timeout_shift = 3, 214 .wdt_reset_mask = 0x02, 215 .wdt_reset_val = 0x02, 216 }; 217 218 static const struct sunxi_wdt_reg sun6i_wdt_reg = { 219 .wdt_ctrl = 0x10, 220 .wdt_cfg = 0x14, 221 .wdt_mode = 0x18, 222 .wdt_timeout_shift = 4, 223 .wdt_reset_mask = 0x03, 224 .wdt_reset_val = 0x01, 225 }; 226 227 static const struct of_device_id sunxi_wdt_dt_ids[] = { 228 { .compatible = "allwinner,sun4i-a10-wdt", .data = &sun4i_wdt_reg }, 229 { .compatible = "allwinner,sun6i-a31-wdt", .data = &sun6i_wdt_reg }, 230 { /* sentinel */ } 231 }; 232 MODULE_DEVICE_TABLE(of, sunxi_wdt_dt_ids); 233 234 static int sunxi_wdt_probe(struct platform_device *pdev) 235 { 236 struct sunxi_wdt_dev *sunxi_wdt; 237 struct resource *res; 238 int err; 239 240 sunxi_wdt = devm_kzalloc(&pdev->dev, sizeof(*sunxi_wdt), GFP_KERNEL); 241 if (!sunxi_wdt) 242 return -EINVAL; 243 244 sunxi_wdt->wdt_regs = of_device_get_match_data(&pdev->dev); 245 if (!sunxi_wdt->wdt_regs) 246 return -ENODEV; 247 248 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 249 sunxi_wdt->wdt_base = devm_ioremap_resource(&pdev->dev, res); 250 if (IS_ERR(sunxi_wdt->wdt_base)) 251 return PTR_ERR(sunxi_wdt->wdt_base); 252 253 sunxi_wdt->wdt_dev.info = &sunxi_wdt_info; 254 sunxi_wdt->wdt_dev.ops = &sunxi_wdt_ops; 255 sunxi_wdt->wdt_dev.timeout = WDT_MAX_TIMEOUT; 256 sunxi_wdt->wdt_dev.max_timeout = WDT_MAX_TIMEOUT; 257 sunxi_wdt->wdt_dev.min_timeout = WDT_MIN_TIMEOUT; 258 sunxi_wdt->wdt_dev.parent = &pdev->dev; 259 260 watchdog_init_timeout(&sunxi_wdt->wdt_dev, timeout, &pdev->dev); 261 watchdog_set_nowayout(&sunxi_wdt->wdt_dev, nowayout); 262 watchdog_set_restart_priority(&sunxi_wdt->wdt_dev, 128); 263 264 watchdog_set_drvdata(&sunxi_wdt->wdt_dev, sunxi_wdt); 265 266 sunxi_wdt_stop(&sunxi_wdt->wdt_dev); 267 268 watchdog_stop_on_reboot(&sunxi_wdt->wdt_dev); 269 err = devm_watchdog_register_device(&pdev->dev, &sunxi_wdt->wdt_dev); 270 if (unlikely(err)) 271 return err; 272 273 dev_info(&pdev->dev, "Watchdog enabled (timeout=%d sec, nowayout=%d)", 274 sunxi_wdt->wdt_dev.timeout, nowayout); 275 276 return 0; 277 } 278 279 static struct platform_driver sunxi_wdt_driver = { 280 .probe = sunxi_wdt_probe, 281 .driver = { 282 .name = DRV_NAME, 283 .of_match_table = sunxi_wdt_dt_ids, 284 }, 285 }; 286 287 module_platform_driver(sunxi_wdt_driver); 288 289 module_param(timeout, uint, 0); 290 MODULE_PARM_DESC(timeout, "Watchdog heartbeat in seconds"); 291 292 module_param(nowayout, bool, 0); 293 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started " 294 "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 295 296 MODULE_LICENSE("GPL"); 297 MODULE_AUTHOR("Carlo Caione <carlo.caione@gmail.com>"); 298 MODULE_AUTHOR("Henrik Nordstrom <henrik@henriknordstrom.net>"); 299 MODULE_DESCRIPTION("sunxi WatchDog Timer Driver"); 300 MODULE_VERSION(DRV_VERSION); 301