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进行交互。
通常,ASoC把snd_soc_platform_driver注册为一个系统的platform_driver,不要被这两个相像的术语所迷惑,
snd_soc_platform_driver是针对ASoC子系统的,platform_driver是来自Linux的设备驱动模型。我们要做的就是:
定义一个snd_soc_platform_driver结构的实例;
在platform_driver的probe回调中利用ASoC的API:snd_soc_register_platform()注册上面定义的实例;
实现snd_soc_platform_driver中的各个回调函数;
/* SoC platform interface */
struct snd_soc_platform_driver {
int (*probe)(struct snd_soc_platform *);
int (*remove)(struct snd_soc_platform *);
int (*suspend)(struct snd_soc_dai *dai);
int (*resume)(struct snd_soc_dai *dai);
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);
};
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 (*wall_clock)(struct snd_pcm_substream *substream,
struct timespec *audio_ts);
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);
};
mt_soc_pcm_dl1_i2s0Dl1.c
static int __init mtk_I2S0dl1_soc_platform_init(void)
{
int ret;
soc_mtkI2S0dl1_dev = platform_device_alloc(MT_SOC_I2S0DL1_PCM, -1);
ret = platform_device_add(soc_mtkI2S0dl1_dev);
ret = platform_driver_register(&mtk_I2S0dl1_driver);
return ret;
}
/*static struct platform_driver mtk_I2S0dl1_driver = {//Linux platform driver
.driver = {
.name = MT_SOC_I2S0DL1_PCM,
.owner = THIS_MODULE,
},
.probe = mtk_I2S0dl1_probe,
.remove = mtk_I2S0dl1_remove,
};*/
//mtk_I2S0dl1_driver和soc_mtkI2S0dl1_dev匹配之后会调用probe函数
static int mtk_I2S0dl1_probe(struct platform_device *pdev)
{
pdev->dev.coherent_dma_mask = DMA_BIT_MASK(64);
if (!pdev->dev.dma_mask)
pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask;
if (pdev->dev.of_node)
dev_set_name(&pdev->dev, "%s", MT_SOC_I2S0DL1_PCM);//通过name匹配machine的platform driver
mDev = &pdev->dev;
return snd_soc_register_platform(&pdev->dev,
&mtk_I2S0dl1_soc_platform);//注册回调函数
}
PCM 数据管理可以说是 ALSA 系统中最核心的部分,这部分的工作有两个(回放情形):
copy_from_user 把用户态的音频数据拷贝到 dma buffer 中;
启动 dma 设备把音频数据从 dma buffer 传送到 I2S tx FIFO。
当数据送到 I2S tx FIFO 后,剩下的是启动 I2S 控制器把数据传送到 Codec,然后 DAC 把音频数字信号转换成模拟信号,
再输出到 SPK/HP。关于 I2S (cpu_dai) 和 Codec,在以上两章已经描述清楚了。
为什么要使用 dma 传输?两个原因:首先在数据传输过程中,不需要 cpu 的参与,节省 cpu 的开销;其次传输速度快,
提高硬件设备的吞吐量。对于 ARM,它不能直接把数据从 A 地址搬运到 B 地址,只能把数据从 A 地址搬运到一个寄存器,
然后再从这个寄存器搬运到 B 地址;而 dma 有突发(Burst)传输能力,这种模式下一次能传输几个甚至十几个字节的数据,
尤其适合大数据的高速传输。一个 dma 传输块里面,可以划分为若干个周期,每传输完一个周期产生一个中断。
Platform driver实例
//platform driver的实例,后续使用ops对PCM进行操作
/*snd_soc_platform_driver mtk_I2S0dl1_soc_platform = {
.ops = &mtk_I2S0dl1_ops,
.pcm_new = mtk_asoc_pcm_I2S0dl1_new,
.probe = mtk_afe_I2S0dl1_probe,
};
snd_pcm_ops mtk_I2S0dl1_ops = {
.open = mtk_pcm_I2S0dl1_open,
.close = mtk_pcm_I2S0dl1_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = mtk_pcm_I2S0dl1_hw_params,
.hw_free = mtk_pcm_I2S0dl1_hw_free,
.prepare = mtk_pcm_I2S0dl1_prepare,
.trigger = mtk_pcm_I2S0dl1_trigger,
.pointer = mtk_pcm_I2S0dl1_pointer,
.copy = mtk_pcm_I2S0dl1_copy,
.silence = mtk_pcm_I2S0dl1_silence,
.page = mtk_I2S0dl1_pcm_page,
};*/
//platform driver的probe函数,在声卡注册时调用
static int mtk_afe_I2S0dl1_probe(struct snd_soc_platform *platform)
{
snd_soc_add_platform_controls(platform, Audio_snd_I2S0dl1_controls,
ARRAY_SIZE(Audio_snd_I2S0dl1_controls));
//分配DMA内存
AudDrv_Allocate_mem_Buffer(platform->dev, Soc_Aud_Digital_Block_MEM_DL1,
Dl1_MAX_BUFFER_SIZE);
Dl1_Playback_dma_buf = Get_Mem_Buffer(Soc_Aud_Digital_Block_MEM_DL1);
return 0;
}
/*static const struct snd_kcontrol_new Audio_snd_I2S0dl1_controls[] = {
SOC_ENUM_EXT("Audio_I2S0dl1_hd_Switch", Audio_I2S0dl1_Enum[0],
Audio_I2S0dl1_hdoutput_Get, Audio_I2S0dl1_hdoutput_Set),
SOC_SINGLE_EXT("Audio IRQ1 CNT", SND_SOC_NOPM, 0, IRQ_MAX_RATE, 0,
Audio_Irqcnt1_Get, Audio_Irqcnt1_Set),
};
static int Audio_I2S0dl1_hdoutput_Set(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
PRINTK_AUD_DL1("%s()\n", __func__);
if (ucontrol->value.enumerated.item[0] > ARRAY_SIZE(I2S0dl1_HD_output)) {
pr_warn("return -EINVAL\n");
return -EINVAL;
}
mI2S0dl1_hdoutput_control = ucontrol->value.integer.value[0];
if (GetMemoryPathEnable(Soc_Aud_Digital_Block_MEM_HDMI) == true) {
pr_err("return HDMI enabled\n");
return 0;
}
return 0;
}
static int Audio_Irqcnt1_Set(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
irq1_cnt = ucontrol->value.integer.value[0];
pr_warn("%s()\n", __func__);
AudDrv_Clk_On();
if (irq_user_id && irq1_cnt)
irq_update_user(irq_user_id,
Soc_Aud_IRQ_MCU_MODE_IRQ1_MCU_MODE,
0,
irq1_cnt);
else
pr_warn("warn, cannot update irq counter, user_id = %p, irq1_cnt = %d\n",
irq_user_id, irq1_cnt);
AudDrv_Clk_Off();
return 0;
}
*/
注册platform control数组
int snd_soc_add_platform_controls(struct snd_soc_platform *platform,
const struct snd_kcontrol_new *controls, unsigned int num_controls)
{
return snd_soc_add_component_controls(&platform->component, controls,
num_controls);
}
int snd_soc_add_component_controls(struct snd_soc_component *component,
const struct snd_kcontrol_new *controls, unsigned int num_controls)
{
struct snd_card *card = component->card->snd_card;
return snd_soc_add_controls(card, component->dev, controls,
num_controls, component->name_prefix, component);
}
static int snd_soc_add_controls(struct snd_card *card, struct device *dev,
const struct snd_kcontrol_new *controls, int num_controls,
const char *prefix, void *data)
{
int err, i;
for (i = 0; i < num_controls; i++) {
const struct snd_kcontrol_new *control = &controls[i];
err = snd_ctl_add(card, snd_soc_cnew(control, data,
control->name, prefix));
}
return 0;
}
int snd_ctl_add(struct snd_card *card, struct snd_kcontrol *kcontrol)
{
struct snd_ctl_elem_id id;
unsigned int idx;
unsigned int count;
int err = -EINVAL;
id = kcontrol->id;
if (snd_ctl_find_id(card, &id)) {
err = -EBUSY;
goto error;
}
if (snd_ctl_find_hole(card, kcontrol->count) < 0) {
err = -ENOMEM;
goto error;
}
list_add_tail(&kcontrol->list, &card->controls);//将control添加到card->controls链表进行管理
card->controls_count += kcontrol->count;
kcontrol->id.numid = card->last_numid + 1;
card->last_numid += kcontrol->count;
count = kcontrol->count;
for (idx = 0; idx < count; idx++, id.index++, id.numid++)
snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_ADD, &id);
return 0;
}
snd_soc_platform_driver中的ops字段
该ops字段是一个snd_pcm_ops结构,实现该结构中的各个回调函数是soc platform驱动的主要工作,
他们基本都涉及dma操作以及dma buffer的管理等工作。下面介绍几个重要的回调函数:
ops.open:当应用程序打开一个pcm设备时,该函数会被调用,通常该函数会使用snd_soc_set_runtime_hwparams()设置substream中的snd_pcm_runtime结构里面 的hw_params相关字段,然后为snd_pcm_runtime的private_data字段申请一个私有结构,用于保存该平台的dma参数。
ops.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的地址和大小等参数。要注意的是,该回调可能会被多次调用,具体实现时要小心处理多次申请资源的问题。
ops.prepare:正式开始数据传送之前会调用该函数,该函数通常会完成dma操作的必要准备工作。
ops.trigger:数据传送的开始,暂停,恢复和停止时,该函数会被调用。
ops.pointer:该函数返回传送数据的当前位置。
注册Platform driver实例
snd_soc_register_platform() 该函数用于注册一个snd_soc_platform,只有注册以后,它才可以被Machine驱动使用。
它的代码已经清晰地表达了它的实现过程:
为snd_soc_platform实例申请内存;
从platform_device中获得它的名字,用于Machine驱动的匹配工作;
初始化snd_soc_platform的字段;
把snd_soc_platform实例连接到全局链表platform_list中;
int snd_soc_register_platform(struct device *dev,
const struct snd_soc_platform_driver *platform_drv)
{
struct snd_soc_platform *platform;
int ret;
platform = kzalloc(sizeof(struct snd_soc_platform), GFP_KERNEL);
ret = snd_soc_add_platform(dev, platform, platform_drv);
return ret;
}
int snd_soc_add_platform(struct device *dev, struct snd_soc_platform *platform,
const struct snd_soc_platform_driver *platform_drv)
{
int ret;
//初始化platform的component
ret = snd_soc_component_initialize(&platform->component, &platform_drv->component_driver, dev);
platform->dev = dev;
platform->driver = platform_drv;//保存platform的driver
if (platform_drv->probe)
platform->component.probe = snd_soc_platform_drv_probe;//保存platform driver的probe函数
if (platform_drv->remove)
platform->component.remove = snd_soc_platform_drv_remove;
snd_soc_component_add_unlocked(&platform->component);//将platform component插入component_list链表
list_add(&platform->list, &platform_list);//将platform插入platform_list链表
return 0;
}
/* static int snd_soc_platform_drv_probe(struct snd_soc_component *component)
{
struct snd_soc_platform *platform = snd_soc_component_to_platform(component);
return platform->driver->probe(platform);//调用platform driver的probe函数
}*/
static int snd_soc_component_initialize(struct snd_soc_component *component,
const struct snd_soc_component_driver *driver, struct device *dev)
{
struct snd_soc_dapm_context *dapm;
//匹配machine的platform name MT_SOC_I2S0DL1_PCM
component->name = fmt_single_name(dev, &component->id);
component->dev = dev;
component->driver = driver;//mtk_I2S0dl1_soc_platform没有实现component_driver
component->probe = component->driver->probe;
component->remove = component->driver->remove;
if (!component->dapm_ptr)
component->dapm_ptr = &component->dapm;
dapm = component->dapm_ptr;
dapm->dev = dev;
dapm->component = component;
dapm->bias_level = SND_SOC_BIAS_OFF;
dapm->idle_bias_off = true;
if (driver->seq_notifier)
dapm->seq_notifier = snd_soc_component_seq_notifier;
if (driver->stream_event)
dapm->stream_event = snd_soc_component_stream_event;
component->controls = driver->controls;
component->num_controls = driver->num_controls;
component->dapm_widgets = driver->dapm_widgets;
component->num_dapm_widgets = driver->num_dapm_widgets;
component->dapm_routes = driver->dapm_routes;
component->num_dapm_routes = driver->num_dapm_routes;
INIT_LIST_HEAD(&component->dai_list);
mutex_init(&component->io_mutex);
return 0;
}
static char *fmt_single_name(struct device *dev, int *id)
{
char *found, name[NAME_SIZE];
int id1, id2;
strlcpy(name, dev_name(dev), NAME_SIZE);
/* are we a "%s.%d" name (platform and SPI components) */
found = strstr(name, dev->driver->name);
if (found) {
/* get ID */
if (sscanf(&found[strlen(dev->driver->name)], ".%d", id) == 1) {
if (*id == -1)
found[strlen(dev->driver->name)] = '\0';
}
} else {
/* I2C component devices are named "bus-addr" */
if (sscanf(name, "%x-%x", &id1, &id2) == 2) {
char tmp[NAME_SIZE];
/* create unique ID number from I2C addr and bus */
*id = ((id1 & 0xffff) << 16) + id2;
/* sanitize component name for DAI link creation */
snprintf(tmp, NAME_SIZE, "%s.%s", dev->driver->name, name);
strlcpy(name, tmp, NAME_SIZE);
} else
*id = 0;
}
return kstrdup(name, GFP_KERNEL);
}