Module Name: src Committed By: jmcneill Date: Sun Aug 27 16:05:26 UTC 2017
Modified Files: src/sys/arch/arm/sunxi: files.sunxi sunxi_codec.c sunxi_codec.h src/sys/arch/evbarm/conf: SUNXI Added Files: src/sys/arch/arm/sunxi: sun4i_a10_codec.c sun4i_dma.c Log Message: Add sun4i DMA and audio codec support. To generate a diff of this commit: cvs rdiff -u -r1.19 -r1.20 src/sys/arch/arm/sunxi/files.sunxi cvs rdiff -u -r0 -r1.1 src/sys/arch/arm/sunxi/sun4i_a10_codec.c \ src/sys/arch/arm/sunxi/sun4i_dma.c cvs rdiff -u -r1.1 -r1.2 src/sys/arch/arm/sunxi/sunxi_codec.c \ src/sys/arch/arm/sunxi/sunxi_codec.h cvs rdiff -u -r1.24 -r1.25 src/sys/arch/evbarm/conf/SUNXI 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.19 src/sys/arch/arm/sunxi/files.sunxi:1.20 --- src/sys/arch/arm/sunxi/files.sunxi:1.19 Sun Aug 27 02:19:46 2017 +++ src/sys/arch/arm/sunxi/files.sunxi Sun Aug 27 16:05:26 2017 @@ -1,4 +1,4 @@ -# $NetBSD: files.sunxi,v 1.19 2017/08/27 02:19:46 jmcneill Exp $ +# $NetBSD: files.sunxi,v 1.20 2017/08/27 16:05:26 jmcneill Exp $ # # Configuration info for Allwinner sunxi family SoCs # @@ -124,7 +124,12 @@ device sunxiwdt: sysmon_wdog attach sunxiwdt at fdt with sunxi_wdt file arch/arm/sunxi/sunxi_wdt.c sunxi_wdt -# DMA controller +# DMA controller (sun4i) +device sun4idma +attach sun4idma at fdt with sun4i_dma +file arch/arm/sunxi/sun4i_dma.c sun4i_dma + +# DMA controller (sun6i) device sun6idma attach sun6idma at fdt with sun6i_dma file arch/arm/sunxi/sun6i_dma.c sun6i_dma @@ -133,8 +138,9 @@ file arch/arm/sunxi/sun6i_dma.c sun6i_d device sunxicodec: audiobus, auconv, mulaw, aurateconv attach sunxicodec at fdt with sunxi_codec file arch/arm/sunxi/sunxi_codec.c sunxi_codec +file arch/arm/sunxi/sun4i_a10_codec.c sunxi_codec -# Audio codec (analog part) +# H3 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 Index: src/sys/arch/arm/sunxi/sunxi_codec.c diff -u src/sys/arch/arm/sunxi/sunxi_codec.c:1.1 src/sys/arch/arm/sunxi/sunxi_codec.c:1.2 --- src/sys/arch/arm/sunxi/sunxi_codec.c:1.1 Sun Aug 6 17:15:45 2017 +++ src/sys/arch/arm/sunxi/sunxi_codec.c Sun Aug 27 16:05:26 2017 @@ -1,4 +1,4 @@ -/* $NetBSD: sunxi_codec.c,v 1.1 2017/08/06 17:15:45 jmcneill Exp $ */ +/* $NetBSD: sunxi_codec.c,v 1.2 2017/08/27 16:05:26 jmcneill Exp $ */ /*- * Copyright (c) 2014-2017 Jared McNeill <jmcne...@invisible.ca> @@ -29,7 +29,7 @@ #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 $"); +__KERNEL_RCSID(0, "$NetBSD: sunxi_codec.c,v 1.2 2017/08/27 16:05:26 jmcneill Exp $"); #include <sys/param.h> #include <sys/bus.h> @@ -90,6 +90,7 @@ __KERNEL_RCSID(0, "$NetBSD: sunxi_codec. #define AC_ADC_CNT(_sc) ((_sc)->sc_cfg->ADC_CNT) static const struct of_compat_data compat_data[] = { + A10_CODEC_COMPATDATA, H3_CODEC_COMPATDATA, { NULL } }; @@ -626,14 +627,12 @@ sunxi_codec_clock_init(int phandle) /* 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; + if (rst != NULL) { + error = fdtbus_reset_deassert(rst); + if (error != 0) { + aprint_error(": couldn't de-assert reset: %d\n", error); + return error; + } } return 0; Index: src/sys/arch/arm/sunxi/sunxi_codec.h diff -u src/sys/arch/arm/sunxi/sunxi_codec.h:1.1 src/sys/arch/arm/sunxi/sunxi_codec.h:1.2 --- src/sys/arch/arm/sunxi/sunxi_codec.h:1.1 Sun Aug 6 17:15:45 2017 +++ src/sys/arch/arm/sunxi/sunxi_codec.h Sun Aug 27 16:05:26 2017 @@ -1,4 +1,4 @@ -/* $NetBSD: sunxi_codec.h,v 1.1 2017/08/06 17:15:45 jmcneill Exp $ */ +/* $NetBSD: sunxi_codec.h,v 1.2 2017/08/27 16:05:26 jmcneill Exp $ */ /*- * Copyright (c) 2014-2017 Jared McNeill <jmcne...@invisible.ca> @@ -126,4 +126,8 @@ extern const struct sunxi_codec_conf sun #define H3_CODEC_COMPATDATA #endif +extern const struct sunxi_codec_conf sun4i_a10_codecconf; +#define A10_CODEC_COMPATDATA \ + { "allwinner,sun4i-a10-codec", (uintptr_t)&sun4i_a10_codecconf } + #endif /* !_ARM_SUNXI_CODEC_H */ Index: src/sys/arch/evbarm/conf/SUNXI diff -u src/sys/arch/evbarm/conf/SUNXI:1.24 src/sys/arch/evbarm/conf/SUNXI:1.25 --- src/sys/arch/evbarm/conf/SUNXI:1.24 Sun Aug 27 02:22:17 2017 +++ src/sys/arch/evbarm/conf/SUNXI Sun Aug 27 16:05:26 2017 @@ -1,5 +1,5 @@ # -# $NetBSD: SUNXI,v 1.24 2017/08/27 02:22:17 jmcneill Exp $ +# $NetBSD: SUNXI,v 1.25 2017/08/27 16:05:26 jmcneill Exp $ # # Allwinner sunxi family # @@ -114,7 +114,8 @@ sunxiintc* at fdt? pass 1 # Allwinner I # Memory controller # DMA controller -sun6idma* at fdt? # DMA controller +sun4idma* at fdt? # DMA controller (sun4i) +sun6idma* at fdt? # DMA controller (sun6i) # Clock and Reset controller Added files: Index: src/sys/arch/arm/sunxi/sun4i_a10_codec.c diff -u /dev/null src/sys/arch/arm/sunxi/sun4i_a10_codec.c:1.1 --- /dev/null Sun Aug 27 16:05:26 2017 +++ src/sys/arch/arm/sunxi/sun4i_a10_codec.c Sun Aug 27 16:05:26 2017 @@ -0,0 +1,256 @@ +/* $NetBSD: sun4i_a10_codec.c,v 1.1 2017/08/27 16:05:26 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: sun4i_a10_codec.c,v 1.1 2017/08/27 16:05:26 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 A10_DEFAULT_PAVOL 0x20 + +#define A10_DAC_ACTRL 0x10 +#define A10_DACAREN __BIT(31) +#define A10_DACALEN __BIT(30) +#define A10_MIXEN __BIT(29) +#define A10_LNG __BIT(26) +#define A10_FMG __BITS(25,23) +#define A10_MICG __BITS(22,20) +#define A10_LLNS __BIT(19) +#define A10_RLNS __BIT(18) +#define A10_LFMS __BIT(17) +#define A10_RFMS __BIT(16) +#define A10_LDACLMIXS __BIT(15) +#define A10_RDACRMIXS __BIT(14) +#define A10_LDACRMIXS __BIT(13) +#define A10_MICLS __BIT(12) +#define A10_MICRS __BIT(11) +#define A10_DACPAS __BIT(8) +#define A10_MIXPAS __BIT(7) +#define A10_PAMUTE __BIT(6) +#define A10_PAVOL __BITS(5,0) + +#define A10_ADC_ACTRL 0x28 +#define A10_ADCREN __BIT(31) +#define A10_ADCLEN __BIT(30) +#define A10_PREG1EN __BIT(29) +#define A10_PREG2EN __BIT(28) +#define A10_VMICEN __BIT(27) +#define A10_PREG1 __BITS(26,25) +#define A10_PREG2 __BITS(24,23) +#define A10_ADCG __BITS(22,20) +#define A10_ADCIS __BITS(19,17) +#define A10_LNRDF __BIT(16) +#define A10_LNPREG __BITS(15,13) +#define A10_MIC1NEN __BIT(12) +#define A10_DITHER __BIT(8) +#define A10_PA_EN __BIT(4) +#define A10_DDE __BIT(3) +#define A10_COMPTEN __BIT(2) +#define A10_PTDBS __BITS(1,0) + +enum a10_codec_mixer_ctrl { + A10_CODEC_OUTPUT_CLASS, + A10_CODEC_INPUT_CLASS, + + A10_CODEC_OUTPUT_MASTER_VOLUME, + A10_CODEC_INPUT_DAC_VOLUME, + + A10_CODEC_MIXER_CTRL_LAST +}; + +static const struct a10_codec_mixer { + const char * name; + enum a10_codec_mixer_ctrl mixer_class; + u_int reg; + u_int mask; +} a10_codec_mixers[A10_CODEC_MIXER_CTRL_LAST] = { + [A10_CODEC_OUTPUT_MASTER_VOLUME] = { AudioNmaster, + A10_CODEC_OUTPUT_CLASS, A10_DAC_ACTRL, A10_PAVOL }, + [A10_CODEC_INPUT_DAC_VOLUME] = { AudioNdac, + A10_CODEC_INPUT_CLASS, A10_DAC_ACTRL, A10_PAVOL }, +}; + +#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)) +#define SET4(sc, reg, mask) \ + WR4((sc), (reg), RD4((sc), (reg)) | (mask)) +#define CLR4(sc, reg, mask) \ + WR4((sc), (reg), RD4((sc), (reg)) & ~(mask)) + +static int +a10_codec_init(struct sunxi_codec_softc *sc) +{ + + /* Unmute PA */ + SET4(sc, A10_DAC_ACTRL, A10_PAMUTE); + /* Set initial volume */ + CLR4(sc, A10_DAC_ACTRL, A10_PAVOL); + SET4(sc, A10_DAC_ACTRL, __SHIFTIN(A10_DEFAULT_PAVOL, A10_PAVOL)); + /* Enable PA */ + SET4(sc, A10_ADC_ACTRL, A10_PA_EN); + + return 0; +} + +static void +a10_codec_mute(struct sunxi_codec_softc *sc, int mute, u_int mode) +{ + if (mode == AUMODE_PLAY) { + const uint32_t pmask = A10_DACAREN|A10_DACALEN|A10_DACPAS; + if (mute) { + /* Mute DAC l/r channels to output mixer */ + CLR4(sc, A10_DAC_ACTRL, pmask); + } else { + /* Enable DAC analog l/r channels and output mixer */ + SET4(sc, A10_DAC_ACTRL, pmask); + } + } else { + const uint32_t rmask = A10_ADCREN|A10_ADCLEN; + if (mute) { + /* Disable ADC analog l/r channels */ + CLR4(sc, A10_ADC_ACTRL, rmask); + } else { + /* Enable ADC analog l/r channels */ + SET4(sc, A10_ADC_ACTRL, rmask); + } + } +} + +static int +a10_codec_set_port(struct sunxi_codec_softc *sc, mixer_ctrl_t *mc) +{ + const struct a10_codec_mixer *mix; + u_int val, shift; + int nvol; + + switch (mc->dev) { + case A10_CODEC_OUTPUT_MASTER_VOLUME: + case A10_CODEC_INPUT_DAC_VOLUME: + mix = &a10_codec_mixers[mc->dev]; + val = RD4(sc, 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); + WR4(sc, mix->reg, val); + return 0; + } + + return ENXIO; +} + +static int +a10_codec_get_port(struct sunxi_codec_softc *sc, mixer_ctrl_t *mc) +{ + const struct a10_codec_mixer *mix; + u_int val, shift; + int nvol; + + switch (mc->dev) { + case A10_CODEC_OUTPUT_MASTER_VOLUME: + case A10_CODEC_INPUT_DAC_VOLUME: + mix = &a10_codec_mixers[mc->dev]; + val = RD4(sc, 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; + } + + return ENXIO; +} + +static int +a10_codec_query_devinfo(struct sunxi_codec_softc *sc, mixer_devinfo_t *di) +{ + const struct a10_codec_mixer *mix; + + switch (di->index) { + case A10_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 A10_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 A10_CODEC_OUTPUT_MASTER_VOLUME: + case A10_CODEC_INPUT_DAC_VOLUME: + mix = &a10_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; + } + + return ENXIO; +} + +const struct sunxi_codec_conf sun4i_a10_codecconf = { + .name = "A10 Audio Codec", + + .init = a10_codec_init, + .mute = a10_codec_mute, + .set_port = a10_codec_set_port, + .get_port = a10_codec_get_port, + .query_devinfo = a10_codec_query_devinfo, + + .DPC = 0x00, + .DAC_FIFOC = 0x04, + .DAC_FIFOS = 0x08, + .DAC_TXDATA = 0x0c, + .ADC_FIFOC = 0x1c, + .ADC_FIFOS = 0x20, + .ADC_RXDATA = 0x24, + .DAC_CNT = 0x30, + .ADC_CNT = 0x34, +}; Index: src/sys/arch/arm/sunxi/sun4i_dma.c diff -u /dev/null src/sys/arch/arm/sunxi/sun4i_dma.c:1.1 --- /dev/null Sun Aug 27 16:05:26 2017 +++ src/sys/arch/arm/sunxi/sun4i_dma.c Sun Aug 27 16:05:26 2017 @@ -0,0 +1,413 @@ +/* $NetBSD: sun4i_dma.c,v 1.1 2017/08/27 16:05:26 jmcneill Exp $ */ + +/*- + * Copyright (c) 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: sun4i_dma.c,v 1.1 2017/08/27 16:05:26 jmcneill Exp $"); + +#include <sys/param.h> +#include <sys/bus.h> +#include <sys/device.h> +#include <sys/intr.h> +#include <sys/systm.h> +#include <sys/mutex.h> +#include <sys/bitops.h> +#include <sys/kmem.h> + +#include <dev/fdt/fdtvar.h> + +#define DMA_MAX_TYPES 2 +#define DMA_TYPE_NORMAL 0 +#define DMA_TYPE_DEDICATED 1 +#define DMA_MAX_CHANNELS 8 +#define DMA_MAX_DRQS 32 + +#define DRQ_TYPE_SDRAM 0x16 + +#define DMA_IRQ_EN_REG 0x00 +#define DMA_IRQ_PEND_STAS_REG 0x04 +#define DMA_IRQ_PEND_STAS_END_MASK 0xaaaaaaaa +#define NDMA_CTRL_REG(n) (0x100 + (n) * 0x20) +#define NDMA_CTRL_LOAD __BIT(31) +#define NDMA_CTRL_CONTI_EN __BIT(30) +#define NDMA_CTRL_WAIT_STATE __BITS(29,27) +#define NDMA_CTRL_DST_DATA_WIDTH __BITS(26,25) +#define NDMA_CTRL_DST_BST_LEN __BITS(24,23) +#define NDMA_CTRL_DST_ADDR_TYPE __BIT(21) +#define NDMA_CTRL_DST_DRQ_TYPE __BITS(20,16) +#define NDMA_CTRL_BC_MODE_SEL __BIT(15) +#define NDMA_CTRL_SRC_DATA_WIDTH __BITS(10,9) +#define NDMA_CTRL_SRC_BST_LEN __BITS(8,7) +#define NDMA_CTRL_SRC_ADDR_TYPE __BIT(5) +#define NDMA_CTRL_SRC_DRQ_TYPE __BITS(4,0) +#define NDMA_SRC_ADDR_REG(n) (0x100 + (n) * 0x20 + 0x4) +#define NDMA_DEST_ADDR_REG(n) (0x100 + (n) * 0x20 + 0x8) +#define NDMA_BC_REG(n) (0x100 + (n) * 0x20 + 0xc) +#define DDMA_CTRL_REG(n) (0x300 + (n) * 0x20) +#define DDMA_CTRL_LOAD __BIT(31) +#define DDMA_CTRL_BSY_STA __BIT(30) +#define DDMA_CTRL_CONTI_EN __BIT(29) +#define DDMA_CTRL_DST_DATA_WIDTH __BITS(26,25) +#define DDMA_CTRL_DST_BST_LEN __BITS(24,23) +#define DDMA_CTRL_DST_ADDR_MODE __BITS(22,21) +#define DDMA_CTRL_DST_DRQ_TYPE __BITS(20,16) +#define DDMA_CTRL_BC_MODE_SEL __BIT(15) +#define DDMA_CTRL_SRC_DATA_WIDTH __BITS(10,9) +#define DDMA_CTRL_SRC_BST_LEN __BITS(8,7) +#define DDMA_CTRL_SRC_ADDR_MODE __BITS(6,5) +#define DDMA_CTRL_SRC_DRQ_TYPE __BITS(4,0) +#define DDMA_SRC_ADDR_REG(n) (0x300 + (n) * 0x20 + 0x4) +#define DDMA_DEST_ADDR_REG(n) (0x300 + (n) * 0x20 + 0x8) +#define DDMA_BC_REG(n) (0x300 + (n) * 0x20 + 0xc) +#define DDMA_PARA_REG(n) (0x300 + (n) * 0x20 + 0x18) +#define DDMA_PARA_DST_DATA_BLK_SIZE __BITS(31,24) +#define DDMA_PARA_DST_WAIT_CLK_CYC __BITS(23,16) +#define DDMA_PARA_SRC_DATA_BLK_SIZE __BITS(15,8) +#define DDMA_PARA_SRC_WAIT_CLK_CYC __BITS(7,0) + +static const struct of_compat_data compat_data[] = { + { "allwinner,sun4i-a10-dma", 1 }, + { NULL } +}; + +struct sun4idma_channel { + uint8_t ch_type; + uint8_t ch_index; + uint32_t ch_irqmask; + void (*ch_callback)(void *); + void *ch_callbackarg; + u_int ch_drq; +}; + +struct sun4idma_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; + void *sc_ih; + + kmutex_t sc_lock; + + struct sun4idma_channel sc_chan[DMA_MAX_TYPES][DMA_MAX_CHANNELS]; +}; + +#define DMA_READ(sc, reg) \ + bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg)) +#define DMA_WRITE(sc, reg, val) \ + bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val)) + +static void * +sun4idma_acquire(device_t dev, const void *data, size_t len, + void (*cb)(void *), void *cbarg) +{ + struct sun4idma_softc *sc = device_private(dev); + struct sun4idma_channel *ch = NULL; + const uint32_t *specifier = data; + uint32_t irqen; + uint8_t index; + + if (len != 8) + return NULL; + + const u_int type = be32toh(specifier[0]); + const u_int drq = be32toh(specifier[1]); + + if (type >= DMA_MAX_TYPES || drq >= DMA_MAX_DRQS) + return NULL; + + mutex_enter(&sc->sc_lock); + + for (index = 0; index < DMA_MAX_CHANNELS; index++) { + if (sc->sc_chan[type][index].ch_callback == NULL) { + ch = &sc->sc_chan[type][index]; + ch->ch_callback = cb; + ch->ch_callbackarg = cbarg; + ch->ch_drq = drq; + + irqen = DMA_READ(sc, DMA_IRQ_EN_REG); + irqen |= ch->ch_irqmask; + DMA_WRITE(sc, DMA_IRQ_EN_REG, irqen); + + break; + } + } + + mutex_exit(&sc->sc_lock); + + return ch; +} + +static void +sun4idma_release(device_t dev, void *priv) +{ + struct sun4idma_softc *sc = device_private(dev); + struct sun4idma_channel *ch = priv; + uint32_t irqen; + + mutex_enter(&sc->sc_lock); + + irqen = DMA_READ(sc, DMA_IRQ_EN_REG); + irqen &= ~ch->ch_irqmask; + DMA_WRITE(sc, DMA_IRQ_EN_REG, irqen); + + ch->ch_callback = NULL; + ch->ch_callbackarg = NULL; + + mutex_exit(&sc->sc_lock); +} + +static int +sun4idma_transfer_ndma(struct sun4idma_softc *sc, struct sun4idma_channel *ch, + struct fdtbus_dma_req *req) +{ + uint32_t cfg, mem_cfg, dev_cfg, src, dst; + uint32_t mem_width, dev_width, mem_burst, dev_burst; + + mem_width = req->dreq_mem_opt.opt_bus_width >> 4; + dev_width = req->dreq_dev_opt.opt_bus_width >> 4; + mem_burst = req->dreq_mem_opt.opt_burst_len == 1 ? 0 : + (req->dreq_mem_opt.opt_burst_len >> 3) + 1; + dev_burst = req->dreq_dev_opt.opt_burst_len == 1 ? 0 : + (req->dreq_dev_opt.opt_burst_len >> 3) + 1; + + mem_cfg = __SHIFTIN(mem_width, NDMA_CTRL_SRC_DATA_WIDTH) | + __SHIFTIN(mem_burst, NDMA_CTRL_SRC_BST_LEN) | + __SHIFTIN(DRQ_TYPE_SDRAM, NDMA_CTRL_SRC_DRQ_TYPE); + dev_cfg = __SHIFTIN(dev_width, NDMA_CTRL_SRC_DATA_WIDTH) | + __SHIFTIN(dev_burst, NDMA_CTRL_SRC_BST_LEN) | + __SHIFTIN(ch->ch_drq, NDMA_CTRL_SRC_DRQ_TYPE) | + NDMA_CTRL_SRC_ADDR_TYPE; + + if (req->dreq_dir == FDT_DMA_READ) { + src = req->dreq_dev_phys; + dst = req->dreq_segs[0].ds_addr; + cfg = mem_cfg << 16 | dev_cfg; + } else { + src = req->dreq_segs[0].ds_addr; + dst = req->dreq_dev_phys; + cfg = dev_cfg << 16 | mem_cfg; + } + + DMA_WRITE(sc, NDMA_SRC_ADDR_REG(ch->ch_index), src); + DMA_WRITE(sc, NDMA_DEST_ADDR_REG(ch->ch_index), dst); + DMA_WRITE(sc, NDMA_BC_REG(ch->ch_index), req->dreq_segs[0].ds_len); + DMA_WRITE(sc, NDMA_CTRL_REG(ch->ch_index), cfg | NDMA_CTRL_LOAD); + + return 0; +} + +static int +sun4idma_transfer_ddma(struct sun4idma_softc *sc, struct sun4idma_channel *ch, + struct fdtbus_dma_req *req) +{ + uint32_t cfg, mem_cfg, dev_cfg, src, dst; + uint32_t mem_width, dev_width, mem_burst, dev_burst; + + mem_width = req->dreq_mem_opt.opt_bus_width >> 4; + dev_width = req->dreq_dev_opt.opt_bus_width >> 4; + mem_burst = req->dreq_mem_opt.opt_burst_len == 1 ? 0 : + (req->dreq_mem_opt.opt_burst_len >> 3) + 1; + dev_burst = req->dreq_dev_opt.opt_burst_len == 1 ? 0 : + (req->dreq_dev_opt.opt_burst_len >> 3) + 1; + + mem_cfg = __SHIFTIN(mem_width, DDMA_CTRL_SRC_DATA_WIDTH) | + __SHIFTIN(mem_burst, DDMA_CTRL_SRC_BST_LEN) | + __SHIFTIN(DRQ_TYPE_SDRAM, DDMA_CTRL_SRC_DRQ_TYPE) | + __SHIFTIN(0, DDMA_CTRL_SRC_ADDR_MODE); + dev_cfg = __SHIFTIN(dev_width, DDMA_CTRL_SRC_DATA_WIDTH) | + __SHIFTIN(dev_burst, DDMA_CTRL_SRC_BST_LEN) | + __SHIFTIN(ch->ch_drq, DDMA_CTRL_SRC_DRQ_TYPE) | + __SHIFTIN(1, DDMA_CTRL_SRC_ADDR_MODE); + + if (req->dreq_dir == FDT_DMA_READ) { + src = req->dreq_dev_phys; + dst = req->dreq_segs[0].ds_addr; + cfg = mem_cfg << 16 | dev_cfg; + } else { + src = req->dreq_segs[0].ds_addr; + dst = req->dreq_dev_phys; + cfg = dev_cfg << 16 | mem_cfg; + } + + DMA_WRITE(sc, DDMA_SRC_ADDR_REG(ch->ch_index), src); + DMA_WRITE(sc, DDMA_DEST_ADDR_REG(ch->ch_index), dst); + DMA_WRITE(sc, DDMA_BC_REG(ch->ch_index), req->dreq_segs[0].ds_len); + DMA_WRITE(sc, DDMA_PARA_REG(ch->ch_index), 0); + DMA_WRITE(sc, DDMA_CTRL_REG(ch->ch_index), cfg | DDMA_CTRL_LOAD); + + return 0; +} + +static int +sun4idma_transfer(device_t dev, void *priv, struct fdtbus_dma_req *req) +{ + struct sun4idma_softc *sc = device_private(dev); + struct sun4idma_channel *ch = priv; + + if (req->dreq_nsegs != 1) + return EINVAL; + + if (ch->ch_type == DMA_TYPE_NORMAL) + return sun4idma_transfer_ndma(sc, ch, req); + else + return sun4idma_transfer_ddma(sc, ch, req); +} + +static void +sun4idma_halt(device_t dev, void *priv) +{ + struct sun4idma_softc *sc = device_private(dev); + struct sun4idma_channel *ch = priv; + + if (ch->ch_type == DMA_TYPE_NORMAL) + DMA_WRITE(sc, NDMA_CTRL_REG(ch->ch_index), 0); + else + DMA_WRITE(sc, DDMA_CTRL_REG(ch->ch_index), 0); +} + +static const struct fdtbus_dma_controller_func sun4idma_funcs = { + .acquire = sun4idma_acquire, + .release = sun4idma_release, + .transfer = sun4idma_transfer, + .halt = sun4idma_halt +}; + +static int +sun4idma_intr(void *priv) +{ + struct sun4idma_softc *sc = priv; + uint32_t pend, mask, bit; + uint8_t type, index; + + pend = DMA_READ(sc, DMA_IRQ_PEND_STAS_REG); + if (pend == 0) + return 0; + + DMA_WRITE(sc, DMA_IRQ_PEND_STAS_REG, pend); + + pend &= DMA_IRQ_PEND_STAS_END_MASK; + + while ((bit = ffs32(pend)) != 0) { + mask = __BIT(bit - 1); + pend &= ~mask; + type = ((bit - 1) / 2) / 8; + index = ((bit - 1) / 2) % 8; + + if (sc->sc_chan[type][index].ch_callback == NULL) + continue; + sc->sc_chan[type][index].ch_callback( + sc->sc_chan[type][index].ch_callbackarg); + } + + return 1; +} + +static int +sun4idma_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 +sun4idma_attach(device_t parent, device_t self, void *aux) +{ + struct sun4idma_softc * const sc = device_private(self); + struct fdt_attach_args * const faa = aux; + const int phandle = faa->faa_phandle; + struct clk *clk; + char intrstr[128]; + bus_addr_t addr; + bus_size_t size; + u_int index, type; + + if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) { + aprint_error(": couldn't get registers\n"); + return; + } + + if ((clk = fdtbus_clock_get_index(phandle, 0)) == NULL || + clk_enable(clk) != 0) { + aprint_error(": couldn't enable clock\n"); + return; + } + + sc->sc_dev = self; + sc->sc_phandle = phandle; + sc->sc_dmat = faa->faa_dmat; + 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; + } + mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_SCHED); + + if (!fdtbus_intr_str(phandle, 0, intrstr, sizeof(intrstr))) { + aprint_error(": failed to decode interrupt\n"); + return; + } + + aprint_naive("\n"); + aprint_normal(": DMA controller\n"); + + DMA_WRITE(sc, DMA_IRQ_EN_REG, 0); + DMA_WRITE(sc, DMA_IRQ_PEND_STAS_REG, ~0); + + for (type = 0; type < DMA_MAX_TYPES; type++) { + for (index = 0; index < DMA_MAX_CHANNELS; index++) { + struct sun4idma_channel *ch = &sc->sc_chan[type][index]; + ch->ch_type = type; + ch->ch_index = index; + ch->ch_irqmask = __BIT((type * 16) + (index * 2) + 1); + ch->ch_callback = NULL; + ch->ch_callbackarg = NULL; + + if (type == DMA_TYPE_NORMAL) + DMA_WRITE(sc, NDMA_CTRL_REG(index), 0); + else + DMA_WRITE(sc, DDMA_CTRL_REG(index), 0); + } + } + + sc->sc_ih = fdtbus_intr_establish(phandle, 0, IPL_SCHED, + FDT_INTR_MPSAFE, sun4idma_intr, sc); + if (sc->sc_ih == NULL) { + aprint_error_dev(sc->sc_dev, + "couldn't establish interrupt on %s\n", intrstr); + return; + } + aprint_normal_dev(sc->sc_dev, "interrupting on %s\n", intrstr); + + fdtbus_register_dma_controller(self, phandle, &sun4idma_funcs); +} + +CFATTACH_DECL_NEW(sun4i_dma, sizeof(struct sun4idma_softc), + sun4idma_match, sun4idma_attach, NULL, NULL);