一、Platform驱动的作用
ASoC被分为Machine,Platform和Codec三大部件,Platform驱动的主要作用是完成音频数据的管理,最终通过CPU的数字音频接口(DA〉把音频数据传送给Codec进行处理,最终由Codec输出驱动耳机或者是喇叭的音频信号。在具体实现上,ASoC又把Platform驱动分为两个部分: snd_soc_platform_driver和snd_soc_dai_driver。其中,platform_driver负责管理音频数据,把音频数据通过dma或其他操作传送至cpudai中,dai_driver则主要完成cpu一侧的dai的参数配置,同时也会通过一定的途径把必要的dma等参数与snd_soc_platform_driver进行交互。
Linux内核版本:4.1.15
主芯片:IMX6ULL
codec芯片:WM8960
二、snd_soc_dai_driver
2.1、snd_soc_dai_driver的注册
dai驱动通常对应cpu的一个或几个I2S/PCM接口,实现一个dai驱动大致可以分为以下几个步骤:
- 定义一个snd_soc_dai_driver结构的实例;
- 在对应的platform_driver中的probe回调中通过API: snd_soc_register_dai或者snd_soc_register_dais注册snd_soc_dai实例;
- 实现snd_soc_dai_driver结构中的probe、suspend等回调;
- 实现snd_soc_dai_driver结构中的snd_soc_dai_ops字段中的回调函数;
具体代码流程如下(sound\soc\fsl\fsl_sai.c):
只是部分代码片段,详情请参考具体代码
/*snd_soc_dai_driver结构的实例*/
static struct snd_soc_dai_driver fsl_sai_dai = {
.probe = fsl_sai_dai_probe,
.playback = {
.stream_name = "CPU-Playback",
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 192000,
.rates = SNDRV_PCM_RATE_KNOT,
.formats = FSL_SAI_FORMATS,
},
.capture = {
.stream_name = "CPU-Capture",
.channels_min = 1,
.channels_max = 2,
.rate_min = 8000,
.rate_max = 192000,
.rates = SNDRV_PCM_RATE_KNOT,
.formats = FSL_SAI_FORMATS,
},
.ops = &fsl_sai_pcm_dai_ops,
};
/*platform 平台probe函数*/
static int fsl_sai_probe(struct platform_device *pdev)
{
/*注册component组件参数为fsl_component fsl_sai_dai*/
ret = devm_snd_soc_register_component(&pdev->dev, &fsl_component,
&fsl_sai_dai, 1);
}
/*进入devm_snd_soc_register_component函数*/
int devm_snd_soc_register_component(struct device *dev,
const struct snd_soc_component_driver *cmpnt_drv,
struct snd_soc_dai_driver *dai_drv, int num_dai)
{
/*调用snd_soc_register_component注册cmpnt_drv、 dai_drv */
ret = snd_soc_register_component(dev, cmpnt_drv, dai_drv, num_dai);
return ret;
}
/*进入snd_soc_register_component函数*/
int snd_soc_register_component(struct device *dev,
const struct snd_soc_component_driver *cmpnt_drv,
struct snd_soc_dai_driver *dai_drv,
int num_dai)
{
/*调用snd_soc_register_dais 注册dai_drv*/
ret = snd_soc_register_dais(cmpnt, dai_drv, num_dai, true);
return ret;
}
/*进入snd_soc_register_dais函数*/
static int snd_soc_register_dais(struct snd_soc_component *component,
struct snd_soc_dai_driver *dai_drv, size_t count,
bool legacy_dai_naming)
{
/**申请dai空间 */
dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL);
/*将dai->list添加到component->dai_list中去*/
list_add(&dai->list, &component->dai_list);
return ret;
}
/*此时cpu_dai接口注册完成*/
2.2、snd_soc_dai 重要字段
snd_soc_dai 表现形式:
struct snd_soc_dai {
const char *name;
int id;
struct device *dev;
/* driver ops */
struct snd_soc_dai_driver *driver;
/* 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;
void *capture_dma_data;
/* 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;
struct snd_soc_component *component;
/* CODEC TDM slot masks and params (for fixup) */
unsigned int tx_mask;
unsigned int rx_mask;
struct list_head list;
};
snd_soc_dai 该结构在snd_soc_register_dai函数中通过动态内存申请获得.简要介绍一下几个重要字段:
- driver 指向关联的snd_soc_dai_driver结构,由注册时通过参数传入.
- playback_dma_data 用于保存该dai播放stream的dma信息目标地址,dma传送单元大小和通道号等;
- capture_dma_data 同上,用于录音stream;
- platform指向关联的snd_soc_platform结构;
2.3、snd_soc_dai_driver关键字
snd_soc_dai_driver表现形式:
struct snd_soc_dai_driver {
/* DAI description */
const char *name;
unsigned int id;
unsigned int base;
/* 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 */
bool compress_dai;
/* DAI is also used for the control bus */
bool bus_control;
/* ops */
const struct snd_soc_dai_ops *ops;
/* DAI capabilities */
struct snd_soc_pcm_stream capture;
struct snd_soc_pcm_stream playback;
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;
};
snd_soc_dai_driver该结构需要自己根据不同的soc芯片进行定义,关键字段介绍如下:
- probe、remove回调函数,分别在声卡加载和卸载时被调用;
- suspend、 resume 电源管理回调函数;
- ops指向snd_soc_dai_ops结构,用于配置和控制该dai;
- playback snd_soc_pcm_stream结构,用于指出该dai支持的声道数,码率,数据格式等能力;.
- capture snd_soc_pcm_stream结构,用于指出该dai支持的声道数,码率,数据格式等能力;
2.3、snd_soc_dai_driver中的ops字段
ops字段指向一个snd_soc_dai_ops结构,该结构实际上是一组回调函数的集合,dai的配置和控制几乎都是通过这些回调函数来实现的,这些回调函数基本可以分为3大类,驱动程序可以根据实际情况实现其中的一部分:
工作时钟配置函数通常由machine驱动调用:
- set_sysclk设置dai的主时钟;
- set_pll设置PLL参数;
- set_clkdiv设置分频系数;
dai的格式配置参数,通常也由machine驱动调用:
- set_fmt设置dai的格式;
- set_tdm_slot如果dai支持时分复用,用于设置时分复用的slot;. set_channel_map声道的时分复用映射设置;
- set_tristate设置dai引脚的状态,当与其他dai并联使用同一引脚时需要使用该回调;
标准的snd_soc_ops回调通常由soc-core在进行PCM操作时调用:
startup:打开设备,设备开始工作的时候回调
shutdown.:关闭设备前调用
hw_params:设置硬件的相关参数
trigger:DAM开始时传输,结束传输,暂停传世,恢复传输的时候被回调
三、snd_soc_platform_driver
3.1、snd_soc_platform_driver的注册
snd_soc_platform_driver的注册流程:
- 定义一个snd_soc_platform_driver结构的实例;
- 一般在platform_driver的probe回调中利用ASoC的APl: snd_soc_register_platform()注册上面定义的实例;。
- 实现snd_soc_platform_driver中的各个回调函数;
在4.1.15 IM6ULL中WM8960Platform驱动注册代码流程如下:
/*snd_soc_platform_driver实例*/
static const struct snd_soc_platform_driver dmaengine_pcm_platform = {
.component_driver = {
.probe_order = SND_SOC_COMP_ORDER_LATE,
},
.ops = &dmaengine_pcm_ops,
.pcm_new = dmaengine_pcm_new,
};
/*注册流程*/
static int fsl_sai_probe(struct platform_device *pdev)
{
imx_pcm_dma_init(pdev, buffer_size);
}
int imx_pcm_dma_init(struct platform_device *pdev, size_t size)
{
devm_snd_dmaengine_pcm_register(&pdev->dev,
config,
SND_DMAENGINE_PCM_FLAG_COMPAT);
}
int devm_snd_dmaengine_pcm_register(struct device *dev,
const struct snd_dmaengine_pcm_config *config, unsigned int flags)
{
snd_dmaengine_pcm_register(dev, config, flags);
}
int snd_dmaengine_pcm_register(struct device *dev,
const struct snd_dmaengine_pcm_config *config, unsigned int flags)
{
snd_soc_add_platform(dev, &pcm->platform,
&dmaengine_pcm_platform);
}
int snd_soc_add_platform(struct device *dev, struct snd_soc_platform *platform,
const struct snd_soc_platform_driver *platform_drv)
{
list_add(&platform->list, &platform_list);
}
/*最终调用list_add把Platform驱动添加到platform_list列表中*/
3.2、snd_soc_platform_driver中的ops字段
snd_soc_platform_driver表现形式如下:
/* SoC platform interface */
struct snd_soc_platform_driver {
int (*probe)(struct snd_soc_platform *);
int (*remove)(struct snd_soc_platform *);
struct snd_soc_component_driver component_driver;
/* pcm creation and destruction */
int (*pcm_new)(struct snd_soc_pcm_runtime *);
void (*pcm_free)(struct snd_pcm *);
/*
* For platform caused delay reporting.
* Optional.
*/
snd_pcm_sframes_t (*delay)(struct snd_pcm_substream *,
struct snd_soc_dai *);
/* platform stream pcm ops */
const struct snd_pcm_ops *ops;
/* platform stream compress ops */
const struct snd_compr_ops *compr_ops;
int (*bespoke_trigger)(struct snd_pcm_substream *, int);
};
snd_soc_platform_driver中的ops字段指的是snd_pcm_ops, 表现形式如下:
struct snd_pcm_ops {
int (*open)(struct snd_pcm_substream *substream);
int (*close)(struct snd_pcm_substream *substream);
int (*ioctl)(struct snd_pcm_substream * substream,
unsigned int cmd, void *arg);
int (*hw_params)(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params);
int (*hw_free)(struct snd_pcm_substream *substream);
int (*prepare)(struct snd_pcm_substream *substream);
int (*trigger)(struct snd_pcm_substream *substream, int cmd);
snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *substream);
int (*get_time_info)(struct snd_pcm_substream *substream,
struct timespec *system_ts, struct timespec *audio_ts,
struct snd_pcm_audio_tstamp_config *audio_tstamp_config,
struct snd_pcm_audio_tstamp_report *audio_tstamp_report);
int (*copy)(struct snd_pcm_substream *substream, int channel,
snd_pcm_uframes_t pos,
void __user *buf, snd_pcm_uframes_t count);
int (*silence)(struct snd_pcm_substream *substream, int channel,
snd_pcm_uframes_t pos, snd_pcm_uframes_t count);
struct page *(*page)(struct snd_pcm_substream *substream,
unsigned long offset);
int (*mmap)(struct snd_pcm_substream *substream, struct vm_area_struct *vma);
int (*ack)(struct snd_pcm_substream *substream);
};
该ops字段是一个snd_pcm_ops结构,实现该结构中的各个回调函数是soc platform驱动的主要工作,他们基本都涉及dma操作以及dma buffer的管理等工作。下面介绍几个重要的回调函数:
open:当应用程序打开一个pcm设备时,该函数会被调用,通常,该函数会使用snd_soc_set_runtime_hwparams()设置substream中的snd_pcm_runtime结构里面的hw_params相关字段,然后为snd_pcm_runtime的private_data字段申请一个私有结构,用于保存该平台的dma参数。
hw_params:驱动的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的地址和大小等参数。要注意的是,该回调可能会被多次调用,具体实现时要小心处理多次申请资源的问题。
prepare:正式开始数据传送之前会调用该函数,该函数通常会完成dma操作的必要准备工作。
trigger:数据传送的开始,暂停,恢复和停止时,该函数会被调用。
pointer:该函数返回传送数据的当前位置。