九鼎创展论坛
标题: x4412&ibox项目实战70-RTC驱动实验 [打印本页]
作者: armeasy 时间: 2014-10-22 15:07
标题: x4412&ibox项目实战70-RTC驱动实验
linux内核的RTC驱动都保存在kernel/drivers/rtc目录下,相关源码如下:
- kernel/drivers/rtc/alarm.c
- kernel/drivers/rtc/alarm-dev.c
- kernel/drivers/rtc/class.c
- kernel/drivers/rtc/interface.c
- kernel/drivers/rtc/rtc-dev.c
- kernel/drivers/rtc/rtc-lib.c
- kernel/drivers/rtc/rtc-proc.c
- kernel/drivers/rtc/rtc-s3c.c
- kernel/drivers/rtc/rtc-sysfs.c
- kernel/drivers/rtc/rtc-core.h
- kernel/arch/arm/plat-samsung/dev-rtc.c
复制代码 在kernel/drivers/rtc下集成了linux的RTC驱动模型,当我们添加新的平台的RTC驱动时,只需要在此模型的基础上增加与平台相关的驱动即可。默认该目录下有大量平台相关的驱动源码可参考。针对三星平台的驱动源码为rtc-s3c.c。
kernel/drivers/rtc/Makefile文件部分内容如下:
- obj-$(CONFIG_RTC_LIB) += rtc-lib.o
- obj-$(CONFIG_RTC_HCTOSYS) += hctosys.o
- obj-$(CONFIG_RTC_CLASS) += rtc-core.o
- rtc-core-y := class.o interface.o
-
- obj-$(CONFIG_RTC_INTF_ALARM) += alarm.o
- obj-$(CONFIG_RTC_INTF_ALARM_DEV) += alarm-dev.o
- rtc-core-$(CONFIG_RTC_INTF_DEV) += rtc-dev.o
- rtc-core-$(CONFIG_RTC_INTF_PROC) += rtc-proc.o
- rtc-core-$(CONFIG_RTC_INTF_SYSFS) += rtc-sysfs.o
-
- # Keep the list ordered.
- ……
- obj-$(CONFIG_RTC_DRV_S3C) += rtc-s3c.o
- ……
复制代码 从这里可以看出rtc驱动相关的源码文件。rtc-lib.c中封装了一些时钟转换的函数,如每月有多少天,每年有多少天,秒转化为时钟等,这些函数供整个RTC驱动模型调用,不限平台。在rtc-core.h中声明了一些必要的函数,供驱动调用。interface.c中封装了一些接口函数,如读取时钟,设置时钟,读取闹铃,设置闹铃等。alarm.c和alarm-dev.c文件封装了具体的闹铃操作方法。rtc-proc.c文件用于给rtc驱动增加proc文件系统的功能,rtc-sysfs.c文件用于给rtc驱动增加sysfs的功能,class.c、rtc-dev.c、rtc-s3c.c文件构成了RTC驱动的主体,它实现了RTC字符设备驱动,对外封装了IOCTL接口。如果把RTC驱动比作是一辆汽车,rtc-lib.c,rtc-core.h,interface.c,rtc-proc.c,rtc-sysfs.c就相当于汽车的壳子,而class.c,rtc-dev.c以及rtc-s3c.c就相当于汽车的发动机。下面我们就从class.c开始,贯穿整个RTC驱动。
在class.c的初始化函数rtc_init中,通过class_create函数注册了一个类,通过rtc_dev_init函数注册一个字符设备驱动,rtc_sysfs_init函数用于支持sysfs属性。
- static int __init rtc_init(void)
- {
- rtc_class = class_create(THIS_MODULE, "rtc");//注册一个类
- if (IS_ERR(rtc_class)) {
- printk(KERN_ERR "%s: couldn't create class\n", __FILE__);
- return PTR_ERR(rtc_class);
- }
- rtc_class->suspend = rtc_suspend;
- rtc_class->resume = rtc_resume;
- rtc_dev_init();//给字符设备分配设备号
- rtc_sysfs_init(rtc_class);//支持sysfs属性
- return 0;
- }
复制代码 rtc_dev_init函数原型如下:
- void __init rtc_dev_init(void)
- {
- int err;
-
- err = alloc_chrdev_region(&rtc_devt, 0, RTC_DEV_MAX, "rtc");
- if (err < 0)
- printk(KERN_ERR "%s: failed to allocate char dev region\n",
- __FILE__);
- }
复制代码 它仅仅是调用alloc_chrdev_region函数,用于自动分配设备号,将将设备号保存到全局变量rtc_devt中。
rtc_sysfs_init函数原型如下:
- void __init rtc_sysfs_init(struct class *rtc_class)
- {
- rtc_class->dev_attrs = rtc_attrs;
- }
复制代码 这里只是给注册的类rtc_class的成员dev_attrs赋值。到此,class.c的初始化函数也就完成了,程序中还有一个关键的返回rtc_device结构体的函数rtc_device_register,它将会被其他程序调用,字符设备驱动就是在这里注册的。
在kernel/arch/arm/plat-samsung/dev-rtc.c中定义了一个平台设备s3c_device_rtc,同时为这个设备分配了一些资源:
- static struct resource s3c_rtc_resource[] = {
- [0] = {
- .start = S3C_PA_RTC,
- .end = S3C_PA_RTC + 0xff,
- .flags = IORESOURCE_MEM,
- },
- [1] = {
- .start = IRQ_RTC_ALARM,
- .end = IRQ_RTC_ALARM,
- .flags = IORESOURCE_IRQ,
- },
- [2] = {
- .start = IRQ_RTC_TIC,
- .end = IRQ_RTC_TIC,
- .flags = IORESOURCE_IRQ
- }
- };
-
- struct platform_device s3c_device_rtc = {
- .name = "s3c64xx-rtc",
- .id = -1,
- .num_resources = ARRAY_SIZE(s3c_rtc_resource),
- .resource = s3c_rtc_resource,
- };
复制代码 在kernel/arch/arm/mach-exynos/mach-x4412.c中,在平台设备的结构体指针smdk4x12_devices中包含了s3c_device_rtc,通过platform_add_devices函数会将包含s3c_device_rtc在内的一长串平台设备添加进内核。
在rtc-s3c.c的初始化函数s3c_rtc_init中,通过platform_driver_register函数将名为s3c-rtc的平台驱动:
- static struct platform_device_id s3c_rtc_driver_ids[] = {
- {
- .name = "s3c2410-rtc",
- .driver_data = TYPE_S3C2410,
- }, {
- .name = "s3c64xx-rtc",
- .driver_data = TYPE_S3C64XX,
- },
- { }
- };
- MODULE_DEVICE_TABLE(platform, s3c_rtc_driver_ids);
- static struct platform_driver s3c_rtc_driver = {
- .probe = s3c_rtc_probe,
- .remove = __devexit_p(s3c_rtc_remove),
- .suspend = s3c_rtc_suspend,
- .resume = s3c_rtc_resume,
- .id_table = s3c_rtc_driver_ids,
- .driver = {
- .name = "s3c-rtc",
- .owner = THIS_MODULE,
- },
- };
复制代码 很明显,平台驱动结构体s3c_rtc_driver中的驱动名称为s3c-rtc,而在平台设备结构体s3c_device_rtc中的设备名称却为s3c64xx-rtc,二者并不相同,那么探测函数s3c_rtc_probe是如何执行的呢?
事实上,platform_device和platform_driver有两种匹配方式。在kernel/drivers/base/platform.c中,匹配函数原型如下:
- static int platform_match(struct device *dev, struct device_driver *drv)
- {
- struct platform_device *pdev = to_platform_device(dev);
- struct platform_driver *pdrv = to_platform_driver(drv);
-
- /* Attempt an OF style match first */
- if (of_driver_match_device(dev, drv))
- return 1;
-
- /* Then try to match against the id table */
- if (pdrv->id_table)
- return platform_match_id(pdrv->id_table, pdev) != NULL;
-
- /* fall-back to driver name match */
- return (strcmp(pdev->name, drv->name) == 0);
- }
复制代码 该函数的作用就是将平台设备绑定到平台驱动,绑定成功后,平台驱动的probe函数将会执行。可以看到,程序首先判断平台驱动结构体是否存在id_table结构成员,如果存在则比较id_table中的成员名称,如果不存在,再比较platform_driver中的成员名称。
回到rtc-s3c.c文件,由于在platform_driver中已经存在id_table,platform_match函数会调用platform_match_id将平台设备的名称s3c64xx-rtc与结构体数组s3c_rtc_driver_ids中的成员名称比较,如果找到相同的名称,会将匹配上的platform_device_id保存到platform_device结构的id_entry中,在probe的时候就可以通过id_entry中的driver_data判断匹配的具体是哪一组ID。
在探测函数s3c_rtc_probe中,通过platform_get_irq从平台设备中读取中断号,通过platform_get_resource函数从平台设备中获取IO资源,request_mem_region函数用于给IO资源申请内存空间。得到IO资源信息后,通过ioremap函数将内存映像到虚拟地址,供后续寄存器操作。clk_get函数用于获取RTC时钟的CLK结构体,clk_enable函数用于使能RTC时钟,s3c_rtc_enable函数最终打开RTC,RTC开始工作。到这里,就可以获取RTC时钟信息了。
s3c_rtc_gettime函数用于读取时钟信息,并保存到rtc_tm结构体中。
紧接着调用rtc_device_register函数,该函数将完成rtc字符设备的注册,节点的生成。在rtc_device_register函数的开始定义了一个指向rtc_device结构体的指针rtc,并对其成员进行了初始化。在rtc_dev_prepare函数中,调用cdev_init函数初始化一个字符设备:
- void rtc_dev_prepare(struct rtc_device *rtc)
- {
- if (!rtc_devt)
- return;
-
- if (rtc->id >= RTC_DEV_MAX) {
- pr_debug("%s: too many RTC devices\n", rtc->name);
- return;
- }
-
- rtc->dev.devt = MKDEV(MAJOR(rtc_devt), rtc->id);
-
- #ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL
- INIT_WORK(&rtc->uie_task, rtc_uie_task);
- setup_timer(&rtc->uie_timer, rtc_uie_timer, (unsigned long)rtc);
- #endif
-
- cdev_init(&rtc->char_dev, &rtc_dev_fops);
- rtc->char_dev.owner = rtc->owner;
- }
复制代码 在rtc_device_register中,紧接着调用device_register函数,用于在/dev下创建设备节点。在class.c的初始化函数中,通过class_create函数注册了一个类,但是程序并没有调用device_create函数创建设备节点。在这里,device_register函数充当了device_create的功能。
紧接着rtc_dev_add_device函数调用cdev_add函数完成字符设备的注册:
- void rtc_dev_add_device(struct rtc_device *rtc)
- {
- if (cdev_add(&rtc->char_dev, rtc->dev.devt, 1))
- printk(KERN_WARNING "%s: failed to add char device %d:%d\n",
- rtc->name, MAJOR(rtc_devt), rtc->id);
- else
- pr_debug("%s: dev (%d:%d)\n", rtc->name,
- MAJOR(rtc_devt), rtc->id);
- }
复制代码 执行到这里,字符设备驱动已经注册成功,在/dev下会产生rtc0节点。rtc_sysfs_add_device函数用于支持sysfs,在/sys/devices/platform目录下将会产生关于rtc的相关目录和文件。rtc_proc_add_device函数用于支持proc,在/proc目录下将会产生关于rtc的相关文件。
执行完rtc_device_register函数后,会返回rtc结构体,在探测函数s3c_rtc_probe中,通过platform_set_drvdata函数将前面返回的结构体rtc保存到pdev的结构成员中。request_irq函数用于申请RTC闹钟中断,probe函数到此结束。
在rtc-dev.c中,cdev_init函数指定了file_operations结构体rtc_dev_fops:
- static const struct file_operations rtc_dev_fops = {
- .owner = THIS_MODULE,
- .llseek = no_llseek,
- .read = rtc_dev_read,
- .poll = rtc_dev_poll,
- .unlocked_ioctl = rtc_dev_ioctl,
- .open = rtc_dev_open,
- .release = rtc_dev_release,
- .fasync = rtc_dev_fasync,
- };
复制代码 这些成员函数构成了和应用交互数据的桥梁。
作者: chenxingyu 时间: 2014-11-6 09:44
看着不错,顶一个
欢迎光临 九鼎创展论坛 (http://bbs.9tripod.com/) |
Powered by Discuz! X3.2 |