老的时钟管理和新的时钟管理并无本质的差别。
这里先列出新的新的common clock framework和老的区别,后面再分析老的实现。
1.老的时钟框架没有区分各种时钟类型,新的框架把时钟分成了五个不同的时钟类型,固定频率的,分频的,开关类型的,多选一类型。
2.老的时钟框架需要厂商自己提供ops函数,新的时钟框架因为已经抽象出了5种时钟,所以这五种时钟的ops内核框架都已经抽象出来实现了。
3.老的时钟框架所有的时钟都是用同一个struct clksrc_clk(见下面)维护,所有的信息都是采用静态分配方式的clk,而新的框架分成五类时钟后,都是在注册时动态分配的clk。
4.老的时钟框架只维护了一个clock链表,而新的时钟框架因为抽象了时钟类型,采用树形结构有维护了一个链表。
5.老的时钟框架clk结构厂商自己定义,新的框架有内核定义。
下面这就是老的时钟框架对一个时钟资源的描述
/**
* struct clksrc_clk - class of clock for newer style samsung devices.
* @clk: the standard clock representation
* @sources: the sources for this clock
* @reg_src: the register definition for selecting the clock's source
* @reg_div: the register definition for the clock's output divisor
*
* This clock implements the features required by the newer SoCs where
* the standard clock block provides an input mux and a post-mux divisor
* to provide the periperhal's clock.
*
* The array of @sources provides the mapping of mux position to the
* clock, and @reg_src shows the code where to modify to change the mux
* position. The @reg_div defines how to change the divider settings on
* the output.
*/
struct clksrc_clk {
struct clk clk;
struct clksrc_sources *sources;
struct clksrc_reg reg_src;
struct clksrc_reg reg_div;
};
/**
* struct clk_ops - standard clock operations
* @set_rate: set the clock rate, see clk_set_rate().
* @get_rate: get the clock rate, see clk_get_rate().
* @round_rate: round a given clock rate, see clk_round_rate().
* @set_parent: set the clock's parent, see clk_set_parent().
*
* Group the common clock implementations together so that we
* don't have to keep setting the same fields again. We leave
* enable in struct clk.
*
* Adding an extra layer of indirection into the process should
* not be a problem as it is unlikely these operations are going
* to need to be called quickly.
*/
struct clk_ops {
int (*set_rate)(struct clk *c, unsigned long rate);
unsigned long (*get_rate)(struct clk *c);
unsigned long (*round_rate)(struct clk *c, unsigned long rate);
int (*set_parent)(struct clk *c, struct clk *parent);
};
struct clk {
struct list_head list;
struct module *owner;
struct clk *parent; /* 父时钟 */
const char *name;
const char *devname; /* 设备名 */
int id;
int usage;
unsigned long rate; /* 时钟频率 */
unsigned long ctrlbit; /* 控制位 */
struct clk_ops *ops; /* 操作接口 */
int (*enable)(struct clk *, int enable);
struct clk_lookup lookup;
#if defined(CONFIG_PM_DEBUG) && defined(CONFIG_DEBUG_FS)
struct dentry *dent; /* For visible tree hierarchy */
#endif
};
下面就对旧的时钟对时钟的注册使用。
s5pv210使用下面这个函数描述的。
/* 这个参数就是输入时钟,s5pv210的一般都是24M */
void __init s5pv210_init_clocks(int xtal)
{
printk(KERN_DEBUG "%s: initializing clocks\n", __func__);
s3c24xx_register_baseclocks(xtal);
s5p_register_clocks(xtal);
s5pv210_register_clocks();
s5pv210_setup_clocks();
}
基本时钟初始化,早期的
/* initialise all the clocks */
int __init s3c24xx_register_baseclocks(unsigned long xtal)
{
printk(KERN_INFO "S3C24XX Clocks, Copyright 2004 Simtec Electronics\n");
clk_xtal.rate = xtal; /* 初始化时钟 */
/* register our clocks */
if (s3c24xx_register_clock(&clk_xtal) < 0)
printk(KERN_ERR "failed to register master xtal\n");
if (s3c24xx_register_clock(&clk_mpll) < 0)
printk(KERN_ERR "failed to register mpll clock\n");
if (s3c24xx_register_clock(&clk_upll) < 0)
printk(KERN_ERR "failed to register upll clock\n");
if (s3c24xx_register_clock(&clk_f) < 0)
printk(KERN_ERR "failed to register cpu fclk\n");
if (s3c24xx_register_clock(&clk_h) < 0)
printk(KERN_ERR "failed to register cpu hclk\n");
if (s3c24xx_register_clock(&clk_p) < 0)
printk(KERN_ERR "failed to register cpu pclk\n");
return 0;
}
/* initialise the clock system */
/**
* s3c24xx_register_clock() - register a clock
* @clk: The clock to register
*
* Add the specified clock to the list of clocks known by the system.
*/
int s3c24xx_register_clock(struct clk *clk)
{
if (clk->enable == NULL) /* 这边对使能如果没绑定,统一绑定空函数 */
clk->enable = clk_null_enable;
/* fill up the clk_lookup structure and register it*/
clk->lookup.dev_id = clk->devname; /* 设备名 */
clk->lookup.con_id = clk->name; /* 时钟名 */
clk->lookup.clk = clk; /* lookup和clk互相挂钩 */
clkdev_add(&clk->lookup); /* 注册到clock链表上 */
return 0;
}
void clkdev_add(struct clk_lookup *cl)
{
mutex_lock(&clocks_mutex);
list_add_tail(&cl->node, &clocks); /* 最终注册到clock链表 */
mutex_unlock(&clocks_mutex);
}
/* 固定时钟源 */
//时钟在上面入口初始化的
struct clk clk_xtal = {
.name = "xtal",
.rate = 0,
.parent = NULL,
.ctrlbit = 0,
};
struct clk clk_ext = {
.name = "ext",
};
struct clk clk_epll = {
.name = "epll",
};
/* 倍频 */
struct clk clk_mpll = {
.name = "mpll",
.ops = &clk_ops_def_setrate,
};
struct clk clk_upll = {
.name = "upll",
.parent = NULL,
.ctrlbit = 0,
};
struct clk clk_f = {
.name = "fclk",
.rate = 0,
.parent = &clk_mpll,
.ctrlbit = 0,
};
struct clk clk_h = {
.name = "hclk",
.rate = 0,
.parent = NULL,
.ctrlbit = 0,
.ops = &clk_ops_def_setrate,
};
struct clk clk_p = {
.name = "pclk",
.rate = 0,
.parent = NULL,
.ctrlbit = 0,
.ops = &clk_ops_def_setrate,
};
早期的函数都是使用的默认ops
/* base clocks */
int clk_default_setrate(struct clk *clk, unsigned long rate)
{
clk->rate = rate;
return 0;
}
struct clk_ops clk_ops_def_setrate = {
.set_rate = clk_default_setrate,
};
/* 注册s5p通用的时钟 */
static struct clk *s5p_clks[] __initdata = {
&clk_ext_xtal_mux,
&clk_48m,
&s5p_clk_27m,
&clk_fout_apll,
&clk_fout_mpll,
&clk_fout_epll,
&clk_fout_dpll,
&clk_fout_vpll,
&clk_vpll,
&clk_xusbxti,
};
void __init s5p_register_clocks(unsigned long xtal_freq)
{
int ret;
clk_ext_xtal_mux.rate = xtal_freq; /* 初始化外部时钟24M */
/* 注册s5p都有的 */
ret = s3c24xx_register_clocks(s5p_clks, ARRAY_SIZE(s5p_clks));
if (ret > 0)
printk(KERN_ERR "Failed to register s5p clocks\n");
}
/* fin_apll, fin_mpll and fin_epll are all the same clock, which we call
* clk_ext_xtal_mux.
*/
struct clk clk_ext_xtal_mux = {
.name = "ext_xtal",
.id = -1,
};
struct clk clk_xusbxti = {
.name = "xusbxti",
.id = -1,
.rate = 24000000,
};
struct clk s5p_clk_27m = {
.name = "clk_27m",
.id = -1,
.rate = 27000000,
};
/* 48MHz USB Phy clock output */
struct clk clk_48m = {
.name = "clk_48m",
.id = -1,
.rate = 48000000,
};
/* APLL clock output
* No need .ctrlbit, this is always on
*/
struct clk clk_fout_apll = {
.name = "fout_apll",
.id = -1,
};
/* BPLL clock output */
struct clk clk_fout_bpll = {
.name = "fout_bpll",
.id = -1,
};
struct clk clk_fout_bpll_div2 = {
.name = "fout_bpll_div2",
.id = -1,
};
/* CPLL clock output */
struct clk clk_fout_cpll = {
.name = "fout_cpll",
.id = -1,
};
/* MPLL clock output
* No need .ctrlbit, this is always on
*/
struct clk clk_fout_mpll = {
.name = "fout_mpll",
.id = -1,
};
struct clk clk_fout_mpll_div2 = {
.name = "fout_mpll_div2",
.id = -1,
};
/* EPLL clock output */
struct clk clk_fout_epll = {
.name = "fout_epll",
.id = -1,
.ctrlbit = (1 << 31),
};
/* DPLL clock output */
struct clk clk_fout_dpll = {
.name = "fout_dpll",
.id = -1,
.ctrlbit = (1 << 31),
};
/* VPLL clock output */
struct clk clk_fout_vpll = {
.name = "fout_vpll",
.id = -1,
.ctrlbit = (1 << 31),
};
void __init s5pv210_register_clocks(void)
{
int ptr;
/* 注册固定时钟 */
s3c24xx_register_clocks(clks, ARRAY_SIZE(clks));
/* 注册系统时钟源 */
for (ptr = 0; ptr < ARRAY_SIZE(sysclks); ptr++)
s3c_register_clksrc(sysclks[ptr], 1);
/* 注册显示相关时钟源 */
for (ptr = 0; ptr < ARRAY_SIZE(sclk_tv); ptr++)
s3c_register_clksrc(sclk_tv[ptr], 1);
/* 注册设备时钟源(uart mmc spi) */
for (ptr = 0; ptr < ARRAY_SIZE(clksrc_cdev); ptr++)
s3c_register_clksrc(clksrc_cdev[ptr], 1);
/* 注册sclk下面的设备源 */
s3c_register_clksrc(clksrcs, ARRAY_SIZE(clksrcs));
/* 注册部分设备时钟 */
s3c_register_clocks(init_clocks, ARRAY_SIZE(init_clocks));
/* 注册部设备分时钟的开关 */
s3c_register_clocks(init_clocks_off, ARRAY_SIZE(init_clocks_off));
/* 默认关掉这些设备的时钟 */
s3c_disable_clocks(init_clocks_off, ARRAY_SIZE(init_clocks_off));
/* 加入到设备管理表中 */
clkdev_add_table(s5pv210_clk_lookup, ARRAY_SIZE(s5pv210_clk_lookup));
/* 注册部分设备源 */
s3c24xx_register_clocks(clk_cdev, ARRAY_SIZE(clk_cdev));
/* 关掉这些设备 */
for (ptr = 0; ptr < ARRAY_SIZE(clk_cdev); ptr++)
s3c_disable_clocks(clk_cdev[ptr], 1);
}
可以看到老的时钟子系统相当的简单,三星这边总共只用到了四个函数,下面一一分析。
1.s3c24xx_register_clocks,使用这个函数注册的都是单独的ops操作函数,或没有ops操作函数的一些集合
/**
* s3c24xx_register_clocks() - register an array of clock pointers
* @clks: Pointer to an array of struct clk pointers
* @nr_clks: The number of clocks in the @clks array.
*
* Call s3c24xx_register_clock() for all the clock pointers contained
* in the @clks list. Returns the number of failures.
*/
int s3c24xx_register_clocks(struct clk **clks, int nr_clks)
{
int fails = 0;
/* 注册多个clk */
for (; nr_clks > 0; nr_clks--, clks++) {
if (s3c24xx_register_clock(*clks) < 0) {
struct clk *clk = *clks;
printk(KERN_ERR "%s: failed to register %p: %s\n",
__func__, clk, clk->name);
fails++;
}
}
return fails;
}
第一类使用上面操作接口的就是一些固定时钟的,不能更改,也不能开关。
static struct clk *clks[] __initdata = {
&clk_sclk_hdmi27m,
&clk_sclk_hdmiphy,
&clk_sclk_usbphy0,
&clk_sclk_usbphy1,
&clk_pcmcdclk0,
&clk_pcmcdclk1,
&clk_pcmcdclk2,
};
static struct clk clk_sclk_hdmi27m = {
.name = "sclk_hdmi27m",
.rate = 27000000,
};
static struct clk clk_sclk_hdmiphy = {
.name = "sclk_hdmiphy",
};
static struct clk clk_sclk_usbphy0 = {
.name = "sclk_usbphy0",
};
static struct clk clk_sclk_usbphy1 = {
.name = "sclk_usbphy1",
};
static struct clk clk_pcmcdclk0 = {
.name = "pcmcdclk",
};
static struct clk clk_pcmcdclk1 = {
.name = "pcmcdclk",
};
static struct clk clk_pcmcdclk2 = {
.name = "pcmcdclk",
};
第二类使用这种时钟的是,既可以开关,父时钟也已经记录
static struct clk init_clocks[] = {
{
.name = "hclk_imem",
.parent = &clk_hclk_msys.clk,
.ctrlbit = (1 << 5), /* 时钟开关由第五位决定 */
.enable = s5pv210_clk_ip0_ctrl, /* 提高了开关时钟函数 */
.ops = &clk_hclk_imem_ops, /* 内存控制的提高了ops函数 */
}, {
.name = "uart",
.devname = "s5pv210-uart.0",
.parent = &clk_pclk_psys.clk,
.enable = s5pv210_clk_ip3_ctrl, /* 提高了开关时钟函数 */
.ctrlbit = (1 << 17), /* 时钟开关由第17bit决定 */
}, {
.name = "uart",
.devname = "s5pv210-uart.1",
.parent = &clk_pclk_psys.clk,
.enable = s5pv210_clk_ip3_ctrl,
.ctrlbit = (1 << 18),
}, {
.name = "uart",
.devname = "s5pv210-uart.2",
.parent = &clk_pclk_psys.clk,
.enable = s5pv210_clk_ip3_ctrl,
.ctrlbit = (1 << 19),
}, {
.name = "uart",
.devname = "s5pv210-uart.3",
.parent = &clk_pclk_psys.clk,
.enable = s5pv210_clk_ip3_ctrl,
.ctrlbit = (1 << 20),
}, {
.name = "sromc",
.parent = &clk_hclk_psys.clk,
.enable = s5pv210_clk_ip1_ctrl,
.ctrlbit = (1 << 26),
},
};
上面的是类似的,所以我就以imem和uart的分析一下
/* 父子时钟关系固定,不能改变,不向新的多选一开关方便 */
static struct clksrc_clk clk_armclk = {
.clk = {
.name = "armclk",
},
.sources = &clkset_armclk,
.reg_src = { .reg = S5P_CLK_SRC0, .shift = 16, .size = 1 },
.reg_div = { .reg = S5P_CLK_DIV0, .shift = 0, .size = 3 },
};
static struct clksrc_clk clk_hclk_msys = {
.clk = {
.name = "hclk_msys",
.parent = &clk_armclk.clk, /* 父时钟是armclk */
},
/* 分屏寄存器固定映射好的虚拟地址 */
.reg_div = { .reg = S5P_CLK_DIV0, .shift = 8, .size = 3 },
};
static struct clksrc_clk clk_pclk_msys = {
.clk = {
.name = "pclk_msys",
.parent = &clk_hclk_msys.clk, /* 父时钟是msys的hclk */
},
.reg_div = { .reg = S5P_CLK_DIV0, .shift = 12, .size = 3 },
};
上面用到的寄存器是内核启动期间静态映射好的,可以直接使用的虚拟地址
static struct clk_ops clk_hclk_imem_ops = {
.get_rate = s5pv210_clk_imem_get_rate,
};
static unsigned long s5pv210_clk_imem_get_rate(struct clk *clk)
{
/* imem提供了时钟获取的ops接口,为parent的一般 */
return clk_get_rate(clk->parent) / 2;
}
/* imem的时钟开关函数 */
static int s5pv210_clk_ip0_ctrl(struct clk *clk, int enable)
{
/* 这里的寄存器是已经映射好的虚拟地址 */
return s5p_gatectrl(S5P_CLKGATE_IP0, clk, enable);
}
/* 串口的时钟开关函数 */
static int s5pv210_clk_ip3_ctrl(struct clk *clk, int enable)
{
return s5p_gatectrl(S5P_CLKGATE_IP3, clk, enable);
}
int s5p_gatectrl(void __iomem *reg, struct clk *clk, int enable)
{
unsigned int ctrlbit = clk->ctrlbit; /* 控制位 */
u32 con;
/* 读改写,来根据enable传的真假值来确定ctrlbit控制的那个时钟的使能 */
con = __raw_readl(reg);
con = enable ? (con | ctrlbit) : (con & ~ctrlbit);
__raw_writel(con, reg);
return 0;
}
/* 其它固定类型设备的时钟的开关如下,和上完全一样,就不分析了 */
static struct clk init_clocks_off[] = {
{
.name = "rot",
.parent = &clk_hclk_dsys.clk,
.enable = s5pv210_clk_ip0_ctrl,
.ctrlbit = (1<<29),
}, {
.name = "fimc",
.devname = "s5pv210-fimc.0",
.parent = &clk_hclk_dsys.clk,
.enable = s5pv210_clk_ip0_ctrl,
.ctrlbit = (1 << 24),
}, {
.name = "fimc",
.devname = "s5pv210-fimc.1",
.parent = &clk_hclk_dsys.clk,
.enable = s5pv210_clk_ip0_ctrl,
.ctrlbit = (1 << 25),
}, {
.name = "fimc",
.devname = "s5pv210-fimc.2",
.parent = &clk_hclk_dsys.clk,
.enable = s5pv210_clk_ip0_ctrl,
.ctrlbit = (1 << 26),
}, {
.name = "jpeg",
.parent = &clk_hclk_dsys.clk,
.enable = s5pv210_clk_ip0_ctrl,
.ctrlbit = (1 << 28),
}, {
.name = "mfc",
.devname = "s5p-mfc",
.parent = &clk_pclk_psys.clk,
.enable = s5pv210_clk_ip0_ctrl,
.ctrlbit = (1 << 16),
}, {
.name = "dac",
.devname = "s5p-sdo",
.parent = &clk_hclk_dsys.clk,
.enable = s5pv210_clk_ip1_ctrl,
.ctrlbit = (1 << 10),
}, {
.name = "mixer",
.devname = "s5p-mixer",
.parent = &clk_hclk_dsys.clk,
.enable = s5pv210_clk_ip1_ctrl,
.ctrlbit = (1 << 9),
}, {
.name = "vp",
.devname = "s5p-mixer",
.parent = &clk_hclk_dsys.clk,
.enable = s5pv210_clk_ip1_ctrl,
.ctrlbit = (1 << 8),
}, {
.name = "hdmi",
.devname = "s5pv210-hdmi",
.parent = &clk_hclk_dsys.clk,
.enable = s5pv210_clk_ip1_ctrl,
.ctrlbit = (1 << 11),
}, {
.name = "hdmiphy",
.devname = "s5pv210-hdmi",
.enable = s5pv210_clk_hdmiphy_ctrl,
.ctrlbit = (1 << 0),
}, {
.name = "dacphy",
.devname = "s5p-sdo",
.enable = exynos4_clk_dac_ctrl,
.ctrlbit = (1 << 0),
}, {
.name = "otg",
.parent = &clk_hclk_psys.clk,
.enable = s5pv210_clk_ip1_ctrl,
.ctrlbit = (1<<16),
}, {
.name = "usb-host",
.parent = &clk_hclk_psys.clk,
.enable = s5pv210_clk_ip1_ctrl,
.ctrlbit = (1<<17),
}, {
.name = "lcd",
.parent = &clk_hclk_dsys.clk,
.enable = s5pv210_clk_ip1_ctrl,
.ctrlbit = (1<<0),
}, {
.name = "cfcon",
.parent = &clk_hclk_psys.clk,
.enable = s5pv210_clk_ip1_ctrl,
.ctrlbit = (1<<25),
}, {
.name = "systimer",
.parent = &clk_pclk_psys.clk,
.enable = s5pv210_clk_ip3_ctrl,
.ctrlbit = (1<<16),
}, {
.name = "watchdog",
.parent = &clk_pclk_psys.clk,
.enable = s5pv210_clk_ip3_ctrl,
.ctrlbit = (1<<22),
}, {
.name = "rtc",
.parent = &clk_pclk_psys.clk,
.enable = s5pv210_clk_ip3_ctrl,
.ctrlbit = (1<<15),
}, {
.name = "i2c",
.devname = "s3c2440-i2c.0",
.parent = &clk_pclk_psys.clk,
.enable = s5pv210_clk_ip3_ctrl,
.ctrlbit = (1<<7),
}, {
.name = "i2c",
.devname = "s3c2440-i2c.1",
.parent = &clk_pclk_psys.clk,
.enable = s5pv210_clk_ip3_ctrl,
.ctrlbit = (1 << 10),
}, {
.name = "i2c",
.devname = "s3c2440-i2c.2",
.parent = &clk_pclk_psys.clk,
.enable = s5pv210_clk_ip3_ctrl,
.ctrlbit = (1<<9),
}, {
.name = "i2c",
.devname = "s3c2440-hdmiphy-i2c",
.parent = &clk_pclk_psys.clk,
.enable = s5pv210_clk_ip3_ctrl,
.ctrlbit = (1 << 11),
}, {
.name = "spi",
.devname = "s5pv210-spi.0",
.parent = &clk_pclk_psys.clk,
.enable = s5pv210_clk_ip3_ctrl,
.ctrlbit = (1<<12),
}, {
.name = "spi",
.devname = "s5pv210-spi.1",
.parent = &clk_pclk_psys.clk,
.enable = s5pv210_clk_ip3_ctrl,
.ctrlbit = (1<<13),
}, {
.name = "spi",
.devname = "s5pv210-spi.2",
.parent = &clk_pclk_psys.clk,
.enable = s5pv210_clk_ip3_ctrl,
.ctrlbit = (1<<14),
}, {
.name = "timers",
.parent = &clk_pclk_psys.clk,
.enable = s5pv210_clk_ip3_ctrl,
.ctrlbit = (1<<23),
}, {
.name = "adc",
.parent = &clk_pclk_psys.clk,
.enable = s5pv210_clk_ip3_ctrl,
.ctrlbit = (1<<24),
}, {
.name = "keypad",
.parent = &clk_pclk_psys.clk,
.enable = s5pv210_clk_ip3_ctrl,
.ctrlbit = (1<<21),
}, {
.name = "iis",
.devname = "samsung-i2s.0",
.parent = &clk_p,
.enable = s5pv210_clk_ip3_ctrl,
.ctrlbit = (1<<4),
}, {
.name = "iis",
.devname = "samsung-i2s.1",
.parent = &clk_p,
.enable = s5pv210_clk_ip3_ctrl,
.ctrlbit = (1 << 5),
}, {
.name = "iis",
.devname = "samsung-i2s.2",
.parent = &clk_p,
.enable = s5pv210_clk_ip3_ctrl,
.ctrlbit = (1 << 6),
}, {
.name = "spdif",
.parent = &clk_p,
.enable = s5pv210_clk_ip3_ctrl,
.ctrlbit = (1 << 0),
},
};
static struct clk *clk_cdev[] = {
&clk_hsmmc0,
&clk_hsmmc1,
&clk_hsmmc2,
&clk_hsmmc3,
&clk_pdma0,
&clk_pdma1,
};
/* 提供开关时钟函数,提供开关位,提供父时钟 */
static struct clk clk_hsmmc0 = {
.name = "hsmmc",
.devname = "s3c-sdhci.0",
.parent = &clk_hclk_psys.clk,
.enable = s5pv210_clk_ip2_ctrl,
.ctrlbit = (1<<16),
};
static struct clk clk_hsmmc1 = {
.name = "hsmmc",
.devname = "s3c-sdhci.1",
.parent = &clk_hclk_psys.clk,
.enable = s5pv210_clk_ip2_ctrl,
.ctrlbit = (1<<17),
};
static struct clk clk_hsmmc2 = {
.name = "hsmmc",
.devname = "s3c-sdhci.2",
.parent = &clk_hclk_psys.clk,
.enable = s5pv210_clk_ip2_ctrl,
.ctrlbit = (1<<18),
};
static struct clk clk_hsmmc3 = {
.name = "hsmmc",
.devname = "s3c-sdhci.3",
.parent = &clk_hclk_psys.clk,
.enable = s5pv210_clk_ip2_ctrl,
.ctrlbit = (1<<19),
};
static struct clk clk_pdma0 = {
.name = "pdma0",
.parent = &clk_hclk_psys.clk,
.enable = s5pv210_clk_ip0_ctrl,
.ctrlbit = (1 << 3),
};
static struct clk clk_pdma1 = {
.name = "pdma1",
.parent = &clk_hclk_psys.clk,
.enable = s5pv210_clk_ip0_ctrl,
.ctrlbit = (1 << 4),
};
当然上面的开关类时钟中,大部分设备的时钟内核启动时就关掉了,调用的是clk自己提供的开关函数,
/**
* s3c_disable_clocks() - disable an array of clocks
* @clkp: Pointer to the first clock in the array.
* @nr_clks: Number of clocks to register.
*
* for internal use only at initialisation time. disable the clocks in the
* @clkp array.
*/
void __init s3c_disable_clocks(struct clk *clkp, int nr_clks)
{
for (; nr_clks > 0; nr_clks--, clkp++)
(clkp->enable)(clkp, 0); /* 第二个参数为0,则关时钟 */
}
开始和资源有关系的时钟之前,先看一下,怎么描述这些资源时钟,多选一,分配等
/**
* struct clksrc_sources - list of sources for a given clock
* @sources: array of pointers to clocks
* @nr_sources: The size of @sources
*/
struct clksrc_sources {
unsigned int nr_sources;
struct clk **sources;
};
/**
* struct clksrc_reg - register definition for clock control bits
* @reg: pointer to the register in virtual memory.
* @shift: the shift in bits to where the bitfield is.
* @size: the size in bits of the bitfield.
*
* This specifies the size and position of the bits we are interested
* in within the register specified by @reg.
*/
struct clksrc_reg {
void __iomem *reg; /* 资源寄存器 */
unsigned short shift; /* 资源在这个寄存器的第几位开始控制 */
unsigned short size; /* 资源由几位管理 */
};
/**
* struct clksrc_clk - class of clock for newer style samsung devices.
* @clk: the standard clock representation
* @sources: the sources for this clock
* @reg_src: the register definition for selecting the clock's source
* @reg_div: the register definition for the clock's output divisor
*
* This clock implements the features required by the newer SoCs where
* the standard clock block provides an input mux and a post-mux divisor
* to provide the periperhal's clock.
*
* The array of @sources provides the mapping of mux position to the
* clock, and @reg_src shows the code where to modify to change the mux
* position. The @reg_div defines how to change the divider settings on
* the output.
*/
struct clksrc_clk {
struct clk clk;
struct clksrc_sources *sources; /* 多选一使用可选的所有可能 */
struct clksrc_reg reg_src; /* 多选一 */
struct clksrc_reg reg_div; /* 分频 */
};
/* 这类资源控制,比如分频,倍频,多路切换的都需要配置,所以都要有ops函数的 */
void __init s3c_register_clksrc(struct clksrc_clk *clksrc, int size)
{
int ret;
for (; size > 0; size--, clksrc++) {
if (!clksrc->reg_div.reg && !clksrc->reg_src.reg)
printk(KERN_ERR "%s: clock %s has no registers set\n",
__func__, clksrc->clk.name);
/* fill in the default functions */
/* 如果clk没设置ops接口,则由下面几个选择 */
if (!clksrc->clk.ops) {
if (!clksrc->reg_div.reg) /* 父时钟对子时钟分频 */
clksrc->clk.ops = &clksrc_ops_nodiv;
else if (!clksrc->reg_src.reg)
clksrc->clk.ops = &clksrc_ops_nosrc; /* 选择父时钟 */
else
clksrc->clk.ops = &clksrc_ops; /* 既能选择父时钟,有可以选择分频系数 */
}
/* setup the clocksource, but do not announce it
* as it may be re-set by the setup routines
* called after the rest of the clocks have been
* registered
*/
/* 设置时钟源 */
s3c_set_clksrc(clksrc, false);
/* 把该clk绑定到clock链表上 */
ret = s3c24xx_register_clock(&clksrc->clk);
if (ret < 0) {
printk(KERN_ERR "%s: failed to register %s (%d)\n",
__func__, clksrc->clk.name, ret);
}
}
}
/* 下面就是上面的三个操作集合,其实总共就4个函数,下面我们一一分析 */
static struct clk_ops clksrc_ops = {
.set_parent = s3c_setparent_clksrc,
.get_rate = s3c_getrate_clksrc,
.set_rate = s3c_setrate_clksrc,
.round_rate = s3c_roundrate_clksrc,
};
static struct clk_ops clksrc_ops_nodiv = {
.set_parent = s3c_setparent_clksrc,
};
static struct clk_ops clksrc_ops_nosrc = {
.get_rate = s3c_getrate_clksrc,
.set_rate = s3c_setrate_clksrc,
.round_rate = s3c_roundrate_clksrc,
};
多选一时钟,跟换其父时钟操作
static int s3c_setparent_clksrc(struct clk *clk, struct clk *parent)
{
struct clksrc_clk *sclk = to_clksrc(clk);
struct clksrc_sources *srcs = sclk->sources;
u32 clksrc = __raw_readl(sclk->reg_src.reg);
u32 mask = bit_mask(sclk->reg_src.shift, sclk->reg_src.size);
int src_nr = -1;
int ptr;
/* 在clk的时钟源中找到使用的parent */
for (ptr = 0; ptr < srcs->nr_sources; ptr++)
if (srcs->sources[ptr] == parent) {
src_nr = ptr;
break;
}
/* 跟换clk的parent,并设置(寄存器)跟换时钟源 */
if (src_nr >= 0) {
clk->parent = parent;
clksrc &= ~mask;
clksrc |= src_nr << sclk->reg_src.shift;
__raw_writel(clksrc, sclk->reg_src.reg);
return 0;
}
return -EINVAL;
}
获取时钟频率
static unsigned long s3c_getrate_clksrc(struct clk *clk)
{
struct clksrc_clk *sclk = to_clksrc(clk);
unsigned long rate = clk_get_rate(clk->parent); /* 获取父时钟频率 */
u32 clkdiv = __raw_readl(sclk->reg_div.reg); /* 获得父时钟对该时钟的分频值 */
u32 mask = bit_mask(sclk->reg_div.shift, sclk->reg_div.size);
/* 分频系数 */
clkdiv &= mask;
clkdiv >>= sclk->reg_div.shift;
clkdiv++;
/* 得到该时钟的频率 */
rate /= clkdiv;
return rate;
}
设置时钟频率
/* 如果有计算频率的函数,则计算设置后的精确值 */
long clk_round_rate(struct clk *clk, unsigned long rate)
{
/* 这种计算的好处是,父时钟频率是25M,如果子时钟频率要20M
* 父时钟没法分频得到20M时钟,对多分频得到12.5M,这个12.5就是父时钟计算的
* 最接近并且小于20M的频率
*/
if (!IS_ERR_OR_NULL(clk) && clk->ops && clk->ops->round_rate)
return (clk->ops->round_rate)(clk, rate);
return rate;
}
static int s3c_setrate_clksrc(struct clk *clk, unsigned long rate)
{
struct clksrc_clk *sclk = to_clksrc(clk);
void __iomem *reg = sclk->reg_div.reg;
unsigned int div;
u32 mask = bit_mask(sclk->reg_div.shift, sclk->reg_div.size);
u32 val;
rate = clk_round_rate(clk, rate); /* 经过计算调整后的频率 */
div = clk_get_rate(clk->parent) / rate; /* 得到父时钟频率,除本时钟频率,得到分频因子 */
if (div > (1 << sclk->reg_div.size))
return -EINVAL;
/* 设置寄存器,更改父时钟对该子时钟的分频因子,改变时钟 */
val = __raw_readl(reg);
val &= ~mask;
val |= (div - 1) << sclk->reg_div.shift;
__raw_writel(val, reg);
return 0;
}
计算得到最满足要求的频率
static unsigned long s3c_roundrate_clksrc(struct clk *clk,
unsigned long rate)
{
struct clksrc_clk *sclk = to_clksrc(clk);
unsigned long parent_rate = clk_get_rate(clk->parent);
int max_div = 1 << sclk->reg_div.size;
int div;
/* 子时钟频率不能大于父时钟频率 */
if (rate >= parent_rate)
rate = parent_rate;
else {
div = parent_rate / rate; /* 得到小于设定的分频时钟最接近的时钟 */
if (parent_rate % rate)
div++;
if (div == 0)
div = 1;
if (div > max_div) /* 分频值不能超过分频上限 */
div = max_div;
rate = parent_rate / div; /* 最终计算的最合适的值 */
}
return rate;
}
根据寄存器的默认值,确定当前的父时钟是那个,并绑定到clk上
/* Clock initialisation code */
void __init_or_cpufreq s3c_set_clksrc(struct clksrc_clk *clk, bool announce)
{
struct clksrc_sources *srcs = clk->sources;
u32 mask = bit_mask(clk->reg_src.shift, clk->reg_src.size);
u32 clksrc;
/* 错误检查 */
if (!clk->reg_src.reg) {
if (!clk->clk.parent)
printk(KERN_ERR "%s: no parent clock specified\n",
clk->clk.name);
return;
}
/* 得到时钟寄存器数据 */
clksrc = __raw_readl(clk->reg_src.reg);
clksrc &= mask;
/* 获取时钟源设定值 */
clksrc >>= clk->reg_src.shift;
/* 检查父时钟的个数 */
if (clksrc > srcs->nr_sources || !srcs->sources[clksrc]) {
printk(KERN_ERR "%s: bad source %d\n",
clk->clk.name, clksrc);
return;
}
/* 绑定父时钟 */
clk->clk.parent = srcs->sources[clksrc];
if (announce)
printk(KERN_INFO "%s: source is %s (%d), rate is %ld\n",
clk->clk.name, clk->clk.parent->name, clksrc,
clk_get_rate(&clk->clk));
}
上面就是多选一和分频类时钟的选择的一些通用的注册接口。
下面就看有哪些时钟用到了上面的。
/* Clock initialisation code */
static struct clksrc_clk *sysclks[] = {
&clk_mout_apll,
&clk_mout_epll,
&clk_mout_mpll,
&clk_armclk,
&clk_hclk_msys,
&clk_sclk_a2m,
&clk_hclk_dsys,
&clk_hclk_psys,
&clk_pclk_msys,
&clk_pclk_dsys,
&clk_pclk_psys,
&clk_vpllsrc,
&clk_sclk_vpll,
&clk_mout_dmc0,
&clk_sclk_dmc0,
&clk_sclk_audio0,
&clk_sclk_audio1,
&clk_sclk_audio2,
&clk_sclk_spdif,
};
static struct clksrc_clk clk_mout_apll = {
.clk = {
.name = "mout_apll",
},
.sources = &clk_src_apll,
.reg_src = { .reg = S5P_CLK_SRC0, .shift = 0, .size = 1 },
};
static struct clksrc_clk clk_mout_epll = {
.clk = {
.name = "mout_epll",
},
.sources = &clk_src_epll,
.reg_src = { .reg = S5P_CLK_SRC0, .shift = 8, .size = 1 },
};
static struct clksrc_clk clk_mout_mpll = {
.clk = {
.name = "mout_mpll",
},
.sources = &clk_src_mpll,
.reg_src = { .reg = S5P_CLK_SRC0, .shift = 4, .size = 1 },
};
static struct clk *clkset_armclk_list[] = {
[0] = &clk_mout_apll.clk,
[1] = &clk_mout_mpll.clk,
};
static struct clksrc_sources clkset_armclk = {
.sources = clkset_armclk_list,
.nr_sources = ARRAY_SIZE(clkset_armclk_list),
};
static struct clksrc_clk clk_armclk = {
.clk = {
.name = "armclk",
},
.sources = &clkset_armclk,
.reg_src = { .reg = S5P_CLK_SRC0, .shift = 16, .size = 1 },
.reg_div = { .reg = S5P_CLK_DIV0, .shift = 0, .size = 3 },
};
static struct clksrc_clk clk_hclk_msys = {
.clk = {
.name = "hclk_msys",
.parent = &clk_armclk.clk,
},
.reg_div = { .reg = S5P_CLK_DIV0, .shift = 8, .size = 3 },
};
static struct clksrc_clk clk_pclk_msys = {
.clk = {
.name = "pclk_msys",
.parent = &clk_hclk_msys.clk,
},
.reg_div = { .reg = S5P_CLK_DIV0, .shift = 12, .size = 3 },
};
static struct clksrc_clk clk_sclk_a2m = {
.clk = {
.name = "sclk_a2m",
.parent = &clk_mout_apll.clk,
},
.reg_div = { .reg = S5P_CLK_DIV0, .shift = 4, .size = 3 },
};
static struct clk *clkset_hclk_sys_list[] = {
[0] = &clk_mout_mpll.clk,
[1] = &clk_sclk_a2m.clk,
};
static struct clksrc_sources clkset_hclk_sys = {
.sources = clkset_hclk_sys_list,
.nr_sources = ARRAY_SIZE(clkset_hclk_sys_list),
};
static struct clksrc_clk clk_hclk_dsys = {
.clk = {
.name = "hclk_dsys",
},
.sources = &clkset_hclk_sys,
.reg_src = { .reg = S5P_CLK_SRC0, .shift = 20, .size = 1 },
.reg_div = { .reg = S5P_CLK_DIV0, .shift = 16, .size = 4 },
};
static struct clksrc_clk clk_pclk_dsys = {
.clk = {
.name = "pclk_dsys",
.parent = &clk_hclk_dsys.clk,
},
.reg_div = { .reg = S5P_CLK_DIV0, .shift = 20, .size = 3 },
};
static struct clksrc_clk clk_hclk_psys = {
.clk = {
.name = "hclk_psys",
},
.sources = &clkset_hclk_sys,
.reg_src = { .reg = S5P_CLK_SRC0, .shift = 24, .size = 1 },
.reg_div = { .reg = S5P_CLK_DIV0, .shift = 24, .size = 4 },
};
static struct clksrc_clk clk_pclk_psys = {
.clk = {
.name = "pclk_psys",
.parent = &clk_hclk_psys.clk,
},
.reg_div = { .reg = S5P_CLK_DIV0, .shift = 28, .size = 3 },
};
可以看到这些都是分配或者多选一的。
最后就是倍频的pll类型的时钟了,最后面设置。
这个只针对单独的处理器,单独设置
void __init_or_cpufreq s5pv210_setup_clocks(void)
{
struct clk *xtal_clk;
unsigned long vpllsrc;
unsigned long armclk;
unsigned long hclk_msys;
unsigned long hclk_dsys;
unsigned long hclk_psys;
unsigned long pclk_msys;
unsigned long pclk_dsys;
unsigned long pclk_psys;
unsigned long apll;
unsigned long mpll;
unsigned long epll;
unsigned long vpll;
unsigned int ptr;
u32 clkdiv0, clkdiv1;
/* Set functions for clk_fout_epll */
/* pll相关的ops绑定已经注册到mux的source上 */
clk_fout_epll.enable = s5p_epll_enable;
clk_fout_epll.ops = &s5pv210_epll_ops;
printk(KERN_DEBUG "%s: registering clocks\n", __func__);
/* 获取bootloader启动后寄存器设置的分频 */
clkdiv0 = __raw_readl(S5P_CLK_DIV0);
clkdiv1 = __raw_readl(S5P_CLK_DIV1);
printk(KERN_DEBUG "%s: clkdiv0 = %08x, clkdiv1 = %08x\n",
__func__, clkdiv0, clkdiv1);
/* 打印基本输入时钟 */
xtal_clk = clk_get(NULL, "xtal");
BUG_ON(IS_ERR(xtal_clk));
/* 得到基本时钟输入频率 */
xtal = clk_get_rate(xtal_clk);
clk_put(xtal_clk);
printk(KERN_DEBUG "%s: xtal is %ld\n", __func__, xtal);
/* 计算倍频后额值 */
apll = s5p_get_pll45xx(xtal, __raw_readl(S5P_APLL_CON), pll_4508);
mpll = s5p_get_pll45xx(xtal, __raw_readl(S5P_MPLL_CON), pll_4502);
epll = s5p_get_pll46xx(xtal, __raw_readl(S5P_EPLL_CON),
__raw_readl(S5P_EPLL_CON1), pll_4600);
vpllsrc = clk_get_rate(&clk_vpllsrc.clk);
vpll = s5p_get_pll45xx(vpllsrc, __raw_readl(S5P_VPLL_CON), pll_4502);
/* 绑定操作函数和倍频后的输出速率 */
clk_fout_apll.ops = &clk_fout_apll_ops;
clk_fout_mpll.rate = mpll;
clk_fout_epll.rate = epll;
clk_fout_vpll.ops = &s5pv210_vpll_ops;
clk_fout_vpll.rate = vpll;
printk(KERN_INFO "S5PV210: PLL settings, A=%ld, M=%ld, E=%ld V=%ld",
apll, mpll, epll, vpll);
/* pll后的时钟速率已经得到,其后面的各个sys中的时钟也就可以直接计算得到了 */
armclk = clk_get_rate(&clk_armclk.clk);
hclk_msys = clk_get_rate(&clk_hclk_msys.clk);
hclk_dsys = clk_get_rate(&clk_hclk_dsys.clk);
hclk_psys = clk_get_rate(&clk_hclk_psys.clk);
pclk_msys = clk_get_rate(&clk_pclk_msys.clk);
pclk_dsys = clk_get_rate(&clk_pclk_dsys.clk);
pclk_psys = clk_get_rate(&clk_pclk_psys.clk);
/* 打印出相关时钟 */
printk(KERN_INFO "S5PV210: ARMCLK=%ld, HCLKM=%ld, HCLKD=%ld\n"
"HCLKP=%ld, PCLKM=%ld, PCLKD=%ld, PCLKP=%ld\n",
armclk, hclk_msys, hclk_dsys, hclk_psys,
pclk_msys, pclk_dsys, pclk_psys);
/* 跟新线面注册的clk的时钟 */
clk_f.rate = armclk;
clk_h.rate = hclk_psys;
clk_p.rate = pclk_psys;
/* 跟新并打印clksrcs数组中的各个模块时钟 */
for (ptr = 0; ptr < ARRAY_SIZE(clksrcs); ptr++)
s3c_set_clksrc(&clksrcs[ptr], true);
}
下面就是内核启动过程中打印的相关信息。上面都已经见到了。
分析的第一个函数中打印的。
最后一个函数中打印的各PLL后输出的值和各sys域的值。
最后一个函数因为参数announce传的是true而打印出的各时钟模块的值。
/* Clock initialisation code */
void __init_or_cpufreq s3c_set_clksrc(struct clksrc_clk *clk, bool announce)
{
struct clksrc_sources *srcs = clk->sources;
u32 mask = bit_mask(clk->reg_src.shift, clk->reg_src.size);
u32 clksrc;
if (!clk->reg_src.reg) {
if (!clk->clk.parent)
printk(KERN_ERR "%s: no parent clock specified\n",
clk->clk.name);
return;
}
clksrc = __raw_readl(clk->reg_src.reg);
clksrc &= mask;
clksrc >>= clk->reg_src.shift;
if (clksrc > srcs->nr_sources || !srcs->sources[clksrc]) {
printk(KERN_ERR "%s: bad source %d\n",
clk->clk.name, clksrc);
return;
}
clk->clk.parent = srcs->sources[clksrc];
if (announce)
printk(KERN_INFO "%s: source is %s (%d), rate is %ld\n",
clk->clk.name, clk->clk.parent->name, clksrc,
clk_get_rate(&clk->clk));
}
static struct clksrc_clk clksrcs[] = {
{
.clk = {
.name = "sclk_dmc",
},
.sources = &clkset_group1,
.reg_src = { .reg = S5P_CLK_SRC6, .shift = 24, .size = 2 },
.reg_div = { .reg = S5P_CLK_DIV6, .shift = 28, .size = 4 },
}, {
.clk = {
.name = "sclk_onenand",
},
.sources = &clkset_sclk_onenand,
.reg_src = { .reg = S5P_CLK_SRC0, .shift = 28, .size = 1 },
.reg_div = { .reg = S5P_CLK_DIV6, .shift = 12, .size = 3 },
}, {
.clk = {
.name = "sclk_fimc",
.devname = "s5pv210-fimc.0",
.enable = s5pv210_clk_mask1_ctrl,
.ctrlbit = (1 << 2),
},
.sources = &clkset_group2,
.reg_src = { .reg = S5P_CLK_SRC3, .shift = 12, .size = 4 },
.reg_div = { .reg = S5P_CLK_DIV3, .shift = 12, .size = 4 },
}, {
.clk = {
.name = "sclk_fimc",
.devname = "s5pv210-fimc.1",
.enable = s5pv210_clk_mask1_ctrl,
.ctrlbit = (1 << 3),
},
.sources = &clkset_group2,
.reg_src = { .reg = S5P_CLK_SRC3, .shift = 16, .size = 4 },
.reg_div = { .reg = S5P_CLK_DIV3, .shift = 16, .size = 4 },
}, {
.clk = {
.name = "sclk_fimc",
.devname = "s5pv210-fimc.2",
.enable = s5pv210_clk_mask1_ctrl,
.ctrlbit = (1 << 4),
},
.sources = &clkset_group2,
.reg_src = { .reg = S5P_CLK_SRC3, .shift = 20, .size = 4 },
.reg_div = { .reg = S5P_CLK_DIV3, .shift = 20, .size = 4 },
}, {
.clk = {
.name = "sclk_cam0",
.enable = s5pv210_clk_mask0_ctrl,
.ctrlbit = (1 << 3),
},
.sources = &clkset_group2,
.reg_src = { .reg = S5P_CLK_SRC1, .shift = 12, .size = 4 },
.reg_div = { .reg = S5P_CLK_DIV1, .shift = 12, .size = 4 },
}, {
.clk = {
.name = "sclk_cam1",
.enable = s5pv210_clk_mask0_ctrl,
.ctrlbit = (1 << 4),
},
.sources = &clkset_group2,
.reg_src = { .reg = S5P_CLK_SRC1, .shift = 16, .size = 4 },
.reg_div = { .reg = S5P_CLK_DIV1, .shift = 16, .size = 4 },
}, {
.clk = {
.name = "sclk_fimd",
.enable = s5pv210_clk_mask0_ctrl,
.ctrlbit = (1 << 5),
},
.sources = &clkset_group2,
.reg_src = { .reg = S5P_CLK_SRC1, .shift = 20, .size = 4 },
.reg_div = { .reg = S5P_CLK_DIV1, .shift = 20, .size = 4 },
}, {
.clk = {
.name = "sclk_mfc",
.devname = "s5p-mfc",
.enable = s5pv210_clk_ip0_ctrl,
.ctrlbit = (1 << 16),
},
.sources = &clkset_group1,
.reg_src = { .reg = S5P_CLK_SRC2, .shift = 4, .size = 2 },
.reg_div = { .reg = S5P_CLK_DIV2, .shift = 4, .size = 4 },
}, {
.clk = {
.name = "sclk_g2d",
.enable = s5pv210_clk_ip0_ctrl,
.ctrlbit = (1 << 12),
},
.sources = &clkset_group1,
.reg_src = { .reg = S5P_CLK_SRC2, .shift = 8, .size = 2 },
.reg_div = { .reg = S5P_CLK_DIV2, .shift = 8, .size = 4 },
}, {
.clk = {
.name = "sclk_g3d",
.enable = s5pv210_clk_ip0_ctrl,
.ctrlbit = (1 << 8),
},
.sources = &clkset_group1,
.reg_src = { .reg = S5P_CLK_SRC2, .shift = 0, .size = 2 },
.reg_div = { .reg = S5P_CLK_DIV2, .shift = 0, .size = 4 },
}, {
.clk = {
.name = "sclk_csis",
.enable = s5pv210_clk_mask0_ctrl,
.ctrlbit = (1 << 6),
},
.sources = &clkset_group2,
.reg_src = { .reg = S5P_CLK_SRC1, .shift = 24, .size = 4 },
.reg_div = { .reg = S5P_CLK_DIV1, .shift = 28, .size = 4 },
}, {
.clk = {
.name = "sclk_pwi",
.enable = s5pv210_clk_mask0_ctrl,
.ctrlbit = (1 << 29),
},
.sources = &clkset_group2,
.reg_src = { .reg = S5P_CLK_SRC6, .shift = 20, .size = 4 },
.reg_div = { .reg = S5P_CLK_DIV6, .shift = 24, .size = 4 },
}, {
.clk = {
.name = "sclk_pwm",
.enable = s5pv210_clk_mask0_ctrl,
.ctrlbit = (1 << 19),
},
.sources = &clkset_group2,
.reg_src = { .reg = S5P_CLK_SRC5, .shift = 12, .size = 4 },
.reg_div = { .reg = S5P_CLK_DIV5, .shift = 12, .size = 4 },
},
};
最后列出三星自己提供的api函数(注意老的时钟绝大多数由厂家来实现)
下面这些函数三星的实现和common clock framework里面的实现基本是一样的,只不过就是管理没common clock framework正式。
int clk_enable(struct clk *clk)
{
unsigned long flags;
if (IS_ERR(clk) || clk == NULL)
return -EINVAL;
/* 使能某个时钟,必须父时钟先使能 */
clk_enable(clk->parent);
spin_lock_irqsave(&clocks_lock, flags);
/* 只要使能过,则引用计数++,在引用计数为0时,代表时钟关,此时需要调用真正的开时钟函数 */
if ((clk->usage++) == 0)
(clk->enable)(clk, 1);
spin_unlock_irqrestore(&clocks_lock, flags);
return 0;
}
void clk_disable(struct clk *clk)
{
unsigned long flags;
if (IS_ERR(clk) || clk == NULL)
return;
spin_lock_irqsave(&clocks_lock, flags);
/* 关时钟前先减1,它的引用计数,可能其他设备也在用这个时钟 */
if ((--clk->usage) == 0)
(clk->enable)(clk, 0);
spin_unlock_irqrestore(&clocks_lock, flags);
clk_disable(clk->parent); /* 父时钟的引用计数也要减1 */
}
unsigned long clk_get_rate(struct clk *clk)
{
if (IS_ERR_OR_NULL(clk))
return 0;
if (clk->rate != 0)
return clk->rate; /* 该时钟有值,直接返回 */
/* 时钟值为0,则调用相关函数计算时钟 */
if (clk->ops != NULL && clk->ops->get_rate != NULL)
return (clk->ops->get_rate)(clk);
/* 有父时钟,该时钟速率就是父时钟速率 */
if (clk->parent != NULL)
return clk_get_rate(clk->parent);
/* 如果以上都不是则返回0 */
return clk->rate;
}
/* 计算最接近的时钟范围 */
long clk_round_rate(struct clk *clk, unsigned long rate)
{
if (!IS_ERR_OR_NULL(clk) && clk->ops && clk->ops->round_rate)
return (clk->ops->round_rate)(clk, rate);
return rate;
}
/* 设置时钟速率 */
int clk_set_rate(struct clk *clk, unsigned long rate)
{
unsigned long flags;
int ret;
if (IS_ERR_OR_NULL(clk))
return -EINVAL;
/* We do not default just do a clk->rate = rate as
* the clock may have been made this way by choice.
*/
WARN_ON(clk->ops == NULL);
WARN_ON(clk->ops && clk->ops->set_rate == NULL);
if (clk->ops == NULL || clk->ops->set_rate == NULL)
return -EINVAL;
spin_lock_irqsave(&clocks_lock, flags);
ret = (clk->ops->set_rate)(clk, rate);
spin_unlock_irqrestore(&clocks_lock, flags);
return ret;
}
/* 获取父时钟 */
struct clk *clk_get_parent(struct clk *clk)
{
return clk->parent;
}
/* 跟换父时钟 */
int clk_set_parent(struct clk *clk, struct clk *parent)
{
unsigned long flags;
int ret = 0;
if (IS_ERR_OR_NULL(clk) || IS_ERR_OR_NULL(parent))
return -EINVAL;
spin_lock_irqsave(&clocks_lock, flags);
if (clk->ops && clk->ops->set_parent)
ret = (clk->ops->set_parent)(clk, parent);
spin_unlock_irqrestore(&clocks_lock, flags);
return ret;
}
当然老的时钟框架中对驱动工程师而言,使用和新的是一样的
因为操作步骤都是一样的,因为都是。
步骤都是获得时钟,使能时钟。
获得时钟所用的clk_get函数也都是完全一样的。因为都是从clock链表中查找返回一个clk指针。
唯一的差别就是使能的函数这里。
/* clk_prepare_enable helps cases using clk_enable in non-atomic context. */
static inline int clk_prepare_enable(struct clk *clk)
{
int ret;
ret = clk_prepare(clk);
if (ret)
return ret;
ret = clk_enable(clk);
if (ret)
clk_unprepare(clk);
return ret;
}
/* clk_disable_unprepare helps cases using clk_disable in non-atomic context. */
static inline void clk_disable_unprepare(struct clk *clk)
{
clk_disable(clk);
clk_unprepare(clk);
}
老的框架是没有clk_prepare/clk_unprepare函数的,所以就用了一个可能睡眠的函数,表明这个函数的功能。
#ifdef CONFIG_HAVE_CLK_PREPARE /* 这个宏是 common clock framework才有的 */
int clk_prepare(struct clk *clk);
#else
static inline int clk_prepare(struct clk *clk)
{
might_sleep();
return 0;
}
#endif
这个宏主要用来做调试工作,在你不确定不期望睡眠的地方是否真的不会睡眠时,就把这个宏加进去,进行调试。
当然前提是打开相关调试宏。
void __might_sleep(const char *file, int line, int preempt_offset)
{
static unsigned long prev_jiffy; /* ratelimiting */
rcu_sleep_check(); /* WARN_ON_ONCE() by default, no rate limit reqd. */
if ((preempt_count_equals(preempt_offset) && !irqs_disabled() &&
!is_idle_task(current)) ||
system_state != SYSTEM_RUNNING || oops_in_progress)
return;
if (time_before(jiffies, prev_jiffy + HZ) && prev_jiffy)
return;
prev_jiffy = jiffies;
printk(KERN_ERR
"BUG: sleeping function called from invalid context at %s:%d\n",
file, line);
printk(KERN_ERR
"in_atomic(): %d, irqs_disabled(): %d, pid: %d, name: %s\n",
in_atomic(), irqs_disabled(),
current->pid, current->comm);
debug_show_held_locks(current);
if (irqs_disabled())
print_irqtrace_events(current);
#ifdef CONFIG_DEBUG_PREEMPT
if (!preempt_count_equals(preempt_offset)) {
pr_err("Preemption disabled at:");
print_ip_sym(current->preempt_disable_ip);
pr_cont("\n");
}
#endif
dump_stack(); /* 打印出栈的回溯信息 */
}