diff --git a/drivers/spi/CMakeLists.txt b/drivers/spi/CMakeLists.txt index 6d4119b5c8..248462701b 100644 --- a/drivers/spi/CMakeLists.txt +++ b/drivers/spi/CMakeLists.txt @@ -23,5 +23,6 @@ zephyr_library_sources_ifdef(CONFIG_SPI_GECKO spi_gecko.c) zephyr_library_sources_ifdef(CONFIG_SPI_XLNX_AXI_QUADSPI spi_xlnx_axi_quadspi.c) zephyr_library_sources_ifdef(CONFIG_ESP32_SPIM spi_esp32_spim.c) zephyr_library_sources_ifdef(CONFIG_SPI_TEST spi_test.c) +zephyr_library_sources_ifdef(CONFIG_SPI_PSOC6 spi_psoc6.c) zephyr_library_sources_ifdef(CONFIG_USERSPACE spi_handlers.c) diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 8bc4a079f6..e069b47192 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -73,4 +73,6 @@ source "drivers/spi/Kconfig.esp32" source "drivers/spi/Kconfig.test" +source "drivers/spi/Kconfig.psoc6" + endif # SPI diff --git a/drivers/spi/Kconfig.psoc6 b/drivers/spi/Kconfig.psoc6 new file mode 100644 index 0000000000..6b2adce59b --- /dev/null +++ b/drivers/spi/Kconfig.psoc6 @@ -0,0 +1,10 @@ +# Cypress SCB[SPI] configuration + +# Copyright (c) 2021 ATL Electronics +# SPDX-License-Identifier: Apache-2.0 + +config SPI_PSOC6 + bool "PSoC-6 MCU SCB spi driver" + depends on SOC_FAMILY_PSOC6 + help + This option enables the SCB[SPI] driver for PSoC-6 SoC family. diff --git a/drivers/spi/spi_psoc6.c b/drivers/spi/spi_psoc6.c new file mode 100644 index 0000000000..5f4d4b2c7c --- /dev/null +++ b/drivers/spi/spi_psoc6.c @@ -0,0 +1,421 @@ +/* + * Copyright (c) 2016, Freescale Semiconductor, Inc. + * Copyright (c) 2017, NXP + * Copyright (c) 2021, ATL Electronics. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT cypress_psoc6_spi + +#define LOG_LEVEL CONFIG_SPI_LOG_LEVEL +#include +LOG_MODULE_REGISTER(spi_psoc6); + +#include +#include +#include + +#include "spi_context.h" + +#include "cy_syslib.h" +#include "cy_sysclk.h" +#include "cy_scb_spi.h" +#include "cy_sysint.h" + +#define SPI_CHIP_SELECT_COUNT 4 +#define SPI_MAX_DATA_WIDTH 16 +#define SPI_PSOC6_CLK_DIV_NUMBER 1 + +struct spi_psoc6_config { + CySCB_Type *base; + uint32_t periph_id; + void (*irq_config_func)(const struct device *dev); + uint32_t num_pins; + struct soc_gpio_pin pins[]; +}; + +struct spi_psoc6_transfer { + uint8_t *txData; + uint8_t *rxData; + size_t dataSize; +}; + +struct spi_psoc6_data { + struct spi_context ctx; + struct cy_stc_scb_spi_config cfg; + struct spi_psoc6_transfer xfer; +}; + +static void spi_psoc6_transfer_next_packet(const struct device *dev) +{ + const struct spi_psoc6_config *config = dev->config; + struct spi_psoc6_data *data = dev->data; + struct spi_context *ctx = &data->ctx; + struct spi_psoc6_transfer *xfer = &data->xfer; + uint32_t count; + + LOG_DBG("TX L: %d, RX L: %d", ctx->tx_len, ctx->rx_len); + + if ((ctx->tx_len == 0U) && (ctx->rx_len == 0U)) { + /* nothing left to rx or tx, we're done! */ + xfer->dataSize = 0U; + + spi_context_cs_control(ctx, false); + spi_context_complete(ctx, 0U); + return; + } + + if (ctx->tx_len == 0U) { + /* rx only, nothing to tx */ + xfer->txData = NULL; + xfer->rxData = ctx->rx_buf; + xfer->dataSize = ctx->rx_len; + } else if (ctx->rx_len == 0U) { + /* tx only, nothing to rx */ + xfer->txData = (uint8_t *) ctx->tx_buf; + xfer->rxData = NULL; + xfer->dataSize = ctx->tx_len; + } else if (ctx->tx_len == ctx->rx_len) { + /* rx and tx are the same length */ + xfer->txData = (uint8_t *) ctx->tx_buf; + xfer->rxData = ctx->rx_buf; + xfer->dataSize = ctx->tx_len; + } else if (ctx->tx_len > ctx->rx_len) { + /* Break up the tx into multiple transfers so we don't have to + * rx into a longer intermediate buffer. Leave chip select + * active between transfers. + */ + xfer->txData = (uint8_t *) ctx->tx_buf; + xfer->rxData = ctx->rx_buf; + xfer->dataSize = ctx->rx_len; + } else { + /* Break up the rx into multiple transfers so we don't have to + * tx from a longer intermediate buffer. Leave chip select + * active between transfers. + */ + xfer->txData = (uint8_t *) ctx->tx_buf; + xfer->rxData = ctx->rx_buf; + xfer->dataSize = ctx->tx_len; + } + + if (xfer->txData != NULL) { + if (Cy_SCB_SPI_WriteArray(config->base, xfer->txData, + xfer->dataSize) != xfer->dataSize) { + goto err; + } + } else { + /* Need fill TX fifo with garbage to perform read. + * This keeps logic simple and saves stack. + * Use 0 as dummy data. + */ + for (count = 0U; count < xfer->dataSize; count++) { + if (Cy_SCB_SPI_Write(config->base, 0U) == 0U) { + goto err; + } + } + } + + LOG_DBG("TRX L: %d", xfer->dataSize); + + return; +err: + /* no FIFO available to run the transfer */ + xfer->dataSize = 0U; + + spi_context_cs_control(ctx, false); + spi_context_complete(ctx, -ENOMEM); +} + +static void spi_psoc6_isr(const struct device *dev) +{ + const struct spi_psoc6_config *config = dev->config; + struct spi_psoc6_data *data = dev->data; + + Cy_SCB_ClearMasterInterrupt(config->base, + CY_SCB_MASTER_INTR_SPI_DONE); + + /* extract data from RX FIFO */ + if (data->xfer.rxData != NULL) { + Cy_SCB_SPI_ReadArray(config->base, + data->xfer.rxData, + data->xfer.dataSize); + } else { + Cy_SCB_ClearRxFifo(config->base); + } + + /* Set next data block */ + spi_context_update_tx(&data->ctx, 1, data->xfer.dataSize); + spi_context_update_rx(&data->ctx, 1, data->xfer.dataSize); + + /* Start next block + * Since 1 byte at TX FIFO will start transfer data, let's try + * minimize ISR recursion disabling all interrupt sources when add + * data on TX FIFO + */ + Cy_SCB_SetMasterInterruptMask(config->base, 0U); + + spi_psoc6_transfer_next_packet(dev); + + if (data->xfer.dataSize > 0U) { + Cy_SCB_SetMasterInterruptMask(config->base, + CY_SCB_MASTER_INTR_SPI_DONE); + } +} + +static uint32_t spi_psoc6_get_freqdiv(uint32_t frequency) +{ + uint32_t oversample; + uint32_t bus_freq = 100000000UL; + /* + * TODO: Get PerBusSpeed when clocks are available to PSoC-6. + * Currently the bus freq is fixed to 50Mhz and max SPI clk can be + * 12.5MHz. + */ + + for (oversample = 4; oversample < 16; oversample++) { + if ((bus_freq / oversample) <= frequency) { + break; + } + } + + /* Oversample [4, 16] */ + return oversample; +} + +static void spi_psoc6_master_get_defaults(struct cy_stc_scb_spi_config *cfg) +{ + cfg->spiMode = CY_SCB_SPI_MASTER; + cfg->subMode = CY_SCB_SPI_MOTOROLA; + cfg->sclkMode = 0U; + cfg->oversample = 0U; + cfg->rxDataWidth = 0U; + cfg->txDataWidth = 0U; + cfg->enableMsbFirst = false; + cfg->enableFreeRunSclk = false; + cfg->enableInputFilter = false; + cfg->enableMisoLateSample = false; + cfg->enableTransferSeperation = false; + cfg->ssPolarity = 0U; + cfg->enableWakeFromSleep = false; + cfg->rxFifoTriggerLevel = 0U; + cfg->rxFifoIntEnableMask = 0U; + cfg->txFifoTriggerLevel = 0U; + cfg->txFifoIntEnableMask = 0U; + cfg->masterSlaveIntEnableMask = 0U; +} + +static int spi_psoc6_configure(const struct device *dev, + const struct spi_config *spi_cfg) +{ + struct spi_psoc6_data *data = dev->data; + uint32_t word_size; + + if (spi_context_configured(&data->ctx, spi_cfg)) { + /* This configuration is already in use */ + return 0; + } + + word_size = SPI_WORD_SIZE_GET(spi_cfg->operation); + if (word_size > SPI_MAX_DATA_WIDTH) { + LOG_ERR("Word size %d is greater than %d", + word_size, SPI_MAX_DATA_WIDTH); + return -EINVAL; + } + + if (SPI_OP_MODE_GET(spi_cfg->operation) == SPI_OP_MODE_MASTER) { + spi_psoc6_master_get_defaults(&data->cfg); + + if (spi_cfg->slave > SPI_CHIP_SELECT_COUNT) { + LOG_ERR("Slave %d is greater than %d", + spi_cfg->slave, SPI_CHIP_SELECT_COUNT); + return -EINVAL; + } + + data->cfg.rxDataWidth = data->cfg.txDataWidth = word_size; + + if (SPI_MODE_GET(spi_cfg->operation) & SPI_MODE_CPHA) { + if (SPI_MODE_GET(spi_cfg->operation) & SPI_MODE_CPOL) { + data->cfg.sclkMode = CY_SCB_SPI_CPHA1_CPOL1; + } else { + data->cfg.sclkMode = CY_SCB_SPI_CPHA1_CPOL0; + } + } else { + if (SPI_MODE_GET(spi_cfg->operation) & SPI_MODE_CPOL) { + data->cfg.sclkMode = CY_SCB_SPI_CPHA0_CPOL1; + } else { + data->cfg.sclkMode = CY_SCB_SPI_CPHA0_CPOL0; + } + } + + data->cfg.enableMsbFirst = !!!(spi_cfg->operation & + SPI_TRANSFER_LSB); + data->cfg.oversample = spi_psoc6_get_freqdiv(spi_cfg->frequency); + + data->ctx.config = spi_cfg; + spi_context_cs_configure(&data->ctx); + } else { + /* Slave mode is not implemented yet. */ + return -ENOTSUP; + } + + return 0; +} + +static void spi_psoc6_transceive_sync_loop(const struct device *dev) +{ + const struct spi_psoc6_config *config = dev->config; + struct spi_psoc6_data *data = dev->data; + + while (data->xfer.dataSize > 0U) { + while (!Cy_SCB_IsTxComplete(config->base)) { + ; + } + + if (data->xfer.rxData != NULL) { + Cy_SCB_SPI_ReadArray(config->base, + data->xfer.rxData, + data->xfer.dataSize); + } else { + Cy_SCB_ClearRxFifo(config->base); + } + + spi_context_update_tx(&data->ctx, 1, data->xfer.dataSize); + spi_context_update_rx(&data->ctx, 1, data->xfer.dataSize); + + spi_psoc6_transfer_next_packet(dev); + } +} + +static int spi_psoc6_transceive(const struct device *dev, + const struct spi_config *spi_cfg, + const struct spi_buf_set *tx_bufs, + const struct spi_buf_set *rx_bufs, + bool asynchronous, + struct k_poll_signal *signal) +{ + const struct spi_psoc6_config *config = dev->config; + struct spi_psoc6_data *data = dev->data; + int ret; + + spi_context_lock(&data->ctx, asynchronous, signal, spi_cfg); + + LOG_DBG("\n\n"); + + ret = spi_psoc6_configure(dev, spi_cfg); + if (ret) { + goto out; + } + + Cy_SCB_SPI_Init(config->base, &data->cfg, NULL); + Cy_SCB_SPI_SetActiveSlaveSelect(config->base, spi_cfg->slave); + Cy_SCB_SPI_Enable(config->base); + + spi_context_buffers_setup(&data->ctx, tx_bufs, rx_bufs, 1); + + spi_context_cs_control(&data->ctx, true); + + spi_psoc6_transfer_next_packet(dev); + + if (asynchronous) { + Cy_SCB_SetMasterInterruptMask(config->base, + CY_SCB_MASTER_INTR_SPI_DONE); + } else { + spi_psoc6_transceive_sync_loop(dev); + } + + ret = spi_context_wait_for_completion(&data->ctx); + + Cy_SCB_SPI_Disable(config->base, NULL); + +out: + spi_context_release(&data->ctx, ret); + + return ret; +} + +static int spi_psoc6_transceive_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) +{ + return spi_psoc6_transceive(dev, spi_cfg, tx_bufs, + rx_bufs, false, NULL); +} + +#ifdef CONFIG_SPI_ASYNC +static int spi_psoc6_transceive_async(const struct device *dev, + const struct spi_config *spi_cfg, + const struct spi_buf_set *tx_bufs, + const struct spi_buf_set *rx_bufs, + struct k_poll_signal *async) +{ + return spi_psoc6_transceive(dev, spi_cfg, tx_bufs, + rx_bufs, true, async); +} +#endif /* CONFIG_SPI_ASYNC */ + +static int spi_psoc6_release(const struct device *dev, + const struct spi_config *config) +{ + struct spi_psoc6_data *data = dev->data; + + spi_context_unlock_unconditionally(&data->ctx); + + return 0; +} + +static int spi_psoc6_init(const struct device *dev) +{ + const struct spi_psoc6_config *config = dev->config; + + soc_gpio_list_configure(config->pins, config->num_pins); + + Cy_SysClk_PeriphAssignDivider(config->periph_id, + CY_SYSCLK_DIV_8_BIT, + SPI_PSOC6_CLK_DIV_NUMBER); + Cy_SysClk_PeriphSetDivider(CY_SYSCLK_DIV_8_BIT, + SPI_PSOC6_CLK_DIV_NUMBER, 0U); + Cy_SysClk_PeriphEnableDivider(CY_SYSCLK_DIV_8_BIT, + SPI_PSOC6_CLK_DIV_NUMBER); + +#ifdef CONFIG_SPI_ASYNC + config->irq_config_func(dev); +#endif + + return spi_psoc6_release(dev, NULL); +} + +static const struct spi_driver_api spi_psoc6_driver_api = { + .transceive = spi_psoc6_transceive_sync, +#ifdef CONFIG_SPI_ASYNC + .transceive_async = spi_psoc6_transceive_async, +#endif + .release = spi_psoc6_release, +}; + +#define SPI_PSOC6_DEVICE_INIT(n) \ + static void spi_psoc6_spi##n##_irq_cfg(const struct device *port); \ + static const struct spi_psoc6_config spi_psoc6_config_##n = { \ + .base = (CySCB_Type *)DT_INST_REG_ADDR(n), \ + .periph_id = DT_INST_PROP(n, peripheral_id), \ + .num_pins = CY_PSOC6_DT_INST_NUM_PINS(n), \ + .pins = CY_PSOC6_DT_INST_PINS(n), \ + .irq_config_func = spi_psoc6_spi##n##_irq_cfg, \ + }; \ + static struct spi_psoc6_data spi_psoc6_dev_data_##n = { \ + SPI_CONTEXT_INIT_LOCK(spi_psoc6_dev_data_##n, ctx), \ + SPI_CONTEXT_INIT_SYNC(spi_psoc6_dev_data_##n, ctx), \ + }; \ + DEVICE_DT_INST_DEFINE(n, &spi_psoc6_init, NULL, \ + &spi_psoc6_dev_data_##n, \ + &spi_psoc6_config_##n, POST_KERNEL, \ + CONFIG_SPI_INIT_PRIORITY, \ + &spi_psoc6_driver_api); \ + static void spi_psoc6_spi##n##_irq_cfg(const struct device *port) \ + { \ + CY_PSOC6_DT_INST_NVIC_INSTALL(n, \ + spi_psoc6_isr); \ + }; + +DT_INST_FOREACH_STATUS_OKAY(SPI_PSOC6_DEVICE_INIT) diff --git a/dts/arm/cypress/psoc6.dtsi b/dts/arm/cypress/psoc6.dtsi index f6209d20e2..1c1b08d079 100644 --- a/dts/arm/cypress/psoc6.dtsi +++ b/dts/arm/cypress/psoc6.dtsi @@ -248,6 +248,97 @@ }; }; + spi0: spi@40610000 { + compatible = "cypress,psoc6-spi"; + reg = <0x40610000 0x10000>; + interrupts = <41 7>; + peripheral-id = <0>; + status = "disabled"; + label = "spi_0"; + #address-cells = <1>; + #size-cells = <0>; + }; + spi1: spi@40620000 { + compatible = "cypress,psoc6-spi"; + reg = <0x40620000 0x10000>; + interrupts = <42 7>; + peripheral-id = <1>; + status = "disabled"; + label = "spi_1"; + #address-cells = <1>; + #size-cells = <0>; + }; + spi2: spi@40630000 { + compatible = "cypress,psoc6-spi"; + reg = <0x40630000 0x10000>; + interrupts = <43 7>; + peripheral-id = <2>; + status = "disabled"; + label = "spi_2"; + #address-cells = <1>; + #size-cells = <0>; + }; + spi3: spi@40640000 { + compatible = "cypress,psoc6-spi"; + reg = <0x40640000 0x10000>; + interrupts = <44 7>; + peripheral-id = <3>; + status = "disabled"; + label = "spi_3"; + #address-cells = <1>; + #size-cells = <0>; + }; + spi4: spi@40650000 { + compatible = "cypress,psoc6-spi"; + reg = <0x40650000 0x10000>; + interrupts = <45 7>; + peripheral-id = <4>; + status = "disabled"; + label = "spi_4"; + #address-cells = <1>; + #size-cells = <0>; + }; + spi5: spi@40660000 { + compatible = "cypress,psoc6-spi"; + reg = <0x40660000 0x10000>; + interrupts = <46 7>; + peripheral-id = <5>; + status = "disabled"; + label = "spi_5"; + #address-cells = <1>; + #size-cells = <0>; + }; + spi6: spi@40670000 { + compatible = "cypress,psoc6-spi"; + reg = <0x40670000 0x10000>; + interrupts = <47 7>; + peripheral-id = <6>; + status = "disabled"; + label = "spi_6"; + #address-cells = <1>; + #size-cells = <0>; + }; + spi7: spi@40680000 { + compatible = "cypress,psoc6-spi"; + reg = <0x40680000 0x10000>; + interrupts = <48 7>; + peripheral-id = <7>; + status = "disabled"; + label = "spi_7"; + #address-cells = <1>; + #size-cells = <0>; + }; + spi8: spi@40690000 { + compatible = "cypress,psoc6-spi"; + reg = <0x40690000 0x10000>; + interrupts = <18 7>; + peripheral-id = <8>; + status = "disabled"; + label = "spi_8"; + #address-cells = <1>; + #size-cells = <0>; + }; + uart0: uart@40610000 { compatible = "cypress,psoc6-uart"; reg = <0x40610000 0x10000>; diff --git a/soc/arm/cypress/psoc6/Kconfig.defconfig.series b/soc/arm/cypress/psoc6/Kconfig.defconfig.series index 29107566aa..93483d1e82 100644 --- a/soc/arm/cypress/psoc6/Kconfig.defconfig.series +++ b/soc/arm/cypress/psoc6/Kconfig.defconfig.series @@ -21,6 +21,10 @@ config SYS_CLOCK_HW_CYCLES_PER_SEC source "soc/arm/cypress/psoc6/Kconfig.defconfig.psoc*" +config SPI_PSOC6 + default y + depends on SPI + config UART_PSOC6 default y depends on SERIAL