1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * WDT driver for Lenovo SE30 device 4 */ 5 6 #define dev_fmt(fmt) KBUILD_MODNAME ": " fmt 7 8 #include <linux/io.h> 9 #include <linux/dmi.h> 10 #include <linux/delay.h> 11 #include <linux/iommu.h> 12 #include <linux/kernel.h> 13 #include <linux/module.h> 14 #include <linux/moduleparam.h> 15 #include <linux/platform_device.h> 16 #include <linux/watchdog.h> 17 18 #define IOREGION_OFFSET 4 /* Use EC port 1 */ 19 #define IOREGION_LENGTH 4 20 21 #define WATCHDOG_TIMEOUT 60 22 23 #define MIN_TIMEOUT 1 24 #define MAX_TIMEOUT 255 25 #define MAX_WAIT 10 26 27 static int timeout; /* in seconds */ 28 module_param(timeout, int, 0); 29 MODULE_PARM_DESC(timeout, 30 "Watchdog timeout in seconds. 1 <= timeout <= 255, default=" 31 __MODULE_STRING(WATCHDOG_TIMEOUT) "."); 32 33 static bool nowayout = WATCHDOG_NOWAYOUT; 34 module_param(nowayout, bool, 0); 35 MODULE_PARM_DESC(nowayout, 36 "Watchdog cannot be stopped once started (default=" 37 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 38 39 #define LNV_SE30_NAME "lenovo-se30-wdt" 40 #define LNV_SE30_ID 0x0110 41 #define CHIPID_MASK 0xFFF0 42 43 #define CHIPID_REG 0x20 44 #define SIO_REG 0x2e 45 #define LDN_REG 0x07 46 #define UNLOCK_KEY 0x87 47 #define LOCK_KEY 0xAA 48 #define LD_NUM_SHM 0x0F 49 #define LD_BASE_ADDR 0xF8 50 51 #define WDT_MODULE 0x10 52 #define WDT_CFG_INDEX 0x15 /* WD configuration register */ 53 #define WDT_CNT_INDEX 0x16 /* WD timer count register */ 54 #define WDT_CFG_RESET 0x2 55 56 /* Host Interface WIN2 offset definition */ 57 #define SHM_WIN_SIZE 0xFF 58 #define SHM_WIN_MOD_OFFSET 0x01 59 #define SHM_WIN_CMD_OFFSET 0x02 60 #define SHM_WIN_SEL_OFFSET 0x03 61 #define SHM_WIN_CTL_OFFSET 0x04 62 #define VAL_SHM_WIN_CTRL_WR 0x40 63 #define VAL_SHM_WIN_CTRL_RD 0x80 64 #define SHM_WIN_ID_OFFSET 0x08 65 #define SHM_WIN_DAT_OFFSET 0x10 66 67 struct nct6692_reg { 68 unsigned char mod; 69 unsigned char cmd; 70 unsigned char sel; 71 unsigned int idx; 72 }; 73 74 /* Watchdog is based on NCT6692 device */ 75 struct lenovo_se30_wdt { 76 unsigned char __iomem *shm_base_addr; 77 struct nct6692_reg wdt_cfg; 78 struct nct6692_reg wdt_cnt; 79 struct watchdog_device wdt; 80 }; 81 82 static inline void superio_outb(int ioreg, int reg, int val) 83 { 84 outb(reg, ioreg); 85 outb(val, ioreg + 1); 86 } 87 88 static inline int superio_inb(int ioreg, int reg) 89 { 90 outb(reg, ioreg); 91 return inb(ioreg + 1); 92 } 93 94 static inline int superio_enter(int key, int addr, const char *name) 95 { 96 if (!request_muxed_region(addr, 2, name)) { 97 pr_err("I/O address 0x%04x already in use\n", addr); 98 return -EBUSY; 99 } 100 outb(key, addr); /* Enter extended function mode */ 101 outb(key, addr); /* Again according to manual */ 102 103 return 0; 104 } 105 106 static inline void superio_exit(int key, int addr) 107 { 108 outb(key, addr); /* Leave extended function mode */ 109 release_region(addr, 2); 110 } 111 112 static int shm_get_ready(unsigned char __iomem *shm_base_addr, 113 const struct nct6692_reg *reg) 114 { 115 unsigned char pre_id, new_id; 116 int loop = 0; 117 118 iowrite8(reg->mod, shm_base_addr + SHM_WIN_MOD_OFFSET); 119 iowrite8(reg->cmd, shm_base_addr + SHM_WIN_CMD_OFFSET); 120 iowrite8(reg->sel, shm_base_addr + SHM_WIN_SEL_OFFSET); 121 122 pre_id = ioread8(shm_base_addr + SHM_WIN_ID_OFFSET); 123 iowrite8(VAL_SHM_WIN_CTRL_RD, shm_base_addr + SHM_WIN_CTL_OFFSET); 124 125 /* Loop checking when interface is ready */ 126 while (loop < MAX_WAIT) { 127 new_id = ioread8(shm_base_addr + SHM_WIN_ID_OFFSET); 128 if (new_id != pre_id) 129 return 0; 130 loop++; 131 usleep_range(10, 125); 132 } 133 return -ETIMEDOUT; 134 } 135 136 static int read_shm_win(unsigned char __iomem *shm_base_addr, 137 const struct nct6692_reg *reg, 138 unsigned char idx_offset, 139 unsigned char *data) 140 { 141 int err = shm_get_ready(shm_base_addr, reg); 142 143 if (err) 144 return err; 145 *data = ioread8(shm_base_addr + SHM_WIN_DAT_OFFSET + reg->idx + idx_offset); 146 return 0; 147 } 148 149 static int write_shm_win(unsigned char __iomem *shm_base_addr, 150 const struct nct6692_reg *reg, 151 unsigned char idx_offset, 152 unsigned char val) 153 { 154 int err = shm_get_ready(shm_base_addr, reg); 155 156 if (err) 157 return err; 158 iowrite8(val, shm_base_addr + SHM_WIN_DAT_OFFSET + reg->idx + idx_offset); 159 iowrite8(VAL_SHM_WIN_CTRL_WR, shm_base_addr + SHM_WIN_CTL_OFFSET); 160 err = shm_get_ready(shm_base_addr, reg); 161 return err; 162 } 163 164 static int lenovo_se30_wdt_enable(struct lenovo_se30_wdt *data, unsigned int timeout) 165 { 166 if (timeout) { 167 int err = write_shm_win(data->shm_base_addr, &data->wdt_cfg, 0, WDT_CFG_RESET); 168 169 if (err) 170 return err; 171 } 172 return write_shm_win(data->shm_base_addr, &data->wdt_cnt, 0, timeout); 173 } 174 175 static int lenovo_se30_wdt_start(struct watchdog_device *wdog) 176 { 177 struct lenovo_se30_wdt *data = watchdog_get_drvdata(wdog); 178 179 return lenovo_se30_wdt_enable(data, wdog->timeout); 180 } 181 182 static int lenovo_se30_wdt_stop(struct watchdog_device *wdog) 183 { 184 struct lenovo_se30_wdt *data = watchdog_get_drvdata(wdog); 185 186 return lenovo_se30_wdt_enable(data, 0); 187 } 188 189 static unsigned int lenovo_se30_wdt_get_timeleft(struct watchdog_device *wdog) 190 { 191 struct lenovo_se30_wdt *data = watchdog_get_drvdata(wdog); 192 unsigned char timeleft; 193 int err; 194 195 err = read_shm_win(data->shm_base_addr, &data->wdt_cnt, 0, &timeleft); 196 if (err) 197 return 0; 198 return timeleft; 199 } 200 201 static int lenovo_se30_wdt_ping(struct watchdog_device *wdt) 202 { 203 struct lenovo_se30_wdt *data = watchdog_get_drvdata(wdt); 204 int err = 0; 205 206 /* 207 * Device does not support refreshing WDT_TIMER_REG register when 208 * the watchdog is active. Need to disable, feed and enable again 209 */ 210 err = lenovo_se30_wdt_enable(data, 0); 211 if (err) 212 return err; 213 214 err = write_shm_win(data->shm_base_addr, &data->wdt_cnt, 0, wdt->timeout); 215 if (!err) 216 err = lenovo_se30_wdt_enable(data, wdt->timeout); 217 218 return err; 219 } 220 221 static const struct watchdog_info lenovo_se30_wdt_info = { 222 .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | 223 WDIOF_MAGICCLOSE, 224 .identity = "Lenovo SE30 watchdog", 225 }; 226 227 static const struct watchdog_ops lenovo_se30_wdt_ops = { 228 .owner = THIS_MODULE, 229 .start = lenovo_se30_wdt_start, 230 .stop = lenovo_se30_wdt_stop, 231 .ping = lenovo_se30_wdt_ping, 232 .get_timeleft = lenovo_se30_wdt_get_timeleft, 233 }; 234 235 static int lenovo_se30_wdt_probe(struct platform_device *pdev) 236 { 237 struct device *dev = &pdev->dev; 238 struct lenovo_se30_wdt *priv; 239 unsigned long base_phys; 240 unsigned short val; 241 int err; 242 243 err = superio_enter(UNLOCK_KEY, SIO_REG, LNV_SE30_NAME); 244 if (err) 245 return err; 246 247 val = superio_inb(SIO_REG, CHIPID_REG) << 8; 248 val |= superio_inb(SIO_REG, CHIPID_REG + 1); 249 250 if ((val & CHIPID_MASK) != LNV_SE30_ID) { 251 superio_exit(LOCK_KEY, SIO_REG); 252 return -ENODEV; 253 } 254 255 superio_outb(SIO_REG, LDN_REG, LD_NUM_SHM); 256 base_phys = (superio_inb(SIO_REG, LD_BASE_ADDR) | 257 (superio_inb(SIO_REG, LD_BASE_ADDR + 1) << 8) | 258 (superio_inb(SIO_REG, LD_BASE_ADDR + 2) << 16) | 259 (superio_inb(SIO_REG, LD_BASE_ADDR + 3) << 24)) & 260 0xFFFFFFFF; 261 262 superio_exit(LOCK_KEY, SIO_REG); 263 if (base_phys == 0xFFFFFFFF || base_phys == 0) 264 return -ENODEV; 265 266 priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); 267 if (!priv) 268 return -ENOMEM; 269 270 if (!devm_request_mem_region(dev, base_phys, SHM_WIN_SIZE, LNV_SE30_NAME)) 271 return -EBUSY; 272 273 priv->shm_base_addr = devm_ioremap(dev, base_phys, SHM_WIN_SIZE); 274 if (!priv->shm_base_addr) 275 return -ENOMEM; 276 277 priv->wdt_cfg.mod = WDT_MODULE; 278 priv->wdt_cfg.idx = WDT_CFG_INDEX; 279 priv->wdt_cnt.mod = WDT_MODULE; 280 priv->wdt_cnt.idx = WDT_CNT_INDEX; 281 282 priv->wdt.ops = &lenovo_se30_wdt_ops; 283 priv->wdt.info = &lenovo_se30_wdt_info; 284 priv->wdt.timeout = WATCHDOG_TIMEOUT; /* Set default timeout */ 285 priv->wdt.min_timeout = MIN_TIMEOUT; 286 priv->wdt.max_timeout = MAX_TIMEOUT; 287 priv->wdt.parent = dev; 288 289 watchdog_init_timeout(&priv->wdt, timeout, dev); 290 watchdog_set_drvdata(&priv->wdt, priv); 291 watchdog_set_nowayout(&priv->wdt, nowayout); 292 watchdog_stop_on_reboot(&priv->wdt); 293 watchdog_stop_on_unregister(&priv->wdt); 294 295 return devm_watchdog_register_device(dev, &priv->wdt); 296 } 297 298 static struct platform_device *pdev; 299 300 static struct platform_driver lenovo_se30_wdt_driver = { 301 .driver = { 302 .name = LNV_SE30_NAME, 303 }, 304 .probe = lenovo_se30_wdt_probe, 305 }; 306 307 static int lenovo_se30_create_platform_device(const struct dmi_system_id *id) 308 { 309 int err; 310 311 pdev = platform_device_alloc(LNV_SE30_NAME, -1); 312 if (!pdev) 313 return -ENOMEM; 314 315 err = platform_device_add(pdev); 316 if (err) 317 platform_device_put(pdev); 318 319 return err; 320 } 321 322 static const struct dmi_system_id lenovo_se30_wdt_dmi_table[] __initconst = { 323 { 324 .ident = "LENOVO-SE30", 325 .matches = { 326 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 327 DMI_MATCH(DMI_PRODUCT_NAME, "11NA"), 328 }, 329 .callback = lenovo_se30_create_platform_device, 330 }, 331 { 332 .ident = "LENOVO-SE30", 333 .matches = { 334 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 335 DMI_MATCH(DMI_PRODUCT_NAME, "11NB"), 336 }, 337 .callback = lenovo_se30_create_platform_device, 338 }, 339 { 340 .ident = "LENOVO-SE30", 341 .matches = { 342 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 343 DMI_MATCH(DMI_PRODUCT_NAME, "11NC"), 344 }, 345 .callback = lenovo_se30_create_platform_device, 346 }, 347 { 348 .ident = "LENOVO-SE30", 349 .matches = { 350 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 351 DMI_MATCH(DMI_PRODUCT_NAME, "11NH"), 352 }, 353 .callback = lenovo_se30_create_platform_device, 354 }, 355 { 356 .ident = "LENOVO-SE30", 357 .matches = { 358 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 359 DMI_MATCH(DMI_PRODUCT_NAME, "11NJ"), 360 }, 361 .callback = lenovo_se30_create_platform_device, 362 }, 363 { 364 .ident = "LENOVO-SE30", 365 .matches = { 366 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 367 DMI_MATCH(DMI_PRODUCT_NAME, "11NK"), 368 }, 369 .callback = lenovo_se30_create_platform_device, 370 }, 371 {} 372 }; 373 MODULE_DEVICE_TABLE(dmi, lenovo_se30_wdt_dmi_table); 374 375 static int __init lenovo_se30_wdt_init(void) 376 { 377 if (!dmi_check_system(lenovo_se30_wdt_dmi_table)) 378 return -ENODEV; 379 380 return platform_driver_register(&lenovo_se30_wdt_driver); 381 } 382 383 static void __exit lenovo_se30_wdt_exit(void) 384 { 385 if (pdev) 386 platform_device_unregister(pdev); 387 platform_driver_unregister(&lenovo_se30_wdt_driver); 388 } 389 390 module_init(lenovo_se30_wdt_init); 391 module_exit(lenovo_se30_wdt_exit); 392 393 MODULE_AUTHOR("Mark Pearson <mpearson-lenovo@squebb.ca>"); 394 MODULE_AUTHOR("David Ober <dober@lenovo.com>"); 395 MODULE_DESCRIPTION("Lenovo SE30 watchdog driver"); 396 MODULE_LICENSE("GPL"); 397