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