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