alsa 驱动介绍

article/2025/10/3 2:55:59

Machine
以装配有CS4270的一款android 智能电视的为例
/sound/soc/samsung/exynos.c


Platform
以Samsung cpu exynos4412为例
/sound/soc/samsung/


Codec
以wolfson的Codec芯片cs4270为例
/sound/soc/codecs/cs4270.c


ALSA 框架介绍


Alsa 太多太杂,很难整理的规整,只能看到哪里写到哪里
 

ASoC被分为Machine,Platform和Codec三大部件,

 

Platform驱动的主要作用是完成音频数据的管理,最终通过CPU的数字音频接口(DAI)把音频数据传送

给Codec进行处理,最终由Codec输出驱动耳机或者是喇叭的音信信号。在具体实现上,ASoC有把Platform驱动分为两个部分:snd_soc_platform_driver
和snd_soc_dai_driver。其中,platform_driver负责管理音频数据,把音频数据通过dma或其他操作传送至cpu dai中,dai_driver则主要完成cpu一侧的
dai的参数配置,同时也会通过一定的途径把必要的dma等参数与snd_soc_platform_driver进行交互。

 

Machine 是指某一款机器,可以是某款设备,某款开发板,又或者是某款智能手机,由此可以看出Machine几乎是不可重用的,每个Machine上的硬件实

现可能都不一样,CPU不一样,Codec不一样,音频的输入、输出设备也不一样,Machine为CPU、Codec、输入输出设备提供了一个载体。
Platform 一般是指某一个SoC平台,比如pxaxxx,s3cxxxx,omapxxx等等,与音频相关的通常包含该SoC中的时钟、DMA、I2S、PCM等等,只要指定了SoC,那么我们可以认为它会有一个对应的Platform,它只与SoC相关,与Machine无关,这样我们就可以把Platform抽象出来,使得同一款SoC不用做任何的改动,就可以用在不同的Machine中。实际上,把Platform认为是某个SoC更好理解。

 

Codec 字面上的意思就是编解码器,Codec里面包含了I2S接口、D/A、A/D、Mixer、PA(功放),通常包含多种输入(Mic、Line-in、I2S、PCM)和多个

输出(耳机、喇叭、听筒,Line-out),Codec和Platform一样,是可重用的部件,同一个Codec可以被不同的Machine使用。嵌入式Codec通常通过I2C对
内部的寄存器进行控制。

Machine驱动的初始化,codec和dai的注册,都会调用snd_soc_instantiate_cards()进行一次声卡和codec,dai,platform的匹配绑定过程,这里所说的
绑定,正如Machine驱动一文中所描述,就是通过3个全局链表,按名字进行匹配,把匹配的codec,dai和platform实例赋值给声卡每对dai的snd_soc_pcm_runtime变量中。一旦绑定成功,将会使得codec和dai驱动的probe回调被调用

alsa架构的数据交互,是通过对PCM设备的操作来完成的, PCM设备分成playback和capture两个stream, 每个stream底下有N个substream

alsa驱动最底层需要调试的有三块: DMA部分,IIS驱动部分,codec部分

 

\

 

 

 

\

 

\

 

IIS介绍

A)I2S有四根线,
1.串行时钟SCLK,也叫位时钟(BCLK),即对应数字音频的每一位数据,SCLK都有1个脉冲。SCLK的频率=2×采样频率×采样位数。
2. 帧时钟LRCK,(也称WS),用于切换左右声道的数据。LRCK为“1”表示正在传输的是右声道的数据,为“0”则表示正在传输的是左声道的数据。LRCK
的频率等于采样频率。
3.串行数据SDATA,就是用二进制补码表示的音频数据。
4.有时为了使系统间能够更好地同步,还需要另外传输一个信号MCLK,称为主时钟,也叫系统时钟(Sys Clock),是采样频率的256倍或384倍。

 

B)声音数据DAT一般在CLK的上升沿进行采样,有些DAC也是可以调的。每个声道里面可以容纳的CLK数必须多于数据的位数,多出来的时钟和数据DAC会丢弃不用,比如16bit采样的声音数据当一个声道是32个CLK且left-justify的时候,后面十六个时钟的数据会被DAC丢掉,不影响的。



C)I2S数据的格式分I2S, Left-justify, Right-justify。三种格式的区别在于声音数据与WS的对应关系:
1 . I2S模式DAT的MSB在WS变化后的第二个上升沿开始传输
2. Left-justify模式DAT的MSB在WS变化后的第一个上升沿开始传输
3. Right-justify模式DAT的LSB在WS即将变换到下一声道前的最后一个时钟传输


I2S部分涉及的几个频率:
* 输出采样频率 fs = 44.1KHz. (也有其它fs的音源, 但加了resampler后, 都变成44.1KHz输出了). 这是个关键频率.
* LRCLK, 就等于fs. (L/R声道信号)
* BCLK = 32倍fs = 1411.2KHz = 1.4112MHz. (bit clock). 2声道16bit, 故32倍fs. 若2声道24bit, 则48倍fs.
* MCLK是整个audio模块的工作频率, 通常选fs的256, 384, 512倍. 比如: 256倍fs = 11289.6KHz = 11.2896MHz.

从频率设置来说, MCLK是个主要频率, 它是整个audio模块的工作频率.

那么, 从软件来说要设置两个方面的寄存器: 一是该PLL从晶振频率如何得到PLLout频率(比如P/M/S/k). 二是PLLout如何分频得到audio部分的MCLK.


IIS驱动部分最重要的就是注册以下钩子函数,挂到了alsa驱动上

?

1

2

3

4

5

6

7

8

9

10

static const struct snd_soc_dai_ops samsung_i2s_dai_ops = {

    .trigger = i2s_trigger,

    .hw_params = i2s_hw_params,

    .set_fmt = i2s_set_fmt,

    .set_clkp = i2s_set_clkp,

    .set_sysclk = i2s_set_sysclk,

    .startup = i2s_startup,

    .shutdown = i2s_shutdown,

    .delay = i2s_delay,

};




codec芯片介绍
cs4270的驱动要设置的参数有:
静音,传输模式,比特位长度,时钟主从模式,音量大小
cs4270驱动里面定义了snd_soc_dai_driver结构成员,里面定义了playback和capture两个substream,同事也挂了一个snd_soc_dai_ops结构体,里面全
是操作函数指针。
alsa上面一层层的最终会调用到这些指针
 

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

static const struct snd_soc_dai_ops cs4270_dai_ops = {

    .hw_params  = cs4270_hw_params,

    .set_sysclk = cs4270_set_dai_sysclk,

    .set_fmt    = cs4270_set_dai_fmt,

    .digital_mute   = cs4270_dai_mute,

};

 

 

static struct snd_soc_dai_driver cs4270_dai = {

    .name = "cs4270-hifi",

    .playback = {

        .stream_name = "Playback",

        .channels_min = 1,

        .channels_max = 2,

        .rates = SNDRV_PCM_RATE_CONTINUOUS,

        .rate_min = 4000,

        .rate_max = 216000,

        .formats = CS4270_FORMATS,

    },

    .capture = {

        .stream_name = "Capture",

        .channels_min = 1,

        .channels_max = 2,

        .rates = SNDRV_PCM_RATE_CONTINUOUS,

        .rate_min = 4000,

        .rate_max = 216000,

        .formats = CS4270_FORMATS,

    },

    .ops = &cs4270_dai_ops,

};





DMA介绍

IIS总线是慢速总线,相对于CPU来说,太慢。所以采用DMA的方式最能节省CPU性能。
PCM playback的时候,DMA目的地址是IIS FIFO寄存器。源地址是存放PCM数据的内存。
DMA的驱动采用了linux pl330的驱动架构,采用中断的方式来触发后续DMA。
IIS中通过DMA的方式写入FIFO寄存器,在DMA的驱动中挂接了一个回调函数audio_buffdone。DMA完成后,回函数调用,刷新alsa的环,便于下一次DMA
DMA的目的地址,就是IIS发送寄存器的地址。源地址,就是申请的DMA buffer,只不过DMAbuffer被映射成了一个环
 

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

static void dma_enqueue(struct snd_pcm_substream *substream)

{

    struct runtime_data *prtd = substream->runtime->private_data;

    dma_addr_t pos = prtd->dma_pos;

    unsigned int limit;

    struct samsung_dma_prep dma_info;

 

 

    pr_debug("Entered %s\n", __func__);

 

 

    limit = (prtd->dma_end - prtd->dma_start) / prtd->dma_period;

 

 

    pr_debug("%s: loaded %d, limit %d\n",

                __func__, prtd->dma_loaded, limit);

 

 

    dma_info.cap = (samsung_dma_has_circular() ? DMA_CYCLIC : DMA_SLAVE);

    dma_info.direction =

        (substream->stream == SNDRV_PCM_STREAM_PLAYBACK

        ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM);

    dma_info.fp = audio_buffdone;   //回调函数

    dma_info.fp_param = substream;

    dma_info.period = prtd->dma_period;

    dma_info.len = prtd->dma_period*limit;

 

 

    while (prtd->dma_loaded < limit) {

        pr_debug("dma_loaded: %d\n", prtd->dma_loaded);

 

 

        if ((pos + dma_info.period) > prtd->dma_end) {

            dma_info.period  = prtd->dma_end - pos;

            pr_debug("%s: corrected dma len %ld\n",

                    __func__, dma_info.period);

        }

 

 

        dma_info.buf = pos;

        prtd->params->ops->prepare(prtd->params->ch, &dma_info); //DMA注册

 

 

        prtd->dma_loaded++;

        pos += prtd->dma_period;

        if (pos >= prtd->dma_end)

            pos = prtd->dma_start;

    }

 

 

    prtd->dma_pos = pos;

}

static void audio_buffdone(void *data)

{

    struct snd_pcm_substream *substream = data;

    struct runtime_data *prtd = substream->runtime->private_data;

 

 

    pr_debug("Entered %s\n", __func__);

 

 

    if (prtd->state & ST_RUNNING) {

        prtd->dma_pos += prtd->dma_period;

        if (prtd->dma_pos >= prtd->dma_end)

            prtd->dma_pos = prtd->dma_start;

 

 

        if (substream)

            snd_pcm_period_elapsed(substream);

 

 

        spin_lock(&prtd->lock);

        if (!samsung_dma_has_circular()) {

            prtd->dma_loaded--;

            dma_enqueue(substream);

        }

        spin_unlock(&prtd->lock);

    }

}




DMA部分主要通过注册以下钩子函数来挂到alsa驱动里面
 

?

1

2

3

4

5

6

7

8

9

10

11

static struct snd_pcm_ops dma_ops = {

    .open       = dma_open,

    .close      = dma_close,

    .ioctl      = snd_pcm_lib_ioctl,

    .hw_params  = dma_hw_params,

    .hw_free    = dma_hw_free,

    .prepare    = dma_prepare,

    .trigger    = dma_trigger,

    .pointer    = dma_pointer,

    .mmap       = dma_mmap,

};



alsa数据读写简介

播放时,应用程序把音频数据源源不断地写入dma buffer中,然后相应platform的dma操作则不停地从该buffer中取出数据,经dai送往codec中。录音时
则正好相反,codec源源不断地把A/D转换好的音频数据经过dai送入dma buffer中,而应用程序则不断地从该buffer中读走音频数据

以播放(playback)为例,我现在知道至少有3个途径可以完成对dma buffer的写入:
应用程序调用alsa-lib的snd_pcm_writei、snd_pcm_writen函数;
应用程序使用ioctl:SNDRV_PCM_IOCTL_WRITEI_FRAMES或SNDRV_PCM_IOCTL_WRITEN_FRAMES;
应用程序使用alsa-lib的snd_pcm_mmap_begin/snd_pcm_mmap_commit;

以上几种方式最终把数据写入dma buffer中,然后修改runtime->control->appl_ptr的值。
播放过程中,通常会配置成每一个period size生成一个dma中断,中断处理函数最重要的任务就是:
更新dma的硬件的当前位置,该数值通常保存在runtime->private_data中;
调用snd_pcm_period_elapsed函数,该函数会进一步调用snd_pcm_update_hw_ptr0函数更新上述所说的4个缓冲区管理字段,然后唤醒相应的等待进程;
这个中断实际上在DMA驱动内部,给DMA驱动一个回调函数就可以了。就是我们前面说的audio_buffdone

Playback时数据流向

/sound/soc/samsung/里面,写入到DMA源buffer时,
用的是mmap的写入方式,不是采用的snd_pcm_hw_writei

几个关键点:
1,Pos计算方式
dma_pointer里面, res = prtd->dma_pos - prtd->dma_start;
此pos就是在DMA 源buffer中的位置
2,dma的初始化
dma_enqueue里面,把DMA源buffer切成period_size大小,挂到DMA队列里
每次period_size传输完了,就会调用audio_buffdone终端处理函数,更新dma_pos
同时audio_buffdone也会调用snd_pcm_update_hw_ptr0重新计算hw_ptr,从而计算是不是有足够的可用空间,来唤醒等待的poll

从alsalib来看,要先调用snd_pcm_start,触发DMA操作开始
snd_pcm_start---SNDRV_PCM_IOCTL_START---snd_pcm_action_lock_irq--snd_pcm_do_start----dma_trigger
每次写入mmap buffer之前,要先snd_pcm_wait,等待有足够可用的空间.
snd_pcm_wait----snd_pcm_wait_nocheck----poll-----snd_pcm_playback_poll---poll_wait
然后调用snd_pcm_mmap_begin获取mmap 内存
然后写入,然后调用snd_pcm_mmap_commit做一下alsalib和驱动里面的环同步

DMA实际上是在不停的DMA的。有空闲的了,上层就不用wait了,就会写入了

\

几个典型调用流程

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

设置hw_param参数时,调用流程

snd_pcm_hw_params

_snd_pcm_hw_params

pcm->ops->hw_params----snd_pcm_hw_hw_params

SNDRV_PCM_IOCTL_HW_PARAMS

 

 

snd_pcm_common_ioctl1

snd_pcm_hw_params_user

snd_pcm_hw_params

substream->ops->hw_params

soc_pcm_hw_params

codec_dai->driver->ops->hw_params

cpu_dai->driver->ops->hw_params

cs4270_hw_params

 

 

 

设置mixer 参数时,volume为例,调用流程

snd_mixer_selem_set_playback_volume_all            

snd_mixer_selem_set_playback_volume

set_volume_ops

_snd_mixer_selem_set_volume---selem_write

selem_write_main

elem_write_volume

snd_hctl_elem_write

snd_ctl_elem_write

snd_ctl_hw_elem_write

 

 

snd_ctl_elem_write_user

snd_ctl_elem_write

snd_soc_put_volsw

snd_soc_update_bits_locked

regmap_update_bits_check

 

 

IIS clk 设置流程

 

 

snd_pcm_common_ioctl1

snd_pcm_hw_params_user

snd_pcm_hw_params

substream->ops->hw_params

soc_pcm_hw_params

rtd->dai_link->ops->hw_params

smdk_wm8994_pcm_hw_params

snd_soc_dai_set_sysclk

dai->driver->ops->set_sysclk

i2s_set_sysclk

转自:https://www.2cto.com/kf/201606/520305.html

 


http://chatgpt.dhexx.cn/article/JoBDiDdS.shtml

相关文章

ALSA Configure

0. 前言 本文主要介绍alsa-lib配置文件相关代码的分析内容。 1. 配置文件的路径 在alsa-lib中&#xff0c;函数 snd_config_topdir 用于获取配置文件的路径&#xff0c;有两个方法可以进行配置&#xff1a; 使用环境变量 ALSA_CONFIG_DIR 进行配置。在生成configure时&…

ALSA应用层编程播放音乐

关于ALSA&#xff0c;网上也有介绍&#xff0c;但是我在看的时候看的也是一脸懵逼&#xff0c;不是介绍的不好&#xff0c;是因为我之前对于嵌入式软件这一块实在没什么了解&#xff0c;之前一直学的JAVA&#xff0c;整个体系跟JAVA还是有很大的区别&#xff0c;要学的也完全是…

ALSA系统简析

一 音频架构 如图所示 是 嵌入式系统的音频连接 音频编解码器将数字音频信号 转换成 扬声器播放所需要的模拟声音信号。而通过麦克风时&#xff0c;则执行相反的过程。 数字音频信号通过 PCM技术对模拟信号以某个比特率采样得到的&#xff0c;编解码器的任务就是以支持的PCM…

ALSA编程精华

https://www.cnblogs.com/cslunatic/p/3677729.html 一、前序 这里了解一下各个参数的含义以及一些基本概念。 声音是连续模拟量&#xff0c;计算机将它离散化之后用数字表示&#xff0c;就有了以下几个名词术语。 样本长度(sample)&#xff1a;样本是记录音频数据最基本的…

ALSA学习笔记

文章目录 一、概述二、系统架构三、常用操作命令1、安装ALSA2、查看音频设备3、列出音频设备4、音量控制器 四、常见问题1、cannot open mixer: 没有那个文件或目录 一、概述 ALSA&#xff08;Advanced Linux Sound Architecture&#xff09;&#xff0c;高级Linux声音架构的简…

音频ALSA架构简介

一、ALSA架构 ALSA&#xff08;Advanced Linux Sound Architecture&#xff09;即高级 Linux 声音架构。 嵌入式移动设备的音频子系统目前主要是ALSA 驱动 asoc 框架&#xff0c;其中包括 codec driver、 platform driver、 machine driver 等。 codec driver只关心 codec 本身…

Linux ALSA 之一:ALSA 架构简介

一、概述 ALSA是 Advanced Linux Sound Architecture 的缩写&#xff0c;目前已经成为了linux的主流音频体系结构。 在 Linux 内核设备驱动层&#xff0c;ALSA 提供了 alsa-driver&#xff0c;在应用层&#xff0c;ALSA 为我们提供了 alsa-lib&#xff0c;故在其支持下&#…

ALSA (高级Linux声音架构)、ASOC基础知识

目录 第一节&#xff1a;什么是ALSA和ASOC 第二节&#xff1a;ALSA框架 第三节&#xff1a;ALSA的使用 第四节&#xff1a;ASOC的硬件框架 第四节&#xff1a;ASOC的软件框架 第一节&#xff1a;什么是ALSA和ASOC ALSA是Advanced Linux Sound Architecture&#xff0c;高级…

动态链接库dlopen的函数的使用

转自&#xff1a;http://blog.const.net.cn/a/17154.htm 编译时候要加入 -ldl (指定dl库) dlopen 基本定义 功能&#xff1a;打开一个动态链接库 [喝小酒的网摘]http://blog.const.net.cn/a/17154.htm 包含头文件&#xff1a; #include <dlfcn.h> 函数定义&#xff…

dlopen的用法

1、前言 为了使程序方便扩展&#xff0c;具备通用性&#xff0c;可以采用插件形式。采用异步事件驱动模型&#xff0c;保证主程序逻辑不变&#xff0c;将各个业务已动态链接库的形式加载进来&#xff0c;这就是所谓的插件。linux提供了加载和处理动态链接库的系统调用&#xff…

织梦上一篇下一篇没有了改为英文

织梦上一篇下一篇没有了改为英文 网站根目录找到 include/arc.archives.class.php 文件 打开找到 上一篇改为 Previous上一篇后面的“没有了” 改为 No Previous原图修改后 接着放下翻&#xff0c;紧贴着下面的“下一篇&#xff0c;没有了” 找到 下一篇改为 在这里插入…

在wordpress文章页中显示上一篇和下一篇文章

查看原文&#xff1a;http://www.hellonet8.com/1162.html 今天博主在看别人博客的时候发现他们的文章末尾会有显示上一篇和下一篇的文章链接&#xff0c;所以也想在自己的博客中添加这个功能。这么做顺便可以增加文章之间的相关性&#xff0c;对搜索引擎的蜘蛛也会友好些。废话…

js 实现 点击上一篇、下一篇功能

列表界面&#xff1a; 详细界面&#xff1a; 思路&#xff1a; 1. 首先目录列表渲染的数据是通过接口调用取到的值&#xff0c;然后点击具体某一条数据的时候&#xff0c;获取到他的 ID&#xff0c;然后通过路由跳转的时候带到详细信息页面。 2. 在详细页面中&#xff0c;先再…

Vue中 实现上一篇下一篇的功能

效果&#xff1a; 看下html页面 <div class"NewsDetails_cont_footer"><!-- 使用三目运算符判断 按钮是否可以点击 --><div click"last" :class"lastNoShow ? noClick : btn"><img src"../assets/img/newsDetail/公共…

java实现上一篇下一篇功能

根据文章类型查询&#xff0c;实现上一篇、下一篇的效果 自定义实体Dto(这里只放出扩展字段) Getter Setter public class OsArticleDto extends BaseDto {/** */private static final long serialVersionUID 1L;/** 上一篇文章id*/private String beforeId;/*** 上一篇文章…

vue实现上一篇下一篇

先来看一下效果图 请求回来所有文章&#xff0c;根据索引进行上一篇下一篇的判断 首先为两个按钮绑定点击事件 <buttonclick"last(lastId, columnId)":disabled"isLast":class"{ disClick: isLast true }">上一篇:{{ lastTitle }}</b…

Django针对上一篇和下一篇文章标题的实现逻辑

1、要求显示效果 2、前端html内容 <div><nav aria-label"..."><ul class"pager"><li><a href"/blog/detail/{{ previous_article.article_id }}">上一篇&#xff1a;{{ previous_article.title }}</a><…

程序员,我们应该如何去学习

IT技术的发展日新月异&#xff0c;新技术层出不穷&#xff0c;具有良好的学习能力&#xff0c;能及时获取新知识、随时补充和丰富自己&#xff0c;已成为程序员职业发展的核心竞争力。本文中&#xff0c;作者结合多年的学习经验总结出了提高程序员学习能力的三个要点。 众所周知…

程序员该如何学习新知识

想必大家都不是张无忌&#xff0c;人家三十年才可以练成的乾坤大挪移&#xff0c;张无忌大侠两个时辰就可以搞定&#xff0c;作为一个普通的程序员&#xff0c;经常遇到很多新技术和新知识&#xff0c;it界就是这样&#xff0c;日新月异&#xff0c; 那么我们如何学习一门技术和…

@程序员,这四个学习建议值得收藏

在我看来&#xff0c;学习能力应该是一个人最重要的能力之一。因为我们赖以生存的所有技能&#xff0c;无一例外都是通过学习获得的。那些优秀的人&#xff0c;也不过是学习能力或者学习效率比一般人强而已。 这样的观点被很多人论证过&#xff0c;商业理论家阿里德赫斯&#…