sun50i_a64_acodec.c revision 1.2
1/* $NetBSD: sun50i_a64_acodec.c,v 1.2 2018/05/10 00:30:56 jmcneill 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: sun50i_a64_acodec.c,v 1.2 2018/05/10 00:30:56 jmcneill 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 <dev/audio_dai.h>
40
41#include <dev/fdt/fdtvar.h>
42
43#define	A64_PR_CFG		0x00
44#define	 A64_AC_PR_RST		__BIT(28)
45#define	 A64_AC_PR_RW		__BIT(24)
46#define	 A64_AC_PR_ADDR		__BITS(20,16)
47#define	 A64_ACDA_PR_WDAT	__BITS(15,8)
48#define	 A64_ACDA_PR_RDAT	__BITS(7,0)
49
50#define	A64_HP_CTRL		0x00
51#define	 A64_HPPA_EN		__BIT(6)
52#define	 A64_HPVOL		__BITS(5,0)
53#define	A64_OL_MIX_CTRL		0x01
54#define	 A64_LMIXMUTE_LDAC	__BIT(1)
55#define	A64_OR_MIX_CTRL		0x02
56#define	 A64_RMIXMUTE_RDAC	__BIT(1)
57#define	A64_LINEOUT_CTRL0	0x05
58#define	 A64_LINEOUT_LEFT_EN	__BIT(7)
59#define	 A64_LINEOUT_RIGHT_EN	__BIT(6)
60#define	A64_LINEOUT_CTRL1	0x06
61#define	 A64_LINEOUT_VOL	__BITS(4,0)
62#define	A64_MIC1_CTRL		0x07
63#define	 A64_MIC1G		__BITS(6,4)
64#define	 A64_MIC1AMPEN		__BIT(3)
65#define	 A64_MIC1BOOST		__BITS(2,0)
66#define	A64_MIC2_CTRL		0x08
67#define	 A64_MIC2_SEL		__BIT(7)
68#define	 A64_MIC2G		__BITS(6,4)
69#define	 A64_MIC2AMPEN		__BIT(3)
70#define	 A64_MIC2BOOST		__BITS(2,0)
71#define	A64_LINEIN_CTRL		0x09
72#define	 A64_LINEING		__BITS(6,4)
73#define	A64_MIX_DAC_CTRL	0x0a
74#define	 A64_DACAREN		__BIT(7)
75#define	 A64_DACALEN		__BIT(6)
76#define	 A64_RMIXEN		__BIT(5)
77#define	 A64_LMIXEN		__BIT(4)
78#define	 A64_RHPPAMUTE		__BIT(3)
79#define	 A64_LHPPAMUTE		__BIT(2)
80#define	 A64_RHPIS		__BIT(1)
81#define	 A64_LHPIS		__BIT(0)
82#define	A64_L_ADCMIX_SRC	0x0b
83#define	A64_R_ADCMIX_SRC	0x0c
84#define	 A64_ADCMIX_SRC_MIC1	__BIT(6)
85#define	 A64_ADCMIX_SRC_MIC2	__BIT(5)
86#define	 A64_ADCMIX_SRC_LINEIN	__BIT(2)
87#define	 A64_ADCMIX_SRC_OMIXER	__BIT(0)
88#define	A64_ADC_CTRL		0x0d
89#define	 A64_ADCREN		__BIT(7)
90#define	 A64_ADCLEN		__BIT(6)
91#define	 A64_ADCG		__BITS(2,0)
92
93struct a64_acodec_softc {
94	device_t		sc_dev;
95	bus_space_tag_t		sc_bst;
96	bus_space_handle_t	sc_bsh;
97	int			sc_phandle;
98
99	struct audio_dai_device	sc_dai;
100};
101
102enum a64_acodec_mixer_ctrl {
103	A64_CODEC_OUTPUT_CLASS,
104	A64_CODEC_INPUT_CLASS,
105	A64_CODEC_RECORD_CLASS,
106
107	A64_CODEC_OUTPUT_MASTER_VOLUME,
108	A64_CODEC_OUTPUT_HP_VOLUME,
109	A64_CODEC_INPUT_DAC_VOLUME,
110	A64_CODEC_INPUT_LINEIN_VOLUME,
111	A64_CODEC_INPUT_MIC1_VOLUME,
112	A64_CODEC_INPUT_MIC2_VOLUME,
113	A64_CODEC_RECORD_AGC_VOLUME,
114	A64_CODEC_RECORD_SOURCE,
115
116	A64_CODEC_MIXER_CTRL_LAST
117};
118
119static const struct a64_acodec_mixer {
120	const char *			name;
121	enum a64_acodec_mixer_ctrl	mixer_class;
122	u_int				reg;
123	u_int				mask;
124} a64_acodec_mixers[A64_CODEC_MIXER_CTRL_LAST] = {
125	[A64_CODEC_OUTPUT_MASTER_VOLUME]	= { AudioNmaster,
126	    A64_CODEC_OUTPUT_CLASS, A64_LINEOUT_CTRL1, A64_LINEOUT_VOL },
127	[A64_CODEC_OUTPUT_HP_VOLUME]	= { AudioNheadphone,
128	    A64_CODEC_OUTPUT_CLASS, A64_HP_CTRL, A64_HPVOL },
129	[A64_CODEC_INPUT_DAC_VOLUME]	= { AudioNdac,
130	    A64_CODEC_INPUT_CLASS, A64_LINEOUT_CTRL1, A64_LINEOUT_VOL },
131	[A64_CODEC_INPUT_LINEIN_VOLUME]	= { AudioNline,
132	    A64_CODEC_INPUT_CLASS, A64_LINEIN_CTRL, A64_LINEING },
133	[A64_CODEC_INPUT_MIC1_VOLUME]	= { "mic1",
134	    A64_CODEC_INPUT_CLASS, A64_MIC1_CTRL, A64_MIC1G },
135	[A64_CODEC_INPUT_MIC2_VOLUME]	= { "mic2",
136	    A64_CODEC_INPUT_CLASS, A64_MIC2_CTRL, A64_MIC2G },
137	[A64_CODEC_RECORD_AGC_VOLUME]	= { AudioNagc,
138	    A64_CODEC_RECORD_CLASS, A64_ADC_CTRL, A64_ADCG },
139};
140
141#define	RD4(sc, reg)			\
142	bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg))
143#define	WR4(sc, reg, val)		\
144	bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val))
145
146static u_int
147a64_acodec_pr_read(struct a64_acodec_softc *sc, u_int addr)
148{
149	uint32_t val;
150
151	/* Read current value */
152	val = RD4(sc, A64_PR_CFG);
153
154	/* De-assert reset */
155	val |= A64_AC_PR_RST;
156	WR4(sc, A64_PR_CFG, val);
157
158	/* Read mode */
159	val &= ~A64_AC_PR_RW;
160	WR4(sc, A64_PR_CFG, val);
161
162	/* Set address */
163	val &= ~A64_AC_PR_ADDR;
164	val |= __SHIFTIN(addr, A64_AC_PR_ADDR);
165	WR4(sc, A64_PR_CFG, val);
166
167	/* Read data */
168	return __SHIFTOUT(RD4(sc, A64_PR_CFG), A64_ACDA_PR_RDAT);
169}
170
171static void
172a64_acodec_pr_write(struct a64_acodec_softc *sc, u_int addr, u_int data)
173{
174	uint32_t val;
175
176	/* Read current value */
177	val = RD4(sc, A64_PR_CFG);
178
179	/* De-assert reset */
180	val |= A64_AC_PR_RST;
181	WR4(sc, A64_PR_CFG, val);
182
183	/* Set address */
184	val &= ~A64_AC_PR_ADDR;
185	val |= __SHIFTIN(addr, A64_AC_PR_ADDR);
186	WR4(sc, A64_PR_CFG, val);
187
188	/* Write data */
189	val &= ~A64_ACDA_PR_WDAT;
190	val |= __SHIFTIN(data, A64_ACDA_PR_WDAT);
191	WR4(sc, A64_PR_CFG, val);
192
193	/* Write mode */
194	val |= A64_AC_PR_RW;
195	WR4(sc, A64_PR_CFG, val);
196
197	/* Clear write mode */
198	val &= ~A64_AC_PR_RW;
199	WR4(sc, A64_PR_CFG, val);
200}
201
202static void
203a64_acodec_pr_set_clear(struct a64_acodec_softc *sc, u_int addr, u_int set, u_int clr)
204{
205	u_int old, new;
206
207	old = a64_acodec_pr_read(sc, addr);
208	new = set | (old & ~clr);
209	a64_acodec_pr_write(sc, addr, new);
210}
211
212static int
213a64_acodec_trigger_output(void *priv, void *start, void *end, int blksize,
214    void (*intr)(void *), void *intrarg, const audio_params_t *params)
215{
216	struct a64_acodec_softc * const sc = priv;
217
218	/* Enable DAC analog l/r channels, HP PA, and output mixer */
219	a64_acodec_pr_set_clear(sc, A64_MIX_DAC_CTRL,
220	    A64_DACAREN | A64_DACALEN | A64_RMIXEN | A64_LMIXEN |
221	    A64_RHPPAMUTE | A64_LHPPAMUTE, 0);
222	/* Unmute DAC l/r channels to output mixer */
223	a64_acodec_pr_set_clear(sc, A64_OL_MIX_CTRL,
224	    A64_LMIXMUTE_LDAC, 0);
225	a64_acodec_pr_set_clear(sc, A64_OR_MIX_CTRL,
226	    A64_RMIXMUTE_RDAC, 0);
227
228	return 0;
229}
230
231static int
232a64_acodec_trigger_input(void *priv, void *start, void *end, int blksize,
233    void (*intr)(void *), void *intrarg, const audio_params_t *params)
234{
235	struct a64_acodec_softc * const sc = priv;
236
237	/* Enable ADC analog l/r channels */
238	a64_acodec_pr_set_clear(sc, A64_ADC_CTRL,
239	    A64_ADCREN | A64_ADCLEN, 0);
240
241	return 0;
242}
243
244static int
245a64_acodec_halt_output(void *priv)
246{
247	struct a64_acodec_softc * const sc = priv;
248
249	/* Mute DAC l/r channels to output mixer */
250	a64_acodec_pr_set_clear(sc, A64_OL_MIX_CTRL,
251	    0, A64_LMIXMUTE_LDAC);
252	a64_acodec_pr_set_clear(sc, A64_OR_MIX_CTRL,
253	    0, A64_RMIXMUTE_RDAC);
254	/* Disable DAC analog l/r channels, HP PA, and output mixer */
255	a64_acodec_pr_set_clear(sc, A64_MIX_DAC_CTRL,
256	    0, A64_DACAREN | A64_DACALEN | A64_RMIXEN | A64_LMIXEN |
257	    A64_RHPPAMUTE | A64_LHPPAMUTE);
258
259	return 0;
260}
261
262static int
263a64_acodec_halt_input(void *priv)
264{
265	struct a64_acodec_softc * const sc = priv;
266
267	/* Disable ADC analog l/r channels */
268	a64_acodec_pr_set_clear(sc, A64_ADC_CTRL,
269	    0, A64_ADCREN | A64_ADCLEN);
270
271	return 0;
272}
273
274static int
275a64_acodec_set_port(void *priv, mixer_ctrl_t *mc)
276{
277	struct a64_acodec_softc * const sc = priv;
278	const struct a64_acodec_mixer *mix;
279	u_int val, shift;
280	int nvol;
281
282	switch (mc->dev) {
283	case A64_CODEC_OUTPUT_MASTER_VOLUME:
284	case A64_CODEC_OUTPUT_HP_VOLUME:
285	case A64_CODEC_INPUT_DAC_VOLUME:
286	case A64_CODEC_INPUT_LINEIN_VOLUME:
287	case A64_CODEC_INPUT_MIC1_VOLUME:
288	case A64_CODEC_INPUT_MIC2_VOLUME:
289	case A64_CODEC_RECORD_AGC_VOLUME:
290		mix = &a64_acodec_mixers[mc->dev];
291		val = a64_acodec_pr_read(sc, mix->reg);
292		shift = 8 - fls32(__SHIFTOUT_MASK(mix->mask));
293		nvol = mc->un.value.level[AUDIO_MIXER_LEVEL_LEFT] >> shift;
294		val &= ~mix->mask;
295		val |= __SHIFTIN(nvol, mix->mask);
296		a64_acodec_pr_write(sc, mix->reg, val);
297		return 0;
298
299	case A64_CODEC_RECORD_SOURCE:
300		a64_acodec_pr_write(sc, A64_L_ADCMIX_SRC, mc->un.mask);
301		a64_acodec_pr_write(sc, A64_R_ADCMIX_SRC, mc->un.mask);
302		return 0;
303	}
304
305	return ENXIO;
306}
307
308static int
309a64_acodec_get_port(void *priv, mixer_ctrl_t *mc)
310{
311	struct a64_acodec_softc * const sc = priv;
312	const struct a64_acodec_mixer *mix;
313	u_int val, shift;
314	int nvol;
315
316	switch (mc->dev) {
317	case A64_CODEC_OUTPUT_MASTER_VOLUME:
318	case A64_CODEC_OUTPUT_HP_VOLUME:
319	case A64_CODEC_INPUT_DAC_VOLUME:
320	case A64_CODEC_INPUT_LINEIN_VOLUME:
321	case A64_CODEC_INPUT_MIC1_VOLUME:
322	case A64_CODEC_INPUT_MIC2_VOLUME:
323	case A64_CODEC_RECORD_AGC_VOLUME:
324		mix = &a64_acodec_mixers[mc->dev];
325		val = a64_acodec_pr_read(sc, mix->reg);
326		shift = 8 - fls32(__SHIFTOUT_MASK(mix->mask));
327		nvol = __SHIFTOUT(val, mix->mask) << shift;
328		mc->un.value.level[AUDIO_MIXER_LEVEL_LEFT] = nvol;
329		mc->un.value.level[AUDIO_MIXER_LEVEL_RIGHT] = nvol;
330		return 0;
331
332	case A64_CODEC_RECORD_SOURCE:
333		mc->un.mask =
334		    a64_acodec_pr_read(sc, A64_L_ADCMIX_SRC) |
335		    a64_acodec_pr_read(sc, A64_R_ADCMIX_SRC);
336		return 0;
337	}
338
339	return ENXIO;
340}
341
342static int
343a64_acodec_query_devinfo(void *priv, mixer_devinfo_t *di)
344{
345	const struct a64_acodec_mixer *mix;
346
347	switch (di->index) {
348	case A64_CODEC_OUTPUT_CLASS:
349		di->mixer_class = di->index;
350		strcpy(di->label.name, AudioCoutputs);
351		di->type = AUDIO_MIXER_CLASS;
352		di->next = di->prev = AUDIO_MIXER_LAST;
353		return 0;
354
355	case A64_CODEC_INPUT_CLASS:
356		di->mixer_class = di->index;
357		strcpy(di->label.name, AudioCinputs);
358		di->type = AUDIO_MIXER_CLASS;
359		di->next = di->prev = AUDIO_MIXER_LAST;
360		return 0;
361
362	case A64_CODEC_RECORD_CLASS:
363		di->mixer_class = di->index;
364		strcpy(di->label.name, AudioCrecord);
365		di->type = AUDIO_MIXER_CLASS;
366		di->next = di->prev = AUDIO_MIXER_LAST;
367		return 0;
368
369	case A64_CODEC_OUTPUT_MASTER_VOLUME:
370	case A64_CODEC_OUTPUT_HP_VOLUME:
371	case A64_CODEC_INPUT_DAC_VOLUME:
372	case A64_CODEC_INPUT_LINEIN_VOLUME:
373	case A64_CODEC_INPUT_MIC1_VOLUME:
374	case A64_CODEC_INPUT_MIC2_VOLUME:
375	case A64_CODEC_RECORD_AGC_VOLUME:
376		mix = &a64_acodec_mixers[di->index];
377		di->mixer_class = mix->mixer_class;
378		strcpy(di->label.name, mix->name);
379		di->un.v.delta =
380		    256 / (__SHIFTOUT_MASK(mix->mask) + 1);
381		di->type = AUDIO_MIXER_VALUE;
382		di->next = di->prev = AUDIO_MIXER_LAST;
383		di->un.v.num_channels = 2;
384		strcpy(di->un.v.units.name, AudioNvolume);
385		return 0;
386
387	case A64_CODEC_RECORD_SOURCE:
388		di->mixer_class = A64_CODEC_RECORD_CLASS;
389		strcpy(di->label.name, AudioNsource);
390		di->type = AUDIO_MIXER_SET;
391		di->next = di->prev = AUDIO_MIXER_LAST;
392		di->un.s.num_mem = 4;
393		strcpy(di->un.s.member[0].label.name, AudioNline);
394		di->un.s.member[0].mask = A64_ADCMIX_SRC_LINEIN;
395		strcpy(di->un.s.member[1].label.name, "mic1");
396		di->un.s.member[1].mask = A64_ADCMIX_SRC_MIC1;
397		strcpy(di->un.s.member[2].label.name, "mic2");
398		di->un.s.member[2].mask = A64_ADCMIX_SRC_MIC2;
399		strcpy(di->un.s.member[3].label.name, AudioNdac);
400		di->un.s.member[3].mask = A64_ADCMIX_SRC_OMIXER;
401		return 0;
402
403	}
404
405	return ENXIO;
406}
407
408static const struct audio_hw_if a64_acodec_hw_if = {
409	.trigger_output = a64_acodec_trigger_output,
410	.trigger_input = a64_acodec_trigger_input,
411	.halt_output = a64_acodec_halt_output,
412	.halt_input = a64_acodec_halt_input,
413	.set_port = a64_acodec_set_port,
414	.get_port = a64_acodec_get_port,
415	.query_devinfo = a64_acodec_query_devinfo,
416};
417
418static audio_dai_tag_t
419a64_acodec_dai_get_tag(device_t dev, const void *data, size_t len)
420{
421	struct a64_acodec_softc * const sc = device_private(dev);
422
423	if (len != 4)
424		return NULL;
425
426	return &sc->sc_dai;
427}
428
429static struct fdtbus_dai_controller_func a64_acodec_dai_funcs = {
430	.get_tag = a64_acodec_dai_get_tag
431};
432
433static const char * compatible[] = {
434	"allwinner,sun50i-a64-codec-analog",
435	NULL
436};
437
438static int
439a64_acodec_match(device_t parent, cfdata_t cf, void *aux)
440{
441	struct fdt_attach_args * const faa = aux;
442
443	return of_match_compatible(faa->faa_phandle, compatible);
444}
445
446static void
447a64_acodec_attach(device_t parent, device_t self, void *aux)
448{
449	struct a64_acodec_softc * const sc = device_private(self);
450	struct fdt_attach_args * const faa = aux;
451	const int phandle = faa->faa_phandle;
452	bus_addr_t addr;
453	bus_size_t size;
454
455	sc->sc_dev = self;
456	if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
457		aprint_error(": couldn't get registers\n");
458		return;
459	}
460	sc->sc_bst = faa->faa_bst;
461	if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) {
462		aprint_error(": couldn't map registers\n");
463		return;
464	}
465
466	sc->sc_phandle = phandle;
467
468	aprint_naive("\n");
469	aprint_normal(": A64 Audio Codec (analog part)\n");
470
471	/* Right & Left LINEOUT enable */
472	a64_acodec_pr_set_clear(sc, A64_LINEOUT_CTRL0,
473	    A64_LINEOUT_LEFT_EN | A64_LINEOUT_RIGHT_EN, 0);
474	/* Right & Left Headphone PA enable */
475	a64_acodec_pr_set_clear(sc, A64_HP_CTRL,
476	    A64_HPPA_EN, 0);
477
478	sc->sc_dai.dai_hw_if = &a64_acodec_hw_if;
479	sc->sc_dai.dai_dev = self;
480	sc->sc_dai.dai_priv = sc;
481	fdtbus_register_dai_controller(self, phandle, &a64_acodec_dai_funcs);
482}
483
484CFATTACH_DECL_NEW(a64_acodec, sizeof(struct a64_acodec_softc),
485    a64_acodec_match, a64_acodec_attach, NULL, NULL);
486