diff options
author | Ziyan <jaraidaniel@gmail.com> | 2016-04-30 23:33:32 +0200 |
---|---|---|
committer | Ziyan <jaraidaniel@gmail.com> | 2016-04-30 23:47:51 +0200 |
commit | 1cd5dbf5a4112a4debad48900d094c4e996d5ca1 (patch) | |
tree | e84780c0645795e55ec27f524642d4942a7e36d8 /sound | |
parent | 79c05f32ad443fd5ef1e2a4aa50ef8597e1b696a (diff) | |
download | kernel_samsung_espresso10-1cd5dbf5a4112a4debad48900d094c4e996d5ca1.zip kernel_samsung_espresso10-1cd5dbf5a4112a4debad48900d094c4e996d5ca1.tar.gz kernel_samsung_espresso10-1cd5dbf5a4112a4debad48900d094c4e996d5ca1.tar.bz2 |
ASoC: add espresso machine driver
Diffstat (limited to 'sound')
-rw-r--r-- | sound/soc/omap/Kconfig | 10 | ||||
-rw-r--r-- | sound/soc/omap/Makefile | 2 | ||||
-rw-r--r-- | sound/soc/omap/espresso.c | 767 |
3 files changed, 779 insertions, 0 deletions
diff --git a/sound/soc/omap/Kconfig b/sound/soc/omap/Kconfig index dd98cc4..dff7376 100644 --- a/sound/soc/omap/Kconfig +++ b/sound/soc/omap/Kconfig @@ -192,3 +192,13 @@ config SND_OMAP_SOC_IGEP0020 select SND_SOC_TWL4030 help Say Y if you want to add support for Soc audio on IGEP v2 board. + +config SND_OMAP_SOC_ESPRESSO + tristate "SoC Audio support for espresso using Wolfson WM1811 Codec" + depends on MACH_OMAP4_ESPRESSO + depends on SND_OMAP_SOC + select SND_OMAP_SOC_MCBSP + select SND_SOC_WM8994 + select SND_SOC_WM_HUBS + help + Say Y if you want to add support for SoC audio on the Espresso board. diff --git a/sound/soc/omap/Makefile b/sound/soc/omap/Makefile index 215e3c6..3c55683 100644 --- a/sound/soc/omap/Makefile +++ b/sound/soc/omap/Makefile @@ -20,6 +20,7 @@ obj-$(CONFIG_SND_OMAP_SOC_HDMI) += snd-soc-omap-hdmi.o obj-$(CONFIG_SND_OMAP_SOC_VXREC) += snd-soc-omap-vxrec.o # OMAP Machine Support +snd-soc-espresso-objs := espresso.o snd-soc-n810-objs := n810.o snd-soc-rx51-objs := rx51.o snd-soc-ams-delta-objs := ams-delta.o @@ -49,4 +50,5 @@ obj-$(CONFIG_SND_OMAP_SOC_OMAP3_PANDORA) += snd-soc-omap3pandora.o obj-$(CONFIG_SND_OMAP_SOC_OMAP3_BEAGLE) += snd-soc-omap3beagle.o obj-$(CONFIG_SND_OMAP_SOC_ZOOM2) += snd-soc-zoom2.o obj-$(CONFIG_SND_OMAP_SOC_IGEP0020) += snd-soc-igep0020.o +obj-$(CONFIG_SND_OMAP_SOC_ESPRESSO) += snd-soc-espresso.o obj-$(CONFIG_SND_OMAP_SOC_OMAP4_HDMI) += snd-soc-omap4-hdmi.o diff --git a/sound/soc/omap/espresso.c b/sound/soc/omap/espresso.c new file mode 100644 index 0000000..ccecbd1 --- /dev/null +++ b/sound/soc/omap/espresso.c @@ -0,0 +1,767 @@ +/* + * sound/soc/omap/omap4_wm8994.c + * + * Copyright (c) 2009 Samsung Electronics Co. Ltd + * + * 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. + */ + +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <linux/i2c.h> +#include <linux/i2c/twl.h> +#include <sound/core.h> +#include <linux/slab.h> +#include <linux/workqueue.h> +#include <linux/regulator/machine.h> +#include <linux/input.h> +#include <linux/wakelock.h> +#include <linux/suspend.h> +#include <linux/delay.h> + +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/jack.h> + +#include <linux/mfd/wm8994/core.h> +#include <linux/mfd/wm8994/registers.h> +#include <linux/mfd/wm8994/pdata.h> + +#include <asm/mach-types.h> +#include <plat/hardware.h> +#include <plat/mcbsp.h> +#include <linux/gpio.h> +#include <linux/pm_qos_params.h> + +#include "omap-pcm.h" +#include "omap-mcbsp.h" +#include "../codecs/wm8994.h" + +#include "../../../arch/arm/mach-omap2/board-espresso.h" + +#define WM8994_DEFAULT_MCLK1 26000000 +#define WM8994_DEFAULT_MCLK2 32768 +#define WM8994_DEFAULT_SYNC_CLK 11289600 + +#define USE_SND_EAR_GND_SEL + +struct snd_soc_codec *the_codec; +int dock_status; + +static struct pm_qos_request_list pm_qos_handle; + +static struct gpio mclk = { + .flags = GPIOF_OUT_INIT_LOW, + .label = "CODEC_CLK_REQ", +}; + +static struct gpio main_mic_bias = { + .flags = GPIOF_OUT_INIT_LOW, + .label = "MICBIAS_EN", +}; + +static struct gpio sub_mic_bias = { + .flags = GPIOF_OUT_INIT_LOW, + .label = "SUB_MICBIAS_EN", +}; + +#ifdef USE_SND_EAR_GND_SEL +static struct gpio ear_select = { + .flags = GPIOF_OUT_INIT_LOW, + .label = "EAR_GND_SEL", +}; + +static int hp_output_mode; +const char *hp_analogue_text[] = { + "VoiceCall Mode", "Playback Mode" +}; +#endif /* USE_SND_EAR_GND_SEL */ + +static int input_clamp; +const char *input_clamp_text[] = { + "Off", "On" +}; + +static int aif2_mode; +const char *aif2_mode_text[] = { + "Slave", "Master" +}; + +static int pm_mode; +const char *pm_mode_text[] = { + "Off", "On" +}; + +static void set_mclk(bool on) +{ + if (on) + gpio_set_value(mclk.gpio, 1); + else + gpio_set_value(mclk.gpio, 0); +} + +static int main_mic_bias_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + gpio_set_value(main_mic_bias.gpio, SND_SOC_DAPM_EVENT_ON(event)); + return 0; +} + +static int sub_mic_bias_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + gpio_set_value(sub_mic_bias.gpio, SND_SOC_DAPM_EVENT_ON(event)); + return 0; +} + +#ifdef USE_SND_EAR_GND_SEL +static const struct soc_enum hp_mode_enum[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(hp_analogue_text), hp_analogue_text), +}; + +static int get_hp_output_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = hp_output_mode; + return 0; +} + +static int set_hp_output_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + if (hp_output_mode == ucontrol->value.integer.value[0]) + return 0; + + hp_output_mode = ucontrol->value.integer.value[0]; + gpio_set_value(ear_select.gpio, hp_output_mode); + + pr_debug("set hp mode : %s\n", hp_analogue_text[hp_output_mode]); + + return 0; +} +#endif /* USE_SND_EAR_GND_SEL */ + +static const struct soc_enum input_clamp_enum[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(input_clamp_text), input_clamp_text), +}; + +static int get_input_clamp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = input_clamp; + return 0; +} + +static int set_input_clamp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + input_clamp = ucontrol->value.integer.value[0]; + + if (input_clamp) { + snd_soc_update_bits(codec, WM8994_INPUT_MIXER_1, + WM8994_INPUTS_CLAMP, WM8994_INPUTS_CLAMP); + msleep(100); + } else { + snd_soc_update_bits(codec, WM8994_INPUT_MIXER_1, + WM8994_INPUTS_CLAMP, 0); + } + pr_info("set fm input_clamp : %s\n", input_clamp_text[input_clamp]); + + return 0; +} + +static const struct soc_enum aif2_mode_enum[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(aif2_mode_text), aif2_mode_text), +}; + +static int get_aif2_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = aif2_mode; + return 0; +} + +static int set_aif2_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + if (aif2_mode == ucontrol->value.integer.value[0]) + return 0; + + aif2_mode = ucontrol->value.integer.value[0]; + + pr_info("set aif2 mode : %s\n", aif2_mode_text[aif2_mode]); + + return 0; +} + +static const struct soc_enum pm_mode_enum[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(pm_mode_text), pm_mode_text), +}; + +static int get_pm_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = pm_mode; + return 0; +} + +static int set_pm_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + if (pm_mode == ucontrol->value.integer.value[0]) + return 0; + + if (pm_mode) + pm_qos_update_request(&pm_qos_handle, PM_QOS_DEFAULT_VALUE); + else + pm_qos_update_request(&pm_qos_handle, 7); + + pm_mode = ucontrol->value.integer.value[0]; + + pr_info("set pm mode : %s\n", pm_mode_text[pm_mode]); + + return 0; +} + +void notify_dock_status(int status) +{ + if (!the_codec) + return; + + dock_status = status; + pr_info("%s: status=%d", __func__, dock_status); + + if (the_codec->suspended) + return; + + if (status) + wm8994_vmid_mode(the_codec, WM8994_VMID_FORCE); + else + wm8994_vmid_mode(the_codec, WM8994_VMID_NORMAL); +} + +static int omap4_wm8994_start_fll1(struct snd_soc_dai *aif1_dai) +{ + int ret; + + dev_dbg(aif1_dai->dev, "Moving to audio clocking settings\n"); + + /* Switch the FLL */ + ret = snd_soc_dai_set_pll(aif1_dai, + WM8994_FLL1, + WM8994_FLL_SRC_MCLK1, + WM8994_DEFAULT_MCLK1, 44100 * 256); + if (ret < 0) + dev_err(aif1_dai->dev, "Unable to start FLL1: %d\n", ret); + + /* Then switch AIF1CLK to it */ + ret = snd_soc_dai_set_sysclk(aif1_dai, + WM8994_SYSCLK_FLL1, + 44100 * 256, + SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(aif1_dai->dev, "Unable to switch to FLL1: %d\n", ret); + + return ret; +} + +static int omap4_hifi_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 *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret; + + ret = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) { + pr_err("can't set codec DAI configuration\n"); + return ret; + } + + ret = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) { + pr_err("can't set CPU DAI configuration\n"); + return ret; + } + + ret = omap4_wm8994_start_fll1(codec_dai); + if (ret < 0) { + pr_err("can't start fll1\n"); + return ret; + } + + return 0; +} + +static struct snd_soc_ops hifi_ops = { + .hw_params = omap4_hifi_hw_params, +}; + +static int omap4_wm8994_aif2_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 *codec_dai = rtd->codec_dai; + int ret; + int prate; + int bclk; + + pr_debug("%s: enter, aif2_mode=%d\n", __func__, aif2_mode); + + prate = params_rate(params); + switch (prate) { + case 8000: + case 16000: + break; + default: + dev_warn(codec_dai->dev, "Unsupported LRCLK %d, falling back to 8000Hz\n", + (int)params_rate(params)); + prate = 8000; + } + + /* Set the codec DAI configuration */ + if (aif2_mode == 0) { + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBS_CFS); + } else { + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM); + } + if (ret < 0) + return ret; + + switch (prate) { + case 8000: + bclk = 256000; + break; + case 16000: + bclk = 512000; + break; + default: + return -EINVAL; + } + + if (aif2_mode == 0) { + ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL2, + WM8994_FLL_SRC_BCLK, + bclk, prate * 256); + } else { + ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL2, + WM8994_FLL_SRC_MCLK1, + WM8994_DEFAULT_MCLK1, prate * 256); + } + if (ret < 0) + dev_err(codec_dai->dev, "Unable to configure FLL2: %d\n", ret); + + ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL2, + prate * 256, SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(codec_dai->dev, "Unable to switch to FLL2: %d\n", ret); + + return 0; +} + +static struct snd_soc_ops omap4_wm8994_aif2_ops = { + .hw_params = omap4_wm8994_aif2_hw_params, +}; + +static int omap4_wm8994_aif3_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + pr_err("%s: enter\n", __func__); + return 0; +} + +static struct snd_soc_ops omap4_wm8994_aif3_ops = { + .hw_params = omap4_wm8994_aif3_hw_params, +}; + +static const struct snd_kcontrol_new omap4_controls[] = { + SOC_DAPM_PIN_SWITCH("HP"), + SOC_DAPM_PIN_SWITCH("SPK"), + SOC_DAPM_PIN_SWITCH("RCV"), + SOC_DAPM_PIN_SWITCH("LINEOUT"), + + SOC_DAPM_PIN_SWITCH("Main Mic"), + + SOC_DAPM_PIN_SWITCH("Headset Mic"), + +#ifdef USE_SND_EAR_GND_SEL + SOC_ENUM_EXT("HP Output Mode", hp_mode_enum[0], + get_hp_output_mode, set_hp_output_mode), +#endif /* USE_SND_EAR_GND_SEL */ + + SOC_ENUM_EXT("Input Clamp", input_clamp_enum[0], + get_input_clamp, set_input_clamp), + SOC_ENUM_EXT("AIF2 Mode", aif2_mode_enum[0], + get_aif2_mode, set_aif2_mode), + SOC_ENUM_EXT("PM Constraints Mode", pm_mode_enum[0], + get_pm_mode, set_pm_mode), +}; + +static const struct snd_kcontrol_new omap4_submic_controls[] = { + SOC_DAPM_PIN_SWITCH("Sub Mic"), +}; + +const struct snd_soc_dapm_widget omap4_dapm_widgets[] = { + SND_SOC_DAPM_HP("HP", NULL), + SND_SOC_DAPM_SPK("SPK", NULL), + SND_SOC_DAPM_SPK("RCV", NULL), + SND_SOC_DAPM_LINE("LINEOUT", NULL), + + SND_SOC_DAPM_MIC("Main Mic", main_mic_bias_event), + + SND_SOC_DAPM_MIC("Headset Mic", NULL), +}; + +const struct snd_soc_dapm_widget omap4_dapm_submic_widgets[] = { + SND_SOC_DAPM_MIC("Sub Mic", sub_mic_bias_event), +}; + +const struct snd_soc_dapm_route omap4_dapm_routes[] = { + { "HP", NULL, "HPOUT1L" }, + { "HP", NULL, "HPOUT1R" }, + + { "SPK", NULL, "SPKOUTLN" }, + { "SPK", NULL, "SPKOUTLP" }, + { "SPK", NULL, "SPKOUTRN" }, + { "SPK", NULL, "SPKOUTRP" }, + + { "RCV", NULL, "HPOUT2N" }, + { "RCV", NULL, "HPOUT2P" }, + + { "LINEOUT", NULL, "LINEOUT1N" }, + { "LINEOUT", NULL, "LINEOUT1P" }, + + { "IN1LP", NULL, "Main Mic" }, + { "IN1LN", NULL, "Main Mic" }, + + { "IN1RP", NULL, "MICBIAS2" }, + { "IN1RN", NULL, "MICBIAS2" }, + { "MICBIAS2", NULL, "Headset Mic" }, +}; + +const struct snd_soc_dapm_route omap4_submic_dapm_routes[] = { + { "IN2RP:VXRP", NULL, "Sub Mic" }, + { "IN2RN", NULL, "Sub Mic" }, +}; + +int omap4_wm8994_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_codec *codec = rtd->codec; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + struct snd_soc_dapm_context *dapm = &codec->dapm; + struct snd_soc_dai *aif1_dai = rtd->codec_dai; + int ret; + + the_codec = codec; + + mclk.gpio = wm8994->pdata->mclk_gpio; + ret = gpio_request(mclk.gpio, "mclk"); + if (ret < 0) + goto mclk_err; + gpio_direction_output(mclk.gpio, 0); + + main_mic_bias.gpio = wm8994->pdata->main_mic_bias_gpio; + ret = gpio_request(main_mic_bias.gpio, "main_mic_bias"); + if (ret < 0) + goto main_mic_err; + gpio_direction_output(main_mic_bias.gpio, 0); + +#ifdef USE_SND_EAR_GND_SEL + hp_output_mode = 1; + ear_select.gpio = wm8994->pdata->ear_select_gpio; + ret = gpio_request(ear_select.gpio, "ear_select"); + if (ret < 0) + goto ear_select_err; + gpio_direction_output(ear_select.gpio, hp_output_mode); +#endif /* USE_SND_EAR_GND_SEL */ + + set_mclk(true); /* enable 26M CLK */ + + ret = snd_soc_add_controls(codec, omap4_controls, + ARRAY_SIZE(omap4_controls)); + + if (wm8994->pdata->use_submic) { + sub_mic_bias.gpio = wm8994->pdata->submic_gpio; + ret = gpio_request(sub_mic_bias.gpio, "sub_mic_bias"); + if (ret < 0) { + pr_err("%s: failed to request gpio %s\n", __func__, sub_mic_bias.label); + wm8994->pdata->use_submic = false; + goto submic_error; + } + gpio_direction_output(sub_mic_bias.gpio, 0); + + snd_soc_add_controls(codec, omap4_submic_controls, + ARRAY_SIZE(omap4_submic_controls)); + } +submic_error: + + ret = snd_soc_dapm_new_controls(dapm, omap4_dapm_widgets, + ARRAY_SIZE(omap4_dapm_widgets)); + + if (wm8994->pdata->use_submic) { + snd_soc_dapm_new_controls(dapm, omap4_dapm_submic_widgets, + ARRAY_SIZE(omap4_dapm_submic_widgets)); + } + + if (ret != 0) + dev_err(codec->dev, "Failed to add DAPM widgets: %d\n", ret); + + ret = snd_soc_dapm_add_routes(dapm, omap4_dapm_routes, + ARRAY_SIZE(omap4_dapm_routes)); + + if (wm8994->pdata->use_submic) { + snd_soc_dapm_add_routes(dapm, omap4_submic_dapm_routes, + ARRAY_SIZE(omap4_submic_dapm_routes)); + } + + if (ret != 0) + dev_err(codec->dev, "Failed to add DAPM routes: %d\n", ret); + + ret = snd_soc_dai_set_sysclk(aif1_dai, WM8994_SYSCLK_MCLK2, + WM8994_DEFAULT_MCLK2, SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(codec->dev, "Failed to boot clocking\n"); + + ret = snd_soc_dapm_force_enable_pin(dapm, "AIF1CLK"); + if (ret < 0) + dev_err(codec->dev, "Failed to enable AIF1CLK: %d\n", ret); + + /* set up NC codec pins */ + snd_soc_dapm_nc_pin(dapm, "IN2LP:VXRN"); + snd_soc_dapm_nc_pin(dapm, "IN2LN"); + + /* set up ignore pins */ + snd_soc_dapm_ignore_suspend(dapm, "RCV"); + snd_soc_dapm_ignore_suspend(dapm, "SPK"); + snd_soc_dapm_ignore_suspend(dapm, "LINEOUT"); + snd_soc_dapm_ignore_suspend(dapm, "HP"); + snd_soc_dapm_ignore_suspend(dapm, "Main Mic"); + if (wm8994->pdata->use_submic) { + snd_soc_dapm_ignore_suspend(dapm, "Sub Mic"); + } + + snd_soc_dapm_ignore_suspend(dapm, "Headset Mic"); + snd_soc_dapm_ignore_suspend(dapm, "AIF1DACDAT"); + snd_soc_dapm_ignore_suspend(dapm, "AIF2DACDAT"); + snd_soc_dapm_ignore_suspend(dapm, "AIF3DACDAT"); + snd_soc_dapm_ignore_suspend(dapm, "AIF1ADCDAT"); + snd_soc_dapm_ignore_suspend(dapm, "AIF2ADCDAT"); + snd_soc_dapm_ignore_suspend(dapm, "AIF3ADCDAT"); + + /* By default use idle_bias_off, will override for WM8994 */ + codec->dapm.idle_bias_off = 0; + + return snd_soc_dapm_sync(dapm); + +#ifdef USE_SND_EAR_GND_SEL +ear_select_err: + gpio_free(ear_select.gpio); +#endif /* USE_SND_EAR_GND_SEL */ +main_mic_err: + gpio_free(main_mic_bias.gpio); +mclk_err: + gpio_free(mclk.gpio); + + return ret; +} + +static struct snd_soc_dai_driver ext_dai[] = { +{ + .name = "CP", + .playback = { + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 16000, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 16000, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, +{ + .name = "BT", + .playback = { + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 16000, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 16000, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}, +}; + +static struct snd_soc_dai_link omap4_dai[] = { +{ + .name = "MCBSP AIF1", + .stream_name = "HIFI MCBSP Tx/RX", + .cpu_dai_name = "omap-mcbsp-dai.2", + .codec_dai_name = "wm8994-aif1", + .platform_name = "omap-pcm-audio", + .codec_name = "wm8994-codec", + .init = omap4_wm8994_init, + .ops = &hifi_ops, +}, +{ + .name = "WM1811 Voice", + .stream_name = "Voice Tx/Rx", + .cpu_dai_name = "CP", + .codec_dai_name = "wm8994-aif2", + .platform_name = "snd-soc-dummy", + .codec_name = "wm8994-codec", + .ignore_suspend = 1, + .ops = &omap4_wm8994_aif2_ops, +}, +{ + .name = "WM1811 BT", + .stream_name = "BT Tx/Rx", + .cpu_dai_name = "BT", + .codec_dai_name = "wm8994-aif3", + .platform_name = "snd-soc-dummy", + .codec_name = "wm8994-codec", + .ignore_suspend = 1, + .ops = &omap4_wm8994_aif3_ops, +}, +}; + +static int wm8994_suspend_pre(struct snd_soc_card *card) +{ + struct snd_soc_codec *codec = card->rtd->codec; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + + if (dock_status == 1 && wm8994->vmid_mode == WM8994_VMID_FORCE) { + pr_info("%s: entering force vmid mode\n", __func__); + wm8994_vmid_mode(codec, WM8994_VMID_NORMAL); + } + + snd_soc_dapm_disable_pin(&codec->dapm, "AIF1CLK"); + + return 0; +} + +static int wm8994_resume_post(struct snd_soc_card *card) +{ + struct snd_soc_codec *codec = card->rtd->codec; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + + if (dock_status == 1 && wm8994->vmid_mode == WM8994_VMID_NORMAL) { + pr_info("%s: entering normal vmid mode\n", __func__); + wm8994_vmid_mode(codec, WM8994_VMID_FORCE); + } + + snd_soc_dapm_force_enable_pin(&codec->dapm, "AIF1CLK"); + + return 0; +} + +static int wm8994_suspend_post(struct snd_soc_card *card) +{ + struct snd_soc_codec *codec = card->rtd->codec; + + if (!codec->active) { + set_mclk(false); /* disble 26M CLK */ + } + return 0; +} + +static int wm8994_resume_pre(struct snd_soc_card *card) +{ + set_mclk(true); /* enable 26M CLK */ + return 0; +} + +static struct snd_soc_card omap4_wm8994 = { + .name = "omap4_wm8994", + .dai_link = omap4_dai, + .num_links = ARRAY_SIZE(omap4_dai), + .suspend_post = wm8994_suspend_post, + .resume_pre = wm8994_resume_pre, + .suspend_pre = wm8994_suspend_pre, + .resume_post = wm8994_resume_post, +}; + +static struct platform_device *omap4_wm8994_snd_device; + +static int __init omap4_audio_init(void) +{ + int ret; + + pm_qos_add_request(&pm_qos_handle, PM_QOS_CPU_DMA_LATENCY, + PM_QOS_DEFAULT_VALUE); + + omap4_wm8994_snd_device = platform_device_alloc("soc-audio", -1); + if (!omap4_wm8994_snd_device) { + pr_err("Platform device allocation failed\n"); + ret = -ENOMEM; + goto device_err; + } + + ret = snd_soc_register_dais(&omap4_wm8994_snd_device->dev, + ext_dai, ARRAY_SIZE(ext_dai)); + if (ret != 0) { + pr_err("Failed to register external DAIs: %d\n", ret); + goto dai_err; + } + + platform_set_drvdata(omap4_wm8994_snd_device, &omap4_wm8994); + + ret = platform_device_add(omap4_wm8994_snd_device); + if (ret) { + pr_err("Platform device allocation failed\n"); + goto err; + } + return ret; + +err: + snd_soc_unregister_dais(&omap4_wm8994_snd_device->dev, + ARRAY_SIZE(ext_dai)); +dai_err: + platform_device_put(omap4_wm8994_snd_device); +device_err: + return ret; +} +module_init(omap4_audio_init); + +static void __exit omap4_audio_exit(void) +{ + platform_device_unregister(omap4_wm8994_snd_device); + pm_qos_remove_request(&pm_qos_handle); +} +module_exit(omap4_audio_exit); + +MODULE_AUTHOR("Quartz.Jang <quartz.jang@samsung.com"); +MODULE_DESCRIPTION("ALSA Soc WM8994 omap4"); +MODULE_LICENSE("GPL"); |