From: 邱建斌 Date: Tue, 20 Aug 2013 03:26:22 +0000 (+0800) Subject: ak4396: add ak4396 dac support X-Git-Tag: firefly_0821_release~6718 X-Git-Url: http://demsky.eecs.uci.edu/git/?a=commitdiff_plain;h=4beee34fab4f36d90838f1fd1d733432c8a3df0a;p=firefly-linux-kernel-4.4.55.git ak4396: add ak4396 dac support --- diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index fc59e4e4dabe..fdc4fa94efdf 100755 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -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 diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 964bd7baf7f5..0aaf90b2c3d3 100755 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -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 index 000000000000..da34d20c8c75 --- /dev/null +++ b/sound/soc/codecs/ak4396.c @@ -0,0 +1,473 @@ +/* + * AK4396 ALSA SoC (ASoC) driver + * + * Copyright (c) 2009 Daniel Mack + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 "); +MODULE_DESCRIPTION("Asahi Kasei AK4396 ALSA SoC driver"); +MODULE_LICENSE("GPL"); + diff --git a/sound/soc/rk29/Kconfig b/sound/soc/rk29/Kconfig index eb3d83d94cbc..cb95c2319a62 100755 --- a/sound/soc/rk29/Kconfig +++ b/sound/soc/rk29/Kconfig @@ -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 diff --git a/sound/soc/rk29/Makefile b/sound/soc/rk29/Makefile index 7bbd6adf6b1b..97834810f6ab 100755 --- a/sound/soc/rk29/Makefile +++ b/sound/soc/rk29/Makefile @@ -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 index 000000000000..c5a324014069 --- /dev/null +++ b/sound/soc/rk29/rk29_ak4396.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include "rk29_pcm.h" +#include "rk29_i2s.h" + +#include + +#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");