/* * 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 = 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)