ie6xx_wdt.c (7602B)
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 48static unsigned int timeout = DEFAULT_TIME; 49module_param(timeout, uint, 0); 50MODULE_PARM_DESC(timeout, 51 "Default Watchdog timer setting (" 52 __MODULE_STRING(DEFAULT_TIME) "s)." 53 "The range is from 1 to 600"); 54 55static bool nowayout = WATCHDOG_NOWAYOUT; 56module_param(nowayout, bool, 0); 57MODULE_PARM_DESC(nowayout, 58 "Watchdog cannot be stopped once started (default=" 59 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 60 61static u8 resetmode = 0x10; 62module_param(resetmode, byte, 0); 63MODULE_PARM_DESC(resetmode, 64 "Resetmode bits: 0x08 warm reset (cold reset otherwise), " 65 "0x10 reset enable, 0x20 disable toggle GPIO[4] (default=0x10)"); 66 67static 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 */ 80static 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 86static 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 95static 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 132static 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 144static 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 157static 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 164static 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 172static 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 181static 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 200DEFINE_SHOW_ATTRIBUTE(ie6xx_wdt); 201 202static 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 209static void ie6xx_wdt_debugfs_exit(void) 210{ 211 debugfs_remove(ie6xx_wdt_data.debugfs); 212} 213 214#else 215static void ie6xx_wdt_debugfs_init(void) 216{ 217} 218 219static void ie6xx_wdt_debugfs_exit(void) 220{ 221} 222#endif 223 224static 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 262misc_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 269static int 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 return 0; 281} 282 283static struct platform_driver ie6xx_wdt_driver = { 284 .probe = ie6xx_wdt_probe, 285 .remove = ie6xx_wdt_remove, 286 .driver = { 287 .name = DRIVER_NAME, 288 }, 289}; 290 291static int __init ie6xx_wdt_init(void) 292{ 293 /* Check boot parameters to verify that their initial values */ 294 /* are in range. */ 295 if ((timeout < MIN_TIME) || 296 (timeout > MAX_TIME)) { 297 pr_err("Watchdog timer: value of timeout %d (dec) " 298 "is out of range from %d to %d (dec)\n", 299 timeout, MIN_TIME, MAX_TIME); 300 return -EINVAL; 301 } 302 303 return platform_driver_register(&ie6xx_wdt_driver); 304} 305 306static void __exit ie6xx_wdt_exit(void) 307{ 308 platform_driver_unregister(&ie6xx_wdt_driver); 309} 310 311late_initcall(ie6xx_wdt_init); 312module_exit(ie6xx_wdt_exit); 313 314MODULE_AUTHOR("Alexander Stein <alexander.stein@systec-electronic.com>"); 315MODULE_DESCRIPTION("Intel Atom E6xx Watchdog Device Driver"); 316MODULE_LICENSE("GPL"); 317MODULE_ALIAS("platform:" DRIVER_NAME);