blob: d8a7583ff3413cbd42f65653721d65d1362342b5 [file] [log] [blame]
/*
* 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/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:
sprintf(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;
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)