来逐一分析。

看一个设备驱动,一般都从模块初始化和退出函数开始,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);

linux i2c模型 rtc模型 详细分析,Linux RTC驱动分析(一)----pcf8563驱动和class.c相关推荐

  1. Linux 学习笔记之超详细基础linux命令 Part 3

    Linux学习笔记之超详细基础linux命令 by:授客 QQ:1033553122 ---------------------------------接Part 2----------------- ...

  2. linux如何运行verilog,linux系统下ncverilog的详细命令linux系统下ncverilog的详细命令.doc...

    linux系统下ncverilog的详细命令linux系统下ncverilog的详细命令 ncverilog: 08.10-p002: (c) Copyright 1995-2008 Cadence ...

  3. linux 查看u盘文件,详细介绍Linux系统下检测U盘是不是已经连接的方法

    Linux操作系统是基于Unix操作系统发展而来的一种克隆系统,它诞生于1991年的10月5日(这是第一次正式向外公布的时间).今天就跟着小编一起来看一看:详细介绍linux系统下检测U盘是不是已经连 ...

  4. linux 启用telnet命令行,详细介绍Linux telnet命令的使用

    对Linux系统进行远程登录,Linux telnet命令是必须得掌握的一个知识,虽然telnet并不是***的远程登录的方案,但是不可否认它是最常用的,所以很有必要详细了解Linux telnet命 ...

  5. linux配置ARP内核参数,详细讲解linux内核参数arp_announce和arp_ignore

    linux内核/proc/sys/net/ipv4/conf中下面会有各网卡的配置参数其中arp_ignore和arp_annouce与arp相关 arp_ignore 取值为integer 0 (d ...

  6. 【玩转Linux】史上最详细的Linux命令大全和线上问题排查手册

    文章目录 一.基本命令 1.重启 2.关机 3.清屏 4.显示当前的工作目录 5.帮助命令 6.显示目录下的文件和子目录(ls) 二.文件操作 1.新建文件 2.新建文件夹 3.复制文件或目录 4.删 ...

  7. 熟悉Linux实验实训,非常详细的Linux操作系统与实训教程实验(三)

    实验名称:Linux文件管理 实验目的: 1.掌握文件管理的基本内容和原理: 2.了解文件和目录操作的系统调用用户接口: 实验要求与实验内容:熟悉Linux文件目录管理各种功能命令 实验过程: 1.用 ...

  8. linux od命令详解,详细介绍Linux od命令

    转:http://blog.csdn.net/zuixinnet/article/details/8994002 随着计算机飞速的发展,很多人开始学习Linux,怎样才能学好Linux,一定要学好Li ...

  9. Linux中usb设置burst,详细解读Linux系统中ntpq命令的使用

    命令 "ntpq -q" 输出下面这样的一个表: remote refid st t when poll reach delay offset jitter =========== ...

  10. Linux i2c子系统

    一.前言 因为自己在研发过程中经常要涉及到tp驱动程序,而tp驱动就涉及到i2c驱动.经常可以看到在驱动程序中会定义一个struct i2c_driver的数据结构,并实现里面的某些成员,比如prob ...

最新文章

  1. WPF入门教程系列九——布局之DockPanel与ViewBox(四)
  2. reorder-list——链表、快慢指针、逆转链表、链表合并
  3. java properties读取 封装_java properties 文件读取踩坑记
  4. 手机画面尺寸多少满屏_手机屏幕科普
  5. Syntax error, annotations are only available if source level is 1.5 or greater.
  6. redis(10)--RDB持久化
  7. 数据挖掘笔试面试(10)
  8. ZOJ 1450 Minimal Circle 点集的最小圆覆盖
  9. 资深架构师十几年的架构干货经验总结分享!
  10. C++ set与map、unordered_map、unordered_set与哈希表
  11. 《统计学习方法》—— 逻辑斯谛回归 与 最大熵模型 的介绍以及详细推导
  12. matlab 正态分布相关 API
  13. ArcGIS操作实例视频教程38讲全集(转)
  14. 磁盘管理查看内存软件——WizTree使用
  15. 用极限定义证明微积分基本定理
  16. 春招面试的总结与自我反省
  17. 电影外观调色效果Lr预设
  18. 一个非常非常非常简单的SpringBoot小项目by hazy
  19. 本地生活O2O行业已经逐渐渗透到日常生活中
  20. 微信改版,“内容+服务”成为王道?

热门文章

  1. 移动路由猫虚拟服务器,移动路由器连接光猫怎么设置?
  2. VSCode 修改系统界面和编辑面板字体大小
  3. 网渲显示服务器错误,网络渲染疑难解答指南 | 3ds Max 2021 | Autodesk Knowledge Network...
  4. 如何安装 Ubuntu 22.04 LTS 桌面版 ?
  5. 黄建宏-redis单机服务器
  6. 【Python】多图形混合排版,如何在Matplotlib/Seaborn中实现?
  7. 超适合新手练习的前端网页
  8. 我养的无名花草,四季开花
  9. 粉笔公考——方法精讲——资料分析
  10. matlab之simulink仿真入门