commit 0ff596d02fd9d876a31d038255a6d4f89da9dfed
Author: pbrook <pbrook@c046a42c-6fe2-441c-8c8c-71466251a162>
Date:   Wed May 23 00:03:59 2007 +0000

    I2C/SMBus framework.
    
    
    git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@2845 c046a42c-6fe2-441c-8c8c-71466251a162

diff --git a/Makefile.target b/Makefile.target
index 1e573f4..dc30d47 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -402,6 +402,8 @@ SOUND_HW += fmopl.o adlib.o
 endif
 AUDIODRV+= wavcapture.o
 
+VL_OBJS += i2c.o smbus.o
+
 # SCSI layer
 VL_OBJS+= scsi-disk.o cdrom.o lsi53c895a.o
 
diff --git a/hw/acpi.c b/hw/acpi.c
index 5310570..de4002e 100644
--- a/hw/acpi.c
+++ b/hw/acpi.c
@@ -35,7 +35,7 @@ typedef struct PIIX4PMState {
     uint8_t apms;
     QEMUTimer *tmr_timer;
     int64_t tmr_overflow_time;
-    SMBusDevice *smb_dev[128];
+    i2c_bus *smbus;
     uint8_t smb_stat;
     uint8_t smb_ctl;
     uint8_t smb_cmd;
@@ -63,9 +63,6 @@ typedef struct PIIX4PMState {
 #define SMBHSTDAT1 0x06
 #define SMBBLKDAT 0x07
 
-/* Note: only used for piix4_smbus_register_device */
-static PIIX4PMState *piix4_pm_state;
-
 static uint32_t get_pmtmr(PIIX4PMState *s)
 {
     uint32_t d;
@@ -258,59 +255,44 @@ static void smb_transaction(PIIX4PMState *s)
     uint8_t read = s->smb_addr & 0x01;
     uint8_t cmd = s->smb_cmd;
     uint8_t addr = s->smb_addr >> 1;
-    SMBusDevice *dev = s->smb_dev[addr];
+    i2c_bus *bus = s->smbus;
 
 #ifdef DEBUG
     printf("SMBus trans addr=0x%02x prot=0x%02x\n", addr, prot);
 #endif
-    if (!dev) goto error;
-
     switch(prot) {
     case 0x0:
-        if (!dev->quick_cmd) goto error;
-        (*dev->quick_cmd)(dev, read);
+        smbus_quick_command(bus, addr, read);
         break;
     case 0x1:
         if (read) {
-            if (!dev->receive_byte) goto error;
-            s->smb_data0 = (*dev->receive_byte)(dev);
-        }
-        else {
-            if (!dev->send_byte) goto error;
-            (*dev->send_byte)(dev, cmd);
+            s->smb_data0 = smbus_receive_byte(bus, addr);
+        } else {
+            smbus_send_byte(bus, addr, cmd);
         }
         break;
     case 0x2:
         if (read) {
-            if (!dev->read_byte) goto error;
-            s->smb_data0 = (*dev->read_byte)(dev, cmd);
-        }
-        else {
-            if (!dev->write_byte) goto error;
-            (*dev->write_byte)(dev, cmd, s->smb_data0);
+            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;
-            if (!dev->read_word) goto error;
-            val = (*dev->read_word)(dev, cmd);
+            val = smbus_read_word(bus, addr, cmd);
             s->smb_data0 = val;
             s->smb_data1 = val >> 8;
-        }
-        else {
-            if (!dev->write_word) goto error;
-            (*dev->write_word)(dev, cmd, (s->smb_data1 << 8) | s->smb_data0);
+        } else {
+            smbus_write_word(bus, addr, cmd, (s->smb_data1 << 8) | s->smb_data0);
         }
         break;
     case 0x5:
         if (read) {
-            if (!dev->read_block) goto error;
-            s->smb_data0 = (*dev->read_block)(dev, cmd, s->smb_data);
-        }
-        else {
-            if (!dev->write_block) goto error;
-            (*dev->write_block)(dev, cmd, s->smb_data0, s->smb_data);
+            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:
@@ -469,7 +451,7 @@ static int pm_load(QEMUFile* f,void* opaque,int version_id)
     return 0;
 }
 
-void piix4_pm_init(PCIBus *bus, int devfn)
+i2c_bus *piix4_pm_init(PCIBus *bus, int devfn)
 {
     PIIX4PMState *s;
     uint8_t *pci_conf;
@@ -514,10 +496,7 @@ void piix4_pm_init(PCIBus *bus, int devfn)
     s->tmr_timer = qemu_new_timer(vm_clock, pm_tmr_timer, s);
 
     register_savevm("piix4_pm", 0, 1, pm_save, pm_load, s);
-    piix4_pm_state = s;
-}
 
-void piix4_smbus_register_device(SMBusDevice *dev, uint8_t addr)
-{
-    piix4_pm_state->smb_dev[addr] = dev;
+    s->smbus = i2c_init_bus();
+    return s->smbus;
 }
diff --git a/hw/i2c.c b/hw/i2c.c
new file mode 100644
index 0000000..5d9319d
--- /dev/null
+++ b/hw/i2c.c
@@ -0,0 +1,117 @@
+/* 
+ * QEMU I2C bus interface.
+ *
+ * Copyright (c) 2007 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licenced under the LGPL.
+ */
+
+#include "vl.h"
+
+struct i2c_bus
+{
+    i2c_slave *current_dev;
+    i2c_slave *dev;
+};
+
+/* Create a new I2C bus.  */
+i2c_bus *i2c_init_bus(void)
+{
+    i2c_bus *bus;
+
+    bus = (i2c_bus *)qemu_mallocz(sizeof(i2c_bus));
+    return bus;
+}
+
+/* Create a new slave device.  */
+i2c_slave *i2c_slave_init(i2c_bus *bus, int address, int size)
+{
+    i2c_slave *dev;
+
+    if (size < sizeof(i2c_slave))
+        cpu_abort(cpu_single_env, "I2C struct too small");
+
+    dev = (i2c_slave *)qemu_mallocz(size);
+    dev->address = address;
+    dev->next = bus->dev;
+    bus->dev = dev;
+
+    return dev;
+}
+
+void i2c_set_slave_address(i2c_slave *dev, int address)
+{
+    dev->address = address;
+}
+
+/* Return nonzero if bus is busy.  */
+int i2c_bus_busy(i2c_bus *bus)
+{
+    return bus->current_dev != NULL;
+}
+
+/* Returns nonzero if the bus is already busy, or is the address is not
+   valid.  */
+/* TODO: Make this handle multiple masters.  */
+int i2c_start_transfer(i2c_bus *bus, int address, int recv)
+{
+    i2c_slave *dev;
+
+    for (dev = bus->dev; dev; dev = dev->next) {
+        if (dev->address == address)
+            break;
+    }
+
+    if (!dev)
+        return 1;
+
+    /* If the bus is already busy, assume this is a repeated
+       start condition.  */
+    bus->current_dev = dev;
+    dev->event(dev, recv ? I2C_START_RECV : I2C_START_SEND);
+    return 0;
+}
+
+void i2c_end_transfer(i2c_bus *bus)
+{
+    i2c_slave *dev = bus->current_dev;
+
+    if (!dev)
+        return;
+
+    dev->event(dev, I2C_FINISH);
+
+    bus->current_dev = NULL;
+}
+
+int i2c_send(i2c_bus *bus, uint8_t data)
+{
+    i2c_slave *dev = bus->current_dev;
+
+    if (!dev)
+        return -1;
+
+    return dev->send(dev, data);
+}
+
+int i2c_recv(i2c_bus *bus)
+{
+    i2c_slave *dev = bus->current_dev;
+
+    if (!dev)
+        return -1;
+
+    return dev->recv(dev);
+}
+
+void i2c_nack(i2c_bus *bus)
+{
+    i2c_slave *dev = bus->current_dev;
+
+    if (!dev)
+        return;
+
+    dev->event(dev, I2C_NACK);
+}
+
diff --git a/hw/i2c.h b/hw/i2c.h
new file mode 100644
index 0000000..a3c4de3
--- /dev/null
+++ b/hw/i2c.h
@@ -0,0 +1,49 @@
+#ifndef QEMU_I2C_H
+#define QEMU_I2C_H
+
+/* The QEMU I2C implementation only supports simple transfers that complete
+   immediately.  It does not support slave devices that need to be able to
+   defer their response (eg. CPU slave interfaces where the data is supplied
+   by the device driver in response to an interrupt).  */
+
+enum i2c_event {
+    I2C_START_RECV,
+    I2C_START_SEND,
+    I2C_FINISH,
+    I2C_NACK /* Masker NACKed a recieve byte.  */
+};
+
+typedef struct i2c_slave i2c_slave;
+
+/* Master to slave.  */
+typedef int (*i2c_send_cb)(i2c_slave *s, uint8_t data);
+/* Slave to master.  */
+typedef int (*i2c_recv_cb)(i2c_slave *s);
+/* Notify the slave of a bus state change.  */
+typedef void (*i2c_event_cb)(i2c_slave *s, enum i2c_event event);
+
+struct i2c_slave
+{
+    /* Callbacks to be set by the device.  */
+    i2c_event_cb event;
+    i2c_recv_cb recv;
+    i2c_send_cb send;
+
+    /* Remaining fields for internal use by the I2C code.  */
+    int address;
+    void *next;
+};
+
+typedef struct i2c_bus i2c_bus;
+
+i2c_bus *i2c_init_bus(void);
+i2c_slave *i2c_slave_init(i2c_bus *bus, int address, int size);
+void i2c_set_slave_address(i2c_slave *dev, int address);
+int i2c_bus_busy(i2c_bus *bus);
+int i2c_start_transfer(i2c_bus *bus, int address, int recv);
+void i2c_end_transfer(i2c_bus *bus);
+void i2c_nack(i2c_bus *bus);
+int i2c_send(i2c_bus *bus, uint8_t data);
+int i2c_recv(i2c_bus *bus);
+
+#endif
diff --git a/hw/pc.c b/hw/pc.c
index a311c42..a698ba5 100644
--- a/hw/pc.c
+++ b/hw/pc.c
@@ -897,11 +897,12 @@ static void pc_init1(int ram_size, int vga_ram_size, int boot_device,
 
     if (pci_enabled && acpi_enabled) {
         uint8_t *eeprom_buf = qemu_mallocz(8 * 256); /* XXX: make this persistent */
-        piix4_pm_init(pci_bus, piix3_devfn + 3);
+        i2c_bus *smbus;
+
+        /* TODO: Populate SPD eeprom data.  */
+        smbus = piix4_pm_init(pci_bus, piix3_devfn + 3);
         for (i = 0; i < 8; i++) {
-            SMBusDevice *eeprom = smbus_eeprom_device_init(0x50 + i,
-                eeprom_buf + (i * 256));
-            piix4_smbus_register_device(eeprom, 0x50 + i);
+            smbus_eeprom_device_init(smbus, 0x50 + i, eeprom_buf + (i * 256));
         }
     }
     
diff --git a/hw/smbus.c b/hw/smbus.c
new file mode 100644
index 0000000..651a7a0
--- /dev/null
+++ b/hw/smbus.c
@@ -0,0 +1,303 @@
+/* 
+ * QEMU SMBus device emulation.
+ *
+ * Copyright (c) 2007 CodeSourcery.
+ * Written by Paul Brook
+ *
+ * This code is licenced under the LGPL.
+ */
+
+/* TODO: Implement PEC.  */
+
+#include "vl.h"
+
+//#define DEBUG_SMBUS 1
+
+#ifdef DEBUG_SMBUS
+#define DPRINTF(fmt, args...) \
+do { printf("smbus(%02x): " fmt , dev->i2c.address, ##args); } while (0)
+#define BADF(fmt, args...) \
+do { fprintf(stderr, "smbus: error: " fmt , ##args); exit(1);} while (0)
+#else
+#define DPRINTF(fmt, args...) do {} while(0)
+#define BADF(fmt, args...) \
+do { fprintf(stderr, "smbus: error: " fmt , ##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)
+{
+    DPRINTF("Quick Command %d\n", recv);
+    if (dev->quick_cmd)
+        dev->quick_cmd(dev, recv);
+}
+
+static void smbus_do_write(SMBusDevice *dev)
+{
+    if (dev->data_len == 0) {
+        smbus_do_quick_cmd(dev, 0);
+    } else if (dev->data_len == 1) {
+        DPRINTF("Send Byte\n");
+        if (dev->send_byte) {
+            dev->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 (dev->write_data) {
+            dev->write_data(dev, dev->command, dev->data_buf + 1,
+                            dev->data_len - 1);
+        }
+    }
+}
+
+void smbus_i2c_event(i2c_slave *s, enum i2c_event event)
+{
+    SMBusDevice *dev = (SMBusDevice *)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(i2c_slave *s)
+{
+    SMBusDevice *dev = (SMBusDevice *)s;
+    int ret;
+
+    switch (dev->mode) {
+    case SMBUS_RECV_BYTE:
+        if (dev->receive_byte) {
+            ret = dev->receive_byte(dev);
+        } else {
+            ret = 0;
+        }
+        DPRINTF("Receive Byte %02x\n", ret);
+        dev->mode = SMBUS_DONE;
+        break;
+    case SMBUS_READ_DATA:
+        if (dev->read_data) {
+            ret = dev->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(i2c_slave *s, uint8_t data)
+{
+    SMBusDevice *dev = (SMBusDevice *)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;
+}
+
+SMBusDevice *smbus_device_init(i2c_bus *bus, int address, int size)
+{
+    SMBusDevice *dev;
+
+    dev = (SMBusDevice *)i2c_slave_init(bus, address, size);
+    dev->i2c.event = smbus_i2c_event;
+    dev->i2c.recv = smbus_i2c_recv;
+    dev->i2c.send = smbus_i2c_send;
+
+    return dev;
+}
+
+/* Master device commands.  */
+void smbus_quick_command(i2c_bus *bus, int addr, int read)
+{
+    i2c_start_transfer(bus, addr, read);
+    i2c_end_transfer(bus);
+}
+
+uint8_t smbus_receive_byte(i2c_bus *bus, int 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, int 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, int 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, int 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, int 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, int 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, int 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, int 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);
+}
diff --git a/hw/smbus.h b/hw/smbus.h
index 76fc3c8..9125896 100644
--- a/hw/smbus.h
+++ b/hw/smbus.h
@@ -25,14 +25,46 @@
 typedef struct SMBusDevice SMBusDevice;
 
 struct SMBusDevice {
-    uint8_t addr;
+    /* The SMBus protocol is implemented on top of I2C.  */
+    i2c_slave i2c;
+
+    /* Callbacks set by the device.  */
     void (*quick_cmd)(SMBusDevice *dev, uint8_t read);
     void (*send_byte)(SMBusDevice *dev, uint8_t val);
     uint8_t (*receive_byte)(SMBusDevice *dev);
-    void (*write_byte)(SMBusDevice *dev, uint8_t cmd, uint8_t val);
-    uint8_t (*read_byte)(SMBusDevice *dev, uint8_t cmd);
-    void (*write_word)(SMBusDevice *dev, uint8_t cmd, uint16_t val);
-    uint16_t (*read_word)(SMBusDevice *dev, uint8_t cmd);
-    void (*write_block)(SMBusDevice *dev, uint8_t cmd, uint8_t len, uint8_t *buf);
-    uint8_t (*read_block)(SMBusDevice *dev, uint8_t cmd, uint8_t *buf);
+    /* We can't distinguish between a word write and a block write with
+       length 1, so pass the whole data block including the length byte
+       (if present).  The device is responsible figuring out what type of
+       command  this is.  */
+    void (*write_data)(SMBusDevice *dev, uint8_t cmd, uint8_t *buf, int len);
+    /* Likewise we can't distinguish between defferent reads, or even know
+       the length of the read until the read is complete, so read data a
+       byte at a time.  The device is responsible for adding the length
+       byte on block reads.  */
+    uint8_t (*read_data)(SMBusDevice *dev, uint8_t cmd, int n);
+
+    /* Remaining fields for internal use only.  */
+    int mode;
+    int data_len;
+    uint8_t data_buf[34]; /* command + len + 32 bytes of data.  */
+    uint8_t command;
 };
+
+/* Create a slave device.  */
+SMBusDevice *smbus_device_init(i2c_bus *bus, int address, int size);
+
+/* Master device commands.  */
+void smbus_quick_command(i2c_bus *bus, int addr, int read);
+uint8_t smbus_receive_byte(i2c_bus *bus, int addr);
+void smbus_send_byte(i2c_bus *bus, int addr, uint8_t data);
+uint8_t smbus_read_byte(i2c_bus *bus, int addr, uint8_t command);
+void smbus_write_byte(i2c_bus *bus, int addr, uint8_t command, uint8_t data);
+uint16_t smbus_read_word(i2c_bus *bus, int addr, uint8_t command);
+void smbus_write_word(i2c_bus *bus, int addr, uint8_t command, uint16_t data);
+int smbus_read_block(i2c_bus *bus, int addr, uint8_t command, uint8_t *data);
+void smbus_write_block(i2c_bus *bus, int addr, uint8_t command, uint8_t *data,
+                       int len);
+
+/* smbus_eeprom.c */
+void smbus_eeprom_device_init(i2c_bus *bus, uint8_t addr, uint8_t *buf);
+
diff --git a/hw/smbus_eeprom.c b/hw/smbus_eeprom.c
index d401b17..699bd54 100644
--- a/hw/smbus_eeprom.c
+++ b/hw/smbus_eeprom.c
@@ -58,37 +58,51 @@ static uint8_t eeprom_receive_byte(SMBusDevice *dev)
     return val;
 }
 
-static void eeprom_write_byte(SMBusDevice *dev, uint8_t cmd, uint8_t 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->addr,
-           cmd, val);
+           cmd, buf[0]);
 #endif
-    eeprom->data[cmd] = val;
+    /* 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_byte(SMBusDevice *dev, uint8_t cmd)
+static uint8_t eeprom_read_data(SMBusDevice *dev, uint8_t cmd, int n)
 {
     SMBusEEPROMDevice *eeprom = (SMBusEEPROMDevice *) dev;
-    uint8_t val = eeprom->data[cmd];
-#ifdef DEBUG
-    printf("eeprom_read_byte: addr=0x%02x cmd=0x%02x val=0x%02x\n", dev->addr,
-           cmd, val);
-#endif
-    return val;
+    /* 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);
 }
 
-SMBusDevice *smbus_eeprom_device_init(uint8_t addr, uint8_t *buf)
+void smbus_eeprom_device_init(i2c_bus *bus, uint8_t addr, uint8_t *buf)
 {
-    SMBusEEPROMDevice *eeprom = qemu_mallocz(sizeof(SMBusEEPROMDevice));
-    eeprom->dev.addr = addr;
+    SMBusEEPROMDevice *eeprom;
+    
+    eeprom = (SMBusEEPROMDevice *)smbus_device_init(bus, addr,
+        sizeof(SMBusEEPROMDevice));
+
     eeprom->dev.quick_cmd = eeprom_quick_cmd;
     eeprom->dev.send_byte = eeprom_send_byte;
     eeprom->dev.receive_byte = eeprom_receive_byte;
-    eeprom->dev.write_byte = eeprom_write_byte;
-    eeprom->dev.read_byte = eeprom_read_byte;
+    eeprom->dev.write_data = eeprom_write_data;
+    eeprom->dev.read_data = eeprom_read_data;
     eeprom->data = buf;
     eeprom->offset = 0;
-    return (SMBusDevice *) eeprom;
 }
diff --git a/vl.h b/vl.h
index 36ab2d2..b831d8d 100644
--- a/vl.h
+++ b/vl.h
@@ -1126,17 +1126,16 @@ int pit_get_out(PITState *pit, int channel, int64_t current_time);
 void pcspk_init(PITState *);
 int pcspk_audio_init(AudioState *, qemu_irq *pic);
 
+#include "hw/i2c.h"
+
 #include "hw/smbus.h"
 
 /* acpi.c */
 extern int acpi_enabled;
-void piix4_pm_init(PCIBus *bus, int devfn);
+i2c_bus *piix4_pm_init(PCIBus *bus, int devfn);
 void piix4_smbus_register_device(SMBusDevice *dev, uint8_t addr);
 void acpi_bios_init(void);
 
-/* smbus_eeprom.c */
-SMBusDevice *smbus_eeprom_device_init(uint8_t addr, uint8_t *buf);
-
 /* pc.c */
 extern QEMUMachine pc_machine;
 extern QEMUMachine isapc_machine;
