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.crosthwa...@petalogix.com> --- changed from v2: This patch is new (totally rewitten replacement of (1/4) from v2) hw/spitz.c | 8 ++-- hw/ssi.c | 97 +++++++++++++++++++++++++++++++++++++++++++++++--------- hw/ssi.h | 25 ++++++++++++-- hw/stellaris.c | 6 ++-- hw/tosa.c | 2 +- hw/z2.c | 2 +- 6 files changed, 112 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..d562203 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.crosthwa...@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 ss; }; static struct BusInfo ssi_bus_info = { .name = "SSI", .size = sizeof(SSIBus), + .props = (Property[]) { + DEFINE_PROP_INT32("ss", struct SSISlave, ss, 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(ss, 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,96 @@ 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(SSIBus *bus, const char *name, int32_t ss) { DeviceState *dev; dev = qdev_create(&bus->qbus, name); + qdev_prop_set_int32(dev, "ss", ss); 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->ss == bus->ss) { + return candidate; + } + } + + return NULL; +} + +void ssi_select_slave(SSIBus *bus, int32_t ss) +{ + SSISlave *slave; + SSISlaveClass *ssc; + + if (bus->ss == ss) { + return; + } + + slave = get_current_slave(bus); + if (slave) { + ssc = SSI_SLAVE_GET_CLASS(slave); + if (ssc->set_cs) { + ssc->set_cs(slave, 0); + } + } + bus->ss = ss; + + 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(ss, 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..000d19f 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,39 @@ 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 ss; }; #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 ss); /* 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 ss); + 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