1. 程式人生 > 其它 >【總結筆記】全志平臺 Linux ASOC 框架淺析

【總結筆記】全志平臺 Linux ASOC 框架淺析

技術標籤:分析筆記

ASOC 各部分框圖示意

Platform

一般由 SOC晶片原廠負責編寫,主要涉及到 SOC 內部數字音訊介面DAI(I2S)和 DMA 的暫存器配置。

Codec

一般由硬體方案的驅動工程師或者 Codec 晶片原廠負責編寫,主要涉及到 Codec 晶片相關的暫存器配置。

Machine

一般由硬體方案的驅動工程師編寫,根據專案所選型的 Codec 來選擇對應的 DAI,進行關聯。

ASOC 程式碼關聯關鍵點

--------------------------------------------------------------------------------------------
Machine:關聯 Codec 和 Platform ,完成音效卡的建立,並設定 Codec 和 Platform 對齊格式和主從模式等。
sunxi-sndi2s1.c    -->  snd_soc_register_card()		<-- ac108_machine.c sunxi-snddaudio.c

Codec:針對音訊CODEC的驅動,主要是進行AD、DA轉換,對音訊通路的控制,音量控制、EQ控制等等
sndi2s1.c		   -->	snd_soc_register_codec()	<-- AC108.c

Platform:針對CPU端的驅動,主要包括資料音訊介面的配置,時鐘頻率、資料格式,DMA的設定等等
sunxi-i2s1.c	   -->	snd_soc_register_dai()		<-- sun3iw1_daudio.c
sunxi-i2s1dma.c    -->	snd_soc_register_platform()	<-- sunxi_dma.c
--------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------
sunxi-sndi2s1.c (Machine)		snd_soc_register_card()
--------------------------------------------------------------------------------------------
static struct snd_soc_ops sunxi_sndi2s1_ops = {
	.hw_params 		= sunxi_sndi2s1_hw_params,	// 硬體引數設定
};

static struct snd_soc_dai_link sunxi_sndi2s1_dai_link = {
	.name 			= "I2S1",
	.stream_name 	= "SUNXI-I2S1",
    .init 			= sunxi_i2s1_init,

	//Platform的數字音訊介面(DAI)的名稱,系統根據這個匹配Platform_dai驅動
    //sunxi-i2s1.c --> platform_driver_register()
	.cpu_dai_name 	= "i2s1",

    //Platform的名稱,用來匹配Platform驅動的 
    //sunxi-i2s1dma.c --> snd_soc_register_platform()
	.platform_name 	= "sunxi-i2s1-pcm-audio.0",

	//codec的數字音訊介面(DAI)的名稱,系統根據這個匹配codec_dai驅動
    //sndi2s1.c --> snd_soc_register_codec()
	.codec_dai_name = "sndi2s1",

	//codec的名稱,系統將根據這個名字匹配相應的Codec驅動
    //sndi2s1.c --> platform_device_register()/i2c_add_driver()
	.codec_name 	= "sunxi-i2s1-codec.0",	

	.ops 			= &sunxi_sndi2s1_ops,
};

static struct snd_soc_card snd_soc_sunxi_sndi2s1 = {
    .name 		= "sndi2s1",		// 為我們的音效卡定義一個名字
	.owner 		= THIS_MODULE,
	.dai_link 	= &sunxi_sndi2s1_dai_link,
	.num_links 	= 1,
};	
snd_soc_register_card(&snd_soc_sunxi_sndi2s1)
    
--------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------
sndi2s1.c	 (Codec)		snd_soc_register_codec()
--------------------------------------------------------------------------------------------
struct snd_soc_dai_ops sndi2s1_dai_ops = {
	.startup = sndi2s1_startup,			// 開啟裝置,裝置開始工作的時候回撥
	.shutdown = sndi2s1_shutdown,		// 關閉裝置前的回撥
	.hw_params = sndi2s1_hw_params,		// 設定Codec硬體相關的引數(資料位寬等)
	.digital_mute = sndi2s1_mute,		// Codec靜音操作
	.set_sysclk = sndi2s1_set_dai_sysclk, // 設定 Codec 的主時鐘(SYSCLK_SRC_MCLK、SYSCLK_SRC_PLL)
	.set_clkdiv = sndi2s1_set_dai_clkdiv,	 // 設定Codec的分頻係數
	.set_fmt = sndi2s1_set_dai_fmt		 // 設定Codec傳輸的資料格式(主從、對齊格式、時鐘極性)
};	 //.hw_params\.set_sysclk\.set_clkdiv\.set_fmt 由Machine驅動設定

struct snd_soc_dai_driver sndi2s1_dai = {
	.name = "sndi2s1",					// 用於被snd_soc_dai_link.codec_dai_name 匹配
	.playback = {					// 用於描述播放時Codec支援的聲道數,位元速率,資料格式等能力
		.stream_name = "Playback",
		.channels_min = 1,
		.channels_max = 2,
		.rates = sndi2s1_RATES,
		.formats = sndi2s1_FORMATS,
	},
	.capture = {					// 用於描述錄音時Codec支援的聲道數,位元速率,資料格式等能力
		.stream_name = "Capture",
		.channels_min = 1,
		.channels_max = 2,
		.rates = sndi2s1_RATES,
		.formats = sndi2s1_FORMATS,
	},
	.ops = &sndi2s1_dai_ops,	
};
EXPORT_SYMBOL(sndi2s1_dai);
snd_soc_register_codec(&pdev->dev, &soc_codec_dev_sndi2s1, &sndi2s1_dai, 1);

static struct platform_device sndi2s1_codec_device = {
	.name = "sunxi-i2s1-codec",
};

static struct platform_driver sndi2s1_codec_driver = {
	.driver = {
		.name = "sunxi-i2s1-codec",		// 用於被snd_soc_dai_link.codec_name 匹配
		.owner = THIS_MODULE,
	},
	.probe = sndi2s1_codec_probe,
	.remove = __exit_p(sndi2s1_codec_remove),
};
platform_device_register(&sndi2s1_codec_device)
platform_driver_register(&sndi2s1_codec_driver)
    
--------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------
sunxi-i2s1.c (Platform - dai:I2S 通訊介面)		snd_soc_register_dai()
--------------------------------------------------------------------------------------------
static struct snd_soc_dai_ops sunxi_i2s1_dai_ops = {
	.trigger 	= sunxi_i2s1_trigger,			// I2S 啟動、暫停、恢復、停止時的操作
	.hw_params 	= sunxi_i2s1_hw_params,		// 設定I2S資料位寬等硬體引數
	.set_fmt 	= sunxi_i2s1_set_fmt,			// 設定I2S資料格式(主從、對齊格式、時鐘極性)
	.set_clkdiv = sunxi_i2s1_set_clkdiv,		// 設定I2S的時鐘分頻
     .set_sysclk = sunxi_i2s1_set_sysclk,		// 設定系統時鐘
};	//.hw_params.\set_sysclk\.set_clkdiv\.set_fmt 由Machine驅動設定

static struct snd_soc_dai_driver sunxi_i2s1_dai = {
	.probe 	    = sunxi_i2s1_dai_probe,
	.suspend 	= sunxi_i2s1_suspend,
	.resume 	= sunxi_i2s1_resume,
	.remove 	= sunxi_i2s1_dai_remove,
	.playback 	= {					//用於描述播放時I2S支援的聲道數,位元速率,資料格式等能力
		.channels_min = 1,
		.channels_max = 2,
		.rates = SUNXI_I2S1_RATES,
		.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE,
	},
	.capture 	= {						//用於描述錄音時I2S支援的聲道數,位元速率,資料格式等能力
		.channels_min = 1,
		.channels_max = 2,
		.rates = SUNXI_I2S1_RATES,
		.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE,
	},
     .ops     = &sunxi_i2s1_dai_ops,
};
snd_soc_register_dai(&pdev->dev, &sunxi_i2s1_dai);

static struct platform_device sunxi_i2s1_device = {
	.name 	= "i2s1",
	.id 	= PLATFORM_DEVID_NONE,
};

static struct platform_driver sunxi_i2s1_driver = {
	.probe = sunxi_i2s1_dev_probe,
	.remove = __exit_p(sunxi_i2s1_dev_remove),
	.driver = {
		.name = "i2s1",	// 用於被snd_soc_dai_link.cpu_dai_name 匹配
		.owner = THIS_MODULE,
	},
};
platform_device_register(&sunxi_i2s1_device)
platform_driver_register(&sunxi_i2s1_driver)

--------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------
sunxi-i2s1dma.c (Platform - dai:音訊 DMA 配置)	snd_soc_register_platform()
--------------------------------------------------------------------------------------------
static struct snd_pcm_ops sunxi_pcm_ops = {
	.open			= sunxi_pcm_open,		// 開啟裝置,準備開始播放時呼叫,會開啟 DMA 引擎
	.close			= sunxi_pcm_close,		// 關閉播放,關閉DMA引擎
	.ioctl			= snd_pcm_lib_ioctl,	// 應用層呼叫的 ioctl 回撥
 	// 應用設定播放參數的時候呼叫,根據設定的引數,設定DMA,例如資料寬度,傳輸塊大小,DMA地址
	.hw_params		= sunxi_pcm_hw_params,	
    .hw_free		= sunxi_pcm_hw_free,
	.trigger		= sunxi_pcm_trigger,			// DMA 開始、暫停、恢復、結束傳輸的回撥
	.pointer		= snd_dmaengine_pcm_pointer,	// 返回DMA緩衝的當前指標
	.mmap			= sunxi_pcm_mmap,				// 建立記憶體對映
};

static struct snd_soc_platform_driver sunxi_soc_platform = {
	.ops		= &sunxi_pcm_ops,
	.pcm_new	= sunxi_pcm_new,
	.pcm_free	= sunxi_pcm_free_dma_buffers,
};
snd_soc_register_platform(&pdev->dev, &sunxi_soc_platform);

static struct platform_device sunxi_i2s1_pcm_device = {
	.name = "sunxi-i2s1-pcm-audio",
};

static struct platform_driver sunxi_i2s1_pcm_driver = {
	.probe = sunxi_i2s1_pcm_probe,
	.remove = __exit_p(sunxi_i2s1_pcm_remove),
	.driver = {
		.name = "sunxi-i2s1-pcm-audio",  // 用於被snd_soc_dai_link.platform_name 匹配
		.owner = THIS_MODULE,
	},
};
platform_device_register(&sunxi_i2s1_pcm_device);
platform_driver_register(&sunxi_i2s1_pcm_driver);
--------------------------------------------------------------------------------------------

Machine 例項(sunxi_sndi2s1.c)

/*
 * sound\soc\sunxi\i2s1\sunxi_sndi2s1.c
 * (C) Copyright 2010-2016
 * Reuuimlla Technology Co., Ltd. <www.reuuimllatech.com>
 * huangxin <[email protected]>
 *
 * some simple description for this code
 *
 * 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/clk.h>
#include <linux/mutex.h>

#include <sound/pcm.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>
#include <sound/soc-dapm.h>
#include <linux/io.h>
#include <mach/sys_config.h>

#include "sunxi-i2s1.h"
#include "sunxi-i2s1dma.h"

static bool i2s1_pcm_select 	= 0;

static int i2s1_used 		= 0;
static int i2s1_master 		= 0;
static int audio_format 	= 0;
static int signal_inversion = 0;

/*
*	i2s1_pcm_select == 0:-->	pcm
*	i2s1_pcm_select == 1:-->	i2s
*/
static int sunxi_i2s1_set_audio_mode(struct snd_kcontrol *kcontrol,
	struct snd_ctl_elem_value *ucontrol)
{
	i2s1_pcm_select = ucontrol->value.integer.value[0];

	if (i2s1_pcm_select) {
		audio_format 		= 1;
		signal_inversion 	= 1;
		i2s1_master 			= 4;
	} else {
		audio_format 		= 4;
		signal_inversion 	= 3;
		i2s1_master 			= 1;
	}

	return 0;
}

static int sunxi_i2s1_get_audio_mode(struct snd_kcontrol *kcontrol,
	struct snd_ctl_elem_value *ucontrol)
{
	ucontrol->value.integer.value[0] = i2s1_pcm_select;
	return 0;
}

/* I2s Or Pcm Audio Mode Select */
static const struct snd_kcontrol_new sunxi_i2s1_controls[] = {
	SOC_SINGLE_BOOL_EXT("I2s Or Pcm Audio Mode Select format", 0,
			sunxi_i2s1_get_audio_mode, sunxi_i2s1_set_audio_mode),
};

static int sunxi_sndi2s1_hw_params(struct snd_pcm_substream *substream,
					struct snd_pcm_hw_params *params)
{
	int ret  = 0;
	u32 freq = 22579200;

	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 long sample_rate = params_rate(params);

	switch (sample_rate) {
		case 8000:
		case 16000:
		case 32000:
		case 64000:
		case 128000:
		case 12000:
		case 24000:
		case 48000:
		case 96000:
		case 192000:
			freq = 24576000;
			break;
	}

	/*set system clock source freq and set the mode as i2s1 or pcm*/
	ret = snd_soc_dai_set_sysclk(cpu_dai, 0 , freq, i2s1_pcm_select);
	if (ret < 0) {
		return ret;
	}

	ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_A |
			SND_SOC_DAIFMT_IB_NF | SND_SOC_DAIFMT_CBM_CFM);
	if (ret < 0)
		return ret;
	/*
	* codec clk & FRM master. AP as slave
	*/
	ret = snd_soc_dai_set_fmt(cpu_dai, (audio_format | (signal_inversion<<8) | (i2s1_master<<12)));
	if (ret < 0) {
		return ret;
	}

	ret = snd_soc_dai_set_clkdiv(cpu_dai, 0, sample_rate);
	if (ret < 0) {
		return ret;
	}

	/*
	*	audio_format == SND_SOC_DAIFMT_DSP_A
	*	signal_inversion<<8 == SND_SOC_DAIFMT_IB_NF
	*	i2s1_master<<12 == SND_SOC_DAIFMT_CBM_CFM
	*/
	I2S1_DBG("%s,line:%d,audio_format:%d,SND_SOC_DAIFMT_DSP_A:%d\n",\
			__func__, __LINE__, audio_format, SND_SOC_DAIFMT_DSP_A);
	I2S1_DBG("%s,line:%d,signal_inversion:%d,signal_inversion<<8:%d,SND_SOC_DAIFMT_IB_NF:%d\n",\
			__func__, __LINE__, signal_inversion, signal_inversion<<8, SND_SOC_DAIFMT_IB_NF);
	I2S1_DBG("%s,line:%d,i2s1_master:%d,i2s1_master<<12:%d,SND_SOC_DAIFMT_CBM_CFM:%d\n",\
			__func__, __LINE__, i2s1_master, i2s1_master<<12, SND_SOC_DAIFMT_CBM_CFM);

	return 0;
}

/*
 * Card initialization
 */
static int sunxi_i2s1_init(struct snd_soc_pcm_runtime *rtd)
{
	struct snd_soc_codec *codec = rtd->codec;
	struct snd_soc_card *card = rtd->card;
	int ret;

	/* Add virtual switch */
	ret = snd_soc_add_codec_controls(codec, sunxi_i2s1_controls,
					ARRAY_SIZE(sunxi_i2s1_controls));
	if (ret) {
		dev_warn(card->dev,
				"Failed to register audio mode control, "
				"will continue without it.\n");
	}
	return 0;
}

static struct snd_soc_ops sunxi_sndi2s1_ops = {
	.hw_params 		= sunxi_sndi2s1_hw_params,
};

static struct snd_soc_dai_link sunxi_sndi2s1_dai_link = {
	.name 			= "I2S1",
	.stream_name 	= "SUNXI-I2S1",
	.cpu_dai_name 	= "i2s1",
	.codec_dai_name = "sndi2s1",
	.init 			= sunxi_i2s1_init,
	.platform_name 	= "sunxi-i2s1-pcm-audio.0",
	.codec_name 	= "sunxi-i2s1-codec.0",
	.ops 			= &sunxi_sndi2s1_ops,
};

static struct snd_soc_card snd_soc_sunxi_sndi2s1 = {
	.name 		= "sndi2s1",
	.owner 		= THIS_MODULE,
	.dai_link 	= &sunxi_sndi2s1_dai_link,
	.num_links 	= 1,
};

static int __devinit sunxi_sndi2s1_dev_probe(struct platform_device *pdev)
{
	int ret = 0;
	script_item_u val;
	script_item_value_type_e  type;
	struct snd_soc_card *card = &snd_soc_sunxi_sndi2s1;

	type = script_get_item("i2s1", "i2s1_select", &val);
	if (SCIRPT_ITEM_VALUE_TYPE_INT != type) {
        pr_err("[I2S1] i2s1_select type err!\n");
    }
	i2s1_pcm_select = val.val;
	
	type = script_get_item("i2s1", "i2s1_master", &val);
	if (SCIRPT_ITEM_VALUE_TYPE_INT != type) {
        pr_err("[I2S1] i2s1_master type err!\n");
    }
	i2s1_master = val.val;
	
	type = script_get_item("i2s1", "audio_format", &val);
	if (SCIRPT_ITEM_VALUE_TYPE_INT != type) {
        pr_err("[I2S1] audio_format type err!\n");
    }
	audio_format = val.val;

	type = script_get_item("i2s1", "signal_inversion", &val);
	if (SCIRPT_ITEM_VALUE_TYPE_INT != type) {
        pr_err("[I2S1] signal_inversion type err!\n");
    }
	signal_inversion = val.val;
	card->dev = &pdev->dev;
	ret = snd_soc_register_card(card);
	if (ret) {
		dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", ret);
	}
	return ret;
}

static int __devexit sunxi_sndi2s1_dev_remove(struct platform_device *pdev)
{
	struct snd_soc_card *card = platform_get_drvdata(pdev);

	snd_soc_unregister_card(card);
	return 0;
}

/*data relating*/
static struct platform_device sunxi_i2s1_device = {
	.name 	= "sndi2s1",
	.id 	= PLATFORM_DEVID_NONE,
};

/*method relating*/
static struct platform_driver sunxi_i2s1_driver = {
	.probe = sunxi_sndi2s1_dev_probe,
	.remove = __exit_p(sunxi_sndi2s1_dev_remove),
	.driver = {
		.name = "sndi2s1",
		.owner = THIS_MODULE,
		.pm = &snd_soc_pm_ops,
	},
};

static int __init sunxi_sndi2s1_init(void)
{
	int err = 0;
	script_item_u val;
	script_item_value_type_e  type;
	type = script_get_item("i2s1", "i2s1_used", &val);
	if (SCIRPT_ITEM_VALUE_TYPE_INT != type) {
        	pr_err("[I2S] type err!\n");
    	}
	i2s1_used = val.val;
	if (i2s1_used) {
		if((err = platform_device_register(&sunxi_i2s1_device)) < 0)
			return err;

		if ((err = platform_driver_register(&sunxi_i2s1_driver)) < 0)
			return err;
    	} else {
		pr_warning("I2S1 driver not init,just return.\n");
	}
	return 0;
}
module_init(sunxi_sndi2s1_init);

static void __exit sunxi_sndi2s1_exit(void)
{
	if (i2s1_used) {
		i2s1_used = 0;
		platform_driver_unregister(&sunxi_i2s1_driver);
		platform_device_unregister(&sunxi_i2s1_device);
	}
}
module_exit(sunxi_sndi2s1_exit);
MODULE_AUTHOR("huangxin");
MODULE_DESCRIPTION("SUNXI_sndi2s1 ALSA SoC audio driver");
MODULE_LICENSE("GPL");

Codec 例項(sndi2s1.c)

/*
 * sound\soc\sunxi\i2s1\sndi2s1.c
 * (C) Copyright 2010-2016
 * Reuuimlla Technology Co., Ltd. <www.reuuimllatech.com>
 * huangxin <[email protected]>
 *
 * some simple description for this code
 *
 * 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/delay.h>
#include <linux/slab.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/initval.h>
#include <linux/io.h>
#include <mach/sys_config.h>

struct sndi2s1_priv {
	int sysclk;
	int dai_fmt;

	struct snd_pcm_substream *master_substream;
	struct snd_pcm_substream *slave_substream;
};

static int i2s1_used = 0;
#define sndi2s1_RATES  (SNDRV_PCM_RATE_8000_192000|SNDRV_PCM_RATE_KNOT)
#define sndi2s1_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE | \
		                     SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE)

static int sndi2s1_mute(struct snd_soc_dai *dai, int mute)
{
	return 0;
}

static int sndi2s1_startup(struct snd_pcm_substream *substream,
	struct snd_soc_dai *dai)
{
	return 0;
}

static void sndi2s1_shutdown(struct snd_pcm_substream *substream,
	struct snd_soc_dai *dai)
{
	
}

static int sndi2s1_hw_params(struct snd_pcm_substream *substream,
	struct snd_pcm_hw_params *params,
	struct snd_soc_dai *dai)
{
	return 0;
}

static int sndi2s1_set_dai_sysclk(struct snd_soc_dai *codec_dai,
				  int clk_id, unsigned int freq, int dir)
{
	return 0;
}

static int sndi2s1_set_dai_clkdiv(struct snd_soc_dai *codec_dai, int div_id, int div)
{
	return 0;
}

static int sndi2s1_set_dai_fmt(struct snd_soc_dai *codec_dai,
			       unsigned int fmt)
{
	return 0;
}

struct snd_soc_dai_ops sndi2s1_dai_ops = {
	.startup = sndi2s1_startup,
	.shutdown = sndi2s1_shutdown,
	.hw_params = sndi2s1_hw_params,
	.digital_mute = sndi2s1_mute,
	.set_sysclk = sndi2s1_set_dai_sysclk,
	.set_clkdiv = sndi2s1_set_dai_clkdiv,
	.set_fmt = sndi2s1_set_dai_fmt,
};

struct snd_soc_dai_driver sndi2s1_dai = {
	.name = "sndi2s1",
	/* playback capabilities */
	.playback = {
		.stream_name = "Playback",
		.channels_min = 1,
		.channels_max = 8,
		.rates = sndi2s1_RATES,
		.formats = sndi2s1_FORMATS,
	},
	.capture = {
		.stream_name = "Capture",
		.channels_min = 1,
		.channels_max = 8,
		.rates = sndi2s1_RATES,
		.formats = sndi2s1_FORMATS,
	},
	/* pcm operations */
	.ops = &sndi2s1_dai_ops,	
};
EXPORT_SYMBOL(sndi2s1_dai);
	
static int sndi2s1_soc_probe(struct snd_soc_codec *codec)
{
	struct sndi2s1_priv *sndi2s1;

	sndi2s1 = kzalloc(sizeof(struct sndi2s1_priv), GFP_KERNEL);
	if(sndi2s1 == NULL){		
		return -ENOMEM;
	}		
	snd_soc_codec_set_drvdata(codec, sndi2s1);

	return 0;
}

/* power down chip */
static int sndi2s1_soc_remove(struct snd_soc_codec *codec)
{
	struct sndi2s1_priv *sndi2s1 = snd_soc_codec_get_drvdata(codec);

	kfree(sndi2s1);

	return 0;
}

static struct snd_soc_codec_driver soc_codec_dev_sndi2s1 = {
	.probe 	=	sndi2s1_soc_probe,
	.remove =   sndi2s1_soc_remove,
};

static int __devinit sndi2s1_codec_probe(struct platform_device *pdev)
{
	return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_sndi2s1, &sndi2s1_dai, 1);	
}

static int __devexit sndi2s1_codec_remove(struct platform_device *pdev)
{
	snd_soc_unregister_codec(&pdev->dev);
	return 0;
}

/*data relating*/
static struct platform_device sndi2s1_codec_device = {
	.name = "sunxi-i2s1-codec",
};

/*method relating*/
static struct platform_driver sndi2s1_codec_driver = {
	.driver = {
		.name = "sunxi-i2s1-codec",
		.owner = THIS_MODULE,
	},
	.probe = sndi2s1_codec_probe,
	.remove = __exit_p(sndi2s1_codec_remove),
};

static int __init sndi2s1_codec_init(void)
{	
	int err = 0;
	script_item_u val;
	script_item_value_type_e  type;

	type = script_get_item("i2s1", "i2s1_used", &val);
	if (SCIRPT_ITEM_VALUE_TYPE_INT != type) {
        pr_err("[I2S] type err!\n");
    }

	i2s1_used = val.val;

	if (i2s1_used) {
		if((err = platform_device_register(&sndi2s1_codec_device)) < 0)
			return err;
	
		if ((err = platform_driver_register(&sndi2s1_codec_driver)) < 0)
			return err;
	} else {
       pr_err("[I2S]sndi2s1 cannot find any using configuration for controllers, return directly!\n");
       return 0;
    }
	
	return 0;
}
module_init(sndi2s1_codec_init);

static void __exit sndi2s1_codec_exit(void)
{
	if (i2s1_used) {
		i2s1_used = 0;
		platform_driver_unregister(&sndi2s1_codec_driver);
	}
}
module_exit(sndi2s1_codec_exit);

MODULE_DESCRIPTION("SNDI2S1 ALSA soc codec driver");
MODULE_AUTHOR("huangxin");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:sunxi-i2s1-codec");