1/*	$OpenBSD: tascodec.c,v 1.8 2023/12/26 09:25:15 kettenis 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/ofw_regulator.h>
30#include <dev/ofw/fdt.h>
31
32#include <dev/i2c/i2cvar.h>
33
34#include <dev/audio_if.h>
35
36#define PWR_CTL				0x02
37#define  PWR_CTL_ISNS_PD		(1 << 3)
38#define  PWR_CTL_VSNS_PD		(1 << 2)
39#define  PWR_CTL_MODE_MASK		(3 << 0)
40#define  PWR_CTL_MODE_ACTIVE		(0 << 0)
41#define  PWR_CTL_MODE_MUTE		(1 << 0)
42#define  PWR_CTL_MODE_SHUTDOWN		(2 << 0)
43#define PB_CFG2				0x05
44#define  PB_CFG2_DVC_PCM_MIN		0xc9
45#define  PB_CFG2_DVC_PCM_30DB		0x3c
46#define TDM_CFG0			0x0a
47#define  TDM_CFG0_FRAME_START		(1 << 0)
48#define TDM_CFG1			0x0b
49#define  TDM_CFG1_RX_JUSTIFY		(1 << 6)
50#define  TDM_CFG1_RX_OFFSET_MASK	(0x1f << 1)
51#define  TDM_CFG1_RX_OFFSET_SHIFT	1
52#define  TDM_CFG1_RX_EDGE		(1 << 0)
53#define TDM_CFG2			0x0c
54#define  TDM_CFG2_SCFG_MASK		(3 << 4)
55#define  TDM_CFG2_SCFG_MONO_LEFT	(1 << 4)
56#define  TDM_CFG2_SCFG_MONO_RIGHT	(2 << 4)
57#define  TDM_CFG2_SCFG_STEREO_DOWNMIX	(3 << 4)
58#define TDM_CFG3			0x0d
59#define  TDM_CFG3_RX_SLOT_R_MASK	0xf0
60#define  TDM_CFG3_RX_SLOT_R_SHIFT	4
61#define  TDM_CFG3_RX_SLOT_L_MASK	0x0f
62#define  TDM_CFG3_RX_SLOT_L_SHIFT	0
63
64struct tascodec_softc {
65	struct device		sc_dev;
66	i2c_tag_t		sc_tag;
67	i2c_addr_t		sc_addr;
68
69	struct dai_device	sc_dai;
70	uint8_t			sc_dvc;
71	uint8_t			sc_mute;
72};
73
74int	tascodec_match(struct device *, void *, void *);
75void	tascodec_attach(struct device *, struct device *, void *);
76int	tascodec_activate(struct device *, int);
77
78const struct cfattach tascodec_ca = {
79	sizeof(struct tascodec_softc), tascodec_match, tascodec_attach,
80	NULL, tascodec_activate
81};
82
83struct cfdriver tascodec_cd = {
84	NULL, "tascodec", DV_DULL
85};
86
87int	tascodec_set_format(void *, uint32_t, uint32_t, uint32_t);
88int	tascodec_set_tdm_slot(void *, int);
89
90int	tascodec_set_port(void *, mixer_ctrl_t *);
91int	tascodec_get_port(void *, mixer_ctrl_t *);
92int	tascodec_query_devinfo(void *, mixer_devinfo_t *);
93int	tascodec_trigger_output(void *, void *, void *, int,
94	    void (*)(void *), void *, struct audio_params *);
95int	tascodec_halt_output(void *);
96
97const struct audio_hw_if tascodec_hw_if = {
98	.set_port = tascodec_set_port,
99	.get_port = tascodec_get_port,
100	.query_devinfo = tascodec_query_devinfo,
101	.trigger_output = tascodec_trigger_output,
102	.halt_output = tascodec_halt_output,
103};
104
105uint8_t	tascodec_read(struct tascodec_softc *, int);
106void	tascodec_write(struct tascodec_softc *, int, uint8_t);
107
108int
109tascodec_match(struct device *parent, void *match, void *aux)
110{
111	struct i2c_attach_args *ia = aux;
112
113	return iic_is_compatible(ia, "ti,tas2770");
114}
115
116void
117tascodec_attach(struct device *parent, struct device *self, void *aux)
118{
119	struct tascodec_softc *sc = (struct tascodec_softc *)self;
120	struct i2c_attach_args *ia = aux;
121	int node = *(int *)ia->ia_cookie;
122	uint32_t *sdz_gpio;
123	int sdz_gpiolen;
124	uint8_t cfg2;
125
126	sc->sc_tag = ia->ia_tag;
127	sc->sc_addr = ia->ia_addr;
128
129	printf("\n");
130
131	regulator_enable(OF_getpropint(node, "SDZ-supply", 0));
132
133	sdz_gpiolen = OF_getproplen(node, "shutdown-gpios");
134	if (sdz_gpiolen > 0) {
135		sdz_gpio = malloc(sdz_gpiolen, M_TEMP, M_WAITOK);
136		OF_getpropintarray(node, "shutdown-gpios",
137		    sdz_gpio, sdz_gpiolen);
138		gpio_controller_config_pin(sdz_gpio, GPIO_CONFIG_OUTPUT);
139		gpio_controller_set_pin(sdz_gpio, 1);
140		free(sdz_gpio, M_TEMP, sdz_gpiolen);
141		delay(1000);
142	}
143
144	/* Set volume to a reasonable level. */
145	sc->sc_dvc = PB_CFG2_DVC_PCM_30DB;
146	sc->sc_mute = PWR_CTL_MODE_ACTIVE;
147	tascodec_write(sc, PB_CFG2, sc->sc_dvc);
148
149	/* Default to stereo downmix mode for now. */
150	cfg2 = tascodec_read(sc, TDM_CFG2);
151	cfg2 &= ~TDM_CFG2_SCFG_MASK;
152	cfg2 |= TDM_CFG2_SCFG_STEREO_DOWNMIX;
153	tascodec_write(sc, TDM_CFG2, cfg2);
154
155	sc->sc_dai.dd_node = node;
156	sc->sc_dai.dd_cookie = sc;
157	sc->sc_dai.dd_hw_if = &tascodec_hw_if;
158	sc->sc_dai.dd_set_format = tascodec_set_format;
159	sc->sc_dai.dd_set_tdm_slot = tascodec_set_tdm_slot;
160	dai_register(&sc->sc_dai);
161}
162
163int
164tascodec_activate(struct device *self, int act)
165{
166	struct tascodec_softc *sc = (struct tascodec_softc *)self;
167
168	switch (act) {
169	case DVACT_POWERDOWN:
170		tascodec_write(sc, PWR_CTL,
171		    PWR_CTL_ISNS_PD | PWR_CTL_VSNS_PD | PWR_CTL_MODE_SHUTDOWN);
172		break;
173	}
174
175	return 0;
176}
177
178int
179tascodec_set_format(void *cookie, uint32_t fmt, uint32_t pol,
180    uint32_t clk)
181{
182	struct tascodec_softc *sc = cookie;
183	uint8_t cfg0, cfg1;
184
185	cfg0 = tascodec_read(sc, TDM_CFG0);
186	cfg1 = tascodec_read(sc, TDM_CFG1);
187	cfg1 &= ~TDM_CFG1_RX_OFFSET_MASK;
188
189	switch (fmt) {
190	case DAI_FORMAT_I2S:
191		cfg0 |= TDM_CFG0_FRAME_START;
192		cfg1 &= ~TDM_CFG1_RX_JUSTIFY;
193		cfg1 |= (1 << TDM_CFG1_RX_OFFSET_SHIFT);
194		cfg1 &= ~TDM_CFG1_RX_EDGE;
195		break;
196	case DAI_FORMAT_RJ:
197		cfg0 &= ~TDM_CFG0_FRAME_START;
198		cfg1 |= TDM_CFG1_RX_JUSTIFY;
199		cfg1 &= ~TDM_CFG1_RX_EDGE;
200		break;
201	case DAI_FORMAT_LJ:
202		cfg0 &= ~TDM_CFG0_FRAME_START;
203		cfg1 &= ~TDM_CFG1_RX_JUSTIFY;
204		cfg1 &= ~TDM_CFG1_RX_EDGE;
205		break;
206	default:
207		return EINVAL;
208	}
209
210	if (pol & DAI_POLARITY_IB)
211		cfg1 ^= TDM_CFG1_RX_EDGE;
212	if (pol & DAI_POLARITY_IF)
213		cfg0 ^= TDM_CFG0_FRAME_START;
214
215	if (!(clk & DAI_CLOCK_CBM) || !(clk & DAI_CLOCK_CFM))
216		return EINVAL;
217
218	tascodec_write(sc, TDM_CFG0, cfg0);
219	tascodec_write(sc, TDM_CFG1, cfg1);
220
221	return 0;
222}
223
224int
225tascodec_set_tdm_slot(void *cookie, int slot)
226{
227	struct tascodec_softc *sc = cookie;
228	uint8_t cfg2, cfg3;
229
230	if (slot < 0 || slot >= 16)
231		return EINVAL;
232
233	cfg2 = tascodec_read(sc, TDM_CFG2);
234	cfg3 = tascodec_read(sc, TDM_CFG3);
235	cfg2 &= ~TDM_CFG2_SCFG_MASK;
236	cfg2 |= TDM_CFG2_SCFG_MONO_LEFT;
237	cfg3 &= ~TDM_CFG3_RX_SLOT_L_MASK;
238	cfg3 |= slot << TDM_CFG3_RX_SLOT_L_SHIFT;
239	tascodec_write(sc, TDM_CFG2, cfg2);
240	tascodec_write(sc, TDM_CFG3, cfg3);
241
242	return 0;
243}
244
245/*
246 * Mixer controls; the gain of the TAS2770 is determined by the
247 * amplifier gain and digital volume control setting, but we only
248 * expose the digital volume control setting through the mixer
249 * interface.
250 */
251enum {
252	TASCODEC_MASTER_VOL,
253	TASCODEC_MASTER_MUTE,
254	TASCODEC_OUTPUT_CLASS
255};
256
257int
258tascodec_set_port(void *priv, mixer_ctrl_t *mc)
259{
260	struct tascodec_softc *sc = priv;
261	u_char level;
262	uint8_t mode;
263
264	switch (mc->dev) {
265	case TASCODEC_MASTER_VOL:
266		level = mc->un.value.level[AUDIO_MIXER_LEVEL_MONO];
267		sc->sc_dvc = (PB_CFG2_DVC_PCM_MIN * (255 - level)) / 255;
268		tascodec_write(sc, PB_CFG2, sc->sc_dvc);
269		return 0;
270
271	case TASCODEC_MASTER_MUTE:
272		sc->sc_mute = mc->un.ord ?
273		    PWR_CTL_MODE_MUTE : PWR_CTL_MODE_ACTIVE;
274		mode = tascodec_read(sc, PWR_CTL);
275		if ((mode & PWR_CTL_MODE_MASK) == PWR_CTL_MODE_ACTIVE ||
276		    (mode & PWR_CTL_MODE_MASK) == PWR_CTL_MODE_MUTE) {
277			mode &= ~PWR_CTL_MODE_MASK;
278			mode |= sc->sc_mute;
279			tascodec_write(sc, PWR_CTL, mode);
280		}
281		return 0;
282
283	}
284
285	return EINVAL;
286}
287
288int
289tascodec_get_port(void *priv, mixer_ctrl_t *mc)
290{
291	struct tascodec_softc *sc = priv;
292	u_char level;
293
294	switch (mc->dev) {
295	case TASCODEC_MASTER_VOL:
296		mc->un.value.num_channels = 1;
297		level = 255 - ((255 * sc->sc_dvc) / PB_CFG2_DVC_PCM_MIN);
298		mc->un.value.level[AUDIO_MIXER_LEVEL_MONO] = level;
299		return 0;
300
301	case TASCODEC_MASTER_MUTE:
302		mc->un.ord = (sc->sc_mute == PWR_CTL_MODE_MUTE);
303		return 0;
304	}
305
306	return EINVAL;
307}
308
309int
310tascodec_query_devinfo(void *priv, mixer_devinfo_t *di)
311{
312	switch (di->index) {
313	case TASCODEC_MASTER_VOL:
314		di->mixer_class = TASCODEC_OUTPUT_CLASS;
315		di->prev = AUDIO_MIXER_LAST;
316		di->next = TASCODEC_MASTER_MUTE;
317		strlcpy(di->label.name, AudioNmaster, sizeof(di->label.name));
318		di->type = AUDIO_MIXER_VALUE;
319		di->un.v.num_channels = 1;
320		strlcpy(di->un.v.units.name, AudioNvolume,
321		    sizeof(di->un.v.units.name));
322		return 0;
323
324	case TASCODEC_MASTER_MUTE:
325		di->mixer_class = TASCODEC_OUTPUT_CLASS;
326		di->prev = TASCODEC_MASTER_VOL;
327		di->next = AUDIO_MIXER_LAST;
328		strlcpy(di->label.name, AudioNmute, sizeof(di->label.name));
329		di->type = AUDIO_MIXER_ENUM;
330		di->un.e.num_mem = 2;
331		di->un.e.member[0].ord = 0;
332		strlcpy(di->un.e.member[0].label.name, AudioNoff,
333		    MAX_AUDIO_DEV_LEN);
334		di->un.e.member[1].ord = 1;
335		strlcpy(di->un.e.member[1].label.name, AudioNon,
336		    MAX_AUDIO_DEV_LEN);
337		return 0;
338
339	case TASCODEC_OUTPUT_CLASS:
340		di->mixer_class = TASCODEC_OUTPUT_CLASS;
341		di->next = di->prev = AUDIO_MIXER_LAST;
342		strlcpy(di->label.name, AudioCoutputs, sizeof(di->label.name));
343		di->type = AUDIO_MIXER_CLASS;
344		return 0;
345	}
346
347	return ENXIO;
348}
349
350int
351tascodec_trigger_output(void *cookie, void *start, void *end, int blksize,
352    void (*intr)(void *), void *intrarg, struct audio_params *params)
353{
354	struct tascodec_softc *sc = cookie;
355
356	tascodec_write(sc, PWR_CTL,
357	    PWR_CTL_ISNS_PD | PWR_CTL_VSNS_PD | sc->sc_mute);
358	return 0;
359}
360
361int
362tascodec_halt_output(void *cookie)
363{
364	struct tascodec_softc *sc = cookie;
365
366	tascodec_write(sc, PWR_CTL,
367	    PWR_CTL_ISNS_PD | PWR_CTL_VSNS_PD | PWR_CTL_MODE_SHUTDOWN);
368	return 0;
369}
370
371uint8_t
372tascodec_read(struct tascodec_softc *sc, int reg)
373{
374	uint8_t cmd = reg;
375	uint8_t val;
376	int error;
377
378	iic_acquire_bus(sc->sc_tag, I2C_F_POLL);
379	error = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr,
380	    &cmd, sizeof cmd, &val, sizeof val, I2C_F_POLL);
381	iic_release_bus(sc->sc_tag, I2C_F_POLL);
382
383	if (error) {
384		printf("%s: can't read register 0x%02x\n",
385		    sc->sc_dev.dv_xname, reg);
386		val = 0xff;
387	}
388
389	return val;
390}
391
392void
393tascodec_write(struct tascodec_softc *sc, int reg, uint8_t val)
394{
395	uint8_t cmd = reg;
396	int error;
397
398	iic_acquire_bus(sc->sc_tag, I2C_F_POLL);
399	error = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr,
400	    &cmd, sizeof cmd, &val, sizeof val, I2C_F_POLL);
401	iic_release_bus(sc->sc_tag, I2C_F_POLL);
402
403	if (error) {
404		printf("%s: can't write register 0x%02x\n",
405		    sc->sc_dev.dv_xname, reg);
406	}
407}
408