17.5实例1:S3C2410+UDA1341 OSS驱动
17.5.1 S3C2410与UDA1341接口硬件描述 如图17.7,S3C2410处理器内置了IIS总线接口,S3C2410的IIS总线时钟信号SCK与Philip公司的UDA1341的BCK连接,字段选择连接于WS引脚。UDA1341提供两个音频通道,分别用于输入和输出,对应的引脚连接:IIS总线的音频输出IISSDO对应于UDA1341的音频输入;IIS总线的音频输入IISSDI对应于UDA1341的音频输出。UDA1341的L3接口相当于一个混音器控制接口,可以用来控制输入/输出音频信号的音量大小、低音等。L3接口的引脚L3MODE、L3DATA、L3CLOCK分别连接到S3C2410的3个GPIO来控制。 图17.7 S3C2410与UDA1341 IIS接口连接 Philips 公司的UDA1341支持IIS总线数据格式,采用位元流转换技术进行信号处理,完成声音信号的模数转换,具有可编程增益放大器和数字自动增益控制器,其低功耗、低电压的特点使其非常适合用于MD/CD、笔记本电脑等便携式设备。UDA1341对外提供2组音频信号输入接口,每组包括左右2个声道。 图17.8 UDA1341 内部结构 如图17.8所示,2组音频输入在UDA1341内部的处理存在很大差别:第一组音频信号输入后经过1个0 dB/6 dB开关后采样送入数字混音器:第二组音频信号输入后先经过可编程增益放大器(PGA),然后再进行采样,采样后的数据要再经过数字自动增益控制器(AGC)送入数字混音器。设计硬件电路时选用第二组输入音频信号,这样可以通过软件的方法实现对系统输入音量大小的调节。显然选用第二组可以通过L3总线接口控制AGC来实现。另外,选择通道2还可以通过PGA对从MIC输入的信号进行片内放大。 S3C2410与UDA1341之间的IIS接口有3种工作方式: • 正常传输模式。该模式下使用IISCON寄存器对FIFO进行控制,CPU通过轮询方式访问FIFO寄存器,以完成对FIFO缓存传输或接收的处理。 • DMA模式。通过设置IISFCON寄存器使IIS接口工作于这种模式。在该模式下,FIFO寄存器组的控制权掌握在DMA控制器上,当FIFO满时,由DMA控制器对FIFO中的数据进行处理。DMA模式的选择由IISCON寄存器的第4和第5位控制。 • 传输/接收模式。该模式下,IIS数据线将通过双通道DMA同时接收和发送音频数据。在OSS驱动中,将使用此模式。 17.5.2注册dsp和mixer接口 如代码清单17.28,在UDA1341 OSS驱动的模块加载函数中,将完成如下工作: • 初始化IIS接口硬件,设置L3总线对应的GPIO。 • 申请用于音频数据传输的DMA通道。 • 初始化UDA1341到恰当的工作模式。 • 注册dsp和mixer接口。 代码清单17.28 UDA1341 OSS驱动模块加载函数 1 //音频(dsp)文件操作 2 static struct file_operations smdk2410_audio_fops = 3 { 4 llseek: smdk2410_audio_llseek, 5 write: smdk2410_audio_write, 6 read: smdk2410_audio_read, 7 poll: smdk2410_audio_poll, 8 ioctl: smdk2410_audio_ioctl, 9 open: smdk2410_audio_open, 10 release: smdk2410_audio_release 11 }; 12 //混音器文件操作 13 static struct file_operations smdk2410_mixer_fops = 14 { 15 ioctl: smdk2410_mixer_ioctl, 16 open: smdk2410_mixer_open, 17 release: smdk2410_mixer_release 18 }; 19 20 int __init s3c2410_uda1341_init(void) 21 { 22 unsigned long flags; 23 24 local_irq_save(flags); 25 26 /* 设置IIS接口引脚GPIO */ 27 28 set_gpio_ctrl(GPIO_L3CLOCK); // GPB 4: L3CLOCK, 输出 29 set_gpio_ctrl(GPIO_L3DATA); // GPB 3: L3DATA, 输出 30 set_gpio_ctrl(GPIO_L3MODE); // GPB 2: L3MODE, 输出 31 32 33 set_gpio_ctrl(GPIO_E3 | GPIO_PULLUP_EN | GPIO_MODE_IISSDI); //GPE 3: IISSDI 34 set_gpio_ctrl(GPIO_E0 | GPIO_PULLUP_EN | GPIO_MODE_IISSDI); //GPE 0: IISLRCK 35 set_gpio_ctrl(GPIO_E1 | GPIO_PULLUP_EN | GPIO_MODE_IISSCLK); //GPE 1:IISSCLK 36 set_gpio_ctrl(GPIO_E2 | GPIO_PULLUP_EN | GPIO_MODE_CDCLK); //GPE 2: CDCLK 37 set_gpio_ctrl(GPIO_E4 | GPIO_PULLUP_EN | GPIO_MODE_IISSDO); //GPE 4: IISSDO 38 39 local_irq_restore(flags); 40 41 init_uda1341(); 42 43 /* 输出流采样DMA通道2 */ 44 output_stream.dma_ch = DMA_CH2; 45 46 if (audio_init_dma(&output_stream, "UDA1341 out")) 47 { 48 audio_clear_dma(&output_stream); 49 printk(KERN_WARNING AUDIO_NAME_VERBOSE ": unable to get DMA channels/n"); 50 return - EBUSY; 51 } 52 /* 输入流采样DMA通道1 */ 53 input_stream.dma_ch = DMA_CH1; 54 55 if (audio_init_dma(&input_stream, "UDA1341 in")) 56 { 57 audio_clear_dma(&input_stream); 58 printk(KERN_WARNING AUDIO_NAME_VERBOSE ": unable to get DMA channels/n"); 59 return - EBUSY; 60 } 61 62 /* 注册dsp和mixer设备接口 */ 63 audio_dev_dsp = register_sound_dsp(&smdk2410_audio_fops, - 1); 64 audio_dev_mixer = register_sound_mixer(&smdk2410_mixer_fops, - 1); 65 66 printk(AUDIO_NAME_VERBOSE " initialized/n"); 67 68 return 0; 69 } UDA1341 OSS驱动的模块卸载函数中,将完成与模块加载函数相反的工作,如代码清单17.29。 代码清单17.29 UDA1341 OSS驱动模块卸载函数 1 void __exit s3c2410_uda1341_exit(void) 2 { 3 //注销dsp和mixer设备接口 4 unregister_sound_dsp(audio_dev_dsp); 5 unregister_sound_mixer(audio_dev_mixer); 6 7 //注销DMA通道 8 audio_clear_dma(&output_stream); 9 audio_clear_dma(&input_stream); /* input */ 10 printk(AUDIO_NAME_VERBOSE " unloaded/n"); 11 } 17.5.3 mixer接口IO控制函数 UDA1341 OSS驱动的ioctl()函数处理多个mixer命令,如SOUND_MIXER_INFO、 SOUND_MIXER_READ_STEREODEVS、SOUND_MIXER_WRITE_VOLUME等,用于获得或设置音量和增益等信息,如代码清单17.30所示。 代码清单17.30 UDA1341 OSS驱动ioctl()函数 1 static int smdk2410_mixer_ioctl(struct inode *inode, struct file *file, 2 unsigned int cmd, unsigned long arg) 3 { 4 int ret; 5 long val = 0; 6 7 switch (cmd) 8 { 9 case SOUND_MIXER_INFO: //获得mixer信息 10 { 11 mixer_info info; 12 strncpy(info.id, "UDA1341", sizeof(info.id)); 13 strncpy(info.name, "Philips UDA1341", sizeof(info.name)); 14 info.modify_counter = audio_mix_modcnt; 15 return copy_to_user((void*)arg, &info, sizeof(info)); 16 } 17 18 case SOUND_OLD_MIXER_INFO: 19 { 20 _old_mixer_info info; 21 strncpy(info.id, "UDA1341", sizeof(info.id)); 22 strncpy(info.name, "Philips UDA1341", sizeof(info.name)); 23 return copy_to_user((void*)arg, &info, sizeof(info)); 24 } 25 26 case SOUND_MIXER_READ_STEREODEVS://获取设备对立体声的支持 27 return put_user(0, (long*)arg); 28 29 case SOUND_MIXER_READ_CAPS: //获取声卡能力 30 val = SOUND_CAP_EXCL_INPUT; 31 return put_user(val, (long*)arg); 32 33 case SOUND_MIXER_WRITE_VOLUME: //设置音量 34 ret = get_user(val, (long*)arg); 35 if (ret) 36 return ret; 37 uda1341_volume = 63-(((val &0xff) + 1) *63) / 100; 38 uda1341_l3_address(UDA1341_REG_DATA0); 39 uda1341_l3_data(uda1341_volume); 40 break; 41 42 case SOUND_MIXER_READ_VOLUME: //获取音量 43 val = ((63-uda1341_volume) *100) / 63; 44 val |= val << 8; 45 return put_user(val, (long*)arg); 46 47 case SOUND_MIXER_READ_IGAIN: //获得增益 48 val = ((31-mixer_igain) *100) / 31; 49 return put_user(val, (int*)arg); 50 51 case SOUND_MIXER_WRITE_IGAIN: //设置增益 52 ret = get_user(val, (int*)arg); 53 if (ret) 54 return ret; 55 mixer_igain = 31-(val *31 / 100); 56 /* 使用mixer增益通道1 */ 57 uda1341_l3_address(UDA1341_REG_DATA0); 58 uda1341_l3_data(EXTADDR(EXT0)); 59 uda1341_l3_data(EXTDATA(EXT0_CH1_GAIN(mixer_igain))); 60 break; 61 62 default: 63 DPRINTK("mixer ioctl %u unknown/n", cmd); 64 return - ENOSYS; 65 } 66 67 audio_mix_modcnt++; 68 return 0; 69 } 17.5.4 dsp接口音频数据传输 OSS声卡驱动中,dsp接口的读写函数是核心中的核心,直接对应着录音和放音的流程。 OSS 的读函数存在一个与普通字符设备驱动读函数不同的地方,那就是一般而言,对于普通字符设备驱动,如果用户要求读count个字节,而实际上只有 count1字节可获得(count1< count)时,它会将这count1字节拷贝给用户后即返回count1;而dsp接口的读函数会分次拷贝,如果第1次不能满足,它会等待第2次,直到 “count1 + count2 + ... = count”为止再返回count。这种设计是合理的,因为OSS驱动应该负责音频数据的流量控制。代码清单17.31给出了UDA1341 OSS驱动的读函数实现。 代码清单17.31 UDA1341 OSS驱动的读函数 1 static ssize_t smdk2410_audio_read(struct file *file, char *buffer, size_t 2 count, loff_t *ppos) 3 { 4 const char *buffer0 = buffer; 5 audio_stream_t *s = &input_stream; //得到数据区的指针 6 int chunksize, ret = 0; 7 8 DPRINTK("audio_read: count=%d/n", count); 9 10 if (ppos != &file->f_pos) 11 return - ESPIPE; 12 13 if (!s->buffers) 14 { 15 int i; 16 17 if (audio_setup_buf(s)) 18 return - ENOMEM; 19 //依次从缓存区读取数据 20 for (i = 0; i < s->nbfrags; i++) 21 { 22 audio_buf_t *b = s->buf; 23 down(&b->sem); 24 s3c2410_dma_queue_buffer(s->dma_ch, (void*)b, b->dma_addr, s->fragsize, 25 DMA_BUF_RD); 26 NEXT_BUF(s, buf); 27 } 28 } 29 30 //满足用户的所有读需求 31 while (count > 0) 32 { 33 audio_buf_t *b = s->buf; 34 35 if (file->f_flags &O_NONBLOCK) //非阻塞 36 { 37 ret = - EAGAIN; 38 if (down_trylock(&b->sem)) 39 break; 40 } 41 else 42 { 43 ret = - ERESTARTSYS; 44 if (down_interruptible(&b->sem)) 45 break; 46 } 47 48 chunksize = b->size; 49 //从缓存区读取数据 50 if (chunksize > count) 51 chunksize = count; 52 DPRINTK("read %d from %d/n", chunksize, s->buf_idx); 53 if (copy_to_user(buffer, b->start + s->fragsize - b->size, //调用拷贝函数 54 chunksize)) 55 { 56 up(&b->sem); 57 return - EFAULT; 58 } 59 b->size -= chunksize; 60 61 buffer += chunksize; 62 count -= chunksize; //已经给用户拷贝了一部分,count减少 63 if (b->size > 0) 64 { 65 up(&b->sem); 66 break; 67 } 68 //将缓存区释放 69 s3c2410_dma_queue_buffer(s->dma_ch, (void*)b, b->dma_addr, s->fragsize, 70 DMA_BUF_RD); 71 72 NEXT_BUF(s, buf); 73 } 74 75 if ((buffer - buffer0)) 76 ret = buffer - buffer0; 77 78 return ret; 79 } OSS驱动dsp接口的写函数与读函数类似,一般来说,它也应该满足用户的所有写需求后再返回,如代码清单17.32。 代码清单17.32 UDA1341 OSS驱动的写函数 1 static ssize_t smdk2410_audio_write(struct file *file, const char *buffer, 2 size_t count, loff_t *ppos) 3 { 4 const char *buffer0 = buffer; 5 audio_stream_t *s = &output_stream; 6 int chunksize, ret = 0; 7 8 DPRINTK("audio_write : start count=%d/n", count); 9 10 switch (file->f_flags &O_ACCMODE) 11 { 12 case O_WRONLY: //只写 13 case O_RDWR: //读写 14 break; 15 default: //只读不合法 16 return - EPERM; 17 } 18 //设置DMA缓冲区 19 if (!s->buffers && audio_setup_buf(s)) 20 return - ENOMEM; 21 22 count &= ~0x03; 23 24 while (count > 0) //直到满足用户的所有写需求 25 { 26 audio_buf_t *b = s->buf; 27 //非阻塞访问 28 if (file->f_flags &O_NONBLOCK) 29 { 30 ret = - EAGAIN; 31 if (down_trylock(&b->sem)) 32 break; 33 } 34 else 35 { 36 ret = - ERESTARTSYS; 37 if (down_interruptible(&b->sem)) 38 break; 39 } 40 //从用户空间拷贝音频数据 41 if (audio_channels == 2) 42 { 43 chunksize = s->fragsize - b->size; 44 if (chunksize > count) 45 chunksize = count; 46 DPRINTK("write %d to %d/n", chunksize, s->buf_idx); 47 if (copy_from_user(b->start + b->size, buffer, chunksize)) 48 { 49 up(&b->sem); 50 return - EFAULT; 51 } 52 b->size += chunksize; 53 } 54 else 55 { 56 chunksize = (s->fragsize - b->size) >> 1; 57 58 if (chunksize > count) 59 chunksize = count; 60 DPRINTK("write %d to %d/n", chunksize *2, s->buf_idx); 61 if (copy_from_user_mono_stereo(b->start + b->size, buffer, chunksize)) 62 { 63 up(&b->sem); 64 return - EFAULT; 65 } 66 67 b->size += chunksize * 2; 68 } 69 70 buffer += chunksize; 71 count -= chunksize; //已经从用户拷贝了一部分,count减少 72 if (b->size < s->fragsize) 73 { 74 up(&b->sem); 75 break; 76 } 77 //发起DMA操作 78 s3c2410_dma_queue_buffer(s->dma_ch, (void*)b, b->dma_addr, b->size, 79 DMA_BUF_WR); 80 b->size = 0; 81 NEXT_BUF(s, buf); 82 } 83 84 if ((buffer - buffer0)) 85 ret = buffer - buffer0; 86 87 DPRINTK("audio_write : end count=%d/n/n", ret); 88 89 return ret; 90 } |