| /* |
| * Copyright (c) 2021 Microchip Technology Inc. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT microchip_xec_qmspi_ldma |
| |
| #include <errno.h> |
| #include <soc.h> |
| |
| #include <zephyr/device.h> |
| #include <zephyr/drivers/clock_control.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/drivers/spi/rtio.h> |
| #include <zephyr/dt-bindings/clock/mchp_xec_pcr.h> |
| #include <zephyr/dt-bindings/interrupt-controller/mchp-xec-ecia.h> |
| #include <zephyr/irq.h> |
| #include <zephyr/pm/device.h> |
| #include <zephyr/sys/sys_io.h> |
| #include <zephyr/sys/util.h> |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(spi_xec, CONFIG_SPI_LOG_LEVEL); |
| |
| #include "spi_context.h" |
| |
| /* #define MCHP_XEC_QMSPI_DEBUG 1 */ |
| |
| /* MEC172x QMSPI controller SPI Mode 3 signalling has an anomaly where |
| * received data is shifted off the input line(s) improperly. Received |
| * data bytes will be left shifted by 1. Work-around for SPI Mode 3 is |
| * to sample input line(s) on same edge as output data is ready. |
| */ |
| #define XEC_QMSPI_SPI_MODE_3_ANOMALY 1 |
| |
| /* common clock control device node for all Microchip XEC chips */ |
| #define MCHP_XEC_CLOCK_CONTROL_NODE DT_NODELABEL(pcr) |
| |
| /* spin loops waiting for HW to clear soft reset bit */ |
| #define XEC_QMSPI_SRST_LOOPS 16 |
| |
| /* microseconds for busy wait and total wait interval */ |
| #define XEC_QMSPI_WAIT_INTERVAL 8 |
| #define XEC_QMSPI_WAIT_COUNT 64 |
| |
| /* QSPI transfer and DMA done */ |
| #define XEC_QSPI_HW_XFR_DMA_DONE (MCHP_QMSPI_STS_DONE | MCHP_QMSPI_STS_DMA_DONE) |
| |
| /* QSPI hardware error status |
| * Misprogrammed control or descriptors (software error) |
| * Overflow TX FIFO |
| * Underflow RX FIFO |
| */ |
| #define XEC_QSPI_HW_ERRORS (MCHP_QMSPI_STS_PROG_ERR | \ |
| MCHP_QMSPI_STS_TXB_ERR | \ |
| MCHP_QMSPI_STS_RXB_ERR) |
| |
| #define XEC_QSPI_HW_ERRORS_LDMA (MCHP_QMSPI_STS_LDMA_RX_ERR | \ |
| MCHP_QMSPI_STS_LDMA_TX_ERR) |
| |
| #define XEC_QSPI_HW_ERRORS_ALL (XEC_QSPI_HW_ERRORS | \ |
| XEC_QSPI_HW_ERRORS_LDMA) |
| |
| #define XEC_QSPI_TIMEOUT_US (100 * 1000) /* 100 ms */ |
| |
| /* Device constant configuration parameters */ |
| struct spi_qmspi_config { |
| struct qmspi_regs *regs; |
| const struct device *clk_dev; |
| struct mchp_xec_pcr_clk_ctrl clksrc; |
| uint32_t clock_freq; |
| 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 chip_sel; |
| uint8_t width; /* 0(half) 1(single), 2(dual), 4(quad) */ |
| uint8_t unused[1]; |
| const struct pinctrl_dev_config *pcfg; |
| void (*irq_config_func)(void); |
| }; |
| |
| #define XEC_QMSPI_XFR_FLAG_TX BIT(0) |
| #define XEC_QMSPI_XFR_FLAG_RX BIT(1) |
| |
| /* Device run time data */ |
| struct spi_qmspi_data { |
| struct spi_context ctx; |
| uint32_t base_freq_hz; |
| uint32_t spi_freq_hz; |
| uint32_t qstatus; |
| uint8_t np; /* number of data pins: 1, 2, or 4 */ |
| #ifdef CONFIG_SPI_ASYNC |
| spi_callback_t cb; |
| void *userdata; |
| size_t xfr_len; |
| #endif |
| uint32_t tempbuf[2]; |
| #ifdef MCHP_XEC_QMSPI_DEBUG |
| uint32_t bufcnt_status; |
| uint32_t rx_ldma_ctrl0; |
| uint32_t tx_ldma_ctrl0; |
| uint32_t qunits; |
| uint32_t qxfru; |
| uint32_t xfrlen; |
| |
| #endif |
| }; |
| |
| static int xec_qmspi_spin_yield(int *counter, int max_count) |
| { |
| *counter = *counter + 1; |
| |
| if (*counter > max_count) { |
| return -ETIMEDOUT; |
| } |
| |
| k_busy_wait(XEC_QMSPI_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 qmspi_reset(struct qmspi_regs *regs) |
| { |
| uint32_t taps[3]; |
| uint32_t malt1; |
| uint32_t cstm; |
| uint32_t mode; |
| uint32_t cnt = XEC_QMSPI_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 qmspi_encoded_fdiv(const struct device *dev, uint32_t freq_hz) |
| { |
| struct spi_qmspi_data *qdata = dev->data; |
| |
| if (freq_hz == 0u) { |
| return 0u; /* maximum frequency divider */ |
| } |
| |
| return (qdata->base_freq_hz / freq_hz); |
| } |
| |
| /* Program QMSPI frequency divider field in the mode register. |
| * MEC172x QMSPI input clock source is the Fast Peripheral domain whose |
| * clock is controlled by the PCR turbo clock. 96 MHz if turbo mode |
| * enabled else 48 MHz. Query the clock control driver to get clock |
| * rate of fast peripheral domain. MEC172x QMSPI clock divider has |
| * been expanded to a 16-bit field encoded as: |
| * 0 = divide by 0x10000 |
| * 1 to 0xffff = divide by this value. |
| */ |
| static int qmspi_set_frequency(struct spi_qmspi_data *qdata, struct qmspi_regs *regs, |
| uint32_t freq_hz) |
| { |
| uint32_t clk = MCHP_QMSPI_INPUT_CLOCK_FREQ_HZ; |
| uint32_t fdiv = 0u; /* maximum divider */ |
| |
| if (qdata->base_freq_hz) { |
| clk = qdata->base_freq_hz; |
| } |
| |
| if (freq_hz) { |
| fdiv = 1u; |
| if (freq_hz < clk) { |
| fdiv = clk / freq_hz; |
| } |
| } |
| |
| regs->MODE = ((regs->MODE & ~(MCHP_QMSPI_M_FDIV_MASK)) | |
| ((fdiv << MCHP_QMSPI_M_FDIV_POS) & MCHP_QMSPI_M_FDIV_MASK)); |
| |
| if (!fdiv) { |
| fdiv = 0x10000u; |
| } |
| |
| qdata->spi_freq_hz = clk / fdiv; |
| |
| return 0; |
| } |
| |
| /* |
| * SPI signalling mode: CPOL and CPHA |
| * CPOL = 0 is clock idles low, 1 is clock idle high |
| * CPHA = 0 Transmitter changes data on trailing of preceding clock cycle. |
| * Receiver samples data on leading edge of clock cycle. |
| * 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 max. SPI frequency (48MHz). |
| * SPI frequency == 48MHz sample and change data on same edge. |
| * Mode 0: CPOL=0 CHPA=0 (CHPA_MISO=1 and CHPA_MOSI=0) |
| * Mode 3: CPOL=1 CHPA=1 (CHPA_MISO=0 and CHPA_MOSI=1) |
| * |
| * There is an anomaly in MEC172x for SPI signalling mode 3. We must |
| * set CHPA_MISO=0 for SPI Mode 3 at all frequencies. |
| */ |
| |
| const uint8_t smode_tbl[4] = { |
| 0x00u, 0x06u, 0x01u, |
| #ifdef XEC_QMSPI_SPI_MODE_3_ANOMALY |
| 0x03u, /* CPOL=1, CPHA_MOSI=1, CPHA_MISO=0 */ |
| #else |
| 0x07u, /* CPOL=1, CPHA_MOSI=1, CPHA_MISO=1 */ |
| #endif |
| }; |
| |
| const uint8_t smode48_tbl[4] = { |
| 0x04u, 0x02u, 0x05u, 0x03u |
| }; |
| |
| static void qmspi_set_signalling_mode(struct spi_qmspi_data *qdata, |
| struct qmspi_regs *regs, uint32_t smode) |
| { |
| const uint8_t *ptbl; |
| uint32_t m; |
| |
| ptbl = smode_tbl; |
| if (qdata->spi_freq_hz >= MHZ(48)) { |
| ptbl = smode48_tbl; |
| } |
| |
| m = (uint32_t)ptbl[smode & 0x03]; |
| regs->MODE = (regs->MODE & ~(MCHP_QMSPI_M_SIG_MASK)) |
| | (m << MCHP_QMSPI_M_SIG_POS); |
| } |
| |
| #ifdef CONFIG_SPI_EXTENDED_MODES |
| /* |
| * QMSPI HW support single, dual, and quad. |
| * Return QMSPI Control/Descriptor register encoded value. |
| */ |
| static uint32_t encode_lines(const struct spi_config *config) |
| { |
| uint32_t qlines; |
| |
| switch (config->operation & SPI_LINES_MASK) { |
| case SPI_LINES_SINGLE: |
| qlines = MCHP_QMSPI_C_IFM_1X; |
| break; |
| #if DT_INST_PROP(0, lines) > 1 |
| case SPI_LINES_DUAL: |
| qlines = MCHP_QMSPI_C_IFM_2X; |
| break; |
| #endif |
| #if DT_INST_PROP(0, lines) > 2 |
| case SPI_LINES_QUAD: |
| qlines = MCHP_QMSPI_C_IFM_4X; |
| break; |
| #endif |
| default: |
| qlines = 0xffu; |
| } |
| |
| return qlines; |
| } |
| |
| static uint8_t npins_from_spi_config(const struct spi_config *config) |
| { |
| switch (config->operation & SPI_LINES_MASK) { |
| case SPI_LINES_DUAL: |
| return 2u; |
| case SPI_LINES_QUAD: |
| return 4u; |
| default: |
| return 1u; |
| } |
| } |
| #endif /* CONFIG_SPI_EXTENDED_MODES */ |
| |
| static int spi_feature_support(const struct spi_config *config) |
| { |
| if (config->operation & (SPI_TRANSFER_LSB | SPI_OP_MODE_SLAVE | SPI_MODE_LOOP)) { |
| LOG_ERR("Driver does not support LSB first, slave, or loop back"); |
| return -ENOTSUP; |
| } |
| |
| if (config->operation & SPI_CS_ACTIVE_HIGH) { |
| LOG_ERR("CS active high not supported"); |
| return -ENOTSUP; |
| } |
| |
| if (config->operation & SPI_LOCK_ON) { |
| LOG_ERR("Lock On not supported"); |
| return -ENOTSUP; |
| } |
| |
| if (SPI_WORD_SIZE_GET(config->operation) != 8) { |
| LOG_ERR("Word size != 8 not supported"); |
| return -ENOTSUP; |
| } |
| |
| return 0; |
| } |
| |
| /* Configure QMSPI. |
| * NOTE: QMSPI Shared SPI port has two chip selects. |
| * Private SPI and internal SPI ports support one chip select. |
| * Hardware supports dual and quad I/O. Dual and quad are allowed |
| * if SPI extended mode is enabled at build time. User must |
| * provide pin configuration via DTS. |
| */ |
| static int qmspi_configure(const struct device *dev, |
| const struct spi_config *config) |
| { |
| const struct spi_qmspi_config *cfg = dev->config; |
| struct spi_qmspi_data *qdata = dev->data; |
| struct qmspi_regs *regs = cfg->regs; |
| uint32_t smode; |
| int ret; |
| |
| if (!config) { |
| return -EINVAL; |
| } |
| |
| if (spi_context_configured(&qdata->ctx, config)) { |
| return 0; |
| } |
| |
| qmspi_set_frequency(qdata, regs, config->frequency); |
| |
| /* check new configuration */ |
| ret = spi_feature_support(config); |
| if (ret) { |
| return ret; |
| } |
| |
| #ifdef CONFIG_SPI_EXTENDED_MODES |
| smode = encode_lines(config); |
| if (smode == 0xff) { |
| LOG_ERR("Requested lines mode not supported"); |
| return -ENOTSUP; |
| } |
| qdata->np = npins_from_spi_config(config); |
| #else |
| smode = MCHP_QMSPI_C_IFM_1X; |
| qdata->np = 1u; |
| #endif |
| regs->CTRL = smode; |
| |
| smode = 0; |
| if ((config->operation & SPI_MODE_CPHA) != 0U) { |
| smode |= BIT(0); |
| } |
| |
| if ((config->operation & SPI_MODE_CPOL) != 0U) { |
| smode |= BIT(1); |
| } |
| |
| qmspi_set_signalling_mode(qdata, regs, smode); |
| |
| /* chip select */ |
| smode = regs->MODE & ~(MCHP_QMSPI_M_CS_MASK); |
| if (cfg->chip_sel == 0) { |
| smode |= MCHP_QMSPI_M_CS0; |
| } else { |
| smode |= MCHP_QMSPI_M_CS1; |
| } |
| regs->MODE = smode; |
| |
| /* chip select timing and TAPS adjust */ |
| 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 = qmspi_encoded_fdiv(dev, cfg->cs1_freq); |
| |
| regs->MODE_ALT1 = (fdiv << MCHP_QMSPI_MA1_CS1_CDIV_POS) & |
| MCHP_QMSPI_MA1_CS1_CDIV_MSK; |
| regs->MODE_ALT1 |= MCHP_QMSPI_MA1_CS1_CDIV_EN; |
| } |
| |
| qdata->ctx.config = config; |
| |
| regs->MODE |= MCHP_QMSPI_M_ACTIVATE; |
| |
| return 0; |
| } |
| |
| static uint32_t encode_npins(uint8_t npins) |
| { |
| if (npins == 4) { |
| return MCHP_QMSPI_C_IFM_4X; |
| } else if (npins == 2) { |
| return MCHP_QMSPI_C_IFM_2X; |
| } else { |
| return MCHP_QMSPI_C_IFM_1X; |
| } |
| } |
| |
| /* Common controller transfer initialziation using Local-DMA. |
| * Full-duplex: controller configured to transmit and receive simultaneouly. |
| * Half-duplex(dual/quad): User may only specify TX or RX buffer sets. |
| * Passing both buffers sets is reported as an error. |
| */ |
| static inline int qmspi_xfr_cm_init(const struct device *dev, |
| const struct spi_buf_set *tx_bufs, |
| const struct spi_buf_set *rx_bufs) |
| { |
| const struct spi_qmspi_config *devcfg = dev->config; |
| struct spi_qmspi_data *qdata = dev->data; |
| struct qmspi_regs *regs = devcfg->regs; |
| |
| regs->IEN = 0; |
| regs->EXE = MCHP_QMSPI_EXE_CLR_FIFOS; |
| regs->LDMA_RX_DESCR_BM = 0; |
| regs->LDMA_TX_DESCR_BM = 0; |
| regs->MODE &= ~(MCHP_QMSPI_M_LDMA_TX_EN | MCHP_QMSPI_M_LDMA_RX_EN); |
| regs->STS = 0xffffffffu; |
| regs->CTRL = encode_npins(qdata->np); |
| |
| qdata->qstatus = 0; |
| |
| #ifdef CONFIG_SPI_EXTENDED_MODES |
| if (qdata->np != 1) { |
| if (tx_bufs && rx_bufs) { |
| LOG_ERR("Cannot specify both TX and RX buffers in half-duplex(dual/quad)"); |
| return -EPROTONOSUPPORT; |
| } |
| } |
| #endif |
| |
| return 0; |
| } |
| |
| /* QMSPI Local-DMA transfer configuration: |
| * Support full and half(dual/quad) duplex transfers. |
| * Requires caller to have checked that only one direction was setup |
| * in the SPI context: TX or RX not both. (refer to qmspi_xfr_cm_init) |
| * Supports spi_buf's where data pointer is NULL and length non-zero. |
| * These buffers are used as TX tri-state I/O clock only generation or |
| * RX data discard for certain SPI command protocols using dual/quad I/O. |
| * 1. Get largest contiguous data size from SPI context. |
| * 2. If the SPI TX context has a non-zero length configure Local-DMA TX |
| * channel 1 for contiguous data size. If TX context has valid buffer |
| * configure channel to use context buffer with address increment. |
| * If the TX buffer pointer is NULL interpret byte length as the number |
| * of clocks to generate with output line(s) tri-stated. NOTE: The controller |
| * must be configured with TX disabled to not drive output line(s) during |
| * clock generation. Also, no data should be written to TX FIFO. The unit |
| * size can be set to bits. The number of units to transfer must be computed |
| * based upon the number of output pins in the IOM field: full-duplex is one |
| * bit per clock, dual is 2 bits per clock, and quad is 4 bits per clock. |
| * For example, if I/O lines is 4 (quad) meaning 4 bits per clock and the |
| * user wants 7 clocks then the number of bit units is 4 * 7 = 28. |
| * 3. If instead, the SPI RX context has a non-zero length configure Local-DMA |
| * RX channel 1 for the contiguous data size. If RX context has a valid |
| * buffer configure channel to use buffer with address increment else |
| * configure channel for driver data temporary buffer without address |
| * increment. |
| * 4. Update QMSPI Control register. |
| */ |
| static uint32_t qmspi_ldma_encode_unit_size(uint32_t maddr, size_t len) |
| { |
| uint8_t temp = (maddr | (uint32_t)len) & 0x3u; |
| |
| if (temp == 0) { |
| return MCHP_QMSPI_LDC_ASZ_4; |
| } else if (temp == 2) { |
| return MCHP_QMSPI_LDC_ASZ_2; |
| } else { |
| return MCHP_QMSPI_LDC_ASZ_1; |
| } |
| } |
| |
| static uint32_t qmspi_unit_size(size_t xfrlen) |
| { |
| if ((xfrlen & 0xfu) == 0u) { |
| return 16u; |
| } else if ((xfrlen & 0x3u) == 0u) { |
| return 4u; |
| } else { |
| return 1u; |
| } |
| } |
| |
| static uint32_t qmspi_encode_unit_size(uint32_t units_in_bytes) |
| { |
| if (units_in_bytes == 16u) { |
| return MCHP_QMSPI_C_XFR_UNITS_16; |
| } else if (units_in_bytes == 4u) { |
| return MCHP_QMSPI_C_XFR_UNITS_4; |
| } else { |
| return MCHP_QMSPI_C_XFR_UNITS_1; |
| } |
| } |
| |
| static size_t q_ldma_cfg(const struct device *dev) |
| { |
| const struct spi_qmspi_config *devcfg = dev->config; |
| struct spi_qmspi_data *qdata = dev->data; |
| struct spi_context *ctx = &qdata->ctx; |
| struct qmspi_regs *regs = devcfg->regs; |
| |
| size_t ctx_xfr_len = spi_context_max_continuous_chunk(ctx); |
| uint32_t ctrl, ldctrl, mstart, qunits, qxfru, xfrlen; |
| |
| regs->EXE = MCHP_QMSPI_EXE_CLR_FIFOS; |
| regs->MODE &= ~(MCHP_QMSPI_M_LDMA_RX_EN | MCHP_QMSPI_M_LDMA_TX_EN); |
| regs->LDRX[0].CTRL = 0; |
| regs->LDRX[0].MSTART = 0; |
| regs->LDRX[0].LEN = 0; |
| regs->LDTX[0].CTRL = 0; |
| regs->LDTX[0].MSTART = 0; |
| regs->LDTX[0].LEN = 0; |
| |
| if (ctx_xfr_len == 0) { |
| return 0; |
| } |
| |
| qunits = qmspi_unit_size(ctx_xfr_len); |
| ctrl = qmspi_encode_unit_size(qunits); |
| qxfru = ctx_xfr_len / qunits; |
| if (qxfru > 0x7fffu) { |
| qxfru = 0x7fffu; |
| } |
| ctrl |= (qxfru << MCHP_QMSPI_C_XFR_NUNITS_POS); |
| xfrlen = qxfru * qunits; |
| |
| #ifdef MCHP_XEC_QMSPI_DEBUG |
| qdata->qunits = qunits; |
| qdata->qxfru = qxfru; |
| qdata->xfrlen = xfrlen; |
| #endif |
| if (spi_context_tx_buf_on(ctx)) { |
| mstart = (uint32_t)ctx->tx_buf; |
| ctrl |= MCHP_QMSPI_C_TX_DATA | MCHP_QMSPI_C_TX_LDMA_CH0; |
| ldctrl = qmspi_ldma_encode_unit_size(mstart, xfrlen); |
| ldctrl |= MCHP_QMSPI_LDC_INCR_EN | MCHP_QMSPI_LDC_EN; |
| regs->MODE |= MCHP_QMSPI_M_LDMA_TX_EN; |
| regs->LDTX[0].LEN = xfrlen; |
| regs->LDTX[0].MSTART = mstart; |
| regs->LDTX[0].CTRL = ldctrl; |
| } |
| |
| if (spi_context_rx_buf_on(ctx)) { |
| mstart = (uint32_t)ctx->rx_buf; |
| ctrl |= MCHP_QMSPI_C_RX_LDMA_CH0 | MCHP_QMSPI_C_RX_EN; |
| ldctrl = MCHP_QMSPI_LDC_EN | MCHP_QMSPI_LDC_INCR_EN; |
| ldctrl |= qmspi_ldma_encode_unit_size(mstart, xfrlen); |
| regs->MODE |= MCHP_QMSPI_M_LDMA_RX_EN; |
| regs->LDRX[0].LEN = xfrlen; |
| regs->LDRX[0].MSTART = mstart; |
| regs->LDRX[0].CTRL = ldctrl; |
| } |
| |
| regs->CTRL = (regs->CTRL & 0x3u) | ctrl; |
| |
| return xfrlen; |
| } |
| |
| /* Start and wait for QMSPI synchronous transfer(s) to complete. |
| * Initialize QMSPI controller for Local-DMA operation. |
| * Iterate over SPI context with non-zero TX or RX data lengths. |
| * 1. Configure QMSPI Control register and Local-DMA channel(s) |
| * 2. Clear QMSPI status |
| * 3. Start QMSPI transfer |
| * 4. Poll QMSPI status for transfer done and DMA done with timeout. |
| * 5. Hardware anomaly work-around: Poll with timeout QMSPI Local-DMA |
| * TX and RX channels until hardware clears both channel enables. |
| * This indicates hardware is really done with transfer to/from memory. |
| * 6. Update SPI context with amount of data transmitted and received. |
| * If SPI configuration hold chip select on flag is not set then instruct |
| * QMSPI to de-assert chip select. |
| * Set SPI context as complete |
| */ |
| static int qmspi_xfr_sync(const struct device *dev, |
| const struct spi_config *spi_cfg, |
| const struct spi_buf_set *tx_bufs, |
| const struct spi_buf_set *rx_bufs) |
| { |
| const struct spi_qmspi_config *devcfg = dev->config; |
| struct spi_qmspi_data *qdata = dev->data; |
| struct spi_context *ctx = &qdata->ctx; |
| struct qmspi_regs *regs = devcfg->regs; |
| size_t xfr_len; |
| |
| int ret = qmspi_xfr_cm_init(dev, tx_bufs, rx_bufs); |
| |
| if (ret) { |
| return ret; |
| } |
| |
| while (spi_context_tx_on(ctx) || spi_context_rx_on(ctx)) { |
| xfr_len = q_ldma_cfg(dev); |
| regs->STS = 0xffffffffu; |
| regs->EXE = MCHP_QMSPI_EXE_START; |
| |
| #ifdef MCHP_XEC_QMSPI_DEBUG |
| uint32_t temp = regs->STS; |
| |
| while (!(temp & MCHP_QMSPI_STS_DONE)) { |
| temp = regs->STS; |
| } |
| qdata->qstatus = temp; |
| qdata->bufcnt_status = regs->BCNT_STS; |
| qdata->rx_ldma_ctrl0 = regs->LDRX[0].CTRL; |
| qdata->tx_ldma_ctrl0 = regs->LDTX[0].CTRL; |
| #else |
| uint32_t wcnt = 0; |
| |
| qdata->qstatus = regs->STS; |
| while (!(qdata->qstatus & MCHP_QMSPI_STS_DONE)) { |
| k_busy_wait(1u); |
| if (++wcnt > XEC_QSPI_TIMEOUT_US) { |
| regs->EXE = MCHP_QMSPI_EXE_STOP; |
| return -ETIMEDOUT; |
| } |
| qdata->qstatus = regs->STS; |
| } |
| #endif |
| spi_context_update_tx(ctx, 1, xfr_len); |
| spi_context_update_rx(ctx, 1, xfr_len); |
| } |
| |
| if (!(spi_cfg->operation & SPI_HOLD_ON_CS)) { |
| regs->EXE = MCHP_QMSPI_EXE_STOP; |
| } |
| |
| spi_context_complete(ctx, dev, 0); |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_SPI_ASYNC |
| /* Configure QMSPI such that QMSPI transfer FSM and LDMA FSM are synchronized. |
| * Transfer length must be programmed into control/descriptor register(s) and |
| * LDMA register(s). LDMA override length bit must NOT be set. |
| */ |
| static int qmspi_xfr_start_async(const struct device *dev, const struct spi_buf_set *tx_bufs, |
| const struct spi_buf_set *rx_bufs) |
| { |
| const struct spi_qmspi_config *devcfg = dev->config; |
| struct spi_qmspi_data *qdata = dev->data; |
| struct qmspi_regs *regs = devcfg->regs; |
| int ret; |
| |
| ret = qmspi_xfr_cm_init(dev, tx_bufs, rx_bufs); |
| if (ret) { |
| return ret; |
| } |
| |
| qdata->xfr_len = q_ldma_cfg(dev); |
| if (!qdata->xfr_len) { |
| return 0; /* nothing to do */ |
| } |
| |
| regs->STS = 0xffffffffu; |
| regs->EXE = MCHP_QMSPI_EXE_START; |
| regs->IEN = MCHP_QMSPI_IEN_XFR_DONE | MCHP_QMSPI_IEN_PROG_ERR |
| | MCHP_QMSPI_IEN_LDMA_RX_ERR | MCHP_QMSPI_IEN_LDMA_TX_ERR; |
| |
| return 0; |
| } |
| |
| /* Wrapper to start asynchronous (interrupts enabled) SPI transaction */ |
| static int qmspi_xfr_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 spi_qmspi_data *qdata = dev->data; |
| int err = 0; |
| |
| qdata->qstatus = 0; |
| qdata->xfr_len = 0; |
| |
| err = qmspi_xfr_start_async(dev, tx_bufs, rx_bufs); |
| |
| return err; |
| } |
| #endif /* CONFIG_SPI_ASYNC */ |
| |
| /* Start (a)synchronous transaction using QMSPI Local-DMA */ |
| static int qmspi_transceive(const struct device *dev, |
| const struct spi_config *config, |
| const struct spi_buf_set *tx_bufs, |
| const struct spi_buf_set *rx_bufs, |
| bool asynchronous, |
| spi_callback_t cb, |
| void *user_data) |
| { |
| struct spi_qmspi_data *qdata = dev->data; |
| struct spi_context *ctx = &qdata->ctx; |
| int err = 0; |
| |
| if (!config) { |
| return -EINVAL; |
| } |
| |
| if (!tx_bufs && !rx_bufs) { |
| return 0; |
| } |
| |
| spi_context_lock(&qdata->ctx, asynchronous, cb, user_data, config); |
| |
| err = qmspi_configure(dev, config); |
| if (err != 0) { |
| spi_context_release(ctx, err); |
| return err; |
| } |
| |
| spi_context_cs_control(ctx, true); |
| spi_context_buffers_setup(ctx, tx_bufs, rx_bufs, 1); |
| |
| #ifdef CONFIG_SPI_ASYNC |
| if (asynchronous) { |
| qdata->cb = cb; |
| qdata->userdata = user_data; |
| err = qmspi_xfr_async(dev, config, tx_bufs, rx_bufs); |
| } else { |
| err = qmspi_xfr_sync(dev, config, tx_bufs, rx_bufs); |
| } |
| #else |
| err = qmspi_xfr_sync(dev, config, tx_bufs, rx_bufs); |
| #endif |
| if (err) { /* de-assert CS# and give semaphore */ |
| spi_context_unlock_unconditionally(ctx); |
| return err; |
| } |
| |
| if (asynchronous) { |
| return err; |
| } |
| |
| err = spi_context_wait_for_completion(ctx); |
| if (!(config->operation & SPI_HOLD_ON_CS)) { |
| spi_context_cs_control(ctx, false); |
| } |
| spi_context_release(ctx, err); |
| |
| return err; |
| } |
| |
| static int qmspi_transceive_sync(const struct device *dev, |
| const struct spi_config *config, |
| const struct spi_buf_set *tx_bufs, |
| const struct spi_buf_set *rx_bufs) |
| { |
| return qmspi_transceive(dev, config, tx_bufs, rx_bufs, false, NULL, NULL); |
| } |
| |
| #ifdef CONFIG_SPI_ASYNC |
| |
| static int qmspi_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, |
| spi_callback_t cb, |
| void *userdata) |
| { |
| return qmspi_transceive(dev, config, tx_bufs, rx_bufs, true, cb, userdata); |
| } |
| #endif /* CONFIG_SPI_ASYNC */ |
| |
| static int qmspi_release(const struct device *dev, |
| const struct spi_config *config) |
| { |
| struct spi_qmspi_data *data = dev->data; |
| const struct spi_qmspi_config *cfg = dev->config; |
| struct qmspi_regs *regs = cfg->regs; |
| int ret = 0; |
| int counter = 0; |
| |
| if (regs->STS & MCHP_QMSPI_STS_ACTIVE_RO) { |
| /* Force CS# to de-assert on next unit boundary */ |
| regs->EXE = MCHP_QMSPI_EXE_STOP; |
| while (regs->STS & MCHP_QMSPI_STS_ACTIVE_RO) { |
| ret = xec_qmspi_spin_yield(&counter, XEC_QMSPI_WAIT_COUNT); |
| if (ret != 0) { |
| break; |
| } |
| } |
| } |
| |
| spi_context_unlock_unconditionally(&data->ctx); |
| |
| return ret; |
| } |
| |
| /* QMSPI interrupt handler called by Zephyr ISR |
| * All transfers use QMSPI Local-DMA specified by the Control register. |
| * QMSPI descriptor mode not used. |
| * Full-duplex always uses LDMA TX channel 0 and RX channel 0 |
| * Half-duplex(dual/quad) use one of TX channel 0 or RX channel 0 |
| */ |
| void qmspi_xec_isr(const struct device *dev) |
| { |
| const struct spi_qmspi_config *cfg = dev->config; |
| struct spi_qmspi_data *data = dev->data; |
| struct qmspi_regs *regs = cfg->regs; |
| uint32_t qstatus = regs->STS; |
| #ifdef CONFIG_SPI_ASYNC |
| struct spi_context *ctx = &data->ctx; |
| int xstatus = 0; |
| #endif |
| |
| regs->IEN = 0; |
| data->qstatus = qstatus; |
| regs->STS = MCHP_QMSPI_STS_RW1C_MASK; |
| mchp_xec_ecia_girq_src_clr(cfg->girq, cfg->girq_pos); |
| |
| #ifdef CONFIG_SPI_ASYNC |
| if (qstatus & XEC_QSPI_HW_ERRORS_ALL) { |
| xstatus = -EIO; |
| data->qstatus |= BIT(7); |
| regs->EXE = MCHP_QMSPI_EXE_STOP; |
| spi_context_cs_control(ctx, false); |
| spi_context_complete(ctx, dev, xstatus); |
| if (data->cb) { |
| data->cb(dev, xstatus, data->userdata); |
| } |
| return; |
| } |
| |
| /* Clear Local-DMA enables in Mode and Control registers */ |
| regs->MODE &= ~(MCHP_QMSPI_M_LDMA_RX_EN | MCHP_QMSPI_M_LDMA_TX_EN); |
| regs->CTRL &= MCHP_QMSPI_C_IFM_MASK; |
| |
| spi_context_update_tx(ctx, 1, data->xfr_len); |
| spi_context_update_rx(ctx, 1, data->xfr_len); |
| |
| data->xfr_len = q_ldma_cfg(dev); |
| if (data->xfr_len) { |
| regs->STS = 0xffffffffu; |
| regs->EXE = MCHP_QMSPI_EXE_START; |
| regs->IEN = MCHP_QMSPI_IEN_XFR_DONE | MCHP_QMSPI_IEN_PROG_ERR |
| | MCHP_QMSPI_IEN_LDMA_RX_ERR | MCHP_QMSPI_IEN_LDMA_TX_ERR; |
| return; |
| } |
| |
| if (!(ctx->owner->operation & SPI_HOLD_ON_CS)) { |
| regs->EXE = MCHP_QMSPI_EXE_STOP; |
| spi_context_cs_control(&data->ctx, false); |
| } |
| |
| spi_context_complete(&data->ctx, dev, xstatus); |
| |
| if (data->cb) { |
| data->cb(dev, xstatus, data->userdata); |
| } |
| #endif /* CONFIG_SPI_ASYNC */ |
| } |
| |
| #ifdef CONFIG_PM_DEVICE |
| /* If the application wants the QMSPI pins to be disabled in suspend it must |
| * define pinctr-1 values for each pin in the app/project DT overlay. |
| */ |
| static int qmspi_xec_pm_action(const struct device *dev, enum pm_device_action action) |
| { |
| const struct spi_qmspi_config *devcfg = dev->config; |
| int ret; |
| |
| switch (action) { |
| case PM_DEVICE_ACTION_RESUME: |
| ret = pinctrl_apply_state(devcfg->pcfg, PINCTRL_STATE_DEFAULT); |
| break; |
| case PM_DEVICE_ACTION_SUSPEND: |
| ret = pinctrl_apply_state(devcfg->pcfg, PINCTRL_STATE_SLEEP); |
| if (ret == -ENOENT) { /* pinctrl-1 does not exist */ |
| ret = 0; |
| } |
| break; |
| default: |
| ret = -ENOTSUP; |
| } |
| |
| return ret; |
| } |
| #endif /* CONFIG_PM_DEVICE */ |
| |
| /* |
| * 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 qmspi_xec_init(const struct device *dev) |
| { |
| const struct spi_qmspi_config *cfg = dev->config; |
| struct spi_qmspi_data *qdata = dev->data; |
| struct qmspi_regs *regs = cfg->regs; |
| clock_control_subsys_t clkss = (clock_control_subsys_t)MCHP_XEC_PCR_CLK_PERIPH_FAST; |
| int ret = 0; |
| |
| qdata->base_freq_hz = 0u; |
| qdata->qstatus = 0; |
| qdata->np = cfg->width; |
| #ifdef CONFIG_SPI_ASYNC |
| qdata->xfr_len = 0; |
| #endif |
| |
| if (!cfg->clk_dev) { |
| LOG_ERR("XEC QMSPI-LDMA clock device not configured"); |
| return -EINVAL; |
| } |
| |
| ret = clock_control_on(cfg->clk_dev, (clock_control_subsys_t)&cfg->clksrc); |
| if (ret < 0) { |
| LOG_ERR("XEC QMSPI-LDMA enable clock source error %d", ret); |
| return ret; |
| } |
| |
| ret = clock_control_get_rate(cfg->clk_dev, clkss, &qdata->base_freq_hz); |
| if (ret) { |
| LOG_ERR("XEC QMSPI-LDMA clock get rate error %d", ret); |
| return ret; |
| } |
| |
| /* controller in known state before enabling pins */ |
| qmspi_reset(regs); |
| mchp_xec_ecia_girq_src_clr(cfg->girq, cfg->girq_pos); |
| |
| ret = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT); |
| if (ret != 0) { |
| LOG_ERR("XEC QMSPI-LDMA pinctrl setup failed (%d)", ret); |
| return ret; |
| } |
| |
| /* default SPI Mode 0 signalling */ |
| const struct spi_config spi_cfg = { |
| .frequency = cfg->clock_freq, |
| .operation = SPI_LINES_SINGLE | SPI_WORD_SET(8), |
| }; |
| |
| ret = qmspi_configure(dev, &spi_cfg); |
| if (ret) { |
| LOG_ERR("XEC QMSPI-LDMA init configure failed (%d)", ret); |
| return ret; |
| } |
| |
| #ifdef CONFIG_SPI_ASYNC |
| cfg->irq_config_func(); |
| mchp_xec_ecia_enable(cfg->girq, cfg->girq_pos); |
| #endif |
| |
| spi_context_unlock_unconditionally(&qdata->ctx); |
| |
| return 0; |
| } |
| |
| static const struct spi_driver_api spi_qmspi_xec_driver_api = { |
| .transceive = qmspi_transceive_sync, |
| #ifdef CONFIG_SPI_ASYNC |
| .transceive_async = qmspi_transceive_async, |
| #endif |
| #ifdef CONFIG_SPI_RTIO |
| .iodev_submit = spi_rtio_iodev_default_submit, |
| #endif |
| .release = qmspi_release, |
| }; |
| |
| #define XEC_QMSPI_CS_TIMING_VAL(a, b, c, d) (((a) & 0xFu) \ |
| | (((b) & 0xFu) << 8) \ |
| | (((c) & 0xFu) << 16) \ |
| | (((d) & 0xFu) << 24)) |
| |
| #define XEC_QMSPI_TAPS_ADJ_VAL(a, b) (((a) & 0xffu) | (((b) & 0xffu) << 8)) |
| |
| #define XEC_QMSPI_CS_TIMING(i) XEC_QMSPI_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_QMSPI_TAPS_ADJ(i) XEC_QMSPI_TAPS_ADJ_VAL( \ |
| DT_INST_PROP_OR(i, tctradj, 0), \ |
| DT_INST_PROP_OR(i, tsckadj, 0)) |
| |
| #define XEC_QMSPI_GIRQ(i) \ |
| MCHP_XEC_ECIA_GIRQ(DT_INST_PROP_BY_IDX(i, girqs, 0)) |
| |
| #define XEC_QMSPI_GIRQ_POS(i) \ |
| MCHP_XEC_ECIA_GIRQ_POS(DT_INST_PROP_BY_IDX(i, girqs, 0)) |
| |
| #define XEC_QMSPI_NVIC_AGGR(i) \ |
| MCHP_XEC_ECIA_NVIC_AGGR(DT_INST_PROP_BY_IDX(i, girqs, 0)) |
| |
| #define XEC_QMSPI_NVIC_DIRECT(i) \ |
| MCHP_XEC_ECIA_NVIC_DIRECT(DT_INST_PROP_BY_IDX(i, girqs, 0)) |
| |
| #define XEC_QMSPI_PCR_INFO(i) \ |
| MCHP_XEC_PCR_SCR_ENCODE(DT_INST_CLOCKS_CELL(i, regidx), \ |
| DT_INST_CLOCKS_CELL(i, bitpos), \ |
| DT_INST_CLOCKS_CELL(i, domain)) |
| |
| /* |
| * 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 QMSPI_XEC_DEVICE(i) \ |
| \ |
| PINCTRL_DT_INST_DEFINE(i); \ |
| \ |
| static void qmspi_xec_irq_config_func_##i(void) \ |
| { \ |
| IRQ_CONNECT(DT_INST_IRQN(i), \ |
| DT_INST_IRQ(i, priority), \ |
| qmspi_xec_isr, \ |
| DEVICE_DT_INST_GET(i), 0); \ |
| irq_enable(DT_INST_IRQN(i)); \ |
| } \ |
| \ |
| static struct spi_qmspi_data qmspi_xec_data_##i = { \ |
| SPI_CONTEXT_INIT_LOCK(qmspi_xec_data_##i, ctx), \ |
| SPI_CONTEXT_INIT_SYNC(qmspi_xec_data_##i, ctx), \ |
| }; \ |
| static const struct spi_qmspi_config qmspi_xec_config_##i = { \ |
| .regs = (struct qmspi_regs *) DT_INST_REG_ADDR(i), \ |
| .clk_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(i)), \ |
| .clksrc = { .pcr_info = XEC_QMSPI_PCR_INFO(i), }, \ |
| .clock_freq = DT_INST_PROP_OR(i, clock_frequency, MHZ(12)), \ |
| .cs1_freq = DT_INST_PROP_OR(i, cs1_freq, 0), \ |
| .cs_timing = XEC_QMSPI_CS_TIMING(i), \ |
| .taps_adj = XEC_QMSPI_TAPS_ADJ(i), \ |
| .girq = XEC_QMSPI_GIRQ(i), \ |
| .girq_pos = XEC_QMSPI_GIRQ_POS(i), \ |
| .girq_nvic_aggr = XEC_QMSPI_NVIC_AGGR(i), \ |
| .girq_nvic_direct = XEC_QMSPI_NVIC_DIRECT(i), \ |
| .irq_pri = DT_INST_IRQ(i, priority), \ |
| .chip_sel = DT_INST_PROP_OR(i, chip_select, 0), \ |
| .width = DT_INST_PROP_OR(0, lines, 1), \ |
| .irq_config_func = qmspi_xec_irq_config_func_##i, \ |
| .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(i), \ |
| }; \ |
| PM_DEVICE_DT_INST_DEFINE(i, qmspi_xec_pm_action); \ |
| DEVICE_DT_INST_DEFINE(i, qmspi_xec_init, \ |
| PM_DEVICE_DT_INST_GET(i), \ |
| &qmspi_xec_data_##i, &qmspi_xec_config_##i, \ |
| POST_KERNEL, CONFIG_SPI_INIT_PRIORITY, \ |
| &spi_qmspi_xec_driver_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(QMSPI_XEC_DEVICE) |