forked from raspberrypi/pico-examples
-
Notifications
You must be signed in to change notification settings - Fork 0
/
pio_i2c.c
147 lines (128 loc) · 5.05 KB
/
pio_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
/**
* Copyright (c) 2021 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "pio_i2c.h"
const int PIO_I2C_ICOUNT_LSB = 10;
const int PIO_I2C_FINAL_LSB = 9;
const int PIO_I2C_DATA_LSB = 1;
const int PIO_I2C_NAK_LSB = 0;
bool pio_i2c_check_error(PIO pio, uint sm) {
return pio_interrupt_get(pio, sm);
}
void pio_i2c_resume_after_error(PIO pio, uint sm) {
pio_sm_drain_tx_fifo(pio, sm);
pio_sm_exec(pio, sm, (pio->sm[sm].execctrl & PIO_SM0_EXECCTRL_WRAP_BOTTOM_BITS) >> PIO_SM0_EXECCTRL_WRAP_BOTTOM_LSB);
pio_interrupt_clear(pio, sm);
}
void pio_i2c_rx_enable(PIO pio, uint sm, bool en) {
if (en)
hw_set_bits(&pio->sm[sm].shiftctrl, PIO_SM0_SHIFTCTRL_AUTOPUSH_BITS);
else
hw_clear_bits(&pio->sm[sm].shiftctrl, PIO_SM0_SHIFTCTRL_AUTOPUSH_BITS);
}
static inline void pio_i2c_put16(PIO pio, uint sm, uint16_t data) {
while (pio_sm_is_tx_fifo_full(pio, sm))
;
// some versions of GCC dislike this
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstrict-aliasing"
*(io_rw_16 *)&pio->txf[sm] = data;
#pragma GCC diagnostic pop
}
// If I2C is ok, block and push data. Otherwise fall straight through.
void pio_i2c_put_or_err(PIO pio, uint sm, uint16_t data) {
while (pio_sm_is_tx_fifo_full(pio, sm))
if (pio_i2c_check_error(pio, sm))
return;
if (pio_i2c_check_error(pio, sm))
return;
// some versions of GCC dislike this
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstrict-aliasing"
*(io_rw_16 *)&pio->txf[sm] = data;
#pragma GCC diagnostic pop
}
uint8_t pio_i2c_get(PIO pio, uint sm) {
return (uint8_t)pio_sm_get(pio, sm);
}
void pio_i2c_start(PIO pio, uint sm) {
pio_i2c_put_or_err(pio, sm, 1u << PIO_I2C_ICOUNT_LSB); // Escape code for 2 instruction sequence
pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC1_SD0]); // We are already in idle state, just pull SDA low
pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC0_SD0]); // Also pull clock low so we can present data
}
void pio_i2c_stop(PIO pio, uint sm) {
pio_i2c_put_or_err(pio, sm, 2u << PIO_I2C_ICOUNT_LSB);
pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC0_SD0]); // SDA is unknown; pull it down
pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC1_SD0]); // Release clock
pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC1_SD1]); // Release SDA to return to idle state
};
void pio_i2c_repstart(PIO pio, uint sm) {
pio_i2c_put_or_err(pio, sm, 3u << PIO_I2C_ICOUNT_LSB);
pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC0_SD1]);
pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC1_SD1]);
pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC1_SD0]);
pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC0_SD0]);
}
static void pio_i2c_wait_idle(PIO pio, uint sm) {
// Finished when TX runs dry or SM hits an IRQ
pio->fdebug = 1u << (PIO_FDEBUG_TXSTALL_LSB + sm);
while (!(pio->fdebug & 1u << (PIO_FDEBUG_TXSTALL_LSB + sm) || pio_i2c_check_error(pio, sm)))
tight_loop_contents();
}
int pio_i2c_write_blocking(PIO pio, uint sm, uint8_t addr, uint8_t *txbuf, uint len) {
int err = 0;
pio_i2c_start(pio, sm);
pio_i2c_rx_enable(pio, sm, false);
pio_i2c_put16(pio, sm, (addr << 2) | 1u);
while (len && !pio_i2c_check_error(pio, sm)) {
if (!pio_sm_is_tx_fifo_full(pio, sm)) {
--len;
pio_i2c_put_or_err(pio, sm, (*txbuf++ << PIO_I2C_DATA_LSB) | ((len == 0) << PIO_I2C_FINAL_LSB) | 1u);
}
}
pio_i2c_stop(pio, sm);
pio_i2c_wait_idle(pio, sm);
if (pio_i2c_check_error(pio, sm)) {
err = -1;
pio_i2c_resume_after_error(pio, sm);
pio_i2c_stop(pio, sm);
}
return err;
}
int pio_i2c_read_blocking(PIO pio, uint sm, uint8_t addr, uint8_t *rxbuf, uint len) {
int err = 0;
pio_i2c_start(pio, sm);
pio_i2c_rx_enable(pio, sm, true);
while (!pio_sm_is_rx_fifo_empty(pio, sm))
(void)pio_i2c_get(pio, sm);
pio_i2c_put16(pio, sm, (addr << 2) | 3u);
uint32_t tx_remain = len; // Need to stuff 0xff bytes in to get clocks
bool first = true;
while ((tx_remain || len) && !pio_i2c_check_error(pio, sm)) {
if (tx_remain && !pio_sm_is_tx_fifo_full(pio, sm)) {
--tx_remain;
pio_i2c_put16(pio, sm, (0xffu << 1) | (tx_remain ? 0 : (1u << PIO_I2C_FINAL_LSB) | (1u << PIO_I2C_NAK_LSB)));
}
if (!pio_sm_is_rx_fifo_empty(pio, sm)) {
if (first) {
// Ignore returned address byte
(void)pio_i2c_get(pio, sm);
first = false;
}
else {
--len;
*rxbuf++ = pio_i2c_get(pio, sm);
}
}
}
pio_i2c_stop(pio, sm);
pio_i2c_wait_idle(pio, sm);
if (pio_i2c_check_error(pio, sm)) {
err = -1;
pio_i2c_resume_after_error(pio, sm);
pio_i2c_stop(pio, sm);
}
return err;
}