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

2023-12-12 00:59

本文主要是介绍学习笔记 --- LINUX ASoC声卡驱动接口分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

        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 
   播放过程中不断按小键盘的减号("-")会降低音量
             不断按小键盘的加号("+")会降低音量







这篇关于学习笔记 --- LINUX ASoC声卡驱动接口分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/482694

相关文章

基于Linux的ffmpeg python的关键帧抽取

《基于Linux的ffmpegpython的关键帧抽取》本文主要介绍了基于Linux的ffmpegpython的关键帧抽取,实现以按帧或时间间隔抽取关键帧,文中通过示例代码介绍的非常详细,对大家的学... 目录1.FFmpeg的环境配置1) 创建一个虚拟环境envjavascript2) ffmpeg-py

Linux脚本(shell)的使用方式

《Linux脚本(shell)的使用方式》:本文主要介绍Linux脚本(shell)的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录概述语法详解数学运算表达式Shell变量变量分类环境变量Shell内部变量自定义变量:定义、赋值自定义变量:引用、修改、删

Linux链表操作方式

《Linux链表操作方式》:本文主要介绍Linux链表操作方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、链表基础概念与内核链表优势二、内核链表结构与宏解析三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势六、典型应用场景七、调试技巧与

Go学习记录之runtime包深入解析

《Go学习记录之runtime包深入解析》Go语言runtime包管理运行时环境,涵盖goroutine调度、内存分配、垃圾回收、类型信息等核心功能,:本文主要介绍Go学习记录之runtime包的... 目录前言:一、runtime包内容学习1、作用:① Goroutine和并发控制:② 垃圾回收:③ 栈和

详解Linux中常见环境变量的特点与设置

《详解Linux中常见环境变量的特点与设置》环境变量是操作系统和用户设置的一些动态键值对,为运行的程序提供配置信息,理解环境变量对于系统管理、软件开发都很重要,下面小编就为大家详细介绍一下吧... 目录前言一、环境变量的概念二、常见的环境变量三、环境变量特点及其相关指令3.1 环境变量的全局性3.2、环境变

Linux系统中的firewall-offline-cmd详解(收藏版)

《Linux系统中的firewall-offline-cmd详解(收藏版)》firewall-offline-cmd是firewalld的一个命令行工具,专门设计用于在没有运行firewalld服务的... 目录主要用途基本语法选项1. 状态管理2. 区域管理3. 服务管理4. 端口管理5. ICMP 阻断

Linux实现线程同步的多种方式汇总

《Linux实现线程同步的多种方式汇总》本文详细介绍了Linux下线程同步的多种方法,包括互斥锁、自旋锁、信号量以及它们的使用示例,通过这些同步机制,可以解决线程安全问题,防止资源竞争导致的错误,示例... 目录什么是线程同步?一、互斥锁(单人洗手间规则)适用场景:特点:二、条件变量(咖啡厅取餐系统)工作流

Linux中修改Apache HTTP Server(httpd)默认端口的完整指南

《Linux中修改ApacheHTTPServer(httpd)默认端口的完整指南》ApacheHTTPServer(简称httpd)是Linux系统中最常用的Web服务器之一,本文将详细介绍如何... 目录一、修改 httpd 默认端口的步骤1. 查找 httpd 配置文件路径2. 编辑配置文件3. 保存

Linux使用scp进行远程目录文件复制的详细步骤和示例

《Linux使用scp进行远程目录文件复制的详细步骤和示例》在Linux系统中,scp(安全复制协议)是一个使用SSH(安全外壳协议)进行文件和目录安全传输的命令,它允许在远程主机之间复制文件和目录,... 目录1. 什么是scp?2. 语法3. 示例示例 1: 复制本地目录到远程主机示例 2: 复制远程主

Android学习总结之Java和kotlin区别超详细分析

《Android学习总结之Java和kotlin区别超详细分析》Java和Kotlin都是用于Android开发的编程语言,它们各自具有独特的特点和优势,:本文主要介绍Android学习总结之Ja... 目录一、空安全机制真题 1:Kotlin 如何解决 Java 的 NullPointerExceptio