学习目标: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源码为例,分析驱动程序框架。源码目录位于:sound\soc\s3c24xx\s3c2410-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:
  static int s3c2410iis_probe(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct resource *res;
unsigned long flags; res = platform_get_resource(pdev, IORESOURCE_MEM, );
iis_base = (void *)S3C24XX_VA_IIS ;
iis_clock = clk_get(dev, "iis"); clk_enable(iis_clock);
local_irq_save(flags);
/* 配置GPIO */
/* GPB 4: L3CLOCK, OUTPUT */
s3c2410_gpio_cfgpin(S3C2410_GPB4, S3C2410_GPB4_OUTP);
s3c2410_gpio_pullup(S3C2410_GPB4,);
/* GPB 3: L3DATA, OUTPUT */
s3c2410_gpio_cfgpin(S3C2410_GPB3,S3C2410_GPB3_OUTP);
/* GPB 2: L3MODE, OUTPUT */
s3c2410_gpio_cfgpin(S3C2410_GPB2,S3C2410_GPB2_OUTP);
s3c2410_gpio_pullup(S3C2410_GPB2,);
/* GPE 3: I2SSDI */
s3c2410_gpio_cfgpin(S3C2410_GPE3,S3C2410_GPE3_I2SSDI);
s3c2410_gpio_pullup(S3C2410_GPE3,);
/* GPE 0: I2SLRCK */
s3c2410_gpio_cfgpin(S3C2410_GPE0,S3C2410_GPE0_I2SLRCK);
s3c2410_gpio_pullup(S3C2410_GPE0,);
/* GPE 1: I2SSCLK */
s3c2410_gpio_cfgpin(S3C2410_GPE1,S3C2410_GPE1_I2SSCLK);
s3c2410_gpio_pullup(S3C2410_GPE1,);
/* GPE 2: CDCLK */
s3c2410_gpio_cfgpin(S3C2410_GPE2,S3C2410_GPE2_CDCLK);
s3c2410_gpio_pullup(S3C2410_GPE2,);
/* GPE 4: I2SSDO */
s3c2410_gpio_cfgpin(S3C2410_GPE4,S3C2410_GPE4_I2SSDO);
s3c2410_gpio_pullup(S3C2410_GPE4,); local_irq_restore(flags);
  
init_s3c2410_iis_bus();///* 设置S3C2440的IIS控制器 */ init_uda1341();// /* 使用L3接口初始化uda1341芯片 */     /* 设置两个DMA通道:一个用于播放,另一个用于录音 */
output_stream.dma_ch = DMACH_I2S_OUT;
if (audio_init_dma(&output_stream, "UDA1341 out")) {
audio_clear_dma(&output_stream,&s3c2410iis_dma_out);
}
input_stream.dma_ch = DMACH_I2S_IN;
if (audio_init_dma(&input_stream, "UDA1341 in")) {
audio_clear_dma(&input_stream,&s3c2410iis_dma_in);
}
audio_dev_dsp = register_sound_dsp(&smdk2410_audio_fops, -); //
        -->sound_insert_unit(&chains[], fops, dev, , ,
"dsp", S_IWUSR | S_IRUSR, NULL); audio_dev_mixer = register_sound_mixer(&smdk2410_mixer_fops, -); //
       -->sound_insert_unit(&chains[], fops, dev, , ,
"mixer", S_IRUSR | S_IWUSR, NULL);
......
}
其中, /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.6\sound\Sound_core.c,(也可从该源码看起,再看sound\soc\s3c24xx\s3c2410-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,
};
 int soundcore_open(struct inode *inode, struct file *file)
{
int chain;
int unit = iminor(inode);
struct sound_unit *s;
const struct file_operations *new_fops = NULL; chain=unit&0x0F;
if(chain== || chain==) /* dsp/audio/dsp16 */
{
unit&=0xF0;
unit|=;
chain=;
} spin_lock(&sound_loader_lock);
s = __look_for_unit(chain, unit);
if (s)
new_fops = fops_get(s->unit_fops);
if (!new_fops) {
spin_unlock(&sound_loader_lock);
request_module("sound-slot-%i", unit>>);
request_module("sound-service-%i-%i", unit>>, chain);
spin_lock(&sound_loader_lock);
s = __look_for_unit(chain, unit);
if (s)
new_fops = fops_get(s->unit_fops);
}
if (new_fops) {
int err = ;
const struct file_operations *old_fops = file->f_op;
file->f_op = new_fops;
spin_unlock(&sound_loader_lock);
if(file->f_op->open)
err = file->f_op->open(inode,file);
if (err) {
fops_put(file->f_op);
file->f_op = fops_get(old_fops);
}
fops_put(old_fops);
return err;
}
spin_unlock(&sound_loader_lock);
return -ENODEV;
}

函数框架: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)初始化函数之前,需要根据时序图写操作寄存器函数

 static void wm8976_write_reg(unsigned char reg, unsigned int data)
{
int i;
unsigned long flags;
unsigned short val = (reg << ) | (data & 0x1ff);//16bit, 寄存器地址7位,数据9位 s3c2410_gpio_setpin(S3C2410_GPB2,);//CSB
s3c2410_gpio_setpin(S3C2410_GPB3,);//SDIN
s3c2410_gpio_setpin(S3C2410_GPB4,);//SCLK local_irq_save(flags); for (i = ; i < ; i++){ //先传高位
if (val & (<<)) //
{
s3c2410_gpio_setpin(S3C2410_GPB4,);//时钟低电平
s3c2410_gpio_setpin(S3C2410_GPB3,);//数据线输出1
udelay();
s3c2410_gpio_setpin(S3C2410_GPB4,);//时钟高电平
}
else //
{
s3c2410_gpio_setpin(S3C2410_GPB4,);
s3c2410_gpio_setpin(S3C2410_GPB3,);
udelay();
s3c2410_gpio_setpin(S3C2410_GPB4,);
} val = val << ;
} s3c2410_gpio_setpin(S3C2410_GPB2,);//传输完成后让CSB信号产生低脉冲,写入WM8976
udelay();
s3c2410_gpio_setpin(S3C2410_GPB2,);//恢复高电平
s3c2410_gpio_setpin(S3C2410_GPB3,);
s3c2410_gpio_setpin(S3C2410_GPB4,); local_irq_restore(flags); //结束开中断
}

2)根据wm8976G芯片数据手册(page 87),写初始化函数,初始化寄存器(使能输出声道1,2,混响器等)

芯片手册:

初始化函数:

 static void init_wm8976(void)
{
uda1341_volume = ;
uda1341_boost = ; /* software reset */
wm8976_write_reg(, ); /* 地址3 bit[6-5]: OUT2的左/右声道打开
* 地址2 bit[3-2]: 左/右通道输出混音打开
* 地址1 bit[1-0]: 左/右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)由于wm8976的控制接口和时序图与uda1341不一样,所以需要根据芯片手册修改mixer的file_operations->ioctl函数

 static int smdk2410_mixer_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
int ret;
long val = ; 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(, (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) + ) * ) / ;
wm8976_write_reg(, (<<)|uda1341_volume);
wm8976_write_reg(, (<<)|uda1341_volume);
//uda1341_l3_address(UDA1341_REG_DATA0);
//uda1341_l3_data(uda1341_volume);
break; case SOUND_MIXER_READ_VOLUME:
val = (uda1341_volume * ) / ;
return put_user(val, (long *) arg); case SOUND_MIXER_READ_IGAIN:
val = ((- mixer_igain) * ) / ;
return put_user(val, (int *) arg); case SOUND_MIXER_WRITE_IGAIN:
ret = get_user(val, (int *) arg);
if (ret)
return ret;
mixer_igain = - (val * / );
/* 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 ;
}

2.3 配置和编译内核

1) 确定内核里已经配置了sound\soc\s3c24xx\s3c2410-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

十七、S3C2440 音频解码芯片WM8976声卡驱动移植、madplay测试的更多相关文章

  1. 31.Linux-wm9876声卡驱动(移植+测试)

    本节学习目的 1)分析Linux中的OSS声卡系统 2)移植wm9876声卡 3)使用madplay应用程序播放mp3 1.声音三要素 采样频率 音频采样率是指录音设备在一秒钟内对声音信号的采样次数, ...

  2. AM335x(TQ335x)学习笔记——WM8960声卡驱动移植

    经过一段时间的调试,终于调好了TQ335x的声卡驱动.TQ335x采用的Codec是WM8960,本文来总结下WM8960驱动在AM335x平台上的移植方法.Linux声卡驱动架构有OSS和ALSA两 ...

  3. Linux声卡驱动移植和測试

    一.分析驱动程序,依据开发板改动代码 代码太长,就不贴了,几个注意点: 1. 查看开发板原理图和S3C2410的datasheet,UDA1341的L3MODE.L3DATA.L3CLOCK分别与S3 ...

  4. Linux音频驱动学习之:(2)移植wm8976声卡驱动(linux-3.4.2)

    1.wm8976驱动程序: /* * wm8976.h -- WM8976 Soc Audio driver * * This program is free software; you can re ...

  5. OK6410移植madplay播放器,王明学learn

    对于ok6410的madplay移植主要包括三部分.声卡驱动移植,播放器的移植,以及alsa库的移植. 一.首先移植声卡驱动以及播放器 ok6410采用WM97系列的声卡芯片,要使得内核支持该驱动,首 ...

  6. 瑞昱Realtek(Realtek HD Audio Driver)音频声卡驱动R2.49 for Win7_Vista

    不管是在高端系列主板上,还是在低端系列主板上,我们都能看到Realtek瑞昱的身影,Realtek HD Audio Driver能够支持所有的Realtek HD Audio音频驱动.Realtek ...

  7. SPI在linux3.14.78 FS_S5PC100(Cortex A8)和S3C2440上驱动移植(deep dive)

    由于工作的原因,对SPI的理解最为深刻,也和SPI最有感情了,之前工作都是基于OSEK操作系统上进行实现,也在US/OS3上实现过SPI驱动的实现和测试,但是都是基于基本的寄存器操作,没有一个系统软件 ...

  8. 36、ALSA声卡驱动和应用

    (注意:内核上电的时候会把一些没运行的控制器模块的时钟都关掉,所有在写驱动的时候需要在使用的使用使用clk_get和clk_enable使能时钟) (说明:与ALSA声卡对应的是OSS架构,第二期视频 ...

  9. linux网卡驱动移植

    这里重要的是物理层PHY receiver,MAC(media access control)层,这里与软件中的协议栈不同,在硬件上MAC是PHY的下一层.DM9000A将MAC和PHY做到一起,也可 ...

随机推荐

  1. Pwn with File结构体(一)

    前言 本文由 本人 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274 利用 FILE 结构体进行攻击,在现在的 ctf 比赛中也经常出现 ...

  2. 分布式部署下的报表调用 API调用 权限问题以及性能方案

     背景描述: 客户的实际情况是需要在具体系统构架前,通过与厂商讨论确定最终的系统架构方案. 需求是客户自己有管理系统,希望建立一个独立的报表服务器,该报表服务器可以对多个管理系统提供报表服务,不知 ...

  3. iphone使用linux命令apt-get也没有问题

    那么教程开始: 首先安装cydia这个越了yu就有自带的哦 然后添加源,比如apt.91.我忘了,大家可以在http://frank-dev-blog.club/?post=45找一个 查找termi ...

  4. Pig sample用法举例

    some = sample data 0.1   遍历整个数据集,获取指定比例的行数的数据,获取的数据不确定,条数也不准确.   内部重写为filter data by random() <= ...

  5. 9 tensorflow提示in different while loops的错误该如何解决

    示例代码 ii=tf.constant(0,dtype=tf.int32) loop__cond=lambda a: tf.less(a,sentence_length) loop__vars=[ii ...

  6. PyQt4(简单界面)

    import sys; from PyQt4 import QtCore, QtGui; app=QtGui.QApplication(sys.argv); widget=QtGui.QWidget( ...

  7. 选中复选框,才能在文本框中输东西。button按钮已启用,

  8. MySQL 5.7.24 privileges有哪些?

    root@localhost:3306.sock [mysql]>select version();+------------+| version()  |+------------+| 5.7 ...

  9. Linux bzip2命令详解

    Linux bzip/bunzip2命令是.bz2文件的解压缩程序. bunzip2可解压缩.bz2格式的压缩文件.bunzip2实际上是bzip2的符号连接,执行bunzip2与bzip2 -d的效 ...

  10. centos7.4应用之KVM

    最小安装系统: 参考博客:https://www.cnblogs.com/chenjiahe/p/5911965.html 辅助命令 yum install make bison flex autom ...