Unverified Commit c8bec335 authored by David Lechner's avatar David Lechner Committed by Mark Brown
Browse files

spi: move split xfers for CS_WORD emulation



This moves splitting transfers for CS_WORD software emulation to the
same place where we split transfers for controller-specific reasons.

This fixes a few subtle bugs.

The calculation for maxsize was wrong for bit sizes between 17 and 24.
This is fixed by making use of spi_split_transfers_maxwords() which
already has the correct calculation.

Also, since this indirectly calls spi_res_alloc(), to avoid leaking
resources, spi_finalize_current_message() would need to be called
on all error paths in __spi_validate() and callers of __spi_validate()
would need to do the same. This is fixed by moving the call to
__spi_pump_transfer_message() where it is already splitting transfers
for other reasons and correctly releases resources in the subsequent
error paths.

Fixes: cbaa62e0 ("spi: add software implementation for SPI_CS_WORD")
Signed-off-by: default avatarDavid Lechner <dlechner@baylibre.com>
Link: https://lore.kernel.org/r/20240126212358.3916280-2-dlechner@baylibre.com


Signed-off-by: default avatarMark Brown <broonie@kernel.org>
parent 1e942b5b
Loading
Loading
Loading
Loading
+31 −32
Original line number Diff line number Diff line
@@ -1743,6 +1743,29 @@ static int __spi_pump_transfer_message(struct spi_controller *ctlr,

	trace_spi_message_start(msg);

	/*
	 * If an SPI controller does not support toggling the CS line on each
	 * transfer (indicated by the SPI_CS_WORD flag) or we are using a GPIO
	 * for the CS line, we can emulate the CS-per-word hardware function by
	 * splitting transfers into one-word transfers and ensuring that
	 * cs_change is set for each transfer.
	 */
	if ((msg->spi->mode & SPI_CS_WORD) && (!(ctlr->mode_bits & SPI_CS_WORD) ||
					       spi_is_csgpiod(msg->spi))) {
		ret = spi_split_transfers_maxwords(ctlr, msg, 1, GFP_KERNEL);
		if (ret) {
			msg->status = ret;
			spi_finalize_current_message(ctlr);
			return ret;
		}

		list_for_each_entry(xfer, &msg->transfers, transfer_list) {
			/* Don't change cs_change on the last entry in the list */
			if (list_is_last(&xfer->transfer_list, &msg->transfers))
				break;
			xfer->cs_change = 1;
		}
	} else {
		ret = spi_split_transfers_maxsize(ctlr, msg,
						  spi_max_transfer_size(msg->spi),
						  GFP_KERNEL | GFP_DMA);
@@ -1751,6 +1774,7 @@ static int __spi_pump_transfer_message(struct spi_controller *ctlr,
			spi_finalize_current_message(ctlr);
			return ret;
		}
	}

	if (ctlr->prepare_message) {
		ret = ctlr->prepare_message(ctlr, msg);
@@ -4061,31 +4085,6 @@ static int __spi_validate(struct spi_device *spi, struct spi_message *message)

	message->spi = spi;

	/*
	 * If an SPI controller does not support toggling the CS line on each
	 * transfer (indicated by the SPI_CS_WORD flag) or we are using a GPIO
	 * for the CS line, we can emulate the CS-per-word hardware function by
	 * splitting transfers into one-word transfers and ensuring that
	 * cs_change is set for each transfer.
	 */
	if ((spi->mode & SPI_CS_WORD) && (!(ctlr->mode_bits & SPI_CS_WORD) ||
					  spi_is_csgpiod(spi))) {
		size_t maxsize = BITS_TO_BYTES(spi->bits_per_word);
		int ret;

		ret = spi_split_transfers_maxsize(ctlr, message, maxsize,
						  GFP_KERNEL);
		if (ret)
			return ret;

		list_for_each_entry(xfer, &message->transfers, transfer_list) {
			/* Don't change cs_change on the last entry in the list */
			if (list_is_last(&xfer->transfer_list, &message->transfers))
				break;
			xfer->cs_change = 1;
		}
	}

	/*
	 * Half-duplex links include original MicroWire, and ones with
	 * only one data pin like SPI_3WIRE (switches direction) or where