asoc: tegra: enable audio on ardbeg
Nikesh Oswal [Fri, 31 May 2013 09:53:34 +0000 (14:53 +0530)]
Bug 1256430

Change-Id: I93a2416a2d5344a461a6a60171b43bb0fd4245cb
Signed-off-by: Nikesh Oswal <noswal@nvidia.com>
Reviewed-on: http://git-master/r/234493
Reviewed-by: Sumit Bhattacharya <sumitb@nvidia.com>

arch/arm/configs/tegra12_android_defconfig
arch/arm/mach-tegra/Kconfig
arch/arm/mach-tegra/board-ardbeg.c
arch/arm/mach-tegra/board-ardbeg.h
sound/soc/tegra/Kconfig
sound/soc/tegra/Makefile
sound/soc/tegra/tegra_asoc_utils.c
sound/soc/tegra/tegra_rt5645.c [new file with mode: 0644]

index 01ee049..03dfc93 100644 (file)
@@ -365,6 +365,7 @@ CONFIG_SND_SOC_TEGRA=y
 CONFIG_SND_SOC_TEGRA_TLV320AIC326X=y
 CONFIG_SND_SOC_TEGRA_RT5639=y
 CONFIG_SND_SOC_TEGRA_RT5640=y
+CONFIG_SND_SOC_TEGRA_RT5645=y
 CONFIG_SND_SOC_TEGRA_CS42L73=y
 CONFIG_UHID=y
 CONFIG_HID_A4TECH=y
index 02bb184..6552e39 100644 (file)
@@ -278,6 +278,7 @@ config MACH_BONAIRE
 config MACH_ARDBEG
        bool "ARDBEG board"
        depends on ARCH_TEGRA_12x_SOC || ARCH_TEGRA_11x_SOC
+       select MACH_HAS_SND_SOC_TEGRA_RT5645 if SND_SOC
        help
          Support for NVIDIA ARDBEG Development platform
 
index bad1088..d21e19f 100644 (file)
 
 static struct board_info board_info, display_board_info;
 
+static struct i2c_board_info __initdata rt5645_board_info = {
+       I2C_BOARD_INFO("rt5645", 0x1a),
+};
+
 static __initdata struct tegra_clk_init_table ardbeg_clk_init_table[] = {
        /* name         parent          rate            enabled */
        { "pll_m",      NULL,           0,              false},
@@ -171,6 +175,7 @@ static void ardbeg_i2c_init(void)
        platform_device_register(&tegra11_i2c_device2);
        platform_device_register(&tegra11_i2c_device1);
 #endif
+       i2c_register_board_info(0, &rt5645_board_info, 1);
 }
 
 static struct platform_device *ardbeg_uart_devices[] __initdata = {
@@ -209,6 +214,43 @@ static struct uart_clk_parent uart_parent_clk[] = {
 };
 
 static struct tegra_uart_platform_data ardbeg_uart_pdata;
+
+static struct tegra_asoc_platform_data ardbeg_audio_pdata = {
+       .gpio_spkr_en = TEGRA_GPIO_SPKR_EN,
+       .gpio_hp_det = TEGRA_GPIO_HP_DET,
+       .gpio_hp_mute = -1,
+       .gpio_int_mic_en = TEGRA_GPIO_INT_MIC_EN,
+       .gpio_ext_mic_en = TEGRA_GPIO_EXT_MIC_EN,
+       .gpio_ldo1_en = TEGRA_GPIO_LDO1_EN,
+       .gpio_codec1 = TEGRA_GPIO_CODEC1_EN,
+       .gpio_codec2 = TEGRA_GPIO_CODEC2_EN,
+       .gpio_codec3 = TEGRA_GPIO_CODEC3_EN,
+       .i2s_param[HIFI_CODEC]       = {
+               .audio_port_id = 1,
+               .is_i2s_master = 1,
+               .i2s_mode = TEGRA_DAIFMT_I2S,
+       },
+       .i2s_param[BT_SCO] = {
+               .audio_port_id = 3,
+               .is_i2s_master = 1,
+               .i2s_mode = TEGRA_DAIFMT_DSP_A,
+       },
+};
+
+static void ardbeg_audio_init(void)
+{
+       ardbeg_audio_pdata.codec_name = "rt5645.0-001a";
+       ardbeg_audio_pdata.codec_dai_name = "rt5645-aif1";
+}
+
+static struct platform_device ardbeg_audio_device = {
+       .name       = "tegra-snd-rt5645",
+       .id       = 0,
+       .dev       = {
+               .platform_data = &ardbeg_audio_pdata,
+       },
+};
+
 static struct tegra_uart_platform_data ardbeg_loopback_uart_pdata;
 
 static void __init uart_debug_init(void)
@@ -292,20 +334,18 @@ static struct platform_device *ardbeg_devices[] __initdata = {
 #endif
 #endif
        &tegra_ahub_device,
-#if 0
        &tegra_dam_device0,
        &tegra_dam_device1,
        &tegra_dam_device2,
        &tegra_i2s_device1,
        &tegra_i2s_device3,
        &tegra_i2s_device4,
+       &ardbeg_audio_device,
        &tegra_spdif_device,
        &spdif_dit_device,
        &bluetooth_dit_device,
        &tegra_pcm_device,
-       &ardbeg_audio_device,
        &tegra_hda_device,
-#endif
 #if defined(CONFIG_CRYPTO_DEV_TEGRA_AES)
        &tegra_aes_device,
 #endif
@@ -585,6 +625,7 @@ static void __init tegra_ardbeg_late_init(void)
        ardbeg_i2c_init();
        ardbeg_spi_init();
        ardbeg_uart_init();
+       ardbeg_audio_init();
        platform_add_devices(ardbeg_devices, ARRAY_SIZE(ardbeg_devices));
        //tegra_ram_console_debug_init();
        tegra_io_dpd_init();
index 6dc6d2e..caf6680 100644 (file)
@@ -58,4 +58,28 @@ enum tegra_bb_type {
 #define UTMI2_PORT_OWNER_XUSB   0x2
 #define HSIC1_PORT_OWNER_XUSB   0x4
 
+/* Audio-related GPIOs */
+#ifdef CONFIG_ARCH_TEGRA_11x_SOC
+#define TEGRA_GPIO_CDC_IRQ TEGRA_GPIO_PW3
+#define TEGRA_GPIO_LDO1_EN TEGRA_GPIO_PV3
+#define TEGRA_GPIO_HP_DET  TEGRA_GPIO_PR7
+#define TEGRA_GPIO_CODEC1_EN  -1 /*TEGRA_GPIO_PP3*/
+#define TEGRA_GPIO_CODEC2_EN  -1 /*TEGRA_GPIO_PP1*/
+#define TEGRA_GPIO_CODEC3_EN  -1 /*TEGRA_GPIO_PV0*/
+#define TEGRA_GPIO_INT_MIC_EN -1 /*TEGRA_GPIO_PK3*/
+#define TEGRA_GPIO_SPKR_EN    -1
+#define TEGRA_GPIO_EXT_MIC_EN -1
+#else
+/*for t124 ardbeg */
+#define TEGRA_GPIO_CDC_IRQ TEGRA_GPIO_PH4
+#define TEGRA_GPIO_LDO1_EN TEGRA_GPIO_PR2
+#define TEGRA_GPIO_HP_DET  TEGRA_GPIO_PR7
+#define TEGRA_GPIO_CODEC1_EN  -1 /*TEGRA_GPIO_PP3*/
+#define TEGRA_GPIO_CODEC2_EN  -1 /*TEGRA_GPIO_PP1*/
+#define TEGRA_GPIO_CODEC3_EN  -1 /*TEGRA_GPIO_PV0*/
+#define TEGRA_GPIO_INT_MIC_EN -1 /*TEGRA_GPIO_PK3*/
+#define TEGRA_GPIO_SPKR_EN    -1
+#define TEGRA_GPIO_EXT_MIC_EN -1
+#endif
+
 #endif
index baf22d6..f2ca511 100644 (file)
@@ -237,6 +237,26 @@ config SND_SOC_TEGRA_RT5640
          boards using the ALC5640 codec. Currently, the supported boards
          are Kai,Cardhu,Dalmore and Macallan.
 
+config MACH_HAS_SND_SOC_TEGRA_RT5645
+       bool
+       help
+         Machines that use the SND_SOC_TEGRA_RT5645 driver should select
+         this config option, in order to allow the user to enable
+         SND_SOC_TEGRA_RT5645.
+
+config SND_SOC_TEGRA_RT5645
+       tristate "SoC Audio support for Tegra boards using a ALC5645 codec"
+       depends on SND_SOC_TEGRA && I2C && TEGRA_DC
+       select SND_SOC_TEGRA30_I2S if !ARCH_TEGRA_2x_SOC
+       select SND_SOC_TEGRA30_SPDIF if !ARCH_TEGRA_2x_SOC
+       select SND_SOC_RT5645
+       select SND_SOC_SPDIF
+       select SND_SOC_TEGRA30_DAM if !ARCH_TEGRA_2x_SOC
+       help
+         Say Y or M here if you want to add support for SoC audio on Tegra
+         boards using the ALC5645 codec. Currently, the supported boards
+         are Ardbeg.
+
 config MACH_HAS_SND_SOC_TEGRA_MAX98095
        bool
        help
index 8aac709..bf798c9 100644 (file)
@@ -34,6 +34,7 @@ snd-soc-tegra-max98088-objs := tegra_max98088.o
 snd-soc-tegra-aic326x-objs := tegra_aic326x.o
 snd-soc-tegra-aic325x-objs := tegra_aic325x.o
 snd-soc-tegra-rt5640-objs := tegra_rt5640.o
+snd-soc-tegra-rt5645-objs := tegra_rt5645.o
 snd-soc-tegra-max98095-objs := tegra_max98095.o
 snd-soc-tegra-vcm-objs := tegra_vcm.o
 snd-soc-tegra-cs42l73-objs := tegra_cs42l73.o
@@ -48,6 +49,7 @@ 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_TLV320AIC325X) += snd-soc-tegra-aic325x.o
 obj-$(CONFIG_SND_SOC_TEGRA_RT5640) += snd-soc-tegra-rt5640.o
+obj-$(CONFIG_SND_SOC_TEGRA_RT5645) += snd-soc-tegra-rt5645.o
 obj-$(CONFIG_SND_SOC_TEGRA_MAX98095) += snd-soc-tegra-max98095.o
 obj-$(CONFIG_SND_SOC_TEGRA_P1852) += snd-soc-tegra-vcm.o
 obj-$(CONFIG_SND_SOC_TEGRA_E1853) += snd-soc-tegra-vcm.o
index 22c8581..8994163 100644 (file)
@@ -182,8 +182,8 @@ static int tegra_get_dma_ch_id(struct snd_kcontrol *kcontrol,
        struct snd_soc_pcm_runtime *rtd;
        struct snd_pcm_substream *substream;
        struct tegra_runtime_data *prtd;
+       struct dma_chan *chan;
 
-       ucontrol->value.integer.value[0] = -1;
        if (data->avp_device_id < 0)
                return 0;
 
@@ -192,10 +192,15 @@ static int tegra_get_dma_ch_id(struct snd_kcontrol *kcontrol,
        if (!substream || !substream->runtime)
                return 0;
 
-       prtd = substream->runtime->private_data;
-       if (!prtd || !prtd->dma_chan)
+       prtd = (struct tegra_runtime_data *)
+               snd_dmaengine_pcm_get_data(substream);
+
+       if (!prtd)
                return 0;
 
+       chan = snd_dmaengine_pcm_get_chan(substream);
+
+       ucontrol->value.integer.value[0] = -1;
        ucontrol->value.integer.value[0] =
                tegra_dma_get_channel_id(prtd->dma_chan);
        return 0;
@@ -213,17 +218,22 @@ static int tegra_set_dma_addr(struct snd_kcontrol *kcontrol,
        if (data->avp_device_id < 0)
                return 0;
 
-       data->avp_dma_addr = ucontrol->value.integer.value[0];
+       rtd = &card->rtd[data->avp_device_id];
+       substream =
+       rtd->pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
 
        rtd = &card->rtd[data->avp_device_id];
        substream = rtd->pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
        if (!substream || !substream->runtime)
                return 0;
 
-       prtd = substream->runtime->private_data;
+       prtd = (struct tegra_runtime_data *)
+               snd_dmaengine_pcm_get_data(substream);
+
        if (!prtd)
                return 0;
 
+       data->avp_dma_addr = (dma_addr_t)ucontrol->value.integer.value[0];
        prtd->avp_dma_addr = data->avp_dma_addr;
        return 1;
 }
@@ -237,19 +247,22 @@ static int tegra_get_dma_addr(struct snd_kcontrol *kcontrol,
        struct snd_pcm_substream *substream;
        struct tegra_runtime_data *prtd;
 
-       ucontrol->value.integer.value[0] = 0;
        if (data->avp_device_id < 0)
                return 0;
 
        rtd = &card->rtd[data->avp_device_id];
        substream = rtd->pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
+
        if (!substream || !substream->runtime)
                return 0;
 
-       prtd = substream->runtime->private_data;
-       if (!prtd || !prtd->dma_chan)
+       prtd = (struct tegra_runtime_data *)
+               snd_dmaengine_pcm_get_data(substream);
+
+       if (!prtd)
                return 0;
 
+       ucontrol->value.integer.value[0] = 0;
        ucontrol->value.integer.value[0] = prtd->avp_dma_addr ?
                                           prtd->avp_dma_addr :
                                           substream->runtime->dma_addr;
@@ -412,8 +425,8 @@ EXPORT_SYMBOL_GPL(tegra_asoc_utils_clk_disable);
 
 int tegra_asoc_utils_register_ctls(struct tegra_asoc_utils_data *data)
 {
-       int i;
        int ret = 0;
+       int i;
 
        /* Add AVP related alsa controls */
        data->avp_device_id = -1;
diff --git a/sound/soc/tegra/tegra_rt5645.c b/sound/soc/tegra/tegra_rt5645.c
new file mode 100644 (file)
index 0000000..3da8e18
--- /dev/null
@@ -0,0 +1,938 @@
+/*
+ * tegra_rt5645.c - Tegra machine ASoC driver for boards using ALC5640 codec.
+ *
+ * Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved.
+ * 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/clk.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+#include <linux/regulator/consumer.h>
+#include <linux/delay.h>
+#ifdef CONFIG_SWITCH
+#include <linux/switch.h>
+#endif
+#include <linux/pm_runtime.h>
+#include <mach/tegra_asoc_pdata.h>
+#include <mach/gpio-tegra.h>
+#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/rt5645.h"
+#include "../codecs/rt5645.h"
+
+#include "tegra_pcm.h"
+#include "tegra_asoc_utils.h"
+#include <linux/tfa9887.h>
+#include "tegra30_ahub.h"
+#include "tegra30_i2s.h"
+
+#define DRV_NAME "tegra-snd-rt5645"
+
+#define DAI_LINK_HIFI          0
+#define DAI_LINK_SPDIF         1
+#define DAI_LINK_BTSCO         2
+#define NUM_DAI_LINKS          3
+
+const char *tegra_rt5645_i2s_dai_name[TEGRA30_NR_I2S_IFC] = {
+       "tegra30-i2s.0",
+       "tegra30-i2s.1",
+       "tegra30-i2s.2",
+       "tegra30-i2s.3",
+       "tegra30-i2s.4",
+};
+
+#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_rt5645 {
+       struct tegra_asoc_utils_data util_data;
+       struct tegra_asoc_platform_data *pdata;
+       struct regulator *spk_reg;
+       struct regulator *dmic_reg;
+       struct regulator *cdc_en;
+       int gpio_requested;
+#ifdef CONFIG_SWITCH
+       int jack_status;
+#endif
+       enum snd_soc_bias_level bias_level;
+       int clock_enabled;
+};
+
+static int tegra_rt5645_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_rt5645 *machine = snd_soc_card_get_drvdata(card);
+       struct tegra_asoc_platform_data *pdata = machine->pdata;
+       int srate, mclk, i2s_daifmt;
+       int err, rate;
+
+       srate = params_rate(params);
+       mclk = 256 * srate;
+
+       i2s_daifmt = SND_SOC_DAIFMT_NB_NF;
+       i2s_daifmt |= pdata->i2s_param[HIFI_CODEC].is_i2s_master ?
+                       SND_SOC_DAIFMT_CBS_CFS : SND_SOC_DAIFMT_CBM_CFM;
+
+       switch (pdata->i2s_param[HIFI_CODEC].i2s_mode) {
+       case TEGRA_DAIFMT_I2S:
+               i2s_daifmt |= SND_SOC_DAIFMT_I2S;
+               break;
+       case TEGRA_DAIFMT_DSP_A:
+               i2s_daifmt |= SND_SOC_DAIFMT_DSP_A;
+               break;
+       case TEGRA_DAIFMT_DSP_B:
+               i2s_daifmt |= SND_SOC_DAIFMT_DSP_B;
+               break;
+       case TEGRA_DAIFMT_LEFT_J:
+               i2s_daifmt |= SND_SOC_DAIFMT_LEFT_J;
+               break;
+       case TEGRA_DAIFMT_RIGHT_J:
+               i2s_daifmt |= SND_SOC_DAIFMT_RIGHT_J;
+               break;
+       default:
+               dev_err(card->dev, "Can't configure i2s format\n");
+               return -EINVAL;
+       }
+
+       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);
+
+       rate = clk_get_rate(machine->util_data.clk_cdev1);
+
+       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, rate, SND_SOC_CLOCK_IN);
+       if (err < 0) {
+               dev_err(card->dev, "codec_dai clock not set\n");
+               return err;
+       }
+
+       return 0;
+}
+
+static int tegra_bt_sco_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_rt5645 *machine = snd_soc_card_get_drvdata(card);
+       struct tegra_asoc_platform_data *pdata = machine->pdata;
+       int srate, mclk, min_mclk, i2s_daifmt;
+       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 = 64 * 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);
+
+       i2s_daifmt = SND_SOC_DAIFMT_NB_NF;
+       i2s_daifmt |= pdata->i2s_param[BT_SCO].is_i2s_master ?
+                       SND_SOC_DAIFMT_CBS_CFS : SND_SOC_DAIFMT_CBM_CFM;
+
+       switch (pdata->i2s_param[BT_SCO].i2s_mode) {
+       case TEGRA_DAIFMT_I2S:
+               i2s_daifmt |= SND_SOC_DAIFMT_I2S;
+               break;
+       case TEGRA_DAIFMT_DSP_A:
+               i2s_daifmt |= SND_SOC_DAIFMT_DSP_A;
+               break;
+       case TEGRA_DAIFMT_DSP_B:
+               i2s_daifmt |= SND_SOC_DAIFMT_DSP_B;
+               break;
+       case TEGRA_DAIFMT_LEFT_J:
+               i2s_daifmt |= SND_SOC_DAIFMT_LEFT_J;
+               break;
+       case TEGRA_DAIFMT_RIGHT_J:
+               i2s_daifmt |= SND_SOC_DAIFMT_RIGHT_J;
+               break;
+       default:
+               dev_err(card->dev, "Can't configure i2s format\n");
+               return -EINVAL;
+       }
+
+       err = snd_soc_dai_set_fmt(rtd->cpu_dai, i2s_daifmt);
+       if (err < 0) {
+               dev_err(card->dev, "cpu_dai fmt 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_rt5645 *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_rt5645 *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_rt5645_ops = {
+       .hw_params = tegra_rt5645_hw_params,
+       .hw_free = tegra_hw_free,
+};
+
+static struct snd_soc_ops tegra_rt5645_bt_sco_ops = {
+       .hw_params = tegra_bt_sco_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_rt5645_hp_jack;
+
+static struct snd_soc_jack_gpio tegra_rt5645_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_rt5645_headset_switch = {
+       .name = "h2w",
+};
+
+static int tegra_rt5645_jack_notifier(struct notifier_block *self,
+                             unsigned long action, void *dev)
+{
+       struct snd_soc_jack *jack = dev;
+       struct snd_soc_codec *codec = jack->codec;
+       struct snd_soc_card *card = codec->card;
+       struct tegra_rt5645 *machine = snd_soc_card_get_drvdata(card);
+       struct tegra_asoc_platform_data *pdata = machine->pdata;
+       enum headset_state state = BIT_NO_HEADSET;
+       unsigned char status_jack = 0;
+
+       if (jack == &tegra_rt5645_hp_jack) {
+               if (action) {
+                       /* Enable ext mic; enable signal is active-low */
+                       if (gpio_is_valid(pdata->gpio_ext_mic_en))
+                               gpio_direction_output(
+                               pdata->gpio_ext_mic_en, 0);
+
+                       status_jack = rt5645_headset_detect(codec, 1);
+                       machine->jack_status &= ~SND_JACK_HEADPHONE;
+                       machine->jack_status &= ~SND_JACK_MICROPHONE;
+
+                       if (status_jack == RT5645_HEADPHO_DET)
+                               machine->jack_status |=
+                               SND_JACK_HEADPHONE;
+                       else if (status_jack == RT5645_HEADSET_DET) {
+                                       machine->jack_status |=
+                                                       SND_JACK_HEADPHONE;
+                                       machine->jack_status |=
+                                                       SND_JACK_MICROPHONE;
+                       }
+               } else {
+                       /* Disable ext mic; enable signal is active-low */
+                       if (gpio_is_valid(pdata->gpio_ext_mic_en))
+                               gpio_direction_output(
+                               pdata->gpio_ext_mic_en, 1);
+
+                       rt5645_headset_detect(codec, 0);
+
+                       machine->jack_status &= ~SND_JACK_HEADPHONE;
+                       machine->jack_status &= ~SND_JACK_MICROPHONE;
+               }
+       }
+
+       switch (machine->jack_status) {
+       case SND_JACK_HEADPHONE:
+               state = BIT_HEADSET_NO_MIC;
+               break;
+       case SND_JACK_HEADSET:
+               state = BIT_HEADSET;
+               break;
+       case SND_JACK_MICROPHONE:
+               /* mic: would not report */
+       default:
+               state = BIT_NO_HEADSET;
+       }
+
+       switch_set_state(&tegra_rt5645_headset_switch, state);
+
+       return NOTIFY_OK;
+}
+
+static struct notifier_block tegra_rt5645_jack_detect_nb = {
+       .notifier_call = tegra_rt5645_jack_notifier,
+};
+#else
+static struct snd_soc_jack_pin tegra_rt5645_hp_jack_pins[] = {
+       {
+               .pin = "Headphone Jack",
+               .mask = SND_JACK_HEADPHONE,
+       },
+};
+
+#endif
+
+static int tegra_rt5645_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_rt5645 *machine = snd_soc_card_get_drvdata(card);
+       struct tegra_asoc_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_rt5645_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_rt5645 *machine = snd_soc_card_get_drvdata(card);
+       struct tegra_asoc_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_rt5645_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_rt5645 *machine = snd_soc_card_get_drvdata(card);
+       struct tegra_asoc_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_rt5645_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_rt5645 *machine = snd_soc_card_get_drvdata(card);
+       struct tegra_asoc_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_rt5645_event_int_spk),
+       SND_SOC_DAPM_HP("Headphone Jack", tegra_rt5645_event_hp),
+       SND_SOC_DAPM_MIC("Mic Jack", tegra_rt5645_event_ext_mic),
+       SND_SOC_DAPM_MIC("Int Mic", tegra_rt5645_event_int_mic),
+};
+
+static const struct snd_soc_dapm_route ardbeg_audio_map[] = {
+       {"Headphone Jack", NULL, "HPOR"},
+       {"Headphone Jack", NULL, "HPOL"},
+       {"Int Spk", NULL, "SPOR"},
+       {"Int Spk", NULL, "SPOL"},
+       {"micbias2", NULL, "Mic Jack"},
+       {"IN1P", NULL, "micbias2"},
+       {"IN1N", NULL, "micbias2"},
+       {"micbias1", NULL, "Int Mic"},
+       {"IN2P", NULL, "micbias1"},
+       {"IN2N", NULL, "micbias1"},
+/*
+       {"DMIC L1", NULL, "Int Mic"},
+       {"DMIC L2", NULL, "Int Mic"},
+       {"DMIC R1", NULL, "Int Mic"},
+       {"DMIC R2", NULL, "Int Mic"},
+*/
+};
+
+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_rt5645_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_rt5645 *machine = snd_soc_card_get_drvdata(card);
+       struct tegra_asoc_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");
+               } else {
+                       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");
+               } else {
+                       machine->gpio_requested |= GPIO_EXT_MIC_EN;
+
+                       /* Disable ext mic; enable signal is active-low */
+                       gpio_direction_output(pdata->gpio_ext_mic_en, 1);
+               }
+       }
+
+       if (gpio_is_valid(pdata->gpio_hp_det)) {
+               tegra_rt5645_hp_jack_gpio.gpio = pdata->gpio_hp_det;
+               snd_soc_jack_new(codec, "Headphone Jack", SND_JACK_HEADPHONE,
+                               &tegra_rt5645_hp_jack);
+#ifndef CONFIG_SWITCH
+               snd_soc_jack_add_pins(&tegra_rt5645_hp_jack,
+                                       ARRAY_SIZE(tegra_rt5645_hp_jack_pins),
+                                       tegra_rt5645_hp_jack_pins);
+#else
+               snd_soc_jack_notifier_register(&tegra_rt5645_hp_jack,
+                                       &tegra_rt5645_jack_detect_nb);
+#endif
+               snd_soc_jack_add_gpios(&tegra_rt5645_hp_jack,
+                                       1,
+                                       &tegra_rt5645_hp_jack_gpio);
+               machine->gpio_requested |= GPIO_HP_DET;
+       }
+
+       ret = tegra_asoc_utils_register_ctls(&machine->util_data);
+       if (ret < 0)
+               return ret;
+
+       /* 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_rt5645_dai[NUM_DAI_LINKS] = {
+       [DAI_LINK_HIFI] = {
+               .name = "rt5645",
+               .stream_name = "rt5645 PCM",
+               .codec_name = "rt5645.0-001a",
+               .platform_name = "tegra-pcm-audio",
+               .cpu_dai_name = "tegra30-i2s.1",
+               .codec_dai_name = "rt5645-aif1",
+               .init = tegra_rt5645_init,
+               .ops = &tegra_rt5645_ops,
+       },
+
+       [DAI_LINK_SPDIF] = {
+               .name = "SPDIF",
+               .stream_name = "SPDIF PCM",
+               .codec_name = "spdif-dit.0",
+               .platform_name = "tegra30-spdif",
+               .cpu_dai_name = "tegra30-spdif",
+               .codec_dai_name = "dit-hifi",
+               .ops = &tegra_spdif_ops,
+       },
+
+       [DAI_LINK_BTSCO] = {
+               .name = "BT-SCO",
+               .stream_name = "BT SCO PCM",
+               .codec_name = "spdif-dit.1",
+               .platform_name = "tegra-pcm-audio",
+               .cpu_dai_name = "tegra30-i2s.3",
+               .codec_dai_name = "dit-hifi",
+               .ops = &tegra_rt5645_bt_sco_ops,
+       },
+};
+
+static int tegra_rt5645_resume_pre(struct snd_soc_card *card)
+{
+       int val;
+       struct snd_soc_jack_gpio *gpio = &tegra_rt5645_hp_jack_gpio;
+
+       if (gpio_is_valid(gpio->gpio)) {
+               val = gpio_get_value(gpio->gpio);
+               val = gpio->invert ? !val : val;
+               snd_soc_jack_report(gpio->jack, val, gpio->report);
+       }
+
+       return 0;
+}
+
+static int tegra_rt5645_set_bias_level(struct snd_soc_card *card,
+       struct snd_soc_dapm_context *dapm, enum snd_soc_bias_level level)
+{
+       struct tegra_rt5645 *machine = snd_soc_card_get_drvdata(card);
+
+       if (machine->bias_level == SND_SOC_BIAS_OFF &&
+               level != SND_SOC_BIAS_OFF && (!machine->clock_enabled)) {
+               machine->clock_enabled = 1;
+               tegra_asoc_utils_clk_enable(&machine->util_data);
+               machine->bias_level = level;
+       }
+
+       return 0;
+}
+
+static int tegra_rt5645_set_bias_level_post(struct snd_soc_card *card,
+       struct snd_soc_dapm_context *dapm, enum snd_soc_bias_level level)
+{
+       struct tegra_rt5645 *machine = snd_soc_card_get_drvdata(card);
+
+       if (machine->bias_level != SND_SOC_BIAS_OFF &&
+               level == SND_SOC_BIAS_OFF && machine->clock_enabled) {
+               machine->clock_enabled = 0;
+               tegra_asoc_utils_clk_disable(&machine->util_data);
+       }
+
+       machine->bias_level = level;
+
+       return 0 ;
+}
+
+static struct snd_soc_card snd_soc_tegra_rt5645 = {
+       .name = "tegra-rt5645",
+       .owner = THIS_MODULE,
+       .dai_link = tegra_rt5645_dai,
+       .num_links = ARRAY_SIZE(tegra_rt5645_dai),
+       .resume_pre = tegra_rt5645_resume_pre,
+       .set_bias_level = tegra_rt5645_set_bias_level,
+       .set_bias_level_post = tegra_rt5645_set_bias_level_post,
+       .controls = cardhu_controls,
+       .num_controls = ARRAY_SIZE(cardhu_controls),
+       .dapm_widgets = cardhu_dapm_widgets,
+       .num_dapm_widgets = ARRAY_SIZE(cardhu_dapm_widgets),
+       .dapm_routes = ardbeg_audio_map,
+       .num_dapm_routes = ARRAY_SIZE(ardbeg_audio_map),
+       .fully_routed = true,
+};
+
+static int tegra_rt5645_driver_probe(struct platform_device *pdev)
+{
+       struct snd_soc_card *card = &snd_soc_tegra_rt5645;
+       struct tegra_rt5645 *machine;
+       struct tegra_asoc_platform_data *pdata;
+       int ret;
+       int codec_id;
+
+       pdata = pdev->dev.platform_data;
+       if (!pdata) {
+               dev_err(&pdev->dev, "No platform data supplied\n");
+               return -EINVAL;
+       }
+       if (pdata->codec_name)
+               card->dai_link->codec_name = pdata->codec_name;
+
+       if (pdata->codec_dai_name)
+               card->dai_link->codec_dai_name = pdata->codec_dai_name;
+
+       machine = kzalloc(sizeof(struct tegra_rt5645), GFP_KERNEL);
+       if (!machine) {
+               dev_err(&pdev->dev, "Can't allocate tegra_rt5645 struct\n");
+               return -ENOMEM;
+       }
+
+       if (gpio_is_valid(pdata->gpio_ldo1_en)) {
+               ret = gpio_request(pdata->gpio_ldo1_en, "rt5645");
+               if (ret)
+                       dev_err(&pdev->dev, "Fail gpio_request AUDIO_LDO1\n");
+
+               ret = gpio_direction_output(pdata->gpio_ldo1_en, 1);
+               if (ret)
+                       dev_err(&pdev->dev, "Fail gpio_direction AUDIO_LDO1\n");
+
+               msleep(200);
+       }
+
+       if (gpio_is_valid(pdata->gpio_codec1)) {
+               ret = gpio_request(pdata->gpio_codec1, "rt5645");
+               if (ret)
+                       dev_err(&pdev->dev, "Fail gpio_request GPIO_CODEC1\n");
+
+               ret = gpio_direction_output(pdata->gpio_codec1, 1);
+               if (ret)
+                       dev_err(&pdev->dev, "Fail gpio_direction GPIO_CODEC1\n");
+
+               msleep(200);
+       }
+
+       if (gpio_is_valid(pdata->gpio_codec2)) {
+               ret = gpio_request(pdata->gpio_codec2, "rt5645");
+               if (ret)
+                       dev_err(&pdev->dev, "Fail gpio_request GPIO_CODEC2\n");
+
+               ret = gpio_direction_output(pdata->gpio_codec2, 1);
+               if (ret)
+                       dev_err(&pdev->dev, "Fail gpio_direction GPIO_CODEC2\n");
+
+               msleep(200);
+       }
+
+       if (gpio_is_valid(pdata->gpio_codec3)) {
+               ret = gpio_request(pdata->gpio_codec3, "rt5645");
+               if (ret)
+                       dev_err(&pdev->dev, "Fail gpio_request GPIO_CODEC3\n");
+
+               ret = gpio_direction_output(pdata->gpio_codec3, 1);
+               if (ret)
+                       dev_err(&pdev->dev, "Fail gpio_direction GPIO_CODEC3\n");
+
+               msleep(200);
+       }
+
+       machine->pdata = pdata;
+
+       ret = tegra_asoc_utils_init(&machine->util_data, &pdev->dev, card);
+       if (ret)
+               goto err_free_machine;
+
+       machine->bias_level = SND_SOC_BIAS_STANDBY;
+       machine->clock_enabled = 1;
+
+       if (!gpio_is_valid(pdata->gpio_ldo1_en)) {
+               machine->cdc_en = regulator_get(&pdev->dev, "ldo1_en");
+               if (IS_ERR(machine->cdc_en)) {
+                       dev_err(&pdev->dev, "ldo1_en regulator not found %ld\n",
+                                       PTR_ERR(machine->cdc_en));
+                       machine->cdc_en = 0;
+               } else {
+                       regulator_enable(machine->cdc_en);
+               }
+       }
+
+       machine->spk_reg = regulator_get(&pdev->dev, "vdd_spk");
+       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 = tegra_asoc_switch_register(&tegra_rt5645_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);
+
+#ifndef CONFIG_ARCH_TEGRA_2x_SOC
+       codec_id = pdata->i2s_param[HIFI_CODEC].audio_port_id;
+       tegra_rt5645_dai[DAI_LINK_HIFI].cpu_dai_name =
+       tegra_rt5645_i2s_dai_name[codec_id];
+       tegra_rt5645_dai[DAI_LINK_HIFI].platform_name =
+       tegra_rt5645_i2s_dai_name[codec_id];
+
+       codec_id = pdata->i2s_param[BT_SCO].audio_port_id;
+       tegra_rt5645_dai[DAI_LINK_BTSCO].cpu_dai_name =
+       tegra_rt5645_i2s_dai_name[codec_id];
+       tegra_rt5645_dai[DAI_LINK_BTSCO].platform_name =
+       tegra_rt5645_i2s_dai_name[codec_id];
+#endif
+
+       card->dapm.idle_bias_off = 1;
+       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, "sound card not instantiated (%d)\n",
+                       ret);
+               goto err_unregister_card;
+       }
+
+#ifndef CONFIG_ARCH_TEGRA_2x_SOC
+       ret = tegra_asoc_utils_set_parent(&machine->util_data,
+                               pdata->i2s_param[HIFI_CODEC].is_i2s_master);
+       if (ret) {
+               dev_err(&pdev->dev, "tegra_asoc_utils_set_parent failed (%d)\n",
+                       ret);
+               goto err_unregister_card;
+       }
+#endif
+
+       return 0;
+
+err_unregister_card:
+       snd_soc_unregister_card(card);
+err_unregister_switch:
+#ifdef CONFIG_SWITCH
+       tegra_asoc_switch_unregister(&tegra_rt5645_headset_switch);
+err_fini_utils:
+#endif
+       tegra_asoc_utils_fini(&machine->util_data);
+err_free_machine:
+       kfree(machine);
+       return ret;
+}
+
+static int tegra_rt5645_driver_remove(struct platform_device *pdev)
+{
+       struct snd_soc_card *card = platform_get_drvdata(pdev);
+       struct tegra_rt5645 *machine = snd_soc_card_get_drvdata(card);
+       struct tegra_asoc_platform_data *pdata = machine->pdata;
+
+       if (machine->gpio_requested & GPIO_HP_DET)
+               snd_soc_jack_free_gpios(&tegra_rt5645_hp_jack,
+                                       1,
+                                       &tegra_rt5645_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);
+
+       if (machine->cdc_en) {
+               regulator_disable(machine->cdc_en);
+               regulator_put(machine->cdc_en);
+       }
+
+       if (gpio_is_valid(pdata->gpio_ldo1_en)) {
+               gpio_set_value(pdata->gpio_ldo1_en, 0);
+               gpio_free(pdata->gpio_ldo1_en);
+       }
+
+       snd_soc_unregister_card(card);
+
+       tegra_asoc_utils_fini(&machine->util_data);
+
+#ifdef CONFIG_SWITCH
+       tegra_asoc_switch_unregister(&tegra_rt5645_headset_switch);
+#endif
+       kfree(machine);
+
+       return 0;
+}
+
+static struct platform_driver tegra_rt5645_driver = {
+       .driver = {
+               .name = DRV_NAME,
+               .owner = THIS_MODULE,
+               .pm = &snd_soc_pm_ops,
+       },
+       .probe = tegra_rt5645_driver_probe,
+       .remove = tegra_rt5645_driver_remove,
+};
+
+static int __init tegra_rt5645_modinit(void)
+{
+       return platform_driver_register(&tegra_rt5645_driver);
+}
+module_init(tegra_rt5645_modinit);
+
+static void __exit tegra_rt5645_modexit(void)
+{
+       platform_driver_unregister(&tegra_rt5645_driver);
+}
+module_exit(tegra_rt5645_modexit);
+
+MODULE_AUTHOR("Nikesh Oswal <noswal@nvidia.com>");
+MODULE_DESCRIPTION("Tegra+rt5645 machine ASoC driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);