summaryrefslogblamecommitdiffstats
path: root/bsps/arm/imxrt/spi/imxrt-lpspi.c
blob: 80b47f96637e0b943d457490e00495b10920cbe5 (plain) (tree)



































                                                                              
                        







                            
                             
                             
 

                                 


                           

                                 

                           
 




























































































                                                                              
                             







                                        






                                                                          





















                                                                             















                                       





                                               






















                                                                                
     



                                         
     


   
                                    



                              
                             

                                
                      
 

                                      







                                               














                                                                
     













                                                






                                                                  




                                          

                                  

 








                                                                  











                                                                             








                                      

                                          

                   
                                                     



                  
                   



           








                                                                              
















                                                













                                                                             











































































                                                                       
                                                






















































                                                                      

















                                                                               



















































                                                                            
                                          







                                                     
/* 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>
#include <imxrt/lpspi.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;

  rtems_binary_semaphore sem;
  bool cs_change_on_last_msg;

  uint32_t rx_msg_todo;
  const spi_ioc_transfer *rx_msg;
  size_t remaining_rx_size;
  uint8_t *rx_buf;

  uint32_t tx_msg_todo;
  const spi_ioc_transfer *tx_msg;
  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);
  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;
  }

  if (bus->cs_change_on_last_msg) {
    /* No CONTC on first write. Otherwise upper 8 bits are not written. */
    regs->TCR = tcr;
  }
  regs->TCR = tcr | LPSPI_TCR_CONTC_MASK;

  bus->cs_change_on_last_msg = msg->cs_change;
}

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_next_tx_msg(
  struct imxrt_lpspi_bus *bus,
  volatile LPSPI_Type *regs
)
{
  if (bus->tx_msg_todo > 0) {
    const spi_ioc_transfer *msg;

    msg = bus->tx_msg;

    imxrt_lpspi_config(bus, regs, msg);
    bus->remaining_tx_size = msg->len;
    bus->tx_buf = msg->tx_buf;
  }
}

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->tx_msg_todo > 0 || bus->remaining_tx_size > 0)) {
    if (bus->remaining_tx_size > 0) {
      if (bus->remaining_tx_size == 1 && bus->tx_msg->cs_change) {
        /*
         * Necessary for getting the last data out of the Rx FIFO. 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."
         */
        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;
    }
    if (bus->remaining_tx_size == 0) {
      --bus->tx_msg_todo;
      ++bus->tx_msg;
      imxrt_lpspi_next_tx_msg(bus, regs);
    }
  }
}

static void imxrt_lpspi_next_rx_msg(
  struct imxrt_lpspi_bus *bus,
  volatile LPSPI_Type *regs
)
{
  if (bus->rx_msg_todo > 0) {
    const spi_ioc_transfer *msg;

    msg = bus->rx_msg;

    bus->remaining_rx_size = msg->len;
    bus->rx_buf = msg->rx_buf;
  }
}

static void imxrt_lpspi_pull_data_from_rx_fifo(
  struct imxrt_lpspi_bus *bus,
  volatile LPSPI_Type *regs
)
{
  uint32_t data;
  while (imxrt_lpspi_rx_fifo_not_empty(regs)
      && (bus->rx_msg_todo > 0 || bus->remaining_rx_size > 0)) {
    if (bus->remaining_rx_size > 0) {
      data = regs->RDR;
      if (bus->rx_buf != NULL) {
        *bus->rx_buf = data;
        ++bus->rx_buf;
      }
      --bus->remaining_rx_size;
    }
    if (bus->remaining_rx_size == 0) {
      --bus->rx_msg_todo;
      ++bus->rx_msg;
      imxrt_lpspi_next_rx_msg(bus, regs);
    }
  }
}

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->tx_msg_todo > 0 || bus->remaining_tx_size > 0) {
    regs->IER = LPSPI_IER_TDIE_MASK;
  } else if (bus->rx_msg_todo > 0 || bus->remaining_rx_size > 0) {
    regs->IER = LPSPI_IER_RDIE_MASK;
  } else {
    regs->IER = 0;
    rtems_binary_semaphore_post(&bus->sem);
  }
}

static inline int imxrt_lpspi_settings_ok(
  struct imxrt_lpspi_bus *bus,
  const spi_ioc_transfer *msg,
  const spi_ioc_transfer *prev_msg
)
{
  /* 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;
  }

  if (prev_msg != NULL && !prev_msg->cs_change) {
    /*
     * A lot of settings have to be the same in this case because the upper 8
     * bit of TCR can't be changed if it is a continuous transfer.
     */
    if (prev_msg->cs != msg->cs ||
        prev_msg->speed_hz != msg->speed_hz ||
        prev_msg->mode != msg->mode) {
      return -EINVAL;
    }
  }

  return 0;
}

static int imxrt_lpspi_check_messages(
  struct imxrt_lpspi_bus *bus,
  const spi_ioc_transfer *msg,
  uint32_t size
)
{
  const spi_ioc_transfer *prev_msg = NULL;

  while(size > 0) {
    int rv;
    rv = imxrt_lpspi_settings_ok(bus, msg, prev_msg);
    if (rv != 0) {
      return rv;
    }

    prev_msg = msg;
    ++msg;
    --size;
  }

  /*
   * Check whether cs_change is set on last message. Can't work without it
   * because the last received data is only put into the FIFO if it is the end
   * of a transfer or if another TX byte is put into the FIFO.
   */
  if (!prev_msg->cs_change) {
    return -EINVAL;
  }

  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->tx_msg_todo = n;
    bus->tx_msg = &msgs[0];
    bus->rx_msg_todo = n;
    bus->rx_msg = &msgs[0];
    bus->cs_change_on_last_msg = true;

    imxrt_lpspi_next_rx_msg(bus, bus->regs);
    imxrt_lpspi_next_tx_msg(bus, bus->regs);
    /*
     * Enable the transmit FIFO empty interrupt which will cause an interrupt
     * instantly because there is no data in the transmit FIFO. The interrupt
     * will then fill the FIFO. So nothing else to do here.
     */
    bus->regs->IER = LPSPI_IER_TDIE_MASK;
    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, NULL);

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

static int imxrt_lpspi_ioctl(spi_bus *base, ioctl_command_t command, void *arg)
{
  struct imxrt_lpspi_bus *bus;
  bus = (struct imxrt_lpspi_bus *) base;
  int err = 0;

  switch (command) {
    case IMXRT_LPSPI_GET_REGISTERS:
      *(volatile LPSPI_Type**)arg = bus->regs;
      break;
    default:
      err = -EINVAL;
      break;
  }

  return err;
}

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;
      bus->base.ioctl = imxrt_lpspi_ioctl;

      eno = spi_bus_register(&bus->base, bus_path);
      if (eno != 0) {
        bsp_fatal(IMXRT_FATAL_LPSPI_REGISTER_FAILED);
      }
    }
  } while (node >= 0);
}