123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266 |
- /*
- * Copyright (C) 2016 National Instruments Corp.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- */
- #include <linux/acpi.h>
- #include <linux/bitops.h>
- #include <linux/device.h>
- #include <linux/io.h>
- #include <linux/module.h>
- #include <linux/platform_device.h>
- #include <linux/watchdog.h>
- #define LOCK 0xA5
- #define UNLOCK 0x5A
- #define WDT_CTRL_RESET_EN BIT(7)
- #define WDT_RELOAD_PORT_EN BIT(7)
- #define WDT_CTRL 1
- #define WDT_RELOAD_CTRL 2
- #define WDT_PRESET_PRESCALE 4
- #define WDT_REG_LOCK 5
- #define WDT_COUNT 6
- #define WDT_RELOAD_PORT 7
- #define WDT_MIN_TIMEOUT 1
- #define WDT_MAX_TIMEOUT 464
- #define WDT_DEFAULT_TIMEOUT 80
- #define WDT_MAX_COUNTER 15
- static unsigned int timeout;
- module_param(timeout, uint, 0);
- MODULE_PARM_DESC(timeout,
- "Watchdog timeout in seconds. (default="
- __MODULE_STRING(WDT_DEFAULT_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) ")");
- struct nic7018_wdt {
- u16 io_base;
- u32 period;
- struct watchdog_device wdd;
- };
- struct nic7018_config {
- u32 period;
- u8 divider;
- };
- static const struct nic7018_config nic7018_configs[] = {
- { 2, 4 },
- { 32, 5 },
- };
- static inline u32 nic7018_timeout(u32 period, u8 counter)
- {
- return period * counter - period / 2;
- }
- static const struct nic7018_config *nic7018_get_config(u32 timeout,
- u8 *counter)
- {
- const struct nic7018_config *config;
- u8 count;
- if (timeout < 30 && timeout != 16) {
- config = &nic7018_configs[0];
- count = timeout / 2 + 1;
- } else {
- config = &nic7018_configs[1];
- count = DIV_ROUND_UP(timeout + 16, 32);
- if (count > WDT_MAX_COUNTER)
- count = WDT_MAX_COUNTER;
- }
- *counter = count;
- return config;
- }
- static int nic7018_set_timeout(struct watchdog_device *wdd,
- unsigned int timeout)
- {
- struct nic7018_wdt *wdt = watchdog_get_drvdata(wdd);
- const struct nic7018_config *config;
- u8 counter;
- config = nic7018_get_config(timeout, &counter);
- outb(counter << 4 | config->divider,
- wdt->io_base + WDT_PRESET_PRESCALE);
- wdd->timeout = nic7018_timeout(config->period, counter);
- wdt->period = config->period;
- return 0;
- }
- static int nic7018_start(struct watchdog_device *wdd)
- {
- struct nic7018_wdt *wdt = watchdog_get_drvdata(wdd);
- u8 control;
- nic7018_set_timeout(wdd, wdd->timeout);
- control = inb(wdt->io_base + WDT_RELOAD_CTRL);
- outb(control | WDT_RELOAD_PORT_EN, wdt->io_base + WDT_RELOAD_CTRL);
- outb(1, wdt->io_base + WDT_RELOAD_PORT);
- control = inb(wdt->io_base + WDT_CTRL);
- outb(control | WDT_CTRL_RESET_EN, wdt->io_base + WDT_CTRL);
- return 0;
- }
- static int nic7018_stop(struct watchdog_device *wdd)
- {
- struct nic7018_wdt *wdt = watchdog_get_drvdata(wdd);
- outb(0, wdt->io_base + WDT_CTRL);
- outb(0, wdt->io_base + WDT_RELOAD_CTRL);
- outb(0xF0, wdt->io_base + WDT_PRESET_PRESCALE);
- return 0;
- }
- static int nic7018_ping(struct watchdog_device *wdd)
- {
- struct nic7018_wdt *wdt = watchdog_get_drvdata(wdd);
- outb(1, wdt->io_base + WDT_RELOAD_PORT);
- return 0;
- }
- static unsigned int nic7018_get_timeleft(struct watchdog_device *wdd)
- {
- struct nic7018_wdt *wdt = watchdog_get_drvdata(wdd);
- u8 count;
- count = inb(wdt->io_base + WDT_COUNT) & 0xF;
- if (!count)
- return 0;
- return nic7018_timeout(wdt->period, count);
- }
- static const struct watchdog_info nic7018_wdd_info = {
- .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
- .identity = "NIC7018 Watchdog",
- };
- static const struct watchdog_ops nic7018_wdd_ops = {
- .owner = THIS_MODULE,
- .start = nic7018_start,
- .stop = nic7018_stop,
- .ping = nic7018_ping,
- .set_timeout = nic7018_set_timeout,
- .get_timeleft = nic7018_get_timeleft,
- };
- static int nic7018_probe(struct platform_device *pdev)
- {
- struct device *dev = &pdev->dev;
- struct watchdog_device *wdd;
- struct nic7018_wdt *wdt;
- struct resource *io_rc;
- int ret;
- wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
- if (!wdt)
- return -ENOMEM;
- platform_set_drvdata(pdev, wdt);
- io_rc = platform_get_resource(pdev, IORESOURCE_IO, 0);
- if (!io_rc) {
- dev_err(dev, "missing IO resources\n");
- return -EINVAL;
- }
- if (!devm_request_region(dev, io_rc->start, resource_size(io_rc),
- KBUILD_MODNAME)) {
- dev_err(dev, "failed to get IO region\n");
- return -EBUSY;
- }
- wdt->io_base = io_rc->start;
- wdd = &wdt->wdd;
- wdd->info = &nic7018_wdd_info;
- wdd->ops = &nic7018_wdd_ops;
- wdd->min_timeout = WDT_MIN_TIMEOUT;
- wdd->max_timeout = WDT_MAX_TIMEOUT;
- wdd->timeout = WDT_DEFAULT_TIMEOUT;
- wdd->parent = dev;
- watchdog_set_drvdata(wdd, wdt);
- watchdog_set_nowayout(wdd, nowayout);
- ret = watchdog_init_timeout(wdd, timeout, dev);
- if (ret)
- dev_warn(dev, "unable to set timeout value, using default\n");
- /* Unlock WDT register */
- outb(UNLOCK, wdt->io_base + WDT_REG_LOCK);
- ret = watchdog_register_device(wdd);
- if (ret) {
- outb(LOCK, wdt->io_base + WDT_REG_LOCK);
- dev_err(dev, "failed to register watchdog\n");
- return ret;
- }
- dev_dbg(dev, "io_base=0x%04X, timeout=%d, nowayout=%d\n",
- wdt->io_base, timeout, nowayout);
- return 0;
- }
- static int nic7018_remove(struct platform_device *pdev)
- {
- struct nic7018_wdt *wdt = platform_get_drvdata(pdev);
- watchdog_unregister_device(&wdt->wdd);
- /* Lock WDT register */
- outb(LOCK, wdt->io_base + WDT_REG_LOCK);
- return 0;
- }
- static const struct acpi_device_id nic7018_device_ids[] = {
- {"NIC7018", 0},
- {"", 0},
- };
- MODULE_DEVICE_TABLE(acpi, nic7018_device_ids);
- static struct platform_driver watchdog_driver = {
- .probe = nic7018_probe,
- .remove = nic7018_remove,
- .driver = {
- .name = KBUILD_MODNAME,
- .acpi_match_table = ACPI_PTR(nic7018_device_ids),
- },
- };
- module_platform_driver(watchdog_driver);
- MODULE_DESCRIPTION("National Instruments NIC7018 Watchdog driver");
- MODULE_AUTHOR("Hui Chun Ong <hui.chun.ong@ni.com>");
- MODULE_LICENSE("GPL");
|