drivers : spi : mec1501 : XEC SPI driver
SPI driver for MEC1501 QMSPI supporting synchronous only.
Signed-off-by: Scott Worley <scott.worley@microchip.com>
diff --git a/boards/arm/mec15xxevb_assy6853/Kconfig.defconfig b/boards/arm/mec15xxevb_assy6853/Kconfig.defconfig
index 2a50d61..fc2d091 100644
--- a/boards/arm/mec15xxevb_assy6853/Kconfig.defconfig
+++ b/boards/arm/mec15xxevb_assy6853/Kconfig.defconfig
@@ -30,7 +30,7 @@
default y
config PINMUX_XEC_GPIO200_236
- default y if ADC_XEC
+ default y if ADC_XEC || SPI_XEC_QMSPI
default n
config PINMUX_XEC_GPIO240_276
@@ -119,4 +119,21 @@
endif #PS2
+if SPI
+
+config SPI_XEC_QMSPI
+ default y
+
+if SPI_XEC_QMSPI
+
+config SPI_0
+ default y
+
+config SPI_0_OP_MODES
+ default 1
+
+endif # SPI_XEC_QMSPI
+
+endif # SPI
+
endif # BOARD_MEC15XXEVB_ASSY6853
diff --git a/boards/arm/mec15xxevb_assy6853/mec15xxevb_assy6853.dts b/boards/arm/mec15xxevb_assy6853/mec15xxevb_assy6853.dts
index 7593f67..69c931e 100644
--- a/boards/arm/mec15xxevb_assy6853/mec15xxevb_assy6853.dts
+++ b/boards/arm/mec15xxevb_assy6853/mec15xxevb_assy6853.dts
@@ -74,3 +74,9 @@
status = "okay";
};
+&spi0 {
+ status = "okay";
+ port_sel = <0>;
+ chip_select = <0>;
+ lines = <1>;
+};
diff --git a/boards/arm/mec15xxevb_assy6853/mec15xxevb_assy6853_defconfig b/boards/arm/mec15xxevb_assy6853/mec15xxevb_assy6853_defconfig
index dd3bb03..37b4b49 100644
--- a/boards/arm/mec15xxevb_assy6853/mec15xxevb_assy6853_defconfig
+++ b/boards/arm/mec15xxevb_assy6853/mec15xxevb_assy6853_defconfig
@@ -22,3 +22,5 @@
CONFIG_I2C_INIT_PRIORITY=60
CONFIG_ESPI=y
+
+CONFIG_SPI=y
diff --git a/boards/arm/mec15xxevb_assy6853/pinmux.c b/boards/arm/mec15xxevb_assy6853/pinmux.c
index f6ea9a6..6e84ff2 100644
--- a/boards/arm/mec15xxevb_assy6853/pinmux.c
+++ b/boards/arm/mec15xxevb_assy6853/pinmux.c
@@ -261,6 +261,40 @@
MCHP_GPIO_CTRL_MUX_F1 | MCHP_GPIO_CTRL_BUFT_OPENDRAIN);
#endif /* CONFIG_KSCAN_XEC */
+#ifdef CONFIG_SPI_XEC_QMSPI
+#if defined(DT_INST_0_MICROCHIP_XEC_QMSPI)
+ mchp_pcr_periph_slp_ctrl(PCR_QMSPI, MCHP_PCR_SLEEP_DIS);
+
+#if DT_SPI_XEC_QMSPI_0_PORT_SEL == 0
+ /* Port 0: Shared SPI pins. Shared has two chip selects */
+#if DT_SPI_XEC_QMSPI_0_CHIP_SELECT == 0
+ pinmux_pin_set(portb, MCHP_GPIO_055, MCHP_GPIO_CTRL_MUX_F2);
+#else
+ pinmux_pin_set(porta, MCHP_GPIO_002, MCHP_GPIO_CTRL_MUX_F2);
+#endif
+ pinmux_pin_set(portb, MCHP_GPIO_056, MCHP_GPIO_CTRL_MUX_F2);
+ pinmux_pin_set(porte, MCHP_GPIO_223, MCHP_GPIO_CTRL_MUX_F1);
+ pinmux_pin_set(porte, MCHP_GPIO_224, MCHP_GPIO_CTRL_MUX_F2);
+#if DT_SPI_XEC_QMSPI_0_LINES == 4
+ pinmux_pin_set(porte, MCHP_GPIO_227, MCHP_GPIO_CTRL_MUX_F1);
+ pinmux_pin_set(porta, MCHP_GPIO_016, MCHP_GPIO_CTRL_MUX_F2);
+#endif
+
+#else
+ /* Port 1: Private SPI pins. Only one chip select */
+ pinmux_pin_set(portc, MCHP_GPIO_124, MCHP_GPIO_CTRL_MUX_F1);
+ pinmux_pin_set(portc, MCHP_GPIO_125, MCHP_GPIO_CTRL_MUX_F1);
+ pinmux_pin_set(portc, MCHP_GPIO_121, MCHP_GPIO_CTRL_MUX_F1);
+ pinmux_pin_set(portc, MCHP_GPIO_122, MCHP_GPIO_CTRL_MUX_F1);
+#if DT_SPI_XEC_QMSPI_0_LINES == 4
+ pinmux_pin_set(portc, MCHP_GPIO_123, MCHP_GPIO_CTRL_MUX_F1);
+ pinmux_pin_set(portc, MCHP_GPIO_126, MCHP_GPIO_CTRL_MUX_F1);
+#endif
+#endif /* DT_SPI_XEC_QMSPI_0_PORT_SEL == 0 */
+
+#endif /* DT_INST_0_MICROCHIP_XEC_QMSPI */
+#endif /* CONFIG_SPI_XEC_QMSPI */
+
return 0;
}
diff --git a/drivers/spi/CMakeLists.txt b/drivers/spi/CMakeLists.txt
index c5602b9..88ea5fa 100644
--- a/drivers/spi/CMakeLists.txt
+++ b/drivers/spi/CMakeLists.txt
@@ -15,5 +15,6 @@
zephyr_library_sources_ifdef(CONFIG_NRFX_SPIS spi_nrfx_spis.c)
zephyr_library_sources_ifdef(CONFIG_SPI_LITESPI spi_litespi.c)
zephyr_library_sources_ifdef(CONFIG_SPI_OC_SIMPLE spi_oc_simple.c)
+zephyr_library_sources_ifdef(CONFIG_SPI_XEC_QMSPI spi_xec_qmspi.c)
zephyr_library_sources_ifdef(CONFIG_USERSPACE spi_handlers.c)
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index c7a408b..8845ce5 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -201,4 +201,6 @@
source "drivers/spi/Kconfig.oc_simple"
+source "drivers/spi/Kconfig.xec_qmspi"
+
endif # SPI
diff --git a/drivers/spi/Kconfig.xec_qmspi b/drivers/spi/Kconfig.xec_qmspi
new file mode 100644
index 0000000..da0c27a
--- /dev/null
+++ b/drivers/spi/Kconfig.xec_qmspi
@@ -0,0 +1,12 @@
+# Kconfig - Microchip XEC QMSPI
+#
+# Copyright (c) 2019 Microchip Technology Inc.
+# SPDX-License-Identifier: Apache-2.0
+
+menuconfig SPI_XEC_QMSPI
+ bool "Microchip XEC QMSPI driver"
+ default y
+ depends on SOC_FAMILY_MEC
+ select DMA if SPI_ASYNC
+ help
+ Enable support for the Microchip XEC QMSPI driver.
diff --git a/drivers/spi/spi_xec_qmspi.c b/drivers/spi/spi_xec_qmspi.c
new file mode 100644
index 0000000..0ba5964
--- /dev/null
+++ b/drivers/spi/spi_xec_qmspi.c
@@ -0,0 +1,681 @@
+/*
+ * Copyright (c) 2019 Microchip Technology Inc.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <logging/log.h>
+LOG_MODULE_REGISTER(spi_xec, CONFIG_SPI_LOG_LEVEL);
+
+#include "spi_context.h"
+#include <errno.h>
+#include <device.h>
+#include <drivers/spi.h>
+#include <soc.h>
+
+/* Device constant configuration parameters */
+struct spi_qmspi_config {
+ QMSPI_Type *regs;
+ u32_t cs_timing;
+ u8_t girq;
+ u8_t girq_pos;
+ u8_t girq_nvic_aggr;
+ u8_t girq_nvic_direct;
+ u8_t irq_pri;
+ u8_t chip_sel;
+ u8_t width; /* 1(single), 2(dual), 4(quad) */
+};
+
+/* Device run time data */
+struct spi_qmspi_data {
+ struct spi_context ctx;
+};
+
+static inline u32_t descr_rd(QMSPI_Type *regs, u32_t did)
+{
+ uintptr_t raddr = (uintptr_t)regs + MCHP_QMSPI_DESC0_OFS +
+ ((did & MCHP_QMSPI_C_NEXT_DESCR_MASK0) << 2);
+
+ return REG32(raddr);
+}
+
+static inline void descr_wr(QMSPI_Type *regs, u32_t did, u32_t val)
+{
+ uintptr_t raddr = (uintptr_t)regs + MCHP_QMSPI_DESC0_OFS +
+ ((did & MCHP_QMSPI_C_NEXT_DESCR_MASK0) << 2);
+
+ REG32(raddr) = val;
+}
+
+static inline void txb_wr8(QMSPI_Type *regs, u8_t data8)
+{
+ REG8(®s->TX_FIFO) = data8;
+}
+
+static inline u8_t rxb_rd8(QMSPI_Type *regs)
+{
+ return REG8(®s->RX_FIFO);
+}
+
+/*
+ * Program QMSPI frequency.
+ * MEC1501 base frequency is 48MHz. QMSPI frequency divider field in the
+ * mode register is defined as: 0=maximum divider of 256. Values 1 through
+ * 255 divide 48MHz by that value.
+ */
+static void qmspi_set_frequency(QMSPI_Type *regs, u32_t freq_hz)
+{
+ u32_t div, qmode;
+
+ if (freq_hz == 0) {
+ div = 0; /* max divider = 256 */
+ } else {
+ div = MCHP_QMSPI_INPUT_CLOCK_FREQ_HZ / freq_hz;
+ if (div == 0) {
+ div = 1; /* max freq. divider = 1 */
+ } else if (div > 0xffu) {
+ div = 0u; /* max divider = 256 */
+ }
+ }
+
+ qmode = regs->MODE & ~(MCHP_QMSPI_M_FDIV_MASK);
+ qmode |= (div << MCHP_QMSPI_M_FDIV_POS) & MCHP_QMSPI_M_FDIV_MASK;
+ regs->MODE = qmode;
+}
+
+/*
+ * 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 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
+ * MEC1501 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)
+ */
+
+const u8_t smode_tbl[4] = {
+ 0x00u, 0x06u, 0x01u, 0x07u
+};
+
+const u8_t smode48_tbl[4] = {
+ 0x04u, 0x02u, 0x05u, 0x03u
+};
+
+static void qmspi_set_signalling_mode(QMSPI_Type *regs, u32_t smode)
+{
+ const u8_t *ptbl;
+ u32_t m;
+
+ ptbl = smode_tbl;
+ if (((regs->MODE >> MCHP_QMSPI_M_FDIV_POS) &
+ MCHP_QMSPI_M_FDIV_MASK0) == 1) {
+ ptbl = smode48_tbl;
+ }
+
+ m = (u32_t)ptbl[smode & 0x03];
+ regs->MODE = (regs->MODE & ~(MCHP_QMSPI_M_SIG_MASK))
+ | (m << MCHP_QMSPI_M_SIG_POS);
+}
+
+/*
+ * QMSPI HW support single, dual, and quad.
+ * Return QMSPI Control/Descriptor register encoded value.
+ */
+static u32_t qmspi_config_get_lines(const struct spi_config *config)
+{
+ u32_t qlines;
+
+ switch (config->operation & SPI_LINES_MASK) {
+ case SPI_LINES_SINGLE:
+ qlines = MCHP_QMSPI_C_IFM_1X;
+ break;
+#if DT_SPI_XEC_QMSPI_0_LINES > 1
+ case SPI_LINES_DUAL:
+ qlines = MCHP_QMSPI_C_IFM_2X;
+ break;
+#endif
+#if DT_SPI_XEC_QMSPI_0_LINES > 2
+ case SPI_LINES_QUAD:
+ qlines = MCHP_QMSPI_C_IFM_4X;
+ break;
+#endif
+ default:
+ qlines = 0xffu;
+ }
+
+ return qlines;
+}
+
+/*
+ * Configure QMSPI.
+ * NOTE: QMSPI can control two chip selects. At this time we use CS0# only.
+ */
+static int qmspi_configure(struct device *dev,
+ const struct spi_config *config)
+{
+ const struct spi_qmspi_config *cfg = dev->config->config_info;
+ struct spi_qmspi_data *data = dev->driver_data;
+ QMSPI_Type *regs = cfg->regs;
+ u32_t smode;
+
+ if (spi_context_configured(&data->ctx, config)) {
+ return 0;
+ }
+
+ if (config->operation & (SPI_TRANSFER_LSB | SPI_OP_MODE_SLAVE
+ | SPI_MODE_LOOP)) {
+ return -ENOTSUP;
+ }
+
+ smode = qmspi_config_get_lines(config);
+ if (smode == 0xff) {
+ return -ENOTSUP;
+ }
+
+ regs->CTRL = smode;
+
+ /* Use the requested or next highest possible frequency */
+ qmspi_set_frequency(regs, config->frequency);
+
+ smode = 0;
+ if ((config->operation & SPI_MODE_CPHA) != 0U) {
+ smode |= (1ul << 0);
+ }
+
+ if ((config->operation & SPI_MODE_CPOL) != 0U) {
+ smode |= (1ul << 1);
+ }
+
+ qmspi_set_signalling_mode(regs, smode);
+
+ if (SPI_WORD_SIZE_GET(config->operation) != 8) {
+ return -ENOTSUP;
+ }
+
+ /* chip select */
+ smode = regs->MODE & ~(MCHP_QMSPI_M_CS_MASK);
+#if DT_SPI_XEC_QMSPI_0_CHIP_SELECT == 0
+ smode |= MCHP_QMSPI_M_CS0;
+#else
+ smode |= MCHP_QMSPI_M_CS1;
+#endif
+ regs->MODE = smode;
+
+ /* chip select timing */
+ regs->CSTM = cfg->cs_timing;
+
+ data->ctx.config = config;
+
+ /* Add driver specific data to SPI context structure */
+ spi_context_cs_configure(&data->ctx);
+
+ regs->MODE |= MCHP_QMSPI_M_ACTIVATE;
+
+ return 0;
+}
+
+/*
+ * Transmit dummy clocks - QMSPI will generate requested number of
+ * SPI clocks with I/O pins tri-stated.
+ * Single mode: 1 bit per clock -> IFM field = 00b. Max 0x7fff clocks
+ * Dual mode: 2 bits per clock -> IFM field = 01b. Max 0x3fff clocks
+ * Quad mode: 4 bits per clock -> IFM fiels = 1xb. Max 0x1fff clocks
+ * QMSPI unit size set to bits.
+ */
+static int qmspi_tx_dummy_clocks(QMSPI_Type *regs, u32_t nclocks)
+{
+ u32_t descr, ifm, qstatus;
+
+ ifm = regs->CTRL & MCHP_QMSPI_C_IFM_MASK;
+ descr = ifm | MCHP_QMSPI_C_TX_DIS | MCHP_QMSPI_C_XFR_UNITS_BITS
+ | MCHP_QMSPI_C_DESCR_LAST | MCHP_QMSPI_C_DESCR0;
+
+ if (ifm & 0x01) {
+ nclocks <<= 1;
+ } else if (ifm & 0x02) {
+ nclocks <<= 2;
+ }
+ descr |= (nclocks << MCHP_QMSPI_C_XFR_NUNITS_POS);
+
+ descr_wr(regs, 0, descr);
+
+ regs->CTRL |= MCHP_QMSPI_C_DESCR_EN;
+ regs->IEN = 0;
+ regs->STS = 0xfffffffful;
+
+ regs->EXE = MCHP_QMSPI_EXE_START;
+ do {
+ qstatus = regs->STS;
+ if (qstatus & MCHP_QMSPI_STS_PROG_ERR) {
+ return -EIO;
+ }
+ } while ((qstatus & MCHP_QMSPI_STS_DONE) == 0);
+
+ return 0;
+}
+
+/*
+ * Return unit size power of 2 given number of bytes to transfer.
+ */
+static u32_t qlen_shift(u32_t len)
+{
+ u32_t ushift;
+
+ /* is len a multiple of 4 or 16? */
+ if ((len & 0x0F) == 0) {
+ ushift = 4;
+ } else if ((len & 0x03) == 0) {
+ ushift = 2;
+ } else {
+ ushift = 0;
+ }
+
+ return ushift;
+}
+
+/*
+ * Return QMSPI unit size of the number of units field in QMSPI
+ * control/descriptor register.
+ * Input: power of 2 unit size 4, 2, or 0(default) corresponding
+ * to 16, 4, or 1 byte units.
+ */
+static u32_t get_qunits(u32_t qshift)
+{
+ if (qshift == 4) {
+ return MCHP_QMSPI_C_XFR_UNITS_16;
+ } else if (qshift == 2) {
+ return MCHP_QMSPI_C_XFR_UNITS_4;
+ } else {
+ return MCHP_QMSPI_C_XFR_UNITS_1;
+ }
+}
+
+/*
+ * Allocate(build) one or more descriptors.
+ * QMSPI contains 16 32-bit descriptor registers used as a linked
+ * list of operations. Using only 32-bits there are limitations.
+ * Each descriptor is limited to 0x7FFF units where unit size can
+ * be 1, 4, or 16 bytes. A descriptor can perform transmit or receive
+ * but not both simultaneously. Order of descriptor processing is specified
+ * by the first descriptor field of the control register, the next descriptor
+ * fields in each descriptor, and the descriptors last flag.
+ */
+static int qmspi_descr_alloc(QMSPI_Type *regs, const struct spi_buf *txb,
+ int didx, bool is_tx)
+{
+ u32_t descr, qshift, n, nu;
+ int dn;
+
+ if (didx >= MCHP_QMSPI_MAX_DESCR) {
+ return -EAGAIN;
+ }
+
+ if (txb->len == 0) {
+ return didx; /* nothing to do */
+ }
+
+ /* b[1:0] IFM and b[3:2] transmit mode */
+ descr = (regs->CTRL & MCHP_QMSPI_C_IFM_MASK);
+ if (is_tx) {
+ descr |= MCHP_QMSPI_C_TX_DATA;
+ } else {
+ descr |= MCHP_QMSPI_C_RX_EN;
+ }
+
+ /* b[11:10] unit size 1, 4, or 16 bytes */
+ qshift = qlen_shift(txb->len);
+ nu = txb->len >> qshift;
+ descr |= get_qunits(qshift);
+
+ do {
+ descr &= 0x0FFFul;
+
+ dn = didx + 1;
+ /* b[15:12] next descriptor pointer */
+ descr |= ((dn & MCHP_QMSPI_C_NEXT_DESCR_MASK0) <<
+ MCHP_QMSPI_C_NEXT_DESCR_POS);
+
+ n = nu;
+ if (n > MCHP_QMSPI_C_MAX_UNITS) {
+ n = MCHP_QMSPI_C_MAX_UNITS;
+ }
+
+ descr |= (n << MCHP_QMSPI_C_XFR_NUNITS_POS);
+ descr_wr(regs, didx, descr);
+
+ if (dn < MCHP_QMSPI_MAX_DESCR) {
+ didx++;
+ } else {
+ return -EAGAIN;
+ }
+
+ nu -= n;
+ } while (nu);
+
+ return dn;
+}
+
+static int qmspi_tx(QMSPI_Type *regs, const struct spi_buf *tx_buf,
+ bool close)
+{
+ const u8_t *p = tx_buf->buf;
+ size_t tlen = tx_buf->len;
+ u32_t descr;
+ int didx;
+
+ if (tlen == 0) {
+ return 0;
+ }
+
+ /* Buffer pointer is NULL and number of bytes != 0 ? */
+ if (p == NULL) {
+ return qmspi_tx_dummy_clocks(regs, tlen);
+ }
+
+ didx = qmspi_descr_alloc(regs, tx_buf, 0, true);
+ if (didx < 0) {
+ return didx;
+ }
+
+ /* didx points to last allocated descriptor + 1 */
+ __ASSERT(didx > 0, "QMSPI descriptor index=%d expected > 0\n", didx);
+ didx--;
+
+ descr = descr_rd(regs, didx) | MCHP_QMSPI_C_DESCR_LAST;
+ if (close) {
+ descr |= MCHP_QMSPI_C_CLOSE;
+ }
+ descr_wr(regs, didx, descr);
+
+ regs->CTRL = (regs->CTRL & MCHP_QMSPI_C_IFM_MASK) |
+ MCHP_QMSPI_C_DESCR_EN | MCHP_QMSPI_C_DESCR0;
+ regs->IEN = 0;
+ regs->STS = 0xfffffffful;
+
+ /* preload TX_FIFO */
+ while (tlen) {
+ tlen--;
+ txb_wr8(regs, *p);
+ p++;
+
+ if (regs->STS & MCHP_QMSPI_STS_TXBF_RO) {
+ break;
+ }
+ }
+
+ regs->EXE = MCHP_QMSPI_EXE_START;
+
+ if (regs->STS & MCHP_QMSPI_STS_PROG_ERR) {
+ return -EIO;
+ }
+
+ while (tlen) {
+
+ while (regs->STS & MCHP_QMSPI_STS_TXBF_RO) {
+ }
+
+ txb_wr8(regs, *p);
+ p++;
+ tlen--;
+ }
+
+ /* Wait for TX FIFO to drain and last byte to be clocked out */
+ for (;;) {
+ if (regs->STS & MCHP_QMSPI_STS_DONE) {
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static int qmspi_rx(QMSPI_Type *regs, const struct spi_buf *rx_buf,
+ bool close)
+{
+ u8_t *p = rx_buf->buf;
+ size_t rlen = rx_buf->len;
+ u32_t descr;
+ int didx;
+ u8_t data_byte;
+
+ if (rlen == 0) {
+ return 0;
+ }
+
+ didx = qmspi_descr_alloc(regs, rx_buf, 0, false);
+ if (didx < 0) {
+ return didx;
+ }
+
+ /* didx points to last allocated descriptor + 1 */
+ __ASSERT_NO_MSG(didx > 0);
+ didx--;
+
+ descr = descr_rd(regs, didx) | MCHP_QMSPI_C_DESCR_LAST;
+ if (close) {
+ descr |= MCHP_QMSPI_C_CLOSE;
+ }
+ descr_wr(regs, didx, descr);
+
+ regs->CTRL = (regs->CTRL & MCHP_QMSPI_C_IFM_MASK)
+ | MCHP_QMSPI_C_DESCR_EN | MCHP_QMSPI_C_DESCR0;
+ regs->IEN = 0;
+ regs->STS = 0xfffffffful;
+
+ /*
+ * Trigger read based on the descriptor(s) programmed above.
+ * QMSPI will generate clocks until the RX FIFO is filled.
+ * More clocks will be generated as we pull bytes from the RX FIFO.
+ * QMSPI Programming error will be triggered after start if
+ * descriptors were programmed options that cannot be enabled
+ * simultaneously.
+ */
+ regs->EXE = MCHP_QMSPI_EXE_START;
+ if (regs->STS & MCHP_QMSPI_STS_PROG_ERR) {
+ return -EIO;
+ }
+
+ while (rlen) {
+ if (!(regs->STS & MCHP_QMSPI_STS_RXBE_RO)) {
+ data_byte = rxb_rd8(regs);
+ if (p != NULL) {
+ *p++ = data_byte;
+ }
+ rlen--;
+ }
+ }
+
+ return 0;
+}
+
+static int qmspi_transceive(struct device *dev,
+ const struct spi_config *config,
+ const struct spi_buf_set *tx_bufs,
+ const struct spi_buf_set *rx_bufs)
+{
+ const struct spi_qmspi_config *cfg = dev->config->config_info;
+ struct spi_qmspi_data *data = dev->driver_data;
+ QMSPI_Type *regs = cfg->regs;
+ const struct spi_buf *ptx;
+ const struct spi_buf *prx;
+ size_t nb;
+ u32_t descr, last_didx;
+ int err;
+
+ spi_context_lock(&data->ctx, false, NULL);
+
+ err = qmspi_configure(dev, config);
+ if (err != 0) {
+ goto done;
+ }
+
+ spi_context_cs_control(&data->ctx, true);
+
+ if (tx_bufs != NULL) {
+ ptx = tx_bufs->buffers;
+ nb = tx_bufs->count;
+ while (nb--) {
+ err = qmspi_tx(regs, ptx, false);
+ if (err != 0) {
+ goto done;
+ }
+ ptx++;
+ }
+ }
+
+ if (rx_bufs != NULL) {
+ prx = rx_bufs->buffers;
+ nb = rx_bufs->count;
+ while (nb--) {
+ err = qmspi_rx(regs, prx, false);
+ if (err != 0) {
+ goto done;
+ }
+ prx++;
+ }
+ }
+
+ /*
+ * If caller doesn't need CS# held asserted then find the last
+ * descriptor, set its close flag, and set stop.
+ */
+ if (!(config->operation & SPI_HOLD_ON_CS)) {
+ /* Get last descriptor from status register */
+ last_didx = (regs->STS >> MCHP_QMSPI_C_NEXT_DESCR_POS)
+ & MCHP_QMSPI_C_NEXT_DESCR_MASK0;
+ descr = descr_rd(regs, last_didx) | MCHP_QMSPI_C_CLOSE;
+ descr_wr(regs, last_didx, descr);
+ regs->EXE = MCHP_QMSPI_EXE_STOP;
+ }
+
+ spi_context_cs_control(&data->ctx, false);
+
+done:
+ spi_context_release(&data->ctx, err);
+ return err;
+}
+
+static int qmspi_transceive_sync(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);
+}
+
+#ifdef CONFIG_SPI_ASYNC
+static int qmspi_transceive_async(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 -ENOTSUP;
+}
+#endif
+
+static int qmspi_release(struct device *dev,
+ const struct spi_config *config)
+{
+ struct spi_qmspi_data *data = dev->driver_data;
+ const struct spi_qmspi_config *cfg = dev->config->config_info;
+ QMSPI_Type *regs = cfg->regs;
+
+ /* Force CS# to de-assert on next unit boundary */
+ regs->EXE = MCHP_QMSPI_EXE_STOP;
+
+ while (regs->STS & MCHP_QMSPI_STS_ACTIVE_RO) {
+ }
+
+ spi_context_unlock_unconditionally(&data->ctx);
+
+ return 0;
+}
+
+/*
+ * Initialize QMSPI controller.
+ * Disable sleep control.
+ * Disable and clear interrupt status.
+ * Initialize SPI context.
+ * QMSPI will be configured and enabled when the transceive API is called.
+ */
+static int qmspi_init(struct device *dev)
+{
+ const struct spi_qmspi_config *cfg = dev->config->config_info;
+ struct spi_qmspi_data *data = dev->driver_data;
+ QMSPI_Type *regs = cfg->regs;
+
+ mchp_pcr_periph_slp_ctrl(PCR_QMSPI, MCHP_PCR_SLEEP_DIS);
+
+ regs->MODE = MCHP_QMSPI_M_SRST;
+
+ MCHP_GIRQ_CLR_EN(cfg->girq, cfg->girq_pos);
+ MCHP_GIRQ_SRC_CLR(cfg->girq, cfg->girq_pos);
+
+ MCHP_GIRQ_BLK_CLREN(cfg->girq);
+ NVIC_ClearPendingIRQ(cfg->girq_nvic_direct);
+
+ spi_context_unlock_unconditionally(&data->ctx);
+
+ return 0;
+}
+
+static const struct spi_driver_api spi_qmspi_driver_api = {
+ .transceive = qmspi_transceive_sync,
+#ifdef CONFIG_SPI_ASYNC
+ .transceive_async = qmspi_transceive_async,
+#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_0_CS_TIMING XEC_QMSPI_CS_TIMING_VAL( \
+ DT_SPI_XEC_QMSPI_0_DCSCKON, \
+ DT_SPI_XEC_QMSPI_0_DCKCSOFF, \
+ DT_SPI_XEC_QMSPI_0_DLDH, \
+ DT_SPI_XEC_QMSPI_0_DCSDA)
+
+#ifdef DT_SPI_XEC_QMSPI_0_BASE_ADDRESS
+
+static const struct spi_qmspi_config spi_qmspi_0_config = {
+ .regs = (QMSPI_Type *)DT_SPI_XEC_QMSPI_0_BASE_ADDRESS,
+ .cs_timing = XEC_QMSPI_0_CS_TIMING,
+ .girq = MCHP_QMSPI_GIRQ_NUM,
+ .girq_pos = MCHP_QMSPI_GIRQ_POS,
+ .girq_nvic_direct = MCHP_QMSPI_GIRQ_NVIC_DIRECT,
+ .irq_pri = DT_SPI_XEC_QMSPI_0_IRQ_PRI,
+ .chip_sel = DT_SPI_XEC_QMSPI_0_CHIP_SELECT,
+ .width = DT_SPI_XEC_QMSPI_0_LINES
+};
+
+static struct spi_qmspi_data spi_qmspi_0_dev_data = {
+ SPI_CONTEXT_INIT_LOCK(spi_qmspi_0_dev_data, ctx),
+ SPI_CONTEXT_INIT_SYNC(spi_qmspi_0_dev_data, ctx)
+};
+
+DEVICE_AND_API_INIT(spi_xec_qmspi_0,
+ DT_SPI_XEC_QMSPI_0_LABEL,
+ &qmspi_init, &spi_qmspi_0_dev_data,
+ &spi_qmspi_0_config, POST_KERNEL,
+ CONFIG_SPI_INIT_PRIORITY, &spi_qmspi_driver_api);
+
+#endif /* DT_SPI_XEC_QMSPI_0_BASE_ADDRESS */
diff --git a/dts/arm/microchip/mec1501hsz.dtsi b/dts/arm/microchip/mec1501hsz.dtsi
index 1d57725..9c77bef 100644
--- a/dts/arm/microchip/mec1501hsz.dtsi
+++ b/dts/arm/microchip/mec1501hsz.dtsi
@@ -298,6 +298,24 @@
#address-cells = <1>;
#size-cells = <0>;
};
+ spi0: spi@40070000 {
+ compatible = "microchip,xec-qmspi";
+ reg = <0x40070000 0x400>;
+ interrupts = <91 2>;
+ clock-frequency = <12000000>;
+ label = "SPI_0";
+ rxdma = <11>;
+ txdma = <10>;
+ lines = <1>;
+ chip_select = <0>;
+ dcsckon = <6>;
+ dckcsoff = <4>;
+ dldh = <6>;
+ dcsda = <6>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ status = "disabled";
+ };
};
};
diff --git a/dts/bindings/spi/microchip,xec-qmspi.yaml b/dts/bindings/spi/microchip,xec-qmspi.yaml
new file mode 100644
index 0000000..3f6ba0a
--- /dev/null
+++ b/dts/bindings/spi/microchip,xec-qmspi.yaml
@@ -0,0 +1,63 @@
+#
+# Copyright (c) 2018, Google LLC.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+title: Microchip XEC Quad Master SPI driver
+
+description: >
+ This binding gives a base representation of the Microchip XEC QMSPI controller
+
+compatible: "microchip,xec-qmspi"
+
+include: spi-controller.yaml
+
+properties:
+ reg:
+ required: true
+
+ port_sel:
+ type: int
+ required: true
+ description: SPI Port 0 or 1.
+
+ rxdma:
+ type: int
+ required: true
+ description: Receive DMA channel
+
+ txdma:
+ type: int
+ required: true
+ description: Transmit DMA channel
+
+ lines:
+ type: int
+ required: true
+ description: QMSPI lines 1, 2, or 4
+
+ chip_select:
+ type: int
+ required: true
+ description: Use QMSPI CS0# or CS1#
+
+ dcsckon:
+ type: int
+ required: true
+ description: Delay in system clocks from CS# assertion to first clock edge
+
+ dckcsoff:
+ type: int
+ required: true
+ description: Delay in system clocks from last clock edge to CS# de-assertion
+
+ dldh:
+ type: int
+ required: true
+ description: Delay in system clocks from CS# de-assertion to driving HOLD# and WP#
+
+ dcsda:
+ type: int
+ required: true
+ description: Delay in system clocks from CS# de-assertion to CS# assertion
diff --git a/soc/arm/microchip_mec/mec1501/Kconfig.defconfig.mec1501hsz b/soc/arm/microchip_mec/mec1501/Kconfig.defconfig.mec1501hsz
index 6d75fe0..45cf806 100644
--- a/soc/arm/microchip_mec/mec1501/Kconfig.defconfig.mec1501hsz
+++ b/soc/arm/microchip_mec/mec1501/Kconfig.defconfig.mec1501hsz
@@ -77,4 +77,11 @@
endif # KSCAN
+if SPI
+
+config SPI_XEC_QMSPI
+ def_bool y
+
+endif # SPI
+
endif # SOC_MEC1501_HSZ
diff --git a/soc/arm/microchip_mec/mec1501/dts_fixup.h b/soc/arm/microchip_mec/mec1501/dts_fixup.h
index 405c790..cda6fd4 100644
--- a/soc/arm/microchip_mec/mec1501/dts_fixup.h
+++ b/soc/arm/microchip_mec/mec1501/dts_fixup.h
@@ -154,3 +154,35 @@
#define DT_PS2_XEC_1_LABEL DT_MICROCHIP_XEC_PS2_40009040_LABEL
#define DT_PS2_XEC_1_SIZE DT_MICROCHIP_XEC_PS2_40009040_SIZE
+#define DT_SPI_XEC_QMSPI_0_LABEL \
+ DT_MICROCHIP_XEC_QMSPI_40070000_LABEL
+#define DT_SPI_XEC_QMSPI_0_BASE_ADDRESS \
+ DT_MICROCHIP_XEC_QMSPI_40070000_BASE_ADDRESS
+#define DT_SPI_XEC_QMSPI_0_IRQ \
+ DT_MICROCHIP_XEC_QMSPI_40070000_IRQ_0
+#define DT_SPI_XEC_QMSPI_0_IRQ_PRI \
+ DT_MICROCHIP_XEC_QMSPI_40070000_IRQ_0_PRIORITY
+#define DT_SPI_XEC_QMSPI_0_PORT_SEL \
+ DT_MICROCHIP_XEC_QMSPI_40070000_PORT_SEL
+#define DT_SPI_XEC_QMSPI_0_LINES \
+ DT_MICROCHIP_XEC_QMSPI_40070000_LINES
+#define DT_SPI_XEC_QMSPI_0_RXDMA \
+ DT_MICROCHIP_XEC_QMSPI_40070000_RXDMA
+#define DT_SPI_XEC_QMSPI_0_TXDMA \
+ DT_MICROCHIP_XEC_QMSPI_40070000_TXDMA
+#define DT_SPI_XEC_QMSPI_0_GIRQ \
+ DT_MICROCHIP_XEC_QMSPI_40070000_GIRQ
+#define DT_SPI_XEC_QMSPI_0_GIRQ_POS \
+ DT_MICROCHIP_XEC_QMSPI_40070000_GIRQ_POS
+#define DT_SPI_XEC_QMSPI_0_CLOCK_FREQUENCY \
+ DT_MICROCHIP_XEC_QMSPI_40070000_CLOCK_FREQUENCY
+#define DT_SPI_XEC_QMSPI_0_CHIP_SELECT \
+ DT_MICROCHIP_XEC_QMSPI_40070000_CHIP_SELECT
+#define DT_SPI_XEC_QMSPI_0_DCSCKON \
+ DT_MICROCHIP_XEC_QMSPI_40070000_DCSCKON
+#define DT_SPI_XEC_QMSPI_0_DCKCSOFF \
+ DT_MICROCHIP_XEC_QMSPI_40070000_DCKCSOFF
+#define DT_SPI_XEC_QMSPI_0_DLDH \
+ DT_MICROCHIP_XEC_QMSPI_40070000_DLDH
+#define DT_SPI_XEC_QMSPI_0_DCSDA \
+ DT_MICROCHIP_XEC_QMSPI_40070000_DCSDA