Linux音频(1):alsa架构和RK3588 PCM实例

《Linux Sound Subsystem Documentation》对Kernel ALSA进行了详细介绍:Designs and Implementations介绍了ALSA的一些设计和实现;The ALSA Driver API分类介绍了ALSA API,然后Writing an ALSA Driver介绍了如何编写一个ALSA驱动,同时Advanced Linux Sound Architecture介绍了如何对驱动进行配置;对于嵌入式设备,给出了分层依据,以及不同层级驱动实现介绍。

ALSA是Linux Kernel下用于管理音频设备和音频的软件架构,提供了标准接口,用于应用程序和硬件设备之间的音频通信,以及音频设备之间的通信。

整个Linux下ALSA相关软件架构可以分为两部分:

用户空间

alsa-lib将对ALSA设备操作进行封装。

alsa-utils以及ALSA相关应用,对ALSA设备进行操作。

pulseaudio作为Daemon程序,对Card进行配置。

内核空间

ALSA Core提供通用的Card、PCM、Control、DAI、DAPM等模块注册,以及调试接口。

ASoC驱动,包括:Codec驱动、Platform驱动、Machine驱动。

1 RK3588 Audio相关硬件架构

RK3588 Audio相关硬件配置如下:

SoC

Audio Codec

VAD(Voice Activity Detect):在低功耗模式下,检测音频输入用于唤醒SoC。

I2S/SPDIF等接口。

耳机插拔通过中断通知SoC。

Codec:外接Codec芯片,I2C进行控制,I2S收发数据。

Class D Amplifier:Speaker D类功放,通过GPIO进行开关。

通过I2C对Codec进行配置,数据通过I2S在Codec和SoC之间传输。

编写一个SoC ALSA解决方案的主要工作包括:

Platform(DAI I2S/PCM)驱动、Codec驱动、Machine驱动编写。

Platform/Codec/Machine相关的DAPM Widget、Route Path、Control编写。

pulseaudio的conf配置文件编写。

音频效果调试、时序优化、Pop/Click声消除等。

2 ALSA内核配置

Device Drivers   ->Sound card support
    ->Advanced Linux Sound Architecture
      Enable OSS Emulation
      PCM timer interface
      HR-timer backend support
      Dynamic device file minor numbers
        Max number of sound cards
      Sound Proc FS Support
        Verbose procfs contents
      Verbose printk--在printk里面显示printk对应的文件和行号等信息。
      Debug--通过/sys/module/snd/parameters/debug配置调试信息输出。
      Sequencer support
        Sequencer dummy client
        Use HR-timer as default sequencer timer
      Generic sound devices
      PCI sound devices
      HD-Audio
      Pre-allocated buffer size for HD-audio driver
      SPI sound devices
      USB sound devices
        USB Audio/MIDI driver--支持基于USB的音频设备。
      ALSA for SoC audio support
        ASoC support for Rockchip
          Rockchip Digital Loopback Driver
          Rockchip I2S Device Driver
          Rockchip I2S/TDM Device Driver
          Rockchip Multi-DAIS Device Driver
          Rockchip PDM Controller Driver
          Rockchip SAI Controller Driver
          Rockchip SPDIF Device Driver
          Rockchip SPDIFRX Device Driver
          Rockchip Voice Activity Detection Driver
          ASoC support for Rockchip boards using a MAX98090 codec
          ASoC support for Rockchip multicodecs
          ASoC support for Rockchip HDMI audio
          CODEC drivers
            Everest Semi ES8323 CODEC
          ASoC Simple sound card support

3 ALSA驱动文件 4 ALSA子系统 4.1 ASOC划分

ALSA SoC将嵌入式音频系统分为多种可复用的component驱动:

Platform类驱动:Audio DMA、DAI、Audio DSP。

DAI驱动(AC97/I2S/PCM)

DAI描述。

DAI配置。

PCM描述。

sysclk配置。

suspend和resume。

DSP驱动

DAPM graph。

Mixer control。

DMA IO读写DSP buffer。

Machine类驱动:描述和绑定其他Component的胶水层,处理Machine相关Control和Machine层级的音频事件。

4.2 DAPM和Endpoint Widget

DAPM可以划分为4个电源域:Codec bias domain,Platform/Machine domain,Path domain,Stream domain。

:Codec的输入引脚。

:Codec的输出引脚。

:Microphone。

:Headphone。

:Speaker。

:Line 输入或输出。

SND_SOC_DAPM_SIGGEN:信号发生器。

:可编程增益放大器或衰减小部件。

:将多个模拟信号混合到一个模拟信号。

:开关切换。

:切换选择多路输入中的一路作为输出。

:PRE widget。

:POST widget。

:AIF输入。

:AIF输出。

:DAC。

:ADC。

SND_SOC_DAPM_CLOCK_SUPPLY:时钟提供。

:寄存器。

:时钟或电源提供。

SND_SOC_DAPM_REGULATOR_SUPPLY:电源提供。

4.3 子系统初始化

alsa_sound_init()注册Sound Card字符设备序号,并创建一系列procfs:

alsa_sound_init
  register_chrdev--注册CONFIG_SND_MAJOR类型字符设备,操作函数集为snd_fops。
  snd_info_init
    snd_info_create_entry--创建/proc/asound目录。
    create_subdir--创建/proc/asound/seq子目录。
    snd_info_version_init--创建/proc/asound/version,显示alsa版本号。
    snd_minor_info_init--创建/proc/asound/devices,显示所有主设备号为CONFIG_SND_MAJOR的设备。
    snd_card_info_init--创建/proc/asound/cards,显示当前注册的Sound Card。
    snd_info_minor_register

alsa_seq_device_init()创建Seq Bus,并创建响应的procfs:

alsa_seq_device_init
  bus_register--注册snd_seq_bus_type总线。
  seq_dev_proc_init--创建/proc/asound/seq/drivers。

类型设备提供了给用户空间对硬件设备进行控制和更新固件的手段:

alsa_hwdep_init   snd_hwdep_proc_init--创建/proc/asound/hwdep节点。
  snd_ctl_register_ioctl/snd_ctl_register_ioctl_compat--增加hwdep类型Control的ioctl命令处理。

alsa_timer_init()创建ALSA timer设备和procfs:

alsa_timer_init
  snd_device_initialize
  snd_timer_register_system
  snd_register_device--创建/dev/snd/timers设备,操作函数集为snd_timer_f_ops。
  snd_timer_proc_init--创建/proc/asound/timers节点,显示系统创建的ALSA相关timer。

alsa_pcm_init()注册PCM Control相关ioctl命令处理,并创建pcm相关procfs:

alsa_pcm_init
  snd_ctl_register_ioctl/snd_ctl_register_ioctl_compat--注册PCM Control相关ioctl命令处理。
  snd_pcm_proc_init--创建/proc/asound/pcm节点,查看当前PCM设备列表。

alsa_rawmidi_init()注册midi相关ioctl命令处理:

alsa_rawmidi_init
  snd_ctl_unregister_ioctl/snd_ctl_unregister_ioctl_compat--注册midi相关ioctl命令处理。

alsa_seq_init()注册Seq设备、创建Seq相关proc接口等。

init_soundcore()创建sound类:

init_soundcore
  init_oss_soundcore--
  class_create--创建sound_class类。

snd_hrtimer_init()创建名称为HR timer的ALSA timer。

alsa_sound_last_init()遍历所有已注册的Sound Card,增加引用计数。

snd_soc_init()创建asoc的debugfs目录及节点等:

snd_soc_init
  snd_soc_debugfs_init--创建/sys/kernel/debug/asoc目录,以及dais和components两个节点。components遍历component_list列表;dais遍历每个component下的dai列表。
  snd_soc_util_init--注册"snd-soc-dummy"设备。
    soc_dummy_driver
      snd_soc_dummy_probe
        devm_snd_soc_register_component--注册"snd-soc-dummy-dai"等。
  soc_driver
    soc_probe
      devm_snd_soc_register_card
      snd_register_device

5 ALSA数据结构和API

The ALSA Driver API将ALSA API进行分类,详细如下:

devm_snd_soc_register_component()注册一个带资源管理的Component到ALSA子系统:

devm_snd_soc_register_component
  snd_soc_register_component
    snd_soc_component_initialize
    snd_soc_add_component
      snd_soc_register_dais
        snd_soc_register_dai
          list_add_tail--component->dai_list
      list_add--component_list

devm_snd_soc_register_card()注册一个带资源管理的ALSA Sound Card:

devm_snd_soc_register_card
  snd_soc_register_card
    snd_soc_bind_card
      snd_soc_dapm_init--snd_soc_card.dapm初始化。
      soc_check_tplg_fes
      soc_bind_aux_dev--绑定snd_soc_aux_dev设备。
        snd_soc_component_set_aux
      snd_soc_add_pcm_runtime
        ->soc_new_pcm_runtime
          ->device_add_groups--为当前设备创建dapm_widget(显示DAPM Widget的On/Off状态)和pmdown_time属性。
      snd_card_new
        --分配struct snd_card,并初始化。
        --snd_ctl_create--创建/dev/snd/controlCx,即Cardx的Control节点。
          --snd_device_initialize
          --dev_set_name--设置设备名称为controlCx。
          --snd_device_new--创建ALSA设备Component,操作函数中注册成员snd_ctl_dev_register负责创建设备。
            --snd_ctl_dev_register
              --snd_register_device--设备对应的操作函数集为snd_ctl_f_ops
        --snd_info_card_create
          --create_subdir--创建/proc/asound/cardX目录。
          --snd_card_ro_proc_new--创建/proc/asound/cardX/id节点。
      --soc_init_card_debugfs--为每个snd_soc_card创建debugfs。
        --debugfs_create_u32--创建dapm_pop_time节点。
        --snd_soc_dapm_debugfs_init--创建dapm目录,以及bias_level节点。
      --soc_resume_init
      --snd_soc_dapm_new_controls--为snd_soc_card的dapm_widgets和of_dapm_widgets创建Controls。
      --snd_soc_card_probe--调用snd_soc_card的probe函数。
      --soc_probe_link_components--探测当前Card上DAI连接所使用的所有Component。
        --soc_probe_component
          --snd_soc_component_module_get_when_probe
          --soc_set_name_prefix
          --soc_init_component_debugfs--
          --snd_soc_dapm_init--初始化struct snd_soc_dapm_context结构体,将card和component关联起来。
          --snd_soc_dapm_new_controls--创建component所属的widget。
          --snd_soc_dapm_new_dai_widgets--创建DAI的Playback/Capture Widget。
          --snd_soc_component_probe--调用component的probe函数。
          --snd_soc_component_init--调用component的init函数。
          --snd_soc_add_component_controls--给当前Component添加一系列Controls。
          --snd_soc_dapm_add_routes--创建两个DAPM widget之间的路由。
      soc_probe_aux_devices--为Aux设备创建Component。
      soc_probe_link_dais
        snd_soc_pcm_dai_probe
      soc_init_pcm_runtime
        snd_soc_link_init
        soc_dpcm_debugfs_add
        soc_new_pcm--创建snc_pcm设备。
          snd_pcm_new
            _snd_pcm_new
              snd_pcm_new_stream--创建Playback和Capture两个Stream。
              snd_device_new--创建PCM类型的Sound设备,调用snd_pcm_dev_register创建设备。
                snd_pcm_dev_register
                  snd_register_device--创建/dev/snd/pcmCxDxp和/dev/snd/pcmCxDxc设备。操作函数集为snd_pcm_f_ops
          snd_soc_pcm_component_new
        snd_soc_pcm_dai_new
      snd_soc_dapm_link_dai_widgets
        snd_soc_dapm_add_path--创建Source和Sink之间的连接通路。
      snd_soc_dapm_connect_dai_link_widgets
      snd_soc_add_card_controls--根据snd_soc_card的controls创建Controls。
      snd_soc_dapm_add_routes--根据snd_soc_card的dapm_routes和of_dapm_routes创建路由。
      snd_soc_set_dmi_name
      soc_setup_card_name
      snd_component_add
      snd_soc_card_late_probe--调用snd_soc_card的late_probe函数。
      snd_soc_dapm_new_widgets--根据snd_soc_card的widgets列表,创建。
      snd_card_register--注册snd_card设备,并创建proc节点。
        snd_device_register_all
          __snd_device_register
        snd_info_card_register
          snd_info_register
          proc_symlink--创建到Card的链接。
      dapm_mark_endpoints_dirty
      snd_soc_dapm_sync

6 ALSA驱动

Component是对一系列功能的抽象,Component包含多个Widget。一个Control是对一个控制功能的抽象。Route是不同Widget之间的路由。

6.1 Platform class driver: DAI I2S

RK3588的DAI使用i2s,DTS配置如下:

i2s0_8ch: i2s@fe470000 { compatible = "rockchip,rk3588-i2s-tdm"; reg = <0x0 0xfe470000 0x0 0x1000>; interrupts = <GIC_SPI 180 IRQ_TYPE_LEVEL_HIGH>; clocks = <&cru MCLK_I2S0_8CH_TX>, <&cru MCLK_I2S0_8CH_RX>, <&cru HCLK_I2S0_8CH>; clock-names = "mclk_tx", "mclk_rx", "hclk"; assigned-clocks = <&cru CLK_I2S0_8CH_TX_SRC>, <&cru CLK_I2S0_8CH_RX_SRC>; assigned-clock-parents = <&cru PLL_AUPLL>, <&cru PLL_AUPLL>; dmas = <&dmac0 0>, <&dmac0 1>; dma-names = "tx", "rx"; power-domains = <&power RK3588_PD_AUDIO>; resets = <&cru SRST_M_I2S0_8CH_TX>, <&cru SRST_M_I2S0_8CH_RX>; reset-names = "tx-m", "rx-m"; rockchip,clk-trcm = <1>; pinctrl-names = "default"; pinctrl-0 = <&i2s0_lrck &i2s0_sclk &i2s0_sdi0 &i2s0_sdi1 &i2s0_sdi2 &i2s0_sdi3 &i2s0_sdo0 &i2s0_sdo1 &i2s0_sdo2 &i2s0_sdo3>; #sound-dai-cells = <0>; status = "disabled"; };

rockchip_i2s_tdm_driver作为DAI驱动,做如下工作:

解析dts,处理寄存器映射、中断、时钟、复位、pinctrl等。

注册中断处理函数rockchip_i2s_tdm_isr。

将I2S TDM作为component注册到ALSA子系统。

PCM所使用的rx/tx DMA引擎注册。

rockchip_i2s_tdm_driver
  rockchip_i2s_tdm_probe
    rockchip_i2s_tdm_dai_prepare--分配一个初始化好的的struct snd_soc_dai_driver结构体。
    devm_platform_get_and_ioremap_resource
    devm_regmap_init_mmio
    platform_get_irq_optional
    devm_request_irq--如有中断,则注册处理函数。
      rockchip_i2s_tdm_isr
    rockchip_i2s_tdm_tx_path_prepare
    rockchip_i2s_tdm_rx_path_prepare
    devm_snd_soc_register_component--component和dai驱动分别为rockchip_i2s_tdm_componentrockchip_i2s_tdm_dai
      rockchip_i2s_tdm_snd_controls--创建"I2STDM Digital Loopback Mode",控制I2S传输模式。
      rockchip_i2s_tdm_dai_ops--I2S TDM接口操作函数。
    devm_snd_dmaengine_pcm_register

I2S TDM Component的Controls为:

static const struct snd_kcontrol_new rockchip_i2s_tdm_snd_controls[] = { SOC_ENUM_EXT("I2STDM Digital Loopback Mode", loopback_mode, rockchip_i2s_tdm_loopback_get, rockchip_i2s_tdm_loopback_put),--支持4种模式:"Disabled", "Mode1", "Mode2", "Mode2 Swap"。 };

I2S DAI驱动为:

struct snd_soc_dai_driver rockchip_i2s_tdm_dai = { .probe = rockchip_i2s_tdm_dai_probe, .playback = { .stream_name = "Playback", .channels_min = 2, .channels_max = 16, .rates = SNDRV_PCM_RATE_8000_192000, .formats = (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE), }, .capture = { .stream_name = "Capture", .channels_min = 2, .channels_max = 16, .rates = SNDRV_PCM_RATE_8000_192000, .formats = (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE), }, .ops = &rockchip_i2s_tdm_dai_ops, }; static const struct snd_soc_dai_ops rockchip_i2s_tdm_dai_ops = { .startup = rockchip_i2s_tdm_startup, .shutdown = rockchip_i2s_tdm_shutdown, .hw_params = rockchip_i2s_tdm_hw_params, .set_sysclk = rockchip_i2s_tdm_set_sysclk, .set_fmt = rockchip_i2s_tdm_set_fmt, .set_tdm_slot = rockchip_dai_tdm_slot, .trigger = rockchip_i2s_tdm_trigger, };

Platform相关的DAPM Widget、Control、Route:

DAPM Widget:Capture和Playback。

Control:rockchip_i2s_tdm_snd_controls。

Route:。

6.2 Codec class driver: ES8388

ES8388的框架图如下:

ALC:自动电平控制(Automatic Level Control,ALC),是针对由于器件本身变化,环境引起工作点变化等,在电路中加入的稳定电平的电路,在一定范围内,ALC电路自动纠正偏移的电平回到要求的数值。

使用ES8388作为Codec作为I2C Client:

&i2c7 { status = "okay"; es8388: es8388@11 { status = "okay"; #sound-dai-cells = <0>; compatible = "everest,es8388", "everest,es8323"; reg = <0x11>; clocks = <&cru I2S0_8CH_MCLKOUT>; clock-names = "mclk"; assigned-clocks = <&cru I2S0_8CH_MCLKOUT>; assigned-clock-rates = <12288000>; pinctrl-names = "default"; pinctrl-0 = <&i2s0_mclk>; }; };

es8323_i2c_probe()初始化ES8388,做如下工作:

使用I2C作为Regmap接口。

通过I2C接口读取ES8388作为测试。

将ES8388作为Component注册到ALSA子系统。

es8323_i2c_driver
  es8323_i2c_probe
    devm_regmap_init_i2c--将i2c注册为regmap设备。
    i2c_set_clientdata--分配一个es8323_priv结构体作为i2c_client的私有数据。
    devm_snd_soc_register_component--注册一个compoment,当设备被注销时component也同样会被注销。

Component对应的驱动函数集为soc_codec_dev_es8323:

static const struct snd_soc_component_driver soc_codec_dev_es8323 = { .probe = es8323_probe, .remove = es8323_remove, .suspend = es8323_suspend, .resume = es8323_resume, .set_bias_level = es8323_set_bias_level, .dapm_widgets = es8323_dapm_widgets, .num_dapm_widgets = ARRAY_SIZE(es8323_dapm_widgets), .dapm_routes = audio_map, .num_dapm_routes = ARRAY_SIZE(audio_map), .controls = es8323_snd_controls, .num_controls = ARRAY_SIZE(es8323_snd_controls), }; static const struct snd_soc_dapm_widget es8323_dapm_widgets[] = { SND_SOC_DAPM_INPUT("LINPUT1"), SND_SOC_DAPM_INPUT("LINPUT2"), SND_SOC_DAPM_INPUT("RINPUT1"), SND_SOC_DAPM_INPUT("RINPUT2"), SND_SOC_DAPM_MUX("Left PGA Mux", SND_SOC_NOPM, 0, 0, &es8323_left_dac_mux_controls),--对应LIN1/LIN2/LIN1-RIN1/LIN2-RIN2输入的Mux。 SND_SOC_DAPM_MUX("Right PGA Mux", SND_SOC_NOPM, 0, 0, &es8323_right_dac_mux_controls),--对应RIN1/RIN2/LIN1-RIN1/LIN2-RIN2输入的Mux。 SND_SOC_DAPM_MICBIAS("Mic Bias", ES8323_ADCPOWER, 3, 1),--对应ES8388的Mic Bias。 SND_SOC_DAPM_MUX("Differential Mux", SND_SOC_NOPM, 0, 0, &es8323_diffmux_controls),--选择LIN1-RIN1或者LIN2-RIN2。 SND_SOC_DAPM_MUX("Left ADC Mux", SND_SOC_NOPM, 0, 0, &es8323_monomux_controls),--当Mono时选择Left ADC还是Right ADC。 SND_SOC_DAPM_MUX("Right ADC Mux", SND_SOC_NOPM, 0, 0, &es8323_monomux_controls),--当Mono时选择Left ADC还是Right ADC。 SND_SOC_DAPM_MUX("Left Line Mux", SND_SOC_NOPM, 0, 0, &es8323_left_line_controls),--对应LIN1/LIN2/micL输入的Mux。 SND_SOC_DAPM_MUX("Right Line Mux", SND_SOC_NOPM, 0, 0, &es8323_right_line_controls),--对应RIN1/RIN2/micR输入的Mux。 SND_SOC_DAPM_ADC("Right ADC", "Right Capture", ES8323_ADCPOWER, 4, 1),--对应ES8388框图中的Right ADC power。 SND_SOC_DAPM_ADC("Left ADC", "Left Capture", ES8323_ADCPOWER, 5, 1),--对应ES8388框图中的Left ADC power。 /* gModify.Cmmt Implement when suspend/startup */ SND_SOC_DAPM_DAC("Right DAC", "Right Playback", ES8323_DACPOWER, 6, 1),--对应ES8388框图中的Right DAC power。 SND_SOC_DAPM_DAC("Left DAC", "Left Playback", ES8323_DACPOWER, 7, 1),--对应ES8388框图中的Left DAC power。 SND_SOC_DAPM_AIF_OUT("I2S OUT", "Capture", 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_IN("I2S IN", "Playback", 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_MIXER("Left Mixer", SND_SOC_NOPM, 0, 0, &es8323_left_mixer_controls[0],--DACL和LIN到mixL的Mixer设置。 ARRAY_SIZE(es8323_left_mixer_controls)), SND_SOC_DAPM_MIXER("Right Mixer", SND_SOC_NOPM, 0, 0, &es8323_right_mixer_controls[0],--DACR和RIN到mixR的Mixer设置。 ARRAY_SIZE(es8323_right_mixer_controls)), SND_SOC_DAPM_PGA("Right ADC Power", ES8323_ADCPOWER, 6, 1, NULL, 0),--对应Right analog input power。 SND_SOC_DAPM_PGA("Left ADC Power", ES8323_ADCPOWER, 7, 1, NULL, 0),--对应Left analog input power。 SND_SOC_DAPM_PGA("Right Out 2", ES8323_DACPOWER, 2, 0, NULL, 0),--对应ES8388框图中的ROUT2。 SND_SOC_DAPM_PGA("Left Out 2", ES8323_DACPOWER, 3, 0, NULL, 0),--对应ES8388框图中的LOUT2。 SND_SOC_DAPM_PGA("Right Out 1", ES8323_DACPOWER, 4, 0, NULL, 0),--对应ES8388框图中的ROUT1。 SND_SOC_DAPM_PGA("Left Out 1", ES8323_DACPOWER, 5, 0, NULL, 0),--对应ES8388框图中的LOUT1。 SND_SOC_DAPM_PGA("LAMP", ES8323_ADCCONTROL1, 4, 0, NULL, 0),--对应ES8388框图中的Left MicAmp增益调节。 SND_SOC_DAPM_PGA("RAMP", ES8323_ADCCONTROL1, 0, 0, NULL, 0),--对应ES8388框图中的Right MicAmp增益调节。 SND_SOC_DAPM_OUTPUT("LOUT1"), SND_SOC_DAPM_OUTPUT("ROUT1"), SND_SOC_DAPM_OUTPUT("LOUT2"), SND_SOC_DAPM_OUTPUT("ROUT2"), SND_SOC_DAPM_OUTPUT("VREF"), }; static const struct snd_soc_dapm_route audio_map[] = { {"Left PGA Mux", "Line 1L", "LINPUT1"}, {"Left PGA Mux", "Line 2L", "LINPUT2"}, {"Left PGA Mux", "DifferentialL", "Differential Mux"}, {"Right PGA Mux", "Line 1R", "RINPUT1"}, {"Right PGA Mux", "Line 2R", "RINPUT2"}, {"Right PGA Mux", "DifferentialR", "Differential Mux"}, {"Differential Mux", "Line 1", "LINPUT1"}, {"Differential Mux", "Line 1", "RINPUT1"}, {"Differential Mux", "Line 2", "LINPUT2"}, {"Differential Mux", "Line 2", "RINPUT2"}, {"Left ADC Mux", "Stereo", "Right PGA Mux"}, {"Left ADC Mux", "Stereo", "Left PGA Mux"}, {"Left ADC Mux", "Mono (Left)", "Left PGA Mux"}, {"Right ADC Mux", "Stereo", "Left PGA Mux"}, {"Right ADC Mux", "Stereo", "Right PGA Mux"}, {"Right ADC Mux", "Mono (Right)", "Right PGA Mux"}, {"Left ADC Power", NULL, "Left ADC Mux"}, {"Right ADC Power", NULL, "Right ADC Mux"}, {"Left ADC", NULL, "Left ADC Power"}, {"Right ADC", NULL, "Right ADC Power"}, {"I2S OUT", NULL, "Left ADC"}, {"I2S OUT", NULL, "Right ADC"}, {"Left Line Mux", "Line 1L", "LINPUT1"}, {"Left Line Mux", "Line 2L", "LINPUT2"}, {"Left Line Mux", "MicL", "Left PGA Mux"}, {"Right Line Mux", "Line 1R", "RINPUT1"}, {"Right Line Mux", "Line 2R", "RINPUT2"}, {"Right Line Mux", "MicR", "Right PGA Mux"}, {"Right DAC", NULL, "I2S IN"}, {"Left DAC", NULL, "I2S IN"}, {"Left Mixer", "Left Playback Switch", "Left DAC"}, {"Left Mixer", "Left Bypass Switch", "Left Line Mux"}, {"Right Mixer", "Right Playback Switch", "Right DAC"}, {"Right Mixer", "Right Bypass Switch", "Right Line Mux"}, {"Left Out 1", NULL, "Left Mixer"}, {"LOUT1", NULL, "Left Out 1"}, {"Right Out 1", NULL, "Right Mixer"}, {"ROUT1", NULL, "Right Out 1"}, {"Left Out 2", NULL, "Left Mixer"}, {"LOUT2", NULL, "Left Out 2"}, {"Right Out 2", NULL, "Right Mixer"}, {"ROUT2", NULL, "Right Out 2"}, }; static const struct snd_kcontrol_new es8323_snd_controls[] = { SOC_ENUM("3D Mode", es8323_enum[4]), SOC_SINGLE("ALC Capture Target Volume", ES8323_ADCCONTROL11, 4, 15, 0), SOC_SINGLE("ALC Capture Max PGA", ES8323_ADCCONTROL10, 3, 7, 0), SOC_SINGLE("ALC Capture Min PGA", ES8323_ADCCONTROL10, 0, 7, 0), SOC_ENUM("ALC Capture Function", es8323_enum[5]), SOC_SINGLE("ALC Capture ZC Switch", ES8323_ADCCONTROL13, 6, 1, 0), SOC_SINGLE("ALC Capture Hold Time", ES8323_ADCCONTROL11, 0, 15, 0), SOC_SINGLE("ALC Capture Decay Time", ES8323_ADCCONTROL12, 4, 15, 0), SOC_SINGLE("ALC Capture Attack Time", ES8323_ADCCONTROL12, 0, 15, 0), SOC_SINGLE("ALC Capture NG Threshold", ES8323_ADCCONTROL14, 3, 31, 0), SOC_ENUM("ALC Capture NG Type", es8323_enum[6]), SOC_SINGLE("ALC Capture NG Switch", ES8323_ADCCONTROL14, 0, 1, 0), SOC_SINGLE("ZC Timeout Switch", ES8323_ADCCONTROL13, 6, 1, 0), SOC_DOUBLE_R_TLV("Capture Digital Volume", ES8323_ADCCONTROL8, ES8323_ADCCONTROL9, 0, 192, 1, adc_tlv), SOC_SINGLE("Capture Mute", ES8323_ADCCONTROL7, 2, 1, 0), SOC_SINGLE_TLV("Left Channel Capture Volume", ES8323_ADCCONTROL1, 4, 8, 0, bypass_tlv), SOC_SINGLE_TLV("Right Channel Capture Volume", ES8323_ADCCONTROL1, 0, 8, 0, bypass_tlv), SOC_ENUM("Playback De-emphasis", es8323_enum[7]), SOC_ENUM("Capture Polarity", es8323_enum[8]), SOC_DOUBLE_R_TLV("PCM Volume", ES8323_DACCONTROL4, ES8323_DACCONTROL5, 0, 192, 1, dac_tlv),--控制Left DAC和Right DAC的Volume。 SOC_SINGLE_TLV("Left Mixer Left Bypass Volume", ES8323_DACCONTROL17, 3, 7, 1, bypass_tlv2), SOC_SINGLE_TLV("Right Mixer Right Bypass Volume", ES8323_DACCONTROL20, 3, 7, 1, bypass_tlv2), SOC_DOUBLE_R_TLV("Output 1 Playback Volume", ES8323_DACCONTROL24, ES8323_DACCONTROL25, 0, 33, 0, out_tlv), SOC_DOUBLE_R_TLV("Output 2 Playback Volume", ES8323_DACCONTROL26, ES8323_DACCONTROL27, 0, 33, 0, out_tlv), };

es8323_dai定义了ES8388和SoC I2S TDM接口之间DAI的Plaback/Capture能力和操作函数集:

static struct snd_soc_dai_driver es8323_dai = { .name = "ES8323 HiFi", .playback = { .stream_name = "Playback", .channels_min = 1, .channels_max = 2, .rates = es8323_RATES, .formats = es8323_FORMATS, }, .capture = { .stream_name = "Capture", .channels_min = 1, .channels_max = 2, .rates = es8323_RATES, .formats = es8323_FORMATS, }, .ops = &es8323_ops, .symmetric_rates = 1, };

es8323_ops定义了DAI操作函数集:

static struct snd_soc_dai_ops es8323_ops = { .startup = es8323_pcm_startup, .hw_params = es8323_pcm_hw_params, .set_fmt = es8323_set_dai_fmt, .set_sysclk = es8323_set_dai_sysclk, .mute_stream = es8323_mute, .no_capture_mute = 1, };

Codec相关的DAPM Widget、Control、Route:

DAPM Widget:es8323_dapm_widgets。

Control:es8323_snd_controls。

Route:audio_map。

6.3 Machine Class Driver

Machine相关dts定义了耳机检测、耳机按键检测、Speaker功放开关等,还定义了关联的DAI和Codec。

es8388_sound: es8388-sound { status = "okay"; compatible = "rockchip,multicodecs-card"; rockchip,card-name = "rockchip-es8388";--Sound Card名称。 hp-det-gpio = <&gpio1 RK_PC4 GPIO_ACTIVE_LOW>;--Headphone检测GPIO引脚。 io-channels = <&saradc 3>;--耳机ADC作为按键检测接口。 io-channel-names = "adc-detect"; keyup-threshold-microvolt = <1800000>; poll-interval = <100>; spk-con-gpio = <&gpio3 RK_PB2 GPIO_ACTIVE_HIGH>;--Speaker功放开关GPIO。 //hp-con-gpio = <&gpio3 RK_PB2 GPIO_ACTIVE_HIGH>; rockchip,format = "i2s"; rockchip,mclk-fs = <256>; rockchip,cpu = <&i2s0_8ch>;--Card所使用的DAI接口。 rockchip,codec = <&es8388>;--Card所使用的作为Platform设备的Codec。 rockchip,audio-routing =--定义Card路由。 "Headphone", "LOUT1", "Headphone", "ROUT1", "Speaker", "LOUT2", "Speaker", "ROUT2", "Headphone", "Headphone Power", "Headphone", "Headphone Power", "Speaker", "Speaker Power", "Speaker", "Speaker Power", "LINPUT2", "Main Mic",--Headset Mic插孔作为Main Mic使用。 "RINPUT2", "Main Mic", "LINPUT1", "Headset Mic", "RINPUT1", "Headset Mic"; pinctrl-names = "default"; pinctrl-0 = <&hp_det>; play-pause-key { label = "playpause"; linux,code = <KEY_PLAYPAUSE>; press-threshold-microvolt = <2000>; }; };

rk_multicodecs_probe()作为Machine Driver,处理SOC相关配置,以及将Codec和SoC DAI进行关联:

获取Sound Card名称。

初始化snd_soc_dai_link,关联DAI和Codec。

初始化sound_soc_card,初始化dapm_widgets、controls、routes等。

获取Speaker功放开关GPIO、HP检测GPIO、耳机按键检查ADC等,并注册中断、按键等。

注册Sound Card。

rockchip_multicodecs_driver
  rk_multicodecs_probe
    wait_locked_card
    snd_soc_of_parse_card_name
    ->初始化名为"dailink-multicodecs"的struct snd_soc_dai_link,init初始化函数为rk_dailink_init;ops对应操作函数集为rk_ops;cpus对应数据来自于节点"rockchip,cpu";codecs数据来自于节点"rockchip,codec"。
      ->rk_dailink_init
        ->snd_soc_card_jack_new
          ->snd_jack_new
          ->snd_soc_jack_add_pins--对应的Jack Pin为jack_pins。
            ->snd_jack_add_new_kctl--为每个Jack Pin创建Control。
        ->snd_soc_jack_add_zones
        ->devm_request_threaded_irq--注册hp-det-gpio的中断处理函数headset_det_irq_thread()。上升沿和下降沿都可以触发。
          ->headset_det_irq_thread--中断中进行workqueue调度。
            ->adc_jack_handler--对应work处理函数。
    ->初始化struct snd_soc_card结构体,dapm_widgets和controls分别为mc_dapm_widgetsmc_controls
    devm_iio_channel_get--获取ADC检测通道。
    device_property_read_u32--读取"keyup-threshold-microvolt"值。
    mc_keys_load_keymap--解析play-pause-key,获取按键按下电压阈值和键值。
    devm_input_allocate_device--分配Input_dev设备。
    mc_keys_setup_polling--设置input设备poller,对应的worker函数为mc_keys_poller_work。
      ->mc_keys_poll
    mc_set_poll_interval--设置poll间隔。
    input_register_device--注册Headset上按键作为Input设备。
    devm_gpiod_get_optional--获取"spk-con-gpio"、"hp-con-gpio"、"hp-det"3个GPIO。其中hp-det作为中断对应的处理函数为headset_det_irq_thread。
    devm_extcon_dev_allocate--分配EXTCON_JACK_MICROPHONE和EXTCON_JACK_HEADPHONE两个extcon设备。
    devm_extcon_dev_register--注册headset的ExtCon设备。
    snd_soc_of_parse_audio_routing--解析"rockchip,audio-routing",初始化struct snd_soc_card的routes
    snd_soc_card_set_drvdata
    --带资源管理的注册声卡的接口,包含的dapm_widgets为mc_dapm_widgets,controls为mc_controls,dai_link操作函数为rk_ops。

adc_jack_handler()进行Jack插拔中断处理:

static void adc_jack_handler(struct work_struct *work) { struct multicodecs_data *mc_data = container_of(to_delayed_work(work), struct multicodecs_data, handler); struct snd_soc_jack *jack_headset = mc_data->jack_headset; int adc, ret = 0; if (!gpiod_get_value(mc_data->hp_det_gpio)) {--检测hp-det-gpio的值,如果为低电平,即Jack拔出。 snd_soc_jack_report(jack_headset, 0, SND_JACK_HEADSET);--去使能相关引脚;上报input子系统状态。 extcon_set_state_sync(mc_data->extcon, EXTCON_JACK_HEADPHONE, false);--同步相关extcon状态。 extcon_set_state_sync(mc_data->extcon, EXTCON_JACK_MICROPHONE, false); if (mc_data->poller) mc_keys_poller_stop(mc_data->poller);--停止poller。 return; } if (!mc_data->adc) {--如果不存在adc通道,则Jack仅有Headphone,没有Mic。 /* no ADC, so is headphone */ snd_soc_jack_report(jack_headset, SND_JACK_HEADPHONE, SND_JACK_HEADSET);-- extcon_set_state_sync(mc_data->extcon, EXTCON_JACK_HEADPHONE, true); extcon_set_state_sync(mc_data->extcon, EXTCON_JACK_MICROPHONE, false); return; } ret = iio_read_channel_processed(mc_data->adc, &adc);--读取ADC的值。 if (ret < 0) {--读取错误,则认为Jack仅是Headphone。 /* failed to read ADC, so assume headphone */ snd_soc_jack_report(jack_headset, SND_JACK_HEADPHONE, SND_JACK_HEADSET); extcon_set_state_sync(mc_data->extcon, EXTCON_JACK_HEADPHONE, true); extcon_set_state_sync(mc_data->extcon, EXTCON_JACK_MICROPHONE, false); } else {--读取正确,认为Jack包含Headphone和Mic。 snd_soc_jack_report(jack_headset, snd_soc_jack_get_type(jack_headset, adc), SND_JACK_HEADSET); extcon_set_state_sync(mc_data->extcon, EXTCON_JACK_HEADPHONE, true); if (snd_soc_jack_get_type(jack_headset, adc) == SND_JACK_HEADSET) {--根据ADC的值 extcon_set_state_sync(mc_data->extcon, EXTCON_JACK_MICROPHONE, true); if (mc_data->poller) mc_keys_poller_start(mc_data->poller); } } };

Machine驱动的Widget和Controls如下:

static const struct snd_soc_dapm_widget mc_dapm_widgets[] = { SND_SOC_DAPM_HP("Headphone", NULL), SND_SOC_DAPM_SPK("Speaker", NULL), SND_SOC_DAPM_MIC("Main Mic", NULL), SND_SOC_DAPM_MIC("Headset Mic", NULL), SND_SOC_DAPM_SUPPLY("Speaker Power", SND_SOC_NOPM, 0, 0, mc_spk_event,--通过spk-con-gpio开关功放。 SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), SND_SOC_DAPM_SUPPLY("Headphone Power", SND_SOC_NOPM, 0, 0, mc_hp_event,--通过spk-con-gpio开关功放。 SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), }; static const struct snd_kcontrol_new mc_controls[] = { SOC_DAPM_PIN_SWITCH("Headphone"), SOC_DAPM_PIN_SWITCH("Speaker"), SOC_DAPM_PIN_SWITCH("Main Mic"), SOC_DAPM_PIN_SWITCH("Headset Mic"), };

Machine相关的DAPM Widget、Control、Route:

DAPM Widget:mc_dapm_widgets。

Control:mc_controls。

Route:DTS的rockchip,audio-routing。

6.4 Audio Graph

在alsa中添加如下代码,建立Audio Graph的dot数据:

diff --git a/kernel/sound/soc/soc-dapm.c b/kernel/sound/soc/soc-dapm.c index 2924d89bf..1a812a0cc 100644 --- a/kernel/sound/soc/soc-dapm.c +++ b/kernel/sound/soc/soc-dapm.c @@ -2830,6 +2830,7 @@ static int snd_soc_dapm_add_path(struct snd_soc_dapm_context *dapm, struct snd_soc_dapm_path *path; int ret; + printk("AudioGraph \"%s\" -> \"%s\"\n", wsource->name, wsink->name); if (wsink->is_supply && !wsource->is_supply) { dev_err(dapm->dev, "Connecting non-supply widget to supply widget is not supported (%s -> %s)\n", @@ -2934,6 +2935,10 @@ static int snd_soc_dapm_add_route(struct snd_soc_dapm_context *dapm, unsigned int sink_ref = 0; unsigned int source_ref = 0; int ret; + if(route->control != NULL) { + printk("AudioGraph \"%s\" -> \"%s\"\n", route->control, route->sink); + printk("AudioGraph \"%s\" -> \"%s\"\n", route->source, route->control); + } prefix = soc_dapm_prefix(dapm); if (prefix) { @@ -3612,6 +3617,47 @@ int snd_soc_dapm_put_pin_switch(struct snd_kcontrol *kcontrol, return ret; } EXPORT_SYMBOL_GPL(snd_soc_dapm_put_pin_switch); +char *dapm_type[] = { + "Input", /* input pin */ + "Output", /* output pin */ + "Mux", /* selects 1 analog signal from many inputs */ + "Demux", /* connects the input to one of multiple outputs */ + "Mixer", /* mixes several analog signals together */ + "Mixer", /* mixer with named controls */ + "PGA", /* programmable gain/attenuation (volume) */ + "Output driver", /* output driver */ + "ADC", /* analog to digital converter */ + "DAC", /* digital to analog converter */ + "Mic bias", /* microphone bias (power) - DEPRECATED: use snd_soc_dapm_supply */ + "Mic", /* microphone */ + "Headphone", /* headphones */ + "Speaker", /* speaker */ + "Line IO", /* line input/output */ + "Switch", /* analog switch */ + "VMID", /* codec bias/vmid - to minimise pops */ + "Pre widget", /* machine specific pre widget - exec first */ + "Post widget", /* machine specific post widget - exec last */ + "Supply", /* power/clock supply */ + "Pinctrl", /* pinctrl */ + "Regulator", /* external regulator */ + "Clock", /* external clock */ + "AIF in", /* audio interface input */ + "AIF out", /* audio interface output */ + "Signal generator", /* signal generator */ + "Sink", + "DAI in", /* link to DAI structure */ + "DAI out", + "DAI link", /* link between two DAI structures */ + "kcontrol", /* Auto-disabled kcontrol */ + "Internal buffer", /* DSP/CODEC internal buffer */ + "Internal scheduler", /* DSP/CODEC internal scheduler */ + "Effect", /* DSP/CODEC effect component */ + "Src", /* DSP/CODEC SRC component */ + "Asrc", /* DSP/CODEC ASRC component */ + "Encoder", /* FW/SW audio encoder component */ + "Decoder", /* FW/SW audio decoder component */ + +}; struct snd_soc_dapm_widget * snd_soc_dapm_new_control_unlocked(struct snd_soc_dapm_context *dapm, @@ -3621,6 +3667,12 @@ snd_soc_dapm_new_control_unlocked(struct snd_soc_dapm_context *dapm, struct snd_soc_dapm_widget *w; const char *prefix; int ret; + printk("AudioGraph \"%s\"[shape=box label=\"Card(%s)\\nComponent(%s)\\nDAPM Widget(%s(%s))\"]\n", + widget->name==NULL?"NULL":widget->name, + dapm->card==NULL?"NULL":dapm->card->name, + dapm->component==NULL?"NULL":dapm->component->name, + widget->name==NULL?"NULL":widget->name, + dapm_type[widget->id]); if ((w = dapm_cnew_widget(widget)) == NULL) return ERR_PTR(-ENOMEM);

将删除dot数据插入:

digraph board { rankdir=TB concentrate=true }

通过dot -Tpng alsa.dot  -o alsa.png生成如下图片:

7 ALSA调试节点

/dev/snd中存放Sound Card的Control和PCM设备节点:

/dev/snd/ |-- by-path | |-- platform-es8388-sound -> ../controlC0 |-- controlC0 |-- pcmC0D0c |-- pcmC0D0p |-- seq `-- timer

/proc/asound/中提供ALSA通用信息,以及每个Sound Card的详细信息: 

/proc/asound/ |-- card0 | |-- id | |-- pcm0c | | |-- info | | |-- sub0 | | | |-- hw_params | | | |-- info | | | |-- prealloc | | | |-- prealloc_max | | | |-- status | | | |-- sw_params | | | `-- xrun_injection | | `-- xrun_debug | `-- pcm0p | |-- info | |-- sub0 | | |-- hw_params | | |-- info | | |-- prealloc | | |-- prealloc_max | | |-- status | | |-- sw_params | | `-- xrun_injection | `-- xrun_debug |-- cards |-- devices |-- hwdep |-- pcm |-- rockchipes8388 -> card0 |-- seq | |-- clients | |-- drivers | |-- queues | `-- timer |-- timers `-- version

/sys/class/sound/存放ALSA Sound类设备的链接:

/sys/class/sound/ |-- card0 -> ../../devices/platform/es8388-sound/sound/card0 |-- controlC0 -> ../../devices/platform/es8388-sound/sound/card0/controlC0 |-- pcmC0D0c -> ../../devices/platform/es8388-sound/sound/card0/pcmC0D0c |-- pcmC0D0p -> ../../devices/platform/es8388-sound/sound/card0/pcmC0D0p |-- seq -> ../../devices/virtual/sound/seq `-- timer -> ../../devices/virtual/sound/timer

ASLA Sound  Card详细信息:

/sys/devices/platform/es8388-sound/sound/card0 |-- controlC0 | |-- dev | |-- device -> ../../card0 | |-- subsystem -> ../../../../../../class/sound | `-- uevent |-- device -> ../../../es8388-sound |-- id |-- input2 | |-- capabilities | | |-- abs | | |-- ev | | |-- ff | | |-- key | | |-- led | | |-- msc | | |-- rel | | |-- snd | | `-- sw | |-- device -> ../../card0 | |-- event2 | | |-- dev | | |-- device -> ../../input2 | | |-- subsystem -> ../../../../../../../class/input | | `-- uevent | |-- id | | |-- bustype | | |-- product | | |-- vendor | | `-- version | |-- modalias | |-- name | |-- phys | |-- properties | |-- subsystem -> ../../../../../../class/input | |-- uevent | `-- uniq |-- number |-- pcmC0D0c | |-- dev | |-- device -> ../../card0 | |-- pcm_class | |-- subsystem -> ../../../../../../class/sound | `-- uevent |-- pcmC0D0p | |-- dev | |-- device -> ../../card0 | |-- pcm_class | |-- subsystem -> ../../../../../../class/sound | `-- uevent |-- subsystem -> ../../../../../class/sound `-- uevent

8 ALSA相关Tracepoint

Tracepoint为ALSA提供了一系列追踪函数锚点。

ASoC相关Tracepoint有:

/sys/kernel/debug/tracing/events/asoc/ |-- enable |-- filter |-- snd_soc_bias_level_done--设置Sound Card的Bias Level结束,不一定成功。 |-- snd_soc_bias_level_start--开始设置Sound Card的Bias Level。 |-- snd_soc_dapm_connected |-- snd_soc_dapm_done |-- snd_soc_dapm_path--显示当前DAPM的路径,前面有*号的表示处于连接状态。 |-- snd_soc_dapm_start--开始遍历DAPM Widget的up_list和down_list进行On/Off。 |-- snd_soc_dapm_walk_done--遍历DAPM Widget结束。 |-- snd_soc_dapm_widget_event_done |-- snd_soc_dapm_widget_event_start |-- snd_soc_dapm_widget_power--跟踪DAPM Widget的On/Off。 |-- snd_soc_jack_irq |-- snd_soc_jack_notify `-- snd_soc_jack_report--上报Jack状态Mask表示Jack类型,status表示插拔状态。

PCM相关Tracepoint有:

/sys/kernel/debug/tracing/events/snd_pcm/ |-- applptr |-- applptr_start |-- enable |-- filter |-- hw_interval_param--记录snd_pcm_hw_params的intervals参数。 |-- hw_mask_param--记录snd_pcm_hw_params的masks参数。 |-- hw_ptr_error |-- hwptr `-- xrun

打开追踪所有行为:

echo 1 > /sys/kernel/debug/tracing/events/asoc/enable
echo 1 > /sys/kernel/debug/tracing/events/snd_pcm/enable

追踪Playback/Capture的行为使用:

echo > /sys/kernel/debug/tracing/trace
echo 0 > /sys/kernel/debug/tracing/events/enable
echo 1 > /sys/kernel/debug/tracing/events/asoc/enable

使用arecord/aplay查看DAPM Path路径如下:

2025-08-26 17:22 点击量:7