首页 > 技术文章 > rk音频驱动之platform

wen123456 2020-11-26 15:50 原文

Rk_i2s.c (sound\soc\rockchip) 
1.入口函数
subsys_initcall_sync(rockchip_i2s_init); //在module_init前面加载
i2s1: i2s1@100b0000 {
                compatible = "rockchip-i2s";
                reg = <0x100b0000 0x1000>;
                i2s-id = <1>;
                clocks = <&clk_i2s1>, <&clk_i2s1_out>, <&clk_gates8 8>;
                clock-names = "i2s_clk", "i2s_mclk", "i2s_hclk";
                interrupts = <GIC_SPI 27 IRQ_TYPE_LEVEL_HIGH>;
                dmas = <&pdma 14>, <&pdma 15>;
                #dma-cells = <2>;
                dma-names = "tx", "rx";
                status = "disabled";
        };

static const struct of_device_id rockchip_i2s_match[] = {
 { .compatible = "rockchip-i2s", },{},};
static const struct dev_pm_ops rockchip_i2s_pm_ops = {
 SET_RUNTIME_PM_OPS(rockchip_i2s_runtime_suspend, rockchip_i2s_runtime_resume,
      NULL)
 SET_SYSTEM_SLEEP_PM_OPS(rockchip_i2s_suspend, rockchip_i2s_resume)
};
static struct platform_driver rockchip_i2s_driver = {
 .probe = rockchip_i2s_probe,
 .remove = rockchip_i2s_remove,
 .driver = {
  .name = "rockchip-i2s",
  .owner = THIS_MODULE,
  .of_match_table = of_match_ptr(rockchip_i2s_match),
  .pm = &rockchip_i2s_pm_ops,  //休眠唤醒的一些操作
 },};

rockchip_i2s_dai结构体
static struct snd_soc_dai_ops rockchip_i2s_dai_ops = {
 .trigger = rockchip_i2s_trigger,
 .hw_params = rockchip_i2s_hw_params,
 .set_fmt = rockchip_i2s_set_fmt,
 .set_clkdiv = rockchip_i2s_set_clkdiv,
 .set_sysclk = rockchip_i2s_set_sysclk,
};
static struct snd_soc_dai_driver rockchip_i2s_dai = {
 .probe = rockchip_i2s_dai_probe,
 .playback = {
  .stream_name = "Playback",
  .channels_min = 2,
  .channels_max = 8,
  .rates = ROCKCHIP_I2S_RATES,
  .formats = ROCKCHIP_I2S_FORMATS,
 },
 .capture = {
  .stream_name = "Capture",
  .channels_min = 2,
  .channels_max = 8,
  .rates = ROCKCHIP_I2S_RATES,
  .formats = ROCKCHIP_I2S_FORMATS,
 },
 .ops = &rockchip_i2s_dai_ops,
 .symmetric_rates = 1,
};


rockchip_i2s_probe
    ret = of_property_read_u32(node, "i2s-id", &pdev->id); //这里id=1
    i2s = devm_kzalloc(&pdev->dev, sizeof(*i2s), GFP_KERNEL); //分配rk_i2s_dev结构体
    //以clock的名称(不提供也行)为参数,调用clk get接口,获取clock的句柄
    i2s->hclk = devm_clk_get(&pdev->dev, "i2s_hclk");  ////获取i2s_hclk
    /* clk_prepare_enable helps cases using clk_enable in non-atomic context. */
    //使能clk在非原子上下文
    clk_prepare_enable(i2s->hclk); 
    i2s->clk = devm_clk_get(&pdev->dev, "i2s_clk"); //获取i2s_clk
    INIT_DELAYED_WORK(&i2s->clk_delayed_work, set_clk_later_work);
        clk_set_rate(i2s->clk, I2S_DEFAULT_FREQ); //设置时钟频率,这里是#define I2S_DEFAULT_FREQ (11289600)
    schedule_delayed_work(&i2s->clk_delayed_work, msecs_to_jiffies(10)); //10毫秒后执行
    clk_prepare_enable(i2s->clk); //使能clk
    i2s->mclk = devm_clk_get(&pdev->dev, "i2s_mclk"); //获取i2s_mclk
    clk_prepare_enable(i2s->mclk); //使能
    //get a resource for a device,获取IO内存资源,从index0,对应DTS里面的reg
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    regs = devm_ioremap_resource(&pdev->dev, res); //映射,供用户使用,check, request region, and ioremap resource
    //Initialise managed register map,初始化管理的寄存器的映射
    i2s->regmap = devm_regmap_init_mmio(&pdev->dev, regs,&rockchip_i2s_regmap_config); 
    i2s->playback_dma_data.addr = res->start + I2S_TXDR;  //设置dma的起始地址
    i2s->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; //带宽
    i2s->playback_dma_data.maxburst = I2S_DMA_BURST_SIZE;  //爆发传输的最大数据
    i2s->capture_dma_data.addr = res->start + I2S_RXDR;
    i2s->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
    i2s->capture_dma_data.maxburst = I2S_DMA_BURST_SIZE;
    i2s->dev = &pdev->dev;
    pm_runtime_enable(&pdev->dev); //使能运行电源管理
    soc_dai = devm_kzalloc(&pdev->dev, sizeof(*soc_dai), GFP_KERNEL); //分配snd_soc_dai_driver结构体
    memcpy(soc_dai, &rockchip_i2s_dai, sizeof(*soc_dai)); //把rockchip_i2s_dai赋值给soc_dai
    if (!of_property_read_u32(node, "rockchip,playback-channels", &val)) // 获取播放通道
        soc_dai->playback.channels_max = val; //赋值,默认是8
    if (!of_property_read_u32(node, "rockchip,capture-channels", &val))
        soc_dai->capture.channels_max = val;
    ret = snd_soc_register_component(&pdev->dev, &rockchip_i2s_component, soc_dai, 1); // 单独分析
    ret = rockchip_pcm_platform_register(&pdev->dev); //单独分析
    ret = of_property_read_u32(node, "rockchip,xfer-mode", &i2s->xfer_mode); //这里没有设置
    if (ret < 0)
        i2s->xfer_mode = I2S_XFER_MODE; //默认使用I2S模式
    rockchip_snd_txctrl(i2s, 0);
        if (on) //设置相应的寄存器进行写
            //Perform a read/modify/write cycle on the register map,前面有初始化这个 register map
            regmap_update_bits(i2s->regmap, I2S_DMACR, I2S_DMACR_TDE_MASK, I2S_DMACR_TDE_ENABLE);
            regmap_update_bits(i2s->regmap, I2S_XFER,I2S_XFER_TXS_MASK | I2S_XFER_RXS_MASK,
       I2S_XFER_TXS_START | I2S_XFER_RXS_START);
        else //设置相应的寄存器停止写
            i2s->tx_start = false;
            regmap_update_bits(i2s->regmap, I2S_DMACR, I2S_DMACR_TDE_MASK, I2S_DMACR_TDE_DISABLE);
            if (!i2s->rx_start)
                regmap_update_bits(i2s->regmap, I2S_XFER,   I2S_XFER_TXS_MASK |I2S_XFER_RXS_MASK,
        I2S_XFER_TXS_STOP | I2S_XFER_RXS_STOP);
    rockchip_snd_rxctrl(i2s, 0); //停止读
    cpumask_clear(&cpumask);
    cpumask_set_cpu(cpu_id, &cpumask); //设置与CPU 1亲和
    irq_set_affinity(irq, &cpumask);  //irq 与 cpu 1相关联




2.单独分析
snd_soc_register_component
    cmpnt = devm_kzalloc(dev, sizeof(*cmpnt), GFP_KERNEL); //分配snd_soc_component结构体
    cmpnt->name = fmt_single_name(dev, &cmpnt->id); //用设备的名字赋值
    cmpnt->dev = dev;
    cmpnt->driver = cmpnt_drv;
    cmpnt->num_dai = num_dai; //这里等于1
    ret = snd_soc_register_dai(dev, dai_drv); //注册dai
        dai = kzalloc(sizeof(struct snd_soc_dai), GFP_KERNEL); //分配snd_soc_codec结构体
        /* create DAI component name */
        dai->name = fmt_single_name(dev, &dai->id); //得到dai的名字
        dai->dev = dev;
        dai->driver = dai_drv;
        dai->dapm.dev = dev;
        list_for_each_entry(codec, &codec_list, list) //从codec链表找到对应的codec
            if (codec->dev == dev)
                dai->codec = codec;
        list_add(&dai->list, &dai_list); //把dai->list加入dai_list
    list_add(&cmpnt->list, &component_list); //把cmpnt->list加入到component_list链表


3.rockchip_pcm_platform_register
static const struct snd_pcm_hardware rockchip_pcm_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_S24_LE |
        SNDRV_PCM_FMTBIT_S20_3LE |
        SNDRV_PCM_FMTBIT_S16_LE |
        SNDRV_PCM_FMTBIT_S32_LE ,
 .channels_min = 2,
 .channels_max = 8,
 .buffer_bytes_max = 2*1024*1024,/*128*1024,*/
 .period_bytes_min = 64,
 .period_bytes_max = 512*1024,/*32*1024,//2048*4,///PAGE_SIZE*2,*/
 .periods_min = 3,
 .periods_max = 128,
 .fifo_size = 16,
};
static const struct snd_dmaengine_pcm_config rockchip_dmaengine_pcm_config = {
 .pcm_hardware = &rockchip_pcm_hardware,
 .prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config,
 .compat_filter_fn = NULL,
 .prealloc_buffer_size = PAGE_SIZE * 512,
};

static const struct snd_pcm_ops dmaengine_no_residue_pcm_ops = {
 .open = dmaengine_pcm_open,
 .close = snd_dmaengine_pcm_close,
 .ioctl = snd_pcm_lib_ioctl,
 .hw_params = dmaengine_pcm_hw_params,
 .hw_free = snd_pcm_lib_free_pages,
 .trigger = snd_dmaengine_pcm_trigger,
 .pointer = snd_dmaengine_pcm_pointer_no_residue,
};

static const struct snd_soc_platform_driver dmaengine_no_residue_pcm_platform = {
 .ops = &dmaengine_no_residue_pcm_ops,
 .pcm_new = dmaengine_pcm_new,
 .pcm_free = dmaengine_pcm_free,
 .probe_order = SND_SOC_COMP_ORDER_LATE,
};

rockchip_pcm_platform_register
    return snd_dmaengine_pcm_register(dev, &rockchip_dmaengine_pcm_configSND_DMAENGINE_PCM_FLAG_COMPAT|
   SND_DMAENGINE_PCM_FLAG_NO_RESIDUE); //注册一个基于dmaengine的pcm设备,就是申请DMA运行
        pcm = kzalloc(sizeof(*pcm), GFP_KERNEL); //分配dmaengine_pcm结构体
        pcm->config = config;
        pcm->flags = flags;
        dmaengine_pcm_request_chan_of(pcm, dev);
        for (i = SNDRV_PCM_STREAM_PLAYBACK; i <= SNDRV_PCM_STREAM_CAPTURE; i++)
            pcm->chan[i] = dma_request_slave_channel(dev, dmaengine_pcm_dma_channel_names[i]); //申请DMA通道,rx和tx
                return of_dma_request_slave_channel(dev->of_node, name); //分配单独的DMA通道
                    count = of_property_count_strings(np, "dma-names"); //获取dts里面的rx,tx的dma
                    if (of_dma_match_channel(np, name, i, &dma_spec)) //匹配名字
                    ofdma = of_dma_find_controller(&dma_spec); //Get a DMA controller in DT DMA helpers list
                    chan = ofdma->of_dma_xlate(&dma_spec, ofdma); //获得通道
        if (flags & SND_DMAENGINE_PCM_FLAG_NO_RESIDUE) //我们这里有个这个标志
            //Add a platform to the ASoC core
            return snd_soc_add_platform(dev, &pcm->platform, &dmaengine_no_residue_pcm_platform); 
            /* create platform component name */
            platform->name = fmt_single_name(dev, &platform->id);
            platform->dev = dev;
            platform->driver = platform_drv;
            platform->dapm.dev = dev;
            platform->dapm.platform = platform;
            platform->dapm.stream_event = platform_drv->stream_event; //
            list_add(&platform->list, &platform_list);  //把platform->list加入到platform_list
                


4.rockchip_i2s_dai_probe
    struct rk_i2s_dev *i2s = to_info(dai);
    dai->capture_dma_data = &i2s->capture_dma_data; //capture_dma_data在probe函数有初始化,地址,长度等
    dai->playback_dma_data = &i2s->playback_dma_data;  //capture_dma_data在probe函数有初始化



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

    struct dmaengine_pcm *pcm = soc_platform_to_pcm(rtd->platform);
    const struct snd_dmaengine_pcm_config *config = pcm->config;
    struct snd_pcm_substream *substream;
    snd_pcm_lib_preallocate_free_for_all(pcm);
        for (i = SNDRV_PCM_STREAM_PLAYBACK; i <= SNDRV_PCM_STREAM_CAPTURE; i++)
            substream = rtd->pcm->streams[i].substream; //得到分配的substream
            if (!pcm->chan[i] && (pcm->flags & SND_DMAENGINE_PCM_FLAG_COMPAT)) //这里应该已经申请过了
                //try to allocate an exclusive slave channel,分配一个dma通道
                pcm->chan[i] = dma_request_slave_channel(rtd->platform->dev, dmaengine_pcm_dma_channel_names[i]);
                    if (dev->of_node)
                        return of_dma_request_slave_channel(dev->of_node, name);
    //分配DMA内存
    ret = snd_pcm_lib_preallocate_pages(substream, SNDRV_DMA_TYPE_DEV, dmaengine_dma_dev(pcm, substream),
    config->prealloc_buffer_size, config->pcm_hardware->buffer_bytes_max); //pre-allocation for the given DMA type
        substream->dma_buffer.dev.type = type;
        substream->dma_buffer.dev.dev = data;
        //pre-allocate the buffer and create a proc file for the substream
        return snd_pcm_lib_preallocate_pages1(substream, size, max);
            if (size > 0 && preallocate_dma && substream->number < maximum_substreams)
                preallocate_pcm_pages(substream, size);
                    /* already reserved? */
                    if (snd_dma_get_reserved_buf(dmab, substream->dma_buf_id) > 0) //已经有保留的dma buf
                        if (dmab->bytes >= size) //大于要申请的,返回
                            return 0;
                    //allocate the buffer area according to the given type
                    err = snd_dma_alloc_pages(dmab->dev.type, dmab->dev.dev, size, dmab)) 
                        switch (type) //不同的类型有不同的分配方法
                        case SNDRV_DMA_TYPE_DEV:
                              dmab->area = snd_malloc_dev_pages(device, size, &dmab->addr);
                                pg = get_order(size);
                                gfp_flags = GFP_KERNEL
                                | __GFP_COMP /* compound page lets parts be mapped */
                                | __GFP_NORETRY /* don't trigger OOM-killer */
                                | __GFP_NOWARN; /* no stack trace print - this call is non-critical */
                                res = dma_alloc_coherent(dev, PAGE_SIZE << pg, dma, gfp_flags);
            substream->dma_max = max;
            preallocate_info_init(substream); //proc接口,/proc/asound/card0/pcm0c/sub0/prealloc

6.rockchip_i2s_set_fmt cpu dai设置
Rk_i2s.c 
static int rockchip_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
    mask = I2S_CKR_MSS_MASK;
    switch (fmt & SND_SOC_DAIFMT_MASTER_MASK)  //设置主从模式
    case SND_SOC_DAIFMT_CBS_CFS:
    /* Codec is slave, so set cpu master */
        val = I2S_CKR_MSS_MASTER;
    case SND_SOC_DAIFMT_CBM_CFM:
        /* Codec is master, so set cpu slave */
        val = I2S_CKR_MSS_SLAVE;
    //Perform a read/modify/write cycle on the register map
    regmap_update_bits(i2s->regmap, I2S_CKR, mask, val); //更新寄存器
    witch (fmt & SND_SOC_DAIFMT_FORMAT_MASK)  //DAI的发射传输协议
    ...................
    case SND_SOC_DAIFMT_I2S:
        val = I2S_TXCR_IBM_NORMAL;
    ...........
    regmap_update_bits(i2s->regmap, I2S_TXCR, mask, val);
    mask = I2S_RXCR_IBM_MASK;
    switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) //DAI的接收传输协议
    case SND_SOC_DAIFMT_I2S:
        val = I2S_RXCR_IBM_NORMAL;
    regmap_update_bits(i2s->regmap, I2S_RXCR, mask, val);


7.rockchip_i2s_set_sysclk,设置clk
static int rockchip_i2s_set_sysclk(struct snd_soc_dai *cpu_dai,int clk_id, unsigned int freq, int dir)
    ret = clk_set_rate(i2s->clk, freq); //设置i2s的时钟

8.rockchip_i2s_set_clkdiv,设置分频
static int rockchip_i2s_set_clkdiv(struct snd_soc_dai *cpu_dai, int div_id, int div)
    switch (div_id)  
    case ROCKCHIP_DIV_BCLK: //有Bclk和mclk之分
        val |= I2S_CKR_TSD(div);
        val |= I2S_CKR_RSD(div);
        regmap_update_bits(i2s->regmap, I2S_CKR, I2S_CKR_TSD_MASK | I2S_CKR_RSD_MASK, val);
    case ROCKCHIP_DIV_MCLK:
        。。。。。。。。


9.snd_pcm_lib_ioctl
    switch (cmd)
    case SNDRV_PCM_IOCTL1_RESET:
        return snd_pcm_lib_ioctl_reset(substream, arg);
            // 硬件逻辑位置,播放时相当于读指针,录音时相当于写指针;,这里是还原这个指针
            if (snd_pcm_running(substream) && snd_pcm_update_hw_ptr(substream) >= 0)
                runtime->status->hw_ptr %= runtime->buffer_size;
            else
                runtime->status->hw_ptr = 0;
                runtime->hw_ptr_wrap = 0;
    case SNDRV_PCM_IOCTL1_CHANNEL_INFO:
        return snd_pcm_lib_ioctl_channel_info(substream, arg);
    case SNDRV_PCM_IOCTL1_FIFO_SIZE:
        return snd_pcm_lib_ioctl_fifo_size(substream, arg);


10.snd_dmaengine_pcm_trigger
DMA的触发器,包括播放,暂停,停止等
int snd_dmaengine_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
    switch (cmd)
    case SNDRV_PCM_TRIGGER_START: //开始
        ret = dmaengine_pcm_prepare_and_submit(substream);
            direction = snd_pcm_substream_to_dma_direction(substream); //获取方向
                if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
                    return DMA_MEM_TO_DEV; //这是MEM到设备
                else
                    return DMA_DEV_TO_MEM; //这设备到MEM
            #ifdef CONFIG_ARCH_ROCKCHIP //这里rk独有的
            desc = dmaengine_prep_dma_infiniteloop(chan, //DMA通道,初始化有分配
                                    substream->runtime->dma_addr, //BUF的源地址
                             snd_pcm_lib_buffer_bytes(substream), //这里是runtime->buffer_size* runtime->frame_bits / 8;,传输的总长度
                             snd_pcm_lib_period_bytes(substream),//runtime->period_size*runtime->frame_bits / 8; 传输的周期长度
                                                                  direction, flags, //方向和flags = DMA_CTRL_ACK;
              //传输的总长度/传输的周期长度 = 传输周期数
              snd_pcm_lib_buffer_bytes(substream)/snd_pcm_lib_period_bytes(substream)); //runtime->period_size*runtime->frame_bits / 8;
                //调用到Pl330.c (kernel\drivers\dma) 里面的pl330_prep_dma_cyclic,暂不分析
                return chan->device->device_prep_dma_cyclic(chan, buf_addr, buf_len, period_len, dir, flags, &t);
            desc->callback = dmaengine_pcm_dma_complete; //如果完成传输,暂不分析
            desc->callback_param = substream;
            ///*本次传输的跟踪cookie,如果本次传输位于独立的链表,则设置为-EBUSY*/
            prtd->cookie = dmaengine_submit(desc); ///*设置准备好的描述符被DMA引擎执行*/
        // This allows drivers to push copies to HW in batches,reducing MMIO writes where possible.
        //这使得驱动程序可以批量地将副本推送到HW中,从而减少MMIO的写入。
        dma_async_issue_pending(prtd->dma_chan); 
    case SNDRV_PCM_TRIGGER_RESUME: //唤醒
    case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
        dmaengine_resume(prtd->dma_chan);
    case SNDRV_PCM_TRIGGER_SUSPEND: //暂停
    case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
        dmaengine_pause(prtd->dma_chan);
    case SNDRV_PCM_TRIGGER_STOP: //停止
       dmaengine_terminate_all(prtd->dma_chan);
    
            
    







推荐阅读