From 3cd035d863bd516a2967810c22258d82535c051f Mon Sep 17 00:00:00 2001 From: Paul Brook Date: Fri, 20 Nov 2009 23:37:15 +0000 Subject: [PATCH] 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 --- hw/bitbang_i2c.c | 409 +++++++++++++++++++++++++++++++------------------------ hw/bitbang_i2c.h | 14 ++ hw/musicpal.c | 2 +- 3 files changed, 243 insertions(+), 182 deletions(-) rewrite hw/bitbang_i2c.c (62%) create mode 100644 hw/bitbang_i2c.h diff --git a/hw/bitbang_i2c.c b/hw/bitbang_i2c.c dissimilarity index 62% index 443ddb2b0f..4ee99a18b9 100644 --- a/hw/bitbang_i2c.c +++ b/hw/bitbang_i2c.c @@ -1,181 +1,228 @@ -/* - * Bit-Bang i2c emulation extracted from - * Marvell MV88W8618 / Freecom MusicPal emulation. - * - * Copyright (c) 2008 Jan Kiszka - * - * This code is licenced under the GNU GPL v2. - */ -#include "hw.h" -#include "i2c.h" -#include "sysbus.h" - -typedef enum bitbang_i2c_state { - STOPPED = 0, - INITIALIZING, - 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 -} bitbang_i2c_state; - -typedef struct bitbang_i2c_interface { - SysBusDevice busdev; - i2c_bus *bus; - bitbang_i2c_state state; - int last_data; - int last_clock; - uint8_t buffer; - int current_addr; - qemu_irq out; -} bitbang_i2c_interface; - -static void bitbang_i2c_enter_stop(bitbang_i2c_interface *i2c) -{ - 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) -{ - 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; - - case INITIALIZING: - if (clock_goes_down && data == 0) - i2c->state = SENDING_BIT7; - else - bitbang_i2c_enter_stop(i2c); - break; - - 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; - - 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; - - 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->last_data = data; - i2c->last_clock = clock; -} - -static int bitbang_i2c_init(SysBusDevice *dev) -{ - bitbang_i2c_interface *s = FROM_SYSBUS(bitbang_i2c_interface, 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; - - qdev_init_gpio_in(&dev->qdev, bitbang_i2c_gpio_set, 2); - qdev_init_gpio_out(&dev->qdev, &s->out, 1); - - return 0; -} - -static void bitbang_i2c_register(void) -{ - sysbus_register_dev("bitbang_i2c", - sizeof(bitbang_i2c_interface), bitbang_i2c_init); -} - -device_init(bitbang_i2c_register) +/* + * Bit-Bang i2c emulation extracted from + * Marvell MV88W8618 / Freecom MusicPal emulation. + * + * Copyright (c) 2008 Jan Kiszka + * + * This code is licenced under the GNU GPL v2. + */ +#include "hw.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, + 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 +} bitbang_i2c_state; + +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; +}; + +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: + 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_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; +} + +/* 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 gpio_i2c_init(SysBusDevice *dev) +{ + GPIOI2CState *s = FROM_SYSBUS(GPIOI2CState, dev); + i2c_bus *bus; + + sysbus_init_mmio(dev, 0x0, 0); + + bus = i2c_init_bus(&dev->qdev, "i2c"); + 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); + + 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_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 0000000000..519d2dc22f --- /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 0d21f1778f..264669f618 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); -- 2.11.4.GIT