PCM data flow - 4 - ASoC platform driver

概述中提到音频Platform驱动主要作用是音频数据的传输,这里又细分为两步:

·          把音频数据从dma buffer通过dma操作搬运到cpu_dai FIFO,这部分驱动用snd_soc_platform_driver描述,后面分析用pcm_dma指代它。

·          把音频数据从cpu_dai FIFO通过数字音频接口(I2S/PCM/AC97)传送到codec_dai,这部分驱动用snd_soc_dai_driver描述,后面分析用cpu_dai指代它。

那么dma buffer中的音频数据从哪而来?保留这个问题,在后面章节pcm native分析。


我们浏览下platform_drv中的几个重要的结构体,其中浅蓝色部分是cpu_dai相关的,浅绿色部分是pcm_dma相关的。而snd_soc_dai是cpu_dai注册时所创建的dai实例,snd_soc_platform是pcm_dma注册时所创建的platform实例,这些实例方便asoc-core管理。从snd_soc_dai中,我们看到一个dai必须绑定它链结的codec和platform。



cpu dai


从上图我们可知,实现一个cpu_dai驱动主要工作有:

·          实现dai操作函数,见snd_soc_dai_ops字段定义,用于配置控制dai,如系统时钟设置set_sysclk()、格式设置set_fmt()、硬件参数设置hw_params()、触发dai启动或停止传输trigger()等。

·          实现dai_drv的probe函数(dai初始化工作)、remove函数(dai卸载工作)、suspend/resume函数(dai电源管理配置)

·          初始化dai_drv实例snd_soc_dai_driver,包括playback和capture的能力描述信息、dai操作函数集指针、probe/remove回调、电源管理相关的suspend/resume回调。

·          通过snd_soc_register_dai()把初始化完成的snd_soc_dai_driver注册到soc-core:首先创建并初始化一个snd_soc_dai实例,然后把该snd_soc_dai实例插入到dai_list链表,Machine驱动初始化时会遍历该链表,以找到dai_link声明的cpu_dai并绑定。

/**
 * snd_soc_register_dai - Register a DAI with the ASoC core
 *
 * @dai: DAI to register
 */
int snd_soc_register_dai(struct device *dev,
        struct snd_soc_dai_driver *dai_drv)
{
    struct snd_soc_dai *dai;

    dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL);
    if (dai == NULL)
        return -ENOMEM;

    /* create DAI component name */
    dai->name = fmt_single_name(dev, &dai->id);
    if (dai->name == NULL) {
        kfree(dai);
        return -ENOMEM;
    }

    dai->dev = dev;
    dai->driver = dai_drv;
    if (!dai->driver->ops)
        dai->driver->ops = &null_dai_ops;

    mutex_lock(&client_mutex);
    list_add(&dai->list, &dai_list);
    mutex_unlock(&client_mutex);

    return 0;
}

注意dai操作函数的实现是cpu_dai驱动的主体,主要工作是配置好相关寄存器让硬件正常运转,因此这依赖于具体平台的cpu dai specification。snd_soc_dai_ops字段的详细说明见Codec audio operations章节。

cpu_dai驱动应该算是这个系列中最简单的一环,因此不多花费笔墨在这里了。倒是某些平台上,dma设备信息(设备的总线地址、通道号、传输单元大小)是在这里初始化的,这点要留意,这些dma设备信息在pcm_dma驱动中用到。

以Exynos平台为例,代码位置sound/soc/samsung/i2s.c。

Samsung Exynos平台的音频dma设备信息用s3c_dma_params结构体描述:

struct s3c_dma_params {
    struct s3c2410_dma_client *client;  /* stream identifier */
    int channel;                /* Channel ID */
    dma_addr_t dma_addr;
    int dma_size;           /* Size of the DMA transfer */
    unsigned ch;
    struct samsung_dma_ops *ops;
};

·          client:流标识符

·          channel:通道号

·          dma_addr:设备的总线地址,这里通常指向I2S tx FIFO或I2S rx FIFO地址

·          dma_size:dma传输单元大小

·          ops:平台dma操作函数

sound/soc/samsung/i2s.c中设置dma设备信息的相关代码片段:

struct i2s_dai {
    // ...
    /* Driver for this DAI */
    struct snd_soc_dai_driver i2s_dai_drv;
    /* DMA parameters */
    struct s3c_dma_params dma_playback; // playback dma描述信息
    struct s3c_dma_params dma_capture;  // capture dma描述信息
    struct s3c_dma_params idma_playback;// playback idma描述信息,idma仅用于回放,用于三星平台的LPA(低功耗音频)模式
    // ...
};

static __devinit int samsung_i2s_probe(struct platform_device *pdev)
{
    // ...
    // 从platform_device中取得resource,得到playback dma通道号
    res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
    if (!res) {
        dev_err(&pdev->dev, "Unable to get I2S-TX dma resource\n");
        return -ENXIO;
    }
    dma_pl_chan = res->start; // dma_pl_chan中的pl是playback简写

    // 从platform_device中取得resource,得到capture dma通道号
    res = platform_get_resource(pdev, IORESOURCE_DMA, 1);
    if (!res) {
        dev_err(&pdev->dev, "Unable to get I2S-RX dma resource\n");
        return -ENXIO;
    }
    dma_cp_chan = res->start; // dma_cp_chan中的cp是capture的简写

    // 从platform_device中取得resource,得到playback idma通道号
    res = platform_get_resource(pdev, IORESOURCE_DMA, 2);
    if (res)
        dma_pl_sec_chan = res->start;
    else
        dma_pl_sec_chan = 0;

    // 从platform_device中取得resource,得到I2S的基地址
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!res) {
        dev_err(&pdev->dev, "Unable to get I2S SFR address\n");
        return -ENXIO;
    }

    if (!request_mem_region(res->start, resource_size(res),
                            "samsung-i2s")) {
        dev_err(&pdev->dev, "Unable to request SFR region\n");
        return -EBUSY;
    }
    regs_base = res->start;
    
    // ...
    pri_dai->dma_playback.dma_addr = regs_base + I2STXD; // 设置playback dma设备地址为I2S tx FIFO地址
    pri_dai->dma_capture.dma_addr = regs_base + I2SRXD; // 设置capture dma设备地址为I2S rx FIFO地址
    pri_dai->dma_playback.client =
        (struct s3c2410_dma_client *)&pri_dai->dma_playback;
    pri_dai->dma_capture.client =
        (struct s3c2410_dma_client *)&pri_dai->dma_capture;
    pri_dai->dma_playback.channel = dma_pl_chan; // 设置playback dma通道号
    pri_dai->dma_capture.channel = dma_cp_chan; // 设置capture dma通道号
    pri_dai->src_clk = i2s_cfg->src_clk;
    pri_dai->dma_playback.dma_size = 4; // 设置playback dma传输单元大小为4个字节
    pri_dai->dma_capture.dma_size = 4; // 设置capture dma传输单元大小为4个字节
我们再看看Board初始化时,如何设定platform_device的resource,文件arch/arm/mach-exynos/dev-audio.c:
static struct resource exynos4_i2s0_resource[] = {
    [0] = {
        .start  = EXYNOS4_PA_I2S0, // start字段保存的是I2S基地址
        .end    = EXYNOS4_PA_I2S0 + 0x100 - 1,
        .flags  = IORESOURCE_MEM,  // 置该resource标识为MEM资源
    },
    [1] = {
        .start  = DMACH_I2S0_TX,   // start字段保存的是用于回放的dma通道号
        .end    = DMACH_I2S0_TX,
        .flags  = IORESOURCE_DMA,  // 置该resource标识为DMA资源
    },
    [2] = {
        .start  = DMACH_I2S0_RX,   // start字段保存的是用于录制的dma通道号
        .end    = DMACH_I2S0_RX,
        .flags  = IORESOURCE_DMA,  // 置该resource标识为DMA资源
    },
    [3] = {
        .start  = DMACH_I2S0S_TX,  // start字段保存的是用于回放的idma通道号
        .end    = DMACH_I2S0S_TX,
        .flags  = IORESOURCE_DMA,  // 置该resource标识为DMA资源
    },
};

struct platform_device exynos4_device_i2s0 = {
    .name = "samsung-i2s", // platform_device名称标识为"samsung-i2s",将与i2s.c中的samsung_i2s_driver匹配
    .id = 0,
    .num_resources = ARRAY_SIZE(exynos4_i2s0_resource),
    .resource = exynos4_i2s0_resource,
    .dev = {
        .platform_data = &i2sv5_pdata,
    },
};

当samsung_i2s_driver初始化时,通过platform_get_resource()函数来获取platform_device声明的resource。


struct resource结构中我们通常关心start、end和flags这3个字段,分别标明资源的开始值、结束值和类型。flags可以为IORESOURCE_IO、IORESOURCE_MEM、IORESOURCE_IRQ、IORESOURCE_DMA等。start、end的含义会随着flags而变更,如当flags为IORESOURCE_MEM时,start、end分别表示该platform_device占据的内存的开始地址和结束地址;当flags为IORESOURCE_IRQ时,start、end分别表示该platform_device使用的中断号的开始值和结束值,如果只使用了1个中断号,开始和结束值相同。对于同种类型的资源而言,可以有多份,譬如说某设备占据了2个内存区域,则可以定义2个IORESOURCE_MEM资源。
以上内容摘自:http://21cnbao.blog.51cto.com/109393/337609


pcm dma


pcm数据管理可以说是ALSA系统中最核心的部分,这部分的工作有两个:

·          copy_from_user把用户态的音频数据拷贝到dma buffer;

·          启动dma把音频数据从dma buffer传送到I2S tx FIFO,这是回放的情形。

当数据送到I2S tx FIFO后,剩下的是启动I2S把数据传送到codec,然后DAC把数字PCM信号转换成模拟信号,送给SPK/HS输出,关于I2S(cpu_dai)和codec,在以上两章已经描述清楚了。

为什么要使用dma传输?两个原因:首先在数据传输过程中,不需要cpu的参与,节省了cpu的开销;其次传输速度快,提高硬件设备的吞吐量。对于Arm,它不能直接把数据从A地址搬运到B地址,只能把数据从A地址搬运到一个寄存器,然后再从这个寄存器搬运到B地址;而dma有突发(Burst)传输能力,这种模式下一次能传输几个甚至十几个字节的数据,尤其适合大数据的高速传输。一个dma传输块里面,可以划分若干个帧,每传输完一帧都可以产生一个中断。

写这个文档的初衷是为了描述清楚pcm数据流向,这里先剖析pcm dma驱动,以便后面pcm native的分析。

以Exynos平台为例,代码位置sound/soc/samsung/dma.c。


·          浅绿色:pcm_dma驱动共有的实现

·          浅灰色:samsung exynos平台的pcm_dma驱动特有的实现

·          浅紫色:pcm native关键结构

·          浅橙色:snd_soc_platform是pcm_dma注册时所创建的platform实例


我们先看看dma相关的结构,对于回放来说,dma把内存缓冲区的音频数据传送到I2S tx FIFO;对于录制来说,dma把I2S rx FIFO的音频数据传送到内存缓存区。因此在dma传输之前,必须确定data buffer和I2S FIFO信息。

snd_dma_buffer:数据缓存区,用于保存从用户态拷贝过来的音频数据,包括dma buffer的物理首地址,虚拟首地址、大小等信息;其中物理地址用于设定dma传输的源地址(回放情形)或目的地址(录制情形),虚拟地址用于与用户态的音频数据拷贝。

s3c_dma_params:dma设备描述,包括设备总线地址(回放的情形下为I2S tx FIFO首地址,设置为dma传输的目的地址)、dma通道号、dma传输数据单元大小,这些信息在i2s.c中获取。

runtime_data:dma运行期信息

·          state:记录dma状态,启动或停止;

·          dma_loaded:dma装载计数,每当启动一次dma传输,该计数加一;每当完成一次dma传输,该计数减一;

·          dma_period:dma周期数据大小;

·          dma_start:指向dma buffer的物理首地址;

·          dma_pos:记录dma buffer当前指针位置,当dma每传输一次数据,都会更新该指针;

·          dma_end:dma buffer结束位置;

·          params:dma设备描述信息,包括设备总线地址、dma通道号、传输单元大小。


pcm operations


pcm_dma操作函数的实现是本模块实现的主体,它用snd_pcm_ops结构体描述:

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

下面介绍几个重要的回调函数:

·          open:打开pcm逻辑设备时,会回调该函数,用于为runtime设定硬件约束,为runtime的private_data申请一个私有结构,保存dma资源如通道号、传输单元、缓冲区信息、IO设备信息等。代码如下:

static const struct snd_pcm_hardware dma_hardware = {
    .info           = SNDRV_PCM_INFO_INTERLEAVED |
                    SNDRV_PCM_INFO_BLOCK_TRANSFER |
                    SNDRV_PCM_INFO_MMAP |
                    SNDRV_PCM_INFO_MMAP_VALID |
                    SNDRV_PCM_INFO_PAUSE |
                    SNDRV_PCM_INFO_RESUME,
    .formats        = SNDRV_PCM_FMTBIT_S16_LE |
                    SNDRV_PCM_FMTBIT_U16_LE |
                    SNDRV_PCM_FMTBIT_U8 |
                    SNDRV_PCM_FMTBIT_S8,
    .channels_min       = 2,
    .channels_max       = 2,
    .buffer_bytes_max   = 128*1024,
    .period_bytes_min   = PAGE_SIZE,
    .period_bytes_max   = PAGE_SIZE*2,
    .periods_min        = 2,
    .periods_max        = 128,
    .fifo_size      = 32,
};

static int dma_open(struct snd_pcm_substream *substream)
{
    struct snd_pcm_runtime *runtime = substream->runtime;
    struct runtime_data *prtd;

    pr_debug("Entered %s\n", __func__);

    // 设置substream运行期信息中的hw字段,可以把dma_hardware看成该platform的硬件约束
    snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
    snd_soc_set_runtime_hwparams(substream, &dma_hardware);

    // 为runtime_data分配内存,用于保存dma资源,包括缓冲区信息、IO设备信息、通道号、传输单元大小 
    prtd = kzalloc(sizeof(struct runtime_data), GFP_KERNEL);
    if (prtd == NULL)
        return -ENOMEM;

    spin_lock_init(&prtd->lock);

    // runtime中的private_data字段指向runtime_data 
    runtime->private_data = prtd;
    return 0;
}

·          hw_params:设置pcm硬件参数时(cmd:SNDRV_PCM_IOCTL_HW_PARAMS),会回调该函数,一般用于初始化dma资源,包括通道号、传输单元、缓冲区信息、IO设备信息等。代码如下:

static int dma_hw_params(struct snd_pcm_substream *substream,
    struct snd_pcm_hw_params *params)
{
    struct snd_pcm_runtime *runtime = substream->runtime;
    struct runtime_data *prtd = runtime->private_data;
    struct snd_soc_pcm_runtime *rtd = substream->private_data;
    unsigned long totbytes = params_buffer_bytes(params); 
    struct s3c_dma_params *dma =
        snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); // 从cpu dai驱动i2s.c取得dma设备资源
    struct samsung_dma_info dma_info;

    /* return if this is a bufferless transfer e.g.
     * codec <--> BT codec or GSM modem -- lg FIXME */
    if (!dma)
        return 0;

    /* this may get called several times by oss emulation
     * with different params -HW */
    if (prtd->params == NULL) {
        /* prepare DMA */
        prtd->params = dma; // 该字段保存的是dma设备资源,如I2S tx FIFO地址、dma通道号、dma传输单元等

        prtd->params->ops = samsung_dma_get_ops(); // dma操作函数集,这些操作函数实现见:arch/arm/plat-samsung/dma-ops.c

        //...
        prtd->params->ch = prtd->params->ops->request(
                prtd->params->channel, &dma_info);
    }

    snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); // 这里把dma buffer相关信息赋给substream runtime,dma_buffer在创建pcm逻辑设备时分配

    runtime->dma_bytes = totbytes;

    spin_lock_irq(&prtd->lock);
    prtd->dma_loaded = 0;
    prtd->dma_period = params_period_bytes(params);
    prtd->dma_start = runtime->dma_addr; // dma buffer的物理首地址
    prtd->dma_pos = prtd->dma_start;
    prtd->dma_end = prtd->dma_start + totbytes;
    spin_unlock_irq(&prtd->lock);

    return 0;
}

 

·          prepare:当数据已准备好时(cmd:SNDRV_PCM_IOCTL_PREPARE),会回调该函数告知dma数据已就绪。代码如下:

static int dma_prepare(struct snd_pcm_substream *substream)
{
    struct runtime_data *prtd = substream->runtime->private_data;
    int ret = 0;

    pr_debug("Entered %s\n", __func__);

    /* return if this is a bufferless transfer e.g.
     * codec <--> BT codec or GSM modem -- lg FIXME */
    if (!prtd->params)
        return 0;

    /* flush the DMA channel */
    prtd->params->ops->flush(prtd->params->ch);

    prtd->dma_loaded = 0; // 初始化dma装载计数
    prtd->dma_pos = prtd->dma_start; // 设置dma buffer当前指针为dma buffer首地址

    /* enqueue dma buffers */
    dma_enqueue(substream); // 插入到dma传输队列中

    return ret;
}

dma_enqueue()函数,把当前dma buffer插入到dma传输队列中,当触发trigger()启动dma传输后,dma系统将会把dma buffer数据传送到FIFO(回放的情形)。

注意:每次dma传输完成后,都要调用snd_pcm_period_elapsed()告知pcm native一个周期的数据已经传送到FIFO上了,然后再次调用dma_enqueue(),dma传输...如此循环,直到触发trigger()停止dma传输。

·          trigger:pcm数据传送开始、停止、暂停、恢复时,会回调该函数启动或停止dma传输(补充:当上层第一次调用pcm_write()时,触发trigger启动dma传输;当上层调用pcm_stop()或pcm_drop()时,触发trigger停止dma传输)。trigger函数里面的操作必须是原子的,不能有可能引起睡眠的操作,并且应尽量简单。代码如下:

static int dma_trigger(struct snd_pcm_substream *substream, int cmd)
{
    struct runtime_data *prtd = substream->runtime->private_data;
    int ret = 0;

    pr_debug("Entered %s\n", __func__);

    spin_lock(&prtd->lock);

    switch (cmd) {
    case SNDRV_PCM_TRIGGER_START:
    case SNDRV_PCM_TRIGGER_RESUME:
    case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
        prtd->state |= ST_RUNNING;
        prtd->params->ops->trigger(prtd->params->ch); // 启动dma传输
        break;

    case SNDRV_PCM_TRIGGER_STOP:
    case SNDRV_PCM_TRIGGER_SUSPEND:
    case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
        prtd->state &= ~ST_RUNNING;
        prtd->params->ops->stop(prtd->params->ch); // 停止dma传输
        break;

    default:
        ret = -EINVAL;
        break;
    }

    spin_unlock(&prtd->lock);

    return ret;
}

·          pointer:该回调函数返回传输数据的当前位置。当dma每完成一次传输后,都会调用该函数获得传输数据的当前位置,这样pcm native可根据它来计算dma buffer指针位置及可用空间。该函数也是原子的。代码如下:

static snd_pcm_uframes_t
dma_pointer(struct snd_pcm_substream *substream)
{
    struct snd_pcm_runtime *runtime = substream->runtime;
    struct runtime_data *prtd = runtime->private_data;
    unsigned long res;

    pr_debug("Entered %s\n", __func__);

    res = prtd->dma_pos - prtd->dma_start; // 当前位置减去首地址,其实就是已传输数据的size

    pr_debug("Pointer offset: %lu\n", res);

    /* we seem to be getting the odd error from the pcm library due
     * to out-of-bounds pointers. this is maybe due to the dma engine
     * not having loaded the new values for the channel before being
     * called... (todo - fix )
     */

    if (res >= snd_pcm_lib_buffer_bytes(substream)) {
        if (res == snd_pcm_lib_buffer_bytes(substream))
            res = 0;
    }

    return bytes_to_frames(substream->runtime, res); // 单位转化为frames
}


dma buffer allocation


pcm operations小节,数次提及dma buffer,即dma数据缓冲区,用于保存上层拷贝过来的音频数据。dma buffer的分配,一般发生在pcm_dma驱动初始化阶段(probe)或pcm逻辑设备创建阶段(pcm_new)。代码如下:

static int preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
{
    struct snd_pcm_substream *substream = pcm->streams[stream].substream;
    struct snd_dma_buffer *buf = &substream->dma_buffer;
    size_t size = dma_hardware.buffer_bytes_max; // dma buffer大小不得超过hardware的buffer_bytes_max

    buf->dev.type = SNDRV_DMA_TYPE_DEV;
    buf->dev.dev = pcm->card->dev;
    buf->private_data = NULL;
    buf->area = dma_alloc_writecombine(pcm->card->dev, size,
                       &buf->addr, GFP_KERNEL); // area字段是dma buffer的虚拟首地址,addr字段是dma buffer的物理首地址
    if (!buf->area)
        return -ENOMEM;
    buf->bytes = size;
    return 0;
}

static int dma_new(struct snd_soc_pcm_runtime *rtd)
{
    struct snd_card *card = rtd->card->snd_card;
    struct snd_pcm *pcm = rtd->pcm;
    int ret = 0;

    if (!card->dev->dma_mask)
        card->dev->dma_mask = &dma_mask;
    if (!card->dev->coherent_dma_mask)
        card->dev->coherent_dma_mask = DMA_BIT_MASK(32);

    if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
        ret = preallocate_dma_buffer(pcm, // 为playback stream分配的dma buffer
            SNDRV_PCM_STREAM_PLAYBACK);
        if (ret)
            goto out;
    }

    if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
        ret = preallocate_dma_buffer(pcm, // 为capture stream分配的dma buffer
            SNDRV_PCM_STREAM_CAPTURE);
        if (ret)
            goto out;
    }
out:
    return ret;
}

static struct snd_soc_platform_driver samsung_asoc_platform = {
    .ops        = &dma_ops,
    .pcm_new    = dma_new,
    .pcm_free   = dma_free_dma_buffers,
};

当soc-core调用soc_new_pcm()创建pcm逻辑设备时,会回调pcm_new()完成dma buffer的分配,注意回放子流和录制子流都有着各自的dma buffer。


pcm dma register


上两个小节,我们介绍了pcm_dma操作函数的作用及实现和dma buffer的分配,本小节分析pcm_dma注册过程。

当platform_driver:

static struct platform_driver asoc_dma_driver = {
    .driver = {
        .name = "samsung-audio",
        .owner = THIS_MODULE,
    },
    .probe = samsung_asoc_platform_probe,
    .remove = __devexit_p(samsung_asoc_platform_remove),
};

name = "samsung-audio"的platform_device(该platform_device在arch/arm/plat-samsung/devs.c中创建)匹配后,立即回调samsung_asoc_platform_probe()注册platform:

static struct snd_soc_platform_driver samsung_asoc_platform = {
    .ops        = &dma_ops,
    .pcm_new    = dma_new,
    .pcm_free   = dma_free_dma_buffers,
};

static int __devinit samsung_asoc_platform_probe(struct platform_device *pdev)
{
    return snd_soc_register_platform(&pdev->dev, &samsung_asoc_platform);
}

snd_soc_register_platform:将platform_drv注册到asoc-core。

·          创建一个snd_soc_platform实例,包含platform_drv(snd_soc_platform_driver)的相关信息,封装给asoc-core使用。

·          把以上创建并初始化好的platform插入到platform_list链表上,Machine驱动初始化时会遍历该链表,以找到dai_link声明的platform并绑定。

代码实现:

/**
 * snd_soc_register_platform - Register a platform with the ASoC core
 *
 * @platform: platform to register
 */
int snd_soc_register_platform(struct device *dev,
        struct snd_soc_platform_driver *platform_drv)
{
    struct snd_soc_platform *platform;

    platform = kzalloc(sizeof(struct snd_soc_platform), GFP_KERNEL);
    if (platform == NULL)
        return -ENOMEM;

    /* create platform component name */
    platform->name = fmt_single_name(dev, &platform->id);
    if (platform->name == NULL) {
        kfree(platform);
        return -ENOMEM;
    }

    platform->dev = dev;
    platform->driver = platform_drv;
    platform->dapm.dev = dev;
    platform->dapm.platform = platform;
    platform->dapm.stream_event = platform_drv->stream_event;
    mutex_init(&platform->mutex);

    mutex_lock(&client_mutex);
    list_add(&platform->list, &platform_list);
    mutex_unlock(&client_mutex);

    return 0;
}


至此,完成了platform驱动的实现,回放的情形下,pcm_dma负责把dma buffer中的数据搬运到I2S tx FIFO,I2S负责把FIFO中的数据传送到codec。

至于alsa如何把音频数据从userspace拷贝到dma buffer,如何管理dma buffer,如何启动I2S和DMA传输,见后续pcm native分析。

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是将TinyALSA转换为ALSA SoC驱动程序的示例代码: 1. 定义硬件描述符 ``` static struct snd_soc_dai_driver my_dai = { .name = "my_dai", .playback = { .stream_name = "Playback", .channels_min = 1, .channels_max = 2, .rates = SNDRV_PCM_RATE_8000_48000, .formats = SNDRV_PCM_FMTBIT_S16_LE, }, .capture = { .stream_name = "Capture", .channels_min = 1, .channels_max = 2, .rates = SNDRV_PCM_RATE_8000_48000, .formats = SNDRV_PCM_FMTBIT_S16_LE, }, }; ``` 2. 注册SoC驱动程序 ``` static int my_probe(struct platform_device *pdev) { int ret; ret = snd_soc_register_dai(&pdev->dev, &my_dai); if (ret) { dev_err(&pdev->dev, "Failed to register DAI: %d\n", ret); return ret; } return 0; } static int my_remove(struct platform_device *pdev) { snd_soc_unregister_dai(&pdev->dev); return 0; } static const struct of_device_id my_of_match[] = { { .compatible = "my,codec" }, {}, }; MODULE_DEVICE_TABLE(of, my_of_match); static struct platform_driver my_driver = { .driver = { .name = "my-driver", .owner = THIS_MODULE, .of_match_table = my_of_match, }, .probe = my_probe, .remove = my_remove, }; module_platform_driver(my_driver); ``` 3. 在SoC DAI中实现PCM操作 ``` static int my_dai_playback_trigger(struct snd_pcm_substream *substream, int cmd) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *codec_dai = rtd->codec_dai; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; switch (cmd) { case SNDRV_PCM_TRIGGER_START: // 实现开始播放的操作 break; case SNDRV_PCM_TRIGGER_STOP: // 实现停止播放的操作 break; } return 0; } static int my_dai_playback_prepare(struct snd_pcm_substream *substream) { return 0; } static struct snd_soc_ops my_dai_ops = { .trigger = my_dai_playback_trigger, .prepare = my_dai_playback_prepare, }; static struct snd_soc_dai_driver my_dai = { .name = "my_dai", .playback = { .stream_name = "Playback", .channels_min = 1, .channels_max = 2, .rates = SNDRV_PCM_RATE_8000_48000, .formats = SNDRV_PCM_FMTBIT_S16_LE, }, .ops = &my_dai_ops, }; ``` 以上是将TinyALSA转换为ALSA SoC驱动程序的示例代码,其中包括定义硬件描述符、注册SoC驱动程序、在SoC DAI中实现PCM操作等步骤。开发者可以根据自己的需求进行修改和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值