All of lore.kernel.org
 help / color / mirror / Atom feed
From: Michel Pollet <buserror@gmail.com>
To: qemu-devel@nongnu.org
Cc: Michel Pollet <buserror@gmail.com>
Subject: [Qemu-devel] [PATCH 04/13] mxs/imx23: Add DMA driver
Date: Wed, 11 Dec 2013 13:56:23 +0000	[thread overview]
Message-ID: <1386770192-19585-5-git-send-email-buserror@gmail.com> (raw)
In-Reply-To: <1386770192-19585-1-git-send-email-buserror@gmail.com>

This driver works sufficiently well that linux can use it to access
the SD card using the SD->DMA->SSI->SD. It hasn't been tested for
much else.

Signed-off-by: Michel Pollet <buserror@gmail.com>
---
 hw/dma/Makefile.objs |   1 +
 hw/dma/mxs_dma.c     | 347 +++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 348 insertions(+)
 create mode 100644 hw/dma/mxs_dma.c

diff --git a/hw/dma/Makefile.objs b/hw/dma/Makefile.objs
index 0e65ed0..3373aa1 100644
--- a/hw/dma/Makefile.objs
+++ b/hw/dma/Makefile.objs
@@ -8,6 +8,7 @@ common-obj-$(CONFIG_XILINX_AXI) += xilinx_axidma.o
 common-obj-$(CONFIG_ETRAXFS) += etraxfs_dma.o
 common-obj-$(CONFIG_STP2000) += sparc32_dma.o
 common-obj-$(CONFIG_SUN4M) += sun4m_iommu.o
+common-obj-$(CONFIG_MXS) += mxs_dma.o
 
 obj-$(CONFIG_OMAP) += omap_dma.o soc_dma.o
 obj-$(CONFIG_PXA2XX) += pxa2xx_dma.o
diff --git a/hw/dma/mxs_dma.c b/hw/dma/mxs_dma.c
new file mode 100644
index 0000000..9ac787b
--- /dev/null
+++ b/hw/dma/mxs_dma.c
@@ -0,0 +1,347 @@
+/*
+ * mxs_dma.c
+ *
+ * Copyright: Michel Pollet <buserror@gmail.com>
+ *
+ * QEMU Licence
+ */
+
+/*
+ * Implements the DMA block of the mxs.
+ * The current implementation can run chains of commands etc, however it's only
+ * been tested with SSP for SD/MMC card access. It ought to work with normal SPI
+ * too, and possibly other peripherals, however it's entirely untested
+ */
+#include "hw/sysbus.h"
+#include "hw/arm/mxs.h"
+
+/*
+ * DMA IO block register numbers
+ */
+enum {
+    DMA_CTRL0 = 0x0,
+    DMA_CTRL1 = 0x1,
+    DMA_CTRL2 = 0x2,
+    DMA_DEVSEL1 = 0x3,
+    DMA_DEVSEL2 = 0x4,
+    DMA_MAX,
+
+    /*
+     * The DMA block for APBH and APBX have a different base address,
+     * but they share a 7 words stride between channels.
+     */
+    DMA_STRIDE = 0x70,
+    /*
+     * Neither blocks uses that many, but there is space for them...
+     */
+    DMA_MAX_CHANNELS = 16,
+};
+
+/*
+ * DMA channel register numbers
+ */
+enum {
+    CH_CURCMD = 0,
+    CH_NEXTCMD = 1,
+    CH_CMD = 2,
+    CH_BUFFER_ADDR = 3,
+    CH_SEMA = 4,
+    CH_DEBUG1 = 5,
+    CH_DEBUG2 = 6,
+};
+
+/*
+ * Channel command bit numbers
+ */
+enum {
+    CH_CMD_IRQ_COMPLETE = 3,
+    CH_CMD_SEMAPHORE = 6,
+};
+
+/*
+ * nicked from linux
+ * this is the memory representation of a DMA request
+ */
+struct mxs_dma_ccw {
+    uint32_t next;
+    uint16_t bits;
+    uint16_t xfer_bytes;
+#define MAX_XFER_BYTES	0xff00
+    uint32_t bufaddr;
+#define MXS_PIO_WORDS	16
+    uint32_t pio_words[MXS_PIO_WORDS];
+}__attribute__((packed));
+
+/*
+ * Per channel DMA description
+ */
+typedef struct mxs_dma_channel {
+    QEMUTimer *timer;
+    struct mxs_dma_state *dma;
+    int channel; // channel index
+    hwaddr base; // base of peripheral
+    hwaddr dataoffset; // offset of the true in/out data latch register
+    uint32_t r[10];
+    qemu_irq irq;
+} mxs_dma_channel;
+
+
+typedef struct mxs_dma_state {
+    SysBusDevice busdev;
+    MemoryRegion iomem;
+    const char * name;
+
+    struct soc_dma_s * dma;
+    uint32_t r[DMA_MAX];
+
+    hwaddr base; // base of peripheral
+    mxs_dma_channel channel[DMA_MAX_CHANNELS];
+} mxs_dma_state;
+
+static void mxs_dma_ch_update(mxs_dma_channel *s)
+{
+    struct mxs_dma_ccw req;
+    int i;
+
+    /* increment the semaphore, if needed */
+    s->r[CH_SEMA] = (((s->r[CH_SEMA] >> 16) & 0xff) +
+            (s->r[CH_SEMA] & 0xff)) << 16;
+    if (!((s->r[CH_SEMA] >> 16) & 0xff)) {
+        return;
+    }
+    /* read the request from memory */
+    cpu_physical_memory_read(s->r[CH_NEXTCMD], &req, sizeof(req));
+    /* update the latch registers accordingly */
+    s->r[CH_CURCMD] = s->r[CH_NEXTCMD];
+    s->r[CH_NEXTCMD] = req.next;
+    s->r[CH_CMD] = (req.xfer_bytes << 16) | req.bits;
+    s->r[CH_BUFFER_ADDR] = req.bufaddr;
+
+    /* write PIO registers first, if any */
+    for (i = 0; i < (req.bits >> 12); i++) {
+        cpu_physical_memory_rw(s->base + (i << 4),
+                (uint8_t*) &req.pio_words[i], 4, 1);
+    }
+    /* next handle any "data" requests */
+    switch (req.bits & 0x3) {
+        case 0:
+            break; // PIO only
+        case 0x1: { // WRITE (from periph to memory)
+            uint32_t buf = req.bufaddr;
+            uint8_t b = 0;
+            while (req.xfer_bytes--) {
+                cpu_physical_memory_rw(s->base + s->dataoffset, &b, 1, 0);
+                cpu_physical_memory_rw(buf, &b, 1, 1);
+                buf++;
+            }
+        }   break;
+        case 0x2: { // READ (from memory to periph)
+            uint32_t buf = req.bufaddr;
+            uint8_t b = 0;
+            while (req.xfer_bytes--) {
+                cpu_physical_memory_rw(buf, &b, 1, 0);
+                cpu_physical_memory_rw(s->base + s->dataoffset, &b, 1, 1);
+                buf++;
+            }
+        }   break;
+    }
+
+    s->dma->r[DMA_CTRL1] |= 1 << s->channel;
+    /* trigger IRQ if requested */
+    if ((s->dma->r[DMA_CTRL1] >> 16) & (1 << s->channel)) {
+        if (req.bits & (1 << CH_CMD_IRQ_COMPLETE)) {
+            qemu_set_irq(s->irq, 1);
+        }
+    }
+
+    /* decrement semaphore if requested */
+    if (s->r[CH_CMD] & (1 << CH_CMD_SEMAPHORE)) {
+        s->r[CH_SEMA] = (((s->r[CH_SEMA] >> 16) & 0xff) - 1) << 16;
+    }
+    /* If the semaphore is still on, try to trigger a chained request */
+    if ((s->r[CH_SEMA] >> 16) & 0xff) {
+        int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+        timer_mod(s->timer, now + 10);
+    }
+}
+
+/* called on one shot timer activation */
+static void mxs_dma_ch_run(void *opaque)
+{
+    mxs_dma_channel *s = opaque;
+    mxs_dma_ch_update(s);
+}
+
+static uint64_t mxs_dma_read(void *opaque, hwaddr offset, unsigned size)
+{
+    mxs_dma_state *s = (mxs_dma_state *) opaque;
+    uint32_t res = 0;
+
+    switch (offset >> 4) {
+        case 0 ... DMA_MAX - 1:
+            res = s->r[offset >> 4];
+            break;
+        default:
+            if (offset >= s->base) {
+                offset -= s->base;
+                int channel = offset / DMA_STRIDE;
+                int word = (offset % DMA_STRIDE) >> 4;
+                res = s->channel[channel].r[word];
+            } else
+                qemu_log_mask(LOG_GUEST_ERROR,
+                        "%s: bad offset 0x%x\n", __func__, (int) offset);
+            break;
+    }
+
+    return res;
+}
+
+static void mxs_dma_write(void *opaque, hwaddr offset, uint64_t value,
+        unsigned size)
+{
+    mxs_dma_state *s = (mxs_dma_state *) opaque;
+    uint32_t oldvalue = 0;
+    int channel, word, i;
+
+    switch (offset >> 4) {
+        case 0 ... DMA_MAX - 1:
+            oldvalue = mxs_write(&s->r[offset >> 4], offset, value, size);
+            break;
+        default:
+            if (offset >= s->base) {
+                channel = (offset - s->base) / DMA_STRIDE;
+                word = (offset - s->base) % DMA_STRIDE;
+                oldvalue = mxs_write(
+                        &s->channel[channel].r[word >> 4], word,
+                        value, size);
+                switch (word >> 4) {
+                    case CH_SEMA:
+                        // mask the new semaphore value, as only the lowest 8 bits are RW
+                        s->channel[channel].r[CH_SEMA] =
+                                (oldvalue & ~0xff) |
+                                (s->channel[channel].r[CH_SEMA] & 0xff);
+                        mxs_dma_ch_update(&s->channel[channel]);
+                        break;
+                }
+            } else {
+                qemu_log_mask(LOG_GUEST_ERROR,
+                        "%s: bad offset 0x%x\n", __func__, (int) offset);
+            }
+            break;
+    }
+    switch (offset >> 4) {
+        case DMA_CTRL0:
+            if ((oldvalue ^ s->r[DMA_CTRL0]) == 0x80000000
+                    && !(oldvalue & 0x80000000)) {
+                // printf("%s write reseting, anding clockgate\n", s->name);
+                s->r[DMA_CTRL0] |= 0x40000000;
+            }
+            break;
+        case DMA_CTRL1:
+            for (i = 0; i < DMA_MAX_CHANNELS; i++)
+                if (s->channel[i].r[CH_NEXTCMD] &&
+                        !(s->r[DMA_CTRL1] & (1 << i))) {
+                    int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+                    /* add a bit of latency to the timer. Ideally would
+                     * do some calculation proportional to the transfer
+                     * size. TODO ?
+                     */
+                    timer_mod(s->channel[i].timer, now + 100000);
+                }
+            break;
+    }
+}
+
+
+static const MemoryRegionOps mxs_dma_ops = {
+    .read = mxs_dma_read,
+    .write = mxs_dma_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void mxs_dma_common_init(mxs_dma_state *s)
+{
+    int i;
+    memory_region_init_io(&s->iomem, OBJECT(s), &mxs_dma_ops, s, "mxs_dma", 0x2000);
+    sysbus_init_mmio(&s->busdev, &s->iomem);
+    for (i = 0; i < DMA_MAX_CHANNELS; i++) {
+        s->channel[i].dma = s;
+        s->channel[i].channel = i;
+        s->channel[i].timer =
+                timer_new_ns(QEMU_CLOCK_VIRTUAL, mxs_dma_ch_run, &s->channel[i]);
+    }
+}
+
+static int mxs_apbh_dma_init(SysBusDevice *dev)
+{
+    mxs_dma_state *s = OBJECT_CHECK(mxs_dma_state, dev, "mxs_apbh_dma");
+
+    mxs_dma_common_init(s);
+    s->name = "dma_apbh";
+    s->base = 0x40;
+    sysbus_init_irq(dev, &s->channel[MX23_DMA_SSP1].irq);
+    sysbus_init_irq(dev, &s->channel[MX23_DMA_SSP2].irq);
+    s->channel[MX23_DMA_SSP1].base = MX23_SSP1_BASE_ADDR;
+    s->channel[MX23_DMA_SSP1].dataoffset = 0x70;
+    s->channel[MX23_DMA_SSP2].base = MX23_SSP2_BASE_ADDR;
+    s->channel[MX23_DMA_SSP2].dataoffset = 0x70;
+
+    return 0;
+}
+
+static int mxs_apbx_dma_init(SysBusDevice *dev)
+{
+//    mxs_dma_state *s = FROM_SYSBUS(mxs_dma_state, dev);
+    mxs_dma_state *s = OBJECT_CHECK(mxs_dma_state, dev, "mxs_apbx_dma");
+
+    mxs_dma_common_init(s);
+    s->name = "dma_apbx";
+    s->base = 0x100;
+    sysbus_init_irq(dev, &s->channel[MX23_DMA_ADC].irq);
+    sysbus_init_irq(dev, &s->channel[MX23_DMA_DAC].irq);
+    sysbus_init_irq(dev, &s->channel[MX23_DMA_SPDIF].irq);
+    sysbus_init_irq(dev, &s->channel[MX23_DMA_I2C].irq);
+    sysbus_init_irq(dev, &s->channel[MX23_DMA_SAIF0].irq);
+    sysbus_init_irq(dev, &s->channel[MX23_DMA_UART0_RX].irq);
+    sysbus_init_irq(dev, &s->channel[MX23_DMA_UART0_TX].irq);
+    sysbus_init_irq(dev, &s->channel[MX23_DMA_UART1_RX].irq);
+    sysbus_init_irq(dev, &s->channel[MX23_DMA_UART1_TX].irq);
+    sysbus_init_irq(dev, &s->channel[MX23_DMA_SAIF1].irq);
+
+    return 0;
+}
+
+static void mxs_apbh_dma_class_init(ObjectClass *klass, void *data)
+{
+    SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass);
+
+    sdc->init = mxs_apbh_dma_init;
+}
+
+static void mxs_apbx_dma_class_init(ObjectClass *klass, void *data)
+{
+    SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass);
+
+    sdc->init = mxs_apbx_dma_init;
+}
+
+static TypeInfo apbh_dma_info = {
+    .name          = "mxs_apbh_dma",
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(mxs_dma_state),
+    .class_init    = mxs_apbh_dma_class_init,
+};
+static TypeInfo apbx_dma_info = {
+    .name          = "mxs_apbx_dma",
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(mxs_dma_state),
+    .class_init    = mxs_apbx_dma_class_init,
+};
+
+static void mxs_dma_register(void)
+{
+    type_register_static(&apbh_dma_info);
+    type_register_static(&apbx_dma_info);
+}
+
+type_init(mxs_dma_register)
-- 
1.8.5.1

  parent reply	other threads:[~2013-12-11 13:49 UTC|newest]

Thread overview: 36+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2013-12-11 13:56 [Qemu-devel] [PATCH 00/13] Freescale mxs/imx23 + Olimex Olinuxino support Michel Pollet
2013-12-11 13:56 ` [Qemu-devel] [PATCH 01/13] mxs/imx23: Add main header file Michel Pollet
2013-12-11 13:56 ` [Qemu-devel] [PATCH 02/13] mxs: Add CONFIG_MXS to the arm-softmmu config Michel Pollet
2014-01-06 15:08   ` Peter Maydell
2013-12-11 13:56 ` [Qemu-devel] [PATCH 03/13] mxs/imx23: Add uart driver Michel Pollet
2014-01-06 15:19   ` Peter Maydell
2014-01-11  7:39     ` Peter Crosthwaite
2013-12-11 13:56 ` Michel Pollet [this message]
2014-01-06 15:35   ` [Qemu-devel] [PATCH 04/13] mxs/imx23: Add DMA driver Peter Maydell
2014-01-10  0:52     ` Peter Crosthwaite
2014-01-10  0:54       ` Peter Crosthwaite
2014-01-10 10:55       ` Peter Maydell
2013-12-11 13:56 ` [Qemu-devel] [PATCH 05/13] mxs/imx23: Add the interrupt collector Michel Pollet
2014-01-06 15:41   ` Peter Maydell
2014-01-11  8:29   ` Peter Crosthwaite
2013-12-11 13:56 ` [Qemu-devel] [PATCH 06/13] mxs/imx23: Add digctl driver Michel Pollet
2014-01-06 15:46   ` Peter Maydell
2014-01-08 18:39     ` M P
2014-01-08 18:55       ` Peter Maydell
2014-01-11  8:44         ` Peter Crosthwaite
2014-01-11  8:39   ` Peter Crosthwaite
2013-12-11 13:56 ` [Qemu-devel] [PATCH 07/13] mxs/imx23: Implements the pin mux, GPIOs Michel Pollet
2014-01-06 15:52   ` Peter Maydell
2014-01-08 18:16     ` M P
2013-12-11 13:56 ` [Qemu-devel] [PATCH 08/13] mxs/imx23: Add SSP/SPI driver Michel Pollet
2014-01-11  9:08   ` Peter Crosthwaite
2013-12-11 13:56 ` [Qemu-devel] [PATCH 09/13] mxs/imx23: Add the RTC block Michel Pollet
2014-01-11  9:16   ` Peter Crosthwaite
2013-12-11 13:56 ` [Qemu-devel] [PATCH 10/13] mxs/imx23: Add the timers Michel Pollet
2013-12-11 13:56 ` [Qemu-devel] [PATCH 11/13] mxs/imx23: Add the USB driver Michel Pollet
2014-01-11  9:57   ` Peter Crosthwaite
2013-12-11 13:56 ` [Qemu-devel] [PATCH 12/13] mxs/imx23: Main core instantiation and minor IO blocks Michel Pollet
2013-12-11 13:56 ` [Qemu-devel] [PATCH 13/13] mxs/imx23: Adds support for an Olinuxino board Michel Pollet
2013-12-13 12:53 ` [Qemu-devel] [PATCH 00/13] Freescale mxs/imx23 + Olimex Olinuxino support M P
2013-12-13 13:29   ` Peter Maydell
2013-12-13 13:45     ` M P

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1386770192-19585-5-git-send-email-buserror@gmail.com \
    --to=buserror@gmail.com \
    --cc=qemu-devel@nongnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.