blob: ae1dba2c542278de0b04394936a426a89783ee59 [file] [log] [blame]
/****************************************************************************
* arch/arm/src/max326xx/max32660/max32660_spim.c
*
* SPDX-License-Identifier: Apache-2.0
*
* 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.
*
****************************************************************************/
/****************************************************************************
* The external functions, max326_spi1/2/3select and max326_spi1/2/3status
* must be provided by board-specific logic. They are implementations of
* the select and status methods of the SPI interface defined by struct
* spi_ops_s (see include/nuttx/spi/spi.h). All other methods (including
* max326_spibus_initialize()) are provided by common MAX326 logic. To use
* this common SPI logic on your board:
*
* 1. Provide logic in max326_boardinitialize() to configure SPI chip
* select pins.
* 2. Provide max326_spi1/2/3select() and max326_spi1/2/3status() functions
* in your board-specific logic. These functions will perform chip
* selection and status operations using GPIOs in the way your board is
* configured.
* 3. Add a calls to max326_spibus_initialize() in your low level
* application initialization logic
* 4. The handle returned by max326_spibus_initialize() may then be used
* to bind the SPI driver to higher level logic (e.g., calling
* mmcsd_spislotinitialize(), for example, will bind the SPI driver to
* the SPI MMC/SD driver).
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <sys/types.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>
#include <nuttx/irq.h>
#include <nuttx/arch.h>
#include <nuttx/mutex.h>
#include <nuttx/spi/spi.h>
#include "arm_internal.h"
#include "chip.h"
#include "hardware/max326_pinmux.h"
#include "max326_clockconfig.h"
#include "max326_periphclks.h"
#include "max326_gpio.h"
#include "max326_dma.h"
#include "max326_spim.h"
#include <arch/board/board.h>
#if defined(CONFIG_MAX326XX_HAVE_SPIM)
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#ifdef CONFIG_MAX326_SPI_DMA
# error DMA not yet supported
#endif
#ifndef CONFIG_DEBUG_MEMCARD_INFO
# undef CONFIG_MAX326_SPI_REGDEBUG
#endif
/****************************************************************************
* Private Types
****************************************************************************/
struct max326_spidev_s
{
struct spi_dev_s dev; /* Externally visible part of the SPI interface */
const void *txbuffer; /* Tx buffer pointer (may be NULL) */
void *rxbuffer; /* Rx buffer pointer (may be NULL) */
uint32_t base; /* SPI base address */
uint32_t frequency; /* Requested clock frequency */
uint32_t actual; /* Actual clock frequency */
mutex_t lock; /* Held while chip is selected for mutual exclusion */
uint16_t rxbytes; /* Number of bytes received into rxbuffer */
uint16_t txbytes; /* Number of bytes sent from txbuffer */
uint16_t xfrlen; /* Transfer length */
bool initialized; /* True: SPI interface been initialized */
bool data16; /* True: 16- vs 8-bit data transfers */
bool wire3; /* True: 3- vs 4-pin mode */
bool busy; /* True: Transfer started */
#ifdef CONFIG_MAX326_SPI_INTERRUPTS
uint8_t irq; /* SPI IRQ number */
#endif
uint8_t nbits; /* Width of word in bits (4 through 16) */
uint8_t mode; /* Mode 0,1,2,3 */
#ifdef CONFIG_MAX326_SPI_REGDEBUG
/* Debug stuff */
bool wrlast; /* Last was a write */
uint32_t addrlast; /* Last address */
uint32_t vallast; /* Last value */
int ntimes; /* Number of times */
#endif
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/* Helpers */
#ifdef CONFIG_MAX326_SPI_REGDEBUG
static bool spi_checkreg(struct max326_spidev_s *priv, bool wr,
uint32_t regval, uintptr_t regaddr);
static uint32_t spi_getreg(struct max326_spidev_s *priv,
unsigned int offset);
static void spi_putreg(struct max326_spidev_s *priv,
unsigned int offset, uint32_t value);
#else
# define spi_checkreg(priv,wr,regval,regaddr) (false)
static inline uint32_t spi_getreg(struct max326_spidev_s *priv,
unsigned int offset);
static inline void spi_putreg(struct max326_spidev_s *priv,
unsigned int offset, uint32_t value);
#endif
static void spi_modify_ctrl0(struct max326_spidev_s *priv, uint32_t setbits,
uint32_t clrbits);
static void spi_modify_ctrl1(struct max326_spidev_s *priv, uint32_t setbits,
uint32_t clrbits);
static void spi_modify_ctrl2(struct max326_spidev_s *priv, uint32_t setbits,
uint32_t clrbits);
static void spi_modify_dma(struct max326_spidev_s *priv, uint32_t setbits,
uint32_t clrbits);
#ifdef CONFIG_MAX326_SPI_INTERRUPTS
static void spi_modify_inten(struct max326_spidev_s *priv, uint32_t setbits,
uint32_t clrbits)
#endif
/* Interrupt support */
static int spi_poll(struct max326_spidev_s *priv);
#ifdef CONFIG_MAX326_SPI_INTERRUPTS
static int spi_interrupt(int irq, void *context, void *arg);
#endif
/* SPI methods */
static int spi_lock(struct spi_dev_s *dev, bool lock);
#ifdef CONFIG_MAX326XX_SPIM0
static void spi0_select(struct spi_dev_s *dev, uint32_t devid,
bool selected);
#endif
static uint32_t spi_setfrequency(struct spi_dev_s *dev,
uint32_t frequency);
static void spi_setmode(struct spi_dev_s *dev, enum spi_mode_e mode);
static void spi_setbits(struct spi_dev_s *dev, int nbits);
#ifdef CONFIG_SPI_HWFEATURES
static int spi_hwfeatures(struct spi_dev_s *dev,
spi_hwfeatures_t features);
#endif
static uint32_t spi_send(struct spi_dev_s *dev, uint32_t wd);
static void spi_exchange(struct spi_dev_s *dev, const void *txbuffer,
void *rxbuffer, size_t nwords);
#ifndef CONFIG_SPI_EXCHANGE
static void spi_sndblock(struct spi_dev_s *dev, const void *txbuffer,
size_t nwords);
static void spi_recvblock(struct spi_dev_s *dev, void *rxbuffer,
size_t nwords);
#endif
/* Initialization */
static void spi_bus_initialize(struct max326_spidev_s *priv);
/****************************************************************************
* Private Data
****************************************************************************/
/* NOTE: This is somewhat over-designed since there is only a single SPI
* peripheral. However, it supports simple group to additional SPI
* peripherals by extending this logic.
*/
#ifdef CONFIG_MAX326XX_SPIM0
static const struct spi_ops_s g_sp0iops =
{
.lock = spi_lock,
.select = spi0_select,
.setfrequency = spi_setfrequency,
.setmode = spi_setmode,
.setbits = spi_setbits,
#ifdef CONFIG_SPI_HWFEATURES
.hwfeatures = spi_hwfeatures,
#endif
.status = max326_spi0status,
#ifdef CONFIG_SPI_CMDDATA
.cmddata = max326_spi0cmddata,
#endif
.send = spi_send,
#ifdef CONFIG_SPI_EXCHANGE
.exchange = spi_exchange,
#else
.sndblock = spi_sndblock,
.recvblock = spi_recvblock,
#endif
#ifdef CONFIG_SPI_CALLBACK
.registercallback = max326_spi0register, /* Provided externally */
#else
.registercallback = 0, /* Not implemented */
#endif
};
static struct max326_spidev_s g_spi0dev =
{
.dev =
{
.ops = &g_sp0iops,
},
.base = MAX326_SPI0_BASE,
.lock = NXMUTEX_INITIALIZER,
#ifdef CONFIG_MAX326_SPI_INTERRUPTS
.irq = MAX326_IRQ_SPI,
#endif
#ifdef CONFIG_MAX326XX_3WIRE
.wire3 = true,
#endif
};
#endif
/****************************************************************************
* Private Functions
****************************************************************************/
#ifdef CONFIG_MAX326_SPI_REGDEBUG
static bool spi_checkreg(struct max326_spidev_s *priv, bool wr,
uint32_t regval, uintptr_t regaddr)
{
if (wr == priv->wrlast && /* Same kind of access? */
regval == priv->vallast && /* Same value? */
regaddr == priv->addrlast) /* Same address? */
{
/* Yes, then just keep a count of the number of times we did this. */
priv->ntimes++;
return false;
}
else
{
/* Did we do the previous operation more than once? */
if (priv->ntimes > 0)
{
/* Yes... show how many times we did it */
mcinfo("...[Repeats %d times]...\n", priv->ntimes);
}
/* Save information about the new access */
priv->wrlast = wr;
priv->vallast = regval;
priv->addrlast = regaddr;
priv->ntimes = 0;
}
/* Return true if this is the first time that we have done this operation */
return true;
}
#endif
/****************************************************************************
* Name: spi_getreg
*
* Description:
* Get the contents of the SPI register at offset
*
* Input Parameters:
* priv - private SPI device structure
* offset - offset to the register of interest
*
* Returned Value:
* The contents of the 32-bit register
*
****************************************************************************/
#ifdef CONFIG_MAX326_SPI_REGDEBUG
static uint32_t spi_getreg(struct max326_spidev_s *priv, unsigned int offset)
{
uintptr_t regaddr = priv->base + offset;
uint32_t regval = getreg32(regaddr);
if (spi_checkreg(priv, false, regval, regaddr))
{
mcinfo("%08x->%08x\n", regaddr, regval);
}
return regval;
}
#else
static inline uint32_t spi_getreg(struct max326_spidev_s *priv,
unsigned int offset)
{
return getreg32(priv->base + offset);
}
#endif
/****************************************************************************
* Name: spi_putreg
*
* Description:
* Write a 16-bit value to the SPI register at offset
*
* Input Parameters:
* priv - Private SPI device structure
* offset - offset to the register of interest
* regval - the 32-bit value to be written
*
* Returned Value:
* The contents of the 16-bit register
*
****************************************************************************/
#ifdef CONFIG_MAX326_SPI_REGDEBUG
static void spi_putreg(struct max326_spidev_s *priv, unsigned int offset,
uint32_t regval)
{
uintptr_t regaddr = priv->base + offset;
if (spi_checkreg(priv, true, regval, regaddr))
{
mcinfo("%08x<-%08x\n", regaddr, regval);
}
putreg32(regval, regaddr);
}
#else
static inline void spi_putreg(struct max326_spidev_s *priv,
unsigned int offset, uint32_t regval)
{
putreg32(regval, priv->base + offset);
}
#endif
/****************************************************************************
* Name: spi_modify_ctrl0
*
* Description:
* Clear and set bits in the CTRL0 register
*
* Input Parameters:
* priv - Device-specific state data
* clrbits - The bits to clear
* setbits - The bits to set
*
* Returned Value:
* None
*
****************************************************************************/
static void spi_modify_ctrl0(struct max326_spidev_s *priv, uint32_t setbits,
uint32_t clrbits)
{
uint32_t ctrl0;
ctrl0 = spi_getreg(priv, MAX326_SPI_CTRL0_OFFSET);
ctrl0 &= ~clrbits;
ctrl0 |= setbits;
spi_putreg(priv, MAX326_SPI_CTRL0_OFFSET, ctrl0);
}
/****************************************************************************
* Name: spi_modify_ctrl1
*
* Description:
* Clear and set bits in the CTRL1 register
*
* Input Parameters:
* priv - Device-specific state data
* clrbits - The bits to clear
* setbits - The bits to set
*
* Returned Value:
* None
*
****************************************************************************/
static void spi_modify_ctrl1(struct max326_spidev_s *priv, uint32_t setbits,
uint32_t clrbits)
{
uint32_t ctrl1;
ctrl1 = spi_getreg(priv, MAX326_SPI_CTRL1_OFFSET);
ctrl1 &= ~clrbits;
ctrl1 |= setbits;
spi_putreg(priv, MAX326_SPI_CTRL1_OFFSET, ctrl1);
}
/****************************************************************************
* Name: spi_modify_ctrl2
*
* Description:
* Clear and set bits in the CTRL1 register
*
* Input Parameters:
* priv - Device-specific state data
* clrbits - The bits to clear
* setbits - The bits to set
*
* Returned Value:
* None
*
****************************************************************************/
static void spi_modify_ctrl2(struct max326_spidev_s *priv, uint32_t setbits,
uint32_t clrbits)
{
uint32_t ctrl2;
ctrl2 = spi_getreg(priv, MAX326_SPI_CTRL2_OFFSET);
ctrl2 &= ~clrbits;
ctrl2 |= setbits;
spi_putreg(priv, MAX326_SPI_CTRL2_OFFSET, ctrl2);
}
/****************************************************************************
* Name: spi_modify_dma
*
* Description:
* Clear and set bits in the DMA register
*
* Input Parameters:
* priv - Device-specific state data
* clrbits - The bits to clear
* setbits - The bits to set
*
* Returned Value:
* None
*
****************************************************************************/
static void spi_modify_dma(struct max326_spidev_s *priv, uint32_t setbits,
uint32_t clrbits)
{
uint32_t dma;
dma = spi_getreg(priv, MAX326_SPI_DMA_OFFSET);
dma &= ~clrbits;
dma |= setbits;
spi_putreg(priv, MAX326_SPI_DMA_OFFSET, dma);
}
/****************************************************************************
* Name: spi_modify_inten
*
* Description:
* Clear and set bits in the INTEN register
*
* Input Parameters:
* priv - Device-specific state data
* clrbits - The bits to clear
* setbits - The bits to set
*
* Returned Value:
* None
*
****************************************************************************/
#ifdef CONFIG_MAX326_SPI_INTERRUPTS
static void spi_modify_inten(struct max326_spidev_s *priv, uint32_t setbits,
uint32_t clrbits)
{
uint32_t inten;
inten = spi_getreg(priv, MAX326_SPI_INTEN_OFFSET);
inten &= ~clrbits;
inten |= setbits;
spi_putreg(priv, MAX326_SPI_INTEN_OFFSET, inten);
}
#endif
/****************************************************************************
* Name: spi_poll
*
* Description:
* Handle SPI events. This may be called repeatedly in polled mode or may
* be called from spi_interrupt() in interrupt mode.
*
* Returned Value:
* Returns OK when the transfer is complete, -EBUSY is the transfer is
* still in progress. Could also return other negated errno values if
* an error is detected.
*
****************************************************************************/
static int spi_poll(struct max326_spidev_s *priv)
{
const uint8_t *src;
uint8_t *dest;
uint32_t inten;
uint32_t length;
uint32_t regval;
uint32_t tmp;
int txavail;
int rxavail;
int remaining;
/* Get the transfer size in units of bytes */
length = priv->data16 ? priv->xfrlen << 1 : priv->xfrlen;
/* Is there a Tx source buffer? */
inten = 0;
if (priv->txbuffer != NULL)
{
/* Need to know when all bytes are transmitted. */
inten |= SPI_INT_TXEMPTY;
/* Calculate how many bytes we can write to the FIFO */
regval = spi_getreg(priv, MAX326_SPI_DMA_OFFSET);
tmp = (regval & SPI_DMA_TXFIFOCNT_MASK) >> SPI_DMA_TXFIFOCNT_SHIFT;
txavail = MAX326_SPI_FIFO_DEPTH - tmp;
if ((length - priv->txbytes) < txavail)
{
txavail = (length - priv->txbytes);
}
if (priv->data16)
{
txavail &= ~1;
}
/* Write Tx buffer data to the Tx FIFO */
src = &((const uint8_t *)priv->txbuffer)[priv->txbytes];
while (txavail > 0)
{
dest = (uint8_t *)(priv->base + MAX326_SPI_DATA_OFFSET);
if (txavail > 3)
{
*dest++ = *src++;
*dest++ = *src++;
*dest++ = *src++;
*dest = *src++;
txavail -= 4;
priv->txbytes += 4;
}
else if (txavail > 1)
{
*dest++ = *src++;
*dest = *src++;
txavail -= 2;
priv->txbytes += 2;
}
else if (!priv->data16)
{
*dest = *src++;
txavail -= 1;
priv->txbytes += 1;
}
}
}
remaining = length - priv->txbytes;
/* Set the TX interrupts */
if (remaining > 0)
{
if (remaining > MAX326_SPI_FIFO_DEPTH)
{
/* Set the TX FIFO almost empty interrupt if we have to refill */
regval = SPI_DMA_TXFIFOLVL(MAX326_SPI_FIFO_DEPTH);
}
else
{
regval = SPI_DMA_TXFIFOLVL(remaining);
}
spi_modify_dma(priv, regval, SPI_DMA_TXFIFOLVL_MASK);
inten |= SPI_INT_TXLEVEL;
}
/* Break out if we've transmitted all the bytes and not receiving */
if ((priv->rxbuffer == NULL) && priv->txbytes == length &&
(spi_getreg(priv, MAX326_SPI_DMA_OFFSET) &
SPI_DMA_TXFIFOCNT_MASK) == 0)
{
goto done;
}
/* Read from the RX FIFO */
if (priv->rxbuffer != NULL)
{
/* Wait for there to be data in the RX FIFO */
regval = spi_getreg(priv, MAX326_SPI_DMA_OFFSET);
rxavail = (regval & SPI_DMA_RXFIFOCNT_MASK) >> SPI_DMA_RXFIFOCNT_SHIFT;
if (length - priv->rxbytes < rxavail)
{
rxavail = length - priv->rxbytes;
}
dest = &((uint8_t *)priv->rxbuffer)[priv->rxbytes];
if (!priv->data16 || rxavail >= sizeof(uint16_t))
{
/* Read data from the Rx FIFO */
src = (const uint8_t *)(priv->base + MAX326_SPI_DATA_OFFSET);
while (rxavail > 0)
{
if (rxavail > 3)
{
*dest++ = *src++;
*dest++ = *src++;
*dest++ = *src++;
*dest++ = *src;
rxavail -= 4;
priv->rxbytes += 4;
}
else if (rxavail > 1)
{
*dest++ = *src++;
*dest++ = *src;
rxavail -= 2;
priv->rxbytes += 2;
}
else
{
*dest++ = *src;
rxavail -= 1;
priv->rxbytes += 1;
}
/* Don't read less than 2 bytes if we are using 16-bit data
* transfers.
*/
if (rxavail < sizeof(uint16_t) && priv->data16)
{
break;
}
}
}
remaining = length - priv->rxbytes;
if (remaining > 0)
{
if (remaining > MAX326_SPI_FIFO_DEPTH)
{
regval = SPI_DMA_RXFIFOLVL(2);
}
else
{
regval = SPI_DMA_RXFIFOLVL(remaining - 1);
}
spi_modify_dma(priv, regval, SPI_DMA_RXFIFOLVL_MASK);
inten |= SPI_INT_RXLEVEL;
}
/* Break out if we've received all the bytes and we're not
* transmitting.
*/
if (priv->txbuffer == NULL && priv->rxbytes == length)
{
goto done;
}
}
/* Break out once we've transmitted and received all of the data. */
if (priv->rxbytes == length && priv->txbytes == length)
{
regval = spi_getreg(priv, MAX326_SPI_DMA_OFFSET);
if ((regval & SPI_DMA_TXFIFOCNT_MASK) == 0)
{
goto done;
}
}
#ifdef CONFIG_MAX326_SPI_INTERRUPTS
/* Enable interrupts for the next phase */
spi_putreg(priv, MAX326_SPI_INTEN_OFFSET, inten);
#endif
/* Return busy to indicate that we are not finished with the transfer */
return -EBUSY;
done:
#ifdef CONFIG_MAX326_SPI_INTERRUPTS
/* We are done.. disable interrupts */
spi_putreg(priv, MAX326_SPI_INTEN_OFFSET, 0);
#endif
/* Return OK to indicate that we are finished with the transfer */
return OK;
}
/****************************************************************************
* Name: spi_interrupt
*
* Description:
* This is the SPI interrupt handler. It will be invoked when an
* interrupt received on the 'irq'.
*
****************************************************************************/
#ifdef CONFIG_MAX326_SPI_INTERRUPTS
static int spi_interrupt(int irq, void *context, void *arg)
{
struct max326_spidev_s *priv = (struct max326_spidev_s *)arg;
uint32_t intfl;
uint32_t regval;
unsigned int rxavail;
unsigned int rxlevel;
int ret;
/* Read pending interrupt flags, interrupt enables, and SPI status
* registers.
*/
intfl = spi_getreg(priv, MAX326_SPI_INTFL_OFFSET);
/* Disable all interrupts */
spi_putreg(priv, MAX326_SPI_INTEN_OFFSET, 0);
/* Clear pending interrupt flags */
max326_serialout(priv, MAX326_SPI_INTFL_OFFSET, intfl & SPI_INT_ALL);
/* Handle any active request */
if (intfl != 0)
{
do
{
ret = spi_poll(priv);
DEBUGASSERT(ret == OK || ret == -EBUSY);
/* Check if there is more Rx data to be read */
regval = spi_getreg(priv, MAX326_SPI_DMA_OFFSET);
rxavail = (regval & SPI_DMA_TXFIFOCNT_MASK) >>
SPI_DMA_TXFIFOCNT_SHIFT;
rxlevel = (regval & SPI_DMA_RXFIFOLVL_MASK) >>
SPI_DMA_RXFIFOLVL_SHIFT;
}
while (/* RX buffer != NULL && */ rxavail > rxlevel);
}
return OK;
}
#endif
/****************************************************************************
* Name: 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 spi_lock(struct spi_dev_s *dev, bool lock)
{
struct max326_spidev_s *priv = (struct max326_spidev_s *)dev;
int ret;
if (lock)
{
ret = nxmutex_lock(&priv->lock);
}
else
{
ret = nxmutex_unlock(&priv->lock);
}
return ret;
}
/****************************************************************************
* Name: SPI_SELECT
*
* Description:
* Enable/disable the SPI chip select. The implementation of this method
* must include handshaking: If a device is selected, it must hold off
* all other attempts to select the device until the device is deselected.
* Required.
*
* Input Parameters:
* dev - Device-specific state data
* devid - Identifies the device to select
* selected - true: slave selected, false: slave de-selected
*
* Returned Value:
* None
*
****************************************************************************/
#ifdef CONFIG_MAX326XX_SPIM0
static void spi0_select(struct spi_dev_s *dev, uint32_t devid, bool selected)
{
struct max326_spidev_s *priv;
/* Forward the call to the board-specific implementation */
DEBUGASSERT(dev != NULL);
max326_spi0select(dev, devid, selected);
/* The hardware requires that the SPI block be disabled and re-enabled at
* end of each transaction to cancel the on ongoing transaction (while
* chip select is inactive).
*/
if (!selected)
{
priv = (struct max326_spidev_s *)dev;
spi_modify_ctrl0(priv, 0, SPI_CTRL0_SPIEN);
}
}
#endif
/****************************************************************************
* Name: 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 spi_setfrequency(struct spi_dev_s *dev, uint32_t frequency)
{
struct max326_spidev_s *priv = (struct max326_spidev_s *)dev;
/* Has the frequency changed? */
if (frequency != priv->frequency)
{
uint32_t pclk;
uint32_t tmpbaud;
uint32_t actual;
uint32_t error;
uint32_t regval;
unsigned int tmpscale;
unsigned int tmphigh;
unsigned int scale;
unsigned int high;
/* The SPI clock derives from the PCLK:
*
* Fspi = Fpclk / (1 << CLKCFG.scale)
*
* The SPI high time is determined by Tspi and CLKCFG.high:
*
* Thi = Tspi * CLKCFG.high
*
* Similar the SPI low time id determined by Tspi and CLKCFG.low:
*
* Tlow = Tspi * CLKCFG.low
*
* The BAUD is the given by:
*
* Fbaud = 1 / (Thi + Tlow)
*
* If we assume that Thi == Tlow, then:
*
* Thi = Tspi * CLKCFG.high
* Fbaud = 1 / (2 * Thi)
*
* And
*
* Fbaud = Fpclk / CLKCFG.high / (1 << (CLKCFG.scale + 1)
* CLKCFG.high = Fpclk / (1 << (CLKCFG.scale + 1)) / Fbaud
*
* Example: Fpclk = 48MHz, Fbaud=1MHz
*
* scale CLKCFG.high Resulting Fbaud
* 0 24 1,000,0000 (exact)
* 1 12 1,000,0000 (exact)
* 2 6 1,000,0000 (exact)
* 3 3 1,000,0000 (exact)
* 4 1 1,500,0000
* 5 0 Invalid
* 6 0 Invalid
* 7 0 Invalid
* 8 0 Invalid
*
* Fbaud / 2 = Fspi / CLKCFG.high
* Fbaud = 2 * Fspi / CLKCFG.high
*/
/* There are only 9 possible values for CLKCFG.scale. Let's try them
* all and pick the best.
*/
pclk = max326_pclk_frequency();
DEBUGASSERT(frequency > 0 && frequency <= pclk);
error = UINT32_MAX;
for (tmpscale = 0; tmpscale < 9; tmpscale++)
{
tmphigh = (pclk / frequency) >> (tmpscale + 1);
if (tmphigh > 0)
{
uint32_t tmperr;
/* Calculate the frequency difference */
tmpbaud = (pclk / tmphigh) >> (tmpscale + 1);
if (tmpbaud > frequency)
{
tmperr = tmpbaud - frequency;
}
else
{
tmperr = frequency - tmpbaud;
}
/* Save the best result we find (or the lowest scale in case
* of ties).
*/
if (tmperr < error)
{
error = tmperr;
actual = tmpbaud;
scale = tmpscale;
high = tmphigh;
}
}
}
regval = spi_getreg(priv, MAX326_SPI_CLKCFG_OFFSET);
regval &= ~(SPI_CLKCFG_LO_MASK | SPI_CLKCFG_HI_MASK |
SPI_CLKCFG_SCALE_MASK);
regval |= (SPI_CLKCFG_LO(high) | SPI_CLKCFG_HI(high) |
SPI_CLKCFG_SCALE(scale));
spi_putreg(priv, MAX326_SPI_CLKCFG_OFFSET, regval);
/* Save the frequency selection so that subsequent reconfigurations
* will be faster.
*/
spiinfo("Frequency %d->%d\n", frequency, actual);
priv->frequency = frequency;
priv->actual = actual;
}
return priv->actual;
}
/****************************************************************************
* Name: spi_setmode
*
* Description:
* Set the SPI mode. see enum spi_mode_e for mode definitions
*
* Input Parameters:
* dev - Device-specific state data
* mode - The SPI mode requested
*
* Returned Value:
* Returns the actual frequency selected
*
****************************************************************************/
static void spi_setmode(struct spi_dev_s *dev, enum spi_mode_e mode)
{
struct max326_spidev_s *priv = (struct max326_spidev_s *)dev;
uint16_t setbits;
uint16_t clrbits;
spiinfo("mode=%d\n", mode);
/* Has the mode changed? */
if (mode != priv->mode)
{
/* Yes... Set CR1 appropriately */
switch (mode)
{
case SPIDEV_MODE0: /* CPOL=0; CPHA=0 */
setbits = 0;
clrbits = SPI_CTRL2_CLKPOL | SPI_CTRL2_CLKPHA;
break;
case SPIDEV_MODE1: /* CPOL=0; CPHA=1 */
setbits = SPI_CTRL2_CLKPHA;
clrbits = SPI_CTRL2_CLKPOL;
break;
case SPIDEV_MODE2: /* CPOL=1; CPHA=0 */
setbits = SPI_CTRL2_CLKPOL;
clrbits = SPI_CTRL2_CLKPHA;
break;
case SPIDEV_MODE3: /* CPOL=1; CPHA=1 */
setbits = SPI_CTRL2_CLKPOL | SPI_CTRL2_CLKPHA;
clrbits = 0;
break;
default:
return;
}
spi_modify_ctrl2(priv, setbits, clrbits);
/* Save the mode so that subsequent re-configurations will be
* faster.
*/
priv->mode = mode;
}
}
/****************************************************************************
* Name: spi_setbits
*
* Description:
* Set the number of bits per word.
*
* Input Parameters:
* dev - Device-specific state data
* nbits - The number of bits requested
*
* Returned Value:
* None
*
****************************************************************************/
static void spi_setbits(struct spi_dev_s *dev, int nbits)
{
struct max326_spidev_s *priv = (struct max326_spidev_s *)dev;
spiinfo("nbits=%d\n", nbits);
DEBUGASSERT(nbits > 0 && nbits <= 16);
/* Has the number of bits changed? */
if (nbits != priv->nbits)
{
/* Yes... Set CTRL2 appropriately */
spi_modify_ctrl2(priv, SPI_CTRL2_NUMBITS(nbits),
SPI_CTRL2_NUMBITS_MASK);
/* Will we have to do 16- or 8-bit data transfers? */
priv->data16 = (nbits > 8);
/* Save the selection so that subsequent re-configurations will be
* faster.
*/
priv->nbits = nbits;
}
}
/****************************************************************************
* Name: spi_hwfeatures
*
* Description:
* Set hardware-specific feature flags.
*
* Input Parameters:
* dev - Device-specific state data
* features - H/W feature flags
*
* Returned Value:
* Zero (OK) if the selected H/W features are enabled; A negated errno
* value if any H/W feature is not supportable.
*
****************************************************************************/
#ifdef CONFIG_SPI_HWFEATURES
static int spi_hwfeatures(struct spi_dev_s *dev, spi_hwfeatures_t features)
{
return -ENOSYS;
}
#endif
/****************************************************************************
* Name: 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 spi_send(struct spi_dev_s *dev, uint32_t wd)
{
uint32_t ret;
spiinfo("wd=%04u\n", wd);
spi_exchange(dev, &wd, &ret, 1);
return ret;
}
/****************************************************************************
* Name: 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
*
****************************************************************************/
static void spi_exchange(struct spi_dev_s *dev, const void *txbuffer,
void *rxbuffer, size_t nwords)
{
struct max326_spidev_s *priv = (struct max326_spidev_s *)dev;
#ifndef CONFIG_MAX326_SPI_INTERRUPTS
uint32_t regval;
#endif
int ret;
spiinfo("txbuffer=%p rxbuffer=%p nwords=%d\n", txbuffer, rxbuffer, nwords);
DEBUGASSERT(priv != NULL && nwords > 0 && nwords <= UINT16_MAX);
DEBUGASSERT(txbuffer != NULL || rxbuffer != NULL);
/* Setup for the transfer */
priv->txbuffer = txbuffer;
priv->rxbuffer = rxbuffer;
priv->txbytes = 0;
priv->rxbytes = 0;
priv->xfrlen = nwords;
priv->busy = false;
/* NOTE: The MAX326 slave select pin is not used. It is controlled as a
* GPIO output from the upper half driver.
*/
/* Setup the Rx number of words to exchange */
if (rxbuffer != NULL)
{
/* Note: If the SPI port is set to operate in 4-wire mode, the RXNUM
* field is ignored and the TXNUM field is used for both the number
* of words to receive or transmit.
*/
if (priv->wire3)
{
spi_modify_ctrl1(priv, SPI_CTRL1_RXNUMCH(nwords),
SPI_CTRL1_RXNUMCH_MASK);
}
else
{
spi_modify_ctrl1(priv, SPI_CTRL1_TXNUMCH(nwords),
SPI_CTRL1_TXNUMCH_MASK);
}
/* Enable the DMA Rx FIFO */
spi_modify_dma(priv, SPI_DMA_RXFIFOEN, 0);
}
else
{
/* No Rx data sink */
spi_modify_ctrl1(priv, 0, SPI_CTRL1_RXNUMCH_MASK);
spi_modify_dma(priv, 0, SPI_DMA_RXFIFOEN);
}
/* Four wire mode always operates in full duplex. We must have some Tx
* buffer available in all cases.
*/
if (!priv->wire3 && txbuffer == NULL)
{
size_t nbytes = priv->data16 ? nwords << 1 : nwords;
/* We will dual-purpose the the Rx buffer, initialized to zero */
memset(priv->rxbuffer, 0, nbytes);
priv->txbuffer = priv->rxbuffer;
}
/* Set up the number of Tx words to exchange */
if (priv->txbuffer != NULL)
{
spi_modify_ctrl1(priv, SPI_CTRL1_TXNUMCH(nwords),
SPI_CTRL1_TXNUMCH_MASK);
/* Enable the DMA Tx FIFO */
spi_modify_dma(priv, SPI_DMA_TXFIFOEN, 0);
}
else
{
/* No Tx data source */
spi_modify_dma(priv, 0, SPI_DMA_TXFIFOEN);
}
/* Flush the Rx and Tx FIFOs */
spi_modify_dma(priv, SPI_DMA_TXFIFOCLR | SPI_DMA_RXFIFOCLR, 0);
/* Clear pending interrupts */
regval = spi_getreg(priv, MAX326_SPI_INTFL_OFFSET);
spi_putreg(priv, MAX326_SPI_INTFL_OFFSET, regval);
/* Re-enable SPI to start the next transfer */
spi_modify_ctrl0(priv, SPI_CTRL0_SPIEN, 0);
/* Poll one time to setup the transfer */
ret = spi_poll(priv);
DEBUGASSERT(ret == OK || ret == -EBUSY);
/* Initiate data transmission */
spi_modify_ctrl0(priv, SPI_CTRL0_START, 0);
priv->busy = true;
#ifndef CONFIG_MAX326_SPI_INTERRUPTS
/* Poll repeatedly until the transfer is complete */
do
{
ret = spi_poll(priv);
}
while (ret != OK);
/* Then wait for the master done interrupt */
do
{
regval = spi_getreg(priv, MAX326_SPI_INTFL_OFFSET);
}
while ((regval & SPI_INT_MDONE) == 0);
#endif
}
/****************************************************************************
* Name: spi_sndblock
*
* Description:
* Send a block of data on SPI
*
* Input Parameters:
* dev - Device-specific state data
* txbuffer - 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
*
****************************************************************************/
#ifndef CONFIG_SPI_EXCHANGE
static void spi_sndblock(struct spi_dev_s *dev, const void *txbuffer,
size_t nwords)
{
spiinfo("txbuffer=%p nwords=%d\n", txbuffer, nwords);
spi_exchange(dev, txbuffer, NULL, nwords);
}
#endif
/****************************************************************************
* Name: spi_recvblock
*
* Description:
* Receive a block of data from SPI
*
* Input Parameters:
* dev - Device-specific state data
* rxbuffer - 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
*
****************************************************************************/
#ifndef CONFIG_SPI_EXCHANGE
static void spi_recvblock(struct spi_dev_s *dev, void *rxbuffer,
size_t nwords)
{
spiinfo("rxbuffer=%p nwords=%d\n", rxbuffer, nwords);
spi_exchange(dev, NULL, rxbuffer, nwords);
}
#endif
/****************************************************************************
* Name: spi_bus_initialize
*
* Description:
* Initialize the selected SPI bus in its default state (Master, 8-bit,
* mode 0, etc.)
*
* Input Parameters:
* priv - private SPI device structure
*
* Returned Value:
* None
*
****************************************************************************/
static void spi_bus_initialize(struct max326_spidev_s *priv)
{
uint32_t setbits;
uint32_t clrbits;
uint32_t regval;
/* Enable SPI */
spi_modify_ctrl0(priv, SPI_CTRL0_SPIEN, 0);
/* Setup slaved select timing (even in Master mode?) */
regval = (SPI_SSTIME_SSACT1(1) | SPI_SSTIME_SSACT2(1) |
SPI_SSTIME_SSINACT(1));
spi_putreg(priv, MAX326_SPI_SSTIME_OFFSET, regval);
/* Configure CTRL0. Default configuration:
* Mode 0: CTRL2: CLKPHA=0 and CLKPOL=0
* 8-bit: CTRL2: NUMBITS=8
*/
clrbits = SPI_CTRL2_CLKPHA | SPI_CTRL2_CLKPOL | SPI_CTRL2_NUMBITS_MASK;
setbits = SPI_CTRL2_NUMBITS(8);
spi_modify_ctrl2(priv, setbits, clrbits);
priv->frequency = 0;
priv->nbits = 8;
priv->mode = SPIDEV_MODE0;
/* Enable Master mode */
spi_modify_ctrl0(priv, SPI_CTRL0_MMEN, 0);
/* Select a default frequency of approx. 400KHz */
spi_setfrequency((struct spi_dev_s *)priv, 400000);
/* Set up 3- or 4-pin mode */
regval = priv->wire3 ? SPI_CTRL2_DATWIDTH_SINGLE : SPI_CTRL2_DATWIDTH_DUAL;
spi_modify_ctrl2(priv, regval, SPI_CTRL2_DATWIDTH_MASK);
/* Disable all interrupts at the peripheral */
spi_putreg(priv, MAX326_SPI_INTEN_OFFSET, 0);
/* Clear pending interrupts */
regval = spi_getreg(priv, MAX326_SPI_INTFL_OFFSET);
spi_putreg(priv, MAX326_SPI_INTFL_OFFSET, regval);
#ifdef CONFIG_MAX326_SPI_INTERRUPTS
/* Attach the interrupt handler and enable the IRQ at the NVIC.
* Interrupts are (probably) still disabled at the SPI peripheral.
*/
ret = irq_attach(priv->irq, spi_interrupt, priv);
if (ret == OK)
{
up_enable_irq(priv->irq);
}
#endif
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: max326_spibus_initialize
*
* Description:
* Initialize the selected SPI bus
*
* Input Parameters:
* Port number (for hardware that has multiple SPI interfaces)
*
* Returned Value:
* Valid SPI device structure reference on success; a NULL on failure
*
****************************************************************************/
struct spi_dev_s *max326_spibus_initialize(int bus)
{
struct max326_spidev_s *priv = NULL;
irqstate_t flags = enter_critical_section();
#ifdef CONFIG_MAX326XX_SPIM0
if (bus == 0)
{
/* Select SPI0 */
priv = &g_spi0dev;
/* Only configure if the bus is not already configured */
if (!priv->initialized)
{
/* Enable peripheral clocking */
max326_spi0_enableclk();
/* Configure SPI0 pins: SCK, MISO, and MOSI */
max326_gpio_config(GPIO_SPI0_SCK);
max326_gpio_config(GPIO_SPI0_MISO);
max326_gpio_config(GPIO_SPI0_MOSI);
/* Set up default configuration: Master, 8-bit, etc. */
spi_bus_initialize(priv);
priv->initialized = true;
}
}
else
#endif
{
spierr("ERROR: Unsupported SPI bus: %d\n", bus);
}
leave_critical_section(flags);
return (struct spi_dev_s *)priv;
}
#endif /* CONFIG_MAX326XX_HAVE_SPIM */