From patchwork Fri Mar 30 06:37:12 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Peter A. G. Crosthwaite" X-Patchwork-Id: 149577 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 384C3B6F98 for ; Fri, 30 Mar 2012 18:07:20 +1100 (EST) Received: from localhost ([::1]:56122 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1SDVTL-0000qi-MX for incoming@patchwork.ozlabs.org; Fri, 30 Mar 2012 02:38:07 -0400 Received: from eggs.gnu.org ([208.118.235.92]:34658) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1SDVT0-0000bL-Rr for qemu-devel@nongnu.org; Fri, 30 Mar 2012 02:37:49 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1SDVSx-0004Bk-Vl for qemu-devel@nongnu.org; Fri, 30 Mar 2012 02:37:46 -0400 Received: from mail-iy0-f173.google.com ([209.85.210.173]:58765) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1SDVSx-0003yv-R8 for qemu-devel@nongnu.org; Fri, 30 Mar 2012 02:37:43 -0400 Received: by mail-iy0-f173.google.com with SMTP id j26so695412iaf.4 for ; Thu, 29 Mar 2012 23:37:42 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20120113; h=mime-version:from:to:cc:subject:date:message-id:x-mailer :in-reply-to:references:in-reply-to:references:x-gm-message-state; bh=mzaIhNsnbYe4MaFzUQ2p8PqF3UdO7yEkWs7gaFFTGCc=; b=YdshH0oH7+k9QjqRwC8ldb48AXzCgqY2ZsXG0A9jRxCLtwnOFrO+d+n8Ulv7rDfMGg pQjOs/K+URgmO3JyQPfcC2DKHu4vzy6dAhE3isqsGJO9P1s5M77l6vUSzLGOVpSpy/fh iy5kmBBlhGQBu7W1TAwlyW24LUuOwD+hSo3EJyMOr/K3xyjBO7Z1bW1AluTR+U/1BaPp 427DIIlctzX6MMLlKfJCLLHKQHm3f1vQT6HuCgnlVu1HFeFsH82h3ybO7DbIWOqJvU18 fpm0p1oMnnNCTKCRGbz0C5n1CnE4INbrVfzCEifoWZHBVzgIC8y3Eo/OdEhEjK0EZSBR hW5A== MIME-Version: 1.0 Received: by 10.42.203.148 with SMTP id fi20mr544331icb.10.1333089462883; Thu, 29 Mar 2012 23:37:42 -0700 (PDT) Received: from localhost ([124.148.20.9]) by mx.google.com with ESMTPS id zv10sm1019103igb.13.2012.03.29.23.37.39 (version=TLSv1/SSLv3 cipher=OTHER); Thu, 29 Mar 2012 23:37:42 -0700 (PDT) From: "Peter A. G. Crosthwaite" To: qemu-devel@nongnu.org, paul@codesourcery.com, edgar.iglesias@gmail.com Date: Fri, 30 Mar 2012 16:37:12 +1000 Message-Id: <5d023aa0f88154e3b7bb754965fefb60a51cad9e.1333088411.git.peter.crosthwaite@petalogix.com> X-Mailer: git-send-email 1.7.3.2 In-Reply-To: References: In-Reply-To: References: X-Gm-Message-State: ALoCoQkd63PcGhctZEHj/gfqGudmjVMCbRUtWJUj0aif4GIUYDLbbhetZLH0xRD/HM6RETyKVx+4 X-detected-operating-system: by eggs.gnu.org: Genre and OS details not recognized. X-Received-From: 209.85.210.173 Cc: peter.crosthwaite@petalogix.com, john.williams@petalogix.com Subject: [Qemu-devel] [RFC PATCH v1 3/4] xilinx_spi: initial version 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 device model for xilinx XPS SPI controller (v2.0) Signed-off-by: Peter A. G. Crosthwaite --- Makefile.target | 1 + hw/xilinx_spi.c | 477 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 478 insertions(+), 0 deletions(-) create mode 100644 hw/xilinx_spi.c diff --git a/Makefile.target b/Makefile.target index fcccf1b..15edfa4 100644 --- a/Makefile.target +++ b/Makefile.target @@ -322,6 +322,7 @@ obj-microblaze-y += petalogix_ml605_mmu.o obj-microblaze-y += microblaze_boot.o obj-microblaze-y += spi.o obj-microblaze-y += m25p80.o +obj-microblaze-y += xilinx_spi.o obj-microblaze-y += microblaze_pic_cpu.o obj-microblaze-y += xilinx_intc.o diff --git a/hw/xilinx_spi.c b/hw/xilinx_spi.c new file mode 100644 index 0000000..a5c8aa8 --- /dev/null +++ b/hw/xilinx_spi.c @@ -0,0 +1,477 @@ +/* + * QEMU model of the Xilinx SPI Controller + * + * Copyright (C) 2010 Edgar E. Iglesias. + * Copyright (C) 2012 Peter A. G. Crosthwaite + * Copyright (C) 2012 PetaLogix + * + * 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.h" +#include "ptimer.h" +#include "qemu-log.h" + +#include "spi.h" + +#ifdef XILINX_SPI_ERR_DEBUG +#define DB_PRINT(...) do { \ + fprintf(stderr, ": %s: ", __func__); \ + fprintf(stderr, ## __VA_ARGS__); \ + } while (0); +#else + #define DB_PRINT(...) +#endif + +#define R_DGIER (0x1c / 4) +#define R_DGIER_IE (1 << 31) + +#define R_IPISR (0x20 / 4) +#define IRQ_DRR_NOT_EMPTY (1 << (31 - 23)) +#define IRQ_DRR_OVERRUN (1 << (31 - 26)) +#define IRQ_DRR_FULL (1 << (31 - 27)) +#define IRQ_TX_FF_HALF_EMPTY (1 << 6) +#define IRQ_DTR_UNDERRUN (1 << 3) +#define IRQ_DTR_EMPTY (1 << (31 - 29)) + +#define R_IPIER (0x28 / 4) +#define R_SRR (0x40 / 4) +#define R_SPICR (0x60 / 4) +#define R_SPICR_TXFF_RST (1 << 5) +#define R_SPICR_RXFF_RST (1 << 6) +#define R_SPICR_MTI (1 << 8) + +#define R_SPISR (0x64 / 4) +#define SR_TX_FULL (1 << 3) +#define SR_TX_EMPTY (1 << 2) +#define SR_RX_FULL (1 << 1) +#define SR_RX_EMPTY (1 << 0) + + +#define R_SPIDTR (0x68 / 4) +#define R_SPIDRR (0x6C / 4) +#define R_SPISSR (0x70 / 4) +#define R_TX_FF_OCY (0x74 / 4) +#define R_RX_FF_OCY (0x78 / 4) +#define R_MAX (0x7C / 4) + +struct XilinxSPI { + SysBusDevice busdev; + MemoryRegion mmio; + qemu_irq irq; + int irqline; + + QEMUBH *bh; + ptimer_state *ptimer; + + spi_bus *spi; + + uint32_t c_fifo_exist; + + uint8_t rx_fifo[256]; + unsigned int rx_fifo_pos; + unsigned int rx_fifo_len; + + uint8_t tx_fifo[256]; + unsigned int tx_fifo_pos; + unsigned int tx_fifo_len; + + /* Slave select. */ + uint8_t num_cs; + int cmd_ongoing; + + uint32_t regs[R_MAX]; +}; + +static void txfifo_reset(struct XilinxSPI *s) +{ + s->tx_fifo_pos = 0; + s->tx_fifo_len = 0; + + s->regs[R_SPISR] &= ~SR_TX_FULL; + s->regs[R_SPISR] |= SR_TX_EMPTY; + s->regs[R_SPISR] &= ~SR_TX_FULL; + s->regs[R_IPISR] |= IRQ_DTR_EMPTY; +} + +static void rxfifo_reset(struct XilinxSPI *s) +{ + s->rx_fifo_pos = 0; + s->rx_fifo_len = 0; + + s->regs[R_SPISR] |= SR_RX_EMPTY; + s->regs[R_SPISR] &= ~SR_RX_FULL; + s->regs[R_IPISR] &= ~IRQ_DRR_NOT_EMPTY; + s->regs[R_IPISR] &= ~IRQ_DRR_OVERRUN; +} + +static void xlx_spi_reset(struct XilinxSPI *s) +{ + memset(s->regs, 0, sizeof s->regs); + + rxfifo_reset(s); + txfifo_reset(s); + + s->regs[R_SPISSR] = 1; + spi_set_cs(s->spi, 0); +} + +static void xlx_spi_update_irq(struct XilinxSPI *s) +{ + uint32_t pending; + pending = s->regs[R_IPISR] & s->regs[R_IPIER]; + + pending = pending && (s->regs[R_DGIER] & R_DGIER_IE); + pending = !!pending; + + /* This call lies right in the data paths so dont call the + irq chain unless things really changed. */ + if (pending != s->irqline) { + s->irqline = pending; + qemu_set_irq(s->irq, pending); + } +} + +static inline int spi_master_enabled(struct XilinxSPI *s) +{ + return !(s->regs[R_SPICR] & R_SPICR_MTI); +} + +static int spi_slave_select(struct XilinxSPI *s, uint32_t v) +{ + unsigned int ss; + + ss = ffs(v) - 1; + return ss < s->num_cs ? ss : SPI_BUS_NO_CS; +} + +static inline int txfifo_empty(struct XilinxSPI *s) +{ + return s->tx_fifo_len == 0; +} + +static inline int txfifo_full(struct XilinxSPI *s) +{ + return s->tx_fifo_len >= ARRAY_SIZE(s->tx_fifo); +} + +static inline int rxfifo_empty(struct XilinxSPI *s) +{ + return s->rx_fifo_len == 0; +} + +static inline int rxfifo_full(struct XilinxSPI *s) +{ + return s->rx_fifo_len >= ARRAY_SIZE(s->rx_fifo); +} + +static inline void txfifo_put(struct XilinxSPI *s, uint8_t v) +{ + s->regs[R_SPISR] &= ~SR_TX_EMPTY; + s->regs[R_IPISR] &= ~IRQ_DTR_EMPTY; + + s->tx_fifo[s->tx_fifo_pos] = v; + s->tx_fifo_pos++; + s->tx_fifo_pos &= ARRAY_SIZE(s->tx_fifo) - 1; + s->tx_fifo_len++; + + s->regs[R_SPISR] &= ~SR_TX_FULL; + if (txfifo_full(s)) { + s->regs[R_SPISR] |= SR_TX_FULL; + } +} + +static inline uint32_t txfifo_get(struct XilinxSPI *s) +{ + uint32_t r = 0; + assert(s->tx_fifo_len); + + r = s->tx_fifo[(s->tx_fifo_pos - s->tx_fifo_len) & + (ARRAY_SIZE(s->tx_fifo) - 1)]; + s->tx_fifo_len--; + + s->regs[R_SPISR] &= ~SR_TX_FULL; + if (txfifo_empty(s)) { + s->regs[R_SPISR] |= SR_TX_EMPTY; + s->regs[R_IPISR] |= IRQ_DTR_EMPTY; + } + + return r; +} + +static inline void rxfifo_put(struct XilinxSPI *s, uint8_t v) +{ + DB_PRINT("%x\n", v); + s->regs[R_SPISR] &= ~SR_RX_EMPTY; + s->regs[R_IPISR] |= IRQ_DRR_NOT_EMPTY; + + s->rx_fifo[s->rx_fifo_pos] = v; + s->rx_fifo_pos++; + s->rx_fifo_pos &= ARRAY_SIZE(s->rx_fifo) - 1; + s->rx_fifo_len++; + + s->regs[R_SPISR] &= ~SR_RX_FULL; + if (s->rx_fifo_len >= ARRAY_SIZE(s->rx_fifo)) { + s->regs[R_SPISR] |= SR_RX_FULL; + s->regs[R_IPISR] |= IRQ_DRR_OVERRUN; + } +} + +static inline uint32_t rxfifo_get(struct XilinxSPI *s) +{ + uint32_t r = 0; + assert(s->rx_fifo_len); + + r = s->rx_fifo[(s->rx_fifo_pos - s->rx_fifo_len) & + (ARRAY_SIZE(s->rx_fifo) - 1)]; + s->rx_fifo_len--; + + s->regs[R_SPISR] &= ~SR_RX_FULL; + if (rxfifo_empty(s)) { + s->regs[R_SPISR] |= SR_RX_EMPTY; + s->regs[R_IPISR] &= ~IRQ_DRR_NOT_EMPTY; + } + + return r; +} + +static void spi_timer_run(struct XilinxSPI *s, int delay) +{ + ptimer_set_count(s->ptimer, delay); + ptimer_run(s->ptimer, 1); +} + +static void +spi_flush_txfifo(struct XilinxSPI *s) +{ + uint32_t value; + SpiSlaveState st; + + while (!txfifo_empty(s)) { + value = txfifo_get(s); + DB_PRINT("data transfer:%x\n", value); + st = spi_send(s->spi, value, 8); + while (st == SPI_DATA_PENDING) { + uint32_t d; + st = spi_recv(s->spi, &d); + rxfifo_put(s, d); + } + } +} + +static uint64_t +spi_read(void *opaque, target_phys_addr_t addr, unsigned int size) +{ + struct XilinxSPI *s = opaque; + uint32_t r = 0; + + addr >>= 2; + switch (addr) { + case R_SPIDRR: + if (rxfifo_empty(s)) { + DB_PRINT("Read from empty FIFO!\n"); + return 0xdeadbeef; + } + + r = rxfifo_get(s); + break; + + case R_SPISR: + r = s->regs[addr]; + if (rxfifo_empty(s)) { + spi_timer_run(s, 1); + } + break; + + default: + if (addr < ARRAY_SIZE(s->regs)) { + r = s->regs[addr]; + } + break; + + } + DB_PRINT("addr=" TARGET_FMT_plx " = %x\n", addr * 4, r); + xlx_spi_update_irq(s); + return r; +} + +static void +spi_write(void *opaque, target_phys_addr_t addr, + uint64_t val64, unsigned int size) +{ + struct XilinxSPI *s = opaque; + uint32_t value = val64; + + DB_PRINT("addr=" TARGET_FMT_plx " = %x\n", addr, value); + addr >>= 2; + switch (addr) { + case R_SRR: + if (value != 0xa) { + DB_PRINT("Invalid write to SRR %x\n", value); + } else { + xlx_spi_reset(s); + } + break; + + case R_SPIDTR: + txfifo_put(s, value); + + if (!spi_master_enabled(s)) { + goto done; + } else { + DB_PRINT("DTR and master enabled?\n"); + } + spi_flush_txfifo(s); + break; + + case R_SPISR: + DB_PRINT("Invalid write to SPISR %x\n", value); + break; + + case R_IPISR: + /* Toggle the bits. */ + s->regs[addr] ^= value; + break; + + /* Slave Select Register. */ + case R_SPISSR: + spi_set_cs(s->spi, spi_slave_select(s, ~value)); + s->regs[addr] = value; + break; + + case R_SPICR: + /* FIXME: reset irq and sr state to empty queues. */ + if (value & R_SPICR_RXFF_RST) { + rxfifo_reset(s); + } + + if (value & R_SPICR_TXFF_RST) { + txfifo_reset(s); + } + value &= ~(R_SPICR_RXFF_RST | R_SPICR_TXFF_RST); + s->regs[addr] = value; + + if (!(value & R_SPICR_MTI)) { + /* + * The linux driver, when issuing READS to a flash memory, + * first sets up the tx part, starts the transmition and + * waits for the tx part to end. After that it sets up + * the rx part, assumeing the CPU is faster than the flash. + * + * If we simply flush the txfifo and consume all rx data + * from the flash, we'll hit a race in the linux driver and + * some rx data will be arrive to the linux driver too early. + * + * That's why we implement this hack. If we are at the start + * of a new cmd (i.e s->cmd_ongoing == 0), then we delay + * the processing a bit, to give linux time to set things + * up. + * + * FIXME: The mentione hack doesn't work. Investigate why. + */ + if (0 && s->cmd_ongoing) { + spi_flush_txfifo(s); + } else { + /* When releasing the master disable, initiate a timer + that eventually will flush the txfifo. */ + spi_timer_run(s, 1); + } + } + break; + + default: + if (addr < ARRAY_SIZE(s->regs)) { + s->regs[addr] = value; + } + break; + } + +done: + xlx_spi_update_irq(s); +} + +static const MemoryRegionOps spi_ops = { + .read = spi_read, + .write = spi_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4 + } +}; + +static void timer_hit(void *opaque) +{ + struct XilinxSPI *s = opaque; + + if (!txfifo_empty(s)) { + spi_flush_txfifo(s); + s->cmd_ongoing = 1; + } + xlx_spi_update_irq(s); +} + +static int xilinx_spi_init(SysBusDevice *dev) +{ + struct XilinxSPI *s = FROM_SYSBUS(typeof(*s), dev); + + DB_PRINT("\n"); + sysbus_init_irq(dev, &s->irq); + + memory_region_init_io(&s->mmio, &spi_ops, s, "xilinx-spi", R_MAX * 4); + sysbus_init_mmio(dev, &s->mmio); + + s->bh = qemu_bh_new(timer_hit, s); + s->ptimer = ptimer_init(s->bh); + ptimer_set_freq(s->ptimer, 10 * 1000 * 1000); + + s->spi = spi_init_bus(&dev->qdev, s->num_cs, "spi"); + + xlx_spi_reset(s); + return 0; +} + +static Property xilinx_spi_properties[] = { + DEFINE_PROP_UINT8("num-cs", struct XilinxSPI, num_cs, 1), + DEFINE_PROP_END_OF_LIST(), +}; + +static void xilinx_spi_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = xilinx_spi_init; + dc->props = xilinx_spi_properties; +} + +static TypeInfo xilinx_spi_info = { + .name = "xilinx,spi", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(struct XilinxSPI), + .class_init = xilinx_spi_class_init, +}; + +static void xilinx_spi_register_types(void) +{ + type_register_static(&xilinx_spi_info); +} + +type_init(xilinx_spi_register_types)