linux驱动分析之DS1302 RTC

系统开机时一般会通过/dev/rtc设备来获取当时时间,所以肯定有一个驱动用于支持这个/dev/rtc设备的。在内核的drivers/char中找到了ds1302.c的驱动。在这个驱动中就注册了字符型设备。并支持RTC设备的ioctl(),RTC_RD_TIMERTC_SET_TIME2个最为重要的控制方法。

先看一下这个模块注册函数:

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寄存器,分别写入0x00x80,看来这个最高位,控制着是否可写。要往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;

}


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

linux驱动分析之DS1302 RTC_第1张图片



从数据手册上来看,单次写操作:

1、先将CE应该是片选,拉高。

2、根据SCLK依次送出16个数据。

3、再将CE拉底。

而这个驱动,却不是,也对应不起来。只有RST_HRST_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,表示不支持!

这里的commandslinux内核中默认规定的。

/*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_TIMEioctl()获取时间。
 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.


linux驱动分析之DS1302 RTC_第2张图片


linux驱动分析之DS1302 RTC_第3张图片


linux驱动分析之DS1302 RTC_第4张图片

你可能感兴趣的:(Linux驱动开发)