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