| /* |
| * Copyright (c) 2018 SiFive Inc. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT sifive_spi0 |
| |
| #define LOG_LEVEL CONFIG_SPI_LOG_LEVEL |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(spi_sifive); |
| |
| #include "spi_sifive.h" |
| |
| #include <soc.h> |
| #include <stdbool.h> |
| |
| /* Helper Functions */ |
| |
| static ALWAYS_INLINE |
| void sys_set_mask(mem_addr_t addr, uint32_t mask, uint32_t value) |
| { |
| uint32_t temp = sys_read32(addr); |
| |
| temp &= ~(mask); |
| temp |= value; |
| |
| sys_write32(temp, addr); |
| } |
| |
| static int spi_config(const struct device *dev, uint32_t frequency, |
| uint16_t operation) |
| { |
| uint32_t div; |
| uint32_t fmt_len; |
| |
| if (operation & SPI_HALF_DUPLEX) { |
| return -ENOTSUP; |
| } |
| |
| if (SPI_OP_MODE_GET(operation) != SPI_OP_MODE_MASTER) { |
| return -ENOTSUP; |
| } |
| |
| if (operation & SPI_MODE_LOOP) { |
| return -ENOTSUP; |
| } |
| |
| /* Set the SPI frequency */ |
| div = (SPI_CFG(dev)->f_sys / (frequency * 2U)) - 1; |
| sys_write32((SF_SCKDIV_DIV_MASK & div), SPI_REG(dev, REG_SCKDIV)); |
| |
| /* Set the polarity */ |
| if (operation & SPI_MODE_CPOL) { |
| /* If CPOL is set, then SCK idles at logical 1 */ |
| sys_set_bit(SPI_REG(dev, REG_SCKMODE), SF_SCKMODE_POL); |
| } else { |
| /* SCK idles at logical 0 */ |
| sys_clear_bit(SPI_REG(dev, REG_SCKMODE), SF_SCKMODE_POL); |
| } |
| |
| /* Set the phase */ |
| if (operation & SPI_MODE_CPHA) { |
| /* |
| * If CPHA is set, then data is sampled |
| * on the trailing SCK edge |
| */ |
| sys_set_bit(SPI_REG(dev, REG_SCKMODE), SF_SCKMODE_PHA); |
| } else { |
| /* Data is sampled on the leading SCK edge */ |
| sys_clear_bit(SPI_REG(dev, REG_SCKMODE), SF_SCKMODE_PHA); |
| } |
| |
| /* Get the frame length */ |
| fmt_len = SPI_WORD_SIZE_GET(operation); |
| if (fmt_len > SF_FMT_LEN_MASK) { |
| return -ENOTSUP; |
| } |
| |
| /* Set the frame length */ |
| fmt_len = fmt_len << SF_FMT_LEN; |
| fmt_len &= SF_FMT_LEN_MASK; |
| sys_set_mask(SPI_REG(dev, REG_FMT), SF_FMT_LEN_MASK, fmt_len); |
| |
| if (IS_ENABLED(CONFIG_SPI_EXTENDED_MODES) && |
| (operation & SPI_LINES_MASK) != SPI_LINES_SINGLE) { |
| return -ENOTSUP; |
| } |
| /* Set single line operation */ |
| sys_set_mask(SPI_REG(dev, REG_FMT), |
| SF_FMT_PROTO_MASK, |
| SF_FMT_PROTO_SINGLE); |
| |
| /* Set the endianness */ |
| if (operation & SPI_TRANSFER_LSB) { |
| sys_set_bit(SPI_REG(dev, REG_FMT), SF_FMT_ENDIAN); |
| } else { |
| sys_clear_bit(SPI_REG(dev, REG_FMT), SF_FMT_ENDIAN); |
| } |
| |
| return 0; |
| } |
| |
| static ALWAYS_INLINE bool spi_sifive_send_available(const struct device *dev) |
| { |
| return !(sys_read32(SPI_REG(dev, REG_TXDATA)) & SF_TXDATA_FULL); |
| } |
| |
| static ALWAYS_INLINE |
| void spi_sifive_send(const struct device *dev, uint8_t frame) |
| { |
| sys_write32((uint32_t) frame, SPI_REG(dev, REG_TXDATA)); |
| } |
| |
| static ALWAYS_INLINE |
| bool spi_sifive_recv(const struct device *dev, uint8_t *val) |
| { |
| uint32_t reg = sys_read32(SPI_REG(dev, REG_RXDATA)); |
| |
| if (reg & SF_RXDATA_EMPTY) { |
| return false; |
| } |
| *val = (uint8_t) reg; |
| return true; |
| } |
| |
| static void spi_sifive_xfer(const struct device *dev, const bool hw_cs_control) |
| { |
| struct spi_context *ctx = &SPI_DATA(dev)->ctx; |
| uint8_t txd, rxd; |
| int queued_frames = 0; |
| |
| while (spi_context_tx_on(ctx) || spi_context_rx_on(ctx) || queued_frames > 0) { |
| bool send = false; |
| |
| /* As long as frames remain to be sent, attempt to queue them on Tx FIFO. If |
| * the FIFO is full then another attempt will be made next pass. If Rx length |
| * > Tx length then queue dummy Tx in order to read the requested Rx data. |
| */ |
| if (spi_context_tx_buf_on(ctx)) { |
| send = true; |
| txd = *ctx->tx_buf; |
| } else if (queued_frames == 0) { /* Implies spi_context_rx_on(). */ |
| send = true; |
| txd = 0U; |
| } |
| |
| if (send && spi_sifive_send_available(dev)) { |
| spi_sifive_send(dev, txd); |
| queued_frames++; |
| spi_context_update_tx(ctx, 1, 1); |
| } |
| |
| if (queued_frames > 0 && spi_sifive_recv(dev, &rxd)) { |
| if (spi_context_rx_buf_on(ctx)) { |
| *ctx->rx_buf = rxd; |
| } |
| queued_frames--; |
| spi_context_update_rx(ctx, 1, 1); |
| } |
| } |
| |
| /* Deassert the CS line */ |
| if (!hw_cs_control) { |
| spi_context_cs_control(&SPI_DATA(dev)->ctx, false); |
| } else { |
| sys_write32(SF_CSMODE_OFF, SPI_REG(dev, REG_CSMODE)); |
| } |
| |
| spi_context_complete(ctx, dev, 0); |
| } |
| |
| /* API Functions */ |
| |
| static int spi_sifive_init(const struct device *dev) |
| { |
| int err; |
| #ifdef CONFIG_PINCTRL |
| struct spi_sifive_cfg *cfg = (struct spi_sifive_cfg *)dev->config; |
| #endif |
| /* Disable SPI Flash mode */ |
| sys_clear_bit(SPI_REG(dev, REG_FCTRL), SF_FCTRL_EN); |
| |
| err = spi_context_cs_configure_all(&SPI_DATA(dev)->ctx); |
| if (err < 0) { |
| return err; |
| } |
| |
| #ifdef CONFIG_PINCTRL |
| err = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT); |
| if (err < 0) { |
| return err; |
| } |
| #endif |
| |
| /* Make sure the context is unlocked */ |
| spi_context_unlock_unconditionally(&SPI_DATA(dev)->ctx); |
| return 0; |
| } |
| |
| static int spi_sifive_transceive(const struct device *dev, |
| const struct spi_config *config, |
| const struct spi_buf_set *tx_bufs, |
| const struct spi_buf_set *rx_bufs) |
| { |
| int rc = 0; |
| bool hw_cs_control = false; |
| |
| /* Lock the SPI Context */ |
| spi_context_lock(&SPI_DATA(dev)->ctx, false, NULL, NULL, config); |
| |
| /* Configure the SPI bus */ |
| SPI_DATA(dev)->ctx.config = config; |
| |
| /* |
| * If the chip select configuration is not present, we'll ask the |
| * SPI peripheral itself to control the CS line |
| */ |
| if (!spi_cs_is_gpio(config)) { |
| hw_cs_control = true; |
| } |
| |
| if (!hw_cs_control) { |
| /* |
| * If the user has requested manual GPIO control, ask the |
| * context for control and disable HW control |
| */ |
| sys_write32(SF_CSMODE_OFF, SPI_REG(dev, REG_CSMODE)); |
| } else { |
| /* |
| * Tell the hardware to control the requested CS pin. |
| * NOTE: |
| * For the SPI peripheral, the pin number is not the |
| * GPIO pin, but the index into the list of available |
| * CS lines for the SPI peripheral. |
| */ |
| sys_write32(config->slave, SPI_REG(dev, REG_CSID)); |
| sys_write32(SF_CSMODE_OFF, SPI_REG(dev, REG_CSMODE)); |
| } |
| |
| rc = spi_config(dev, config->frequency, config->operation); |
| if (rc < 0) { |
| spi_context_release(&SPI_DATA(dev)->ctx, rc); |
| return rc; |
| } |
| |
| spi_context_buffers_setup(&SPI_DATA(dev)->ctx, tx_bufs, rx_bufs, 1); |
| |
| /* Assert the CS line */ |
| if (!hw_cs_control) { |
| spi_context_cs_control(&SPI_DATA(dev)->ctx, true); |
| } else { |
| sys_write32(SF_CSMODE_HOLD, SPI_REG(dev, REG_CSMODE)); |
| } |
| |
| /* Perform transfer */ |
| spi_sifive_xfer(dev, hw_cs_control); |
| |
| rc = spi_context_wait_for_completion(&SPI_DATA(dev)->ctx); |
| |
| spi_context_release(&SPI_DATA(dev)->ctx, rc); |
| |
| return rc; |
| } |
| |
| static int spi_sifive_release(const struct device *dev, |
| const struct spi_config *config) |
| { |
| spi_context_unlock_unconditionally(&SPI_DATA(dev)->ctx); |
| return 0; |
| } |
| |
| /* Device Instantiation */ |
| |
| static struct spi_driver_api spi_sifive_api = { |
| .transceive = spi_sifive_transceive, |
| .release = spi_sifive_release, |
| }; |
| |
| #define SPI_INIT(n) \ |
| PINCTRL_DT_INST_DEFINE(n); \ |
| static struct spi_sifive_data spi_sifive_data_##n = { \ |
| SPI_CONTEXT_INIT_LOCK(spi_sifive_data_##n, ctx), \ |
| SPI_CONTEXT_INIT_SYNC(spi_sifive_data_##n, ctx), \ |
| SPI_CONTEXT_CS_GPIOS_INITIALIZE(DT_DRV_INST(n), ctx) \ |
| }; \ |
| static struct spi_sifive_cfg spi_sifive_cfg_##n = { \ |
| .base = DT_INST_REG_ADDR_BY_NAME(n, control), \ |
| .f_sys = SIFIVE_PERIPHERAL_CLOCK_FREQUENCY, \ |
| .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \ |
| }; \ |
| DEVICE_DT_INST_DEFINE(n, \ |
| spi_sifive_init, \ |
| NULL, \ |
| &spi_sifive_data_##n, \ |
| &spi_sifive_cfg_##n, \ |
| POST_KERNEL, \ |
| CONFIG_SPI_INIT_PRIORITY, \ |
| &spi_sifive_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(SPI_INIT) |