| /* |
| * 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 <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(cat1_spi); |
| |
| #include "spi_context.h" |
| |
| #include <zephyr/drivers/pinctrl.h> |
| #include <zephyr/drivers/spi.h> |
| #include <zephyr/drivers/spi/rtio.h> |
| #include <zephyr/kernel.h> |
| |
| #include <cyhal_scb_common.h> |
| #include <cyhal_spi.h> |
| |
| #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 |
| #ifdef CONFIG_SPI_RTIO |
| .iodev_submit = spi_rtio_iodev_default_submit, |
| #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 = CY_SCB_SPI_MOTOROLA, \ |
| .oversample = IFX_CAT1_SPI_DEFAULT_OVERSAMPLE, \ |
| .enableMisoLateSample = true, \ |
| .ssPolarity = CY_SCB_SPI_ACTIVE_LOW, \ |
| }, \ |
| .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) |