text: /*
   * Copyright (c) 2020 Henrik Brix Andersen <henrik@brixandersen.dk>
   *
   * SPDX-License-Identifier: Apache-2.0
   */
  
  #define DT_DRV_COMPAT xlnx_xps_spi_2_00_a
  
  #include <device.h>
  #include <drivers/spi.h>
  #include <sys/sys_io.h>
  #include <logging/log.h>
  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->operation & SPI_HALF_DUPLEX) {
  		LOG_ERR("Half-duplex not supported");
  		return -ENOTSUP;
  	}
  
  	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;
  
  	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)
  {
  	int err;
  	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);
  
  	err = spi_context_cs_configure_all(&data->ctx);
  	if (err < 0) {
  		return err;
  	}
  
  	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),	\
  		SPI_CONTEXT_CS_GPIOS_INITIALIZE(DT_DRV_INST(n), ctx)	\
  	};								\
  									\
  	DEVICE_DT_INST_DEFINE(n, &xlnx_quadspi_init,			\
  			    NULL,					\
  			    &xlnx_quadspi_data_##n,			\
  			    &xlnx_quadspi_config_##n, POST_KERNEL,	\
  			    CONFIG_SPI_INIT_PRIORITY,			\
  			    &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)
  