ak4396: add ak4396 dac support
author邱建斌 <qjb@rock-chips.com>
Tue, 20 Aug 2013 03:26:22 +0000 (11:26 +0800)
committer邱建斌 <qjb@rock-chips.com>
Tue, 20 Aug 2013 03:26:22 +0000 (11:26 +0800)
sound/soc/codecs/Kconfig
sound/soc/codecs/Makefile
sound/soc/codecs/ak4396.c [new file with mode: 0755]
sound/soc/rk29/Kconfig
sound/soc/rk29/Makefile
sound/soc/rk29/rk29_ak4396.c [new file with mode: 0755]

index fc59e4e4dabebe44377f81dba29e13ad64d945c6..fdc4fa94efdf9359ce561163b4399bd34e1e1bbc 100755 (executable)
@@ -80,6 +80,7 @@ config SND_SOC_ALL_CODECS
        select SND_SOC_WM8900 if I2C
        select SND_SOC_RT5621 if I2C
        select SND_SOC_RT5631 if I2C
+       select SND_SOC_AK4396 if SPI_MASTER
        select SND_SOC_RT5631_PHONE if I2C
        select SND_SOC_RT5625 if I2C
        select SND_SOC_RT5640 if I2C
@@ -347,6 +348,9 @@ config SND_SOC_RT5639
        
 config SND_SOC_RT5616
        tristate
+
+config SND_SOC_AK4396
+       tristate
        
 config SND_SOC_RT5631
        tristate
index 964bd7baf7f5baeb21817235f9c172f4ee925e43..0aaf90b2c3d311430f79c76a770ca5127764ef32 100755 (executable)
@@ -65,6 +65,7 @@ snd-soc-wm8900-objs := wm8900.o
 snd-soc-rt5621-objs := rt5621.o
 snd-soc-rt5623-objs := rt5623.o
 snd-soc-rt5631-objs := rt5631.o
+snd-soc-ak4396-objs := ak4396.o
 snd-soc-rt5616-objs := rt5616.o
 snd-soc-rt5631-phone-objs := rt5631_phone.o
 snd-soc-rt5625-objs := rt5625.o
@@ -180,6 +181,7 @@ obj-$(CONFIG_SND_SOC_WM8804)        += snd-soc-wm8804.o
 obj-$(CONFIG_SND_SOC_WM8900)   += snd-soc-wm8900.o
 obj-$(CONFIG_SND_SOC_RT5621)   += snd-soc-rt5621.o
 obj-$(CONFIG_SND_SOC_RT5623)   += snd-soc-rt5623.o
+obj-$(CONFIG_SND_SOC_AK4396)   += snd-soc-ak4396.o
 obj-$(CONFIG_SND_SOC_RT5631)   += snd-soc-rt5631.o
 obj-$(CONFIG_SND_SOC_RT5616)   += snd-soc-rt5616.o
 obj-$(CONFIG_SND_SOC_RT5631_PHONE)     += snd-soc-rt5631-phone.o
diff --git a/sound/soc/codecs/ak4396.c b/sound/soc/codecs/ak4396.c
new file mode 100755 (executable)
index 0000000..da34d20
--- /dev/null
@@ -0,0 +1,473 @@
+/*
+ * AK4396 ALSA SoC (ASoC) driver
+ *
+ * Copyright (c) 2009 Daniel Mack <daniel@caiaq.de>
+ *
+ *  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/module.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <linux/spi/spi.h>
+#include <sound/asoundef.h>
+#include <linux/delay.h>
+#include <mach/iomux.h>
+
+/* AK4396 registers addresses */
+#define AK4396_REG_CONTROL1            0x00
+#define AK4396_REG_CONTROL2            0x01
+#define AK4396_REG_CONTROL3            0x02
+#define AK4396_REG_LCH_ATT             0x03
+#define AK4396_REG_RCH_ATT    0x04
+#define AK4396_NUM_REGS                                5
+
+#define AK4396_REG_MASK                                0x1f
+#define AK4396_WRITE                                   0x20  /*C1 C0 R/W A4 A3 A2 A1 A0 8bit==0010 0000 */
+
+/* Bit masks for AK4396 registers */
+#define AK4396_CONTROL1_RSTN           (1 << 0)
+#define AK4396_CONTROL1_DIF0           (1 << 1)
+#define AK4396_CONTROL1_DIF1           (1 << 2)
+#define AK4396_CONTROL1_DIF2           (1 << 3)
+
+#define DRV_NAME "AK4396"
+
+struct ak4396_private {
+       enum snd_soc_control_type control_type;
+       void *control_data;
+       unsigned int sysclk;
+};
+
+#if 0
+
+static const u16 ak4396_reg[AK4396_NUM_REGS] = {
+       0x87, 0x02, 0x00, 0xff, 0xff
+}; //CONFIG_LINF
+#else
+static const u16 ak4396_reg[AK4396_NUM_REGS] = {
+       0x05, 0x02, 0x00, 0xff, 0xff
+}; //CONFIG_LINF
+
+#endif
+
+static void on_off_ext_amp(int i)
+{
+
+    #ifdef SPK_CTL
+    //gpio_direction_output(SPK_CTL, GPIO_LOW);
+    gpio_set_value(SPK_CTL, i);
+    printk("*** %s() SPEAKER set as %d\n", __FUNCTION__, i);
+    #endif
+    #ifdef EAR_CON_PIN
+    //gpio_direction_output(EAR_CON_PIN, GPIO_LOW);
+    gpio_set_value(EAR_CON_PIN, i);
+    printk("*** %s() HEADPHONE set as %d\n", __FUNCTION__, i);
+    mdelay(50);
+    #endif
+}
+
+void codec_set_spk(bool on)
+{
+       on_off_ext_amp(on);
+}
+
+static int ak4396_fill_cache(struct snd_soc_codec *codec)
+{
+       int i;
+       u8 *reg_cache = codec->reg_cache;
+       struct spi_device *spi = codec->control_data;
+
+       for (i = 0; i < codec->driver->reg_cache_size; i++) {
+               int ret = spi_w8r8(spi, i);
+               if (ret < 0) {
+                       dev_err(&spi->dev, "SPI write failure\n");
+                       return ret;
+               }
+
+               reg_cache[i] = ret;
+       }
+
+       return 0;
+}
+
+/* read the reg_cache */
+static unsigned int ak4396_read_reg_cache(struct snd_soc_codec *codec,
+                                         unsigned int reg)
+{
+       u8 *reg_cache = codec->reg_cache;
+
+       if (reg >= codec->driver->reg_cache_size)
+               return -EINVAL;
+       
+//     printk("read reg_cache[%x]====%d\n", reg, reg_cache[reg]);
+       return reg_cache[reg];
+}
+
+static int ak4396_spi_write(struct snd_soc_codec *codec, unsigned int reg,
+                           unsigned int value)
+{
+       u8 *cache = codec->reg_cache;
+       struct spi_device *spi = codec->control_data;
+
+       if (reg >= codec->driver->reg_cache_size)
+               return -EINVAL;
+
+       /* only write to the hardware if value has changed */
+       //if (cache[reg] != value)
+       //{
+               u8 tmp[2] = { (reg & AK4396_REG_MASK) | AK4396_WRITE, value};
+               //printk("tmp[0]===%d\n", tmp[0]);
+               //printk("tmp[1]===%d\n", tmp[1]);
+               if (spi_write(spi, tmp, sizeof(tmp))) {
+                       dev_err(&spi->dev, "SPI write failed\n");
+                       return -EIO;
+               }
+       
+               cache[reg] = value;
+       //}     
+       return 0;
+}
+
+/* write the register space */
+static ak4396_write(struct snd_soc_codec *codec)
+{
+       int ret, val, i;
+       val = 0;
+       int addr[5] = {0x00, 0x01, 0x02, 0x03, 0x04};
+       int dat[5] = {0x87, 0x02, 0x00, 0xff, 0xff};
+//     while(1){
+       val |= AK4396_CONTROL1_RSTN;
+       ak4396_spi_write(codec, AK4396_REG_CONTROL1, val);
+       
+       for(i=0; i<5; i++)
+        {
+               ret = ak4396_spi_write(codec, addr[i], dat[i]); 
+               if (ret < 0)
+                       printk("ak4396_spi_write failed!\n");
+       
+                printk("write %d time(s)\n", i);       
+       }
+//     }
+}
+
+/*
+ * Note that this should be called from init rather than from hw_params.
+ */
+static int ak4396_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+               int clk_id, unsigned int freq, int dir)
+{
+       struct snd_soc_codec *codec = codec_dai->codec;
+       struct ak4396_private *ak4396 = snd_soc_codec_get_drvdata(codec);
+
+        printk("Enter::%s----%d\n",__FUNCTION__,__LINE__);
+       printk("freq======%d\n", freq); 
+       ak4396->sysclk = freq;
+
+       return 0;
+}
+
+static int ak4396_set_dai_fmt(struct snd_soc_dai *codec_dai,
+                             unsigned int format)
+{
+       struct snd_soc_codec *codec = codec_dai->codec;
+       int val = 0;
+       
+       printk("%s----%d, format[%02x]\n",__FUNCTION__,__LINE__,format);
+       val = ak4396_read_reg_cache(codec, AK4396_REG_CONTROL1);
+       if (val < 0)
+               return val;
+       val &= ~(AK4396_CONTROL1_DIF0 | AK4396_CONTROL1_DIF1 | AK4396_CONTROL1_DIF2);
+//     printk("ak4396 val=%d\n", val);
+
+       /* set DAI format */
+       switch (format & SND_SOC_DAIFMT_FORMAT_MASK) {
+       case SND_SOC_DAIFMT_RIGHT_J:
+               val |= AK4396_CONTROL1_DIF2 ;
+               printk("SND_SOC_DAIFMT_RIGHT_J: \n");
+               break;
+       case SND_SOC_DAIFMT_LEFT_J:
+               val |= AK4396_CONTROL1_DIF1 ;
+               printk("SND_SOC_DAIFMT_LEFT_J: \n");
+               break;
+       case SND_SOC_DAIFMT_I2S:
+               val |= AK4396_CONTROL1_DIF0 | AK4396_CONTROL1_DIF1 ;
+               //val |= 0x87;
+       
+               printk("SND_SOC_DAIFMT_I2S is ok!\n");
+               break;
+       default:
+               dev_err(codec->dev, "invalid dai format\n");
+               return -EINVAL;
+       }
+
+       /* This device can only be slave */
+       if ((format & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS)
+       {
+               printk("%s failed!----%d\n",__FUNCTION__,__LINE__);
+               return -EINVAL;
+       }
+       
+       //val |= AK4396_CONTROL1_RSTN;
+       printk("AK4396 CONTROL1 val ==== %d\n", val);
+       ak4396_spi_write(codec, AK4396_REG_CONTROL1, val);
+
+       return 0;
+}
+
+static int ak4396_hw_params(struct snd_pcm_substream *substream,
+                           struct snd_pcm_hw_params *params,
+                           struct snd_soc_dai *dai)
+{
+       struct snd_soc_pcm_runtime *rtd = substream->private_data;
+       struct snd_soc_codec *codec = rtd->codec;
+       int val = 0;
+
+       switch (params_rate(params)) {
+
+        case 176400:
+                val |= IEC958_AES3_CON_FS_176400;
+                printk("params_rate::=176400!\n");
+                break;
+
+        case 192000:
+                val |= IEC958_AES3_CON_FS_192000;
+                printk("params_rate::=192000!\n");
+                break;
+
+        case 88200:
+                val |= IEC958_AES3_CON_FS_88200;
+                printk("params_rate::=88200!\n");
+                break;
+
+
+        case 96000:
+                val |= IEC958_AES3_CON_FS_96000;
+                printk("params_rate::=96000!\n");
+                break;
+
+       case 44100:
+               val |= IEC958_AES3_CON_FS_44100;
+               printk("params_rate::=44100!\n");
+               break;
+       case 48000:
+               val |= IEC958_AES3_CON_FS_48000;
+               break;
+       case 32000:
+               val |= IEC958_AES3_CON_FS_32000;
+               break;
+       default:
+               dev_err(codec->dev, "unsupported sampling rate\n");
+               return -EINVAL;
+       }
+       val = 0;
+       val = ak4396_read_reg_cache(codec, AK4396_REG_CONTROL1);
+       //reset RSTN bit;
+       val &= 0xFE;
+       printk("val ==== %d\n", val);
+        ak4396_spi_write(codec, AK4396_REG_CONTROL1, val);
+        val |= 0x01;
+        printk("val ==== %d\n", val);
+        ak4396_spi_write(codec, AK4396_REG_CONTROL1, val);
+       
+       //printk("val === %d\n", val);
+       //ak4396_spi_write(codec, AK4396_REG_CONTROL2, val);
+       return 0;
+}
+
+static struct snd_soc_dai_ops ak4396_dai_ops = {
+       .hw_params = ak4396_hw_params,
+       .set_fmt = ak4396_set_dai_fmt,
+       .set_sysclk = ak4396_set_dai_sysclk,
+};
+
+static struct snd_soc_dai_driver ak4396_dai = {
+       .name = "AK4396 HiFi",
+       .playback = {
+               .stream_name = "Playback",
+               .channels_min = 2,
+               .channels_max = 2,
+               .rates = SNDRV_PCM_RATE_8000_192000,
+               .formats = SNDRV_PCM_FMTBIT_S16_LE  |
+                          SNDRV_PCM_FMTBIT_S24_3LE |
+                          SNDRV_PCM_FMTBIT_S24_LE  |
+                          SNDRV_PCM_FMTBIT_S32_LE
+       },
+       .ops = &ak4396_dai_ops,
+};
+
+struct snd_soc_codec *codec_temp; 
+static int ak4396_probe(struct snd_soc_codec *codec)
+{
+       struct ak4396_private *ak4396 = snd_soc_codec_get_drvdata(codec);
+       int ret;
+       printk("ak4396_probe begin!\n");
+       codec->control_data = ak4396->control_data;
+       codec_temp = codec;
+#if 0
+       /* read all regs and fill the cache */
+       ret = ak4396_fill_cache(codec);
+       if (ret < 0) {
+               dev_err(codec->dev, "failed to fill register cache\n");
+               return ret;
+       }
+#endif
+       /* write to ak4396_reg */
+//     ak4396_write(codec);
+       
+       printk("ak4396_probe is ok!\n");
+       dev_info(codec->dev, "SPI device initialized\n");
+       return 0;
+}
+
+static int ak4396_remove(struct snd_soc_codec *codec)
+{
+       int val, ret;
+
+       val = ak4396_read_reg_cache(codec, AK4396_REG_CONTROL1);
+       if (val < 0)
+               return val;
+
+       /* set non-reset bits */
+       val &= ~AK4396_CONTROL1_RSTN;
+       ret = ak4396_spi_write(codec, AK4396_REG_CONTROL1, val);
+
+       return ret;
+}
+
+static int ak4396_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{      
+       return 0;
+}
+
+static int ak4396_resume(struct snd_soc_codec *codec)
+{
+       //ak4396_write(codec);
+       return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_device_ak4396 = {
+       .probe =        ak4396_probe,
+       .remove =       ak4396_remove,
+       .suspend =      ak4396_suspend,
+       .resume =       ak4396_resume,
+       .reg_cache_size = AK4396_NUM_REGS,
+       .reg_word_size = sizeof(u16),
+       .reg_cache_default = ak4396_reg,
+};
+
+static struct class *cls = NULL;
+
+static ssize_t store_ak4396_reg(struct class *dev,
+               struct class_attribute *attr, const char *buf, size_t count)
+{
+       int reg, value, ret;
+//     char buf[10] = "123 11";
+       char *start = buf;
+
+       printk("%s, the first dat  is reg, the second dat is  data, data type is dex\n", __FUNCTION__);
+       while (*start == ' ')
+               start++;
+       reg = simple_strtoull(start, &start, 16);
+
+       while (*start == ' ')
+               start++;
+       value = simple_strtoull(start, &start, 16);
+
+       
+       ret = ak4396_spi_write(codec_temp, reg, value); 
+       if (ret < 0)
+                       printk("ak4396_spi_write failed!\n");
+
+       printk("reg = %d, value =%d\n", reg, value);
+//     return 0;               
+}
+static struct class_attribute attr[] = {
+       __ATTR(write_reg, 0644, NULL, store_ak4396_reg),
+       __ATTR_NULL,
+};
+static int ak4396_spi_probe(struct spi_device *spi)
+{
+       struct ak4396_private *ak4396;
+       int ret;
+       printk("ak4396_spi_probe begin!\n");
+
+#if 0 //defined(CONFIG_ARCH_RK3188)
+       iomux_set(SPI1_CS0);
+       iomux_set(SPI1_CLK);
+       iomux_set(SPI1_TXD);
+       printk("iomux_set is OK!!!\n");
+#endif
+
+       spi->bits_per_word = 8;
+       spi->mode = SPI_MODE_0;
+       ret = spi_setup(spi);
+       if (ret < 0)
+               return ret;
+
+       ak4396 = kzalloc(sizeof(struct ak4396_private), GFP_KERNEL);
+       if (ak4396 == NULL)
+               return -ENOMEM;
+
+       ak4396->control_data = spi;
+       ak4396->control_type = SND_SOC_SPI;
+       spi_set_drvdata(spi, ak4396);
+
+       cls = class_create(THIS_MODULE, DRV_NAME);
+       if (IS_ERR(cls))
+       {
+               printk("class_create failed!\n");               
+       }
+       ret = class_create_file(cls, attr);
+       if (ret < 0)
+       {
+               printk("class_create_file failed!\n");          
+       }
+
+       ret = snd_soc_register_codec(&spi->dev,
+                       &soc_codec_device_ak4396, &ak4396_dai, 1);
+       if (ret < 0)
+               kfree(ak4396);
+
+       printk("ak4396_spi_probe successful!\n");
+       return ret;
+}
+
+static int __devexit ak4396_spi_remove(struct spi_device *spi)
+{
+       snd_soc_unregister_codec(&spi->dev);
+       kfree(spi_get_drvdata(spi));
+       return 0;
+}
+
+static struct spi_driver ak4396_spi_driver = {
+       .driver  = {
+               .name   = DRV_NAME,
+               .owner  = THIS_MODULE,
+       },
+       .probe  = ak4396_spi_probe,
+       .remove = __devexit_p(ak4396_spi_remove),
+};
+
+static int __init ak4396_init(void)
+{
+       printk("%s\n", __FUNCTION__);
+       return spi_register_driver(&ak4396_spi_driver);
+}
+module_init(ak4396_init);
+
+static void __exit ak4396_exit(void)
+{
+       spi_unregister_driver(&ak4396_spi_driver);
+}
+module_exit(ak4396_exit);
+
+MODULE_AUTHOR("Daniel Mack <daniel@caiaq.de>");
+MODULE_DESCRIPTION("Asahi Kasei AK4396 ALSA SoC driver");
+MODULE_LICENSE("GPL");
+
index eb3d83d94cbcffbb9a03e19890147939bbc532c7..cb95c2319a62feeea93a390d841d91f4b37a2824 100755 (executable)
@@ -87,6 +87,15 @@ choice
 endchoice
 endif
 
+config SND_RK29_SOC_AK4396
+       tristate "SoC I2S Audio support for rockchip - AK4396"
+       depends on SND_RK29_SOC
+       select SND_RK29_SOC_I2S
+       select SND_SOC_AK4396
+       help
+         Say Y if you want to add support for SoC audio on rockchip
+         with the AK4396.
+
 config SND_RK29_SOC_ES8323
        tristate "SoC I2S Audio support for rockchip - ES8323"
        depends on SND_RK29_SOC
@@ -136,6 +145,7 @@ config SND_RK29_SOC_RT5623
        help
          Say Y if you want to add support for SoC audio on rockchip
          with the rt5623.
+
 config SND_RK29_SOC_RT5631
        tristate "SoC I2S Audio support for rockchip - RT5631"
        depends on SND_RK29_SOC
index 7bbd6adf6b1b5c4914015104e7268bee53651bb7..97834810f6ab41367907471a7e8f7d72bda59767 100755 (executable)
@@ -15,6 +15,7 @@ obj-$(CONFIG_SND_RK_SOC_SPDIF) += snd-soc-rockchip-spdif.o
 snd-soc-wm8900-objs := rk29_wm8900.o
 snd-soc-rt5621-objs := rk29_rt5621.o
 snd-soc-rt5631-objs := rk29_rt5631.o
+snd-soc-ak4396-objs := rk29_ak4396.o
 snd-soc-rt5616-objs := rk29_rt5616.o
 snd-soc-rt5631-phone-objs := rk29_rt5631_phone.o
 snd-soc-rt5625-objs := rk29_rt5625.o
@@ -41,6 +42,7 @@ obj-$(CONFIG_SND_RK29_SOC_WM8988) += snd-soc-wm8988.o
 obj-$(CONFIG_SND_RK29_SOC_WM8900) += snd-soc-wm8900.o
 obj-$(CONFIG_SND_RK29_SOC_RT5621) += snd-soc-rt5621.o
 obj-$(CONFIG_SND_RK29_SOC_RT5631) += snd-soc-rt5631.o
+obj-$(CONFIG_SND_RK29_SOC_AK4396) += snd-soc-ak4396.o
 obj-$(CONFIG_SND_RK29_SOC_RT5631_PHONE) += snd-soc-rt5631-phone.o
 obj-$(CONFIG_SND_RK29_SOC_RT5625) += snd-soc-rt5625.o
 obj-$(CONFIG_SND_RK29_SOC_RT5640) += snd-soc-rt5640.o
diff --git a/sound/soc/rk29/rk29_ak4396.c b/sound/soc/rk29/rk29_ak4396.c
new file mode 100755 (executable)
index 0000000..c5a3240
--- /dev/null
@@ -0,0 +1,177 @@
+/*
+ * rk29_ak4396.c  --  SoC audio for rockchip
+ *
+ * Driver for rockchip ak4396 audio
+ *
+ *  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/module.h>
+#include <linux/device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <asm/io.h>
+#include <mach/hardware.h>
+#include "rk29_pcm.h"
+#include "rk29_i2s.h"
+
+#include <mach/gpio.h>
+
+#if 1
+#define        DBG(x...)       printk(KERN_INFO x)
+#else
+#define        DBG(x...)
+#endif
+
+static int rk29_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;
+    unsigned int pll_out = 0; 
+    int ret=-1;
+       
+    DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__);    
+
+    /* set codec DAI configuration */
+    #if defined (CONFIG_SND_RK29_CODEC_SOC_SLAVE) 
+    ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_RIGHT_J |
+               SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); 
+    if (ret < 0)        return ret; 
+    /* set cpu DAI configuration */
+    ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_RIGHT_J |
+               SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
+    if (ret < 0)        return ret;            
+    #endif     
+
+    switch(params_rate(params)) {
+    case 8000:
+    case 16000:
+    case 24000:
+    case 32000:
+    case 48000:
+        pll_out = 12288000;
+        break;
+    case 11025:
+    case 22050:
+    case 44100:
+        pll_out = 11289600;
+        break;
+       case 88200:
+       case 176400:
+               pll_out = 11289600*2;
+               break;
+       case 96000:
+       case 192000:
+               pll_out = 12288000*2;
+               break;
+    default:
+        DBG("Enter:%s, %d, Error rate=%d\n",__FUNCTION__,__LINE__,params_rate(params));
+        return -EINVAL;
+    }
+
+    DBG("Enter:%s, %d, rate=%d\n",__FUNCTION__,__LINE__,params_rate(params));
+
+       #if defined (CONFIG_SND_RK29_CODEC_SOC_SLAVE)
+       snd_soc_dai_set_sysclk(cpu_dai, 0, pll_out, 0);
+       snd_soc_dai_set_clkdiv(cpu_dai, ROCKCHIP_DIV_BCLK, (2 * 32 )-1); //bclk = 2 * 32 * lrck
+
+       switch(params_rate(params)){
+       case 192000:
+       case 176400:
+               snd_soc_dai_set_clkdiv(cpu_dai, ROCKCHIP_DIV_MCLK,1);
+        DBG("Enter:%s, %d, MCLK=%d BCLK=%d LRCK=%d\n",
+               __FUNCTION__,__LINE__,pll_out,pll_out/2,params_rate(params));
+               break;
+    default :
+        snd_soc_dai_set_clkdiv(cpu_dai, ROCKCHIP_DIV_MCLK, 3);
+        DBG("default:%s, %d, MCLK=%d BCLK=%d LRCK=%d\n",
+               __FUNCTION__,__LINE__,pll_out,pll_out/4,params_rate(params));                   
+               break;
+    }
+       snd_soc_dai_set_sysclk(codec_dai,0,pll_out,SND_SOC_CLOCK_IN);
+       #endif
+       return ret;
+}
+
+/*
+ * Logic for a ak4396 as connected on a rockchip board.
+ */
+static int rk29_ak4396_init(struct snd_soc_pcm_runtime *rtd)
+{
+    DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__);
+
+    return 0;
+}
+
+static struct snd_soc_ops rk29_ops = {
+         .hw_params = rk29_hw_params,
+};
+
+static struct snd_soc_dai_link rk29_dai = {
+       .name = "AK4396",
+       .stream_name = "AK4396 PCM",
+       .codec_name = "spi1.0",
+       .platform_name = "rockchip-audio",
+#if defined(CONFIG_SND_RK29_SOC_I2S_8CH)       
+       .cpu_dai_name = "rk29_i2s.0",
+#elif defined(CONFIG_SND_RK29_SOC_I2S_2CH)
+       .cpu_dai_name = "rk29_i2s.1",
+#else
+       .cpu_dai_name = "rk29_i2s.2",
+#endif
+       .codec_dai_name = "AK4396 HiFi",
+       .init = rk29_ak4396_init,
+       .ops = &rk29_ops,
+};
+
+static struct snd_soc_card snd_soc_card_rk29 = {
+       .name = "RK29_AK4396",
+       .dai_link = &rk29_dai,
+       .num_links = 1,
+};
+
+static struct platform_device *rk29_snd_device;
+
+static int __init audio_card_init(void)
+{
+    int ret =0;        
+
+    rk29_snd_device = platform_device_alloc("soc-audio", -1);
+    if (!rk29_snd_device) {
+           printk("platform device allocation failed\n");
+        ret = -ENOMEM;
+        return ret;
+       }
+
+       platform_set_drvdata(rk29_snd_device, &snd_soc_card_rk29);
+
+       ret = platform_device_add(rk29_snd_device);
+       if (ret) {
+           printk("platform device add failed\n");
+           platform_device_put(rk29_snd_device);
+        return ret;
+       }
+               
+       return ret;
+}
+
+static void __exit audio_card_exit(void)
+{
+    platform_device_unregister(rk29_snd_device);;      
+}
+
+module_init(audio_card_init);
+module_exit(audio_card_exit);
+/* Module information */
+MODULE_AUTHOR("rockchip");
+MODULE_DESCRIPTION("ROCKCHIP i2s ASoC Interface");
+MODULE_LICENSE("GPL");