/* * 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/irq.h" #include "hw/ssi/imx_spi.h" #include "migration/vmstate.h" #include "qemu/log.h" #include "qemu/module.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 const char *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: snprintf(unknown, sizeof(unknown), "%u ?", reg); return unknown; } } static const VMStateDescription vmstate_imx_spi = { .name = TYPE_IMX_SPI, .version_id = 1, .minimum_version_id = 1, .fields = (const 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) { uint32_t burst; burst = EXTRACT(s->regs[ECSPI_CONREG], ECSPI_CONREG_BURST_LENGTH) + 1; if (burst % 8) { burst = ROUND_UP(burst, 8); } return burst; } 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; 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 = (s->burst_length % 32) ? : 32; rx = 0; while (tx_burst > 0) { uint8_t byte = tx >> (tx_burst - 8); 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); rx = (rx << 8) | byte; /* Remove 8 bits from the actual burst */ tx_burst -= 8; s->burst_length -= 8; } 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, rx); } if (s->burst_length <= 0) { 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; s->regs[ECSPI_CONREG] &= ~ECSPI_CONREG_XCH; } /* 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_common_reset(IMXSPIState *s) { int i; for (i = 0; i < ARRAY_SIZE(s->regs); i++) { switch (i) { case ECSPI_CONREG: /* CONREG is not updated on soft reset */ break; case ECSPI_STATREG: s->regs[i] = 0x00000003; break; default: s->regs[i] = 0; break; } } imx_spi_rxfifo_reset(s); imx_spi_txfifo_reset(s); s->burst_length = 0; } static void imx_spi_soft_reset(IMXSPIState *s) { int i; imx_spi_common_reset(s); imx_spi_update_irq(s); for (i = 0; i < ECSPI_NUM_CS; i++) { qemu_set_irq(s->cs_lines[i], 1); } } static void imx_spi_reset(DeviceState *dev) { IMXSPIState *s = IMX_SPI(dev); imx_spi_common_reset(s); s->regs[ECSPI_CONREG] = 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; } value = s->regs[index]; if (imx_spi_is_enabled(s)) { switch (index) { case ECSPI_RXDATA: 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: break; } imx_spi_update_irq(s); } DPRINTF("reg[%s] => 0x%" PRIx32 "\n", imx_spi_reg_name(index), value); 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; uint32_t burst; 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); if (!imx_spi_is_enabled(s)) { /* Block is disabled */ if (index != ECSPI_CONREG) { /* Ignore access */ return; } } 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: 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; burst = EXTRACT(s->regs[ECSPI_CONREG], ECSPI_CONREG_BURST_LENGTH) + 1; if (burst % 8) { qemu_log_mask(LOG_UNIMP, "[%s]%s: burst length %d not supported: rounding up to next multiple of 8\n", TYPE_IMX_SPI, __func__, burst); } if (!imx_spi_is_enabled(s)) { /* device is disabled, so this is a soft reset */ imx_spi_soft_reset(s); return; } if (imx_spi_channel_is_master(s)) { int i; /* We are in master mode */ for (i = 0; i < ECSPI_NUM_CS; 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; case ECSPI_MSGDATA: /* it is not clear from the spec what MSGDATA is for */ /* Anyway it is not used by Linux driver */ /* So for now we just ignore it */ qemu_log_mask(LOG_UNIMP, "[%s]%s: Trying to write to MSGDATA, ignoring\n", TYPE_IMX_SPI, __func__); 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); for (i = 0; i < ECSPI_NUM_CS; ++i) { sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->cs_lines[i]); } 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; device_class_set_legacy_reset(dc, 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)