Message ID | 1364213400-10266-11-git-send-email-dantesu@gmail.com |
---|---|
State | New |
Headers | show |
Hi Kuo-Jung, On Mon, Mar 25, 2013 at 10:09 PM, Kuo-Jung Su <dantesu@gmail.com> wrote: > From: Kuo-Jung Su <dantesu@faraday-tech.com> > > The Faraday FTDMAC020 provides eight configurable > channels for the memory-to-memory, memory-to-peripheral, > peripheral-to-peripheral, and peripheral-to-memory transfers. > > 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/arm/Makefile.objs | 2 +- > hw/arm/ftplat_a369soc.c | 14 ++ > hw/ftdmac020.c | 599 +++++++++++++++++++++++++++++++++++++++++++++++ > hw/ftdmac020.h | 107 +++++++++ > 4 files changed, 721 insertions(+), 1 deletion(-) > create mode 100644 hw/ftdmac020.c > create mode 100644 hw/ftdmac020.h > > diff --git a/hw/arm/Makefile.objs b/hw/arm/Makefile.objs > index 6a41b21..6510c51 100644 > --- a/hw/arm/Makefile.objs > +++ b/hw/arm/Makefile.objs > @@ -25,7 +25,7 @@ obj-y += strongarm.o > obj-y += imx_serial.o imx_ccm.o imx_timer.o imx_avic.o > obj-$(CONFIG_KVM) += kvm/arm_gic.o > obj-y += ftintc020.o ftahbc020.o ftddrii030.o ftpwmtmr010.o ftwdt010.o \ > - ftrtc011.o > + ftrtc011.o ftdmac020.o > > obj-y := $(addprefix ../,$(obj-y)) > > diff --git a/hw/arm/ftplat_a369soc.c b/hw/arm/ftplat_a369soc.c > index bd696c4..59e2c61 100644 > --- a/hw/arm/ftplat_a369soc.c > +++ b/hw/arm/ftplat_a369soc.c > @@ -168,6 +168,20 @@ static void a369soc_chip_init(FaradaySoCState *s) > sysbus_connect_irq(SYS_BUS_DEVICE(ds), 3, s->pic[44]); > /* Hour (Edge) */ > sysbus_connect_irq(SYS_BUS_DEVICE(ds), 4, s->pic[45]); > + > + /* ftdmac020 */ > + s->hdma[0] = sysbus_create_varargs("ftdmac020", > + 0x90300000, > + s->pic[0], /* ALL (NC in A369) */ > + s->pic[15], /* TC */ > + s->pic[16], /* ERR */ > + NULL); > + s->hdma[1] = sysbus_create_varargs("ftdmac020", > + 0x96100000, > + s->pic[0], /* ALL (NC in A369) */ > + s->pic[17], /* TC */ > + s->pic[18], /* ERR */ > + NULL); > } > > static void a369soc_realize(DeviceState *dev, Error **errp) > diff --git a/hw/ftdmac020.c b/hw/ftdmac020.c > new file mode 100644 > index 0000000..81b49b2 > --- /dev/null > +++ b/hw/ftdmac020.c > @@ -0,0 +1,599 @@ > +/* > + * 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 descending address mode is not implemented. > + */ > + > +#include "hw/sysbus.h" > +#include "sysemu/dma.h" > +#include "sysemu/sysemu.h" > +#include "sysemu/blockdev.h" > + > +#include "hw/ftdmac020.h" > + > +#define TYPE_FTDMAC020 "ftdmac020" > + > +enum ftdmac020_irqpin { > + IRQ_ALL = 0, > + IRQ_TC, > + IRQ_ERR, > +}; > + > +typedef struct Ftdmac020State Ftdmac020State; > + > +/** > + * struct Ftdmac020LLD - 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 Ftdmac020LLD { > + uint32_t src; > + uint32_t dst; > + uint32_t next; > + uint32_t ctrl; > + uint32_t size; > +} Ftdmac020LLD; > + > +typedef struct Ftdmac020Chan { > + Ftdmac020State *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; > +} Ftdmac020Chan; > + > +typedef struct Ftdmac020State { > + /*< private >*/ > + SysBusDevice parent; > + > + /*< public >*/ > + MemoryRegion iomem; > + qemu_irq irq[3]; > + > + Ftdmac020Chan chan[8]; > + qemu_irq ack[16]; > + uint32_t req; > + > + int busy; /* Busy Channel ID */ > + int bh_owner; > + QEMUBH *bh; > + DMAContext *dma; > + > + /* 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; > +} Ftdmac020State; > + > +#define FTDMAC020(obj) \ > + OBJECT_CHECK(Ftdmac020State, obj, TYPE_FTDMAC020) > + > +static void ftdmac020_update_irq(Ftdmac020State *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(Ftdmac020Chan *c) > +{ > + uint32_t tmp; > + > + /* 1. decode burst size */ > + tmp = extract32(c->ccr, 16, 3); > + c->burst = 1 << (tmp ? tmp + 1 : 0); > + > + /* 2. decode source width */ > + tmp = extract32(c->ccr, 11, 2); > + c->src_bw = 8 << tmp; > + > + /* 3. decode destination width */ > + tmp = extract32(c->ccr, 8, 2); > + c->dst_bw = 8 << tmp; > + > + /* 4. decode source address stride */ > + tmp = extract32(c->ccr, 5, 2); > + if (tmp == 2) { > + c->src_stride = 0; > + } else { > + c->src_stride = c->src_bw >> 3; > + } > + > + /* 5. decode destination address stride */ > + tmp = extract32(c->ccr, 3, 2); > + if (tmp == 2) { > + c->dst_stride = 0; > + } else { > + c->dst_stride = c->dst_bw >> 3; > + } > +} > + > +static void ftdmac020_chan_start(Ftdmac020Chan *c) > +{ > + Ftdmac020State *s = c->chip; > + Ftdmac020LLD desc; > + hwaddr src, dst; > + uint8_t buf[4096] __attribute__ ((aligned (8))); > + int i, len, stride, src_hs, dst_hs; > + > + if (!(c->ccr & CCR_START)) { > + return; > + } > + > + s->busy = c->id; > + > + /* DMA src/dst address */ > + src = c->src; > + dst = c->dst; > + > + /* DMA hardware handshake id */ > + src_hs = (c->cfg & CFG_SRC_HANDSHAKE_EN) ? extract32(c->cfg, 3, 4) : -1; > + dst_hs = (c->cfg & CFG_DST_HANDSHAKE_EN) ? extract32(c->cfg, 9, 4) : -1; > + > + /* DMA src/dst sanity check */ > + if (cpu_physical_memory_is_io(src) && c->src_stride) { > + fprintf(stderr, > + "ftdmac020: src is an I/O device with non-fixed address?\n"); > + abort(); > + } This doesn't look like a QEMU fatal to me. Its seems like software usage policy of the DMA. Does the real hardware actually do any preventative action on striding io accesses? If not then I would either remove this assertion or at least demote it to a LOG_GUEST_ERROR. There may be corner case applications where this is valid. I think its also worthwhile trying to avoid use of cpu_physical_foo() functions from device land. > + if (cpu_physical_memory_is_io(dst) && c->dst_stride) { > + fprintf(stderr, > + "ftdmac020: dst is an I/O device with non-fixed address?\n"); > + abort(); > + } > + > + while (c->len > 0) { > + /* > + * Postpone this DMA action > + * if the corresponding dma request is not asserted > + */ > + if ((src_hs >= 0) && !(s->req & BIT(src_hs))) { > + break; > + } > + if ((dst_hs >= 0) && !(s->req & BIT(dst_hs))) { > + break; > + } > + > + len = MIN(sizeof(buf), c->burst * (c->src_bw >> 3)); > + > + /* load data from source into local buffer */ > + if (c->src_stride) { > + dma_memory_read(s->dma, src, buf, len); > + src += len; > + } else { > + stride = c->src_bw >> 3; > + for (i = 0; i < len; i += stride) { > + dma_memory_read(s->dma, src, buf + i, stride); > + } > + } > + > + /* DMA Hardware Handshake */ > + if (src_hs >= 0) { > + qemu_set_irq(s->ack[src_hs], 1); > + } > + > + /* store data into destination from local buffer */ > + if (c->dst_stride) { > + dma_memory_write(s->dma, dst, buf, len); > + dst += len; > + } else { > + stride = c->dst_bw >> 3; > + for (i = 0; i < len; i += stride) { > + dma_memory_write(s->dma, dst, buf + i, 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) { > + /* update the channel transfer status */ > + if (!(c->ccr & CCR_MASK_TC)) { > + s->tcsr |= BIT(c->id); > + if (!(c->cfg & CFG_MASK_TCI)) { > + s->tcisr |= BIT(c->id); > + } > + ftdmac020_update_irq(s); > + } > + /* try to load next lld */ > + if (c->llp) { > + c->llp_cnt += 1; > + dma_memory_read(s->dma, 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) > + | (extract32(desc.ctrl, 29, 3) << 24) > + | ((desc.ctrl & BIT(28)) ? CCR_MASK_TC : 0) > + | (extract32(desc.ctrl, 25, 3) << 11) > + | (extract32(desc.ctrl, 22, 3) << 8) > + | (extract32(desc.ctrl, 20, 2) << 5) > + | (extract32(desc.ctrl, 18, 2) << 3) > + | (extract32(desc.ctrl, 16, 2) << 1); > + ftdmac020_chan_ccr_decode(c); > + > + src = c->src; > + dst = c->dst; > + } else { > + /* clear dma start bit */ > + c->ccr &= ~CCR_START; > + } > + } > + } > + > + /* update dma src/dst address */ > + c->src = src; > + c->dst = dst; > + > + s->busy = -1; > +} > + > +static void ftdmac020_chan_reset(Ftdmac020Chan *c) > +{ > + c->ccr = 0; > + c->cfg = 0; > + c->src = 0; > + c->dst = 0; > + c->llp = 0; > + c->len = 0; > +} > + > +static void ftdmac020_bh(void *opaque) > +{ > + Ftdmac020State *s = FTDMAC020(opaque); > + Ftdmac020Chan *c = NULL; > + int i, jobs, done; > + > + ++s->bh_owner; > + 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; > + } > + } > + } > + --s->bh_owner; > + > + /* > + * Devices those with an infinite FIFO (always ready for R/W) Grammar (- the "those"?). > + * would trigger a new DMA handshake transaction here. > + * (i.e. ftnandc021, ftsdc010) > + */ > + if ((jobs - done) && s->req) { > + qemu_bh_schedule(s->bh); > + } > +} > + > +static void ftdmac020_handle_req(void *opaque, int line, int level) > +{ > + Ftdmac020State *s = FTDMAC020(opaque); > + > + if (level) { > + /* > + * Devices those wait for data from externaI/O s/those/that > + * would trigger a new DMA handshake transaction here. > + * (i.e. ftssp010) A nitpick, but do you mean e.g. instead of i.e.? Regards, Peter > + */ > + if (!(s->req & BIT(line))) { > + /* a simple workaround for BH reentry issue */ > + if (!s->bh_owner) { > + qemu_bh_schedule(s->bh); > + } > + } > + s->req |= BIT(line); > + } else { > + s->req &= ~BIT(line); > + qemu_set_irq(s->ack[line], 0); > + } > +} > + > +static void ftdmac020_chip_reset(Ftdmac020State *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) > +{ > + Ftdmac020State *s = FTDMAC020(opaque); > + Ftdmac020Chan *c = NULL; > + uint32_t i, ret = 0; > + > + 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: > + for (i = 0; i < 8; ++i) { > + c = s->chan + i; > + ret |= (c->ccr & CCR_START) ? BIT(i) : 0; > + } > + break; > + case REG_CBSR: > + return (s->busy > 0) ? BIT(s->busy) : 0; > + case REG_CSR: > + return s->csr; > + case REG_SYNC: > + return s->sync; > + case REG_REVISION: > + /* rev. = 1.13.0 */ > + return 0x00011300; > + case REG_FEATURE: > + /* fifo = 32 bytes, support linked list, 8 channels, AHB0 only */ > + return 0x00008105; > + case REG_CHAN_BASE(0) ... REG_CHAN_BASE(7) + 0x14: > + 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; > + default: > + qemu_log_mask(LOG_GUEST_ERROR, > + "ftdmac020: undefined memory access@%#" HWADDR_PRIx "\n", > + addr); > + break; > + } > + break; > + default: > + qemu_log_mask(LOG_GUEST_ERROR, > + "ftdmac020: undefined memory access@%#" HWADDR_PRIx "\n", addr); > + break; > + } > + > + return ret; > +} > + > +static void ftdmac020_mem_write(void *opaque, > + hwaddr addr, > + uint64_t val, > + unsigned size) > +{ > + Ftdmac020State *s = FTDMAC020(opaque); > + Ftdmac020Chan *c = NULL; > + > + 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; > + case REG_CHAN_BASE(0) ... REG_CHAN_BASE(7) + 0x14: > + 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_bh_schedule(s->bh); > + } > + 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: > + qemu_log_mask(LOG_GUEST_ERROR, > + "ftdmac020: undefined memory access@%#" HWADDR_PRIx "\n", > + addr); > + break; > + } > + break; > + default: > + qemu_log_mask(LOG_GUEST_ERROR, > + "ftdmac020: undefined memory access@%#" HWADDR_PRIx "\n", addr); > + break; > + } > +} > + > +static const MemoryRegionOps mmio_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) > +{ > + Ftdmac020State *s = FTDMAC020(SYS_BUS_DEVICE(ds)); > + > + ftdmac020_chip_reset(s); > +} > + > +static void ftdmac020_realize(DeviceState *dev, Error **errp) > +{ > + Ftdmac020State *s = FTDMAC020(dev); > + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); > + int i; > + > + memory_region_init_io(&s->iomem, > + &mmio_ops, > + s, > + TYPE_FTDMAC020, > + 0x1000); > + sysbus_init_mmio(sbd, &s->iomem); > + for (i = 0; i < 3; ++i) { > + sysbus_init_irq(sbd, &s->irq[i]); > + } > + qdev_init_gpio_in(&sbd->qdev, ftdmac020_handle_req, 16); > + qdev_init_gpio_out(&sbd->qdev, s->ack, 16); > + > + s->busy = -1; > + s->dma = &dma_context_memory; > + s->bh = qemu_bh_new(ftdmac020_bh, s); > + for (i = 0; i < 8; ++i) { > + Ftdmac020Chan *c = s->chan + i; > + c->id = i; > + c->chip = s; > + } > +} > + > +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, Ftdmac020State), > + VMSTATE_UINT32(eaisr, Ftdmac020State), > + VMSTATE_UINT32(tcsr, Ftdmac020State), > + VMSTATE_UINT32(easr, Ftdmac020State), > + VMSTATE_UINT32(cesr, Ftdmac020State), > + VMSTATE_UINT32(cbsr, Ftdmac020State), > + VMSTATE_UINT32(csr, Ftdmac020State), > + VMSTATE_UINT32(sync, Ftdmac020State), > + VMSTATE_END_OF_LIST() > + } > +}; > + > +static void ftdmac020_class_init(ObjectClass *klass, void *data) > +{ > + DeviceClass *dc = DEVICE_CLASS(klass); > + > + dc->vmsd = &vmstate_ftdmac020; > + dc->reset = ftdmac020_reset; > + dc->realize = ftdmac020_realize; > + dc->no_user = 1; > +} > + > +static const TypeInfo ftdmac020_info = { > + .name = TYPE_FTDMAC020, > + .parent = TYPE_SYS_BUS_DEVICE, > + .instance_size = sizeof(Ftdmac020State), > + .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..86ee58c > --- /dev/null > +++ b/hw/ftdmac020.h > @@ -0,0 +1,107 @@ > +/* > + * 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. > + */ > + > +#ifndef HW_ARM_FTDMAC020_H > +#define HW_ARM_FTDMAC020_H > + > +#include "qemu/bitops.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) & BIT(10)) > +#define FEATURE_DUALBUS(f) ((f) & BIT(9)) > +#define FEATURE_LLP(f) ((f) & BIT(8)) > +#define FEATURE_FIFOAW(f) ((f) & 0xF) > + > +/* > + * Channel control register > + */ > +#define CCR_START BIT(0) > +#define CCR_DST_M1 BIT(1) /* dst is a slave device of AHB1 */ > +#define CCR_SRC_M1 BIT(2) /* src is a slave device of AHB1 */ > +#define CCR_DST_INC (0 << 3)/* dst addr: next = curr++ */ > +#define CCR_DST_DEC (1 << 3)/* dst addr: next = curr-- */ > +#define CCR_DST_FIXED (2 << 3) > +#define CCR_SRC_INC (0 << 5)/* src addr: next = curr++ */ > +#define CCR_SRC_DEC (1 << 5)/* src addr: next = curr-- */ > +#define CCR_SRC_FIXED (2 << 5) > +#define CCR_HANDSHAKE BIT(7) /* DMA HW handshake enabled */ > +#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 BIT(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 BIT(19) > +#define CCR_BUFFERABLE BIT(20) > +#define CCR_CACHEABLE BIT(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 BIT(31) /* DMA Transfer terminated(finished) */ > + > +/* > + * Channel configuration register > + */ > +#define CFG_MASK_TCI BIT(0) /* disable tc interrupt */ > +#define CFG_MASK_EI BIT(1) /* disable error interrupt */ > +#define CFG_MASK_AI BIT(2) /* disable abort interrupt */ > +#define CFG_SRC_HANDSHAKE(x) (((x) & 0xf) << 3) > +#define CFG_SRC_HANDSHAKE_EN BIT(7) > +#define CFG_BUSY BIT(8) > +#define CFG_DST_HANDSHAKE(x) (((x) & 0xf) << 9) > +#define CFG_DST_HANDSHAKE_EN BIT(13) > +#define CFG_LLP_CNT(cfg) (((cfg) >> 16) & 0xf) > + > +#endif /* HW_ARM_FTDMAC020_H */ > -- > 1.7.9.5 > >
2013/3/29 Peter Crosthwaite <peter.crosthwaite@xilinx.com>: > Hi Kuo-Jung, > > On Mon, Mar 25, 2013 at 10:09 PM, Kuo-Jung Su <dantesu@gmail.com> wrote: >> From: Kuo-Jung Su <dantesu@faraday-tech.com> >> >> The Faraday FTDMAC020 provides eight configurable >> channels for the memory-to-memory, memory-to-peripheral, >> peripheral-to-peripheral, and peripheral-to-memory transfers. >> >> 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/arm/Makefile.objs | 2 +- >> hw/arm/ftplat_a369soc.c | 14 ++ >> hw/ftdmac020.c | 599 +++++++++++++++++++++++++++++++++++++++++++++++ >> hw/ftdmac020.h | 107 +++++++++ >> 4 files changed, 721 insertions(+), 1 deletion(-) >> create mode 100644 hw/ftdmac020.c >> create mode 100644 hw/ftdmac020.h >> >> diff --git a/hw/arm/Makefile.objs b/hw/arm/Makefile.objs >> index 6a41b21..6510c51 100644 >> --- a/hw/arm/Makefile.objs >> +++ b/hw/arm/Makefile.objs >> @@ -25,7 +25,7 @@ obj-y += strongarm.o >> obj-y += imx_serial.o imx_ccm.o imx_timer.o imx_avic.o >> obj-$(CONFIG_KVM) += kvm/arm_gic.o >> obj-y += ftintc020.o ftahbc020.o ftddrii030.o ftpwmtmr010.o ftwdt010.o \ >> - ftrtc011.o >> + ftrtc011.o ftdmac020.o >> >> obj-y := $(addprefix ../,$(obj-y)) >> >> diff --git a/hw/arm/ftplat_a369soc.c b/hw/arm/ftplat_a369soc.c >> index bd696c4..59e2c61 100644 >> --- a/hw/arm/ftplat_a369soc.c >> +++ b/hw/arm/ftplat_a369soc.c >> @@ -168,6 +168,20 @@ static void a369soc_chip_init(FaradaySoCState *s) >> sysbus_connect_irq(SYS_BUS_DEVICE(ds), 3, s->pic[44]); >> /* Hour (Edge) */ >> sysbus_connect_irq(SYS_BUS_DEVICE(ds), 4, s->pic[45]); >> + >> + /* ftdmac020 */ >> + s->hdma[0] = sysbus_create_varargs("ftdmac020", >> + 0x90300000, >> + s->pic[0], /* ALL (NC in A369) */ >> + s->pic[15], /* TC */ >> + s->pic[16], /* ERR */ >> + NULL); >> + s->hdma[1] = sysbus_create_varargs("ftdmac020", >> + 0x96100000, >> + s->pic[0], /* ALL (NC in A369) */ >> + s->pic[17], /* TC */ >> + s->pic[18], /* ERR */ >> + NULL); >> } >> >> static void a369soc_realize(DeviceState *dev, Error **errp) >> diff --git a/hw/ftdmac020.c b/hw/ftdmac020.c >> new file mode 100644 >> index 0000000..81b49b2 >> --- /dev/null >> +++ b/hw/ftdmac020.c >> @@ -0,0 +1,599 @@ >> +/* >> + * 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 descending address mode is not implemented. >> + */ >> + >> +#include "hw/sysbus.h" >> +#include "sysemu/dma.h" >> +#include "sysemu/sysemu.h" >> +#include "sysemu/blockdev.h" >> + >> +#include "hw/ftdmac020.h" >> + >> +#define TYPE_FTDMAC020 "ftdmac020" >> + >> +enum ftdmac020_irqpin { >> + IRQ_ALL = 0, >> + IRQ_TC, >> + IRQ_ERR, >> +}; >> + >> +typedef struct Ftdmac020State Ftdmac020State; >> + >> +/** >> + * struct Ftdmac020LLD - 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 Ftdmac020LLD { >> + uint32_t src; >> + uint32_t dst; >> + uint32_t next; >> + uint32_t ctrl; >> + uint32_t size; >> +} Ftdmac020LLD; >> + >> +typedef struct Ftdmac020Chan { >> + Ftdmac020State *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; >> +} Ftdmac020Chan; >> + >> +typedef struct Ftdmac020State { >> + /*< private >*/ >> + SysBusDevice parent; >> + >> + /*< public >*/ >> + MemoryRegion iomem; >> + qemu_irq irq[3]; >> + >> + Ftdmac020Chan chan[8]; >> + qemu_irq ack[16]; >> + uint32_t req; >> + >> + int busy; /* Busy Channel ID */ >> + int bh_owner; >> + QEMUBH *bh; >> + DMAContext *dma; >> + >> + /* 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; >> +} Ftdmac020State; >> + >> +#define FTDMAC020(obj) \ >> + OBJECT_CHECK(Ftdmac020State, obj, TYPE_FTDMAC020) >> + >> +static void ftdmac020_update_irq(Ftdmac020State *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(Ftdmac020Chan *c) >> +{ >> + uint32_t tmp; >> + >> + /* 1. decode burst size */ >> + tmp = extract32(c->ccr, 16, 3); >> + c->burst = 1 << (tmp ? tmp + 1 : 0); >> + >> + /* 2. decode source width */ >> + tmp = extract32(c->ccr, 11, 2); >> + c->src_bw = 8 << tmp; >> + >> + /* 3. decode destination width */ >> + tmp = extract32(c->ccr, 8, 2); >> + c->dst_bw = 8 << tmp; >> + >> + /* 4. decode source address stride */ >> + tmp = extract32(c->ccr, 5, 2); >> + if (tmp == 2) { >> + c->src_stride = 0; >> + } else { >> + c->src_stride = c->src_bw >> 3; >> + } >> + >> + /* 5. decode destination address stride */ >> + tmp = extract32(c->ccr, 3, 2); >> + if (tmp == 2) { >> + c->dst_stride = 0; >> + } else { >> + c->dst_stride = c->dst_bw >> 3; >> + } >> +} >> + >> +static void ftdmac020_chan_start(Ftdmac020Chan *c) >> +{ >> + Ftdmac020State *s = c->chip; >> + Ftdmac020LLD desc; >> + hwaddr src, dst; >> + uint8_t buf[4096] __attribute__ ((aligned (8))); >> + int i, len, stride, src_hs, dst_hs; >> + >> + if (!(c->ccr & CCR_START)) { >> + return; >> + } >> + >> + s->busy = c->id; >> + >> + /* DMA src/dst address */ >> + src = c->src; >> + dst = c->dst; >> + >> + /* DMA hardware handshake id */ >> + src_hs = (c->cfg & CFG_SRC_HANDSHAKE_EN) ? extract32(c->cfg, 3, 4) : -1; >> + dst_hs = (c->cfg & CFG_DST_HANDSHAKE_EN) ? extract32(c->cfg, 9, 4) : -1; >> + >> + /* DMA src/dst sanity check */ >> + if (cpu_physical_memory_is_io(src) && c->src_stride) { >> + fprintf(stderr, >> + "ftdmac020: src is an I/O device with non-fixed address?\n"); >> + abort(); >> + } > > This doesn't look like a QEMU fatal to me. Its seems like software > usage policy of the DMA. Does the real hardware actually do any > preventative action on striding io accesses? If not then I would > either remove this assertion or at least demote it to a > LOG_GUEST_ERROR. There may be corner case applications where this is > valid. I think its also worthwhile trying to avoid use of > cpu_physical_foo() functions from device land. > It's a programming error, which is used to identify possible bug in driver. And I'll remove this sanity check in next release. ,>> + if (cpu_physical_memory_is_io(dst) && c->dst_stride) { >> + fprintf(stderr, >> + "ftdmac020: dst is an I/O device with non-fixed address?\n"); >> + abort(); >> + } >> + >> + while (c->len > 0) { >> + /* >> + * Postpone this DMA action >> + * if the corresponding dma request is not asserted >> + */ >> + if ((src_hs >= 0) && !(s->req & BIT(src_hs))) { >> + break; >> + } >> + if ((dst_hs >= 0) && !(s->req & BIT(dst_hs))) { >> + break; >> + } >> + >> + len = MIN(sizeof(buf), c->burst * (c->src_bw >> 3)); >> + >> + /* load data from source into local buffer */ >> + if (c->src_stride) { >> + dma_memory_read(s->dma, src, buf, len); >> + src += len; >> + } else { >> + stride = c->src_bw >> 3; >> + for (i = 0; i < len; i += stride) { >> + dma_memory_read(s->dma, src, buf + i, stride); >> + } >> + } >> + >> + /* DMA Hardware Handshake */ >> + if (src_hs >= 0) { >> + qemu_set_irq(s->ack[src_hs], 1); >> + } >> + >> + /* store data into destination from local buffer */ >> + if (c->dst_stride) { >> + dma_memory_write(s->dma, dst, buf, len); >> + dst += len; >> + } else { >> + stride = c->dst_bw >> 3; >> + for (i = 0; i < len; i += stride) { >> + dma_memory_write(s->dma, dst, buf + i, 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) { >> + /* update the channel transfer status */ >> + if (!(c->ccr & CCR_MASK_TC)) { >> + s->tcsr |= BIT(c->id); >> + if (!(c->cfg & CFG_MASK_TCI)) { >> + s->tcisr |= BIT(c->id); >> + } >> + ftdmac020_update_irq(s); >> + } >> + /* try to load next lld */ >> + if (c->llp) { >> + c->llp_cnt += 1; >> + dma_memory_read(s->dma, 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) >> + | (extract32(desc.ctrl, 29, 3) << 24) >> + | ((desc.ctrl & BIT(28)) ? CCR_MASK_TC : 0) >> + | (extract32(desc.ctrl, 25, 3) << 11) >> + | (extract32(desc.ctrl, 22, 3) << 8) >> + | (extract32(desc.ctrl, 20, 2) << 5) >> + | (extract32(desc.ctrl, 18, 2) << 3) >> + | (extract32(desc.ctrl, 16, 2) << 1); >> + ftdmac020_chan_ccr_decode(c); >> + >> + src = c->src; >> + dst = c->dst; >> + } else { >> + /* clear dma start bit */ >> + c->ccr &= ~CCR_START; >> + } >> + } >> + } >> + >> + /* update dma src/dst address */ >> + c->src = src; >> + c->dst = dst; >> + >> + s->busy = -1; >> +} >> + >> +static void ftdmac020_chan_reset(Ftdmac020Chan *c) >> +{ >> + c->ccr = 0; >> + c->cfg = 0; >> + c->src = 0; >> + c->dst = 0; >> + c->llp = 0; >> + c->len = 0; >> +} >> + >> +static void ftdmac020_bh(void *opaque) >> +{ >> + Ftdmac020State *s = FTDMAC020(opaque); >> + Ftdmac020Chan *c = NULL; >> + int i, jobs, done; >> + >> + ++s->bh_owner; >> + 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; >> + } >> + } >> + } >> + --s->bh_owner; >> + >> + /* >> + * Devices those with an infinite FIFO (always ready for R/W) > > Grammar (- the "those"?). > Awkward, now you know my English really sucks. :P >> + * would trigger a new DMA handshake transaction here. >> + * (i.e. ftnandc021, ftsdc010) >> + */ >> + if ((jobs - done) && s->req) { >> + qemu_bh_schedule(s->bh); >> + } >> +} >> + >> +static void ftdmac020_handle_req(void *opaque, int line, int level) >> +{ >> + Ftdmac020State *s = FTDMAC020(opaque); >> + >> + if (level) { >> + /* >> + * Devices those wait for data from externaI/O > > s/those/that > >> + * would trigger a new DMA handshake transaction here. >> + * (i.e. ftssp010) > > A nitpick, but do you mean e.g. instead of i.e.? > Both, because FTSSP010 is the only one would trigger DMA in that way. > Regards, > Peter > >> + */ >> + if (!(s->req & BIT(line))) { >> + /* a simple workaround for BH reentry issue */ >> + if (!s->bh_owner) { >> + qemu_bh_schedule(s->bh); >> + } >> + } >> + s->req |= BIT(line); >> + } else { >> + s->req &= ~BIT(line); >> + qemu_set_irq(s->ack[line], 0); >> + } >> +} >> + >> +static void ftdmac020_chip_reset(Ftdmac020State *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) >> +{ >> + Ftdmac020State *s = FTDMAC020(opaque); >> + Ftdmac020Chan *c = NULL; >> + uint32_t i, ret = 0; >> + >> + 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: >> + for (i = 0; i < 8; ++i) { >> + c = s->chan + i; >> + ret |= (c->ccr & CCR_START) ? BIT(i) : 0; >> + } >> + break; >> + case REG_CBSR: >> + return (s->busy > 0) ? BIT(s->busy) : 0; >> + case REG_CSR: >> + return s->csr; >> + case REG_SYNC: >> + return s->sync; >> + case REG_REVISION: >> + /* rev. = 1.13.0 */ >> + return 0x00011300; >> + case REG_FEATURE: >> + /* fifo = 32 bytes, support linked list, 8 channels, AHB0 only */ >> + return 0x00008105; >> + case REG_CHAN_BASE(0) ... REG_CHAN_BASE(7) + 0x14: >> + 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; >> + default: >> + qemu_log_mask(LOG_GUEST_ERROR, >> + "ftdmac020: undefined memory access@%#" HWADDR_PRIx "\n", >> + addr); >> + break; >> + } >> + break; >> + default: >> + qemu_log_mask(LOG_GUEST_ERROR, >> + "ftdmac020: undefined memory access@%#" HWADDR_PRIx "\n", addr); >> + break; >> + } >> + >> + return ret; >> +} >> + >> +static void ftdmac020_mem_write(void *opaque, >> + hwaddr addr, >> + uint64_t val, >> + unsigned size) >> +{ >> + Ftdmac020State *s = FTDMAC020(opaque); >> + Ftdmac020Chan *c = NULL; >> + >> + 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; >> + case REG_CHAN_BASE(0) ... REG_CHAN_BASE(7) + 0x14: >> + 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_bh_schedule(s->bh); >> + } >> + 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: >> + qemu_log_mask(LOG_GUEST_ERROR, >> + "ftdmac020: undefined memory access@%#" HWADDR_PRIx "\n", >> + addr); >> + break; >> + } >> + break; >> + default: >> + qemu_log_mask(LOG_GUEST_ERROR, >> + "ftdmac020: undefined memory access@%#" HWADDR_PRIx "\n", addr); >> + break; >> + } >> +} >> + >> +static const MemoryRegionOps mmio_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) >> +{ >> + Ftdmac020State *s = FTDMAC020(SYS_BUS_DEVICE(ds)); >> + >> + ftdmac020_chip_reset(s); >> +} >> + >> +static void ftdmac020_realize(DeviceState *dev, Error **errp) >> +{ >> + Ftdmac020State *s = FTDMAC020(dev); >> + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); >> + int i; >> + >> + memory_region_init_io(&s->iomem, >> + &mmio_ops, >> + s, >> + TYPE_FTDMAC020, >> + 0x1000); >> + sysbus_init_mmio(sbd, &s->iomem); >> + for (i = 0; i < 3; ++i) { >> + sysbus_init_irq(sbd, &s->irq[i]); >> + } >> + qdev_init_gpio_in(&sbd->qdev, ftdmac020_handle_req, 16); >> + qdev_init_gpio_out(&sbd->qdev, s->ack, 16); >> + >> + s->busy = -1; >> + s->dma = &dma_context_memory; >> + s->bh = qemu_bh_new(ftdmac020_bh, s); >> + for (i = 0; i < 8; ++i) { >> + Ftdmac020Chan *c = s->chan + i; >> + c->id = i; >> + c->chip = s; >> + } >> +} >> + >> +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, Ftdmac020State), >> + VMSTATE_UINT32(eaisr, Ftdmac020State), >> + VMSTATE_UINT32(tcsr, Ftdmac020State), >> + VMSTATE_UINT32(easr, Ftdmac020State), >> + VMSTATE_UINT32(cesr, Ftdmac020State), >> + VMSTATE_UINT32(cbsr, Ftdmac020State), >> + VMSTATE_UINT32(csr, Ftdmac020State), >> + VMSTATE_UINT32(sync, Ftdmac020State), >> + VMSTATE_END_OF_LIST() >> + } >> +}; >> + >> +static void ftdmac020_class_init(ObjectClass *klass, void *data) >> +{ >> + DeviceClass *dc = DEVICE_CLASS(klass); >> + >> + dc->vmsd = &vmstate_ftdmac020; >> + dc->reset = ftdmac020_reset; >> + dc->realize = ftdmac020_realize; >> + dc->no_user = 1; >> +} >> + >> +static const TypeInfo ftdmac020_info = { >> + .name = TYPE_FTDMAC020, >> + .parent = TYPE_SYS_BUS_DEVICE, >> + .instance_size = sizeof(Ftdmac020State), >> + .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..86ee58c >> --- /dev/null >> +++ b/hw/ftdmac020.h >> @@ -0,0 +1,107 @@ >> +/* >> + * 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. >> + */ >> + >> +#ifndef HW_ARM_FTDMAC020_H >> +#define HW_ARM_FTDMAC020_H >> + >> +#include "qemu/bitops.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) & BIT(10)) >> +#define FEATURE_DUALBUS(f) ((f) & BIT(9)) >> +#define FEATURE_LLP(f) ((f) & BIT(8)) >> +#define FEATURE_FIFOAW(f) ((f) & 0xF) >> + >> +/* >> + * Channel control register >> + */ >> +#define CCR_START BIT(0) >> +#define CCR_DST_M1 BIT(1) /* dst is a slave device of AHB1 */ >> +#define CCR_SRC_M1 BIT(2) /* src is a slave device of AHB1 */ >> +#define CCR_DST_INC (0 << 3)/* dst addr: next = curr++ */ >> +#define CCR_DST_DEC (1 << 3)/* dst addr: next = curr-- */ >> +#define CCR_DST_FIXED (2 << 3) >> +#define CCR_SRC_INC (0 << 5)/* src addr: next = curr++ */ >> +#define CCR_SRC_DEC (1 << 5)/* src addr: next = curr-- */ >> +#define CCR_SRC_FIXED (2 << 5) >> +#define CCR_HANDSHAKE BIT(7) /* DMA HW handshake enabled */ >> +#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 BIT(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 BIT(19) >> +#define CCR_BUFFERABLE BIT(20) >> +#define CCR_CACHEABLE BIT(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 BIT(31) /* DMA Transfer terminated(finished) */ >> + >> +/* >> + * Channel configuration register >> + */ >> +#define CFG_MASK_TCI BIT(0) /* disable tc interrupt */ >> +#define CFG_MASK_EI BIT(1) /* disable error interrupt */ >> +#define CFG_MASK_AI BIT(2) /* disable abort interrupt */ >> +#define CFG_SRC_HANDSHAKE(x) (((x) & 0xf) << 3) >> +#define CFG_SRC_HANDSHAKE_EN BIT(7) >> +#define CFG_BUSY BIT(8) >> +#define CFG_DST_HANDSHAKE(x) (((x) & 0xf) << 9) >> +#define CFG_DST_HANDSHAKE_EN BIT(13) >> +#define CFG_LLP_CNT(cfg) (((cfg) >> 16) & 0xf) >> + >> +#endif /* HW_ARM_FTDMAC020_H */ >> -- >> 1.7.9.5 >> >> -- Best wishes, Kuo-Jung Su
diff --git a/hw/arm/Makefile.objs b/hw/arm/Makefile.objs index 6a41b21..6510c51 100644 --- a/hw/arm/Makefile.objs +++ b/hw/arm/Makefile.objs @@ -25,7 +25,7 @@ obj-y += strongarm.o obj-y += imx_serial.o imx_ccm.o imx_timer.o imx_avic.o obj-$(CONFIG_KVM) += kvm/arm_gic.o obj-y += ftintc020.o ftahbc020.o ftddrii030.o ftpwmtmr010.o ftwdt010.o \ - ftrtc011.o + ftrtc011.o ftdmac020.o obj-y := $(addprefix ../,$(obj-y)) diff --git a/hw/arm/ftplat_a369soc.c b/hw/arm/ftplat_a369soc.c index bd696c4..59e2c61 100644 --- a/hw/arm/ftplat_a369soc.c +++ b/hw/arm/ftplat_a369soc.c @@ -168,6 +168,20 @@ static void a369soc_chip_init(FaradaySoCState *s) sysbus_connect_irq(SYS_BUS_DEVICE(ds), 3, s->pic[44]); /* Hour (Edge) */ sysbus_connect_irq(SYS_BUS_DEVICE(ds), 4, s->pic[45]); + + /* ftdmac020 */ + s->hdma[0] = sysbus_create_varargs("ftdmac020", + 0x90300000, + s->pic[0], /* ALL (NC in A369) */ + s->pic[15], /* TC */ + s->pic[16], /* ERR */ + NULL); + s->hdma[1] = sysbus_create_varargs("ftdmac020", + 0x96100000, + s->pic[0], /* ALL (NC in A369) */ + s->pic[17], /* TC */ + s->pic[18], /* ERR */ + NULL); } static void a369soc_realize(DeviceState *dev, Error **errp) diff --git a/hw/ftdmac020.c b/hw/ftdmac020.c new file mode 100644 index 0000000..81b49b2 --- /dev/null +++ b/hw/ftdmac020.c @@ -0,0 +1,599 @@ +/* + * 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 descending address mode is not implemented. + */ + +#include "hw/sysbus.h" +#include "sysemu/dma.h" +#include "sysemu/sysemu.h" +#include "sysemu/blockdev.h" + +#include "hw/ftdmac020.h" + +#define TYPE_FTDMAC020 "ftdmac020" + +enum ftdmac020_irqpin { + IRQ_ALL = 0, + IRQ_TC, + IRQ_ERR, +}; + +typedef struct Ftdmac020State Ftdmac020State; + +/** + * struct Ftdmac020LLD - 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 Ftdmac020LLD { + uint32_t src; + uint32_t dst; + uint32_t next; + uint32_t ctrl; + uint32_t size; +} Ftdmac020LLD; + +typedef struct Ftdmac020Chan { + Ftdmac020State *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; +} Ftdmac020Chan; + +typedef struct Ftdmac020State { + /*< private >*/ + SysBusDevice parent; + + /*< public >*/ + MemoryRegion iomem; + qemu_irq irq[3]; + + Ftdmac020Chan chan[8]; + qemu_irq ack[16]; + uint32_t req; + + int busy; /* Busy Channel ID */ + int bh_owner; + QEMUBH *bh; + DMAContext *dma; + + /* 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; +} Ftdmac020State; + +#define FTDMAC020(obj) \ + OBJECT_CHECK(Ftdmac020State, obj, TYPE_FTDMAC020) + +static void ftdmac020_update_irq(Ftdmac020State *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(Ftdmac020Chan *c) +{ + uint32_t tmp; + + /* 1. decode burst size */ + tmp = extract32(c->ccr, 16, 3); + c->burst = 1 << (tmp ? tmp + 1 : 0); + + /* 2. decode source width */ + tmp = extract32(c->ccr, 11, 2); + c->src_bw = 8 << tmp; + + /* 3. decode destination width */ + tmp = extract32(c->ccr, 8, 2); + c->dst_bw = 8 << tmp; + + /* 4. decode source address stride */ + tmp = extract32(c->ccr, 5, 2); + if (tmp == 2) { + c->src_stride = 0; + } else { + c->src_stride = c->src_bw >> 3; + } + + /* 5. decode destination address stride */ + tmp = extract32(c->ccr, 3, 2); + if (tmp == 2) { + c->dst_stride = 0; + } else { + c->dst_stride = c->dst_bw >> 3; + } +} + +static void ftdmac020_chan_start(Ftdmac020Chan *c) +{ + Ftdmac020State *s = c->chip; + Ftdmac020LLD desc; + hwaddr src, dst; + uint8_t buf[4096] __attribute__ ((aligned (8))); + int i, len, stride, src_hs, dst_hs; + + if (!(c->ccr & CCR_START)) { + return; + } + + s->busy = c->id; + + /* DMA src/dst address */ + src = c->src; + dst = c->dst; + + /* DMA hardware handshake id */ + src_hs = (c->cfg & CFG_SRC_HANDSHAKE_EN) ? extract32(c->cfg, 3, 4) : -1; + dst_hs = (c->cfg & CFG_DST_HANDSHAKE_EN) ? extract32(c->cfg, 9, 4) : -1; + + /* DMA src/dst sanity check */ + if (cpu_physical_memory_is_io(src) && c->src_stride) { + fprintf(stderr, + "ftdmac020: src is an I/O device with non-fixed address?\n"); + abort(); + } + if (cpu_physical_memory_is_io(dst) && c->dst_stride) { + fprintf(stderr, + "ftdmac020: dst is an I/O device with non-fixed address?\n"); + abort(); + } + + while (c->len > 0) { + /* + * Postpone this DMA action + * if the corresponding dma request is not asserted + */ + if ((src_hs >= 0) && !(s->req & BIT(src_hs))) { + break; + } + if ((dst_hs >= 0) && !(s->req & BIT(dst_hs))) { + break; + } + + len = MIN(sizeof(buf), c->burst * (c->src_bw >> 3)); + + /* load data from source into local buffer */ + if (c->src_stride) { + dma_memory_read(s->dma, src, buf, len); + src += len; + } else { + stride = c->src_bw >> 3; + for (i = 0; i < len; i += stride) { + dma_memory_read(s->dma, src, buf + i, stride); + } + } + + /* DMA Hardware Handshake */ + if (src_hs >= 0) { + qemu_set_irq(s->ack[src_hs], 1); + } + + /* store data into destination from local buffer */ + if (c->dst_stride) { + dma_memory_write(s->dma, dst, buf, len); + dst += len; + } else { + stride = c->dst_bw >> 3; + for (i = 0; i < len; i += stride) { + dma_memory_write(s->dma, dst, buf + i, 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) { + /* update the channel transfer status */ + if (!(c->ccr & CCR_MASK_TC)) { + s->tcsr |= BIT(c->id); + if (!(c->cfg & CFG_MASK_TCI)) { + s->tcisr |= BIT(c->id); + } + ftdmac020_update_irq(s); + } + /* try to load next lld */ + if (c->llp) { + c->llp_cnt += 1; + dma_memory_read(s->dma, 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) + | (extract32(desc.ctrl, 29, 3) << 24) + | ((desc.ctrl & BIT(28)) ? CCR_MASK_TC : 0) + | (extract32(desc.ctrl, 25, 3) << 11) + | (extract32(desc.ctrl, 22, 3) << 8) + | (extract32(desc.ctrl, 20, 2) << 5) + | (extract32(desc.ctrl, 18, 2) << 3) + | (extract32(desc.ctrl, 16, 2) << 1); + ftdmac020_chan_ccr_decode(c); + + src = c->src; + dst = c->dst; + } else { + /* clear dma start bit */ + c->ccr &= ~CCR_START; + } + } + } + + /* update dma src/dst address */ + c->src = src; + c->dst = dst; + + s->busy = -1; +} + +static void ftdmac020_chan_reset(Ftdmac020Chan *c) +{ + c->ccr = 0; + c->cfg = 0; + c->src = 0; + c->dst = 0; + c->llp = 0; + c->len = 0; +} + +static void ftdmac020_bh(void *opaque) +{ + Ftdmac020State *s = FTDMAC020(opaque); + Ftdmac020Chan *c = NULL; + int i, jobs, done; + + ++s->bh_owner; + 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; + } + } + } + --s->bh_owner; + + /* + * Devices those with an infinite FIFO (always ready for R/W) + * would trigger a new DMA handshake transaction here. + * (i.e. ftnandc021, ftsdc010) + */ + if ((jobs - done) && s->req) { + qemu_bh_schedule(s->bh); + } +} + +static void ftdmac020_handle_req(void *opaque, int line, int level) +{ + Ftdmac020State *s = FTDMAC020(opaque); + + if (level) { + /* + * Devices those wait for data from externaI/O + * would trigger a new DMA handshake transaction here. + * (i.e. ftssp010) + */ + if (!(s->req & BIT(line))) { + /* a simple workaround for BH reentry issue */ + if (!s->bh_owner) { + qemu_bh_schedule(s->bh); + } + } + s->req |= BIT(line); + } else { + s->req &= ~BIT(line); + qemu_set_irq(s->ack[line], 0); + } +} + +static void ftdmac020_chip_reset(Ftdmac020State *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) +{ + Ftdmac020State *s = FTDMAC020(opaque); + Ftdmac020Chan *c = NULL; + uint32_t i, ret = 0; + + 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: + for (i = 0; i < 8; ++i) { + c = s->chan + i; + ret |= (c->ccr & CCR_START) ? BIT(i) : 0; + } + break; + case REG_CBSR: + return (s->busy > 0) ? BIT(s->busy) : 0; + case REG_CSR: + return s->csr; + case REG_SYNC: + return s->sync; + case REG_REVISION: + /* rev. = 1.13.0 */ + return 0x00011300; + case REG_FEATURE: + /* fifo = 32 bytes, support linked list, 8 channels, AHB0 only */ + return 0x00008105; + case REG_CHAN_BASE(0) ... REG_CHAN_BASE(7) + 0x14: + 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; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "ftdmac020: undefined memory access@%#" HWADDR_PRIx "\n", + addr); + break; + } + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "ftdmac020: undefined memory access@%#" HWADDR_PRIx "\n", addr); + break; + } + + return ret; +} + +static void ftdmac020_mem_write(void *opaque, + hwaddr addr, + uint64_t val, + unsigned size) +{ + Ftdmac020State *s = FTDMAC020(opaque); + Ftdmac020Chan *c = NULL; + + 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; + case REG_CHAN_BASE(0) ... REG_CHAN_BASE(7) + 0x14: + 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_bh_schedule(s->bh); + } + 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: + qemu_log_mask(LOG_GUEST_ERROR, + "ftdmac020: undefined memory access@%#" HWADDR_PRIx "\n", + addr); + break; + } + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "ftdmac020: undefined memory access@%#" HWADDR_PRIx "\n", addr); + break; + } +} + +static const MemoryRegionOps mmio_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) +{ + Ftdmac020State *s = FTDMAC020(SYS_BUS_DEVICE(ds)); + + ftdmac020_chip_reset(s); +} + +static void ftdmac020_realize(DeviceState *dev, Error **errp) +{ + Ftdmac020State *s = FTDMAC020(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + int i; + + memory_region_init_io(&s->iomem, + &mmio_ops, + s, + TYPE_FTDMAC020, + 0x1000); + sysbus_init_mmio(sbd, &s->iomem); + for (i = 0; i < 3; ++i) { + sysbus_init_irq(sbd, &s->irq[i]); + } + qdev_init_gpio_in(&sbd->qdev, ftdmac020_handle_req, 16); + qdev_init_gpio_out(&sbd->qdev, s->ack, 16); + + s->busy = -1; + s->dma = &dma_context_memory; + s->bh = qemu_bh_new(ftdmac020_bh, s); + for (i = 0; i < 8; ++i) { + Ftdmac020Chan *c = s->chan + i; + c->id = i; + c->chip = s; + } +} + +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, Ftdmac020State), + VMSTATE_UINT32(eaisr, Ftdmac020State), + VMSTATE_UINT32(tcsr, Ftdmac020State), + VMSTATE_UINT32(easr, Ftdmac020State), + VMSTATE_UINT32(cesr, Ftdmac020State), + VMSTATE_UINT32(cbsr, Ftdmac020State), + VMSTATE_UINT32(csr, Ftdmac020State), + VMSTATE_UINT32(sync, Ftdmac020State), + VMSTATE_END_OF_LIST() + } +}; + +static void ftdmac020_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->vmsd = &vmstate_ftdmac020; + dc->reset = ftdmac020_reset; + dc->realize = ftdmac020_realize; + dc->no_user = 1; +} + +static const TypeInfo ftdmac020_info = { + .name = TYPE_FTDMAC020, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(Ftdmac020State), + .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..86ee58c --- /dev/null +++ b/hw/ftdmac020.h @@ -0,0 +1,107 @@ +/* + * 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. + */ + +#ifndef HW_ARM_FTDMAC020_H +#define HW_ARM_FTDMAC020_H + +#include "qemu/bitops.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) & BIT(10)) +#define FEATURE_DUALBUS(f) ((f) & BIT(9)) +#define FEATURE_LLP(f) ((f) & BIT(8)) +#define FEATURE_FIFOAW(f) ((f) & 0xF) + +/* + * Channel control register + */ +#define CCR_START BIT(0) +#define CCR_DST_M1 BIT(1) /* dst is a slave device of AHB1 */ +#define CCR_SRC_M1 BIT(2) /* src is a slave device of AHB1 */ +#define CCR_DST_INC (0 << 3)/* dst addr: next = curr++ */ +#define CCR_DST_DEC (1 << 3)/* dst addr: next = curr-- */ +#define CCR_DST_FIXED (2 << 3) +#define CCR_SRC_INC (0 << 5)/* src addr: next = curr++ */ +#define CCR_SRC_DEC (1 << 5)/* src addr: next = curr-- */ +#define CCR_SRC_FIXED (2 << 5) +#define CCR_HANDSHAKE BIT(7) /* DMA HW handshake enabled */ +#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 BIT(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 BIT(19) +#define CCR_BUFFERABLE BIT(20) +#define CCR_CACHEABLE BIT(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 BIT(31) /* DMA Transfer terminated(finished) */ + +/* + * Channel configuration register + */ +#define CFG_MASK_TCI BIT(0) /* disable tc interrupt */ +#define CFG_MASK_EI BIT(1) /* disable error interrupt */ +#define CFG_MASK_AI BIT(2) /* disable abort interrupt */ +#define CFG_SRC_HANDSHAKE(x) (((x) & 0xf) << 3) +#define CFG_SRC_HANDSHAKE_EN BIT(7) +#define CFG_BUSY BIT(8) +#define CFG_DST_HANDSHAKE(x) (((x) & 0xf) << 9) +#define CFG_DST_HANDSHAKE_EN BIT(13) +#define CFG_LLP_CNT(cfg) (((cfg) >> 16) & 0xf) + +#endif /* HW_ARM_FTDMAC020_H */