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