一、ALSA framwork
1.涉及文件、函数
sound/core/sound.c snd_register_device_for_dev 创建次设备
sound/core/init.c snd_card_create 创建contorl设备
sound/core/pcm.c snd_pcm_new 创建 pcm 设备
snd_card_create 和 snd_pcm_new 通过调用 snd_register_device_for_dev 函数创建次设备
主要思路:通过次设备号获取新的fops对不同次设备进行操作
2.框架图
☆ SNDRV_CTL_IOCTL_ELEM_WRITE ☆ // 调用put回调,设置音量值
SNDRV_CTL_IOCTL_ELEM_READ // 调用get回调,获取音量值
SNDRV_CTL_IOCTL_ELEM_INFO // 调用info回调
二、ASOC framwork
1.重要文件、函数、结构
涉及文件
sound\sound_core.c
......
sound\soc\mxs\mxs-sgtl5000.c
sound\soc\codecs\ sgtl5000.c
......
sound\soc\msm\msm8x10.c
sound\soc\codecs\msm8x10-wcd.c
......
涉及函数
snd_soc_register_card
snd_soc_register_codec
snd_soc_register_dai
snd_soc_register_platform
重要结构
(card) snd_soc_card
(dai_link) snd_soc_dai_link (snd_soc_card->dai_link)
(codec ) snd_soc_codec_driver
(dai) snd_soc_dai_driver
(dma) snd_soc_platform_driver
----------
cat /sys/class/sound/card0/id 的字符串和 snd_soc_card->name 名字相同
2.ASOC组成
—— ASOC 就是对 ALSA 驱动框架的进一步封装,驱动框架分为了三个部分
2.1 machine
指定了 cpu 端的DAI、 codec端的 DAI、 板子的platform名字 、 codec名字 (snd_soc_card->dai_link 结构中指定)
snd_soc_register_card(struct snd_soc_card *card) // 注册一个card
通常有两种方法注册一个card
—— 在machine驱动中注册一个名为 “soc-audio” 的 platform device,并将 struct snd_soc_card 结构变量作为这个device的私有数据,在 soc-core.c 调用 名为 “soc-audio” 的 platform drv的probe函数,probe中调用 snd_soc_register_card 注册这个card
—— 直接在machine驱动中调用 snd_soc_register_card 函数注册card
☆ snd_soc_register_card 函数解析
1.snd_soc_register_card
2. snd_soc_instantiate_card(card); // ☆ 核心函数 ☆
3.1/* soc_bind_dai_link ,就是将machine的 《dai_link》 结构中的
codec、codec端的dai、cpu端的dai依次从已经注册的三个链表中找到并且赋值给《runtime》结构
以下三个函数的作用就是将 dai、dma、codec 注册到三个链表中
snd_soc_register_dai
snd_soc_register_platform
snd_soc_register_codec
*/
for (i = 0; i < card->num_links; i++)
soc_bind_dai_link(card, i);
3.1.1 /* find CPU DAI */
rtd->cpu_dai = cpu_dai;
3.1.2 /* find_codec */
rtd->codec = codec;
3.1.3 /* find CODEC DAI */
rtd->codec_dai = codec_dai;
3.1.4 /* find_platform */
rtd->platform = platform;
3.2 /* initialize the register cache for each available codec */
ret = snd_soc_init_codec_cache(codec, compress_type);
3.3 snd_card_create // ☆ ALSA 创建contorl设备节点
3.4 /* ☆ 此处依次调用以下三个结构模块的probe函数,注意不是驱动的probe ☆
snd_soc_platform_driver
snd_soc_codec_driver
snd_soc_dai_driver
*/
soc_probe_dai_link
/* probe the cpu_dai */
/* probe the CODEC */
/* probe the platform */
/* probe the CODEC DAI */
/* create the pcm */
ret = soc_new_pcm(rtd, num);
struct snd_pcm_ops *soc_pcm_ops = &rtd->ops;
soc_pcm_ops->open = soc_pcm_open;
soc_pcm_ops->close = soc_pcm_close;
soc_pcm_ops->hw_params = soc_pcm_hw_params;
soc_pcm_ops->hw_free = soc_pcm_hw_free;
soc_pcm_ops->prepare = soc_pcm_prepare;
soc_pcm_ops->trigger = soc_pcm_trigger;
soc_pcm_ops->pointer = soc_pcm_pointer;
snd_pcm_new // ☆ ALSA 创建pcm设备节点
3.5 snd_card_register // ☆ ALSA 创建sys节点 /sys/class/sound/cardx
2.2 platform
注册了cpu端的 DAI 和 DMA 驱动
snd_soc_register_platform(struct device *dev,struct snd_soc_platform_driver *platform_drv) // 注册 DMA
snd_soc_register_dai(struct device *dev,struct snd_soc_dai_driver *dai_drv) // 注册 dai
重要结构
struct snd_soc_platform_driver
重要回调函数
当应用程序打开一个pcm设备时,该函数会被调用,通常,该函数会使用snd_soc_set_runtime_hwparams()设置substream中的snd_pcm_runtime结构里面的hw_params相关字段,然后为snd_pcm_runtime的private_data字段申请一个私有结构,用于保存该平台的dma参数。
snd_soc_platform_driver->ops->open
驱动的hw_params阶段,该函数会被调用。通常,该函数会通过snd_soc_dai_get_dma_data函数获得对应的dai的dma参数,获得的参数一般都会保存在snd_pcm_runtime结构的private_data字段。然后通过snd_pcm_set_runtime_buffer函数设置snd_pcm_runtime结构中的dma buffer的地址和大小等参数。要注意的是,该回调可能会被多次调用,具体实现时要小心处理多次申请资源的问题。
snd_soc_platform_driver->ops->hw_params
正式开始数据传送之前会调用该函数,该函数通常会完成dma操作的必要准备工作。
snd_soc_platform_driver->ops->prepare
数据传送的开始,暂停,恢复和停止时,该函数会被调用。
snd_soc_platform_driver->ops->trigger
该函数返回传送数据的当前位置。
snd_soc_platform_driver->ops->pointer
2.3 codec
注册了codec和codec端的dai,指定了control 、dapm_widgets、dapm_route
snd_soc_register_codec(struct device *dev,
const struct snd_soc_codec_driver *codec_drv,
struct snd_soc_dai_driver *dai_drv,
int num_dai)
// 注册codec和 codec 端的dai,下面两个结构作为这个函数的参数
struct snd_soc_codec_driver
struct snd_soc_dai_driver
// 结构成员
snd_soc_codec_driver->controls // ☆ 音量调节等Control设备 ☆ snd_kcontrol_new结构数组
snd_soc_codec_driver->dapm_widgets // ☆ dapm_widgets control集合 ☆ snd_soc_dapm_widget 结构数组
snd_soc_codec_driver->dapm_route // ☆ 路由 ☆ snd_soc_dapm_route 结构数组
// 重要回调函数
snd_soc_dai_driver->ops->startup
snd_soc_dai_driver->ops->hw_params
snd_soc_codec_driver->ops->probe //dai驱动的probe函数,由snd_soc_instantiate_card回调
三、open、ioctl操作设备节点的函数调用过程
1.播放过程
snd_pcm_open
☆ 依次调用cpu_dai、 dma,、codec_dai,、machine 的open或startup函数 ☆
ret = cpu_dai->driver->ops->startup(substream, cpu_dai); // cpu_dai startup
ret = platform->driver->ops->open(substream); // dma open
ret = codec_dai->driver->ops->startup(substream); // codec_dai startup
ret = rtd->dai_link->ops->startup(substream); // machine startup
snd_pcm_playback_ioctl1
☆ SNDRV_PCM_IOCTL_HW_PARAMS
snd_pcm_hw_params_user
soc_pcm_hw_params
☆ 依次调用 machine 、codec_dai、cpu_dai、platform(dma) 的hw_params函数 ☆
SNDRV_PCM_IOCTL_PREPARE :
snd_pcm_prepare(substream, file);
snd_power_wait // 电源管理相关
.... 调用到platform里的prepare
☆ SNDRV_PCM_IOCTL_WRITEI_FRAMES : // 循环数据传输
snd_pcm_lib_write
snd_pcm_lib_write1(substream, (unsigned long)buf, size, nonblock, snd_pcm_lib_write_transfer)
snd_pcm_lib_write_transfer
copy_from_user
snd_pcm_start(substream); // 启动传输
2.音量控制过程
见最开始时序图
codec驱动中搜索 “.put” 关键字直接找到音量控制相关操作
重要结构
/*
定义一个kcontrol主要就是定义一个snd_kcontrol_new结构
一个kcontrol代表着一个mixer(混音器),或者是一个mux(多路开关),又或者是一个音量控制器等等。
*/
struct snd_kcontrol_new
四、重要结构解析
snd_soc_codec
* SoC Audio Codec device */
struct snd_soc_codec {
const char *name; /* Codec的名字*/
struct device *dev; /* 指向Codec设备的指针 */
const struct snd_soc_codec_driver *driver; /* 指向该codec的驱动的指针 */
struct snd_soc_card *card; /* 指向Machine驱动的card实例 */
int num_dai; /* 该Codec数字接口的个数,目前越来越多的Codec带有多个I2S或者是PCM接口 */
int (*volatile_register)(...); /* 用于判定某一寄存器是否是volatile */
int (*readable_register)(...); /* 用于判定某一寄存器是否可读 */
int (*writable_register)(...); /* 用于判定某一寄存器是否可写 */
/* runtime */
......
/* codec IO */
void *control_data; /* 该指针指向的结构用于对codec的控制,通常和read,write字段联合使用 */
enum snd_soc_control_type control_type;/* 可以是SND_SOC_SPI,SND_SOC_I2C,SND_SOC_REGMAP中的一种 */
unsigned int (*read)(struct snd_soc_codec *, unsigned int); /* 读取Codec寄存器的函数 */
int (*write)(struct snd_soc_codec *, unsigned int, unsigned int); /* 写入Codec寄存器的函数 */
/* dapm */
struct snd_soc_dapm_context dapm; /* 用于DAPM控件 */
};
snd_soc_codec_driver
/* codec driver */
struct snd_soc_codec_driver {
/* driver ops */
int (*probe)(struct snd_soc_codec *); /* codec驱动的probe函数,由snd_soc_instantiate_card回调 */
int (*remove)(struct snd_soc_codec *);
int (*suspend)(struct snd_soc_codec *); /* 电源管理 */
int (*resume)(struct snd_soc_codec *); /* 电源管理 */
/* Default control and setup, added after probe() is run */
const struct snd_kcontrol_new *controls; /* ☆ 音频控件指针 */
const struct snd_soc_dapm_widget *dapm_widgets; /* ☆ dapm部件指针 */
const struct snd_soc_dapm_route *dapm_routes; /* ☆ dapm路由指针 */
/* codec wide operations */
int (*set_sysclk)(...); /* 时钟配置函数 */
int (*set_pll)(...); /* 锁相环配置函数 */
/* codec IO */
unsigned int (*read)(...); /* 读取codec寄存器函数 */
int (*write)(...); /* 写入codec寄存器函数 */
int (*volatile_register)(...); /* 用于判定某一寄存器是否是volatile */
int (*readable_register)(...); /* 用于判定某一寄存器是否可读 */
int (*writable_register)(...); /* 用于判定某一寄存器是否可写 */
/* codec bias level */
int (*set_bias_level)(...); /* 偏置电压配置函数 */
};
snd_soc_dai
/*
* digital Audio Interface runtime data.
* Holds runtime data for a DAI.
*/
struct snd_soc_dai {
const char *name; /* dai的名字 */
struct device *dev; /* 设备指针 */
/* driver ops */
struct snd_soc_dai_driver *driver; /* 指向dai驱动结构的指针 */
/* DAI runtime info */
unsigned int capture_active:1; /* stream is in use */
unsigned int playback_active:1; /* stream is in use */
/* DAI DMA data */
void *playback_dma_data; /* 用于管理playback dma */
void *capture_dma_data; /* 用于管理capture dma */
/* parent platform/codec */
union {
struct snd_soc_platform *platform; /* 如果是cpu dai,指向所绑定的平台 */
struct snd_soc_codec *codec; /* 如果是codec dai指向所绑定的codec */
};
struct snd_soc_card *card; /* 指向Machine驱动中的crad实例 */
};
snd_soc_dai_driver
struct snd_soc_dai_driver {
/* DAI description */
const char *name; /* dai驱动名字 */
/* DAI driver callbacks */
int (*probe)(struct snd_soc_dai *dai); /* dai驱动的probe函数,由snd_soc_instantiate_card回调 */
int (*remove)(struct snd_soc_dai *dai);
int (*suspend)(struct snd_soc_dai *dai); /* 电源管理 */
int (*resume)(struct snd_soc_dai *dai);
/* ops */
const struct snd_soc_dai_ops *ops; /* 指向本dai的snd_soc_dai_ops结构 */
/* DAI capabilities */
struct snd_soc_pcm_stream capture; /* ☆ 描述capture的能力 ☆ */
struct snd_soc_pcm_stream playback; /* ☆ 描述playback的能力 ☆ */
};
snd_soc_dai_driver->ops
struct snd_soc_dai_ops {
/*
* 工作时钟配置函数 通常由machine驱动调用
*
*/
int (*set_sysclk)(...); // 设置dai的主时钟;
int (*set_pll)(...);
int (*set_clkdiv)(...);
/*
* DAI format configuration
* Called by soc_card drivers, normally in their hw_params.
*/
int (*set_fmt)(...); // dai 的格式配置函数 通常由machine驱动调用
int (*set_tdm_slot)(...); // 如果dai支持时分复用,用于设置时分复用的slot;
int (*set_channel_map)(...); // 声道的时分复用映射设置
int (*set_tristate)(...); // 设置dai引脚的状态,当与其他dai并联使用同一引脚时需要使用该回调
/*
* DAI digital mute - optional.
* 抗pop,pop声 由soc-core调用:
*/
int (*digital_mute)(...);
/*
* 标准的snd_soc_ops回调 ☆
* 通常由soc-core在进行PCM操作时调用 ☆
*/
int (*startup)(...);
void (*shutdown)(...);
int (*hw_params)(...);
int (*hw_free)(...);
int (*prepare)(...);
int (*trigger)(...);
/*
* For hardware based FIFO caused delay reporting.
* Optional.
*/
snd_pcm_sframes_t (*delay)(...);
};