Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

watchdog: lenovo_se30_wdt: Watchdog driver for Lenovo SE30 platform

Watchdog driver implementation for Lenovo SE30 platform.

Signed-off-by: Mark Pearson <mpearson-lenovo@squebb.ca>
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
Link: https://lore.kernel.org/r/20250131204855.1827-1-mpearson-lenovo@squebb.ca
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Wim Van Sebroeck <wim@linux-watchdog.org>

authored by

Mark Pearson and committed by
Wim Van Sebroeck
c284153a 331c8349

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