1 /*
2  * This driver supports the analog controls for the internal codec
3  * found in Allwinner's A31s, A23, A33 and H3 SoCs.
4  *
5  * Copyright 2016 Chen-Yu Tsai <wens@csie.org>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  */
17 
18 #include <linux/io.h>
19 #include <linux/kernel.h>
20 #include <linux/module.h>
21 #include <linux/of.h>
22 #include <linux/of_device.h>
23 #include <linux/platform_device.h>
24 #include <linux/regmap.h>
25 
26 #include <sound/soc.h>
27 #include <sound/soc-dapm.h>
28 #include <sound/tlv.h>
29 
30 /* Codec analog control register offsets and bit fields */
31 #define SUN8I_ADDA_HP_VOLC		0x00
32 #define SUN8I_ADDA_HP_VOLC_PA_CLK_GATE		7
33 #define SUN8I_ADDA_HP_VOLC_HP_VOL		0
34 #define SUN8I_ADDA_LOMIXSC		0x01
35 #define SUN8I_ADDA_LOMIXSC_MIC1			6
36 #define SUN8I_ADDA_LOMIXSC_MIC2			5
37 #define SUN8I_ADDA_LOMIXSC_PHONE		4
38 #define SUN8I_ADDA_LOMIXSC_PHONEN		3
39 #define SUN8I_ADDA_LOMIXSC_LINEINL		2
40 #define SUN8I_ADDA_LOMIXSC_DACL			1
41 #define SUN8I_ADDA_LOMIXSC_DACR			0
42 #define SUN8I_ADDA_ROMIXSC		0x02
43 #define SUN8I_ADDA_ROMIXSC_MIC1			6
44 #define SUN8I_ADDA_ROMIXSC_MIC2			5
45 #define SUN8I_ADDA_ROMIXSC_PHONE		4
46 #define SUN8I_ADDA_ROMIXSC_PHONEP		3
47 #define SUN8I_ADDA_ROMIXSC_LINEINR		2
48 #define SUN8I_ADDA_ROMIXSC_DACR			1
49 #define SUN8I_ADDA_ROMIXSC_DACL			0
50 #define SUN8I_ADDA_DAC_PA_SRC		0x03
51 #define SUN8I_ADDA_DAC_PA_SRC_DACAREN		7
52 #define SUN8I_ADDA_DAC_PA_SRC_DACALEN		6
53 #define SUN8I_ADDA_DAC_PA_SRC_RMIXEN		5
54 #define SUN8I_ADDA_DAC_PA_SRC_LMIXEN		4
55 #define SUN8I_ADDA_DAC_PA_SRC_RHPPAMUTE		3
56 #define SUN8I_ADDA_DAC_PA_SRC_LHPPAMUTE		2
57 #define SUN8I_ADDA_DAC_PA_SRC_RHPIS		1
58 #define SUN8I_ADDA_DAC_PA_SRC_LHPIS		0
59 #define SUN8I_ADDA_PHONEIN_GCTRL	0x04
60 #define SUN8I_ADDA_PHONEIN_GCTRL_PHONEPG	4
61 #define SUN8I_ADDA_PHONEIN_GCTRL_PHONENG	0
62 #define SUN8I_ADDA_LINEIN_GCTRL		0x05
63 #define SUN8I_ADDA_LINEIN_GCTRL_LINEING		4
64 #define SUN8I_ADDA_LINEIN_GCTRL_PHONEG		0
65 #define SUN8I_ADDA_MICIN_GCTRL		0x06
66 #define SUN8I_ADDA_MICIN_GCTRL_MIC1G		4
67 #define SUN8I_ADDA_MICIN_GCTRL_MIC2G		0
68 #define SUN8I_ADDA_PAEN_HP_CTRL		0x07
69 #define SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN		7
70 #define SUN8I_ADDA_PAEN_HP_CTRL_LINEOUTEN	7	/* H3 specific */
71 #define SUN8I_ADDA_PAEN_HP_CTRL_HPCOM_FC	5
72 #define SUN8I_ADDA_PAEN_HP_CTRL_COMPTEN		4
73 #define SUN8I_ADDA_PAEN_HP_CTRL_PA_ANTI_POP_CTRL	2
74 #define SUN8I_ADDA_PAEN_HP_CTRL_LTRNMUTE	1
75 #define SUN8I_ADDA_PAEN_HP_CTRL_RTLNMUTE	0
76 #define SUN8I_ADDA_PHONEOUT_CTRL	0x08
77 #define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUTG	5
78 #define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUTEN	4
79 #define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUT_MIC1	3
80 #define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUT_MIC2	2
81 #define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUT_RMIX	1
82 #define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUT_LMIX	0
83 #define SUN8I_ADDA_PHONE_GAIN_CTRL	0x09
84 #define SUN8I_ADDA_PHONE_GAIN_CTRL_LINEOUT_VOL	3
85 #define SUN8I_ADDA_PHONE_GAIN_CTRL_PHONEPREG	0
86 #define SUN8I_ADDA_MIC2G_CTRL		0x0a
87 #define SUN8I_ADDA_MIC2G_CTRL_MIC2AMPEN		7
88 #define SUN8I_ADDA_MIC2G_CTRL_MIC2BOOST		4
89 #define SUN8I_ADDA_MIC2G_CTRL_LINEOUTLEN	3
90 #define SUN8I_ADDA_MIC2G_CTRL_LINEOUTREN	2
91 #define SUN8I_ADDA_MIC2G_CTRL_LINEOUTLSRC	1
92 #define SUN8I_ADDA_MIC2G_CTRL_LINEOUTRSRC	0
93 #define SUN8I_ADDA_MIC1G_MICBIAS_CTRL	0x0b
94 #define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_HMICBIASEN	7
95 #define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MMICBIASEN	6
96 #define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_HMICBIAS_MODE	5
97 #define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MIC1AMPEN		3
98 #define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MIC1BOOST		0
99 #define SUN8I_ADDA_LADCMIXSC		0x0c
100 #define SUN8I_ADDA_LADCMIXSC_MIC1		6
101 #define SUN8I_ADDA_LADCMIXSC_MIC2		5
102 #define SUN8I_ADDA_LADCMIXSC_PHONE		4
103 #define SUN8I_ADDA_LADCMIXSC_PHONEN		3
104 #define SUN8I_ADDA_LADCMIXSC_LINEINL		2
105 #define SUN8I_ADDA_LADCMIXSC_OMIXRL		1
106 #define SUN8I_ADDA_LADCMIXSC_OMIXRR		0
107 #define SUN8I_ADDA_RADCMIXSC		0x0d
108 #define SUN8I_ADDA_RADCMIXSC_MIC1		6
109 #define SUN8I_ADDA_RADCMIXSC_MIC2		5
110 #define SUN8I_ADDA_RADCMIXSC_PHONE		4
111 #define SUN8I_ADDA_RADCMIXSC_PHONEP		3
112 #define SUN8I_ADDA_RADCMIXSC_LINEINR		2
113 #define SUN8I_ADDA_RADCMIXSC_OMIXR		1
114 #define SUN8I_ADDA_RADCMIXSC_OMIXL		0
115 #define SUN8I_ADDA_RES			0x0e
116 #define SUN8I_ADDA_RES_MMICBIAS_SEL		4
117 #define SUN8I_ADDA_RES_PA_ANTI_POP_CTRL		0
118 #define SUN8I_ADDA_ADC_AP_EN		0x0f
119 #define SUN8I_ADDA_ADC_AP_EN_ADCREN		7
120 #define SUN8I_ADDA_ADC_AP_EN_ADCLEN		6
121 #define SUN8I_ADDA_ADC_AP_EN_ADCG		0
122 
123 /* Analog control register access bits */
124 #define ADDA_PR			0x0		/* PRCM base + 0x1c0 */
125 #define ADDA_PR_RESET			BIT(28)
126 #define ADDA_PR_WRITE			BIT(24)
127 #define ADDA_PR_ADDR_SHIFT		16
128 #define ADDA_PR_ADDR_MASK		GENMASK(4, 0)
129 #define ADDA_PR_DATA_IN_SHIFT		8
130 #define ADDA_PR_DATA_IN_MASK		GENMASK(7, 0)
131 #define ADDA_PR_DATA_OUT_SHIFT		0
132 #define ADDA_PR_DATA_OUT_MASK		GENMASK(7, 0)
133 
134 /* regmap access bits */
135 static int adda_reg_read(void *context, unsigned int reg, unsigned int *val)
136 {
137 	void __iomem *base = (void __iomem *)context;
138 	u32 tmp;
139 
140 	/* De-assert reset */
141 	writel(readl(base) | ADDA_PR_RESET, base);
142 
143 	/* Clear write bit */
144 	writel(readl(base) & ~ADDA_PR_WRITE, base);
145 
146 	/* Set register address */
147 	tmp = readl(base);
148 	tmp &= ~(ADDA_PR_ADDR_MASK << ADDA_PR_ADDR_SHIFT);
149 	tmp |= (reg & ADDA_PR_ADDR_MASK) << ADDA_PR_ADDR_SHIFT;
150 	writel(tmp, base);
151 
152 	/* Read back value */
153 	*val = readl(base) & ADDA_PR_DATA_OUT_MASK;
154 
155 	return 0;
156 }
157 
158 static int adda_reg_write(void *context, unsigned int reg, unsigned int val)
159 {
160 	void __iomem *base = (void __iomem *)context;
161 	u32 tmp;
162 
163 	/* De-assert reset */
164 	writel(readl(base) | ADDA_PR_RESET, base);
165 
166 	/* Set register address */
167 	tmp = readl(base);
168 	tmp &= ~(ADDA_PR_ADDR_MASK << ADDA_PR_ADDR_SHIFT);
169 	tmp |= (reg & ADDA_PR_ADDR_MASK) << ADDA_PR_ADDR_SHIFT;
170 	writel(tmp, base);
171 
172 	/* Set data to write */
173 	tmp = readl(base);
174 	tmp &= ~(ADDA_PR_DATA_IN_MASK << ADDA_PR_DATA_IN_SHIFT);
175 	tmp |= (val & ADDA_PR_DATA_IN_MASK) << ADDA_PR_DATA_IN_SHIFT;
176 	writel(tmp, base);
177 
178 	/* Set write bit to signal a write */
179 	writel(readl(base) | ADDA_PR_WRITE, base);
180 
181 	/* Clear write bit */
182 	writel(readl(base) & ~ADDA_PR_WRITE, base);
183 
184 	return 0;
185 }
186 
187 static const struct regmap_config adda_pr_regmap_cfg = {
188 	.name		= "adda-pr",
189 	.reg_bits	= 5,
190 	.reg_stride	= 1,
191 	.val_bits	= 8,
192 	.reg_read	= adda_reg_read,
193 	.reg_write	= adda_reg_write,
194 	.fast_io	= true,
195 	.max_register	= 24,
196 };
197 
198 /* mixer controls */
199 static const struct snd_kcontrol_new sun8i_codec_mixer_controls[] = {
200 	SOC_DAPM_DOUBLE_R("DAC Playback Switch",
201 			  SUN8I_ADDA_LOMIXSC,
202 			  SUN8I_ADDA_ROMIXSC,
203 			  SUN8I_ADDA_LOMIXSC_DACL, 1, 0),
204 	SOC_DAPM_DOUBLE_R("DAC Reversed Playback Switch",
205 			  SUN8I_ADDA_LOMIXSC,
206 			  SUN8I_ADDA_ROMIXSC,
207 			  SUN8I_ADDA_LOMIXSC_DACR, 1, 0),
208 	SOC_DAPM_DOUBLE_R("Line In Playback Switch",
209 			  SUN8I_ADDA_LOMIXSC,
210 			  SUN8I_ADDA_ROMIXSC,
211 			  SUN8I_ADDA_LOMIXSC_LINEINL, 1, 0),
212 	SOC_DAPM_DOUBLE_R("Mic1 Playback Switch",
213 			  SUN8I_ADDA_LOMIXSC,
214 			  SUN8I_ADDA_ROMIXSC,
215 			  SUN8I_ADDA_LOMIXSC_MIC1, 1, 0),
216 	SOC_DAPM_DOUBLE_R("Mic2 Playback Switch",
217 			  SUN8I_ADDA_LOMIXSC,
218 			  SUN8I_ADDA_ROMIXSC,
219 			  SUN8I_ADDA_LOMIXSC_MIC2, 1, 0),
220 };
221 
222 /* ADC mixer controls */
223 static const struct snd_kcontrol_new sun8i_codec_adc_mixer_controls[] = {
224 	SOC_DAPM_DOUBLE_R("Mixer Capture Switch",
225 			  SUN8I_ADDA_LADCMIXSC,
226 			  SUN8I_ADDA_RADCMIXSC,
227 			  SUN8I_ADDA_LADCMIXSC_OMIXRL, 1, 0),
228 	SOC_DAPM_DOUBLE_R("Mixer Reversed Capture Switch",
229 			  SUN8I_ADDA_LADCMIXSC,
230 			  SUN8I_ADDA_RADCMIXSC,
231 			  SUN8I_ADDA_LADCMIXSC_OMIXRR, 1, 0),
232 	SOC_DAPM_DOUBLE_R("Line In Capture Switch",
233 			  SUN8I_ADDA_LADCMIXSC,
234 			  SUN8I_ADDA_RADCMIXSC,
235 			  SUN8I_ADDA_LADCMIXSC_LINEINL, 1, 0),
236 	SOC_DAPM_DOUBLE_R("Mic1 Capture Switch",
237 			  SUN8I_ADDA_LADCMIXSC,
238 			  SUN8I_ADDA_RADCMIXSC,
239 			  SUN8I_ADDA_LADCMIXSC_MIC1, 1, 0),
240 	SOC_DAPM_DOUBLE_R("Mic2 Capture Switch",
241 			  SUN8I_ADDA_LADCMIXSC,
242 			  SUN8I_ADDA_RADCMIXSC,
243 			  SUN8I_ADDA_LADCMIXSC_MIC2, 1, 0),
244 };
245 
246 /* volume / mute controls */
247 static const DECLARE_TLV_DB_SCALE(sun8i_codec_out_mixer_pregain_scale,
248 				  -450, 150, 0);
249 static const DECLARE_TLV_DB_RANGE(sun8i_codec_mic_gain_scale,
250 	0, 0, TLV_DB_SCALE_ITEM(0, 0, 0),
251 	1, 7, TLV_DB_SCALE_ITEM(2400, 300, 0),
252 );
253 
254 static const struct snd_kcontrol_new sun8i_codec_common_controls[] = {
255 	/* Mixer pre-gains */
256 	SOC_SINGLE_TLV("Line In Playback Volume", SUN8I_ADDA_LINEIN_GCTRL,
257 		       SUN8I_ADDA_LINEIN_GCTRL_LINEING,
258 		       0x7, 0, sun8i_codec_out_mixer_pregain_scale),
259 	SOC_SINGLE_TLV("Mic1 Playback Volume", SUN8I_ADDA_MICIN_GCTRL,
260 		       SUN8I_ADDA_MICIN_GCTRL_MIC1G,
261 		       0x7, 0, sun8i_codec_out_mixer_pregain_scale),
262 	SOC_SINGLE_TLV("Mic2 Playback Volume",
263 		       SUN8I_ADDA_MICIN_GCTRL, SUN8I_ADDA_MICIN_GCTRL_MIC2G,
264 		       0x7, 0, sun8i_codec_out_mixer_pregain_scale),
265 
266 	/* Microphone Amp boost gains */
267 	SOC_SINGLE_TLV("Mic1 Boost Volume", SUN8I_ADDA_MIC1G_MICBIAS_CTRL,
268 		       SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MIC1BOOST, 0x7, 0,
269 		       sun8i_codec_mic_gain_scale),
270 	SOC_SINGLE_TLV("Mic2 Boost Volume", SUN8I_ADDA_MIC2G_CTRL,
271 		       SUN8I_ADDA_MIC2G_CTRL_MIC2BOOST, 0x7, 0,
272 		       sun8i_codec_mic_gain_scale),
273 
274 	/* ADC */
275 	SOC_SINGLE_TLV("ADC Gain Capture Volume", SUN8I_ADDA_ADC_AP_EN,
276 		       SUN8I_ADDA_ADC_AP_EN_ADCG, 0x7, 0,
277 		       sun8i_codec_out_mixer_pregain_scale),
278 };
279 
280 static const struct snd_soc_dapm_widget sun8i_codec_common_widgets[] = {
281 	/* ADC */
282 	SND_SOC_DAPM_ADC("Left ADC", NULL, SUN8I_ADDA_ADC_AP_EN,
283 			 SUN8I_ADDA_ADC_AP_EN_ADCLEN, 0),
284 	SND_SOC_DAPM_ADC("Right ADC", NULL, SUN8I_ADDA_ADC_AP_EN,
285 			 SUN8I_ADDA_ADC_AP_EN_ADCREN, 0),
286 
287 	/* DAC */
288 	SND_SOC_DAPM_DAC("Left DAC", NULL, SUN8I_ADDA_DAC_PA_SRC,
289 			 SUN8I_ADDA_DAC_PA_SRC_DACALEN, 0),
290 	SND_SOC_DAPM_DAC("Right DAC", NULL, SUN8I_ADDA_DAC_PA_SRC,
291 			 SUN8I_ADDA_DAC_PA_SRC_DACAREN, 0),
292 	/*
293 	 * Due to this component and the codec belonging to separate DAPM
294 	 * contexts, we need to manually link the above widgets to their
295 	 * stream widgets at the card level.
296 	 */
297 
298 	/* Line In */
299 	SND_SOC_DAPM_INPUT("LINEIN"),
300 
301 	/* Microphone inputs */
302 	SND_SOC_DAPM_INPUT("MIC1"),
303 	SND_SOC_DAPM_INPUT("MIC2"),
304 
305 	/* Microphone Bias */
306 	SND_SOC_DAPM_SUPPLY("MBIAS", SUN8I_ADDA_MIC1G_MICBIAS_CTRL,
307 			    SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MMICBIASEN,
308 			    0, NULL, 0),
309 
310 	/* Mic input path */
311 	SND_SOC_DAPM_PGA("Mic1 Amplifier", SUN8I_ADDA_MIC1G_MICBIAS_CTRL,
312 			 SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MIC1AMPEN, 0, NULL, 0),
313 	SND_SOC_DAPM_PGA("Mic2 Amplifier", SUN8I_ADDA_MIC2G_CTRL,
314 			 SUN8I_ADDA_MIC2G_CTRL_MIC2AMPEN, 0, NULL, 0),
315 
316 	/* Mixers */
317 	SND_SOC_DAPM_MIXER("Left Mixer", SUN8I_ADDA_DAC_PA_SRC,
318 			   SUN8I_ADDA_DAC_PA_SRC_LMIXEN, 0,
319 			   sun8i_codec_mixer_controls,
320 			   ARRAY_SIZE(sun8i_codec_mixer_controls)),
321 	SND_SOC_DAPM_MIXER("Right Mixer", SUN8I_ADDA_DAC_PA_SRC,
322 			   SUN8I_ADDA_DAC_PA_SRC_RMIXEN, 0,
323 			   sun8i_codec_mixer_controls,
324 			   ARRAY_SIZE(sun8i_codec_mixer_controls)),
325 	SND_SOC_DAPM_MIXER("Left ADC Mixer", SUN8I_ADDA_ADC_AP_EN,
326 			   SUN8I_ADDA_ADC_AP_EN_ADCLEN, 0,
327 			   sun8i_codec_adc_mixer_controls,
328 			   ARRAY_SIZE(sun8i_codec_adc_mixer_controls)),
329 	SND_SOC_DAPM_MIXER("Right ADC Mixer", SUN8I_ADDA_ADC_AP_EN,
330 			   SUN8I_ADDA_ADC_AP_EN_ADCREN, 0,
331 			   sun8i_codec_adc_mixer_controls,
332 			   ARRAY_SIZE(sun8i_codec_adc_mixer_controls)),
333 };
334 
335 static const struct snd_soc_dapm_route sun8i_codec_common_routes[] = {
336 	/* Microphone Routes */
337 	{ "Mic1 Amplifier", NULL, "MIC1"},
338 	{ "Mic2 Amplifier", NULL, "MIC2"},
339 
340 	/* Left Mixer Routes */
341 	{ "Left Mixer", "DAC Playback Switch", "Left DAC" },
342 	{ "Left Mixer", "DAC Reversed Playback Switch", "Right DAC" },
343 	{ "Left Mixer", "Line In Playback Switch", "LINEIN" },
344 	{ "Left Mixer", "Mic1 Playback Switch", "Mic1 Amplifier" },
345 	{ "Left Mixer", "Mic2 Playback Switch", "Mic2 Amplifier" },
346 
347 	/* Right Mixer Routes */
348 	{ "Right Mixer", "DAC Playback Switch", "Right DAC" },
349 	{ "Right Mixer", "DAC Reversed Playback Switch", "Left DAC" },
350 	{ "Right Mixer", "Line In Playback Switch", "LINEIN" },
351 	{ "Right Mixer", "Mic1 Playback Switch", "Mic1 Amplifier" },
352 	{ "Right Mixer", "Mic2 Playback Switch", "Mic2 Amplifier" },
353 
354 	/* Left ADC Mixer Routes */
355 	{ "Left ADC Mixer", "Mixer Capture Switch", "Left Mixer" },
356 	{ "Left ADC Mixer", "Mixer Reversed Capture Switch", "Right Mixer" },
357 	{ "Left ADC Mixer", "Line In Capture Switch", "LINEIN" },
358 	{ "Left ADC Mixer", "Mic1 Capture Switch", "Mic1 Amplifier" },
359 	{ "Left ADC Mixer", "Mic2 Capture Switch", "Mic2 Amplifier" },
360 
361 	/* Right ADC Mixer Routes */
362 	{ "Right ADC Mixer", "Mixer Capture Switch", "Right Mixer" },
363 	{ "Right ADC Mixer", "Mixer Reversed Capture Switch", "Left Mixer" },
364 	{ "Right ADC Mixer", "Line In Capture Switch", "LINEIN" },
365 	{ "Right ADC Mixer", "Mic1 Capture Switch", "Mic1 Amplifier" },
366 	{ "Right ADC Mixer", "Mic2 Capture Switch", "Mic2 Amplifier" },
367 
368 	/* ADC Routes */
369 	{ "Left ADC", NULL, "Left ADC Mixer" },
370 	{ "Right ADC", NULL, "Right ADC Mixer" },
371 };
372 
373 /* headphone specific controls, widgets, and routes */
374 static const DECLARE_TLV_DB_SCALE(sun8i_codec_hp_vol_scale, -6300, 100, 1);
375 static const struct snd_kcontrol_new sun8i_codec_headphone_controls[] = {
376 	SOC_SINGLE_TLV("Headphone Playback Volume",
377 		       SUN8I_ADDA_HP_VOLC,
378 		       SUN8I_ADDA_HP_VOLC_HP_VOL, 0x3f, 0,
379 		       sun8i_codec_hp_vol_scale),
380 	SOC_DOUBLE("Headphone Playback Switch",
381 		   SUN8I_ADDA_DAC_PA_SRC,
382 		   SUN8I_ADDA_DAC_PA_SRC_LHPPAMUTE,
383 		   SUN8I_ADDA_DAC_PA_SRC_RHPPAMUTE, 1, 0),
384 };
385 
386 static const char * const sun8i_codec_hp_src_enum_text[] = {
387 	"DAC", "Mixer",
388 };
389 
390 static SOC_ENUM_DOUBLE_DECL(sun8i_codec_hp_src_enum,
391 			    SUN8I_ADDA_DAC_PA_SRC,
392 			    SUN8I_ADDA_DAC_PA_SRC_LHPIS,
393 			    SUN8I_ADDA_DAC_PA_SRC_RHPIS,
394 			    sun8i_codec_hp_src_enum_text);
395 
396 static const struct snd_kcontrol_new sun8i_codec_hp_src[] = {
397 	SOC_DAPM_ENUM("Headphone Source Playback Route",
398 		      sun8i_codec_hp_src_enum),
399 };
400 
401 static const struct snd_soc_dapm_widget sun8i_codec_headphone_widgets[] = {
402 	SND_SOC_DAPM_MUX("Headphone Source Playback Route",
403 			 SND_SOC_NOPM, 0, 0, sun8i_codec_hp_src),
404 	SND_SOC_DAPM_OUT_DRV("Headphone Amp", SUN8I_ADDA_PAEN_HP_CTRL,
405 			     SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN, 0, NULL, 0),
406 	SND_SOC_DAPM_SUPPLY("HPCOM Protection", SUN8I_ADDA_PAEN_HP_CTRL,
407 			    SUN8I_ADDA_PAEN_HP_CTRL_COMPTEN, 0, NULL, 0),
408 	SND_SOC_DAPM_REG(snd_soc_dapm_supply, "HPCOM", SUN8I_ADDA_PAEN_HP_CTRL,
409 			 SUN8I_ADDA_PAEN_HP_CTRL_HPCOM_FC, 0x3, 0x3, 0),
410 	SND_SOC_DAPM_OUTPUT("HP"),
411 };
412 
413 static const struct snd_soc_dapm_route sun8i_codec_headphone_routes[] = {
414 	{ "Headphone Source Playback Route", "DAC", "Left DAC" },
415 	{ "Headphone Source Playback Route", "DAC", "Right DAC" },
416 	{ "Headphone Source Playback Route", "Mixer", "Left Mixer" },
417 	{ "Headphone Source Playback Route", "Mixer", "Right Mixer" },
418 	{ "Headphone Amp", NULL, "Headphone Source Playback Route" },
419 	{ "HPCOM", NULL, "HPCOM Protection" },
420 	{ "HP", NULL, "Headphone Amp" },
421 };
422 
423 static int sun8i_codec_add_headphone(struct snd_soc_component *cmpnt)
424 {
425 	struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt);
426 	struct device *dev = cmpnt->dev;
427 	int ret;
428 
429 	ret = snd_soc_add_component_controls(cmpnt,
430 					     sun8i_codec_headphone_controls,
431 					     ARRAY_SIZE(sun8i_codec_headphone_controls));
432 	if (ret) {
433 		dev_err(dev, "Failed to add Headphone controls: %d\n", ret);
434 		return ret;
435 	}
436 
437 	ret = snd_soc_dapm_new_controls(dapm, sun8i_codec_headphone_widgets,
438 					ARRAY_SIZE(sun8i_codec_headphone_widgets));
439 	if (ret) {
440 		dev_err(dev, "Failed to add Headphone DAPM widgets: %d\n", ret);
441 		return ret;
442 	}
443 
444 	ret = snd_soc_dapm_add_routes(dapm, sun8i_codec_headphone_routes,
445 				      ARRAY_SIZE(sun8i_codec_headphone_routes));
446 	if (ret) {
447 		dev_err(dev, "Failed to add Headphone DAPM routes: %d\n", ret);
448 		return ret;
449 	}
450 
451 	return 0;
452 }
453 
454 /* hmic specific widget */
455 static const struct snd_soc_dapm_widget sun8i_codec_hmic_widgets[] = {
456 	SND_SOC_DAPM_SUPPLY("HBIAS", SUN8I_ADDA_MIC1G_MICBIAS_CTRL,
457 			    SUN8I_ADDA_MIC1G_MICBIAS_CTRL_HMICBIASEN,
458 			    0, NULL, 0),
459 };
460 
461 static int sun8i_codec_add_hmic(struct snd_soc_component *cmpnt)
462 {
463 	struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt);
464 	struct device *dev = cmpnt->dev;
465 	int ret;
466 
467 	ret = snd_soc_dapm_new_controls(dapm, sun8i_codec_hmic_widgets,
468 					ARRAY_SIZE(sun8i_codec_hmic_widgets));
469 	if (ret)
470 		dev_err(dev, "Failed to add Mic3 DAPM widgets: %d\n", ret);
471 
472 	return ret;
473 }
474 
475 /* line out specific controls, widgets and routes */
476 static const DECLARE_TLV_DB_RANGE(sun8i_codec_lineout_vol_scale,
477 	0, 1, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 1),
478 	2, 31, TLV_DB_SCALE_ITEM(-4350, 150, 0),
479 );
480 static const struct snd_kcontrol_new sun8i_codec_lineout_controls[] = {
481 	SOC_SINGLE_TLV("Line Out Playback Volume",
482 		       SUN8I_ADDA_PHONE_GAIN_CTRL,
483 		       SUN8I_ADDA_PHONE_GAIN_CTRL_LINEOUT_VOL, 0x1f, 0,
484 		       sun8i_codec_lineout_vol_scale),
485 	SOC_DOUBLE("Line Out Playback Switch",
486 		   SUN8I_ADDA_MIC2G_CTRL,
487 		   SUN8I_ADDA_MIC2G_CTRL_LINEOUTLEN,
488 		   SUN8I_ADDA_MIC2G_CTRL_LINEOUTREN, 1, 0),
489 };
490 
491 static const char * const sun8i_codec_lineout_src_enum_text[] = {
492 	"Stereo", "Mono Differential",
493 };
494 
495 static SOC_ENUM_DOUBLE_DECL(sun8i_codec_lineout_src_enum,
496 			    SUN8I_ADDA_MIC2G_CTRL,
497 			    SUN8I_ADDA_MIC2G_CTRL_LINEOUTLSRC,
498 			    SUN8I_ADDA_MIC2G_CTRL_LINEOUTRSRC,
499 			    sun8i_codec_lineout_src_enum_text);
500 
501 static const struct snd_kcontrol_new sun8i_codec_lineout_src[] = {
502 	SOC_DAPM_ENUM("Line Out Source Playback Route",
503 		      sun8i_codec_lineout_src_enum),
504 };
505 
506 static const struct snd_soc_dapm_widget sun8i_codec_lineout_widgets[] = {
507 	SND_SOC_DAPM_MUX("Line Out Source Playback Route",
508 			 SND_SOC_NOPM, 0, 0, sun8i_codec_lineout_src),
509 	/* It is unclear if this is a buffer or gate, model it as a supply */
510 	SND_SOC_DAPM_SUPPLY("Line Out Enable", SUN8I_ADDA_PAEN_HP_CTRL,
511 			    SUN8I_ADDA_PAEN_HP_CTRL_LINEOUTEN, 0, NULL, 0),
512 	SND_SOC_DAPM_OUTPUT("LINEOUT"),
513 };
514 
515 static const struct snd_soc_dapm_route sun8i_codec_lineout_routes[] = {
516 	{ "Line Out Source Playback Route", "Stereo", "Left Mixer" },
517 	{ "Line Out Source Playback Route", "Stereo", "Right Mixer" },
518 	{ "Line Out Source Playback Route", "Mono Differential", "Left Mixer" },
519 	{ "Line Out Source Playback Route", "Mono Differential", "Right Mixer" },
520 	{ "LINEOUT", NULL, "Line Out Source Playback Route" },
521 	{ "LINEOUT", NULL, "Line Out Enable", },
522 };
523 
524 static int sun8i_codec_add_lineout(struct snd_soc_component *cmpnt)
525 {
526 	struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt);
527 	struct device *dev = cmpnt->dev;
528 	int ret;
529 
530 	ret = snd_soc_add_component_controls(cmpnt,
531 					     sun8i_codec_lineout_controls,
532 					     ARRAY_SIZE(sun8i_codec_lineout_controls));
533 	if (ret) {
534 		dev_err(dev, "Failed to add Line Out controls: %d\n", ret);
535 		return ret;
536 	}
537 
538 	ret = snd_soc_dapm_new_controls(dapm, sun8i_codec_lineout_widgets,
539 					ARRAY_SIZE(sun8i_codec_lineout_widgets));
540 	if (ret) {
541 		dev_err(dev, "Failed to add Line Out DAPM widgets: %d\n", ret);
542 		return ret;
543 	}
544 
545 	ret = snd_soc_dapm_add_routes(dapm, sun8i_codec_lineout_routes,
546 				      ARRAY_SIZE(sun8i_codec_lineout_routes));
547 	if (ret) {
548 		dev_err(dev, "Failed to add Line Out DAPM routes: %d\n", ret);
549 		return ret;
550 	}
551 
552 	return 0;
553 }
554 
555 struct sun8i_codec_analog_quirks {
556 	bool has_headphone;
557 	bool has_hmic;
558 	bool has_lineout;
559 };
560 
561 static const struct sun8i_codec_analog_quirks sun8i_a23_quirks = {
562 	.has_headphone	= true,
563 	.has_hmic	= true,
564 };
565 
566 static const struct sun8i_codec_analog_quirks sun8i_h3_quirks = {
567 	.has_lineout	= true,
568 };
569 
570 static int sun8i_codec_analog_cmpnt_probe(struct snd_soc_component *cmpnt)
571 {
572 	struct device *dev = cmpnt->dev;
573 	const struct sun8i_codec_analog_quirks *quirks;
574 	int ret;
575 
576 	/*
577 	 * This would never return NULL unless someone directly registers a
578 	 * platform device matching this driver's name, without specifying a
579 	 * device tree node.
580 	 */
581 	quirks = of_device_get_match_data(dev);
582 
583 	/* Add controls, widgets, and routes for individual features */
584 
585 	if (quirks->has_headphone) {
586 		ret = sun8i_codec_add_headphone(cmpnt);
587 		if (ret)
588 			return ret;
589 	}
590 
591 	if (quirks->has_hmic) {
592 		ret = sun8i_codec_add_hmic(cmpnt);
593 		if (ret)
594 			return ret;
595 	}
596 
597 	if (quirks->has_lineout) {
598 		ret = sun8i_codec_add_lineout(cmpnt);
599 		if (ret)
600 			return ret;
601 	}
602 
603 	return 0;
604 }
605 
606 static const struct snd_soc_component_driver sun8i_codec_analog_cmpnt_drv = {
607 	.controls		= sun8i_codec_common_controls,
608 	.num_controls		= ARRAY_SIZE(sun8i_codec_common_controls),
609 	.dapm_widgets		= sun8i_codec_common_widgets,
610 	.num_dapm_widgets	= ARRAY_SIZE(sun8i_codec_common_widgets),
611 	.dapm_routes		= sun8i_codec_common_routes,
612 	.num_dapm_routes	= ARRAY_SIZE(sun8i_codec_common_routes),
613 	.probe			= sun8i_codec_analog_cmpnt_probe,
614 };
615 
616 static const struct of_device_id sun8i_codec_analog_of_match[] = {
617 	{
618 		.compatible = "allwinner,sun8i-a23-codec-analog",
619 		.data = &sun8i_a23_quirks,
620 	},
621 	{
622 		.compatible = "allwinner,sun8i-h3-codec-analog",
623 		.data = &sun8i_h3_quirks,
624 	},
625 	{}
626 };
627 MODULE_DEVICE_TABLE(of, sun8i_codec_analog_of_match);
628 
629 static int sun8i_codec_analog_probe(struct platform_device *pdev)
630 {
631 	struct resource *res;
632 	struct regmap *regmap;
633 	void __iomem *base;
634 
635 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
636 	base = devm_ioremap_resource(&pdev->dev, res);
637 	if (IS_ERR(base)) {
638 		dev_err(&pdev->dev, "Failed to map the registers\n");
639 		return PTR_ERR(base);
640 	}
641 
642 	regmap = devm_regmap_init(&pdev->dev, NULL, base, &adda_pr_regmap_cfg);
643 	if (IS_ERR(regmap)) {
644 		dev_err(&pdev->dev, "Failed to create regmap\n");
645 		return PTR_ERR(regmap);
646 	}
647 
648 	return devm_snd_soc_register_component(&pdev->dev,
649 					       &sun8i_codec_analog_cmpnt_drv,
650 					       NULL, 0);
651 }
652 
653 static struct platform_driver sun8i_codec_analog_driver = {
654 	.driver = {
655 		.name = "sun8i-codec-analog",
656 		.of_match_table = sun8i_codec_analog_of_match,
657 	},
658 	.probe = sun8i_codec_analog_probe,
659 };
660 module_platform_driver(sun8i_codec_analog_driver);
661 
662 MODULE_DESCRIPTION("Allwinner internal codec analog controls driver");
663 MODULE_AUTHOR("Chen-Yu Tsai <wens@csie.org>");
664 MODULE_LICENSE("GPL");
665 MODULE_ALIAS("platform:sun8i-codec-analog");
666