| /* | 
 |  * Arm PrimeCell PL022 Synchronous Serial Port | 
 |  * | 
 |  * Copyright (c) 2007 CodeSourcery. | 
 |  * Written by Paul Brook | 
 |  * | 
 |  * This code is licensed under the GPL. | 
 |  */ | 
 |  | 
 | #include "hw/sysbus.h" | 
 | #include "hw/ssi.h" | 
 |  | 
 | //#define DEBUG_PL022 1 | 
 |  | 
 | #ifdef DEBUG_PL022 | 
 | #define DPRINTF(fmt, ...) \ | 
 | do { printf("pl022: " fmt , ## __VA_ARGS__); } while (0) | 
 | #define BADF(fmt, ...) \ | 
 | do { fprintf(stderr, "pl022: error: " fmt , ## __VA_ARGS__); exit(1);} while (0) | 
 | #else | 
 | #define DPRINTF(fmt, ...) do {} while(0) | 
 | #define BADF(fmt, ...) \ | 
 | do { fprintf(stderr, "pl022: error: " fmt , ## __VA_ARGS__);} while (0) | 
 | #endif | 
 |  | 
 | #define PL022_CR1_LBM 0x01 | 
 | #define PL022_CR1_SSE 0x02 | 
 | #define PL022_CR1_MS  0x04 | 
 | #define PL022_CR1_SDO 0x08 | 
 |  | 
 | #define PL022_SR_TFE  0x01 | 
 | #define PL022_SR_TNF  0x02 | 
 | #define PL022_SR_RNE  0x04 | 
 | #define PL022_SR_RFF  0x08 | 
 | #define PL022_SR_BSY  0x10 | 
 |  | 
 | #define PL022_INT_ROR 0x01 | 
 | #define PL022_INT_RT  0x04 | 
 | #define PL022_INT_RX  0x04 | 
 | #define PL022_INT_TX  0x08 | 
 |  | 
 | #define TYPE_PL022 "pl022" | 
 | #define PL022(obj) OBJECT_CHECK(PL022State, (obj), TYPE_PL022) | 
 |  | 
 | typedef struct PL022State { | 
 |     SysBusDevice parent_obj; | 
 |  | 
 |     MemoryRegion iomem; | 
 |     uint32_t cr0; | 
 |     uint32_t cr1; | 
 |     uint32_t bitmask; | 
 |     uint32_t sr; | 
 |     uint32_t cpsr; | 
 |     uint32_t is; | 
 |     uint32_t im; | 
 |     /* The FIFO head points to the next empty entry.  */ | 
 |     int tx_fifo_head; | 
 |     int rx_fifo_head; | 
 |     int tx_fifo_len; | 
 |     int rx_fifo_len; | 
 |     uint16_t tx_fifo[8]; | 
 |     uint16_t rx_fifo[8]; | 
 |     qemu_irq irq; | 
 |     SSIBus *ssi; | 
 | } PL022State; | 
 |  | 
 | static const unsigned char pl022_id[8] = | 
 |   { 0x22, 0x10, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1 }; | 
 |  | 
 | static void pl022_update(PL022State *s) | 
 | { | 
 |     s->sr = 0; | 
 |     if (s->tx_fifo_len == 0) | 
 |         s->sr |= PL022_SR_TFE; | 
 |     if (s->tx_fifo_len != 8) | 
 |         s->sr |= PL022_SR_TNF; | 
 |     if (s->rx_fifo_len != 0) | 
 |         s->sr |= PL022_SR_RNE; | 
 |     if (s->rx_fifo_len == 8) | 
 |         s->sr |= PL022_SR_RFF; | 
 |     if (s->tx_fifo_len) | 
 |         s->sr |= PL022_SR_BSY; | 
 |     s->is = 0; | 
 |     if (s->rx_fifo_len >= 4) | 
 |         s->is |= PL022_INT_RX; | 
 |     if (s->tx_fifo_len <= 4) | 
 |         s->is |= PL022_INT_TX; | 
 |  | 
 |     qemu_set_irq(s->irq, (s->is & s->im) != 0); | 
 | } | 
 |  | 
 | static void pl022_xfer(PL022State *s) | 
 | { | 
 |     int i; | 
 |     int o; | 
 |     int val; | 
 |  | 
 |     if ((s->cr1 & PL022_CR1_SSE) == 0) { | 
 |         pl022_update(s); | 
 |         DPRINTF("Disabled\n"); | 
 |         return; | 
 |     } | 
 |  | 
 |     DPRINTF("Maybe xfer %d/%d\n", s->tx_fifo_len, s->rx_fifo_len); | 
 |     i = (s->tx_fifo_head - s->tx_fifo_len) & 7; | 
 |     o = s->rx_fifo_head; | 
 |     /* ??? We do not emulate the line speed. | 
 |        This may break some applications.  The are two problematic cases: | 
 |         (a) A driver feeds data into the TX FIFO until it is full, | 
 |          and only then drains the RX FIFO.  On real hardware the CPU can | 
 |          feed data fast enough that the RX fifo never gets chance to overflow. | 
 |         (b) A driver transmits data, deliberately allowing the RX FIFO to | 
 |          overflow because it ignores the RX data anyway. | 
 |  | 
 |        We choose to support (a) by stalling the transmit engine if it would | 
 |        cause the RX FIFO to overflow.  In practice much transmit-only code | 
 |        falls into (a) because it flushes the RX FIFO to determine when | 
 |        the transfer has completed.  */ | 
 |     while (s->tx_fifo_len && s->rx_fifo_len < 8) { | 
 |         DPRINTF("xfer\n"); | 
 |         val = s->tx_fifo[i]; | 
 |         if (s->cr1 & PL022_CR1_LBM) { | 
 |             /* Loopback mode.  */ | 
 |         } else { | 
 |             val = ssi_transfer(s->ssi, val); | 
 |         } | 
 |         s->rx_fifo[o] = val & s->bitmask; | 
 |         i = (i + 1) & 7; | 
 |         o = (o + 1) & 7; | 
 |         s->tx_fifo_len--; | 
 |         s->rx_fifo_len++; | 
 |     } | 
 |     s->rx_fifo_head = o; | 
 |     pl022_update(s); | 
 | } | 
 |  | 
 | static uint64_t pl022_read(void *opaque, hwaddr offset, | 
 |                            unsigned size) | 
 | { | 
 |     PL022State *s = (PL022State *)opaque; | 
 |     int val; | 
 |  | 
 |     if (offset >= 0xfe0 && offset < 0x1000) { | 
 |         return pl022_id[(offset - 0xfe0) >> 2]; | 
 |     } | 
 |     switch (offset) { | 
 |     case 0x00: /* CR0 */ | 
 |       return s->cr0; | 
 |     case 0x04: /* CR1 */ | 
 |       return s->cr1; | 
 |     case 0x08: /* DR */ | 
 |         if (s->rx_fifo_len) { | 
 |             val = s->rx_fifo[(s->rx_fifo_head - s->rx_fifo_len) & 7]; | 
 |             DPRINTF("RX %02x\n", val); | 
 |             s->rx_fifo_len--; | 
 |             pl022_xfer(s); | 
 |         } else { | 
 |             val = 0; | 
 |         } | 
 |         return val; | 
 |     case 0x0c: /* SR */ | 
 |         return s->sr; | 
 |     case 0x10: /* CPSR */ | 
 |         return s->cpsr; | 
 |     case 0x14: /* IMSC */ | 
 |         return s->im; | 
 |     case 0x18: /* RIS */ | 
 |         return s->is; | 
 |     case 0x1c: /* MIS */ | 
 |         return s->im & s->is; | 
 |     case 0x20: /* DMACR */ | 
 |         /* Not implemented.  */ | 
 |         return 0; | 
 |     default: | 
 |         qemu_log_mask(LOG_GUEST_ERROR, | 
 |                       "pl022_read: Bad offset %x\n", (int)offset); | 
 |         return 0; | 
 |     } | 
 | } | 
 |  | 
 | static void pl022_write(void *opaque, hwaddr offset, | 
 |                         uint64_t value, unsigned size) | 
 | { | 
 |     PL022State *s = (PL022State *)opaque; | 
 |  | 
 |     switch (offset) { | 
 |     case 0x00: /* CR0 */ | 
 |         s->cr0 = value; | 
 |         /* Clock rate and format are ignored.  */ | 
 |         s->bitmask = (1 << ((value & 15) + 1)) - 1; | 
 |         break; | 
 |     case 0x04: /* CR1 */ | 
 |         s->cr1 = value; | 
 |         if ((s->cr1 & (PL022_CR1_MS | PL022_CR1_SSE)) | 
 |                    == (PL022_CR1_MS | PL022_CR1_SSE)) { | 
 |             BADF("SPI slave mode not implemented\n"); | 
 |         } | 
 |         pl022_xfer(s); | 
 |         break; | 
 |     case 0x08: /* DR */ | 
 |         if (s->tx_fifo_len < 8) { | 
 |             DPRINTF("TX %02x\n", (unsigned)value); | 
 |             s->tx_fifo[s->tx_fifo_head] = value & s->bitmask; | 
 |             s->tx_fifo_head = (s->tx_fifo_head + 1) & 7; | 
 |             s->tx_fifo_len++; | 
 |             pl022_xfer(s); | 
 |         } | 
 |         break; | 
 |     case 0x10: /* CPSR */ | 
 |         /* Prescaler.  Ignored.  */ | 
 |         s->cpsr = value & 0xff; | 
 |         break; | 
 |     case 0x14: /* IMSC */ | 
 |         s->im = value; | 
 |         pl022_update(s); | 
 |         break; | 
 |     case 0x20: /* DMACR */ | 
 |         if (value) { | 
 |             qemu_log_mask(LOG_UNIMP, "pl022: DMA not implemented\n"); | 
 |         } | 
 |         break; | 
 |     default: | 
 |         qemu_log_mask(LOG_GUEST_ERROR, | 
 |                       "pl022_write: Bad offset %x\n", (int)offset); | 
 |     } | 
 | } | 
 |  | 
 | static void pl022_reset(PL022State *s) | 
 | { | 
 |     s->rx_fifo_len = 0; | 
 |     s->tx_fifo_len = 0; | 
 |     s->im = 0; | 
 |     s->is = PL022_INT_TX; | 
 |     s->sr = PL022_SR_TFE | PL022_SR_TNF; | 
 | } | 
 |  | 
 | static const MemoryRegionOps pl022_ops = { | 
 |     .read = pl022_read, | 
 |     .write = pl022_write, | 
 |     .endianness = DEVICE_NATIVE_ENDIAN, | 
 | }; | 
 |  | 
 | static int pl022_post_load(void *opaque, int version_id) | 
 | { | 
 |     PL022State *s = opaque; | 
 |  | 
 |     if (s->tx_fifo_head < 0 || | 
 |         s->tx_fifo_head >= ARRAY_SIZE(s->tx_fifo) || | 
 |         s->rx_fifo_head < 0 || | 
 |         s->rx_fifo_head >= ARRAY_SIZE(s->rx_fifo)) { | 
 |         return -1; | 
 |     } | 
 |     return 0; | 
 | } | 
 |  | 
 | static const VMStateDescription vmstate_pl022 = { | 
 |     .name = "pl022_ssp", | 
 |     .version_id = 1, | 
 |     .minimum_version_id = 1, | 
 |     .post_load = pl022_post_load, | 
 |     .fields = (VMStateField[]) { | 
 |         VMSTATE_UINT32(cr0, PL022State), | 
 |         VMSTATE_UINT32(cr1, PL022State), | 
 |         VMSTATE_UINT32(bitmask, PL022State), | 
 |         VMSTATE_UINT32(sr, PL022State), | 
 |         VMSTATE_UINT32(cpsr, PL022State), | 
 |         VMSTATE_UINT32(is, PL022State), | 
 |         VMSTATE_UINT32(im, PL022State), | 
 |         VMSTATE_INT32(tx_fifo_head, PL022State), | 
 |         VMSTATE_INT32(rx_fifo_head, PL022State), | 
 |         VMSTATE_INT32(tx_fifo_len, PL022State), | 
 |         VMSTATE_INT32(rx_fifo_len, PL022State), | 
 |         VMSTATE_UINT16(tx_fifo[0], PL022State), | 
 |         VMSTATE_UINT16(rx_fifo[0], PL022State), | 
 |         VMSTATE_UINT16(tx_fifo[1], PL022State), | 
 |         VMSTATE_UINT16(rx_fifo[1], PL022State), | 
 |         VMSTATE_UINT16(tx_fifo[2], PL022State), | 
 |         VMSTATE_UINT16(rx_fifo[2], PL022State), | 
 |         VMSTATE_UINT16(tx_fifo[3], PL022State), | 
 |         VMSTATE_UINT16(rx_fifo[3], PL022State), | 
 |         VMSTATE_UINT16(tx_fifo[4], PL022State), | 
 |         VMSTATE_UINT16(rx_fifo[4], PL022State), | 
 |         VMSTATE_UINT16(tx_fifo[5], PL022State), | 
 |         VMSTATE_UINT16(rx_fifo[5], PL022State), | 
 |         VMSTATE_UINT16(tx_fifo[6], PL022State), | 
 |         VMSTATE_UINT16(rx_fifo[6], PL022State), | 
 |         VMSTATE_UINT16(tx_fifo[7], PL022State), | 
 |         VMSTATE_UINT16(rx_fifo[7], PL022State), | 
 |         VMSTATE_END_OF_LIST() | 
 |     } | 
 | }; | 
 |  | 
 | static int pl022_init(SysBusDevice *sbd) | 
 | { | 
 |     DeviceState *dev = DEVICE(sbd); | 
 |     PL022State *s = PL022(dev); | 
 |  | 
 |     memory_region_init_io(&s->iomem, OBJECT(s), &pl022_ops, s, "pl022", 0x1000); | 
 |     sysbus_init_mmio(sbd, &s->iomem); | 
 |     sysbus_init_irq(sbd, &s->irq); | 
 |     s->ssi = ssi_create_bus(dev, "ssi"); | 
 |     pl022_reset(s); | 
 |     vmstate_register(dev, -1, &vmstate_pl022, s); | 
 |     return 0; | 
 | } | 
 |  | 
 | static void pl022_class_init(ObjectClass *klass, void *data) | 
 | { | 
 |     SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass); | 
 |  | 
 |     sdc->init = pl022_init; | 
 | } | 
 |  | 
 | static const TypeInfo pl022_info = { | 
 |     .name          = TYPE_PL022, | 
 |     .parent        = TYPE_SYS_BUS_DEVICE, | 
 |     .instance_size = sizeof(PL022State), | 
 |     .class_init    = pl022_class_init, | 
 | }; | 
 |  | 
 | static void pl022_register_types(void) | 
 | { | 
 |     type_register_static(&pl022_info); | 
 | } | 
 |  | 
 | type_init(pl022_register_types) |