[PATCH v3 5/8] spi: core: add spi_merge_transfers method
kernel at martin.sperl.org
kernel at martin.sperl.org
Mon Dec 14 07:20:22 PST 2015
From: Martin Sperl <kernel at martin.sperl.org>
Added method spi_merge_transfers which consolidates multiple
spi_transfers into a single spi_transfer making use of
spi_res for management of resources/reverting changes to the
spi_message structure.
Signed-off-by: Martin Sperl <kernel at martin.sperl.org>
---
drivers/spi/spi.c | 227 +++++++++++++++++++++++++++++++++++++++++++++++
include/linux/spi/spi.h | 9 ++
2 files changed, 236 insertions(+)
diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index f276c99..fe3b18e 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -148,6 +148,7 @@ SPI_STATISTICS_TRANSFER_BYTES_HISTO(16, "65536+");
SPI_STATISTICS_SHOW(transfers_split_maxsize, "%lu");
SPI_STATISTICS_SHOW(transfers_split_unaligned, "%lu");
SPI_STATISTICS_SHOW(transfers_split_unaligned_copy, "%lu");
+SPI_STATISTICS_SHOW(transfers_merged, "%lu");
static struct attribute *spi_dev_attrs[] = {
&dev_attr_modalias.attr,
@@ -189,6 +190,7 @@ static struct attribute *spi_device_statistics_attrs[] = {
&dev_attr_spi_device_transfers_split_maxsize.attr,
&dev_attr_spi_device_transfers_split_unaligned.attr,
&dev_attr_spi_device_transfers_split_unaligned_copy.attr,
+ &dev_attr_spi_device_transfers_merged.attr,
NULL,
};
@@ -234,6 +236,7 @@ static struct attribute *spi_master_statistics_attrs[] = {
&dev_attr_spi_master_transfers_split_maxsize.attr,
&dev_attr_spi_master_transfers_split_unaligned.attr,
&dev_attr_spi_master_transfers_split_unaligned_copy.attr,
+ &dev_attr_spi_master_transfers_merged.attr,
NULL,
};
@@ -2552,6 +2555,230 @@ int spi_split_transfers_unaligned(struct spi_master *master,
}
EXPORT_SYMBOL_GPL(spi_split_transfers_unaligned);
+static void __spi_merge_transfers_release(struct spi_master *master,
+ struct spi_message *msg,
+ struct spi_replaced_transfers *srt)
+{
+ u8 *ptr = srt->inserted_transfers[0].rx_buf;
+ struct spi_transfer *xfer;
+
+ /* copy the data to the final locations */
+ list_for_each_entry(xfer, &srt->replaced_transfers, transfer_list) {
+ /* fill data only if rx_buf is set */
+ if (xfer->rx_buf)
+ memcpy(xfer->rx_buf, ptr, xfer->len);
+ /* update the pointer for next loop */
+ ptr += xfer->len;
+ }
+}
+
+static int __spi_merge_transfers_do(struct spi_master *master,
+ struct spi_message *msg,
+ struct spi_transfer **xfer_start,
+ struct spi_transfer *xfer_end,
+ int count,
+ size_t size,
+ gfp_t gfp)
+{
+ size_t align = master->dma_alignment ? : sizeof(int);
+ size_t align_overhead = BIT(align) - 1;
+ size_t size_to_alloc = size + align_overhead;
+ struct spi_transfer *xfer_new, *xfer;
+ struct spi_replaced_transfers *srt;
+ u8 *data_tx;
+ int i;
+
+ /* for anything but 3-wire mode we need rx/tx_buf to be set */
+ if (!(msg->spi->mode & SPI_3WIRE))
+ size_to_alloc *= 2;
+
+ /* replace count transfers starting with xfer_start
+ * and replace them with a single transfer (which is returned)
+ * the extra data requeste starts at:
+ * returned pointer + sizeof(struct_transfer)
+ */
+ srt = spi_replace_transfers(msg, *xfer_start, count, 1,
+ __spi_merge_transfers_release,
+ size_to_alloc, gfp);
+ if (!srt)
+ return -ENOMEM;
+
+ /* the inserted transfer */
+ xfer_new = srt->inserted_transfers;
+
+ /* pointer to data_tx data allocated - aligned */
+ data_tx = PTR_ALIGN(srt->extradata, align);
+
+ /* fill the transfer with the settings from the last replaced */
+ xfer_new->cs_change = xfer_end->cs_change;
+ xfer_new->delay_usecs = xfer_end->delay_usecs;
+
+ /* set the size of the transfer */
+ xfer_new->len = size;
+
+ /* now fill in the corresponding aligned pointers */
+ if (msg->spi->mode & SPI_3WIRE) {
+ /* if the first transfer has tx_buf set,
+ * then we are transmitting
+ */
+ if ((*xfer_start)->tx_buf) {
+ xfer_new->tx_buf = data_tx;
+ xfer_new->rx_buf = NULL;
+ } else {
+ xfer_new->tx_buf = NULL;
+ xfer_new->rx_buf = data_tx;
+ }
+ } else {
+ xfer_new->tx_buf = data_tx;
+ xfer_new->rx_buf = PTR_ALIGN(data_tx + size, align);
+ }
+
+ /* copy the data we need for tx (if it is set) */
+ if (xfer_new->tx_buf) {
+ for (xfer = *xfer_start, i = 0;
+ i < count;
+ i++, xfer = list_next_entry(xfer, transfer_list)) {
+ /* fill data only if tx_buf is set */
+ if (xfer->tx_buf)
+ memcpy(data_tx, xfer->tx_buf, xfer->len);
+ /* update pointer to after memcpy for next loop */
+ data_tx += xfer->len;
+ }
+ }
+
+ /* update the transfer to the one we have just put into the list */
+ *xfer_start = xfer_new;
+
+ /* increment statistics counters */
+ SPI_STATISTICS_ADD_TO_FIELD(&master->statistics,
+ transfers_merged, count);
+ SPI_STATISTICS_ADD_TO_FIELD(&msg->spi->statistics,
+ transfers_merged, count);
+
+ return 0;
+}
+
+static int __spi_merge_transfers(struct spi_master *master,
+ struct spi_message *msg,
+ struct spi_transfer **xfer_start,
+ size_t min_size,
+ size_t max_size,
+ gfp_t gfp)
+{
+ struct spi_transfer *xfer, *xfer_end;
+ int count;
+ size_t size;
+
+ /* loop transfers until we reach
+ * * the end of the list
+ * * a change in some essential parameters in spi_transfer
+ * compared to the first transfer we check
+ * (speed, bits, direction in 3 wire mode)
+ * * settings that immediately indicate we need to stop testing
+ * the next transfer (cs_change, delay_usecs)
+ */
+ for (count = 0, size = 0, xfer = *xfer_start, xfer_end = xfer;
+ !list_is_last(&xfer->transfer_list, &msg->transfers);
+ xfer = list_next_entry(xfer, transfer_list)) {
+ /* now check on total size */
+ if (size + xfer->len > max_size)
+ break;
+ /* these checks are only necessary on subsequent transfers */
+ if (count) {
+ /* check if we differ from the first transfer */
+ if (xfer->speed_hz != (*xfer_start)->speed_hz)
+ break;
+ if (xfer->tx_nbits != (*xfer_start)->tx_nbits)
+ break;
+ if (xfer->rx_nbits != (*xfer_start)->rx_nbits)
+ break;
+ if (xfer->bits_per_word !=
+ (*xfer_start)->bits_per_word)
+ break;
+
+ /* 3-wire we need to handle in a special way */
+ if (msg->spi->mode & SPI_3WIRE) {
+ /* did we switch directions in 3 wire mode ? */
+ if (xfer->tx_buf && (*xfer_start)->rx_buf)
+ break;
+ if (xfer->rx_buf && (*xfer_start)->tx_buf)
+ break;
+ }
+ }
+ /* otherwise update counters for the last few tests,
+ * that only depend on settings of the current transfer
+ */
+ count++;
+ size += xfer->len;
+ xfer_end = xfer;
+
+ /* check for conditions that would trigger a merge
+ * based only on the current transfer
+ * so we need count and size updated already...
+ */
+ if (xfer->cs_change)
+ break;
+ if (xfer->delay_usecs)
+ break;
+ }
+
+ /* merge only when we have at least 2 transfers to handle */
+ if (count < 2)
+ return 0;
+
+ /* and also only if we really have reached our min_size */
+ if (size < min_size)
+ return 0;
+
+ /* do the transformation for real now */
+ return __spi_merge_transfers_do(master, msg,
+ xfer_start, xfer_end,
+ count, size, gfp);
+}
+
+/**
+ * spi_merge_tranfers - merges multiple spi_transfers into a single one
+ * copying the corresponding data
+ * @master: the @spi_master for this transfer
+ * @message: the @spi_message to transform
+ * @max_size: the minimum total length when to apply this transform
+ * @max_size: the maximum total length when to apply this transform
+ * @gfp: gfp flags
+ *
+ * Return: status of transformation
+ */
+int spi_merge_transfers(struct spi_master *master,
+ struct spi_message *msg,
+ size_t min_size,
+ size_t max_size,
+ gfp_t gfp)
+{
+ struct spi_transfer *xfer;
+ int ret;
+
+ /* nothing to merge if the list is empty */
+ if (list_is_singular(&msg->transfers))
+ return 0;
+
+ /* if the total transfer size is too small, then skip */
+ if (msg->frame_length < min_size)
+ return 0;
+
+ /* iterate over all transfers and modify xfer if we have
+ * replaced some transfers
+ */
+ list_for_each_entry(xfer, &msg->transfers, transfer_list) {
+ /* test if it is a merge candidate */
+ ret = __spi_merge_transfers(master, msg, &xfer,
+ min_size, max_size, gfp);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(spi_merge_transfers);
+
/*-------------------------------------------------------------------------*/
/* Core methods for SPI master protocol drivers. Some of the
diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h
index deb94a3..7172e73 100644
--- a/include/linux/spi/spi.h
+++ b/include/linux/spi/spi.h
@@ -63,6 +63,9 @@ extern struct bus_type spi_bus_type;
* @transfers_split_unaligned_copy:
* number of transfers that have been aligned by copying
* at least one buffer (typically tx)
+ * @transfers_merged:
+ * number of transfers that have been merged to allow
+ * for better efficiency using dma
*/
struct spi_statistics {
spinlock_t lock; /* lock for the whole structure */
@@ -86,6 +89,7 @@ struct spi_statistics {
unsigned long transfers_split_maxsize;
unsigned long transfers_split_unaligned;
unsigned long transfers_split_unaligned_copy;
+ unsigned long transfers_merged;
};
void spi_statistics_add_transfer_stats(struct spi_statistics *stats,
@@ -945,6 +949,11 @@ extern int spi_split_transfers_unaligned(struct spi_master *master,
size_t min_size,
size_t alignment,
gfp_t gfp);
+extern int spi_merge_transfers(struct spi_master *master,
+ struct spi_message *msg,
+ size_t min_size,
+ size_t max_size,
+ gfp_t gfp);
/*---------------------------------------------------------------------------*/
--
1.7.10.4
More information about the linux-rpi-kernel
mailing list