/**
* @file
*
* @brief EEPROM Driver Implementation
*
* @ingroup I2CEEPROM
*/
/*
* Copyright (c) 2014 embedded brains GmbH. All rights reserved.
*
* embedded brains GmbH
* Dornierstr. 4
* 82178 Puchheim
* Germany
* <rtems@embedded-brains.de>
*
* The license and distribution terms for this file may be
* found in the file LICENSE in this distribution or at
* http://www.rtems.org/license/LICENSE.
*/
#if HAVE_CONFIG_H
#include "config.h"
#endif
#include <dev/i2c/eeprom.h>
#include <string.h>
#define EEPROM_MAX_ADDRESS_BYTES 4
#define EEPROM_MAX_PAGE_SIZE 128
typedef struct {
i2c_dev base;
uint16_t address_bytes;
uint16_t page_size;
uint32_t size;
uint16_t i2c_address_mask;
uint16_t i2c_address_shift;
rtems_interval program_timeout;
} eeprom;
static uint16_t eeprom_i2c_addr(eeprom *dev, uint32_t off)
{
return dev->base.address
| ((off >> dev->i2c_address_shift) & dev->i2c_address_mask);
}
static ssize_t eeprom_read(
i2c_dev *base,
void *buf,
size_t n,
off_t offset
)
{
eeprom *dev = (eeprom *) base;
off_t avail = dev->size - offset;
uint32_t off = (uint32_t) offset;
uint8_t *in = buf;
size_t todo;
if (avail <= 0) {
return 0;
}
if (n > avail) {
n = (size_t) avail;
}
todo = n;
while (todo > 0) {
uint16_t i2c_addr = eeprom_i2c_addr(dev, off);
/*
* Limit the transfer size so that it can be stored in 8-bits. This may
* help some bus controllers.
*/
uint16_t cur = (uint16_t) (todo < 255 ? todo : 255);
uint8_t addr[EEPROM_MAX_ADDRESS_BYTES] = {
(uint8_t) off,
(uint8_t) (off >> 8),
(uint8_t) (off >> 16),
(uint8_t) (off >> 24)
};
i2c_msg msgs[2] = {
{
.addr = i2c_addr,
.flags = 0,
.len = dev->address_bytes,
.buf = &addr[0]
}, {
.addr = i2c_addr,
.flags = I2C_M_RD,
.buf = in,
.len = cur
}
};
int err;
err = i2c_bus_transfer(dev->base.bus, &msgs[0], RTEMS_ARRAY_SIZE(msgs));
if (err != 0) {
return err;
}
todo -= cur;
off += cur;
in += cur;
}
return (ssize_t) n;
}
static ssize_t eeprom_write(
i2c_dev *base,
const void *buf,
size_t n,
off_t offset
)
{
eeprom *dev = (eeprom *) base;
off_t avail = dev->size - offset;
uint32_t off = (uint32_t) offset;
const uint8_t *out = buf;
size_t todo;
if (avail <= 0) {
return 0;
}
if (n > avail) {
n = (size_t) avail;
}
todo = n;
while (todo > 0) {
uint16_t i2c_addr = eeprom_i2c_addr(dev, off);
uint16_t rem = dev->page_size - (off & (dev->page_size - 1));
uint16_t cur = (uint16_t) (todo < rem ? todo : rem);
uint8_t addr[EEPROM_MAX_ADDRESS_BYTES] = {
(uint8_t) off,
(uint8_t) (off >> 8),
(uint8_t) (off >> 16),
(uint8_t) (off >> 24)
};
i2c_msg msgs[2] = {
{
.addr = i2c_addr,
.flags = 0,
.len = dev->address_bytes,
.buf = &addr[0]
}, {
.addr = i2c_addr,
.flags = I2C_M_NOSTART,
.buf = RTEMS_DECONST(uint8_t *, out),
.len = cur
}
};
uint8_t in[EEPROM_MAX_PAGE_SIZE];
int err;
ssize_t m;
rtems_interval timeout;
err = i2c_bus_transfer(dev->base.bus, &msgs[0], RTEMS_ARRAY_SIZE(msgs));
if (err != 0) {
return err;
}
timeout = rtems_clock_tick_later(dev->program_timeout);
do {
m = eeprom_read(&dev->base, &in[0], cur, off);
} while (m != cur && rtems_clock_tick_before(timeout));
if (m != cur) {
return -ETIMEDOUT;
}
if (memcmp(&in[0], &out[0], cur) != 0) {
return -EIO;
}
todo -= cur;
off += cur;
out += cur;
}
return (ssize_t) n;
}
static off_t eeprom_get_size(i2c_dev *base)
{
eeprom *dev = (eeprom *) base;
return dev->size;
}
static blksize_t eeprom_get_block_size(i2c_dev *base)
{
eeprom *dev = (eeprom *) base;
return dev->page_size;
}
int i2c_dev_register_eeprom(
const char *bus_path,
const char *dev_path,
uint16_t i2c_address,
uint16_t address_bytes,
uint16_t page_size_in_bytes,
uint32_t size_in_bytes,
uint32_t program_timeout_in_ms
)
{
uint32_t extra_address;
eeprom *dev;
if (address_bytes > EEPROM_MAX_ADDRESS_BYTES) {
rtems_set_errno_and_return_minus_one(ERANGE);
} else if (address_bytes == EEPROM_MAX_ADDRESS_BYTES) {
extra_address = 0;
} else {
extra_address = size_in_bytes >> (8 * address_bytes);
}
if (extra_address != 0 && (extra_address & (extra_address - 1)) != 0) {
rtems_set_errno_and_return_minus_one(EINVAL);
}
if (page_size_in_bytes > EEPROM_MAX_PAGE_SIZE) {
page_size_in_bytes = EEPROM_MAX_PAGE_SIZE;
}
if (program_timeout_in_ms == 0) {
program_timeout_in_ms = 1000;
}
dev = (eeprom *)
i2c_dev_alloc_and_init(sizeof(*dev), bus_path, i2c_address);
if (dev == NULL) {
return -1;
}
dev->base.read = eeprom_read;
dev->base.write = eeprom_write;
dev->base.get_size = eeprom_get_size;
dev->base.get_block_size = eeprom_get_block_size;
dev->address_bytes = address_bytes;
dev->page_size = page_size_in_bytes;
dev->size = size_in_bytes;
dev->program_timeout = RTEMS_MILLISECONDS_TO_TICKS(program_timeout_in_ms);
if (extra_address != 0) {
dev->i2c_address_mask = extra_address - 1;
dev->i2c_address_shift = (uint16_t) (8 * address_bytes);
}
return i2c_dev_register(&dev->base, dev_path);
}