1. 程式人生 > >Android音訊驅動-ASOC之Platform

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);
}