[PATCH] clk: at91: add audio pll clock driver
Michael Turquette
mturquette at baylibre.com
Thu Aug 27 16:58:49 PDT 2015
Quoting Boris Brezillon (2015-08-27 02:30:35)
> Hi Nicolas,
>
> On Fri, 31 Jul 2015 12:17:44 +0200
> Nicolas Ferre <nicolas.ferre at atmel.com> wrote:
>
> > This new clock driver set allows to have a fractional divided clock
> > that would generate a precise clock particularly suitable for
> > audio applications.
> >
> > The main audio pll clock has two children clocks: one that is connected
> > to the PMC, the other that can directly drive a pad. As these two routes
> > have different enable bits and different dividers and divider formula,
> > they are handled by two different drivers.
> > Each of them would modify the rate of the main audio pll parent.
>
> Hm, not sure allowing both children to modify the parent clk rate is
> a good idea, but let's see how it works.
Might be a good idea to use clk_set_rate_range? Of course most audio use
cases need exact rates...
Regards,
Mike
>
> >
> > Signed-off-by: Nicolas Ferre <nicolas.ferre at atmel.com>
> > ---
> > .../devicetree/bindings/clock/at91-clock.txt | 38 +++
> > drivers/clk/at91/Makefile | 2 +
> > drivers/clk/at91/clk-audio-pll-pad.c | 220 +++++++++++++++++
> > drivers/clk/at91/clk-audio-pll-pmc.c | 171 ++++++++++++++
> > drivers/clk/at91/clk-audio-pll.c | 261 +++++++++++++++++++++
> > drivers/clk/at91/pmc.c | 14 ++
> > drivers/clk/at91/pmc.h | 7 +
> > include/linux/clk/at91_pmc.h | 25 ++
> > 8 files changed, 738 insertions(+)
> > create mode 100644 drivers/clk/at91/clk-audio-pll-pad.c
> > create mode 100644 drivers/clk/at91/clk-audio-pll-pmc.c
> > create mode 100644 drivers/clk/at91/clk-audio-pll.c
> >
>
> [...]
>
> > +
> > +static int clk_audio_pll_compute_qdpad(unsigned long q_rate, unsigned long rate,
> > + unsigned long *qd, u8 *div, u8 *ext_div)
> > +{
> > + unsigned long tmp_qd;
> > + unsigned long rem2, rem3;
> > + unsigned long ldiv, lext_div;
> > +
> > + if (!rate)
> > + return -EINVAL;
> > +
> > + tmp_qd = q_rate / rate;
> > + if (!tmp_qd || tmp_qd > AUDIO_PLL_QDPAD_MAX)
> > + return -EINVAL;
>
> Do you really want to return an error in this case?
> I mean, you're calling this function from ->round_rate(), and
> ->round_rate() is supposed to find the closest rate, not to return an
> error if the rate is too low or to high.
> ITOH, you're calling the same function from ->set_rate(), which is
> supposed to complain if the requested rate is not exactly met.
>
> Maybe you can deal with that by passing an additional argument to this
> function. Something like:
>
> tmp_qd = q_rate / rate;
>
> if (!strict) {
> if (!tmp_qd)
> tmp_qd = 1;
> else if (tmp_qd > AUDIO_PLL_QDPAD_MAX)
> tmp_qd = AUDIO_PLL_QDPAD_MAX;
> }
>
> if (!tmp_qd || tmp_qd > AUDIO_PLL_QDPAD_MAX)
> return -EINVAL;
>
> > +
> > + if (tmp_qd <= AT91_PMC_AUDIO_PLL_QDPAD_EXTDIV_MAX) {
> > + ldiv = 1;
> > + lext_div = tmp_qd;
> > + } else {
> > + rem2 = tmp_qd % 2;
> > + rem3 = tmp_qd % 3;
> > +
> > + if (rem3 == 0 ||
> > + tmp_qd > AT91_PMC_AUDIO_PLL_QDPAD_EXTDIV_MAX * 2 ||
> > + rem3 < rem2) {
> > + ldiv = 3;
> > + lext_div = tmp_qd / 3;
> > + } else {
> > + ldiv = 2;
> > + lext_div = tmp_qd >> 1;
> > + }
> > + }
> > +
> > + pr_debug("A PLL/PAD: %s, qd = %lu (div = %lu, ext_div = %lu)\n",
> > + __func__, ldiv * lext_div, ldiv, lext_div);
> > +
> > + /* if we were given variable to store, we can provide them */
> > + if (qd)
> > + *qd = ldiv * lext_div;
> > + if (div && ext_div) {
> > + /* we can cast here as we verified the bounds just above */
> > + *div = (u8)ldiv;
> > + *ext_div = (u8)lext_div;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static long clk_audio_pll_pad_round_rate(struct clk_hw *hw, unsigned long rate,
> > + unsigned long *parent_rate)
>
> I thought we were trying to get rid of the ->round_rate() function in
> favor of the ->determine_rate() one (which is more flexible), but maybe
> I'm wrong. Stephen, Mike, what's your opinion?
>
> > +{
> > + struct clk *pclk = __clk_get_parent(hw->clk);
> > + long best_rate = -EINVAL;
> > + unsigned long best_parent_rate = 0;
> > + unsigned long tmp_qd;
> > +
> > + pr_debug("A PLL/PAD: %s, rate = %lu (parent_rate = %lu)\n",
> > + __func__, rate, *parent_rate);
> > +
> > + if (clk_audio_pll_compute_qdpad(AUDIO_PLL_REFERENCE_FOUT, rate,
> > + &tmp_qd, NULL, NULL))
> > + return -EINVAL;
>
> You're calculating your divisor based on the AUDIO_PLL_REFERENCE_FOUT
> value...
>
> > +
> > + best_parent_rate = __clk_round_rate(pclk, rate * tmp_qd);
>
> ... and then asking your parent to adapt its rate based on the returned
> divisor. Wouldn't it be preferable to search for the best parent rate
> when choosing the divisor?
>
> > + best_rate = best_parent_rate / tmp_qd;
> > +
> > + pr_debug("A PLL/PAD: %s, best_rate = %ld, best_parent_rate = %lu\n",
> > + __func__, best_rate, best_parent_rate);
> > +
> > + *parent_rate = best_parent_rate;
> > + return best_rate;
> > +}
>
> [...]
>
> > +
> > +static long clk_audio_pll_pmc_round_rate(struct clk_hw *hw, unsigned long rate,
> > + unsigned long *parent_rate)
> > +{
> > + struct clk *pclk = __clk_get_parent(hw->clk);
> > + long best_rate = -EINVAL;
> > + unsigned long best_parent_rate = 0;
> > + unsigned long tmp_qd;
> > +
> > + pr_debug("A PLL/PMC: %s, rate = %lu (parent_rate = %lu)\n",
> > + __func__, rate, *parent_rate);
> > +
> > + if (clk_audio_pll_compute_qdpmc(AUDIO_PLL_REFERENCE_FOUT, rate, &tmp_qd))
> > + return -EINVAL;
> > +
> > + best_parent_rate = __clk_round_rate(pclk, rate * tmp_qd);
>
> Ditto.
>
> BTW, I'm not sure this is safe to allow both pll-audio children to
> change their parent rate. What will happen if one of them change the
> pll-audio rate while the other is still using it?
>
> > + best_rate = best_parent_rate / tmp_qd;
> > +
> > + pr_debug("A PLL/PMC: %s, best_rate = %ld, best_parent_rate = %lu (qd = %lu)\n",
> > + __func__, best_rate, best_parent_rate, tmp_qd - 1);
> > +
> > + *parent_rate = best_parent_rate;
> > + return best_rate;
> > +}
>
> [...]
>
> > +
> > +#define to_clk_audio_frac(hw) container_of(hw, struct clk_audio_frac, hw)
> > +
> > +/* make sure that pll is in reset state beforehand */
> > +static int clk_audio_pll_prepare(struct clk_hw *hw)
> > +{
> > + struct clk_audio_frac *fck = to_clk_audio_frac(hw);
> > + struct at91_pmc *pmc = fck->pmc;
> > + u32 tmp;
> > +
> > + pmc_lock(pmc);
> > + tmp = pmc_read(pmc, AT91_PMC_AUDIO_PLL0) & ~AT91_PMC_AUDIO_PLL_RESETN;
> > + pmc_write(pmc, AT91_PMC_AUDIO_PLL0, tmp);
> > + pmc_unlock(pmc);
>
> Nothing sleeps in this code, so you could move everything in your
> ->enable() function.
>
> > + return 0;
> > +}
> > +
> > +static void clk_audio_pll_unprepare(struct clk_hw *hw)
> > +{
> > + clk_audio_pll_prepare(hw);
>
> Ditto.
>
> Best Regards,
>
> Boris
>
> --
> Boris Brezillon, Free Electrons
> Embedded Linux and Kernel engineering
> http://free-electrons.com
> --
> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> the body of a message to majordomo at vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at http://www.tux.org/lkml/
More information about the linux-arm-kernel
mailing list