IMX6Q RTC驱动分析

对于在工作中学习驱动的,讲究的是先使用,再理解。好吧,我们来看看板子里是如何注册的?

在板文件里,它的注册函数是这样的:

imx6q_add_imx_snvs_rtc()

好吧,让我们追踪下去:

 1 extern const struct imx_snvs_rtc_data imx6q_imx_snvs_rtc_data __initconst;
 2 #define imx6q_add_imx_snvs_rtc()  3  imx_add_snvs_rtc(&imx6q_imx_snvs_rtc_data)
 4
 5 #define imx_snvs_rtc_data_entry_single(soc)     6  {         7   .iobase = soc ## _SNVS_BASE_ADDR,    8   .irq = soc ## _INT_SNVS,     9  }
10
11 #ifdef CONFIG_SOC_IMX6Q
12 const struct imx_snvs_rtc_data imx6q_imx_snvs_rtc_data __initconst =
13  imx_snvs_rtc_data_entry_single(MX6Q);
14 #endif /* ifdef CONFIG_SOC_IMX6Q */
15
16 struct platform_device *__init imx_add_snvs_rtc(
17   const struct imx_snvs_rtc_data *data)
18 {
19  struct resource res[] = {
20   {
21    .start = data->iobase,
22    .end = data->iobase + SZ_4K - 1,
23    .flags = IORESOURCE_MEM,
24   }, {
25    .start = data->irq,
26    .end = data->irq,
27    .flags = IORESOURCE_IRQ,
28   },
29  };
30
31  return imx_add_platform_device("snvs_rtc", 0,
32    res, ARRAY_SIZE(res), NULL, 0);
33 }

最终调用imx_add_platform_device将rtc注册进去。

那么在驱动端,其代码是如何的呢?分析下主要的部分:

 1 /*!
 2  * The RTC driver structure
 3  */
 4 static struct rtc_class_ops snvs_rtc_ops = {
 5     .open = snvs_rtc_open,
 6     .release = snvs_rtc_release,
 7     .read_time = snvs_rtc_read_time,
 8     .set_time = snvs_rtc_set_time,
 9     .read_alarm = snvs_rtc_read_alarm,
10     .set_alarm = snvs_rtc_set_alarm,
11     .proc = snvs_rtc_proc,
12     .ioctl = snvs_rtc_ioctl,
13     .alarm_irq_enable = snvs_rtc_alarm_irq_enable,
14 };

rtc_class_ops里面的函数实例需要我们去完成。

定义一个platform_driver结构体:

 1 /*!
 2  * Contains pointers to the power management callback functions.
 3  */
 4 static struct platform_driver snvs_rtc_driver = {
 5     .driver = {
 6            .name = "snvs_rtc",     //注意这个要和device端名字一样
 7            },
 8     .probe = snvs_rtc_probe,
 9     .remove = __exit_p(snvs_rtc_remove),
10     .suspend = snvs_rtc_suspend,
11     .resume = snvs_rtc_resume,
12 };

在probe函数中完成rtc_class_ops的注册。好吧,详细的分析下probe:

 1 /*! SNVS RTC Power management control */
 2 static int snvs_rtc_probe(struct platform_device *pdev)
 3 {
 4     struct timespec tv;
 5     struct resource *res;
 6     struct rtc_device *rtc;
 7     struct rtc_drv_data *pdata = NULL;
 8     void __iomem *ioaddr;
 9     u32 lp_cr;
10     int ret = 0;
11
12     res = platform_get_resource(pdev, IORESOURCE_MEM, 0);     //获取资源,即注册在device里面的内存首末地址
13     if (!res)
14         return -ENODEV;
15
16     pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);
17     if (!pdata)
18         return -ENOMEM;
19
20     pdata->baseaddr = res->start;
21     pdata->ioaddr = ioremap(pdata->baseaddr, 0xC00);    //分配IO内存空间
22     ioaddr = pdata->ioaddr;
23     pdata->irq = platform_get_irq(pdev, 0);            //获取中断号
24     platform_set_drvdata(pdev, pdata);                //将rtc_drv_data 赋给platform_device的私有数据
25
26
27     /* Added to support sysfs wakealarm attribute */
28     pdev->dev.power.can_wakeup = 1;                //
29
30     /* initialize glitch detect */                //在分配IO内存后,就可以对其寄存器进行硬件的一些初始化工作。
31     __raw_writel(SNVS_LPPGDR_INIT, ioaddr + SNVS_LPPGDR);
32     udelay(100);
33
34     /* clear lp interrupt status */
35     __raw_writel(0xFFFFFFFF, ioaddr + SNVS_LPSR);
36
37     /* Enable RTC */
38     lp_cr = __raw_readl(ioaddr + SNVS_LPCR);
39     if ((lp_cr & SNVS_LPCR_SRTC_ENV) == 0)
40         __raw_writel(lp_cr | SNVS_LPCR_SRTC_ENV, ioaddr + SNVS_LPCR);
41
42     udelay(100);
43
44     __raw_writel(0xFFFFFFFF, ioaddr + SNVS_LPSR);
45     udelay(100);
46
47     if (pdata->irq >= 0) {                    //设置中断函数
48         if (request_irq(pdata->irq, snvs_rtc_interrupt, IRQF_SHARED,
49                 pdev->name, pdev) < 0) {
50             dev_warn(&pdev->dev, "interrupt not available.\n");
51             pdata->irq = -1;
52         } else {
53             disable_irq(pdata->irq);
54             pdata->irq_enable = false;
55         }
56     }
57
58     rtc = rtc_device_register(pdev->name, &pdev->dev,        //重要!!!RTC设备注册!!!
59                   &snvs_rtc_ops, THIS_MODULE);
60     if (IS_ERR(rtc)) {
61         ret = PTR_ERR(rtc);
62         goto err_out;
63     }
64
65     pdata->rtc = rtc;                            //将注册上的rtc,赋给驱动!
66
67     tv.tv_nsec = 0;
68     tv.tv_sec = rtc_read_lp_counter(ioaddr + SNVS_LPSRTCMR);
69
70     /* Remove can_wakeup flag to add common power wakeup interface */
71     pdev->dev.power.can_wakeup = 0;
72
73     /* By default, devices should wakeup if they can */
74     /* So snvs is set as "should wakeup" as it can */
75     device_init_wakeup(&pdev->dev, 1);
76
77     return ret;
78
79 err_out:
80     iounmap(ioaddr);
81     if (pdata->irq >= 0)
82         free_irq(pdata->irq, pdev);
83     kfree(pdata);
84     return ret;
85 }

OK,上面加了些注释,基本上的流程是这样:先获取device的资源,mem或irq,然后映射内存空间,对硬件进行初始化。注册irq,设置irq服务函数。
接着注册rtc设备,将rtc_class_ops注册到设备里。将rtc设备赋值给rtc驱动。

remove函数

 1 static int __exit snvs_rtc_remove(struct platform_device *pdev)
 2 {
 3     struct rtc_drv_data *pdata = platform_get_drvdata(pdev);
 4     rtc_device_unregister(pdata->rtc);
 5     if (pdata->irq >= 0)
 6         free_irq(pdata->irq, pdev);
 7
 8     kfree(pdata);
 9     return 0;
10 }

获取设备里面驱动数据,然后将驱动里面rtc注销。注销irq。释放驱动数据空间。

驱动里面的init函数和exit函数就很简单了,针对platform_driver进行注册和注销。

 1 /*!
 2  * Contains pointers to the power management callback functions.
 3  */
 4 static struct platform_driver snvs_rtc_driver = {
 5     .driver = {
 6            .name = "snvs_rtc",
 7            },
 8     .probe = snvs_rtc_probe,
 9     .remove = __exit_p(snvs_rtc_remove),
10     .suspend = snvs_rtc_suspend,
11     .resume = snvs_rtc_resume,
12 };
13
14 /*!
15  * This function creates the /proc/driver/rtc file and registers the device RTC
16  * in the /dev/misc directory. It also reads the RTC value from external source
17  * and setup the internal RTC properly.
18  *
19  * @return  -1 if RTC is failed to initialize; 0 is successful.
20  */
21 static int __init snvs_rtc_init(void)
22 {
23     return platform_driver_register(&snvs_rtc_driver);
24 }
25
26 /*!
27  * This function removes the /proc/driver/rtc file and un-registers the
28  * device RTC from the /dev/misc directory.
29  */
30 static void __exit snvs_rtc_exit(void)
31 {
32     platform_driver_unregister(&snvs_rtc_driver);
33
34 }

好吧,这是IMX6Q自带的rtc,它有缺点就是耗电流较大!!!所以freescale的工程师也不建议使用!!!纽扣电池扛不了多久!

我们选用了一个intersil公司的isl1208作为RTC芯片。它的驱动,在发行的linux版本上都有,在config文件中添加就行。如何实现rtc的功能呢???

还是先看device端,因为isl1208是isl1208是I2C接口,所以我们只需在板级端,注册其I2C的信息即可,这个信息包括isl1208的地址,以及驱动名。这两个信息都要注意!

其地址:1101111X,当x为0时是写操作,为1是读操作。在i2c_board_info的注册时是不要后面的x位的,高位右移一位。其地址为0x6f;

驱动名:device的驱动名要和i2c_device_id里面的name一致,而不是i2c_driver里面driver的name一致。

好吧,看device端的设置吧:

 1 static struct imxi2c_platform_data mx6q_sabresd_i2c_data = {
 2     .bitrate = 100000,
 3 };
 4
 5 static struct i2c_board_info mxc_i2c0_board_info[] __initdata = {
 6 {
 7         I2C_BOARD_INFO("wm89**", 0x1a),
 8     },
 9     {
10         I2C_BOARD_INFO("ov564x", 0x3c),
11         .platform_data = (void *)&camera_data,
12     },
13     {
14         I2C_BOARD_INFO("mma8451", 0x1c),
15         .platform_data = (void *)&mma8451_position,
16     },
17     {
18         I2C_BOARD_INFO("isl1208", 0x6f),
19     },
20
21 };

在board_init函数里面,加入:

1 imx6q_add_imx_i2c(0, &mx6q_sabresd_i2c_data);

2 i2c_register_board_info(0, mxc_i2c0_board_info, ARRAY_SIZE(mxc_i2c0_board_info));

这样device端就完成了注册工作。

driver端呢?

跟上面的差不多,要完成一个rtc_device_register将isl1208_rtc_ops这些操作硬件的函数注册进去。

代码发行的版本都有,就不贴了。

罗里吧嗦那么多,其实就是提了些代码的流程。对代码的理解还不够!要努力啊!

时间: 2024-10-26 17:25:36

IMX6Q RTC驱动分析的相关文章

【转载】RTC驱动分析

RTC驱动分析 关于RTC驱动的知识点,我看到一篇大牛的超详细的文章, 博文地址一:http://blog.csdn.net/yaozhenguo2006/article/details/6824970 博文地址二:http://helloyesyes.iteye.com/blog/1072433 以下是转载过来的部分知识点: 一. 驱动模型结构        与RTC核心有关的文件有:        /drivers/rtc/class.c          这个文件向linux设备模型核心注

IMX6Q Camera驱动分析 (3)

还是看看 mxc_v4l2_capture.c做了什么吧. 懒得手敲代码了,还是老样子截图说事吧. 可见,Camera驱动还是作为了一个平台驱动. 看看匹配内容和probe函数做了什么吧 于是查看设备树相关部分 看看 mxc_v4l2_probe 函数 进入 init_camera_struct 看看 这是读取设备树的信息,为以后创建master类型的driver做铺垫.注意传入的cam_data一直被填充 看到一个类型为master的驱动生成. 看到了吗,如我第二篇所述,一个master的类型

Linux RTC驱动模型分析

RTC简介 RTC(real-time clock)简称实时时钟,主要作用是用来记时,产生闹钟等.RTC因为有备份电池,所以即使计算机关机掉电,也不会影响RTC记时.而RTC和系统时间(主要靠软件模拟)的区别在于,RTC会在掉电后数据不丢失,在下次启动依旧可以重新设置当前时间给计算机.而系统时间主要靠软件模拟产生,在掉电之后会丢失,需要在下次计算机重新启动之后重新模拟产生.RTC时间在每次系统启动的时候会使用,在以后需要的时候会将设置的时间写入到RTC中,别的时候获取时间都通过软件可以获得. R

linux RTC 驱动模型分析【转】

转自:http://blog.csdn.net/yaozhenguo2006/article/details/6824970 RTC(real time clock)实时时钟,主要作用是给Linux系统提供时间.RTC因为是电池供电的,所以掉电后时间不丢失.Linux内核把RTC用作“离线”的时间与日期维护器.当Linux内核启动时,它从RTC中读取时间与日期,作为基准值.在运行期间内核完全抛开RTC,以软件的形式维护系统的当前时间与日期,并在需要时将时间回写RTC芯片.另外如果RTC提供了IR

RTC驱动模型分析

①RTC设备层: 设备资源的定义:arch/arm/plat-s3c24xx/devs.c static struct resource s3c_rtc_resource[] = { [0] = { .start = S3C24XX_PA_RTC, .end = S3C24XX_PA_RTC + 0xff, .flags = IORESOURCE_MEM, }, [1] = { .start = IRQ_RTC, .end = IRQ_RTC, .flags = IORESOURCE_IRQ,

电源管理之pmu驱动分析

电源管理芯片可以为多设备供电,且这些设备电压电流有所不同.为这些设备提供的稳压器代码模型即为regulator. 说白了regulator就是稳压器,它提供电源供给.简单的可以gpio操作,高电平开电,低电平关电.一般的还包括电流值, 电压值等. 一般regulator有两种不同的电源,即:ldo和sd. Ldo适合电压要求比较稳,但是功率不是很大的设备. Sd适合功率要求比较大,但可以接受较小的纹波的设备. 除此之外pmu还可能集成,charger,battery, 音频功放等等. 首先我们分

20150311 NandFlash驱动分析

20150311 NandFlash驱动分析 2015-03-11 李海沿 一.结构体详解 MTD体系结构: 在linux中提供了MTD(Memory Technology Device,内存技术设备)系统来建立Flash针对linux的统一.抽象的接口 引入MTD后,linux系统中的Flash设备驱动及接口可分为4层: 设备节点 MTD设备层 MTD原始设备层 硬件驱动层 硬件驱动层:Flash硬件驱动层负责底层硬件设备实际的读.写.擦除,Linux MTD设备的NAND型Flash驱动位于

linux串口驱动分析

linux串口驱动分析 硬件资源及描写叙述 s3c2440A 通用异步接收器和发送器(UART)提供了三个独立的异步串行 I/O(SIO)port,每一个port都能够在中断模式或 DMA 模式下操作.UART 使用系统时钟能够支持最高 115.2Kbps 的波特率.每一个 UART 通道对于接收器和发送器包含了 2 个 64 位的 FIFO. 寄存器 名称 地址 在linux中的描写叙述 (2410 和 2440 处理器对内存地址映射关系同样) UART 线性控制寄存器(ULCONn) ULC

rtems 4.11 RTC驱动 (arm, beagle)

RTC驱动的框架在 c/src/lib/libbsp/shared/tod.c 中,大部分功能都已经实现了,入口函数是 rtc_initialize(),BSP要实现的东西非常少. beagle的实现在 c/src/lib/libbsp/arm/beagle/rtc.c中,提供一个 rtc_tbl RTC_Table[] 数组,数组的大小存储在 RTC_Count 全局变量中,每一个RTC_Table元素就是一个可能的RTC芯片,rtc_initialize()时,会调用每个RTC_Table元