1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Intel Atom E6xx Watchdog driver 4 * 5 * Copyright (C) 2011 Alexander Stein 6 * <alexander.stein@systec-electronic.com> 7 */ 8 9 #include <linux/module.h> 10 #include <linux/moduleparam.h> 11 #include <linux/platform_device.h> 12 #include <linux/io.h> 13 #include <linux/kernel.h> 14 #include <linux/types.h> 15 #include <linux/watchdog.h> 16 #include <linux/seq_file.h> 17 #include <linux/debugfs.h> 18 #include <linux/uaccess.h> 19 #include <linux/spinlock.h> 20 21 #define DRIVER_NAME "ie6xx_wdt" 22 23 #define PV1 0x00 24 #define PV2 0x04 25 26 #define RR0 0x0c 27 #define RR1 0x0d 28 #define WDT_RELOAD 0x01 29 #define WDT_TOUT 0x02 30 31 #define WDTCR 0x10 32 #define WDT_PRE_SEL 0x04 33 #define WDT_RESET_SEL 0x08 34 #define WDT_RESET_EN 0x10 35 #define WDT_TOUT_EN 0x20 36 37 #define DCR 0x14 38 39 #define WDTLR 0x18 40 #define WDT_LOCK 0x01 41 #define WDT_ENABLE 0x02 42 #define WDT_TOUT_CNF 0x03 43 44 #define MIN_TIME 1 45 #define MAX_TIME (10 * 60) /* 10 minutes */ 46 #define DEFAULT_TIME 60 47 48 static unsigned int timeout = DEFAULT_TIME; 49 module_param(timeout, uint, 0); 50 MODULE_PARM_DESC(timeout, 51 "Default Watchdog timer setting (" 52 __MODULE_STRING(DEFAULT_TIME) "s)." 53 "The range is from 1 to 600"); 54 55 static bool nowayout = WATCHDOG_NOWAYOUT; 56 module_param(nowayout, bool, 0); 57 MODULE_PARM_DESC(nowayout, 58 "Watchdog cannot be stopped once started (default=" 59 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 60 61 static u8 resetmode = 0x10; 62 module_param(resetmode, byte, 0); 63 MODULE_PARM_DESC(resetmode, 64 "Resetmode bits: 0x08 warm reset (cold reset otherwise), " 65 "0x10 reset enable, 0x20 disable toggle GPIO[4] (default=0x10)"); 66 67 static struct { 68 unsigned short sch_wdtba; 69 spinlock_t unlock_sequence; 70 #ifdef CONFIG_DEBUG_FS 71 struct dentry *debugfs; 72 #endif 73 } ie6xx_wdt_data; 74 75 /* 76 * This is needed to write to preload and reload registers 77 * struct ie6xx_wdt_data.unlock_sequence must be used 78 * to prevent sequence interrupts 79 */ 80 static void ie6xx_wdt_unlock_registers(void) 81 { 82 outb(0x80, ie6xx_wdt_data.sch_wdtba + RR0); 83 outb(0x86, ie6xx_wdt_data.sch_wdtba + RR0); 84 } 85 86 static int ie6xx_wdt_ping(struct watchdog_device *wdd) 87 { 88 spin_lock(&ie6xx_wdt_data.unlock_sequence); 89 ie6xx_wdt_unlock_registers(); 90 outb(WDT_RELOAD, ie6xx_wdt_data.sch_wdtba + RR1); 91 spin_unlock(&ie6xx_wdt_data.unlock_sequence); 92 return 0; 93 } 94 95 static int ie6xx_wdt_set_timeout(struct watchdog_device *wdd, unsigned int t) 96 { 97 u32 preload; 98 u64 clock; 99 u8 wdtcr; 100 101 /* Watchdog clock is PCI Clock (33MHz) */ 102 clock = 33000000; 103 /* and the preload value is loaded into [34:15] of the down counter */ 104 preload = (t * clock) >> 15; 105 /* 106 * Manual states preload must be one less. 107 * Does not wrap as t is at least 1 108 */ 109 preload -= 1; 110 111 spin_lock(&ie6xx_wdt_data.unlock_sequence); 112 113 /* Set ResetMode & Enable prescaler for range 10ms to 10 min */ 114 wdtcr = resetmode & 0x38; 115 outb(wdtcr, ie6xx_wdt_data.sch_wdtba + WDTCR); 116 117 ie6xx_wdt_unlock_registers(); 118 outl(0, ie6xx_wdt_data.sch_wdtba + PV1); 119 120 ie6xx_wdt_unlock_registers(); 121 outl(preload, ie6xx_wdt_data.sch_wdtba + PV2); 122 123 ie6xx_wdt_unlock_registers(); 124 outb(WDT_RELOAD | WDT_TOUT, ie6xx_wdt_data.sch_wdtba + RR1); 125 126 spin_unlock(&ie6xx_wdt_data.unlock_sequence); 127 128 wdd->timeout = t; 129 return 0; 130 } 131 132 static int ie6xx_wdt_start(struct watchdog_device *wdd) 133 { 134 ie6xx_wdt_set_timeout(wdd, wdd->timeout); 135 136 /* Enable the watchdog timer */ 137 spin_lock(&ie6xx_wdt_data.unlock_sequence); 138 outb(WDT_ENABLE, ie6xx_wdt_data.sch_wdtba + WDTLR); 139 spin_unlock(&ie6xx_wdt_data.unlock_sequence); 140 141 return 0; 142 } 143 144 static int ie6xx_wdt_stop(struct watchdog_device *wdd) 145 { 146 if (inb(ie6xx_wdt_data.sch_wdtba + WDTLR) & WDT_LOCK) 147 return -1; 148 149 /* Disable the watchdog timer */ 150 spin_lock(&ie6xx_wdt_data.unlock_sequence); 151 outb(0, ie6xx_wdt_data.sch_wdtba + WDTLR); 152 spin_unlock(&ie6xx_wdt_data.unlock_sequence); 153 154 return 0; 155 } 156 157 static const struct watchdog_info ie6xx_wdt_info = { 158 .identity = "Intel Atom E6xx Watchdog", 159 .options = WDIOF_SETTIMEOUT | 160 WDIOF_MAGICCLOSE | 161 WDIOF_KEEPALIVEPING, 162 }; 163 164 static const struct watchdog_ops ie6xx_wdt_ops = { 165 .owner = THIS_MODULE, 166 .start = ie6xx_wdt_start, 167 .stop = ie6xx_wdt_stop, 168 .ping = ie6xx_wdt_ping, 169 .set_timeout = ie6xx_wdt_set_timeout, 170 }; 171 172 static struct watchdog_device ie6xx_wdt_dev = { 173 .info = &ie6xx_wdt_info, 174 .ops = &ie6xx_wdt_ops, 175 .min_timeout = MIN_TIME, 176 .max_timeout = MAX_TIME, 177 }; 178 179 #ifdef CONFIG_DEBUG_FS 180 181 static int ie6xx_wdt_show(struct seq_file *s, void *unused) 182 { 183 seq_printf(s, "PV1 = 0x%08x\n", 184 inl(ie6xx_wdt_data.sch_wdtba + PV1)); 185 seq_printf(s, "PV2 = 0x%08x\n", 186 inl(ie6xx_wdt_data.sch_wdtba + PV2)); 187 seq_printf(s, "RR = 0x%08x\n", 188 inw(ie6xx_wdt_data.sch_wdtba + RR0)); 189 seq_printf(s, "WDTCR = 0x%08x\n", 190 inw(ie6xx_wdt_data.sch_wdtba + WDTCR)); 191 seq_printf(s, "DCR = 0x%08x\n", 192 inl(ie6xx_wdt_data.sch_wdtba + DCR)); 193 seq_printf(s, "WDTLR = 0x%08x\n", 194 inw(ie6xx_wdt_data.sch_wdtba + WDTLR)); 195 196 seq_printf(s, "\n"); 197 return 0; 198 } 199 200 DEFINE_SHOW_ATTRIBUTE(ie6xx_wdt); 201 202 static void ie6xx_wdt_debugfs_init(void) 203 { 204 /* /sys/kernel/debug/ie6xx_wdt */ 205 ie6xx_wdt_data.debugfs = debugfs_create_file("ie6xx_wdt", 206 S_IFREG | S_IRUGO, NULL, NULL, &ie6xx_wdt_fops); 207 } 208 209 static void ie6xx_wdt_debugfs_exit(void) 210 { 211 debugfs_remove(ie6xx_wdt_data.debugfs); 212 } 213 214 #else 215 static void ie6xx_wdt_debugfs_init(void) 216 { 217 } 218 219 static void ie6xx_wdt_debugfs_exit(void) 220 { 221 } 222 #endif 223 224 static int ie6xx_wdt_probe(struct platform_device *pdev) 225 { 226 struct resource *res; 227 u8 wdtlr; 228 int ret; 229 230 res = platform_get_resource(pdev, IORESOURCE_IO, 0); 231 if (!res) 232 return -ENODEV; 233 234 if (!request_region(res->start, resource_size(res), pdev->name)) { 235 dev_err(&pdev->dev, "Watchdog region 0x%llx already in use!\n", 236 (u64)res->start); 237 return -EBUSY; 238 } 239 240 ie6xx_wdt_data.sch_wdtba = res->start; 241 dev_dbg(&pdev->dev, "WDT = 0x%X\n", ie6xx_wdt_data.sch_wdtba); 242 243 ie6xx_wdt_dev.timeout = timeout; 244 watchdog_set_nowayout(&ie6xx_wdt_dev, nowayout); 245 ie6xx_wdt_dev.parent = &pdev->dev; 246 247 spin_lock_init(&ie6xx_wdt_data.unlock_sequence); 248 249 wdtlr = inb(ie6xx_wdt_data.sch_wdtba + WDTLR); 250 if (wdtlr & WDT_LOCK) 251 dev_warn(&pdev->dev, 252 "Watchdog Timer is Locked (Reg=0x%x)\n", wdtlr); 253 254 ie6xx_wdt_debugfs_init(); 255 256 ret = watchdog_register_device(&ie6xx_wdt_dev); 257 if (ret) 258 goto misc_register_error; 259 260 return 0; 261 262 misc_register_error: 263 ie6xx_wdt_debugfs_exit(); 264 release_region(res->start, resource_size(res)); 265 ie6xx_wdt_data.sch_wdtba = 0; 266 return ret; 267 } 268 269 static void ie6xx_wdt_remove(struct platform_device *pdev) 270 { 271 struct resource *res; 272 273 res = platform_get_resource(pdev, IORESOURCE_IO, 0); 274 ie6xx_wdt_stop(NULL); 275 watchdog_unregister_device(&ie6xx_wdt_dev); 276 ie6xx_wdt_debugfs_exit(); 277 release_region(res->start, resource_size(res)); 278 ie6xx_wdt_data.sch_wdtba = 0; 279 } 280 281 static struct platform_driver ie6xx_wdt_driver = { 282 .probe = ie6xx_wdt_probe, 283 .remove_new = ie6xx_wdt_remove, 284 .driver = { 285 .name = DRIVER_NAME, 286 }, 287 }; 288 289 static int __init ie6xx_wdt_init(void) 290 { 291 /* Check boot parameters to verify that their initial values */ 292 /* are in range. */ 293 if ((timeout < MIN_TIME) || 294 (timeout > MAX_TIME)) { 295 pr_err("Watchdog timer: value of timeout %d (dec) " 296 "is out of range from %d to %d (dec)\n", 297 timeout, MIN_TIME, MAX_TIME); 298 return -EINVAL; 299 } 300 301 return platform_driver_register(&ie6xx_wdt_driver); 302 } 303 304 static void __exit ie6xx_wdt_exit(void) 305 { 306 platform_driver_unregister(&ie6xx_wdt_driver); 307 } 308 309 late_initcall(ie6xx_wdt_init); 310 module_exit(ie6xx_wdt_exit); 311 312 MODULE_AUTHOR("Alexander Stein <alexander.stein@systec-electronic.com>"); 313 MODULE_DESCRIPTION("Intel Atom E6xx Watchdog Device Driver"); 314 MODULE_LICENSE("GPL"); 315 MODULE_ALIAS("platform:" DRIVER_NAME); 316