Linux下的I2S驱动学习

原文链接: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的分析结束。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值