1/* $NetBSD: sun8i_h3_codec.c,v 1.5 2021/01/27 03:10:20 thorpej Exp $ */
2
3/*-
4 * Copyright (c) 2014-2017 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_h3_codec.c,v 1.5 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
39#include <sys/audioio.h>
40#include <dev/audio/audio_if.h>
41
42#include <arm/sunxi/sunxi_codec.h>
43
44#define	H3_PR_CFG		0x00
45#define	 H3_AC_PR_RST		__BIT(28)
46#define	 H3_AC_PR_RW		__BIT(24)
47#define	 H3_AC_PR_ADDR		__BITS(20,16)
48#define	 H3_ACDA_PR_WDAT	__BITS(15,8)
49#define	 H3_ACDA_PR_RDAT	__BITS(7,0)
50
51#define	H3_LOMIXSC		0x01
52#define	 H3_LOMIXSC_LDAC	__BIT(1)
53#define	H3_ROMIXSC		0x02
54#define	 H3_ROMIXSC_RDAC	__BIT(1)
55#define	H3_DAC_PA_SRC		0x03
56#define	 H3_DACAREN		__BIT(7)
57#define	 H3_DACALEN		__BIT(6)
58#define	 H3_RMIXEN		__BIT(5)
59#define	 H3_LMIXEN		__BIT(4)
60#define	H3_LINEIN_GCTR		0x05
61#define	 H3_LINEING		__BITS(6,4)
62#define	H3_MIC_GCTR		0x06
63#define	 H3_MIC1_GAIN		__BITS(6,4)
64#define	 H3_MIC2_GAIN		__BITS(2,0)
65#define	H3_PAEN_CTR		0x07
66#define	 H3_LINEOUTEN		__BIT(7)
67#define	H3_LINEOUT_VOLC		0x09
68#define	 H3_LINEOUTVOL		__BITS(7,3)
69#define	H3_MIC2G_LINEOUT_CTR	0x0a
70#define	 H3_LINEOUT_LSEL	__BIT(3)
71#define	 H3_LINEOUT_RSEL	__BIT(2)
72#define	H3_LADCMIXSC		0x0c
73#define	H3_RADCMIXSC		0x0d
74#define	 H3_ADCMIXSC_MIC1	__BIT(6)
75#define	 H3_ADCMIXSC_MIC2	__BIT(5)
76#define	 H3_ADCMIXSC_LINEIN	__BIT(2)
77#define	 H3_ADCMIXSC_OMIXER	__BITS(1,0)
78#define	H3_ADC_AP_EN		0x0f
79#define	 H3_ADCREN		__BIT(7)
80#define	 H3_ADCLEN		__BIT(6)
81#define	 H3_ADCG		__BITS(2,0)
82
83struct h3_codec_softc {
84	device_t		sc_dev;
85	bus_space_tag_t		sc_bst;
86	bus_space_handle_t	sc_bsh;
87	int			sc_phandle;
88};
89
90enum h3_codec_mixer_ctrl {
91	H3_CODEC_OUTPUT_CLASS,
92	H3_CODEC_INPUT_CLASS,
93	H3_CODEC_RECORD_CLASS,
94
95	H3_CODEC_OUTPUT_MASTER_VOLUME,
96	H3_CODEC_INPUT_DAC_VOLUME,
97	H3_CODEC_INPUT_LINEIN_VOLUME,
98	H3_CODEC_INPUT_MIC1_VOLUME,
99	H3_CODEC_INPUT_MIC2_VOLUME,
100	H3_CODEC_RECORD_AGC_VOLUME,
101	H3_CODEC_RECORD_SOURCE,
102
103	H3_CODEC_MIXER_CTRL_LAST
104};
105
106static const struct h3_codec_mixer {
107	const char *			name;
108	enum h3_codec_mixer_ctrl	mixer_class;
109	u_int				reg;
110	u_int				mask;
111} h3_codec_mixers[H3_CODEC_MIXER_CTRL_LAST] = {
112	[H3_CODEC_OUTPUT_MASTER_VOLUME]	= { AudioNmaster,
113	    H3_CODEC_OUTPUT_CLASS, H3_LINEOUT_VOLC, H3_LINEOUTVOL },
114	[H3_CODEC_INPUT_DAC_VOLUME]	= { AudioNdac,
115	    H3_CODEC_INPUT_CLASS, H3_LINEOUT_VOLC, H3_LINEOUTVOL },
116	[H3_CODEC_INPUT_LINEIN_VOLUME]	= { AudioNline,
117	    H3_CODEC_INPUT_CLASS, H3_LINEIN_GCTR, H3_LINEING },
118	[H3_CODEC_INPUT_MIC1_VOLUME]	= { "mic1",
119	    H3_CODEC_INPUT_CLASS, H3_MIC_GCTR, H3_MIC1_GAIN },
120	[H3_CODEC_INPUT_MIC2_VOLUME]	= { "mic2",
121	    H3_CODEC_INPUT_CLASS, H3_MIC_GCTR, H3_MIC2_GAIN },
122	[H3_CODEC_RECORD_AGC_VOLUME]	= { AudioNagc,
123	    H3_CODEC_RECORD_CLASS, H3_ADC_AP_EN, H3_ADCG },
124};
125
126#define	RD4(sc, reg)			\
127	bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg))
128#define	WR4(sc, reg, val)		\
129	bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val))
130
131static struct h3_codec_softc *
132h3_codec_find(int phandle)
133{
134	struct h3_codec_softc *csc;
135	device_t dev;
136
137	dev = device_find_by_driver_unit("h3codec", 0);
138	if (dev == NULL)
139		return NULL;
140	csc = device_private(dev);
141	if (csc->sc_phandle != phandle)
142		return NULL;
143
144	return csc;
145}
146
147static u_int
148h3_codec_pr_read(struct h3_codec_softc *csc, u_int addr)
149{
150	uint32_t val;
151
152	/* Read current value */
153	val = RD4(csc, H3_PR_CFG);
154
155	/* De-assert reset */
156	val |= H3_AC_PR_RST;
157	WR4(csc, H3_PR_CFG, val);
158
159	/* Read mode */
160	val &= ~H3_AC_PR_RW;
161	WR4(csc, H3_PR_CFG, val);
162
163	/* Set address */
164	val &= ~H3_AC_PR_ADDR;
165	val |= __SHIFTIN(addr, H3_AC_PR_ADDR);
166	WR4(csc, H3_PR_CFG, val);
167
168	/* Read data */
169	return __SHIFTOUT(RD4(csc, H3_PR_CFG), H3_ACDA_PR_RDAT);
170}
171
172static void
173h3_codec_pr_write(struct h3_codec_softc *csc, u_int addr, u_int data)
174{
175	uint32_t val;
176
177	/* Read current value */
178	val = RD4(csc, H3_PR_CFG);
179
180	/* De-assert reset */
181	val |= H3_AC_PR_RST;
182	WR4(csc, H3_PR_CFG, val);
183
184	/* Set address */
185	val &= ~H3_AC_PR_ADDR;
186	val |= __SHIFTIN(addr, H3_AC_PR_ADDR);
187	WR4(csc, H3_PR_CFG, val);
188
189	/* Write data */
190	val &= ~H3_ACDA_PR_WDAT;
191	val |= __SHIFTIN(data, H3_ACDA_PR_WDAT);
192	WR4(csc, H3_PR_CFG, val);
193
194	/* Write mode */
195	val |= H3_AC_PR_RW;
196	WR4(csc, H3_PR_CFG, val);
197
198	/* Clear write mode */
199	val &= ~H3_AC_PR_RW;
200	WR4(csc, H3_PR_CFG, val);
201}
202
203static void
204h3_codec_pr_set_clear(struct h3_codec_softc *csc, u_int addr, u_int set, u_int clr)
205{
206	u_int old, new;
207
208	old = h3_codec_pr_read(csc, addr);
209	new = set | (old & ~clr);
210	h3_codec_pr_write(csc, addr, new);
211}
212
213static int
214h3_codec_init(struct sunxi_codec_softc *sc)
215{
216	struct h3_codec_softc *csc;
217	int phandle;
218
219	/* Lookup the codec analog controls phandle */
220	phandle = fdtbus_get_phandle(sc->sc_phandle,
221	    "allwinner,codec-analog-controls");
222	if (phandle < 0) {
223		aprint_error_dev(sc->sc_dev,
224		    "missing allwinner,codec-analog-controls property\n");
225		return ENXIO;
226	}
227
228	/* Find a matching h3codec instance */
229	sc->sc_codec_priv = h3_codec_find(phandle);
230	if (sc->sc_codec_priv == NULL) {
231		aprint_error_dev(sc->sc_dev, "couldn't find codec analog controls\n");
232		return ENOENT;
233	}
234	csc = sc->sc_codec_priv;
235
236	/* Right & Left LINEOUT enable */
237	h3_codec_pr_set_clear(csc, H3_PAEN_CTR, H3_LINEOUTEN, 0);
238	h3_codec_pr_set_clear(csc, H3_MIC2G_LINEOUT_CTR,
239	    H3_LINEOUT_LSEL | H3_LINEOUT_RSEL, 0);
240
241	return 0;
242}
243
244static void
245h3_codec_mute(struct sunxi_codec_softc *sc, int mute, u_int mode)
246{
247	struct h3_codec_softc * const csc = sc->sc_codec_priv;
248
249	if (mode == AUMODE_PLAY) {
250		if (mute) {
251			/* Mute DAC l/r channels to output mixer */
252			h3_codec_pr_set_clear(csc, H3_LOMIXSC,
253			    0, H3_LOMIXSC_LDAC);
254			h3_codec_pr_set_clear(csc, H3_ROMIXSC,
255			    0, H3_ROMIXSC_RDAC);
256			/* Disable DAC analog l/r channels and output mixer */
257			h3_codec_pr_set_clear(csc, H3_DAC_PA_SRC,
258			    0, H3_DACAREN | H3_DACALEN | H3_RMIXEN | H3_LMIXEN);
259		} else {
260			/* Enable DAC analog l/r channels and output mixer */
261			h3_codec_pr_set_clear(csc, H3_DAC_PA_SRC,
262			    H3_DACAREN | H3_DACALEN | H3_RMIXEN | H3_LMIXEN, 0);
263			/* Unmute DAC l/r channels to output mixer */
264			h3_codec_pr_set_clear(csc, H3_LOMIXSC, H3_LOMIXSC_LDAC, 0);
265			h3_codec_pr_set_clear(csc, H3_ROMIXSC, H3_ROMIXSC_RDAC, 0);
266		}
267	} else {
268		if (mute) {
269			/* Disable ADC analog l/r channels */
270			h3_codec_pr_set_clear(csc, H3_ADC_AP_EN,
271			    0, H3_ADCREN | H3_ADCLEN);
272		} else {
273			/* Enable ADC analog l/r channels */
274			h3_codec_pr_set_clear(csc, H3_ADC_AP_EN,
275			    H3_ADCREN | H3_ADCLEN, 0);
276		}
277	}
278}
279
280static int
281h3_codec_set_port(struct sunxi_codec_softc *sc, mixer_ctrl_t *mc)
282{
283	struct h3_codec_softc * const csc = sc->sc_codec_priv;
284	const struct h3_codec_mixer *mix;
285	u_int val, shift;
286	int nvol;
287
288	switch (mc->dev) {
289	case H3_CODEC_OUTPUT_MASTER_VOLUME:
290	case H3_CODEC_INPUT_DAC_VOLUME:
291	case H3_CODEC_INPUT_LINEIN_VOLUME:
292	case H3_CODEC_INPUT_MIC1_VOLUME:
293	case H3_CODEC_INPUT_MIC2_VOLUME:
294	case H3_CODEC_RECORD_AGC_VOLUME:
295		mix = &h3_codec_mixers[mc->dev];
296		val = h3_codec_pr_read(csc, mix->reg);
297		shift = 8 - fls32(__SHIFTOUT_MASK(mix->mask));
298		nvol = mc->un.value.level[AUDIO_MIXER_LEVEL_LEFT] >> shift;
299		val &= ~mix->mask;
300		val |= __SHIFTIN(nvol, mix->mask);
301		h3_codec_pr_write(csc, mix->reg, val);
302		return 0;
303
304	case H3_CODEC_RECORD_SOURCE:
305		h3_codec_pr_write(csc, H3_LADCMIXSC, mc->un.mask);
306		h3_codec_pr_write(csc, H3_RADCMIXSC, mc->un.mask);
307		return 0;
308	}
309
310	return ENXIO;
311}
312
313static int
314h3_codec_get_port(struct sunxi_codec_softc *sc, mixer_ctrl_t *mc)
315{
316	struct h3_codec_softc * const csc = sc->sc_codec_priv;
317	const struct h3_codec_mixer *mix;
318	u_int val, shift;
319	int nvol;
320
321	switch (mc->dev) {
322	case H3_CODEC_OUTPUT_MASTER_VOLUME:
323	case H3_CODEC_INPUT_DAC_VOLUME:
324	case H3_CODEC_INPUT_LINEIN_VOLUME:
325	case H3_CODEC_INPUT_MIC1_VOLUME:
326	case H3_CODEC_INPUT_MIC2_VOLUME:
327	case H3_CODEC_RECORD_AGC_VOLUME:
328		mix = &h3_codec_mixers[mc->dev];
329		val = h3_codec_pr_read(csc, mix->reg);
330		shift = 8 - fls32(__SHIFTOUT_MASK(mix->mask));
331		nvol = __SHIFTOUT(val, mix->mask) << shift;
332		mc->un.value.level[AUDIO_MIXER_LEVEL_LEFT] = nvol;
333		mc->un.value.level[AUDIO_MIXER_LEVEL_RIGHT] = nvol;
334		return 0;
335
336	case H3_CODEC_RECORD_SOURCE:
337		mc->un.mask =
338		    h3_codec_pr_read(csc, H3_LADCMIXSC) |
339		    h3_codec_pr_read(csc, H3_RADCMIXSC);
340		return 0;
341	}
342
343	return ENXIO;
344}
345
346static int
347h3_codec_query_devinfo(struct sunxi_codec_softc *sc, mixer_devinfo_t *di)
348{
349	const struct h3_codec_mixer *mix;
350
351	switch (di->index) {
352	case H3_CODEC_OUTPUT_CLASS:
353		di->mixer_class = di->index;
354		strcpy(di->label.name, AudioCoutputs);
355		di->type = AUDIO_MIXER_CLASS;
356		di->next = di->prev = AUDIO_MIXER_LAST;
357		return 0;
358
359	case H3_CODEC_INPUT_CLASS:
360		di->mixer_class = di->index;
361		strcpy(di->label.name, AudioCinputs);
362		di->type = AUDIO_MIXER_CLASS;
363		di->next = di->prev = AUDIO_MIXER_LAST;
364		return 0;
365
366	case H3_CODEC_RECORD_CLASS:
367		di->mixer_class = di->index;
368		strcpy(di->label.name, AudioCrecord);
369		di->type = AUDIO_MIXER_CLASS;
370		di->next = di->prev = AUDIO_MIXER_LAST;
371		return 0;
372
373	case H3_CODEC_OUTPUT_MASTER_VOLUME:
374	case H3_CODEC_INPUT_DAC_VOLUME:
375	case H3_CODEC_INPUT_LINEIN_VOLUME:
376	case H3_CODEC_INPUT_MIC1_VOLUME:
377	case H3_CODEC_INPUT_MIC2_VOLUME:
378	case H3_CODEC_RECORD_AGC_VOLUME:
379		mix = &h3_codec_mixers[di->index];
380		di->mixer_class = mix->mixer_class;
381		strcpy(di->label.name, mix->name);
382		di->un.v.delta =
383		    256 / (__SHIFTOUT_MASK(mix->mask) + 1);
384		di->type = AUDIO_MIXER_VALUE;
385		di->next = di->prev = AUDIO_MIXER_LAST;
386		di->un.v.num_channels = 2;
387		strcpy(di->un.v.units.name, AudioNvolume);
388		return 0;
389
390	case H3_CODEC_RECORD_SOURCE:
391		di->mixer_class = H3_CODEC_RECORD_CLASS;
392		strcpy(di->label.name, AudioNsource);
393		di->type = AUDIO_MIXER_SET;
394		di->next = di->prev = AUDIO_MIXER_LAST;
395		di->un.s.num_mem = 4;
396		strcpy(di->un.s.member[0].label.name, AudioNline);
397		di->un.s.member[0].mask = H3_ADCMIXSC_LINEIN;
398		strcpy(di->un.s.member[1].label.name, "mic1");
399		di->un.s.member[1].mask = H3_ADCMIXSC_MIC1;
400		strcpy(di->un.s.member[2].label.name, "mic2");
401		di->un.s.member[2].mask = H3_ADCMIXSC_MIC2;
402		strcpy(di->un.s.member[3].label.name, AudioNdac);
403		di->un.s.member[3].mask = H3_ADCMIXSC_OMIXER;
404		return 0;
405
406	}
407
408	return ENXIO;
409}
410
411const struct sunxi_codec_conf sun8i_h3_codecconf = {
412	.name = "H3 Audio Codec",
413
414	.init = h3_codec_init,
415	.mute = h3_codec_mute,
416	.set_port = h3_codec_set_port,
417	.get_port = h3_codec_get_port,
418	.query_devinfo = h3_codec_query_devinfo,
419
420	.DPC		= 0x00,
421	.DAC_FIFOC	= 0x04,
422	.DAC_FIFOS	= 0x08,
423	.DAC_TXDATA	= 0x20,
424	.ADC_FIFOC	= 0x10,
425	.ADC_FIFOS	= 0x14,
426	.ADC_RXDATA	= 0x18,
427	.DAC_CNT	= 0x40,
428	.ADC_CNT	= 0x44,
429};
430
431/*
432 * Device glue, only here to claim resources on behalf of the sunxi_codec driver.
433 */
434
435static const struct device_compatible_entry compat_data[] = {
436	{ .compat = "allwinner,sun8i-h3-codec-analog" },
437	DEVICE_COMPAT_EOL
438};
439
440static int
441h3_codec_match(device_t parent, cfdata_t cf, void *aux)
442{
443	struct fdt_attach_args * const faa = aux;
444
445	return of_compatible_match(faa->faa_phandle, compat_data);
446}
447
448static void
449h3_codec_attach(device_t parent, device_t self, void *aux)
450{
451	struct h3_codec_softc * const sc = device_private(self);
452	struct fdt_attach_args * const faa = aux;
453	const int phandle = faa->faa_phandle;
454	bus_addr_t addr;
455	bus_size_t size;
456
457	sc->sc_dev = self;
458	if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
459		aprint_error(": couldn't get registers\n");
460		return;
461	}
462	sc->sc_bst = faa->faa_bst;
463	if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) {
464		aprint_error(": couldn't map registers\n");
465		return;
466	}
467
468	sc->sc_phandle = phandle;
469
470	aprint_naive("\n");
471	aprint_normal(": H3 Audio Codec (analog part)\n");
472}
473
474CFATTACH_DECL_NEW(h3_codec, sizeof(struct h3_codec_softc),
475    h3_codec_match, h3_codec_attach, NULL, NULL);
476