⑩tiny4412 Linux驱动开发之Watchdog驱动程序

本次来开发自己的watchdog驱动程序.watchdog是系统的硬件守护者,在Linux里面的守护进程则是软件守护者,两者维护的对象有一些差异,这里先暂不做讨论,直接说看门狗的驱动:

在三星平台片上集成了看门狗外设,这种外设只有4个寄存器,别看寄存器比较少,但是它使用的频率是很高的,从结构上看,可以把它看作一个定时器,当然,看门狗也可以作为定时器使用,我们在驱动代码里面已经写了一个开关,开关为0时,作为看门狗复位使用,开关为1时,作为定时器使用,因为,片上已经集成了很多专门的定时器,所以,一般没必要用看门狗去做定时器用,所以,我们写的这个驱动里面没有实现ioctl,在ioctl方法里实际上可以设置看门狗模式,超时时间等等,暂时没有写.我们来看一下三星官方提供的datasheet:


从上图可以看出,一共有4个寄存器,这4个寄存器的作用可以参考下图:


结合图和datasheet,可以知道寄存器的作用如下:

WTCON:    看门狗配置寄存器

WTDAT:    看门狗数据寄存器

WTCNT:    看门狗计数寄存器

WTCLRINT:看门狗清中断寄存器

我们这个驱动主要是实现的是看门狗复位,没有用作定时器,所以WTCLRINT不需要用到,所以没有配置,它的作用就是在配置为中断模式时,随便往这个寄存器写一个不超过0xffffffff的值,就可以清除中断,我们看看官方怎们说的:


"你能使用WTCLRINT寄存器清除中断, 中断服务函数有责任在中断服务完成之后清除中断,方法是写任意值到这个寄存器,不允许读取这个寄存器"(虽然说是任意值,但不要超过0xffffffff,否则会寄存器溢出,因为它是32bit寄存器)

顾名思义喽,如果配制成中断模式,在中断服务函数里执行完后,要往此寄存器写一个值,以清除中断.

下面,我们来看一下其它几个寄存器都是干嘛的:


WTCON配置寄存器只使用了低16位,第0为配置重启是否有效,第2位配置中断是否有效,第[4:3]这两位配置二级时钟分频,第5位是看门狗使能位,第[15:8]这8位是看门狗时钟预分频位,可以配置为0~255,因为配置中不允许是0,所以,实际设计当中是[15:8] + 1,也就是说,配置的数再加1,比如寄存器配置的是255,那么芯片会自动加1,也就是256.从而可以避免是0这种情况发生.

然后接下来是WTDAT和WTCNT寄存器:


它们的作用是WTDAT存储重载数值,WTCNT则是存储计数器值,这个值会递减,当递减到0,正常情况下,则WTDAT的值会重载入WTCNT,从上图可以看出,它们只用到了寄存器的低16位,也就是说其最大值是65535(0xffff).

好了了解了寄存器之后,我们就直接上代码,代码里关键地方有写注释,所以设计思路就不讲了,直接看代码(参考s3c2410_wdt.c):

#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/platform_device.h>
#include <linux/types.h>
#include <linux/timer.h>
#include <linux/miscdevice.h>
#include <linux/watchdog.h>
#include <linux/interrupt.h>
#include <linux/clk.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cpufreq.h>
#include <linux/err.h>
#include <linux/of.h>

#include <mach/map.h>

#include <plat/regs-watchdog.h>


#undef  S3C_VA_WATCHDOG
#define S3C_VA_WATCHDOG (0)

#ifdef CONFIG_CPU_FREQ
#undef CONFIG_CPU_FREQ
#endif

#define CONFIG_S3C2410_WATCHDOG_ATBOOT          (0)
#define CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME    (15)



struct wdt_object{
    bool    nowayout;       // 是否允许看门狗被关闭
    int     tmr_margin;     // 默认喂狗时间
    int     tmr_atboot;     // 系统启动时是否启动看门狗
    int     soft_noboot;    // 看门狗工作模式,0:复位 1,中断
    unsigned int    wdt_count;
    void __iomem    *wdt_base;
    struct device   *wdt_dev;
    struct resource *wdt_mem;
    struct resource *wdt_irq;
    struct clk      *wdt_clock;
};

struct wdt_object *wdt_drv;
static DEFINE_SPINLOCK(wdt_lock);

void __exynos4_wdt_stop(void)
{
    unsigned long wtcon;

    wtcon = readl(wdt_drv->wdt_base + S3C2410_WTCON);
    wtcon &= ~(S3C2410_WTCON_ENABLE | S3C2410_WTCON_RSTEN);
    writel(wtcon, wdt_drv->wdt_base + S3C2410_WTCON);
}

int 
exynos4_wdt_start(struct watchdog_device *wdd)
{
    unsigned long wtcon;

    spin_lock(&wdt_lock);

    __exynos4_wdt_stop();

    wtcon = readl(wdt_drv->wdt_base + S3C2410_WTCON);
    wtcon |= S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV128;

    // 设置看门狗工作模式, 0,复位, 1,中断
    if(wdt_drv->soft_noboot){
        wtcon |= S3C2410_WTCON_INTEN;
        wtcon &= ~S3C2410_WTCON_RSTEN;
    } else {
        wtcon &= ~S3C2410_WTCON_INTEN;
        wtcon |= S3C2410_WTCON_RSTEN;
    }

    // 把配置后的参数回写寄存器
    writel(wdt_drv->wdt_count, wdt_drv->wdt_base + S3C2410_WTDAT);
    writel(wdt_drv->wdt_count, wdt_drv->wdt_base + S3C2410_WTCNT);
    writel(wtcon, wdt_drv->wdt_base + S3C2410_WTCON);

    spin_unlock(&wdt_lock);

    return 0;
}

int 
exynos4_wdt_stop(struct watchdog_device *wdd)
{
    spin_lock(&wdt_lock);

    __exynos4_wdt_stop();

    spin_unlock(&wdt_lock);

    return 0;
}

int 
exynos4_wdt_keeplive(struct watchdog_device *wdd)
{
    spin_lock(&wdt_lock);
    writel(wdt_drv->wdt_count, wdt_drv->wdt_base + S3C2410_WTCNT);
    spin_unlock(&wdt_lock);

    return 0;
}

int 
exynos4_wdt_set_heartbeat(struct watchdog_device *wdd, unsigned int timeout)
{
    unsigned long freq  = clk_get_rate(wdt_drv->wdt_clock);  // 获得watchdog时钟频率
    unsigned long wtcon = 0;    // 配置WTCON值的临时变量
    unsigned int count;         // WTCNT的计数值
    unsigned int divisor = 1;   // WTCON[15:8]的预分频系数

    if(timeout < 1)
        return -EINVAL;

    freq /= 128;                // 默认使用128位分频
    count = timeout * freq;     // 总计数值 = 秒数 * 每秒的时钟滴答数

    // WTCNT是一个16位寄存器,最大值为65535(C中有0的缘故使用65535),也就是0xffff
    if(count >= 0x10000){
        // divisor使用的是WTCON的高8位,所以最大值是255,由于可以配置0,所以设计之初就自动加1,防止是0,所以最大值是255 + 1
        for(divisor = 1; divisor <= 0x100; divisor++){
            if((count / divisor) < 0x10000)
                break;
        }

        // 经过上述分频(预分频和二级分频)之后,如果值仍然大于等于0x10000则返回出错
        if((count / divisor) >= 0x10000){
            printk("timeout %d too big\n", timeout);
            return -EINVAL;
        }
    }

    count /= divisor;   // 分频后,最终计数值
    wdt_drv->wdt_count = count;

    // 配置WTCON
    wtcon = readl(wdt_drv->wdt_base + S3C2410_WTCON);
    wtcon &= ~S3C2410_WTCON_PRESCALE_MASK;
    wtcon |= S3C2410_WTCON_PRESCALE(divisor - 1);

    // 回写寄存器
    writel(count, wdt_drv->wdt_base + S3C2410_WTDAT);
    writel(wtcon, wdt_drv->wdt_base + S3C2410_WTCON);

    // 最终的设置超时值
    wdd->timeout = (count * divisor) / freq;

    return 0;
}

#define OPTIONS (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE)

static const struct watchdog_info exynos4_wdt_ident = {
    .options    = OPTIONS,
    .firmware_version = 0,
    .identity   = "S3C2410 Watchdog"
};

static struct watchdog_ops exynos4_wdt_ops = {
    .owner  = THIS_MODULE,
    .start  = exynos4_wdt_start,
    .stop   = exynos4_wdt_stop,
    .ping   = exynos4_wdt_keeplive,
    .set_timeout = exynos4_wdt_set_heartbeat,
};

struct watchdog_device exynos4_wdd = {
    .info = &exynos4_wdt_ident,
    .ops  = &exynos4_wdt_ops,
};

irqreturn_t 
wdt_irq_handler(int inqno, void *dev_id)
{
    // 我个人感觉这里除了喂狗之外,还需要往WTCLRINT中写一个不大于0xffffffff的值,以清除中断,没有测试,有机会再试试
    exynos4_wdt_keeplive(&exynos4_wdd);

    return IRQ_HANDLED;
}

static inline int exynos4_wdt_is_running(void)
{
    return readl(wdt_drv->wdt_base + S3C2410_WTCON) & S3C2410_WTCON_ENABLE;
}

#ifdef CONFIG_CPU_FREQ
/* 当CPU频率发生改变后,就会通知所以注册CPU频率通知链的所有模块,并执行绑定的相应函数,此处正是看门狗相关的执行函数 */
static int exynos4_wdt_cpufreq_transition(struct notifier_block *nb, unsigned long val, void *data)
{
    int ret;

    if(!exynos4_wdt_is_running())
        goto done;

    if(CPUFREQ_PRECHANGE == val){
        
        exynos4_wdt_keeplive(&exynos4_wdd);

    } else if(CPUFREQ_POSTCHANGE == val){
        exynos4_wdt_stop(&exynos4_wdd);

        // 动态调整WTCON的分频系数和WTDAT的数值
        ret = exynos4_wdt_set_heartbeat(&exynos4_wdd, exynos4_wdd.timeout);
        if(ret >= 0)
            exynos4_wdt_start(&exynos4_wdd);
        else
            return ret;
    }

done:
    return 0;
}

static struct notifier_block exynos4_wdt_cpufreq_transition_nb = {
    .notifier_call  = exynos4_wdt_cpufreq_transition,
};

static inline int exynos4_wdt_cpufreq_register(void)
{
    return cpufreq_register_notifier(&exynos4_wdt_cpufreq_transition_nb,
                            CPUFREQ_TRANSITION_NOTIFIER);
}

static inline void exynos4_wdt_cpufreq_deregister(void)
{
    cpufreq_unregister_notifier(&exynos4_wdt_cpufreq_transition_nb,
                            CPUFREQ_TRANSITION_NOTIFIER);
}
#else
static inline int exynos4_wdt_cpufreq_register(void)
{
    return 0;
}

static inline void exynos4_wdt_cpufreq_deregister(void)
{
}
#endif

int 
exynos4_wdt_probe(struct platform_device *pdev)
{
    int started = 0,
        ret = -1,
        size = 0;

    printk("--------%s--------\n", __func__);

    // 1,申请设备对象
    wdt_drv = kzalloc(sizeof(struct wdt_object), GFP_KERNEL);
    if(NULL == wdt_drv){
        printk("kzalloc failed!\n");
        return -ENOMEM;
    }

    // 2,预置看门狗状态
    wdt_drv->nowayout = WATCHDOG_NOWAYOUT;                         // 不允许看门狗关闭
    wdt_drv->tmr_margin = CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME;    // 设置看门狗复位时间15s
    wdt_drv->tmr_atboot = CONFIG_S3C2410_WATCHDOG_ATBOOT;          // 系统启动时同时启动看门狗

    // 3,记录设备dev
    wdt_drv->wdt_dev = &pdev->dev;

    // 4,获取看门狗内存资源
    wdt_drv->wdt_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if(NULL == wdt_drv->wdt_mem){
        printk("platform get memory failed !\n");
        goto err1;
    }

    // 5,获取看门狗中断资源
    wdt_drv->wdt_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
    if(NULL == wdt_drv->wdt_irq){
        printk("platform get irq failed !\n");
        goto err2;
    }

    // 6,获取内存资源长度,并申请一块IO内存,对应看门狗的3个寄存器
    size = resource_size(wdt_drv->wdt_mem);
    if(!request_mem_region(wdt_drv->wdt_mem->start, size, pdev->name)){
        printk("request mem failed !\n");
        ret = -EBUSY;
        goto err2;
    }

    // 7,将设备内存映射到虚拟空间
    wdt_drv->wdt_base = ioremap(wdt_drv->wdt_mem->start, size);
    if(NULL == wdt_drv->wdt_base){
        printk("ioremap failed !\n");
        ret = -EINVAL;
        goto err3;
    }

    // 8,获取watchdog时钟频率
    wdt_drv->wdt_clock = clk_get(&pdev->dev, "watchdog");
    if(IS_ERR(wdt_drv->wdt_clock)){
        printk("clk get failed !\n");
        ret = PTR_ERR(wdt_drv->wdt_clock);
        goto err4;
    }

    // 9,使有效watchdog时钟
    clk_enable(wdt_drv->wdt_clock);

    // 10,注册CPU频率通知链
    ret = exynos4_wdt_cpufreq_register();
    if(ret < 0){
        printk("cpu frequency register failed !\n");
        goto err5;
    }

    // 11,设置看门狗复位时间tmr_margin,如果设置时间不合法,返回非0,并重置为默认时间
    if(exynos4_wdt_set_heartbeat(&exynos4_wdd, wdt_drv->tmr_margin)){
        started = exynos4_wdt_set_heartbeat(&exynos4_wdd,
                        CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME);
        if(0 == started)
            printk("wdt used default time !\n");
        else
            printk("default time is fault, cannot start\n");
    }

    // 12,申请中断,并注册中断处理函数
    ret = request_irq(wdt_drv->wdt_irq->start, wdt_irq_handler, 0, pdev->name, pdev);
    if(ret != 0){
        printk("request irq failed !\n");
        goto err6;
    }

    // 13,设置nowayout进status
    watchdog_set_nowayout(&exynos4_wdd, wdt_drv->nowayout);

    // 14,注册看门狗驱动
    ret = watchdog_register_device(&exynos4_wdd);
    if(ret){
        printk("watchdog register failed !\n");
        goto err7;
    }

    // 15,判断是否开机就启动watchdog,并做出相应动作
    if(wdt_drv->tmr_atboot && 0 == started){
        printk("starting watchdog timer...\n");
        exynos4_wdt_start(&exynos4_wdd);
    } else if(!wdt_drv->tmr_atboot){
        exynos4_wdt_stop(&exynos4_wdd);
    }

    return 0;

err7:
    free_irq(wdt_drv->wdt_irq->start, pdev);
err6:
    exynos4_wdt_cpufreq_deregister();
err5:
    clk_disable(wdt_drv->wdt_clock);
    clk_put(wdt_drv->wdt_clock);
    wdt_drv->wdt_clock = NULL;
err4:
    iounmap(wdt_drv->wdt_base);
err3:
    release_mem_region(wdt_drv->wdt_mem->start, size);
err2:
    wdt_drv->wdt_irq = NULL;
    wdt_drv->wdt_mem = NULL;
err1:
    kfree(wdt_drv);
    return ret;
}

int 
exynos4_wdt_remove(struct platform_device *pdev)
{
    watchdog_unregister_device(&exynos4_wdd);
    free_irq(wdt_drv->wdt_irq->start, pdev);

    exynos4_wdt_cpufreq_deregister();

    clk_disable(wdt_drv->wdt_clock);
    clk_put(wdt_drv->wdt_clock);
    wdt_drv->wdt_clock = NULL;

    iounmap(wdt_drv->wdt_base);

    release_mem_region(wdt_drv->wdt_mem->start, resource_size(wdt_drv->wdt_mem));
    wdt_drv->wdt_irq = NULL;
    wdt_drv->wdt_mem = NULL;
    kfree(wdt_drv);

    return 0;
}

void 
exynos4_wdt_shutdown(struct platform_device *pdev)
{
    exynos4_wdt_stop(&exynos4_wdd);
}

#if defined CONFIG_PM
static unsigned long wtcon_save;
static unsigned long wtdat_save;

int 
exynos4_wdt_suspend(struct platform_device *pdev, pm_message_t state)
{
    wtcon_save = readl(wdt_drv->wdt_base + S3C2410_WTCON);
    wtdat_save = readl(wdt_drv->wdt_base + S3C2410_WTDAT);

    exynos4_wdt_stop(&exynos4_wdd);

    return 0;
}

int 
exynos4_wdt_resume(struct platform_device *pdev)
{
    writel(wtcon_save, wdt_drv->wdt_base + S3C2410_WTCON);
    writel(wtdat_save, wdt_drv->wdt_base + S3C2410_WTDAT);
    writel(wtdat_save, wdt_drv->wdt_base + S3C2410_WTCNT);

    return 0;
}
#else
#define exynos4_wdt_suspend     NULL
#define exynos4_wdt_resume      NULL
#endif

static const struct of_device_id exynos4_wdt_match[] = {
    {.compatible = "samsung,s3c2410-wdt"},
    {},
};

struct platform_driver exynos4_wdt_drv = {
    .driver = {
        .owner  = THIS_MODULE,
        .name   = "s3c2410-wdt",
        .of_match_table = of_match_ptr(exynos4_wdt_match),
    },
    .probe  = exynos4_wdt_probe,
    .remove = exynos4_wdt_remove,
    .shutdown=exynos4_wdt_shutdown,
    .suspend= exynos4_wdt_suspend,
    .resume = exynos4_wdt_resume,
};

static void __exit
exynos4_wdt_exit(void)
{
    platform_driver_unregister(&exynos4_wdt_drv);
}

static int __init
exynos4_wdt_init(void)
{
    return platform_driver_register(&exynos4_wdt_drv);
}

module_init(exynos4_wdt_init);
module_exit(exynos4_wdt_exit);

MODULE_LICENSE("GPL");

驱动程序使用了平台总线,平台数据定义在devs.c里面,可以自己去看一下,然后,再来一个测试代码:

#include <stdio.h>  
#include <unistd.h>  
#include <linux/watchdog.h>  
#include <fcntl.h>  


int main(void)  
{  
    int fd, val, ret;  
      
    fd = open("/dev/watchdog", O_RDWR);  
    if(fd < 0){  
        printf("open device fail\n");  
        return -1;  
    }  
      
    while(1){  
        ret = write(fd, &val, sizeof(val));  
        if(ret < 0){  
            perror("watchdog write wrong\n");  
            return -1;  
        } 
        printf("nihao 0419 watchdog test!\n");	
        sleep(1);  
    } 

    if(close(fd) < 0){
	perror("close failed");
	return -1;
    } 
      
    return 0;  
}

本次,我们直接让此驱动和内核一块加载,所以我们需要做以下事情:

1).首先把驱动文件放在\drivers\watchdog\目录下,然后修改此目录下的Kconfig和Makefile文件,如下:

// Kconfig里增加如下代码
config EXYNOS4_WATCHDOG
	tristate "EXYNOS4 Watchdog"
	depends on HAVE_S3C2410_WATCHDOG
	select WATCHDOG_CORE
	help
		customer watchdog demo test
// Makefile里增加如下代码
obj-$(CONFIG_EXYNOS4_WATCHDOG) += exynos4_wdt.o

然后,我们配置一下menuconfig,改成如下:


如上,把S3C2410_Watchdog去掉,把EXYNOS4_Watchdog选上,重新编译内核,然后开发板加载新内核,我们可以看到如下信息:


对,我们在驱动程序里有让probe方法打印函数名,说明开机加载成功,然后,用我们的测试程序进行测试,因为我们在看门狗里配置的是15s,测试程序里是每秒都写一次看门狗,所以,不会重启系统,但是这时,我们按下Ctrl + C,程序停止了,然后等15s之后,系统就会重启了.

猜你喜欢

转载自blog.csdn.net/qq_23922117/article/details/80634958