drivers: spi: Add Gecko SPI driver
This commit adds SPI driver and its bindings using the USART peripheral
for Silicon Labs EFM32 and EFR32 MCUs.
Signed-off-by: Christian Taedcke <hacking@taedcke.com>
diff --git a/drivers/spi/CMakeLists.txt b/drivers/spi/CMakeLists.txt
index e1f637e..7a463b4 100644
--- a/drivers/spi/CMakeLists.txt
+++ b/drivers/spi/CMakeLists.txt
@@ -18,5 +18,6 @@
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_SPI_GECKO spi_gecko.c)
zephyr_library_sources_ifdef(CONFIG_USERSPACE spi_handlers.c)
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 820f8bf..dc84b14 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -244,4 +244,6 @@
source "drivers/spi/Kconfig.xec_qmspi"
+source "drivers/spi/Kconfig.gecko"
+
endif # SPI
diff --git a/drivers/spi/Kconfig.gecko b/drivers/spi/Kconfig.gecko
new file mode 100644
index 0000000..c2228d9
--- /dev/null
+++ b/drivers/spi/Kconfig.gecko
@@ -0,0 +1,14 @@
+# Kconfig.gecko - Gecko SPI configuration option
+#
+# Copyright (c) 2019 Christian Taedcke <hacking@taedcke.com>
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+menuconfig SPI_GECKO
+ bool "Gecko SPI controller driver"
+ depends on HAS_SILABS_GECKO
+ depends on GPIO_GECKO
+ select SOC_GECKO_USART
+ help
+ Enable the SPI peripherals on Gecko
diff --git a/drivers/spi/spi_gecko.c b/drivers/spi/spi_gecko.c
new file mode 100644
index 0000000..4d0c243
--- /dev/null
+++ b/drivers/spi/spi_gecko.c
@@ -0,0 +1,344 @@
+/*
+ * Copyright (c) 2019 Christian Taedcke <hacking@taedcke.com>
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#define LOG_LEVEL CONFIG_SPI_LOG_LEVEL
+#include <logging/log.h>
+LOG_MODULE_REGISTER(spi_gecko);
+#include "spi_context.h"
+
+#include <sys/sys_io.h>
+#include <device.h>
+#include <drivers/spi.h>
+#include <soc.h>
+
+#include "em_cmu.h"
+#include "em_usart.h"
+
+#include <stdbool.h>
+
+#ifndef CONFIG_SOC_GECKO_HAS_INDIVIDUAL_PIN_LOCATION
+#error "Individual pin location support is required"
+#endif
+
+#define CLOCK_USART(id) _CONCAT(cmuClock_USART, id)
+
+#define SPI_WORD_SIZE 8
+
+#define DEV_DATA(dev) ((struct spi_gecko_data *) ((dev)->driver_data))
+
+/* Structure Declarations */
+
+struct spi_gecko_data {
+ struct spi_context ctx;
+};
+
+struct spi_gecko_config {
+ USART_TypeDef *base;
+ CMU_Clock_TypeDef clock;
+ struct soc_gpio_pin pin_rx;
+ struct soc_gpio_pin pin_tx;
+ struct soc_gpio_pin pin_clk;
+ u8_t loc_rx;
+ u8_t loc_tx;
+ u8_t loc_clk;
+};
+
+
+/* Helper Functions */
+static int spi_config(struct device *dev, const struct spi_config *config,
+ u16_t *control)
+{
+ const struct spi_gecko_config *gecko_config = dev->config->config_info;
+ struct spi_gecko_data *data = DEV_DATA(dev);
+
+ if (SPI_WORD_SIZE_GET(config->operation) != SPI_WORD_SIZE) {
+ LOG_ERR("Word size must be %d", SPI_WORD_SIZE);
+ 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 ((config->operation & SPI_LINES_MASK) != SPI_LINES_SINGLE) {
+ LOG_ERR("Only supports single mode");
+ return -ENOTSUP;
+ }
+
+ if (config->operation & SPI_TRANSFER_LSB) {
+ LOG_ERR("LSB first not supported");
+ return -ENOTSUP;
+ }
+
+ if (config->operation & (SPI_MODE_CPOL | SPI_MODE_CPHA)) {
+ LOG_ERR("Only supports CPOL=CPHA=0");
+ return -ENOTSUP;
+ }
+
+ if (config->operation & SPI_OP_MODE_SLAVE) {
+ LOG_ERR("Slave mode not supported");
+ return -ENOTSUP;
+ }
+
+ /* Set Loopback */
+ if (config->operation & SPI_MODE_LOOP) {
+ gecko_config->base->CTRL |= USART_CTRL_LOOPBK;
+ } else {
+ gecko_config->base->CTRL &= ~USART_CTRL_LOOPBK;
+ }
+
+ /* Set word size */
+ gecko_config->base->FRAME = usartDatabits8
+ | USART_FRAME_STOPBITS_DEFAULT
+ | USART_FRAME_PARITY_DEFAULT;
+
+ /* At this point, it's mandatory to set this on the context! */
+ data->ctx.config = config;
+
+ spi_context_cs_configure(&data->ctx);
+
+ return 0;
+}
+
+static void spi_gecko_send(USART_TypeDef *usart, u8_t frame)
+{
+ /* Write frame to register */
+ USART_Tx(usart, frame);
+
+ /* Wait until the transfer ends */
+ while (!(usart->STATUS & USART_STATUS_TXC)) {
+ }
+}
+
+static u8_t spi_gecko_recv(USART_TypeDef *usart)
+{
+ /* Return data inside rx register */
+ return (u8_t)usart->RXDATA;
+}
+
+static bool spi_gecko_transfer_ongoing(struct spi_gecko_data *data)
+{
+ return spi_context_tx_on(&data->ctx) || spi_context_rx_on(&data->ctx);
+}
+
+static inline u8_t spi_gecko_next_tx(struct spi_gecko_data *data)
+{
+ u8_t tx_frame = 0;
+
+ if (spi_context_tx_buf_on(&data->ctx)) {
+ tx_frame = UNALIGNED_GET((u8_t *)(data->ctx.tx_buf));
+ }
+
+ return tx_frame;
+}
+
+static int spi_gecko_shift_frames(USART_TypeDef *usart,
+ struct spi_gecko_data *data)
+{
+ u8_t tx_frame;
+ u8_t rx_frame;
+
+ tx_frame = spi_gecko_next_tx(data);
+ spi_gecko_send(usart, tx_frame);
+ spi_context_update_tx(&data->ctx, 1, 1);
+
+ rx_frame = spi_gecko_recv(usart);
+
+ if (spi_context_rx_buf_on(&data->ctx)) {
+ UNALIGNED_PUT(rx_frame, (u8_t *)data->ctx.rx_buf);
+ }
+ spi_context_update_rx(&data->ctx, 1, 1);
+ return 0;
+}
+
+
+static void spi_gecko_xfer(struct device *dev,
+ const struct spi_config *config)
+{
+ int ret;
+ struct spi_context *ctx = &DEV_DATA(dev)->ctx;
+ const struct spi_gecko_config *gecko_config = dev->config->config_info;
+ struct spi_gecko_data *data = DEV_DATA(dev);
+
+ spi_context_cs_control(ctx, true);
+
+ do {
+ ret = spi_gecko_shift_frames(gecko_config->base, data);
+ } while (!ret && spi_gecko_transfer_ongoing(data));
+
+ spi_context_cs_control(ctx, false);
+ spi_context_complete(ctx, 0);
+}
+
+static void spi_gecko_init_pins(struct device *dev)
+{
+ const struct spi_gecko_config *config = dev->config->config_info;
+
+ soc_gpio_configure(&config->pin_rx);
+ soc_gpio_configure(&config->pin_tx);
+ soc_gpio_configure(&config->pin_clk);
+
+ /* disable all pins while configuring */
+ config->base->ROUTEPEN = 0;
+
+ config->base->ROUTELOC0 =
+ (config->loc_tx << _USART_ROUTELOC0_TXLOC_SHIFT) |
+ (config->loc_rx << _USART_ROUTELOC0_RXLOC_SHIFT) |
+ (config->loc_clk << _USART_ROUTELOC0_CLKLOC_SHIFT);
+
+ config->base->ROUTELOC1 = _USART_ROUTELOC1_RESETVALUE;
+
+ config->base->ROUTEPEN = USART_ROUTEPEN_RXPEN | USART_ROUTEPEN_TXPEN |
+ USART_ROUTEPEN_CLKPEN;
+}
+
+
+/* API Functions */
+
+static int spi_gecko_init(struct device *dev)
+{
+ const struct spi_gecko_config *config = dev->config->config_info;
+ USART_InitSync_TypeDef usartInit = USART_INITSYNC_DEFAULT;
+
+ /* The peripheral and gpio clock are already enabled from soc and gpio
+ * driver
+ */
+
+ usartInit.enable = usartDisable;
+ usartInit.baudrate = 1000000;
+ usartInit.databits = usartDatabits8;
+ usartInit.master = 1;
+ usartInit.msbf = 1;
+ usartInit.clockMode = usartClockMode0;
+#if defined(USART_INPUT_RXPRS) && defined(USART_TRIGCTRL_AUTOTXTEN)
+ usartInit.prsRxEnable = 0;
+ usartInit.prsRxCh = 0;
+ usartInit.autoTx = 0;
+#endif
+
+ /* Enable USART clock */
+ CMU_ClockEnable(config->clock, true);
+
+ /* Init USART */
+ USART_InitSync(config->base, &usartInit);
+
+ /* Initialize USART pins */
+ spi_gecko_init_pins(dev);
+
+ /* Enable the peripheral */
+ config->base->CMD = (uint32_t) usartEnable;
+
+ return 0;
+}
+
+static int spi_gecko_transceive(struct device *dev,
+ const struct spi_config *config,
+ const struct spi_buf_set *tx_bufs,
+ const struct spi_buf_set *rx_bufs)
+{
+ u16_t control = 0;
+
+ spi_config(dev, config, &control);
+ spi_context_buffers_setup(&DEV_DATA(dev)->ctx, tx_bufs, rx_bufs, 1);
+ spi_gecko_xfer(dev, config);
+ return 0;
+}
+
+#ifdef CONFIG_SPI_ASYNC
+static int spi_gecko_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 /* CONFIG_SPI_ASYNC */
+
+static int spi_gecko_release(struct device *dev,
+ const struct spi_config *config)
+{
+ const struct spi_gecko_config *gecko_config = dev->config->config_info;
+
+ if (!(gecko_config->base->STATUS & USART_STATUS_TXIDLE)) {
+ return -EBUSY;
+ }
+ return 0;
+}
+
+/* Device Instantiation */
+static struct spi_driver_api spi_gecko_api = {
+ .transceive = spi_gecko_transceive,
+#ifdef CONFIG_SPI_ASYNC
+ .transceive_async = spi_gecko_transceive_async,
+#endif /* CONFIG_SPI_ASYNC */
+ .release = spi_gecko_release,
+};
+
+#define SPI_INIT2(n, usart) \
+ static struct spi_gecko_data spi_gecko_data_##n = { \
+ SPI_CONTEXT_INIT_LOCK(spi_gecko_data_##n, ctx), \
+ SPI_CONTEXT_INIT_SYNC(spi_gecko_data_##n, ctx), \
+ }; \
+ static struct spi_gecko_config spi_gecko_cfg_##n = { \
+ .base = (USART_TypeDef *) \
+ DT_INST_##n##_SILABS_GECKO_SPI_USART_BASE_ADDRESS, \
+ .clock = CLOCK_USART(usart), \
+ .pin_rx = { DT_INST_##n##_SILABS_GECKO_SPI_USART_LOCATION_RX_1, \
+ DT_INST_##n##_SILABS_GECKO_SPI_USART_LOCATION_RX_2, \
+ gpioModeInput, 1}, \
+ .pin_tx = { DT_INST_##n##_SILABS_GECKO_SPI_USART_LOCATION_TX_1, \
+ DT_INST_##n##_SILABS_GECKO_SPI_USART_LOCATION_TX_2, \
+ gpioModePushPull, 1}, \
+ .pin_clk = { DT_INST_##n##_SILABS_GECKO_SPI_USART_LOCATION_CLK_1, \
+ DT_INST_##n##_SILABS_GECKO_SPI_USART_LOCATION_CLK_2, \
+ gpioModePushPull, 1}, \
+ .loc_rx = DT_INST_##n##_SILABS_GECKO_SPI_USART_LOCATION_RX_0, \
+ .loc_tx = DT_INST_##n##_SILABS_GECKO_SPI_USART_LOCATION_TX_0, \
+ .loc_clk = DT_INST_##n##_SILABS_GECKO_SPI_USART_LOCATION_CLK_0, \
+ }; \
+ DEVICE_AND_API_INIT(spi_##n, \
+ DT_INST_##n##_SILABS_GECKO_SPI_USART_LABEL, \
+ spi_gecko_init, \
+ &spi_gecko_data_##n, \
+ &spi_gecko_cfg_##n, \
+ POST_KERNEL, \
+ CONFIG_SPI_INIT_PRIORITY, \
+ &spi_gecko_api)
+
+#define SPI_ID(n) DT_INST_##n##_SILABS_GECKO_SPI_USART_PERIPHERAL_ID
+
+#define SPI_INIT(n) SPI_INIT2(n, SPI_ID(n))
+
+#ifdef DT_INST_0_SILABS_GECKO_SPI_USART_LABEL
+
+SPI_INIT(0);
+
+#endif /* DT_INST_0_SILABS_GECKO_SPI_USART_LABEL */
+
+#ifdef DT_INST_1_SILABS_GECKO_SPI_USART_LABEL
+
+SPI_INIT(1);
+
+#endif /* DT_INST_1_SILABS_GECKO_SPI_USART_LABEL */
+
+#ifdef DT_INST_2_SILABS_GECKO_SPI_USART_LABEL
+
+SPI_INIT(2);
+
+#endif /* DT_INST_2_SILABS_GECKO_SPI_USART_LABEL */
+
+#ifdef DT_INST_3_SILABS_GECKO_SPI_USART_LABEL
+
+SPI_INIT(3);
+
+#endif /* DT_INST_3_SILABS_GECKO_SPI_USART_LABEL */
diff --git a/dts/bindings/spi/silabs,gecko-spi-usart.yaml b/dts/bindings/spi/silabs,gecko-spi-usart.yaml
new file mode 100644
index 0000000..8c68fe3
--- /dev/null
+++ b/dts/bindings/spi/silabs,gecko-spi-usart.yaml
@@ -0,0 +1,38 @@
+title: GECKO USART SPI
+
+description: >
+ This binding gives a base representation of the Gecko USART SPI
+
+compatible: "silabs,gecko-spi-usart"
+
+include: spi-controller.yaml
+
+properties:
+ reg:
+ required: true
+
+ interrupts:
+ required: true
+
+ peripheral-id:
+ type: int
+ required: true
+ description: peripheral ID
+
+ # Note: Not all SoC series support setting individual pin location. If this
+ # is a case all location-* properties need to have identical value.
+
+ location-rx:
+ type: array
+ required: true
+ description: RX pin configuration defined as <location port pin>
+
+ location-tx:
+ type: array
+ required: true
+ description: TX pin configuration defined as <location port pin>
+
+ location-clk:
+ type: array
+ required: true
+ description: CLK pin configuration defined as <location port pin>