aboutsummaryrefslogtreecommitdiff
path: root/hw/i2c
diff options
context:
space:
mode:
Diffstat (limited to 'hw/i2c')
-rw-r--r--hw/i2c/Makefile.objs4
-rw-r--r--hw/i2c/core.c246
-rw-r--r--hw/i2c/pm_smbus.c185
-rw-r--r--hw/i2c/smbus.c335
-rw-r--r--hw/i2c/smbus_eeprom.c156
-rw-r--r--hw/i2c/smbus_ich9.c127
-rw-r--r--hw/i2c/versatile_i2c.c107
7 files changed, 1160 insertions, 0 deletions
diff --git a/hw/i2c/Makefile.objs b/hw/i2c/Makefile.objs
index e69de29bb2..f6bd8fa6ed 100644
--- a/hw/i2c/Makefile.objs
+++ b/hw/i2c/Makefile.objs
@@ -0,0 +1,4 @@
+common-obj-y += core.o smbus.o smbus_eeprom.o
+common-obj-$(CONFIG_VERSATILE_I2C) += versatile_i2c.o
+common-obj-$(CONFIG_ACPI) += smbus_ich9.o
+common-obj-$(CONFIG_APM) += pm_smbus.o
diff --git a/hw/i2c/core.c b/hw/i2c/core.c
new file mode 100644
index 0000000000..0c4fc1dbaa
--- /dev/null
+++ b/hw/i2c/core.c
@@ -0,0 +1,246 @@
+/*
+ * QEMU I2C bus interface.
+ *
+ * Copyright (c) 2007 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the LGPL.
+ */
+
+#include "hw/i2c/i2c.h"
+
+struct i2c_bus
+{
+ BusState qbus;
+ I2CSlave *current_dev;
+ I2CSlave *dev;
+ uint8_t saved_address;
+};
+
+static Property i2c_props[] = {
+ DEFINE_PROP_UINT8("address", struct I2CSlave, address, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+#define TYPE_I2C_BUS "i2c-bus"
+#define I2C_BUS(obj) OBJECT_CHECK(i2c_bus, (obj), TYPE_I2C_BUS)
+
+static const TypeInfo i2c_bus_info = {
+ .name = TYPE_I2C_BUS,
+ .parent = TYPE_BUS,
+ .instance_size = sizeof(i2c_bus),
+};
+
+static void i2c_bus_pre_save(void *opaque)
+{
+ i2c_bus *bus = opaque;
+
+ bus->saved_address = bus->current_dev ? bus->current_dev->address : -1;
+}
+
+static int i2c_bus_post_load(void *opaque, int version_id)
+{
+ i2c_bus *bus = opaque;
+
+ /* The bus is loaded before attached devices, so load and save the
+ current device id. Devices will check themselves as loaded. */
+ bus->current_dev = NULL;
+ return 0;
+}
+
+static const VMStateDescription vmstate_i2c_bus = {
+ .name = "i2c_bus",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .pre_save = i2c_bus_pre_save,
+ .post_load = i2c_bus_post_load,
+ .fields = (VMStateField []) {
+ VMSTATE_UINT8(saved_address, i2c_bus),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+/* Create a new I2C bus. */
+i2c_bus *i2c_init_bus(DeviceState *parent, const char *name)
+{
+ i2c_bus *bus;
+
+ bus = FROM_QBUS(i2c_bus, qbus_create(TYPE_I2C_BUS, parent, name));
+ vmstate_register(NULL, -1, &vmstate_i2c_bus, bus);
+ return bus;
+}
+
+void i2c_set_slave_address(I2CSlave *dev, uint8_t address)
+{
+ dev->address = address;
+}
+
+/* Return nonzero if bus is busy. */
+int i2c_bus_busy(i2c_bus *bus)
+{
+ return bus->current_dev != NULL;
+}
+
+/* Returns non-zero if the address is not valid. */
+/* TODO: Make this handle multiple masters. */
+int i2c_start_transfer(i2c_bus *bus, uint8_t address, int recv)
+{
+ BusChild *kid;
+ I2CSlave *slave = NULL;
+ I2CSlaveClass *sc;
+
+ QTAILQ_FOREACH(kid, &bus->qbus.children, sibling) {
+ DeviceState *qdev = kid->child;
+ I2CSlave *candidate = I2C_SLAVE(qdev);
+ if (candidate->address == address) {
+ slave = candidate;
+ break;
+ }
+ }
+
+ if (!slave) {
+ return 1;
+ }
+
+ sc = I2C_SLAVE_GET_CLASS(slave);
+ /* If the bus is already busy, assume this is a repeated
+ start condition. */
+ bus->current_dev = slave;
+ if (sc->event) {
+ sc->event(slave, recv ? I2C_START_RECV : I2C_START_SEND);
+ }
+ return 0;
+}
+
+void i2c_end_transfer(i2c_bus *bus)
+{
+ I2CSlave *dev = bus->current_dev;
+ I2CSlaveClass *sc;
+
+ if (!dev) {
+ return;
+ }
+
+ sc = I2C_SLAVE_GET_CLASS(dev);
+ if (sc->event) {
+ sc->event(dev, I2C_FINISH);
+ }
+
+ bus->current_dev = NULL;
+}
+
+int i2c_send(i2c_bus *bus, uint8_t data)
+{
+ I2CSlave *dev = bus->current_dev;
+ I2CSlaveClass *sc;
+
+ if (!dev) {
+ return -1;
+ }
+
+ sc = I2C_SLAVE_GET_CLASS(dev);
+ if (sc->send) {
+ return sc->send(dev, data);
+ }
+
+ return -1;
+}
+
+int i2c_recv(i2c_bus *bus)
+{
+ I2CSlave *dev = bus->current_dev;
+ I2CSlaveClass *sc;
+
+ if (!dev) {
+ return -1;
+ }
+
+ sc = I2C_SLAVE_GET_CLASS(dev);
+ if (sc->recv) {
+ return sc->recv(dev);
+ }
+
+ return -1;
+}
+
+void i2c_nack(i2c_bus *bus)
+{
+ I2CSlave *dev = bus->current_dev;
+ I2CSlaveClass *sc;
+
+ if (!dev) {
+ return;
+ }
+
+ sc = I2C_SLAVE_GET_CLASS(dev);
+ if (sc->event) {
+ sc->event(dev, I2C_NACK);
+ }
+}
+
+static int i2c_slave_post_load(void *opaque, int version_id)
+{
+ I2CSlave *dev = opaque;
+ i2c_bus *bus;
+ bus = FROM_QBUS(i2c_bus, qdev_get_parent_bus(&dev->qdev));
+ if (bus->saved_address == dev->address) {
+ bus->current_dev = dev;
+ }
+ return 0;
+}
+
+const VMStateDescription vmstate_i2c_slave = {
+ .name = "I2CSlave",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .post_load = i2c_slave_post_load,
+ .fields = (VMStateField []) {
+ VMSTATE_UINT8(address, I2CSlave),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static int i2c_slave_qdev_init(DeviceState *dev)
+{
+ I2CSlave *s = I2C_SLAVE(dev);
+ I2CSlaveClass *sc = I2C_SLAVE_GET_CLASS(s);
+
+ return sc->init(s);
+}
+
+DeviceState *i2c_create_slave(i2c_bus *bus, const char *name, uint8_t addr)
+{
+ DeviceState *dev;
+
+ dev = qdev_create(&bus->qbus, name);
+ qdev_prop_set_uint8(dev, "address", addr);
+ qdev_init_nofail(dev);
+ return dev;
+}
+
+static void i2c_slave_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *k = DEVICE_CLASS(klass);
+ k->init = i2c_slave_qdev_init;
+ k->bus_type = TYPE_I2C_BUS;
+ k->props = i2c_props;
+}
+
+static const TypeInfo i2c_slave_type_info = {
+ .name = TYPE_I2C_SLAVE,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(I2CSlave),
+ .abstract = true,
+ .class_size = sizeof(I2CSlaveClass),
+ .class_init = i2c_slave_class_init,
+};
+
+static void i2c_slave_register_types(void)
+{
+ type_register_static(&i2c_bus_info);
+ type_register_static(&i2c_slave_type_info);
+}
+
+type_init(i2c_slave_register_types)
diff --git a/hw/i2c/pm_smbus.c b/hw/i2c/pm_smbus.c
new file mode 100644
index 0000000000..0b5bb89976
--- /dev/null
+++ b/hw/i2c/pm_smbus.c
@@ -0,0 +1,185 @@
+/*
+ * PC SMBus implementation
+ * splitted from acpi.c
+ *
+ * Copyright (c) 2006 Fabrice Bellard
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2 as published by the Free Software Foundation.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+#include "hw/hw.h"
+#include "hw/i386/pc.h"
+#include "hw/i2c/pm_smbus.h"
+#include "hw/i2c/smbus.h"
+
+/* no save/load? */
+
+#define SMBHSTSTS 0x00
+#define SMBHSTCNT 0x02
+#define SMBHSTCMD 0x03
+#define SMBHSTADD 0x04
+#define SMBHSTDAT0 0x05
+#define SMBHSTDAT1 0x06
+#define SMBBLKDAT 0x07
+
+//#define DEBUG
+
+#ifdef DEBUG
+# define SMBUS_DPRINTF(format, ...) printf(format, ## __VA_ARGS__)
+#else
+# define SMBUS_DPRINTF(format, ...) do { } while (0)
+#endif
+
+
+static void smb_transaction(PMSMBus *s)
+{
+ uint8_t prot = (s->smb_ctl >> 2) & 0x07;
+ uint8_t read = s->smb_addr & 0x01;
+ uint8_t cmd = s->smb_cmd;
+ uint8_t addr = s->smb_addr >> 1;
+ i2c_bus *bus = s->smbus;
+
+ SMBUS_DPRINTF("SMBus trans addr=0x%02x prot=0x%02x\n", addr, prot);
+ switch(prot) {
+ case 0x0:
+ smbus_quick_command(bus, addr, read);
+ break;
+ case 0x1:
+ if (read) {
+ s->smb_data0 = smbus_receive_byte(bus, addr);
+ } else {
+ smbus_send_byte(bus, addr, cmd);
+ }
+ break;
+ case 0x2:
+ if (read) {
+ s->smb_data0 = smbus_read_byte(bus, addr, cmd);
+ } else {
+ smbus_write_byte(bus, addr, cmd, s->smb_data0);
+ }
+ break;
+ case 0x3:
+ if (read) {
+ uint16_t val;
+ val = smbus_read_word(bus, addr, cmd);
+ s->smb_data0 = val;
+ s->smb_data1 = val >> 8;
+ } else {
+ smbus_write_word(bus, addr, cmd, (s->smb_data1 << 8) | s->smb_data0);
+ }
+ break;
+ case 0x5:
+ if (read) {
+ s->smb_data0 = smbus_read_block(bus, addr, cmd, s->smb_data);
+ } else {
+ smbus_write_block(bus, addr, cmd, s->smb_data, s->smb_data0);
+ }
+ break;
+ default:
+ goto error;
+ }
+ return;
+
+ error:
+ s->smb_stat |= 0x04;
+}
+
+static void smb_ioport_writeb(void *opaque, hwaddr addr, uint64_t val,
+ unsigned width)
+{
+ PMSMBus *s = opaque;
+
+ SMBUS_DPRINTF("SMB writeb port=0x%04x val=0x%02x\n", addr, val);
+ switch(addr) {
+ case SMBHSTSTS:
+ s->smb_stat = 0;
+ s->smb_index = 0;
+ break;
+ case SMBHSTCNT:
+ s->smb_ctl = val;
+ if (val & 0x40)
+ smb_transaction(s);
+ break;
+ case SMBHSTCMD:
+ s->smb_cmd = val;
+ break;
+ case SMBHSTADD:
+ s->smb_addr = val;
+ break;
+ case SMBHSTDAT0:
+ s->smb_data0 = val;
+ break;
+ case SMBHSTDAT1:
+ s->smb_data1 = val;
+ break;
+ case SMBBLKDAT:
+ s->smb_data[s->smb_index++] = val;
+ if (s->smb_index > 31)
+ s->smb_index = 0;
+ break;
+ default:
+ break;
+ }
+}
+
+static uint64_t smb_ioport_readb(void *opaque, hwaddr addr, unsigned width)
+{
+ PMSMBus *s = opaque;
+ uint32_t val;
+
+ switch(addr) {
+ case SMBHSTSTS:
+ val = s->smb_stat;
+ break;
+ case SMBHSTCNT:
+ s->smb_index = 0;
+ val = s->smb_ctl & 0x1f;
+ break;
+ case SMBHSTCMD:
+ val = s->smb_cmd;
+ break;
+ case SMBHSTADD:
+ val = s->smb_addr;
+ break;
+ case SMBHSTDAT0:
+ val = s->smb_data0;
+ break;
+ case SMBHSTDAT1:
+ val = s->smb_data1;
+ break;
+ case SMBBLKDAT:
+ val = s->smb_data[s->smb_index++];
+ if (s->smb_index > 31)
+ s->smb_index = 0;
+ break;
+ default:
+ val = 0;
+ break;
+ }
+ SMBUS_DPRINTF("SMB readb port=0x%04x val=0x%02x\n", addr, val);
+ return val;
+}
+
+static const MemoryRegionOps pm_smbus_ops = {
+ .read = smb_ioport_readb,
+ .write = smb_ioport_writeb,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 1,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+void pm_smbus_init(DeviceState *parent, PMSMBus *smb)
+{
+ smb->smbus = i2c_init_bus(parent, "i2c");
+ memory_region_init_io(&smb->io, &pm_smbus_ops, smb, "pm-smbus", 64);
+}
diff --git a/hw/i2c/smbus.c b/hw/i2c/smbus.c
new file mode 100644
index 0000000000..25d2d04163
--- /dev/null
+++ b/hw/i2c/smbus.c
@@ -0,0 +1,335 @@
+/*
+ * QEMU SMBus device emulation.
+ *
+ * Copyright (c) 2007 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licensed under the LGPL.
+ */
+
+/* TODO: Implement PEC. */
+
+#include "hw/hw.h"
+#include "hw/i2c/i2c.h"
+#include "hw/i2c/smbus.h"
+
+//#define DEBUG_SMBUS 1
+
+#ifdef DEBUG_SMBUS
+#define DPRINTF(fmt, ...) \
+do { printf("smbus(%02x): " fmt , dev->i2c.address, ## __VA_ARGS__); } while (0)
+#define BADF(fmt, ...) \
+do { fprintf(stderr, "smbus: error: " fmt , ## __VA_ARGS__); exit(1);} while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while(0)
+#define BADF(fmt, ...) \
+do { fprintf(stderr, "smbus: error: " fmt , ## __VA_ARGS__);} while (0)
+#endif
+
+enum {
+ SMBUS_IDLE,
+ SMBUS_WRITE_DATA,
+ SMBUS_RECV_BYTE,
+ SMBUS_READ_DATA,
+ SMBUS_DONE,
+ SMBUS_CONFUSED = -1
+};
+
+static void smbus_do_quick_cmd(SMBusDevice *dev, int recv)
+{
+ SMBusDeviceClass *sc = SMBUS_DEVICE_GET_CLASS(dev);
+
+ DPRINTF("Quick Command %d\n", recv);
+ if (sc->quick_cmd) {
+ sc->quick_cmd(dev, recv);
+ }
+}
+
+static void smbus_do_write(SMBusDevice *dev)
+{
+ SMBusDeviceClass *sc = SMBUS_DEVICE_GET_CLASS(dev);
+
+ if (dev->data_len == 0) {
+ smbus_do_quick_cmd(dev, 0);
+ } else if (dev->data_len == 1) {
+ DPRINTF("Send Byte\n");
+ if (sc->send_byte) {
+ sc->send_byte(dev, dev->data_buf[0]);
+ }
+ } else {
+ dev->command = dev->data_buf[0];
+ DPRINTF("Command %d len %d\n", dev->command, dev->data_len - 1);
+ if (sc->write_data) {
+ sc->write_data(dev, dev->command, dev->data_buf + 1,
+ dev->data_len - 1);
+ }
+ }
+}
+
+static void smbus_i2c_event(I2CSlave *s, enum i2c_event event)
+{
+ SMBusDevice *dev = SMBUS_DEVICE(s);
+
+ switch (event) {
+ case I2C_START_SEND:
+ switch (dev->mode) {
+ case SMBUS_IDLE:
+ DPRINTF("Incoming data\n");
+ dev->mode = SMBUS_WRITE_DATA;
+ break;
+ default:
+ BADF("Unexpected send start condition in state %d\n", dev->mode);
+ dev->mode = SMBUS_CONFUSED;
+ break;
+ }
+ break;
+
+ case I2C_START_RECV:
+ switch (dev->mode) {
+ case SMBUS_IDLE:
+ DPRINTF("Read mode\n");
+ dev->mode = SMBUS_RECV_BYTE;
+ break;
+ case SMBUS_WRITE_DATA:
+ if (dev->data_len == 0) {
+ BADF("Read after write with no data\n");
+ dev->mode = SMBUS_CONFUSED;
+ } else {
+ if (dev->data_len > 1) {
+ smbus_do_write(dev);
+ } else {
+ dev->command = dev->data_buf[0];
+ DPRINTF("%02x: Command %d\n", dev->i2c.address,
+ dev->command);
+ }
+ DPRINTF("Read mode\n");
+ dev->data_len = 0;
+ dev->mode = SMBUS_READ_DATA;
+ }
+ break;
+ default:
+ BADF("Unexpected recv start condition in state %d\n", dev->mode);
+ dev->mode = SMBUS_CONFUSED;
+ break;
+ }
+ break;
+
+ case I2C_FINISH:
+ switch (dev->mode) {
+ case SMBUS_WRITE_DATA:
+ smbus_do_write(dev);
+ break;
+ case SMBUS_RECV_BYTE:
+ smbus_do_quick_cmd(dev, 1);
+ break;
+ case SMBUS_READ_DATA:
+ BADF("Unexpected stop during receive\n");
+ break;
+ default:
+ /* Nothing to do. */
+ break;
+ }
+ dev->mode = SMBUS_IDLE;
+ dev->data_len = 0;
+ break;
+
+ case I2C_NACK:
+ switch (dev->mode) {
+ case SMBUS_DONE:
+ /* Nothing to do. */
+ break;
+ case SMBUS_READ_DATA:
+ dev->mode = SMBUS_DONE;
+ break;
+ default:
+ BADF("Unexpected NACK in state %d\n", dev->mode);
+ dev->mode = SMBUS_CONFUSED;
+ break;
+ }
+ }
+}
+
+static int smbus_i2c_recv(I2CSlave *s)
+{
+ SMBusDevice *dev = SMBUS_DEVICE(s);
+ SMBusDeviceClass *sc = SMBUS_DEVICE_GET_CLASS(dev);
+ int ret;
+
+ switch (dev->mode) {
+ case SMBUS_RECV_BYTE:
+ if (sc->receive_byte) {
+ ret = sc->receive_byte(dev);
+ } else {
+ ret = 0;
+ }
+ DPRINTF("Receive Byte %02x\n", ret);
+ dev->mode = SMBUS_DONE;
+ break;
+ case SMBUS_READ_DATA:
+ if (sc->read_data) {
+ ret = sc->read_data(dev, dev->command, dev->data_len);
+ dev->data_len++;
+ } else {
+ ret = 0;
+ }
+ DPRINTF("Read data %02x\n", ret);
+ break;
+ default:
+ BADF("Unexpected read in state %d\n", dev->mode);
+ dev->mode = SMBUS_CONFUSED;
+ ret = 0;
+ break;
+ }
+ return ret;
+}
+
+static int smbus_i2c_send(I2CSlave *s, uint8_t data)
+{
+ SMBusDevice *dev = SMBUS_DEVICE(s);
+
+ switch (dev->mode) {
+ case SMBUS_WRITE_DATA:
+ DPRINTF("Write data %02x\n", data);
+ dev->data_buf[dev->data_len++] = data;
+ break;
+ default:
+ BADF("Unexpected write in state %d\n", dev->mode);
+ break;
+ }
+ return 0;
+}
+
+static int smbus_device_init(I2CSlave *i2c)
+{
+ SMBusDevice *dev = SMBUS_DEVICE(i2c);
+ SMBusDeviceClass *sc = SMBUS_DEVICE_GET_CLASS(dev);
+
+ return sc->init(dev);
+}
+
+/* Master device commands. */
+void smbus_quick_command(i2c_bus *bus, uint8_t addr, int read)
+{
+ i2c_start_transfer(bus, addr, read);
+ i2c_end_transfer(bus);
+}
+
+uint8_t smbus_receive_byte(i2c_bus *bus, uint8_t addr)
+{
+ uint8_t data;
+
+ i2c_start_transfer(bus, addr, 1);
+ data = i2c_recv(bus);
+ i2c_nack(bus);
+ i2c_end_transfer(bus);
+ return data;
+}
+
+void smbus_send_byte(i2c_bus *bus, uint8_t addr, uint8_t data)
+{
+ i2c_start_transfer(bus, addr, 0);
+ i2c_send(bus, data);
+ i2c_end_transfer(bus);
+}
+
+uint8_t smbus_read_byte(i2c_bus *bus, uint8_t addr, uint8_t command)
+{
+ uint8_t data;
+ i2c_start_transfer(bus, addr, 0);
+ i2c_send(bus, command);
+ i2c_start_transfer(bus, addr, 1);
+ data = i2c_recv(bus);
+ i2c_nack(bus);
+ i2c_end_transfer(bus);
+ return data;
+}
+
+void smbus_write_byte(i2c_bus *bus, uint8_t addr, uint8_t command, uint8_t data)
+{
+ i2c_start_transfer(bus, addr, 0);
+ i2c_send(bus, command);
+ i2c_send(bus, data);
+ i2c_end_transfer(bus);
+}
+
+uint16_t smbus_read_word(i2c_bus *bus, uint8_t addr, uint8_t command)
+{
+ uint16_t data;
+ i2c_start_transfer(bus, addr, 0);
+ i2c_send(bus, command);
+ i2c_start_transfer(bus, addr, 1);
+ data = i2c_recv(bus);
+ data |= i2c_recv(bus) << 8;
+ i2c_nack(bus);
+ i2c_end_transfer(bus);
+ return data;
+}
+
+void smbus_write_word(i2c_bus *bus, uint8_t addr, uint8_t command, uint16_t data)
+{
+ i2c_start_transfer(bus, addr, 0);
+ i2c_send(bus, command);
+ i2c_send(bus, data & 0xff);
+ i2c_send(bus, data >> 8);
+ i2c_end_transfer(bus);
+}
+
+int smbus_read_block(i2c_bus *bus, uint8_t addr, uint8_t command, uint8_t *data)
+{
+ int len;
+ int i;
+
+ i2c_start_transfer(bus, addr, 0);
+ i2c_send(bus, command);
+ i2c_start_transfer(bus, addr, 1);
+ len = i2c_recv(bus);
+ if (len > 32)
+ len = 0;
+ for (i = 0; i < len; i++)
+ data[i] = i2c_recv(bus);
+ i2c_nack(bus);
+ i2c_end_transfer(bus);
+ return len;
+}
+
+void smbus_write_block(i2c_bus *bus, uint8_t addr, uint8_t command, uint8_t *data,
+ int len)
+{
+ int i;
+
+ if (len > 32)
+ len = 32;
+
+ i2c_start_transfer(bus, addr, 0);
+ i2c_send(bus, command);
+ i2c_send(bus, len);
+ for (i = 0; i < len; i++)
+ i2c_send(bus, data[i]);
+ i2c_end_transfer(bus);
+}
+
+static void smbus_device_class_init(ObjectClass *klass, void *data)
+{
+ I2CSlaveClass *sc = I2C_SLAVE_CLASS(klass);
+
+ sc->init = smbus_device_init;
+ sc->event = smbus_i2c_event;
+ sc->recv = smbus_i2c_recv;
+ sc->send = smbus_i2c_send;
+}
+
+static const TypeInfo smbus_device_type_info = {
+ .name = TYPE_SMBUS_DEVICE,
+ .parent = TYPE_I2C_SLAVE,
+ .instance_size = sizeof(SMBusDevice),
+ .abstract = true,
+ .class_size = sizeof(SMBusDeviceClass),
+ .class_init = smbus_device_class_init,
+};
+
+static void smbus_device_register_types(void)
+{
+ type_register_static(&smbus_device_type_info);
+}
+
+type_init(smbus_device_register_types)
diff --git a/hw/i2c/smbus_eeprom.c b/hw/i2c/smbus_eeprom.c
new file mode 100644
index 0000000000..0154283762
--- /dev/null
+++ b/hw/i2c/smbus_eeprom.c
@@ -0,0 +1,156 @@
+/*
+ * QEMU SMBus EEPROM device
+ *
+ * Copyright (c) 2007 Arastra, Inc.
+ *
+ * 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 "hw/hw.h"
+#include "hw/i2c/i2c.h"
+#include "hw/i2c/smbus.h"
+
+//#define DEBUG
+
+typedef struct SMBusEEPROMDevice {
+ SMBusDevice smbusdev;
+ void *data;
+ uint8_t offset;
+} SMBusEEPROMDevice;
+
+static void eeprom_quick_cmd(SMBusDevice *dev, uint8_t read)
+{
+#ifdef DEBUG
+ printf("eeprom_quick_cmd: addr=0x%02x read=%d\n", dev->i2c.address, read);
+#endif
+}
+
+static void eeprom_send_byte(SMBusDevice *dev, uint8_t val)
+{
+ SMBusEEPROMDevice *eeprom = (SMBusEEPROMDevice *) dev;
+#ifdef DEBUG
+ printf("eeprom_send_byte: addr=0x%02x val=0x%02x\n",
+ dev->i2c.address, val);
+#endif
+ eeprom->offset = val;
+}
+
+static uint8_t eeprom_receive_byte(SMBusDevice *dev)
+{
+ SMBusEEPROMDevice *eeprom = (SMBusEEPROMDevice *) dev;
+ uint8_t *data = eeprom->data;
+ uint8_t val = data[eeprom->offset++];
+#ifdef DEBUG
+ printf("eeprom_receive_byte: addr=0x%02x val=0x%02x\n",
+ dev->i2c.address, val);
+#endif
+ return val;
+}
+
+static void eeprom_write_data(SMBusDevice *dev, uint8_t cmd, uint8_t *buf, int len)
+{
+ SMBusEEPROMDevice *eeprom = (SMBusEEPROMDevice *) dev;
+ int n;
+#ifdef DEBUG
+ printf("eeprom_write_byte: addr=0x%02x cmd=0x%02x val=0x%02x\n",
+ dev->i2c.address, cmd, buf[0]);
+#endif
+ /* An page write operation is not a valid SMBus command.
+ It is a block write without a length byte. Fortunately we
+ get the full block anyway. */
+ /* TODO: Should this set the current location? */
+ if (cmd + len > 256)
+ n = 256 - cmd;
+ else
+ n = len;
+ memcpy(eeprom->data + cmd, buf, n);
+ len -= n;
+ if (len)
+ memcpy(eeprom->data, buf + n, len);
+}
+
+static uint8_t eeprom_read_data(SMBusDevice *dev, uint8_t cmd, int n)
+{
+ SMBusEEPROMDevice *eeprom = (SMBusEEPROMDevice *) dev;
+ /* If this is the first byte then set the current position. */
+ if (n == 0)
+ eeprom->offset = cmd;
+ /* As with writes, we implement block reads without the
+ SMBus length byte. */
+ return eeprom_receive_byte(dev);
+}
+
+static int smbus_eeprom_initfn(SMBusDevice *dev)
+{
+ SMBusEEPROMDevice *eeprom = (SMBusEEPROMDevice *)dev;
+
+ eeprom->offset = 0;
+ return 0;
+}
+
+static Property smbus_eeprom_properties[] = {
+ DEFINE_PROP_PTR("data", SMBusEEPROMDevice, data),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void smbus_eeprom_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ SMBusDeviceClass *sc = SMBUS_DEVICE_CLASS(klass);
+
+ sc->init = smbus_eeprom_initfn;
+ sc->quick_cmd = eeprom_quick_cmd;
+ sc->send_byte = eeprom_send_byte;
+ sc->receive_byte = eeprom_receive_byte;
+ sc->write_data = eeprom_write_data;
+ sc->read_data = eeprom_read_data;
+ dc->props = smbus_eeprom_properties;
+}
+
+static const TypeInfo smbus_eeprom_info = {
+ .name = "smbus-eeprom",
+ .parent = TYPE_SMBUS_DEVICE,
+ .instance_size = sizeof(SMBusEEPROMDevice),
+ .class_init = smbus_eeprom_class_initfn,
+};
+
+static void smbus_eeprom_register_types(void)
+{
+ type_register_static(&smbus_eeprom_info);
+}
+
+type_init(smbus_eeprom_register_types)
+
+void smbus_eeprom_init(i2c_bus *smbus, int nb_eeprom,
+ const uint8_t *eeprom_spd, int eeprom_spd_size)
+{
+ int i;
+ uint8_t *eeprom_buf = g_malloc0(8 * 256); /* XXX: make this persistent */
+ if (eeprom_spd_size > 0) {
+ memcpy(eeprom_buf, eeprom_spd, eeprom_spd_size);
+ }
+
+ for (i = 0; i < nb_eeprom; i++) {
+ DeviceState *eeprom;
+ eeprom = qdev_create((BusState *)smbus, "smbus-eeprom");
+ qdev_prop_set_uint8(eeprom, "address", 0x50 + i);
+ qdev_prop_set_ptr(eeprom, "data", eeprom_buf + (i * 256));
+ qdev_init_nofail(eeprom);
+ }
+}
diff --git a/hw/i2c/smbus_ich9.c b/hw/i2c/smbus_ich9.c
new file mode 100644
index 0000000000..ca229789f4
--- /dev/null
+++ b/hw/i2c/smbus_ich9.c
@@ -0,0 +1,127 @@
+/*
+ * ACPI implementation
+ *
+ * Copyright (c) 2006 Fabrice Bellard
+ * Copyright (c) 2009 Isaku Yamahata <yamahata at valinux co jp>
+ * VA Linux Systems Japan K.K.
+ * Copyright (C) 2012 Jason Baron <jbaron@redhat.com>
+ *
+ * This is based on acpi.c, but heavily rewritten.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2 as published by the Free Software Foundation.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ *
+ */
+#include "hw/hw.h"
+#include "hw/i386/pc.h"
+#include "hw/i2c/pm_smbus.h"
+#include "hw/pci/pci.h"
+#include "sysemu/sysemu.h"
+#include "hw/i2c/i2c.h"
+#include "hw/i2c/smbus.h"
+
+#include "hw/i386/ich9.h"
+
+#define TYPE_ICH9_SMB_DEVICE "ICH9 SMB"
+#define ICH9_SMB_DEVICE(obj) \
+ OBJECT_CHECK(ICH9SMBState, (obj), TYPE_ICH9_SMB_DEVICE)
+
+typedef struct ICH9SMBState {
+ PCIDevice dev;
+
+ PMSMBus smb;
+} ICH9SMBState;
+
+static const VMStateDescription vmstate_ich9_smbus = {
+ .name = "ich9_smb",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(dev, struct ICH9SMBState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void ich9_smbus_write_config(PCIDevice *d, uint32_t address,
+ uint32_t val, int len)
+{
+ ICH9SMBState *s = ICH9_SMB_DEVICE(d);
+
+ pci_default_write_config(d, address, val, len);
+ if (range_covers_byte(address, len, ICH9_SMB_HOSTC)) {
+ uint8_t hostc = s->dev.config[ICH9_SMB_HOSTC];
+ if ((hostc & ICH9_SMB_HOSTC_HST_EN) &&
+ !(hostc & ICH9_SMB_HOSTC_I2C_EN)) {
+ memory_region_set_enabled(&s->smb.io, true);
+ } else {
+ memory_region_set_enabled(&s->smb.io, false);
+ }
+ }
+}
+
+static int ich9_smbus_initfn(PCIDevice *d)
+{
+ ICH9SMBState *s = ICH9_SMB_DEVICE(d);
+
+ /* TODO? D31IP.SMIP in chipset configuration space */
+ pci_config_set_interrupt_pin(d->config, 0x01); /* interrupt pin 1 */
+
+ pci_set_byte(d->config + ICH9_SMB_HOSTC, 0);
+ /* TODO bar0, bar1: 64bit BAR support*/
+
+ pm_smbus_init(&d->qdev, &s->smb);
+ pci_register_bar(d, ICH9_SMB_SMB_BASE_BAR, PCI_BASE_ADDRESS_SPACE_IO,
+ &s->smb.io);
+ return 0;
+}
+
+static void ich9_smb_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+
+ k->vendor_id = PCI_VENDOR_ID_INTEL;
+ k->device_id = PCI_DEVICE_ID_INTEL_ICH9_6;
+ k->revision = ICH9_A2_SMB_REVISION;
+ k->class_id = PCI_CLASS_SERIAL_SMBUS;
+ dc->no_user = 1;
+ dc->vmsd = &vmstate_ich9_smbus;
+ dc->desc = "ICH9 SMBUS Bridge";
+ k->init = ich9_smbus_initfn;
+ k->config_write = ich9_smbus_write_config;
+}
+
+i2c_bus *ich9_smb_init(PCIBus *bus, int devfn, uint32_t smb_io_base)
+{
+ PCIDevice *d =
+ pci_create_simple_multifunction(bus, devfn, true, TYPE_ICH9_SMB_DEVICE);
+ ICH9SMBState *s = ICH9_SMB_DEVICE(d);
+ return s->smb.smbus;
+}
+
+static const TypeInfo ich9_smb_info = {
+ .name = TYPE_ICH9_SMB_DEVICE,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(ICH9SMBState),
+ .class_init = ich9_smb_class_init,
+};
+
+static void ich9_smb_register(void)
+{
+ type_register_static(&ich9_smb_info);
+}
+
+type_init(ich9_smb_register);
diff --git a/hw/i2c/versatile_i2c.c b/hw/i2c/versatile_i2c.c
new file mode 100644
index 0000000000..d0444aecac
--- /dev/null
+++ b/hw/i2c/versatile_i2c.c
@@ -0,0 +1,107 @@
+/*
+ * ARM Versatile I2C controller
+ *
+ * Copyright (c) 2006-2007 CodeSourcery.
+ * Copyright (c) 2012 Oskar Andero <oskar.andero@gmail.com>
+ *
+ * This file is derived from hw/realview.c by Paul Brook
+ *
+ * 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.
+ *
+ * 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/sysbus.h"
+#include "hw/bitbang_i2c.h"
+
+typedef struct {
+ SysBusDevice busdev;
+ MemoryRegion iomem;
+ bitbang_i2c_interface *bitbang;
+ int out;
+ int in;
+} VersatileI2CState;
+
+static uint64_t versatile_i2c_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ VersatileI2CState *s = (VersatileI2CState *)opaque;
+
+ if (offset == 0) {
+ return (s->out & 1) | (s->in << 1);
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Bad offset 0x%x\n", __func__, (int)offset);
+ return -1;
+ }
+}
+
+static void versatile_i2c_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ VersatileI2CState *s = (VersatileI2CState *)opaque;
+
+ switch (offset) {
+ case 0:
+ s->out |= value & 3;
+ break;
+ case 4:
+ s->out &= ~value;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Bad offset 0x%x\n", __func__, (int)offset);
+ }
+ bitbang_i2c_set(s->bitbang, BITBANG_I2C_SCL, (s->out & 1) != 0);
+ s->in = bitbang_i2c_set(s->bitbang, BITBANG_I2C_SDA, (s->out & 2) != 0);
+}
+
+static const MemoryRegionOps versatile_i2c_ops = {
+ .read = versatile_i2c_read,
+ .write = versatile_i2c_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int versatile_i2c_init(SysBusDevice *dev)
+{
+ VersatileI2CState *s = FROM_SYSBUS(VersatileI2CState, dev);
+ i2c_bus *bus;
+
+ bus = i2c_init_bus(&dev->qdev, "i2c");
+ s->bitbang = bitbang_i2c_init(bus);
+ memory_region_init_io(&s->iomem, &versatile_i2c_ops, s,
+ "versatile_i2c", 0x1000);
+ sysbus_init_mmio(dev, &s->iomem);
+ return 0;
+}
+
+static void versatile_i2c_class_init(ObjectClass *klass, void *data)
+{
+ SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+ k->init = versatile_i2c_init;
+}
+
+static const TypeInfo versatile_i2c_info = {
+ .name = "versatile_i2c",
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(VersatileI2CState),
+ .class_init = versatile_i2c_class_init,
+};
+
+static void versatile_i2c_register_types(void)
+{
+ type_register_static(&versatile_i2c_info);
+}
+
+type_init(versatile_i2c_register_types)