| /* |
| * Copyright (c) 2022 Renesas Electronics Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT renesas_smartbond_spi |
| |
| #define LOG_LEVEL CONFIG_SPI_LOG_LEVEL |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(spi_smartbond); |
| |
| #include "spi_context.h" |
| |
| #include <zephyr/drivers/gpio.h> |
| #include <zephyr/drivers/pinctrl.h> |
| #include <zephyr/drivers/spi.h> |
| #include <zephyr/pm/device.h> |
| #include <zephyr/pm/policy.h> |
| #include <zephyr/pm/device_runtime.h> |
| |
| #include <DA1469xAB.h> |
| #include <da1469x_pd.h> |
| |
| #define DIVN_CLK 32000000 /* divN_clk 32MHz */ |
| #define SCLK_FREQ_2MHZ (DIVN_CLK / 14) /* 2.285714MHz*/ |
| #define SCLK_FREQ_4MHZ (DIVN_CLK / 8) /* 4MHz */ |
| #define SCLK_FREQ_8MHZ (DIVN_CLK / 4) /* 8MHz */ |
| #define SCLK_FREQ_16MHZ (DIVN_CLK / 2) /* 16MHz */ |
| |
| struct spi_smartbond_cfg { |
| SPI_Type *regs; |
| int periph_clock_config; |
| const struct pinctrl_dev_config *pcfg; |
| }; |
| |
| struct spi_smartbond_data { |
| struct spi_context ctx; |
| uint8_t dfs; |
| #if defined(CONFIG_PM_DEVICE) |
| ATOMIC_DEFINE(pm_policy_state_flag, 1); |
| uint32_t spi_ctrl_reg; |
| #endif |
| }; |
| |
| static inline void spi_smartbond_enable(const struct spi_smartbond_cfg *cfg, bool enable) |
| { |
| if (enable) { |
| cfg->regs->SPI_CTRL_REG |= SPI_SPI_CTRL_REG_SPI_ON_Msk; |
| cfg->regs->SPI_CTRL_REG &= ~SPI_SPI_CTRL_REG_SPI_RST_Msk; |
| } else { |
| cfg->regs->SPI_CTRL_REG &= ~SPI_SPI_CTRL_REG_SPI_ON_Msk; |
| cfg->regs->SPI_CTRL_REG |= SPI_SPI_CTRL_REG_SPI_RST_Msk; |
| } |
| } |
| |
| static inline bool spi_smartbond_isenabled(const struct spi_smartbond_cfg *cfg) |
| { |
| return (!!(cfg->regs->SPI_CTRL_REG & SPI_SPI_CTRL_REG_SPI_ON_Msk)) && |
| (!(cfg->regs->SPI_CTRL_REG & SPI_SPI_CTRL_REG_SPI_RST_Msk)); |
| } |
| |
| static inline int spi_smartbond_set_speed(const struct spi_smartbond_cfg *cfg, |
| const uint32_t frequency) |
| { |
| if (frequency < SCLK_FREQ_2MHZ) { |
| LOG_ERR("Frequency is lower than minimal SCLK %d", SCLK_FREQ_2MHZ); |
| return -ENOTSUP; |
| } else if (frequency < SCLK_FREQ_4MHZ) { |
| cfg->regs->SPI_CTRL_REG = |
| (cfg->regs->SPI_CTRL_REG & ~SPI_SPI_CTRL_REG_SPI_CLK_Msk) | |
| 3UL << SPI_SPI_CTRL_REG_SPI_CLK_Pos; |
| } else if (frequency < SCLK_FREQ_8MHZ) { |
| cfg->regs->SPI_CTRL_REG = (cfg->regs->SPI_CTRL_REG & ~SPI_SPI_CTRL_REG_SPI_CLK_Msk); |
| } else if (frequency < SCLK_FREQ_16MHZ) { |
| cfg->regs->SPI_CTRL_REG = |
| (cfg->regs->SPI_CTRL_REG & ~SPI_SPI_CTRL_REG_SPI_CLK_Msk) | |
| 1UL << SPI_SPI_CTRL_REG_SPI_CLK_Pos; |
| } else { |
| cfg->regs->SPI_CTRL_REG = |
| (cfg->regs->SPI_CTRL_REG & ~SPI_SPI_CTRL_REG_SPI_CLK_Msk) | |
| 2UL << SPI_SPI_CTRL_REG_SPI_CLK_Pos; |
| } |
| return 0; |
| } |
| |
| static inline int spi_smartbond_set_word_size(const struct spi_smartbond_cfg *cfg, |
| struct spi_smartbond_data *data, |
| const uint32_t operation) |
| { |
| switch (SPI_WORD_SIZE_GET(operation)) { |
| case 8: |
| data->dfs = 1; |
| cfg->regs->SPI_CTRL_REG = |
| (cfg->regs->SPI_CTRL_REG & ~SPI_SPI_CTRL_REG_SPI_WORD_Msk); |
| break; |
| case 16: |
| data->dfs = 2; |
| cfg->regs->SPI_CTRL_REG = |
| (cfg->regs->SPI_CTRL_REG & ~SPI_SPI_CTRL_REG_SPI_WORD_Msk) | |
| (1UL << SPI_SPI_CTRL_REG_SPI_WORD_Pos); |
| break; |
| case 32: |
| data->dfs = 4; |
| cfg->regs->SPI_CTRL_REG = |
| (cfg->regs->SPI_CTRL_REG & ~SPI_SPI_CTRL_REG_SPI_WORD_Msk) | |
| (2UL << SPI_SPI_CTRL_REG_SPI_WORD_Pos); |
| break; |
| default: |
| LOG_ERR("Word size not supported"); |
| return -ENOTSUP; |
| } |
| |
| return 0; |
| } |
| |
| static inline void spi_smartbond_pm_policy_state_lock_get(struct spi_smartbond_data *data) |
| { |
| #if defined(CONFIG_PM_DEVICE) |
| if (atomic_test_and_set_bit(data->pm_policy_state_flag, 0) == 0) { |
| /* |
| * Prevent the SoC from entering the normal sleep state as PDC does not support |
| * waking up the application core following SPI events. |
| */ |
| pm_policy_state_lock_get(PM_STATE_STANDBY, PM_ALL_SUBSTATES); |
| } |
| #endif |
| } |
| |
| static inline void spi_smartbond_pm_policy_state_lock_put(struct spi_smartbond_data *data) |
| { |
| #if defined(CONFIG_PM_DEVICE) |
| if (atomic_test_and_clear_bit(data->pm_policy_state_flag, 0) == 1) { |
| /* |
| * Allow the SoC to enter the normal sleep state once SPI transactions are done. |
| */ |
| pm_policy_state_lock_put(PM_STATE_STANDBY, PM_ALL_SUBSTATES); |
| } |
| #endif |
| } |
| |
| static int spi_smartbond_configure(const struct spi_smartbond_cfg *cfg, |
| struct spi_smartbond_data *data, |
| const struct spi_config *spi_cfg) |
| { |
| int rc; |
| |
| if (spi_context_configured(&data->ctx, spi_cfg)) { |
| #ifdef CONFIG_PM_DEVICE |
| spi_smartbond_enable(cfg, true); |
| #endif |
| return 0; |
| } |
| |
| if (spi_cfg->operation & SPI_OP_MODE_SLAVE) { |
| LOG_ERR("Slave mode not yet supported"); |
| return -ENOTSUP; |
| } |
| |
| if (spi_cfg->operation & SPI_HALF_DUPLEX) { |
| LOG_ERR("Half-duplex not supported"); |
| return -ENOTSUP; |
| } |
| |
| if (IS_ENABLED(CONFIG_SPI_EXTENDED_MODES) && |
| (spi_cfg->operation & SPI_LINES_MASK) != SPI_LINES_SINGLE) { |
| LOG_ERR("Only single line mode is supported"); |
| return -ENOTSUP; |
| } |
| |
| if (spi_cfg->operation & SPI_MODE_LOOP) { |
| LOG_ERR("Loopback mode is not supported"); |
| return -ENOTSUP; |
| } |
| |
| if (spi_smartbond_isenabled(cfg)) { |
| spi_smartbond_enable(cfg, false); |
| } |
| |
| rc = spi_smartbond_set_speed(cfg, spi_cfg->frequency); |
| if (rc) { |
| return rc; |
| } |
| |
| cfg->regs->SPI_CTRL_REG = |
| (spi_cfg->operation & SPI_MODE_CPOL) |
| ? (cfg->regs->SPI_CTRL_REG | SPI_SPI_CTRL_REG_SPI_POL_Msk) |
| : (cfg->regs->SPI_CTRL_REG & ~SPI_SPI_CTRL_REG_SPI_POL_Msk); |
| |
| cfg->regs->SPI_CTRL_REG = |
| (spi_cfg->operation & SPI_MODE_CPHA) |
| ? (cfg->regs->SPI_CTRL_REG | SPI_SPI_CTRL_REG_SPI_PHA_Msk) |
| : (cfg->regs->SPI_CTRL_REG & ~SPI_SPI_CTRL_REG_SPI_PHA_Msk); |
| |
| rc = spi_smartbond_set_word_size(cfg, data, spi_cfg->operation); |
| if (rc) { |
| return rc; |
| } |
| |
| cfg->regs->SPI_CTRL_REG &= ~(SPI_SPI_CTRL_REG_SPI_FIFO_MODE_Msk); |
| |
| spi_smartbond_enable(cfg, true); |
| |
| cfg->regs->SPI_CTRL_REG &= ~SPI_SPI_CTRL_REG_SPI_MINT_Msk; |
| |
| data->ctx.config = spi_cfg; |
| |
| return 0; |
| } |
| |
| static int spi_smartbond_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) |
| { |
| const struct spi_smartbond_cfg *cfg = dev->config; |
| struct spi_smartbond_data *data = dev->data; |
| struct spi_context *ctx = &data->ctx; |
| uint32_t bitmask; |
| int rc; |
| |
| spi_smartbond_pm_policy_state_lock_get(data); |
| |
| spi_context_lock(&data->ctx, false, NULL, NULL, spi_cfg); |
| rc = spi_smartbond_configure(cfg, data, spi_cfg); |
| if (rc == 0) { |
| spi_context_buffers_setup(ctx, tx_bufs, rx_bufs, data->dfs); |
| spi_context_cs_control(ctx, true); |
| |
| bitmask = ~((~0UL) << SPI_WORD_SIZE_GET(data->ctx.config->operation)); |
| while (spi_context_tx_buf_on(ctx) || spi_context_rx_buf_on(ctx)) { |
| if (spi_context_tx_buf_on(ctx)) { |
| cfg->regs->SPI_RX_TX_REG = (*(uint32_t *)ctx->tx_buf) & bitmask; |
| spi_context_update_tx(ctx, data->dfs, 1); |
| } else { |
| cfg->regs->SPI_RX_TX_REG = 0UL; |
| } |
| |
| while (!(cfg->regs->SPI_CTRL_REG & SPI_SPI_CTRL_REG_SPI_INT_BIT_Msk)) { |
| }; |
| if (spi_context_rx_buf_on(ctx)) { |
| (*(uint32_t *)ctx->rx_buf) = cfg->regs->SPI_RX_TX_REG & bitmask; |
| spi_context_update_rx(ctx, data->dfs, 1); |
| } else { |
| (void)cfg->regs->SPI_RX_TX_REG; |
| } |
| cfg->regs->SPI_CLEAR_INT_REG = 1UL; |
| } |
| } |
| spi_context_cs_control(ctx, false); |
| spi_context_release(&data->ctx, rc); |
| |
| spi_smartbond_pm_policy_state_lock_put(data); |
| |
| return rc; |
| } |
| #ifdef CONFIG_SPI_ASYNC |
| static int spi_smartbond_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 -ENOTSUP; |
| } |
| #endif |
| |
| static int spi_smartbond_release(const struct device *dev, const struct spi_config *spi_cfg) |
| { |
| struct spi_smartbond_data *data = dev->data; |
| struct spi_context *ctx = &data->ctx; |
| |
| if (!spi_context_configured(ctx, spi_cfg)) { |
| LOG_ERR("SPI configuration was not the last one to be used"); |
| return -EINVAL; |
| } |
| |
| spi_context_unlock_unconditionally(ctx); |
| |
| return 0; |
| } |
| |
| static const struct spi_driver_api spi_smartbond_driver_api = { |
| .transceive = spi_smartbond_transceive, |
| #ifdef CONFIG_SPI_ASYNC |
| .transceive_async = spi_smartbond_transceive_async, |
| #endif |
| .release = spi_smartbond_release, |
| }; |
| |
| static int spi_smartbond_resume(const struct device *dev) |
| { |
| const struct spi_smartbond_cfg *cfg = dev->config; |
| struct spi_smartbond_data *data = dev->data; |
| int rc; |
| |
| CRG_COM->RESET_CLK_COM_REG = cfg->periph_clock_config << 1; |
| CRG_COM->SET_CLK_COM_REG = cfg->periph_clock_config; |
| |
| rc = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT); |
| if (rc < 0) { |
| LOG_ERR("Failed to configure SPI pins"); |
| return rc; |
| } |
| |
| rc = spi_context_cs_configure_all(&data->ctx); |
| if (rc < 0) { |
| LOG_ERR("Failed to configure CS pins: %d", rc); |
| return rc; |
| } |
| |
| spi_context_unlock_unconditionally(&data->ctx); |
| |
| return 0; |
| } |
| |
| #if defined(CONFIG_PM_DEVICE) |
| static int spi_smartbond_suspend(const struct device *dev) |
| { |
| int ret; |
| const struct spi_smartbond_cfg *config = dev->config; |
| struct spi_smartbond_data *data = dev->data; |
| |
| data->spi_ctrl_reg = config->regs->SPI_CTRL_REG; |
| /* Disable the SPI digital block */ |
| config->regs->SPI_CTRL_REG &= ~SPI_SPI_CTRL_REG_SPI_EN_CTRL_Msk; |
| /* Gate SPI clocking */ |
| CRG_COM->RESET_CLK_COM_REG = config->periph_clock_config; |
| |
| ret = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_SLEEP); |
| if (ret < 0) { |
| LOG_WRN("Failed to configure the SPI pins to inactive state"); |
| } |
| |
| return ret; |
| } |
| |
| static int spi_smartbond_pm_action(const struct device *dev, |
| enum pm_device_action action) |
| { |
| int ret; |
| |
| switch (action) { |
| case PM_DEVICE_ACTION_RESUME: |
| da1469x_pd_acquire(MCU_PD_DOMAIN_COM); |
| ret = spi_smartbond_resume(dev); |
| break; |
| case PM_DEVICE_ACTION_SUSPEND: |
| ret = spi_smartbond_suspend(dev); |
| da1469x_pd_release(MCU_PD_DOMAIN_COM); |
| break; |
| default: |
| ret = -ENOTSUP; |
| } |
| |
| return ret; |
| } |
| #endif |
| |
| static int spi_smartbond_init(const struct device *dev) |
| { |
| int ret; |
| struct spi_smartbond_data *data = dev->data; |
| |
| #ifdef CONFIG_PM_DEVICE_RUNTIME |
| /* Make sure device state is marked as suspended */ |
| pm_device_init_suspended(dev); |
| |
| ret = pm_device_runtime_enable(dev); |
| |
| #else |
| da1469x_pd_acquire(MCU_PD_DOMAIN_COM); |
| ret = spi_smartbond_resume(dev); |
| #endif |
| spi_context_unlock_unconditionally(&data->ctx); |
| |
| return ret; |
| } |
| |
| #define SPI_SMARTBOND_DEVICE(id) \ |
| PINCTRL_DT_INST_DEFINE(id); \ |
| static const struct spi_smartbond_cfg spi_smartbond_##id##_cfg = { \ |
| .regs = (SPI_Type *)DT_INST_REG_ADDR(id), \ |
| .periph_clock_config = DT_INST_PROP(id, periph_clock_config), \ |
| .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(id), \ |
| }; \ |
| static struct spi_smartbond_data spi_smartbond_##id##_data = { \ |
| SPI_CONTEXT_INIT_LOCK(spi_smartbond_##id##_data, ctx), \ |
| SPI_CONTEXT_INIT_SYNC(spi_smartbond_##id##_data, ctx), \ |
| SPI_CONTEXT_CS_GPIOS_INITIALIZE(DT_DRV_INST(id), ctx)}; \ |
| PM_DEVICE_DT_INST_DEFINE(id, spi_smartbond_pm_action); \ |
| DEVICE_DT_INST_DEFINE(id, \ |
| spi_smartbond_init, \ |
| PM_DEVICE_DT_INST_GET(id), \ |
| &spi_smartbond_##id##_data, \ |
| &spi_smartbond_##id##_cfg, \ |
| POST_KERNEL, CONFIG_SPI_INIT_PRIORITY, \ |
| &spi_smartbond_driver_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(SPI_SMARTBOND_DEVICE) |