S3C2440上看门狗(Watchdog)驱动开发实例讲解

2019-07-12 15:30发布

S3C2440上看门狗(Watchdog)驱动开发实例讲解 S3C2440上看门狗(Watchdog)驱动开发实例讲解  
嵌入式Linux之我行,主要讲述和总结了本人在学习嵌入式linux中的每个步骤。一为总结经验,二希望能给想入门嵌入式Linux的朋友提供方便。如有错误之处,谢请指正。 ·         共享资源,欢迎转载:http://hbhuanggang.cublog.cn 一、开发环境 ·         机:VMWare--Fedora 9 ·         开发板:Mini2440--64MB Nand,Kernel:2.6.30.4 ·         编译器:arm-linux-gcc-4.3.2 二、相关概念 1、平台设备及平台设备驱动: 这个在前面篇幅:S3C2440RTC时钟驱动开发实例讲解中已经讲过了。这里只需了解一下系统为我们定义的看门狗(Watchdog)平台设备及资源情况,在arch/arm/plat-s3c24xx/devs.c中,如下: /* Watchdog */ /*定义了Watchdog平台设备使用的资源,这些资源在驱动程序中都会用到*/ static struct resource s3c_wdt_resource[] = { [0] = { /*Watchdog所使用IO端口资源范围*/ .start = S3C24XX_PA_WATCHDOG, .end = S3C24XX_PA_WATCHDOG + S3C24XX_SZ_WATCHDOG - 1, .flags = IORESOURCE_MEM, }, [1] = { /*Watchdog中断资源*/ .start = IRQ_WDT, .end = IRQ_WDT, .flags = IORESOURCE_IRQ, } }; /*定义了Watchdog平台设备*/ struct platform_device s3c_device_wdt = { .name = "s3c2410-wdt", /*设备名称*/ .id = -1, .num_resources = ARRAY_SIZE(s3c_wdt_resource), /*资源数量*/ .resource = s3c_wdt_resource, /*引用上面定义的资源*/ }; EXPORT_SYMBOL(s3c_device_wdt);
2、混杂设备(misc设备) misc设备是Linux定义的主设备号为10的特殊字符设备,因为不符合字符设备的范畴,所以被归纳为misc设备,在Linux中有很多这种设备,例如:LED设备、Watchdog设备等等,系统会根据设备的次设备号来区分具体是哪个设备,通常这些次设备号被定义在include/linux/miscdevice.h中。在Linux中用miscdevice结构体来描述一个misc设备,这就意味着被定义为misc设备的驱动中就要实现该结构体中的接口函数。该结构体也定义在miscdevice.h中,如下: struct miscdevice { int minor; const char *name; const struct file_operations *fops; struct list_head list; struct device *parent; struct device *this_device; };
三、实例讲解 1Watchdog硬件结构图分析: 我们从结构图和数据手册得知,看门狗Watchdog主要是实现系统自动复位的功能,他是利用芯片内部的定时器,定时输出连接到电路的复位端,程序在一定时间范围内对定时器清零(俗称喂狗),所以程序在正常工作时,定时器总是不能溢出,也就不能产生复位信号;如果程序出现错误,不在定时周期内复位看门狗,那么定时器就会溢出而产生复位信号使系统复位。 S3C2440Watchdog模块提供了三个寄存器来对Watchdog进行操作,他们分别是:定时器控制寄存器WTCON、定时器数据寄存器WTDAT和定时器计数寄存器WTCNT。注意:在对定时器数据寄存器WTDAT进行操作时必须在定时器控制寄存器WTCON使能之前写入一个计数目标值,当Watchdog使能开启后,WTDAT中的值会自动被加载到计数寄存器WTCNT中,然后WatchdogCPU内部的时钟分频和时钟除数因子得到一个工作周期,当每个周期结束时计数寄存器WTCNT中的值会1,直到递减为0时,如果还不重新往WTCNT中写入新的计数目标值(喂狗),则Watchdog就产生复位信号使系统复位。关于这些寄存器的功能和寄存器的各个位的操作值请参考数据手册。 2Watchdog驱动程序具体实现步骤(建立驱动文件my2440_watchdog.c) 注意:在每步中,为了让代码逻辑更加有条理和容易理解,就没有考虑代码的顺序,比如函数要先定义后调用。如果要编译此代码,请严格按照C语言的规范来调整代码的顺序。 、依然是驱动程序的最基本结构:Watchdog驱动的初始化和卸载部分及其他,如下: #include #include #include #include /*Watchdog平台驱动结构体,平台驱动结构体定义在platform_device.h中,该结构体内的接口函数在第②、④步中实现*/ static struct platform_driver watchdog_driver = { .probe = watchdog_probe, /*Watchdog探测函数,在第②步中实现*/ .remove = __devexit_p(watchdog_remove),/*Watchdog移除函数, 在第④步中实现*/ .shutdown = watchdog_shutdown, /*Watchdog关闭函数,在第④步中实现*/ .suspend = watchdog_suspend, /*Watchdog挂起函数,在第④步中实现*/ .resume = watchdog_resume, /*Watchdog恢复函数,在第④步中实现*/ .driver = { /*注意这里的名称一定要和系统中定义平台设备的地方一致,这样才能把平台设备与该平台设备的驱动关联起来*/ .name = "s3c2410-wdt", .owner = THIS_MODULE, }, }; static int __init watchdog_init(void) { /*将Watchdog注册成平台设备驱动*/ return platform_driver_register(&watchdog_driver); } static void __exit watchdog_exit(void) { /*注销Watchdog平台设备驱动*/ platform_driver_unregister(&watchdog_driver); } module_init(watchdog_init); module_exit(watchdog_exit); /*驱动程序模块参数,如果在加载驱动模块时没有设定这些参数,则这些参数将采用默认值, 这些参数在接下来的步骤中将被一一用到,参数具体作用也将在各步骤中来说明*/ module_param(tmr_margin, int, 0); module_param(tmr_atboot, int, 0); module_param(nowayout, int, 0); module_param(soft_noboot,int, 0); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Huang Gang"); MODULE_DESCRIPTION("My2440 Watchdog Driver");
Watchdog平台驱动结构中探测函数watchdog_probe的实现。探测就意味着在系统总线中去检测设备的存在,然后获取设备有用的相关资源信息,以便我们使用这些信息。代码如下: #include #include #include #include #include #include /*定义了一个用来保存watchdog的IO端口占用的IO空间和经过虚拟映射后的内存地址*/ static struct resource *wdt_mem; static void __iomem *wdt_base; /*保存watchdog中断号,NO_IRQ宏定义在irq.h中*/ static int wdt_irqno = NO_IRQ; /*保存从平台时钟队列中获取watchdog的时钟*/ static struct clk *wdt_clock; #define CONFIG_WATCHDOG_ATBOOT (0) #define CONFIG_WATCHDOG_DEFAULT_TIME (15) static int tmr_atboot = CONFIG_WATCHDOG_ATBOOT; static int tmr_margin = CONFIG_WATCHDOG_DEFAULT_TIME; static int soft_noboot; static unsigned int wdt_count;/*用于保存经计算后得到的计数寄存器WTCNT的计数值*/ /*申明并初始化一个自旋锁wdt_pie_lock,对Watchdog资源进行互斥访问*/ static DEFINE_SPINLOCK(wdt_pie_lock); static int __devinit watchdog_probe(struct platform_device *pdev) { int ret; int started = 0; struct resource *res;/*定义一个资源,用来保存获取的watchdog的IO资源*/ /*在系统定义的watchdog平台设备中获取watchdog中断号 platform_get_irq定义在platform_device.h中*/ wdt_irqno = platform_get_irq(pdev, 0); if(wdt_irqno < 0) { /*获取watchdog中断号不成功错误处理 dev_err定义在device.h中,在platform_device.h中已经引用,所以这里就不需再引用了*/ dev_err(&pdev->dev, "no irq for watchdog "); return -ENOENT; } /*申请Watchdog中断服务,这里使用的是快速中断:IRQF_DISABLED。 中断服务程序为:wdt_irq,将Watchdog平台设备pdev做参数传递过去了*/ ret = request_irq(wdt_irqno, wdt_irq, IRQF_DISABLED, pdev->name, pdev); if(ret) { /*错误处理*/ dev_err(dev, "IRQ%d error %d ", wdt_irqno, ret); return ret; } /*获取watchdog平台设备所使用的IO端口资源,注意这个IORESOURCE_MEM标志和watchdog平台设备定义中的一致*/ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (res == NULL) { /*错误处理*/ dev_err(&pdev->dev, "failed to get memory region resource "); return -ENOENT; } /*从平台时钟队列中获取watchdog的时钟,这里为什么要取得这个时钟,因为看门狗定时器的工作周期是由这个 时钟和时钟除数因子得到的。注意这里的watchdog参数要与系统中定义的时钟名称一致才能获取得到,也就是 说,系统必须先定义得有watchdog。系统的一些时钟定义在arch/arm/plat-s3c24xx/s3c2410-clock.c中*/ wdt_clock = clk_get(&pdev->dev, "watchdog"); if (IS_ERR(wdt_clock)) { /*错误处理*/ dev_err(&pdev->dev, "failed to find watchdog clock source "); return PTR_ERR(wdt_clock); } /*时钟获取后要使能后才可以使用,clk_enable定义在arch/arm/plat-s3c/clock.c中*/ clk_enable(wdt_clock); /*申请watchdog的IO端口资源所占用的IO空间(要注意理解IO空间和内存空间的区别), request_mem_region定义在ioport.h中*/ wdt_mem = request_mem_region(res->start, res->end - res->start + 1, pdev->name); if (wdt_mem == NULL) { /*错误处理*/ dev_err(&pdev->dev, "failed to reserve memory region "); ret = -ENOENT; goto err_noclk; } /*将watchdog的IO端口占用的这段IO空间映射到内存的虚拟地址,ioremap定义在io.h中。 注意:IO空间要映射后才能使用,以后对虚拟地址的操作就是对IO空间的操作,*/ wdt_base = ioremap(res->start, res->end - res->start + 1); if (wdt_base == NULL) { /*错误处理*/ dev_err(&pdev->dev, "failed ioremap() "); ret = -EINVAL; goto err_noreq; } /*好了,通过上面的步骤已经将watchdog的资源都准备好了,下面就开始使用啦*/ /*这里是计算并设置看门狗定时器时钟周期值,wdt_set_heartbeat定义在下面。符合数据手册中要求的“在看门狗定时 器开始工作之前,一个初始值必须先写入看门狗定时器计数寄存器WTCNT中”。其实这里就是初始化看门狗定时器*/ if (wdt_set_heartbeat(pdev, tmr_margin)) { /*这里调用两次的意思是看能不能设置成期望的值,如果不能就设置默认的值*/ started = wdt_set_heartbeat(pdev, CONFIG_WATCHDOG_DEFAULT_TIME); /*打印设置的值信息*/ if (started == 0) dev_info(&pdev->dev, "tmr_margin value out of range, default %d used ", CONFIG_WATCHDOG_DEFAULT_TIME); else dev_info(&pdev->dev, "default timer value is out of range, cannot start "); } /*device_init_wakeup该函数定义在pm_wakeup.h中,定义如下: static inline void device_init_wakeup(struct device *dev, int val){ dev->power.can_wakeup = dev->power.should_wakeup = !!val;} 显然这个函数是让驱动支持电源管理的,这里只要知道,can_wakeup为1时表明这个设备可以被唤醒,设备驱动为了支持 Linux中的电源管理,有责任调用device_init_wakeup()来初始化can_wakeup,而should_wakeup则是在设备的电源状态 发生变化的时候被device_may_wakeup()用来测试,测试它该不该变化,因此can_wakeup表明的是一种能力, 而should_wakeup表明的是有了这种能力以后去不去做某件事。好了,我们没有必要深入研究电源管理的内容了, 要不就扯远了,电源管路以后再讲*/ device_init_wakeup(&pdev->dev, 1); /*把看门狗设备又注册成为misc设备,misc_register定义在miscdevice.h中 wdt_miscdev结构体定义及内部接口函数在第③步中讲*/ ret = misc_register(&wdt_miscdev); if (ret) { /*错误处理*/ dev_err(&pdev->dev, "cannot register miscdev on minor=%d (%d) ", WATCHDOG_MINOR, ret); goto err_nomap; } /*函数wdt_start_or_stop定义在下面*/ if (tmr_atboot && started == 0) { wdt_start_or_stop(1);/*参数1表示启动看门狗定时器*/ } else if (!tmr_atboot) { wdt_start_or_stop(0);/*参数0表示停止看门狗定时器*/ } return 0; //以下是上面错误处理的跳转点 err_noclk: clk_disable(wdt_clock); clk_put(wdt_clock); err_noreq: release_resource(wdt_mem); kfree(wdt_mem); err_nomap: iounmap(wdt_base); return ret; } /*看门狗定时器中断服务程序*/ static irqreturn_t wdt_irq(int irq, void *argv) { /*主要要做的事情是在看门狗定时器计数寄存器值递减到0之前重新写入新值(即:“喂狗”)*/ wdt_keepalive(); return IRQ_HANDLED; } /*看门狗定时器“喂狗”*/ static void wdt_keepalive(void) { spin_lock(&wdt_lock);/*获取自旋锁保护临界区资源*/ writel(wdt_count, wdt_base + S3C2410_WTCNT);/*往计数寄存器WTCNT重新写入计数值*/ spin_unlock(&wdt_lock);/*释放自旋锁,即解锁*/ } /*计算并设置看门狗定时器时钟周期值并初始化看门狗定时器*/ static int wdt_set_heartbeat(struct platform_device *pdev, int timeout) { unsigned int freq = clk_get_rate(wdt_clock); unsigned int count; unsigned int divisor = 1; unsigned long wtcon; if (timeout < 1) return -EINVAL; freq /= 128; count = timeout * freq; if (count >= 0x10000) { for (divisor = 1; divisor <= 0x100; divisor++) { if ((count / divisor) < 0x10000) break; } if ((count / divisor) >= 0x10000) { dev_err(&pdev->dev, "timeout %d too big ", timeout); return -EINVAL; } } tmr_margin = timeout; count /= divisor; wdt_count = count; wtcon = readl(wdt_base + S3C2410_WTCON);/* wtcon=1000000000100001 这是控制寄存器的默认值,看数据手册得到*/ wtcon &= ~S3C2410_WTCON_PRESCALE_MASK; /* wtcon=1000000000100001 & ~1111111100000000 = 0000000000100001 */ /*S3C2410_WTCON_PRESCALE宏是将divisor-1的值向左位移8位,也就是说该值的右8位都为0,则计算如下: wtcon=0000000000100001 | (xxxxxxxx)00000000 = (xxxxxxxx)00100001*/ wtcon |= S3C2410_WTCON_PRESCALE(divisor-1); /*设置看门狗定时器数据寄存器WTDAT的值,然后WTDAT的值会自动加载到WTCNT中*/ writel(count, wdt_base + S3C2410_WTDAT); /*根据数据手册和上面计算的wtcon值可得,下面是设置看门狗定时控制寄存器WTCON为: 看门狗定时器输出使能有效、一个保留位默认0、中断使能无效、时钟除数因子为16、 看门狗定时器使能有效、两个保留位默认00、预定标器值为xxxxxxxx的内容。*/ writel(wtcon, wdt_base + S3C2410_WTCON); return 0; } /*根据标志flag的值来启动或者停止看门狗定时器,1表示启动,0表示停止*/ static void wdt_start_or_stop(int flag) { unsigned long wtcon; spin_lock(&wdt_pie_lock);/*获取自旋锁保护临界区资源*/ /*停止看门狗定时器,以下各寄存器的位操作请参照数据手册*/ wtcon = readl(wdt_base + S3C2410_WTCON); wtcon &= ~(S3C2410_WTCON_ENABLE | S3C2410_WTCON_RSTEN); writel(wtcon, wdt_base + S3C2410_WTCON); if(!flag) { wtcon = readl(wdt_base + S3C2410_WTCON); wtcon |= S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV128; if (soft_noboot) { wtcon |= S3C2410_WTCON_INTEN; wtcon &= ~S3C2410_WTCON_RSTEN; } else { wtcon &= ~S3C2410_WTCON_INTEN; wtcon |= S3C2410_WTCON_RSTEN; } writel(wdt_count, wdt_base + S3C2410_WTDAT); writel(wdt_count, wdt_base + S3C2410_WTCNT); writel(wtcon, wdt_base + S3C2410_WTCON); } spin_unlock(&wdt_pie_lock);/*释放自旋锁,即解锁*/ }
、实现misc设备中对设备文件的操作,代码如下: #include #include #include /*申明并初始化一个信号量open_clock,对Watchdog资源进行互斥访问,注意:这里的信号量和第②步中的 自旋锁的区别,虽然都是达到资源互斥访问的目的,但信号量是进程级的,也就是说信号量是用在多个进程 中对同一资源的互斥访问,下面的使用会在wdt_open和wdt_release两个进程中对Watchdog资源进行互斥访问。 对于自旋锁和信号量的具体区别,请在网上找,这里不再多说了*/ static DECLARE_MUTEX(open_clock); /*用来表示Linux内核配置选项中配不配置CONFIG_WATCHDOG_NOWAYOUT项,WATCHDOG_NOWAYOUT定义在watchdog.h中*/ static int nowayout = WATCHDOG_NOWAYOUT; typedef enum close_state { CLOSE_STATE_NOT, CLOSE_STATE_ALLOW = 0x4021 } close_state_t; static close_state_t allow_close; /*用于记录看门狗定时器的当前的操作状态*/ /*misc设备结构体实现*/ static struct miscdevice wdt_miscdev = { .minor = WATCHDOG_MINOR, /*WATCHDOG_MINOR次设备号定义在miscdevice.h中为130*/ .name = "watchdog", /*设备名称*/ .fops = &wdt_fops, /*实现字符设备的相关操作*/ }; /*字符设备的相关操作实现*/ static const struct file_operations wdt_fops = { .owner = THIS_MODULE, .open = wdt_open, .release = wdt_release, .write = wdt_write, .ioctl = wdt_ioctl, .llseek = no_llseek, /*定义为不可定位,即屏蔽seek操作,no_llseek定义在fs.h中*/ }; /*看门狗设备驱动的打开接口函数*/ static int wdt_open(struct inode *inode, struct file *file) { /*试着获取信号量(即:加锁),如果获取不成功,说明其他进程此时占用了,就返回忙*/ if(down_trylock(&open_clock)) { return -EBUSY; } if(nowayout) { /*如果内核配置了CONFIG_WATCHDOG_NOWAYOUT项,则使模块使用计数加1*/ __module_get(THIS_MODULE); } /*开始记录看门狗定时器的当前操作状态为:无状态*/ allow_close = CLOSE_STATE_NOT; /*启动看门狗定时器*/ wdt_start_or_stop(1); /*表示返回的这个设备文件是不可以被seek操作的,nonseekable_open定义在fs.h中*/ return nonseekable_open(inode, file); } /*看门狗设备驱动的关闭接口函数*/ static int wdt_release(struct inode *inode, struct file *file) { /*如果判断到当前操作状态是可以关闭看门狗定时器时就关闭,否则就是“喂狗”状态*/ if(allow_close == CLOSE_STATE_ALLOW) { wdt_start_or_stop(0);/*关闭*/ } else { wdt_keepalive();/*“喂狗”*/ } /*恢复看门狗定时器的当前操作状态为:无状态*/ allow_close = CLOSE_STATE_NOT; /*释放获取的信号量(即:解锁),与wdt_open中加锁相对应*/ up(&open_lock); return 0; } /*看门狗设备驱动的写数据接口函数*/ static ssize_t wdt_write(struct file *file, const char __user *buff, size_t len, loff_t *ppos) { if(len)/*判断有数据写入*/ { if(!nowayout)/*如果没有配置内核CONFIG_WATCHDOG_NOWAYOUT选项*/ { size_t i; /*设看门狗定时器的当前操作状态为:无状态*/ allow_close = CLOSE_STATE_NOT; for (i = 0; i != len; i++) { char c; if (get_user(c, data + i)) return -EFAULT; if (c == 'V')/*判断写入的数据是"V"时,则设看门狗定时器的当前操作状态为关闭*/ allow_close = CLOSE_STATE_ALLOW; } } /*上面的意思是想要看门狗定时器可以被关闭,则内核不要配置CONFIG_WATCHDOG_NOWAYOUT选项, 对于下面这里还要“喂狗”一次,我刚开始觉得不需要,因为在看门狗定时器中断里面不断的在 “喂狗”。后来想想,这里还必须要“喂狗”一次,因为当上面我们判断到写入的数据是"V"时, 看门狗定时器的当前操作状态马上被设置为关闭,再当驱动去调用看门狗设备驱动的关闭接口函数时, 看门狗定时器中断被禁止,无法再实现“喂狗”,所以这里要手动“喂狗”一次,否则定时器溢出系统被复位*/ wdt_keepalive(); } return len; } /*用于支持看门狗IO控制中获取看门狗信息的命令WDIOC_GETSUPPORT, 下面的宏和看门狗信息结构体定义在watchdog.h中*/ #define OPTIONS WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE static const struct watchdog_info wdt_ident = { .options = OPTIONS, .firmware_version = 0, .identity = "S3C2440 Watchdog", }; /*看门狗设备驱动的IO控制接口函数*/ static long wdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { void __user *argp = (void __user *)arg; int __user *p = argp; int new_margin; /*以下对看门狗定时器IO控制的命令定义在watchdog.h中*/ switch (cmd) { case WDIOC_GETSUPPORT: /*获取看门狗的支持信息,wdt_ident定义在上面*/ return copy_to_user(argp, &wdt_ident, sizeof(wdt_ident)) ? -EFAULT : 0; case WDIOC_GETSTATUS: case WDIOC_GETBOOTSTATUS:/*获取看门够状态*/ return put_user(0, p); case WDIOC_KEEPALIVE:/*喂狗命令*/ wdt_keepalive(); return 0; case WDIOC_SETTIMEOUT:/*设置定时器溢出时间值命令(时间单位为秒)*/ if (get_user(new_margin, p))/*获取时间值*/ return -EFAULT; if (wdt_set_heartbeat(new_margin))/*设置到计数寄存器WTCNT中*/ return -EINVAL; wdt_keepalive();/*喂狗*/ return put_user(tmr_margin, p); case WDIOC_GETTIMEOUT:/*读取定时器默认溢出时间值命令(时间单位为秒)*/ return put_user(tmr_margin, p); default: return -ENOTTY; } }
Watchdog平台驱动的设备移除、挂起和恢复接口函数的实现,代码如下: /*Watchdog平台驱动的设备移除接口函数的实现*/ static int __devexit wdt_remove(struct platform_device *dev) { /*释放获取的Watchdog平台设备的IO资源*/ release_resource(wdt_mem); kfree(wdt_mem); wdt_mem = NULL; /*同watchdog_probe中中断的申请相对应,在那里申请中断,这里就释放中断*/ free_irq(wdt_irqno, dev); wdt_irq = NULL; /*释放获取的Watchdog平台设备的时钟*/ clk_disable(wdt_clock); clk_put(wdt_clock); wdt_clock = NULL; /*释放Watchdog设备虚拟地址映射空间*/ iounmap(wdt_base); /*注销misc设备*/ misc_deregister(&wdt_miscdev); return 0; } /*Watchdog平台驱动的设备关闭接口函数的实现*/ static void wdt_shutdown(struct platform_device *dev) { /*停止看门狗定时器*/ wdt_start_or_stop(0); } /*对Watchdog平台设备驱动电源管理的支持。CONFIG_PM这个宏定义在内核中, 当配置内核时选上电源管理,则Watchdog平台驱动的设备挂起和恢复功能均有效, 这时候你应该明白了在第②步中为什么要有device_init_wakeup(&pdev->dev, 1)这句吧!!*/ #ifdef CONFIG_PM /*定义两个变量来分别保存挂起时的WTCON和WTDAT值,到恢复的时候使用*/ static unsigned long wtcon_save; static unsigned long wtdat_save; /*Watchdog平台驱动的设备挂起接口函数的实现*/ static int wdt_suspend(struct platform_device *dev, pm_message_t state) { /*保存挂起时的WTCON和WTDAT值*/ wtcon_save = readl(wdt_base + S3C2410_WTCON); wtdat_save = readl(wdt_base + S3C2410_WTDAT); /*停止看门狗定时器*/ wdt_start_or_stop(0); return 0; } /*Watchdog平台驱动的设备恢复接口函数的实现*/ static int wdt_resume(struct platform_device *dev) { /*恢复挂起时的WTCON和WTDAT值,注意这个顺序*/ writel(wtdat_save, wdt_base + S3C2410_WTDAT); writel(wtdat_save, wdt_base + S3C2410_WTCNT); writel(wtcon_save, wdt_base + S3C2410_WTCON); return 0; } #else /*配置内核时没选上电源管理,Watchdog平台驱动的设备挂起和恢复功能均无效,这两个函数也就无需实现了*/ #define wdt_suspend NULL #define wdt_resume NULL #endif
四、回过头再来分析理解具体Watchdog驱动程序代码的结构 在上面的各步骤中,我已对Watchdog驱动程序的每行代码的作用都做了详细的讲述,但到结尾部分后,也许你会有点找不着北的感觉。的确,整个代码太长,而且用文字的方式也确实很难把整个驱动的结构描述清晰。下面,我就用图形的方式来概括上面各步骤之间的关系,使整个驱动程序的结构更加清晰明了。