From 6b91f0155b9338b7fa53d1d64f4e40c43a5d3bba Mon Sep 17 00:00:00 2001 From: Peter Crosthwaite Date: Mon, 3 Jun 2013 17:17:41 +0100 Subject: [PATCH 01/24] xilinx_spips: seperate SPI and QSPI as two classes Make SPI and QSPI different classes. QSPIPS is setup as a child of SPIPS. Only QSPI has the LQSPI functionality, so move all that to the child class. Signed-off-by: Peter Crosthwaite Reviewed-by: Peter Maydell Reviewed-by: Edgar E. Iglesias Message-id: 2cdd0cadb5ba77ca02fde5cae627852dc9a64c71.1369117359.git.peter.crosthwaite@xilinx.com Signed-off-by: Peter Maydell --- hw/arm/xilinx_zynq.c | 2 +- hw/ssi/xilinx_spips.c | 69 ++++++++++++++++++++++++++++++++++--------- 2 files changed, 56 insertions(+), 15 deletions(-) diff --git a/hw/arm/xilinx_zynq.c b/hw/arm/xilinx_zynq.c index 41505c32ba..4602a6f579 100644 --- a/hw/arm/xilinx_zynq.c +++ b/hw/arm/xilinx_zynq.c @@ -66,7 +66,7 @@ static inline void zynq_init_spi_flashes(uint32_t base_addr, qemu_irq irq, int num_busses = is_qspi ? NUM_QSPI_BUSSES : 1; int num_ss = is_qspi ? NUM_QSPI_FLASHES : NUM_SPI_FLASHES; - dev = qdev_create(NULL, "xilinx,spips"); + dev = qdev_create(NULL, is_qspi ? "xlnx.ps7-qspi" : "xlnx.ps7-spi"); qdev_prop_set_uint8(dev, "num-txrx-bytes", is_qspi ? 4 : 1); qdev_prop_set_uint8(dev, "num-ss-bits", num_ss); qdev_prop_set_uint8(dev, "num-busses", num_busses); diff --git a/hw/ssi/xilinx_spips.c b/hw/ssi/xilinx_spips.c index b2397f4a42..734adf0ea7 100644 --- a/hw/ssi/xilinx_spips.c +++ b/hw/ssi/xilinx_spips.c @@ -129,7 +129,8 @@ typedef enum { } FlashCMD; typedef struct { - SysBusDevice busdev; + SysBusDevice parent_obj; + MemoryRegion iomem; MemoryRegion mmlqspi; @@ -149,15 +150,23 @@ typedef struct { uint8_t num_txrx_bytes; uint32_t regs[R_MAX]; +} XilinxSPIPS; + +typedef struct { + XilinxSPIPS parent_obj; uint32_t lqspi_buf[LQSPI_CACHE_SIZE]; hwaddr lqspi_cached_addr; -} XilinxSPIPS; +} XilinxQSPIPS; -#define TYPE_XILINX_SPIPS "xilinx,spips" + +#define TYPE_XILINX_SPIPS "xlnx.ps7-spi" +#define TYPE_XILINX_QSPIPS "xlnx.ps7-qspi" #define XILINX_SPIPS(obj) \ OBJECT_CHECK(XilinxSPIPS, (obj), TYPE_XILINX_SPIPS) +#define XILINX_QSPIPS(obj) \ + OBJECT_CHECK(XilinxQSPIPS, (obj), TYPE_XILINX_QSPIPS) static inline int num_effective_busses(XilinxSPIPS *s) { @@ -436,11 +445,12 @@ static uint64_t lqspi_read(void *opaque, hwaddr addr, unsigned int size) { int i; + XilinxQSPIPS *q = opaque; XilinxSPIPS *s = opaque; - if (addr >= s->lqspi_cached_addr && - addr <= s->lqspi_cached_addr + LQSPI_CACHE_SIZE - 4) { - return s->lqspi_buf[(addr - s->lqspi_cached_addr) >> 2]; + if (addr >= q->lqspi_cached_addr && + addr <= q->lqspi_cached_addr + LQSPI_CACHE_SIZE - 4) { + return q->lqspi_buf[(addr - q->lqspi_cached_addr) >> 2]; } else { int flash_addr = (addr / num_effective_busses(s)); int slave = flash_addr >> LQSPI_ADDRESS_BITS; @@ -484,14 +494,14 @@ lqspi_read(void *opaque, hwaddr addr, unsigned int size) for (i = 0; i < LQSPI_CACHE_SIZE / 4; ++i) { tx_data_bytes(s, 0, 4); xilinx_spips_flush_txfifo(s); - rx_data_bytes(s, &s->lqspi_buf[cache_entry], 4); + rx_data_bytes(s, &q->lqspi_buf[cache_entry], 4); cache_entry++; } s->regs[R_CONFIG] |= CS; xilinx_spips_update_cs_lines(s); - s->lqspi_cached_addr = addr; + q->lqspi_cached_addr = addr; return lqspi_read(opaque, addr, size); } } @@ -511,7 +521,7 @@ static void xilinx_spips_realize(DeviceState *dev, Error **errp) SysBusDevice *sbd = SYS_BUS_DEVICE(dev); int i; - DB_PRINT("inited device model\n"); + DB_PRINT("realized spips\n"); s->spi = g_new(SSIBus *, s->num_busses); for (i = 0; i < s->num_busses; ++i) { @@ -531,15 +541,30 @@ static void xilinx_spips_realize(DeviceState *dev, Error **errp) memory_region_init_io(&s->iomem, &spips_ops, s, "spi", R_MAX*4); sysbus_init_mmio(sbd, &s->iomem); + s->irqline = -1; + + fifo8_create(&s->rx_fifo, RXFF_A); + fifo8_create(&s->tx_fifo, TXFF_A); +} + +static void xilinx_qspips_realize(DeviceState *dev, Error **errp) +{ + XilinxSPIPS *s = XILINX_SPIPS(dev); + XilinxQSPIPS *q = XILINX_QSPIPS(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + + DB_PRINT("realized qspips\n"); + + s->num_busses = 2; + s->num_cs = 2; + s->num_txrx_bytes = 4; + + xilinx_spips_realize(dev, errp); memory_region_init_io(&s->mmlqspi, &lqspi_ops, s, "lqspi", (1 << LQSPI_ADDRESS_BITS) * 2); sysbus_init_mmio(sbd, &s->mmlqspi); - s->irqline = -1; - s->lqspi_cached_addr = ~0ULL; - - fifo8_create(&s->rx_fifo, RXFF_A); - fifo8_create(&s->tx_fifo, TXFF_A); + q->lqspi_cached_addr = ~0ULL; } static int xilinx_spips_post_load(void *opaque, int version_id) @@ -570,6 +595,14 @@ static Property xilinx_spips_properties[] = { DEFINE_PROP_UINT8("num-txrx-bytes", XilinxSPIPS, num_txrx_bytes, 1), DEFINE_PROP_END_OF_LIST(), }; + +static void xilinx_qspips_class_init(ObjectClass *klass, void * data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = xilinx_qspips_realize; +} + static void xilinx_spips_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); @@ -587,9 +620,17 @@ static const TypeInfo xilinx_spips_info = { .class_init = xilinx_spips_class_init, }; +static const TypeInfo xilinx_qspips_info = { + .name = TYPE_XILINX_QSPIPS, + .parent = TYPE_XILINX_SPIPS, + .instance_size = sizeof(XilinxQSPIPS), + .class_init = xilinx_qspips_class_init, +}; + static void xilinx_spips_register_types(void) { type_register_static(&xilinx_spips_info); + type_register_static(&xilinx_qspips_info); } type_init(xilinx_spips_register_types) From 87920b44a1e04a45bf74ee0a88a2dfa51c65acdb Mon Sep 17 00:00:00 2001 From: Peter Crosthwaite Date: Mon, 3 Jun 2013 17:17:41 +0100 Subject: [PATCH 02/24] xilinx_spips: Make interrupts clear on read By default these interrupts are clear on read. Signed-off-by: Peter Crosthwaite Reviewed-by: Peter Maydell Reviewed-by: Edgar E. Iglesias Message-id: 999ff0091ed3cc3969a431bf55c00ef934cecc8e.1369117359.git.peter.crosthwaite@xilinx.com Signed-off-by: Peter Maydell --- hw/ssi/xilinx_spips.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/hw/ssi/xilinx_spips.c b/hw/ssi/xilinx_spips.c index 734adf0ea7..261d948ba7 100644 --- a/hw/ssi/xilinx_spips.c +++ b/hw/ssi/xilinx_spips.c @@ -330,6 +330,10 @@ static uint64_t xilinx_spips_read(void *opaque, hwaddr addr, mask = 0x0002FFFF; break; case R_INTR_STATUS: + ret = s->regs[addr] & IXR_ALL; + s->regs[addr] = 0; + DB_PRINT("addr=" TARGET_FMT_plx " = %x\n", addr * 4, ret); + return ret; case R_INTR_MASK: mask = IXR_ALL; break; From 3ea728d0c87011845666caedc582eaa9629797db Mon Sep 17 00:00:00 2001 From: Peter Crosthwaite Date: Mon, 3 Jun 2013 17:17:41 +0100 Subject: [PATCH 03/24] xilinx_spips: Inhibit interrupts in LQSPI mode The real hardware does not produce interrupts in LQSPI mode. Inhibit generation of interrupts when the LQ_MODE bit is set. Signed-off-by: Peter Crosthwaite Reviewed-by: Peter Maydell Reviewed-by: Edgar E. Iglesias Message-id: dff794a06872009ea7e5733ce6adcff94d18bbd0.1369117359.git.peter.crosthwaite@xilinx.com Signed-off-by: Peter Maydell --- hw/ssi/xilinx_spips.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/hw/ssi/xilinx_spips.c b/hw/ssi/xilinx_spips.c index 261d948ba7..a8691d51c4 100644 --- a/hw/ssi/xilinx_spips.c +++ b/hw/ssi/xilinx_spips.c @@ -204,6 +204,9 @@ static void xilinx_spips_update_cs_lines(XilinxSPIPS *s) static void xilinx_spips_update_ixr(XilinxSPIPS *s) { + if (s->regs[R_LQSPI_CFG] & LQSPI_CFG_LQ_MODE) { + return; + } /* These are set/cleared as they occur */ s->regs[R_INTR_STATUS] &= (IXR_TX_FIFO_UNDERFLOW | IXR_RX_FIFO_OVERFLOW | IXR_TX_FIFO_MODE_FAIL); @@ -256,7 +259,9 @@ static void xilinx_spips_flush_txfifo(XilinxSPIPS *s) for (i = 0; i < num_effective_busses(s); ++i) { if (!i || s->snoop_state == SNOOP_STRIPING) { if (fifo8_is_empty(&s->tx_fifo)) { - s->regs[R_INTR_STATUS] |= IXR_TX_FIFO_UNDERFLOW; + if (!(s->regs[R_LQSPI_CFG] & LQSPI_CFG_LQ_MODE)) { + s->regs[R_INTR_STATUS] |= IXR_TX_FIFO_UNDERFLOW; + } xilinx_spips_update_ixr(s); return; } else { From abef5fa6438d654de59dfa083166f41a4067f6b7 Mon Sep 17 00:00:00 2001 From: Peter Crosthwaite Date: Mon, 3 Jun 2013 17:17:41 +0100 Subject: [PATCH 04/24] xilinx_spips: Add verbose LQSPI debug output You really need this is you want to track a guest banging on LQSPI. Signed-off-by: Peter Crosthwaite Reviewed-by: Peter Maydell Reviewed-by: Edgar E. Iglesias Message-id: 26e508da4af11058d37daa777064c9e5c2a69abb.1369117359.git.peter.crosthwaite@xilinx.com Signed-off-by: Peter Maydell --- hw/ssi/xilinx_spips.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/hw/ssi/xilinx_spips.c b/hw/ssi/xilinx_spips.c index a8691d51c4..29636ce9ec 100644 --- a/hw/ssi/xilinx_spips.c +++ b/hw/ssi/xilinx_spips.c @@ -456,10 +456,13 @@ lqspi_read(void *opaque, hwaddr addr, unsigned int size) int i; XilinxQSPIPS *q = opaque; XilinxSPIPS *s = opaque; + uint32_t ret; if (addr >= q->lqspi_cached_addr && addr <= q->lqspi_cached_addr + LQSPI_CACHE_SIZE - 4) { - return q->lqspi_buf[(addr - q->lqspi_cached_addr) >> 2]; + ret = q->lqspi_buf[(addr - q->lqspi_cached_addr) >> 2]; + DB_PRINT("addr: %08x, data: %08x\n", (unsigned)addr, (unsigned)ret); + return ret; } else { int flash_addr = (addr / num_effective_busses(s)); int slave = flash_addr >> LQSPI_ADDRESS_BITS; From 10e60b35d04359fba1d759925018204527a1b9f5 Mon Sep 17 00:00:00 2001 From: Peter Crosthwaite Date: Mon, 3 Jun 2013 17:17:42 +0100 Subject: [PATCH 05/24] xilinx_spips: Fix QSPI FIFO size QSPI has a bigger FIFO than the regular SPI controller. Differentiate between the two with correct FIFO sizes for each. This is the first piece of class data for SPIPS, so this patch sees the creation of the XilinxSPIPSClass definition and assoicated QOM constructs. Signed-off-by: Peter Crosthwaite Reviewed-by: Peter Maydell Reviewed-by: Edgar E. Iglesias Message-id: acee25dd5e203215cbc15ca5d3cb5d5b2efebe7b.1369117359.git.peter.crosthwaite@xilinx.com Signed-off-by: Peter Maydell --- hw/ssi/xilinx_spips.c | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/hw/ssi/xilinx_spips.c b/hw/ssi/xilinx_spips.c index 29636ce9ec..86f33ef1f5 100644 --- a/hw/ssi/xilinx_spips.c +++ b/hw/ssi/xilinx_spips.c @@ -106,6 +106,9 @@ #define RXFF_A 32 #define TXFF_A 32 +#define RXFF_A_Q (64 * 4) +#define TXFF_A_Q (64 * 4) + /* 16MB per linear region */ #define LQSPI_ADDRESS_BITS 24 /* Bite off 4k chunks at a time */ @@ -159,12 +162,23 @@ typedef struct { hwaddr lqspi_cached_addr; } XilinxQSPIPS; +typedef struct XilinxSPIPSClass { + SysBusDeviceClass parent_class; + + uint32_t rx_fifo_size; + uint32_t tx_fifo_size; +} XilinxSPIPSClass; #define TYPE_XILINX_SPIPS "xlnx.ps7-spi" #define TYPE_XILINX_QSPIPS "xlnx.ps7-qspi" #define XILINX_SPIPS(obj) \ OBJECT_CHECK(XilinxSPIPS, (obj), TYPE_XILINX_SPIPS) +#define XILINX_SPIPS_CLASS(klass) \ + OBJECT_CLASS_CHECK(XilinxSPIPSClass, (klass), TYPE_XILINX_SPIPS) +#define XILINX_SPIPS_GET_CLASS(obj) \ + OBJECT_GET_CLASS(XilinxSPIPSClass, (obj), TYPE_XILINX_SPIPS) + #define XILINX_QSPIPS(obj) \ OBJECT_CHECK(XilinxQSPIPS, (obj), TYPE_XILINX_QSPIPS) @@ -531,6 +545,7 @@ static void xilinx_spips_realize(DeviceState *dev, Error **errp) { XilinxSPIPS *s = XILINX_SPIPS(dev); SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + XilinxSPIPSClass *xsc = XILINX_SPIPS_GET_CLASS(s); int i; DB_PRINT("realized spips\n"); @@ -555,8 +570,8 @@ static void xilinx_spips_realize(DeviceState *dev, Error **errp) s->irqline = -1; - fifo8_create(&s->rx_fifo, RXFF_A); - fifo8_create(&s->tx_fifo, TXFF_A); + fifo8_create(&s->rx_fifo, xsc->rx_fifo_size); + fifo8_create(&s->tx_fifo, xsc->tx_fifo_size); } static void xilinx_qspips_realize(DeviceState *dev, Error **errp) @@ -611,18 +626,25 @@ static Property xilinx_spips_properties[] = { static void xilinx_qspips_class_init(ObjectClass *klass, void * data) { DeviceClass *dc = DEVICE_CLASS(klass); + XilinxSPIPSClass *xsc = XILINX_SPIPS_CLASS(klass); dc->realize = xilinx_qspips_realize; + xsc->rx_fifo_size = RXFF_A_Q; + xsc->tx_fifo_size = TXFF_A_Q; } static void xilinx_spips_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); + XilinxSPIPSClass *xsc = XILINX_SPIPS_CLASS(klass); dc->realize = xilinx_spips_realize; dc->reset = xilinx_spips_reset; dc->props = xilinx_spips_properties; dc->vmsd = &vmstate_xilinx_spips; + + xsc->rx_fifo_size = RXFF_A; + xsc->tx_fifo_size = TXFF_A; } static const TypeInfo xilinx_spips_info = { @@ -630,6 +652,7 @@ static const TypeInfo xilinx_spips_info = { .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(XilinxSPIPS), .class_init = xilinx_spips_class_init, + .class_size = sizeof(XilinxSPIPSClass), }; static const TypeInfo xilinx_qspips_info = { From b5cd9143e78da8ea9e17fa523aacbedd267aae54 Mon Sep 17 00:00:00 2001 From: Peter Crosthwaite Date: Mon, 3 Jun 2013 17:17:42 +0100 Subject: [PATCH 06/24] xilinx_spips: Trash LQ page cache on mode change Invalidate the LQSPI cached page when transitioning into LQSPI mode. Otherwise there is a possibility that the controller will return stale data to the guest when transitioning back to LQ_MODE after a page program. Signed-off-by: Peter Crosthwaite Reviewed-by: Peter Maydell Reviewed-by: Edgar E. Iglesias Message-id: 677490a6ee1953fe5d366e599d665de645ac84db.1369117359.git.peter.crosthwaite@xilinx.com Signed-off-by: Peter Maydell --- hw/ssi/xilinx_spips.c | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/hw/ssi/xilinx_spips.c b/hw/ssi/xilinx_spips.c index 86f33ef1f5..cf4c43e81d 100644 --- a/hw/ssi/xilinx_spips.c +++ b/hw/ssi/xilinx_spips.c @@ -165,6 +165,8 @@ typedef struct { typedef struct XilinxSPIPSClass { SysBusDeviceClass parent_class; + const MemoryRegionOps *reg_ops; + uint32_t rx_fifo_size; uint32_t tx_fifo_size; } XilinxSPIPSClass; @@ -462,6 +464,25 @@ static const MemoryRegionOps spips_ops = { .endianness = DEVICE_LITTLE_ENDIAN, }; +static void xilinx_qspips_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + XilinxQSPIPS *q = XILINX_QSPIPS(opaque); + + xilinx_spips_write(opaque, addr, value, size); + addr >>= 2; + + if (addr == R_LQSPI_CFG) { + q->lqspi_cached_addr = ~0ULL; + } +} + +static const MemoryRegionOps qspips_ops = { + .read = xilinx_spips_read, + .write = xilinx_qspips_write, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + #define LQSPI_CACHE_SIZE 1024 static uint64_t @@ -565,7 +586,7 @@ static void xilinx_spips_realize(DeviceState *dev, Error **errp) sysbus_init_irq(sbd, &s->cs_lines[i]); } - memory_region_init_io(&s->iomem, &spips_ops, s, "spi", R_MAX*4); + memory_region_init_io(&s->iomem, xsc->reg_ops, s, "spi", R_MAX*4); sysbus_init_mmio(sbd, &s->iomem); s->irqline = -1; @@ -629,6 +650,7 @@ static void xilinx_qspips_class_init(ObjectClass *klass, void * data) XilinxSPIPSClass *xsc = XILINX_SPIPS_CLASS(klass); dc->realize = xilinx_qspips_realize; + xsc->reg_ops = &qspips_ops; xsc->rx_fifo_size = RXFF_A_Q; xsc->tx_fifo_size = TXFF_A_Q; } @@ -643,6 +665,7 @@ static void xilinx_spips_class_init(ObjectClass *klass, void *data) dc->props = xilinx_spips_properties; dc->vmsd = &vmstate_xilinx_spips; + xsc->reg_ops = &spips_ops; xsc->rx_fifo_size = RXFF_A; xsc->tx_fifo_size = TXFF_A; } From e100f3be0cccc58692b3988ec2e35cd8b906ca05 Mon Sep 17 00:00:00 2001 From: Peter Crosthwaite Date: Mon, 3 Jun 2013 17:17:42 +0100 Subject: [PATCH 07/24] xilinx_spips: Add automatic start support SPI has a mode where it automatically starts based on tx fifo occupancy. Implemented. Signed-off-by: Peter Crosthwaite Reviewed-by: Peter Maydell Reviewed-by: Edgar E. Iglesias Message-id: f4e9accb5de87b526fff6ed937f63278db76533b.1369117359.git.peter.crosthwaite@xilinx.com Signed-off-by: Peter Maydell --- hw/ssi/xilinx_spips.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hw/ssi/xilinx_spips.c b/hw/ssi/xilinx_spips.c index cf4c43e81d..0c04ec9486 100644 --- a/hw/ssi/xilinx_spips.c +++ b/hw/ssi/xilinx_spips.c @@ -451,7 +451,8 @@ static void xilinx_spips_write(void *opaque, hwaddr addr, } s->regs[addr] = (s->regs[addr] & ~mask) | (value & mask); no_reg_update: - if (man_start_com) { + if ((man_start_com && s->regs[R_CONFIG] & MAN_START_EN) || + (fifo8_is_empty(&s->tx_fifo) && s->regs[R_CONFIG] & MAN_START_EN)) { xilinx_spips_flush_txfifo(s); } xilinx_spips_update_ixr(s); From c4f08ffe134810307cebd66f2cbcffe5cd707915 Mon Sep 17 00:00:00 2001 From: Peter Crosthwaite Date: Mon, 3 Jun 2013 17:17:42 +0100 Subject: [PATCH 08/24] xilinx_spips: Implement automatic CS Implement the automatic CS control feature. If the MANUAL_CS bit is cleared then the chip select stay de-asserted as long as the tx FIFO is empty. Signed-off-by: Peter Crosthwaite Reviewed-by: Peter Maydell Reviewed-by: Edgar E. Iglesias Message-id: 1d67383adc42761af715a93f161344b9284dfc9a.1369117359.git.peter.crosthwaite@xilinx.com Signed-off-by: Peter Maydell --- hw/ssi/xilinx_spips.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/hw/ssi/xilinx_spips.c b/hw/ssi/xilinx_spips.c index 0c04ec9486..631d010cd9 100644 --- a/hw/ssi/xilinx_spips.c +++ b/hw/ssi/xilinx_spips.c @@ -190,6 +190,12 @@ static inline int num_effective_busses(XilinxSPIPS *s) s->regs[R_LQSPI_CFG] & LQSPI_CFG_TWO_MEM) ? s->num_busses : 1; } +static inline bool xilinx_spips_cs_is_set(XilinxSPIPS *s, int i, int field) +{ + return ~field & (1 << i) && (s->regs[R_CONFIG] & MANUAL_CS + || !fifo8_is_empty(&s->tx_fifo)); +} + static void xilinx_spips_update_cs_lines(XilinxSPIPS *s) { int i, j; @@ -202,14 +208,15 @@ static void xilinx_spips_update_cs_lines(XilinxSPIPS *s) int cs_to_set = (j * s->num_cs + i + upage) % (s->num_cs * s->num_busses); - if (~field & (1 << i) && !found) { + if (xilinx_spips_cs_is_set(s, i, field) && !found) { DB_PRINT("selecting slave %d\n", i); qemu_set_irq(s->cs_lines[cs_to_set], 0); } else { + DB_PRINT("deselecting slave %d\n", i); qemu_set_irq(s->cs_lines[cs_to_set], 1); } } - if (~field & (1 << i)) { + if (xilinx_spips_cs_is_set(s, i, field)) { found = true; } } @@ -451,12 +458,13 @@ static void xilinx_spips_write(void *opaque, hwaddr addr, } s->regs[addr] = (s->regs[addr] & ~mask) | (value & mask); no_reg_update: + xilinx_spips_update_cs_lines(s); if ((man_start_com && s->regs[R_CONFIG] & MAN_START_EN) || (fifo8_is_empty(&s->tx_fifo) && s->regs[R_CONFIG] & MAN_START_EN)) { xilinx_spips_flush_txfifo(s); } - xilinx_spips_update_ixr(s); xilinx_spips_update_cs_lines(s); + xilinx_spips_update_ixr(s); } static const MemoryRegionOps spips_ops = { @@ -510,7 +518,7 @@ lqspi_read(void *opaque, hwaddr addr, unsigned int size) fifo8_reset(&s->rx_fifo); s->regs[R_CONFIG] &= ~CS; - s->regs[R_CONFIG] |= (~(1 << slave) << CS_SHIFT) & CS; + s->regs[R_CONFIG] |= ((~(1 << slave) << CS_SHIFT) & CS) | MANUAL_CS; xilinx_spips_update_cs_lines(s); /* instruction */ @@ -534,6 +542,7 @@ lqspi_read(void *opaque, hwaddr addr, unsigned int size) DB_PRINT("pushing dummy byte\n"); fifo8_push(&s->tx_fifo, 0); } + xilinx_spips_update_cs_lines(s); xilinx_spips_flush_txfifo(s); fifo8_reset(&s->rx_fifo); @@ -545,6 +554,7 @@ lqspi_read(void *opaque, hwaddr addr, unsigned int size) rx_data_bytes(s, &q->lqspi_buf[cache_entry], 4); cache_entry++; } + xilinx_spips_update_cs_lines(s); s->regs[R_CONFIG] |= CS; xilinx_spips_update_cs_lines(s); From 15408b428f5b4db56da555fbda4f1aaf40d77f4b Mon Sep 17 00:00:00 2001 From: Peter Crosthwaite Date: Mon, 3 Jun 2013 17:17:43 +0100 Subject: [PATCH 09/24] xilinx_spips: lqspi: Dont touch config register The LQSPI mode is supposed to work via the automatic CS mode feature rather than manipulate CS lines itself. Now that auto CS is implemented remove LQSPIs CS mode override logic. There is still a need to manipulate the U_PAGE bit in LQSPI config register to implement dual-stack mode however. Signed-off-by: Peter Crosthwaite Reviewed-by: Peter Maydell Reviewed-by: Edgar E. Iglesias Message-id: 000c8dd54df09523f17052638100722ef0f5a3af.1369117359.git.peter.crosthwaite@xilinx.com Signed-off-by: Peter Maydell --- hw/ssi/xilinx_spips.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/hw/ssi/xilinx_spips.c b/hw/ssi/xilinx_spips.c index 631d010cd9..ea8a593214 100644 --- a/hw/ssi/xilinx_spips.c +++ b/hw/ssi/xilinx_spips.c @@ -511,16 +511,16 @@ lqspi_read(void *opaque, hwaddr addr, unsigned int size) int flash_addr = (addr / num_effective_busses(s)); int slave = flash_addr >> LQSPI_ADDRESS_BITS; int cache_entry = 0; + uint32_t u_page_save = s->regs[R_LQSPI_STS] & ~LQSPI_CFG_U_PAGE; + + s->regs[R_LQSPI_STS] &= ~LQSPI_CFG_U_PAGE; + s->regs[R_LQSPI_STS] |= slave ? LQSPI_CFG_U_PAGE : 0; DB_PRINT("config reg status: %08x\n", s->regs[R_LQSPI_CFG]); fifo8_reset(&s->tx_fifo); fifo8_reset(&s->rx_fifo); - s->regs[R_CONFIG] &= ~CS; - s->regs[R_CONFIG] |= ((~(1 << slave) << CS_SHIFT) & CS) | MANUAL_CS; - xilinx_spips_update_cs_lines(s); - /* instruction */ DB_PRINT("pushing read instruction: %02x\n", (uint8_t)(s->regs[R_LQSPI_CFG] & LQSPI_CFG_INST_CODE)); @@ -554,9 +554,9 @@ lqspi_read(void *opaque, hwaddr addr, unsigned int size) rx_data_bytes(s, &q->lqspi_buf[cache_entry], 4); cache_entry++; } - xilinx_spips_update_cs_lines(s); - s->regs[R_CONFIG] |= CS; + s->regs[R_LQSPI_STS] &= ~LQSPI_CFG_U_PAGE; + s->regs[R_LQSPI_STS] |= u_page_save; xilinx_spips_update_cs_lines(s); q->lqspi_cached_addr = addr; From 2133a5f6b8f8941a6a3734c6c1990656553de76c Mon Sep 17 00:00:00 2001 From: Peter Crosthwaite Date: Mon, 3 Jun 2013 17:17:43 +0100 Subject: [PATCH 10/24] xilinx_spips: Fix CTRL register RW bits The CTRL register was RAZ/WI on some of the RW bits. Even though the function behind these bits is invalid in QEMU, they should still be guest accessible. Fix. Signed-off-by: Peter Crosthwaite Reviewed-by: Peter Maydell Reviewed-by: Edgar E. Iglesias Message-id: b7aaad93163ce4af0c428635804ac7b77a567b25.1369117359.git.peter.crosthwaite@xilinx.com Signed-off-by: Peter Maydell --- hw/ssi/xilinx_spips.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/hw/ssi/xilinx_spips.c b/hw/ssi/xilinx_spips.c index ea8a593214..3e9e76c165 100644 --- a/hw/ssi/xilinx_spips.c +++ b/hw/ssi/xilinx_spips.c @@ -56,6 +56,7 @@ #define CLK_PH (1 << 2) #define CLK_POL (1 << 1) #define MODE_SEL (1 << 0) +#define R_CONFIG_RSVD (0x7bf40000) /* interrupt mechanism */ #define R_INTR_STATUS (0x04 / 4) @@ -355,7 +356,7 @@ static uint64_t xilinx_spips_read(void *opaque, hwaddr addr, addr >>= 2; switch (addr) { case R_CONFIG: - mask = 0x0002FFFF; + mask = ~(R_CONFIG_RSVD | MAN_START_COM); break; case R_INTR_STATUS: ret = s->regs[addr] & IXR_ALL; @@ -415,7 +416,7 @@ static void xilinx_spips_write(void *opaque, hwaddr addr, addr >>= 2; switch (addr) { case R_CONFIG: - mask = 0x0002FFFF; + mask = ~(R_CONFIG_RSVD | MAN_START_COM); if (value & MAN_START_COM) { man_start_com = 1; } From 9151da25a71a171d19beec107a8ee2066cb65e7c Mon Sep 17 00:00:00 2001 From: Peter Crosthwaite Date: Mon, 3 Jun 2013 17:17:43 +0100 Subject: [PATCH 11/24] xilinx_spips: Fix striping behaviour The QSPI controller was using byte-wide stripes when striping across the two flashes in dual parallel mode. The real hardware however uses individual bit striping. QEMU misbehaves in the (corner) case where data is written/read in dual-parallel mode and read/written back in single mode. Signed-off-by: Peter Crosthwaite Reviewed-by: Peter Maydell Reviewed-by: Edgar E. Iglesias Message-id: 718a61df1bf746ec06f6da44d12f8317af7b08ce.1369117359.git.peter.crosthwaite@xilinx.com Signed-off-by: Peter Maydell --- hw/ssi/xilinx_spips.c | 84 ++++++++++++++++++++++++++++++++----------- 1 file changed, 63 insertions(+), 21 deletions(-) diff --git a/hw/ssi/xilinx_spips.c b/hw/ssi/xilinx_spips.c index 3e9e76c165..7222e152e6 100644 --- a/hw/ssi/xilinx_spips.c +++ b/hw/ssi/xilinx_spips.c @@ -273,35 +273,77 @@ static void xilinx_spips_reset(DeviceState *d) xilinx_spips_update_cs_lines(s); } +/* N way (num) in place bit striper. Lay out row wise bits (LSB to MSB) + * column wise (from element 0 to N-1). num is the length of x, and dir + * reverses the direction of the transform. Best illustrated by example: + * Each digit in the below array is a single bit (num == 3): + * + * {{ 76543210, } ----- stripe (dir == false) -----> {{ FCheb630, } + * { hgfedcba, } { GDAfc741, } + * { HGFEDCBA, }} <---- upstripe (dir == true) ----- { HEBgda52, }} + */ + +static inline void stripe8(uint8_t *x, int num, bool dir) +{ + uint8_t r[num]; + memset(r, 0, sizeof(uint8_t) * num); + int idx[2] = {0, 0}; + int bit[2] = {0, 0}; + int d = dir; + + for (idx[0] = 0; idx[0] < num; ++idx[0]) { + for (bit[0] = 0; bit[0] < 8; ++bit[0]) { + r[idx[d]] |= x[idx[!d]] & 1 << bit[!d] ? 1 << bit[d] : 0; + idx[1] = (idx[1] + 1) % num; + if (!idx[1]) { + bit[1]++; + } + } + } + memcpy(x, r, sizeof(uint8_t) * num); +} + static void xilinx_spips_flush_txfifo(XilinxSPIPS *s) { for (;;) { int i; - uint8_t rx; uint8_t tx = 0; + uint8_t tx_rx[num_effective_busses(s)]; + + if (fifo8_is_empty(&s->tx_fifo)) { + if (!(s->regs[R_LQSPI_CFG] & LQSPI_CFG_LQ_MODE)) { + s->regs[R_INTR_STATUS] |= IXR_TX_FIFO_UNDERFLOW; + } + xilinx_spips_update_ixr(s); + return; + } else if (s->snoop_state == SNOOP_STRIPING) { + for (i = 0; i < num_effective_busses(s); ++i) { + tx_rx[i] = fifo8_pop(&s->tx_fifo); + } + stripe8(tx_rx, num_effective_busses(s), false); + } else { + tx = fifo8_pop(&s->tx_fifo); + for (i = 0; i < num_effective_busses(s); ++i) { + tx_rx[i] = tx; + } + } for (i = 0; i < num_effective_busses(s); ++i) { - if (!i || s->snoop_state == SNOOP_STRIPING) { - if (fifo8_is_empty(&s->tx_fifo)) { - if (!(s->regs[R_LQSPI_CFG] & LQSPI_CFG_LQ_MODE)) { - s->regs[R_INTR_STATUS] |= IXR_TX_FIFO_UNDERFLOW; - } - xilinx_spips_update_ixr(s); - return; - } else { - tx = fifo8_pop(&s->tx_fifo); - } - } - rx = ssi_transfer(s->spi[i], (uint32_t)tx); - DB_PRINT("tx = %02x rx = %02x\n", tx, rx); - if (!i || s->snoop_state == SNOOP_STRIPING) { - if (fifo8_is_full(&s->rx_fifo)) { - s->regs[R_INTR_STATUS] |= IXR_RX_FIFO_OVERFLOW; - DB_PRINT("rx FIFO overflow"); - } else { - fifo8_push(&s->rx_fifo, (uint8_t)rx); - } + DB_PRINT("tx = %02x\n", tx_rx[i]); + tx_rx[i] = ssi_transfer(s->spi[i], (uint32_t)tx_rx[i]); + DB_PRINT("rx = %02x\n", tx_rx[i]); + } + + if (fifo8_is_full(&s->rx_fifo)) { + s->regs[R_INTR_STATUS] |= IXR_RX_FIFO_OVERFLOW; + DB_PRINT("rx FIFO overflow"); + } else if (s->snoop_state == SNOOP_STRIPING) { + stripe8(tx_rx, num_effective_busses(s), true); + for (i = 0; i < num_effective_busses(s); ++i) { + fifo8_push(&s->rx_fifo, (uint8_t)tx_rx[i]); } + } else { + fifo8_push(&s->rx_fifo, (uint8_t)tx_rx[0]); } switch (s->snoop_state) { From c37fc509d288ff693e42b2025451241fdd481948 Mon Sep 17 00:00:00 2001 From: Peter Crosthwaite Date: Mon, 3 Jun 2013 17:17:43 +0100 Subject: [PATCH 12/24] xilinx_spips: Debug msgs for Snoop state This is worth keeping track of when debugging the device model. Signed-off-by: Peter Crosthwaite Reviewed-by: Peter Maydell Reviewed-by: Edgar E. Iglesias Message-id: d3b44ecf23d671798b062eee5dc362c716ea54cd.1369117359.git.peter.crosthwaite@xilinx.com Signed-off-by: Peter Maydell --- hw/ssi/xilinx_spips.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hw/ssi/xilinx_spips.c b/hw/ssi/xilinx_spips.c index 7222e152e6..8fbbd24f7b 100644 --- a/hw/ssi/xilinx_spips.c +++ b/hw/ssi/xilinx_spips.c @@ -223,6 +223,7 @@ static void xilinx_spips_update_cs_lines(XilinxSPIPS *s) } if (!found) { s->snoop_state = SNOOP_CHECKING; + DB_PRINT("moving to snoop check state\n"); } } @@ -346,6 +347,7 @@ static void xilinx_spips_flush_txfifo(XilinxSPIPS *s) fifo8_push(&s->rx_fifo, (uint8_t)tx_rx[0]); } + DB_PRINT("initial snoop state: %x\n", (unsigned)s->snoop_state); switch (s->snoop_state) { case (SNOOP_CHECKING): switch (tx) { /* new instruction code */ @@ -374,6 +376,7 @@ static void xilinx_spips_flush_txfifo(XilinxSPIPS *s) default: s->snoop_state--; } + DB_PRINT("final snoop state: %x\n", (unsigned)s->snoop_state); } } From 4a5b6fa8d9bdeec71f4f92e8b7a31fc344ef512f Mon Sep 17 00:00:00 2001 From: Peter Crosthwaite Date: Mon, 3 Jun 2013 17:17:43 +0100 Subject: [PATCH 13/24] xilinx_spips: Multiple debug verbosity levels The debug printfs on every SPI operation is extremely verbose. Add a second level of debug for this. Signed-off-by: Peter Crosthwaite Reviewed-by: Peter Maydell Reviewed-by: Edgar E. Iglesias Message-id: f63478b8e5b29cc011cdc10e29f8537bb2fc2b5e.1369117359.git.peter.crosthwaite@xilinx.com Signed-off-by: Peter Maydell --- hw/ssi/xilinx_spips.c | 70 ++++++++++++++++++++++++++----------------- 1 file changed, 42 insertions(+), 28 deletions(-) diff --git a/hw/ssi/xilinx_spips.c b/hw/ssi/xilinx_spips.c index 8fbbd24f7b..665f471bb1 100644 --- a/hw/ssi/xilinx_spips.c +++ b/hw/ssi/xilinx_spips.c @@ -30,15 +30,17 @@ #include "hw/ssi.h" #include "qemu/bitops.h" -#ifdef XILINX_SPIPS_ERR_DEBUG -#define DB_PRINT(...) do { \ - fprintf(stderr, ": %s: ", __func__); \ - fprintf(stderr, ## __VA_ARGS__); \ - } while (0); -#else - #define DB_PRINT(...) +#ifndef XILINX_SPIPS_ERR_DEBUG +#define XILINX_SPIPS_ERR_DEBUG 0 #endif +#define DB_PRINT_L(level, ...) do { \ + if (XILINX_SPIPS_ERR_DEBUG > (level)) { \ + fprintf(stderr, ": %s: ", __func__); \ + fprintf(stderr, ## __VA_ARGS__); \ + } \ +} while (0); + /* config register */ #define R_CONFIG (0x00 / 4) #define IFMODE (1 << 31) @@ -210,10 +212,10 @@ static void xilinx_spips_update_cs_lines(XilinxSPIPS *s) (s->num_cs * s->num_busses); if (xilinx_spips_cs_is_set(s, i, field) && !found) { - DB_PRINT("selecting slave %d\n", i); + DB_PRINT_L(0, "selecting slave %d\n", i); qemu_set_irq(s->cs_lines[cs_to_set], 0); } else { - DB_PRINT("deselecting slave %d\n", i); + DB_PRINT_L(0, "deselecting slave %d\n", i); qemu_set_irq(s->cs_lines[cs_to_set], 1); } } @@ -223,7 +225,7 @@ static void xilinx_spips_update_cs_lines(XilinxSPIPS *s) } if (!found) { s->snoop_state = SNOOP_CHECKING; - DB_PRINT("moving to snoop check state\n"); + DB_PRINT_L(1, "moving to snoop check state\n"); } } @@ -306,6 +308,8 @@ static inline void stripe8(uint8_t *x, int num, bool dir) static void xilinx_spips_flush_txfifo(XilinxSPIPS *s) { + int debug_level = 0; + for (;;) { int i; uint8_t tx = 0; @@ -330,14 +334,14 @@ static void xilinx_spips_flush_txfifo(XilinxSPIPS *s) } for (i = 0; i < num_effective_busses(s); ++i) { - DB_PRINT("tx = %02x\n", tx_rx[i]); + DB_PRINT_L(debug_level, "tx = %02x\n", tx_rx[i]); tx_rx[i] = ssi_transfer(s->spi[i], (uint32_t)tx_rx[i]); - DB_PRINT("rx = %02x\n", tx_rx[i]); + DB_PRINT_L(debug_level, "rx = %02x\n", tx_rx[i]); } if (fifo8_is_full(&s->rx_fifo)) { s->regs[R_INTR_STATUS] |= IXR_RX_FIFO_OVERFLOW; - DB_PRINT("rx FIFO overflow"); + DB_PRINT_L(0, "rx FIFO overflow"); } else if (s->snoop_state == SNOOP_STRIPING) { stripe8(tx_rx, num_effective_busses(s), true); for (i = 0; i < num_effective_busses(s); ++i) { @@ -347,7 +351,8 @@ static void xilinx_spips_flush_txfifo(XilinxSPIPS *s) fifo8_push(&s->rx_fifo, (uint8_t)tx_rx[0]); } - DB_PRINT("initial snoop state: %x\n", (unsigned)s->snoop_state); + DB_PRINT_L(debug_level, "initial snoop state: %x\n", + (unsigned)s->snoop_state); switch (s->snoop_state) { case (SNOOP_CHECKING): switch (tx) { /* new instruction code */ @@ -372,11 +377,17 @@ static void xilinx_spips_flush_txfifo(XilinxSPIPS *s) break; case (SNOOP_STRIPING): case (SNOOP_NONE): + /* Once we hit the boring stuff - squelch debug noise */ + if (!debug_level) { + DB_PRINT_L(0, "squelching debug info ....\n"); + debug_level = 1; + } break; default: s->snoop_state--; } - DB_PRINT("final snoop state: %x\n", (unsigned)s->snoop_state); + DB_PRINT_L(debug_level, "final snoop state: %x\n", + (unsigned)s->snoop_state); } } @@ -406,7 +417,7 @@ static uint64_t xilinx_spips_read(void *opaque, hwaddr addr, case R_INTR_STATUS: ret = s->regs[addr] & IXR_ALL; s->regs[addr] = 0; - DB_PRINT("addr=" TARGET_FMT_plx " = %x\n", addr * 4, ret); + DB_PRINT_L(0, "addr=" TARGET_FMT_plx " = %x\n", addr * 4, ret); return ret; case R_INTR_MASK: mask = IXR_ALL; @@ -427,11 +438,12 @@ static uint64_t xilinx_spips_read(void *opaque, hwaddr addr, break; case R_RX_DATA: rx_data_bytes(s, &ret, s->num_txrx_bytes); - DB_PRINT("addr=" TARGET_FMT_plx " = %x\n", addr * 4, ret); + DB_PRINT_L(0, "addr=" TARGET_FMT_plx " = %x\n", addr * 4, ret); xilinx_spips_update_ixr(s); return ret; } - DB_PRINT("addr=" TARGET_FMT_plx " = %x\n", addr * 4, s->regs[addr] & mask); + DB_PRINT_L(0, "addr=" TARGET_FMT_plx " = %x\n", addr * 4, + s->regs[addr] & mask); return s->regs[addr] & mask; } @@ -457,7 +469,7 @@ static void xilinx_spips_write(void *opaque, hwaddr addr, int man_start_com = 0; XilinxSPIPS *s = opaque; - DB_PRINT("addr=" TARGET_FMT_plx " = %x\n", addr, (unsigned)value); + DB_PRINT_L(0, "addr=" TARGET_FMT_plx " = %x\n", addr, (unsigned)value); addr >>= 2; switch (addr) { case R_CONFIG: @@ -551,7 +563,8 @@ lqspi_read(void *opaque, hwaddr addr, unsigned int size) if (addr >= q->lqspi_cached_addr && addr <= q->lqspi_cached_addr + LQSPI_CACHE_SIZE - 4) { ret = q->lqspi_buf[(addr - q->lqspi_cached_addr) >> 2]; - DB_PRINT("addr: %08x, data: %08x\n", (unsigned)addr, (unsigned)ret); + DB_PRINT_L(1, "addr: %08x, data: %08x\n", (unsigned)addr, + (unsigned)ret); return ret; } else { int flash_addr = (addr / num_effective_busses(s)); @@ -562,17 +575,18 @@ lqspi_read(void *opaque, hwaddr addr, unsigned int size) s->regs[R_LQSPI_STS] &= ~LQSPI_CFG_U_PAGE; s->regs[R_LQSPI_STS] |= slave ? LQSPI_CFG_U_PAGE : 0; - DB_PRINT("config reg status: %08x\n", s->regs[R_LQSPI_CFG]); + DB_PRINT_L(0, "config reg status: %08x\n", s->regs[R_LQSPI_CFG]); fifo8_reset(&s->tx_fifo); fifo8_reset(&s->rx_fifo); /* instruction */ - DB_PRINT("pushing read instruction: %02x\n", - (uint8_t)(s->regs[R_LQSPI_CFG] & LQSPI_CFG_INST_CODE)); + DB_PRINT_L(0, "pushing read instruction: %02x\n", + (unsigned)(uint8_t)(s->regs[R_LQSPI_CFG] & + LQSPI_CFG_INST_CODE)); fifo8_push(&s->tx_fifo, s->regs[R_LQSPI_CFG] & LQSPI_CFG_INST_CODE); /* read address */ - DB_PRINT("pushing read address %06x\n", flash_addr); + DB_PRINT_L(0, "pushing read address %06x\n", flash_addr); fifo8_push(&s->tx_fifo, (uint8_t)(flash_addr >> 16)); fifo8_push(&s->tx_fifo, (uint8_t)(flash_addr >> 8)); fifo8_push(&s->tx_fifo, (uint8_t)flash_addr); @@ -585,14 +599,14 @@ lqspi_read(void *opaque, hwaddr addr, unsigned int size) /* dummy bytes */ for (i = 0; i < (extract32(s->regs[R_LQSPI_CFG], LQSPI_CFG_DUMMY_SHIFT, LQSPI_CFG_DUMMY_WIDTH)); ++i) { - DB_PRINT("pushing dummy byte\n"); + DB_PRINT_L(0, "pushing dummy byte\n"); fifo8_push(&s->tx_fifo, 0); } xilinx_spips_update_cs_lines(s); xilinx_spips_flush_txfifo(s); fifo8_reset(&s->rx_fifo); - DB_PRINT("starting QSPI data read\n"); + DB_PRINT_L(0, "starting QSPI data read\n"); for (i = 0; i < LQSPI_CACHE_SIZE / 4; ++i) { tx_data_bytes(s, 0, 4); @@ -626,7 +640,7 @@ static void xilinx_spips_realize(DeviceState *dev, Error **errp) XilinxSPIPSClass *xsc = XILINX_SPIPS_GET_CLASS(s); int i; - DB_PRINT("realized spips\n"); + DB_PRINT_L(0, "realized spips\n"); s->spi = g_new(SSIBus *, s->num_busses); for (i = 0; i < s->num_busses; ++i) { @@ -658,7 +672,7 @@ static void xilinx_qspips_realize(DeviceState *dev, Error **errp) XilinxQSPIPS *q = XILINX_QSPIPS(dev); SysBusDevice *sbd = SYS_BUS_DEVICE(dev); - DB_PRINT("realized qspips\n"); + DB_PRINT_L(0, "realized qspips\n"); s->num_busses = 2; s->num_cs = 2; From a66418f6f181ca6ee04e77896674253ff83db45e Mon Sep 17 00:00:00 2001 From: Peter Crosthwaite Date: Mon, 3 Jun 2013 17:17:44 +0100 Subject: [PATCH 14/24] xilinx_spips: lqspi: Push more data to tx-fifo Do 16 words per fifo flush. Increases performance and decreases debug verbosity. This data depth has no real hardware analogue, so just go with something that has reasonable performance. Signed-off-by: Peter Crosthwaite Reviewed-by: Peter Maydell Reviewed-by: Edgar E. Iglesias Message-id: 5621ee4621941d3639b5cacfdec26bd3148f31d5.1369117359.git.peter.crosthwaite@xilinx.com Signed-off-by: Peter Maydell --- hw/ssi/xilinx_spips.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/hw/ssi/xilinx_spips.c b/hw/ssi/xilinx_spips.c index 665f471bb1..e975a876c6 100644 --- a/hw/ssi/xilinx_spips.c +++ b/hw/ssi/xilinx_spips.c @@ -608,11 +608,14 @@ lqspi_read(void *opaque, hwaddr addr, unsigned int size) DB_PRINT_L(0, "starting QSPI data read\n"); - for (i = 0; i < LQSPI_CACHE_SIZE / 4; ++i) { - tx_data_bytes(s, 0, 4); + while (cache_entry < LQSPI_CACHE_SIZE / 4) { + for (i = 0; i < 16; ++i) { + tx_data_bytes(s, 0, 4); + } xilinx_spips_flush_txfifo(s); - rx_data_bytes(s, &q->lqspi_buf[cache_entry], 4); - cache_entry++; + for (i = 0; i < 16; ++i) { + rx_data_bytes(s, &q->lqspi_buf[cache_entry++], 4); + } } s->regs[R_LQSPI_STS] &= ~LQSPI_CFG_U_PAGE; From b0b7ae6259e96e775315357c813b74e85637bf32 Mon Sep 17 00:00:00 2001 From: Peter Crosthwaite Date: Mon, 3 Jun 2013 17:17:44 +0100 Subject: [PATCH 15/24] xilinx_spips: lqspi: Fix byte/misaligned access The LQSPI bus attachment supports byte/halfword and misaligned accesses. Fixed. Refactored the LQSPI cache to be byte-wise instead of word wise accordingly. Signed-off-by: Peter Crosthwaite Reviewed-by: Peter Maydell Reviewed-by: Edgar E. Iglesias Message-id: 5ec47b13563ad2d22105a1f26186d7756718394b.1369117359.git.peter.crosthwaite@xilinx.com Signed-off-by: Peter Maydell --- hw/ssi/xilinx_spips.c | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/hw/ssi/xilinx_spips.c b/hw/ssi/xilinx_spips.c index e975a876c6..05a3adaa90 100644 --- a/hw/ssi/xilinx_spips.c +++ b/hw/ssi/xilinx_spips.c @@ -161,7 +161,7 @@ typedef struct { typedef struct { XilinxSPIPS parent_obj; - uint32_t lqspi_buf[LQSPI_CACHE_SIZE]; + uint8_t lqspi_buf[LQSPI_CACHE_SIZE]; hwaddr lqspi_cached_addr; } XilinxQSPIPS; @@ -391,14 +391,12 @@ static void xilinx_spips_flush_txfifo(XilinxSPIPS *s) } } -static inline void rx_data_bytes(XilinxSPIPS *s, uint32_t *value, int max) +static inline void rx_data_bytes(XilinxSPIPS *s, uint8_t *value, int max) { int i; - *value = 0; for (i = 0; i < max && !fifo8_is_empty(&s->rx_fifo); ++i) { - uint32_t next = fifo8_pop(&s->rx_fifo) & 0xFF; - *value |= next << 8 * (s->regs[R_CONFIG] & ENDIAN ? 3-i : i); + value[i] = fifo8_pop(&s->rx_fifo); } } @@ -408,6 +406,7 @@ static uint64_t xilinx_spips_read(void *opaque, hwaddr addr, XilinxSPIPS *s = opaque; uint32_t mask = ~0; uint32_t ret; + uint8_t rx_buf[4]; addr >>= 2; switch (addr) { @@ -437,7 +436,10 @@ static uint64_t xilinx_spips_read(void *opaque, hwaddr addr, mask = 0; break; case R_RX_DATA: - rx_data_bytes(s, &ret, s->num_txrx_bytes); + memset(rx_buf, 0, sizeof(rx_buf)); + rx_data_bytes(s, rx_buf, s->num_txrx_bytes); + ret = s->regs[R_CONFIG] & ENDIAN ? cpu_to_be32(*(uint32_t *)rx_buf) + : cpu_to_le32(*(uint32_t *)rx_buf); DB_PRINT_L(0, "addr=" TARGET_FMT_plx " = %x\n", addr * 4, ret); xilinx_spips_update_ixr(s); return ret; @@ -562,7 +564,8 @@ lqspi_read(void *opaque, hwaddr addr, unsigned int size) if (addr >= q->lqspi_cached_addr && addr <= q->lqspi_cached_addr + LQSPI_CACHE_SIZE - 4) { - ret = q->lqspi_buf[(addr - q->lqspi_cached_addr) >> 2]; + uint8_t *retp = &q->lqspi_buf[addr - q->lqspi_cached_addr]; + ret = cpu_to_le32(*(uint32_t *)retp); DB_PRINT_L(1, "addr: %08x, data: %08x\n", (unsigned)addr, (unsigned)ret); return ret; @@ -608,13 +611,13 @@ lqspi_read(void *opaque, hwaddr addr, unsigned int size) DB_PRINT_L(0, "starting QSPI data read\n"); - while (cache_entry < LQSPI_CACHE_SIZE / 4) { - for (i = 0; i < 16; ++i) { - tx_data_bytes(s, 0, 4); + while (cache_entry < LQSPI_CACHE_SIZE) { + for (i = 0; i < 64; ++i) { + tx_data_bytes(s, 0, 1); } xilinx_spips_flush_txfifo(s); - for (i = 0; i < 16; ++i) { - rx_data_bytes(s, &q->lqspi_buf[cache_entry++], 4); + for (i = 0; i < 64; ++i) { + rx_data_bytes(s, &q->lqspi_buf[cache_entry++], 1); } } @@ -622,7 +625,7 @@ lqspi_read(void *opaque, hwaddr addr, unsigned int size) s->regs[R_LQSPI_STS] |= u_page_save; xilinx_spips_update_cs_lines(s); - q->lqspi_cached_addr = addr; + q->lqspi_cached_addr = flash_addr * num_effective_busses(s); return lqspi_read(opaque, addr, size); } } @@ -631,7 +634,7 @@ static const MemoryRegionOps lqspi_ops = { .read = lqspi_read, .endianness = DEVICE_NATIVE_ENDIAN, .valid = { - .min_access_size = 4, + .min_access_size = 1, .max_access_size = 4 } }; From 656f416c65d8ddbfd17500676865f0ac8f960801 Mon Sep 17 00:00:00 2001 From: Peter Crosthwaite Date: Mon, 3 Jun 2013 17:17:44 +0100 Subject: [PATCH 16/24] sd/sdhci.c: Only reset data_count on new commands The data_count variable was being reset on every transfer, including DMA transfer resumptions. This is incorrect, it should only be set on a new command. Manifests as a bug when using ADMA and there is a timer delay between ADMA frames where the fifo is left in a non empty state. Signed-off-by: Peter Crosthwaite Reviewed-by: Igor Mitsyanko Message-id: 15a98609cc32315211b0963091a8efd67522e160.1369370934.git.peter.crosthwaite@xilinx.com Signed-off-by: Peter Maydell --- hw/sd/sdhci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hw/sd/sdhci.c b/hw/sd/sdhci.c index 91dc9b082d..0a84540e12 100644 --- a/hw/sd/sdhci.c +++ b/hw/sd/sdhci.c @@ -260,6 +260,7 @@ static void sdhci_send_command(SDHCIState *s) sdhci_update_irq(s); if (s->blksize && (s->cmdreg & SDHC_CMD_DATA_PRESENT)) { + s->data_count = 0; sdhci_do_data_transfer(s); } } @@ -773,7 +774,6 @@ static void sdhci_do_adma(SDHCIState *s) static void sdhci_data_transfer(SDHCIState *s) { SDHCIClass *k = SDHCI_GET_CLASS(s); - s->data_count = 0; if (s->trnmod & SDHC_TRNS_DMA) { switch (SDHC_DMA_TYPE(s->hostctl)) { From dcdb4cd8507d93ecad10ae54b28d9609453c0312 Mon Sep 17 00:00:00 2001 From: Peter Crosthwaite Date: Mon, 3 Jun 2013 17:17:44 +0100 Subject: [PATCH 17/24] sd/sdhci: Fix Buffer Write Ready interrupt This interrupt is not risen after the last block is written to sd. It is mutually exclusive with the end of transfer conditions. Fix. Signed-off-by: Peter Crosthwaite Reviewed-by: Igor Mitsyanko Message-id: 7ca9fd3e03ce1bec94aff08f607c15a0ec3d3371.1369370934.git.peter.crosthwaite@xilinx.com Signed-off-by: Peter Maydell --- hw/sd/sdhci.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/hw/sd/sdhci.c b/hw/sd/sdhci.c index 0a84540e12..ea510b53d0 100644 --- a/hw/sd/sdhci.c +++ b/hw/sd/sdhci.c @@ -405,15 +405,14 @@ static void sdhci_write_block_to_card(SDHCIState *s) /* Next data can be written through BUFFER DATORT register */ s->prnsts |= SDHC_SPACE_AVAILABLE; - if (s->norintstsen & SDHC_NISEN_WBUFRDY) { - s->norintsts |= SDHC_NIS_WBUFRDY; - } /* Finish transfer if that was the last block of data */ if ((s->trnmod & SDHC_TRNS_MULTI) == 0 || ((s->trnmod & SDHC_TRNS_MULTI) && (s->trnmod & SDHC_TRNS_BLK_CNT_EN) && (s->blkcnt == 0))) { SDHCI_GET_CLASS(s)->end_data_transfer(s); + } else if (s->norintstsen & SDHC_NISEN_WBUFRDY) { + s->norintsts |= SDHC_NIS_WBUFRDY; } /* Generate Block Gap Event if requested and if not the last block */ From 677ff2ae660b3a55f936a8836513cbab185f6e4f Mon Sep 17 00:00:00 2001 From: Peter Crosthwaite Date: Mon, 3 Jun 2013 17:17:45 +0100 Subject: [PATCH 18/24] sd/sdhci.c: Fix bdata_read DPRINT message This message was printing out the data in decimal only, which is not very friendly to the debugging developer. Add hex variant in parenthesis to make it consistent with other similar messages in this module. Signed-off-by: Peter Crosthwaite Reviewed-by: Igor Mitsyanko Reviewed-by: Peter Maydell Message-id: d624179649137832eaa8caa263ef9589b4395d5e.1369370934.git.peter.crosthwaite@xilinx.com Signed-off-by: Peter Maydell --- hw/sd/sdhci.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hw/sd/sdhci.c b/hw/sd/sdhci.c index ea510b53d0..15345dc071 100644 --- a/hw/sd/sdhci.c +++ b/hw/sd/sdhci.c @@ -880,7 +880,8 @@ static uint32_t sdhci_read(SDHCIState *s, unsigned int offset, unsigned size) case SDHC_BDATA: if (sdhci_buff_access_is_sequential(s, offset - SDHC_BDATA)) { ret = SDHCI_GET_CLASS(s)->bdata_read(s, size); - DPRINT_L2("read %ub: addr[0x%04x] -> %u\n", size, offset, ret); + DPRINT_L2("read %ub: addr[0x%04x] -> %u(0x%x)\n", size, offset, + ret, ret); return ret; } break; From 1d32c26f28d6e25f447b8ba40440c7d228ed4006 Mon Sep 17 00:00:00 2001 From: Peter Crosthwaite Date: Mon, 3 Jun 2013 17:17:45 +0100 Subject: [PATCH 19/24] sd/sdhci:ADMA: fix interrupt The end of transfer check was occurring and potentially returning before the interrupt flag was checked. This means the interrupt will be missed if it occurs on the last packet. Fix by checking for the interrupt before checking for the end of transfer. Signed-off-by: Peter Crosthwaite Reviewed-by: Igor Mitsyanko Message-id: 9969ec154777957ec738fc4e539d68e7494d0081.1369370934.git.peter.crosthwaite@xilinx.com Signed-off-by: Peter Maydell --- hw/sd/sdhci.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/hw/sd/sdhci.c b/hw/sd/sdhci.c index 15345dc071..e64899cafb 100644 --- a/hw/sd/sdhci.c +++ b/hw/sd/sdhci.c @@ -730,6 +730,15 @@ static void sdhci_do_adma(SDHCIState *s) break; } + if (dscr.attr & SDHC_ADMA_ATTR_INT) { + DPRINT_L1("ADMA interrupt: admasysaddr=0x%lx\n", s->admasysaddr); + if (s->norintstsen & SDHC_NISEN_DMA) { + s->norintsts |= SDHC_NIS_DMA; + } + + sdhci_update_irq(s); + } + /* ADMA transfer terminates if blkcnt == 0 or by END attribute */ if (((s->trnmod & SDHC_TRNS_BLK_CNT_EN) && (s->blkcnt == 0)) || (dscr.attr & SDHC_ADMA_ATTR_END)) { @@ -752,15 +761,6 @@ static void sdhci_do_adma(SDHCIState *s) return; } - if (dscr.attr & SDHC_ADMA_ATTR_INT) { - DPRINT_L1("ADMA interrupt: admasysaddr=0x%lx\n", s->admasysaddr); - if (s->norintstsen & SDHC_NISEN_DMA) { - s->norintsts |= SDHC_NIS_DMA; - } - - sdhci_update_irq(s); - return; - } } /* we have unfinished business - reschedule to continue ADMA */ From 37ab4a566816f518fb958ea49734d51d1ccbd227 Mon Sep 17 00:00:00 2001 From: Peter Crosthwaite Date: Mon, 3 Jun 2013 17:17:45 +0100 Subject: [PATCH 20/24] sd/sd.c: Fix "inquiry" ACMD41 QEMU models two (of the three) ACMD41 has two modes, "inquiry" and "first". The selection logic for which of the two is incorrect - it compares != 0 for the entire argument value rather than only bits 23:0 as per the spec. Fix. Signed-off-by: Peter Crosthwaite Message-id: 3ef0a7fd1b2f3ebb23b4fdeabcc14caf3fad6d71.1369622254.git.peter.crosthwaite@xilinx.com Reviewed-by: Igor Mitsyanko Signed-off-by: Peter Maydell --- hw/sd/sd.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/hw/sd/sd.c b/hw/sd/sd.c index 2e0ef3e5aa..346d86f69c 100644 --- a/hw/sd/sd.c +++ b/hw/sd/sd.c @@ -43,6 +43,8 @@ do { fprintf(stderr, "SD: " fmt , ## __VA_ARGS__); } while (0) #define DPRINTF(fmt, ...) do {} while(0) #endif +#define ACMD41_ENQUIRY_MASK 0x00ffffff + typedef enum { sd_r0 = 0, /* no response */ sd_r1, /* normal response command */ @@ -1277,9 +1279,14 @@ static sd_rsp_type_t sd_app_command(SDState *sd, } switch (sd->state) { case sd_idle_state: - /* We accept any voltage. 10000 V is nothing. */ - if (req.arg) + /* We accept any voltage. 10000 V is nothing. + * + * We don't model init delay so just advance straight to ready state + * unless it's an enquiry ACMD41 (bits 23:0 == 0). + */ + if (req.arg & ACMD41_ENQUIRY_MASK) { sd->state = sd_ready_state; + } return sd_r3; From a50c0d6f72830ca3016e63ded6ccfd4e951c172f Mon Sep 17 00:00:00 2001 From: Jean-Christophe DUBOIS Date: Mon, 3 Jun 2013 17:17:45 +0100 Subject: [PATCH 21/24] i.MX: split GPT and EPIT timer implementation There is no common code between these 2 timer implementation. So it is better to split them. Signed-off-by: Jean-Christophe DUBOIS Message-id: 1368990197-19694-1-git-send-email-jcd@tribudubois.net Signed-off-by: Peter Maydell --- hw/timer/Makefile.objs | 3 +- hw/timer/imx_epit.c | 404 ++++++++++++++++++++++++++++ hw/timer/{imx_timer.c => imx_gpt.c} | 366 +------------------------ 3 files changed, 410 insertions(+), 363 deletions(-) create mode 100644 hw/timer/imx_epit.c rename hw/timer/{imx_timer.c => imx_gpt.c} (58%) diff --git a/hw/timer/Makefile.objs b/hw/timer/Makefile.objs index e4bd17fbb7..32b5c1a9ba 100644 --- a/hw/timer/Makefile.objs +++ b/hw/timer/Makefile.objs @@ -11,7 +11,8 @@ common-obj-$(CONFIG_XILINX) += xilinx_timer.o common-obj-$(CONFIG_SLAVIO) += slavio_timer.o common-obj-$(CONFIG_ETRAXFS) += etraxfs_timer.o common-obj-$(CONFIG_GRLIB) += grlib_gptimer.o -common-obj-$(CONFIG_IMX) += imx_timer.o +common-obj-$(CONFIG_IMX) += imx_epit.o +common-obj-$(CONFIG_IMX) += imx_gpt.o common-obj-$(CONFIG_LM32) += lm32_timer.o common-obj-$(CONFIG_MILKYMIST) += milkymist-sysctl.o diff --git a/hw/timer/imx_epit.c b/hw/timer/imx_epit.c new file mode 100644 index 0000000000..451e64670e --- /dev/null +++ b/hw/timer/imx_epit.c @@ -0,0 +1,404 @@ +/* + * IMX EPIT Timer + * + * Copyright (c) 2008 OK Labs + * Copyright (c) 2011 NICTA Pty Ltd + * Originally written by Hans Jiang + * Updated by Peter Chubb + * + * This code is licensed under GPL version 2 or later. See + * the COPYING file in the top-level directory. + * + */ + +#include "hw/hw.h" +#include "qemu/bitops.h" +#include "qemu/timer.h" +#include "hw/ptimer.h" +#include "hw/sysbus.h" +#include "hw/arm/imx.h" + +//#define DEBUG_TIMER 1 +#ifdef DEBUG_TIMER +# define DPRINTF(fmt, args...) \ + do { printf("imx_timer: " fmt , ##args); } while (0) +#else +# define DPRINTF(fmt, args...) do {} while (0) +#endif + +/* + * Define to 1 for messages about attempts to + * access unimplemented registers or similar. + */ +#define DEBUG_IMPLEMENTATION 1 +#if DEBUG_IMPLEMENTATION +# define IPRINTF(fmt, args...) \ + do { fprintf(stderr, "imx_timer: " fmt, ##args); } while (0) +#else +# define IPRINTF(fmt, args...) do {} while (0) +#endif + +/* + * EPIT: Enhanced periodic interrupt timer + */ + +#define CR_EN (1 << 0) +#define CR_ENMOD (1 << 1) +#define CR_OCIEN (1 << 2) +#define CR_RLD (1 << 3) +#define CR_PRESCALE_SHIFT (4) +#define CR_PRESCALE_MASK (0xfff) +#define CR_SWR (1 << 16) +#define CR_IOVW (1 << 17) +#define CR_DBGEN (1 << 18) +#define CR_WAITEN (1 << 19) +#define CR_DOZEN (1 << 20) +#define CR_STOPEN (1 << 21) +#define CR_CLKSRC_SHIFT (24) +#define CR_CLKSRC_MASK (0x3 << CR_CLKSRC_SHIFT) + +#define TIMER_MAX 0XFFFFFFFFUL + +/* + * Exact clock frequencies vary from board to board. + * These are typical. + */ +static const IMXClk imx_timerp_clocks[] = { + 0, /* 00 disabled */ + IPG, /* 01 ipg_clk, ~532MHz */ + IPG, /* 10 ipg_clk_highfreq */ + CLK_32k, /* 11 ipg_clk_32k -- ~32kHz */ +}; + +typedef struct { + SysBusDevice busdev; + ptimer_state *timer_reload; + ptimer_state *timer_cmp; + MemoryRegion iomem; + DeviceState *ccm; + + uint32_t cr; + uint32_t sr; + uint32_t lr; + uint32_t cmp; + uint32_t cnt; + + uint32_t freq; + qemu_irq irq; +} IMXTimerPState; + +/* + * Update interrupt status + */ +static void imx_timerp_update(IMXTimerPState *s) +{ + if (s->sr && (s->cr & CR_OCIEN)) { + qemu_irq_raise(s->irq); + } else { + qemu_irq_lower(s->irq); + } +} + +static void set_timerp_freq(IMXTimerPState *s) +{ + unsigned clksrc; + unsigned prescaler; + uint32_t freq; + + clksrc = extract32(s->cr, CR_CLKSRC_SHIFT, 2); + prescaler = 1 + extract32(s->cr, CR_PRESCALE_SHIFT, 12); + + freq = imx_clock_frequency(s->ccm, imx_timerp_clocks[clksrc]) / prescaler; + + s->freq = freq; + DPRINTF("Setting ptimer frequency to %u\n", freq); + + if (freq) { + ptimer_set_freq(s->timer_reload, freq); + ptimer_set_freq(s->timer_cmp, freq); + } +} + +static void imx_timerp_reset(DeviceState *dev) +{ + IMXTimerPState *s = container_of(dev, IMXTimerPState, busdev.qdev); + + /* + * Soft reset doesn't touch some bits; hard reset clears them + */ + s->cr &= ~(CR_EN|CR_ENMOD|CR_STOPEN|CR_DOZEN|CR_WAITEN|CR_DBGEN); + s->sr = 0; + s->lr = TIMER_MAX; + s->cmp = 0; + s->cnt = 0; + /* stop both timers */ + ptimer_stop(s->timer_cmp); + ptimer_stop(s->timer_reload); + /* compute new frequency */ + set_timerp_freq(s); + /* init both timers to TIMER_MAX */ + ptimer_set_limit(s->timer_cmp, TIMER_MAX, 1); + ptimer_set_limit(s->timer_reload, TIMER_MAX, 1); + if (s->freq && (s->cr & CR_EN)) { + /* if the timer is still enabled, restart it */ + ptimer_run(s->timer_reload, 1); + } +} + +static uint32_t imx_timerp_update_counts(IMXTimerPState *s) +{ + s->cnt = ptimer_get_count(s->timer_reload); + + return s->cnt; +} + +static uint64_t imx_timerp_read(void *opaque, hwaddr offset, + unsigned size) +{ + IMXTimerPState *s = (IMXTimerPState *)opaque; + + DPRINTF("p-read(offset=%x)", (unsigned int)(offset >> 2)); + switch (offset >> 2) { + case 0: /* Control Register */ + DPRINTF("cr %x\n", s->cr); + return s->cr; + + case 1: /* Status Register */ + DPRINTF("sr %x\n", s->sr); + return s->sr; + + case 2: /* LR - ticks*/ + DPRINTF("lr %x\n", s->lr); + return s->lr; + + case 3: /* CMP */ + DPRINTF("cmp %x\n", s->cmp); + return s->cmp; + + case 4: /* CNT */ + imx_timerp_update_counts(s); + DPRINTF(" cnt = %x\n", s->cnt); + return s->cnt; + } + + IPRINTF("imx_timerp_read: Bad offset %x\n", + (int)offset >> 2); + return 0; +} + +static void imx_reload_compare_timer(IMXTimerPState *s) +{ + if ((s->cr & CR_OCIEN) && s->cmp) { + /* if the compare feature is on */ + uint32_t tmp = imx_timerp_update_counts(s); + if (tmp > s->cmp) { + /* reinit the cmp timer if required */ + ptimer_set_count(s->timer_cmp, tmp - s->cmp); + if ((s->cr & CR_EN)) { + /* Restart the cmp timer if required */ + ptimer_run(s->timer_cmp, 0); + } + } + } +} + +static void imx_timerp_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + IMXTimerPState *s = (IMXTimerPState *)opaque; + DPRINTF("p-write(offset=%x, value = %x)\n", (unsigned int)offset >> 2, + (unsigned int)value); + + switch (offset >> 2) { + case 0: /* CR */ + s->cr = value & 0x03ffffff; + if (s->cr & CR_SWR) { + /* handle the reset */ + imx_timerp_reset(&s->busdev.qdev); + } else { + set_timerp_freq(s); + } + + if (s->freq && (s->cr & CR_EN)) { + if (s->cr & CR_ENMOD) { + if (s->cr & CR_RLD) { + ptimer_set_limit(s->timer_reload, s->lr, 1); + } else { + ptimer_set_limit(s->timer_reload, TIMER_MAX, 1); + } + } + + imx_reload_compare_timer(s); + + ptimer_run(s->timer_reload, 1); + } else { + /* stop both timers */ + ptimer_stop(s->timer_reload); + ptimer_stop(s->timer_cmp); + } + break; + + case 1: /* SR - ACK*/ + /* writing 1 to OCIF clear the OCIF bit */ + if (value & 0x01) { + s->sr = 0; + imx_timerp_update(s); + } + break; + + case 2: /* LR - set ticks */ + s->lr = value; + + if (s->cr & CR_RLD) { + /* Also set the limit if the LRD bit is set */ + /* If IOVW bit is set then set the timer value */ + ptimer_set_limit(s->timer_reload, s->lr, s->cr & CR_IOVW); + } else if (s->cr & CR_IOVW) { + /* If IOVW bit is set then set the timer value */ + ptimer_set_count(s->timer_reload, s->lr); + } + + imx_reload_compare_timer(s); + + break; + + case 3: /* CMP */ + s->cmp = value; + + imx_reload_compare_timer(s); + + break; + + default: + IPRINTF("imx_timerp_write: Bad offset %x\n", + (int)offset >> 2); + } +} + +static void imx_timerp_reload(void *opaque) +{ + IMXTimerPState *s = (IMXTimerPState *)opaque; + + DPRINTF("imxp reload\n"); + + if (!(s->cr & CR_EN)) { + return; + } + + if (s->cr & CR_RLD) { + ptimer_set_limit(s->timer_reload, s->lr, 1); + } else { + ptimer_set_limit(s->timer_reload, TIMER_MAX, 1); + } + + if (s->cr & CR_OCIEN) { + /* if compare register is 0 then we handle the interrupt here */ + if (s->cmp == 0) { + s->sr = 1; + imx_timerp_update(s); + } else if (s->cmp <= s->lr) { + /* We should launch the compare register */ + ptimer_set_count(s->timer_cmp, s->lr - s->cmp); + ptimer_run(s->timer_cmp, 0); + } else { + IPRINTF("imxp reload: s->lr < s->cmp\n"); + } + } +} + +static void imx_timerp_cmp(void *opaque) +{ + IMXTimerPState *s = (IMXTimerPState *)opaque; + + DPRINTF("imxp compare\n"); + + ptimer_stop(s->timer_cmp); + + /* compare register is not 0 */ + if (s->cmp) { + s->sr = 1; + imx_timerp_update(s); + } +} + +void imx_timerp_create(const hwaddr addr, + qemu_irq irq, + DeviceState *ccm) +{ + IMXTimerPState *pp; + DeviceState *dev; + + dev = sysbus_create_simple("imx_timerp", addr, irq); + pp = container_of(dev, IMXTimerPState, busdev.qdev); + pp->ccm = ccm; +} + +static const MemoryRegionOps imx_timerp_ops = { + .read = imx_timerp_read, + .write = imx_timerp_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static const VMStateDescription vmstate_imx_timerp = { + .name = "imx-timerp", + .version_id = 2, + .minimum_version_id = 2, + .minimum_version_id_old = 2, + .fields = (VMStateField[]) { + VMSTATE_UINT32(cr, IMXTimerPState), + VMSTATE_UINT32(sr, IMXTimerPState), + VMSTATE_UINT32(lr, IMXTimerPState), + VMSTATE_UINT32(cmp, IMXTimerPState), + VMSTATE_UINT32(cnt, IMXTimerPState), + VMSTATE_UINT32(freq, IMXTimerPState), + VMSTATE_PTIMER(timer_reload, IMXTimerPState), + VMSTATE_PTIMER(timer_cmp, IMXTimerPState), + VMSTATE_END_OF_LIST() + } +}; + +static int imx_timerp_init(SysBusDevice *dev) +{ + IMXTimerPState *s = FROM_SYSBUS(IMXTimerPState, dev); + QEMUBH *bh; + + DPRINTF("imx_timerp_init\n"); + sysbus_init_irq(dev, &s->irq); + memory_region_init_io(&s->iomem, &imx_timerp_ops, + s, "imxp-timer", + 0x00001000); + sysbus_init_mmio(dev, &s->iomem); + + bh = qemu_bh_new(imx_timerp_reload, s); + s->timer_reload = ptimer_init(bh); + + bh = qemu_bh_new(imx_timerp_cmp, s); + s->timer_cmp = ptimer_init(bh); + + return 0; +} + + +static void imx_timerp_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + k->init = imx_timerp_init; + dc->vmsd = &vmstate_imx_timerp; + dc->reset = imx_timerp_reset; + dc->desc = "i.MX periodic timer"; +} + +static const TypeInfo imx_timerp_info = { + .name = "imx_timerp", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(IMXTimerPState), + .class_init = imx_timerp_class_init, +}; + +static void imx_timer_register_types(void) +{ + type_register_static(&imx_timerp_info); +} + +type_init(imx_timer_register_types) diff --git a/hw/timer/imx_timer.c b/hw/timer/imx_gpt.c similarity index 58% rename from hw/timer/imx_timer.c rename to hw/timer/imx_gpt.c index 7693bb7364..d8c4f0baee 100644 --- a/hw/timer/imx_timer.c +++ b/hw/timer/imx_gpt.c @@ -1,5 +1,5 @@ /* - * IMX31 Timer + * IMX GPT Timer * * Copyright (c) 2008 OK Labs * Copyright (c) 2011 NICTA Pty Ltd @@ -12,6 +12,7 @@ */ #include "hw/hw.h" +#include "qemu/bitops.h" #include "qemu/timer.h" #include "hw/ptimer.h" #include "hw/sysbus.h" @@ -54,7 +55,6 @@ * (free-running timer from 0 to OCR1 or TIMER_MAX) . */ - #define TIMER_MAX 0XFFFFFFFFUL /* Control register. Not all of these bits have any effect (yet) */ @@ -148,6 +148,7 @@ static void imx_timerg_set_freq(IMXTimerGState *s) freq = imx_clock_frequency(s->ccm, imx_timerg_clocks[clksrc]) / (1 + s->pr); DPRINTF("Setting gtimer clksrc %d to frequency %d\n", clksrc, freq); + if (freq) { ptimer_set_freq(s->timer, freq); } @@ -206,7 +207,7 @@ static uint64_t imx_timerg_read(void *opaque, hwaddr offset, { IMXTimerGState *s = (IMXTimerGState *)opaque; - DPRINTF("g-read(offset=%x)", offset >> 2); + DPRINTF("g-read(offset=%x)", (unsigned int)(offset >> 2)); switch (offset >> 2) { case 0: /* Control Register */ DPRINTF(" cr = %x\n", s->cr); @@ -427,347 +428,6 @@ static int imx_timerg_init(SysBusDevice *dev) return 0; } - - -/* - * EPIT: Enhanced periodic interrupt timer - */ - -#define CR_EN (1 << 0) -#define CR_ENMOD (1 << 1) -#define CR_OCIEN (1 << 2) -#define CR_RLD (1 << 3) -#define CR_PRESCALE_SHIFT (4) -#define CR_PRESCALE_MASK (0xfff) -#define CR_SWR (1 << 16) -#define CR_IOVW (1 << 17) -#define CR_DBGEN (1 << 18) -#define CR_WAITEN (1 << 19) -#define CR_DOZEN (1 << 20) -#define CR_STOPEN (1 << 21) -#define CR_CLKSRC_SHIFT (24) -#define CR_CLKSRC_MASK (0x3 << CR_CLKSRC_SHIFT) - - -/* - * Exact clock frequencies vary from board to board. - * These are typical. - */ -static const IMXClk imx_timerp_clocks[] = { - 0, /* 00 disabled */ - IPG, /* 01 ipg_clk, ~532MHz */ - IPG, /* 10 ipg_clk_highfreq */ - CLK_32k, /* 11 ipg_clk_32k -- ~32kHz */ -}; - -typedef struct { - SysBusDevice busdev; - ptimer_state *timer_reload; - ptimer_state *timer_cmp; - MemoryRegion iomem; - DeviceState *ccm; - - uint32_t cr; - uint32_t sr; - uint32_t lr; - uint32_t cmp; - uint32_t cnt; - - uint32_t freq; - qemu_irq irq; -} IMXTimerPState; - -/* - * Update interrupt status - */ -static void imx_timerp_update(IMXTimerPState *s) -{ - if (s->sr && (s->cr & CR_OCIEN)) { - qemu_irq_raise(s->irq); - } else { - qemu_irq_lower(s->irq); - } -} - -static void set_timerp_freq(IMXTimerPState *s) -{ - int clksrc; - unsigned prescaler; - uint32_t freq; - - clksrc = (s->cr & CR_CLKSRC_MASK) >> CR_CLKSRC_SHIFT; - prescaler = 1 + ((s->cr >> CR_PRESCALE_SHIFT) & CR_PRESCALE_MASK); - freq = imx_clock_frequency(s->ccm, imx_timerp_clocks[clksrc]) / prescaler; - - s->freq = freq; - DPRINTF("Setting ptimer frequency to %u\n", freq); - - if (freq) { - ptimer_set_freq(s->timer_reload, freq); - ptimer_set_freq(s->timer_cmp, freq); - } -} - -static void imx_timerp_reset(DeviceState *dev) -{ - IMXTimerPState *s = container_of(dev, IMXTimerPState, busdev.qdev); - - /* - * Soft reset doesn't touch some bits; hard reset clears them - */ - s->cr &= ~(CR_EN|CR_ENMOD|CR_STOPEN|CR_DOZEN|CR_WAITEN|CR_DBGEN); - s->sr = 0; - s->lr = TIMER_MAX; - s->cmp = 0; - s->cnt = 0; - /* stop both timers */ - ptimer_stop(s->timer_cmp); - ptimer_stop(s->timer_reload); - /* compute new frequency */ - set_timerp_freq(s); - /* init both timers to TIMER_MAX */ - ptimer_set_limit(s->timer_cmp, TIMER_MAX, 1); - ptimer_set_limit(s->timer_reload, TIMER_MAX, 1); - if (s->freq && (s->cr & CR_EN)) { - /* if the timer is still enabled, restart it */ - ptimer_run(s->timer_reload, 1); - } -} - -static uint32_t imx_timerp_update_counts(IMXTimerPState *s) -{ - s->cnt = ptimer_get_count(s->timer_reload); - - return s->cnt; -} - -static uint64_t imx_timerp_read(void *opaque, hwaddr offset, - unsigned size) -{ - IMXTimerPState *s = (IMXTimerPState *)opaque; - - DPRINTF("p-read(offset=%x)", offset >> 2); - switch (offset >> 2) { - case 0: /* Control Register */ - DPRINTF("cr %x\n", s->cr); - return s->cr; - - case 1: /* Status Register */ - DPRINTF("sr %x\n", s->sr); - return s->sr; - - case 2: /* LR - ticks*/ - DPRINTF("lr %x\n", s->lr); - return s->lr; - - case 3: /* CMP */ - DPRINTF("cmp %x\n", s->cmp); - return s->cmp; - - case 4: /* CNT */ - imx_timerp_update_counts(s); - DPRINTF(" cnt = %x\n", s->cnt); - return s->cnt; - } - - IPRINTF("imx_timerp_read: Bad offset %x\n", - (int)offset >> 2); - return 0; -} - -static void imx_reload_compare_timer(IMXTimerPState *s) -{ - if ((s->cr & CR_OCIEN) && s->cmp) { - /* if the compare feature is on */ - uint32_t tmp = imx_timerp_update_counts(s); - if (tmp > s->cmp) { - /* reinit the cmp timer if required */ - ptimer_set_count(s->timer_cmp, tmp - s->cmp); - if ((s->cr & CR_EN)) { - /* Restart the cmp timer if required */ - ptimer_run(s->timer_cmp, 0); - } - } - } -} - -static void imx_timerp_write(void *opaque, hwaddr offset, - uint64_t value, unsigned size) -{ - IMXTimerPState *s = (IMXTimerPState *)opaque; - DPRINTF("p-write(offset=%x, value = %x)\n", (unsigned int)offset >> 2, - (unsigned int)value); - - switch (offset >> 2) { - case 0: /* CR */ - s->cr = value & 0x03ffffff; - if (s->cr & CR_SWR) { - /* handle the reset */ - imx_timerp_reset(&s->busdev.qdev); - } else { - set_timerp_freq(s); - } - - if (s->freq && (s->cr & CR_EN)) { - if (s->cr & CR_ENMOD) { - if (s->cr & CR_RLD) { - ptimer_set_limit(s->timer_reload, s->lr, 1); - } else { - ptimer_set_limit(s->timer_reload, TIMER_MAX, 1); - } - } - - imx_reload_compare_timer(s); - - ptimer_run(s->timer_reload, 1); - } else { - /* stop both timers */ - ptimer_stop(s->timer_reload); - ptimer_stop(s->timer_cmp); - } - break; - - case 1: /* SR - ACK*/ - /* writing 1 to OCIF clear the OCIF bit */ - if (value & 0x01) { - s->sr = 0; - imx_timerp_update(s); - } - break; - - case 2: /* LR - set ticks */ - s->lr = value; - - if (s->cr & CR_RLD) { - /* Also set the limit if the LRD bit is set */ - /* If IOVW bit is set then set the timer value */ - ptimer_set_limit(s->timer_reload, s->lr, s->cr & CR_IOVW); - } else if (s->cr & CR_IOVW) { - /* If IOVW bit is set then set the timer value */ - ptimer_set_count(s->timer_reload, s->lr); - } - - imx_reload_compare_timer(s); - - break; - - case 3: /* CMP */ - s->cmp = value; - - imx_reload_compare_timer(s); - - break; - - default: - IPRINTF("imx_timerp_write: Bad offset %x\n", - (int)offset >> 2); - } -} - -static void imx_timerp_reload(void *opaque) -{ - IMXTimerPState *s = (IMXTimerPState *)opaque; - - DPRINTF("imxp reload\n"); - - if (!(s->cr & CR_EN)) { - return; - } - - if (s->cr & CR_RLD) { - ptimer_set_limit(s->timer_reload, s->lr, 1); - } else { - ptimer_set_limit(s->timer_reload, TIMER_MAX, 1); - } - - if (s->cr & CR_OCIEN) { - /* if compare register is 0 then we handle the interrupt here */ - if (s->cmp == 0) { - s->sr = 1; - imx_timerp_update(s); - } else if (s->cmp <= s->lr) { - /* We should launch the compare register */ - ptimer_set_count(s->timer_cmp, s->lr - s->cmp); - ptimer_run(s->timer_cmp, 0); - } else { - IPRINTF("imxp reload: s->lr < s->cmp\n"); - } - } -} - -static void imx_timerp_cmp(void *opaque) -{ - IMXTimerPState *s = (IMXTimerPState *)opaque; - - DPRINTF("imxp compare\n"); - - ptimer_stop(s->timer_cmp); - - /* compare register is not 0 */ - if (s->cmp) { - s->sr = 1; - imx_timerp_update(s); - } -} - -void imx_timerp_create(const hwaddr addr, - qemu_irq irq, - DeviceState *ccm) -{ - IMXTimerPState *pp; - DeviceState *dev; - - dev = sysbus_create_simple("imx_timerp", addr, irq); - pp = container_of(dev, IMXTimerPState, busdev.qdev); - pp->ccm = ccm; -} - -static const MemoryRegionOps imx_timerp_ops = { - .read = imx_timerp_read, - .write = imx_timerp_write, - .endianness = DEVICE_NATIVE_ENDIAN, -}; - -static const VMStateDescription vmstate_imx_timerp = { - .name = "imx-timerp", - .version_id = 2, - .minimum_version_id = 2, - .minimum_version_id_old = 2, - .fields = (VMStateField[]) { - VMSTATE_UINT32(cr, IMXTimerPState), - VMSTATE_UINT32(sr, IMXTimerPState), - VMSTATE_UINT32(lr, IMXTimerPState), - VMSTATE_UINT32(cmp, IMXTimerPState), - VMSTATE_UINT32(cnt, IMXTimerPState), - VMSTATE_UINT32(freq, IMXTimerPState), - VMSTATE_PTIMER(timer_reload, IMXTimerPState), - VMSTATE_PTIMER(timer_cmp, IMXTimerPState), - VMSTATE_END_OF_LIST() - } -}; - -static int imx_timerp_init(SysBusDevice *dev) -{ - IMXTimerPState *s = FROM_SYSBUS(IMXTimerPState, dev); - QEMUBH *bh; - - DPRINTF("imx_timerp_init\n"); - sysbus_init_irq(dev, &s->irq); - memory_region_init_io(&s->iomem, &imx_timerp_ops, - s, "imxp-timer", - 0x00001000); - sysbus_init_mmio(dev, &s->iomem); - - bh = qemu_bh_new(imx_timerp_reload, s); - s->timer_reload = ptimer_init(bh); - - bh = qemu_bh_new(imx_timerp_cmp, s); - s->timer_cmp = ptimer_init(bh); - - return 0; -} - - void imx_timerg_create(const hwaddr addr, qemu_irq irq, DeviceState *ccm) @@ -790,23 +450,6 @@ static void imx_timerg_class_init(ObjectClass *klass, void *data) dc->desc = "i.MX general timer"; } -static void imx_timerp_class_init(ObjectClass *klass, void *data) -{ - DeviceClass *dc = DEVICE_CLASS(klass); - SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); - k->init = imx_timerp_init; - dc->vmsd = &vmstate_imx_timerp; - dc->reset = imx_timerp_reset; - dc->desc = "i.MX periodic timer"; -} - -static const TypeInfo imx_timerp_info = { - .name = "imx_timerp", - .parent = TYPE_SYS_BUS_DEVICE, - .instance_size = sizeof(IMXTimerPState), - .class_init = imx_timerp_class_init, -}; - static const TypeInfo imx_timerg_info = { .name = "imx_timerg", .parent = TYPE_SYS_BUS_DEVICE, @@ -816,7 +459,6 @@ static const TypeInfo imx_timerg_info = { static void imx_timer_register_types(void) { - type_register_static(&imx_timerp_info); type_register_static(&imx_timerg_info); } From 11a5e48249d8c34211065c25ddf222f031a0338e Mon Sep 17 00:00:00 2001 From: Igor Mitsyanko Date: Mon, 3 Jun 2013 17:17:46 +0100 Subject: [PATCH 22/24] hw/arm/exynos4210.c: convert chipid_and_omr to an mmio region Exynos SoC was misusing memory_region_init_ram_ptr(): this interface can safely be used only for memory regions which size is a multiple of target page size. Change chipid_and_omr memory to an mmio region to fix this. Signed-off-by: Igor Mitsyanko Message-id: 1368199981-45292-2-git-send-email-i.mitsyanko@gmail.com Signed-off-by: Peter Maydell --- hw/arm/exynos4210.c | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/hw/arm/exynos4210.c b/hw/arm/exynos4210.c index c8101d3e84..502b1069c0 100644 --- a/hw/arm/exynos4210.c +++ b/hw/arm/exynos4210.c @@ -79,6 +79,28 @@ static uint8_t chipid_and_omr[] = { 0x11, 0x02, 0x21, 0x43, 0x09, 0x00, 0x00, 0x00 }; +static uint64_t exynos4210_chipid_and_omr_read(void *opaque, hwaddr offset, + unsigned size) +{ + assert(offset < sizeof(chipid_and_omr)); + return chipid_and_omr[offset]; +} + +static void exynos4210_chipid_and_omr_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + return; +} + +static const MemoryRegionOps exynos4210_chipid_and_omr_ops = { + .read = exynos4210_chipid_and_omr_read, + .write = exynos4210_chipid_and_omr_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .impl = { + .max_access_size = 1, + } +}; + void exynos4210_write_secondary(ARMCPU *cpu, const struct arm_boot_info *info) { @@ -219,9 +241,8 @@ Exynos4210State *exynos4210_init(MemoryRegion *system_mem, /*** Memory ***/ /* Chip-ID and OMR */ - memory_region_init_ram_ptr(&s->chipid_mem, "exynos4210.chipid", - sizeof(chipid_and_omr), chipid_and_omr); - memory_region_set_readonly(&s->chipid_mem, true); + memory_region_init_io(&s->chipid_mem, &exynos4210_chipid_and_omr_ops, + NULL, "exynos4210.chipid", sizeof(chipid_and_omr)); memory_region_add_subregion(system_mem, EXYNOS4210_CHIPID_ADDR, &s->chipid_mem); From 6539ed21b1d5c93e8add2f7b2bf9856889e6d1ac Mon Sep 17 00:00:00 2001 From: Igor Mitsyanko Date: Mon, 3 Jun 2013 17:17:46 +0100 Subject: [PATCH 23/24] exynos4210.c: register rom_mem for memory migration Even if we do not register newly created RAM MemoryRegion for migration with vmstate_register_ram_global() function, ram_save_setup() still saves this region to snapshot file with empty idstr=="". Consequently this results in error during VM loading in ram_load(). Register rom_mem for migration. Signed-off-by: Igor Mitsyanko Message-id: 1368199981-45292-3-git-send-email-i.mitsyanko@gmail.com Signed-off-by: Peter Maydell --- hw/arm/exynos4210.c | 1 + 1 file changed, 1 insertion(+) diff --git a/hw/arm/exynos4210.c b/hw/arm/exynos4210.c index 502b1069c0..8186f1486b 100644 --- a/hw/arm/exynos4210.c +++ b/hw/arm/exynos4210.c @@ -249,6 +249,7 @@ Exynos4210State *exynos4210_init(MemoryRegion *system_mem, /* Internal ROM */ memory_region_init_ram(&s->irom_mem, "exynos4210.irom", EXYNOS4210_IROM_SIZE); + vmstate_register_ram_global(&s->irom_mem); memory_region_set_readonly(&s->irom_mem, true); memory_region_add_subregion(system_mem, EXYNOS4210_IROM_BASE_ADDR, &s->irom_mem); From 95669e69848eda87861e1ec3016562101542f543 Mon Sep 17 00:00:00 2001 From: Jean-Christophe DUBOIS Date: Mon, 3 Jun 2013 17:17:46 +0100 Subject: [PATCH 24/24] i.MX: Improve EPIT timer code. * Unify function and type naming * use dynamic cast whenever possible * simplify Debug printf. * use new style device intialization. Signed-off-by: Jean-Christophe DUBOIS Reviewed-by: Peter Chubb Message-id: 1369839656-24466-1-git-send-email-jcd@tribudubois.net Signed-off-by: Peter Maydell --- hw/timer/imx_epit.c | 238 +++++++++++++++++++++++++------------------- 1 file changed, 133 insertions(+), 105 deletions(-) diff --git a/hw/timer/imx_epit.c b/hw/timer/imx_epit.c index 451e64670e..7cdb0060ab 100644 --- a/hw/timer/imx_epit.c +++ b/hw/timer/imx_epit.c @@ -5,6 +5,7 @@ * Copyright (c) 2011 NICTA Pty Ltd * Originally written by Hans Jiang * Updated by Peter Chubb + * Updated by Jean-Christophe Dubois * * This code is licensed under GPL version 2 or later. See * the COPYING file in the top-level directory. @@ -18,10 +19,31 @@ #include "hw/sysbus.h" #include "hw/arm/imx.h" -//#define DEBUG_TIMER 1 -#ifdef DEBUG_TIMER +#define TYPE_IMX_EPIT "imx.epit" + +#define DEBUG_TIMER 0 +#if DEBUG_TIMER + +static char const *imx_epit_reg_name(uint32_t reg) +{ + switch (reg) { + case 0: + return "CR"; + case 1: + return "SR"; + case 2: + return "LR"; + case 3: + return "CMP"; + case 4: + return "CNT"; + default: + return "[?]"; + } +} + # define DPRINTF(fmt, args...) \ - do { printf("imx_timer: " fmt , ##args); } while (0) + do { printf("%s: " fmt , __func__, ##args); } while (0) #else # define DPRINTF(fmt, args...) do {} while (0) #endif @@ -32,12 +54,15 @@ */ #define DEBUG_IMPLEMENTATION 1 #if DEBUG_IMPLEMENTATION -# define IPRINTF(fmt, args...) \ - do { fprintf(stderr, "imx_timer: " fmt, ##args); } while (0) +# define IPRINTF(fmt, args...) \ + do { fprintf(stderr, "%s: " fmt, __func__, ##args); } while (0) #else # define IPRINTF(fmt, args...) do {} while (0) #endif +#define IMX_EPIT(obj) \ + OBJECT_CHECK(IMXEPITState, (obj), TYPE_IMX_EPIT) + /* * EPIT: Enhanced periodic interrupt timer */ @@ -63,7 +88,7 @@ * Exact clock frequencies vary from board to board. * These are typical. */ -static const IMXClk imx_timerp_clocks[] = { +static const IMXClk imx_epit_clocks[] = { 0, /* 00 disabled */ IPG, /* 01 ipg_clk, ~532MHz */ IPG, /* 10 ipg_clk_highfreq */ @@ -85,32 +110,33 @@ typedef struct { uint32_t freq; qemu_irq irq; -} IMXTimerPState; +} IMXEPITState; /* * Update interrupt status */ -static void imx_timerp_update(IMXTimerPState *s) +static void imx_epit_update_int(IMXEPITState *s) { - if (s->sr && (s->cr & CR_OCIEN)) { + if (s->sr && (s->cr & CR_OCIEN) && (s->cr & CR_EN)) { qemu_irq_raise(s->irq); } else { qemu_irq_lower(s->irq); } } -static void set_timerp_freq(IMXTimerPState *s) +static void imx_epit_set_freq(IMXEPITState *s) { - unsigned clksrc; - unsigned prescaler; + uint32_t clksrc; + uint32_t prescaler; uint32_t freq; clksrc = extract32(s->cr, CR_CLKSRC_SHIFT, 2); prescaler = 1 + extract32(s->cr, CR_PRESCALE_SHIFT, 12); - freq = imx_clock_frequency(s->ccm, imx_timerp_clocks[clksrc]) / prescaler; + freq = imx_clock_frequency(s->ccm, imx_epit_clocks[clksrc]) / prescaler; s->freq = freq; + DPRINTF("Setting ptimer frequency to %u\n", freq); if (freq) { @@ -119,9 +145,9 @@ static void set_timerp_freq(IMXTimerPState *s) } } -static void imx_timerp_reset(DeviceState *dev) +static void imx_epit_reset(DeviceState *dev) { - IMXTimerPState *s = container_of(dev, IMXTimerPState, busdev.qdev); + IMXEPITState *s = IMX_EPIT(dev); /* * Soft reset doesn't touch some bits; hard reset clears them @@ -135,7 +161,7 @@ static void imx_timerp_reset(DeviceState *dev) ptimer_stop(s->timer_cmp); ptimer_stop(s->timer_reload); /* compute new frequency */ - set_timerp_freq(s); + imx_epit_set_freq(s); /* init both timers to TIMER_MAX */ ptimer_set_limit(s->timer_cmp, TIMER_MAX, 1); ptimer_set_limit(s->timer_reload, TIMER_MAX, 1); @@ -145,52 +171,56 @@ static void imx_timerp_reset(DeviceState *dev) } } -static uint32_t imx_timerp_update_counts(IMXTimerPState *s) +static uint32_t imx_epit_update_count(IMXEPITState *s) { s->cnt = ptimer_get_count(s->timer_reload); return s->cnt; } -static uint64_t imx_timerp_read(void *opaque, hwaddr offset, - unsigned size) +static uint64_t imx_epit_read(void *opaque, hwaddr offset, unsigned size) { - IMXTimerPState *s = (IMXTimerPState *)opaque; + IMXEPITState *s = IMX_EPIT(opaque); + uint32_t reg_value = 0; + uint32_t reg = offset >> 2; - DPRINTF("p-read(offset=%x)", (unsigned int)(offset >> 2)); - switch (offset >> 2) { + switch (reg) { case 0: /* Control Register */ - DPRINTF("cr %x\n", s->cr); - return s->cr; + reg_value = s->cr; + break; case 1: /* Status Register */ - DPRINTF("sr %x\n", s->sr); - return s->sr; + reg_value = s->sr; + break; case 2: /* LR - ticks*/ - DPRINTF("lr %x\n", s->lr); - return s->lr; + reg_value = s->lr; + break; case 3: /* CMP */ - DPRINTF("cmp %x\n", s->cmp); - return s->cmp; + reg_value = s->cmp; + break; case 4: /* CNT */ - imx_timerp_update_counts(s); - DPRINTF(" cnt = %x\n", s->cnt); - return s->cnt; + imx_epit_update_count(s); + reg_value = s->cnt; + break; + + default: + IPRINTF("Bad offset %x\n", reg); + break; } - IPRINTF("imx_timerp_read: Bad offset %x\n", - (int)offset >> 2); - return 0; + DPRINTF("(%s) = 0x%08x\n", imx_epit_reg_name(reg), reg_value); + + return reg_value; } -static void imx_reload_compare_timer(IMXTimerPState *s) +static void imx_epit_reload_compare_timer(IMXEPITState *s) { if ((s->cr & CR_OCIEN) && s->cmp) { /* if the compare feature is on */ - uint32_t tmp = imx_timerp_update_counts(s); + uint32_t tmp = imx_epit_update_count(s); if (tmp > s->cmp) { /* reinit the cmp timer if required */ ptimer_set_count(s->timer_cmp, tmp - s->cmp); @@ -202,21 +232,22 @@ static void imx_reload_compare_timer(IMXTimerPState *s) } } -static void imx_timerp_write(void *opaque, hwaddr offset, - uint64_t value, unsigned size) +static void imx_epit_write(void *opaque, hwaddr offset, uint64_t value, + unsigned size) { - IMXTimerPState *s = (IMXTimerPState *)opaque; - DPRINTF("p-write(offset=%x, value = %x)\n", (unsigned int)offset >> 2, - (unsigned int)value); + IMXEPITState *s = IMX_EPIT(opaque); + uint32_t reg = offset >> 2; - switch (offset >> 2) { + DPRINTF("(%s, value = 0x%08x)\n", imx_epit_reg_name(reg), (uint32_t)value); + + switch (reg) { case 0: /* CR */ s->cr = value & 0x03ffffff; if (s->cr & CR_SWR) { /* handle the reset */ - imx_timerp_reset(&s->busdev.qdev); + imx_epit_reset(DEVICE(s)); } else { - set_timerp_freq(s); + imx_epit_set_freq(s); } if (s->freq && (s->cr & CR_EN)) { @@ -228,7 +259,7 @@ static void imx_timerp_write(void *opaque, hwaddr offset, } } - imx_reload_compare_timer(s); + imx_epit_reload_compare_timer(s); ptimer_run(s->timer_reload, 1); } else { @@ -242,7 +273,7 @@ static void imx_timerp_write(void *opaque, hwaddr offset, /* writing 1 to OCIF clear the OCIF bit */ if (value & 0x01) { s->sr = 0; - imx_timerp_update(s); + imx_epit_update_int(s); } break; @@ -258,28 +289,29 @@ static void imx_timerp_write(void *opaque, hwaddr offset, ptimer_set_count(s->timer_reload, s->lr); } - imx_reload_compare_timer(s); + imx_epit_reload_compare_timer(s); break; case 3: /* CMP */ s->cmp = value; - imx_reload_compare_timer(s); + imx_epit_reload_compare_timer(s); break; default: - IPRINTF("imx_timerp_write: Bad offset %x\n", - (int)offset >> 2); + IPRINTF("Bad offset %x\n", reg); + + break; } } -static void imx_timerp_reload(void *opaque) +static void imx_epit_timeout(void *opaque) { - IMXTimerPState *s = (IMXTimerPState *)opaque; + IMXEPITState *s = IMX_EPIT(opaque); - DPRINTF("imxp reload\n"); + DPRINTF("\n"); if (!(s->cr & CR_EN)) { return; @@ -295,110 +327,106 @@ static void imx_timerp_reload(void *opaque) /* if compare register is 0 then we handle the interrupt here */ if (s->cmp == 0) { s->sr = 1; - imx_timerp_update(s); + imx_epit_update_int(s); } else if (s->cmp <= s->lr) { /* We should launch the compare register */ ptimer_set_count(s->timer_cmp, s->lr - s->cmp); ptimer_run(s->timer_cmp, 0); } else { - IPRINTF("imxp reload: s->lr < s->cmp\n"); + IPRINTF("s->lr < s->cmp\n"); } } } -static void imx_timerp_cmp(void *opaque) +static void imx_epit_cmp(void *opaque) { - IMXTimerPState *s = (IMXTimerPState *)opaque; + IMXEPITState *s = IMX_EPIT(opaque); - DPRINTF("imxp compare\n"); + DPRINTF("\n"); ptimer_stop(s->timer_cmp); /* compare register is not 0 */ if (s->cmp) { s->sr = 1; - imx_timerp_update(s); + imx_epit_update_int(s); } } -void imx_timerp_create(const hwaddr addr, - qemu_irq irq, - DeviceState *ccm) +void imx_timerp_create(const hwaddr addr, qemu_irq irq, DeviceState *ccm) { - IMXTimerPState *pp; + IMXEPITState *pp; DeviceState *dev; - dev = sysbus_create_simple("imx_timerp", addr, irq); - pp = container_of(dev, IMXTimerPState, busdev.qdev); + dev = sysbus_create_simple(TYPE_IMX_EPIT, addr, irq); + pp = IMX_EPIT(dev); pp->ccm = ccm; } -static const MemoryRegionOps imx_timerp_ops = { - .read = imx_timerp_read, - .write = imx_timerp_write, +static const MemoryRegionOps imx_epit_ops = { + .read = imx_epit_read, + .write = imx_epit_write, .endianness = DEVICE_NATIVE_ENDIAN, }; -static const VMStateDescription vmstate_imx_timerp = { - .name = "imx-timerp", +static const VMStateDescription vmstate_imx_timer_epit = { + .name = TYPE_IMX_EPIT, .version_id = 2, .minimum_version_id = 2, .minimum_version_id_old = 2, .fields = (VMStateField[]) { - VMSTATE_UINT32(cr, IMXTimerPState), - VMSTATE_UINT32(sr, IMXTimerPState), - VMSTATE_UINT32(lr, IMXTimerPState), - VMSTATE_UINT32(cmp, IMXTimerPState), - VMSTATE_UINT32(cnt, IMXTimerPState), - VMSTATE_UINT32(freq, IMXTimerPState), - VMSTATE_PTIMER(timer_reload, IMXTimerPState), - VMSTATE_PTIMER(timer_cmp, IMXTimerPState), + VMSTATE_UINT32(cr, IMXEPITState), + VMSTATE_UINT32(sr, IMXEPITState), + VMSTATE_UINT32(lr, IMXEPITState), + VMSTATE_UINT32(cmp, IMXEPITState), + VMSTATE_UINT32(cnt, IMXEPITState), + VMSTATE_UINT32(freq, IMXEPITState), + VMSTATE_PTIMER(timer_reload, IMXEPITState), + VMSTATE_PTIMER(timer_cmp, IMXEPITState), VMSTATE_END_OF_LIST() } }; -static int imx_timerp_init(SysBusDevice *dev) +static void imx_epit_realize(DeviceState *dev, Error **errp) { - IMXTimerPState *s = FROM_SYSBUS(IMXTimerPState, dev); + IMXEPITState *s = IMX_EPIT(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); QEMUBH *bh; - DPRINTF("imx_timerp_init\n"); - sysbus_init_irq(dev, &s->irq); - memory_region_init_io(&s->iomem, &imx_timerp_ops, - s, "imxp-timer", - 0x00001000); - sysbus_init_mmio(dev, &s->iomem); + DPRINTF("\n"); - bh = qemu_bh_new(imx_timerp_reload, s); + sysbus_init_irq(sbd, &s->irq); + memory_region_init_io(&s->iomem, &imx_epit_ops, s, TYPE_IMX_EPIT, + 0x00001000); + sysbus_init_mmio(sbd, &s->iomem); + + bh = qemu_bh_new(imx_epit_timeout, s); s->timer_reload = ptimer_init(bh); - bh = qemu_bh_new(imx_timerp_cmp, s); + bh = qemu_bh_new(imx_epit_cmp, s); s->timer_cmp = ptimer_init(bh); - - return 0; } - -static void imx_timerp_class_init(ObjectClass *klass, void *data) +static void imx_epit_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); - SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); - k->init = imx_timerp_init; - dc->vmsd = &vmstate_imx_timerp; - dc->reset = imx_timerp_reset; + + dc->realize = imx_epit_realize; + dc->reset = imx_epit_reset; + dc->vmsd = &vmstate_imx_timer_epit; dc->desc = "i.MX periodic timer"; } -static const TypeInfo imx_timerp_info = { - .name = "imx_timerp", +static const TypeInfo imx_epit_info = { + .name = TYPE_IMX_EPIT, .parent = TYPE_SYS_BUS_DEVICE, - .instance_size = sizeof(IMXTimerPState), - .class_init = imx_timerp_class_init, + .instance_size = sizeof(IMXEPITState), + .class_init = imx_epit_class_init, }; -static void imx_timer_register_types(void) +static void imx_epit_register_types(void) { - type_register_static(&imx_timerp_info); + type_register_static(&imx_epit_info); } -type_init(imx_timer_register_types) +type_init(imx_epit_register_types)