diff --git a/boards/arm/cy8cproto_063_ble/cy8cproto_063_ble.yaml b/boards/arm/cy8cproto_063_ble/cy8cproto_063_ble.yaml index ba6ceaa0f06..c9d851c5ffc 100644 --- a/boards/arm/cy8cproto_063_ble/cy8cproto_063_ble.yaml +++ b/boards/arm/cy8cproto_063_ble/cy8cproto_063_ble.yaml @@ -18,3 +18,4 @@ supported: - uart - i2c - watchdog + - spi diff --git a/boards/arm/cy8cproto_063_ble/cy8cproto_063_ble_defconfig b/boards/arm/cy8cproto_063_ble/cy8cproto_063_ble_defconfig index bc356fad3b4..a3d7e7beab3 100644 --- a/boards/arm/cy8cproto_063_ble/cy8cproto_063_ble_defconfig +++ b/boards/arm/cy8cproto_063_ble/cy8cproto_063_ble_defconfig @@ -25,6 +25,9 @@ CONFIG_SERIAL=y # Enable pin controller CONFIG_PINCTRL=y +# Enable GPIO +CONFIG_GPIO=y + # Enable clock controller CONFIG_CLOCK_CONTROL=y diff --git a/drivers/spi/CMakeLists.txt b/drivers/spi/CMakeLists.txt index 0eaea3714b8..269b28549eb 100644 --- a/drivers/spi/CMakeLists.txt +++ b/drivers/spi/CMakeLists.txt @@ -48,3 +48,4 @@ zephyr_library_sources_ifdef(CONFIG_MSPI_AMBIQ mspi_ambiq.c) zephyr_library_sources_ifdef(CONFIG_SPI_RTIO spi_rtio.c) zephyr_library_sources_ifdef(CONFIG_SPI_ASYNC spi_signal.c) zephyr_library_sources_ifdef(CONFIG_USERSPACE spi_handlers.c) +zephyr_library_sources_ifdef(CONFIG_SPI_INFINEON_CAT1 spi_ifx_cat1.c) diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index cbc11b374ab..75f7993ef27 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -133,4 +133,6 @@ source "drivers/spi/Kconfig.ambiq" source "drivers/spi/Kconfig.rpi_pico" +source "drivers/spi/Kconfig.ifx_cat1" + endif # SPI diff --git a/drivers/spi/Kconfig.ifx_cat1 b/drivers/spi/Kconfig.ifx_cat1 new file mode 100644 index 00000000000..ab55fb6d67b --- /dev/null +++ b/drivers/spi/Kconfig.ifx_cat1 @@ -0,0 +1,12 @@ +# Infineon CAT1 SPI configuration options + +# Copyright (c) 2022 Cypress Semiconductor Corporation. +# SPDX-License-Identifier: Apache-2.0 + +config SPI_INFINEON_CAT1 + bool "Infineon CAT1 SPI driver" + default y + depends on DT_HAS_INFINEON_CAT1_SPI_ENABLED + select USE_INFINEON_SPI + help + This option enables the SPI driver for Infineon CAT1 family. diff --git a/drivers/spi/spi_ifx_cat1.c b/drivers/spi/spi_ifx_cat1.c new file mode 100644 index 00000000000..ceafe8e6635 --- /dev/null +++ b/drivers/spi/spi_ifx_cat1.c @@ -0,0 +1,360 @@ +/* + * Copyright 2023 Cypress Semiconductor Corporation (an Infineon company) or + * an affiliate of Cypress Semiconductor Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT infineon_cat1_spi + +#define LOG_LEVEL CONFIG_SPI_LOG_LEVEL +#include +LOG_MODULE_REGISTER(cat1_spi); + +#include "spi_context.h" + +#include +#include +#include + +#include +#include + +#define IFX_CAT1_SPI_LOCK_TMOUT_MS (30 * 1000) +#define IFX_CAT1_SPI_DEFAULT_OVERSAMPLE (4) +#define IFX_CAT1_SPI_MIN_DATA_WIDTH (8) +#define IFX_CAT1_SPI_MAX_DATA_WIDTH (32) + +/* Device config structure */ +struct ifx_cat1_spi_config { + CySCB_Type *reg_addr; + const struct pinctrl_dev_config *pcfg; + cy_stc_scb_spi_config_t scb_spi_config; + uint8_t irq_priority; +}; + +/* Data structure */ +struct ifx_cat1_spi_data { + struct spi_context ctx; + cyhal_spi_t obj; /* SPI CYHAL object */ + cyhal_resource_inst_t hw_resource; + uint8_t dfs_value; + size_t chunk_len; +}; + +static int32_t get_hw_block_num(CySCB_Type *reg_addr) +{ + uint32_t i; + + for (i = 0u; i < _SCB_ARRAY_SIZE; i++) { + if (_CYHAL_SCB_BASE_ADDRESSES[i] == reg_addr) { + return i; + } + } + + return -ENOMEM; +} + +static uint8_t get_dfs_value(struct spi_context *ctx) +{ + switch (SPI_WORD_SIZE_GET(ctx->config->operation)) { + case 8: + return 1; + case 16: + return 2; + case 32: + return 4; + default: + return 1; + } +} + +static void transfer_chunk(const struct device *dev) +{ + struct ifx_cat1_spi_data *const data = dev->data; + struct spi_context *ctx = &data->ctx; + int ret = 0; + size_t chunk_len = spi_context_max_continuous_chunk(ctx); + + if (chunk_len == 0) { + goto exit; + } + + data->chunk_len = chunk_len; + + cy_rslt_t result = cyhal_spi_transfer_async( + &data->obj, ctx->tx_buf, spi_context_tx_buf_on(ctx) ? chunk_len : 0, ctx->rx_buf, + spi_context_rx_buf_on(ctx) ? chunk_len : 0); + if (result == CY_RSLT_SUCCESS) { + return; + } + + ret = -EIO; + +exit: + spi_context_cs_control(ctx, false); + spi_context_complete(ctx, dev, ret); +} + +static void spi_interrupt_callback(void *arg, cyhal_spi_event_t event) +{ + const struct device *dev = (const struct device *)arg; + struct ifx_cat1_spi_data *const data = dev->data; + struct spi_context *ctx = &data->ctx; + + if (event & CYHAL_SPI_IRQ_ERROR) { +#if defined(CONFIG_SPI_ASYNC) + cyhal_spi_abort_async(&data->obj); +#endif + spi_context_cs_control(ctx, false); + spi_context_complete(ctx, dev, -EIO); + } + + if (event & CYHAL_SPI_IRQ_DONE) { + spi_context_update_tx(ctx, data->dfs_value, data->chunk_len); + spi_context_update_rx(ctx, data->dfs_value, data->chunk_len); + + transfer_chunk(dev); + } +} + +int spi_config(const struct device *dev, const struct spi_config *spi_cfg) +{ + cy_rslt_t result; + struct ifx_cat1_spi_data *const data = dev->data; + const struct ifx_cat1_spi_config *const config = dev->config; + cy_stc_scb_spi_config_t scb_spi_config = config->scb_spi_config; + struct spi_context *ctx = &data->ctx; + bool spi_mode_cpol = false; + bool spi_mode_cpha = false; + + /* check if configuration was changed from previous run, if so skip setup again */ + if (spi_context_configured(ctx, spi_cfg)) { + /* Already configured. No need to do it again. */ + return 0; + } + + if (SPI_MODE_GET(spi_cfg->operation) & SPI_MODE_LOOP) { + return -ENOTSUP; + } + + if (SPI_WORD_SIZE_GET(spi_cfg->operation) > IFX_CAT1_SPI_MAX_DATA_WIDTH) { + LOG_ERR("Word size %d is greater than %d", SPI_WORD_SIZE_GET(spi_cfg->operation), + IFX_CAT1_SPI_MAX_DATA_WIDTH); + return -EINVAL; + } + + if (SPI_WORD_SIZE_GET(spi_cfg->operation) < IFX_CAT1_SPI_MIN_DATA_WIDTH) { + LOG_ERR("Word size %d is less than %d", SPI_WORD_SIZE_GET(spi_cfg->operation), + IFX_CAT1_SPI_MIN_DATA_WIDTH); + return -EINVAL; + } + + if (SPI_OP_MODE_GET(spi_cfg->operation) == SPI_OP_MODE_SLAVE) { + scb_spi_config.spiMode = CY_SCB_SPI_SLAVE; + scb_spi_config.oversample = 0; + scb_spi_config.enableMisoLateSample = false; + } else { + scb_spi_config.spiMode = CY_SCB_SPI_MASTER; + } + + if (SPI_MODE_GET(spi_cfg->operation) & SPI_MODE_CPOL) { + spi_mode_cpol = true; + } + + if (SPI_MODE_GET(spi_cfg->operation) & SPI_MODE_CPHA) { + spi_mode_cpha = true; + } + + if (SPI_WORD_SIZE_GET(spi_cfg->operation)) { + scb_spi_config.txDataWidth = SPI_WORD_SIZE_GET(spi_cfg->operation); + scb_spi_config.rxDataWidth = SPI_WORD_SIZE_GET(spi_cfg->operation); + } + + if (spi_mode_cpha) { + scb_spi_config.sclkMode = + spi_mode_cpol ? CY_SCB_SPI_CPHA1_CPOL1 : CY_SCB_SPI_CPHA1_CPOL0; + } else { + scb_spi_config.sclkMode = + spi_mode_cpol ? CY_SCB_SPI_CPHA0_CPOL1 : CY_SCB_SPI_CPHA0_CPOL0; + } + + scb_spi_config.enableMsbFirst = (spi_cfg->operation & SPI_TRANSFER_LSB) ? false : true; + + /* Force free resource */ + if (data->obj.base != NULL) { + cyhal_spi_free(&data->obj); + } + + /* Initialize the SPI peripheral */ + cyhal_spi_configurator_t spi_init_cfg = {.resource = &data->hw_resource, + .config = &scb_spi_config, + .gpios = {NC, {NC, NC, NC, NC}, NC, NC}}; + + result = cyhal_spi_init_cfg(&data->obj, &spi_init_cfg); + if (result != CY_RSLT_SUCCESS) { + return -ENOTSUP; + } + + /* Assigns a programmable divider to a selected IP block */ + en_clk_dst_t clk_idx = _cyhal_scb_get_clock_index(spi_init_cfg.resource->block_num); + + result = _cyhal_utils_peri_pclk_assign_divider(clk_idx, &data->obj.clock); + if (result != CY_RSLT_SUCCESS) { + return -ENOTSUP; + } + + /* Configure Slave select polarity */ + if (SPI_OP_MODE_GET(spi_cfg->operation) == SPI_OP_MODE_SLAVE) { + Cy_SCB_SPI_SetActiveSlaveSelectPolarity(data->obj.base, CY_SCB_SPI_SLAVE_SELECT0, + scb_spi_config.ssPolarity); + } + + /* Set the data rate */ + result = cyhal_spi_set_frequency(&data->obj, spi_cfg->frequency); + if (result != CY_RSLT_SUCCESS) { + return -EIO; + } + + /* Write 0 when NULL buffer is provided for Tx/Rx */ + data->obj.write_fill = 0; + + /* Register common SPI callback */ + cyhal_spi_register_callback(&data->obj, spi_interrupt_callback, (void *)dev); + cyhal_spi_enable_event(&data->obj, CYHAL_SPI_IRQ_DONE, config->irq_priority, true); + + /* Store spi config in context */ + ctx->config = spi_cfg; + + data->dfs_value = get_dfs_value(ctx); + + return 0; +} + +static int 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, spi_callback_t cb, void *userdata) +{ + int result; + struct ifx_cat1_spi_data *const data = dev->data; + struct spi_context *ctx = &data->ctx; + + spi_context_lock(ctx, asynchronous, cb, userdata, spi_cfg); + + result = spi_config(dev, spi_cfg); + if (result) { + LOG_ERR("Error in SPI Configuration (result: 0x%x)", result); + return result; + } + + spi_context_buffers_setup(ctx, tx_bufs, rx_bufs, data->dfs_value); + + spi_context_cs_control(ctx, true); + + transfer_chunk(dev); + + result = spi_context_wait_for_completion(&data->ctx); + + spi_context_release(ctx, result); + + return result; +} + +static int ifx_cat1_spi_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 transceive(dev, spi_cfg, tx_bufs, rx_bufs, false, NULL, NULL); +} + +#if defined(CONFIG_SPI_ASYNC) +static int ifx_cat1_spi_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, spi_callback_t cb, + void *userdata) +{ + return transceive(dev, spi_cfg, tx_bufs, rx_bufs, true, cb, userdata); +} +#endif + +static int ifx_cat1_spi_release(const struct device *dev, const struct spi_config *spi_cfg) +{ + struct ifx_cat1_spi_data *const data = dev->data; + + cyhal_spi_free(&data->obj); + + return 0; +} + +static const struct spi_driver_api ifx_cat1_spi_api = { + .transceive = ifx_cat1_spi_transceive_sync, +#if defined(CONFIG_SPI_ASYNC) + .transceive_async = ifx_cat1_spi_transceive_async, +#endif + .release = ifx_cat1_spi_release, +}; + +static int ifx_cat1_spi_init(const struct device *dev) +{ + struct ifx_cat1_spi_data *const data = dev->data; + const struct ifx_cat1_spi_config *const config = dev->config; + int ret; + + /* Dedicate SCB HW resource */ + data->hw_resource.type = CYHAL_RSC_SCB; + data->hw_resource.block_num = get_hw_block_num(config->reg_addr); + + /* Configure dt provided device signals when available */ + ret = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT); + if (ret < 0) { + return ret; + } + + /* Configure slave select (master) */ + spi_context_cs_configure_all(&data->ctx); + + spi_context_unlock_unconditionally(&data->ctx); + + return 0; +} + +#define IFX_CAT1_SPI_INIT(n) \ + PINCTRL_DT_INST_DEFINE(n); \ + static struct ifx_cat1_spi_data spi_cat1_data_##n = { \ + SPI_CONTEXT_INIT_LOCK(spi_cat1_data_##n, ctx), \ + SPI_CONTEXT_INIT_SYNC(spi_cat1_data_##n, ctx), \ + SPI_CONTEXT_CS_GPIOS_INITIALIZE(DT_DRV_INST(n), ctx)}; \ + static struct ifx_cat1_spi_config spi_cat1_config_##n = { \ + .reg_addr = (CySCB_Type *)DT_INST_REG_ADDR(n), \ + .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \ + .scb_spi_config = \ + {.spiMode = CY_SCB_SPI_MASTER, /* overwrite by cfg */ \ + .sclkMode = CY_SCB_SPI_CPHA0_CPOL0, /* overwrite by cfg */ \ + .rxDataWidth = 8, /* overwrite by cfg */ \ + .txDataWidth = 8, /* overwrite by cfg */ \ + .enableMsbFirst = true, /* overwrite by cfg */ \ + .subMode = DT_INST_PROP_OR(n, sub_mode, CY_SCB_SPI_MOTOROLA), \ + .oversample = \ + DT_INST_PROP_OR(n, oversample, IFX_CAT1_SPI_DEFAULT_OVERSAMPLE), \ + .enableFreeRunSclk = DT_INST_PROP_OR(n, enable_free_run_sclk, false), \ + .enableInputFilter = DT_INST_PROP_OR(n, enable_input_filter, false), \ + .enableMisoLateSample = \ + DT_INST_PROP_OR(n, enable_miso_late_sample, true), \ + .enableTransferSeperation = \ + DT_INST_PROP_OR(n, enable_transfer_seperation, false), \ + .enableWakeFromSleep = DT_INST_PROP_OR(n, enableWakeFromSleep, false), \ + .ssPolarity = DT_INST_PROP_OR(n, ss_polarity, CY_SCB_SPI_ACTIVE_LOW), \ + .rxFifoTriggerLevel = DT_INST_PROP_OR(n, rx_fifo_trigger_level, 0), \ + .rxFifoIntEnableMask = DT_INST_PROP_OR(n, rx_fifo_int_enable_mask, 0), \ + .txFifoTriggerLevel = DT_INST_PROP_OR(n, tx_fifo_trigger_level, 0), \ + .txFifoIntEnableMask = DT_INST_PROP_OR(n, tx_fifo_int_enable_mask, 0), \ + .masterSlaveIntEnableMask = \ + DT_INST_PROP_OR(n, master_slave_int_enable_mask, 0)}, \ + \ + .irq_priority = DT_INST_IRQ(n, priority), \ + }; \ + DEVICE_DT_INST_DEFINE(n, &ifx_cat1_spi_init, NULL, &spi_cat1_data_##n, \ + &spi_cat1_config_##n, POST_KERNEL, \ + CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &ifx_cat1_spi_api); + +DT_INST_FOREACH_STATUS_OKAY(IFX_CAT1_SPI_INIT) diff --git a/dts/bindings/spi/infineon,cat1-spi.yaml b/dts/bindings/spi/infineon,cat1-spi.yaml new file mode 100644 index 00000000000..4b1000b3497 --- /dev/null +++ b/dts/bindings/spi/infineon,cat1-spi.yaml @@ -0,0 +1,20 @@ +# Copyright (c) 2022 Cypress Semiconductor Corporation (an Infineon company) or +# an affiliate of Cypress Semiconductor Corporation +# +# SPDX-License-Identifier: Apache-2.0 + +description: Infineon CAT1 SPI + +compatible: "infineon,cat1-spi" + +include: [spi-controller.yaml, pinctrl-device.yaml] + +properties: + reg: + required: true + + interrupts: + required: true + + pinctrl-0: + required: true diff --git a/tests/drivers/spi/spi_loopback/boards/cy8cproto_062_4343w.overlay b/tests/drivers/spi/spi_loopback/boards/cy8cproto_062_4343w.overlay new file mode 100644 index 00000000000..893e8416631 --- /dev/null +++ b/tests/drivers/spi/spi_loopback/boards/cy8cproto_062_4343w.overlay @@ -0,0 +1,41 @@ +spi1: &scb3 { + compatible = "infineon,cat1-spi"; + status = "okay"; + + pinctrl-0 = <&p6_0_scb3_spi_m_mosi &p6_1_scb3_spi_m_miso &p6_2_scb3_spi_m_clk>; + pinctrl-names = "default"; + cs-gpios = <&gpio_prt6 3 GPIO_ACTIVE_LOW>; + + slow@0 { + compatible = "test-spi-loopback-slow"; + reg = <0>; + spi-max-frequency = <2000000>; + }; + fast@0 { + compatible = "test-spi-loopback-fast"; + reg = <0>; + spi-max-frequency = <3000000>; + }; +}; + +&gpio_prt6 { + status = "okay"; +}; + +&pinctrl { + /* Configure pin control bias mode for SPI pins */ + p6_0_scb3_spi_m_mosi: p6_0_scb3_spi_m_mosi { + pinmux = ; + drive-push-pull; + }; + + p6_1_scb3_spi_m_miso: p6_1_scb3_spi_m_miso { + pinmux = ; + input-enable; + }; + + p6_2_scb3_spi_m_clk: p6_2_scb3_spi_m_clk { + pinmux = ; + drive-push-pull; + }; +}; diff --git a/tests/drivers/spi/spi_loopback/boards/cy8cproto_063_ble.overlay b/tests/drivers/spi/spi_loopback/boards/cy8cproto_063_ble.overlay new file mode 100644 index 00000000000..e6ddcc3ef2e --- /dev/null +++ b/tests/drivers/spi/spi_loopback/boards/cy8cproto_063_ble.overlay @@ -0,0 +1,42 @@ +&pinctrl { + /* Configure pin control bias mode for SPI pins (MASTER) */ + p10_0_scb1_spi_m_mosi: p10_0_scb1_spi_m_mosi { + pinmux = ; + drive-push-pull; + }; + + p10_1_scb1_spi_m_miso: p10_1_scb1_spi_m_miso { + pinmux = ; + input-enable; + }; + + p10_2_scb1_spi_m_clk: p10_2_scb1_spi_m_clk { + pinmux = ; + drive-push-pull; + }; +}; + +spi: &scb1 { + compatible = "infineon,cat1-spi"; + status = "okay"; + + pinctrl-0 = <&p10_0_scb1_spi_m_mosi &p10_1_scb1_spi_m_miso &p10_2_scb1_spi_m_clk>; + pinctrl-names = "default"; + cs-gpios = <&gpio_prt10 3 GPIO_ACTIVE_LOW>; + + slow@0 { + compatible = "test-spi-loopback-slow"; + reg = <0>; + spi-max-frequency = <200000>; + }; + + fast@0 { + compatible = "test-spi-loopback-fast"; + reg = <0>; + spi-max-frequency = <1500000>; + }; +}; + +&gpio_prt10 { + status = "okay"; +};