[v4,3/3] ASoC: fsl: add imx-es8328 machine driver
diff mbox

Message ID 1403063242-20840-4-git-send-email-xobs@kosagi.com
State New, archived
Headers show

Commit Message

Sean Cross June 18, 2014, 3:47 a.m. UTC
This adds an initial machine driver for the ES8328 audio codec.  The
driver supports headphones, an audio regulator for both the codec itself
and a speaker amp, and supports reparenting and adjusting clocks to supply
the audio codec with the unusual frequency it requires.

Signed-off-by: Sean Cross <xobs@kosagi.com>
---
 .../devicetree/bindings/sound/imx-audio-es8328.txt |  68 ++++
 sound/soc/fsl/Kconfig                              |  14 +
 sound/soc/fsl/Makefile                             |   2 +
 sound/soc/fsl/imx-es8328.c                         | 359 +++++++++++++++++++++
 4 files changed, 443 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/sound/imx-audio-es8328.txt
 create mode 100644 sound/soc/fsl/imx-es8328.c

Comments

Mark Brown June 18, 2014, 10:02 a.m. UTC | #1
On Wed, Jun 18, 2014 at 11:47:21AM +0800, Sean Cross wrote:

> +config SND_SOC_IMX_ES8328
> +	tristate "SoC Audio support for i.MX boards with the ES8328 codec"
> +	depends on OF && (I2C || SPI)
> +	select SND_SOC_ES8328_I2C if I2C
> +	select SND_SOC_ES8328_SPI if SPI_MASTER

I expect this is going to break configurations where I2C is modular and
SPI is enabled since it'll try to build in the I2C bus interface.

> +static int imx_set_frequency(struct imx_es8328_data *data, int freq)
> +{
> +	int ret;
> +
> +	ret = clk_set_parent(data->output_clk, data->codec_clk);
> +	if (ret) {
> +		dev_err(data->dev, "unable to set clk output");
> +		return ret;
> +	}
> +
> +	ret = clk_set_parent(data->codec_clk_sel, data->codec_clk_post_div);
> +	if (ret) {
> +		dev_err(data->dev, "unable to set clk parent");
> +		return ret;
> +	}

This should be handled by the clock bindings not open coded in the
driver - leaving this here most likely won't play nicely when the clock
API can configure the defaults for the tree.  There is supposed to be
support for setting default clock trees going in (or perhaps already in)
the clock bindings.

> +	data->codec_regulator = devm_regulator_get(dev, "codec");
> +	if (IS_ERR(data->codec_regulator)) {
> +		dev_err(dev, "No codec regulator\n");
> +		data->codec_regulator = NULL;
> +	} else {
> +		ret = regulator_enable(data->codec_regulator);
> +		if (ret)
> +			dev_err(dev,
> +				"Unable to enable codec regulator: %d\n", ret);
> +	}

No, this is broken.  The CODEC should request its own supplies which
need to correspond to the supplies the physical device has and failing
to get the supplies should be a fatal error unless the device works
without power (in which case why bother enablin them at all?).

> +	ret = snd_soc_register_card(&data->card);
> +	if (ret)
> +		goto fail;

devm_snd_soc_register_card().
Sean Cross June 18, 2014, 10:22 a.m. UTC | #2
On 06/18/14 18:02, Mark Brown wrote:
> On Wed, Jun 18, 2014 at 11:47:21AM +0800, Sean Cross wrote:
>
>> +config SND_SOC_IMX_ES8328
>> +	tristate "SoC Audio support for i.MX boards with the ES8328 codec"
>> +	depends on OF && (I2C || SPI)
>> +	select SND_SOC_ES8328_I2C if I2C
>> +	select SND_SOC_ES8328_SPI if SPI_MASTER
> I expect this is going to break configurations where I2C is modular and
> SPI is enabled since it'll try to build in the I2C bus interface.
I see this pattern used frequently in the blackfin tree.  Is there a way
to achieve this without breaking configuration?
>> +static int imx_set_frequency(struct imx_es8328_data *data, int freq)
>> +{
>> +	int ret;
>> +
>> +	ret = clk_set_parent(data->output_clk, data->codec_clk);
>> +	if (ret) {
>> +		dev_err(data->dev, "unable to set clk output");
>> +		return ret;
>> +	}
>> +
>> +	ret = clk_set_parent(data->codec_clk_sel, data->codec_clk_post_div);
>> +	if (ret) {
>> +		dev_err(data->dev, "unable to set clk parent");
>> +		return ret;
>> +	}
> This should be handled by the clock bindings not open coded in the
> driver - leaving this here most likely won't play nicely when the clock
> API can configure the defaults for the tree.  There is supposed to be
> support for setting default clock trees going in (or perhaps already in)
> the clock bindings.
Can you give me more information on it?  Currently, it looks like most
boards use a 24 MHz clock, judging from this comment in
mach-imx/clk-imx6q.c:

        /*
         * Let's initially set up CLKO with OSC24M, since this configuration
         * is widely used by imx6q board designs to clock audio codec.
         */

This codec requires the more unusual 22.5792 MHz clock.  What is the
appropriate method of obtaining this particular frequency?

>> +	data->codec_regulator = devm_regulator_get(dev, "codec");
>> +	if (IS_ERR(data->codec_regulator)) {
>> +		dev_err(dev, "No codec regulator\n");
>> +		data->codec_regulator = NULL;
>> +	} else {
>> +		ret = regulator_enable(data->codec_regulator);
>> +		if (ret)
>> +			dev_err(dev,
>> +				"Unable to enable codec regulator: %d\n", ret);
>> +	}
> No, this is broken.  The CODEC should request its own supplies which
> need to correspond to the supplies the physical device has and failing
> to get the supplies should be a fatal error unless the device works
> without power (in which case why bother enablin them at all?).
Not all codecs have power supplies.  Most don't, in fact, it's just this
board where a switch was added to reset the codec in case it gets
wedged.  Boards might use this codec but omit the power switch to save
cost, because they are confident the codec won't get stuck, in which
case they would be forced to use a dummy regulator.

Additionally, since the regulator is external to the codec (as it
physically cuts 3.3V from the power supply), it doesn't make sense to
put it in the codec driver.


Sean
Mark Brown June 18, 2014, 10:31 a.m. UTC | #3
On Wed, Jun 18, 2014 at 06:22:52PM +0800, Sean Cross wrote:
> On 06/18/14 18:02, Mark Brown wrote:

> > This should be handled by the clock bindings not open coded in the
> > driver - leaving this here most likely won't play nicely when the clock
> > API can configure the defaults for the tree.  There is supposed to be
> > support for setting default clock trees going in (or perhaps already in)
> > the clock bindings.

> Can you give me more information on it?  Currently, it looks like most
> boards use a 24 MHz clock, judging from this comment in
> mach-imx/clk-imx6q.c:

Look at the clock API, this stuff was introduced in the last merge
window if it's there yet at all.

> This codec requires the more unusual 22.5792 MHz clock.  What is the
> appropriate method of obtaining this particular frequency?

clk_set_rate() on the directly connected clock, the problem is fiddling
about with the parenting rather than setting the rate.

> > No, this is broken.  The CODEC should request its own supplies which
> > need to correspond to the supplies the physical device has and failing
> > to get the supplies should be a fatal error unless the device works
> > without power (in which case why bother enablin them at all?).

> Not all codecs have power supplies.  Most don't, in fact, it's just this

The manufactuers of those that don't are being awfully quiet about what
sounds like a rather impressive feature...

> Additionally, since the regulator is external to the codec (as it
> physically cuts 3.3V from the power supply), it doesn't make sense to
> put it in the codec driver.

I'm not sure you've quite understood what the regulator API is there
for.
Sean Cross June 19, 2014, 1:34 a.m. UTC | #4
On 06/18/14 18:31, Mark Brown wrote:
> On Wed, Jun 18, 2014 at 06:22:52PM +0800, Sean Cross wrote:
>> On 06/18/14 18:02, Mark Brown wrote:
>>> This should be handled by the clock bindings not open coded in the
>>> driver - leaving this here most likely won't play nicely when the clock
>>> API can configure the defaults for the tree.  There is supposed to be
>>> support for setting default clock trees going in (or perhaps already in)
>>> the clock bindings.
>> Can you give me more information on it?  Currently, it looks like most
>> boards use a 24 MHz clock, judging from this comment in
>> mach-imx/clk-imx6q.c:
> Look at the clock API, this stuff was introduced in the last merge
> window if it's there yet at all.
I don't see anything mentioning it.  If I remove the parenting calls
then the es8328 will refuse to run as its clock is at 66 MHz rather than
the requested 22.5792 MHz.  But since you say the default clock tree
support is going to be merged, I'd like to try using that code.  Is this
the "assigned-clock-parents" patch you're referring to, or is there a
patchset that will automatically reparent as necessary?

>> This codec requires the more unusual 22.5792 MHz clock.  What is the
>> appropriate method of obtaining this particular frequency?
> clk_set_rate() on the directly connected clock, the problem is fiddling
> about with the parenting rather than setting the rate.
>
>>> No, this is broken.  The CODEC should request its own supplies which
>>> need to correspond to the supplies the physical device has and failing
>>> to get the supplies should be a fatal error unless the device works
>>> without power (in which case why bother enablin them at all?).
>> Not all codecs have power supplies.  Most don't, in fact, it's just this
> The manufactuers of those that don't are being awfully quiet about what
> sounds like a rather impressive feature...
>
>> Additionally, since the regulator is external to the codec (as it
>> physically cuts 3.3V from the power supply), it doesn't make sense to
>> put it in the codec driver.
> I'm not sure you've quite understood what the regulator API is there
> for.
I'm having trouble understanding where the separating line is between
machine drivers and codec drivers.  A random sampling of codec drivers
doesn't yield any drivers grabbing their own power switches.  Is that an
oversight?  Should every codec driver include at least one regulator to
describe its power source?

If you like, I can move the regulator from the machine driver to the
codec driver, and make it non-optional.  But we've done designs in the
past where it just hangs off the 3.3V rail where it's non-switchable,
and the concept of describing a regulator seemed overkill.


Sean
Mark Brown June 19, 2014, 10:39 a.m. UTC | #5
On Thu, Jun 19, 2014 at 09:34:19AM +0800, Sean Cross wrote:
> On 06/18/14 18:31, Mark Brown wrote:

> > Look at the clock API, this stuff was introduced in the last merge
> > window if it's there yet at all.

> support is going to be merged, I'd like to try using that code.  Is this
> the "assigned-clock-parents" patch you're referring to, or is there a
> patchset that will automatically reparent as necessary?

assigned-clock-parents sounds like it's what I was thinking of.

> machine drivers and codec drivers.  A random sampling of codec drivers
> doesn't yield any drivers grabbing their own power switches.  Is that an

$ grep regulator_get sound/soc/codecs/*.c | wc -l 
19
$ grep regulator_bulk_get sound/soc/codecs/*.c | wc -l
32

Please put a bit more effort into trying to understand what you're
doing, the common patterns, the terminology and so on - people will
be much more willing to help if it appears that there is active
engagement on your part.
Charles Keepax June 19, 2014, 2:27 p.m. UTC | #6
On Thu, Jun 19, 2014 at 09:34:19AM +0800, Sean Cross wrote:
> On 06/18/14 18:31, Mark Brown wrote:
> > On Wed, Jun 18, 2014 at 06:22:52PM +0800, Sean Cross wrote:
> >> On 06/18/14 18:02, Mark Brown wrote:
> >>> No, this is broken.  The CODEC should request its own supplies which
> >>> need to correspond to the supplies the physical device has and failing
> >>> to get the supplies should be a fatal error unless the device works
> >>> without power (in which case why bother enablin them at all?).
> >> Not all codecs have power supplies.  Most don't, in fact, it's just this
> > The manufactuers of those that don't are being awfully quiet about what
> > sounds like a rather impressive feature...
> >
> >> Additionally, since the regulator is external to the codec (as it
> >> physically cuts 3.3V from the power supply), it doesn't make sense to
> >> put it in the codec driver.
> > I'm not sure you've quite understood what the regulator API is there
> > for.
> I'm having trouble understanding where the separating line is between
> machine drivers and codec drivers.  A random sampling of codec drivers
> doesn't yield any drivers grabbing their own power switches.  Is that an
> oversight?  Should every codec driver include at least one regulator to
> describe its power source?
> 
> If you like, I can move the regulator from the machine driver to the
> codec driver, and make it non-optional.  But we've done designs in the
> past where it just hangs off the 3.3V rail where it's non-switchable,
> and the concept of describing a regulator seemed overkill.
> 
> 
> Sean
> 

I think for your power switch you should be able to use the
existing gpio regulator binding, so you shouldn't need to
actually write any code.

Take for example wm2200.c, this registers two supplies for the
CODEC through DAPM:

SND_SOC_DAPM_REGULATOR_SUPPLY("CPVDD", 20, 0),
SND_SOC_DAPM_REGULATOR_SUPPLY("AVDD", 20, 0),

These are then hooked up to whatever parts of the CODEC require
power from them. CPVDD powers the charge pumps so is only hooked
up to those, AVDD is the main analogue power for the chip so is
needed whenever the CODEC is up, for such supplies one approach
is to just to hook them up to all the CODEC inputs and outputs,
which is what we do here.

You mention that your power gate might only exist on some
hardware. This hardware differences are what device tree is there
to describe. So lets say your CODEC has a main power supply called
AVDD as in my example above:

codec_pwr: regulator@0 {
	compatble = "regulator-fixed";

	... fixed regulator stuff ...
};

codec_pwr_switch: regulator@0 {
	compatible = "regulator-gpio";

	... regulator gpio stuff ...
};

/* Board with switch */
codec: es8328@11 {
       compatible = "es8328";
       reg = <0x11>;
	AVDD-supply = <&codec_pwr_switch>;
};

/* Board without switch */
codec: es8328@11 {
       compatible = "es8328";
       reg = <0x11>;
	AVDD-supply = <&codec_pwr>;
};

Thanks,
Charles
Mark Brown June 19, 2014, 3:13 p.m. UTC | #7
On Thu, Jun 19, 2014 at 03:27:37PM +0100, Charles Keepax wrote:

> You mention that your power gate might only exist on some
> hardware. This hardware differences are what device tree is there
> to describe. So lets say your CODEC has a main power supply called
> AVDD as in my example above:

FWIW the datasheet I just googled appears to say there are power
supplies AVDD, DVDD, PVDD and HPVDD - all four of them should appear in
the driver.

Patch
diff mbox

diff --git a/Documentation/devicetree/bindings/sound/imx-audio-es8328.txt b/Documentation/devicetree/bindings/sound/imx-audio-es8328.txt
new file mode 100644
index 0000000..9498e42
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/imx-audio-es8328.txt
@@ -0,0 +1,68 @@ 
+Freescale i.MX audio complex with ES8328 codec
+
+Required properties:
+- compatible : "fsl,imx-audio-es8328"
+- model : The user-visible name of this sound complex
+- clocks : A list of clocks required to run the codec at 22.5792 MHz:
+- clock-names : Descriptive names of the various clocks:
+
+    clk_sel : Main audio PLL source selector
+    clk_src : Main audio PLL
+    clk_post_div : Clock source, after a 1-32 divider
+    clk : The final clock stage before reaching output
+    output_clk : The resulting output clock frequency
+
+- ssi-controller : The phandle of the i.MX SSI controller
+- jack-gpio : Optional GPIO for headphone jack
+- codec-supply : Power regulator for audio codec chip
+- audio-amp-supply : Power regulator for speaker amps
+- audio-codec : The phandle of the ES8328 audio codec
+- audio-routing : A list of the connections between audio components.
+  Each entry is a pair of strings, the first being the connection's sink,
+  the second being the connection's source. Valid names could be power
+  supplies, ES8328 pins, and the jacks on the board:
+
+  Power supplies:
+   * audio-amp
+   * codec
+
+  ES8328 pins:
+   * LOUT1
+   * LOUT2
+   * ROUT1
+   * ROUT2
+
+  Board connectors:
+   * Headphone
+   * Speaker
+
+- mux-int-port : The internal port of the i.MX audio muxer (AUDMUX)
+- mux-ext-port : The external port of the i.MX audio muxer
+
+Note: The AUDMUX port numbering should start at 1, which is consistent with
+hardware manual.
+
+Example:
+
+sound {
+	compatible = "fsl,imx-audio-es8328";
+	model = "imx-audio-es8328";
+	clocks = <&clks 169>, <&clks 57>, <&clks 173>,
+		 <&clks 203>, <&clks 201>;
+	clock-names = "cko1", "cko1_sel", "pll4_audio",
+		      "pll4_post_div", "cko_sel";
+	ssi-controller = <&ssi1>;
+	audio-codec = <&codec>;
+	jack-gpio = <&gpio5 15 0>;
+	codec-supply = <&reg_audio_codec>;
+	audio-amp-supply = <&reg_audio_amp>;
+	audio-routing =
+		"Speaker", "LOUT2",
+		"Speaker", "ROUT2",
+		"Speaker", "audio-amp",
+		"Headphone", "ROUT1",
+		"Headphone", "LOUT1";
+	mux-int-port = <1>;
+	mux-ext-port = <3>;
+};
+
diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig
index 3793362..c0ace69 100644
--- a/sound/soc/fsl/Kconfig
+++ b/sound/soc/fsl/Kconfig
@@ -229,6 +229,20 @@  config SND_SOC_IMX_WM8962
 	  Say Y if you want to add support for SoC audio on an i.MX board with
 	  a wm8962 codec.
 
+config SND_SOC_IMX_ES8328
+	tristate "SoC Audio support for i.MX boards with the ES8328 codec"
+	depends on OF && (I2C || SPI)
+	select SND_SOC_ES8328_I2C if I2C
+	select SND_SOC_ES8328_SPI if SPI_MASTER
+	select SND_SOC_IMX_PCM_DMA
+	select SND_SOC_IMX_AUDMUX
+	select SND_SOC_FSL_SSI
+	select SND_SOC_FSL_UTILS
+	select SND_SOC_IMX_PCM_FIQ
+	help
+	  Say Y if you want to add support for the ES8328 audio codec connected
+	  via SSI/I2S over either SPI or I2C.
+
 config SND_SOC_IMX_SGTL5000
 	tristate "SoC Audio support for i.MX boards with sgtl5000"
 	depends on OF && I2C
diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile
index db254e3..e561766 100644
--- a/sound/soc/fsl/Makefile
+++ b/sound/soc/fsl/Makefile
@@ -48,6 +48,7 @@  snd-soc-eukrea-tlv320-objs := eukrea-tlv320.o
 snd-soc-phycore-ac97-objs := phycore-ac97.o
 snd-soc-mx27vis-aic32x4-objs := mx27vis-aic32x4.o
 snd-soc-wm1133-ev1-objs := wm1133-ev1.o
+snd-soc-imx-es8328-objs := imx-es8328.o
 snd-soc-imx-sgtl5000-objs := imx-sgtl5000.o
 snd-soc-imx-wm8962-objs := imx-wm8962.o
 snd-soc-imx-spdif-objs := imx-spdif.o
@@ -57,6 +58,7 @@  obj-$(CONFIG_SND_SOC_EUKREA_TLV320) += snd-soc-eukrea-tlv320.o
 obj-$(CONFIG_SND_SOC_PHYCORE_AC97) += snd-soc-phycore-ac97.o
 obj-$(CONFIG_SND_SOC_MX27VIS_AIC32X4) += snd-soc-mx27vis-aic32x4.o
 obj-$(CONFIG_SND_MXC_SOC_WM1133_EV1) += snd-soc-wm1133-ev1.o
+obj-$(CONFIG_SND_SOC_IMX_ES8328) += snd-soc-imx-es8328.o
 obj-$(CONFIG_SND_SOC_IMX_SGTL5000) += snd-soc-imx-sgtl5000.o
 obj-$(CONFIG_SND_SOC_IMX_WM8962) += snd-soc-imx-wm8962.o
 obj-$(CONFIG_SND_SOC_IMX_SPDIF) += snd-soc-imx-spdif.o
diff --git a/sound/soc/fsl/imx-es8328.c b/sound/soc/fsl/imx-es8328.c
new file mode 100644
index 0000000..ccfdc5d
--- /dev/null
+++ b/sound/soc/fsl/imx-es8328.c
@@ -0,0 +1,359 @@ 
+/*
+ * Copyright 2012 Freescale Semiconductor, Inc.
+ * Copyright 2012 Linaro Ltd.
+ *
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/i2c.h>
+#include <linux/clk.h>
+#include <linux/gpio.h>
+#include <linux/of_gpio.h>
+#include <linux/regulator/consumer.h>
+#include <linux/clk-provider.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#include "imx-audmux.h"
+
+#define DAI_NAME_SIZE	32
+#define IMX6Q_SYSCLK 0x00
+
+struct imx_es8328_data {
+	struct device *dev;
+	struct snd_soc_dai_link dai;
+	struct snd_soc_card card;
+	struct regulator *codec_regulator;
+	char codec_dai_name[DAI_NAME_SIZE];
+	char platform_name[DAI_NAME_SIZE];
+	struct clk *codec_clk;
+	struct clk *codec_clk_src;
+	struct clk *codec_clk_sel;
+	struct clk *codec_clk_post_div;
+	struct clk *output_clk;
+	unsigned int clk_freq_src;
+	unsigned int clk_frequency;
+	int power_gpio;
+	int jack_gpio;
+};
+
+static struct snd_soc_jack_gpio headset_jack_gpios[] = {
+	{
+		.gpio = -1,
+		.name = "headset-gpio",
+		.report = SND_JACK_HEADSET,
+		.invert = 0,
+		.debounce_time = 200,
+	},
+};
+
+static struct snd_soc_jack headset_jack;
+
+static int imx_es8328_dai_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct imx_es8328_data *data = container_of(rtd->card,
+					struct imx_es8328_data, card);
+	struct device *dev = rtd->card->dev;
+	int ret;
+
+	ret = snd_soc_dai_set_sysclk(rtd->codec_dai, IMX6Q_SYSCLK,
+				     data->clk_frequency, SND_SOC_CLOCK_IN);
+	if (ret) {
+		dev_err(dev, "could not set codec driver clock params to %d\n",
+			data->clk_frequency);
+		return ret;
+	}
+
+	/* Headphone jack detection */
+	if (gpio_is_valid(data->jack_gpio)) {
+		ret = snd_soc_jack_new(rtd->codec, "Headphone",
+				       SND_JACK_HEADPHONE | SND_JACK_BTN_0,
+				       &headset_jack);
+		if (ret)
+			return ret;
+
+		headset_jack_gpios[0].gpio = data->jack_gpio;
+		ret = snd_soc_jack_add_gpios(&headset_jack,
+					     ARRAY_SIZE(headset_jack_gpios),
+					     headset_jack_gpios);
+	}
+
+	return ret;
+}
+
+static const struct snd_soc_dapm_widget imx_es8328_dapm_widgets[] = {
+	SND_SOC_DAPM_MIC("Mic Jack", NULL),
+	SND_SOC_DAPM_HP("Headphone", NULL),
+	SND_SOC_DAPM_SPK("Speaker", NULL),
+	SND_SOC_DAPM_REGULATOR_SUPPLY("audio-amp", 1, 0),
+};
+
+static int imx_set_frequency(struct imx_es8328_data *data, int freq)
+{
+	int ret;
+
+	ret = clk_set_parent(data->output_clk, data->codec_clk);
+	if (ret) {
+		dev_err(data->dev, "unable to set clk output");
+		return ret;
+	}
+
+	ret = clk_set_parent(data->codec_clk_sel, data->codec_clk_post_div);
+	if (ret) {
+		dev_err(data->dev, "unable to set clk parent");
+		return ret;
+	}
+
+	data->clk_freq_src = clk_round_rate(data->codec_clk_src, freq * 32);
+	data->clk_frequency = clk_round_rate(data->codec_clk, freq);
+	dev_dbg(data->dev, "clock source frequency: %d\n", data->clk_freq_src);
+	dev_dbg(data->dev, "clock frequency: %d\n", data->clk_frequency);
+
+	ret = clk_set_rate(data->codec_clk_src, data->clk_freq_src);
+	if (ret) {
+		dev_err(data->dev, "unable to set source clock rate\n");
+		return ret;
+	}
+
+	ret = clk_set_rate(data->codec_clk, data->clk_frequency);
+	if (ret) {
+		dev_err(data->dev, "unable to set codec clock rate\n");
+		return ret;
+	}
+
+	ret = clk_prepare_enable(data->codec_clk);
+	if (ret) {
+		dev_err(data->dev, "unable to prepare codec clk\n");
+		return ret;
+	}
+
+	ret = clk_prepare_enable(data->codec_clk_src);
+	if (ret) {
+		dev_err(data->dev, "unable to prepare codec clk source\n");
+		return ret;
+	}
+	return ret;
+}
+
+static int imx_es8328_probe(struct platform_device *pdev)
+{
+	struct device_node *np = pdev->dev.of_node;
+	struct device_node *ssi_np, *codec_np;
+	struct platform_device *ssi_pdev;
+	struct imx_es8328_data *data;
+	int int_port, ext_port;
+	int ret;
+	struct device *dev = &pdev->dev;
+
+	ret = of_property_read_u32(np, "mux-int-port", &int_port);
+	if (ret) {
+		dev_err(&pdev->dev, "mux-int-port missing or invalid\n");
+		return ret;
+	}
+	ret = of_property_read_u32(np, "mux-ext-port", &ext_port);
+	if (ret) {
+		dev_err(&pdev->dev, "mux-ext-port missing or invalid\n");
+		return ret;
+	}
+
+	/*
+	 * The port numbering in the hardware manual starts at 1, while
+	 * the audmux API expects it starts at 0.
+	 */
+	int_port--;
+	ext_port--;
+	ret = imx_audmux_v2_configure_port(int_port,
+			IMX_AUDMUX_V2_PTCR_SYN |
+			IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) |
+			IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) |
+			IMX_AUDMUX_V2_PTCR_TFSDIR |
+			IMX_AUDMUX_V2_PTCR_TCLKDIR,
+			IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port));
+	if (ret) {
+		dev_err(&pdev->dev, "audmux internal port setup failed\n");
+		return ret;
+	}
+	ret = imx_audmux_v2_configure_port(ext_port,
+			IMX_AUDMUX_V2_PTCR_SYN,
+			IMX_AUDMUX_V2_PDCR_RXDSEL(int_port));
+	if (ret) {
+		dev_err(&pdev->dev, "audmux external port setup failed\n");
+		return ret;
+	}
+
+	ssi_np = of_parse_phandle(pdev->dev.of_node, "ssi-controller", 0);
+	codec_np = of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
+	if (!ssi_np || !codec_np) {
+		dev_err(&pdev->dev, "phandle missing or invalid\n");
+		ret = -EINVAL;
+		goto fail;
+	}
+
+	ssi_pdev = of_find_device_by_node(ssi_np);
+	if (!ssi_pdev) {
+		dev_err(&pdev->dev, "failed to find SSI platform device\n");
+		ret = -EINVAL;
+		goto fail;
+	}
+
+	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+	if (!data) {
+		ret = -ENOMEM;
+		goto fail;
+	}
+
+	data->dev = dev;
+
+	data->jack_gpio = of_get_named_gpio(pdev->dev.of_node,
+				"jack-gpio", 0);
+
+	data->power_gpio = of_get_named_gpio(pdev->dev.of_node,
+				"power-gpio", 0);
+	if (gpio_is_valid(data->power_gpio))
+		devm_gpio_request_one(&pdev->dev, data->power_gpio,
+				    GPIOF_OUT_INIT_HIGH,
+				    "audio codec power switch");
+
+	/* Setup clocks */
+	data->codec_clk = devm_clk_get(dev, "clk");
+	if (IS_ERR(data->codec_clk)) {
+		dev_err(dev,
+			"codec clock missing or invalid\n");
+		goto fail;
+	}
+
+	data->codec_clk_sel = devm_clk_get(dev, "clk_sel");
+	if (IS_ERR(data->codec_clk_sel)) {
+		dev_err(dev,
+			"codec clock select missing or invalid\n");
+		goto fail;
+	}
+
+	data->codec_clk_src = devm_clk_get(dev, "clk_src");
+	if (IS_ERR(data->codec_clk_src)) {
+		dev_err(dev,
+			"codec clock source missing or invalid\n");
+		goto fail;
+	}
+
+	data->codec_clk_post_div = devm_clk_get(dev, "clk_post_div");
+	if (IS_ERR(data->codec_clk_post_div)) {
+		dev_err(dev,
+			"codec clock post-div missing or invalid\n");
+		goto fail;
+	}
+
+	data->output_clk = devm_clk_get(dev, "output_clk");
+	if (IS_ERR(data->output_clk)) {
+		dev_err(dev,
+			"system clock missing or invalid\n");
+		goto fail;
+	}
+
+	ret = imx_set_frequency(data, 22579200);
+	if (ret)
+		goto fail;
+
+	data->codec_regulator = devm_regulator_get(dev, "codec");
+	if (IS_ERR(data->codec_regulator)) {
+		dev_err(dev, "No codec regulator\n");
+		data->codec_regulator = NULL;
+	} else {
+		ret = regulator_enable(data->codec_regulator);
+		if (ret)
+			dev_err(dev,
+				"Unable to enable codec regulator: %d\n", ret);
+	}
+
+	data->dai.name = "hifi";
+	data->dai.stream_name = "hifi";
+	data->dai.codec_dai_name = "es8328-hifi-analog";
+	data->dai.codec_of_node = codec_np;
+	data->dai.cpu_of_node = ssi_np;
+	data->dai.platform_of_node = ssi_np;
+	data->dai.init = &imx_es8328_dai_init;
+	data->dai.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+			    SND_SOC_DAIFMT_CBM_CFM;
+
+	data->card.dev = &pdev->dev;
+	data->card.dapm_widgets = imx_es8328_dapm_widgets;
+	data->card.num_dapm_widgets = ARRAY_SIZE(imx_es8328_dapm_widgets);
+	ret = snd_soc_of_parse_card_name(&data->card, "model");
+	if (ret)
+		goto fail;
+	ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing");
+	if (ret)
+		goto fail;
+	data->card.num_links = 1;
+	data->card.owner = THIS_MODULE;
+	data->card.dai_link = &data->dai;
+
+	ret = snd_soc_register_card(&data->card);
+	if (ret)
+		goto fail;
+
+	platform_set_drvdata(pdev, data);
+fail:
+	if (ssi_np)
+		of_node_put(ssi_np);
+	if (codec_np)
+		of_node_put(codec_np);
+
+	return ret;
+}
+
+static int imx_es8328_remove(struct platform_device *pdev)
+{
+	struct imx_es8328_data *data = platform_get_drvdata(pdev);
+
+	snd_soc_jack_free_gpios(&headset_jack, ARRAY_SIZE(headset_jack_gpios),
+				headset_jack_gpios);
+	if (data->codec_regulator) {
+		int ret;
+
+		ret = regulator_disable(data->codec_regulator);
+		if (ret)
+			dev_err(&pdev->dev,
+				"Unable to disable codec regulator: %d\n", ret);
+	}
+
+	if (data->codec_clk)
+		clk_disable_unprepare(data->codec_clk);
+
+	if (data->codec_clk_src)
+		clk_disable_unprepare(data->codec_clk_src);
+
+	snd_soc_unregister_card(&data->card);
+
+	return 0;
+}
+
+static const struct of_device_id imx_es8328_dt_ids[] = {
+	{ .compatible = "fsl,imx-audio-es8328", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_es8328_dt_ids);
+
+static struct platform_driver imx_es8328_driver = {
+	.driver = {
+		.name = "imx-es8328",
+		.owner = THIS_MODULE,
+		.of_match_table = imx_es8328_dt_ids,
+	},
+	.probe = imx_es8328_probe,
+	.remove = imx_es8328_remove,
+};
+module_platform_driver(imx_es8328_driver);
+
+MODULE_AUTHOR("Sean Cross <xobs@kosagi.com>");
+MODULE_DESCRIPTION("Kosagi i.MX6 ES8328 ASoC machine driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:imx-audio-es8328");