From f043b9bd3bf25626fb1a311dd7fa041eacc68adc Mon Sep 17 00:00:00 2001 From: Sebastian Huber Date: Fri, 29 Sep 2017 10:43:03 +0200 Subject: bsp/imx: Add I2C bus driver Update #3090. --- c/src/lib/libbsp/arm/imx/Makefile.am | 3 + c/src/lib/libbsp/arm/imx/i2c/imx-i2c.c | 437 +++++++++++++++++++++++++++++++++ c/src/lib/libbsp/arm/imx/include/bsp.h | 11 + 3 files changed, 451 insertions(+) create mode 100644 c/src/lib/libbsp/arm/imx/i2c/imx-i2c.c diff --git a/c/src/lib/libbsp/arm/imx/Makefile.am b/c/src/lib/libbsp/arm/imx/Makefile.am index e206614676..42352fb950 100644 --- a/c/src/lib/libbsp/arm/imx/Makefile.am +++ b/c/src/lib/libbsp/arm/imx/Makefile.am @@ -117,6 +117,9 @@ libbsp_a_SOURCES += ../shared/include/arm-cache-l1.h libbsp_a_SOURCES += ../shared/armv467ar-basic-cache/cache_.h libbsp_a_CPPFLAGS += -I$(srcdir)/../shared/armv467ar-basic-cache +# I2C +libbsp_a_SOURCES += i2c/imx-i2c.c + # Start hooks libbsp_a_SOURCES += startup/bspstarthooks.c diff --git a/c/src/lib/libbsp/arm/imx/i2c/imx-i2c.c b/c/src/lib/libbsp/arm/imx/i2c/imx-i2c.c new file mode 100644 index 0000000000..eec1a2e497 --- /dev/null +++ b/c/src/lib/libbsp/arm/imx/i2c/imx-i2c.c @@ -0,0 +1,437 @@ +/* + * Copyright (c) 2017 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. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define IMX_I2C_TRANSMIT (IMX_I2C_I2CR_IEN | IMX_I2C_I2CR_IIEN \ + | IMX_I2C_I2CR_MSTA | IMX_I2C_I2CR_MTX) + +#define IMX_I2C_RECEIVE (IMX_I2C_I2CR_IEN | IMX_I2C_I2CR_IIEN \ + | IMX_I2C_I2CR_MSTA) + +typedef struct { + i2c_bus base; + volatile imx_i2c *regs; + uint32_t msg_todo; + const i2c_msg *msg; + bool read; + bool start; + uint16_t restart; + uint32_t chunk_total; + uint32_t chunk_done; + uint16_t buf_todo; + uint8_t *buf; + rtems_id task_id; + int eno; + rtems_vector_number irq; +} imx_i2c_bus; + +typedef struct { + uint16_t divisor; + uint8_t ifdr; +} imx_i2c_clock_divisor; + +static const imx_i2c_clock_divisor imx_i2c_clock_divisor_table[] = { + { 0, 0x20 }, { 22, 0x20 }, { 24, 0x21 }, { 26, 0x22 }, + { 28, 0x23 }, { 30, 0x00 }, { 32, 0x24 }, { 36, 0x25 }, + { 40, 0x26 }, { 42, 0x03 }, { 44, 0x27 }, { 48, 0x28 }, + { 52, 0x05 }, { 56, 0x29 }, { 60, 0x06 }, { 64, 0x2a }, + { 72, 0x2b }, { 80, 0x2c }, { 88, 0x09 }, { 96, 0x2d }, + { 104, 0x0a }, { 112, 0x2e }, { 128, 0x2f }, { 144, 0x0c }, + { 160, 0x30 }, { 192, 0x31 }, { 224, 0x32 }, { 240, 0x0f }, + { 256, 0x33 }, { 288, 0x10 }, { 320, 0x34 }, { 384, 0x35 }, + { 448, 0x36 }, { 480, 0x13 }, { 512, 0x37 }, { 576, 0x14 }, + { 640, 0x38 }, { 768, 0x39 }, { 896, 0x3a }, { 960, 0x17 }, + { 1024, 0x3b }, { 1152, 0x18 }, { 1280, 0x3c }, { 1536, 0x3d }, + { 1792, 0x3e }, { 1920, 0x1b }, { 2048, 0x3f }, { 2304, 0x1c }, + { 2560, 0x1d }, { 3072, 0x1e }, { 3840, 0x1f }, { 0xffff, 0x1f } +}; + +static void imx_i2c_stop(volatile imx_i2c *regs) +{ + regs->i2cr = IMX_I2C_I2CR_IEN; + regs->i2sr = 0; + regs->i2sr; +} + +static void imx_i2c_trigger_receive(imx_i2c_bus *bus, volatile imx_i2c *regs) +{ + uint16_t i2cr; + + i2cr = IMX_I2C_RECEIVE; + + if (bus->chunk_total == 1) { + i2cr |= IMX_I2C_I2CR_TXAK; + } + + regs->i2cr = i2cr; + regs->i2dr; +} + +static void imx_i2c_done(imx_i2c_bus *bus, int eno) +{ + /* + * Generates a stop in case of transmit, otherwise, only disables interrupts + * (IMX_I2C_I2CR_MSTA is already cleared). + */ + imx_i2c_stop(bus->regs); + + bus->eno = eno; + rtems_event_transient_send(bus->task_id); +} + +static const i2c_msg *imx_i2c_msg_inc(imx_i2c_bus *bus) +{ + const i2c_msg *next; + + next = bus->msg + 1; + bus->msg = next; + --bus->msg_todo; + return next; +} + +static void imx_i2c_msg_inc_and_set_buf(imx_i2c_bus *bus) +{ + const i2c_msg *next; + + next = imx_i2c_msg_inc(bus); + bus->buf_todo = next->len; + bus->buf = next->buf; +} + +static void imx_i2c_buf_inc(imx_i2c_bus *bus) +{ + ++bus->buf; + --bus->buf_todo; + ++bus->chunk_done; +} + +static void imx_i2c_buf_push(imx_i2c_bus *bus, uint8_t c) +{ + while (true) { + if (bus->buf_todo > 0) { + bus->buf[0] = c; + imx_i2c_buf_inc(bus); + break; + } + + imx_i2c_msg_inc_and_set_buf(bus); + } +} + +static uint8_t imx_i2c_buf_pop(imx_i2c_bus *bus) +{ + while (true) { + if (bus->buf_todo > 0) { + uint8_t c; + + c = bus->buf[0]; + imx_i2c_buf_inc(bus); + return c; + } + + imx_i2c_msg_inc_and_set_buf(bus); + } +} + +RTEMS_STATIC_ASSERT(I2C_M_RD == 1, imx_i2c_read_flag); + +static void imx_i2c_setup_chunk(imx_i2c_bus *bus, volatile imx_i2c *regs) +{ + while (true) { + const i2c_msg *msg; + int flags; + int can_continue; + uint32_t i; + + if (bus->msg_todo == 0) { + imx_i2c_done(bus, 0); + break; + } + + msg = bus->msg; + flags = msg->flags; + + bus->read = (flags & I2C_M_RD) != 0; + bus->start = (flags & I2C_M_NOSTART) == 0; + bus->chunk_total = msg->len; + bus->chunk_done = 0; + bus->buf_todo = msg->len; + bus->buf = msg->buf; + + can_continue = (flags & I2C_M_RD) | I2C_M_NOSTART; + + for (i = 1; i < bus->msg_todo; ++i) { + if ((msg[i].flags & (I2C_M_RD | I2C_M_NOSTART)) != can_continue) { + break; + } + + bus->chunk_total += msg[i].len; + } + + if (bus->start) { + regs->i2cr = IMX_I2C_TRANSMIT | bus->restart; + regs->i2dr = (uint8_t) ((msg->addr << 1) | (flags & I2C_M_RD)); + bus->restart = IMX_I2C_I2CR_RSTA; + break; + } else if (bus->chunk_total > 0) { + if (bus->read) { + imx_i2c_trigger_receive(bus, regs); + } else { + regs->i2cr = IMX_I2C_TRANSMIT; + regs->i2dr = imx_i2c_buf_pop(bus); + } + + break; + } else { + ++bus->msg; + --bus->msg_todo; + } + } +} + +static void imx_i2c_transfer_complete( + imx_i2c_bus *bus, + volatile imx_i2c *regs, + uint16_t i2sr +) +{ + if (bus->start) { + bus->start = false; + + if ((i2sr & IMX_I2C_I2SR_RXAK) != 0) { + imx_i2c_done(bus, EIO); + return; + } + + if (bus->read) { + imx_i2c_trigger_receive(bus, regs); + return; + } + } + + if (bus->chunk_done < bus->chunk_total) { + if (bus->read) { + if (bus->chunk_done + 2 == bus->chunk_total) { + /* Receive second last byte with NACK */ + regs->i2cr = IMX_I2C_RECEIVE | IMX_I2C_I2CR_TXAK; + } else if (bus->chunk_done + 1 == bus->chunk_total) { + /* Receive last byte with STOP */ + bus->restart = 0; + regs->i2cr = (IMX_I2C_RECEIVE | IMX_I2C_I2CR_TXAK) + & ~IMX_I2C_I2CR_MSTA; + } + + imx_i2c_buf_push(bus, (uint8_t) regs->i2dr); + + if (bus->chunk_done == bus->chunk_total) { + imx_i2c_msg_inc(bus); + imx_i2c_setup_chunk(bus, regs); + } + } else { + if (bus->chunk_done > 0 && (i2sr & IMX_I2C_I2SR_RXAK) != 0) { + imx_i2c_done(bus, EIO); + return; + } + + regs->i2dr = imx_i2c_buf_pop(bus); + } + } else { + imx_i2c_msg_inc(bus); + imx_i2c_setup_chunk(bus, regs); + } +} + +static void imx_i2c_interrupt(void *arg) +{ + imx_i2c_bus *bus; + volatile imx_i2c *regs; + uint16_t i2sr; + + bus = arg; + regs = bus->regs; + + i2sr = regs->i2sr; + regs->i2sr = 0; + + if ((i2sr & (IMX_I2C_I2SR_IAL | IMX_I2C_I2SR_ICF)) == IMX_I2C_I2SR_ICF) { + imx_i2c_transfer_complete(bus, regs, i2sr); + } else { + imx_i2c_done(bus, EIO); + } +} + +static int imx_i2c_wait_for_not_busy(volatile imx_i2c *regs) +{ + rtems_interval timeout; + bool before; + + if ((regs->i2sr & IMX_I2C_I2SR_IBB) == 0) { + return 0; + } + + timeout = rtems_clock_tick_later(10); + + do { + before = rtems_clock_tick_before(timeout); + + if ((regs->i2sr & IMX_I2C_I2SR_IBB) == 0) { + return 0; + } + } while (before); + + return ETIMEDOUT; +} + +static int imx_i2c_transfer(i2c_bus *base, i2c_msg *msgs, uint32_t n) +{ + imx_i2c_bus *bus; + int supported_flags; + uint32_t i; + volatile imx_i2c *regs; + int eno; + rtems_status_code sc; + + supported_flags = I2C_M_RD; + + for (i = 0; i < n; ++i) { + if ((msgs[i].flags & ~supported_flags) != 0) { + return -EINVAL; + } + + supported_flags |= I2C_M_NOSTART; + } + + bus = (imx_i2c_bus *) base; + regs = bus->regs; + + eno = imx_i2c_wait_for_not_busy(regs); + if (eno != 0) { + return -eno; + } + + bus->msg_todo = n; + bus->msg = &msgs[0]; + bus->restart = 0; + bus->task_id = rtems_task_self(); + bus->eno = 0; + + regs->i2sr = 0; + imx_i2c_setup_chunk(bus, regs); + + sc = rtems_event_transient_receive(RTEMS_WAIT, bus->base.timeout); + if (sc != RTEMS_SUCCESSFUL) { + imx_i2c_stop(bus->regs); + rtems_event_transient_clear(); + return -ETIMEDOUT; + } + + return -bus->eno; +} + +static int imx_i2c_set_clock(i2c_bus *base, unsigned long clock) +{ + imx_i2c_bus *bus; + uint32_t ipg_clock; + uint16_t div; + size_t i; + const imx_i2c_clock_divisor *clock_divisor; + + bus = (imx_i2c_bus *) base; + ipg_clock = imx_ccm_ipg_hz(); + div = (uint16_t) ((ipg_clock + clock - 1) / clock); + + for (i = 0; i < RTEMS_ARRAY_SIZE(imx_i2c_clock_divisor_table); ++i) { + clock_divisor = &imx_i2c_clock_divisor_table[i]; + + if (clock_divisor->divisor >= div) { + break; + } + } + + bus->regs->ifdr = clock_divisor->ifdr; + return 0; +} + +static void imx_i2c_destroy(i2c_bus *base) +{ + imx_i2c_bus *bus; + + bus = (imx_i2c_bus *) base; + rtems_interrupt_handler_remove(bus->irq, imx_i2c_interrupt, bus); + i2c_bus_destroy_and_free(&bus->base); +} + +static int imx_i2c_init(imx_i2c_bus *bus) +{ + rtems_status_code sc; + + imx_i2c_set_clock(&bus->base, I2C_BUS_CLOCK_DEFAULT); + bus->regs->i2cr = IMX_I2C_I2CR_IEN; + + sc = rtems_interrupt_handler_install( + bus->irq, + "I2C", + RTEMS_INTERRUPT_UNIQUE, + imx_i2c_interrupt, + bus + ); + if (sc != RTEMS_SUCCESSFUL) { + return EAGAIN; + } + + return 0; +} + +int i2c_bus_register_imx(const char *bus_path, const char *alias) +{ + const void *fdt; + int node; + imx_i2c_bus *bus; + int eno; + + fdt = bsp_fdt_get(); + alias = fdt_get_alias(fdt, alias); + + if (alias == NULL) { + rtems_set_errno_and_return_minus_one(ENXIO); + } + + bus = (imx_i2c_bus *) i2c_bus_alloc_and_init(sizeof(*bus)); + if (bus == NULL){ + return -1; + } + + node = fdt_path_offset(fdt, alias); + bus->regs = imx_get_reg_of_node(fdt, node); + bus->irq = imx_get_irq_of_node(fdt, node, 0); + + eno = imx_i2c_init(bus); + if (eno != 0) { + (*bus->base.destroy)(&bus->base); + rtems_set_errno_and_return_minus_one(eno); + } + + bus->base.transfer = imx_i2c_transfer; + bus->base.set_clock = imx_i2c_set_clock; + bus->base.destroy = imx_i2c_destroy; + + return i2c_bus_register(&bus->base, bus_path); +} diff --git a/c/src/lib/libbsp/arm/imx/include/bsp.h b/c/src/lib/libbsp/arm/imx/include/bsp.h index 08b3127dba..e4debf2a14 100644 --- a/c/src/lib/libbsp/arm/imx/include/bsp.h +++ b/c/src/lib/libbsp/arm/imx/include/bsp.h @@ -49,6 +49,17 @@ rtems_vector_number imx_get_irq_of_node( size_t index ); +/** + * @brief Registers an IMX I2C bus driver. + * + * @param[in] bus_path The I2C bus driver device path, e.g. "/dev/i2c-0". + * @param[in] alias_or_path The FDT alias or path, e.g. "i2c0". + * + * @retval 0 Successful operation. + * @retval -1 An error occurred. The errno is set to indicate the error. + */ +int i2c_bus_register_imx(const char *bus_path, const char *alias_or_path); + #ifdef __cplusplus } #endif /* __cplusplus */ -- cgit v1.2.3