diff options
author | Vinod G <vinodg@nvidia.com> | 2011-09-14 16:31:23 -0700 |
---|---|---|
committer | Annamaria Pyreddy <apyreddy@nvidia.com> | 2011-09-15 18:09:33 -0700 |
commit | 4e235492eb984298c5de00fcfa2692515c0c2a3c (patch) | |
tree | eed75fb436ca96960e5c1980cb34571fd94740fb /sound | |
parent | 1b5030c925af043997dc833f2228d35af0d2b43e (diff) |
arm: tegra: soc: Add machine driver for aic3262.
Add the machine driver support for the aic3262 codec
bug 816608
Change-Id: Ib3f510f953d59b239353b62635165fece5ce9e49
Reviewed-on: http://git-master/r/52496
Reviewed-by: Vinod Gopalakrishnakurup <vinodg@nvidia.com>
Tested-by: Vinod Gopalakrishnakurup <vinodg@nvidia.com>
Reviewed-by: Scott Peterson <speterson@nvidia.com>
Diffstat (limited to 'sound')
-rw-r--r-- | sound/soc/tegra/Makefile | 1 | ||||
-rw-r--r-- | sound/soc/tegra/tegra_soc_aic326x.c | 477 |
2 files changed, 478 insertions, 0 deletions
diff --git a/sound/soc/tegra/Makefile b/sound/soc/tegra/Makefile index 205c240121db..41ee945438d5 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_TLV320AIC326X} += tegra_soc_aic326x.o diff --git a/sound/soc/tegra/tegra_soc_aic326x.c b/sound/soc/tegra/tegra_soc_aic326x.c new file mode 100644 index 000000000000..7d486c44b8b8 --- /dev/null +++ b/sound/soc/tegra/tegra_soc_aic326x.c @@ -0,0 +1,477 @@ +/* +* tegra_soc_aic326x.c +* +* Copyright (c) 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. +*/ + + +#include "tegra_soc.h" +#include "../codecs/tlv320aic326x.h" +#include <sound/soc-dapm.h> +#include <linux/regulator/consumer.h> + +#include <linux/types.h> +#include <sound/jack.h> +#include <linux/switch.h> +#include <mach/gpio.h> +#include <mach/audio.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/workqueue.h> + +/* Board Specific GPIO configuration for Whistler */ +#define TEGRA_GPIO_PW3 179 + +static struct aic326x_headphone_jack +{ + struct snd_jack *jack; + int gpio; + struct work_struct work; + struct snd_soc_codec *pcodec; +}; + +static struct aic326x_headphone_jack *aic326x_jack; + +static struct platform_device *tegra_snd_device; +static struct regulator *aic326x_reg; + +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; + +static int aic326x_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; + struct tegra_audio_data *audio_data = rtd->socdev->codec_data; + enum dac_dap_data_format data_fmt; + int dai_flag = 0, sys_clk; + int err; + + if (tegra_das_is_port_master(tegra_audio_codec_type_hifi)) + dai_flag |= SND_SOC_DAIFMT_CBM_CFM; + else + dai_flag |= SND_SOC_DAIFMT_CBS_CFS; + + 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_i2s) + dai_flag |= SND_SOC_DAIFMT_I2S; + else + 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 = clk_get_rate(audio_data->dap_mclk); + 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 aic326x_hifi_hw_free(struct snd_pcm_substream *substream) +{ + return 0; +} + +int aic326x_codec_startup(struct snd_pcm_substream *substream) +{ + tegra_das_power_mode(true); + return 0; +} + +void aic326x_codec_shutdown(struct snd_pcm_substream *substream) +{ + tegra_das_power_mode(false); +} + +int aic326x_soc_suspend_pre(struct platform_device *pdev, pm_message_t state) +{ +#if 0 + disable_irq(gpio_to_irq(aic326x_jack->gpio)); +#endif + return 0; +} + +int aic326x_soc_suspend_post(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct tegra_audio_data *audio_data = socdev->codec_data; + clk_disable(audio_data->dap_mclk); + + return 0; +} + +int aic326x_soc_resume_pre(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct tegra_audio_data *audio_data = socdev->codec_data; + clk_enable(audio_data->dap_mclk); + + return 0; +} + +int aic326x_soc_resume_post(struct platform_device *pdev) +{ +#if 0 + enable_irq(gpio_to_irq(aic326x_jack->gpio)); +#endif + return 0; +} + +static struct snd_soc_ops aic326x_hifi_ops = { + .hw_params = aic326x_hifi_hw_params, + .hw_free = aic326x_hifi_hw_free, + .startup = aic326x_codec_startup, + .shutdown = aic326x_codec_shutdown, +}; + +void tegra_ext_control(struct snd_soc_codec *codec, int new_con) +{ + struct tegra_audio_data *audio_data = codec->socdev->codec_data; + + /* 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_EAR_SPK) + snd_soc_dapm_enable_pin(codec, "EarPiece"); + else + snd_soc_dapm_disable_pin(codec, "EarPiece"); + + 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_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_OUT) + snd_soc_dapm_enable_pin(codec, "Headset Out"); + else + snd_soc_dapm_disable_pin(codec, "Headset Out"); + + if (new_con & TEGRA_HEADSET_IN) + snd_soc_dapm_enable_pin(codec, "Headset In"); + else + snd_soc_dapm_disable_pin(codec, "Headset In"); + + + /* signal a DAPM event */ + snd_soc_dapm_sync(codec); + audio_data->codec_con = new_con; +} + +/*tegra machine dapm widgets */ +static const struct snd_soc_dapm_widget aic326x_tegra_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone", NULL), + SND_SOC_DAPM_HP("EarPiece", NULL), + SND_SOC_DAPM_HP("Headset Out", NULL), + SND_SOC_DAPM_MIC("Headset In", NULL), + SND_SOC_DAPM_SPK("Int Spk", NULL), + SND_SOC_DAPM_MIC("Ext Mic", NULL), + SND_SOC_DAPM_LINE("Linein", NULL), +}; + +/* Tegra machine audio map (connections to the codec pins) */ +static const struct snd_soc_dapm_route aic326x_audio_map[] = { + + /* headphone*/ + {"Headphone", NULL, "HPL"}, + {"Headphone", NULL, "HPR"}, + + /* earpiece */ + {"EarPiece", NULL, "LOL"}, + {"EarPiece", NULL, "LOR"}, + + /* headset Jack */ + {"Headset Out", NULL, "HPL"}, + {"Headset Out", NULL, "HPR"}, + {"IN2L", NULL, "Headset In"}, + + /* build-in speaker */ + {"Int Spk", NULL, "SPKL"}, + {"Int Spk", NULL, "SPKR"}, + + /* external mic is stero */ + {"IN2L", NULL, "Ext Mic"}, + {"IN2R", NULL, "Ext Mic"}, + + /* Line in */ + {"RECL", NULL, "Linein"}, + {"RECR", NULL, "Linein"}, +}; + +#if 0 +static void aic326x_intr_work(struct work_struct *work) +{ + unsigned int value; + + /* Do something here */ + mutex_lock(&aic326x_jack->pcodec->mutex); + + /* GPIO4 interrupt disable (also disable other interrupts) */ + /* Invert GPIO4 interrupt polarity */ + /* GPIO4 interrupt enable */ + + mutex_unlock(&aic326x_jack->pcodec->mutex); +} + +static irqreturn_t aic326x_irq(int irq, void *data) +{ + schedule_work(&aic326x_jack->work); + return IRQ_HANDLED; +} +#endif + +static int aic326x_codec_init(struct snd_soc_codec *codec) +{ + struct tegra_audio_data *audio_data = codec->socdev->codec_data; + int ret = 0; + + if (!audio_data->init_done) { + audio_data->dap_mclk = tegra_das_get_dap_mclk(); + if (!audio_data->dap_mclk) { + pr_err("Failed to get dap mclk\n"); + ret = -ENODEV; + return ret; + } + clk_enable(audio_data->dap_mclk); + + /* Add tegra specific widgets */ + snd_soc_dapm_new_controls(codec, aic326x_tegra_dapm_widgets, + ARRAY_SIZE(aic326x_tegra_dapm_widgets)); + + /* Set up tegra specific audio path audio_map */ + snd_soc_dapm_add_routes(codec, aic326x_audio_map, + ARRAY_SIZE(aic326x_audio_map)); + + /* Add jack detection */ + /*ret = tegra_jack_init(codec); + if (ret < 0) { + pr_err("Failed in jack init\n"); + return ret; + }*/ + + /* Default to OFF */ + /*tegra_ext_control(codec, TEGRA_AUDIO_OFF);*/ + + ret = tegra_controls_init(codec); + if (ret < 0) { + pr_err("Failed in controls init\n"); + return ret; + } + +#if 0 + if (!aic326x_jack) { + unsigned int value; + aic326x_jack = kzalloc(sizeof(*aic326x_jack), + GFP_KERNEL); + if (!aic326x_jack) { + pr_err("failed to allocate aic326x-jack\n"); + return -ENOMEM; + } + + aic326x_jack->gpio = TEGRA_GPIO_PW3; + aic326x_jack->pcodec = codec; + + INIT_WORK(&aic326x_jack->work, aic326x_intr_work); + + ret = snd_jack_new(codec->card, + "Headphone Jack", SND_JACK_HEADPHONE, + &aic326x_jack->jack); + if (ret < 0) + goto failed; + + ret = gpio_request(aic326x_jack->gpio, + "headphone-detect-gpio"); + if (ret) + goto failed; + + ret = gpio_direction_input(aic326x_jack->gpio); + if (ret) + goto gpio_failed; + + tegra_gpio_enable(aic326x_jack->gpio); + + ret = request_irq(gpio_to_irq(aic326x_jack->gpio), + aic326x_irq, + IRQF_TRIGGER_FALLING, + "aic326x", + aic326x_jack); + + if (ret) + goto gpio_failed; + + /* Configure GPIO pin to generate the interrupt */ + /* Active low Interrupt */ + /* interupt when low Headphone connected */ + /* Enable GPIO interrupt,disable other interrupts */ + + } +#endif + audio_data->codec = codec; + audio_data->init_done = 1; + } + + return ret; +#if 0 +gpio_failed: + gpio_free(aic326x_jack->gpio); +failed: + kfree(aic326x_jack); + aic326x_jack = NULL; + return ret; +#endif +} + +static struct snd_soc_dai_link tegra_soc_dai[] = { + { + .name = "tlv320aic3262", + .stream_name = "tlv320aic3262 HiFi", + .cpu_dai = &tegra_i2s_dai[0], + .codec_dai = &tlv320aic3262_dai, + .init = aic326x_codec_init, + .ops = &aic326x_hifi_ops, + }, +}; + +static struct tegra_audio_data aic326x_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 aic326x_tegra_snd_soc = { + .name = "tegra", + .platform = &tegra_soc_platform, + .dai_link = tegra_soc_dai, + .num_links = ARRAY_SIZE(tegra_soc_dai), + .suspend_pre = aic326x_soc_suspend_pre, + .suspend_post = aic326x_soc_suspend_post, + .resume_pre = aic326x_soc_resume_pre, + .resume_post = aic326x_soc_resume_post, +}; + +static struct snd_soc_device aic326x_tegra_snd_devdata = { + .card = &aic326x_tegra_snd_soc, + .codec_dev = &soc_codec_dev_aic3262, + .codec_data = &aic326x_audio_data, +}; + +static int __init aic326x_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, &aic326x_tegra_snd_devdata); + aic326x_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; + } + + aic326x_reg = regulator_get(NULL, "avddio_audio"); + if (IS_ERR(aic326x_reg)) { + ret = PTR_ERR(aic326x_reg); + pr_err("unable to get aic326x regulator\n"); + goto fail; + } + + ret = regulator_enable(aic326x_reg); + if (ret) { + pr_err("aic326x regulator enable failed\n"); + goto err_put_regulator; + } + return 0; + +fail: + if (tegra_snd_device) { + platform_device_put(tegra_snd_device); + tegra_snd_device = 0; + } + + return ret; + +err_put_regulator: + regulator_put(aic326x_reg); + return ret; +} + +static void __exit aic326x_tegra_exit(void) +{ + platform_device_unregister(tegra_snd_device); + regulator_disable(aic326x_reg); + regulator_put(aic326x_reg); + + if (aic326x_jack) { + gpio_free(aic326x_jack->gpio); + kfree(aic326x_jack); + aic326x_jack = NULL; + } +} + +module_init(aic326x_tegra_init); +module_exit(aic326x_tegra_exit); + +/* Module information */ +MODULE_DESCRIPTION("Tegra ALSA SoC"); +MODULE_LICENSE("GPL"); + |