diff mbox

[v2,04/20] arm: add Faraday FTDMAC020 AHB DMA support

Message ID 1359101996-11667-5-git-send-email-dantesu@gmail.com
State New
Headers show

Commit Message

Kuo-Jung Su Jan. 25, 2013, 8:19 a.m. UTC
From: Kuo-Jung Su <dantesu@faraday-tech.com>

The Faraday FTDMAC020 is designed to enhance the system performance
and reduce the processor-interrupt generation. The system efficiency
is improved by employing the high-speed data transfers between the
system and device. The DMA controller provides eight configurable
channels for the memory-to-memory, memory-to-peripheral,
peripheral-to-peripheral, and peripheral-to-memory transfers
with shared buffer.

Each DMA channel supports chain transfer and can be programmed
to one of the 16 handshaking channels in the hardware handshake mode.

The main function of the hardware handshake mode is to provide an
indication of the device status. Users can also disable the hardware
handshake mode by programming the register when a DMA transfer is not
necessary of referring to the handshaking channels.

Signed-off-by: Kuo-Jung Su <dantesu@faraday-tech.com>
---
 hw/ftdmac020.c |  667 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 hw/ftdmac020.h |  105 +++++++++
 2 files changed, 772 insertions(+)
 create mode 100644 hw/ftdmac020.c
 create mode 100644 hw/ftdmac020.h
diff mbox

Patch

diff --git a/hw/ftdmac020.c b/hw/ftdmac020.c
new file mode 100644
index 0000000..58b453e
--- /dev/null
+++ b/hw/ftdmac020.c
@@ -0,0 +1,667 @@ 
+/*
+ * QEMU model of the FTDMAC020 DMA Controller
+ *
+ * Copyright (C) 2012 Faraday Technology
+ * Written by Dante Su <dantesu@faraday-tech.com>
+ *
+ * This file is licensed under GNU GPL v2.
+ *
+ * Note: The FTDMAC020 decreasing address mode is not implemented.
+ */
+
+#include "sysbus.h"
+#include "sysemu/sysemu.h"
+#include "sysemu/blockdev.h"
+
+#include "ftdmac020.h"
+
+#define TYPE_FTDMAC020    "ftdmac020"
+
+enum ftdmac020_irqpin {
+    IRQ_ALL = 0,
+    IRQ_TC,
+    IRQ_ERR,
+};
+
+typedef struct ftdmac020_state ftdmac020_state;
+
+/**
+ * struct ftdmac020_lld - hardware link list descriptor.
+ * @src: source physical address
+ * @dst: destination physical addr
+ * @next: phsical address to the next link list descriptor
+ * @ctrl: control field
+ * @size: transfer size
+ *
+ * should be word aligned
+ */
+typedef struct ftdmac020_lld {
+    uint32_t src;
+    uint32_t dst;
+    uint32_t next;
+    uint32_t ctrl;
+    uint32_t size;
+} ftdmac020_lld;
+
+typedef struct ftdmac020_chan {
+    ftdmac020_state *chip;
+
+    int id;
+    int burst;
+    int llp_cnt;
+    int src_bw;
+    int src_stride;
+    int dst_bw;
+    int dst_stride;
+
+    /* HW register cache */
+    uint32_t ccr;
+    uint32_t cfg;
+    uint32_t src;
+    uint32_t dst;
+    uint32_t llp;
+    uint32_t len;
+} ftdmac020_chan;
+
+typedef struct ftdmac020_state {
+    SysBusDevice busdev;
+    MemoryRegion iomem;
+    qemu_irq irq[3];
+
+    ftdmac020_chan chan[8];
+    qemu_irq       ack[16];
+    uint32_t       req;
+
+    int busy;    /* Busy Channel ID */
+    QEMUTimer *qtimer;
+
+    /* HW register cache */
+    uint32_t tcisr;
+    uint32_t eaisr;
+    uint32_t tcsr;
+    uint32_t easr;
+    uint32_t cesr;
+    uint32_t cbsr;
+    uint32_t csr;
+    uint32_t sync;
+} ftdmac020_state;
+
+#define FTDMAC020(obj) \
+    OBJECT_CHECK(ftdmac020_state, obj, TYPE_FTDMAC020)
+
+static void ftdmac020_update_irq(ftdmac020_state *s)
+{
+    uint32_t tc, err;
+
+    /* 1. Checking TC interrupts */
+    tc = s->tcisr & 0xff;
+    qemu_set_irq(s->irq[IRQ_TC], tc ? 1 : 0);
+
+    /* 2. Checking Error/Abort interrupts */
+    err = s->eaisr & 0x00ff00ff;
+    qemu_set_irq(s->irq[IRQ_ERR], err ? 1 : 0);
+
+    /* 3. Checking interrupt summary (TC | Error | Abort) */
+    qemu_set_irq(s->irq[IRQ_ALL], (tc || err) ? 1 : 0);
+}
+
+static void ftdmac020_chan_ccr_decode(ftdmac020_chan *c)
+{
+    uint32_t tmp;
+
+    /* 1. decode burst size */
+    tmp = (c->ccr >> 16) & 0x07;
+    c->burst  = 1 << (tmp ? tmp + 1 : 0);
+
+    /* 2. decode source width */
+    tmp = (c->ccr >> 11) & 0x03;
+    c->src_bw = 8 << tmp;
+
+    /* 3. decode destination width */
+    tmp = (c->ccr >> 8) & 0x03;
+    c->dst_bw = 8 << tmp;
+
+    /* 4. decode source address stride */
+    tmp = (c->ccr >> 5) & 0x03;
+    if (tmp == 2) {
+        c->src_stride = 0;
+    } else {
+        c->src_stride = c->src_bw >> 3;
+    }
+
+    /* 5. decode destination address stride */
+    tmp = (c->ccr >> 3) & 0x03;
+    if (tmp == 2) {
+        c->dst_stride = 0;
+    } else {
+        c->dst_stride = c->dst_bw >> 3;
+    }
+}
+
+static void ftdmac020_chan_start(ftdmac020_chan *c)
+{
+    ftdmac020_state *s = c->chip;
+    hwaddr src, dst, src_len, dst_len;
+    int src_hs, dst_hs;
+    const uint8_t *src_map = NULL, *src_ptr = NULL;
+    uint8_t *dst_map = NULL, *dst_ptr = NULL;
+    uint8_t buf[4096];
+    ftdmac020_lld desc;
+    int len = 0;
+
+    if (!(c->ccr & 0x01)) {
+        return;
+    }
+
+    s->busy = c->id;
+
+    /* DMA src/dst address */
+    src = c->src;
+    dst = c->dst;
+    /* DMA hardware handshake id */
+    src_hs = (c->cfg & 0x0080) ? ((c->cfg >> 3) & 0x0f) : -1;
+    dst_hs = (c->cfg & 0x2000) ? ((c->cfg >> 9) & 0x0f) : -1;
+    /* DMA src/dst data length */
+    src_len = c->src_stride ? (c->len * (c->src_bw >> 3)) : (c->src_bw >> 3);
+    dst_len = c->dst_stride ? (c->len * (c->dst_bw >> 3)) : (c->dst_bw >> 3);
+    if (!cpu_physical_memory_is_io(c->src)) {
+        src_map = src_ptr = cpu_physical_memory_map(c->src, &src_len, 0);
+    }
+    if (!cpu_physical_memory_is_io(c->dst)) {
+        dst_map = dst_ptr = cpu_physical_memory_map(c->dst, &dst_len, 1);
+    }
+
+    while (c->len > 0) {
+        /*
+         * Postpone this DMA action
+         * if the corresponding dma request is not asserted
+         */
+        if ((src_hs >= 0) && !(s->req & (1 << src_hs))) {
+            break;
+        }
+        if ((dst_hs >= 0) && !(s->req & (1 << dst_hs))) {
+            break;
+        }
+
+        len = MIN(sizeof(buf), c->burst * (c->src_bw >> 3));
+
+        /* load data from source into local buffer */
+        if (src_ptr) {
+            if (c->src_stride) {
+                memcpy(buf, src_ptr, len);
+                src += len;
+                src_ptr += len;
+            } else {
+                int i;
+                switch (c->src_bw) {
+                case 8:
+                    for (i = 0; i < len; i += 1) {
+                        *(uint8_t *)(buf + i) = *(const uint8_t *)src_ptr;
+                    }
+                    break;
+                case 16:
+                    for (i = 0; i < len; i += 2) {
+                        *(uint16_t *)(buf + i) = *(const uint16_t *)src_ptr;
+                    }
+                    break;
+                case 32:
+                    for (i = 0; i < len; i += 4) {
+                        *(uint32_t *)(buf + i) = *(const uint32_t *)src_ptr;
+                    }
+                    break;
+                default:
+                    for (i = 0; i < len; i += 8) {
+                        *(uint64_t *)(buf + i) = *(const uint64_t *)src_ptr;
+                    }
+                    break;
+                }
+            }
+        } else {
+            uint32_t rl, stride = c->src_bw >> 3;
+            for (rl = 0; rl < len; rl += stride, src += c->src_stride) {
+                cpu_physical_memory_read(src, (uint64_t *)(buf + rl), stride);
+            }
+        }
+
+        /* DMA Hardware Handshake */
+        if (src_hs >= 0) {
+            qemu_set_irq(s->ack[src_hs], 1);
+        }
+
+        /* store data into destination from local buffer */
+        if (dst_ptr) {
+            if (c->dst_stride) {
+                memcpy(dst_ptr, buf, len);
+                dst += len;
+                dst_ptr += len;
+            } else {
+                int i;
+                switch (c->dst_bw) {
+                case 8:
+                    for (i = 0; i < len; i += 1) {
+                        *(uint8_t *)dst_ptr = *(uint8_t *)(buf + i);
+                    }
+                    break;
+                case 16:
+                    for (i = 0; i < len; i += 2) {
+                        *(uint16_t *)dst_ptr = *(uint16_t *)(buf + i);
+                    }
+                    break;
+                case 32:
+                    for (i = 0; i < len; i += 4) {
+                        *(uint32_t *)dst_ptr = *(uint32_t *)(buf + i);
+                    }
+                    break;
+                default:
+                    for (i = 0; i < len; i += 8) {
+                        *(uint64_t *)dst_ptr = *(uint64_t *)(buf + i);
+                    }
+                    break;
+                }
+            }
+        } else {
+            uint32_t wl, stride = c->dst_bw >> 3;
+            for (wl = 0; wl < len; wl += stride, dst += c->dst_stride) {
+                cpu_physical_memory_write(dst, (uint64_t *)(buf + wl), stride);
+            }
+        }
+
+        /* DMA Hardware Handshake */
+        if (dst_hs >= 0) {
+            qemu_set_irq(s->ack[dst_hs], 1);
+        }
+
+        /* update the channel transfer size */
+        c->len -= len / (c->src_bw >> 3);
+
+        if (c->len == 0) {
+            /* release the memory mappings */
+            if (src_map) {
+                cpu_physical_memory_unmap((void *)src_map,
+                                          src_len,
+                                          0,
+                                          (hwaddr)(src_ptr - src_map));
+                src_map = src_ptr = NULL;
+            }
+            if (dst_map) {
+                cpu_physical_memory_unmap(dst_map,
+                                          dst_len,
+                                          1,
+                                          (hwaddr)(dst_ptr - dst_map));
+                dst_map = dst_ptr = NULL;
+            }
+            /* update the channel transfer status */
+            if (!(c->ccr & CCR_MASK_TC)) {
+                s->tcsr |= (1 << c->id);
+                if (!(c->cfg & CFG_MASK_TCI)) {
+                    s->tcisr |= (1 << c->id);
+                }
+                ftdmac020_update_irq(s);
+            }
+            /* try to load next lld */
+            if (c->llp) {
+                c->llp_cnt += 1;
+                cpu_physical_memory_read(c->llp, &desc, sizeof(desc));
+
+                desc.src  = le32_to_cpu(desc.src);
+                desc.dst  = le32_to_cpu(desc.dst);
+                desc.next = le32_to_cpu(desc.next);
+                desc.size = le32_to_cpu(desc.size);
+                desc.ctrl = le32_to_cpu(desc.ctrl);
+
+                c->src = desc.src;
+                c->dst = desc.dst;
+                c->llp = desc.next & 0xfffffffc;
+                c->len = desc.size & 0x003fffff;
+                c->ccr = (c->ccr & 0x78f8c081)
+                       | (((desc.ctrl >> 29) & 0x07) << 24)
+                       | ((desc.ctrl & (1 << 28)) ? (1 << 31) : 0)
+                       | (((desc.ctrl >> 25) & 0x07) << 11)
+                       | (((desc.ctrl >> 22) & 0x07) << 8)
+                       | (((desc.ctrl >> 20) & 0x03) << 5)
+                       | (((desc.ctrl >> 18) & 0x03) << 3)
+                       | (((desc.ctrl >> 16) & 0x03) << 1);
+                ftdmac020_chan_ccr_decode(c);
+
+                src = c->src;
+                dst = c->dst;
+                src_len = c->src_stride ? (c->len * (c->src_bw >> 3))
+                                        : (c->src_bw >> 3);
+                dst_len = c->dst_stride ? (c->len * (c->dst_bw >> 3))
+                                        : (c->dst_bw >> 3);
+                if (!cpu_physical_memory_is_io(c->src)) {
+                    src_map = src_ptr = cpu_physical_memory_map(c->src,
+                                                                &src_len,
+                                                                0);
+                }
+                if (!cpu_physical_memory_is_io(c->dst)) {
+                    dst_map = dst_ptr = cpu_physical_memory_map(c->dst,
+                                                                &dst_len,
+                                                                1);
+                }
+            } else {
+                /* clear dma start bit */
+                c->ccr &= 0xfffffffe;
+            }
+        }
+    }
+
+    /* release the memory mappings */
+    if (src_map) {
+        cpu_physical_memory_unmap((void *)src_map,
+                                  src_len,
+                                  0,
+                                  (hwaddr)(src_ptr - src_map));
+    }
+    if (dst_map) {
+        cpu_physical_memory_unmap(dst_map,
+                                  dst_len,
+                                  1,
+                                  (hwaddr)(dst_ptr - dst_map));
+    }
+
+    /* update dma src/dst address */
+    c->src = src;
+    c->dst = dst;
+
+    s->busy = -1;
+}
+
+static void ftdmac020_chan_reset(ftdmac020_chan *c)
+{
+    c->ccr = 0;
+    c->cfg = 0;
+    c->src = 0;
+    c->dst = 0;
+    c->llp = 0;
+    c->len = 0;
+}
+
+static void ftdmac020_timer_tick(void *opaque)
+{
+    ftdmac020_state *s = FTDMAC020(opaque);
+    ftdmac020_chan  *c = NULL;
+    int i, jobs, done;
+
+    jobs = 0;
+    done = 0;
+    for (i = 0; i < 8; ++i) {
+        c = s->chan + i;
+        if (c->ccr & CCR_START) {
+            ++jobs;
+            ftdmac020_chan_start(c);
+            if (!(c->ccr & CCR_START)) {
+                ++done;
+            }
+        }
+    }
+
+    /* ToDo: Use mutex to skip this periodic checker */
+    if (jobs - done > 0) {
+        qemu_mod_timer(s->qtimer,
+            qemu_get_clock_ns(vm_clock) + 1);
+    } else {
+        qemu_mod_timer(s->qtimer,
+            qemu_get_clock_ns(vm_clock) + (get_ticks_per_sec() >> 2));
+    }
+}
+
+static void ftdmac020_handle_req(void *opaque, int line, int level)
+{
+    ftdmac020_state *s = FTDMAC020(opaque);
+
+    if (level) {
+        s->req |= (1 << line);
+    } else {
+        s->req &= ~(1 << line);
+        qemu_set_irq(s->ack[line], 0);
+    }
+}
+
+static void ftdmac020_chip_reset(ftdmac020_state *s)
+{
+    int i;
+
+    s->tcisr = 0;
+    s->eaisr = 0;
+    s->tcsr = 0;
+    s->easr = 0;
+    s->cesr = 0;
+    s->cbsr = 0;
+    s->csr  = 0;
+    s->sync = 0;
+
+    for (i = 0; i < 8; ++i) {
+        ftdmac020_chan_reset(s->chan + i);
+    }
+
+    /* We can assume our GPIO have been wired up now */
+    for (i = 0; i < 16; ++i) {
+        qemu_set_irq(s->ack[i], 0);
+    }
+    s->req = 0;
+}
+
+static uint64_t ftdmac020_mem_read(void *opaque, hwaddr addr, unsigned size)
+{
+    ftdmac020_state *s = FTDMAC020(opaque);
+    ftdmac020_chan  *c = NULL;
+    uint32_t i, ret = 0;
+
+    if (addr < 0x100) {
+        switch (addr) {
+        case REG_ISR:
+            /* 1. Checking TC interrupts */
+            ret |= s->tcisr & 0xff;
+            /* 2. Checking Error interrupts */
+            ret |= s->eaisr & 0xff;
+            /* 3. Checking Abort interrupts */
+            ret |= (s->eaisr >> 16) & 0xff;
+            break;
+        case REG_TCISR:
+            return s->tcisr;
+        case REG_EAISR:
+            return s->eaisr;
+        case REG_TCSR:
+            return s->tcsr;
+        case REG_EASR:
+            return s->easr;
+        case REG_CESR:
+            ret = 0;
+            for (i = 0; i < 8; ++i) {
+                c = s->chan + i;
+                ret |= (c->ccr & 0x01) ? (1 << i) : 0;
+            }
+            break;
+        case REG_CBSR:
+            return (s->busy > 0) ? (1 << s->busy) : 0;
+        case REG_CSR:
+            return s->csr;
+        case REG_SYNC:
+            return s->sync;
+        case REG_REVISION:
+            return 0x00011300;
+        case REG_FEATURE:
+            return 0x00008105;
+        default:
+            break;
+        }
+    } else if (addr < 0x1f8) {
+        c = s->chan + REG_CHAN_ID(addr);
+        switch (addr & 0x1f) {
+        case REG_CHAN_CCR:
+            return c->ccr;
+        case REG_CHAN_CFG:
+            ret = c->cfg;
+            ret |= (s->busy == c->id) ? (1 << 8) : 0;
+            ret |= (c->llp_cnt & 0x0f) << 16;
+            break;
+        case REG_CHAN_SRC:
+            return c->src;
+        case REG_CHAN_DST:
+            return c->dst;
+        case REG_CHAN_LLP:
+            return c->llp;
+        case REG_CHAN_LEN:
+            return c->len;
+        }
+    }
+
+    return ret;
+}
+
+static void ftdmac020_mem_write(void    *opaque,
+                                hwaddr   addr,
+                                uint64_t val,
+                                unsigned size)
+{
+    ftdmac020_state *s = FTDMAC020(opaque);
+    ftdmac020_chan  *c = NULL;
+
+    if (addr < 0x100) {
+        switch (addr) {
+        case REG_TCCLR:
+            s->tcisr &= ~((uint32_t)val);
+            s->tcsr &= ~((uint32_t)val);
+            ftdmac020_update_irq(s);
+            break;
+        case REG_EACLR:
+            s->eaisr &= ~((uint32_t)val);
+            s->easr &= ~((uint32_t)val);
+            ftdmac020_update_irq(s);
+            break;
+        case REG_CSR:
+            s->csr = (uint32_t)val;
+            break;
+        case REG_SYNC:
+            /* In QEMU, devices are all in the same clock domain
+             * so there is nothing needs to be done.
+             */
+            s->sync = (uint32_t)val;
+            break;
+        default:
+            break;
+        }
+    } else if (addr < 0x1f8) {
+        c = s->chan + REG_CHAN_ID(addr);
+        switch (addr & 0x1f) {
+        case REG_CHAN_CCR:
+            if (!(c->ccr & CCR_START) && (val & CCR_START)) {
+                c->llp_cnt = 0;
+            }
+            c->ccr = (uint32_t)val & 0x87FFBFFF;
+            if (c->ccr & CCR_START) {
+                ftdmac020_chan_ccr_decode(c);
+                /* kick-off DMA engine */
+                qemu_mod_timer(s->qtimer, qemu_get_clock_ns(vm_clock) + 1);
+            }
+            break;
+        case REG_CHAN_CFG:
+            c->cfg = (uint32_t)val & 0x3eff;
+            break;
+        case REG_CHAN_SRC:
+            c->src = (uint32_t)val;
+            break;
+        case REG_CHAN_DST:
+            c->dst = (uint32_t)val;
+            break;
+        case REG_CHAN_LLP:
+            c->llp = (uint32_t)val & 0xfffffffc;
+            break;
+        case REG_CHAN_LEN:
+            c->len = (uint32_t)val & 0x003fffff;
+            break;
+        default:
+            break;
+        }
+    }
+}
+
+static const MemoryRegionOps ftdmac020_ops = {
+    .read  = ftdmac020_mem_read,
+    .write = ftdmac020_mem_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4
+    }
+};
+
+static void ftdmac020_reset(DeviceState *ds)
+{
+    SysBusDevice *busdev = SYS_BUS_DEVICE(ds);
+    ftdmac020_state *s = FTDMAC020(FROM_SYSBUS(ftdmac020_state, busdev));
+
+    ftdmac020_chip_reset(s);
+}
+
+static int ftdmac020_init(SysBusDevice *dev)
+{
+    ftdmac020_state *s = FTDMAC020(FROM_SYSBUS(ftdmac020_state, dev));
+    int i;
+
+    memory_region_init_io(&s->iomem,
+                          &ftdmac020_ops,
+                          s,
+                          TYPE_FTDMAC020,
+                          0x1000);
+    sysbus_init_mmio(dev, &s->iomem);
+    for (i = 0; i < 3; ++i) {
+        sysbus_init_irq(dev, &s->irq[i]);
+    }
+    qdev_init_gpio_in(&s->busdev.qdev, ftdmac020_handle_req, 16);
+    qdev_init_gpio_out(&s->busdev.qdev, s->ack, 16);
+
+    s->busy = -1;
+    s->qtimer = qemu_new_timer_ns(vm_clock, ftdmac020_timer_tick, s);
+    for (i = 0; i < 8; ++i) {
+        ftdmac020_chan *c = s->chan + i;
+        c->id   = i;
+        c->chip = s;
+    }
+
+    return 0;
+}
+
+static const VMStateDescription vmstate_ftdmac020 = {
+    .name = TYPE_FTDMAC020,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .minimum_version_id_old = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT32(tcisr, ftdmac020_state),
+        VMSTATE_UINT32(eaisr, ftdmac020_state),
+        VMSTATE_UINT32(tcsr, ftdmac020_state),
+        VMSTATE_UINT32(easr, ftdmac020_state),
+        VMSTATE_UINT32(cesr, ftdmac020_state),
+        VMSTATE_UINT32(cbsr, ftdmac020_state),
+        VMSTATE_UINT32(csr, ftdmac020_state),
+        VMSTATE_UINT32(sync, ftdmac020_state),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void ftdmac020_class_init(ObjectClass *klass, void *data)
+{
+    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    k->init     = ftdmac020_init;
+    dc->vmsd    = &vmstate_ftdmac020;
+    dc->reset   = ftdmac020_reset;
+    dc->no_user = 1;
+}
+
+static const TypeInfo ftdmac020_info = {
+    .name           = TYPE_FTDMAC020,
+    .parent         = TYPE_SYS_BUS_DEVICE,
+    .instance_size  = sizeof(ftdmac020_state),
+    .class_init     = ftdmac020_class_init,
+};
+
+static void ftdmac020_register_types(void)
+{
+    type_register_static(&ftdmac020_info);
+}
+
+type_init(ftdmac020_register_types)
diff --git a/hw/ftdmac020.h b/hw/ftdmac020.h
new file mode 100644
index 0000000..e240a92
--- /dev/null
+++ b/hw/ftdmac020.h
@@ -0,0 +1,105 @@ 
+/*
+ * QEMU model of the FTDMAC020 DMA Controller
+ *
+ * Copyright (C) 2012 Faraday Technology
+ * Written by Dante Su <dantesu@faraday-tech.com>
+ *
+ * This file is licensed under GNU GPL.
+ *
+ * Note: The FTDMAC020 decreasing address mode is not implemented.
+ */
+
+#ifndef __FTDMAC020_H
+#define __FTDMAC020_H
+
+#define REG_ISR         0x00    /* Interrupt Status Register */
+#define REG_TCISR       0x04    /* Terminal Count Interrupt Status Register */
+#define REG_TCCLR       0x08    /* Terminal Count Status Clear Register */
+#define REG_EAISR       0x0c    /* Error/Abort Interrupt Status Register */
+#define REG_EACLR       0x10    /* Error/Abort Status Clear Register */
+#define REG_TCSR        0x14    /* Terminal Count Status Register */
+#define REG_EASR        0x18    /* Error/Abort Status Register */
+#define REG_CESR        0x1c    /* Channel Enable Status Register */
+#define REG_CBSR        0x20    /* Channel Busy Status Register */
+#define REG_CSR         0x24    /* Configuration Status Register */
+#define REG_SYNC        0x28    /* Synchronization Register */
+#define REG_REVISION    0x30
+#define REG_FEATURE     0x34
+
+#define REG_CHAN_ID(addr)   (((addr) - 0x100) >> 5)
+#define REG_CHAN_BASE(ch)   (0x100 + ((ch) << 5))
+
+#define REG_CHAN_CCR        0x00
+#define REG_CHAN_CFG        0x04
+#define REG_CHAN_SRC        0x08
+#define REG_CHAN_DST        0x0C
+#define REG_CHAN_LLP        0x10
+#define REG_CHAN_LEN        0x14
+
+/*
+ * Feature register
+ */
+#define FEATURE_NCHAN(f)    (((f) >> 12) & 0xF)
+#define FEATURE_BRIDGE(f)   ((f) & (1 << 10))
+#define FEATURE_DUALBUS(f)  ((f) & (1 << 9))
+#define FEATURE_LLP(f)      ((f) & (1 << 8))
+#define FEATURE_FIFOAW(f)   ((f) & 0xF)
+
+/*
+ * Channel control register
+ */
+#define CCR_START           (1 << 0)
+#define CCR_DST_M1          (1 << 1)
+#define CCR_SRC_M1          (1 << 2)
+#define CCR_DST_INC         (0 << 3)
+#define CCR_DST_DEC         (1 << 3)
+#define CCR_DST_FIXED       (2 << 3)
+#define CCR_SRC_INC         (0 << 5)
+#define CCR_SRC_DEC         (1 << 5)
+#define CCR_SRC_FIXED       (2 << 5)
+#define CCR_HANDSHAKE       (1 << 7)
+#define CCR_DST_WIDTH_8     (0 << 8)
+#define CCR_DST_WIDTH_16    (1 << 8)
+#define CCR_DST_WIDTH_32    (2 << 8)
+#define CCR_DST_WIDTH_64    (3 << 8)
+#define CCR_SRC_WIDTH_8     (0 << 11)
+#define CCR_SRC_WIDTH_16    (1 << 11)
+#define CCR_SRC_WIDTH_32    (2 << 11)
+#define CCR_SRC_WIDTH_64    (3 << 11)
+#define CCR_ABORT           (1 << 15)
+#define CCR_BURST_1         (0 << 16)
+#define CCR_BURST_4         (1 << 16)
+#define CCR_BURST_8         (2 << 16)
+#define CCR_BURST_16        (3 << 16)
+#define CCR_BURST_32        (4 << 16)
+#define CCR_BURST_64        (5 << 16)
+#define CCR_BURST_128       (6 << 16)
+#define CCR_BURST_256       (7 << 16)
+#define CCR_PRIVILEGED      (1 << 19)
+#define CCR_BUFFERABLE      (1 << 20)
+#define CCR_CACHEABLE       (1 << 21)
+#define CCR_PRIO_0          (0 << 22)
+#define CCR_PRIO_1          (1 << 22)
+#define CCR_PRIO_2          (2 << 22)
+#define CCR_PRIO_3          (3 << 22)
+#define CCR_FIFOTH_1        (0 << 24)
+#define CCR_FIFOTH_2        (1 << 24)
+#define CCR_FIFOTH_4        (2 << 24)
+#define CCR_FIFOTH_8        (3 << 24)
+#define CCR_FIFOTH_16       (4 << 24)
+#define CCR_MASK_TC         (1 << 31)
+
+/*
+ * Channel configuration register
+ */
+#define CFG_MASK_TCI            (1 << 0)    /* mask(disable) tc interrupt */
+#define CFG_MASK_EI             (1 << 1)    /* mask(disable) error interrupt */
+#define CFG_MASK_AI             (1 << 2)    /* mask(disable) abort interrupt */
+#define CFG_SRC_HANDSHAKE(x)    (((x) & 0xf) << 3)
+#define CFG_SRC_HANDSHAKE_EN    (1 << 7)
+#define CFG_BUSY                (1 << 8)
+#define CFG_DST_HANDSHAKE(x)    (((x) & 0xf) << 9)
+#define CFG_DST_HANDSHAKE_EN    (1 << 13)
+#define CFG_LLP_CNT(cfg)        (((cfg) >> 16) & 0xf)
+
+#endif    /* __FTDMAC020_H */