Chinaunix首页 | 论坛 | 博客
  • 博客访问: 260414
  • 博文数量: 128
  • 博客积分: 65
  • 博客等级: 民兵
  • 技术积分: 487
  • 用 户 组: 普通用户
  • 注册时间: 2012-07-24 17:43
个人简介

人生境界:明智、中庸、诚信、谦逊

文章分类

全部博文(128)

文章存档

2014年(12)

2013年(116)

我的朋友

分类: 嵌入式

2013-09-27 13:12:40

        本文的RTC使用I2C接口操作pcf8563,这里针对pcf8563对Linux的RTC框架进行分析。

        Linux常见的驱动在driver目录下都有一个文件夹,进入kernel主目录下的drivers/rtc,发现下面包含了许多芯片的RTC驱动,我们这里是以pcf8563为主,她是我们要分析的核心。其他几个相关联的文件分别是:alarm.c、alarm-dev.c、class.c、hctosys.c、interface.c和rtc-dev.c。
        class.c:向linux设备模型核心注册了一个类RTC,提供了RTC子系统的一些公共函数,让各个RTC驱动注册集成到我们的linux内核中,向驱动程序提供了注册/注销接口。
        rtc-dev.c:定义了基本的设备文件操作函数,用户程序与RTC驱动的接口函数,这里定义了每个ioctl命令需要调用的函数,还有open,read等。
        interface.c:提供了ioctl各个命令需要调用的函数。
        rtc-sysfs.c:与sysfs有关,提供通过sys文件系统操作pcf8563。
        rtc-proc.c:与proc文件系统有关,提供通过proc文件系统操作pcf8563。

        hctosys.c:系统起来之后会调用到这个文件中的rtc_hctosys()函数,主要功能是系统起来的时候去读RTC硬件中的时间,然后更新我们的系统时间。

        rtc.h:定义了与RTC有关的数据结构。

        pcf8563.c是最底层的直接和硬件打交道的驱动文件,pcf8563.c上面一层为interface.c,为接口文件,它主要是对pcf8563.c进行封装,给上层提供统一的接口,屏蔽底层差异化。Interface.c再往上就到了rtc-dev.c.,rtc-dev.c最终生成了/dev/rtc设备节点,上层的应用程序就是通过操作此文件来进行RTC相关的设置系统时间和闹钟等操作的。

        文件关系图如下:


        接下来我们从最底层驱动程序(pcf8563.cRTC核心(class.c来逐一分析。
        看一个设备驱动,一般都从模块初始化和退出函数开始,pcf8563.c的为:
static int __init pcf8563_init(void)
{
    return i2c_add_driver(&pcf8563_driver);
}

static void __exit pcf8563_exit(void)
{
    i2c_del_driver(&pcf8563_driver);
}
        因为pcf8563为I2C接口设备,此处就是添加或者删除I2C设备。接下来看下rtc结构体pcf8563_driver
static struct i2c_driver pcf8563_driver = {
    .driver        = {
        .name    = "rtc-pcf8563",
    },
    .probe        = pcf8563_probe,
    .remove        = pcf8563_remove,
    .id_table    = pcf8563_id,
};
        I2C设备结构体比较简单,主要就是探测和移除函数,首先看下探测设备函数pcf8563_probe
static int pcf8563_probe(struct i2c_client *client,
                const struct i2c_device_id *id)
{
    struct pcf8563 *pcf8563;
    int err = 0;
    dev_dbg(&client->dev, "%s\n", __func__);
    if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
        return -ENODEV;
    pcf8563 = kzalloc(sizeof(struct pcf8563), GFP_KERNEL);
    if (!pcf8563)
        return -ENOMEM;
    dev_info(&client->dev, "chip found, driver version " DRV_VERSION "\n");
    i2c_set_clientdata(client, pcf8563);
    pcf8563->rtc = rtc_device_register(pcf8563_driver.driver.name,
                &client->dev, &pcf8563_rtc_ops, THIS_MODULE);
    if (IS_ERR(pcf8563->rtc)) {
        err = PTR_ERR(pcf8563->rtc);
        goto exit_kfree;
    }
    return 0;
exit_kfree:
    kfree(pcf8563);
    return err;
}
        探测函数比较简单,比较重要的语句为红色标注部分,这里主要涉及到两个部分。
        1、rtc设备注册函数rtc_device_register,此函数完成rtc设备的注册,在后面会重点讲述。
        2、
pcf8563_rtc_ops此结构体定义了操作pcf8563的函数,包括读时间和设置时间等,上层调用的对时间操作就是调用此处的函数,具体如下:

static const struct rtc_class_ops pcf8563_rtc_ops = {
    .read_time    = pcf8563_rtc_read_time,
    .set_time    = pcf8563_rtc_set_time,
};
        读时间函数pcf8563_rtc_read_time,就是通过I2C接口读取pcf8563时间寄存器里的值,具体如下:
static int pcf8563_rtc_read_time(struct device *dev, struct rtc_time *tm)
{
    return pcf8563_get_datetime(to_i2c_client(dev), tm);
}
static int pcf8563_get_datetime(struct i2c_client *client, struct rtc_time *tm)
{
    struct pcf8563 *pcf8563 = i2c_get_clientdata(client);
    unsigned char buf[13] = { PCF8563_REG_ST1 };
    struct i2c_msg msgs[] = {
        { client->addr, 0, 1, buf },    /* setup read ptr */
        { client->addr, I2C_M_RD, 13, buf },    /* read status + date */
    };
    /* read registers */
    if ((i2c_transfer(client->adapter, msgs, 2)) != 2) {
        dev_err(&client->dev, "%s: read error\n", __func__);
        return -EIO;
    }
    if (buf[PCF8563_REG_SC] & PCF8563_SC_LV)
        dev_info(&client->dev,
            "low voltage detected, date/time is not reliable.\n");
    dev_dbg(&client->dev,
        "%s: raw data is st1=%02x, st2=%02x, sec=%02x, min=%02x, hr=%02x, "
        "mday=%02x, wday=%02x, mon=%02x, year=%02x\n",
        __func__,
        buf[0], buf[1], buf[2], buf[3],
        buf[4], buf[5], buf[6], buf[7],
        buf[8]);
    tm->tm_sec = bcd2bin(buf[PCF8563_REG_SC] & 0x7F);
    tm->tm_min = bcd2bin(buf[PCF8563_REG_MN] & 0x7F);
    tm->tm_hour = bcd2bin(buf[PCF8563_REG_HR] & 0x3F); /* rtc hr 0-23 */
    tm->tm_mday = bcd2bin(buf[PCF8563_REG_DM] & 0x3F);
    tm->tm_wday = buf[PCF8563_REG_DW] & 0x07;
    tm->tm_mon = bcd2bin(buf[PCF8563_REG_MO] & 0x1F) - 1; /* rtc mn 1-12 */
    tm->tm_year = bcd2bin(buf[PCF8563_REG_YR]);
    if (tm->tm_year < 70)
        tm->tm_year += 100;    /* assume we are in 1970...2069 */
    /* detect the polarity heuristically. see note above. */
    pcf8563->c_polarity = (buf[PCF8563_REG_MO] & PCF8563_MO_C) ?
        (tm->tm_year >= 100) : (tm->tm_year < 100);
    dev_dbg(&client->dev, "%s: tm is secs=%d, mins=%d, hours=%d, "
        "mday=%d, mon=%d, year=%d, wday=%d\n",
        __func__,
        tm->tm_sec, tm->tm_min, tm->tm_hour,
        tm->tm_mday, tm->tm_mon, tm->tm_year, tm->tm_wday);
    /* the clock can give out invalid datetime, but we cannot return
     * -EINVAL otherwise hwclock will refuse to set the time on bootup.
     */
    if (rtc_valid_tm(tm) < 0)
        dev_err(&client->dev, "retrieved date/time is not valid.\n");
    return 0;
}
        设置时间函数pcf8563_rtc_set_time,就是通过I2C接口写pcf8563时间寄存器里的值,具体如下:
static int pcf8563_rtc_set_time(struct device *dev, struct rtc_time *tm)
{
    return pcf8563_set_datetime(to_i2c_client(dev), tm);
}
static int pcf8563_set_datetime(struct i2c_client *client, struct rtc_time *tm)
{
    struct pcf8563 *pcf8563 = i2c_get_clientdata(client);
    int i, err;
    unsigned char buf[9];
    dev_dbg(&client->dev, "%s: secs=%d, mins=%d, hours=%d, "
        "mday=%d, mon=%d, year=%d, wday=%d\n",
        __func__,
        tm->tm_sec, tm->tm_min, tm->tm_hour,
        tm->tm_mday, tm->tm_mon, tm->tm_year, tm->tm_wday);
    /* hours, minutes and seconds */
    buf[PCF8563_REG_SC] = bin2bcd(tm->tm_sec);
    buf[PCF8563_REG_MN] = bin2bcd(tm->tm_min);
    buf[PCF8563_REG_HR] = bin2bcd(tm->tm_hour);
    buf[PCF8563_REG_DM] = bin2bcd(tm->tm_mday);
    /* month, 1 - 12 */
    buf[PCF8563_REG_MO] = bin2bcd(tm->tm_mon + 1);
    /* year and century */
    buf[PCF8563_REG_YR] = bin2bcd(tm->tm_year % 100);
    if (pcf8563->c_polarity ? (tm->tm_year >= 100) : (tm->tm_year < 100))
        buf[PCF8563_REG_MO] |= PCF8563_MO_C;
    buf[PCF8563_REG_DW] = tm->tm_wday & 0x07;
    /* write register's data */
    for (i = 0; i < 7; i++) {
        unsigned char data[2] = { PCF8563_REG_SC + i,
                        buf[PCF8563_REG_SC + i] };
        err = i2c_master_send(client, data, sizeof(data));
        if (err != sizeof(data)) {
            dev_err(&client->dev,
                "%s: err=%d addr=%02x, data=%02x\n",
                __func__, err, data[0], data[1]);
            return -EIO;
        }
    };
    return 0;
}
        class.c:

        接下来讲述最重要的rtc注册函数rtc_device_register,在class.c中
struct rtc_device *rtc_device_register(const char *name, struct device *dev,
                    const struct rtc_class_ops *ops,
                    struct module *owner)
{
    struct rtc_device *rtc;
    struct rtc_wkalrm alrm;
    int id, err;
    if (idr_pre_get(&rtc_idr, GFP_KERNEL) == 0) {
        err = -ENOMEM;
        goto exit;
    }
    mutex_lock(&idr_lock);
    err = idr_get_new(&rtc_idr, NULL, &id);
    mutex_unlock(&idr_lock);
    if (err < 0)
        goto exit;
    id = id & MAX_ID_MASK;
    rtc = kzalloc(sizeof(struct rtc_device), GFP_KERNEL);    //申请rtc_device结构体
    if (rtc == NULL) {
        err = -ENOMEM;
        goto exit_idr;
    }
    rtc->id = id;
    rtc->ops = ops;
    rtc->owner = owner;
    rtc->irq_freq = 1;
    rtc->max_user_freq = 64;
    rtc->dev.parent = dev;
    rtc->dev.class = rtc_class;
    rtc->dev.release = rtc_device_release;    //rtc释放函数,在后面讲述
    mutex_init(&rtc->ops_lock);
    spin_lock_init(&rtc->irq_lock);
    spin_lock_init(&rtc->irq_task_lock);
    init_waitqueue_head(&rtc->irq_queue);
    /* Init timerqueue */
    timerqueue_init_head(&rtc->timerqueue);
    INIT_WORK(&rtc->irqwork, rtc_timer_do_work);
    /* Init aie timer */
    rtc_timer_init(&rtc->aie_timer, rtc_aie_update_irq, (void *)rtc);
    /* Init uie timer */
    rtc_timer_init(&rtc->uie_rtctimer, rtc_uie_update_irq, (void *)rtc);
    /* Init pie timer */
    hrtimer_init(&rtc->pie_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
    rtc->pie_timer.function = rtc_pie_update_irq;
    rtc->pie_enabled = 0;
    /* Check to see if there is an ALARM already set in hw */
    err = __rtc_read_alarm(rtc, &alrm);
    if (!err && !rtc_valid_tm(&alrm.time))
        rtc_initialize_alarm(rtc, &alrm);
    strlcpy(rtc->name, name, RTC_DEVICE_NAME_SIZE);
    dev_set_name(&rtc->dev, "rtc%d", id);
    rtc_dev_prepare(rtc);    //rtc-dev.c中的设备结构初始化,第二部分讲述
    err = device_register(&rtc->dev);
    if (err) {
        put_device(&rtc->dev);
        goto exit_kfree;
    }
    rtc_dev_add_device(rtc);    //rtc-dev.c中的增加设备,第二部分讲述
    rtc_sysfs_add_device(rtc);    //rtc-sysfs.c中创建/sys文件,第三部分讲述
    rtc_proc_add_device(rtc);    //rtc-proc.c中的增加proc,第三部分讲述
    dev_info(dev, "rtc core: registered %s as %s\n",
            rtc->name, dev_name(&rtc->dev));
    return rtc;
exit_kfree:
    kfree(rtc);
exit_idr:
    mutex_lock(&idr_lock);
    idr_remove(&rtc_idr, id);
    mutex_unlock(&idr_lock);
exit:
    dev_err(dev, "rtc core: unable to register %s, err = %d\n",
            name, err);
    return ERR_PTR(err);
}
        (1)处理一个idr结构,idr在linux内核中指的就是整数ID管理机制,从本质上来说,idr是一种将整数ID号和特定指针关联在一起的机制。这个机制最早是在2003年2月加入内核的,当时是作为POSIX定时器的一个补丁。现在在内核的很多地方都可以找到idr的身影。这里从内核中获取一个idr结构,并与id相关联。
        (2)分配了一个rtc_device的结构--rtc,并且初始化了相关的成员:id, rtc_class_ops等等。
        (3)首先调用rtc_dev_prepare(在rtc-dev.c中定义)。因为RTC设备本质来讲还是字符设备,所以这里初始化了字符设备相关的结构:设备号以及文件操作。然后调用device_register将设备注册到linux设备模型核心。这样在模块加载的时候,udev daemon就会自动为我们创建设备文件rtc(n)。
        (4)先后调用rtc_dev_add_device,rtc_sysfs_add_device,rtc_proc_add_device三个函数。 rtc_dev_add_device注册字符设备,rtc_sysfs_add_device只是为设备添加了一个闹钟属性,rtc_proc_add_device 创建proc文件系统接口。
void rtc_dev_add_device(struct rtc_device *rtc)
{
    if (cdev_add(&rtc->char_dev, rtc->dev.devt, 1))
        printk(KERN_WARNING "%s: failed to add char device %d:%d\n",
            rtc->name, MAJOR(rtc_devt), rtc->id);
    else
        pr_debug("%s: dev (%d:%d)\n", rtc->name,
            MAJOR(rtc_devt), rtc->id);
}
void rtc_sysfs_add_device(struct rtc_device *rtc)
{
    int err;
    /* not all RTCs support both alarms and wakeup */
    if (!rtc_does_wakealarm(rtc))
        return;
    err = device_create_file(&rtc->dev, &dev_attr_wakealarm);
    if (err)
        dev_err(rtc->dev.parent,
            "failed to create alarm attribute, %d\n", err);
}

        初始化函数rtc_init()为:
static int __init rtc_init(void)
{
    rtc_class = class_create(THIS_MODULE, "rtc");    //创建rtc设备类
    if (IS_ERR(rtc_class)) {
        printk(KERN_ERR "%s: couldn't create class\n", __FILE__);
        return PTR_ERR(rtc_class);
    }
    rtc_class->suspend = rtc_suspend;    //挂起函数,后面讲述
    rtc_class->resume = rtc_resume;    //恢复函数,后面讲述
    rtc_dev_init();    //分配设备号,rtc-dev.c,后面讲述
    rtc_sysfs_init(rtc_class);    //创建sys,rtc-sysfs.c后面讲述
    return 0;
}
        rtc_init 首先调用class_create创建了一个类--rtc。我们知道类是一个设备的高层视图,他抽象出了底层的实现细节。类的作用就是向用户空间提供设备 的信息,驱动程序不需要直接处理类。然后初始化类结构的相应成员,rtc_suspend,rtc_resume这两个函数也是在class.c中实现 的。接下来调用rtc_dev_init(),这个函数为RTC设备动态分配设备号,保存在rtc_devt中。最后调用 rtc_sysfs_init,初始化rtc_class的属性。
subsys_initcall(rtc_init);
        由subsys_initcall(rtc_init);知道,此函数在系统开始运行的时候即被执行。
static int rtc_suspend(struct device *dev, pm_message_t mesg)
{
    struct rtc_device    *rtc = to_rtc_device(dev);
    struct rtc_time        tm;
    if (strcmp(dev_name(&rtc->dev), CONFIG_RTC_HCTOSYS_DEVICE) != 0)
        return 0;
    rtc_read_time(rtc, &tm);
    ktime_get_ts(&oldts);
    rtc_tm_to_time(&tm, &oldtime);
    return 0;
}
static int rtc_resume(struct device *dev)
{
    struct rtc_device    *rtc = to_rtc_device(dev);
    struct rtc_time        tm;
    time_t            newtime;
    struct timespec        time;
    struct timespec        newts;
    if (strcmp(dev_name(&rtc->dev), CONFIG_RTC_HCTOSYS_DEVICE) != 0)
        return 0;
    ktime_get_ts(&newts);
    rtc_read_time(rtc, &tm);
    if (rtc_valid_tm(&tm) != 0) {
        pr_debug("%s:  bogus resume time\n", dev_name(&rtc->dev));
        return 0;
    }
    rtc_tm_to_time(&tm, &newtime);
    if (newtime <= oldtime) {
        if (newtime < oldtime)
            pr_debug("%s:  time travel!\n", dev_name(&rtc->dev));
        return 0;
    }
    /* calculate the RTC time delta */
    set_normalized_timespec(&time, newtime - oldtime, 0);
    /* subtract kernel time between rtc_suspend to rtc_resume */
    time = timespec_sub(time, timespec_sub(newts, oldts));
    timekeeping_inject_sleeptime(&time);
    return 0;
}
void __init rtc_dev_init(void)
{
    int err;
    err = alloc_chrdev_region(&rtc_devt, 0, RTC_DEV_MAX, "rtc");
    if (err < 0)
        printk(KERN_ERR "%s: failed to allocate char dev region\n",
            __FILE__);
}
        在rtc_device_register函数中有一个rtc释放函数rtc_device_release:
static void rtc_device_release(struct device *dev)
{
    struct rtc_device *rtc = to_rtc_device(dev);
    mutex_lock(&idr_lock);
    idr_remove(&rtc_idr, rtc->id);
    mutex_unlock(&idr_lock);
    kfree(rtc);
}
        退出函数:
static void __exit rtc_exit(void)
{
    rtc_dev_exit();
    class_destroy(rtc_class);
    idr_destroy(&rtc_idr);
}
module_exit(rtc_exit);

阅读(647) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~