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.
This commit is contained in:
Mark Brown 2023-10-10 17:23:42 +01:00
commit 8097dbd4b6
No known key found for this signature in database
GPG key ID: 24D68B725D5487D0
3 changed files with 94 additions and 45 deletions

View file

@ -39,6 +39,12 @@ properties:
power-domains:
maxItems: 1
renesas,csi-no-ss:
type: boolean
description:
The CSI Slave Selection (SS) pin won't be used to enable transmission and
reception. Only available when in target mode.
required:
- compatible
- reg
@ -50,6 +56,9 @@ required:
- '#address-cells'
- '#size-cells'
dependencies:
renesas,csi-no-ss: [ spi-slave ]
unevaluatedProperties: false
examples:

View file

@ -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"

View file

@ -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));