linux驱动 wm8994

2016-12-09 23:54:05 changliang7731 阅读数 4435
  • 2017年5月信息安全工程师真题69-70题

    软考信息安全工程师基础知识选择题75道题,考察的知识面广、内容细致。所以每年都有大量的同学在选择题上面马失前蹄。本次基础知识历年真题讲解,对近年来的每一道真题都进行了详细的讲解,对于一些发散性的考题给...

    56人学习 徐朋
    免费试看

关于alsa架构已经啃了好久好久,但是也卡了好久好久。难说皮毛到底有看懂多少,不管,我们先来啃wm8960 codec的驱动代码:

必要相关函数说明:
////////////////////////////////////////////////////////////////////////////

1.#define SOC_ENUM_SINGLE(xreg, xshift, xmax, xtexts) \
    SOC_ENUM_DOUBLE(xreg, xshift, xshift, xmax, xtexts)

#define SOC_ENUM_DOUBLE(xreg, xshift_l, xshift_r, xmax, xtexts) \
{   .reg = xreg, .shift_l = xshift_l, .shift_r = xshift_r, \
    .max = xmax, .texts = xtexts, \
    .mask = xmax ? roundup_pow_of_two(xmax) - 1 : 0}
ex:
#define WM8960_ALC3     0x13
static const char *wm8960_alcmode[] = {"ALC", "Limiter"};
SOC_ENUM_SINGLE(WM8960_ALC3, 8, 2, wm8960_alcmode)

这个宏定义的作用:reg 0x13的bit8是的功能是select alc mode,
设置0:alc mode 1:limite mode.
1.大概猜测:
SOC_ENUM_SINGLE(xreg, xshift, xmax, xtexts)
Defines an single enumerated control as follows:-

xreg = register
xshift = control bit(s) offset in register
xmask = control bit(s) size
xtexts = pointer to array of strings that describe each setting

函数的作用:
用这个define去填充某个特殊的结构体,从而实现相应的初始化设定:

 /* enumerated kcontrol */
struct soc_enum {
    unsigned short reg;
    unsigned short reg2;
    unsigned char shift_l;
    unsigned char shift_r;
    unsigned int max;
    unsigned int mask;
    const char * const *texts;
    const unsigned int *values;
};

根据理解,我们发现SOC_ENUM_SINGLE()这个宏用来填充soc_enum的结构体,如果将它展开:

SOC_ENUM_SINGLE(WM8960_ALC3, 8, 2, wm8960_alcmode)
--->
    {   
    .reg = WM8960_ALC3, 
    .shift_l = 8, 
    .shift_r = 8,
    .max = 2, 
    .texts = {"ALC", "Limiter"},
    .mask = xmax ? roundup_pow_of_two(xmax) - 1 : 0//mask=?
    }

////////////////////////////////////////////////////////////////////////////

2.#define SOC_ENUM(xname, xenum) \
{   .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname,\
    .info = snd_soc_info_enum_double, \
    .get = snd_soc_get_enum_double, .put = snd_soc_put_enum_double, \
    .private_value = (unsigned long)&xenum }

ex:
static const struct soc_enum wm8960_enum0 = SOC_ENUM_SINGLE(WM8960_ALC3, 8, 2, wm8960_alcmode);

SOC_ENUM(“ALC Function”, wm8960_enum0),
同样,展开后:

--->
    {   
    .iface = SNDRV_CTL_ELEM_IFACE_MIXER, 
    .name = "ADC Polarity",
    .info = snd_soc_info_enum_double, 
    .get = snd_soc_get_enum_double, 
    .put = snd_soc_put_enum_double, 
    .private_value = (unsigned long)&wm8960_enum0, 
    }

所以,我们发现,这个宏实际上还是对某个结构体的填充.用来填充snd_kcontrol_new这个结构体
这边传入的snd_soc_info_enum_double这个函数:

int snd_soc_info_enum_double(struct snd_kcontrol *kcontrol,
    struct snd_ctl_elem_info *uinfo)
{
    struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;

    uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
    uinfo->count = e->shift_l == e->shift_r ? 1 : 2;
    uinfo->value.enumerated.items = e->max;

    if (uinfo->value.enumerated.item > e->max - 1)
        uinfo->value.enumerated.item = e->max - 1;
    strcpy(uinfo->value.enumerated.name,
        e->texts[uinfo->value.enumerated.item]);
    return 0;
}

是为了获得kcontrol->private_value的某些参数

int snd_soc_get_enum_double(struct snd_kcontrol *kcontrol,
    struct snd_ctl_elem_value *ucontrol)
{
    struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
    struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
    unsigned int val;

    val = snd_soc_read(codec, e->reg);
    ucontrol->value.enumerated.item[0]
        = (val >> e->shift_l) & e->mask;
    if (e->shift_l != e->shift_r)
        ucontrol->value.enumerated.item[1] =
            (val >> e->shift_r) & e->mask;

    return 0;
}

是为了读取对应的kcontrol的reg的某些bit的状态

int snd_soc_put_enum_double(struct snd_kcontrol *kcontrol,
    struct snd_ctl_elem_value *ucontrol)
{
    struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
    struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
    unsigned int val;
    unsigned int mask;

    if (ucontrol->value.enumerated.item[0] > e->max - 1)
        return -EINVAL;
    val = ucontrol->value.enumerated.item[0] << e->shift_l;
    mask = e->mask << e->shift_l;
    if (e->shift_l != e->shift_r) {
        if (ucontrol->value.enumerated.item[1] > e->max - 1)
            return -EINVAL;
        val |= ucontrol->value.enumerated.item[1] << e->shift_r;
        mask |= e->mask << e->shift_r;
    }

    return snd_soc_update_bits_locked(codec, e->reg, mask, val);
}

同理,这边是设置对应kcontrol的reg的某些bit的状态.

struct snd_kcontrol_new {
    snd_ctl_elem_iface_t iface; /* interface identifier */
    unsigned int device;        /* device/client number */
    unsigned int subdevice;     /* subdevice (substream) number */
    const unsigned char *name;  /* ASCII name of item */
    unsigned int index;     /* index of item */
    unsigned int access;        /* access rights */
    unsigned int count;     /* count of same elements */
    snd_kcontrol_info_t *info;
    snd_kcontrol_get_t *get;
    snd_kcontrol_put_t *put;
    union {
        snd_kcontrol_tlv_rw_t *c;
        const unsigned int *p;
    } tlv;
    unsigned long private_value;
};

所以,这个宏同样是完成初始化动作,而且我们可以看到,
SOC_ENUM_SINGLE()
SOC_ENUM()—->需要先完成SOC_ENUM_XXX(),当然上面的例子是通过SOC_ENUM_SINGLE()来实现private data的初始化的
////////////////////////////////////////////////////////////////////////////
本质上,所有的宏基本上都是为了实现snd_kcontrol_new的结构体而服务,用来初始化snd_kcontrol_new的宏有:

SOC_ENUM(xname, xenum)
SOC_DAPM_SINGLE(xname, reg, shift, max, invert)
SOC_DOUBLE_R_TLV(xname, reg_left, reg_right, xshift, xmax, xinvert, tlv_array)
SOC_DOUBLE_R(xname, reg_left, reg_right, xshift, xmax, xinvert)
SOC_SINGLE_TLV(xname, reg, shift, max, invert, tlv_array)
SOC_SINGLE(xname, reg, shift, max, invert)
SOC_SINGLE_BOOL_EXT(xname, xdata, xhandler_get, xhandler_put)

//DAPM
SOC_DAPM_SINGLE(xname, reg, shift, max, invert)

所以,我们可以知道,这些宏都是用来初始化snd_kcontrol_new结构体的,每个宏对应某个寄存器的特殊操作,可以实现读写控制。其中DAPM(digital audio power maniger)
也算snd_kcontrol_new的一种实例,snd_kcontrol_new结构是一种很重要的数据结构。它实现了许多寄存器的实例化的操作
////////////////////////////////////////////////////////////////////////////

#define SND_SOC_DAPM_INPUT(wname) \
{   .id = snd_soc_dapm_input, .name = wname, .kcontrol_news = NULL, \
    .num_kcontrols = 0, .reg = SND_SOC_NOPM }
ex: 
SND_SOC_DAPM_INPUT("LINPUT1")   

同样展开

--->
    {   
    .id = snd_soc_dapm_input, 
    .name = "LINPUT1", 
    .kcontrol_news = NULL,
    .num_kcontrols = 0, 
    .reg = SND_SOC_NOPM 
    }
#define SND_SOC_DAPM_MIXER(wname, wreg, wshift, winvert, \
     wcontrols, wncontrols)\
{   .id = snd_soc_dapm_mixer, .name = wname, .reg = wreg, .shift = wshift, \
    .invert = winvert, .kcontrol_news = wcontrols, .num_kcontrols = wncontrols}

ex:
SND_SOC_DAPM_MIXER(“Left Input Mixer”, WM8960_POWER3, 5, 0,
wm8960_lin, ARRAY_SIZE(wm8960_lin)),
展开:

--->
    {   
    .id = snd_soc_dapm_mixer, 
    .name = "Left Input Mixer", 
    .reg = WM8960_POWER3, 
    .shift = 5, 
    .invert = 0, 
    .kcontrol_news = wm8960_lin, 
    .num_kcontrols = ARRAY_SIZE(wm8960_lin)
    }

我们发现这两个宏,同样是实现了对某个结构体的初始化.
只是因宏的不同,填充的结构体的成员变量会有差异.
这个结构体是:

/* dapm widget */
struct snd_soc_dapm_widget {
    enum snd_soc_dapm_type id;
    const char *name;       /* widget name */
    const char *sname;  /* stream name */
    struct snd_soc_codec *codec;
    struct snd_soc_platform *platform;
    struct list_head list;
    struct snd_soc_dapm_context *dapm;

    void *priv;             /* widget specific data */
    struct regulator *regulator;        /* attached regulator */
    const struct snd_soc_pcm_stream *params; /* params for dai links */

    /* dapm control */
    int reg;                /* negative reg = no direct dapm */
    unsigned char shift;            /* bits to shift */
    unsigned int value;             /* widget current value */
    unsigned int mask;          /* non-shifted mask */
    unsigned int on_val;            /* on state value */
    unsigned int off_val;           /* off state value */
    unsigned char power:1;          /* block power status */
    unsigned char invert:1;         /* invert the power bit */
    unsigned char active:1;         /* active stream on DAC, ADC's */
    unsigned char connected:1;      /* connected codec pin */
    unsigned char new:1;            /* cnew complete */
    unsigned char ext:1;            /* has external widgets */
    unsigned char force:1;          /* force state */
    unsigned char ignore_suspend:1;         /* kept enabled over suspend */
    unsigned char new_power:1;      /* power from this run */
    unsigned char power_checked:1;      /* power checked this run */
    int subseq;             /* sort within widget type */

    int (*power_check)(struct snd_soc_dapm_widget *w);

    /* external events */
    unsigned short event_flags;     /* flags to specify event types */
    int (*event)(struct snd_soc_dapm_widget*, struct snd_kcontrol *, int);

    /* kcontrols that relate to this widget */
    int num_kcontrols;
    const struct snd_kcontrol_new *kcontrol_news;
    struct snd_kcontrol **kcontrols;

    /* widget input and outputs */
    struct list_head sources;
    struct list_head sinks;

    /* used during DAPM updates */
    struct list_head power_list;
    struct list_head dirty;
    int inputs;
    int outputs;

    struct clk *clk;
};

首先这个是DAPM相关的东西,大概可以理解为 数位声音电源管理小部件专用的结构体.
这个结构体里面有包含const struct snd_kcontrol_new *kcontrol_news;
也就是说在某些情况下,为了实现DAPM-widget,有时需要先初始化snd_kcontrol_new数据结构.

通常用来实例化DAPM widget的宏有:

SND_SOC_DAPM_INPUT(wname)
SND_SOC_DAPM_SUPPLY(wname, wreg, wshift, winvert, wevent, wflags)
SND_SOC_DAPM_MIXER(wname, wreg, wshift, winvert,wcontrols, wncontrols)
SND_SOC_DAPM_ADC(wname, stname, wreg, wshift, winvert)
SND_SOC_DAPM_DAC(wname, stname, wreg, wshift, winvert)
SND_SOC_DAPM_PGA(wname, wreg, wshift, winvert,wcontrols, wncontrols)
SND_SOC_DAPM_OUTPUT(wname)

DAPM widget的实例化后,我们便可以控制某个小部件的电源模块的开关.前面有说过,因为省电的关系,需要单独增加DAPM widget的电源小部件,以方便独立对某个模块
进行电源控制.
////////////////////////////////////////////////////////////////////////////

/*
 * DAPM audio route definition.
 *
 * Defines an audio route originating at source via control and finishing
 * at sink.
 */
struct snd_soc_dapm_route {
    const char *sink;
    const char *control;
    const char *source;

    /* Note: currently only supported for links where source is a supply */
    int (*connected)(struct snd_soc_dapm_widget *source,
             struct snd_soc_dapm_widget *sink);
};

根据字面理解,这边的作用是实现一条audio路由通路。
将sink端的DAPM和source的DAPM连接起来,这样便可以进行声音的录制/播放
ex:
{ “Left Boost Mixer”, “LINPUT1 Switch”, “LINPUT1” }
意义:将 “Left Boost Mixer”(sink端)的DAPM与 “LINPUT1”(source端)的DAPM进行一次connect. “LINPUT1 Switch”用来提供control的操作函数

当wm8960_probe(struct snd_soc_codec *codec)时,它做的事情:

{
    /*kcontrol: 控件*/
    snd_soc_add_codec_controls(codec, wm8960_snd_controls,
                     ARRAY_SIZE(wm8960_snd_controls));
    /*widgets: 组件*/
    wm8960_add_widgets(codec);
}

在static int wm8960_add_widgets(struct snd_soc_codec *codec)里面

{
    snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets,
                  ARRAY_SIZE(wm8960_dapm_widgets));

    snd_soc_dapm_add_routes(dapm, audio_paths, ARRAY_SIZE(audio_paths));
}

在wm8960_i2c_probe里面
会有:

ret = snd_soc_register_codec(&i2c->dev,
            &soc_codec_dev_wm8960, &wm8960_dai, 1);// dai num=1

大致上可以这样认为:
首先出册IIC驱动,当match到对应设备时,执行iic_probe函数,同时最后再使用snd_soc_register_codec函数注册codec。
当match到对应的设备时,进行它自己的probe函数,此函数会向codec中添加一些控制小模块和DAPM的组件,同时加载DAPM路由表.
整个codec的驱动函数大致流程就是这样

2018-10-12 20:49:12 TSZ0000 阅读数 757
  • 2017年5月信息安全工程师真题69-70题

    软考信息安全工程师基础知识选择题75道题,考察的知识面广、内容细致。所以每年都有大量的同学在选择题上面马失前蹄。本次基础知识历年真题讲解,对近年来的每一道真题都进行了详细的讲解,对于一些发散性的考题给...

    56人学习 徐朋
    免费试看

wm8960

    一款音频编解码芯片,提供A/D,D/A转换,mixer混音器功能,具有立体声功能,带左右声道和喇叭。

    通过IIS传输音频数据,通过IIC控制CODEC芯片。

数据图

     1.串行时钟SCLK,也叫位时钟(BCLK),即对应数字音频的每一位数据,SCLK都有1个脉冲。SCLK的频率=2×采样频率×采样位数。
    2. 帧时钟LRCK,(也称WS),用于切换左右声道的数据。LRCK为“1”表示正在传输的是右声道的数据,为“0”则表示正在传输的是左声道的数据。LRCK的频率等于采样频率。
    3.串行数据SDATA,就是用二进制补码表示的音频数据。
    4.MCLK,称为主时钟,也叫系统时钟(Sys Clock),是采样频率的256倍或384倍。

ALSA 架构

    分三大块:Platform,Codec,Machine。

    Platform:作用是完成音频数据的管理,最终通过CPU的数字音频接口(DAI)通过IIS协议把音频数据传送给Codec进行处理。

    Codec:编解码器,Codec里面包含了I2S接口、D/A、A/D、Mixer、PA(功放),通常包含多种输入(Mic、Line-in、I2S、PCM)和多个输出(耳机、喇叭、听筒,Line-out),Codec和Platform一样,是可重用的部件。

    Machine:为Platform CPU、Codec、输入输出设备提供了一个载体和连接关系。

2017-11-20 19:03:00 weixin_34250709 阅读数 92
  • 2017年5月信息安全工程师真题69-70题

    软考信息安全工程师基础知识选择题75道题,考察的知识面广、内容细致。所以每年都有大量的同学在选择题上面马失前蹄。本次基础知识历年真题讲解,对近年来的每一道真题都进行了详细的讲解,对于一些发散性的考题给...

    56人学习 徐朋
    免费试看

本节学习目的

  • 1)分析Linux中的OSS声卡系统
  • 2)移植wm9876声卡
  • 3)使用madplay应用程序播放mp3

 


1.声音三要素

采样频率

音频采样率是指录音设备在一秒钟内对声音信号的采样次数, 常用的采样率有:

  • 8KHz      - 电话所用采样率, 对于人的说话已经足够清除
  • 22.05KHz - 无线电广播所用采样率
  • 32KHz   -  miniDV 数码视频、DAT所用采样率
  • 44.1KHz - 音频 CD, 也常用于 MPEG-1 音频(VCD, SVCD, MP3)所用采样率
  • 48KHz   - miniDV、数字电视、DVD、DAT、电影和专业音频所用的数字声音所用采样率
  • 50KHz   - 商用数字录音机所用采样率
  • 96K     -   BD-ROM(蓝光盘)音轨、和 HD-DVD (高清晰度 DVD)音轨等所用采样率

而2440开发板的采样频率IISRCK最高可以达到96KHz,满足了很多常用的采样场合,如下图所示:

其中CODECLK便是MCLK

 

量化位数

指每个采样点里传输的数字信号次数,如下图所示, 其中蓝线表示模拟信号,红线表示数字信号,量化位越高,数字信号就越可能接近原始信号,音质越好

 

一般的量化位数为:

  • 8位:  分成 256 次;
  • 16位: 分为65536次, 已到 CD 标准;
  • 32位: 分为 4294967296次,很少用到

2440的开发板只支持8位,16位,如下图所示:

其中LRCK就是采样频率,当LRCK为低时,表示传输的采样数据是左声道,当LRCK为高时,表示传输的采样数据是右声道,每个采样点,SD(serial data)都可以传输8位,或16位数字信号(从低位到高位传输)

 

声道数

常有单声道和立体声之分,(有的也处理成两个喇叭输出同一个声道的声音),而立体声更能感受到空间效果,但数据量翻倍

 

所以,声音的每秒数据量(字节/s)= (采样频率 × 量化位数 × 声道数) / 8;

2. WM9876声卡硬件分析

声卡是负责录音、播音、调节音量和声音合成等的一种多媒体板卡 

本节使用的声卡是2440板上自带的WM9876声卡

 

 

当我们播放声音时 ,将数字信号传入I2SDO脚,声卡便通过解码,产生模拟信号到喇叭/耳机

录音时,声卡便获取麦克风的模拟信号,编码出数字信号到I2SDI引脚上

WM8976接口分为两种:I2S接口(提供音频接收和发送)、控制接口(控制音量大小,使能各个输出通道等)

IIS接口相关的引脚如下    

  • MCLK :    主机为编解码芯片提供的系统同步时钟 ( Master/system clock input ) ,在2440中,一般设置为256fs,或者384fs
  • BCLK:      编解码芯片提供的串行时钟信号 ( Audio bit clock output ) ,也就是量化位深,比如I2SIRCK=44.1khz,量化位为32位,则BCLK=44.1khz*32*2
  • I2SLRCK: 采样频率信号,当为低电平时是采样的是左声道信号,为高电平是右声道信号,常见有44.1Khz(1fs)
  • I2SDI :  ADC数据输入
  • I2SDO :DAC数据输出

如下图所示:

控制接口相关的引脚如下

  • CSB/GPIO1: 3线 控制数据使能引脚
  • SCLK: 3线/2线 时钟引脚
  • SDIN: 3线/2线 数据输入输出引脚
  • MODE: 3线/2线 控制选择,当MODE为高,表示为3线控制,MODE位低,表示2线控制,如下图所示:

                           

其它引脚如下:

  • R/LOUT1:音频左/右输出通道1,外接耳机插孔
  • R/LOUT2:音频左/右输出通道2,未接
  • OUT3:单声道输出通道3,未接
  • OUT4:单声道输出通道4,未接
  • LIP/LIN:音频输入通道,外接麦克风

 

那么3线和2线的控制引脚又有什么区别?

3线控制:

如下图所示,3线控制,每周期都要传输16位数据(7位寄存器地址+9位寄存器数据),传输完成后,给CSB一个上升沿便完成一次数据的传输

 

2线控制:

如下图所示,2线控制就是I2C通信方式

 

本节的WM8976的MODE脚接的高电平,所以是3线控制

 

3.接下来便来分析linux内核的声卡系统

在linux声卡中存在两种声卡系统,一种是OSS(开放声音系统),一种是ALSA(先

进Linux声音架构)。本节系统以OSS(Open Sound System)为例 ,

内核以linux-2.6.22.6版本为例,位于:linux-2.6.22.6\sound\Sound_core.c

3.1首先进入入口函数

如下图所示:

 

入口函数里,只注册了一个主设备号为(SOUND_MAJOR)14的“sound”字符设备和class类,这里为什么没有创建设备节点?

是因为, 当注册声卡系统的驱动后,才会有设备节点,此时这里的代码是没有驱动的,后面会分析到

3.2 再来看看“sound”字符设备的file_perations:

 

这里只有个.open,为什么没有.read,.write函数?

显然在.open函数里做了某些处理,我们进入soundcore_open()来看看

3.2 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; //定义一个新的file_operations
       chain=unit&0x0F;  
       if(chain==4 || chain==5)       /* dsp/audio/dsp16 */
       {
              unit&=0xF0;
              unit|=3;
              chain=3;                             
       }

       spin_lock(&sound_loader_lock);          
       s = __look_for_unit(chain, unit);     //里面通过chains[chain]数组里找到sound_unit结构体
                                             //一个sound_unit对应一个声卡驱动

       if (s)
              new_fops = fops_get(s->unit_fops);    //通过sound_unit,获取对应的file_operations
              ... ...

       if (new_fops) {                                           //当找到file_operations
              int err = 0;
              const struct file_operations *old_fops = file->f_op;//设上次的file_operations等于当前的

              file->f_op = new_fops;                //设置系统的file_operations等于s-> unit_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;
}

通过上面的代码和注释分析到,系统声卡之所以只有一个open(),里面是通过次设备号来调用__look_for_unit()函数,找到chains[chain]数组里的驱动声卡sound_unit结构体,然后来替换系统声卡的file_operations,实现偷天换日的效果。

__look_for_unit()函数如下图所示:

 

其中chains[]数组定义如下所示:

 

其中, chains[0]存放的Mixers,实现调节音量,高音等,就是我们VM8976的控制接口

chains[3]存放的DSP,用来实现音频输入输出,就是我们VM8976的I2S接口

显然VM8976的驱动有2个,需要将2个file_operations放入chains[0]和chains[3]数组里,供给系统的open()来调用

3.3 我们以DSP为例,搜索chains[3]来看看

 

如上图所示,显然register_sound_dsp()函数就是被我们声卡驱动调用的,用来注册dsp设备节点,继续进入sound_insert_unit()函数看看

3.4 sound_insert_unit()函数如下

static int sound_insert_unit(struct sound_unit **list, const struct file_operations *fops, int index, int low, int top, const char *name, umode_t mode, struct device *dev)
{

       struct sound_unit *s = kmalloc(sizeof(*s), GFP_KERNEL);   //分配个新的sound_unit
       int r;
       if (!s)
              return -ENOMEM;
       
       spin_lock(&sound_loader_lock);

// __sound_insert_unit()里主要实现:将分配的新的s插入到chains[3]里,然后并放入fops操作结构体
       r = __sound_insert_unit(s, list, fops, index, low, top); 

       spin_unlock(&sound_loader_lock);
       if (r < 0)
              goto fail;

       else if (r < SOUND_STEP)
              sprintf(s->name, "sound/%s", name);         //s->name="sound/dsp"
       else
              sprintf(s->name, "sound/%s%d", name, r / SOUND_STEP);      

       device_create(sound_class, dev, MKDEV(SOUND_MAJOR, s->unit_minor),s->name+6);      
                         //s->name+6="dsp",也就是在/dev下创建"dsp"的设备节点 return r; fail: kfree(s); return r; }

所以,register_sound_dsp()函数用来创建/dev/dsp 设备节点,同时将dsp相关的file_operations放入chains[3]里面

3.5 同样, Mixers的驱动流程也是这样,它的函数是register_sound_mixer(),如下图所示

 

也是创建/dev/mixer设备节点, 同时将dsp相关的file_operations放入chains[0]里面

 

3.6 接下来,我们便搜索register_sound_dsp()函数,看看被哪些声卡驱动调用

如下图所示,找到一个支持s3c24xx板卡的声卡驱动uda1341

uda1341声卡和WM8976声卡非常相似,音频都是I2S接口,就只有控制部分不一样

uda1341声卡的硬件,如下图所示:

 

它的控制引脚只有3个:

L3MODE:模式引脚,为高表示传输的是数据,为低表示传输的是寄存器地址

L3CLOCK:时钟引脚

L3DATA:   数据输入/输出引脚

控制接口的时序如下所示:

 

和WM8976的控制时序完全不一样,WM8976控制时序如下所示:

 

 

所以接下来,便修改S3c2410-uda1341.c的控制部分,来移植为wm8976驱动

4.移植wm8976驱动

 首先进入uda1341的probe函数

static int s3c2410iis_probe(struct device *dev)
{
       struct platform_device *pdev = to_platform_device(dev);
       struct resource *res;
       unsigned long flags;
       printk ("s3c2410iis_probe...\n");
       /*获取资源*/
       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
       if (res == NULL) {
              printk(KERN_INFO PFX "failed to get memory region resouce\n");
              return -ENOENT;
       }

       iis_base = (void *)S3C24XX_VA_IIS ;
       if (iis_base == 0) {
              printk(KERN_INFO PFX "failed to ioremap() region\n");
              return -EINVAL;
       }

/*获取I2S时钟,并使能*/
       iis_clock = clk_get(dev, "iis");
       if (iis_clock == NULL) {
              printk(KERN_INFO PFX "failed to find clock source\n");
              return -ENOENT;
       }
       clk_enable(iis_clock);

       /*进入临界区, 禁止中断,并保存中断状态*/
       local_irq_save(flags);

       /*设置管脚功能*/
       /* GPB 4: L3CLOCK, OUTPUT */
       s3c2410_gpio_cfgpin(S3C2410_GPB4, S3C2410_GPB4_OUTP);
       s3c2410_gpio_pullup(S3C2410_GPB4,1);
       /* 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,1);
       /* GPE 3: I2SSDI */
       s3c2410_gpio_cfgpin(S3C2410_GPE3,S3C2410_GPE3_I2SSDI);
       s3c2410_gpio_pullup(S3C2410_GPE3,0);
       /* GPE 0: I2SLRCK */
       s3c2410_gpio_cfgpin(S3C2410_GPE0,S3C2410_GPE0_I2SLRCK);
       s3c2410_gpio_pullup(S3C2410_GPE0,0);
       /* GPE 1: I2SSCLK */
       s3c2410_gpio_cfgpin(S3C2410_GPE1,S3C2410_GPE1_I2SSCLK);
       s3c2410_gpio_pullup(S3C2410_GPE1,0);
       /* GPE 2: CDCLK */
       s3c2410_gpio_cfgpin(S3C2410_GPE2,S3C2410_GPE2_CDCLK);
       s3c2410_gpio_pullup(S3C2410_GPE2,0);
       /* GPE 4: I2SSDO */
       s3c2410_gpio_cfgpin(S3C2410_GPE4,S3C2410_GPE4_I2SSDO);
       s3c2410_gpio_pullup(S3C2410_GPE4,0);

       /*退出临界区,使能中断,并恢复之前保存的flags中断状态*/
       local_irq_restore(flags);

       /*设置2440的I2S寄存器*/
       init_s3c2410_iis_bus();

       /*初始化uda1341声卡的控制部分*/
       init_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);
              printk( KERN_WARNING AUDIO_NAME_VERBOSE": unable to get DMA channels\n" );
              return -EBUSY;
       }

    /*设置DMA输入通道,用来接收声音*/
       input_stream.dma_ch = DMACH_I2S_IN;
       if (audio_init_dma(&input_stream, "UDA1341 in")) {
              audio_clear_dma(&input_stream,&s3c2410iis_dma_in);
              printk( KERN_WARNING AUDIO_NAME_VERBOSE": unable to get DMA channels\n" );
              return -EBUSY;
       }
   

  /*创建/dev/dsp,/dev/mixer两个设备节点,
并将smdk2410_audio_fops和smdk2410_mixer_fops 两个file_operations放入chains[0]和chains[3]里,供给内核的声卡系统调用
*/ audio_dev_dsp = register_sound_dsp(&smdk2410_audio_fops, -1); audio_dev_mixer = register_sound_mixer(&smdk2410_mixer_fops, -1); printk(AUDIO_NAME_VERBOSE " initialized\n"); return 0; }

从上面的代码来看, uda1341的管脚和wm8976的管脚连接都是一样的,只有init_uda1341()不一样,里面是初始化uda1341的控制引脚接口,所以需要屏蔽,然后自己来写个init_wm8976()函数

 

4.1写init_wm8976()函数之前需要先写一个寄存器操作函数

参考wm8976芯片手册时序图:

 

所以,代码如下: 

static void wm8976_write_reg(unsigned char reg, unsigned int data)
{
int i;
unsigned long flags;
//对于wm8976来说,数据的高七位表示寄存器地址,低9位表示寄存器的值
unsigned short val = (reg << 9) | (data & 0x1ff);
/*wm8976引脚csb,dat,clk分别对应2440芯片的GPB2,3,4引脚*/ s3c2410_gpio_setpin(S3C2410_GPB2,1); s3c2410_gpio_setpin(S3C2410_GPB3,1); s3c2410_gpio_setpin(S3C2410_GPB4,1); /*退出临界区,使能中断,并恢复之前保存的flags中断状态*/ local_irq_save(flags);
/*把val值写入wm8976,共16位,从高到低传输*/ for (i = 0; i < 16; i++){ if (val & (1<<15)) { s3c2410_gpio_setpin(S3C2410_GPB4,0); s3c2410_gpio_setpin(S3C2410_GPB3,1); udelay(1); s3c2410_gpio_setpin(S3C2410_GPB4,1); } else { s3c2410_gpio_setpin(S3C2410_GPB4,0); s3c2410_gpio_setpin(S3C2410_GPB3,0); udelay(1); s3c2410_gpio_setpin(S3C2410_GPB4,1); } val = val << 1; } //传输完成,需要让csb信号产生低脉冲,写入wm8976 s3c2410_gpio_setpin(S3C2410_GPB2,0); udelay(1); //引脚恢复到高电平状态 s3c2410_gpio_setpin(S3C2410_GPB2,1); s3c2410_gpio_setpin(S3C2410_GPB3,1); s3c2410_gpio_setpin(S3C2410_GPB4,1); local_irq_restore(flags); }

 

4.2.参考wm8976g.pdf第87页,来初始化wm8976,使能输出声道1,2,混响器等

static void init_wm8976(void)
{
       uda1341_volume = 57;         // wm8976的音量默认值,后面会讲到
       uda1341_boost = 0;

       /* software reset */
       wm8976_write_reg(0, 0);

       /* BIT[6-5]:使能音频的输出左右通道2
        * BIT[3]: 使能mixer混音器的输出右通道  
        * BIT[2]: 使能mixer混音器的输出右通道  
        * BIT[1]: 使能DAC传输的右通道
     * BIT[0]: 使能DAC传输的左通道
        */
       wm8976_write_reg(0x3, 0x6f);
     /* BIT[4]: 使能输出麦克风电压
        */
       wm8976_write_reg(0x1, 0x1f);  
       wm8976_write_reg(0x2, 0x185);//ROUT1EN LOUT1EN, inpu PGA enable ,ADC enable
       wm8976_write_reg(0x6, 0x0);//SYSCLK=MCLK 
       wm8976_write_reg(0x4, 0x10); // [4:3]=10:使用I2S接口传输                   
       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
}

wm8976初始化修改完成后,还需要修改音量控制等函数,之前就分析了uda1341的probe函数,里面会注册dsp、mixer设备节点:

/dev/dsp

用来播发和录音,由于uda1341和wm8976都用了I2S接口,所以dsp的file_operations不需要修改,

/dev/mixer

用来控制音量,调低音,高音等,由于wm8976的控制接口不一样,所以需要修改mixer的file_operations->ioctl函数

4.3 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 = 0; 

switch (cmd) {
       case SOUND_MIXER_INFO:             //CASE : 获取声卡的描述信息
{
       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_MIXER_WRITE_VOLUME:                //CASE: 写音量,音量值为0~99
ret = get_user(val, (long *) arg);  //读用户层的数据,并放在val里
            if (ret)
                   return ret;

     uda1341_volume = 63 - (((val & 0xff) + 1) * 63) / 100; //转换为寄存器音量值
     uda1341_l3_address(UDA1341_REG_DATA0); //写入音量的寄存器地址
     uda1341_l3_data(uda1341_volume);          //写入转换后的寄存器值数据          
     break;

case SOUND_MIXER_READ_VOLUME:                   //CASE:  读音量,音量值为0~100
     val = ((63 - uda1341_volume) * 100) / 63;  //将寄存器音量值转换为原始数据
     val |= val << 8;                  
     return put_user(val, (long *) arg);                     //上传音量值

case SOUND_MIXER_READ_IGAIN:                     //CASE: 读(in gain)混音输入增益
     val = ((31- mixer_igain) * 100) / 31;                 
     return put_user(val, (int *) arg);
case SOUND_MIXER_WRITE_IGAIN:     //CASE: 写(in gain)混音输入增益 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; } return 0; }

从上面的代码来看,显然接下还要修改以下几个与控制接口相关的case:

  • case SOUND_MIXER_WRITE_VOLUME:            //写音量
  • case SOUND_MIXER_READ_VOLUME:             //读音量
  • case SOUND_MIXER_READ_IGAIN:                 //读(in gain)混音输入增益
  • case SOUND_MIXER_WRITE_IGAIN:                //写(in gain)混音输入增益

 

4.4修改“case SOUND_MIXER_WRITE_VOLUME:”和“case SOUND_MIXER_READ_VOLUME:”

如下图所示(参考wm8976手册的P86页):

  

其中52,53对应的输出左右通道1的音量,54,55对应的输出左右通道2的音量

而我们耳机位于输出左右通道1,如下图所示,所以我们需要设置52,53的寄存器

 

接下来,便来看看寄存器,如何读写音量

我们以53通道1寄存器为例:

 

如上图所示:

  • bit8:  为1,表示每次写入音量值,即立刻更新音量
  • bit7:  位1,表示通道1的左右声道都静音
  • bit6:       位1,表示通道1的右声道静音
  • bit5~0:   表示音量大小,默认值为57(111001),最大值为63

所以修改的内容如下所示:

case SOUND_MIXER_WRITE_VOLUME:     //音量0~100
    ret = get_user(val, (long *) arg);          //读取应用数据,存到val里
    if (ret)
            return ret;
    uda1341_volume = (((val & 0xff)) * 63) / 100;       //最大值为63,最小值为0

wm8976_write_reg(52, (1<<8)| uda1341_volume);
wm8976_write_reg(53, (1<<8)| uda1341_volume);
    break;


case SOUND_MIXER_READ_VOLUME:
val = (uda1341_volume * 100) / 63;       //最大值为99
     return put_user(val, (long *) arg);

 

4.5修改“case SOUND_MIXER_READ_IGAIN:”和“case SOUND_MIXER_WRITE_IGAIN:”

参考wm8976手册的P86页,如下图所示:

 

其中50,51对应的就是左右混音控制寄存器

我们以50左声道混音寄存器为例:

  

如上图所示:

bit8~6: 混音输入增益,默认值为0,最大值为7

所以修改的内容如下所示:

1)首先修改混音输入增益的初始默认值为0,如下图所示

 

2)修改“case SOUND_MIXER_READ_IGAIN:”和“case SOUND_MIXER_WRITE_IGAIN:”

case SOUND_MIXER_READ_IGAIN:   //混音输入:0~100

val = (mixer_igain* 100) / 7;
return put_user(val, (int *) arg);

 

case SOUND_MIXER_WRITE_IGAIN:
ret = get_user(val, (int *) arg);
if (ret)
return ret;

mixer_igain = val * 7 / 100;
/* use mixer gain channel 1*/
wm8976_write_reg(50, mixer_igain<<6);
wm8976_write_reg(51, mixer_igain<<6);
break;

 

5.配置,修改内核文件

5.1 make menuconfig 配置内核

-> Device Drivers

  -> Sound

    -> Advanced Linux Sound Architecture  // 兼容OSS

      -> Advanced Linux Sound Architecture

        -> System on Chip audio support

        <*> I2S of the Samsung S3C24XX chips              //*:将/linux-2.6.22.6/sound/soc/s3c24xx下的makefile指定的文件加入内核里

5.2 将修改好的s3c-wm8976.c放入/linux-2.6.22.6/sound/soc/s3c24xx目录下

5.3修改该目录下的makefile

obj-y += s3c2410-uda1341.o

改为:

obj-y += s3c-wm8976.o  

5.4 make uImage,如下图所示,可以看到内核已经被编译

 

最后下载并启动内核,如下图所示,可以看到该两个设备节点

 

6.测试与运行

6.1使用wav测试声卡

wav是属于一个未经压缩的音频文件,所以可以直接调用给我们声卡播放

播放:

     cat Windows.wav > /dev/dsp

录音(还需要修改下驱动才行):

   cat /dev/dsp > sound.bin 

   //然后对着麦克风说话

   ctrl+c    //退出

   cat sound.bin > /dev/dsp  // 就可以听到录下的声音

 

6.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播放器的源码

2)先创建安装目录mkdir tmp

3)接下来先安装2个库(./configure使用参考: http://www.cnblogs.com/lifexy/p/7866453.html )

cd   libid3tag-0.15.1b/ libmad-0.15.1b                          
./configure  --host=arm-linux --prefix=/app/tmp     //修改configure,设置编译器,设置安装路径
make               //编译
make install      //安装到/app/tmp目录下

4)最后安装madplay-0.15.2b

cd madplay-0.15.2b
./configure --host=arm-linux --prefix=/app/tmp  CFLAGS="-I/app/tmp/include" LDFLAGS="-L/app/tmp/lib"                
                                     // CFLAGS:指定头文件, LDFLAGS:指定库文件 make make install

 

5)把/app/tmp/bin下的所有文件复制到开发板nfs的bin目录下,

cd /app/tmp/bin /
cp  * /work/nfs_root/bin

6)把/app/tmp/lib下的带so文件 复制到开发板nfs的lib目录里:

cd /app/tmp/lib/
cp  *so* /work/nfs_root/lib/  -d              //带链接复制

 

7)使用madplay播放mp3

  ./madplay 1.mp3 2.mp3 3.mp3         //循环播放3首歌

并可以使用热键来控制,常用的有以下几种:

  • f    下一首
  • b   下一首
  • i    获取播放时间和播放歌曲名
  • p   播放暂停
  • s   停止
  • +  音量加
  • -   音量减

 

 

2015-07-18 18:58:39 u010177751 阅读数 7258
  • 2017年5月信息安全工程师真题69-70题

    软考信息安全工程师基础知识选择题75道题,考察的知识面广、内容细致。所以每年都有大量的同学在选择题上面马失前蹄。本次基础知识历年真题讲解,对近年来的每一道真题都进行了详细的讲解,对于一些发散性的考题给...

    56人学习 徐朋
    免费试看
I.mx6s上移植wm8960驱动
  此篇博文只记录移植的步骤,其他不做分析。首先上一张wm8960的硬件连接图:







































1  上电操作
   配置wm8960的上电脚,文件位置:arch/arm/mach-mx6/board-mx6q_sabresd.c

         #define SABRESD_CODEC_PWR_EN    IMX_GPIO_NR(7, 12)
 440     /* Enable wm8960 power supply */
 441     gpio_request(SABRESD_CODEC_PWR_EN, "audio-power");
 442     gpio_direction_output(SABRESD_CODEC_PWR_EN, 1);
 443     msleep(1);
 444     gpio_set_value(SABRESD_CODEC_PWR_EN, 1);
 445     printk("Power up wm8960 successful %s\n", __FUNCTION__);

另外,根据原理图可知上电脚为GPIO17,所以相关配置头文件里需将其配置为gpio口,文件位置:arch/arm/mach-mx6/board-mx6dl_sabresd.h

236     /* CODEC_PWR_EN */
237     MX6DL_PAD_GPIO_17__GPIO_7_12,

2:配置I2C,用于client的生成,文件位置:arch/arm/mach-mx6/board-mx6q_sabresd.c 需确认你的i2c是接的哪个控制器。

 805 static struct i2c_board_info mxc_i2c0_board_info[] __initdata = {
 806     {
 807         I2C_BOARD_INFO("wm8960", 0x1a),
 808     },
};

3: 修改wm8960  codec相关的数据结构,此处根据wm8962修改而来,文件位置:arch/arm/mach-mx6/board-mx6q_sabresd.c
以下是修改的地方

 57 #include <sound/wm8962.h>
 58 #include <sound/wm8960.h>

        ...省略部分内容...

 410 static struct platform_device mx6_sabresd_audio_wm8960_device = {
 411     .name = "imx-wm8960",
 412 };
 413 
 414 static struct mxc_audio_platform_data wm8960_data;
 415 
 416 static int wm8960_clk_enable(int enable)
 417 {
 418     if (enable) {
 419         clk_enable(clko);
 420         printk("%s:wm clk enable\n", __FUNCTION__);
 421     }
 422     else {
 423         clk_disable(clko);
 424         printk("%s:wm clk disable\n", __FUNCTION__);
 425     }
 426     return 0;
 427 }
 428
 429 static int mxc_wm8960_init(void)
 430 {
 431     int rate;
 432 
 433     clko = clk_get(NULL, "clko_clk");
 434     if (IS_ERR(clko)) {
 435         pr_err("can't get CLKO clock.\n");
 436         return PTR_ERR(clko);
 437     }
 438     /* both audio codec and comera use CLKO clk*/
 439     rate = clk_round_rate(clko, 24000000);
 440     clk_set_rate(clko, rate);
 441 
 442     wm8960_data.sysclk = rate;
 443 
 444     /* Enable wm8960 power supply */
 445     gpio_request(SABRESD_CODEC_PWR_EN, "audio-power");
 446     gpio_direction_output(SABRESD_CODEC_PWR_EN, 1);
 447     msleep(1);
 448     gpio_set_value(SABRESD_CODEC_PWR_EN, 1);
 449     printk("%s:Power up wm8960 successful\n", __FUNCTION__);
 450 
 451     return 0;
 452 }
 453 
 454 /* Note: we use struct wm8962_pdata for wm8960_config_data */
 455 /* struct wm8962_pdata defind at linux/wm8962.h */
 456 static struct wm8962_pdata wm8960_config_data = {
 457     .gpio_init = {
 458         [2] = WM8960_GPIO_FN_DMICCLK,
 459         [4] = 0x8000 | WM8960_GPIO_FN_DMICDAT,
 460     },
 461     .clock_enable = wm8960_clk_enable,
 462 };
 463 
 464 static struct mxc_audio_platform_data wm8960_data = {
 465     .ssi_num = 1,
 466     .src_port = 2,
 467     .ext_port = 3,
 468     .hp_gpio = -1,                  //SABRESD_HEADPHONE_DET,
 469 //  .hp_active_low = 1,
 470     .mic_gpio = -1,                 //SABRESD_MICROPHONE_DET,
 471 //  .mic_active_low = 1,
 472     .init = mxc_wm8960_init,
 473     .clock_enable = wm8960_clk_enable,
 474 };
 475 
 476 static struct regulator_consumer_supply sabresd_vwm8960_consumers[] = {
 477     REGULATOR_SUPPLY("SPKVDD1", "0-001a"),
 478     REGULATOR_SUPPLY("SPKVDD2", "0-001a"),
 479 };
 480 
 481 static struct regulator_init_data sabresd_vwm8960_init = {
 482     .constraints = {
 483         .name = "SPKVDD",
 484         .valid_ops_mask =  REGULATOR_CHANGE_STATUS,
 485         .boot_on = 1,
 486     },
 487     .num_consumer_supplies = ARRAY_SIZE(sabresd_vwm8960_consumers),
 488     .consumer_supplies = sabresd_vwm8960_consumers,
 489 };
 490 
 491 static struct fixed_voltage_config sabresd_vwm8960_reg_config = {
 492     .supply_name    = "SPKVDD",
 493     .microvolts     = 4200000,
 494     .gpio           = SABRESD_CODEC_PWR_EN,
 495     .enable_high    = 1,
 496     .enabled_at_boot = 1,
 497     .init_data      = &sabresd_vwm8960_init,
 498 };
 499 
 500 static struct platform_device sabresd_vwm8960_reg_devices = {
 501     .name   = "reg-fixed-voltage",
 502     .id     = 4,
 503     .dev    = {
 504         .platform_data = &sabresd_vwm8960_reg_config,
 505     },
 506 };
        ...省略部分内容...

 811 static struct i2c_board_info mxc_i2c0_board_info[] __initdata = {
 812     {
 813         I2C_BOARD_INFO("wm8960", 0x1a),
 814     },
 815     {
 816         I2C_BOARD_INFO("ov564x", 0x3c),
 817         .platform_data = (void *)&camera_data,
 818     },
 819     {
 820         I2C_BOARD_INFO("mma8451", 0x1c),
 821         .platform_data = (void *)&mma8451_position,
 822     },
 823 };
 824 
         ...省略部分内容...

1535 static int __init imx6q_init_audio(void)
1536 {
1537     if (board_is_mx6_reva()) {
1538         mxc_register_device(&mx6_sabresd_audio_wm8958_device,
1539                     &wm8958_data);
1540         imx6q_add_imx_ssi(1, &mx6_sabresd_ssi_pdata);
1541 
1542         mxc_wm8958_init();
1543     } else {
1544         platform_device_register(&sabresd_vwm8960_reg_devices);
1545         mxc_register_device(&mx6_sabresd_audio_wm8960_device,
1546                     &wm8960_data);
1547         imx6q_add_imx_ssi(1, &mx6_sabresd_ssi_pdata);
1548 
1549         mxc_wm8960_init();
1550         printk("%s\n", __FUNCTION__);
1551     }
1552 
1553     return 0;
1554 }

         ...省略部分内容...

1859     if (board_is_mx6_reva()) {
1860         strcpy(mxc_i2c0_board_info[0].type, "wm8958");
1861         mxc_i2c0_board_info[0].platform_data = &wm8958_config_data;
1862     } else {
1863         strcpy(mxc_i2c0_board_info[0].type, "wm8960");
1864         mxc_i2c0_board_info[0].platform_data = &wm8960_config_data;
1865     }

4 修改wm8960.h:include/sound/wm8960.h

 9 #ifndef _WM8960_PDATA_H
 10 #define _WM8960_PDATA_H
 11 
 12 #define WM8960_DRES_400R 0
 13 #define WM8960_DRES_200R 1
 14 #define WM8960_DRES_600R 2
 15 #define WM8960_DRES_150R 3
 16 #define WM8960_MAX_GPIO  6
 17 
 18 #define WM8960_GPIO_FN_DMICCLK  19
 19 #define WM8960_GPIO_FN_DMICDAT  20
 20 
 21 
 22 struct wm8960_data {
 23     bool capless;  /* Headphone outputs configured in capless mode */
 24     
 25     int dres;  /* Discharge resistance for headphone outputs */
 26   
 27     int gpio_base;
 28     u32 gpio_init[WM8960_MAX_GPIO];
 29   
 30     u32 mic_cfg;
 31     bool irq_active_low;
 32     bool spk_mono;
 33 };
 34 
 35 #endif

5 拷贝imx-wm8960.c到sound/soc/imx/目录下(获取地址:https://github.com/PDi-Communication-Systems-Inc/kernel-imx/blob/6bfe025386e4419a50b1b1d5a847a1329d1745cd/sound/soc/imx/imx-wm8960.c)

修改同目录下Kconfig,添加如下:
 80 config SND_SOC_IMX_WM8960
 81     tristate "SoC Audio support for IMX boards with WM8960"
 82     select SND_MXC_SOC_MX2
 83     select SND_SOC_WM8960
 84     help
 85       Say Y if you want to add support for SoC audio on an i.MX board with
 86       a WM8960 codec.

修改同目录下Makefile:

18 snd-soc-imx-wm8960-objs := imx-wm8960.o
30 obj-$(CONFIG_SND_SOC_IMX_WM8960) += snd-soc-imx-wm8960.o

用make menuconfig配置上wm8960,编译下载至开发板即可进行验证。

 Ps :一些调试命令及输出log:
root@imx6solo ~$ cat /proc/asound/cards 
 0 [wm8960audio    ]: wm8960-audio - wm8960-audio
                      wm8960-audio


还可以用  ls /dev/snd 看是否有相关节点,如有,则声卡驱动大致ok。下面接着介绍测试的先关方法

移植alsa-lib

 从官网(http://www.alsa-project.org/main/index.php/Download)下载 alsa-lib-1.0.29.tar.bz2包,
 6.1解压执行:
 $ cd alsa-lib-1.0.29
 $./configure --host=arm-none-linux-gnueabi --prefix=/home/***/alsa/alsa-lib
6.2 编译:
$ make

6.3 安装(需要root权限)
sudo make install

7 移植alsa-utils
从官网(http://www.alsa-project.org/main/index.php/Download)下载 alsa-utils-1.0.29.tar.bz2包,
7.1解压执行:
./configure --host=arm-none-linux-gnueabi --prefix=/home/**/alsa/alsa-utils --with-alsa-inc-prefix=/home/**/alsa/alsa-lib/include --with-alsa-prefix=/home/**/alsa/alsa-lib/lib --disable-alsamixer



注意上面最后的参数:--disable-alsamixer 若不加此参数编译会报错:configure error required courses helper header not found 具体原因不清楚!!

7.2 编译
make

7.3安装(需要root权限)
sudo make install

8 开发板配置:
8.1 库的拷贝:
sudo cp alsa-lib/lib/libasound.* /home/**/rootfs/lib/

8.2  alsa-utils拷贝
将alsa-utils/bin目录小的内容拷贝到目标板根文件系统中的bin下
sudo cp alsa-utils/bin/* /home/**/rootfs/bin

8.3 alsa的配置文件拷贝
 除了库之外alsa的配置文件也需要拷贝到目标板根文件系统中,这里需要注意的是share目录在目标板的存放位置必须和在主机的存放路径一致!!
sudo cp -R **/alsa/alsa-lib/share/ /home/**/rootfs/**/alsa/alsa-lib/
重新挂载文件系统到目标板,通过 aplay  -l可以查看我们的音频设备。
root@imx6solo ~$ aplay -L
null
    Discard all samples (playback) or generate zero samples (capture)
root@imx6solo ~$ aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: wm8960audio [wm8960-audio], device 0: HiFi wm8960-hifi-0 []
  Subdevices: 1/1
  Subdevice #0: subdevice #0

播放声音文件:
aplay **.wav

最开始一直没有声音,后来发现是wm8960没有初始化的原因,所以要对其进行初始化:

初始化步骤:

第1步 设置power1 2 3;
第2步 设置时钟;
第3步 设置ADC-DAC,注意设置非静音;
第4步 设置audio interface;
第5步 设置volume;
第6步 设置mixer;

参考如下代码(init at imx-wm8960.c:imx_hifi_hw_params function):
void wm8960_init(struct snd_soc_dai *codec_dai)
{
struct snd_soc_codec *codec = codec_dai->codec;

snd_soc_write(codec, 0x19, 0xc0); /* power1, ok*/
snd_soc_write(codec, 0x1a, 0x199); /* power2, ok*/

snd_soc_write(codec, 0x31, 0xf7); /* classd1 : enable L&R, ok */
snd_soc_write(codec, 0x33, 0x11b); /* classd3 : volume max, ok */

snd_soc_write(codec, 0x28, 0x179); /* ok */
snd_soc_write(codec, 0x29, 0x179); /* ok */

snd_soc_write(codec, 0x22, 0x100); /* dac to mixer, ok */
snd_soc_write(codec, 0x25, 0x100); /* dac to mixer, ok */

snd_soc_write(codec, 0x2f, 0x0c); /* left & right output mixer enable, ok */
snd_soc_write(codec, 0x05, 0x00); /* ok */
}


2014-08-29 14:28:34 cluzax 阅读数 15351
  • 2017年5月信息安全工程师真题69-70题

    软考信息安全工程师基础知识选择题75道题,考察的知识面广、内容细致。所以每年都有大量的同学在选择题上面马失前蹄。本次基础知识历年真题讲解,对近年来的每一道真题都进行了详细的讲解,对于一些发散性的考题给...

    56人学习 徐朋
    免费试看

假期前一天,和同事同买的无线网卡到货了,查了一下没有linux驱动,直接崩溃掉;当天晚上查到了这款无线网卡芯片是realtek 8188eu或者8192us,但是不能确定。把无线网卡在windows7系统中驱动起来,看到芯片是8188eu,之后看了N多教程终于加载成功了。

测试在kali和backtrack上面都安装成功了。

首先下载Linux版本驱动 8188eu_USB_linux.zip

下载成功以后把driver文件夹的zip文件放Linux里面去,用unzip命令解压。

解压好以后更新内核
apt-get install build-essential
sudo apt-get install linux-headers-$(uname -r)
 编译的时候还的修改下那个makefile 文件

CONFIG_PLATFORM_I386_PC =n n改成y
CONFIG_PLATFORM_BCM2708 = y 改成n
然后 是这一段里的 INSTALL_PREFIX := 改成 INSTALL_PREFIX := 8188eu
ifeq ($(CONFIG_PLATFORM_I386_PC), y)
EXTRA_CFLAGS += -DCONFIG_LITTLE_ENDIAN
SUBARCH := $(shell uname -m | sed -e s/i.86/i386/)
ARCH ?= $(SUBARCH)
CROSS_COMPILE ?=
KVER := $(shell uname -r)
KSRC := /lib/modules/$(KVER)/build
MODDESTDIR := /lib/modules/$(KVER)/kernel/drivers/net/wireless/
INSTALL_PREFIX := 8188eu
endif
好了这样编译就通过来。

然后make && make install
sudo echo "8188eu" >> /etc/modules
接着重新启动系统 网卡就可以用了。
重启以后可以使用dmesg|grep 8188查看加载信息。

点击查看原图

上一张在bt5r3上安装无线驱动测试成功的图

点击查看原图

wm8962驱动梳理一

阅读数 262