[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