[PATCH 3/9] Add generic EEPROM I2C device driver
Sebastian Huber
sebastian.huber at embedded-brains.de
Thu Nov 13 09:30:21 UTC 2014
---
cpukit/dev/Makefile.am | 2 +
cpukit/dev/i2c/eeprom.c | 264 ++++++++++++++++++++++++++++++++++++
cpukit/dev/include/dev/i2c/eeprom.h | 58 ++++++++
cpukit/dev/preinstall.am | 4 +
testsuites/libtests/i2c01/init.c | 142 ++++++++++++++++++-
5 files changed, 469 insertions(+), 1 deletion(-)
create mode 100644 cpukit/dev/i2c/eeprom.c
create mode 100644 cpukit/dev/include/dev/i2c/eeprom.h
diff --git a/cpukit/dev/Makefile.am b/cpukit/dev/Makefile.am
index c2913a6..f782da2 100644
--- a/cpukit/dev/Makefile.am
+++ b/cpukit/dev/Makefile.am
@@ -6,6 +6,7 @@ include_dev_HEADERS =
include_dev_i2cdir = $(includedir)/dev/i2c
include_dev_i2c_HEADERS =
+include_dev_i2c_HEADERS += include/dev/i2c/eeprom.h
include_dev_i2c_HEADERS += include/dev/i2c/i2c.h
include_linuxdir = $(includedir)/linux
@@ -16,6 +17,7 @@ include_linux_HEADERS += include/linux/i2c-dev.h
noinst_LIBRARIES = libdev.a
libdev_a_SOURCES =
+libdev_a_SOURCES += i2c/eeprom.c
libdev_a_SOURCES += i2c/i2c-bus.c
libdev_a_SOURCES += i2c/i2c-dev.c
diff --git a/cpukit/dev/i2c/eeprom.c b/cpukit/dev/i2c/eeprom.c
new file mode 100644
index 0000000..fe9b0d1
--- /dev/null
+++ b/cpukit/dev/i2c/eeprom.c
@@ -0,0 +1,264 @@
+/**
+ * @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 at 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) {
+ errno = ERANGE;
+
+ return -1;
+ }
+
+ if (page_size_in_bytes > EEPROM_MAX_PAGE_SIZE) {
+ page_size_in_bytes = EEPROM_MAX_PAGE_SIZE;
+ }
+
+ extra_address = size_in_bytes >> (8 * address_bytes);
+ if (extra_address != 0 && (extra_address & (extra_address - 1)) != 0) {
+ errno = EINVAL;
+
+ return -1;
+ }
+
+ 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);
+}
diff --git a/cpukit/dev/include/dev/i2c/eeprom.h b/cpukit/dev/include/dev/i2c/eeprom.h
new file mode 100644
index 0000000..73df5ad
--- /dev/null
+++ b/cpukit/dev/include/dev/i2c/eeprom.h
@@ -0,0 +1,58 @@
+/**
+ * @file
+ *
+ * @brief EEPROM Driver API
+ *
+ * @ingroup I2CEEPROM
+ */
+
+/*
+ * Copyright (c) 2014 embedded brains GmbH. All rights reserved.
+ *
+ * embedded brains GmbH
+ * Dornierstr. 4
+ * 82178 Puchheim
+ * Germany
+ * <rtems at 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.
+ */
+
+#ifndef _DEV_I2C_EEPROM_H
+#define _DEV_I2C_EEPROM_H
+
+#include <dev/i2c/i2c.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/**
+ * @defgroup I2CEEPROM EEPROM Driver
+ *
+ * @ingroup I2CDevice
+ *
+ * @brief Driver for EEPROM device.
+ *
+ * @{
+ */
+
+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
+);
+
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* _DEV_I2C_EEPROM_H */
diff --git a/cpukit/dev/preinstall.am b/cpukit/dev/preinstall.am
index 8239327..4472b81 100644
--- a/cpukit/dev/preinstall.am
+++ b/cpukit/dev/preinstall.am
@@ -23,6 +23,10 @@ $(PROJECT_INCLUDE)/dev/i2c/$(dirstamp):
@: > $(PROJECT_INCLUDE)/dev/i2c/$(dirstamp)
PREINSTALL_DIRS += $(PROJECT_INCLUDE)/dev/i2c/$(dirstamp)
+$(PROJECT_INCLUDE)/dev/i2c/eeprom.h: include/dev/i2c/eeprom.h $(PROJECT_INCLUDE)/dev/i2c/$(dirstamp)
+ $(INSTALL_DATA) $< $(PROJECT_INCLUDE)/dev/i2c/eeprom.h
+PREINSTALL_FILES += $(PROJECT_INCLUDE)/dev/i2c/eeprom.h
+
$(PROJECT_INCLUDE)/dev/i2c/i2c.h: include/dev/i2c/i2c.h $(PROJECT_INCLUDE)/dev/i2c/$(dirstamp)
$(INSTALL_DATA) $< $(PROJECT_INCLUDE)/dev/i2c/i2c.h
PREINSTALL_FILES += $(PROJECT_INCLUDE)/dev/i2c/i2c.h
diff --git a/testsuites/libtests/i2c01/init.c b/testsuites/libtests/i2c01/init.c
index 7f38d34..b0024e4 100644
--- a/testsuites/libtests/i2c01/init.c
+++ b/testsuites/libtests/i2c01/init.c
@@ -17,6 +17,7 @@
#endif
#include <dev/i2c/i2c.h>
+#include <dev/i2c/eeprom.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
@@ -36,6 +37,10 @@ const char rtems_test_name[] = "I2C 1";
#define DEVICE_SIMPLE_READ_WRITE (0UL << SPARE_ADDRESS_BITS)
+#define DEVICE_EEPROM (1UL << SPARE_ADDRESS_BITS)
+
+#define EEPROM_SIZE 512
+
typedef struct test_device test_device;
struct test_device {
@@ -53,14 +58,31 @@ typedef struct {
} test_device_simple_read_write;
typedef struct {
+ test_device base;
+ unsigned current_address;
+ uint8_t data[EEPROM_SIZE];
+} test_device_eeprom;
+
+typedef struct {
i2c_bus base;
unsigned long clock;
- test_device *devices[1];
+ test_device *devices[2];
test_device_simple_read_write simple_read_write;
+ test_device_eeprom eeprom;
} test_bus;
static const char bus_path[] = "/dev/i2c-0";
+static const char eeprom_path[] = "/dev/i2c-0.eeprom-0";
+
+static void cyclic_inc(unsigned *val, unsigned cycle)
+{
+ unsigned v = *val;
+ unsigned m = cycle - 1U;
+
+ *val = (v & ~m) + ((v + 1U) & m);
+}
+
static int test_simple_read_write_transfer(
i2c_bus *bus,
i2c_msg *msgs,
@@ -83,6 +105,48 @@ static int test_simple_read_write_transfer(
}
}
+static int test_eeprom_transfer(
+ i2c_bus *bus,
+ i2c_msg *msgs,
+ uint32_t msg_count,
+ test_device *base
+)
+{
+ test_device_eeprom *dev = (test_device_eeprom *) base;
+ i2c_msg *msg = &msgs[0];
+ uint32_t i;
+
+ if (msg_count > 0 && (msg->flags & I2C_M_RD) == 0) {
+ if (msg->len < 1) {
+ return -EIO;
+ }
+
+ dev->current_address = msg->buf[0] | ((msg->addr & 0x1) << 8);
+ --msg->len;
+ ++msg->buf;
+ }
+
+ for (i = 0; i < msg_count; ++i) {
+ int j;
+
+ msg = &msgs[i];
+
+ if ((msg->flags & I2C_M_RD) != 0) {
+ for (j = 0; j < msg->len; ++j) {
+ msg->buf[j] = dev->data[dev->current_address];
+ cyclic_inc(&dev->current_address, sizeof(dev->data));
+ }
+ } else {
+ for (j = 0; j < msg->len; ++j) {
+ dev->data[dev->current_address] = msg->buf[j];
+ cyclic_inc(&dev->current_address, 8);
+ }
+ }
+ }
+
+ return 0;
+}
+
static int test_transfer(i2c_bus *base, i2c_msg *msgs, uint32_t msg_count)
{
test_bus *bus = (test_bus *) base;
@@ -157,6 +221,78 @@ static void test_simple_read_write(test_bus *bus, int fd)
rtems_test_assert(memcmp(&buf[0], &abc[0], sizeof(buf)) == 0);
}
+static void test_eeprom(void)
+{
+ int rv;
+ int fd_in;
+ int fd_out;
+ struct stat st;
+ uint8_t in[EEPROM_SIZE];
+ uint8_t out[EEPROM_SIZE];
+ ssize_t n;
+ off_t off;
+ size_t i;
+
+ rv = i2c_dev_register_eeprom(
+ &bus_path[0],
+ &eeprom_path[0],
+ DEVICE_EEPROM,
+ 1,
+ 8,
+ sizeof(out),
+ 0
+ );
+ rtems_test_assert(rv == 0);
+
+ fd_in = open(&eeprom_path[0], O_RDWR);
+ rtems_test_assert(fd_in >= 0);
+
+ fd_out = open(&eeprom_path[0], O_RDWR);
+ rtems_test_assert(fd_out >= 0);
+
+ rv = fstat(fd_in, &st);
+ rtems_test_assert(rv == 0);
+ rtems_test_assert(st.st_blksize == 8);
+ rtems_test_assert(st.st_size == sizeof(out));
+
+ memset(&out[0], 0, sizeof(out));
+
+ n = read(fd_in, &in[0], sizeof(in) + 1);
+ rtems_test_assert(n == (ssize_t) sizeof(in));
+
+ rtems_test_assert(memcmp(&in[0], &out[0], sizeof(in)) == 0);
+
+ off = lseek(fd_in, 0, SEEK_CUR);
+ rtems_test_assert(off == sizeof(out));
+
+ for (i = 0; i < sizeof(out); ++i) {
+ off = lseek(fd_out, 0, SEEK_CUR);
+ rtems_test_assert(off == i);
+
+ out[i] = (uint8_t) i;
+
+ n = write(fd_out, &out[i], sizeof(out[i]));
+ rtems_test_assert(n == (ssize_t) sizeof(out[i]));
+
+ off = lseek(fd_in, 0, SEEK_SET);
+ rtems_test_assert(off == 0);
+
+ n = read(fd_in, &in[0], sizeof(in));
+ rtems_test_assert(n == (ssize_t) sizeof(in));
+
+ rtems_test_assert(memcmp(&in[0], &out[0], sizeof(in)) == 0);
+ }
+
+ rv = close(fd_in);
+ rtems_test_assert(rv == 0);
+
+ rv = close(fd_out);
+ rtems_test_assert(rv == 0);
+
+ rv = unlink(&eeprom_path[0]);
+ rtems_test_assert(rv == 0);
+}
+
static void test(void)
{
rtems_resource_snapshot snapshot;
@@ -178,6 +314,9 @@ static void test(void)
bus->simple_read_write.base.transfer = test_simple_read_write_transfer;
bus->devices[0] = &bus->simple_read_write.base;
+ bus->eeprom.base.transfer = test_eeprom_transfer;
+ bus->devices[1] = &bus->eeprom.base;
+
rv = i2c_bus_register(&bus->base, &bus_path[0]);
rtems_test_assert(rv == 0);
@@ -244,6 +383,7 @@ static void test(void)
rtems_test_assert(bus->base.timeout == 0);
test_simple_read_write(bus, fd);
+ test_eeprom();
rv = close(fd);
rtems_test_assert(rv == 0);
--
1.8.4.5
More information about the devel
mailing list