| /* |
| * Copyright (c) 2019 Antmicro <www.antmicro.com> |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT litex_spi |
| |
| #define LOG_LEVEL CONFIG_SPI_LOG_LEVEL |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(spi_litespi); |
| #include "spi_litespi.h" |
| #include <stdbool.h> |
| |
| /* Helper Functions */ |
| static int spi_config(const struct spi_config *config, uint16_t *control) |
| { |
| uint8_t cs = 0x00; |
| |
| if (config->slave != 0) { |
| if (config->slave >= SPI_MAX_CS_SIZE) { |
| LOG_ERR("More slaves than supported"); |
| return -ENOTSUP; |
| } |
| cs = (uint8_t)(config->slave); |
| } |
| |
| if (config->operation & SPI_HALF_DUPLEX) { |
| LOG_ERR("Half-duplex not supported"); |
| return -ENOTSUP; |
| } |
| |
| if (SPI_WORD_SIZE_GET(config->operation) != 8) { |
| 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_MODE_CPOL | SPI_MODE_CPHA)) { |
| LOG_ERR("Only supports CPOL=CPHA=0"); |
| 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) { |
| litex_write8(SPI_ENABLE, SPI_LOOPBACK_ADDR); |
| } |
| /* Set word size */ |
| *control = (uint16_t) (SPI_WORD_SIZE_GET(config->operation) |
| << POSITION_WORD_SIZE); |
| /* Write configurations */ |
| litex_write8(cs, SPI_CS_ADDR); |
| litex_write16(*control, SPI_CONTROL_ADDR); |
| |
| return 0; |
| } |
| |
| static void spi_litespi_send(const struct device *dev, uint8_t frame, |
| uint16_t control) |
| { |
| /* Write frame to register */ |
| litex_write8(frame, SPI_MOSI_DATA_ADDR); |
| /* Start the transfer */ |
| litex_write16(control | SPI_ENABLE, SPI_CONTROL_ADDR); |
| /* Wait until the transfer ends */ |
| while (!(litex_read8(SPI_STATUS_ADDR))) |
| ; |
| } |
| |
| static uint8_t spi_litespi_recv(void) |
| { |
| /* Return data inside MISO register */ |
| return litex_read8(SPI_MISO_DATA_ADDR); |
| } |
| |
| static void spi_litespi_xfer(const struct device *dev, |
| const struct spi_config *config, |
| uint16_t control) |
| { |
| struct spi_context *ctx = &SPI_DATA(dev)->ctx; |
| uint32_t send_len = spi_context_longest_current_buf(ctx); |
| uint8_t read_data; |
| |
| for (uint32_t i = 0; i < send_len; i++) { |
| /* Send a frame */ |
| if (i < ctx->tx_len) { |
| spi_litespi_send(dev, (uint8_t) (ctx->tx_buf)[i], |
| control); |
| } else { |
| /* Send dummy bytes */ |
| spi_litespi_send(dev, 0, control); |
| } |
| /* Receive a frame */ |
| read_data = spi_litespi_recv(); |
| if (i < ctx->rx_len) { |
| ctx->rx_buf[i] = read_data; |
| } |
| } |
| spi_context_complete(ctx, 0); |
| } |
| |
| /* API Functions */ |
| |
| static int spi_litespi_init(const struct device *dev) |
| { |
| return 0; |
| } |
| |
| static int spi_litespi_transceive(const struct device *dev, |
| const struct spi_config *config, |
| const struct spi_buf_set *tx_bufs, |
| const struct spi_buf_set *rx_bufs) |
| { |
| uint16_t control = 0; |
| |
| spi_config(config, &control); |
| spi_context_buffers_setup(&SPI_DATA(dev)->ctx, tx_bufs, rx_bufs, 1); |
| spi_litespi_xfer(dev, config, control); |
| return 0; |
| } |
| |
| #ifdef CONFIG_SPI_ASYNC |
| static int spi_litespi_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_litespi_release(const struct device *dev, |
| const struct spi_config *config) |
| { |
| if (!(litex_read8(SPI_STATUS_ADDR))) { |
| return -EBUSY; |
| } |
| return 0; |
| } |
| |
| /* Device Instantiation */ |
| static struct spi_driver_api spi_litespi_api = { |
| .transceive = spi_litespi_transceive, |
| #ifdef CONFIG_SPI_ASYNC |
| .transceive_async = spi_litespi_transceive_async, |
| #endif /* CONFIG_SPI_ASYNC */ |
| .release = spi_litespi_release, |
| }; |
| |
| #define SPI_INIT(n) \ |
| static struct spi_litespi_data spi_litespi_data_##n = { \ |
| SPI_CONTEXT_INIT_LOCK(spi_litespi_data_##n, ctx), \ |
| SPI_CONTEXT_INIT_SYNC(spi_litespi_data_##n, ctx), \ |
| }; \ |
| static struct spi_litespi_cfg spi_litespi_cfg_##n = { \ |
| .base = DT_INST_REG_ADDR_BY_NAME(n, control), \ |
| }; \ |
| DEVICE_DT_INST_DEFINE(n, \ |
| spi_litespi_init, \ |
| NULL, \ |
| &spi_litespi_data_##n, \ |
| &spi_litespi_cfg_##n, \ |
| POST_KERNEL, \ |
| CONFIG_SPI_INIT_PRIORITY, \ |
| &spi_litespi_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(SPI_INIT) |