From patchwork Tue Apr 3 05:50:05 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: 150324 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 DECDDB6FC3 for ; Tue, 3 Apr 2012 15:50:48 +1000 (EST) Received: from localhost ([::1]:41979 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1SEwdh-00017e-MY for incoming@patchwork.ozlabs.org; Tue, 03 Apr 2012 01:50:45 -0400 Received: from eggs.gnu.org ([208.118.235.92]:54366) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1SEwdN-0000hK-6D for qemu-devel@nongnu.org; Tue, 03 Apr 2012 01:50:30 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1SEwdI-0003F3-V8 for qemu-devel@nongnu.org; Tue, 03 Apr 2012 01:50:24 -0400 Received: from mail-iy0-f173.google.com ([209.85.210.173]:60495) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1SEwdI-0002xO-Pt for qemu-devel@nongnu.org; Tue, 03 Apr 2012 01:50:20 -0400 Received: by mail-iy0-f173.google.com with SMTP id j26so6296886iaf.4 for ; Mon, 02 Apr 2012 22:50:19 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20120113; h=from:to:cc:subject:date:message-id:x-mailer:in-reply-to:references :in-reply-to:references:x-gm-message-state; bh=ZTa0UB4Qd5MyU+1BeiVXdpzRRqdFXWZcYJyyAzFQCX0=; b=WW5uRR45ExaFhxSa6YdS7O7uNwEzOc4rwH/uPivz9gMn3bX0+qhpYiTZooIevmBVy/ OxY3ovMsHZsXnBxSg2u33BVxKfcpaMg0SgNW+8HtOEgFiaLFsmeZXcaSzcEJpf0CaLNS MQglEhj/vZERib6Gmtqj9kxhcHpRjdyk5DNbrqs3paRW9f3OOitabJlrnglNdgTKPVXT DVpSWm8hv7iwh2OJ0/O5C1Upqz8fTc4qLLkIPHmNxXBov9qcvtTGYR2n7hBOT44TT2GH VW73CQoxeRwySKnWSvol6IMNV0ITMmAJuTl68u2gcO9oHo680hu7DL2nMaJTo2f7j1zQ iIcw== Received: by 10.43.134.200 with SMTP id id8mr7256929icc.32.1333432219809; Mon, 02 Apr 2012 22:50:19 -0700 (PDT) Received: from localhost ([124.148.20.9]) by mx.google.com with ESMTPS id vr4sm28623275igb.1.2012.04.02.22.50.15 (version=TLSv1/SSLv3 cipher=OTHER); Mon, 02 Apr 2012 22:50:18 -0700 (PDT) From: "Peter A. G. Crosthwaite" To: qemu-devel@nongnu.org, paul@codesourcery.com, edgar.iglesias@gmail.com, peter.maydell@linaro.org Date: Tue, 3 Apr 2012 15:50:05 +1000 Message-Id: X-Mailer: git-send-email 1.7.3.2 In-Reply-To: References: In-Reply-To: References: X-Gm-Message-State: ALoCoQmKsuGdFYZn9YB6eY8/9L1JVDgEnW9qO2vQ5IwyrSjWC5YzPE1JF1HprU2BW1bRrpKH227d 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] [PATCH v2 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 --- changed from v1: converted spi api to modified txrx style Makefile.target | 1 + hw/xilinx_spi.c | 482 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 483 insertions(+), 0 deletions(-) create mode 100644 hw/xilinx_spi.c diff --git a/Makefile.target b/Makefile.target index 6c568b4..be28bfe 100644 --- a/Makefile.target +++ b/Makefile.target @@ -321,6 +321,7 @@ obj-microblaze-y = petalogix_s3adsp1800_mmu.o obj-microblaze-y += petalogix_ml605_mmu.o obj-microblaze-y += microblaze_boot.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..5e40015 --- /dev/null +++ b/hw/xilinx_spi.c @@ -0,0 +1,482 @@ +/* + * 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; + + SPIBus *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; + DB_PRINT("irq_change_of of state %d\n", 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 : SPIBus_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 uint8_t txfifo_get(struct XilinxSPI *s) +{ + uint8_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) +{ + uint8_t tx; + uint8_t txz = 0x00; + uint8_t rx; + uint8_t rxz = 00; + + while (!txfifo_empty(s)) { + tx = txfifo_get(s); + DB_PRINT("data transfer:%x\n", tx); + if (spi_txrx(s->spi, &tx, &txz, &rx, &rxz, 8)) { + hw_error("xilinx spi txrx with no device selected"); + } + if (!rxz) { + rxfifo_put(s, rx); + } else if (!~rxz) { + hw_error("xilinx spi does not support partially tristated bytes\n"); + } + } +} + +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)