blob: 9622e7050e7456fd10cde695c617b7afdd2ffa1b [file] [log] [blame]
/****************************************************************************
* arch/arm/src/samv7/sam_serial_spi.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.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <sys/types.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>
#include <nuttx/kmalloc.h>
#include <nuttx/irq.h>
#include <nuttx/arch.h>
#include <nuttx/signal.h>
#include <nuttx/fs/ioctl.h>
#include <nuttx/spi/spi.h>
#include <arch/board/board.h>
#include "arm_internal.h"
#include "sam_config.h"
#include "hardware/sam_pinmap.h"
#include "hardware/sam_uart.h"
#include "sam_gpio.h"
#include "sam_periphclks.h"
#include "sam_serial_spi.h"
#ifdef CONFIG_SAMV7_USART_IS_SPI_MASTER
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define FAST_USART_CLOCK BOARD_MCK_FREQUENCY
#define SLOW_USART_CLOCK (BOARD_MCK_FREQUENCY >> 3)
#define SERIAL_SPI_MAX_DIVIDER 65534
#define SERIAL_SPI_MIN_DIVIDER 6
/****************************************************************************
* Private Types
****************************************************************************/
struct sam_serial_spi_pins_s
{
uint32_t mosi;
uint32_t miso;
uint32_t sck;
uint32_t nss;
};
struct sam_serial_spi_s
{
struct sam_serial_spi_pins_s pins;
uint32_t base; /* SPI controller register base address */
uint32_t frequency; /* Requested clock frequency */
uint32_t actual; /* Actual clock frequency */
uint8_t mode; /* Mode 0,1,2,3 */
uint8_t nbits; /* Width of word in bits (8 or 9) */
mutex_t spilock; /* Assures mutually exclusive access to SPI */
bool initialized; /* TRUE: Controller has been initialized */
};
/* The overall state of one SPI controller */
struct sam_spidev_s
{
const struct spi_ops_s *ops;
struct sam_serial_spi_s *priv;
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
static inline uint32_t serial_getreg(struct sam_serial_spi_s *priv,
int offset);
static inline void serial_putreg(struct sam_serial_spi_s *priv, int offset,
uint32_t value);
static inline void serial_flush(struct sam_serial_spi_s *priv);
/* SPI master methods */
static int serial_spi_lock(struct spi_dev_s *dev, bool lock);
static void serial_spi_select(struct spi_dev_s *dev, uint32_t devid,
bool selected);
static uint32_t serial_spi_setfrequency(struct spi_dev_s *dev,
uint32_t frequency);
static void serial_spi_setmode(struct spi_dev_s *dev, enum spi_mode_e mode);
static void serial_spi_setbits(struct spi_dev_s *dev, int nbits);
static uint8_t serial_spi_status(struct spi_dev_s *dev, uint32_t devid);
static uint32_t serial_spi_send(struct spi_dev_s *dev, uint32_t wd);
static void serial_spi_exchange(struct spi_dev_s *dev, const void *txbuffer,
void *rxbuffer, size_t nwords);
#ifndef CONFIG_SPI_EXCHANGE
static void serial_spi_sndblock(struct spi_dev_s *dev, const void *buffer,
size_t nwords);
static void serial_spi_recvblock(struct spi_dev_s *dev, void *buffer,
size_t nwords);
#endif
/****************************************************************************
* Private Data
****************************************************************************/
/* SERIAL_SPI driver operations */
static const struct spi_ops_s g_spiops =
{
.lock = serial_spi_lock,
.select = serial_spi_select,
.setfrequency = serial_spi_setfrequency,
.setmode = serial_spi_setmode,
.setbits = serial_spi_setbits,
.status = serial_spi_status,
#ifdef CONFIG_SPI_CMDDATA
.cmddata = NULL,
#endif
.send = serial_spi_send,
#ifdef CONFIG_SPI_EXCHANGE
.exchange = serial_spi_exchange,
#else
.sndblock = serial_spi_sndblock,
.recvblock = serial_spi_recvblock,
#endif
.registercallback = NULL,
};
/* This is the overall state of the SPI0 controller */
#ifdef CONFIG_SAMV7_USART0_SPI_MASTER
static struct sam_serial_spi_s sam_serial0spi_priv =
{
.pins =
{
.mosi = GPIO_USART0_TXD,
.miso = GPIO_USART0_RXD,
.sck = GPIO_USART0_SCK,
.nss = GPIO_USART0_RTS,
},
.base = SAM_USART0_BASE,
.actual = 0,
.mode = 0,
.nbits = 0,
.spilock = NXMUTEX_INITIALIZER,
.initialized = false,
};
#endif
#ifdef CONFIG_SAMV7_USART1_SPI_MASTER
static struct sam_serial_spi_s sam_serial1spi_priv =
{
.pins =
{
.mosi = GPIO_USART1_TXD,
.miso = GPIO_USART1_RXD,
.sck = GPIO_USART1_SCK,
.nss = GPIO_USART1_RTS,
},
.base = SAM_USART1_BASE,
.actual = 0,
.mode = 0,
.nbits = 0,
.spilock = NXMUTEX_INITIALIZER,
.initialized = false,
};
#endif
#ifdef CONFIG_SAMV7_USART2_SPI_MASTER
static struct sam_serial_spi_s sam_serial2spi_priv =
{
.pins =
{
.mosi = GPIO_USART2_TXD,
.miso = GPIO_USART2_RXD,
.sck = GPIO_USART2_SCK,
.nss = GPIO_USART2_RTS,
},
.base = SAM_USART2_BASE,
.actual = 0,
.mode = 0,
.nbits = 0,
.spilock = NXMUTEX_INITIALIZER,
.initialized = false,
};
#endif
/****************************************************************************
* Private Functions
****************************************************************************/
static inline uint32_t serial_getreg(struct sam_serial_spi_s *priv,
int offset)
{
return getreg32(priv->base + offset);
}
/****************************************************************************
* Name: sam_serialout
****************************************************************************/
static inline void serial_putreg(struct sam_serial_spi_s *priv, int offset,
uint32_t value)
{
putreg32(value, priv->base + offset);
}
/****************************************************************************
* Name: serial_flush
****************************************************************************/
static inline void serial_flush(struct sam_serial_spi_s *priv)
{
uint32_t status;
/* Make sure the no TX activity is in progress... waiting if necessary */
status = serial_getreg(priv, SAM_UART_SR_OFFSET);
while ((status & UART_INT_TXRDY) == 0)
{
nxsched_usleep(100);
status = serial_getreg(priv, SAM_UART_SR_OFFSET);
}
/* Then make sure that there is no pending RX data .. reading as
* discarding as necessary.
*/
while ((serial_getreg(priv, SAM_UART_SR_OFFSET) & UART_INT_RXRDY) != 0)
{
serial_getreg(priv, SAM_UART_RHR_OFFSET);
}
}
/****************************************************************************
* Name: serial_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 serial_spi_lock(struct spi_dev_s *dev, bool lock)
{
struct sam_spidev_s *spi = (struct sam_spidev_s *)dev;
struct sam_serial_spi_s *priv = (struct sam_serial_spi_s *)spi->priv;
int ret;
spiinfo("lock=%d\n", lock);
if (lock)
{
ret = nxmutex_lock(&priv->spilock);
}
else
{
ret = nxmutex_unlock(&priv->spilock);
}
return ret;
}
/****************************************************************************
* Name: serial_spi_select
*
* Description:
* This function does not actually set the chip select line. Rather, it
* simply maps the device ID into a chip select number and retains that
* chip select number for later use.
*
* Input Parameters:
* dev - Device-specific state data
* devid - Device ID
* selected - true if CS is selected
*
* Returned Value:
* None
*
****************************************************************************/
static void serial_spi_select(struct spi_dev_s *dev, uint32_t devid,
bool selected)
{
struct sam_spidev_s *spi = (struct sam_spidev_s *)dev;
struct sam_serial_spi_s *priv = (struct sam_serial_spi_s *)spi->priv;
/* There is only one CS. */
spiinfo("Chip select %d\n", selected);
if (selected)
{
serial_putreg(priv, SAM_UART_CR_OFFSET, UART_CR_FCS);
}
else
{
serial_putreg(priv, SAM_UART_CR_OFFSET, UART_CR_RCS);
}
}
/****************************************************************************
* Name: serial_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 serial_spi_setfrequency(struct spi_dev_s *dev,
uint32_t frequency)
{
struct sam_spidev_s *spi = (struct sam_spidev_s *)dev;
struct sam_serial_spi_s *priv = (struct sam_serial_spi_s *)spi->priv;
uint32_t intpart;
uint32_t regval;
uint32_t selected_clk;
uint32_t actual;
spiinfo("frequency=%ld\n", frequency);
if (priv->frequency == frequency)
{
return priv->actual;
}
/* Disable receiver and transmitter */
serial_putreg(priv, SAM_UART_CR_OFFSET, UART_CR_RXDIS | UART_CR_TXDIS);
/* Configure the SPI frequency/baud rate:
*
* divisor = selected clock / baud rate
*/
selected_clk = FAST_USART_CLOCK;
intpart = (selected_clk + (frequency >> 1)) / frequency;
if ((intpart & ~UART_BRGR_CD_MASK) != 0)
{
/* Use the divided USART clock */
selected_clk = SLOW_USART_CLOCK;
intpart = (selected_clk + (frequency >> 1)) /
frequency;
/* Re-select the clock source */
regval = serial_getreg(priv, SAM_UART_MR_OFFSET);
regval &= ~UART_MR_USCLKS_MASK;
regval |= UART_MR_USCLKS_MCKDIV;
serial_putreg(priv, SAM_UART_MR_OFFSET, regval);
/* Value written in UART_BRGR_CD must be even to ensure a
* 50:50 mark of the SCK pin. This applies only if
* UART_MR_USCLKS_MCKDIV is selected
*/
if (intpart % 2 != 0)
{
intpart += 1;
}
}
/* Value written in UART_BRGR_CD greater or equal to 6. */
if (intpart < SERIAL_SPI_MIN_DIVIDER)
{
intpart = SERIAL_SPI_MIN_DIVIDER;
}
else if (intpart > SERIAL_SPI_MAX_DIVIDER)
{
intpart = SERIAL_SPI_MAX_DIVIDER;
}
regval = UART_BRGR_CD(intpart);
serial_putreg(priv, SAM_UART_BRGR_OFFSET, regval);
/* Enable receiver & transmitter */
serial_putreg(priv, SAM_UART_CR_OFFSET, UART_CR_RXEN | UART_CR_TXEN);
actual = selected_clk / intpart;
spiinfo("Frequency %ld->%ld\n", priv->frequency, actual);
priv->frequency = frequency;
priv->actual = actual;
return actual;
}
/****************************************************************************
* Name: serial_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 serial_spi_setmode(struct spi_dev_s *dev, enum spi_mode_e mode)
{
struct sam_spidev_s *spi = (struct sam_spidev_s *)dev;
struct sam_serial_spi_s *priv = (struct sam_serial_spi_s *)spi->priv;
uint32_t regval;
spiinfo("mode=%d\n", mode);
/* Perform operation only if mode has changed. */
if (mode != priv->mode)
{
/* Yes. Set the mode:
*
* MODE
* SPI CPOL NCPHA
* 0 0 1
* 1 0 0
* 2 1 1
* 3 1 0
*/
regval = serial_getreg(priv, SAM_UART_MR_OFFSET);
regval &= ~(UART_MR_CPOL | UART_MR_CPHA);
switch (mode)
{
case SPIDEV_MODE0: /* CPOL=0; CPHA=1 */
regval |= UART_MR_CPHA;
break;
case SPIDEV_MODE1: /* CPOL=0; CPHA=0 */
break;
case SPIDEV_MODE2: /* CPOL=1; CPHA=1 */
regval |= UART_MR_CPOL | UART_MR_CPHA;
break;
case SPIDEV_MODE3: /* CPOL=1; CPHA=0 */
regval |= UART_MR_CPOL;
break;
default:
DEBUGASSERT(FALSE);
return;
}
serial_putreg(priv, SAM_UART_MR_OFFSET, regval);
/* Save the mode so that subsequent re-configurations will be faster */
priv->mode = mode;
}
}
/****************************************************************************
* Name: serial_spi_setbits
*
* Description:
* Set the number if bits per word.
*
* Input Parameters:
* dev - Device-specific state data
* nbits - The number of bits requested
*
* Returned Value:
* none
*
****************************************************************************/
static void serial_spi_setbits(struct spi_dev_s *dev, int nbits)
{
/* Only 8 bit transfer is supported. */
spiinfo("Only 8 bit transfer is supported on USART SPI.\n");
}
/****************************************************************************
* Name: serial_spi_status
*
* Description:
* Return status information associated with the SPI device.
*
* Input Parameters:
* devid - Identifies the (logical) device
*
* Returned Value:
* Bit-encoded SPI status (see include/nuttx/spi/spi.h.
*
****************************************************************************/
static uint8_t serial_spi_status(struct spi_dev_s *dev, uint32_t devid)
{
return 0;
}
/****************************************************************************
* Name: serial_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 serial_spi_send(struct spi_dev_s *dev, uint32_t wd)
{
uint8_t txbyte;
uint8_t rxbyte;
txbyte = (uint8_t)wd;
rxbyte = (uint8_t)0;
serial_spi_exchange(dev, &txbyte, &rxbyte, 1);
spierr("Sent %02x received %02x\n", txbyte, rxbyte);
return (uint32_t)rxbyte;
}
/****************************************************************************
* Name: serial_spi_exchange
*
* Description:
* Exchange a block of data from SPI.
*
* Input Parameters:
* dev - Device-specific state data
* txbuffer - A pointer to the buffer of data to be sent
* rxbuffer - A pointer to the buffer in which to receive data
* nwords - the length of data that 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 serial_spi_exchange(struct spi_dev_s *dev, const void *txbuffer,
void *rxbuffer, size_t nwords)
{
struct sam_spidev_s *spi = (struct sam_spidev_s *)dev;
struct sam_serial_spi_s *priv = (struct sam_serial_spi_s *)spi->priv;
uint32_t data;
uint32_t status;
uint8_t *rxptr;
uint8_t *txptr;
spiinfo("txbuffer=%p rxbuffer=%p nwords=%d\n", txbuffer, rxbuffer, nwords);
/* Set up working pointers */
rxptr = (uint8_t *)rxbuffer;
txptr = (uint8_t *)txbuffer;
/* Make sure that any previous transfer is flushed from the hardware */
serial_flush(priv);
for (; nwords > 0; nwords--)
{
/* Get the data to send (0xff if there is no data source). */
if (txptr)
{
data = (uint32_t)*txptr++;
}
else
{
data = 0xffff;
}
/* Wait for any previous data written to the TDR to be transferred
* to the serializer.
*/
status = serial_getreg(priv, SAM_UART_SR_OFFSET);
while ((status & UART_INT_TXRDY) == 0)
{
nxsched_usleep(100);
status = serial_getreg(priv, SAM_UART_SR_OFFSET);
}
/* Write the data to transmitted to the Transmit Data Register (TDR) */
serial_putreg(priv, SAM_UART_THR_OFFSET, data);
/* Wait for the read data to be available in the RDR. */
status = serial_getreg(priv, SAM_UART_SR_OFFSET);
while ((status & UART_INT_RXRDY) == 0)
{
nxsched_usleep(100);
status = serial_getreg(priv, SAM_UART_SR_OFFSET);
}
/* Read the received data from the SPI Data Register. */
data = serial_getreg(priv, SAM_UART_RHR_OFFSET);
if (rxptr)
{
*rxptr++ = (uint8_t)data;
}
}
}
/****************************************************************************
* Name: serial_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
*
****************************************************************************/
#ifndef CONFIG_SPI_EXCHANGE
static void serial_spi_sndblock(struct spi_dev_s *dev, const void *buffer,
size_t nwords)
{
/* spi_exchange can do this. */
serial_spi_exchange(dev, buffer, NULL, nwords);
}
/****************************************************************************
* Name: serial_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 serial_spi_recvblock(struct spi_dev_s *dev, void *buffer,
size_t nwords)
{
/* spi_exchange can do this. */
serial_spi_exchange(dev, NULL, buffer, nwords);
}
#endif /* CONFIG_SPI_EXCHANGE */
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: sam_serial_spi_initialize
*
* Description:
* Initialize the selected SPI port in master mode
*
* Input Parameters:
* port - USART interface to be used (0-2)
*
* Returned Value:
* Valid SPI device structure reference on success; a NULL on failure
*
****************************************************************************/
struct spi_dev_s *sam_serial_spi_initialize(int port)
{
struct sam_serial_spi_s *priv;
struct sam_spidev_s *spi;
irqstate_t flags;
uint32_t regval;
spiinfo("Initializing USART%d as SPI\n", port);
DEBUGASSERT(port >= 0 && port < SAMV7_NUSART);
switch (port)
{
#ifdef CONFIG_SAMV7_USART0_SPI_MASTER
case 0:
sam_usart0_enableclk();
priv = &sam_serial0spi_priv;
break;
#endif
#ifdef CONFIG_SAMV7_USART1_SPI_MASTER
case 1:
sam_usart1_enableclk();
priv = &sam_serial1spi_priv;
break;
#endif
#ifdef CONFIG_SAMV7_USART2_SPI_MASTER
case 2:
sam_usart2_enableclk();
priv = &sam_serial2spi_priv;
break;
#endif
default:
spierr("ERROR: Incorrect port number %d\n", port);
return NULL;
}
spi = kmm_zalloc(sizeof(struct sam_spidev_s));
if (!spi)
{
spierr("ERROR: Could not allocate sam_spics_s structure!\n");
return NULL;
}
/* Select the SPI operations */
spi->ops = &g_spiops;
spi->priv = priv;
if (!priv->initialized)
{
flags = enter_critical_section();
sam_configgpio(priv->pins.mosi);
sam_configgpio(priv->pins.miso);
sam_configgpio(priv->pins.sck);
sam_configgpio(priv->pins.nss);
/* Disable write protection */
serial_putreg(priv, SAM_UART_WPMR_OFFSET, USART_WPMR_WPKEY);
serial_putreg(priv, SAM_UART_MR_OFFSET, 0);
serial_putreg(priv, SAM_UART_RTOR_OFFSET, 0);
serial_putreg(priv, SAM_UART_TTGR_OFFSET, 0);
/* Reset and disable receiver and transmitter */
serial_putreg(priv, SAM_UART_CR_OFFSET,
(UART_CR_RSTRX | UART_CR_RSTTX | UART_CR_RXDIS |
UART_CR_TXDIS));
/* Reset status bits */
serial_putreg(priv, SAM_UART_CR_OFFSET, UART_CR_RSTSTA);
leave_critical_section(flags);
/* Configure mode register. Set master mode, 8 bits and SPI Mode 0 */
regval = UART_MR_MODE_SPIMSTR | UART_MR_CLKO | UART_MR_CHRL_8BITS |
UART_MR_CPHA;
serial_putreg(priv, SAM_UART_MR_OFFSET, regval);
/* Enable receiver & transmitter */
serial_putreg(priv, SAM_UART_CR_OFFSET, (UART_CR_RXEN | UART_CR_TXEN));
spi->priv->mode = 0;
spi->priv->nbits = 8;
spi->priv->frequency = 0;
spi->priv->actual = 0;
priv->initialized = true;
}
return (struct spi_dev_s *)spi;
}
#endif /* CONFIG_SAMV7_USART_IS_SPI_MASTER */