diff options
author | Mark Brown <broonie@kernel.org> | 2023-10-10 19:23:42 +0300 |
---|---|---|
committer | Mark Brown <broonie@kernel.org> | 2023-10-10 19:23:42 +0300 |
commit | 8097dbd4b631b1f4cfe3def909f828b071d99ad7 (patch) | |
tree | cd964fedd4285b1303b4e6111dfb011ea81e8b4c /drivers | |
parent | 9aaa25df9b02bfe5579cbc9b4cc1177c662ec33f (diff) | |
parent | a4f7ef6db74197898c48236ad01f8e0eccc1e52b (diff) | |
download | linux-8097dbd4b631b1f4cfe3def909f828b071d99ad7.tar.xz |
spi: Add RZ/V2M CSI target support
Merge series from Fabrizio Castro <fabrizio.castro.jz@renesas.com>:
The CSI IP found inside the Renesas RZ/V2M SoC supports both SPI host
and target. This series extends the CSI dt-bindings and driver to
add SPI target support.
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/spi/Kconfig | 3 | ||||
-rw-r--r-- | drivers/spi/spi-rzv2m-csi.c | 127 |
2 files changed, 85 insertions, 45 deletions
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 2c21d5b96fdc..d4ac184bce95 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -862,7 +862,8 @@ config SPI_RZV2M_CSI tristate "Renesas RZ/V2M CSI controller" depends on ARCH_RENESAS || COMPILE_TEST help - SPI driver for Renesas RZ/V2M Clocked Serial Interface (CSI) + SPI driver for Renesas RZ/V2M Clocked Serial Interface (CSI). + CSI supports both SPI host and SPI target roles. config SPI_QCOM_QSPI tristate "QTI QSPI controller" diff --git a/drivers/spi/spi-rzv2m-csi.c b/drivers/spi/spi-rzv2m-csi.c index d0f51b17aa7c..741e0f44c49c 100644 --- a/drivers/spi/spi-rzv2m-csi.c +++ b/drivers/spi/spi-rzv2m-csi.c @@ -11,6 +11,7 @@ #include <linux/interrupt.h> #include <linux/iopoll.h> #include <linux/log2.h> +#include <linux/of.h> #include <linux/platform_device.h> #include <linux/property.h> #include <linux/reset.h> @@ -38,6 +39,9 @@ #define CSI_MODE_SETUP 0x00000040 /* CSI_CLKSEL */ +#define CSI_CLKSEL_SS_ENA BIT(19) +#define CSI_CLKSEL_SS_POL BIT(18) +#define CSI_CLKSEL_SS (CSI_CLKSEL_SS_ENA | CSI_CLKSEL_SS_POL) #define CSI_CLKSEL_CKP BIT(17) #define CSI_CLKSEL_DAP BIT(16) #define CSI_CLKSEL_MODE (CSI_CLKSEL_CKP|CSI_CLKSEL_DAP) @@ -82,6 +86,10 @@ #define CSI_MAX_SPI_SCKO (8 * HZ_PER_MHZ) +#define CSI_CLKSEL_SS_DISABLED 0 +#define CSI_CLKSEL_SS_ENABLED_ACTIVE_LOW BIT(1) +#define CSI_CLKSEL_SS_ENABLED_ACTIVE_HIGH GENMASK(1, 0) + struct rzv2m_csi_priv { void __iomem *base; struct clk *csiclk; @@ -99,6 +107,8 @@ struct rzv2m_csi_priv { wait_queue_head_t wait; u32 errors; u32 status; + bool target_aborted; + bool use_ss_pin; }; static void rzv2m_csi_reg_write_bit(const struct rzv2m_csi_priv *csi, @@ -193,6 +203,14 @@ static int rzv2m_csi_read_rxfifo(struct rzv2m_csi_priv *csi) return 0; } +static inline void rzv2m_csi_empty_rxfifo(struct rzv2m_csi_priv *csi) +{ + unsigned int i; + + for (i = 0; i < csi->words_to_transfer; i++) + readl(csi->base + CSI_IFIFO); +} + static inline void rzv2m_csi_calc_current_transfer(struct rzv2m_csi_priv *csi) { unsigned int bytes_transferred = max(csi->bytes_received, csi->bytes_sent); @@ -279,32 +297,23 @@ static int rzv2m_csi_wait_for_interrupt(struct rzv2m_csi_priv *csi, rzv2m_csi_enable_irqs(csi, enable_bits); - ret = wait_event_timeout(csi->wait, - ((csi->status & wait_mask) == wait_mask) || - csi->errors, HZ); + if (spi_controller_is_target(csi->controller)) { + ret = wait_event_interruptible(csi->wait, + ((csi->status & wait_mask) == wait_mask) || + csi->errors || csi->target_aborted); + if (ret || csi->target_aborted) + ret = -EINTR; + } else { + ret = wait_event_timeout(csi->wait, + ((csi->status & wait_mask) == wait_mask) || + csi->errors, HZ) == 0 ? -ETIMEDOUT : 0; + } rzv2m_csi_disable_irqs(csi, enable_bits); if (csi->errors) return -EIO; - if (!ret) - return -ETIMEDOUT; - - return 0; -} - -static int rzv2m_csi_wait_for_tx_empty(struct rzv2m_csi_priv *csi) -{ - int ret; - - if (readl(csi->base + CSI_OFIFOL) == 0) - return 0; - - ret = rzv2m_csi_wait_for_interrupt(csi, CSI_INT_TREND, CSI_CNT_TREND_E); - if (ret == -ETIMEDOUT) - csi->errors |= TX_TIMEOUT_ERROR; - return ret; } @@ -312,7 +321,7 @@ static inline int rzv2m_csi_wait_for_rx_ready(struct rzv2m_csi_priv *csi) { int ret; - if (readl(csi->base + CSI_IFIFOL) == csi->bytes_to_transfer) + if (readl(csi->base + CSI_IFIFOL) >= csi->bytes_to_transfer) return 0; ret = rzv2m_csi_wait_for_interrupt(csi, CSI_INT_R_TRGR, @@ -388,6 +397,7 @@ static void rzv2m_csi_setup_operating_mode(struct rzv2m_csi_priv *csi, static int rzv2m_csi_setup(struct spi_device *spi) { struct rzv2m_csi_priv *csi = spi_controller_get_devdata(spi->controller); + u32 slave_selection = CSI_CLKSEL_SS_DISABLED; int ret; rzv2m_csi_sw_reset(csi, 0); @@ -402,8 +412,17 @@ static int rzv2m_csi_setup(struct spi_device *spi) rzv2m_csi_reg_write_bit(csi, CSI_MODE, CSI_MODE_DIR, !!(spi->mode & SPI_LSB_FIRST)); - /* Set the operation mode as master */ - rzv2m_csi_reg_write_bit(csi, CSI_CLKSEL, CSI_CLKSEL_SLAVE, 0); + /* Set the role, 1 for target and 0 for host */ + rzv2m_csi_reg_write_bit(csi, CSI_CLKSEL, CSI_CLKSEL_SLAVE, + !!spi_controller_is_target(csi->controller)); + + if (csi->use_ss_pin) + slave_selection = spi->mode & SPI_CS_HIGH ? + CSI_CLKSEL_SS_ENABLED_ACTIVE_HIGH : + CSI_CLKSEL_SS_ENABLED_ACTIVE_LOW; + + /* Configure the slave selection (SS) pin */ + rzv2m_csi_reg_write_bit(csi, CSI_CLKSEL, CSI_CLKSEL_SS, slave_selection); /* Give the IP a SW reset */ ret = rzv2m_csi_sw_reset(csi, 1); @@ -431,9 +450,13 @@ static int rzv2m_csi_pio_transfer(struct rzv2m_csi_priv *csi) /* Make sure the TX FIFO is empty */ writel(0, csi->base + CSI_OFIFOL); + /* Make sure the RX FIFO is empty */ + writel(0, csi->base + CSI_IFIFOL); + csi->bytes_sent = 0; csi->bytes_received = 0; csi->errors = 0; + csi->target_aborted = false; rzv2m_csi_disable_all_irqs(csi); rzv2m_csi_clear_all_irqs(csi); @@ -452,28 +475,21 @@ static int rzv2m_csi_pio_transfer(struct rzv2m_csi_priv *csi) rzv2m_csi_enable_irqs(csi, CSI_INT_OVERF | CSI_INT_UNDER); - /* Make sure the RX FIFO is empty */ - writel(0, csi->base + CSI_IFIFOL); - writel(readl(csi->base + CSI_INT), csi->base + CSI_INT); csi->status = 0; - rzv2m_csi_start_stop_operation(csi, 1, false); - /* TX */ if (csi->txbuf) { ret = rzv2m_csi_fill_txfifo(csi); if (ret) break; - ret = rzv2m_csi_wait_for_tx_empty(csi); - if (ret) - break; - if (csi->bytes_sent == csi->buffer_len) tx_completed = true; } + rzv2m_csi_start_stop_operation(csi, 1, false); + /* * Make sure the RX FIFO contains the desired number of words. * We then either flush its content, or we copy it onto @@ -483,31 +499,28 @@ static int rzv2m_csi_pio_transfer(struct rzv2m_csi_priv *csi) if (ret) break; - /* RX */ - if (csi->rxbuf) { + if (!spi_controller_is_target(csi->controller)) rzv2m_csi_start_stop_operation(csi, 0, false); + /* RX */ + if (csi->rxbuf) { ret = rzv2m_csi_read_rxfifo(csi); if (ret) break; if (csi->bytes_received == csi->buffer_len) rx_completed = true; + } else { + rzv2m_csi_empty_rxfifo(csi); } - ret = rzv2m_csi_start_stop_operation(csi, 0, true); - if (ret) - goto pio_quit; - if (csi->errors) { ret = -EIO; - goto pio_quit; + break; } } rzv2m_csi_start_stop_operation(csi, 0, true); - -pio_quit: rzv2m_csi_disable_all_irqs(csi); rzv2m_csi_enable_rx_trigger(csi, false); rzv2m_csi_clear_all_irqs(csi); @@ -529,7 +542,8 @@ static int rzv2m_csi_transfer_one(struct spi_controller *controller, rzv2m_csi_setup_operating_mode(csi, transfer); - rzv2m_csi_setup_clock(csi, transfer->speed_hz); + if (!spi_controller_is_target(csi->controller)) + rzv2m_csi_setup_clock(csi, transfer->speed_hz); ret = rzv2m_csi_pio_transfer(csi); if (ret) { @@ -546,24 +560,48 @@ static int rzv2m_csi_transfer_one(struct spi_controller *controller, return ret; } +static int rzv2m_csi_target_abort(struct spi_controller *ctlr) +{ + struct rzv2m_csi_priv *csi = spi_controller_get_devdata(ctlr); + + csi->target_aborted = true; + wake_up(&csi->wait); + + return 0; +} + static int rzv2m_csi_probe(struct platform_device *pdev) { + struct device_node *np = pdev->dev.of_node; struct spi_controller *controller; struct device *dev = &pdev->dev; struct rzv2m_csi_priv *csi; struct reset_control *rstc; + bool target_mode; int irq; int ret; - controller = devm_spi_alloc_host(dev, sizeof(*csi)); + target_mode = of_property_read_bool(np, "spi-slave"); + + if (target_mode) + controller = devm_spi_alloc_target(dev, sizeof(*csi)); + else + controller = devm_spi_alloc_host(dev, sizeof(*csi)); + if (!controller) return -ENOMEM; csi = spi_controller_get_devdata(controller); platform_set_drvdata(pdev, csi); + csi->use_ss_pin = false; + if (spi_controller_is_target(controller) && + !of_property_read_bool(np, "renesas,csi-no-ss")) + csi->use_ss_pin = true; + csi->dev = dev; csi->controller = controller; + csi->target_aborted = false; csi->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(csi->base)) @@ -589,11 +627,12 @@ static int rzv2m_csi_probe(struct platform_device *pdev) init_waitqueue_head(&csi->wait); - controller->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LSB_FIRST; + controller->mode_bits = SPI_CPOL | SPI_CPHA | SPI_LSB_FIRST | SPI_CS_HIGH; controller->bits_per_word_mask = SPI_BPW_MASK(16) | SPI_BPW_MASK(8); controller->setup = rzv2m_csi_setup; controller->transfer_one = rzv2m_csi_transfer_one; controller->use_gpio_descriptors = true; + controller->target_abort = rzv2m_csi_target_abort; device_set_node(&controller->dev, dev_fwnode(dev)); |