[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Qemu-devel] [PATCH 04/13] mxs/imx23: Add DMA driver
From: |
Michel Pollet |
Subject: |
[Qemu-devel] [PATCH 04/13] mxs/imx23: Add DMA driver |
Date: |
Wed, 11 Dec 2013 13:56:23 +0000 |
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 <address@hidden>
---
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 <address@hidden>
+ *
+ * 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
- [Qemu-devel] [PATCH 00/13] Freescale mxs/imx23 + Olimex Olinuxino support, Michel Pollet, 2013/12/11
- [Qemu-devel] [PATCH 03/13] mxs/imx23: Add uart driver, Michel Pollet, 2013/12/11
- [Qemu-devel] [PATCH 01/13] mxs/imx23: Add main header file, Michel Pollet, 2013/12/11
- [Qemu-devel] [PATCH 07/13] mxs/imx23: Implements the pin mux, GPIOs, Michel Pollet, 2013/12/11
- [Qemu-devel] [PATCH 06/13] mxs/imx23: Add digctl driver, Michel Pollet, 2013/12/11
- [Qemu-devel] [PATCH 08/13] mxs/imx23: Add SSP/SPI driver, Michel Pollet, 2013/12/11
- [Qemu-devel] [PATCH 04/13] mxs/imx23: Add DMA driver,
Michel Pollet <=
- [Qemu-devel] [PATCH 05/13] mxs/imx23: Add the interrupt collector, Michel Pollet, 2013/12/11
- [Qemu-devel] [PATCH 09/13] mxs/imx23: Add the RTC block, Michel Pollet, 2013/12/11
- [Qemu-devel] [PATCH 02/13] mxs: Add CONFIG_MXS to the arm-softmmu config, Michel Pollet, 2013/12/11
- [Qemu-devel] [PATCH 13/13] mxs/imx23: Adds support for an Olinuxino board, Michel Pollet, 2013/12/11
- [Qemu-devel] [PATCH 12/13] mxs/imx23: Main core instantiation and minor IO blocks, Michel Pollet, 2013/12/11
- [Qemu-devel] [PATCH 11/13] mxs/imx23: Add the USB driver, Michel Pollet, 2013/12/11
- [Qemu-devel] [PATCH 10/13] mxs/imx23: Add the timers, Michel Pollet, 2013/12/11
- Re: [Qemu-devel] [PATCH 00/13] Freescale mxs/imx23 + Olimex Olinuxino support, M P, 2013/12/13