| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396 |
- // SPDX-License-Identifier: GPL-2.0-or-later
- /*
- * WDT driver for Lenovo SE30 device
- */
- #define dev_fmt(fmt) KBUILD_MODNAME ": " fmt
- #include <linux/io.h>
- #include <linux/dmi.h>
- #include <linux/delay.h>
- #include <linux/iommu.h>
- #include <linux/kernel.h>
- #include <linux/module.h>
- #include <linux/moduleparam.h>
- #include <linux/platform_device.h>
- #include <linux/watchdog.h>
- #define IOREGION_OFFSET 4 /* Use EC port 1 */
- #define IOREGION_LENGTH 4
- #define WATCHDOG_TIMEOUT 60
- #define MIN_TIMEOUT 1
- #define MAX_TIMEOUT 255
- #define MAX_WAIT 10
- static int timeout; /* in seconds */
- module_param(timeout, int, 0);
- MODULE_PARM_DESC(timeout,
- "Watchdog timeout in seconds. 1 <= timeout <= 255, default="
- __MODULE_STRING(WATCHDOG_TIMEOUT) ".");
- static bool nowayout = WATCHDOG_NOWAYOUT;
- module_param(nowayout, bool, 0);
- MODULE_PARM_DESC(nowayout,
- "Watchdog cannot be stopped once started (default="
- __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
- #define LNV_SE30_NAME "lenovo-se30-wdt"
- #define LNV_SE30_ID 0x0110
- #define CHIPID_MASK 0xFFF0
- #define CHIPID_REG 0x20
- #define SIO_REG 0x2e
- #define LDN_REG 0x07
- #define UNLOCK_KEY 0x87
- #define LOCK_KEY 0xAA
- #define LD_NUM_SHM 0x0F
- #define LD_BASE_ADDR 0xF8
- #define WDT_MODULE 0x10
- #define WDT_CFG_INDEX 0x15 /* WD configuration register */
- #define WDT_CNT_INDEX 0x16 /* WD timer count register */
- #define WDT_CFG_RESET 0x2
- /* Host Interface WIN2 offset definition */
- #define SHM_WIN_SIZE 0xFF
- #define SHM_WIN_MOD_OFFSET 0x01
- #define SHM_WIN_CMD_OFFSET 0x02
- #define SHM_WIN_SEL_OFFSET 0x03
- #define SHM_WIN_CTL_OFFSET 0x04
- #define VAL_SHM_WIN_CTRL_WR 0x40
- #define VAL_SHM_WIN_CTRL_RD 0x80
- #define SHM_WIN_ID_OFFSET 0x08
- #define SHM_WIN_DAT_OFFSET 0x10
- struct nct6692_reg {
- unsigned char mod;
- unsigned char cmd;
- unsigned char sel;
- unsigned int idx;
- };
- /* Watchdog is based on NCT6692 device */
- struct lenovo_se30_wdt {
- unsigned char __iomem *shm_base_addr;
- struct nct6692_reg wdt_cfg;
- struct nct6692_reg wdt_cnt;
- struct watchdog_device wdt;
- };
- static inline void superio_outb(int ioreg, int reg, int val)
- {
- outb(reg, ioreg);
- outb(val, ioreg + 1);
- }
- static inline int superio_inb(int ioreg, int reg)
- {
- outb(reg, ioreg);
- return inb(ioreg + 1);
- }
- static inline int superio_enter(int key, int addr, const char *name)
- {
- if (!request_muxed_region(addr, 2, name)) {
- pr_err("I/O address 0x%04x already in use\n", addr);
- return -EBUSY;
- }
- outb(key, addr); /* Enter extended function mode */
- outb(key, addr); /* Again according to manual */
- return 0;
- }
- static inline void superio_exit(int key, int addr)
- {
- outb(key, addr); /* Leave extended function mode */
- release_region(addr, 2);
- }
- static int shm_get_ready(unsigned char __iomem *shm_base_addr,
- const struct nct6692_reg *reg)
- {
- unsigned char pre_id, new_id;
- int loop = 0;
- iowrite8(reg->mod, shm_base_addr + SHM_WIN_MOD_OFFSET);
- iowrite8(reg->cmd, shm_base_addr + SHM_WIN_CMD_OFFSET);
- iowrite8(reg->sel, shm_base_addr + SHM_WIN_SEL_OFFSET);
- pre_id = ioread8(shm_base_addr + SHM_WIN_ID_OFFSET);
- iowrite8(VAL_SHM_WIN_CTRL_RD, shm_base_addr + SHM_WIN_CTL_OFFSET);
- /* Loop checking when interface is ready */
- while (loop < MAX_WAIT) {
- new_id = ioread8(shm_base_addr + SHM_WIN_ID_OFFSET);
- if (new_id != pre_id)
- return 0;
- loop++;
- usleep_range(10, 125);
- }
- return -ETIMEDOUT;
- }
- static int read_shm_win(unsigned char __iomem *shm_base_addr,
- const struct nct6692_reg *reg,
- unsigned char idx_offset,
- unsigned char *data)
- {
- int err = shm_get_ready(shm_base_addr, reg);
- if (err)
- return err;
- *data = ioread8(shm_base_addr + SHM_WIN_DAT_OFFSET + reg->idx + idx_offset);
- return 0;
- }
- static int write_shm_win(unsigned char __iomem *shm_base_addr,
- const struct nct6692_reg *reg,
- unsigned char idx_offset,
- unsigned char val)
- {
- int err = shm_get_ready(shm_base_addr, reg);
- if (err)
- return err;
- iowrite8(val, shm_base_addr + SHM_WIN_DAT_OFFSET + reg->idx + idx_offset);
- iowrite8(VAL_SHM_WIN_CTRL_WR, shm_base_addr + SHM_WIN_CTL_OFFSET);
- err = shm_get_ready(shm_base_addr, reg);
- return err;
- }
- static int lenovo_se30_wdt_enable(struct lenovo_se30_wdt *data, unsigned int timeout)
- {
- if (timeout) {
- int err = write_shm_win(data->shm_base_addr, &data->wdt_cfg, 0, WDT_CFG_RESET);
- if (err)
- return err;
- }
- return write_shm_win(data->shm_base_addr, &data->wdt_cnt, 0, timeout);
- }
- static int lenovo_se30_wdt_start(struct watchdog_device *wdog)
- {
- struct lenovo_se30_wdt *data = watchdog_get_drvdata(wdog);
- return lenovo_se30_wdt_enable(data, wdog->timeout);
- }
- static int lenovo_se30_wdt_stop(struct watchdog_device *wdog)
- {
- struct lenovo_se30_wdt *data = watchdog_get_drvdata(wdog);
- return lenovo_se30_wdt_enable(data, 0);
- }
- static unsigned int lenovo_se30_wdt_get_timeleft(struct watchdog_device *wdog)
- {
- struct lenovo_se30_wdt *data = watchdog_get_drvdata(wdog);
- unsigned char timeleft;
- int err;
- err = read_shm_win(data->shm_base_addr, &data->wdt_cnt, 0, &timeleft);
- if (err)
- return 0;
- return timeleft;
- }
- static int lenovo_se30_wdt_ping(struct watchdog_device *wdt)
- {
- struct lenovo_se30_wdt *data = watchdog_get_drvdata(wdt);
- int err = 0;
- /*
- * Device does not support refreshing WDT_TIMER_REG register when
- * the watchdog is active. Need to disable, feed and enable again
- */
- err = lenovo_se30_wdt_enable(data, 0);
- if (err)
- return err;
- err = write_shm_win(data->shm_base_addr, &data->wdt_cnt, 0, wdt->timeout);
- if (!err)
- err = lenovo_se30_wdt_enable(data, wdt->timeout);
- return err;
- }
- static const struct watchdog_info lenovo_se30_wdt_info = {
- .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
- WDIOF_MAGICCLOSE,
- .identity = "Lenovo SE30 watchdog",
- };
- static const struct watchdog_ops lenovo_se30_wdt_ops = {
- .owner = THIS_MODULE,
- .start = lenovo_se30_wdt_start,
- .stop = lenovo_se30_wdt_stop,
- .ping = lenovo_se30_wdt_ping,
- .get_timeleft = lenovo_se30_wdt_get_timeleft,
- };
- static int lenovo_se30_wdt_probe(struct platform_device *pdev)
- {
- struct device *dev = &pdev->dev;
- struct lenovo_se30_wdt *priv;
- unsigned long base_phys;
- unsigned short val;
- int err;
- err = superio_enter(UNLOCK_KEY, SIO_REG, LNV_SE30_NAME);
- if (err)
- return err;
- val = superio_inb(SIO_REG, CHIPID_REG) << 8;
- val |= superio_inb(SIO_REG, CHIPID_REG + 1);
- if ((val & CHIPID_MASK) != LNV_SE30_ID) {
- superio_exit(LOCK_KEY, SIO_REG);
- return -ENODEV;
- }
- superio_outb(SIO_REG, LDN_REG, LD_NUM_SHM);
- base_phys = (superio_inb(SIO_REG, LD_BASE_ADDR) |
- (superio_inb(SIO_REG, LD_BASE_ADDR + 1) << 8) |
- (superio_inb(SIO_REG, LD_BASE_ADDR + 2) << 16) |
- (superio_inb(SIO_REG, LD_BASE_ADDR + 3) << 24)) &
- 0xFFFFFFFF;
- superio_exit(LOCK_KEY, SIO_REG);
- if (base_phys == 0xFFFFFFFF || base_phys == 0)
- return -ENODEV;
- priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
- if (!priv)
- return -ENOMEM;
- if (!devm_request_mem_region(dev, base_phys, SHM_WIN_SIZE, LNV_SE30_NAME))
- return -EBUSY;
- priv->shm_base_addr = devm_ioremap(dev, base_phys, SHM_WIN_SIZE);
- if (!priv->shm_base_addr)
- return -ENOMEM;
- priv->wdt_cfg.mod = WDT_MODULE;
- priv->wdt_cfg.idx = WDT_CFG_INDEX;
- priv->wdt_cnt.mod = WDT_MODULE;
- priv->wdt_cnt.idx = WDT_CNT_INDEX;
- priv->wdt.ops = &lenovo_se30_wdt_ops;
- priv->wdt.info = &lenovo_se30_wdt_info;
- priv->wdt.timeout = WATCHDOG_TIMEOUT; /* Set default timeout */
- priv->wdt.min_timeout = MIN_TIMEOUT;
- priv->wdt.max_timeout = MAX_TIMEOUT;
- priv->wdt.parent = dev;
- watchdog_init_timeout(&priv->wdt, timeout, dev);
- watchdog_set_drvdata(&priv->wdt, priv);
- watchdog_set_nowayout(&priv->wdt, nowayout);
- watchdog_stop_on_reboot(&priv->wdt);
- watchdog_stop_on_unregister(&priv->wdt);
- return devm_watchdog_register_device(dev, &priv->wdt);
- }
- static struct platform_device *pdev;
- static struct platform_driver lenovo_se30_wdt_driver = {
- .driver = {
- .name = LNV_SE30_NAME,
- },
- .probe = lenovo_se30_wdt_probe,
- };
- static int lenovo_se30_create_platform_device(const struct dmi_system_id *id)
- {
- int err;
- pdev = platform_device_alloc(LNV_SE30_NAME, -1);
- if (!pdev)
- return -ENOMEM;
- err = platform_device_add(pdev);
- if (err)
- platform_device_put(pdev);
- return err;
- }
- static const struct dmi_system_id lenovo_se30_wdt_dmi_table[] __initconst = {
- {
- .ident = "LENOVO-SE30",
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
- DMI_MATCH(DMI_PRODUCT_NAME, "11NA"),
- },
- .callback = lenovo_se30_create_platform_device,
- },
- {
- .ident = "LENOVO-SE30",
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
- DMI_MATCH(DMI_PRODUCT_NAME, "11NB"),
- },
- .callback = lenovo_se30_create_platform_device,
- },
- {
- .ident = "LENOVO-SE30",
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
- DMI_MATCH(DMI_PRODUCT_NAME, "11NC"),
- },
- .callback = lenovo_se30_create_platform_device,
- },
- {
- .ident = "LENOVO-SE30",
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
- DMI_MATCH(DMI_PRODUCT_NAME, "11NH"),
- },
- .callback = lenovo_se30_create_platform_device,
- },
- {
- .ident = "LENOVO-SE30",
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
- DMI_MATCH(DMI_PRODUCT_NAME, "11NJ"),
- },
- .callback = lenovo_se30_create_platform_device,
- },
- {
- .ident = "LENOVO-SE30",
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
- DMI_MATCH(DMI_PRODUCT_NAME, "11NK"),
- },
- .callback = lenovo_se30_create_platform_device,
- },
- {}
- };
- MODULE_DEVICE_TABLE(dmi, lenovo_se30_wdt_dmi_table);
- static int __init lenovo_se30_wdt_init(void)
- {
- if (!dmi_check_system(lenovo_se30_wdt_dmi_table))
- return -ENODEV;
- return platform_driver_register(&lenovo_se30_wdt_driver);
- }
- static void __exit lenovo_se30_wdt_exit(void)
- {
- if (pdev)
- platform_device_unregister(pdev);
- platform_driver_unregister(&lenovo_se30_wdt_driver);
- }
- module_init(lenovo_se30_wdt_init);
- module_exit(lenovo_se30_wdt_exit);
- MODULE_AUTHOR("Mark Pearson <mpearson-lenovo@squebb.ca>");
- MODULE_AUTHOR("David Ober <dober@lenovo.com>");
- MODULE_DESCRIPTION("Lenovo SE30 watchdog driver");
- MODULE_LICENSE("GPL");
|