diff options
author | Alan Tull <r80115@freescale.com> | 2009-04-24 17:00:59 -0500 |
---|---|---|
committer | Alan Tull <r80115@freescale.com> | 2009-04-30 08:14:35 -0500 |
commit | 83f43f21cadf5c29973a126d8065bb210c75f35d (patch) | |
tree | c1fabd3ef862980dba19dd5d3c77eee8431b4719 /sound | |
parent | 03742aa04f000a83a4534414c935ef82055ac928 (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.c | 223 | ||||
-rw-r--r-- | sound/soc/imx/Kconfig | 9 | ||||
-rw-r--r-- | sound/soc/imx/Makefile | 2 | ||||
-rw-r--r-- | sound/soc/imx/imx-3stack-wm8350.c | 714 |
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"); |