/* * Copyright (c) 2020 Henrik Brix Andersen * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT xlnx_xps_spi_2_00_a #include #include #include #include LOG_MODULE_REGISTER(xlnx_quadspi, CONFIG_SPI_LOG_LEVEL); #include "spi_context.h" /* AXI Quad SPI v3.2 register offsets (See Xilinx PG153 for details) */ #define SRR_OFFSET 0x40 #define SPICR_OFFSET 0x60 #define SPISR_OFFSET 0x64 #define SPI_DTR_OFFSET 0x68 #define SPI_DRR_OFFSET 0x6c #define SPISSR_OFFSET 0x70 #define SPI_TX_FIFO_OCR_OFFSET 0x74 #define SPI_RX_FIFO_OCR_OFFSET 0x78 #define DGIER_OFFSET 0x1c #define IPISR_OFFSET 0x20 #define IPIER_OFFSET 0x28 /* SRR bit definitions */ #define SRR_SOFTRESET_MAGIC 0xa /* SPICR bit definitions */ #define SPICR_LOOP BIT(0) #define SPICR_SPE BIT(1) #define SPICR_MASTER BIT(2) #define SPICR_CPOL BIT(3) #define SPICR_CPHA BIT(4) #define SPICR_TX_FIFO_RESET BIT(5) #define SPICR_RX_FIFO_RESET BIT(6) #define SPICR_MANUAL_SS BIT(7) #define SPICR_MASTER_XFER_INH BIT(8) #define SPICR_LSB_FIRST BIT(9) /* SPISR bit definitions */ #define SPISR_RX_EMPTY BIT(0) #define SPISR_RX_FULL BIT(1) #define SPISR_TX_EMPTY BIT(2) #define SPISR_TX_FULL BIT(3) #define SPISR_MODF BIT(4) #define SPISR_SLAVE_MODE_SELECT BIT(5) #define SPISR_CPOL_CPHA_ERROR BIT(6) #define SPISR_SLAVE_MODE_ERROR BIT(7) #define SPISR_MSB_ERROR BIT(8) #define SPISR_LOOPBACK_ERROR BIT(9) #define SPISR_COMMAND_ERROR BIT(10) #define SPISR_ERROR_MASK (SPISR_COMMAND_ERROR | \ SPISR_LOOPBACK_ERROR | \ SPISR_MSB_ERROR | \ SPISR_SLAVE_MODE_ERROR | \ SPISR_CPOL_CPHA_ERROR) /* DGIER bit definitions */ #define DGIER_GIE BIT(31) /* IPISR and IPIER bit definitions */ #define IPIXR_MODF BIT(0) #define IPIXR_SLAVE_MODF BIT(1) #define IPIXR_DTR_EMPTY BIT(2) #define IPIXR_DTR_UNDERRUN BIT(3) #define IPIXR_DRR_FULL BIT(4) #define IPIXR_DRR_OVERRUN BIT(5) #define IPIXR_TX_FIFO_HALF_EMPTY BIT(6) #define IPIXR_SLAVE_MODE_SELECT BIT(7) #define IPIXR_DDR_NOT_EMPTY BIT(8) #define IPIXR_CPOL_CPHA_ERROR BIT(9) #define IPIXR_SLAVE_MODE_ERROR BIT(10) #define IPIXR_MSB_ERROR BIT(11) #define IPIXR_LOOPBACK_ERROR BIT(12) #define IPIXR_COMMAND_ERROR BIT(13) struct xlnx_quadspi_config { mm_reg_t base; void (*irq_config_func)(const struct device *dev); uint8_t num_ss_bits; uint8_t num_xfer_bytes; }; struct xlnx_quadspi_data { struct spi_context ctx; }; static inline uint32_t xlnx_quadspi_read32(const struct device *dev, mm_reg_t offset) { const struct xlnx_quadspi_config *config = dev->config; return sys_read32(config->base + offset); } static inline void xlnx_quadspi_write32(const struct device *dev, uint32_t value, mm_reg_t offset) { const struct xlnx_quadspi_config *config = dev->config; sys_write32(value, config->base + offset); } static void xlnx_quadspi_cs_control(const struct device *dev, bool on) { const struct xlnx_quadspi_config *config = dev->config; struct xlnx_quadspi_data *data = dev->data; struct spi_context *ctx = &data->ctx; uint32_t spissr = BIT_MASK(config->num_ss_bits); if (IS_ENABLED(CONFIG_SPI_SLAVE) && spi_context_is_slave(ctx)) { /* Skip slave select assert/de-assert in slave mode */ return; } if (on) { /* SPISSR is one-hot, active-low */ spissr &= ~BIT(ctx->config->slave); } else if (ctx->config->operation & SPI_HOLD_ON_CS) { /* Skip slave select de-assert */ return; } xlnx_quadspi_write32(dev, spissr, SPISSR_OFFSET); spi_context_cs_control(ctx, on); } static int xlnx_quadspi_configure(const struct device *dev, const struct spi_config *spi_cfg) { const struct xlnx_quadspi_config *config = dev->config; struct xlnx_quadspi_data *data = dev->data; struct spi_context *ctx = &data->ctx; uint32_t word_size; uint32_t spicr; uint32_t spisr; if (spi_context_configured(ctx, spi_cfg)) { /* Configuration already active, just enable SPI IOs */ spicr = xlnx_quadspi_read32(dev, SPICR_OFFSET); spicr |= SPICR_SPE; xlnx_quadspi_write32(dev, spicr, SPICR_OFFSET); return 0; } if (spi_cfg->slave >= config->num_ss_bits) { LOG_ERR("unsupported slave %d, num_ss_bits %d", spi_cfg->slave, config->num_ss_bits); return -ENOTSUP; } if (spi_cfg->operation & SPI_CS_ACTIVE_HIGH) { LOG_ERR("unsupported CS polarity active high"); return -ENOTSUP; } if (!IS_ENABLED(CONFIG_SPI_SLAVE) && \ (spi_cfg->operation & SPI_OP_MODE_SLAVE)) { LOG_ERR("slave mode support not enabled"); return -ENOTSUP; } word_size = SPI_WORD_SIZE_GET(spi_cfg->operation); if (word_size != (config->num_xfer_bytes * 8)) { LOG_ERR("unsupported word size %d bits, num_xfer_bytes %d", word_size, config->num_xfer_bytes); return -ENOTSUP; } /* Reset FIFOs, SPI IOs enabled */ spicr = SPICR_TX_FIFO_RESET | SPICR_RX_FIFO_RESET | SPICR_SPE; /* Master mode, inhibit master transmit, manual slave select */ if (!IS_ENABLED(CONFIG_SPI_SLAVE) || (spi_cfg->operation & SPI_OP_MODE_SLAVE) == 0U) { spicr |= SPICR_MASTER | SPICR_MASTER_XFER_INH | SPICR_MANUAL_SS; } if (spi_cfg->operation & SPI_MODE_CPOL) { spicr |= SPICR_CPOL; } if (spi_cfg->operation & SPI_MODE_CPHA) { spicr |= SPICR_CPHA; } if (spi_cfg->operation & SPI_MODE_LOOP) { spicr |= SPICR_LOOP; } if (spi_cfg->operation & SPI_TRANSFER_LSB) { spicr |= SPICR_LSB_FIRST; } /* * Write configuration and verify it is compliant with the IP core * configuration. Tri-state SPI IOs on error. */ xlnx_quadspi_write32(dev, spicr, SPICR_OFFSET); spisr = xlnx_quadspi_read32(dev, SPISR_OFFSET); if (spisr & SPISR_ERROR_MASK) { LOG_ERR("unsupported configuration, spisr = 0x%08x", spisr); xlnx_quadspi_write32(dev, SPICR_MASTER_XFER_INH, SPICR_OFFSET); ctx->config = NULL; return -ENOTSUP; } ctx->config = spi_cfg; if (!IS_ENABLED(CONFIG_SPI_SLAVE) || !spi_context_is_slave(ctx)) { spi_context_cs_configure(ctx); } return 0; } static void xlnx_quadspi_start_tx(const struct device *dev) { const struct xlnx_quadspi_config *config = dev->config; struct xlnx_quadspi_data *data = dev->data; struct spi_context *ctx = &data->ctx; size_t xfer_len; uint32_t spicr = 0U; uint32_t spisr; uint32_t dtr = 0U; if (!spi_context_tx_on(ctx) && !spi_context_rx_on(ctx)) { /* All done, de-assert slave select */ xlnx_quadspi_cs_control(dev, false); if ((ctx->config->operation & SPI_HOLD_ON_CS) == 0U) { /* Tri-state SPI IOs */ spicr = xlnx_quadspi_read32(dev, SPICR_OFFSET); spicr &= ~(SPICR_SPE); xlnx_quadspi_write32(dev, spicr, SPICR_OFFSET); } spi_context_complete(ctx, 0); return; } if (!IS_ENABLED(CONFIG_SPI_SLAVE) || !spi_context_is_slave(ctx)) { /* Inhibit master transaction while writing TX data */ spicr = xlnx_quadspi_read32(dev, SPICR_OFFSET); spicr |= SPICR_MASTER_XFER_INH; xlnx_quadspi_write32(dev, spicr, SPICR_OFFSET); } /* We can only see as far as the current rx buffer */ xfer_len = spi_context_longest_current_buf(ctx); /* Write TX data */ while (xfer_len--) { if (spi_context_tx_buf_on(ctx)) { switch (config->num_xfer_bytes) { case 1: dtr = UNALIGNED_GET((uint8_t *)(ctx->tx_buf)); break; case 2: dtr = UNALIGNED_GET((uint16_t *)(ctx->tx_buf)); break; case 4: dtr = UNALIGNED_GET((uint32_t *)(ctx->tx_buf)); break; default: __ASSERT(0, "unsupported num_xfer_bytes"); } } else { /* No TX buffer. Use dummy TX data */ dtr = 0U; } xlnx_quadspi_write32(dev, dtr, SPI_DTR_OFFSET); spi_context_update_tx(ctx, config->num_xfer_bytes, 1); spisr = xlnx_quadspi_read32(dev, SPISR_OFFSET); if (spisr & SPISR_TX_FULL) { break; } } spisr = xlnx_quadspi_read32(dev, SPISR_OFFSET); if (spisr & SPISR_COMMAND_ERROR) { /* Command not supported by memory type configured in IP core */ LOG_ERR("unsupported command"); xlnx_quadspi_cs_control(dev, false); spicr = xlnx_quadspi_read32(dev, SPICR_OFFSET); if ((ctx->config->operation & SPI_HOLD_ON_CS) == 0U) { /* Tri-state SPI IOs */ spicr &= ~(SPICR_SPE); } xlnx_quadspi_write32(dev, spicr | SPICR_TX_FIFO_RESET, SPICR_OFFSET); spi_context_complete(ctx, -ENOTSUP); } if (!IS_ENABLED(CONFIG_SPI_SLAVE) || !spi_context_is_slave(ctx)) { /* Uninhibit master transaction */ spicr &= ~(SPICR_MASTER_XFER_INH); xlnx_quadspi_write32(dev, spicr, SPICR_OFFSET); } } static int xlnx_quadspi_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, bool async, struct k_poll_signal *signal) { const struct xlnx_quadspi_config *config = dev->config; struct xlnx_quadspi_data *data = dev->data; struct spi_context *ctx = &data->ctx; int ret; spi_context_lock(ctx, async, signal, spi_cfg); ret = xlnx_quadspi_configure(dev, spi_cfg); if (ret) { goto out; } spi_context_buffers_setup(ctx, tx_bufs, rx_bufs, config->num_xfer_bytes); xlnx_quadspi_cs_control(dev, true); xlnx_quadspi_start_tx(dev); ret = spi_context_wait_for_completion(ctx); out: spi_context_release(ctx, ret); return ret; } static int xlnx_quadspi_transceive_blocking(const struct device *dev, const struct spi_config *spi_cfg, const struct spi_buf_set *tx_bufs, const struct spi_buf_set *rx_bufs) { return xlnx_quadspi_transceive(dev, spi_cfg, tx_bufs, rx_bufs, false, NULL); } #ifdef CONFIG_SPI_ASYNC static int xlnx_quadspi_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, struct k_poll_signal *signal) { return xlnx_quadspi_transceive(dev, spi_cfg, tx_bufs, rx_bufs, true, signal); } #endif /* CONFIG_SPI_ASYNC */ static int xlnx_quadspi_release(const struct device *dev, const struct spi_config *spi_cfg) { const struct xlnx_quadspi_config *config = dev->config; struct xlnx_quadspi_data *data = dev->data; uint32_t spicr; /* Force slave select de-assert */ xlnx_quadspi_write32(dev, BIT_MASK(config->num_ss_bits), SPISSR_OFFSET); /* Tri-state SPI IOs */ spicr = xlnx_quadspi_read32(dev, SPICR_OFFSET); spicr &= ~(SPICR_SPE); xlnx_quadspi_write32(dev, spicr, SPICR_OFFSET); spi_context_unlock_unconditionally(&data->ctx); return 0; } static void xlnx_quadspi_isr(const struct device *dev) { const struct xlnx_quadspi_config *config = dev->config; struct xlnx_quadspi_data *data = dev->data; struct spi_context *ctx = &data->ctx; uint32_t temp; uint32_t drr; /* Acknowledge interrupt */ temp = xlnx_quadspi_read32(dev, IPISR_OFFSET); xlnx_quadspi_write32(dev, temp, IPISR_OFFSET); if (temp & IPIXR_DTR_EMPTY) { temp = xlnx_quadspi_read32(dev, SPISR_OFFSET); /* Read RX data */ while (!(temp & SPISR_RX_EMPTY)) { drr = xlnx_quadspi_read32(dev, SPI_DRR_OFFSET); if (spi_context_rx_buf_on(ctx)) { switch (config->num_xfer_bytes) { case 1: UNALIGNED_PUT(drr, (uint8_t *)ctx->rx_buf); break; case 2: UNALIGNED_PUT(drr, (uint16_t *)ctx->rx_buf); break; case 4: UNALIGNED_PUT(drr, (uint32_t *)ctx->rx_buf); break; default: __ASSERT(0, "unsupported num_xfer_bytes"); } } spi_context_update_rx(ctx, config->num_xfer_bytes, 1); temp = xlnx_quadspi_read32(dev, SPISR_OFFSET); } /* Start next TX */ xlnx_quadspi_start_tx(dev); } else { LOG_WRN("unhandled interrupt, ipisr = 0x%08x", temp); } } static int xlnx_quadspi_init(const struct device *dev) { const struct xlnx_quadspi_config *config = dev->config; struct xlnx_quadspi_data *data = dev->data; /* Reset controller */ xlnx_quadspi_write32(dev, SRR_SOFTRESET_MAGIC, SRR_OFFSET); config->irq_config_func(dev); /* Enable DTR Empty interrupt */ xlnx_quadspi_write32(dev, IPIXR_DTR_EMPTY, IPIER_OFFSET); xlnx_quadspi_write32(dev, DGIER_GIE, DGIER_OFFSET); spi_context_unlock_unconditionally(&data->ctx); return 0; } static const struct spi_driver_api xlnx_quadspi_driver_api = { .transceive = xlnx_quadspi_transceive_blocking, #ifdef CONFIG_SPI_ASYNC .transceive_async = xlnx_quadspi_transceive_async, #endif /* CONFIG_SPI_ASYNC */ .release = xlnx_quadspi_release, }; #define XLNX_QUADSPI_INIT(n) \ static void xlnx_quadspi_config_func_##n(const struct device *dev); \ \ static const struct xlnx_quadspi_config xlnx_quadspi_config_##n = { \ .base = DT_INST_REG_ADDR(n), \ .irq_config_func = xlnx_quadspi_config_func_##n, \ .num_ss_bits = DT_INST_PROP(n, xlnx_num_ss_bits), \ .num_xfer_bytes = \ DT_INST_PROP(n, xlnx_num_transfer_bits) / 8, \ }; \ \ static struct xlnx_quadspi_data xlnx_quadspi_data_##n = { \ SPI_CONTEXT_INIT_LOCK(xlnx_quadspi_data_##n, ctx), \ SPI_CONTEXT_INIT_SYNC(xlnx_quadspi_data_##n, ctx), \ }; \ \ DEVICE_DT_INST_DEFINE(n, &xlnx_quadspi_init, \ NULL, \ &xlnx_quadspi_data_##n, \ &xlnx_quadspi_config_##n, POST_KERNEL, \ CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \ &xlnx_quadspi_driver_api); \ \ static void xlnx_quadspi_config_func_##n(const struct device *dev) \ { \ IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), \ xlnx_quadspi_isr, \ DEVICE_DT_INST_GET(n), 0); \ irq_enable(DT_INST_IRQN(n)); \ } DT_INST_FOREACH_STATUS_OKAY(XLNX_QUADSPI_INIT)