-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathi2c.c
254 lines (215 loc) · 7.21 KB
/
i2c.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
/* i2c.c */
/* Copyright (c) 2019 J. M. Spivey */
#include "microbian.h"
#include "hardware.h"
#include <stddef.h>
/* On the V1, there is only one I2C bus, but on the V2 there are
separate buses for internal and external devices. A client program
should call i2c_init passing I2C_INTERNAL or I2C_EXTERNAL (or once for
each) according to its needs. On the V1 board, these two constants
are equal, and we'll make sure that only one driver process is
started. Both I2C interfaces have the same register layout, and we
use some navish C language tricks to refer to both interfaces with the
same code. */
static int I2C_TASK[N_I2C];
static const struct {
unsigned scl;
unsigned sda;
int irq;
} i2c_pins[N_I2C] = {
#ifdef UBIT_V1
{ I2C_SCL, I2C_SDA, I2C_IRQ }
#endif
#ifdef UBIT_V2
{ I2C0_SCL, I2C0_SDA, I2C0_IRQ },
{ I2C1_SCL, I2C1_SDA, I2C1_IRQ }
#endif
};
/* i2c_wait -- wait for an expected interrupt event and detect error */
static int i2c_wait(int bus, unsigned volatile *event)
{
int irq = i2c_pins[bus].irq;
receive(INTERRUPT, NULL);
if (I2C[bus]->ERROR) {
I2C[bus]->ERROR = 0;
clear_pending(irq);
enable_irq(irq);
return ERR;
}
assert(*event);
*event = 0;
clear_pending(irq);
enable_irq(irq);
return OK;
}
/* i2c_do_write -- send one or more bytes */
static int i2c_do_write(int bus, char *buf, int n)
{
int status = OK;
/* The I2C hardware makes zero-length writes impossible, because
there is no event generated when the address has been sent. */
for (int i = 0; i < n; i++) {
I2C[bus]->TXD = buf[i];
status = i2c_wait(bus, &I2C[bus]->TXDSENT);
if (status != OK) return status;
}
return OK;
}
/* i2c_stop -- signal stop condition */
static void i2c_stop(int bus)
{
I2C[bus]->STOP = 1;
i2c_wait(bus, &I2C[bus]->STOPPED);
}
/* i2c_task -- driver process for I2C hardware */
static void i2c_task(int bus)
{
unsigned scl = i2c_pins[bus].scl, sda = i2c_pins[bus].sda;
int irq = i2c_pins[bus].irq;
message m;
int client, addr, n1, n2, status, error = 0;
char *buf1, *buf2;
/* Configure pins -- thanks to friends at University of Cantabria */
gpio_drive(scl, GPIO_DRIVE_S0D1);
gpio_drive(sda, GPIO_DRIVE_S0D1);
/* Configure I2C hardware */
I2C[bus]->PSELSCL = scl;
I2C[bus]->PSELSDA = sda;
I2C[bus]->FREQUENCY = I2C_FREQUENCY_100kHz;
I2C[bus]->ENABLE = I2C_ENABLE_Enabled;
/* Enable interrupts */
I2C[bus]->INTEN = BIT(I2C_INT_RXDREADY) | BIT(I2C_INT_TXDSENT)
| BIT(I2C_INT_STOPPED) | BIT(I2C_INT_ERROR);
connect(irq);
enable_irq(irq);
while (1) {
receive(ANY, &m);
client = m.sender;
addr = m.byte1; /* Address [0..127] without R/W flag */
n1 = m.byte2; /* Number of bytes in command */
n2 = m.byte3; /* Number of bytes to transfer (R/W) */
buf1 = m.ptr2; /* Buffer for command */
buf2 = m.ptr3; /* Buffer for transfer */
switch (m.type) {
case READ:
I2C[bus]->ADDRESS = addr;
status = OK;
if (n1 > 0) {
/* Write followed by read, with repeated start */
I2C[bus]->STARTTX = 1;
status = i2c_do_write(bus, buf1, n1);
}
/* The hardware reference manual is wrong in several ways,
but the following code (based on timing diagrams in the
reference manual) works reliably. */
if (status == OK) {
for (int i = 0; i < n2; i++) {
/* On all but the last byte, use SUSPEND to send
an ACK after receiving the byte. Use STOP to
send a NACK at the end. */
if (i < n2-1)
I2C[bus]->SHORTS = BIT(I2C_BB_SUSPEND);
else
I2C[bus]->SHORTS = BIT(I2C_BB_STOP);
/* Start the first byte with STARTTX, and the rest
with RESUME following the SUSPEND. */
if (i == 0)
I2C[bus]->STARTRX = 1;
else
I2C[bus]->RESUME = 1;
status = i2c_wait(bus, &I2C[bus]->RXDREADY);
if (status != OK) break;
buf2[i] = I2C[bus]->RXD;
}
}
if (status == OK)
i2c_wait(bus, &I2C[bus]->STOPPED);
if (status != OK) {
i2c_stop(bus);
error = I2C[bus]->ERRORSRC;
I2C[bus]->ERRORSRC = I2C_ERRORSRC_All;
}
I2C[bus]->SHORTS = 0;
m.type = REPLY;
m.int1 = status;
m.int2 = error;
send(client, &m);
break;
case WRITE:
I2C[bus]->ADDRESS = addr;
status = OK;
/* A single write transaction */
I2C[bus]->STARTTX = 1;
if (n1 > 0)
status = i2c_do_write(bus, buf1, n1);
if (status == OK && n2 > 0)
status = i2c_do_write(bus, buf2, n2);
i2c_stop(bus);
if (status != OK) {
error = I2C[bus]->ERRORSRC;
I2C[bus]->ERRORSRC = I2C_ERRORSRC_All;
}
m.type = REPLY;
m.int1 = status;
m.int2 = error;
send(client, &m);
break;
default:
badmesg(m.type);
}
}
}
/* i2c_init -- start I2C driver process */
void i2c_init(int bus)
{
/* If internal and external busnels are the same, then i2c_init
may be called twice for the same bus. */
if (I2C_TASK[bus] == 0)
I2C_TASK[bus] = start("I2C", i2c_task, bus, 256);
}
/* i2c_xfer -- i2c transaction with command write then data read or write */
int i2c_xfer(int bus, int kind, int addr,
byte *buf1, int n1, byte *buf2, int n2) {
message m;
m.type = kind;
m.byte1 = addr;
m.byte2 = n1;
m.byte3 = n2;
m.ptr2 = buf1;
m.ptr3 = buf2;
sendrec(I2C_TASK[bus], &m);
return m.int1;
}
/* i2c_probe -- try to access an I2C device */
int i2c_probe(int bus, int addr)
{
byte buf = 0;
return i2c_xfer(bus, WRITE, addr, &buf, 1, NULL, 0);
}
/* i2c_read_bytes -- send command and read multi-byte result */
void i2c_read_bytes(int bus, int addr, int cmd, byte *buf2, int n2)
{
byte buf1 = cmd;
int status = i2c_xfer(bus, READ, addr, &buf1, 1, buf2, n2);
assert(status == OK);
}
/* i2c_read_reg -- send command and read one byte */
int i2c_read_reg(int bus, int addr, int cmd)
{
byte buf;
i2c_read_bytes(bus, addr, cmd, &buf, 1);
return buf;
}
/* i2c_write_bytes -- send command and write multi-byte data */
void i2c_write_bytes(int bus, int addr, int cmd, byte *buf2, int n2)
{
byte buf1 = cmd;
int status = i2c_xfer(bus, WRITE, addr, &buf1, 1, buf2, n2);
assert(status == OK);
}
/* i2c_write_reg -- send command and write data */
void i2c_write_reg(int bus, int addr, int cmd, int val)
{
byte buf = val;
i2c_write_bytes(bus, addr, cmd, &buf, 1);
}