JZ2440使用WM9876声卡,其硬件原理图:
其控制接口有2线和3线模式,有MODE管脚决定,高电平为3线模式,低电平为2线模式,我们这里MODE接高电平,
使用3线接口模式。
3线接口不是L3接口,但与L3接口相似,s3c2410-uda1341.c已经有了L3接口的操作,但没有3线接口的操作,我们把
L3接口的操作修改一下支持3线接口,用3线接口接到的那三个GPIO来模拟3线接口的时序,把对3线的操作抽象出来。
3线信号:
CSB:
SCLK:位时钟(芯片BCK脚)(一时钟一位数据)
SD:数据
说明:
every rising dege of SCLK in one data bit from the SDIN pin. A rising dege on CSB/GPIO1 pin
latches in a complete control word consisting of the last 16 bits.
每次传输16位,传完第16位后,产生一个CSB上升沿,代表一次数据传输完毕。每位数据在SCLK的上升沿传输。
3线控制信号一个控制字由16 bit组成,这 16 bit 数据的含义:前7位(bit 15 : bit 9):是地址选择位,选择芯
片的那个寄存器操作。余下9位(bit 8 : bit 0)是数据位,写到寄存器对应的各位。
传输顺序是先高位后低位,与UDA1341顺序相反。
3线控制信号的数据发送时序图:
对s3c2410-uda1341.c文件进行修改,完成下面3步,使支持3线接口:
1、添加wm8976写寄存器函数wm8976_write_reg(),根据时序图,用与3线相接的GPIO模拟数据发送时的时序。
static void wm8976_write_reg(unsigned char reg, unsigned int data)
{
int i;
unsigned long flags;
unsigned short val = (reg << 9) | (data & 0x1ff);// 将寄存器和数据合并成16bit
//首先把三条线都输出为高电平
s3c2410_gpio_setpin(S3C2410_GPB2,1);//S3C2410_GPB2:L3MODE
s3c2410_gpio_setpin(S3C2410_GPB3,1);//S3C2410_GPB3: L3DATA
s3c2410_gpio_setpin(S3C2410_GPB4,1);//S3C2410_GPB4: L3CLOCK
local_irq_save(flags); // 先关中断
//将合并好的16位数据,依次发出,在时钟的上升沿获得数据
for (i = 0; i < 16; i++) { // WM8976是先发出bit15位
if (val & (1<<15)) // 如果是第15位
{
// 先给L3CLOCK上升沿,然后拉高L3DATA线
s3c2410_gpio_setpin(S3C2410_GPB4,0);
// 同上,L3DATA拉高
s3c2410_gpio_setpin(S3C2410_GPB3,1);
udelay(1); // 延时一会
// 拉高L3CLOCK
s3c2410_gpio_setpin(S3C2410_GPB4,1);
}
else
{ // 先给L3CLOCK上升沿,过段时间拉低L3DATA线
s3c2410_gpio_setpin(S3C2410_GPB4,0);
s3c2410_gpio_setpin(S3C2410_GPB3,0);
udelay(1);
s3c2410_gpio_setpin(S3C2410_GPB4,1);
}
val = val << 1; // 左移一位,发出下一位的寄存器数据
}
//还需要wm8976_write_reg()最后加上:
s3c2410_gpio_setpin(S3C2410_GPB2,0);// 拉低L3MODE
udelay(1); // 延时一会
s3c2410_gpio_setpin(S3C2410_GPB2,1);// S3C2410_GPB2:L3MODE
// 为稳定,拉高L3DATA线
s3c2410_gpio_setpin(S3C2410_GPB3,1);
s3c2410_gpio_setpin(S3C2410_GPB4,1);// 同上,拉高L3CLOCK
local_irq_restore(flags); // 开中断
}
2、添加芯片初始化函数init_wm8976(),往哪个寄存器写什么值,写寄存器的顺序,参考芯片手册page88:
static void init_wm8976(void)
{
uda1341_volume = 57;
uda1341_boost = 0;
/* software reset */
wm8976_write_reg(0, 0);
/* OUT2的左/右声道打开
* 左/右通道输出混音打开
* 左/右DAC打开
*/
wm8976_write_reg(0x3, 0x6f);
wm8976_write_reg(0x1, 0x1f);//biasen,BUFIOEN.VMIDSEL=11b
wm8976_write_reg(0x2, 0x185);//ROUT1EN LOUT1EN, inpu PGA enable ,ADC enable
wm8976_write_reg(0x6, 0x0);//SYSCLK=MCLK
wm8976_write_reg(0x4, 0x10);//16bit
wm8976_write_reg(0x2B,0x10);//BTL OUTPUT
wm8976_write_reg(0x9, 0x50);//Jack detect enable
wm8976_write_reg(0xD, 0x21);//Jack detect
wm8976_write_reg(0x7, 0x01);//Jack detect
}
3、修改一下音量的调节,也是往某个寄存器写值实现。音量值范围:ioctl:0-100,对应芯片:63-0。要调一下比例。
static int smdk2410_mixer_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
int ret;
long val = 0;
switch (cmd) {
case SOUND_MIXER_INFO:
{
mixer_info info;
strncpy(info.id, "UDA1341", sizeof(info.id));
strncpy(info.name,"Philips UDA1341", sizeof(info.name));
info.modify_counter = audio_mix_modcnt;
return copy_to_user((void *)arg, &info, sizeof(info));
}
case SOUND_OLD_MIXER_INFO:
{
_old_mixer_info info;
strncpy(info.id, "UDA1341", sizeof(info.id));
strncpy(info.name,"Philips UDA1341", sizeof(info.name));
return copy_to_user((void *)arg, &info, sizeof(info));
}
case SOUND_MIXER_READ_STEREODEVS:
return put_user(0, (long *) arg);
case SOUND_MIXER_READ_CAPS:
val = SOUND_CAP_EXCL_INPUT;
return put_user(val, (long *) arg);
case SOUND_MIXER_WRITE_VOLUME:
ret = get_user(val, (long *) arg);
if (ret)
return ret;
/* ioctl: val越大表示音量越大, 0-最小, 100-最大
* UDA1341: 寄存器的值越小音量越大
* WM8976: 52,53号寄存器bit[5:0]表示音量, 值越大音量越大, 0-63
*/
uda1341_volume = (((val & 0xff) + 1) * 63) / 100;
wm8976_write_reg(52, (1<<8)|uda1341_volume);
wm8976_write_reg(53, (1<<8)|uda1341_volume);
//uda1341_l3_address(UDA1341_REG_DATA0);
//uda1341_l3_data(uda1341_volume);
break;
case SOUND_MIXER_READ_VOLUME:
val = (uda1341_volume * 100) / 63;
return put_user(val, (long *) arg);
case SOUND_MIXER_READ_IGAIN:
val = ((31- mixer_igain) * 100) / 31;
return put_user(val, (int *) arg);
case SOUND_MIXER_WRITE_IGAIN:
ret = get_user(val, (int *) arg);
if (ret)
return ret;
mixer_igain = 31 - (val * 31 / 100);
/* use mixer gain channel 1*/
//uda1341_l3_address(UDA1341_REG_DATA0);
//uda1341_l3_data(EXTADDR(EXT0));
//uda1341_l3_data(EXTDATA(EXT0_CH1_GAIN(mixer_igain)));
break;
default:
DPRINTK("mixer ioctl %u unknown\n", cmd);
return -ENOSYS;
}
audio_mix_modcnt++;
return 0;
}
把修改好的s3c2410-uda1341.c另存为s3c-wm8976.c。
测试:
1. 确定内核里已经配置了sound\soc\s3c24xx\s3c2410-uda1341.c
-> Device Drivers
-> Sound
-> Advanced Linux Sound Architecture // 兼容OSS
-> Advanced Linux Sound Architecture
-> System on Chip audio support
<*> I2S of the Samsung S3C24XX chips
2. 修改sound/soc/s3c24xx/Makefile
obj-y += s3c2410-uda1341.o
改为:
obj-y += s3c-wm8976.o
3. make uImage
使用新内核启动
4. ls -l /dev/dsp /dev/mixer
5. 播放:
在WINDOWS PC里找一个wav文件,放到开发板根文件系统里
cat Windows.wav > /dev/dsp
6. 录音:
cat /dev/dsp > sound.bin
然后对着麦克风说话
ctrl+c退出
cat sound.bin > /dev/dsp // 就可以听到录下的声音
参考文章:L3接口与UDA1341声卡测试点击打开链接