/** * @file * * @brief EEPROM Driver Implementation * * @ingroup I2CEEPROM */ /* * Copyright (c) 2014 embedded brains GmbH. All rights reserved. * * embedded brains GmbH * Dornierstr. 4 * 82178 Puchheim * Germany * * * 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 #include #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 void eeprom_set_addr( const eeprom *dev, uint32_t off, uint8_t addr[EEPROM_MAX_ADDRESS_BYTES] ) { int shift = 24 - (4 - dev->address_bytes) * 8; addr[0] = (uint8_t) (off >> shift); shift -= 8; addr[1] = (uint8_t) (off >> shift); shift -= 8; addr[2] = (uint8_t) (off >> shift); shift -= 8; addr[3] = (uint8_t) (off >> shift); } 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]; 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; eeprom_set_addr(dev, off, addr); 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]; 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; eeprom_set_addr(dev, off, addr); 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); }