On 20 April 2012 03:12, Peter A. G. Crosthwaite <peter.crosthwa...@petalogix.com> wrote: > device model for xilinx XPS SPI controller (v2.0) > > Signed-off-by: Peter A. G. Crosthwaite <peter.crosthwa...@petalogix.com> > --- > changed from v2: > converted spi api to ssi api > changed from v1: > converted spi api to modified txrx style > > Makefile.target | 1 + > hw/xilinx_spi.c | 453 > +++++++++++++++++++++++++++++++++++++++++++++++++++++++ > 2 files changed, 454 insertions(+), 0 deletions(-) > create mode 100644 hw/xilinx_spi.c > > diff --git a/Makefile.target b/Makefile.target > index 3f7c38e..e163c64 100644 > --- a/Makefile.target > +++ b/Makefile.target > @@ -327,6 +327,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..82a6b32 > --- /dev/null > +++ b/hw/xilinx_spi.c > @@ -0,0 +1,453 @@ > +/* > + * QEMU model of the Xilinx SPI Controller > + * > + * Copyright (C) 2010 Edgar E. Iglesias. > + * Copyright (C) 2012 Peter A. G. Crosthwaite > <peter.crosthwa...@petalogix.com> > + * 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 "ssi.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; > + > + SSIBus *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]; > +};
Needs save/load support. > + > +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; Duplicate of the first line, some typo here?? > + 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; > + ssi_select_slave(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. */ "don't". > + 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 : SSI_SLAVE_SELECT_NONE; > +} > + > +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); > +} You should ptimer_stop() this timer in some reset function somewhere, otherwise it might erroneously fire if it happened to be running when we do a reset. > + > +static void > +spi_flush_txfifo(struct XilinxSPI *s) > +{ > + uint32_t tx; > + uint32_t rx; > + > + while (!txfifo_empty(s)) { > + tx = (uint32_t)txfifo_get(s); > + DB_PRINT("data transfer:%x\n", tx); > + rx = ssi_transfer(s->spi, (uint32_t)tx); > + rxfifo_put(s, rx); > + } > +} > + > +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: > + ssi_select_slave(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)) { > + /* 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 = ssi_create_bus(&dev->qdev, "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) -- PMM