linux clk模型

本模块是对平台(CPU 内部集成)的所有时钟源进行建模,提供对时钟源(clk)的操作接口。

Clocking rate (Crystal/DPLL/ARM core): 26.0/332/500 MHz 

clk,clocksource, 锁相环(phase lock loops: pll.),多个时钟源,晶振,脉冲频率,时钟频率,晶振漂移,
clk表示CPU内部的一个频率(输入或输出)
clocksource,表示一个硬时钟。(RTC,PIT, TSC?)
CPU内部会有DPLLs,这些DPLLS用于产生许多不现的频率,供外设,总线等使用。
对DPLLS的控制都是通过寄存器,其中有XX_CLKSEL_xx,这个寄存器控制这个DPLLS输出的进行分频的除数。DIV值。:dpll m*n/p
在omap34xxCPU中,基本所有的频率都是由sys_clk,经dplls锁相后,向外输出得到的。
而sys_clk其实就是 内部自己产生的输入:osc_sys_clk 或者是 外部晶振输入。



1: //节电,给多个设备重用时钟。
一般情况下,固件启动的过程会输出一个描述板子上所有存在设备的表。如果没有这个表,系统启动代码建立正确的设备的唯一方法就是为一个特定的板子编译一个kernel。这种board-specific kernel广泛用在嵌入式和一般系统的开发上。
//即,通用的启动过程时,由固件引导初始化硬件,并把一个描述板子上所有设备的表传给系统。然后系统就可以进行对应的加载,实现PC机的通用化,启动。
//但对于嵌入式来说,一般是根据特定的板,生成特定的内核。

在大部分情况下,设备的memory和IRQ资源不足够让驱动正常工作。board setup co de会用device的platform_da ta域来为设备提供一些额外的资源。/*嵌入式系统上的设备会频繁地使用一个或者多个时钟,这些时钟因为节电的原因只有在真正使用的时候才会被打开,系统在启动过程中会为设备分配时钟*/,可以通过clk_get(&pdev->dev, clock_name)来获得需要的时钟。


/* 
 clk初始化,采用一个链表把所有到时钟结构体链接在一起,该链表的头是clocks。 
 davinci_clk_init()例程由io.c中的davinci_map_common_io()例程调用,后者被
 davinci_map_io()例程调用,而其又被注册到board_evm.c中的机器描述符中(struct machine_desc),
 故具体调用过程是:
 start_kernel()-->setup_arch()-->paging_init()-->  mdesc->map_io()(其就是davinci_map_io())-->davinci_map_common_io()-->davinci_clk_init()。
*/
2:
arch/arm/plat_xx: 特定平台的通用共用的代码。 平台
arch/arm/mach_xx: 完全针对单个板的底层代码。 单个板

3:
从一个时钟产生器(CLOCK generator)中产生的时钟分两类:
interfaces clocks: 供给接口电路(总线?) xx_ICLK
functional clocks: 供给功能模块。 xx_FCLK

clk结构是根据平台自定的结构。
clk模型是平台特定的,
/arch/arm/xx平台/clock.h: 提供clk结构,及操作接口,宏定义。
/arch/arm/xx平台/clock.c: 提供操作于该平台的时钟的操作。


omap3430:
static LIST_HEAD(clocks);//全局的所有clocks的链表。
static struct clk_functions *arch_clock;//系统的 平台结构相关时钟操作结构.
static LIST_HEAD(clk_notifier_list);//全局变量:所有的时钟通知结构 链表, 一个时钟可能对应一串的通知结构.notifier_head.
//时钟回调结构, 用于监控某个CLK,当CLK变化时则会通知
struct clk_notifier {
struct clk *clk;
struct blocking_notifier_head notifier_head;
struct list_head node;
};

__setup("mpurate=", omap_clk_setup);//?????????????????

/* Propagate rate to children */
void propagate_rate(struct clk *tclk, u8 rate_storage)


//******************clk 实现 *************************
linux 中clk 模型,由于clk是与具体平台相关的部分。所以这部分是平台自定。
而要omap中,它把这个clk模型,拆分成两部分:公共代码部分,平台板相关部分。
(arch/arm/plat_xx: 为系列平台相关的公共代码实现,会调用私有部分的接口实现)
(arch/arm/mach_xx: 为特定单个平台板相关的完成私有代码实现,会调用公共部分的接口实现)
最终向内核关联,初始化的是由 单个平台板上的代码来做。arch_initcall

//*******************omap 上的实现*********************************
omap34xx上有许多的clk,有输出输入的,
32K_CLK, SYS_CLK, L3_ICLK, L4_ICLK, DSS_TV_CLK, 48M_CLK, 12, 96, ....
通过DPLL输出了好几个供外设使用的频率。所以需要有模块来控制这些DPLL的输出,使能等等。


1:公共模块代码部分。是这一系列平台共通的代码。
主要结构: clk, clk_functions, clk_notifier
结构: clockdomain,
全局的平台结构相关的clk操作函数集: arch_clk
全局的clk结构链表:  clocks
全局的clk_notifier结构链表: clk_notifier_list

模块中提供操作函数: 注册,注销(clk, clk_notifier),等等。
注册: 初始化相关结构, 添加到全局链表中, 调用arch_clk对应的操作函数(平台结构相关的操作.)
基本上本模块中导出的函数接口,都会调用到arch_clk中某个函数.以实现平台相关的clk相关操作。

arch_clk的初始化接口:
int __init clk_init(struct clk_functions * custom_clocks)
{
if (!custom_clocks) {
printk(KERN_ERR "No custom clock functions registered\n");
BUG();
}

arch_clock = custom_clocks;

return 0;
}
2:具体平台板代码实现。
clk_init(&omap2_clk_functions);
static struct clk_functions omap2_clk_functions = {
.clk_register = omap2_clk_register,
.clk_enable = omap2_clk_enable,
.clk_disable = omap2_clk_disable,
.clk_round_rate = omap2_clk_round_rate,
.clk_round_rate_parent = omap2_clk_round_rate_parent,
.clk_set_rate = omap2_clk_set_rate,
.clk_set_parent = omap2_clk_set_parent,
.clk_get_parent = omap2_clk_get_parent,
.clk_disable_unused = omap2_clk_disable_unused,
#ifdef CONFIG_CPU_FREQ
.clk_init_cpufreq_table = omap2_clk_init_cpufreq_table,
#endif
};

初始化clk模型:
static struct clk *on chip_34xx_clks[] __initdata = {
。。。。
}
int __init omap2_clk_init(void)
{
...
clk_init(&omap2_clk_functions);//注册板相关的clk操作集。
clk_register(*clkp);//注册所有的板上时钟。
recalculate_root_clocks(); //重新计算所有的已注册 的时钟的 频率
/*
* On ly enable those clocks we will need, let the drivers
* enable other clocks as necessary
*/
clk_enable_init_clocks(); //打开我们现在要使用的时钟。即那些有(ALWAYS_ENABLED)父时钟的时钟。
==> if (clkp->flags & ENABLE_ON_INIT)  clk_enable(clkp);
}

//**************DPLL 数字锁相环, 分频?*************************************
struct clksel_rate {
u32 val;//the respected register's value.这个分频方式下,寄存器的值
u8 div;//the divisor  parent.rate/div。 分频方式 , 除数。
u8 flags;
};
设置频率:(由父频率分频得到/ 通过设置寄存器的值,可以使输出的频率改变, parent.rate / div)
int omap2_clksel_set_rate(struct clk *clk, unsigned long rate)//通过写对应的寄存器来控制这个CLK的频率。
validrate = omap2_clksel_round_rate_div(clk, rate, &new_div);//==>test_rate = clk->parent->rate / clkr->div;
if (validrate != rate)
      return -EINVAL;
clk->rate = clk->parent->rate / new_div;//这里表明了父时钟与子时钟间的频率关系。
_omap2xxx_clk_commit(clk);//最后 会调用这个,有点像linux的barrier,即使刚才的频率调整生效。



//************************************* arm 的频率 源 定义********************************************************/
arm core:
static const struct clksel osc_sys_clksel[] = {
{ .parent = &virt_12m_ck,   .rates = osc_sys_12m_rates },
{ .parent = &virt_13m_ck,   .rates = osc_sys_13m_rates },
{ .parent = &virt_16_8m_ck, .rates = osc_sys_16_8m_rates },
{ .parent = &virt_19_2m_ck, .rates = osc_sys_19_2m_rates },
{ .parent = &virt_26m_ck,   .rates = osc_sys_26m_rates },
{ .parent = &virt_38_4m_ck, .rates = osc_sys_38_4m_rates },
{ .parent = NULL },
};

/* Oscillator clock */
/* 12, 13, 16.8, 19.2, 26, or 38.4 MHz */
static struct clk osc_sys_ck = {
.name = "osc_sys_ck",
.prcm_mod = OMAP3430_CCR_MOD | CLK_REG_IN_PRM,
.init = &omap2_init_clksel_parent,
.clksel_reg = OMAP3_PRM_CLKSEL_OFFSET,
.clksel_mask = OMAP3430_SYS_CLKIN_SEL_MASK,
.clksel = osc_sys_clksel,
/* REVISIT: deal with autoextclkmode? */
.flags = CLOCK_IN_OMAP343X | ALWAYS_ENABLED,
.clkdm = { .name = "prm_clkdm" },
.recalc = &omap2_clksel_recalc,
};
/* Latency: this clock is on ly enabled after PRM_CLKSETUP.SETUP_TIME */
/* Feeds DPLLs - divided first by PRM_CLKSRC_CTRL.SYSCLKDIV? */
static struct clk sys_ck = {
.name = "sys_ck",
.parent = &osc_sys_ck,
.prcm_mod = OMAP3430_GR_MOD | CLK_REG_IN_PRM,
.init = &omap2_init_clksel_parent,
.clksel_reg = OMAP3_PRM_CLKSRC_CTRL_OFFSET,
.clksel_mask = OMAP_SYSCLKDIV_MASK,
.clksel = sys_clksel,
.flags = CLOCK_IN_OMAP343X | ALWAYS_ENABLED,
.clkdm = { .name = "prm_clkdm" },
.recalc = &omap2_clksel_recalc,
};
/* DPLL1 */
/* MPU clock source */
/* Type: DPLL */
static struct dpll_da ta dpll1_dd = {
.mult_div1_reg = OMAP3430_CM_CLKSEL1_PLL,
.mult_mask = OMAP3430_MPU_DPLL_MULT_MASK,
.div1_mask = OMAP3430_MPU_DPLL_DIV_MASK,
.freqsel_mask = OMAP3430_MPU_DPLL_FREQSEL_MASK,
.control_reg = OMAP3430_CM_CLKEN_PLL,
.enable_mask = OMAP3430_EN_MPU_DPLL_MASK,
.modes = (1 << DPLL_LOW_POWER_BYPASS) | (1 << DPLL_LOCKED),
.auto_recal_bit = OMAP3430_EN_MPU_DPLL_DRIFTGUARD_SHIFT,
.recal_en_bit = OMAP3430_MPU_DPLL_RECAL_EN_SHIFT,
.recal_st_bit = OMAP3430_MPU_DPLL_ST_SHIFT,
.autoidle_reg = OMAP3430_CM_AUTOIDLE_PLL,
.autoidle_mask = OMAP3430_AUTO_MPU_DPLL_MASK,
.idlest_reg = OMAP3430_CM_IDLEST_PLL,
.idlest_mask = OMAP3430_ST_MPU_CLK_MASK,
.bypass_clk = &dpll1_fck,
.max_multiplier = OMAP3_MAX_DPLL_MULT,
.min_divider = 1,
.max_divider = OMAP3_MAX_DPLL_DIV,
.rate_tolerance = DEFAULT_DPLL_RATE_TOLERANCE
};
static struct clk dpll1_ck = {
.name = "dpll1_ck",
.parent = &sys_ck,
.prcm_mod = MPU_MOD,
.dpll_da ta = &dpll1_dd,
.flags = CLOCK_IN_OMAP343X | ALWAYS_ENABLED | RECALC_ON_ENABLE,
.round_rate = &omap2_dpll_round_rate,
.set_rate = &omap3_noncore_dpll_set_rate,
.clkdm = { .name = "dpll1_clkdm" },
.recalc = &omap3_dpll_recalc,
};
/*
 * This virtual clock provides the CLKOUTX2 output from the DPLL if the
 * DPLL isn't bypassed.
 */
static struct clk dpll1_x2_ck = {
.name = "dpll1_x2_ck",
.parent = &dpll1_ck,
.flags = CLOCK_IN_OMAP343X | PARENT_CONTROLS_CLOCK,
.clkdm = { .name = "dpll1_clkdm" },
.recalc = &omap3_clkoutx2_recalc,
};

/*
 * Does not exist in the TRM - needed to separate the M2 divider from
 * bypass selection in mpu_ck
 */
static struct clk dpll1_x2m2_ck = {
.name = "dpll1_x2m2_ck",
.parent = &dpll1_x2_ck,
.prcm_mod = MPU_MOD,
.init = &omap2_init_clksel_parent,
.clksel_reg = OMAP3430_CM_CLKSEL2_PLL,
.clksel_mask = OMAP3430_MPU_DPLL_CLKOUT_DIV_MASK,
.clksel = div16_dpll1_x2m2_clksel,
.flags = CLOCK_IN_OMAP343X | PARENT_CONTROLS_CLOCK,
.clkdm = { .name = "dpll1_clkdm" },
.recalc = &omap2_clksel_recalc,
};

static struct clk mpu_ck = {
.name = "mpu_ck",
.parent = &dpll1_x2m2_ck,
.flags = CLOCK_IN_OMAP343X | PARENT_CONTROLS_CLOCK,
.clkdm = { .name = "mpu_clkdm" },
.recalc = &followparent_recalc,
};
/* arm_fck is divided by two when DPLL1 locked; otherwise, passthrough mpu_ck */
static const struct clksel_rate arm_fck_rates[] = {
{ .div = 1, .val = 0, .flags = RATE_IN_343X | DEFAULT_RATE },
{ .div = 2, .val = 1, .flags = RATE_IN_343X },
{ .div = 0 },
};

static const struct clksel arm_fck_clksel[] = {
{ .parent = &mpu_ck, .rates = arm_fck_rates },
{ .parent = NULL }
};
static struct clk arm_fck = {
.name = "arm_fck",
.parent = &mpu_ck,
.prcm_mod = MPU_MOD,
.init = &omap2_init_clksel_parent,
.clksel_reg = OMAP3430_CM_IDLEST_PLL,
.clksel_mask = OMAP3430_ST_MPU_CLK_MASK,
.clksel = arm_fck_clksel,
.flags = CLOCK_IN_OMAP343X | PARENT_CONTROLS_CLOCK,
.clkdm = { .name = "mpu_clkdm" },
.recalc = &omap2_clksel_recalc,
};


//*********************************************************************************************/

struct clk_notifier_da ta {
struct clk *clk;
unsigned long old_rate;
unsigned long new_rate;
};
//时钟结构。
struct clk {
struct list_head node;
const char *name;
int id;
struct clk *parent;
unsigned long rate;
unsigned long temp_rate;
struct list_head children;
__u32 flags;
u32 enable_reg;
void (*recalc)(struct clk *, unsigned long, u8);
int (*set_rate)(struct clk *, unsigned long);
long (*round_rate)(struct clk *, unsigned long);
void (*init)(struct clk *);
int (*enable)(struct clk *);
void (*disable)(struct clk *);
u16 notifier_count;
__u8 enable_bit;
__s8 usecount;
u8 idlest_bit;
#if defined(CONFIG_ARCH_OMAP2) || defined(CONFIG_ARCH_OMAP3)
u8 fixed_div;
u32 clksel_mask;
const struct clksel *clksel;
struct dpll_da ta *dpll_da ta;
union {
const char *name;
struct clockdomain *ptr;
} clkdm;
u16 clksel_reg;
s16 prcm_mod;
#else
__u8 rate_offset;
__u8 src_offset;
#endif
#if defined(CONFIG_PM_DEBUG) && defined(CONFIG_DEBUG_FS)
struct dentry *dent; /* For visible tree hierarchy */
#endif
};

struct clk_functions {
int (*clk_register)(struct clk *clk);
int (*clk_enable)(struct clk *clk);
void (*clk_disable)(struct clk *clk);
long (*clk_round_rate)(struct clk *clk, unsigned long rate);
long (*clk_round_rate_parent)(struct clk *clk,
struct clk *parent);
int (*clk_set_rate)(struct clk *clk, unsigned long rate);
int (*clk_set_parent)(struct clk *clk, struct clk *parent);
struct clk * (*clk_get_parent)(struct clk *clk);
void (*clk_allow_idle)(struct clk *clk);
void (*clk_deny_idle)(struct clk *clk);
void (*clk_disable_unused)(struct clk *clk);
#ifdef CONFIG_CPU_FREQ
void (*clk_init_cpufreq_table)(struct cpufreq_frequency_table **);
#endif
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值