Commit a9fa2893 authored by Paul Cercueil's avatar Paul Cercueil Committed by Stephen Boyd

clk: ingenic: Add support for divider tables

Some clocks provided on Ingenic SoCs have dividers, whose hardware value
as written in the register cannot be expressed as an affine function
to the actual divider value.

For instance, for the CPU clock on the JZ4770, the dividers are coded as
follows:

    ------------------
    | Bits     | Div |
    ------------------
    | 0  0  0  |  1  |
    | 0  0  1  |  2  |
    | 0  1  0  |  3  |
    | 0  1  1  |  4  |
    | 1  0  0  |  6  |
    | 1  0  1  |  8  |
    | 1  1  0  | 12  |
    ------------------

To support this setup, we introduce a new field in the
ingenic_cgu_div_info structure that allows to specify the divider table.
Signed-off-by: default avatarPaul Cercueil <paul@crapouillou.net>
Signed-off-by: default avatarStephen Boyd <sboyd@kernel.org>
parent a188339c
...@@ -384,8 +384,11 @@ ingenic_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) ...@@ -384,8 +384,11 @@ ingenic_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
div_reg = readl(cgu->base + clk_info->div.reg); div_reg = readl(cgu->base + clk_info->div.reg);
div = (div_reg >> clk_info->div.shift) & div = (div_reg >> clk_info->div.shift) &
GENMASK(clk_info->div.bits - 1, 0); GENMASK(clk_info->div.bits - 1, 0);
div += 1;
div *= clk_info->div.div; if (clk_info->div.div_table)
div = clk_info->div.div_table[div];
else
div = (div + 1) * clk_info->div.div;
rate /= div; rate /= div;
} else if (clk_info->type & CGU_CLK_FIXDIV) { } else if (clk_info->type & CGU_CLK_FIXDIV) {
...@@ -395,16 +398,37 @@ ingenic_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) ...@@ -395,16 +398,37 @@ ingenic_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
return rate; return rate;
} }
static unsigned int
ingenic_clk_calc_hw_div(const struct ingenic_cgu_clk_info *clk_info,
unsigned int div)
{
unsigned int i;
for (i = 0; i < (1 << clk_info->div.bits)
&& clk_info->div.div_table[i]; i++) {
if (clk_info->div.div_table[i] >= div)
return i;
}
return i - 1;
}
static unsigned static unsigned
ingenic_clk_calc_div(const struct ingenic_cgu_clk_info *clk_info, ingenic_clk_calc_div(const struct ingenic_cgu_clk_info *clk_info,
unsigned long parent_rate, unsigned long req_rate) unsigned long parent_rate, unsigned long req_rate)
{ {
unsigned div; unsigned int div, hw_div;
/* calculate the divide */ /* calculate the divide */
div = DIV_ROUND_UP(parent_rate, req_rate); div = DIV_ROUND_UP(parent_rate, req_rate);
/* and impose hardware constraints */ if (clk_info->div.div_table) {
hw_div = ingenic_clk_calc_hw_div(clk_info, div);
return clk_info->div.div_table[hw_div];
}
/* Impose hardware constraints */
div = min_t(unsigned, div, 1 << clk_info->div.bits); div = min_t(unsigned, div, 1 << clk_info->div.bits);
div = max_t(unsigned, div, 1); div = max_t(unsigned, div, 1);
...@@ -447,7 +471,7 @@ ingenic_clk_set_rate(struct clk_hw *hw, unsigned long req_rate, ...@@ -447,7 +471,7 @@ ingenic_clk_set_rate(struct clk_hw *hw, unsigned long req_rate,
const struct ingenic_cgu_clk_info *clk_info; const struct ingenic_cgu_clk_info *clk_info;
const unsigned timeout = 100; const unsigned timeout = 100;
unsigned long rate, flags; unsigned long rate, flags;
unsigned div, i; unsigned int hw_div, div, i;
u32 reg, mask; u32 reg, mask;
int ret = 0; int ret = 0;
...@@ -460,13 +484,18 @@ ingenic_clk_set_rate(struct clk_hw *hw, unsigned long req_rate, ...@@ -460,13 +484,18 @@ ingenic_clk_set_rate(struct clk_hw *hw, unsigned long req_rate,
if (rate != req_rate) if (rate != req_rate)
return -EINVAL; return -EINVAL;
if (clk_info->div.div_table)
hw_div = ingenic_clk_calc_hw_div(clk_info, div);
else
hw_div = ((div / clk_info->div.div) - 1);
spin_lock_irqsave(&cgu->lock, flags); spin_lock_irqsave(&cgu->lock, flags);
reg = readl(cgu->base + clk_info->div.reg); reg = readl(cgu->base + clk_info->div.reg);
/* update the divide */ /* update the divide */
mask = GENMASK(clk_info->div.bits - 1, 0); mask = GENMASK(clk_info->div.bits - 1, 0);
reg &= ~(mask << clk_info->div.shift); reg &= ~(mask << clk_info->div.shift);
reg |= ((div / clk_info->div.div) - 1) << clk_info->div.shift; reg |= hw_div << clk_info->div.shift;
/* clear the stop bit */ /* clear the stop bit */
if (clk_info->div.stop_bit != -1) if (clk_info->div.stop_bit != -1)
......
...@@ -88,6 +88,8 @@ struct ingenic_cgu_mux_info { ...@@ -88,6 +88,8 @@ struct ingenic_cgu_mux_info {
* isn't one * isn't one
* @busy_bit: the index of the busy bit within reg, or -1 if there isn't one * @busy_bit: the index of the busy bit within reg, or -1 if there isn't one
* @stop_bit: the index of the stop bit within reg, or -1 if there isn't one * @stop_bit: the index of the stop bit within reg, or -1 if there isn't one
* @div_table: optional table to map the value read from the register to the
* actual divider value
*/ */
struct ingenic_cgu_div_info { struct ingenic_cgu_div_info {
unsigned reg; unsigned reg;
...@@ -97,6 +99,7 @@ struct ingenic_cgu_div_info { ...@@ -97,6 +99,7 @@ struct ingenic_cgu_div_info {
s8 ce_bit; s8 ce_bit;
s8 busy_bit; s8 busy_bit;
s8 stop_bit; s8 stop_bit;
const u8 *div_table;
}; };
/** /**
......
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