diff options
author | Yang Zhang <yang.z.zhang@intel.com> | 2015-08-28 09:58:54 +0800 |
---|---|---|
committer | Yang Zhang <yang.z.zhang@intel.com> | 2015-09-01 12:44:00 +0800 |
commit | e44e3482bdb4d0ebde2d8b41830ac2cdb07948fb (patch) | |
tree | 66b09f592c55df2878107a468a91d21506104d3f /qemu/hw/i2c | |
parent | 9ca8dbcc65cfc63d6f5ef3312a33184e1d726e00 (diff) |
Add qemu 2.4.0
Change-Id: Ic99cbad4b61f8b127b7dc74d04576c0bcbaaf4f5
Signed-off-by: Yang Zhang <yang.z.zhang@intel.com>
Diffstat (limited to 'qemu/hw/i2c')
-rw-r--r-- | qemu/hw/i2c/Makefile.objs | 7 | ||||
-rw-r--r-- | qemu/hw/i2c/bitbang_i2c.c | 252 | ||||
-rw-r--r-- | qemu/hw/i2c/bitbang_i2c.h | 14 | ||||
-rw-r--r-- | qemu/hw/i2c/core.c | 245 | ||||
-rw-r--r-- | qemu/hw/i2c/exynos4210_i2c.c | 336 | ||||
-rw-r--r-- | qemu/hw/i2c/omap_i2c.c | 504 | ||||
-rw-r--r-- | qemu/hw/i2c/pm_smbus.c | 227 | ||||
-rw-r--r-- | qemu/hw/i2c/smbus.c | 361 | ||||
-rw-r--r-- | qemu/hw/i2c/smbus_eeprom.c | 158 | ||||
-rw-r--r-- | qemu/hw/i2c/smbus_ich9.c | 129 | ||||
-rw-r--r-- | qemu/hw/i2c/versatile_i2c.c | 113 |
11 files changed, 2346 insertions, 0 deletions
diff --git a/qemu/hw/i2c/Makefile.objs b/qemu/hw/i2c/Makefile.objs new file mode 100644 index 000000000..0f130608c --- /dev/null +++ b/qemu/hw/i2c/Makefile.objs @@ -0,0 +1,7 @@ +common-obj-y += core.o smbus.o smbus_eeprom.o +common-obj-$(CONFIG_VERSATILE_I2C) += versatile_i2c.o +common-obj-$(CONFIG_ACPI_X86) += smbus_ich9.o +common-obj-$(CONFIG_APM) += pm_smbus.o +common-obj-$(CONFIG_BITBANG_I2C) += bitbang_i2c.o +common-obj-$(CONFIG_EXYNOS4) += exynos4210_i2c.o +obj-$(CONFIG_OMAP) += omap_i2c.o diff --git a/qemu/hw/i2c/bitbang_i2c.c b/qemu/hw/i2c/bitbang_i2c.c new file mode 100644 index 000000000..6d1bb03d6 --- /dev/null +++ b/qemu/hw/i2c/bitbang_i2c.c @@ -0,0 +1,252 @@ +/* + * Bit-Bang i2c emulation extracted from + * Marvell MV88W8618 / Freecom MusicPal emulation. + * + * Copyright (c) 2008 Jan Kiszka + * + * This code is licensed under the GNU GPL v2. + * + * 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 "bitbang_i2c.h" +#include "hw/sysbus.h" + +//#define DEBUG_BITBANG_I2C + +#ifdef DEBUG_BITBANG_I2C +#define DPRINTF(fmt, ...) \ +do { printf("bitbang_i2c: " fmt , ## __VA_ARGS__); } while (0) +#else +#define DPRINTF(fmt, ...) do {} while(0) +#endif + +typedef enum bitbang_i2c_state { + STOPPED = 0, + SENDING_BIT7, + SENDING_BIT6, + SENDING_BIT5, + SENDING_BIT4, + SENDING_BIT3, + SENDING_BIT2, + SENDING_BIT1, + SENDING_BIT0, + WAITING_FOR_ACK, + RECEIVING_BIT7, + RECEIVING_BIT6, + RECEIVING_BIT5, + RECEIVING_BIT4, + RECEIVING_BIT3, + RECEIVING_BIT2, + RECEIVING_BIT1, + RECEIVING_BIT0, + SENDING_ACK, + SENT_NACK +} bitbang_i2c_state; + +struct bitbang_i2c_interface { + I2CBus *bus; + bitbang_i2c_state state; + int last_data; + int last_clock; + int device_out; + uint8_t buffer; + int current_addr; +}; + +static void bitbang_i2c_enter_stop(bitbang_i2c_interface *i2c) +{ + DPRINTF("STOP\n"); + if (i2c->current_addr >= 0) + i2c_end_transfer(i2c->bus); + i2c->current_addr = -1; + i2c->state = STOPPED; +} + +/* Set device data pin. */ +static int bitbang_i2c_ret(bitbang_i2c_interface *i2c, int level) +{ + i2c->device_out = level; + //DPRINTF("%d %d %d\n", i2c->last_clock, i2c->last_data, i2c->device_out); + return level & i2c->last_data; +} + +/* Leave device data pin unodified. */ +static int bitbang_i2c_nop(bitbang_i2c_interface *i2c) +{ + return bitbang_i2c_ret(i2c, i2c->device_out); +} + +/* Returns data line level. */ +int bitbang_i2c_set(bitbang_i2c_interface *i2c, int line, int level) +{ + int data; + + if (level != 0 && level != 1) { + abort(); + } + + if (line == BITBANG_I2C_SDA) { + if (level == i2c->last_data) { + return bitbang_i2c_nop(i2c); + } + i2c->last_data = level; + if (i2c->last_clock == 0) { + return bitbang_i2c_nop(i2c); + } + if (level == 0) { + DPRINTF("START\n"); + /* START condition. */ + i2c->state = SENDING_BIT7; + i2c->current_addr = -1; + } else { + /* STOP condition. */ + bitbang_i2c_enter_stop(i2c); + } + return bitbang_i2c_ret(i2c, 1); + } + + data = i2c->last_data; + if (i2c->last_clock == level) { + return bitbang_i2c_nop(i2c); + } + i2c->last_clock = level; + if (level == 0) { + /* State is set/read at the start of the clock pulse. + release the data line at the end. */ + return bitbang_i2c_ret(i2c, 1); + } + switch (i2c->state) { + case STOPPED: + case SENT_NACK: + return bitbang_i2c_ret(i2c, 1); + + case SENDING_BIT7 ... SENDING_BIT0: + i2c->buffer = (i2c->buffer << 1) | data; + /* will end up in WAITING_FOR_ACK */ + i2c->state++; + return bitbang_i2c_ret(i2c, 1); + + case WAITING_FOR_ACK: + if (i2c->current_addr < 0) { + i2c->current_addr = i2c->buffer; + DPRINTF("Address 0x%02x\n", i2c->current_addr); + i2c_start_transfer(i2c->bus, i2c->current_addr >> 1, + i2c->current_addr & 1); + } else { + DPRINTF("Sent 0x%02x\n", i2c->buffer); + i2c_send(i2c->bus, i2c->buffer); + } + if (i2c->current_addr & 1) { + i2c->state = RECEIVING_BIT7; + } else { + i2c->state = SENDING_BIT7; + } + return bitbang_i2c_ret(i2c, 0); + + case RECEIVING_BIT7: + i2c->buffer = i2c_recv(i2c->bus); + DPRINTF("RX byte 0x%02x\n", i2c->buffer); + /* Fall through... */ + case RECEIVING_BIT6 ... RECEIVING_BIT0: + data = i2c->buffer >> 7; + /* will end up in SENDING_ACK */ + i2c->state++; + i2c->buffer <<= 1; + return bitbang_i2c_ret(i2c, data); + + case SENDING_ACK: + i2c->state = RECEIVING_BIT7; + if (data != 0) { + DPRINTF("NACKED\n"); + i2c->state = SENT_NACK; + i2c_nack(i2c->bus); + } else { + DPRINTF("ACKED\n"); + } + return bitbang_i2c_ret(i2c, 1); + } + abort(); +} + +bitbang_i2c_interface *bitbang_i2c_init(I2CBus *bus) +{ + bitbang_i2c_interface *s; + + s = g_malloc0(sizeof(bitbang_i2c_interface)); + + s->bus = bus; + s->last_data = 1; + s->last_clock = 1; + s->device_out = 1; + + return s; +} + +/* GPIO interface. */ + +#define TYPE_GPIO_I2C "gpio_i2c" +#define GPIO_I2C(obj) OBJECT_CHECK(GPIOI2CState, (obj), TYPE_GPIO_I2C) + +typedef struct GPIOI2CState { + SysBusDevice parent_obj; + + MemoryRegion dummy_iomem; + bitbang_i2c_interface *bitbang; + int last_level; + qemu_irq out; +} GPIOI2CState; + +static void bitbang_i2c_gpio_set(void *opaque, int irq, int level) +{ + GPIOI2CState *s = opaque; + + level = bitbang_i2c_set(s->bitbang, irq, level); + if (level != s->last_level) { + s->last_level = level; + qemu_set_irq(s->out, level); + } +} + +static int gpio_i2c_init(SysBusDevice *sbd) +{ + DeviceState *dev = DEVICE(sbd); + GPIOI2CState *s = GPIO_I2C(dev); + I2CBus *bus; + + memory_region_init(&s->dummy_iomem, OBJECT(s), "gpio_i2c", 0); + sysbus_init_mmio(sbd, &s->dummy_iomem); + + bus = i2c_init_bus(dev, "i2c"); + s->bitbang = bitbang_i2c_init(bus); + + qdev_init_gpio_in(dev, bitbang_i2c_gpio_set, 2); + qdev_init_gpio_out(dev, &s->out, 1); + + return 0; +} + +static void gpio_i2c_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = gpio_i2c_init; + set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories); + dc->desc = "Virtual GPIO to I2C bridge"; +} + +static const TypeInfo gpio_i2c_info = { + .name = TYPE_GPIO_I2C, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(GPIOI2CState), + .class_init = gpio_i2c_class_init, +}; + +static void bitbang_i2c_register_types(void) +{ + type_register_static(&gpio_i2c_info); +} + +type_init(bitbang_i2c_register_types) diff --git a/qemu/hw/i2c/bitbang_i2c.h b/qemu/hw/i2c/bitbang_i2c.h new file mode 100644 index 000000000..3a7126d5d --- /dev/null +++ b/qemu/hw/i2c/bitbang_i2c.h @@ -0,0 +1,14 @@ +#ifndef BITBANG_I2C_H +#define BITBANG_I2C_H + +#include "hw/i2c/i2c.h" + +typedef struct bitbang_i2c_interface bitbang_i2c_interface; + +#define BITBANG_I2C_SDA 0 +#define BITBANG_I2C_SCL 1 + +bitbang_i2c_interface *bitbang_i2c_init(I2CBus *bus); +int bitbang_i2c_set(bitbang_i2c_interface *i2c, int line, int level); + +#endif diff --git a/qemu/hw/i2c/core.c b/qemu/hw/i2c/core.c new file mode 100644 index 000000000..5a6402634 --- /dev/null +++ b/qemu/hw/i2c/core.c @@ -0,0 +1,245 @@ +/* + * 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 I2CBus +{ + 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(I2CBus, (obj), TYPE_I2C_BUS) + +static const TypeInfo i2c_bus_info = { + .name = TYPE_I2C_BUS, + .parent = TYPE_BUS, + .instance_size = sizeof(I2CBus), +}; + +static void i2c_bus_pre_save(void *opaque) +{ + I2CBus *bus = opaque; + + bus->saved_address = bus->current_dev ? bus->current_dev->address : -1; +} + +static int i2c_bus_post_load(void *opaque, int version_id) +{ + I2CBus *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, + .pre_save = i2c_bus_pre_save, + .post_load = i2c_bus_post_load, + .fields = (VMStateField[]) { + VMSTATE_UINT8(saved_address, I2CBus), + VMSTATE_END_OF_LIST() + } +}; + +/* Create a new I2C bus. */ +I2CBus *i2c_init_bus(DeviceState *parent, const char *name) +{ + I2CBus *bus; + + bus = 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(I2CBus *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(I2CBus *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(I2CBus *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(I2CBus *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(I2CBus *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(I2CBus *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; + I2CBus *bus; + bus = I2C_BUS(qdev_get_parent_bus(DEVICE(dev))); + 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, + .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(I2CBus *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; + set_bit(DEVICE_CATEGORY_MISC, k->categories); + 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/qemu/hw/i2c/exynos4210_i2c.c b/qemu/hw/i2c/exynos4210_i2c.c new file mode 100644 index 000000000..fb99dfda1 --- /dev/null +++ b/qemu/hw/i2c/exynos4210_i2c.c @@ -0,0 +1,336 @@ +/* + * Exynos4210 I2C Bus Serial Interface Emulation + * + * Copyright (C) 2012 Samsung Electronics Co Ltd. + * Maksim Kozlov, <m.kozlov@samsung.com> + * Igor Mitsyanko, <i.mitsyanko@samsung.com> + * + * 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 "qemu/timer.h" +#include "hw/sysbus.h" +#include "hw/i2c/i2c.h" + +#ifndef EXYNOS4_I2C_DEBUG +#define EXYNOS4_I2C_DEBUG 0 +#endif + +#define TYPE_EXYNOS4_I2C "exynos4210.i2c" +#define EXYNOS4_I2C(obj) \ + OBJECT_CHECK(Exynos4210I2CState, (obj), TYPE_EXYNOS4_I2C) + +/* Exynos4210 I2C memory map */ +#define EXYNOS4_I2C_MEM_SIZE 0x14 +#define I2CCON_ADDR 0x00 /* control register */ +#define I2CSTAT_ADDR 0x04 /* control/status register */ +#define I2CADD_ADDR 0x08 /* address register */ +#define I2CDS_ADDR 0x0c /* data shift register */ +#define I2CLC_ADDR 0x10 /* line control register */ + +#define I2CCON_ACK_GEN (1 << 7) +#define I2CCON_INTRS_EN (1 << 5) +#define I2CCON_INT_PEND (1 << 4) + +#define EXYNOS4_I2C_MODE(reg) (((reg) >> 6) & 3) +#define I2C_IN_MASTER_MODE(reg) (((reg) >> 6) & 2) +#define I2CMODE_MASTER_Rx 0x2 +#define I2CMODE_MASTER_Tx 0x3 +#define I2CSTAT_LAST_BIT (1 << 0) +#define I2CSTAT_OUTPUT_EN (1 << 4) +#define I2CSTAT_START_BUSY (1 << 5) + + +#if EXYNOS4_I2C_DEBUG +#define DPRINT(fmt, args...) \ + do { fprintf(stderr, "QEMU I2C: "fmt, ## args); } while (0) + +static const char *exynos4_i2c_get_regname(unsigned offset) +{ + switch (offset) { + case I2CCON_ADDR: + return "I2CCON"; + case I2CSTAT_ADDR: + return "I2CSTAT"; + case I2CADD_ADDR: + return "I2CADD"; + case I2CDS_ADDR: + return "I2CDS"; + case I2CLC_ADDR: + return "I2CLC"; + default: + return "[?]"; + } +} + +#else +#define DPRINT(fmt, args...) do { } while (0) +#endif + +typedef struct Exynos4210I2CState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + I2CBus *bus; + qemu_irq irq; + + uint8_t i2ccon; + uint8_t i2cstat; + uint8_t i2cadd; + uint8_t i2cds; + uint8_t i2clc; + bool scl_free; +} Exynos4210I2CState; + +static inline void exynos4210_i2c_raise_interrupt(Exynos4210I2CState *s) +{ + if (s->i2ccon & I2CCON_INTRS_EN) { + s->i2ccon |= I2CCON_INT_PEND; + qemu_irq_raise(s->irq); + } +} + +static void exynos4210_i2c_data_receive(void *opaque) +{ + Exynos4210I2CState *s = (Exynos4210I2CState *)opaque; + int ret; + + s->i2cstat &= ~I2CSTAT_LAST_BIT; + s->scl_free = false; + ret = i2c_recv(s->bus); + if (ret < 0 && (s->i2ccon & I2CCON_ACK_GEN)) { + s->i2cstat |= I2CSTAT_LAST_BIT; /* Data is not acknowledged */ + } else { + s->i2cds = ret; + } + exynos4210_i2c_raise_interrupt(s); +} + +static void exynos4210_i2c_data_send(void *opaque) +{ + Exynos4210I2CState *s = (Exynos4210I2CState *)opaque; + + s->i2cstat &= ~I2CSTAT_LAST_BIT; + s->scl_free = false; + if (i2c_send(s->bus, s->i2cds) < 0 && (s->i2ccon & I2CCON_ACK_GEN)) { + s->i2cstat |= I2CSTAT_LAST_BIT; + } + exynos4210_i2c_raise_interrupt(s); +} + +static uint64_t exynos4210_i2c_read(void *opaque, hwaddr offset, + unsigned size) +{ + Exynos4210I2CState *s = (Exynos4210I2CState *)opaque; + uint8_t value; + + switch (offset) { + case I2CCON_ADDR: + value = s->i2ccon; + break; + case I2CSTAT_ADDR: + value = s->i2cstat; + break; + case I2CADD_ADDR: + value = s->i2cadd; + break; + case I2CDS_ADDR: + value = s->i2cds; + s->scl_free = true; + if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Rx && + (s->i2cstat & I2CSTAT_START_BUSY) && + !(s->i2ccon & I2CCON_INT_PEND)) { + exynos4210_i2c_data_receive(s); + } + break; + case I2CLC_ADDR: + value = s->i2clc; + break; + default: + value = 0; + DPRINT("ERROR: Bad read offset 0x%x\n", (unsigned int)offset); + break; + } + + DPRINT("read %s [0x%02x] -> 0x%02x\n", exynos4_i2c_get_regname(offset), + (unsigned int)offset, value); + return value; +} + +static void exynos4210_i2c_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + Exynos4210I2CState *s = (Exynos4210I2CState *)opaque; + uint8_t v = value & 0xff; + + DPRINT("write %s [0x%02x] <- 0x%02x\n", exynos4_i2c_get_regname(offset), + (unsigned int)offset, v); + + switch (offset) { + case I2CCON_ADDR: + s->i2ccon = (v & ~I2CCON_INT_PEND) | (s->i2ccon & I2CCON_INT_PEND); + if ((s->i2ccon & I2CCON_INT_PEND) && !(v & I2CCON_INT_PEND)) { + s->i2ccon &= ~I2CCON_INT_PEND; + qemu_irq_lower(s->irq); + if (!(s->i2ccon & I2CCON_INTRS_EN)) { + s->i2cstat &= ~I2CSTAT_START_BUSY; + } + + if (s->i2cstat & I2CSTAT_START_BUSY) { + if (s->scl_free) { + if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Tx) { + exynos4210_i2c_data_send(s); + } else if (EXYNOS4_I2C_MODE(s->i2cstat) == + I2CMODE_MASTER_Rx) { + exynos4210_i2c_data_receive(s); + } + } else { + s->i2ccon |= I2CCON_INT_PEND; + qemu_irq_raise(s->irq); + } + } + } + break; + case I2CSTAT_ADDR: + s->i2cstat = + (s->i2cstat & I2CSTAT_START_BUSY) | (v & ~I2CSTAT_START_BUSY); + + if (!(s->i2cstat & I2CSTAT_OUTPUT_EN)) { + s->i2cstat &= ~I2CSTAT_START_BUSY; + s->scl_free = true; + qemu_irq_lower(s->irq); + break; + } + + /* Nothing to do if in i2c slave mode */ + if (!I2C_IN_MASTER_MODE(s->i2cstat)) { + break; + } + + if (v & I2CSTAT_START_BUSY) { + s->i2cstat &= ~I2CSTAT_LAST_BIT; + s->i2cstat |= I2CSTAT_START_BUSY; /* Line is busy */ + s->scl_free = false; + + /* Generate start bit and send slave address */ + if (i2c_start_transfer(s->bus, s->i2cds >> 1, s->i2cds & 0x1) && + (s->i2ccon & I2CCON_ACK_GEN)) { + s->i2cstat |= I2CSTAT_LAST_BIT; + } else if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Rx) { + exynos4210_i2c_data_receive(s); + } + exynos4210_i2c_raise_interrupt(s); + } else { + i2c_end_transfer(s->bus); + if (!(s->i2ccon & I2CCON_INT_PEND)) { + s->i2cstat &= ~I2CSTAT_START_BUSY; + } + s->scl_free = true; + } + break; + case I2CADD_ADDR: + if ((s->i2cstat & I2CSTAT_OUTPUT_EN) == 0) { + s->i2cadd = v; + } + break; + case I2CDS_ADDR: + if (s->i2cstat & I2CSTAT_OUTPUT_EN) { + s->i2cds = v; + s->scl_free = true; + if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Tx && + (s->i2cstat & I2CSTAT_START_BUSY) && + !(s->i2ccon & I2CCON_INT_PEND)) { + exynos4210_i2c_data_send(s); + } + } + break; + case I2CLC_ADDR: + s->i2clc = v; + break; + default: + DPRINT("ERROR: Bad write offset 0x%x\n", (unsigned int)offset); + break; + } +} + +static const MemoryRegionOps exynos4210_i2c_ops = { + .read = exynos4210_i2c_read, + .write = exynos4210_i2c_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static const VMStateDescription exynos4210_i2c_vmstate = { + .name = "exynos4210.i2c", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT8(i2ccon, Exynos4210I2CState), + VMSTATE_UINT8(i2cstat, Exynos4210I2CState), + VMSTATE_UINT8(i2cds, Exynos4210I2CState), + VMSTATE_UINT8(i2cadd, Exynos4210I2CState), + VMSTATE_UINT8(i2clc, Exynos4210I2CState), + VMSTATE_BOOL(scl_free, Exynos4210I2CState), + VMSTATE_END_OF_LIST() + } +}; + +static void exynos4210_i2c_reset(DeviceState *d) +{ + Exynos4210I2CState *s = EXYNOS4_I2C(d); + + s->i2ccon = 0x00; + s->i2cstat = 0x00; + s->i2cds = 0xFF; + s->i2clc = 0x00; + s->i2cadd = 0xFF; + s->scl_free = true; +} + +static int exynos4210_i2c_realize(SysBusDevice *sbd) +{ + DeviceState *dev = DEVICE(sbd); + Exynos4210I2CState *s = EXYNOS4_I2C(dev); + + memory_region_init_io(&s->iomem, OBJECT(s), &exynos4210_i2c_ops, s, + TYPE_EXYNOS4_I2C, EXYNOS4_I2C_MEM_SIZE); + sysbus_init_mmio(sbd, &s->iomem); + sysbus_init_irq(sbd, &s->irq); + s->bus = i2c_init_bus(dev, "i2c"); + return 0; +} + +static void exynos4210_i2c_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *sbdc = SYS_BUS_DEVICE_CLASS(klass); + + dc->vmsd = &exynos4210_i2c_vmstate; + dc->reset = exynos4210_i2c_reset; + sbdc->init = exynos4210_i2c_realize; +} + +static const TypeInfo exynos4210_i2c_type_info = { + .name = TYPE_EXYNOS4_I2C, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(Exynos4210I2CState), + .class_init = exynos4210_i2c_class_init, +}; + +static void exynos4210_i2c_register_types(void) +{ + type_register_static(&exynos4210_i2c_type_info); +} + +type_init(exynos4210_i2c_register_types) diff --git a/qemu/hw/i2c/omap_i2c.c b/qemu/hw/i2c/omap_i2c.c new file mode 100644 index 000000000..b6f544a22 --- /dev/null +++ b/qemu/hw/i2c/omap_i2c.c @@ -0,0 +1,504 @@ +/* + * TI OMAP on-chip I2C controller. Only "new I2C" mode supported. + * + * Copyright (C) 2007 Andrzej Zaborowski <balrog@zabor.org> + * + * 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/hw.h" +#include "hw/i2c/i2c.h" +#include "hw/arm/omap.h" +#include "hw/sysbus.h" + +#define TYPE_OMAP_I2C "omap_i2c" +#define OMAP_I2C(obj) OBJECT_CHECK(OMAPI2CState, (obj), TYPE_OMAP_I2C) + +typedef struct OMAPI2CState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + qemu_irq irq; + qemu_irq drq[2]; + I2CBus *bus; + + uint8_t revision; + void *iclk; + void *fclk; + + uint8_t mask; + uint16_t stat; + uint16_t dma; + uint16_t count; + int count_cur; + uint32_t fifo; + int rxlen; + int txlen; + uint16_t control; + uint16_t addr[2]; + uint8_t divider; + uint8_t times[2]; + uint16_t test; +} OMAPI2CState; + +#define OMAP2_INTR_REV 0x34 +#define OMAP2_GC_REV 0x34 + +static void omap_i2c_interrupts_update(OMAPI2CState *s) +{ + qemu_set_irq(s->irq, s->stat & s->mask); + if ((s->dma >> 15) & 1) /* RDMA_EN */ + qemu_set_irq(s->drq[0], (s->stat >> 3) & 1); /* RRDY */ + if ((s->dma >> 7) & 1) /* XDMA_EN */ + qemu_set_irq(s->drq[1], (s->stat >> 4) & 1); /* XRDY */ +} + +static void omap_i2c_fifo_run(OMAPI2CState *s) +{ + int ack = 1; + + if (!i2c_bus_busy(s->bus)) + return; + + if ((s->control >> 2) & 1) { /* RM */ + if ((s->control >> 1) & 1) { /* STP */ + i2c_end_transfer(s->bus); + s->control &= ~(1 << 1); /* STP */ + s->count_cur = s->count; + s->txlen = 0; + } else if ((s->control >> 9) & 1) { /* TRX */ + while (ack && s->txlen) + ack = (i2c_send(s->bus, + (s->fifo >> ((-- s->txlen) << 3)) & + 0xff) >= 0); + s->stat |= 1 << 4; /* XRDY */ + } else { + while (s->rxlen < 4) + s->fifo |= i2c_recv(s->bus) << ((s->rxlen ++) << 3); + s->stat |= 1 << 3; /* RRDY */ + } + } else { + if ((s->control >> 9) & 1) { /* TRX */ + while (ack && s->count_cur && s->txlen) { + ack = (i2c_send(s->bus, + (s->fifo >> ((-- s->txlen) << 3)) & + 0xff) >= 0); + s->count_cur --; + } + if (ack && s->count_cur) + s->stat |= 1 << 4; /* XRDY */ + else + s->stat &= ~(1 << 4); /* XRDY */ + if (!s->count_cur) { + s->stat |= 1 << 2; /* ARDY */ + s->control &= ~(1 << 10); /* MST */ + } + } else { + while (s->count_cur && s->rxlen < 4) { + s->fifo |= i2c_recv(s->bus) << ((s->rxlen ++) << 3); + s->count_cur --; + } + if (s->rxlen) + s->stat |= 1 << 3; /* RRDY */ + else + s->stat &= ~(1 << 3); /* RRDY */ + } + if (!s->count_cur) { + if ((s->control >> 1) & 1) { /* STP */ + i2c_end_transfer(s->bus); + s->control &= ~(1 << 1); /* STP */ + s->count_cur = s->count; + s->txlen = 0; + } else { + s->stat |= 1 << 2; /* ARDY */ + s->control &= ~(1 << 10); /* MST */ + } + } + } + + s->stat |= (!ack) << 1; /* NACK */ + if (!ack) + s->control &= ~(1 << 1); /* STP */ +} + +static void omap_i2c_reset(DeviceState *dev) +{ + OMAPI2CState *s = OMAP_I2C(dev); + + s->mask = 0; + s->stat = 0; + s->dma = 0; + s->count = 0; + s->count_cur = 0; + s->fifo = 0; + s->rxlen = 0; + s->txlen = 0; + s->control = 0; + s->addr[0] = 0; + s->addr[1] = 0; + s->divider = 0; + s->times[0] = 0; + s->times[1] = 0; + s->test = 0; +} + +static uint32_t omap_i2c_read(void *opaque, hwaddr addr) +{ + OMAPI2CState *s = opaque; + int offset = addr & OMAP_MPUI_REG_MASK; + uint16_t ret; + + switch (offset) { + case 0x00: /* I2C_REV */ + return s->revision; /* REV */ + + case 0x04: /* I2C_IE */ + return s->mask; + + case 0x08: /* I2C_STAT */ + return s->stat | (i2c_bus_busy(s->bus) << 12); + + case 0x0c: /* I2C_IV */ + if (s->revision >= OMAP2_INTR_REV) + break; + ret = ctz32(s->stat & s->mask); + if (ret != 32) { + s->stat ^= 1 << ret; + ret++; + } else { + ret = 0; + } + omap_i2c_interrupts_update(s); + return ret; + + case 0x10: /* I2C_SYSS */ + return (s->control >> 15) & 1; /* I2C_EN */ + + case 0x14: /* I2C_BUF */ + return s->dma; + + case 0x18: /* I2C_CNT */ + return s->count_cur; /* DCOUNT */ + + case 0x1c: /* I2C_DATA */ + ret = 0; + if (s->control & (1 << 14)) { /* BE */ + ret |= ((s->fifo >> 0) & 0xff) << 8; + ret |= ((s->fifo >> 8) & 0xff) << 0; + } else { + ret |= ((s->fifo >> 8) & 0xff) << 8; + ret |= ((s->fifo >> 0) & 0xff) << 0; + } + if (s->rxlen == 1) { + s->stat |= 1 << 15; /* SBD */ + s->rxlen = 0; + } else if (s->rxlen > 1) { + if (s->rxlen > 2) + s->fifo >>= 16; + s->rxlen -= 2; + } else { + /* XXX: remote access (qualifier) error - what's that? */ + } + if (!s->rxlen) { + s->stat &= ~(1 << 3); /* RRDY */ + if (((s->control >> 10) & 1) && /* MST */ + ((~s->control >> 9) & 1)) { /* TRX */ + s->stat |= 1 << 2; /* ARDY */ + s->control &= ~(1 << 10); /* MST */ + } + } + s->stat &= ~(1 << 11); /* ROVR */ + omap_i2c_fifo_run(s); + omap_i2c_interrupts_update(s); + return ret; + + case 0x20: /* I2C_SYSC */ + return 0; + + case 0x24: /* I2C_CON */ + return s->control; + + case 0x28: /* I2C_OA */ + return s->addr[0]; + + case 0x2c: /* I2C_SA */ + return s->addr[1]; + + case 0x30: /* I2C_PSC */ + return s->divider; + + case 0x34: /* I2C_SCLL */ + return s->times[0]; + + case 0x38: /* I2C_SCLH */ + return s->times[1]; + + case 0x3c: /* I2C_SYSTEST */ + if (s->test & (1 << 15)) { /* ST_EN */ + s->test ^= 0xa; + return s->test; + } else + return s->test & ~0x300f; + } + + OMAP_BAD_REG(addr); + return 0; +} + +static void omap_i2c_write(void *opaque, hwaddr addr, + uint32_t value) +{ + OMAPI2CState *s = opaque; + int offset = addr & OMAP_MPUI_REG_MASK; + int nack; + + switch (offset) { + case 0x00: /* I2C_REV */ + case 0x0c: /* I2C_IV */ + case 0x10: /* I2C_SYSS */ + OMAP_RO_REG(addr); + return; + + case 0x04: /* I2C_IE */ + s->mask = value & (s->revision < OMAP2_GC_REV ? 0x1f : 0x3f); + break; + + case 0x08: /* I2C_STAT */ + if (s->revision < OMAP2_INTR_REV) { + OMAP_RO_REG(addr); + return; + } + + /* RRDY and XRDY are reset by hardware. (in all versions???) */ + s->stat &= ~(value & 0x27); + omap_i2c_interrupts_update(s); + break; + + case 0x14: /* I2C_BUF */ + s->dma = value & 0x8080; + if (value & (1 << 15)) /* RDMA_EN */ + s->mask &= ~(1 << 3); /* RRDY_IE */ + if (value & (1 << 7)) /* XDMA_EN */ + s->mask &= ~(1 << 4); /* XRDY_IE */ + break; + + case 0x18: /* I2C_CNT */ + s->count = value; /* DCOUNT */ + break; + + case 0x1c: /* I2C_DATA */ + if (s->txlen > 2) { + /* XXX: remote access (qualifier) error - what's that? */ + break; + } + s->fifo <<= 16; + s->txlen += 2; + if (s->control & (1 << 14)) { /* BE */ + s->fifo |= ((value >> 8) & 0xff) << 8; + s->fifo |= ((value >> 0) & 0xff) << 0; + } else { + s->fifo |= ((value >> 0) & 0xff) << 8; + s->fifo |= ((value >> 8) & 0xff) << 0; + } + s->stat &= ~(1 << 10); /* XUDF */ + if (s->txlen > 2) + s->stat &= ~(1 << 4); /* XRDY */ + omap_i2c_fifo_run(s); + omap_i2c_interrupts_update(s); + break; + + case 0x20: /* I2C_SYSC */ + if (s->revision < OMAP2_INTR_REV) { + OMAP_BAD_REG(addr); + return; + } + + if (value & 2) { + omap_i2c_reset(DEVICE(s)); + } + break; + + case 0x24: /* I2C_CON */ + s->control = value & 0xcf87; + if (~value & (1 << 15)) { /* I2C_EN */ + if (s->revision < OMAP2_INTR_REV) { + omap_i2c_reset(DEVICE(s)); + } + break; + } + if ((value & (1 << 15)) && !(value & (1 << 10))) { /* MST */ + fprintf(stderr, "%s: I^2C slave mode not supported\n", + __FUNCTION__); + break; + } + if ((value & (1 << 15)) && value & (1 << 8)) { /* XA */ + fprintf(stderr, "%s: 10-bit addressing mode not supported\n", + __FUNCTION__); + break; + } + if ((value & (1 << 15)) && value & (1 << 0)) { /* STT */ + nack = !!i2c_start_transfer(s->bus, s->addr[1], /* SA */ + (~value >> 9) & 1); /* TRX */ + s->stat |= nack << 1; /* NACK */ + s->control &= ~(1 << 0); /* STT */ + s->fifo = 0; + if (nack) + s->control &= ~(1 << 1); /* STP */ + else { + s->count_cur = s->count; + omap_i2c_fifo_run(s); + } + omap_i2c_interrupts_update(s); + } + break; + + case 0x28: /* I2C_OA */ + s->addr[0] = value & 0x3ff; + break; + + case 0x2c: /* I2C_SA */ + s->addr[1] = value & 0x3ff; + break; + + case 0x30: /* I2C_PSC */ + s->divider = value; + break; + + case 0x34: /* I2C_SCLL */ + s->times[0] = value; + break; + + case 0x38: /* I2C_SCLH */ + s->times[1] = value; + break; + + case 0x3c: /* I2C_SYSTEST */ + s->test = value & 0xf80f; + if (value & (1 << 11)) /* SBB */ + if (s->revision >= OMAP2_INTR_REV) { + s->stat |= 0x3f; + omap_i2c_interrupts_update(s); + } + if (value & (1 << 15)) /* ST_EN */ + fprintf(stderr, "%s: System Test not supported\n", __FUNCTION__); + break; + + default: + OMAP_BAD_REG(addr); + return; + } +} + +static void omap_i2c_writeb(void *opaque, hwaddr addr, + uint32_t value) +{ + OMAPI2CState *s = opaque; + int offset = addr & OMAP_MPUI_REG_MASK; + + switch (offset) { + case 0x1c: /* I2C_DATA */ + if (s->txlen > 2) { + /* XXX: remote access (qualifier) error - what's that? */ + break; + } + s->fifo <<= 8; + s->txlen += 1; + s->fifo |= value & 0xff; + s->stat &= ~(1 << 10); /* XUDF */ + if (s->txlen > 2) + s->stat &= ~(1 << 4); /* XRDY */ + omap_i2c_fifo_run(s); + omap_i2c_interrupts_update(s); + break; + + default: + OMAP_BAD_REG(addr); + return; + } +} + +static const MemoryRegionOps omap_i2c_ops = { + .old_mmio = { + .read = { + omap_badwidth_read16, + omap_i2c_read, + omap_badwidth_read16, + }, + .write = { + omap_i2c_writeb, /* Only the last fifo write can be 8 bit. */ + omap_i2c_write, + omap_badwidth_write16, + }, + }, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static int omap_i2c_init(SysBusDevice *sbd) +{ + DeviceState *dev = DEVICE(sbd); + OMAPI2CState *s = OMAP_I2C(dev); + + if (!s->fclk) { + hw_error("omap_i2c: fclk not connected\n"); + } + if (s->revision >= OMAP2_INTR_REV && !s->iclk) { + /* Note that OMAP1 doesn't have a separate interface clock */ + hw_error("omap_i2c: iclk not connected\n"); + } + sysbus_init_irq(sbd, &s->irq); + sysbus_init_irq(sbd, &s->drq[0]); + sysbus_init_irq(sbd, &s->drq[1]); + memory_region_init_io(&s->iomem, OBJECT(s), &omap_i2c_ops, s, "omap.i2c", + (s->revision < OMAP2_INTR_REV) ? 0x800 : 0x1000); + sysbus_init_mmio(sbd, &s->iomem); + s->bus = i2c_init_bus(dev, NULL); + return 0; +} + +static Property omap_i2c_properties[] = { + DEFINE_PROP_UINT8("revision", OMAPI2CState, revision, 0), + DEFINE_PROP_PTR("iclk", OMAPI2CState, iclk), + DEFINE_PROP_PTR("fclk", OMAPI2CState, fclk), + DEFINE_PROP_END_OF_LIST(), +}; + +static void omap_i2c_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + k->init = omap_i2c_init; + dc->props = omap_i2c_properties; + dc->reset = omap_i2c_reset; + /* Reason: pointer properties "iclk", "fclk" */ + dc->cannot_instantiate_with_device_add_yet = true; +} + +static const TypeInfo omap_i2c_info = { + .name = TYPE_OMAP_I2C, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(OMAPI2CState), + .class_init = omap_i2c_class_init, +}; + +static void omap_i2c_register_types(void) +{ + type_register_static(&omap_i2c_info); +} + +I2CBus *omap_i2c_bus(DeviceState *omap_i2c) +{ + OMAPI2CState *s = OMAP_I2C(omap_i2c); + return s->bus; +} + +type_init(omap_i2c_register_types) diff --git a/qemu/hw/i2c/pm_smbus.c b/qemu/hw/i2c/pm_smbus.c new file mode 100644 index 000000000..ce1713d26 --- /dev/null +++ b/qemu/hw/i2c/pm_smbus.c @@ -0,0 +1,227 @@ +/* + * 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 STS_HOST_BUSY (1) +#define STS_INTR (1<<1) +#define STS_DEV_ERR (1<<2) +#define STS_BUS_ERR (1<<3) +#define STS_FAILED (1<<4) +#define STS_SMBALERT (1<<5) +#define STS_INUSE_STS (1<<6) +#define STS_BYTE_DONE (1<<7) +/* Signs of successfully transaction end : +* ByteDoneStatus = 1 (STS_BYTE_DONE) and INTR = 1 (STS_INTR ) +*/ + +//#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; + I2CBus *bus = s->smbus; + int ret; + + SMBUS_DPRINTF("SMBus trans addr=0x%02x prot=0x%02x\n", addr, prot); + /* Transaction isn't exec if STS_DEV_ERR bit set */ + if ((s->smb_stat & STS_DEV_ERR) != 0) { + goto error; + } + switch(prot) { + case 0x0: + ret = smbus_quick_command(bus, addr, read); + goto done; + case 0x1: + if (read) { + ret = smbus_receive_byte(bus, addr); + goto data8; + } else { + ret = smbus_send_byte(bus, addr, cmd); + goto done; + } + case 0x2: + if (read) { + ret = smbus_read_byte(bus, addr, cmd); + goto data8; + } else { + ret = smbus_write_byte(bus, addr, cmd, s->smb_data0); + goto done; + } + break; + case 0x3: + if (read) { + ret = smbus_read_word(bus, addr, cmd); + goto data16; + } else { + ret = smbus_write_word(bus, addr, cmd, (s->smb_data1 << 8) | s->smb_data0); + goto done; + } + break; + case 0x5: + if (read) { + ret = smbus_read_block(bus, addr, cmd, s->smb_data); + goto data8; + } else { + ret = smbus_write_block(bus, addr, cmd, s->smb_data, s->smb_data0); + goto done; + } + break; + default: + goto error; + } + abort(); + +data16: + if (ret < 0) { + goto error; + } + s->smb_data1 = ret >> 8; +data8: + if (ret < 0) { + goto error; + } + s->smb_data0 = ret; +done: + if (ret < 0) { + goto error; + } + s->smb_stat |= STS_BYTE_DONE | STS_INTR; + return; + +error: + s->smb_stat |= STS_DEV_ERR; + return; + +} + +static void smb_ioport_writeb(void *opaque, hwaddr addr, uint64_t val, + unsigned width) +{ + PMSMBus *s = opaque; + + SMBUS_DPRINTF("SMB writeb port=0x%04" HWADDR_PRIx + " val=0x%02" PRIx64 "\n", addr, val); + switch(addr) { + case SMBHSTSTS: + s->smb_stat = (~(val & 0xff)) & s->smb_stat; + 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%04" HWADDR_PRIx " 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, OBJECT(parent), &pm_smbus_ops, smb, + "pm-smbus", 64); +} diff --git a/qemu/hw/i2c/smbus.c b/qemu/hw/i2c/smbus.c new file mode 100644 index 000000000..6e27ae8bd --- /dev/null +++ b/qemu/hw/i2c/smbus.c @@ -0,0 +1,361 @@ +/* + * 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. */ +int smbus_quick_command(I2CBus *bus, uint8_t addr, int read) +{ + if (i2c_start_transfer(bus, addr, read)) { + return -1; + } + i2c_end_transfer(bus); + return 0; +} + +int smbus_receive_byte(I2CBus *bus, uint8_t addr) +{ + uint8_t data; + + if (i2c_start_transfer(bus, addr, 1)) { + return -1; + } + data = i2c_recv(bus); + i2c_nack(bus); + i2c_end_transfer(bus); + return data; +} + +int smbus_send_byte(I2CBus *bus, uint8_t addr, uint8_t data) +{ + if (i2c_start_transfer(bus, addr, 0)) { + return -1; + } + i2c_send(bus, data); + i2c_end_transfer(bus); + return 0; +} + +int smbus_read_byte(I2CBus *bus, uint8_t addr, uint8_t command) +{ + uint8_t data; + if (i2c_start_transfer(bus, addr, 0)) { + return -1; + } + i2c_send(bus, command); + i2c_start_transfer(bus, addr, 1); + data = i2c_recv(bus); + i2c_nack(bus); + i2c_end_transfer(bus); + return data; +} + +int smbus_write_byte(I2CBus *bus, uint8_t addr, uint8_t command, uint8_t data) +{ + if (i2c_start_transfer(bus, addr, 0)) { + return -1; + } + i2c_send(bus, command); + i2c_send(bus, data); + i2c_end_transfer(bus); + return 0; +} + +int smbus_read_word(I2CBus *bus, uint8_t addr, uint8_t command) +{ + uint16_t data; + if (i2c_start_transfer(bus, addr, 0)) { + return -1; + } + 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; +} + +int smbus_write_word(I2CBus *bus, uint8_t addr, uint8_t command, uint16_t data) +{ + if (i2c_start_transfer(bus, addr, 0)) { + return -1; + } + i2c_send(bus, command); + i2c_send(bus, data & 0xff); + i2c_send(bus, data >> 8); + i2c_end_transfer(bus); + return 0; +} + +int smbus_read_block(I2CBus *bus, uint8_t addr, uint8_t command, uint8_t *data) +{ + int len; + int i; + + if (i2c_start_transfer(bus, addr, 0)) { + return -1; + } + 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; +} + +int smbus_write_block(I2CBus *bus, uint8_t addr, uint8_t command, uint8_t *data, + int len) +{ + int i; + + if (len > 32) + len = 32; + + if (i2c_start_transfer(bus, addr, 0)) { + return -1; + } + i2c_send(bus, command); + i2c_send(bus, len); + for (i = 0; i < len; i++) { + i2c_send(bus, data[i]); + } + i2c_end_transfer(bus); + return 0; +} + +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/qemu/hw/i2c/smbus_eeprom.c b/qemu/hw/i2c/smbus_eeprom.c new file mode 100644 index 000000000..72c09cba6 --- /dev/null +++ b/qemu/hw/i2c/smbus_eeprom.c @@ -0,0 +1,158 @@ +/* + * 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 + /* A 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; + /* Reason: pointer property "data" */ + dc->cannot_instantiate_with_device_add_yet = true; +} + +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(I2CBus *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/qemu/hw/i2c/smbus_ich9.c b/qemu/hw/i2c/smbus_ich9.c new file mode 100644 index 000000000..91d4d322c --- /dev/null +++ b/qemu/hw/i2c/smbus_ich9.c @@ -0,0 +1,129 @@ +/* + * 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, + .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 void ich9_smbus_realize(PCIDevice *d, Error **errp) +{ + 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); +} + +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->vmsd = &vmstate_ich9_smbus; + dc->desc = "ICH9 SMBUS Bridge"; + k->realize = ich9_smbus_realize; + k->config_write = ich9_smbus_write_config; + /* + * Reason: part of ICH9 southbridge, needs to be wired up by + * pc_q35_init() + */ + dc->cannot_instantiate_with_device_add_yet = true; +} + +I2CBus *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/qemu/hw/i2c/versatile_i2c.c b/qemu/hw/i2c/versatile_i2c.c new file mode 100644 index 000000000..3c0c2c106 --- /dev/null +++ b/qemu/hw/i2c/versatile_i2c.c @@ -0,0 +1,113 @@ +/* + * 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 "bitbang_i2c.h" + +#define TYPE_VERSATILE_I2C "versatile_i2c" +#define VERSATILE_I2C(obj) \ + OBJECT_CHECK(VersatileI2CState, (obj), TYPE_VERSATILE_I2C) + +typedef struct VersatileI2CState { + SysBusDevice parent_obj; + + 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 *sbd) +{ + DeviceState *dev = DEVICE(sbd); + VersatileI2CState *s = VERSATILE_I2C(dev); + I2CBus *bus; + + bus = i2c_init_bus(dev, "i2c"); + s->bitbang = bitbang_i2c_init(bus); + memory_region_init_io(&s->iomem, OBJECT(s), &versatile_i2c_ops, s, + "versatile_i2c", 0x1000); + sysbus_init_mmio(sbd, &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 = TYPE_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) |