mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/
synced 2025-04-19 20:58:31 +09:00
watchdog: lenovo_se30_wdt: Watchdog driver for Lenovo SE30 platform
Watchdog driver implementation for Lenovo SE30 platform. Signed-off-by: Mark Pearson <mpearson-lenovo@squebb.ca> Reviewed-by: Guenter Roeck <linux@roeck-us.net> Link: https://lore.kernel.org/r/20250131204855.1827-1-mpearson-lenovo@squebb.ca Signed-off-by: Guenter Roeck <linux@roeck-us.net> Signed-off-by: Wim Van Sebroeck <wim@linux-watchdog.org>
This commit is contained in:
parent
331c834960
commit
c284153a2c
@ -279,6 +279,18 @@ config LENOVO_SE10_WDT
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called lenovo-se10-wdt.
|
||||
|
||||
config LENOVO_SE30_WDT
|
||||
tristate "Lenovo SE30 Watchdog"
|
||||
depends on (X86 && DMI) || COMPILE_TEST
|
||||
depends on HAS_IOPORT
|
||||
select WATCHDOG_CORE
|
||||
help
|
||||
If you say yes here you get support for the watchdog
|
||||
functionality for the Lenovo SE30 platform.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called lenovo-se30-wdt.
|
||||
|
||||
config MENF21BMC_WATCHDOG
|
||||
tristate "MEN 14F021P00 BMC Watchdog"
|
||||
depends on MFD_MENF21BMC || COMPILE_TEST
|
||||
|
@ -124,6 +124,7 @@ obj-$(CONFIG_I6300ESB_WDT) += i6300esb.o
|
||||
obj-$(CONFIG_IE6XX_WDT) += ie6xx_wdt.o
|
||||
obj-$(CONFIG_ITCO_WDT) += iTCO_wdt.o
|
||||
obj-$(CONFIG_LENOVO_SE10_WDT) += lenovo_se10_wdt.o
|
||||
obj-$(CONFIG_LENOVO_SE30_WDT) += lenovo_se30_wdt.o
|
||||
ifeq ($(CONFIG_ITCO_VENDOR_SUPPORT),y)
|
||||
obj-$(CONFIG_ITCO_WDT) += iTCO_vendor_support.o
|
||||
endif
|
||||
|
393
drivers/watchdog/lenovo_se30_wdt.c
Normal file
393
drivers/watchdog/lenovo_se30_wdt.c
Normal file
@ -0,0 +1,393 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* WDT driver for Lenovo SE30 device
|
||||
*/
|
||||
|
||||
#define dev_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#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);
|
||||
|
||||
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");
|
Loading…
x
Reference in New Issue
Block a user