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~
  • 相关阅读:
    P2015 二叉苹果树(树形DP)
    Treats for the Cows (区间DP)
    You Are the One(区间DP 好题)
    Palindrome subsequence (区间DP)
    Cutting Sticks(区间DP)
    L2-013 红色警报 (dfs判断图连通性)
    L2-001 紧急救援 (dijkstra+dfs回溯路径)
    多线程 -- JMM、volatile关键字、内存屏障、happens-before原则、缓存一致性
    多线程 -- 各种锁的概念
    Spring Boot 学习笔记(十六)启动原理、运行流程、自动配置原理
  • 原文地址:https://www.cnblogs.com/subo_peng/p/5324394.html
Copyright © 2011-2022 走看看