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