| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205 |
- // SPDX-License-Identifier: GPL-2.0-only
- /*
- * Advantech Embedded Controller Watchdog Driver
- *
- * This driver supports Advantech products with ITE based Embedded Controller.
- * It does not support Advantech products with other ECs or without EC.
- *
- * Copyright (C) 2022 Advantech Europe B.V.
- */
- #include <linux/delay.h>
- #include <linux/io.h>
- #include <linux/isa.h>
- #include <linux/kernel.h>
- #include <linux/module.h>
- #include <linux/moduleparam.h>
- #include <linux/watchdog.h>
- #define DRIVER_NAME "advantech_ec_wdt"
- /* EC IO region */
- #define EC_BASE_ADDR 0x299
- #define EC_ADDR_EXTENT 2
- /* EC minimum IO access delay in ms */
- #define EC_MIN_DELAY 10
- /* EC interface definitions */
- #define EC_ADDR_CMD (EC_BASE_ADDR + 1)
- #define EC_ADDR_DATA EC_BASE_ADDR
- #define EC_CMD_EC_PROBE 0x30
- #define EC_CMD_COMM 0x89
- #define EC_CMD_WDT_START 0x28
- #define EC_CMD_WDT_STOP 0x29
- #define EC_CMD_WDT_RESET 0x2A
- #define EC_DAT_EN_DLY_H 0x58
- #define EC_DAT_EN_DLY_L 0x59
- #define EC_DAT_RST_DLY_H 0x5E
- #define EC_DAT_RST_DLY_L 0x5F
- #define EC_MAGIC 0x95
- /* module parameters */
- #define MIN_TIME 1
- #define MAX_TIME 6000 /* 100 minutes */
- #define DEFAULT_TIME 60
- static unsigned int timeout;
- static ktime_t ec_timestamp;
- module_param(timeout, uint, 0);
- MODULE_PARM_DESC(timeout,
- "Default Watchdog timer setting (" __MODULE_STRING(DEFAULT_TIME) "s). The range is from " __MODULE_STRING(MIN_TIME) " to " __MODULE_STRING(MAX_TIME) ".");
- static void adv_ec_wdt_timing_gate(void)
- {
- ktime_t time_cur, time_delta;
- /* ensure minimum delay between IO accesses*/
- time_cur = ktime_get();
- time_delta = ktime_to_ms(ktime_sub(time_cur, ec_timestamp));
- if (time_delta < EC_MIN_DELAY) {
- time_delta = EC_MIN_DELAY - time_delta;
- usleep_range(time_delta * 1000, (time_delta + 1) * 1000);
- }
- ec_timestamp = ktime_get();
- }
- static void adv_ec_wdt_outb(unsigned char value, unsigned short port)
- {
- adv_ec_wdt_timing_gate();
- outb(value, port);
- }
- static unsigned char adv_ec_wdt_inb(unsigned short port)
- {
- adv_ec_wdt_timing_gate();
- return inb(port);
- }
- static int adv_ec_wdt_ping(struct watchdog_device *wdd)
- {
- adv_ec_wdt_outb(EC_CMD_WDT_RESET, EC_ADDR_CMD);
- return 0;
- }
- static int adv_ec_wdt_set_timeout(struct watchdog_device *wdd, unsigned int t)
- {
- unsigned int val;
- /* scale time to EC 100 ms base */
- val = t * 10;
- /* reset enable delay, just in case it was set by BIOS etc. */
- adv_ec_wdt_outb(EC_CMD_COMM, EC_ADDR_CMD);
- adv_ec_wdt_outb(EC_DAT_EN_DLY_H, EC_ADDR_DATA);
- adv_ec_wdt_outb(0, EC_ADDR_DATA);
- adv_ec_wdt_outb(EC_CMD_COMM, EC_ADDR_CMD);
- adv_ec_wdt_outb(EC_DAT_EN_DLY_L, EC_ADDR_DATA);
- adv_ec_wdt_outb(0, EC_ADDR_DATA);
- /* set reset delay */
- adv_ec_wdt_outb(EC_CMD_COMM, EC_ADDR_CMD);
- adv_ec_wdt_outb(EC_DAT_RST_DLY_H, EC_ADDR_DATA);
- adv_ec_wdt_outb(val >> 8, EC_ADDR_DATA);
- adv_ec_wdt_outb(EC_CMD_COMM, EC_ADDR_CMD);
- adv_ec_wdt_outb(EC_DAT_RST_DLY_L, EC_ADDR_DATA);
- adv_ec_wdt_outb(val & 0xFF, EC_ADDR_DATA);
- wdd->timeout = t;
- return 0;
- }
- static int adv_ec_wdt_start(struct watchdog_device *wdd)
- {
- adv_ec_wdt_set_timeout(wdd, wdd->timeout);
- adv_ec_wdt_outb(EC_CMD_WDT_START, EC_ADDR_CMD);
- return 0;
- }
- static int adv_ec_wdt_stop(struct watchdog_device *wdd)
- {
- adv_ec_wdt_outb(EC_CMD_WDT_STOP, EC_ADDR_CMD);
- return 0;
- }
- static const struct watchdog_info adv_ec_wdt_info = {
- .identity = DRIVER_NAME,
- .options = WDIOF_SETTIMEOUT |
- WDIOF_MAGICCLOSE |
- WDIOF_KEEPALIVEPING,
- };
- static const struct watchdog_ops adv_ec_wdt_ops = {
- .owner = THIS_MODULE,
- .start = adv_ec_wdt_start,
- .stop = adv_ec_wdt_stop,
- .ping = adv_ec_wdt_ping,
- .set_timeout = adv_ec_wdt_set_timeout,
- };
- static struct watchdog_device adv_ec_wdt_dev = {
- .info = &adv_ec_wdt_info,
- .ops = &adv_ec_wdt_ops,
- .min_timeout = MIN_TIME,
- .max_timeout = MAX_TIME,
- .timeout = DEFAULT_TIME,
- };
- static int adv_ec_wdt_probe(struct device *dev, unsigned int id)
- {
- if (!devm_request_region(dev, EC_BASE_ADDR, EC_ADDR_EXTENT, dev_name(dev))) {
- dev_err(dev, "Unable to lock port addresses (0x%X-0x%X)\n",
- EC_BASE_ADDR, EC_BASE_ADDR + EC_ADDR_EXTENT);
- return -EBUSY;
- }
- watchdog_init_timeout(&adv_ec_wdt_dev, timeout, dev);
- watchdog_stop_on_reboot(&adv_ec_wdt_dev);
- watchdog_stop_on_unregister(&adv_ec_wdt_dev);
- return devm_watchdog_register_device(dev, &adv_ec_wdt_dev);
- }
- static struct isa_driver adv_ec_wdt_driver = {
- .probe = adv_ec_wdt_probe,
- .driver = {
- .name = DRIVER_NAME,
- },
- };
- static int __init adv_ec_wdt_init(void)
- {
- unsigned int val;
- /* quick probe for EC */
- if (!request_region(EC_BASE_ADDR, EC_ADDR_EXTENT, DRIVER_NAME))
- return -EBUSY;
- adv_ec_wdt_outb(EC_CMD_EC_PROBE, EC_ADDR_CMD);
- val = adv_ec_wdt_inb(EC_ADDR_DATA);
- release_region(EC_BASE_ADDR, EC_ADDR_EXTENT);
- if (val != EC_MAGIC)
- return -ENODEV;
- return isa_register_driver(&adv_ec_wdt_driver, 1);
- }
- static void __exit adv_ec_wdt_exit(void)
- {
- isa_unregister_driver(&adv_ec_wdt_driver);
- }
- module_init(adv_ec_wdt_init);
- module_exit(adv_ec_wdt_exit);
- MODULE_AUTHOR("Thomas Kastner <thomas.kastner@advantech.com>");
- MODULE_DESCRIPTION("Advantech Embedded Controller Watchdog Device Driver");
- MODULE_LICENSE("GPL");
- MODULE_VERSION("20221019");
- MODULE_ALIAS("isa:" DRIVER_NAME);
|