学习笔记 --- LINUX ASoC声卡驱动接口分析

来源:互联网 发布:列车网络控制系统 编辑:程序博客网 时间:2024/06/16 15:39

        ASoC(ALSA System on Chip)是ALSA在SoC方面的发展和演变,它在本质上仍然属于ALSA,但是在ALSA架构的基础上对CPU相关的代码和CODEC相关的代码进行了分离。其原因是,采用传统ALSA架构的情况下,同一型号的CODEC工作于不同的CPU时,需要不同的驱动,这不符合代码重用的要求。

ASoC驱动有以下三部分组成:

(1)       CODEC驱动:由内核源代码sound/soc/codecs/uda134x.c实现

(2)       平台驱动:由内核源代码sound/soc/s3c24xx/s3c24xx-i2s.c实现CPU端的DAI驱动,由sound/soc/s3c24xx/s3c24xx_pcm.c实现CPU端的DMA驱动

(3)       板驱动:由内核源代码sound/soc/s3c24xx/s3c24xx_uda134x.c实现,它将第一部分和第二部分进行绑定。

在以上三部分之上的是ASoC核心层,由内核源代码中的sound/soc/soc-core.c实现,查看其源代码发现它完全是一个传统的ALSA驱动。

在以上3部分中,12基本上都可以仍然是通用的驱动了,也就是说,CODEC驱动认为自己可以连接任意CPU,而CPUI2SPCM、或AC’97接口对应的平台驱动则认为自己可以连接任意符合接口类型的CODEC,只有3是不通用的,由特性的电路板上具体的CPUCODEC确定,因此它很像一个插座,上面插上了CODEC和平台这两个插头。

先来分析上层入口:

static int s3c24xx_uda134x_probe(struct platform_device *pdev){int ret;printk(KERN_INFO "S3C24XX_UDA134X SoC Audio driver\n");s3c24xx_uda134x_l3_pins = pdev->dev.platform_data;if (s3c24xx_uda134x_l3_pins == NULL) {printk(KERN_ERR "S3C24XX_UDA134X SoC Audio: "       "unable to find platform data\n");return -ENODEV;}s3c24xx_uda134x.power = s3c24xx_uda134x_l3_pins->power;s3c24xx_uda134x.model = s3c24xx_uda134x_l3_pins->model;        //设置L3总线的接口都为输出if (s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_data,      "data") < 0)return -EBUSY;if (s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_clk,      "clk") < 0) {gpio_free(s3c24xx_uda134x_l3_pins->l3_data);return -EBUSY;}if (s3c24xx_uda134x_setup_pin(s3c24xx_uda134x_l3_pins->l3_mode,      "mode") < 0) {gpio_free(s3c24xx_uda134x_l3_pins->l3_data);gpio_free(s3c24xx_uda134x_l3_pins->l3_clk);return -EBUSY;}s3c24xx_uda134x_snd_device = platform_device_alloc("soc-audio", -1);  //分配一个设备,名字为soc-audio,这个名字会匹配这个设备的驱动if (!s3c24xx_uda134x_snd_device) {printk(KERN_ERR "S3C24XX_UDA134X SoC Audio: "       "Unable to register\n");return -ENOMEM;}platform_set_drvdata(s3c24xx_uda134x_snd_device,     &s3c24xx_uda134x_snd_devdata);   //设置ASoC驱动所需数据s3c24xx_uda134x_snd_devdata.dev = &s3c24xx_uda134x_snd_device->dev;ret = platform_device_add(s3c24xx_uda134x_snd_device);  //添加一个ASoC设备,将会匹配驱动 soc-audioif (ret) { printk(KERN_ERR "S3C24XX_UDA134X SoC Audio: Unable to add\n");platform_device_put(s3c24xx_uda134x_snd_device);}return ret;}
上面设置好一个设备的数据:s3c24xx_uda134x_snd_devdata

static struct snd_soc_device s3c24xx_uda134x_snd_devdata = {.card = &snd_soc_s3c24xx_uda134x,     //板级= codec(L3) +CPU(IIS,DMA) 操作.codec_dev = &soc_codec_dev_uda134x,     //codec 级.codec_data = &s3c24xx_uda134x,     //codec 实现方法};

然后添加这个设备,这个设备名:soc-audio,对应内核源代码中的sound/soc/soc-core.c驱动名称:

/* ASoC platform driver */static struct platform_driver soc_driver = {.driver= {.name= "soc-audio",.owner= THIS_MODULE,.pm= &soc_pm_ops,},.probe= soc_probe,.remove= soc_remove,};

匹配完后,调用驱动中的probe函数:soc_probe

static int soc_probe(struct platform_device *pdev){int ret = 0;struct snd_soc_device *socdev = platform_get_drvdata(pdev); //获得设备数据,这个数据就是添加设备的时候设置好的struct snd_soc_card *card = socdev->card;/* Bodge while we push things out of socdev */card->socdev = socdev;/* Bodge while we unpick instantiation */card->dev = &pdev->dev;ret = snd_soc_register_card(card);if (ret != 0) {dev_err(&pdev->dev, "Failed to register card\n");return ret;}return 0;}
这个函数开始就获得添加设备的时候传进来的数据:snd_soc_device

然后调用:snd_soc_register_card 函数注册板级,可以知道音频驱动以板级为入口,再ASoC驱动管理CODEC与平台控制。

这个函数会调用snd_soc_instantiate_cards->snd_soc_instantiate_card:

static void snd_soc_instantiate_card(struct snd_soc_card *card){struct platform_device *pdev = container_of(card->dev,    struct platform_device,    dev);struct snd_soc_codec_device *codec_dev = card->socdev->codec_dev;struct snd_soc_platform *platform;struct snd_soc_dai *dai;int i, found, ret, ac97;if (card->instantiated)return;found = 0;list_for_each_entry(platform, &platform_list, list)if (card->platform == platform) {found = 1;break;}if (!found) {dev_dbg(card->dev, "Platform %s not registered\n",card->platform->name);return;}ac97 = 0;for (i = 0; i < card->num_links; i++) {found = 0;list_for_each_entry(dai, &dai_list, list)if (card->dai_link[i].cpu_dai == dai) {found = 1;break;}if (!found) {dev_dbg(card->dev, "DAI %s not registered\n",card->dai_link[i].cpu_dai->name);return;}if (card->dai_link[i].cpu_dai->ac97_control)ac97 = 1;}for (i = 0; i < card->num_links; i++) {if (!card->dai_link[i].codec_dai->ops)card->dai_link[i].codec_dai->ops = &null_dai_ops;}/* If we have AC97 in the system then don't wait for the * codec.  This will need revisiting if we have to handle * systems with mixed AC97 and non-AC97 parts.  Only check for * DAIs currently; we can't do this per link since some AC97 * codecs have non-AC97 DAIs. */if (!ac97)for (i = 0; i < card->num_links; i++) {found = 0;list_for_each_entry(dai, &dai_list, list)if (card->dai_link[i].codec_dai == dai) {found = 1;break;}if (!found) {dev_dbg(card->dev, "DAI %s not registered\n",card->dai_link[i].codec_dai->name);return;}}/* Note that we do not current check for codec components */dev_dbg(card->dev, "All components present, instantiating\n");/* Found everything, bring it up */if (card->probe) {ret = card->probe(pdev);    // 1 执行板级安装if (ret < 0)return;}for (i = 0; i < card->num_links; i++) {struct snd_soc_dai *cpu_dai = card->dai_link[i].cpu_dai;if (cpu_dai->probe) {ret = cpu_dai->probe(pdev, cpu_dai); // 2 执行CPU dai安装if (ret < 0)goto cpu_dai_err;}}if (codec_dev->probe) {ret = codec_dev->probe(pdev);      // 3 执行codec级安装if (ret < 0)goto cpu_dai_err;}if (platform->probe) {ret = platform->probe(pdev);    // 4 执行平台级安装 if (ret < 0)goto platform_err;}/* DAPM stream work */INIT_DELAYED_WORK(&card->delayed_work, close_delayed_work);#ifdef CONFIG_PM/* deferred resume work */INIT_WORK(&card->deferred_resume_work, soc_resume_deferred);#endifcard->instantiated = 1;return;platform_err:if (codec_dev->remove)codec_dev->remove(pdev);cpu_dai_err:for (i--; i >= 0; i--) {struct snd_soc_dai *cpu_dai = card->dai_link[i].cpu_dai;if (cpu_dai->remove)cpu_dai->remove(pdev, cpu_dai);}if (card->remove)card->remove(pdev);}
这个函数调用各个部分的probe函数执行。到这里就交给ASoC核心处理了。这些probe处理,都是通过之前添加设备的时候传进来的snd_soc_device数据:

/* SoC Device - the audio subsystem */struct snd_soc_device {struct device *dev;struct snd_soc_card *card;struct snd_soc_codec_device *codec_dev;void *codec_data;};
所以我们的驱动工作就主要是完成这个数据结构。

这个结构体是对ASoC设备的整体封装,包括了:

1 板级用的snd_soc_card

CODEC用的snd_soc_codec_device

3 codec_data(平台给CODEC用的数据)

看下具体设置:

1 snd_coc_card

static struct snd_soc_card snd_soc_s3c24xx_uda134x = {.name = "S3C24XX_UDA134X",.platform = &s3c24xx_soc_platform,  //CPU的DMA操作音频数据在IIS的传送.dai_link = &s3c24xx_uda134x_dai_link,  //CPU与CODEC DAI集合.num_links = 1,};

struct snd_soc_platform s3c24xx_soc_platform = {.name= "s3c24xx-audio",.pcm_ops = &s3c24xx_pcm_ops, //DMA操作.pcm_new= s3c24xx_pcm_new, //分配DMA内存.pcm_free= s3c24xx_pcm_free_dma_buffers, //释放DMA内存};

static struct snd_soc_dai_link s3c24xx_uda134x_dai_link = {.name = "UDA134X",.stream_name = "UDA134X",.codec_dai = &uda134x_dai,      //DODEC DAI ---- uda134x 的L3总线控制操作(决定L3发什么).cpu_dai = &s3c24xx_i2s_dai,   //CPU DAI -----CPU 的IIS总线配置.ops = &s3c24xx_uda134x_ops, };

1.1 DODEC DAI  :

struct snd_soc_dai uda134x_dai = {.name = "UDA134X",/* playback capabilities */.playback = {.stream_name = "Playback",.channels_min = 1,.channels_max = 2,.rates = UDA134X_RATES,.formats = UDA134X_FORMATS,},/* capture capabilities */.capture = {.stream_name = "Capture",.channels_min = 1,.channels_max = 2,.rates = UDA134X_RATES,.formats = UDA134X_FORMATS,},/* pcm operations */.ops = &uda134x_dai_ops,};

1.1.1 对于DODEC  L3的操作集uda134x_dai_ops:

static struct snd_soc_dai_ops uda134x_dai_ops = {.startup= uda134x_startup,.shutdown= uda134x_shutdown,.hw_params= uda134x_hw_params,.digital_mute= uda134x_mute,.set_sysclk= uda134x_set_dai_sysclk,.set_fmt= uda134x_set_dai_fmt,};

1.2 CPU DAI  :

struct snd_soc_dai s3c24xx_i2s_dai = {.name = "s3c24xx-i2s",.id = 0,.probe = s3c24xx_i2s_probe, //完成CPU上I2S接口的初始化.suspend = s3c24xx_i2s_suspend,.resume = s3c24xx_i2s_resume,.playback = {.channels_min = 2,.channels_max = 2,.rates = S3C24XX_I2S_RATES,.formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},.capture = {.channels_min = 2,.channels_max = 2,.rates = S3C24XX_I2S_RATES,.formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},.ops = &s3c24xx_i2s_dai_ops,};

1.2.1 对于CPU IIS 的操作集s3c24xx_i2s_dai_ops:

static struct snd_soc_dai_ops s3c24xx_i2s_dai_ops = {.trigger= s3c24xx_i2s_trigger,.hw_params= s3c24xx_i2s_hw_params,.set_fmt= s3c24xx_i2s_set_fmt,.set_clkdiv= s3c24xx_i2s_set_clkdiv,.set_sysclk= s3c24xx_i2s_set_sysclk,};

snd_soc_codec_device
struct snd_soc_codec_device soc_codec_dev_uda134x = {.probe =        uda134x_soc_probe,  //初始化复位uda1341,装载指令操作结构uda1341_snd_controls.remove =       uda134x_soc_remove,.suspend =      uda134x_soc_suspend,.resume =       uda134x_soc_resume,};
codec_data
static struct uda134x_platform_data s3c24xx_uda134x = {.l3 = {.setdat = setdat, //设置数据线电平.setclk = setclk, //设置CLK电平.setmode = setmode, //设置MODE电平.data_hold = 1,.data_setup = 1,.clock_high = 1,.mode_hold = 1,.mode = 1,.mode_setup = 1,},};
这个结构体设置L3总线的发送数据方法。

综合以上,我们的工作就是完成snd_soc_device这个结构体,然后注册一个名为soc-audio的设备。内核一般都已经实现好了声卡驱动和平台驱动,我们只要修改声卡的控制口L3就可以了。

————————————————————————————————————————————————————————————————————————————


测试:
1. 确定内核里已经配置了sound\soc\s3c24xx\s3c2410-uda1341.c
-> Device Drivers
  -> Sound
    -> Advanced Linux Sound Architecture
      -> Advanced Linux Sound Architecture
        -> System on Chip audio support
        <*> I2S of the Samsung S3C24XX chips


2. make uImage
   使用新内核启动


3. ls -l /dev/dsp /dev/mixer
4. 播放:
   在WINDOWS PC里找一个wav文件,放到开发板根文件系统里
   cat Windows.wav > /dev/dsp
5. 录音:
   cat /dev/dsp > sound.bin  
   然后对着麦克风说话
   ctrl+c退出
   cat sound.bin > /dev/dsp  // 就可以听到录下的声音


使用madplay测试声卡:
1. 解压:
tar xzf libid3tag-0.15.1b.tar.gz  // 库
tar xzf libmad-0.15.1b.tar.gz     // 库
tar xzf madplay-0.15.2b.tar.gz    // APP


2. 编译 libid3tag-0.15.1b
mkdir tmp
cd libid3tag-0.15.1b
./configure --host=arm-linux --prefix=/work/drivers_and_test/21th_sound/app/tmp
make
make install


3. 编译 libmad-0.15.1b
cd libmad-0.15.1b
./configure --host=arm-linux --prefix=/work/drivers_and_test/21th_sound/app/tmp
make
make install


4. 编译madplay
cd madplay-0.15.2b/
./configure --host=arm-linux --prefix=/work/drivers_and_test/21th_sound/app/tmp LDFLAGS="-L/work/drivers_and_test/21th_sound/app/tmp/lib" CFLAGS="-I /work/drivers_and_test/21th_sound/app/tmp/include"
make
make install


5. 把tmp/bin/*  tmp/lib/*so* 复制到根文件系统:


6. 把一个mp3文件复制到根文件系统


7. madplay --tty-control /1.mp3 
   播放过程中不断按小键盘的减号("-")会降低音量
             不断按小键盘的加号("+")会降低音量







0 0