×
单片机 > 单片机程序设计 > 详情

S3C2440 RTC实时时钟 驱动分析以及使用(三十)

发布时间:2024-05-16 发布时间:
|

RTC驱动分析总结:


driversrtcrtc-s3c.c

s3c_rtc_init

platform_driver_register

s3c_rtc_probe

rtc_device_register("s3c", &pdev->dev, &s3c_rtcops, THIS_MODULE)

rtc_dev_prepare

cdev_init(&rtc->char_dev, &rtc_dev_fops);

rtc_dev_add_device

cdev_add

linux中的rtc驱动位于drivers/rtc下,里面包含了许多开发平台的RTC驱动,我们这里是以S3C24XX为主,所以它的RTC驱动为rc-s3c.c


1、进入./drivers/rtc/rtc-s3c.c


还是首先进入入口函数,如下图所示:


这里注册了一个"s3c2410-rtc"名称的平台设备驱动


而"s3c2410-rtc"的平台设备,在./arch/arm/plat-s3c24xx/devs.c里定义了,只不过没有注册,如下图所示:


当内核匹配到有与它名称同名的平台设备,就会调用.probe函数,接下来我们便进入s3c2410_rtcdrv->probe函数中看看,做了什么:


static int s3c_rtc_probe(struct platform_device *pdev)

{

struct rtc_device *rtc; //rtc设备结构体

struct resource *res;

int ret;

s3c_rtc_tickno = platform_get_irq(pdev, 1); //获取IRQ_TICK节拍中断资源

s3c_rtc_alarmno = platform_get_irq(pdev, 0); //获取IRQ_RTC闹钟中断资源

res = platform_get_resource(pdev, IORESOURCE_MEM, 0); //获取内存资源

s3c_rtc_mem = request_mem_region(res->start,res->end-res->start+1,pdev->name);//申请内存资源

s3c_rtc_base = ioremap(res->start, res->end - res->start + 1); //对内存进行重映射

s3c_rtc_enable(pdev, 1); //设置硬件相关设置,使能RTC寄存器

s3c_rtc_setfreq(s3c_rtc_freq); //设置TICONT寄存器,使能节拍中断,设置节拍计数值

/*1.注册RTC设备*/

rtc = rtc_device_register("s3c", &pdev->dev, &s3c_rtcops,THIS_MODULE);

rtc->max_user_freq = 128;

platform_set_drvdata(pdev, rtc);

return 0;

}

显然最终会调用rtc_device_register()函数来向内核注册rtc_device设备,注册成功会返回一个已注册好的rtc_device。


而s3c_rtcops是一个rtc_class_ops结构体,里面就是保存如何操作这个rtc设备的函数,比如读写RTC时间,读写闹钟时间等,注册后,会保存在rtc_device->ops里


该函数在drivers/rtc/class.c文件内被定义。class.c文件主要定义了RTC子系统,


而内核初始化,便会进入class.c,进入rtc_init()->rtc_dev_init(),来注册字符设备


err = alloc_chrdev_region(&rtc_devt, 0, RTC_DEV_MAX, "rtc");

//RTC_DEV_MAX=16,表示只注册0~15个次设备号,设备编号保存在rtc_devt中

2、它与rtc_device_register()函数注册RTC设备,会有什么关系?


接下来便来看rtc_device_register(),代码如下:


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;//定义一个rtc_device结构体

... ...

rtc = kzalloc(sizeof(struct rtc_device), GFP_KERNEL);//分配rtc_device结构体为全局变量

... ...

//设置rtc_device

rtc->id = id;

rtc->ops = ops;//将s3c_rtcops保存在rtc_device->ops里

rtc->owner = owner;

rtc->max_user_freq = 64;

rtc->dev.parent = dev;

rtc->dev.class = rtc_class;

rtc->dev.release = rtc_device_release;

... ...

rtc_dev_prepare(rtc);//1.做提前准备,初始化cdev结构体

... ...

rtc_dev_add_device(rtc);//2.在/dev下创建rtc相关文件,将cdev添加到系统中

rtc_sysfs_add_device(rtc);//在/sysfs下创建rtc相关文件

rtc_proc_add_device(rtc);//在/proc下创建rtc相关文件

}

上面的rtc_dev_prepare(rtc)和rtc_dev_add_device(rtc)主要做了以下两个(位于./drivers/rtc/rtc-dev.c):


cdev_init(&rtc->char_dev, &rtc_dev_fops); //绑定file_operations

cdev_add(&rtc->char_dev, rtc->dev.devt, 1);//注册rtc->char_dev字符设备,添加一个从设备到系统中

显然这里的注册字符设备,和我们上节讲的一模一样的流程/zixunimg/eeworldimg/blog.csdn.net/xiaodingqq/article/details/81974606


所以"s3c2410-rtc"平台设备驱动的.probe主要做了以下几件事:


1、设置RTC相关寄存器


2、分配rtc_device结构体


3、设置rtc_device结构体


3.1 将struct rtc_class_ops s3c_rtcops放入rtc_device->ops,实现对RTC读写时间等操作


4、注册rtc->chr_dev字符设备,且该字符设备的操作结构体为:struct file_operations rtc_dev_fops


3、上面的file_operations操作结构体rtc_dev_fops的成员,如下图所示:


3.1 当我们应用层open("/dev/rtcXX")时,就会调用rtc_dev_fops->rtc_dev_open(),我们来看看如何open的:


static int rtc_dev_open(struct inode *inode, struct file *file)

{

int err;

struct rtc_device *rtc = container_of(inode->i_cdev,struct rtc_device, char_dev);//获取对应的rtc_device

const struct rtc_class_ops *ops = rtc->ops;//最终等于s3c_rtcops

... ...

file->private_data = rtc;//设置file结构体的私有成员等于rtc_device,再次执行ioctl等函数时,直接就可以提取file->private_date即可

err = ops->open ? ops->open(rtc->dev.parent) : 0;//调用s3c_rtcops->open

... ...

mutex_unlock(&rtc->char_lock);

return err;

}

显然最终还是调用rtc_device下的s3c_rtcops->open


static const struct rtc_class_ops s3c_rtcops = {

.open = s3c_rtc_open,

.release = s3c_rtc_release,

.ioctl = s3c_rtc_ioctl,

.read_time = s3c_rtc_gettime,

.set_time = s3c_rtc_settime,

.read_alarm = s3c_rtc_getalarm,

.set_alarm = s3c_rtc_setalarm,

.proc = s3c_rtc_proc,

};

则s3c_rtc_open()函数里主要是申请了两个中断,一个闹钟中断,一个计时中断:


static int s3c_rtc_open(struct device *dev)

{

struct platform_device *pdev = to_platform_device(dev);

struct rtc_device *rtc_dev = platform_get_drvdata(pdev);

int ret;

//申请闹钟中断

ret = request_irq(s3c_rtc_alarmno, s3c_rtc_alarmirq,

IRQF_DISABLED, "s3c2410-rtc alarm", rtc_dev);

if (ret) {

dev_err(dev, "IRQ%d error %dn", s3c_rtc_alarmno, ret);

return ret;

}

//申请计时中断

ret = request_irq(s3c_rtc_tickno, s3c_rtc_tickirq,

IRQF_DISABLED, "s3c2410-rtc tick", rtc_dev);

if (ret) {

dev_err(dev, "IRQ%d error %dn", s3c_rtc_tickno, ret);

goto tick_err;

}

return ret;

tick_err:

free_irq(s3c_rtc_alarmno, rtc_dev);

return ret;

}


3.2 当我们应用层open后,使用ioctl(int fd, unsigned long cmd, ...)时,就会调用rtc_dev_fops->rtc_dev_ioctl():


static int rtc_dev_ioctl(struct inode *inode, struct file *file,

unsigned int cmd, unsigned long arg)

{

int err = 0;

struct rtc_device *rtc = file->private_data;提取rtc_device

... ...

void __user *uarg = (void __user *) arg;

switch (cmd) {

case RTC_EPOCH_SET:

case RTC_SET_TIME: //设置时间

if (!capable(CAP_SYS_TIME))

return -EACCES;

break;

case RTC_IRQP_SET://改变中断触发速度

if (arg > rtc->max_user_freq && !capable(CAP_SYS_RESOURCE))

return -EACCES;

break;

... ...

}

.. ...

switch (cmd) {

case RTC_ALM_READ: //读闹钟时间

err = rtc_read_alarm(rtc, &alarm);//调用s3c_rtcops->read_alarm

if (err < 0)

return err;

if (copy_to_user(uarg, &alarm.time, sizeof(tm)))//传时间数据

return -EFAULT;

break;

case RTC_ALM_SET://设置闹钟时间,调用s3c_rtcops->set_alarm

... ...

case RTC_RD_TIME://读RTC时间,调用s3c_rtcops->read_alarm

... ...

case RTC_SET_TIME://写RTC时间,调用s3c_rtcops->set_time

... ...

case RTC_IRQP_SET://该败了中断触发频率,调用s3c_rtcops->irq_set_freq

... ...

... ...

}


最终还是调用s3c_rtcops下的成员函数,我们以s3c_rtcops->raed_alarm()函数为例,看看如何读出时间:


static int s3c_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm)

{

unsigned int have_retried = 0;

void __iomem *base = s3c_rtc_base;//获取RTC相关寄存器基地址

retry_get_time:

//获取年,月,日,时,分,秒寄存器

rtc_tm->tm_min = readb(base + S3C2410_RTCMIN);

rtc_tm->tm_hour = readb(base + S3C2410_RTCHOUR);

rtc_tm->tm_mday = readb(base + S3C2410_RTCDATE);

rtc_tm->tm_mon = readb(base + S3C2410_RTCMON);

rtc_tm->tm_year = readb(base + S3C2410_RTCYEAR);

rtc_tm->tm_sec = readb(base + S3C2410_RTCSEC);

//判断寄存器中是0,则表示过去了一分钟,那么小时,天,月等寄存器中的值都可能已经变化,需要重新读取这些寄存器的值

if (rtc_tm->tm_sec == 0 && !have_retried) {

have_retried = 1;

goto retry_get_time;

}

... ...

//将获取的寄存器值,转换为真正的时间数据

BCD_TO_BIN(rtc_tm->tm_sec);

BCD_TO_BIN(rtc_tm->tm_min);

BCD_TO_BIN(rtc_tm->tm_hour);

BCD_TO_BIN(rtc_tm->tm_mday);

BCD_TO_BIN(rtc_tm->tm_mon);

BCD_TO_BIN(rtc_tm->tm_year);

rtc_tm->tm_year += 100;//存储器中存放的是从1900年开始的时间,所以加上100

rtc_tm->tm_mon -= 1;

return 0;

}

同样,在s3c_rtcops->set_time()函数里,也是向相关寄存器写入RTC时间


所以,总结如下所示:


rtc_device->char_dev: 字符设备,与应用层、以及更底层的函数打交道


rtc_device->ops: 更底层的操作函数,直接操作硬件相关的寄存器,被rtc_device->char_dev调用


4、修改内核


我们单板上使用ls /dev/rtc*,找不到该字符设备,因为内核里只定义了s3c_device_rtc

[1] [2]
S3C2440RTC实时时钟驱动分析

『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
基于AT91M42800A的LED显示系统设计