Commit c6d67fb0 authored by Ulrich Hecht's avatar Ulrich Hecht Committed by Geert Uytterhoeven

clk: shmobile: div6: support selectable-input clocks

Support for setting the parent at initialization time based on the current
hardware configuration in DIV6 clocks with selectable parents as found in
the r8a73a4, r8a7740, sh73a0, and other SoCs.
Signed-off-by: default avatarUlrich Hecht <ulrich.hecht+renesas@gmail.com>
Signed-off-by: default avatarGeert Uytterhoeven <geert+renesas@glider.be>
parent 206c5f60
...@@ -32,6 +32,9 @@ struct div6_clock { ...@@ -32,6 +32,9 @@ struct div6_clock {
struct clk_hw hw; struct clk_hw hw;
void __iomem *reg; void __iomem *reg;
unsigned int div; unsigned int div;
u32 src_shift;
u32 src_width;
u8 *parents;
}; };
#define to_div6_clock(_hw) container_of(_hw, struct div6_clock, hw) #define to_div6_clock(_hw) container_of(_hw, struct div6_clock, hw)
...@@ -39,8 +42,11 @@ struct div6_clock { ...@@ -39,8 +42,11 @@ struct div6_clock {
static int cpg_div6_clock_enable(struct clk_hw *hw) static int cpg_div6_clock_enable(struct clk_hw *hw)
{ {
struct div6_clock *clock = to_div6_clock(hw); struct div6_clock *clock = to_div6_clock(hw);
u32 val;
clk_writel(CPG_DIV6_DIV(clock->div - 1), clock->reg); val = (clk_readl(clock->reg) & ~(CPG_DIV6_DIV_MASK | CPG_DIV6_CKSTP))
| CPG_DIV6_DIV(clock->div - 1);
clk_writel(val, clock->reg);
return 0; return 0;
} }
...@@ -52,7 +58,7 @@ static void cpg_div6_clock_disable(struct clk_hw *hw) ...@@ -52,7 +58,7 @@ static void cpg_div6_clock_disable(struct clk_hw *hw)
/* DIV6 clocks require the divisor field to be non-zero when stopping /* DIV6 clocks require the divisor field to be non-zero when stopping
* the clock. * the clock.
*/ */
clk_writel(CPG_DIV6_CKSTP | CPG_DIV6_DIV(CPG_DIV6_DIV_MASK), clk_writel(clk_readl(clock->reg) | CPG_DIV6_CKSTP | CPG_DIV6_DIV_MASK,
clock->reg); clock->reg);
} }
...@@ -94,12 +100,53 @@ static int cpg_div6_clock_set_rate(struct clk_hw *hw, unsigned long rate, ...@@ -94,12 +100,53 @@ static int cpg_div6_clock_set_rate(struct clk_hw *hw, unsigned long rate,
{ {
struct div6_clock *clock = to_div6_clock(hw); struct div6_clock *clock = to_div6_clock(hw);
unsigned int div = cpg_div6_clock_calc_div(rate, parent_rate); unsigned int div = cpg_div6_clock_calc_div(rate, parent_rate);
u32 val;
clock->div = div; clock->div = div;
val = clk_readl(clock->reg) & ~CPG_DIV6_DIV_MASK;
/* Only program the new divisor if the clock isn't stopped. */ /* Only program the new divisor if the clock isn't stopped. */
if (!(clk_readl(clock->reg) & CPG_DIV6_CKSTP)) if (!(val & CPG_DIV6_CKSTP))
clk_writel(CPG_DIV6_DIV(clock->div - 1), clock->reg); clk_writel(val | CPG_DIV6_DIV(clock->div - 1), clock->reg);
return 0;
}
static u8 cpg_div6_clock_get_parent(struct clk_hw *hw)
{
struct div6_clock *clock = to_div6_clock(hw);
unsigned int i;
u8 hw_index;
if (clock->src_width == 0)
return 0;
hw_index = (clk_readl(clock->reg) >> clock->src_shift) &
(BIT(clock->src_width) - 1);
for (i = 0; i < __clk_get_num_parents(hw->clk); i++) {
if (clock->parents[i] == hw_index)
return i;
}
pr_err("%s: %s DIV6 clock set to invalid parent %u\n",
__func__, __clk_get_name(hw->clk), hw_index);
return 0;
}
static int cpg_div6_clock_set_parent(struct clk_hw *hw, u8 index)
{
struct div6_clock *clock = to_div6_clock(hw);
u8 hw_index;
u32 mask;
if (index >= __clk_get_num_parents(hw->clk))
return -EINVAL;
mask = ~((BIT(clock->src_width) - 1) << clock->src_shift);
hw_index = clock->parents[index];
clk_writel((clk_readl(clock->reg) & mask) |
(hw_index << clock->src_shift), clock->reg);
return 0; return 0;
} }
...@@ -108,6 +155,8 @@ static const struct clk_ops cpg_div6_clock_ops = { ...@@ -108,6 +155,8 @@ static const struct clk_ops cpg_div6_clock_ops = {
.enable = cpg_div6_clock_enable, .enable = cpg_div6_clock_enable,
.disable = cpg_div6_clock_disable, .disable = cpg_div6_clock_disable,
.is_enabled = cpg_div6_clock_is_enabled, .is_enabled = cpg_div6_clock_is_enabled,
.get_parent = cpg_div6_clock_get_parent,
.set_parent = cpg_div6_clock_set_parent,
.recalc_rate = cpg_div6_clock_recalc_rate, .recalc_rate = cpg_div6_clock_recalc_rate,
.round_rate = cpg_div6_clock_round_rate, .round_rate = cpg_div6_clock_round_rate,
.set_rate = cpg_div6_clock_set_rate, .set_rate = cpg_div6_clock_set_rate,
...@@ -115,20 +164,33 @@ static const struct clk_ops cpg_div6_clock_ops = { ...@@ -115,20 +164,33 @@ static const struct clk_ops cpg_div6_clock_ops = {
static void __init cpg_div6_clock_init(struct device_node *np) static void __init cpg_div6_clock_init(struct device_node *np)
{ {
unsigned int num_parents, valid_parents;
const char **parent_names;
struct clk_init_data init; struct clk_init_data init;
struct div6_clock *clock; struct div6_clock *clock;
const char *parent_name;
const char *name; const char *name;
struct clk *clk; struct clk *clk;
unsigned int i;
int ret; int ret;
clock = kzalloc(sizeof(*clock), GFP_KERNEL); clock = kzalloc(sizeof(*clock), GFP_KERNEL);
if (!clock) { if (!clock)
pr_err("%s: failed to allocate %s DIV6 clock\n", return;
num_parents = of_clk_get_parent_count(np);
if (num_parents < 1) {
pr_err("%s: no parent found for %s DIV6 clock\n",
__func__, np->name); __func__, np->name);
return; return;
} }
clock->parents = kmalloc_array(num_parents, sizeof(*clock->parents),
GFP_KERNEL);
parent_names = kmalloc_array(num_parents, sizeof(*parent_names),
GFP_KERNEL);
if (!parent_names)
return;
/* Remap the clock register and read the divisor. Disabling the /* Remap the clock register and read the divisor. Disabling the
* clock overwrites the divisor, so we need to cache its value for the * clock overwrites the divisor, so we need to cache its value for the
* enable operation. * enable operation.
...@@ -150,9 +212,34 @@ static void __init cpg_div6_clock_init(struct device_node *np) ...@@ -150,9 +212,34 @@ static void __init cpg_div6_clock_init(struct device_node *np)
goto error; goto error;
} }
parent_name = of_clk_get_parent_name(np, 0);
if (parent_name == NULL) { for (i = 0, valid_parents = 0; i < num_parents; i++) {
pr_err("%s: failed to get %s DIV6 clock parent name\n", const char *name = of_clk_get_parent_name(np, i);
if (name) {
parent_names[valid_parents] = name;
clock->parents[valid_parents] = i;
valid_parents++;
}
}
switch (num_parents) {
case 1:
/* fixed parent clock */
clock->src_shift = clock->src_width = 0;
break;
case 4:
/* clock with EXSRC bits 6-7 */
clock->src_shift = 6;
clock->src_width = 2;
break;
case 8:
/* VCLK with EXSRC bits 12-14 */
clock->src_shift = 12;
clock->src_width = 3;
break;
default:
pr_err("%s: invalid number of parents for DIV6 clock %s\n",
__func__, np->name); __func__, np->name);
goto error; goto error;
} }
...@@ -161,8 +248,8 @@ static void __init cpg_div6_clock_init(struct device_node *np) ...@@ -161,8 +248,8 @@ static void __init cpg_div6_clock_init(struct device_node *np)
init.name = name; init.name = name;
init.ops = &cpg_div6_clock_ops; init.ops = &cpg_div6_clock_ops;
init.flags = CLK_IS_BASIC; init.flags = CLK_IS_BASIC;
init.parent_names = &parent_name; init.parent_names = parent_names;
init.num_parents = 1; init.num_parents = valid_parents;
clock->hw.init = &init; clock->hw.init = &init;
...@@ -175,11 +262,13 @@ static void __init cpg_div6_clock_init(struct device_node *np) ...@@ -175,11 +262,13 @@ static void __init cpg_div6_clock_init(struct device_node *np)
of_clk_add_provider(np, of_clk_src_simple_get, clk); of_clk_add_provider(np, of_clk_src_simple_get, clk);
kfree(parent_names);
return; return;
error: error:
if (clock->reg) if (clock->reg)
iounmap(clock->reg); iounmap(clock->reg);
kfree(parent_names);
kfree(clock); kfree(clock);
} }
CLK_OF_DECLARE(cpg_div6_clk, "renesas,cpg-div6-clock", cpg_div6_clock_init); CLK_OF_DECLARE(cpg_div6_clk, "renesas,cpg-div6-clock", cpg_div6_clock_init);
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment