diff mbox

[v4,21/23] hw/arm: add Faraday FTSPI020 SPI flash controller support

Message ID 1361870054-18564-22-git-send-email-dantesu@gmail.com
State New
Headers show

Commit Message

Kuo-Jung Su Feb. 26, 2013, 9:14 a.m. UTC
From: Kuo-Jung Su <dantesu@faraday-tech.com>

The FTSPI020 is an integrated SPI Flash controller
which supports upto 4 flash chips.

Signed-off-by: Kuo-Jung Su <dantesu@faraday-tech.com>
---
 hw/arm/Makefile.objs      |    1 +
 hw/arm/faraday_a369.c     |   13 ++
 hw/arm/faraday_a369_soc.c |    4 +
 hw/arm/ftspi020.c         |  333 +++++++++++++++++++++++++++++++++++++++++++++
 hw/arm/ftspi020.h         |   81 +++++++++++
 5 files changed, 432 insertions(+)
 create mode 100644 hw/arm/ftspi020.c
 create mode 100644 hw/arm/ftspi020.h

Comments

Peter Crosthwaite Feb. 26, 2013, 11:08 a.m. UTC | #1
Hi Kuo-Jung,

On Tue, Feb 26, 2013 at 7:14 PM, Kuo-Jung Su <dantesu@gmail.com> wrote:
> From: Kuo-Jung Su <dantesu@faraday-tech.com>
>
> The FTSPI020 is an integrated SPI Flash controller
> which supports upto 4 flash chips.
>
> Signed-off-by: Kuo-Jung Su <dantesu@faraday-tech.com>
> ---

Please provide change logs below the line as per the patch submission process.

>  hw/arm/Makefile.objs      |    1 +
>  hw/arm/faraday_a369.c     |   13 ++
>  hw/arm/faraday_a369_soc.c |    4 +
>  hw/arm/ftspi020.c         |  333 +++++++++++++++++++++++++++++++++++++++++++++
>  hw/arm/ftspi020.h         |   81 +++++++++++
>  5 files changed, 432 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 c25eba2..2ed1c7c 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 46fc570..ace0784 100644
> --- a/hw/arm/faraday_a369.c
> +++ b/hw/arm/faraday_a369.c
> @@ -67,6 +67,19 @@ a369_board_init(QEMUMachineInitArgs *args)
>          wm8731_data_req_set(s->codec, ftssp010_i2s_data_req, s->i2s[0]);
>      }
>
> +    /* Attach the spi flash to ftspi020.0 */
> +    nr_flash = 1;
> +    for (i = 0; i < nr_flash; i++) {
> +        SSIBus *ssi = (SSIBus *)qdev_get_child_bus(s->spi[1], "spi");
> +        DeviceState *fl = ssi_create_slave_no_init(ssi, "m25p80");
> +        qemu_irq cs_line;
> +
> +        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(s->spi[1]), i + 1, cs_line);
> +    }
> +
>      if (args->kernel_filename) {
>          s->bi = g_new0(struct arm_boot_info, 1);
>
> diff --git a/hw/arm/faraday_a369_soc.c b/hw/arm/faraday_a369_soc.c
> index eb9dd2f..8b07689 100644
> --- a/hw/arm/faraday_a369_soc.c
> +++ b/hw/arm/faraday_a369_soc.c
> @@ -272,6 +272,10 @@ a369soc_device_init(FaradaySoCState *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[4]);
> +    s->spi[1] = ds;
>  }
>
>  static int a369soc_init(SysBusDevice *busdev)
> diff --git a/hw/arm/ftspi020.c b/hw/arm/ftspi020.c
> new file mode 100644
> index 0000000..fb8a510
> --- /dev/null
> +++ b/hw/arm/ftspi020.c
> @@ -0,0 +1,333 @@
> +/*
> + * Faraday FTSPI020 Flash Controller
> + *
> + * Copyright (c) 2012 Faraday Technology
> + * Written by Dante Su <dantesu@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;
> +    qemu_irq *cs_lines;
> +
> +    int wip;    /* SPI Flash Status: Write In Progress BIT shift */
> +
> +    /* 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)
> +{
> +    qemu_set_irq(s->irq, s->isr ? 1 : 0);
> +}
> +
> +static void ftspi020_handle_ack(void *opaque, int line, int level)
> +{
> +    Ftspi020State *s = FTSPI020(opaque);
> +
> +    if (!(s->icr & ICR_DMA)) {
> +        return;
> +    }
> +
> +    if (level) {
> +        qemu_set_irq(s->req, 0);
> +    } else if (s->cmd[2]) {
> +        qemu_set_irq(s->req, 1);
> +    }
> +}
> +
> +static int ftspi020_do_command(Ftspi020State *s)
> +{
> +    uint32_t cs   = extract32(s->cmd[3],  8, 2);
> +    uint32_t cmd  = extract32(s->cmd[3], 24, 8);
> +    uint32_t ilen = extract32(s->cmd[1], 24, 2);
> +    uint32_t alen = extract32(s->cmd[1],  0, 3);
> +    uint32_t dcyc = extract32(s->cmd[1], 16, 8);
> +
> +    if (dcyc % 8) {
> +        hw_error("ftspi020: bad dummy clock (%u) to QEMU\n", dcyc);
> +        exit(1);
> +    }
> +
> +    /* 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] & (CMD3_RDSR | CMD3_WRITE)) == CMD3_RDSR) {
> +        uint32_t rdsr;
> +
> +        ssi_transfer(s->spi, cmd);
> +        do {
> +            rdsr = ssi_transfer(s->spi, 0x00);
> +            if (s->cmd[3] & CMD3_RDSR_SW) {
> +                break;
> +            }
> +        } while (rdsr & (1 << s->wip));
> +        s->rdsr = rdsr;
> +    } 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, extract32(s->cmd[0], i * 8, 8));
> +        }
> +        /* dummy cycles */
> +        for (i = 0; i < (dcyc >> 3); ++i) {
> +            ssi_transfer(s->spi, 0x00);
> +        }
> +    }
> +
> +    if (!s->cmd[2]) {
> +        qemu_set_irq(s->cs_lines[cs], 1);
> +    } else if (s->icr & ICR_DMA) {
> +        qemu_set_irq(s->req, 1);
> +    }
> +
> +    if (s->cmd[3] & CMD3_INTR) {
> +        s->isr |= ISR_CMDFIN;
> +    }
> +    ftspi020_update_irq(s);
> +
> +    return 0;
> +}
> +
> +static void ftspi020_chip_reset(Ftspi020State *s)
> +{
> +    int i;
> +
> +    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 NOT reset cs lines here, or the QEMU would crash */

More information please. This should work. What happened?

> +}
> +
> +static uint64_t
> +ftspi020_mem_read(void *opaque, hwaddr addr, unsigned size)
> +{
> +    Ftspi020State *s = FTSPI020(opaque);
> +    uint64_t ret = 0;
> +
> +    switch (addr) {
> +    case REG_CMD0 ... REG_CMD3:
> +        return s->cmd[(addr - REG_CMD0) / 4];
> +    case REG_CR:
> +        return s->ctrl;
> +    case REG_TR:
> +        return s->timing;
> +    case REG_SR:
> +        /* In QEMU, the data fifo is always ready for read/write */
> +        return SR_RX_READY | SR_TX_READY;
> +    case REG_ISR:
> +        return s->isr;
> +    case REG_ICR:
> +        return s->icr;
> +    case REG_RDSR:
> +        return s->rdsr;
> +    case REG_REVR:
> +        return 0x00010001;  /* rev. 1.0.1 */
> +    case REG_FEAR:
> +        return FEAR_CLKM_SYNC
> +            | FEAR_CMDQ(2) | FEAR_RXFIFO(32) | FEAR_TXFIFO(32);
> +    case REG_DR:
> +        if (!(s->cmd[3] & CMD3_WRITE)) {
> +            int i;
> +            uint32_t cs = extract32(s->cmd[3], 8, 2);
> +            for (i = 0; i < 4 && s->cmd[2]; i++, s->cmd[2]--) {
> +                ret = deposit32(ret, i * 8, 8,
> +                    ssi_transfer(s->spi, 0x00) & 0xff);
> +            }
> +            if (!s->cmd[2]) {
> +                qemu_set_irq(s->cs_lines[cs], 1);
> +                if (s->cmd[3] & CMD3_INTR) {
> +                    s->isr |= ISR_CMDFIN;
> +                }
> +                ftspi020_update_irq(s);
> +            }
> +        }
> +        break;
> +        /* we don't care */
> +    default:

You could qemu_log_mask(LOG_GUEST_ERROR on undefined memory accesses.

> +        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_CMD0 ... REG_CMD2:
> +        s->cmd[(addr - REG_CMD0) / 4] = (uint32_t)val;
> +        break;
> +    case REG_CMD3:
> +        s->cmd[3] = (uint32_t)val;
> +        ftspi020_do_command(s);
> +        break;
> +    case REG_CR:
> +        if (val & CR_ABORT) {
> +            ftspi020_chip_reset(s);
> +            val &= ~CR_ABORT;
> +        }
> +        s->ctrl = (uint32_t)val;
> +        s->wip  = extract32(val, 16, 3);
> +        break;
> +    case REG_TR:
> +        s->timing = (uint32_t)val;
> +        break;
> +    case REG_DR:
> +        if (s->cmd[3] & CMD3_WRITE) {
> +            int i;
> +            uint32_t cs = extract32(s->cmd[3], 8, 2);
> +            for (i = 0; i < 4 && s->cmd[2]; i++, s->cmd[2]--) {
> +                ssi_transfer(s->spi, extract32((uint32_t)val, i * 8, 8));
> +            }
> +            if (!s->cmd[2]) {
> +                qemu_set_irq(s->cs_lines[cs], 1);
> +                if (s->cmd[3] & CMD3_INTR) {
> +                    s->isr |= ISR_CMDFIN;
> +                }
> +                ftspi020_update_irq(s);
> +            }
> +        }
> +        break;
> +    case REG_ISR:
> +        s->isr &= ~((uint32_t)val);
> +        ftspi020_update_irq(s);
> +        break;
> +    case REG_ICR:
> +        s->icr = (uint32_t)val;
> +        break;
> +        /* we don't care */
> +    default:
> +        break;
> +    }
> +}
> +
> +static const MemoryRegionOps ftspi020_ops = {
> +    .read  = ftspi020_mem_read,
> +    .write = ftspi020_mem_write,

Your REG_FOO macros are jumping in fours, and the case statement in
your read/write handlers only handles those values, leading me to
believe your device only supports full word access. You should enforce
this by setting min/max_access size to 4 here.

Regards,
Peter

> +    .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->cs_lines = g_new(qemu_irq, CFG_NR_CSLINES);
> +    ssi_auto_connect_slaves(DEVICE(s), s->cs_lines, s->spi);
> +    for (i = 0; i < CFG_NR_CSLINES; ++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),
> +        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..47b5d2e
> --- /dev/null
> +++ b/hw/arm/ftspi020.h
> @@ -0,0 +1,81 @@
> +/*
> + * Faraday FTSPI020 Flash Controller
> + *
> + * Copyright (c) 2012 Faraday Technology
> + * Written by Dante Su <dantesu@faraday-tech.com>
> + *
> + * This code is licensed under GNU GPL v2+.
> + */
> +
> +#ifndef HW_ARM_FTSPI020_H
> +#define HW_ARM_FTSPI020_H
> +
> +#include "qemu/bitops.h"
> +
> +/* Number of CS lines */
> +#define CFG_NR_CSLINES      4
> +
> +/******************************************************************************
> + * FTSPI020 registers
> + *****************************************************************************/
> +#define REG_CMD0            0x00    /* Flash address */
> +#define REG_CMD1            0x04
> +#define REG_CMD2            0x08    /* Flash data counter */
> +#define REG_CMD3            0x0c
> +#define REG_CR              0x10    /* Control Register */
> +#define REG_TR              0x14    /* AC Timing Register */
> +#define REG_SR              0x18    /* Status Register */
> +#define REG_ICR             0x20    /* Interrupt Control Register */
> +#define REG_ISR             0x24    /* Interrupt Status Register */
> +#define REG_RDSR            0x28    /* Read Status Register */
> +#define REG_REVR            0x50    /* Revision Register */
> +#define REG_FEAR            0x54    /* Feature Register */
> +#define REG_DR              0x100   /* Data Register */
> +
> +#define CMD1_CTRD           BIT(28) /* Enable 1 byte continuous read */
> +#define CMD1_INST_LEN(x)    (((x) & 0x03) << 24)/* instruction length */
> +#define CMD1_DCLK_LEN(x)    (((x) & 0xff) << 16)/* dummy clock length */
> +#define CMD1_ADDR_LEN(x)    (((x) & 0x07) << 0) /* address length */
> +
> +#define CMD3_INST_OPC(x)    (((x) & 0xff) << 24)/* instruction op code */
> +#define CMD3_CTRD_OPC(x)    (((x) & 0xff) << 16)/* cont. read op code */
> +#define CMD3_CS(x)          (((x) & 0x0f) << 8) /* chip select */
> +#define CMD3_OPM_STD        (0)         /* standard 1-bit serial mode */
> +#define CMD3_OPM_DUAL       (1 << 5)    /* fast read dual */
> +#define CMD3_OPM_QUAD       (2 << 5)    /* fast read quad */
> +#define CMD3_OPM_DUALIO     (3 << 5)    /* fast read dual io */
> +#define CMD3_OPM_QUADIO     (4 << 5)    /* fast read quad io */
> +#define CMD3_DTR            BIT(4)  /* Enable double transfer rate */
> +#define CMD3_RDSR_HW        (0)     /* Enable HW polling RDSR */
> +#define CMD3_RDSR_SW        BIT(3)  /* Disable HW polling RDSR */
> +#define CMD3_RDSR           BIT(2)  /* Indicate it's a RDSR command */
> +#define CMD3_READ           0       /* Indicate it's a read command */
> +#define CMD3_WRITE          BIT(1)  /* Indicate it's a write command */
> +#define CMD3_INTR           BIT(0)  /* Enable interrupt and status update */
> +
> +#define CR_BUSYBIT(x)       (((x) & 0x07) << 16) /* Busy bit in the RDSR */
> +#define CR_ABORT            BIT(8)
> +#define CR_MODE0            0       /* SPI MODE0 */
> +#define CR_MODE3            BIT(4)  /* SPI MODE3 */
> +#define CR_CLKDIV(n)        ((n) & 0x03)    /* Clock divider = 2 * (n + 1) */
> +
> +#define TR_TRACE(x)         (((x) & 0x0f) << 4) /* trace delay */
> +#define TR_CS(x)            (((x) & 0x0f) << 0) /* cs delay */
> +
> +#define SR_RX_READY         BIT(1)  /* Rx Ready */
> +#define SR_TX_READY         BIT(0)  /* Tx Ready */
> +
> +#define ICR_RX_THRES(x)     (((x) & 0x03) << 12)/* rx interrupt threshold */
> +#define ICR_TX_THRES(x)     (((x) & 0x03) << 8) /* tx interrupt threshold */
> +#define ICR_DMA             BIT(0)  /* Enable DMA HW handshake */
> +
> +#define ISR_CMDFIN          BIT(0)  /* Command finished interrupt */
> +
> +#define FEAR_CLKM_BYPORT    0       /* clock mode = byport */
> +#define FEAR_CLKM_SYNC      BIT(25) /* clock mode = sync */
> +#define FEAR_SUPP_DTR       BIT(24) /* support double transfer rate */
> +#define FEAR_CMDQ(x)        (((x) & 0x07) << 16)    /* cmd queue depth */
> +#define FEAR_RXFIFO(x)      (((x) & 0xff) << 8)     /* rx fifo depth */
> +#define FEAR_TXFIFO(x)      (((x) & 0xff) << 0)     /* tx fifo depth */
> +
> +#endif  /* HW_ARM_FTSPI020_H */
> --
> 1.7.9.5
>
>
Andreas Färber Feb. 26, 2013, 1:13 p.m. UTC | #2
Am 26.02.2013 12:08, schrieb Peter Crosthwaite:
> On Tue, Feb 26, 2013 at 7:14 PM, Kuo-Jung Su <dantesu@gmail.com> wrote:
>> From: Kuo-Jung Su <dantesu@faraday-tech.com>
>>
>> The FTSPI020 is an integrated SPI Flash controller
>> which supports upto 4 flash chips.

"up to"

>>
>> Signed-off-by: Kuo-Jung Su <dantesu@faraday-tech.com>
>> ---
> 
> Please provide change logs below the line as per the patch submission process.

Let's try not to give contradictory instructions here: The change log
can be found in the cover letter.
It does not mention any changes of this file since v3.

http://wiki.qemu.org/Contribute/SubmitAPatch seems to describe a single
patch only, where as you say the change log is expected below --- rather
than in the commit message. Copying change logs into every single patch
of a long series is either troublesome and error-prone when done by hand
or if --- is inserted into the commit message leads to cherry-picking
not working as expected. The cover letter needs to be written up anyway,
so is the easiest solution.

Andreas
Kuo-Jung Su Feb. 27, 2013, 2:08 a.m. UTC | #3
2013/2/26 Peter Crosthwaite <peter.crosthwaite@xilinx.com>:
> Hi Kuo-Jung,
>
> On Tue, Feb 26, 2013 at 7:14 PM, Kuo-Jung Su <dantesu@gmail.com> wrote:
>> From: Kuo-Jung Su <dantesu@faraday-tech.com>
>>
>> The FTSPI020 is an integrated SPI Flash controller
>> which supports upto 4 flash chips.
>>
>> Signed-off-by: Kuo-Jung Su <dantesu@faraday-tech.com>
>> ---
>
> Please provide change logs below the line as per the patch submission process.
>
>>  hw/arm/Makefile.objs      |    1 +
>>  hw/arm/faraday_a369.c     |   13 ++
>>  hw/arm/faraday_a369_soc.c |    4 +
>>  hw/arm/ftspi020.c         |  333 +++++++++++++++++++++++++++++++++++++++++++++
>>  hw/arm/ftspi020.h         |   81 +++++++++++
>>  5 files changed, 432 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 c25eba2..2ed1c7c 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 46fc570..ace0784 100644
>> --- a/hw/arm/faraday_a369.c
>> +++ b/hw/arm/faraday_a369.c
>> @@ -67,6 +67,19 @@ a369_board_init(QEMUMachineInitArgs *args)
>>          wm8731_data_req_set(s->codec, ftssp010_i2s_data_req, s->i2s[0]);
>>      }
>>
>> +    /* Attach the spi flash to ftspi020.0 */
>> +    nr_flash = 1;
>> +    for (i = 0; i < nr_flash; i++) {
>> +        SSIBus *ssi = (SSIBus *)qdev_get_child_bus(s->spi[1], "spi");
>> +        DeviceState *fl = ssi_create_slave_no_init(ssi, "m25p80");
>> +        qemu_irq cs_line;
>> +
>> +        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(s->spi[1]), i + 1, cs_line);
>> +    }
>> +
>>      if (args->kernel_filename) {
>>          s->bi = g_new0(struct arm_boot_info, 1);
>>
>> diff --git a/hw/arm/faraday_a369_soc.c b/hw/arm/faraday_a369_soc.c
>> index eb9dd2f..8b07689 100644
>> --- a/hw/arm/faraday_a369_soc.c
>> +++ b/hw/arm/faraday_a369_soc.c
>> @@ -272,6 +272,10 @@ a369soc_device_init(FaradaySoCState *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[4]);
>> +    s->spi[1] = ds;
>>  }
>>
>>  static int a369soc_init(SysBusDevice *busdev)
>> diff --git a/hw/arm/ftspi020.c b/hw/arm/ftspi020.c
>> new file mode 100644
>> index 0000000..fb8a510
>> --- /dev/null
>> +++ b/hw/arm/ftspi020.c
>> @@ -0,0 +1,333 @@
>> +/*
>> + * Faraday FTSPI020 Flash Controller
>> + *
>> + * Copyright (c) 2012 Faraday Technology
>> + * Written by Dante Su <dantesu@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;
>> +    qemu_irq *cs_lines;
>> +
>> +    int wip;    /* SPI Flash Status: Write In Progress BIT shift */
>> +
>> +    /* 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)
>> +{
>> +    qemu_set_irq(s->irq, s->isr ? 1 : 0);
>> +}
>> +
>> +static void ftspi020_handle_ack(void *opaque, int line, int level)
>> +{
>> +    Ftspi020State *s = FTSPI020(opaque);
>> +
>> +    if (!(s->icr & ICR_DMA)) {
>> +        return;
>> +    }
>> +
>> +    if (level) {
>> +        qemu_set_irq(s->req, 0);
>> +    } else if (s->cmd[2]) {
>> +        qemu_set_irq(s->req, 1);
>> +    }
>> +}
>> +
>> +static int ftspi020_do_command(Ftspi020State *s)
>> +{
>> +    uint32_t cs   = extract32(s->cmd[3],  8, 2);
>> +    uint32_t cmd  = extract32(s->cmd[3], 24, 8);
>> +    uint32_t ilen = extract32(s->cmd[1], 24, 2);
>> +    uint32_t alen = extract32(s->cmd[1],  0, 3);
>> +    uint32_t dcyc = extract32(s->cmd[1], 16, 8);
>> +
>> +    if (dcyc % 8) {
>> +        hw_error("ftspi020: bad dummy clock (%u) to QEMU\n", dcyc);
>> +        exit(1);
>> +    }
>> +
>> +    /* 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] & (CMD3_RDSR | CMD3_WRITE)) == CMD3_RDSR) {
>> +        uint32_t rdsr;
>> +
>> +        ssi_transfer(s->spi, cmd);
>> +        do {
>> +            rdsr = ssi_transfer(s->spi, 0x00);
>> +            if (s->cmd[3] & CMD3_RDSR_SW) {
>> +                break;
>> +            }
>> +        } while (rdsr & (1 << s->wip));
>> +        s->rdsr = rdsr;
>> +    } 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, extract32(s->cmd[0], i * 8, 8));
>> +        }
>> +        /* dummy cycles */
>> +        for (i = 0; i < (dcyc >> 3); ++i) {
>> +            ssi_transfer(s->spi, 0x00);
>> +        }
>> +    }
>> +
>> +    if (!s->cmd[2]) {
>> +        qemu_set_irq(s->cs_lines[cs], 1);
>> +    } else if (s->icr & ICR_DMA) {
>> +        qemu_set_irq(s->req, 1);
>> +    }
>> +
>> +    if (s->cmd[3] & CMD3_INTR) {
>> +        s->isr |= ISR_CMDFIN;
>> +    }
>> +    ftspi020_update_irq(s);
>> +
>> +    return 0;
>> +}
>> +
>> +static void ftspi020_chip_reset(Ftspi020State *s)
>> +{
>> +    int i;
>> +
>> +    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 NOT reset cs lines here, or the QEMU would crash */
>
> More information please. This should work. What happened?
>

If there is no spi flash connected with the specific cs_line,
de-activate the cs_line
in the ftspi020_chip_reset() would crash the QEMU without any error message.
I've checked qemu_set_irq() and ssi_cs_default(), but no luck.

>> +}
>> +
>> +static uint64_t
>> +ftspi020_mem_read(void *opaque, hwaddr addr, unsigned size)
>> +{
>> +    Ftspi020State *s = FTSPI020(opaque);
>> +    uint64_t ret = 0;
>> +
>> +    switch (addr) {
>> +    case REG_CMD0 ... REG_CMD3:
>> +        return s->cmd[(addr - REG_CMD0) / 4];
>> +    case REG_CR:
>> +        return s->ctrl;
>> +    case REG_TR:
>> +        return s->timing;
>> +    case REG_SR:
>> +        /* In QEMU, the data fifo is always ready for read/write */
>> +        return SR_RX_READY | SR_TX_READY;
>> +    case REG_ISR:
>> +        return s->isr;
>> +    case REG_ICR:
>> +        return s->icr;
>> +    case REG_RDSR:
>> +        return s->rdsr;
>> +    case REG_REVR:
>> +        return 0x00010001;  /* rev. 1.0.1 */
>> +    case REG_FEAR:
>> +        return FEAR_CLKM_SYNC
>> +            | FEAR_CMDQ(2) | FEAR_RXFIFO(32) | FEAR_TXFIFO(32);
>> +    case REG_DR:
>> +        if (!(s->cmd[3] & CMD3_WRITE)) {
>> +            int i;
>> +            uint32_t cs = extract32(s->cmd[3], 8, 2);
>> +            for (i = 0; i < 4 && s->cmd[2]; i++, s->cmd[2]--) {
>> +                ret = deposit32(ret, i * 8, 8,
>> +                    ssi_transfer(s->spi, 0x00) & 0xff);
>> +            }
>> +            if (!s->cmd[2]) {
>> +                qemu_set_irq(s->cs_lines[cs], 1);
>> +                if (s->cmd[3] & CMD3_INTR) {
>> +                    s->isr |= ISR_CMDFIN;
>> +                }
>> +                ftspi020_update_irq(s);
>> +            }
>> +        }
>> +        break;
>> +        /* we don't care */
>> +    default:
>
> You could qemu_log_mask(LOG_GUEST_ERROR on undefined memory accesses.
>

Got it, thanks.

>> +        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_CMD0 ... REG_CMD2:
>> +        s->cmd[(addr - REG_CMD0) / 4] = (uint32_t)val;
>> +        break;
>> +    case REG_CMD3:
>> +        s->cmd[3] = (uint32_t)val;
>> +        ftspi020_do_command(s);
>> +        break;
>> +    case REG_CR:
>> +        if (val & CR_ABORT) {
>> +            ftspi020_chip_reset(s);
>> +            val &= ~CR_ABORT;
>> +        }
>> +        s->ctrl = (uint32_t)val;
>> +        s->wip  = extract32(val, 16, 3);
>> +        break;
>> +    case REG_TR:
>> +        s->timing = (uint32_t)val;
>> +        break;
>> +    case REG_DR:
>> +        if (s->cmd[3] & CMD3_WRITE) {
>> +            int i;
>> +            uint32_t cs = extract32(s->cmd[3], 8, 2);
>> +            for (i = 0; i < 4 && s->cmd[2]; i++, s->cmd[2]--) {
>> +                ssi_transfer(s->spi, extract32((uint32_t)val, i * 8, 8));
>> +            }
>> +            if (!s->cmd[2]) {
>> +                qemu_set_irq(s->cs_lines[cs], 1);
>> +                if (s->cmd[3] & CMD3_INTR) {
>> +                    s->isr |= ISR_CMDFIN;
>> +                }
>> +                ftspi020_update_irq(s);
>> +            }
>> +        }
>> +        break;
>> +    case REG_ISR:
>> +        s->isr &= ~((uint32_t)val);
>> +        ftspi020_update_irq(s);
>> +        break;
>> +    case REG_ICR:
>> +        s->icr = (uint32_t)val;
>> +        break;
>> +        /* we don't care */
>> +    default:
>> +        break;
>> +    }
>> +}
>> +
>> +static const MemoryRegionOps ftspi020_ops = {
>> +    .read  = ftspi020_mem_read,
>> +    .write = ftspi020_mem_write,
>
> Your REG_FOO macros are jumping in fours, and the case statement in
> your read/write handlers only handles those values, leading me to
> believe your device only supports full word access. You should enforce
> this by setting min/max_access size to 4 here.
>

Got it, thanks.

> Regards,
> Peter
>
>> +    .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->cs_lines = g_new(qemu_irq, CFG_NR_CSLINES);
>> +    ssi_auto_connect_slaves(DEVICE(s), s->cs_lines, s->spi);
>> +    for (i = 0; i < CFG_NR_CSLINES; ++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),
>> +        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..47b5d2e
>> --- /dev/null
>> +++ b/hw/arm/ftspi020.h
>> @@ -0,0 +1,81 @@
>> +/*
>> + * Faraday FTSPI020 Flash Controller
>> + *
>> + * Copyright (c) 2012 Faraday Technology
>> + * Written by Dante Su <dantesu@faraday-tech.com>
>> + *
>> + * This code is licensed under GNU GPL v2+.
>> + */
>> +
>> +#ifndef HW_ARM_FTSPI020_H
>> +#define HW_ARM_FTSPI020_H
>> +
>> +#include "qemu/bitops.h"
>> +
>> +/* Number of CS lines */
>> +#define CFG_NR_CSLINES      4
>> +
>> +/******************************************************************************
>> + * FTSPI020 registers
>> + *****************************************************************************/
>> +#define REG_CMD0            0x00    /* Flash address */
>> +#define REG_CMD1            0x04
>> +#define REG_CMD2            0x08    /* Flash data counter */
>> +#define REG_CMD3            0x0c
>> +#define REG_CR              0x10    /* Control Register */
>> +#define REG_TR              0x14    /* AC Timing Register */
>> +#define REG_SR              0x18    /* Status Register */
>> +#define REG_ICR             0x20    /* Interrupt Control Register */
>> +#define REG_ISR             0x24    /* Interrupt Status Register */
>> +#define REG_RDSR            0x28    /* Read Status Register */
>> +#define REG_REVR            0x50    /* Revision Register */
>> +#define REG_FEAR            0x54    /* Feature Register */
>> +#define REG_DR              0x100   /* Data Register */
>> +
>> +#define CMD1_CTRD           BIT(28) /* Enable 1 byte continuous read */
>> +#define CMD1_INST_LEN(x)    (((x) & 0x03) << 24)/* instruction length */
>> +#define CMD1_DCLK_LEN(x)    (((x) & 0xff) << 16)/* dummy clock length */
>> +#define CMD1_ADDR_LEN(x)    (((x) & 0x07) << 0) /* address length */
>> +
>> +#define CMD3_INST_OPC(x)    (((x) & 0xff) << 24)/* instruction op code */
>> +#define CMD3_CTRD_OPC(x)    (((x) & 0xff) << 16)/* cont. read op code */
>> +#define CMD3_CS(x)          (((x) & 0x0f) << 8) /* chip select */
>> +#define CMD3_OPM_STD        (0)         /* standard 1-bit serial mode */
>> +#define CMD3_OPM_DUAL       (1 << 5)    /* fast read dual */
>> +#define CMD3_OPM_QUAD       (2 << 5)    /* fast read quad */
>> +#define CMD3_OPM_DUALIO     (3 << 5)    /* fast read dual io */
>> +#define CMD3_OPM_QUADIO     (4 << 5)    /* fast read quad io */
>> +#define CMD3_DTR            BIT(4)  /* Enable double transfer rate */
>> +#define CMD3_RDSR_HW        (0)     /* Enable HW polling RDSR */
>> +#define CMD3_RDSR_SW        BIT(3)  /* Disable HW polling RDSR */
>> +#define CMD3_RDSR           BIT(2)  /* Indicate it's a RDSR command */
>> +#define CMD3_READ           0       /* Indicate it's a read command */
>> +#define CMD3_WRITE          BIT(1)  /* Indicate it's a write command */
>> +#define CMD3_INTR           BIT(0)  /* Enable interrupt and status update */
>> +
>> +#define CR_BUSYBIT(x)       (((x) & 0x07) << 16) /* Busy bit in the RDSR */
>> +#define CR_ABORT            BIT(8)
>> +#define CR_MODE0            0       /* SPI MODE0 */
>> +#define CR_MODE3            BIT(4)  /* SPI MODE3 */
>> +#define CR_CLKDIV(n)        ((n) & 0x03)    /* Clock divider = 2 * (n + 1) */
>> +
>> +#define TR_TRACE(x)         (((x) & 0x0f) << 4) /* trace delay */
>> +#define TR_CS(x)            (((x) & 0x0f) << 0) /* cs delay */
>> +
>> +#define SR_RX_READY         BIT(1)  /* Rx Ready */
>> +#define SR_TX_READY         BIT(0)  /* Tx Ready */
>> +
>> +#define ICR_RX_THRES(x)     (((x) & 0x03) << 12)/* rx interrupt threshold */
>> +#define ICR_TX_THRES(x)     (((x) & 0x03) << 8) /* tx interrupt threshold */
>> +#define ICR_DMA             BIT(0)  /* Enable DMA HW handshake */
>> +
>> +#define ISR_CMDFIN          BIT(0)  /* Command finished interrupt */
>> +
>> +#define FEAR_CLKM_BYPORT    0       /* clock mode = byport */
>> +#define FEAR_CLKM_SYNC      BIT(25) /* clock mode = sync */
>> +#define FEAR_SUPP_DTR       BIT(24) /* support double transfer rate */
>> +#define FEAR_CMDQ(x)        (((x) & 0x07) << 16)    /* cmd queue depth */
>> +#define FEAR_RXFIFO(x)      (((x) & 0xff) << 8)     /* rx fifo depth */
>> +#define FEAR_TXFIFO(x)      (((x) & 0xff) << 0)     /* tx fifo depth */
>> +
>> +#endif  /* HW_ARM_FTSPI020_H */
>> --
>> 1.7.9.5
>>
>>



--
Best wishes,
Kuo-Jung Su
Kuo-Jung Su Feb. 27, 2013, 2:14 a.m. UTC | #4
2013/2/26 Andreas Färber <afaerber@suse.de>:
> Am 26.02.2013 12:08, schrieb Peter Crosthwaite:
>> On Tue, Feb 26, 2013 at 7:14 PM, Kuo-Jung Su <dantesu@gmail.com> wrote:
>>> From: Kuo-Jung Su <dantesu@faraday-tech.com>
>>>
>>> The FTSPI020 is an integrated SPI Flash controller
>>> which supports upto 4 flash chips.
>
> "up to"
>

Got it, thanks.

My English sucks, so if it's not too much bother, please help me to
correct my sentence.

>>>
>>> Signed-off-by: Kuo-Jung Su <dantesu@faraday-tech.com>
>>> ---
>>
>> Please provide change logs below the line as per the patch submission process.
>
> Let's try not to give contradictory instructions here: The change log
> can be found in the cover letter.
> It does not mention any changes of this file since v3.
>
> http://wiki.qemu.org/Contribute/SubmitAPatch seems to describe a single
> patch only, where as you say the change log is expected below --- rather
> than in the commit message. Copying change logs into every single patch
> of a long series is either troublesome and error-prone when done by hand
> or if --- is inserted into the commit message leads to cherry-picking
> not working as expected. The cover letter needs to be written up anyway,
> so is the easiest solution.

Sorry for my laziness, the change logs would be added to the cover letter
at the incoming v5 patch.


>
> Andreas
>
> --
> SUSE LINUX Products GmbH, Maxfeldstr. 5, 90409 Nürnberg, Germany
> GF: Jeff Hawn, Jennifer Guild, Felix Imendörffer; HRB 16746 AG Nürnberg



--
Best wishes,
Kuo-Jung Su
diff mbox

Patch

diff --git a/hw/arm/Makefile.objs b/hw/arm/Makefile.objs
index c25eba2..2ed1c7c 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 46fc570..ace0784 100644
--- a/hw/arm/faraday_a369.c
+++ b/hw/arm/faraday_a369.c
@@ -67,6 +67,19 @@  a369_board_init(QEMUMachineInitArgs *args)
         wm8731_data_req_set(s->codec, ftssp010_i2s_data_req, s->i2s[0]);
     }
 
+    /* Attach the spi flash to ftspi020.0 */
+    nr_flash = 1;
+    for (i = 0; i < nr_flash; i++) {
+        SSIBus *ssi = (SSIBus *)qdev_get_child_bus(s->spi[1], "spi");
+        DeviceState *fl = ssi_create_slave_no_init(ssi, "m25p80");
+        qemu_irq cs_line;
+
+        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(s->spi[1]), i + 1, cs_line);
+    }
+
     if (args->kernel_filename) {
         s->bi = g_new0(struct arm_boot_info, 1);
 
diff --git a/hw/arm/faraday_a369_soc.c b/hw/arm/faraday_a369_soc.c
index eb9dd2f..8b07689 100644
--- a/hw/arm/faraday_a369_soc.c
+++ b/hw/arm/faraday_a369_soc.c
@@ -272,6 +272,10 @@  a369soc_device_init(FaradaySoCState *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[4]);
+    s->spi[1] = ds;
 }
 
 static int a369soc_init(SysBusDevice *busdev)
diff --git a/hw/arm/ftspi020.c b/hw/arm/ftspi020.c
new file mode 100644
index 0000000..fb8a510
--- /dev/null
+++ b/hw/arm/ftspi020.c
@@ -0,0 +1,333 @@ 
+/*
+ * Faraday FTSPI020 Flash Controller
+ *
+ * Copyright (c) 2012 Faraday Technology
+ * Written by Dante Su <dantesu@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;
+    qemu_irq *cs_lines;
+
+    int wip;    /* SPI Flash Status: Write In Progress BIT shift */
+
+    /* 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)
+{
+    qemu_set_irq(s->irq, s->isr ? 1 : 0);
+}
+
+static void ftspi020_handle_ack(void *opaque, int line, int level)
+{
+    Ftspi020State *s = FTSPI020(opaque);
+
+    if (!(s->icr & ICR_DMA)) {
+        return;
+    }
+
+    if (level) {
+        qemu_set_irq(s->req, 0);
+    } else if (s->cmd[2]) {
+        qemu_set_irq(s->req, 1);
+    }
+}
+
+static int ftspi020_do_command(Ftspi020State *s)
+{
+    uint32_t cs   = extract32(s->cmd[3],  8, 2);
+    uint32_t cmd  = extract32(s->cmd[3], 24, 8);
+    uint32_t ilen = extract32(s->cmd[1], 24, 2);
+    uint32_t alen = extract32(s->cmd[1],  0, 3);
+    uint32_t dcyc = extract32(s->cmd[1], 16, 8);
+
+    if (dcyc % 8) {
+        hw_error("ftspi020: bad dummy clock (%u) to QEMU\n", dcyc);
+        exit(1);
+    }
+
+    /* 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] & (CMD3_RDSR | CMD3_WRITE)) == CMD3_RDSR) {
+        uint32_t rdsr;
+
+        ssi_transfer(s->spi, cmd);
+        do {
+            rdsr = ssi_transfer(s->spi, 0x00);
+            if (s->cmd[3] & CMD3_RDSR_SW) {
+                break;
+            }
+        } while (rdsr & (1 << s->wip));
+        s->rdsr = rdsr;
+    } 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, extract32(s->cmd[0], i * 8, 8));
+        }
+        /* dummy cycles */
+        for (i = 0; i < (dcyc >> 3); ++i) {
+            ssi_transfer(s->spi, 0x00);
+        }
+    }
+
+    if (!s->cmd[2]) {
+        qemu_set_irq(s->cs_lines[cs], 1);
+    } else if (s->icr & ICR_DMA) {
+        qemu_set_irq(s->req, 1);
+    }
+
+    if (s->cmd[3] & CMD3_INTR) {
+        s->isr |= ISR_CMDFIN;
+    }
+    ftspi020_update_irq(s);
+
+    return 0;
+}
+
+static void ftspi020_chip_reset(Ftspi020State *s)
+{
+    int i;
+
+    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 NOT reset cs lines here, or the QEMU would crash */
+}
+
+static uint64_t
+ftspi020_mem_read(void *opaque, hwaddr addr, unsigned size)
+{
+    Ftspi020State *s = FTSPI020(opaque);
+    uint64_t ret = 0;
+
+    switch (addr) {
+    case REG_CMD0 ... REG_CMD3:
+        return s->cmd[(addr - REG_CMD0) / 4];
+    case REG_CR:
+        return s->ctrl;
+    case REG_TR:
+        return s->timing;
+    case REG_SR:
+        /* In QEMU, the data fifo is always ready for read/write */
+        return SR_RX_READY | SR_TX_READY;
+    case REG_ISR:
+        return s->isr;
+    case REG_ICR:
+        return s->icr;
+    case REG_RDSR:
+        return s->rdsr;
+    case REG_REVR:
+        return 0x00010001;  /* rev. 1.0.1 */
+    case REG_FEAR:
+        return FEAR_CLKM_SYNC
+            | FEAR_CMDQ(2) | FEAR_RXFIFO(32) | FEAR_TXFIFO(32);
+    case REG_DR:
+        if (!(s->cmd[3] & CMD3_WRITE)) {
+            int i;
+            uint32_t cs = extract32(s->cmd[3], 8, 2);
+            for (i = 0; i < 4 && s->cmd[2]; i++, s->cmd[2]--) {
+                ret = deposit32(ret, i * 8, 8,
+                    ssi_transfer(s->spi, 0x00) & 0xff);
+            }
+            if (!s->cmd[2]) {
+                qemu_set_irq(s->cs_lines[cs], 1);
+                if (s->cmd[3] & CMD3_INTR) {
+                    s->isr |= ISR_CMDFIN;
+                }
+                ftspi020_update_irq(s);
+            }
+        }
+        break;
+        /* we don't care */
+    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_CMD0 ... REG_CMD2:
+        s->cmd[(addr - REG_CMD0) / 4] = (uint32_t)val;
+        break;
+    case REG_CMD3:
+        s->cmd[3] = (uint32_t)val;
+        ftspi020_do_command(s);
+        break;
+    case REG_CR:
+        if (val & CR_ABORT) {
+            ftspi020_chip_reset(s);
+            val &= ~CR_ABORT;
+        }
+        s->ctrl = (uint32_t)val;
+        s->wip  = extract32(val, 16, 3);
+        break;
+    case REG_TR:
+        s->timing = (uint32_t)val;
+        break;
+    case REG_DR:
+        if (s->cmd[3] & CMD3_WRITE) {
+            int i;
+            uint32_t cs = extract32(s->cmd[3], 8, 2);
+            for (i = 0; i < 4 && s->cmd[2]; i++, s->cmd[2]--) {
+                ssi_transfer(s->spi, extract32((uint32_t)val, i * 8, 8));
+            }
+            if (!s->cmd[2]) {
+                qemu_set_irq(s->cs_lines[cs], 1);
+                if (s->cmd[3] & CMD3_INTR) {
+                    s->isr |= ISR_CMDFIN;
+                }
+                ftspi020_update_irq(s);
+            }
+        }
+        break;
+    case REG_ISR:
+        s->isr &= ~((uint32_t)val);
+        ftspi020_update_irq(s);
+        break;
+    case REG_ICR:
+        s->icr = (uint32_t)val;
+        break;
+        /* we don't care */
+    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->cs_lines = g_new(qemu_irq, CFG_NR_CSLINES);
+    ssi_auto_connect_slaves(DEVICE(s), s->cs_lines, s->spi);
+    for (i = 0; i < CFG_NR_CSLINES; ++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),
+        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..47b5d2e
--- /dev/null
+++ b/hw/arm/ftspi020.h
@@ -0,0 +1,81 @@ 
+/*
+ * Faraday FTSPI020 Flash Controller
+ *
+ * Copyright (c) 2012 Faraday Technology
+ * Written by Dante Su <dantesu@faraday-tech.com>
+ *
+ * This code is licensed under GNU GPL v2+.
+ */
+
+#ifndef HW_ARM_FTSPI020_H
+#define HW_ARM_FTSPI020_H
+
+#include "qemu/bitops.h"
+
+/* Number of CS lines */
+#define CFG_NR_CSLINES      4
+
+/******************************************************************************
+ * FTSPI020 registers
+ *****************************************************************************/
+#define REG_CMD0            0x00    /* Flash address */
+#define REG_CMD1            0x04
+#define REG_CMD2            0x08    /* Flash data counter */
+#define REG_CMD3            0x0c
+#define REG_CR              0x10    /* Control Register */
+#define REG_TR              0x14    /* AC Timing Register */
+#define REG_SR              0x18    /* Status Register */
+#define REG_ICR             0x20    /* Interrupt Control Register */
+#define REG_ISR             0x24    /* Interrupt Status Register */
+#define REG_RDSR            0x28    /* Read Status Register */
+#define REG_REVR            0x50    /* Revision Register */
+#define REG_FEAR            0x54    /* Feature Register */
+#define REG_DR              0x100   /* Data Register */
+
+#define CMD1_CTRD           BIT(28) /* Enable 1 byte continuous read */
+#define CMD1_INST_LEN(x)    (((x) & 0x03) << 24)/* instruction length */
+#define CMD1_DCLK_LEN(x)    (((x) & 0xff) << 16)/* dummy clock length */
+#define CMD1_ADDR_LEN(x)    (((x) & 0x07) << 0) /* address length */
+
+#define CMD3_INST_OPC(x)    (((x) & 0xff) << 24)/* instruction op code */
+#define CMD3_CTRD_OPC(x)    (((x) & 0xff) << 16)/* cont. read op code */
+#define CMD3_CS(x)          (((x) & 0x0f) << 8) /* chip select */
+#define CMD3_OPM_STD        (0)         /* standard 1-bit serial mode */
+#define CMD3_OPM_DUAL       (1 << 5)    /* fast read dual */
+#define CMD3_OPM_QUAD       (2 << 5)    /* fast read quad */
+#define CMD3_OPM_DUALIO     (3 << 5)    /* fast read dual io */
+#define CMD3_OPM_QUADIO     (4 << 5)    /* fast read quad io */
+#define CMD3_DTR            BIT(4)  /* Enable double transfer rate */
+#define CMD3_RDSR_HW        (0)     /* Enable HW polling RDSR */
+#define CMD3_RDSR_SW        BIT(3)  /* Disable HW polling RDSR */
+#define CMD3_RDSR           BIT(2)  /* Indicate it's a RDSR command */
+#define CMD3_READ           0       /* Indicate it's a read command */
+#define CMD3_WRITE          BIT(1)  /* Indicate it's a write command */
+#define CMD3_INTR           BIT(0)  /* Enable interrupt and status update */
+
+#define CR_BUSYBIT(x)       (((x) & 0x07) << 16) /* Busy bit in the RDSR */
+#define CR_ABORT            BIT(8)
+#define CR_MODE0            0       /* SPI MODE0 */
+#define CR_MODE3            BIT(4)  /* SPI MODE3 */
+#define CR_CLKDIV(n)        ((n) & 0x03)    /* Clock divider = 2 * (n + 1) */
+
+#define TR_TRACE(x)         (((x) & 0x0f) << 4) /* trace delay */
+#define TR_CS(x)            (((x) & 0x0f) << 0) /* cs delay */
+
+#define SR_RX_READY         BIT(1)  /* Rx Ready */
+#define SR_TX_READY         BIT(0)  /* Tx Ready */
+
+#define ICR_RX_THRES(x)     (((x) & 0x03) << 12)/* rx interrupt threshold */
+#define ICR_TX_THRES(x)     (((x) & 0x03) << 8) /* tx interrupt threshold */
+#define ICR_DMA             BIT(0)  /* Enable DMA HW handshake */
+
+#define ISR_CMDFIN          BIT(0)  /* Command finished interrupt */
+
+#define FEAR_CLKM_BYPORT    0       /* clock mode = byport */
+#define FEAR_CLKM_SYNC      BIT(25) /* clock mode = sync */
+#define FEAR_SUPP_DTR       BIT(24) /* support double transfer rate */
+#define FEAR_CMDQ(x)        (((x) & 0x07) << 16)    /* cmd queue depth */
+#define FEAR_RXFIFO(x)      (((x) & 0xff) << 8)     /* rx fifo depth */
+#define FEAR_TXFIFO(x)      (((x) & 0xff) << 0)     /* tx fifo depth */
+
+#endif  /* HW_ARM_FTSPI020_H */