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(&regs->TX_FIFO) = data8;
+}
+
+static inline u8_t rxb_rd8(QMSPI_Type *regs)
+{
+	return REG8(&regs->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