[PATCH 65/133] [Jaunty SRU] ARM.imx51 Freescale:ENGR00111898-2 wm8350: soc audio support

Brad Figg brad.figg at canonical.com
Thu Jul 9 16:48:55 UTC 2009


Porting wm8350 audio codec support from 2.6.29.
Porting 3stack wm8350 audio support from 2.6.26.
Add LineIn --> HP loopback

Signed-off-by: Alan Tull <r80115 at freescale.com>
Signed-off-by: Brad Figg <brad.figg at canonical.com>
---
 sound/soc/codecs/wm8350.c         |  223 +++++-------
 sound/soc/imx/Kconfig             |    9 +
 sound/soc/imx/Makefile            |    2 +
 sound/soc/imx/imx-3stack-wm8350.c |  714 +++++++++++++++++++++++++++++++++++++
 4 files changed, 818 insertions(+), 130 deletions(-)
 create mode 100644 sound/soc/imx/imx-3stack-wm8350.c

diff --git a/sound/soc/codecs/wm8350.c b/sound/soc/codecs/wm8350.c
index e3989d4..b15a4f0 100644
--- a/sound/soc/codecs/wm8350.c
+++ b/sound/soc/codecs/wm8350.c
@@ -35,14 +35,6 @@
 #define WM8350_RAMP_UP		1
 #define WM8350_RAMP_DOWN	2
 
-/* We only include the analogue supplies here; the digital supplies
- * need to be available well before this driver can be probed.
- */
-static const char *supply_names[] = {
-	"AVDD",
-	"HPVDD",
-};
-
 struct wm8350_output {
 	u16 active;
 	u16 left_vol;
@@ -52,12 +44,14 @@ struct wm8350_output {
 };
 
 struct wm8350_data {
-	struct snd_soc_codec codec;
 	struct wm8350_output out1;
 	struct wm8350_output out2;
-	struct regulator_bulk_data supplies[ARRAY_SIZE(supply_names)];
+	struct regulator_bulk_data supplies[2];
 };
 
+static int wm8350_set_bias_level(struct snd_soc_codec *codec,
+				 enum snd_soc_bias_level level);
+
 static unsigned int wm8350_codec_cache_read(struct snd_soc_codec *codec,
 					    unsigned int reg)
 {
@@ -506,6 +500,26 @@ static const struct snd_kcontrol_new wm8350_snd_controls[] = {
 		     14, 1, 1),
 };
 
+/* Same as snd_soc_dapm_put_volsw, but change power state */
+static int wm8350_dapm_put_volsw(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol);
+	struct snd_soc_codec *codec = widget->codec;
+	if (ucontrol->value.integer.value[0]) {
+		if (codec->bias_level != SND_SOC_BIAS_ON) {
+			wm8350_set_bias_level(codec, SND_SOC_BIAS_PREPARE);
+			snd_soc_dapm_put_volsw(kcontrol, ucontrol);
+			wm8350_set_bias_level(codec, SND_SOC_BIAS_ON);
+		} else
+			snd_soc_dapm_put_volsw(kcontrol, ucontrol);
+	} else {
+		snd_soc_dapm_put_volsw(kcontrol, ucontrol);
+		wm8350_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+	}
+	return 0;
+}
+
 /*
  * DAPM Controls
  */
@@ -514,8 +528,9 @@ static const struct snd_kcontrol_new wm8350_snd_controls[] = {
 static const struct snd_kcontrol_new wm8350_left_play_mixer_controls[] = {
 	SOC_DAPM_SINGLE("Playback Switch",
 			WM8350_LEFT_MIXER_CONTROL, 11, 1, 0),
-	SOC_DAPM_SINGLE("Left Bypass Switch",
-			WM8350_LEFT_MIXER_CONTROL, 2, 1, 0),
+	SOC_SINGLE_EXT("Left Bypass Switch",
+			WM8350_LEFT_MIXER_CONTROL, 2, 1, 0,
+			snd_soc_dapm_get_volsw, wm8350_dapm_put_volsw),
 	SOC_DAPM_SINGLE("Right Playback Switch",
 			WM8350_LEFT_MIXER_CONTROL, 12, 1, 0),
 	SOC_DAPM_SINGLE("Left Sidetone Switch",
@@ -528,8 +543,9 @@ static const struct snd_kcontrol_new wm8350_left_play_mixer_controls[] = {
 static const struct snd_kcontrol_new wm8350_right_play_mixer_controls[] = {
 	SOC_DAPM_SINGLE("Playback Switch",
 			WM8350_RIGHT_MIXER_CONTROL, 12, 1, 0),
-	SOC_DAPM_SINGLE("Right Bypass Switch",
-			WM8350_RIGHT_MIXER_CONTROL, 3, 1, 0),
+	SOC_SINGLE_EXT("Right Bypass Switch",
+			WM8350_RIGHT_MIXER_CONTROL, 3, 1, 0,
+			snd_soc_dapm_get_volsw, wm8350_dapm_put_volsw),
 	SOC_DAPM_SINGLE("Left Playback Switch",
 			WM8350_RIGHT_MIXER_CONTROL, 11, 1, 0),
 	SOC_DAPM_SINGLE("Left Sidetone Switch",
@@ -798,14 +814,14 @@ static int wm8350_add_widgets(struct snd_soc_codec *codec)
 					wm8350_dapm_widgets,
 					ARRAY_SIZE(wm8350_dapm_widgets));
 	if (ret != 0) {
-		dev_err(codec->dev, "dapm control register failed\n");
+		pr_err("wm8350: dapm control register failed\n");
 		return ret;
 	}
 
 	/* set up audio paths */
 	ret = snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
 	if (ret != 0) {
-		dev_err(codec->dev, "DAPM route register failed\n");
+		pr_err("wm8350: DAPM route register failed\n");
 		return ret;
 	}
 
@@ -964,10 +980,11 @@ static int wm8350_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
 	return 0;
 }
 
-static int wm8350_pcm_trigger(struct snd_pcm_substream *substream,
-			      int cmd, struct snd_soc_dai *codec_dai)
+static int wm8350_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
 {
-	struct snd_soc_codec *codec = codec_dai->codec;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_device *socdev = rtd->socdev;
+	struct snd_soc_codec *codec = socdev->codec;
 	int master = wm8350_codec_cache_read(codec, WM8350_AI_DAC_CONTROL) &
 	    WM8350_BCLK_MSTR;
 	int enabled = 0;
@@ -988,8 +1005,7 @@ static int wm8350_pcm_trigger(struct snd_pcm_substream *substream,
 	}
 
 	if (!enabled) {
-		dev_err(codec->dev,
-		       "%s: invalid audio path - no clocks available\n",
+		pr_err("wm8350: %s: invalid audio path - no clocks available\n",
 		       __func__);
 		return -EINVAL;
 	}
@@ -997,10 +1013,11 @@ static int wm8350_pcm_trigger(struct snd_pcm_substream *substream,
 }
 
 static int wm8350_pcm_hw_params(struct snd_pcm_substream *substream,
-				struct snd_pcm_hw_params *params,
-				struct snd_soc_dai *codec_dai)
+				struct snd_pcm_hw_params *params)
 {
-	struct snd_soc_codec *codec = codec_dai->codec;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_device *socdev = rtd->socdev;
+	struct snd_soc_codec *codec = socdev->codec;
 	u16 iface = wm8350_codec_read(codec, WM8350_AI_FORMATING) &
 	    ~WM8350_AIF_WL_MASK;
 
@@ -1124,10 +1141,10 @@ static int wm8350_set_fll(struct snd_soc_dai *codec_dai,
 	fll_1 = wm8350_codec_read(codec, WM8350_FLL_CONTROL_1) &
 	    ~(WM8350_FLL_OUTDIV_MASK | WM8350_FLL_RSP_RATE_MASK | 0xc000);
 	wm8350_codec_write(codec, WM8350_FLL_CONTROL_1,
-			   fll_1 | (fll_div.div << 8) | 0x50);
+			   fll_1 | (fll_div.div << 8));
 	wm8350_codec_write(codec, WM8350_FLL_CONTROL_2,
-			   (fll_div.ratio << 11) | (fll_div.
-						    n & WM8350_FLL_N_MASK));
+			   (fll_div.ratio << 11) |
+			   (fll_div.n & WM8350_FLL_N_MASK));
 	wm8350_codec_write(codec, WM8350_FLL_CONTROL_3, fll_div.k);
 	fll_4 = wm8350_codec_read(codec, WM8350_FLL_CONTROL_4) &
 	    ~(WM8350_FLL_FRAC | WM8350_FLL_SLOW_LOCK_REF);
@@ -1328,24 +1345,17 @@ static int wm8350_resume(struct platform_device *pdev)
 	return 0;
 }
 
-static struct snd_soc_codec *wm8350_codec;
-
-static int wm8350_probe(struct platform_device *pdev)
+static int wm8350_init(struct snd_soc_device *socdev)
 {
-	struct snd_soc_device *socdev = platform_get_drvdata(pdev);
-	struct snd_soc_codec *codec;
-	struct wm8350 *wm8350;
-	struct wm8350_data *priv;
+	struct snd_soc_codec *codec = socdev->codec;
+	struct wm8350 *wm8350 = codec->control_data;
+	struct wm8350_data *priv = codec->private_data;
 	int ret;
 	struct wm8350_output *out1;
 	struct wm8350_output *out2;
 
-	BUG_ON(!wm8350_codec);
-
-	socdev->codec = wm8350_codec;
-	codec = socdev->codec;
-	wm8350 = codec->control_data;
-	priv = codec->private_data;
+	/* Put the codec into reset if it wasn't already */
+	wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_5, WM8350_CODEC_ENA);
 
 	/* Enable the codec */
 	wm8350_set_bits(wm8350, WM8350_POWER_MGMT_5, WM8350_CODEC_ENA);
@@ -1368,22 +1378,14 @@ static int wm8350_probe(struct platform_device *pdev)
 			   WM8350_OUT2R_VOL_MASK) >> WM8350_OUT1R_VOL_SHIFT;
 	wm8350_reg_write(wm8350, WM8350_LOUT1_VOLUME, 0);
 	wm8350_reg_write(wm8350, WM8350_ROUT1_VOLUME, 0);
+	wm8350_set_bits(wm8350, WM8350_LOUT1_VOLUME, WM8350_OUT1_VU);
 	wm8350_reg_write(wm8350, WM8350_LOUT2_VOLUME, 0);
 	wm8350_reg_write(wm8350, WM8350_ROUT2_VOLUME, 0);
-
-	/* Latch VU bits & mute */
-	wm8350_set_bits(wm8350, WM8350_LOUT1_VOLUME,
-			WM8350_OUT1_VU | WM8350_OUT1L_MUTE);
-	wm8350_set_bits(wm8350, WM8350_LOUT2_VOLUME,
-			WM8350_OUT2_VU | WM8350_OUT2L_MUTE);
-	wm8350_set_bits(wm8350, WM8350_ROUT1_VOLUME,
-			WM8350_OUT1_VU | WM8350_OUT1R_MUTE);
-	wm8350_set_bits(wm8350, WM8350_ROUT2_VOLUME,
-			WM8350_OUT2_VU | WM8350_OUT2R_MUTE);
+	wm8350_set_bits(wm8350, WM8350_LOUT2_VOLUME, WM8350_OUT2_VU);
 
 	ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
 	if (ret < 0) {
-		dev_err(&pdev->dev, "failed to create pcms\n");
+		pr_err("wm8350: failed to create pcms\n");
 		return ret;
 	}
 
@@ -1392,9 +1394,9 @@ static int wm8350_probe(struct platform_device *pdev)
 
 	wm8350_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
 
-	ret = snd_soc_init_card(socdev);
+	ret = snd_soc_register_card(socdev);
 	if (ret < 0) {
-		dev_err(&pdev->dev, "failed to register card\n");
+		pr_err("wm8350: failed to register card\n");
 		goto card_err;
 	}
 
@@ -1410,6 +1412,7 @@ static int wm8350_remove(struct platform_device *pdev)
 {
 	struct snd_soc_device *socdev = platform_get_drvdata(pdev);
 	struct snd_soc_codec *codec = socdev->codec;
+	struct wm8350_data *priv = codec->private_data;
 	struct wm8350 *wm8350 = codec->control_data;
 	int ret;
 
@@ -1427,6 +1430,12 @@ static int wm8350_remove(struct platform_device *pdev)
 
 	wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_5, WM8350_CODEC_ENA);
 
+	snd_soc_free_pcms(socdev);
+	snd_soc_dapm_free(socdev);
+	regulator_bulk_free(ARRAY_SIZE(priv->supplies), priv->supplies);
+	kfree(codec->private_data);
+	kfree(codec);
+
 	return 0;
 }
 
@@ -1454,57 +1463,50 @@ struct snd_soc_dai wm8350_dai = {
 	 },
 	.ops = {
 		 .hw_params = wm8350_pcm_hw_params,
-		 .digital_mute = wm8350_mute,
 		 .trigger = wm8350_pcm_trigger,
-		 .set_fmt = wm8350_set_dai_fmt,
+	 },
+	.dai_ops = {
+		 .digital_mute = wm8350_mute,
 		 .set_sysclk = wm8350_set_dai_sysclk,
+		 .set_fmt = wm8350_set_dai_fmt,
 		 .set_pll = wm8350_set_fll,
 		 .set_clkdiv = wm8350_set_clkdiv,
 	 },
 };
 EXPORT_SYMBOL_GPL(wm8350_dai);
 
-struct snd_soc_codec_device soc_codec_dev_wm8350 = {
-	.probe = 	wm8350_probe,
-	.remove = 	wm8350_remove,
-	.suspend = 	wm8350_suspend,
-	.resume =	wm8350_resume,
-};
-EXPORT_SYMBOL_GPL(soc_codec_dev_wm8350);
-
-static int wm8350_codec_probe(struct platform_device *pdev)
+static int wm8350_probe(struct platform_device *pdev)
 {
-	struct wm8350 *wm8350 = platform_get_drvdata(pdev);
-	struct wm8350_data *priv;
+	struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+	struct wm8350 *wm8350 = socdev->codec_data;
+	struct wm8350_audio_platform_data *platform =
+		wm8350->codec.platform_data;
 	struct snd_soc_codec *codec;
-	int ret, i;
+	struct wm8350_data *priv;
+	int ret;
 
-	if (wm8350->codec.platform_data == NULL) {
-		dev_err(&pdev->dev, "No audio platform data supplied\n");
-		return -EINVAL;
-	}
+	codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+	if (codec == NULL)
+		return -ENOMEM;
 
+	socdev->codec = codec;
+
+	ret = -ENODEV;
 	priv = kzalloc(sizeof(struct wm8350_data), GFP_KERNEL);
 	if (priv == NULL)
-		return -ENOMEM;
+		goto err_codec;
 
-	for (i = 0; i < ARRAY_SIZE(supply_names); i++)
-		priv->supplies[i].supply = supply_names[i];
+	priv->supplies[0].supply = platform->regulator1;
+	priv->supplies[1].supply = platform->regulator2;
 
-	ret = regulator_bulk_get(wm8350->dev, ARRAY_SIZE(priv->supplies),
+	ret = regulator_bulk_get(&pdev->dev, ARRAY_SIZE(priv->supplies),
 				 priv->supplies);
 	if (ret != 0)
 		goto err_priv;
 
-	codec = &priv->codec;
-	wm8350->codec.codec = codec;
-
-	wm8350_dai.dev = &pdev->dev;
-
 	mutex_init(&codec->mutex);
 	INIT_LIST_HEAD(&codec->dapm_widgets);
 	INIT_LIST_HEAD(&codec->dapm_paths);
-	codec->dev = &pdev->dev;
 	codec->name = "WM8350";
 	codec->owner = THIS_MODULE;
 	codec->read = wm8350_codec_read;
@@ -1517,65 +1519,26 @@ static int wm8350_codec_probe(struct platform_device *pdev)
 	codec->private_data = priv;
 	codec->control_data = wm8350;
 
-	/* Put the codec into reset if it wasn't already */
-	wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_5, WM8350_CODEC_ENA);
-
 	INIT_DELAYED_WORK(&codec->delayed_work, wm8350_pga_work);
-	ret = snd_soc_register_codec(codec);
-	if (ret != 0)
-		goto err_supply;
 
-	wm8350_codec = codec;
+	wm8350_init(socdev);
 
-	ret = snd_soc_register_dai(&wm8350_dai);
-	if (ret != 0)
-		goto err_codec;
 	return 0;
 
-err_codec:
-	snd_soc_unregister_codec(codec);
-err_supply:
-	regulator_bulk_free(ARRAY_SIZE(priv->supplies), priv->supplies);
 err_priv:
-	kfree(priv);
-	wm8350_codec = NULL;
+	kfree(codec->private_data);
+err_codec:
+	kfree(codec);
 	return ret;
 }
 
-static int __devexit wm8350_codec_remove(struct platform_device *pdev)
-{
-	struct wm8350 *wm8350 = platform_get_drvdata(pdev);
-	struct snd_soc_codec *codec = wm8350->codec.codec;
-	struct wm8350_data *priv = codec->private_data;
-
-	snd_soc_unregister_dai(&wm8350_dai);
-	snd_soc_unregister_codec(codec);
-	regulator_bulk_free(ARRAY_SIZE(priv->supplies), priv->supplies);
-	kfree(priv);
-	wm8350_codec = NULL;
-	return 0;
-}
-
-static struct platform_driver wm8350_codec_driver = {
-	.driver = {
-		   .name = "wm8350-codec",
-		   .owner = THIS_MODULE,
-		   },
-	.probe = wm8350_codec_probe,
-	.remove = __devexit_p(wm8350_codec_remove),
+struct snd_soc_codec_device soc_codec_dev_wm8350 = {
+	.probe = 	wm8350_probe,
+	.remove = 	wm8350_remove,
+	.suspend = 	wm8350_suspend,
+	.resume =	wm8350_resume,
 };
-
-static __init int wm8350_init(void)
-{
-	return platform_driver_register(&wm8350_codec_driver);
-}
-module_init(wm8350_init);
-
-static __exit void wm8350_exit(void)
-{
-	platform_driver_unregister(&wm8350_codec_driver);
-}
-module_exit(wm8350_exit);
+EXPORT_SYMBOL_GPL(soc_codec_dev_wm8350);
 
 MODULE_DESCRIPTION("ASoC WM8350 driver");
 MODULE_AUTHOR("Liam Girdwood");
diff --git a/sound/soc/imx/Kconfig b/sound/soc/imx/Kconfig
index 4722515..26e66b4 100644
--- a/sound/soc/imx/Kconfig
+++ b/sound/soc/imx/Kconfig
@@ -17,6 +17,15 @@ config SND_MXC_SOC_IRAM
  help
    Say Y if you don't want Audio playback buffers in external ram
 
+config SND_SOC_IMX_3STACK_WM8350
+ tristate "SoC Audio support for IMX - WM8350"
+ depends on MFD_WM8350
+ select SND_MXC_SOC_SSI
+ select SND_SOC_WM8350
+ help
+   Say Y if you want to add support for SoC audio on IMX 3STACK
+   with the WM8350.
+
 config SND_SOC_IMX_3STACK_SGTL5000
  tristate "SoC Audio support for IMX - SGTL5000"
  select SND_MXC_SOC_SSI
diff --git a/sound/soc/imx/Makefile b/sound/soc/imx/Makefile
index a9d959e..b0d41e0 100644
--- a/sound/soc/imx/Makefile
+++ b/sound/soc/imx/Makefile
@@ -6,6 +6,8 @@ obj-$(CONFIG_SND_MXC_SOC) += snd-soc-imx.o
 obj-$(CONFIG_SND_MXC_SOC_SSI) += snd-soc-imx-ssi.o
 
 # i.MX Machine Support
+snd-soc-imx-3stack-wm8350-objs := imx-3stack-wm8350.o
+obj-$(CONFIG_SND_SOC_IMX_3STACK_WM8350) += snd-soc-imx-3stack-wm8350.o
 snd-soc-imx-3stack-sgtl5000-objs := imx-3stack-sgtl5000.o
 obj-$(CONFIG_SND_SOC_IMX_3STACK_SGTL5000) += snd-soc-imx-3stack-sgtl5000.o
 
diff --git a/sound/soc/imx/imx-3stack-wm8350.c b/sound/soc/imx/imx-3stack-wm8350.c
new file mode 100644
index 0000000..b7895df
--- /dev/null
+++ b/sound/soc/imx/imx-3stack-wm8350.c
@@ -0,0 +1,714 @@
+/*
+ * imx-3stack-wm8350.c  --  i.MX 3Stack Driver for Wolfson WM8350 Codec
+ *
+ * Copyright 2007 Wolfson Microelectronics PLC.
+ * Copyright 2007-2009 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * Author: Liam Girdwood
+ *         liam.girdwood at wolfsonmicro.com or linux at wolfsonmicro.com
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ *  Revision history
+ *    19th Jun 2007   Initial version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/bitops.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/irq.h>
+#include <linux/io.h>
+#include <linux/err.h>
+#include <linux/mfd/wm8350/core.h>
+#include <linux/mfd/wm8350/audio.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+
+#include <mach/dma.h>
+#include <mach/spba.h>
+#include <mach/clock.h>
+#include <mach/mxc.h>
+
+#include "../codecs/wm8350.h"
+#include "imx-ssi.h"
+#include "imx-pcm.h"
+
+void gpio_activate_audio_ports(void);
+
+/* SSI BCLK and LRC master */
+#define WM8350_SSI_MASTER	1
+
+struct imx_3stack_priv {
+	int lr_clk_active;
+	int playback_active;
+	int capture_active;
+	struct platform_device *pdev;
+	struct wm8350 *wm8350;
+};
+
+static struct imx_3stack_priv machine_priv;
+
+struct _wm8350_audio {
+	unsigned int channels;
+	snd_pcm_format_t format;
+	unsigned int rate;
+	unsigned int sysclk;
+	unsigned int bclkdiv;
+	unsigned int clkdiv;
+	unsigned int lr_rate;
+};
+
+/* in order of power consumption per rate (lowest first) */
+static const struct _wm8350_audio wm8350_audio[] = {
+	/* 16bit mono modes */
+
+	{1, SNDRV_PCM_FORMAT_S16_LE, 8000, 12288000,
+	WM8350_BCLK_DIV_48, WM8350_DACDIV_6, 32,},
+	{1, SNDRV_PCM_FORMAT_S16_LE, 16000, 12288000,
+	 WM8350_BCLK_DIV_24, WM8350_DACDIV_6, 32,},
+	{1, SNDRV_PCM_FORMAT_S16_LE, 32000, 12288000,
+	 WM8350_BCLK_DIV_12, WM8350_DACDIV_3, 32,},
+	{1, SNDRV_PCM_FORMAT_S16_LE, 48000, 12288000,
+	 WM8350_BCLK_DIV_8, WM8350_DACDIV_2, 32,},
+	{1, SNDRV_PCM_FORMAT_S16_LE, 96000, 24576000,
+	 WM8350_BCLK_DIV_8, WM8350_DACDIV_2, 32,},
+	{1, SNDRV_PCM_FORMAT_S16_LE, 11025, 11289600,
+	 WM8350_BCLK_DIV_32, WM8350_DACDIV_4, 32,},
+	{1, SNDRV_PCM_FORMAT_S16_LE, 22050, 11289600,
+	 WM8350_BCLK_DIV_16, WM8350_DACDIV_4, 32,},
+	{1, SNDRV_PCM_FORMAT_S16_LE, 44100, 11289600,
+	 WM8350_BCLK_DIV_8, WM8350_DACDIV_2, 32,},
+	{1, SNDRV_PCM_FORMAT_S16_LE, 88200, 22579200,
+	 WM8350_BCLK_DIV_8, WM8350_DACDIV_2, 32,},
+
+	/* 16 bit stereo modes */
+	{2, SNDRV_PCM_FORMAT_S16_LE, 8000, 12288000,
+	 WM8350_BCLK_DIV_48, WM8350_DACDIV_6, 32,},
+	{2, SNDRV_PCM_FORMAT_S16_LE, 16000, 12288000,
+	 WM8350_BCLK_DIV_24, WM8350_DACDIV_3, 32,},
+	{2, SNDRV_PCM_FORMAT_S16_LE, 32000, 12288000,
+	 WM8350_BCLK_DIV_12, WM8350_DACDIV_1_5, 32,},
+	{2, SNDRV_PCM_FORMAT_S16_LE, 48000, 12288000,
+	 WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,},
+	{2, SNDRV_PCM_FORMAT_S16_LE, 96000, 24576000,
+	 WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,},
+	{2, SNDRV_PCM_FORMAT_S16_LE, 11025, 11289600,
+	 WM8350_BCLK_DIV_32, WM8350_DACDIV_4, 32,},
+	{2, SNDRV_PCM_FORMAT_S16_LE, 22050, 11289600,
+	 WM8350_BCLK_DIV_16, WM8350_DACDIV_2, 32,},
+	{2, SNDRV_PCM_FORMAT_S16_LE, 44100, 11289600,
+	 WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,},
+	{2, SNDRV_PCM_FORMAT_S16_LE, 88200, 22579200,
+	 WM8350_BCLK_DIV_8, WM8350_DACDIV_1, 32,},
+
+	/* 24bit stereo modes */
+	{2, SNDRV_PCM_FORMAT_S24_LE, 48000, 12288000,
+	 WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,},
+	{2, SNDRV_PCM_FORMAT_S24_LE, 96000, 24576000,
+	 WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,},
+	{2, SNDRV_PCM_FORMAT_S24_LE, 44100, 11289600,
+	 WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,},
+	{2, SNDRV_PCM_FORMAT_S24_LE, 88200, 22579200,
+	 WM8350_BCLK_DIV_4, WM8350_DACDIV_1, 64,},
+};
+
+#if WM8350_SSI_MASTER
+static int imx_3stack_startup(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->socdev->codec;
+	struct wm8350 *wm8350 = codec->control_data;
+	struct imx_3stack_priv *priv = &machine_priv;
+
+	/* In master mode the LR clock can come from either the DAC or ADC.
+	 * We use the LR clock from whatever stream is enabled first.
+	 */
+
+	if (!priv->lr_clk_active) {
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			wm8350_clear_bits(wm8350, WM8350_CLOCK_CONTROL_2,
+					  WM8350_LRC_ADC_SEL);
+		else
+			wm8350_set_bits(wm8350, WM8350_CLOCK_CONTROL_2,
+					WM8350_LRC_ADC_SEL);
+	}
+	priv->lr_clk_active++;
+	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+		priv->capture_active = 1;
+	else
+		priv->playback_active = 1;
+	return 0;
+}
+#else
+#define imx_3stack_startup NULL
+#endif
+
+static int imx_3stack_audio_hw_params(struct snd_pcm_substream *substream,
+				      struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai_link *machine = rtd->dai;
+	struct snd_soc_dai *cpu_dai = machine->cpu_dai;
+	struct snd_soc_dai *codec_dai = machine->codec_dai;
+	struct imx_3stack_priv *priv = &machine_priv;
+	int ret = 0;
+	int i, found = 0;
+	snd_pcm_format_t format = params_format(params);
+	unsigned int rate = params_rate(params);
+	unsigned int channels = params_channels(params);
+	u32 dai_format;
+
+	/* only need to do this once as capture and playback are sync */
+	if (priv->lr_clk_active > 1)
+		return 0;
+
+	/* find the correct audio parameters */
+	for (i = 0; i < ARRAY_SIZE(wm8350_audio); i++) {
+		if (rate == wm8350_audio[i].rate &&
+		    format == wm8350_audio[i].format &&
+		    channels == wm8350_audio[i].channels) {
+			found = 1;
+			break;
+		}
+	}
+	if (!found) {
+		printk(KERN_ERR "%s: invalid params\n", __func__);
+		return -EINVAL;
+	}
+
+#if WM8350_SSI_MASTER
+	/* codec FLL input is 32768 kHz from MCLK */
+	codec_dai->dai_ops.set_pll(codec_dai, 0, 32768, wm8350_audio[i].sysclk);
+#else
+	/* codec FLL input is rate from DAC LRC */
+	codec_dai->dai_ops.set_pll(codec_dai, 0, rate, wm8350_audio[i].sysclk);
+#endif
+
+#if WM8350_SSI_MASTER
+	dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+	    SND_SOC_DAIFMT_CBM_CFM | SND_SOC_DAIFMT_SYNC;
+	if (channels == 2)
+		dai_format |= SND_SOC_DAIFMT_TDM;
+
+	/* set codec DAI configuration */
+	ret = snd_soc_dai_set_fmt(codec_dai, dai_format);
+	if (ret < 0)
+		return ret;
+
+	/* set cpu DAI configuration */
+	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+		dai_format &= ~SND_SOC_DAIFMT_INV_MASK;
+		/* Invert frame to switch mic from right channel to left */
+		dai_format |= SND_SOC_DAIFMT_NB_IF;
+	}
+	ret = snd_soc_dai_set_fmt(cpu_dai, dai_format);
+	if (ret < 0)
+		return ret;
+
+	/* set 32KHZ as the codec system clock for DAC and ADC */
+	snd_soc_dai_set_sysclk(codec_dai, WM8350_MCLK_SEL_PLL_32K,
+			       wm8350_audio[i].sysclk, SND_SOC_CLOCK_IN);
+#else
+	dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+	    SND_SOC_DAIFMT_CBS_CFS | SND_SOC_DAIFMT_SYNC;
+	if (channels == 2)
+		dai_format |= SND_SOC_DAIFMT_TDM;
+
+	/* set codec DAI configuration */
+	ret = snd_soc_dai_set_fmt(codec_dai, dai_format);
+	if (ret < 0)
+		return ret;
+
+	/* set cpu DAI configuration */
+	ret = snd_soc_dai_set_fmt(cpu_dai, dai_format);
+	if (ret < 0)
+		return ret;
+
+	/* set DAC LRC as the codec system clock for DAC and ADC */
+	snd_soc_dai_set_sysclk(codec_dai, WM8350_MCLK_SEL_PLL_DAC,
+			       wm8350_audio[i].sysclk, SND_SOC_CLOCK_IN);
+#endif
+
+	/* set i.MX active slot mask */
+	snd_soc_dai_set_tdm_slot(cpu_dai,
+				 channels == 1 ? 0xfffffffe : 0xfffffffc,
+				 channels);
+
+	/* set the SSI system clock as input (unused) */
+	snd_soc_dai_set_sysclk(cpu_dai, IMX_SSP_SYS_CLK, 0, SND_SOC_CLOCK_IN);
+
+	/* set codec BCLK division for sample rate */
+	snd_soc_dai_set_clkdiv(codec_dai, WM8350_BCLK_CLKDIV,
+			       wm8350_audio[i].bclkdiv);
+
+	/* DAI is synchronous and clocked with DAC LRCLK & ADC LRC */
+	snd_soc_dai_set_clkdiv(codec_dai,
+			       WM8350_DACLR_CLKDIV,
+			       wm8350_audio[i].lr_rate);
+	snd_soc_dai_set_clkdiv(codec_dai,
+			       WM8350_ADCLR_CLKDIV,
+			       wm8350_audio[i].lr_rate);
+
+	/* now configure DAC and ADC clocks */
+	snd_soc_dai_set_clkdiv(codec_dai,
+			       WM8350_DAC_CLKDIV, wm8350_audio[i].clkdiv);
+
+	snd_soc_dai_set_clkdiv(codec_dai,
+			       WM8350_ADC_CLKDIV, wm8350_audio[i].clkdiv);
+
+	return 0;
+}
+
+static void imx_3stack_shutdown(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_codec *codec = rtd->socdev->codec;
+	struct snd_soc_dai_link *machine = rtd->dai;
+	struct snd_soc_dai *codec_dai = machine->codec_dai;
+	struct imx_3stack_priv *priv = &machine_priv;
+	struct wm8350 *wm8350 = codec->control_data;
+
+	/* disable the PLL if there are no active Tx or Rx channels */
+	if (!codec_dai->active)
+		snd_soc_dai_set_pll(codec_dai, 0, 0, 0);
+	priv->lr_clk_active--;
+
+	/*
+	 * We need to keep track of active streams in master mode and
+	 * switch LRC source if necessary.
+	 */
+
+	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+		priv->capture_active = 0;
+	else
+		priv->playback_active = 0;
+
+	if (priv->capture_active)
+		wm8350_set_bits(wm8350, WM8350_CLOCK_CONTROL_2,
+				WM8350_LRC_ADC_SEL);
+	else if (priv->playback_active)
+		wm8350_clear_bits(wm8350, WM8350_CLOCK_CONTROL_2,
+				  WM8350_LRC_ADC_SEL);
+}
+
+/*
+ * imx_3stack WM8350 HiFi DAI operations.
+ */
+static struct snd_soc_ops imx_3stack_ops = {
+	.startup = imx_3stack_startup,
+	.shutdown = imx_3stack_shutdown,
+	.hw_params = imx_3stack_audio_hw_params,
+};
+
+/* need to refine these */
+static struct wm8350_audio_platform_data imx_3stack_wm8350_setup = {
+	.vmid_discharge_msecs = 1000,
+	.drain_msecs = 30,
+	.cap_discharge_msecs = 700,
+	.vmid_charge_msecs = 700,
+	.vmid_s_curve = WM8350_S_CURVE_SLOW,
+	.dis_out4 = WM8350_DISCHARGE_SLOW,
+	.dis_out3 = WM8350_DISCHARGE_SLOW,
+	.dis_out2 = WM8350_DISCHARGE_SLOW,
+	.dis_out1 = WM8350_DISCHARGE_SLOW,
+	.vroi_out4 = WM8350_TIE_OFF_500R,
+	.vroi_out3 = WM8350_TIE_OFF_500R,
+	.vroi_out2 = WM8350_TIE_OFF_500R,
+	.vroi_out1 = WM8350_TIE_OFF_500R,
+	.vroi_enable = 0,
+	.codec_current_on = WM8350_CODEC_ISEL_1_0,
+	.codec_current_standby = WM8350_CODEC_ISEL_0_5,
+	.codec_current_charge = WM8350_CODEC_ISEL_1_5,
+};
+
+static void imx_3stack_init_dam(int ssi_port, int dai_port)
+{
+	unsigned int ssi_ptcr = 0;
+	unsigned int dai_ptcr = 0;
+	unsigned int ssi_pdcr = 0;
+	unsigned int dai_pdcr = 0;
+	/* WM8350 uses SSI1 or SSI2 via AUDMUX port dai_port for audio */
+
+	/* reset port ssi_port & dai_port */
+	__raw_writel(0, DAM_PTCR(ssi_port));
+	__raw_writel(0, DAM_PTCR(dai_port));
+	__raw_writel(0, DAM_PDCR(ssi_port));
+	__raw_writel(0, DAM_PDCR(dai_port));
+
+	/* set to synchronous */
+	ssi_ptcr |= AUDMUX_PTCR_SYN;
+	dai_ptcr |= AUDMUX_PTCR_SYN;
+
+#if WM8350_SSI_MASTER
+	/* set Rx sources ssi_port <--> dai_port */
+	ssi_pdcr |= AUDMUX_PDCR_RXDSEL(dai_port);
+	dai_pdcr |= AUDMUX_PDCR_RXDSEL(ssi_port);
+
+	/* set Tx frame direction and source  dai_port--> ssi_port output */
+	ssi_ptcr |= AUDMUX_PTCR_TFSDIR;
+	ssi_ptcr |= AUDMUX_PTCR_TFSSEL(AUDMUX_FROM_TXFS, dai_port);
+
+	/* set Tx Clock direction and source dai_port--> ssi_port output */
+	ssi_ptcr |= AUDMUX_PTCR_TCLKDIR;
+	ssi_ptcr |= AUDMUX_PTCR_TCSEL(AUDMUX_FROM_TXFS, dai_port);
+#else
+	/* set Rx sources ssi_port <--> dai_port */
+	ssi_pdcr |= AUDMUX_PDCR_RXDSEL(dai_port);
+	dai_pdcr |= AUDMUX_PDCR_RXDSEL(ssi_port);
+
+	/* set Tx frame direction and source  ssi_port --> dai_port output */
+	dai_ptcr |= AUDMUX_PTCR_TFSDIR;
+	dai_ptcr |= AUDMUX_PTCR_TFSSEL(AUDMUX_FROM_TXFS, ssi_port);
+
+	/* set Tx Clock direction and source ssi_port--> dai_port output */
+	dai_ptcr |= AUDMUX_PTCR_TCLKDIR;
+	dai_ptcr |= AUDMUX_PTCR_TCSEL(AUDMUX_FROM_TXFS, ssi_port);
+#endif
+
+	__raw_writel(ssi_ptcr, DAM_PTCR(ssi_port));
+	__raw_writel(dai_ptcr, DAM_PTCR(dai_port));
+	__raw_writel(ssi_pdcr, DAM_PDCR(ssi_port));
+	__raw_writel(dai_pdcr, DAM_PDCR(dai_port));
+}
+
+static const struct snd_soc_dapm_route audio_map[] = {
+	/* SiMIC --> IN1LN (with automatic bias) via SP1 */
+	{"IN1RP", NULL, "Mic Bias"},
+	{"Mic Bias", NULL, "SiMIC"},
+
+	/* Mic 1 Jack --> IN1LN and IN1LP (with automatic bias) */
+	{"IN1LN", NULL, "Mic Bias"},
+	{"IN1LP", NULL, "Mic1 Jack"},
+	{"Mic Bias", NULL, "Mic1 Jack"},
+
+	/* Mic 2 Jack --> IN1RN and IN1RP (with automatic bias) */
+	{"IN1RN", NULL, "Mic2 Jack"},
+	{"IN1RP", NULL, "Mic Bias"},
+	{"Mic Bias", NULL, "Mic2 Jack"},
+
+	/* Line in Jack --> AUX (L+R) */
+	{"IN3R", NULL, "Line In Jack"},
+	{"IN3L", NULL, "Line In Jack"},
+
+	/* Out1 --> Headphone Jack */
+	{"Headphone Jack", NULL, "OUT1R"},
+	{"Headphone Jack", NULL, "OUT1L"},
+
+	/* Out1 --> Line Out Jack */
+	{"Line Out Jack", NULL, "OUT2R"},
+	{"Line Out Jack", NULL, "OUT2L"},
+};
+
+static int wm8350_jack_func;
+static int wm8350_spk_func;
+
+static void headphone_detect_handler(struct work_struct *work)
+{
+	struct imx_3stack_priv *priv = &machine_priv;
+	struct platform_device *pdev = priv->pdev;
+	struct wm8350 *wm8350 = priv->wm8350;
+
+	sysfs_notify(&pdev->dev.kobj, NULL, "headphone");
+	wm8350_unmask_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_R);
+}
+
+static DECLARE_DELAYED_WORK(hp_event, headphone_detect_handler);
+
+static void imx_3stack_jack_handler(struct wm8350 *wm8350, int irq, void *data)
+{
+	wm8350_mask_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_R);
+	schedule_delayed_work(&hp_event, msecs_to_jiffies(200));
+}
+
+static ssize_t show_headphone(struct device_driver *dev, char *buf)
+{
+	struct imx_3stack_priv *priv = &machine_priv;
+	u16 reg;
+
+	reg = wm8350_reg_read(priv->wm8350, WM8350_JACK_PIN_STATUS);
+
+	if (reg & WM8350_JACK_R_LVL)
+		strcpy(buf, "speaker\n");
+	else
+		strcpy(buf, "headphone\n");
+
+	return strlen(buf);
+}
+
+static DRIVER_ATTR(headphone, S_IRUGO | S_IWUSR, show_headphone, NULL);
+
+static const char *jack_function[] = { "off", "on"
+};
+
+static const char *spk_function[] = { "off", "on" };
+
+static const struct soc_enum wm8350_enum[] = {
+	SOC_ENUM_SINGLE_EXT(2, jack_function),
+	SOC_ENUM_SINGLE_EXT(2, spk_function),
+};
+
+static int wm8350_get_jack(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.enumerated.item[0] = wm8350_jack_func;
+	return 0;
+}
+
+static int wm8350_set_jack(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+	if (wm8350_jack_func == ucontrol->value.enumerated.item[0])
+		return 0;
+
+	wm8350_jack_func = ucontrol->value.enumerated.item[0];
+	if (wm8350_jack_func)
+		snd_soc_dapm_enable_pin(codec, "Headphone Jack");
+	else
+		snd_soc_dapm_disable_pin(codec, "Headphone Jack");
+
+	snd_soc_dapm_sync(codec);
+	return 1;
+}
+
+static int wm8350_get_spk(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.enumerated.item[0] = wm8350_spk_func;
+	return 0;
+}
+
+static int wm8350_set_spk(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+	if (wm8350_spk_func == ucontrol->value.enumerated.item[0])
+		return 0;
+
+	wm8350_spk_func = ucontrol->value.enumerated.item[0];
+	if (wm8350_spk_func)
+		snd_soc_dapm_enable_pin(codec, "Line Out Jack");
+	else
+		snd_soc_dapm_disable_pin(codec, "Line Out Jack");
+
+	snd_soc_dapm_sync(codec);
+	return 1;
+}
+
+static int spk_amp_event(struct snd_soc_dapm_widget *w,
+			 struct snd_kcontrol *kcontrol, int event)
+{
+	struct imx_3stack_priv *priv = &machine_priv;
+	struct platform_device *pdev = priv->pdev;
+	struct mxc_audio_platform_data *plat = pdev->dev.platform_data;
+
+	if (plat->amp_enable == NULL)
+		return 0;
+
+	if (SND_SOC_DAPM_EVENT_ON(event))
+		plat->amp_enable(1);
+	else
+		plat->amp_enable(0);
+
+	return 0;
+}
+
+static const struct snd_soc_dapm_widget imx_3stack_dapm_widgets[] = {
+	SND_SOC_DAPM_MIC("SiMIC", NULL),
+	SND_SOC_DAPM_MIC("Mic1 Jack", NULL),
+	SND_SOC_DAPM_MIC("Mic2 Jack", NULL),
+	SND_SOC_DAPM_LINE("Line In Jack", NULL),
+	SND_SOC_DAPM_LINE("Line Out Jack", NULL),
+	SND_SOC_DAPM_SPK("Ext Spk", spk_amp_event),
+	SND_SOC_DAPM_HP("Headphone Jack", NULL),
+};
+
+static const struct snd_kcontrol_new wm8350_machine_controls[] = {
+	SOC_ENUM_EXT("Jack Function", wm8350_enum[0], wm8350_get_jack,
+		     wm8350_set_jack),
+	SOC_ENUM_EXT("Speaker Function", wm8350_enum[1], wm8350_get_spk,
+		     wm8350_set_spk),
+};
+
+static int imx_3stack_wm8350_init(struct snd_soc_codec *codec)
+{
+	struct imx_3stack_priv *priv = &machine_priv;
+	struct wm8350 *wm8350 = priv->wm8350;
+	int i, ret;
+
+	codec->control_data = wm8350;
+
+	/* Add imx_3stack specific controls */
+	for (i = 0; i < ARRAY_SIZE(wm8350_machine_controls); i++) {
+		ret = snd_ctl_add(codec->card,
+				  snd_soc_cnew(&wm8350_machine_controls[i],
+					       codec, NULL));
+		if (ret < 0)
+			return ret;
+	}
+
+	/* Add imx_3stack specific widgets */
+	snd_soc_dapm_new_controls(codec, imx_3stack_dapm_widgets,
+				  ARRAY_SIZE(imx_3stack_dapm_widgets));
+
+	/* Set up imx_3stack specific audio path audio_map */
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	snd_soc_dapm_sync(codec);
+
+	return 0;
+
+}
+
+/* imx_3stack digital audio interface glue - connects codec <--> CPU */
+static struct snd_soc_dai_link imx_3stack_dai = {
+	.name = "WM8350",
+	.stream_name = "WM8350",
+	.cpu_dai = &imx_ssi_dai,
+	.codec_dai = &wm8350_dai,
+	.init = imx_3stack_wm8350_init,
+	.ops = &imx_3stack_ops,
+};
+
+static int imx_3stack_machine_probe(struct platform_device *pdev)
+{
+	struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+	struct imx_3stack_priv *priv = &machine_priv;
+	struct wm8350 *wm8350 = priv->wm8350;
+
+	socdev->codec_data = wm8350;
+	wm8350->codec.platform_data = &imx_3stack_wm8350_setup;
+
+	return 0;
+}
+
+/* imx_3stack audio machine driver */
+static struct snd_soc_machine snd_soc_machine_imx_3stack = {
+	.name = "imx-3stack",
+	.dai_link = &imx_3stack_dai,
+	.num_links = 1,
+	.probe = imx_3stack_machine_probe,
+};
+
+static struct snd_soc_device imx_3stack_snd_devdata = {
+	.machine = &snd_soc_machine_imx_3stack,
+	.platform = &imx_soc_platform,
+	.codec_dev = &soc_codec_dev_wm8350,
+};
+
+static int __devinit imx_3stack_wm8350_probe(struct platform_device *pdev)
+{
+	struct mxc_audio_platform_data *plat = pdev->dev.platform_data;
+	struct imx_3stack_priv *priv = &machine_priv;
+	struct wm8350 *wm8350 = plat->priv;
+	int ret = 0;
+	u16 reg;
+
+	priv->pdev = pdev;
+	priv->wm8350 = wm8350;
+	imx_ssi_dai.private_data = plat;
+
+	imx_3stack_wm8350_setup.regulator1 = plat->regulator1;
+	imx_3stack_wm8350_setup.regulator2 = plat->regulator2;
+
+	gpio_activate_audio_ports();
+	imx_3stack_init_dam(plat->src_port, plat->ext_port);
+
+	if (plat->src_port == 2)
+		strcpy(imx_ssi_dai.name, "imx-ssi-3");
+	else
+		strcpy(imx_ssi_dai.name, "imx-ssi-1");
+
+	ret = driver_create_file(pdev->dev.driver, &driver_attr_headphone);
+	if (ret < 0) {
+		pr_err("%s:failed to create driver_attr_headphone\n", __func__);
+		return ret;
+	}
+
+	/* enable slow clock gen for jack detect */
+	reg = wm8350_reg_read(wm8350, WM8350_POWER_MGMT_4);
+	wm8350_reg_write(wm8350, WM8350_POWER_MGMT_4, reg | WM8350_TOCLK_ENA);
+	/* enable jack detect */
+	reg = wm8350_reg_read(wm8350, WM8350_JACK_DETECT);
+	wm8350_reg_write(wm8350, WM8350_JACK_DETECT, reg | WM8350_JDR_ENA);
+	wm8350_register_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_R,
+			    imx_3stack_jack_handler, NULL);
+	wm8350_unmask_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_R);
+
+	wm8350_jack_func = 1;
+	wm8350_spk_func = 1;
+
+	return 0;
+}
+
+static int imx_3stack_wm8350_remove(struct platform_device *pdev)
+{
+	struct imx_3stack_priv *priv = &machine_priv;
+	struct wm8350 *wm8350 = priv->wm8350;
+
+	wm8350_mask_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_R);
+	wm8350_free_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_R);
+
+	return 0;
+}
+
+static struct platform_driver imx_3stack_wm8350_audio_driver = {
+	.probe = imx_3stack_wm8350_probe,
+	.remove = __devexit_p(imx_3stack_wm8350_remove),
+	.driver = {
+		   .name = "wm8350-imx-3stack-audio",
+		   },
+};
+
+static struct platform_device *imx_3stack_snd_device;
+
+static int __init imx_3stack_init(void)
+{
+	int ret;
+
+	ret = platform_driver_register(&imx_3stack_wm8350_audio_driver);
+	if (ret)
+		return -ENOMEM;
+
+	imx_3stack_snd_device = platform_device_alloc("soc-audio", -1);
+	if (!imx_3stack_snd_device)
+		return -ENOMEM;
+
+	platform_set_drvdata(imx_3stack_snd_device, &imx_3stack_snd_devdata);
+	imx_3stack_snd_devdata.dev = &imx_3stack_snd_device->dev;
+	ret = platform_device_add(imx_3stack_snd_device);
+
+	if (ret)
+		platform_device_put(imx_3stack_snd_device);
+
+	return ret;
+}
+
+static void __exit imx_3stack_exit(void)
+{
+	platform_driver_unregister(&imx_3stack_wm8350_audio_driver);
+	platform_device_unregister(imx_3stack_snd_device);
+}
+
+module_init(imx_3stack_init);
+module_exit(imx_3stack_exit);
+
+MODULE_AUTHOR("Liam Girdwood");
+MODULE_DESCRIPTION("PMIC WM8350 Driver for i.MX 3STACK");
+MODULE_LICENSE("GPL");
-- 
1.6.0.4





More information about the kernel-team mailing list