转载:Linux common clock framework(2)_clock provider - Red_Point - 博客园
TCC803X时钟框架:
总线:
外设:
不管总线还是外设时钟,都有相应的寄存器配置:
注意:以PCLKCTRL_DDIB01为例,开头P表示外设。DDIBUS:Display Driver Interface BUS.
以LCD0的时钟为例,其对应的寄存器0x1400,00F4:
clock framework将clock分为fixed rate、gate、devider、mux、fixed factor、composite六类,每一类clock都有相似的功能、相似的控制方式,因而可以使用相同的逻辑统一处理,这充分体现了面向对象的思想。参考引用文章:3.2.2 clock分类及register
dts中clk配置(删减版):
clocks {
compatible = "telechips,ckc";
#address-cells = <1>;
#size-cells = <1>;
osc24m: oscillator {
compatible = "fixed-clock";
#clock-cells = <0>;
clock-frequency = <24000000>;
clock-output-names = "osc24m";
};
clk_pll: pll_clk {
compatible = "telechips,clk-pll";
#clock-cells = <1>;
clocks = <&osc24m>;
clock-output-names = "pll0", "pll1", "pll2", "pll3",
"pll4";
};
clk_ext0: clk_ext@0 {
compatible = "fixed-clock";
#clock-cells = <0>;
clock-frequency = <24000000>;
clock-output-names = "clk_ext0";
};
clk_ext1: clk_ext@1 {
compatible = "fixed-clock";
#clock-cells = <0>;
clock-frequency = <24000000>;
clock-output-names = "clk_ext1";
};
clk_fbus: clk@14000008 {
compatible = "telechips,clk-fbus";
#clock-cells = <1>;
clock-indices =
<FBUS_CPU0>, <FBUS_CPU1>, <FBUS_CBUS>, <FBUS_CMBUS>,
clock-output-names =
"fbus_cpu0", "fbus_cpu1", "fbus_cbus", "fbus_cmbus",
telechips,clock-flags =
<0>, /* FBUS_CPU0 */
<CLK_IGNORE_UNUSED>, /* FBUS_CPU1 */
<(CLK_SET_RATE_GATE | CLK_IGNORE_UNUSED)>, /* FBUS_CBUS */
<(CLK_SET_RATE_GATE | CLK_IGNORE_UNUSED)>, /* FBUS_CMBUS */
<(CLK_SET_RATE_GATE | CLK_IGNORE_UNUSED)>,
};
clk_peri: clk@140000d0 {
compatible = "telechips,clk-peri";
#clock-cells = <1>;
clock-indices =
<PERI_TCX>, <PERI_TCT>, <PERI_TCZ>,
<PERI_LCDTIMER>, <PERI_LCD0>, <PERI_LCD1>,
<PERI_LCD2>, <PERI_CPUINTERFACE0>,
<PERI_CPUINTERFACE1>, <PERI_HDMI_CORE>,
<PERI_HDMI_AUDIO>, <PERI_HDMI_PCLK>,
clock-output-names =
"peri_tcx", "peri_tct", "peri_tcz",
"peri_lcdtimer", "peri_lcd0", "peri_lcd1",
"peri_lcd2", "peri_cpuinterface0",
"peri_cpuinterface1", "peri_hdmi_core",
"peri_hdmi_audio", "peri_hdmi_pclk",
telechips,clock-flags =
<CLK_IGNORE_UNUSED>, /* PERI_TCX */
<CLK_IGNORE_UNUSED>, /* PERI_TCT */
<CLK_IGNORE_UNUSED>, /* PERI_TCZ */
<CLK_IGNORE_UNUSED>, /* PERI_LCDTIMER */
<CLK_IGNORE_UNUSED>, /* PERI_LCD0 */
<CLK_IGNORE_UNUSED>, /* PERI_LCD1 */
<CLK_IGNORE_UNUSED>, /* PERI_LCD2 */
<CLK_IGNORE_UNUSED>, /* PERI_CPUINTERFACE0 */
<CLK_IGNORE_UNUSED>, /* PERI_CPUINTERFACE1 */
<CLK_IGNORE_UNUSED>, /* PERI_HDMI_CORE */
<CLK_IGNORE_UNUSED>, /* PERI_HDMI_AUDIO */
<CLK_IGNORE_UNUSED>, /* PERI_HDMI_PCLK */
};
clk_io: clk@16051000 {
compatible = "telechips,clk-iobus";
#clock-cells = <1>;
clocks = <&clk_fbus FBUS_IO>;
clock-indices =
<IOBUS_ASRC_DMA>, <IOBUS_ASRC>, <IOBUS_IC_TC>,
<IOBUS_CEC1>, <IOBUS_DMA0>, <IOBUS_DMA1>,
clock-output-names =
"iobus_asrc_dma", "iobus_asrc", "iobus_ic_tc",
"iobus_cec1", "iobus_dma0", "iobus_dma1",
telechips,clock-flags =
<CLK_IGNORE_UNUSED>, /* IOBUS_ASRC_DMA */
<CLK_IGNORE_UNUSED>, /* IOBUS_ASRC */
<CLK_IGNORE_UNUSED>, /* IOBUS_IC_TC */
<CLK_IGNORE_UNUSED>, /* IOBUS_CEC1 */
<CLK_IGNORE_UNUSED>, /* IOBUS_DMA0 */
<CLK_IGNORE_UNUSED>, /* IOBUS_DMA1 */
};
}
注:从dts看,telechips每个时钟如果有父时钟,那就只有1个。
代码分析:
CLK_OF_DECLARE(tcc_clk_peri, "telechips,clk-peri", tcc_peri_init);
#define CLK_OF_DECLARE(name, compat, fn) OF_DECLARE_1(clk, name, compat, fn)
#define OF_DECLARE_1(table, name, compat, fn) \
_OF_DECLARE(table, name, compat, fn, of_init_fn_1)
#define _OF_DECLARE(table, name, compat, fn, fn_type) \
static const struct of_device_id __of_table_##name \
__used __section(__##table##_of_table) \
= { .compatible = compat, \
.data = (fn == (fn_type)NULL) ? fn : fn }
extern struct of_device_id __clk_of_table; // include\linux\clk-provider.h
time_init
of_clk_init(NULL);
void __init of_clk_init(const struct of_device_id *matches)
if (!matches)
matches = &__clk_of_table;
for_each_matching_node_and_match( , matches, &match) {
struct clock_provider *parent;
if (!of_device_is_available(np))
continue;
parent = kzalloc(sizeof(*parent), GFP_KERNEL);
if (!parent) {
list_for_each_entry_safe(clk_provider, next,
&clk_provider_list, node) {
list_del(&clk_provider->node);
of_node_put(clk_provider->np);
kfree(clk_provider);
}
of_node_put(np);
return;
}
parent->clk_init_cb = match->data;
parent->np = of_node_get(np);
list_add_tail(&parent->node, &clk_provider_list);
}
static struct clk_ops tcc_peri_ops = {
.enable = tcc_peri_enable,
.disable = tcc_peri_disable,
.recalc_rate = tcc_peri_recalc_rate,
.round_rate = tcc_round_rate,
.set_rate = tcc_peri_set_rate,
.is_enabled = tcc_peri_is_enabled,
.debug_init = tcc_clk_debug_init,
};
static void __init tcc_peri_init(struct device_node *np)
{
tcc_clk_register(np, &tcc_peri_ops);
}
int tcc_clk_register(struct device_node *np, struct clk_ops *ops)
struct clk_onecell_data *clk_data;
struct tcc_clk *tcc_clk;
int num_clks;
num_clks = of_property_count_strings(np, "clock-output-names");
clk_data = kzalloc(sizeof(struct clk_onecell_data), GFP_KERNEL);
clk_data->clks = kcalloc(num_clks, sizeof(struct clk *), GFP_KERNEL);
for (i = 0; i < num_clks; i++) {
struct clk_init_data init = { };
const char *clk_name;
u32 index;
u32 flags = 0;
tcc_clk = kzalloc(sizeof(tcc_clk), GFP_KERNEL);
of_property_read_string_index(np, "clock-output-names",i, &clk_name);
of_property_read_u32_index(np, "clock-indices",i, &index);
of_property_read_u32_index(np, "telechips,clock-flags",i, &flags);
init.name = clk_name;
init.num_parents = of_clk_get_parent_count(np);
parent_names = kzalloc(sizeof(char *) * init.num_parents,GFP_KERNEL);
of_clk_parent_fill(np, parent_names, init.num_parents);
init.parent_names = parent_names;
init.flags = flags; /*重点*/
init.ops = ops;
tcc_clk->hw.init = &init; /*重点*/
tcc_clk->ops = ops;
tcc_clk->id = index;
clk = clk_register(NULL, &tcc_clk->hw);
ret = clk_register_clkdev(clk, clk_name, NULL);
clk_data->clks[i] = clk;
}
clk_data->clk_num = i;
return of_clk_add_provider(np, tcc_onecell_get, clk_data);
int of_clk_parent_fill(struct device_node *np, const char **parents,
unsigned int size)
{
unsigned int i = 0;
while (i < size && (parents[i] = of_clk_get_parent_name(np, i)) != NULL)
i++;
return i;
}
const char *of_clk_get_parent_name(struct device_node *np, int index)
{
struct of_phandle_args clkspec;
struct property *prop;
const char *clk_name;
const __be32 *vp;
u32 pv;
int rc;
int count;
struct clk *clk;
rc = of_parse_phandle_with_args(np, "clocks", "#clock-cells", index,&clkspec);
index = clkspec.args_count ? clkspec.args[0] : 0;
count = 0;
/* if there is an indices property, use it to transfer the index
* specified into an array offset for the clock-output-names property.
*/
of_property_for_each_u32(clkspec.np, "clock-indices", prop, vp, pv) {
if (index == pv) {
index = count;
break;
}
count++;
}
if (of_property_read_string_index(clkspec.np, "clock-output-names",index,
&clk_name) < 0) {
clk = of_clk_get_from_provider(&clkspec);
if (IS_ERR(clk)) {
if (clkspec.args_count == 0)
clk_name = clkspec.np->name;
else
clk_name = NULL;
} else {
clk_name = __clk_get_name(clk);
clk_put(clk);
}
}
of_node_put(clkspec.np);
return clk_name;
}
struct clk *of_clk_get_from_provider(struct of_phandle_args *clkspec)
{
return __of_clk_get_from_provider(clkspec, NULL, __func__);
}
struct clk *__of_clk_get_from_provider(struct of_phandle_args *clkspec,
const char *dev_id, const char *con_id)
{
struct of_clk_provider *provider;
struct clk *clk = ERR_PTR(-EPROBE_DEFER);
struct clk_hw *hw;
list_for_each_entry(provider, &of_clk_providers, link) {
if (provider->node == clkspec->np) {
hw = __of_clk_get_hw_from_provider(provider, clkspec);
clk = __clk_create_clk(hw, dev_id, con_id);
}
if (!IS_ERR(clk)) {
break;
}
}
return clk;
}
struct clk *clk_register(struct device *dev, struct clk_hw *hw)
{
int i, ret;
struct clk_core *core;
core = kzalloc(sizeof(*core), GFP_KERNEL);
core->name = kstrdup_const(hw->init->name, GFP_KERNEL);
core->ops = hw->init->ops;
if (dev && dev->driver)
core->owner = dev->driver->owner;
core->hw = hw;
core->flags = hw->init->flags;
core->num_parents = hw->init->num_parents;
core->min_rate = 0;
core->max_rate = ULONG_MAX;
hw->core = core;
/* allocate local copy in case parent_names is __initdata */
core->parent_names = kcalloc(core->num_parents, sizeof(char *), GFP_KERNEL);
/* copy each string name in case parent_names is __initdata */
for (i = 0; i < core->num_parents; i++) {
core->parent_names[i] = kstrdup_const(hw->init->parent_names[i],GFP_KERNEL);
}
/* avoid unnecessary string look-ups of clk_core's possible parents. */
core->parents = kcalloc(core->num_parents, sizeof(*core->parents),GFP_KERNEL);
INIT_HLIST_HEAD(&core->clks);
hw->clk = __clk_create_clk(hw, NULL, NULL);
ret = __clk_core_init(core);
if (!ret)
return hw->clk;
}
struct clk *__clk_create_clk(struct clk_hw *hw, const char *dev_id,
const char *con_id)
{
struct clk *clk;
clk = kzalloc(sizeof(*clk), GFP_KERNEL);
clk->core = hw->core;
clk->dev_id = dev_id;
clk->con_id = kstrdup_const(con_id, GFP_KERNEL);
clk->max_rate = ULONG_MAX;
hlist_add_head(&clk->clks_node, &hw->core->clks);
return clk;
}
static int __clk_core_init(struct clk_core *core)
{
int i, ret = 0;
struct clk_core *orphan;
struct hlist_node *tmp2;
unsigned long rate;
/* check to see if a clock with this name is already registered */
if (clk_core_lookup(core->name)) {}
core->parent = __clk_init_parent(core);
if (core->parent) {
hlist_add_head(&core->child_node,&core->parent->children);
core->orphan = core->parent->orphan;
} else if (!core->num_parents) {
hlist_add_head(&core->child_node, &clk_root_list);
core->orphan = false;
} else {
hlist_add_head(&core->child_node, &clk_orphan_list);
core->orphan = true;
}
if (core->ops->recalc_accuracy)
core->accuracy = core->ops->recalc_accuracy(core->hw,
__clk_get_accuracy(core->parent));
else if (core->parent)
core->accuracy = core->parent->accuracy;
else
core->accuracy = 0;
/*
* Set clk's phase.
* Since a phase is by definition relative to its parent, just
* query the current clock phase, or just assume it's in phase.
*/
if (core->ops->get_phase)
core->phase = core->ops->get_phase(core->hw);
else
core->phase = 0;
/*
* Set clk's rate. The preferred method is to use .recalc_rate. For
* simple clocks and lazy developers the default fallback is to use the
* parent's rate. If a clock doesn't have a parent (or is orphaned)
* then rate is set to zero.
*/
if (core->ops->recalc_rate)
rate = core->ops->recalc_rate(core->hw,
clk_core_get_rate_nolock(core->parent));
else if (core->parent)
rate = core->parent->rate;
else
rate = 0;
core->rate = core->req_rate = rate;
/*
* Enable CLK_IS_CRITICAL clocks so newly added critical clocks
* don't get accidentally disabled when walking the orphan tree and
* reparenting clocks
*/
if (core->flags & CLK_IS_CRITICAL) {
unsigned long flags;
clk_core_prepare(core);
flags = clk_enable_lock();
clk_core_enable(core);
clk_enable_unlock(flags);
}
/*
* walk the list of orphan clocks and reparent any that newly finds a
* parent.
*/
hlist_for_each_entry_safe(orphan, tmp2, &clk_orphan_list, child_node) {
struct clk_core *parent = __clk_init_parent(orphan);
/*
* We need to use __clk_set_parent_before() and _after() to
* to properly migrate any prepare/enable count of the orphan
* clock. This is important for CLK_IS_CRITICAL clocks, which
* are enabled during init but might not have a parent yet.
*/
if (parent) {
/* update the clk tree topology */
__clk_set_parent_before(orphan, parent);
__clk_set_parent_after(orphan, parent, NULL);
__clk_recalc_accuracies(orphan);
__clk_recalc_rates(orphan, 0);
}
}
/*
* optional platform-specific magic
*
* The .init callback is not used by any of the basic clock types, but
* exists for weird hardware that must perform initialization magic.
* Please consider other ways of solving initialization problems before
* using this callback, as its use is discouraged.
*/
if (core->ops->init)
core->ops->init(core->hw);
}
static struct clk_core *__clk_init_parent(struct clk_core *core)
{
u8 index = 0;
if (core->num_parents > 1 && core->ops->get_parent)
index = core->ops->get_parent(core->hw);
return clk_core_get_parent_by_index(core, index);
}
int clk_register_clkdev(struct clk *clk, const char *con_id,
const char *dev_id)
{
struct clk_lookup *cl;
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;
}
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 struct clk *tcc_onecell_get(struct of_phandle_args *clkspec, void *data)
{
struct clk_onecell_data *clk_data = data;
unsigned int idx = clkspec->args[0];
int i;
for (i = 0; i < clk_data->clk_num; i++) {
struct clk *clk = clk_data->clks[i];
struct tcc_clk *tcc_clk;
tcc_clk = to_tcc_clk(__clk_get_hw(clk));
if (idx == tcc_clk->id)
return clk;
}
return NULL;
}
int of_clk_add_provider(struct device_node *np,
struct clk *(*clk_src_get)(struct of_phandle_args *clkspec,void *data),
void *data)
{
struct of_clk_provider *cp;
int ret;
cp = kzalloc(sizeof(*cp), GFP_KERNEL);
cp->node = of_node_get(np);
cp->data = data;
cp->get = clk_src_get;
list_add(&cp->link, &of_clk_providers);
ret = of_clk_set_defaults(np, true);
return ret;
}