-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Extract musicpal.c I2C bitbanging code and make it gpio aware
Signed-off-by: Benoit Canet <[email protected]> Signed-off-by: Andrzej Zaborowski <[email protected]>
- Loading branch information
1 parent
343ec8e
commit 3ead03b
Showing
2 changed files
with
180 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
/* | ||
* 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 void 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); | ||
} | ||
|
||
static void bitbang_i2c_register(void) | ||
{ | ||
sysbus_register_dev("bitbang_i2c", | ||
sizeof(bitbang_i2c_interface), bitbang_i2c_init); | ||
} | ||
|
||
device_init(bitbang_i2c_register) |