de># include < linux/ irq. h> # include < asm / io. h> # include < linux/ clk. h> # include < linux/ ioport. h> # include < plat/ regs- watchdog. h> # include < linux/ miscdevice. h> /*定义了一个用来保存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/n" ) ; 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/n" , 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/n" ) ; 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/n" ) ; 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/n" ) ; 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()/n" ) ; 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/n" , CONFIG_WATCHDOG_DEFAULT_TIME) ; else dev_info( & pdev- > dev, "default timer value is out of range, cannot start/n" ) ; } /*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)/n" , 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/n" , 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) ; /*释放自旋锁,即解锁*/ } de> |