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) { 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(const struct dmi_system_id *id) 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 239 return err; 240 } 241 242 static const struct dmi_system_id se10_dmi_table[] __initconst = { 243 { 244 .ident = "LENOVO-SE10", 245 .matches = { 246 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 247 DMI_MATCH(DMI_PRODUCT_NAME, "12NH"), 248 }, 249 .callback = se10_create_platform_device, 250 }, 251 { 252 .ident = "LENOVO-SE10", 253 .matches = { 254 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 255 DMI_MATCH(DMI_PRODUCT_NAME, "12NJ"), 256 }, 257 .callback = se10_create_platform_device, 258 }, 259 { 260 .ident = "LENOVO-SE10", 261 .matches = { 262 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 263 DMI_MATCH(DMI_PRODUCT_NAME, "12NK"), 264 }, 265 .callback = se10_create_platform_device, 266 }, 267 { 268 .ident = "LENOVO-SE10", 269 .matches = { 270 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 271 DMI_MATCH(DMI_PRODUCT_NAME, "12NL"), 272 }, 273 .callback = se10_create_platform_device, 274 }, 275 { 276 .ident = "LENOVO-SE10", 277 .matches = { 278 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 279 DMI_MATCH(DMI_PRODUCT_NAME, "12NM"), 280 }, 281 .callback = se10_create_platform_device, 282 }, 283 {} 284 }; 285 MODULE_DEVICE_TABLE(dmi, se10_dmi_table); 286 287 static int __init se10_wdt_init(void) 288 { 289 if (!dmi_check_system(se10_dmi_table)) 290 return -ENODEV; 291 292 return platform_driver_register(&se10_wdt_driver); 293 } 294 295 static void __exit se10_wdt_exit(void) 296 { 297 if (se10_pdev) 298 platform_device_unregister(se10_pdev); 299 platform_driver_unregister(&se10_wdt_driver); 300 } 301 302 module_init(se10_wdt_init); 303 module_exit(se10_wdt_exit); 304 305 MODULE_LICENSE("GPL"); 306 MODULE_AUTHOR("David Ober<dober@lenovo.com>"); 307 MODULE_AUTHOR("Mark Pearson <mpearson-lenovo@squebb.ca>"); 308 MODULE_DESCRIPTION("WDT driver for Lenovo SE10"); 309