I2C/SMBus framework.

git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@2845 c046a42c-6fe2-441c-8c8c-71466251a162
This commit is contained in:
pbrook 2007-05-23 00:03:59 +00:00
parent c6fdf5fca0
commit 0ff596d02f
9 changed files with 566 additions and 70 deletions

View File

@ -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

View File

@ -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;
}

117
hw/i2c.c Normal file
View File

@ -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);
}

49
hw/i2c.h Normal file
View File

@ -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

View File

@ -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));
}
}

303
hw/smbus.c Normal file
View File

@ -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);
}

View File

@ -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);

View File

@ -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;
}

7
vl.h
View File

@ -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;