rk2928: add audio codec.
authorZheng Yang <zhengyang@rock-chips.com>
Mon, 23 Jul 2012 10:14:13 +0000 (18:14 +0800)
committerZheng Yang <zhengyang@rock-chips.com>
Mon, 23 Jul 2012 10:14:13 +0000 (18:14 +0800)
arch/arm/mach-rk2928/devices.c
sound/soc/codecs/Kconfig [changed mode: 0644->0755]
sound/soc/codecs/Makefile [changed mode: 0644->0755]
sound/soc/codecs/rk2928_codec.c [new file with mode: 0755]
sound/soc/codecs/rk2928_codec.h [new file with mode: 0755]
sound/soc/rk29/Kconfig
sound/soc/rk29/Makefile [changed mode: 0644->0755]
sound/soc/rk29/rk2928-card.c [new file with mode: 0755]

index 4d9f398261387b40507dc5651274ba7ef3ae185a..5c670f4330df45878efdcb72f7eb3400f1c15694 100755 (executable)
@@ -679,6 +679,24 @@ static void __init rk2928_init_sdmmc(void)
        platform_device_register(&device_sdmmc1);
 #endif
 }
+
+#ifdef CONFIG_SND_SOC_RK2928
+static struct resource resources_acodec[] = {
+       {
+               .start  = RK2928_ACODEC_PHYS,
+               .end    = RK2928_ACODEC_PHYS + RK2928_ACODEC_SIZE - 1,
+               .flags  = IORESOURCE_MEM,
+       },
+};
+
+static struct platform_device device_acodec = {
+       .name   = "rk2928-codec",
+       .id             = -1,
+       .num_resources  = ARRAY_SIZE(resources_acodec),
+       .resource       = resources_acodec,
+};
+#endif
+
 static int __init rk2928_init_devices(void)
 {
        rk2928_init_dma();
@@ -699,6 +717,9 @@ static int __init rk2928_init_devices(void)
        rk_serial_debug_init(DEBUG_UART_BASE, IRQ_DEBUG_UART, IRQ_UART_SIGNAL, -1);
 #endif
        rk2928_init_i2s();
+#ifdef CONFIG_SND_SOC_RK2928
+       platform_device_register(&device_acodec);
+#endif
        return 0;
 }
 arch_initcall(rk2928_init_devices);
old mode 100644 (file)
new mode 100755 (executable)
index ab6dbb5..7a3904d
@@ -418,3 +418,7 @@ config SND_SOC_WM2000
 
 config SND_SOC_WM9090
        tristate
+
+config SND_SOC_RK2928
+       tristate
+       depends on ARCH_RK2928
\ No newline at end of file
old mode 100644 (file)
new mode 100755 (executable)
index ea82f0d..2e6d428
@@ -89,7 +89,7 @@ snd-soc-wm-hubs-objs := wm_hubs.o
 snd-soc-rk1000-objs := rk1000_codec.o
 snd-soc-jz4740-codec-objs := jz4740.o
 snd-soc-rk610-objs := rk610_codec.o
-
+snd-soc-rk2928-objs := rk2928_codec.o
 # Amp
 snd-soc-lm4857-objs := lm4857.o
 snd-soc-max9877-objs := max9877.o
@@ -188,6 +188,7 @@ obj-$(CONFIG_SND_SOC_WM9713)        += snd-soc-wm9713.o
 obj-$(CONFIG_SND_SOC_WM_HUBS)  += snd-soc-wm-hubs.o
 obj-$(CONFIG_SND_SOC_RK1000)   += snd-soc-rk1000.o
 obj-$(CONFIG_SND_SOC_RK610)            += snd-soc-rk610.o
+obj-$(CONFIG_SND_SOC_RK2928)   += snd-soc-rk2928.o
 # Amp
 obj-$(CONFIG_SND_SOC_LM4857)   += snd-soc-lm4857.o
 obj-$(CONFIG_SND_SOC_MAX9877)  += snd-soc-max9877.o
diff --git a/sound/soc/codecs/rk2928_codec.c b/sound/soc/codecs/rk2928_codec.c
new file mode 100755 (executable)
index 0000000..57086b8
--- /dev/null
@@ -0,0 +1,337 @@
+/*
+ * rk2928_codec.c ALSA SoC RK2928 codec driver
+ *
+ * Copyright 2012 Rockchip
+ *
+ * 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 <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/gpio.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <asm/io.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "rk2928_codec.h"
+
+static struct rk2928_codec_data {
+       struct device   *dev;
+       struct clk              *hclk;
+       int                     regbase;
+       int                             regbase_phy;
+       int                             regsize_phy;
+       int     mute;
+} rk2928_data;
+
+static const struct snd_soc_dapm_widget rk2928_dapm_widgets[] = {
+       SND_SOC_DAPM_DAC("DACL", "HIFI Playback", CODEC_REG_POWER, 5, 1),
+       SND_SOC_DAPM_DAC("DACR", "HIFI Playback", CODEC_REG_POWER, 4, 1),
+       SND_SOC_DAPM_PGA("DACL Amp", CODEC_REG_DAC_GAIN, 2, 0, NULL, 0),
+       SND_SOC_DAPM_PGA("DACR Amp", CODEC_REG_DAC_GAIN, 0, 0, NULL, 0),
+       SND_SOC_DAPM_OUT_DRV("DACL Drv", CODEC_REG_DAC_MUTE, 1, 1, NULL, 0),
+       SND_SOC_DAPM_OUT_DRV("DACR Drv", CODEC_REG_DAC_MUTE, 0, 1, NULL, 0),
+       SND_SOC_DAPM_OUTPUT("SPKL"),
+       SND_SOC_DAPM_OUTPUT("SPKR"),
+       SND_SOC_DAPM_ADC("ADCL", "HIFI Capture", CODEC_REG_POWER, 3, 1),
+       SND_SOC_DAPM_ADC("ADCR", "HIFI Capture", CODEC_REG_POWER, 2, 1),
+       SND_SOC_DAPM_INPUT("MICL"),
+       SND_SOC_DAPM_INPUT("MICR"),
+};
+
+static const struct snd_soc_dapm_route rk2928_audio_map[] = {
+       {"DACL Drv", "DACL Amp", "DACL"},
+       {"DACR Drv", "DACR Amp", "DACR"},
+       {"SPKL", NULL, "DACL Drv"},
+       {"SPKR", NULL, "DACR Drv"},
+       {"ADCL", NULL, "MICL"},
+       {"ADCR", NULL, "MICR"},
+};
+
+void codec_set_spk(bool on)
+{
+       
+}
+
+static unsigned int rk2928_read(struct snd_soc_codec *codec, unsigned int reg)
+{      
+       return readl(rk2928_data.regbase + reg);
+}
+
+static int rk2928_write(struct snd_soc_codec *codec, unsigned int reg, unsigned int value)
+{
+       DBG("%s reg 0x%02x value 0x%02x", __FUNCTION__, reg, value);
+       writel(value, rk2928_data.regbase + reg);
+       return 0;
+}
+
+static int rk2928_write_mask(struct snd_soc_codec *codec, unsigned int reg, 
+                                       unsigned int mask, unsigned int value)
+{
+       unsigned int regvalue = rk2928_read(codec, reg);
+       
+       DBG("%s reg 0x%02x mask 0x%02x value 0x%02x", __FUNCTION__, reg, mask, value);
+       
+       regvalue &= ~mask;
+       regvalue |= mask & value;
+       return rk2928_write(codec, reg, regvalue);
+}
+
+static int rk2928_audio_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;
+//     struct rk2928_codec_data *priv = snd_soc_codec_get_drvdata(codec);
+       
+       DBG("%s", __FUNCTION__);
+
+       return 0;
+}
+
+static int rk2928_audio_trigger(struct snd_pcm_substream *substream, int cmd,
+                               struct snd_soc_dai *dai)
+{
+//     struct snd_soc_pcm_runtime *rtd = substream->private_data;
+//     struct snd_soc_codec *codec = rtd->codec;
+//     struct rk2928_codec_data *priv = snd_soc_codec_get_drvdata(codec);
+       int err = 0;
+
+       DBG("%s cmd 0x%x", __FUNCTION__, cmd);
+       
+       switch (cmd) {
+               case SNDRV_PCM_TRIGGER_START:
+               case SNDRV_PCM_TRIGGER_RESUME:
+               case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+                       break;
+               case SNDRV_PCM_TRIGGER_STOP:
+               case SNDRV_PCM_TRIGGER_SUSPEND:
+               case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+                       break;
+               default:
+                       err = -EINVAL;
+       }
+       return err;
+}
+
+static int rk2928_audio_startup(struct snd_pcm_substream *substream,
+                                 struct snd_soc_dai *dai)
+{
+//     struct snd_soc_pcm_runtime *rtd = substream->private_data;
+//     struct snd_soc_codec *codec = rtd->codec;
+       DBG("%s", __FUNCTION__);
+       return 0;
+}
+
+static int rk2928_set_bias_level(struct snd_soc_codec *codec,
+                             enum snd_soc_bias_level level)
+{
+       DBG("%s level %d", __FUNCTION__, level);
+       
+       if(codec == NULL)
+               return -1;
+               
+       switch(level)
+       {
+               case SND_SOC_BIAS_ON:
+                       break;
+               case SND_SOC_BIAS_PREPARE:
+                       rk2928_write_mask(codec, CODEC_REG_POWER, m_PD_MIC_BIAS | m_PD_CODEC, v_PD_MIC_BIAS(0) | v_PD_CODEC(0));
+                       break;
+               case SND_SOC_BIAS_STANDBY:
+               case SND_SOC_BIAS_OFF:
+                       rk2928_write(codec, CODEC_REG_POWER, v_PWR_OFF);
+                       break;
+               default:
+                       return -1;
+       }
+       codec->dapm.bias_level = level;
+       return 0;
+}
+
+static int rk2928_probe(struct snd_soc_codec *codec)
+{
+       struct platform_device *pdev = to_platform_device(codec->dev);
+       struct snd_soc_dapm_context *dapm = &codec->dapm;
+       struct resource *res, *mem;
+       int ret;
+       
+       DBG("%s", __FUNCTION__);
+       
+       snd_soc_codec_set_drvdata(codec, &rk2928_data);
+       
+       rk2928_data.dev = &pdev->dev;
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (!res) {
+               dev_err(&pdev->dev, "Unable to get register resource\n");
+               ret = -ENXIO;
+               goto err0;
+       }
+       rk2928_data.regbase_phy = res->start;
+       rk2928_data.regsize_phy = (res->end - res->start) + 1;
+       mem = request_mem_region(res->start, (res->end - res->start) + 1, pdev->name);
+       if (!mem)
+       {
+       dev_err(&pdev->dev, "failed to request mem region for rk2928 codec\n");
+       ret = -ENOENT;
+       goto err0;
+       }
+
+       
+       rk2928_data.regbase = (int)ioremap(res->start, (res->end - res->start) + 1);
+       if (!rk2928_data.regbase) {
+               dev_err(&pdev->dev, "cannot ioremap acodec registers\n");
+               ret = -ENXIO;
+               goto err1;
+       }
+       
+       rk2928_write(codec, CODEC_REG_DAC_MUTE, v_MUTE_DAC(1));
+       rk2928_write(codec, CODEC_REG_DAC_GAIN, v_GAIN_DAC(DAC_GAIN_3DB_P));
+       rk2928_set_bias_level(codec, SND_SOC_BIAS_OFF);
+       
+       snd_soc_dapm_new_controls(dapm, rk2928_dapm_widgets,
+                       ARRAY_SIZE(rk2928_dapm_widgets));
+       snd_soc_dapm_add_routes(dapm, rk2928_audio_map, ARRAY_SIZE(rk2928_audio_map));
+       
+       return 0;
+       
+err1:
+       release_mem_region(res->start,(res->end - res->start) + 1);
+//     clk_disable(rk2928_data.hclk);
+err0:
+       DBG("%s failed", __FUNCTION__);
+       return ret;
+}
+
+static int rk2928_remove(struct snd_soc_codec *codec)
+{
+       return 0;
+}
+
+static int rk2928_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+       DBG("%s", __FUNCTION__);
+       rk2928_set_bias_level(codec, SND_SOC_BIAS_OFF);
+       return 0;
+}
+
+static int rk2928_resume(struct snd_soc_codec *codec)
+{
+       DBG("%s", __FUNCTION__);
+       rk2928_write(codec, CODEC_REG_POWER, v_PD_ADC(1) | v_PD_DAC(1) | v_PD_MIC_BIAS(1));
+       return 0;
+}
+
+static struct snd_soc_codec_driver rk2928_audio_codec_drv = {
+       .probe = rk2928_probe,
+       .remove = rk2928_remove,
+       .suspend = rk2928_suspend,
+       .resume = rk2928_resume,
+       .read = rk2928_read,
+       .write = rk2928_write,
+       .set_bias_level = rk2928_set_bias_level,
+};
+
+static struct snd_soc_dai_ops rk2928_audio_codec_ops = {
+       .hw_params = rk2928_audio_hw_params,
+       .trigger = rk2928_audio_trigger,
+       .startup = rk2928_audio_startup,
+};
+
+static struct snd_soc_dai_driver rk2928_codec_dai = {
+       .name = "rk2928-codec",
+       .playback = {
+               .stream_name = "HIFI Playback",
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = SNDRV_PCM_RATE_8000_48000,
+               .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | 
+                       SNDRV_PCM_FMTBIT_S24_LE,
+       },
+       .capture = {
+               .stream_name = "HIFI Capture",
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = SNDRV_PCM_RATE_8000_48000,
+               .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S18_3LE,
+       },
+       .ops = &rk2928_audio_codec_ops,
+};
+
+static __devinit int rk2928_codec_probe(struct platform_device *pdev)
+{
+       int r;
+       
+       DBG("%s", __FUNCTION__);
+       
+       /* Register ASoC codec DAI */
+       r = snd_soc_register_codec(&pdev->dev, &rk2928_audio_codec_drv,
+                                       &rk2928_codec_dai, 1);
+       if (r) {
+               dev_err(&pdev->dev, "can't register ASoC rk2928 audio codec\n");
+               return r;
+       }
+       
+       DBG("%s success", __FUNCTION__);
+       return 0;
+       
+}
+
+static int __devexit rk2928_codec_remove(struct platform_device *pdev)
+{
+       snd_soc_unregister_codec(&pdev->dev);
+       return 0;
+}
+
+static struct platform_driver rk2928_codec_driver = {
+       .probe          = rk2928_codec_probe,
+       .remove         = __devexit_p(rk2928_codec_remove),
+       .driver         = {
+               .name   = "rk2928-codec",
+               .owner  = THIS_MODULE,
+       },
+};
+
+static int __init rk2928_codec_init(void)
+{
+       return platform_driver_register(&rk2928_codec_driver);
+}
+module_init(rk2928_codec_init);
+
+static void __exit rk2928_codec_exit(void)
+{
+       #ifdef CODEC_I2C_MODE
+       i2c_del_driver(&rk2928_codec_driver);
+       #else
+       platform_driver_unregister(&rk2928_codec_driver);
+       #endif
+}
+module_exit(rk2928_codec_exit);
+
+MODULE_DESCRIPTION("ASoC RK2928 codec driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/rk2928_codec.h b/sound/soc/codecs/rk2928_codec.h
new file mode 100755 (executable)
index 0000000..42401a9
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * rk2928.h ALSA SoC RK2928 codec driver
+ *
+ * Copyright 2012 Rockchip
+ *
+ * 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.
+ */
+
+#ifndef __RK2928_CODEC_H__
+#define __RK2928_CODEC_H__
+
+
+#define CODEC_REG_ADC_PGA_GAIN 0x0b
+       #define m_MIC_GAIN_CHANNEL_L            (0x0F << 4)
+       #define m_MIC_GAIN_CHANNEL_R            (0x0F)
+       #define v_MIC_GAIN_CHANNEL_L(n)         ((n) << 4)
+       #define v_MIC_GAIN_CHANNEL_R(n)         (n)
+       
+#define CODEC_REG_POWER                        0x0c
+       #define m_PD_CODEC                      (0x01)
+       #define m_PD_MIC_BIAS           (0x01 << 1)
+       #define m_PD_ADC                        (0x03 << 2)
+       #define m_PD_DAC                        (0x03 << 4)
+       #define v_PD_CODEC(n)           (n)
+       #define v_PD_MIC_BIAS(n)        (n << 1)
+       #define v_PD_ADC_R(n)           (n << 2)
+       #define v_PD_ADC_L(n)           (n << 3)
+       #define v_PD_DAC_R(n)           (n << 4)
+       #define v_PD_DAC_L(n)           (n << 5)
+       #define v_PD_ADC(n)                     (v_PD_ADC_L(n) | v_PD_ADC_R(n))
+       #define v_PD_DAC(n)                     (v_PD_DAC_L(n) | v_PD_DAC_R(n))
+       #define v_PWR_OFF                       v_PD_DAC_L(1) | v_PD_DAC_R(1) | v_PD_ADC_L(1) | v_PD_ADC_R(1) | v_PD_MIC_BIAS(1) | v_PD_CODEC(1)
+       
+#define CODEC_REG_VCM_BIAS             0x0d
+       #define v_MIC_BIAS(n)           (n)
+       enum {
+               VCM_RESISTOR_100K = 0,
+               VCM_RESISTOR_25K
+       };
+       #define v_VCM_25K_100K(n)       (n << 2)
+       
+#define CODEC_REG_DAC_MUTE             0x0e
+       #define v_MUTE_DAC_L(n)         (n << 1)
+       #define v_MUTE_DAC_R(n)         (n)
+       #define v_MUTE_DAC(n)   v_MUTE_DAC_L(n) | v_MUTE_DAC_R(n)
+       
+#define CODEC_REG_ADC_SOURCE   0x0f
+       enum {
+               ADC_SRC_MIC = 0,
+               ADC_SRC_LINE_IN
+       };
+       #define v_SRC_ADC_L(n)          (n << 1)
+       #define v_SRC_ADC_R(n)          (n)
+       
+#define CODEC_REG_DAC_GAIN             0x10
+       #define m_GAIN_DAC_L            (0x03 << 2)
+       #define m_GAIN_DAC_R            (0x03)
+       enum {
+               DAC_GAIN_0DB = 0,
+               DAC_GAIN_3DB_P = 0x2,   //3db
+               DAC_GAIN_3DB_N                  //-3db
+       };
+       #define v_GAIN_DAC_L(n)         (n << 2)
+       #define v_GAIN_DAC_R(n)         (n)
+       #define v_GAIN_DAC(n)           (v_GAIN_DAC_L(n) | v_GAIN_DAC_R(n))
+       
+//#ifndef DEBUG
+//#define DEBUG
+//#endif
+       
+#ifdef DEBUG
+#define DBG(format, ...) \
+               printk(KERN_INFO "RK2928 CODEC: " format "\n", ## __VA_ARGS__)
+#else
+#define DBG(format, ...)
+#endif
+
+
+
+#endif /* __RK2928_CODEC_H__ */
index 63e28d3c21e15b75929812951f395670f19b5d66..c95b1d03768f08d7a008475b672af7988330e0a9 100755 (executable)
@@ -169,7 +169,17 @@ config SND_RK29_SOC_RK610
          Say Y if you want to add support for SoC audio on rockchip
          with the RK610(JETTA).
 
-if SND_RK29_SOC_WM8988 || SND_RK29_SOC_RK1000 || SND_RK29_SOC_WM8994 || SND_RK29_SOC_WM8900 || SND_RK29_SOC_RT5621 || SND_RK29_SOC_RT5631 || SND_RK29_SOC_RT5625 || SND_RK29_SOC_CS42L52 || SND_RK29_SOC_AIC3111 || SND_RK29_SOC_HDMI || SND_RK29_SOC_RK610 || SND_RK29_SOC_AIC3262
+config SND_RK_SOC_RK2928
+       tristate "SoC I2S Audio support for rockchip - RK2928"
+       depends on SND_RK29_SOC && ARCH_RK2928
+       select SND_RK29_SOC_I2S
+       select SND_SOC_RK2928
+       select SND_RK29_CODEC_SOC_SLAVE
+       help
+         Say Y if you want to add support for SoC audio on rockchip
+         with the RK2928 internal codec.
+         
+if SND_RK29_SOC_WM8988 || SND_RK29_SOC_RK1000 || SND_RK29_SOC_WM8994 || SND_RK29_SOC_WM8900 || SND_RK29_SOC_RT5621 || SND_RK29_SOC_RT5631 || SND_RK29_SOC_RT5625 || SND_RK29_SOC_CS42L52 || SND_RK29_SOC_AIC3111 || SND_RK29_SOC_HDMI || SND_RK29_SOC_RK610 || SND_RK29_SOC_AIC3262 || SND_RK_SOC_RK2928
 choice
   bool "Set i2s type"
   default SND_RK29_CODEC_SOC_SLAVE
old mode 100644 (file)
new mode 100755 (executable)
index 0d06e37..1f96222
@@ -28,6 +28,7 @@ snd-soc-wm8994-objs := rk29_wm8994.o
 snd-soc-hdmi-objs := rk29_hdmi.o
 snd-soc-rk610-objs := rk29_jetta_codec.o
 snd-soc-aic3262-objs := rk29_aic3262.o
+snd-soc-rk2928-objs := rk2928-card.o
 
 obj-$(CONFIG_SND_RK29_SOC_WM8994) += snd-soc-wm8994.o
 obj-$(CONFIG_SND_RK29_SOC_WM8988) += snd-soc-wm8988.o
@@ -41,3 +42,4 @@ obj-$(CONFIG_SND_RK29_SOC_AIC3111) += snd-soc-aic3111.o
 obj-$(CONFIG_SND_RK29_SOC_AIC3262) += snd-soc-aic3262.o
 obj-$(CONFIG_SND_RK29_SOC_HDMI) += snd-soc-hdmi.o
 obj-$(CONFIG_SND_RK29_SOC_RK610) += snd-soc-rk610.o
+obj-$(CONFIG_SND_RK_SOC_RK2928) += snd-soc-rk2928.o
\ No newline at end of file
diff --git a/sound/soc/rk29/rk2928-card.c b/sound/soc/rk29/rk2928-card.c
new file mode 100755 (executable)
index 0000000..185d8ea
--- /dev/null
@@ -0,0 +1,163 @@
+/*
+ * rk2928-card.c  --  SoC audio for RockChip RK2928
+ *
+ *
+ * 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 <linux/delay.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+
+#include "rk29_pcm.h"
+#include "rk29_i2s.h"
+
+#ifdef DEBUG
+#define DBG(format, ...) \
+               printk(KERN_INFO "RK2928 Card: " format "\n", ## __VA_ARGS__)
+#else
+#define DBG(format, ...)
+#endif
+
+static int rk2928_dai_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 *cpu_dai = rtd->cpu_dai;
+       unsigned int pll_out = 0; 
+       int div_bclk,div_mclk;
+       int ret;
+         
+    DBG("Enter::%s----%d\n",__FUNCTION__,__LINE__);    
+
+    /* set cpu DAI configuration */
+     #if defined (CONFIG_SND_RK29_CODEC_SOC_SLAVE) 
+               DBG("Set cpu_dai master\n");    
+        ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+                        SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM);
+    #endif     
+    #if defined (CONFIG_SND_RK29_CODEC_SOC_MASTER)  
+           ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
+                        SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);        
+               DBG("Set cpu_dai slave\n");                             
+    #endif             
+    if (ret < 0)
+        return ret;
+       
+    switch(params_rate(params)) {
+        case 8000:
+        case 16000:
+        case 24000:
+        case 32000:
+        case 48000:
+        case 96000:
+            pll_out = 12288000;
+            break;
+        case 11025:
+        case 22050:
+        case 44100:
+        case 88200:
+            pll_out = 11289600;
+            break;
+        case 176400:
+                       pll_out = 11289600*2;
+               break;
+        case 192000:
+               pll_out = 12288000*2;
+               break;
+        default:
+            DBG("Enter:%s, %d, Error rate=%d\n",__FUNCTION__,__LINE__,params_rate(params));
+            return -EINVAL;
+            break;
+       }
+       DBG("Enter:%s, %d, rate=%d\n",__FUNCTION__,__LINE__,params_rate(params));
+
+       #if defined (CONFIG_SND_RK29_CODEC_SOC_MASTER)  
+               snd_soc_dai_set_sysclk(cpu_dai, 0, pll_out, 0);
+       #endif  
+       
+       #if defined (CONFIG_SND_RK29_CODEC_SOC_SLAVE)
+               div_bclk = 63;
+               div_mclk = pll_out/(params_rate(params)*64) - 1;
+               
+               snd_soc_dai_set_sysclk(cpu_dai, 0, pll_out, 0);
+               snd_soc_dai_set_clkdiv(cpu_dai, ROCKCHIP_DIV_BCLK,div_bclk);
+               snd_soc_dai_set_clkdiv(cpu_dai, ROCKCHIP_DIV_MCLK, div_mclk);
+       #endif
+       
+       return 0;
+}
+
+static struct snd_soc_ops rk2928_dai_ops = {
+       .hw_params = rk2928_dai_hw_params,
+};
+
+static struct snd_soc_dai_link rk2928_dai[] = {
+       {
+               .name = "RK2928",
+               .stream_name = "RK2928",
+               .cpu_dai_name = "rk29_i2s.0",
+               .platform_name = "rockchip-audio",
+               .codec_name = "rk2928-codec.0-0001",
+               .codec_dai_name = "rk2928-codec",
+               .ops = &rk2928_dai_ops,
+       },
+};
+
+/* Audio machine driver */
+static struct snd_soc_card snd_soc_rk2928 = {
+       .name = "RK2928",
+       .dai_link = rk2928_dai,
+       .num_links = ARRAY_SIZE(rk2928_dai),
+};
+
+static struct platform_device *rk2928_snd_device;
+
+static int __init rk2928_soc_init(void)
+{
+       int ret;
+
+       printk(KERN_INFO "RK2928 SoC init\n");
+
+       rk2928_snd_device = platform_device_alloc("soc-audio", -1);
+       if (!rk2928_snd_device) {
+               printk(KERN_ERR "Platform device allocation failed\n");
+               return -ENOMEM;
+       }
+
+       platform_set_drvdata(rk2928_snd_device, &snd_soc_rk2928);
+
+       ret = platform_device_add(rk2928_snd_device);
+       if (ret)
+               goto err1;
+
+       return 0;
+
+err1:
+       printk(KERN_ERR "Unable to add platform device\n");
+       platform_device_put(rk2928_snd_device);
+
+       return ret;
+}
+module_init(rk2928_soc_init);
+
+static void __exit rk2928_soc_exit(void)
+{
+       platform_device_unregister(rk2928_snd_device);
+}
+module_exit(rk2928_soc_exit);
+
+MODULE_DESCRIPTION("ALSA SoC RK2928");
+MODULE_LICENSE("GPL");
\ No newline at end of file