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

linux驱动开发: wm8960 codec代码分析相关推荐

  1. 最全Linux驱动开发全流程详细解析(持续更新)

    Linux驱动开发详细解析 一.驱动概念 驱动与底层硬件直接打交道,充当了硬件与应用软件中间的桥梁. 具体任务 读写设备寄存器(实现控制的方式) 完成设备的轮询.中断处理.DMA通信(CPU与外设通信 ...

  2. 【正点原子Linux连载】第四十三章 Linux设备树 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

  3. 嵌入式linux驱动开发之点亮led(驱动编程思想之初体验)

    这节我们就开始开始进行实战啦!这里顺便说一下啊,出来做开发的基础很重要啊,基础不好,迟早是要恶补的.个人深刻觉得像这种嵌入式的开发对C语言和微机接口与原理是非常依赖的,必须要有深厚的基础才能hold的 ...

  4. Linux驱动开发必看详解神秘内核(完全转载)

    Linux驱动开发必看详解神秘内核 完全转载-链接:http://blog.chinaunix.net/uid-21356596-id-1827434.html IT168 技术文档]在开始步入Lin ...

  5. 【嵌入式Linux】嵌入式Linux驱动开发基础知识之LED驱动框架--面向对象、分层设计思想

    文章目录 前言 1.LED驱动程序框架 1.1.对于LED驱动,我们想要什么样的接口? 1.2.LED驱动要怎么写,才能支持多个板子?分层写 1.3.程序分析 驱动程序 应用程序 Makefile 1 ...

  6. 【嵌入式Linux】嵌入式Linux驱动开发基础知识之第一个驱动

    文章目录 前言 1.Hello驱动 1.1.APP打开的文件在内核中如何表示? 1.2.打开字符设备节点时,内核中也有对应的struct file 1.3.如何编写驱动程序? 1.4.驱动程序代码 1 ...

  7. 【正点原子MP157连载】第二十三章 Linux设备树-摘自【正点原子】STM32MP1嵌入式Linux驱动开发指南V1.7

    1)实验平台:正点原子STM32MP157开发板 2)购买链接:https://item.taobao.com/item.htm?&id=629270721801 3)全套实验源码+手册+视频 ...

  8. 字符设备驱动基础-linux驱动开发第2部分-朱有鹏-专题视频课程

    字符设备驱动基础-linux驱动开发第2部分-5673人已学习 课程介绍         本课程是linux驱动开发的第2个课程,从零开始带领大家逐渐熟悉内核模块,并且一步步写出一个字符设备驱动程序来 ...

  9. 【正点原子MP157连载】第二十章 字符设备驱动开发-摘自【正点原子】STM32MP1嵌入式Linux驱动开发指南V1.7

    1)实验平台:正点原子STM32MP157开发板 2)购买链接:https://item.taobao.com/item.htm?&id=629270721801 3)全套实验源码+手册+视频 ...

最新文章

  1. MNIST手写数字识别
  2. C语言结构-演员请就位
  3. 10nm时代,高通霸主地位还能挺多久?
  4. file.php https,使用HTTPS链接和php方法(file_get_contents,getimagesize)
  5. 启动服务器如何删除文件,在服务器启动时用Filter来删除某种类型的文件
  6. html css 表头,css固定表格表头(各浏览器通用)
  7. 【报告分享】2021技术趋势报告-德勤.pdf(附下载链接)
  8. ProtoBuf与JSON
  9. 2016年的第一天上班,顺便开通了博客
  10. springboot 返回输出流_Spring Boot 静态资源处理,妙招
  11. 电子商务概论(农)之形考作业三
  12. 在Win10与Ubuntu双系统中删除Ubuntu
  13. 浅析MultipartResolver
  14. html图形渐变颜色一半一半,CSS3教程:background-image之放射性渐变(radial-gradient)详解...
  15. 浏览器开发工具的秘密
  16. 构建自己的个人信息资料库
  17. 算法学习(动态规划)- 数塔问题
  18. 深度学习笔记(6)BatchNorm批量标准化
  19. 下载yahoo股票历史数据
  20. 当 GUC 参数 enable_dynamic_workload 设置为 on

热门文章

  1. 绝对能看懂的子网划分过程及细节
  2. 为什么很多人说现在做什么生意都不好做?
  3. .site域名总量十强:西部数码、阿里云占80%份额
  4. linux操作系统学习网站整理
  5. ​ Twisted——基于事件驱动的Python网络框架
  6. 【CV现状】 - 图像分割
  7. 阿里P7Android社招面试的经历,最全Android知识总结
  8. 在VB中使用水晶报表总结
  9. Beta阶段团队项目开发篇章1
  10. 200瓦PFC方案 200瓦pfc控制器方案,采用ucc28019a全套图纸