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