Hi,
This series follows previous improvements for the other Allwinner SoCs to
improve audio quality, in particular the speed and pitch of audio playback.
The audio PLLs in Allwinner SoCs cannot produce the correct frequency to
match the audio sample rate families through integer factors. As such
the audio is either too fast or too flow.
This is dealt with by using sigma-delta modulation, a form of fractional-N
synthesis, on the divider. The parameters used are copied from the BSP
kernel.
As these parameters assume that one of the untracked dividers is set to
2, we first add a fixed /2 post divider to the PLL, and force the
untracked divider to /2. Then we introduce the sigma-delta modulation
parameters.
This has been tested with SPDIF playback on the Cubietruck Plus, and an
external PCM5122 DAC from a PiFi DAC 2.0+, connected via I2S to the
Banana Pi M3. The I2C and I2S support used in this latter test will be
sent as a separate series later.
Please have a look.
Regards
ChenYu
Chen-Yu Tsai (3):
clk: sunxi-ng: Support fixed post-dividers on NM style clocks
clk: sunxi-ng: sun8i: a83t: Add /2 fixed post divider to audio PLL
clk: sunxi-ng: sun8i: a83t: Use sigma-delta modulation for audio PLL
drivers/clk/sunxi-ng/ccu-sun8i-a83t.c | 18 ++++++++++---
drivers/clk/sunxi-ng/ccu_nm.c | 50 ++++++++++++++++++++++++++---------
drivers/clk/sunxi-ng/ccu_nm.h | 2 ++
3 files changed, 54 insertions(+), 16 deletions(-)
--
2.15.0
The audio blocks require specific clock rates. Until now we were using
the closest clock rate possible with integer N-M factors. This resulted
in audio playback being slightly slower than it should be.
The vendor kernel gets around this (for newer SoCs) by using sigma-delta
modulation to generate a fractional-N factor. This patch copies the
parameters for the A83T.
Signed-off-by: Chen-Yu Tsai <[email protected]>
---
drivers/clk/sunxi-ng/ccu-sun8i-a83t.c | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/drivers/clk/sunxi-ng/ccu-sun8i-a83t.c b/drivers/clk/sunxi-ng/ccu-sun8i-a83t.c
index 06b69e433d0f..04a9c33f53f0 100644
--- a/drivers/clk/sunxi-ng/ccu-sun8i-a83t.c
+++ b/drivers/clk/sunxi-ng/ccu-sun8i-a83t.c
@@ -76,17 +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 |
- CCU_FEATURE_FIXED_POSTDIV,
+ CCU_FEATURE_FIXED_POSTDIV |
+ CCU_FEATURE_SIGMA_DELTA_MOD,
.hw.init = CLK_HW_INIT("pll-audio", "osc24M",
&ccu_nm_ops, CLK_SET_RATE_UNGATE),
},
--
2.15.0
On the A83T, the audio PLL should have its div1 set to 0, or /1, and
div2 set to 1, or /2. This setting is the default, and is required
to match the sigma-delta modulation parameters from the BSP kernel.
This patch adds a /2 fixed post divider to the audio PLL, and fixes
the enforced d1 & d2 values. This also resolves the mismatch between
the values mentioned in the comment for the audio PLL, and the actual
enforced values.
Signed-off-by: Chen-Yu Tsai <[email protected]>
---
drivers/clk/sunxi-ng/ccu-sun8i-a83t.c | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/drivers/clk/sunxi-ng/ccu-sun8i-a83t.c b/drivers/clk/sunxi-ng/ccu-sun8i-a83t.c
index 5cedcd0d8be8..06b69e433d0f 100644
--- a/drivers/clk/sunxi-ng/ccu-sun8i-a83t.c
+++ b/drivers/clk/sunxi-ng/ccu-sun8i-a83t.c
@@ -81,10 +81,12 @@ static struct ccu_nm pll_audio_clk = {
.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,
.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,
.hw.init = CLK_HW_INIT("pll-audio", "osc24M",
&ccu_nm_ops, CLK_SET_RATE_UNGATE),
},
@@ -889,9 +891,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 */
--
2.15.0
On the A83T, the audio PLL should have its div1 set to 0, or /1, and
div2 set to 1, or /2. This setting is the default, and is required
to match the sigma-delta modulation parameters from the BSP kernel.
To do this, we first add fixed post-divider to the NM style clocks,
which is the type of clock the audio PLL clock is modeled into.
Signed-off-by: Chen-Yu Tsai <[email protected]>
---
drivers/clk/sunxi-ng/ccu_nm.c | 50 ++++++++++++++++++++++++++++++++-----------
drivers/clk/sunxi-ng/ccu_nm.h | 2 ++
2 files changed, 39 insertions(+), 13 deletions(-)
diff --git a/drivers/clk/sunxi-ng/ccu_nm.c b/drivers/clk/sunxi-ng/ccu_nm.c
index 7620aa973a6e..a16de092bf94 100644
--- a/drivers/clk/sunxi-ng/ccu_nm.c
+++ b/drivers/clk/sunxi-ng/ccu_nm.c
@@ -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;
+
+ if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV)
+ rate /= nm->fixed_post_div;
- return parent_rate * n / m;
+ 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))
+ if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV)
+ rate *= nm->fixed_post_div;
+
+ 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 (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;
+
+ if (nm->common.features & CCU_FEATURE_FIXED_POSTDIV)
+ rate /= nm->fixed_post_div;
- return *parent_rate * _nm.n / _nm.m;
+ 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);
diff --git a/drivers/clk/sunxi-ng/ccu_nm.h b/drivers/clk/sunxi-ng/ccu_nm.h
index c623b0c7a23c..eba586b4c7d0 100644
--- a/drivers/clk/sunxi-ng/ccu_nm.h
+++ b/drivers/clk/sunxi-ng/ccu_nm.h
@@ -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;
};
--
2.15.0
On Fri, Dec 08, 2017 at 04:35:09PM +0800, Chen-Yu Tsai wrote:
> Hi,
>
> This series follows previous improvements for the other Allwinner SoCs to
> improve audio quality, in particular the speed and pitch of audio playback.
> The audio PLLs in Allwinner SoCs cannot produce the correct frequency to
> match the audio sample rate families through integer factors. As such
> the audio is either too fast or too flow.
>
> This is dealt with by using sigma-delta modulation, a form of fractional-N
> synthesis, on the divider. The parameters used are copied from the BSP
> kernel.
>
> As these parameters assume that one of the untracked dividers is set to
> 2, we first add a fixed /2 post divider to the PLL, and force the
> untracked divider to /2. Then we introduce the sigma-delta modulation
> parameters.
>
> This has been tested with SPDIF playback on the Cubietruck Plus, and an
> external PCM5122 DAC from a PiFi DAC 2.0+, connected via I2S to the
> Banana Pi M3. The I2C and I2S support used in this latter test will be
> sent as a separate series later.
Applied, thanks!
Maxime
--
Maxime Ripard, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com