Module Name: src Committed By: jmcneill Date: Sun Aug 6 17:15:45 UTC 2017
Modified Files: src/sys/arch/arm/sunxi: files.sunxi Added Files: src/sys/arch/arm/sunxi: sun8i_h3_codec.c sunxi_codec.c sunxi_codec.h Log Message: Add support for Allwinner H3 audio codec. To generate a diff of this commit: cvs rdiff -u -r1.15 -r1.16 src/sys/arch/arm/sunxi/files.sunxi cvs rdiff -u -r0 -r1.1 src/sys/arch/arm/sunxi/sun8i_h3_codec.c \ src/sys/arch/arm/sunxi/sunxi_codec.c src/sys/arch/arm/sunxi/sunxi_codec.h Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/sys/arch/arm/sunxi/files.sunxi diff -u src/sys/arch/arm/sunxi/files.sunxi:1.15 src/sys/arch/arm/sunxi/files.sunxi:1.16 --- src/sys/arch/arm/sunxi/files.sunxi:1.15 Sat Aug 5 17:51:49 2017 +++ src/sys/arch/arm/sunxi/files.sunxi Sun Aug 6 17:15:45 2017 @@ -1,4 +1,4 @@ -# $NetBSD: files.sunxi,v 1.15 2017/08/05 17:51:49 jmcneill Exp $ +# $NetBSD: files.sunxi,v 1.16 2017/08/06 17:15:45 jmcneill Exp $ # # Configuration info for Allwinner sunxi family SoCs # @@ -113,6 +113,16 @@ device sun6idma attach sun6idma at fdt with sun6i_dma file arch/arm/sunxi/sun6i_dma.c sun6i_dma +# Audio codec +device sunxicodec: audiobus, auconv, mulaw, aurateconv +attach sunxicodec at fdt with sunxi_codec +file arch/arm/sunxi/sunxi_codec.c sunxi_codec + +# Audio codec (analog part) +device h3codec +attach h3codec at fdt with h3_codec +file arch/arm/sunxi/sun8i_h3_codec.c h3_codec needs-flag + # SOC parameters defflag opt_soc.h SOC_SUNXI defflag opt_soc.h SOC_SUN8I: SOC_SUNXI Added files: Index: src/sys/arch/arm/sunxi/sun8i_h3_codec.c diff -u /dev/null src/sys/arch/arm/sunxi/sun8i_h3_codec.c:1.1 --- /dev/null Sun Aug 6 17:15:45 2017 +++ src/sys/arch/arm/sunxi/sun8i_h3_codec.c Sun Aug 6 17:15:45 2017 @@ -0,0 +1,471 @@ +/* $NetBSD: sun8i_h3_codec.c,v 1.1 2017/08/06 17:15:45 jmcneill Exp $ */ + +/*- + * Copyright (c) 2014-2017 Jared McNeill <jmcne...@invisible.ca> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__KERNEL_RCSID(0, "$NetBSD: sun8i_h3_codec.c,v 1.1 2017/08/06 17:15:45 jmcneill Exp $"); + +#include <sys/param.h> +#include <sys/bus.h> +#include <sys/cpu.h> +#include <sys/device.h> +#include <sys/kmem.h> +#include <sys/bitops.h> + +#include <sys/audioio.h> +#include <dev/audio_if.h> + +#include <arm/sunxi/sunxi_codec.h> + +#define H3_PR_CFG 0x00 +#define H3_AC_PR_RW __BIT(24) +#define H3_AC_PR_RST __BIT(18) +#define H3_AC_PR_ADDR __BITS(20,16) +#define H3_ACDA_PR_WDAT __BITS(15,8) +#define H3_ACDA_PR_RDAT __BITS(7,0) + +#define H3_LOMIXSC 0x01 +#define H3_LOMIXSC_LDAC __BIT(1) +#define H3_ROMIXSC 0x02 +#define H3_ROMIXSC_RDAC __BIT(1) +#define H3_DAC_PA_SRC 0x03 +#define H3_DACAREN __BIT(7) +#define H3_DACALEN __BIT(6) +#define H3_RMIXEN __BIT(5) +#define H3_LMIXEN __BIT(4) +#define H3_LINEIN_GCTR 0x05 +#define H3_LINEING __BITS(6,4) +#define H3_MIC_GCTR 0x06 +#define H3_MIC1_GAIN __BITS(6,4) +#define H3_MIC2_GAIN __BITS(2,0) +#define H3_PAEN_CTR 0x07 +#define H3_LINEOUTEN __BIT(7) +#define H3_LINEOUT_VOLC 0x09 +#define H3_LINEOUTVOL __BITS(7,3) +#define H3_MIC2G_LINEOUT_CTR 0x0a +#define H3_LINEOUT_LSEL __BIT(3) +#define H3_LINEOUT_RSEL __BIT(2) +#define H3_LADCMIXSC 0x0c +#define H3_RADCMIXSC 0x0d +#define H3_ADCMIXSC_MIC1 __BIT(6) +#define H3_ADCMIXSC_MIC2 __BIT(5) +#define H3_ADCMIXSC_LINEIN __BIT(2) +#define H3_ADCMIXSC_OMIXER __BITS(1,0) +#define H3_ADC_AP_EN 0x0f +#define H3_ADCREN __BIT(7) +#define H3_ADCLEN __BIT(6) +#define H3_ADCG __BITS(2,0) + +struct h3_codec_softc { + device_t sc_dev; + bus_space_tag_t sc_bst; + bus_space_handle_t sc_bsh; + int sc_phandle; +}; + +enum h3_codec_mixer_ctrl { + H3_CODEC_OUTPUT_CLASS, + H3_CODEC_INPUT_CLASS, + H3_CODEC_RECORD_CLASS, + + H3_CODEC_OUTPUT_MASTER_VOLUME, + H3_CODEC_INPUT_DAC_VOLUME, + H3_CODEC_INPUT_LINEIN_VOLUME, + H3_CODEC_INPUT_MIC1_VOLUME, + H3_CODEC_INPUT_MIC2_VOLUME, + H3_CODEC_RECORD_AGC_VOLUME, + H3_CODEC_RECORD_SOURCE, + + H3_CODEC_MIXER_CTRL_LAST +}; + +static const struct h3_codec_mixer { + const char * name; + enum h3_codec_mixer_ctrl mixer_class; + u_int reg; + u_int mask; +} h3_codec_mixers[H3_CODEC_MIXER_CTRL_LAST] = { + [H3_CODEC_OUTPUT_MASTER_VOLUME] = { AudioNmaster, + H3_CODEC_OUTPUT_CLASS, H3_LINEOUT_VOLC, H3_LINEOUTVOL }, + [H3_CODEC_INPUT_DAC_VOLUME] = { AudioNdac, + H3_CODEC_INPUT_CLASS, H3_LINEOUT_VOLC, H3_LINEOUTVOL }, + [H3_CODEC_INPUT_LINEIN_VOLUME] = { AudioNline, + H3_CODEC_INPUT_CLASS, H3_LINEIN_GCTR, H3_LINEING }, + [H3_CODEC_INPUT_MIC1_VOLUME] = { "mic1", + H3_CODEC_INPUT_CLASS, H3_MIC_GCTR, H3_MIC1_GAIN }, + [H3_CODEC_INPUT_MIC2_VOLUME] = { "mic2", + H3_CODEC_INPUT_CLASS, H3_MIC_GCTR, H3_MIC2_GAIN }, + [H3_CODEC_RECORD_AGC_VOLUME] = { AudioNagc, + H3_CODEC_RECORD_CLASS, H3_ADC_AP_EN, H3_ADCG }, +}; + +#define RD4(sc, reg) \ + bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg)) +#define WR4(sc, reg, val) \ + bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val)) + +static struct h3_codec_softc * +h3_codec_find(int phandle) +{ + struct h3_codec_softc *csc; + device_t dev; + + dev = device_find_by_driver_unit("h3codec", 0); + if (dev == NULL) + return NULL; + csc = device_private(dev); + if (csc->sc_phandle != phandle) + return NULL; + + return csc; +} + +static u_int +h3_codec_pr_read(struct h3_codec_softc *csc, u_int addr) +{ + uint32_t val; + + /* Read current value */ + val = RD4(csc, H3_PR_CFG); + + /* De-assert reset */ + val |= H3_AC_PR_RST; + WR4(csc, H3_PR_CFG, val); + + /* Read mode */ + val &= ~H3_AC_PR_RW; + WR4(csc, H3_PR_CFG, val); + + /* Set address */ + val &= ~H3_AC_PR_ADDR; + val |= __SHIFTIN(addr, H3_AC_PR_ADDR); + WR4(csc, H3_PR_CFG, val); + + /* Read data */ + return __SHIFTOUT(RD4(csc, H3_PR_CFG), H3_ACDA_PR_RDAT); +} + +static void +h3_codec_pr_write(struct h3_codec_softc *csc, u_int addr, u_int data) +{ + uint32_t val; + + /* Read current value */ + val = RD4(csc, H3_PR_CFG); + + /* De-assert reset */ + val |= H3_AC_PR_RST; + WR4(csc, H3_PR_CFG, val); + + /* Set address */ + val &= ~H3_AC_PR_ADDR; + val |= __SHIFTIN(addr, H3_AC_PR_ADDR); + WR4(csc, H3_PR_CFG, val); + + /* Write data */ + val &= ~H3_ACDA_PR_WDAT; + val |= __SHIFTIN(data, H3_ACDA_PR_WDAT); + WR4(csc, H3_PR_CFG, val); + + /* Write mode */ + val |= H3_AC_PR_RW; + WR4(csc, H3_PR_CFG, val); +} + +static void +h3_codec_pr_set_clear(struct h3_codec_softc *csc, u_int addr, u_int set, u_int clr) +{ + u_int old, new; + + old = h3_codec_pr_read(csc, addr); + new = set | (old & ~clr); + h3_codec_pr_write(csc, addr, new); +} + +static int +h3_codec_init(struct sunxi_codec_softc *sc) +{ + struct h3_codec_softc *csc; + int phandle; + + /* Lookup the codec analog controls phandle */ + phandle = fdtbus_get_phandle(sc->sc_phandle, + "allwinner,codec-analog-controls"); + if (phandle < 0) { + aprint_error_dev(sc->sc_dev, + "missing allwinner,codec-analog-controls property\n"); + return ENXIO; + } + + /* Find a matching h3codec instance */ + sc->sc_codec_priv = h3_codec_find(phandle); + if (sc->sc_codec_priv == NULL) { + aprint_error_dev(sc->sc_dev, "couldn't find codec analog controls\n"); + return ENOENT; + } + csc = sc->sc_codec_priv; + + /* Right & Left LINEOUT enable */ + h3_codec_pr_set_clear(csc, H3_PAEN_CTR, H3_LINEOUTEN, 0); + h3_codec_pr_set_clear(csc, H3_MIC2G_LINEOUT_CTR, + H3_LINEOUT_LSEL | H3_LINEOUT_RSEL, 0); + + return 0; +} + +static void +h3_codec_mute(struct sunxi_codec_softc *sc, int mute, u_int mode) +{ + struct h3_codec_softc * const csc = sc->sc_codec_priv; + + if (mode == AUMODE_PLAY) { + if (mute) { + /* Mute DAC l/r channels to output mixer */ + h3_codec_pr_set_clear(csc, H3_LOMIXSC, + 0, H3_LOMIXSC_LDAC); + h3_codec_pr_set_clear(csc, H3_ROMIXSC, + 0, H3_ROMIXSC_RDAC); + /* Disable DAC analog l/r channels and output mixer */ + h3_codec_pr_set_clear(csc, H3_DAC_PA_SRC, + 0, H3_DACAREN | H3_DACALEN | H3_RMIXEN | H3_LMIXEN); + } else { + /* Enable DAC analog l/r channels and output mixer */ + h3_codec_pr_set_clear(csc, H3_DAC_PA_SRC, + H3_DACAREN | H3_DACALEN | H3_RMIXEN | H3_LMIXEN, 0); + /* Unmute DAC l/r channels to output mixer */ + h3_codec_pr_set_clear(csc, H3_LOMIXSC, H3_LOMIXSC_LDAC, 0); + h3_codec_pr_set_clear(csc, H3_ROMIXSC, H3_ROMIXSC_RDAC, 0); + } + } else { + if (mute) { + /* Disable ADC analog l/r channels */ + h3_codec_pr_set_clear(csc, H3_ADC_AP_EN, + 0, H3_ADCREN | H3_ADCLEN); + } else { + /* Enable ADC analog l/r channels */ + h3_codec_pr_set_clear(csc, H3_ADC_AP_EN, + H3_ADCREN | H3_ADCLEN, 0); + } + } +} + +static int +h3_codec_set_port(struct sunxi_codec_softc *sc, mixer_ctrl_t *mc) +{ + struct h3_codec_softc * const csc = sc->sc_codec_priv; + const struct h3_codec_mixer *mix; + u_int val, shift; + int nvol; + + switch (mc->dev) { + case H3_CODEC_OUTPUT_MASTER_VOLUME: + case H3_CODEC_INPUT_DAC_VOLUME: + case H3_CODEC_INPUT_LINEIN_VOLUME: + case H3_CODEC_INPUT_MIC1_VOLUME: + case H3_CODEC_INPUT_MIC2_VOLUME: + case H3_CODEC_RECORD_AGC_VOLUME: + mix = &h3_codec_mixers[mc->dev]; + val = h3_codec_pr_read(csc, mix->reg); + shift = 8 - fls32(__SHIFTOUT_MASK(mix->mask)); + nvol = mc->un.value.level[AUDIO_MIXER_LEVEL_LEFT] >> shift; + val &= ~mix->mask; + val |= __SHIFTIN(nvol, mix->mask); + h3_codec_pr_write(csc, mix->reg, val); + return 0; + + case H3_CODEC_RECORD_SOURCE: + h3_codec_pr_write(csc, H3_LADCMIXSC, mc->un.mask); + h3_codec_pr_write(csc, H3_RADCMIXSC, mc->un.mask); + return 0; + } + + return ENXIO; +} + +static int +h3_codec_get_port(struct sunxi_codec_softc *sc, mixer_ctrl_t *mc) +{ + struct h3_codec_softc * const csc = sc->sc_codec_priv; + const struct h3_codec_mixer *mix; + u_int val, shift; + int nvol; + + switch (mc->dev) { + case H3_CODEC_OUTPUT_MASTER_VOLUME: + case H3_CODEC_INPUT_DAC_VOLUME: + case H3_CODEC_INPUT_LINEIN_VOLUME: + case H3_CODEC_INPUT_MIC1_VOLUME: + case H3_CODEC_INPUT_MIC2_VOLUME: + case H3_CODEC_RECORD_AGC_VOLUME: + mix = &h3_codec_mixers[mc->dev]; + val = h3_codec_pr_read(csc, mix->reg); + shift = 8 - fls32(__SHIFTOUT_MASK(mix->mask)); + nvol = __SHIFTOUT(val, mix->mask) << shift; + mc->un.value.level[AUDIO_MIXER_LEVEL_LEFT] = nvol; + mc->un.value.level[AUDIO_MIXER_LEVEL_RIGHT] = nvol; + return 0; + + case H3_CODEC_RECORD_SOURCE: + mc->un.mask = + h3_codec_pr_read(csc, H3_LADCMIXSC) | + h3_codec_pr_read(csc, H3_RADCMIXSC); + return 0; + } + + return ENXIO; +} + +static int +h3_codec_query_devinfo(struct sunxi_codec_softc *sc, mixer_devinfo_t *di) +{ + const struct h3_codec_mixer *mix; + + switch (di->index) { + case H3_CODEC_OUTPUT_CLASS: + di->mixer_class = di->index; + strcpy(di->label.name, AudioCoutputs); + di->type = AUDIO_MIXER_CLASS; + di->next = di->prev = AUDIO_MIXER_LAST; + return 0; + + case H3_CODEC_INPUT_CLASS: + di->mixer_class = di->index; + strcpy(di->label.name, AudioCinputs); + di->type = AUDIO_MIXER_CLASS; + di->next = di->prev = AUDIO_MIXER_LAST; + return 0; + + case H3_CODEC_RECORD_CLASS: + di->mixer_class = di->index; + strcpy(di->label.name, AudioCrecord); + di->type = AUDIO_MIXER_CLASS; + di->next = di->prev = AUDIO_MIXER_LAST; + return 0; + + case H3_CODEC_OUTPUT_MASTER_VOLUME: + case H3_CODEC_INPUT_DAC_VOLUME: + case H3_CODEC_INPUT_LINEIN_VOLUME: + case H3_CODEC_INPUT_MIC1_VOLUME: + case H3_CODEC_INPUT_MIC2_VOLUME: + case H3_CODEC_RECORD_AGC_VOLUME: + mix = &h3_codec_mixers[di->index]; + di->mixer_class = mix->mixer_class; + strcpy(di->label.name, mix->name); + di->un.v.delta = + 256 / (__SHIFTOUT_MASK(mix->mask) + 1); + di->type = AUDIO_MIXER_VALUE; + di->next = di->prev = AUDIO_MIXER_LAST; + di->un.v.num_channels = 2; + strcpy(di->un.v.units.name, AudioNvolume); + return 0; + + case H3_CODEC_RECORD_SOURCE: + di->mixer_class = H3_CODEC_RECORD_CLASS; + strcpy(di->label.name, AudioNsource); + di->type = AUDIO_MIXER_SET; + di->next = di->prev = AUDIO_MIXER_LAST; + di->un.s.num_mem = 4; + strcpy(di->un.s.member[0].label.name, AudioNline); + di->un.s.member[0].mask = H3_ADCMIXSC_LINEIN; + strcpy(di->un.s.member[1].label.name, "mic1"); + di->un.s.member[1].mask = H3_ADCMIXSC_MIC1; + strcpy(di->un.s.member[2].label.name, "mic2"); + di->un.s.member[2].mask = H3_ADCMIXSC_MIC2; + strcpy(di->un.s.member[3].label.name, AudioNdac); + di->un.s.member[3].mask = H3_ADCMIXSC_OMIXER; + return 0; + + } + + return ENXIO; +} + +const struct sunxi_codec_conf sun8i_h3_codecconf = { + .name = "H3 Audio Codec", + + .init = h3_codec_init, + .mute = h3_codec_mute, + .set_port = h3_codec_set_port, + .get_port = h3_codec_get_port, + .query_devinfo = h3_codec_query_devinfo, + + .DPC = 0x00, + .DAC_FIFOC = 0x04, + .DAC_FIFOS = 0x08, + .DAC_TXDATA = 0x20, + .ADC_FIFOC = 0x10, + .ADC_FIFOS = 0x14, + .ADC_RXDATA = 0x18, + .DAC_CNT = 0x40, + .ADC_CNT = 0x44, +}; + +/* + * Device glue, only here to claim resources on behalf of the sunxi_codec driver. + */ + +static const char * compatible[] = { + "allwinner,sun8i-h3-codec-analog", + NULL +}; + +static int +h3_codec_match(device_t parent, cfdata_t cf, void *aux) +{ + struct fdt_attach_args * const faa = aux; + + return of_match_compatible(faa->faa_phandle, compatible); +} + +static void +h3_codec_attach(device_t parent, device_t self, void *aux) +{ + struct h3_codec_softc * const sc = device_private(self); + struct fdt_attach_args * const faa = aux; + const int phandle = faa->faa_phandle; + bus_addr_t addr; + bus_size_t size; + + sc->sc_dev = self; + if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) { + aprint_error(": couldn't get registers\n"); + return; + } + sc->sc_bst = faa->faa_bst; + if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) { + aprint_error(": couldn't map registers\n"); + return; + } + + sc->sc_phandle = phandle; + + aprint_naive("\n"); + aprint_normal(": H3 Audio Codec (analog part)\n"); +} + +CFATTACH_DECL_NEW(h3_codec, sizeof(struct h3_codec_softc), + h3_codec_match, h3_codec_attach, NULL, NULL); Index: src/sys/arch/arm/sunxi/sunxi_codec.c diff -u /dev/null src/sys/arch/arm/sunxi/sunxi_codec.c:1.1 --- /dev/null Sun Aug 6 17:15:45 2017 +++ src/sys/arch/arm/sunxi/sunxi_codec.c Sun Aug 6 17:15:45 2017 @@ -0,0 +1,751 @@ +/* $NetBSD: sunxi_codec.c,v 1.1 2017/08/06 17:15:45 jmcneill Exp $ */ + +/*- + * Copyright (c) 2014-2017 Jared McNeill <jmcne...@invisible.ca> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "opt_ddb.h" + +#include <sys/cdefs.h> +__KERNEL_RCSID(0, "$NetBSD: sunxi_codec.c,v 1.1 2017/08/06 17:15:45 jmcneill Exp $"); + +#include <sys/param.h> +#include <sys/bus.h> +#include <sys/cpu.h> +#include <sys/device.h> +#include <sys/kmem.h> +#include <sys/gpio.h> + +#include <sys/audioio.h> +#include <dev/audio_if.h> +#include <dev/auconv.h> + +#include <dev/fdt/fdtvar.h> + +#include <arm/sunxi/sunxi_codec.h> + +#define TX_TRIG_LEVEL 0xf +#define RX_TRIG_LEVEL 0x7 +#define DRQ_CLR_CNT 0x3 + +#define AC_DAC_DPC(_sc) ((_sc)->sc_cfg->DPC) +#define DAC_DPC_EN_DA 0x80000000 +#define AC_DAC_FIFOC(_sc) ((_sc)->sc_cfg->DAC_FIFOC) +#define DAC_FIFOC_FS __BITS(31,29) +#define DAC_FS_48KHZ 0 +#define DAC_FS_32KHZ 1 +#define DAC_FS_24KHZ 2 +#define DAC_FS_16KHZ 3 +#define DAC_FS_12KHZ 4 +#define DAC_FS_8KHZ 5 +#define DAC_FS_192KHZ 6 +#define DAC_FS_96KHZ 7 +#define DAC_FIFOC_FIFO_MODE __BITS(25,24) +#define FIFO_MODE_24_31_8 0 +#define FIFO_MODE_16_31_16 0 +#define FIFO_MODE_16_15_0 1 +#define DAC_FIFOC_DRQ_CLR_CNT __BITS(22,21) +#define DAC_FIFOC_TX_TRIG_LEVEL __BITS(14,8) +#define DAC_FIFOC_MONO_EN __BIT(6) +#define DAC_FIFOC_TX_BITS __BIT(5) +#define DAC_FIFOC_DRQ_EN __BIT(4) +#define DAC_FIFOC_FIFO_FLUSH __BIT(0) +#define AC_DAC_FIFOS(_sc) ((_sc)->sc_cfg->DAC_FIFOS) +#define AC_DAC_TXDATA(_sc) ((_sc)->sc_cfg->DAC_TXDATA) +#define AC_ADC_FIFOC(_sc) ((_sc)->sc_cfg->ADC_FIFOC) +#define ADC_FIFOC_FS __BITS(31,29) +#define ADC_FS_48KHZ 0 +#define ADC_FIFOC_EN_AD __BIT(28) +#define ADC_FIFOC_RX_FIFO_MODE __BIT(24) +#define ADC_FIFOC_RX_TRIG_LEVEL __BITS(12,8) +#define ADC_FIFOC_MONO_EN __BIT(7) +#define ADC_FIFOC_RX_BITS __BIT(6) +#define ADC_FIFOC_DRQ_EN __BIT(4) +#define ADC_FIFOC_FIFO_FLUSH __BIT(0) +#define AC_ADC_FIFOS(_sc) ((_sc)->sc_cfg->ADC_FIFOS) +#define AC_ADC_RXDATA(_sc) ((_sc)->sc_cfg->ADC_RXDATA) +#define AC_DAC_CNT(_sc) ((_sc)->sc_cfg->DAC_CNT) +#define AC_ADC_CNT(_sc) ((_sc)->sc_cfg->ADC_CNT) + +static const struct of_compat_data compat_data[] = { + H3_CODEC_COMPATDATA, + { NULL } +}; + +#define CODEC_READ(sc, reg) \ + bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg)) +#define CODEC_WRITE(sc, reg, val) \ + bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val)) + +static int +sunxi_codec_allocdma(struct sunxi_codec_softc *sc, size_t size, + size_t align, struct sunxi_codec_dma *dma) +{ + int error; + + dma->dma_size = size; + error = bus_dmamem_alloc(sc->sc_dmat, dma->dma_size, align, 0, + dma->dma_segs, 1, &dma->dma_nsegs, BUS_DMA_WAITOK); + if (error) + return error; + + error = bus_dmamem_map(sc->sc_dmat, dma->dma_segs, dma->dma_nsegs, + dma->dma_size, &dma->dma_addr, BUS_DMA_WAITOK | BUS_DMA_COHERENT); + if (error) + goto free; + + error = bus_dmamap_create(sc->sc_dmat, dma->dma_size, dma->dma_nsegs, + dma->dma_size, 0, BUS_DMA_WAITOK, &dma->dma_map); + if (error) + goto unmap; + + error = bus_dmamap_load(sc->sc_dmat, dma->dma_map, dma->dma_addr, + dma->dma_size, NULL, BUS_DMA_WAITOK); + if (error) + goto destroy; + + return 0; + +destroy: + bus_dmamap_destroy(sc->sc_dmat, dma->dma_map); +unmap: + bus_dmamem_unmap(sc->sc_dmat, dma->dma_addr, dma->dma_size); +free: + bus_dmamem_free(sc->sc_dmat, dma->dma_segs, dma->dma_nsegs); + + return error; +} + +static void +sunxi_codec_freedma(struct sunxi_codec_softc *sc, struct sunxi_codec_dma *dma) +{ + bus_dmamap_unload(sc->sc_dmat, dma->dma_map); + bus_dmamap_destroy(sc->sc_dmat, dma->dma_map); + bus_dmamem_unmap(sc->sc_dmat, dma->dma_addr, dma->dma_size); + bus_dmamem_free(sc->sc_dmat, dma->dma_segs, dma->dma_nsegs); +} + +static int +sunxi_codec_transfer(struct sunxi_codec_chan *ch) +{ + bus_dma_segment_t seg; + + seg.ds_addr = ch->ch_cur_phys; + seg.ds_len = ch->ch_blksize; + ch->ch_req.dreq_segs = &seg; + ch->ch_req.dreq_nsegs = 1; + + return fdtbus_dma_transfer(ch->ch_dma, &ch->ch_req); +} + +static int +sunxi_codec_open(void *priv, int flags) +{ + return 0; +} + +static void +sunxi_codec_close(void *priv) +{ +} + +static int +sunxi_codec_drain(void *priv) +{ + struct sunxi_codec_softc * const sc = priv; + uint32_t val; + + val = CODEC_READ(sc, AC_DAC_FIFOC(sc)); + CODEC_WRITE(sc, AC_DAC_FIFOC(sc), val | DAC_FIFOC_FIFO_FLUSH); + + val = CODEC_READ(sc, AC_ADC_FIFOC(sc)); + CODEC_WRITE(sc, AC_ADC_FIFOC(sc), val | ADC_FIFOC_FIFO_FLUSH); + + return 0; +} + +static int +sunxi_codec_query_encoding(void *priv, struct audio_encoding *ae) +{ + struct sunxi_codec_softc * const sc = priv; + + return auconv_query_encoding(sc->sc_encodings, ae); +} + +static int +sunxi_codec_set_params(void *priv, int setmode, int usemode, + audio_params_t *play, audio_params_t *rec, + stream_filter_list_t *pfil, stream_filter_list_t *rfil) +{ + struct sunxi_codec_softc * const sc = priv; + int index; + + if (play && (setmode & AUMODE_PLAY)) { + index = auconv_set_converter(&sc->sc_format, 1, + AUMODE_PLAY, play, true, pfil); + if (index < 0) + return EINVAL; + sc->sc_pchan.ch_params = pfil->req_size > 0 ? + pfil->filters[0].param : *play; + } + if (rec && (setmode & AUMODE_RECORD)) { + index = auconv_set_converter(&sc->sc_format, 1, + AUMODE_RECORD, rec, true, rfil); + if (index < 0) + return EINVAL; + sc->sc_rchan.ch_params = rfil->req_size > 0 ? + rfil->filters[0].param : *rec; + } + + return 0; +} + +static int +sunxi_codec_set_port(void *priv, mixer_ctrl_t *mc) +{ + struct sunxi_codec_softc * const sc = priv; + + return sc->sc_cfg->set_port(sc, mc); +} + +static int +sunxi_codec_get_port(void *priv, mixer_ctrl_t *mc) +{ + struct sunxi_codec_softc * const sc = priv; + + return sc->sc_cfg->get_port(sc, mc); +} + +static int +sunxi_codec_query_devinfo(void *priv, mixer_devinfo_t *di) +{ + struct sunxi_codec_softc * const sc = priv; + + return sc->sc_cfg->query_devinfo(sc, di); +} + +static void * +sunxi_codec_allocm(void *priv, int dir, size_t size) +{ + struct sunxi_codec_softc * const sc = priv; + struct sunxi_codec_dma *dma; + int error; + + dma = kmem_alloc(sizeof(*dma), KM_SLEEP); + + error = sunxi_codec_allocdma(sc, size, 16, dma); + if (error) { + kmem_free(dma, sizeof(*dma)); + device_printf(sc->sc_dev, "couldn't allocate DMA memory (%d)\n", + error); + return NULL; + } + + LIST_INSERT_HEAD(&sc->sc_dmalist, dma, dma_list); + + return dma->dma_addr; +} + +static void +sunxi_codec_freem(void *priv, void *addr, size_t size) +{ + struct sunxi_codec_softc * const sc = priv; + struct sunxi_codec_dma *dma; + + LIST_FOREACH(dma, &sc->sc_dmalist, dma_list) + if (dma->dma_addr == addr) { + sunxi_codec_freedma(sc, dma); + LIST_REMOVE(dma, dma_list); + kmem_free(dma, sizeof(*dma)); + break; + } +} + +static paddr_t +sunxi_codec_mappage(void *priv, void *addr, off_t off, int prot) +{ + struct sunxi_codec_softc * const sc = priv; + struct sunxi_codec_dma *dma; + + if (off < 0) + return -1; + + LIST_FOREACH(dma, &sc->sc_dmalist, dma_list) + if (dma->dma_addr == addr) { + return bus_dmamem_mmap(sc->sc_dmat, dma->dma_segs, + dma->dma_nsegs, off, prot, BUS_DMA_WAITOK); + } + + return -1; +} + +static int +sunxi_codec_getdev(void *priv, struct audio_device *adev) +{ + struct sunxi_codec_softc * const sc = priv; + + snprintf(adev->name, sizeof(adev->name), "Allwinner"); + snprintf(adev->version, sizeof(adev->version), "%s", + sc->sc_cfg->name); + snprintf(adev->config, sizeof(adev->config), "sunxicodec"); + + return 0; +} + +static int +sunxi_codec_get_props(void *priv) +{ + return AUDIO_PROP_PLAYBACK|AUDIO_PROP_CAPTURE| + AUDIO_PROP_INDEPENDENT|AUDIO_PROP_MMAP| + AUDIO_PROP_FULLDUPLEX; +} + +static int +sunxi_codec_round_blocksize(void *priv, int bs, int mode, + const audio_params_t *params) +{ + bs &= ~3; + if (bs == 0) + bs = 4; + return bs; +} + +static size_t +sunxi_codec_round_buffersize(void *priv, int dir, size_t bufsize) +{ + return bufsize; +} + +static int +sunxi_codec_trigger_output(void *priv, void *start, void *end, int blksize, + void (*intr)(void *), void *intrarg, const audio_params_t *params) +{ + struct sunxi_codec_softc * const sc = priv; + struct sunxi_codec_chan *ch = &sc->sc_pchan; + struct sunxi_codec_dma *dma; + bus_addr_t pstart; + bus_size_t psize; + uint32_t val; + int error; + + pstart = 0; + psize = (uintptr_t)end - (uintptr_t)start; + + LIST_FOREACH(dma, &sc->sc_dmalist, dma_list) + if (dma->dma_addr == start) { + pstart = dma->dma_map->dm_segs[0].ds_addr; + break; + } + if (pstart == 0) { + device_printf(sc->sc_dev, "bad addr %p\n", start); + return EINVAL; + } + + ch->ch_intr = intr; + ch->ch_intrarg = intrarg; + ch->ch_start_phys = ch->ch_cur_phys = pstart; + ch->ch_end_phys = pstart + psize; + ch->ch_blksize = blksize; + + /* Flush DAC FIFO */ + val = CODEC_READ(sc, AC_DAC_FIFOC(sc)); + CODEC_WRITE(sc, AC_DAC_FIFOC(sc), val | DAC_FIFOC_FIFO_FLUSH); + + /* Clear DAC FIFO status */ + val = CODEC_READ(sc, AC_DAC_FIFOS(sc)); + CODEC_WRITE(sc, AC_DAC_FIFOS(sc), val); + + /* Unmute output */ + if (sc->sc_cfg->mute) + sc->sc_cfg->mute(sc, 0, ch->ch_mode); + + /* Configure DAC FIFO */ + CODEC_WRITE(sc, AC_DAC_FIFOC(sc), + __SHIFTIN(DAC_FS_48KHZ, DAC_FIFOC_FS) | + __SHIFTIN(FIFO_MODE_16_15_0, DAC_FIFOC_FIFO_MODE) | + __SHIFTIN(DRQ_CLR_CNT, DAC_FIFOC_DRQ_CLR_CNT) | + __SHIFTIN(TX_TRIG_LEVEL, DAC_FIFOC_TX_TRIG_LEVEL)); + + /* Enable DAC DRQ */ + val = CODEC_READ(sc, AC_DAC_FIFOC(sc)); + CODEC_WRITE(sc, AC_DAC_FIFOC(sc), val | DAC_FIFOC_DRQ_EN); + + /* Start DMA transfer */ + error = sunxi_codec_transfer(ch); + if (error != 0) { + aprint_error_dev(sc->sc_dev, + "failed to start DMA transfer: %d\n", error); + return error; + } + + return 0; +} + +static int +sunxi_codec_trigger_input(void *priv, void *start, void *end, int blksize, + void (*intr)(void *), void *intrarg, const audio_params_t *params) +{ + struct sunxi_codec_softc * const sc = priv; + struct sunxi_codec_chan *ch = &sc->sc_rchan; + struct sunxi_codec_dma *dma; + bus_addr_t pstart; + bus_size_t psize; + uint32_t val; + int error; + + pstart = 0; + psize = (uintptr_t)end - (uintptr_t)start; + + LIST_FOREACH(dma, &sc->sc_dmalist, dma_list) + if (dma->dma_addr == start) { + pstart = dma->dma_map->dm_segs[0].ds_addr; + break; + } + if (pstart == 0) { + device_printf(sc->sc_dev, "bad addr %p\n", start); + return EINVAL; + } + + ch->ch_intr = intr; + ch->ch_intrarg = intrarg; + ch->ch_start_phys = ch->ch_cur_phys = pstart; + ch->ch_end_phys = pstart + psize; + ch->ch_blksize = blksize; + + /* Flush ADC FIFO */ + val = CODEC_READ(sc, AC_ADC_FIFOC(sc)); + CODEC_WRITE(sc, AC_ADC_FIFOC(sc), val | ADC_FIFOC_FIFO_FLUSH); + + /* Clear ADC FIFO status */ + val = CODEC_READ(sc, AC_ADC_FIFOS(sc)); + CODEC_WRITE(sc, AC_ADC_FIFOS(sc), val); + + /* Unmute input */ + if (sc->sc_cfg->mute) + sc->sc_cfg->mute(sc, 0, ch->ch_mode); + + /* Configure ADC FIFO */ + CODEC_WRITE(sc, AC_ADC_FIFOC(sc), + __SHIFTIN(ADC_FS_48KHZ, ADC_FIFOC_FS) | + __SHIFTIN(RX_TRIG_LEVEL, ADC_FIFOC_RX_TRIG_LEVEL) | + ADC_FIFOC_EN_AD | ADC_FIFOC_RX_FIFO_MODE); + + /* Enable ADC DRQ */ + val = CODEC_READ(sc, AC_ADC_FIFOC(sc)); + CODEC_WRITE(sc, AC_ADC_FIFOC(sc), val | ADC_FIFOC_DRQ_EN); + + /* Start DMA transfer */ + error = sunxi_codec_transfer(ch); + if (error != 0) { + aprint_error_dev(sc->sc_dev, + "failed to start DMA transfer: %d\n", error); + return error; + } + + return 0; +} + +static int +sunxi_codec_halt_output(void *priv) +{ + struct sunxi_codec_softc * const sc = priv; + struct sunxi_codec_chan *ch = &sc->sc_pchan; + uint32_t val; + + /* Disable DMA channel */ + fdtbus_dma_halt(ch->ch_dma); + + /* Mute output */ + if (sc->sc_cfg->mute) + sc->sc_cfg->mute(sc, 1, ch->ch_mode); + + /* Disable DAC DRQ */ + val = CODEC_READ(sc, AC_DAC_FIFOC(sc)); + CODEC_WRITE(sc, AC_DAC_FIFOC(sc), val & ~DAC_FIFOC_DRQ_EN); + + ch->ch_intr = NULL; + ch->ch_intrarg = NULL; + + return 0; +} + +static int +sunxi_codec_halt_input(void *priv) +{ + struct sunxi_codec_softc * const sc = priv; + struct sunxi_codec_chan *ch = &sc->sc_rchan; + uint32_t val; + + /* Disable DMA channel */ + fdtbus_dma_halt(ch->ch_dma); + + /* Mute output */ + if (sc->sc_cfg->mute) + sc->sc_cfg->mute(sc, 1, ch->ch_mode); + + /* Disable ADC DRQ */ + val = CODEC_READ(sc, AC_ADC_FIFOC(sc)); + CODEC_WRITE(sc, AC_ADC_FIFOC(sc), val & ~ADC_FIFOC_DRQ_EN); + + return 0; +} + +static void +sunxi_codec_get_locks(void *priv, kmutex_t **intr, kmutex_t **thread) +{ + struct sunxi_codec_softc * const sc = priv; + + *intr = &sc->sc_intr_lock; + *thread = &sc->sc_lock; +} + +static const struct audio_hw_if sunxi_codec_hw_if = { + .open = sunxi_codec_open, + .close = sunxi_codec_close, + .drain = sunxi_codec_drain, + .query_encoding = sunxi_codec_query_encoding, + .set_params = sunxi_codec_set_params, + .allocm = sunxi_codec_allocm, + .freem = sunxi_codec_freem, + .mappage = sunxi_codec_mappage, + .getdev = sunxi_codec_getdev, + .set_port = sunxi_codec_set_port, + .get_port = sunxi_codec_get_port, + .query_devinfo = sunxi_codec_query_devinfo, + .get_props = sunxi_codec_get_props, + .round_blocksize = sunxi_codec_round_blocksize, + .round_buffersize = sunxi_codec_round_buffersize, + .trigger_output = sunxi_codec_trigger_output, + .trigger_input = sunxi_codec_trigger_input, + .halt_output = sunxi_codec_halt_output, + .halt_input = sunxi_codec_halt_input, + .get_locks = sunxi_codec_get_locks, +}; + +static void +sunxi_codec_dmaintr(void *priv) +{ + struct sunxi_codec_chan * const ch = priv; + + ch->ch_cur_phys += ch->ch_blksize; + if (ch->ch_cur_phys >= ch->ch_end_phys) + ch->ch_cur_phys = ch->ch_start_phys; + + if (ch->ch_intr) { + ch->ch_intr(ch->ch_intrarg); + sunxi_codec_transfer(ch); + } +} + +static int +sunxi_codec_chan_init(struct sunxi_codec_softc *sc, + struct sunxi_codec_chan *ch, u_int mode, const char *dmaname) +{ + ch->ch_sc = sc; + ch->ch_mode = mode; + ch->ch_dma = fdtbus_dma_get(sc->sc_phandle, dmaname, sunxi_codec_dmaintr, ch); + if (ch->ch_dma == NULL) { + aprint_error(": couldn't get dma channel \"%s\"\n", dmaname); + return ENXIO; + } + + if (mode == AUMODE_PLAY) { + ch->ch_req.dreq_dir = FDT_DMA_WRITE; + ch->ch_req.dreq_dev_phys = + sc->sc_baseaddr + AC_DAC_TXDATA(sc); + } else { + ch->ch_req.dreq_dir = FDT_DMA_READ; + ch->ch_req.dreq_dev_phys = + sc->sc_baseaddr + AC_ADC_RXDATA(sc); + } + ch->ch_req.dreq_mem_opt.opt_bus_width = 16; + ch->ch_req.dreq_mem_opt.opt_burst_len = 4; + ch->ch_req.dreq_dev_opt.opt_bus_width = 16; + ch->ch_req.dreq_dev_opt.opt_burst_len = 4; + + return 0; +} + +static int +sunxi_codec_clock_init(int phandle) +{ + struct fdtbus_reset *rst; + struct clk *clk; + int error; + + /* Set codec clock to 24.576MHz, suitable for 48 kHz sampling rates */ + clk = fdtbus_clock_get(phandle, "codec"); + if (clk == NULL) { + aprint_error(": couldn't find codec clock\n"); + return ENXIO; + } + error = clk_set_rate(clk, 24576000); + if (error != 0) { + aprint_error(": couldn't set codec clock rate: %d\n", error); + return error; + } + error = clk_enable(clk); + if (error != 0) { + aprint_error(": couldn't enable codec clock: %d\n", error); + return error; + } + + /* Enable APB clock */ + clk = fdtbus_clock_get(phandle, "apb"); + if (clk == NULL) { + aprint_error(": couldn't find apb clock\n"); + return ENXIO; + } + error = clk_enable(clk); + if (error != 0) { + aprint_error(": couldn't enable apb clock: %d\n", error); + return error; + } + + /* De-assert reset */ + rst = fdtbus_reset_get_index(phandle, 0); + if (rst == NULL) { + aprint_error(": couldn't find reset\n"); + return ENXIO; + } + error = fdtbus_reset_deassert(rst); + if (error != 0) { + aprint_error(": couldn't de-assert reset: %d\n", error); + return error; + } + + return 0; +} + +static int +sunxi_codec_match(device_t parent, cfdata_t cf, void *aux) +{ + struct fdt_attach_args * const faa = aux; + + return of_match_compat_data(faa->faa_phandle, compat_data); +} + +static void +sunxi_codec_attach(device_t parent, device_t self, void *aux) +{ + struct sunxi_codec_softc * const sc = device_private(self); + struct fdt_attach_args * const faa = aux; + const int phandle = faa->faa_phandle; + bus_addr_t addr; + bus_size_t size; + uint32_t val; + int error; + + if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) { + aprint_error(": couldn't get registers\n"); + return; + } + + if (sunxi_codec_clock_init(phandle) != 0) + return; + + sc->sc_dev = self; + sc->sc_phandle = phandle; + sc->sc_baseaddr = addr; + sc->sc_bst = faa->faa_bst; + if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) { + aprint_error(": couldn't map registers\n"); + return; + } + sc->sc_dmat = faa->faa_dmat; + LIST_INIT(&sc->sc_dmalist); + sc->sc_cfg = (void *)of_search_compatible(phandle, compat_data)->data; + mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_NONE); + mutex_init(&sc->sc_intr_lock, MUTEX_DEFAULT, IPL_SCHED); + + if (sunxi_codec_chan_init(sc, &sc->sc_pchan, AUMODE_PLAY, "tx") != 0 || + sunxi_codec_chan_init(sc, &sc->sc_rchan, AUMODE_RECORD, "rx") != 0) { + aprint_error(": couldn't setup channels\n"); + return; + } + + /* Optional PA mute GPIO */ + sc->sc_pin_pa = fdtbus_gpio_acquire(phandle, "allwinner,pa-gpios", GPIO_PIN_OUTPUT); + if (sc->sc_pin_pa != NULL) + fdtbus_gpio_write(sc->sc_pin_pa, 1); + + aprint_naive("\n"); + aprint_normal(": %s\n", sc->sc_cfg->name); + + /* Enable DAC */ + val = CODEC_READ(sc, AC_DAC_DPC(sc)); + val |= DAC_DPC_EN_DA; + CODEC_WRITE(sc, AC_DAC_DPC(sc), val); + + /* Initialize codec */ + if (sc->sc_cfg->init(sc) != 0) { + aprint_error_dev(self, "couldn't initialize codec\n"); + return; + } + + sc->sc_format.mode = AUMODE_PLAY|AUMODE_RECORD; + sc->sc_format.encoding = AUDIO_ENCODING_SLINEAR_LE; + sc->sc_format.validbits = 16; + sc->sc_format.precision = 16; + sc->sc_format.channels = 2; + sc->sc_format.channel_mask = AUFMT_STEREO; + sc->sc_format.frequency_type = 0; + sc->sc_format.frequency[0] = sc->sc_format.frequency[1] = 48000; + + error = auconv_create_encodings(&sc->sc_format, 1, &sc->sc_encodings); + if (error) { + aprint_error_dev(self, "couldn't create encodings\n"); + return; + } + + audio_attach_mi(&sunxi_codec_hw_if, sc, self); +} + +CFATTACH_DECL_NEW(sunxi_codec, sizeof(struct sunxi_codec_softc), + sunxi_codec_match, sunxi_codec_attach, NULL, NULL); + +#ifdef DDB +void sunxicodec_dump(void); + +void +sunxicodec_dump(void) +{ + struct sunxi_codec_softc *sc; + device_t dev; + + dev = device_find_by_driver_unit("sunxicodec", 0); + if (dev == NULL) + return; + sc = device_private(dev); + + device_printf(dev, "AC_DAC_DPC: %08x\n", CODEC_READ(sc, AC_DAC_DPC(sc))); + device_printf(dev, "AC_DAC_FIFOC: %08x\n", CODEC_READ(sc, AC_DAC_FIFOC(sc))); + device_printf(dev, "AC_DAC_FIFOS: %08x\n", CODEC_READ(sc, AC_DAC_FIFOS(sc))); + device_printf(dev, "AC_ADC_FIFOC: %08x\n", CODEC_READ(sc, AC_ADC_FIFOC(sc))); + device_printf(dev, "AC_ADC_FIFOS: %08x\n", CODEC_READ(sc, AC_ADC_FIFOS(sc))); + device_printf(dev, "AC_DAC_CNT: %08x\n", CODEC_READ(sc, AC_DAC_CNT(sc))); + device_printf(dev, "AC_ADC_CNT: %08x\n", CODEC_READ(sc, AC_ADC_CNT(sc))); +} +#endif Index: src/sys/arch/arm/sunxi/sunxi_codec.h diff -u /dev/null src/sys/arch/arm/sunxi/sunxi_codec.h:1.1 --- /dev/null Sun Aug 6 17:15:45 2017 +++ src/sys/arch/arm/sunxi/sunxi_codec.h Sun Aug 6 17:15:45 2017 @@ -0,0 +1,129 @@ +/* $NetBSD: sunxi_codec.h,v 1.1 2017/08/06 17:15:45 jmcneill Exp $ */ + +/*- + * Copyright (c) 2014-2017 Jared McNeill <jmcne...@invisible.ca> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _ARM_SUNXI_CODEC_H +#define _ARM_SUNXI_CODEC_H + +#include <sys/audioio.h> +#include <dev/audio_if.h> +#include <dev/auconv.h> + +#include <dev/fdt/fdtvar.h> + +#include "h3_codec.h" + +struct sunxi_codec_softc; + +struct sunxi_codec_conf { + const char *name; + + /* initialize codec */ + int (*init)(struct sunxi_codec_softc *); + /* toggle DAC/ADC mute */ + void (*mute)(struct sunxi_codec_softc *, int, u_int); + /* mixer controls */ + int (*set_port)(struct sunxi_codec_softc *, + mixer_ctrl_t *); + int (*get_port)(struct sunxi_codec_softc *, + mixer_ctrl_t *); + int (*query_devinfo)(struct sunxi_codec_softc *, + mixer_devinfo_t *); + + /* register map */ + bus_size_t DPC, + DAC_FIFOC, + DAC_FIFOS, + DAC_TXDATA, + ADC_FIFOC, + ADC_FIFOS, + ADC_RXDATA, + DAC_CNT, + ADC_CNT; +}; + +struct sunxi_codec_chan { + struct sunxi_codec_softc *ch_sc; + u_int ch_mode; + + struct fdtbus_dma *ch_dma; + struct fdtbus_dma_req ch_req; + + audio_params_t ch_params; + + bus_addr_t ch_start_phys; + bus_addr_t ch_end_phys; + bus_addr_t ch_cur_phys; + int ch_blksize; + + void (*ch_intr)(void *); + void *ch_intrarg; +}; + +struct sunxi_codec_dma { + LIST_ENTRY(sunxi_codec_dma) dma_list; + bus_dmamap_t dma_map; + void *dma_addr; + size_t dma_size; + bus_dma_segment_t dma_segs[1]; + int dma_nsegs; +}; + +struct sunxi_codec_softc { + device_t sc_dev; + bus_space_tag_t sc_bst; + bus_space_handle_t sc_bsh; + bus_dma_tag_t sc_dmat; + int sc_phandle; + bus_addr_t sc_baseaddr; + + struct sunxi_codec_conf *sc_cfg; + void *sc_codec_priv; + + struct fdtbus_gpio_pin *sc_pin_pa; + + LIST_HEAD(, sunxi_codec_dma) sc_dmalist; + + kmutex_t sc_lock; + kmutex_t sc_intr_lock; + + struct audio_format sc_format; + struct audio_encoding_set *sc_encodings; + + struct sunxi_codec_chan sc_pchan; + struct sunxi_codec_chan sc_rchan; +}; + +#if NH3_CODEC > 0 +extern const struct sunxi_codec_conf sun8i_h3_codecconf; +#define H3_CODEC_COMPATDATA \ + { "allwinner,sun8i-h3-codec", (uintptr_t)&sun8i_h3_codecconf } +#else +#define H3_CODEC_COMPATDATA +#endif + +#endif /* !_ARM_SUNXI_CODEC_H */