summaryrefslogtreecommitdiff
path: root/sound
diff options
context:
space:
mode:
authorManoj Gangwal <mgangwal@nvidia.com>2011-12-20 21:01:20 +0530
committerRohan Somvanshi <rsomvanshi@nvidia.com>2012-01-10 08:15:17 -0800
commit043d445707d9502031984d9dfeeb643bc513e8ab (patch)
tree0a27237c914754b6a22570f11c46644a5794f431 /sound
parente63d69c493e0c0cc5f0f81fd09381e61178964d3 (diff)
asoc: tegra: ALC5640 machine: Add ALC5640 audio codec
Changes made to add support of ALC5640 codec for cardhu. Bug 910788 Change-Id: I134b95a20d1a377eeb534e263fb74c0871e2fd0f Signed-off-by: Manoj Gangwal <mgangwal@nvidia.com> Reviewed-on: http://git-master/r/71459 Reviewed-by: Sumit Bhattacharya <sumitb@nvidia.com> Reviewed-by: Automatic_Commit_Validation_User Reviewed-by: Bharat Nihalani <bnihalani@nvidia.com> (cherry picked from commit 844ef13a8281fe307b1adddcdc440b7e191d8926) Reviewed-on: http://git-master/r/73956 Reviewed-by: Varun Wadekar <vwadekar@nvidia.com> Tested-by: Varun Wadekar <vwadekar@nvidia.com>
Diffstat (limited to 'sound')
-rw-r--r--sound/soc/tegra/Kconfig21
-rw-r--r--sound/soc/tegra/Makefile2
-rw-r--r--sound/soc/tegra/tegra_rt5640.c573
3 files changed, 596 insertions, 0 deletions
diff --git a/sound/soc/tegra/Kconfig b/sound/soc/tegra/Kconfig
index b1f6bf4ce99a..59a6633915db 100644
--- a/sound/soc/tegra/Kconfig
+++ b/sound/soc/tegra/Kconfig
@@ -150,3 +150,24 @@ config SND_SOC_TEGRA_TLV320AIC326X
help
Say Y or M here if you want to add support for SoC audio on Tegra
boards using the TI AIC326X codec.
+
+config MACH_HAS_SND_SOC_TEGRA_RT5640
+ bool
+ help
+ Machines that use the SND_SOC_TEGRA_RT5640 driver should select
+ this config option, in order to allow the user to enable
+ SND_SOC_TEGRA_RT5640.
+
+config SND_SOC_TEGRA_RT5640
+ tristate "SoC Audio support for Tegra boards using a ALC5640 codec"
+ depends on SND_SOC_TEGRA && I2C && TEGRA_DC
+ depends on MACH_HAS_SND_SOC_TEGRA_RT5640
+ select SND_SOC_TEGRA30_I2S if ARCH_TEGRA_3x_SOC
+ select SND_SOC_TEGRA30_SPDIF if ARCH_TEGRA_3x_SOC
+ select SND_SOC_RT5640
+ select SND_SOC_SPDIF
+ select SND_SOC_TEGRA30_DAM if ARCH_TEGRA_3x_SOC
+ help
+ Say Y or M here if you want to add support for SoC audio on Tegra
+ boards using the ALC5640 codec. Currently, the supported boards
+ are Kai and Cardhu.
diff --git a/sound/soc/tegra/Makefile b/sound/soc/tegra/Makefile
index 821e9b06a6f5..dceef1137534 100644
--- a/sound/soc/tegra/Makefile
+++ b/sound/soc/tegra/Makefile
@@ -27,9 +27,11 @@ snd-soc-tegra-trimslice-objs := trimslice.o
snd-soc-tegra-wm8753-objs := tegra_wm8753.o
snd-soc-tegra-max98088-objs := tegra_max98088.o
snd-soc-tegra-aic326x-objs := tegra_aic326x.o
+snd-soc-tegra-rt5640-objs := tegra_rt5640.o
obj-$(CONFIG_SND_SOC_TEGRA_WM8903) += snd-soc-tegra-wm8903.o
obj-$(CONFIG_SND_SOC_TEGRA_TRIMSLICE) += snd-soc-tegra-trimslice.o
obj-$(CONFIG_SND_SOC_TEGRA_WM8753) += snd-soc-tegra-wm8753.o
obj-$(CONFIG_SND_SOC_TEGRA_MAX98088) += snd-soc-tegra-max98088.o
obj-$(CONFIG_SND_SOC_TEGRA_TLV320AIC326X) += snd-soc-tegra-aic326x.o
+obj-$(CONFIG_SND_SOC_TEGRA_RT5640) += snd-soc-tegra-rt5640.o
diff --git a/sound/soc/tegra/tegra_rt5640.c b/sound/soc/tegra/tegra_rt5640.c
new file mode 100644
index 000000000000..5bbfab3b65bb
--- /dev/null
+++ b/sound/soc/tegra/tegra_rt5640.c
@@ -0,0 +1,573 @@
+/*
+ * tegra_rt5640.c - Tegra machine ASoC driver for boards using ALC5640 codec.
+ *
+ * Author: Johnny Qiu <joqiu@nvidia.com>
+ * Copyright (C) 2011 - NVIDIA, Inc.
+ *
+ * Based on code copyright/by:
+ *
+ * (c) 2009, 2010 Nvidia Graphics Pvt. Ltd.
+ *
+ * Copyright 2007 Wolfson Microelectronics PLC.
+ * Author: Graeme Gregory
+ * graeme.gregory@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
+ * version 2 as published by the Free Software Foundation.
+ *
+ * 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 St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <asm/mach-types.h>
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+#include <linux/regulator/consumer.h>
+#ifdef CONFIG_SWITCH
+#include <linux/switch.h>
+#endif
+
+#include <mach/tegra_rt5640_pdata.h>
+
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include "../codecs/rt5640.h"
+
+#include "tegra_pcm.h"
+#include "tegra_asoc_utils.h"
+
+#define DRV_NAME "tegra-snd-rt5640"
+
+#define GPIO_SPKR_EN BIT(0)
+#define GPIO_HP_MUTE BIT(1)
+#define GPIO_INT_MIC_EN BIT(2)
+#define GPIO_EXT_MIC_EN BIT(3)
+#define GPIO_HP_DET BIT(4)
+
+struct tegra_rt5640 {
+ struct tegra_asoc_utils_data util_data;
+ struct tegra_rt5640_platform_data *pdata;
+ struct regulator *spk_reg;
+ struct regulator *dmic_reg;
+ int gpio_requested;
+#ifdef CONFIG_SWITCH
+ int jack_status;
+#endif
+};
+
+static int tegra_rt5640_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;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_card *card = codec->card;
+ struct tegra_rt5640 *machine = snd_soc_card_get_drvdata(card);
+ int srate, mclk, i2s_daifmt;
+ int err;
+
+ srate = params_rate(params);
+ mclk = 256 * srate;
+ err = tegra_asoc_utils_set_rate(&machine->util_data, srate, mclk);
+ if (err < 0) {
+ if (!(machine->util_data.set_mclk % mclk))
+ mclk = machine->util_data.set_mclk;
+ else {
+ dev_err(card->dev, "Can't configure clocks\n");
+ return err;
+ }
+ }
+
+ tegra_asoc_utils_lock_clk_rate(&machine->util_data, 1);
+
+ i2s_daifmt = SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS;
+
+ i2s_daifmt |= SND_SOC_DAIFMT_I2S;
+
+ err = snd_soc_dai_set_fmt(codec_dai, i2s_daifmt);
+ if (err < 0) {
+ dev_err(card->dev, "codec_dai fmt not set\n");
+ return err;
+ }
+
+ err = snd_soc_dai_set_fmt(cpu_dai, i2s_daifmt);
+ if (err < 0) {
+ dev_err(card->dev, "cpu_dai fmt not set\n");
+ return err;
+ }
+
+ err = snd_soc_dai_set_sysclk(codec_dai, 0, mclk,
+ SND_SOC_CLOCK_IN);
+ if (err < 0) {
+ dev_err(card->dev, "codec_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)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_card *card = rtd->card;
+ struct tegra_rt5640 *machine = snd_soc_card_get_drvdata(card);
+ int srate, mclk, min_mclk;
+ int err;
+
+ srate = params_rate(params);
+ switch (srate) {
+ case 11025:
+ case 22050:
+ case 44100:
+ case 88200:
+ mclk = 11289600;
+ break;
+ case 8000:
+ case 16000:
+ case 32000:
+ case 48000:
+ case 64000:
+ case 96000:
+ mclk = 12288000;
+ break;
+ default:
+ return -EINVAL;
+ }
+ min_mclk = 128 * srate;
+
+ err = tegra_asoc_utils_set_rate(&machine->util_data, srate, mclk);
+ if (err < 0) {
+ if (!(machine->util_data.set_mclk % min_mclk))
+ mclk = machine->util_data.set_mclk;
+ else {
+ dev_err(card->dev, "Can't configure clocks\n");
+ return err;
+ }
+ }
+
+ tegra_asoc_utils_lock_clk_rate(&machine->util_data, 1);
+
+ return 0;
+}
+
+static int tegra_hw_free(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct tegra_rt5640 *machine = snd_soc_card_get_drvdata(rtd->card);
+
+ tegra_asoc_utils_lock_clk_rate(&machine->util_data, 0);
+
+ return 0;
+}
+
+static struct snd_soc_ops tegra_rt5640_ops = {
+ .hw_params = tegra_rt5640_hw_params,
+ .hw_free = tegra_hw_free,
+};
+
+static struct snd_soc_ops tegra_spdif_ops = {
+ .hw_params = tegra_spdif_hw_params,
+ .hw_free = tegra_hw_free,
+};
+
+static struct snd_soc_jack tegra_rt5640_hp_jack;
+
+static struct snd_soc_jack_gpio tegra_rt5640_hp_jack_gpio = {
+ .name = "headphone detect",
+ .report = SND_JACK_HEADPHONE,
+ .debounce_time = 150,
+ .invert = 1,
+};
+
+#ifdef CONFIG_SWITCH
+/* These values are copied from Android WiredAccessoryObserver */
+enum headset_state {
+ BIT_NO_HEADSET = 0,
+ BIT_HEADSET = (1 << 0),
+ BIT_HEADSET_NO_MIC = (1 << 1),
+};
+
+static struct switch_dev tegra_rt5640_headset_switch = {
+ .name = "h2w1",
+};
+#else
+static struct snd_soc_jack_pin tegra_rt5640_hp_jack_pins[] = {
+ {
+ .pin = "Headphone Jack",
+ .mask = SND_JACK_HEADPHONE,
+ },
+};
+
+static struct snd_soc_jack_pin tegra_rt5640_mic_jack_pins[] = {
+ {
+ .pin = "Mic Jack",
+ .mask = SND_JACK_MICROPHONE,
+ },
+};
+#endif
+
+static int tegra_rt5640_event_int_spk(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ struct snd_soc_dapm_context *dapm = w->dapm;
+ struct snd_soc_card *card = dapm->card;
+ struct tegra_rt5640 *machine = snd_soc_card_get_drvdata(card);
+ struct tegra_rt5640_platform_data *pdata = machine->pdata;
+
+ if (machine->spk_reg) {
+ if (SND_SOC_DAPM_EVENT_ON(event))
+ regulator_enable(machine->spk_reg);
+ else
+ regulator_disable(machine->spk_reg);
+ }
+
+ if (!(machine->gpio_requested & GPIO_SPKR_EN))
+ return 0;
+
+ gpio_set_value_cansleep(pdata->gpio_spkr_en,
+ SND_SOC_DAPM_EVENT_ON(event));
+
+ return 0;
+}
+
+static int tegra_rt5640_event_hp(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ struct snd_soc_dapm_context *dapm = w->dapm;
+ struct snd_soc_card *card = dapm->card;
+ struct tegra_rt5640 *machine = snd_soc_card_get_drvdata(card);
+ struct tegra_rt5640_platform_data *pdata = machine->pdata;
+
+ if (!(machine->gpio_requested & GPIO_HP_MUTE))
+ return 0;
+
+ gpio_set_value_cansleep(pdata->gpio_hp_mute,
+ !SND_SOC_DAPM_EVENT_ON(event));
+
+ return 0;
+}
+
+static int tegra_rt5640_event_int_mic(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ struct snd_soc_dapm_context *dapm = w->dapm;
+ struct snd_soc_card *card = dapm->card;
+ struct tegra_rt5640 *machine = snd_soc_card_get_drvdata(card);
+ struct tegra_rt5640_platform_data *pdata = machine->pdata;
+
+ if (machine->dmic_reg) {
+ if (SND_SOC_DAPM_EVENT_ON(event))
+ regulator_enable(machine->dmic_reg);
+ else
+ regulator_disable(machine->dmic_reg);
+ }
+
+ if (!(machine->gpio_requested & GPIO_INT_MIC_EN))
+ return 0;
+
+ gpio_set_value_cansleep(pdata->gpio_int_mic_en,
+ SND_SOC_DAPM_EVENT_ON(event));
+
+ return 0;
+}
+
+static int tegra_rt5640_event_ext_mic(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ struct snd_soc_dapm_context *dapm = w->dapm;
+ struct snd_soc_card *card = dapm->card;
+ struct tegra_rt5640 *machine = snd_soc_card_get_drvdata(card);
+ struct tegra_rt5640_platform_data *pdata = machine->pdata;
+
+ if (!(machine->gpio_requested & GPIO_EXT_MIC_EN))
+ return 0;
+
+ gpio_set_value_cansleep(pdata->gpio_ext_mic_en,
+ SND_SOC_DAPM_EVENT_ON(event));
+
+ return 0;
+}
+
+static const struct snd_soc_dapm_widget cardhu_dapm_widgets[] = {
+ SND_SOC_DAPM_SPK("Int Spk", tegra_rt5640_event_int_spk),
+ SND_SOC_DAPM_HP("Headphone Jack", tegra_rt5640_event_hp),
+ SND_SOC_DAPM_MIC("Mic Jack", tegra_rt5640_event_ext_mic),
+ SND_SOC_DAPM_MIC("Int Mic", tegra_rt5640_event_int_mic),
+};
+
+static const struct snd_soc_dapm_route cardhu_audio_map[] = {
+ {"Headphone Jack", NULL, "HPOR"},
+ {"Headphone Jack", NULL, "HPOL"},
+ {"Int Spk", NULL, "SPORP"},
+ {"Int Spk", NULL, "SPORN"},
+ {"Int Spk", NULL, "SPOLP"},
+ {"Int Spk", NULL, "SPOLN"},
+ {"micbias1", NULL, "Mic Jack"},
+ {"IN1P", NULL, "micbias1"},
+ {"IN1N", NULL, "micbias1"},
+ {"micbias1", NULL, "Int Mic"},
+ {"IN2P", NULL, "micbias1"},
+};
+
+static const struct snd_kcontrol_new cardhu_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Int Spk"),
+ SOC_DAPM_PIN_SWITCH("Headphone Jack"),
+ SOC_DAPM_PIN_SWITCH("Mic Jack"),
+ SOC_DAPM_PIN_SWITCH("Int Mic"),
+};
+
+static int tegra_rt5640_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_codec *codec = rtd->codec;
+ struct snd_soc_dapm_context *dapm = &codec->dapm;
+ struct snd_soc_card *card = codec->card;
+ struct tegra_rt5640 *machine = snd_soc_card_get_drvdata(card);
+ struct tegra_rt5640_platform_data *pdata = machine->pdata;
+ int ret;
+
+ if (gpio_is_valid(pdata->gpio_spkr_en)) {
+ ret = gpio_request(pdata->gpio_spkr_en, "spkr_en");
+ if (ret) {
+ dev_err(card->dev, "cannot get spkr_en gpio\n");
+ return ret;
+ }
+ machine->gpio_requested |= GPIO_SPKR_EN;
+
+ gpio_direction_output(pdata->gpio_spkr_en, 0);
+ }
+
+ if (gpio_is_valid(pdata->gpio_hp_mute)) {
+ ret = gpio_request(pdata->gpio_hp_mute, "hp_mute");
+ if (ret) {
+ dev_err(card->dev, "cannot get hp_mute gpio\n");
+ return ret;
+ }
+ machine->gpio_requested |= GPIO_HP_MUTE;
+
+ gpio_direction_output(pdata->gpio_hp_mute, 0);
+ }
+
+ if (gpio_is_valid(pdata->gpio_int_mic_en)) {
+ ret = gpio_request(pdata->gpio_int_mic_en, "int_mic_en");
+ if (ret) {
+ dev_err(card->dev, "cannot get int_mic_en gpio\n");
+ return ret;
+ }
+ machine->gpio_requested |= GPIO_INT_MIC_EN;
+
+ /* Disable int mic; enable signal is active-high */
+ gpio_direction_output(pdata->gpio_int_mic_en, 0);
+ }
+
+ if (gpio_is_valid(pdata->gpio_ext_mic_en)) {
+ ret = gpio_request(pdata->gpio_ext_mic_en, "ext_mic_en");
+ if (ret) {
+ dev_err(card->dev, "cannot get ext_mic_en gpio\n");
+ return ret;
+ }
+ machine->gpio_requested |= GPIO_EXT_MIC_EN;
+
+ /* Enable ext mic; enable signal is active-low */
+ gpio_direction_output(pdata->gpio_ext_mic_en, 0);
+ }
+
+ ret = snd_soc_add_controls(codec, cardhu_controls,
+ ARRAY_SIZE(cardhu_controls));
+ if (ret < 0)
+ return ret;
+
+ snd_soc_dapm_new_controls(dapm, cardhu_dapm_widgets,
+ ARRAY_SIZE(cardhu_dapm_widgets));
+
+ snd_soc_dapm_add_routes(dapm, cardhu_audio_map,
+ ARRAY_SIZE(cardhu_audio_map));
+ /* FIXME: Calculate automatically based on DAPM routes? */
+ snd_soc_dapm_nc_pin(dapm, "LOUTL");
+ snd_soc_dapm_nc_pin(dapm, "LOUTR");
+
+ snd_soc_dapm_sync(dapm);
+
+ return 0;
+}
+
+static struct snd_soc_dai_link tegra_rt5640_dai[] = {
+ {
+ .name = "RT5640",
+ .stream_name = "RT5640 PCM",
+ .codec_name = "rt5640.4-001c",
+ .platform_name = "tegra-pcm-audio",
+ .cpu_dai_name = "tegra30-i2s.1",
+ .codec_dai_name = "rt5640-aif2",
+ .init = tegra_rt5640_init,
+ .ops = &tegra_rt5640_ops,
+ },
+ {
+ .name = "SPDIF",
+ .stream_name = "SPDIF PCM",
+ .codec_name = "spdif-dit.0",
+ .platform_name = "tegra-pcm-audio",
+ .cpu_dai_name = "tegra30-spdif",
+ .codec_dai_name = "dit-hifi",
+ .ops = &tegra_spdif_ops,
+ },
+};
+
+static struct snd_soc_card snd_soc_tegra_rt5640 = {
+ .name = "tegra-rt5640",
+ .dai_link = tegra_rt5640_dai,
+ .num_links = ARRAY_SIZE(tegra_rt5640_dai),
+};
+
+static __devinit int tegra_rt5640_driver_probe(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = &snd_soc_tegra_rt5640;
+ struct tegra_rt5640 *machine;
+ struct tegra_rt5640_platform_data *pdata;
+ int ret;
+
+ pdata = pdev->dev.platform_data;
+ if (!pdata) {
+ dev_err(&pdev->dev, "No platform data supplied\n");
+ return -EINVAL;
+ }
+
+ machine = kzalloc(sizeof(struct tegra_rt5640), GFP_KERNEL);
+ if (!machine) {
+ dev_err(&pdev->dev, "Can't allocate tegra_rt5640 struct\n");
+ return -ENOMEM;
+ }
+
+ machine->pdata = pdata;
+
+ ret = tegra_asoc_utils_init(&machine->util_data, &pdev->dev);
+ if (ret)
+ goto err_free_machine;
+
+ machine->spk_reg = regulator_get(&pdev->dev, "vdd_spk_amp");
+ if (IS_ERR(machine->spk_reg)) {
+ dev_info(&pdev->dev, "No speaker regulator found\n");
+ machine->spk_reg = 0;
+ }
+
+#ifdef CONFIG_SWITCH
+ /* Addd h2w swith class support */
+ ret = switch_dev_register(&tegra_rt5640_headset_switch);
+ if (ret < 0)
+ goto err_fini_utils;
+#endif
+
+ card->dev = &pdev->dev;
+ platform_set_drvdata(pdev, card);
+ snd_soc_card_set_drvdata(card, machine);
+
+ ret = snd_soc_register_card(card);
+ if (ret) {
+ dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n",
+ ret);
+ goto err_unregister_switch;
+ }
+
+ if (!card->instantiated) {
+ ret = -ENODEV;
+ dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n",
+ ret);
+ goto err_unregister_card;
+ }
+
+ return 0;
+
+err_unregister_card:
+ snd_soc_unregister_card(card);
+err_unregister_switch:
+#ifdef CONFIG_SWITCH
+ switch_dev_unregister(&tegra_rt5640_headset_switch);
+err_fini_utils:
+#endif
+ tegra_asoc_utils_fini(&machine->util_data);
+err_free_machine:
+ kfree(machine);
+ return ret;
+}
+
+static int __devexit tegra_rt5640_driver_remove(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = platform_get_drvdata(pdev);
+ struct tegra_rt5640 *machine = snd_soc_card_get_drvdata(card);
+ struct tegra_rt5640_platform_data *pdata = machine->pdata;
+
+ if (machine->gpio_requested & GPIO_HP_DET)
+ snd_soc_jack_free_gpios(&tegra_rt5640_hp_jack,
+ 1,
+ &tegra_rt5640_hp_jack_gpio);
+ if (machine->gpio_requested & GPIO_EXT_MIC_EN)
+ gpio_free(pdata->gpio_ext_mic_en);
+ if (machine->gpio_requested & GPIO_INT_MIC_EN)
+ gpio_free(pdata->gpio_int_mic_en);
+ if (machine->gpio_requested & GPIO_HP_MUTE)
+ gpio_free(pdata->gpio_hp_mute);
+ if (machine->gpio_requested & GPIO_SPKR_EN)
+ gpio_free(pdata->gpio_spkr_en);
+ machine->gpio_requested = 0;
+
+ if (machine->spk_reg)
+ regulator_put(machine->spk_reg);
+ if (machine->dmic_reg)
+ regulator_put(machine->dmic_reg);
+
+ snd_soc_unregister_card(card);
+
+ tegra_asoc_utils_fini(&machine->util_data);
+
+#ifdef CONFIG_SWITCH
+ switch_dev_unregister(&tegra_rt5640_headset_switch);
+#endif
+ kfree(machine);
+
+ return 0;
+}
+
+static struct platform_driver tegra_rt5640_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ .pm = &snd_soc_pm_ops,
+ },
+ .probe = tegra_rt5640_driver_probe,
+ .remove = __devexit_p(tegra_rt5640_driver_remove),
+};
+
+static int __init tegra_rt5640_modinit(void)
+{
+ return platform_driver_register(&tegra_rt5640_driver);
+}
+module_init(tegra_rt5640_modinit);
+
+static void __exit tegra_rt5640_modexit(void)
+{
+ platform_driver_unregister(&tegra_rt5640_driver);
+}
+module_exit(tegra_rt5640_modexit);
+
+MODULE_AUTHOR("Johnny Qiu <joqiu@nvidia.com>");
+MODULE_DESCRIPTION("Tegra+RT5640 machine ASoC driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);