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
64 struct 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
74 int tascodec_match(struct device *, void *, void *);
75 void tascodec_attach(struct device *, struct device *, void *);
76 int tascodec_activate(struct device *, int);
77
78 const struct cfattach tascodec_ca = {
79 sizeof(struct tascodec_softc), tascodec_match, tascodec_attach,
80 NULL, tascodec_activate
81 };
82
83 struct cfdriver tascodec_cd = {
84 NULL, "tascodec", DV_DULL
85 };
86
87 int tascodec_set_format(void *, uint32_t, uint32_t, uint32_t);
88 int tascodec_set_tdm_slot(void *, int);
89
90 int tascodec_set_port(void *, mixer_ctrl_t *);
91 int tascodec_get_port(void *, mixer_ctrl_t *);
92 int tascodec_query_devinfo(void *, mixer_devinfo_t *);
93 int tascodec_trigger_output(void *, void *, void *, int,
94 void (*)(void *), void *, struct audio_params *);
95 int tascodec_halt_output(void *);
96
97 const 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
105 uint8_t tascodec_read(struct tascodec_softc *, int);
106 void tascodec_write(struct tascodec_softc *, int, uint8_t);
107
108 int
tascodec_match(struct device * parent,void * match,void * aux)109 tascodec_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
116 void
tascodec_attach(struct device * parent,struct device * self,void * aux)117 tascodec_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
163 int
tascodec_activate(struct device * self,int act)164 tascodec_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
178 int
tascodec_set_format(void * cookie,uint32_t fmt,uint32_t pol,uint32_t clk)179 tascodec_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
224 int
tascodec_set_tdm_slot(void * cookie,int slot)225 tascodec_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 */
251 enum {
252 TASCODEC_MASTER_VOL,
253 TASCODEC_MASTER_MUTE,
254 TASCODEC_OUTPUT_CLASS
255 };
256
257 int
tascodec_set_port(void * priv,mixer_ctrl_t * mc)258 tascodec_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
288 int
tascodec_get_port(void * priv,mixer_ctrl_t * mc)289 tascodec_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
309 int
tascodec_query_devinfo(void * priv,mixer_devinfo_t * di)310 tascodec_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
350 int
tascodec_trigger_output(void * cookie,void * start,void * end,int blksize,void (* intr)(void *),void * intrarg,struct audio_params * params)351 tascodec_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
361 int
tascodec_halt_output(void * cookie)362 tascodec_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
371 uint8_t
tascodec_read(struct tascodec_softc * sc,int reg)372 tascodec_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
392 void
tascodec_write(struct tascodec_softc * sc,int reg,uint8_t val)393 tascodec_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