linux/drivers/watchdog/ni903x_wdt.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 * Copyright (C) 2016 National Instruments Corp.
   4 */
   5
   6#include <linux/acpi.h>
   7#include <linux/device.h>
   8#include <linux/interrupt.h>
   9#include <linux/io.h>
  10#include <linux/module.h>
  11#include <linux/watchdog.h>
  12
  13#define NIWD_CONTROL    0x01
  14#define NIWD_COUNTER2   0x02
  15#define NIWD_COUNTER1   0x03
  16#define NIWD_COUNTER0   0x04
  17#define NIWD_SEED2      0x05
  18#define NIWD_SEED1      0x06
  19#define NIWD_SEED0      0x07
  20
  21#define NIWD_IO_SIZE    0x08
  22
  23#define NIWD_CONTROL_MODE               0x80
  24#define NIWD_CONTROL_PROC_RESET         0x20
  25#define NIWD_CONTROL_PET                0x10
  26#define NIWD_CONTROL_RUNNING            0x08
  27#define NIWD_CONTROL_CAPTURECOUNTER     0x04
  28#define NIWD_CONTROL_RESET              0x02
  29#define NIWD_CONTROL_ALARM              0x01
  30
  31#define NIWD_PERIOD_NS          30720
  32#define NIWD_MIN_TIMEOUT        1
  33#define NIWD_MAX_TIMEOUT        515
  34#define NIWD_DEFAULT_TIMEOUT    60
  35
  36#define NIWD_NAME               "ni903x_wdt"
  37
  38struct ni903x_wdt {
  39        struct device *dev;
  40        u16 io_base;
  41        struct watchdog_device wdd;
  42};
  43
  44static unsigned int timeout;
  45module_param(timeout, uint, 0);
  46MODULE_PARM_DESC(timeout,
  47                 "Watchdog timeout in seconds. (default="
  48                 __MODULE_STRING(NIWD_DEFAULT_TIMEOUT) ")");
  49
  50static int nowayout = WATCHDOG_NOWAYOUT;
  51module_param(nowayout, int, S_IRUGO);
  52MODULE_PARM_DESC(nowayout,
  53                 "Watchdog cannot be stopped once started (default="
  54                 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
  55
  56static void ni903x_start(struct ni903x_wdt *wdt)
  57{
  58        u8 control = inb(wdt->io_base + NIWD_CONTROL);
  59
  60        outb(control | NIWD_CONTROL_RESET, wdt->io_base + NIWD_CONTROL);
  61        outb(control | NIWD_CONTROL_PET, wdt->io_base + NIWD_CONTROL);
  62}
  63
  64static int ni903x_wdd_set_timeout(struct watchdog_device *wdd,
  65                                  unsigned int timeout)
  66{
  67        struct ni903x_wdt *wdt = watchdog_get_drvdata(wdd);
  68        u32 counter = timeout * (1000000000 / NIWD_PERIOD_NS);
  69
  70        outb(((0x00FF0000 & counter) >> 16), wdt->io_base + NIWD_SEED2);
  71        outb(((0x0000FF00 & counter) >> 8), wdt->io_base + NIWD_SEED1);
  72        outb((0x000000FF & counter), wdt->io_base + NIWD_SEED0);
  73
  74        wdd->timeout = timeout;
  75
  76        return 0;
  77}
  78
  79static unsigned int ni903x_wdd_get_timeleft(struct watchdog_device *wdd)
  80{
  81        struct ni903x_wdt *wdt = watchdog_get_drvdata(wdd);
  82        u8 control, counter0, counter1, counter2;
  83        u32 counter;
  84
  85        control = inb(wdt->io_base + NIWD_CONTROL);
  86        control |= NIWD_CONTROL_CAPTURECOUNTER;
  87        outb(control, wdt->io_base + NIWD_CONTROL);
  88
  89        counter2 = inb(wdt->io_base + NIWD_COUNTER2);
  90        counter1 = inb(wdt->io_base + NIWD_COUNTER1);
  91        counter0 = inb(wdt->io_base + NIWD_COUNTER0);
  92
  93        counter = (counter2 << 16) | (counter1 << 8) | counter0;
  94
  95        return counter / (1000000000 / NIWD_PERIOD_NS);
  96}
  97
  98static int ni903x_wdd_ping(struct watchdog_device *wdd)
  99{
 100        struct ni903x_wdt *wdt = watchdog_get_drvdata(wdd);
 101        u8 control;
 102
 103        control = inb(wdt->io_base + NIWD_CONTROL);
 104        outb(control | NIWD_CONTROL_PET, wdt->io_base + NIWD_CONTROL);
 105
 106        return 0;
 107}
 108
 109static int ni903x_wdd_start(struct watchdog_device *wdd)
 110{
 111        struct ni903x_wdt *wdt = watchdog_get_drvdata(wdd);
 112
 113        outb(NIWD_CONTROL_RESET | NIWD_CONTROL_PROC_RESET,
 114             wdt->io_base + NIWD_CONTROL);
 115
 116        ni903x_wdd_set_timeout(wdd, wdd->timeout);
 117        ni903x_start(wdt);
 118
 119        return 0;
 120}
 121
 122static int ni903x_wdd_stop(struct watchdog_device *wdd)
 123{
 124        struct ni903x_wdt *wdt = watchdog_get_drvdata(wdd);
 125
 126        outb(NIWD_CONTROL_RESET, wdt->io_base + NIWD_CONTROL);
 127
 128        return 0;
 129}
 130
 131static acpi_status ni903x_resources(struct acpi_resource *res, void *data)
 132{
 133        struct ni903x_wdt *wdt = data;
 134        u16 io_size;
 135
 136        switch (res->type) {
 137        case ACPI_RESOURCE_TYPE_IO:
 138                if (wdt->io_base != 0) {
 139                        dev_err(wdt->dev, "too many IO resources\n");
 140                        return AE_ERROR;
 141                }
 142
 143                wdt->io_base = res->data.io.minimum;
 144                io_size = res->data.io.address_length;
 145
 146                if (io_size < NIWD_IO_SIZE) {
 147                        dev_err(wdt->dev, "memory region too small\n");
 148                        return AE_ERROR;
 149                }
 150
 151                if (!devm_request_region(wdt->dev, wdt->io_base, io_size,
 152                                         NIWD_NAME)) {
 153                        dev_err(wdt->dev, "failed to get memory region\n");
 154                        return AE_ERROR;
 155                }
 156
 157                return AE_OK;
 158
 159        case ACPI_RESOURCE_TYPE_END_TAG:
 160        default:
 161                /* Ignore unsupported resources, e.g. IRQ */
 162                return AE_OK;
 163        }
 164}
 165
 166static const struct watchdog_info ni903x_wdd_info = {
 167        .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
 168        .identity = "NI Watchdog",
 169};
 170
 171static const struct watchdog_ops ni903x_wdd_ops = {
 172        .owner = THIS_MODULE,
 173        .start = ni903x_wdd_start,
 174        .stop = ni903x_wdd_stop,
 175        .ping = ni903x_wdd_ping,
 176        .set_timeout = ni903x_wdd_set_timeout,
 177        .get_timeleft = ni903x_wdd_get_timeleft,
 178};
 179
 180static int ni903x_acpi_add(struct acpi_device *device)
 181{
 182        struct device *dev = &device->dev;
 183        struct watchdog_device *wdd;
 184        struct ni903x_wdt *wdt;
 185        acpi_status status;
 186        int ret;
 187
 188        wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
 189        if (!wdt)
 190                return -ENOMEM;
 191
 192        device->driver_data = wdt;
 193        wdt->dev = dev;
 194
 195        status = acpi_walk_resources(device->handle, METHOD_NAME__CRS,
 196                                     ni903x_resources, wdt);
 197        if (ACPI_FAILURE(status) || wdt->io_base == 0) {
 198                dev_err(dev, "failed to get resources\n");
 199                return -ENODEV;
 200        }
 201
 202        wdd = &wdt->wdd;
 203        wdd->info = &ni903x_wdd_info;
 204        wdd->ops = &ni903x_wdd_ops;
 205        wdd->min_timeout = NIWD_MIN_TIMEOUT;
 206        wdd->max_timeout = NIWD_MAX_TIMEOUT;
 207        wdd->timeout = NIWD_DEFAULT_TIMEOUT;
 208        wdd->parent = dev;
 209        watchdog_set_drvdata(wdd, wdt);
 210        watchdog_set_nowayout(wdd, nowayout);
 211        watchdog_init_timeout(wdd, timeout, dev);
 212
 213        ret = watchdog_register_device(wdd);
 214        if (ret)
 215                return ret;
 216
 217        /* Switch from boot mode to user mode */
 218        outb(NIWD_CONTROL_RESET | NIWD_CONTROL_MODE,
 219             wdt->io_base + NIWD_CONTROL);
 220
 221        dev_dbg(dev, "io_base=0x%04X, timeout=%d, nowayout=%d\n",
 222                wdt->io_base, timeout, nowayout);
 223
 224        return 0;
 225}
 226
 227static int ni903x_acpi_remove(struct acpi_device *device)
 228{
 229        struct ni903x_wdt *wdt = acpi_driver_data(device);
 230
 231        ni903x_wdd_stop(&wdt->wdd);
 232        watchdog_unregister_device(&wdt->wdd);
 233
 234        return 0;
 235}
 236
 237static const struct acpi_device_id ni903x_device_ids[] = {
 238        {"NIC775C", 0},
 239        {"", 0},
 240};
 241MODULE_DEVICE_TABLE(acpi, ni903x_device_ids);
 242
 243static struct acpi_driver ni903x_acpi_driver = {
 244        .name = NIWD_NAME,
 245        .ids = ni903x_device_ids,
 246        .ops = {
 247                .add = ni903x_acpi_add,
 248                .remove = ni903x_acpi_remove,
 249        },
 250};
 251
 252module_acpi_driver(ni903x_acpi_driver);
 253
 254MODULE_DESCRIPTION("NI 903x Watchdog");
 255MODULE_AUTHOR("Jeff Westfahl <jeff.westfahl@ni.com>");
 256MODULE_AUTHOR("Kyle Roeschley <kyle.roeschley@ni.com>");
 257MODULE_LICENSE("GPL");
 258