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