685 lines
18 KiB
C
685 lines
18 KiB
C
/*
|
|
* Copyright 2022 TOKITA Hiroshi <tokita.hiroshi@fujitsu.com>
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT arm_pl022
|
|
|
|
#include <errno.h>
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/drivers/spi.h>
|
|
#include <zephyr/sys/util.h>
|
|
#include <soc.h>
|
|
#if IS_ENABLED(CONFIG_PINCTRL)
|
|
#include <zephyr/drivers/pinctrl.h>
|
|
#endif
|
|
|
|
#define LOG_LEVEL CONFIG_SPI_LOG_LEVEL
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(spi_pl022);
|
|
|
|
#include "spi_context.h"
|
|
|
|
#define SSP_MASK(regname, name) GENMASK(SSP_##regname##_##name##_MSB, SSP_##regname##_##name##_LSB)
|
|
|
|
/* PL022 Register definitions */
|
|
|
|
/*
|
|
* Macros to access SSP Registers with their offsets
|
|
*/
|
|
#define SSP_CR0(r) (r + 0x000)
|
|
#define SSP_CR1(r) (r + 0x004)
|
|
#define SSP_DR(r) (r + 0x008)
|
|
#define SSP_SR(r) (r + 0x00C)
|
|
#define SSP_CPSR(r) (r + 0x010)
|
|
#define SSP_IMSC(r) (r + 0x014)
|
|
#define SSP_RIS(r) (r + 0x018)
|
|
#define SSP_MIS(r) (r + 0x01C)
|
|
#define SSP_ICR(r) (r + 0x020)
|
|
#define SSP_DMACR(r) (r + 0x024)
|
|
|
|
/*
|
|
* Control Register 0
|
|
*/
|
|
#define SSP_CR0_SCR_MSB 15
|
|
#define SSP_CR0_SCR_LSB 8
|
|
#define SSP_CR0_SPH_MSB 7
|
|
#define SSP_CR0_SPH_LSB 7
|
|
#define SSP_CR0_SPO_MSB 6
|
|
#define SSP_CR0_SPO_LSB 6
|
|
#define SSP_CR0_FRF_MSB 5
|
|
#define SSP_CR0_FRF_LSB 4
|
|
#define SSP_CR0_DSS_MSB 3
|
|
#define SSP_CR0_DSS_LSB 0
|
|
|
|
/* Data size select */
|
|
#define SSP_CR0_MASK_DSS SSP_MASK(CR0, DSS)
|
|
/* Frame format */
|
|
#define SSP_CR0_MASK_FRF SSP_MASK(CR0, FRF)
|
|
/* Polarity */
|
|
#define SSP_CR0_MASK_SPO SSP_MASK(CR0, SPO)
|
|
/* Phase */
|
|
#define SSP_CR0_MASK_SPH SSP_MASK(CR0, SPH)
|
|
/* Serial Clock Rate */
|
|
#define SSP_CR0_MASK_SCR SSP_MASK(CR0, SCR)
|
|
|
|
/*
|
|
* Control Register 1
|
|
*/
|
|
#define SSP_CR1_SOD_MSB 3
|
|
#define SSP_CR1_SOD_LSB 3
|
|
#define SSP_CR1_MS_MSB 2
|
|
#define SSP_CR1_MS_LSB 2
|
|
#define SSP_CR1_SSE_MSB 1
|
|
#define SSP_CR1_SSE_LSB 1
|
|
#define SSP_CR1_LBM_MSB 0
|
|
#define SSP_CR1_LBM_LSB 0
|
|
|
|
/* Loopback Mode */
|
|
#define SSP_CR1_MASK_LBM SSP_MASK(CR1, LBM)
|
|
/* Port Enable */
|
|
#define SSP_CR1_MASK_SSE SSP_MASK(CR1, SSE)
|
|
/* Controller/Peripheral (Master/Slave) select */
|
|
#define SSP_CR1_MASK_MS SSP_MASK(CR1, MS)
|
|
/* Peripheral (Slave) mode output disabled */
|
|
#define SSP_CR1_MASK_SOD SSP_MASK(CR1, SOD)
|
|
|
|
/*
|
|
* Status Register
|
|
*/
|
|
#define SSP_SR_BSY_MSB 4
|
|
#define SSP_SR_BSY_LSB 4
|
|
#define SSP_SR_RFF_MSB 3
|
|
#define SSP_SR_RFF_LSB 3
|
|
#define SSP_SR_RNE_MSB 2
|
|
#define SSP_SR_RNE_LSB 2
|
|
#define SSP_SR_TNF_MSB 1
|
|
#define SSP_SR_TNF_LSB 1
|
|
#define SSP_SR_TFE_MSB 0
|
|
#define SSP_SR_TFE_LSB 0
|
|
|
|
/* TX FIFO empty */
|
|
#define SSP_SR_MASK_TFE SSP_MASK(SR, TFE)
|
|
/* TX FIFO not full */
|
|
#define SSP_SR_MASK_TNF SSP_MASK(SR, TNF)
|
|
/* RX FIFO not empty */
|
|
#define SSP_SR_MASK_RNE SSP_MASK(SR, RNE)
|
|
/* RX FIFO full */
|
|
#define SSP_SR_MASK_RFF SSP_MASK(SR, RFF)
|
|
/* Busy Flag */
|
|
#define SSP_SR_MASK_BSY SSP_MASK(SR, BSY)
|
|
|
|
/*
|
|
* Clock Prescale Register
|
|
*/
|
|
#define SSP_CPSR_CPSDVSR_MSB 7
|
|
#define SSP_CPSR_CPSDVSR_LSB 0
|
|
/* Clock prescale divider */
|
|
#define SSP_CPSR_MASK_CPSDVSR SSP_MASK(CPSR, CPSDVSR)
|
|
|
|
/*
|
|
* Interrupt Mask Set/Clear Register
|
|
*/
|
|
#define SSP_IMSC_TXIM_MSB 3
|
|
#define SSP_IMSC_TXIM_LSB 3
|
|
#define SSP_IMSC_RXIM_MSB 2
|
|
#define SSP_IMSC_RXIM_LSB 2
|
|
#define SSP_IMSC_RTIM_MSB 1
|
|
#define SSP_IMSC_RTIM_LSB 1
|
|
#define SSP_IMSC_RORIM_MSB 0
|
|
#define SSP_IMSC_RORIM_LSB 0
|
|
|
|
/* Receive Overrun Interrupt mask */
|
|
#define SSP_IMSC_MASK_RORIM SSP_MASK(IMSC, RORIM)
|
|
/* Receive timeout Interrupt mask */
|
|
#define SSP_IMSC_MASK_RTIM SSP_MASK(IMSC, RTIM)
|
|
/* Receive FIFO Interrupt mask */
|
|
#define SSP_IMSC_MASK_RXIM SSP_MASK(IMSC, RXIM)
|
|
/* Transmit FIFO Interrupt mask */
|
|
#define SSP_IMSC_MASK_TXIM SSP_MASK(IMSC, TXIM)
|
|
|
|
/*
|
|
* Raw Interrupt Status Register
|
|
*/
|
|
#define SSP_RIS_TXRIS_MSB 3
|
|
#define SSP_RIS_TXRIS_LSB 3
|
|
#define SSP_RIS_RXRIS_MSB 2
|
|
#define SSP_RIS_RXRIS_LSB 2
|
|
#define SSP_RIS_RTRIS_MSB 1
|
|
#define SSP_RIS_RTRIS_LSB 1
|
|
#define SSP_RIS_RORRIS_MSB 0
|
|
#define SSP_RIS_RORRIS_LSB 0
|
|
|
|
/* Receive Overrun Raw Interrupt status */
|
|
#define SSP_RIS_MASK_RORRIS SSP_MASK(RIS, RORRIS)
|
|
/* Receive Timeout Raw Interrupt status */
|
|
#define SSP_RIS_MASK_RTRIS SSP_MASK(RIS, RTRIS)
|
|
/* Receive FIFO Raw Interrupt status */
|
|
#define SSP_RIS_MASK_RXRIS SSP_MASK(RIS, RXRIS)
|
|
/* Transmit FIFO Raw Interrupt status */
|
|
#define SSP_RIS_MASK_TXRIS SSP_MASK(RIS, TXRIS)
|
|
|
|
/*
|
|
* Masked Interrupt Status Register
|
|
*/
|
|
#define SSP_MIS_TXMIS_MSB 3
|
|
#define SSP_MIS_TXMIS_LSB 3
|
|
#define SSP_MIS_RXMIS_MSB 2
|
|
#define SSP_MIS_RXMIS_LSB 2
|
|
#define SSP_MIS_RTMIS_MSB 1
|
|
#define SSP_MIS_RTMIS_LSB 1
|
|
#define SSP_MIS_RORMIS_MSB 0
|
|
#define SSP_MIS_RORMIS_LSB 0
|
|
|
|
/* Receive Overrun Masked Interrupt status */
|
|
#define SSP_MIS_MASK_RORMIS SSP_MASK(MIS, RORMIS)
|
|
/* Receive Timeout Masked Interrupt status */
|
|
#define SSP_MIS_MASK_RTMIS SSP_MASK(MIS, RTMIS)
|
|
/* Receive FIFO Masked Interrupt status */
|
|
#define SSP_MIS_MASK_RXMIS SSP_MASK(MIS, RXMIS)
|
|
/* Transmit FIFO Masked Interrupt status */
|
|
#define SSP_MIS_MASK_TXMIS SSP_MASK(MIS, TXMIS)
|
|
|
|
/*
|
|
* Interrupt Clear Register
|
|
*/
|
|
#define SSP_ICR_RTIC_MSB 1
|
|
#define SSP_ICR_RTIC_LSB 1
|
|
#define SSP_ICR_RORIC_MSB 0
|
|
#define SSP_ICR_RORIC_LSB 0
|
|
|
|
/* Receive Overrun Raw Clear Interrupt bit */
|
|
#define SSP_ICR_MASK_RORIC SSP_MASK(ICR, RORIC)
|
|
/* Receive Timeout Clear Interrupt bit */
|
|
#define SSP_ICR_MASK_RTIC SSP_MASK(ICR, RTIC)
|
|
|
|
/*
|
|
* DMA Control Register
|
|
*/
|
|
#define SSP_DMACR_TXDMAE_MSB 1
|
|
#define SSP_DMACR_TXDMAE_LSB 1
|
|
#define SSP_DMACR_RXDMAE_MSB 0
|
|
#define SSP_DMACR_RXDMAE_LSB 0
|
|
|
|
/* Receive DMA Enable bit */
|
|
#define SSP_DMACR_MASK_RXDMAE SSP_MASK(DMACR, RXDMAE)
|
|
/* Transmit DMA Enable bit */
|
|
#define SSP_DMACR_MASK_TXDMAE SSP_MASK(DMACR, TXDMAE)
|
|
|
|
/* End register definitions */
|
|
|
|
/*
|
|
* Clock Parameter ranges
|
|
*/
|
|
#define CPSDVR_MIN 0x02
|
|
#define CPSDVR_MAX 0xFE
|
|
|
|
#define SCR_MIN 0x00
|
|
#define SCR_MAX 0xFF
|
|
|
|
/* Fifo depth */
|
|
#define SSP_FIFO_DEPTH 8
|
|
|
|
/*
|
|
* Register READ/WRITE macros
|
|
*/
|
|
#define SSP_READ_REG(reg) (*((volatile uint32_t *)reg))
|
|
#define SSP_WRITE_REG(reg, val) (*((volatile uint32_t *)reg) = val)
|
|
|
|
/*
|
|
* Status check macros
|
|
*/
|
|
#define SSP_BUSY(reg) (SSP_READ_REG(SSP_SR(reg)) & SSP_SR_MASK_BSY)
|
|
#define SSP_RX_FIFO_NOT_EMPTY(reg) (SSP_READ_REG(SSP_SR(reg)) & SSP_SR_MASK_RNE)
|
|
#define SSP_TX_FIFO_EMPTY(reg) (SSP_READ_REG(SSP_SR(reg)) & SSP_SR_MASK_TFE)
|
|
#define SSP_TX_FIFO_NOT_FULL(reg) (SSP_READ_REG(SSP_SR(reg)) & SSP_SR_MASK_TNF)
|
|
|
|
/*
|
|
* Max frequency
|
|
*/
|
|
#define MAX_FREQ_CONTROLLER_MODE(cfg) (cfg->pclk / 2)
|
|
#define MAX_FREQ_PERIPHERAL_MODE(cfg) (cfg->pclk / 12)
|
|
|
|
struct spi_pl022_cfg {
|
|
const uint32_t reg;
|
|
const uint32_t pclk;
|
|
#if IS_ENABLED(CONFIG_PINCTRL)
|
|
const struct pinctrl_dev_config *pincfg;
|
|
#endif
|
|
#if IS_ENABLED(CONFIG_SPI_PL022_INTERRUPT)
|
|
void (*irq_config)(const struct device *port);
|
|
#endif
|
|
};
|
|
|
|
struct spi_pl022_data {
|
|
struct spi_context ctx;
|
|
uint32_t tx_count;
|
|
uint32_t rx_count;
|
|
};
|
|
|
|
/* Helper Functions */
|
|
|
|
static inline uint32_t spi_pl022_calc_prescale(const uint32_t pclk, const uint32_t baud)
|
|
{
|
|
uint32_t prescale;
|
|
|
|
/* prescale only can take even number */
|
|
for (prescale = CPSDVR_MIN; prescale < CPSDVR_MAX; prescale += 2) {
|
|
if (pclk < (prescale + 2) * CPSDVR_MAX * baud) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return prescale;
|
|
}
|
|
|
|
static inline uint32_t spi_pl022_calc_postdiv(const uint32_t pclk,
|
|
const uint32_t baud, const uint32_t prescale)
|
|
{
|
|
uint32_t postdiv;
|
|
|
|
for (postdiv = SCR_MAX + 1; postdiv > SCR_MIN + 1; --postdiv) {
|
|
if (pclk / (prescale * (postdiv - 1)) > baud) {
|
|
break;
|
|
}
|
|
}
|
|
return postdiv - 1;
|
|
}
|
|
|
|
static int spi_pl022_configure(const struct device *dev,
|
|
const struct spi_config *spicfg)
|
|
{
|
|
const struct spi_pl022_cfg *cfg = dev->config;
|
|
struct spi_pl022_data *data = dev->data;
|
|
const uint16_t op = spicfg->operation;
|
|
uint32_t prescale;
|
|
uint32_t postdiv;
|
|
uint32_t cr0;
|
|
uint32_t cr1;
|
|
|
|
if (spi_context_configured(&data->ctx, spicfg)) {
|
|
return 0;
|
|
}
|
|
|
|
if (spicfg->frequency > MAX_FREQ_CONTROLLER_MODE(cfg)) {
|
|
LOG_ERR("Frequency is up to %u in controller mode.", MAX_FREQ_CONTROLLER_MODE(cfg));
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
if (op & SPI_TRANSFER_LSB) {
|
|
LOG_ERR("LSB-first not supported");
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
/* Half-duplex mode has not been implemented */
|
|
if (op & SPI_HALF_DUPLEX) {
|
|
LOG_ERR("Half-duplex not supported");
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
/* Peripheral mode has not been implemented */
|
|
if (SPI_OP_MODE_GET(op) != SPI_OP_MODE_MASTER) {
|
|
LOG_ERR("Peripheral mode is not supported");
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
/* Word sizes other than 8 bits has not been implemented */
|
|
if (SPI_WORD_SIZE_GET(op) != 8) {
|
|
LOG_ERR("Word sizes other than 8 bits are not supported");
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
/* configure registers */
|
|
|
|
prescale = spi_pl022_calc_prescale(cfg->pclk, spicfg->frequency);
|
|
postdiv = spi_pl022_calc_postdiv(cfg->pclk, spicfg->frequency, prescale);
|
|
|
|
cr0 = 0;
|
|
cr0 |= (postdiv << SSP_CR0_SCR_LSB);
|
|
cr0 |= (SPI_WORD_SIZE_GET(op) - 1);
|
|
cr0 |= (op & SPI_MODE_CPOL) ? SSP_CR0_MASK_SPO : 0;
|
|
cr0 |= (op & SPI_MODE_CPHA) ? SSP_CR0_MASK_SPH : 0;
|
|
|
|
cr1 = 0;
|
|
cr1 |= SSP_CR1_MASK_SSE; /* Always enable SPI */
|
|
cr1 |= (op & SPI_MODE_LOOP) ? SSP_CR1_MASK_LBM : 0;
|
|
|
|
SSP_WRITE_REG(SSP_CPSR(cfg->reg), prescale);
|
|
SSP_WRITE_REG(SSP_CR0(cfg->reg), cr0);
|
|
SSP_WRITE_REG(SSP_CR1(cfg->reg), cr1);
|
|
|
|
#if IS_ENABLED(CONFIG_SPI_PL022_INTERRUPT)
|
|
SSP_WRITE_REG(SSP_IMSC(cfg->reg),
|
|
SSP_IMSC_MASK_RORIM | SSP_IMSC_MASK_RTIM | SSP_IMSC_MASK_RXIM);
|
|
#endif
|
|
|
|
data->ctx.config = spicfg;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline bool spi_pl022_transfer_ongoing(struct spi_pl022_data *data)
|
|
{
|
|
return spi_context_tx_on(&data->ctx) || spi_context_rx_on(&data->ctx);
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_SPI_PL022_INTERRUPT)
|
|
|
|
static void spi_pl022_async_xfer(const struct device *dev)
|
|
{
|
|
const struct spi_pl022_cfg *cfg = dev->config;
|
|
struct spi_pl022_data *data = dev->data;
|
|
struct spi_context *ctx = &data->ctx;
|
|
/* Process by per chunk */
|
|
size_t chunk_len = spi_context_max_continuous_chunk(ctx);
|
|
uint32_t txrx;
|
|
|
|
/* Read RX FIFO */
|
|
while (SSP_RX_FIFO_NOT_EMPTY(cfg->reg) && (data->rx_count < chunk_len)) {
|
|
txrx = SSP_READ_REG(SSP_DR(cfg->reg));
|
|
|
|
/* Discard received data if rx buffer not assigned */
|
|
if (ctx->rx_buf) {
|
|
*(((uint8_t *)ctx->rx_buf) + data->rx_count) = (uint8_t)txrx;
|
|
}
|
|
data->rx_count++;
|
|
}
|
|
|
|
/* Check transfer finished.
|
|
* The transmission of this chunk is complete if both the tx_count
|
|
* and the rx_count reach greater than or equal to the chunk_len.
|
|
* chunk_len is zero here means the transfer is already complete.
|
|
*/
|
|
if (MIN(data->tx_count, data->rx_count) >= chunk_len && chunk_len > 0) {
|
|
spi_context_update_tx(ctx, 1, chunk_len);
|
|
spi_context_update_rx(ctx, 1, chunk_len);
|
|
if (spi_pl022_transfer_ongoing(data)) {
|
|
/* Next chunk is available, reset the count and continue processing */
|
|
data->tx_count = 0;
|
|
data->rx_count = 0;
|
|
chunk_len = spi_context_max_continuous_chunk(ctx);
|
|
} else {
|
|
/* All data is processed, complete the process */
|
|
spi_context_complete(ctx, 0);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Fill up TX FIFO */
|
|
for (uint32_t i = 0; i < SSP_FIFO_DEPTH; i++) {
|
|
if ((data->tx_count < chunk_len) && SSP_TX_FIFO_NOT_FULL(cfg->reg)) {
|
|
/* Send 0 in the case of read only operation */
|
|
txrx = 0;
|
|
|
|
if (ctx->tx_buf) {
|
|
txrx = *(((uint8_t *)ctx->tx_buf) + data->tx_count);
|
|
}
|
|
SSP_WRITE_REG(SSP_DR(cfg->reg), txrx);
|
|
data->tx_count++;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void spi_pl022_start_async_xfer(const struct device *dev)
|
|
{
|
|
const struct spi_pl022_cfg *cfg = dev->config;
|
|
struct spi_pl022_data *data = dev->data;
|
|
|
|
/* Ensure writable */
|
|
while (!SSP_TX_FIFO_EMPTY(cfg->reg))
|
|
;
|
|
/* Drain RX FIFO */
|
|
while (SSP_RX_FIFO_NOT_EMPTY(cfg->reg))
|
|
SSP_READ_REG(SSP_DR(cfg->reg));
|
|
|
|
data->tx_count = 0;
|
|
data->rx_count = 0;
|
|
|
|
SSP_WRITE_REG(SSP_ICR(cfg->reg), SSP_ICR_MASK_RORIC | SSP_ICR_MASK_RTIC);
|
|
|
|
spi_pl022_async_xfer(dev);
|
|
}
|
|
|
|
static void spi_pl022_isr(const struct device *dev)
|
|
{
|
|
const struct spi_pl022_cfg *cfg = dev->config;
|
|
struct spi_pl022_data *data = dev->data;
|
|
struct spi_context *ctx = &data->ctx;
|
|
uint32_t mis = SSP_READ_REG(SSP_MIS(cfg->reg));
|
|
|
|
if (mis & SSP_MIS_MASK_RORMIS) {
|
|
SSP_WRITE_REG(SSP_IMSC(cfg->reg), 0);
|
|
spi_context_complete(ctx, -EIO);
|
|
} else {
|
|
spi_pl022_async_xfer(dev);
|
|
}
|
|
|
|
SSP_WRITE_REG(SSP_ICR(cfg->reg), SSP_ICR_MASK_RORIC | SSP_ICR_MASK_RTIC);
|
|
}
|
|
|
|
#else
|
|
|
|
static void spi_pl022_xfer(const struct device *dev)
|
|
{
|
|
const struct spi_pl022_cfg *cfg = dev->config;
|
|
struct spi_pl022_data *data = dev->data;
|
|
const size_t chunk_len = spi_context_max_continuous_chunk(&data->ctx);
|
|
const void *txbuf = data->ctx.tx_buf;
|
|
void *rxbuf = data->ctx.rx_buf;
|
|
uint32_t txrx;
|
|
|
|
data->tx_count = 0;
|
|
data->rx_count = 0;
|
|
|
|
/* Ensure writable */
|
|
while (!SSP_TX_FIFO_EMPTY(cfg->reg))
|
|
;
|
|
/* Drain RX FIFO */
|
|
while (SSP_RX_FIFO_NOT_EMPTY(cfg->reg))
|
|
SSP_READ_REG(SSP_DR(cfg->reg));
|
|
|
|
while (data->rx_count < chunk_len || data->tx_count < chunk_len) {
|
|
/* Fill up fifo with available TX data */
|
|
while (SSP_TX_FIFO_NOT_FULL(cfg->reg) && data->tx_count < chunk_len) {
|
|
/* Send 0 in the case of read only operation */
|
|
txrx = 0;
|
|
|
|
if (txbuf) {
|
|
txrx = ((uint8_t *)txbuf)[data->tx_count];
|
|
}
|
|
SSP_WRITE_REG(SSP_DR(cfg->reg), txrx);
|
|
data->tx_count++;
|
|
}
|
|
while (SSP_RX_FIFO_NOT_EMPTY(cfg->reg) && data->rx_count < chunk_len) {
|
|
txrx = SSP_READ_REG(SSP_DR(cfg->reg));
|
|
|
|
/* Discard received data if rx buffer not assigned */
|
|
if (rxbuf) {
|
|
((uint8_t *)rxbuf)[data->rx_count] = (uint8_t)txrx;
|
|
}
|
|
data->rx_count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
static int spi_pl022_transceive_impl(const struct device *dev,
|
|
const struct spi_config *config,
|
|
const struct spi_buf_set *tx_bufs,
|
|
const struct spi_buf_set *rx_bufs,
|
|
struct k_poll_signal *async)
|
|
{
|
|
struct spi_pl022_data *data = dev->data;
|
|
struct spi_context *ctx = &data->ctx;
|
|
int ret;
|
|
|
|
spi_context_lock(&data->ctx, (async ? true : false), async, config);
|
|
|
|
ret = spi_pl022_configure(dev, config);
|
|
if (ret < 0) {
|
|
goto error;
|
|
}
|
|
|
|
spi_context_buffers_setup(ctx, tx_bufs, rx_bufs, 1);
|
|
|
|
spi_context_cs_control(ctx, true);
|
|
|
|
#if IS_ENABLED(CONFIG_SPI_PL022_INTERRUPT)
|
|
spi_pl022_start_async_xfer(dev);
|
|
ret = spi_context_wait_for_completion(ctx);
|
|
#else
|
|
do {
|
|
spi_pl022_xfer(dev);
|
|
spi_context_update_tx(ctx, 1, data->tx_count);
|
|
spi_context_update_rx(ctx, 1, data->rx_count);
|
|
} while (spi_pl022_transfer_ongoing(data));
|
|
|
|
#ifdef CONFIG_SPI_ASYNC
|
|
spi_context_complete(&data->ctx, ret);
|
|
#endif
|
|
#endif
|
|
|
|
spi_context_cs_control(ctx, false);
|
|
|
|
error:
|
|
spi_context_release(&data->ctx, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* API Functions */
|
|
|
|
static int spi_pl022_transceive(const struct device *dev,
|
|
const struct spi_config *config,
|
|
const struct spi_buf_set *tx_bufs,
|
|
const struct spi_buf_set *rx_bufs)
|
|
{
|
|
return spi_pl022_transceive_impl(dev, config, tx_bufs, rx_bufs, NULL);
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_SPI_ASYNC)
|
|
|
|
static int spi_pl022_transceive_async(const struct device *dev,
|
|
const struct spi_config *config,
|
|
const struct spi_buf_set *tx_bufs,
|
|
const struct spi_buf_set *rx_bufs,
|
|
struct k_poll_signal *async)
|
|
{
|
|
return spi_pl022_transceive_impl(dev, config, tx_bufs, rx_bufs, async);
|
|
}
|
|
|
|
#endif
|
|
|
|
static int spi_pl022_release(const struct device *dev,
|
|
const struct spi_config *config)
|
|
{
|
|
struct spi_pl022_data *data = dev->data;
|
|
|
|
spi_context_unlock_unconditionally(&data->ctx);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct spi_driver_api spi_pl022_api = {
|
|
.transceive = spi_pl022_transceive,
|
|
#if IS_ENABLED(CONFIG_SPI_ASYNC)
|
|
.transceive_async = spi_pl022_transceive_async,
|
|
#endif
|
|
.release = spi_pl022_release
|
|
};
|
|
|
|
static int spi_pl022_init(const struct device *dev)
|
|
{
|
|
/* Initialize with lowest frequency */
|
|
const struct spi_config spicfg = {
|
|
.frequency = 0,
|
|
.operation = SPI_WORD_SET(8),
|
|
.slave = 0,
|
|
};
|
|
struct spi_pl022_data *data = dev->data;
|
|
int ret;
|
|
|
|
#if IS_ENABLED(CONFIG_PINCTRL)
|
|
const struct spi_pl022_cfg *cfg = dev->config;
|
|
|
|
ret = pinctrl_apply_state(cfg->pincfg, PINCTRL_STATE_DEFAULT);
|
|
if (ret) {
|
|
LOG_ERR("Failed to apply pinctrl state");
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
#if IS_ENABLED(CONFIG_SPI_PL022_INTERRUPT)
|
|
cfg->irq_config(dev);
|
|
#endif
|
|
|
|
ret = spi_pl022_configure(dev, &spicfg);
|
|
if (ret < 0) {
|
|
LOG_ERR("Failed to configure spi");
|
|
return ret;
|
|
}
|
|
|
|
ret = spi_context_cs_configure_all(&data->ctx);
|
|
if (ret < 0) {
|
|
LOG_ERR("Failed to spi_context configure");
|
|
return ret;
|
|
}
|
|
|
|
/* Make sure the context is unlocked */
|
|
spi_context_unlock_unconditionally(&data->ctx);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_PINCTRL)
|
|
#define PINCTRL_DEFINE(n) PINCTRL_DT_INST_DEFINE(n);
|
|
#define PINCTRL_INIT(n) .pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n),
|
|
#else
|
|
#define PINCTRL_DEFINE(n)
|
|
#define PINCTRL_INIT(n)
|
|
#endif /* CONFIG_PINCTRL */
|
|
|
|
#if IS_ENABLED(CONFIG_SPI_PL022_INTERRUPT)
|
|
#define DECLARE_IRQ_CONFIGURE(idx) \
|
|
static void spi_pl022_irq_config_##idx(const struct device *dev) \
|
|
{ \
|
|
IRQ_CONNECT(DT_INST_IRQN(idx), \
|
|
DT_INST_IRQ(idx, priority), \
|
|
spi_pl022_isr, DEVICE_DT_INST_GET(idx), 0); \
|
|
irq_enable(DT_INST_IRQN(idx)); \
|
|
}
|
|
#define IRQ_HANDLER(idx) .irq_config = spi_pl022_irq_config_##idx,
|
|
#else
|
|
#define DECLARE_IRQ_CONFIGURE(idx)
|
|
#define IRQ_HANDLER(idx)
|
|
#endif
|
|
|
|
#define SPI_PL022_INIT(idx) \
|
|
PINCTRL_DEFINE(idx) \
|
|
DECLARE_IRQ_CONFIGURE(idx) \
|
|
static struct spi_pl022_data spi_pl022_data_##idx = { \
|
|
SPI_CONTEXT_INIT_LOCK(spi_pl022_data_##idx, ctx), \
|
|
SPI_CONTEXT_INIT_SYNC(spi_pl022_data_##idx, ctx), \
|
|
SPI_CONTEXT_CS_GPIOS_INITIALIZE(DT_DRV_INST(idx), ctx) \
|
|
}; \
|
|
static struct spi_pl022_cfg spi_pl022_cfg_##idx = { \
|
|
.reg = DT_INST_REG_ADDR(idx), \
|
|
.pclk = DT_INST_PROP_BY_PHANDLE(idx, clocks, clock_frequency), \
|
|
IRQ_HANDLER(idx) \
|
|
PINCTRL_INIT(idx) \
|
|
}; \
|
|
DEVICE_DT_INST_DEFINE(idx, \
|
|
spi_pl022_init, \
|
|
NULL, \
|
|
&spi_pl022_data_##idx, \
|
|
&spi_pl022_cfg_##idx, \
|
|
POST_KERNEL, \
|
|
CONFIG_SPI_INIT_PRIORITY, \
|
|
&spi_pl022_api);
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(SPI_PL022_INIT)
|