f436aeb12c
Driver did not use pinmux API. Signed-off-by: Gerard Marull-Paretas <gerard.marull@nordicsemi.no>
715 lines
19 KiB
C
715 lines
19 KiB
C
/*
|
|
* Copyright (c) 2022 Microchip Technology Inc.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT microchip_xec_qmspi_full_duplex
|
|
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(spi_xec, CONFIG_SPI_LOG_LEVEL);
|
|
|
|
#include <errno.h>
|
|
#include <zephyr/device.h>
|
|
#include <zephyr/drivers/clock_control/mchp_xec_clock_control.h>
|
|
#include <zephyr/drivers/gpio.h>
|
|
#include <zephyr/drivers/interrupt_controller/intc_mchp_xec_ecia.h>
|
|
#include <zephyr/drivers/pinctrl.h>
|
|
#include <zephyr/drivers/spi.h>
|
|
#include <zephyr/dt-bindings/interrupt-controller/mchp-xec-ecia.h>
|
|
#include <zephyr/sys/__assert.h>
|
|
#include <zephyr/sys/sys_io.h>
|
|
#include <zephyr/sys/util.h>
|
|
#include <soc.h>
|
|
|
|
#include "spi_context.h"
|
|
#include "spi_xec_qmspi_full_duplex.h"
|
|
|
|
#if XEC_QSPI_TX_FIFO_SIZE < XEC_QSPI_RX_FIFO_SIZE
|
|
#define XEC_QSPI_CHUNK_SIZE XEC_QSPI_TX_FIFO_SIZE
|
|
#else
|
|
#define XEC_QSPI_CHUNK_SIZE XEC_QSPI_RX_FIFO_SIZE
|
|
#endif
|
|
|
|
/* spin loops waiting for HW to clear soft reset bit */
|
|
#define XEC_QSPI_SRST_LOOPS 16
|
|
|
|
/* microseconds for busy wait and total wait interval */
|
|
#define XEC_QSPI_WAIT_INTERVAL 8
|
|
#define XEC_QSPI_WAIT_COUNT 64
|
|
#define XEC_QSPI_WAIT_FULL_FIFO 1024
|
|
|
|
/* 3 Tap Regs - Tap, Tap Ctrl, Tap Adjust */
|
|
#define TAP_REGS_MAX 3
|
|
|
|
#define CLOCK_DIV_0_VALUE 0x10000
|
|
|
|
/*
|
|
* Maximum number of units to generate clocks with data lines
|
|
* tri-stated depends upon bus width. Maximum bus width is 4.
|
|
*/
|
|
#define XEC_QSPI_MAX_TSCLK_UNITS (MCHP_QMSPI_C_MAX_UNITS / 4)
|
|
|
|
#define XEC_QSPI_HALF_DUPLEX 0
|
|
#define XEC_QSPI_FULL_DUPLEX 1
|
|
#define XEC_QSPI_DUAL 2
|
|
#define XEC_QSPI_QUAD 4
|
|
|
|
#define XEC_QSPI_STS_ERRORS (BIT(XEC_QSPI_STS_TXB_ERR_POS) | \
|
|
BIT(XEC_QSPI_STS_RXB_ERR_POS) | \
|
|
BIT(XEC_QSPI_STS_PROG_ERR_POS) | \
|
|
BIT(XEC_QSPI_STS_LDMA_RX_ERR_POS) | \
|
|
BIT(XEC_QSPI_STS_LDMA_TX_ERR_POS))
|
|
|
|
#define XEC_QSPI_IEN_DONE_ERR (BIT(XEC_QSPI_IEN_XFR_DONE_POS) | \
|
|
BIT(XEC_QSPI_IEN_TXB_ERR_POS) | \
|
|
BIT(XEC_QSPI_IEN_RXB_ERR_POS) | \
|
|
BIT(XEC_QSPI_IEN_PROG_ERR_POS) | \
|
|
BIT(XEC_QSPI_IEN_LDMA_RX_ERR_POS) | \
|
|
BIT(XEC_QSPI_IEN_LDMA_TX_ERR_POS));
|
|
|
|
/* Device constant configuration parameters */
|
|
struct spi_xec_qspi_config {
|
|
struct qmspi_regs *regs;
|
|
uint32_t cs1_freq;
|
|
uint32_t cs_timing;
|
|
uint16_t taps_adj;
|
|
uint8_t girq;
|
|
uint8_t girq_pos;
|
|
uint8_t girq_nvic_aggr;
|
|
uint8_t girq_nvic_direct;
|
|
uint8_t irq_pri;
|
|
uint8_t pcr_idx;
|
|
uint8_t pcr_pos;
|
|
uint8_t chip_sel;
|
|
uint8_t width; /* 0(half) 1(single), 2(dual), 4(quad) */
|
|
uint8_t unused[2];
|
|
const struct pinctrl_dev_config *pcfg;
|
|
};
|
|
|
|
#define XEC_QMSPI_XFR_FLAG_TX BIT(0)
|
|
#define XEC_QMSPI_XFR_FLAG_STARTED BIT(1)
|
|
|
|
/* Device run time data */
|
|
struct spi_xec_qspi_data {
|
|
struct spi_context ctx;
|
|
uint32_t qstatus;
|
|
uint8_t np; /* number of data pins: 1, 2, or 4 */
|
|
};
|
|
|
|
static int xec_qspi_spin_yield(int *counter, int max_count)
|
|
{
|
|
*counter = *counter + 1;
|
|
|
|
if (*counter > max_count) {
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
k_busy_wait(XEC_QSPI_WAIT_INTERVAL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* reset QMSPI controller with save/restore of timing registers.
|
|
* Some QMSPI timing register may be modified by the Boot-ROM OTP
|
|
* values.
|
|
*/
|
|
static void xec_qspi_reset(struct qmspi_regs *regs)
|
|
{
|
|
uint32_t taps[TAP_REGS_MAX];
|
|
uint32_t malt1;
|
|
uint32_t cstm;
|
|
uint32_t mode;
|
|
uint32_t cnt = XEC_QSPI_SRST_LOOPS;
|
|
|
|
taps[0] = regs->TM_TAPS;
|
|
taps[1] = regs->TM_TAPS_ADJ;
|
|
taps[2] = regs->TM_TAPS_CTRL;
|
|
malt1 = regs->MODE_ALT1;
|
|
cstm = regs->CSTM;
|
|
mode = regs->MODE;
|
|
regs->MODE = MCHP_QMSPI_M_SRST;
|
|
while (regs->MODE & MCHP_QMSPI_M_SRST) {
|
|
if (cnt == 0) {
|
|
break;
|
|
}
|
|
cnt--;
|
|
}
|
|
regs->MODE = 0;
|
|
regs->MODE = mode & ~MCHP_QMSPI_M_ACTIVATE;
|
|
regs->CSTM = cstm;
|
|
regs->MODE_ALT1 = malt1;
|
|
regs->TM_TAPS = taps[0];
|
|
regs->TM_TAPS_ADJ = taps[1];
|
|
regs->TM_TAPS_CTRL = taps[2];
|
|
}
|
|
|
|
static uint32_t qspi_source_clock_freq(void)
|
|
{
|
|
struct pcr_regs const *pcr =
|
|
(struct pcr_regs *)(DT_REG_ADDR_BY_IDX(DT_NODELABEL(pcr), 0));
|
|
|
|
if (pcr->TURBO_CLK & MCHP_PCR_TURBO_CLK_96M) {
|
|
return MEC172X_QSPI_TURBO_SRC_CLOCK_HZ;
|
|
}
|
|
return MEC172X_QSPI_SRC_CLOCK_HZ;
|
|
}
|
|
|
|
/*
|
|
* Calculate QMSPI frequency divider register field value based upon
|
|
* the configured QMSPI input frequency: 48 or 96 MHz.
|
|
* The hardware divider is encoded as:
|
|
* 0 is divide by full divider range: 256 or 65536.
|
|
* Non-zero is divide by that value: 1 to 256 or 655356.
|
|
*/
|
|
static uint32_t qspi_encoded_fdiv(uint32_t freq_hz)
|
|
{
|
|
uint32_t fdiv = 1;
|
|
uint32_t src_clk = qspi_source_clock_freq();
|
|
|
|
if (freq_hz < (src_clk / 256u)) {
|
|
fdiv = 0; /* HW fdiv = 0 is divide by 256 */
|
|
} else if (freq_hz < src_clk) {
|
|
/* truncated integer division may result in lower freq. */
|
|
fdiv = src_clk / freq_hz;
|
|
}
|
|
|
|
return fdiv;
|
|
}
|
|
|
|
/* Program QMSPI frequency divider field in mode register */
|
|
static void qspi_set_frequency(struct qmspi_regs *regs, uint32_t freq_hz)
|
|
{
|
|
uint32_t fdiv, mode;
|
|
|
|
fdiv = qspi_encoded_fdiv(freq_hz);
|
|
mode = regs->MODE & ~(XEC_QSPI_M_CLK_DIV_MASK);
|
|
mode |= ((fdiv << XEC_QSPI_M_CLK_DIV_POS) & XEC_QSPI_M_CLK_DIV_MASK);
|
|
regs->MODE = mode;
|
|
}
|
|
|
|
static uint32_t qspi_get_frequency(struct qmspi_regs *regs)
|
|
{
|
|
uint32_t src_clk = qspi_source_clock_freq();
|
|
uint32_t fdiv = (regs->MODE & XEC_QSPI_M_CLK_DIV_MASK)
|
|
>> XEC_QSPI_M_CLK_DIV_POS;
|
|
|
|
if (fdiv == 0) {
|
|
fdiv = CLOCK_DIV_0_VALUE;
|
|
}
|
|
|
|
return (src_clk / fdiv);
|
|
}
|
|
|
|
/*
|
|
* SPI signalling mode: CPOL and CPHA
|
|
* CPOL = 0 is clock idle state is low, 1 is clock idle state is high
|
|
* CPHA = 0 Transmitter changes data on trailing of preceding clock cycle.
|
|
* Receiver samples data on leading edge of clock cyle.
|
|
* 1 Transmitter changes data on leading edge of current clock cycle.
|
|
* Receiver samples data on the trailing edge of clock cycle.
|
|
* SPI Mode nomenclature:
|
|
* Mode CPOL CPHA
|
|
* 0 0 0
|
|
* 1 0 1
|
|
* 2 1 0
|
|
* 3 1 1
|
|
* QMSPI has three controls, CPOL, CPHA for output and CPHA for input.
|
|
* SPI frequency < 48MHz
|
|
* Mode 0: CPOL=0 CHPA=0 (CHPA_MISO=0 and CHPA_MOSI=0)
|
|
* Mode 3: CPOL=1 CHPA=1 (CHPA_MISO=1 and CHPA_MOSI=1)
|
|
* Data sheet recommends when QMSPI set at >= 48MHz, sample and change data
|
|
* on the same edge.
|
|
* Mode 0: CPOL=0 CPHA=0 (CHPA_MISO=1 and CHPA_MOSI=0)
|
|
* Mode 3: CPOL=1 CPHA=1 (CHPA_MISO=0 and CHPA_MOSI=1)
|
|
*
|
|
* smode_tbl and smode48_tbl has the byte values for Mode 0, 1, 2, 3
|
|
*
|
|
* Byte values correspond to bits 8. 9. 10 in QMSPI Mode Register
|
|
* Bit 8 - CPOL
|
|
* Bit 9 - CHPA MOSI
|
|
* Bit 10 - CHPA MISO
|
|
*/
|
|
const uint8_t smode_tbl[4] = {
|
|
0x00u, 0x06u, 0x01u, 0x07u
|
|
};
|
|
|
|
const uint8_t smode48_tbl[4] = {
|
|
0x04u, 0x02u, 0x05u, 0x03u
|
|
};
|
|
|
|
static void qspi_set_signalling_mode(struct qmspi_regs *regs, uint32_t smode)
|
|
{
|
|
const uint8_t *ptbl;
|
|
uint32_t m;
|
|
|
|
ptbl = smode_tbl;
|
|
if (qspi_get_frequency(regs) >= MHZ(48)) {
|
|
ptbl = smode48_tbl;
|
|
}
|
|
|
|
m = (uint32_t)ptbl[smode & GENMASK(1, 0)];
|
|
regs->MODE = (regs->MODE & ~(XEC_QSPI_M_CP_MSK)) |
|
|
(m << XEC_QSPI_M_CPOL_POS);
|
|
}
|
|
|
|
static uint8_t npins_from_spi_config(const struct spi_config *config)
|
|
{
|
|
uint8_t lines = 1u;
|
|
|
|
if (IS_ENABLED(CONFIG_SPI_EXTENDED_MODES)) {
|
|
switch (config->operation & SPI_LINES_MASK) {
|
|
case SPI_LINES_DUAL:
|
|
lines = 2u;
|
|
break;
|
|
case SPI_LINES_QUAD:
|
|
lines = 4u;
|
|
break;
|
|
default:
|
|
lines = 1u;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return lines;
|
|
}
|
|
|
|
/*
|
|
* Configure QSPI.
|
|
* NOTE: QSPI Port 0 has two chip selects available. Ports 1 & 2
|
|
* support only CS0#.
|
|
*/
|
|
static int qspi_configure(const struct device *dev,
|
|
const struct spi_config *spi_conf)
|
|
{
|
|
const struct spi_xec_qspi_config * const cfg = dev->config;
|
|
struct spi_xec_qspi_data * const qdata = dev->data;
|
|
struct qmspi_regs * const regs = cfg->regs;
|
|
struct spi_context *ctx = &qdata->ctx;
|
|
uint32_t smode;
|
|
|
|
if (spi_context_configured(ctx, spi_conf)) {
|
|
return 0;
|
|
}
|
|
|
|
if (spi_conf->operation & (SPI_TRANSFER_LSB | SPI_OP_MODE_SLAVE
|
|
| SPI_MODE_LOOP)) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
if ((IS_ENABLED(CONFIG_SPI_EXTENDED_MODES) &&
|
|
((spi_conf->operation & SPI_LINES_MASK) != SPI_LINES_SINGLE))) {
|
|
LOG_ERR("Single(full-duplex) only");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (spi_conf->operation & SPI_CS_ACTIVE_HIGH) {
|
|
LOG_ERR("CS active high not supported");
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
if (SPI_WORD_SIZE_GET(spi_conf->operation) != 8) {
|
|
LOG_ERR("Word size != 8 not supported");
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
smode = SPI_LINES_SINGLE;
|
|
qdata->np = npins_from_spi_config(spi_conf);
|
|
regs->CTRL = smode;
|
|
|
|
/* Use the requested or next highest possible frequency */
|
|
qspi_set_frequency(regs, spi_conf->frequency);
|
|
|
|
smode = 0;
|
|
if ((spi_conf->operation & SPI_MODE_CPHA) != 0U) {
|
|
smode |= (BIT(0));
|
|
}
|
|
|
|
if ((spi_conf->operation & SPI_MODE_CPOL) != 0U) {
|
|
smode |= (BIT(1));
|
|
}
|
|
|
|
qspi_set_signalling_mode(regs, smode);
|
|
|
|
/* chip select */
|
|
smode = regs->MODE & ~(XEC_QSPI_M_CS_SEL_MSK);
|
|
if (cfg->chip_sel == 0) {
|
|
smode |= XEC_QSPI_M_CS0_SEL;
|
|
} else {
|
|
smode |= XEC_QSPI_M_CS1_SEL;
|
|
}
|
|
regs->MODE = smode;
|
|
|
|
/* chip select timing */
|
|
regs->CSTM = cfg->cs_timing;
|
|
|
|
regs->TM_TAPS_ADJ = cfg->taps_adj;
|
|
/* CS1 alternate mode (frequency) */
|
|
regs->MODE_ALT1 = 0;
|
|
if (cfg->cs1_freq) {
|
|
uint32_t fdiv = qspi_encoded_fdiv(cfg->cs1_freq);
|
|
|
|
regs->MODE_ALT1 = (fdiv << XEC_QSPI_MALT1_CLK_DIV_POS) &
|
|
XEC_QSPI_MALT1_CLK_DIV_MSK;
|
|
regs->MODE_ALT1 |= BIT(XEC_QSPI_MALT1_EN_POS);
|
|
}
|
|
|
|
ctx->config = spi_conf;
|
|
|
|
regs->MODE |= BIT(XEC_QSPI_M_ACTV_POS);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static uint32_t encode_npins(uint8_t npins)
|
|
{
|
|
uint32_t encoding = XEC_QSPI_C_IFC_1X;
|
|
|
|
if (npins == 4) {
|
|
encoding = XEC_QSPI_C_IFC_4X;
|
|
} else if (npins == 2) {
|
|
encoding = XEC_QSPI_C_IFC_2X;
|
|
} else {
|
|
encoding = XEC_QSPI_C_IFC_1X;
|
|
}
|
|
|
|
return encoding;
|
|
}
|
|
|
|
/* Allocate QMSPI HW descriptor registers to process the given number of bytes
|
|
* or until all descriptors are allocated. Returns the number of remaining
|
|
* bytes not in allocation. Updates the word pointed to by ndescr with the
|
|
* number of descriptors allocated. Descriptor allocation always begins with
|
|
* descriptor 0. Descriptor QMSPI unit size, number of units, and next
|
|
* descriptor fields are programmed other fields in descr_base are preserved.
|
|
*/
|
|
static size_t descr_alloc(struct qmspi_regs * const regs, size_t nbytes,
|
|
uint32_t descr_base, uint32_t *ndescr)
|
|
{
|
|
size_t nb = nbytes;
|
|
uint32_t idx = 0u;
|
|
uint32_t descr = 0u;
|
|
|
|
descr_base &= ~(XEC_QSPI_C_Q_XFR_UNITS_MSK | XEC_QSPI_C_Q_NUNITS_MSK |
|
|
XEC_QSPI_C_FN_DESCR_MSK);
|
|
|
|
while (nb && (idx < 16)) {
|
|
if (nb <= XEC_QSPI_C_Q_NUNITS_MAX) {
|
|
descr = nb;
|
|
descr <<= XEC_QSPI_C_Q_NUNITS_POS;
|
|
descr |= XEC_QSPI_C_Q_XFR_UNITS_1B;
|
|
nb = 0u;
|
|
} else {
|
|
descr = (nb >> 4);
|
|
nb -= (descr << 4);
|
|
descr <<= XEC_QSPI_C_Q_NUNITS_POS;
|
|
descr |= XEC_QSPI_C_Q_XFR_UNITS_16B;
|
|
}
|
|
|
|
descr |= (XEC_QSPI_C_IFC_1X | XEC_QSPI_C_TX_EN_DATA |
|
|
BIT(XEC_QSPI_C_RX_EN_POS));
|
|
descr |= XEC_QSPI_C_FN_DESCR((idx + 1u));
|
|
|
|
regs->DESCR[idx++] = descr_base | descr;
|
|
}
|
|
|
|
if (idx) {
|
|
regs->DESCR[idx - 1u] |= BIT(XEC_QSPI_D_DESCR_LAST_POS);
|
|
}
|
|
|
|
if (ndescr) {
|
|
*ndescr = idx;
|
|
}
|
|
|
|
return nb;
|
|
}
|
|
|
|
/* Polling full-duplex transfer using QMSPI descriptors and FIFO's.
|
|
* Allocate hardware descriptors for maximum total transfer size.
|
|
* Descriptors configured for both transmit and receive.
|
|
* If TX context has no data, transmit 0 bytes.
|
|
* If RX context has no buffer, throw away received bytes.
|
|
* If user set SPI_HOLD_ON_CS flag configure to not de-assert chip select
|
|
* when the last descriptor is completed. When transfer is completed without
|
|
* error mark context complete.
|
|
*/
|
|
static int xec_qspi_fd_descr(const struct device *dev,
|
|
const struct spi_config *spi_conf,
|
|
const struct spi_buf_set *tx_bufs,
|
|
const struct spi_buf_set *rx_bufs)
|
|
{
|
|
const struct spi_xec_qspi_config *cfg = dev->config;
|
|
struct spi_xec_qspi_data * const qdata = dev->data;
|
|
struct qmspi_regs * const regs = cfg->regs;
|
|
struct spi_context *ctx = &qdata->ctx;
|
|
uint32_t descr_base, nd;
|
|
size_t len, ntx, nrx, rem, xfr_len;
|
|
uint8_t txb, rxb;
|
|
bool close = true;
|
|
|
|
xfr_len = MAX(spi_context_total_tx_len(ctx), spi_context_total_rx_len(ctx));
|
|
if (!xfr_len) {
|
|
return 0;
|
|
}
|
|
|
|
regs->CTRL = 0;
|
|
regs->EXE = BIT(XEC_QSPI_EXE_CLR_FIFOS_POS);
|
|
regs->STS |= regs->STS;
|
|
regs->CTRL = BIT(XEC_QSPI_C_DESCR_MODE_EN_POS);
|
|
|
|
descr_base = encode_npins(qdata->np);
|
|
descr_base |= (XEC_QSPI_C_TX_EN_DATA | BIT(XEC_QSPI_C_RX_EN_POS) |
|
|
BIT(XEC_QSPI_C_CLOSE_POS));
|
|
|
|
if (spi_conf->operation & SPI_HOLD_ON_CS) {
|
|
close = false;
|
|
}
|
|
|
|
len = xfr_len;
|
|
while (len) {
|
|
nd = 0u;
|
|
rem = descr_alloc(regs, len, descr_base, &nd);
|
|
|
|
__ASSERT_NO_MSG(nd != 0);
|
|
__ASSERT_NO_MSG(rem < len);
|
|
|
|
if ((rem == 0) && close) {
|
|
regs->DESCR[nd - 1u] |= BIT(XEC_QSPI_C_CLOSE_POS);
|
|
}
|
|
|
|
/* NOTE: start with TX FIFO empty causes read-only TX stall
|
|
* status to be set.
|
|
*/
|
|
regs->EXE = BIT(XEC_QSPI_EXE_START_POS);
|
|
|
|
ntx = len - rem;
|
|
nrx = ntx;
|
|
while (ntx || nrx) {
|
|
if (regs->STS & XEC_QSPI_STS_ERRORS) {
|
|
LOG_ERR("QMSPI errors(sts): 0x%08x\n", regs->STS);
|
|
return -EIO;
|
|
}
|
|
if (ntx && !(regs->STS & BIT(XEC_QSPI_STS_TXB_FULL_POS))) {
|
|
txb = 0u;
|
|
if (ctx->tx_buf) {
|
|
txb = *(uint8_t *)(ctx->tx_buf);
|
|
}
|
|
sys_write8(txb, (mem_addr_t)®s->TX_FIFO);
|
|
spi_context_update_tx(ctx, 1, 1);
|
|
ntx--;
|
|
}
|
|
if (nrx && !(regs->STS & BIT(XEC_QSPI_STS_RXB_EMPTY_POS))) {
|
|
rxb = sys_read8((mem_addr_t)®s->RX_FIFO);
|
|
if (ctx->rx_buf) {
|
|
*(uint8_t *)(ctx->rx_buf) = rxb;
|
|
}
|
|
spi_context_update_rx(ctx, 1, 1);
|
|
nrx--;
|
|
}
|
|
}
|
|
|
|
len = rem;
|
|
}
|
|
|
|
spi_context_complete(ctx, dev, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int xec_qspi_xfr(const struct device *dev,
|
|
const struct spi_config *spi_conf,
|
|
const struct spi_buf_set *tx_bufs,
|
|
const struct spi_buf_set *rx_bufs,
|
|
bool asynchronous)
|
|
{
|
|
const struct spi_xec_qspi_config *cfg = dev->config;
|
|
struct spi_xec_qspi_data * const qdata = dev->data;
|
|
struct qmspi_regs * const regs = cfg->regs;
|
|
struct spi_context *ctx = &qdata->ctx;
|
|
int ret = 0;
|
|
|
|
spi_context_lock(ctx, asynchronous, NULL, NULL, spi_conf);
|
|
|
|
ret = qspi_configure(dev, spi_conf);
|
|
if (ret != 0) {
|
|
spi_context_release(ctx, ret);
|
|
return ret;
|
|
}
|
|
|
|
spi_context_cs_control(&qdata->ctx, true);
|
|
spi_context_buffers_setup(ctx, tx_bufs, rx_bufs, 1);
|
|
|
|
ret = xec_qspi_fd_descr(dev, spi_conf, tx_bufs, rx_bufs);
|
|
if (ret) {
|
|
regs->EXE = BIT(XEC_QSPI_EXE_STOP_POS);
|
|
spi_context_unlock_unconditionally(&qdata->ctx);
|
|
return ret;
|
|
}
|
|
|
|
if (!(spi_conf->operation & SPI_HOLD_ON_CS)) {
|
|
spi_context_cs_control(ctx, false);
|
|
}
|
|
|
|
/* Attempts to take semaphore with timeout. Descriptor transfer
|
|
* routine completes the context giving the semaphore.
|
|
*/
|
|
ret = spi_context_wait_for_completion(ctx);
|
|
/* gives semaphore */
|
|
spi_context_release(ctx, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int xec_qspi_transceive(const struct device *dev,
|
|
const struct spi_config *spi_conf,
|
|
const struct spi_buf_set *tx_bufs,
|
|
const struct spi_buf_set *rx_bufs)
|
|
{
|
|
return xec_qspi_xfr(dev, spi_conf, tx_bufs, rx_bufs, false);
|
|
}
|
|
|
|
#ifdef CONFIG_SPI_ASYNC
|
|
static int xec_qspi_transceive_async(const struct device *dev,
|
|
const struct spi_config *spi_conf,
|
|
const struct spi_buf_set *tx_bufs,
|
|
const struct spi_buf_set *rx_bufs,
|
|
struct k_poll_signal *async)
|
|
{
|
|
return -ENOTSUP;
|
|
}
|
|
#endif
|
|
|
|
static int xec_qspi_release(const struct device *dev,
|
|
const struct spi_config *spi_conf)
|
|
{
|
|
struct spi_xec_qspi_data * const qdata = dev->data;
|
|
const struct spi_xec_qspi_config *cfg = dev->config;
|
|
struct qmspi_regs * const regs = cfg->regs;
|
|
struct spi_context *ctx = &qdata->ctx;
|
|
int ret = 0;
|
|
int counter = 0;
|
|
|
|
if (regs->STS & BIT(XEC_QSPI_STS_XFR_ACTIVE_POS)) {
|
|
/* Force CS# to de-assert on next unit boundary */
|
|
regs->EXE = BIT(XEC_QSPI_EXE_STOP_POS);
|
|
while (regs->STS & BIT(XEC_QSPI_STS_XFR_ACTIVE_POS)) {
|
|
ret = xec_qspi_spin_yield(&counter,
|
|
XEC_QSPI_WAIT_COUNT);
|
|
if (ret != 0) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
spi_context_unlock_unconditionally(ctx);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Called for each QMSPI controller instance
|
|
* Initialize QMSPI controller.
|
|
* Disable sleep control.
|
|
* Disable and clear interrupt status.
|
|
* Initialize SPI context.
|
|
* QMSPI will be fully configured and enabled when the transceive API
|
|
* is called.
|
|
*/
|
|
static int xec_qspi_init(const struct device *dev)
|
|
{
|
|
const struct spi_xec_qspi_config *cfg = dev->config;
|
|
struct spi_xec_qspi_data * const qdata = dev->data;
|
|
struct qmspi_regs * const regs = cfg->regs;
|
|
int ret = 0;
|
|
|
|
qdata->qstatus = 0;
|
|
qdata->np = cfg->width;
|
|
|
|
z_mchp_xec_pcr_periph_sleep(cfg->pcr_idx, cfg->pcr_pos, 0);
|
|
|
|
ret = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT);
|
|
if (ret != 0) {
|
|
LOG_ERR("QSPI pinctrl setup failed (%d)", ret);
|
|
return ret;
|
|
}
|
|
|
|
xec_qspi_reset(regs);
|
|
|
|
spi_context_unlock_unconditionally(&qdata->ctx);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct spi_driver_api spi_xec_qspi_driver_api = {
|
|
.transceive = xec_qspi_transceive,
|
|
.release = xec_qspi_release,
|
|
#ifdef CONFIG_SPI_ASYNC
|
|
.transceive_async = xec_qspi_transceive_async,
|
|
#endif
|
|
};
|
|
|
|
#define XEC_QSPI_CS_TIMING_VAL(a, b, c, d) (((a) & 0xFu) \
|
|
| (((b) & 0xFu) << 8) \
|
|
| (((c) & 0xFu) << 16) \
|
|
| (((d) & 0xFu) << 24))
|
|
|
|
#define XEC_QSPI_TAPS_ADJ_VAL(a, b) (((a) & 0xffu) | (((b) & 0xffu) << 8))
|
|
|
|
#define XEC_QSPI_CS_TIMING(i) XEC_QSPI_CS_TIMING_VAL( \
|
|
DT_INST_PROP_OR(i, dcsckon, 6), \
|
|
DT_INST_PROP_OR(i, dckcsoff, 4), \
|
|
DT_INST_PROP_OR(i, dldh, 6), \
|
|
DT_INST_PROP_OR(i, dcsda, 6))
|
|
|
|
#define XEC_QSPI_TAPS_ADJ(i) XEC_QSPI_TAPS_ADJ_VAL( \
|
|
DT_INST_PROP_OR(i, tctradj, 0), \
|
|
DT_INST_PROP_OR(i, tsckadj, 0))
|
|
|
|
#define XEC_QSPI_GIRQ(i) \
|
|
MCHP_XEC_ECIA_GIRQ(DT_INST_PROP_BY_IDX(i, girqs, 0))
|
|
|
|
#define XEC_QSPI_GIRQ_POS(i) \
|
|
MCHP_XEC_ECIA_GIRQ_POS(DT_INST_PROP_BY_IDX(i, girqs, 0))
|
|
|
|
#define XEC_QSPI_NVIC_AGGR(i) \
|
|
MCHP_XEC_ECIA_NVIC_AGGR(DT_INST_PROP_BY_IDX(i, girqs, 0))
|
|
|
|
#define XEC_QSPI_NVIC_DIRECT(i) \
|
|
MCHP_XEC_ECIA_NVIC_DIRECT(DT_INST_PROP_BY_IDX(i, girqs, 0))
|
|
|
|
/*
|
|
* The instance number, i is not related to block ID's rather the
|
|
* order the DT tools process all DT files in a build.
|
|
*/
|
|
#define XEC_QSPI_DEVICE(i) \
|
|
\
|
|
PINCTRL_DT_INST_DEFINE(i); \
|
|
\
|
|
static struct spi_xec_qspi_data xec_qspi_data_##i = { \
|
|
SPI_CONTEXT_INIT_LOCK(xec_qspi_data_##i, ctx), \
|
|
SPI_CONTEXT_INIT_SYNC(xec_qspi_data_##i, ctx), \
|
|
}; \
|
|
static const struct spi_xec_qspi_config xec_qspi_config_##i = { \
|
|
.regs = (struct qmspi_regs *) DT_INST_REG_ADDR(i), \
|
|
.cs1_freq = DT_INST_PROP_OR(i, cs1_freq, 0), \
|
|
.cs_timing = XEC_QSPI_CS_TIMING(i), \
|
|
.taps_adj = XEC_QSPI_TAPS_ADJ(i), \
|
|
.girq = XEC_QSPI_GIRQ(i), \
|
|
.girq_pos = XEC_QSPI_GIRQ_POS(i), \
|
|
.girq_nvic_aggr = XEC_QSPI_NVIC_AGGR(i), \
|
|
.girq_nvic_direct = XEC_QSPI_NVIC_DIRECT(i), \
|
|
.irq_pri = DT_INST_IRQ(i, priority), \
|
|
.pcr_idx = DT_INST_PROP_BY_IDX(i, pcrs, 0), \
|
|
.pcr_pos = DT_INST_PROP_BY_IDX(i, pcrs, 1), \
|
|
.chip_sel = DT_INST_PROP_OR(i, chip_select, 0), \
|
|
.width = DT_INST_PROP_OR(i, lines, 1), \
|
|
.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(i), \
|
|
}; \
|
|
DEVICE_DT_INST_DEFINE(i, &xec_qspi_init, NULL, \
|
|
&xec_qspi_data_##i, &xec_qspi_config_##i, \
|
|
POST_KERNEL, CONFIG_SPI_INIT_PRIORITY, \
|
|
&spi_xec_qspi_driver_api);
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(XEC_QSPI_DEVICE)
|