linux/drivers/watchdog/ie6xx_wdt.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 *      Intel Atom E6xx Watchdog driver
   4 *
   5 *      Copyright (C) 2011 Alexander Stein
   6 *                <alexander.stein@systec-electronic.com>
   7 */
   8
   9#include <linux/module.h>
  10#include <linux/moduleparam.h>
  11#include <linux/platform_device.h>
  12#include <linux/io.h>
  13#include <linux/kernel.h>
  14#include <linux/types.h>
  15#include <linux/watchdog.h>
  16#include <linux/seq_file.h>
  17#include <linux/debugfs.h>
  18#include <linux/uaccess.h>
  19#include <linux/spinlock.h>
  20
  21#define DRIVER_NAME "ie6xx_wdt"
  22
  23#define PV1     0x00
  24#define PV2     0x04
  25
  26#define RR0     0x0c
  27#define RR1     0x0d
  28#define WDT_RELOAD      0x01
  29#define WDT_TOUT        0x02
  30
  31#define WDTCR   0x10
  32#define WDT_PRE_SEL     0x04
  33#define WDT_RESET_SEL   0x08
  34#define WDT_RESET_EN    0x10
  35#define WDT_TOUT_EN     0x20
  36
  37#define DCR     0x14
  38
  39#define WDTLR   0x18
  40#define WDT_LOCK        0x01
  41#define WDT_ENABLE      0x02
  42#define WDT_TOUT_CNF    0x03
  43
  44#define MIN_TIME        1
  45#define MAX_TIME        (10 * 60) /* 10 minutes */
  46#define DEFAULT_TIME    60
  47
  48static unsigned int timeout = DEFAULT_TIME;
  49module_param(timeout, uint, 0);
  50MODULE_PARM_DESC(timeout,
  51                "Default Watchdog timer setting ("
  52                __MODULE_STRING(DEFAULT_TIME) "s)."
  53                "The range is from 1 to 600");
  54
  55static bool nowayout = WATCHDOG_NOWAYOUT;
  56module_param(nowayout, bool, 0);
  57MODULE_PARM_DESC(nowayout,
  58        "Watchdog cannot be stopped once started (default="
  59                __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
  60
  61static u8 resetmode = 0x10;
  62module_param(resetmode, byte, 0);
  63MODULE_PARM_DESC(resetmode,
  64        "Resetmode bits: 0x08 warm reset (cold reset otherwise), "
  65        "0x10 reset enable, 0x20 disable toggle GPIO[4] (default=0x10)");
  66
  67static struct {
  68        unsigned short sch_wdtba;
  69        spinlock_t unlock_sequence;
  70#ifdef CONFIG_DEBUG_FS
  71        struct dentry *debugfs;
  72#endif
  73} ie6xx_wdt_data;
  74
  75/*
  76 * This is needed to write to preload and reload registers
  77 * struct ie6xx_wdt_data.unlock_sequence must be used
  78 * to prevent sequence interrupts
  79 */
  80static void ie6xx_wdt_unlock_registers(void)
  81{
  82        outb(0x80, ie6xx_wdt_data.sch_wdtba + RR0);
  83        outb(0x86, ie6xx_wdt_data.sch_wdtba + RR0);
  84}
  85
  86static int ie6xx_wdt_ping(struct watchdog_device *wdd)
  87{
  88        spin_lock(&ie6xx_wdt_data.unlock_sequence);
  89        ie6xx_wdt_unlock_registers();
  90        outb(WDT_RELOAD, ie6xx_wdt_data.sch_wdtba + RR1);
  91        spin_unlock(&ie6xx_wdt_data.unlock_sequence);
  92        return 0;
  93}
  94
  95static int ie6xx_wdt_set_timeout(struct watchdog_device *wdd, unsigned int t)
  96{
  97        u32 preload;
  98        u64 clock;
  99        u8 wdtcr;
 100
 101        /* Watchdog clock is PCI Clock (33MHz) */
 102        clock = 33000000;
 103        /* and the preload value is loaded into [34:15] of the down counter */
 104        preload = (t * clock) >> 15;
 105        /*
 106         * Manual states preload must be one less.
 107         * Does not wrap as t is at least 1
 108         */
 109        preload -= 1;
 110
 111        spin_lock(&ie6xx_wdt_data.unlock_sequence);
 112
 113        /* Set ResetMode & Enable prescaler for range 10ms to 10 min */
 114        wdtcr = resetmode & 0x38;
 115        outb(wdtcr, ie6xx_wdt_data.sch_wdtba + WDTCR);
 116
 117        ie6xx_wdt_unlock_registers();
 118        outl(0, ie6xx_wdt_data.sch_wdtba + PV1);
 119
 120        ie6xx_wdt_unlock_registers();
 121        outl(preload, ie6xx_wdt_data.sch_wdtba + PV2);
 122
 123        ie6xx_wdt_unlock_registers();
 124        outb(WDT_RELOAD | WDT_TOUT, ie6xx_wdt_data.sch_wdtba + RR1);
 125
 126        spin_unlock(&ie6xx_wdt_data.unlock_sequence);
 127
 128        wdd->timeout = t;
 129        return 0;
 130}
 131
 132static int ie6xx_wdt_start(struct watchdog_device *wdd)
 133{
 134        ie6xx_wdt_set_timeout(wdd, wdd->timeout);
 135
 136        /* Enable the watchdog timer */
 137        spin_lock(&ie6xx_wdt_data.unlock_sequence);
 138        outb(WDT_ENABLE, ie6xx_wdt_data.sch_wdtba + WDTLR);
 139        spin_unlock(&ie6xx_wdt_data.unlock_sequence);
 140
 141        return 0;
 142}
 143
 144static int ie6xx_wdt_stop(struct watchdog_device *wdd)
 145{
 146        if (inb(ie6xx_wdt_data.sch_wdtba + WDTLR) & WDT_LOCK)
 147                return -1;
 148
 149        /* Disable the watchdog timer */
 150        spin_lock(&ie6xx_wdt_data.unlock_sequence);
 151        outb(0, ie6xx_wdt_data.sch_wdtba + WDTLR);
 152        spin_unlock(&ie6xx_wdt_data.unlock_sequence);
 153
 154        return 0;
 155}
 156
 157static const struct watchdog_info ie6xx_wdt_info = {
 158        .identity =     "Intel Atom E6xx Watchdog",
 159        .options =      WDIOF_SETTIMEOUT |
 160                        WDIOF_MAGICCLOSE |
 161                        WDIOF_KEEPALIVEPING,
 162};
 163
 164static const struct watchdog_ops ie6xx_wdt_ops = {
 165        .owner =        THIS_MODULE,
 166        .start =        ie6xx_wdt_start,
 167        .stop =         ie6xx_wdt_stop,
 168        .ping =         ie6xx_wdt_ping,
 169        .set_timeout =  ie6xx_wdt_set_timeout,
 170};
 171
 172static struct watchdog_device ie6xx_wdt_dev = {
 173        .info =         &ie6xx_wdt_info,
 174        .ops =          &ie6xx_wdt_ops,
 175        .min_timeout =  MIN_TIME,
 176        .max_timeout =  MAX_TIME,
 177};
 178
 179#ifdef CONFIG_DEBUG_FS
 180
 181static int ie6xx_wdt_show(struct seq_file *s, void *unused)
 182{
 183        seq_printf(s, "PV1   = 0x%08x\n",
 184                inl(ie6xx_wdt_data.sch_wdtba + PV1));
 185        seq_printf(s, "PV2   = 0x%08x\n",
 186                inl(ie6xx_wdt_data.sch_wdtba + PV2));
 187        seq_printf(s, "RR    = 0x%08x\n",
 188                inw(ie6xx_wdt_data.sch_wdtba + RR0));
 189        seq_printf(s, "WDTCR = 0x%08x\n",
 190                inw(ie6xx_wdt_data.sch_wdtba + WDTCR));
 191        seq_printf(s, "DCR   = 0x%08x\n",
 192                inl(ie6xx_wdt_data.sch_wdtba + DCR));
 193        seq_printf(s, "WDTLR = 0x%08x\n",
 194                inw(ie6xx_wdt_data.sch_wdtba + WDTLR));
 195
 196        seq_printf(s, "\n");
 197        return 0;
 198}
 199
 200DEFINE_SHOW_ATTRIBUTE(ie6xx_wdt);
 201
 202static void ie6xx_wdt_debugfs_init(void)
 203{
 204        /* /sys/kernel/debug/ie6xx_wdt */
 205        ie6xx_wdt_data.debugfs = debugfs_create_file("ie6xx_wdt",
 206                S_IFREG | S_IRUGO, NULL, NULL, &ie6xx_wdt_fops);
 207}
 208
 209static void ie6xx_wdt_debugfs_exit(void)
 210{
 211        debugfs_remove(ie6xx_wdt_data.debugfs);
 212}
 213
 214#else
 215static void ie6xx_wdt_debugfs_init(void)
 216{
 217}
 218
 219static void ie6xx_wdt_debugfs_exit(void)
 220{
 221}
 222#endif
 223
 224static int ie6xx_wdt_probe(struct platform_device *pdev)
 225{
 226        struct resource *res;
 227        u8 wdtlr;
 228        int ret;
 229
 230        res = platform_get_resource(pdev, IORESOURCE_IO, 0);
 231        if (!res)
 232                return -ENODEV;
 233
 234        if (!request_region(res->start, resource_size(res), pdev->name)) {
 235                dev_err(&pdev->dev, "Watchdog region 0x%llx already in use!\n",
 236                        (u64)res->start);
 237                return -EBUSY;
 238        }
 239
 240        ie6xx_wdt_data.sch_wdtba = res->start;
 241        dev_dbg(&pdev->dev, "WDT = 0x%X\n", ie6xx_wdt_data.sch_wdtba);
 242
 243        ie6xx_wdt_dev.timeout = timeout;
 244        watchdog_set_nowayout(&ie6xx_wdt_dev, nowayout);
 245        ie6xx_wdt_dev.parent = &pdev->dev;
 246
 247        spin_lock_init(&ie6xx_wdt_data.unlock_sequence);
 248
 249        wdtlr = inb(ie6xx_wdt_data.sch_wdtba + WDTLR);
 250        if (wdtlr & WDT_LOCK)
 251                dev_warn(&pdev->dev,
 252                        "Watchdog Timer is Locked (Reg=0x%x)\n", wdtlr);
 253
 254        ie6xx_wdt_debugfs_init();
 255
 256        ret = watchdog_register_device(&ie6xx_wdt_dev);
 257        if (ret)
 258                goto misc_register_error;
 259
 260        return 0;
 261
 262misc_register_error:
 263        ie6xx_wdt_debugfs_exit();
 264        release_region(res->start, resource_size(res));
 265        ie6xx_wdt_data.sch_wdtba = 0;
 266        return ret;
 267}
 268
 269static int ie6xx_wdt_remove(struct platform_device *pdev)
 270{
 271        struct resource *res;
 272
 273        res = platform_get_resource(pdev, IORESOURCE_IO, 0);
 274        ie6xx_wdt_stop(NULL);
 275        watchdog_unregister_device(&ie6xx_wdt_dev);
 276        ie6xx_wdt_debugfs_exit();
 277        release_region(res->start, resource_size(res));
 278        ie6xx_wdt_data.sch_wdtba = 0;
 279
 280        return 0;
 281}
 282
 283static struct platform_driver ie6xx_wdt_driver = {
 284        .probe          = ie6xx_wdt_probe,
 285        .remove         = ie6xx_wdt_remove,
 286        .driver         = {
 287                .name   = DRIVER_NAME,
 288        },
 289};
 290
 291static int __init ie6xx_wdt_init(void)
 292{
 293        /* Check boot parameters to verify that their initial values */
 294        /* are in range. */
 295        if ((timeout < MIN_TIME) ||
 296            (timeout > MAX_TIME)) {
 297                pr_err("Watchdog timer: value of timeout %d (dec) "
 298                  "is out of range from %d to %d (dec)\n",
 299                  timeout, MIN_TIME, MAX_TIME);
 300                return -EINVAL;
 301        }
 302
 303        return platform_driver_register(&ie6xx_wdt_driver);
 304}
 305
 306static void __exit ie6xx_wdt_exit(void)
 307{
 308        platform_driver_unregister(&ie6xx_wdt_driver);
 309}
 310
 311late_initcall(ie6xx_wdt_init);
 312module_exit(ie6xx_wdt_exit);
 313
 314MODULE_AUTHOR("Alexander Stein <alexander.stein@systec-electronic.com>");
 315MODULE_DESCRIPTION("Intel Atom E6xx Watchdog Device Driver");
 316MODULE_LICENSE("GPL");
 317MODULE_ALIAS("platform:" DRIVER_NAME);
 318