xref: /openbsd-src/sys/dev/fdt/tascodec.c (revision c1a45aed656e7d5627c30c92421893a76f370ccb)
1 /*	$OpenBSD: tascodec.c,v 1.3 2022/03/21 19:22:40 miod Exp $	*/
2 /*
3  * Copyright (c) 2022 Mark Kettenis <kettenis@openbsd.org>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 #include <sys/param.h>
19 #include <sys/systm.h>
20 #include <sys/audioio.h>
21 #include <sys/device.h>
22 #include <sys/malloc.h>
23 
24 #include <machine/bus.h>
25 
26 #include <dev/ofw/openfirm.h>
27 #include <dev/ofw/ofw_gpio.h>
28 #include <dev/ofw/ofw_misc.h>
29 #include <dev/ofw/fdt.h>
30 
31 #include <dev/i2c/i2cvar.h>
32 
33 #include <dev/audio_if.h>
34 
35 #define PWR_CTL				0x02
36 #define  PWR_CTL_ISNS_PD		(1 << 3)
37 #define  PWR_CTL_VSNS_PD		(1 << 2)
38 #define  PWR_CTL_MODE_ACTIVE		(0 << 0)
39 #define  PWR_CTL_MODE_MUTE		(1 << 0)
40 #define  PWR_CTL_MODE_SHUTDOWN		(2 << 0)
41 #define PB_CFG2				0x05
42 #define  PB_CFG2_DVC_PCM_MIN		0xc9
43 #define TDM_CFG0			0x0a
44 #define  TDM_CFG0_FRAME_START		(1 << 0)
45 #define TDM_CFG1			0x0b
46 #define  TDM_CFG1_RX_JUSTIFY		(1 << 6)
47 #define  TDM_CFG1_RX_OFFSET_MASK	(0x1f << 1)
48 #define  TDM_CFG1_RX_OFFSET_SHIFT	1
49 #define  TDM_CFG1_RX_EDGE		(1 << 0)
50 #define TDM_CFG2			0x0c
51 #define  TDM_CFG2_SCFG_MASK		(3 << 4)
52 #define  TDM_CFG2_SCFG_STEREO_DOWNMIX	(3 << 4)
53 
54 struct tascodec_softc {
55 	struct device		sc_dev;
56 	i2c_tag_t		sc_tag;
57 	i2c_addr_t		sc_addr;
58 
59 	struct dai_device	sc_dai;
60 	uint8_t			sc_dvc;
61 };
62 
63 int	tascodec_match(struct device *, void *, void *);
64 void	tascodec_attach(struct device *, struct device *, void *);
65 int	tascodec_activate(struct device *, int);
66 
67 const struct cfattach tascodec_ca = {
68 	sizeof(struct tascodec_softc), tascodec_match, tascodec_attach,
69 	NULL, tascodec_activate
70 };
71 
72 struct cfdriver tascodec_cd = {
73 	NULL, "tascodec", DV_DULL
74 };
75 
76 int	tascodec_set_format(void *, uint32_t, uint32_t, uint32_t);
77 
78 int	tascodec_set_port(void *, mixer_ctrl_t *);
79 int	tascodec_get_port(void *, mixer_ctrl_t *);
80 int	tascodec_query_devinfo(void *, mixer_devinfo_t *);
81 int	tascodec_trigger_output(void *, void *, void *, int,
82 	    void (*)(void *), void *, struct audio_params *);
83 int	tascodec_halt_output(void *);
84 
85 const struct audio_hw_if tascodec_hw_if = {
86 	.set_port = tascodec_set_port,
87 	.get_port = tascodec_get_port,
88 	.query_devinfo = tascodec_query_devinfo,
89 	.trigger_output = tascodec_trigger_output,
90 	.halt_output = tascodec_halt_output,
91 };
92 
93 uint8_t	tascodec_read(struct tascodec_softc *, int);
94 void	tascodec_write(struct tascodec_softc *, int, uint8_t);
95 
96 int
97 tascodec_match(struct device *parent, void *match, void *aux)
98 {
99 	struct i2c_attach_args *ia = aux;
100 
101 	return iic_is_compatible(ia, "ti,tas2770");
102 }
103 
104 void
105 tascodec_attach(struct device *parent, struct device *self, void *aux)
106 {
107 	struct tascodec_softc *sc = (struct tascodec_softc *)self;
108 	struct i2c_attach_args *ia = aux;
109 	int node = *(int *)ia->ia_cookie;
110 	uint32_t *reset_gpio;
111 	int reset_gpiolen;
112 	uint8_t cfg2;
113 
114 	sc->sc_tag = ia->ia_tag;
115 	sc->sc_addr = ia->ia_addr;
116 
117 	printf("\n");
118 
119 	reset_gpiolen = OF_getproplen(node, "reset-gpios");
120 	if (reset_gpiolen > 0) {
121 		reset_gpio = malloc(reset_gpiolen, M_TEMP, M_WAITOK);
122 		OF_getpropintarray(node, "reset-gpios",
123 		    reset_gpio, reset_gpiolen);
124 		gpio_controller_config_pin(reset_gpio, GPIO_CONFIG_OUTPUT);
125 		gpio_controller_set_pin(reset_gpio, 1);
126 		free(reset_gpio, M_TEMP, reset_gpiolen);
127 		delay(1000);
128 	}
129 
130 	sc->sc_dvc = tascodec_read(sc, PB_CFG2);
131 	if (sc->sc_dvc > PB_CFG2_DVC_PCM_MIN)
132 		sc->sc_dvc = PB_CFG2_DVC_PCM_MIN;
133 
134 	/* Default to stereo downmix mode for now. */
135 	cfg2 = tascodec_read(sc, TDM_CFG2);
136 	cfg2 &= ~TDM_CFG2_SCFG_MASK;
137 	cfg2 |= TDM_CFG2_SCFG_STEREO_DOWNMIX;
138 	tascodec_write(sc, TDM_CFG2, cfg2);
139 
140 	sc->sc_dai.dd_node = node;
141 	sc->sc_dai.dd_cookie = sc;
142 	sc->sc_dai.dd_hw_if = &tascodec_hw_if;
143 	sc->sc_dai.dd_set_format = tascodec_set_format;
144 	dai_register(&sc->sc_dai);
145 }
146 
147 int
148 tascodec_activate(struct device *self, int act)
149 {
150 	struct tascodec_softc *sc = (struct tascodec_softc *)self;
151 
152 	switch (act) {
153 	case DVACT_POWERDOWN:
154 		tascodec_write(sc, PWR_CTL,
155 		    PWR_CTL_ISNS_PD | PWR_CTL_VSNS_PD | PWR_CTL_MODE_SHUTDOWN);
156 		break;
157 	}
158 
159 	return 0;
160 }
161 
162 int
163 tascodec_set_format(void *cookie, uint32_t fmt, uint32_t pol,
164     uint32_t clk)
165 {
166 	struct tascodec_softc *sc = cookie;
167 	uint8_t cfg0, cfg1;
168 
169 	cfg0 = tascodec_read(sc, TDM_CFG0);
170 	cfg1 = tascodec_read(sc, TDM_CFG1);
171 	cfg1 &= ~TDM_CFG1_RX_OFFSET_MASK;
172 
173 	switch (fmt) {
174 	case DAI_FORMAT_I2S:
175 		cfg0 |= TDM_CFG0_FRAME_START;
176 		cfg1 &= ~TDM_CFG1_RX_JUSTIFY;
177 		cfg1 |= (1 << TDM_CFG1_RX_OFFSET_SHIFT);
178 		cfg1 &= ~TDM_CFG1_RX_EDGE;
179 		break;
180 	case DAI_FORMAT_RJ:
181 		cfg0 &= ~TDM_CFG0_FRAME_START;
182 		cfg1 |= TDM_CFG1_RX_JUSTIFY;
183 		cfg1 &= ~TDM_CFG1_RX_EDGE;
184 		break;
185 	case DAI_FORMAT_LJ:
186 		cfg0 &= ~TDM_CFG0_FRAME_START;
187 		cfg1 &= ~TDM_CFG1_RX_JUSTIFY;
188 		cfg1 &= ~TDM_CFG1_RX_EDGE;
189 		break;
190 	default:
191 		return EINVAL;
192 	}
193 
194 	if (pol & DAI_POLARITY_IB)
195 		cfg1 ^= TDM_CFG1_RX_EDGE;
196 	if (pol & DAI_POLARITY_IF)
197 		cfg0 ^= TDM_CFG0_FRAME_START;
198 
199 	if (!(clk & DAI_CLOCK_CBM) || !(clk & DAI_CLOCK_CFM))
200 		return EINVAL;
201 
202 	tascodec_write(sc, TDM_CFG0, cfg0);
203 	tascodec_write(sc, TDM_CFG1, cfg1);
204 
205 	return 0;
206 }
207 
208 /*
209  * Mixer controls; the gain of the TAS2770 is determined by the
210  * amplifier gain and digital volume control setting, but we only
211  * expose the digital volume control setting through the mixer
212  * interface.
213  */
214 enum {
215 	TASCODEC_MASTER_VOL,
216 	TASCODEC_OUTPUT_CLASS
217 };
218 
219 int
220 tascodec_set_port(void *priv, mixer_ctrl_t *mc)
221 {
222 	struct tascodec_softc *sc = priv;
223 	u_char level;
224 
225 	switch (mc->dev) {
226 	case TASCODEC_MASTER_VOL:
227 		level = mc->un.value.level[AUDIO_MIXER_LEVEL_MONO];
228 		sc->sc_dvc = (PB_CFG2_DVC_PCM_MIN * (255 - level)) / 255;
229 		tascodec_write(sc, PB_CFG2, sc->sc_dvc);
230 		return 0;
231 	}
232 
233 	return EINVAL;
234 }
235 
236 int
237 tascodec_get_port(void *priv, mixer_ctrl_t *mc)
238 {
239 	struct tascodec_softc *sc = priv;
240 	u_char level;
241 
242 	switch (mc->dev) {
243 	case TASCODEC_MASTER_VOL:
244 		mc->un.value.num_channels = 1;
245 		level = 255 - ((255 * sc->sc_dvc) / PB_CFG2_DVC_PCM_MIN);
246 		mc->un.value.level[AUDIO_MIXER_LEVEL_MONO] = level;
247 		return 0;
248 	}
249 
250 	return EINVAL;
251 }
252 
253 int
254 tascodec_query_devinfo(void *priv, mixer_devinfo_t *di)
255 {
256 	switch (di->index) {
257 	case TASCODEC_MASTER_VOL:
258 		di->mixer_class = TASCODEC_OUTPUT_CLASS;
259 		di->next = di->prev = AUDIO_MIXER_LAST;
260 		strlcpy(di->label.name, AudioNmaster, sizeof(di->label.name));
261 		di->type = AUDIO_MIXER_VALUE;
262 		di->un.v.num_channels = 1;
263 		strlcpy(di->un.v.units.name, AudioNvolume,
264 		    sizeof(di->un.v.units.name));
265 		return 0;
266 
267 	case TASCODEC_OUTPUT_CLASS:
268 		di->mixer_class = TASCODEC_OUTPUT_CLASS;
269 		di->next = di->prev = AUDIO_MIXER_LAST;
270 		strlcpy(di->label.name, AudioCoutputs, sizeof(di->label.name));
271 		di->type = AUDIO_MIXER_CLASS;
272 		return 0;
273 	}
274 
275 	return ENXIO;
276 }
277 
278 int
279 tascodec_trigger_output(void *cookie, void *start, void *end, int blksize,
280     void (*intr)(void *), void *intrarg, struct audio_params *params)
281 {
282 	struct tascodec_softc *sc = cookie;
283 
284 	tascodec_write(sc, PWR_CTL,
285 	    PWR_CTL_ISNS_PD | PWR_CTL_VSNS_PD | PWR_CTL_MODE_ACTIVE);
286 	return 0;
287 }
288 
289 int
290 tascodec_halt_output(void *cookie)
291 {
292 	struct tascodec_softc *sc = cookie;
293 
294 	tascodec_write(sc, PWR_CTL,
295 	    PWR_CTL_ISNS_PD | PWR_CTL_VSNS_PD | PWR_CTL_MODE_SHUTDOWN);
296 	return 0;
297 }
298 
299 uint8_t
300 tascodec_read(struct tascodec_softc *sc, int reg)
301 {
302 	uint8_t cmd = reg;
303 	uint8_t val;
304 	int error;
305 
306 	iic_acquire_bus(sc->sc_tag, I2C_F_POLL);
307 	error = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr,
308 	    &cmd, sizeof cmd, &val, sizeof val, I2C_F_POLL);
309 	iic_release_bus(sc->sc_tag, I2C_F_POLL);
310 
311 	if (error) {
312 		printf("%s: can't read register 0x%02x\n",
313 		    sc->sc_dev.dv_xname, reg);
314 		val = 0xff;
315 	}
316 
317 	return val;
318 }
319 
320 void
321 tascodec_write(struct tascodec_softc *sc, int reg, uint8_t val)
322 {
323 	uint8_t cmd = reg;
324 	int error;
325 
326 	iic_acquire_bus(sc->sc_tag, I2C_F_POLL);
327 	error = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr,
328 	    &cmd, sizeof cmd, &val, sizeof val, I2C_F_POLL);
329 	iic_release_bus(sc->sc_tag, I2C_F_POLL);
330 
331 	if (error) {
332 		printf("%s: can't write register 0x%02x\n",
333 		    sc->sc_dev.dv_xname, reg);
334 	}
335 }
336