小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

RTC實時時鐘驅(qū)動

 黃浦江中的一條魚 2009-07-16

RTC 實時時鐘驅(qū)動 收藏

 RTC 實時時鐘驅(qū)動

-------I2C軟件模擬通信

 

 

內(nèi)核版本: linux-2.4.21

文檔設(shè)計: 侯輝華

    : 1.01

    : 2007/06/10

 

 

 

 

 

 

內(nèi)容簡介: 介紹接在I2C總線上RTC實時時鐘設(shè)備的驅(qū)動, 使用軟件模擬的方法完成I2C的通信; 介紹了Linux下的時鐘系統(tǒng), 以及I2C的層次結(jié)構(gòu).

 

 

目錄索引:

 

一.        Linux下的時鐘系統(tǒng)簡介.

二.        Linux對時間的表示.

三.        Linux時鐘中斷的初始化及處理.

四.        RTC設(shè)備驅(qū)動程序.

五.        I2C總線讀寫.

.  Linux下的I2C驅(qū)動層次結(jié)構(gòu)..
.  Linux下的時鐘系統(tǒng)簡介

 

實際上,linux系統(tǒng)有兩個時鐘:一個是由主板電池驅(qū)動的“Real Time Clock”也叫做RTC或者叫CMOS時鐘,硬件時鐘。當操作系統(tǒng)關(guān)機的時候,用這個來記錄時間,但是對于運行的系統(tǒng)是不用這個時間的。另一個時間是 “System clock”也叫內(nèi)核時鐘或者軟件時鐘,是由軟件根據(jù)時間中斷來進行計數(shù)的,內(nèi)核時鐘在系統(tǒng)關(guān)機的情況下是不存在的,所以,當操作系統(tǒng)啟動的時候,內(nèi)核時鐘是要讀取RTC時間來進行時間同步.

 
linux
的內(nèi)核時間實際上是記錄從197011日距離現(xiàn)在的秒數(shù),并且以GMT(格林尼治時間)(或者叫UTC- Coordinated Universal Time)為標準, UTC是不隨著DST(夏令時)變換,需要有變化的是由應(yīng)用程序自身來完成時間的轉(zhuǎn)換。

 

.  Linux對時間的表示

通常,操作系統(tǒng)可以使用三種方法來表示系統(tǒng)的當前時間與日期:①最簡單的一種方法就是直接用一個64位的計數(shù)器來對時鐘滴答進行計數(shù)。②第二種方法就是用一個32位計數(shù)器來對秒進行計數(shù),同時還用一個32位的輔助計數(shù)器對時鐘滴答計數(shù),之子累積到一秒為止。因為232次方超過136年,因此這種方法直至22世紀都可以讓系統(tǒng)工作得很好。③第三種方法也是按時鐘滴答進行計數(shù),但是是相對于系統(tǒng)啟動以來的滴答次數(shù),而不是相對于相對于某個確定的外部時刻;當讀外部后備時鐘(如RTC)或用戶輸入實際時間時,根據(jù)當前的滴答次數(shù)計算系統(tǒng)當前時間。

Linux通常都采用第三種方法來維護系統(tǒng)的時間與日期, 通過時鐘點滴進行計時的基礎(chǔ)原理, 可以參看下面介紹的參考文檔, 主要原理是通過硬件的中斷累積來計時, 但必要要設(shè)置硬件中斷一次所須的時間, 一般具體的不同的芯片都不同, EP9302系統(tǒng)具體設(shè)置如下:

1.         EP93xx系列芯片有四個Timer計時器, 使用的是Timer1, 與具體芯片相關(guān)的內(nèi)容在如下兩個文件:

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

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

2.         針對整個Arm體系的時鐘相關(guān)文件為:

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

時鐘中斷計時的主要相關(guān)函數(shù)為如下兩個:

1.         ep93xx_gettimeoffset()的作用就是返回距最近一次時鐘中斷發(fā)生后, Timer已經(jīng)累積的時間(但還未滿足引發(fā)一次時鐘中斷), 這個時間值單位為微秒, 獲取當前時間時, 就是累計已經(jīng)發(fā)生的Timer中斷次數(shù)所經(jīng)歷的時間, 然后加上這個即將要發(fā)生中斷所過去的時間, 這樣取得當前時間的精度是相當高的.

2.         LATCH的含義是指一次時鐘中斷要經(jīng)過多少個Timer時鐘周期, TIMER1LOAD寄存器設(shè)置的就是這個值, ep93xx的計時器Timer1會將這個值一直遞減直至0, 如此就引發(fā)一次時鐘中斷, 然后又重LATCH重頭開始遞減.

3.         xtime記載的即為系統(tǒng)自開機以來的當前時間, 單位為秒, 精確度為微秒. 因此在開機時必須從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,設(shè)定時鐘中斷周期

{

         gettimeoffset = ep93xx_gettimeoffset;

         outl(0, TIMER1CONTROL);

         outl(LATCH - 1, TIMER1LOAD);          //設(shè)定Timer經(jīng)多少個Timer時鐘周期后產(chǎn)生中斷

         outl(0xc8, TIMER1CONTROL);

         xtime.tv_sec = inl(RTCDR);           //ep93xx內(nèi)部RTC模塊讀取時間,后將重新從cmos.

}

以下詳細介紹一下時鐘點滴計時的幾個基本參數(shù), 以下定義的出處, 除非特別指出, 一般是位于各自不同的平臺的文件夾下定義:

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

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

 

1.         時鐘周期(clock cycle)的頻率:計時器Timer晶體振蕩器在1秒時間內(nèi)產(chǎn)生的時鐘脈沖個數(shù)就是時鐘周期的頻率, 要注意這個Timer的時鐘周期頻率要與時鐘中斷的頻率區(qū)別開來,  Linux用宏CLOCK_TICK_RATE來表示計時器的輸入時鐘脈沖的頻率(此值在EP93xx上是508KHZ),該宏定義在timex.h頭文件中:

#define CLOCK_TICK_RATE 508000 /* Underlying HZ */

2.         時鐘中斷(clock tick):我們知道當計數(shù)器減到0值時,它就在IRQ0上產(chǎn)生一次時鐘中斷,也即一次時鐘中斷, 計數(shù)器的初始值決定了要過多少時鐘周期才產(chǎn)生一次時鐘中斷,因此也就決定了一次時鐘滴答的時間間隔長度. EP93xx系統(tǒng)中, Timer1TIMER1LOAD 的值決定過多少時鐘周期后產(chǎn)生一次時鐘中斷.

3.         時鐘中斷的頻率(HZ):也即1秒時間內(nèi)Timer所產(chǎn)生的時鐘中斷次數(shù)。確定了時鐘中斷的頻率值后也就可以確定Timer的計數(shù)器初值。Linux內(nèi)核用宏HZ來表示時鐘中斷的頻率,而且在不同的平臺上HZ有不同的定義值。對于SPARCMIPS、ARMi386等平臺HZ的值都是100。該宏在ARM平臺上的定義如下(param.h):

#ifndef HZ

#define HZ 100

#endif

據(jù)HZ值,可知每隔(1000msHZ)=10ms發(fā)生一次時鐘中斷.

 

4.         時鐘中斷的時間間隔: Linux用全局變量tick來表示時鐘中斷的時間間隔長度,其實定義了HZ之后, 即決定了此間隔值, tick變量的單位是微妙(μs), 該變量定義在kernel/timer.c文件中,如下:

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

5.         LATCHLinux用宏LATCH來定義要設(shè)置到Timer中的值,它表示TImer將沒隔多少個時鐘周期產(chǎn)生一次時鐘中斷。顯然LATCH應(yīng)該由下列公式計算: LATCH=(1秒之內(nèi)的Timer時鐘周期個數(shù))÷(1秒之內(nèi)的時鐘中斷次數(shù))=CLOCK_TICK_RATE)÷(HZ.

 

.  Linux時鐘中斷的初始化及處理

 

以下著重描述一下時鐘中斷時與RTC相關(guān)的修改:

 

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

描述:時鐘初始化化, start_kernel()當中調(diào)用.

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

描述:安裝時鐘中斷服務(wù)程序,從RTC更新初始化系統(tǒng)時鐘, time_init()當中調(diào)用.

/*

 * 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設(shè)備中讀取當前時間

         set_rtc = set_cmos_time;                 //初始化更新系統(tǒng)時間到RTC的函數(shù), do_set_rtc()中調(diào)用

/////////

         timer_irq.handler = ep93xx_timer_interrupt;     //時鐘中斷服務(wù)程序

         setup_arm_irq(IRQ_TIMER1, &timer_irq);      //安裝時鐘中斷處理

}

 

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

描述:讀寫I2CRTC設(shè)備,軟件模擬方式, 引自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

描述:設(shè)置及讀寫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

描述:時鐘中斷服務(wù)程序,并檢測更新系統(tǒng)時鐘到RTC, setup_timer()當中調(diào)用.

/*

 * 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, 檢測是否要更新系統(tǒng)當前時間到cmos rtc設(shè)備...

         do_timer(regs);

         do_profile(regs);

}

 

一般認為時鐘中斷計時的精度較高,所以在時鐘中斷服務(wù)程序中會每隔11分鐘(660)就檢測一次是否須要將此時的系統(tǒng)時間寫回到RTC當中, 所以在ep93xx的時鐘中斷服務(wù)程序中,須要加上do_set_rtc(),關(guān)于這個函數(shù)具體的功能請具體參見源碼.

 


.  RTC設(shè)備驅(qū)動程序

 

主要指出RTC設(shè)備及相關(guān)操作,這一塊相當簡單,RTC時鐘處理成一簡單字符設(shè)備.

 

基礎(chǔ)文件結(jié)構(gòu):

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設(shè)備的文件結(jié)構(gòu),指文件打開及釋放操作,其實最核心的還是ioctl,這里可以進行時間讀取以及時間設(shè)置操作, 具體使用示例可以參考rtctest.c示例文件.

 

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

描述:時間設(shè)置與讀取.

get_rtc_time(struct rtc_time *tm);

Set_rtc_time(struct rtc_time *tm);

 

文件: linux-2.4.21\drivers\charpcf8563_rtc.c

描述: I2C讀寫, 這兩個函數(shù)是從文件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\charpcf8563_rtc.c

描述: 模塊初始化,負責注冊/注消RTC字符設(shè)備.

int __init pcf8563_init(void)

void __exit pcf8563_exit(void)


.  I2C總線讀寫

 

i2c總線讀寫方式為軟件模擬方式,因為ep93xx沒有相關(guān)的i2c總線控制器,因此只能通過軟件方式來模擬I2C總線的讀寫, 在調(diào)試過程中遇到如下問題,注意如下即可:

 

1.       注意延時的時間,I2C的開始條件與讀寫時都須要一定的延時,根據(jù)主設(shè)備(ep93xx cpu)的運行速度,此延時必須是一個穩(wěn)定的時間,通常采取讀取特定外設(shè)i/o以達此目的.

2.       注意在改變I2CSDA數(shù)據(jù)線狀態(tài)時,必須是在SCL時鐘線為低的時候,因為根據(jù)開始與結(jié)束條件的要求,開始與結(jié)束條件是在SCL時鐘線高的時候SDA拉高或者拉低,所以如果在傳送數(shù)據(jù)時,SCL為高,則會被當成開始或結(jié)束條件,通信失敗.

3.       在讀寫I2C總線時,每傳送或接收一BYTE數(shù)據(jù),必須要進行回應(yīng).

Ø         寫回應(yīng):主設(shè)備寫完八位數(shù)據(jù)后,在第九個周期等待從設(shè)備來拉低SDA作為回應(yīng),因此須先將SDA在第八周期SCL低時拉高,之后拉高SCL等從設(shè)備回應(yīng),等到從設(shè)備回應(yīng)后拉低SCL,第九周期結(jié)束,一個BYTE傳送完成.

Ø         讀回應(yīng):主設(shè)備讀從設(shè)備八位數(shù)據(jù)后,也應(yīng)該在第九周期進行回應(yīng),分如下兩種情況: 連續(xù)讀n個字節(jié)時,前n-1個字節(jié)以拉低作回應(yīng),第n個字節(jié)則為拉高SDA回應(yīng),因此如若是每次只讀一個字節(jié),則回應(yīng)為拉高.

4.       操作SDA/SCL PIN時,注意在讀SDA時,將其設(shè)置成輸入狀態(tài).

 

文件: linux-2.4.21\drivers\i2ci2c-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();

                   outl(inl(I2C_SDA_PORT) & ~I2C_SDA_MASK, I2C_SDA_PORT); // drive pin

         }                

         restore_flags(flags);

}

 

具體有關(guān)I2C總線軟件模擬通信部分,參考源碼.


.  Linux下的I2C驅(qū)動層次結(jié)構(gòu).

 

Linux最大的特點,就是將一系列的驅(qū)動中共有的東西抽象出來,大大提高代碼的共享與利用率,這樣使具體硬件設(shè)備的驅(qū)動作者無須關(guān)注驅(qū)動中共性的部分,I2C驅(qū)動同樣如此,這里我不打算詳細的分析I2C的層次結(jié)構(gòu),只是簡單的陪析一下其大略的層次, 并指出幾個不易理解的地方。

 

1.       適配器層(adapters)

描述: 這一層簡單的理解,可以理解成是I2C總線的抽象,它提供訪問I2C總線的方法規(guī)則,并包含在物理總線上適用此規(guī)則進行I2C通信的I2C設(shè)備, 而且I2C設(shè)備并不須是在同一條總線上,只須滿足相同的訪問I2C總線方法規(guī)則,且設(shè)備地址不重重疊.

2.       I2C設(shè)備驅(qū)動層(i2c client driver)

描述:具體到每個掛在I2C上的設(shè)備, 針對特定的設(shè)備可以有不同的驅(qū)動, 諸如設(shè)備標志ID, 設(shè)備名稱, 設(shè)備屬性志Flag, 還有就是設(shè)備連接及拔除的相應(yīng)處理.

3.       I2C數(shù)據(jù)傳送規(guī)則層(algorithms)

描述: 針對具體的I2C總線, 可以有不同的數(shù)據(jù)傳輸規(guī)則, 分為三種:

ü         大體來說如果本身就有I2C控制器的, I2C使用起來就比較簡單, 傳輸入數(shù)據(jù)時就是讀寫一些寄存器即可.

ü         通地軟件模擬方式進行數(shù)據(jù)傳輸, I2C中這個規(guī)則被稱為algorithms for bit-shift, 這種編程起來稍微麻煩一些, 要用軟件模擬I2C的通信協(xié)議規(guī)則.

ü         通過每三方總線間接的連接I2C總線的, ISA總線, 此時I2C是間接連接在系統(tǒng)上, 這種方式通常與具體的所連接上的總線相關(guān).

另外, 還有一種規(guī)則是針對SMBus總線, 這個總線是Intel推出的兼容I2C協(xié)議的, 可能一次傳送一個字,兩個字節(jié),多個字節(jié)等等, 這里沒有用到,并不詳述, 由此我們可以理會I2C設(shè)計者設(shè)計的結(jié)構(gòu)的靈活程度.

 

以上簡述了I2C驅(qū)動層次結(jié)構(gòu), 現(xiàn)在具體的就PCF8563 RTC來描述每一層具體由哪些文件組成, 結(jié)合實際的驅(qū)動文件說明:

 

1.       適配器層----------------- i2c-core.c, i2c-dev.c

2.       I2C設(shè)備驅(qū)動層---------pcf8563-rtc.c

3.       I2C數(shù)據(jù)傳送規(guī)則層--- i2c-algo-bit.c, i2c-ep93xx

 

大體對照I2C驅(qū)動的層次結(jié)構(gòu), 結(jié)合以上文件來看, 可以理解I2C的驅(qū)動.

 

I2C驅(qū)動代碼中不易理解的幾點:

 

結(jié)合在代碼閱讀時我所經(jīng)歷的過程, 說明以下幾點不易明白的地方:

 

1.       i2c設(shè)備的識別

描述:每個I2C設(shè)備有自己特定的設(shè)備地址,通常為讀地址及寫地址; 2.4版當中是采取遍歷的方式來查找I2C設(shè)備應(yīng)該, 每個i2c設(shè)備都會標明自己所處的地址, 描述i2c設(shè)備所處地址范圍的結(jié)構(gòu)比較復(fù)雜, 它包括描述i2c設(shè)備所處地址范圍, 要忽略的地址范圍, 應(yīng)當強制檢測的范圍等, 個人感覺些結(jié)構(gòu)設(shè)計過于重復(fù), 2.6版中已經(jīng)簡化.

具體的I2C設(shè)備檢測時, 即根據(jù)這些地址, 0~0x7f開始檢測, 檢測時會跳出過無須檢測的范圍. 比如一i2c設(shè)備寫地址為0xa0, 則在此描述時給出的地址值是0x50, 因此在真實寫此設(shè)備時, 必須將地址左移兩位得真實地址.

 

2.4i2c地址描述結(jié)構(gòu):

struct i2c_client_address_data {

      unsigned short *normal_i2c;

      unsigned short *normal_i2c_range;

      unsigned short *probe;

      unsigned short *probe_range;

      unsigned short *ignore;

      unsigned short *ignore_range;

      unsigned short *force;

};

2.6i2c地址描述結(jié)構(gòu):

struct i2c_client_address_data {

      unsigned short *normal_i2c;

      unsigned short *probe;

      unsigned short *ignore;

      unsigned short **forces;

};

 

2.       i2c設(shè)備設(shè)備的讀與寫

描述:設(shè)備地址一般為7, 此地址是字節(jié)的高七位, 最低的一位用于描述是讀設(shè)備還是寫設(shè)備, 因此可知I2C設(shè)備讀寫地址必然相連, 因此讀寫地址轉(zhuǎn)換通常為最低位或上1, 或者清除1, 另外還記得這里描述的地址必須左移兩位, 因為給出的設(shè)備地址是以低七位形式給出.

寫地址一般是小于讀地址, 所以通常讀地址都是寫地址或上1即得, 因此在設(shè)備驅(qū)動結(jié)構(gòu)i2c_client, 僅用一addr成員來描述設(shè)備地址.

具體來說, i2c-algo-bit.c文件下的bit_doAddress()函數(shù), 如果是七位地址的I2C總線, 其處理如下, 將設(shè)備地址左移兩位得寫地址, 然后或上1, 即得讀地址.

addr = ( msg->addr << 1 );

if (flags & I2C_M_RD )

addr |= 1;

3.       i2c adapters algorithms的管理.

描述: 每一個adapters 都會有一個唯一的標志, 如我們的EP93xx則定義為I2C_HW_B_EP93XX, 每一種algorithms都也有一個ID標志, bit-shift algorithms的標志即為: I2C_ALGO_BIT.,  關(guān)于這些標志的定義, 可以查看i2c-id.h文件.

        具體的, 添加一個bit-shift algorithmsadapters , 可以調(diào)用i2c_bit_add_bus()函數(shù), 這個調(diào)用由用戶發(fā)出, 一般在模塊加載的init函數(shù)中調(diào)用, 每當加入一個adapters, 都會檢測已經(jīng)注冊的i2c driver設(shè)備是否適用此adapters, 如果適用則調(diào)用該i2c設(shè)備driver下的attach_adapter, 通知設(shè)備發(fā)現(xiàn)其相應(yīng)適配器, 只有找到相應(yīng)適配器, 該設(shè)備才能使用i2c總線.

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多