linux/drivers/watchdog/m54xx_wdt.c
<<
>>
Prefs
   1/*
   2 * drivers/watchdog/m54xx_wdt.c
   3 *
   4 * Watchdog driver for ColdFire MCF547x & MCF548x processors
   5 * Copyright 2010 (c) Philippe De Muyter <phdm@macqel.be>
   6 *
   7 * Adapted from the IXP4xx watchdog driver, which carries these notices:
   8 *
   9 *  Author: Deepak Saxena <dsaxena@plexity.net>
  10 *
  11 *  Copyright 2004 (c) MontaVista, Software, Inc.
  12 *  Based on sa1100 driver, Copyright (C) 2000 Oleg Drokin <green@crimea.edu>
  13 *
  14 * This file is licensed under  the terms of the GNU General Public
  15 * License version 2. This program is licensed "as is" without any
  16 * warranty of any kind, whether express or implied.
  17 */
  18
  19#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  20
  21#include <linux/module.h>
  22#include <linux/moduleparam.h>
  23#include <linux/types.h>
  24#include <linux/kernel.h>
  25#include <linux/fs.h>
  26#include <linux/miscdevice.h>
  27#include <linux/watchdog.h>
  28#include <linux/init.h>
  29#include <linux/bitops.h>
  30#include <linux/ioport.h>
  31#include <linux/uaccess.h>
  32
  33#include <asm/coldfire.h>
  34#include <asm/m54xxsim.h>
  35#include <asm/m54xxgpt.h>
  36
  37static bool nowayout = WATCHDOG_NOWAYOUT;
  38static unsigned int heartbeat = 30;     /* (secs) Default is 0.5 minute */
  39static unsigned long wdt_status;
  40
  41#define WDT_IN_USE              0
  42#define WDT_OK_TO_CLOSE         1
  43
  44static void wdt_enable(void)
  45{
  46        unsigned int gms0;
  47
  48        /* preserve GPIO usage, if any */
  49        gms0 = __raw_readl(MCF_GPT_GMS0);
  50        if (gms0 & MCF_GPT_GMS_TMS_GPIO)
  51                gms0 &= (MCF_GPT_GMS_TMS_GPIO | MCF_GPT_GMS_GPIO_MASK
  52                                                        | MCF_GPT_GMS_OD);
  53        else
  54                gms0 = MCF_GPT_GMS_TMS_GPIO | MCF_GPT_GMS_OD;
  55        __raw_writel(gms0, MCF_GPT_GMS0);
  56        __raw_writel(MCF_GPT_GCIR_PRE(heartbeat*(MCF_BUSCLK/0xffff)) |
  57                        MCF_GPT_GCIR_CNT(0xffff), MCF_GPT_GCIR0);
  58        gms0 |= MCF_GPT_GMS_OCPW(0xA5) | MCF_GPT_GMS_WDEN | MCF_GPT_GMS_CE;
  59        __raw_writel(gms0, MCF_GPT_GMS0);
  60}
  61
  62static void wdt_disable(void)
  63{
  64        unsigned int gms0;
  65
  66        /* disable watchdog */
  67        gms0 = __raw_readl(MCF_GPT_GMS0);
  68        gms0 &= ~(MCF_GPT_GMS_WDEN | MCF_GPT_GMS_CE);
  69        __raw_writel(gms0, MCF_GPT_GMS0);
  70}
  71
  72static void wdt_keepalive(void)
  73{
  74        unsigned int gms0;
  75
  76        gms0 = __raw_readl(MCF_GPT_GMS0);
  77        gms0 |= MCF_GPT_GMS_OCPW(0xA5);
  78        __raw_writel(gms0, MCF_GPT_GMS0);
  79}
  80
  81static int m54xx_wdt_open(struct inode *inode, struct file *file)
  82{
  83        if (test_and_set_bit(WDT_IN_USE, &wdt_status))
  84                return -EBUSY;
  85
  86        clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
  87        wdt_enable();
  88        return nonseekable_open(inode, file);
  89}
  90
  91static ssize_t m54xx_wdt_write(struct file *file, const char *data,
  92                                                size_t len, loff_t *ppos)
  93{
  94        if (len) {
  95                if (!nowayout) {
  96                        size_t i;
  97
  98                        clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
  99
 100                        for (i = 0; i != len; i++) {
 101                                char c;
 102
 103                                if (get_user(c, data + i))
 104                                        return -EFAULT;
 105                                if (c == 'V')
 106                                        set_bit(WDT_OK_TO_CLOSE, &wdt_status);
 107                        }
 108                }
 109                wdt_keepalive();
 110        }
 111        return len;
 112}
 113
 114static const struct watchdog_info ident = {
 115        .options        = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT |
 116                                WDIOF_KEEPALIVEPING,
 117        .identity       = "Coldfire M54xx Watchdog",
 118};
 119
 120static long m54xx_wdt_ioctl(struct file *file, unsigned int cmd,
 121                                                         unsigned long arg)
 122{
 123        int ret = -ENOTTY;
 124        int time;
 125
 126        switch (cmd) {
 127        case WDIOC_GETSUPPORT:
 128                ret = copy_to_user((struct watchdog_info *)arg, &ident,
 129                                   sizeof(ident)) ? -EFAULT : 0;
 130                break;
 131
 132        case WDIOC_GETSTATUS:
 133                ret = put_user(0, (int *)arg);
 134                break;
 135
 136        case WDIOC_GETBOOTSTATUS:
 137                ret = put_user(0, (int *)arg);
 138                break;
 139
 140        case WDIOC_KEEPALIVE:
 141                wdt_keepalive();
 142                ret = 0;
 143                break;
 144
 145        case WDIOC_SETTIMEOUT:
 146                ret = get_user(time, (int *)arg);
 147                if (ret)
 148                        break;
 149
 150                if (time <= 0 || time > 30) {
 151                        ret = -EINVAL;
 152                        break;
 153                }
 154
 155                heartbeat = time;
 156                wdt_enable();
 157                /* Fall through */
 158
 159        case WDIOC_GETTIMEOUT:
 160                ret = put_user(heartbeat, (int *)arg);
 161                break;
 162        }
 163        return ret;
 164}
 165
 166static int m54xx_wdt_release(struct inode *inode, struct file *file)
 167{
 168        if (test_bit(WDT_OK_TO_CLOSE, &wdt_status))
 169                wdt_disable();
 170        else {
 171                pr_crit("Device closed unexpectedly - timer will not stop\n");
 172                wdt_keepalive();
 173        }
 174        clear_bit(WDT_IN_USE, &wdt_status);
 175        clear_bit(WDT_OK_TO_CLOSE, &wdt_status);
 176
 177        return 0;
 178}
 179
 180
 181static const struct file_operations m54xx_wdt_fops = {
 182        .owner          = THIS_MODULE,
 183        .llseek         = no_llseek,
 184        .write          = m54xx_wdt_write,
 185        .unlocked_ioctl = m54xx_wdt_ioctl,
 186        .open           = m54xx_wdt_open,
 187        .release        = m54xx_wdt_release,
 188};
 189
 190static struct miscdevice m54xx_wdt_miscdev = {
 191        .minor          = WATCHDOG_MINOR,
 192        .name           = "watchdog",
 193        .fops           = &m54xx_wdt_fops,
 194};
 195
 196static int __init m54xx_wdt_init(void)
 197{
 198        if (!request_mem_region(MCF_GPT_GCIR0, 4, "Coldfire M54xx Watchdog")) {
 199                pr_warn("I/O region busy\n");
 200                return -EBUSY;
 201        }
 202        pr_info("driver is loaded\n");
 203
 204        return misc_register(&m54xx_wdt_miscdev);
 205}
 206
 207static void __exit m54xx_wdt_exit(void)
 208{
 209        misc_deregister(&m54xx_wdt_miscdev);
 210        release_mem_region(MCF_GPT_GCIR0, 4);
 211}
 212
 213module_init(m54xx_wdt_init);
 214module_exit(m54xx_wdt_exit);
 215
 216MODULE_AUTHOR("Philippe De Muyter <phdm@macqel.be>");
 217MODULE_DESCRIPTION("Coldfire M54xx Watchdog");
 218
 219module_param(heartbeat, int, 0);
 220MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds (default 30s)");
 221
 222module_param(nowayout, bool, 0);
 223MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started");
 224
 225MODULE_LICENSE("GPL");
 226