Patchwork [02/18] hw: add QEMU model for Faraday APB DMA

login
register
mail settings
Submitter Dante
Date Jan. 18, 2013, 6:28 a.m.
Message ID <1358490506-18281-2-git-send-email-dantesu@faraday-tech.com>
Download mbox | patch
Permalink /patch/213482/
State New
Headers show

Comments

Dante - Jan. 18, 2013, 6:28 a.m.
Signed-off-by: Kuo-Jung Su <dantesu@faraday-tech.com>
---
 hw/ftapbbrg020.c |  485 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 hw/ftapbbrg020.h |   43 +++++
 2 files changed, 528 insertions(+)
 create mode 100644 hw/ftapbbrg020.c
 create mode 100644 hw/ftapbbrg020.h
Blue Swirl - Jan. 19, 2013, 8:51 a.m.
On Fri, Jan 18, 2013 at 6:28 AM, Dante <dantesu@faraday-tech.com> wrote:
> Signed-off-by: Kuo-Jung Su <dantesu@faraday-tech.com>
> ---
>  hw/ftapbbrg020.c |  485 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  hw/ftapbbrg020.h |   43 +++++
>  2 files changed, 528 insertions(+)
>  create mode 100644 hw/ftapbbrg020.c
>  create mode 100644 hw/ftapbbrg020.h
>
> diff --git a/hw/ftapbbrg020.c b/hw/ftapbbrg020.c
> new file mode 100644
> index 0000000..3378312
> --- /dev/null
> +++ b/hw/ftapbbrg020.c
> @@ -0,0 +1,485 @@
> +/*
> + * QEMU model of the FTAPBBRG020 DMA Controller
> + *
> + * Copyright (C) 2012 Faraday Technology
> + * Copyright (C) 2012 Dante Su <dantesu@faraday-tech.com>
> + *
> + * Note: The FTAPBBRG020 DMA decreasing address mode is not implemented.
> + *
> + * Permission is hereby granted, free of charge, to any person obtaining a copy
> + * of this software and associated documentation files (the "Software"), to deal
> + * in the Software without restriction, including without limitation the rights
> + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
> + * copies of the Software, and to permit persons to whom the Software is
> + * furnished to do so, subject to the following conditions:
> + *
> + * The above copyright notice and this permission notice shall be included in
> + * all copies or substantial portions of the Software.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
> + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
> + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
> + * THE SOFTWARE.
> + */
> +
> +#include "sysbus.h"
> +#include "sysemu/sysemu.h"
> +#include "sysemu/blockdev.h"
> +
> +#include "ftapbbrg020.h"
> +
> +#ifndef min
> +#define min(a, b)    ((a) < (b) ? (a) : (b))
> +#endif
> +
> +#ifndef max
> +#define max(a, b)    ((a) > (b) ? (a) : (b))
> +#endif
> +
> +typedef struct _ftapbbrg020_state ftapbbrg020_state;

Underscores are not allowed at the start of identifiers, see HACKING.

> +
> +typedef struct _ftapbbrg020_chan {
> +    ftapbbrg020_state *chip;
> +
> +    int id;
> +    int burst;
> +    int src_bw;
> +    int src_stride;
> +    int dst_bw;
> +    int dst_stride;
> +
> +    /* HW register caches */
> +    uint32_t src;
> +    uint32_t dst;
> +    uint32_t len;
> +    uint32_t cmd;
> +} ftapbbrg020_chan;
> +
> +typedef struct _ftapbbrg020_state {
> +    SysBusDevice busdev;
> +    MemoryRegion iomem;
> +    qemu_irq irq;
> +
> +    ftapbbrg020_chan chan[4];
> +    qemu_irq         ack[16];
> +    uint32_t         req;
> +
> +    int busy;    /* Busy Channel ID */
> +    QEMUTimer *qtimer;
> +
> +} ftapbbrg020_state;
> +
> +static inline uint32_t
> +ftapbbrg020_get_isr(ftapbbrg020_state *s)
> +{
> +    int i;
> +    uint32_t isr = 0;
> +    ftapbbrg020_chan *chan;
> +
> +    for (i = 0; i < 4; ++i) {
> +        chan = s->chan + i;
> +        isr |= (chan->cmd & 0x12);
> +    }
> +
> +    return isr;
> +}
> +
> +static inline void
> +ftapbbrg020_update_irq(ftapbbrg020_state *s)
> +{
> +    uint32_t isr = ftapbbrg020_get_isr(s);
> +
> +    if (isr)
> +        qemu_set_irq(s->irq, 1);
> +    else
> +        qemu_set_irq(s->irq, 0);
> +}
> +
> +static void
> +ftapbbrg020_chan_cmd_decode(ftapbbrg020_chan *c)
> +{
> +    uint32_t tmp;
> +
> +    /* 1. decode burst size */
> +    c->burst = (c->cmd & 0x08) ? 4 : 1;
> +
> +    /* 2. decode source/destination width */
> +    tmp = (c->cmd >> 20) & 0x03;
> +    if (tmp > 2)
> +        tmp = 2;
> +    c->src_bw = c->dst_bw = 8 << (2 - tmp);
> +
> +    /* 3. decode source address stride */
> +    switch((c->cmd >> 8) & 0x03) {
> +    case 0:
> +        c->src_stride = 0;
> +        break;
> +    case 1:
> +        c->src_stride = c->src_bw >> 3;
> +        break;
> +    case 2:
> +        c->src_stride = 2 * (c->src_bw >> 3);
> +        break;
> +    case 3:
> +        c->src_stride = 4 * (c->src_bw >> 3);
> +        break;
> +    }
> +
> +    /* 4. decode destination address stride */
> +    switch((c->cmd >> 12) & 0x03) {
> +    case 0:
> +        c->dst_stride = 0;
> +        break;
> +    case 1:
> +        c->dst_stride = c->dst_bw >> 3;
> +        break;
> +    case 2:
> +        c->dst_stride = 2 * (c->dst_bw >> 3);
> +        break;
> +    case 3:
> +        c->dst_stride = 4 * (c->dst_bw >> 3);
> +        break;
> +    }
> +}
> +
> +static void
> +ftapbbrg020_chan_start(ftapbbrg020_chan *c)
> +{
> +    ftapbbrg020_state *s = c->chip;
> +    hwaddr src, dst, src_len, dst_len;
> +    uint8_t *src_map = NULL, *dst_map = NULL;
> +    uint8_t *src_ptr = NULL, *dst_ptr = NULL;

src_* could probably be const.

> +    uint8_t buf[4096];
> +    int src_hs = 0, dst_hs = 0, len = 0;
> +
> +    if (!(c->cmd & 0x01))
> +        return;
> +
> +    s->busy = c->id;
> +
> +    src = c->src;
> +    dst = c->dst;
> +    src_hs = (c->cmd >> 24) & 0xf;
> +    dst_hs = (c->cmd >> 16) & 0xf;
> +    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) {
> +
> +        if (src_hs && !(s->req & (1 << src_hs)))
> +            break;
> +
> +        if (dst_hs && !(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) = *(uint8_t *)src_ptr;

How is this and the other cases below different from memcpy()?

> +                    break;
> +                case 16:
> +                    for (i = 0; i < len; i += 2)
> +                        *(uint16_t *)(buf + i) = *(uint16_t *)src_ptr;
> +                    break;
> +                default:
> +                    for (i = 0; i < len; i += 4)
> +                        *(uint32_t *)(buf + i) = *(uint32_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) {
> +            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;
> +                default:
> +                    for (i = 0; i < len; i += 4)
> +                        *(uint32_t *)dst_ptr = *(uint32_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) {
> +            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(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->cmd & 0x04) {
> +                c->cmd |= 0x02;
> +                ftapbbrg020_update_irq(s);
> +            }
> +            /* clear start bit */
> +            c->cmd &= 0xfffffffe;
> +        }
> +    }
> +
> +    /* release the memory mappings */
> +    if (src_map)
> +        cpu_physical_memory_unmap(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 src/dst address */
> +    c->src = src;
> +    c->dst = dst;
> +
> +    s->busy = -1;
> +}
> +
> +static void
> +ftapbbrg020_chan_reset(ftapbbrg020_chan *c)
> +{
> +    c->cmd = 0;
> +    c->src = 0;
> +    c->dst = 0;
> +    c->len = 0;
> +}
> +
> +static void
> +ftapbbrg020_timer_tick(void *opaque)
> +{
> +    ftapbbrg020_state *s = (ftapbbrg020_state *)opaque;
> +    ftapbbrg020_chan *c = NULL;
> +    int i, jobs, done;
> +
> +    jobs = 0;
> +    done = 0;
> +    for (i = 0; i < 4; ++i) {
> +        c = s->chan + i;
> +        if (c->cmd & 0x01) {
> +            ++jobs;
> +            ftapbbrg020_chan_start(c);
> +            if (!(c->cmd & 0x01))
> +                ++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
> +ftapbbrg020_handle_req(void *opaque, int line, int level)
> +{
> +    ftapbbrg020_state *s = (ftapbbrg020_state *)opaque;

Useless cast in C.

> +
> +    if (level) {
> +        s->req |= (1 << line);
> +    } else {
> +        s->req &= ~(1 << line);
> +        qemu_set_irq(s->ack[line], 0);
> +    }
> +}
> +
> +static void ftapbbrg020_chip_reset(ftapbbrg020_state *s)
> +{
> +    int i;
> +
> +    for (i = 0; i < 4; ++i) {
> +        ftapbbrg020_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
> +ftapbbrg020_mem_read(void *opaque, hwaddr addr, unsigned int size)
> +{
> +    ftapbbrg020_state *s = opaque;
> +    ftapbbrg020_chan  *c = NULL;
> +    uint32_t ret = 0;
> +
> +    if (addr >= 0x80 && addr < 0xC0) {
> +        c = s->chan + REG_CHAN_ID(addr);
> +        switch(addr & 0x0f) {
> +        case REG_CHAN_CMD:
> +            return c->cmd;
> +        case REG_CHAN_SRC:
> +            return c->src;
> +        case REG_CHAN_DST:
> +            return c->dst;
> +        case REG_CHAN_CYC:
> +            return c->len;
> +        }
> +    } else {
> +        switch(addr) {
> +        case 0xC8:    /* revision register */

The comment could be replaced by self-documenting enum for 0xC8.

> +            return 0x00010800;
> +        default:
> +            break;
> +        }
> +    }
> +
> +    return ret;
> +}
> +
> +static void
> +ftapbbrg020_mem_write(void *opaque, hwaddr addr, uint64_t val, unsigned int size)
> +{
> +    ftapbbrg020_state *s = opaque;
> +    ftapbbrg020_chan  *c = NULL;
> +
> +    if (addr >= 0x80 && addr < 0xC0) {
> +        c = s->chan + REG_CHAN_ID(addr);
> +        switch(addr & 0x0f) {
> +        case REG_CHAN_CMD:
> +            c->cmd = (uint32_t)val;
> +            ftapbbrg020_update_irq(s);
> +            if (c->cmd & 0x01) {
> +                ftapbbrg020_chan_cmd_decode(c);
> +                /* kick-off DMA engine */
> +                qemu_mod_timer(s->qtimer, qemu_get_clock_ns(vm_clock) + 1);
> +            }
> +            break;
> +        case REG_CHAN_SRC:
> +            c->src = (uint32_t)val;
> +            break;
> +        case REG_CHAN_DST:
> +            c->dst = (uint32_t)val;
> +            break;
> +        case REG_CHAN_CYC:
> +            c->len = (uint32_t)val & 0x00ffffff;
> +            break;
> +        }
> +    }
> +}
> +
> +static const MemoryRegionOps ftapbbrg020_mem_ops = {
> +    .read  = ftapbbrg020_mem_read,
> +    .write = ftapbbrg020_mem_write,
> +    .endianness = DEVICE_LITTLE_ENDIAN,
> +    .valid = {
> +        .min_access_size = 4,
> +        .max_access_size = 4
> +    }
> +};
> +
> +static void ftapbbrg020_reset(DeviceState *d)
> +{
> +    ftapbbrg020_state *s = DO_UPCAST(ftapbbrg020_state, busdev.qdev, d);
> +    ftapbbrg020_chip_reset(s);
> +}
> +
> +static int ftapbbrg020_init(SysBusDevice *dev)
> +{
> +    ftapbbrg020_state *s = FROM_SYSBUS(typeof(*s), dev);
> +    int i;
> +
> +    memory_region_init_io(&s->iomem, &ftapbbrg020_mem_ops, s, "ftapbbrg020", 0x1000);
> +    sysbus_init_mmio(dev, &s->iomem);
> +    sysbus_init_irq(dev, &s->irq);
> +    qdev_init_gpio_in (&s->busdev.qdev, ftapbbrg020_handle_req, 16);
> +    qdev_init_gpio_out(&s->busdev.qdev, s->ack, 16);
> +
> +    s->busy = -1;
> +    s->qtimer = qemu_new_timer_ns(vm_clock, ftapbbrg020_timer_tick, s);
> +    for (i = 0; i < 4; ++i) {
> +        ftapbbrg020_chan *c = s->chan + i;
> +        c->id   = i;
> +        c->chip = s;
> +    }
> +
> +    return 0;
> +}
> +
> +static const VMStateDescription vmstate_ftapbbrg020 = {
> +    .name = "ftapbbrg020",
> +    .version_id = 1,
> +    .minimum_version_id = 1,
> +    .minimum_version_id_old = 1,
> +    .fields = (VMStateField[]) {
> +        VMSTATE_END_OF_LIST()
> +    }
> +};
> +
> +static void ftapbbrg020_class_init(ObjectClass *klass, void *data)
> +{
> +    SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass);
> +    DeviceClass *k = DEVICE_CLASS(klass);
> +
> +    sdc->init  = ftapbbrg020_init;
> +    k->vmsd    = &vmstate_ftapbbrg020;
> +    k->reset   = ftapbbrg020_reset;
> +    k->no_user = 1;
> +}
> +
> +static TypeInfo ftapbbrg020_info = {

const

> +    .name           = "ftapbbrg020",
> +    .parent         = TYPE_SYS_BUS_DEVICE,
> +    .instance_size  = sizeof(ftapbbrg020_state),
> +    .class_init     = ftapbbrg020_class_init,
> +};
> +
> +static void ftapbbrg020_register_types(void)
> +{
> +    type_register_static(&ftapbbrg020_info);
> +}
> +
> +type_init(ftapbbrg020_register_types)
> diff --git a/hw/ftapbbrg020.h b/hw/ftapbbrg020.h
> new file mode 100644
> index 0000000..0279f10
> --- /dev/null
> +++ b/hw/ftapbbrg020.h
> @@ -0,0 +1,43 @@
> +/*
> + *  arch/arm/mach-faraday/drivers/ftapbbrg020.h
> + *
> + *  Faraday FTAPBB020 APB Bridge with DMA function
> + *
> + *  Copyright (C) 2010 Faraday Technology
> + *  Copyright (C) 2012 Dante Su <dantesu@faraday-tech.com>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
> + */
> +
> +#ifndef __FTAPBBRG020_H
> +#define __FTAPBBRG020_H
> +
> +/*
> + * Channel base address
> + * @ch: channle id (0 <= id <= 3)
> + *      i.e. 0: Channel A
> + *           1: Channel B
> + *           2: Channel C
> + *           3: Channel D
> + */
> +#define REG_CHAN_ID(off)    (((off) - 0x80) >> 4)
> +#define REG_CHAN_BASE(ch)    (0x80 + ((ch) << 4))
> +
> +#define REG_CHAN_SRC        0x00
> +#define REG_CHAN_DST        0x04
> +#define REG_CHAN_CYC        0x08
> +#define REG_CHAN_CMD        0x0C
> +
> +#endif    /* __FTAPBB020_H */
> --
> 1.7.9.5
>
>
> ********************* Confidentiality Notice ************************
> This electronic message and any attachments may contain
> confidential and legally privileged information or
> information which is otherwise protected from disclosure.
> If you are not the intended recipient,please do not disclose
> the contents, either in whole or in part, to anyone,and
> immediately delete the message and any attachments from
> your computer system and destroy all hard copies.
> Thank you for your cooperation.
> ***********************************************************************
>
>

Patch

diff --git a/hw/ftapbbrg020.c b/hw/ftapbbrg020.c
new file mode 100644
index 0000000..3378312
--- /dev/null
+++ b/hw/ftapbbrg020.c
@@ -0,0 +1,485 @@ 
+/*
+ * QEMU model of the FTAPBBRG020 DMA Controller
+ *
+ * Copyright (C) 2012 Faraday Technology
+ * Copyright (C) 2012 Dante Su <dantesu@faraday-tech.com>
+ *
+ * Note: The FTAPBBRG020 DMA decreasing address mode is not implemented.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "sysbus.h"
+#include "sysemu/sysemu.h"
+#include "sysemu/blockdev.h"
+
+#include "ftapbbrg020.h"
+
+#ifndef min
+#define min(a, b)    ((a) < (b) ? (a) : (b))
+#endif
+
+#ifndef max
+#define max(a, b)    ((a) > (b) ? (a) : (b))
+#endif
+
+typedef struct _ftapbbrg020_state ftapbbrg020_state;
+
+typedef struct _ftapbbrg020_chan {
+    ftapbbrg020_state *chip;
+
+    int id;
+    int burst;
+    int src_bw;
+    int src_stride;
+    int dst_bw;
+    int dst_stride;
+    
+    /* HW register caches */
+    uint32_t src;
+    uint32_t dst;
+    uint32_t len;
+    uint32_t cmd;
+} ftapbbrg020_chan;
+
+typedef struct _ftapbbrg020_state {
+    SysBusDevice busdev;
+    MemoryRegion iomem;
+    qemu_irq irq;
+    
+    ftapbbrg020_chan chan[4];
+    qemu_irq         ack[16];
+    uint32_t         req;
+
+    int busy;    /* Busy Channel ID */
+    QEMUTimer *qtimer;
+
+} ftapbbrg020_state;
+
+static inline uint32_t
+ftapbbrg020_get_isr(ftapbbrg020_state *s)
+{
+    int i;
+    uint32_t isr = 0;
+    ftapbbrg020_chan *chan;
+
+    for (i = 0; i < 4; ++i) {
+        chan = s->chan + i;
+        isr |= (chan->cmd & 0x12);
+    }
+
+    return isr;
+}
+
+static inline void 
+ftapbbrg020_update_irq(ftapbbrg020_state *s)
+{
+    uint32_t isr = ftapbbrg020_get_isr(s);
+    
+    if (isr)
+        qemu_set_irq(s->irq, 1);
+    else
+        qemu_set_irq(s->irq, 0);
+}
+
+static void 
+ftapbbrg020_chan_cmd_decode(ftapbbrg020_chan *c)
+{
+    uint32_t tmp;
+    
+    /* 1. decode burst size */
+    c->burst = (c->cmd & 0x08) ? 4 : 1;
+        
+    /* 2. decode source/destination width */
+    tmp = (c->cmd >> 20) & 0x03;
+    if (tmp > 2)
+        tmp = 2;
+    c->src_bw = c->dst_bw = 8 << (2 - tmp);
+
+    /* 3. decode source address stride */
+    switch((c->cmd >> 8) & 0x03) {
+    case 0:
+        c->src_stride = 0;
+        break;
+    case 1:
+        c->src_stride = c->src_bw >> 3;
+        break;
+    case 2:
+        c->src_stride = 2 * (c->src_bw >> 3);
+        break;
+    case 3:
+        c->src_stride = 4 * (c->src_bw >> 3);
+        break;
+    }
+
+    /* 4. decode destination address stride */
+    switch((c->cmd >> 12) & 0x03) {
+    case 0:
+        c->dst_stride = 0;
+        break;
+    case 1:
+        c->dst_stride = c->dst_bw >> 3;
+        break;
+    case 2:
+        c->dst_stride = 2 * (c->dst_bw >> 3);
+        break;
+    case 3:
+        c->dst_stride = 4 * (c->dst_bw >> 3);
+        break;
+    }
+}
+
+static void 
+ftapbbrg020_chan_start(ftapbbrg020_chan *c)
+{
+    ftapbbrg020_state *s = c->chip;
+    hwaddr src, dst, src_len, dst_len;
+    uint8_t *src_map = NULL, *dst_map = NULL;
+    uint8_t *src_ptr = NULL, *dst_ptr = NULL;
+    uint8_t buf[4096];
+    int src_hs = 0, dst_hs = 0, len = 0;
+
+    if (!(c->cmd & 0x01))
+        return;
+
+    s->busy = c->id;
+    
+    src = c->src;
+    dst = c->dst;
+    src_hs = (c->cmd >> 24) & 0xf;
+    dst_hs = (c->cmd >> 16) & 0xf;
+    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) {
+
+        if (src_hs && !(s->req & (1 << src_hs)))
+            break;
+
+        if (dst_hs && !(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) = *(uint8_t *)src_ptr;
+                    break;
+                case 16:
+                    for (i = 0; i < len; i += 2)
+                        *(uint16_t *)(buf + i) = *(uint16_t *)src_ptr;
+                    break;
+                default:
+                    for (i = 0; i < len; i += 4)
+                        *(uint32_t *)(buf + i) = *(uint32_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) {
+            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;
+                default:
+                    for (i = 0; i < len; i += 4)
+                        *(uint32_t *)dst_ptr = *(uint32_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) {
+            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(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->cmd & 0x04) {
+                c->cmd |= 0x02;
+                ftapbbrg020_update_irq(s);
+            }
+            /* clear start bit */
+            c->cmd &= 0xfffffffe;
+        }
+    }
+    
+    /* release the memory mappings */
+    if (src_map)
+        cpu_physical_memory_unmap(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 src/dst address */
+    c->src = src;
+    c->dst = dst;
+
+    s->busy = -1;
+}
+
+static void 
+ftapbbrg020_chan_reset(ftapbbrg020_chan *c)
+{
+    c->cmd = 0;
+    c->src = 0;
+    c->dst = 0;
+    c->len = 0;
+}
+
+static void 
+ftapbbrg020_timer_tick(void *opaque)
+{
+    ftapbbrg020_state *s = (ftapbbrg020_state *)opaque;
+    ftapbbrg020_chan *c = NULL;
+    int i, jobs, done;
+    
+    jobs = 0;
+    done = 0;
+    for (i = 0; i < 4; ++i) {
+        c = s->chan + i;
+        if (c->cmd & 0x01) {
+            ++jobs;
+            ftapbbrg020_chan_start(c);
+            if (!(c->cmd & 0x01))
+                ++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 
+ftapbbrg020_handle_req(void *opaque, int line, int level)
+{
+    ftapbbrg020_state *s = (ftapbbrg020_state *)opaque;
+
+    if (level) {
+        s->req |= (1 << line);
+    } else {
+        s->req &= ~(1 << line);
+        qemu_set_irq(s->ack[line], 0);
+    }
+}
+
+static void ftapbbrg020_chip_reset(ftapbbrg020_state *s)
+{
+    int i;
+    
+    for (i = 0; i < 4; ++i) {
+        ftapbbrg020_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
+ftapbbrg020_mem_read(void *opaque, hwaddr addr, unsigned int size)
+{
+    ftapbbrg020_state *s = opaque;
+    ftapbbrg020_chan  *c = NULL;
+    uint32_t ret = 0;
+
+    if (addr >= 0x80 && addr < 0xC0) {
+        c = s->chan + REG_CHAN_ID(addr);
+        switch(addr & 0x0f) {
+        case REG_CHAN_CMD:
+            return c->cmd;
+        case REG_CHAN_SRC:
+            return c->src;
+        case REG_CHAN_DST:
+            return c->dst;
+        case REG_CHAN_CYC:
+            return c->len;
+        }
+    } else {
+        switch(addr) {
+        case 0xC8:    /* revision register */
+            return 0x00010800;
+        default:
+            break;
+        }
+    }
+
+    return ret;
+}
+
+static void
+ftapbbrg020_mem_write(void *opaque, hwaddr addr, uint64_t val, unsigned int size)
+{
+    ftapbbrg020_state *s = opaque;
+    ftapbbrg020_chan  *c = NULL;
+    
+    if (addr >= 0x80 && addr < 0xC0) {
+        c = s->chan + REG_CHAN_ID(addr);
+        switch(addr & 0x0f) {
+        case REG_CHAN_CMD:
+            c->cmd = (uint32_t)val;
+            ftapbbrg020_update_irq(s);
+            if (c->cmd & 0x01) {
+                ftapbbrg020_chan_cmd_decode(c);
+                /* kick-off DMA engine */
+                qemu_mod_timer(s->qtimer, qemu_get_clock_ns(vm_clock) + 1);
+            }
+            break;
+        case REG_CHAN_SRC:
+            c->src = (uint32_t)val;
+            break;
+        case REG_CHAN_DST:
+            c->dst = (uint32_t)val;
+            break;
+        case REG_CHAN_CYC:
+            c->len = (uint32_t)val & 0x00ffffff;
+            break;
+        }
+    }
+}
+
+static const MemoryRegionOps ftapbbrg020_mem_ops = {
+    .read  = ftapbbrg020_mem_read,
+    .write = ftapbbrg020_mem_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4
+    }
+};
+
+static void ftapbbrg020_reset(DeviceState *d)
+{
+    ftapbbrg020_state *s = DO_UPCAST(ftapbbrg020_state, busdev.qdev, d);
+    ftapbbrg020_chip_reset(s);
+}
+
+static int ftapbbrg020_init(SysBusDevice *dev)
+{
+    ftapbbrg020_state *s = FROM_SYSBUS(typeof(*s), dev);
+    int i;
+
+    memory_region_init_io(&s->iomem, &ftapbbrg020_mem_ops, s, "ftapbbrg020", 0x1000);
+    sysbus_init_mmio(dev, &s->iomem);
+    sysbus_init_irq(dev, &s->irq);
+    qdev_init_gpio_in (&s->busdev.qdev, ftapbbrg020_handle_req, 16);
+    qdev_init_gpio_out(&s->busdev.qdev, s->ack, 16);
+    
+    s->busy = -1;
+    s->qtimer = qemu_new_timer_ns(vm_clock, ftapbbrg020_timer_tick, s);
+    for (i = 0; i < 4; ++i) {
+        ftapbbrg020_chan *c = s->chan + i;
+        c->id   = i;
+        c->chip = s;
+    }
+
+    return 0;
+}
+
+static const VMStateDescription vmstate_ftapbbrg020 = {
+    .name = "ftapbbrg020",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .minimum_version_id_old = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void ftapbbrg020_class_init(ObjectClass *klass, void *data)
+{
+    SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass);
+    DeviceClass *k = DEVICE_CLASS(klass);
+
+    sdc->init  = ftapbbrg020_init;
+    k->vmsd    = &vmstate_ftapbbrg020;
+    k->reset   = ftapbbrg020_reset;
+    k->no_user = 1;
+}
+
+static TypeInfo ftapbbrg020_info = {
+    .name           = "ftapbbrg020",
+    .parent         = TYPE_SYS_BUS_DEVICE,
+    .instance_size  = sizeof(ftapbbrg020_state),
+    .class_init     = ftapbbrg020_class_init,
+};
+
+static void ftapbbrg020_register_types(void)
+{
+    type_register_static(&ftapbbrg020_info);
+}
+
+type_init(ftapbbrg020_register_types)
diff --git a/hw/ftapbbrg020.h b/hw/ftapbbrg020.h
new file mode 100644
index 0000000..0279f10
--- /dev/null
+++ b/hw/ftapbbrg020.h
@@ -0,0 +1,43 @@ 
+/*
+ *  arch/arm/mach-faraday/drivers/ftapbbrg020.h
+ *
+ *  Faraday FTAPBB020 APB Bridge with DMA function
+ *
+ *  Copyright (C) 2010 Faraday Technology
+ *  Copyright (C) 2012 Dante Su <dantesu@faraday-tech.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef __FTAPBBRG020_H
+#define __FTAPBBRG020_H
+
+/*
+ * Channel base address
+ * @ch: channle id (0 <= id <= 3)
+ *      i.e. 0: Channel A
+ *           1: Channel B
+ *           2: Channel C
+ *           3: Channel D
+ */
+#define REG_CHAN_ID(off)    (((off) - 0x80) >> 4)
+#define REG_CHAN_BASE(ch)    (0x80 + ((ch) << 4))
+
+#define REG_CHAN_SRC        0x00
+#define REG_CHAN_DST        0x04
+#define REG_CHAN_CYC        0x08
+#define REG_CHAN_CMD        0x0C
+
+#endif    /* __FTAPBB020_H */