1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * WDT driver for Lenovo SE10. 4 */ 5 6 #include <linux/delay.h> 7 #include <linux/dmi.h> 8 #include <linux/io.h> 9 #include <linux/module.h> 10 #include <linux/moduleparam.h> 11 #include <linux/platform_device.h> 12 #include <linux/string.h> 13 #include <linux/types.h> 14 #include <linux/watchdog.h> 15 16 #define STATUS_PORT 0x6C 17 #define CMD_PORT 0x6C 18 #define DATA_PORT 0x68 19 #define OUTBUF_FULL 0x01 20 #define INBUF_EMPTY 0x02 21 #define CFG_LDN 0x07 22 #define CFG_BRAM_LDN 0x10 /* for BRAM Base */ 23 #define CFG_PORT 0x2E 24 #define CFG_SIZE 2 25 #define CMD_SIZE 4 26 #define BRAM_SIZE 2 27 28 #define UNLOCK_KEY 0x87 29 #define LOCK_KEY 0xAA 30 31 #define CUS_WDT_SWI 0x1A 32 #define CUS_WDT_CFG 0x1B 33 #define CUS_WDT_FEED 0xB0 34 #define CUS_WDT_CNT 0xB1 35 36 #define DRVNAME "lenovo-se10-wdt" 37 38 /*The timeout range is 1-255 seconds*/ 39 #define MIN_TIMEOUT 1 40 #define MAX_TIMEOUT 255 41 #define MAX_WAIT 10 42 43 #define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */ 44 static unsigned short bram_base; 45 static struct platform_device *se10_pdev; 46 47 static int timeout; /* in seconds */ 48 module_param(timeout, int, 0); 49 MODULE_PARM_DESC(timeout, 50 "Watchdog timeout in seconds. 1 <= timeout <= 255, default=" 51 __MODULE_STRING(WATCHDOG_TIMEOUT) "."); 52 53 static bool nowayout = WATCHDOG_NOWAYOUT; 54 module_param(nowayout, bool, 0); 55 MODULE_PARM_DESC(nowayout, 56 "Watchdog cannot be stopped once started (default=" 57 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 58 59 struct se10_wdt { 60 struct watchdog_device wdd; 61 }; 62 63 static int set_bram(unsigned char offset, unsigned char val) 64 { 65 if (!request_muxed_region(bram_base, BRAM_SIZE, DRVNAME)) 66 return -EBUSY; 67 outb(offset, bram_base); 68 outb(val, bram_base + 1); 69 release_region(bram_base, BRAM_SIZE); 70 return 0; 71 } 72 73 static void wait_for_buffer(int condition) 74 { 75 int loop = 0; 76 77 while (1) { 78 if (inb(STATUS_PORT) & condition || loop > MAX_WAIT) 79 break; 80 loop++; 81 usleep_range(10, 125); 82 } 83 } 84 85 static void send_cmd(unsigned char cmd) 86 { 87 wait_for_buffer(INBUF_EMPTY); 88 outb(cmd, CMD_PORT); 89 wait_for_buffer(INBUF_EMPTY); 90 } 91 92 static void lpc_write(unsigned char index, unsigned char data) 93 { 94 outb(index, CFG_PORT); 95 outb(data, CFG_PORT + 1); 96 } 97 98 static unsigned char lpc_read(unsigned char index) 99 { 100 outb(index, CFG_PORT); 101 return inb(CFG_PORT + 1); 102 } 103 104 static int wdt_start(struct watchdog_device *wdog) 105 { 106 return set_bram(CUS_WDT_SWI, 0x80); 107 } 108 109 static int wdt_set_timeout(struct watchdog_device *wdog, unsigned int timeout) 110 { 111 wdog->timeout = timeout; 112 return set_bram(CUS_WDT_CFG, wdog->timeout); 113 } 114 115 static int wdt_stop(struct watchdog_device *wdog) 116 { 117 return set_bram(CUS_WDT_SWI, 0); 118 } 119 120 static unsigned int wdt_get_time(struct watchdog_device *wdog) 121 { 122 unsigned char time; 123 124 if (!request_muxed_region(CMD_PORT, CMD_SIZE, DRVNAME)) 125 return -EBUSY; 126 send_cmd(CUS_WDT_CNT); 127 wait_for_buffer(OUTBUF_FULL); 128 time = inb(DATA_PORT); 129 release_region(CMD_PORT, CMD_SIZE); 130 return time; 131 } 132 133 static int wdt_ping(struct watchdog_device *wdog) 134 { 135 if (!request_muxed_region(CMD_PORT, CMD_SIZE, DRVNAME)) 136 return -EBUSY; 137 send_cmd(CUS_WDT_FEED); 138 release_region(CMD_PORT, CMD_SIZE); 139 return 0; 140 } 141 142 static const struct watchdog_info wdt_info = { 143 .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, 144 .identity = "Lenovo SE10 Watchdog", 145 }; 146 147 static const struct watchdog_ops se10_wdt_ops = { 148 .owner = THIS_MODULE, 149 .start = wdt_start, 150 .stop = wdt_stop, 151 .ping = wdt_ping, 152 .set_timeout = wdt_set_timeout, 153 .get_timeleft = wdt_get_time, 154 }; 155 156 static unsigned int get_chipID(void) 157 { 158 unsigned char msb, lsb; 159 160 outb(UNLOCK_KEY, CFG_PORT); 161 outb(0x01, CFG_PORT); 162 outb(0x55, CFG_PORT); 163 outb(0x55, CFG_PORT); 164 msb = lpc_read(0x20); 165 lsb = lpc_read(0x21); 166 outb(LOCK_KEY, CFG_PORT); 167 return (msb * 256 + lsb); 168 } 169 170 static int se10_wdt_probe(struct platform_device *pdev) 171 { 172 struct device *dev = &pdev->dev; 173 struct se10_wdt *priv; 174 unsigned int chip_id; 175 int ret; 176 177 if (!request_muxed_region(CFG_PORT, CFG_SIZE, DRVNAME)) 178 return -EBUSY; 179 180 chip_id = get_chipID(); 181 if (chip_id != 0x5632 && chip_id != 0x5652) { 182 release_region(CFG_PORT, CFG_SIZE); 183 return -ENODEV; 184 } 185 186 lpc_write(CFG_LDN, CFG_BRAM_LDN); 187 bram_base = (lpc_read(0x60) << 8) | lpc_read(0x61); 188 release_region(CFG_PORT, CFG_SIZE); 189 190 dev_info(dev, "Found Lenovo SE10 0x%x\n", chip_id); 191 192 priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); 193 if (!priv) 194 return -ENOMEM; 195 196 watchdog_set_drvdata(&priv->wdd, priv); 197 198 priv->wdd.parent = dev; 199 priv->wdd.info = &wdt_info; 200 priv->wdd.ops = &se10_wdt_ops; 201 priv->wdd.timeout = WATCHDOG_TIMEOUT; /* Set default timeout */ 202 priv->wdd.min_timeout = MIN_TIMEOUT; 203 priv->wdd.max_timeout = MAX_TIMEOUT; 204 205 set_bram(CUS_WDT_CFG, WATCHDOG_TIMEOUT); /* Set time to default */ 206 207 watchdog_init_timeout(&priv->wdd, timeout, dev); 208 watchdog_set_nowayout(&priv->wdd, nowayout); 209 watchdog_stop_on_reboot(&priv->wdd); 210 watchdog_stop_on_unregister(&priv->wdd); 211 212 ret = devm_watchdog_register_device(dev, &priv->wdd); 213 214 dev_dbg(&pdev->dev, "initialized. timeout=%d sec (nowayout=%d)\n", 215 priv->wdd.timeout, nowayout); 216 217 return ret; 218 } 219 220 static struct platform_driver se10_wdt_driver = { 221 .driver = { 222 .name = DRVNAME, 223 }, 224 .probe = se10_wdt_probe, 225 }; 226 227 static int se10_create_platform_device(void) 228 { 229 int err; 230 231 se10_pdev = platform_device_alloc("lenovo-se10-wdt", -1); 232 if (!se10_pdev) 233 return -ENOMEM; 234 235 err = platform_device_add(se10_pdev); 236 if (err) { 237 platform_device_put(se10_pdev); 238 se10_pdev = NULL; 239 } 240 return err; 241 } 242 243 static const struct dmi_system_id se10_dmi_table[] __initconst = { 244 { 245 .ident = "LENOVO-SE10", 246 .matches = { 247 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 248 DMI_MATCH(DMI_PRODUCT_NAME, "12NH"), 249 }, 250 }, 251 { 252 .ident = "LENOVO-SE10", 253 .matches = { 254 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 255 DMI_MATCH(DMI_PRODUCT_NAME, "12NJ"), 256 }, 257 }, 258 { 259 .ident = "LENOVO-SE10", 260 .matches = { 261 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 262 DMI_MATCH(DMI_PRODUCT_NAME, "12NK"), 263 }, 264 }, 265 { 266 .ident = "LENOVO-SE10", 267 .matches = { 268 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 269 DMI_MATCH(DMI_PRODUCT_NAME, "12NL"), 270 }, 271 }, 272 { 273 .ident = "LENOVO-SE10", 274 .matches = { 275 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 276 DMI_MATCH(DMI_PRODUCT_NAME, "12NM"), 277 }, 278 }, 279 { 280 .ident = "LENOVO-SE10-G2", 281 .matches = { 282 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 283 DMI_MATCH(DMI_PRODUCT_NAME, "13LJ"), 284 }, 285 }, 286 { 287 .ident = "LENOVO-SE10-G2", 288 .matches = { 289 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 290 DMI_MATCH(DMI_PRODUCT_NAME, "13LK"), 291 }, 292 }, 293 { 294 .ident = "LENOVO-SE10-G2", 295 .matches = { 296 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 297 DMI_MATCH(DMI_PRODUCT_NAME, "13S1"), 298 }, 299 }, 300 { 301 .ident = "LENOVO-SE10-G2", 302 .matches = { 303 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 304 DMI_MATCH(DMI_PRODUCT_NAME, "13S2"), 305 }, 306 }, 307 { 308 .ident = "LENOVO-SE10-G2", 309 .matches = { 310 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 311 DMI_MATCH(DMI_PRODUCT_NAME, "13S3"), 312 }, 313 }, 314 { 315 .ident = "LENOVO-SE10-G2", 316 .matches = { 317 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 318 DMI_MATCH(DMI_PRODUCT_NAME, "13S4"), 319 }, 320 }, 321 { 322 .ident = "LENOVO-SE10-G2", 323 .matches = { 324 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 325 DMI_MATCH(DMI_PRODUCT_NAME, "13S5"), 326 }, 327 }, 328 { 329 .ident = "LENOVO-SE10-G2", 330 .matches = { 331 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 332 DMI_MATCH(DMI_PRODUCT_NAME, "13S6"), 333 }, 334 }, 335 {} 336 }; 337 MODULE_DEVICE_TABLE(dmi, se10_dmi_table); 338 339 static int __init se10_wdt_init(void) 340 { 341 int err; 342 343 if (!dmi_check_system(se10_dmi_table)) 344 return -ENODEV; 345 346 err = platform_driver_register(&se10_wdt_driver); 347 if (err) 348 return err; 349 350 err = se10_create_platform_device(); 351 if (err) 352 platform_driver_unregister(&se10_wdt_driver); 353 354 return err; 355 } 356 357 static void __exit se10_wdt_exit(void) 358 { 359 if (se10_pdev) 360 platform_device_unregister(se10_pdev); 361 platform_driver_unregister(&se10_wdt_driver); 362 } 363 364 module_init(se10_wdt_init); 365 module_exit(se10_wdt_exit); 366 367 MODULE_LICENSE("GPL"); 368 MODULE_AUTHOR("David Ober<dober@lenovo.com>"); 369 MODULE_AUTHOR("Mark Pearson <mpearson-lenovo@squebb.ca>"); 370 MODULE_DESCRIPTION("WDT driver for Lenovo SE10"); 371