uboot中timer定時器的設置,uboot匯編部分沒有對timer初始化,而是在C中。在C入口函數void start_armboot(void)中,首先是通過如下代碼方式調用:
[cpp]
typedef int (init_fnc_t) (void);
[html]
init_fnc_t *init_sequence[] = {
a/a/l/board.c
#if defined(CONFIG_ARCH_CPU_INIT)
arch_cpu_init, /* basic arch cpu dependent setup */
#endif
board_init, /* basic board dependent setup */
#if defined(CONFIG_USE_IRQ)
interrupt_init, /* set up exceptions */
#endif
timer_init, /* initialize timer */
#ifdef CONFIG_FSL_ESDHC
get_clocks,
#endif
env_init, /* initialize environment */
init_baudrate, /* initialze baudrate settings */
serial_init, /* serial communications setup */
console_init_f, /* stage 1 init of console */
display_banner, /* say that we are here */
#if defined(CONFIG_DISPLAY_CPUINFO)
print_cpuinfo, /* display cpu info (and speed) */
#endif
#if defined(CONFIG_DISPLAY_BOARDINFO)
checkboard, /* display board info */
#endif
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)
init_func_i2c,
#endif
dram_init, /* configure available RAM banks */
#if defined(CONFIG_CMD_PCI) || defined (CONFIG_PCI)
arm_pci_init,
#endif
display_dram_config,
NULL,
};
這裡是u-boot的C代碼部分的入口:
[cpp]
void start_armboot (void)
{
init_fnc_t **init_fnc_ptr;
。
。
。
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
上面的函數調用很復雜,不是討論這個所以暫時不深究,直接看它調用arch/arm/cpu/arm920t/s3c24x0/timer.c文件中的函數int timer_init (void),代碼如下:
[cpp]
int timer_init(void)
{
struct s3c24x0_timers *timers = s3c24x0_get_base_timers();
ulong tmr;
/* use PWM Timer 4 because it has no output */
/* prescaler for Timer 4 is 16 */
writel(0x0f00, &timers->TCFG0);
if (timer_load_val == 0) {
/*
* for 10 ms clock period @ PCLK with 4 bit divider = 1/2
* (default) and prescaler = 16. Should be 10390
* @33.25MHz and 15625 @ 50 MHz
*/
timer_load_val = get_PCLK() / (2 * 16 * 100);
timer_clk = get_PCLK() / (2 * 16);
}
/* load value for 10 ms timeout */
lastdec = timer_load_val;
writel(timer_load_val, &timers->TCNTB4);
/* auto load, manual update of Timer 4 */
tmr = (readl(&timers->TCON) & ~0x0700000) | 0x0600000;
writel(tmr, &timers->TCON);
/* auto load, start Timer 4 */
tmr = (tmr & ~0x0700000) | 0x0500000;
writel(tmr, &timers->TCON);
timestamp = 0;
return (0);
}
以下是對函數的詳細分析:
1、先看數據結構s3c24x0_timers
[cpp]
struct s3c24x0_timers {
u32 TCFG0;
u32 TCFG1;
u32 TCON;
struct s3c24x0_timer ch[4];
u32 TCNTB4;
u32 TCNTO4;
};
有引出來了另一個結構s3c24x0_timer
[cpp]
struct s3c24x0_timer {
u32 TCNTB;
u32 TCMPB;
u32 TCNTO;
};
看下這兩個結構是不是涵蓋了所有timer的寄存器,對照s3c2440的datasheet得出結論是:timer寄存器地址空間從(0x51000000~0x51000040)依次對應的寄存器名是卻是是上面的結構體中的寄存器,總共是17個寄存器.
為什麼會有4個結構s3c24x0_timer呢?因為2440有5個timer,其中前四個timer0~timer3工作方式類似,都有外部引腳引出,而第五個timer4不同與他們,第一個就是它沒有TCMPB寄存器,具體的區別可以看看這個文檔或是datasheet:
2、s3c24x0_get_base_timers()函數返回的就是timer寄存器空間的起始地址0x51000000
[cpp]
static inline struct s3c24x0_timers *s3c24x0_get_base_timers(void)
{
return (struct s3c24x0_timers *)S3C24X0_TIMER_BASE;
}
3、
[cpp]
/* use PWM Timer 4 because it has no output */
/* prescaler for Timer 4 is 16 */
這個注釋是不是有問題呢,我怎麼查不到timer4預分頻器是16位的,都是8位。是不是要表達計數器是16位的呢?
4、writel(0x0ff,&timers->TCFG0)的作用是什麼呢?在arch/arm/include/asm/io.h文件中既然有3處定義了這個函數,考察了一下只有下面被編譯:
[cpp]
#define writel(v,a) __arch_putl(v,a)
[cpp]
#define __arch_putl(v,a) (*(volatile unsigned int *)(a) = (v))
分析:就是往a寄存器寫入v.
這裡就是往寄存器TCFG0寫入0x0ff,先看下TCFG0寄存器信息:
將timer0、1的預分頻器的值寫ff就是255,
其值N為: 0~255
輸出頻率為:PCLK ÷(N+1)
這時候N = 255,結合這篇文檔http://blog.csdn.net/sonbai/article/details/8687858知道PCLK的頻率是101.25MHz,輸出頻率就出來了,這時候最低,同時沒有用到他們,所以不考慮了。t只考慮imer4,這時候N = 0,輸出頻率最大就是PCLK。為什麼要設最大,先放著
5、timer_load_val是一個全局變量,顧名思義它是裝入TCNTB4寄存器的值。看下面的注釋:
[cpp]
/*
* for 10 ms clock period @ PCLK with 4 bit divider = 1/2
* (default) and prescaler = 16. Should be 10390
* @33.25MHz and 15625 @ 50 MHz
*/
它的意思是如果要將timer4的時鐘周期設置為10ms的話,要通過PCLK、分配器divider默認的值1/2和prescaler = 16來設置如何設置(?)來看一下下面這個函數
[cpp]
/* return PCLK frequency */
ulong get_PCLK(void)
{
struct s3c24x0_clock_power *clk_power = s3c24x0_get_base_clock_power();
return (readl(&clk_power->CLKDIVN) & 1) ? get_HCLK() / 2 : get_HCLK();
}
看看結構s3c24x0_clock_power
[cpp]
/* CLOCK & POWER MANAGEMENT (see S3C2400 manual chapter 6) */
/* (see S3C2410 manual chapter 7) */
struct s3c24x0_clock_power {
u32 LOCKTIME;
u32 MPLLCON;
u32 UPLLCON;
u32 CLKCON;
u32 CLKSLOW;
u32 CLKDIVN;
#if defined(CONFIG_S3C2440)
U32 CAMDIVN;
#endif
};
涵蓋了所有時鐘設置的寄存器,s3c24x0_get_base_clock_power();返回的是(struct s3c24x0_clock_power *)0x4C000000也就是時鐘設置寄存器的起始地址
[cpp]
return (readl(&clk_power->CLKDIVN) & 1) ? get_HCLK() / 2 : get_HCLK();
判斷一下CLKDIVN是不是被設置成了1,如果是說明PCLK = 1/2HCLK,不在去看get_HCLK了。前面我已經說過PCLK = 101.25MHz,至於timer_load_val = 101.25MHz / (2*16*100);這個公式,分析如下:
我們要設置timer4的時鐘周期是T = 10ms;
又 T = [1 /(PCLK / (預分頻器的值(prescaler)*分頻器的值(div)))] *timer_load_val
已知T、PCLK、perscaler、div,就可以求的
timer_load_val = PCLK / (perscaler * div)
但是這裡為什麼會有100呢?(暫時不清楚,望大家給我一個解釋)
timer_clk下面暫時沒有遇到,先不考慮。
6、
[cpp]
/* load value for 10 ms timeout */
lastdec = timer_load_val;
writel(timer_load_val, &timers->TCNTB4);
然後將timer_load_val的值寫入TCNTB4寄存器中
7、下面是要配置TCON寄存器,也就是timer控制寄存器
[cpp]
/* auto load, manual update of Timer 4 */
tmr = (readl(&timers->TCON) & ~0x0700000) | 0x0600000;
writel(tmr, &timers->TCON);
先來看看TCON寄存器信息:
TCON現實與(~0x0700000)與運算,即TCON = TCON & F8FFFFF,也就是將TCON的20、21、21這幾個位清零,然後TCON = TCON | 0X0600000將位21、20設置為1,對照datasheet知道將timer4設置為自動裝載,和手動更新.這就有一個矛盾既然自動轉載干嘛還要手動更新呢?看下8就知道了。writel()函數已經介紹過了。
8、你看下面這個代碼就很有趣,他和上面的那個代碼很類似
[cpp]
/* auto load, start Timer 4 */
tmr = (tmr & ~0x0700000) | 0x0500000;
writel(tmr, &timers->TCON);
起始2440定時器第一次往TCONTB裝入初始值是要手動更新的,如果沒有更新初值自動更新那更新什麼呢,他不知道,TCONTB默認是0,更新0有什麼用呢。所以需要手動更新,然後關掉手動更新,設置好後,再啟動timer4就完成了timer4的設置和運行。
9、timestamp = 0,這句我們暫時還沒涉及到,他是後面要用的。
10、然後返回
[cpp]
return (0);