diff options
Diffstat (limited to 'hw/i2c')
-rw-r--r-- | hw/i2c/Makefile.objs | 4 | ||||
-rw-r--r-- | hw/i2c/core.c | 246 | ||||
-rw-r--r-- | hw/i2c/pm_smbus.c | 185 | ||||
-rw-r--r-- | hw/i2c/smbus.c | 335 | ||||
-rw-r--r-- | hw/i2c/smbus_eeprom.c | 156 | ||||
-rw-r--r-- | hw/i2c/smbus_ich9.c | 127 | ||||
-rw-r--r-- | hw/i2c/versatile_i2c.c | 107 |
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) |