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