diff options
author | Paul Brook <paul@codesourcery.com> | 2009-11-20 23:37:15 +0000 |
---|---|---|
committer | Paul Brook <paul@codesourcery.com> | 2009-11-22 21:27:40 +0000 |
commit | 3cd035d863bd516a2967810c22258d82535c051f (patch) | |
tree | fdbd357881c1df41ca0928ec54c42a6cfe0bdd28 | |
parent | 1dfe3943e9f74cf55a8c84238b8d1a9d3486387f (diff) |
GPIO I2C rework
Reqrite bitbanging I2C implementation. New code improves stop/start
condition handling, and gives more accurate input line level.
Introduce intermediate abstraction layer for I2C bitbanging that
is not connected via a GPIO port.
Signed-off-by: Paul Brook <paul@codesourcery.com>
-rw-r--r-- | hw/bitbang_i2c.c | 245 | ||||
-rw-r--r-- | hw/bitbang_i2c.h | 14 | ||||
-rw-r--r-- | hw/musicpal.c | 2 |
3 files changed, 161 insertions, 100 deletions
diff --git a/hw/bitbang_i2c.c b/hw/bitbang_i2c.c index 443ddb2b0..4ee99a18b 100644 --- a/hw/bitbang_i2c.c +++ b/hw/bitbang_i2c.c @@ -7,12 +7,20 @@ * This code is licenced under the GNU GPL v2. */ #include "hw.h" -#include "i2c.h" +#include "bitbang_i2c.h" #include "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, - INITIALIZING, SENDING_BIT7, SENDING_BIT6, SENDING_BIT5, @@ -33,138 +41,171 @@ typedef enum bitbang_i2c_state { SENDING_ACK } bitbang_i2c_state; -typedef struct bitbang_i2c_interface { - SysBusDevice busdev; +struct bitbang_i2c_interface { i2c_bus *bus; bitbang_i2c_state state; int last_data; int last_clock; + int device_out; uint8_t buffer; int current_addr; - qemu_irq out; -} bitbang_i2c_interface; +}; 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; } -static void bitbang_i2c_gpio_set(void *opaque, int irq, int level) +/* 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) { - bitbang_i2c_interface *i2c = opaque; int data; - int clock; - int data_goes_up; - int data_goes_down; - int clock_goes_up; - int clock_goes_down; - - /* get pins states */ - data = i2c->last_data; - clock = i2c->last_clock; - - if (irq == 0) - data = level; - if (irq == 1) - clock = level; - - /* compute pins changes */ - data_goes_up = data == 1 && i2c->last_data == 0; - data_goes_down = data == 0 && i2c->last_data == 1; - clock_goes_up = clock == 1 && i2c->last_clock == 0; - clock_goes_down = clock == 0 && i2c->last_clock == 1; - - if (data_goes_up == 0 && data_goes_down == 0 && - clock_goes_up == 0 && clock_goes_down == 0) - return; - - if (!i2c) - return; - - if ((RECEIVING_BIT7 > i2c->state && i2c->state > RECEIVING_BIT0) - || i2c->state == WAITING_FOR_ACK) - qemu_set_irq(i2c->out, 0); - switch (i2c->state) { - case STOPPED: - if (data_goes_down && clock == 1) - i2c->state = INITIALIZING; - break; + if (level != 0 && level != 1) { + abort(); + } - case INITIALIZING: - if (clock_goes_down && data == 0) + 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; - else + i2c->current_addr = -1; + } else { + /* STOP condition. */ bitbang_i2c_enter_stop(i2c); - break; + } + 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: + return bitbang_i2c_ret(i2c, 1); case SENDING_BIT7 ... SENDING_BIT0: - if (clock_goes_down) { - i2c->buffer = (i2c->buffer << 1) | data; - /* will end up in WAITING_FOR_ACK */ - i2c->state++; - } else if (data_goes_up && clock == 1) - bitbang_i2c_enter_stop(i2c); - break; + 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 (clock_goes_down) { - if (i2c->current_addr < 0) { - i2c->current_addr = i2c->buffer; - i2c_start_transfer(i2c->bus, (i2c->current_addr & 0xfe) / 2, - i2c->buffer & 1); - } else - i2c_send(i2c->bus, i2c->buffer); - if (i2c->current_addr & 1) { - i2c->state = RECEIVING_BIT7; - i2c->buffer = i2c_recv(i2c->bus); - } else - i2c->state = SENDING_BIT7; - } else if (data_goes_up && clock == 1) - bitbang_i2c_enter_stop(i2c); - break; - - case RECEIVING_BIT7 ... RECEIVING_BIT0: - qemu_set_irq(i2c->out, i2c->buffer >> 7); - if (clock_goes_down) { - /* will end up in SENDING_ACK */ - i2c->state++; - i2c->buffer <<= 1; - } else if (data_goes_up && clock == 1) - bitbang_i2c_enter_stop(i2c); - break; + 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: - if (clock_goes_down) { - i2c->state = RECEIVING_BIT7; - if (data == 0) - i2c->buffer = i2c_recv(i2c->bus); - else - i2c_nack(i2c->bus); - } else if (data_goes_up && clock == 1) - bitbang_i2c_enter_stop(i2c); - break; + i2c->state = RECEIVING_BIT7; + if (data != 0) { + DPRINTF("NACKED\n"); + i2c_nack(i2c->bus); + } else { + DPRINTF("ACKED\n"); + } + return bitbang_i2c_ret(i2c, 1); } + abort(); +} + +bitbang_i2c_interface *bitbang_i2c_init(i2c_bus *bus) +{ + bitbang_i2c_interface *s; + + s = qemu_mallocz(sizeof(bitbang_i2c_interface)); + + s->bus = bus; + s->last_data = 1; + s->last_clock = 1; + s->device_out = 1; + + return s; +} - i2c->last_data = data; - i2c->last_clock = clock; +/* GPIO interface. */ +typedef struct { + SysBusDevice busdev; + 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 bitbang_i2c_init(SysBusDevice *dev) +static int gpio_i2c_init(SysBusDevice *dev) { - bitbang_i2c_interface *s = FROM_SYSBUS(bitbang_i2c_interface, dev); + GPIOI2CState *s = FROM_SYSBUS(GPIOI2CState, dev); i2c_bus *bus; sysbus_init_mmio(dev, 0x0, 0); bus = i2c_init_bus(&dev->qdev, "i2c"); - s->bus = bus; - - s->last_data = 1; - s->last_clock = 1; + s->bitbang = bitbang_i2c_init(bus); qdev_init_gpio_in(&dev->qdev, bitbang_i2c_gpio_set, 2); qdev_init_gpio_out(&dev->qdev, &s->out, 1); @@ -172,10 +213,16 @@ static int bitbang_i2c_init(SysBusDevice *dev) return 0; } +static SysBusDeviceInfo gpio_i2c_info = { + .init = gpio_i2c_init, + .qdev.name = "gpio_i2c", + .qdev.desc = "Virtual GPIO to I2C bridge", + .qdev.size = sizeof(GPIOI2CState), +}; + static void bitbang_i2c_register(void) { - sysbus_register_dev("bitbang_i2c", - sizeof(bitbang_i2c_interface), bitbang_i2c_init); + sysbus_register_withprop(&gpio_i2c_info); } device_init(bitbang_i2c_register) diff --git a/hw/bitbang_i2c.h b/hw/bitbang_i2c.h new file mode 100644 index 000000000..519d2dc22 --- /dev/null +++ b/hw/bitbang_i2c.h @@ -0,0 +1,14 @@ +#ifndef BITBANG_I2C_H +#define BITBANG_I2C_H + +#include "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(i2c_bus *bus); +int bitbang_i2c_set(bitbang_i2c_interface *i2c, int line, int level); + +#endif diff --git a/hw/musicpal.c b/hw/musicpal.c index 0d21f1778..264669f61 100644 --- a/hw/musicpal.c +++ b/hw/musicpal.c @@ -1565,7 +1565,7 @@ static void musicpal_init(ram_addr_t ram_size, musicpal_misc_init(); dev = sysbus_create_simple("musicpal_gpio", MP_GPIO_BASE, pic[MP_GPIO_IRQ]); - i2c_dev = sysbus_create_simple("bitbang_i2c", 0, NULL); + i2c_dev = sysbus_create_simple("gpio_i2c", 0, NULL); i2c = (i2c_bus *)qdev_get_child_bus(i2c_dev, "i2c"); lcd_dev = sysbus_create_simple("musicpal_lcd", MP_LCD_BASE, NULL); |