123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429 |
- // SPDX-License-Identifier: GPL-2.0+
- /*
- * exar_wdt.c - Driver for the watchdog present in some
- * Exar/MaxLinear UART chips like the XR28V38x.
- *
- * (c) Copyright 2022 D. Müller <[email protected]>.
- *
- */
- #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
- #include <linux/io.h>
- #include <linux/list.h>
- #include <linux/module.h>
- #include <linux/platform_device.h>
- #include <linux/slab.h>
- #include <linux/watchdog.h>
- #define DRV_NAME "exar_wdt"
- static const unsigned short sio_config_ports[] = { 0x2e, 0x4e };
- static const unsigned char sio_enter_keys[] = { 0x67, 0x77, 0x87, 0xA0 };
- #define EXAR_EXIT_KEY 0xAA
- #define EXAR_LDN 0x07
- #define EXAR_DID 0x20
- #define EXAR_VID 0x23
- #define EXAR_WDT 0x26
- #define EXAR_ACT 0x30
- #define EXAR_RTBASE 0x60
- #define EXAR_WDT_LDEV 0x08
- #define EXAR_VEN_ID 0x13A8
- #define EXAR_DEV_382 0x0382
- #define EXAR_DEV_384 0x0384
- /* WDT runtime registers */
- #define WDT_CTRL 0x00
- #define WDT_VAL 0x01
- #define WDT_UNITS_10MS 0x0 /* the 10 millisec unit of the HW is not used */
- #define WDT_UNITS_SEC 0x2
- #define WDT_UNITS_MIN 0x4
- /* default WDT control for WDTOUT signal activ / rearm by read */
- #define EXAR_WDT_DEF_CONF 0
- struct wdt_pdev_node {
- struct list_head list;
- struct platform_device *pdev;
- const char name[16];
- };
- struct wdt_priv {
- /* the lock for WDT io operations */
- spinlock_t io_lock;
- struct resource wdt_res;
- struct watchdog_device wdt_dev;
- unsigned short did;
- unsigned short config_port;
- unsigned char enter_key;
- unsigned char unit;
- unsigned char timeout;
- };
- #define WATCHDOG_TIMEOUT 60
- static int timeout = WATCHDOG_TIMEOUT;
- module_param(timeout, int, 0);
- MODULE_PARM_DESC(timeout,
- "Watchdog timeout in seconds. 1<=timeout<=15300, 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) ")");
- static int exar_sio_enter(const unsigned short config_port,
- const unsigned char key)
- {
- if (!request_muxed_region(config_port, 2, DRV_NAME))
- return -EBUSY;
- /* write the ENTER-KEY twice */
- outb(key, config_port);
- outb(key, config_port);
- return 0;
- }
- static void exar_sio_exit(const unsigned short config_port)
- {
- outb(EXAR_EXIT_KEY, config_port);
- release_region(config_port, 2);
- }
- static unsigned char exar_sio_read(const unsigned short config_port,
- const unsigned char reg)
- {
- outb(reg, config_port);
- return inb(config_port + 1);
- }
- static void exar_sio_write(const unsigned short config_port,
- const unsigned char reg, const unsigned char val)
- {
- outb(reg, config_port);
- outb(val, config_port + 1);
- }
- static unsigned short exar_sio_read16(const unsigned short config_port,
- const unsigned char reg)
- {
- unsigned char msb, lsb;
- msb = exar_sio_read(config_port, reg);
- lsb = exar_sio_read(config_port, reg + 1);
- return (msb << 8) | lsb;
- }
- static void exar_sio_select_wdt(const unsigned short config_port)
- {
- exar_sio_write(config_port, EXAR_LDN, EXAR_WDT_LDEV);
- }
- static void exar_wdt_arm(const struct wdt_priv *priv)
- {
- unsigned short rt_base = priv->wdt_res.start;
- /* write timeout value twice to arm watchdog */
- outb(priv->timeout, rt_base + WDT_VAL);
- outb(priv->timeout, rt_base + WDT_VAL);
- }
- static void exar_wdt_disarm(const struct wdt_priv *priv)
- {
- unsigned short rt_base = priv->wdt_res.start;
- /*
- * use two accesses with different values to make sure
- * that a combination of a previous single access and
- * the ones below with the same value are not falsely
- * interpreted as "arm watchdog"
- */
- outb(0xFF, rt_base + WDT_VAL);
- outb(0, rt_base + WDT_VAL);
- }
- static int exar_wdt_start(struct watchdog_device *wdog)
- {
- struct wdt_priv *priv = watchdog_get_drvdata(wdog);
- unsigned short rt_base = priv->wdt_res.start;
- spin_lock(&priv->io_lock);
- exar_wdt_disarm(priv);
- outb(priv->unit, rt_base + WDT_CTRL);
- exar_wdt_arm(priv);
- spin_unlock(&priv->io_lock);
- return 0;
- }
- static int exar_wdt_stop(struct watchdog_device *wdog)
- {
- struct wdt_priv *priv = watchdog_get_drvdata(wdog);
- spin_lock(&priv->io_lock);
- exar_wdt_disarm(priv);
- spin_unlock(&priv->io_lock);
- return 0;
- }
- static int exar_wdt_keepalive(struct watchdog_device *wdog)
- {
- struct wdt_priv *priv = watchdog_get_drvdata(wdog);
- unsigned short rt_base = priv->wdt_res.start;
- spin_lock(&priv->io_lock);
- /* reading the WDT_VAL reg will feed the watchdog */
- inb(rt_base + WDT_VAL);
- spin_unlock(&priv->io_lock);
- return 0;
- }
- static int exar_wdt_set_timeout(struct watchdog_device *wdog, unsigned int t)
- {
- struct wdt_priv *priv = watchdog_get_drvdata(wdog);
- bool unit_min = false;
- /*
- * if new timeout is bigger then 255 seconds, change the
- * unit to minutes and round the timeout up to the next whole minute
- */
- if (t > 255) {
- unit_min = true;
- t = DIV_ROUND_UP(t, 60);
- }
- /* save for later use in exar_wdt_start() */
- priv->unit = unit_min ? WDT_UNITS_MIN : WDT_UNITS_SEC;
- priv->timeout = t;
- wdog->timeout = unit_min ? t * 60 : t;
- if (watchdog_hw_running(wdog))
- exar_wdt_start(wdog);
- return 0;
- }
- static const struct watchdog_info exar_wdt_info = {
- .options = WDIOF_KEEPALIVEPING |
- WDIOF_SETTIMEOUT |
- WDIOF_MAGICCLOSE,
- .identity = "Exar/MaxLinear XR28V38x Watchdog",
- };
- static const struct watchdog_ops exar_wdt_ops = {
- .owner = THIS_MODULE,
- .start = exar_wdt_start,
- .stop = exar_wdt_stop,
- .ping = exar_wdt_keepalive,
- .set_timeout = exar_wdt_set_timeout,
- };
- static int exar_wdt_config(struct watchdog_device *wdog,
- const unsigned char conf)
- {
- struct wdt_priv *priv = watchdog_get_drvdata(wdog);
- int ret;
- ret = exar_sio_enter(priv->config_port, priv->enter_key);
- if (ret)
- return ret;
- exar_sio_select_wdt(priv->config_port);
- exar_sio_write(priv->config_port, EXAR_WDT, conf);
- exar_sio_exit(priv->config_port);
- return 0;
- }
- static int __init exar_wdt_probe(struct platform_device *pdev)
- {
- struct device *dev = &pdev->dev;
- struct wdt_priv *priv = dev->platform_data;
- struct watchdog_device *wdt_dev = &priv->wdt_dev;
- struct resource *res;
- int ret;
- res = platform_get_resource(pdev, IORESOURCE_IO, 0);
- if (!res)
- return -ENXIO;
- spin_lock_init(&priv->io_lock);
- wdt_dev->info = &exar_wdt_info;
- wdt_dev->ops = &exar_wdt_ops;
- wdt_dev->min_timeout = 1;
- wdt_dev->max_timeout = 255 * 60;
- watchdog_init_timeout(wdt_dev, timeout, NULL);
- watchdog_set_nowayout(wdt_dev, nowayout);
- watchdog_stop_on_reboot(wdt_dev);
- watchdog_stop_on_unregister(wdt_dev);
- watchdog_set_drvdata(wdt_dev, priv);
- ret = exar_wdt_config(wdt_dev, EXAR_WDT_DEF_CONF);
- if (ret)
- return ret;
- exar_wdt_set_timeout(wdt_dev, timeout);
- /* Make sure that the watchdog is not running */
- exar_wdt_stop(wdt_dev);
- ret = devm_watchdog_register_device(dev, wdt_dev);
- if (ret)
- return ret;
- dev_info(dev, "XR28V%X WDT initialized. timeout=%d sec (nowayout=%d)\n",
- priv->did, timeout, nowayout);
- return 0;
- }
- static unsigned short __init exar_detect(const unsigned short config_port,
- const unsigned char key,
- unsigned short *rt_base)
- {
- int ret;
- unsigned short base = 0;
- unsigned short vid, did;
- ret = exar_sio_enter(config_port, key);
- if (ret)
- return 0;
- vid = exar_sio_read16(config_port, EXAR_VID);
- did = exar_sio_read16(config_port, EXAR_DID);
- /* check for the vendor and device IDs we currently know about */
- if (vid == EXAR_VEN_ID &&
- (did == EXAR_DEV_382 ||
- did == EXAR_DEV_384)) {
- exar_sio_select_wdt(config_port);
- /* is device active? */
- if (exar_sio_read(config_port, EXAR_ACT) == 0x01)
- base = exar_sio_read16(config_port, EXAR_RTBASE);
- }
- exar_sio_exit(config_port);
- if (base) {
- pr_debug("Found a XR28V%X WDT (conf: 0x%x / rt: 0x%04x)\n",
- did, config_port, base);
- *rt_base = base;
- return did;
- }
- return 0;
- }
- static struct platform_driver exar_wdt_driver = {
- .driver = {
- .name = DRV_NAME,
- },
- };
- static LIST_HEAD(pdev_list);
- static int __init exar_wdt_register(struct wdt_priv *priv, const int idx)
- {
- struct wdt_pdev_node *n;
- n = kzalloc(sizeof(*n), GFP_KERNEL);
- if (!n)
- return -ENOMEM;
- INIT_LIST_HEAD(&n->list);
- scnprintf((char *)n->name, sizeof(n->name), DRV_NAME ".%d", idx);
- priv->wdt_res.name = n->name;
- n->pdev = platform_device_register_resndata(NULL, DRV_NAME, idx,
- &priv->wdt_res, 1,
- priv, sizeof(*priv));
- if (IS_ERR(n->pdev)) {
- int err = PTR_ERR(n->pdev);
- kfree(n);
- return err;
- }
- list_add_tail(&n->list, &pdev_list);
- return 0;
- }
- static void exar_wdt_unregister(void)
- {
- struct wdt_pdev_node *n, *t;
- list_for_each_entry_safe(n, t, &pdev_list, list) {
- platform_device_unregister(n->pdev);
- list_del(&n->list);
- kfree(n);
- }
- }
- static int __init exar_wdt_init(void)
- {
- int ret, i, j, idx = 0;
- /* search for active Exar watchdogs on all possible locations */
- for (i = 0; i < ARRAY_SIZE(sio_config_ports); i++) {
- for (j = 0; j < ARRAY_SIZE(sio_enter_keys); j++) {
- unsigned short did, rt_base = 0;
- did = exar_detect(sio_config_ports[i],
- sio_enter_keys[j],
- &rt_base);
- if (did) {
- struct wdt_priv priv = {
- .wdt_res = DEFINE_RES_IO(rt_base, 2),
- .did = did,
- .config_port = sio_config_ports[i],
- .enter_key = sio_enter_keys[j],
- };
- ret = exar_wdt_register(&priv, idx);
- if (!ret)
- idx++;
- }
- }
- }
- if (!idx)
- return -ENODEV;
- ret = platform_driver_probe(&exar_wdt_driver, exar_wdt_probe);
- if (ret)
- exar_wdt_unregister();
- return ret;
- }
- static void __exit exar_wdt_exit(void)
- {
- exar_wdt_unregister();
- platform_driver_unregister(&exar_wdt_driver);
- }
- module_init(exar_wdt_init);
- module_exit(exar_wdt_exit);
- MODULE_AUTHOR("David Müller <[email protected]>");
- MODULE_DESCRIPTION("Exar/MaxLinear Watchdog Driver");
- MODULE_LICENSE("GPL");
|