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);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章