学习笔记 --- 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 rm命令误操作的多场景防护方案与实践

《防止Linuxrm命令误操作的多场景防护方案与实践》在Linux系统中,rm命令是删除文件和目录的高效工具,但一旦误操作,如执行rm-rf/或rm-rf/*,极易导致系统数据灾难,本文针对不同场景... 目录引言理解 rm 命令及误操作风险rm 命令基础常见误操作案例防护方案使用 rm编程 别名及安全删除

Linux下MySQL数据库定时备份脚本与Crontab配置教学

《Linux下MySQL数据库定时备份脚本与Crontab配置教学》在生产环境中,数据库是核心资产之一,定期备份数据库可以有效防止意外数据丢失,本文将分享一份MySQL定时备份脚本,并讲解如何通过cr... 目录备份脚本详解脚本功能说明授权与可执行权限使用 Crontab 定时执行编辑 Crontab添加定

使用docker搭建嵌入式Linux开发环境

《使用docker搭建嵌入式Linux开发环境》本文主要介绍了使用docker搭建嵌入式Linux开发环境,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 目录1、前言2、安装docker3、编写容器管理脚本4、创建容器1、前言在日常开发全志、rk等不同

linux系统上安装JDK8全过程

《linux系统上安装JDK8全过程》文章介绍安装JDK的必要性及Linux下JDK8的安装步骤,包括卸载旧版本、下载解压、配置环境变量等,强调开发需JDK,运行可选JRE,现JDK已集成JRE... 目录为什么要安装jdk?1.查看linux系统是否有自带的jdk:2.下载jdk压缩包2.解压3.配置环境

Linux搭建ftp服务器的步骤

《Linux搭建ftp服务器的步骤》本文给大家分享Linux搭建ftp服务器的步骤,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录ftp搭建1:下载vsftpd工具2:下载客户端工具3:进入配置文件目录vsftpd.conf配置文件4:

Java+AI驱动实现PDF文件数据提取与解析

《Java+AI驱动实现PDF文件数据提取与解析》本文将和大家分享一套基于AI的体检报告智能评估方案,详细介绍从PDF上传、内容提取到AI分析、数据存储的全流程自动化实现方法,感兴趣的可以了解下... 目录一、核心流程:从上传到评估的完整链路二、第一步:解析 PDF,提取体检报告内容1. 引入依赖2. 封装

Linux实现查看某一端口是否开放

《Linux实现查看某一端口是否开放》文章介绍了三种检查端口6379是否开放的方法:通过lsof查看进程占用,用netstat区分TCP/UDP监听状态,以及用telnet测试远程连接可达性... 目录1、使用lsof 命令来查看端口是否开放2、使用netstat 命令来查看端口是否开放3、使用telnet

Linux系统管理与进程任务管理方式

《Linux系统管理与进程任务管理方式》本文系统讲解Linux管理核心技能,涵盖引导流程、服务控制(Systemd与GRUB2)、进程管理(前台/后台运行、工具使用)、计划任务(at/cron)及常用... 目录引言一、linux系统引导过程与服务控制1.1 系统引导的五个关键阶段1.2 GRUB2的进化优

Unity新手入门学习殿堂级知识详细讲解(图文)

《Unity新手入门学习殿堂级知识详细讲解(图文)》Unity是一款跨平台游戏引擎,支持2D/3D及VR/AR开发,核心功能模块包括图形、音频、物理等,通过可视化编辑器与脚本扩展实现开发,项目结构含A... 目录入门概述什么是 UnityUnity引擎基础认知编辑器核心操作Unity 编辑器项目模式分类工程

Linux查询服务器 IP 地址的命令详解

《Linux查询服务器IP地址的命令详解》在服务器管理和网络运维中,快速准确地获取服务器的IP地址是一项基本但至关重要的技能,下面我们来看看Linux中查询服务器IP的相关命令使用吧... 目录一、hostname 命令:简单高效的 IP 查询工具命令详解实际应用技巧注意事项二、ip 命令:新一代网络配置全