/* 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 struct imxrt_lpspi_bus { spi_bus base; volatile LPSPI_Type *regs; rtems_vector_number irq; uint32_t src_clock_hz; clock_ip_name_t clock_ip; uint32_t msg_todo; const spi_ioc_transfer *msg; rtems_binary_semaphore sem; uint32_t tcr; size_t remaining_rx_size; uint8_t *rx_buf; size_t remaining_tx_size; const uint8_t *tx_buf; uint32_t fifo_size; }; static const uint32_t word_size = 8; static unsigned div_round_up(unsigned divident, unsigned divisor) { return (divident + divisor - 1) / divisor; } static void imxrt_lpspi_find_clockdivs( struct imxrt_lpspi_bus *bus, uint32_t max_baud_hz, unsigned *sckdiv, unsigned *prescale ) { const unsigned max_sckdif = LPSPI_CCR_SCKDIV_MASK >> LPSPI_CCR_SCKDIV_SHIFT; const unsigned max_prescale = LPSPI_TCR_PRESCALE_MASK >> LPSPI_TCR_PRESCALE_SHIFT; unsigned best_baud_hz; int best_sckdif; int best_prescale; int check_baud_hz; int check_sckdif; int check_prescale; /* Start with slowest possible */ best_sckdif = max_sckdif; best_prescale = max_prescale; best_baud_hz = div_round_up(bus->src_clock_hz, (1 << best_prescale) * (best_sckdif + 2)); for (check_prescale = 0; check_prescale <= max_prescale && best_baud_hz < max_baud_hz; ++check_prescale) { check_sckdif = div_round_up(bus->src_clock_hz, (1 << check_prescale) * max_baud_hz) - 2; if (check_sckdif > max_sckdif) { check_sckdif = max_sckdif; } check_baud_hz = div_round_up(bus->src_clock_hz, (1 << check_prescale) * (check_sckdif + 2)); if (check_baud_hz <= max_baud_hz && check_baud_hz > best_baud_hz) { best_baud_hz = check_baud_hz; best_sckdif = check_sckdif; best_prescale = check_prescale; } } *sckdiv = best_sckdif; *prescale = best_prescale; } static void imxrt_lpspi_config( struct imxrt_lpspi_bus *bus, volatile LPSPI_Type *regs, const spi_ioc_transfer *msg ) { uint32_t ccr_orig; uint32_t ccr; uint32_t tcr; unsigned sckdiv; unsigned prescale; ccr_orig = ccr = regs->CCR; tcr = 0; imxrt_lpspi_find_clockdivs(bus, msg->speed_hz, &sckdiv, &prescale); /* Currently just force half a clock after and before chip select. */ ccr = LPSPI_CCR_SCKDIV(sckdiv) | LPSPI_CCR_SCKPCS(sckdiv) | LPSPI_CCR_PCSSCK(sckdiv) | LPSPI_CCR_DBT(sckdiv); tcr |= LPSPI_TCR_PRESCALE(prescale); if ((msg->mode & SPI_CPOL) != 0) { tcr |= LPSPI_TCR_CPOL_MASK; } if ((msg->mode & SPI_CPHA) != 0) { tcr |= LPSPI_TCR_CPHA_MASK; } if (msg->mode & SPI_LSB_FIRST) { tcr |= LPSPI_TCR_LSBF_MASK; } tcr |= LPSPI_TCR_PCS(msg->cs); if (!msg->cs_change) { tcr |= LPSPI_TCR_CONT_MASK; } tcr |= LPSPI_TCR_FRAMESZ(word_size-1); if (ccr_orig != ccr) { regs->CR &= ~LPSPI_CR_MEN_MASK; regs->CCR = ccr; regs->CR |= LPSPI_CR_MEN_MASK; } /* No CONTC on first write. Otherwise upper 8 bits are not written. */ regs->TCR = tcr; regs->TCR = tcr | LPSPI_TCR_CONTC_MASK | LPSPI_TCR_CONT_MASK; } static inline bool imxrt_lpspi_rx_fifo_not_empty( volatile LPSPI_Type *regs ) { return ((regs->RSR & LPSPI_RSR_RXEMPTY_MASK) == 0); } static inline bool imxrt_lpspi_tx_fifo_not_full( struct imxrt_lpspi_bus *bus, volatile LPSPI_Type *regs ) { /* * We might add two things to the FIFO: A TCR and data. Therefore leave one * extra space. */ return ((regs->FSR & LPSPI_FSR_TXCOUNT_MASK) >> LPSPI_FSR_TXCOUNT_SHIFT) < bus->fifo_size - 2; } static void imxrt_lpspi_fill_tx_fifo( struct imxrt_lpspi_bus *bus, volatile LPSPI_Type *regs ) { while(imxrt_lpspi_tx_fifo_not_full(bus, regs) && bus->remaining_tx_size > 0) { if (bus->remaining_tx_size == 1) { regs->TCR &= ~(LPSPI_TCR_CONT_MASK); } if (bus->tx_buf != NULL) { regs->TDR = bus->tx_buf[0]; ++bus->tx_buf; } else { regs->TDR = 0; } --bus->remaining_tx_size; } } static void imxrt_lpspi_next_msg( struct imxrt_lpspi_bus *bus, volatile LPSPI_Type *regs ) { if (bus->msg_todo > 0) { const spi_ioc_transfer *msg; msg = bus->msg; imxrt_lpspi_config(bus, regs, msg); bus->remaining_tx_size = msg->len; bus->remaining_rx_size = msg->len; bus->rx_buf = msg->rx_buf; bus->tx_buf = msg->tx_buf; imxrt_lpspi_fill_tx_fifo(bus, regs); regs->IER = LPSPI_IER_TDIE_MASK; } else { regs->IER = 0; rtems_binary_semaphore_post(&bus->sem); } } static void imxrt_lpspi_pull_data_from_rx_fifo( struct imxrt_lpspi_bus *bus, volatile LPSPI_Type *regs ) { while (imxrt_lpspi_rx_fifo_not_empty(regs) && bus->remaining_rx_size > 0) { uint32_t data; data = regs->RDR; if (bus->rx_buf != NULL) { *bus->rx_buf = data; ++bus->rx_buf; } --bus->remaining_rx_size; } } static void imxrt_lpspi_interrupt(void *arg) { struct imxrt_lpspi_bus *bus; volatile LPSPI_Type *regs; bus = arg; regs = bus->regs; imxrt_lpspi_pull_data_from_rx_fifo(bus, regs); imxrt_lpspi_fill_tx_fifo(bus, regs); if (bus->remaining_tx_size == 0) { if (bus->remaining_rx_size > 0) { regs->IER = LPSPI_IER_RDIE_MASK; } else { --bus->msg_todo; ++bus->msg; imxrt_lpspi_next_msg(bus, regs); } } } static inline int imxrt_lpspi_settings_ok( struct imxrt_lpspi_bus *bus, const spi_ioc_transfer *msg ) { if (msg->cs_change == 0) { /* * This one most likely would need a bigger workaround if it is necessary. * See "i.MX RT1050 Processor Reference Manual Rev. 4" Chapter 47.3.2.2 * "Receive FIFO and Data Match": * * "During a continuous transfer, if the transmit FIFO is empty, then the * receive data is only written to the receive FIFO after the transmit FIFO * is written or after the Transmit Command Register (TCR) is written to end * the frame." * * It might is possible to extend the driver so that it can work with an * empty read buffer. */ return -EINVAL; } /* most of this is currently just not implemented */ if (msg->cs > 3 || msg->speed_hz > bus->base.max_speed_hz || msg->delay_usecs != 0 || (msg->mode & ~(SPI_CPHA | SPI_CPOL | SPI_LSB_FIRST)) != 0 || msg->bits_per_word != word_size) { return -EINVAL; } return 0; } static int imxrt_lpspi_check_messages( struct imxrt_lpspi_bus *bus, const spi_ioc_transfer *msg, uint32_t size ) { while(size > 0) { int rv; rv = imxrt_lpspi_settings_ok(bus, msg); if (rv != 0) { return rv; } ++msg; --size; } return 0; } static int imxrt_lpspi_transfer( spi_bus *base, const spi_ioc_transfer *msgs, uint32_t n ) { struct imxrt_lpspi_bus *bus; int rv; bus = (struct imxrt_lpspi_bus *) base; rv = imxrt_lpspi_check_messages(bus, msgs, n); if (rv == 0) { bus->msg_todo = n; bus->msg = &msgs[0]; imxrt_lpspi_next_msg(bus, bus->regs); rtems_binary_semaphore_wait(&bus->sem); } return rv; } static void imxrt_lpspi_sw_reset(volatile LPSPI_Type *regs) { regs->CR = LPSPI_CR_RST_MASK | LPSPI_CR_RRF_MASK | LPSPI_CR_RTF_MASK; regs->CR = 0; } static void imxrt_lpspi_destroy(spi_bus *base) { struct imxrt_lpspi_bus *bus; volatile LPSPI_Type *regs; bus = (struct imxrt_lpspi_bus *) base; regs = bus->regs; imxrt_lpspi_sw_reset(regs); CLOCK_DisableClock(bus->clock_ip); rtems_interrupt_handler_remove(bus->irq, imxrt_lpspi_interrupt, bus); spi_bus_destroy_and_free(&bus->base); } static int imxrt_lpspi_hw_init(struct imxrt_lpspi_bus *bus) { rtems_status_code sc; volatile LPSPI_Type *regs; regs = bus->regs; CLOCK_EnableClock(bus->clock_ip); imxrt_lpspi_sw_reset(regs); regs->CFGR1 |= LPSPI_CFGR1_MASTER_MASK; regs->FCR = LPSPI_FCR_TXWATER(0) | LPSPI_FCR_RXWATER(0); regs->CR |= LPSPI_CR_MEN_MASK; bus->fifo_size = 1 << ((regs->PARAM & LPSPI_PARAM_TXFIFO_MASK) >> LPSPI_PARAM_TXFIFO_SHIFT); sc = rtems_interrupt_handler_install( bus->irq, "LPSPI", RTEMS_INTERRUPT_UNIQUE, imxrt_lpspi_interrupt, bus ); if (sc != RTEMS_SUCCESSFUL) { return EAGAIN; } return 0; } static int imxrt_lpspi_setup(spi_bus *base) { struct imxrt_lpspi_bus *bus; int rv; spi_ioc_transfer msg = { .cs_change = base->cs_change, .cs = base->cs, .bits_per_word = base->bits_per_word, .mode = base->mode, .speed_hz = base->speed_hz, .delay_usecs = base->delay_usecs, .rx_buf = NULL, .tx_buf = NULL, }; bus = (struct imxrt_lpspi_bus *) base; rv = imxrt_lpspi_settings_ok(bus, &msg); /* * Nothing to do besides checking. * Every transfer will later overwrite the settings anyway. */ return rv; } static uint32_t imxrt_lpspi_get_src_freq(void) { uint32_t freq; uint32_t mux; uint32_t divider; mux = CLOCK_GetMux(kCLOCK_LpspiMux); switch (mux) { case 0: /* PLL3 PFD1 */ freq = CLOCK_GetFreq(kCLOCK_Usb1PllPfd1Clk); break; case 1: /* PLL3 PFD0 */ freq = CLOCK_GetFreq(kCLOCK_Usb1PllPfd0Clk); break; case 2: /* PLL2 */ freq = CLOCK_GetFreq(kCLOCK_SysPllClk); break; case 3: /* PLL2 PFD2 */ freq = CLOCK_GetFreq(kCLOCK_SysPllPfd2Clk); break; default: freq = 0; } divider = CLOCK_GetDiv(kCLOCK_LpspiDiv) + 1; freq /= divider; return freq; } static clock_ip_name_t imxrt_lpspi_clock_ip(volatile LPSPI_Type *regs) { LPSPI_Type *const base_addresses[] = LPSPI_BASE_PTRS; static const clock_ip_name_t lpspi_clocks[] = LPSPI_CLOCKS; size_t i; for (i = 0; i < RTEMS_ARRAY_SIZE(base_addresses); ++i) { if (base_addresses[i] == regs) { return lpspi_clocks[i]; } } return kCLOCK_IpInvalid; } void imxrt_lpspi_init(void) { const void *fdt; int node; fdt = bsp_fdt_get(); node = -1; do { node = fdt_node_offset_by_compatible(fdt, node, "nxp,imxrt-lpspi"); if (node >= 0 && imxrt_fdt_node_is_enabled(fdt, node)) { struct imxrt_lpspi_bus *bus; int eno; const char *bus_path; bus = (struct imxrt_lpspi_bus*) spi_bus_alloc_and_init(sizeof(*bus)); if (bus == NULL) { bsp_fatal(IMXRT_FATAL_LPSPI_ALLOC_FAILED); } rtems_binary_semaphore_init(&bus->sem, "LPSPI"); bus->regs = imx_get_reg_of_node(fdt, node); if (bus->regs == NULL) { bsp_fatal(IMXRT_FATAL_LPSPI_INVALID_FDT); } bus->irq = imx_get_irq_of_node(fdt, node, 0); if (bus->irq == BSP_INTERRUPT_VECTOR_INVALID) { bsp_fatal(IMXRT_FATAL_LPSPI_INVALID_FDT); } bus_path = fdt_getprop(fdt, node, "rtems,path", NULL); if (bus_path == NULL) { bsp_fatal(IMXRT_FATAL_LPSPI_INVALID_FDT); } bus->clock_ip = imxrt_lpspi_clock_ip(bus->regs); bus->src_clock_hz = imxrt_lpspi_get_src_freq(); /* Absolut maximum is 30MHz according to electrical characteristics */ bus->base.max_speed_hz = MIN(bus->src_clock_hz / 2, 30000000); bus->base.delay_usecs = 0; eno = imxrt_lpspi_hw_init(bus); if (eno != 0) { bsp_fatal(IMXRT_FATAL_LPSPI_HW_INIT_FAILED); } bus->base.transfer = imxrt_lpspi_transfer; bus->base.destroy = imxrt_lpspi_destroy; bus->base.setup = imxrt_lpspi_setup; eno = spi_bus_register(&bus->base, bus_path); if (eno != 0) { bsp_fatal(IMXRT_FATAL_LPSPI_REGISTER_FAILED); } } } while (node >= 0); }