您的位置:首页 > 运维架构 > Linux

linux驱动分析之DS1302 RTC

2015-05-13 14:30 513 查看
系统开机时一般会通过/dev/rtc设备来获取当时时间,所以肯定有一个驱动用于支持这个/dev/rtc设备的。在内核的drivers/char中找到了ds1302.c的驱动。在这个驱动中就注册了字符型设备。并支持RTC设备的ioctl(),RTC_RD_TIME和RTC_SET_TIME这2个最为重要的控制方法。
先看一下这个模块注册函数:

module_init(ds1302_register);

再往下找,下面调用了register_chrdev()注册字符设备接口,将主设备号,设备名称和函数操作指针绑定在了一起。

staticint__init
ds1302_register(void)

{

ds1302_init();

if(register_chrdev(RTC_MAJOR_NR,
ds1302_name, &rtc_fops)) {

printk(KERN_INFO"%s:unable to get major %d for rtc\n",

ds1302_name, RTC_MAJOR_NR);

return-1;

}

return0;

}
int__init
ds1302_init(void)
{
if(!ds1302_probe())

{
return-1;
}
return0;
}

下面来看看probe()函数是干什么的,从字面意思是枚举设备。从英文上来看,是通过向它的RAM写入一个值再读回来判断读回来的跟写入的是否一样,一样就说明找到设备了。

/*Probe for the chip by writing something to its RAM and try reading itback. */

#defineMAGIC_PATTERN
0x42 这个就是特定的值。

staticint__init
ds1302_probe(void)
{
intretval,
res, baur;

baur=(boot_cpu_data.bus_clock/(2*1000*1000));

printk("%s:Set PLD_RTCBAUR = %d\n",ds1302_name,baur);

outw(0x0000,(unsignedlong)PLD_RTCCR);
outw(0x0000,(unsignedlong)PLD_RTCRSTODT);
outw(baur,(unsignedlong)PLD_RTCBAUR);

/*Try to talk to
timekeeper.*/
ds1302_wenable();
这里是写使能吗?
/*write RAM byte 0 */
/*write something magic */
out_byte_rtc(0xc0,MAGIC_PATTERN);
往0xc0寄存器写个这个特定的值。

/*read RAM byte 0 */
if((res=
in_byte_rtc(0xc1)) == MAGIC_PATTERN) {
charbuf[100];
ds1302_wdisable();写禁止。
printk("%s:RTC found.\n",ds1302_name);
get_rtc_status(buf);
printk(buf);
retval= 1;
读回来的跟写入的一模一样,所以枚举成功。
}else{
printk("%s:RTC not found.\n",ds1302_name);
retval= 0;
读回来的值有问题。
}

returnretval;
}

再来看看这个写使能,写关闭是什么意思,很简单,就是往0x8e寄存器,分别写入0x0和0x80,看来这个最高位,控制着是否可写。要往ds1302寄存器里写操作,必须将最高位清0呀。
/*Enable writing. */
staticvoid
ds1302_wenable(void)
{
out_byte_rtc(0x8e,0x00);
}
/*Disable writing. */
staticvoid
ds1302_wdisable(void)
{
out_byte_rtc(0x8e,0x80);
}

再来看看,底层的读写函数,看一样,数据是怎么被送到ds1302的。

先看写函数,从流程上来看,好像是遵守了一些时序,

1、先将RST拉高;

2、再写入8位数据;

3、再产生WE(WriteEnable)写使能信号

4、再等待。???等什么信号呢?

5、再将RST拉底。

/*Send 8 bits. */
staticvoidout_byte_rtc(unsignedintreg_addr,unsignedcharx)
{
//RSTH
outw(0x0001,(unsignedlong)PLD_RTCRSTODT);
//writedata
outw(((x<<8)|(reg_addr&0xff)),(unsignedlong)PLD_RTCWRDATA);
//WE
outw(0x0002,(unsignedlong)PLD_RTCCR);
//wait
while(inw((unsignedlong)PLD_RTCCR));
//RSTL
outw(0x0000,(unsignedlong)PLD_RTCRSTODT);
}
staticunsignedchar
in_byte_rtc(unsignedintreg_addr)
{
unsignedcharretval;
//RSTH
outw(0x0001,(unsignedlong)PLD_RTCRSTODT);
//writedata
outw((reg_addr&0xff),(unsignedlong)PLD_RTCRDDATA);
//RE
outw(0x0001,(unsignedlong)PLD_RTCCR);
//wait
while(inw((unsignedlong)PLD_RTCCR));
//readdata
retval=(inw((unsignedlong)PLD_RTCRDDATA)&
0xff00)>>8;
//RSTL
outw(0x0000,(unsignedlong)PLD_RTCRSTODT);
returnretval;
}

光猜不行,果断去找数据手册。



从数据手册上来看,单次写操作:
1、先将CE应该是片选,拉高。
2、根据SCLK依次送出16个数据。
3、再将CE拉底。
而这个驱动,却不是,也对应不起来。只有RST_H和RST_L看着跟CE信号差不多。从字面上来看,有好多PLC_xxx的地址,看来这个DS1302应该是接在了CPLD上。先将数据送给CPLD,再由CPLD产生时序送出DS1302。
读时序跟此类似。

再来看看字符设备操作的结构体指针:
staticconststructfile_operationsrtc_fops=
{
.owner =THIS_MODULE,
.unlocked_ioctl =rtc_ioctl,
.llseek =noop_llseek,
};

可以看到这里没有常见的open(),read(),write(),close(),而只有ioctl()和lseek()。
看来上层应用程序是使用ioctl()来跟设备驱动程序进行数据交互的。而再在内核中搜索一下noop_llseek(),会发现如下:(意思就是说默认实现)。
/**
*noop_llseek - No Operation Performed
llseekimplementation
*@file:
filestructure to seek on
*@offset: file offset to seek to
*@origin: type of seek
*
*This is an implementation of ->llseekuseablefor
the rare special case when
*userspaceexpects
the seek to succeed but the (device) file is actually not
*able to perform the seek. In this case you use noop_llseek() insteadof
*falling back to the default implementation of ->llseek.
*/
loff_tnoop_llseek(structfile*file,
loff_toffset,
intorigin)
{
returnfile->f_pos;
}
EXPORT_SYMBOL(noop_llseek);

这个不关心,再来看看最为关键的ioctl()函数,这里只支持2个最关键的命令:
RTC_RD_TIME:从DS1302中获取时间。
RTC_SET_TIME:设置时间到DS1302中。
而对于其中的命令均返回-EINVAL,表示不支持!
这里的commands是linux内核中默认规定的。
/*ioctl that supports RTC_RD_TIME and RTC_SET_TIME (read and settime/date). */
staticlongrtc_ioctl(structfile*file,
unsignedintcmd,
unsignedlongarg)
{
unsignedlongflags;

switch(cmd){
caseRTC_RD_TIME:
/*read the time/date from RTC */
{
structrtc_timertc_tm;

memset(&rtc_tm,0,
sizeof(structrtc_time));
这里使用mutex互斥锁,是为了防止多个应用同时获取时间导致时序混乱。加了锁后,同一时间只能有一个进程执行get_rtc_time()函数。这里还是比较关键的!!!
mutex_lock(&rtc_mutex);
get_rtc_time(&rtc_tm);
这里调用get_rtc_time()函数封填充结构体。
mutex_unlock(&rtc_mutex);
使用copy_to_user()将内核中的数据拷贝到用户空间。
if(copy_to_user((structrtc_time*)arg,&rtc_tm,
sizeof(structrtc_time)))
return-EFAULT;
return0;
}

caseRTC_SET_TIME:
/*set the RTC */
{
structrtc_timertc_tm;
unsignedcharmon,
day, hrs, min, sec, leap_yr;
unsignedintyrs;

if(!capable(CAP_SYS_TIME))
return-EPERM;

从用户空间先将数据拷贝到内核空间。
if(copy_from_user(&rtc_tm,
(structrtc_time*)arg,sizeof(structrtc_time)))
return-EFAULT;

yrs= rtc_tm.tm_year+
1900;
mon= rtc_tm.tm_mon+
1; /*tm_mon starts at zero */
day= rtc_tm.tm_mday;
hrs= rtc_tm.tm_hour;
min= rtc_tm.tm_min;
sec= rtc_tm.tm_sec;

对一些值进行正确性的判断!!
if((yrs
< 1970) || (yrs > 2069))
return-EINVAL;

leap_yr= ((!(yrs % 4) && (yrs % 100)) || !(yrs % 400));

if((mon
> 12) || (day == 0))
return-EINVAL;

if(day
> (days_in_mo[mon] + ((mon == 2) && leap_yr)))
return-EINVAL;

if((hrs
>= 24) || (min >= 60) || (sec >= 60))
return-EINVAL;

if(yrs
>= 2000)
yrs-= 2000;
/*RTC (0, 1, ... 69) */
else
yrs-= 1900;
/*RTC (70, 71, ... 99) */

将值从binary转换为BCD格式。因为DS1302只支持BCD格式的数据。
sec= bin2bcd(sec);
min= bin2bcd(min);
hrs= bin2bcd(hrs);
day= bin2bcd(day);
mon= bin2bcd(mon);
yrs= bin2bcd(yrs);

写入DS1302设备中。这里使用了CMOS_WRITE定义,在内核中看了一把,说明了就是定义了out_byte_rtc()函数,不知道为何要重新定义?这样的好处可能是如果我改变了接口,而这个函数是不用改的,可以直接通过修改CMOS_WRITE定义指向新的函数即可。(泥玛这样也行?麻烦不?)
mutex_lock(&rtc_mutex);
local_irq_save(flags);
CMOS_WRITE(yrs,RTC_YEAR);
CMOS_WRITE(mon,RTC_MONTH);
CMOS_WRITE(day,RTC_DAY_OF_MONTH);
CMOS_WRITE(hrs,RTC_HOURS);
CMOS_WRITE(min,RTC_MINUTES);
CMOS_WRITE(sec,RTC_SECONDS);
local_irq_restore(flags);
mutex_unlock(&rtc_mutex);

/*Notice that at this point, the
RTC is updated but
* the kernel is still running with the old time.
* You need to set that separately with
settimeofday
* or
adjtimex.
*/
return0;
}

caseRTC_SET_CHARGE:/*set
the RTC TRICKLE CHARGE register */
{
inttcs_val;

if(!capable(CAP_SYS_TIME))
return-EPERM;

if(copy_from_user(&tcs_val,(int*)arg,sizeof(int)))
return-EFAULT;

这个是什么命令?俺还真不知道。上网查了一看,没找到。算了不理它了。
mutex_lock(&rtc_mutex);
tcs_val=
RTC_TCR_PATTERN| (tcs_val & 0x0F);
ds1302_writereg(RTC_TRICKLECHARGER,tcs_val);
mutex_unlock(&rtc_mutex);
return0;
}
default:
其他命令一律返回不支持!听到没有,俺不支持!呵呵………^.^
return-EINVAL;
}
}

再来看看获取RTC时间的函数:
基本上就是通过读函数将寄存器读取回来,再进行转换。
void
get_rtc_time(structrtc_time*rtc_tm)
{
unsignedlongflags;

local_irq_save(flags);

rtc_tm->tm_sec=
CMOS_READ(RTC_SECONDS);
rtc_tm->tm_min=
CMOS_READ(RTC_MINUTES);
rtc_tm->tm_hour=
CMOS_READ(RTC_HOURS);
rtc_tm->tm_mday=
CMOS_READ(RTC_DAY_OF_MONTH);
rtc_tm->tm_mon=
CMOS_READ(RTC_MONTH);
rtc_tm->tm_year=
CMOS_READ(RTC_YEAR);

local_irq_restore(flags);

rtc_tm->tm_sec=
bcd2bin(rtc_tm->tm_sec);
rtc_tm->tm_min=
bcd2bin(rtc_tm->tm_min);
rtc_tm->tm_hour=
bcd2bin(rtc_tm->tm_hour);
rtc_tm->tm_mday=
bcd2bin(rtc_tm->tm_mday);
rtc_tm->tm_mon=
bcd2bin(rtc_tm->tm_mon);
rtc_tm->tm_year=
bcd2bin(rtc_tm->tm_year);

/*
* Account for differences between how the RTC uses the values
* and how they are defined in a
structrtc_time;
*/

if(rtc_tm->tm_year<=
69)
rtc_tm->tm_year+=
100;

rtc_tm->tm_mon--;
}
在枚举到设备的时候,会调用一个函数获取当时时间并打印出来:
就是读取了一下当前时间,再在一个buffer中格式化了一下,调用printk()输出来的。
get_rtc_status(buf);
printk(buf);

int
get_rtc_status(char*buf)
{
char*p;
structrtc_timetm;

p= buf;

get_rtc_time(&tm);

/*
* There is no way to tell if the
luserhas the RTC set for local
* time or for Universal Standard Time (GMT). Probably local though.
*/

p+= sprintf(p,
"rtc_time\t:%02d:%02d:%02d\n"
"rtc_date\t:%04d-%02d-%02d\n",
tm.tm_hour,tm.tm_min,tm.tm_sec,
tm.tm_year+
1900, tm.tm_mon+ 1, tm.tm_mday);

return
p - buf;
}

#defineRTC_MAJOR_NR 121
/*local major, change later */

staticDEFINE_MUTEX(rtc_mutex);
staticconstchards1302_name[]
= "ds1302";

好了,结构还是比较清楚的,但是具体怎么使用呢?
linux下一切皆文件,应用还是得套用老方法open(),read(),write(),close()的方法来使用RTC驱动。
下面是我从网上找的一些使用方法:

1
Howto use RTC driver
****************************************************************
1.Introducation
****************************************************************
ADSP21535has one Real Time Clock RTC device, and the RTC driver
isdesigned as a standard Linux RTC driver.
TheRTC device major/minor numbers:
majorminor
10135
TheRTC device name is /dev/rtc.
Whenthe read function is called, the application is blocked
untilthe RTC interrupt is generated.
****************************************************************
2.system call
****************************************************************
TheRTC device driver is designed as a standard Linux RTC
driver,and the following system calls are supported.
2.1open: The standard open function call.
intopen("/dev/rtc", int oflag, /* mode_t mode */...);
Theopen function is used to establish the connection between the RTC
devicewith a file descriptor.
-oflag:
O_RDONLYOpen for reading only
O_WRONLYOpen for writing only
O_RDWROpen for reading and writing
USAGE:
------
intfd;
fd= open("/dev/rtc", O_RDONLY, 0);
...
close(fd);
2.2close: The standard open function call.
intclose(int file_handler);
Theclose function is used to disconnect the RTC device with therelevant
filedescriptor.
USAGE:
------
intfd;
fd= open("/dev/rtc", O_RDONLY, 0);
...
close(fd);
2.3ioctl: the standard ioctl function call(refer to section 3).
intioctl(int file_handler, int request, /* arg */...);

2
Theioctl command is used to configure the RTC device.
USAGE:
------
intfd;
structrtc_time rtc_tm;
intret;
fd= open("/dev/rtc", O_RDONLY, 0);
...
//the ioctl command RTC_RD_TIME is used to read the current timer.
//about the detail ioctl command, refer to section 3
ret= ioctl(rtc_fd, RTC_RD_TIME, &rtc_tm);
...
close(fd);
2.4read: the standard read function call.
ssize_tread(int file_handler, viod *buf, size_t nbytes);
Inthe RTC driver, the read function is used to wait for the RTC device
interrupt.
Whencall the read function, the application is locked until a interruptis
generated.
USAGE:
------
intfd;
intret;
structrtc_time rtc_tm;
unsignedlong data;
fd= open("/dev/rtc", O_RDONLY, 0);
ret= ioctl(fd, RTC_ALM_SET, &rtc_tm);
//call the read function to wait the Alarm interrupt
ret= read(fd, &data, sizeof(unsigned long));
...
close(fd);
****************************************************************
3.RTC deivce ioctl
****************************************************************
RTC_SWCNT_OFF:This ioctl does not need an argument, and it can
beused to disable the RTC stop-watch interrupt.
RTC_SWCNT_ON:This ioctl does not need an argument, and it can
beused to enable the RTC stop-watch interrupt.
RTC_AIE_OFF:This ioctl does not need an argument, and it can
beused to disable the RTC alarm interrupt.
RTC_AIE_ON:This ioctl does not need an argument, and it can
beused to enable the RTC alarm interrupt.
RTC_UIE_OFF:This ioctl does not need an argument, and it can
beused to disable the RTC update interrupt.

3
RTC_UIE_ON:This ioctl does not need an argument, and it can
beused to enable the RTC update interrupt.
RTC_ALM_READ:This ioctl needs one argument(struct rtc_time *),
andit can be used to get the current RTC alarm
parameter.
RTC_ALM_SET:This ioctl needs one argument(struct rtc_time *),
andit can be used to set the RTC alarm.
RTC_RD_TIME:This ioctl needs one argument(struct rtc_time *),
andit can be used to get the current RTC time.
RTC_SET_TIME:This ioctl needs one argument(struct rtc_time *),
andit can be used to set the current RTC time.
RTC_EPOCH_READ:Thisioctl needs one argument(long *), and it
canbe used to get the current RTC epoch.
RTC_EPOCH_SET:This ioctl needs one argument(long), and it can
beused to set the current RTC epoch.
RTC_SWCNT_SET:This ioctl needs one argument(long), and it can
beused to set the current RTC stop-watch (the
unitis minute).
RTC_SWCNT_RD:This ioctl needs one argument(long *), and it
canbe used to get the current RTC stop-watch
(theunit is minute).
****************************************************************
4.Caution
****************************************************************

下面是一个中国人写的程序:
#include <linux/rtc.h>

#include <sys/ioctl.h>

void ReadRTC()

{

int fdrtc =open("/dev/rtc",O_RDONLY);

if (fdrtc ==-1){

perror("/dev/rtc");

return;

}

structrtc_time rtc_tm;

这时调用标准的RTC_RD_TIME的ioctl()获取时间。

intretval = ioctl(fdrtc,RTC_RD_TIME,&rtc_tm);

if (retval== -1){

perror("ioctl");

close(fdrtc);

return;

}

charcTime[100] = {0};

sprintf(cTime,"now time is :%d-%d-%d%02d:%02d:%02d/n",

rtc_tm.tm_year +1900,rtc_tm.tm_mon +1,rtc_tm.tm_mday,rtc_tm.tm_hour,rtc_tm.tm_min,rtc_tm.tm_sec);

printf(cTime);

close(fdrtc);

}

voidSetRTC()

{

int fdrtc = open("/dev/rtc",O_RDONLY);

if(fdrtc == -1){

perror("/dev/rtc");

return;

}

structrtc_time rtc_tm;

先获取当前时间

intretval = ioctl(fdrtc,RTC_RD_TIME,&rtc_tm);

if (retval==-1){

perror("ioctl");

close(fdrtc);

return;

}

rtc_tm.tm_year= 111;

rtc_tm.tm_mon = 0;

rtc_tm.tm_mday =20;

rtc_tm.tm_hour = 19;

int nInput =0;

printf("input -1,to stop settime/n");

scanf("%d",&nInput);

if(nInput == -1){

return;

}

rtc_tm.tm_min= nInput;

scanf("%d",&nInput);

if(nInput == -1){

return;

}

rtc_tm.tm_sec= nInput;

//set time这里使用标准的RTC_SET_TIME设置时间

retval= ioctl(fdrtc,RTC_SET_TIME,&rtc_tm);

if (retval ==-1){

perror("ioctl");

close(fdrtc);

return;

}

close(fdrtc);

}

暂时就这样吧。这个简单的字符设备RTC驱动还是比较简单的。继续分析其他驱动。
May 13,2015 byzhangshaoyan.





内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: