summaryrefslogtreecommitdiffstats
path: root/bsps/arm/imxrt/spi/imxrt-lpspi.c
diff options
context:
space:
mode:
Diffstat (limited to 'bsps/arm/imxrt/spi/imxrt-lpspi.c')
-rw-r--r--bsps/arm/imxrt/spi/imxrt-lpspi.c533
1 files changed, 533 insertions, 0 deletions
diff --git a/bsps/arm/imxrt/spi/imxrt-lpspi.c b/bsps/arm/imxrt/spi/imxrt-lpspi.c
new file mode 100644
index 0000000000..6da3ffdecf
--- /dev/null
+++ b/bsps/arm/imxrt/spi/imxrt-lpspi.c
@@ -0,0 +1,533 @@
+/* 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/spi/spi.h>
+#include <fsl_clock.h>
+#include <libfdt.h>
+
+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);
+}