* [Qemu-devel] [PATCH V4 1/5] SSI: Built in multiple device support
2012-06-04 8:08 [Qemu-devel] [PATCH V4 0/5] Ehnahced SSI bus support + M25P80 SPI flash + Xilinx SPI controller Peter A. G. Crosthwaite
@ 2012-06-04 8:08 ` Peter A. G. Crosthwaite
2012-06-04 8:08 ` [Qemu-devel] [PATCH V4 2/5] m25p80: initial verion Peter A. G. Crosthwaite
` (4 subsequent siblings)
5 siblings, 0 replies; 12+ messages in thread
From: Peter A. G. Crosthwaite @ 2012-06-04 8:08 UTC (permalink / raw)
To: qemu-devel, paul, edgar.iglesias, peter.maydell, stefanha
Cc: peter.crosthwaite, john.williams
Added support for multiple devices attached to a single SSI bus (Previously
SSI masters with multiple slaves were emulated as multiple point to point SSI
busses)
Signed-off-by: Peter A. G. Crosthwaite <peter.crosthwaite@petalogix.com>
---
changed from v3:
added ssi_create_slave_noinit
changed from v2:
This patch is new (totally rewitten replacement of (1/4) from v2)
hw/spitz.c | 8 ++--
hw/ssi.c | 107 ++++++++++++++++++++++++++++++++++++++++++++++++--------
hw/ssi.h | 28 ++++++++++++--
hw/stellaris.c | 6 ++--
hw/tosa.c | 2 +-
hw/z2.c | 2 +-
6 files changed, 125 insertions(+), 28 deletions(-)
diff --git a/hw/spitz.c b/hw/spitz.c
index 1d6d2b0..f63a9bf 100644
--- a/hw/spitz.c
+++ b/hw/spitz.c
@@ -669,18 +669,18 @@ static void spitz_ssp_attach(PXA2xxState *cpu)
DeviceState *dev;
void *bus;
- mux = ssi_create_slave(cpu->ssp[CORGI_SSP_PORT - 1], "corgi-ssp");
+ mux = ssi_create_slave(cpu->ssp[CORGI_SSP_PORT - 1], "corgi-ssp", 0);
bus = qdev_get_child_bus(mux, "ssi0");
- ssi_create_slave(bus, "spitz-lcdtg");
+ ssi_create_slave(bus, "spitz-lcdtg", 0);
bus = qdev_get_child_bus(mux, "ssi1");
- dev = ssi_create_slave(bus, "ads7846");
+ dev = ssi_create_slave(bus, "ads7846", 0);
qdev_connect_gpio_out(dev, 0,
qdev_get_gpio_in(cpu->gpio, SPITZ_GPIO_TP_INT));
bus = qdev_get_child_bus(mux, "ssi2");
- max1111 = ssi_create_slave(bus, "max1111");
+ max1111 = ssi_create_slave(bus, "max1111", 0);
max111x_set_input(max1111, MAX1111_BATT_VOLT, SPITZ_BATTERY_VOLT);
max111x_set_input(max1111, MAX1111_BATT_TEMP, 0);
max111x_set_input(max1111, MAX1111_ACIN_VOLT, SPITZ_CHARGEON_ACIN);
diff --git a/hw/ssi.c b/hw/ssi.c
index 8f2d9bc..af7e887 100644
--- a/hw/ssi.c
+++ b/hw/ssi.c
@@ -2,6 +2,8 @@
* QEMU Synchronous Serial Interface support
*
* Copyright (c) 2009 CodeSourcery.
+ * Copyright (c) 2012 Peter A.G. Crosthwaite (peter.crosthwaite@petalogix.com)
+ * Copyright (c) 2012 PetaLogix Pty Ltd.
* Written by Paul Brook
*
* This code is licensed under the GNU GPL v2.
@@ -14,24 +16,33 @@
struct SSIBus {
BusState qbus;
+ int32_t selected_slave;
};
static struct BusInfo ssi_bus_info = {
.name = "SSI",
.size = sizeof(SSIBus),
+ .props = (Property[]) {
+ DEFINE_PROP_INT32("slave_select", struct SSISlave, slave_select, 0),
+ DEFINE_PROP_END_OF_LIST(),
+ }
+};
+
+static const VMStateDescription vmstate_ssi_bus = {
+ .name = "ssi_bus",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(selected_slave, SSIBus),
+ VMSTATE_END_OF_LIST()
+ }
};
static int ssi_slave_init(DeviceState *dev)
{
SSISlave *s = SSI_SLAVE(dev);
SSISlaveClass *ssc = SSI_SLAVE_GET_CLASS(s);
- SSIBus *bus;
-
- bus = FROM_QBUS(SSIBus, qdev_get_parent_bus(dev));
- if (QTAILQ_FIRST(&bus->qbus.children) != dev
- || QTAILQ_NEXT(dev, sibling) != NULL) {
- hw_error("Too many devices on SSI bus");
- }
return ssc->init(s);
}
@@ -46,40 +57,106 @@ static void ssi_slave_class_init(ObjectClass *klass, void *data)
static TypeInfo ssi_slave_info = {
.name = TYPE_SSI_SLAVE,
.parent = TYPE_DEVICE,
+ .instance_size = sizeof(struct SSISlave),
.class_init = ssi_slave_class_init,
.class_size = sizeof(SSISlaveClass),
.abstract = true,
};
-DeviceState *ssi_create_slave(SSIBus *bus, const char *name)
+DeviceState *ssi_create_slave_no_init(SSIBus *bus, const char *name,
+ int32_t slave_select)
{
DeviceState *dev;
dev = qdev_create(&bus->qbus, name);
+ qdev_prop_set_int32(dev, "slave_select", slave_select);
+ return dev;
+}
+
+DeviceState *ssi_create_slave(SSIBus *bus, const char *name,
+ int32_t slave_select)
+{
+ DeviceState *dev;
+ dev = ssi_create_slave_no_init(bus, name, slave_select);
qdev_init_nofail(dev);
return dev;
+
}
SSIBus *ssi_create_bus(DeviceState *parent, const char *name)
{
- BusState *bus;
- bus = qbus_create(&ssi_bus_info, parent, name);
- return FROM_QBUS(SSIBus, bus);
+ SSIBus *bus;
+
+ bus = FROM_QBUS(SSIBus, qbus_create(&ssi_bus_info, parent, name));
+ vmstate_register(NULL, -1, &vmstate_ssi_bus, bus);
+ return bus;
+}
+
+static SSISlave *get_current_slave(SSIBus *bus)
+{
+ DeviceState *qdev;
+
+ QTAILQ_FOREACH(qdev, &bus->qbus.children, sibling) {
+ SSISlave *candidate = SSI_SLAVE_FROM_QDEV(qdev);
+ if (candidate->slave_select == bus->selected_slave) {
+ return candidate;
+ }
+ }
+
+ return NULL;
+}
+
+void ssi_select_slave(SSIBus *bus, int32_t selected_slave)
+{
+ SSISlave *slave;
+ SSISlaveClass *ssc;
+
+ if (bus->selected_slave == selected_slave) {
+ return;
+ }
+
+ slave = get_current_slave(bus);
+ if (slave) {
+ ssc = SSI_SLAVE_GET_CLASS(slave);
+ if (ssc->set_cs) {
+ ssc->set_cs(slave, 0);
+ }
+ }
+ bus->selected_slave = selected_slave;
+
+ slave = get_current_slave(bus);
+ if (slave) {
+ ssc = SSI_SLAVE_GET_CLASS(slave);
+ if (ssc->set_cs) {
+ ssc->set_cs(slave, 1);
+ }
+ }
+
}
uint32_t ssi_transfer(SSIBus *bus, uint32_t val)
{
- DeviceState *dev;
SSISlave *slave;
SSISlaveClass *ssc;
- dev = QTAILQ_FIRST(&bus->qbus.children);
- if (!dev) {
+
+ slave = get_current_slave(bus);
+ if (!slave) {
return 0;
}
- slave = SSI_SLAVE(dev);
ssc = SSI_SLAVE_GET_CLASS(slave);
return ssc->transfer(slave, val);
}
+const VMStateDescription vmstate_ssi_slave = {
+ .name = "SSISlave",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(slave_select, SSISlave),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
static void ssi_slave_register_types(void)
{
type_register_static(&ssi_slave_info);
diff --git a/hw/ssi.h b/hw/ssi.h
index 06657d7..7f0ea36 100644
--- a/hw/ssi.h
+++ b/hw/ssi.h
@@ -1,10 +1,10 @@
/* QEMU Synchronous Serial Interface support. */
-/* In principle SSI is a point-point interface. As such the qemu
- implementation has a single slave device on a "bus".
+/* In principle SSI is a point-point interface.
However it is fairly common for boards to have multiple slaves
connected to a single master, and select devices with an external
- chip select. This is implemented in qemu by having an explicit mux device.
+ chip select. SSI busses can therefore have any number of slaves,
+ of which a master can select using ssi_select_slave().
It is assumed that master and slave are both using the same transfer width.
*/
@@ -29,22 +29,42 @@ typedef struct SSISlaveClass {
int (*init)(SSISlave *dev);
uint32_t (*transfer)(SSISlave *dev, uint32_t val);
+ int (*set_cs)(SSISlave *dev, int state);
} SSISlaveClass;
struct SSISlave {
DeviceState qdev;
+
+ int32_t slave_select;
};
#define SSI_SLAVE_FROM_QDEV(dev) DO_UPCAST(SSISlave, qdev, dev)
#define FROM_SSI_SLAVE(type, dev) DO_UPCAST(type, ssidev, dev)
-DeviceState *ssi_create_slave(SSIBus *bus, const char *name);
+DeviceState *ssi_create_slave(SSIBus *bus, const char *name,
+ int32_t slave_select);
+DeviceState *ssi_create_slave_no_init(SSIBus *bus, const char *name,
+ int32_t slave_select);
/* Master interface. */
SSIBus *ssi_create_bus(DeviceState *parent, const char *name);
+#define SSI_SLAVE_SELECT_NONE (-1)
+
+void ssi_select_slave(SSIBus *bus, int32_t selected_slave);
+
uint32_t ssi_transfer(SSIBus *bus, uint32_t val);
+extern const VMStateDescription vmstate_ssi_slave;
+
+#define VMSTATE_SSI_SLAVE(_field, _state) { \
+ .name = (stringify(_field)), \
+ .size = sizeof(SSISlave), \
+ .vmsd = &vmstate_ssi_slave, \
+ .flags = VMS_STRUCT, \
+ .offset = vmstate_offset_value(_state, _field, SSISlave), \
+}
+
/* max111x.c */
void max111x_set_input(DeviceState *dev, int line, uint8_t value);
diff --git a/hw/stellaris.c b/hw/stellaris.c
index 562fbbf..e0600a1 100644
--- a/hw/stellaris.c
+++ b/hw/stellaris.c
@@ -1309,14 +1309,14 @@ static void stellaris_init(const char *kernel_filename, const char *cpu_model,
void *bus;
bus = qdev_get_child_bus(dev, "ssi");
- mux = ssi_create_slave(bus, "evb6965-ssi");
+ mux = ssi_create_slave(bus, "evb6965-ssi", 0);
gpio_out[GPIO_D][0] = qdev_get_gpio_in(mux, 0);
bus = qdev_get_child_bus(mux, "ssi0");
- ssi_create_slave(bus, "ssi-sd");
+ ssi_create_slave(bus, "ssi-sd", 0);
bus = qdev_get_child_bus(mux, "ssi1");
- dev = ssi_create_slave(bus, "ssd0323");
+ dev = ssi_create_slave(bus, "ssd0323", 0);
gpio_out[GPIO_C][7] = qdev_get_gpio_in(dev, 0);
/* Make sure the select pin is high. */
diff --git a/hw/tosa.c b/hw/tosa.c
index 6baa17d..3986810 100644
--- a/hw/tosa.c
+++ b/hw/tosa.c
@@ -196,7 +196,7 @@ static void tosa_tg_init(PXA2xxState *cpu)
{
i2c_bus *bus = pxa2xx_i2c_bus(cpu->i2c[0]);
i2c_create_slave(bus, "tosa_dac", DAC_BASE);
- ssi_create_slave(cpu->ssp[1], "tosa-ssp");
+ ssi_create_slave(cpu->ssp[1], "tosa-ssp", 0);
}
diff --git a/hw/z2.c b/hw/z2.c
index 654ac55..2a0ecf5 100644
--- a/hw/z2.c
+++ b/hw/z2.c
@@ -346,7 +346,7 @@ static void z2_init(ram_addr_t ram_size,
type_register_static(&zipit_lcd_info);
type_register_static(&aer915_info);
- z2_lcd = ssi_create_slave(cpu->ssp[1], "zipit-lcd");
+ z2_lcd = ssi_create_slave(cpu->ssp[1], "zipit-lcd", 0);
bus = pxa2xx_i2c_bus(cpu->i2c[0]);
i2c_create_slave(bus, "aer915", 0x55);
wm = i2c_create_slave(bus, "wm8750", 0x1b);
--
1.7.3.2
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [Qemu-devel] [PATCH V4 2/5] m25p80: initial verion
2012-06-04 8:08 [Qemu-devel] [PATCH V4 0/5] Ehnahced SSI bus support + M25P80 SPI flash + Xilinx SPI controller Peter A. G. Crosthwaite
2012-06-04 8:08 ` [Qemu-devel] [PATCH V4 1/5] SSI: Built in multiple device support Peter A. G. Crosthwaite
@ 2012-06-04 8:08 ` Peter A. G. Crosthwaite
2012-06-04 8:08 ` [Qemu-devel] [PATCH V4 3/5] xilinx_spi: initial version Peter A. G. Crosthwaite
` (3 subsequent siblings)
5 siblings, 0 replies; 12+ messages in thread
From: Peter A. G. Crosthwaite @ 2012-06-04 8:08 UTC (permalink / raw)
To: qemu-devel, paul, edgar.iglesias, peter.maydell, stefanha
Cc: peter.crosthwaite, john.williams
Added device model for m25p80 SPI flash
Signed-off-by: Peter A. G. Crosthwaite <peter.crosthwaite@petalogix.com>
---
changed from v3:
changed licence to v2 or later (PMM review)
generalised device model - rather than being fixed to the fl064k, it can handle a wide range of m25p80 devices
refactored erase commands (previously they were fl064k specific and used spansions broken terminology)
typdef'd strcuts and enums
fixed some camel casing
added comment to explain why bdrv_sync_complete is a nop (PMM review)
removed hardcoded "512" for BDRV_SECTOR_SIZE
flash_sync_area: use bdrv_aio_writev instead of bdrv_write
flash_chip_erase/flash_block_erase32k/flash_sector_erase: consolidated to one function
decode_new_cmd: fixed multi-statement lines (PMM review)
CHIP_ERASE->BULK_ERASE
init: drive_get -> drive_get_next (PMM review)
changed from v2:
updated for SSI slave interface
used async io (suggested - Stefan Hajnoczi)
changed from v1:
converted spi api to modified txrx style
factored out lots of common code and inlined overly short single call functions.
undated for txrx style spi interface
Makefile.target | 1 +
hw/m25p80.c | 557 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 558 insertions(+), 0 deletions(-)
create mode 100644 hw/m25p80.c
diff --git a/Makefile.target b/Makefile.target
index 1582904..77feb83 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -292,6 +292,7 @@ obj-mips-$(CONFIG_FULONG) += bonito.o vt82c686.o mips_fulong2e.o
obj-microblaze-y = petalogix_s3adsp1800_mmu.o
obj-microblaze-y += petalogix_ml605_mmu.o
obj-microblaze-y += microblaze_boot.o
+obj-microblaze-y += m25p80.o
obj-microblaze-y += microblaze_pic_cpu.o
obj-microblaze-y += xilinx_intc.o
diff --git a/hw/m25p80.c b/hw/m25p80.c
new file mode 100644
index 0000000..0edb67f
--- /dev/null
+++ b/hw/m25p80.c
@@ -0,0 +1,557 @@
+/*
+ * ST M25P80 emulator. Emulate all SPI flash devices based on the m25p80 command
+ * set. Known devices table current as of Jun/2012 and taked from linux.
+ * See drivers/mtd/devices/m25p80.c.
+ *
+ * Copyright (C) 2011 Edgar E. Iglesias <edgar.iglesias@gmail.com>
+ * Copyright (C) 2012 Peter A. G. Crosthwaite <peter.crosthwaite@petalogix.com>
+ * Copyright (C) 2012 PetaLogix
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) a later version of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw.h"
+#include "blockdev.h"
+#include "ssi.h"
+#include "devices.h"
+
+#ifdef M25P80_ERR_DEBUG
+#define DB_PRINT(...) do { \
+ fprintf(stderr, ": %s: ", __func__); \
+ fprintf(stderr, ## __VA_ARGS__); \
+ } while (0);
+#else
+ #define DB_PRINT(...)
+#endif
+
+typedef struct FlashPartInfo {
+ const char *part_name;
+ /* jedec code. (jedec >> 16) & 0xff is the 1st byte, >> 8 the 2nd etc */
+ uint32_t jedec;
+ /* extended jedec code */
+ uint16_t ext_jedec;
+ /* there is confusion between manufacturers as to what a sector is. In this
+ * device model, a "sector" is the size that is erased by the ERASE_SECTOR
+ * command (opcode 0xd8).
+ */
+ uint32_t sector_size;
+ uint32_t n_sectors;
+ uint32_t page_size;
+ uint8_t flags;
+#define ER_4K 1
+#define ER_32K 2
+} FlashPartInfo;
+
+/* adapted from linux */
+
+#define INFO(_part_name, _jedec, _ext_jedec, _sector_size, _n_sectors, _flags)\
+ .part_name = (_part_name),\
+ .jedec = (_jedec),\
+ .ext_jedec = (_ext_jedec),\
+ .sector_size = (_sector_size),\
+ .n_sectors = (_n_sectors),\
+ .page_size = 256,\
+ .flags = (_flags),\
+
+static const FlashPartInfo known_devices[] = {
+ /* Atmel -- some are (confusingly) marketed as "DataFlash" */
+ { INFO("at25fs010", 0x1f6601, 0, 32 << 10, 4, ER_4K) },
+ { INFO("at25fs040", 0x1f6604, 0, 64 << 10, 8, ER_4K) },
+
+ { INFO("at25df041a", 0x1f4401, 0, 64 << 10, 8, ER_4K) },
+ { INFO("at25df321a", 0x1f4701, 0, 64 << 10, 64, ER_4K) },
+ { INFO("at25df641", 0x1f4800, 0, 64 << 10, 128, ER_4K) },
+
+ { INFO("at26f004", 0x1f0400, 0, 64 << 10, 8, ER_4K) },
+ { INFO("at26df081a", 0x1f4501, 0, 64 << 10, 16, ER_4K) },
+ { INFO("at26df161a", 0x1f4601, 0, 64 << 10, 32, ER_4K) },
+ { INFO("at26df321", 0x1f4700, 0, 64 << 10, 64, ER_4K) },
+
+ /* EON -- en25xxx */
+ { INFO("en25f32", 0x1c3116, 0, 64 << 10, 64, ER_4K) },
+ { INFO("en25p32", 0x1c2016, 0, 64 << 10, 64, 0) },
+ { INFO("en25q32b", 0x1c3016, 0, 64 << 10, 64, 0) },
+ { INFO("en25p64", 0x1c2017, 0, 64 << 10, 128, 0) },
+
+ /* Intel/Numonyx -- xxxs33b */
+ { INFO("160s33b", 0x898911, 0, 64 << 10, 32, 0) },
+ { INFO("320s33b", 0x898912, 0, 64 << 10, 64, 0) },
+ { INFO("640s33b", 0x898913, 0, 64 << 10, 128, 0) },
+
+ /* Macronix */
+ { INFO("mx25l4005a", 0xc22013, 0, 64 << 10, 8, ER_4K) },
+ { INFO("mx25l8005", 0xc22014, 0, 64 << 10, 16, 0) },
+ { INFO("mx25l1606e", 0xc22015, 0, 64 << 10, 32, ER_4K) },
+ { INFO("mx25l3205d", 0xc22016, 0, 64 << 10, 64, 0) },
+ { INFO("mx25l6405d", 0xc22017, 0, 64 << 10, 128, 0) },
+ { INFO("mx25l12805d", 0xc22018, 0, 64 << 10, 256, 0) },
+ { INFO("mx25l12855e", 0xc22618, 0, 64 << 10, 256, 0) },
+ { INFO("mx25l25635e", 0xc22019, 0, 64 << 10, 512, 0) },
+ { INFO("mx25l25655e", 0xc22619, 0, 64 << 10, 512, 0) },
+
+ /* Spansion -- single (large) sector size only, at least
+ * for the chips listed here (without boot sectors).
+ */
+ { INFO("s25sl004a", 0x010212, 0, 64 << 10, 8, 0) },
+ { INFO("s25sl008a", 0x010213, 0, 64 << 10, 16, 0) },
+ { INFO("s25sl016a", 0x010214, 0, 64 << 10, 32, 0) },
+ { INFO("s25sl032a", 0x010215, 0, 64 << 10, 64, 0) },
+ { INFO("s25sl032p", 0x010215, 0x4d00, 64 << 10, 64, ER_4K) },
+ { INFO("s25sl064a", 0x010216, 0, 64 << 10, 128, 0) },
+ { INFO("s25fl256s0", 0x010219, 0x4d00, 256 << 10, 128, 0) },
+ { INFO("s25fl256s1", 0x010219, 0x4d01, 64 << 10, 512, 0) },
+ { INFO("s25fl512s", 0x010220, 0x4d00, 256 << 10, 256, 0) },
+ { INFO("s70fl01gs", 0x010221, 0x4d00, 256 << 10, 256, 0) },
+ { INFO("s25sl12800", 0x012018, 0x0300, 256 << 10, 64, 0) },
+ { INFO("s25sl12801", 0x012018, 0x0301, 64 << 10, 256, 0) },
+ { INFO("s25fl129p0", 0x012018, 0x4d00, 256 << 10, 64, 0) },
+ { INFO("s25fl129p1", 0x012018, 0x4d01, 64 << 10, 256, 0) },
+ { INFO("s25fl016k", 0xef4015, 0, 64 << 10, 32, ER_4K | ER_32K) },
+ { INFO("s25fl064k", 0xef4017, 0, 64 << 10, 128, ER_4K | ER_32K) },
+
+ /* SST -- large erase sizes are "overlays", "sectors" are 4<< 10 */
+ { INFO("sst25vf040b", 0xbf258d, 0, 64 << 10, 8, ER_4K) },
+ { INFO("sst25vf080b", 0xbf258e, 0, 64 << 10, 16, ER_4K) },
+ { INFO("sst25vf016b", 0xbf2541, 0, 64 << 10, 32, ER_4K) },
+ { INFO("sst25vf032b", 0xbf254a, 0, 64 << 10, 64, ER_4K) },
+ { INFO("sst25wf512", 0xbf2501, 0, 64 << 10, 1, ER_4K) },
+ { INFO("sst25wf010", 0xbf2502, 0, 64 << 10, 2, ER_4K) },
+ { INFO("sst25wf020", 0xbf2503, 0, 64 << 10, 4, ER_4K) },
+ { INFO("sst25wf040", 0xbf2504, 0, 64 << 10, 8, ER_4K) },
+
+ /* ST Microelectronics -- newer production may have feature updates */
+ { INFO("m25p05", 0x202010, 0, 32 << 10, 2, 0) },
+ { INFO("m25p10", 0x202011, 0, 32 << 10, 4, 0) },
+ { INFO("m25p20", 0x202012, 0, 64 << 10, 4, 0) },
+ { INFO("m25p40", 0x202013, 0, 64 << 10, 8, 0) },
+ { INFO("m25p80", 0x202014, 0, 64 << 10, 16, 0) },
+ { INFO("m25p16", 0x202015, 0, 64 << 10, 32, 0) },
+ { INFO("m25p32", 0x202016, 0, 64 << 10, 64, 0) },
+ { INFO("m25p64", 0x202017, 0, 64 << 10, 128, 0) },
+ { INFO("m25p128", 0x202018, 0, 256 << 10, 64, 0) },
+
+ { INFO("m45pe10", 0x204011, 0, 64 << 10, 2, 0) },
+ { INFO("m45pe80", 0x204014, 0, 64 << 10, 16, 0) },
+ { INFO("m45pe16", 0x204015, 0, 64 << 10, 32, 0) },
+
+ { INFO("m25pe80", 0x208014, 0, 64 << 10, 16, 0) },
+ { INFO("m25pe16", 0x208015, 0, 64 << 10, 32, ER_4K) },
+
+ { INFO("m25px32", 0x207116, 0, 64 << 10, 64, ER_4K) },
+ { INFO("m25px32-s0", 0x207316, 0, 64 << 10, 64, ER_4K) },
+ { INFO("m25px32-s1", 0x206316, 0, 64 << 10, 64, ER_4K) },
+ { INFO("m25px64", 0x207117, 0, 64 << 10, 128, 0) },
+
+ /* Winbond -- w25x "blocks" are 64k, "sectors" are 4KiB */
+ { INFO("w25x10", 0xef3011, 0, 64 << 10, 2, ER_4K) },
+ { INFO("w25x20", 0xef3012, 0, 64 << 10, 4, ER_4K) },
+ { INFO("w25x40", 0xef3013, 0, 64 << 10, 8, ER_4K) },
+ { INFO("w25x80", 0xef3014, 0, 64 << 10, 16, ER_4K) },
+ { INFO("w25x16", 0xef3015, 0, 64 << 10, 32, ER_4K) },
+ { INFO("w25x32", 0xef3016, 0, 64 << 10, 64, ER_4K) },
+ { INFO("w25q32", 0xef4016, 0, 64 << 10, 64, ER_4K) },
+ { INFO("w25x64", 0xef3017, 0, 64 << 10, 128, ER_4K) },
+ { INFO("w25q64", 0xef4017, 0, 64 << 10, 128, ER_4K) },
+
+ /* Numonyx -- n25q128 */
+ { INFO("n25q128", 0x20ba18, 0, 64 << 10, 256, 0) },
+
+ { },
+};
+
+typedef enum {
+ NOP = 0,
+ PP = 0x2,
+ READ = 0x3,
+ WRDI = 0x4,
+ RDSR = 0x5,
+ WREN = 0x6,
+ FAST_READ = 0xb,
+ ERASE_4K = 0x20,
+ ERASE_32K = 0x52,
+ ERASE_SECTOR = 0xd8,
+ JEDEC_READ = 0x9f,
+ BULK_ERASE = 0xc7,
+} FlashCMD;
+
+typedef enum {
+ STATE_IDLE,
+ STATE_PAGE_PROGRAM,
+ STATE_READ,
+ STATE_COLLECTING_DATA,
+ STATE_READING_DATA,
+} CMDState;
+
+typedef struct Flash {
+ SSISlave ssidev;
+ uint32_t r;
+
+ BlockDriverState *bdrv;
+ CMDState state;
+
+ uint8_t *storage;
+ uint32_t size;
+ int page_size;
+
+ uint8_t data[16];
+ uint32_t len;
+ uint32_t pos;
+ uint8_t needed_bytes;
+ FlashCMD cmd_in_progress;
+
+ int64_t dirty_page;
+
+ uint64_t waddr;
+ int write_enable;
+
+ char *part_name;
+ const FlashPartInfo *pi;
+
+} Flash;
+
+static void bdrv_sync_complete(void *opaque, int ret)
+{
+ /* do nothing. Masters do not directly interact with the backing store,
+ * only the working copy so no mutexing required.
+ */
+}
+
+static void flash_sync_page(Flash *s, int page)
+{
+ if (s->bdrv) {
+ int bdrv_sector, nb_sectors;
+ QEMUIOVector iov;
+
+ bdrv_sector = (page * s->pi->page_size) / BDRV_SECTOR_SIZE;
+ nb_sectors = DIV_ROUND_UP(s->pi->page_size, BDRV_SECTOR_SIZE);
+ qemu_iovec_init(&iov, 1);
+ qemu_iovec_add(&iov, s->storage + bdrv_sector * BDRV_SECTOR_SIZE,
+ nb_sectors * BDRV_SECTOR_SIZE);
+ bdrv_aio_writev(s->bdrv, bdrv_sector, &iov, nb_sectors,
+ bdrv_sync_complete, NULL);
+ }
+}
+
+static inline void flash_sync_area(Flash *s, int64_t off, int64_t len)
+{
+ int64_t start, end;
+ int nb_sectors;
+ QEMUIOVector iov;
+
+ if (!s->bdrv) {
+ return;
+ }
+
+ assert(!(len % BDRV_SECTOR_SIZE));
+ start = off / BDRV_SECTOR_SIZE;
+ end = (off + len) / BDRV_SECTOR_SIZE;
+ nb_sectors = end - start;
+ qemu_iovec_init(&iov, 1);
+ qemu_iovec_add(&iov, s->storage + (start * BDRV_SECTOR_SIZE),
+ nb_sectors * BDRV_SECTOR_SIZE);
+ bdrv_aio_writev(s->bdrv, start, &iov, nb_sectors, bdrv_sync_complete, NULL);
+}
+
+static void flash_erase(Flash *s, int offset, FlashCMD cmd)
+{
+ uint32_t len;
+ uint8_t capa_to_assert = 0;
+
+ switch (cmd) {
+ case ERASE_4K:
+ len = 4 << 10;
+ capa_to_assert = ER_4K;
+ break;
+ case ERASE_32K:
+ len = 32 << 10;
+ capa_to_assert = ER_32K;
+ break;
+ case ERASE_SECTOR:
+ len = s->pi->sector_size;
+ break;
+ case BULK_ERASE:
+ len = s->size;
+ default:
+ assert(false);
+ }
+
+ DB_PRINT("offset = %#x, len = %d\n", offset, len);
+ if ((s->pi->flags & capa_to_assert) != capa_to_assert) {
+ hw_error("m25p80: %dk erase size not supported by device\n", len);
+ }
+
+ if (!s->write_enable) {
+ DB_PRINT("erase with write protect!\n");
+ return;
+ }
+ memset(s->storage + offset, 0xff, len);
+ flash_sync_area(s, offset, len);
+}
+
+static inline void flash_sync_dirty(Flash *s, int64_t newpage)
+{
+ if (s->dirty_page >= 0 && s->dirty_page != newpage) {
+ flash_sync_page(s, s->dirty_page);
+ s->dirty_page = newpage;
+ }
+}
+
+static inline
+void flash_write8(Flash *s, uint64_t addr, uint8_t data)
+{
+ int64_t page = addr / s->pi->page_size;
+ uint8_t prev = s->storage[s->waddr];
+
+ if (!s->write_enable) {
+ DB_PRINT("write with write protect!\n");
+ }
+
+ if ((prev ^ data) & data) {
+ DB_PRINT("programming zero to one! addr=%lx %x -> %x\n",
+ addr, prev, data);
+ }
+ s->storage[s->waddr] ^= ~data & s->storage[s->waddr];
+
+ flash_sync_dirty(s, page);
+ s->dirty_page = page;
+}
+
+static void complete_collecting_data(Flash *s)
+{
+ s->waddr = s->data[0] << 16;
+ s->waddr |= s->data[1] << 8;
+ s->waddr |= s->data[2];
+
+ switch (s->cmd_in_progress) {
+ case PP:
+ s->state = STATE_PAGE_PROGRAM;
+ break;
+ case READ:
+ case FAST_READ:
+ s->state = STATE_READ;
+ break;
+ case ERASE_4K:
+ case ERASE_32K:
+ case ERASE_SECTOR:
+ flash_erase(s, s->waddr, s->cmd_in_progress);
+ break;
+ default:
+ break;
+ }
+}
+
+static void decode_new_cmd(Flash *s, uint32_t value)
+{
+ s->cmd_in_progress = value;
+ DB_PRINT("decoded new command:%d\n", value);
+
+ switch (value) {
+
+ case ERASE_4K:
+ case ERASE_32K:
+ case ERASE_SECTOR:
+ case READ:
+ case PP:
+ s->needed_bytes = 3;
+ s->pos = 0;
+ s->len = 0;
+ s->state = STATE_COLLECTING_DATA;
+ break;
+
+ case FAST_READ:
+ s->needed_bytes = 4;
+ s->pos = 0;
+ s->len = 0;
+ s->state = STATE_COLLECTING_DATA;
+ break;
+
+ case WRDI:
+ s->write_enable = 0;
+ break;
+ case WREN:
+ s->write_enable = 1;
+ break;
+
+ case RDSR:
+ s->data[0] = (!!s->write_enable) << 1;
+ s->pos = 0;
+ s->len = 1;;
+ s->state = STATE_READING_DATA;
+ break;
+
+ case JEDEC_READ:
+ DB_PRINT("populated jedec code\n");
+ s->data[0] = (s->pi->jedec >> 16) & 0xff;
+ s->data[1] = (s->pi->jedec >> 8) & 0xff;
+ s->data[2] = s->pi->jedec & 0xff;
+ if (s->pi->ext_jedec) {
+ s->data[3] = (s->pi->ext_jedec >> 8) & 0xff;
+ s->data[4] = s->pi->ext_jedec & 0xff;
+ s->len = 5;
+ } else {
+ s->len = 3;
+ }
+ s->pos = 0;
+ s->state = STATE_READING_DATA;
+ break;
+
+ case BULK_ERASE:
+ if (s->write_enable) {
+ DB_PRINT("chip erase\n");
+ flash_erase(s, 0, BULK_ERASE);
+ } else {
+ DB_PRINT("chip erase with write protect!\n");
+ }
+ break;
+ case NOP:
+ break;
+ default:
+ DB_PRINT("Unknown cmd %x\n", value);
+ break;
+ }
+}
+
+static int m25p80_cs(SSISlave *ss, int select)
+{
+ Flash *s = FROM_SSI_SLAVE(Flash, ss);
+
+ if (!select) {
+ s->len = 0;
+ s->pos = 0;
+ s->state = STATE_IDLE;
+ flash_sync_dirty(s, -1);
+ DB_PRINT("deselect\n");
+ }
+
+ return 0;
+}
+
+static uint32_t m25p80_transfer8(SSISlave *ss, uint32_t tx)
+{
+ Flash *s = FROM_SSI_SLAVE(Flash, ss);
+ uint32_t r = 0;
+
+ switch (s->state) {
+
+ case STATE_PAGE_PROGRAM:
+ DB_PRINT("page program waddr=%lx data=%x\n", s->waddr, (uint8_t)tx);
+ flash_write8(s, s->waddr, (uint8_t)tx);
+ s->waddr++;
+ break;
+
+ case STATE_READ:
+ r = s->storage[s->waddr];
+ DB_PRINT("READ 0x%lx=%x\n", s->waddr, r);
+ s->waddr = (s->waddr + 1) % s->size;
+ break;
+
+ case STATE_COLLECTING_DATA:
+ s->data[s->len] = (uint8_t)tx;
+ s->len++;
+
+ if (s->len == s->needed_bytes) {
+ complete_collecting_data(s);
+ }
+ break;
+
+ case STATE_READING_DATA:
+ r = s->data[s->pos];
+ s->pos++;
+ if (s->pos == s->len) {
+ s->pos = 0;
+ s->state = STATE_IDLE;
+ }
+ break;
+
+ default:
+ case STATE_IDLE:
+ decode_new_cmd(s, (uint8_t)tx);
+ break;
+ }
+
+ return r;
+}
+
+static int m25p80_init(SSISlave *ss)
+{
+ DriveInfo *dinfo;
+ Flash *s = FROM_SSI_SLAVE(Flash, ss);
+ const FlashPartInfo *i;
+
+ dinfo = drive_get_next(IF_MTD);
+
+ if (!s->part_name) { /* default to actual m25p80 if no partname given */
+ s->part_name = (char *)"m25p80";
+ }
+
+ i = known_devices;
+ for (i = known_devices;; i++) {
+ assert(i);
+ if (!i->part_name) {
+ fprintf(stderr, "Unknown SPI flash part: \"%s\"\n", s->part_name);
+ return 1;
+ } else if (!strcmp(i->part_name, s->part_name)) {
+ s->pi = i;
+ break;
+ }
+ }
+
+ s->size = s->pi->sector_size * s->pi->n_sectors;
+ s->dirty_page = -1;
+ s->storage = qemu_blockalign(s->bdrv, s->size);
+
+ if (dinfo && dinfo->bdrv) {
+ int rsize;
+
+ s->bdrv = dinfo->bdrv;
+ rsize = MIN(bdrv_getlength(s->bdrv), s->size);
+ if (bdrv_read(s->bdrv, 0, s->storage, DIV_ROUND_UP(s->size,
+ BDRV_SECTOR_SIZE))) {
+ fprintf(stderr, "Failed to initialize SPI flash!\n");
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static Property m25p80_properties[] = {
+ DEFINE_PROP_STRING("partname", Flash, part_name),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void m25p80_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SSISlaveClass *k = SSI_SLAVE_CLASS(klass);
+
+ k->init = m25p80_init;
+ k->transfer = m25p80_transfer8;
+ k->set_cs = m25p80_cs;
+ dc->props = m25p80_properties;
+}
+
+static TypeInfo m25p80_info = {
+ .name = "m25p80",
+ .parent = TYPE_SSI_SLAVE,
+ .instance_size = sizeof(Flash),
+ .class_init = m25p80_class_init,
+};
+
+static void m25p80_register_types(void)
+{
+ type_register_static(&m25p80_info);
+}
+
+type_init(m25p80_register_types)
--
1.7.3.2
^ permalink raw reply related [flat|nested] 12+ messages in thread
* [Qemu-devel] [PATCH V4 3/5] xilinx_spi: initial version
2012-06-04 8:08 [Qemu-devel] [PATCH V4 0/5] Ehnahced SSI bus support + M25P80 SPI flash + Xilinx SPI controller Peter A. G. Crosthwaite
2012-06-04 8:08 ` [Qemu-devel] [PATCH V4 1/5] SSI: Built in multiple device support Peter A. G. Crosthwaite
2012-06-04 8:08 ` [Qemu-devel] [PATCH V4 2/5] m25p80: initial verion Peter A. G. Crosthwaite
@ 2012-06-04 8:08 ` Peter A. G. Crosthwaite
2012-06-04 8:08 ` [Qemu-devel] [PATCH V4 4/5] petalogix-ml605: added spi controller with m25p80 Peter A. G. Crosthwaite
` (2 subsequent siblings)
5 siblings, 0 replies; 12+ messages in thread
From: Peter A. G. Crosthwaite @ 2012-06-04 8:08 UTC (permalink / raw)
To: qemu-devel, paul, edgar.iglesias, peter.maydell, stefanha
Cc: peter.crosthwaite, john.williams
device model for xilinx XPS SPI controller (v2.0)
Signed-off-by: Peter A. G. Crosthwaite <peter.crosthwaite@petalogix.com>
---
changed from v3:
typedef'd struct XilinxSPI
changed unsigned int -> uin32_t
removed unused vars (c_fifo_exist and cmd_ongoing)
txfifo_reset removed duplicate s->regs[R_SPISR] &= ~SR_TX_FULL (PMM review)
reset: changed to Device Class style reset
reset: stope the ptimer (pmm review)
xlx_spi_update_irq: dont -> don't (PMM review)
init: set irq_line to 1 (force refresh on vmsd load)
init: dropped call to reset
implemetned vmsd
changed from v2:
converted spi api to ssi api
changed from v1:
converted spi api to modified txrx style
Makefile.target | 1 +
hw/xilinx_spi.c | 481 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 482 insertions(+), 0 deletions(-)
create mode 100644 hw/xilinx_spi.c
diff --git a/Makefile.target b/Makefile.target
index 77feb83..4471317 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -293,6 +293,7 @@ obj-microblaze-y = petalogix_s3adsp1800_mmu.o
obj-microblaze-y += petalogix_ml605_mmu.o
obj-microblaze-y += microblaze_boot.o
obj-microblaze-y += m25p80.o
+obj-microblaze-y += xilinx_spi.o
obj-microblaze-y += microblaze_pic_cpu.o
obj-microblaze-y += xilinx_intc.o
diff --git a/hw/xilinx_spi.c b/hw/xilinx_spi.c
new file mode 100644
index 0000000..cae88ad
--- /dev/null
+++ b/hw/xilinx_spi.c
@@ -0,0 +1,481 @@
+/*
+ * QEMU model of the Xilinx SPI Controller
+ *
+ * Copyright (C) 2010 Edgar E. Iglesias.
+ * Copyright (C) 2012 Peter A. G. Crosthwaite <peter.crosthwaite@petalogix.com>
+ * Copyright (C) 2012 PetaLogix
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "sysbus.h"
+#include "sysemu.h"
+#include "ptimer.h"
+#include "qemu-log.h"
+
+#include "ssi.h"
+
+#ifdef XILINX_SPI_ERR_DEBUG
+#define DB_PRINT(...) do { \
+ fprintf(stderr, ": %s: ", __func__); \
+ fprintf(stderr, ## __VA_ARGS__); \
+ } while (0);
+#else
+ #define DB_PRINT(...)
+#endif
+
+#define R_DGIER (0x1c / 4)
+#define R_DGIER_IE (1 << 31)
+
+#define R_IPISR (0x20 / 4)
+#define IRQ_DRR_NOT_EMPTY (1 << (31 - 23))
+#define IRQ_DRR_OVERRUN (1 << (31 - 26))
+#define IRQ_DRR_FULL (1 << (31 - 27))
+#define IRQ_TX_FF_HALF_EMPTY (1 << 6)
+#define IRQ_DTR_UNDERRUN (1 << 3)
+#define IRQ_DTR_EMPTY (1 << (31 - 29))
+
+#define R_IPIER (0x28 / 4)
+#define R_SRR (0x40 / 4)
+#define R_SPICR (0x60 / 4)
+#define R_SPICR_TXFF_RST (1 << 5)
+#define R_SPICR_RXFF_RST (1 << 6)
+#define R_SPICR_MTI (1 << 8)
+
+#define R_SPISR (0x64 / 4)
+#define SR_TX_FULL (1 << 3)
+#define SR_TX_EMPTY (1 << 2)
+#define SR_RX_FULL (1 << 1)
+#define SR_RX_EMPTY (1 << 0)
+
+
+#define R_SPIDTR (0x68 / 4)
+#define R_SPIDRR (0x6C / 4)
+#define R_SPISSR (0x70 / 4)
+#define R_TX_FF_OCY (0x74 / 4)
+#define R_RX_FF_OCY (0x78 / 4)
+#define R_MAX (0x7C / 4)
+
+typedef struct XilinxSPI {
+ SysBusDevice busdev;
+ MemoryRegion mmio;
+ qemu_irq irq;
+ int irqline;
+
+ QEMUBH *bh;
+ ptimer_state *ptimer;
+
+ SSIBus *spi;
+
+ uint8_t rx_fifo[256];
+ uint32_t rx_fifo_pos;
+ uint32_t rx_fifo_len;
+
+ uint8_t tx_fifo[256];
+ uint32_t tx_fifo_pos;
+ uint32_t tx_fifo_len;
+
+ /* Slave select. */
+ uint8_t num_cs;
+
+ uint32_t regs[R_MAX];
+} XilinxSPI;
+
+static void txfifo_reset(XilinxSPI *s)
+{
+ s->tx_fifo_pos = 0;
+ s->tx_fifo_len = 0;
+
+ s->regs[R_SPISR] &= ~SR_TX_FULL;
+ s->regs[R_SPISR] |= SR_TX_EMPTY;
+ s->regs[R_IPISR] |= IRQ_DTR_EMPTY;
+}
+
+static void rxfifo_reset(XilinxSPI *s)
+{
+ s->rx_fifo_pos = 0;
+ s->rx_fifo_len = 0;
+
+ s->regs[R_SPISR] |= SR_RX_EMPTY;
+ s->regs[R_SPISR] &= ~SR_RX_FULL;
+ s->regs[R_IPISR] &= ~IRQ_DRR_NOT_EMPTY;
+ s->regs[R_IPISR] &= ~IRQ_DRR_OVERRUN;
+}
+
+static void xlx_spi_do_reset(XilinxSPI *s)
+{
+ memset(s->regs, 0, sizeof s->regs);
+
+ rxfifo_reset(s);
+ txfifo_reset(s);
+
+ s->regs[R_SPISSR] = 1;
+ ptimer_stop(s->ptimer);
+ ssi_select_slave(s->spi, 0);
+}
+
+static void xlx_spi_reset(DeviceState *d)
+{
+ xlx_spi_do_reset(DO_UPCAST(XilinxSPI, busdev.qdev, d));
+}
+
+static void xlx_spi_update_irq(XilinxSPI *s)
+{
+ uint32_t pending;
+ pending = s->regs[R_IPISR] & s->regs[R_IPIER];
+
+ pending = pending && (s->regs[R_DGIER] & R_DGIER_IE);
+ pending = !!pending;
+
+ /* This call lies right in the data paths so don't call the
+ irq chain unless things really changed. */
+ if (pending != s->irqline) {
+ s->irqline = pending;
+ DB_PRINT("irq_change_of of state %d\n", pending);
+ qemu_set_irq(s->irq, pending);
+ }
+}
+
+static inline int spi_master_enabled(XilinxSPI *s)
+{
+ return !(s->regs[R_SPICR] & R_SPICR_MTI);
+}
+
+static int spi_slave_select(XilinxSPI *s, uint32_t v)
+{
+ unsigned int ss;
+
+ ss = ffs(v) - 1;
+ return ss < s->num_cs ? ss : SSI_SLAVE_SELECT_NONE;
+}
+
+static inline int txfifo_empty(XilinxSPI *s)
+{
+ return s->tx_fifo_len == 0;
+}
+
+static inline int txfifo_full(XilinxSPI *s)
+{
+ return s->tx_fifo_len >= ARRAY_SIZE(s->tx_fifo);
+}
+
+static inline int rxfifo_empty(XilinxSPI *s)
+{
+ return s->rx_fifo_len == 0;
+}
+
+static inline int rxfifo_full(XilinxSPI *s)
+{
+ return s->rx_fifo_len >= ARRAY_SIZE(s->rx_fifo);
+}
+
+static inline void txfifo_put(XilinxSPI *s, uint8_t v)
+{
+ s->regs[R_SPISR] &= ~SR_TX_EMPTY;
+ s->regs[R_IPISR] &= ~IRQ_DTR_EMPTY;
+
+ s->tx_fifo[s->tx_fifo_pos] = v;
+ s->tx_fifo_pos++;
+ s->tx_fifo_pos &= ARRAY_SIZE(s->tx_fifo) - 1;
+ s->tx_fifo_len++;
+
+ s->regs[R_SPISR] &= ~SR_TX_FULL;
+ if (txfifo_full(s)) {
+ s->regs[R_SPISR] |= SR_TX_FULL;
+ }
+}
+
+static inline uint8_t txfifo_get(XilinxSPI *s)
+{
+ uint8_t r = 0;
+ assert(s->tx_fifo_len);
+
+ r = s->tx_fifo[(s->tx_fifo_pos - s->tx_fifo_len) &
+ (ARRAY_SIZE(s->tx_fifo) - 1)];
+ s->tx_fifo_len--;
+
+ s->regs[R_SPISR] &= ~SR_TX_FULL;
+ if (txfifo_empty(s)) {
+ s->regs[R_SPISR] |= SR_TX_EMPTY;
+ s->regs[R_IPISR] |= IRQ_DTR_EMPTY;
+ }
+
+ return r;
+}
+
+static inline void rxfifo_put(XilinxSPI *s, uint8_t v)
+{
+ DB_PRINT("%x\n", v);
+ s->regs[R_SPISR] &= ~SR_RX_EMPTY;
+ s->regs[R_IPISR] |= IRQ_DRR_NOT_EMPTY;
+
+ s->rx_fifo[s->rx_fifo_pos] = v;
+ s->rx_fifo_pos++;
+ s->rx_fifo_pos &= ARRAY_SIZE(s->rx_fifo) - 1;
+ s->rx_fifo_len++;
+
+ s->regs[R_SPISR] &= ~SR_RX_FULL;
+ if (s->rx_fifo_len >= ARRAY_SIZE(s->rx_fifo)) {
+ s->regs[R_SPISR] |= SR_RX_FULL;
+ s->regs[R_IPISR] |= IRQ_DRR_OVERRUN;
+ }
+}
+
+static inline uint32_t rxfifo_get(XilinxSPI *s)
+{
+ uint32_t r = 0;
+ assert(s->rx_fifo_len);
+
+ r = s->rx_fifo[(s->rx_fifo_pos - s->rx_fifo_len) &
+ (ARRAY_SIZE(s->rx_fifo) - 1)];
+ s->rx_fifo_len--;
+
+ s->regs[R_SPISR] &= ~SR_RX_FULL;
+ if (rxfifo_empty(s)) {
+ s->regs[R_SPISR] |= SR_RX_EMPTY;
+ s->regs[R_IPISR] &= ~IRQ_DRR_NOT_EMPTY;
+ }
+
+ return r;
+}
+
+static void spi_timer_run(XilinxSPI *s, int delay)
+{
+ ptimer_set_count(s->ptimer, delay);
+ ptimer_run(s->ptimer, 1);
+}
+
+static void
+spi_flush_txfifo(XilinxSPI *s)
+{
+ uint32_t tx;
+ uint32_t rx;
+
+ while (!txfifo_empty(s)) {
+ tx = (uint32_t)txfifo_get(s);
+ DB_PRINT("data transfer:%x\n", tx);
+ rx = ssi_transfer(s->spi, (uint32_t)tx);
+ rxfifo_put(s, rx);
+ }
+}
+
+static uint64_t
+spi_read(void *opaque, target_phys_addr_t addr, unsigned int size)
+{
+ XilinxSPI *s = opaque;
+ uint32_t r = 0;
+
+ addr >>= 2;
+ switch (addr) {
+ case R_SPIDRR:
+ if (rxfifo_empty(s)) {
+ DB_PRINT("Read from empty FIFO!\n");
+ return 0xdeadbeef;
+ }
+
+ r = rxfifo_get(s);
+ break;
+
+ case R_SPISR:
+ r = s->regs[addr];
+ if (rxfifo_empty(s)) {
+ spi_timer_run(s, 1);
+ }
+ break;
+
+ default:
+ if (addr < ARRAY_SIZE(s->regs)) {
+ r = s->regs[addr];
+ }
+ break;
+
+ }
+ DB_PRINT("addr=" TARGET_FMT_plx " = %x\n", addr * 4, r);
+ xlx_spi_update_irq(s);
+ return r;
+}
+
+static void
+spi_write(void *opaque, target_phys_addr_t addr,
+ uint64_t val64, unsigned int size)
+{
+ XilinxSPI *s = opaque;
+ uint32_t value = val64;
+
+ DB_PRINT("addr=" TARGET_FMT_plx " = %x\n", addr, value);
+ addr >>= 2;
+ switch (addr) {
+ case R_SRR:
+ if (value != 0xa) {
+ DB_PRINT("Invalid write to SRR %x\n", value);
+ } else {
+ xlx_spi_do_reset(s);
+ }
+ break;
+
+ case R_SPIDTR:
+ txfifo_put(s, value);
+
+ if (!spi_master_enabled(s)) {
+ goto done;
+ } else {
+ DB_PRINT("DTR and master enabled?\n");
+ }
+ spi_flush_txfifo(s);
+ break;
+
+ case R_SPISR:
+ DB_PRINT("Invalid write to SPISR %x\n", value);
+ break;
+
+ case R_IPISR:
+ /* Toggle the bits. */
+ s->regs[addr] ^= value;
+ break;
+
+ /* Slave Select Register. */
+ case R_SPISSR:
+ ssi_select_slave(s->spi, spi_slave_select(s, ~value));
+ s->regs[addr] = value;
+ break;
+
+ case R_SPICR:
+ /* FIXME: reset irq and sr state to empty queues. */
+ if (value & R_SPICR_RXFF_RST) {
+ rxfifo_reset(s);
+ }
+
+ if (value & R_SPICR_TXFF_RST) {
+ txfifo_reset(s);
+ }
+ value &= ~(R_SPICR_RXFF_RST | R_SPICR_TXFF_RST);
+ s->regs[addr] = value;
+
+ if (!(value & R_SPICR_MTI)) {
+ /* When releasing the master disable, initiate a timer
+ that eventually will flush the txfifo. */
+ spi_timer_run(s, 1);
+ }
+ break;
+
+ default:
+ if (addr < ARRAY_SIZE(s->regs)) {
+ s->regs[addr] = value;
+ }
+ break;
+ }
+
+done:
+ xlx_spi_update_irq(s);
+}
+
+static const MemoryRegionOps spi_ops = {
+ .read = spi_read,
+ .write = spi_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4
+ }
+};
+
+static void timer_hit(void *opaque)
+{
+ XilinxSPI *s = opaque;
+
+ if (!txfifo_empty(s)) {
+ spi_flush_txfifo(s);
+ }
+ xlx_spi_update_irq(s);
+}
+
+static int xilinx_spi_init(SysBusDevice *dev)
+{
+ XilinxSPI *s = FROM_SYSBUS(typeof(*s), dev);
+
+ DB_PRINT("\n");
+ sysbus_init_irq(dev, &s->irq);
+
+ memory_region_init_io(&s->mmio, &spi_ops, s, "xilinx-spi", R_MAX * 4);
+ sysbus_init_mmio(dev, &s->mmio);
+
+ s->bh = qemu_bh_new(timer_hit, s);
+ s->ptimer = ptimer_init(s->bh);
+ s->irqline = -1;
+ ptimer_set_freq(s->ptimer, 10 * 1000 * 1000);
+
+ s->spi = ssi_create_bus(&dev->qdev, "spi");
+
+ return 0;
+}
+
+static int xilinx_spi_post_load(void *opaque, int version_id)
+{
+ xlx_spi_update_irq((XilinxSPI *)opaque);
+ return 0;
+}
+
+static const VMStateDescription vmstate_xilinx_spi = {
+ .name = "xilinx_spi",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .post_load = xilinx_spi_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_PTIMER(ptimer, XilinxSPI),
+ VMSTATE_UINT8_ARRAY(rx_fifo, XilinxSPI, 256),
+ VMSTATE_UINT32(rx_fifo_pos, XilinxSPI),
+ VMSTATE_UINT32(rx_fifo_len, XilinxSPI),
+ VMSTATE_UINT8_ARRAY(tx_fifo, XilinxSPI, 256),
+ VMSTATE_UINT32(tx_fifo_pos, XilinxSPI),
+ VMSTATE_UINT32(tx_fifo_len, XilinxSPI),
+ VMSTATE_UINT32_ARRAY(regs, XilinxSPI, R_MAX),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property xilinx_spi_properties[] = {
+ DEFINE_PROP_UINT8("num-cs", XilinxSPI, num_cs, 1),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void xilinx_spi_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = xilinx_spi_init;
+ dc->reset = xlx_spi_reset;
+ dc->props = xilinx_spi_properties;
+ dc->vmsd = &vmstate_xilinx_spi;
+}
+
+static TypeInfo xilinx_spi_info = {
+ .name = "xilinx,spi",
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(XilinxSPI),
+ .class_init = xilinx_spi_class_init,
+};
+
+static void xilinx_spi_register_types(void)
+{
+ type_register_static(&xilinx_spi_info);
+}
+
+type_init(xilinx_spi_register_types)
--
1.7.3.2
^ permalink raw reply related [flat|nested] 12+ messages in thread