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 }
probe function

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這些操作硬件的函數注冊進去。

代碼發行的版本都有,就不貼了。

羅里吧嗦那么多,其實就是提了些代碼的流程。對代碼的理解還不夠!要努力啊!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM