Chinaunix首页 | 论坛 | 博客
  • 博客访问: 217603
  • 博文数量: 72
  • 博客积分: 3890
  • 博客等级: 中校
  • 技术积分: 810
  • 用 户 组: 普通用户
  • 注册时间: 2009-01-05 20:00
文章分类

全部博文(72)

文章存档

2010年(20)

2009年(52)

我的朋友

分类: LINUX

2009-01-31 23:17:55




关于IIS总线:
参考资料:
http://hi.baidu.com/llhg/blog/item/5a098a1917a5364043a9ada0.html
《armlinux学习笔记--IIS音频驱动程序分析》

  I2S(Inter—IC Sound)总线是飞利浦公司为数字音频设备之间的音频数据传输而制定的一种总线标准。它是一种面向多媒体的音频总线专用于音频设备之间的数据传输,为数字立体声提供序列的连接至标准编解码器。IIS总线只处理声音数据,其他信号(如控制信号)必须单独传输。
  I2S的信号:
1.串行时钟SCLK,也叫位时钟(BCLK),即对应数字音频的每一位数据,SCLK都有1个脉冲。SCLK的频率=通(声)道数×采样频率×采样位数  
2.帧时钟LRCK,用于切换左右声道的数据。比如:LRCK为“1”表示正在传输的是左声道的数据,为“0”则表示正在传输的是右声道的数据。在S3C2440中,又IISMOD控制。LRCK的频率等于采样频率。
3.串行数据SDATA,就是用二进制补码表示的音频数据。
4.CDCLK 为UDA1341 芯片提供系统的同步时钟,也称为编解码时钟,即提供UDA1341 芯片进行音频的A/D,D/A 采样时的采样时钟
  在S3C2410 芯片与UDA1341 芯片的连线中,关于时钟信号的连线有:I2SLRCLK 到WS,I2SSCLK 到BCK,CDCLK 到SYSCLK。其中CDCLK 为UDA1341 芯片提供系统的同步时钟,也称为编解码时钟,即提供UDA1341 芯片进行音频的A/D,D/A 采样时的采样时钟。而其他2组时钟只是在进行IIS 总线传输数据时提供串行数据的位时钟和左右声道的切换。
  CDCLK 是由S3C2410 内部的APH 总线时钟首先经过一个IIS 的模式选择(256fs 或384fs),然后再经过一个IIS 的预分频器分频后得到。S3C2410 主频202M,它的APH 总线频率是202/4=50M,在选择IIS 的主时钟模式为384fs后,经过IIS 的PSR(分频比例因子)得到的由IPSR_A 分出的一个频率用于IIS 时钟输出也可以说是同步,另一个由IPSR_B 分出的频率CDCLK 则直接作为UDA1341 的系统时钟,即编解码时钟。
  这里在分频前要进行IIS 的主时钟频率选择(这里选择了384fs)是因为在分频时会根据384 这个系数和采样频率fs 进行分频,最后将系数384 乘以fs 得到CDCLK 时钟输出频率。
  而在UDA1341 芯片的初始化中也需要进行系统时钟的设置(512fs,384fs 或256fs),在进行音频的编解码时会根据SYSCLK 输入的系统时钟除以相应的系数,来得到采样频率fs。所以对于S3C2410 芯片的IIS 控制器和UDA1341 芯片,两者相应的CDCLK 和SYSCLK 的时钟频率需要设置一致。我在这里都设为了384fs,在调试过程中,我试着将两者设的不一致,结果就放不出声音了。还有一点要注意,由于预分频值与 384 这个系数和采样频率fs 有关,所以在计算预分频值的函数iispsr_value 中,384 这个系数也要和CDCLK 和SYSCLK 设置的系数一致。如果设置不一致的话,会导致声音播放的太快或太慢。
  
关于UDA1341:
  UDA1341支持IIS总线数据格式,采用位元流转换技术进行信号处理,具有可编程增益放大器(PGA)和数字自动增益控制器(AGC)。 UDA1341对外提供两组音频信号输入接口,每组包括左右2个声道。由于IIS总线只处理音频数据,因此UDA1341还内置用于传输控制信号的L3总线接口。L3接口相当于混音器控制接口,可以控制输入/输出音频信号的低音及音量大小等。
  两组音频输入在UDA1341内部的处理存在很大的差别:第一组音频信号输入后巾帼一个0dB/6dB开关后采样送入数字混音器;第二组音频信号输入后先经过可编程增益放大器(PGA),然后再进行采样,采样后的数据在经过自动增益控制器(AGC)送入数字混音器。硬件电路选用第二组输入音频信号,这样可以通过软件的方法实现对系统输入音量大小的调节,显然,这可以用L3总线接口控制AGC来实现。

关于驱动:
1,对缓存区的管理:
  为了提高传输速度和系统性能,所以采用DMA传输的方式,但是,如果只采用一个DMA缓存区,在写的时候,则必须等待该缓存区的数据都传输完毕,同时,读操作也会被阻塞,系统的性能会大打折扣。所以,驱动采用循环缓存区的方法,比如,假设有N维护缓存区,先写缓存区1,然后再写缓存区2,这样就不用管缓存区1的数据是否被传输完,当写完第N个缓存区后,又开始回到第一个,而此时第一个缓存区相当有可能已传输完毕。循环缓存区的大小和多少可以通过ioctl来设置。
管理循环缓存区的两个结构:
typedef struct {
    int size; /* buffer size */
    char *start; /* point to actual buffer */
    dma_addr_t dma_addr; /* physical buffer address */
    struct semaphore sem; /* down before touching the buffer */
    int master; /* owner for buffer allocation, contain size when true */
} audio_buf_t;
typedef struct {
    audio_buf_t *buffers; /* pointer to audio buffer structures */
    audio_buf_t *buf; /* current buffer used by read/write */
    u_int buf_idx; /* index for the pointer above */
    u_int fragsize; /* fragment i.e. buffer size */
    u_int nbfrags; /* nbr of fragments */
    enum dma_ch dmach;
} audio_stream_t;
static audio_stream_t output_stream;
static audio_stream_t input_stream;
audio_buf_t内记录了每个DMA缓存区的长度,虚拟地址,物理地址以及一个用来防止多个write对缓存区同时操作的信号量。
audio_stream_t内包含了指向循环缓存区开始的指针,正被使用的缓存区的指针,每个缓存区的大小,缓存区的个数以及使用的dma通道号。

static int audio_setup_buf(audio_stream_t * s)
{
    ...
    ...
    for (frag = 0; frag < s->nbfrags; frag++) {
        audio_buf_t *b = &s->buffers[frag];
        /*开始分配尽可能大的DMA缓存区,如果暂时不能分配大的DMA缓存区,那就先分配小的缓存区。*/
        if (!dmasize) {//看分配的DMA缓存区是否被用完
            dmasize = (s->nbfrags - frag) * s->fragsize;
        /*分配尽可能大的dma缓存区,如果分配不能满足,则减少要分配的大小。如果连最小的缓存区都不能满足,则出错。*/
            do {
                dmabuf = dma_alloc_coherent(NULL, dmasize, &dmaphys, GFP_KERNEL|GFP_DMA);
                if (!dmabuf)
                    dmasize -= s->fragsize;
} while (!dmabuf && dmasize);
            if (!dmabuf)
                goto err;
            b->master = dmasize;
}
        /*初始化每个audio_buf_t结构*/
        b->start = dmabuf;
        b->dma_addr = dmaphys;
        sema_init(&b->sem, 1);
        printk(KERN_WARNING "buf %d: start %p dma %d\n", frag, b->start, b->dma_addr);
        dmabuf += s->fragsize;
        dmaphys += s->fragsize;
        dmasize -= s->fragsize;
}
     ...
     ...
}
audio_setup_buf()这个函数为output_stream,input_stream这两个流分配循环缓冲区

关于l3:
  uda1341可以用L3总线接口调整pga,agc,vc等,来实现mixer。要通过L3来设置pga等,则需要先写相应的地址,然后在写相应的数据。开发板用GPIO口来与L3接口相连:
S3C2410_GPB2 L3MODE
S3C2410_GPB3 L3CLOC1341_REG_STATUS
S3C2410_GPB4 L3CLOCK
  L3地址时序:
  首先将L3MODE,L3CLOCK置1,等待一段时间,然后重复以下步骤8次(对应与8位地址):将L3CLOCK拉低,置L3DATA为高或低(对应与数据位是1还是0),等待一段时间后,拉高L3CLOCK。当8次结束后,将L3MODE,L3CLOCK恢复到1。
图:
  L3数据时序(写):
  L3MODE,L3CLOCK初始值都为1.重复以下步骤8次:
将L3CLOCK拉低,置L3DATA为高或低(对应与数据位是1还是0),等待一段时间后,拉高L3CLOCK。当8次结束后,将L3MODE,L3CLOCK恢复到1。
图:
  L3数据时序(读):
  还不太懂。
L3的操作在uda1341_l3_address(),uda1341_l3_data()中实现。

驱动中的其它部分比如读写等就没啥好说的了,都不难。

阅读(1037) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~