本文均属自己阅读源码的点滴总结,转账请注明出处谢谢。
欢迎和大家交流。qq:1037701636 email:200803090209@zjut.com,gzzaigcn2012@gmail.com
过去的一周,一直处在纠结的时刻中,一周过去了,基本问题和疑惑也在渐渐的解决中,回过头去想想,原来问题的出现,只是一个小小的地方就可以解决。也觉得出现问题定位不到问题的所在也是只身能力的不足。为了将触摸屏驱动完全按照自己的想法来工作起来,从linux内核到驱动,到各种添加打印信息,修改添加源码;再到uboot里面去看源码,读源码,读汇编(真心伤不起啊),差点没回过去看x-loader的源码。好吧,下面就和大家来分析一下beagelboard-xm(dm3730)的时钟配置的代码吧。在内核的话这里就不提了,内核会维护着一个核心的结构体,在加载时钟驱动时,会通过读取每一个时钟对应的register来完成初始化,后续其他需要时钟时只要简单的调用api就可以了。
下面和大家分析uboot中的时钟配置相关的内容吧,希望和大家交流,其实也只是懂了大部分而已:
1.uboot如何启动时钟配置函数?
在uboot的时钟初始化当然也不是最先的初始化,因为rom启动了x-loader,也会加载x-loader来完成第一次核心的时钟初始化,uboot里面的初始化在sram中进行时其实这么算应该是第二次了。
在前面的博文由驱动板级初始化发生的联想:内核解压,机器码匹配,uboot之bootm解析里面其实已经有所分析到,这里在详细的进行一下分析,包括我认为的uboot启动部分的时钟模块设计到的核心架构。
核心的目录:/arch/arm/cpu/armv7/omap3,/arch/arm/lib,/arch/arm/include/arch-omap3,
时钟初始化设计到的核心文件:
/arch/arm/cpu/armv7/start.s ,syslib.c,u-boot.lds
/arch/arm/include/arch-omap3/clock.c,board.c,sysinfo.c,lowlevel_init.s
/arch/arm/include/arch-omap3/clocks_omap3.h
uboot的核心启动过程在这里不做详细分析,网上有这方面很详细的uboot之start.s启动分析 uboot之start.s启动分析 uboot之start.s启动分析,里面的内容很丰富,整个start,s的过程很清楚明了。
简单说一下uboot如何进入时钟的初始化配置模块:
在start,s中如下代码:
#ifndef CONFIG_SKIP_LOWLEVEL_INIT这个就是跳入cpu的初始化,会调用lowlevel_init如下:
blcpu_init_crit
#endif
/*在lowlevel_init中跳入位于lowlevel_init.s的源代码处
* Jump to board specific initialization...
* The Mask ROM will have already initialized
* basic memory. Go here to bump up clock rate and handle
* wake up conditions.
*/
movip, lr@ persevere link reg across call
bllowlevel_init@ go setup pll,mux,memory
movlr, ip@ restore link
movpc, lr@ back to my caller
/*
.globl lowlevel_init这里进入s_init函数中来完成pll,mux,memory的初始化。分别完成pll时钟,复合管脚的配置以及内存模块的配置
lowlevel_init:
ldrsp, SRAM_STACK
strip, [sp]/* stash old link register */
movip, lr/* save link reg across call */
bls_init/* go setup pll, mux, memory */
ldrip, [sp]/* restore save ip */
movlr, ip/* restore link reg */
/* back to arch calling code */
movpc, lr
/* the literal pools origin */
.ltorg
void s_init(void)
{
int in_sdram = is_running_in_sdram();
watchdog_init();
try_unlock_memory();
/*
* Right now flushing at low MPU speed.
* Need to move after clock init
*/
invalidate_dcache(get_device_type());
#ifndef CONFIG_ICACHE_OFF
icache_enable();
#endif
#ifdef CONFIG_L2_OFF
l2_cache_disable();
#else
l2_cache_enable();
#endif
/*
* Writing to AuxCR in U-boot using SMI for GP DEV
* Currently SMI in Kernel on ES2 devices seems to have an issue
* Once that is resolved, we can postpone this config to kernel
*/
if (get_device_type() == GP_DEVICE)
setup_auxcr();
set_muxconf_regs();//设置复合管脚
delay(100);
prcm_init();//时钟的配置调用
per_clocks_enable();//把配置好的时钟使能。
if (!in_sdram)
mem_init();
}
至此就可以进入DM3730专用的电源 复位,时钟控制模块,在该函数内部来完成所需要的时钟配置。
在完成lowlevel_init之后就是调用startarm_boot进入C语言的世界(该函数也在Start.s之中,只是在调用完cpu_init_crit之后才会被调用 ldr pc, _start_armboot @ jump to C code)。
2.dm3730专属的时钟模块解析和源码的配置解析
dm3730的prcm的内容很庞大,看了一个星期也没消化多少,无论对于硬件开发还是软件设计,对电源的合理管理是一个很重要的部分,在cpu越来越快的时代,功耗一直都是开发首先需要考虑的关键问题。Prcm可以很好的帮助开发者来合理的设计自己的时钟,当然参考TI的设计源码是较为理想的一部份内容,对于TI的庞大的时钟,可以使用TI的时钟树ClockTree(http://www.ti.com/general/docs/wtbu/wtbudocumentcenter.tsp?templateId=6123&navigationId=12037)来方便自己的设计。
在uboot中对prcm的pll3(core_pll),pll4,per_pll(pll5)还有mpu的时钟分别进行了初始化。下图为PRCM模块的核心视图。
void prcm_init(void)
{
u32 osc_clk = 0, sys_clkin_sel;
u32 clk_index, sil_index = 0;
struct prm *prm_base = (struct prm *)PRM_BASE; // 48306000
struct prcm *prcm_base = (struct prcm *)PRCM_BASE;/*
* Gauge the input clock speed and find out the sys_clkin_sel
* value corresponding to the input clock.
*/
osc_clk = get_osc_clk_speed();//获取时钟,即板子上输入的晶振频率26MHz
//根据输入时钟的实际频率获取时钟的相关select,用于后续时钟的配置
get_sys_clkin_sel(osc_clk, &sys_clkin_sel);//3
/* set input crystal speed *///
sr32(&prm_base->clksel, 0, 3, sys_clkin_sel); //设置系统时钟对于寄存器为sys_clkin_sel = 3,26MHz/* If the input clock is greater than 19.2M always divide/2 */
//if ((!(get_cpu_family() == CPU_OMAP36XX))&sys_clkin_sel > 2) { //by gzzCore_clk = 664MHz
if(sys_clkin_sel > 2) {
/* input clock divider */
sr32(&prm_base->clksrc_ctrl, 6, 2, 2);//48306000+1270
clk_index = sys_clkin_sel / 2;// =1,sys_clk = 13M,divide into 1/2
} else {
/* input clock divider */
sr32(&prm_base->clksrc_ctrl, 6, 2, 1);
clk_index = sys_clkin_sel;
}if (get_cpu_family() == CPU_OMAP36XX) { //匹配得到属于cpu omap36xx 家族
/* Unlock MPU DPLL (slows things down, and needed later) */
sr32(&prcm_base->clken_pll_mpu, 0, 3, PLL_LOW_POWER_BYPASS);
wait_on_value(ST_MPU_CLK, 0, &prcm_base->idlest_pll_mpu,
LDELAY);//先延时时间使mpu无效dpll3_init_36xx(0, clk_index); //pll3时钟初始化
dpll4_init_36xx(0, clk_index);//pll4时钟模块
iva_init_36xx(0, clk_index);//dsp模块需要的时钟
mpu_init_36xx(0, clk_index);//cpu处理器时钟/* Lock MPU DPLL to set frequency */
sr32(&prcm_base->clken_pll_mpu, 0, 3, PLL_LOCK);
wait_on_value(ST_MPU_CLK, 1, &prcm_base->idlest_pll_mpu,
LDELAY);
} else {
/*
..........................省略。。
. * }
/* Set up GPTimers to sys_clk source only */
sr32(&prcm_base->clksel_per, 0, 8, 0xff);
sr32(&prcm_base->clksel_wkup, 0, 1, 1);//定时器时钟的配置sdelay(5000);
}
这部分的代码其实核心就死配置相关的时钟寄存器,主要有两个核心模块prm和prcm,地址分别为:
#define PRCM_BASE 0x48004000
#define PRM_BASE 0x48306000
由于需求自己只是对 dpll3_init_36xx(0, clk_index);做了深入的分析
static void dpll3_init_36xx(u32 sil_index, u32 clk_index)
{
struct prcm *prcm_base = (struct prcm *)PRCM_BASE;
dpll_param *ptr = (dpll_param *) get_36x_core_dpll_param();
void (*f_lock_pll) (u32, u32, u32, u32);
int xip_safe, p0, p1, p2, p3;
xip_safe = is_running_in_sram();
/* Moving it to the right sysclk base */
ptr += clk_index;
if (xip_safe) {
/* CORE DPLL */
/* Select relock bypass: CM_CLKEN_PLL[0:2] */
sr32(&prcm_base->clken_pll, 0, 3, PLL_FAST_RELOCK_BYPASS);//48004000+d00
wait_on_value(ST_CORE_CLK, 0, &prcm_base->idlest_ckgen,//48004000 +d20
LDELAY);
/* CM_CLKSEL1_EMU[DIV_DPLL3] */
sr32(&prcm_base->clksel1_emu, 16, 5, CORE_M3X2);//48004000+1140
/* M2 (CORE_DPLL_CLKOUT_DIV): CM_CLKSEL1_PLL[27:31] */
sr32(&prcm_base->clksel1_pll, 27, 5, ptr->m2); ////48004000+d40
/* M (CORE_DPLL_MULT): CM_CLKSEL1_PLL[16:26] */
sr32(&prcm_base->clksel1_pll, 16, 11, ptr->m);
/* N (CORE_DPLL_DIV): CM_CLKSEL1_PLL[8:14] */
sr32(&prcm_base->clksel1_pll, 8, 7, ptr->n);
/* Source is the CM_96M_FCLK: CM_CLKSEL1_PLL[6] */
sr32(&prcm_base->clksel1_pll, 6, 1, 0);
/* SSI */
sr32(&prcm_base->clksel_core, 8, 4, CORE_SSI_DIV);//48004000+a40
/* FSUSB */
sr32(&prcm_base->clksel_core, 4, 2, CORE_FUSB_DIV);
/* L4 */
sr32(&prcm_base->clksel_core, 2, 2, 2);//by gzz
/* L3 */
sr32(&prcm_base->clksel_core, 0, 2, 2);
/* GFX */
sr32(&prcm_base->clksel_gfx, 0, 3, GFX_DIV);
/* RESET MGR */
sr32(&prcm_base->clksel_wkup, 1, 2, WKUP_RSM);
/* FREQSEL (CORE_DPLL_FREQSEL): CM_CLKEN_PLL[4:7] */
sr32(&prcm_base->clken_pll, 4, 4, ptr->fsel);
/* LOCK MODE */
sr32(&prcm_base->clken_pll, 0, 3, PLL_LOCK);
wait_on_value(ST_CORE_CLK, 1, &prcm_base->idlest_ckgen,
LDELAY);
} else if (is_running_in_flash()) {
/*
* if running from flash, jump to small relocated code
* area in SRAM.
*/
f_lock_pll = (void *) ((u32) &_end_vect - (u32) &_start +
SRAM_VECT_CODE);
p0 = readl(&prcm_base->clken_pll);
sr32(&p0, 0, 3, PLL_FAST_RELOCK_BYPASS);
/* FREQSEL (CORE_DPLL_FREQSEL): CM_CLKEN_PLL[4:7] */
sr32(&p0, 4, 4, ptr->fsel);
p1 = readl(&prcm_base->clksel1_pll);
/* M2 (CORE_DPLL_CLKOUT_DIV): CM_CLKSEL1_PLL[27:31] */
sr32(&p1, 27, 5, ptr->m2);
/* M (CORE_DPLL_MULT): CM_CLKSEL1_PLL[16:26] */
sr32(&p1, 16, 11, ptr->m);
/* N (CORE_DPLL_DIV): CM_CLKSEL1_PLL[8:14] */
sr32(&p1, 8, 7, ptr->n);
/* Source is the CM_96M_FCLK: CM_CLKSEL1_PLL[6] */
sr32(&p1, 6, 1, 0);
p2 = readl(&prcm_base->clksel_core);
/* SSI */
sr32(&p2, 8, 4, CORE_SSI_DIV);
/* FSUSB */
sr32(&p2, 4, 2, CORE_FUSB_DIV);
/* L4 */
sr32(&p2, 2, 2, 2);
/* L3 */
sr32(&p2, 0, 2, 2);
p3 = (u32)&prcm_base->idlest_ckgen;
(*f_lock_pll) (p0, p1, p2, p3);
}
}
在 dpll_param *ptr = (dpll_param *) get_36x_core_dpll_param();中获取core_dpll的参数配置,这部分的参数是dpll时钟倍频,分频,配置时钟所用,由结构体
/* Used to index into DPLL parameter tables */
typedef struct {
unsigned int m;
unsigned int n;
unsigned int fsel;
unsigned int m2;
} dpll_param;
在完成,这边先简单介绍pll3的硬件部分,详细内容见DM3730 TRM PRCM部分:
从图中可以看到M,N,M2分别代表倍频,分频参数的配置,可看下面的图:
很清楚可以发现, 如果要输出想要的时钟配置,只需要设置相应的register即可。在源代码中通过sr32来完成。而uboot的这组dpll_param参数来至于levelInit.s中,在这里定义了相应SYS_CLK对于的pll3时钟的输出,pll因为作为了芯片内部其他模块的输入时钟,以及用于实现对各个接口模块的同步性使用的L3,L4_clock.也称之为core_clock.
core_36x_dpll_param:
/* 12MHz */
.word 100, 2, 0, 1
/* 13MHz */
.word 200, 12, 0, 1
/* 19.2MHz */
.word 375, 17, 0, 1
/* 26MHz */
.word 400, 12, 0, 1
/* 38.4MHz */
.word 375, 35, 0, 1
所以可以根据需要修改这些参数来实现初始化的配置。比如现SYS_CLK=26M,但是程序的clk_index只是对应在13MHz的参数,所以可以最终输出的时钟频率为400MHz,而L3,L4的时钟也在这个基础上分别进行2和4分频,分频参数可以在/arch/arm/include/arch-omap3/clocks_omap3.h中进行修改,到此,时钟的uboot的初始化也变得不在复杂。
但是这段uboot代码不知道是什么原因,其实是不会被执行的,因为代码里体现出要查询代码的执行位置,如sdam,nand等,由于uboot不在上述设备中被执行,所以都会跳过该初始化。我理解的原因是:这里也许是觉得xloader之前已经完成了pll3时钟的初始化所以不再需要配置,比较xloader已经需要初始化某些模块了,所以这个就没必要重复了。其他的pll4,mpu,iva等时钟需要再次初始化修改。
到这里为止就,整个核心的uboot时钟的初始化就完成了,在完成s_init后,mov pc lr返回s_init调用后再返回start.s中后就是板级的启动了board_init_f.
/* the mask ROM code should have PLL and others stable */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
blcpu_init_crit
#endif
/* Set stackpointer in internal RAM to call board_init_f */
call_board_init_f:
ldrsp, =(CONFIG_SYS_INIT_SP_ADDR)
ldrr0,=0x00000000
blboard_init_f
板子的初始化启动后进入main_loop等待输入,过延时后加载内核。
直至整个DM3730的dvsdk之uboot 的启动过程就为简单分析到这里,开源的世界,庞大而且复杂,希望自己可以更努力,遇到问题努力解决,加油吧!!!