From 4de3a55fcdbeed0d6f53681fb21737cd69e767af Mon Sep 17 00:00:00 2001 From: kettenis Date: Fri, 3 Feb 2023 13:22:59 +0000 Subject: [PATCH] Add sncodec(4) a driver for the TI SN012776/TAS2764 digital amplifier. ok ratchov@ --- sys/dev/fdt/sncodec.c | 379 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 379 insertions(+) create mode 100644 sys/dev/fdt/sncodec.c diff --git a/sys/dev/fdt/sncodec.c b/sys/dev/fdt/sncodec.c new file mode 100644 index 00000000000..bcc656be65f --- /dev/null +++ b/sys/dev/fdt/sncodec.c @@ -0,0 +1,379 @@ +/* $OpenBSD: sncodec.c,v 1.1 2023/02/03 13:22:59 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 + +#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_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_PCM_MIN 0xc9 +#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; +}; + +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"); + + 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); + } + + sc->sc_dvc = sncodec_read(sc, DVC); + if (sc->sc_dvc > DVC_PCM_MIN) + sc->sc_dvc = DVC_PCM_MIN; + + /* 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 TAS2770 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_OUTPUT_CLASS +}; + +int +sncodec_set_port(void *priv, mixer_ctrl_t *mc) +{ + struct sncodec_softc *sc = priv; + u_char level; + + switch (mc->dev) { + case SNCODEC_MASTER_VOL: + level = mc->un.value.level[AUDIO_MIXER_LEVEL_MONO]; + sc->sc_dvc = (DVC_PCM_MIN * (255 - level)) / 255; + sncodec_write(sc, DVC, sc->sc_dvc); + 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_PCM_MIN); + mc->un.value.level[AUDIO_MIXER_LEVEL_MONO] = level; + 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->next = di->prev = AUDIO_MIXER_LAST; + 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_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 | MODE_CTRL_MODE_ACTIVE); + 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); + } +} -- 2.20.1