From 12dea0afa5b75ce5e2bc85f8cf4e6c5ca4a392e2 Mon Sep 17 00:00:00 2001 From: Chris Johns Date: Wed, 16 Aug 2017 14:49:58 +1000 Subject: 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(+) create mode 100644 cpukit/dev/i2c/xilinx-axi-i2c.c create mode 100644 cpukit/dev/include/dev/i2c/xilinx-axi-i2c.h (limited to 'cpukit/dev') diff --git a/cpukit/dev/Makefile.am b/cpukit/dev/Makefile.am index fbd84fd639..f8428b7f32 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 0000000000..0248958a53 --- /dev/null +++ b/cpukit/dev/i2c/xilinx-axi-i2c.c @@ -0,0 +1,943 @@ +/* + * Copyright (c) 2016-2017 Chris Johns 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 + +#include +#include +#include +#include + +#include +#include + +/* + * 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 0000000000..fafac346ec --- /dev/null +++ b/cpukit/dev/include/dev/i2c/xilinx-axi-i2c.h @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2016-2017 Chris Johns 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 + +/* + * 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 307d0a4a6e..d86719aba3 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) -- cgit v1.2.3