Index: head/sys/arm/allwinner/a31_dmac.c =================================================================== --- head/sys/arm/allwinner/a31_dmac.c (nonexistent) +++ head/sys/arm/allwinner/a31_dmac.c (revision 308236) @@ -0,0 +1,557 @@ +/*- + * Copyright (c) 2016 Jared D. McNeill + * 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. + * + */ + +/* + * Allwinner DMA controller + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include + +#include "sunxi_dma_if.h" + +#define DMA_IRQ_EN_REG0 0x00 +#define DMA_IRQ_EN_REG1 0x04 +#define DMA_IRQ_EN_REG(ch) (DMA_IRQ_EN_REG0 + ((ch) / 8) * 4) +#define DMA_PKG_IRQ_EN(ch) (1 << (((ch) % 8) * 4 + 1)) +#define DMA_PKG_IRQ_MASK 0x2222222222222222ULL +#define DMA_IRQ_PEND_REG0 0x10 +#define DMA_IRQ_PEND_REG1 0x14 +#define DMA_IRQ_PEND_REG(ch) (DMA_IRQ_PEND_REG0 + ((ch) / 8) * 4) +#define DMA_STA_REG 0x30 +#define DMA_EN_REG(n) (0x100 + (n) * 0x40 + 0x00) +#define DMA_EN (1 << 0) +#define DMA_PAU_REG(n) (0x100 + (n) * 0x40 + 0x04) +#define DMA_STAR_ADDR_REG(n) (0x100 + (n) * 0x40 + 0x08) +#define DMA_CFG_REG(n) (0x100 + (n) * 0x40 + 0x0c) +#define DMA_DEST_DATA_WIDTH (0x3 << 25) +#define DMA_DEST_DATA_WIDTH_SHIFT 25 +#define DMA_DEST_BST_LEN (0x3 << 22) +#define DMA_DEST_BST_LEN_SHIFT 22 +#define DMA_DEST_ADDR_MODE (0x1 << 21) +#define DMA_DEST_ADDR_MODE_SHIFT 21 +#define DMA_DEST_DRQ_TYPE (0x1f << 16) +#define DMA_DEST_DRQ_TYPE_SHIFT 16 +#define DMA_SRC_DATA_WIDTH (0x3 << 9) +#define DMA_SRC_DATA_WIDTH_SHIFT 9 +#define DMA_SRC_BST_LEN (0x3 << 6) +#define DMA_SRC_BST_LEN_SHIFT 6 +#define DMA_SRC_ADDR_MODE (0x1 << 5) +#define DMA_SRC_ADDR_MODE_SHIFT 5 +#define DMA_SRC_DRQ_TYPE (0x1f << 0) +#define DMA_SRC_DRQ_TYPE_SHIFT 0 +#define DMA_DATA_WIDTH_8BIT 0 +#define DMA_DATA_WIDTH_16BIT 1 +#define DMA_DATA_WIDTH_32BIT 2 +#define DMA_DATA_WIDTH_64BIT 3 +#define DMA_ADDR_MODE_LINEAR 0 +#define DMA_ADDR_MODE_IO 1 +#define DMA_BST_LEN_1 0 +#define DMA_BST_LEN_4 1 +#define DMA_BST_LEN_8 2 +#define DMA_BST_LEN_16 3 +#define DMA_CUR_SRC_REG(n) (0x100 + (n) * 0x40 + 0x10) +#define DMA_CUR_DEST_REG(n) (0x100 + (n) * 0x40 + 0x14) +#define DMA_BCNT_LEFT_REG(n) (0x100 + (n) * 0x40 + 0x18) +#define DMA_PARA_REG(n) (0x100 + (n) * 0x40 + 0x1c) +#define WAIT_CYC (0xff << 0) +#define WAIT_CYC_SHIFT 0 + +struct a31dmac_desc { + uint32_t config; + uint32_t srcaddr; + uint32_t dstaddr; + uint32_t bcnt; + uint32_t para; + uint32_t next; +#define DMA_NULL 0xfffff800 +}; +#define DESC_ALIGN 4 +#define DESC_SIZE sizeof(struct a31dmac_desc) + +struct a31dmac_config { + u_int nchans; +}; + +static const struct a31dmac_config a31_config = { .nchans = 16 }; +static const struct a31dmac_config h3_config = { .nchans = 12 }; +static const struct a31dmac_config a83t_config = { .nchans = 8 }; +static const struct a31dmac_config a64_config = { .nchans = 8 }; + +static struct ofw_compat_data compat_data[] = { + { "allwinner,sun6i-a31-dma", (uintptr_t)&a31_config }, + { "allwinner,sun8i-a83t-dma", (uintptr_t)&a83t_config }, + { "allwinner,sun8i-h3-dma", (uintptr_t)&h3_config }, + { "allwinner,sun50i-a64-dma", (uintptr_t)&a64_config }, + { NULL, (uintptr_t)NULL } +}; + +struct a31dmac_softc; + +struct a31dmac_channel { + struct a31dmac_softc * sc; + uint8_t index; + void (*callback)(void *); + void * callbackarg; + + bus_dmamap_t dmamap; + struct a31dmac_desc *desc; + bus_addr_t physaddr; +}; + +struct a31dmac_softc { + struct resource * res[2]; + struct mtx mtx; + void * ih; + + bus_dma_tag_t dmat; + + u_int nchans; + struct a31dmac_channel * chans; +}; + +static struct resource_spec a31dmac_spec[] = { + { SYS_RES_MEMORY, 0, RF_ACTIVE }, + { SYS_RES_IRQ, 0, RF_ACTIVE }, + { -1, 0 } +}; + +#define DMA_READ(sc, reg) bus_read_4((sc)->res[0], (reg)) +#define DMA_WRITE(sc, reg, val) bus_write_4((sc)->res[0], (reg), (val)) + +static void a31dmac_intr(void *); +static void a31dmac_dmamap_cb(void *, bus_dma_segment_t *, int, int); + +static int +a31dmac_probe(device_t dev) +{ + if (!ofw_bus_status_okay(dev)) + return (ENXIO); + + if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data) + return (ENXIO); + + device_set_desc(dev, "Allwinner DMA controller"); + return (BUS_PROBE_DEFAULT); +} + +static int +a31dmac_attach(device_t dev) +{ + struct a31dmac_softc *sc; + struct a31dmac_config *conf; + u_int index; + hwreset_t rst; + clk_t clk; + int error; + + sc = device_get_softc(dev); + conf = (void *)ofw_bus_search_compatible(dev, compat_data)->ocd_data; + clk = NULL; + rst = NULL; + + if (bus_alloc_resources(dev, a31dmac_spec, sc->res)) { + device_printf(dev, "cannot allocate resources for device\n"); + return (ENXIO); + } + + mtx_init(&sc->mtx, "a31 dmac", NULL, MTX_SPIN); + + /* Clock and reset setup */ + if (clk_get_by_ofw_index(dev, 0, 0, &clk) != 0) { + device_printf(dev, "cannot get clock\n"); + goto fail; + } + if (clk_enable(clk) != 0) { + device_printf(dev, "cannot enable clock\n"); + goto fail; + } + if (hwreset_get_by_ofw_idx(dev, 0, 0, &rst) != 0) { + device_printf(dev, "cannot get hwreset\n"); + goto fail; + } + if (hwreset_deassert(rst) != 0) { + device_printf(dev, "cannot de-assert reset\n"); + goto fail; + } + + /* Descriptor DMA */ + error = bus_dma_tag_create( + bus_get_dma_tag(dev), /* Parent tag */ + DESC_ALIGN, 0, /* alignment, boundary */ + BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ + BUS_SPACE_MAXADDR, /* highaddr */ + NULL, NULL, /* filter, filterarg */ + DESC_SIZE, 1, /* maxsize, nsegs */ + DESC_SIZE, /* maxsegsize */ + 0, /* flags */ + NULL, NULL, /* lockfunc, lockarg */ + &sc->dmat); + if (error != 0) { + device_printf(dev, "cannot create dma tag\n"); + goto fail; + } + + /* Disable all interrupts and clear pending status */ + DMA_WRITE(sc, DMA_IRQ_EN_REG0, 0); + DMA_WRITE(sc, DMA_IRQ_EN_REG1, 0); + DMA_WRITE(sc, DMA_IRQ_PEND_REG0, ~0); + DMA_WRITE(sc, DMA_IRQ_PEND_REG1, ~0); + + /* Initialize channels */ + sc->nchans = conf->nchans; + sc->chans = malloc(sizeof(*sc->chans) * sc->nchans, M_DEVBUF, + M_WAITOK | M_ZERO); + + for (index = 0; index < sc->nchans; index++) { + sc->chans[index].sc = sc; + sc->chans[index].index = index; + sc->chans[index].callback = NULL; + sc->chans[index].callbackarg = NULL; + + error = bus_dmamem_alloc(sc->dmat, + (void **)&sc->chans[index].desc, + BUS_DMA_WAITOK | BUS_DMA_COHERENT, + &sc->chans[index].dmamap); + if (error != 0) { + device_printf(dev, "cannot allocate dma mem\n"); + goto fail; + } + error = bus_dmamap_load(sc->dmat, sc->chans[index].dmamap, + sc->chans[index].desc, sizeof(*sc->chans[index].desc), + a31dmac_dmamap_cb, &sc->chans[index], BUS_DMA_WAITOK); + if (error != 0) { + device_printf(dev, "cannot load dma map\n"); + goto fail; + } + + DMA_WRITE(sc, DMA_EN_REG(index), 0); + } + + error = bus_setup_intr(dev, sc->res[1], INTR_MPSAFE | INTR_TYPE_MISC, + NULL, a31dmac_intr, sc, &sc->ih); + if (error != 0) { + device_printf(dev, "could not setup interrupt handler\n"); + bus_release_resources(dev, a31dmac_spec, sc->res); + mtx_destroy(&sc->mtx); + return (ENXIO); + } + + OF_device_register_xref(OF_xref_from_node(ofw_bus_get_node(dev)), dev); + return (0); + +fail: + for (index = 0; index < sc->nchans; index++) + if (sc->chans[index].desc != NULL) { + bus_dmamap_unload(sc->dmat, sc->chans[index].dmamap); + bus_dmamem_free(sc->dmat, sc->chans[index].desc, + sc->chans[index].dmamap); + } + if (sc->chans != NULL) + free(sc->chans, M_DEVBUF); + if (sc->ih != NULL) + bus_teardown_intr(dev, sc->res[1], sc->ih); + if (rst != NULL) + hwreset_release(rst); + if (clk != NULL) + clk_release(clk); + bus_release_resources(dev, a31dmac_spec, sc->res); + + return (ENXIO); +} + +static void +a31dmac_dmamap_cb(void *priv, bus_dma_segment_t *segs, int nsegs, int error) +{ + struct a31dmac_channel *ch; + + if (error != 0) + return; + + ch = priv; + ch->physaddr = segs[0].ds_addr; +} + +static void +a31dmac_intr(void *priv) +{ + struct a31dmac_softc *sc; + uint32_t pend0, pend1, bit; + uint64_t pend, mask; + u_int index; + + sc = priv; + pend0 = DMA_READ(sc, DMA_IRQ_PEND_REG0); + pend1 = sc->nchans > 8 ? DMA_READ(sc, DMA_IRQ_PEND_REG1) : 0; + if (pend0 == 0 && pend1 == 0) + return; + + if (pend0 != 0) + DMA_WRITE(sc, DMA_IRQ_PEND_REG0, pend0); + if (pend1 != 0) + DMA_WRITE(sc, DMA_IRQ_PEND_REG1, pend1); + + pend = pend0 | ((uint64_t)pend1 << 32); + + while ((bit = ffsll(pend & DMA_PKG_IRQ_MASK)) != 0) { + mask = (1U << (bit - 1)); + pend &= ~mask; + index = (bit - 1) / 4; + + if (index >= sc->nchans) + continue; + if (sc->chans[index].callback == NULL) + continue; + sc->chans[index].callback(sc->chans[index].callbackarg); + } +} + +static int +a31dmac_set_config(device_t dev, void *priv, const struct sunxi_dma_config *cfg) +{ + struct a31dmac_channel *ch; + uint32_t config, para; + unsigned int dst_dw, dst_bl, dst_wc, dst_am; + unsigned int src_dw, src_bl, src_wc, src_am; + + ch = priv; + + switch (cfg->dst_width) { + case 8: + dst_dw = DMA_DATA_WIDTH_8BIT; + break; + case 16: + dst_dw = DMA_DATA_WIDTH_16BIT; + break; + case 32: + dst_dw = DMA_DATA_WIDTH_32BIT; + break; + case 64: + dst_dw = DMA_DATA_WIDTH_64BIT; + break; + default: + return (EINVAL); + } + switch (cfg->dst_burst_len) { + case 1: + dst_bl = DMA_BST_LEN_1; + break; + case 4: + dst_bl = DMA_BST_LEN_4; + break; + case 8: + dst_bl = DMA_BST_LEN_8; + break; + case 16: + dst_bl = DMA_BST_LEN_16; + break; + default: + return (EINVAL); + } + switch (cfg->src_width) { + case 8: + src_dw = DMA_DATA_WIDTH_8BIT; + break; + case 16: + src_dw = DMA_DATA_WIDTH_16BIT; + break; + case 32: + src_dw = DMA_DATA_WIDTH_32BIT; + break; + case 64: + src_dw = DMA_DATA_WIDTH_64BIT; + default: + return (EINVAL); + } + switch (cfg->src_burst_len) { + case 1: + src_bl = DMA_BST_LEN_1; + break; + case 4: + src_bl = DMA_BST_LEN_4; + break; + case 8: + src_bl = DMA_BST_LEN_8; + break; + case 16: + src_bl = DMA_BST_LEN_16; + break; + default: + return (EINVAL); + } + dst_am = cfg->dst_noincr ? DMA_ADDR_MODE_IO : DMA_ADDR_MODE_LINEAR; + src_am = cfg->src_noincr ? DMA_ADDR_MODE_IO : DMA_ADDR_MODE_LINEAR; + dst_wc = cfg->dst_wait_cyc; + src_wc = cfg->src_wait_cyc; + if (dst_wc != src_wc) + return (EINVAL); + + config = (dst_dw << DMA_DEST_DATA_WIDTH_SHIFT) | + (dst_bl << DMA_DEST_BST_LEN_SHIFT) | + (dst_am << DMA_DEST_ADDR_MODE_SHIFT) | + (cfg->dst_drqtype << DMA_DEST_DRQ_TYPE_SHIFT) | + (src_dw << DMA_SRC_DATA_WIDTH_SHIFT) | + (src_bl << DMA_SRC_BST_LEN_SHIFT) | + (src_am << DMA_SRC_ADDR_MODE_SHIFT) | + (cfg->src_drqtype << DMA_SRC_DRQ_TYPE_SHIFT); + para = (dst_wc << WAIT_CYC_SHIFT); + + ch->desc->config = htole32(config); + ch->desc->para = htole32(para); + + return (0); +} + +static void * +a31dmac_alloc(device_t dev, bool dedicated, void (*cb)(void *), void *cbarg) +{ + struct a31dmac_softc *sc; + struct a31dmac_channel *ch; + uint32_t irqen; + u_int index; + + sc = device_get_softc(dev); + ch = NULL; + + mtx_lock_spin(&sc->mtx); + for (index = 0; index < sc->nchans; index++) { + if (sc->chans[index].callback == NULL) { + ch = &sc->chans[index]; + ch->callback = cb; + ch->callbackarg = cbarg; + + irqen = DMA_READ(sc, DMA_IRQ_EN_REG(index)); + irqen |= DMA_PKG_IRQ_EN(index); + DMA_WRITE(sc, DMA_IRQ_EN_REG(index), irqen); + break; + } + } + mtx_unlock_spin(&sc->mtx); + + return (ch); +} + +static void +a31dmac_free(device_t dev, void *priv) +{ + struct a31dmac_channel *ch; + struct a31dmac_softc *sc; + uint32_t irqen; + u_int index; + + ch = priv; + sc = ch->sc; + index = ch->index; + + mtx_lock_spin(&sc->mtx); + + irqen = DMA_READ(sc, DMA_IRQ_EN_REG(index)); + irqen &= ~DMA_PKG_IRQ_EN(index); + DMA_WRITE(sc, DMA_IRQ_EN_REG(index), irqen); + DMA_WRITE(sc, DMA_IRQ_PEND_REG(index), DMA_PKG_IRQ_EN(index)); + + ch->callback = NULL; + ch->callbackarg = NULL; + + mtx_unlock_spin(&sc->mtx); +} + +static int +a31dmac_transfer(device_t dev, void *priv, bus_addr_t src, bus_addr_t dst, + size_t nbytes) +{ + struct a31dmac_channel *ch; + struct a31dmac_softc *sc; + + ch = priv; + sc = ch->sc; + + ch->desc->srcaddr = htole32((uint32_t)src); + ch->desc->dstaddr = htole32((uint32_t)dst); + ch->desc->bcnt = htole32(nbytes); + ch->desc->next = htole32(DMA_NULL); + + DMA_WRITE(sc, DMA_STAR_ADDR_REG(ch->index), (uint32_t)ch->physaddr); + DMA_WRITE(sc, DMA_EN_REG(ch->index), DMA_EN); + + return (0); +} + +static void +a31dmac_halt(device_t dev, void *priv) +{ + struct a31dmac_channel *ch; + struct a31dmac_softc *sc; + + ch = priv; + sc = ch->sc; + + DMA_WRITE(sc, DMA_EN_REG(ch->index), 0); +} + +static device_method_t a31dmac_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, a31dmac_probe), + DEVMETHOD(device_attach, a31dmac_attach), + + /* sunxi DMA interface */ + DEVMETHOD(sunxi_dma_alloc, a31dmac_alloc), + DEVMETHOD(sunxi_dma_free, a31dmac_free), + DEVMETHOD(sunxi_dma_set_config, a31dmac_set_config), + DEVMETHOD(sunxi_dma_transfer, a31dmac_transfer), + DEVMETHOD(sunxi_dma_halt, a31dmac_halt), + + DEVMETHOD_END +}; + +static driver_t a31dmac_driver = { + "a31dmac", + a31dmac_methods, + sizeof(struct a31dmac_softc) +}; + +static devclass_t a31dmac_devclass; + +DRIVER_MODULE(a31dmac, simplebus, a31dmac_driver, a31dmac_devclass, 0, 0); Property changes on: head/sys/arm/allwinner/a31_dmac.c ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Added: svn:keywords ## -0,0 +1 ## +FreeBSD=%H \ No newline at end of property Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: head/sys/arm/allwinner/files.allwinner =================================================================== --- head/sys/arm/allwinner/files.allwinner (revision 308235) +++ head/sys/arm/allwinner/files.allwinner (revision 308236) @@ -1,58 +1,59 @@ # $FreeBSD$ kern/kern_clocksource.c standard arm/allwinner/a10_ahci.c optional ahci arm/allwinner/a10_codec.c optional sound arm/allwinner/a10_common.c standard arm/allwinner/a10_dmac.c standard +arm/allwinner/a31_dmac.c standard arm/allwinner/a10_ehci.c optional ehci arm/allwinner/aw_usbphy.c optional ehci arm/allwinner/a10_gpio.c optional gpio arm/allwinner/a10_mmc.c optional mmc arm/allwinner/a10_sramc.c standard arm/allwinner/aw_nmi.c optional intrng arm/allwinner/aw_if_dwc.c optional dwc arm/allwinner/aw_rsb.c optional rsb | p2wi arm/allwinner/aw_rtc.c standard arm/allwinner/aw_ts.c standard arm/allwinner/aw_wdog.c standard arm/allwinner/aw_machdep.c standard arm/allwinner/aw_mp.c optional smp arm/allwinner/axp209.c optional axp209 arm/allwinner/axp81x.c optional axp81x arm/allwinner/if_awg.c optional awg arm/allwinner/if_emac.c optional emac arm/allwinner/sunxi_dma_if.m standard dev/iicbus/twsi/a10_twsi.c optional twsi dev/usb/controller/generic_ohci.c optional ohci dev/usb/controller/generic_usb_if.m optional ohci arm/allwinner/aw_sid.c standard arm/allwinner/aw_thermal.c standard dev/iicbus/sy8106a.c optional sy8106a arm/allwinner/aw_cir.c optional aw_cir evdev #arm/allwinner/console.c standard arm/allwinner/a10_fb.c optional vt arm/allwinner/a10_hdmi.c optional hdmi arm/allwinner/a10_hdmiaudio.c optional hdmi sound arm/arm/hdmi_if.m optional hdmi arm/allwinner/aw_reset.c standard arm/allwinner/aw_ccu.c standard arm/allwinner/clk/aw_ahbclk.c standard arm/allwinner/clk/aw_apbclk.c standard arm/allwinner/clk/aw_axiclk.c standard arm/allwinner/clk/aw_codecclk.c standard arm/allwinner/clk/aw_cpuclk.c standard arm/allwinner/clk/aw_cpusclk.c standard arm/allwinner/clk/aw_debeclk.c standard arm/allwinner/clk/aw_gate.c standard arm/allwinner/clk/aw_gmacclk.c standard arm/allwinner/clk/aw_hdmiclk.c standard arm/allwinner/clk/aw_lcdclk.c standard arm/allwinner/clk/aw_modclk.c standard arm/allwinner/clk/aw_mmcclk.c standard arm/allwinner/clk/aw_oscclk.c standard arm/allwinner/clk/aw_pll.c standard arm/allwinner/clk/aw_thsclk.c standard arm/allwinner/clk/aw_usbclk.c standard