aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChen-Yu Tsai <wens@csie.org>2016-11-12 01:46:40 -0500
committerMark Brown <broonie@kernel.org>2016-11-22 12:59:27 -0500
commitba2ff3027b5ab4a96b9d2832822311c3ccbf3011 (patch)
treeb4091f4f68b06a4b69f92a29ca41c7170afefd3d
parent837e71847aefd82c903ee0bb2ff2589e70b0808f (diff)
ASoC: sunxi: Add support for A23/A33/H3 codec's analog path controls
The internal codec on A23/A33/H3 is split into 2 parts. The analog path controls are routed through an embedded custom register bus accessed through the PRCM block. The SoCs share a common set of inputs, outputs, and audio paths. The following table lists the differences. ---------------------------------------- | Feature \ SoC | A23 | A33 | H3 | ---------------------------------------- | Headphone | v | v | | ---------------------------------------- | Line Out | | | v | ---------------------------------------- | Phone In/Out | v | v | | ---------------------------------------- Add an ASoC component driver for it. This should be tied to the codec audio card as an auxiliary device. This patch adds the commont paths and controls, and variant specific headphone out and line out. Signed-off-by: Chen-Yu Tsai <wens@csie.org> Signed-off-by: Mark Brown <broonie@kernel.org>
-rw-r--r--sound/soc/sunxi/Kconfig8
-rw-r--r--sound/soc/sunxi/Makefile1
-rw-r--r--sound/soc/sunxi/sun8i-codec-analog.c665
3 files changed, 674 insertions, 0 deletions
diff --git a/sound/soc/sunxi/Kconfig b/sound/soc/sunxi/Kconfig
index dd2368297fd3..6c344e16aca4 100644
--- a/sound/soc/sunxi/Kconfig
+++ b/sound/soc/sunxi/Kconfig
@@ -9,6 +9,14 @@ config SND_SUN4I_CODEC
9 Select Y or M to add support for the Codec embedded in the Allwinner 9 Select Y or M to add support for the Codec embedded in the Allwinner
10 A10 and affiliated SoCs. 10 A10 and affiliated SoCs.
11 11
12config SND_SUN8I_CODEC_ANALOG
13 tristate "Allwinner sun8i Codec Analog Controls Support"
14 depends on MACH_SUN8I || COMPILE_TEST
15 select REGMAP
16 help
17 Say Y or M if you want to add support for the analog controls for
18 the codec embedded in newer Allwinner SoCs.
19
12config SND_SUN4I_I2S 20config SND_SUN4I_I2S
13 tristate "Allwinner A10 I2S Support" 21 tristate "Allwinner A10 I2S Support"
14 select SND_SOC_GENERIC_DMAENGINE_PCM 22 select SND_SOC_GENERIC_DMAENGINE_PCM
diff --git a/sound/soc/sunxi/Makefile b/sound/soc/sunxi/Makefile
index 604c7b842837..241c0df9ca0c 100644
--- a/sound/soc/sunxi/Makefile
+++ b/sound/soc/sunxi/Makefile
@@ -1,3 +1,4 @@
1obj-$(CONFIG_SND_SUN4I_CODEC) += sun4i-codec.o 1obj-$(CONFIG_SND_SUN4I_CODEC) += sun4i-codec.o
2obj-$(CONFIG_SND_SUN4I_I2S) += sun4i-i2s.o 2obj-$(CONFIG_SND_SUN4I_I2S) += sun4i-i2s.o
3obj-$(CONFIG_SND_SUN4I_SPDIF) += sun4i-spdif.o 3obj-$(CONFIG_SND_SUN4I_SPDIF) += sun4i-spdif.o
4obj-$(CONFIG_SND_SUN8I_CODEC_ANALOG) += sun8i-codec-analog.o
diff --git a/sound/soc/sunxi/sun8i-codec-analog.c b/sound/soc/sunxi/sun8i-codec-analog.c
new file mode 100644
index 000000000000..222bbd440b1e
--- /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 */
135static 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
158static 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
187static 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 */
199static 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 */
223static 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 */
247static const DECLARE_TLV_DB_SCALE(sun8i_codec_out_mixer_pregain_scale,
248 -450, 150, 0);
249static 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
254static 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
280static 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
335static 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 */
374static const DECLARE_TLV_DB_SCALE(sun8i_codec_hp_vol_scale, -6300, 100, 1);
375static 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
386static const char * const sun8i_codec_hp_src_enum_text[] = {
387 "DAC", "Mixer",
388};
389
390static 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
396static 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
401static 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
413static 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
423static 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 */
455static 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
461static 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 */
476static 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);
480static 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
491static const char * const sun8i_codec_lineout_src_enum_text[] = {
492 "Stereo", "Mono Differential",
493};
494
495static 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
501static 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
506static 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
515static 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
524static 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
555struct sun8i_codec_analog_quirks {
556 bool has_headphone;
557 bool has_hmic;
558 bool has_lineout;
559};
560
561static const struct sun8i_codec_analog_quirks sun8i_a23_quirks = {
562 .has_headphone = true,
563 .has_hmic = true,
564};
565
566static const struct sun8i_codec_analog_quirks sun8i_h3_quirks = {
567 .has_lineout = true,
568};
569
570static 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 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
606static 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
616static 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};
627MODULE_DEVICE_TABLE(of, sun8i_codec_analog_of_match);
628
629static 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
653static 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};
660module_platform_driver(sun8i_codec_analog_driver);
661
662MODULE_DESCRIPTION("Allwinner internal codec analog controls driver");
663MODULE_AUTHOR("Chen-Yu Tsai <wens@csie.org>");
664MODULE_LICENSE("GPL");
665MODULE_ALIAS("platform:sun8i-codec-analog");