diff mbox series

[1/2] ASoC: wm8904: rework FLL handling

Message ID 3e104cf18cb4e26096f0fe065a2c0e80373f861c.1563817201.git.mirq-linux@rere.qmqm.pl (mailing list archive)
State New, archived
Headers show
Series wm8904: adapt driver for use with audio-graph-card | expand

Commit Message

Michał Mirosław July 22, 2019, 5:57 p.m. UTC
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@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(-)

Comments

Charles Keepax July 23, 2019, 8:27 a.m. UTC | #1
On Mon, Jul 22, 2019 at 07:57:21PM +0200, Michał Mirosław wrote:
> 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.
> 

Do you have thoughts on which CODECs you would be including in
this? These older parts often have small differences between the
configuration that might make this challenging so if you have
plans here would be good to have a look from this end.

> Signed-off-by: Michał Mirosław <mirq-linux@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)
> +

Would be keen to see some other peoples thoughts on this one, but
this feels a little off to me. Selecting the FLL input is
generally something that would be done on a use-case by use-case
basis rather than being a property of the hardware that would
belong in device tree.

Thanks,
Charles
Mark Brown July 23, 2019, 10:52 a.m. UTC | #2
On Tue, Jul 23, 2019 at 09:27:16AM +0100, Charles Keepax wrote:
> On Mon, Jul 22, 2019 at 07:57:21PM +0200, Michał Mirosław wrote:
> > 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.

> Do you have thoughts on which CODECs you would be including in
> this? These older parts often have small differences between the
> configuration that might make this challenging so if you have
> plans here would be good to have a look from this end.

Right, it's not like it's the same IP being dropped into multiple chips
in an identical fashion.  There's a lot of high level similarities in
the register interfaces but also many small per device tweaks, and it's
not clear what benefit we get from refactoring at this point.
Michał Mirosław July 23, 2019, 11:54 a.m. UTC | #3
On Tue, Jul 23, 2019 at 11:52:48AM +0100, Mark Brown wrote:
> On Tue, Jul 23, 2019 at 09:27:16AM +0100, Charles Keepax wrote:
> > On Mon, Jul 22, 2019 at 07:57:21PM +0200, Michał Mirosław wrote:
> > > 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.
> 
> > Do you have thoughts on which CODECs you would be including in
> > this? These older parts often have small differences between the
> > configuration that might make this challenging so if you have
> > plans here would be good to have a look from this end.
> 
> Right, it's not like it's the same IP being dropped into multiple chips
> in an identical fashion.  There's a lot of high level similarities in
> the register interfaces but also many small per device tweaks, and it's
> not clear what benefit we get from refactoring at this point.

This would be mainly code separation, so it's easier to understand and
has a potential for direct reuse. I can see that clock selection needs
to be changed, but the idea is to have it configurable via device-tree.

I picked at random WM9081. It's FLL implementation looks very similar -
major diffferences being in FLL_OUTDIV selection (direct divider vs 2^N)
and register block offset.

Another random pick - WM8900. The general FLL idea seems the same, but
this one has a bit more complicated register layout, so I wouldn't
consider it at first.

WM8994 - it has two FLL's but otherwise has identical register layout
for them as WM8904. The only difference is in clock source selection.

Best Regards,
Michał Mirosław
Mark Brown July 23, 2019, 2:38 p.m. UTC | #4
On Tue, Jul 23, 2019 at 01:54:15PM +0200, Michał Mirosław wrote:
> On Tue, Jul 23, 2019 at 11:52:48AM +0100, Mark Brown wrote:

> > Right, it's not like it's the same IP being dropped into multiple chips
> > in an identical fashion.  There's a lot of high level similarities in
> > the register interfaces but also many small per device tweaks, and it's
> > not clear what benefit we get from refactoring at this point.

> This would be mainly code separation, so it's easier to understand and
> has a potential for direct reuse. I can see that clock selection needs
> to be changed, but the idea is to have it configurable via device-tree.

Not all the world is DT...

> I picked at random WM9081. It's FLL implementation looks very similar -
> major diffferences being in FLL_OUTDIV selection (direct divider vs 2^N)
> and register block offset.

> Another random pick - WM8900. The general FLL idea seems the same, but
> this one has a bit more complicated register layout, so I wouldn't
> consider it at first.

Yeah, there's a lot that on the surface looks similar but there's a lot
of variations in the detail - different numbers getting plugged in,
register layouts getting tweaked, different sets of sources and so on.
I get that we can potentially combine the implementations which in
theory is code reuse but what is the end goal for that code reuse given
that all the refactoring is going to warrant some testing over a bunch
of different parts.
Charles Keepax July 23, 2019, 2:53 p.m. UTC | #5
On Tue, Jul 23, 2019 at 01:54:15PM +0200, Michał Mirosław wrote:
> On Tue, Jul 23, 2019 at 11:52:48AM +0100, Mark Brown wrote:
> > On Tue, Jul 23, 2019 at 09:27:16AM +0100, Charles Keepax wrote:
> > > On Mon, Jul 22, 2019 at 07:57:21PM +0200, Michał Mirosław wrote:
> > > > 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.
> > 
> > > Do you have thoughts on which CODECs you would be including in
> > > this? These older parts often have small differences between the
> > > configuration that might make this challenging so if you have
> > > plans here would be good to have a look from this end.
> > 
> > Right, it's not like it's the same IP being dropped into multiple chips
> > in an identical fashion.  There's a lot of high level similarities in
> > the register interfaces but also many small per device tweaks, and it's
> > not clear what benefit we get from refactoring at this point.
> 
> This would be mainly code separation, so it's easier to understand and
> has a potential for direct reuse. I can see that clock selection needs
> to be changed, but the idea is to have it configurable via device-tree.
> 
> I picked at random WM9081. It's FLL implementation looks very similar -
> major diffferences being in FLL_OUTDIV selection (direct divider vs 2^N)
> and register block offset.
> 
> Another random pick - WM8900. The general FLL idea seems the same, but
> this one has a bit more complicated register layout, so I wouldn't
> consider it at first.
> 
> WM8994 - it has two FLL's but otherwise has identical register layout
> for them as WM8904. The only difference is in clock source selection.
> 

The register layouts do look similar but the code controlling the
FLLs in these cases is quite different. I am somewhat nervous
there are subtle factors at play here which are going to cause
problems and its very hard to seperate divergence and actually
required sequencing here.

At the very least if you are really sure you want to proceed in
this direction I think we should look at splitting the patch into
two parts one that factors out the functionality and a separate
patch that adds any new functionality. It makes things much
easier to review that way.

Thanks,
Charles
diff mbox series

Patch

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 */