【总结笔记】全志平台 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 <huangxin@Reuuimllatech.com>
*
* 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 <huangxin@Reuuimllatech.com>
*
* 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");