-
Notifications
You must be signed in to change notification settings - Fork 2k
/
Copy pathi2c_nrf52_nrf9160.c
410 lines (332 loc) · 12.1 KB
/
i2c_nrf52_nrf9160.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
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
/*
* Copyright (C) 2017 HAW Hamburg
* 2018 Freie Universität Berlin
* 2018 Mesotic SAS
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup cpu_nrf5x_common
* @{
*
* @file
* @brief Low-level I2C (TWI) peripheral driver implementation
*
* @author Dimitri Nahm <[email protected]>
* @author Hauke Petersen <[email protected]>
* @author Dylan Laduranty <[email protected]>
*
* As this implementation is based on the nRF5x TWIM peripheral, it can not
* issue a read following a read (or a write following a write) without
* creating a (repeated) start condition:
* <https://devzone.nordicsemi.com/f/nordic-q-a/66615/nrf52840-twim-how-to-write-multiple-buffers-without-repeated-start-condition>,
* backed also by later experiments discussed in the [Rust embedded
* channel](https://matrix.to/#/!BHcierreUuwCMxVqOf:matrix.org/$JwNejRaeJx_tvqKgS88GenDG8ZNHrkTW09896dIehQ8?via=matrix.org&via=catircservices.org&via=tchncs.de).
* Due to this shortcoming in the hardware, any operations with I2C_NOSTART
* fail.
*
* Relatedly, the successful termination of a read or write can not be detected
* by an interrupt (only the eventual STOPPED condition after the event
* short-circuiting of LASTTX/LASTRX to STOP triggers one). There are LASTTX /
* LASTRX interrupts, but while the LASTTX is sensible enough (the last byte
* has been read, is being written, the caller may now repurpose the buffers),
* the LASTRX interrupt fires at the start of the last byte reading, and the
* user can not reliably know when the last byte was written (at least not
* easily). Therefore, reads with I2C_NOSTOP are not supported.
*
* In combination, these still allow the typical I2C operations: A single
* write, and a write (selecting a register) followed by a read, as well as
* stand-alone reads. More complex patterns are not supported; in particular,
* scatter-gather reads or writes are not possible.
*
* @}
*/
#include <assert.h>
#include <string.h>
#include <errno.h>
#include "cpu.h"
#include "mutex.h"
#include "assert.h"
#include "periph/i2c.h"
#include "periph/gpio.h"
#include "byteorder.h"
#define ENABLE_DEBUG 0
#include "debug.h"
/**
* @brief If any of the 8 lower bits are set, the speed value is invalid
*/
#define INVALID_SPEED_MASK (0xff)
/**
* @brief Allocate a tx buffer
*/
static uint8_t tx_buf[256];
/**
* @brief Mutex for locking the TX buffer
*/
static mutex_t buffer_lock;
/**
* @brief Initialized dev locks (we have a maximum of two devices...)
*/
static mutex_t locks[I2C_NUMOF];
/**
* @brief array with a busy mutex for each I2C device, used to block the
* thread until the transfer is done
*/
static mutex_t busy[I2C_NUMOF];
void i2c_isr_handler(void *arg);
static inline NRF_TWIM_Type *bus(i2c_t dev)
{
return i2c_config[dev].dev;
}
/**
* @brief Like i2c_write_bytes, but with the constraint (created by the
* hardware) that data is in RAM
*/
static int direct_i2c_write_bytes(i2c_t dev, uint16_t addr, const void *data,
size_t len,
uint8_t flags);
/**
* Block until the interrupt described by inten_success_flag or
* TWIM_INTEN_ERROR_Msk fires.
*
* Allowed values for inten_success_flag are
* * TWIM_INTEN_STOPPED_Msk (when a stop condition is to be set and the short
* circuit will pull TWIM into the stopped condition)
* * TWIM_INTEN_LASTTX_Msk (when sending without a stop condition)
*
* (TWIM_INTEN_LASTRX_Msk makes no sense here because that interrupt fires
* before the data is ready).
*
* Any addition needs to be added to the mask in i2c_isr_handler.
*/
static int finish(i2c_t dev, int inten_success_flag)
{
DEBUG("[i2c] waiting for success (STOPPED/LASTTX) or ERROR event\n");
/* Unmask interrupts */
bus(dev)->INTENSET = inten_success_flag | TWIM_INTEN_ERROR_Msk;
mutex_lock(&busy[dev]);
if ((bus(dev)->EVENTS_STOPPED)) {
bus(dev)->EVENTS_STOPPED = 0;
DEBUG("[i2c] finish: stop event occurred\n");
}
if (inten_success_flag & TWIM_INTEN_LASTTX_Msk) {
/* The interrupt is raised already when the last TX is started, but we
* have to wait until it was actually transmitted lest the transmission
* would be suppressed immediately by the next following write --
* careful here: enabling DEBUG introduces enough latency that the
* issue doesn't show up any more. */
while (bus(dev)->TXD.AMOUNT != bus(dev)->TXD.MAXCNT &&
!bus(dev)->EVENTS_ERROR) {}
}
if (bus(dev)->EVENTS_ERROR) {
bus(dev)->EVENTS_ERROR = 0;
if (bus(dev)->ERRORSRC & TWIM_ERRORSRC_ANACK_Msk) {
bus(dev)->ERRORSRC = TWIM_ERRORSRC_ANACK_Msk;
DEBUG("[i2c] check_error: NACK on address byte\n");
return -ENXIO;
}
if (bus(dev)->ERRORSRC & TWIM_ERRORSRC_DNACK_Msk) {
bus(dev)->ERRORSRC = TWIM_ERRORSRC_DNACK_Msk;
DEBUG("[i2c] check_error: NACK on data byte\n");
return -EIO;
}
}
return 0;
}
static void _init_pins(i2c_t dev)
{
gpio_init(i2c_config[dev].scl, GPIO_IN_OD_PU);
gpio_init(i2c_config[dev].sda, GPIO_IN_OD_PU);
}
/* Beware: This needs to be kept in sync with the SPI version of this.
* Specifically, when registers are configured that are valid to the peripheral
* in both SPI and I2C mode, the register needs to be configured in both the I2C
* and the SPI variant of _setup_shared_peripheral() to avoid from parameters
* leaking from one bus into the other */
static void _setup_shared_peripheral(i2c_t dev)
{
bus(dev)->PSEL.SCL = i2c_config[dev].scl;
bus(dev)->PSEL.SDA = i2c_config[dev].sda;
bus(dev)->FREQUENCY = i2c_config[dev].speed;
}
void i2c_init(i2c_t dev)
{
assert(dev < I2C_NUMOF);
/* Initialize mutex */
mutex_init(&busy[dev]);
mutex_lock(&busy[dev]);
/* disable device during initialization, will be enabled when acquire is
* called */
bus(dev)->ENABLE = TWIM_ENABLE_ENABLE_Disabled;
/* configure pins */
_init_pins(dev);
/* configure shared periphal speed */
_setup_shared_peripheral(dev);
shared_irq_register_i2c(bus(dev), i2c_isr_handler, (void *)(uintptr_t)dev);
/* We expect that the device was being acquired before
* the i2c_init_master() function is called, so it should be enabled when
* exiting this function. */
bus(dev)->ENABLE = TWIM_ENABLE_ENABLE_Enabled;
}
#ifdef MODULE_PERIPH_I2C_RECONFIGURE
void i2c_init_pins(i2c_t dev)
{
assert(dev < I2C_NUMOF);
_init_pins(dev);
bus(dev)->ENABLE = TWIM_ENABLE_ENABLE_Enabled;
mutex_unlock(&locks[dev]);
}
void i2c_deinit_pins(i2c_t dev)
{
assert(dev < I2C_NUMOF);
mutex_lock(&locks[dev]);
bus(dev)->ENABLE = TWIM_ENABLE_ENABLE_Disabled;
}
#endif /* MODULE_PERIPH_I2C_RECONFIGURE */
void i2c_acquire(i2c_t dev)
{
assert(dev < I2C_NUMOF);
if (IS_USED(MODULE_PERIPH_I2C_RECONFIGURE)) {
mutex_lock(&locks[dev]);
}
nrf5x_i2c_acquire(bus(dev), i2c_isr_handler, (void *)(uintptr_t)dev);
_setup_shared_peripheral(dev);
bus(dev)->ENABLE = TWIM_ENABLE_ENABLE_Enabled;
DEBUG("[i2c] acquired dev %i\n", (int)dev);
}
void i2c_release(i2c_t dev)
{
assert(dev < I2C_NUMOF);
bus(dev)->ENABLE = TWIM_ENABLE_ENABLE_Disabled;
if (IS_USED(MODULE_PERIPH_I2C_RECONFIGURE)) {
mutex_unlock(&locks[dev]);
}
nrf5x_i2c_release(bus(dev));
DEBUG("[i2c] released dev %i\n", (int)dev);
}
int i2c_write_regs(i2c_t dev, uint16_t addr, uint16_t reg,
const void *data, size_t len, uint8_t flags)
{
assert((dev < I2C_NUMOF) && data && (len > 0) && (len < 253));
if (flags & (I2C_NOSTART | I2C_ADDR10)) {
return -EOPNOTSUPP;
}
/* the nrf52's TWI device does not support to do two consecutive transfers
* without a repeated start condition in between. So we have to put all data
* to be transferred into a buffer (tx_buf).
* */
uint8_t reg_addr_len; /* Length in bytes of the register address */
/* Lock tx_buf */
mutex_lock(&buffer_lock);
if (flags & (I2C_REG16)) {
reg_addr_len = 2;
/* Prepare the 16-bit register transfer */
tx_buf[0] = reg >> 8; /* AddrH in the first byte */
tx_buf[1] = reg & 0xFF; /* AddrL in the second byte */
}
else{
reg_addr_len = 1;
tx_buf[0] = reg;
}
memcpy(&tx_buf[reg_addr_len], data, len);
int ret = direct_i2c_write_bytes(dev, addr, tx_buf, reg_addr_len + len, flags);
/* Release tx_buf */
mutex_unlock(&buffer_lock);
return ret;
}
int i2c_read_bytes(i2c_t dev, uint16_t addr, void *data, size_t len,
uint8_t flags)
{
assert((dev < I2C_NUMOF) && data && (len > 0) && (len < 256));
if (flags & (I2C_NOSTART | I2C_ADDR10 | I2C_NOSTOP)) {
return -EOPNOTSUPP;
}
DEBUG("[i2c] read_bytes: %i bytes from addr 0x%02x\n", (int)len, (int)addr);
bus(dev)->ADDRESS = addr;
bus(dev)->RXD.PTR = (uint32_t)data;
bus(dev)->RXD.MAXCNT = (uint8_t)len;
int inten_success_flag;
bus(dev)->SHORTS = TWIM_SHORTS_LASTRX_STOP_Msk;
inten_success_flag = TWIM_INTEN_STOPPED_Msk;
/* Start transmission */
bus(dev)->TASKS_STARTRX = 1;
return finish(dev, inten_success_flag);
}
int i2c_read_regs(i2c_t dev, uint16_t addr, uint16_t reg,
void *data, size_t len, uint8_t flags)
{
assert((dev < I2C_NUMOF) && data && (len > 0) && (len < 256));
if (flags & (I2C_NOSTART | I2C_ADDR10 | I2C_NOSTOP)) {
return -EOPNOTSUPP;
}
DEBUG("[i2c] read_regs: %i byte(s) from reg 0x%02x at addr 0x%02x\n",
(int)len, (int)reg, (int)addr);
/* Prepare transfer */
bus(dev)->ADDRESS = addr;
if (flags & (I2C_REG16)) {
/* Register endianness for 16 bit */
reg = htons(reg);
bus(dev)->TXD.MAXCNT = 2;
}
else {
bus(dev)->TXD.MAXCNT = 1;
}
bus(dev)->TXD.PTR = (uint32_t)®
bus(dev)->RXD.PTR = (uint32_t)data;
bus(dev)->RXD.MAXCNT = (uint8_t)len;
int inten_success_flag = TWIM_INTEN_STOPPED_Msk;
bus(dev)->SHORTS = TWIM_SHORTS_LASTTX_STARTRX_Msk | TWIM_SHORTS_LASTRX_STOP_Msk;
/* Start transfer */
bus(dev)->TASKS_STARTTX = 1;
return finish(dev, inten_success_flag);
}
int i2c_write_bytes(i2c_t dev, uint16_t addr, const void *data, size_t len,
uint8_t flags)
{
if ((unsigned int)data >= CPU_RAM_BASE && (unsigned int)data < CPU_RAM_BASE + CPU_RAM_SIZE) {
return direct_i2c_write_bytes(dev, addr, data, len, flags);
}
/* These are critical for the memcpy; direct_i2c_write_bytes makes some
* more */
assert((len > 0) && (len < 256));
/* Lock tx_buf */
mutex_lock(&buffer_lock);
memcpy(tx_buf, data, len);
int result = direct_i2c_write_bytes(dev, addr, tx_buf, len, flags);
/* Release tx_buf */
mutex_unlock(&buffer_lock);
return result;
}
int direct_i2c_write_bytes(i2c_t dev, uint16_t addr, const void *data,
size_t len,
uint8_t flags)
{
assert((dev < I2C_NUMOF) && data && (len > 0) && (len < 256));
if (flags & (I2C_NOSTART | I2C_ADDR10)) {
return -EOPNOTSUPP;
}
DEBUG("[i2c] write_bytes: %i byte(s) to addr 0x%02x\n", (int)len, (int)addr);
bus(dev)->ADDRESS = addr;
bus(dev)->TXD.PTR = (uint32_t)data;
bus(dev)->TXD.MAXCNT = (uint8_t)len;
int inten_success_flag;
if (!(flags & I2C_NOSTOP)) {
bus(dev)->SHORTS = TWIM_SHORTS_LASTTX_STOP_Msk;
inten_success_flag = TWIM_INTEN_STOPPED_Msk;
}
else {
bus(dev)->SHORTS = 0;
inten_success_flag = TWIM_INTEN_LASTTX_Msk;
}
bus(dev)->TASKS_STARTTX = 1;
return finish(dev, inten_success_flag);
}
void i2c_isr_handler(void *arg)
{
i2c_t dev = (i2c_t)(uintptr_t)arg;
/* Mask interrupts to ensure that they only trigger once */
bus(dev)->INTENCLR = TWIM_INTEN_STOPPED_Msk | TWIM_INTEN_ERROR_Msk | TWIM_INTEN_LASTTX_Msk;
mutex_unlock(&busy[dev]);
}