| /* |
| * Copyright (c) 2024 Daikin Comfort Technologies North America, Inc. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT silabs_gecko_spi_eusart |
| |
| #include <stdbool.h> |
| |
| #include <zephyr/sys/sys_io.h> |
| #include <zephyr/device.h> |
| #include <zephyr/drivers/spi.h> |
| #include <zephyr/drivers/clock_control.h> |
| #include <zephyr/drivers/clock_control/clock_control_silabs.h> |
| #include <zephyr/drivers/pinctrl.h> |
| #include <zephyr/logging/log.h> |
| |
| #include <em_cmu.h> |
| #include <em_eusart.h> |
| |
| LOG_MODULE_REGISTER(spi_gecko_eusart, CONFIG_SPI_LOG_LEVEL); |
| |
| #include "spi_context.h" |
| |
| #define SPI_WORD_SIZE 8 |
| |
| /* Structure Declarations */ |
| |
| struct spi_gecko_eusart_data { |
| struct spi_context ctx; |
| }; |
| |
| struct spi_gecko_eusart_config { |
| EUSART_TypeDef *base; |
| const struct device *clock_dev; |
| const struct silabs_clock_control_cmu_config clock_cfg; |
| uint32_t clock_frequency; |
| const struct pinctrl_dev_config *pcfg; |
| }; |
| |
| /* Helper Functions */ |
| static int spi_eusart_config(const struct device *dev, const struct spi_config *config, |
| uint16_t *control) |
| { |
| struct spi_gecko_eusart_data *data = dev->data; |
| const struct spi_gecko_eusart_config *gecko_config = dev->config; |
| uint32_t spi_frequency; |
| |
| EUSART_SpiAdvancedInit_TypeDef eusartAdvancedSpiInit = EUSART_SPI_ADVANCED_INIT_DEFAULT; |
| EUSART_SpiInit_TypeDef eusartInit = EUSART_SPI_MASTER_INIT_DEFAULT_HF; |
| |
| int err; |
| |
| err = clock_control_get_rate(gecko_config->clock_dev, |
| (clock_control_subsys_t)&gecko_config->clock_cfg, |
| &spi_frequency); |
| if (err) { |
| return err; |
| } |
| /* Max supported SPI frequency is half the source clock */ |
| spi_frequency /= 2; |
| |
| if (spi_context_configured(&data->ctx, config)) { |
| /* Already configured. No need to do it again. */ |
| return 0; |
| } |
| |
| if (config->operation & SPI_HALF_DUPLEX) { |
| LOG_ERR("Half-duplex not supported"); |
| return -ENOTSUP; |
| } |
| |
| if (SPI_WORD_SIZE_GET(config->operation) != SPI_WORD_SIZE) { |
| LOG_ERR("Word size must be %d", SPI_WORD_SIZE); |
| return -ENOTSUP; |
| } |
| |
| if (IS_ENABLED(CONFIG_SPI_EXTENDED_MODES) && |
| (config->operation & SPI_LINES_MASK) != SPI_LINES_SINGLE) { |
| LOG_ERR("Only supports single mode"); |
| return -ENOTSUP; |
| } |
| |
| if (config->operation & SPI_TRANSFER_LSB) { |
| LOG_ERR("LSB first not supported"); |
| return -ENOTSUP; |
| } |
| |
| if (config->operation & SPI_OP_MODE_SLAVE) { |
| LOG_ERR("Slave mode not supported"); |
| return -ENOTSUP; |
| } |
| |
| /* Set frequency to the minimum of what the device supports, what the |
| * user has configured the controller to, and the max frequency for the |
| * transaction. |
| */ |
| if (gecko_config->clock_frequency > spi_frequency) { |
| LOG_ERR("SPI clock-frequency too high"); |
| return -EINVAL; |
| } |
| spi_frequency = MIN(gecko_config->clock_frequency, spi_frequency); |
| if (config->frequency) { |
| spi_frequency = MIN(config->frequency, spi_frequency); |
| } |
| eusartInit.bitRate = spi_frequency; |
| |
| if (config->operation & SPI_MODE_LOOP) { |
| eusartInit.loopbackEnable = eusartLoopbackEnable; |
| } else { |
| eusartInit.loopbackEnable = eusartLoopbackDisable; |
| } |
| |
| /* Set Clock Mode */ |
| if (config->operation & SPI_MODE_CPOL) { |
| if (config->operation & SPI_MODE_CPHA) { |
| eusartInit.clockMode = eusartClockMode3; |
| } else { |
| eusartInit.clockMode = eusartClockMode2; |
| } |
| } else { |
| if (config->operation & SPI_MODE_CPHA) { |
| eusartInit.clockMode = eusartClockMode1; |
| } else { |
| eusartInit.clockMode = eusartClockMode0; |
| } |
| } |
| |
| if (config->operation & SPI_CS_ACTIVE_HIGH) { |
| eusartAdvancedSpiInit.csPolarity = eusartCsActiveHigh; |
| } else { |
| eusartAdvancedSpiInit.csPolarity = eusartCsActiveLow; |
| } |
| |
| eusartInit.databits = eusartDataBits8; |
| eusartInit.advancedSettings = &eusartAdvancedSpiInit; |
| |
| /* Enable EUSART clock */ |
| err = clock_control_on(gecko_config->clock_dev, |
| (clock_control_subsys_t)&gecko_config->clock_cfg); |
| if (err < 0) { |
| return err; |
| } |
| |
| /* Initialize the EUSART */ |
| EUSART_SpiInit(gecko_config->base, &eusartInit); |
| |
| data->ctx.config = config; |
| |
| /* Enable the peripheral */ |
| gecko_config->base->CMD = (uint32_t)eusartEnable; |
| |
| return 0; |
| } |
| |
| static void spi_gecko_eusart_send(EUSART_TypeDef *eusart, uint8_t frame) |
| { |
| /* Write frame to register */ |
| EUSART_Tx(eusart, frame); |
| |
| /* Wait until the transfer ends */ |
| while (!(eusart->STATUS & EUSART_STATUS_TXC)) { |
| } |
| } |
| |
| static uint8_t spi_gecko_eusart_recv(EUSART_TypeDef *eusart) |
| { |
| /* Return data inside rx register */ |
| return EUSART_Rx(eusart); |
| } |
| |
| static bool spi_eusart_transfer_ongoing(struct spi_gecko_eusart_data *data) |
| { |
| return spi_context_tx_on(&data->ctx) || spi_context_rx_on(&data->ctx); |
| } |
| |
| static inline uint8_t spi_eusart_next_tx(struct spi_gecko_eusart_data *data) |
| { |
| uint8_t tx_frame = 0; |
| |
| if (spi_context_tx_buf_on(&data->ctx)) { |
| tx_frame = UNALIGNED_GET((uint8_t *)(data->ctx.tx_buf)); |
| } |
| |
| return tx_frame; |
| } |
| |
| static int spi_eusart_shift_frames(EUSART_TypeDef *eusart, struct spi_gecko_eusart_data *data) |
| { |
| uint8_t tx_frame; |
| uint8_t rx_frame; |
| |
| tx_frame = spi_eusart_next_tx(data); |
| spi_gecko_eusart_send(eusart, tx_frame); |
| spi_context_update_tx(&data->ctx, 1, 1); |
| |
| rx_frame = spi_gecko_eusart_recv(eusart); |
| |
| if (spi_context_rx_buf_on(&data->ctx)) { |
| UNALIGNED_PUT(rx_frame, (uint8_t *)data->ctx.rx_buf); |
| } |
| spi_context_update_rx(&data->ctx, 1, 1); |
| return 0; |
| } |
| |
| static void spi_gecko_eusart_xfer(const struct device *dev, const struct spi_config *config) |
| { |
| int ret; |
| struct spi_gecko_eusart_data *data = dev->data; |
| struct spi_context *ctx = &data->ctx; |
| const struct spi_gecko_eusart_config *gecko_config = dev->config; |
| |
| spi_context_cs_control(ctx, true); |
| |
| do { |
| ret = spi_eusart_shift_frames(gecko_config->base, data); |
| } while (!ret && spi_eusart_transfer_ongoing(data)); |
| |
| spi_context_cs_control(ctx, false); |
| spi_context_complete(ctx, dev, 0); |
| } |
| |
| /* API Functions */ |
| static int spi_gecko_eusart_init(const struct device *dev) |
| { |
| int err; |
| const struct spi_gecko_eusart_config *config = dev->config; |
| struct spi_gecko_eusart_data *data = dev->data; |
| |
| err = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT); |
| if (err < 0) { |
| return err; |
| } |
| |
| err = spi_context_cs_configure_all(&data->ctx); |
| if (err < 0) { |
| return err; |
| } |
| spi_context_unlock_unconditionally(&data->ctx); |
| |
| return 0; |
| } |
| |
| static int spi_gecko_eusart_transceive(const struct device *dev, const struct spi_config *config, |
| const struct spi_buf_set *tx_bufs, |
| const struct spi_buf_set *rx_bufs) |
| { |
| struct spi_gecko_eusart_data *data = dev->data; |
| uint16_t control = 0; |
| int ret; |
| |
| spi_context_lock(&data->ctx, false, NULL, NULL, config); |
| |
| ret = spi_eusart_config(dev, config, &control); |
| if (ret < 0) { |
| spi_context_release(&data->ctx, ret); |
| return ret; |
| } |
| |
| spi_context_buffers_setup(&data->ctx, tx_bufs, rx_bufs, 1); |
| spi_gecko_eusart_xfer(dev, config); |
| |
| spi_context_release(&data->ctx, ret); |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_SPI_ASYNC |
| static int spi_gecko_eusart_transceive_async(const struct device *dev, |
| const struct spi_config *config, |
| const struct spi_buf_set *tx_bufs, |
| const struct spi_buf_set *rx_bufs, |
| struct k_poll_signal *async) |
| { |
| return -ENOTSUP; |
| } |
| #endif /* CONFIG_SPI_ASYNC */ |
| |
| static int spi_gecko_eusart_release(const struct device *dev, const struct spi_config *config) |
| { |
| const struct spi_gecko_eusart_config *gecko_config = dev->config; |
| struct spi_gecko_eusart_data *data = dev->data; |
| |
| spi_context_unlock_unconditionally(&data->ctx); |
| |
| if (!(gecko_config->base->STATUS & EUSART_STATUS_TXIDLE)) { |
| return -EBUSY; |
| } |
| return 0; |
| } |
| |
| /* Device Instantiation */ |
| static const struct spi_driver_api spi_gecko_eusart_api = { |
| .transceive = spi_gecko_eusart_transceive, |
| #ifdef CONFIG_SPI_ASYNC |
| .transceive_async = spi_gecko_eusart_transceive_async, |
| #endif /* CONFIG_SPI_ASYNC */ |
| .release = spi_gecko_eusart_release, |
| }; |
| |
| #define SPI_INIT(n) \ |
| PINCTRL_DT_INST_DEFINE(n); \ |
| static struct spi_gecko_eusart_data spi_gecko_eusart_data_##n = { \ |
| SPI_CONTEXT_INIT_LOCK(spi_gecko_eusart_data_##n, ctx), \ |
| SPI_CONTEXT_INIT_SYNC(spi_gecko_eusart_data_##n, ctx), \ |
| SPI_CONTEXT_CS_GPIOS_INITIALIZE(DT_DRV_INST(n), ctx)}; \ |
| static struct spi_gecko_eusart_config spi_gecko_eusart_cfg_##n = { \ |
| .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \ |
| .base = (EUSART_TypeDef *)DT_INST_REG_ADDR(n), \ |
| .clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(n)), \ |
| .clock_cfg = SILABS_DT_INST_CLOCK_CFG(n), \ |
| .clock_frequency = DT_INST_PROP_OR(n, clock_frequency, 1000000) \ |
| }; \ |
| DEVICE_DT_INST_DEFINE(n, spi_gecko_eusart_init, NULL, &spi_gecko_eusart_data_##n, \ |
| &spi_gecko_eusart_cfg_##n, POST_KERNEL, CONFIG_SPI_INIT_PRIORITY, \ |
| &spi_gecko_eusart_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(SPI_INIT) |