[alsa-devel] [PATCH 1/2] ASoC: wm8904: rework FLL handling
Michał Mirosław
mirq-linux at rere.qmqm.pl
Mon Jul 22 19:57:21 CEST 2019
Extract and rework FLL handling. This makes it possible to reuse
the code for other Wolfson codecs and makes codec adapt SYSCLK to
exactly match frequency required for used sampling rate.
Signed-off-by: Michał Mirosław <mirq-linux at rere.qmqm.pl>
---
.../devicetree/bindings/sound/wm8904.txt | 4 +
sound/soc/atmel/atmel_wm8904.c | 11 +-
sound/soc/codecs/Kconfig | 4 +
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/wm8904.c | 454 +++++-------------
sound/soc/codecs/wm8904.h | 5 -
sound/soc/codecs/wm_fll.c | 339 +++++++++++++
sound/soc/codecs/wm_fll.h | 26 +
8 files changed, 492 insertions(+), 353 deletions(-)
diff --git a/Documentation/devicetree/bindings/sound/wm8904.txt b/Documentation/devicetree/bindings/sound/wm8904.txt
index 66bf261423b9..6b51d87f5987 100644
--- a/Documentation/devicetree/bindings/sound/wm8904.txt
+++ b/Documentation/devicetree/bindings/sound/wm8904.txt
@@ -9,6 +9,10 @@ Required properties:
- clocks: reference to
<Documentation/devicetree/bindings/clock/clock-bindings.txt>
+Optional properties:
+ - wlf,fll-input: FLL input signal: "mclk", "bclk", "lrclk" or "osc"
+ ("mclk" by default)
+
Pins on the device (for linking into audio routes):
* IN1L
diff --git a/sound/soc/atmel/atmel_wm8904.c b/sound/soc/atmel/atmel_wm8904.c
index 776b27d3686e..b77ea2495efe 100644
--- a/sound/soc/atmel/atmel_wm8904.c
+++ b/sound/soc/atmel/atmel_wm8904.c
@@ -30,20 +30,11 @@ static int atmel_asoc_wm8904_hw_params(struct snd_pcm_substream *substream,
struct snd_soc_dai *codec_dai = rtd->codec_dai;
int ret;
- ret = snd_soc_dai_set_pll(codec_dai, WM8904_FLL_MCLK, WM8904_FLL_MCLK,
- 32768, params_rate(params) * 256);
- if (ret < 0) {
- pr_err("%s - failed to set wm8904 codec PLL.", __func__);
- return ret;
- }
-
/*
* As here wm8904 use FLL output as its system clock
- * so calling set_sysclk won't care freq parameter
- * then we pass 0
*/
ret = snd_soc_dai_set_sysclk(codec_dai, WM8904_CLK_FLL,
- 0, SND_SOC_CLOCK_IN);
+ params_rate(params) * 256, SND_SOC_CLOCK_IN);
if (ret < 0) {
pr_err("%s -failed to set wm8904 SYSCLK\n", __func__);
return ret;
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 9f89a5346299..15a39faf33ab 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -281,6 +281,9 @@ config SND_SOC_ARIZONA
default m if SND_SOC_WM8997=m
default m if SND_SOC_WM8998=m
+config SND_SOC_WM_FLL
+ tristate
+
config SND_SOC_WM_HUBS
tristate
default y if SND_SOC_WM8993=y || SND_SOC_WM8994=y
@@ -1325,6 +1328,7 @@ config SND_SOC_WM8903
config SND_SOC_WM8904
tristate "Wolfson Microelectronics WM8904 CODEC"
depends on I2C
+ select SND_SOC_WM_FLL
config SND_SOC_WM8940
tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 5b4bb8cf4325..22704fbb7497 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -269,6 +269,7 @@ snd-soc-wm9090-objs := wm9090.o
snd-soc-wm9705-objs := wm9705.o
snd-soc-wm9712-objs := wm9712.o
snd-soc-wm9713-objs := wm9713.o
+snd-soc-wm-fll-objs := wm_fll.o
snd-soc-wm-hubs-objs := wm_hubs.o
snd-soc-zx-aud96p22-objs := zx_aud96p22.o
# Amp
@@ -549,6 +550,7 @@ obj-$(CONFIG_SND_SOC_WM9705) += snd-soc-wm9705.o
obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o
obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o
obj-$(CONFIG_SND_SOC_WM_ADSP) += snd-soc-wm-adsp.o
+obj-$(CONFIG_SND_SOC_WM_FLL) += snd-soc-wm-fll.o
obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o
obj-$(CONFIG_SND_SOC_ZX_AUD96P22) += snd-soc-zx-aud96p22.o
diff --git a/sound/soc/codecs/wm8904.c b/sound/soc/codecs/wm8904.c
index 5ebdd1d9afde..033a38e408b5 100644
--- a/sound/soc/codecs/wm8904.c
+++ b/sound/soc/codecs/wm8904.c
@@ -24,6 +24,7 @@
#include <sound/tlv.h>
#include <sound/wm8904.h>
+#include "wm_fll.h"
#include "wm8904.h"
enum wm8904_type {
@@ -66,12 +67,8 @@ struct wm8904_priv {
int retune_mobile_cfg;
struct soc_enum retune_mobile_enum;
- /* FLL setup */
- int fll_src;
- int fll_fref;
- int fll_fout;
-
/* Clocking configuration */
+ struct wm_fll_data fll;
unsigned int mclk_rate;
int sysclk_src;
unsigned int sysclk_rate;
@@ -311,35 +308,109 @@ static bool wm8904_readable_register(struct device *dev, unsigned int reg)
}
}
-static int wm8904_configure_clocking(struct snd_soc_component *component)
+static void wm8904_unprepare_sysclk(struct wm8904_priv *priv)
{
+ switch (priv->sysclk_src) {
+ case WM8904_CLK_MCLK:
+ clk_disable_unprepare(priv->mclk);
+ break;
+
+ case WM8904_CLK_FLL:
+ wm_fll_disable(&priv->fll);
+ break;
+ }
+}
+
+static int wm8904_prepare_sysclk(struct wm8904_priv *priv)
+{
+ int err;
+
+ switch (priv->sysclk_src) {
+ case WM8904_CLK_MCLK:
+ err = clk_set_rate(priv->mclk, priv->mclk_rate);
+ if (!err)
+ err = clk_prepare_enable(priv->mclk);
+ break;
+
+ case WM8904_CLK_FLL:
+ err = wm_fll_enable(&priv->fll);
+ break;
+
+ default:
+ err = -EINVAL;
+ break;
+ }
+
+ return err;
+}
+
+static void wm8904_disable_sysclk(struct wm8904_priv *priv)
+{
+ regmap_update_bits(priv->regmap, WM8904_CLOCK_RATES_2,
+ WM8904_CLK_SYS_ENA, 0);
+ wm8904_unprepare_sysclk(priv);
+}
+
+static int wm8904_enable_sysclk(struct wm8904_priv *priv)
+{
+ int err;
+
+ err = wm8904_prepare_sysclk(priv);
+ if (err < 0)
+ return err;
+
+ err = regmap_update_bits(priv->regmap, WM8904_CLOCK_RATES_2,
+ WM8904_CLK_SYS_ENA_MASK, WM8904_CLK_SYS_ENA);
+ if (err < 0)
+ wm8904_unprepare_sysclk(priv);
+
+ return err;
+}
+
+static int wm8904_set_sysclk(struct snd_soc_dai *dai, int clk_id,
+ unsigned int rate, int dir)
+{
+ struct snd_soc_component *component = dai->component;
struct wm8904_priv *wm8904 = snd_soc_component_get_drvdata(component);
- unsigned int clock0, clock2, rate;
+ unsigned int clock0, clock2;
+ int err;
+
+ switch (clk_id) {
+ case WM8904_CLK_MCLK:
+ case WM8904_CLK_FLL:
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ if (clk_id == wm8904->sysclk_src && rate == wm8904->mclk_rate)
+ return 0;
+
+ dev_dbg(dai->dev, "Clock source is %d at %uHz\n", clk_id, rate);
/* Gate the clock while we're updating to avoid misclocking */
clock2 = snd_soc_component_read32(component, WM8904_CLOCK_RATES_2);
- snd_soc_component_update_bits(component, WM8904_CLOCK_RATES_2,
- WM8904_SYSCLK_SRC, 0);
+ wm8904_disable_sysclk(wm8904);
+
+ wm8904->sysclk_src = clk_id;
+ wm8904->mclk_rate = rate;
- /* This should be done on init() for bypass paths */
switch (wm8904->sysclk_src) {
case WM8904_CLK_MCLK:
- dev_dbg(component->dev, "Using %dHz MCLK\n", wm8904->mclk_rate);
+ dev_dbg(component->dev, "Using %dHz MCLK\n", rate);
clock2 &= ~WM8904_SYSCLK_SRC;
- rate = wm8904->mclk_rate;
-
- /* Ensure the FLL is stopped */
- snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_1,
- WM8904_FLL_OSC_ENA | WM8904_FLL_ENA, 0);
break;
case WM8904_CLK_FLL:
- dev_dbg(component->dev, "Using %dHz FLL clock\n",
- wm8904->fll_fout);
+ err = wm_fll_set_rate(&wm8904->fll, rate);
+ if (err < 0)
+ return err;
+
+ dev_dbg(component->dev, "Using %dHz FLL clock\n", rate);
clock2 |= WM8904_SYSCLK_SRC;
- rate = wm8904->fll_fout;
break;
default:
@@ -356,11 +427,18 @@ static int wm8904_configure_clocking(struct snd_soc_component *component)
wm8904->sysclk_rate = rate;
}
- snd_soc_component_update_bits(component, WM8904_CLOCK_RATES_0, WM8904_MCLK_DIV,
- clock0);
-
+ snd_soc_component_update_bits(component, WM8904_CLOCK_RATES_0,
+ WM8904_MCLK_DIV, clock0);
snd_soc_component_update_bits(component, WM8904_CLOCK_RATES_2,
- WM8904_CLK_SYS_ENA | WM8904_SYSCLK_SRC, clock2);
+ WM8904_SYSCLK_SRC, clock2);
+
+ if (clock2 & WM8904_CLK_SYS_ENA) {
+ err = wm8904_enable_sysclk(wm8904);
+ if (err < 0) {
+ dev_err(component->dev, "Failed to reenable CLK_SYS: %d\n", err);
+ return err;
+ }
+ }
dev_dbg(component->dev, "CLK_SYS is %dHz\n", wm8904->sysclk_rate);
@@ -670,33 +748,21 @@ static int sysclk_event(struct snd_soc_dapm_widget *w,
{
struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
struct wm8904_priv *wm8904 = snd_soc_component_get_drvdata(component);
+ int ret = 0;
switch (event) {
case SND_SOC_DAPM_PRE_PMU:
- /* If we're using the FLL then we only start it when
- * required; we assume that the configuration has been
- * done previously and all we need to do is kick it
- * off.
- */
- switch (wm8904->sysclk_src) {
- case WM8904_CLK_FLL:
- snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_1,
- WM8904_FLL_OSC_ENA,
- WM8904_FLL_OSC_ENA);
-
- snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_1,
- WM8904_FLL_ENA,
- WM8904_FLL_ENA);
- break;
-
- default:
- break;
- }
+ ret = wm8904_prepare_sysclk(wm8904);
+ if (ret)
+ dev_err(component->dev,
+ "Failed to prepare SYSCLK: %d\n", ret);
+ else
+ dev_dbg(component->dev, "SYSCLK on\n");
break;
case SND_SOC_DAPM_POST_PMD:
- snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_1,
- WM8904_FLL_OSC_ENA | WM8904_FLL_ENA, 0);
+ wm8904_unprepare_sysclk(wm8904);
+ dev_dbg(component->dev, "SYSCLK off\n");
break;
}
@@ -1275,7 +1341,7 @@ static int wm8904_hw_params(struct snd_pcm_substream *substream,
{
struct snd_soc_component *component = dai->component;
struct wm8904_priv *wm8904 = snd_soc_component_get_drvdata(component);
- int ret, i, best, best_val, cur_val;
+ int i, best, best_val, cur_val;
unsigned int aif1 = 0;
unsigned int aif2 = 0;
unsigned int aif3 = 0;
@@ -1310,13 +1376,8 @@ static int wm8904_hw_params(struct snd_pcm_substream *substream,
return -EINVAL;
}
-
dev_dbg(component->dev, "Target BCLK is %dHz\n", wm8904->bclk);
- ret = wm8904_configure_clocking(component);
- if (ret != 0)
- return ret;
-
/* Select nearest CLK_SYS_RATE */
best = 0;
best_val = abs((wm8904->sysclk_rate / clk_sys_rates[0].ratio)
@@ -1368,8 +1429,8 @@ static int wm8904_hw_params(struct snd_pcm_substream *substream,
}
}
wm8904->bclk = (wm8904->sysclk_rate * 10) / bclk_divs[best].div;
- dev_dbg(component->dev, "Selected BCLK_DIV of %d for %dHz BCLK\n",
- bclk_divs[best].div, wm8904->bclk);
+ dev_dbg(component->dev, "Selected BCLK_DIV of %d.%d for %dHz BCLK\n",
+ bclk_divs[best].div / 10, bclk_divs[best].div % 10, wm8904->bclk);
aif2 |= bclk_divs[best].bclk_div;
/* LRCLK is a simple fraction of BCLK */
@@ -1396,34 +1457,6 @@ static int wm8904_hw_params(struct snd_pcm_substream *substream,
return 0;
}
-
-static int wm8904_set_sysclk(struct snd_soc_dai *dai, int clk_id,
- unsigned int freq, int dir)
-{
- struct snd_soc_component *component = dai->component;
- struct wm8904_priv *priv = snd_soc_component_get_drvdata(component);
-
- switch (clk_id) {
- case WM8904_CLK_MCLK:
- priv->sysclk_src = clk_id;
- priv->mclk_rate = freq;
- break;
-
- case WM8904_CLK_FLL:
- priv->sysclk_src = clk_id;
- break;
-
- default:
- return -EINVAL;
- }
-
- dev_dbg(dai->dev, "Clock source is %d at %uHz\n", clk_id, freq);
-
- wm8904_configure_clocking(component);
-
- return 0;
-}
-
static int wm8904_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
{
struct snd_soc_component *component = dai->component;
@@ -1563,253 +1596,6 @@ static int wm8904_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
return 0;
}
-struct _fll_div {
- u16 fll_fratio;
- u16 fll_outdiv;
- u16 fll_clk_ref_div;
- u16 n;
- u16 k;
-};
-
-/* The size in bits of the FLL divide multiplied by 10
- * to allow rounding later */
-#define FIXED_FLL_SIZE ((1 << 16) * 10)
-
-static struct {
- unsigned int min;
- unsigned int max;
- u16 fll_fratio;
- int ratio;
-} fll_fratios[] = {
- { 0, 64000, 4, 16 },
- { 64000, 128000, 3, 8 },
- { 128000, 256000, 2, 4 },
- { 256000, 1000000, 1, 2 },
- { 1000000, 13500000, 0, 1 },
-};
-
-static int fll_factors(struct _fll_div *fll_div, unsigned int Fref,
- unsigned int Fout)
-{
- u64 Kpart;
- unsigned int K, Ndiv, Nmod, target;
- unsigned int div;
- int i;
-
- /* Fref must be <=13.5MHz */
- div = 1;
- fll_div->fll_clk_ref_div = 0;
- while ((Fref / div) > 13500000) {
- div *= 2;
- fll_div->fll_clk_ref_div++;
-
- if (div > 8) {
- pr_err("Can't scale %dMHz input down to <=13.5MHz\n",
- Fref);
- return -EINVAL;
- }
- }
-
- pr_debug("Fref=%u Fout=%u\n", Fref, Fout);
-
- /* Apply the division for our remaining calculations */
- Fref /= div;
-
- /* Fvco should be 90-100MHz; don't check the upper bound */
- div = 4;
- while (Fout * div < 90000000) {
- div++;
- if (div > 64) {
- pr_err("Unable to find FLL_OUTDIV for Fout=%uHz\n",
- Fout);
- return -EINVAL;
- }
- }
- target = Fout * div;
- fll_div->fll_outdiv = div - 1;
-
- pr_debug("Fvco=%dHz\n", target);
-
- /* Find an appropriate FLL_FRATIO and factor it out of the target */
- for (i = 0; i < ARRAY_SIZE(fll_fratios); i++) {
- if (fll_fratios[i].min <= Fref && Fref <= fll_fratios[i].max) {
- fll_div->fll_fratio = fll_fratios[i].fll_fratio;
- target /= fll_fratios[i].ratio;
- break;
- }
- }
- if (i == ARRAY_SIZE(fll_fratios)) {
- pr_err("Unable to find FLL_FRATIO for Fref=%uHz\n", Fref);
- return -EINVAL;
- }
-
- /* Now, calculate N.K */
- Ndiv = target / Fref;
-
- fll_div->n = Ndiv;
- Nmod = target % Fref;
- pr_debug("Nmod=%d\n", Nmod);
-
- /* Calculate fractional part - scale up so we can round. */
- Kpart = FIXED_FLL_SIZE * (long long)Nmod;
-
- do_div(Kpart, Fref);
-
- K = Kpart & 0xFFFFFFFF;
-
- if ((K % 10) >= 5)
- K += 5;
-
- /* Move down to proper range now rounding is done */
- fll_div->k = K / 10;
-
- pr_debug("N=%x K=%x FLL_FRATIO=%x FLL_OUTDIV=%x FLL_CLK_REF_DIV=%x\n",
- fll_div->n, fll_div->k,
- fll_div->fll_fratio, fll_div->fll_outdiv,
- fll_div->fll_clk_ref_div);
-
- return 0;
-}
-
-static int wm8904_set_fll(struct snd_soc_dai *dai, int fll_id, int source,
- unsigned int Fref, unsigned int Fout)
-{
- struct snd_soc_component *component = dai->component;
- struct wm8904_priv *wm8904 = snd_soc_component_get_drvdata(component);
- struct _fll_div fll_div;
- int ret, val;
- int clock2, fll1;
-
- /* Any change? */
- if (source == wm8904->fll_src && Fref == wm8904->fll_fref &&
- Fout == wm8904->fll_fout)
- return 0;
-
- clock2 = snd_soc_component_read32(component, WM8904_CLOCK_RATES_2);
-
- if (Fout == 0) {
- dev_dbg(component->dev, "FLL disabled\n");
-
- wm8904->fll_fref = 0;
- wm8904->fll_fout = 0;
-
- /* Gate SYSCLK to avoid glitches */
- snd_soc_component_update_bits(component, WM8904_CLOCK_RATES_2,
- WM8904_CLK_SYS_ENA, 0);
-
- snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_1,
- WM8904_FLL_OSC_ENA | WM8904_FLL_ENA, 0);
-
- goto out;
- }
-
- /* Validate the FLL ID */
- switch (source) {
- case WM8904_FLL_MCLK:
- case WM8904_FLL_LRCLK:
- case WM8904_FLL_BCLK:
- ret = fll_factors(&fll_div, Fref, Fout);
- if (ret != 0)
- return ret;
- break;
-
- case WM8904_FLL_FREE_RUNNING:
- dev_dbg(component->dev, "Using free running FLL\n");
- /* Force 12MHz and output/4 for now */
- Fout = 12000000;
- Fref = 12000000;
-
- memset(&fll_div, 0, sizeof(fll_div));
- fll_div.fll_outdiv = 3;
- break;
-
- default:
- dev_err(component->dev, "Unknown FLL ID %d\n", fll_id);
- return -EINVAL;
- }
-
- /* Save current state then disable the FLL and SYSCLK to avoid
- * misclocking */
- fll1 = snd_soc_component_read32(component, WM8904_FLL_CONTROL_1);
- snd_soc_component_update_bits(component, WM8904_CLOCK_RATES_2,
- WM8904_CLK_SYS_ENA, 0);
- snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_1,
- WM8904_FLL_OSC_ENA | WM8904_FLL_ENA, 0);
-
- /* Unlock forced oscilator control to switch it on/off */
- snd_soc_component_update_bits(component, WM8904_CONTROL_INTERFACE_TEST_1,
- WM8904_USER_KEY, WM8904_USER_KEY);
-
- if (fll_id == WM8904_FLL_FREE_RUNNING) {
- val = WM8904_FLL_FRC_NCO;
- } else {
- val = 0;
- }
-
- snd_soc_component_update_bits(component, WM8904_FLL_NCO_TEST_1, WM8904_FLL_FRC_NCO,
- val);
- snd_soc_component_update_bits(component, WM8904_CONTROL_INTERFACE_TEST_1,
- WM8904_USER_KEY, 0);
-
- switch (fll_id) {
- case WM8904_FLL_MCLK:
- snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_5,
- WM8904_FLL_CLK_REF_SRC_MASK, 0);
- break;
-
- case WM8904_FLL_LRCLK:
- snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_5,
- WM8904_FLL_CLK_REF_SRC_MASK, 1);
- break;
-
- case WM8904_FLL_BCLK:
- snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_5,
- WM8904_FLL_CLK_REF_SRC_MASK, 2);
- break;
- }
-
- if (fll_div.k)
- val = WM8904_FLL_FRACN_ENA;
- else
- val = 0;
- snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_1,
- WM8904_FLL_FRACN_ENA, val);
-
- snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_2,
- WM8904_FLL_OUTDIV_MASK | WM8904_FLL_FRATIO_MASK,
- (fll_div.fll_outdiv << WM8904_FLL_OUTDIV_SHIFT) |
- (fll_div.fll_fratio << WM8904_FLL_FRATIO_SHIFT));
-
- snd_soc_component_write(component, WM8904_FLL_CONTROL_3, fll_div.k);
-
- snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_4, WM8904_FLL_N_MASK,
- fll_div.n << WM8904_FLL_N_SHIFT);
-
- snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_5,
- WM8904_FLL_CLK_REF_DIV_MASK,
- fll_div.fll_clk_ref_div
- << WM8904_FLL_CLK_REF_DIV_SHIFT);
-
- dev_dbg(component->dev, "FLL configured for %dHz->%dHz\n", Fref, Fout);
-
- wm8904->fll_fref = Fref;
- wm8904->fll_fout = Fout;
- wm8904->fll_src = source;
-
- /* Enable the FLL if it was previously active */
- snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_1,
- WM8904_FLL_OSC_ENA, fll1);
- snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_1,
- WM8904_FLL_ENA, fll1);
-
-out:
- /* Reenable SYSCLK if it was previously active */
- snd_soc_component_update_bits(component, WM8904_CLOCK_RATES_2,
- WM8904_CLK_SYS_ENA, clock2);
-
- return 0;
-}
-
static int wm8904_digital_mute(struct snd_soc_dai *codec_dai, int mute)
{
struct snd_soc_component *component = codec_dai->component;
@@ -1857,15 +1643,6 @@ static int wm8904_set_bias_level(struct snd_soc_component *component,
return ret;
}
- ret = clk_prepare_enable(wm8904->mclk);
- if (ret) {
- dev_err(component->dev,
- "Failed to enable MCLK: %d\n", ret);
- regulator_bulk_disable(ARRAY_SIZE(wm8904->supplies),
- wm8904->supplies);
- return ret;
- }
-
regcache_cache_only(wm8904->regmap, false);
regcache_sync(wm8904->regmap);
@@ -1908,7 +1685,6 @@ static int wm8904_set_bias_level(struct snd_soc_component *component,
regulator_bulk_disable(ARRAY_SIZE(wm8904->supplies),
wm8904->supplies);
- clk_disable_unprepare(wm8904->mclk);
break;
}
return 0;
@@ -1923,7 +1699,6 @@ static const struct snd_soc_dai_ops wm8904_dai_ops = {
.set_sysclk = wm8904_set_sysclk,
.set_fmt = wm8904_set_fmt,
.set_tdm_slot = wm8904_set_tdm_slot,
- .set_pll = wm8904_set_fll,
.hw_params = wm8904_hw_params,
.digital_mute = wm8904_digital_mute,
};
@@ -2151,6 +1926,8 @@ static int wm8904_i2c_probe(struct i2c_client *i2c,
return ret;
}
+ wm_fll_init(&wm8904->fll, &i2c->dev);
+
if (i2c->dev.of_node) {
const struct of_device_id *match;
@@ -2262,6 +2039,7 @@ static int wm8904_i2c_probe(struct i2c_client *i2c,
WM8904_POBCTRL, 0);
/* Can leave the device powered off until we need it */
+ wm8904_disable_sysclk(wm8904);
regcache_cache_only(wm8904->regmap, true);
regulator_bulk_disable(ARRAY_SIZE(wm8904->supplies), wm8904->supplies);
diff --git a/sound/soc/codecs/wm8904.h b/sound/soc/codecs/wm8904.h
index c1bca52f9927..60af09e0bb15 100644
--- a/sound/soc/codecs/wm8904.h
+++ b/sound/soc/codecs/wm8904.h
@@ -13,11 +13,6 @@
#define WM8904_CLK_MCLK 1
#define WM8904_CLK_FLL 2
-#define WM8904_FLL_MCLK 1
-#define WM8904_FLL_BCLK 2
-#define WM8904_FLL_LRCLK 3
-#define WM8904_FLL_FREE_RUNNING 4
-
/*
* Register values.
*/
diff --git a/sound/soc/codecs/wm_fll.c b/sound/soc/codecs/wm_fll.c
new file mode 100644
index 000000000000..c76714e5fee5
--- /dev/null
+++ b/sound/soc/codecs/wm_fll.c
@@ -0,0 +1,339 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * wm_fll.c -- WM89xx FLL support
+ *
+ * Copyright 2019 Michał Mirosław
+ *
+ * WM8904 can generate its clock directly from MCLK, from
+ * internal FLL synchronizing to one of hw frame clocks
+ * or from FLL's VCO in free-running mode
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include "wm_fll.h"
+#include "wm8904.h"
+
+enum wm_fll_ref_source
+{
+ FLL_REF_MCLK,
+ FLL_REF_BCLK,
+ FLL_REF_FSCLK,
+ FLL_REF_OSC,
+};
+
+static const char *const wm_fll_parents[] = {
+ "mclk", "bclk", "lrclk", "osc"
+};
+
+static bool wm_fll_in_free_running_mode(struct wm_fll_data *hw)
+{
+ unsigned int val;
+
+ if (regmap_read(hw->regmap, WM8904_FLL_NCO_TEST_0, &val) < 0)
+ return false;
+
+ return FIELD_GET(WM8904_FLL_FRC_NCO_MASK, val);
+}
+
+static int wm_fll_get_parent(struct wm_fll_data *hw)
+{
+ unsigned int val;
+ int err;
+
+ /* free-running mode? */
+ if (wm_fll_in_free_running_mode(hw))
+ return FLL_REF_OSC;
+
+ err = regmap_read(hw->regmap, WM8904_FLL_CONTROL_5, &val);
+ if (err < 0)
+ return err;
+
+ return FIELD_GET(WM8904_FLL_CLK_REF_SRC_MASK, val);
+}
+
+static int wm_fll_set_parent(struct wm_fll_data *hw, enum wm_fll_ref_source index)
+{
+ unsigned val;
+ bool osc_en = index == FLL_REF_OSC;
+ int err;
+
+ if (osc_en) {
+ /* set osc freq (approx 96MHz) */
+ val = FIELD_PREP(WM8904_FLL_FRC_NCO_VAL_MASK, 0x19);
+ err = regmap_update_bits(hw->regmap, WM8904_FLL_NCO_TEST_1,
+ WM8904_FLL_FRC_NCO_VAL_MASK, val);
+ if (err < 0)
+ return err;
+ }
+
+ /* set free-running mode */
+ val = FIELD_PREP(WM8904_FLL_FRC_NCO_MASK, osc_en);
+ err = regmap_update_bits(hw->regmap, WM8904_FLL_NCO_TEST_0,
+ WM8904_FLL_FRC_NCO_MASK, val);
+ if (osc_en || err < 0)
+ return err;
+
+ /* set FLL reference input */
+ val = FIELD_PREP(WM8904_FLL_CLK_REF_SRC_MASK, index);
+ err = regmap_update_bits(hw->regmap, WM8904_FLL_CONTROL_5,
+ WM8904_FLL_CLK_REF_SRC_MASK, val);
+
+ return err;
+}
+
+int wm_fll_enable(struct wm_fll_data *hw)
+{
+ unsigned int val;
+ int clk_src;
+ int err, retry;
+
+ clk_src = wm_fll_get_parent(hw);
+ if (clk_src == FLL_REF_MCLK) {
+ err = clk_prepare_enable(hw->mclk);
+ if (err < 0)
+ return err;
+ }
+
+ err = regmap_update_bits(hw->regmap, WM8904_FLL_CONTROL_1,
+ WM8904_FLL_OSC_ENA_MASK, WM8904_FLL_OSC_ENA);
+ if (err < 0)
+ goto err_out;
+
+ err = regmap_write(hw->regmap, WM8904_INTERRUPT_STATUS,
+ WM8904_FLL_LOCK_EINT);
+ if (err < 0)
+ goto err_out;
+
+ err = regmap_update_bits(hw->regmap, WM8904_FLL_CONTROL_1,
+ WM8904_FLL_ENA_MASK, WM8904_FLL_ENA);
+ if (err)
+ goto err_out;
+
+ if (clk_src == FLL_REF_OSC) {
+ usleep_range(150, 250);
+ return 0;
+ }
+
+ for (retry = 3; retry; --retry) {
+ msleep(1);
+ err = regmap_read(hw->regmap, WM8904_INTERRUPT_STATUS, &val);
+ if (err < 0)
+ goto err_out;
+
+ if (val & WM8904_FLL_LOCK_EINT)
+ break;
+ }
+
+ /* it seems that FLL_LOCK might never be asserted */
+ /* eg. WM8904's FLL doesn't, but works anyway */
+ return 0;
+
+err_out:
+ wm_fll_disable(hw);
+
+ if (clk_src == FLL_REF_MCLK)
+ clk_disable_unprepare(hw->mclk);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(wm_fll_enable);
+
+int wm_fll_disable(struct wm_fll_data *hw)
+{
+ return regmap_update_bits(hw->regmap, WM8904_FLL_CONTROL_1,
+ WM8904_FLL_ENA_MASK|WM8904_FLL_OSC_ENA_MASK,
+ 0);
+}
+EXPORT_SYMBOL_GPL(wm_fll_disable);
+
+static int wm_fll_is_enabled(struct wm_fll_data *hw)
+{
+ unsigned int val;
+ int err;
+
+ err = regmap_read(hw->regmap, WM8904_FLL_CONTROL_1, &val);
+ if (err < 0)
+ return err;
+
+ return FIELD_GET(WM8904_FLL_ENA_MASK, val);
+}
+
+static unsigned int wm_fll_apply_refdiv(unsigned long *parent_rate)
+{
+ unsigned int refdiv;
+
+ /* FLL input divider; should ensure Fin <= 13.5MHz */
+
+ refdiv = DIV_ROUND_UP(*parent_rate, 13500000);
+ refdiv = order_base_2(refdiv);
+ if (refdiv > 3)
+ refdiv = 3;
+
+ *parent_rate >>= refdiv;
+
+ return refdiv;
+}
+
+static unsigned int wm_fll_apply_fratio(unsigned long *parent_rate)
+{
+ unsigned int fratio;
+
+ /* FLL comparator divider; efectively Fin multiplier */
+ /* as tabularized in WM8904 datasheet */
+
+ if (*parent_rate >= 256000)
+ fratio = *parent_rate < 1024000;
+ else if (*parent_rate >= 64000)
+ fratio = 2 + (*parent_rate < 128000);
+ else
+ fratio = 4;
+
+ *parent_rate <<= fratio;
+
+ return fratio;
+}
+
+static unsigned int wm_fll_apply_outdiv_rev(unsigned long *rate)
+{
+ unsigned int div;
+
+ /* Fvco -> Fout divider; target: 90 <= Fvco <= 100 MHz */
+
+ div = DIV_ROUND_UP(90000000, *rate);
+ if (div > 64) {
+ *rate = 90000000;
+ return 64;
+ }
+
+ if (div < 4)
+ div = 4;
+
+ *rate *= div;
+ return div;
+}
+
+int wm_fll_set_rate(struct wm_fll_data *hw, unsigned long rate)
+{
+ unsigned long long freq;
+ unsigned long mclk_rate;
+ unsigned int val, mask, refdiv, outdiv, fratio;
+ int err, enabled;
+
+ err = wm_fll_get_parent(hw);
+ if (err < 0)
+ return err;
+
+ if (err != FLL_REF_OSC) {
+ unsigned long parent_rate = mclk_rate = clk_get_rate(hw->mclk);
+
+ refdiv = wm_fll_apply_refdiv(&parent_rate);
+ fratio = wm_fll_apply_fratio(&parent_rate);
+ outdiv = wm_fll_apply_outdiv_rev(&rate);
+
+ freq = (unsigned long long)rate << 16;
+ freq += parent_rate / 2;
+ do_div(freq, parent_rate);
+
+ rate = (freq * parent_rate) >> 16;
+ } else {
+ unsigned long vco_rate = 96000000;
+
+ mclk_rate = 0;
+ fratio = refdiv = 0;
+ rate = DIV_ROUND_CLOSEST(vco_rate, rate);
+ outdiv = clamp_t(unsigned long, rate, 4, 64);
+ freq = 0x177 << 16;
+
+ rate = vco_rate;
+ }
+
+ /* configure */
+
+ enabled = err = wm_fll_is_enabled(hw);
+ if (err > 0)
+ err = wm_fll_disable(hw);
+ if (err < 0)
+ return err;
+
+ dev_dbg(regmap_get_device(hw->regmap),
+ "configuring FLL for %luHz -> %luHz -> %luHz%s\n",
+ mclk_rate, rate, rate / outdiv,
+ enabled ? " (while enabled)" : "");
+ dev_dbg(regmap_get_device(hw->regmap),
+ "FLL settings: N=%llu K=%llu FRATIO=%u OUTDIV=%u REF_DIV=%u\n",
+ freq >> 16, freq & 0xFFFF, fratio, outdiv, refdiv);
+
+ val = FIELD_PREP(WM8904_FLL_CLK_REF_DIV_MASK, refdiv);
+ err = regmap_update_bits(hw->regmap, WM8904_FLL_CONTROL_5,
+ WM8904_FLL_CLK_REF_DIV_MASK, val);
+ if (err < 0)
+ return err;
+
+ val = FIELD_PREP(WM8904_FLL_OUTDIV_MASK, outdiv - 1) |
+ FIELD_PREP(WM8904_FLL_FRATIO_MASK, fratio);
+ mask = WM8904_FLL_OUTDIV_MASK | WM8904_FLL_FRATIO_MASK;
+ err = regmap_update_bits(hw->regmap, WM8904_FLL_CONTROL_2, mask, val);
+ if (err < 0)
+ return err;
+
+ err = regmap_write(hw->regmap, WM8904_FLL_CONTROL_3, (uint16_t)freq);
+ if (err < 0)
+ return err;
+
+ val = FIELD_PREP(WM8904_FLL_FRACN_ENA_MASK, !!(uint16_t)freq);
+ err = regmap_update_bits(hw->regmap, WM8904_FLL_CONTROL_1,
+ WM8904_FLL_FRACN_ENA_MASK, val);
+ if (err < 0)
+ return err;
+
+ val = FIELD_PREP(WM8904_FLL_N_MASK, freq >> 16);
+ err = regmap_update_bits(hw->regmap, WM8904_FLL_CONTROL_4,
+ WM8904_FLL_N_MASK, val);
+ if (err < 0)
+ return err;
+
+ if (enabled)
+ err = wm_fll_enable(hw);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(wm_fll_set_rate);
+
+int wm_fll_init(struct wm_fll_data *hw, struct device *dev)
+{
+ const char *src;
+ int err, index;
+
+ err = of_property_read_string(dev->of_node, "wlf,fll-input", &src);
+ if (err == -EINVAL)
+ index = err = 0;
+ else if (err >= 0)
+ index = err = match_string(wm_fll_parents,
+ ARRAY_SIZE(wm_fll_parents), src);
+ if (err < 0)
+ return err;
+
+ hw->mclk = NULL;
+ if (!index) {
+ hw->mclk = devm_clk_get(dev, "mclk");
+ if (IS_ERR(hw->mclk)) {
+ err = PTR_ERR(hw->mclk);
+ dev_err(dev, "Failed to get MCLK for FLL: %d\n", err);
+ return err;
+ }
+ }
+
+ hw->regmap = dev_get_regmap(dev, NULL);
+ if (!hw->regmap) {
+ dev_err(dev, "driver BUG: regmap not configured\n");
+ return -EINVAL;
+ }
+
+ err = wm_fll_set_parent(hw, index);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(wm_fll_init);
diff --git a/sound/soc/codecs/wm_fll.h b/sound/soc/codecs/wm_fll.h
new file mode 100644
index 000000000000..2518a0910179
--- /dev/null
+++ b/sound/soc/codecs/wm_fll.h
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * wm_fll.h -- FLL support for Wolfson codecs
+ *
+ * Copyright 2019 Michał Mirosław
+ */
+
+#ifndef _WM_FLL_H
+#define _WM_FLL_H
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/regmap.h>
+
+struct wm_fll_data
+{
+ struct regmap *regmap;
+ struct clk *mclk;
+};
+
+int wm_fll_init(struct wm_fll_data *hw, struct device *dev);
+int wm_fll_enable(struct wm_fll_data *hw);
+int wm_fll_disable(struct wm_fll_data *hw);
+int wm_fll_set_rate(struct wm_fll_data *hw, unsigned long rate);
+
+#endif /* _WM_FLL_H */
--
2.20.1
More information about the Alsa-devel
mailing list