Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-or-later
0002 /*
0003  * Copyright (C) 2016 National Instruments Corp.
0004  */
0005 
0006 #include <linux/acpi.h>
0007 #include <linux/device.h>
0008 #include <linux/interrupt.h>
0009 #include <linux/io.h>
0010 #include <linux/module.h>
0011 #include <linux/watchdog.h>
0012 
0013 #define NIWD_CONTROL    0x01
0014 #define NIWD_COUNTER2   0x02
0015 #define NIWD_COUNTER1   0x03
0016 #define NIWD_COUNTER0   0x04
0017 #define NIWD_SEED2  0x05
0018 #define NIWD_SEED1  0x06
0019 #define NIWD_SEED0  0x07
0020 
0021 #define NIWD_IO_SIZE    0x08
0022 
0023 #define NIWD_CONTROL_MODE       0x80
0024 #define NIWD_CONTROL_PROC_RESET     0x20
0025 #define NIWD_CONTROL_PET        0x10
0026 #define NIWD_CONTROL_RUNNING        0x08
0027 #define NIWD_CONTROL_CAPTURECOUNTER 0x04
0028 #define NIWD_CONTROL_RESET      0x02
0029 #define NIWD_CONTROL_ALARM      0x01
0030 
0031 #define NIWD_PERIOD_NS      30720
0032 #define NIWD_MIN_TIMEOUT    1
0033 #define NIWD_MAX_TIMEOUT    515
0034 #define NIWD_DEFAULT_TIMEOUT    60
0035 
0036 #define NIWD_NAME       "ni903x_wdt"
0037 
0038 struct ni903x_wdt {
0039     struct device *dev;
0040     u16 io_base;
0041     struct watchdog_device wdd;
0042 };
0043 
0044 static unsigned int timeout;
0045 module_param(timeout, uint, 0);
0046 MODULE_PARM_DESC(timeout,
0047          "Watchdog timeout in seconds. (default="
0048          __MODULE_STRING(NIWD_DEFAULT_TIMEOUT) ")");
0049 
0050 static int nowayout = WATCHDOG_NOWAYOUT;
0051 module_param(nowayout, int, S_IRUGO);
0052 MODULE_PARM_DESC(nowayout,
0053          "Watchdog cannot be stopped once started (default="
0054          __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
0055 
0056 static void ni903x_start(struct ni903x_wdt *wdt)
0057 {
0058     u8 control = inb(wdt->io_base + NIWD_CONTROL);
0059 
0060     outb(control | NIWD_CONTROL_RESET, wdt->io_base + NIWD_CONTROL);
0061     outb(control | NIWD_CONTROL_PET, wdt->io_base + NIWD_CONTROL);
0062 }
0063 
0064 static int ni903x_wdd_set_timeout(struct watchdog_device *wdd,
0065                   unsigned int timeout)
0066 {
0067     struct ni903x_wdt *wdt = watchdog_get_drvdata(wdd);
0068     u32 counter = timeout * (1000000000 / NIWD_PERIOD_NS);
0069 
0070     outb(((0x00FF0000 & counter) >> 16), wdt->io_base + NIWD_SEED2);
0071     outb(((0x0000FF00 & counter) >> 8), wdt->io_base + NIWD_SEED1);
0072     outb((0x000000FF & counter), wdt->io_base + NIWD_SEED0);
0073 
0074     wdd->timeout = timeout;
0075 
0076     return 0;
0077 }
0078 
0079 static unsigned int ni903x_wdd_get_timeleft(struct watchdog_device *wdd)
0080 {
0081     struct ni903x_wdt *wdt = watchdog_get_drvdata(wdd);
0082     u8 control, counter0, counter1, counter2;
0083     u32 counter;
0084 
0085     control = inb(wdt->io_base + NIWD_CONTROL);
0086     control |= NIWD_CONTROL_CAPTURECOUNTER;
0087     outb(control, wdt->io_base + NIWD_CONTROL);
0088 
0089     counter2 = inb(wdt->io_base + NIWD_COUNTER2);
0090     counter1 = inb(wdt->io_base + NIWD_COUNTER1);
0091     counter0 = inb(wdt->io_base + NIWD_COUNTER0);
0092 
0093     counter = (counter2 << 16) | (counter1 << 8) | counter0;
0094 
0095     return counter / (1000000000 / NIWD_PERIOD_NS);
0096 }
0097 
0098 static int ni903x_wdd_ping(struct watchdog_device *wdd)
0099 {
0100     struct ni903x_wdt *wdt = watchdog_get_drvdata(wdd);
0101     u8 control;
0102 
0103     control = inb(wdt->io_base + NIWD_CONTROL);
0104     outb(control | NIWD_CONTROL_PET, wdt->io_base + NIWD_CONTROL);
0105 
0106     return 0;
0107 }
0108 
0109 static int ni903x_wdd_start(struct watchdog_device *wdd)
0110 {
0111     struct ni903x_wdt *wdt = watchdog_get_drvdata(wdd);
0112 
0113     outb(NIWD_CONTROL_RESET | NIWD_CONTROL_PROC_RESET,
0114          wdt->io_base + NIWD_CONTROL);
0115 
0116     ni903x_wdd_set_timeout(wdd, wdd->timeout);
0117     ni903x_start(wdt);
0118 
0119     return 0;
0120 }
0121 
0122 static int ni903x_wdd_stop(struct watchdog_device *wdd)
0123 {
0124     struct ni903x_wdt *wdt = watchdog_get_drvdata(wdd);
0125 
0126     outb(NIWD_CONTROL_RESET, wdt->io_base + NIWD_CONTROL);
0127 
0128     return 0;
0129 }
0130 
0131 static acpi_status ni903x_resources(struct acpi_resource *res, void *data)
0132 {
0133     struct ni903x_wdt *wdt = data;
0134     u16 io_size;
0135 
0136     switch (res->type) {
0137     case ACPI_RESOURCE_TYPE_IO:
0138         if (wdt->io_base != 0) {
0139             dev_err(wdt->dev, "too many IO resources\n");
0140             return AE_ERROR;
0141         }
0142 
0143         wdt->io_base = res->data.io.minimum;
0144         io_size = res->data.io.address_length;
0145 
0146         if (io_size < NIWD_IO_SIZE) {
0147             dev_err(wdt->dev, "memory region too small\n");
0148             return AE_ERROR;
0149         }
0150 
0151         if (!devm_request_region(wdt->dev, wdt->io_base, io_size,
0152                      NIWD_NAME)) {
0153             dev_err(wdt->dev, "failed to get memory region\n");
0154             return AE_ERROR;
0155         }
0156 
0157         return AE_OK;
0158 
0159     case ACPI_RESOURCE_TYPE_END_TAG:
0160     default:
0161         /* Ignore unsupported resources, e.g. IRQ */
0162         return AE_OK;
0163     }
0164 }
0165 
0166 static const struct watchdog_info ni903x_wdd_info = {
0167     .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
0168     .identity = "NI Watchdog",
0169 };
0170 
0171 static const struct watchdog_ops ni903x_wdd_ops = {
0172     .owner = THIS_MODULE,
0173     .start = ni903x_wdd_start,
0174     .stop = ni903x_wdd_stop,
0175     .ping = ni903x_wdd_ping,
0176     .set_timeout = ni903x_wdd_set_timeout,
0177     .get_timeleft = ni903x_wdd_get_timeleft,
0178 };
0179 
0180 static int ni903x_acpi_add(struct acpi_device *device)
0181 {
0182     struct device *dev = &device->dev;
0183     struct watchdog_device *wdd;
0184     struct ni903x_wdt *wdt;
0185     acpi_status status;
0186     int ret;
0187 
0188     wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
0189     if (!wdt)
0190         return -ENOMEM;
0191 
0192     device->driver_data = wdt;
0193     wdt->dev = dev;
0194 
0195     status = acpi_walk_resources(device->handle, METHOD_NAME__CRS,
0196                      ni903x_resources, wdt);
0197     if (ACPI_FAILURE(status) || wdt->io_base == 0) {
0198         dev_err(dev, "failed to get resources\n");
0199         return -ENODEV;
0200     }
0201 
0202     wdd = &wdt->wdd;
0203     wdd->info = &ni903x_wdd_info;
0204     wdd->ops = &ni903x_wdd_ops;
0205     wdd->min_timeout = NIWD_MIN_TIMEOUT;
0206     wdd->max_timeout = NIWD_MAX_TIMEOUT;
0207     wdd->timeout = NIWD_DEFAULT_TIMEOUT;
0208     wdd->parent = dev;
0209     watchdog_set_drvdata(wdd, wdt);
0210     watchdog_set_nowayout(wdd, nowayout);
0211     watchdog_init_timeout(wdd, timeout, dev);
0212 
0213     ret = watchdog_register_device(wdd);
0214     if (ret)
0215         return ret;
0216 
0217     /* Switch from boot mode to user mode */
0218     outb(NIWD_CONTROL_RESET | NIWD_CONTROL_MODE,
0219          wdt->io_base + NIWD_CONTROL);
0220 
0221     dev_dbg(dev, "io_base=0x%04X, timeout=%d, nowayout=%d\n",
0222         wdt->io_base, timeout, nowayout);
0223 
0224     return 0;
0225 }
0226 
0227 static int ni903x_acpi_remove(struct acpi_device *device)
0228 {
0229     struct ni903x_wdt *wdt = acpi_driver_data(device);
0230 
0231     ni903x_wdd_stop(&wdt->wdd);
0232     watchdog_unregister_device(&wdt->wdd);
0233 
0234     return 0;
0235 }
0236 
0237 static const struct acpi_device_id ni903x_device_ids[] = {
0238     {"NIC775C", 0},
0239     {"", 0},
0240 };
0241 MODULE_DEVICE_TABLE(acpi, ni903x_device_ids);
0242 
0243 static struct acpi_driver ni903x_acpi_driver = {
0244     .name = NIWD_NAME,
0245     .ids = ni903x_device_ids,
0246     .ops = {
0247         .add = ni903x_acpi_add,
0248         .remove = ni903x_acpi_remove,
0249     },
0250 };
0251 
0252 module_acpi_driver(ni903x_acpi_driver);
0253 
0254 MODULE_DESCRIPTION("NI 903x Watchdog");
0255 MODULE_AUTHOR("Jeff Westfahl <jeff.westfahl@ni.com>");
0256 MODULE_AUTHOR("Kyle Roeschley <kyle.roeschley@ni.com>");
0257 MODULE_LICENSE("GPL");