summaryrefslogtreecommitdiffstats
path: root/bsps/arm/imxrt/i2c
diff options
context:
space:
mode:
Diffstat (limited to 'bsps/arm/imxrt/i2c')
-rw-r--r--bsps/arm/imxrt/i2c/imxrt-lpi2c.c489
1 files changed, 489 insertions, 0 deletions
diff --git a/bsps/arm/imxrt/i2c/imxrt-lpi2c.c b/bsps/arm/imxrt/i2c/imxrt-lpi2c.c
new file mode 100644
index 0000000000..783c6e18e6
--- /dev/null
+++ b/bsps/arm/imxrt/i2c/imxrt-lpi2c.c
@@ -0,0 +1,489 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+
+/*
+ * Copyright (C) 2020 embedded brains GmbH (http://www.embedded-brains.de)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <bsp.h>
+#include <bsp/fatal.h>
+#include <bsp/fdt.h>
+#include <bsp/irq.h>
+
+#include <chip.h>
+#include <dev/i2c/i2c.h>
+#include <fsl_clock.h>
+#include <fsl_lpi2c.h>
+#include <libfdt.h>
+
+#define LPI2C_MTDR_CMD_transmit LPI2C_MTDR_CMD(0)
+#define LPI2C_MTDR_CMD_receive LPI2C_MTDR_CMD(1)
+#define LPI2C_MTDR_CMD_stop LPI2C_MTDR_CMD(2)
+#define LPI2C_MTDR_CMD_receive_and_discard LPI2C_MTDR_CMD(3)
+#define LPI2C_MTDR_CMD_start_and_transmit LPI2C_MTDR_CMD(4)
+#define LPI2C_MTDR_CMD_start_and_transmit_NACK LPI2C_MTDR_CMD(5)
+#define LPI2C_MTDR_CMD_start_and_transmit_highspeed LPI2C_MTDR_CMD(6)
+#define LPI2C_MTDR_CMD_start_and_transmit_highspeed_NACK LPI2C_MTDR_CMD(7)
+
+#define LPI2C_INT_ERRORS_SERIOUS ( \
+ LPI2C_MSR_FEF_MASK | LPI2C_MSR_ALF_MASK | LPI2C_MSR_PLTF_MASK )
+
+#define LPI2C_INT_ERROR_NO_ACK (LPI2C_MSR_NDF_MASK)
+
+#define LPI2C_INT_ERRORS (LPI2C_INT_ERRORS_SERIOUS | LPI2C_INT_ERROR_NO_ACK)
+
+#define LPI2C_INT_ADDRESSED (LPI2C_INT_ERRORS | LPI2C_MSR_TDF_MASK)
+
+#define LPI2C_INT_STOP_SENT (LPI2C_INT_ERRORS | LPI2C_MSR_SDF_MASK)
+
+#define LPI2C_INT_RECEIVED (LPI2C_INT_ERRORS | LPI2C_MSR_RDF_MASK)
+
+#define LPI2C_INT_TRANSMITTED (LPI2C_INT_ERRORS | LPI2C_MSR_TDF_MASK)
+
+struct imxrt_lpi2c_bus {
+ i2c_bus base;
+ volatile LPI2C_Type *regs;
+ rtems_vector_number irq;
+ uint32_t src_clock_hz;
+ clock_ip_name_t clock_ip;
+ unsigned long clock;
+
+ rtems_binary_semaphore sem;
+ int eno;
+
+ uint32_t msg_todo;
+ const i2c_msg *msg;
+
+ /* Everything that is necessary for the current message */
+ uint32_t chunk_todo;
+ uint16_t buf_todo;
+ uint8_t *buf;
+ bool stop;
+ bool read;
+};
+
+static void imxrt_lpi2c_sw_reset(volatile LPI2C_Type *regs)
+{
+ regs->MCR = LPI2C_MCR_RST_MASK | LPI2C_MCR_RRF_MASK | LPI2C_MCR_RTF_MASK;
+ regs->SCR = LPI2C_SCR_RST_MASK | LPI2C_SCR_RRF_MASK | LPI2C_SCR_RTF_MASK;
+ regs->MCR = 0;
+ regs->SCR = 0;
+}
+
+static int imxrt_lpi2c_set_clock(i2c_bus *base, unsigned long clock)
+{
+ struct imxrt_lpi2c_bus *bus;
+ volatile LPI2C_Type *regs;
+
+ bus = (struct imxrt_lpi2c_bus *) base;
+ regs = bus->regs;
+
+ bus->clock = clock;
+
+ /*
+ * Maybe there is a more efficient way than used by that function. But
+ * changing clock doesn't happen often. So it should be OK for now.
+ */
+ LPI2C_MasterSetBaudRate((LPI2C_Type *)regs, bus->src_clock_hz, clock);
+
+ return 0;
+}
+
+static void imxrt_lpi2c_do_reinit(
+ struct imxrt_lpi2c_bus *bus,
+ volatile LPI2C_Type *regs
+)
+{
+ regs->MIER = 0;
+ imxrt_lpi2c_sw_reset(regs);
+
+ regs->MCFGR2 = LPI2C_MCFGR2_FILTSDA(0) | LPI2C_MCFGR2_FILTSCL(0) |
+ LPI2C_MCFGR2_BUSIDLE(0);
+ regs->MCFGR3 = LPI2C_MCFGR3_PINLOW(0);
+
+ regs->MFCR = LPI2C_MFCR_RXWATER(0) | LPI2C_MFCR_TXWATER(1);
+
+ imxrt_lpi2c_set_clock(&bus->base, bus->clock);
+}
+
+static void imxrt_lpi2c_done(
+ struct imxrt_lpi2c_bus *bus,
+ volatile LPI2C_Type *regs
+)
+{
+ regs->MIER = 0;
+ regs->MCR &= ~LPI2C_MCR_MEN_MASK;
+ rtems_binary_semaphore_post(&bus->sem);
+}
+
+static void imxrt_lpi2c_next_msg(
+ struct imxrt_lpi2c_bus *bus,
+ volatile LPI2C_Type *regs
+);
+
+static void imxrt_lpi2c_transmit_next(
+ struct imxrt_lpi2c_bus *bus,
+ volatile LPI2C_Type *regs
+)
+{
+ if (bus->chunk_todo == 0) {
+ /* Check whether a stop has to be send */
+ if (bus->stop) {
+ regs->MTDR = LPI2C_MTDR_CMD_stop;
+ bus->stop = false;
+ regs->MIER = LPI2C_INT_STOP_SENT;
+ } else {
+ imxrt_lpi2c_next_msg(bus, regs);
+ }
+ } else {
+ if (bus->read) {
+ uint16_t to_read;
+ to_read = MIN(bus->chunk_todo, 256);
+ bus->chunk_todo -= to_read;
+
+ regs->MTDR = LPI2C_MTDR_CMD_receive | (to_read - 1);
+ regs->MIER = LPI2C_INT_RECEIVED;
+ } else {
+ regs->MTDR = LPI2C_MTDR_CMD_transmit | *bus->buf;
+ ++bus->buf;
+ --bus->buf_todo;
+ --bus->chunk_todo;
+ regs->MIER = LPI2C_INT_TRANSMITTED;
+ }
+ }
+}
+
+static void imxrt_lpi2c_next_msg(
+ struct imxrt_lpi2c_bus *bus,
+ volatile LPI2C_Type *regs
+)
+{
+ if (bus->msg_todo == 0) {
+ imxrt_lpi2c_done(bus, regs);
+ } else {
+ const i2c_msg *msg;
+ int flags;
+ bool start;
+ uint16_t addr;
+
+ msg = bus->msg;
+ flags = msg->flags;
+
+ addr = msg->addr;
+ start = (flags & I2C_M_NOSTART) == 0;
+ bus->read = (flags & I2C_M_RD) != 0;
+ bus->chunk_todo = msg->len;
+ bus->buf_todo = msg->len;
+ bus->buf = msg->buf;
+ bus->stop = (flags & I2C_M_STOP) != 0 || bus->msg_todo <= 1;
+
+ ++bus->msg;
+ --bus->msg_todo;
+
+ if (start) {
+ uint32_t mtdr;
+ mtdr = LPI2C_MTDR_CMD_start_and_transmit;
+ mtdr |= addr << 1;
+ if (bus->read) {
+ mtdr |= 1;
+ }
+ regs->MTDR = mtdr;
+ regs->MIER = LPI2C_INT_ADDRESSED;
+ } else {
+ imxrt_lpi2c_transmit_next(bus, regs);
+ }
+ }
+}
+
+static void imxrt_lpi2c_interrupt(void *arg)
+{
+ struct imxrt_lpi2c_bus *bus;
+ volatile LPI2C_Type *regs;
+ uint32_t msr;
+
+ bus = arg;
+ regs = bus->regs;
+
+ msr = regs->MSR;
+ regs->MSR = msr;
+
+ if ((msr & LPI2C_INT_ERROR_NO_ACK) != 0) {
+ /* Just end the transmission */
+ bus->eno = EIO;
+ imxrt_lpi2c_done(bus, regs);
+ } else if ((msr & LPI2C_INT_ERRORS_SERIOUS) != 0) {
+ /* Some worse error occurred. Reset hardware. */
+ bus->eno = EIO;
+ imxrt_lpi2c_do_reinit(bus, regs);
+ imxrt_lpi2c_done(bus, regs);
+ } else {
+ uint32_t mrdr;
+ while (((mrdr = regs->MRDR) & LPI2C_MRDR_RXEMPTY_MASK) == 0) {
+ if (bus->read && bus->buf_todo > 0) {
+ *bus->buf = (mrdr & LPI2C_MRDR_DATA_MASK) >> LPI2C_MRDR_DATA_SHIFT;
+ ++bus->buf;
+ --bus->buf_todo;
+ }
+ }
+
+ if (
+ ((msr & LPI2C_MSR_TDF_MASK) != 0) &&
+ (!bus->read || bus->chunk_todo > 0 || bus->buf_todo == 0)
+ ) {
+ imxrt_lpi2c_transmit_next(bus, regs);
+ }
+ }
+}
+
+static int imxrt_lpi2c_wait_for_not_busy(volatile LPI2C_Type *regs)
+{
+ rtems_interval timeout;
+ bool before;
+
+ if ((regs->MSR & LPI2C_MSR_BBF_MASK) == 0) {
+ return 0;
+ }
+
+ timeout = rtems_clock_tick_later_usec(5000);
+
+ do {
+ before = rtems_clock_tick_before(timeout);
+
+ if ((regs->MSR & LPI2C_MSR_BBF_MASK) == 0) {
+ return 0;
+ }
+ } while (before);
+
+ return ETIMEDOUT;
+}
+
+static void imxrt_lpi2c_first_msg(
+ struct imxrt_lpi2c_bus *bus,
+ volatile LPI2C_Type *regs
+)
+{
+ if ((regs->MCR & LPI2C_MCR_MEN_MASK) == 0) {
+ regs->MCR |= LPI2C_MCR_MEN_MASK;
+ }
+
+ imxrt_lpi2c_next_msg(bus, regs);
+}
+
+static int imxrt_lpi2c_transfer(i2c_bus *base, i2c_msg *msgs, uint32_t n)
+{
+ struct imxrt_lpi2c_bus *bus;
+ volatile LPI2C_Type *regs;
+ int supported_flags;
+ int eno;
+ uint16_t i;
+
+ bus = (struct imxrt_lpi2c_bus *) base;
+ regs = bus->regs;
+
+ supported_flags = I2C_M_RD | I2C_M_STOP;
+
+ for (i = 0; i < n; ++i) {
+ if ((msgs[i].flags & ~supported_flags) != 0) {
+ return -EINVAL;
+ }
+
+ supported_flags |= I2C_M_NOSTART;
+ }
+
+ eno = imxrt_lpi2c_wait_for_not_busy(regs);
+ if (eno != 0) {
+ imxrt_lpi2c_do_reinit(bus, regs);
+ return -eno;
+ }
+
+ bus->msg_todo = n;
+ bus->msg = &msgs[0];
+ bus->eno = 0;
+
+ imxrt_lpi2c_first_msg(bus, regs);
+
+ eno = rtems_binary_semaphore_wait_timed_ticks(&bus->sem, bus->base.timeout);
+ if (eno != 0) {
+ /* Timeout */
+ imxrt_lpi2c_do_reinit(bus, regs);
+ rtems_binary_semaphore_try_wait(&bus->sem);
+ return -eno;
+ }
+
+ return -bus->eno;
+}
+
+static void imxrt_lpi2c_destroy(i2c_bus *base)
+{
+ struct imxrt_lpi2c_bus *bus;
+ volatile LPI2C_Type *regs;
+
+ bus = (struct imxrt_lpi2c_bus *) base;
+ regs = bus->regs;
+ imxrt_lpi2c_sw_reset(regs);
+ CLOCK_DisableClock(bus->clock_ip);
+
+ rtems_interrupt_handler_remove(bus->irq, imxrt_lpi2c_interrupt, bus);
+ i2c_bus_destroy_and_free(&bus->base);
+}
+
+static int imxrt_lpi2c_hw_init(struct imxrt_lpi2c_bus *bus)
+{
+ rtems_status_code sc;
+ volatile LPI2C_Type *regs;
+
+ regs = bus->regs;
+
+ CLOCK_EnableClock(bus->clock_ip);
+
+ bus->clock = I2C_BUS_CLOCK_DEFAULT;
+ imxrt_lpi2c_do_reinit(bus, regs);
+
+ sc = rtems_interrupt_handler_install(
+ bus->irq,
+ "LPI2C",
+ RTEMS_INTERRUPT_UNIQUE,
+ imxrt_lpi2c_interrupt,
+ bus
+ );
+ if (sc != RTEMS_SUCCESSFUL) {
+ return EAGAIN;
+ }
+
+ return 0;
+}
+
+static uint32_t imxrt_lpi2c_get_src_freq(void)
+{
+ uint32_t freq;
+ uint32_t mux;
+ uint32_t divider;
+
+ mux = CLOCK_GetMux(kCLOCK_Lpi2cMux);
+ divider = 1;
+
+ switch (mux) {
+ case 0: /* pll3_sw_clk */
+ freq = CLOCK_GetFreq(kCLOCK_Usb1PllClk);
+ divider = 8;
+ break;
+ case 1: /* OSC */
+ freq = CLOCK_GetFreq(kCLOCK_OscClk);
+ break;
+ default:
+ freq = 0;
+ }
+
+ divider *= CLOCK_GetDiv(kCLOCK_Lpi2cDiv) + 1;
+ freq /= divider;
+
+ return freq;
+}
+
+static clock_ip_name_t imxrt_lpi2c_clock_ip(volatile LPI2C_Type *regs)
+{
+ LPI2C_Type *const base_addresses[] = LPI2C_BASE_PTRS;
+ static const clock_ip_name_t lpi2c_clocks[] = LPI2C_CLOCKS;
+ size_t i;
+
+ for (i = 0; i < RTEMS_ARRAY_SIZE(base_addresses); ++i) {
+ if (base_addresses[i] == regs) {
+ return lpi2c_clocks[i];
+ }
+ }
+
+ return kCLOCK_IpInvalid;
+}
+
+void imxrt_lpi2c_init(void)
+{
+ const void *fdt;
+ int node;
+
+ fdt = bsp_fdt_get();
+ node = -1;
+
+ do {
+ node = fdt_node_offset_by_compatible(fdt, node, "nxp,imxrt-lpi2c");
+
+ if (node >= 0 && imxrt_fdt_node_is_enabled(fdt, node)) {
+ struct imxrt_lpi2c_bus *bus;
+ int eno;
+ const char *bus_path;
+
+ bus = (struct imxrt_lpi2c_bus*) i2c_bus_alloc_and_init(sizeof(*bus));
+ if (bus == NULL) {
+ bsp_fatal(IMXRT_FATAL_LPI2C_ALLOC_FAILED);
+ }
+
+ rtems_binary_semaphore_init(&bus->sem, "LPI2C");
+
+ bus->regs = imx_get_reg_of_node(fdt, node);
+ if (bus->regs == NULL) {
+ (*bus->base.destroy)(&bus->base);
+ bsp_fatal(IMXRT_FATAL_LPI2C_INVALID_FDT);
+ }
+
+ bus->irq = imx_get_irq_of_node(fdt, node, 0);
+ if (bus->irq == BSP_INTERRUPT_VECTOR_INVALID) {
+ (*bus->base.destroy)(&bus->base);
+ bsp_fatal(IMXRT_FATAL_LPI2C_INVALID_FDT);
+ }
+
+ bus_path = fdt_getprop(fdt, node, "rtems,path", NULL);
+ if (bus_path == NULL) {
+ (*bus->base.destroy)(&bus->base);
+ bsp_fatal(IMXRT_FATAL_LPI2C_INVALID_FDT);
+ }
+
+ bus->clock_ip = imxrt_lpi2c_clock_ip(bus->regs);
+ bus->src_clock_hz = imxrt_lpi2c_get_src_freq();
+
+ eno = imxrt_lpi2c_hw_init(bus);
+ if (eno != 0) {
+ (*bus->base.destroy)(&bus->base);
+ bsp_fatal(IMXRT_FATAL_LPI2C_HW_INIT_FAILED);
+ }
+
+ bus->base.transfer = imxrt_lpi2c_transfer;
+ bus->base.set_clock = imxrt_lpi2c_set_clock;
+ bus->base.destroy = imxrt_lpi2c_destroy;
+
+ /*
+ * Need at least three FIFO bytes:
+ * 1. One to two data to transmit or receive.
+ * Two is necessary for long receives without NACK.
+ * 2. A stop condition.
+ */
+ if ((1 << ((bus->regs->PARAM & LPI2C_PARAM_MTXFIFO_MASK) >>
+ LPI2C_PARAM_MTXFIFO_SHIFT)) < 3) {
+ bsp_fatal(IMXRT_FATAL_LPI2C_UNSUPPORTED_HARDWARE);
+ }
+
+ eno = i2c_bus_register(&bus->base, bus_path);
+ if (eno != 0) {
+ bsp_fatal(IMXRT_FATAL_LPI2C_REGISTER_FAILED);
+ }
+ }
+ } while (node >= 0);
+}