By: fulinux
E-mail: fulinux@sina.com
Blog: https://blog.csdn.net/fulinus
喜欢的盆友欢迎点赞和订阅!
你的喜欢就是我写作的动力!
目录
- 系统框架图
- FLCK,HCLK,PCLK
- 时钟源的选择
- 时钟体系流程
- 流程分析
- 驱动中的clk
系统框架图
2440是一个SOC(system on chip)系统,不仅有很多CPU,还有很多外设,在2440芯片手册有系统框架图如下:
2440框架图中,不仅有CPU,而且有很多外设,其中外设分为两部分,一部分是AHB总线,一部分是APB总线。
FLCK,HCLK,PCLK
因此引入了三个时钟,FLCK,HCLK,PCLK
CPU工作与FCLK,最大工作频率400MHZ
AHB总线工作于HCLK,最大工作频率136MHZ
APB总线工作与PCLK,最大工作频率68MHZ
晶振OSC = 12M,如何得到FCLK、HCLK、PCLK呢?
时钟源的选择
从图中看出时钟源有两个分别是外部晶振和引脚,通过OM[3:2]来选择,模式选择如下图
在2440的原理图上,可以看到OM3,OM2都接到了GND上,因此会选择使用外部晶振作为输入 :
时钟体系流程
12MHZ晶振通过MPLL锁相环得到FLCK,CPU直接使用FCLK,FCLK通过PDIV分频得到PCLK提供给APB总线上的设备使用,FCLK通过HDIV分频得到HCLK提供给AHB总线上使用,12MHZ晶振也会经过UPLL锁相环提供给USB使用。
注:一旦设置PLL,就会锁定LOCK TIME 直到PLL输出稳定,然后CPU开始工作于新的频率FCLK
流程分析
一上电,复位引脚会维持一段时间(等待电源稳定),通过复位芯片来维持,如下:
- 根据OM[3:2]的值FCLK等于晶振的12M
- PLL锁存OM[3:2]的值,同时CPU开始运行(复位引脚被拉高)
- 设置PLL
- 在CPU停止运行一段时间设置PLL
- 设置完成后FLCK等于PLL输出的新的频率
- 寄存器的设置(目的FCLK=400MZH, HCLK=100MZH, PCLK=50MHZ)
- 设置LOCKTIME寄存器,决定MPLL和UPLL的LOCKTIME
设置为默认值即可 - 设置MPLLCON/UPLLCON寄存器,决定FCLKi,如下图
其中公式如下图:
参考设置值如下,我们选择MDIV=92, PDIV = 1, SDIV=1即可设置
m = (92 + 8) = 100
p = (1 + 2) =3
s = 1
Mpll = (2 * 100 * 12000000) / (3 * 2^1) = 400MHz
FCLK = Mpll = 400MHZ:
MPLLCON = (92 << 12) | (1 << 4) | (1 << 0);
设置CLKCON寄存器(关掉用不到的设备)
设置CLKDIVN寄存器,决定HCLK,PCLK,如下图:
首先要设置bit[2:1]为10,这时HCLK=FCLK/4=100MHZ,但是需要CAMDIVN[9]=0(默认值就是0)
设置bit[0]为1,这时PCLK=HCLK/2=50MHZ
bit[3]取默认值
如果HDIVN不等于0,CPU必须设置为异步模式,CPU会工作于HCLK,如下图
对于此处的汇编命令 orr r0,r0,#R1_nF:OR:R1_iA ,查阅相关资料大概了解了一下:【它扯到了协处理器P15的C1寄存器】可以参考以下地址了解协处理器P15(https://www.cnblogs.com/lifexy/p/7203786.html)
原来iA位和nF位是控制CPU总线模式的:
orr r0,r0,#R1_nF:OR:R1_iA
这命令的意思肯定是让CPU的总线模式从“fast bus mode”变为“asynchronous bus mode”
怎么理解#R1_nF:OR:R1_iA这东西,刚开始以为这是arm指令的某个命令,网上找到有人把它理解成条件运算符(exp1?exp2:exp3;),其实它就是对r0寄存器的30,31位置“1”的一条伪代码。所以我们在bootloader里会看到这样的代码:
mrc p15,0,r0,c1,c0,0
orr r0,r0,#0xc0000000;R1_nF:OR:R1_iA
mcr p15,0,r0,c1,c0,0
所以 #R1_nF:OR:R1_iA 就是 #0xc0000000的意思
提高系统时钟代码如下(FCLK=400MHZ,HCLK=100MHZ,PCLK=50MHZ):
.text
.global _start
_start:
/* 1.关闭看门狗 */
ldr r0, =0x53000000
ldr r1, =0
str r1, [r0]
/* 2.设置时钟 */
/* 2.1 设置LOCKTIME(0x4C000000)=0xFFFFFFFF */
ldr r0, =0x4C000000
ldr r1, =0xFFFFFFFF
str r1, [r0]
/* 2.2 设置CLKDIVN(0x4C000014) = 0x5 FCLK : HCLK : PCLK = 400m : 100m : 50m*/
ldr r0, =0x4C000014
ldr r1, =0x5
str r1, [r0]
/* 2.3 设置CPU处于异步模式 */
mrc p15,0,r0,c1,c0,0
orr r0,r0,#0xc0000000 /* #R1_nF:OR:R1_iA */
mcr p15,0,r0,c1,c0,0
/* 2.4 设置MPLLCON(0x4C000004)=(92<<12) | (1 << 4) | (1 << 0)
* m = MDIV + 8 = 100
* p = PDIV + 2 = 3
* s = SDIV = 1
* Mpll = (2 * m * Fin) / (p * 2 ^ s)= (2 * 100 * 12) / (3 * 2 ^ 1) = 400MHZ
*/
ldr r0, =0x4C000004
ldr r1, =(92<<12) | (1 << 4) | (1 << 0)
str r1, [r0]
/* 一旦设置PLL, 就会锁定lock time直到PLL输出稳定
* 然后CPU工作于新的频率FCLK
*/
/* 3.设置栈
* 自动分辨NOR启动或者NAND启动
* 向0地址写入0,在读出来,如果写入则是NAND,否则是NOR
*/
ldr r0, =0
ldr r1, [r0] /* 读出原来的值备份 */
str r0, [r0] /* 向0地址写入0 */
ldr r2, [r0] /* 再次读出来 */
cmp r1, r2
ldr sp, =0x40000000 + 4096 /* nor启动 */
moveq sp, #4096 /* nand启动 */
streq r1, [r0] /* 恢复原来的值 */
bl main
halt:
b halt
驱动中的clk
drivers/clk/samsung/clk-s3c2410.c
static void __init mini2440_init_time(void)
{
s3c2440_init_clocks(12000000);
+----s3c2410_common_clk_init(NULL, 12000000, 1, S3C24XX_VA_CLKPWR);
void __init s3c2410_common_clk_init(struct device_node *np, unsigned long xti_f, //0
int current_soc, //S3C2440
void __iomem *base) //0
{
struct samsung_clk_provider *samsung_clk_init(np, reg_base, NR_CLKS); // NR_CLKS=41
struct samsung_clk_provider * ctx = kzalloc(sizeof(struct samsung_clk_provider), GFP_KERNEL);
struct clk **clk_table = kcalloc(nr_clks, sizeof(struct clk *), GFP_KERNEL);
ctx->reg_base = base;
ctx->clk_data.clks = clk_table;
ctx->clk_data.clk_num = nr_clks;
spin_lock_init(&ctx->lock);
/*
* plls follow different calculation schemes, with the
* upll following the same scheme as the s3c2410 plls
*/
s3c244x_common_plls[mpll].rate_table = pll_s3c244x_12mhz_tbl;
s3c244x_common_plls[upll].rate_table = pll_s3c2410_12mhz_tbl;
//看下pll_s3c244x_12mhz_tbl
/* S3C244x specific clocks */
static struct samsung_pll_rate_table pll_s3c244x_12mhz_tbl[] __initdata = {
/* sorted in descending order */
PLL_35XX_RATE(400000000, 0x5c, 1, 1), //MDIV = 0x5c = 92, PDIV = 1, SDIV=1
//...
PLL_35XX_RATE(75000000, 0x75, 3, 3),
{ /* sentinel */ },
};
/* Register PLLs. */
samsung_clk_register_pll(ctx, s3c244x_common_plls, ARRAY_SIZE(s3c244x_common_plls), reg_base);
}
下面看下samsung_clk_register_pll函数:
drivers/clk/samsung/clk-pll.c
void __init samsung_clk_register_pll(struct samsung_clk_provider *ctx,
const struct samsung_pll_clock *pll_list,
unsigned int nr_pll, void __iomem *base)
{
int cnt;
for (cnt = 0; cnt < nr_pll; cnt++)
_samsung_clk_register_pll(ctx, &pll_list[cnt], base);
}
static void __init _samsung_clk_register_pll(struct samsung_clk_provider *ctx,
const struct samsung_pll_clock *pll_clk,
void __iomem *base)
{
struct samsung_clk_pll *pll = kzalloc(sizeof(*pll), GFP_KERNEL);
struct clk *clk;
struct clk_init_data init;
init.name = pll_clk->name;
init.flags = pll_clk->flags;
init.parent_names = &pll_clk->parent_name;
init.num_parents = 1;
if (pll_clk->rate_table) {
/* find count of rates in rate_table */
for (len = 0; pll_clk->rate_table[len].rate != 0; )
len++;
pll->rate_count = len;
pll->rate_table = kmemdup(pll_clk->rate_table,
pll->rate_count *
sizeof(struct samsung_pll_rate_table),
GFP_KERNEL);
}
}
init.ops = &samsung_s3c2440_mpll_clk_ops; //下面
pll->hw.init = &init;
pll->type = pll_clk->type;
pll->lock_reg = base + pll_clk->lock_offset;
pll->con_reg = base + pll_clk->con_offset;
static struct samsung_pll_clock s3c244x_common_plls[] __initdata = {
[mpll] = PLL(pll_s3c2440_mpll, MPLL, "mpll", "xti",
LOCKTIME, MPLLCON, NULL),
[upll] = PLL(pll_s3c2410_upll, UPLL, "upll", "xti",
LOCKTIME, UPLLCON, NULL),
};
#define __PLL(_typ, _id, _dname, _name, _pname, _flags, _lock, _con, \
_rtable, _alias) \
{ \
.id = _id, \
.type = _typ, \
.dev_name = _dname, \
.name = _name, \
.parent_name = _pname, \
.flags = CLK_GET_RATE_NOCACHE, \
.con_offset = _con, \
.lock_offset = _lock, \
.rate_table = _rtable, \
.alias = _alias, \
}
#define PLL(_typ, _id, _name, _pname, _lock, _con, _rtable) \
__PLL(_typ, _id, NULL, _name, _pname, CLK_GET_RATE_NOCACHE, \
_lock, _con, _rtable, _name)
init.ops = &samsung_s3c2440_mpll_clk_ops;
static const struct clk_ops samsung_s3c2440_mpll_clk_ops = {
.recalc_rate = samsung_s3c2440_mpll_recalc_rate,
.enable = samsung_s3c2410_mpll_enable,
.disable = samsung_s3c2410_mpll_disable,
.round_rate = samsung_pll_round_rate,
.set_rate = samsung_s3c2410_pll_set_rate,
};
static unsigned long samsung_s3c2440_mpll_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct samsung_clk_pll *pll = to_clk_pll(hw);
u32 pll_con, mdiv, pdiv, sdiv;
u64 fvco = parent_rate;
pll_con = readl_relaxed(pll->con_reg);
mdiv = (pll_con >> PLLS3C2410_MDIV_SHIFT) & PLLS3C2410_MDIV_MASK;
pdiv = (pll_con >> PLLS3C2410_PDIV_SHIFT) & PLLS3C2410_PDIV_MASK;
sdiv = (pll_con >> PLLS3C2410_SDIV_SHIFT) & PLLS3C2410_SDIV_MASK;
fvco *= (2 * (mdiv + 8));
do_div(fvco, (pdiv + 2) << sdiv);
return (unsigned int)fvco;
}
static int samsung_s3c2410_mpll_enable(struct clk_hw *hw)
{
return samsung_s3c2410_pll_enable(hw, 5, true); //CLKSLOW[5]:MPLL_OFF
}
static int samsung_s3c2410_pll_enable(struct clk_hw *hw, int bit, bool enable)
{
struct samsung_clk_pll *pll = to_clk_pll(hw);
u32 pll_en = readl_relaxed(pll->lock_reg + PLLS3C2410_ENABLE_REG_OFFSET);
u32 pll_en_orig = pll_en;
if (enable)
pll_en &= ~BIT(bit);
else
pll_en |= BIT(bit);
writel_relaxed(pll_en, pll->lock_reg + PLLS3C2410_ENABLE_REG_OFFSET); //0x10
/* if we started the UPLL, then allow to settle */
if (enable && (pll_en_orig & BIT(bit)))
udelay(300);
return 0;
}
static int samsung_s3c2410_upll_enable(struct clk_hw *hw)
{
return samsung_s3c2410_pll_enable(hw, 7, true); //CLKSLOW[7]:UCLK_ON
}
static int samsung_s3c2410_pll_set_rate(struct clk_hw *hw, unsigned long drate,
unsigned long prate)
{
struct samsung_clk_pll *pll = to_clk_pll(hw);
const struct samsung_pll_rate_table *rate;
u32 tmp;
/* Get required rate settings from table */
rate = samsung_get_pll_settings(pll, drate); //下面根据频率值得到rate结构,包含mdiv、pdiv和sdiv数据。
if (!rate) {
pr_err("%s: Invalid rate : %lu for pll clk %s\n", __func__,
drate, clk_hw_get_name(hw));
return -EINVAL;
}
tmp = readl_relaxed(pll->con_reg);
/* Change PLL PMS values */
tmp &= ~((PLLS3C2410_MDIV_MASK << PLLS3C2410_MDIV_SHIFT) |
(PLLS3C2410_PDIV_MASK << PLLS3C2410_PDIV_SHIFT) |
(PLLS3C2410_SDIV_MASK << PLLS3C2410_SDIV_SHIFT));
tmp |= (rate->mdiv << PLLS3C2410_MDIV_SHIFT) |
(rate->pdiv << PLLS3C2410_PDIV_SHIFT) |
(rate->sdiv << PLLS3C2410_SDIV_SHIFT);
writel_relaxed(tmp, pll->con_reg);
/* Time to settle according to the manual */
udelay(300);
return 0;
}
继续看下:
static void __init _samsung_clk_register_pll(struct samsung_clk_provider *ctx,
const struct samsung_pll_clock *pll_clk,
void __iomem *base)
{
clk = clk_register(NULL, &pll->hw);
samsung_clk_add_lookup(ctx, clk, pll_clk->id);
ret = clk_register_clkdev(clk, pll_clk->alias, pll_clk->dev_name);
struct samsung_gate_clock s3c2410_common_gates[] __initdata = {
GATE(PCLK_SPI, "spi", "pclk", CLKCON, 18, 0, 0),
GATE(PCLK_I2S, "i2s", "pclk", CLKCON, 17, 0, 0),
GATE(PCLK_I2C, "i2c", "pclk", CLKCON, 16, 0, 0),
GATE(PCLK_ADC, "adc", "pclk", CLKCON, 15, 0, 0),
GATE(PCLK_RTC, "rtc", "pclk", CLKCON, 14, 0, 0),
GATE(PCLK_GPIO, "gpio", "pclk", CLKCON, 13, CLK_IGNORE_UNUSED, 0),
GATE(PCLK_UART2, "uart2", "pclk", CLKCON, 12, 0, 0),
GATE(PCLK_UART1, "uart1", "pclk", CLKCON, 11, 0, 0),
GATE(PCLK_UART0, "uart0", "pclk", CLKCON, 10, 0, 0),
GATE(PCLK_SDI, "sdi", "pclk", CLKCON, 9, 0, 0),
GATE(PCLK_PWM, "pwm", "pclk", CLKCON, 8, 0, 0),
GATE(HCLK_USBD, "usb-device", "hclk", CLKCON, 7, 0, 0),
GATE(HCLK_USBH, "usb-host", "hclk", CLKCON, 6, 0, 0),
GATE(HCLK_LCD, "lcd", "hclk", CLKCON, 5, 0, 0),
GATE(HCLK_NAND, "nand", "hclk", CLKCON, 4, 0, 0),
};
samsung_clk_register_gate(ctx, s3c2410_common_gates, ARRAY_SIZE(s3c2410_common_gates));
for (idx = 0; idx < nr_clk; idx++, list++) {
clk = clk_register_gate(NULL, list->name, list->parent_name,
list->flags, ctx->reg_base + list->offset,
list->bit_idx, list->gate_flags, &ctx->lock);
/* register a clock lookup only if a clock alias is specified */
if (list->alias) {
ret = clk_register_clkdev(clk, list->alias,
list->dev_name);
}
看下clk_register_gate(drivers/clk/clk-gate.c)
struct clk *clk_register_gate(struct device *dev, const char *name,
const char *parent_name, unsigned long flags,
void __iomem *reg, u8 bit_idx,
u8 clk_gate_flags, spinlock_t *lock)
{
struct clk_hw *hw;
hw = clk_hw_register_gate(dev, name, parent_name, flags, reg,
bit_idx, clk_gate_flags, lock);
if (IS_ERR(hw))
return ERR_CAST(hw);
return hw->clk;
}
EXPORT_SYMBOL_GPL(clk_register_gate);
/**
* clk_hw_register_gate - register a gate clock with the clock framework
* @dev: device that is registering this clock
* @name: name of this clock
* @parent_name: name of this clock's parent
* @flags: framework-specific flags for this clock
* @reg: register address to control gating of this clock
* @bit_idx: which bit in the register controls gating of this clock
* @clk_gate_flags: gate-specific flags for this clock
* @lock: shared register lock for this clock
*/
struct clk_hw *clk_hw_register_gate(struct device *dev, const char *name,
const char *parent_name, unsigned long flags,
void __iomem *reg, u8 bit_idx,
u8 clk_gate_flags, spinlock_t *lock)
{
struct clk_gate *gate;
struct clk_hw *hw;
struct clk_init_data init = {};
int ret;
if (clk_gate_flags & CLK_GATE_HIWORD_MASK) {
if (bit_idx > 15) {
pr_err("gate bit exceeds LOWORD field\n");
return ERR_PTR(-EINVAL);
}
}
/* allocate the gate */
gate = kzalloc(sizeof(*gate), GFP_KERNEL);
if (!gate)
return ERR_PTR(-ENOMEM);
init.name = name;
init.ops = &clk_gate_ops; //下面看下这个
init.flags = flags | CLK_IS_BASIC;
init.parent_names = (parent_name ? &parent_name: NULL);
init.num_parents = (parent_name ? 1 : 0);
/* struct clk_gate assignments */
gate->reg = reg;
gate->bit_idx = bit_idx;
gate->flags = clk_gate_flags;
gate->lock = lock;
gate->hw.init = &init;
hw = &gate->hw;
ret = clk_hw_register(dev, hw);
if (ret) {
kfree(gate);
hw = ERR_PTR(ret);
}
return hw;
}
EXPORT_SYMBOL_GPL(clk_hw_register_gate);
const struct clk_ops clk_gate_ops = {
.enable = clk_gate_enable,
.disable = clk_gate_disable,
.is_enabled = clk_gate_is_enabled,
};
EXPORT_SYMBOL_GPL(clk_gate_ops);
static int clk_gate_enable(struct clk_hw *hw)
{
clk_gate_endisable(hw, 1);
return 0;
}
*
* For disabling clock, enable = 0
* set2dis = 1 -> set bit -> set = 1
* set2dis = 0 -> clear bit -> set = 0
*
* So, result is always: enable xor set2dis.
*/
static void clk_gate_endisable(struct clk_hw *hw, int enable)
{
struct clk_gate *gate = to_clk_gate(hw);
int set = gate->flags & CLK_GATE_SET_TO_DISABLE ? 1 : 0;
unsigned long uninitialized_var(flags);
u32 reg;
set ^= enable;
if (gate->lock)
spin_lock_irqsave(gate->lock, flags);
else
__acquire(gate->lock);
if (gate->flags & CLK_GATE_HIWORD_MASK) {
reg = BIT(gate->bit_idx + 16);
if (set)
reg |= BIT(gate->bit_idx);
} else {
reg = clk_readl(gate->reg);
if (set)
reg |= BIT(gate->bit_idx);
else
reg &= ~BIT(gate->bit_idx);
}
clk_writel(reg, gate->reg);
if (gate->lock)
spin_unlock_irqrestore(gate->lock, flags);
else
__release(gate->lock);
}
看下clk_register_clkdev(drivers/clk/clkdev.c)
/**
* clk_register_clkdev - register one clock lookup for a struct clk
* @clk: struct clk to associate with all clk_lookups
* @con_id: connection ID string on device
* @dev_id: string describing device name
*
* con_id or dev_id may be NULL as a wildcard, just as in the rest of
* clkdev.
*
* To make things easier for mass registration, we detect error clks
* from a previous clk_register() call, and return the error code for
* those. This is to permit this function to be called immediately
* after clk_register().
*/
int clk_register_clkdev(struct clk *clk, const char *con_id,
const char *dev_id)
{
struct clk_lookup *cl;
if (IS_ERR(clk))
return PTR_ERR(clk);
/*
* Since dev_id can be NULL, and NULL is handled specially, we must
* pass it as either a NULL format string, or with "%s".
*/
if (dev_id)
cl = __clk_register_clkdev(__clk_get_hw(clk), con_id, "%s", dev_id);
else
cl = __clk_register_clkdev(__clk_get_hw(clk), con_id, NULL);
return cl ? 0 : -ENOMEM;
}
EXPORT_SYMBOL(clk_register_clkdev);
static struct clk_lookup *__clk_register_clkdev(struct clk_hw *hw,
const char *con_id,
const char *dev_id, ...)
{
struct clk_lookup *cl;
va_list ap;
va_start(ap, dev_id);
cl = vclkdev_create(hw, con_id, dev_id, ap);
va_end(ap);
return cl;
}
static struct clk_lookup *
vclkdev_create(struct clk_hw *hw, const char *con_id, const char *dev_fmt,
va_list ap)
{
struct clk_lookup *cl;
cl = vclkdev_alloc(hw, con_id, dev_fmt, ap);
if (cl)
__clkdev_add(cl);
return cl;
}
static void __clkdev_add(struct clk_lookup *cl)
{
mutex_lock(&clocks_mutex);
list_add_tail(&cl->node, &clocks);
mutex_unlock(&clocks_mutex);
}
static LIST_HEAD(clocks); //clocks链表
所有的struct clk_lookup
都会被添加到clocks
链表中。
使用的时候:
info->clk = devm_clk_get(&pdev->dev, "nand");
static void devm_clk_release(struct device *dev, void *res)
{
clk_put(*(struct clk **)res);
}
struct clk *devm_clk_get(struct device *dev, const char *id)
{
struct clk **ptr, *clk;
ptr = devres_alloc(devm_clk_release, sizeof(*ptr), GFP_KERNEL);
if (!ptr)
return ERR_PTR(-ENOMEM);
clk = clk_get(dev, id);
if (!IS_ERR(clk)) {
*ptr = clk;
devres_add(dev, ptr);
} else {
devres_free(ptr);
}
return clk;
}
//drivers/clk/clkdev.c
struct clk *clk_get(struct device *dev, const char *con_id)
{
const char *dev_id = dev ? dev_name(dev) : NULL;
struct clk *clk;
if (dev) {
clk = __of_clk_get_by_name(dev->of_node, dev_id, con_id);
if (!IS_ERR(clk) || PTR_ERR(clk) == -EPROBE_DEFER)
return clk;
}
return clk_get_sys(dev_id, con_id);
}
EXPORT_SYMBOL(clk_get);
/*
* Find the correct struct clk for the device and connection ID.
* We do slightly fuzzy matching here:
* An entry with a NULL ID is assumed to be a wildcard.
* If an entry has a device ID, it must match
* If an entry has a connection ID, it must match
* Then we take the most specific entry - with the following
* order of precedence: dev+con > dev only > con only.
*/
static struct clk_lookup *clk_find(const char *dev_id, const char *con_id)
{
struct clk_lookup *p, *cl = NULL;
int match, best_found = 0, best_possible = 0;
if (dev_id)
best_possible += 2;
if (con_id)
best_possible += 1;
list_for_each_entry(p, &clocks, node) { //这里clocks
match = 0;
if (p->dev_id) {
if (!dev_id || strcmp(p->dev_id, dev_id))
continue;
match += 2;
}
if (p->con_id) {
if (!con_id || strcmp(p->con_id, con_id))
continue;
match += 1;
}
if (match > best_found) {
cl = p;
if (match != best_possible)
best_found = match;
else
break;
}
}
return cl;
}
这里的devm_clk_get和devm_clk_release实现机制很有意思。
XTIpll是clk最初的源,相当于OSC:
FRATE(XTI, "xti", NULL, 0, 0)
XTIpll经过OM[3:2]后分两路,一路MPLL器件,另一路是UPLL,因此由如下定义:
[mpll] = PLL(pll_s3c2440_mpll, MPLL, "mpll", "xti", LOCKTIME, MPLLCON, NULL),
[upll] = PLL(pll_s3c2410_upll, UPLL, "upll", "xti", LOCKTIME, UPLLCON, NULL),
因为我们的MPLL是我们关注的点,所以下面继续沿着MPLL分析,MPLL经过分频器后可以得到FCLK。
FCLK有两个时钟源,分别是MPLL和DIV_SLOW,可以通过时钟选择器选择时钟:
//PNAME(fclk_p) = { "mpll", "div_slow" };
MUX(FCLK, "fclk", fclk_p, CLKSLOW, 4, 1),
FCLK通过分频器后分别得到HCLK、PCLK:
//PNAME(hclk_p) = { "fclk", "div_hclk_2", "div_hclk_4", "div_hclk_3" }; //0, 1, 2, 3
MUX(HCLK, "hclk", hclk_p, CLKDIVN, 1, 2) //id, dev_name, cname, parent_name offset, shift, width
其中div_hclk_4是通过CLKDIVN分频后得到的,也就是HCLK=FCLK / 4:
问题:那么在哪里设置了CLKDIVN[2:1] = 2呢?
samsung_clk_register_mux(ctx, s3c244x_common_muxes, ARRAY_SIZE(s3c244x_common_muxes));
struct clk *clk = clk_register_mux(NULL, list->name, list->parent_names,
list->num_parents, list->flags,
ctx->reg_base + list->offset,
list->shift, list->width, list->mux_flags, &ctx->lock);
return clk_register_mux_table(dev, name, parent_names, num_parents,
flags, reg, shift, mask, clk_mux_flags, NULL, lock);
hw = clk_hw_register_mux_table(dev, name, parent_names, num_parents,
flags, reg, shift, mask, clk_mux_flags, table, lock);
//init.ops = &clk_mux_ops;
const struct clk_ops clk_mux_ops = {
.get_parent = clk_mux_get_parent,
.set_parent = clk_mux_set_parent,
.determine_rate = __clk_mux_determine_rate,
};
//static int clk_mux_set_parent(struct clk_hw *hw, u8 index) 这里的index表示的是hclk_p中的索引,即如果是2的话就是div_hclk_4,而且2就是10b
val |= index << mux->shift;
clk_writel(val, mux->reg);
看下div_hclk_4的时钟设置,时钟源是fclk,还有一点看上图HCLK=FCLK / 4的条件是CAMDIVN[9]位要为0:
static struct clk_div_table div_hclk_4_d[] = {
{ .val = 0, .div = 4 },
{ .val = 1, .div = 8 },
{ /* sentinel */ },
};
DIV_T(0, "div_hclk_4", "fclk", CAMDIVN, 9, 1, div_hclk_4_d) // id, name, parent_name, offset, shift, width, table
这里是怎么实现当HCLK=FCLK / 4的时候设置CAMDIVN[9] = 0呢?看下面逻辑:
samsung_clk_register_div(ctx, s3c244x_common_dividers, ARRAY_SIZE(s3c244x_common_dividers));
clk = clk_register_divider_table(NULL, list->name,
list->parent_name, list->flags, //flags = 0
ctx->reg_base + list->offset, //offset = CAMDIVN
list->shift, list->width, list->div_flags, //div_flags = 0
list->table, &ctx->lock);
struct clk_hw *hw = _register_divider(dev, name, parent_name, flags, reg, shift, width, clk_divider_flags, table, lock);
init.ops = &clk_divider_ops;
const struct clk_ops clk_divider_ops = {
.recalc_rate = clk_divider_recalc_rate,
.round_rate = clk_divider_round_rate,
.set_rate = clk_divider_set_rate,
};
//clk_divider_set_rate
value = divider_get_val(rate, parent_rate, divider->table, divider->width, divider->flags);
div = DIV_ROUND_UP_ULL((u64)parent_rate, rate); //parent_rate = 400M rate = 100M, div = 4
_get_table_val(table, div);
if (clkt->div == div) //4 = 4
return clkt->val; //val = 0
所以上面的{ .val = 0, .div = 4 }
就是这个意义。
我们继续看下nand的时钟设置:
struct samsung_gate_clock s3c2410_common_gates[] __initdata = {
//...
GATE(HCLK_NAND, "nand", "hclk", CLKCON, 4, 0, 0), //id, name, parent_name, offset, bit_idx, flags, gate_flags
samsung_clk_register_gate(ctx, s3c2410_common_gates, ARRAY_SIZE(s3c2410_common_gates));
clk = clk_register_gate(NULL, list->name, list->parent_name,
list->flags, ctx->reg_base + list->offset,
list->bit_idx, list->gate_flags, &ctx->lock);
struct clk_hw *hw = clk_hw_register_gate(dev, name, parent_name, flags, reg, bit_idx, clk_gate_flags, lock);
//init.ops = &clk_gate_ops;
const struct clk_ops clk_gate_ops = {
.enable = clk_gate_enable,
.disable = clk_gate_disable,
.is_enabled = clk_gate_is_enabled,
};
//static int clk_gate_enable(struct clk_hw *hw)
clk_gate_endisable(hw, 1);
reg = clk_readl(gate->reg);
if (set)
reg |= BIT(gate->bit_idx);
else
reg &= ~BIT(gate->bit_idx);
clk_writel(reg, gate->reg);
arch/arm/mach-s3c24xx/mach-mini2440.c
static void __init mini2440_init_time(void)
{
s3c2440_init_clocks(12000000);
+----s3c2410_common_clk_init(NULL, 12000000, 1, S3C24XX_VA_CLKPWR);
+--------s3c2410_common_clk_register_fixed_ext(ctx, 12000000);
+------------clk = clk_register_fixed_rate(NULL, list->name,list->parent_name, list->flags, list->fixed_rate); //fixed_rate=12000000
samsung_timer_init();
}