[rtems commit] dev/i2c: Add Xilinx AXI I2C driver.

Chris Johns chrisj at rtems.org
Sun Aug 20 01:12:46 UTC 2017


Module:    rtems
Branch:    master
Commit:    12dea0afa5b75ce5e2bc85f8cf4e6c5ca4a392e2
Changeset: http://git.rtems.org/rtems/commit/?id=12dea0afa5b75ce5e2bc85f8cf4e6c5ca4a392e2

Author:    Chris Johns <chrisj at rtems.org>
Date:      Wed Aug 16 14:49:58 2017 +1000

dev/i2c: Add Xilinx AXI I2C driver.

This is a generic driver for use with Xilinx AXI I2C controller IP.

Closes #3100.

---

 cpukit/dev/Makefile.am                      |   2 +
 cpukit/dev/i2c/xilinx-axi-i2c.c             | 943 ++++++++++++++++++++++++++++
 cpukit/dev/include/dev/i2c/xilinx-axi-i2c.h |  86 +++
 cpukit/dev/preinstall.am                    |   4 +
 4 files changed, 1035 insertions(+)

diff --git a/cpukit/dev/Makefile.am b/cpukit/dev/Makefile.am
index fbd84fd..f8428b7 100644
--- a/cpukit/dev/Makefile.am
+++ b/cpukit/dev/Makefile.am
@@ -10,6 +10,7 @@ include_dev_i2c_HEADERS += include/dev/i2c/eeprom.h
 include_dev_i2c_HEADERS += include/dev/i2c/gpio-nxp-pca9535.h
 include_dev_i2c_HEADERS += include/dev/i2c/i2c.h
 include_dev_i2c_HEADERS += include/dev/i2c/switch-nxp-pca9548a.h
+include_dev_i2c_HEADERS += include/dev/i2c/xilinx-axi-i2c.h
 
 include_dev_spidir = $(includedir)/dev/spi
 include_dev_spi_HEADERS =
@@ -36,6 +37,7 @@ libdev_a_SOURCES += i2c/gpio-nxp-pca9535.c
 libdev_a_SOURCES += i2c/i2c-bus.c
 libdev_a_SOURCES += i2c/i2c-dev.c
 libdev_a_SOURCES += i2c/switch-nxp-pca9548a.c
+libdev_a_SOURCES += i2c/xilinx-axi-i2c.c
 libdev_a_SOURCES += spi/spi-bus.c
 libdev_a_SOURCES += serial/sc16is752.c
 libdev_a_SOURCES += serial/sc16is752-spi.c
diff --git a/cpukit/dev/i2c/xilinx-axi-i2c.c b/cpukit/dev/i2c/xilinx-axi-i2c.c
new file mode 100644
index 0000000..0248958
--- /dev/null
+++ b/cpukit/dev/i2c/xilinx-axi-i2c.c
@@ -0,0 +1,943 @@
+/*
+ * Copyright (c) 2016-2017 Chris Johns <chrisj at rtems.org>  All rights reserved.
+ *
+ * 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.
+ */
+
+/*
+ * Xilinx AXI IIC Interface v2.0. See PG090.pdf.
+ *
+ * Note, only master support is provided and no dynamic mode by design.
+ */
+
+#include <stdarg.h>
+
+#include <rtems.h>
+#include <rtems/bspIo.h>
+#include <rtems/irq-extension.h>
+#include <rtems/score/assert.h>
+
+#include <dev/i2c/i2c.h>
+#include <dev/i2c/xilinx-axi-i2c.h>
+
+/*
+ * Register map.
+ */
+#define REG_GIE                    0x01c
+#define REG_ISR                    0x020
+#define REG_IER                    0x028
+#define REG_SOFTR                  0x040
+#define REG_CR                     0x100
+#define REG_SR                     0x104
+#define REG_TX_FIFO                0x108
+#define REG_RX_FIFO                0x10c
+#define REG_ADR                    0x110
+#define REG_TX_FIFO_OCY            0x114
+#define REG_RX_FIFO_OCY            0x118
+#define REG_TEN_ADR                0x11c
+#define REG_RX_FIFO_PIRQ           0x120
+#define REG_GPO                    0x124
+#define REG_TSUSTA                 0x128
+#define REG_TSUSTO                 0x12c
+#define REG_THDSTA                 0x130
+#define REG_TSUDAT                 0x134
+#define REG_TBUF                   0x138
+#define REG_THIGH                  0x13c
+#define REG_TLOW                   0x140
+#define REG_THDDAT                 0x144
+
+/*
+ * Interrupts.
+ */
+#define INT_ARB_LOST               (1 << 0)
+#define INT_TX_ERROR               (1 << 1)
+#define INT_TX_FIFO_EMPTY          (1 << 2)
+#define INT_RX_FIFO_FULL           (1 << 3)
+#define INT_BUS_NOT_BUSY           (1 << 4)
+#define INT_ADDRESSED_AS_SLAVE     (1 << 5)
+#define INT_NOT_ADDRESSED_AS_SLAVE (1 << 6)
+#define INT_TX_FIFO_HALF_FULL      (1 << 7)
+#define INT_ALL                    (0xff)
+
+/*
+ * Command register.
+ */
+#define CR_EN                      (1 << 0)
+#define CR_TX_FIFO_RESET           (1 << 1)
+#define CR_MSMS                    (1 << 2)
+#define CR_TX                      (1 << 3)
+#define CR_TXAK                    (1 << 4)
+#define CR_RSTA                    (1 << 5)
+#define CR_GC_EN                   (1 << 6)
+
+/*
+ * Status register.
+ */
+#define SR_ABGC                    (1 << 0)
+#define SR_AAS                     (1 << 1)
+#define SR_BB                      (1 << 2)
+#define SR_SRW                     (1 << 3)
+#define SR_TX_FIFO_FULL            (1 << 4)
+#define SR_RX_FIFO_FULL            (1 << 5)
+#define SR_RX_FIFO_EMPTY           (1 << 6)
+#define SR_TX_FIFO_EMPTY           (1 << 7)
+
+/*
+ * FIFO Sizes.
+ */
+#define TX_FIFO_SIZE      16
+#define TX_FIFO_HALF_SIZE (TX_FIFO_SIZE / 2)
+#define RX_FIFO_SIZE      16
+
+/*
+ * Address flags.
+ */
+#define ADDR_TEN  (1 << 31)
+#define ADDR_GPO  (1 << 30)
+
+typedef struct {
+  i2c_bus               base;
+  uint32_t              regs;
+  i2c_msg*              msgs;
+  uint32_t              msgs_remaining;
+  i2c_msg*              current_msg;
+  uint32_t              current_msg_todo;
+  uint8_t*              current_msg_byte;
+  uint32_t              current_todo;
+  uint32_t              irqstatus;
+  bool                  read;
+  uint32_t              addr;
+  rtems_id              task_id;
+  bool                  gpo_address;
+  xilinx_aix_i2c_timing timing;
+  rtems_vector_number   irq;
+} xilinx_axi_i2c_bus;
+
+xilinx_axi_i2c_bus* axi_i2c_bus;
+
+/*
+ * Real-time trace buffering with a small overhead. The data can be dumped from
+ * gdb with:
+ *
+ * define axi-i2c
+ *  set $i = 0
+ *  while $i < axi_trace_in
+ *    printf "%4d %08x %08x %08x : ", \
+ *     $i, axi_trace[$i].vars[0], axi_trace[$i].vars[1], axi_trace[$i].vars[2]
+ *    output axi_trace[$i].state
+ *    printf "\n"
+ *    set $i = $i + 1
+ *  end
+ * end
+ */
+
+typedef enum
+{
+  AXI_I2C_NOP,
+  AXI_I2C_BEGIN,
+  AXI_I2C_END,
+  AXI_I2C_TRANSFER,
+  AXI_I2C_ADDRESS,
+  AXI_I2C_START_TRANSFER,
+  AXI_I2C_WRITE,
+  AXI_I2C_READ,
+  AXI_I2C_TX_FIFO,
+  AXI_I2C_RX_FIFO,
+  AXI_I2C_RX_FIFO_LEVEL,
+  AXI_I2C_INT,
+  AXI_I2C_INT_DONE,
+  AXI_I2C_INT_ERROR,
+  AXI_I2C_BUS_NOT_BUSY,
+  AXI_I2C_REG_WRITE,
+  AXI_I2C_REG_READ,
+  AXI_I2C_TIMEOUT,
+  AXI_I2C_WAKE
+} axi_i2c_state;
+
+#define RTEMS_AXI_I2C_TRACE 0
+#if RTEMS_AXI_I2C_TRACE
+
+#define DRIVER_REG_TRACE 1
+
+typedef struct
+{
+  axi_i2c_state state;
+  uint32_t      vars[3];
+} axi_i2c_trace;
+
+#define AXI_I2C_TRACE 5000
+axi_i2c_trace axi_trace[AXI_I2C_TRACE];
+int axi_trace_in;
+
+static inline void axi_trace_reset(void)
+{
+  axi_trace_in = 0;
+}
+
+static inline void axi_trace_append(axi_i2c_state state,
+                                    uint32_t      v1,
+                                    uint32_t      v2,
+                                    uint32_t      v3)
+{
+  if (axi_trace_in < AXI_I2C_TRACE) {
+    axi_i2c_trace rec = { state, { v1, v2, v3 } };
+    axi_trace[axi_trace_in++] = rec;
+  }
+}
+#else
+#define axi_trace_reset()
+#define axi_trace_append(s, v1, v2, v3)
+#endif
+
+#define DRIVER_DEBUG 0
+#define DRIVER_DEBUG_DEFAULT true
+#if DRIVER_DEBUG
+#ifndef RTEMS_PRINTFLIKE
+#define RTEMS_PRINTFLIKE( _format_pos, _ap_pos ) \
+__attribute__((__format__(__printf__, _format_pos, _ap_pos)))
+#endif
+static bool drv_printk_enable = DRIVER_DEBUG_DEFAULT;
+static void drv_printk(const char* format, ...) RTEMS_PRINTFLIKE(1, 2);
+static void
+drv_printk(const char* format, ...)
+{
+  va_list ap;
+  va_start(ap, format);
+  if (drv_printk_enable)
+    vprintk(format, ap);
+  va_end(ap);
+}
+#else
+#define drv_printk(_fmt, ...)
+#endif
+
+static inline void
+xilinx_axi_i2c_reg_write(const xilinx_axi_i2c_bus* bus, uint32_t reg, uint32_t value)
+{
+  #if DRIVER_REG_TRACE
+    axi_trace_append(AXI_I2C_REG_WRITE, reg, value, 0);
+  #endif
+  *((volatile uint32_t*) (bus->regs + reg))= value;
+}
+
+static inline uint32_t
+xilinx_axi_i2c_reg_read(const xilinx_axi_i2c_bus* bus, uint32_t reg)
+{
+  uint32_t value = *((volatile uint32_t*) (bus->regs + reg));
+  #if DRIVER_REG_TRACE
+    axi_trace_append(AXI_I2C_REG_READ, reg, value, 0);
+  #endif
+  return value;
+}
+
+static uint32_t
+xilinx_axi_i2c_read_irq_status(const xilinx_axi_i2c_bus* bus)
+{
+  return xilinx_axi_i2c_reg_read(bus, REG_ISR);
+}
+
+static uint32_t
+xilinx_axi_i2c_read_irq_enabled(const xilinx_axi_i2c_bus* bus)
+{
+  return xilinx_axi_i2c_reg_read(bus, REG_IER);
+}
+
+static void
+xilinx_axi_i2c_clear_irq(const xilinx_axi_i2c_bus* bus, uint32_t mask)
+{
+  /*
+   * The ISR bits can be toggled so only write a 1 if set.
+   */
+  xilinx_axi_i2c_reg_write(bus, REG_ISR,
+                           xilinx_axi_i2c_reg_read(bus, REG_ISR) & mask);
+}
+
+static inline void
+xilinx_axi_i2c_enable_irq(const xilinx_axi_i2c_bus* bus, uint32_t mask)
+{
+  xilinx_axi_i2c_reg_write(bus, REG_IER,
+                           xilinx_axi_i2c_reg_read(bus, REG_IER) | mask);
+}
+
+static inline void
+xilinx_axi_i2c_disable_irq(const xilinx_axi_i2c_bus* bus, uint32_t mask)
+{
+  xilinx_axi_i2c_reg_write(bus, REG_IER,
+                           xilinx_axi_i2c_reg_read(bus, REG_IER) & ~mask);
+}
+
+static void
+xilinx_axi_i2c_clear_enable_irq(const xilinx_axi_i2c_bus* bus, uint32_t mask)
+{
+  xilinx_axi_i2c_clear_irq(bus, mask);
+  xilinx_axi_i2c_enable_irq(bus, mask);
+}
+
+static void
+xilinx_axi_i2c_disable_clear_irq(const xilinx_axi_i2c_bus* bus, uint32_t mask)
+{
+  xilinx_axi_i2c_disable_irq(bus, mask);
+  xilinx_axi_i2c_clear_irq(bus, mask);
+}
+
+static inline void
+xilinx_axi_i2c_disable_all_irq(const xilinx_axi_i2c_bus* bus)
+{
+  xilinx_axi_i2c_reg_write(bus, REG_GIE, 0);
+}
+
+static void
+xilinx_axi_i2c_enable_interrupts(const xilinx_axi_i2c_bus* bus)
+{
+  xilinx_axi_i2c_reg_write(bus, REG_GIE, 1 << 31);
+}
+
+static void
+xilinx_axi_i2c_disable_interrupts(const xilinx_axi_i2c_bus* bus)
+{
+  xilinx_axi_i2c_reg_write(bus, REG_GIE, 0);
+  xilinx_axi_i2c_reg_write(bus, REG_IER, 0);
+}
+
+static inline void
+xilinx_axi_i2c_write_cr(const xilinx_axi_i2c_bus* bus, uint32_t value)
+{
+  xilinx_axi_i2c_reg_write(bus, REG_CR, value);
+}
+
+static inline uint32_t
+xilinx_axi_i2c_read_cr(const xilinx_axi_i2c_bus* bus)
+{
+  return xilinx_axi_i2c_reg_read(bus, REG_CR);
+}
+
+static inline void
+xilinx_axi_i2c_set_cr(const xilinx_axi_i2c_bus* bus, uint32_t mask)
+{
+  xilinx_axi_i2c_reg_write(bus, REG_CR,
+                           xilinx_axi_i2c_reg_read(bus, REG_CR) | mask);
+}
+
+static inline void
+xilinx_axi_i2c_clear_cr(const xilinx_axi_i2c_bus* bus, uint32_t mask)
+{
+  xilinx_axi_i2c_write_cr(bus, xilinx_axi_i2c_read_cr(bus) & ~mask);
+}
+
+static inline uint32_t
+xilinx_axi_i2c_read_sr(const xilinx_axi_i2c_bus* bus)
+{
+  return xilinx_axi_i2c_reg_read(bus, REG_SR);
+}
+
+static inline uint32_t
+xilinx_axi_i2c_read_rx_level(const xilinx_axi_i2c_bus* bus)
+{
+  if ((xilinx_axi_i2c_read_sr(bus) & SR_RX_FIFO_EMPTY) != 0)
+    return 0;
+  return xilinx_axi_i2c_reg_read(bus, REG_RX_FIFO_OCY) + 1;
+}
+
+static inline void
+xilinx_axi_i2c_write_rx_pirq(const xilinx_axi_i2c_bus* bus, uint32_t level)
+{
+  if (level != 0)
+    xilinx_axi_i2c_reg_write(bus, REG_RX_FIFO_PIRQ, level - 1);
+  else
+    xilinx_axi_i2c_reg_write(bus, REG_RX_FIFO_PIRQ, level);
+}
+
+static inline uint32_t
+xilinx_axi_i2c_read_tx_space(const xilinx_axi_i2c_bus* bus)
+{
+  if ((xilinx_axi_i2c_read_sr(bus) & SR_TX_FIFO_EMPTY) != 0)
+    return TX_FIFO_SIZE;
+  return TX_FIFO_SIZE - xilinx_axi_i2c_reg_read(bus, REG_TX_FIFO_OCY) - 1;
+}
+
+static void
+xilinx_axi_i2c_write_tx_fifo_data(xilinx_axi_i2c_bus* bus, uint32_t data)
+{
+  axi_trace_append(AXI_I2C_TX_FIFO, data, 0, 0);
+  xilinx_axi_i2c_reg_write(bus, REG_TX_FIFO, data);
+}
+
+static inline uint32_t
+xilinx_axi_i2c_read_rx_fifo_data(xilinx_axi_i2c_bus* bus)
+{
+  uint32_t data = xilinx_axi_i2c_reg_read(bus, REG_RX_FIFO);
+  axi_trace_append(AXI_I2C_RX_FIFO, data, 0, 0);
+  return data;
+}
+
+static void
+xilinx_axi_i2c_reset(xilinx_axi_i2c_bus* bus)
+{
+  xilinx_axi_i2c_reg_write(bus, REG_SOFTR, 0x0a);
+  if ((bus->timing.valid_mask & XILINX_AIX_I2C_ALL_REGS) != 0)
+  {
+    static const uint32_t r[8] = {
+      REG_TSUSTA,
+      REG_TSUSTO,
+      REG_THDSTA,
+      REG_TSUDAT,
+      REG_TBUF,
+      REG_THIGH,
+      REG_TLOW,
+      REG_THDDAT
+    };
+    static const uint32_t m[8] = {
+      XILINX_AIX_I2C_TSUSTA,
+      XILINX_AIX_I2C_TSUSTO,
+      XILINX_AIX_I2C_THDSTA,
+      XILINX_AIX_I2C_TSUDAT,
+      XILINX_AIX_I2C_TBUF,
+      XILINX_AIX_I2C_THIGH,
+      XILINX_AIX_I2C_TLOW,
+      XILINX_AIX_I2C_THDDAT
+    };
+    uint32_t        vm = bus->timing.valid_mask;
+    const uint32_t* u = &bus->timing.TSUSTA;
+    size_t          i;
+    for (i = 0; i < (sizeof(r) / sizeof(r[0])); ++i, ++u) {
+      if ((vm & m[i]) != 0) {
+        xilinx_axi_i2c_reg_write(bus, r[i], *u);
+      }
+    }
+  }
+}
+
+
+static void
+xilinx_axi_i2c_reinit(xilinx_axi_i2c_bus* bus)
+{
+  drv_printk("axi-i2c: reinit\n");
+  xilinx_axi_i2c_reset(bus);
+  xilinx_axi_i2c_write_rx_pirq(bus, RX_FIFO_SIZE);
+  xilinx_axi_i2c_write_cr(bus, CR_TX_FIFO_RESET);
+  xilinx_axi_i2c_write_cr(bus, CR_EN);
+  xilinx_axi_i2c_clear_enable_irq(bus, INT_ARB_LOST);
+}
+
+static void
+xilinx_axi_i2c_wakeup(xilinx_axi_i2c_bus* bus)
+{
+  axi_trace_append(AXI_I2C_WAKE, bus->task_id, bus->irqstatus, 0);
+  drv_printk("axi-i2c: wakeup: irqstatus: %08lx\n", bus->irqstatus);
+  rtems_status_code sc = rtems_event_transient_send(bus->task_id);
+  _Assert(sc == RTEMS_SUCCESSFUL);
+  (void) sc;
+}
+
+static void
+xilinx_axi_i2c_next_byte(xilinx_axi_i2c_bus* bus)
+{
+  --bus->current_todo;
+  --bus->current_msg_todo;
+  ++bus->current_msg_byte;
+  if (bus->current_msg_todo == 0) {
+    if (bus->msgs_remaining != 0 &&
+        (bus->msgs[0].flags & I2C_M_NOSTART) != 0) {
+      bus->current_msg_todo = bus->msgs[0].len;
+      bus->current_msg_byte = bus->msgs[0].buf;
+      ++bus->msgs;
+      --bus->msgs_remaining;
+    }
+  }
+}
+
+static void
+xilinx_axi_i2c_read_rx_byte(xilinx_axi_i2c_bus* bus)
+{
+  *bus->current_msg_byte = (uint8_t) xilinx_axi_i2c_read_rx_fifo_data(bus);
+  xilinx_axi_i2c_next_byte(bus);
+}
+
+static void
+xilinx_axi_i2c_read_rx_bytes(xilinx_axi_i2c_bus* bus, uint32_t count)
+{
+  while (count-- > 0)
+    xilinx_axi_i2c_read_rx_byte(bus);
+}
+
+static void
+xilinx_axi_i2c_set_rx_fifo_level(xilinx_axi_i2c_bus* bus)
+{
+  uint32_t size;
+  if (bus->current_todo > RX_FIFO_SIZE) {
+    size = RX_FIFO_SIZE;
+  } else {
+    size = bus->current_todo - 1;
+  }
+  axi_trace_append(AXI_I2C_RX_FIFO_LEVEL, size, 0, 0);
+  xilinx_axi_i2c_write_rx_pirq(bus, size);
+}
+
+static bool xilinx_axi_i2c_start_transfer(xilinx_axi_i2c_bus* bus);
+
+static bool
+xilinx_axi_i2c_read_rx_fifo(xilinx_axi_i2c_bus* bus)
+{
+  drv_printk("axi-i2c: read rx fifo: length:%lu\n", bus->current_todo);
+
+  if (bus->current_todo == 0) {
+    return false;
+  }
+
+  if ((xilinx_axi_i2c_read_sr(bus) & SR_RX_FIFO_EMPTY) == 0) {
+    uint32_t level = xilinx_axi_i2c_read_rx_level(bus);
+    bool     active;
+
+    drv_printk("axi-i2c: read rx fifo: level:%lu\n", level);
+
+    if (level > bus->current_todo)
+      level = bus->current_todo;
+
+    switch (bus->current_todo - level) {
+      case 1:
+        drv_printk("axi-i2c: read rx fifo: one more\n");
+        /*
+         * One more byte to be received. This is set up by programming the RX
+         * FIFO programmable depth interrupt register with a value that is 2
+         * less than the number we need (the register is minus 1). When we have
+         * one byte left disable the TX error interrupt because setting the NO
+         * ACK bit in the command register causes a TX error interrupt. Set the
+         * TXAK bit in the CR to not-acknowledge the next byte received telling
+         * the slave sender the master accepts no more data, then read the
+         * FIFO. If the FIFO is ready before the TXAK bit is set the slave will
+         * see a request for more data. We will come back to the next case
+         * statement for the last byte once it has been received.
+         */
+        xilinx_axi_i2c_disable_clear_irq(bus, INT_TX_ERROR);
+        xilinx_axi_i2c_set_cr(bus, CR_TXAK);
+        xilinx_axi_i2c_write_rx_pirq(bus, 0);
+        xilinx_axi_i2c_read_rx_bytes(bus, level);
+        break;
+
+      case 0:
+        drv_printk("axi-i2c: read rx fifo: no more\n");
+        /*
+         * We should have 1 byte in the FIFO which is the last byte received
+         * with a NACK. If there are no more message we need to send a STOP by
+         * clearing he MSMS bit in the CR and then waiting for the bus to not
+         * be busy.
+         */
+        xilinx_axi_i2c_disable_clear_irq(bus,
+                                         INT_RX_FIFO_FULL | INT_TX_ERROR);
+        if (bus->msgs_remaining == 0) {
+          xilinx_axi_i2c_clear_cr(bus, CR_MSMS);
+          xilinx_axi_i2c_clear_enable_irq(bus, INT_BUS_NOT_BUSY);
+          active = true;
+        }
+        xilinx_axi_i2c_read_rx_byte(bus);
+        if (bus->msgs_remaining != 0)
+          active = xilinx_axi_i2c_start_transfer(bus);
+        return active;
+
+      default:
+        drv_printk("axi-i2c: read rx fifo: more:%lu\n", bus->current_todo - level);
+        /*
+         * All the requested data is in the FIFO so read it and update the PIRQ
+         * level. The PIRQ size is always one less than the maximum size.
+         */
+        xilinx_axi_i2c_read_rx_bytes(bus, level);
+        if (bus->current_todo > RX_FIFO_SIZE) {
+          xilinx_axi_i2c_write_rx_pirq(bus, RX_FIFO_SIZE);
+        } else {
+          xilinx_axi_i2c_write_rx_pirq(bus, bus->current_todo - 1);
+        }
+        break;
+    }
+  }
+
+  return true;
+}
+
+static void
+xilinx_axi_i2c_write_tx_byte(xilinx_axi_i2c_bus* bus)
+{
+  xilinx_axi_i2c_write_tx_fifo_data(bus, *bus->current_msg_byte);
+  xilinx_axi_i2c_next_byte(bus);
+}
+
+static void
+xilinx_axi_i2c_write_tx_bytes(xilinx_axi_i2c_bus* bus)
+{
+  uint32_t space = xilinx_axi_i2c_read_tx_space(bus);
+  uint32_t level = bus->current_todo - 1;
+  uint32_t i;
+  drv_printk("axi-i2c: tx fifo load: space:%lu level:%lu\n", space, level);
+  if (level < space)
+    space = level;
+  for (i = 0; i < space; ++i)
+    xilinx_axi_i2c_write_tx_byte(bus);
+}
+
+static bool
+xilinx_axi_i2c_write_tx_fifo(xilinx_axi_i2c_bus* bus)
+{
+  bool more = true;
+  drv_printk("axi-i2c: write tx fifo: current_todo: %lu\n", bus->current_todo);
+  switch (bus->current_todo) {
+    case 0:
+      xilinx_axi_i2c_disable_clear_irq(bus,
+                                       INT_TX_FIFO_EMPTY |
+                                       INT_TX_FIFO_HALF_FULL |
+                                       INT_TX_ERROR |
+                                       INT_BUS_NOT_BUSY);
+      more = xilinx_axi_i2c_start_transfer(bus);
+      break;
+    case 1:
+      /*
+       * If transmitting and the last byte issue a stop and wait for the bus to
+       * not be busy.
+       */
+      if (!bus->read && bus->msgs_remaining == 0) {
+        xilinx_axi_i2c_clear_cr(bus, CR_MSMS);
+        xilinx_axi_i2c_clear_enable_irq(bus, INT_BUS_NOT_BUSY);
+      }
+      xilinx_axi_i2c_write_tx_byte(bus);
+      break;
+    default:
+      xilinx_axi_i2c_write_tx_bytes(bus);
+      break;
+  }
+  return more;
+}
+
+static void
+xilinx_axi_i2c_write_address(xilinx_axi_i2c_bus* bus)
+{
+  if ((bus->addr & ADDR_GPO) != 0)
+    xilinx_axi_i2c_reg_write(bus, REG_GPO, (bus->addr >> 12) & 0xf);
+  if ((bus->addr & ADDR_TEN) != 0)
+    xilinx_axi_i2c_write_tx_fifo_data(bus, (bus->addr >> 8) & 0xff);
+  xilinx_axi_i2c_write_tx_fifo_data(bus, bus->addr & 0xff);
+}
+
+static void
+xilinx_axi_i2c_start_read(xilinx_axi_i2c_bus* bus)
+{
+  uint32_t cr;
+  uint32_t set = INT_RX_FIFO_FULL;
+  axi_trace_append(AXI_I2C_READ, bus->current_todo, 0, 0);
+  drv_printk("axi-i2c: start read: size: %lu\n", bus->current_todo);
+  /*
+   * Is this a restart? If there is no active STOP it is a restart.
+   */
+  cr = xilinx_axi_i2c_read_cr(bus);
+  if ((cr & CR_MSMS) != 0) {
+    cr |= CR_RSTA;
+    xilinx_axi_i2c_write_cr(bus, cr);
+  }
+  xilinx_axi_i2c_write_address(bus);
+  xilinx_axi_i2c_set_rx_fifo_level(bus);
+  /*
+   * We must NACK the last byte so if we are receiving a single byte issue a
+   * NACK.
+   */
+  cr &= ~(CR_TX | CR_TXAK);
+  if (bus->current_todo == 1) {
+    cr |= CR_TXAK;
+  } else {
+    set |= INT_TX_ERROR;
+  }
+  /*
+   * Issue a start.
+   */
+  cr |= CR_MSMS;
+  xilinx_axi_i2c_clear_enable_irq(bus, set);
+  xilinx_axi_i2c_write_cr(bus, cr);
+}
+
+static void
+xilinx_axi_i2c_start_write(xilinx_axi_i2c_bus* bus)
+{
+  uint32_t space;
+  uint32_t enable;
+  uint32_t cr;
+  axi_trace_append(AXI_I2C_WRITE, bus->current_todo, 0, 0);
+  cr = xilinx_axi_i2c_read_cr(bus);
+  /*
+   * If a master issue a restart if there is no active STOP on the bus.
+   */
+  if ((cr & CR_MSMS) != 0) {
+    cr |= CR_RSTA;
+    xilinx_axi_i2c_write_cr(bus, cr);
+  }
+  xilinx_axi_i2c_write_address(bus);
+  if (bus->current_todo > 1)
+    xilinx_axi_i2c_write_tx_bytes(bus);
+  space = xilinx_axi_i2c_read_tx_space(bus);
+  enable = INT_TX_FIFO_EMPTY | INT_TX_ERROR;
+  if (space > TX_FIFO_HALF_SIZE && bus->current_todo > 1) {
+    enable |= INT_TX_FIFO_HALF_FULL;
+  }
+  xilinx_axi_i2c_clear_enable_irq(bus, enable);
+  cr &= ~CR_TXAK;
+  cr |= CR_MSMS | CR_TX;
+  xilinx_axi_i2c_write_cr(bus, cr);
+}
+
+static bool
+xilinx_axi_i2c_start_transfer(xilinx_axi_i2c_bus* bus)
+{
+  const i2c_msg* msgs = bus->msgs;
+  uint32_t       msg_todo = bus->msgs_remaining;
+  uint32_t       i;
+
+  axi_trace_append(AXI_I2C_START_TRANSFER, msg_todo, 0, 0);
+  drv_printk("axi-i2c: start transfer: messages: %lu\n", msg_todo);
+
+  if (msg_todo == 0) {
+    xilinx_axi_i2c_clear_cr(bus, CR_MSMS);
+    return false;
+  }
+
+  /*
+   * Get the amount of data to transfer. It can span message buffers if the
+   * I2C_M_NOSTART flag is set.
+   */
+  bus->current_todo = msgs[0].len;
+  for (i = 1; i < msg_todo && (msgs[i].flags & I2C_M_NOSTART) != 0; ++i) {
+    bus->current_todo += msgs[i].len;
+  }
+
+  bus->read = (msgs->flags & I2C_M_RD) != 0;
+
+  if ((msgs->flags & I2C_M_TEN) != 0) {
+    bus->addr = (ADDR_TEN |
+                 ((msgs->addr & (3 << 8)) << 1) |
+                 ((bus->read ? 1 : 0) << 8) |
+                 (msgs->addr & 0xff));
+  }
+  else {
+    bus->addr = (msgs->addr & 0x7f) << 1 | (bus->read ? 1 : 0);
+  }
+
+  if (bus->gpo_address)
+    bus->addr |= ADDR_GPO | (msgs->addr & 0xf000);
+
+  axi_trace_append(AXI_I2C_TRANSFER,
+                   bus->msgs_remaining,
+                   bus->current_todo, bus->addr);
+
+  /*
+   * The bus->msgs is left pointing to the next message because we may need to
+   * start a new message while completing the current message.
+   */
+  bus->current_msg_todo = msgs[0].len;
+  bus->current_msg_byte = msgs[0].buf;
+  ++bus->msgs;
+  --bus->msgs_remaining;
+
+  if (bus->read) {
+    xilinx_axi_i2c_start_read(bus);
+  } else {
+    xilinx_axi_i2c_start_write(bus);
+  }
+
+  return true;
+}
+
+static void
+  xilinx_axi_i2c_interrupt(void* arg)
+{
+  xilinx_axi_i2c_bus* bus = arg;
+  uint32_t            status = xilinx_axi_i2c_read_irq_status(bus);
+  uint32_t            enabled = xilinx_axi_i2c_read_irq_enabled(bus);
+  uint32_t            active = status & enabled;
+  uint32_t            clear = 0;
+  int                 done = 0;
+
+  axi_trace_append(AXI_I2C_INT, active, status, enabled);
+
+  drv_printk("axi-i2c: interrupt: active:%02lx isr:%02lx ier:%02lx\n",
+             active, status, enabled);
+
+  /*
+   * An error or we lost arbitration. If transmitting and there is more data to
+   * send a INT_TX_ERROR means the slave issue a NOT ACK because there was not
+   * slave at the address or the addressed slave will not accept any more data.
+   *
+   * Clean up and wake the user.
+   */
+  if (((active & INT_ARB_LOST) != 0) ||
+      (!bus->read && (active & INT_TX_ERROR) != 0)) {
+    bus->irqstatus = active & (INT_ARB_LOST | INT_TX_ERROR);
+    axi_trace_append(AXI_I2C_INT_ERROR, bus->irqstatus, 0, 0);
+    xilinx_axi_i2c_reinit(bus);
+    xilinx_axi_i2c_clear_cr(bus, CR_EN);
+    xilinx_axi_i2c_wakeup(bus);
+    return;
+  }
+
+  /*
+   * RX FIFO full?
+   */
+  if ((active & INT_RX_FIFO_FULL) != 0) {
+    clear |= INT_RX_FIFO_FULL;
+
+    if (bus->read && !xilinx_axi_i2c_read_rx_fifo(bus)) {
+      ++done;
+      axi_trace_append(AXI_I2C_INT_DONE, done, clear, 0);
+    }
+
+    if (bus->current_todo == 0) {
+      clear |= status & INT_TX_ERROR;
+    }
+  }
+
+  /*
+   * TX FIFO empty or half empty?
+   */
+  if ((active & (INT_TX_FIFO_EMPTY | INT_TX_FIFO_HALF_FULL)) != 0) {
+    clear |= active & (INT_TX_FIFO_EMPTY | INT_TX_FIFO_HALF_FULL);
+
+    if (!bus->read && !xilinx_axi_i2c_write_tx_fifo(bus)) {
+      ++done;
+      axi_trace_append(AXI_I2C_INT_DONE, done, clear, 1);
+    }
+  }
+
+  /*
+   * Gate the bus not busy interrupt with the bus busy status to know the bus
+   * is really not busy. It could be an interrupt left over from starting the
+   * transmission.
+   */
+  if ((active & INT_BUS_NOT_BUSY) != 0) {
+    if ((xilinx_axi_i2c_read_sr(bus) & SR_BB) == 0) {
+      xilinx_axi_i2c_disable_clear_irq(bus, INT_BUS_NOT_BUSY);
+      if (bus->read && !xilinx_axi_i2c_read_rx_fifo(bus)) {
+        ++done;
+        axi_trace_append(AXI_I2C_BUS_NOT_BUSY, done, clear, 0);
+      }
+      else if (!bus->read && !xilinx_axi_i2c_write_tx_fifo(bus)) {
+        ++done;
+        axi_trace_append(AXI_I2C_BUS_NOT_BUSY, done, clear, 1);
+      }
+    }
+    else {
+      clear |= INT_BUS_NOT_BUSY;
+    }
+  }
+
+  if (clear != 0)
+    xilinx_axi_i2c_clear_irq(bus, clear);
+
+  if (done != 0) {
+    xilinx_axi_i2c_disable_interrupts(bus);
+    xilinx_axi_i2c_clear_cr(bus, CR_EN);
+    xilinx_axi_i2c_wakeup(bus);
+  }
+}
+
+static int xilinx_axi_i2c_transfer(i2c_bus* base,
+                                   i2c_msg* msgs,
+                                   uint32_t msg_count)
+{
+  xilinx_axi_i2c_bus* bus = (xilinx_axi_i2c_bus *) base;
+  rtems_status_code   sc;
+  int                 r = 0;
+
+  axi_trace_reset();
+  axi_trace_append(AXI_I2C_BEGIN, msg_count, 0, 0);
+
+  drv_printk("axi-i2c: i2c transfer\n");
+
+  _Assert(msg_count > 0);
+
+  bus->msgs = &msgs[0];
+  bus->msgs_remaining = msg_count;
+  bus->irqstatus = 0;
+  bus->task_id = rtems_task_self();
+
+  xilinx_axi_i2c_reinit(bus);
+  xilinx_axi_i2c_start_transfer(bus);
+  xilinx_axi_i2c_enable_interrupts(bus);
+
+  sc = rtems_event_transient_receive(RTEMS_WAIT, bus->base.timeout);
+  if (sc != RTEMS_SUCCESSFUL) {
+    axi_trace_append(AXI_I2C_TIMEOUT, 0, 0, 0);
+    xilinx_axi_i2c_reinit(bus);
+    rtems_event_transient_clear();
+    r = -ETIMEDOUT;
+  }
+
+  if (r == 0 && bus->irqstatus != 0)
+    r = -EIO;
+
+  axi_trace_append(AXI_I2C_END, bus->irqstatus, r, 0);
+
+  return r;
+}
+
+static int xilinx_axi_i2c_set_clock(i2c_bus *base, unsigned long clock)
+{
+  xilinx_axi_i2c_bus* bus = (xilinx_axi_i2c_bus*) base;
+
+  if ((bus->timing.valid_mask & XILINX_AIX_I2C_AXI_CLOCK) == 0)
+    return -EIO;
+
+  bus->timing.THIGH =
+    (bus->timing.AXI_CLOCK / (2 * clock)) - 7 - bus->timing.SCL_INERTIAL_DELAY;
+  bus->timing.TLOW = bus->timing.THIGH;
+
+  bus->timing.valid_mask |= XILINX_AIX_I2C_THIGH | XILINX_AIX_I2C_TLOW;
+
+  return 0;
+}
+
+static void xilinx_axi_i2c_destroy(i2c_bus* base)
+{
+  xilinx_axi_i2c_bus* bus = (xilinx_axi_i2c_bus*) base;
+  rtems_status_code sc;
+
+  sc = rtems_interrupt_handler_remove(bus->irq, xilinx_axi_i2c_interrupt, bus);
+  _Assert(sc == RTEMS_SUCCESSFUL);
+  (void) sc;
+
+  i2c_bus_destroy_and_free(&bus->base);
+}
+
+int
+i2c_bus_register_xilinx_aix_i2c(const char*                  bus_path,
+                                uintptr_t                    register_base,
+                                rtems_vector_number          irq,
+                                bool                         gpo_address,
+                                const xilinx_aix_i2c_timing* timing)
+{
+  xilinx_axi_i2c_bus* bus;
+  rtems_status_code   sc;
+
+  bus = (xilinx_axi_i2c_bus*) i2c_bus_alloc_and_init(sizeof(*bus));
+  if (bus == NULL) {
+    return -1;
+  }
+
+  bus->regs = register_base;
+  bus->irq = irq;
+  bus->gpo_address = gpo_address;
+  bus->timing = *timing;
+
+  sc = rtems_interrupt_handler_install(irq,
+                                       "Xilinx AXI I2C",
+                                       RTEMS_INTERRUPT_UNIQUE,
+                                       xilinx_axi_i2c_interrupt,
+                                       bus);
+  if (sc != RTEMS_SUCCESSFUL) {
+    drv_printk("axi-i2c: interrupt attach failed\n");
+    (*bus->base.destroy)(&bus->base);
+    rtems_set_errno_and_return_minus_one(EIO);
+  }
+
+  bus->base.transfer = xilinx_axi_i2c_transfer;
+  bus->base.set_clock = xilinx_axi_i2c_set_clock;
+  bus->base.destroy = xilinx_axi_i2c_destroy;
+
+  axi_i2c_bus = bus;
+
+  return i2c_bus_register(&bus->base, bus_path);
+}
diff --git a/cpukit/dev/include/dev/i2c/xilinx-axi-i2c.h b/cpukit/dev/include/dev/i2c/xilinx-axi-i2c.h
new file mode 100644
index 0000000..fafac34
--- /dev/null
+++ b/cpukit/dev/include/dev/i2c/xilinx-axi-i2c.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2016-2017 Chris Johns <chrisj at rtems.org>  All rights reserved.
+ *
+ * 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.
+ */
+
+/*
+ * Xilinx AXI IIC Interface v2.0. See PG090.pdf.
+ *
+ * Note, only master support is provided and no dynamic mode by design.
+ *
+ * The clock set up is to be handled by the IP integrator. There are too many
+ * factors handling this in software.
+ */
+
+
+#ifndef XILINX_AXI_I2C_H
+#define XILINX_AXI_I2C_H
+
+#include <dev/i2c/i2c.h>
+
+/*
+ * The PL integrator controls the timing. This interface allows software to
+ * override those settings. It pays to check the timing with ChipScope.
+ *
+ * If you set the AXI bus frequency you can use the clock speed ioctl call to
+ * change the speed dymanically. The ioctl call overrides the defaults passed
+ * in.
+ *
+ * Set the valid mask to the values that are to be set.
+ */
+#define XILINX_AIX_I2C_AXI_CLOCK (1 << 0)
+#define XILINX_AIX_I2C_TSUSTA    (1 << 1)
+#define XILINX_AIX_I2C_TSUSTO    (1 << 2)
+#define XILINX_AIX_I2C_THDSTA    (1 << 3)
+#define XILINX_AIX_I2C_TSUDAT    (1 << 4)
+#define XILINX_AIX_I2C_TBUF      (1 << 5)
+#define XILINX_AIX_I2C_THIGH     (1 << 6)
+#define XILINX_AIX_I2C_TLOW      (1 << 7)
+#define XILINX_AIX_I2C_THDDAT    (1 << 8)
+#define XILINX_AIX_I2C_ALL_REGS  (XILINX_AIX_I2C_TSUSTA | \
+                                  XILINX_AIX_I2C_TSUSTO | \
+                                  XILINX_AIX_I2C_THDSTA | \
+                                  XILINX_AIX_I2C_TSUDAT | \
+                                  XILINX_AIX_I2C_TBUF   | \
+                                  XILINX_AIX_I2C_THIGH  | \
+                                  XILINX_AIX_I2C_TLOW   | \
+                                  XILINX_AIX_I2C_THDDAT)
+typedef struct
+{
+  uint32_t valid_mask;
+  uint32_t AXI_CLOCK;
+  uint32_t SCL_INERTIAL_DELAY;
+  uint32_t TSUSTA;
+  uint32_t TSUSTO;
+  uint32_t THDSTA;
+  uint32_t TSUDAT;
+  uint32_t TBUF;
+  uint32_t THIGH;
+  uint32_t TLOW;
+  uint32_t THDDAT;
+} xilinx_aix_i2c_timing;
+
+/*
+ * Register the driver.
+ *
+ * The driver can multipex a number of I2C buses (in master mode only) using
+ * the GPO port. The PL designer can use the output pins to select a bus. This
+ * is useful if connecting a number of slave devices that have limit selectable
+ * addresses.
+ *
+ * @param bus_path The driver's device path.
+ * @param register_base AXI base address.
+ * @param irq AXI FPGA interrupt.
+ * @param gpio_address Bits 12:15 of a slave address it written to the GPO.
+ * @param timing Override the default timing. NULL means no changes.
+ */
+int i2c_bus_register_xilinx_aix_i2c(const char*                  bus_path,
+                                    uintptr_t                    register_base,
+                                    rtems_vector_number          irq,
+                                    bool                         ten_gpio,
+                                    const xilinx_aix_i2c_timing* timing);
+
+#endif
diff --git a/cpukit/dev/preinstall.am b/cpukit/dev/preinstall.am
index 307d0a4..d86719a 100644
--- a/cpukit/dev/preinstall.am
+++ b/cpukit/dev/preinstall.am
@@ -39,6 +39,10 @@ $(PROJECT_INCLUDE)/dev/i2c/switch-nxp-pca9548a.h: include/dev/i2c/switch-nxp-pca
 	$(INSTALL_DATA) $< $(PROJECT_INCLUDE)/dev/i2c/switch-nxp-pca9548a.h
 PREINSTALL_FILES += $(PROJECT_INCLUDE)/dev/i2c/switch-nxp-pca9548a.h
 
+$(PROJECT_INCLUDE)/dev/i2c/xilinx-axi-i2c.h: include/dev/i2c/xilinx-axi-i2c.h $(PROJECT_INCLUDE)/dev/i2c/$(dirstamp)
+	$(INSTALL_DATA) $< $(PROJECT_INCLUDE)/dev/i2c/xilinx-axi-i2c.h
+PREINSTALL_FILES += $(PROJECT_INCLUDE)/dev/i2c/xilinx-axi-i2c.h
+
 $(PROJECT_INCLUDE)/dev/spi/$(dirstamp):
 	@$(MKDIR_P) $(PROJECT_INCLUDE)/dev/spi
 	@: > $(PROJECT_INCLUDE)/dev/spi/$(dirstamp)



More information about the vc mailing list