diff options
author | pbrook <pbrook@c046a42c-6fe2-441c-8c8c-71466251a162> | 2007-05-23 00:03:59 +0000 |
---|---|---|
committer | pbrook <pbrook@c046a42c-6fe2-441c-8c8c-71466251a162> | 2007-05-23 00:03:59 +0000 |
commit | 0ff596d02fd9d876a31d038255a6d4f89da9dfed (patch) | |
tree | 754c9dfaee7006ab91d80333141835d9213a6d06 /hw | |
parent | c6fdf5fca0149fbc2d05d8d5ad43e3686c37514e (diff) |
I2C/SMBus framework.
git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@2845 c046a42c-6fe2-441c-8c8c-71466251a162
Diffstat (limited to 'hw')
-rw-r--r-- | hw/acpi.c | 57 | ||||
-rw-r--r-- | hw/i2c.c | 117 | ||||
-rw-r--r-- | hw/i2c.h | 49 | ||||
-rw-r--r-- | hw/pc.c | 9 | ||||
-rw-r--r-- | hw/smbus.c | 303 | ||||
-rw-r--r-- | hw/smbus.h | 46 | ||||
-rw-r--r-- | hw/smbus_eeprom.c | 46 |
7 files changed, 561 insertions, 66 deletions
@@ -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 0000000000..5d9319da4c --- /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 0000000000..a3c4de3cac --- /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 @@ -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 0000000000..651a7a0b97 --- /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 76fc3c8395..9125896cc9 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 d401b17565..699bd54175 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; } |