YAHAL
Yet Another Hardware Abstraction Library
Loading...
Searching...
No Matches
i2c_rp2040.cpp
1// ---------------------------------------------
2// This file is part of
3// _ _ __ _ _ __ __
4// ( \/ ) /__\ ( )_( ) /__\ ( )
5// \ / /(__)\ ) _ ( /(__)\ )(__
6// (__)(__)(__)(_) (_)(__)(__)(____)
7//
8// Yet Another HW Abstraction Library
9// Copyright (C) Andreas Terstegge
10// BSD Licensed (see file LICENSE)
11//
12// ---------------------------------------------
13//
14
15#include "i2c_rp2040.h"
16#include "system_rp2040.h"
17#include <cassert>
18
19using namespace _IO_BANK0_;
20using namespace _RESETS_;
21
22i2c_rp2040:: i2c_rp2040(uint8_t index,
23 gpio_pin_t sda_pin,
24 gpio_pin_t scl_pin,
25 uint16_t mode)
26 : _initialized(false), _index(index), _sda(sda_pin), _scl(scl_pin), _mode(mode),
27 _restart_on_next(false) {
28
29 assert(index < 2);
30 assert((sda_pin <= 28) && (scl_pin <= 29));
31 if (index == 0) {
32 assert( (((sda_pin-0) % 4) == 0) &&
33 (((scl_pin-1) % 4) == 0) );
34 } else {
35 assert( (((sda_pin-2) % 4) == 0) &&
36 (((scl_pin-3) % 4) == 0) );
37 }
38 _i2c = (index==0) ? &I2C0 : &I2C1;
39 _i2c_set = (index==0) ? &I2C0_SET : &I2C1_SET;
40 _i2c_clr = (index==0) ? &I2C0_CLR : &I2C1_CLR;
41}
42
43i2c_rp2040::~i2c_rp2040() {
44 // Wait for pending operations
45// while (_EUSCI_STATW & EUSCI_A_STATW_BUSY);
46
47 // Reset CTLW0 register to default values
48 // (EUSCI_A is in reset state)
49// _EUSCI_CTLW0 = EUSCI_A_CTLW0_SWRST;
50}
51
52void i2c_rp2040::initialize() {
53 // Take SPI out of reset state
54 if (_index) RESETS_CLR.RESET.i2c1 = 1;
55 else RESETS_CLR.RESET.i2c0 = 1;
56 // Configure GPIO pins SDA and SCL
57 _sda.setSEL(GPIO_CTRL_FUNCSEL__i2c);
58 _scl.setSEL(GPIO_CTRL_FUNCSEL__i2c);
59
60 // Disable unit
61 _i2c->IC_ENABLE.ENABLE = 0;
62
63 // Configure IC_CON register
64 _i2c->IC_CON.MASTER_MODE = 1;
65 _i2c->IC_CON.SPEED = IC_CON_SPEED__FAST;
66 _i2c->IC_CON.IC_10BITADDR_MASTER = 0;
67 _i2c->IC_CON.IC_RESTART_EN = 1;
68 _i2c->IC_CON.IC_SLAVE_DISABLE = 1;
69 _i2c->IC_CON.TX_EMPTY_CTRL = 1;
70
71 // FIFO watermark levels
72 _i2c->IC_TX_TL = 0;
73 _i2c->IC_RX_TL = 0;
74
75 _initialized = true;
76
77 // Finally set speed and enable unit
78 setSpeed(400000);
79}
80
81int16_t i2c_rp2040::i2cRead (uint16_t addr, uint8_t *rxbuf,
82 uint16_t len, bool sendStop) {
83 if (!_initialized) initialize();
84
85 _i2c->IC_ENABLE.ENABLE = 0;
86 _i2c->IC_TAR = addr;
87 _i2c->IC_ENABLE.ENABLE = 1;
88
89 int byte_ctr;
90 for (byte_ctr = 0; byte_ctr < len; ++byte_ctr) {
91 bool first = byte_ctr == 0;
92 bool last = byte_ctr == len - 1;
93
94 while(_i2c->IC_TXFLR == 16) ;
95
96 uint32_t cmd = 1 << 8;
97 if (first && _restart_on_next) {
98 cmd |= (1 << 10);
99 }
100 if (last && sendStop) {
101 cmd |= (1 << 9);
102 }
103 _i2c->IC_DATA_CMD = cmd;
104
105 while(_i2c->IC_RXFLR == 0) ;
106
107 *rxbuf++ = _i2c->IC_DATA_CMD.DAT;
108 }
109 _restart_on_next = !sendStop;
110 return len;
111}
112
113int16_t i2c_rp2040::i2cWrite(uint16_t addr, uint8_t *txbuf,
114 uint16_t len, bool sendStop) {
115 if (!_initialized) initialize();
116
117 _i2c->IC_ENABLE.ENABLE = 0;
118 _i2c->IC_TAR = addr;
119 _i2c->IC_ENABLE.ENABLE = 1;
120
121 int byte_ctr;
122 for (byte_ctr = 0; byte_ctr < len; ++byte_ctr) {
123 bool first = byte_ctr == 0;
124 bool last = byte_ctr == len - 1;
125
126 uint32_t cmd = *txbuf++;
127 if (first && _restart_on_next) {
128 cmd |= (1 << 10);
129 }
130 if (last && sendStop) {
131 cmd |= (1 << 9);
132 }
133 _i2c->IC_DATA_CMD = cmd;
134
135 // Wait until the transmission of the address/data from the internal
136 // shift register has completed. For this to function correctly, the
137 // TX_EMPTY_CTRL flag in IC_CON must be set. The TX_EMPTY_CTRL flag
138 // was set in i2c_init.
139 while(!_i2c->IC_RAW_INTR_STAT.TX_EMPTY) ;
140 }
141 _restart_on_next = !sendStop;
142 return len;
143}
144
145void i2c_rp2040::setSpeed(uint32_t freq) {
146 if (!_initialized) initialize();
147
148 // I2C is synchronous design that runs from clk_sys
149 uint32_t freq_in = CLK_SYS;
150
151 // TODO there are some subtleties to I2C timing which we are completely ignoring here
152 uint32_t period = (freq_in + freq / 2) / freq;
153 uint32_t lcnt = period * 3 / 5; // oof this one hurts
154 uint32_t hcnt = period - lcnt;
155
156 // Per I2C-bus specification a device in standard or fast mode must
157 // internally provide a hold time of at least 300ns for the SDA signal to
158 // bridge the undefined region of the falling edge of SCL. A smaller hold
159 // time of 120ns is used for fast mode plus.
160 uint32_t sda_tx_hold_count;
161 if (freq < 1000000) {
162 // sda_tx_hold_count = freq_in [cycles/s] * 300ns * (1s / 1e9ns)
163 // Reduce 300/1e9 to 3/1e7 to avoid numbers that don't fit in uint.
164 // Add 1 to avoid division truncation.
165 sda_tx_hold_count = ((freq_in * 3) / 10000000) + 1;
166 } else {
167 // sda_tx_hold_count = freq_in [cycles/s] * 120ns * (1s / 1e9ns)
168 // Reduce 120/1e9 to 3/25e6 to avoid numbers that don't fit in uint.
169 // Add 1 to avoid division truncation.
170 sda_tx_hold_count = ((freq_in * 3) / 25000000) + 1;
171 }
172 assert(sda_tx_hold_count <= lcnt - 2);
173
174 _i2c->IC_ENABLE.ENABLE = 0;
175 // Always use "fast" mode (<= 400 kHz, works fine for standard mode too)
176 _i2c->IC_CON.SPEED = IC_CON_SPEED__FAST;
177 _i2c->IC_FS_SCL_HCNT = hcnt;
178 _i2c->IC_FS_SCL_LCNT = lcnt;
179 _i2c->IC_FS_SPKLEN = lcnt < 16 ? 1 : lcnt / 16;
180 _i2c->IC_SDA_HOLD.IC_SDA_TX_HOLD = sda_tx_hold_count;
181
182 _i2c->IC_ENABLE.ENABLE = 1;
183 return; // freq_in / period;
184}