九鼎创展论坛

标题: s3c6410在linux下的WATCHDOG TIMER(看门狗定时器)驱动(2) [打印本页]

作者: armeasy    时间: 2014-10-17 19:44
标题: s3c6410在linux下的WATCHDOG TIMER(看门狗定时器)驱动(2)

在上一篇中看了看门狗在linux中驱动实现的整体架构,作为混杂设备和平台设备存在。现在开始看平台设备对应的probe函数。

static struct platform_driver s3c2410wdt_driver = {
.probe  = s3c2410wdt_probe,
.remove  = s3c2410wdt_remove,
.shutdown  = s3c2410wdt_shutdown,
.suspend  = s3c2410wdt_suspend,
.resume  = s3c2410wdt_resume,
.driver  = {
.owner  = THIS_MODULE,
.name = "s3c2410-wdt",
},
};

1、probe函数:

s3c2410wdt_probe函数源码如下:

/* device interface */
static int s3c2410wdt_probe(struct platform_device *pdev)
{
struct resource *res;
struct device *dev;
unsigned int wtcon;
int started = 0;
int ret;
int size;
DBG("%s: probe=%p\n", __func__, pdev);


dev = &pdev->dev;
wdt_dev = &pdev->dev;


/* get the memory region for the watchdog timer */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res == NULL) {
dev_err(dev, "no memory resource specified\n");
return -ENOENT;
} 获得看门狗的内存资源


size = (res->end - res->start) + 1; 内存资源所占的字节数
wdt_mem = request_mem_region(res->start, size, pdev->name);申请I/O内存
if (wdt_mem == NULL) {
dev_err(dev, "failed to get memory region\n");
ret = -ENOENT;
goto err_req;
}

wdt_base = ioremap(res->start, size);进行内存映射,得到对应的虚拟地址
if (wdt_base == NULL) {
dev_err(dev, "failed to ioremap() region\n");
ret = -EINVAL;
goto err_req;
}
DBG("probe: mapped wdt_base=%p\n", wdt_base);


wdt_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);获得看门狗的中断号
if (wdt_irq == NULL) {
dev_err(dev, "no irq resource specified\n");
ret = -ENOENT;
goto err_map;
}


ret = request_irq(wdt_irq->start, s3c2410wdt_irq, 0, pdev->name, pdev);
if (ret != 0) {
dev_err(dev, "failed to install irq (%d)\n", ret);
goto err_map;
}申请中断,并注册中断处理函数s3c2410wdt_irq()


wdt_clock = clk_get(&pdev->dev, "watchdog");得到看门狗的时钟源
if (IS_ERR(wdt_clock)) {
dev_err(dev, "failed to find watchdog clock source\n");
ret = PTR_ERR(wdt_clock);
goto err_irq;
}


clk_enable(wdt_clock);使能看门狗时钟


/* see if we can actually set the requested timer margin, and if
* not, try the default value */设置看门狗复位时间为tmr_margin,如果时间不合法,则返回非0,重新设置为默认值。主要涉及到s3c2410wdt_set_heartbeat函数,源码看下面:
if (s3c2410wdt_set_heartbeat(tmr_margin)) {
started = s3c2410wdt_set_heartbeat(
CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME);


if (started == 0)
dev_info(dev,
  "tmr_margin value out of range, default %d used\n",
      CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME);
else
dev_info(dev, "default timer value is out of range, cannot start\n");
}

s3c2410wdt_set_heartbeat函数源码:

static int s3c2410wdt_set_heartbeat(int timeout)
{
unsigned int freq = clk_get_rate(wdt_clock);得到看门狗的时钟频率PCLK
unsigned int count;
unsigned int divisor = 1;
unsigned long wtcon;


if (timeout < 1)看门狗的复位时间不能小于1秒
return -EINVAL;


freq /= 128;看门狗默认使用128的四项分频系数,不太懂。看下图,明明默认值是16,为何说成是128呢?





count = timeout * freq;秒数乘以每秒的时钟数等于计数值


DBG("%s: count=%d, timeout=%d, freq=%d\n",
   __func__, count, timeout, freq);


/* if the count is bigger than the watchdog register,
  then work out what we need to do (and if) we can
  actually make this value
*/

填入WTCNT寄存器的值不能大于0x10000,因为它是16位

if (count >= 0x10000) {

for (divisor = 1; divisor <= 0x100; divisor++) {从1到256之间寻找一个合适的预分频系数
if ((count / divisor) < 0x10000)
break;
}


if ((count / divisor) >= 0x10000) {如果经过预分频和四项分频的计数值仍然大于0x10000,则复位时间太长,看门狗不能达到
dev_err(wdt_dev, "timeout %d too big\n", timeout);
return -EINVAL;
}
}


tmr_margin = timeout;

DBG("%s: timeout=%d, divisor=%d, count=%d (%08x)\n",
   __func__, timeout, divisor, count, count/divisor);

count /= divisor;分频后的计数值
wdt_count = count;


/* update the pre-scaler */
wtcon = readl(wdt_base + S3C2410_WTCON);读WTCON寄存器
wtcon &= ~S3C2410_WTCON_PRESCALE_MASK;将WTCON寄存器高8为清零

其中:#define S3C2410_WTCON_PRESCALE_MASK (0xff00)
wtcon |= S3C2410_WTCON_PRESCALE(divisor-1);填入预分频系数,其中有:

#define S3C2410_WTCON_PRESCALE(x) ((x) << 8)


writel(count, wdt_base + S3C2410_WTDAT);将计数值写入数据寄存器WTDAT
writel(wtcon, wdt_base + S3C2410_WTCON);写回WTCON寄存器


return 0;
}s3c2410wdt_set_heartbeat到这里就结束了。回到s3c2410wdt_probe函数中接着看:




ret = misc_register(&s3c2410wdt_miscdev);注册混杂设备的函数,源码如下:

/**
* misc_register-register a miscellaneous device
* @misc: device structure
*
* Register a miscellaneous device with the kernel. If the minor
* number is set to %MISC_DYNAMIC_MINOR a minor number is assigned
* and placed in the minor field of the structure. For other cases
* the minor number requested is used.
*
* The structure passed is linked into the kernel and may not be
* destroyed until it has been unregistered.
*
* A zero is returned on success and a negative errno code for
* failure.
*/

int misc_register(struct miscdevice * misc)
{
struct miscdevice *c;
dev_t dev;
int err = 0;


INIT_LIST_HEAD(&misc->list);


mutex_lock(&misc_mtx);
list_for_each_entry(c, &misc_list, list) {
if (c->minor == misc->minor) {
mutex_unlock(&misc_mtx);
return -EBUSY;
}
}判断次设备号的有效性


if (misc->minor == MISC_DYNAMIC_MINOR) {
int i = DYNAMIC_MINORS;
while (--i >= 0)
if ( (misc_minors[i>>3] & (1 << (i&7))) == 0)
break;
if (i<0) {
mutex_unlock(&misc_mtx);
return -EBUSY;
}
misc->minor = i;
}


if (misc->minor < DYNAMIC_MINORS)
misc_minors[misc->minor >> 3] |= 1 << (misc->minor & 7);
dev = MKDEV(MISC_MAJOR, misc->minor);

动态分配次设备号时的操作


misc->this_device = device_create(misc_class, misc->parent, dev, NULL,
"%s", misc->name); 看下面这句注释:

* device_create - creates a device and registers it with sysfs
if (IS_ERR(misc->this_device)) {
err = PTR_ERR(misc->this_device);
goto out;
}


/*
* Add it to the front, so that later devices can "override"
* earlier defaults
*/
list_add(&misc->list, &misc_list); 把此设备加入到混杂设备链表
out:
mutex_unlock(&misc_mtx);
return err;
} misc_register函数终于完了。

if (ret) {
dev_err(dev, "cannot register miscdev on minor=%d (%d)\n",
WATCHDOG_MINOR, ret);
goto err_clk;
}


if (tmr_atboot && started == 0) {  开机时就立即启动看门狗定时器
dev_info(dev, "starting watchdog timer\n");
s3c2410wdt_start();启动看门狗,这个函数下篇再说
} else if (!tmr_atboot) {
/* if we're not enabling the watchdog, then ensure it is
* disabled if it has been left running from the bootloader
* or other source */


s3c2410wdt_stop();停止看门狗,同上
}


/* print out a statement of readiness */
wtcon = readl(wdt_base + S3C2410_WTCON);读控制寄存器WTCON


dev_info(dev, "watchdog %sactive, reset %sabled, irq %sabled\n",
(wtcon & S3C2410_WTCON_ENABLE) ?  "" : "in",看门狗能否启动


(wtcon & S3C2410_WTCON_RSTEN) ? "" : "dis",看门狗能否复位


(wtcon & S3C2410_WTCON_INTEN) ? "" : "en"); 看门狗能否中断


return 0;


err_clk:
clk_disable(wdt_clock);
clk_put(wdt_clock);


err_irq:
free_irq(wdt_irq->start, pdev);


err_map:
iounmap(wdt_base);


err_req:
release_resource(wdt_mem);
kfree(wdt_mem);


return ret;
}

注:如果大家对混杂设备的结构还不是很明白,那推荐大家看一篇博文,链接地址在下面:

Linux驱动修炼之道-混杂设备的链接地址

在上面的misc_register函数中,有段动态分配混杂设备次设备号的处理,我但是没具体分析,因为我也不太明白,后来找了下资料,找到篇博文,分析的很好,下面是链接地址:

混杂设备动态次设备号分析的链接地址








欢迎光临 九鼎创展论坛 (http://bbs.9tripod.com/) Powered by Discuz! X3.2