linux驱动开发: wm8960 codec代码分析

转载地址:https://blog.csdn.net/changliang7731/article/details/53548165

关于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的驱动函数大致流程就是这样

阅读更多
个人分类: kernel-driver-alsa
上一篇wm8960驱动:裸机代码研读
下一篇WM8960驱动的移植记录
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭