转载请注明出处:http://blog.csdn.net/gotowu/article/details/46329809

1、I2S概述

既然要学习I2S,就要想、首先知道他是干什么用的。

I2S(Inter—IC Sound)总线, 又称 集成电路内置音频总线,是飞利浦公司为数字音频设备之间的音频数据传输而制定的一种总线标准,该总线专责于音频设备之间的数据传输,广泛应用于各种多媒体系统。它采用了沿独立的导线传输时钟与数据信号的设计,通过将数据和时钟信号分离,避免了因时差诱发的失真,为用户节省了购买抵抗音频抖动的专业设备的费用。

2、I2S的总线规范

1.串行时钟SCLK,也叫位时钟(BCLK),即对应数字音频的每一位数据,SCLK都有1个脉冲。SCLK的频率=2×采样频率×采样位数。

2. 帧时钟LRCK,(也称WS),用于切换左右声道的数据。LRCK为“1”表示正在传输的是右声道的数据,为“0”则表示正在传输的是左声道的数据。LRCK的频率等于采样频率。

3.串行数据SDATA,就是用二进制补码表示的音频数据。

3、I2S有4线,包括串行数据输入IISDI,串行数据输出IISDO,左右通道选择IISLRCK,和穿行位时钟IISCLK。生成IISLRCK和IISCLK的设备是主设备。

I2S驱动是作为接口驱动,供linux音频驱动使用的,因此它的代码中,必然要有音频驱动的一些东西。分析的时候适当的结合一下音频驱动就好看了。

下面来看看I2S驱动

使用平台设备注册IIS驱动。

module_platform_driver(s3c24xx_iis_driver);

module_platform_driver()宏的作用就是定义指定名称的平台设备驱动注册函数和平台设备驱动注销函数,并且在函数体内分别通过platform_driver_register()函数和platform_driver_unregister()函数注册和注销该平台设备驱动。

跟踪到s3c24xx_iis_driver,发现主要是做了两个事,一个是probe函数,还有一个就是remove。

static struct platform_driver s3c24xx_iis_driver = {
.probe  = s3c24xx_iis_dev_probe,
.remove = __devexit_p(s3c24xx_iis_dev_remove),
.driver = {
.name = "s3c24xx-iis",
.owner = THIS_MODULE,
},
};

在s3c24xx_iis_dev_probe中,通过snd_soc_register_dai注册一个ASOC核心的DAI接口,DAI是数字音频接口。

static __devinit int s3c24xx_iis_dev_probe(struct platform_device *pdev)
{
return snd_soc_register_dai(&pdev->dev, &s3c24xx_i2s_dai);
}

s3c24xx_iis_dev_remove与s3c24xx_iis_dev_probe相反,不在赘述。

snd_soc_register_dais函数显示为每个snd_soc_dai实例分配内存,确定dai的名字,用snd_soc_dai_driver实例的字段对它进行必要初始化,最后把该dai链接到全局链表dai_list中。

dai驱动通常对应cpu的一个或几个I2S/PCM接口,与snd_soc_platform一样,dai驱动也是实现为一个platform driver,实现一个dai驱动大致可以分为以下几个步骤:

1.定义一个snd_soc_dai_driver结构的实例;

2.在对应的platform_driver中的probe回调中通过API:snd_soc_register_dai或者snd_soc_register_dais,注册snd_soc_dai实例;

3.实现snd_soc_dai_driver结构中的probe、suspend等回调;

4.实现snd_soc_dai_driver结构中的snd_soc_dai_ops字段中的回调函数;

snd_soc_register_dai

定义一个snd_soc_dai_driver结构

static struct snd_soc_dai_driver s3c24xx_i2s_dai = {
.probe = s3c24xx_i2s_probe,
.suspend = s3c24xx_i2s_suspend,
.resume = s3c24xx_i2s_resume,
.playback = {                          //播放
.channels_min = 2,
.channels_max = 2,
.rates = S3C24XX_I2S_RATES,
.formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},
.capture = {                         //录音
.channels_min = 2,
.channels_max = 2,
.rates = S3C24XX_I2S_RATES,
.formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE,},
.ops = &s3c24xx_i2s_dai_ops,
};

主要包括s3c24xx_i2s_probe,和 &s3c24xx_i2s_dai_ops,

对s3c24xx_i2s_probe进行填充

static int s3c24xx_i2s_probe(struct snd_soc_dai *dai)
{
pr_debug("Entered %s\n", __func__);s3c24xx_i2s.regs = ioremap(S3C2410_PA_IIS, 0x100);
if (s3c24xx_i2s.regs == NULL)
return -ENXIO;s3c24xx_i2s.iis_clk = clk_get(dai->dev, "iis");
if (IS_ERR(s3c24xx_i2s.iis_clk)) {
pr_err("failed to get iis_clock\n");
iounmap(s3c24xx_i2s.regs);
return PTR_ERR(s3c24xx_i2s.iis_clk);
}
clk_enable(s3c24xx_i2s.iis_clk);/* 配置I2S的管脚在正确的模式 */
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);writel(S3C2410_IISCON_IISEN, s3c24xx_i2s.regs + S3C2410_IISCON);s3c24xx_snd_txctrl(0);
s3c24xx_snd_rxctrl(0);return 0;
}

对于ioremap,Ioremap宏定义在asm/io.h内。要访问s3c2410平台上的I2S寄存器, 查看datasheet 知道IIS物理地址为0x55000000,我们把它定义为宏S3C2410_PA_IIS,如下:

#define S3C2410_PA_IIS    (0x55000000)

若要在内核空间(iis驱动)中访问这段I/O寄存器(IIS)资源需要先建立到内核地址空间的映射:

s3c24xx_i2s.regs = ioremap(S3C2410_PA_IIS, 0x100);

创建好了之后,我们就可以通过readl(s3c24xx_i2s.regs)或writel(value, s3c24xx_i2s.regs)等IO接口函数去访问它。

再看s3c24xx_i2s_dai_ops

static const struct snd_soc_dai_ops s3c24xx_i2s_dai_ops = {
.trigger= s3c24xx_i2s_trigger,            //触发
.hw_params= s3c24xx_i2s_hw_params,    //设置硬件参数
.set_fmt= s3c24xx_i2s_set_fmt,           //设置dai的格式
.set_clkdiv= s3c24xx_i2s_set_clkdiv,     //设置分频系数
.set_sysclk= s3c24xx_i2s_set_sysclk,    //设置dai的主时钟
};

首先来看看I2S的触发,有六种情况

static int s3c24xx_i2s_trigger(struct snd_pcm_substream *substream, int cmd,struct snd_soc_dai *dai)
{
int ret = 0;
struct s3c_dma_params *dma_data =
snd_soc_dai_get_dma_data(dai, substream);  //DMA获取到的数据是播放的还是录音pr_debug("Entered %s\n", __func__);switch (cmd) {
case SNDRV_PCM_TRIGGER_START: //此处case后无语句,则即便cmd为SNDRV_PCM_TRIGGER_START
case SNDRV_PCM_TRIGGER_RESUME: //依然会一直执行后面的语句,直到出现break
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
if (!s3c24xx_snd_is_clkmaster()) {
ret = s3c24xx_snd_lrsync();
if (ret)
goto exit_err;
}if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
s3c24xx_snd_rxctrl(1);         //录音
else
s3c24xx_snd_txctrl(1);         //播放s3c2410_dma_ctrl(dma_data->channel, S3C2410_DMAOP_STARTED); //改变信道的状态
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
s3c24xx_snd_rxctrl(0);
else
s3c24xx_snd_txctrl(0);
break;
default:
ret = -EINVAL;
break;
}exit_err:
return ret;
}

在看s3c24xx_i2s_hw_params

static int s3c24xx_i2s_hw_params(struct snd_pcm_substream *substream,struct snd_pcm_hw_params *params,struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct s3c_dma_params *dma_data;
u32 iismod;pr_debug("Entered %s\n", __func__);if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
dma_data = &s3c24xx_i2s_pcm_stereo_out;
else
dma_data = &s3c24xx_i2s_pcm_stereo_in;snd_soc_dai_set_dma_data(rtd->cpu_dai, substream, dma_data);  /* Working copies of register */
iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
pr_debug("hw_params r: IISMOD: %x\n", iismod);switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S8:
iismod &= ~S3C2410_IISMOD_16BIT;
dma_data->dma_size = 1;
break;
case SNDRV_PCM_FORMAT_S16_LE:
iismod |= S3C2410_IISMOD_16BIT;
dma_data->dma_size = 2;
break;
default:
return -EINVAL;
}

snd_soc_dai_set_dma_data(rtd->cpu_dai, substream, dma_data);通过(substream->stream == SNDRV_PCM_STREAM_PLAYBACK)进行判断是录音还是播放。

通过switch (params_format(params))进行判断是选择8位还是16位的传输通道,并设置相应的IISMOD和dma宽度。

设置传输的格式和主从模式的选择。

static int s3c24xx_i2s_set_fmt(struct snd_soc_dai *cpu_dai,
unsigned int fmt)
{
u32 iismod;pr_debug("Entered %s\n", __func__);iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);
pr_debug("hw_params r: IISMOD: %x \n", iismod);switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBM_CFM:                             //设置主or从模式当IISMOD的
iismod |= S3C2410_IISMOD_SLAVE;                   //第8位为0时是master mode
break;                                                                 //为1时是slave mode
case SND_SOC_DAIFMT_CBS_CFS:
iismod &= ~S3C2410_IISMOD_SLAVE;
break;
default:
return -EINVAL;
}switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_LEFT_J:
iismod |= S3C2410_IISMOD_MSB;        //#define S3C2410_IISMOD_MSB(1 << 4)
break;                                                   //IISMOD的第4位为1时是 MSB (Left)-justified format
case SND_SOC_DAIFMT_I2S:                        //IISMOD的第4位为0时是IIS compatible format
iismod &= ~S3C2410_IISMOD_MSB;
break;
default:
return -EINVAL;
}writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);    //将IISMOD的状态写入IISMOD的地址
pr_debug("hw_params w: IISMOD: %x \n", iismod);
return 0;
}

下面让我们再来看看I2S对时钟分频的操作

首先主设备时钟频率MCLK=PCLK/预分频值,I2SLRCK频率=主设备时钟频率/CODECLK,

CODECLK的采样频率类型为256fs和384fs。

串行为采用频率BCLK类型有16/32/48fs,可以通过设置串行位数和CODECLK采样频率完成。

串行位时钟频率=CODECLK的采用类型/串行数据位数

static int s3c24xx_i2s_set_clkdiv(struct snd_soc_dai *cpu_dai,
int div_id, int div)
{
u32 reg;pr_debug("Entered %s\n", __func__);switch (div_id) {
case S3C24XX_DIV_BCLK:                          //串行时钟频率
reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~S3C2410_IISMOD_FS_MASK;
writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD);
break;
case S3C24XX_DIV_MCLK:                           //主时钟频率
reg = readl(s3c24xx_i2s.regs + S3C2410_IISMOD) & ~(S3C2410_IISMOD_384FS);
writel(reg | div, s3c24xx_i2s.regs + S3C2410_IISMOD);
break;
case S3C24XX_DIV_PRESCALER:                //预分频值设定
writel(div, s3c24xx_i2s.regs + S3C2410_IISPSR);
reg = readl(s3c24xx_i2s.regs + S3C2410_IISCON);
writel(reg | S3C2410_IISCON_PSCEN, s3c24xx_i2s.regs + S3C2410_IISCON);
break;
default:
return -EINVAL;
}return 0;
}

reg | S3C2410_IISCON_PSCEN使能预分频

下面我们来设置系统的时钟的时钟源

static int s3c24xx_i2s_set_sysclk(struct snd_soc_dai *cpu_dai,
int clk_id, unsigned int freq, int dir)
{
u32 iismod = readl(s3c24xx_i2s.regs + S3C2410_IISMOD);    //读取IISMOD的状态pr_debug("Entered %s\n", __func__);iismod &= ~S3C2440_IISMOD_MPLL;          //初始化选择PCLK 外设时钟switch (clk_id) {
case S3C24XX_CLKSRC_PCLK:  //如果clk_id为0,则使用PCLK
break;
case S3C24XX_CLKSRC_MPLL:  //如果clk_id为1,则选择MPLL
iismod |= S3C2440_IISMOD_MPLL;
break;
default:
return -EINVAL;
}writel(iismod, s3c24xx_i2s.regs + S3C2410_IISMOD);
return 0;
}

这里要解释一下MPLL(phase locked loop),S3C2440有两个PLL(phase locked loop)一个是MPLL,一个是UPLL。MPLL用于CPU及其他外围器件,UPLL用于USB。从S3C2440的DATASHEET里可以看到,S3C2440最大支持400MHz的主频,但是这并不意味着一定工作在400MHz下面,可以通过设定MPLL, UPLL寄存器来设定CPU的工作频率。

到此 s3c24xx_i2s_dai_ops的分析结束。

Linux下的I2S驱动学习相关推荐

  1. Linux下的硬件驱动——USB设备(下)

    Linux下的硬件驱动--USB设备(下)(驱动开发部分) 文档选项 打印本页 将此页作为电子邮件发送 未显示需要 JavaScript 的文档选项 级别: 初级 赵明, 联想软件设计中心嵌入式研发处 ...

  2. Linux下的硬件驱动——USB设备配置以及开发

    Linux下的硬件驱动--USB设备(上)(驱动配置部分) USB设备越来越多,而Linux在硬件配置上仍然没有做到完全即插即用,对于Linux怎样配置和使用他们,也越来越成为困扰我们的一大问题.本文 ...

  3. Linux下的硬件驱动——USB设备

    想起当初对于破安卓手机,挂在系统上可是费了好些劲,今偶遇USB驱动开发,收集备用,哪天一生气,说不定也写一个linux下的手机驱动,类似于91手机助手的,也不用配置了. Linux下的硬件驱动--US ...

  4. LINUX下USB1.1设备学习小记(2)_协…

    LINUX下USB1.1设备学习小记(2)_协议 (2009-03-27 14:40) 分类: 文章转载 USB协议: 先看USB接口 可以看出,在USB使用了4根线,分别为电源线,地线,信号线和差分 ...

  5. linux下rpm,yum学习

    linux下RPM及yum学习 linux中程序管理程序主要分为两类 dpkg(Debian Packager):debian,Ubuntu,Knoppix         rpm(Redhat Pa ...

  6. linux下的加密解密学习

    linux下的加密解密学习 加密/解密:         加密协议:加密解密使用同一秘钥:3des,aes         公钥加密:公钥私钥对         数字签名,密钥交换          ...

  7. linux下测试RTC驱动相关的命令date和hwclock常见用法简介

    之前对Linux下面时间相关的内容,一无所知,第一次见到hwclock,不知为何物,也没找到解释清楚的帖子.故此整理一下,简单介绍Linux下验证rtc驱动是否工作正常,相关的的命令:date和hwc ...

  8. linux查看网卡的驱动命令行,Linux下查看网卡驱动和版本信息

    Linux下查看网卡驱动和版本信息 查看网卡生产厂商和信号 查看基本信息:lspci 查看详细信息:lspci -vvv # 3个小写的v 查看网卡信息:lspci | grep Ethernet 查 ...

  9. linux查看当前igb的版本信息,Linux下查看网卡驱动和版本信息

    Linux下查看网卡驱动和版本信息 查看网卡生产厂商和信号 查看基本信息:lspci 查看详细信息:lspci -vvv  # 3个小写的v 查看网卡信息:lspci | grep Ethernet ...

最新文章

  1. 第七节:HtmlHelper及其扩展
  2. js中的DOM对象和jQuery对象的比较
  3. hdu4513--Manacher算法--回文串的O(n)算法
  4. 【luogu 1024 一元三次方程求解】二分思想
  5. NSURLSession实现文件上传
  6. 重建总结5_重建列表
  7. 05_坐标变换与视觉测量学习笔记
  8. 易买网(注册Ajax讲解)
  9. 杰奇2.4珊瑚模板简繁转换出现乱码怎么解决
  10. 记一次使用screw 生成MySQL数据库文档
  11. HttpPrinter-网页打印控件
  12. 【亲测好用】磁盘管理器:Disk Xray Mac版
  13. 放大器指标:1db压缩点
  14. 快速入门开发实现订单类图片识别结果抽象解析
  15. 自然语言处理中CNN模型几种常见的Max Pooling操作
  16. 一些http和tomcat知识补充
  17. python逐行写入文件_Python文件逐行写入
  18. Bear and Big Brother
  19. Java复习之抽象类和接口
  20. POJ 3095 Linear Pachinko 字符串模拟

热门文章

  1. 【计算机毕业设计】075电影评论网站
  2. TWS无线高清防水降噪无线耳机——极度未知HyperX云雀真无线耳机
  3. YBTOJ:喂养宠物
  4. Oracle12c新特性大全 存储资源隔离+flex+diskgroup
  5. java字符串转字节流_java 字节→字符转换流
  6. 等保级别最高为几级?市面上常见吗?
  7. 计算机没有外审的核心期刊,期刊外审太快是不是不好_核心期刊外审一般多长时间_论文外审是什么意思...
  8. 【填坑】minicom中文乱码问题
  9. 读Google Borg
  10. Codeforces Round #673 (Div. 2) B. Two Arrays