Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 146 additions & 41 deletions components/storage/blockdevice/COMPONENT_I2CEE/I2CEEBlockDevice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,21 @@ using namespace mbed;

I2CEEBlockDevice::I2CEEBlockDevice(
PinName sda, PinName scl, uint8_t addr,
bd_size_t size, bd_size_t block, int freq)
: _i2c_addr(addr), _size(size), _block(block)
bd_size_t size, bd_size_t block, int freq,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't directly relate to this PR, but this driver should now really have a constructor that takes const i2c_pinmap_t & and passes to I2C to offer the possibility to avoid pulling in pinmap tables.

It would be nice to add this, but I guess as a follow-up PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think this should be a separate PR as it actually has nothing to do with the 8bit-mode.

bool address_is_eight_bit)
: _i2c_addr(addr), _size(size), _block(block),
_address_is_eight_bit(address_is_eight_bit)
{
_i2c = new (_i2c_buffer) I2C(sda, scl);
_i2c->frequency(freq);
}

I2CEEBlockDevice::I2CEEBlockDevice(
I2C *i2c_obj, uint8_t addr,
bd_size_t size, bd_size_t block)
: _i2c_addr(addr), _size(size), _block(block)
bd_size_t size, bd_size_t block,
bool address_is_eight_bit)
: _i2c_addr(addr), _size(size), _block(block),
_address_is_eight_bit(address_is_eight_bit)
{
_i2c = i2c_obj;
}
Expand All @@ -58,64 +62,99 @@ int I2CEEBlockDevice::read(void *buffer, bd_addr_t addr, bd_size_t size)
// Check the address and size fit onto the chip.
MBED_ASSERT(is_valid_read(addr, size));

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

if (!_i2c->write(_i2c_addr | 0) ||
!_i2c->write((char)(addr >> 8)) ||
!_i2c->write((char)(addr & 0xff))) {
return BD_ERROR_DEVICE_ERROR;
}
auto const handler = [&](const bd_addr_t &pagedStart, const bd_size_t &pagedLength, const uint8_t &pagedDeviceAddress) -> int {
_i2c->start();

_i2c->stop();
if (1 != _i2c->write(pagedDeviceAddress))
{
return BD_ERROR_DEVICE_ERROR;
}

auto err = _sync();
if (err) {
return err;
}
if (!_address_is_eight_bit && 1 != _i2c->write((char)(pagedStart >> 8u)))
{
return BD_ERROR_DEVICE_ERROR;
}

if (0 != _i2c->read(_i2c_addr, static_cast<char *>(buffer), size)) {
return BD_ERROR_DEVICE_ERROR;
}
if (1 != _i2c->write((char)(pagedStart & 0xffu)))
{
return BD_ERROR_DEVICE_ERROR;
}

return 0;
_i2c->stop();

auto err = _sync();
if (err)
{
return err;
}

if (0 != _i2c->read(_i2c_addr, charBuffer, pagedLength))
{
return BD_ERROR_DEVICE_ERROR;
}

charBuffer += size;

return BD_ERROR_OK;
};

return do_paged(addr, size, handler);
}

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

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

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

if (!_i2c->write(_i2c_addr | 0) ||
!_i2c->write((char)(addr >> 8)) ||
!_i2c->write((char)(addr & 0xff))) {
return BD_ERROR_DEVICE_ERROR;
}
_i2c->start();

for (unsigned i = 0; i < chunk; i++) {
_i2c->write(static_cast<const char *>(buffer)[i]);
}
if (1 != _i2c->write(pagedDeviceAddress)) {
return BD_ERROR_DEVICE_ERROR;
}

_i2c->stop();
if (!_address_is_eight_bit && 1 != _i2c->write((char)(pagedStart >> 8u))) {
return BD_ERROR_DEVICE_ERROR;
}

int err = _sync();
if (1 != _i2c->write((char)(addr & 0xffu))) {
return BD_ERROR_DEVICE_ERROR;
}

if (err) {
return err;
for (unsigned i = 0; i < chunk; i++) {
if (1 != _i2c->write(charBuffer[i])) {
return BD_ERROR_DEVICE_ERROR;
}
}

_i2c->stop();

int err = _sync();

if (err) {
return err;
}

addr += chunk;
size -= chunk;
charBuffer += chunk;
}

addr += chunk;
size -= chunk;
buffer = static_cast<const char *>(buffer) + chunk;
}
return BD_ERROR_OK;
};

return 0;
auto const originalSize = size;
return do_paged(addr, originalSize, handler);
}

int I2CEEBlockDevice::erase(bd_addr_t addr, bd_size_t size)
Expand Down Expand Up @@ -164,3 +203,69 @@ const char *I2CEEBlockDevice::get_type() const
{
return "I2CEE";
}

int I2CEEBlockDevice::do_paged(const bd_addr_t &startAddress,
const bd_size_t &length,
const paged_handler &handler)
{
// This helper is only used for eight bit mode.
if (!this->_address_is_eight_bit) {
return handler(startAddress, length, get_paged_device_address(0));
}

auto currentStartAddress = startAddress;

auto const pageSize = 256;
bd_size_t lengthDone = 0;
while (lengthDone != length) {
/* Integer division => Round down */
uint8_t const currentPage = currentStartAddress / pageSize;
bd_addr_t const nextPageBegin = (currentPage + 1) * pageSize;
bd_addr_t const currentReadEndAddressExclusive = std::min(nextPageBegin, startAddress + length);
bd_size_t const currentLength = currentReadEndAddressExclusive - currentStartAddress;
bd_addr_t const pagedBegin = currentStartAddress - (currentPage * pageSize);
uint8_t const pagedDeviceAddress = get_paged_device_address(currentPage);

auto const handlerReturn = handler(pagedBegin, currentLength, pagedDeviceAddress);
if (handlerReturn != BD_ERROR_OK) {
return handlerReturn;
}

currentStartAddress = currentReadEndAddressExclusive;
lengthDone += currentLength;
}

return BD_ERROR_OK;
}

Copy link
Contributor

@kjbracey kjbracey Feb 19, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Think you've gone overboard and now have an excess blank line :)

(Any Git diff should show both cases in red normally).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, I didn't know where you wanted the NL.
Where exactly did you want the NL?
Thanks.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's possible I'm being misled by GitHub's view, but I think originally you had an unterminated final } line. I think you went to the bottom and pressed "Enter" twice rather than once, so you now have an excess blank line.

uint8_t I2CEEBlockDevice::get_paged_device_address(const uint8_t &page)
{
if (!this->_address_is_eight_bit) {
return this->_i2c_addr;
} else {
// This method uses a dynamically created bit mask for the page given.
// This ensures compatibility with all sizes of ICs.
// E. g. the 512K variants have two user address bits and one page bit.
// We don't want to forcefully override the two user address bits.

// Create a mask to cover all bits required to set page
// i starts at one because the LSB is used for R/W in I2C
uint8_t i = 1;
uint8_t addressMask = 0;
auto p = page;
while (p != 0u) {
addressMask |= (1u << i);
p >>= 1u;
i++;
}

uint8_t pagedDeviceAddress = this->_i2c_addr & static_cast<uint8_t>(~addressMask);
// Assert page < 0b111, because we don't have
// more bits for page encoding
// Don't actually write 0b111, this is a nonstandard extension.
MBED_ASSERT(page < 0x7);
pagedDeviceAddress |= static_cast<uint8_t>(page << 1u);

return pagedDeviceAddress;
}
}
67 changes: 52 additions & 15 deletions components/storage/blockdevice/COMPONENT_I2CEE/I2CEEBlockDevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,29 +59,37 @@ class I2CEEBlockDevice : public BlockDevice {
public:
/** Constructor to create an I2CEEBlockDevice on I2C pins
*
* @param sda The pin name for the sda line of the I2C bus.
* @param scl The pin name for the scl line of the I2C bus.
* @param addr The 8bit I2C address of the chip, common range 0xa0 - 0xae.
* @param size The size of the device in bytes
* @param block The page size of the device in bytes, defaults to 32bytes
* @param freq The frequency of the I2C bus, defaults to 400K.
* @param sda The pin name for the sda line of the I2C bus.
* @param scl The pin name for the scl line of the I2C bus.
* @param addr The 8bit I2C address of the chip, common range 0xa0 - 0xae.
* @param size The size of the device in bytes
* @param block The page size of the device in bytes, defaults to 32bytes
* @param freq The frequency of the I2C bus, defaults to 400K.
* @param address_is_eight_bit Specifies whether the EEPROM device is using eight bit
* addresses instead of 16 bit addresses. This is used for example
* in AT24C series chips.
*/
I2CEEBlockDevice(
PinName sda, PinName scl, uint8_t address,
bd_size_t size, bd_size_t block = 32,
int bus_speed = 400000);
int bus_speed = 400000,
bool address_is_eight_bit = false);

/** Constructor to create an I2CEEBlockDevice on I2C pins
*
* @param i2c The I2C instance pointer
* @param addr The 8bit I2C address of the chip, common range 0xa0 - 0xae.
* @param size The size of the device in bytes
* @param block The page size of the device in bytes, defaults to 32bytes
* @param freq The frequency of the I2C bus, defaults to 400K.
*/
*
* @param i2c The I2C instance pointer
* @param addr The 8bit I2C address of the chip, common range 0xa0 - 0xae.
* @param size The size of the device in bytes
* @param block The page size of the device in bytes, defaults to 32bytes
* @param freq The frequency of the I2C bus, defaults to 400K.
* @param address_is_eight_bit Specifies whether the EEPROM device is using eight bit
* addresses instead of 16 bit addresses. This is used for example
* in AT24C series chips.
*/
I2CEEBlockDevice(
mbed::I2C *i2c_obj, uint8_t address,
bd_size_t size, bd_size_t block = 32);
bd_size_t size, bd_size_t block = 32,
bool address_is_eight_bit = false);

/** Destructor of I2CEEBlockDevice
*/
Expand Down Expand Up @@ -169,7 +177,36 @@ class I2CEEBlockDevice : public BlockDevice {
uint32_t _size;
uint32_t _block;

bool _address_is_eight_bit;

int _sync();

using paged_handler = std::function<int(const bd_addr_t &address, const bd_size_t &length, const uint8_t &deviceAddress)>;

/**
* Executes a handler across page boundaries for eight bit mode.
* When eight bit mode is disabled, the function does not do paging at all.
* When eight bit mode is enabled, this function splits the requested
* address space into multiple pages when needed.
* This is required when a read or write must be done across multiple pages.
*
* @param startAddress The address to start
* @param length The requested length of the operation
* @param handler The handler to execute
* @return Returns 0 when all calls to handler() return 0. Otherwise the
* error code from the first non-zero handler() call.
*/
int do_paged(const bd_addr_t &startAddress, const bd_size_t &length, const paged_handler &handler);

/**
* Gets the device's I2C address with respect to the requested page.
* When eight bit mode is disabled, this function is a noop.
* When eight bit mode is enabled, it sets the bits required for this bit
* in the devices address. Other bits remain unchained.
* @param page The requested page
* @return The device's I2C address for that page
*/
uint8_t get_paged_device_address(const uint8_t &page);
};


Expand Down