blob: e2a000a49111c9a5d806ca54d11cfc01b4ade28d [file] [log] [blame]
/*
* TI OMAP processor's Multichannel SPI emulation.
*
* Copyright (C) 2007-2009 Nokia Corporation
*
* Original code for OMAP2 by Andrzej Zaborowski <andrew@openedhand.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 or
* (at your option) any later version of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "hw.h"
#include "omap.h"
#include "sysbus.h"
#include "spi.h"
//#define SPI_DEBUG
#ifdef SPI_DEBUG
#define TRACE(fmt,...) fprintf(stderr, "%s@%d: " fmt "\n", __FUNCTION__, \
__LINE__, ##__VA_ARGS__);
#else
#define TRACE(...)
#endif
#define SPI_FIFOSIZE 64
#define SPI_REV_OMAP2420 0x14
#define SPI_REV_OMAP3430 0x21
#define IS_OMAP3_SPI(s) ((s)->revision >= SPI_REV_OMAP3430)
typedef struct omap_mcspi_bus_s {
SPIBus *bus;
qemu_irq irq;
int chnum;
uint8_t revision;
uint32_t sysconfig;
uint32_t systest;
uint32_t irqst;
uint32_t irqen;
uint32_t wken;
uint32_t control;
uint32_t xferlevel;
struct omap_mcspi_fifo_s {
int start;
int len;
int size;
uint8_t buf[SPI_FIFOSIZE];
} tx_fifo, rx_fifo;
int fifo_ch;
int fifo_wcnt;
struct omap_mcspi_ch_s {
qemu_irq txdrq;
qemu_irq rxdrq;
uint32_t tx;
uint32_t rx;
uint32_t config;
uint32_t status;
uint32_t control;
} *ch;
} OMAPSPIBusState;
typedef struct omap_mcspi_s {
SysBusDevice busdev;
int mpu_model;
int buscount;
OMAPSPIBusState *bus;
} OMAPSPIState;
static inline void omap_mcspi_interrupt_update(OMAPSPIBusState *s)
{
qemu_set_irq(s->irq, s->irqst & s->irqen);
}
static inline void omap_mcspi_dmarequest_update(OMAPSPIBusState *s,
int chnum)
{
struct omap_mcspi_ch_s *ch = &s->ch[chnum];
if ((ch->control & 1) && /* EN */
(ch->config & (1 << 14)) && /* DMAW */
(ch->status & (1 << 1)) && /* TXS */
((ch->config >> 12) & 3) != 1) { /* TRM */
if (!IS_OMAP3_SPI(s) ||
!(ch->config & (1 << 27)) || /* FFEW */
s->tx_fifo.len <= (s->xferlevel & 0x3f)) /* AEL */
qemu_irq_raise(ch->txdrq);
else
qemu_irq_lower(ch->txdrq);
}
if ((ch->control & 1) && /* EN */
(ch->config & (1 << 15)) && /* DMAW */
(ch->status & (1 << 0)) && /* RXS */
((ch->config >> 12) & 3) != 2) { /* TRM */
if (!IS_OMAP3_SPI(s) ||
!(ch->config & (1 << 28)) || /* FFER */
s->rx_fifo.len >= ((s->xferlevel >> 8) & 0x3f)) /* AFL */
qemu_irq_raise(ch->rxdrq);
else
qemu_irq_lower(ch->rxdrq);
}
}
static void omap_mcspi_fifo_reset(OMAPSPIBusState *s)
{
struct omap_mcspi_ch_s *ch;
s->tx_fifo.len = 0;
s->rx_fifo.len = 0;
s->tx_fifo.start = 0;
s->rx_fifo.start = 0;
if (s->fifo_ch < 0) {
s->tx_fifo.size = s->rx_fifo.size = 0;
} else {
ch = &s->ch[s->fifo_ch];
s->tx_fifo.size = ((ch->config >> 27) & 1) ? SPI_FIFOSIZE : 0;
s->rx_fifo.size = ((ch->config >> 28) & 1) ? SPI_FIFOSIZE : 0;
if (((ch->config >> 27) & 3) == 3) {
s->tx_fifo.size >>= 1;
s->rx_fifo.size >>= 1;
}
}
}
/* returns next word in FIFO or the n first bytes if there is not
* enough data in FIFO */
static uint32_t omap_mcspi_fifo_get(struct omap_mcspi_fifo_s *s, int wl)
{
uint32_t v, sh;
for (v = 0, sh = 0; wl > 0 && s->len; wl -= 8, s->len--, sh += 8) {
v |= ((uint32_t)s->buf[s->start++]) << sh;
if (s->start >= s->size)
s->start = 0;
}
return v;
}
/* pushes a word to FIFO or the first n bytes of the word if the FIFO
* is too full to hold the full word */
static void omap_mcspi_fifo_put(struct omap_mcspi_fifo_s *s, int wl,
uint32_t v)
{
int p = s->start + s->len;
for (; wl > 0 && s->len < s->size; wl -=8, v >>= 8, s->len++) {
if (p >= s->size)
p -= s->size;
s->buf[p++] = (uint8_t)(v & 0xff);
}
}
static void omap_mcspi_transfer_run(OMAPSPIBusState *s, int chnum)
{
struct omap_mcspi_ch_s *ch = s->ch + chnum;
int trm = (ch->config >> 12) & 3;
int wl;
if (!(ch->control & 1)) /* EN */
return;
if ((ch->status & 1) && trm != 2 && /* RXS */
!(ch->config & (1 << 19))) /* TURBO */
goto intr_update;
if ((ch->status & (1 << 1)) && trm != 1) /* TXS */
goto intr_update;
if (!(s->control & 1) || /* SINGLE */
(ch->config & (1 << 20))) { /* FORCE */
wl = 1 + (0x1f & (ch->config >> 7)); /* WL */
if (!IS_OMAP3_SPI(s) || s->fifo_ch != chnum ||
!((ch->config >> 27) & 3)) { /* FFER | FFEW */
ch->rx = spi_txrx(s->bus, chnum, ch->tx, wl);
} else {
switch ((ch->config >> 27) & 3) {
case 1: /* !FFER, FFEW */
if (trm != 1)
ch->tx = omap_mcspi_fifo_get(&s->tx_fifo, wl);
ch->rx = spi_txrx(s->bus, chnum, ch->tx, wl);
s->fifo_wcnt--;
break;
case 2: /* FFER, !FFEW */
ch->rx = spi_txrx(s->bus, chnum, ch->tx, wl);
if (trm != 2)
omap_mcspi_fifo_put(&s->rx_fifo, wl, ch->rx);
s->fifo_wcnt--;
break;
case 3: /* FFER, FFEW */
while (s->rx_fifo.len < s->rx_fifo.size &&
s->tx_fifo.len && s->fifo_wcnt) {
if (trm != 1)
ch->tx = omap_mcspi_fifo_get(&s->tx_fifo, wl);
ch->rx = spi_txrx(s->bus, chnum, ch->tx, wl);
if (trm != 2)
omap_mcspi_fifo_put(&s->rx_fifo, wl, ch->rx);
s->fifo_wcnt--;
}
break;
default:
break;
}
if ((ch->config & (1 << 28)) && /* FFER */
s->rx_fifo.len >= s->rx_fifo.size)
ch->status |= 1 << 6; /* RXFFF */
ch->status &= ~(1 << 5); /* RXFFE */
ch->status &= ~(1 << 4); /* TXFFF */
if ((ch->config & (1 << 27)) && /* FFEW */
!s->tx_fifo.len)
ch->status |= 1 << 3; /* TXFFE */
if (!s->fifo_wcnt &&
((s->xferlevel >> 16) & 0xffff)) /* WCNT */
s->irqst |= 1 << 17; /* EOW */
}
}
ch->tx = 0;
ch->status |= 1 << 2; /* EOT */
ch->status |= 1 << 1; /* TXS */
if (trm != 2) {
ch->status |= 1; /* RXS */
} else {
ch->status &= ~1; /* RXS */
}
intr_update:
if ((ch->status & 1) && trm != 2 && /* RXS */
!(ch->config & (1 << 19))) /* TURBO */
if (!IS_OMAP3_SPI(s) || s->fifo_ch != chnum ||
!((ch->config >> 28) & 1) || /* FFER */
s->rx_fifo.len >= ((s->xferlevel >> 8) & 0x3f)) /* AFL */
s->irqst |= 1 << (2 + 4 * chnum); /* RX_FULL */
if ((ch->status & (1 << 1)) && trm != 1) /* TXS */
if (!IS_OMAP3_SPI(s) || s->fifo_ch != chnum ||
!((ch->config >> 27) & 1) || /* FFEW */
s->tx_fifo.len <= (s->xferlevel & 0x3f)) /* AEL */
s->irqst |= 1 << (4 * chnum); /* TX_EMPTY */
omap_mcspi_interrupt_update(s);
omap_mcspi_dmarequest_update(s, chnum);
}
static void omap_mcspi_bus_reset(OMAPSPIBusState *s)
{
int ch;
s->sysconfig = 0;
s->systest = 0;
s->irqst = 0;
s->irqen = 0;
s->wken = 0;
s->control = 4;
s->fifo_ch = -1;
omap_mcspi_fifo_reset(s);
for (ch = 0; ch < s->chnum; ch ++) {
s->ch[ch].config = 0x060000;
s->ch[ch].status = 2; /* TXS */
s->ch[ch].control = 0;
omap_mcspi_dmarequest_update(s, ch);
}
omap_mcspi_interrupt_update(s);
}
static uint32_t omap_mcspi_read(void *opaque, target_phys_addr_t addr)
{
OMAPSPIBusState *s = (OMAPSPIBusState *) opaque;
int ch = 0;
uint32_t ret;
switch (addr) {
case 0x00: /* MCSPI_REVISION */
TRACE("REVISION = 0x%08x", s->revision);
return s->revision;
case 0x10: /* MCSPI_SYSCONFIG */
TRACE("SYSCONFIG = 0x%08x", s->sysconfig);
return s->sysconfig;
case 0x14: /* MCSPI_SYSSTATUS */
TRACE("SYSSTATUS = 0x00000001");
return 1; /* RESETDONE */
case 0x18: /* MCSPI_IRQSTATUS */
TRACE("IRQSTATUS = 0x%08x", s->irqst);
return s->irqst;
case 0x1c: /* MCSPI_IRQENABLE */
TRACE("IRQENABLE = 0x%08x", s->irqen);
return s->irqen;
case 0x20: /* MCSPI_WAKEUPENABLE */
TRACE("WAKEUPENABLE = 0x%08x", s->wken);
return s->wken;
case 0x24: /* MCSPI_SYST */
TRACE("SYST = 0x%08x", s->systest);
return s->systest;
case 0x28: /* MCSPI_MODULCTRL */
TRACE("MODULCTRL = 0x%08x", s->control);
return s->control;
case 0x68: ch ++;
case 0x54: ch ++;
case 0x40: ch ++;
case 0x2c: /* MCSPI_CHCONF */
TRACE("CHCONF%d = 0x%08x", ch,
(ch < s->chnum) ? s->ch[ch].config : 0);
return (ch < s->chnum) ? s->ch[ch].config : 0;
case 0x6c: ch ++;
case 0x58: ch ++;
case 0x44: ch ++;
case 0x30: /* MCSPI_CHSTAT */
TRACE("CHSTAT%d = 0x%08x", ch,
(ch < s->chnum) ? s->ch[ch].status : 0);
return (ch < s->chnum) ? s->ch[ch].status : 0;
case 0x70: ch ++;
case 0x5c: ch ++;
case 0x48: ch ++;
case 0x34: /* MCSPI_CHCTRL */
TRACE("CHCTRL%d = 0x%08x", ch,
(ch < s->chnum) ? s->ch[ch].control : 0);
return (ch < s->chnum) ? s->ch[ch].control : 0;
case 0x74: ch ++;
case 0x60: ch ++;
case 0x4c: ch ++;
case 0x38: /* MCSPI_TX */
TRACE("TX%d = 0x%08x", ch,
(ch < s->chnum) ? s->ch[ch].tx : 0);
return (ch < s->chnum) ? s->ch[ch].tx : 0;
case 0x78: ch ++;
case 0x64: ch ++;
case 0x50: ch ++;
case 0x3c: /* MCSPI_RX */
if (ch < s->chnum) {
if (!IS_OMAP3_SPI(s) || ch != s->fifo_ch ||
!(s->ch[ch].config & (1 << 28))) { /* FFER */
s->ch[ch].status &= ~1; /* RXS */
ret = s->ch[ch].rx;
TRACE("RX%d = 0x%08x", ch, ret);
omap_mcspi_transfer_run(s, ch);
return ret;
}
if (!s->rx_fifo.len) {
TRACE("rxfifo underflow!");
} else {
qemu_irq_lower(s->ch[ch].rxdrq);
s->ch[ch].status &= ~(1 << 6); /* RXFFF */
if (((s->ch[ch].config >> 12) & 3) != 2) /* TRM */
ret = omap_mcspi_fifo_get(&s->rx_fifo,
1 + ((s->ch[ch].config >> 7) & 0x1f)); /* WL */
else
ret = s->ch[ch].rx;
TRACE("RX%d = 0x%08x", ch, ret);
if (!s->rx_fifo.len) {
s->ch[ch].status &= ~1; /* RXS */
s->ch[ch].status |= 1 << 5; /* RXFFE */
omap_mcspi_transfer_run(s, ch);
}
return ret;
}
}
TRACE("RX%d = 0x00000000", ch);
return 0;
case 0x7c: /* MCSPI_XFERLEVEL */
if (IS_OMAP3_SPI(s)) {
if ((s->xferlevel >> 16) & 0xffff) /* WCNT */
ret = ((s->xferlevel & 0xffff0000) - (s->fifo_wcnt << 16));
else
ret = ((-s->fifo_wcnt) & 0xffff) << 16;
TRACE("XFERLEVEL = 0x%08x", (s->xferlevel & 0xffff) | ret);
return (s->xferlevel & 0xffff) | ret;
}
break;
default:
break;
}
OMAP_BAD_REG(addr);
return 0;
}
static void omap_mcspi_write(void *opaque, target_phys_addr_t addr,
uint32_t value)
{
OMAPSPIBusState *s = (OMAPSPIBusState *) opaque;
uint32_t old;
int ch = 0;
switch (addr) {
case 0x00: /* MCSPI_REVISION */
case 0x14: /* MCSPI_SYSSTATUS */
case 0x30: /* MCSPI_CHSTAT0 */
case 0x3c: /* MCSPI_RX0 */
case 0x44: /* MCSPI_CHSTAT1 */
case 0x50: /* MCSPI_RX1 */
case 0x58: /* MCSPI_CHSTAT2 */
case 0x64: /* MCSPI_RX2 */
case 0x6c: /* MCSPI_CHSTAT3 */
case 0x78: /* MCSPI_RX3 */
/* silently ignore */
//OMAP_RO_REGV(addr, value);
return;
case 0x10: /* MCSPI_SYSCONFIG */
TRACE("SYSCONFIG = 0x%08x", value);
if (value & (1 << 1)) /* SOFTRESET */
omap_mcspi_bus_reset(s);
s->sysconfig = value & 0x31d;
break;
case 0x18: /* MCSPI_IRQSTATUS */
TRACE("IRQSTATUS = 0x%08x", value);
if (!((s->control & (1 << 3)) && (s->systest & (1 << 11)))) {
s->irqst &= ~value;
omap_mcspi_interrupt_update(s);
}
break;
case 0x1c: /* MCSPI_IRQENABLE */
TRACE("IRQENABLE = 0x%08x", value);
s->irqen = value & (IS_OMAP3_SPI(s) ? 0x3777f : 0x1777f);
omap_mcspi_interrupt_update(s);
break;
case 0x20: /* MCSPI_WAKEUPENABLE */
TRACE("WAKEUPENABLE = 0x%08x", value);
s->wken = value & 1;
break;
case 0x24: /* MCSPI_SYST */
TRACE("SYST = 0x%08x", value);
if (s->control & (1 << 3)) /* SYSTEM_TEST */
if (value & (1 << 11)) { /* SSB */
s->irqst |= 0x1777f;
omap_mcspi_interrupt_update(s);
}
s->systest = value & 0xfff;
break;
case 0x28: /* MCSPI_MODULCTRL */
TRACE("MODULCTRL = 0x%08x", value);
if (value & (1 << 3)) /* SYSTEM_TEST */
if (s->systest & (1 << 11)) { /* SSB */
s->irqst |= IS_OMAP3_SPI(s) ? 0x3777f : 0x1777f;
omap_mcspi_interrupt_update(s);
}
s->control = value & 0xf;
break;
case 0x68: ch ++;
case 0x54: ch ++;
case 0x40: ch ++;
case 0x2c: /* MCSPI_CHCONF */
TRACE("CHCONF%d = 0x%08x", ch, value);
if (ch < s->chnum) {
old = s->ch[ch].config;
s->ch[ch].config = value & (IS_OMAP3_SPI(s)
? 0x3fffffff : 0x7fffff);
if (IS_OMAP3_SPI(s) &&
((value ^ old) & (3 << 27))) { /* FFER | FFEW */
s->fifo_ch = ((value & (3 << 27))) ? ch : -1;
omap_mcspi_fifo_reset(s);
}
if (((value ^ old) & (3 << 14)) || /* DMAR | DMAW */
(IS_OMAP3_SPI(s) &&
((value ^ old) & (3 << 27)))) /* FFER | FFEW */
omap_mcspi_dmarequest_update(s, ch);
if (((value >> 12) & 3) == 3) { /* TRM */
TRACE("invalid TRM value (3)");
}
if (((value >> 7) & 0x1f) < 3) { /* WL */
TRACE("invalid WL value (%i)", (value >> 7) & 0x1f);
}
if (IS_OMAP3_SPI(s) && ((value >> 23) & 1)) { /* SBE */
TRACE("start-bit mode is not supported");
}
}
break;
case 0x70: ch ++;
case 0x5c: ch ++;
case 0x48: ch ++;
case 0x34: /* MCSPI_CHCTRL */
TRACE("CHCTRL%d = 0x%08x", ch, value);
if (ch < s->chnum) {
old = s->ch[ch].control;
s->ch[ch].control = value & (IS_OMAP3_SPI(s) ? 0xff01 : 1);
if (value & ~old & 1) { /* EN */
if (IS_OMAP3_SPI(s) && s->fifo_ch == ch)
omap_mcspi_fifo_reset(s);
omap_mcspi_transfer_run(s, ch);
}
}
break;
case 0x74: ch ++;
case 0x60: ch ++;
case 0x4c: ch ++;
case 0x38: /* MCSPI_TX */
TRACE("TX%d = 0x%08x", ch, value);
if (ch < s->chnum) {
if (!IS_OMAP3_SPI(s) || s->fifo_ch != ch ||
!(s->ch[ch].config & (1 << 27))) { /* FFEW */
s->ch[ch].tx = value;
s->ch[ch].status &= ~0x06; /* EOT | TXS */
omap_mcspi_transfer_run(s, ch);
} else {
if (s->tx_fifo.len >= s->tx_fifo.size) {
TRACE("txfifo overflow!");
} else {
qemu_irq_lower(s->ch[ch].txdrq);
s->ch[ch].status &= ~0x0e; /* TXFFE | EOT | TXS */
if (((s->ch[ch].config >> 12) & 3) != 1) { /* TRM */
omap_mcspi_fifo_put(
&s->tx_fifo,
1 + ((s->ch[ch].config >> 7) & 0x1f), /* WL */
value);
if (s->tx_fifo.len >= s->tx_fifo.size)
s->ch[ch].status |= 1 << 4; /* TXFFF */
if (s->tx_fifo.len >= (s->xferlevel & 0x3f))
omap_mcspi_transfer_run(s, ch);
} else {
s->ch[ch].tx = value;
omap_mcspi_transfer_run(s, ch);
}
}
}
}
break;
case 0x7c: /* MCSPI_XFERLEVEL */
TRACE("XFERLEVEL = 0x%08x", value);
if (IS_OMAP3_SPI(s)) {
if (value != s->xferlevel) {
s->fifo_wcnt = (value >> 16) & 0xffff;
s->xferlevel = value & 0xffff3f3f;
omap_mcspi_fifo_reset(s);
}
} else
OMAP_BAD_REGV(addr, value);
break;
default:
OMAP_BAD_REGV(addr, value);
return;
}
}
static CPUReadMemoryFunc * const omap_mcspi_readfn[] = {
omap_badwidth_read32,
omap_badwidth_read32,
omap_mcspi_read,
};
static CPUWriteMemoryFunc * const omap_mcspi_writefn[] = {
omap_badwidth_write32,
omap_badwidth_write32,
omap_mcspi_write,
};
static void omap_mcspi_reset(DeviceState *qdev)
{
int i;
OMAPSPIState *s = FROM_SYSBUS(OMAPSPIState, sysbus_from_qdev(qdev));
for (i = 0; i < s->buscount; i++) {
omap_mcspi_bus_reset(&s->bus[i]);
}
}
static int omap_mcspi_init(SysBusDevice *busdev)
{
int i, j;
OMAPSPIBusState *bs;
OMAPSPIState *s = FROM_SYSBUS(OMAPSPIState, busdev);
s->buscount = (s->mpu_model < omap3430) ? 2 : 4;
s->bus = g_new0(OMAPSPIBusState, s->buscount);
for (i = 0; i < s->buscount; i++) {
bs = &s->bus[i];
if (s->mpu_model < omap3430) {
bs->revision = SPI_REV_OMAP2420;
bs->chnum = i ? 2 : 4;
} else {
bs->revision = SPI_REV_OMAP3430;
bs->chnum = (i > 2) ? 1 : (i ? 2 : 4);
}
sysbus_init_irq(busdev, &bs->irq);
bs->bus = spi_init_bus(&busdev->qdev, NULL, bs->chnum);
bs->ch = g_new0(struct omap_mcspi_ch_s, bs->chnum);
for (j = 0; j < bs->chnum; j++) {
sysbus_init_irq(busdev, &bs->ch[j].txdrq);
sysbus_init_irq(busdev, &bs->ch[j].rxdrq);
}
sysbus_init_mmio(busdev, 0x1000,
cpu_register_io_memory(omap_mcspi_readfn,
omap_mcspi_writefn, bs,
DEVICE_NATIVE_ENDIAN));
}
return 0;
}
SPIBus *omap_mcspi_bus(DeviceState *qdev, int bus_number)
{
OMAPSPIState *s = FROM_SYSBUS(OMAPSPIState, sysbus_from_qdev(qdev));
if (bus_number < s->buscount) {
return s->bus[bus_number].bus;
}
hw_error("%s: invalid bus number %d\n", __FUNCTION__, bus_number);
}
static SysBusDeviceInfo omap_mcspi_info = {
.init = omap_mcspi_init,
.qdev.name = "omap_mcspi",
.qdev.size = sizeof(OMAPSPIState),
.qdev.reset = omap_mcspi_reset,
.qdev.props = (Property[]) {
DEFINE_PROP_INT32("mpu_model", OMAPSPIState, mpu_model, 0),
DEFINE_PROP_END_OF_LIST()
}
};
static void omap_mcspi_register_device(void)
{
sysbus_register_withprop(&omap_mcspi_info);
}
device_init(omap_mcspi_register_device)