| /* |
| * Copyright (c) 2019 Christian Taedcke <hacking@taedcke.com> |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT silabs_gecko_spi_usart |
| |
| #define LOG_LEVEL CONFIG_SPI_LOG_LEVEL |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(spi_gecko); |
| #include "spi_context.h" |
| |
| #include <zephyr/sys/sys_io.h> |
| #include <zephyr/device.h> |
| #include <zephyr/drivers/spi.h> |
| #include <soc.h> |
| |
| #include "em_cmu.h" |
| #include "em_usart.h" |
| |
| #include <stdbool.h> |
| |
| #ifdef CONFIG_PINCTRL |
| #include <zephyr/drivers/pinctrl.h> |
| #else |
| #ifndef CONFIG_SOC_GECKO_HAS_INDIVIDUAL_PIN_LOCATION |
| #error "Individual pin location support is required" |
| #endif |
| #endif /* CONFIG_PINCTRL */ |
| |
| #if DT_NODE_HAS_PROP(n, peripheral_id) |
| #define CLOCK_USART(id) _CONCAT(cmuClock_USART, id) |
| #define GET_GECKO_USART_CLOCK(n) CLOCK_USART(DT_INST_PROP(n, peripheral_id)) |
| #else |
| #if (USART_COUNT <= 2) |
| #define CLOCK_USART(ref) (((ref) == USART0) ? cmuClock_USART0 \ |
| : ((ref) == USART1) ? cmuClock_USART1 \ |
| : -1) |
| #elif (USART_COUNT == 3) |
| #define CLOCK_USART(ref) (((ref) == USART0) ? cmuClock_USART0 \ |
| : ((ref) == USART1) ? cmuClock_USART1 \ |
| : ((ref) == USART2) ? cmuClock_USART2 \ |
| : -1) |
| #elif (USART_COUNT == 4) |
| #define CLOCK_USART(ref) (((ref) == USART0) ? cmuClock_USART0 \ |
| : ((ref) == USART1) ? cmuClock_USART1 \ |
| : ((ref) == USART2) ? cmuClock_USART2 \ |
| : ((ref) == USART3) ? cmuClock_USART3 \ |
| : -1) |
| #elif (USART_COUNT == 5) |
| #define CLOCK_USART(ref) (((ref) == USART0) ? cmuClock_USART0 \ |
| : ((ref) == USART1) ? cmuClock_USART1 \ |
| : ((ref) == USART2) ? cmuClock_USART2 \ |
| : ((ref) == USART3) ? cmuClock_USART3 \ |
| : ((ref) == USART4) ? cmuClock_USART4 \ |
| : -1) |
| #elif (USART_COUNT == 6) |
| #define CLOCK_USART(ref) (((ref) == USART0) ? cmuClock_USART0 \ |
| : ((ref) == USART1) ? cmuClock_USART1 \ |
| : ((ref) == USART2) ? cmuClock_USART2 \ |
| : ((ref) == USART3) ? cmuClock_USART3 \ |
| : ((ref) == USART4) ? cmuClock_USART4 \ |
| : ((ref) == USART5) ? cmuClock_USART5 \ |
| : -1) |
| #else |
| #error "Undefined number of USARTs." |
| #endif /* USART_COUNT */ |
| #define GET_GECKO_USART_CLOCK(id) CLOCK_USART((USART_TypeDef *)DT_INST_REG_ADDR(id)) |
| #endif /* DT_NODE_HAS_PROP(n, peripheral_id) */ |
| |
| |
| #define SPI_WORD_SIZE 8 |
| |
| /* Structure Declarations */ |
| |
| struct spi_gecko_data { |
| struct spi_context ctx; |
| }; |
| |
| struct spi_gecko_config { |
| USART_TypeDef *base; |
| CMU_Clock_TypeDef clock; |
| #ifdef CONFIG_PINCTRL |
| const struct pinctrl_dev_config *pcfg; |
| #else |
| struct soc_gpio_pin pin_rx; |
| struct soc_gpio_pin pin_tx; |
| struct soc_gpio_pin pin_clk; |
| uint8_t loc_rx; |
| uint8_t loc_tx; |
| uint8_t loc_clk; |
| #endif /* CONFIG_PINCTRL */ |
| }; |
| |
| |
| /* Helper Functions */ |
| static int spi_config(const struct device *dev, |
| const struct spi_config *config, |
| uint16_t *control) |
| { |
| const struct spi_gecko_config *gecko_config = dev->config; |
| struct spi_gecko_data *data = dev->data; |
| |
| 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 (config->operation & SPI_CS_ACTIVE_HIGH) { |
| LOG_ERR("CS active high not supported"); |
| return -ENOTSUP; |
| } |
| |
| if (config->operation & SPI_LOCK_ON) { |
| LOG_ERR("Lock On not supported"); |
| 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 Loopback */ |
| if (config->operation & SPI_MODE_LOOP) { |
| gecko_config->base->CTRL |= USART_CTRL_LOOPBK; |
| } else { |
| gecko_config->base->CTRL &= ~USART_CTRL_LOOPBK; |
| } |
| |
| /* Set CPOL */ |
| if (config->operation & SPI_MODE_CPOL) { |
| gecko_config->base->CTRL |= USART_CTRL_CLKPOL; |
| } else { |
| gecko_config->base->CTRL &= ~USART_CTRL_CLKPOL; |
| } |
| |
| /* Set CPHA */ |
| if (config->operation & SPI_MODE_CPHA) { |
| gecko_config->base->CTRL |= USART_CTRL_CLKPHA; |
| } else { |
| gecko_config->base->CTRL &= ~USART_CTRL_CLKPHA; |
| } |
| |
| /* Set word size */ |
| gecko_config->base->FRAME = usartDatabits8 |
| | USART_FRAME_STOPBITS_DEFAULT |
| | USART_FRAME_PARITY_DEFAULT; |
| |
| /* At this point, it's mandatory to set this on the context! */ |
| data->ctx.config = config; |
| |
| return 0; |
| } |
| |
| static void spi_gecko_send(USART_TypeDef *usart, uint8_t frame) |
| { |
| /* Write frame to register */ |
| USART_Tx(usart, frame); |
| |
| /* Wait until the transfer ends */ |
| while (!(usart->STATUS & USART_STATUS_TXC)) { |
| } |
| } |
| |
| static uint8_t spi_gecko_recv(USART_TypeDef *usart) |
| { |
| /* Return data inside rx register */ |
| return (uint8_t)usart->RXDATA; |
| } |
| |
| static bool spi_gecko_transfer_ongoing(struct spi_gecko_data *data) |
| { |
| return spi_context_tx_on(&data->ctx) || spi_context_rx_on(&data->ctx); |
| } |
| |
| static inline uint8_t spi_gecko_next_tx(struct spi_gecko_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_gecko_shift_frames(USART_TypeDef *usart, |
| struct spi_gecko_data *data) |
| { |
| uint8_t tx_frame; |
| uint8_t rx_frame; |
| |
| tx_frame = spi_gecko_next_tx(data); |
| spi_gecko_send(usart, tx_frame); |
| spi_context_update_tx(&data->ctx, 1, 1); |
| |
| rx_frame = spi_gecko_recv(usart); |
| |
| 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_xfer(const struct device *dev, |
| const struct spi_config *config) |
| { |
| int ret; |
| struct spi_gecko_data *data = dev->data; |
| struct spi_context *ctx = &data->ctx; |
| const struct spi_gecko_config *gecko_config = dev->config; |
| |
| spi_context_cs_control(ctx, true); |
| |
| do { |
| ret = spi_gecko_shift_frames(gecko_config->base, data); |
| } while (!ret && spi_gecko_transfer_ongoing(data)); |
| |
| spi_context_cs_control(ctx, false); |
| spi_context_complete(ctx, dev, 0); |
| } |
| |
| #ifndef CONFIG_PINCTRL |
| static void spi_gecko_init_pins(const struct device *dev) |
| { |
| const struct spi_gecko_config *config = dev->config; |
| |
| GPIO_PinModeSet(config->pin_rx.port, config->pin_rx.pin, |
| config->pin_rx.mode, config->pin_rx.out); |
| GPIO_PinModeSet(config->pin_tx.port, config->pin_tx.pin, |
| config->pin_tx.mode, config->pin_tx.out); |
| GPIO_PinModeSet(config->pin_clk.port, config->pin_clk.pin, |
| config->pin_clk.mode, config->pin_clk.out); |
| |
| /* disable all pins while configuring */ |
| config->base->ROUTEPEN = 0; |
| |
| config->base->ROUTELOC0 = |
| (config->loc_tx << _USART_ROUTELOC0_TXLOC_SHIFT) | |
| (config->loc_rx << _USART_ROUTELOC0_RXLOC_SHIFT) | |
| (config->loc_clk << _USART_ROUTELOC0_CLKLOC_SHIFT); |
| |
| config->base->ROUTELOC1 = _USART_ROUTELOC1_RESETVALUE; |
| |
| config->base->ROUTEPEN = USART_ROUTEPEN_RXPEN | USART_ROUTEPEN_TXPEN | |
| USART_ROUTEPEN_CLKPEN; |
| } |
| #endif /* !CONFIG_PINCTRL */ |
| |
| |
| /* API Functions */ |
| |
| static int spi_gecko_init(const struct device *dev) |
| { |
| int err; |
| const struct spi_gecko_config *config = dev->config; |
| struct spi_gecko_data *data = dev->data; |
| USART_InitSync_TypeDef usartInit = USART_INITSYNC_DEFAULT; |
| |
| /* The peripheral and gpio clock are already enabled from soc and gpio |
| * driver |
| */ |
| |
| usartInit.enable = usartDisable; |
| usartInit.baudrate = 1000000; |
| usartInit.databits = usartDatabits8; |
| usartInit.master = 1; |
| usartInit.msbf = 1; |
| usartInit.clockMode = usartClockMode0; |
| #if defined(USART_INPUT_RXPRS) && defined(USART_TRIGCTRL_AUTOTXTEN) |
| usartInit.prsRxEnable = 0; |
| usartInit.prsRxCh = 0; |
| usartInit.autoTx = 0; |
| #endif |
| |
| /* Enable USART clock */ |
| CMU_ClockEnable(config->clock, true); |
| |
| /* Init USART */ |
| USART_InitSync(config->base, &usartInit); |
| |
| #ifdef CONFIG_PINCTRL |
| err = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT); |
| if (err < 0) { |
| return err; |
| } |
| #else |
| /* Initialize USART pins */ |
| spi_gecko_init_pins(dev); |
| #endif /* CONFIG_PINCTRL */ |
| |
| err = spi_context_cs_configure_all(&data->ctx); |
| if (err < 0) { |
| return err; |
| } |
| |
| /* Enable the peripheral */ |
| config->base->CMD = (uint32_t) usartEnable; |
| |
| return 0; |
| } |
| |
| static int spi_gecko_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_data *data = dev->data; |
| uint16_t control = 0; |
| |
| spi_config(dev, config, &control); |
| spi_context_buffers_setup(&data->ctx, tx_bufs, rx_bufs, 1); |
| spi_gecko_xfer(dev, config); |
| return 0; |
| } |
| |
| #ifdef CONFIG_SPI_ASYNC |
| static int spi_gecko_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_release(const struct device *dev, |
| const struct spi_config *config) |
| { |
| const struct spi_gecko_config *gecko_config = dev->config; |
| |
| if (!(gecko_config->base->STATUS & USART_STATUS_TXIDLE)) { |
| return -EBUSY; |
| } |
| return 0; |
| } |
| |
| /* Device Instantiation */ |
| static struct spi_driver_api spi_gecko_api = { |
| .transceive = spi_gecko_transceive, |
| #ifdef CONFIG_SPI_ASYNC |
| .transceive_async = spi_gecko_transceive_async, |
| #endif /* CONFIG_SPI_ASYNC */ |
| .release = spi_gecko_release, |
| }; |
| |
| #ifdef CONFIG_PINCTRL |
| #define SPI_INIT(n) \ |
| PINCTRL_DT_INST_DEFINE(n); \ |
| static struct spi_gecko_data spi_gecko_data_##n = { \ |
| SPI_CONTEXT_INIT_LOCK(spi_gecko_data_##n, ctx), \ |
| SPI_CONTEXT_INIT_SYNC(spi_gecko_data_##n, ctx), \ |
| SPI_CONTEXT_CS_GPIOS_INITIALIZE(DT_DRV_INST(n), ctx) \ |
| }; \ |
| static struct spi_gecko_config spi_gecko_cfg_##n = { \ |
| .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \ |
| .base = (USART_TypeDef *) \ |
| DT_INST_REG_ADDR(n), \ |
| .clock = GET_GECKO_USART_CLOCK(n) \ |
| }; \ |
| DEVICE_DT_INST_DEFINE(n, \ |
| spi_gecko_init, \ |
| NULL, \ |
| &spi_gecko_data_##n, \ |
| &spi_gecko_cfg_##n, \ |
| POST_KERNEL, \ |
| CONFIG_SPI_INIT_PRIORITY, \ |
| &spi_gecko_api); |
| #else |
| #define SPI_INIT(n) \ |
| static struct spi_gecko_data spi_gecko_data_##n = { \ |
| SPI_CONTEXT_INIT_LOCK(spi_gecko_data_##n, ctx), \ |
| SPI_CONTEXT_INIT_SYNC(spi_gecko_data_##n, ctx), \ |
| SPI_CONTEXT_CS_GPIOS_INITIALIZE(DT_DRV_INST(n), ctx) \ |
| }; \ |
| static struct spi_gecko_config spi_gecko_cfg_##n = { \ |
| .base = (USART_TypeDef *) \ |
| DT_INST_REG_ADDR(n), \ |
| .clock = GET_GECKO_USART_CLOCK(n), \ |
| .pin_rx = { DT_INST_PROP_BY_IDX(n, location_rx, 1), \ |
| DT_INST_PROP_BY_IDX(n, location_rx, 2), \ |
| gpioModeInput, 1}, \ |
| .pin_tx = { DT_INST_PROP_BY_IDX(n, location_tx, 1), \ |
| DT_INST_PROP_BY_IDX(n, location_tx, 2), \ |
| gpioModePushPull, 1}, \ |
| .pin_clk = { DT_INST_PROP_BY_IDX(n, location_clk, 1), \ |
| DT_INST_PROP_BY_IDX(n, location_clk, 2), \ |
| gpioModePushPull, 1}, \ |
| .loc_rx = DT_INST_PROP_BY_IDX(n, location_rx, 0), \ |
| .loc_tx = DT_INST_PROP_BY_IDX(n, location_tx, 0), \ |
| .loc_clk = DT_INST_PROP_BY_IDX(n, location_clk, 0), \ |
| }; \ |
| DEVICE_DT_INST_DEFINE(n, \ |
| spi_gecko_init, \ |
| NULL, \ |
| &spi_gecko_data_##n, \ |
| &spi_gecko_cfg_##n, \ |
| POST_KERNEL, \ |
| CONFIG_SPI_INIT_PRIORITY, \ |
| &spi_gecko_api); |
| #endif /* CONFIG_PINCTRL */ |
| |
| DT_INST_FOREACH_STATUS_OKAY(SPI_INIT) |