一、應用測試工具的使用
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=8192 Period count: min=2 max=64 PCM 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=16384 Period count: min=2 max=128
(2) tinymix :通過/dev/snd/controlC0節點設置獲取控制信息,進行控件的設置。比如設置鏈路,音量調節等。

# tinymix Mixer name: 'S3C2440_UDA1341' Number of controls: 50 ctl type num name value 0 INT 2 Capture Volume 51 51 1 INT 2 Capture Volume ZC Switch 0 0 2 BOOL 2 Capture Switch Off On 3 INT 2 Playback Volume 255 255 4 INT 2 Headphone Playback Volume 121 121 5 BOOL 2 Headphone Playback ZC Switch Off Off 6 INT 2 Speaker Playback Volume 121 121 7 BOOL 2 Speaker Playback ZC Switch Off Off ....
# 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信息(設備節點)
shell@tiny4412:/system # ls /dev/snd/ -l total 0 crw-rw---- 1 1000 1005 116, 0 Jan 1 12:00 controlC0 crw-rw---- 1 1000 1005 116, 24 Jan 1 12:00 pcmC0D0c crw-rw---- 1 1000 1005 116, 16 Jan 1 12:00 pcmC0D0p crw-rw---- 1 1000 1005 116, 25 Jan 1 12:00 pcmC0D1c crw-rw---- 1 1000 1005 116, 17 Jan 1 12:00 pcmC0D1p crw-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文件信息
shell@tiny4412:/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下的 shell@tiny4412:/sys/kernel/debug/asoc/TINY4412_UDA8960/wm8960-codec.0-001a/dapm # ls HP_L Left Speaker Output Right Boost Mixer HP_R Left Speaker PGA Right DAC LINPUT1 MICB Right Input Mixer LINPUT2 Mic Onboard Right Output Mixer LINPUT3 Mono Output Mixer Right Speaker Output LOUT1 PGA OUT3 Right Speaker PGA Left ADC RINPUT1 SPK_LN Left Boost Mixer RINPUT2 SPK_LP Left DAC RINPUT3 SPK_RN Left Input Mixer ROUT1 PGA SPK_RP Left Output Mixer Right ADC bias_level ② Codec的寄存器信息 shell@tiny4412:/sys/kernel/debug/asoc/TINY4412_UDA8960/wm8960-codec.0-001a # ls cache_only cache_sync codec_reg ③ 為消除pop音的延時時間 /sys/kernel/debug/asoc/TINY4412_UDA8960# ls dapm_pop_time ④ dapm的bias_level /sys/kernel/debug/asoc/TINY4412_UDA8960/dapm # cat bias_level Off ⑤ 系統中所有注冊的Codec,dais和Platform驅動的名字 /sys/kernel/debug/asoc # ls S3C2440_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\n", 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_new SND_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\n", 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\n", 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\n", 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\n"); } else { if (pdata->dres > WM8960_DRES_MAX) { dev_err(codec->dev, "Invalid DRES: %d\n", 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\n", ret); return ret; } ret = wm8960_reset(codec); if (ret < 0) { dev_err(codec->dev, "Failed to issue reset\n"); 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, }; #endif static 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\n", 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");
(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\n", __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!\n"); 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; } /* 參考sound\soc\samsung\s3c24xx_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", /* * 這個名字用於和sound\soc\samsung\I2s.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相關)驅動,指定這個名字是要求 * 使用sound\soc\samsung\dma.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");