Linux ALSA驱动框架分析

一、ALSA驱动框架介绍 

1、ALSA介绍

        ALSA(Advanced Linux Sound Architecture-高级linux声音架构),目前已经成为了linux的主流音频体系结构,ALSA在内核部分提供alsa-driver对音频驱动进行耦合和管理,在用户空间空间提供alsa-lib,应用开发人员可以使用alsa-lib接口控制声卡。

2、音频播放流程

        一个音频文件的播放流程如下图,由dma将音频文件的内容从内存搬运到IIS控制器,后传送到codec音频解码芯片,将数字信号转换为模拟信号输出给耳机或者喇叭

3、alsa设备文件介绍

目录:/dev/snd

controlC0:用于声卡的控制,例如通道选择,混音,麦克风的控制等;

midiC0D0:用于播放midi音频;

pcmC0D0c : 用于录音的pcm设备;

pcmC0D0p :用于播放的pcm设备;

seq :音序器;

timer :定时器;

二、I2S介绍

I2S(Inter-IC Sound)总线,也称为集成电路内置音频总线,是飞利浦公司为数字音频设备之间的音频数据传输而制定的一种总线标准。该总线专门用于音频设备之间的音频数据传输,广泛应用于各种多媒体系统。

I2S总线具有以下特点:

1、采用沿独立的导线传输时钟与数据信号的设计,通过将数据和时钟信号分离,避免了因时差诱发的失真,为用户节省了购买抵抗音频抖动的专业设备的费用。

2、总线中有三条数据信号线:BCLK(串行时钟)、LRCLK(左右时钟)和SD(串行数据)。BCLK对应数字音频的每一位数据,LRCLK用于选择左右声道,SD用于传输音频数据。

3、还可以传输一个信号MCLK,称为主时钟,也叫系统时钟(Sys Clock),是采样频率的256倍或384倍,有时为了使系统间能够更好地同步,需要传输这个信号。

MCLK、BCLK、LRCLK、SD如何计算:
在计算BCLK之前,先需要确定音频所需的采样率、采样深度、采样通道数
BCLK=采样率 x 采样宽度 x 采样通道数
假如FS=44.1Khz FS_length=16,channel=2
LRCLK=44.1Khz
BCLK=44.1Khz*16*2=1.414Mhz
MCLK为主时钟(不是一定必须要的)
MCLK=MCLK_FS(过采样率)*FS 一般为256/512等
假如过采样率为256
那么MCLK=256*44.1Khz=11.2Mhz

I2S Philips 标准时序图如下所示:

三、CPU_DAI驱动分析

1、CPU_DAI相关的重要数据

struct snd_soc_dai {
	const char *name;  //描述dai的名称
	int id; 
	struct device *dev;   

	/* driver ops */
	struct snd_soc_dai_driver *driver;    //指向snd_soc_dai_driver,这个结构里描述了cpu_dai的支持类型和各种对cpu_dai进行操作的api

	/* DAI runtime info */
	unsigned int capture_active:1;		/* stream is in use */
	unsigned int playback_active:1;		/* stream is in use */
	unsigned int symmetric_rates:1;
	unsigned int symmetric_channels:1;
	unsigned int symmetric_samplebits:1;
	unsigned int active;
	unsigned char probed:1;

	struct snd_soc_dapm_widget *playback_widget;
	struct snd_soc_dapm_widget *capture_widget;

	/* DAI DMA data */
	void *playback_dma_data;    //cpu_dai播放时使用的dma
	void *capture_dma_data;    //cpu_dai录制时使用的dma

	/* Symmetry data - only valid if symmetry is being enforced */
	unsigned int rate;
	unsigned int channels;
	unsigned int sample_bits;

	/* parent platform/codec */
	struct snd_soc_codec *codec;    //指向所关联的codec设备
	struct snd_soc_component *component;     //指向所关联的component

	/* CODEC TDM slot masks and params (for fixup) */
	unsigned int tx_mask;
	unsigned int rx_mask;

	struct list_head list;
};
struct snd_soc_dai_driver {
	/* DAI description */
	const char *name;    
	unsigned int id;
	unsigned int base;
	struct snd_soc_dobj dobj;

	/* DAI driver callbacks */
	int (*probe)(struct snd_soc_dai *dai);
	int (*remove)(struct snd_soc_dai *dai);
	int (*suspend)(struct snd_soc_dai *dai);    //休眠接口
	int (*resume)(struct snd_soc_dai *dai);    //唤醒接口
	/* compress dai */
	int (*compress_new)(struct snd_soc_pcm_runtime *rtd, int num);
	/* Optional Callback used at pcm creation*/
	int (*pcm_new)(struct snd_soc_pcm_runtime *rtd,
		       struct snd_soc_dai *dai);
	/* DAI is also used for the control bus */
	bool bus_control;

	/* ops */
	const struct snd_soc_dai_ops *ops;    //对cpu_dai进行操作的api接口集合
	const struct snd_soc_cdai_ops *cops;

	/* DAI capabilities */
	struct snd_soc_pcm_stream capture;    //描述播放模式dai支持的参数
	struct snd_soc_pcm_stream playback;    //描述录制模式dai支持的参数
	unsigned int symmetric_rates:1;
	unsigned int symmetric_channels:1;
	unsigned int symmetric_samplebits:1;

	/* probe ordering - for components with runtime dependencies */
	int probe_order;
	int remove_order;
};
struct snd_soc_dai_ops {
	/*
	 * DAI clocking configuration, all optional.
	 * Called by soc_card drivers, normally in their hw_params.
	 */

        //设置时钟
	int (*set_sysclk)(struct snd_soc_dai *dai,
		int clk_id, unsigned int freq, int dir);
        //设置锁相环
	int (*set_pll)(struct snd_soc_dai *dai, int pll_id, int source,
		unsigned int freq_in, unsigned int freq_out);
        //设置时钟分频系数
	int (*set_clkdiv)(struct snd_soc_dai *dai, int div_id, int div);
	int (*set_bclk_ratio)(struct snd_soc_dai *dai, unsigned int ratio);

	/*
	 * DAI format configuration
	 * Called by soc_card drivers, normally in their hw_params.
	 */
	int (*set_fmt)(struct snd_soc_dai *dai, unsigned int fmt);
	int (*xlate_tdm_slot_mask)(unsigned int slots,
		unsigned int *tx_mask, unsigned int *rx_mask);
	int (*set_tdm_slot)(struct snd_soc_dai *dai,
		unsigned int tx_mask, unsigned int rx_mask,
		int slots, int slot_width);
	int (*set_channel_map)(struct snd_soc_dai *dai,
		unsigned int tx_num, unsigned int *tx_slot,
		unsigned int rx_num, unsigned int *rx_slot);
	int (*get_channel_map)(struct snd_soc_dai *dai,
			unsigned int *tx_num, unsigned int *tx_slot,
			unsigned int *rx_num, unsigned int *rx_slot);
	int (*set_tristate)(struct snd_soc_dai *dai, int tristate);

	int (*set_sdw_stream)(struct snd_soc_dai *dai,
			void *stream, int direction);
	/*
	 * DAI digital mute - optional.
	 * Called by soc-core to minimise any pops.
	 */
	int (*digital_mute)(struct snd_soc_dai *dai, int mute);
	int (*mute_stream)(struct snd_soc_dai *dai, int mute, int stream);

	/*
	 * ALSA PCM audio operations - all optional.
	 * Called by soc-core during audio PCM operations.
	 */
	int (*startup)(struct snd_pcm_substream *,
		struct snd_soc_dai *);
	void (*shutdown)(struct snd_pcm_substream *,
		struct snd_soc_dai *);
	int (*hw_params)(struct snd_pcm_substream *,
		struct snd_pcm_hw_params *, struct snd_soc_dai *);
	int (*hw_free)(struct snd_pcm_substream *,
		struct snd_soc_dai *);
	int (*prepare)(struct snd_pcm_substream *,
		struct snd_soc_dai *);
	/*
	 * NOTE: Commands passed to the trigger function are not necessarily
	 * compatible with the current state of the dai. For example this
	 * sequence of commands is possible: START STOP STOP.
	 * So do not unconditionally use refcounting functions in the trigger
	 * function, e.g. clk_enable/disable.
	 */
	int (*trigger)(struct snd_pcm_substream *, int,
		struct snd_soc_dai *);
	int (*bespoke_trigger)(struct snd_pcm_substream *, int,
		struct snd_soc_dai *);
	/*
	 * For hardware based FIFO caused delay reporting.
	 * Optional.
	 */
	snd_pcm_sframes_t (*delay)(struct snd_pcm_substream *,
		struct snd_soc_dai *);
};

2、DTS-NODE

i2s1_8ch: i2s@fe410000 {
        compatible = "rockchip,rk3568-i2s-tdm";
        reg = <0x0 0xfe410000 0x0 0x1000>;
        interrupts = <GIC_SPI 53 IRQ_TYPE_LEVEL_HIGH>;
        clocks = <&cru MCLK_I2S1_8CH_TX>, <&cru MCLK_I2S1_8CH_RX>, <&cru HCLK_I2S1_8CH>;
        clock-names = "mclk_tx", "mclk_rx", "hclk";
        dmas = <&dmac1 2>, <&dmac1 3>;
        dma-names = "tx", "rx";
        resets = <&cru SRST_M_I2S1_8CH_TX>, <&cru SRST_M_I2S1_8CH_RX>;
        reset-names = "tx-m", "rx-m";
        rockchip,cru = <&cru>;
        rockchip,grf = <&grf>;
        #sound-dai-cells = <0>;
        pinctrl-names = "default";
        pinctrl-0 = <&i2s1m0_sclktx
                 &i2s1m0_sclkrx
                 &i2s1m0_lrcktx
                 &i2s1m0_lrckrx
                 &i2s1m0_sdi0
                 &i2s1m0_sdi1
                 &i2s1m0_sdi2
                 &i2s1m0_sdi3
                 &i2s1m0_sdo0
                 &i2s1m0_sdo1
                 &i2s1m0_sdo2
                 &i2s1m0_sdo3>;
        status = "disabled";
};

3、rockchip_i2s_tdm_probe分析

        cpu_dai驱动从dts获取到cpu_dai硬件信息后进入static int rockchip_i2s_tdm_probe(struct platform_device *pdev)函数,进行一系列cpu_dai的硬件初始化工作

static int rockchip_i2s_tdm_probe(struct platform_device *pdev)
{
    ...
    //初始化snd_soc_dai_driver结构,dai的支持配置和各种用于控制dai的api接口
    rockchip_i2s_tdm_dai_prepare(pdev, &soc_dai);
    //从设备树获取bclk_fs值,没有默认64
    i2s_tdm->bclk_fs = 64;
    if (!of_property_read_u32(node, "rockchip,bclk-fs", &val)) {
	if ((val >= 32) && (val % 2 == 0))
		i2s_tdm->bclk_fs = val;
    }
    //从设备树中读取各种初始化cpu_dai相关的设置保存到i2s_tdm,例如I2S的时钟参数等
    of_property_read_u32(node, ..., &val);
    devm_reset_control_get(&pdev->dev, ...);
    devm_clk_get(&pdev->dev, "hclk")
    devm_clk_get(&pdev->dev, "mclk_tx");
    devm_clk_get(&pdev->dev, "mclk_rx")
    
    //获取cpu_dai的寄存器地址,并初始化cpu_dai
    platform_get_resource(pdev, IORESOURCE_MEM, 0);
    devm_ioremap_resource(&pdev->dev, res);
   
    //初始化regmap   
    devm_regmap_init_mmio(&pdev->dev, regs, &rockchip_i2s_tdm_regmap_config);
    
    //TX/RX的dma参数获取
    i2s_tdm->playback_dma_data.addr = res->start + I2S_TXDR;
    i2s_tdm->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
    i2s_tdm->playback_dma_data.maxburst = 8;
    i2s_tdm->capture_dma_data.addr = res->start + I2S_RXDR;
    i2s_tdm->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
    i2s_tdm->capture_dma_data.maxburst = 8;
    
    //初始化cpu_dai的tx和rx通路
    rockchip_i2s_tdm_tx_path_prepare(i2s_tdm, node);
    rockchip_i2s_tdm_rx_path_prepare(i2s_tdm, node);
    i2s_tdm->soc_data->init(&pdev->dev, res->start);
    
    //注册component组件
    devm_snd_soc_register_component(&pdev->dev,
					      &rockchip_i2s_tdm_component,
					      soc_dai, 1);
        - >snd_soc_register_component(dev, cmpnt_drv, dai_drv, num_dai);
    //获取dma    
    devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
        ->snd_soc_add_platform(dev, &pcm->platform,&dmaengine_pcm_platform);
    return 0;
}

        snd_soc_register_component(dev, cmpnt_drv, dai_drv, num_dai)后续分析

snd_soc_register_component(dev, cmpnt_drv, dai_drv, num_dai)->
    snd_soc_component_initialize(cmpnt, cmpnt_drv, dev); //初始化component
            //注册了一个dai,并填充snd_soc_dai_driver数据
            snd_soc_register_dais(cmpnt, dai_drv, num_dai, true); 
            //将component加入component_list链表
            snd_soc_component_add(cmpnt);

        该函数被传入两个关键参数struct snd_soc_component_driver cmpnt_drv、struct snd_soc_dai_driver dai_drv,定义如下

static const struct snd_soc_component_driver rockchip_i2s_component = {
	.name = DRV_NAME,
};
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),
		},
		.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),
		},
        //对cpu_dai的api操作集合,如dai的时钟配置、格式配置等
		.ops = &rockchip_i2s_tdm_dai_ops, 
};

        snd_soc_add_platform(dev, &pcm->platform,&dmaengine_pcm_platform)后续分析

dmaengine_pcm_request_chan_of(pcm, dev, config);
     //根据name去dts中找资源,申请对应的DMA通道
     chan = dma_request_slave_channel_reason(dev, name);
         return of_dma_request_slave_channel(dev->of_node, name);
     //注册platform到ASoC Core
     ret = snd_soc_add_platform(dev, &pcm->platform,&dmaengine_pcm_platform);

        platform中需要完成音频数据管理和音频数据的dma搬运,其中就涉及到了dma相关的操作,snd_soc_add_platform的目的就是完成dma通道的申请,并将pcm和dma关联起来

四、CODEC驱动分析

1、CODEC相关的重要数据

static const struct regmap_config rk817_codec_regmap_config = {
	.name = "rk817-codec",
	.reg_bits = 8,    //寄存器地址位数(必选项)
	.val_bits = 8,    //寄存器值位数(必选项)
	.reg_stride = 1,
	.max_register = 0x4f,    //最大寄存器值
	.cache_type = REGCACHE_FLAT,
	.volatile_reg = rk817_volatile_register,    
	.writeable_reg = rk817_codec_register,    //寄存器是否可写
	.readable_reg = rk817_codec_register,   //寄存器是否可写读
	.reg_defaults = rk817_reg_defaults,    //默认寄存器配置参数
	.num_reg_defaults = ARRAY_SIZE(rk817_reg_defaults),
};

2、DTS-NODE

rk809_codec: codec {
            #sound-dai-cells = <0>;
            compatible = "rockchip,rk809-codec", "rockchip,rk817-codec";
            clocks = <&cru I2S1_MCLKOUT>;
            clock-names = "mclk";
            assigned-clocks = <&cru I2S1_MCLKOUT>, <&cru I2S1_MCLK_TX_IOE>;
            assigned-clock-rates = <12288000>;
            assigned-clock-parents = <&cru I2S1_MCLKOUT_TX>, <&cru I2S1_MCLKOUT_TX>;
            pinctrl-names = "default","spk_gpio";
            pinctrl-0 = <&i2s1m0_mclk>;
            pinctrl-1 = <&spk_ctl_gpio>;
            hp-volume = <20>;
            spk-volume = <3>;
            //mic-in-differential;

            capture-volume = <0>;
            io-channels = <&saradc 4>;
            hp-det-adc-value = <1000>;
            spk-ctl-gpios = <&gpio3 RK_PC5 GPIO_ACTIVE_HIGH>;
            status = "okay";
};

3、rk817_platform_probe分析

        codec_dai驱动从dts获取到硬件信息后进入static int rk817_platform_probe(struct platform_device *pdev)函数,进行一系列codec_dai的硬件初始化工作

static int rk817_platform_probe(struct platform_device *pdev)
{
    ...
    rk817_codec_data = devm_kzalloc(&pdev->dev,sizeof(struct rk817_codec_priv),
    GFP_KERNEL);    //给rk817_codec_data申请空间
    platform_set_drvdata(pdev, rk817_codec_data); //rk817_codec_data绑定pdev
    
    //从dts节点中获取adc通道、gpio节点、volume值等
    rk817_codec_parse_dt_property(&pdev->dev, rk817_codec_data); 
    //regmap初始化
    rk817_codec_data->regmap = devm_regmap_init_i2c(rk817->i2c,
					    &rk817_codec_regmap_config);
    //mclk获取
    rk817_codec_data->mclk = devm_clk_get(&pdev->dev, "mclk");
    //componen注册
    devm_snd_soc_register_component(&pdev->dev, &soc_codec_dev_rk817,
					      rk817_dai, ARRAY_SIZE(rk817_dai));
    ...    
}

        devm_snd_soc_register_component后续分析:初始化snd_soc_component的实例,然后注册dai,最终将注册的dai放入component->dai_list中,然后将分配的component放入到component_list链表中,遍历全局的component链表可以找到cpu_dai,codec_dai

devm_snd_soc_register_component->
    snd_soc_register_component->
        snd_soc_add_component->
            snd_soc_component_initialize    //component初始化
            snd_soc_register_dais    //dai注册
            snd_soc_component_add    //component加入全局链表

五、MACHINE(simple-card.c)驱动分析

1、MACHINE相关的重要数据

struct snd_soc_dai_link {
	/* config - must be set by machine driver */
	const char *name;			/* Codec name */
	const char *stream_name;		/* Stream name */
	/*
	 * You MAY specify the link's CPU-side device, either by device name,
	 * or by DT/OF node, but not both. If this information is omitted,
	 * the CPU-side DAI is matched using .cpu_dai_name only, which hence
	 * must be globally unique. These fields are currently typically used
	 * only for codec to codec links, or systems using device tree.
	 */
	const char *cpu_name;
	struct device_node *cpu_of_node;
	/*
	 * You MAY specify the DAI name of the CPU DAI. If this information is
	 * omitted, the CPU-side DAI is matched using .cpu_name/.cpu_of_node
	 * only, which only works well when that device exposes a single DAI.
	 */
	const char *cpu_dai_name;
	/*
	 * You MUST specify the link's codec, either by device name, or by
	 * DT/OF node, but not both.
	 */
	const char *codec_name;
	struct device_node *codec_of_node;
	/* You MUST specify the DAI name within the codec */
	const char *codec_dai_name;

	struct snd_soc_dai_link_component *codecs;
	unsigned int num_codecs;

	/*
	 * You MAY specify the link's platform/PCM/DMA driver, either by
	 * device name, or by DT/OF node, but not both. Some forms of link
	 * do not need a platform.
	 */
	const char *platform_name;
	struct device_node *platform_of_node;
	int id;	/* optional ID for machine driver link identification */

	const struct snd_soc_pcm_stream *params;
	unsigned int num_params;

	unsigned int dai_fmt;           /* format to set on init */

	enum snd_soc_dpcm_trigger trigger[2]; /* trigger type for DPCM */

	/* codec/machine specific init - e.g. add machine controls */
	int (*init)(struct snd_soc_pcm_runtime *rtd);

	/* optional hw_params re-writing for BE and FE sync */
	int (*be_hw_params_fixup)(struct snd_soc_pcm_runtime *rtd,
			struct snd_pcm_hw_params *params);

	/* machine stream operations */
	const struct snd_soc_ops *ops;
	const struct snd_soc_compr_ops *compr_ops;

	/* Mark this pcm with non atomic ops */
	bool nonatomic;

	/* For unidirectional dai links */
	unsigned int playback_only:1;
	unsigned int capture_only:1;

	/* Keep DAI active over suspend */
	unsigned int ignore_suspend:1;

	/* Symmetry requirements */
	unsigned int symmetric_rates:1;
	unsigned int symmetric_channels:1;
	unsigned int symmetric_samplebits:1;

	/* Do not create a PCM for this DAI link (Backend link) */
	unsigned int no_pcm:1;

	/* This DAI link can route to other DAI links at runtime (Frontend)*/
	unsigned int dynamic:1;

	/* This DAI link can be reconfigured at runtime (Backend) */
	unsigned int dynamic_be:1;

	/*
	 * This DAI can support no host IO (no pcm data is
	 * copied to from host)
	 */
	unsigned int no_host_mode:2;

	/* DPCM capture and Playback support */
	unsigned int dpcm_capture:1;
	unsigned int dpcm_playback:1;

	/* DPCM used FE & BE merged format */
	unsigned int dpcm_merged_format:1;
	/* DPCM used FE & BE merged channel */
	unsigned int dpcm_merged_chan:1;
	/* DPCM used FE & BE merged rate */
	unsigned int dpcm_merged_rate:1;

	/* pmdown_time is ignored at stop */
	unsigned int ignore_pmdown_time:1;

	/* Do not create a PCM for this DAI link (Backend link) */
	unsigned int ignore:1;

	struct list_head list; /* DAI link list of the soc card */
	struct snd_soc_dobj dobj; /* For topology */

	/* this value determines what all ops can be started asynchronously */
	enum snd_soc_async_ops async_ops;
};
struct snd_soc_card {
	const char *name;
	const char *long_name;
	const char *driver_name;
	char dmi_longname[80];
	char topology_shortname[32];

	struct device *dev;
	struct snd_card *snd_card;
	struct module *owner;

	struct mutex mutex;
	struct mutex dapm_mutex;
	struct mutex dapm_power_mutex;

	bool instantiated;
	bool topology_shortname_created;

	int (*probe)(struct snd_soc_card *card);
	int (*late_probe)(struct snd_soc_card *card);
	int (*remove)(struct snd_soc_card *card);

	/* the pre and post PM functions are used to do any PM work before and
	 * after the codec and DAI's do any PM work. */
	int (*suspend_pre)(struct snd_soc_card *card);
	int (*suspend_post)(struct snd_soc_card *card);
	int (*resume_pre)(struct snd_soc_card *card);
	int (*resume_post)(struct snd_soc_card *card);

	/* callbacks */
	int (*set_bias_level)(struct snd_soc_card *,
			      struct snd_soc_dapm_context *dapm,
			      enum snd_soc_bias_level level);
	int (*set_bias_level_post)(struct snd_soc_card *,
				   struct snd_soc_dapm_context *dapm,
				   enum snd_soc_bias_level level);

	int (*add_dai_link)(struct snd_soc_card *,
			    struct snd_soc_dai_link *link);
	void (*remove_dai_link)(struct snd_soc_card *,
			    struct snd_soc_dai_link *link);

	long pmdown_time;

	/* CPU <--> Codec DAI links  */
	struct snd_soc_dai_link *dai_link;  /* predefined links only */
	int num_links;  /* predefined links only */
	struct list_head dai_link_list; /* all links */
	int num_dai_links;

	struct list_head rtd_list;
	int num_rtd;

	/* optional codec specific configuration */
	struct snd_soc_codec_conf *codec_conf;
	int num_configs;

	/*
	 * optional auxiliary devices such as amplifiers or codecs with DAI
	 * link unused
	 */
	struct snd_soc_aux_dev *aux_dev;
	int num_aux_devs;
	struct list_head aux_comp_list;

	const struct snd_kcontrol_new *controls;
	int num_controls;

	/*
	 * Card-specific routes and widgets.
	 * Note: of_dapm_xxx for Device Tree; Otherwise for driver build-in.
	 */
	const struct snd_soc_dapm_widget *dapm_widgets;
	int num_dapm_widgets;
	const struct snd_soc_dapm_route *dapm_routes;
	int num_dapm_routes;
	const struct snd_soc_dapm_widget *of_dapm_widgets;
	int num_of_dapm_widgets;
	const struct snd_soc_dapm_route *of_dapm_routes;
	int num_of_dapm_routes;
	bool fully_routed;

	struct work_struct deferred_resume_work;

	/* lists of probed devices belonging to this card */
	struct list_head component_dev_list;

	struct list_head widgets;
	struct list_head paths;
	struct list_head dapm_list;
	struct list_head dapm_dirty;

	/* attached dynamic objects */
	struct list_head dobj_list;

	/* Generic DAPM context for the card */
	struct snd_soc_dapm_context dapm;
	struct snd_soc_dapm_stats dapm_stats;
	struct snd_soc_dapm_update *update;

#ifdef CONFIG_DEBUG_FS
	struct dentry *debugfs_card_root;
	struct dentry *debugfs_pop_time;
#endif
	u32 pop_time;

	void *drvdata;
};

2、DTS-NODE

    rk809_sound: rk809-sound {
        status = "okay";
        compatible = "simple-audio-card";
        simple-audio-card,format = "i2s";
        simple-audio-card,name = "rockchip,rk809-codec";
        simple-audio-card,mclk-fs = <256>;
        simple-audio-card,widgets =
                        "Microphone", "Mic Jack",
                        "Headphone", "Headphone Jack";
        simple-audio-card,routing =
                        "Mic Jack", "MICBIAS1",
                        "IN1P", "Mic Jack",
                        "Headphone Jack", "HPOL",
                        "Headphone Jack", "HPOR";
        simple-audio-card,cpu {
            sound-dai = <&i2s1_8ch>;
        };
        simple-audio-card,codec {
            sound-dai = <&rk809_codec>;
        };
    };

3、asoc_simple_card_probe分析

        codec_dai驱动从dts获取到cpu_dai和codec_dai的连接关系和节点后,进入asoc_simple_card_probe,进行声卡的注册和初始化工作

static int asoc_simple_card_probe(struct platform_device *pdev)
{
    struct simple_card_data *priv;
    struct snd_soc_dai_link *dai_link;
    struct simple_dai_props *dai_props;
    struct device *dev = &pdev->dev;
    struct device_node *np = dev->of_node;
    struct snd_soc_card *card;
    int num, ret;
    ...
    //获取节点"simple-audio-card,dai-link"
    //由于节点不存在 num = 1 
    if (np && of_get_child_by_name(np, PREFIX "dai-link"))
        num = of_get_child_count(np);
    }else{ num = 1; }

    //申请 priv\dai_props\dai_link的内存空间
    //priv->dai_props priv->dai_link
	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
	dai_props = devm_kcalloc(dev, num, sizeof(*dai_props), GFP_KERNEL);
	dai_link  = devm_kcalloc(dev, num, sizeof(*dai_link), GFP_KERNEL);
    
    //初始化snd_soc_card
    card = simple_priv_to_card(priv); //card=&(priv->snd_card)
    card->owner		= THIS_MODULE;
    card->dev		= dev;
    card->dai_link		= priv->dai_link;
    card->num_links		= num;
    card->probe		= asoc_simple_soc_card_probe;
    
    //如果simple-card 存在
    //对priv->dai_link priv->snd_card 部分填充和初始化
    if (np && of_device_is_available(np)){
      asoc_simple_card_parse_of(priv); //详细分析在下
    } else {...};      
    //实例化和填充card,绑定dailink,创建音频节点pcmC0D0P,并完成cpu_dai和codec的probe 
    ret=devm_snd_soc_register_card(dev, card)->
        return snd_soc_register_card(card); //详细分析在下
    return ret;
}
asoc_simple_card_parse_ of ( priv )分析如下:
static int asoc_simple_card_parse_of(struct simple_card_data *priv)
{
    //获取节点"simple-audio-card,dai-link"
    //不存在dai_link=NULL
    struct device_node *dai_link;
    dai_link = of_get_child_by_name(node, PREFIX "dai-link");
    
    /* 
    从设备树解析声卡的widgets
    card->of_dapm_widgets = widgets;
        -----card->of_dapm_widgets[0].id = snd_soc_dapm_mic;
        -----card->of_dapm_widgets[0].name = "Microphone";
        -----card->of_dapm_widgets[0].event = "Mic Jack"; 
    card->num_of_dapm_widgets = num_widgets;
        -----num_of_dapm_widgets = 2;
	*/
    ret = asoc_simple_card_of_parse_widgets(card, PREFIX);
    
    /*
    从设备树解析声卡的route
    card->num_of_dapm_routes = num_routes;
        -----num_of_dapm_routes = 4;
    card->of_dapm_routes = routes;
        -----card->of_dapm_routes[0].sink = "Mic Jack"; 
        -----card->of_dapm_routes[0].sourece = "MICBIAS1"
        -----card->of_dapm_routes[1].sink = "IN1P"; 
        -----card->of_dapm_routes[1].sourece = "Mic Jack"
    */
    ret = asoc_simple_card_of_parse_routing(card, PREFIX, 1);
    if (dai_link) {
        ...
    }else{
        /* 以下列出函数功能*/
        /*1 card->dai_link->dai_fmt = i2s 
             simple-audio-card,format = "i2s"
        */
        /*2 card->dai_link->cpu_of_node= "<&i2s1_8ch>" 
            single_cpu=1
        */
        /*3 card->dai_link->codec_of_node = "<&rk809_codec>"
        */
        /*4 require clk,codec_dai->clk = "clk of i2s"
            cpu_dai->systemclk = clk_get_rate(clk); 
            cpu_dai->clk_direction = SND_SOC_CLOCK_IN;
        */
        /*5 require clk,codec_dai->clk = "I2S_MCSL_OUT"
            codec_dai->systemclk = clk_get_rate(clk); 
            codec_dai->clk_direction = SND_SOC_CLOCK_IN;
        */
        /*6 dai_link->codec_dai_name = "tlv320aic31xx-hifi"
            static struct snd_soc_dai_driver 
                            aic31xx_dai_driver[] = {
            {
                .name = "tlv320aic31xx-hifi",
                .playback = {
                    ...
                },
                .capture = {
                    ...
                },
                .ops = &aic31xx_dai_ops,
                .symmetric_rates = 1,
                }
	        };
            cpu_dai_name 由于dai->driver的名字不存在
            所以 dai_link->codec_dai_name = "rockchip-i2s-tdm"
            struct snd_soc_component_driver        
                            rockchip_i2s_tdm_component = {
	             .name = DRV_NAME,
            };     
            
        */
        /*7 dai_link->name = 
                            "rockchip-i2s-tdm-tlv320aic31xx-hifi"
            dai_link->stream_name = 
                            "rockchip-i2s-tdm-tlv320aic31xx-hifi"
        */
        /*8 dai_link->cpu_name = NULL;
            dai_link->codec_name = NULL;
        */
        ret = asoc_simple_card_dai_link_of(node, priv, 0, true);
    }
    
    //card->name = "rockchip,tlv320aic31xx-codec";
    //simple-audio-card,name = "rockchip,tlv320aic31xx-codec";
    ret = asoc_simple_card_parse_card_name(card, PREFIX);
    ...
}
snd_soc_register_card的分析流程如下:
int snd_soc_register_card(struct snd_soc_card *card)
{
    struct snd_soc_pcm_runtime *rtd;
    //num_links == 1 
    for (i = 0; i < card->num_links; i++) {
        struct snd_soc_dai_link *link = &card->dai_link[i];
        //dai_link->codecs[0].name = dai_link->codec_name;
        //dai_link->codecs[0].of_node = dai_link->codec_of_node;
		 //dai_link->codecs[0].dai_name = dai_link->codec_dai_name;
        ret = soc_init_dai_link(card, link);
	}
    
    //card->dev->driver_data = card;
    dev_set_drvdata(card->dev, card);
    
    //card相关链表头的初始化
    INIT_LIST_HEAD(&card->widgets);
    INIT_LIST_HEAD(&card->paths);
    INIT_LIST_HEAD(&card->dapm_list);
    INIT_LIST_HEAD(&card->aux_comp_list);
    INIT_LIST_HEAD(&card->component_dev_list);
    INIT_LIST_HEAD(&card->dai_link_list);
    INIT_LIST_HEAD(&card->rtd_list);
    INIT_LIST_HEAD(&card->dapm_dirty);
    INIT_LIST_HEAD(&card->dobj_list);
    
    //card相关锁的初始化
    mutex_init(&card->mutex);
    mutex_init(&card->dapm_mutex);
    mutex_init(&card->dapm_power_mutex);
    
    //声卡的部分参数实例化填充以及声卡的注册
    ret = snd_soc_instantiate_card(card); //详细分析在下
    ...
}

snd_soc_instantiate_card的分析流程如下:
static int snd_soc_instantiate_card(struct snd_soc_card *card)
{
    struct snd_soc_pcm_runtime *rtd;
    struct snd_soc_dai_link *dai_link;
    
    //num_links==1
    for (i = 0; i < card->num_links; i++) {
        //详细分析在下
        //找到cpu/codec的snd_soc_dai
        ret = soc_bind_dai_link(card, &card->dai_link[i]);
    }
    
    //将card->list加入到card->dai_link_list
    snd_soc_add_dai_link(card, card->dai_link+i);
    
    //创建新的声卡
    //sprintf(str, "card%i", card->number);
    //entry = snd_info_create_subdir(card->module, str, NULL);
    ret = snd_card_new(card->dev, SNDRV_DEFAULT_IDX1, 
        SNDRV_DEFAULT_STR1,card->owner, 0, &card->snd_card);  
    
    //添加widgets
	if (card->of_dapm_widgets)
        snd_soc_dapm_new_controls(&card->dapm, 
            card->of_dapm_widgets,card->num_of_dapm_widgets);
    ...
    //执行probe函数asoc_simple_soc_card_probe
    card->probe(card);
    
    //执行cpu和codec的component->driver->probe函数
    //component->driver->probe(component)
    for(...)
        ret = soc_probe_link_components(card, rtd, order);
    //执行cpu和codec的dai->driver->probe函数 (一般未定义)
    //执行dai_link->init asoc_simple_card_dai_init
    //执行cpu和codec的dai->driver->ops->set_fmt
    //创建pcm soc_new_pcm(rtd, num) 初始化rtd->ops
    //rtd->pcm = pcm;
    for(...)
        ret = soc_probe_link_dais(card, rtd, order);
        
    //添加route
	if (card->of_dapm_routes)
        snd_soc_dapm_add_routes(&card->dapm, card->of_dapm_routes,
					card->num_of_dapm_routes);

    //声卡名称填充 cat /proc/asound/cards
    snprintf(card->snd_card->shortname, 
       sizeof(card->snd_card->shortname),"%s", card->name);
    snprintf(card->snd_card->driver,
        sizeof(card->snd_card->driver),
		 "%s", card->driver_name ? card->driver_name : card->name);
    ...

    //注册声卡,注册回调函数,如上面提到的snd_pcm_dev_register函数
    //该函数建立设备节点:/dev/snd/pcmCxxDxxp和 /dev/snd/pcmCxxDxxc
    snd_card_register(card->snd_card);   
}
static int soc_bind_dai_link的分析流程如下:
static int soc_bind_dai_link(struct snd_soc_card *card,
	struct snd_soc_dai_link *dai_link)
{
    struct snd_soc_pcm_runtime *rtd;

    //遍历card->rtd_list
    //如果rtd->dai_link == dai_link,表明当前的dai_link已经绑定
    if (soc_is_dai_link_bound(card, dai_link)) {
		return 0;
	}
    
    //rtd申请和填充
    //rtd->card = card;
    //rtd->dai_link = dai_link;
    /*rtd->codec_dais = kcalloc(dai_link->num_codecs,
				sizeof(struct snd_soc_dai *),GFP_KERNEL);
    */
    rtd = soc_new_pcm_runtime(card, dai_link);
    
    //从component链表中找到已经注册的cpu snd_soc_dai,并填充rtd
    //并将cpu dai的component链表加入rtd的component链表
    rtd->cpu_dai = snd_soc_find_dai(&cpu_dai_component);
    snd_soc_rtdcom_add(rtd, rtd->cpu_dai->component);
    
    //rtd->num_codecs = dai_link->num_codecs =1;
    //从component链表中找到已经注册的codec snd_soc_dai,并填充rtd
    //并将codec dai的component链表加入rtd的component链表
    rtd->num_codecs = dai_link->num_codecs
    for (i = 0; i < rtd->num_codecs; i++) {
        codec_dais[i] = snd_soc_find_dai(&codecs[i]);
        snd_soc_rtdcom_add(rtd, codec_dais[i]->component);
    }
    
    //将该rtd的链表加入到card->rtd_list中
    soc_add_pcm_runtime(card, rtd);
}

六、ALSA-Probe的流程

        下图是板子内核启动到probe完成阶段的流程图,可以看到ALSA初始化音频的整个流程是怎么初始化声卡,和配置一些基本参数的

step 1:
cpu_dai_probe:获取设备树有关I2S的硬件描述信息,如时钟参数,dma通道参数,regmap_i2c初始化,然后 devm_snd_soc_register_component注册cpu_dai,调用 devm_snd_dmaengine_pcm_register注册dmaengine

codec_dai_probe:硬件资源的获取,regmap_i2c初始化,然后调用devm_snd_soc_register_component注册

asoc_simple_card_probe:dai_link获取,获取cpu_dai,codec_dai的信息,初始化函数接口,调用devm_snd_soc_register_card执行声卡的注册,注册过程中依次执行具体设备的probe,声卡的probe,cpu_dai probe ,codec_probe

step 2:

rk817_probe: snd_soc_add_component_controls,设置音频通路相关的寄存器

rockchip_i2s_tdm_dai_probe: snd_soc_add_dai_controls ,设置I2S相关的寄存器

asoc_simple_soc_card_probe: asoc_simple_card_init_hp ,asoc_simple_card_init_hp ?

asoc_simple_card_hw_params : rockchip_i2s_tdm_set_sysclk设置I2S的时钟参数,rk817_set_dai_sysclk设置codec一层具体的时钟参数

snd_soc_dai_set_fmt:rk817_set_dai_fmt 设置音频数据格式,是I2S还是PCM,CODEC是master还是slave

七、Tinyalsa pcm_open的调用流程

        先看下pcm的fops,接口如下,定义在pcm_native.c

const struct file_operations snd_pcm_f_ops[2] = {
	{
		.owner =		THIS_MODULE,
		.write =		snd_pcm_write,
		.write_iter =		snd_pcm_writev,
		.open =			snd_pcm_playback_open,
		.release =		snd_pcm_release,
		.llseek =		no_llseek,
		.poll =			snd_pcm_playback_poll,
		.unlocked_ioctl =	snd_pcm_playback_ioctl,
		.compat_ioctl = 	snd_pcm_ioctl_compat,
		.mmap =			snd_pcm_mmap,
		.fasync =		snd_pcm_fasync,
		.get_unmapped_area =	snd_pcm_get_unmapped_area,
	},
	{
		.owner =		THIS_MODULE,
		.read =			snd_pcm_read,
		.read_iter =		snd_pcm_readv,
		.open =			snd_pcm_capture_open,
		.release =		snd_pcm_release,
		.llseek =		no_llseek,
		.poll =			snd_pcm_capture_poll,
		.unlocked_ioctl =	snd_pcm_capture_ioctl,
		.compat_ioctl = 	snd_pcm_ioctl_compat,
		.mmap =			snd_pcm_mmap,
		.fasync =		snd_pcm_fasync,
		.get_unmapped_area =	snd_pcm_get_unmapped_area,
	}
};

        substream是pcm ops的下一层,绝大部分任务都是在substream中处理,substream的ops定义在soc-pcm.c,定义如下

int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num)
{
        ...
    	/* ASoC PCM operations */
	if (rtd->dai_link->dynamic) {
		rtd->ops.open		= dpcm_fe_dai_open;
		rtd->ops.hw_params	= dpcm_fe_dai_hw_params;
		rtd->ops.prepare	= dpcm_fe_dai_prepare;
		rtd->ops.trigger	= dpcm_fe_dai_trigger;
		rtd->ops.hw_free	= dpcm_fe_dai_hw_free;
		rtd->ops.close		= dpcm_fe_dai_close;
		rtd->ops.pointer	= soc_pcm_pointer;
		rtd->ops.ioctl		= soc_pcm_ioctl;
	} else {
		rtd->ops.open		= soc_pcm_open;
		rtd->ops.hw_params	= soc_pcm_hw_params;
		rtd->ops.prepare	= soc_pcm_prepare;
		rtd->ops.trigger	= soc_pcm_trigger;
		rtd->ops.hw_free	= soc_pcm_hw_free;
		rtd->ops.close		= soc_pcm_close;
		rtd->ops.pointer	= soc_pcm_pointer;
		rtd->ops.ioctl		= soc_pcm_ioctl;
	}
        ...
	if (playback)
		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &rtd->ops);

	if (capture)
		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &rtd->ops);
        ...   
}

        下图使用tinyalsa测试,可以看到alsa-driver在pcm_open阶段做了哪些工作,流程是什么

2d04d48509f9370ff4a63042d1b4e7b8.png

step1:设置时钟参数

step2:设置音频参数,根据上层设置的采样率,数据格式来配置codec寄存器,并开启时钟

八、Tinyalsa pcm_write的调用流程

        下图使用tinyalsa测试,可以看到alsa-driver在pcm_write阶段做了哪些工作,流程是什么

        至此整个ALSA-DRIVER框架的内容就分析完了,可能有一些细节的东西内容太多导致没法去描述,有需求的话,可以自行根据需求分析

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值