Index: sys/mips/ingenic/jz4780_pdma.c
===================================================================
--- /dev/null
+++ sys/mips/ingenic/jz4780_pdma.c
@@ -0,0 +1,542 @@
+/*-
+ * Copyright (c) 2016 Ruslan Bukin
+ * All rights reserved.
+ *
+ * This software was developed by SRI International and the University of
+ * Cambridge Computer Laboratory under DARPA/AFRL contract FA8750-10-C-0237
+ * ("CTSRD"), as part of the DARPA CRASH research programme.
+ *
+ * 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
+ */
+
+/* Ingenic JZ4780 PDMA Controller. */
+
+#include
+__FBSDID("$FreeBSD$");
+
+#include "opt_platform.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#ifdef FDT
+#include
+#include
+#include
+#endif
+
+#include
+
+#include
+#include
+
+#include "xdma_if.h"
+
+struct pdma_softc {
+ device_t dev;
+ struct resource *res[2];
+ bus_space_tag_t bst;
+ bus_space_handle_t bsh;
+ void *ih;
+};
+
+struct pdma_fdt_data {
+ int tx;
+ int rx;
+ int chan;
+};
+
+struct pdma_channel {
+ xdma_channel_t *xchan;
+ struct pdma_fdt_data data;
+ int cur_desc;
+ int used;
+ int index;
+ int flags;
+#define CHAN_DESCR_RELINK (1 << 0)
+};
+
+#define PDMA_NCHANNELS 32
+struct pdma_channel pdma_channels[PDMA_NCHANNELS];
+
+static struct resource_spec pdma_spec[] = {
+ { SYS_RES_MEMORY, 0, RF_ACTIVE },
+ { SYS_RES_IRQ, 0, RF_ACTIVE },
+ { -1, 0 }
+};
+
+static int pdma_probe(device_t dev);
+static int pdma_attach(device_t dev);
+static int pdma_detach(device_t dev);
+static int chan_start(struct pdma_softc *sc, struct pdma_channel *chan);
+
+static void
+pdma_intr(void *arg)
+{
+ struct pdma_channel *chan;
+ struct pdma_softc *sc;
+ xdma_channel_t *xchan;
+ xdma_config_t *conf;
+ int pending;
+ int i;
+
+ sc = arg;
+
+ pending = READ4(sc, PDMA_DIRQP);
+
+ /* Ack all the channels */
+ WRITE4(sc, PDMA_DIRQP, 0);
+
+ for (i = 0; i < PDMA_NCHANNELS; i++) {
+ if (pending & (1 << i)) {
+ chan = &pdma_channels[i];
+ xchan = chan->xchan;
+ conf = &xchan->conf;
+
+ /* Disable channel */
+ WRITE4(sc, PDMA_DCS(chan->index), 0);
+
+ if (chan->flags & CHAN_DESCR_RELINK) {
+ /* Enable again */
+ chan->cur_desc = (chan->cur_desc + 1) % \
+ conf->block_num;
+ chan_start(sc, chan);
+ }
+
+ xdma_callback(chan->xchan);
+ }
+ }
+}
+
+static int
+pdma_probe(device_t dev)
+{
+
+ if (!ofw_bus_status_okay(dev))
+ return (ENXIO);
+
+ if (!ofw_bus_is_compatible(dev, "ingenic,jz4780-dma"))
+ return (ENXIO);
+
+ device_set_desc(dev, "Ingenic JZ4780 PDMA Controller");
+
+ return (BUS_PROBE_DEFAULT);
+}
+
+static int
+pdma_attach(device_t dev)
+{
+ struct pdma_softc *sc;
+ phandle_t xref, node;
+ int err;
+ int reg;
+
+ sc = device_get_softc(dev);
+ sc->dev = dev;
+
+ if (bus_alloc_resources(dev, pdma_spec, sc->res)) {
+ device_printf(dev, "could not allocate resources for device\n");
+ return (ENXIO);
+ }
+
+ /* Memory interface */
+ sc->bst = rman_get_bustag(sc->res[0]);
+ sc->bsh = rman_get_bushandle(sc->res[0]);
+
+ /* Setup interrupt handler */
+ err = bus_setup_intr(dev, sc->res[1], INTR_TYPE_MISC | INTR_MPSAFE,
+ NULL, pdma_intr, sc, &sc->ih);
+ if (err) {
+ device_printf(dev, "Unable to alloc interrupt resource.\n");
+ return (ENXIO);
+ }
+
+ node = ofw_bus_get_node(dev);
+ xref = OF_xref_from_node(node);
+ OF_device_register_xref(xref, dev);
+
+ reg = READ4(sc, PDMA_DMAC);
+ reg &= ~(DMAC_HLT | DMAC_AR);
+ reg |= (DMAC_DMAE);
+ WRITE4(sc, PDMA_DMAC, reg);
+
+ WRITE4(sc, PDMA_DMACP, 0);
+
+ return (0);
+}
+
+static int
+pdma_detach(device_t dev)
+{
+ struct pdma_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ bus_release_resources(dev, pdma_spec, sc->res);
+
+ return (0);
+}
+
+static int
+chan_start(struct pdma_softc *sc, struct pdma_channel *chan)
+{
+ struct xdma_channel *xchan;
+
+ xchan = chan->xchan;
+
+ /* 8 byte descriptor. */
+ WRITE4(sc, PDMA_DCS(chan->index), DCS_DES8);
+ WRITE4(sc, PDMA_DDA(chan->index), xchan->descs_phys[chan->cur_desc].ds_addr);
+ WRITE4(sc, PDMA_DDS, (1 << chan->index));
+
+ /* Channel transfer enable. */
+ WRITE4(sc, PDMA_DCS(chan->index), (DCS_DES8 | DCS_CTE));
+
+ return (0);
+}
+
+static int
+chan_stop(struct pdma_softc *sc, struct pdma_channel *chan)
+{
+ int timeout;
+
+ WRITE4(sc, PDMA_DCS(chan->index), 0);
+
+ timeout = 100;
+
+ do {
+ if ((READ4(sc, PDMA_DCS(chan->index)) & DCS_CTE) == 0) {
+ break;
+ }
+ } while (timeout--);
+
+ if (timeout == 0) {
+ device_printf(sc->dev, "%s: Can't stop channel %d\n",
+ __func__, chan->index);
+ }
+
+ return (0);
+}
+
+static int
+pdma_channel_alloc(device_t dev, struct xdma_channel *xchan)
+{
+ struct pdma_channel *chan;
+ struct pdma_softc *sc;
+ int i;
+
+ sc = device_get_softc(dev);
+
+ xdma_assert_locked();
+
+ for (i = 0; i < PDMA_NCHANNELS; i++) {
+ chan = &pdma_channels[i];
+ if (chan->used == 0) {
+ chan->xchan = xchan;
+ xchan->chan = (void *)chan;
+ chan->used = 1;
+ chan->index = i;
+
+ return (0);
+ }
+ }
+
+ return (-1);
+}
+
+static int
+pdma_channel_free(device_t dev, struct xdma_channel *xchan)
+{
+ struct pdma_channel *chan;
+ struct pdma_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ xdma_assert_locked();
+
+ chan = (struct pdma_channel *)xchan->chan;
+ chan->used = 0;
+
+ return (0);
+}
+
+static int
+pdma_channel_prep_memcpy(device_t dev, struct xdma_channel *xchan)
+{
+ struct pdma_channel *chan;
+ struct pdma_hwdesc *desc;
+ struct pdma_softc *sc;
+ xdma_config_t *conf;
+ int ret;
+
+ sc = device_get_softc(dev);
+
+ chan = (struct pdma_channel *)xchan->chan;
+ /* Ensure we are not in operation */
+ chan_stop(sc, chan);
+
+ ret = xdma_desc_alloc(xchan, sizeof(struct pdma_hwdesc), 8);
+ if (ret != 0) {
+ device_printf(sc->dev,
+ "%s: Can't allocate descriptors.\n", __func__);
+ return (-1);
+ }
+
+ conf = &xchan->conf;
+ desc = (struct pdma_hwdesc *)xchan->descs;
+ desc[0].dsa = conf->src_addr;
+ desc[0].dta = conf->dst_addr;
+ desc[0].drt = DRT_AUTO;
+ desc[0].dcm = DCM_SAI | DCM_DAI;
+
+ /* 4 byte copy for now. */
+ desc[0].dtc = (conf->block_len / 4);
+ desc[0].dcm |= DCM_SP_4 | DCM_DP_4 | DCM_TSZ_4;
+ desc[0].dcm |= DCM_TIE;
+
+ return (0);
+}
+
+static int
+access_width(xdma_config_t *conf, uint32_t *dcm, uint32_t *max_width)
+{
+
+ *dcm = 0;
+ *max_width = max(conf->src_width, conf->dst_width);
+
+ switch (conf->src_width) {
+ case 1:
+ *dcm |= DCM_SP_1;
+ break;
+ case 2:
+ *dcm |= DCM_SP_2;
+ break;
+ case 4:
+ *dcm |= DCM_SP_4;
+ break;
+ default:
+ return (-1);
+ }
+
+ switch (conf->dst_width) {
+ case 1:
+ *dcm |= DCM_DP_1;
+ break;
+ case 2:
+ *dcm |= DCM_DP_2;
+ break;
+ case 4:
+ *dcm |= DCM_DP_4;
+ break;
+ default:
+ return (-1);
+ }
+
+ switch (*max_width) {
+ case 1:
+ *dcm |= DCM_TSZ_1;
+ break;
+ case 2:
+ *dcm |= DCM_TSZ_2;
+ break;
+ case 4:
+ *dcm |= DCM_TSZ_4;
+ break;
+ default:
+ return (-1);
+ };
+
+ return (0);
+}
+
+static int
+pdma_channel_prep_cyclic(device_t dev, struct xdma_channel *xchan)
+{
+ struct pdma_fdt_data *data;
+ struct pdma_channel *chan;
+ struct pdma_hwdesc *desc;
+ xdma_controller_t *xdma;
+ struct pdma_softc *sc;
+ xdma_config_t *conf;
+ int max_width;
+ uint32_t reg;
+ uint32_t dcm;
+ int ret;
+ int i;
+
+ sc = device_get_softc(dev);
+
+ conf = &xchan->conf;
+ xdma = xchan->xdma;
+ data = (struct pdma_fdt_data *)xdma->data;
+
+ ret = xdma_desc_alloc(xchan, sizeof(struct pdma_hwdesc), 8);
+ if (ret != 0) {
+ device_printf(sc->dev,
+ "%s: Can't allocate descriptors.\n", __func__);
+ return (-1);
+ }
+
+ chan = (struct pdma_channel *)xchan->chan;
+ /* Ensure we are not in operation */
+ chan_stop(sc, chan);
+ chan->flags = CHAN_DESCR_RELINK;
+ chan->cur_desc = 0;
+
+ desc = (struct pdma_hwdesc *)xchan->descs;
+
+ for (i = 0; i < conf->block_num; i++) {
+ if (conf->direction == XDMA_MEM_TO_DEV) {
+ desc[i].dsa = conf->src_addr + (i * conf->block_len);
+ desc[i].dta = conf->dst_addr;
+ desc[i].drt = data->tx;
+ desc[i].dcm = DCM_SAI;
+ } else if (conf->direction == XDMA_DEV_TO_MEM) {
+ desc[i].dsa = conf->src_addr;
+ desc[i].dta = conf->dst_addr + (i * conf->block_len);
+ desc[i].drt = data->rx;
+ desc[i].dcm = DCM_DAI;
+ } else if (conf->direction == XDMA_MEM_TO_MEM) {
+ desc[i].dsa = conf->src_addr + (i * conf->block_len);
+ desc[i].dta = conf->dst_addr + (i * conf->block_len);
+ desc[i].drt = DRT_AUTO;
+ desc[i].dcm = DCM_SAI | DCM_DAI;
+ }
+
+ if (access_width(conf, &dcm, &max_width) != 0) {
+ device_printf(dev,
+ "%s: can't configure access width\n", __func__);
+ return (-1);
+ }
+
+ desc[i].dcm |= dcm | DCM_TIE;
+ desc[i].dtc = (conf->block_len / max_width);
+
+ /*
+ * PDMA does not provide interrupt after processing each descriptor,
+ * but after processing all the chain only.
+ * As a workaround we do unlink descriptors here, so our chain will
+ * consists of single descriptor only. And then we reconfigure channel
+ * on each interrupt again.
+ */
+ if ((chan->flags & CHAN_DESCR_RELINK) == 0) {
+ if (i != (conf->block_num - 1)) {
+ desc[i].dcm |= DCM_LINK;
+ reg = ((i + 1) * sizeof(struct pdma_hwdesc));
+ desc[i].dtc |= (reg >> 4) << 24;
+ }
+ }
+ }
+
+ return (0);
+}
+
+static int
+pdma_channel_control(device_t dev, xdma_channel_t *xchan, int cmd)
+{
+ struct pdma_channel *chan;
+ struct pdma_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ chan = (struct pdma_channel *)xchan->chan;
+
+ switch (cmd) {
+ case XDMA_CMD_BEGIN:
+ chan_start(sc, chan);
+ break;
+ case XDMA_CMD_TERMINATE:
+ chan_stop(sc, chan);
+ break;
+ case XDMA_CMD_PAUSE:
+ /* TODO: implement me */
+ return (-1);
+ }
+
+ return (0);
+}
+
+#ifdef FDT
+static int
+pdma_ofw_md_data(device_t dev, phandle_t *cells, int ncells, void **ptr)
+{
+ struct pdma_fdt_data *data;
+
+ if (ncells != 3) {
+ return (-1);
+ }
+
+ data = malloc(sizeof(struct pdma_fdt_data), M_DEVBUF, (M_WAITOK | M_ZERO));
+ if (data == NULL) {
+ device_printf(dev, "%s: Cant allocate memory\n", __func__);
+ return (-1);
+ }
+
+ data->tx = cells[0];
+ data->rx = cells[1];
+ data->chan = cells[2];
+
+ *ptr = data;
+
+ return (0);
+}
+#endif
+
+static device_method_t pdma_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, pdma_probe),
+ DEVMETHOD(device_attach, pdma_attach),
+ DEVMETHOD(device_detach, pdma_detach),
+
+ /* xDMA Interface */
+ DEVMETHOD(xdma_channel_alloc, pdma_channel_alloc),
+ DEVMETHOD(xdma_channel_free, pdma_channel_free),
+ DEVMETHOD(xdma_channel_prep_cyclic, pdma_channel_prep_cyclic),
+ DEVMETHOD(xdma_channel_prep_memcpy, pdma_channel_prep_memcpy),
+ DEVMETHOD(xdma_channel_control, pdma_channel_control),
+#ifdef FDT
+ DEVMETHOD(xdma_ofw_md_data, pdma_ofw_md_data),
+#endif
+
+ DEVMETHOD_END
+};
+
+static driver_t pdma_driver = {
+ "pdma",
+ pdma_methods,
+ sizeof(struct pdma_softc),
+};
+
+static devclass_t pdma_devclass;
+
+EARLY_DRIVER_MODULE(pdma, simplebus, pdma_driver, pdma_devclass, 0, 0,
+ BUS_PASS_INTERRUPT + BUS_PASS_ORDER_LATE);