diff mbox

[v3,10/12] i.MX: Add the Freescale SPI Controller

Message ID a2b8b45981e413061bbe88de1f50c85a5000de45.1456868959.git.jcd@tribudubois.net (mailing list archive)
State New, archived
Headers show

Commit Message

Jean-Christophe Dubois March 1, 2016, 10:27 p.m. UTC
Signed-off-by: Jean-Christophe Dubois <jcd@tribudubois.net>
---

Changes since v1:
 * Access SPI slave only at a byte level.
 * rework the CS activation to avoid to reset access to SPI slaves.

Changes since v2:
 * Added #include "qemu/osdep.h"
 * remove previous_level from state struct
 * save burst_length in VMSTATE

 hw/ssi/Makefile.objs     |   1 +
 hw/ssi/imx_spi.c         | 459 +++++++++++++++++++++++++++++++++++++++++++++++
 include/hw/ssi/imx_spi.h | 103 +++++++++++
 3 files changed, 563 insertions(+)
 create mode 100644 hw/ssi/imx_spi.c
 create mode 100644 include/hw/ssi/imx_spi.h

Comments

Peter Maydell March 10, 2016, 10:31 a.m. UTC | #1
On 2 March 2016 at 05:27, Jean-Christophe Dubois <jcd@tribudubois.net> wrote:
> Signed-off-by: Jean-Christophe Dubois <jcd@tribudubois.net>
> ---
>
> +
> +static void imx_spi_reset(DeviceState *dev)
> +{
> +    IMXSPIState *s = IMX_SPI(dev);
> +    int i;
> +
> +    DPRINTF("\n");
> +
> +    memset(s->regs, 0, sizeof(s->regs));
> +
> +    s->regs[ECSPI_STATREG] = 0x00000003;
> +
> +    imx_spi_rxfifo_reset(s);
> +    imx_spi_txfifo_reset(s);
> +
> +    imx_spi_update_irq(s);
> +
> +    s->burst_length = 0;
> +
> +    for (i = 0; i < 4; i++) {
> +        qemu_set_irq(s->cs_lines[i], 0);
> +    }

Calling qemu_set_irq() from a reset function is generally
a bad idea.

> +}

Otherwise
Reviewed-by: Peter Maydell <peter.maydell@linaro.org>

thanks
-- PMM
Jean-Christophe Dubois March 10, 2016, 7:26 p.m. UTC | #2
Le 10/03/2016 11:31, Peter Maydell a écrit :
> On 2 March 2016 at 05:27, Jean-Christophe Dubois <jcd@tribudubois.net> wrote:
>> Signed-off-by: Jean-Christophe Dubois <jcd@tribudubois.net>
>> ---
>>
>> +
>> +static void imx_spi_reset(DeviceState *dev)
>> +{
>> +    IMXSPIState *s = IMX_SPI(dev);
>> +    int i;
>> +
>> +    DPRINTF("\n");
>> +
>> +    memset(s->regs, 0, sizeof(s->regs));
>> +
>> +    s->regs[ECSPI_STATREG] = 0x00000003;
>> +
>> +    imx_spi_rxfifo_reset(s);
>> +    imx_spi_txfifo_reset(s);
>> +
>> +    imx_spi_update_irq(s);
>> +
>> +    s->burst_length = 0;
>> +
>> +    for (i = 0; i < 4; i++) {
>> +        qemu_set_irq(s->cs_lines[i], 0);
>> +    }
> Calling qemu_set_irq() from a reset function is generally
> a bad idea.

Could I assume that all irq lines are set automatically to 0 on reset?

>
>> +}
> Otherwise
> Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
>
> thanks
> -- PMM
>
Peter Maydell March 11, 2016, 12:01 a.m. UTC | #3
On 11 March 2016 at 02:26, Jean-Christophe DUBOIS <jcd@tribudubois.net> wrote:
> Le 10/03/2016 11:31, Peter Maydell a écrit :
>> Calling qemu_set_irq() from a reset function is generally
>> a bad idea.
>
>
> Could I assume that all irq lines are set automatically to 0 on reset?

The way this works is that an irq line has two ends on it,
and no internal state of its own. So on reset, the devices
at each end reset themselves. For the device on the receiving
end of the irq line, it will reset itself into the "assume
this input of mine is at 0" state.

(The reason not to call qemu_set_irq() on reset from the
sending end is that there's no guarantee about which order
the two devices reset themselves -- if you reset before
the receiving end then your call would do nothing anyway
because the subsequent reset in the receiver resets its state
and it forgets about the inbound signal being high.)

This is of course broken for the case where a device wants
to assert a line to 1 when it resets, but we've basically
been able to ignore this so far. Trying to clean up our
reset handling would be a huge job and probably not back
compatible either...

thanks
-- PMM
diff mbox

Patch

diff --git a/hw/ssi/Makefile.objs b/hw/ssi/Makefile.objs
index 9555825..fcbb79e 100644
--- a/hw/ssi/Makefile.objs
+++ b/hw/ssi/Makefile.objs
@@ -4,3 +4,4 @@  common-obj-$(CONFIG_XILINX_SPI) += xilinx_spi.o
 common-obj-$(CONFIG_XILINX_SPIPS) += xilinx_spips.o
 
 obj-$(CONFIG_OMAP) += omap_spi.o
+obj-$(CONFIG_IMX) += imx_spi.o
diff --git a/hw/ssi/imx_spi.c b/hw/ssi/imx_spi.c
new file mode 100644
index 0000000..bf1c660
--- /dev/null
+++ b/hw/ssi/imx_spi.c
@@ -0,0 +1,459 @@ 
+/*
+ * IMX SPI Controller
+ *
+ * Copyright (c) 2016 Jean-Christophe Dubois <jcd@tribudubois.net>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "hw/ssi/imx_spi.h"
+#include "sysemu/sysemu.h"
+
+#ifndef DEBUG_IMX_SPI
+#define DEBUG_IMX_SPI 0
+#endif
+
+#define DPRINTF(fmt, args...) \
+    do { \
+        if (DEBUG_IMX_SPI) { \
+            fprintf(stderr, "[%s]%s: " fmt , TYPE_IMX_SPI, \
+                                             __func__, ##args); \
+        } \
+    } while (0)
+
+static char const *imx_spi_reg_name(uint32_t reg)
+{
+    static char unknown[20];
+
+    switch (reg) {
+    case ECSPI_RXDATA:
+        return  "ECSPI_RXDATA";
+    case ECSPI_TXDATA:
+        return  "ECSPI_TXDATA";
+    case ECSPI_CONREG:
+        return  "ECSPI_CONREG";
+    case ECSPI_CONFIGREG:
+        return  "ECSPI_CONFIGREG";
+    case ECSPI_INTREG:
+        return  "ECSPI_INTREG";
+    case ECSPI_DMAREG:
+        return  "ECSPI_DMAREG";
+    case ECSPI_STATREG:
+        return  "ECSPI_STATREG";
+    case ECSPI_PERIODREG:
+        return  "ECSPI_PERIODREG";
+    case ECSPI_TESTREG:
+        return  "ECSPI_TESTREG";
+    case ECSPI_MSGDATA:
+        return  "ECSPI_MSGDATA";
+    default:
+        sprintf(unknown, "%d ?", reg);
+        return unknown;
+    }
+}
+
+static const VMStateDescription vmstate_imx_spi = {
+    .name = TYPE_IMX_SPI,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_FIFO32(tx_fifo, IMXSPIState),
+        VMSTATE_FIFO32(rx_fifo, IMXSPIState),
+        VMSTATE_INT16(burst_length, IMXSPIState),
+        VMSTATE_UINT32_ARRAY(regs, IMXSPIState, ECSPI_MAX),
+        VMSTATE_END_OF_LIST()
+    },
+};
+
+static void imx_spi_txfifo_reset(IMXSPIState *s)
+{
+    fifo32_reset(&s->tx_fifo);
+    s->regs[ECSPI_STATREG] |= ECSPI_STATREG_TE;
+    s->regs[ECSPI_STATREG] &= ~ECSPI_STATREG_TF;
+}
+
+static void imx_spi_rxfifo_reset(IMXSPIState *s)
+{
+    fifo32_reset(&s->rx_fifo);
+    s->regs[ECSPI_STATREG] &= ~ECSPI_STATREG_RR;
+    s->regs[ECSPI_STATREG] &= ~ECSPI_STATREG_RF;
+    s->regs[ECSPI_STATREG] &= ~ECSPI_STATREG_RO;
+}
+
+static void imx_spi_update_irq(IMXSPIState *s)
+{
+    int level;
+
+    if (fifo32_is_empty(&s->rx_fifo)) {
+        s->regs[ECSPI_STATREG] &= ~ECSPI_STATREG_RR;
+    } else {
+        s->regs[ECSPI_STATREG] |= ECSPI_STATREG_RR;
+    }
+
+    if (fifo32_is_full(&s->rx_fifo)) {
+        s->regs[ECSPI_STATREG] |= ECSPI_STATREG_RF;
+    } else {
+        s->regs[ECSPI_STATREG] &= ~ECSPI_STATREG_RF;
+    }
+
+    if (fifo32_is_empty(&s->tx_fifo)) {
+        s->regs[ECSPI_STATREG] |= ECSPI_STATREG_TE;
+    } else {
+        s->regs[ECSPI_STATREG] &= ~ECSPI_STATREG_TE;
+    }
+
+    if (fifo32_is_full(&s->tx_fifo)) {
+        s->regs[ECSPI_STATREG] |= ECSPI_STATREG_TF;
+    } else {
+        s->regs[ECSPI_STATREG] &= ~ECSPI_STATREG_TF;
+    }
+
+    level = s->regs[ECSPI_STATREG] & s->regs[ECSPI_INTREG] ? 1 : 0;
+
+    qemu_set_irq(s->irq, level);
+
+    DPRINTF("IRQ level is %d\n", level);
+}
+
+static uint8_t imx_spi_selected_channel(IMXSPIState *s)
+{
+    return EXTRACT(s->regs[ECSPI_CONREG], ECSPI_CONREG_CHANNEL_SELECT);
+}
+
+static uint32_t imx_spi_burst_length(IMXSPIState *s)
+{
+    return EXTRACT(s->regs[ECSPI_CONREG], ECSPI_CONREG_BURST_LENGTH) + 1;
+}
+
+static bool imx_spi_is_enabled(IMXSPIState *s)
+{
+    return s->regs[ECSPI_CONREG] & ECSPI_CONREG_EN;
+}
+
+static bool imx_spi_channel_is_master(IMXSPIState *s)
+{
+    uint8_t mode = EXTRACT(s->regs[ECSPI_CONREG], ECSPI_CONREG_CHANNEL_MODE);
+
+    return (mode & (1 << imx_spi_selected_channel(s))) ? true : false;
+}
+
+static bool imx_spi_is_multiple_master_burst(IMXSPIState *s)
+{
+    uint8_t wave = EXTRACT(s->regs[ECSPI_CONFIGREG], ECSPI_CONFIGREG_SS_CTL);
+
+    return imx_spi_channel_is_master(s) &&
+           !(s->regs[ECSPI_CONREG] & ECSPI_CONREG_SMC) &&
+           ((wave & (1 << imx_spi_selected_channel(s))) ? true : false);
+}
+
+static void imx_spi_flush_txfifo(IMXSPIState *s)
+{
+    uint32_t tx;
+    uint32_t rx;
+
+    DPRINTF("Begin: TX Fifo Size = %d, RX Fifo Size = %d\n",
+            fifo32_num_used(&s->tx_fifo), fifo32_num_used(&s->rx_fifo));
+
+    while (!fifo32_is_empty(&s->tx_fifo)) {
+        int tx_burst = 0;
+        int index = 0;
+
+        if (s->burst_length <= 0) {
+            s->burst_length = imx_spi_burst_length(s);
+
+            DPRINTF("Burst length = %d\n", s->burst_length);
+
+            if (imx_spi_is_multiple_master_burst(s)) {
+                s->regs[ECSPI_CONREG] |= ECSPI_CONREG_XCH;
+            }
+        }
+
+        tx = fifo32_pop(&s->tx_fifo);
+
+        DPRINTF("data tx:0x%08x\n", tx);
+
+        tx_burst = MIN(s->burst_length, 32);
+
+        rx = 0;
+
+        while (tx_burst) {
+            uint8_t byte = tx & 0xff;
+
+            DPRINTF("writing 0x%02x\n", (uint32_t)byte);
+
+            /* We need to write one byte at a time */
+            byte = ssi_transfer(s->bus, byte);
+
+            DPRINTF("0x%02x read\n", (uint32_t)byte);
+
+            tx = tx >> 8;
+            rx |= (byte << (index * 8));
+
+            /* Remove 8 bits from the actual burst */
+            tx_burst -= 8;
+            s->burst_length -= 8;
+            index++;
+        }
+
+        DPRINTF("data rx:0x%08x\n", rx);
+
+        if (fifo32_is_full(&s->rx_fifo)) {
+            s->regs[ECSPI_STATREG] |= ECSPI_STATREG_RO;
+        } else {
+            fifo32_push(&s->rx_fifo, (uint8_t)rx);
+        }
+
+        if (s->burst_length <= 0) {
+            s->regs[ECSPI_CONREG] &= ~ECSPI_CONREG_XCH;
+
+            if (!imx_spi_is_multiple_master_burst(s)) {
+                s->regs[ECSPI_STATREG] |= ECSPI_STATREG_TC;
+                break;
+            }
+        }
+    }
+
+    if (fifo32_is_empty(&s->tx_fifo)) {
+        s->regs[ECSPI_STATREG] |= ECSPI_STATREG_TC;
+    }
+
+    /* TODO: We should also use TDR and RDR bits */
+
+    DPRINTF("End: TX Fifo Size = %d, RX Fifo Size = %d\n",
+            fifo32_num_used(&s->tx_fifo), fifo32_num_used(&s->rx_fifo));
+}
+
+static void imx_spi_reset(DeviceState *dev)
+{
+    IMXSPIState *s = IMX_SPI(dev);
+    int i;
+
+    DPRINTF("\n");
+
+    memset(s->regs, 0, sizeof(s->regs));
+
+    s->regs[ECSPI_STATREG] = 0x00000003;
+
+    imx_spi_rxfifo_reset(s);
+    imx_spi_txfifo_reset(s);
+
+    imx_spi_update_irq(s);
+
+    s->burst_length = 0;
+
+    for (i = 0; i < 4; i++) {
+        qemu_set_irq(s->cs_lines[i], 0);
+    }
+}
+
+static uint64_t imx_spi_read(void *opaque, hwaddr offset, unsigned size)
+{
+    uint32_t value = 0;
+    IMXSPIState *s = opaque;
+    uint32_t index = offset >> 2;
+
+    if (index >=  ECSPI_MAX) {
+        qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%"
+                      HWADDR_PRIx "\n", TYPE_IMX_SPI, __func__, offset);
+        return 0;
+    }
+
+    switch (index) {
+    case ECSPI_RXDATA:
+        if (!imx_spi_is_enabled(s)) {
+            value = 0;
+        } else if (fifo32_is_empty(&s->rx_fifo)) {
+            /* value is undefined */
+            value = 0xdeadbeef;
+        } else {
+            /* read from the RX FIFO */
+            value = fifo32_pop(&s->rx_fifo);
+        }
+
+        break;
+    case ECSPI_TXDATA:
+        qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Trying to read from TX FIFO\n",
+                      TYPE_IMX_SPI, __func__);
+
+        /* Reading from TXDATA gives 0 */
+
+        break;
+    case ECSPI_MSGDATA:
+        qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Trying to read from MSG FIFO\n",
+                      TYPE_IMX_SPI, __func__);
+
+        /* Reading from MSGDATA gives 0 */
+
+        break;
+    default:
+        value = s->regs[index];
+        break;
+    }
+
+    DPRINTF("reg[%s] => 0x%" PRIx32 "\n", imx_spi_reg_name(index), value);
+
+    imx_spi_update_irq(s);
+
+    return (uint64_t)value;
+}
+
+static void imx_spi_write(void *opaque, hwaddr offset, uint64_t value,
+                           unsigned size)
+{
+    IMXSPIState *s = opaque;
+    uint32_t index = offset >> 2;
+    uint32_t change_mask;
+
+    if (index >=  ECSPI_MAX) {
+        qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%"
+                      HWADDR_PRIx "\n", TYPE_IMX_SPI, __func__, offset);
+        return;
+    }
+
+    DPRINTF("reg[%s] <= 0x%" PRIx32 "\n", imx_spi_reg_name(index),
+            (uint32_t)value);
+
+    change_mask = s->regs[index] ^ value;
+
+    switch (index) {
+    case ECSPI_RXDATA:
+        qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Trying to write to RX FIFO\n",
+                      TYPE_IMX_SPI, __func__);
+        break;
+    case ECSPI_TXDATA:
+    case ECSPI_MSGDATA:
+        /* Linux is not using MSGDATA */
+        /* We assume the beavior is idential to TXDATA */
+        if (!imx_spi_is_enabled(s)) {
+            /* Ignore writes if device is disabled */
+            break;
+        } else if (fifo32_is_full(&s->tx_fifo)) {
+            /* Ignore writes if queue is full */
+            break;
+        }
+
+        fifo32_push(&s->tx_fifo, (uint32_t)value);
+
+        if (imx_spi_channel_is_master(s) &&
+            (s->regs[ECSPI_CONREG] & ECSPI_CONREG_SMC)) {
+            /*
+             * Start emitting if current channel is master and SMC bit is
+             * set.
+             */
+            imx_spi_flush_txfifo(s);
+        }
+
+        break;
+    case ECSPI_STATREG:
+        /* the RO and TC bits are write-one-to-clear */
+        value &= ECSPI_STATREG_RO | ECSPI_STATREG_TC;
+        s->regs[ECSPI_STATREG] &= ~value;
+
+        break;
+    case ECSPI_CONREG:
+        s->regs[ECSPI_CONREG] = value;
+
+        if (!imx_spi_is_enabled(s)) {
+            /* device is disabled, so this is a reset */
+            imx_spi_reset(DEVICE(s));
+            return;
+        }
+
+        if (imx_spi_channel_is_master(s)) {
+            int i;
+
+            /* We are in master mode */
+
+            for (i = 0; i < 4; i++) {
+                qemu_set_irq(s->cs_lines[i],
+                             i == imx_spi_selected_channel(s) ? 0 : 1);
+            }
+
+            if ((value & change_mask & ECSPI_CONREG_SMC) &&
+                !fifo32_is_empty(&s->tx_fifo)) {
+                /* SMC bit is set and TX FIFO has some slots filled in */
+                imx_spi_flush_txfifo(s);
+            } else if ((value & change_mask & ECSPI_CONREG_XCH) &&
+                !(value & ECSPI_CONREG_SMC)) {
+                /* This is a request to start emitting */
+                imx_spi_flush_txfifo(s);
+            }
+        }
+
+        break;
+    default:
+        s->regs[index] = value;
+
+        break;
+    }
+
+    imx_spi_update_irq(s);
+}
+
+static const struct MemoryRegionOps imx_spi_ops = {
+    .read = imx_spi_read,
+    .write = imx_spi_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .valid = {
+        /*
+         * Our device would not work correctly if the guest was doing
+         * unaligned access. This might not be a limitation on the real
+         * device but in practice there is no reason for a guest to access
+         * this device unaligned.
+         */
+        .min_access_size = 4,
+        .max_access_size = 4,
+        .unaligned = false,
+    },
+};
+
+static void imx_spi_realize(DeviceState *dev, Error **errp)
+{
+    IMXSPIState *s = IMX_SPI(dev);
+    int i;
+
+    s->bus = ssi_create_bus(dev, "spi");
+
+    memory_region_init_io(&s->iomem, OBJECT(dev), &imx_spi_ops, s,
+                          TYPE_IMX_SPI, 0x1000);
+    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem);
+    sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq);
+
+    ssi_auto_connect_slaves(dev, s->cs_lines, s->bus);
+
+    for (i = 0; i < 4; ++i) {
+        sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->cs_lines[i]);
+    }
+
+    s->burst_length = 0;
+
+    fifo32_create(&s->tx_fifo, ECSPI_FIFO_SIZE);
+    fifo32_create(&s->rx_fifo, ECSPI_FIFO_SIZE);
+}
+
+static void imx_spi_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->realize = imx_spi_realize;
+    dc->vmsd = &vmstate_imx_spi;
+    dc->reset = imx_spi_reset;
+    dc->desc = "i.MX SPI Controller";
+}
+
+static const TypeInfo imx_spi_info = {
+    .name          = TYPE_IMX_SPI,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(IMXSPIState),
+    .class_init    = imx_spi_class_init,
+};
+
+static void imx_spi_register_types(void)
+{
+    type_register_static(&imx_spi_info);
+}
+
+type_init(imx_spi_register_types)
diff --git a/include/hw/ssi/imx_spi.h b/include/hw/ssi/imx_spi.h
new file mode 100644
index 0000000..7103953
--- /dev/null
+++ b/include/hw/ssi/imx_spi.h
@@ -0,0 +1,103 @@ 
+/*
+ * IMX SPI Controller
+ *
+ * Copyright 2016 Jean-Christophe Dubois <jcd@tribudubois.net>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#ifndef IMX_SPI_H
+#define IMX_SPI_H
+
+#include "hw/sysbus.h"
+#include "hw/ssi/ssi.h"
+#include "qemu/bitops.h"
+#include "qemu/fifo32.h"
+
+#define ECSPI_FIFO_SIZE 64
+
+#define ECSPI_RXDATA 0
+#define ECSPI_TXDATA 1
+#define ECSPI_CONREG 2
+#define ECSPI_CONFIGREG 3
+#define ECSPI_INTREG 4
+#define ECSPI_DMAREG 5
+#define ECSPI_STATREG 6
+#define ECSPI_PERIODREG 7
+#define ECSPI_TESTREG 8
+#define ECSPI_MSGDATA 16
+#define ECSPI_MAX 17
+
+/* ECSPI_CONREG */
+#define ECSPI_CONREG_EN (1 << 0)
+#define ECSPI_CONREG_HT (1 << 1)
+#define ECSPI_CONREG_XCH (1 << 2)
+#define ECSPI_CONREG_SMC (1 << 3)
+#define ECSPI_CONREG_CHANNEL_MODE_SHIFT 4
+#define ECSPI_CONREG_CHANNEL_MODE_LENGTH 4
+#define ECSPI_CONREG_DRCTL_SHIFT 16
+#define ECSPI_CONREG_DRCTL_LENGTH 2
+#define ECSPI_CONREG_CHANNEL_SELECT_SHIFT 18
+#define ECSPI_CONREG_CHANNEL_SELECT_LENGTH 2
+#define ECSPI_CONREG_BURST_LENGTH_SHIFT 20
+#define ECSPI_CONREG_BURST_LENGTH_LENGTH 12
+
+/* ECSPI_CONFIGREG */
+#define ECSPI_CONFIGREG_SS_CTL_SHIFT 8
+#define ECSPI_CONFIGREG_SS_CTL_LENGTH 4
+
+/* ECSPI_INTREG */
+#define ECSPI_INTREG_TEEN (1 << 0)
+#define ECSPI_INTREG_TDREN (1 << 1)
+#define ECSPI_INTREG_TFEN (1 << 2)
+#define ECSPI_INTREG_RREN (1 << 3)
+#define ECSPI_INTREG_RDREN (1 << 4)
+#define ECSPI_INTREG_RFEN (1 << 5)
+#define ECSPI_INTREG_ROEN (1 << 6)
+#define ECSPI_INTREG_TCEN (1 << 7)
+
+/* ECSPI_DMAREG */
+#define ECSPI_DMAREG_RXTDEN (1 << 31)
+#define ECSPI_DMAREG_RXDEN (1 << 23)
+#define ECSPI_DMAREG_TEDEN (1 << 7)
+#define ECSPI_DMAREG_RX_THRESHOLD_SHIFT 16
+#define ECSPI_DMAREG_RX_THRESHOLD_LENGTH 6
+
+/* ECSPI_STATREG */
+#define ECSPI_STATREG_TE (1 << 0)
+#define ECSPI_STATREG_TDR (1 << 1)
+#define ECSPI_STATREG_TF (1 << 2)
+#define ECSPI_STATREG_RR (1 << 3)
+#define ECSPI_STATREG_RDR (1 << 4)
+#define ECSPI_STATREG_RF (1 << 5)
+#define ECSPI_STATREG_RO (1 << 6)
+#define ECSPI_STATREG_TC (1 << 7)
+
+#define EXTRACT(value, name) extract32(value, name##_SHIFT, name##_LENGTH)
+
+#define TYPE_IMX_SPI "imx.spi"
+#define IMX_SPI(obj) OBJECT_CHECK(IMXSPIState, (obj), TYPE_IMX_SPI)
+
+typedef struct IMXSPIState {
+    /* <private> */
+    SysBusDevice parent_obj;
+
+    /* <public> */
+    MemoryRegion iomem;
+
+    qemu_irq irq;
+
+    qemu_irq cs_lines[4];
+
+    SSIBus *bus;
+
+    uint32_t regs[ECSPI_MAX];
+
+    Fifo32 rx_fifo;
+    Fifo32 tx_fifo;
+
+    int16_t burst_length;
+} IMXSPIState;
+
+#endif /* IMX_SPI_H */