1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * This driver supports the analog controls for the internal codec |
4 | * found in Allwinner's A31s, A23, A33 and H3 SoCs. |
5 | * |
6 | * Copyright 2016 Chen-Yu Tsai <wens@csie.org> |
7 | */ |
8 | |
9 | #include <linux/io.h> |
10 | #include <linux/kernel.h> |
11 | #include <linux/module.h> |
12 | #include <linux/of.h> |
13 | #include <linux/platform_device.h> |
14 | #include <linux/regmap.h> |
15 | |
16 | #include <sound/soc.h> |
17 | #include <sound/soc-dapm.h> |
18 | #include <sound/tlv.h> |
19 | |
20 | #include "sun8i-adda-pr-regmap.h" |
21 | |
22 | /* Codec analog control register offsets and bit fields */ |
23 | #define SUN8I_ADDA_HP_VOLC 0x00 |
24 | #define SUN8I_ADDA_HP_VOLC_PA_CLK_GATE 7 |
25 | #define SUN8I_ADDA_HP_VOLC_HP_VOL 0 |
26 | #define SUN8I_ADDA_LOMIXSC 0x01 |
27 | #define SUN8I_ADDA_LOMIXSC_MIC1 6 |
28 | #define SUN8I_ADDA_LOMIXSC_MIC2 5 |
29 | #define SUN8I_ADDA_LOMIXSC_PHONE 4 |
30 | #define SUN8I_ADDA_LOMIXSC_PHONEN 3 |
31 | #define SUN8I_ADDA_LOMIXSC_LINEINL 2 |
32 | #define SUN8I_ADDA_LOMIXSC_DACL 1 |
33 | #define SUN8I_ADDA_LOMIXSC_DACR 0 |
34 | #define SUN8I_ADDA_ROMIXSC 0x02 |
35 | #define SUN8I_ADDA_ROMIXSC_MIC1 6 |
36 | #define SUN8I_ADDA_ROMIXSC_MIC2 5 |
37 | #define SUN8I_ADDA_ROMIXSC_PHONE 4 |
38 | #define SUN8I_ADDA_ROMIXSC_PHONEP 3 |
39 | #define SUN8I_ADDA_ROMIXSC_LINEINR 2 |
40 | #define SUN8I_ADDA_ROMIXSC_DACR 1 |
41 | #define SUN8I_ADDA_ROMIXSC_DACL 0 |
42 | #define SUN8I_ADDA_DAC_PA_SRC 0x03 |
43 | #define SUN8I_ADDA_DAC_PA_SRC_DACAREN 7 |
44 | #define SUN8I_ADDA_DAC_PA_SRC_DACALEN 6 |
45 | #define SUN8I_ADDA_DAC_PA_SRC_RMIXEN 5 |
46 | #define SUN8I_ADDA_DAC_PA_SRC_LMIXEN 4 |
47 | #define SUN8I_ADDA_DAC_PA_SRC_RHPPAMUTE 3 |
48 | #define SUN8I_ADDA_DAC_PA_SRC_LHPPAMUTE 2 |
49 | #define SUN8I_ADDA_DAC_PA_SRC_RHPIS 1 |
50 | #define SUN8I_ADDA_DAC_PA_SRC_LHPIS 0 |
51 | #define SUN8I_ADDA_PHONEIN_GCTRL 0x04 |
52 | #define SUN8I_ADDA_PHONEIN_GCTRL_PHONEPG 4 |
53 | #define SUN8I_ADDA_PHONEIN_GCTRL_PHONENG 0 |
54 | #define SUN8I_ADDA_LINEIN_GCTRL 0x05 |
55 | #define SUN8I_ADDA_LINEIN_GCTRL_LINEING 4 |
56 | #define SUN8I_ADDA_LINEIN_GCTRL_PHONEG 0 |
57 | #define SUN8I_ADDA_MICIN_GCTRL 0x06 |
58 | #define SUN8I_ADDA_MICIN_GCTRL_MIC1G 4 |
59 | #define SUN8I_ADDA_MICIN_GCTRL_MIC2G 0 |
60 | #define SUN8I_ADDA_PAEN_HP_CTRL 0x07 |
61 | #define SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN 7 |
62 | #define SUN8I_ADDA_PAEN_HP_CTRL_LINEOUTEN 7 /* H3 specific */ |
63 | #define SUN8I_ADDA_PAEN_HP_CTRL_HPCOM_FC 5 |
64 | #define SUN8I_ADDA_PAEN_HP_CTRL_COMPTEN 4 |
65 | #define SUN8I_ADDA_PAEN_HP_CTRL_PA_ANTI_POP_CTRL 2 |
66 | #define SUN8I_ADDA_PAEN_HP_CTRL_LTRNMUTE 1 |
67 | #define SUN8I_ADDA_PAEN_HP_CTRL_RTLNMUTE 0 |
68 | #define SUN8I_ADDA_PHONEOUT_CTRL 0x08 |
69 | #define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUTG 5 |
70 | #define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUTEN 4 |
71 | #define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUT_MIC1 3 |
72 | #define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUT_MIC2 2 |
73 | #define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUT_RMIX 1 |
74 | #define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUT_LMIX 0 |
75 | #define SUN8I_ADDA_PHONE_GAIN_CTRL 0x09 |
76 | #define SUN8I_ADDA_PHONE_GAIN_CTRL_LINEOUT_VOL 3 |
77 | #define SUN8I_ADDA_PHONE_GAIN_CTRL_PHONEPREG 0 |
78 | #define SUN8I_ADDA_MIC2G_CTRL 0x0a |
79 | #define SUN8I_ADDA_MIC2G_CTRL_MIC2AMPEN 7 |
80 | #define SUN8I_ADDA_MIC2G_CTRL_MIC2BOOST 4 |
81 | #define SUN8I_ADDA_MIC2G_CTRL_LINEOUTLEN 3 |
82 | #define SUN8I_ADDA_MIC2G_CTRL_LINEOUTREN 2 |
83 | #define SUN8I_ADDA_MIC2G_CTRL_LINEOUTLSRC 1 |
84 | #define SUN8I_ADDA_MIC2G_CTRL_LINEOUTRSRC 0 |
85 | #define SUN8I_ADDA_MIC1G_MICBIAS_CTRL 0x0b |
86 | #define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_HMICBIASEN 7 |
87 | #define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MMICBIASEN 6 |
88 | #define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_HMICBIAS_MODE 5 |
89 | #define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MIC1AMPEN 3 |
90 | #define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MIC1BOOST 0 |
91 | #define SUN8I_ADDA_LADCMIXSC 0x0c |
92 | #define SUN8I_ADDA_LADCMIXSC_MIC1 6 |
93 | #define SUN8I_ADDA_LADCMIXSC_MIC2 5 |
94 | #define SUN8I_ADDA_LADCMIXSC_PHONE 4 |
95 | #define SUN8I_ADDA_LADCMIXSC_PHONEN 3 |
96 | #define SUN8I_ADDA_LADCMIXSC_LINEINL 2 |
97 | #define SUN8I_ADDA_LADCMIXSC_OMIXRL 1 |
98 | #define SUN8I_ADDA_LADCMIXSC_OMIXRR 0 |
99 | #define SUN8I_ADDA_RADCMIXSC 0x0d |
100 | #define SUN8I_ADDA_RADCMIXSC_MIC1 6 |
101 | #define SUN8I_ADDA_RADCMIXSC_MIC2 5 |
102 | #define SUN8I_ADDA_RADCMIXSC_PHONE 4 |
103 | #define SUN8I_ADDA_RADCMIXSC_PHONEP 3 |
104 | #define SUN8I_ADDA_RADCMIXSC_LINEINR 2 |
105 | #define SUN8I_ADDA_RADCMIXSC_OMIXR 1 |
106 | #define SUN8I_ADDA_RADCMIXSC_OMIXL 0 |
107 | #define SUN8I_ADDA_RES 0x0e |
108 | #define SUN8I_ADDA_RES_MMICBIAS_SEL 4 |
109 | #define SUN8I_ADDA_RES_PA_ANTI_POP_CTRL 0 |
110 | #define SUN8I_ADDA_ADC_AP_EN 0x0f |
111 | #define SUN8I_ADDA_ADC_AP_EN_ADCREN 7 |
112 | #define SUN8I_ADDA_ADC_AP_EN_ADCLEN 6 |
113 | #define SUN8I_ADDA_ADC_AP_EN_ADCG 0 |
114 | |
115 | /* mixer controls */ |
116 | static const struct snd_kcontrol_new sun8i_codec_mixer_controls[] = { |
117 | SOC_DAPM_DOUBLE_R("DAC Playback Switch" , |
118 | SUN8I_ADDA_LOMIXSC, |
119 | SUN8I_ADDA_ROMIXSC, |
120 | SUN8I_ADDA_LOMIXSC_DACL, 1, 0), |
121 | SOC_DAPM_DOUBLE_R("DAC Reversed Playback Switch" , |
122 | SUN8I_ADDA_LOMIXSC, |
123 | SUN8I_ADDA_ROMIXSC, |
124 | SUN8I_ADDA_LOMIXSC_DACR, 1, 0), |
125 | SOC_DAPM_DOUBLE_R("Line In Playback Switch" , |
126 | SUN8I_ADDA_LOMIXSC, |
127 | SUN8I_ADDA_ROMIXSC, |
128 | SUN8I_ADDA_LOMIXSC_LINEINL, 1, 0), |
129 | SOC_DAPM_DOUBLE_R("Mic1 Playback Switch" , |
130 | SUN8I_ADDA_LOMIXSC, |
131 | SUN8I_ADDA_ROMIXSC, |
132 | SUN8I_ADDA_LOMIXSC_MIC1, 1, 0), |
133 | SOC_DAPM_DOUBLE_R("Mic2 Playback Switch" , |
134 | SUN8I_ADDA_LOMIXSC, |
135 | SUN8I_ADDA_ROMIXSC, |
136 | SUN8I_ADDA_LOMIXSC_MIC2, 1, 0), |
137 | }; |
138 | |
139 | /* mixer controls */ |
140 | static const struct snd_kcontrol_new sun8i_v3s_codec_mixer_controls[] = { |
141 | SOC_DAPM_DOUBLE_R("DAC Playback Switch" , |
142 | SUN8I_ADDA_LOMIXSC, |
143 | SUN8I_ADDA_ROMIXSC, |
144 | SUN8I_ADDA_LOMIXSC_DACL, 1, 0), |
145 | SOC_DAPM_DOUBLE_R("DAC Reversed Playback Switch" , |
146 | SUN8I_ADDA_LOMIXSC, |
147 | SUN8I_ADDA_ROMIXSC, |
148 | SUN8I_ADDA_LOMIXSC_DACR, 1, 0), |
149 | SOC_DAPM_DOUBLE_R("Mic1 Playback Switch" , |
150 | SUN8I_ADDA_LOMIXSC, |
151 | SUN8I_ADDA_ROMIXSC, |
152 | SUN8I_ADDA_LOMIXSC_MIC1, 1, 0), |
153 | }; |
154 | |
155 | /* ADC mixer controls */ |
156 | static const struct snd_kcontrol_new sun8i_codec_adc_mixer_controls[] = { |
157 | SOC_DAPM_DOUBLE_R("Mixer Capture Switch" , |
158 | SUN8I_ADDA_LADCMIXSC, |
159 | SUN8I_ADDA_RADCMIXSC, |
160 | SUN8I_ADDA_LADCMIXSC_OMIXRL, 1, 0), |
161 | SOC_DAPM_DOUBLE_R("Mixer Reversed Capture Switch" , |
162 | SUN8I_ADDA_LADCMIXSC, |
163 | SUN8I_ADDA_RADCMIXSC, |
164 | SUN8I_ADDA_LADCMIXSC_OMIXRR, 1, 0), |
165 | SOC_DAPM_DOUBLE_R("Line In Capture Switch" , |
166 | SUN8I_ADDA_LADCMIXSC, |
167 | SUN8I_ADDA_RADCMIXSC, |
168 | SUN8I_ADDA_LADCMIXSC_LINEINL, 1, 0), |
169 | SOC_DAPM_DOUBLE_R("Mic1 Capture Switch" , |
170 | SUN8I_ADDA_LADCMIXSC, |
171 | SUN8I_ADDA_RADCMIXSC, |
172 | SUN8I_ADDA_LADCMIXSC_MIC1, 1, 0), |
173 | SOC_DAPM_DOUBLE_R("Mic2 Capture Switch" , |
174 | SUN8I_ADDA_LADCMIXSC, |
175 | SUN8I_ADDA_RADCMIXSC, |
176 | SUN8I_ADDA_LADCMIXSC_MIC2, 1, 0), |
177 | }; |
178 | |
179 | /* ADC mixer controls */ |
180 | static const struct snd_kcontrol_new sun8i_v3s_codec_adc_mixer_controls[] = { |
181 | SOC_DAPM_DOUBLE_R("Mixer Capture Switch" , |
182 | SUN8I_ADDA_LADCMIXSC, |
183 | SUN8I_ADDA_RADCMIXSC, |
184 | SUN8I_ADDA_LADCMIXSC_OMIXRL, 1, 0), |
185 | SOC_DAPM_DOUBLE_R("Mixer Reversed Capture Switch" , |
186 | SUN8I_ADDA_LADCMIXSC, |
187 | SUN8I_ADDA_RADCMIXSC, |
188 | SUN8I_ADDA_LADCMIXSC_OMIXRR, 1, 0), |
189 | SOC_DAPM_DOUBLE_R("Mic1 Capture Switch" , |
190 | SUN8I_ADDA_LADCMIXSC, |
191 | SUN8I_ADDA_RADCMIXSC, |
192 | SUN8I_ADDA_LADCMIXSC_MIC1, 1, 0), |
193 | }; |
194 | |
195 | /* volume / mute controls */ |
196 | static const DECLARE_TLV_DB_SCALE(sun8i_codec_out_mixer_pregain_scale, |
197 | -450, 150, 0); |
198 | static const DECLARE_TLV_DB_RANGE(sun8i_codec_mic_gain_scale, |
199 | 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0), |
200 | 1, 7, TLV_DB_SCALE_ITEM(2400, 300, 0), |
201 | ); |
202 | |
203 | static const struct snd_kcontrol_new sun8i_codec_common_controls[] = { |
204 | /* Mixer pre-gain */ |
205 | SOC_SINGLE_TLV("Mic1 Playback Volume" , SUN8I_ADDA_MICIN_GCTRL, |
206 | SUN8I_ADDA_MICIN_GCTRL_MIC1G, |
207 | 0x7, 0, sun8i_codec_out_mixer_pregain_scale), |
208 | |
209 | /* Microphone Amp boost gain */ |
210 | SOC_SINGLE_TLV("Mic1 Boost Volume" , SUN8I_ADDA_MIC1G_MICBIAS_CTRL, |
211 | SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MIC1BOOST, 0x7, 0, |
212 | sun8i_codec_mic_gain_scale), |
213 | |
214 | /* ADC */ |
215 | SOC_SINGLE_TLV("ADC Gain Capture Volume" , SUN8I_ADDA_ADC_AP_EN, |
216 | SUN8I_ADDA_ADC_AP_EN_ADCG, 0x7, 0, |
217 | sun8i_codec_out_mixer_pregain_scale), |
218 | }; |
219 | |
220 | static const struct snd_soc_dapm_widget sun8i_codec_common_widgets[] = { |
221 | /* ADC */ |
222 | SND_SOC_DAPM_ADC("Left ADC" , NULL, SUN8I_ADDA_ADC_AP_EN, |
223 | SUN8I_ADDA_ADC_AP_EN_ADCLEN, 0), |
224 | SND_SOC_DAPM_ADC("Right ADC" , NULL, SUN8I_ADDA_ADC_AP_EN, |
225 | SUN8I_ADDA_ADC_AP_EN_ADCREN, 0), |
226 | |
227 | /* DAC */ |
228 | SND_SOC_DAPM_DAC("Left DAC" , NULL, SUN8I_ADDA_DAC_PA_SRC, |
229 | SUN8I_ADDA_DAC_PA_SRC_DACALEN, 0), |
230 | SND_SOC_DAPM_DAC("Right DAC" , NULL, SUN8I_ADDA_DAC_PA_SRC, |
231 | SUN8I_ADDA_DAC_PA_SRC_DACAREN, 0), |
232 | /* |
233 | * Due to this component and the codec belonging to separate DAPM |
234 | * contexts, we need to manually link the above widgets to their |
235 | * stream widgets at the card level. |
236 | */ |
237 | |
238 | /* Microphone input */ |
239 | SND_SOC_DAPM_INPUT("MIC1" ), |
240 | |
241 | /* Mic input path */ |
242 | SND_SOC_DAPM_PGA("Mic1 Amplifier" , SUN8I_ADDA_MIC1G_MICBIAS_CTRL, |
243 | SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MIC1AMPEN, 0, NULL, 0), |
244 | }; |
245 | |
246 | static const struct snd_soc_dapm_widget sun8i_codec_mixer_widgets[] = { |
247 | SND_SOC_DAPM_MIXER("Left Mixer" , SUN8I_ADDA_DAC_PA_SRC, |
248 | SUN8I_ADDA_DAC_PA_SRC_LMIXEN, 0, |
249 | sun8i_codec_mixer_controls, |
250 | ARRAY_SIZE(sun8i_codec_mixer_controls)), |
251 | SND_SOC_DAPM_MIXER("Right Mixer" , SUN8I_ADDA_DAC_PA_SRC, |
252 | SUN8I_ADDA_DAC_PA_SRC_RMIXEN, 0, |
253 | sun8i_codec_mixer_controls, |
254 | ARRAY_SIZE(sun8i_codec_mixer_controls)), |
255 | SND_SOC_DAPM_MIXER("Left ADC Mixer" , SUN8I_ADDA_ADC_AP_EN, |
256 | SUN8I_ADDA_ADC_AP_EN_ADCLEN, 0, |
257 | sun8i_codec_adc_mixer_controls, |
258 | ARRAY_SIZE(sun8i_codec_adc_mixer_controls)), |
259 | SND_SOC_DAPM_MIXER("Right ADC Mixer" , SUN8I_ADDA_ADC_AP_EN, |
260 | SUN8I_ADDA_ADC_AP_EN_ADCREN, 0, |
261 | sun8i_codec_adc_mixer_controls, |
262 | ARRAY_SIZE(sun8i_codec_adc_mixer_controls)), |
263 | }; |
264 | |
265 | static const struct snd_soc_dapm_widget sun8i_v3s_codec_mixer_widgets[] = { |
266 | SND_SOC_DAPM_MIXER("Left Mixer" , SUN8I_ADDA_DAC_PA_SRC, |
267 | SUN8I_ADDA_DAC_PA_SRC_LMIXEN, 0, |
268 | sun8i_v3s_codec_mixer_controls, |
269 | ARRAY_SIZE(sun8i_v3s_codec_mixer_controls)), |
270 | SND_SOC_DAPM_MIXER("Right Mixer" , SUN8I_ADDA_DAC_PA_SRC, |
271 | SUN8I_ADDA_DAC_PA_SRC_RMIXEN, 0, |
272 | sun8i_v3s_codec_mixer_controls, |
273 | ARRAY_SIZE(sun8i_v3s_codec_mixer_controls)), |
274 | SND_SOC_DAPM_MIXER("Left ADC Mixer" , SUN8I_ADDA_ADC_AP_EN, |
275 | SUN8I_ADDA_ADC_AP_EN_ADCLEN, 0, |
276 | sun8i_v3s_codec_adc_mixer_controls, |
277 | ARRAY_SIZE(sun8i_v3s_codec_adc_mixer_controls)), |
278 | SND_SOC_DAPM_MIXER("Right ADC Mixer" , SUN8I_ADDA_ADC_AP_EN, |
279 | SUN8I_ADDA_ADC_AP_EN_ADCREN, 0, |
280 | sun8i_v3s_codec_adc_mixer_controls, |
281 | ARRAY_SIZE(sun8i_v3s_codec_adc_mixer_controls)), |
282 | }; |
283 | |
284 | static const struct snd_soc_dapm_route sun8i_codec_common_routes[] = { |
285 | /* Microphone Routes */ |
286 | { "Mic1 Amplifier" , NULL, "MIC1" }, |
287 | }; |
288 | |
289 | static const struct snd_soc_dapm_route sun8i_codec_mixer_routes[] = { |
290 | /* Left Mixer Routes */ |
291 | { "Left Mixer" , "DAC Playback Switch" , "Left DAC" }, |
292 | { "Left Mixer" , "DAC Reversed Playback Switch" , "Right DAC" }, |
293 | { "Left Mixer" , "Mic1 Playback Switch" , "Mic1 Amplifier" }, |
294 | |
295 | /* Right Mixer Routes */ |
296 | { "Right Mixer" , "DAC Playback Switch" , "Right DAC" }, |
297 | { "Right Mixer" , "DAC Reversed Playback Switch" , "Left DAC" }, |
298 | { "Right Mixer" , "Mic1 Playback Switch" , "Mic1 Amplifier" }, |
299 | |
300 | /* Left ADC Mixer Routes */ |
301 | { "Left ADC Mixer" , "Mixer Capture Switch" , "Left Mixer" }, |
302 | { "Left ADC Mixer" , "Mixer Reversed Capture Switch" , "Right Mixer" }, |
303 | { "Left ADC Mixer" , "Mic1 Capture Switch" , "Mic1 Amplifier" }, |
304 | |
305 | /* Right ADC Mixer Routes */ |
306 | { "Right ADC Mixer" , "Mixer Capture Switch" , "Right Mixer" }, |
307 | { "Right ADC Mixer" , "Mixer Reversed Capture Switch" , "Left Mixer" }, |
308 | { "Right ADC Mixer" , "Mic1 Capture Switch" , "Mic1 Amplifier" }, |
309 | |
310 | /* ADC Routes */ |
311 | { "Left ADC" , NULL, "Left ADC Mixer" }, |
312 | { "Right ADC" , NULL, "Right ADC Mixer" }, |
313 | }; |
314 | |
315 | /* headphone specific controls, widgets, and routes */ |
316 | static const DECLARE_TLV_DB_SCALE(sun8i_codec_hp_vol_scale, -6300, 100, 1); |
317 | static const struct snd_kcontrol_new sun8i_codec_headphone_controls[] = { |
318 | SOC_SINGLE_TLV("Headphone Playback Volume" , |
319 | SUN8I_ADDA_HP_VOLC, |
320 | SUN8I_ADDA_HP_VOLC_HP_VOL, 0x3f, 0, |
321 | sun8i_codec_hp_vol_scale), |
322 | SOC_DOUBLE("Headphone Playback Switch" , |
323 | SUN8I_ADDA_DAC_PA_SRC, |
324 | SUN8I_ADDA_DAC_PA_SRC_LHPPAMUTE, |
325 | SUN8I_ADDA_DAC_PA_SRC_RHPPAMUTE, 1, 0), |
326 | }; |
327 | |
328 | static const char * const sun8i_codec_hp_src_enum_text[] = { |
329 | "DAC" , "Mixer" , |
330 | }; |
331 | |
332 | static SOC_ENUM_DOUBLE_DECL(sun8i_codec_hp_src_enum, |
333 | SUN8I_ADDA_DAC_PA_SRC, |
334 | SUN8I_ADDA_DAC_PA_SRC_LHPIS, |
335 | SUN8I_ADDA_DAC_PA_SRC_RHPIS, |
336 | sun8i_codec_hp_src_enum_text); |
337 | |
338 | static const struct snd_kcontrol_new sun8i_codec_hp_src[] = { |
339 | SOC_DAPM_ENUM("Headphone Source Playback Route" , |
340 | sun8i_codec_hp_src_enum), |
341 | }; |
342 | |
343 | static int sun8i_headphone_amp_event(struct snd_soc_dapm_widget *w, |
344 | struct snd_kcontrol *k, int event) |
345 | { |
346 | struct snd_soc_component *component = snd_soc_dapm_to_component(dapm: w->dapm); |
347 | |
348 | if (SND_SOC_DAPM_EVENT_ON(event)) { |
349 | snd_soc_component_update_bits(component, SUN8I_ADDA_PAEN_HP_CTRL, |
350 | BIT(SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN), |
351 | BIT(SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN)); |
352 | /* |
353 | * Need a delay to have the amplifier up. 700ms seems the best |
354 | * compromise between the time to let the amplifier up and the |
355 | * time not to feel this delay while playing a sound. |
356 | */ |
357 | msleep(msecs: 700); |
358 | } else if (SND_SOC_DAPM_EVENT_OFF(event)) { |
359 | snd_soc_component_update_bits(component, SUN8I_ADDA_PAEN_HP_CTRL, |
360 | BIT(SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN), |
361 | val: 0x0); |
362 | } |
363 | |
364 | return 0; |
365 | } |
366 | |
367 | static const struct snd_soc_dapm_widget sun8i_codec_headphone_widgets[] = { |
368 | SND_SOC_DAPM_MUX("Headphone Source Playback Route" , |
369 | SND_SOC_NOPM, 0, 0, sun8i_codec_hp_src), |
370 | SND_SOC_DAPM_OUT_DRV_E("Headphone Amp" , SUN8I_ADDA_PAEN_HP_CTRL, |
371 | SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN, 0, NULL, 0, |
372 | sun8i_headphone_amp_event, |
373 | SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), |
374 | SND_SOC_DAPM_SUPPLY("HPCOM Protection" , SUN8I_ADDA_PAEN_HP_CTRL, |
375 | SUN8I_ADDA_PAEN_HP_CTRL_COMPTEN, 0, NULL, 0), |
376 | SND_SOC_DAPM_REG(snd_soc_dapm_supply, "HPCOM" , SUN8I_ADDA_PAEN_HP_CTRL, |
377 | SUN8I_ADDA_PAEN_HP_CTRL_HPCOM_FC, 0x3, 0x3, 0), |
378 | SND_SOC_DAPM_OUTPUT("HP" ), |
379 | }; |
380 | |
381 | static const struct snd_soc_dapm_route sun8i_codec_headphone_routes[] = { |
382 | { "Headphone Source Playback Route" , "DAC" , "Left DAC" }, |
383 | { "Headphone Source Playback Route" , "DAC" , "Right DAC" }, |
384 | { "Headphone Source Playback Route" , "Mixer" , "Left Mixer" }, |
385 | { "Headphone Source Playback Route" , "Mixer" , "Right Mixer" }, |
386 | { "Headphone Amp" , NULL, "Headphone Source Playback Route" }, |
387 | { "HPCOM" , NULL, "HPCOM Protection" }, |
388 | { "HP" , NULL, "Headphone Amp" }, |
389 | }; |
390 | |
391 | static int sun8i_codec_add_headphone(struct snd_soc_component *cmpnt) |
392 | { |
393 | struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component: cmpnt); |
394 | struct device *dev = cmpnt->dev; |
395 | int ret; |
396 | |
397 | ret = snd_soc_add_component_controls(component: cmpnt, |
398 | controls: sun8i_codec_headphone_controls, |
399 | ARRAY_SIZE(sun8i_codec_headphone_controls)); |
400 | if (ret) { |
401 | dev_err(dev, "Failed to add Headphone controls: %d\n" , ret); |
402 | return ret; |
403 | } |
404 | |
405 | ret = snd_soc_dapm_new_controls(dapm, widget: sun8i_codec_headphone_widgets, |
406 | ARRAY_SIZE(sun8i_codec_headphone_widgets)); |
407 | if (ret) { |
408 | dev_err(dev, "Failed to add Headphone DAPM widgets: %d\n" , ret); |
409 | return ret; |
410 | } |
411 | |
412 | ret = snd_soc_dapm_add_routes(dapm, route: sun8i_codec_headphone_routes, |
413 | ARRAY_SIZE(sun8i_codec_headphone_routes)); |
414 | if (ret) { |
415 | dev_err(dev, "Failed to add Headphone DAPM routes: %d\n" , ret); |
416 | return ret; |
417 | } |
418 | |
419 | return 0; |
420 | } |
421 | |
422 | /* mbias specific widget */ |
423 | static const struct snd_soc_dapm_widget sun8i_codec_mbias_widgets[] = { |
424 | SND_SOC_DAPM_SUPPLY("MBIAS" , SUN8I_ADDA_MIC1G_MICBIAS_CTRL, |
425 | SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MMICBIASEN, |
426 | 0, NULL, 0), |
427 | }; |
428 | |
429 | static int sun8i_codec_add_mbias(struct snd_soc_component *cmpnt) |
430 | { |
431 | struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component: cmpnt); |
432 | struct device *dev = cmpnt->dev; |
433 | int ret; |
434 | |
435 | ret = snd_soc_dapm_new_controls(dapm, widget: sun8i_codec_mbias_widgets, |
436 | ARRAY_SIZE(sun8i_codec_mbias_widgets)); |
437 | if (ret) |
438 | dev_err(dev, "Failed to add MBIAS DAPM widgets: %d\n" , ret); |
439 | |
440 | return ret; |
441 | } |
442 | |
443 | /* hmic specific widget */ |
444 | static const struct snd_soc_dapm_widget sun8i_codec_hmic_widgets[] = { |
445 | SND_SOC_DAPM_SUPPLY("HBIAS" , SUN8I_ADDA_MIC1G_MICBIAS_CTRL, |
446 | SUN8I_ADDA_MIC1G_MICBIAS_CTRL_HMICBIASEN, |
447 | 0, NULL, 0), |
448 | }; |
449 | |
450 | static int sun8i_codec_add_hmic(struct snd_soc_component *cmpnt) |
451 | { |
452 | struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component: cmpnt); |
453 | struct device *dev = cmpnt->dev; |
454 | int ret; |
455 | |
456 | ret = snd_soc_dapm_new_controls(dapm, widget: sun8i_codec_hmic_widgets, |
457 | ARRAY_SIZE(sun8i_codec_hmic_widgets)); |
458 | if (ret) |
459 | dev_err(dev, "Failed to add Mic3 DAPM widgets: %d\n" , ret); |
460 | |
461 | return ret; |
462 | } |
463 | |
464 | /* line in specific controls, widgets and rines */ |
465 | static const struct snd_kcontrol_new sun8i_codec_linein_controls[] = { |
466 | /* Mixer pre-gain */ |
467 | SOC_SINGLE_TLV("Line In Playback Volume" , SUN8I_ADDA_LINEIN_GCTRL, |
468 | SUN8I_ADDA_LINEIN_GCTRL_LINEING, |
469 | 0x7, 0, sun8i_codec_out_mixer_pregain_scale), |
470 | }; |
471 | |
472 | static const struct snd_soc_dapm_widget sun8i_codec_linein_widgets[] = { |
473 | /* Line input */ |
474 | SND_SOC_DAPM_INPUT("LINEIN" ), |
475 | }; |
476 | |
477 | static const struct snd_soc_dapm_route sun8i_codec_linein_routes[] = { |
478 | { "Left Mixer" , "Line In Playback Switch" , "LINEIN" }, |
479 | |
480 | { "Right Mixer" , "Line In Playback Switch" , "LINEIN" }, |
481 | |
482 | { "Left ADC Mixer" , "Line In Capture Switch" , "LINEIN" }, |
483 | |
484 | { "Right ADC Mixer" , "Line In Capture Switch" , "LINEIN" }, |
485 | }; |
486 | |
487 | static int sun8i_codec_add_linein(struct snd_soc_component *cmpnt) |
488 | { |
489 | struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component: cmpnt); |
490 | struct device *dev = cmpnt->dev; |
491 | int ret; |
492 | |
493 | ret = snd_soc_add_component_controls(component: cmpnt, |
494 | controls: sun8i_codec_linein_controls, |
495 | ARRAY_SIZE(sun8i_codec_linein_controls)); |
496 | if (ret) { |
497 | dev_err(dev, "Failed to add Line In controls: %d\n" , ret); |
498 | return ret; |
499 | } |
500 | |
501 | ret = snd_soc_dapm_new_controls(dapm, widget: sun8i_codec_linein_widgets, |
502 | ARRAY_SIZE(sun8i_codec_linein_widgets)); |
503 | if (ret) { |
504 | dev_err(dev, "Failed to add Line In DAPM widgets: %d\n" , ret); |
505 | return ret; |
506 | } |
507 | |
508 | ret = snd_soc_dapm_add_routes(dapm, route: sun8i_codec_linein_routes, |
509 | ARRAY_SIZE(sun8i_codec_linein_routes)); |
510 | if (ret) { |
511 | dev_err(dev, "Failed to add Line In DAPM routes: %d\n" , ret); |
512 | return ret; |
513 | } |
514 | |
515 | return 0; |
516 | } |
517 | |
518 | |
519 | /* line out specific controls, widgets and routes */ |
520 | static const DECLARE_TLV_DB_RANGE(sun8i_codec_lineout_vol_scale, |
521 | 0, 1, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 1), |
522 | 2, 31, TLV_DB_SCALE_ITEM(-4350, 150, 0), |
523 | ); |
524 | static const struct snd_kcontrol_new sun8i_codec_lineout_controls[] = { |
525 | SOC_SINGLE_TLV("Line Out Playback Volume" , |
526 | SUN8I_ADDA_PHONE_GAIN_CTRL, |
527 | SUN8I_ADDA_PHONE_GAIN_CTRL_LINEOUT_VOL, 0x1f, 0, |
528 | sun8i_codec_lineout_vol_scale), |
529 | SOC_DOUBLE("Line Out Playback Switch" , |
530 | SUN8I_ADDA_MIC2G_CTRL, |
531 | SUN8I_ADDA_MIC2G_CTRL_LINEOUTLEN, |
532 | SUN8I_ADDA_MIC2G_CTRL_LINEOUTREN, 1, 0), |
533 | }; |
534 | |
535 | static const char * const sun8i_codec_lineout_src_enum_text[] = { |
536 | "Stereo" , "Mono Differential" , |
537 | }; |
538 | |
539 | static SOC_ENUM_DOUBLE_DECL(sun8i_codec_lineout_src_enum, |
540 | SUN8I_ADDA_MIC2G_CTRL, |
541 | SUN8I_ADDA_MIC2G_CTRL_LINEOUTLSRC, |
542 | SUN8I_ADDA_MIC2G_CTRL_LINEOUTRSRC, |
543 | sun8i_codec_lineout_src_enum_text); |
544 | |
545 | static const struct snd_kcontrol_new sun8i_codec_lineout_src[] = { |
546 | SOC_DAPM_ENUM("Line Out Source Playback Route" , |
547 | sun8i_codec_lineout_src_enum), |
548 | }; |
549 | |
550 | static const struct snd_soc_dapm_widget sun8i_codec_lineout_widgets[] = { |
551 | SND_SOC_DAPM_MUX("Line Out Source Playback Route" , |
552 | SND_SOC_NOPM, 0, 0, sun8i_codec_lineout_src), |
553 | /* It is unclear if this is a buffer or gate, model it as a supply */ |
554 | SND_SOC_DAPM_SUPPLY("Line Out Enable" , SUN8I_ADDA_PAEN_HP_CTRL, |
555 | SUN8I_ADDA_PAEN_HP_CTRL_LINEOUTEN, 0, NULL, 0), |
556 | SND_SOC_DAPM_OUTPUT("LINEOUT" ), |
557 | }; |
558 | |
559 | static const struct snd_soc_dapm_route sun8i_codec_lineout_routes[] = { |
560 | { "Line Out Source Playback Route" , "Stereo" , "Left Mixer" }, |
561 | { "Line Out Source Playback Route" , "Stereo" , "Right Mixer" }, |
562 | { "Line Out Source Playback Route" , "Mono Differential" , "Left Mixer" }, |
563 | { "Line Out Source Playback Route" , "Mono Differential" , "Right Mixer" }, |
564 | { "LINEOUT" , NULL, "Line Out Source Playback Route" }, |
565 | { "LINEOUT" , NULL, "Line Out Enable" , }, |
566 | }; |
567 | |
568 | static int sun8i_codec_add_lineout(struct snd_soc_component *cmpnt) |
569 | { |
570 | struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component: cmpnt); |
571 | struct device *dev = cmpnt->dev; |
572 | int ret; |
573 | |
574 | ret = snd_soc_add_component_controls(component: cmpnt, |
575 | controls: sun8i_codec_lineout_controls, |
576 | ARRAY_SIZE(sun8i_codec_lineout_controls)); |
577 | if (ret) { |
578 | dev_err(dev, "Failed to add Line Out controls: %d\n" , ret); |
579 | return ret; |
580 | } |
581 | |
582 | ret = snd_soc_dapm_new_controls(dapm, widget: sun8i_codec_lineout_widgets, |
583 | ARRAY_SIZE(sun8i_codec_lineout_widgets)); |
584 | if (ret) { |
585 | dev_err(dev, "Failed to add Line Out DAPM widgets: %d\n" , ret); |
586 | return ret; |
587 | } |
588 | |
589 | ret = snd_soc_dapm_add_routes(dapm, route: sun8i_codec_lineout_routes, |
590 | ARRAY_SIZE(sun8i_codec_lineout_routes)); |
591 | if (ret) { |
592 | dev_err(dev, "Failed to add Line Out DAPM routes: %d\n" , ret); |
593 | return ret; |
594 | } |
595 | |
596 | return 0; |
597 | } |
598 | |
599 | /* mic2 specific controls, widgets and routes */ |
600 | static const struct snd_kcontrol_new sun8i_codec_mic2_controls[] = { |
601 | /* Mixer pre-gain */ |
602 | SOC_SINGLE_TLV("Mic2 Playback Volume" , |
603 | SUN8I_ADDA_MICIN_GCTRL, SUN8I_ADDA_MICIN_GCTRL_MIC2G, |
604 | 0x7, 0, sun8i_codec_out_mixer_pregain_scale), |
605 | |
606 | /* Microphone Amp boost gain */ |
607 | SOC_SINGLE_TLV("Mic2 Boost Volume" , SUN8I_ADDA_MIC2G_CTRL, |
608 | SUN8I_ADDA_MIC2G_CTRL_MIC2BOOST, 0x7, 0, |
609 | sun8i_codec_mic_gain_scale), |
610 | }; |
611 | |
612 | static const struct snd_soc_dapm_widget sun8i_codec_mic2_widgets[] = { |
613 | /* Microphone input */ |
614 | SND_SOC_DAPM_INPUT("MIC2" ), |
615 | |
616 | /* Mic input path */ |
617 | SND_SOC_DAPM_PGA("Mic2 Amplifier" , SUN8I_ADDA_MIC2G_CTRL, |
618 | SUN8I_ADDA_MIC2G_CTRL_MIC2AMPEN, 0, NULL, 0), |
619 | }; |
620 | |
621 | static const struct snd_soc_dapm_route sun8i_codec_mic2_routes[] = { |
622 | { "Mic2 Amplifier" , NULL, "MIC2" }, |
623 | |
624 | { "Left Mixer" , "Mic2 Playback Switch" , "Mic2 Amplifier" }, |
625 | |
626 | { "Right Mixer" , "Mic2 Playback Switch" , "Mic2 Amplifier" }, |
627 | |
628 | { "Left ADC Mixer" , "Mic2 Capture Switch" , "Mic2 Amplifier" }, |
629 | |
630 | { "Right ADC Mixer" , "Mic2 Capture Switch" , "Mic2 Amplifier" }, |
631 | }; |
632 | |
633 | static int sun8i_codec_add_mic2(struct snd_soc_component *cmpnt) |
634 | { |
635 | struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component: cmpnt); |
636 | struct device *dev = cmpnt->dev; |
637 | int ret; |
638 | |
639 | ret = snd_soc_add_component_controls(component: cmpnt, |
640 | controls: sun8i_codec_mic2_controls, |
641 | ARRAY_SIZE(sun8i_codec_mic2_controls)); |
642 | if (ret) { |
643 | dev_err(dev, "Failed to add MIC2 controls: %d\n" , ret); |
644 | return ret; |
645 | } |
646 | |
647 | ret = snd_soc_dapm_new_controls(dapm, widget: sun8i_codec_mic2_widgets, |
648 | ARRAY_SIZE(sun8i_codec_mic2_widgets)); |
649 | if (ret) { |
650 | dev_err(dev, "Failed to add MIC2 DAPM widgets: %d\n" , ret); |
651 | return ret; |
652 | } |
653 | |
654 | ret = snd_soc_dapm_add_routes(dapm, route: sun8i_codec_mic2_routes, |
655 | ARRAY_SIZE(sun8i_codec_mic2_routes)); |
656 | if (ret) { |
657 | dev_err(dev, "Failed to add MIC2 DAPM routes: %d\n" , ret); |
658 | return ret; |
659 | } |
660 | |
661 | return 0; |
662 | } |
663 | |
664 | struct sun8i_codec_analog_quirks { |
665 | bool has_headphone; |
666 | bool has_hmic; |
667 | bool has_linein; |
668 | bool has_lineout; |
669 | bool has_mbias; |
670 | bool has_mic2; |
671 | }; |
672 | |
673 | static const struct sun8i_codec_analog_quirks sun8i_a23_quirks = { |
674 | .has_headphone = true, |
675 | .has_hmic = true, |
676 | .has_linein = true, |
677 | .has_mbias = true, |
678 | .has_mic2 = true, |
679 | }; |
680 | |
681 | static const struct sun8i_codec_analog_quirks sun8i_h3_quirks = { |
682 | .has_linein = true, |
683 | .has_lineout = true, |
684 | .has_mbias = true, |
685 | .has_mic2 = true, |
686 | }; |
687 | |
688 | static int sun8i_codec_analog_add_mixer(struct snd_soc_component *cmpnt, |
689 | const struct sun8i_codec_analog_quirks *quirks) |
690 | { |
691 | struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component: cmpnt); |
692 | struct device *dev = cmpnt->dev; |
693 | int ret; |
694 | |
695 | if (!quirks->has_mic2 && !quirks->has_linein) { |
696 | /* |
697 | * Apply the special widget set which has uses a control |
698 | * without MIC2 and Line In, for SoCs without these. |
699 | * TODO: not all special cases are supported now, this case |
700 | * is present because it's the case of V3s. |
701 | */ |
702 | ret = snd_soc_dapm_new_controls(dapm, |
703 | widget: sun8i_v3s_codec_mixer_widgets, |
704 | ARRAY_SIZE(sun8i_v3s_codec_mixer_widgets)); |
705 | if (ret) { |
706 | dev_err(dev, "Failed to add V3s Mixer DAPM widgets: %d\n" , ret); |
707 | return ret; |
708 | } |
709 | } else { |
710 | /* Apply the generic mixer widget set. */ |
711 | ret = snd_soc_dapm_new_controls(dapm, |
712 | widget: sun8i_codec_mixer_widgets, |
713 | ARRAY_SIZE(sun8i_codec_mixer_widgets)); |
714 | if (ret) { |
715 | dev_err(dev, "Failed to add Mixer DAPM widgets: %d\n" , ret); |
716 | return ret; |
717 | } |
718 | } |
719 | |
720 | ret = snd_soc_dapm_add_routes(dapm, route: sun8i_codec_mixer_routes, |
721 | ARRAY_SIZE(sun8i_codec_mixer_routes)); |
722 | if (ret) { |
723 | dev_err(dev, "Failed to add Mixer DAPM routes: %d\n" , ret); |
724 | return ret; |
725 | } |
726 | |
727 | return 0; |
728 | } |
729 | |
730 | static const struct sun8i_codec_analog_quirks sun8i_v3s_quirks = { |
731 | .has_headphone = true, |
732 | .has_hmic = true, |
733 | }; |
734 | |
735 | static int sun8i_codec_analog_cmpnt_probe(struct snd_soc_component *cmpnt) |
736 | { |
737 | struct device *dev = cmpnt->dev; |
738 | const struct sun8i_codec_analog_quirks *quirks; |
739 | int ret; |
740 | |
741 | /* |
742 | * This would never return NULL unless someone directly registers a |
743 | * platform device matching this driver's name, without specifying a |
744 | * device tree node. |
745 | */ |
746 | quirks = of_device_get_match_data(dev); |
747 | |
748 | /* Add controls, widgets, and routes for individual features */ |
749 | ret = sun8i_codec_analog_add_mixer(cmpnt, quirks); |
750 | if (ret) |
751 | return ret; |
752 | |
753 | if (quirks->has_headphone) { |
754 | ret = sun8i_codec_add_headphone(cmpnt); |
755 | if (ret) |
756 | return ret; |
757 | } |
758 | |
759 | if (quirks->has_hmic) { |
760 | ret = sun8i_codec_add_hmic(cmpnt); |
761 | if (ret) |
762 | return ret; |
763 | } |
764 | |
765 | if (quirks->has_linein) { |
766 | ret = sun8i_codec_add_linein(cmpnt); |
767 | if (ret) |
768 | return ret; |
769 | } |
770 | |
771 | if (quirks->has_lineout) { |
772 | ret = sun8i_codec_add_lineout(cmpnt); |
773 | if (ret) |
774 | return ret; |
775 | } |
776 | |
777 | if (quirks->has_mbias) { |
778 | ret = sun8i_codec_add_mbias(cmpnt); |
779 | if (ret) |
780 | return ret; |
781 | } |
782 | |
783 | if (quirks->has_mic2) { |
784 | ret = sun8i_codec_add_mic2(cmpnt); |
785 | if (ret) |
786 | return ret; |
787 | } |
788 | |
789 | return 0; |
790 | } |
791 | |
792 | static const struct snd_soc_component_driver sun8i_codec_analog_cmpnt_drv = { |
793 | .controls = sun8i_codec_common_controls, |
794 | .num_controls = ARRAY_SIZE(sun8i_codec_common_controls), |
795 | .dapm_widgets = sun8i_codec_common_widgets, |
796 | .num_dapm_widgets = ARRAY_SIZE(sun8i_codec_common_widgets), |
797 | .dapm_routes = sun8i_codec_common_routes, |
798 | .num_dapm_routes = ARRAY_SIZE(sun8i_codec_common_routes), |
799 | .probe = sun8i_codec_analog_cmpnt_probe, |
800 | }; |
801 | |
802 | static const struct of_device_id sun8i_codec_analog_of_match[] = { |
803 | { |
804 | .compatible = "allwinner,sun8i-a23-codec-analog" , |
805 | .data = &sun8i_a23_quirks, |
806 | }, |
807 | { |
808 | .compatible = "allwinner,sun8i-h3-codec-analog" , |
809 | .data = &sun8i_h3_quirks, |
810 | }, |
811 | { |
812 | .compatible = "allwinner,sun8i-v3s-codec-analog" , |
813 | .data = &sun8i_v3s_quirks, |
814 | }, |
815 | {} |
816 | }; |
817 | MODULE_DEVICE_TABLE(of, sun8i_codec_analog_of_match); |
818 | |
819 | static int sun8i_codec_analog_probe(struct platform_device *pdev) |
820 | { |
821 | struct regmap *regmap; |
822 | void __iomem *base; |
823 | |
824 | base = devm_platform_ioremap_resource(pdev, index: 0); |
825 | if (IS_ERR(ptr: base)) { |
826 | dev_err(&pdev->dev, "Failed to map the registers\n" ); |
827 | return PTR_ERR(ptr: base); |
828 | } |
829 | |
830 | regmap = sun8i_adda_pr_regmap_init(dev: &pdev->dev, base); |
831 | if (IS_ERR(ptr: regmap)) { |
832 | dev_err(&pdev->dev, "Failed to create regmap\n" ); |
833 | return PTR_ERR(ptr: regmap); |
834 | } |
835 | |
836 | return devm_snd_soc_register_component(dev: &pdev->dev, |
837 | component_driver: &sun8i_codec_analog_cmpnt_drv, |
838 | NULL, num_dai: 0); |
839 | } |
840 | |
841 | static struct platform_driver sun8i_codec_analog_driver = { |
842 | .driver = { |
843 | .name = "sun8i-codec-analog" , |
844 | .of_match_table = sun8i_codec_analog_of_match, |
845 | }, |
846 | .probe = sun8i_codec_analog_probe, |
847 | }; |
848 | module_platform_driver(sun8i_codec_analog_driver); |
849 | |
850 | MODULE_DESCRIPTION("Allwinner internal codec analog controls driver" ); |
851 | MODULE_AUTHOR("Chen-Yu Tsai <wens@csie.org>" ); |
852 | MODULE_LICENSE("GPL" ); |
853 | MODULE_ALIAS("platform:sun8i-codec-analog" ); |
854 | |