/****************************************************************************
 * arch/arm/src/lpc54xx/lpc54_spi_master.c
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/* TODO:
 *
 *   - There are no interrupt driven transfers, only polled.  I don't
 *     consider this a significant problem because of the higher rate that
 *     would be necessary for interrupt driven transfers.
 *   - Integrate DMA transfers.  This is fairly important because it can
 *     a) improve the data transfer rates and b) free the CPU when the
 *     SPI driver would otherwise be stuck in a tight polling loop.
 */

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <sys/types.h>
#include <stdint.h>
#include <stdbool.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>

#include <arch/board/board.h>
#include <nuttx/arch.h>
#include <nuttx/mutex.h>
#include <nuttx/spi/spi.h>

#include "arm_internal.h"
#include "hardware/lpc54_pinmux.h"
#include "hardware/lpc54_syscon.h"
#include "hardware/lpc54_flexcomm.h"
#include "hardware/lpc54_spi.h"
#include "lpc54_config.h"
#include "lpc54_enableclk.h"
#include "lpc54_gpio.h"
#include "lpc54_spi_master.h"

#ifdef HAVE_SPI_MASTER_DEVICE

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

#define SPI_DUMMYDATA8   0xff
#define SPI_DUMMYDATA16  0xffff

#define SPI_MINWIDTH     4
#ifdef CONFIG_LPC54_SPI_WIDEDATA
#  define SPI_MAXWIDTH   16
#else
#  define SPI_MAXWIDTH   8
#endif

/****************************************************************************
 * Private Types
 ****************************************************************************/

/* This structure describes the state of the SSP driver */

struct lpc54_spidev_s
{
  struct spi_dev_s dev;       /* Externally visible part of the SPI interface */
  uintptr_t base;             /* Base address of Flexcomm registers */
  mutex_t lock;               /* Held while chip is selected for mutual exclusion */
  uint32_t fclock;            /* Flexcomm function clock frequency */
  uint32_t frequency;         /* Requested clock frequency */
  uint32_t actual;            /* Actual clock frequency */
  uint16_t irq;               /* Flexcomm IRQ number */
  uint8_t nbits;              /* Width of word in bits (SPI_MINWIDTH to SPI_MAXWIDTH) */
  uint8_t mode;               /* Mode 0,1,2,3 */
};

/* These structures describes the Rx side of an 8- or 16-bit SPI data
 * exchange.
 */

struct lpc54_rxtransfer8_s
{
  uint8_t *rxptr;         /* Pointer into receive buffer */
  unsigned int remaining; /* Bytes remaining in the receive buffer */
  unsigned int expected;  /* Bytes expected to be received */
};

#ifdef CONFIG_LPC54_SPI_WIDEDATA
struct lpc54_rxtransfer16_s
{
  uint16_t *rxptr;        /* Pointer into receive buffer */
  unsigned int remaining; /* Hwords remaining in the receive buffer */
  unsigned int expected;  /* Hwords expected to be received */
};
#endif

/* These structures describes the Tx side of an 8- or 16-bit SPI data
 * exchange.
 */

struct lpc54_txtransfer8_s
{
  uint32_t txctrl;        /* Tx control bits */
  const uint8_t *txptr;   /* Pointer into transmit buffer */
  unsigned int remaining; /* Bytes remaining in the transmit buffer */
};

#ifdef CONFIG_LPC54_SPI_WIDEDATA
struct lpc54_txtransfer16_s
{
  uint32_t txctrl;        /* Tx control bits */
  const uint16_t *txptr;  /* Pointer into transmit buffer */
  unsigned int remaining; /* Hwords remaining in the transmit buffer */
};
#endif

struct lpc54_txdummy_s
{
  uint32_t txctrl;        /* Tx control bits */
  unsigned int remaining; /* Bytes remaining in the transmit buffer */
};

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

/* Transfer helpers */

static inline size_t lpc54_spi_fifodepth(struct lpc54_spidev_s *priv);
static inline bool lpc54_spi_txavailable(struct lpc54_spidev_s *priv);
static inline bool lpc54_spi_rxavailable(struct lpc54_spidev_s *priv);

static void     lpc54_spi_resetfifos(struct lpc54_spidev_s *priv);
static void     lpc54_spi_rxtransfer8(struct lpc54_spidev_s *priv,
                  struct lpc54_rxtransfer8_s *xfr);
#ifdef CONFIG_LPC54_SPI_WIDEDATA
static void     lpc54_spi_rxtransfer16(struct lpc54_spidev_s *priv,
                  struct lpc54_rxtransfer16_s *xfr);
#endif
static bool     lpc54_spi_txtransfer8(struct lpc54_spidev_s *priv,
                  struct lpc54_txtransfer8_s *xfr);
#ifdef CONFIG_LPC54_SPI_WIDEDATA
static bool     lpc54_spi_txtransfer16(struct lpc54_spidev_s *priv,
                  struct lpc54_txtransfer16_s *xfr);
#endif
static bool     lpc54_spi_txdummy(struct lpc54_spidev_s *priv,
                  struct lpc54_txdummy_s *xfr);
#ifdef CONFIG_SPI_EXCHANGE
static void     lpc54_spi_exchange8(struct lpc54_spidev_s *priv,
                  const void *txbuffer, void *rxbuffer,
                  size_t nwords);
#ifdef CONFIG_LPC54_SPI_WIDEDATA
static void     lpc54_spi_exchange16(struct lpc54_spidev_s *priv,
                  const void *txbuffer, void *rxbuffer,
                  size_t nwords);
#endif
#endif
static void     lpc54_spi_sndblock8(struct lpc54_spidev_s *priv,
                  const void *buffer, size_t nwords);
#ifdef CONFIG_LPC54_SPI_WIDEDATA
static void     lpc54_spi_sndblock16(struct lpc54_spidev_s *priv,
                  const void *buffer, size_t nwords);
#endif
static void     lpc54_spi_recvblock8(struct lpc54_spidev_s *priv,
                  void *buffer, size_t nwords);
#ifdef CONFIG_LPC54_SPI_WIDEDATA
static void     lpc54_spi_recvblock16(struct lpc54_spidev_s *priv,
                  void *buffer, size_t nwords);
#endif

/* SPI methods */

static int      lpc54_spi_lock(struct spi_dev_s *dev, bool lock);
static uint32_t lpc54_spi_setfrequency(struct spi_dev_s *dev,
                  uint32_t frequency);
static void     lpc54_spi_setmode(struct spi_dev_s *dev,
                  enum spi_mode_e mode);
static void     lpc54_spi_setbits(struct spi_dev_s *dev, int nbits);
static uint32_t lpc54_spi_send(struct spi_dev_s *dev, uint32_t wd);
#ifdef CONFIG_SPI_EXCHANGE
static void     lpc54_spi_exchange(struct spi_dev_s *dev,
                  const void *txbuffer, void *rxbuffer,
                  size_t nwords);
#endif
static void     lpc54_spi_sndblock(struct spi_dev_s *dev,
                  const void *buffer, size_t nwords);
static void     lpc54_spi_recvblock(struct spi_dev_s *dev,
                  void *buffer, size_t nwords);

/****************************************************************************
 * Private Data
 ****************************************************************************/

#ifdef CONFIG_LPC54_SPI0_MASTER
static const struct spi_ops_s g_spi0_ops =
{
  .lock              = lpc54_spi_lock,
  .select            = lpc54_spi0_select,   /* Provided externally */
  .setfrequency      = lpc54_spi_setfrequency,
  .setmode           = lpc54_spi_setmode,
  .setbits           = lpc54_spi_setbits,
#ifdef CONFIG_SPI_HWFEATURES
  .hwfeatures        = NULL,                /* Not supported */
#endif
  .status            = lpc54_spi0_status,   /* Provided externally */
#ifdef CONFIG_SPI_CMDDATA
  .cmddata           = lpc54_spi0_cmddata,  /* Provided externally */
#endif
  .send              = lpc54_spi_send,
#ifdef CONFIG_SPI_EXCHANGE
  .exchange          = lpc54_spi_exchange,
#else
  .sndblock          = lpc54_spi_sndblock,
  .recvblock         = lpc54_spi_recvblock,
#endif
#ifdef CONFIG_SPI_CALLBACK
  .registercallback  = lpc54_spi0_register, /* Provided externally */
#else
  .registercallback  = NULL,                /* Not implemented */
#endif
};

static struct lpc54_spidev_s g_spi0_dev =
{
  .lock = NXMUTEX_INITIALIZER,
};
#endif

#ifdef CONFIG_LPC54_SPI1_MASTER
static const struct spi_ops_s g_spi1_ops =
{
  .lock              = lpc54_spi_lock,
  .select            = lpc54_spi1_select,   /* Provided externally */
  .setfrequency      = lpc54_spi_setfrequency,
  .setmode           = lpc54_spi_setmode,
  .setbits           = lpc54_spi_setbits,
#ifdef CONFIG_SPI_HWFEATURES
  .hwfeatures        = NULL,                /* Not supported */
#endif
  .status            = lpc54_spi1_status,   /* Provided externally */
#ifdef CONFIG_SPI_CMDDATA
  .cmddata           = lpc54_spi1_cmddata,  /* Provided externally */
#endif
  .send              = lpc54_spi_send,
#ifdef CONFIG_SPI_EXCHANGE
  .exchange          = lpc54_spi_exchange,
#else
  .sndblock          = lpc54_spi_sndblock,
  .recvblock         = lpc54_spi_recvblock,
#endif
#ifdef CONFIG_SPI_CALLBACK
  .registercallback  = lpc54_spi1_register, /* Provided externally */
#else
  .registercallback  = NULL,                /* Not implemented */
#endif
};

static struct lpc54_spidev_s g_spi1_dev =
{
  .lock = NXMUTEX_INITIALIZER,
};
#endif

#ifdef CONFIG_LPC54_SPI2_MASTER
static const struct spi_ops_s g_spi2_ops =
{
  .lock              = lpc54_spi_lock,
  .select            = lpc54_spi2_select,   /* Provided externally */
  .setfrequency      = lpc54_spi_setfrequency,
  .setmode           = lpc54_spi_setmode,
  .setbits           = lpc54_spi_setbits,
#ifdef CONFIG_SPI_HWFEATURES
  .hwfeatures        = NULL,                /* Not supported */
#endif
  .status            = lpc54_spi2_status,   /* Provided externally */
#ifdef CONFIG_SPI_CMDDATA
  .cmddata           = lpc54_spi2_cmddata,  /* Provided externally */
#endif
  .send              = lpc54_spi_send,
#ifdef CONFIG_SPI_EXCHANGE
  .exchange          = lpc54_spi_exchange,
#else
  .sndblock          = lpc54_spi_sndblock,
  .recvblock         = lpc54_spi_recvblock,
#endif
#ifdef CONFIG_SPI_CALLBACK
  .registercallback  = lpc54_spi2_register, /* Provided externally */
#else
  .registercallback  = NULL,                /* Not implemented */
#endif
};

static struct lpc54_spidev_s g_spi2_dev =
{
  .lock = NXMUTEX_INITIALIZER,
};
#endif

#ifdef CONFIG_LPC54_SPI3_MASTER
static const struct spi_ops_s g_spi3_ops =
{
  .lock              = lpc54_spi_lock,
  .select            = lpc54_spi3_select,   /* Provided externally */
  .setfrequency      = lpc54_spi_setfrequency,
  .setmode           = lpc54_spi_setmode,
  .setbits           = lpc54_spi_setbits,
#ifdef CONFIG_SPI_HWFEATURES
  .hwfeatures        = NULL,                /* Not supported */
#endif
  .status            = lpc54_spi3_status,   /* Provided externally */
#ifdef CONFIG_SPI_CMDDATA
  .cmddata           = lpc54_spi3_cmddata,  /* Provided externally */
#endif
  .send              = lpc54_spi_send,
#ifdef CONFIG_SPI_EXCHANGE
  .exchange          = lpc54_spi_exchange,
#else
  .sndblock          = lpc54_spi_sndblock,
  .recvblock         = lpc54_spi_recvblock,
#endif
#ifdef CONFIG_SPI_CALLBACK
  .registercallback  = lpc54_spi3_register, /* Provided externally */
#else
  .registercallback  = NULL,                /* Not implemented */
#endif
};

static struct lpc54_spidev_s g_spi3_dev =
{
  .lock = NXMUTEX_INITIALIZER,
};
#endif

#ifdef CONFIG_LPC54_SPI4_MASTER
static const struct spi_ops_s g_spi4_ops =
{
  .lock              = lpc54_spi_lock,
  .select            = lpc54_spi4_select,   /* Provided externally */
  .setfrequency      = lpc54_spi_setfrequency,
  .setmode           = lpc54_spi_setmode,
  .setbits           = lpc54_spi_setbits,
#ifdef CONFIG_SPI_HWFEATURES
  .hwfeatures        = NULL,                /* Not supported */
#endif
  .status            = lpc54_spi4_status,   /* Provided externally */
#ifdef CONFIG_SPI_CMDDATA
  .cmddata           = lpc54_spi4_cmddata,  /* Provided externally */
#endif
  .send              = lpc54_spi_send,
#ifdef CONFIG_SPI_EXCHANGE
  .exchange          = lpc54_spi_exchange,
#else
  .sndblock          = lpc54_spi_sndblock,
  .recvblock         = lpc54_spi_recvblock,
#endif
#ifdef CONFIG_SPI_CALLBACK
  .registercallback  = lpc54_spi4_register, /* Provided externally */
#else
  .registercallback  = NULL,                /* Not implemented */
#endif
};

static struct lpc54_spidev_s g_spi4_dev =
{
  .lock = NXMUTEX_INITIALIZER,
};
#endif

#ifdef CONFIG_LPC54_SPI5_MASTER
static const struct spi_ops_s g_spi5_ops =
{
  .lock              = lpc54_spi_lock,
  .select            = lpc54_spi5_select,   /* Provided externally */
  .setfrequency      = lpc54_spi_setfrequency,
  .setmode           = lpc54_spi_setmode,
  .setbits           = lpc54_spi_setbits,
#ifdef CONFIG_SPI_HWFEATURES
  .hwfeatures        = NULL,                /* Not supported */
#endif
  .status            = lpc54_spi5_status,   /* Provided externally */
#ifdef CONFIG_SPI_CMDDATA
  .cmddata           = lpc54_spi5_cmddata,  /* Provided externally */
#endif
  .send              = lpc54_spi_send,
#ifdef CONFIG_SPI_EXCHANGE
  .exchange          = lpc54_spi_exchange,
#else
  .sndblock          = lpc54_spi_sndblock,
  .recvblock         = lpc54_spi_recvblock,
#endif
#ifdef CONFIG_SPI_CALLBACK
  .registercallback  = lpc54_spi5_register, /* Provided externally */
#else
  .registercallback  = NULL,                /* Not implemented */
#endif
};

static struct lpc54_spidev_s g_spi5_dev =
{
  .lock = NXMUTEX_INITIALIZER,
};
#endif

#ifdef CONFIG_LPC54_SPI6_MASTER
static const struct spi_ops_s g_spi6_ops =
{
  .lock              = lpc54_spi_lock,
  .select            = lpc54_spi6_select,   /* Provided externally */
  .setfrequency      = lpc54_spi_setfrequency,
  .setmode           = lpc54_spi_setmode,
  .setbits           = lpc54_spi_setbits,
#ifdef CONFIG_SPI_HWFEATURES
  .hwfeatures        = NULL,                /* Not supported */
#endif
  .status            = lpc54_spi6_status,   /* Provided externally */
#ifdef CONFIG_SPI_CMDDATA
  .cmddata           = lpc54_spi6_cmddata,  /* Provided externally */
#endif
  .send              = lpc54_spi_send,
#ifdef CONFIG_SPI_EXCHANGE
  .exchange          = lpc54_spi_exchange,
#else
  .sndblock          = lpc54_spi_sndblock,
  .recvblock         = lpc54_spi_recvblock,
#endif
#ifdef CONFIG_SPI_CALLBACK
  .registercallback  = lpc54_spi6_register, /* Provided externally */
#else
  .registercallback  = NULL,                /* Not implemented */
#endif
};

static struct lpc54_spidev_s g_spi6_dev =
{
  .lock = NXMUTEX_INITIALIZER,
};
#endif

#ifdef CONFIG_LPC54_SPI7_MASTER
static const struct spi_ops_s g_spi7_ops =
{
  .lock              = lpc54_spi_lock,
  .select            = lpc54_spi7_select,   /* Provided externally */
  .setfrequency      = lpc54_spi_setfrequency,
  .setmode           = lpc54_spi_setmode,
  .setbits           = lpc54_spi_setbits,
#ifdef CONFIG_SPI_HWFEATURES
  .hwfeatures        = NULL,                /* Not supported */
#endif
  .status            = lpc54_spi7_status,   /* Provided externally */
#ifdef CONFIG_SPI_CMDDATA
  .cmddata           = lpc54_spi7_cmddata,  /* Provided externally */
#endif
  .send              = lpc54_spi_send,
#ifdef CONFIG_SPI_EXCHANGE
  .exchange          = lpc54_spi_exchange,
#else
  .sndblock          = lpc54_spi_sndblock,
  .recvblock         = lpc54_spi_recvblock,
#endif
#ifdef CONFIG_SPI_CALLBACK
  .registercallback  = lpc54_spi7_register, /* Provided externally */
#else
  .registercallback  = NULL,                /* Not implemented */
#endif
};

static struct lpc54_spidev_s g_spi7_dev =
{
  .lock = NXMUTEX_INITIALIZER,
};
#endif

#ifdef CONFIG_LPC54_SPI8_MASTER
static const struct spi_ops_s g_spi8_ops =
{
  .lock              = lpc54_spi_lock,
  .select            = lpc54_spi8_select,   /* Provided externally */
  .setfrequency      = lpc54_spi_setfrequency,
  .setmode           = lpc54_spi_setmode,
  .setbits           = lpc54_spi_setbits,
#ifdef CONFIG_SPI_HWFEATURES
  .hwfeatures        = NULL,                /* Not supported */
#endif
  .status            = lpc54_spi8_status,   /* Provided externally */
#ifdef CONFIG_SPI_CMDDATA
  .cmddata           = lpc54_spi8_cmddata,  /* Provided externally */
#endif
  .send              = lpc54_spi_send,
#ifdef CONFIG_SPI_EXCHANGE
  .exchange          = lpc54_spi_exchange,
#else
  .sndblock          = lpc54_spi_sndblock,
  .recvblock         = lpc54_spi_recvblock,
#endif
#ifdef CONFIG_SPI_CALLBACK
  .registercallback  = lpc54_spi8_register, /* Provided externally */
#else
  .registercallback  = NULL,                /* Not implemented */
#endif
};

static struct lpc54_spidev_s g_spi8_dev =
{
  .lock = NXMUTEX_INITIALIZER,
};
#endif

#ifdef CONFIG_LPC54_SPI9_MASTER
static const struct spi_ops_s g_spi9_ops =
{
  .lock              = lpc54_spi_lock,
  .select            = lpc54_spi9_select,   /* Provided externally */
  .setfrequency      = lpc54_spi_setfrequency,
  .setmode           = lpc54_spi_setmode,
  .setbits           = lpc54_spi_setbits,
#ifdef CONFIG_SPI_HWFEATURES
  .hwfeatures        = NULL,                /* Not supported */
#endif
  .status            = lpc54_spi9_status,   /* Provided externally */
#ifdef CONFIG_SPI_CMDDATA
  .cmddata           = lpc54_spi9_cmddata,  /* Provided externally */
#endif
  .send              = lpc54_spi_send,
#ifdef CONFIG_SPI_EXCHANGE
  .exchange          = lpc54_spi_exchange,
#else
  .sndblock          = lpc54_spi_sndblock,
  .recvblock         = lpc54_spi_recvblock,
#endif
#ifdef CONFIG_SPI_CALLBACK
  .registercallback  = lpc54_spi9_register, /* Provided externally */
#else
  .registercallback  = NULL,                /* Not implemented */
#endif
};

static struct lpc54_spidev_s g_spi9_dev =
{
  .lock = NXMUTEX_INITIALIZER,
};
#endif

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: lpc54_spi_putreg
 *
 * Description:
 *   Write a value to a register at the offset from the Flexcomm base.
 *
 ****************************************************************************/

static inline void lpc54_spi_putreg(struct lpc54_spidev_s *priv,
                                    unsigned int regoffset, uint32_t regval)
{
  putreg32(regval, priv->base + regoffset);
}

/****************************************************************************
 * Name: lpc54_spi_getreg
 *
 * Description:
 *   Read the content of a register at the offset from the Flexcomm base.
 *
 ****************************************************************************/

static inline uint32_t lpc54_spi_getreg(struct lpc54_spidev_s *priv,
                                        unsigned int regoffset)
{
  return getreg32(priv->base + regoffset);
}

/****************************************************************************
 * Name: lpc54_spi_fifodepth
 *
 * Description:
 *   Return the depth of the SPI FIFOs.  This is a constant value and could
 *   be hard coded.
 *
 * Input Parameters:
 *   priv - Device-specific state data
 *
 * Returned Value:
 *   The FIFO depth in words of the configured bit width.
 *
 ****************************************************************************/

static inline size_t lpc54_spi_fifodepth(struct lpc54_spidev_s *priv)
{
  uint32_t regval = lpc54_spi_getreg(priv, LPC54_SPI_FIFOCFG_OFFSET);
  return ((regval & SPI_FIFOCFG_SIZE_MASK) >> SPI_FIFOCFG_SIZE_SHIFT) << 3;
}

/****************************************************************************
 * Name: lpc54_spi_txavailable
 *
 * Description:
 *   Return true if the Tx FIFO is not full.
 *
 * Input Parameters:
 *   priv - Device-specific state data
 *
 * Returned Value:
 *   true: Tx FIFO is not full.
 *
 ****************************************************************************/

static inline bool lpc54_spi_txavailable(struct lpc54_spidev_s *priv)
{
  uint32_t regval = lpc54_spi_getreg(priv, LPC54_SPI_FIFOSTAT_OFFSET);
  return ((regval & SPI_FIFOSTAT_TXNOTFULL) != 0);
}

/****************************************************************************
 * Name: lpc54_spi_rxavailable
 *
 * Description:
 *   Return true if the Rx FIFO is not empty.
 *
 * Input Parameters:
 *   priv - Device-specific state data
 *
 * Returned Value:
 *   true: Rx FIFO is not empty.
 *
 ****************************************************************************/

static inline bool lpc54_spi_rxavailable(struct lpc54_spidev_s *priv)
{
  uint32_t regval = lpc54_spi_getreg(priv, LPC54_SPI_FIFOSTAT_OFFSET);
  return ((regval & SPI_FIFOSTAT_RXNOTEMPTY) != 0);
}

/****************************************************************************
 * Name: lpc54_spi_rxdiscard
 *
 * Description:
 *   Read and discard the data until the Rx FIFO is empty.
 *
 * Input Parameters:
 *   priv - Device-specific state data
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void lpc54_spi_rxdiscard(struct lpc54_spidev_s *priv)
{
  while (lpc54_spi_rxavailable(priv))
    {
      lpc54_spi_getreg(priv, LPC54_SPI_FIFORD_OFFSET);
    }
}

/****************************************************************************
 * Name: lpc54_spi_resetfifos
 *
 * Description:
 *   Clear Tx/Rx errors and empty FIFOs.
 *
 * Input Parameters:
 *   priv - Device-specific state data
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void lpc54_spi_resetfifos(struct lpc54_spidev_s *priv)
{
  uint32_t regval;

  /* Clear Tx/Rx errors and empty FIFOs */

  regval  = lpc54_spi_getreg(priv, LPC54_SPI_FIFOCFG_OFFSET);
  regval |= (SPI_FIFOCFG_EMPTYTX | SPI_FIFOCFG_EMPTYRX);
  lpc54_spi_putreg(priv, LPC54_SPI_FIFOCFG_OFFSET, regval);

  regval  = lpc54_spi_getreg(priv, LPC54_SPI_FIFOSTAT_OFFSET);
  regval |= (SPI_FIFOSTAT_TXERR | SPI_FIFOSTAT_RXERR);
  lpc54_spi_putreg(priv, LPC54_SPI_FIFOSTAT_OFFSET, regval);
}

/****************************************************************************
 * Name: lpc54_spi_rxtransfer8 and lpc54_spi_rxtransfer16
 *
 * Description:
 *   Receive one 8-bit or 16-bit value from the selected SPI device.
 *
 * Input Parameters:
 *   priv - Device-specific state data
 *   xfr  - Describes the Rx transfer
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void lpc54_spi_rxtransfer8(struct lpc54_spidev_s *priv,
                                  struct lpc54_rxtransfer8_s *xfr)
{
  /* Read one byte if available and expected */

  if (lpc54_spi_rxavailable(priv))
    {
      /* There is something in the Rx FIFO to be read.  Are we expecting
       * data in the Rx FIFO?  Is there space available in the Rx buffer?
       */

      if (xfr->expected == 0 || xfr->remaining == 0)
        {
          /* No..
           * then just read and discard the data until the Rx FIFO is empty
           */

          lpc54_spi_rxdiscard(priv);
          xfr->expected = 0;
        }
      else
        {
          /* Read and transfer one byte */

          *xfr->rxptr = lpc54_spi_getreg(priv, LPC54_SPI_FIFORD_OFFSET);

          /* Update pointers and counts */

          xfr->rxptr++;
          xfr->remaining--;
          xfr->expected--;
        }
    }
}

#ifdef CONFIG_LPC54_SPI_WIDEDATA
static void lpc54_spi_rxtransfer16(struct lpc54_spidev_s *priv,
                                   struct lpc54_rxtransfer16_s *xfr)
{
  /* Read one HWord if available and expected */

  if (lpc54_spi_rxavailable(priv))
    {
      /* There is something in the Rx FIFO to be read.  Are we expecting
       * data in the Rx FIFO?  Is there space available in the Rx buffer?
       */

      if (xfr->expected == 0 || xfr->remaining == 0)
        {
          /* No.. then just read and discard the data until the Rx FIFO
           * is empty.
           */

          lpc54_spi_rxdiscard(priv);
          xfr->expected = 0;
        }
      else
        {
          /* Read and transfer HWord */

          *xfr->rxptr = lpc54_spi_getreg(priv, LPC54_SPI_FIFORD_OFFSET);

          /* Update pointers and counts */

          xfr->rxptr++;
          xfr->remaining--;
          xfr->expected--;
        }
    }
}
#endif

/****************************************************************************
 * Name: lpc54_spi_txtransfer8 and lpc54_spi_txtransfer16
 *
 * Description:
 *   Send one 8- or 16-bit value to the selected SPI device.
 *
 * Input Parameters:
 *   priv - Device-specific state data
 *   xfr  - Describes the Tx transfer
 *
 * Returned Value:
 *   true:  The value was added to the TxFIFO
 *
 ****************************************************************************/

static bool lpc54_spi_txtransfer8(struct lpc54_spidev_s *priv,
                                  struct lpc54_txtransfer8_s *xfr)
{
  uint32_t regval;

  /* Transmit if txFIFO is not full and there is more Tx data to be sent */

  if (lpc54_spi_txavailable(priv) && xfr->remaining > 0)
    {
      /* Get the next byte to be sent */

      regval = *xfr->txptr;

      /* And send it */

      regval |= xfr->txctrl;
      lpc54_spi_putreg(priv, LPC54_SPI_FIFOWR_OFFSET, regval);

      /* Update pointers and counts */

      xfr->txptr++;
      xfr->remaining--;

      return true;
    }

  return false;
}

#ifdef CONFIG_LPC54_SPI_WIDEDATA
static bool lpc54_spi_txtransfer16(struct lpc54_spidev_s *priv,
                                   struct lpc54_txtransfer16_s *xfr)
{
  uint32_t regval;

  /* Transmit if txFIFO is not full and there is more Tx data to be sent */

  if (lpc54_spi_txavailable(priv) && xfr->remaining > 0)
    {
      /* Get the next byte to be sent */

      regval = *xfr->txptr;

      /* And send it */

      regval |= xfr->txctrl;
      lpc54_spi_putreg(priv, LPC54_SPI_FIFOWR_OFFSET, regval);

      /* Update pointers and counts */

      xfr->txptr++;
      xfr->remaining--;

      return true;
    }

  return false;
}
#endif

/****************************************************************************
 * Name: lpc54_spi_txdummy
 *
 * Description:
 *   Send dummy Tx data when we really only care about the Rx data.
 *
 * Input Parameters:
 *   priv - Device-specific state data
 *   xfr  - Describes the Tx transfer
 *
 * Returned Value:
 *   true:  The dummy value was added to the TxFIFO
 *
 ****************************************************************************/

static bool lpc54_spi_txdummy(struct lpc54_spidev_s *priv,
                              struct lpc54_txdummy_s *xfr)
{
  /* Transmit if txFIFO is not full and there is more Tx data to be sent */

  if (lpc54_spi_txavailable(priv) && xfr->remaining > 0)
    {
      /* Send the dummy data */

      lpc54_spi_putreg(priv, LPC54_SPI_FIFOWR_OFFSET, xfr->txctrl);

      /* Update counts */

      xfr->remaining--;
      return true;
    }

  return false;
}

/****************************************************************************
 * Name: lpc54_spi_exchange8 and lpc54_spi_exchange16
 *
 * Description:
 *   Implements the SPI exchange method for the case of 8-bit and 16-bit
 *   transfers.
 *
 * Input Parameters:
 *   priv     - Device-specific state data
 *   txbuffer - A pointer to the buffer of data to be sent
 *   rxbuffer - A pointer to a buffer in which to receive data
 *   nwords   - the length of data to be exchanged in units of words.
 *              The wordsize is determined by the number of bits-per-word
 *              selected for the SPI interface.  If nbits <= 8, the data is
 *              packed into uint8_t's; if nbits >8, the data is packed into
 *              uint16_t's
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

#ifdef CONFIG_SPI_EXCHANGE
static void lpc54_spi_exchange8(struct lpc54_spidev_s *priv,
                                const void *txbuffer, void *rxbuffer,
                                size_t nwords)
{
  struct lpc54_rxtransfer8_s rxtransfer;
  struct lpc54_txtransfer8_s txtransfer;
  size_t depth;

  DEBUGASSERT(rxbuffer != NULL && txbuffer != NULL);

  /* Get the FIFO depth */

  depth = lpc54_spi_fifodepth(priv);

  /* Set up the transfer data */

  txtransfer.txctrl = SPI_FIFOWR_LEN(priv->nbits) | SPI_FIFOWR_TXSSELN_ALL;
  txtransfer.txptr = (uint8_t *)txbuffer;
  txtransfer.remaining = nwords;
  rxtransfer.rxptr = (uint8_t *)rxbuffer;
  rxtransfer.remaining = nwords;
  rxtransfer.expected = 0;

  /* Clear Tx/Rx errors and empty FIFOs */

  lpc54_spi_resetfifos(priv);

  /* Loop until all Tx data has been sent and until all Rx data has been
   * received.
   */

  while (txtransfer.remaining != 0 || rxtransfer.remaining != 0)
    {
      /* Transfer one byte from the Rx FIFO to the caller's Rx buffer */

      lpc54_spi_rxtransfer8(priv, &rxtransfer);

      /* If sending another byte would exceed the capacity of the Rx FIFO
       * then read-only until there space freed.
       */

      if (rxtransfer.expected < depth)
        {
          /* Attempt to transfer one byte from the caller's Tx buffer to
           * the Tx FIFO.
           */

          if (lpc54_spi_txtransfer8(priv, &txtransfer))
            {
              /* Increment the Rx expected count if successful */

              rxtransfer.expected++;
            }
        }
    }
}
#endif /* CONFIG_SPI_EXCHANGE */

#if defined(CONFIG_SPI_EXCHANGE) && defined(CONFIG_LPC54_SPI_WIDEDATA)
static void lpc54_spi_exchange16(struct lpc54_spidev_s *priv,
                                 const void *txbuffer,
                                 void *rxbuffer,
                                 size_t nwords)
{
  struct lpc54_rxtransfer16_s rxtransfer;
  struct lpc54_txtransfer16_s txtransfer;
  uint32_t regval;
  size_t depth;

  DEBUGASSERT(rxbuffer != NULL && ((uintptr_t)rxbuffer & 1) == 0);
  DEBUGASSERT(txbuffer != NULL && ((uintptr_t)txbuffer & 1) == 0);

  /* Get the FIFO depth */

  depth = lpc54_spi_fifodepth(priv);

  /* Set up the transfer data */

  txtransfer.txctrl = SPI_FIFOWR_LEN(priv->nbits) | SPI_FIFOWR_TXSSELN_ALL;
  txtransfer.txptr = (uint16_t *)txbuffer;
  txtransfer.remaining = nwords;
  rxtransfer.rxptr = (uint16_t *)rxbuffer;
  rxtransfer.remaining = nwords;
  rxtransfer.expected = 0;

  /* Clear Tx/Rx errors and empty FIFOs */

  lpc54_spi_resetfifos(priv);

  /* Loop until all Tx data has been sent and until all Rx data has been
   * received.
   */

  while (txtransfer.remaining || rxtransfer.remaining || rxtransfer.expected)
    {
      /* Transfer one HWord from the Rx FIFO to the caller's Rx buffer */

      lpc54_spi_rxtransfer16(priv, &rxtransfer);

      /* If sending another byte would exceed the capacity of the Rx FIFO
       * then read-only until there space freed.
       */

      if (rxtransfer.expected < depth)
        {
          /* Attempt to send one more byte */

          if (lpc54_spi_txtransfer16(priv, &txtransfer))
            {
              /* Increment the Rx expected count if successful */

              rxtransfer.expected++;
            }
        }
    }
}
#endif /* CONFIG_SPI_EXCHANGE && CONFIG_LPC54_SPI_WIDEDATA */

/****************************************************************************
 * Name: lpc54_spi_sndblock8 and lpc54_spi_sndblock16
 *
 * Description:
 *   Implements the SPI sndblock method for the case of 8- and 16-bit
 *   transfers.
 *
 * Input Parameters:
 *   priv   - Device-specific state data
 *   buffer - A pointer to the buffer of data to be sent
 *   nwords - the length of data to send from the buffer in number of words.
 *            The wordsize is determined by the number of bits-per-word
 *            selected for the SPI interface.  If nbits <= 8, the data is
 *            packed into uint8_t's; if nbits >8, the data is packed into
 *            uint16_t's
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void lpc54_spi_sndblock8(struct lpc54_spidev_s *priv,
                                const void *buffer, size_t nwords)
{
  struct lpc54_txtransfer8_s txtransfer;

  DEBUGASSERT(buffer != NULL);

  /* Set up the transfer data.  NOTE that we are ignoring returned Rx data */

  txtransfer.txctrl    = SPI_FIFOWR_RXIGNORE | SPI_FIFOWR_LEN(priv->nbits) |
                         SPI_FIFOWR_TXSSELN_ALL;
  txtransfer.txptr     = (uint8_t *)buffer;
  txtransfer.remaining = nwords;

  /* Clear Tx/Rx errors and empty FIFOs */

  lpc54_spi_resetfifos(priv);

  /* Loop until all Tx data has been sent */

  while (txtransfer.remaining != 0)
    {
      /* Attempt to transfer one byte from the caller's Tx buffer to the
       * Tx FIFO.
       */

      lpc54_spi_txtransfer8(priv, &txtransfer);
    }
}

#ifdef CONFIG_LPC54_SPI_WIDEDATA
static void lpc54_spi_sndblock16(struct lpc54_spidev_s *priv,
                                 const void *buffer, size_t nwords)
{
  struct lpc54_txtransfer16_s txtransfer;

  DEBUGASSERT(buffer != NULL);

  /* Set up the transfer data.  NOTE that we are ignoring returned Rx data */

  txtransfer.txctrl    = SPI_FIFOWR_RXIGNORE | SPI_FIFOWR_LEN(priv->nbits) |
                         SPI_FIFOWR_TXSSELN_ALL;
  txtransfer.txptr     = (uint16_t *)buffer;
  txtransfer.remaining = nwords;

  /* Clear Tx/Rx errors and empty FIFOs */

  lpc54_spi_resetfifos(priv);

  /* Loop until all Tx data has been sent and until all Rx data has been
   * received.
   */

  while (txtransfer.remaining != 0)
    {
      /* Attempt to transfer one byte from the caller's Tx buffer to the
       * Tx FIFO.
       */

      lpc54_spi_txtransfer16(priv, &txtransfer);
    }
}
#endif /* CONFIG_LPC54_SPI_WIDEDATA */

/****************************************************************************
 * Name: lpc54_spi_recvblock8 and lpc54_spi_recvblock16
 *
 * Description:
 *   Implements the SPI recvblock method for the case of 8- and 16-bit
 *   transfers.
 *
 * Input Parameters:
 *   priv   - Device-specific state data
 *   buffer - A pointer to the buffer in which to receive data
 *   nwords - the length of data that can be received in the buffer in
 *            number of words.  The wordsize is determined by the number of
 *            bits-per-word selected for the SPI interface.  If nbits <= 8,
 *            the data is packed into uint8_t's; if nbits >8, the data is
 *            packed into uint16_t's
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void lpc54_spi_recvblock8(struct lpc54_spidev_s *priv,
                                 void *buffer, size_t nwords)
{
  struct lpc54_rxtransfer8_s rxtransfer;
  struct lpc54_txdummy_s txtransfer;
  size_t depth;

  DEBUGASSERT(buffer != NULL);

  /* Get the FIFO depth */

  depth = lpc54_spi_fifodepth(priv);

  /* Set up the transfer data */

  txtransfer.txctrl    = SPI_DUMMYDATA8 | SPI_FIFOWR_LEN(priv->nbits) |
                         SPI_FIFOWR_TXSSELN_ALL;
  txtransfer.remaining = nwords;
  rxtransfer.rxptr     = (uint8_t *)buffer;
  rxtransfer.remaining = nwords;
  rxtransfer.expected  = 0;

  /* Clear Tx/Rx errors and empty FIFOs */

  lpc54_spi_resetfifos(priv);

  /* Loop until all Tx data has been sent and until all Rx data has been
   * received.
   */

  while (txtransfer.remaining != 0 || rxtransfer.remaining != 0)
    {
      /* Transfer one byte from the Rx FIFO to the caller's Rx buffer */

      lpc54_spi_rxtransfer8(priv, &rxtransfer);

      /* If sending another byte would exceed the capacity of the Rx FIFO
       * then read-only until there space freed.
       */

      if (rxtransfer.expected < depth)
        {
          /* Attempt to transfer one dummy byte to the Tx FIFO. */

          if (lpc54_spi_txdummy(priv, &txtransfer))
            {
              /* Increment the Rx expected count if successful */

              rxtransfer.expected++;
            }
        }
    }
}

#ifdef CONFIG_LPC54_SPI_WIDEDATA
static void lpc54_spi_recvblock16(struct lpc54_spidev_s *priv,
                                  void *buffer, size_t nwords)
{
  struct lpc54_rxtransfer16_s rxtransfer;
  struct lpc54_txdummy_s txtransfer;
  size_t depth;

  DEBUGASSERT(buffer != NULL);

  /* Get the FIFO depth */

  depth = lpc54_spi_fifodepth(priv);

  /* Set up the transfer data */

  txtransfer.txctrl    = SPI_DUMMYDATA16 | SPI_FIFOWR_LEN(priv->nbits) |
                         SPI_FIFOWR_TXSSELN_ALL;
  txtransfer.remaining = nwords;
  rxtransfer.rxptr     = (uint16_t *)rxbuffer;
  rxtransfer.remaining = nwords;
  rxtransfer.expected  = 0;

  /* Clear Tx/Rx errors and empty FIFOs */

  lpc54_spi_resetfifos(priv);

  /* Loop until all Tx data has been sent and until all Rx data has been
   * received.
   */

  while (txtransfer.remaining || rxtransfer.remaining || rxtransfer.expected)
    {
      /* Transfer one HWord from the Rx FIFO to the caller's Rx buffer */

      lpc54_spi_rxtransfer16(priv, &rxtransfer);

      /* If sending another byte would exceed the capacity of the Rx FIFO
       * then read-only until there space freed.
       */

      if (rxtransfer.expected < depth)
        {
          /* Attempt to transfer one dummy HWord to the Tx FIFO. */

          if (lpc54_spi_txdummy(priv, &txtransfer))
            {
              /* Increment the Rx expected count if successful */

              rxtransfer.expected++;
            }
        }
    }
}
#endif /* CONFIG_LPC54_SPI_WIDEDATA */

/****************************************************************************
 * Name: lpc54_spi_lock
 *
 * Description:
 *   On SPI buses where there are multiple devices, it will be necessary to
 *   lock SPI to have exclusive access to the buses for a sequence of
 *   transfers.  The bus should be locked before the chip is selected. After
 *   locking the SPI bus, the caller should then also call the setfrequency,
 *   setbits, and setmode methods to make sure that the SPI is properly
 *   configured for the device.  If the SPI bus is being shared, then it
 *   may have been left in an incompatible state.
 *
 * Input Parameters:
 *   dev  - Device-specific state data
 *   lock - true: Lock spi bus, false: unlock SPI bus
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static int lpc54_spi_lock(struct spi_dev_s *dev, bool lock)
{
  struct lpc54_spidev_s *priv = (struct lpc54_spidev_s *)dev;
  int ret;

  if (lock)
    {
      ret = nxmutex_lock(&priv->lock);
    }
  else
    {
      ret = nxmutex_unlock(&priv->lock);
    }

  return ret;
}

/****************************************************************************
 * Name: lpc54_spi_setfrequency
 *
 * Description:
 *   Set the SPI frequency.
 *
 * Input Parameters:
 *   dev -       Device-specific state data
 *   frequency - The SPI frequency requested
 *
 * Returned Value:
 *   Returns the actual frequency selected
 *
 ****************************************************************************/

static uint32_t lpc54_spi_setfrequency(struct spi_dev_s *dev,
                                       uint32_t frequency)
{
  struct lpc54_spidev_s *priv = (struct lpc54_spidev_s *)dev;
  uint32_t divider;
  uint32_t actual;
  uint32_t regval;

  /* Check if the requested frequency is the same as the current frequency
   * selection.
   */

  DEBUGASSERT(priv != NULL && frequency <= priv->fclock / 2);

  if (priv->frequency == frequency)
    {
      /* We are already at this frequency.  Return the actual. */

      return priv->actual;
    }

  /* Set the new SPI frequency */

  divider = priv->fclock / frequency;
  if (divider > 0x10000)
    {
      divider = 0x10000;
    }

  regval  = lpc54_spi_getreg(priv, LPC54_SPI_DIV_OFFSET);
  regval &= ~SPI_DIV_MASK;
  regval |= SPI_DIV(divider);
  lpc54_spi_putreg(priv, LPC54_SPI_DIV_OFFSET, regval);

  /* Calculate the actual frequency */

  actual  = priv->fclock / divider;

  /* Save the frequency setting */

  priv->frequency = frequency;
  priv->actual    = actual;

  spiinfo("Frequency %d->%d\n", frequency, actual);
  return actual;
}

/****************************************************************************
 * Name: lpc54_spi_setmode
 *
 * Description:
 *   Set the SPI mode. Optional.  See enum spi_mode_e for mode definitions
 *
 * Input Parameters:
 *   dev -  Device-specific state data
 *   mode - The SPI mode requested
 *
 * Returned Value:
 *   none
 *
 ****************************************************************************/

static void lpc54_spi_setmode(struct spi_dev_s *dev,
                              enum spi_mode_e mode)
{
  struct lpc54_spidev_s *priv = (struct lpc54_spidev_s *)dev;
  uint32_t regval;

  /* Has the mode changed? */

  if (mode != priv->mode)
    {
      /* Yes... Set the new mode */

      regval  = lpc54_spi_getreg(priv, LPC54_SPI_CFG_OFFSET);
      regval &= ~(SPI_CFG_CPHA | SPI_CFG_CPOL);

      switch (mode)
        {
        case SPIDEV_MODE0: /* CPOL=0; CPHA=0 */
          break;

        case SPIDEV_MODE1: /* CPOL=0; CPHA=1 */
          regval |= SPI_CFG_CPHA;
          break;

        case SPIDEV_MODE2: /* CPOL=1; CPHA=0 */
          regval |= SPI_CFG_CPOL;
          break;

        case SPIDEV_MODE3: /* CPOL=1; CPHA=1 */
          regval |= (SPI_CFG_CPHA | SPI_CFG_CPOL);
          break;

        default:
          DEBUGPANIC();
          return;
        }

      lpc54_spi_putreg(priv, LPC54_SPI_CFG_OFFSET, regval);

      /* Save the mode so that subsequent re-configurations will be faster */

      priv->mode = mode;
    }
}

/****************************************************************************
 * Name: lpc54_spi_setbits
 *
 * Description:
 *   Set the number if bits per word.
 *
 * Input Parameters:
 *   dev   -  Device-specific state data
 *   nbits - The number of bits requests
 *
 * Returned Value:
 *   none
 *
 ****************************************************************************/

static void lpc54_spi_setbits(struct spi_dev_s *dev, int nbits)
{
  struct lpc54_spidev_s *priv = (struct lpc54_spidev_s *)dev;

  /* The valid range of bit selections is SPI_MINWIDTH through SPI_MAXWIDTH */

  DEBUGASSERT(priv != NULL && nbits >= SPI_MINWIDTH &&
              nbits <= SPI_MAXWIDTH);

  if (nbits >= SPI_MINWIDTH && nbits <= SPI_MAXWIDTH)
    {
      /* Save the selection.  It will be applied when data is transferred. */

      priv->nbits = nbits;
    }
}

/****************************************************************************
 * Name: lpc54_spi_send
 *
 * Description:
 *   Exchange one word on SPI
 *
 * Input Parameters:
 *   dev - Device-specific state data
 *   wd  - The word to send.  the size of the data is determined by the
 *         number of bits selected for the SPI interface.
 *
 * Returned Value:
 *   response
 *
 ****************************************************************************/

static uint32_t lpc54_spi_send(struct spi_dev_s *dev, uint32_t wd)
{
  struct lpc54_spidev_s *priv = (struct lpc54_spidev_s *)dev;
  uint32_t regval;

  DEBUGASSERT(priv != NULL);

  /* Clear Tx/Rx errors and empty FIFOs */

  lpc54_spi_resetfifos(priv);

  /* Send the word.  Since we just reset the FIFOs, we assume that the Tx
   * FIFO is not full and that the Rx FIFO is empty.
   */

  DEBUGASSERT(lpc54_spi_txavailable(priv) || !lpc54_spi_rxavailable(priv));

  regval = wd | SPI_FIFOWR_LEN(priv->nbits) | SPI_FIFOWR_TXSSELN_ALL;
  lpc54_spi_putreg(priv, LPC54_SPI_FIFOWR_OFFSET, regval);

  /* Wait for the Rx FIFO to become non-empty. */

  while (!lpc54_spi_rxavailable(priv))
    {
    }

  /* Then read and return the value from the Rx FIFO */

  return lpc54_spi_getreg(priv, LPC54_SPI_FIFORD_OFFSET);
}

/****************************************************************************
 * Name: lpc54_spi_exchange
 *
 * Description:
 *   Exchange a block of data on SPI
 *
 * Input Parameters:
 *   dev      - Device-specific state data
 *   txbuffer - A pointer to the buffer of data to be sent
 *   rxbuffer - A pointer to a buffer in which to receive data
 *   nwords   - the length of data to be exchanged in units of words.
 *              The wordsize is determined by the number of bits-per-word
 *              selected for the SPI interface.  If nbits <= 8, the data is
 *              packed into uint8_t's; if nbits >8, the data is packed into
 *              uint16_t's
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

#ifdef CONFIG_SPI_EXCHANGE
static void lpc54_spi_exchange(struct lpc54_spidev_s *priv,
                               const void *txbuffer, void *rxbuffer,
                               size_t nwords)
{
  struct lpc54_spidev_s *priv = (struct lpc54_spidev_s *)dev;

  DEBUGASSERT(priv != NULL);

  /* If there is no data sink, then handle this transfer with
   * lpc54_spi_sndblock().
   */

  if (rxbuffer == NULL)
    {
      lpc54_spi_sndblock(priv, txbuffer, nwords)
    }

  /* If there is no data source, then handle this transfer with
   * lpc54_spi_recvblock().
   */

  else if (txbuffer == NULL)
    {
      lpc54_spi_recvblock(priv, rxbuffer, nwords)
    }

#ifdef CONFIG_LPC54_SPI_WIDEDATA
  /* If the data with is > 8-bits, then handle this transfer with
   * lpc54_spi_exchange16().
   */

  else if (priv->nbits > 8)
    {
      lpc54_spi_exchange16(priv, txbuffer, rxbuffer, nwords);
    }
#endif

  /* Otherwise, let lpc54_spi_exchange8() do the job */

  else if (priv->nbits > 8)
    {
      lpc54_spi_exchange8(priv, txbuffer, rxbuffer, nwords);
    }
}
#endif /* CONFIG_SPI_EXCHANGE */

/****************************************************************************
 * Name: lpc54_spi_sndblock
 *
 * Description:
 *   Send a block of data on SPI
 *
 * Input Parameters:
 *   dev -    Device-specific state data
 *   buffer - A pointer to the buffer of data to be sent
 *   nwords - the length of data to send from the buffer in number of words.
 *            The wordsize is determined by the number of bits-per-word
 *            selected for the SPI interface.  If nbits <= 8, the data is
 *            packed into uint8_t's; if nbits >8, the data is packed into
 *            uint16_t's
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void lpc54_spi_sndblock(struct spi_dev_s *dev,
                               const void *buffer, size_t nwords)
{
  struct lpc54_spidev_s *priv = (struct lpc54_spidev_s *)dev;

  spiinfo("buffer=%p nwords=%d\n", buffer, nwords);
  DEBUGASSERT(priv != NULL && buffer != NULL);

#ifdef CONFIG_LPC54_SPI_WIDEDATA
  /* If the data with is > 8-bits, then handle this transfer with
   * lpc54_spi_sndblock16().
   */

  if (priv->nbits > 8)
    {
      lpc54_spi_sndblock16(priv, buffer, nwords);
    }

  /* Otherwise, let lpc54_spi_sndblock8() do the job */

  else if (priv->nbits > 8)
#endif
    {
      lpc54_spi_sndblock8(priv, buffer, nwords);
    }
}

/****************************************************************************
 * Name: lpc54_spi_recvblock
 *
 * Description:
 *   Revice a block of data from SPI
 *
 * Input Parameters:
 *   dev -    Device-specific state data
 *   buffer - A pointer to the buffer in which to receive data
 *   nwords - the length of data that can be received in the buffer in
 *            number of words.  The wordsize is determined by the number of
 *            bits-per-word selected for the SPI interface.  If nbits <= 8,
 *            the data is packed into uint8_t's; if nbits >8, the data is
 *            packed into uint16_t's
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void lpc54_spi_recvblock(struct spi_dev_s *dev, void *buffer,
                                size_t nwords)
{
  struct lpc54_spidev_s *priv = (struct lpc54_spidev_s *)dev;

  spiinfo("buffer=%p nwords=%d\n", buffer, nwords);
  DEBUGASSERT(priv != NULL && buffer != NULL);

#ifdef CONFIG_LPC54_SPI_WIDEDATA
  /* If the data with is > 8-bits, then handle this transfer with
   * lpc54_spi_recvblock16().
   */

  if (priv->nbits > 8)
    {
      lpc54_spi_recvblock16(priv, buffer, nwords);
    }

  /* Otherwise, let lpc54_spi_recvblock8() do the job */

  else if (priv->nbits > 8)
#endif
    {
      lpc54_spi_recvblock8(priv, buffer, nwords);
    }
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: lpc54_spibus_initialize
 *
 * Description:
 *   Initialize the selected SPI port
 *   0 - SPI0
 *   1 - SPI1
 *   ...
 *   9 - SSP9
 *
 * Input Parameters:
 *   port - SPI peripheral number.  0..9
 *
 * Returned Value:
 *   Valid SPI device structure reference on success; a NULL on failure
 *
 ****************************************************************************/

struct spi_dev_s *lpc54_spibus_initialize(int port)
{
  struct lpc54_spidev_s *priv;
  irqstate_t flags;
  uint32_t regval;

  flags = enter_critical_section();

  /* Configure the requested SPI peripheral */

  /* NOTE:  The basic FLEXCOMM initialization was performed in
   * lpc54_lowputc.c.
   */

#ifdef CONFIG_LPC54_SPI0_MASTER
  if (port == 0)
    {
      /* Attach 12 MHz clock to FLEXCOMM0 */

      lpc54_flexcomm0_enableclk();

      /* Set FLEXCOMM0 to the SPI peripheral, locking that configuration
       * in place.
       */

      putreg32(FLEXCOMM_PSELID_PERSEL_SPI | FLEXCOMM_PSELID_LOCK,
               LPC54_FLEXCOMM0_PSELID);

      /* Initialize the state structure */

      priv           = &g_spi0_dev;
      priv->base     = LPC54_FLEXCOMM0_BASE;
      priv->irq      = LPC54_IRQ_FLEXCOMM0;
      priv->fclock   = BOARD_FLEXCOMM0_FCLK;
      priv->dev.ops  = &g_spi0_ops;

      /* Configure SPI pins (defined in board.h) */

      lpc54_gpio_config(GPIO_SPI0_SCK);
      lpc54_gpio_config(GPIO_SPI0_MOSI);
      lpc54_gpio_config(GPIO_SPI0_MISO);

      /* Set up the FLEXCOMM0 function clock */

      putreg32(BOARD_FLEXCOMM0_CLKSEL, LPC54_SYSCON_FCLKSEL0);
    }
  else
#endif
#ifdef CONFIG_LPC54_SPI1_MASTER
  if (port == 1)
    {
      /* Attach 12 MHz clock to FLEXCOMM1 */

      lpc54_flexcomm1_enableclk();

      /* Set FLEXCOMM1 to the SPI peripheral, locking that configuration
       * in place.
       */

      putreg32(FLEXCOMM_PSELID_PERSEL_SPI | FLEXCOMM_PSELID_LOCK,
               LPC54_FLEXCOMM1_PSELID);

      /* Initialize the state structure */

      priv           = &g_spi1_dev;
      priv->base     = LPC54_FLEXCOMM1_BASE;
      priv->irq      = LPC54_IRQ_FLEXCOMM1;
      priv->fclock   = BOARD_FLEXCOMM1_FCLK;
      priv->dev.ops  = &g_spi1_ops;

      /* Configure SPI pins (defined in board.h) */

      lpc54_gpio_config(GPIO_SPI1_SCK);
      lpc54_gpio_config(GPIO_SPI1_MOSI);
      lpc54_gpio_config(GPIO_SPI1_MISO);

      /* Set up the FLEXCOMM1 function clock */

      putreg32(BOARD_FLEXCOMM1_CLKSEL, LPC54_SYSCON_FCLKSEL1);
    }
  else
#endif
#ifdef CONFIG_LPC54_SPI2_MASTER
  if (port == 2)
    {
      /* Attach 12 MHz clock to FLEXCOMM2 */

      lpc54_flexcomm2_enableclk();

      /* Set FLEXCOMM2 to the SPI peripheral, locking that configuration
       * in place.
       */

      putreg32(FLEXCOMM_PSELID_PERSEL_SPI | FLEXCOMM_PSELID_LOCK,
               LPC54_FLEXCOMM2_PSELID);

      /* Initialize the state structure */

      priv           = &g_spi2_dev;
      priv->base     = LPC54_FLEXCOMM2_BASE;
      priv->irq      = LPC54_IRQ_FLEXCOMM2;
      priv->fclock   = BOARD_FLEXCOMM2_FCLK;
      priv->dev.ops  = &g_spi2_ops;

      /* Configure SPI pins (defined in board.h) */

      lpc54_gpio_config(GPIO_SPI2_SCK);
      lpc54_gpio_config(GPIO_SPI2_MOSI);
      lpc54_gpio_config(GPIO_SPI2MISO);

      /* Set up the FLEXCOMM2 function clock */

      putreg32(BOARD_FLEXCOMM2_CLKSEL, LPC54_SYSCON_FCLKSEL2);
    }
  else
#endif
#ifdef CONFIG_LPC54_SPI3_MASTER
  if (port == 3)
    {
      /* Attach 12 MHz clock to FLEXCOMM3 */

      lpc54_flexcomm3_enableclk();

      /* Set FLEXCOMM3 to the SPI peripheral, locking that configuration
       * in place.
       */

      putreg32(FLEXCOMM_PSELID_PERSEL_SPI | FLEXCOMM_PSELID_LOCK,
               LPC54_FLEXCOMM3_PSELID);

      /* Initialize the state structure */

      priv           = &g_spi3_dev;
      priv->base     = LPC54_FLEXCOMM3_BASE;
      priv->irq      = LPC54_IRQ_FLEXCOMM3;
      priv->fclock   = BOARD_FLEXCOMM3_FCLK;
      priv->dev.ops  = &g_spi3_ops;

      /* Configure SPI pins (defined in board.h) */

      lpc54_gpio_config(GPIO_SPI3_SCK);
      lpc54_gpio_config(GPIO_SPI3_MOSI);
      lpc54_gpio_config(GPIO_SPI3_MISO);

      /* Set up the FLEXCOMM3 function clock */

      putreg32(BOARD_FLEXCOMM3_CLKSEL, LPC54_SYSCON_FCLKSEL3);
    }
  else
#endif
#ifdef CONFIG_LPC54_SPI4_MASTER
  if (port == 4)
    {
      /* Attach 12 MHz clock to FLEXCOMM4 */

      lpc54_flexcomm4_enableclk();

      /* Set FLEXCOMM4 to the SPI peripheral, locking that configuration
       * in place.
       */

      putreg32(FLEXCOMM_PSELID_PERSEL_SPI | FLEXCOMM_PSELID_LOCK,
               LPC54_FLEXCOMM4_PSELID);

      /* Initialize the state structure */

      priv           = &g_spi4_dev;
      priv->base     = LPC54_FLEXCOMM4_BASE;
      priv->irq      = LPC54_IRQ_FLEXCOMM4;
      priv->fclock   = BOARD_FLEXCOMM4_FCLK;
      priv->dev.ops  = &g_spi4_ops;

      /* Configure SPI pins (defined in board.h) */

      lpc54_gpio_config(GPIO_SPI4_SCK);
      lpc54_gpio_config(GPIO_SPI4_MOSI);
      lpc54_gpio_config(GPIO_SPI4_MISO);

      /* Set up the FLEXCOMM4 function clock */

      putreg32(BOARD_FLEXCOMM4_CLKSEL, LPC54_SYSCON_FCLKSEL4);
    }
  else
#endif
#ifdef CONFIG_LPC54_SPI5_MASTER
  if (port == 5)
    {
      /* Attach 12 MHz clock to FLEXCOMM5 */

      lpc54_flexcomm5_enableclk();

      /* Set FLEXCOMM5 to the SPI peripheral, locking that configuration
       * in place.
       */

      putreg32(FLEXCOMM_PSELID_PERSEL_SPI | FLEXCOMM_PSELID_LOCK,
               LPC54_FLEXCOMM5_PSELID);

      /* Initialize the state structure */

      priv           = &g_spi5_dev;
      priv->base     = LPC54_FLEXCOMM5_BASE;
      priv->irq      = LPC54_IRQ_FLEXCOMM5;
      priv->fclock   = BOARD_FLEXCOMM5_FCLK;
      priv->dev.ops  = &g_spi5_ops;

      /* Configure SPI pins (defined in board.h) */

      lpc54_gpio_config(GPIO_SPI5_SCK);
      lpc54_gpio_config(GPIO_SPI5_MOSI);
      lpc54_gpio_config(GPIO_SPI5_MISO);

      /* Set up the FLEXCOMM5 function clock */

      putreg32(BOARD_FLEXCOMM5_CLKSEL, LPC54_SYSCON_FCLKSEL5);
    }
  else
#endif
#ifdef CONFIG_LPC54_SPI6_MASTER
  if (port == 6)
    {
      /* Attach 12 MHz clock to FLEXCOMM6 */

      lpc54_flexcomm6_enableclk();

      /* Set FLEXCOMM6 to the SPI peripheral, locking that configuration
       * in place.
       */

      putreg32(FLEXCOMM_PSELID_PERSEL_SPI | FLEXCOMM_PSELID_LOCK,
               LPC54_FLEXCOMM6_PSELID);

      /* Initialize the state structure */

      priv           = &g_spi6_dev;
      priv->base     = LPC54_FLEXCOMM6_BASE;
      priv->irq      = LPC54_IRQ_FLEXCOMM6;
      priv->fclock   = BOARD_FLEXCOMM6_FCLK;
      priv->dev.ops  = &g_spi6_ops;

      /* Configure SPI pins (defined in board.h) */

      lpc54_gpio_config(GPIO_SPI6_SCK);
      lpc54_gpio_config(GPIO_SPI6_MOSI);
      lpc54_gpio_config(GPIO_SPI6_MISO);

      /* Set up the FLEXCOMM6 function clock */

      putreg32(BOARD_FLEXCOMM6_CLKSEL, LPC54_SYSCON_FCLKSEL6);
    }
  else
#endif
#ifdef CONFIG_LPC54_SPI7_MASTER
  if (port == 7)
    {
      /* Attach 12 MHz clock to FLEXCOMM7 */

      lpc54_flexcomm7_enableclk();

      /* Set FLEXCOMM7 to the SPI peripheral, locking that configuration
       * in place.
       */

      putreg32(FLEXCOMM_PSELID_PERSEL_SPI | FLEXCOMM_PSELID_LOCK,
               LPC54_FLEXCOMM7_PSELID);

      /* Initialize the state structure */

      priv           = &g_spi7_dev;
      priv->base     = LPC54_FLEXCOMM7_BASE;
      priv->irq      = LPC54_IRQ_FLEXCOMM7;
      priv->fclock   = BOARD_FLEXCOMM7_FCLK;
      priv->dev.ops  = &g_spi7_ops;

      /* Configure SPI pins (defined in board.h) */

      lpc54_gpio_config(GPIO_SPI7_SCK);
      lpc54_gpio_config(GPIO_SPI7_MOSI);
      lpc54_gpio_config(GPIO_SPI7_MISO);

      /* Set up the FLEXCOMM7 function clock */

      putreg32(BOARD_FLEXCOMM7_CLKSEL, LPC54_SYSCON_FCLKSEL7);
    }
  else
#endif
#ifdef CONFIG_LPC54_SPI8_MASTER
  if (port == 8)
    {
      /* Attach 12 MHz clock to FLEXCOMM8 */

      lpc54_flexcomm8_enableclk();

      /* Set FLEXCOMM8 to the SPI peripheral, locking that configuration
       * in place.
       */

      putreg32(FLEXCOMM_PSELID_PERSEL_SPI | FLEXCOMM_PSELID_LOCK,
               LPC54_FLEXCOMM8_PSELID);

      /* Initialize the state structure */

      priv           = &g_spi8_dev;
      priv->base     = LPC54_FLEXCOMM8_BASE;
      priv->irq      = LPC54_IRQ_FLEXCOMM8;
      priv->fclock   = BOARD_FLEXCOMM8_FCLK;
      priv->dev.ops  = &g_spi8_ops;

      /* Configure SPI pins (defined in board.h) */

      lpc54_gpio_config(GPIO_SPI8_SCK);
      lpc54_gpio_config(GPIO_SPI8_MOSI);
      lpc54_gpio_config(GPIO_SPI8_MISO);

      /* Set up the FLEXCOMM8 function clock */

      putreg32(BOARD_FLEXCOMM8_CLKSEL, LPC54_SYSCON_FCLKSEL8);
    }
  else
#endif
#ifdef CONFIG_LPC54_SPI9_MASTER
  if (port == 9)
    {
      /* Attach 12 MHz clock to FLEXCOMM9 */

      lpc54_flexcomm9_enableclk();

      /* Set FLEXCOMM9 to the SPI peripheral, locking that configuration
       * in place.
       */

      putreg32(FLEXCOMM_PSELID_PERSEL_SPI | FLEXCOMM_PSELID_LOCK,
               LPC54_FLEXCOMM9_PSELID);

      /* Initialize the state structure */

      priv           = &g_spi9_dev;
      priv->base     = LPC54_FLEXCOMM9_BASE;
      priv->irq      = LPC54_IRQ_FLEXCOMM9;
      priv->fclock   = BOARD_FLEXCOMM9_FCLK;
      priv->dev.ops  = &g_spi9_ops;

      /* Configure SPI pins (defined in board.h) */

      lpc54_gpio_config(GPIO_SPI9_SCK);
      lpc54_gpio_config(GPIO_SPI9_MOSI);
      lpc54_gpio_config(GPIO_SPI9_MISO);

      /* Set up the FLEXCOMM9 function clock */

      putreg32(BOARD_FLEXCOMM9_CLKSEL, LPC54_SYSCON_FCLKSEL9);
    }
  else
#endif
    {
      leave_critical_section(flags);
      return NULL;
    }

  leave_critical_section(flags);

  /* Set the initial SPI configuration */

  priv->frequency = 0;
  priv->nbits     = 8;
  priv->mode      = SPIDEV_MODE0;

  /* Configure master mode in mode 0:
   *
   * ENABLE    - Disabled for now      (0)
   * MASTER    - Master mode           (1)
   * LSBF      - MSB first             (0)
   * CPHA/CPOL - Mode 0                (0,0)
   * LOOP      - Disable loopback mode (0)
   * SPOLn     - Active low            (0,0,0)
   */

  regval  = lpc54_spi_getreg(priv, LPC54_SPI_CFG_OFFSET);
  regval &= ~(SPI_CFG_ENABLE | SPI_CFG_LSBF | SPI_CFG_CPHA | SPI_CFG_CPOL |
              SPI_CFG_LOOP | SPI_CFG_SPOL0 | SPI_CFG_SPOL1 | SPI_CFG_SPOL2 |
              SPI_CFG_SPOL3);
  regval |= SPI_CFG_MASTER;
  lpc54_spi_putreg(priv, LPC54_SPI_CFG_OFFSET, regval);

  /* Enable FIFOs */

  regval  = lpc54_spi_getreg(priv, LPC54_SPI_FIFOCFG_OFFSET);
  regval |= (SPI_FIFOCFG_EMPTYTX | SPI_FIFOCFG_EMPTYRX);
  lpc54_spi_putreg(priv, LPC54_SPI_FIFOCFG_OFFSET, regval);

  regval |= (SPI_FIFOCFG_ENABLETX | SPI_FIFOCFG_ENABLERX);
  lpc54_spi_putreg(priv, LPC54_SPI_FIFOCFG_OFFSET, regval);

  /* Set FIFO trigger levels:  Empty for Tx FIFO; 1 word for RxFIFO */

  regval  = lpc54_spi_getreg(priv, LPC54_SPI_FIFOCFG_OFFSET);
  regval &= ~(SPI_FIFOTRIG_TXLVL_MASK | SPI_FIFOTRIG_RXLVL_MASK);
  regval |= (SPI_FIFOTRIG_TXLVL_EMPTY | SPI_FIFOTRIG_RXLVL_NOTEMPTY);

  /* Enable generation of interrupts for selected FIFO trigger levels */

  regval |= (SPI_FIFOTRIG_TXLVLENA | SPI_FIFOTRIG_RXLVLENA);
  lpc54_spi_putreg(priv, LPC54_SPI_FIFOCFG_OFFSET, regval);

  /* Set the delay configuration (not used) */

  regval = (SPI_DLY_PRE_DELAY(0) | SPI_DLY_POST_DELAY(0) |
            SPI_DLY_FRAME_DELAY(0) | SPI_DLY_TRANSFER_DELAY(0));
  lpc54_spi_putreg(priv, LPC54_SPI_DLY_OFFSET, regval);

  /* Select a default frequency of approx. 400KHz */

  lpc54_spi_setfrequency((struct spi_dev_s *)priv, 400000);

  /* Enable the SPI peripheral */

  regval  = lpc54_spi_getreg(priv, LPC54_SPI_CFG_OFFSET);
  regval |= SPI_CFG_ENABLE;
  lpc54_spi_putreg(priv, LPC54_SPI_CFG_OFFSET, regval);

  return &priv->dev;
}

#endif /* HAVE_SPI_MASTER_DEVICE */
