對於在工作中學習驅動的,講究的是先使用,再理解。好吧,我們來看看板子里是如何注冊的?
在板文件里,它的注冊函數是這樣的:
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這些操作硬件的函數注冊進去。
代碼發行的版本都有,就不貼了。
羅里吧嗦那么多,其實就是提了些代碼的流程。對代碼的理解還不夠!要努力啊!