zoukankan      html  css  js  c++  java
  • 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.
    ");
    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这些操作硬件的函数注册进去。

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

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

    活着,就要自立自强~fighting~
  • 相关阅读:
    luogu P1833 樱花 看成混合背包
    luogu P1077 摆花 基础记数dp
    luogu P1095 守望者的逃离 经典dp
    Even Subset Sum Problem CodeForces
    Maximum White Subtree CodeForces
    Sleeping Schedule CodeForces
    Bombs CodeForces
    病毒侵袭持续中 HDU
    病毒侵袭 HDU
    Educational Codeforces Round 35 (Rated for Div. 2)
  • 原文地址:https://www.cnblogs.com/subo_peng/p/5324394.html
Copyright © 2011-2022 走看看