zoukankan      html  css  js  c++  java
  • 十七、S3C2440 音频解码芯片WM8976声卡驱动移植、madplay测试

    学习目标:1. WM9876接口和工作原理;2. WM9876驱动移植;3. WM9876应用测试;4. 问题总结

    1. WM9876接口和工作原理

     本节使用了JZ2440开发板移植WM9876驱动,其结构如下图所示,最后利用madplay工具测试音频文件。

    17-1

    IIS和控制接口;

    WM9876声卡是负责录音、播音、调节音量和声音合成等的一种多媒体板卡。包括两种接口:IIS接口(提供音频接收和发送)、控制接口(控制音量大小,使能各个输出通道等)

    17-2

    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

      

  • 相关阅读:
    Android二维码扫描功能的集成开发
    【IMOOC学习笔记】多种多样的App主界面Tab实现方法(四)
    【IMOOC学习笔记】多种多样的App主界面Tab实现方法(三)
    【IMOOC学习笔记】多种多样的App主界面Tab实现方法(二)
    【IMOOC学习笔记】多种多样的App主界面Tab实现方法(一)
    【第一行代码笔记】(一)
    Android viewpager切换到最后一页时,跳转至其他activity
    scrapy爬取天气数据
    nodejs压缩
    Express文件上传
  • 原文地址:https://www.cnblogs.com/lxl-lennie/p/10297120.html
Copyright © 2011-2022 走看看