Different HW implement different variants of SPI based flow control (FC). To flexible FC implementation a spited it to fallowing common parts: Flow control: Request Sequence Master CS |-------2\_____________________| Slave FC |-----1\_______________________| DATA |-----------3\_________________| Flow control: Ready Sequence Master CS |-----1\_______________________| Slave FC |--------2\____________________| DATA |-----------3\_________________| Flow control: ACK End of Data Master CS |______________________/2------| Slave FC |________________________/3----| DATA |__________________/1----------| Flow control: Pause Master CS |_______________________/------| Slave FC |_______1/-----\3______/-------| DATA |________2/------\4___/--------| Signed-off-by: Oleksij Rempel <linux@xxxxxxxxxxxxxxxx> --- drivers/spi/spi-davinci.c | 10 ++-- drivers/spi/spi-sun4i.c | 3 +- drivers/spi/spi.c | 120 +++++++++++++++++++++++++++++++++++++++++++++- include/linux/spi/spi.h | 51 +++++++++++++++++++- 4 files changed, 175 insertions(+), 9 deletions(-) diff --git a/drivers/spi/spi-davinci.c b/drivers/spi/spi-davinci.c index fddb7a3..8728df9 100644 --- a/drivers/spi/spi-davinci.c +++ b/drivers/spi/spi-davinci.c @@ -351,8 +351,8 @@ static int davinci_spi_setup_transfer(struct spi_device *spi, * * Version 2 hardware supports an optional handshaking signal, * so it can support two more modes: - * - 5 pin SPI variant is standard SPI plus SPI_READY - * - 4 pin with enable is (SPI_READY | SPI_NO_CS) + * - 5 pin SPI variant is standard SPI plus SPI_FC_READY + * - 4 pin with enable is (SPI_FC_READY | SPI_NO_CS) */ if (dspi->version == SPI_VERSION_2) { @@ -374,7 +374,7 @@ static int davinci_spi_setup_transfer(struct spi_device *spi, & SPIDELAY_T2CDELAY_MASK; } - if (spi->mode & SPI_READY) { + if (spi->mode & SPI_FC_READY) { spifmt |= SPIFMT_WAITENA_MASK; delay |= (spicfg->t2edelay << SPIDELAY_T2EDELAY_SHIFT) & SPIDELAY_T2EDELAY_MASK; @@ -452,7 +452,7 @@ static int davinci_spi_setup(struct spi_device *spi) set_io_bits(dspi->base + SPIPC0, 1 << spi->chip_select); } - if (spi->mode & SPI_READY) + if (spi->mode & SPI_FC_READY) set_io_bits(dspi->base + SPIPC0, SPIPC0_SPIENA_MASK); if (spi->mode & SPI_LOOP) @@ -1021,7 +1021,7 @@ static int davinci_spi_probe(struct platform_device *pdev) dspi->bitbang.flags = SPI_NO_CS | SPI_LSB_FIRST | SPI_LOOP; if (dspi->version == SPI_VERSION_2) - dspi->bitbang.flags |= SPI_READY; + dspi->bitbang.flags |= SPI_FC_HW_ONLY | SPI_FC_READY | SPI_FC_PAUSE; if (pdev->dev.of_node) { int i; diff --git a/drivers/spi/spi-sun4i.c b/drivers/spi/spi-sun4i.c index 1ddd9e2..2443728 100644 --- a/drivers/spi/spi-sun4i.c +++ b/drivers/spi/spi-sun4i.c @@ -390,7 +390,8 @@ static int sun4i_spi_probe(struct platform_device *pdev) master->set_cs = sun4i_spi_set_cs; master->transfer_one = sun4i_spi_transfer_one; master->num_chipselect = 4; - master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH | SPI_LSB_FIRST; + master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH | SPI_LSB_FIRST | + SPI_FC_REQUEST | SPI_FC_READY | SPI_FC_STOP_ACK; master->bits_per_word_mask = SPI_BPW_MASK(8); master->dev.of_node = pdev->dev.of_node; master->auto_runtime_pm = true; diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c index 47eff80..3fac4f7 100644 --- a/drivers/spi/spi.c +++ b/drivers/spi/spi.c @@ -37,6 +37,8 @@ #include <linux/kthread.h> #include <linux/ioport.h> #include <linux/acpi.h> +#include <linux/gpio/consumer.h> +#include <linux/interrupt.h> #define CREATE_TRACE_POINTS #include <trace/events/spi.h> @@ -396,6 +398,89 @@ int __spi_register_driver(struct module *owner, struct spi_driver *sdrv) EXPORT_SYMBOL_GPL(__spi_register_driver); /*-------------------------------------------------------------------------*/ +/* + * SPI flow control + */ +int spi_fc_wait_rq(struct spi_device *spi, u32 fc_state) +{ + unsigned long timeout = msecs_to_jiffies(20); + int fc_enabled; + int ret = 1; + + if (spi->mode & fc_state) { + fc_enabled = gpiod_get_value(spi->fc_gpio); + if (spi->cs_enabled != fc_enabled) { + ret = wait_for_completion_io_timeout(&spi->fc_complete, + timeout); + } + if (!ret) + dev_warn(&spi->dev, "FC timeout: requested state: 0x%x\n", fc_state); + } + + return ret; +} + +static irqreturn_t spi_fc_rq(int irq, void *dev_id) +{ + struct spi_device *spi = (struct spi_device *)dev_id; + int fc_enabled; + + fc_enabled = gpiod_get_value(spi->fc_gpio); + + if (spi->mode | SPI_FC_REQUEST && + !spi->cs_enabled && fc_enabled) { + if (spi->request_cb) + spi->request_cb(spi); + } else if (spi->mode | SPI_FC_STOP_ACK && + !spi->cs_enabled && !fc_enabled) { + complete(&spi->fc_complete); + } else if (spi->mode | SPI_FC_READY && + spi->cs_enabled && fc_enabled) { + complete(&spi->fc_complete); + } else { + dev_warn(&spi->dev, "Wrong 5W State. CS:%i, 5W:%i, Mode:0x%x\n", + spi->cs_enabled, fc_enabled, spi->mode); + } + + return IRQ_HANDLED; +} + +/* spi_fc_probe should be called by spi_device driver. */ +int spi_fc_probe(struct spi_device *spi) +{ + struct device_node *np = spi->dev.of_node; + int ret; + + if (!np) + return 0; + + if (!(spi->mode & SPI_FC_MASK) || spi->mode & SPI_FC_HW_ONLY) + return 0; + + spi->fc_gpio = devm_gpiod_get(&spi->dev, "fc", GPIOD_IN); + if (IS_ERR(spi->fc_gpio)) { + ret = PTR_ERR(spi->fc_gpio); + dev_err(&spi->dev, "Failed to request FC GPIO: %d\n", ret); + return ret; + } + + init_completion(&spi->fc_complete); + snprintf(spi->fc_irq_name, sizeof(spi->fc_irq_name), "spi-fc-%s", + dev_name(&spi->dev)); + ret = devm_request_irq(&spi->dev, gpiod_to_irq(spi->fc_gpio), + spi_fc_rq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + spi->fc_irq_name, spi); + if (ret) { + dev_err(&spi->dev, "Failed to request FC IRQ\n"); + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(spi_fc_probe); + +/*-------------------------------------------------------------------------*/ /* SPI devices should normally not be created by SPI device drivers; that * would make them board-specific. Similarly with SPI master drivers. @@ -687,6 +772,9 @@ int spi_register_board_info(struct spi_board_info const *info, unsigned n) static void spi_set_cs(struct spi_device *spi, bool enable) { + spi->cs_enabled = enable; + reinit_completion(&spi->fc_complete); + if (spi->mode & SPI_CS_HIGH) enable = !enable; @@ -941,9 +1029,15 @@ static int spi_transfer_one_message(struct spi_master *master, unsigned long ms = 1; struct spi_statistics *statm = &master->statistics; struct spi_statistics *stats = &msg->spi->statistics; + struct spi_device *spi = msg->spi; spi_set_cs(msg->spi, true); + if (!spi_fc_wait_rq(spi, SPI_FC_READY)) { + ret = -EREMOTEIO; + goto out; + } + SPI_STATISTICS_INCREMENT_FIELD(statm, messages); SPI_STATISTICS_INCREMENT_FIELD(stats, messages); @@ -1006,8 +1100,19 @@ static int spi_transfer_one_message(struct spi_master *master, keep_cs = true; } else { spi_set_cs(msg->spi, false); + + if (!spi_fc_wait_rq(spi, SPI_FC_STOP_ACK)) { + ret = -EREMOTEIO; + break; + } + udelay(10); spi_set_cs(msg->spi, true); + + if (!spi_fc_wait_rq(spi, SPI_FC_READY)) { + ret = -EREMOTEIO; + break; + } } } @@ -1015,8 +1120,12 @@ static int spi_transfer_one_message(struct spi_master *master, } out: - if (ret != 0 || !keep_cs) + + if (ret != 0 || !keep_cs) { spi_set_cs(msg->spi, false); + if (ret != -EREMOTEIO && !spi_fc_wait_rq(spi, SPI_FC_STOP_ACK)) + ret = -EREMOTEIO; + } if (msg->status == -EINPROGRESS) msg->status = ret; @@ -1445,6 +1554,7 @@ of_register_spi_device(struct spi_master *master, struct device_node *nc) int rc; u32 value; + printk("%s:%i\n", __func__, __LINE__); /* Alloc an spi_device */ spi = spi_alloc_device(master); if (!spi) { @@ -1483,6 +1593,14 @@ of_register_spi_device(struct spi_master *master, struct device_node *nc) spi->mode |= SPI_3WIRE; if (of_find_property(nc, "spi-lsb-first", NULL)) spi->mode |= SPI_LSB_FIRST; + if (of_find_property(nc, "spi-fc-ready", NULL)) + spi->mode |= SPI_FC_READY; + if (of_find_property(nc, "spi-fc-stop-ack", NULL)) + spi->mode |= SPI_FC_STOP_ACK; + if (of_find_property(nc, "spi-fc-pause", NULL)) + spi->mode |= SPI_FC_PAUSE; + if (of_find_property(nc, "spi-fc-request", NULL)) + spi->mode |= SPI_FC_REQUEST; /* Device DUAL/QUAD mode */ if (!of_property_read_u32(nc, "spi-tx-bus-width", &value)) { diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h index 53be3a4..0a24688 100644 --- a/include/linux/spi/spi.h +++ b/include/linux/spi/spi.h @@ -148,16 +148,52 @@ struct spi_device { #define SPI_3WIRE 0x10 /* SI/SO signals shared */ #define SPI_LOOP 0x20 /* loopback mode */ #define SPI_NO_CS 0x40 /* 1 dev/bus, no chipselect */ -#define SPI_READY 0x80 /* slave pulls low to pause */ +/* + * Flow control: Ready Sequence (SPI_FC_READY) + * Master CS |-----1\_______________________| + * Slave FC |--------2\____________________| + * DATA |-----------3\_________________| + * 1. Chips Select set to active by Master. + * 2. Flow Control set to active by Slave. + * 3. Master starting Data transmission. + */ +#define SPI_FC_READY 0x80 #define SPI_TX_DUAL 0x100 /* transmit with 2 wires */ #define SPI_TX_QUAD 0x200 /* transmit with 4 wires */ #define SPI_RX_DUAL 0x400 /* receive with 2 wires */ #define SPI_RX_QUAD 0x800 /* receive with 4 wires */ +/* + * Flow control: Pause (SPI_FC_PAUSE) + * Master CS |_______________________/------| + * Slave FC |_______1/-----\3______/-------| + * DATA |________2/------\4_____/------| + */ +#define SPI_FC_PAUSE 0x1000 +/* + * Flow control: ACK End of Data (SPI_FC_STOP_ACK) + * Master CS |______________________/2------| + * Slave FC |________________________/3----| + * DATA |__________________/1----------| + */ +#define SPI_FC_STOP_ACK 0x2000 +/* + * Flow control: Request Sequence (SPI_FC_REQUEST) + * Master CS |-------2\_____________________| + * Slave FC |-----1\_______________________| + * DATA |-----------3\_________________| + */ +#define SPI_FC_REQUEST 0x4000 +/* If complete FC is done by HW or controller driver, set this flag */ +#define SPI_FC_HW_ONLY 0x8000 +#define SPI_FC_MASK (SPI_FC_READY | SPI_FC_PAUSE | \ + SPI_FC_STOP_ACK | SPI_FC_REQUEST) + int irq; void *controller_state; void *controller_data; char modalias[SPI_NAME_SIZE]; int cs_gpio; /* chip select gpio */ + int cs_enabled; /* the statistics */ struct spi_statistics statistics; @@ -171,6 +207,11 @@ struct spi_device { * - chipselect delays * - ... */ + + void (*request_cb)(struct spi_device *spi); + struct completion fc_complete; + struct gpio_desc *fc_gpio; /* request gpio */ + char fc_irq_name[32]; }; static inline struct spi_device *to_spi_device(struct device *dev) @@ -282,7 +323,6 @@ static inline void spi_unregister_driver(struct spi_driver *sdrv) #define module_spi_driver(__spi_driver) \ module_driver(__spi_driver, spi_register_driver, \ spi_unregister_driver) - /** * struct spi_master - interface to SPI master controller * @dev: device interface to this driver @@ -537,6 +577,10 @@ struct spi_master { /* dummy data for full duplex devices */ void *dummy_rx; void *dummy_tx; + + int (*wait_for_rq)(struct spi_device *spi); +#define B5SPI_RQ_ACTIVE 1 /* normally request line is active low */ +#define B5SPI_RQ_INACTIVE 0 }; static inline void *spi_master_get_devdata(struct spi_master *master) @@ -1146,4 +1190,7 @@ spi_transfer_is_last(struct spi_master *master, struct spi_transfer *xfer) return list_is_last(&xfer->transfer_list, &master->cur_msg->transfers); } +int spi_fc_wait_rq(struct spi_device *spi, u32 s5w_state); +int spi_fc_probe(struct spi_device *spi); + #endif /* __LINUX_SPI_H */ -- 1.9.1 -- To unsubscribe from this list: send the line "unsubscribe linux-spi" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html