From 7141afbb0ea41675ef8cf7e60f398aaf900defd9 Mon Sep 17 00:00:00 2001 From: Christian Mauderer Date: Fri, 9 Oct 2020 15:55:35 +0200 Subject: bsp/imxrt: Add new BSP Update #4180 --- bsps/arm/imxrt/i2c/imxrt-lpi2c.c | 489 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 489 insertions(+) create mode 100644 bsps/arm/imxrt/i2c/imxrt-lpi2c.c (limited to 'bsps/arm/imxrt/i2c') 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 +#include +#include +#include + +#include +#include +#include +#include +#include + +#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); +} -- cgit v1.2.3