/* $OpenBSD: sncodec.c,v 1.4 2023/12/26 09:25:15 kettenis Exp $ */ /* * Copyright (c) 2023 Mark Kettenis * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define MODE_CTRL 0x02 #define MODE_CTRL_BOP_SRC (1 << 7) #define MODE_CTRL_ISNS_PD (1 << 4) #define MODE_CTRL_VSNS_PD (1 << 3) #define MODE_CTRL_MODE_MASK (7 << 0) #define MODE_CTRL_MODE_ACTIVE (0 << 0) #define MODE_CTRL_MODE_MUTE (1 << 0) #define MODE_CTRL_MODE_SHUTDOWN (2 << 0) #define TDM_CFG0 0x08 #define TDM_CFG0_FRAME_START (1 << 0) #define TDM_CFG1 0x09 #define TDM_CFG1_RX_JUSTIFY (1 << 6) #define TDM_CFG1_RX_OFFSET_MASK (0x1f << 1) #define TDM_CFG1_RX_OFFSET_SHIFT 1 #define TDM_CFG1_RX_EDGE (1 << 0) #define TDM_CFG2 0x0a #define TDM_CFG2_SCFG_MASK (3 << 4) #define TDM_CFG2_SCFG_MONO_LEFT (1 << 4) #define TDM_CFG2_SCFG_MONO_RIGHT (2 << 4) #define TDM_CFG2_SCFG_STEREO_DOWNMIX (3 << 4) #define TDM_CFG3 0x0c #define TDM_CFG3_RX_SLOT_R_MASK 0xf0 #define TDM_CFG3_RX_SLOT_R_SHIFT 4 #define TDM_CFG3_RX_SLOT_L_MASK 0x0f #define TDM_CFG3_RX_SLOT_L_SHIFT 0 #define DVC 0x1a #define DVC_LVL_MIN 0xc9 #define DVC_LVL_30DB 0x3c #define BOP_CFG0 0x1d uint8_t sncodec_bop_cfg[] = { 0x01, 0x32, 0x02, 0x22, 0x83, 0x2d, 0x80, 0x02, 0x06, 0x32, 0x40, 0x30, 0x02, 0x06, 0x38, 0x40, 0x30, 0x02, 0x06, 0x3e, 0x37, 0x30, 0xff, 0xe6 }; struct sncodec_softc { struct device sc_dev; i2c_tag_t sc_tag; i2c_addr_t sc_addr; struct dai_device sc_dai; uint8_t sc_dvc; uint8_t sc_mute; }; int sncodec_match(struct device *, void *, void *); void sncodec_attach(struct device *, struct device *, void *); int sncodec_activate(struct device *, int); const struct cfattach sncodec_ca = { sizeof(struct sncodec_softc), sncodec_match, sncodec_attach, NULL, sncodec_activate }; struct cfdriver sncodec_cd = { NULL, "sncodec", DV_DULL }; int sncodec_set_format(void *, uint32_t, uint32_t, uint32_t); int sncodec_set_tdm_slot(void *, int); int sncodec_set_port(void *, mixer_ctrl_t *); int sncodec_get_port(void *, mixer_ctrl_t *); int sncodec_query_devinfo(void *, mixer_devinfo_t *); int sncodec_trigger_output(void *, void *, void *, int, void (*)(void *), void *, struct audio_params *); int sncodec_halt_output(void *); const struct audio_hw_if sncodec_hw_if = { .set_port = sncodec_set_port, .get_port = sncodec_get_port, .query_devinfo = sncodec_query_devinfo, .trigger_output = sncodec_trigger_output, .halt_output = sncodec_halt_output, }; uint8_t sncodec_read(struct sncodec_softc *, int); void sncodec_write(struct sncodec_softc *, int, uint8_t); int sncodec_match(struct device *parent, void *match, void *aux) { struct i2c_attach_args *ia = aux; return iic_is_compatible(ia, "ti,tas2764"); } void sncodec_attach(struct device *parent, struct device *self, void *aux) { struct sncodec_softc *sc = (struct sncodec_softc *)self; struct i2c_attach_args *ia = aux; int node = *(int *)ia->ia_cookie; uint32_t *sdz_gpio; int sdz_gpiolen; uint8_t cfg2; int i; sc->sc_tag = ia->ia_tag; sc->sc_addr = ia->ia_addr; printf("\n"); regulator_enable(OF_getpropint(node, "SDZ-supply", 0)); sdz_gpiolen = OF_getproplen(node, "shutdown-gpios"); if (sdz_gpiolen > 0) { sdz_gpio = malloc(sdz_gpiolen, M_TEMP, M_WAITOK); OF_getpropintarray(node, "shutdown-gpios", sdz_gpio, sdz_gpiolen); gpio_controller_config_pin(sdz_gpio, GPIO_CONFIG_OUTPUT); gpio_controller_set_pin(sdz_gpio, 1); free(sdz_gpio, M_TEMP, sdz_gpiolen); delay(1000); } /* Set volume to a reasonable level. */ sc->sc_dvc = DVC_LVL_30DB; sc->sc_mute = MODE_CTRL_MODE_ACTIVE; sncodec_write(sc, DVC, sc->sc_dvc); /* Default to stereo downmix mode for now. */ cfg2 = sncodec_read(sc, TDM_CFG2); cfg2 &= ~TDM_CFG2_SCFG_MASK; cfg2 |= TDM_CFG2_SCFG_STEREO_DOWNMIX; sncodec_write(sc, TDM_CFG2, cfg2); /* Configure brownout prevention. */ for (i = 0; i < nitems(sncodec_bop_cfg); i++) sncodec_write(sc, BOP_CFG0 + i, sncodec_bop_cfg[i]); sc->sc_dai.dd_node = node; sc->sc_dai.dd_cookie = sc; sc->sc_dai.dd_hw_if = &sncodec_hw_if; sc->sc_dai.dd_set_format = sncodec_set_format; sc->sc_dai.dd_set_tdm_slot = sncodec_set_tdm_slot; dai_register(&sc->sc_dai); } int sncodec_activate(struct device *self, int act) { struct sncodec_softc *sc = (struct sncodec_softc *)self; switch (act) { case DVACT_POWERDOWN: sncodec_write(sc, MODE_CTRL, MODE_CTRL_ISNS_PD | MODE_CTRL_VSNS_PD | MODE_CTRL_MODE_SHUTDOWN); break; } return 0; } int sncodec_set_format(void *cookie, uint32_t fmt, uint32_t pol, uint32_t clk) { struct sncodec_softc *sc = cookie; uint8_t cfg0, cfg1; cfg0 = sncodec_read(sc, TDM_CFG0); cfg1 = sncodec_read(sc, TDM_CFG1); cfg1 &= ~TDM_CFG1_RX_OFFSET_MASK; switch (fmt) { case DAI_FORMAT_I2S: cfg0 |= TDM_CFG0_FRAME_START; cfg1 &= ~TDM_CFG1_RX_JUSTIFY; cfg1 |= (1 << TDM_CFG1_RX_OFFSET_SHIFT); cfg1 &= ~TDM_CFG1_RX_EDGE; break; case DAI_FORMAT_RJ: cfg0 &= ~TDM_CFG0_FRAME_START; cfg1 |= TDM_CFG1_RX_JUSTIFY; cfg1 &= ~TDM_CFG1_RX_EDGE; break; case DAI_FORMAT_LJ: cfg0 &= ~TDM_CFG0_FRAME_START; cfg1 &= ~TDM_CFG1_RX_JUSTIFY; cfg1 &= ~TDM_CFG1_RX_EDGE; break; default: return EINVAL; } if (pol & DAI_POLARITY_IB) cfg1 ^= TDM_CFG1_RX_EDGE; if (pol & DAI_POLARITY_IF) cfg0 ^= TDM_CFG0_FRAME_START; if (!(clk & DAI_CLOCK_CBM) || !(clk & DAI_CLOCK_CFM)) return EINVAL; sncodec_write(sc, TDM_CFG0, cfg0); sncodec_write(sc, TDM_CFG1, cfg1); return 0; } int sncodec_set_tdm_slot(void *cookie, int slot) { struct sncodec_softc *sc = cookie; uint8_t cfg2, cfg3; if (slot < 0 || slot >= 16) return EINVAL; cfg2 = sncodec_read(sc, TDM_CFG2); cfg3 = sncodec_read(sc, TDM_CFG3); cfg2 &= ~TDM_CFG2_SCFG_MASK; cfg2 |= TDM_CFG2_SCFG_MONO_LEFT; cfg3 &= ~TDM_CFG3_RX_SLOT_L_MASK; cfg3 |= slot << TDM_CFG3_RX_SLOT_L_SHIFT; sncodec_write(sc, TDM_CFG2, cfg2); sncodec_write(sc, TDM_CFG3, cfg3); return 0; } /* * Mixer controls; the gain of the TAS2764 is determined by the * amplifier gain and digital volume control setting, but we only * expose the digital volume control setting through the mixer * interface. */ enum { SNCODEC_MASTER_VOL, SNCODEC_MASTER_MUTE, SNCODEC_OUTPUT_CLASS }; int sncodec_set_port(void *priv, mixer_ctrl_t *mc) { struct sncodec_softc *sc = priv; u_char level; uint8_t mode; switch (mc->dev) { case SNCODEC_MASTER_VOL: level = mc->un.value.level[AUDIO_MIXER_LEVEL_MONO]; sc->sc_dvc = (DVC_LVL_MIN * (255 - level)) / 255; sncodec_write(sc, DVC, sc->sc_dvc); return 0; case SNCODEC_MASTER_MUTE: sc->sc_mute = mc->un.ord ? MODE_CTRL_MODE_MUTE : MODE_CTRL_MODE_ACTIVE; mode = sncodec_read(sc, MODE_CTRL); if ((mode & MODE_CTRL_MODE_MASK) == MODE_CTRL_MODE_ACTIVE || (mode & MODE_CTRL_MODE_MASK) == MODE_CTRL_MODE_MUTE) { mode &= ~MODE_CTRL_MODE_MASK; mode |= sc->sc_mute; sncodec_write(sc, MODE_CTRL, mode); } return 0; } return EINVAL; } int sncodec_get_port(void *priv, mixer_ctrl_t *mc) { struct sncodec_softc *sc = priv; u_char level; switch (mc->dev) { case SNCODEC_MASTER_VOL: mc->un.value.num_channels = 1; level = 255 - ((255 * sc->sc_dvc) / DVC_LVL_MIN); mc->un.value.level[AUDIO_MIXER_LEVEL_MONO] = level; return 0; case SNCODEC_MASTER_MUTE: mc->un.ord = (sc->sc_mute == MODE_CTRL_MODE_MUTE); return 0; } return EINVAL; } int sncodec_query_devinfo(void *priv, mixer_devinfo_t *di) { switch (di->index) { case SNCODEC_MASTER_VOL: di->mixer_class = SNCODEC_OUTPUT_CLASS; di->prev = AUDIO_MIXER_LAST; di->next = SNCODEC_MASTER_MUTE; strlcpy(di->label.name, AudioNmaster, sizeof(di->label.name)); di->type = AUDIO_MIXER_VALUE; di->un.v.num_channels = 1; strlcpy(di->un.v.units.name, AudioNvolume, sizeof(di->un.v.units.name)); return 0; case SNCODEC_MASTER_MUTE: di->mixer_class = SNCODEC_OUTPUT_CLASS; di->prev = SNCODEC_MASTER_VOL; di->next = AUDIO_MIXER_LAST; strlcpy(di->label.name, AudioNmute, sizeof(di->label.name)); di->type = AUDIO_MIXER_ENUM; di->un.e.num_mem = 2; di->un.e.member[0].ord = 0; strlcpy(di->un.e.member[0].label.name, AudioNoff, MAX_AUDIO_DEV_LEN); di->un.e.member[1].ord = 1; strlcpy(di->un.e.member[1].label.name, AudioNon, MAX_AUDIO_DEV_LEN); return 0; case SNCODEC_OUTPUT_CLASS: di->mixer_class = SNCODEC_OUTPUT_CLASS; di->next = di->prev = AUDIO_MIXER_LAST; strlcpy(di->label.name, AudioCoutputs, sizeof(di->label.name)); di->type = AUDIO_MIXER_CLASS; return 0; } return ENXIO; } int sncodec_trigger_output(void *cookie, void *start, void *end, int blksize, void (*intr)(void *), void *intrarg, struct audio_params *params) { struct sncodec_softc *sc = cookie; sncodec_write(sc, MODE_CTRL, MODE_CTRL_BOP_SRC | MODE_CTRL_ISNS_PD | MODE_CTRL_VSNS_PD | sc->sc_mute); return 0; } int sncodec_halt_output(void *cookie) { struct sncodec_softc *sc = cookie; sncodec_write(sc, MODE_CTRL, MODE_CTRL_BOP_SRC | MODE_CTRL_ISNS_PD | MODE_CTRL_VSNS_PD | MODE_CTRL_MODE_SHUTDOWN); return 0; } uint8_t sncodec_read(struct sncodec_softc *sc, int reg) { uint8_t cmd = reg; uint8_t val; int error; iic_acquire_bus(sc->sc_tag, I2C_F_POLL); error = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr, &cmd, sizeof cmd, &val, sizeof val, I2C_F_POLL); iic_release_bus(sc->sc_tag, I2C_F_POLL); if (error) { printf("%s: can't read register 0x%02x\n", sc->sc_dev.dv_xname, reg); val = 0xff; } return val; } void sncodec_write(struct sncodec_softc *sc, int reg, uint8_t val) { uint8_t cmd = reg; int error; iic_acquire_bus(sc->sc_tag, I2C_F_POLL); error = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr, &cmd, sizeof cmd, &val, sizeof val, I2C_F_POLL); iic_release_bus(sc->sc_tag, I2C_F_POLL); if (error) { printf("%s: can't write register 0x%02x\n", sc->sc_dev.dv_xname, reg); } }