arch/arm/rtl8721dx: add shared Ameba SPI driver

Add a shared NuttX SPI master lower-half for the Realtek Ameba SPI
controllers (SPI0/SPI1) in arch/arm/src/common/ameba, driven through the
SDK fwlib in polling mode with full-duplex exchange and a software chip
select.  Per-chip wiring (controller count, register bases, clock masks,
crossbar pad-mux codes and the fwlib SSI_InitTypeDef layout) lives in
arch/arm/src/rtl8721dx/ameba_spi_chip.h so a port to the other Ameba
chips only supplies a same-named header.

Each controller registers as /dev/spiN from pke8721daf bring-up through
the stock SPI character driver; a dedicated `spi` defconfig drives the
spitool for validation.

Assisted-by: Claude <noreply@anthropic.com>
Signed-off-by: dechao_gong <dechao_gong@realsil.com.cn>
This commit is contained in:
dechao_gong 2026-07-20 11:47:46 +08:00 committed by Alan C. Assis
parent e5a0c4ae88
commit 7dbf782e5b
15 changed files with 1100 additions and 1 deletions

View file

@ -39,6 +39,8 @@ Supported in this NuttX port:
directly on the SDK fwlib register layer
* I2C master buses exposed as ``/dev/i2cN`` character devices, driven directly
on the SDK fwlib register layer
* SPI master buses exposed as ``/dev/spiN`` character devices, driven directly
on the SDK fwlib register layer
Buttons and LEDs
================
@ -116,6 +118,22 @@ external pull-ups on SCL/SDA. Probe a bus with the tool::
nsh> i2c dev -b 0 0x03 0x77 # scan /dev/i2c0 for devices
spi
---
Minimal NSH with the SPI master driver and the ``spi`` tool
(``system/spi``) enabled (no Wi-Fi). The board registers two buses from its
table (see ``boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_spi.c``): SPI0 at
``/dev/spi0`` with CLK/MOSI/MISO/CS on PA14/PA15/PA16/PA17 and SPI1 at
``/dev/spi1`` with CLK/MOSI/MISO on PB18/PB19/PB20 and a software chip-select
on PB21. Edit that table -- controller, CLK/MOSI/MISO pads and CS pad -- to
match a board's wiring; the pads use the same ``AMEBA_PA()`` / ``AMEBA_PB()``
encoding as the GPIO table and are muxed to the SPI function through the SDK
ROM, while the chip-select is driven as a plain GPIO. Exercise a bus with the
tool::
nsh> spi exch -b 1 -x 4 deadbeef # full-duplex transfer on /dev/spi1
Wi-Fi
=====

View file

@ -69,4 +69,20 @@ config AMEBA_I2C
fwlib register layer and drives the DesignWare I2C block in polling
mode.
config AMEBA_SPI
bool "SPI"
default n
select SPI
select SPI_DRIVER
select SPI_EXCHANGE
---help---
Expose the Ameba SPI controllers (SPI0/SPI1) as NuttX SPI master
buses at /dev/spiN. The board selects which controller is used and
its CLK/MOSI/MISO/CS pads in its bring-up code; the chip select is
driven as a plain GPIO (software CS).
The driver (arch/arm/src/common/ameba/ameba_spi.c) sits on the SDK
fwlib register layer and drives the DesignWare SSI block in polling
mode.
endmenu # Ameba Peripheral Support

View file

@ -0,0 +1,648 @@
/****************************************************************************
* arch/arm/src/common/ameba/ameba_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
****************************************************************************/
/* NuttX SPI master lower half for the Realtek Ameba SPI controllers (SPI0
* and SPI1). Each controller is registered from board bring-up and appears
* as /dev/spiN through the stock SPI character driver (spi_register()).
*
* The controller is a Synopsys DesignWare SSI block programmed through the
* SDK fwlib SSI API in polling mode (no interrupts): SSI_Init() programs the
* role/mode/clock, and each word is moved by spinning on SSI_Writeable() /
* SSI_Readable() around SSI_WriteData() / SSI_ReadData(). The block runs in
* transmit-and-receive mode so every word written yields one word read,
* which is exactly the full-duplex exchange NuttX expects.
*
* The SSI fwlib routines resolve to the on-chip ROM symbol table (they are
* _LONG_CALL_ entries), like the UART fwlib and unlike the I2C fwlib. The
* SSI baud divider is derived from the peripheral clock ip_clk =
* PLL_ClkGet() / (HPERI divider + 1), read once at registration.
*
* The chip select is driven as a plain GPIO (software CS) rather than the
* SSI hardware CS: select() toggles the CS pad through GPIO_WriteBit(), so
* any pad can serve as CS and the CS timing is not tied to the FIFO state.
*
* The chip-specific wiring (controller count, register bases, clock masks
* and pad-mux codes) lives in the per-chip ameba_spi_chip.h. To keep the
* vendor headers out of the NuttX include world, the few fwlib symbols and
* the SSI_InitTypeDef layout used here are declared locally rather than
* pulled in from <ameba_spi.h>.
*/
#include <nuttx/config.h>
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <string.h>
#include <debug.h>
#include <inttypes.h>
#include <nuttx/kmalloc.h>
#include <nuttx/mutex.h>
#include <nuttx/spi/spi.h>
#include <nuttx/spi/spi_transfer.h>
#include "ameba_spi.h"
#include "ameba_spi_chip.h"
#include "ameba_gpio_chip.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* The register bases, peripheral-clock masks, crossbar pad-mux codes and
* controller count (AMEBA_NSPI) come from the per-chip ameba_spi_chip.h.
* Everything below is common to every current Ameba chip.
*/
/* fwlib SSI_InitTypeDef field values (SSI_MASTER, TMOD/FRF, SCPOL/SCPH).
* These are identical on every Ameba chip audited.
*/
#define AMEBA_SSI_MASTER 0x1 /* SSI_MASTER (SPI_Role) */
#define AMEBA_SSI_TMOD_TR 0x0 /* Transmit & receive (SPI_TransferMode) */
#define AMEBA_SSI_FRF_SPI 0x0 /* FRF_MOTOROLA_SPI (SPI_DataFrameFormat) */
#define AMEBA_SSI_SCPOL_LOW 0x0 /* SCPOL_INACTIVE_IS_LOW (CPOL=0) */
#define AMEBA_SSI_SCPOL_HIGH 0x1 /* SCPOL_INACTIVE_IS_HIGH (CPOL=1) */
#define AMEBA_SSI_SCPH_MIDDLE 0x0 /* SCPH_TOGGLES_IN_MIDDLE (CPHA=0) */
#define AMEBA_SSI_SCPH_START 0x1 /* SCPH_TOGGLES_AT_START (CPHA=1) */
/* GPIO field values for the software chip select (see ameba_gpio.c). */
#define AMEBA_GPIO_MODE_OUT 0x1 /* GPIO_Mode_OUT */
#define AMEBA_GPIO_PUPD_NONE 0x0 /* GPIO_PuPd_NOPULL */
/* Second/third argument to fwlib "state" style APIs. */
#define AMEBA_DISABLE 0x0
#define AMEBA_ENABLE 0x1
/* Bus defaults used until the upper half programs its own. */
#define AMEBA_SPI_DEFAULT_FREQ 1000000
#define AMEBA_SPI_DEFAULT_BITS 8
/* Bounded spin guarding each FIFO wait so a wedged bus cannot hang. */
#define AMEBA_SPI_TIMEOUT 1000000
/****************************************************************************
* Private Types
****************************************************************************/
/* Layout-compatible mirror of the fwlib SSI_InitTypeDef (all u32, same
* order); passed by address to SSI_StructInit()/SSI_Init().
*/
struct ameba_ssi_init_s
{
uint32_t dmarxlvl; /* SPI_DmaRxDataLevel */
uint32_t dmatxlvl; /* SPI_DmaTxDataLevel */
uint32_t rxthlvl; /* SPI_RxThresholdLevel */
uint32_t txthlvl; /* SPI_TxThresholdLevel */
uint32_t ssenable; /* SPI_SlaveSelectEnable */
uint32_t divider; /* SPI_ClockDivider */
uint32_t framenum; /* SPI_DataFrameNumber */
uint32_t frameformat; /* SPI_DataFrameFormat */
uint32_t framesize; /* SPI_DataFrameSize */
uint32_t intmask; /* SPI_InterruptMask */
uint32_t role; /* SPI_Role */
uint32_t sclkphase; /* SPI_SclkPhase */
uint32_t sclkpolarity; /* SPI_SclkPolarity */
uint32_t transfermode; /* SPI_TransferMode */
};
/* Layout-compatible mirror of the fwlib GPIO_InitTypeDef (see ameba_gpio.c),
* used to configure the software chip-select pad as an output.
*/
struct ameba_gpio_init_s
{
uint32_t mode; /* GPIO_Mode */
uint32_t pupd; /* GPIO_PuPd */
uint32_t ittrigger; /* GPIO_ITTrigger */
uint32_t itpolarity; /* GPIO_ITPolarity */
uint32_t itdebounce; /* GPIO_ITDebounce */
uint32_t pin; /* GPIO_Pin */
};
struct ameba_spi_dev_s
{
struct spi_dev_s dev; /* SPI master lower half (must be first) */
uintptr_t base; /* Non-secure SPI register base address */
uint32_t periph; /* APBPeriph function mask (RCC arg 1) */
uint32_t clk; /* APBPeriph clock mask (RCC arg 2) */
uint32_t ipclk; /* SSI peripheral clock ip_clk (Hz) */
uint32_t frequency; /* Requested bus frequency (Hz) */
uint32_t actual; /* Achieved bus frequency (Hz) */
uint32_t divider; /* SSI clock divider for that frequency */
uint8_t mode; /* Currently programmed enum spi_mode_e */
uint8_t nbits; /* Currently programmed bits per word */
bool dirty; /* SSI needs an SSI_Init() before use */
uint8_t clkpin; /* SCLK pad (AMEBA_PA()/AMEBA_PB()) */
uint8_t mosipin; /* MOSI pad */
uint8_t misopin; /* MISO pad */
uint8_t cspin; /* Chip-select pad (GPIO output) */
uint8_t clkfid; /* Pin mux code for the SCLK pad */
uint8_t mosifid; /* Pin mux code for the MOSI pad */
uint8_t misofid; /* Pin mux code for the MISO pad */
mutex_t lock; /* Serializes bus access */
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/* SDK fwlib SSI/GPIO/pin/clock API, all resolved from the on-chip ROM. */
extern void RCC_PeriphClockCmd(uint32_t periph, uint32_t clock,
uint8_t newstate);
extern void Pinmux_Config(uint8_t pin, uint32_t func);
extern void GPIO_Init(struct ameba_gpio_init_s *init);
extern void GPIO_WriteBit(uint32_t pin, uint32_t state);
extern void SSI_SetRole(void *spidev, uint32_t role);
extern void SSI_StructInit(struct ameba_ssi_init_s *init);
extern void SSI_Init(void *spidev, struct ameba_ssi_init_s *init);
extern void SSI_Cmd(void *spidev, uint32_t newstate);
extern uint32_t SSI_Writeable(void *spidev);
extern uint32_t SSI_Readable(void *spidev);
extern void SSI_WriteData(void *spidev, uint32_t value);
extern uint32_t SSI_ReadData(void *spidev);
/* SPI master lower-half operations. */
static int ameba_spi_lock(struct spi_dev_s *dev, bool lock);
static void ameba_spi_select(struct spi_dev_s *dev, uint32_t devid,
bool selected);
static uint32_t ameba_spi_setfrequency(struct spi_dev_s *dev,
uint32_t frequency);
static void ameba_spi_setmode(struct spi_dev_s *dev, enum spi_mode_e mode);
static void ameba_spi_setbits(struct spi_dev_s *dev, int nbits);
static uint8_t ameba_spi_status(struct spi_dev_s *dev, uint32_t devid);
static uint32_t ameba_spi_send(struct spi_dev_s *dev, uint32_t wd);
#ifdef CONFIG_SPI_EXCHANGE
static void ameba_spi_exchange(struct spi_dev_s *dev,
const void *txbuffer, void *rxbuffer,
size_t nwords);
#else
static void ameba_spi_sndblock(struct spi_dev_s *dev, const void *buffer,
size_t nwords);
static void ameba_spi_recvblock(struct spi_dev_s *dev, void *buffer,
size_t nwords);
#endif
/****************************************************************************
* Private Data
****************************************************************************/
static const struct spi_ops_s g_ameba_spi_ops =
{
.lock = ameba_spi_lock,
.select = ameba_spi_select,
.setfrequency = ameba_spi_setfrequency,
.setmode = ameba_spi_setmode,
.setbits = ameba_spi_setbits,
.status = ameba_spi_status,
.send = ameba_spi_send,
#ifdef CONFIG_SPI_EXCHANGE
.exchange = ameba_spi_exchange,
#else
.sndblock = ameba_spi_sndblock,
.recvblock = ameba_spi_recvblock,
#endif
};
/* Per-controller register base, peripheral function/clock masks and crossbar
* pad-mux codes, indexed by controller number and supplied by the per-chip
* ameba_spi_chip.h.
*/
static const uintptr_t g_spi_base[AMEBA_NSPI] = AMEBA_SPI_BASES;
static const uint32_t g_spi_periph[AMEBA_NSPI] = AMEBA_SPI_APBPERIPH;
static const uint32_t g_spi_clk[AMEBA_NSPI] = AMEBA_SPI_APBPERIPH_CLK;
static const uint8_t g_spi_clkfid[AMEBA_NSPI] = AMEBA_SPI_CLKFID;
static const uint8_t g_spi_mosifid[AMEBA_NSPI] = AMEBA_SPI_MOSIFID;
static const uint8_t g_spi_misofid[AMEBA_NSPI] = AMEBA_SPI_MISOFID;
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: ameba_spi_ipclk
*
* Description:
* Return the SSI peripheral clock ip_clk = PLL_ClkGet() / (HPERI + 1),
* the frequency the baud divider divides down to produce SCLK.
*
****************************************************************************/
static uint32_t ameba_spi_ipclk(void)
{
/* The register address and HPERI field position are chip-specific and are
* expressed by AMEBA_SPI_IPCLK() in the per-chip ameba_spi_chip.h.
*/
return AMEBA_SPI_IPCLK();
}
/****************************************************************************
* Name: ameba_spi_configure
*
* Description:
* (Re)program the SSI block for the currently requested frequency, mode
* and word length, and enable it. The DesignWare SSI can only be
* configured while disabled, so this runs lazily on the first word after
* any of those parameters changes (priv->dirty).
*
****************************************************************************/
static void ameba_spi_configure(struct ameba_spi_dev_s *priv)
{
struct ameba_ssi_init_s init;
void *spidev = (void *)priv->base;
if (!priv->dirty)
{
return;
}
memset(&init, 0, sizeof(init));
SSI_StructInit(&init);
init.role = AMEBA_SSI_MASTER;
init.transfermode = AMEBA_SSI_TMOD_TR;
init.frameformat = AMEBA_SSI_FRF_SPI;
init.ssenable = 0; /* Software CS: do not drive the SSI CS line */
init.intmask = 0; /* Polling: no SSI interrupts */
init.divider = priv->divider;
init.framesize = priv->nbits - 1;
/* enum spi_mode_e encodes CPOL in bit1 and CPHA in bit0. */
init.sclkpolarity = (priv->mode & 0x2) ? AMEBA_SSI_SCPOL_HIGH :
AMEBA_SSI_SCPOL_LOW;
init.sclkphase = (priv->mode & 0x1) ? AMEBA_SSI_SCPH_START :
AMEBA_SSI_SCPH_MIDDLE;
SSI_Cmd(spidev, AMEBA_DISABLE);
SSI_Init(spidev, &init);
SSI_Cmd(spidev, AMEBA_ENABLE);
priv->dirty = false;
}
/****************************************************************************
* Name: ameba_spi_transfer
*
* Description:
* Move nwords words full-duplex in polling mode. Every word written to
* the TX FIFO produces one word in the RX FIFO (transmit-and-receive
* mode); a NULL txbuffer sends 0xffff and a NULL rxbuffer discards the
* received words.
*
****************************************************************************/
static void ameba_spi_transfer(struct ameba_spi_dev_s *priv,
const void *txbuffer, void *rxbuffer,
size_t nwords)
{
void *spidev = (void *)priv->base;
bool wide = priv->nbits > 8;
size_t i;
uint32_t to;
ameba_spi_configure(priv);
for (i = 0; i < nwords; i++)
{
uint32_t word = 0xffff;
uint32_t rxword;
if (txbuffer != NULL)
{
word = wide ? ((const uint16_t *)txbuffer)[i] :
((const uint8_t *)txbuffer)[i];
}
for (to = AMEBA_SPI_TIMEOUT;
to > 0 && SSI_Writeable(spidev) == 0; to--);
SSI_WriteData(spidev, word);
for (to = AMEBA_SPI_TIMEOUT;
to > 0 && SSI_Readable(spidev) == 0; to--);
rxword = SSI_ReadData(spidev);
if (rxbuffer != NULL)
{
if (wide)
{
((uint16_t *)rxbuffer)[i] = (uint16_t)rxword;
}
else
{
((uint8_t *)rxbuffer)[i] = (uint8_t)rxword;
}
}
}
}
/****************************************************************************
* Name: ameba_spi_lock
****************************************************************************/
static int ameba_spi_lock(struct spi_dev_s *dev, bool lock)
{
struct ameba_spi_dev_s *priv = (struct ameba_spi_dev_s *)dev;
return lock ? nxmutex_lock(&priv->lock) : nxmutex_unlock(&priv->lock);
}
/****************************************************************************
* Name: ameba_spi_select
*
* Description:
* Assert (selected) or de-assert the active-low chip-select GPIO. A
* single CS pad is driven per bus, so the devid is not used.
*
****************************************************************************/
static void ameba_spi_select(struct spi_dev_s *dev, uint32_t devid,
bool selected)
{
struct ameba_spi_dev_s *priv = (struct ameba_spi_dev_s *)dev;
GPIO_WriteBit(priv->cspin, selected ? AMEBA_DISABLE : AMEBA_ENABLE);
}
/****************************************************************************
* Name: ameba_spi_setfrequency
*
* Description:
* Choose the largest even SSI divider whose SCLK does not exceed the
* requested frequency (SCLK = ip_clk / divider, even, 2..65534), and
* return the frequency actually achieved.
*
****************************************************************************/
static uint32_t ameba_spi_setfrequency(struct spi_dev_s *dev,
uint32_t frequency)
{
struct ameba_spi_dev_s *priv = (struct ameba_spi_dev_s *)dev;
uint32_t divider;
if (frequency == 0)
{
return priv->actual;
}
if (frequency == priv->frequency)
{
return priv->actual;
}
/* Round the divider down to an even value, then bump it up until SCLK no
* longer exceeds the request; clamp to the 16-bit even range.
*/
divider = (priv->ipclk / frequency / 2) * 2;
if (divider < 2)
{
divider = 2;
}
if ((priv->ipclk / divider) > frequency)
{
divider += 2;
}
if (divider > 0xfffe)
{
divider = 0xfffe;
}
priv->divider = divider;
priv->frequency = frequency;
priv->actual = priv->ipclk / divider;
priv->dirty = true;
spiinfo("frequency=%" PRIu32 " actual=%" PRIu32 " div=%" PRIu32 "\n",
frequency, priv->actual, divider);
return priv->actual;
}
/****************************************************************************
* Name: ameba_spi_setmode
****************************************************************************/
static void ameba_spi_setmode(struct spi_dev_s *dev, enum spi_mode_e mode)
{
struct ameba_spi_dev_s *priv = (struct ameba_spi_dev_s *)dev;
if ((uint8_t)mode != priv->mode)
{
priv->mode = (uint8_t)mode;
priv->dirty = true;
}
}
/****************************************************************************
* Name: ameba_spi_setbits
****************************************************************************/
static void ameba_spi_setbits(struct spi_dev_s *dev, int nbits)
{
struct ameba_spi_dev_s *priv = (struct ameba_spi_dev_s *)dev;
if (nbits >= 4 && nbits <= 16 && (uint8_t)nbits != priv->nbits)
{
priv->nbits = (uint8_t)nbits;
priv->dirty = true;
}
}
/****************************************************************************
* Name: ameba_spi_status
****************************************************************************/
static uint8_t ameba_spi_status(struct spi_dev_s *dev, uint32_t devid)
{
UNUSED(dev);
UNUSED(devid);
return 0;
}
/****************************************************************************
* Name: ameba_spi_send
****************************************************************************/
static uint32_t ameba_spi_send(struct spi_dev_s *dev, uint32_t wd)
{
struct ameba_spi_dev_s *priv = (struct ameba_spi_dev_s *)dev;
uint16_t rxword = 0;
uint16_t txword = (uint16_t)wd;
ameba_spi_transfer(priv, &txword, &rxword, 1);
return rxword;
}
#ifdef CONFIG_SPI_EXCHANGE
/****************************************************************************
* Name: ameba_spi_exchange
****************************************************************************/
static void ameba_spi_exchange(struct spi_dev_s *dev,
const void *txbuffer, void *rxbuffer,
size_t nwords)
{
struct ameba_spi_dev_s *priv = (struct ameba_spi_dev_s *)dev;
ameba_spi_transfer(priv, txbuffer, rxbuffer, nwords);
}
#else
/****************************************************************************
* Name: ameba_spi_sndblock
****************************************************************************/
static void ameba_spi_sndblock(struct spi_dev_s *dev, const void *buffer,
size_t nwords)
{
struct ameba_spi_dev_s *priv = (struct ameba_spi_dev_s *)dev;
ameba_spi_transfer(priv, buffer, NULL, nwords);
}
/****************************************************************************
* Name: ameba_spi_recvblock
****************************************************************************/
static void ameba_spi_recvblock(struct spi_dev_s *dev, void *buffer,
size_t nwords)
{
struct ameba_spi_dev_s *priv = (struct ameba_spi_dev_s *)dev;
ameba_spi_transfer(priv, NULL, buffer, nwords);
}
#endif /* CONFIG_SPI_EXCHANGE */
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: ameba_spi_register
*
* Description:
* See ameba_spi.h.
*
****************************************************************************/
struct spi_dev_s *ameba_spi_register(int bus, uint8_t clkpin,
uint8_t mosipin, uint8_t misopin,
uint8_t cspin)
{
struct ameba_spi_dev_s *priv;
struct ameba_gpio_init_s gpio;
int ret;
if (bus < 0 || bus >= AMEBA_NSPI)
{
return NULL;
}
priv = kmm_zalloc(sizeof(struct ameba_spi_dev_s));
if (priv == NULL)
{
return NULL;
}
priv->dev.ops = &g_ameba_spi_ops;
priv->base = g_spi_base[bus];
priv->periph = g_spi_periph[bus];
priv->clk = g_spi_clk[bus];
priv->clkpin = clkpin;
priv->mosipin = mosipin;
priv->misopin = misopin;
priv->cspin = cspin;
priv->clkfid = g_spi_clkfid[bus];
priv->mosifid = g_spi_mosifid[bus];
priv->misofid = g_spi_misofid[bus];
priv->mode = SPIDEV_MODE0;
priv->nbits = AMEBA_SPI_DEFAULT_BITS;
priv->frequency = 0;
priv->dirty = true;
nxmutex_init(&priv->lock);
/* Gate the SSI peripheral clock and route the CLK/MOSI/MISO pads. */
RCC_PeriphClockCmd(priv->periph, priv->clk, AMEBA_ENABLE);
/* The controller powers up as an SPI slave (the LSYS SPIx_MST bit is clear
* out of reset), and in slave mode the master-only baud-rate and slave-
* enable registers are write-ignored so the block never drives SCLK.
* Switch it to master before any SSI_Init() so that configuration sticks.
*/
SSI_SetRole((void *)priv->base, AMEBA_SSI_MASTER);
Pinmux_Config(priv->clkpin, priv->clkfid);
Pinmux_Config(priv->mosipin, priv->mosifid);
Pinmux_Config(priv->misopin, priv->misofid);
/* Configure the chip-select pad as a GPIO output, de-asserted (high). */
RCC_PeriphClockCmd(AMEBA_APBPERIPH_GPIO, AMEBA_APBPERIPH_GPIO,
AMEBA_ENABLE);
memset(&gpio, 0, sizeof(gpio));
gpio.mode = AMEBA_GPIO_MODE_OUT;
gpio.pupd = AMEBA_GPIO_PUPD_NONE;
gpio.pin = priv->cspin;
GPIO_Init(&gpio);
GPIO_WriteBit(priv->cspin, AMEBA_ENABLE);
/* Latch the peripheral clock and prime the default bus frequency. */
priv->ipclk = ameba_spi_ipclk();
ameba_spi_setfrequency(&priv->dev, AMEBA_SPI_DEFAULT_FREQ);
ret = spi_register(&priv->dev, bus);
if (ret < 0)
{
_err("ERROR: spi_register(/dev/spi%d) failed: %d\n", bus, ret);
nxmutex_destroy(&priv->lock);
kmm_free(priv);
return NULL;
}
return &priv->dev;
}

View file

@ -0,0 +1,92 @@
/****************************************************************************
* arch/arm/src/common/ameba/ameba_spi.h
*
* 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.
*
****************************************************************************/
#ifndef __ARCH_ARM_SRC_COMMON_AMEBA_AMEBA_SPI_H
#define __ARCH_ARM_SRC_COMMON_AMEBA_AMEBA_SPI_H
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <stdint.h>
#include <nuttx/spi/spi.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* The Ameba SPI (DesignWare SSI) controllers exposed to NuttX as SPI master
* buses. The CLK/MOSI/MISO/CS pads are given with the same AMEBA_PA()/
* AMEBA_PB() PinName code used by the GPIO driver (see ameba_gpio.h); any
* pad can be routed to an SPI bus through the pin mux, and the chip select
* is driven as a plain GPIO (software CS) so any pad may serve as CS.
*/
#define AMEBA_SPI0 0
#define AMEBA_SPI1 1
/****************************************************************************
* Public Function Prototypes
****************************************************************************/
#ifdef __cplusplus
#define EXTERN extern "C"
extern "C"
{
#else
#define EXTERN extern
#endif
/****************************************************************************
* Name: ameba_spi_register
*
* Description:
* Configure one Ameba SPI (DesignWare SSI) controller as a master bus,
* registering it with the NuttX SPI character driver at /dev/spiN, where N
* is the bus number. The chip select is driven as a GPIO (software CS).
*
* Input Parameters:
* bus - The controller index, AMEBA_SPI0 or AMEBA_SPI1. Also used as
* the /dev/spiN minor number.
* clkpin - The SCLK pad, encoded with AMEBA_PA()/AMEBA_PB().
* mosipin - The MOSI pad, encoded with AMEBA_PA()/AMEBA_PB().
* misopin - The MISO pad, encoded with AMEBA_PA()/AMEBA_PB().
* cspin - The chip-select pad, driven as an active-low GPIO output.
*
* Returned Value:
* The SPI master lower half on success; NULL on failure.
*
****************************************************************************/
struct spi_dev_s *ameba_spi_register(int bus, uint8_t clkpin,
uint8_t mosipin, uint8_t misopin,
uint8_t cspin);
#undef EXTERN
#ifdef __cplusplus
}
#endif
#endif /* __ARCH_ARM_SRC_COMMON_AMEBA_AMEBA_SPI_H */

View file

@ -52,6 +52,10 @@ if(CONFIG_AMEBA_I2C)
list(APPEND SRCS ${AMEBA_COMMON}/ameba_i2c.c)
endif()
if(CONFIG_AMEBA_SPI)
list(APPEND SRCS ${AMEBA_COMMON}/ameba_spi.c)
endif()
target_include_directories(arch PRIVATE ${AMEBA_COMMON})
target_sources(arch PRIVATE ${SRCS})

View file

@ -62,6 +62,10 @@ ifeq ($(CONFIG_AMEBA_I2C),y)
CHIP_CSRCS += ameba_i2c.c
endif
ifeq ($(CONFIG_AMEBA_SPI),y)
CHIP_CSRCS += ameba_spi.c
endif
############################################################################
# Realtek RTL8721Dx SDK integration
#

View file

@ -146,6 +146,15 @@ ifeq ($(CONFIG_AMEBA_I2C),y)
AMEBA_FWLIB_SRCS += $(AMEBA_SOC)/fwlib/ram_common/ameba_i2c.c
endif
# SPI (DesignWare SSI) register layer. Like I2C, the fwlib SSI API is NOT in
# ROM: the SPI driver (arch/.../common/ameba/ameba_spi.c) calls SSI_Init/
# StructInit/Cmd and SSI_Writeable/Readable/WriteData/ReadData, all compiled
# from this RAM source and linked in (--gc-sections drops the unused DMA/
# interrupt helpers).
ifeq ($(CONFIG_AMEBA_SPI),y)
AMEBA_FWLIB_SRCS += $(AMEBA_SOC)/fwlib/ram_common/ameba_spi.c
endif
# -Wno-int-conversion: the vendored SDK passes NULL to irq_register()'s u32
# "Data" (interrupt context) argument in many places -- an intentional
# NULL-as-context idiom. Silence -Wint-conversion for the SDK fwlib sources

View file

@ -0,0 +1,113 @@
/****************************************************************************
* arch/arm/src/rtl8721dx/ameba_spi_chip.h
*
* 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.
*
****************************************************************************/
#ifndef __ARCH_ARM_SRC_RTL8721DX_AMEBA_SPI_CHIP_H
#define __ARCH_ARM_SRC_RTL8721DX_AMEBA_SPI_CHIP_H
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <stdint.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* Per-chip SPI wiring for RTL8721DX (amebadplus). The shared driver
* (arch/arm/src/common/ameba/ameba_spi.c) includes this header to learn how
* many SPI (DesignWare SSI) controllers the chip exposes and, for each, its
* register base, peripheral-clock masks and crossbar pad-mux codes.
*
* Contract for the other Ameba chips (amebalite / amebasmart / amebagreen2 /
* RTL8720F): supply a same-named header on the chip include path with the
* macros below. What differs per chip, from the fwlib audit:
*
* 1. Controller count and bases: amebadplus exposes two high-speed SSI
* masters, SPI0 and SPI1, in the PERI_HCLK domain. The bases below are
* the NON-secure peripheral aliases (0x4012xxxx); the secure aliases
* (0x5012xxxx) must not be used from the non-secure world.
*
* 2. APBPeriph "function" and "clock" masks are two separate lists because
* RCC_PeriphClockCmd() takes them as distinct arguments. They are
* equal on this chip. NOTE the group selector bit30 is 0 here (unlike
* the Ameba UART/I2C blocks where bit30 is 1), so each chip must supply
* its own values.
*
* 3. Pad mux: SPI0 (master) shares one generic PINMUX_FUNCTION_SPI code
* (8) on all four signals, whereas SPI1 uses per-signal codes
* (CLK/MOSI/MISO/CS). The codes are therefore supplied as one list per
* signal, indexed by controller. The chip-select entries are only used
* when hardware CS is selected; this driver drives CS as a plain GPIO
* (software CS), so the CS pad-mux code is informational.
*/
#define AMEBA_NSPI 2
/* NON-secure SPI register bases (SPI0_REG_BASE / SPI1_REG_BASE). */
#define AMEBA_SPI_BASES { 0x40124000ul, 0x40125000ul }
/* APBPeriph_SPIx (function) and APBPeriph_SPIx_CLOCK masks. Equal on this
* chip; kept as two lists so chips where they differ can supply both. The
* group selector (bit30) is 0 for the SPI block on amebadplus.
*/
#define AMEBA_SPI_APBPERIPH \
{ (((uint32_t)0 << 30) | ((uint32_t)1 << 14)), \
(((uint32_t)0 << 30) | ((uint32_t)1 << 15)) }
#define AMEBA_SPI_APBPERIPH_CLK \
{ (((uint32_t)0 << 30) | ((uint32_t)1 << 14)), \
(((uint32_t)0 << 30) | ((uint32_t)1 << 15)) }
/* Crossbar pad-mux function codes, one list per signal, indexed by
* controller. SPI0 uses the generic PINMUX_FUNCTION_SPI (8) on every
* signal; SPI1 uses its per-signal codes CLK=29 / MISO=30 / MOSI=31 / CS=32.
*/
#define AMEBA_SPI_CLKFID { 8, 29 } /* SPI0 generic, SPI1_CLK */
#define AMEBA_SPI_MOSIFID { 8, 31 } /* SPI0 generic, SPI1_MOSI */
#define AMEBA_SPI_MISOFID { 8, 30 } /* SPI0 generic, SPI1_MISO */
#define AMEBA_SPI_CSFID { 8, 32 } /* SPI0 generic, SPI1_CS */
/* SSI peripheral clock: ip_clk = PLL_ClkGet() / (HPERI divider + 1), the
* frequency the SPI baud divider divides down to make SCLK. On amebadplus
* the HPERI divider field is bits[10:8] of REG_LSYS_CKD_GRP0 in the system-
* control block (non-secure alias 0x41008000 + 0x0220). BOTH the register
* address and the field position are chip-specific -- e.g. on amebalite that
* same bit field is not HPERI -- so the whole computation lives here rather
* than in the shared driver. PLL_ClkGet() is the amebadplus fwlib PLL query;
* each chip header declares the clock-source query its AMEBA_SPI_IPCLK()
* uses (the shared driver includes this header before the macro expands).
*/
extern uint32_t PLL_ClkGet(void);
#define AMEBA_SPI_CKD_GRP0 0x41008220ul
#define AMEBA_SPI_IPCLK() \
(PLL_ClkGet() / \
((((*(volatile uint32_t *)AMEBA_SPI_CKD_GRP0) >> 8) & 0x7) + 1))
#endif /* __ARCH_ARM_SRC_RTL8721DX_AMEBA_SPI_CHIP_H */

View file

@ -0,0 +1,51 @@
#
# This file is autogenerated: PLEASE DO NOT EDIT IT.
#
# You can use "make menuconfig" to make any modifications to the installed .config file.
# You can then do "make savedefconfig" to generate a new defconfig file that includes your
# modifications.
#
# CONFIG_DEBUG_WARN is not set
CONFIG_AMEBA_SPI=y
CONFIG_ARCH="arm"
CONFIG_ARCH_BOARD="pke8721daf"
CONFIG_ARCH_BOARD_PKE8721DAF=y
CONFIG_ARCH_CHIP="rtl8721dx"
CONFIG_ARCH_CHIP_RTL8721DX=y
CONFIG_ARCH_INTERRUPTSTACK=2048
CONFIG_ARCH_STACKDUMP=y
CONFIG_ARMV8M_SYSTICK=y
CONFIG_BUILTIN=y
CONFIG_DEBUG_ASSERTIONS=y
CONFIG_DEBUG_FEATURES=y
CONFIG_DEBUG_FULLOPT=y
CONFIG_DEBUG_SYMBOLS=y
CONFIG_DEFAULT_TASK_STACKSIZE=4096
CONFIG_EXAMPLES_HELLO=y
CONFIG_FS_PROCFS=y
CONFIG_FS_TMPFS=y
CONFIG_IDLETHREAD_STACKSIZE=4096
CONFIG_INIT_ENTRYPOINT="nsh_main"
CONFIG_LIBC_MEMFD_ERROR=y
CONFIG_MM_DEFAULT_ALIGNMENT=32
CONFIG_NSH_BUILTIN_APPS=y
CONFIG_NSH_FILEIOSIZE=512
CONFIG_NSH_READLINE=y
CONFIG_PREALLOC_TIMERS=4
CONFIG_RAM_SIZE=294912
CONFIG_RAM_START=0x20020000
CONFIG_RR_INTERVAL=200
CONFIG_RTL8721DX_FLASH_FS=y
CONFIG_SCHED_HPWORK=y
CONFIG_SCHED_HPWORKPRIORITY=192
CONFIG_SCHED_LPWORK=y
CONFIG_STACK_COLORATION=y
CONFIG_START_DAY=16
CONFIG_START_MONTH=6
CONFIG_START_YEAR=2026
CONFIG_SYSTEM_NSH=y
CONFIG_SYSTEM_NSH_STACKSIZE=2500
CONFIG_SYSTEM_SPITOOL=y
CONFIG_TIMER=y
CONFIG_TIMER_ARCH=y
CONFIG_USEC_PER_TICK=1000

View file

@ -34,11 +34,16 @@ if(CONFIG_AMEBA_I2C)
list(APPEND SRCS rtl8721dx_i2c.c)
endif()
if(CONFIG_AMEBA_SPI)
list(APPEND SRCS rtl8721dx_spi.c)
endif()
target_sources(board PRIVATE ${SRCS})
if(CONFIG_AMEBA_GPIO
OR CONFIG_AMEBA_UART
OR CONFIG_AMEBA_I2C)
OR CONFIG_AMEBA_I2C
OR CONFIG_AMEBA_SPI)
# The board pin/UART tables pull in the shared driver's public headers from
# arch/arm/src/common/ameba/, not on the default board include path.
target_include_directories(board

View file

@ -51,4 +51,13 @@ CSRCS += rtl8721dx_i2c.c
CFLAGS += ${INCDIR_PREFIX}$(TOPDIR)$(DELIM)arch$(DELIM)arm$(DELIM)src$(DELIM)common$(DELIM)ameba
endif
ifeq ($(CONFIG_AMEBA_SPI),y)
CSRCS += rtl8721dx_spi.c
# The board SPI table pulls in the shared driver's public header from
# arch/arm/src/common/ameba/, which is not on the default board include path.
CFLAGS += ${INCDIR_PREFIX}$(TOPDIR)$(DELIM)arch$(DELIM)arm$(DELIM)src$(DELIM)common$(DELIM)ameba
endif
include $(TOPDIR)/boards/Board.mk

View file

@ -138,6 +138,16 @@ int rtl8721dx_bringup(void)
}
#endif
#ifdef CONFIG_AMEBA_SPI
/* Register the board's SPI master buses at /dev/spiN. */
ret = rtl8721dx_spi_initialize();
if (ret < 0)
{
syslog(LOG_ERR, "ERROR: rtl8721dx_spi_initialize failed: %d\n", ret);
}
#endif
/* Install the inter-core HW IPC-semaphore RTOS hooks LAST -- after all the
* flash / WHC bring-up above, and just before this (board_late_initialize)
* path returns and nx_start() hands off to the init task.

View file

@ -108,6 +108,19 @@ int rtl8721dx_uart_initialize(void);
int rtl8721dx_i2c_initialize(void);
#endif
#ifdef CONFIG_AMEBA_SPI
/****************************************************************************
* Name: rtl8721dx_spi_initialize
*
* Description:
* Register the board's SPI master buses at /dev/spiN
* (boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_spi.c).
*
****************************************************************************/
int rtl8721dx_spi_initialize(void);
#endif
#ifdef CONFIG_RTL8721DX_FLASH_FS
/****************************************************************************
* Name: ameba_flash_fs_initialize

View file

@ -0,0 +1,103 @@
/****************************************************************************
* boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_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/param.h>
#include <syslog.h>
#include <errno.h>
#include "ameba_gpio.h"
#include "ameba_spi.h"
#include "rtl8721dx_pke8721daf.h"
#ifdef CONFIG_AMEBA_SPI
/****************************************************************************
* Private Types
****************************************************************************/
/* One entry per SPI bus exposed to NuttX at /dev/spiN. The CLK/MOSI/MISO/CS
* pads below are examples used by the `spi` config (system/spi spitool);
* adjust them to match your board's wiring.
*/
struct rtl8721dx_spi_s
{
int bus; /* Controller index (AMEBA_SPI0/AMEBA_SPI1) */
uint8_t clkpin; /* SCLK pad (AMEBA_PA()/AMEBA_PB() encoding) */
uint8_t mosipin; /* MOSI pad */
uint8_t misopin; /* MISO pad */
uint8_t cspin; /* Chip-select pad (software CS GPIO) */
};
/****************************************************************************
* Private Data
****************************************************************************/
static const struct rtl8721dx_spi_s g_spi_buses[] =
{
{
AMEBA_SPI0, AMEBA_PA(14), AMEBA_PA(15), AMEBA_PA(16), AMEBA_PA(17)
},
{
AMEBA_SPI1, AMEBA_PB(18), AMEBA_PB(19), AMEBA_PB(20), AMEBA_PB(21)
},
};
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: rtl8721dx_spi_initialize
*
* Description:
* Register the board's SPI master buses at /dev/spiN.
*
****************************************************************************/
int rtl8721dx_spi_initialize(void)
{
int i;
for (i = 0; i < (int)nitems(g_spi_buses); i++)
{
if (ameba_spi_register(g_spi_buses[i].bus, g_spi_buses[i].clkpin,
g_spi_buses[i].mosipin, g_spi_buses[i].misopin,
g_spi_buses[i].cspin) == NULL)
{
syslog(LOG_ERR,
"ERROR: ameba_spi_register(/dev/spi%d) failed\n",
g_spi_buses[i].bus);
return -ENODEV;
}
}
return OK;
}
#endif /* CONFIG_AMEBA_SPI */

View file

@ -275,6 +275,7 @@ static const char *g_white_prefix[] =
"FLASH_",
"GPIO_",
"Get_OSC131_", /* Get_OSC131_STATE — Ameba SDK RTC accessor */
"HPERI_", /* HPERI_ClkGet — amebagreen2 SPI ip_clk query */
"I2C_", /* I2C_Init, I2C_MasterWrite, I2C_InitTypeDef, etc. */
"IPC_",
"LOGUART_",
@ -282,14 +283,17 @@ static const char *g_white_prefix[] =
"OSC4M_",
"OSC131K_",
"PAD_",
"PLL_", /* PLL_ClkGet — amebadplus SPI ip_clk query */
"Pinmux_",
"RCC_",
"RTC_", /* RTC_InitTypeDef, RTC_Enable, RTC_SetTime, etc. */
"RTCIO_",
"SDM32K_", /* SDM32K_Enable */
"SSI_", /* SSI_Init, SSI_SetRole, SSI_WriteData, etc. */
"Set_OSC131_", /* Set_OSC131_STATE — Ameba SDK RTC accessor */
"SYSCFG_",
"SYSTIMER_",
"SYS_PLL_", /* SYS_PLL_ClkGet — RTL8720F SPI ip_clk query */
"UART_",
"SystemCoreClock", /* SystemCoreClock, SystemCoreClockUpdate */
"cmse_", /* ARM CMSE TrustZone intrinsics (arm_cmse.h) */