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 */ 44static unsigned short bram_base; 45static struct platform_device *se10_pdev; 46 47static int timeout; /* in seconds */ 48module_param(timeout, int, 0); 49MODULE_PARM_DESC(timeout, 50 "Watchdog timeout in seconds. 1 <= timeout <= 255, default=" 51 __MODULE_STRING(WATCHDOG_TIMEOUT) "."); 52 53static bool nowayout = WATCHDOG_NOWAYOUT; 54module_param(nowayout, bool, 0); 55MODULE_PARM_DESC(nowayout, 56 "Watchdog cannot be stopped once started (default=" 57 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 58 59struct se10_wdt { 60 struct watchdog_device wdd; 61}; 62 63static 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 73static 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 85static 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 92static void lpc_write(unsigned char index, unsigned char data) 93{ 94 outb(index, CFG_PORT); 95 outb(data, CFG_PORT + 1); 96} 97 98static unsigned char lpc_read(unsigned char index) 99{ 100 outb(index, CFG_PORT); 101 return inb(CFG_PORT + 1); 102} 103 104static int wdt_start(struct watchdog_device *wdog) 105{ 106 return set_bram(CUS_WDT_SWI, 0x80); 107} 108 109static 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 115static int wdt_stop(struct watchdog_device *wdog) 116{ 117 return set_bram(CUS_WDT_SWI, 0); 118} 119 120static 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 133static 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 142static const struct watchdog_info wdt_info = { 143 .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, 144 .identity = "Lenovo SE10 Watchdog", 145}; 146 147static 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 156static 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 170static 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 220static struct platform_driver se10_wdt_driver = { 221 .driver = { 222 .name = DRVNAME, 223 }, 224 .probe = se10_wdt_probe, 225}; 226 227static 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 242static 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}; 285MODULE_DEVICE_TABLE(dmi, se10_dmi_table); 286 287static 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 295static 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 302module_init(se10_wdt_init); 303module_exit(se10_wdt_exit); 304 305MODULE_LICENSE("GPL"); 306MODULE_AUTHOR("David Ober<dober@lenovo.com>"); 307MODULE_AUTHOR("Mark Pearson <mpearson-lenovo@squebb.ca>"); 308MODULE_DESCRIPTION("WDT driver for Lenovo SE10"); 309