一、应用测试工具的使用
1.在external/tinyalsa下有以C语言实现的alsa的测试程序,编译后生成tinypcminfo tinyplay tinycap tinymix 四个elf格式的测试工具
(1) tinypcminfo :获取PCM In和PCM
# tinypcminfo -D /dev/snd/controlC0
# tinypcminfo -D /dev/snd/pcmC0D0p Info for card 0, device 0:PCM out: Access: 0x000009 Format[0]: 0x000044 Format[1]: 00000000 Format Name: S16_LE, S24_LE Subformat: 0x000001 Rate: min=8000Hz max=48000Hz Channels: min=2 max=2 Sample bits: min=16 max=32 Period size: min=512 max=8192Period count: min=2 max=64PCM in: Access: 0x000009 Format[0]: 0x000044 Format[1]: 00000000 Format Name: S16_LE, S24_LE Subformat: 0x000001 Rate: min=8000Hz max=48000Hz Channels: min=1 max=2 Sample bits: min=16 max=32 Period size: min=512 max=16384Period count: min=2 max=128View Code
(2) tinymix :通过/dev/snd/controlC0节点设置获取控制信息,进行控件的设置。比如设置链路,音量调节等。
# tinymixMixer name: ‘S3C2440_UDA1341‘Number of controls: 50ctl type num name value0 INT 2 Capture Volume 51 511 INT 2 Capture Volume ZC Switch 0 02 BOOL 2 Capture Switch Off On3 INT 2 Playback Volume 255 2554 INT 2 Headphone Playback Volume 121 1215 BOOL 2 Headphone Playback ZC Switch Off Off6 INT 2 Speaker Playback Volume 121 1217 BOOL 2 Speaker Playback ZC Switch Off Off....View Code
# tinymix 打印出所有的snd_kcontrol项可以通过名字操作:# ./tinymix "Capture Volume" //单获取这项的值 Capture Volume: 51 51 (range 0->63)# ./tinymix "Capture Volume" 10 //单设置这项的值为10 # ./tinymix "Capture Volume" Capture Volume: 10 10 (range 0->63)也可以通过序号操作:# ./tinymix 0 Capture Volume: 10 10 (range 0->63)# ./tinymix 0 20 # ./tinymix 0 Capture Volume: 20 20 (range 0->63)
驱动中对应的file_operations是:struct file_operations snd_ctl_f_ops
(3) tinycap : 使用/dev/snd/pcmC0D0c录音
# tinycap a.wav
const struct file_operations snd_pcm_f_ops[1]
(4) tinyplay : 使用/dev/snd/pcmC0D0p播放声音
# tinyplay a.wav
const struct file_operations snd_pcm_f_ops[0]
二、内核导出信息
1.devtmpfs信息(设备节点)
[email protected]:/system # ls /dev/snd/ -l total 0crw-rw---- 1 1000 1005 116, 0 Jan 1 12:00 controlC0crw-rw---- 1 1000 1005 116, 24 Jan 1 12:00 pcmC0D0ccrw-rw---- 1 1000 1005 116, 16 Jan 1 12:00 pcmC0D0pcrw-rw---- 1 1000 1005 116, 25 Jan 1 12:00 pcmC0D1ccrw-rw---- 1 1000 1005 116, 17 Jan 1 12:00 pcmC0D1pcrw-rw---- 1 1000 1005 116, 33 Jan 1 12:00 timer
controlC0: 起控制作用,C0表示Card0
pcmC0D0c: Card 0,Device 0 capture,用来录音。
pcmC0D0p: Card 0,Device 0 playback,用来录音。
pcmC0D1c: Card 0,Device 1 capture,用来录音。
pcmC0D1p: Card 0,Device 1 playback,用来录音。
timer: 很少使用,暂时不用管。
pcmC0D1c/pcmC0D1p是一个辅助的备份的音频设备,先不管。
ALSA框架中一个声卡可以有多个逻辑Device,上面的pcmC0D0X和pcmC0D1X就是两个逻辑设备,一个Device又有播放、录音通道。
2.procfs文件信息
[email protected]:/system # ls /proc/asound/ card0 cards devices hwdep pcm timers tiny4412 version
3.sysfs文件信息
/sys/class/sound/有声卡的id,number,pcm_class等信息
4.debugfs文件信息
/sys/kernel/debug/asoc/导出信息包括:① Codec各个组件的dapm信息TINY4412_UDA8960/wm8960-codec.0-001a/dapm下的[email protected]:/sys/kernel/debug/asoc/TINY4412_UDA8960/wm8960-codec.0-001a/dapm # lsHP_L Left Speaker Output Right Boost MixerHP_R Left Speaker PGA Right DACLINPUT1 MICB Right Input MixerLINPUT2 Mic Onboard Right Output MixerLINPUT3 Mono Output Mixer Right Speaker OutputLOUT1 PGA OUT3 Right Speaker PGALeft ADC RINPUT1 SPK_LNLeft Boost Mixer RINPUT2 SPK_LPLeft DAC RINPUT3 SPK_RNLeft Input Mixer ROUT1 PGA SPK_RPLeft Output Mixer Right ADC bias_level② Codec的寄存器信息[email protected]:/sys/kernel/debug/asoc/TINY4412_UDA8960/wm8960-codec.0-001a # lscache_only cache_sync codec_reg③ 为消除pop音的延时时间/sys/kernel/debug/asoc/TINY4412_UDA8960# lsdapm_pop_time④ dapm的bias_level/sys/kernel/debug/asoc/TINY4412_UDA8960/dapm # cat bias_level Off⑤ 系统中所有注册的Codec,dais和Platform驱动的名字/sys/kernel/debug/asoc # lsS3C2440_UDA1341 codecs dais platforms
三、驱动实战
驱动分Platform驱动,Codec驱动和Machine驱动。我们对于4412开发板主要任务就是实现Machine驱动的平台设备端,移植调试Codec驱动wm8960.c。
Platform驱动Soc厂商已经实现好了是:sound/soc/samsung/dma.c
Soc端的dai驱动Soc厂商已经实现好了是:sound/soc/samsung/i2s.c
Machine平台设备驱动驱动端sound子系统已经实现好了,是sound/soc/soc-core.c
Codec驱动和Codec端的dai驱动都在Codec驱动中实现。
(1) 调试好的Codec驱动wm8960.c,
/* * wm8960.c -- WM8960 ALSA SoC Audio driver * * Author: Liam Girdwood * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */#include <linux/module.h>#include <linux/moduleparam.h>#include <linux/init.h>#include <linux/delay.h>#include <linux/pm.h>#include <linux/i2c.h>#include <linux/platform_device.h>#include <linux/slab.h>#include <sound/core.h>#include <sound/pcm.h>#include <sound/pcm_params.h>#include <sound/soc.h>#include <sound/initval.h>#include <sound/tlv.h>#include <sound/wm8960.h>#include "wm8960.h"/* R25 - Power 1 */#define WM8960_VMID_MASK 0x180#define WM8960_VREF 0x40/* R26 - Power 2 */#define WM8960_PWR2_LOUT1 0x40#define WM8960_PWR2_ROUT1 0x20#define WM8960_PWR2_OUT3 0x02/* R28 - Anti-pop 1 */#define WM8960_POBCTRL 0x80#define WM8960_BUFDCOPEN 0x10#define WM8960_BUFIOEN 0x08#define WM8960_SOFT_ST 0x04#define WM8960_HPSTBY 0x01/* R29 - Anti-pop 2 */#define WM8960_DISOP 0x40#define WM8960_DRES_MASK 0x30/* * wm8960 register cache * We can‘t read the WM8960 register space when we are * using 2 wire for device control, so we cache them instead. */static const u16 wm8960_reg[WM8960_CACHEREGNUM] = { 0x0097, 0x0097, 0x0000, 0x0000, 0x0000, 0x0008, 0x0000, 0x000a, 0x01c0, 0x0000, 0x00ff, 0x00ff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x007b, 0x0100, 0x0032, 0x0000, 0x00c3, 0x00c3, 0x01c0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0100, 0x0100, 0x0050, 0x0050, 0x0050, 0x0050, 0x0000, 0x0000, 0x0000, 0x0000, 0x0040, 0x0000, 0x0000, 0x0050, 0x0050, 0x0000, 0x0002, 0x0037, 0x004d, 0x0080, 0x0008, 0x0031, 0x0026, 0x00e9,};struct wm8960_priv { enum snd_soc_control_type control_type; void *control_data; int (*set_bias_level)(struct snd_soc_codec *, enum snd_soc_bias_level level); struct snd_soc_dapm_widget *lout1; struct snd_soc_dapm_widget *rout1; struct snd_soc_dapm_widget *out3; bool deemph; int playback_fs;};#define wm8960_reset(c) snd_soc_write(c, WM8960_RESET, 0)/* enumerated controls */ /*极性*/static const char *wm8960_polarity[] = { "No Inversion", "Left Inverted", "Right Inverted", "Stereo Inversion"}; /*立体声反转*/static const char *wm8960_3d_upper_cutoff[] = {"High", "Low"};static const char *wm8960_3d_lower_cutoff[] = {"Low", "High"};static const char *wm8960_alcfunc[] = {"Off", "Right", "Left", "Stereo"};static const char *wm8960_alcmode[] = {"ALC", "Limiter"};/*#define SOC_ENUM_SINGLE(xreg, xshift, xmax, xtexts){ .reg = xreg, .shift_l = xshift, .shift_r = xshift, .max = xmax, .texts = xtexts}*//*soc_mixer_control来描述mixer控件的寄存器信息soc_enum 来描述mux控件的寄存器信息*/static const struct soc_enum wm8960_enum[] = { SOC_ENUM_SINGLE(WM8960_DACCTL1, 5, 4, wm8960_polarity), SOC_ENUM_SINGLE(WM8960_DACCTL2, 5, 4, wm8960_polarity), SOC_ENUM_SINGLE(WM8960_3D, 6, 2, wm8960_3d_upper_cutoff), SOC_ENUM_SINGLE(WM8960_3D, 5, 2, wm8960_3d_lower_cutoff), SOC_ENUM_SINGLE(WM8960_ALC1, 7, 4, wm8960_alcfunc), SOC_ENUM_SINGLE(WM8960_ALC3, 8, 2, wm8960_alcmode),};/*采样率设置*/static const int deemph_settings[] = { 0, 32000, 44100, 48000 };static int wm8960_set_deemph(struct snd_soc_codec *codec){ struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); int val, i, best; /* If we‘re using deemphasis select the nearest available sample * rate. */ if (wm8960->deemph) { best = 1; for (i = 2; i < ARRAY_SIZE(deemph_settings); i++) { if (abs(deemph_settings[i] - wm8960->playback_fs) < abs(deemph_settings[best] - wm8960->playback_fs)) best = i; } val = best << 1; } else { val = 0; } dev_dbg(codec->dev, "Set deemphasis %d", val); return snd_soc_update_bits(codec, WM8960_DACCTL1, 0x6, val);}static int wm8960_get_deemph(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol){ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); ucontrol->value.enumerated.item[0] = wm8960->deemph; return 0;}static int wm8960_put_deemph(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol){ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); int deemph = ucontrol->value.enumerated.item[0]; if (deemph > 1) return -EINVAL; wm8960->deemph = deemph; return wm8960_set_deemph(codec);}/*补充介绍:DECLARE_TLV_DB_LINEAR(beep_tlv, -4500, 0);DECLARE_TLV_DB_LINEAR宏定义的mixer control,它的输出随值的变化而线性变化。 该宏的:第一个参数是要定义变量的名字,第二个参数是最小值,以0.01dB为单位。第三个参数是最大值,以0.01dB为单位。如果该control处于最小值时会做出mute时,需要把第二个参数设为TLV_DB_GAIN_MUTE。这两个宏实际上就是定义一个整形数组,所谓tlv,就是Type-Lenght-Value的意思,数组的第0各元素代表数据的类型,第1个元素代表数据的长度,第三个元素和之后的元素保存该变量的数据。*//*这些定义的数组adc_tlv[]会被放在snd_kcontrol_new.tlv.p中,单位是db.DECLARE_TLV_DB_SCALE宏定义的mixer control,它所代表的值按一个固定的dB值的步长变化。该宏的:第一个参数是要定义变量的名字,第二个参数是最小值,以0.01dB为单位。第三个参数是变化的步长,也是以0.01dB为单位。如果该control处于最小值时会做出mute时,需要把第四个参数设为1。*/static const DECLARE_TLV_DB_SCALE(adc_tlv, -9700, 50, 0);static const DECLARE_TLV_DB_SCALE(dac_tlv, -12700, 50, 1);static const DECLARE_TLV_DB_SCALE(bypass_tlv, -2100, 300, 0);static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1);/* * 这些snd_kcontrol_new定义的控制项可以通过tinymix工具读取出来,可供app设置. * 这些是没有使用DAPM的kcontrol,对比SOC_SINGLE和SOC_DAPM_SINGLE可知其内部指定 * 的回调函数不同。*/static const struct snd_kcontrol_new wm8960_snd_controls[] = {SOC_DOUBLE_R_TLV("Capture Volume", WM8960_LINVOL, WM8960_RINVOL, 0, 63, 0, adc_tlv),SOC_DOUBLE_R("Capture Volume ZC Switch", WM8960_LINVOL, WM8960_RINVOL, 6, 1, 0),SOC_DOUBLE_R("Capture Switch", WM8960_LINVOL, WM8960_RINVOL, 7, 1, 0),SOC_DOUBLE_R_TLV("Playback Volume", WM8960_LDAC, WM8960_RDAC, 0, 255, 0, dac_tlv),SOC_DOUBLE_R_TLV("Headphone Playback Volume", WM8960_LOUT1, WM8960_ROUT1, 0, 127, 0, out_tlv),SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8960_LOUT1, WM8960_ROUT1, 7, 1, 0),SOC_DOUBLE_R_TLV("Speaker Playback Volume", WM8960_LOUT2, WM8960_ROUT2, 0, 127, 0, out_tlv),SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8960_LOUT2, WM8960_ROUT2, 7, 1, 0),SOC_SINGLE("Speaker DC Volume", WM8960_CLASSD3, 3, 5, 0),SOC_SINGLE("Speaker AC Volume", WM8960_CLASSD3, 0, 5, 0),SOC_SINGLE("PCM Playback -6dB Switch", WM8960_DACCTL1, 7, 1, 0),SOC_ENUM("ADC Polarity", wm8960_enum[0]),SOC_SINGLE("ADC High Pass Filter Switch", WM8960_DACCTL1, 0, 1, 0),SOC_ENUM("DAC Polarity", wm8960_enum[2]),SOC_SINGLE_BOOL_EXT("DAC Deemphasis Switch", 0, wm8960_get_deemph, wm8960_put_deemph),SOC_ENUM("3D Filter Upper Cut-Off", wm8960_enum[2]),SOC_ENUM("3D Filter Lower Cut-Off", wm8960_enum[3]),SOC_SINGLE("3D Volume", WM8960_3D, 1, 15, 0),SOC_SINGLE("3D Switch", WM8960_3D, 0, 1, 0),SOC_ENUM("ALC Function", wm8960_enum[4]),SOC_SINGLE("ALC Max Gain", WM8960_ALC1, 4, 7, 0),SOC_SINGLE("ALC Target", WM8960_ALC1, 0, 15, 1),SOC_SINGLE("ALC Min Gain", WM8960_ALC2, 4, 7, 0),SOC_SINGLE("ALC Hold Time", WM8960_ALC2, 0, 15, 0),SOC_ENUM("ALC Mode", wm8960_enum[5]),SOC_SINGLE("ALC Decay", WM8960_ALC3, 4, 15, 0),SOC_SINGLE("ALC Attack", WM8960_ALC3, 0, 15, 0),SOC_SINGLE("Noise Gate Threshold", WM8960_NOISEG, 3, 31, 0),SOC_SINGLE("Noise Gate Switch", WM8960_NOISEG, 0, 1, 0),SOC_DOUBLE_R("ADC PCM Capture Volume", WM8960_LINPATH, WM8960_RINPATH, 4, 3, 0),SOC_SINGLE_TLV("Left Output Mixer Boost Bypass Volume", WM8960_BYPASS1, 4, 7, 1, bypass_tlv),SOC_SINGLE_TLV("Left Output Mixer LINPUT3 Volume", WM8960_LOUTMIX, 4, 7, 1, bypass_tlv),SOC_SINGLE_TLV("Right Output Mixer Boost Bypass Volume", WM8960_BYPASS2, 4, 7, 1, bypass_tlv),SOC_SINGLE_TLV("Right Output Mixer RINPUT3 Volume", WM8960_ROUTMIX, 4, 7, 1, bypass_tlv),};/* * 宏中带DAPM的表示是使用DAPM的kcontrol. * * dapm kcontrol的put回调函数不仅仅会更新控件本身的状态,他还会把这种变化传 * 递到相邻的dapm kcontrol,相邻的dapm kcontrol又会传递这个变化到他自己相邻 * 的dapm kcontrol,知道音频路径的末端,通过这种机制,只要改变其中一个widget * 的连接状态,与之相关的所有widget都会被扫描并测试一下自身是否还在有效的音频 * 路径中,从而可以动态地改变自身的电源状态,这就是dapm的精髓所在。 */static const struct snd_kcontrol_new wm8960_lin_boost[] = {SOC_DAPM_SINGLE("LINPUT2 Switch", WM8960_LINPATH, 6, 1, 0),SOC_DAPM_SINGLE("LINPUT3 Switch", WM8960_LINPATH, 7, 1, 0),SOC_DAPM_SINGLE("LINPUT1 Switch", WM8960_LINPATH, 8, 1, 0),};/* * snd_kcontrol_new是定义个控件*/static const struct snd_kcontrol_new wm8960_lin[] = {SOC_DAPM_SINGLE("Boost Switch", WM8960_LINPATH, 3, 1, 0),};static const struct snd_kcontrol_new wm8960_rin_boost[] = {SOC_DAPM_SINGLE("RINPUT2 Switch", WM8960_RINPATH, 6, 1, 0),SOC_DAPM_SINGLE("RINPUT3 Switch", WM8960_RINPATH, 7, 1, 0),SOC_DAPM_SINGLE("RINPUT1 Switch", WM8960_RINPATH, 8, 1, 0),};static const struct snd_kcontrol_new wm8960_rin[] = {SOC_DAPM_SINGLE("Boost Switch", WM8960_RINPATH, 3, 1, 0),};static const struct snd_kcontrol_new wm8960_loutput_mixer[] = {SOC_DAPM_SINGLE("PCM Playback Switch", WM8960_LOUTMIX, 8, 1, 0),SOC_DAPM_SINGLE("LINPUT3 Switch", WM8960_LOUTMIX, 7, 1, 0),SOC_DAPM_SINGLE("Boost Bypass Switch", WM8960_BYPASS1, 7, 1, 0),};static const struct snd_kcontrol_new wm8960_routput_mixer[] = {SOC_DAPM_SINGLE("PCM Playback Switch", WM8960_ROUTMIX, 8, 1, 0),SOC_DAPM_SINGLE("RINPUT3 Switch", WM8960_ROUTMIX, 7, 1, 0),SOC_DAPM_SINGLE("Boost Bypass Switch", WM8960_BYPASS2, 7, 1, 0),};static const struct snd_kcontrol_new wm8960_mono_out[] = {SOC_DAPM_SINGLE("Left Switch", WM8960_MONOMIX1, 7, 1, 0),SOC_DAPM_SINGLE("Right Switch", WM8960_MONOMIX2, 7, 1, 0),};/*不同的组件初始化的成员不同:SND_SOC_DAPM_INPUT中没有snd_kcontrol_newSND_SOC_DAPM_MIXER中有多个snd_kcontrol_new将上面定义的控件转化为widget*/static const struct snd_soc_dapm_widget wm8960_dapm_widgets[] = {SND_SOC_DAPM_INPUT("LINPUT1"),SND_SOC_DAPM_INPUT("RINPUT1"),SND_SOC_DAPM_INPUT("LINPUT2"),SND_SOC_DAPM_INPUT("RINPUT2"),SND_SOC_DAPM_INPUT("LINPUT3"),SND_SOC_DAPM_INPUT("RINPUT3"),SND_SOC_DAPM_MICBIAS("MICB", WM8960_POWER1, 1, 0),/* * 若arg2=SND_SOC_NOPM则表示此widget不具备电源属性,但是mux的 * 切换会影响到与之相连的其它具备电源属性的电源状态。 */SND_SOC_DAPM_MIXER("Left Boost Mixer", WM8960_POWER1, 5, 0, wm8960_lin_boost, ARRAY_SIZE(wm8960_lin_boost)),SND_SOC_DAPM_MIXER("Right Boost Mixer", WM8960_POWER1, 4, 0, wm8960_rin_boost, ARRAY_SIZE(wm8960_rin_boost)),SND_SOC_DAPM_MIXER("Left Input Mixer", WM8960_POWER3, 5, 0, wm8960_lin, ARRAY_SIZE(wm8960_lin)),SND_SOC_DAPM_MIXER("Right Input Mixer", WM8960_POWER3, 4, 0, wm8960_rin, ARRAY_SIZE(wm8960_rin)),SND_SOC_DAPM_ADC("Left ADC", "Capture", WM8960_POWER1, 3, 0),SND_SOC_DAPM_ADC("Right ADC", "Capture", WM8960_POWER1, 2, 0),SND_SOC_DAPM_DAC("Left DAC", "Playback", WM8960_POWER2, 8, 0),SND_SOC_DAPM_DAC("Right DAC", "Playback", WM8960_POWER2, 7, 0),SND_SOC_DAPM_MIXER("Left Output Mixer", WM8960_POWER3, 3, 0, &wm8960_loutput_mixer[0], ARRAY_SIZE(wm8960_loutput_mixer)),SND_SOC_DAPM_MIXER("Right Output Mixer", WM8960_POWER3, 2, 0, &wm8960_routput_mixer[0], ARRAY_SIZE(wm8960_routput_mixer)),SND_SOC_DAPM_PGA("LOUT1 PGA", WM8960_POWER2, 6, 0, NULL, 0),SND_SOC_DAPM_PGA("ROUT1 PGA", WM8960_POWER2, 5, 0, NULL, 0),SND_SOC_DAPM_PGA("Left Speaker PGA", WM8960_POWER2, 4, 0, NULL, 0),SND_SOC_DAPM_PGA("Right Speaker PGA", WM8960_POWER2, 3, 0, NULL, 0),SND_SOC_DAPM_PGA("Right Speaker Output", WM8960_CLASSD1, 7, 0, NULL, 0),SND_SOC_DAPM_PGA("Left Speaker Output", WM8960_CLASSD1, 6, 0, NULL, 0),SND_SOC_DAPM_OUTPUT("SPK_LP"),SND_SOC_DAPM_OUTPUT("SPK_LN"),SND_SOC_DAPM_OUTPUT("HP_L"),SND_SOC_DAPM_OUTPUT("HP_R"),SND_SOC_DAPM_OUTPUT("SPK_RP"),SND_SOC_DAPM_OUTPUT("SPK_RN"),SND_SOC_DAPM_OUTPUT("OUT3"),};static const struct snd_soc_dapm_widget wm8960_dapm_widgets_out3[] = {SND_SOC_DAPM_MIXER("Mono Output Mixer", WM8960_POWER2, 1, 0, &wm8960_mono_out[0], ARRAY_SIZE(wm8960_mono_out)),};/* Represent OUT3 as a PGA so that it gets turned on with LOUT1/ROUT1 */static const struct snd_soc_dapm_widget wm8960_dapm_widgets_capless[] = { SND_SOC_DAPM_PGA("OUT3 VMID", WM8960_POWER2, 1, 0, NULL, 0),};/*route的source和sink成员都是widget的名字,control成员是两个widget之间连接通过的开关的snd_kcontrol, 若是两个widget之间有开关,那么control成员就是这个开关的名字。若是没有开关而是直连的,那么control成员就设置为NULL,此时这个连接成为静态连接。route会被解析成path,使用snd_soc_dapm_path结构表示。从这个结构体里面可以看出声道的连接路由。*/static const struct snd_soc_dapm_route audio_paths[] = { { "Left Boost Mixer", "LINPUT1 Switch", "LINPUT1" }, { "Left Boost Mixer", "LINPUT2 Switch", "LINPUT2" }, { "Left Boost Mixer", "LINPUT3 Switch", "LINPUT3" }, { "Left Input Mixer", "Boost Switch", "Left Boost Mixer", }, { "Left Input Mixer", NULL, "LINPUT1", }, /* Really Boost Switch */ { "Left Input Mixer", NULL, "LINPUT2" }, { "Left Input Mixer", NULL, "LINPUT3" }, { "Right Boost Mixer", "RINPUT1 Switch", "RINPUT1" }, { "Right Boost Mixer", "RINPUT2 Switch", "RINPUT2" }, { "Right Boost Mixer", "RINPUT3 Switch", "RINPUT3" }, { "Right Input Mixer", "Boost Switch", "Right Boost Mixer", }, { "Right Input Mixer", NULL, "RINPUT1", }, /* Really Boost Switch */ { "Right Input Mixer", NULL, "RINPUT2" }, { "Right Input Mixer", NULL, "LINPUT3" }, { "Left ADC", NULL, "Left Input Mixer" }, { "Right ADC", NULL, "Right Input Mixer" }, { "Left Output Mixer", "LINPUT3 Switch", "LINPUT3" }, { "Left Output Mixer", "Boost Bypass Switch", "Left Boost Mixer"} , { "Left Output Mixer", "PCM Playback Switch", "Left DAC" }, { "Right Output Mixer", "RINPUT3 Switch", "RINPUT3" }, { "Right Output Mixer", "Boost Bypass Switch", "Right Boost Mixer" } , { "Right Output Mixer", "PCM Playback Switch", "Right DAC" }, { "LOUT1 PGA", NULL, "Left Output Mixer" }, { "ROUT1 PGA", NULL, "Right Output Mixer" }, { "HP_L", NULL, "LOUT1 PGA" }, { "HP_R", NULL, "ROUT1 PGA" }, { "Left Speaker PGA", NULL, "Left Output Mixer" }, { "Right Speaker PGA", NULL, "Right Output Mixer" }, { "Left Speaker Output", NULL, "Left Speaker PGA" }, { "Right Speaker Output", NULL, "Right Speaker PGA" }, { "SPK_LN", NULL, "Left Speaker Output" }, { "SPK_LP", NULL, "Left Speaker Output" }, { "SPK_RN", NULL, "Right Speaker Output" }, { "SPK_RP", NULL, "Right Speaker Output" },};static const struct snd_soc_dapm_route audio_paths_out3[] = { { "Mono Output Mixer", "Left Switch", "Left Output Mixer" }, { "Mono Output Mixer", "Right Switch", "Right Output Mixer" }, { "OUT3", NULL, "Mono Output Mixer", }};static const struct snd_soc_dapm_route audio_paths_capless[] = { { "HP_L", NULL, "OUT3 VMID" }, { "HP_R", NULL, "OUT3 VMID" }, { "OUT3 VMID", NULL, "Left Output Mixer" }, { "OUT3 VMID", NULL, "Right Output Mixer" },};static int wm8960_add_widgets(struct snd_soc_codec *codec){ struct wm8960_data *pdata = codec->dev->platform_data; struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); struct snd_soc_dapm_context *dapm = &codec->dapm; struct snd_soc_dapm_widget *w; 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)); /* In capless mode OUT3 is used to provide VMID for the * headphone outputs, otherwise it is used as a mono mixer. */ if (pdata && pdata->capless) { snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets_capless, ARRAY_SIZE(wm8960_dapm_widgets_capless)); snd_soc_dapm_add_routes(dapm, audio_paths_capless, ARRAY_SIZE(audio_paths_capless)); } else { snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets_out3, ARRAY_SIZE(wm8960_dapm_widgets_out3)); snd_soc_dapm_add_routes(dapm, audio_paths_out3, ARRAY_SIZE(audio_paths_out3)); } /* We need to power up the headphone output stage out of * sequence for capless mode. To save scanning the widget * list each time to find the desired power state do so now * and save the result. */ list_for_each_entry(w, &codec->card->widgets, list) { if (w->dapm != &codec->dapm) continue; if (strcmp(w->name, "LOUT1 PGA") == 0) wm8960->lout1 = w; if (strcmp(w->name, "ROUT1 PGA") == 0) wm8960->rout1 = w; if (strcmp(w->name, "OUT3 VMID") == 0) wm8960->out3 = w; } return 0;}static int wm8960_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt){ struct snd_soc_codec *codec = codec_dai->codec; u16 iface = 0; /* set master/slave audio interface */ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { case SND_SOC_DAIFMT_CBM_CFM: iface |= 0x0040; break; case SND_SOC_DAIFMT_CBS_CFS: break; default: return -EINVAL; } /* interface format */ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S: iface |= 0x0002; break; case SND_SOC_DAIFMT_RIGHT_J: break; case SND_SOC_DAIFMT_LEFT_J: iface |= 0x0001; break; case SND_SOC_DAIFMT_DSP_A: iface |= 0x0003; break; case SND_SOC_DAIFMT_DSP_B: iface |= 0x0013; break; default: return -EINVAL; } /* clock inversion */ switch (fmt & SND_SOC_DAIFMT_INV_MASK) { case SND_SOC_DAIFMT_NB_NF: break; case SND_SOC_DAIFMT_IB_IF: iface |= 0x0090; break; case SND_SOC_DAIFMT_IB_NF: iface |= 0x0080; break; case SND_SOC_DAIFMT_NB_IF: iface |= 0x0010; break; default: return -EINVAL; } /* set iface */ snd_soc_write(codec, WM8960_IFACE1, iface); return 0;}static struct { int rate; unsigned int val;} alc_rates[] = { { 48000, 0 }, { 44100, 0 }, { 32000, 1 }, { 22050, 2 }, { 24000, 2 }, { 16000, 3 }, { 11250, 4 }, { 12000, 4 }, { 8000, 5 },};/*这个params从何而来???*/static int wm8960_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai){ struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_codec *codec = rtd->codec; struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); u16 iface = snd_soc_read(codec, WM8960_IFACE1) & 0xfff3; int i; /* bit size */ switch (params_format(params)) { case SNDRV_PCM_FORMAT_S16_LE: break; case SNDRV_PCM_FORMAT_S20_3LE: iface |= 0x0004; break; case SNDRV_PCM_FORMAT_S24_LE: iface |= 0x0008; break; } /* Update filters for the new rate */ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { wm8960->playback_fs = params_rate(params); wm8960_set_deemph(codec); } else { for (i = 0; i < ARRAY_SIZE(alc_rates); i++) if (alc_rates[i].rate == params_rate(params)) snd_soc_update_bits(codec, WM8960_ADDCTL3, 0x7, alc_rates[i].val); } /* set iface */ snd_soc_write(codec, WM8960_IFACE1, iface); return 0;}static int wm8960_mute(struct snd_soc_dai *dai, int mute){ struct snd_soc_codec *codec = dai->codec; u16 mute_reg = snd_soc_read(codec, WM8960_DACCTL1) & 0xfff7; if (mute) snd_soc_write(codec, WM8960_DACCTL1, mute_reg | 0x8); else snd_soc_write(codec, WM8960_DACCTL1, mute_reg); return 0;}static int wm8960_set_bias_level_out3(struct snd_soc_codec *codec, enum snd_soc_bias_level level){ u16 reg; switch (level) { case SND_SOC_BIAS_ON: break; case SND_SOC_BIAS_PREPARE: /* Set VMID to 2x50k */ reg = snd_soc_read(codec, WM8960_POWER1); reg &= ~0x180; reg |= 0x80; snd_soc_write(codec, WM8960_POWER1, reg); break; case SND_SOC_BIAS_STANDBY: if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) { /* Enable anti-pop features */ snd_soc_write(codec, WM8960_APOP1, WM8960_POBCTRL | WM8960_SOFT_ST | WM8960_BUFDCOPEN | WM8960_BUFIOEN); /* Enable & ramp VMID at 2x50k */ reg = snd_soc_read(codec, WM8960_POWER1); reg |= 0x80; snd_soc_write(codec, WM8960_POWER1, reg); msleep(100); /* Enable VREF */ snd_soc_write(codec, WM8960_POWER1, reg | WM8960_VREF); /* Disable anti-pop features */ snd_soc_write(codec, WM8960_APOP1, WM8960_BUFIOEN); } /* Set VMID to 2x250k */ reg = snd_soc_read(codec, WM8960_POWER1); reg &= ~0x180; reg |= 0x100; snd_soc_write(codec, WM8960_POWER1, reg); break; case SND_SOC_BIAS_OFF: /* Enable anti-pop features */ snd_soc_write(codec, WM8960_APOP1, WM8960_POBCTRL | WM8960_SOFT_ST | WM8960_BUFDCOPEN | WM8960_BUFIOEN); /* Disable VMID and VREF, let them discharge */ snd_soc_write(codec, WM8960_POWER1, 0); msleep(600); break; } codec->dapm.bias_level = level; return 0;}static int wm8960_set_bias_level_capless(struct snd_soc_codec *codec, enum snd_soc_bias_level level){ struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); int reg; switch (level) { case SND_SOC_BIAS_ON: break; case SND_SOC_BIAS_PREPARE: switch (codec->dapm.bias_level) { case SND_SOC_BIAS_STANDBY: /* Enable anti pop mode */ snd_soc_update_bits(codec, WM8960_APOP1, WM8960_POBCTRL | WM8960_SOFT_ST | WM8960_BUFDCOPEN, WM8960_POBCTRL | WM8960_SOFT_ST | WM8960_BUFDCOPEN); /* Enable LOUT1, ROUT1 and OUT3 if they‘re enabled */ reg = 0; if (wm8960->lout1 && wm8960->lout1->power) reg |= WM8960_PWR2_LOUT1; if (wm8960->rout1 && wm8960->rout1->power) reg |= WM8960_PWR2_ROUT1; if (wm8960->out3 && wm8960->out3->power) reg |= WM8960_PWR2_OUT3; snd_soc_update_bits(codec, WM8960_POWER2, WM8960_PWR2_LOUT1 | WM8960_PWR2_ROUT1 | WM8960_PWR2_OUT3, reg); /* Enable VMID at 2*50k */ snd_soc_update_bits(codec, WM8960_POWER1, WM8960_VMID_MASK, 0x80); /* Ramp */ msleep(100); /* Enable VREF */ snd_soc_update_bits(codec, WM8960_POWER1, WM8960_VREF, WM8960_VREF); msleep(100); break; case SND_SOC_BIAS_ON: /* Enable anti-pop mode */ snd_soc_update_bits(codec, WM8960_APOP1, WM8960_POBCTRL | WM8960_SOFT_ST | WM8960_BUFDCOPEN, WM8960_POBCTRL | WM8960_SOFT_ST | WM8960_BUFDCOPEN); /* Disable VMID and VREF */ snd_soc_update_bits(codec, WM8960_POWER1, WM8960_VREF | WM8960_VMID_MASK, 0); break; default: break; } break; case SND_SOC_BIAS_STANDBY: switch (codec->dapm.bias_level) { case SND_SOC_BIAS_PREPARE: /* Disable HP discharge */ snd_soc_update_bits(codec, WM8960_APOP2, WM8960_DISOP | WM8960_DRES_MASK, 0); /* Disable anti-pop features */ snd_soc_update_bits(codec, WM8960_APOP1, WM8960_POBCTRL | WM8960_SOFT_ST | WM8960_BUFDCOPEN, WM8960_POBCTRL | WM8960_SOFT_ST | WM8960_BUFDCOPEN); break; default: break; } break; case SND_SOC_BIAS_OFF: break; } codec->dapm.bias_level = level; return 0;}/* PLL divisors */struct _pll_div { u32 pre_div:1; u32 n:4; u32 k:24;};/* The size in bits of the pll divide multiplied by 10 * to allow rounding later */#define FIXED_PLL_SIZE ((1 << 24) * 10)static int pll_factors(unsigned int source, unsigned int target, struct _pll_div *pll_div){ unsigned long long Kpart; unsigned int K, Ndiv, Nmod; pr_debug("WM8960 PLL: setting %dHz->%dHz", source, target); /* Scale up target to PLL operating frequency */ target *= 4; Ndiv = target / source; if (Ndiv < 6) { source >>= 1; pll_div->pre_div = 1; Ndiv = target / source; } else pll_div->pre_div = 0; if ((Ndiv < 6) || (Ndiv > 12)) { pr_err("WM8960 PLL: Unsupported N=%d", Ndiv); return -EINVAL; } pll_div->n = Ndiv; Nmod = target % source; Kpart = FIXED_PLL_SIZE * (long long)Nmod; do_div(Kpart, source); K = Kpart & 0xFFFFFFFF; /* Check if we need to round */ if ((K % 10) >= 5) K += 5; /* Move down to proper range now rounding is done */ K /= 10; pll_div->k = K; pr_debug("WM8960 PLL: N=%x K=%x pre_div=%d", pll_div->n, pll_div->k, pll_div->pre_div); return 0;}static int wm8960_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, int source, unsigned int freq_in, unsigned int freq_out){ struct snd_soc_codec *codec = codec_dai->codec; u16 reg; static struct _pll_div pll_div; int ret; if (freq_in && freq_out) { ret = pll_factors(freq_in, freq_out, &pll_div); if (ret != 0) return ret; } /* Disable the PLL: even if we are changing the frequency the * PLL needs to be disabled while we do so. */ snd_soc_write(codec, WM8960_CLOCK1, snd_soc_read(codec, WM8960_CLOCK1) & ~1); snd_soc_write(codec, WM8960_POWER2, snd_soc_read(codec, WM8960_POWER2) & ~1); if (!freq_in || !freq_out) return 0; reg = snd_soc_read(codec, WM8960_PLL1) & ~0x3f; reg |= pll_div.pre_div << 4; reg |= pll_div.n; if (pll_div.k) { reg |= 0x20; snd_soc_write(codec, WM8960_PLL2, (pll_div.k >> 18) & 0x3f); snd_soc_write(codec, WM8960_PLL3, (pll_div.k >> 9) & 0x1ff); snd_soc_write(codec, WM8960_PLL4, pll_div.k & 0x1ff); } snd_soc_write(codec, WM8960_PLL1, reg); /* Turn it on */ snd_soc_write(codec, WM8960_POWER2, snd_soc_read(codec, WM8960_POWER2) | 1); msleep(250); snd_soc_write(codec, WM8960_CLOCK1, snd_soc_read(codec, WM8960_CLOCK1) | 1); return 0;}static int wm8960_set_dai_clkdiv(struct snd_soc_dai *codec_dai, int div_id, int div){ struct snd_soc_codec *codec = codec_dai->codec; u16 reg; switch (div_id) { case WM8960_SYSCLKDIV: reg = snd_soc_read(codec, WM8960_CLOCK1) & 0x1f9; snd_soc_write(codec, WM8960_CLOCK1, reg | div); break; case WM8960_DACDIV: reg = snd_soc_read(codec, WM8960_CLOCK1) & 0x1c7; snd_soc_write(codec, WM8960_CLOCK1, reg | div); break; case WM8960_OPCLKDIV: reg = snd_soc_read(codec, WM8960_PLL1) & 0x03f; snd_soc_write(codec, WM8960_PLL1, reg | div); break; case WM8960_DCLKDIV: reg = snd_soc_read(codec, WM8960_CLOCK2) & 0x03f; snd_soc_write(codec, WM8960_CLOCK2, reg | div); break; case WM8960_TOCLKSEL: reg = snd_soc_read(codec, WM8960_ADDCTL1) & 0x1fd; snd_soc_write(codec, WM8960_ADDCTL1, reg | div); break; default: return -EINVAL; } return 0;}static int wm8960_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level){ struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); return wm8960->set_bias_level(codec, level);}#define WM8960_RATES SNDRV_PCM_RATE_8000_48000#define WM8960_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE)static struct snd_soc_dai_ops wm8960_dai_ops = { .hw_params = wm8960_hw_params, .digital_mute = wm8960_mute, .set_fmt = wm8960_set_dai_fmt, .set_clkdiv = wm8960_set_dai_clkdiv, .set_pll = wm8960_set_dai_pll,};/*这个是dai接口的驱动*/static struct snd_soc_dai_driver wm8960_dai = { /* * 这个name会传递给snd_soc_codec.name, 后者会和 * codec_conf->dev_name匹配 */ .name = "wm8960-hifi", .playback = { .stream_name = "Playback", .channels_min = 1, .channels_max = 2, .rates = WM8960_RATES, .formats = WM8960_FORMATS,}, .capture = { .stream_name = "Capture", .channels_min = 1, .channels_max = 2, .rates = WM8960_RATES, .formats = WM8960_FORMATS,}, .ops = &wm8960_dai_ops, .symmetric_rates = 1,};static int wm8960_suspend(struct snd_soc_codec *codec, pm_message_t state){ struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); wm8960->set_bias_level(codec, SND_SOC_BIAS_OFF); return 0;}static int wm8960_resume(struct snd_soc_codec *codec){ struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); int i; u8 data[2]; u16 *cache = codec->reg_cache; /* Sync reg_cache with the hardware */ for (i = 0; i < ARRAY_SIZE(wm8960_reg); i++) { data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); data[1] = cache[i] & 0x00ff; codec->hw_write(codec->control_data, data, 2); } wm8960->set_bias_level(codec, SND_SOC_BIAS_STANDBY); return 0;}static int wm8960_probe(struct snd_soc_codec *codec){ struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); struct wm8960_data *pdata = dev_get_platdata(codec->dev); int ret; u16 reg; wm8960->set_bias_level = wm8960_set_bias_level_out3; codec->control_data = wm8960->control_data; if (!pdata) { dev_warn(codec->dev, "No platform data supplied"); } else { if (pdata->dres > WM8960_DRES_MAX) { dev_err(codec->dev, "Invalid DRES: %d", pdata->dres); pdata->dres = 0; } if (pdata->capless) wm8960->set_bias_level = wm8960_set_bias_level_capless; } /*选中codec的控制接口*/ ret = snd_soc_codec_set_cache_io(codec, 7, 9, wm8960->control_type); if (ret < 0) { dev_err(codec->dev, "Failed to set cache I/O: %d", ret); return ret; } ret = wm8960_reset(codec); if (ret < 0) { dev_err(codec->dev, "Failed to issue reset"); return ret; } wm8960->set_bias_level(codec, SND_SOC_BIAS_STANDBY); /*对芯片寄存器进行初始化*/ /* Latch the update bits */ reg = snd_soc_read(codec, WM8960_LINVOL); snd_soc_write(codec, WM8960_LINVOL, reg | 0x100); reg = snd_soc_read(codec, WM8960_RINVOL); snd_soc_write(codec, WM8960_RINVOL, reg | 0x100); reg = snd_soc_read(codec, WM8960_LADC); snd_soc_write(codec, WM8960_LADC, reg | 0x100); reg = snd_soc_read(codec, WM8960_RADC); snd_soc_write(codec, WM8960_RADC, reg | 0x100); reg = snd_soc_read(codec, WM8960_LDAC); snd_soc_write(codec, WM8960_LDAC, reg | 0x100); reg = snd_soc_read(codec, WM8960_RDAC); snd_soc_write(codec, WM8960_RDAC, reg | 0x100); reg = snd_soc_read(codec, WM8960_LOUT1); snd_soc_write(codec, WM8960_LOUT1, reg | 0x100); reg = snd_soc_read(codec, WM8960_ROUT1); snd_soc_write(codec, WM8960_ROUT1, reg | 0x100); reg = snd_soc_read(codec, WM8960_LOUT2); snd_soc_write(codec, WM8960_LOUT2, reg | 0x100); reg = snd_soc_read(codec, WM8960_ROUT2); snd_soc_write(codec, WM8960_ROUT2, reg | 0x100); /* 如果不想每次上电录音之前都要执行这些命令, 就在此设置寄存器: * tinymix "Capture Switch" 0 * tinymix "Left Input Mixer Boost Switch" 1 */ /* Capture Switch off */ snd_soc_update_bits(codec, WM8960_LINVOL, (1<<7), (0<<7)); /* Left Input Mixer Boost Switch */ snd_soc_update_bits(codec, WM8960_LINPATH, (1<<3), (1<<3)); /*初始化controls和widgets*/ snd_soc_add_controls(codec, wm8960_snd_controls, ARRAY_SIZE(wm8960_snd_controls)); wm8960_add_widgets(codec); return 0;}/* power down chip */static int wm8960_remove(struct snd_soc_codec *codec){ struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); wm8960->set_bias_level(codec, SND_SOC_BIAS_OFF); return 0;}/*这个是codec芯片的驱动*/static struct snd_soc_codec_driver soc_codec_dev_wm8960 = { .probe = wm8960_probe, .remove = wm8960_remove, .suspend = wm8960_suspend, .resume = wm8960_resume, .set_bias_level = wm8960_set_bias_level, .reg_cache_size = ARRAY_SIZE(wm8960_reg), .reg_word_size = sizeof(u16), .reg_cache_default = wm8960_reg,};/*这是主机i2c控制接口的驱动*/#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)static __devinit int wm8960_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id){ struct wm8960_priv *wm8960; int ret; wm8960 = kzalloc(sizeof(struct wm8960_priv), GFP_KERNEL); if (wm8960 == NULL) return -ENOMEM; i2c_set_clientdata(i2c, wm8960); /*指定对codec芯片的控制接口是i2c*/ wm8960->control_type = SND_SOC_I2C; wm8960->control_data = i2c; /* * 注册一个codec需要提供snd_soc_codec_driver和snd_soc_dai_driver * 同时可以有多个dai * 这个函数内部会实例化一个snd_soc_codec,若全局链表上有数据还会去实例化 * 一个snd_soc_card(这个结构在其它地方也能实例化) */ ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_wm8960, &wm8960_dai, 1); if (ret < 0) kfree(wm8960); return ret;}static __devexit int wm8960_i2c_remove(struct i2c_client *client){ snd_soc_unregister_codec(&client->dev); kfree(i2c_get_clientdata(client)); return 0;}/*设备端在mach-tiny4412.c中定义*/static const struct i2c_device_id wm8960_i2c_id[] = { { "wm8960", 0 }, { }};MODULE_DEVICE_TABLE(i2c, wm8960_i2c_id);/*它是怎么通过名字匹配到设备的呢?*/static struct i2c_driver wm8960_i2c_driver = { .driver = { .name = "wm8960-codec", .owner = THIS_MODULE, }, .probe = wm8960_i2c_probe, .remove = __devexit_p(wm8960_i2c_remove), .id_table = wm8960_i2c_id,};#endifstatic int __init wm8960_modinit(void){ int ret = 0;#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) ret = i2c_add_driver(&wm8960_i2c_driver); if (ret != 0) { printk(KERN_ERR "Failed to register WM8960 I2C driver: %d", ret); }#endif return ret;}module_init(wm8960_modinit);static void __exit wm8960_exit(void){#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) i2c_del_driver(&wm8960_i2c_driver);#endif}module_exit(wm8960_exit);MODULE_DESCRIPTION("ASoC WM8960 driver");MODULE_AUTHOR("Liam Girdwood");MODULE_LICENSE("GPL");View Code
(2) Machine平台设备驱动设备端(驱动端是: sound/soc/soc-core.c)
#include <linux/clk.h>#include <linux/gpio.h>#include <linux/module.h>#include <sound/soc.h>#include <sound/pcm.h>#include <sound/pcm_params.h>#include "i2s.h"static int set_epll_rate(unsigned long rate){ struct clk *fout_epll; fout_epll = clk_get(NULL, "fout_epll"); if (IS_ERR(fout_epll)) { printk(KERN_ERR "%s: failed to get fout_epll", __func__); return PTR_ERR(fout_epll); } if (rate == clk_get_rate(fout_epll)) goto out; clk_set_rate(fout_epll, rate);out: clk_put(fout_epll); return 0;}/*RFS:IIS Root clk freq select,BFS:bit clock freq select*/static int smdk_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params){ struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *codec_dai = rtd->codec_dai; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; int bfs, psr, rfs, ret; unsigned long rclk; switch (params_format(params)) { case SNDRV_PCM_FORMAT_U24: case SNDRV_PCM_FORMAT_S24: bfs = 48; break; case SNDRV_PCM_FORMAT_U16_LE: case SNDRV_PCM_FORMAT_S16_LE: bfs = 32; break; default: return -EINVAL; } switch (params_rate(params)) { case 16000: case 22050: case 24000: case 32000: case 44100: case 48000: case 88200: case 96000: if (bfs == 48) rfs = 384; /*rfs=bfs*8*/ else rfs = 256; break; case 64000: rfs = 384; break; case 8000: case 11025: case 12000: if (bfs == 48) rfs = 768; else rfs = 512; break; default: return -EINVAL; } rclk = params_rate(params) * rfs; switch (rclk) { case 4096000: case 5644800: case 6144000: case 8467200: case 9216000: psr = 8; break; case 8192000: case 11289600: case 12288000: case 16934400: case 18432000: psr = 4; break; case 22579200: case 24576000: case 33868800: case 36864000: psr = 2; break; case 67737600: case 73728000: psr = 1; break; default: printk("Not yet supported!"); return -EINVAL; } set_epll_rate(rclk * psr); /*调用platform驱动中的dai驱动的函数*/ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); if (ret < 0) return ret; ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); if (ret < 0) return ret; ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_CDCLK, 0, SND_SOC_CLOCK_OUT); if (ret < 0) return ret; ret = snd_soc_dai_set_clkdiv(cpu_dai, SAMSUNG_I2S_DIV_BCLK, bfs); if (ret < 0) return ret; return 0;}/* 参考soundsocsamsungs3c24xx_uda134x.c *//* * 1. 分配注册一个名为soc-audio的平台设备 * 2. 这个平台设备有一个私有数据 snd_soc_card * snd_soc_card里有一项snd_soc_dai_link * snd_soc_dai_link被用来决定ASOC各部分的驱动 */static struct snd_soc_ops tiny4412_wm8960_ops = { /*这里面指定的params从哪里来????*/ .hw_params = smdk_hw_params,};/*#define SND_SOC_DAPM_MIC(wname, wevent){ .id = snd_soc_dapm_mic, .name = "Mic Onboard", .kcontrol_news = NULL, .num_kcontrols = 0, .reg = SND_SOC_NOPM, .event = NULL, .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD 关系的dapm事件: SND_SOC_DAPM_PRE_PMU: widget要上电前发出的事件 SND_SOC_DAPM_POST_PMD: widget要下电后发出的事件}这也是一条输入line的一部分,但是由于是板级的,所以写在这个文件中.处理单板上的widget,定义的虚拟widget*/static const struct snd_soc_dapm_widget tiny4412_wm8960_widgets[] = { SND_SOC_DAPM_MIC("Mic Onboard", NULL),};static const struct snd_soc_dapm_route tiny4412_wm8960_paths[] = { { "MICB", NULL, "Mic Onboard" }, { "LINPUT1", NULL, "MICB" },};static int tiny4412_wm8960_machine_init(struct snd_soc_pcm_runtime *rtd){ struct snd_soc_codec *codec = rtd->codec; struct snd_soc_dapm_context *dapm = &codec->dapm; /* 添加一个虚拟的MIC widget */ snd_soc_dapm_new_controls(dapm, tiny4412_wm8960_widgets, ARRAY_SIZE(tiny4412_wm8960_widgets)); /* 添加2个route */ snd_soc_dapm_add_routes(dapm, tiny4412_wm8960_paths, ARRAY_SIZE(tiny4412_wm8960_paths));#define WM8960_IFACE2 0x9 //设置Pin15(ADCLRC/GPIO)为GPIO ,如果不设置而且Pin15上又没有外部时钟则ADC 工作异常 snd_soc_update_bits(codec, WM8960_IFACE2, 0x40, 0x40); snd_soc_dapm_sync(dapm); return 0;}/* * snd_soc_dai_link被用来决定ASOC各部分的驱动。 * 通过名字的匹配过程是在soc_bind_dai_link()中完成的。 */static struct snd_soc_dai_link tiny4412_wm8960_dai_link = { /*这两个名字不用与匹配信息*/ .name = "NAME_UDA8960", .stream_name = "STREAM_NAME_UDA8960", /* * "wm8960-codec.0-001a"这个名字来自codec驱动,分为2部分, * wm8960-codec是在wm8960.c中指定的i2c驱动的名字,0-001a是i2c * 设备的设备地址,组合起来称为snd_soc_codec->name。 * 这个是用来匹配wm8960这个codec驱动的。 */ .codec_name = "wm8960-codec.0-001a", /* * 这个名字用于匹配wm8960.c中注册的dai,而这个codec的dai的name * 来自snd_soc_dai_driver->name, 因此指定为codec中注册的 * snd_soc_dai_driver中的name就可以了。 * 作用: 用于匹配codec中的哪个codec_dai */ .codec_dai_name = "wm8960-hifi", /* * 这个名字用于和soundsocsamsungI2s.c中使用snd_soc_register_dai注册的dai_name * 匹配上才行, 应该用于Soc与Codec之间通信的接口的选择。 * * codec_dai和cpu_dai都确定下来了,那么一条dai连接就确定下来了。 */ .cpu_dai_name = "samsung-i2s.0", .ops = &tiny4412_wm8960_ops, /* * 用于选择使用哪个platform(Soc相关)驱动,指定这个名字是要求 * 使用soundsocsamsungdma.c中注册的snd_soc_platform * 一个Soc可能注册了多个snd_soc_platform,这个用于选择使用哪个。 */ .platform_name = "samsung-audio", .init = tiny4412_wm8960_machine_init,};static struct snd_soc_card myalsa_card = { /*这里面的几个名字又该如何赋值????*/ /*这个名字去掉"_"显示在//sys/devices/platform/soc-audio/sound/card0/id中*/ .name = "TINY4412_UDA8960", .owner = THIS_MODULE, /* * 这里dai_link其实是个数组,num_links是数组项个数, * 这里只有1项,就直接当指针使用了。 */ .dai_link = &tiny4412_wm8960_dai_link, .num_links = 1,};static void asoc_release(struct device * dev){}/* * 驱动端:/sound/soc/soc-core.c, 驱动已经在core中抽象出来了。 * * 注册成平台设备,snd_soc_card最为平台设备的data,在平台设备 * 的驱动端soc-core.c注册。 */static struct platform_device asoc_dev = { /*通过这个名字匹配驱动soc-core.c,公有的驱动*/ .name = "soc-audio", .id = -1, .dev = { .release = asoc_release, },};static int tiny4412_wm8960_init(void){ platform_set_drvdata(&asoc_dev, &myalsa_card); platform_device_register(&asoc_dev); return 0;}static void tiny4412_wm8960_exit(void){ platform_device_unregister(&asoc_dev);}module_init(tiny4412_wm8960_init);module_exit(tiny4412_wm8960_exit);MODULE_LICENSE("GPL");View Code