Defined SPI bus and SPI slave QOM interfaces. Inspired by and loosely based on existing I2C framework.
Signed-off-by: Peter A. G. Crosthwaite <peter.crosthwa...@petalogix.com> --- changed from v1: minor sylistic changes converted spi api to modified txrx style Makefile.objs | 2 +- hw/spi.c | 148 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ hw/spi.h | 69 ++++++++++++++++++++++++++ 3 files changed, 218 insertions(+), 1 deletions(-) create mode 100644 hw/spi.c create mode 100644 hw/spi.h diff --git a/Makefile.objs b/Makefile.objs index 226b01d..45eb20f 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -116,7 +116,7 @@ common-obj-$(CONFIG_SSD0323) += ssd0323.o common-obj-$(CONFIG_ADS7846) += ads7846.o common-obj-$(CONFIG_MAX111X) += max111x.o common-obj-$(CONFIG_DS1338) += ds1338.o -common-obj-y += i2c.o smbus.o smbus_eeprom.o +common-obj-y += i2c.o smbus.o smbus_eeprom.o spi.o common-obj-y += eeprom93xx.o common-obj-y += scsi-disk.o cdrom.o common-obj-y += scsi-generic.o scsi-bus.o diff --git a/hw/spi.c b/hw/spi.c new file mode 100644 index 0000000..8955d3e --- /dev/null +++ b/hw/spi.c @@ -0,0 +1,148 @@ +/* + * QEMU SPI bus interface. + * + * Copyright (C) 2012 Peter A. G. Crosthwaite <peter.crosthwa...@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 of the License, or (at your option) any later version. + * + * 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 "spi.h" + +static struct BusInfo SPIBus_info = { + .name = "SPI", + .size = sizeof(SPIBus) +}; + +static const VMStateDescription vmstate_SPIBus = { + .name = "SPIBus", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT8(cur_slave, SPIBus), + VMSTATE_END_OF_LIST() + } +}; + +SPIBus *spi_init_bus(DeviceState *parent, int num_slaves, const char *name) +{ + SPIBus *bus; + + bus = FROM_QBUS(SPIBus, qbus_create(&SPIBus_info, parent, name)); + if (num_slaves >= SPIBus_NO_CS) { + hw_error("too many slaves on spi bus: %d\n", num_slaves); + } + bus->num_slaves = num_slaves; + bus->slaves = g_malloc0(sizeof(*bus->slaves) * num_slaves); + vmstate_register(NULL, -1, &vmstate_SPIBus, bus); + return bus; +} + +int spi_attach_slave(SPIBus *bus, SPISlave *slave, int cs) +{ + if (bus->slaves[cs]) { + return 1; + } + bus->slaves[cs] = slave; + return 0; +} + +int spi_set_cs(SPIBus *bus, int cs) +{ + SPISlave *dev; + SPISlaveClass *klass; + + if (bus->cur_slave == cs) { + return 0; + } + + if (bus->cur_slave != SPIBus_NO_CS) { + dev = bus->slaves[bus->cur_slave]; + dev->cs = 0; + klass = SPI_SLAVE_GET_CLASS(dev); + klass->cs(dev, 0); + } + + if (cs >= bus->num_slaves && cs != SPIBus_NO_CS) { + hw_error("attempted to assert non existent spi CS line: %d\n", cs); + } + + bus->cur_slave = (uint8_t)cs; + + if (cs != SPIBus_NO_CS) { + dev = bus->slaves[cs]; + dev->cs = 1; + klass = SPI_SLAVE_GET_CLASS(dev); + klass->cs(dev, 1); + } + return 0; +}; + +int spi_get_cs(SPIBus *bus) +{ + return bus->cur_slave; +} + +int spi_txrx(SPIBus *bus, uint8_t *tx, uint8_t *txz, uint8_t *rx, + uint8_t *rxz, int len) +{ + SPISlave *dev; + SPISlaveClass *klass; + + if (bus->cur_slave == SPIBus_NO_CS) { + return 1; + } + dev = bus->slaves[bus->cur_slave]; + klass = SPI_SLAVE_GET_CLASS(dev); + + return klass->txrx(dev, tx, txz, rx, rxz, len); +} + +const VMStateDescription vmstate_spi_slave = { + .name = "SPISlave", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT8(cs, SPISlave), + VMSTATE_END_OF_LIST() + } +}; + +static int spi_slave_qdev_init(DeviceState *dev) +{ + SPISlave *s = SPI_SLAVE_FROM_QDEV(dev); + SPISlaveClass *sc = SPI_SLAVE_GET_CLASS(s); + + return sc->init(s); +} + +static void spi_slave_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *k = DEVICE_CLASS(klass); + k->init = spi_slave_qdev_init; + k->bus_info = &SPIBus_info; +} + +static TypeInfo spi_slave_type_info = { + .name = TYPE_SPI_SLAVE, + .parent = TYPE_DEVICE, + .instance_size = sizeof(SPISlave), + .abstract = true, + .class_size = sizeof(SPISlaveClass), + .class_init = spi_slave_class_init, +}; + +static void spi_slave_register_types(void) +{ + type_register_static(&spi_slave_type_info); +} + +type_init(spi_slave_register_types) diff --git a/hw/spi.h b/hw/spi.h new file mode 100644 index 0000000..f8a6226 --- /dev/null +++ b/hw/spi.h @@ -0,0 +1,69 @@ +#ifndef QEMU_SPI_H +#define QEMU_SPI_H + +#include "qdev.h" + +/* pass to spi_set_cs to deslect all devices on bus */ + +#define SPIBus_NO_CS 0xFF + +typedef struct SPISlave { + DeviceState qdev; + uint8_t cs; +} SPISlave; + +#define TYPE_SPI_SLAVE "spi-slave" +#define SPI_SLAVE(obj) \ + OBJECT_CHECK(SPISlave, (obj), TYPE_SPI_SLAVE) +#define SPI_SLAVE_CLASS(klass) \ + OBJECT_CLASS_CHECK(SPISlaveClass, (klass), TYPE_SPI_SLAVE) +#define SPI_SLAVE_GET_CLASS(obj) \ + OBJECT_GET_CLASS(SPISlaveClass, (obj), TYPE_SPI_SLAVE) + +typedef struct SPISlaveClass { + DeviceClass parent_class; + + /* Callbacks provided by the device. */ + int (*init)(SPISlave *s); + + /* change the cs pin state */ + void (*cs)(SPISlave *s, uint8_t select); + + /* transaction */ + int (*txrx)(SPISlave *s, uint8_t *tx, uint8_t *txz, uint8_t *rx, + uint8_t *rxz, int len); + +} SPISlaveClass; + +#define SPI_SLAVE_FROM_QDEV(dev) DO_UPCAST(SPISlave, qdev, dev) +#define FROM_SPI_SLAVE(type, dev) DO_UPCAST(type, spi, dev) + +extern const VMStateDescription vmstate_spi_slave; + +#define VMSTATE_SPI_SLAVE(_field, _state) { \ + .name = (stringify(_field)), \ + .size = sizeof(SPISlave), \ + .vmsd = &vmstate_spi_slave, \ + .flags = VMS_STRUCT, \ + .offset = vmstate_offset_value(_state, _field, SPISlave), \ +} + +typedef struct SPIBus { + BusState qbus; + SPISlave **slaves; + uint8_t num_slaves; + uint8_t cur_slave; +} SPIBus; + +/* create a new spi bus */ +SPIBus *spi_init_bus(DeviceState *parent, int num_slaves, const char *name); +int spi_attach_slave(SPIBus *bus, SPISlave *s, int cs); + +/* change the chip select. Return 1 on failure. */ +int spi_set_cs(SPIBus *bus, int cs); +int spi_get_cs(SPIBus *bus); + +int spi_txrx(SPIBus *s, uint8_t *tx, uint8_t *txz, uint8_t *rx, + uint8_t *rxz, int len); + +#endif -- 1.7.3.2