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