]>
Commit | Line | Data |
---|---|---|
ba2ff302 CYT |
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 | ||
50aadc14 IZ |
222 | /* mixer controls */ |
223 | static const struct snd_kcontrol_new sun8i_v3s_codec_mixer_controls[] = { | |
224 | SOC_DAPM_DOUBLE_R("DAC Playback Switch", | |
225 | SUN8I_ADDA_LOMIXSC, | |
226 | SUN8I_ADDA_ROMIXSC, | |
227 | SUN8I_ADDA_LOMIXSC_DACL, 1, 0), | |
228 | SOC_DAPM_DOUBLE_R("DAC Reversed Playback Switch", | |
229 | SUN8I_ADDA_LOMIXSC, | |
230 | SUN8I_ADDA_ROMIXSC, | |
231 | SUN8I_ADDA_LOMIXSC_DACR, 1, 0), | |
232 | SOC_DAPM_DOUBLE_R("Mic1 Playback Switch", | |
233 | SUN8I_ADDA_LOMIXSC, | |
234 | SUN8I_ADDA_ROMIXSC, | |
235 | SUN8I_ADDA_LOMIXSC_MIC1, 1, 0), | |
236 | }; | |
237 | ||
ba2ff302 CYT |
238 | /* ADC mixer controls */ |
239 | static const struct snd_kcontrol_new sun8i_codec_adc_mixer_controls[] = { | |
240 | SOC_DAPM_DOUBLE_R("Mixer Capture Switch", | |
241 | SUN8I_ADDA_LADCMIXSC, | |
242 | SUN8I_ADDA_RADCMIXSC, | |
243 | SUN8I_ADDA_LADCMIXSC_OMIXRL, 1, 0), | |
244 | SOC_DAPM_DOUBLE_R("Mixer Reversed Capture Switch", | |
245 | SUN8I_ADDA_LADCMIXSC, | |
246 | SUN8I_ADDA_RADCMIXSC, | |
247 | SUN8I_ADDA_LADCMIXSC_OMIXRR, 1, 0), | |
248 | SOC_DAPM_DOUBLE_R("Line In Capture Switch", | |
249 | SUN8I_ADDA_LADCMIXSC, | |
250 | SUN8I_ADDA_RADCMIXSC, | |
251 | SUN8I_ADDA_LADCMIXSC_LINEINL, 1, 0), | |
252 | SOC_DAPM_DOUBLE_R("Mic1 Capture Switch", | |
253 | SUN8I_ADDA_LADCMIXSC, | |
254 | SUN8I_ADDA_RADCMIXSC, | |
255 | SUN8I_ADDA_LADCMIXSC_MIC1, 1, 0), | |
256 | SOC_DAPM_DOUBLE_R("Mic2 Capture Switch", | |
257 | SUN8I_ADDA_LADCMIXSC, | |
258 | SUN8I_ADDA_RADCMIXSC, | |
259 | SUN8I_ADDA_LADCMIXSC_MIC2, 1, 0), | |
260 | }; | |
261 | ||
50aadc14 IZ |
262 | /* ADC mixer controls */ |
263 | static const struct snd_kcontrol_new sun8i_v3s_codec_adc_mixer_controls[] = { | |
264 | SOC_DAPM_DOUBLE_R("Mixer Capture Switch", | |
265 | SUN8I_ADDA_LADCMIXSC, | |
266 | SUN8I_ADDA_RADCMIXSC, | |
267 | SUN8I_ADDA_LADCMIXSC_OMIXRL, 1, 0), | |
268 | SOC_DAPM_DOUBLE_R("Mixer Reversed Capture Switch", | |
269 | SUN8I_ADDA_LADCMIXSC, | |
270 | SUN8I_ADDA_RADCMIXSC, | |
271 | SUN8I_ADDA_LADCMIXSC_OMIXRR, 1, 0), | |
272 | SOC_DAPM_DOUBLE_R("Mic1 Capture Switch", | |
273 | SUN8I_ADDA_LADCMIXSC, | |
274 | SUN8I_ADDA_RADCMIXSC, | |
275 | SUN8I_ADDA_LADCMIXSC_MIC1, 1, 0), | |
276 | }; | |
277 | ||
ba2ff302 CYT |
278 | /* volume / mute controls */ |
279 | static const DECLARE_TLV_DB_SCALE(sun8i_codec_out_mixer_pregain_scale, | |
280 | -450, 150, 0); | |
281 | static const DECLARE_TLV_DB_RANGE(sun8i_codec_mic_gain_scale, | |
282 | 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0), | |
283 | 1, 7, TLV_DB_SCALE_ITEM(2400, 300, 0), | |
284 | ); | |
285 | ||
286 | static const struct snd_kcontrol_new sun8i_codec_common_controls[] = { | |
6ff4eb7e | 287 | /* Mixer pre-gain */ |
ba2ff302 CYT |
288 | SOC_SINGLE_TLV("Mic1 Playback Volume", SUN8I_ADDA_MICIN_GCTRL, |
289 | SUN8I_ADDA_MICIN_GCTRL_MIC1G, | |
290 | 0x7, 0, sun8i_codec_out_mixer_pregain_scale), | |
ba2ff302 | 291 | |
3f48947d | 292 | /* Microphone Amp boost gain */ |
ba2ff302 CYT |
293 | SOC_SINGLE_TLV("Mic1 Boost Volume", SUN8I_ADDA_MIC1G_MICBIAS_CTRL, |
294 | SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MIC1BOOST, 0x7, 0, | |
295 | sun8i_codec_mic_gain_scale), | |
ba2ff302 CYT |
296 | |
297 | /* ADC */ | |
298 | SOC_SINGLE_TLV("ADC Gain Capture Volume", SUN8I_ADDA_ADC_AP_EN, | |
299 | SUN8I_ADDA_ADC_AP_EN_ADCG, 0x7, 0, | |
300 | sun8i_codec_out_mixer_pregain_scale), | |
301 | }; | |
302 | ||
303 | static const struct snd_soc_dapm_widget sun8i_codec_common_widgets[] = { | |
304 | /* ADC */ | |
305 | SND_SOC_DAPM_ADC("Left ADC", NULL, SUN8I_ADDA_ADC_AP_EN, | |
306 | SUN8I_ADDA_ADC_AP_EN_ADCLEN, 0), | |
307 | SND_SOC_DAPM_ADC("Right ADC", NULL, SUN8I_ADDA_ADC_AP_EN, | |
308 | SUN8I_ADDA_ADC_AP_EN_ADCREN, 0), | |
309 | ||
310 | /* DAC */ | |
311 | SND_SOC_DAPM_DAC("Left DAC", NULL, SUN8I_ADDA_DAC_PA_SRC, | |
312 | SUN8I_ADDA_DAC_PA_SRC_DACALEN, 0), | |
313 | SND_SOC_DAPM_DAC("Right DAC", NULL, SUN8I_ADDA_DAC_PA_SRC, | |
314 | SUN8I_ADDA_DAC_PA_SRC_DACAREN, 0), | |
315 | /* | |
316 | * Due to this component and the codec belonging to separate DAPM | |
317 | * contexts, we need to manually link the above widgets to their | |
318 | * stream widgets at the card level. | |
319 | */ | |
320 | ||
3f48947d | 321 | /* Microphone input */ |
ba2ff302 | 322 | SND_SOC_DAPM_INPUT("MIC1"), |
ba2ff302 | 323 | |
ba2ff302 CYT |
324 | /* Mic input path */ |
325 | SND_SOC_DAPM_PGA("Mic1 Amplifier", SUN8I_ADDA_MIC1G_MICBIAS_CTRL, | |
326 | SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MIC1AMPEN, 0, NULL, 0), | |
50aadc14 | 327 | }; |
ba2ff302 | 328 | |
50aadc14 | 329 | static const struct snd_soc_dapm_widget sun8i_codec_mixer_widgets[] = { |
ba2ff302 CYT |
330 | SND_SOC_DAPM_MIXER("Left Mixer", SUN8I_ADDA_DAC_PA_SRC, |
331 | SUN8I_ADDA_DAC_PA_SRC_LMIXEN, 0, | |
332 | sun8i_codec_mixer_controls, | |
333 | ARRAY_SIZE(sun8i_codec_mixer_controls)), | |
334 | SND_SOC_DAPM_MIXER("Right Mixer", SUN8I_ADDA_DAC_PA_SRC, | |
335 | SUN8I_ADDA_DAC_PA_SRC_RMIXEN, 0, | |
336 | sun8i_codec_mixer_controls, | |
337 | ARRAY_SIZE(sun8i_codec_mixer_controls)), | |
338 | SND_SOC_DAPM_MIXER("Left ADC Mixer", SUN8I_ADDA_ADC_AP_EN, | |
339 | SUN8I_ADDA_ADC_AP_EN_ADCLEN, 0, | |
340 | sun8i_codec_adc_mixer_controls, | |
341 | ARRAY_SIZE(sun8i_codec_adc_mixer_controls)), | |
342 | SND_SOC_DAPM_MIXER("Right ADC Mixer", SUN8I_ADDA_ADC_AP_EN, | |
343 | SUN8I_ADDA_ADC_AP_EN_ADCREN, 0, | |
344 | sun8i_codec_adc_mixer_controls, | |
345 | ARRAY_SIZE(sun8i_codec_adc_mixer_controls)), | |
346 | }; | |
347 | ||
50aadc14 IZ |
348 | static const struct snd_soc_dapm_widget sun8i_v3s_codec_mixer_widgets[] = { |
349 | SND_SOC_DAPM_MIXER("Left Mixer", SUN8I_ADDA_DAC_PA_SRC, | |
350 | SUN8I_ADDA_DAC_PA_SRC_LMIXEN, 0, | |
351 | sun8i_v3s_codec_mixer_controls, | |
352 | ARRAY_SIZE(sun8i_v3s_codec_mixer_controls)), | |
353 | SND_SOC_DAPM_MIXER("Right Mixer", SUN8I_ADDA_DAC_PA_SRC, | |
354 | SUN8I_ADDA_DAC_PA_SRC_RMIXEN, 0, | |
355 | sun8i_v3s_codec_mixer_controls, | |
356 | ARRAY_SIZE(sun8i_v3s_codec_mixer_controls)), | |
357 | SND_SOC_DAPM_MIXER("Left ADC Mixer", SUN8I_ADDA_ADC_AP_EN, | |
358 | SUN8I_ADDA_ADC_AP_EN_ADCLEN, 0, | |
359 | sun8i_v3s_codec_adc_mixer_controls, | |
360 | ARRAY_SIZE(sun8i_v3s_codec_adc_mixer_controls)), | |
361 | SND_SOC_DAPM_MIXER("Right ADC Mixer", SUN8I_ADDA_ADC_AP_EN, | |
362 | SUN8I_ADDA_ADC_AP_EN_ADCREN, 0, | |
363 | sun8i_v3s_codec_adc_mixer_controls, | |
364 | ARRAY_SIZE(sun8i_v3s_codec_adc_mixer_controls)), | |
365 | }; | |
366 | ||
ba2ff302 CYT |
367 | static const struct snd_soc_dapm_route sun8i_codec_common_routes[] = { |
368 | /* Microphone Routes */ | |
369 | { "Mic1 Amplifier", NULL, "MIC1"}, | |
50aadc14 | 370 | }; |
ba2ff302 | 371 | |
50aadc14 | 372 | static const struct snd_soc_dapm_route sun8i_codec_mixer_routes[] = { |
ba2ff302 CYT |
373 | /* Left Mixer Routes */ |
374 | { "Left Mixer", "DAC Playback Switch", "Left DAC" }, | |
375 | { "Left Mixer", "DAC Reversed Playback Switch", "Right DAC" }, | |
ba2ff302 | 376 | { "Left Mixer", "Mic1 Playback Switch", "Mic1 Amplifier" }, |
ba2ff302 CYT |
377 | |
378 | /* Right Mixer Routes */ | |
379 | { "Right Mixer", "DAC Playback Switch", "Right DAC" }, | |
380 | { "Right Mixer", "DAC Reversed Playback Switch", "Left DAC" }, | |
ba2ff302 | 381 | { "Right Mixer", "Mic1 Playback Switch", "Mic1 Amplifier" }, |
ba2ff302 CYT |
382 | |
383 | /* Left ADC Mixer Routes */ | |
384 | { "Left ADC Mixer", "Mixer Capture Switch", "Left Mixer" }, | |
385 | { "Left ADC Mixer", "Mixer Reversed Capture Switch", "Right Mixer" }, | |
ba2ff302 | 386 | { "Left ADC Mixer", "Mic1 Capture Switch", "Mic1 Amplifier" }, |
ba2ff302 CYT |
387 | |
388 | /* Right ADC Mixer Routes */ | |
389 | { "Right ADC Mixer", "Mixer Capture Switch", "Right Mixer" }, | |
390 | { "Right ADC Mixer", "Mixer Reversed Capture Switch", "Left Mixer" }, | |
ba2ff302 | 391 | { "Right ADC Mixer", "Mic1 Capture Switch", "Mic1 Amplifier" }, |
ba2ff302 CYT |
392 | |
393 | /* ADC Routes */ | |
394 | { "Left ADC", NULL, "Left ADC Mixer" }, | |
395 | { "Right ADC", NULL, "Right ADC Mixer" }, | |
396 | }; | |
397 | ||
398 | /* headphone specific controls, widgets, and routes */ | |
399 | static const DECLARE_TLV_DB_SCALE(sun8i_codec_hp_vol_scale, -6300, 100, 1); | |
400 | static const struct snd_kcontrol_new sun8i_codec_headphone_controls[] = { | |
401 | SOC_SINGLE_TLV("Headphone Playback Volume", | |
402 | SUN8I_ADDA_HP_VOLC, | |
403 | SUN8I_ADDA_HP_VOLC_HP_VOL, 0x3f, 0, | |
404 | sun8i_codec_hp_vol_scale), | |
405 | SOC_DOUBLE("Headphone Playback Switch", | |
406 | SUN8I_ADDA_DAC_PA_SRC, | |
407 | SUN8I_ADDA_DAC_PA_SRC_LHPPAMUTE, | |
408 | SUN8I_ADDA_DAC_PA_SRC_RHPPAMUTE, 1, 0), | |
409 | }; | |
410 | ||
411 | static const char * const sun8i_codec_hp_src_enum_text[] = { | |
412 | "DAC", "Mixer", | |
413 | }; | |
414 | ||
415 | static SOC_ENUM_DOUBLE_DECL(sun8i_codec_hp_src_enum, | |
416 | SUN8I_ADDA_DAC_PA_SRC, | |
417 | SUN8I_ADDA_DAC_PA_SRC_LHPIS, | |
418 | SUN8I_ADDA_DAC_PA_SRC_RHPIS, | |
419 | sun8i_codec_hp_src_enum_text); | |
420 | ||
421 | static const struct snd_kcontrol_new sun8i_codec_hp_src[] = { | |
422 | SOC_DAPM_ENUM("Headphone Source Playback Route", | |
423 | sun8i_codec_hp_src_enum), | |
424 | }; | |
425 | ||
bf14da7e MJ |
426 | static int sun8i_headphone_amp_event(struct snd_soc_dapm_widget *w, |
427 | struct snd_kcontrol *k, int event) | |
428 | { | |
429 | struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); | |
430 | ||
431 | if (SND_SOC_DAPM_EVENT_ON(event)) { | |
432 | snd_soc_component_update_bits(component, SUN8I_ADDA_PAEN_HP_CTRL, | |
433 | BIT(SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN), | |
434 | BIT(SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN)); | |
435 | /* | |
436 | * Need a delay to have the amplifier up. 700ms seems the best | |
437 | * compromise between the time to let the amplifier up and the | |
438 | * time not to feel this delay while playing a sound. | |
439 | */ | |
440 | msleep(700); | |
441 | } else if (SND_SOC_DAPM_EVENT_OFF(event)) { | |
442 | snd_soc_component_update_bits(component, SUN8I_ADDA_PAEN_HP_CTRL, | |
443 | BIT(SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN), | |
444 | 0x0); | |
445 | } | |
446 | ||
447 | return 0; | |
448 | } | |
449 | ||
ba2ff302 CYT |
450 | static const struct snd_soc_dapm_widget sun8i_codec_headphone_widgets[] = { |
451 | SND_SOC_DAPM_MUX("Headphone Source Playback Route", | |
452 | SND_SOC_NOPM, 0, 0, sun8i_codec_hp_src), | |
bf14da7e MJ |
453 | SND_SOC_DAPM_OUT_DRV_E("Headphone Amp", SUN8I_ADDA_PAEN_HP_CTRL, |
454 | SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN, 0, NULL, 0, | |
455 | sun8i_headphone_amp_event, | |
456 | SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), | |
ba2ff302 CYT |
457 | SND_SOC_DAPM_SUPPLY("HPCOM Protection", SUN8I_ADDA_PAEN_HP_CTRL, |
458 | SUN8I_ADDA_PAEN_HP_CTRL_COMPTEN, 0, NULL, 0), | |
459 | SND_SOC_DAPM_REG(snd_soc_dapm_supply, "HPCOM", SUN8I_ADDA_PAEN_HP_CTRL, | |
460 | SUN8I_ADDA_PAEN_HP_CTRL_HPCOM_FC, 0x3, 0x3, 0), | |
461 | SND_SOC_DAPM_OUTPUT("HP"), | |
462 | }; | |
463 | ||
464 | static const struct snd_soc_dapm_route sun8i_codec_headphone_routes[] = { | |
465 | { "Headphone Source Playback Route", "DAC", "Left DAC" }, | |
466 | { "Headphone Source Playback Route", "DAC", "Right DAC" }, | |
467 | { "Headphone Source Playback Route", "Mixer", "Left Mixer" }, | |
468 | { "Headphone Source Playback Route", "Mixer", "Right Mixer" }, | |
469 | { "Headphone Amp", NULL, "Headphone Source Playback Route" }, | |
470 | { "HPCOM", NULL, "HPCOM Protection" }, | |
471 | { "HP", NULL, "Headphone Amp" }, | |
472 | }; | |
473 | ||
474 | static int sun8i_codec_add_headphone(struct snd_soc_component *cmpnt) | |
475 | { | |
476 | struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt); | |
477 | struct device *dev = cmpnt->dev; | |
478 | int ret; | |
479 | ||
480 | ret = snd_soc_add_component_controls(cmpnt, | |
481 | sun8i_codec_headphone_controls, | |
482 | ARRAY_SIZE(sun8i_codec_headphone_controls)); | |
483 | if (ret) { | |
484 | dev_err(dev, "Failed to add Headphone controls: %d\n", ret); | |
485 | return ret; | |
486 | } | |
487 | ||
488 | ret = snd_soc_dapm_new_controls(dapm, sun8i_codec_headphone_widgets, | |
489 | ARRAY_SIZE(sun8i_codec_headphone_widgets)); | |
490 | if (ret) { | |
491 | dev_err(dev, "Failed to add Headphone DAPM widgets: %d\n", ret); | |
492 | return ret; | |
493 | } | |
494 | ||
495 | ret = snd_soc_dapm_add_routes(dapm, sun8i_codec_headphone_routes, | |
496 | ARRAY_SIZE(sun8i_codec_headphone_routes)); | |
497 | if (ret) { | |
498 | dev_err(dev, "Failed to add Headphone DAPM routes: %d\n", ret); | |
499 | return ret; | |
500 | } | |
501 | ||
502 | return 0; | |
503 | } | |
504 | ||
b08a20f5 IZ |
505 | /* mbias specific widget */ |
506 | static const struct snd_soc_dapm_widget sun8i_codec_mbias_widgets[] = { | |
507 | SND_SOC_DAPM_SUPPLY("MBIAS", SUN8I_ADDA_MIC1G_MICBIAS_CTRL, | |
508 | SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MMICBIASEN, | |
509 | 0, NULL, 0), | |
510 | }; | |
511 | ||
512 | static int sun8i_codec_add_mbias(struct snd_soc_component *cmpnt) | |
513 | { | |
514 | struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt); | |
515 | struct device *dev = cmpnt->dev; | |
516 | int ret; | |
517 | ||
518 | ret = snd_soc_dapm_new_controls(dapm, sun8i_codec_mbias_widgets, | |
519 | ARRAY_SIZE(sun8i_codec_mbias_widgets)); | |
520 | if (ret) | |
521 | dev_err(dev, "Failed to add MBIAS DAPM widgets: %d\n", ret); | |
522 | ||
523 | return ret; | |
524 | } | |
525 | ||
ba2ff302 CYT |
526 | /* hmic specific widget */ |
527 | static const struct snd_soc_dapm_widget sun8i_codec_hmic_widgets[] = { | |
528 | SND_SOC_DAPM_SUPPLY("HBIAS", SUN8I_ADDA_MIC1G_MICBIAS_CTRL, | |
529 | SUN8I_ADDA_MIC1G_MICBIAS_CTRL_HMICBIASEN, | |
530 | 0, NULL, 0), | |
531 | }; | |
532 | ||
533 | static int sun8i_codec_add_hmic(struct snd_soc_component *cmpnt) | |
534 | { | |
535 | struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt); | |
536 | struct device *dev = cmpnt->dev; | |
537 | int ret; | |
538 | ||
539 | ret = snd_soc_dapm_new_controls(dapm, sun8i_codec_hmic_widgets, | |
540 | ARRAY_SIZE(sun8i_codec_hmic_widgets)); | |
541 | if (ret) | |
542 | dev_err(dev, "Failed to add Mic3 DAPM widgets: %d\n", ret); | |
543 | ||
544 | return ret; | |
545 | } | |
546 | ||
6ff4eb7e IZ |
547 | /* line in specific controls, widgets and rines */ |
548 | static const struct snd_kcontrol_new sun8i_codec_linein_controls[] = { | |
549 | /* Mixer pre-gain */ | |
550 | SOC_SINGLE_TLV("Line In Playback Volume", SUN8I_ADDA_LINEIN_GCTRL, | |
551 | SUN8I_ADDA_LINEIN_GCTRL_LINEING, | |
552 | 0x7, 0, sun8i_codec_out_mixer_pregain_scale), | |
553 | }; | |
554 | ||
555 | static const struct snd_soc_dapm_widget sun8i_codec_linein_widgets[] = { | |
556 | /* Line input */ | |
557 | SND_SOC_DAPM_INPUT("LINEIN"), | |
558 | }; | |
559 | ||
560 | static const struct snd_soc_dapm_route sun8i_codec_linein_routes[] = { | |
561 | { "Left Mixer", "Line In Playback Switch", "LINEIN" }, | |
562 | ||
563 | { "Right Mixer", "Line In Playback Switch", "LINEIN" }, | |
564 | ||
565 | { "Left ADC Mixer", "Line In Capture Switch", "LINEIN" }, | |
566 | ||
567 | { "Right ADC Mixer", "Line In Capture Switch", "LINEIN" }, | |
568 | }; | |
569 | ||
570 | static int sun8i_codec_add_linein(struct snd_soc_component *cmpnt) | |
571 | { | |
572 | struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt); | |
573 | struct device *dev = cmpnt->dev; | |
574 | int ret; | |
575 | ||
576 | ret = snd_soc_add_component_controls(cmpnt, | |
577 | sun8i_codec_linein_controls, | |
578 | ARRAY_SIZE(sun8i_codec_linein_controls)); | |
579 | if (ret) { | |
580 | dev_err(dev, "Failed to add Line In controls: %d\n", ret); | |
581 | return ret; | |
582 | } | |
583 | ||
584 | ret = snd_soc_dapm_new_controls(dapm, sun8i_codec_linein_widgets, | |
585 | ARRAY_SIZE(sun8i_codec_linein_widgets)); | |
586 | if (ret) { | |
587 | dev_err(dev, "Failed to add Line In DAPM widgets: %d\n", ret); | |
588 | return ret; | |
589 | } | |
590 | ||
591 | ret = snd_soc_dapm_add_routes(dapm, sun8i_codec_linein_routes, | |
592 | ARRAY_SIZE(sun8i_codec_linein_routes)); | |
593 | if (ret) { | |
594 | dev_err(dev, "Failed to add Line In DAPM routes: %d\n", ret); | |
595 | return ret; | |
596 | } | |
597 | ||
598 | return 0; | |
599 | } | |
600 | ||
601 | ||
ba2ff302 CYT |
602 | /* line out specific controls, widgets and routes */ |
603 | static const DECLARE_TLV_DB_RANGE(sun8i_codec_lineout_vol_scale, | |
604 | 0, 1, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 1), | |
605 | 2, 31, TLV_DB_SCALE_ITEM(-4350, 150, 0), | |
606 | ); | |
607 | static const struct snd_kcontrol_new sun8i_codec_lineout_controls[] = { | |
608 | SOC_SINGLE_TLV("Line Out Playback Volume", | |
609 | SUN8I_ADDA_PHONE_GAIN_CTRL, | |
610 | SUN8I_ADDA_PHONE_GAIN_CTRL_LINEOUT_VOL, 0x1f, 0, | |
611 | sun8i_codec_lineout_vol_scale), | |
612 | SOC_DOUBLE("Line Out Playback Switch", | |
613 | SUN8I_ADDA_MIC2G_CTRL, | |
614 | SUN8I_ADDA_MIC2G_CTRL_LINEOUTLEN, | |
615 | SUN8I_ADDA_MIC2G_CTRL_LINEOUTREN, 1, 0), | |
616 | }; | |
617 | ||
618 | static const char * const sun8i_codec_lineout_src_enum_text[] = { | |
619 | "Stereo", "Mono Differential", | |
620 | }; | |
621 | ||
622 | static SOC_ENUM_DOUBLE_DECL(sun8i_codec_lineout_src_enum, | |
623 | SUN8I_ADDA_MIC2G_CTRL, | |
624 | SUN8I_ADDA_MIC2G_CTRL_LINEOUTLSRC, | |
625 | SUN8I_ADDA_MIC2G_CTRL_LINEOUTRSRC, | |
626 | sun8i_codec_lineout_src_enum_text); | |
627 | ||
628 | static const struct snd_kcontrol_new sun8i_codec_lineout_src[] = { | |
629 | SOC_DAPM_ENUM("Line Out Source Playback Route", | |
630 | sun8i_codec_lineout_src_enum), | |
631 | }; | |
632 | ||
633 | static const struct snd_soc_dapm_widget sun8i_codec_lineout_widgets[] = { | |
634 | SND_SOC_DAPM_MUX("Line Out Source Playback Route", | |
635 | SND_SOC_NOPM, 0, 0, sun8i_codec_lineout_src), | |
636 | /* It is unclear if this is a buffer or gate, model it as a supply */ | |
637 | SND_SOC_DAPM_SUPPLY("Line Out Enable", SUN8I_ADDA_PAEN_HP_CTRL, | |
638 | SUN8I_ADDA_PAEN_HP_CTRL_LINEOUTEN, 0, NULL, 0), | |
639 | SND_SOC_DAPM_OUTPUT("LINEOUT"), | |
640 | }; | |
641 | ||
642 | static const struct snd_soc_dapm_route sun8i_codec_lineout_routes[] = { | |
643 | { "Line Out Source Playback Route", "Stereo", "Left Mixer" }, | |
644 | { "Line Out Source Playback Route", "Stereo", "Right Mixer" }, | |
645 | { "Line Out Source Playback Route", "Mono Differential", "Left Mixer" }, | |
646 | { "Line Out Source Playback Route", "Mono Differential", "Right Mixer" }, | |
647 | { "LINEOUT", NULL, "Line Out Source Playback Route" }, | |
648 | { "LINEOUT", NULL, "Line Out Enable", }, | |
649 | }; | |
650 | ||
651 | static int sun8i_codec_add_lineout(struct snd_soc_component *cmpnt) | |
652 | { | |
653 | struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt); | |
654 | struct device *dev = cmpnt->dev; | |
655 | int ret; | |
656 | ||
657 | ret = snd_soc_add_component_controls(cmpnt, | |
658 | sun8i_codec_lineout_controls, | |
659 | ARRAY_SIZE(sun8i_codec_lineout_controls)); | |
660 | if (ret) { | |
661 | dev_err(dev, "Failed to add Line Out controls: %d\n", ret); | |
662 | return ret; | |
663 | } | |
664 | ||
665 | ret = snd_soc_dapm_new_controls(dapm, sun8i_codec_lineout_widgets, | |
666 | ARRAY_SIZE(sun8i_codec_lineout_widgets)); | |
667 | if (ret) { | |
668 | dev_err(dev, "Failed to add Line Out DAPM widgets: %d\n", ret); | |
669 | return ret; | |
670 | } | |
671 | ||
672 | ret = snd_soc_dapm_add_routes(dapm, sun8i_codec_lineout_routes, | |
673 | ARRAY_SIZE(sun8i_codec_lineout_routes)); | |
674 | if (ret) { | |
675 | dev_err(dev, "Failed to add Line Out DAPM routes: %d\n", ret); | |
676 | return ret; | |
677 | } | |
678 | ||
679 | return 0; | |
680 | } | |
681 | ||
3f48947d IZ |
682 | /* mic2 specific controls, widgets and routes */ |
683 | static const struct snd_kcontrol_new sun8i_codec_mic2_controls[] = { | |
684 | /* Mixer pre-gain */ | |
685 | SOC_SINGLE_TLV("Mic2 Playback Volume", | |
686 | SUN8I_ADDA_MICIN_GCTRL, SUN8I_ADDA_MICIN_GCTRL_MIC2G, | |
687 | 0x7, 0, sun8i_codec_out_mixer_pregain_scale), | |
688 | ||
689 | /* Microphone Amp boost gain */ | |
690 | SOC_SINGLE_TLV("Mic2 Boost Volume", SUN8I_ADDA_MIC2G_CTRL, | |
691 | SUN8I_ADDA_MIC2G_CTRL_MIC2BOOST, 0x7, 0, | |
692 | sun8i_codec_mic_gain_scale), | |
693 | }; | |
694 | ||
695 | static const struct snd_soc_dapm_widget sun8i_codec_mic2_widgets[] = { | |
696 | /* Microphone input */ | |
697 | SND_SOC_DAPM_INPUT("MIC2"), | |
698 | ||
699 | /* Mic input path */ | |
700 | SND_SOC_DAPM_PGA("Mic2 Amplifier", SUN8I_ADDA_MIC2G_CTRL, | |
701 | SUN8I_ADDA_MIC2G_CTRL_MIC2AMPEN, 0, NULL, 0), | |
702 | }; | |
703 | ||
704 | static const struct snd_soc_dapm_route sun8i_codec_mic2_routes[] = { | |
705 | { "Mic2 Amplifier", NULL, "MIC2"}, | |
706 | ||
707 | { "Left Mixer", "Mic2 Playback Switch", "Mic2 Amplifier" }, | |
708 | ||
709 | { "Right Mixer", "Mic2 Playback Switch", "Mic2 Amplifier" }, | |
710 | ||
711 | { "Left ADC Mixer", "Mic2 Capture Switch", "Mic2 Amplifier" }, | |
712 | ||
713 | { "Right ADC Mixer", "Mic2 Capture Switch", "Mic2 Amplifier" }, | |
714 | }; | |
715 | ||
716 | static int sun8i_codec_add_mic2(struct snd_soc_component *cmpnt) | |
717 | { | |
718 | struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt); | |
719 | struct device *dev = cmpnt->dev; | |
720 | int ret; | |
721 | ||
722 | ret = snd_soc_add_component_controls(cmpnt, | |
723 | sun8i_codec_mic2_controls, | |
724 | ARRAY_SIZE(sun8i_codec_mic2_controls)); | |
725 | if (ret) { | |
726 | dev_err(dev, "Failed to add MIC2 controls: %d\n", ret); | |
727 | return ret; | |
728 | } | |
729 | ||
730 | ret = snd_soc_dapm_new_controls(dapm, sun8i_codec_mic2_widgets, | |
731 | ARRAY_SIZE(sun8i_codec_mic2_widgets)); | |
732 | if (ret) { | |
733 | dev_err(dev, "Failed to add MIC2 DAPM widgets: %d\n", ret); | |
734 | return ret; | |
735 | } | |
736 | ||
737 | ret = snd_soc_dapm_add_routes(dapm, sun8i_codec_mic2_routes, | |
738 | ARRAY_SIZE(sun8i_codec_mic2_routes)); | |
739 | if (ret) { | |
740 | dev_err(dev, "Failed to add MIC2 DAPM routes: %d\n", ret); | |
741 | return ret; | |
742 | } | |
743 | ||
744 | return 0; | |
745 | } | |
746 | ||
ba2ff302 CYT |
747 | struct sun8i_codec_analog_quirks { |
748 | bool has_headphone; | |
749 | bool has_hmic; | |
6ff4eb7e | 750 | bool has_linein; |
ba2ff302 | 751 | bool has_lineout; |
b08a20f5 | 752 | bool has_mbias; |
3f48947d | 753 | bool has_mic2; |
ba2ff302 CYT |
754 | }; |
755 | ||
756 | static const struct sun8i_codec_analog_quirks sun8i_a23_quirks = { | |
757 | .has_headphone = true, | |
758 | .has_hmic = true, | |
6ff4eb7e | 759 | .has_linein = true, |
b08a20f5 | 760 | .has_mbias = true, |
3f48947d | 761 | .has_mic2 = true, |
ba2ff302 CYT |
762 | }; |
763 | ||
764 | static const struct sun8i_codec_analog_quirks sun8i_h3_quirks = { | |
6ff4eb7e | 765 | .has_linein = true, |
ba2ff302 | 766 | .has_lineout = true, |
b08a20f5 | 767 | .has_mbias = true, |
3f48947d | 768 | .has_mic2 = true, |
ba2ff302 CYT |
769 | }; |
770 | ||
50aadc14 IZ |
771 | static int sun8i_codec_analog_add_mixer(struct snd_soc_component *cmpnt, |
772 | const struct sun8i_codec_analog_quirks *quirks) | |
773 | { | |
774 | struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt); | |
775 | struct device *dev = cmpnt->dev; | |
776 | int ret; | |
777 | ||
778 | if (!quirks->has_mic2 && !quirks->has_linein) { | |
779 | /* | |
780 | * Apply the special widget set which has uses a control | |
781 | * without MIC2 and Line In, for SoCs without these. | |
782 | * TODO: not all special cases are supported now, this case | |
783 | * is present because it's the case of V3s. | |
784 | */ | |
785 | ret = snd_soc_dapm_new_controls(dapm, | |
786 | sun8i_v3s_codec_mixer_widgets, | |
787 | ARRAY_SIZE(sun8i_v3s_codec_mixer_widgets)); | |
788 | if (ret) { | |
789 | dev_err(dev, "Failed to add V3s Mixer DAPM widgets: %d\n", ret); | |
790 | return ret; | |
791 | } | |
792 | } else { | |
793 | /* Apply the generic mixer widget set. */ | |
794 | ret = snd_soc_dapm_new_controls(dapm, | |
795 | sun8i_codec_mixer_widgets, | |
796 | ARRAY_SIZE(sun8i_codec_mixer_widgets)); | |
797 | if (ret) { | |
798 | dev_err(dev, "Failed to add Mixer DAPM widgets: %d\n", ret); | |
799 | return ret; | |
800 | } | |
801 | } | |
802 | ||
803 | ret = snd_soc_dapm_add_routes(dapm, sun8i_codec_mixer_routes, | |
804 | ARRAY_SIZE(sun8i_codec_mixer_routes)); | |
805 | if (ret) { | |
806 | dev_err(dev, "Failed to add Mixer DAPM routes: %d\n", ret); | |
807 | return ret; | |
808 | } | |
809 | ||
810 | return 0; | |
811 | } | |
812 | ||
2cfeaec0 IZ |
813 | static const struct sun8i_codec_analog_quirks sun8i_v3s_quirks = { |
814 | .has_headphone = true, | |
815 | .has_hmic = true, | |
816 | }; | |
817 | ||
ba2ff302 CYT |
818 | static int sun8i_codec_analog_cmpnt_probe(struct snd_soc_component *cmpnt) |
819 | { | |
820 | struct device *dev = cmpnt->dev; | |
821 | const struct sun8i_codec_analog_quirks *quirks; | |
822 | int ret; | |
823 | ||
824 | /* | |
825 | * This would never return NULL unless someone directly registers a | |
826 | * platform device matching this driver's name, without specifying a | |
827 | * device tree node. | |
828 | */ | |
829 | quirks = of_device_get_match_data(dev); | |
830 | ||
831 | /* Add controls, widgets, and routes for individual features */ | |
50aadc14 IZ |
832 | ret = sun8i_codec_analog_add_mixer(cmpnt, quirks); |
833 | if (ret) | |
834 | return ret; | |
ba2ff302 CYT |
835 | |
836 | if (quirks->has_headphone) { | |
837 | ret = sun8i_codec_add_headphone(cmpnt); | |
838 | if (ret) | |
839 | return ret; | |
840 | } | |
841 | ||
842 | if (quirks->has_hmic) { | |
999982ef | 843 | ret = sun8i_codec_add_hmic(cmpnt); |
ba2ff302 | 844 | if (ret) |
6ff4eb7e IZ |
845 | return ret; |
846 | } | |
847 | ||
848 | if (quirks->has_linein) { | |
849 | ret = sun8i_codec_add_linein(cmpnt); | |
850 | if (ret) | |
ba2ff302 CYT |
851 | return ret; |
852 | } | |
853 | ||
854 | if (quirks->has_lineout) { | |
855 | ret = sun8i_codec_add_lineout(cmpnt); | |
856 | if (ret) | |
857 | return ret; | |
858 | } | |
859 | ||
b08a20f5 IZ |
860 | if (quirks->has_mbias) { |
861 | ret = sun8i_codec_add_mbias(cmpnt); | |
862 | if (ret) | |
863 | return ret; | |
864 | } | |
865 | ||
3f48947d IZ |
866 | if (quirks->has_mic2) { |
867 | ret = sun8i_codec_add_mic2(cmpnt); | |
868 | if (ret) | |
869 | return ret; | |
870 | } | |
871 | ||
ba2ff302 CYT |
872 | return 0; |
873 | } | |
874 | ||
875 | static const struct snd_soc_component_driver sun8i_codec_analog_cmpnt_drv = { | |
876 | .controls = sun8i_codec_common_controls, | |
877 | .num_controls = ARRAY_SIZE(sun8i_codec_common_controls), | |
878 | .dapm_widgets = sun8i_codec_common_widgets, | |
879 | .num_dapm_widgets = ARRAY_SIZE(sun8i_codec_common_widgets), | |
880 | .dapm_routes = sun8i_codec_common_routes, | |
881 | .num_dapm_routes = ARRAY_SIZE(sun8i_codec_common_routes), | |
882 | .probe = sun8i_codec_analog_cmpnt_probe, | |
883 | }; | |
884 | ||
885 | static const struct of_device_id sun8i_codec_analog_of_match[] = { | |
886 | { | |
887 | .compatible = "allwinner,sun8i-a23-codec-analog", | |
888 | .data = &sun8i_a23_quirks, | |
889 | }, | |
890 | { | |
891 | .compatible = "allwinner,sun8i-h3-codec-analog", | |
892 | .data = &sun8i_h3_quirks, | |
893 | }, | |
2cfeaec0 IZ |
894 | { |
895 | .compatible = "allwinner,sun8i-v3s-codec-analog", | |
896 | .data = &sun8i_v3s_quirks, | |
897 | }, | |
ba2ff302 CYT |
898 | {} |
899 | }; | |
900 | MODULE_DEVICE_TABLE(of, sun8i_codec_analog_of_match); | |
901 | ||
902 | static int sun8i_codec_analog_probe(struct platform_device *pdev) | |
903 | { | |
904 | struct resource *res; | |
905 | struct regmap *regmap; | |
906 | void __iomem *base; | |
907 | ||
908 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
909 | base = devm_ioremap_resource(&pdev->dev, res); | |
910 | if (IS_ERR(base)) { | |
911 | dev_err(&pdev->dev, "Failed to map the registers\n"); | |
912 | return PTR_ERR(base); | |
913 | } | |
914 | ||
915 | regmap = devm_regmap_init(&pdev->dev, NULL, base, &adda_pr_regmap_cfg); | |
916 | if (IS_ERR(regmap)) { | |
917 | dev_err(&pdev->dev, "Failed to create regmap\n"); | |
918 | return PTR_ERR(regmap); | |
919 | } | |
920 | ||
921 | return devm_snd_soc_register_component(&pdev->dev, | |
922 | &sun8i_codec_analog_cmpnt_drv, | |
923 | NULL, 0); | |
924 | } | |
925 | ||
926 | static struct platform_driver sun8i_codec_analog_driver = { | |
927 | .driver = { | |
928 | .name = "sun8i-codec-analog", | |
929 | .of_match_table = sun8i_codec_analog_of_match, | |
930 | }, | |
931 | .probe = sun8i_codec_analog_probe, | |
932 | }; | |
933 | module_platform_driver(sun8i_codec_analog_driver); | |
934 | ||
935 | MODULE_DESCRIPTION("Allwinner internal codec analog controls driver"); | |
936 | MODULE_AUTHOR("Chen-Yu Tsai <wens@csie.org>"); | |
937 | MODULE_LICENSE("GPL"); | |
938 | MODULE_ALIAS("platform:sun8i-codec-analog"); |