* Tomi Valkeinen <tomi.valkeinen@xxxxxx> [140130 03:19]: > If CLK_SET_RATE_PARENT is set for a clkoutx2 clock, calling > clk_set_rate() on the clock "skips" the x2 multiplier as there are no > set_rate and round_rate functions defined for the clkoutx2. > > This results in getting double the requested clock rates, breaking the > display on omap3430 based devices. This got broken when > d0f58bd3bba3877fb1af4664c4e33273d36f00e4 and related patches were merged > for v3.14, as omapdss driver now relies more on the clk-framework and > CLK_SET_RATE_PARENT. > > This patch implements set_rate and round_rate for clkoutx2. > > Tested on OMAP3430, OMAP3630, OMAP4460. > > Signed-off-by: Tomi Valkeinen <tomi.valkeinen@xxxxxx> Would like to see acks from Paul and Tero on this too before applying. Tony > --- > arch/arm/mach-omap2/cclock3xxx_data.c | 2 + > arch/arm/mach-omap2/dpll3xxx.c | 92 +++++++++++++++++++++++++++++------ > include/linux/clk/ti.h | 4 ++ > 3 files changed, 83 insertions(+), 15 deletions(-) > > diff --git a/arch/arm/mach-omap2/cclock3xxx_data.c b/arch/arm/mach-omap2/cclock3xxx_data.c > index 3b05aea56d1f..11ed9152e665 100644 > --- a/arch/arm/mach-omap2/cclock3xxx_data.c > +++ b/arch/arm/mach-omap2/cclock3xxx_data.c > @@ -433,7 +433,9 @@ static const struct clk_ops dpll4_m5x2_ck_ops = { > .enable = &omap2_dflt_clk_enable, > .disable = &omap2_dflt_clk_disable, > .is_enabled = &omap2_dflt_clk_is_enabled, > + .set_rate = &omap3_clkoutx2_set_rate, > .recalc_rate = &omap3_clkoutx2_recalc, > + .round_rate = &omap3_clkoutx2_round_rate, > }; > > static const struct clk_ops dpll4_m5x2_ck_3630_ops = { > diff --git a/arch/arm/mach-omap2/dpll3xxx.c b/arch/arm/mach-omap2/dpll3xxx.c > index 3185ced807c9..3c418ea54bbe 100644 > --- a/arch/arm/mach-omap2/dpll3xxx.c > +++ b/arch/arm/mach-omap2/dpll3xxx.c > @@ -623,6 +623,32 @@ void omap3_dpll_deny_idle(struct clk_hw_omap *clk) > > /* Clock control for DPLL outputs */ > > +/* Find the parent DPLL for the given clkoutx2 clock */ > +static struct clk_hw_omap *omap3_find_clkoutx2_dpll(struct clk_hw *hw) > +{ > + struct clk_hw_omap *pclk = NULL; > + struct clk *parent; > + > + /* Walk up the parents of clk, looking for a DPLL */ > + do { > + do { > + parent = __clk_get_parent(hw->clk); > + hw = __clk_get_hw(parent); > + } while (hw && (__clk_get_flags(hw->clk) & CLK_IS_BASIC)); > + if (!hw) > + break; > + pclk = to_clk_hw_omap(hw); > + } while (pclk && !pclk->dpll_data); > + > + /* clk does not have a DPLL as a parent? error in the clock data */ > + if (!pclk) { > + WARN_ON(1); > + return NULL; > + } > + > + return pclk; > +} > + > /** > * omap3_clkoutx2_recalc - recalculate DPLL X2 output virtual clock rate > * @clk: DPLL output struct clk > @@ -637,27 +663,14 @@ unsigned long omap3_clkoutx2_recalc(struct clk_hw *hw, > unsigned long rate; > u32 v; > struct clk_hw_omap *pclk = NULL; > - struct clk *parent; > > if (!parent_rate) > return 0; > > - /* Walk up the parents of clk, looking for a DPLL */ > - do { > - do { > - parent = __clk_get_parent(hw->clk); > - hw = __clk_get_hw(parent); > - } while (hw && (__clk_get_flags(hw->clk) & CLK_IS_BASIC)); > - if (!hw) > - break; > - pclk = to_clk_hw_omap(hw); > - } while (pclk && !pclk->dpll_data); > + pclk = omap3_find_clkoutx2_dpll(hw); > > - /* clk does not have a DPLL as a parent? error in the clock data */ > - if (!pclk) { > - WARN_ON(1); > + if (!pclk) > return 0; > - } > > dd = pclk->dpll_data; > > @@ -672,6 +685,55 @@ unsigned long omap3_clkoutx2_recalc(struct clk_hw *hw, > return rate; > } > > +int omap3_clkoutx2_set_rate(struct clk_hw *hw, unsigned long rate, > + unsigned long parent_rate) > +{ > + return 0; > +} > + > +long omap3_clkoutx2_round_rate(struct clk_hw *hw, unsigned long rate, > + unsigned long *prate) > +{ > + const struct dpll_data *dd; > + u32 v; > + struct clk_hw_omap *pclk = NULL; > + > + if (!*prate) > + return 0; > + > + pclk = omap3_find_clkoutx2_dpll(hw); > + > + if (!pclk) > + return 0; > + > + dd = pclk->dpll_data; > + > + /* TYPE J does not have a clkoutx2 */ > + if (dd->flags & DPLL_J_TYPE) { > + *prate = __clk_round_rate(__clk_get_parent(pclk->hw.clk), rate); > + return *prate; > + } > + > + WARN_ON(!dd->enable_mask); > + > + v = omap2_clk_readl(pclk, dd->control_reg) & dd->enable_mask; > + v >>= __ffs(dd->enable_mask); > + > + /* If in bypass, the rate is fixed to the bypass rate*/ > + if (v != OMAP3XXX_EN_DPLL_LOCKED) > + return *prate; > + > + if (__clk_get_flags(hw->clk) & CLK_SET_RATE_PARENT) { > + unsigned long best_parent; > + > + best_parent = (rate / 2); > + *prate = __clk_round_rate(__clk_get_parent(hw->clk), > + best_parent); > + } > + > + return *prate * 2; > +} > + > /* OMAP3/4 non-CORE DPLL clkops */ > const struct clk_hw_omap_ops clkhwops_omap3_dpll = { > .allow_idle = omap3_dpll_allow_idle, > diff --git a/include/linux/clk/ti.h b/include/linux/clk/ti.h > index 092b64168d7f..4a21a872dbbd 100644 > --- a/include/linux/clk/ti.h > +++ b/include/linux/clk/ti.h > @@ -245,6 +245,10 @@ long omap2_dpll_round_rate(struct clk_hw *hw, unsigned long target_rate, > void omap2_init_clk_clkdm(struct clk_hw *clk); > unsigned long omap3_clkoutx2_recalc(struct clk_hw *hw, > unsigned long parent_rate); > +int omap3_clkoutx2_set_rate(struct clk_hw *hw, unsigned long rate, > + unsigned long parent_rate); > +long omap3_clkoutx2_round_rate(struct clk_hw *hw, unsigned long rate, > + unsigned long *prate); > int omap2_clkops_enable_clkdm(struct clk_hw *hw); > void omap2_clkops_disable_clkdm(struct clk_hw *hw); > int omap2_clk_disable_autoidle_all(void); > -- > 1.8.3.2 > -- To unsubscribe from this list: send the line "unsubscribe linux-omap" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html