]>
Commit | Line | Data |
---|---|---|
3ead03bd AZ |
1 | /* |
2 | * Bit-Bang i2c emulation extracted from | |
3 | * Marvell MV88W8618 / Freecom MusicPal emulation. | |
4 | * | |
5 | * Copyright (c) 2008 Jan Kiszka | |
6 | * | |
7 | * This code is licenced under the GNU GPL v2. | |
8 | */ | |
9 | #include "hw.h" | |
10 | #include "i2c.h" | |
11 | #include "sysbus.h" | |
12 | ||
13 | typedef enum bitbang_i2c_state { | |
14 | STOPPED = 0, | |
15 | INITIALIZING, | |
16 | SENDING_BIT7, | |
17 | SENDING_BIT6, | |
18 | SENDING_BIT5, | |
19 | SENDING_BIT4, | |
20 | SENDING_BIT3, | |
21 | SENDING_BIT2, | |
22 | SENDING_BIT1, | |
23 | SENDING_BIT0, | |
24 | WAITING_FOR_ACK, | |
25 | RECEIVING_BIT7, | |
26 | RECEIVING_BIT6, | |
27 | RECEIVING_BIT5, | |
28 | RECEIVING_BIT4, | |
29 | RECEIVING_BIT3, | |
30 | RECEIVING_BIT2, | |
31 | RECEIVING_BIT1, | |
32 | RECEIVING_BIT0, | |
33 | SENDING_ACK | |
34 | } bitbang_i2c_state; | |
35 | ||
36 | typedef struct bitbang_i2c_interface { | |
37 | SysBusDevice busdev; | |
38 | i2c_bus *bus; | |
39 | bitbang_i2c_state state; | |
40 | int last_data; | |
41 | int last_clock; | |
42 | uint8_t buffer; | |
43 | int current_addr; | |
44 | qemu_irq out; | |
45 | } bitbang_i2c_interface; | |
46 | ||
47 | static void bitbang_i2c_enter_stop(bitbang_i2c_interface *i2c) | |
48 | { | |
49 | if (i2c->current_addr >= 0) | |
50 | i2c_end_transfer(i2c->bus); | |
51 | i2c->current_addr = -1; | |
52 | i2c->state = STOPPED; | |
53 | } | |
54 | ||
55 | static void bitbang_i2c_gpio_set(void *opaque, int irq, int level) | |
56 | { | |
57 | bitbang_i2c_interface *i2c = opaque; | |
58 | int data; | |
59 | int clock; | |
60 | int data_goes_up; | |
61 | int data_goes_down; | |
62 | int clock_goes_up; | |
63 | int clock_goes_down; | |
64 | ||
65 | /* get pins states */ | |
66 | data = i2c->last_data; | |
67 | clock = i2c->last_clock; | |
68 | ||
69 | if (irq == 0) | |
70 | data = level; | |
71 | if (irq == 1) | |
72 | clock = level; | |
73 | ||
74 | /* compute pins changes */ | |
75 | data_goes_up = data == 1 && i2c->last_data == 0; | |
76 | data_goes_down = data == 0 && i2c->last_data == 1; | |
77 | clock_goes_up = clock == 1 && i2c->last_clock == 0; | |
78 | clock_goes_down = clock == 0 && i2c->last_clock == 1; | |
79 | ||
80 | if (data_goes_up == 0 && data_goes_down == 0 && | |
81 | clock_goes_up == 0 && clock_goes_down == 0) | |
82 | return; | |
83 | ||
84 | if (!i2c) | |
85 | return; | |
86 | ||
87 | if ((RECEIVING_BIT7 > i2c->state && i2c->state > RECEIVING_BIT0) | |
88 | || i2c->state == WAITING_FOR_ACK) | |
89 | qemu_set_irq(i2c->out, 0); | |
90 | ||
91 | switch (i2c->state) { | |
92 | case STOPPED: | |
93 | if (data_goes_down && clock == 1) | |
94 | i2c->state = INITIALIZING; | |
95 | break; | |
96 | ||
97 | case INITIALIZING: | |
98 | if (clock_goes_down && data == 0) | |
99 | i2c->state = SENDING_BIT7; | |
100 | else | |
101 | bitbang_i2c_enter_stop(i2c); | |
102 | break; | |
103 | ||
104 | case SENDING_BIT7 ... SENDING_BIT0: | |
105 | if (clock_goes_down) { | |
106 | i2c->buffer = (i2c->buffer << 1) | data; | |
107 | /* will end up in WAITING_FOR_ACK */ | |
108 | i2c->state++; | |
109 | } else if (data_goes_up && clock == 1) | |
110 | bitbang_i2c_enter_stop(i2c); | |
111 | break; | |
112 | ||
113 | case WAITING_FOR_ACK: | |
114 | if (clock_goes_down) { | |
115 | if (i2c->current_addr < 0) { | |
116 | i2c->current_addr = i2c->buffer; | |
117 | i2c_start_transfer(i2c->bus, (i2c->current_addr & 0xfe) / 2, | |
118 | i2c->buffer & 1); | |
119 | } else | |
120 | i2c_send(i2c->bus, i2c->buffer); | |
121 | if (i2c->current_addr & 1) { | |
122 | i2c->state = RECEIVING_BIT7; | |
123 | i2c->buffer = i2c_recv(i2c->bus); | |
124 | } else | |
125 | i2c->state = SENDING_BIT7; | |
126 | } else if (data_goes_up && clock == 1) | |
127 | bitbang_i2c_enter_stop(i2c); | |
128 | break; | |
129 | ||
130 | case RECEIVING_BIT7 ... RECEIVING_BIT0: | |
131 | qemu_set_irq(i2c->out, i2c->buffer >> 7); | |
132 | if (clock_goes_down) { | |
133 | /* will end up in SENDING_ACK */ | |
134 | i2c->state++; | |
135 | i2c->buffer <<= 1; | |
136 | } else if (data_goes_up && clock == 1) | |
137 | bitbang_i2c_enter_stop(i2c); | |
138 | break; | |
139 | ||
140 | case SENDING_ACK: | |
141 | if (clock_goes_down) { | |
142 | i2c->state = RECEIVING_BIT7; | |
143 | if (data == 0) | |
144 | i2c->buffer = i2c_recv(i2c->bus); | |
145 | else | |
146 | i2c_nack(i2c->bus); | |
147 | } else if (data_goes_up && clock == 1) | |
148 | bitbang_i2c_enter_stop(i2c); | |
149 | break; | |
150 | } | |
151 | ||
152 | i2c->last_data = data; | |
153 | i2c->last_clock = clock; | |
154 | } | |
155 | ||
81a322d4 | 156 | static int bitbang_i2c_init(SysBusDevice *dev) |
3ead03bd AZ |
157 | { |
158 | bitbang_i2c_interface *s = FROM_SYSBUS(bitbang_i2c_interface, dev); | |
159 | i2c_bus *bus; | |
160 | ||
161 | sysbus_init_mmio(dev, 0x0, 0); | |
162 | ||
163 | bus = i2c_init_bus(&dev->qdev, "i2c"); | |
164 | s->bus = bus; | |
165 | ||
166 | s->last_data = 1; | |
167 | s->last_clock = 1; | |
168 | ||
169 | qdev_init_gpio_in(&dev->qdev, bitbang_i2c_gpio_set, 2); | |
170 | qdev_init_gpio_out(&dev->qdev, &s->out, 1); | |
81a322d4 GH |
171 | |
172 | return 0; | |
3ead03bd AZ |
173 | } |
174 | ||
175 | static void bitbang_i2c_register(void) | |
176 | { | |
177 | sysbus_register_dev("bitbang_i2c", | |
178 | sizeof(bitbang_i2c_interface), bitbang_i2c_init); | |
179 | } | |
180 | ||
181 | device_init(bitbang_i2c_register) |