arm/rp2040: Add RP2040 SPI device support

This commit is contained in:
Yuichi Nakamura 2021-03-05 23:11:37 +09:00 committed by Masayuki Ishikawa
parent a8d269df98
commit 2d7aabf13b
5 changed files with 1261 additions and 0 deletions

View file

@ -73,6 +73,29 @@ config RP2040_UART1_2STOP
endif
config RP2040_SPI
bool "SPI"
select SPI
if RP2040_SPI
config RP2040_SPI0
bool "SPI0"
config RP2040_SPI1
bool "SPI1"
config RP2040_SPI_DRIVER
bool "SPI character driver"
default y
select SPI_DRIVER
---help---
Build in support for a character driver at /dev/spi[N] that may be
used to perform SPI bus transfers from applications. The intent of
this driver is to support SPI testing.
endif
config RP2040_I2C
bool "I2C"
select I2C
@ -96,3 +119,25 @@ config RP2040_I2C_DRIVER
in any real driver application.
endif
menuconfig RP2040_SPISD
bool "SPI SD Card"
default n
select MMCSD_SPI
if RP2040_SPISD
config RP2040_SPISD_SLOT_NO
int "SPI SD Card Slot Number"
default 0
---help---
Select spi sd card slot number.
config RP2040_SPISD_SPI_CH
int "SPI channel number"
default 0
range 0 1
---help---
Select spi channel number to use spi sd card.
endif # SPISD Configuration

View file

@ -67,6 +67,10 @@ CHIP_CSRCS += rp2040_cpuidlestack.c
CHIP_CSRCS += rp2040_testset.c
endif
ifeq ($(CONFIG_RP2040_SPI),y)
CHIP_CSRCS += rp2040_spi.c
endif
ifeq ($(CONFIG_RP2040_I2C),y)
CHIP_CSRCS += rp2040_i2c.c
endif

View file

@ -0,0 +1,160 @@
/****************************************************************************
* arch/arm/src/rp2040/hardware/rp2040_spi.h
*
* Generated from rp2040.svd originally provided by
* Raspberry Pi (Trading) Ltd.
*
* Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
#ifndef __ARCH_ARM_SRC_RP2040_HARDWARE_RP2040_SPI_H
#define __ARCH_ARM_SRC_RP2040_HARDWARE_RP2040_SPI_H
/****************************************************************************
* Included Files
****************************************************************************/
#include "hardware/rp2040_memorymap.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* Register offsets *********************************************************/
#define RP2040_SPI_SSPCR0_OFFSET 0x000000 /* Control register 0 */
#define RP2040_SPI_SSPCR1_OFFSET 0x000004 /* Control register 1 */
#define RP2040_SPI_SSPDR_OFFSET 0x000008 /* Data register */
#define RP2040_SPI_SSPSR_OFFSET 0x00000c /* Status register */
#define RP2040_SPI_SSPCPSR_OFFSET 0x000010 /* Clock prescale register */
#define RP2040_SPI_SSPIMSC_OFFSET 0x000014 /* Interrupt mask set or clear register */
#define RP2040_SPI_SSPRIS_OFFSET 0x000018 /* Raw interrupt status register */
#define RP2040_SPI_SSPMIS_OFFSET 0x00001c /* Masked interrupt status register */
#define RP2040_SPI_SSPICR_OFFSET 0x000020 /* Interrupt clear register */
#define RP2040_SPI_SSPDMACR_OFFSET 0x000024 /* DMA control register */
#define RP2040_SPI_SSPPERIPHID0_OFFSET 0x000fe0 /* Peripheral identification registers */
#define RP2040_SPI_SSPPERIPHID1_OFFSET 0x000fe4 /* Peripheral identification registers */
#define RP2040_SPI_SSPPERIPHID2_OFFSET 0x000fe8 /* Peripheral identification registers */
#define RP2040_SPI_SSPPERIPHID3_OFFSET 0x000fec /* Peripheral identification registers */
#define RP2040_SPI_SSPPCELLID0_OFFSET 0x000ff0 /* PrimeCell identification registers */
#define RP2040_SPI_SSPPCELLID1_OFFSET 0x000ff4 /* PrimeCell identification registers */
#define RP2040_SPI_SSPPCELLID2_OFFSET 0x000ff8 /* PrimeCell identification registers */
#define RP2040_SPI_SSPPCELLID3_OFFSET 0x000ffc /* PrimeCell identification registers */
/* Register definitions *****************************************************/
#define RP2040_SPI_SSPCR0(n) (RP2040_SPI_BASE(n) + RP2040_SPI_SSPCR0_OFFSET)
#define RP2040_SPI_SSPCR1(n) (RP2040_SPI_BASE(n) + RP2040_SPI_SSPCR1_OFFSET)
#define RP2040_SPI_SSPDR(n) (RP2040_SPI_BASE(n) + RP2040_SPI_SSPDR_OFFSET)
#define RP2040_SPI_SSPSR(n) (RP2040_SPI_BASE(n) + RP2040_SPI_SSPSR_OFFSET)
#define RP2040_SPI_SSPCPSR(n) (RP2040_SPI_BASE(n) + RP2040_SPI_SSPCPSR_OFFSET)
#define RP2040_SPI_SSPIMSC(n) (RP2040_SPI_BASE(n) + RP2040_SPI_SSPIMSC_OFFSET)
#define RP2040_SPI_SSPRIS(n) (RP2040_SPI_BASE(n) + RP2040_SPI_SSPRIS_OFFSET)
#define RP2040_SPI_SSPMIS(n) (RP2040_SPI_BASE(n) + RP2040_SPI_SSPMIS_OFFSET)
#define RP2040_SPI_SSPICR(n) (RP2040_SPI_BASE(n) + RP2040_SPI_SSPICR_OFFSET)
#define RP2040_SPI_SSPDMACR(n) (RP2040_SPI_BASE(n) + RP2040_SPI_SSPDMACR_OFFSET)
#define RP2040_SPI_SSPPERIPHID0(n) (RP2040_SPI_BASE(n) + RP2040_SPI_SSPPERIPHID0_OFFSET)
#define RP2040_SPI_SSPPERIPHID1(n) (RP2040_SPI_BASE(n) + RP2040_SPI_SSPPERIPHID1_OFFSET)
#define RP2040_SPI_SSPPERIPHID2(n) (RP2040_SPI_BASE(n) + RP2040_SPI_SSPPERIPHID2_OFFSET)
#define RP2040_SPI_SSPPERIPHID3(n) (RP2040_SPI_BASE(n) + RP2040_SPI_SSPPERIPHID3_OFFSET)
#define RP2040_SPI_SSPPCELLID0(n) (RP2040_SPI_BASE(n) + RP2040_SPI_SSPPCELLID0_OFFSET)
#define RP2040_SPI_SSPPCELLID1(n) (RP2040_SPI_BASE(n) + RP2040_SPI_SSPPCELLID1_OFFSET)
#define RP2040_SPI_SSPPCELLID2(n) (RP2040_SPI_BASE(n) + RP2040_SPI_SSPPCELLID2_OFFSET)
#define RP2040_SPI_SSPPCELLID3(n) (RP2040_SPI_BASE(n) + RP2040_SPI_SSPPCELLID3_OFFSET)
/* Register bit definitions *************************************************/
#define RP2040_SPI_SSPCR0_SCR_SHIFT (8) /* Serial clock rate */
#define RP2040_SPI_SSPCR0_SCR_MASK (0xff << RP2040_SPI_SSPCR0_SCR_SHIFT)
#define RP2040_SPI_SSPCR0_SPH (1 << 7) /* SSPCLKOUT phase */
#define RP2040_SPI_SSPCR0_SPO (1 << 6) /* SSPCLKOUT polarity */
#define RP2040_SPI_SSPCR0_FRF_SHIFT (4) /* Frame format */
#define RP2040_SPI_SSPCR0_FRF_MASK (0x03 << RP2040_SPI_SSPCR0_FRF_SHIFT)
#define RP2040_SPI_SSPCR0_DSS_MASK (0x0f) /* Data Size Select */
#define RP2040_SPI_SSPCR0_DSS_SHIFT (0)
#define RP2040_SPI_SSPCR1_SOD (1 << 3) /* Slave-mode output disable */
#define RP2040_SPI_SSPCR1_MS (1 << 2) /* Master or slave mode select */
#define RP2040_SPI_SSPCR1_SSE (1 << 1) /* Synchronous serial port enable: 0 SSP operation disabled. 1 SSP operation enabled. */
#define RP2040_SPI_SSPCR1_LBM (1 << 0) /* Loop back mode */
#define RP2040_SPI_SSPDR_DATA_MASK (0xffff) /* Transmit/Receive FIFO */
#define RP2040_SPI_SSPSR_BSY (1 << 4) /* PrimeCell SSP busy flag */
#define RP2040_SPI_SSPSR_RFF (1 << 3) /* Receive FIFO full */
#define RP2040_SPI_SSPSR_RNE (1 << 2) /* Receive FIFO not empty */
#define RP2040_SPI_SSPSR_TNF (1 << 1) /* Transmit FIFO not full */
#define RP2040_SPI_SSPSR_TFE (1 << 0) /* Transmit FIFO empty */
#define RP2040_SPI_SSPCPSR_CPSDVSR_MASK (0xff) /* Clock prescale divisor. Must be an even number from 2-254 */
#define RP2040_SPI_SSPIMSC_TXIM (1 << 3) /* Transmit FIFO interrupt mask */
#define RP2040_SPI_SSPIMSC_RXIM (1 << 2) /* Receive FIFO interrupt mask */
#define RP2040_SPI_SSPIMSC_RTIM (1 << 1) /* Receive timeout interrupt mask */
#define RP2040_SPI_SSPIMSC_RORIM (1 << 0) /* Receive overrun interrupt mask */
#define RP2040_SPI_SSPRIS_TXRIS (1 << 3) /* Gives the raw interrupt state, prior to masking, of the SSPTXINTR interrupt */
#define RP2040_SPI_SSPRIS_RXRIS (1 << 2) /* Gives the raw interrupt state, prior to masking, of the SSPRXINTR interrupt */
#define RP2040_SPI_SSPRIS_RTRIS (1 << 1) /* Gives the raw interrupt state, prior to masking, of the SSPRTINTR interrupt */
#define RP2040_SPI_SSPRIS_RORRIS (1 << 0) /* Gives the raw interrupt state, prior to masking, of the SSPRORINTR interrupt */
#define RP2040_SPI_SSPMIS_TXMIS (1 << 3) /* Gives the transmit FIFO masked interrupt state, after masking, of the SSPTXINTR interrupt */
#define RP2040_SPI_SSPMIS_RXMIS (1 << 2) /* Gives the receive FIFO masked interrupt state, after masking, of the SSPRXINTR interrupt */
#define RP2040_SPI_SSPMIS_RTMIS (1 << 1) /* Gives the receive timeout masked interrupt state, after masking, of the SSPRTINTR interrupt */
#define RP2040_SPI_SSPMIS_RORMIS (1 << 0) /* Gives the receive over run masked interrupt status, after masking, of the SSPRORINTR interrupt */
#define RP2040_SPI_SSPICR_RTIC (1 << 1) /* Clears the SSPRTINTR interrupt */
#define RP2040_SPI_SSPICR_RORIC (1 << 0) /* Clears the SSPRORINTR interrupt */
#define RP2040_SPI_SSPDMACR_TXDMAE (1 << 1) /* Transmit DMA Enable. If this bit is set to 1, DMA for the transmit FIFO is enabled. */
#define RP2040_SPI_SSPDMACR_RXDMAE (1 << 0) /* Receive DMA Enable. If this bit is set to 1, DMA for the receive FIFO is enabled. */
#define RP2040_SPI_SSPPERIPHID0_PARTNUMBER0_MASK (0xff) /* These bits read back as 0x22 */
#define RP2040_SPI_SSPPERIPHID1_DESIGNER0_SHIFT (4) /* These bits read back as 0x1 */
#define RP2040_SPI_SSPPERIPHID1_DESIGNER0_MASK (0x0f << RP2040_SPI_SSPPERIPHID1_DESIGNER0_SHIFT)
#define RP2040_SPI_SSPPERIPHID1_PARTNUMBER1_MASK (0x0f) /* These bits read back as 0x0 */
#define RP2040_SPI_SSPPERIPHID2_REVISION_SHIFT (4) /* These bits return the peripheral revision */
#define RP2040_SPI_SSPPERIPHID2_REVISION_MASK (0x0f << RP2040_SPI_SSPPERIPHID2_REVISION_SHIFT)
#define RP2040_SPI_SSPPERIPHID2_DESIGNER1_MASK (0x0f) /* These bits read back as 0x4 */
#define RP2040_SPI_SSPPERIPHID3_CONFIGURATION_MASK (0xff) /* These bits read back as 0x00 */
#define RP2040_SPI_SSPPCELLID0_MASK (0xff) /* These bits read back as 0x0D */
#define RP2040_SPI_SSPPCELLID1_MASK (0xff) /* These bits read back as 0xF0 */
#define RP2040_SPI_SSPPCELLID2_MASK (0xff) /* These bits read back as 0x05 */
#define RP2040_SPI_SSPPCELLID3_MASK (0xff) /* These bits read back as 0xB1 */
#endif /* __ARCH_ARM_SRC_RP2040_HARDWARE_RP2040_SPI_H */

View file

@ -0,0 +1,828 @@
/****************************************************************************
* arch/arm/src/rp2040/rp2040_spi.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.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <sys/types.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <debug.h>
#include <string.h>
#include <arch/board/board.h>
#include <nuttx/irq.h>
#include <nuttx/arch.h>
#include <nuttx/semaphore.h>
#include <nuttx/spi/spi.h>
#include "arm_internal.h"
#include "arm_arch.h"
#include "chip.h"
#include "rp2040_spi.h"
#include "hardware/rp2040_spi.h"
#ifdef CONFIG_RP2040_SPI
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#ifndef __unused
#define __unused __attribute__((unused))
#endif
/* 8 frame FIFOs for both transmit and receive */
#define RP2040_SPI_FIFOSZ 8
/****************************************************************************
* Private Types
****************************************************************************/
/* This structure describes the state of the SPI driver */
struct rp2040_spidev_s
{
struct spi_dev_s spidev; /* Externally visible part of the SPI interface */
uint32_t spibase; /* SPIn base address */
uint32_t spibasefreq;
#ifdef CONFIG_RP2040_SPI_INTERRUPTS
uint8_t spiirq; /* SPI IRQ number */
#endif
sem_t exclsem; /* Held while chip is selected for mutual exclusion */
uint32_t frequency; /* Requested clock frequency */
uint32_t actual; /* Actual clock frequency */
uint8_t nbits; /* Width of word in bits (4 to 16) */
uint8_t mode; /* Mode 0,1,2,3 */
uint8_t port; /* Port number */
int initialized; /* Initialized flag */
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/* Helpers */
static inline uint32_t spi_getreg(FAR struct rp2040_spidev_s *priv,
uint8_t offset);
static inline void spi_putreg(FAR struct rp2040_spidev_s *priv,
uint8_t offset, uint32_t value);
/* SPI methods */
static int spi_lock(FAR struct spi_dev_s *dev, bool lock);
static uint32_t spi_setfrequency(FAR struct spi_dev_s *dev,
uint32_t frequency);
static void spi_setmode(FAR struct spi_dev_s *dev, enum spi_mode_e mode);
static void spi_setbits(FAR struct spi_dev_s *dev, int nbits);
static uint32_t spi_send(FAR struct spi_dev_s *dev, uint32_t wd);
static void __unused spi_exchange(FAR struct spi_dev_s *dev,
FAR const void *txbuffer,
FAR void *rxbuffer,
size_t nwords);
#ifndef CONFIG_SPI_EXCHANGE
static void spi_sndblock(FAR struct spi_dev_s *dev, FAR const void *buffer,
size_t nwords);
static void spi_recvblock(FAR struct spi_dev_s *dev, FAR void *buffer,
size_t nwords);
#endif
/****************************************************************************
* Private Data
****************************************************************************/
#ifdef CONFIG_RP2040_SPI0
static const struct spi_ops_s g_spi0ops =
{
.lock = spi_lock,
.select = rp2040_spi0select, /* Provided externally */
.setfrequency = spi_setfrequency,
.setmode = spi_setmode,
.setbits = spi_setbits,
#ifdef CONFIG_SPI_HWFEATURES
.hwfeatures = 0, /* Not supported */
#endif
.status = rp2040_spi0status, /* Provided externally */
#ifdef CONFIG_SPI_CMDDATA
.cmddata = rp2040_spi0cmddata, /* Provided externally */
#endif
.send = spi_send,
#ifdef CONFIG_SPI_EXCHANGE
.exchange = spi_exchange,
#else
.sndblock = spi_sndblock,
.recvblock = spi_recvblock,
#endif
#ifdef CONFIG_SPI_CALLBACK
.registercallback = rp2040_spi0register, /* Provided externally */
#else
.registercallback = 0, /* Not implemented */
#endif
};
static struct rp2040_spidev_s g_spi0dev =
{
.spidev =
{
&g_spi0ops
},
.spibase = RP2040_SPI0_BASE,
.spibasefreq = 0,
.port = 0,
.initialized = 0,
#ifdef CONFIG_RP2040_SPI_INTERRUPTS
.spiirq = RP2040_SPI0_IRQ,
#endif
};
#endif
#ifdef CONFIG_RP2040_SPI1
static const struct spi_ops_s g_spi1ops =
{
.lock = spi_lock,
.select = rp2040_spi1select, /* Provided externally */
.setfrequency = spi_setfrequency,
.setmode = spi_setmode,
.setbits = spi_setbits,
#ifdef CONFIG_SPI_HWFEATURES
.hwfeatures = 0, /* Not supported */
#endif
.status = rp2040_spi1status, /* Provided externally */
#ifdef CONFIG_SPI_CMDDATA
.cmddata = rp2040_spi1cmddata, /* Provided externally */
#endif
.send = spi_send,
#ifdef CONFIG_SPI_EXCHANGE
.exchange = spi_exchange,
#else
.sndblock = spi_sndblock,
.recvblock = spi_recvblock,
#endif
#ifdef CONFIG_SPI_CALLBACK
.registercallback = rp2040_spi1register, /* Provided externally */
#else
.registercallback = 0, /* Not implemented */
#endif
};
static struct rp2040_spidev_s g_spi1dev =
{
.spidev =
{
&g_spi1ops
},
.spibase = RP2040_SPI1_BASE,
.spibasefreq = 0,
.port = 1,
.initialized = 0,
#ifdef CONFIG_RP2040_SPI_INTERRUPTS
.spiirq = RP2040_SPI1_IRQ,
#endif
};
#endif
/****************************************************************************
* Public Data
****************************************************************************/
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* 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
*
****************************************************************************/
static inline uint32_t spi_getreg(FAR struct rp2040_spidev_s *priv,
uint8_t offset)
{
return getreg32(priv->spibase + (uint32_t)offset);
}
/****************************************************************************
* Name: spi_putreg
*
* Description:
* Write a 32-bit value to the SPI register at offset
*
* Input Parameters:
* priv - private SPI device structure
* offset - offset to the register of interest
* value - the 16-bit value to be written
*
* Returned Value:
* None
*
****************************************************************************/
static inline void spi_putreg(FAR struct rp2040_spidev_s *priv,
uint8_t offset, uint32_t value)
{
putreg32(value, priv->spibase + (uint32_t)offset);
}
/****************************************************************************
* 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(FAR struct spi_dev_s *dev, bool lock)
{
FAR struct rp2040_spidev_s *priv = (FAR struct rp2040_spidev_s *)dev;
if (lock)
{
/* Take the semaphore (perhaps waiting) */
return nxsem_wait_uninterruptible(&priv->exclsem);
}
else
{
return nxsem_post(&priv->exclsem);
}
}
/****************************************************************************
* 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(FAR struct spi_dev_s *dev,
uint32_t frequency)
{
FAR struct rp2040_spidev_s *priv = (FAR struct rp2040_spidev_s *)dev;
uint32_t divisor;
uint32_t actual;
/* Set SPI_CLOCK */
/* frequency = SPI_CLOCK / divisor, or divisor = SPI_CLOCK / frequency */
priv->spibasefreq = BOARD_PERI_FREQ;
divisor = priv->spibasefreq / frequency;
/* "In master mode, CPSDVSRmin = 2 or larger (even numbers only)" */
if (divisor < 2)
{
divisor = 2;
}
else if (divisor > 254)
{
divisor = 254;
}
divisor = (divisor + 1) & ~1;
/* Save the new divisor value */
spi_putreg(priv, RP2040_SPI_SSPCPSR_OFFSET, divisor);
/* Calculate the new actual */
actual = priv->spibasefreq / divisor;
/* Save the frequency setting */
priv->frequency = frequency;
priv->actual = actual;
spiinfo("Frequency %" PRId32 "->%" PRId32 "\n", frequency, actual);
return actual;
}
/****************************************************************************
* Name: 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 spi_setmode(FAR struct spi_dev_s *dev, enum spi_mode_e mode)
{
FAR struct rp2040_spidev_s *priv = (FAR struct rp2040_spidev_s *)dev;
uint32_t regval;
/* Has the mode changed? */
if (mode != priv->mode)
{
/* Yes... Set CR0 appropriately */
regval = spi_getreg(priv, RP2040_SPI_SSPCR0_OFFSET);
regval &= ~(RP2040_SPI_SSPCR0_SPO | RP2040_SPI_SSPCR0_SPH);
switch (mode)
{
case SPIDEV_MODE0: /* CPOL=0; CPHA=0 */
break;
case SPIDEV_MODE1: /* CPOL=0; CPHA=1 */
regval |= RP2040_SPI_SSPCR0_SPH;
break;
case SPIDEV_MODE2: /* CPOL=1; CPHA=0 */
regval |= RP2040_SPI_SSPCR0_SPO;
break;
case SPIDEV_MODE3: /* CPOL=1; CPHA=1 */
regval |= (RP2040_SPI_SSPCR0_SPO | RP2040_SPI_SSPCR0_SPH);
break;
default:
spierr("Bad mode: %d\n", mode);
DEBUGASSERT(FALSE);
return;
}
spi_putreg(priv, RP2040_SPI_SSPCR0_OFFSET, regval);
/* Save the mode so that subsequent re-configurations will be faster */
priv->mode = mode;
}
}
/****************************************************************************
* Name: 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 spi_setbits(FAR struct spi_dev_s *dev, int nbits)
{
FAR struct rp2040_spidev_s *priv = (FAR struct rp2040_spidev_s *)dev;
uint32_t regval;
/* Has the number of bits changed? */
DEBUGASSERT(priv && nbits > 3 && nbits < 17);
if (nbits != priv->nbits)
{
/* Yes... Set CR0 appropriately */
regval = spi_getreg(priv, RP2040_SPI_SSPCR0_OFFSET);
regval &= ~RP2040_SPI_SSPCR0_DSS_MASK;
regval |= ((nbits - 1) << RP2040_SPI_SSPCR0_DSS_SHIFT);
spi_putreg(priv, RP2040_SPI_SSPCR0_OFFSET, regval);
/* Save the selection so that re-configurations will be faster
*/
priv->nbits = nbits;
}
}
/****************************************************************************
* 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(FAR struct spi_dev_s *dev, uint32_t wd)
{
FAR struct rp2040_spidev_s *priv = (FAR struct rp2040_spidev_s *)dev;
register uint32_t regval;
/* Wait while the TX FIFO is full */
while (!(spi_getreg(priv, RP2040_SPI_SSPSR_OFFSET) & RP2040_SPI_SSPSR_TNF))
;
/* Write the byte to the TX FIFO */
spi_putreg(priv, RP2040_SPI_SSPDR_OFFSET, wd);
/* Wait for the RX FIFO not empty */
while (!(spi_getreg(priv, RP2040_SPI_SSPSR_OFFSET) & RP2040_SPI_SSPSR_RNE))
;
/* Get the value from the RX FIFO and return it */
regval = spi_getreg(priv, RP2040_SPI_SSPDR_OFFSET);
spiinfo("%04" PRIx32 "->%04" PRIx32 "\n", wd, regval);
return regval;
}
/****************************************************************************
* Name: spi_do_exchange
*
* Description:
* Exchange a block of data from SPI. Required.
*
* 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 spi_do_exchange(FAR struct spi_dev_s *dev,
FAR const void *txbuffer, FAR void *rxbuffer,
size_t nwords)
{
FAR struct rp2040_spidev_s *priv = (FAR struct rp2040_spidev_s *)dev;
union
{
FAR const uint8_t *p8;
FAR const uint16_t *p16;
FAR const void *pv;
} tx;
union
{
FAR uint8_t *p8;
FAR uint16_t *p16;
FAR void *pv;
} rx;
uint32_t data;
uint32_t datadummy = (priv->nbits > 8) ? 0xffff : 0xff;
uint32_t rxpending = 0;
/* Remaining data to be sent (and no synchronization error has occurred) */
tx.pv = txbuffer;
rx.pv = rxbuffer;
while (nwords || rxpending)
{
/* Write data to the data register while (1) the TX FIFO is
* not full, (2) we have not exceeded the depth of the TX FIFO,
* and (3) there are more bytes to be sent.
*/
spiinfo("TX: rxpending: %" PRId32 " nwords: %d\n", rxpending, nwords);
while ((spi_getreg(priv, RP2040_SPI_SSPSR_OFFSET) &
RP2040_SPI_SSPSR_TNF) &&
(rxpending < RP2040_SPI_FIFOSZ) && nwords)
{
if (txbuffer)
{
if (priv->nbits > 8)
{
data = (uint32_t)*tx.p16++;
}
else
{
data = (uint32_t)*tx.p8++;
}
}
spi_putreg(priv, RP2040_SPI_SSPDR_OFFSET,
txbuffer ? data : datadummy);
nwords--;
rxpending++;
}
/* Now, read the RX data from the RX FIFO
* while the RX FIFO is not empty
*/
spiinfo("RX: rxpending: %" PRId32 "\n", rxpending);
while (spi_getreg(priv, RP2040_SPI_SSPSR_OFFSET) &
RP2040_SPI_SSPSR_RNE)
{
data = spi_getreg(priv, RP2040_SPI_SSPDR_OFFSET);
if (rxbuffer)
{
if (priv->nbits > 8)
{
*rx.p16++ = (uint16_t)data;
}
else
{
*rx.p8++ = (uint8_t)data;
}
}
rxpending--;
}
}
}
/****************************************************************************
* Name: spi_exchange
*
* Description:
* Wrapper function of 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 spi_exchange(FAR struct spi_dev_s *dev, FAR const void *txbuffer,
FAR void *rxbuffer, size_t nwords)
{
spi_do_exchange(dev, txbuffer, rxbuffer, nwords);
}
/****************************************************************************
* Name: 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 spi_sndblock(FAR struct spi_dev_s *dev, FAR const void *buffer,
size_t nwords)
{
return spi_exchange(dev, buffer, NULL, nwords);
}
/****************************************************************************
* Name: 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 spi_recvblock(FAR struct spi_dev_s *dev, FAR void *buffer,
size_t nwords)
{
return spi_exchange(dev, NULL, buffer, nwords);
}
#endif /* !CONFIG_SPI_EXCHANGE */
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: rp2040_spibus_initialize
*
* Description:
* Initialize the selected SPI port
*
* Input Parameter:
* port - Port number
*
* Returned Value:
* Valid SPI device structure reference on success; a NULL on failure
*
****************************************************************************/
FAR struct spi_dev_s *rp2040_spibus_initialize(int port)
{
FAR struct rp2040_spidev_s *priv;
uint32_t regval;
int i;
switch (port)
{
#ifdef CONFIG_RP2040_SPI0
case 0:
priv = &g_spi0dev;
break;
#endif
#ifdef CONFIG_RP2040_SPI1
case 1:
priv = &g_spi1dev;
break;
#endif
default:
return NULL;
}
/* If already initialized */
if (priv->initialized)
{
return &priv->spidev;
}
/* Configure clocking */
priv->spibasefreq = BOARD_PERI_FREQ;
/* Configure 8-bit SPI mode */
spi_putreg(priv, RP2040_SPI_SSPCR0_OFFSET,
((8 - 1) << RP2040_SPI_SSPCR0_DSS_SHIFT) |
(0 << RP2040_SPI_SSPCR0_FRF_SHIFT));
/* Disable SPI and all interrupts (we'll poll for all data) */
spi_putreg(priv, RP2040_SPI_SSPCR1_OFFSET, 0);
spi_putreg(priv, RP2040_SPI_SSPIMSC_OFFSET, 0);
/* Clear interrupts */
spi_putreg(priv, RP2040_SPI_SSPICR_OFFSET, 0x3);
/* Set the initial SPI configuration */
priv->frequency = 0;
priv->nbits = 8;
priv->mode = SPIDEV_MODE0;
/* Select a default frequency of approx. 400KHz */
spi_setfrequency((FAR struct spi_dev_s *)priv, 400000);
/* Initialize the SPI semaphore that enforces mutually exclusive access */
nxsem_init(&priv->exclsem, 0, 1);
regval = spi_getreg(priv, RP2040_SPI_SSPCR1_OFFSET);
spi_putreg(priv, RP2040_SPI_SSPCR1_OFFSET, regval | RP2040_SPI_SSPCR1_SSE);
for (i = 0; i < RP2040_SPI_FIFOSZ; i++)
{
spi_getreg(priv, RP2040_SPI_SSPDR_OFFSET);
}
/* Set a initialized flag */
priv->initialized = 1;
return &priv->spidev;
}
/****************************************************************************
* Name: spi_flush
*
* Description:
* Flush and discard any words left in the RX fifo. This can be done
* after a device is deselected if you worry about such things.
*
* Input Parameters:
* dev - Device-specific state data
*
* Returned Value:
* None
*
****************************************************************************/
void spi_flush(FAR struct spi_dev_s *dev)
{
FAR struct rp2040_spidev_s *priv = (FAR struct rp2040_spidev_s *)dev;
/* Wait for the TX FIFO not full indication */
while (!(spi_getreg(priv, RP2040_SPI_SSPSR_OFFSET) & RP2040_SPI_SSPSR_TNF))
;
spi_putreg(priv, RP2040_SPI_SSPDR_OFFSET, 0xff);
/* Wait until TX FIFO and TX shift buffer are empty */
while (spi_getreg(priv, RP2040_SPI_SSPSR_OFFSET) & RP2040_SPI_SSPSR_BSY);
/* Wait until RX FIFO is not empty */
while (!(spi_getreg(priv, RP2040_SPI_SSPSR_OFFSET) & RP2040_SPI_SSPSR_RNE))
;
/* Then read and discard bytes until the RX FIFO is empty */
do
{
spi_getreg(priv, RP2040_SPI_SSPDR_OFFSET);
}
while (spi_getreg(priv, RP2040_SPI_SSPSR_OFFSET) & RP2040_SPI_SSPSR_RNE);
}
#endif

View file

@ -0,0 +1,224 @@
/****************************************************************************
* arch/arm/src/rp2040/rp2040_spi.h
*
* 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.
*
****************************************************************************/
#ifndef __ARCH_ARM_SRC_RP2040_RP2040_SPI_H
#define __ARCH_ARM_SRC_RP2040_RP2040_SPI_H
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <nuttx/spi/spi.h>
#include "hardware/rp2040_spi.h"
#ifdef CONFIG_RP2040_DMAC
#include "rp2040_dmac.h"
#endif
#if defined(CONFIG_RP2040_SPI0) || defined(CONFIG_RP2040_SPI1)
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* This header file defines interfaces to common SPI logic.
* To use this common SPI logic on your board:
*
* 1. Provide logic in rp2040_boardinitialize() to configure SPI chip select
* pins.
* 2. Provide rp2040_spi0/1select() and rp2040_spi0/1status() 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. If CONFIG_SPI_CMDDATA is defined in the NuttX configuration, provide
* rp2040_spi0/1cmddata() functions in your board-specific logic. These
* functions will perform cmd/data selection operations using GPIOs in the
* way your board is configured.
* 4. Your low level board initialization logic should call
* rp2040_spibus_initialize.
* 5. The handle returned by rp2040_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).
*/
#define RP2040_SPI_DMAC_CHTYPE_TX (0)
#define RP2040_SPI_DMAC_CHTYPE_RX (1)
/****************************************************************************
* Public Types
****************************************************************************/
/****************************************************************************
* Public Data
****************************************************************************/
#ifndef __ASSEMBLY__
#undef EXTERN
#if defined(__cplusplus)
#define EXTERN extern "C"
extern "C"
{
#else
#define EXTERN extern
#endif
/****************************************************************************
* Public Functions Prototypes
****************************************************************************/
/****************************************************************************
* Name: rp2040_spibus_initialize
*
* Description:
* Initialize the selected SPI port
*
* Input Parameter:
* port - Port number
*
* Returned Value:
* Valid SPI device structure reference on success; a NULL on failure
*
****************************************************************************/
FAR struct spi_dev_s *rp2040_spibus_initialize(int port);
/****************************************************************************
* Name: rp2040_spi_dmaconfig
*
* Description:
* Enable DMA configuration.
*
* Input Parameter:
* port - Port number
* chtype - Channel type(TX or RX)
* handle - DMA channel handle
* conf - DMA configuration
*
* Returned Value:
* None
*
****************************************************************************/
#ifdef CONFIG_RP2040_DMAC
void rp2040_spi_dmaconfig(int port, int chtype, DMA_HANDLE handle,
FAR dma_config_t *conf);
#endif
/****************************************************************************
* Name: rp2040_spiXselect, rp2040_spiXstatus, and rp2040_spiXcmddata
*
* Description:
* These functions must be provided in your board-specific logic.
* The rp2040_spi0/1select functions will perform chip selection and the
* rp2040_spi0/1status will perform status operations using GPIOs in
* the way your board is configured.
*
* If CONFIG_SPI_CMDDATA is defined in the NuttX configuration, then
* rp2040_spi0/1cmddata must also be provided.
* This functions performs cmd/data selection operations using GPIOs in
* the way your board is configured.
*
****************************************************************************/
#ifdef CONFIG_RP2040_SPI0
void rp2040_spi0select(FAR struct spi_dev_s *dev,
uint32_t devid,
bool selected);
uint8_t rp2040_spi0status(FAR struct spi_dev_s *dev,
uint32_t devid);
#ifdef CONFIG_SPI_CMDDATA
int rp2040_spi0cmddata(FAR struct spi_dev_s *dev,
uint32_t devid,
bool cmd);
#endif
#endif
#ifdef CONFIG_RP2040_SPI1
void rp2040_spi1select(FAR struct spi_dev_s *dev,
uint32_t devid,
bool selected);
uint8_t rp2040_spi1status(FAR struct spi_dev_s *dev,
uint32_t devid);
#ifdef CONFIG_SPI_CMDDATA
int rp2040_spi1cmddata(FAR struct spi_dev_s *dev,
uint32_t devid,
bool cmd);
#endif
#endif
/****************************************************************************
* Name: spi_flush
*
* Description:
* Flush and discard any words left in the RX fifo. This can be called
* from spi0/1select after a device is deselected (if you worry about such
* things).
*
* Input Parameters:
* dev - Device-specific state data
*
* Returned Value:
* None
*
****************************************************************************/
void spi_flush(FAR struct spi_dev_s *dev);
/****************************************************************************
* Name: rp2040_spiXregister
*
* Description:
* If the board supports a card detect callback to inform the SPI-based
* MMC/SD driver when an SD card is inserted or removed, then
* CONFIG_SPI_CALLBACK should be defined and the following function(s) must
* must be implemented. These functions implements the registercallback
* method of the SPI interface (see include/nuttx/spi/spi.h for details)
*
* Input Parameters:
* dev - Device-specific state data
* callback - The function to call on the media change
* arg - A caller provided value to return with the callback
*
* Returned Value:
* 0 on success; negated errno on failure.
*
****************************************************************************/
#ifdef CONFIG_SPI_CALLBACK
#ifdef CONFIG_RP2040_SPI0
int rp2040_spi0register(FAR struct spi_dev_s *dev,
spi_mediachange_t callback, FAR void *arg);
#endif
#ifdef CONFIG_RP2040_SPI1
int rp2040_spi1register(FAR struct spi_dev_s *dev,
spi_mediachange_t callback, FAR void *arg);
#endif
#endif
#undef EXTERN
#if defined(__cplusplus)
}
#endif
#endif /* __ASSEMBLY__ */
#endif /* CONFIG_RP2040_SPI0/1 */
#endif /* __ARCH_ARM_SRC_RP2040_RP2040_SPI_H */