Allwinner clock changes for 4.16

Fixed-post-divider support is added for two types of our clocks.
 This in turn is used to support odd clocks on some of our SoCs.
 On the A64, the MMC module clocks have a hidden post-divider.
 On the A83T, a post-divider is needed to support programming
 sigma-delta modulation with known fixed parameters from the
 BSP kernel for the audio PLL.
 
 There's also a small cleanup to use the shorter PTR_ERR_OR_ZERO
 macro in various functions.
 -----BEGIN PGP SIGNATURE-----
 
 iQJCBAABCgAsFiEE2nN1m/hhnkhOWjtHOJpUIZwPJDAFAlpEkeEOHHdlbnNAY3Np
 ZS5vcmcACgkQOJpUIZwPJDBpwhAAlWd2kbzI/lLevi69wXOsVH2keWfnD8Pr8DxJ
 4aONajdldN9RfCCaeJtYhqCDW5rEvAJH88msVH0IYwy8NbeWSPAxDyQX5WW3Ugj8
 UUmm+fWcoTY9ePJ3lFDgx2LzI7zyolzsSuqk93lVZi8fi1xUYQ4e/Z7fHj9ONNXD
 1xjPmHU1SXwtrufJ2qIE35K2zCmCnlgIITO8uGPwppg03sLfkSnOLvrCzYhlKv1r
 xqfncyLQdg/XOONV9QYDtn3b2M9A+CYqV6eojqbZFFFbOkqqaSyfGJ8T7o/nEd4n
 k+QmDwg5AqQadWPPScddw+AIeyDD2FhMFhWu7+3A09iMHom+BrMm5QRChRBDwnmI
 sFoLoMmfjCjVvrvZI/R4tGfKyAJFyQ6fEwz6yuCNt8DWwfkE1yrOmK8uZUiQMZeq
 0R1AOVbTzM5a0X35OWJK5wV5Pa1rwu4bSup9QVYYzIjYW4ZWVVaQBYONmuUlVPhI
 lavgvD+C5BoiW2QFr54yJrDwTPifuh9k5I6rKMCUSbktmgE/3xuRX04RuGvyFO30
 vJP2QxA2D+0I6ItqAAz7CaGzb2QOB4eBk0ayXNT5Sjcp8dU1pClVZtPVUCpgpJ/F
 2/CzPqr1qnUPJJ+W6hy1AXl6mVah5eAIlmRDAwVDrHlYrNVNtU1AbxQrZaymAu87
 3ntffVk=
 =IevL
 -----END PGP SIGNATURE-----

Merge tag 'sunxi-clk-for-4.16' of https://git.kernel.org/pub/scm/linux/kernel/git/sunxi/linux into clk-allwinner

Pull Allwinner clock changes from Chen-Yu Tsai:

 "Fixed-post-divider support is added for two types of our clocks.
  This in turn is used to support odd clocks on some of our SoCs.
  On the A64, the MMC module clocks have a hidden post-divider.
  On the A83T, a post-divider is needed to support programming
  sigma-delta modulation with known fixed parameters from the
  BSP kernel for the audio PLL.

  There's also a small cleanup to use the shorter PTR_ERR_OR_ZERO
  macro in various functions."

* tag 'sunxi-clk-for-4.16' of https://git.kernel.org/pub/scm/linux/kernel/git/sunxi/linux:
  clk: sunxi-ng: sun8i: a83t: Use sigma-delta modulation for audio PLL
  clk: sunxi-ng: sun8i: a83t: Add /2 fixed post divider to audio PLL
  clk: sunxi-ng: Support fixed post-dividers on NM style clocks
  clk: sunxi-ng: sun50i: a64: Add 2x fixed post-divider to MMC module clocks
  clk: sunxi-ng: Support fixed post-dividers on MP style clocks
  clk: sunxi: Use PTR_ERR_OR_ZERO()
This commit is contained in:
Stephen Boyd 2017-12-28 10:46:56 -08:00
commit d96f2cf937
7 changed files with 133 additions and 41 deletions

View File

@ -400,28 +400,45 @@ static SUNXI_CCU_MP_WITH_MUX_GATE(nand_clk, "nand", mod0_default_parents, 0x080,
BIT(31), /* gate */
0);
/*
* MMC clocks are the new timing mode (see A83T & H3) variety, but without
* the mode switch. This means they have a 2x post divider between the clock
* and the MMC module. This is not documented in the manual, but is taken
* into consideration when setting the mmc module clocks in the BSP kernel.
* Without it, MMC performance is degraded.
*
* We model it here to be consistent with other SoCs supporting this mode.
* The alternative would be to add the 2x multiplier when setting the MMC
* module clock in the MMC driver, just for the A64.
*/
static const char * const mmc_default_parents[] = { "osc24M", "pll-periph0-2x",
"pll-periph1-2x" };
static SUNXI_CCU_MP_WITH_MUX_GATE(mmc0_clk, "mmc0", mmc_default_parents, 0x088,
0, 4, /* M */
16, 2, /* P */
24, 2, /* mux */
BIT(31), /* gate */
0);
static SUNXI_CCU_MP_WITH_MUX_GATE_POSTDIV(mmc0_clk, "mmc0",
mmc_default_parents, 0x088,
0, 4, /* M */
16, 2, /* P */
24, 2, /* mux */
BIT(31), /* gate */
2, /* post-div */
0);
static SUNXI_CCU_MP_WITH_MUX_GATE(mmc1_clk, "mmc1", mmc_default_parents, 0x08c,
0, 4, /* M */
16, 2, /* P */
24, 2, /* mux */
BIT(31), /* gate */
0);
static SUNXI_CCU_MP_WITH_MUX_GATE_POSTDIV(mmc1_clk, "mmc1",
mmc_default_parents, 0x08c,
0, 4, /* M */
16, 2, /* P */
24, 2, /* mux */
BIT(31), /* gate */
2, /* post-div */
0);
static SUNXI_CCU_MP_WITH_MUX_GATE(mmc2_clk, "mmc2", mmc_default_parents, 0x090,
0, 4, /* M */
16, 2, /* P */
24, 2, /* mux */
BIT(31), /* gate */
0);
static SUNXI_CCU_MP_WITH_MUX_GATE_POSTDIV(mmc2_clk, "mmc2",
mmc_default_parents, 0x090,
0, 4, /* M */
16, 2, /* P */
24, 2, /* mux */
BIT(31), /* gate */
2, /* post-div */
0);
static const char * const ts_parents[] = { "osc24M", "pll-periph0", };
static SUNXI_CCU_MP_WITH_MUX_GATE(ts_clk, "ts", ts_parents, 0x098,

View File

@ -76,15 +76,26 @@ static struct ccu_mult pll_c1cpux_clk = {
*/
#define SUN8I_A83T_PLL_AUDIO_REG 0x008
/* clock rates doubled for post divider */
static struct ccu_sdm_setting pll_audio_sdm_table[] = {
{ .rate = 45158400, .pattern = 0xc00121ff, .m = 29, .n = 54 },
{ .rate = 49152000, .pattern = 0xc000e147, .m = 30, .n = 61 },
};
static struct ccu_nm pll_audio_clk = {
.enable = BIT(31),
.lock = BIT(2),
.n = _SUNXI_CCU_MULT_OFFSET_MIN_MAX(8, 8, 0, 12, 0),
.m = _SUNXI_CCU_DIV(0, 6),
.fixed_post_div = 2,
.sdm = _SUNXI_CCU_SDM(pll_audio_sdm_table, BIT(24),
0x284, BIT(31)),
.common = {
.reg = SUN8I_A83T_PLL_AUDIO_REG,
.lock_reg = CCU_SUN8I_A83T_LOCK_REG,
.features = CCU_FEATURE_LOCK_REG,
.features = CCU_FEATURE_LOCK_REG |
CCU_FEATURE_FIXED_POSTDIV |
CCU_FEATURE_SIGMA_DELTA_MOD,
.hw.init = CLK_HW_INIT("pll-audio", "osc24M",
&ccu_nm_ops, CLK_SET_RATE_UNGATE),
},
@ -889,9 +900,10 @@ static int sun8i_a83t_ccu_probe(struct platform_device *pdev)
if (IS_ERR(reg))
return PTR_ERR(reg);
/* Enforce d1 = 0, d2 = 0 for Audio PLL */
/* Enforce d1 = 0, d2 = 1 for Audio PLL */
val = readl(reg + SUN8I_A83T_PLL_AUDIO_REG);
val &= ~(BIT(16) | BIT(18));
val &= ~BIT(16);
val |= BIT(18);
writel(val, reg + SUN8I_A83T_PLL_AUDIO_REG);
/* Enforce P = 1 for both CPU cluster PLLs */

View File

@ -50,12 +50,19 @@ static unsigned long ccu_mp_round_rate(struct ccu_mux_internal *mux,
unsigned int max_m, max_p;
unsigned int m, p;
if (cmp->common.features & CCU_FEATURE_FIXED_POSTDIV)
rate *= cmp->fixed_post_div;
max_m = cmp->m.max ?: 1 << cmp->m.width;
max_p = cmp->p.max ?: 1 << ((1 << cmp->p.width) - 1);
ccu_mp_find_best(*parent_rate, rate, max_m, max_p, &m, &p);
rate = *parent_rate / p / m;
return *parent_rate / p / m;
if (cmp->common.features & CCU_FEATURE_FIXED_POSTDIV)
rate /= cmp->fixed_post_div;
return rate;
}
static void ccu_mp_disable(struct clk_hw *hw)
@ -83,6 +90,7 @@ static unsigned long ccu_mp_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct ccu_mp *cmp = hw_to_ccu_mp(hw);
unsigned long rate;
unsigned int m, p;
u32 reg;
@ -101,7 +109,11 @@ static unsigned long ccu_mp_recalc_rate(struct clk_hw *hw,
p = reg >> cmp->p.shift;
p &= (1 << cmp->p.width) - 1;
return (parent_rate >> p) / m;
rate = (parent_rate >> p) / m;
if (cmp->common.features & CCU_FEATURE_FIXED_POSTDIV)
rate /= cmp->fixed_post_div;
return rate;
}
static int ccu_mp_determine_rate(struct clk_hw *hw,
@ -129,6 +141,10 @@ static int ccu_mp_set_rate(struct clk_hw *hw, unsigned long rate,
max_m = cmp->m.max ?: 1 << cmp->m.width;
max_p = cmp->p.max ?: 1 << ((1 << cmp->p.width) - 1);
/* Adjust target rate according to post-dividers */
if (cmp->common.features & CCU_FEATURE_FIXED_POSTDIV)
rate = rate * cmp->fixed_post_div;
ccu_mp_find_best(parent_rate, rate, max_m, max_p, &m, &p);
spin_lock_irqsave(cmp->common.lock, flags);

View File

@ -33,9 +33,33 @@ struct ccu_mp {
struct ccu_div_internal m;
struct ccu_div_internal p;
struct ccu_mux_internal mux;
unsigned int fixed_post_div;
struct ccu_common common;
};
#define SUNXI_CCU_MP_WITH_MUX_GATE_POSTDIV(_struct, _name, _parents, _reg, \
_mshift, _mwidth, \
_pshift, _pwidth, \
_muxshift, _muxwidth, \
_gate, _postdiv, _flags) \
struct ccu_mp _struct = { \
.enable = _gate, \
.m = _SUNXI_CCU_DIV(_mshift, _mwidth), \
.p = _SUNXI_CCU_DIV(_pshift, _pwidth), \
.mux = _SUNXI_CCU_MUX(_muxshift, _muxwidth), \
.fixed_post_div = _postdiv, \
.common = { \
.reg = _reg, \
.features = CCU_FEATURE_FIXED_POSTDIV, \
.hw.init = CLK_HW_INIT_PARENTS(_name, \
_parents, \
&ccu_mp_ops, \
_flags), \
} \
}
#define SUNXI_CCU_MP_WITH_MUX_GATE(_struct, _name, _parents, _reg, \
_mshift, _mwidth, \
_pshift, _pwidth, \

View File

@ -70,11 +70,18 @@ static unsigned long ccu_nm_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct ccu_nm *nm = hw_to_ccu_nm(hw);
unsigned long rate;
unsigned long n, m;
u32 reg;
if (ccu_frac_helper_is_enabled(&nm->common, &nm->frac))
return ccu_frac_helper_read_rate(&nm->common, &nm->frac);
if (ccu_frac_helper_is_enabled(&nm->common, &nm->frac)) {
rate = ccu_frac_helper_read_rate(&nm->common, &nm->frac);
if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV)
rate /= nm->fixed_post_div;
return rate;
}
reg = readl(nm->common.base + nm->common.reg);
@ -90,15 +97,15 @@ static unsigned long ccu_nm_recalc_rate(struct clk_hw *hw,
if (!m)
m++;
if (ccu_sdm_helper_is_enabled(&nm->common, &nm->sdm)) {
unsigned long rate =
ccu_sdm_helper_read_rate(&nm->common, &nm->sdm,
m, n);
if (rate)
return rate;
}
if (ccu_sdm_helper_is_enabled(&nm->common, &nm->sdm))
rate = ccu_sdm_helper_read_rate(&nm->common, &nm->sdm, m, n);
else
rate = parent_rate * n / m;
return parent_rate * n / m;
if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV)
rate /= nm->fixed_post_div;
return rate;
}
static long ccu_nm_round_rate(struct clk_hw *hw, unsigned long rate,
@ -107,11 +114,20 @@ static long ccu_nm_round_rate(struct clk_hw *hw, unsigned long rate,
struct ccu_nm *nm = hw_to_ccu_nm(hw);
struct _ccu_nm _nm;
if (ccu_frac_helper_has_rate(&nm->common, &nm->frac, rate))
return rate;
if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV)
rate *= nm->fixed_post_div;
if (ccu_sdm_helper_has_rate(&nm->common, &nm->sdm, rate))
if (ccu_frac_helper_has_rate(&nm->common, &nm->frac, rate)) {
if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV)
rate /= nm->fixed_post_div;
return rate;
}
if (ccu_sdm_helper_has_rate(&nm->common, &nm->sdm, rate)) {
if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV)
rate /= nm->fixed_post_div;
return rate;
}
_nm.min_n = nm->n.min ?: 1;
_nm.max_n = nm->n.max ?: 1 << nm->n.width;
@ -119,8 +135,12 @@ static long ccu_nm_round_rate(struct clk_hw *hw, unsigned long rate,
_nm.max_m = nm->m.max ?: 1 << nm->m.width;
ccu_nm_find_best(*parent_rate, rate, &_nm);
rate = *parent_rate * _nm.n / _nm.m;
return *parent_rate * _nm.n / _nm.m;
if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV)
rate /= nm->fixed_post_div;
return rate;
}
static int ccu_nm_set_rate(struct clk_hw *hw, unsigned long rate,
@ -131,6 +151,10 @@ static int ccu_nm_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long flags;
u32 reg;
/* Adjust target rate according to post-dividers */
if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV)
rate = rate * nm->fixed_post_div;
if (ccu_frac_helper_has_rate(&nm->common, &nm->frac, rate)) {
spin_lock_irqsave(nm->common.lock, flags);

View File

@ -36,6 +36,8 @@ struct ccu_nm {
struct ccu_frac_internal frac;
struct ccu_sdm_internal sdm;
unsigned int fixed_post_div;
struct ccu_common common;
};

View File

@ -98,10 +98,7 @@ static int sun8i_a23_apb0_clk_probe(struct platform_device *pdev)
return PTR_ERR(reg);
clk = sun8i_a23_apb0_register(np, reg);
if (IS_ERR(clk))
return PTR_ERR(clk);
return 0;
return PTR_ERR_OR_ZERO(clk);
}
static const struct of_device_id sun8i_a23_apb0_clk_dt_ids[] = {