Android音訊驅動-ASOC之Platform
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);
}