diff --git a/arch/arm/src/rp2040/Kconfig b/arch/arm/src/rp2040/Kconfig index f11f77526b4..b578d029e0e 100644 --- a/arch/arm/src/rp2040/Kconfig +++ b/arch/arm/src/rp2040/Kconfig @@ -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 diff --git a/arch/arm/src/rp2040/Make.defs b/arch/arm/src/rp2040/Make.defs index f48a4c46bdb..97833fe6329 100644 --- a/arch/arm/src/rp2040/Make.defs +++ b/arch/arm/src/rp2040/Make.defs @@ -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 diff --git a/arch/arm/src/rp2040/hardware/rp2040_spi.h b/arch/arm/src/rp2040/hardware/rp2040_spi.h new file mode 100644 index 00000000000..a514923bf9a --- /dev/null +++ b/arch/arm/src/rp2040/hardware/rp2040_spi.h @@ -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 */ diff --git a/arch/arm/src/rp2040/rp2040_spi.c b/arch/arm/src/rp2040/rp2040_spi.c new file mode 100644 index 00000000000..9ecd56ae1b0 --- /dev/null +++ b/arch/arm/src/rp2040/rp2040_spi.c @@ -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 + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#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 diff --git a/arch/arm/src/rp2040/rp2040_spi.h b/arch/arm/src/rp2040/rp2040_spi.h new file mode 100644 index 00000000000..e2e40da26a8 --- /dev/null +++ b/arch/arm/src/rp2040/rp2040_spi.h @@ -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 +#include +#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 */