From patchwork Tue Mar 1 22:27:47 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jean-Christophe Dubois X-Patchwork-Id: 8471031 Return-Path: X-Original-To: patchwork-qemu-devel@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork2.web.kernel.org (Postfix) with ESMTP id 397EEC0553 for ; Tue, 1 Mar 2016 22:31:32 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id DF75520351 for ; Tue, 1 Mar 2016 22:31:30 +0000 (UTC) Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 7547E2034A for ; Tue, 1 Mar 2016 22:31:29 +0000 (UTC) Received: from localhost ([::1]:52633 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1aaspE-00019S-Ro for patchwork-qemu-devel@patchwork.kernel.org; Tue, 01 Mar 2016 17:31:28 -0500 Received: from eggs.gnu.org ([2001:4830:134:3::10]:47849) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1aaslm-0003Wj-6r for qemu-devel@nongnu.org; Tue, 01 Mar 2016 17:27:58 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1aasli-0004oU-UV for qemu-devel@nongnu.org; Tue, 01 Mar 2016 17:27:54 -0500 Received: from zose-mta02.web4all.fr ([185.49.20.43]:42579) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1aasli-0004nY-Jp for qemu-devel@nongnu.org; Tue, 01 Mar 2016 17:27:50 -0500 Received: from localhost (localhost [127.0.0.1]) by zose-mta02.web4all.fr (Postfix) with ESMTP id 1D182416C0; Tue, 1 Mar 2016 23:27:50 +0100 (CET) Received: from zose-mta02.web4all.fr ([127.0.0.1]) by localhost (zose-mta02.web4all.fr [127.0.0.1]) (amavisd-new, port 10032) with ESMTP id 1VE7DXKwQhM8; Tue, 1 Mar 2016 23:27:49 +0100 (CET) Received: from localhost (localhost [127.0.0.1]) by zose-mta02.web4all.fr (Postfix) with ESMTP id 191314084F; Tue, 1 Mar 2016 23:27:49 +0100 (CET) X-Virus-Scanned: amavisd-new at zose-mta-02.w4a.fr Received: from zose-mta02.web4all.fr ([127.0.0.1]) by localhost (zose-mta02.web4all.fr [127.0.0.1]) (amavisd-new, port 10026) with ESMTP id Q4Jzv1cTUD5T; Tue, 1 Mar 2016 23:27:49 +0100 (CET) Received: from localhost.localdomain (smm49-1-78-235-240-156.fbx.proxad.net [78.235.240.156]) by zose-mta02.web4all.fr (Postfix) with ESMTPSA id A3775406EB; Tue, 1 Mar 2016 23:27:48 +0100 (CET) From: Jean-Christophe Dubois To: qemu-devel@nongnu.org, peter.maydell@linaro.org, crosthwaite.peter@gmail.com Date: Tue, 1 Mar 2016 23:27:47 +0100 Message-Id: X-Mailer: git-send-email 2.5.0 In-Reply-To: References: X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 185.49.20.43 Cc: Jean-Christophe Dubois Subject: [Qemu-devel] [PATCH v3 10/12] i.MX: Add the Freescale SPI Controller X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+patchwork-qemu-devel=patchwork.kernel.org@nongnu.org Sender: qemu-devel-bounces+patchwork-qemu-devel=patchwork.kernel.org@nongnu.org X-Spam-Status: No, score=-6.9 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_HI, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Signed-off-by: Jean-Christophe Dubois Reviewed-by: Peter Maydell --- 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 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 + * + * 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 + * + * 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 { + /* */ + SysBusDevice parent_obj; + + /* */ + 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 */