系统开机时一般会通过/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
#include
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.