xref: /netbsd-src/sys/arch/arm/sunxi/sun8i_codec.c (revision 53b02e147d4ed531c0d2a5ca9b3e8026ba3e99b5)
1 /* $NetBSD: sun8i_codec.c,v 1.9 2021/01/27 03:10:20 thorpej Exp $ */
2 
3 /*-
4  * Copyright (c) 2018 Jared McNeill <jmcneill@invisible.ca>
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
23  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 
29 #include <sys/cdefs.h>
30 __KERNEL_RCSID(0, "$NetBSD: sun8i_codec.c,v 1.9 2021/01/27 03:10:20 thorpej Exp $");
31 
32 #include <sys/param.h>
33 #include <sys/bus.h>
34 #include <sys/cpu.h>
35 #include <sys/device.h>
36 #include <sys/kmem.h>
37 #include <sys/bitops.h>
38 #include <sys/gpio.h>
39 #include <sys/workqueue.h>
40 
41 #include <dev/audio/audio_dai.h>
42 
43 #include <dev/fdt/fdtvar.h>
44 
45 #define	SYSCLK_CTL		0x00c
46 #define	 AIF1CLK_ENA		__BIT(11)
47 #define	 AIF1CLK_SRC		__BITS(9,8)
48 #define	  AIF1CLK_SRC_PLL	2
49 #define	 SYSCLK_ENA		__BIT(3)
50 #define	 SYSCLK_SRC		__BIT(0)
51 
52 #define	MOD_CLK_ENA		0x010
53 #define	MOD_RST_CTL		0x014
54 #define	 MOD_AIF1		__BIT(15)
55 #define	 MOD_ADC		__BIT(3)
56 #define	 MOD_DAC		__BIT(2)
57 
58 #define	SYS_SR_CTRL		0x018
59 #define	 AIF1_FS		__BITS(15,12)
60 #define	  AIF_FS_48KHZ		8
61 
62 #define	AIF1CLK_CTRL		0x040
63 #define	 AIF1_MSTR_MOD		__BIT(15)
64 #define	 AIF1_BCLK_INV		__BIT(14)
65 #define	 AIF1_LRCK_INV		__BIT(13)
66 #define	 AIF1_BCLK_DIV		__BITS(12,9)
67 #define	  AIF1_BCLK_DIV_16	6
68 #define	 AIF1_LRCK_DIV		__BITS(8,6)
69 #define	  AIF1_LRCK_DIV_16	0
70 #define	  AIF1_LRCK_DIV_64	2
71 #define	 AIF1_WORD_SIZ		__BITS(5,4)
72 #define	  AIF1_WORD_SIZ_16	1
73 #define	 AIF1_DATA_FMT		__BITS(3,2)
74 #define	  AIF1_DATA_FMT_I2S	0
75 #define	  AIF1_DATA_FMT_LJ	1
76 #define	  AIF1_DATA_FMT_RJ	2
77 #define	  AIF1_DATA_FMT_DSP	3
78 
79 #define	AIF1_DACDAT_CTRL	0x048
80 #define	 AIF1_DAC0L_ENA		__BIT(15)
81 #define	 AIF1_DAC0R_ENA		__BIT(14)
82 
83 #define	ADC_DIG_CTRL		0x100
84 #define	 ADC_DIG_CTRL_ENAD	__BIT(15)
85 
86 #define	HMIC_CTRL1		0x110
87 #define	 HMIC_CTRL1_N		__BITS(11,8)
88 #define	 HMIC_CTRL1_JACK_IN_IRQ_EN __BIT(4)
89 #define	 HMIC_CTRL1_JACK_OUT_IRQ_EN __BIT(3)
90 #define	 HMIC_CTRL1_MIC_DET_IRQ_EN __BIT(0)
91 
92 #define	HMIC_CTRL2		0x114
93 #define	 HMIC_CTRL2_MDATA_THRES	__BITS(12,8)
94 
95 #define	HMIC_STS		0x118
96 #define	 HMIC_STS_MIC_PRESENT	__BIT(6)
97 #define	 HMIC_STS_JACK_DET_OIRQ	__BIT(4)
98 #define	 HMIC_STS_JACK_DET_IIRQ	__BIT(3)
99 #define	 HMIC_STS_MIC_DET_ST	__BIT(0)
100 
101 #define	DAC_DIG_CTRL		0x120
102 #define	 DAC_DIG_CTRL_ENDA	__BIT(15)
103 
104 #define	DAC_MXR_SRC		0x130
105 #define	 DACL_MXR_SRC		__BITS(15,12)
106 #define	  DACL_MXR_SRC_AIF1_DAC0L 0x8
107 #define	 DACR_MXR_SRC		__BITS(11,8)
108 #define	  DACR_MXR_SRC_AIF1_DAC0R 0x8
109 
110 struct sun8i_codec_softc {
111 	device_t		sc_dev;
112 	bus_space_tag_t		sc_bst;
113 	bus_space_handle_t	sc_bsh;
114 	int			sc_phandle;
115 
116 	struct workqueue	*sc_workq;
117 	struct work		sc_work;
118 
119 	struct audio_dai_device	sc_dai;
120 	audio_dai_tag_t		sc_codec_analog;
121 	int			sc_jackdet_pol;
122 
123 	struct fdtbus_gpio_pin	*sc_pin_pa;
124 
125 	struct clk		*sc_clk_gate;
126 	struct clk		*sc_clk_mod;
127 };
128 
129 #define	RD4(sc, reg)			\
130 	bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg))
131 #define	WR4(sc, reg, val)		\
132 	bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val))
133 
134 static int
135 sun8i_codec_set_port(void *priv, mixer_ctrl_t *mc)
136 {
137 	struct sun8i_codec_softc * const sc = priv;
138 
139 	if (!sc->sc_codec_analog)
140 		return ENXIO;
141 
142 	return audio_dai_set_port(sc->sc_codec_analog, mc);
143 }
144 
145 static int
146 sun8i_codec_get_port(void *priv, mixer_ctrl_t *mc)
147 {
148 	struct sun8i_codec_softc * const sc = priv;
149 
150 	if (!sc->sc_codec_analog)
151 		return ENXIO;
152 
153 	return audio_dai_get_port(sc->sc_codec_analog, mc);
154 }
155 
156 static int
157 sun8i_codec_query_devinfo(void *priv, mixer_devinfo_t *di)
158 {
159 	struct sun8i_codec_softc * const sc = priv;
160 
161 	if (!sc->sc_codec_analog)
162 		return ENXIO;
163 
164 	return audio_dai_query_devinfo(sc->sc_codec_analog, di);
165 }
166 
167 static const struct audio_hw_if sun8i_codec_hw_if = {
168 	.set_port = sun8i_codec_set_port,
169 	.get_port = sun8i_codec_get_port,
170 	.query_devinfo = sun8i_codec_query_devinfo,
171 };
172 
173 static audio_dai_tag_t
174 sun8i_codec_dai_get_tag(device_t dev, const void *data, size_t len)
175 {
176 	struct sun8i_codec_softc * const sc = device_private(dev);
177 
178 	if (len != 4)
179 		return NULL;
180 
181 	return &sc->sc_dai;
182 }
183 
184 static struct fdtbus_dai_controller_func sun8i_codec_dai_funcs = {
185 	.get_tag = sun8i_codec_dai_get_tag
186 };
187 
188 static int
189 sun8i_codec_dai_set_format(audio_dai_tag_t dai, u_int format)
190 {
191 	struct sun8i_codec_softc * const sc = audio_dai_private(dai);
192 	uint32_t val;
193 
194         const u_int fmt = __SHIFTOUT(format, AUDIO_DAI_FORMAT_MASK);
195         const u_int pol = __SHIFTOUT(format, AUDIO_DAI_POLARITY_MASK);
196         const u_int clk = __SHIFTOUT(format, AUDIO_DAI_CLOCK_MASK);
197 
198 	val = RD4(sc, AIF1CLK_CTRL);
199 
200 	val &= ~AIF1_DATA_FMT;
201 	switch (fmt) {
202 	case AUDIO_DAI_FORMAT_I2S:
203 		val |= __SHIFTIN(AIF1_DATA_FMT_I2S, AIF1_DATA_FMT);
204 		break;
205 	case AUDIO_DAI_FORMAT_RJ:
206 		val |= __SHIFTIN(AIF1_DATA_FMT_RJ, AIF1_DATA_FMT);
207 		break;
208 	case AUDIO_DAI_FORMAT_LJ:
209 		val |= __SHIFTIN(AIF1_DATA_FMT_LJ, AIF1_DATA_FMT);
210 		break;
211 	case AUDIO_DAI_FORMAT_DSPA:
212 	case AUDIO_DAI_FORMAT_DSPB:
213 		val |= __SHIFTIN(AIF1_DATA_FMT_DSP, AIF1_DATA_FMT);
214 		break;
215 	default:
216 		return EINVAL;
217 	}
218 
219 	val &= ~(AIF1_BCLK_INV|AIF1_LRCK_INV);
220 	/* Codec LRCK polarity is inverted (datasheet is wrong) */
221 	if (!AUDIO_DAI_POLARITY_F(pol))
222 		val |= AIF1_LRCK_INV;
223 	if (AUDIO_DAI_POLARITY_B(pol))
224 		val |= AIF1_BCLK_INV;
225 
226 	switch (clk) {
227 	case AUDIO_DAI_CLOCK_CBM_CFM:
228 		val &= ~AIF1_MSTR_MOD;	/* codec is master */
229 		break;
230 	case AUDIO_DAI_CLOCK_CBS_CFS:
231 		val |= AIF1_MSTR_MOD;	/* codec is slave */
232 		break;
233 	default:
234 		return EINVAL;
235 	}
236 
237 	val &= ~AIF1_LRCK_DIV;
238 	val |= __SHIFTIN(AIF1_LRCK_DIV_64, AIF1_LRCK_DIV);
239 
240 	val &= ~AIF1_BCLK_DIV;
241 	val |= __SHIFTIN(AIF1_BCLK_DIV_16, AIF1_BCLK_DIV);
242 
243 	WR4(sc, AIF1CLK_CTRL, val);
244 
245 	return 0;
246 }
247 
248 static int
249 sun8i_codec_dai_add_device(audio_dai_tag_t dai, audio_dai_tag_t aux)
250 {
251 	struct sun8i_codec_softc * const sc = audio_dai_private(dai);
252 
253 	if (sc->sc_codec_analog != NULL)
254 		return 0;
255 
256 	sc->sc_codec_analog = aux;
257 
258 	return 0;
259 }
260 
261 static void
262 sun8i_codec_set_jackdet(struct sun8i_codec_softc *sc, bool enable)
263 {
264 	const uint32_t mask =
265 	    HMIC_CTRL1_JACK_IN_IRQ_EN |
266 	    HMIC_CTRL1_JACK_OUT_IRQ_EN |
267 	    HMIC_CTRL1_MIC_DET_IRQ_EN;
268 	uint32_t val;
269 
270 	val = RD4(sc, HMIC_CTRL1);
271 	if (enable)
272 		val |= mask;
273 	else
274 		val &= ~mask;
275 	WR4(sc, HMIC_CTRL1, val);
276 }
277 
278 static int
279 sun8i_codec_intr(void *priv)
280 {
281 	const uint32_t mask =
282 	    HMIC_STS_JACK_DET_OIRQ |
283 	    HMIC_STS_JACK_DET_IIRQ |
284 	    HMIC_STS_MIC_DET_ST;
285 	struct sun8i_codec_softc * const sc = priv;
286 	uint32_t val;
287 
288 	val = RD4(sc, HMIC_STS);
289 	if (val & mask) {
290 		/* Disable jack detect IRQ until work is complete */
291 		sun8i_codec_set_jackdet(sc, false);
292 
293 		/* Schedule pending jack detect task */
294 		workqueue_enqueue(sc->sc_workq, &sc->sc_work, NULL);
295 	}
296 
297 	return 1;
298 }
299 
300 
301 static void
302 sun8i_codec_thread(struct work *wk, void *priv)
303 {
304 	struct sun8i_codec_softc * const sc = priv;
305 	int hpdet = -1, micdet = -1;
306 	uint32_t val;
307 
308 	val = RD4(sc, HMIC_STS);
309 
310 	if (sc->sc_codec_analog) {
311 		if (val & HMIC_STS_JACK_DET_OIRQ)
312 			hpdet = 0 ^ sc->sc_jackdet_pol;
313 		else if (val & HMIC_STS_JACK_DET_IIRQ)
314 			hpdet = 1 ^ sc->sc_jackdet_pol;
315 
316 		if (val & HMIC_STS_MIC_DET_ST)
317 			micdet = !!(val & HMIC_STS_MIC_PRESENT);
318 
319 		if (hpdet != -1) {
320 			audio_dai_jack_detect(sc->sc_codec_analog,
321 			    AUDIO_DAI_JACK_HP, hpdet);
322 		}
323 		if (micdet != -1) {
324 			audio_dai_jack_detect(sc->sc_codec_analog,
325 			    AUDIO_DAI_JACK_MIC, micdet);
326 		}
327 	}
328 
329 	WR4(sc, HMIC_STS, val);
330 
331 	/* Re-enable jack detect IRQ */
332 	sun8i_codec_set_jackdet(sc, true);
333 }
334 
335 static const struct device_compatible_entry compat_data[] = {
336 	{ .compat = "allwinner,sun8i-a33-codec" },
337 	DEVICE_COMPAT_EOL
338 };
339 
340 static int
341 sun8i_codec_match(device_t parent, cfdata_t cf, void *aux)
342 {
343 	struct fdt_attach_args * const faa = aux;
344 
345 	return of_compatible_match(faa->faa_phandle, compat_data);
346 }
347 
348 static void
349 sun8i_codec_attach(device_t parent, device_t self, void *aux)
350 {
351 	struct sun8i_codec_softc * const sc = device_private(self);
352 	struct fdt_attach_args * const faa = aux;
353 	const int phandle = faa->faa_phandle;
354 	char intrstr[128];
355 	bus_addr_t addr;
356 	bus_size_t size;
357 	uint32_t val;
358 	void *ih;
359 
360 	if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
361 		aprint_error(": couldn't get registers\n");
362 		return;
363 	}
364 
365 	if (!fdtbus_intr_str(phandle, 0, intrstr, sizeof(intrstr))) {
366 		aprint_error(": couldn't decode interrupt\n");
367 		return;
368 	}
369 
370 	sc->sc_dev = self;
371 	sc->sc_bst = faa->faa_bst;
372 	if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) {
373 		aprint_error(": couldn't map registers\n");
374 		return;
375 	}
376 	sc->sc_jackdet_pol = 1;
377 
378 	sc->sc_clk_gate = fdtbus_clock_get(phandle, "bus");
379 	sc->sc_clk_mod = fdtbus_clock_get(phandle, "mod");
380 	if (!sc->sc_clk_gate || !sc->sc_clk_mod) {
381 		aprint_error(": couldn't get clocks\n");
382 		return;
383 	}
384 	if (clk_enable(sc->sc_clk_gate) != 0) {
385 		aprint_error(": couldn't enable bus clock\n");
386 		return;
387 	}
388 
389 	sc->sc_phandle = phandle;
390 
391 	aprint_naive("\n");
392 	aprint_normal(": Audio Codec\n");
393 
394 	if (workqueue_create(&sc->sc_workq, "jackdet", sun8i_codec_thread,
395 	    sc, PRI_NONE, IPL_VM, 0) != 0) {
396 		aprint_error_dev(self, "couldn't create jackdet workqueue\n");
397 		return;
398 	}
399 
400 	/* Enable clocks */
401 	val = RD4(sc, SYSCLK_CTL);
402 	val |= AIF1CLK_ENA;
403 	val &= ~AIF1CLK_SRC;
404 	val |= __SHIFTIN(AIF1CLK_SRC_PLL, AIF1CLK_SRC);
405 	val |= SYSCLK_ENA;
406 	val &= ~SYSCLK_SRC;
407 	WR4(sc, SYSCLK_CTL, val);
408 	WR4(sc, MOD_CLK_ENA, MOD_AIF1 | MOD_ADC | MOD_DAC);
409 	WR4(sc, MOD_RST_CTL, MOD_AIF1 | MOD_ADC | MOD_DAC);
410 
411 	/* Enable digital parts */
412 	WR4(sc, DAC_DIG_CTRL, DAC_DIG_CTRL_ENDA);
413 	WR4(sc, ADC_DIG_CTRL, ADC_DIG_CTRL_ENAD);
414 
415 	/* Set AIF1 to 48 kHz */
416 	val = RD4(sc, SYS_SR_CTRL);
417 	val &= ~AIF1_FS;
418 	val |= __SHIFTIN(AIF_FS_48KHZ, AIF1_FS);
419 	WR4(sc, SYS_SR_CTRL, val);
420 
421 	/* Set AIF1 to 16-bit */
422 	val = RD4(sc, AIF1CLK_CTRL);
423 	val &= ~AIF1_WORD_SIZ;
424 	val |= __SHIFTIN(AIF1_WORD_SIZ_16, AIF1_WORD_SIZ);
425 	WR4(sc, AIF1CLK_CTRL, val);
426 
427 	/* Enable AIF1 DAC timelot 0 */
428 	val = RD4(sc, AIF1_DACDAT_CTRL);
429 	val |= AIF1_DAC0L_ENA;
430 	val |= AIF1_DAC0R_ENA;
431 	WR4(sc, AIF1_DACDAT_CTRL, val);
432 
433 	/* DAC mixer source select */
434 	val = RD4(sc, DAC_MXR_SRC);
435 	val &= ~DACL_MXR_SRC;
436 	val |= __SHIFTIN(DACL_MXR_SRC_AIF1_DAC0L, DACL_MXR_SRC);
437 	val &= ~DACR_MXR_SRC;
438 	val |= __SHIFTIN(DACR_MXR_SRC_AIF1_DAC0R, DACR_MXR_SRC);
439 	WR4(sc, DAC_MXR_SRC, val);
440 
441 	/* Enable PA power */
442 	sc->sc_pin_pa = fdtbus_gpio_acquire(phandle, "allwinner,pa-gpios", GPIO_PIN_OUTPUT);
443 	if (sc->sc_pin_pa)
444 		fdtbus_gpio_write(sc->sc_pin_pa, 1);
445 
446 	/* Enable jack detect */
447 	val = RD4(sc, HMIC_CTRL1);
448 	val |= __SHIFTIN(0xff, HMIC_CTRL1_N);
449 	WR4(sc, HMIC_CTRL1, val);
450 
451 	val = RD4(sc, HMIC_CTRL2);
452 	val &= ~HMIC_CTRL2_MDATA_THRES;
453 	val |= __SHIFTIN(0x17, HMIC_CTRL2_MDATA_THRES);
454 	WR4(sc, HMIC_CTRL2, val);
455 
456 	/* Schedule initial jack detect task */
457 	workqueue_enqueue(sc->sc_workq, &sc->sc_work, NULL);
458 
459 	ih = fdtbus_intr_establish_xname(phandle, 0, IPL_VM, FDT_INTR_MPSAFE,
460 	    sun8i_codec_intr, sc, device_xname(self));
461 	if (ih == NULL) {
462 		aprint_error_dev(self, "couldn't establish interrupt on %s\n",
463 		    intrstr);
464 		return;
465 	}
466 	aprint_normal_dev(self, "interrupting on %s\n", intrstr);
467 
468 	sc->sc_dai.dai_set_format = sun8i_codec_dai_set_format;
469 	sc->sc_dai.dai_add_device = sun8i_codec_dai_add_device;
470 	sc->sc_dai.dai_hw_if = &sun8i_codec_hw_if;
471 	sc->sc_dai.dai_dev = self;
472 	sc->sc_dai.dai_priv = sc;
473 	fdtbus_register_dai_controller(self, phandle, &sun8i_codec_dai_funcs);
474 }
475 
476 CFATTACH_DECL_NEW(sun8i_codec, sizeof(struct sun8i_codec_softc),
477     sun8i_codec_match, sun8i_codec_attach, NULL, NULL);
478