📄 at91_wdt.c
字号:
/* linux/drivers/char/watchdog/at91_wdt.c
*
* AT91SAM9261S Watchdog Timer Support
*
* 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.
*
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/config.h>
#include <linux/types.h>
#include <linux/timer.h>
#include <linux/miscdevice.h>
#include <linux/watchdog.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <asm/arch/map.h>
#include <asm/hardware/clock.h>
#include <asm/arch/hardware.h>
#define DEFAULT_SLOW_CLOCK 32768
#define WDT_MR_WDD_MASK (0xfff << 16)
#define WDT_MR_WDV_MASK (0xfff << 0)
#define WDT_CR 0x00
#define WDT_MR 0x04
#define WDT_SR 0x08
#define WDT_MR_WDIDELHLT (0x1 << 29)
#define WDT_MR_WDDBGHLT (0x1 << 28)
#define WDT_MR_WDDIS (0x1 << 15)
#define WDT_MR_WDRPROC (0x1 << 14)
#define WDT_MR_WDRSTEN (0x1 << 13)
#define WDT_MR_WDFIEN (0x1 << 12)
#define PFX "at91-wdt: "
#define AT91_WATCHDOG_DEFAULT_TIMEOUT (15)
#define RSTC_MR_ERSTL 5 //the external reset length [ 2^(ERSTL+1) slow clock cycles ]
static int nowayout = WATCHDOG_NOWAYOUT;
static int wdt_timeout = AT91_WATCHDOG_DEFAULT_TIMEOUT;
static int debug = 0;
module_param(wdt_timeout, int, 0);
module_param(nowayout, int, 0);
module_param(debug, int, 0);
MODULE_PARM_DESC(wdt_timeout, "Watchdog wdt_timeout in seconds. default=" __MODULE_STRING(AT91_WATCHDOG_DEFAULT_TIMEOUT) ")");
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
MODULE_PARM_DESC(debug, "Watchdog debug, set to >1 for debug, (default 0)");
typedef enum close_state {
CLOSE_STATE_NOT,
CLOSE_STATE_ALLOW = 0x4321
} close_state_t;
static DECLARE_MUTEX(open_lock);
static struct resource *wdt_mem;
static struct resource *wdt_irq;
static void __iomem *wdt_base;
static unsigned int wdt_count;
static close_state_t allow_close;
#define DBG(msg...) do { \
if (debug) \
printk(KERN_INFO msg); \
} while(0)
static int at91wdt_seedwd(void)
{
writel(0xa5000001, wdt_base + WDT_CR);
return 0;
}
static int at91wdt_stop(void)
{
unsigned long wtcon;
wtcon = readl(wdt_base + WDT_MR);
wtcon |= (WDT_MR_WDDIS);
wtcon &= ~(WDT_MR_WDRSTEN);
writel(wtcon, wdt_base + WDT_MR);
return 0;
}
static int at91wdt_start(void)
{
unsigned long wtcon;
at91wdt_stop();
wtcon = readl(wdt_base + WDT_MR);
wtcon |= WDT_MR_WDRSTEN;
wtcon &= ~WDT_MR_WDDIS;
writel(wtcon, wdt_base + WDT_MR);
DBG("%s: wdt_count=0x%08x, wtcon=%08lx\n",
__FUNCTION__, wdt_count, wtcon);
return 0;
}
static int at91wdt_set_counter(int timeout)
{
unsigned int freq = DEFAULT_SLOW_CLOCK;
unsigned int count;
unsigned long wtcon;
if (timeout < 1)
return -EINVAL;
freq /= 128;
count = timeout * freq;
if (count >= 0x10000) {
printk(KERN_ERR PFX "timeout %d too big\n", timeout);
return -EINVAL;
}
wdt_timeout = timeout;
wdt_count = count;
wtcon = readl(wdt_base + WDT_MR);
wtcon &= ~WDT_MR_WDV_MASK;
wtcon |= count;
writel(wtcon, wdt_base + WDT_MR);
DBG("%s: count=%d, timeout=%d, freq=%d\n",
__FUNCTION__, count, timeout, freq);
return 0;
}
static int at91wdt_open(struct inode *inode, struct file *file)
{
if(down_trylock(&open_lock))
return -EBUSY;
if (nowayout) {
__module_get(THIS_MODULE);
} else {
allow_close = CLOSE_STATE_ALLOW;
}
/* start the timer */
at91wdt_start();
return nonseekable_open(inode, file);
}
static int at91wdt_release(struct inode *inode, struct file *file)
{
/*
* Shut off the timer.
* Lock it in if it's a module and we set nowayout
*/
if (allow_close == CLOSE_STATE_ALLOW) {
at91wdt_stop();
} else {
printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n");
at91wdt_seedwd();
}
allow_close = CLOSE_STATE_NOT;
up(&open_lock);
return 0;
}
static ssize_t at91wdt_write(struct file *file, const char __user *data,
size_t len, loff_t *ppos)
{
/*
* Refresh the timer.
*/
if(len) {
if (!nowayout) {
size_t i;
/* In case it was set long ago */
allow_close = CLOSE_STATE_NOT;
for (i = 0; i != len; i++) {
char c;
if (get_user(c, data + i))
return -EFAULT;
if (c == 'V')
allow_close = CLOSE_STATE_ALLOW;
}
}
at91wdt_seedwd();
}
return len;
}
static struct watchdog_info at91_wdt_ident = {
.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
.firmware_version = 0,
.identity = "AT91SAM9261S Watchdog",
};
static int at91wdt_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
void __user *argp = (void __user *)arg;
int __user *p = argp;
int new_margin;
switch (cmd) {
default:
return -ENOIOCTLCMD;
case WDIOC_GETSUPPORT:
return copy_to_user(argp, &at91_wdt_ident,
sizeof(at91_wdt_ident)) ? -EFAULT : 0;
case WDIOC_GETSTATUS:
case WDIOC_GETBOOTSTATUS:
return put_user(0, p);
case WDIOC_KEEPALIVE:
at91wdt_seedwd();
return 0;
case WDIOC_SETTIMEOUT:
if (get_user(new_margin, p))
return -EFAULT;
if (at91wdt_set_counter(new_margin))
return -EINVAL;
at91wdt_seedwd();
return put_user(wdt_timeout, p);
case WDIOC_GETTIMEOUT:
return put_user(wdt_timeout, p);
}
}
static struct file_operations at91wdt_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.write = at91wdt_write,
.ioctl = at91wdt_ioctl,
.open = at91wdt_open,
.release = at91wdt_release,
};
static struct miscdevice at91wdt_miscdev = {
.minor = WATCHDOG_MINOR,
.name = "watchdog",
.fops = &at91wdt_fops,
};
static irqreturn_t at91wdt_irq(int irqno, void *param,
struct pt_regs *regs)
{
printk(KERN_INFO PFX "Watchdog timer expired!\n");
at91wdt_seedwd();
return IRQ_HANDLED;
}
/*
* Set the external reset length on pin NRST for exteral devices connected to the system reset
*/
static int at91rst_set_erstl(void)
{
unsigned long rstc_mr;
rstc_mr = readl(AT91C_VA_BASE_RSTC + 0x08);
rstc_mr = (rstc_mr && 0x0fff0ff) || (RSTC_MR_ERSTL << 8)|| (0x0a5 << 24);
writel(rstc_mr, AT91C_VA_BASE_RSTC + 0x08);
}
static int at91wdt_probe(struct platform_device *pdev)
{
struct resource *res;
int ret;
DBG("%s: probe=%p\n", __FUNCTION__, pdev);
wdt_base = AT91C_VA_BASE_WDT;
DBG("probe: mapped wdt_base=%p\n", wdt_base);
res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (res == NULL) {
printk(KERN_INFO PFX "failed to get irq resource\n");
return -ENOENT;
}
ret = request_irq(res->start, at91wdt_irq, 0, pdev->name, pdev);
if (ret != 0) {
printk(KERN_INFO PFX "failed to install irq (%d)\n", ret);
return ret;
}
readl(wdt_base + WDT_SR);
writel(0x3fff2fff, wdt_base + WDT_MR);//reset WDT_MR just in case
if (at91wdt_set_counter(wdt_timeout)) {
at91wdt_set_counter(AT91_WATCHDOG_DEFAULT_TIMEOUT);
printk(KERN_INFO PFX "wdt_timeout value out of range, default %d used\n",
AT91_WATCHDOG_DEFAULT_TIMEOUT);
}
ret = misc_register(&at91wdt_miscdev);
if (ret) {
printk (KERN_ERR PFX "cannot register miscdev on minor=%d (%d)\n",
WATCHDOG_MINOR, ret);
return ret;
}
at91wdt_stop();
at91rst_set_erstl();
return 0;
}
static int at91wdt_remove(struct platform_device *dev)
{
if (wdt_irq != NULL) {
free_irq(wdt_irq->start, dev);
wdt_irq = NULL;
}
misc_deregister(&at91wdt_miscdev);
return 0;
}
static void at91wdt_shutdown(struct platform_device *dev)
{
at91wdt_stop();
}
static struct platform_driver at91wdt_driver = {
.probe = at91wdt_probe,
.remove = at91wdt_remove,
.shutdown = at91wdt_shutdown,
.driver = {
.owner = THIS_MODULE,
.name = "at91-wdt",
},
};
static int __init watchdog_init(void)
{
printk(KERN_INFO "AT91SAM9261S Watchdog Timer Initialization\n");
return platform_driver_register(&at91wdt_driver);
}
static void __exit watchdog_exit(void)
{
platform_driver_unregister(&at91wdt_driver);
}
module_init(watchdog_init);
module_exit(watchdog_exit);
MODULE_AUTHOR("SOLOO");
MODULE_DESCRIPTION("AT91 Watchdog Device Driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -