rk音频驱动之platform
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_config, SND_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);