学习目标:1. WM9876接口和工作原理;2. WM9876驱动移植;3. WM9876应用测试;4. 问题总结
1. WM9876接口和工作原理
本节使用了
JZ2440
开发板移植
WM9876
驱动,其结构如下图所示,最后利用
madplay
工具测试音频文件。
IIS
和控制接口;
WM9876
声卡是负责录音、播音、调节音量和声音合成等的一种多媒体板卡。包括两种接口:
IIS
接口(提供音频接收和发送)、控制接口(控制音量大小,使能各个输出通道等)
1)当我们播放声音时,将数字信号传入I2SDO
脚,声卡便通过解码,产生模拟信号到喇叭
/
耳机;
2)当我们录音时,声卡便获取麦克风的模拟信号,编码出数字信号到
I2SDI
引脚上。
-----------
接口说明
----------------
--> IIS
接口相关的引脚如下:
MCLK
:主机为解码芯片提供的系统同步时钟(
Master/system clock input
)。
BCLK(LRC)
:编解码芯片提供的串行时钟信号(
Audio bit clock output
)。
ISLRCK
:采样频率信号,当为低电平时采样的是左声道信号,高电平时采样的是右声道信号。
I2SDI(ADCDAT)
:
ADC
数据输入。
I2SDO(DACDAT)
:
DAC
数据输出。
-->
控制接口相关的引脚如下:
MODE: 3
线
/2
线控制选择
,
当
MODE
为高
,
表示为
3
线控制
,MODE
位低
,
表示
2
线控制,2
线模式变为
IIC
模式
(
2440
接的高电平为
3
线模式
);
CSB/GPIO1:
控制数据使能引脚;
SCLK:
时钟引脚;
SDIN:
数据输入输出引脚。
2. WM9876驱动移植
2.1 驱动分析
先以uda1341.c
源码为例,分析
驱动程序框架。源码
目录位于:soundsocs3c24xxs3c2410-uda1341.c ,
分析:
1. s3c2410_uda1341_init
static int __init s3c2410_uda1341_init(void) { memzero(&input_stream, sizeof(audio_stream_t)); memzero(&output_stream, sizeof(audio_stream_t)); return driver_register(&s3c2410iis_driver); //注册 }
-->
static struct device_driver s3c2410iis_driver = {
.name = "s3c2410-iis",
.bus = &platform_bus_type, //platform_bus_type类型
.probe = s3c2410iis_probe,
.remove = s3c2410iis_remove,
};
2. s3c2410iis_probe
由于同名驱动和设备,调用
s3c2410iis_probe:
1 static int s3c2410iis_probe(struct device *dev) 2 { 3 struct platform_device *pdev = to_platform_device(dev); 4 struct resource *res; 5 unsigned long flags; 8 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 10 iis_base = (void *)S3C24XX_VA_IIS ; 12 iis_clock = clk_get(dev, "iis"); 14 clk_enable(iis_clock); 16 local_irq_save(flags); 17 /* 配置GPIO */ 18 /* GPB 4: L3CLOCK, OUTPUT */ 19 s3c2410_gpio_cfgpin(S3C2410_GPB4, S3C2410_GPB4_OUTP); 20 s3c2410_gpio_pullup(S3C2410_GPB4,1); 21 /* GPB 3: L3DATA, OUTPUT */ 22 s3c2410_gpio_cfgpin(S3C2410_GPB3,S3C2410_GPB3_OUTP); 23 /* GPB 2: L3MODE, OUTPUT */ 24 s3c2410_gpio_cfgpin(S3C2410_GPB2,S3C2410_GPB2_OUTP); 25 s3c2410_gpio_pullup(S3C2410_GPB2,1); 26 /* GPE 3: I2SSDI */ 27 s3c2410_gpio_cfgpin(S3C2410_GPE3,S3C2410_GPE3_I2SSDI); 28 s3c2410_gpio_pullup(S3C2410_GPE3,0); 29 /* GPE 0: I2SLRCK */ 30 s3c2410_gpio_cfgpin(S3C2410_GPE0,S3C2410_GPE0_I2SLRCK); 31 s3c2410_gpio_pullup(S3C2410_GPE0,0); 32 /* GPE 1: I2SSCLK */ 33 s3c2410_gpio_cfgpin(S3C2410_GPE1,S3C2410_GPE1_I2SSCLK); 34 s3c2410_gpio_pullup(S3C2410_GPE1,0); 35 /* GPE 2: CDCLK */ 36 s3c2410_gpio_cfgpin(S3C2410_GPE2,S3C2410_GPE2_CDCLK); 37 s3c2410_gpio_pullup(S3C2410_GPE2,0); 38 /* GPE 4: I2SSDO */ 39 s3c2410_gpio_cfgpin(S3C2410_GPE4,S3C2410_GPE4_I2SSDO); 40 s3c2410_gpio_pullup(S3C2410_GPE4,0); 41 42 local_irq_restore(flags); 43 44 init_s3c2410_iis_bus();///* 设置S3C2440的IIS控制器 */ 45 46 init_uda1341();// /* 使用L3接口初始化uda1341芯片 */ 47 /* 设置两个DMA通道:一个用于播放,另一个用于录音 */ 48 output_stream.dma_ch = DMACH_I2S_OUT; 49 if (audio_init_dma(&output_stream, "UDA1341 out")) { 50 audio_clear_dma(&output_stream,&s3c2410iis_dma_out); 52 } 54 input_stream.dma_ch = DMACH_I2S_IN; 55 if (audio_init_dma(&input_stream, "UDA1341 in")) { 56 audio_clear_dma(&input_stream,&s3c2410iis_dma_in); 57 } 59 audio_dev_dsp = register_sound_dsp(&smdk2410_audio_fops, -1); // -->sound_insert_unit(&chains[3], fops, dev, 3, 131, "dsp", S_IWUSR | S_IRUSR, NULL); 60 audio_dev_mixer = register_sound_mixer(&smdk2410_mixer_fops, -1); // -->sound_insert_unit(&chains[0], fops, dev, 0, 128, "mixer", S_IRUSR | S_IWUSR, NULL); 61 ...... }
其中, /dev/dsp
设备节点,实现音频的输入输出
IIS
接口(由
chains[3]
数组获得)
/dev/mixer
设备节点,实现音量调节、高音等控制(由
chains[0]
数组获得)
audio_dev_dsp = register_sound_dsp(&smdk2410_audio_fops, -1);
audio_dev_mixer = register_sound_mixer(&smdk2410_mixer_fops, -1);注册了这两个设备节点和file_operation供上层驱动函数调用;
static struct sound_unit *chains[SOUND_STEP]; /* * Allocations * 0 *16 Mixers * 1 *8 Sequencers * 2 *16 Midi * 3 *16 DSP * 4 *16 SunDSP * 5 *16 DSP16 * 6 -- sndstat (obsolete) * 7 *16 unused * 8 -- alternate sequencer (see above) * 9 *16 raw synthesizer access * 10 *16 unused * 11 *16 unused * 12 *16 unused * 13 *16 unused * 14 *16 unused * 15 *16 unused */
接下来,看一次操作函数,源码程序:linux-2.6.22.6soundSound_core.c,(也可从该源码看起,再看
soundsocs3c24xxs3c2410-uda1341.c程序
),仅有一个
open
往后程序可得其他的操作函数:
static const struct file_operations soundcore_fops= { /* We must have an owner or the module locking fails */ .owner = THIS_MODULE, .open = soundcore_open, };
1 int soundcore_open(struct inode *inode, struct file *file) 2 { 3 int chain; 4 int unit = iminor(inode); 5 struct sound_unit *s; 6 const struct file_operations *new_fops = NULL; 7 8 chain=unit&0x0F; 9 if(chain==4 || chain==5) /* dsp/audio/dsp16 */ 10 { 11 unit&=0xF0; 12 unit|=3; 13 chain=3; 14 } 15 16 spin_lock(&sound_loader_lock); 17 s = __look_for_unit(chain, unit); 18 if (s) 19 new_fops = fops_get(s->unit_fops); 20 if (!new_fops) { 21 spin_unlock(&sound_loader_lock); 22 request_module("sound-slot-%i", unit>>4); 23 request_module("sound-service-%i-%i", unit>>4, chain); 24 spin_lock(&sound_loader_lock); 25 s = __look_for_unit(chain, unit); 26 if (s) 27 new_fops = fops_get(s->unit_fops); 28 } 29 if (new_fops) { 30 int err = 0; 31 const struct file_operations *old_fops = file->f_op; 32 file->f_op = new_fops; 33 spin_unlock(&sound_loader_lock); 34 if(file->f_op->open) 35 err = file->f_op->open(inode,file); 36 if (err) { 37 fops_put(file->f_op); 38 file->f_op = fops_get(old_fops); 39 } 40 fops_put(old_fops); 41 return err; 42 } 43 spin_unlock(&sound_loader_lock); 44 return -ENODEV; 45 }
函数框架:app: open () // 假设主设备号为14
soundcore_open函数
--> int unit = iminor(inode);
s = __look_for_unit(chain, unit);
// 从chains数组里得到, 谁来设置这个数组?
new_fops = fops_get(s->unit_fops);
file->f_op = new_fops;
err = file->f_op->open(inode,file);
从源码框架可以看出: 次设备号找到声卡驱动,由chains[chain]数组里找到sound_unit结构体,其中,一个sound_unit对应一个声卡驱动,从而获取该声卡驱动的file_operations,替换声卡的file->f_op,同理,录音和播放的read和write函数也是这种原理。
录音: app: read file->f_op->read
播放: app: write file->f_op->write
具体分析参考博客:https://www.cnblogs.com/lifexy/p/7867782.html
2.2 驱动移植
uda1341声卡和WM8976声卡音频都是I2S接口,只有控制接口不同。通过s3c2410-uda1341.c驱动程序进行移植。接下来主要对控制接口进行配置、操作。
WM8976的3线接口的时序图为:
SOIN:16bit数据 7位寄存器地址+9位寄存器数据。需要写以下函数:
static void wm8976_write_reg(unsigned char reg, unsigned int data)
static void init_wm8976(void)
1)初始化函数之前,需要根据时序图写操作寄存器函数
1 static void wm8976_write_reg(unsigned char reg, unsigned int data) 2 { 3 int i; 4 unsigned long flags; 5 unsigned short val = (reg << 9) | (data & 0x1ff);//16bit, 寄存器地址7位,数据9位 6 7 s3c2410_gpio_setpin(S3C2410_GPB2,1);//CSB 8 s3c2410_gpio_setpin(S3C2410_GPB3,1);//SDIN 9 s3c2410_gpio_setpin(S3C2410_GPB4,1);//SCLK 10 11 local_irq_save(flags); 12 13 for (i = 0; i < 16; i++){ //先传高位 14 if (val & (1<<15)) //1 15 { 16 s3c2410_gpio_setpin(S3C2410_GPB4,0);//时钟低电平 17 s3c2410_gpio_setpin(S3C2410_GPB3,1);//数据线输出1 18 udelay(1); 19 s3c2410_gpio_setpin(S3C2410_GPB4,1);//时钟高电平 20 } 21 else //0 22 { 23 s3c2410_gpio_setpin(S3C2410_GPB4,0); 24 s3c2410_gpio_setpin(S3C2410_GPB3,0); 25 udelay(1); 26 s3c2410_gpio_setpin(S3C2410_GPB4,1); 27 } 28 29 val = val << 1; 30 } 31 32 s3c2410_gpio_setpin(S3C2410_GPB2,0);//传输完成后让CSB信号产生低脉冲,写入WM8976 33 udelay(1); 34 s3c2410_gpio_setpin(S3C2410_GPB2,1);//恢复高电平 35 s3c2410_gpio_setpin(S3C2410_GPB3,1); 36 s3c2410_gpio_setpin(S3C2410_GPB4,1); 37 38 local_irq_restore(flags); //结束开中断 39 }
2)根据wm8976G芯片数据手册(page 87),写初始化函数,初始化寄存器(使能输出声道1,2,混响器等)
芯片手册:
初始化函数:
1 static void init_wm8976(void) 2 { 3 uda1341_volume = 57; 4 uda1341_boost = 0; 5 6 /* software reset */ 7 wm8976_write_reg(0, 0); 8 9 /* 地址3 bit[6-5]: OUT2的左/右声道打开 10 * 地址2 bit[3-2]: 左/右通道输出混音打开 11 * 地址1 bit[1-0]: 左/右DAC打开 12 */ 13 wm8976_write_reg(0x3, 0x6f); 14 15 wm8976_write_reg(0x1, 0x1f);//biasen,BUFIOEN.VMIDSEL=11b 16 wm8976_write_reg(0x2, 0x185);//ROUT1EN LOUT1EN, inpu PGA enable ,ADC enable 17 18 wm8976_write_reg(0x6, 0x0);//SYSCLK=MCLK 19 wm8976_write_reg(0x4, 0x10);//16bit 20 wm8976_write_reg(0x2B,0x10);//BTL OUTPUT 21 wm8976_write_reg(0x9, 0x50);//Jack detect enable 22 wm8976_write_reg(0xD, 0x21);//Jack detect 23 wm8976_write_reg(0x7, 0x01);//Jack detect 24 }
3)由于wm8976的控制接口和时序图与uda1341不一样,所以需要根据芯片手册修改mixer的file_operations->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 case SOUND_MIXER_INFO: 9 { 10 mixer_info info; 11 strncpy(info.id, "UDA1341", sizeof(info.id)); 12 strncpy(info.name,"Philips UDA1341", sizeof(info.name)); 13 info.modify_counter = audio_mix_modcnt; 14 return copy_to_user((void *)arg, &info, sizeof(info)); 15 } 16 17 case SOUND_OLD_MIXER_INFO: 18 { 19 _old_mixer_info info; 20 strncpy(info.id, "UDA1341", sizeof(info.id)); 21 strncpy(info.name,"Philips UDA1341", sizeof(info.name)); 22 return copy_to_user((void *)arg, &info, sizeof(info)); 23 } 24 25 case SOUND_MIXER_READ_STEREODEVS: 26 return put_user(0, (long *) arg); 27 28 case SOUND_MIXER_READ_CAPS: 29 val = SOUND_CAP_EXCL_INPUT; 30 return put_user(val, (long *) arg); 31 32 case SOUND_MIXER_WRITE_VOLUME: 33 ret = get_user(val, (long *) arg); 34 if (ret) 35 return ret; 36 37 /* ioctl: val越大表示音量越大, 0-最小, 100-最大 38 * UDA1341: 寄存器的值越小音量越大 39 * WM8976: 52,53号寄存器bit[5:0]表示音量, 值越大音量越大, 0-63 40 */ 41 42 uda1341_volume = (((val & 0xff) + 1) * 63) / 100; 43 wm8976_write_reg(52, (1<<8)|uda1341_volume); 44 wm8976_write_reg(53, (1<<8)|uda1341_volume); 45 //uda1341_l3_address(UDA1341_REG_DATA0); 46 //uda1341_l3_data(uda1341_volume); 47 break; 48 49 case SOUND_MIXER_READ_VOLUME: 50 val = (uda1341_volume * 100) / 63; 51 return put_user(val, (long *) arg); 52 53 case SOUND_MIXER_READ_IGAIN: 54 val = ((31- mixer_igain) * 100) / 31; 55 return put_user(val, (int *) arg); 56 57 case SOUND_MIXER_WRITE_IGAIN: 58 ret = get_user(val, (int *) arg); 59 if (ret) 60 return ret; 61 mixer_igain = 31 - (val * 31 / 100); 62 /* use mixer gain channel 1*/ 63 //uda1341_l3_address(UDA1341_REG_DATA0); 64 //uda1341_l3_data(EXTADDR(EXT0)); 65 //uda1341_l3_data(EXTDATA(EXT0_CH1_GAIN(mixer_igain))); 66 break; 67 68 default: 69 DPRINTK("mixer ioctl %u unknown ", cmd); 70 return -ENOSYS; 71 } 72 73 audio_mix_modcnt++; 74 return 0; 75 }
2.3 配置和编译内核
1) 确定内核里已经配置了soundsocs3c24xxs3c2410-uda1341.c
-> Device Drivers
-> Sound
-> Advanced Linux Sound Architecture
-> Advanced Linux Sound Architecture // 兼容OSS
-> System on Chip audio support
<*> I2S of the Samsung S3C24XX chips
2) 将修改好的s3c-wm8976.c放入linux-2.6.22.6/sound/soc/s3c24xx目录下;
3) 修改目录linux-2.6.22.6/sound/soc/s3c24xx下的makefile:
obj-y += s3c2410-uda1341.o
改为:
obj-y += s3c-wm8976.o
4) make uImage生成内核镜像文件,在上面目录下会生成对应的s3c-wm8976.o文件;
烧写uImage到开发板,启动可看到/dev/dsp /dev/mixer两个设备节点。
3. WM9876应用测试
3.1 简单测试
1)将.wav音频文件拷贝到开发板:
# tftp -g -r Windows.wav 10.70.12.166 (注意修改网口的IP地址)
2)播放音频:
# cat Windows.wav > /dev/dsp
录音:
# cat /dev/dsp > sound.bin 对着麦克风说话,存到sound.bin文件中
按下 ctrl+c退出
# cat sound.bin > /dev/dsp 读取sound.bin文件
3.2 安装madplay测试
Madplay是一个根据MAD算法写的MP3播放器,而MP3属于高压缩比(11:1)的文件,所以需要madplay解码后才能给我们声卡播放,使用之前,需要先来移植madplay。
步骤如下:
1)首先下载并解压3个文件
- libid3tag-0.15.1b.tar.gz mp3的解码库
- libmad-0.15.1b.tar.gz madplay的文件库
- madplay-0.15.2b.tar.gz madplay播放器的源码
分别解压:
# tar xzf libid3tag-0.15.1b.tar.gz
# tar xzf libmad-0.15.1b.tar.gz
# tar xzf madplay-0.15.2b.tar.gz
2)创建安装目录:# mkdir tmp
编译安装libid3tag-0.15.1b
# mkdir tmp
# cd libid3tag-0.15.1b
# ./configure --host=arm-linux --prefix=/work/drivers_and_test/ 21th_sound/ app/ tmp
# make
# make install
编译安装 libmad-0.15.1b
# cd libmad-0.15.1b
# ./configure --host=arm-linux --prefix=/work/drivers_and_test/21th_sound/app/ tmp
# make
# make install
编译安装madplay
# cd madplay-0.15.2b/
# ./configure --host=arm-linux --prefix=/work/drivers_and_test/21th_sound/app/tmp LDFLAGS="-L/work/drivers_and_test/21th_sound/app/tmp/lib" CFLAGS="-I /work/drivers_and_test/21th_sound/app/tmp/include"
# make
# make install
//CFLAGS:指定头文件,LDFLASG:指定库文件
3)把/tmp/bin目录下的所有文件,复制开发板的bin目录下:
# cp bin/* /work/nfs_root/
4)把/ tmp/lib目录下的带so文件,复制到开发板最小根文件nfs的lib目录下
# cd app/tmp/lib
# cp *so* /work/nfs_root/lib -d //带链接复制
5)将mp3音频文件拷贝至开发板,并使用madplay播放mp3音频
# madplay --tty-control 1.mp3 //播放1.mp3
6)使用madplay控制播放mp3
# madplay --tty-control 1.mp3 //用按键控制声音
# madplay 1.mp3 2.mp3 3.mp3 //循环播放3首歌
控制按键--可以使用热键来控制,常用的有以下几种:
· f 上一首
· b 下一首
· i 获取播放时间和播放歌曲名
· p 播放暂停
· s 停止
· + 音量加
· - 音量减
4. 问题总结
· 注意:安装2个库和madplay可能会遇到错误:
· 1. https://blog.csdn.net/xiaodingqq/article/details/82153464
· 2. arm-linux-gcc和linux内核源码版本问题,以及U-boot版本;
参考:https://www.cnblogs.com/lifexy/p/7867782.html