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 */
117static 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 */
141static 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 */
157static 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 */
181static 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 */
197static const DECLARE_TLV_DB_SCALE(sun8i_codec_out_mixer_pregain_scale,
198				  -450, 150, 0);
199static 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
204static 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
221static 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
247static 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
266static 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
285static const struct snd_soc_dapm_route sun8i_codec_common_routes[] = {
286	/* Microphone Routes */
287	{ "Mic1 Amplifier", NULL, "MIC1"},
288};
289
290static 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 */
317static const DECLARE_TLV_DB_SCALE(sun8i_codec_hp_vol_scale, -6300, 100, 1);
318static 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
329static const char * const sun8i_codec_hp_src_enum_text[] = {
330	"DAC", "Mixer",
331};
332
333static 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
339static 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
344static 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
368static 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
382static 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
392static 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 */
424static 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
430static 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 */
445static 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
451static 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 */
466static 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
473static const struct snd_soc_dapm_widget sun8i_codec_linein_widgets[] = {
474	/* Line input */
475	SND_SOC_DAPM_INPUT("LINEIN"),
476};
477
478static 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
488static 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 */
521static 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);
525static 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
536static const char * const sun8i_codec_lineout_src_enum_text[] = {
537	"Stereo", "Mono Differential",
538};
539
540static 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
546static 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
551static 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
560static 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
569static 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 */
601static 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
613static 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
622static 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
634static 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
665struct 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
674static 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
682static 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
689static 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
731static const struct sun8i_codec_analog_quirks sun8i_v3s_quirks = {
732	.has_headphone	= true,
733	.has_hmic	= true,
734};
735
736static 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
793static 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
803static 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};
818MODULE_DEVICE_TABLE(of, sun8i_codec_analog_of_match);
819
820static 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
842static 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};
849module_platform_driver(sun8i_codec_analog_driver);
850
851MODULE_DESCRIPTION("Allwinner internal codec analog controls driver");
852MODULE_AUTHOR("Chen-Yu Tsai <wens@csie.org>");
853MODULE_LICENSE("GPL");
854MODULE_ALIAS("platform:sun8i-codec-analog");
855