Hi Kuo-Jung, On Wed, Feb 6, 2013 at 7:45 PM, Kuo-Jung Su <dant...@gmail.com> wrote: > From: Kuo-Jung Su <dant...@faraday-tech.com> > > The FTSPI020 is an integrated SPI Flash controller > which supports upto 4 flash chips. > > Signed-off-by: Kuo-Jung Su <dant...@faraday-tech.com> > --- > hw/arm/Makefile.objs | 1 + > hw/arm/faraday_a369.c | 12 ++ > hw/arm/ftspi020.c | 345 > +++++++++++++++++++++++++++++++++++++++++++++++++ > hw/arm/ftspi020.h | 50 +++++++ > 4 files changed, 408 insertions(+) > create mode 100644 hw/arm/ftspi020.c > create mode 100644 hw/arm/ftspi020.h > > diff --git a/hw/arm/Makefile.objs b/hw/arm/Makefile.objs > index 508335a..7178b5d 100644 > --- a/hw/arm/Makefile.objs > +++ b/hw/arm/Makefile.objs > @@ -52,3 +52,4 @@ obj-y += ftgmac100.o > obj-y += ftlcdc200.o > obj-y += fttsc010.o > obj-y += ftsdc010.o > +obj-y += ftspi020.o > diff --git a/hw/arm/faraday_a369.c b/hw/arm/faraday_a369.c > index 4ad48f5..132ee2f 100644 > --- a/hw/arm/faraday_a369.c > +++ b/hw/arm/faraday_a369.c > @@ -203,6 +203,18 @@ a369_device_init(A369State *s) > req = qdev_get_gpio_in(s->hdma[0], 13); > qdev_connect_gpio_out(s->hdma[0], 13, ack); > qdev_connect_gpio_out(ds, 0, req); > + > + /* ftspi020: as an external AHB device */ > + ds = sysbus_create_simple("ftspi020", 0xC0000000, pic[13]); > + spi = (SSIBus *)qdev_get_child_bus(ds, "spi"); > + nr_flash = 1; > + for (i = 0; i < nr_flash; i++) { > + fl = ssi_create_slave_no_init(spi, "m25p80"); > + qdev_prop_set_string(fl, "partname", "w25q64"); > + qdev_init_nofail(fl); > + cs_line = qdev_get_gpio_in(fl, 0); > + sysbus_connect_irq(SYS_BUS_DEVICE(ds), i + 1, cs_line); > + } > } > > static void > diff --git a/hw/arm/ftspi020.c b/hw/arm/ftspi020.c > new file mode 100644 > index 0000000..dab2f6f > --- /dev/null > +++ b/hw/arm/ftspi020.c > @@ -0,0 +1,345 @@ > +/* > + * Faraday FTSPI020 Flash Controller > + * > + * Copyright (c) 2012 Faraday Technology > + * Written by Dante Su <dant...@faraday-tech.com> > + * > + * This code is licensed under GNU GPL v2+. > + */ > + > +#include <hw/hw.h> > +#include <sysemu/sysemu.h> > +#include <hw/sysbus.h> > +#include <hw/ssi.h> > + > +#include "ftspi020.h" > + > +#define TYPE_FTSPI020 "ftspi020" > + > +typedef struct Ftspi020State { > + SysBusDevice busdev; > + MemoryRegion iomem; > + qemu_irq irq; > + > + /* DMA hardware handshake */ > + qemu_irq req; > + > + SSIBus *spi; > + uint8_t num_cs;
Constant. No need for it to be part of the device state, unless you want to drive it from a property and let the machine model decide how my cs lines you have? > + qemu_irq *cs_lines; > + > + int wip; /* SPI Flash Status: Write In Progress BIT shift */ > + uint32_t datacnt; > + > + /* HW register caches */ > + uint32_t cmd[4]; > + uint32_t ctrl; > + uint32_t timing; > + uint32_t icr; > + uint32_t isr; > + uint32_t rdsr; > +} Ftspi020State; > + > +#define FTSPI020(obj) \ > + OBJECT_CHECK(Ftspi020State, obj, TYPE_FTSPI020) > + > +static void ftspi020_update_irq(Ftspi020State *s) > +{ > + if (s->isr) { > + qemu_set_irq(s->irq, 1); > + } else { > + qemu_set_irq(s->irq, 0); > + } > +} > + > +static void ftspi020_handle_ack(void *opaque, int line, int level) > +{ > + Ftspi020State *s = FTSPI020(opaque); > + > + if (!(s->icr & 0x01)) { > + return; > + } > + > + if (level) { > + qemu_set_irq(s->req, 0); > + } else if (s->datacnt > 0) { > + qemu_set_irq(s->req, 1); > + } > +} > + > +static int ftspi020_do_command(Ftspi020State *s) > +{ > + int cs = (s->cmd[3] >> 8) & 0x03; > + int cmd = (s->cmd[3] >> 24) & 0xff; > + int ilen = (s->cmd[1] >> 24) & 0x03; > + int alen = (s->cmd[1] >> 0) & 0x07; > + int dcyc = (s->cmd[1] >> 16) & 0xff; > + bitops.h has helpers that can extract fields for you without all the >> & stuff. Lots of magic numbers here as well, can you #define these offsets with meaningful names? > + /* make sure the spi flash is de-activated */ > + qemu_set_irq(s->cs_lines[cs], 1); > + > + /* activate the spi flash */ > + qemu_set_irq(s->cs_lines[cs], 0); > + > + /* if it's a SPI flash READ_STATUS command */ > + if ((s->cmd[3] & 0x06) == 0x04) { > + do { > + ssi_transfer(s->spi, cmd); > + s->rdsr = ssi_transfer(s->spi, 0x00); > + if (s->cmd[3] & 0x08) { > + break; > + } > + } while (s->rdsr & (1 << s->wip)); > + } else { > + /* otherwise */ > + int i; > + > + ilen = MIN(ilen, 2); > + alen = MIN(alen, 4); > + > + /* command cycles */ > + for (i = 0; i < ilen; ++i) { > + ssi_transfer(s->spi, cmd); > + } > + /* address cycles */ > + for (i = alen - 1; i >= 0; --i) { > + ssi_transfer(s->spi, (s->cmd[0] >> (8 * i)) & 0xff); > + } > + /* dummy cycles */ > + for (i = 0; i < (dcyc >> 3); ++i) { > + ssi_transfer(s->spi, 0x00); > + } > + } > + > + if (s->datacnt <= 0) { == as this is unsigned. > + qemu_set_irq(s->cs_lines[cs], 1); > + } else if (s->icr & 0x01) { > + qemu_set_irq(s->req, 1); > + } > + > + if (s->cmd[3] & 0x01) { > + s->isr |= 0x01; > + } > + ftspi020_update_irq(s); > + > + return 0; > +} > + > +static void ftspi020_chip_reset(Ftspi020State *s) > +{ > + int i; > + > + s->datacnt = 0; > + for (i = 0; i < 4; ++i) { > + s->cmd[i] = 0; > + } > + s->wip = 0; > + s->ctrl = 0; > + s->timing = 0; > + s->icr = 0; > + s->isr = 0; > + s->rdsr = 0; > + > + qemu_set_irq(s->irq, 0); Do the cs lines need resetting here? > +} > + > +static uint64_t ftspi020_mem_read(void *opaque, hwaddr addr, unsigned size) > +{ > + Ftspi020State *s = FTSPI020(opaque); > + uint64_t ret = 0; > + > + switch (addr) { > + case REG_DATA: > + if (!(s->cmd[3] & 0x02)) { > + if (s->datacnt > 0) { > + ret |= (uint32_t)(ssi_transfer(s->spi, 0x00) & 0xff) << 0; > + ret |= (uint32_t)(ssi_transfer(s->spi, 0x00) & 0xff) << 8; > + ret |= (uint32_t)(ssi_transfer(s->spi, 0x00) & 0xff) << 16; > + ret |= (uint32_t)(ssi_transfer(s->spi, 0x00) & 0xff) << 24; loopable: for (i = 0; i < 4; ++i) { ret = deposit32(ret, i * 8, 8, (uint32_t)(ssi_transfer(s->spi, 0x00) & 0xff)); } > + s->datacnt = (s->datacnt < 4) ? 0 : (s->datacnt - 4); > + } > + if (s->datacnt == 0) { > + uint8_t cs = (s->cmd[3] >> 8) & 0x03; > + qemu_set_irq(s->cs_lines[cs], 1); > + if (s->cmd[3] & 0x01) { > + s->isr |= 0x01; > + } > + ftspi020_update_irq(s); > + } > + } > + break; > + case REG_RDST: > + return s->rdsr; > + case REG_CMD0: the case .. syntax would allow you to collapse these 4 into 1: case REG_CMD0 .. REG_CMD3: return s->cmd[(addr - REG_CMD0) / 4] > + return s->cmd[0]; > + case REG_CMD1: > + return s->cmd[1]; > + case REG_CMD2: > + return s->cmd[2]; > + case REG_CMD3: > + return s->cmd[3]; > + case REG_STR: > + /* In QEMU, the data fifo is always ready for read/write */ > + return 0x00000003; > + case REG_ISR: > + return s->isr; > + case REG_ICR: > + return s->icr; > + case REG_CTRL: > + return s->ctrl; > + case REG_ACT: > + return s->timing; > + case REG_REV: > + return 0x00010001; > + case REG_FEA: > + return 0x02022020; Few magic numbers here > + default: > + break; > + } > + > + return ret; > +} > + > +static void ftspi020_mem_write(void *opaque, > + hwaddr addr, > + uint64_t val, > + unsigned size) > +{ > + Ftspi020State *s = FTSPI020(opaque); > + > + switch (addr) { > + case REG_DATA: > + if (s->cmd[3] & 0x02) { > + if (s->datacnt > 0) { > + ssi_transfer(s->spi, (uint8_t)((val >> 0) & 0xff)); > + ssi_transfer(s->spi, (uint8_t)((val >> 8) & 0xff)); > + ssi_transfer(s->spi, (uint8_t)((val >> 16) & 0xff)); > + ssi_transfer(s->spi, (uint8_t)((val >> 24) & 0xff)); > + s->datacnt = (s->datacnt < 4) ? 0 : (s->datacnt - 4); > + } > + if (s->datacnt == 0) { > + uint8_t cs = (s->cmd[3] >> 8) & 0x03; > + qemu_set_irq(s->cs_lines[cs], 1); > + if (s->cmd[3] & 0x01) { > + s->isr |= 0x01; > + } > + ftspi020_update_irq(s); > + } > + } > + break; > + case REG_CMD0: > + s->cmd[0] = (uint32_t)val; > + break; > + case REG_CMD1: > + s->cmd[1] = (uint32_t)val; > + break; > + case REG_CMD2: > + s->datacnt = s->cmd[2] = (uint32_t)val; > + break; > + case REG_CMD3: > + s->cmd[3] = (uint32_t)val; > + ftspi020_do_command(s); > + break; > + case REG_ISR: > + s->isr &= ~((uint32_t)val); > + ftspi020_update_irq(s); > + break; > + case REG_ICR: > + s->icr = (uint32_t)val; > + break; > + case REG_CTRL: > + if (val & 0x100) { > + ftspi020_chip_reset(s); > + } > + s->ctrl = (uint32_t)val & 0x70013; > + s->wip = ((uint32_t)val >> 16) & 0x07; > + break; > + case REG_ACT: > + s->timing = (uint32_t)val; > + break; > + default: > + break; > + } > +} > + > +static const MemoryRegionOps ftspi020_ops = { > + .read = ftspi020_mem_read, > + .write = ftspi020_mem_write, > + .endianness = DEVICE_LITTLE_ENDIAN, > +}; > + > +static void ftspi020_reset(DeviceState *ds) > +{ > + SysBusDevice *busdev = SYS_BUS_DEVICE(ds); > + Ftspi020State *s = FTSPI020(FROM_SYSBUS(Ftspi020State, busdev)); > + > + ftspi020_chip_reset(s); > +} > + > +static int ftspi020_init(SysBusDevice *dev) > +{ > + Ftspi020State *s = FTSPI020(FROM_SYSBUS(Ftspi020State, dev)); > + int i; > + > + memory_region_init_io(&s->iomem, > + &ftspi020_ops, > + s, > + TYPE_FTSPI020, > + 0x1000); > + sysbus_init_mmio(dev, &s->iomem); > + sysbus_init_irq(dev, &s->irq); > + > + s->spi = ssi_create_bus(&dev->qdev, "spi"); > + s->num_cs = 4; > + s->cs_lines = g_new(qemu_irq, s->num_cs); > + ssi_auto_connect_slaves(DEVICE(s), s->cs_lines, s->spi); > + for (i = 0; i < s->num_cs; ++i) { > + sysbus_init_irq(dev, &s->cs_lines[i]); > + } > + > + qdev_init_gpio_in(&s->busdev.qdev, ftspi020_handle_ack, 1); > + qdev_init_gpio_out(&s->busdev.qdev, &s->req, 1); > + > + return 0; > +} > + > +static const VMStateDescription vmstate_ftspi020 = { > + .name = TYPE_FTSPI020, > + .version_id = 1, > + .minimum_version_id = 1, > + .minimum_version_id_old = 1, > + .fields = (VMStateField[]) { > + VMSTATE_UINT32_ARRAY(cmd, Ftspi020State, 4), > + VMSTATE_UINT32(ctrl, Ftspi020State), > + VMSTATE_UINT32(timing, Ftspi020State), > + VMSTATE_UINT32(icr, Ftspi020State), > + VMSTATE_UINT32(isr, Ftspi020State), > + VMSTATE_UINT32(rdsr, Ftspi020State), datacnt is missing. I think you need to save it as it is device state that would need to be preserved if you machine got migrated in the middle of a transaction. > + VMSTATE_END_OF_LIST(), > + } > +}; > + > +static void ftspi020_class_init(ObjectClass *klass, void *data) > +{ > + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); > + DeviceClass *dc = DEVICE_CLASS(klass); > + > + k->init = ftspi020_init; > + dc->vmsd = &vmstate_ftspi020; > + dc->reset = ftspi020_reset; > + dc->no_user = 1; > +} > + > +static const TypeInfo ftspi020_info = { > + .name = TYPE_FTSPI020, > + .parent = TYPE_SYS_BUS_DEVICE, > + .instance_size = sizeof(Ftspi020State), > + .class_init = ftspi020_class_init, > +}; > + > +static void ftspi020_register_types(void) > +{ > + type_register_static(&ftspi020_info); > +} > + > +type_init(ftspi020_register_types) > diff --git a/hw/arm/ftspi020.h b/hw/arm/ftspi020.h > new file mode 100644 > index 0000000..a8a0930 > --- /dev/null > +++ b/hw/arm/ftspi020.h > @@ -0,0 +1,50 @@ > +/* > + * Faraday FTSPI020 Flash Controller > + * > + * Copyright (c) 2012 Faraday Technology > + * Written by Dante Su <dant...@faraday-tech.com> > + * > + * This code is licensed under GNU GPL v2+. > + */ > + > +#ifndef HW_ARM_FTSPI020_H > +#define HW_ARM_FTSPI020_H > + This device is not exporting any extensible APIs there is no need for a header specific to this device. > +/****************************************************************************** > + * FTSPI020 registers > + > *****************************************************************************/ > +#define REG_CMD0 0x00 /* Flash address */ > +#define REG_CMD1 0x04 > +#define REG_CMD2 0x08 > +#define REG_CMD3 0x0c > +#define REG_CTRL 0x10 /* Control Register */ > +#define REG_ACT 0x14 /* AC Timing Register */ > +#define REG_STR 0x18 /* Status Register */ > +#define REG_ICR 0x20 /* Interrupt Control Register */ > +#define REG_ISR 0x24 /* Interrupt Status Register */ > +#define REG_RDST 0x28 /* Read Status Register */ > +#define REG_REV 0x50 /* Revision Register */ > +#define REG_FEA 0x54 /* Feature Register */ > +#define REG_DATA 0x100 /* Data Register */ > + These can just live in the C file - they are private to the implementation. > +/****************************************************************************** > + * Common SPI flash opcodes (Not FTSPI020 specific) > + > *****************************************************************************/ > +#define OPCODE_WREN 0x06 /* Write enable */ > +#define OPCODE_WRSR 0x01 /* Write status register 1 byte */ > +#define OPCODE_RDSR 0x05 /* Read status register */ > +#define OPCODE_NORM_READ 0x03 /* Read data bytes (low frequency) */ > +#define OPCODE_NORM_READ4 0x13 /* Read data bytes (4 bytes address) */ > +#define OPCODE_FAST_READ 0x0b /* Read data bytes (high frequency) */ > +#define OPCODE_FAST_READ4 0x0c /* Read data bytes (4 bytes address) */ > +#define OPCODE_PP 0x02 /* Page program (up to 256 bytes) */ > +#define OPCODE_PP4 0x12 /* Page program (4 bytes address) */ > +#define OPCODE_SE 0xd8 /* Sector erase (usually 64KiB) */ > +#define OPCODE_SE4 0xdc /* Sector erase (4 bytes address) */ > +#define OPCODE_RDID 0x9f /* Read JEDEC ID */ > + This is repetition of M25P80s command opcodes. If anything, we should factor these out into m25p80.h (doesnt exist yet) as this is the second device model (apart from m25p80 itself) to need these. The other one is xilinx_spips.c. However i notice almost all of these are unused by your device model. Apart from the little bit of logic around RDSR, is there any flash command-specific functionality in this device? AFAICT it just accepts command code, address, num dummies and payload size and the details of what those commands mean is abstracted away from this hardware, making this table mostly redundant. If you have future work that will need this that's another matter however. Regards, Peter > +/* Status Register bits. */ > +#define SR_WIP 1 /* Write in progress */ > +#define SR_WEL 2 /* Write enable latch */ > + > +#endif > -- > 1.7.9.5 > >