本文是基于mini2440开发板Linux版本号是linux-2.6.32.2的学习笔记
一. I2S driver 的probe函数
- 映射虚拟内存,IIS寄存器的起始地址是:0x55000000
s3c24xx_i2s.regs = ioremap(S3C2410_PA_IIS, 0x100);
- 获取iis时钟,并使能iis时钟
s3c24xx_i2s.iis_clk = clk_get(&pdev->dev, "iis");
clk_enable(s3c24xx_i2s.iis_clk);
- 配置IIS引脚,IIS有5个引脚,分别是:
I2SLRCK: 表示当前传的声音的声道,I2SLRCK 是低电平时表示传的是“左声道”数据,I2SLRCK 是高电平时传的是“右声道”数据。
I2SSCLK:串行位时钟,I2S传输数据使用
SCDCLK: 2440 提供给解码芯片使用的系统时钟。
I2SSDI:I2S输入信号线
I2SSDO:I2S输出信号线
#define S3C2410_GPE0_I2SLRCK (0x02 << 0)
#define S3C2410_GPE1_I2SSCLK (0x02 << 2)
#define S3C2410_GPE2_CDCLK (0x02 << 4)
#define S3C2410_GPE3_I2SSDI (0x02 << 6)
#define S3C2410_GPE4_I2SSDO (0x02 << 8)
/* Configure the I2S pins in correct mode */
s3c2410_gpio_cfgpin(S3C2410_GPE0, S3C2410_GPE0_I2SLRCK);
s3c2410_gpio_cfgpin(S3C2410_GPE1, S3C2410_GPE1_I2SSCLK);
s3c2410_gpio_cfgpin(S3C2410_GPE2, S3C2410_GPE2_CDCLK);
s3c2410_gpio_cfgpin(S3C2410_GPE3, S3C2410_GPE3_I2SSDI);
s3c2410_gpio_cfgpin(S3C2410_GPE4, S3C2410_GPE4_I2SSDO);
GPECON寄存器:
- I2S enable, IISCON寄存器的第0位写1
#define S3C2410_IISCON_IISEN (1<<0)
writel(S3C2410_IISCON_IISEN, s3c24xx_i2s.regs + S3C2410_IISCON);
- 先控制I2S的发送和接收停止,先不发送和接收。
s3c24xx_snd_txctrl(0);
s3c24xx_snd_rxctrl(0);
二. I2S的发送控制
- 启动I2S的发送
①写 IISCON寄存器,I2S使能,DMA发送请求enable,IISLRCK active
#define S3C2410_IISCON_TXDMAEN (1<<5)
#define S3C2410_IISCON_IISEN (1<<0)
#define S3C2410_IISCON_TXIDLE (1<<3)
iiscon |= S3C2410_IISCON_TXDMAEN | S3C2410_IISCON_IISEN;
iiscon &= ~S3C2410_IISCON_TXIDLE;
②写IISFCON寄存器,使能发送缓存,缓存模式为DMA模式
#define S3C2410_IISFCON_TXDMA (1<<15)
#define S3C2410_IISFCON_TXENABLE (1<<13)
iisfcon |= S3C2410_IISFCON_TXDMA | S3C2410_IISFCON_TXENABLE;
③写 IISMOD寄存器,选择I2S的发送模式。
#define S3C2410_IISMOD_TXMODE (2<<6)
iismod |= S3C2410_IISMOD_TXMODE;
- 停止I2S的发送
停止I2S几乎是跟I2S的发送的控制相反,唯独的区别是没有将I2S disable。
三. I2S的接收控制
- 启动I2S的接收
①写 IISCON寄存器,I2S使能,DMA接收请求enable,IISLRCK active
#define S3C2410_IISCON_RXDMAEN (1<<4)
#define S3C2410_IISCON_IISEN (1<<0)
#define S3C2410_IISCON_RXIDLE (1<<2)
iiscon |= S3C2410_IISCON_RXDMAEN | S3C2410_IISCON_IISEN;
iiscon &= ~S3C2410_IISCON_RXIDLE;
②写 IISMOD寄存器,选择I2S的接收模式。
#define S3C2410_IISMOD_RXMODE (1<<6)
iismod |= S3C2410_IISMOD_RXMODE;
③写IISFCON寄存器,使能接收缓存,缓存模式为DMA模式
#define S3C2410_IISFCON_RXDMA (1<<14)
#define S3C2410_IISFCON_RXENABLE (1<<12)
iisfcon |= S3C2410_IISFCON_RXDMA | S3C2410_IISFCON_RXENABLE;
- 停止I2S的接收
跟启动的寄存器写相反的值。
四. I2S的模式设置
- Master/slave模式设置, IISMOD寄存器的第8位设置
master模式:cpu向音频解码芯片提供时钟
slave模式:音频解码芯片提供时钟
iismod |= S3C2410_IISMOD_SLAVE;
writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
- 设置串行数据格式,IISMOD寄存器的第4位设置
"#define S3C2410_IISMOD_MSB" " (1<<4)"
iismod |= S3C2410_IISMOD_MSB;
该芯片只提供两种数据格式,一种是 MSB (LEFT) JUSTIFIED,另外一种是标准的IIS格式。
IIS 格式数据是经过一个 SCLK 位时钟后,才输入第一个数据"MSB"(SD 上空了一个 SCLK时钟)。最后也是以“MSB”结束。
MSB 格式是在第一个 SCLK 时钟就立刻输出了第一个数据。
五. I2S的s3c24xx_i2s_hw_params函数
- 设置s3c24xx_i2s_hw_params.dai.cpu_dai.dma_data,假设现在是播放。
s3c24xx_i2s_hw_params.dai.cpu_dai.dma_data = s3c24xx_i2s_pcm_stereo_out
static struct s3c24xx_pcm_dma_params s3c24xx_i2s_pcm_stereo_out = {
.client = &s3c24xx_dma_client_out,
.channel = DMACH_I2S_OUT,
.dma_addr = S3C2410_PA_IIS + S3C2410_IISFIFO,
.dma_size = 2,
};
IIS FIFO寄存器的地址是 0x55000010,寄存器的大小是2字节。
- 设置传输每次传输多少位数据
#define S3C2410_IISMOD_16BIT (1<<3)
iismod |= S3C2410_IISMOD_16BIT;
writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
六. s3c24xx_i2s_trigger函数
- 看播放状态,判断substream的方向,播放的话调用s3c24xx_snd_txctrl(1);录制调用s3c24xx_snd_rxctrl(1);
- 启动DMA播放:s3c2410_dma_ctrl(channel, S3C2410_DMAOP_STARTED);
七. IIS时钟设置
- 时钟源设置成 PCLK或者 MPLLin
#define S3C2440_IISMOD_MPLL (1<<9)
iismod |= S3C2440_IISMOD_MPLL;
writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
- 设置分频系数
writel(div, s3c24xx_i2s.regs + S3C2410_IISPSR);
reg = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
writel(reg | S3C2410_IISCON_PSCEN, s3c24xx_i2s.regs + S3C2410_IISCON);
- 设置master时钟频率,是256fs还是384fs
reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~(S3C2410_IISMOD_384FS);
writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD);
综上,采样率 = (PCLK or MPLLin)/f分频系数/(256 or 384)
- 设置串行时钟频率
reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~S3C2410_IISMOD_FS_MASK;
writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD);
八. I2S接口注册
- 注册时间:在启动内核是调用s3c24xx_i2s_init函数。在s3c24xx_i2s_init函数中注册I2S接口。
module_init(s3c24xx_i2s_init);
static int __init s3c24xx_i2s_init(void)
{
return snd_soc_register_dai(&s3c24xx_i2s_dai);
}
- 注册到哪里:注册到一个dai_list的全局链表中,dai_list链表中是各种各样的dai接口。
int snd_soc_register_dai(struct snd_soc_dai *dai)
{
if (!dai->name)
return -EINVAL;
/* The device should become mandatory over time */
if (!dai->dev)
printk(KERN_WARNING "No device for DAI %s\n", dai->name);
if (!dai->ops)
dai->ops = &null_dai_ops;
INIT_LIST_HEAD(&dai->list);
mutex_lock(&client_mutex);
list_add(&dai->list, &dai_list);
snd_soc_instantiate_cards();
mutex_unlock(&client_mutex);
pr_debug("Registered DAI '%s'\n", dai->name);
return 0;
}
九. 总结
- s3c24xx_i2s_dai接口在内核起来时注册。
- s3c24xx_i2s_dai接口包含对芯片的I2S总线的波特率,数据传输模式等设置,一个硬件参数操作集s3c24xx_i2s_dai_ops。
参考博客:
https://blog.csdn.net/gqb_driver/article/details/8551551