summaryrefslogtreecommitdiff
path: root/sound
diff options
context:
space:
mode:
authorTom Cherry <tcherry@nvidia.com>2011-05-26 18:39:07 -0700
committerNiket Sirsi <nsirsi@nvidia.com>2011-06-01 11:58:40 -0700
commit360249a59fcd43d62752d2333c8de06af3edb0f8 (patch)
tree308b5108e9411c9a7318a0fd6ef6a72a3a266958 /sound
parent8e874a094bb52c5864bea2bb7dc893874e890a04 (diff)
sound: soc: tegra: maxim 98088 codec integration
Change-Id: Ie8c616482017c869cd46b8100f0428c1f92a3bcf Reviewed-on: http://git-master/r/34238 Reviewed-by: Ravindra Lokhande <rlokhande@nvidia.com> Tested-by: Ravindra Lokhande <rlokhande@nvidia.com> Reviewed-by: Scott Peterson <speterson@nvidia.com> Reviewed-by: Thomas Cherry <tcherry@nvidia.com> Tested-by: Thomas Cherry <tcherry@nvidia.com>
Diffstat (limited to 'sound')
-rw-r--r--sound/soc/tegra/Makefile1
-rw-r--r--sound/soc/tegra/tegra_soc_max98088.c569
2 files changed, 570 insertions, 0 deletions
diff --git a/sound/soc/tegra/Makefile b/sound/soc/tegra/Makefile
index 993f2dff8115..5f08d958c572 100644
--- a/sound/soc/tegra/Makefile
+++ b/sound/soc/tegra/Makefile
@@ -7,3 +7,4 @@ obj-$(CONFIG_TEGRA_ALSA) += tegra_soc_controls.o
obj-$(CONFIG_TEGRA_GENERIC_CODEC)+= tegra_generic_codec.o
obj-$(CONFIG_SND_SOC_WM8903) += tegra_soc_wm8903.o
obj-$(CONFIG_SND_SOC_WM8753) += tegra_soc_wm8753.o
+obj-$(CONFIG_SND_SOC_MAX98088) += tegra_soc_max98088.o
diff --git a/sound/soc/tegra/tegra_soc_max98088.c b/sound/soc/tegra/tegra_soc_max98088.c
new file mode 100644
index 000000000000..02b6992933fc
--- /dev/null
+++ b/sound/soc/tegra/tegra_soc_max98088.c
@@ -0,0 +1,569 @@
+/*
+ * tegra_soc_max98088.c -- SoC audio for tegra
+ *
+ * Copyright 2010-2011 NVIDIA Corporation
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#define DEBUG
+
+#include <linux/gpio.h>
+#include "tegra_soc.h"
+#include "../codecs/max98088.h"
+
+static struct platform_device *tegra_snd_device;
+
+extern struct snd_soc_dai tegra_i2s_dai[];
+extern struct snd_soc_dai tegra_spdif_dai;
+extern struct snd_soc_dai tegra_generic_codec_dai[];
+extern struct snd_soc_platform tegra_soc_platform;
+extern struct wired_jack_conf tegra_wired_jack_conf;
+
+void speaker_settings(struct snd_soc_codec *codec, int value)
+{
+}
+
+static int tegra_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->dai->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
+ int sys_clk;
+ int err;
+
+#if 0
+#ifdef CONFIG_ARCH_TEGRA_2x_SOC
+ int dai_flag = 0;
+ enum dac_dap_data_format data_fmt;
+
+ if (tegra_das_is_port_master(tegra_audio_codec_type_hifi))
+ dai_flag |= SND_SOC_DAIFMT_CBM_CFM;
+ else
+#endif
+ dai_flag |= SND_SOC_DAIFMT_CBS_CFS;
+
+#ifdef CONFIG_ARCH_TEGRA_2x_SOC
+ data_fmt = tegra_das_get_codec_data_fmt(tegra_audio_codec_type_hifi);
+
+ /* We are supporting DSP and I2s format for now */
+ if (data_fmt & dac_dap_data_format_dsp)
+ dai_flag |= SND_SOC_DAIFMT_DSP_A;
+ else
+#endif
+ dai_flag |= SND_SOC_DAIFMT_I2S;
+
+ err = snd_soc_dai_set_fmt(codec_dai, dai_flag);
+ if (err < 0) {
+ pr_err("codec_dai fmt not set\n");
+ return err;
+ }
+
+ err = snd_soc_dai_set_fmt(cpu_dai, dai_flag);
+ if (err < 0) {
+ pr_err("cpu_dai fmt not set\n");
+ return err;
+ }
+#endif
+
+ err = snd_soc_dai_set_fmt(codec_dai,
+ SND_SOC_DAIFMT_I2S | \
+ SND_SOC_DAIFMT_NB_NF | \
+ SND_SOC_DAIFMT_CBS_CFS);
+ if (err < 0) {
+ pr_err("codec_dai fmt not set\n");
+ return err;
+ }
+
+ err = snd_soc_dai_set_fmt(cpu_dai,
+ SND_SOC_DAIFMT_I2S | \
+ SND_SOC_DAIFMT_NB_NF | \
+ SND_SOC_DAIFMT_CBS_CFS);
+ if (err < 0) {
+ pr_err("cpu_dai fmt not set\n");
+ return err;
+ }
+
+ /*FIXME: not sure this is the right way.
+ This should be samplerate times 256 or 128 based on codec need*/
+ sys_clk = tegra_das_get_mclk_rate();
+ err = snd_soc_dai_set_sysclk(codec_dai, 0, sys_clk, SND_SOC_CLOCK_IN);
+ if (err < 0) {
+ pr_err("codec_dai clock not set\n");
+ return err;
+ }
+
+ err = snd_soc_dai_set_sysclk(cpu_dai, 0, sys_clk, SND_SOC_CLOCK_IN);
+ if (err < 0) {
+ pr_err("cpu_dai clock not set\n");
+ return err;
+ }
+
+ return 0;
+}
+
+static int tegra_voice_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->dai->codec_dai;
+ struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai;
+ int dai_flag = 0, sys_clk;
+ int err;
+
+#ifdef CONFIG_ARCH_TEGRA_2x_SOC
+ enum dac_dap_data_format data_fmt;
+
+ if (tegra_das_is_port_master(tegra_audio_codec_type_bluetooth))
+ dai_flag |= SND_SOC_DAIFMT_CBM_CFM;
+ else
+#endif
+ dai_flag |= SND_SOC_DAIFMT_CBS_CFS;
+
+#ifdef CONFIG_ARCH_TEGRA_2x_SOC
+ data_fmt = tegra_das_get_codec_data_fmt(tegra_audio_codec_type_bluetooth);
+
+ /* We are supporting DSP and I2s format for now */
+ if (data_fmt & dac_dap_data_format_i2s)
+ dai_flag |= SND_SOC_DAIFMT_I2S;
+ else
+#endif
+ dai_flag |= SND_SOC_DAIFMT_DSP_A;
+
+ err = snd_soc_dai_set_fmt(codec_dai, dai_flag);
+ if (err < 0) {
+ pr_err("codec_dai fmt not set\n");
+ return err;
+ }
+
+ err = snd_soc_dai_set_fmt(cpu_dai, dai_flag);
+ if (err < 0) {
+ pr_err("cpu_dai fmt not set\n");
+ return err;
+ }
+
+ sys_clk = tegra_das_get_mclk_rate();
+ err = snd_soc_dai_set_sysclk(codec_dai, 0, sys_clk, SND_SOC_CLOCK_IN);
+ if (err < 0) {
+ pr_err("cpu_dai clock not set\n");
+ return err;
+ }
+
+ err = snd_soc_dai_set_sysclk(cpu_dai, 0, sys_clk, SND_SOC_CLOCK_IN);
+ if (err < 0) {
+ pr_err("cpu_dai clock not set\n");
+ return err;
+ }
+
+ return 0;
+}
+
+static int tegra_spdif_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ return 0;
+}
+
+int tegra_codec_startup(struct snd_pcm_substream *substream)
+{
+ tegra_das_power_mode(true);
+
+ return 0;
+}
+
+void tegra_codec_shutdown(struct snd_pcm_substream *substream)
+{
+ tegra_das_power_mode(false);
+}
+
+int tegra_soc_suspend_pre(struct platform_device *pdev, pm_message_t state)
+{
+ return 0;
+}
+
+int tegra_soc_suspend_post(struct platform_device *pdev, pm_message_t state)
+{
+ tegra_das_disable_mclk();
+
+ return 0;
+}
+
+int tegra_soc_resume_pre(struct platform_device *pdev)
+{
+ tegra_das_enable_mclk();
+
+ return 0;
+}
+
+int tegra_soc_resume_post(struct platform_device *pdev)
+{
+ return 0;
+}
+
+static struct snd_soc_ops tegra_hifi_ops = {
+ .hw_params = tegra_hifi_hw_params,
+ .startup = tegra_codec_startup,
+ .shutdown = tegra_codec_shutdown,
+};
+
+static struct snd_soc_ops tegra_voice_ops = {
+ .hw_params = tegra_voice_hw_params,
+ .startup = tegra_codec_startup,
+ .shutdown = tegra_codec_shutdown,
+};
+
+static struct snd_soc_ops tegra_spdif_ops = {
+ .hw_params = tegra_spdif_hw_params,
+};
+
+void tegra_ext_control(struct snd_soc_codec *codec, int new_con)
+{
+ struct tegra_audio_data *audio_data = codec->socdev->codec_data;
+
+ pr_info("%s: new_con 0x%X #####################\n", __func__, new_con);
+
+ /* Disconnect old codec routes and connect new routes*/
+ if (new_con & TEGRA_HEADPHONE)
+ snd_soc_dapm_enable_pin(codec, "Headphone");
+ else
+ snd_soc_dapm_disable_pin(codec, "Headphone");
+
+ if (new_con & (TEGRA_LINEOUT | TEGRA_EAR_SPK))
+ snd_soc_dapm_enable_pin(codec, "Lineout");
+ else
+ snd_soc_dapm_disable_pin(codec, "Lineout");
+
+ if (new_con & TEGRA_SPK)
+ snd_soc_dapm_enable_pin(codec, "Int Spk");
+ else
+ snd_soc_dapm_disable_pin(codec, "Int Spk");
+
+ if (new_con & TEGRA_INT_MIC)
+ snd_soc_dapm_enable_pin(codec, "Int Mic");
+ else
+ snd_soc_dapm_disable_pin(codec, "Int Mic");
+
+ if (new_con & TEGRA_EXT_MIC)
+ snd_soc_dapm_enable_pin(codec, "Ext Mic");
+ else
+ snd_soc_dapm_disable_pin(codec, "Ext Mic");
+
+ if (new_con & TEGRA_LINEIN)
+ snd_soc_dapm_enable_pin(codec, "Linein");
+ else
+ snd_soc_dapm_disable_pin(codec, "Linein");
+
+ if (new_con & TEGRA_HEADSET)
+ snd_soc_dapm_enable_pin(codec, "Headset");
+ else
+ snd_soc_dapm_disable_pin(codec, "Headset");
+
+ audio_data->codec_con = new_con;
+
+ snd_soc_dapm_enable_pin(codec, "Headphone");
+
+ /* signal a DAPM event */
+ snd_soc_dapm_sync(codec);
+
+}
+
+static int tegra_dapm_event_int_spk(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ if (tegra_wired_jack_conf.en_spkr != -1) {
+ if (tegra_wired_jack_conf.amp_reg) {
+ if (SND_SOC_DAPM_EVENT_ON(event) &&
+ !tegra_wired_jack_conf.amp_reg_enabled) {
+ regulator_enable(tegra_wired_jack_conf.amp_reg);
+ tegra_wired_jack_conf.amp_reg_enabled = 1;
+ } else if (!SND_SOC_DAPM_EVENT_ON(event) &&
+ tegra_wired_jack_conf.amp_reg_enabled) {
+ regulator_disable(tegra_wired_jack_conf.amp_reg);
+ tegra_wired_jack_conf.amp_reg_enabled = 0;
+ }
+ }
+
+ gpio_set_value_cansleep(tegra_wired_jack_conf.en_spkr,
+ SND_SOC_DAPM_EVENT_ON(event) ? 1 : 0);
+ }
+
+ return 0;
+}
+
+static int tegra_dapm_event_int_mic(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ if (tegra_wired_jack_conf.en_mic_int != -1)
+ gpio_set_value_cansleep(tegra_wired_jack_conf.en_mic_int,
+ SND_SOC_DAPM_EVENT_ON(event) ? 1 : 0);
+
+ if (tegra_wired_jack_conf.en_mic_ext != -1)
+ gpio_set_value_cansleep(tegra_wired_jack_conf.en_mic_ext,
+ SND_SOC_DAPM_EVENT_ON(event) ? 0 : 1);
+
+ return 0;
+}
+
+static int tegra_dapm_event_ext_mic(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ if (tegra_wired_jack_conf.en_mic_ext != -1)
+ gpio_set_value_cansleep(tegra_wired_jack_conf.en_mic_ext,
+ SND_SOC_DAPM_EVENT_ON(event) ? 1 : 0);
+
+ if (tegra_wired_jack_conf.en_mic_int != -1)
+ gpio_set_value_cansleep(tegra_wired_jack_conf.en_mic_int,
+ SND_SOC_DAPM_EVENT_ON(event) ? 0 : 1);
+
+ return 0;
+}
+
+/*tegra machine dapm widgets */
+static const struct snd_soc_dapm_widget tegra_dapm_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone", NULL),
+ SND_SOC_DAPM_HP("Headset", NULL),
+ SND_SOC_DAPM_SPK("Lineout", NULL),
+ SND_SOC_DAPM_SPK("Int Spk", tegra_dapm_event_int_spk),
+ SND_SOC_DAPM_MIC("Ext Mic", tegra_dapm_event_ext_mic),
+ SND_SOC_DAPM_MIC("Int Mic", tegra_dapm_event_int_mic),
+ SND_SOC_DAPM_LINE("Linein", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* Headphone connected to HPL and HPR */
+ {"Headphone", NULL, "HPL"},
+ {"Headphone", NULL, "HPR"},
+
+ /* Headset connected to HPL and HPR */
+ {"Headset", NULL, "HPL"},
+ {"Headset", NULL, "HPR"},
+
+ /* Speaker connected to SPKL and SPKR */
+ {"Int Spk", NULL, "SPKL"},
+ {"Int Spk", NULL, "SPKR"},
+
+ /* Receiver connected to RECL and RECR */
+ {"Lineout", NULL, "RECL"},
+ {"Lineout", NULL, "RECR"},
+
+ /*
+ * To be complete, add remained in/out devices such as built-in mic
+ * and headset mic.
+ */
+};
+#if 0
+/* Tegra machine audio map (connections to the codec pins) */
+static const struct snd_soc_dapm_route audio_map[] = {
+
+ /* headphone connected to LHPOUT1, RHPOUT1 */
+ {"Headphone", NULL, "HPOUTR"},
+ {"Headphone", NULL, "HPOUTL"},
+
+ /* headset Jack - in = micin, out = HPOUT*/
+ {"Headset", NULL, "HPOUTR"},
+ {"Headset", NULL, "HPOUTL"},
+ {"IN1L", NULL, "Headset"},
+ {"IN1R", NULL, "Headset"},
+
+ /* lineout connected to LINEOUTR and LINEOUTL */
+ {"Lineout", NULL, "LINEOUTR"},
+ {"Lineout", NULL, "LINEOUTL"},
+
+ /* build-in speaker connected to LON/P RON/P */
+ {"Int Spk", NULL, "RON"},
+ {"Int Spk", NULL, "ROP"},
+ {"Int Spk", NULL, "LON"},
+ {"Int Spk", NULL, "LOP"},
+
+ /* internal mic is mono */
+ {"IN1R", NULL, "Int Mic"},
+
+ /* external mic is stero */
+ {"IN1L", NULL, "Ext Mic"},
+ {"IN1R", NULL, "Ext Mic"},
+
+ /* Line In */
+ {"IN3L", NULL, "Linein"},
+ {"IN3R", NULL, "Linein"},
+};
+#endif
+
+
+static int tegra_codec_init(struct snd_soc_codec *codec)
+{
+ struct tegra_audio_data *audio_data = codec->socdev->codec_data;
+ int err = 0;
+
+ if (!audio_data->init_done) {
+
+ err = tegra_das_open();
+ if (err) {
+ pr_err(" Failed get dap mclk\n");
+ err = -ENODEV;
+ goto wm8903_init_fail;
+ }
+
+ err = tegra_das_enable_mclk();
+ if (err) {
+ pr_err(" Failed to enable dap mclk\n");
+ err = -ENODEV;
+ goto wm8903_init_fail;
+ }
+
+ /* Add tegra specific widgets */
+ snd_soc_dapm_new_controls(codec, tegra_dapm_widgets,
+ ARRAY_SIZE(tegra_dapm_widgets));
+
+ /* Set up tegra specific audio path audio_map */
+ snd_soc_dapm_add_routes(codec, audio_map,
+ ARRAY_SIZE(audio_map));
+
+ /* Add jack detection */
+ err = tegra_jack_init(codec);
+ if (err < 0) {
+ pr_err("Failed in jack init\n");
+ goto wm8903_init_fail;
+ }
+
+ /* Default to OFF */
+ tegra_ext_control(codec, TEGRA_SPK);/* TEGRA_AUDIO_OFF); */
+
+ err = tegra_controls_init(codec);
+ if (err < 0) {
+ pr_err("Failed in controls init\n");
+ goto wm8903_init_fail;
+ }
+
+ audio_data->codec = codec;
+ audio_data->init_done = 1;
+ /* snd_soc_dapm_enable_pin(codec, "Headphone"); */
+
+ }
+
+ return err;
+
+wm8903_init_fail:
+
+ tegra_das_disable_mclk();
+ tegra_das_close();
+ return err;
+}
+
+
+#define TEGRA_CREATE_SOC_DAI_LINK(xname, xstreamname, \
+ xcpudai, xcodecdai, xops) \
+{ \
+ .name = xname, \
+ .stream_name = xstreamname, \
+ .cpu_dai = xcpudai, \
+ .codec_dai = xcodecdai, \
+ .init = tegra_codec_init, \
+ .ops = xops, \
+}
+
+static struct snd_soc_dai_link tegra_soc_dai[] = {
+ TEGRA_CREATE_SOC_DAI_LINK("MAX98088", "MAX98088 HiFi",
+ &tegra_i2s_dai[0], &max98088_dai[0],
+ &tegra_hifi_ops),
+
+#if defined(CONFIG_ARCH_TEGRA_2x_SOC)
+ TEGRA_CREATE_SOC_DAI_LINK("Tegra-generic", "Tegra Generic Voice",
+ &tegra_i2s_dai[1], &tegra_generic_codec_dai[1],
+ &tegra_voice_ops),
+#else
+ /*
+ TEGRA_CREATE_SOC_DAI_LINK("Tegra-generic-0", "Tegra BB Voice",
+ &tegra_i2s_dai[1], &tegra_generic_codec_dai[1],
+ &tegra_voice_ops),
+ TEGRA_CREATE_SOC_DAI_LINK("Tegra-generic-1", "Tegra BT Voice",
+ &tegra_i2s_dai[2], &tegra_generic_codec_dai[2],
+ &tegra_voice_ops),
+ */
+
+#endif
+
+ TEGRA_CREATE_SOC_DAI_LINK("Tegra-spdif", "Tegra Spdif",
+ &tegra_spdif_dai, &tegra_generic_codec_dai[0],
+ &tegra_spdif_ops),
+};
+
+static struct tegra_audio_data audio_data = {
+ .init_done = 0,
+ .play_device = TEGRA_AUDIO_DEVICE_NONE,
+ .capture_device = TEGRA_AUDIO_DEVICE_NONE,
+ .is_call_mode = false,
+ .codec_con = TEGRA_AUDIO_OFF,
+};
+
+static struct snd_soc_card tegra_snd_soc = {
+ .name = "tegra",
+ .platform = &tegra_soc_platform,
+ .dai_link = tegra_soc_dai,
+ .num_links = ARRAY_SIZE(tegra_soc_dai),
+ .suspend_pre = tegra_soc_suspend_pre,
+ .suspend_post = tegra_soc_suspend_post,
+ .resume_pre = tegra_soc_resume_pre,
+ .resume_post = tegra_soc_resume_post,
+};
+
+static struct snd_soc_device tegra_snd_devdata = {
+ .card = &tegra_snd_soc,
+ .codec_dev = &soc_codec_dev_max98088,
+ .codec_data = &audio_data,
+};
+
+static int __init tegra_init(void)
+{
+ int ret = 0;
+
+ tegra_snd_device = platform_device_alloc("soc-audio", -1);
+ if (!tegra_snd_device) {
+ pr_err("failed to allocate soc-audio\n");
+ return -ENOMEM;
+ }
+
+ platform_set_drvdata(tegra_snd_device, &tegra_snd_devdata);
+ tegra_snd_devdata.dev = &tegra_snd_device->dev;
+
+ ret = platform_device_add(tegra_snd_device);
+ if (ret) {
+ pr_err("audio device could not be added\n");
+ goto fail;
+ }
+
+ return 0;
+
+fail:
+ if (tegra_snd_device) {
+ platform_device_put(tegra_snd_device);
+ tegra_snd_device = 0;
+ }
+
+ return ret;
+}
+
+static void __exit tegra_exit(void)
+{
+ tegra_jack_exit();
+ platform_device_unregister(tegra_snd_device);
+}
+
+module_init(tegra_init);
+module_exit(tegra_exit);
+
+/* Module information */
+MODULE_DESCRIPTION("Tegra ALSA SoC for MAX98088");
+MODULE_LICENSE("GPL");