summaryrefslogtreecommitdiff
path: root/sound
diff options
context:
space:
mode:
authorAlan Tull <r80115@freescale.com>2009-04-24 17:00:59 -0500
committerAlan Tull <r80115@freescale.com>2009-04-30 08:14:35 -0500
commit83f43f21cadf5c29973a126d8065bb210c75f35d (patch)
treec1fabd3ef862980dba19dd5d3c77eee8431b4719 /sound
parent03742aa04f000a83a4534414c935ef82055ac928 (diff)
ENGR00111898-2 wm8350: soc audio support
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@freescale.com>
Diffstat (limited to 'sound')
-rw-r--r--sound/soc/codecs/wm8350.c223
-rw-r--r--sound/soc/imx/Kconfig9
-rw-r--r--sound/soc/imx/Makefile2
-rw-r--r--sound/soc/imx/imx-3stack-wm8350.c714
4 files changed, 818 insertions, 130 deletions
diff --git a/sound/soc/codecs/wm8350.c b/sound/soc/codecs/wm8350.c
index e3989d406f54..b15a4f0fbed7 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 e5c59dd5566a..0bf5f9b843bf 100644
--- a/sound/soc/imx/Kconfig
+++ b/sound/soc/imx/Kconfig
@@ -20,6 +20,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 2e4cf2d3e1db..44f93c5fd07f 100644
--- a/sound/soc/imx/Makefile
+++ b/sound/soc/imx/Makefile
@@ -8,6 +8,8 @@ obj-$(CONFIG_SND_MXC_SOC_SSI) += snd-soc-imx-ssi.o
obj-$(CONFIG_SND_MXC_SOC_ESAI) += snd-soc-imx-esai.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
snd-soc-imx-3stack-ak4647-objs := imx-3stack-ak4647.o
diff --git a/sound/soc/imx/imx-3stack-wm8350.c b/sound/soc/imx/imx-3stack-wm8350.c
new file mode 100644
index 000000000000..b7895dff9e06
--- /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@wolfsonmicro.com or linux@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");