博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
IMX6Q RTC驱动分析
阅读量:6812 次
发布时间:2019-06-26

本文共 7428 字,大约阅读时间需要 24 分钟。

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

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

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_IMX6Q12 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 RTC16  * in the /dev/misc directory. It also reads the RTC value from external source17  * 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 the28  * 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这些操作硬件的函数注册进去。

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

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

活着,就要自立自强~fighting~

转载于:https://www.cnblogs.com/subo_peng/p/5324394.html

你可能感兴趣的文章
轻松上云系列之二:其他云数据迁移至阿里云
查看>>
sql server 高可用性技术总结
查看>>
Robot Framework之分层测试流程
查看>>
学习ASP.NET Core Razor 编程系列七——修改列表页面
查看>>
PostgreSQL 10.1 手册_部分 III. 服务器管理_第 23 章 本地化_23.3. 字符集支持
查看>>
读Kafka Consumer源码
查看>>
Android Robolectric使用
查看>>
WPF中的多进程(Threading)处理实例(二)
查看>>
redis 系列7 数据结构之跳跃表
查看>>
Jmeter二次开发环境搭建
查看>>
Mysql 用中间件atlas进行读写分离(学习笔记十四)
查看>>
想要保护自主品牌知识产权需要了解商标注册的一些技巧
查看>>
获取图片的长和宽
查看>>
Java基础算法详解
查看>>
OpenCV3 自动白平衡:灰度世界和完美反射算法
查看>>
Gradle特殊用法
查看>>
[雪峰磁针石博客]2018最佳人工智能数据采集(爬虫)工具书下载
查看>>
手把手学IOT服务端API编程[3、查询产品]|MVP讲堂
查看>>
Java 8新特性
查看>>
查找依赖库的最新版本
查看>>