mirror of
https://github.com/torvalds/linux.git
synced 2024-12-02 17:11:33 +00:00
487722cf2d
I just can't find any value in MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR) and MODULE_ALIAS_MISCDEV(TEMP_MINOR) statements. Either the device is enumerated and the driver already has a module alias (e.g. PCI, USB etc.) that will get the right driver loaded automatically. Or the device is not enumerated and loading its driver will lead to more or less intrusive hardware poking. Such hardware poking should be limited to a bare minimum, so the user should really decide which drivers should be tried and in what order. Trying them all in arbitrary order can't do any good. On top of that, loading that many drivers at once bloats the kernel log. Also many drivers will stay loaded afterward, bloating the output of "lsmod" and wasting memory. Some modules (cs5535_mfgpt which gets loaded as a dependency) can't even be unloaded! If defining char-major-10-130 is needed then it should happen in user-space. Signed-off-by: Jean Delvare <jdelvare@suse.de> Acked-by: Guenter Roeck <linux@roeck-us.net> Signed-off-by: Wim Van Sebroeck <wim@iguana.be> Cc: Stephen Warren <swarren@wwwdotorg.org> Cc: Mike Frysinger <vapier.adi@gmail.com> Cc: Wan ZongShun <mcuos.com@gmail.com> Cc: Ben Dooks <ben-linux@fluff.org> Cc: Kukjin Kim <kgene.kim@samsung.com> Cc: Zwane Mwaikambo <zwane@arm.linux.org.uk> Cc: Jim Cromie <jim.cromie@gmail.com>
301 lines
7.2 KiB
C
301 lines
7.2 KiB
C
/*
|
|
* RDC321x watchdog driver
|
|
*
|
|
* Copyright (C) 2007-2010 Florian Fainelli <florian@openwrt.org>
|
|
*
|
|
* This driver is highly inspired from the cpu5_wdt driver
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/moduleparam.h>
|
|
#include <linux/types.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/miscdevice.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/init.h>
|
|
#include <linux/ioport.h>
|
|
#include <linux/timer.h>
|
|
#include <linux/completion.h>
|
|
#include <linux/jiffies.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/watchdog.h>
|
|
#include <linux/io.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/mfd/rdc321x.h>
|
|
|
|
#define RDC_WDT_MASK 0x80000000 /* Mask */
|
|
#define RDC_WDT_EN 0x00800000 /* Enable bit */
|
|
#define RDC_WDT_WTI 0x00200000 /* Generate CPU reset/NMI/WDT on timeout */
|
|
#define RDC_WDT_RST 0x00100000 /* Reset bit */
|
|
#define RDC_WDT_WIF 0x00040000 /* WDT IRQ Flag */
|
|
#define RDC_WDT_IRT 0x00000100 /* IRQ Routing table */
|
|
#define RDC_WDT_CNT 0x00000001 /* WDT count */
|
|
|
|
#define RDC_CLS_TMR 0x80003844 /* Clear timer */
|
|
|
|
#define RDC_WDT_INTERVAL (HZ/10+1)
|
|
|
|
static int ticks = 1000;
|
|
|
|
/* some device data */
|
|
|
|
static struct {
|
|
struct completion stop;
|
|
int running;
|
|
struct timer_list timer;
|
|
int queue;
|
|
int default_ticks;
|
|
unsigned long inuse;
|
|
spinlock_t lock;
|
|
struct pci_dev *sb_pdev;
|
|
int base_reg;
|
|
} rdc321x_wdt_device;
|
|
|
|
/* generic helper functions */
|
|
|
|
static void rdc321x_wdt_trigger(unsigned long unused)
|
|
{
|
|
unsigned long flags;
|
|
u32 val;
|
|
|
|
if (rdc321x_wdt_device.running)
|
|
ticks--;
|
|
|
|
/* keep watchdog alive */
|
|
spin_lock_irqsave(&rdc321x_wdt_device.lock, flags);
|
|
pci_read_config_dword(rdc321x_wdt_device.sb_pdev,
|
|
rdc321x_wdt_device.base_reg, &val);
|
|
val |= RDC_WDT_EN;
|
|
pci_write_config_dword(rdc321x_wdt_device.sb_pdev,
|
|
rdc321x_wdt_device.base_reg, val);
|
|
spin_unlock_irqrestore(&rdc321x_wdt_device.lock, flags);
|
|
|
|
/* requeue?? */
|
|
if (rdc321x_wdt_device.queue && ticks)
|
|
mod_timer(&rdc321x_wdt_device.timer,
|
|
jiffies + RDC_WDT_INTERVAL);
|
|
else {
|
|
/* ticks doesn't matter anyway */
|
|
complete(&rdc321x_wdt_device.stop);
|
|
}
|
|
|
|
}
|
|
|
|
static void rdc321x_wdt_reset(void)
|
|
{
|
|
ticks = rdc321x_wdt_device.default_ticks;
|
|
}
|
|
|
|
static void rdc321x_wdt_start(void)
|
|
{
|
|
unsigned long flags;
|
|
|
|
if (!rdc321x_wdt_device.queue) {
|
|
rdc321x_wdt_device.queue = 1;
|
|
|
|
/* Clear the timer */
|
|
spin_lock_irqsave(&rdc321x_wdt_device.lock, flags);
|
|
pci_write_config_dword(rdc321x_wdt_device.sb_pdev,
|
|
rdc321x_wdt_device.base_reg, RDC_CLS_TMR);
|
|
|
|
/* Enable watchdog and set the timeout to 81.92 us */
|
|
pci_write_config_dword(rdc321x_wdt_device.sb_pdev,
|
|
rdc321x_wdt_device.base_reg,
|
|
RDC_WDT_EN | RDC_WDT_CNT);
|
|
spin_unlock_irqrestore(&rdc321x_wdt_device.lock, flags);
|
|
|
|
mod_timer(&rdc321x_wdt_device.timer,
|
|
jiffies + RDC_WDT_INTERVAL);
|
|
}
|
|
|
|
/* if process dies, counter is not decremented */
|
|
rdc321x_wdt_device.running++;
|
|
}
|
|
|
|
static int rdc321x_wdt_stop(void)
|
|
{
|
|
if (rdc321x_wdt_device.running)
|
|
rdc321x_wdt_device.running = 0;
|
|
|
|
ticks = rdc321x_wdt_device.default_ticks;
|
|
|
|
return -EIO;
|
|
}
|
|
|
|
/* filesystem operations */
|
|
static int rdc321x_wdt_open(struct inode *inode, struct file *file)
|
|
{
|
|
if (test_and_set_bit(0, &rdc321x_wdt_device.inuse))
|
|
return -EBUSY;
|
|
|
|
return nonseekable_open(inode, file);
|
|
}
|
|
|
|
static int rdc321x_wdt_release(struct inode *inode, struct file *file)
|
|
{
|
|
clear_bit(0, &rdc321x_wdt_device.inuse);
|
|
return 0;
|
|
}
|
|
|
|
static long rdc321x_wdt_ioctl(struct file *file, unsigned int cmd,
|
|
unsigned long arg)
|
|
{
|
|
void __user *argp = (void __user *)arg;
|
|
u32 value;
|
|
static const struct watchdog_info ident = {
|
|
.options = WDIOF_CARDRESET,
|
|
.identity = "RDC321x WDT",
|
|
};
|
|
unsigned long flags;
|
|
|
|
switch (cmd) {
|
|
case WDIOC_KEEPALIVE:
|
|
rdc321x_wdt_reset();
|
|
break;
|
|
case WDIOC_GETSTATUS:
|
|
/* Read the value from the DATA register */
|
|
spin_lock_irqsave(&rdc321x_wdt_device.lock, flags);
|
|
pci_read_config_dword(rdc321x_wdt_device.sb_pdev,
|
|
rdc321x_wdt_device.base_reg, &value);
|
|
spin_unlock_irqrestore(&rdc321x_wdt_device.lock, flags);
|
|
if (copy_to_user(argp, &value, sizeof(u32)))
|
|
return -EFAULT;
|
|
break;
|
|
case WDIOC_GETSUPPORT:
|
|
if (copy_to_user(argp, &ident, sizeof(ident)))
|
|
return -EFAULT;
|
|
break;
|
|
case WDIOC_SETOPTIONS:
|
|
if (copy_from_user(&value, argp, sizeof(int)))
|
|
return -EFAULT;
|
|
switch (value) {
|
|
case WDIOS_ENABLECARD:
|
|
rdc321x_wdt_start();
|
|
break;
|
|
case WDIOS_DISABLECARD:
|
|
return rdc321x_wdt_stop();
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
break;
|
|
default:
|
|
return -ENOTTY;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t rdc321x_wdt_write(struct file *file, const char __user *buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
if (!count)
|
|
return -EIO;
|
|
|
|
rdc321x_wdt_reset();
|
|
|
|
return count;
|
|
}
|
|
|
|
static const struct file_operations rdc321x_wdt_fops = {
|
|
.owner = THIS_MODULE,
|
|
.llseek = no_llseek,
|
|
.unlocked_ioctl = rdc321x_wdt_ioctl,
|
|
.open = rdc321x_wdt_open,
|
|
.write = rdc321x_wdt_write,
|
|
.release = rdc321x_wdt_release,
|
|
};
|
|
|
|
static struct miscdevice rdc321x_wdt_misc = {
|
|
.minor = WATCHDOG_MINOR,
|
|
.name = "watchdog",
|
|
.fops = &rdc321x_wdt_fops,
|
|
};
|
|
|
|
static int rdc321x_wdt_probe(struct platform_device *pdev)
|
|
{
|
|
int err;
|
|
struct resource *r;
|
|
struct rdc321x_wdt_pdata *pdata;
|
|
|
|
pdata = dev_get_platdata(&pdev->dev);
|
|
if (!pdata) {
|
|
dev_err(&pdev->dev, "no platform data supplied\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
r = platform_get_resource_byname(pdev, IORESOURCE_IO, "wdt-reg");
|
|
if (!r) {
|
|
dev_err(&pdev->dev, "failed to get wdt-reg resource\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
rdc321x_wdt_device.sb_pdev = pdata->sb_pdev;
|
|
rdc321x_wdt_device.base_reg = r->start;
|
|
|
|
err = misc_register(&rdc321x_wdt_misc);
|
|
if (err < 0) {
|
|
dev_err(&pdev->dev, "misc_register failed\n");
|
|
return err;
|
|
}
|
|
|
|
spin_lock_init(&rdc321x_wdt_device.lock);
|
|
|
|
/* Reset the watchdog */
|
|
pci_write_config_dword(rdc321x_wdt_device.sb_pdev,
|
|
rdc321x_wdt_device.base_reg, RDC_WDT_RST);
|
|
|
|
init_completion(&rdc321x_wdt_device.stop);
|
|
rdc321x_wdt_device.queue = 0;
|
|
|
|
clear_bit(0, &rdc321x_wdt_device.inuse);
|
|
|
|
setup_timer(&rdc321x_wdt_device.timer, rdc321x_wdt_trigger, 0);
|
|
|
|
rdc321x_wdt_device.default_ticks = ticks;
|
|
|
|
dev_info(&pdev->dev, "watchdog init success\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int rdc321x_wdt_remove(struct platform_device *pdev)
|
|
{
|
|
if (rdc321x_wdt_device.queue) {
|
|
rdc321x_wdt_device.queue = 0;
|
|
wait_for_completion(&rdc321x_wdt_device.stop);
|
|
}
|
|
|
|
misc_deregister(&rdc321x_wdt_misc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver rdc321x_wdt_driver = {
|
|
.probe = rdc321x_wdt_probe,
|
|
.remove = rdc321x_wdt_remove,
|
|
.driver = {
|
|
.owner = THIS_MODULE,
|
|
.name = "rdc321x-wdt",
|
|
},
|
|
};
|
|
|
|
module_platform_driver(rdc321x_wdt_driver);
|
|
|
|
MODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>");
|
|
MODULE_DESCRIPTION("RDC321x watchdog driver");
|
|
MODULE_LICENSE("GPL");
|