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