qemu/hw/bitbang_i2c.c

182 lines
4.5 KiB
C

/*
* 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)