309 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*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 0x6C17#define CMD_PORT 0x6C18#define DATA_PORT 0x6819#define OUTBUF_FULL 0x0120#define INBUF_EMPTY 0x0221#define CFG_LDN 0x0722#define CFG_BRAM_LDN 0x10 /* for BRAM Base */23#define CFG_PORT 0x2E24#define CFG_SIZE 225#define CMD_SIZE 426#define BRAM_SIZE 227 28#define UNLOCK_KEY 0x8729#define LOCK_KEY 0xAA30 31#define CUS_WDT_SWI 0x1A32#define CUS_WDT_CFG 0x1B33#define CUS_WDT_FEED 0xB034#define CUS_WDT_CNT 0xB135 36#define DRVNAME "lenovo-se10-wdt"37 38/*The timeout range is 1-255 seconds*/39#define MIN_TIMEOUT 140#define MAX_TIMEOUT 25541#define MAX_WAIT 1042 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