/* 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); }