Skip to content

Commit 8d1978e

Browse files
committed
I2CEEBlockDevice: Add paging to eight bit mode
1 parent cd34860 commit 8d1978e

File tree

2 files changed

+167
-49
lines changed

2 files changed

+167
-49
lines changed

components/storage/blockdevice/COMPONENT_I2CEE/I2CEEBlockDevice.cpp

Lines changed: 136 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -62,76 +62,96 @@ int I2CEEBlockDevice::read(void *buffer, bd_addr_t addr, bd_size_t size)
6262
// Check the address and size fit onto the chip.
6363
MBED_ASSERT(is_valid_read(addr, size));
6464

65-
_i2c->start();
65+
auto *charBuffer = reinterpret_cast<char *>(buffer);
6666

67-
if (!_i2c->write(_i2c_addr | 0)) {
68-
return BD_ERROR_DEVICE_ERROR;
69-
}
67+
auto const handler = [&](const bd_addr_t &pagedStart, const bd_size_t &pagedLength, const uint8_t &page) -> int
68+
{
69+
_i2c->start();
7070

71-
if (!_address_is_eight_bit && !_i2c->write((char)(addr >> 8))) {
72-
return BD_ERROR_DEVICE_ERROR;
73-
}
71+
auto const pagedDeviceAddress = get_paged_device_address(page);
7472

75-
if (!_i2c->write((char)(addr & 0xff))) {
76-
return BD_ERROR_DEVICE_ERROR;
77-
}
73+
if (!_i2c->write(pagedDeviceAddress)) {
74+
return BD_ERROR_DEVICE_ERROR;
75+
}
7876

79-
_i2c->stop();
77+
if (!_address_is_eight_bit && !_i2c->write((char)(pagedStart >> 8u))) {
78+
return BD_ERROR_DEVICE_ERROR;
79+
}
8080

81-
auto err = _sync();
82-
if (err) {
83-
return err;
84-
}
81+
if (!_i2c->write((char)(pagedStart & 0xffu))) {
82+
return BD_ERROR_DEVICE_ERROR;
83+
}
8584

86-
if (0 != _i2c->read(_i2c_addr, static_cast<char *>(buffer), size)) {
87-
return BD_ERROR_DEVICE_ERROR;
88-
}
85+
_i2c->stop();
8986

90-
return 0;
87+
auto err = _sync();
88+
if (err) {
89+
return err;
90+
}
91+
92+
if (0 != _i2c->read(_i2c_addr, charBuffer, pagedLength)) {
93+
return BD_ERROR_DEVICE_ERROR;
94+
}
95+
96+
charBuffer += size;
97+
98+
return BD_ERROR_OK;
99+
};
100+
101+
return do_paged(addr, size, handler);
91102
}
92103

93104
int I2CEEBlockDevice::program(const void *buffer, bd_addr_t addr, bd_size_t size)
94105
{
95106
// Check the addr and size fit onto the chip.
96107
MBED_ASSERT(is_valid_program(addr, size));
97108

98-
// While we have some more data to write.
99-
while (size > 0) {
100-
uint32_t off = addr % _block;
101-
uint32_t chunk = (off + size < _block) ? size : (_block - off);
109+
auto const *charBuffer = reinterpret_cast<char const *>(buffer);
102110

103-
_i2c->start();
111+
auto const handler = [&](const bd_addr_t &pagedStart, const bd_size_t &pagedLength, const uint8_t &page) -> int
112+
{
113+
// While we have some more data to write.
114+
while (size > 0) {
115+
uint32_t off = addr % _block;
116+
uint32_t chunk = (off + size < _block) ? size : (_block - off);
104117

105-
if (!_i2c->write(_i2c_addr | 0)) {
106-
return BD_ERROR_DEVICE_ERROR;
107-
}
118+
_i2c->start();
108119

109-
if (!_address_is_eight_bit && !_i2c->write((char)(addr >> 8))) {
110-
return BD_ERROR_DEVICE_ERROR;
111-
}
120+
auto const pagedDeviceAddress = get_paged_device_address(page);
112121

113-
if (!_i2c->write((char)(addr & 0xff))) {
114-
return BD_ERROR_DEVICE_ERROR;
115-
}
122+
if (!_i2c->write(pagedDeviceAddress)) {
123+
return BD_ERROR_DEVICE_ERROR;
124+
}
116125

117-
for (unsigned i = 0; i < chunk; i++) {
118-
_i2c->write(static_cast<const char *>(buffer)[i]);
119-
}
126+
if (!_address_is_eight_bit && !_i2c->write((char)(pagedStart >> 8u))) {
127+
return BD_ERROR_DEVICE_ERROR;
128+
}
120129

121-
_i2c->stop();
130+
if (!_i2c->write((char)(addr & 0xffu))) {
131+
return BD_ERROR_DEVICE_ERROR;
132+
}
122133

123-
int err = _sync();
134+
for (unsigned i = 0; i < chunk; i++) {
135+
_i2c->write(charBuffer[i]);
136+
}
124137

125-
if (err) {
126-
return err;
138+
_i2c->stop();
139+
140+
int err = _sync();
141+
142+
if (err) {
143+
return err;
144+
}
145+
146+
addr += chunk;
147+
size -= chunk;
148+
charBuffer += chunk;
127149
}
128150

129-
addr += chunk;
130-
size -= chunk;
131-
buffer = static_cast<const char *>(buffer) + chunk;
132-
}
151+
return BD_ERROR_OK;
152+
};
133153

134-
return 0;
154+
return do_paged(addr, size, handler);
135155
}
136156

137157
int I2CEEBlockDevice::erase(bd_addr_t addr, bd_size_t size)
@@ -180,3 +200,74 @@ const char *I2CEEBlockDevice::get_type() const
180200
{
181201
return "I2CEE";
182202
}
203+
204+
int I2CEEBlockDevice::do_paged(const bd_addr_t &startAddress,
205+
const bd_size_t &length,
206+
const paged_handler &handler)
207+
{
208+
// This helper is only used for eight bit mode.
209+
if (!this->_address_is_eight_bit) {
210+
return handler(startAddress, length, 0);
211+
}
212+
213+
auto currentStartAddress = startAddress;
214+
215+
auto const pageSize = 256;
216+
bd_size_t lengthDone = 0;
217+
while (lengthDone != length)
218+
{
219+
/* Integer division => Round down */
220+
uint8_t const currentPage = currentStartAddress / 256;
221+
bd_addr_t const nextPageBegin = (currentPage + 1) * pageSize;
222+
bd_addr_t const currentReadEndAddressExclusive = std::min(nextPageBegin, startAddress + length);
223+
bd_size_t const currentLength = currentReadEndAddressExclusive - currentStartAddress;
224+
bd_addr_t const pagedBegin = currentStartAddress - (currentPage * pageSize);
225+
226+
auto const handlerReturn = handler(pagedBegin, currentLength, currentPage);
227+
if (handlerReturn != BD_ERROR_OK)
228+
{
229+
return handlerReturn;
230+
}
231+
232+
currentStartAddress = currentReadEndAddressExclusive;
233+
lengthDone += currentLength;
234+
}
235+
236+
return BD_ERROR_OK;
237+
}
238+
239+
uint8_t I2CEEBlockDevice::get_paged_device_address(const uint8_t &page)
240+
{
241+
if (!this->_address_is_eight_bit)
242+
{
243+
return this->_i2c_addr;
244+
}
245+
else
246+
{
247+
// This method uses a dynamically created bit mask for the page given.
248+
// This ensures compatibility with all sizes of ICs.
249+
// E. g. the 512K variants have two user address bits and one page bit.
250+
// We don't want to forcefully override the two user address bits.
251+
252+
// Create a mask to cover all bits required to set page
253+
// i starts at one because the LSB is used for R/W in I2C
254+
uint8_t i = 1;
255+
uint8_t addressMask = 0;
256+
auto p = page;
257+
while (p != 0u)
258+
{
259+
addressMask |= (1u << i);
260+
p >>= 1u;
261+
i++;
262+
}
263+
264+
uint8_t pagedDeviceAddress = this->_i2c_addr & static_cast<uint8_t>(~addressMask);
265+
// Assert page < 0b111, because we don't have
266+
// more bits for page encoding
267+
// Don't actually write 0b111, this is a nonstandard extension.
268+
MBED_ASSERT(page < 0x7);
269+
pagedDeviceAddress |= static_cast<uint8_t>(page << 1u);
270+
271+
return pagedDeviceAddress;
272+
}
273+
}

components/storage/blockdevice/COMPONENT_I2CEE/I2CEEBlockDevice.h

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ class I2CEEBlockDevice : public BlockDevice {
6666
* @param block The page size of the device in bytes, defaults to 32bytes
6767
* @param freq The frequency of the I2C bus, defaults to 400K.
6868
* @param address_is_eight_bit Specifies whether the EEPROM device is using eight bit
69-
* addresses instead of 16 bit addresses. This should not be needed
70-
* unless dealing with very cheap devices.
69+
* addresses instead of 16 bit addresses. This is used for example
70+
* in AT24C series chips.
7171
*/
7272
I2CEEBlockDevice(
7373
PinName sda, PinName scl, uint8_t address,
@@ -83,8 +83,8 @@ class I2CEEBlockDevice : public BlockDevice {
8383
* @param block The page size of the device in bytes, defaults to 32bytes
8484
* @param freq The frequency of the I2C bus, defaults to 400K.
8585
* @param address_is_eight_bit Specifies whether the EEPROM device is using eight bit
86-
* addresses instead of 16 bit addresses. This should not be needed
87-
* unless dealing with very cheap devices.
86+
* addresses instead of 16 bit addresses. This is used for example
87+
* in AT24C series chips.
8888
*/
8989
I2CEEBlockDevice(
9090
mbed::I2C *i2c_obj, uint8_t address,
@@ -180,6 +180,33 @@ class I2CEEBlockDevice : public BlockDevice {
180180
bool _address_is_eight_bit;
181181

182182
int _sync();
183+
184+
using paged_handler = std::function<int(const bd_addr_t &address, const bd_size_t &length, const uint8_t &page)>;
185+
186+
/**
187+
* Executes a handler across page boundaries for eight bit mode.
188+
* When eight bit mode is disabled, the function does not do paging at all.
189+
* When eight bit mode is enabled, this function splits the requested
190+
* address space into multiple pages when needed.
191+
* This is required when a read or write must be done across multiple pages.
192+
*
193+
* @param startAddress The address to start
194+
* @param length The requested length of the operation
195+
* @param handler The handler to execute
196+
* @return Returns 0 when all calls to handler() return 0. Otherwise the
197+
* error code from the first non-zero handler() call.
198+
*/
199+
int do_paged(const bd_addr_t &startAddress, const bd_size_t &length, const paged_handler &handler);
200+
201+
/**
202+
* Gets the device's I2C address with respect to the requested page.
203+
* When eight bit mode is disabled, this function is a noop.
204+
* When eight bit mode is enabled, it sets the bits required for this bit
205+
* in the devices address. Other bits remain unchained.
206+
* @param page The requested page
207+
* @return The device's I2C address for that page
208+
*/
209+
uint8_t get_paged_device_address(const uint8_t &page);
183210
};
184211

185212

0 commit comments

Comments
 (0)