Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1638736
  • 博文数量: 197
  • 博客积分: 10046
  • 博客等级: 上将
  • 技术积分: 1983
  • 用 户 组: 普通用户
  • 注册时间: 2006-08-07 12:36
个人简介

在外企做服务器开发, 目前是项目经理, 管理两个server开发的项目。不做嵌入式好久了。

文章分类
文章存档

2011年(2)

2010年(6)

2009年(18)

2008年(30)

2007年(100)

2006年(41)

分类: LINUX

2008-01-17 10:02:43

文件: linux_rtc_i2c_driver.pdf
大小: 161KB
下载: 下载

 

对于RTC还可以我自己的两篇文章:

[原创] RTC driver 框架以及具体的s3c2410-rtc.c情景分析(beta2)

http://blog.chinaunix.net/u/22617/showart.php?id=270683

 

移植smdk2410的rtc驱动到jk2410开发板碰到的问题的解决

http://blog.chinaunix.net/u/22617/showart.php?id=413400

 

 

 

RTC 实时时钟驱动

-------I2C软件模拟通信

 

 

内核版本: linux-2.4.21

文档设计: 侯辉华

    : 1.01

    : 2007/06/10

 

 

 

 

 

 

内容简介: 介绍接在I2C总线上RTC实时时钟设备的驱动, 使用软件模拟的方法完成I2C的通信; 介绍了Linux下的时钟系统, 以及I2C的层次结构.

 

 

目录索引:

 

一.        Linux下的时钟系统简介.

二.        Linux对时间的表示.

三.        Linux时钟中断的初始化及处理.

四.        RTC设备驱动程序.

五.        I2C总线读写.

.  Linux下的I2C驱动层次结构..
.  Linux下的时钟系统简介

 

实际上,linux系统有两个时钟:一个是由主板电池驱动的“Real Time Clock”也叫做RTC或者叫CMOS时钟,硬件时钟。当操作系统关机的时候,用这个来记录时间,但是对于运行的系统是不用这个时间的。另一个时间是 “System clock”也叫内核时钟或者软件时钟,是由软件根据时间中断来进行计数的,内核时钟在系统关机的情况下是不存在的,所以,当操作系统启动的时候,内核时钟是要读取RTC时间来进行时间同步.


linux
的内核时间实际上是记录从197011距离现在的秒数,并且以GMT(格林尼治时间)(或者叫UTC- Coordinated Universal Time)为标准, UTC是不随着DST(夏令时)变换,需要有变化的是由应用程序自身来完成时间的转换。

 

.  Linux对时间的表示

通常,操作系统可以使用三种方法来表示系统的当前时间与日期:①最简单的一种方法就是直接用一个64位的计数器来对时钟滴答进行计数。②第二种方法就是用一个32位计数器来对秒进行计数,同时还用一个32位的辅助计数器对时钟滴答计数,之子累积到一秒为止。因为232次方超过136年,因此这种方法直至22世纪都可以让系统工作得很好。③第三种方法也是按时钟滴答进行计数,但是是相对于系统启动以来的滴答次数,而不是相对于相对于某个确定的外部时刻;当读外部后备时钟(如RTC)或用户输入实际时间时,根据当前的滴答次数计算系统当前时间。

Linux通常都采用第三种方法来维护系统的时间与日期, 通过时钟点滴进行计时的基础原理, 可以参看下面介绍的参考文档, 主要原理是通过硬件的中断累积来计时, 但必要要设置硬件中断一次所须的时间, 一般具体的不同的芯片都不同, EP9302系统具体设置如下:

1.         EP93xx系列芯片有四个Timer计时器, 使用的是Timer1, 与具体芯片相关的内容在如下两个文件:

linux-2.4.21\arch\arm\mach-ep93xx\time.c,

linux-2.4.21\arch\arm\mach-ep93xx\time.h

2.         针对整个Arm体系的时钟相关文件为:

linux-2.4.21\arch\arm\kernel\time.c

时钟中断计时的主要相关函数为如下两个:

1.         ep93xx_gettimeoffset()的作用就是返回距最近一次时钟中断发生后, Timer已经累积的时间(但还未满足引发一次时钟中断), 这个时间值单位为微秒, 获取当前时间时, 就是累计已经发生的Timer中断次数所经历的时间, 然后加上这个即将要发生中断所过去的时间, 这样取得当前时间的精度是相当高的.

2.         LATCH的含义是指一次时钟中断要经过多少个Timer时钟周期, TIMER1LOAD寄存器设置的就是这个值, ep93xx的计时器Timer1会将这个值一直递减直至0, 如此就引发一次时钟中断, 然后又重LATCH重头开始递减.

3.         xtime记载的即为系统自开机以来的当前时间, 单位为秒, 精确度为微秒. 因此在开机时必须从RTC当中取得真实的时间来赋此初值, EP93xx系列直接初始此值为其自身所带RTC模块的时间值, RTCDR寄存器是EP93xx所自带的RTC模块的寄存器, 其值单位为秒,基准为相对1970, 此处即为我们要改动的地方.将其值从i2cRTC实时芯片中取回赋给它.

static unsigned long ep93xx_gettimeoffset(void)

{

         unsigned long hwticks;

         hwticks = LATCH - (inl(TIMER1VALUE) & 0xffff);

         return ((hwticks * tick) / LATCH);

}

void __init ep93xx_setup_timer(void)     //初始化Timer,设定时钟中断周期

{

         gettimeoffset = ep93xx_gettimeoffset;

         outl(0, TIMER1CONTROL);

         outl(LATCH - 1, TIMER1LOAD);        //设定Timer经多少个Timer时钟周期后产生中断

         outl(0xc8, TIMER1CONTROL);

         xtime.tv_sec = inl(RTCDR);           //ep93xx内部RTC模块读取时间,后将重新从cmos.

}

以下详细介绍一下时钟点滴计时的几个基本参数, 以下定义的出处, 除非特别指出, 一般是位于各自不同的平台的文件夹下定义:

linux-2.4.21\include\asm-arm\arch-ep93xx

linux-2.4.21\arch\arm\mach-ep93xx\

 

1.         时钟周期(clock cycle)的频率:计时器Timer晶体振荡器在1秒时间内产生的时钟脉冲个数就是时钟周期的频率, 要注意这个Timer的时钟周期频率要与时钟中断的频率区别开来,  Linux用宏CLOCK_TICK_RATE来表示计时器的输入时钟脉冲的频率(此值在EP93xx上是508KHZ),该宏定义在timex.h头文件中:

#define CLOCK_TICK_RATE 508000 /* Underlying HZ */

2.         时钟中断(clock tick):我们知道当计数器减到0值时,它就在IRQ0上产生一次时钟中断,也即一次时钟中断, 计数器的初始值决定了要过多少时钟周期才产生一次时钟中断,因此也就决定了一次时钟滴答的时间间隔长度. EP93xx系统中, Timer1TIMER1LOAD 的值决定过多少时钟周期后产生一次时钟中断.

3.         时钟中断的频率(HZ):也即1秒时间内Timer所产生的时钟中断次数。确定了时钟中断的频率值后也就可以确定Timer的计数器初值。Linux内核用宏HZ来表示时钟中断的频率,而且在不同的平台上HZ有不同的定义值。对于SPARCMIPSARMi386等平台HZ的值都是100。该宏在ARM平台上的定义如下(param.h):

#ifndef HZ

#define HZ 100

#endif

HZ值,可知每隔(1000msHZ)=10ms发生一次时钟中断.

 

4.         时钟中断的时间间隔: Linux用全局变量tick来表示时钟中断的时间间隔长度,其实定义了HZ之后, 即决定了此间隔值, tick变量的单位是微妙(μs), 该变量定义在kernel/timer.c文件中,如下:

long tick = (1000000 + HZ/2) / HZ; /* timer interrupt period */.

5.         LATCHLinux用宏LATCH来定义要设置到Timer中的值,它表示TImer将没隔多少个时钟周期产生一次时钟中断。显然LATCH应该由下列公式计算: LATCH=(1秒之内的Timer时钟周期个数)÷(1秒之内的时钟中断次数)=CLOCK_TICK_RATE)÷(HZ.

 

.  Linux时钟中断的初始化及处理

 

以下着重描述一下时钟中断时与RTC相关的修改:

 

文件:linux-2.4.21\arch\arm\kernel\time.c

描述:时钟初始化化, start_kernel()当中调用.

void __init time_init(void)

{

         xtime.tv_usec = 0;

         xtime.tv_sec  = 0;

         setup_timer();

}

 

文件:linux-2.4.21\arch\arm\mach-ep93xx\time.h

描述:安装时钟中断服务程序,从RTC更新初始化系统时钟, time_init()当中调用.

/*

 * Set up timer interrupt, and return the current time in seconds.

*/

static inline void setup_timer(void)

{

         ep93xx_setup_timer();

//houhh 20070713...

/////////

         xtime.tv_sec = get_cmos_time();    //RTC设备中读取当前时间

         set_rtc = set_cmos_time;                //初始化更新系统时间到RTC的函数, do_set_rtc()中调用

/////////

         timer_irq.handler = ep93xx_timer_interrupt;    //时钟中断服务程序

         setup_arm_irq(IRQ_TIMER1, &timer_irq);    //安装时钟中断处理

}

 

文件:linux-2.4.21\arch\arm\mach-ep93xx\time.h

描述:读写I2CRTC设备,软件模拟方式, 引自i2c-ep93xx.c, 将在下面详细介绍.

extern uchar pcf8563_readdata(uchar address);

extern int pcf8563_writedata(uchar address, uchar mdata);

#define CMOS_READ(addr)                  pcf8563_readdata(addr)

#define CMOS_WRITE(data, addr)        pcf8563_writedata(addr, data)

 

 

 

 

文件:linux-2.4.21\arch\arm\mach-ep93xx\time.h

描述:设置及读写RTC, 主要通过宏CMOS_READ/ CMOS_WRITE完成功能.

unsigned long get_cmos_time(void)

static int set_cmos_time(unsigned long nowtime)

 

文件: linux-2.4.21\arch\arm\mach-ep93xx\time.h

描述:时钟中断服务程序,并检测更新系统时钟到RTC, setup_timer()当中调用.

/*

 * IRQ handler for the timer

*/

static void ep93xx_timer_interrupt(int irq, void *dev_id, struct pt_regs *regs)

{

         outl( 1, TIMER1CLEAR );

         do_leds();

         do_set_rtc();       //houhh 20070713, 检测是否要更新系统当前时间到cmos rtc设备...

         do_timer(regs);

         do_profile(regs);

}

 

一般认为时钟中断计时的精度较高,所以在时钟中断服务程序中会每隔11分钟(660)就检测一次是否须要将此时的系统时间写回到RTC当中, 所以在ep93xx的时钟中断服务程序中,须要加上do_set_rtc(),关于这个函数具体的功能请具体参见源码.

 


.  RTC设备驱动程序

 

主要指出RTC设备及相关操作,这一块相当简单,RTC时钟处理成一简单字符设备.

 

基础文件结构:

static struct file_operations pcf8563_fops = {

         owner: THIS_MODULE,

         ioctl: pcf8563_ioctl,

         open: pcf8563_open,

         release: pcf8563_release,

};

文件: linux-2.4.21\drivers\char\pcf8563_rtc.c

描述: RTC设备的文件结构,指文件打开及释放操作,其实最核心的还是ioctl,这里可以进行时间读取以及时间设置操作, 具体使用示例可以参考rtctest.c示例文件.

 

文件: linux-2.4.21\drivers\char\pcf8563_rtc.c

描述:时间设置与读取.

get_rtc_time(struct rtc_time *tm);

Set_rtc_time(struct rtc_time *tm);

 

文件: linux-2.4.21\drivers\char\ pcf8563_rtc.c

描述: I2C读写, 这两个函数是从文件i2c-ep93xx.c中引用的,定义成宏.

extern int pcf8563_writedata(uchar address, uchar mdata);

extern uchar pcf8563_readdata(uchar address);

#define rtc_reg_read(x)          pcf8563_readdata(x)

#define rtc_reg_write(x,y)              pcf8563_writedata(x, y)

 

文件: linux-2.4.21\drivers\char\ pcf8563_rtc.c

描述: 模块初始化,负责注册/注消RTC字符设备.

int __init pcf8563_init(void)

void __exit pcf8563_exit(void)


.  I2C总线读写

 

i2c总线读写方式为软件模拟方式,因为ep93xx没有相关的i2c总线控制器,因此只能通过软件方式来模拟I2C总线的读写, 在调试过程中遇到如下问题,注意如下即可:

 

1.       注意延时的时间,I2C的开始条件与读写时都须要一定的延时,根据主设备(ep93xx cpu)的运行速度,此延时必须是一个稳定的时间,通常采取读取特定外设i/o以达此目的.

2.       注意在改变I2CSDA数据线状态时,必须是在SCL时钟线为低的时候,因为根据开始与结束条件的要求,开始与结束条件是在SCL时钟线高的时候SDA拉高或者拉低,所以如果在传送数据时,SCL为高,则会被当成开始或结束条件,通信失败.

3.       在读写I2C总线时,每传送或接收一BYTE数据,必须要进行回应.

Ø         写回应:主设备写完八位数据后,在第九个周期等待从设备来拉低SDA作为回应,因此须先将SDA在第八周期SCL低时拉高,之后拉高SCL等从设备回应,等到从设备回应后拉低SCL,第九周期结束,一个BYTE传送完成.

Ø         读回应:主设备读从设备八位数据后,也应该在第九周期进行回应,分如下两种情况: 连续读n个字节时,前n-1个字节以拉低作回应,第n个字节则为拉高SDA回应,因此如若是每次只读一个字节,则回应为拉高.

4.       操作SDA/SCL PIN时,注意在读SDA时,将其设置成输入状态.

 

文件: linux-2.4.21\drivers\i2c\ i2c-ep93xx.c

描述: SDA/SCL PIN脚定义, EP93xxGPIOG口的第0, 1.

#define I2C_SDA_PORT      GPIO_PGDR

#define I2C_SDA_DIR         GPIO_PGDDR

#define I2C_SDA_MASK    0x2            //EEDAT...

#define I2C_SCL_PORT       GPIO_PGDR

#define I2C_SCL_DIR         GPIO_PGDDR

#define I2C_SCL_MASK     0x1            //EECLK...

 

文件: linux-2.4.21\drivers\i2c\ i2c-ep93xx.c

描述: SDA/SCL输入输出.

static void bit_ep93xx_setscl(void* data, int state)

{

         unsigned long flags;

         save_flags(flags);

         outl(inl(I2C_SCL_DIR) | I2C_SCL_MASK, I2C_SCL_DIR);  // tristate pin

         if (state){

                   cli();

                   outl(inl(I2C_SCL_PORT) | I2C_SCL_MASK, I2C_SCL_PORT); // drive pin

         }

         else{

                   cli();

                   outl(inl(I2C_SCL_PORT) & ~I2C_SCL_MASK, I2C_SCL_PORT); // drive pin

         }

         restore_flags(flags);

}

//===========================================================================

///  write SCL pin

//===========================================================================

static void bit_ep93xx_setsda(void* data, int state)

{

         unsigned long flags;

         save_flags(flags);

         outl(inl(I2C_SDA_DIR) | I2C_SDA_MASK, I2C_SDA_DIR);    // output...

         if (state){

                   cli(); 

                   outl(inl(I2C_SDA_PORT) | I2C_SDA_MASK, I2C_SDA_PORT); // drive pin

         }

         else{

                   cli();

阅读(4358) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~