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