AlSA驱动中的PCM DMA

ASoC Platform驱动中使用了PCM DMA的构架来实现了申请DMA通道。

首先得probe函数中会调用ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
为设备注册一个dmaengine_pcm。

这个调用除了注册同时还注册了一个释放接口。

核心是调用了
snd_dmaengine_pcm_register(dev, config, flags);

int devm_snd_dmaengine_pcm_register(struct device *dev,
     const struct snd_dmaengine_pcm_config *config, unsigned int flags)  
 {  
    struct device **ptr;  
     int ret;  

     ptr = devres_alloc(devm_dmaengine_pcm_release, sizeof(*ptr), GFP_KERNEL);  
     if (!ptr)  
         return -ENOMEM;  

     ret = snd_dmaengine_pcm_register(dev, config, flags);  
     if (ret == 0) {  
         *ptr = dev;  
         devres_add(dev, ptr);  
     } else {  
         devres_free(ptr);  
     }  

     return ret;  
}  

snd_dmaengine_pcm_register()中通过dmaengine_pcm_request_chan_of()去申请DMA通道。

/**
 * snd_dmaengine_pcm_register - Register a dmaengine based PCM device
 * @dev: The parent device for the PCM device
 * @config: Platform specific PCM configuration
 * @flags: Platform specific quirks
 */
int snd_dmaengine_pcm_register(struct device *dev,
    const struct snd_dmaengine_pcm_config *config, unsigned int flags)
{
    struct dmaengine_pcm *pcm;
    int ret;

    pcm = kzalloc(sizeof(*pcm), GFP_KERNEL);
    if (!pcm)
        return -ENOMEM;

    pcm->config = config;
    pcm->flags = flags;

    ret = dmaengine_pcm_request_chan_of(pcm, dev, config);
    if (ret)
        goto err_free_dma;

    ret = snd_soc_add_platform(dev, &pcm->platform,
        &dmaengine_pcm_platform);
    if (ret)
        goto err_free_dma;

    return 0;

err_free_dma:
    dmaengine_pcm_release_chan(pcm);
    kfree(pcm);
    return ret;
}

dmaengine_pcm_request_chan_of()根据flag 标志以及驱动中定义的数组

static const char * const dmaengine_pcm_dma_channel_names[] = {
[SNDRV_PCM_STREAM_PLAYBACK] = “tx”,
[SNDRV_PCM_STREAM_CAPTURE] = “rx”,
};

获取DMA-name。为参数调用dma_request_slave_channel_reason()

static int dmaengine_pcm_request_chan_of(struct dmaengine_pcm *pcm,
    struct device *dev, const struct snd_dmaengine_pcm_config *config)
{
    unsigned int i;
    const char *name;
    struct dma_chan *chan;

    if ((pcm->flags & SND_DMAENGINE_PCM_FLAG_NO_DT) || !dev->of_node)
        return 0;

    if (config && config->dma_dev) {
        /*
         * If this warning is seen, it probably means that your Linux
         * device structure does not match your HW device structure.
         * It would be best to refactor the Linux device structure to
         * correctly match the HW structure.
         */
        dev_warn(dev, "DMA channels sourced from device %s",
             dev_name(config->dma_dev));
        dev = config->dma_dev;
    }

    for (i = SNDRV_PCM_STREAM_PLAYBACK; i <= SNDRV_PCM_STREAM_CAPTURE;
         i++) {
        if (pcm->flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX)
            name = "rx-tx";
        else
            name = dmaengine_pcm_dma_channel_names[i];
        if (config && config->chan_names[i])
            name = config->chan_names[i];
        chan = dma_request_slave_channel_reason(dev, name);
        if (IS_ERR(chan)) {
            if (PTR_ERR(chan) == -EPROBE_DEFER)
                return -EPROBE_DEFER;
            pcm->chan[i] = NULL;
        } else {
            pcm->chan[i] = chan;
        }
        if (pcm->flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX)
            break;
    }

    if (pcm->flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX)
        pcm->chan[1] = pcm->chan[0];

    return 0;
}


static const char * const dmaengine_pcm_dma_channel_names[] = {
    [SNDRV_PCM_STREAM_PLAYBACK] = "tx",
    [SNDRV_PCM_STREAM_CAPTURE] = "rx",
};

dma_request_slave_channel_reason()根据参数name 并解析device tree 来申请具体的DMA 通道。一个DMA控制器有8个channel,每两个作为一组,既可以输出也可以输入,但它们的控制代码是一样的,并且在DMA注册过程中,已经使DMA处于可用状态了,这里根据传入的name 参数以及在device tree 中的描述申请具体的通道。

device tree的描述如下:

axi_i2s_0: axi-i2s@0x77600000 {    
            compatible = "adi,axi-i2s-1.00.a";    
            reg = <0x77600000 0x1000>;    
             dmas = <&dmac_s 1 &dmac_s 2>;    
             dma-names = "tx", "rx";    
             clocks = <&clkc 15>, <&audio_clock>;    
             clock-names = "axi", "ref";    
        };  

每个需要使用DMA的client都会通过 dmas来引用DMA控制器和通道,通过dma-names实现name的匹配。

/**
 * of_dma_request_slave_channel - Get the DMA slave channel
 * @np:     device node to get DMA request from
 * @name:   name of desired channel
 *
 * Returns pointer to appropriate DMA channel on success or an error pointer.
 */
struct dma_chan *of_dma_request_slave_channel(struct device_node *np,
                          const char *name)
{
    struct of_phandle_args  dma_spec;
    struct of_dma       *ofdma;
    struct dma_chan     *chan;
    int         count, i, start;
    int         ret_no_channel = -ENODEV;
    static atomic_t     last_index;

    if (!np || !name) {
        pr_err("%s: not enough information provided\n", __func__);
        return ERR_PTR(-ENODEV);
    }

    /* Silently fail if there is not even the "dmas" property */
    if (!of_find_property(np, "dmas", NULL))
        return ERR_PTR(-ENODEV);

    count = of_property_count_strings(np, "dma-names");
    if (count < 0) {
        pr_err("%s: dma-names property of node '%s' missing or empty\n",
            __func__, np->full_name);
        return ERR_PTR(-ENODEV);
    }

    /*
     * approximate an average distribution across multiple
     * entries with the same name
     */
    start = atomic_inc_return(&last_index);
    for (i = 0; i < count; i++) {
        if (of_dma_match_channel(np, name,
                     (i + start) % count,
                     &dma_spec))
            continue;

        mutex_lock(&of_dma_lock);
        ofdma = of_dma_find_controller(&dma_spec);

        if (ofdma) {
            chan = ofdma->of_dma_xlate(&dma_spec, ofdma);
        } else {
            ret_no_channel = -EPROBE_DEFER;
            chan = NULL;
        }

        mutex_unlock(&of_dma_lock);

        of_node_put(dma_spec.np);

        if (chan)
            return chan;
    }

    return ERR_PTR(ret_no_channel);
}
  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值