Commit bf732c6b authored by NeilBrown's avatar NeilBrown Committed by Greg Kroah-Hartman
Browse files

staging: mt7621-spi: revised half-duplex message handling



The mt7621 SPI engine has a 32 byte buffer and the driver
currently only allows 32-byte read requests and 36 bytes writes
(there is a 4byte op/addr buffer).

This is an unnecessary limitation.  As the SPI clock is controlled
by the host it is quite acceptable to send a larger message in
multiple smaller transactions.  As long as Chip Select is kept asserted
the whole time, the SPI engine can be run multiple times for
a single SPI message.

This patch factors out the transaction logic and calls for each
transfer in the message.  A write transfer might leave bytes in the
buffer to be combined with a following read transfer, as this is
a common pattern.

With this in place, we can remove the current max_transfer_size limit.

In testing, this increases the read throughput for a NOR flash chip
from 1.4MB/s to 2.3MB/s, a 50% improvement.

Signed-off-by: default avatarNeilBrown <neil@brown.name>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent a83834c1
Loading
Loading
Loading
Loading
+98 −67
Original line number Diff line number Diff line
@@ -65,6 +65,7 @@ struct mt7621_spi {
	unsigned int		sys_freq;
	unsigned int		speed;
	struct clk		*clk;
	int			pending_write;

	struct mt7621_spi_ops	*ops;
};
@@ -96,6 +97,7 @@ static void mt7621_spi_reset(struct mt7621_spi *rs, int duplex)
		master &= ~(1 << 10);

	mt7621_spi_write(rs, MT7621_SPI_MASTER, master);
	rs->pending_write = 0;
}

static void mt7621_spi_set_cs(struct spi_device *spi, int enable)
@@ -173,90 +175,124 @@ static inline int mt7621_spi_wait_till_ready(struct mt7621_spi *rs)
	return -ETIMEDOUT;
}

static int mt7621_spi_transfer_half_duplex(struct spi_master *master,
					   struct spi_message *m)
static void mt7621_spi_read_half_duplex(struct mt7621_spi *rs,
					int rx_len, u8 *buf)
{
	struct mt7621_spi *rs = spi_master_get_devdata(master);
	struct spi_device *spi = m->spi;
	unsigned int speed = spi->max_speed_hz;
	struct spi_transfer *t = NULL;
	int status = 0;
	int i, len = 0;
	int rx_len = 0;
	u32 data[9] = { 0 };
	u32 val;
	/* Combine with any pending write, and perform one or
	 * more half-duplex transactions reading 'len' bytes.
	 * Data to be written is already in MT7621_SPI_DATA*
	 */
	int tx_len = rs->pending_write;

	mt7621_spi_wait_till_ready(rs);
	rs->pending_write = 0;

	list_for_each_entry(t, &m->transfers, transfer_list) {
		const u8 *buf = t->tx_buf;
	while (rx_len || tx_len) {
		int i;
		u32 val = (min(tx_len, 4) * 8) << 24;
		int rx = min(rx_len, 32);

		if (t->rx_buf)
			rx_len += t->len;
		if (tx_len > 4)
			val |= (tx_len - 4) * 8;
		val |= (rx * 8) << 12;
		mt7621_spi_write(rs, MT7621_SPI_MOREBUF, val);

		if (!buf)
			continue;
		tx_len = 0;

		if (t->speed_hz < speed)
			speed = t->speed_hz;
		val = mt7621_spi_read(rs, MT7621_SPI_TRANS);
		val |= SPI_CTL_START;
		mt7621_spi_write(rs, MT7621_SPI_TRANS, val);

		if (WARN_ON(len + t->len > 36)) {
			status = -EIO;
			goto msg_done;
		}
		mt7621_spi_wait_till_ready(rs);

		for (i = 0; i < t->len; i++, len++)
			data[len / 4] |= buf[i] << (8 * (len & 3));
		for (i = 0; i < rx; i++) {
			if ((i % 4) == 0)
				val = mt7621_spi_read(rs, MT7621_SPI_DATA0 + i);
			*buf++ = val & 0xff;
			val >>= 8;
		}
		rx_len -= i;
	}

	if (WARN_ON(rx_len > 32)) {
		status = -EIO;
		goto msg_done;
}

	if (mt7621_spi_prepare(spi, speed)) {
		status = -EIO;
		goto msg_done;
static inline void mt7621_spi_flush(struct mt7621_spi *rs)
{
	mt7621_spi_read_half_duplex(rs, 0, NULL);
}
	data[0] = swab32(data[0]);
	if (len < 4)
		data[0] >>= (4 - len) * 8;

	for (i = 0; i < len; i += 4)
		mt7621_spi_write(rs, MT7621_SPI_OPCODE + i, data[i / 4]);
static void mt7621_spi_write_half_duplex(struct mt7621_spi *rs,
					 int tx_len, const u8 *buf)
{
	int val = 0;
	int len = rs->pending_write;

	if (len & 3) {
		val = mt7621_spi_read(rs, MT7621_SPI_OPCODE + (len & ~3));
		if (len < 4) {
			val <<= (4 - len) * 8;
			val = swab32(val);
		}
	}

	val = (min_t(int, len, 4) * 8) << 24;
	if (len > 4)
		val |= (len - 4) * 8;
	val |= (rx_len * 8) << 12;
	mt7621_spi_write(rs, MT7621_SPI_MOREBUF, val);
	while (tx_len > 0) {
		if (len >= 36) {
			rs->pending_write = len;
			mt7621_spi_flush(rs);
			len = 0;
		}

	mt7621_spi_set_cs(spi, 1);
		val |= *buf++ << (8 * (len & 3));
		len++;
		if ((len & 3) == 0) {
			if (len == 4)
				/* The byte-order of the opcode is weird! */
				val = swab32(val);
			mt7621_spi_write(rs, MT7621_SPI_OPCODE + len - 4, val);
			val = 0;
		}
		tx_len -= 1;
	}
	if (len & 3) {
		if (len < 4) {
			val = swab32(val);
			val >>= (4 - len) * 8;
		}
		mt7621_spi_write(rs, MT7621_SPI_OPCODE + (len & ~3), val);
	}
	rs->pending_write = len;
}

	val = mt7621_spi_read(rs, MT7621_SPI_TRANS);
	val |= SPI_CTL_START;
	mt7621_spi_write(rs, MT7621_SPI_TRANS, val);
static int mt7621_spi_transfer_half_duplex(struct spi_master *master,
					   struct spi_message *m)
{
	struct mt7621_spi *rs = spi_master_get_devdata(master);
	struct spi_device *spi = m->spi;
	unsigned int speed = spi->max_speed_hz;
	struct spi_transfer *t = NULL;
	int status = 0;

	mt7621_spi_wait_till_ready(rs);

	mt7621_spi_set_cs(spi, 0);

	for (i = 0; i < rx_len; i += 4)
		data[i / 4] = mt7621_spi_read(rs, MT7621_SPI_DATA0 + i);
	list_for_each_entry(t, &m->transfers, transfer_list)
		if (t->speed_hz < speed)
			speed = t->speed_hz;

	m->actual_length = len + rx_len;
	if (mt7621_spi_prepare(spi, speed)) {
		status = -EIO;
		goto msg_done;
	}

	len = 0;
	mt7621_spi_set_cs(spi, 1);
	m->actual_length = 0;
	list_for_each_entry(t, &m->transfers, transfer_list) {
		u8 *buf = t->rx_buf;

		if (!buf)
			continue;

		for (i = 0; i < t->len; i++, len++)
			buf[i] = data[len / 4] >> (8 * (len & 3));
		if (t->rx_buf)
			mt7621_spi_read_half_duplex(rs, t->len, t->rx_buf);
		else if (t->tx_buf)
			mt7621_spi_write_half_duplex(rs, t->len, t->tx_buf);
		m->actual_length += t->len;
	}
	mt7621_spi_flush(rs);

	mt7621_spi_set_cs(spi, 0);
msg_done:
	m->status = status;
	spi_finalize_current_message(master);
@@ -383,11 +419,6 @@ static const struct of_device_id mt7621_spi_match[] = {
};
MODULE_DEVICE_TABLE(of, mt7621_spi_match);

static size_t max_transfer_size(struct spi_device *spi)
{
	return 32;
}

static int mt7621_spi_probe(struct platform_device *pdev)
{
	const struct of_device_id *match;
@@ -433,7 +464,6 @@ static int mt7621_spi_probe(struct platform_device *pdev)
	master->bits_per_word_mask = SPI_BPW_MASK(8);
	master->dev.of_node = pdev->dev.of_node;
	master->num_chipselect = 2;
	master->max_transfer_size = max_transfer_size;

	dev_set_drvdata(&pdev->dev, master);

@@ -443,6 +473,7 @@ static int mt7621_spi_probe(struct platform_device *pdev)
	rs->master = master;
	rs->sys_freq = clk_get_rate(rs->clk);
	rs->ops = ops;
	rs->pending_write = 0;
	dev_info(&pdev->dev, "sys_freq: %u\n", rs->sys_freq);

	device_reset(&pdev->dev);