339 lines
9.2 KiB
C
339 lines
9.2 KiB
C
/*
|
|
* S5L8900 SPI Emulation
|
|
*
|
|
* by cmw
|
|
*/
|
|
|
|
#include "hw/arm/ipod_touch_spi.h"
|
|
|
|
static int apple_spi_word_size(IPodTouchSPIState *s)
|
|
{
|
|
switch (R_CFG_WORD_SIZE(REG(s, R_CFG))) {
|
|
case R_CFG_WORD_SIZE_8B:
|
|
return 1;
|
|
case R_CFG_WORD_SIZE_16B:
|
|
return 2;
|
|
case R_CFG_WORD_SIZE_32B:
|
|
return 4;
|
|
default:
|
|
break;
|
|
}
|
|
g_assert_not_reached();
|
|
}
|
|
|
|
static void apple_spi_update_xfer_tx(IPodTouchSPIState *s)
|
|
{
|
|
if (fifo8_is_empty(&s->tx_fifo)) {
|
|
REG(s, R_STATUS) |= R_STATUS_TXEMPTY;
|
|
}
|
|
}
|
|
|
|
static void apple_spi_update_xfer_rx(IPodTouchSPIState *s)
|
|
{
|
|
if (!fifo8_is_empty(&s->rx_fifo)) {
|
|
REG(s, R_STATUS) |= R_STATUS_RXREADY;
|
|
}
|
|
}
|
|
|
|
static void apple_spi_update_irq(IPodTouchSPIState *s)
|
|
{
|
|
uint32_t irq = 0;
|
|
uint32_t mask = 0;
|
|
|
|
if (REG(s, R_CFG) & R_CFG_IE_RXREADY) {
|
|
mask |= R_STATUS_RXREADY;
|
|
}
|
|
if (REG(s, R_CFG) & R_CFG_IE_TXEMPTY) {
|
|
mask |= R_STATUS_TXEMPTY;
|
|
}
|
|
if (REG(s, R_CFG) & R_CFG_IE_COMPLETE) {
|
|
mask |= R_STATUS_COMPLETE;
|
|
}
|
|
|
|
if (REG(s, R_STATUS) & mask) {
|
|
irq = 1;
|
|
}
|
|
if (irq != s->last_irq) {
|
|
s->last_irq = irq;
|
|
qemu_set_irq(s->irq, irq);
|
|
}
|
|
}
|
|
|
|
static void apple_spi_update_cs(IPodTouchSPIState *s)
|
|
{
|
|
BusState *b = BUS(s->spi);
|
|
BusChild *kid = QTAILQ_FIRST(&b->children);
|
|
if (kid) {
|
|
if (object_dynamic_cast(OBJECT(kid->child), TYPE_IPOD_TOUCH_MULTITOUCH)) {
|
|
qemu_set_irq(qdev_get_gpio_in_named(kid->child, SSI_GPIO_CS, 0),
|
|
(REG(s, R_PIN) & R_PIN_CS) != 0);
|
|
}
|
|
/* La sélection de la NOR conserve son comportement historique. */
|
|
}
|
|
}
|
|
|
|
static void apple_spi_cs_set(void *opaque, int pin, int level)
|
|
{
|
|
IPodTouchSPIState *s = IPOD_TOUCH_SPI(opaque);
|
|
if (level) {
|
|
REG(s, R_PIN) |= R_PIN_CS;
|
|
} else {
|
|
REG(s, R_PIN) &= ~R_PIN_CS;
|
|
}
|
|
apple_spi_update_cs(s);
|
|
}
|
|
|
|
static void apple_spi_run(IPodTouchSPIState *s)
|
|
{
|
|
uint32_t tx;
|
|
uint32_t rx;
|
|
|
|
if (!(REG(s, R_CTRL) & R_CTRL_RUN)) {
|
|
return;
|
|
}
|
|
if (REG(s, R_RXCNT) == 0 && REG(s, R_TXCNT) == 0) {
|
|
return;
|
|
}
|
|
|
|
apple_spi_update_xfer_tx(s);
|
|
|
|
//printf("TX queue: %d, RX queue: %d\n", REG(s, R_TXCNT), REG(s, R_RXCNT));
|
|
//printf("TX buffer size: %d, RX buffer size: %d\n", fifo8_num_used(&s->tx_fifo), fifo8_num_used(&s->rx_fifo));
|
|
|
|
while (REG(s, R_TXCNT) && !fifo8_is_empty(&s->tx_fifo)) {
|
|
tx = (uint32_t)fifo8_pop(&s->tx_fifo);
|
|
rx = ssi_transfer(s->spi, tx);
|
|
REG(s, R_TXCNT)--;
|
|
apple_spi_update_xfer_tx(s);
|
|
if (REG(s, R_RXCNT) > 0) {
|
|
if (fifo8_is_full(&s->rx_fifo)) {
|
|
hw_error("Rx buffer overflow: %d\n", fifo8_num_free(&s->rx_fifo));
|
|
qemu_log_mask(LOG_GUEST_ERROR, "%s: rx overflow\n", __func__);
|
|
REG(s, R_STATUS) |= R_STATUS_RXOVERFLOW;
|
|
} else {
|
|
fifo8_push(&s->rx_fifo, (uint8_t)rx);
|
|
REG(s, R_RXCNT)--;
|
|
apple_spi_update_xfer_rx(s);
|
|
}
|
|
}
|
|
}
|
|
|
|
// fetch the remaining bytes by sending sentinel bytes.
|
|
while (!fifo8_is_full(&s->rx_fifo) && (REG(s, R_RXCNT) > 0) && (REG(s, R_CFG) & R_CFG_AGD)) {
|
|
rx = ssi_transfer(s->spi, 0xff);
|
|
if (fifo8_is_full(&s->rx_fifo)) {
|
|
hw_error("Rx buffer overflow: %d\n", fifo8_num_free(&s->rx_fifo));
|
|
qemu_log_mask(LOG_GUEST_ERROR, "%s: rx overflow\n", __func__);
|
|
REG(s, R_STATUS) |= R_STATUS_RXOVERFLOW;
|
|
break;
|
|
} else {
|
|
fifo8_push(&s->rx_fifo, (uint8_t)rx);
|
|
REG(s, R_RXCNT)--;
|
|
apple_spi_update_xfer_rx(s);
|
|
}
|
|
}
|
|
if (REG(s, R_RXCNT) == 0 && REG(s, R_TXCNT) == 0) {
|
|
REG(s, R_STATUS) |= R_STATUS_COMPLETE;
|
|
}
|
|
|
|
//printf("<after> TX buffer size: %d, RX buffer size: %d\n", fifo8_num_used(&s->tx_fifo), fifo8_num_used(&s->rx_fifo));
|
|
}
|
|
|
|
static uint64_t ipod_touch_spi_read(void *opaque, hwaddr addr, unsigned size)
|
|
{
|
|
IPodTouchSPIState *s = IPOD_TOUCH_SPI(opaque);
|
|
//printf("%s (base %d): read from location 0x%08x\n", __func__, s->base, addr);
|
|
|
|
uint32_t r;
|
|
bool run = false;
|
|
|
|
r = s->regs[addr >> 2];
|
|
switch (addr) {
|
|
case R_RXDATA: {
|
|
const uint8_t *buf = NULL;
|
|
int word_size = apple_spi_word_size(s);
|
|
uint32_t num = 0;
|
|
if (fifo8_is_empty(&s->rx_fifo)) {
|
|
hw_error("Rx buffer underflow\n");
|
|
qemu_log_mask(LOG_GUEST_ERROR, "%s: rx underflow\n", __func__);
|
|
r = 0;
|
|
break;
|
|
}
|
|
buf = fifo8_pop_buf(&s->rx_fifo, word_size, &num);
|
|
memcpy(&r, buf, num);
|
|
|
|
if (fifo8_is_empty(&s->rx_fifo)) {
|
|
run = true;
|
|
}
|
|
break;
|
|
}
|
|
case R_STATUS: {
|
|
int val = 0;
|
|
val |= (fifo8_num_used(&s->tx_fifo) << R_STATUS_TXFIFO_SHIFT);
|
|
val |= (fifo8_num_used(&s->rx_fifo) << R_STATUS_RXFIFO_SHIFT);
|
|
r |= val;
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (run) {
|
|
apple_spi_run(s);
|
|
}
|
|
apple_spi_update_irq(s);
|
|
return r;
|
|
}
|
|
|
|
static void ipod_touch_spi_write(void *opaque, hwaddr addr, uint64_t data, unsigned size)
|
|
{
|
|
IPodTouchSPIState *s = IPOD_TOUCH_SPI(opaque);
|
|
//printf("%s (base %d): writing 0x%08x to 0x%08x\n", __func__, s->base, data, addr);
|
|
|
|
uint32_t r = data;
|
|
uint32_t *mmio = ®(s, addr);
|
|
uint32_t old = *mmio;
|
|
bool cs_flg = false;
|
|
bool run = false;
|
|
|
|
switch (addr) {
|
|
case R_CTRL:
|
|
if (r & R_CTRL_TX_RESET) {
|
|
fifo8_reset(&s->tx_fifo);
|
|
/* Le pilote interrompt aussi les commandes au retour de veille.
|
|
Ne pas réutiliser les octets d'une transaction abandonnée. */
|
|
if (s->mt) {
|
|
qemu_set_irq(qdev_get_gpio_in_named(DEVICE(s->mt), SSI_GPIO_CS, 0), 1);
|
|
}
|
|
}
|
|
if (r & R_CTRL_RX_RESET) {
|
|
fifo8_reset(&s->rx_fifo);
|
|
}
|
|
if (r & R_CTRL_RUN && !fifo8_is_empty(&s->tx_fifo)) {
|
|
run = true;
|
|
}
|
|
break;
|
|
case R_STATUS:
|
|
run = true;
|
|
r = old & (~r);
|
|
break;
|
|
case R_PIN:
|
|
cs_flg = true;
|
|
break;
|
|
case R_TXDATA ... R_TXDATA + 3: {
|
|
int word_size = apple_spi_word_size(s);
|
|
if (fifo8_is_full(&s->tx_fifo) || fifo8_num_free(&s->tx_fifo) < word_size) {
|
|
hw_error("Tx buffer overflow: %d\n", fifo8_num_free(&s->tx_fifo));
|
|
}
|
|
fifo8_push_all(&s->tx_fifo, (uint8_t *)&r, word_size);
|
|
break;
|
|
case R_CFG:
|
|
run = true;
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
*mmio = r;
|
|
if (cs_flg) {
|
|
apple_spi_update_cs(s);
|
|
}
|
|
if (run) {
|
|
apple_spi_run(s);
|
|
}
|
|
|
|
if(addr == R_STATUS) {
|
|
apple_spi_update_xfer_tx(s);
|
|
apple_spi_update_xfer_rx(s);
|
|
}
|
|
|
|
apple_spi_update_irq(s);
|
|
}
|
|
|
|
static const MemoryRegionOps spi_ops = {
|
|
.read = ipod_touch_spi_read,
|
|
.write = ipod_touch_spi_write,
|
|
.endianness = DEVICE_NATIVE_ENDIAN,
|
|
};
|
|
|
|
static void ipod_touch_spi_reset(DeviceState *d)
|
|
{
|
|
IPodTouchSPIState *s = (IPodTouchSPIState *)d;
|
|
memset(s->regs, 0, sizeof(s->regs));
|
|
fifo8_reset(&s->tx_fifo);
|
|
fifo8_reset(&s->rx_fifo);
|
|
s->last_irq = 0;
|
|
qemu_irq_lower(s->irq);
|
|
}
|
|
|
|
static uint32_t base_addr = 0;
|
|
|
|
void set_spi_base(uint32_t base)
|
|
{
|
|
base_addr = base;
|
|
}
|
|
|
|
static void ipod_touch_spi_realize(DeviceState *dev, struct Error **errp)
|
|
{
|
|
IPodTouchSPIState *s = IPOD_TOUCH_SPI(dev);
|
|
SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
|
|
|
|
char bus_name[32] = { 0 };
|
|
snprintf(bus_name, sizeof(bus_name), "%s.bus", dev->id);
|
|
s->spi = ssi_create_bus(dev, (const char *)bus_name);
|
|
|
|
sysbus_init_irq(sbd, &s->irq);
|
|
sysbus_init_irq(sbd, &s->cs_line);
|
|
qdev_init_gpio_in_named(dev, apple_spi_cs_set, SSI_GPIO_CS, 1);
|
|
char name[5];
|
|
snprintf(name, 5, "spi%d", base_addr);
|
|
memory_region_init_io(&s->iomem, OBJECT(s), &spi_ops, s, name, 0x100);
|
|
sysbus_init_mmio(sbd, &s->iomem);
|
|
s->base = base_addr;
|
|
|
|
fifo8_create(&s->tx_fifo, R_FIFO_TX_DEPTH);
|
|
fifo8_create(&s->rx_fifo, R_FIFO_RX_DEPTH);
|
|
|
|
// create the peripheral
|
|
switch(s->base) {
|
|
case 0:
|
|
{
|
|
DeviceState *dev = ssi_create_peripheral(s->spi, TYPE_IPOD_TOUCH_NOR_SPI);
|
|
IPodTouchNORSPIState *nor = IPOD_TOUCH_NOR_SPI(dev);
|
|
s->nor = nor;
|
|
break;
|
|
}
|
|
case 1:
|
|
break;
|
|
case 4:
|
|
{
|
|
DeviceState *dev = ssi_create_peripheral(s->spi, TYPE_IPOD_TOUCH_MULTITOUCH);
|
|
IPodTouchMultitouchState *mt = IPOD_TOUCH_MULTITOUCH(dev);
|
|
s->mt = mt;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void ipod_touch_spi_class_init(ObjectClass *klass, void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
dc->realize = ipod_touch_spi_realize;
|
|
dc->reset = ipod_touch_spi_reset;
|
|
}
|
|
|
|
static const TypeInfo ipod_touch_spi_info = {
|
|
.name = TYPE_IPOD_TOUCH_SPI,
|
|
.parent = TYPE_SYS_BUS_DEVICE,
|
|
.instance_size = sizeof(IPodTouchSPIState),
|
|
.class_init = ipod_touch_spi_class_init,
|
|
};
|
|
|
|
static void ipod_touch_spi_register_types(void)
|
|
{
|
|
type_register_static(&ipod_touch_spi_info);
|
|
}
|
|
|
|
type_init(ipod_touch_spi_register_types)
|