From patchwork Fri Jan 18 06:28:10 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Dante X-Patchwork-Id: 213482 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id 604FF2C007A for ; Fri, 18 Jan 2013 17:29:30 +1100 (EST) Received: from localhost ([::1]:52249 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Tw5SC-0001NS-Bj for incoming@patchwork.ozlabs.org; Fri, 18 Jan 2013 01:29:28 -0500 Received: from eggs.gnu.org ([208.118.235.92]:38447) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Tw5RV-0008TI-Sp for qemu-devel@nongnu.org; Fri, 18 Jan 2013 01:28:49 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1Tw5RR-0004YB-2H for qemu-devel@nongnu.org; Fri, 18 Jan 2013 01:28:45 -0500 Received: from ftcsun4.faraday-tech.com ([60.248.181.187]:56143 helo=ftcsun4.faraday.com.tw) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Tw5RP-0004Xa-S6 for qemu-devel@nongnu.org; Fri, 18 Jan 2013 01:28:40 -0500 Received: from ftcpcs100.faraday.com.tw ([192.168.31.10]) by ftcsun4.faraday.com.tw (8.11.7p1+Sun/8.11.6) with ESMTP id r0I6Sam07154 for ; Fri, 18 Jan 2013 14:28:36 +0800 (CST) Received: from FTCPCS63.faraday.com.tw (unverified [192.168.32.63]) by ftcpcs100.faraday.com.tw (Clearswift SMTPRS 5.2.9) with ESMTP id ; Fri, 18 Jan 2013 14:28:37 +0800 Received: from vmware.faraday.com.tw (192.168.65.96) by FTCPCS63.faraday.com.tw (192.168.32.65) with Microsoft SMTP Server (TLS) id 14.2.328.9; Fri, 18 Jan 2013 14:28:37 +0800 From: Dante To: Date: Fri, 18 Jan 2013 14:28:10 +0800 Message-ID: <1358490506-18281-2-git-send-email-dantesu@faraday-tech.com> X-Mailer: git-send-email 1.7.9.5 In-Reply-To: <1358490506-18281-1-git-send-email-dantesu@faraday-tech.com> References: <1358490506-18281-1-git-send-email-dantesu@faraday-tech.com> MIME-Version: 1.0 X-Originating-IP: [192.168.65.96] X-KSE-Antivirus-Interceptor-Info: scan successful X-KSE-Antivirus-Info: Clean X-detected-operating-system: by eggs.gnu.org: Genre and OS details not recognized. X-Received-From: 60.248.181.187 Cc: peter.maydell@linaro.org, paul@codesourcery.com, dantesu@faraday-tech.com Subject: [Qemu-devel] [PATCH 02/18] hw: add QEMU model for Faraday APB DMA X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Signed-off-by: Kuo-Jung Su --- 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 + * + * 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 + * + * 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 */