[1/2] spi: Add the SPI daisy chain support.
diff mbox series

Message ID 20200703141246.17281-1-adrian.fiergolski@fastree3d.com
State Superseded
Headers show
Series
  • [1/2] spi: Add the SPI daisy chain support.
Related show

Commit Message

Adrian Fiergolski July 3, 2020, 2:12 p.m. UTC
The implementation is transparent for the SPI devices and doesn't require
their modifications. It is based on a virtual SPI device (spi-daisy_chain)
and defines two required device tree properties ('spi-daisy-chain-len' and
'spi-daisy-chain-noop') and one optional
('spi-daisy-chain-bits_per_word'). It has been tested on hardware with a
chain of three ltc2694 devices (kernel v4.19).

Signed-off-by: Adrian Fiergolski <adrian.fiergolski@fastree3d.com>
---
 drivers/spi/Kconfig                 |   8 +
 drivers/spi/Makefile                |   1 +
 drivers/spi/spi-daisy_chain.c       | 428 ++++++++++++++++++++++++++++
 drivers/spi/spi.c                   |  65 ++++-
 include/linux/spi/spi-daisy_chain.h |  32 +++
 include/linux/spi/spi.h             |  17 +-
 6 files changed, 541 insertions(+), 10 deletions(-)
 create mode 100644 drivers/spi/spi-daisy_chain.c
 create mode 100644 include/linux/spi/spi-daisy_chain.h

Comments

kernel test robot July 3, 2020, 10:32 p.m. UTC | #1
Hi Adrian,

Thank you for the patch! Perhaps something to improve:

[auto build test WARNING on spi/for-next]
[also build test WARNING on v5.8-rc3 next-20200703]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use  as documented in
https://git-scm.com/docs/git-format-patch]

url:    https://github.com/0day-ci/linux/commits/Adrian-Fiergolski/spi-Add-the-SPI-daisy-chain-support/20200703-221615
base:   https://git.kernel.org/pub/scm/linux/kernel/git/broonie/spi.git for-next
config: x86_64-allyesconfig (attached as .config)
compiler: clang version 11.0.0 (https://github.com/llvm/llvm-project ca464639a1c9dd3944eb055ffd2796e8c2e7639f)
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # install x86_64 cross compiling tool for clang build
        # apt-get install binutils-x86-64-linux-gnu
        # save the attached .config to linux build tree
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross ARCH=x86_64 

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>

All warnings (new ones prefixed by >>):

>> drivers/spi/spi-daisy_chain.c:232:6: warning: no previous prototype for function 'spi_daisy_chain_clean' [-Wmissing-prototypes]
   void spi_daisy_chain_clean(struct list_head *daisy_chain_devs)
        ^
   drivers/spi/spi-daisy_chain.c:232:1: note: declare 'static' if the function is not intended to be used outside of this translation unit
   void spi_daisy_chain_clean(struct list_head *daisy_chain_devs)
   ^
   static 
   1 warning generated.

vim +/spi_daisy_chain_clean +232 drivers/spi/spi-daisy_chain.c

   231	
 > 232	void spi_daisy_chain_clean(struct list_head *daisy_chain_devs)
   233	{
   234		struct spi_device *spi_dev;
   235		struct spi_daisy_chain_device *spi_chain_dev;
   236	
   237		list_for_each_entry(spi_chain_dev, daisy_chain_devs, devices) {
   238			spi_dev = spi_chain_dev->spi;
   239			spi_dev_put(spi_dev);
   240			kfree(spi_chain_dev->no_operation.tx_buf);
   241			kfree(spi_chain_dev);
   242		}
   243		list_del(daisy_chain_devs);
   244		kfree(daisy_chain_devs);
   245	}
   246	

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org
kernel test robot July 4, 2020, 12:18 a.m. UTC | #2
Hi Adrian,

Thank you for the patch! Perhaps something to improve:

[auto build test WARNING on spi/for-next]
[also build test WARNING on v5.8-rc3 next-20200703]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use  as documented in
https://git-scm.com/docs/git-format-patch]

url:    https://github.com/0day-ci/linux/commits/Adrian-Fiergolski/spi-Add-the-SPI-daisy-chain-support/20200703-221615
base:   https://git.kernel.org/pub/scm/linux/kernel/git/broonie/spi.git for-next
config: parisc-allyesconfig (attached as .config)
compiler: hppa-linux-gcc (GCC) 9.3.0
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # save the attached .config to linux build tree
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-9.3.0 make.cross ARCH=parisc 

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>

All warnings (new ones prefixed by >>):

>> drivers/spi/spi-daisy_chain.c:232:6: warning: no previous prototype for 'spi_daisy_chain_clean' [-Wmissing-prototypes]
     232 | void spi_daisy_chain_clean(struct list_head *daisy_chain_devs)
         |      ^~~~~~~~~~~~~~~~~~~~~

vim +/spi_daisy_chain_clean +232 drivers/spi/spi-daisy_chain.c

   231	
 > 232	void spi_daisy_chain_clean(struct list_head *daisy_chain_devs)
   233	{
   234		struct spi_device *spi_dev;
   235		struct spi_daisy_chain_device *spi_chain_dev;
   236	
   237		list_for_each_entry(spi_chain_dev, daisy_chain_devs, devices) {
   238			spi_dev = spi_chain_dev->spi;
   239			spi_dev_put(spi_dev);
   240			kfree(spi_chain_dev->no_operation.tx_buf);
   241			kfree(spi_chain_dev);
   242		}
   243		list_del(daisy_chain_devs);
   244		kfree(daisy_chain_devs);
   245	}
   246	

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org

Patch
diff mbox series

diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 8f1f8fca79e3..822c4b4bdd5c 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -55,6 +55,14 @@  config SPI_MEM
 	  This extension is meant to simplify interaction with SPI memories
 	  by providing a high-level interface to send memory-like commands.
 
+config SPI_DAISY_CHAIN
+	bool "SPI daisy chain support"
+	depends on OF
+	help
+	  This enables support for the SPI daisy chains.
+	  This extension provides a virtual SPI daisy chain device which
+	  links together physical SPI devices on a common SPI daisy chain.
+
 comment "SPI Master Controller Drivers"
 
 config SPI_ALTERA
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index d2e41d3d464a..dbcb98480f51 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -9,6 +9,7 @@  ccflags-$(CONFIG_SPI_DEBUG) := -DDEBUG
 # config declarations into driver model code
 obj-$(CONFIG_SPI_MASTER)		+= spi.o
 obj-$(CONFIG_SPI_MEM)			+= spi-mem.o
+obj-$(CONFIG_SPI_DAISY_CHAIN)		+= spi-daisy_chain.o
 obj-$(CONFIG_SPI_MUX)			+= spi-mux.o
 obj-$(CONFIG_SPI_SPIDEV)		+= spidev.o
 obj-$(CONFIG_SPI_LOOPBACK_TEST)		+= spi-loopback-test.o
diff --git a/drivers/spi/spi-daisy_chain.c b/drivers/spi/spi-daisy_chain.c
new file mode 100644
index 000000000000..37870fe71028
--- /dev/null
+++ b/drivers/spi/spi-daisy_chain.c
@@ -0,0 +1,428 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+/*
+ * A driver handling the SPI daisy chaines.
+ *
+ * Copyright (C) 2020 Fastree3D
+ *	Adrian Fiergolski <Adrian.Fiergolski@fastree3d.com>
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/of.h>
+#include <linux/spi/spi.h>
+
+#include <linux/spi/spi-daisy_chain.h>
+
+/**
+ * spi_daisy_chain_message_pre - generate SPI daisy chain message
+ * @spi: device with which data will be exchanged
+ * @message: describes the data transfers
+ *
+ * The call checks if the SPI device is part of daisy chain. If so, it
+ * generates a message containg no-operation codes
+ * of unaddressed device of a given daisy chain.
+ **/
+
+int spi_daisy_chain_message_pre(struct spi_device *spi,
+				struct spi_message *message)
+{
+	struct spi_transfer *tr, *ntr;
+	struct spi_daisy_chain_device *spi_chain_dev;
+	int rc;
+
+	//the device is not part of a daisy-chain
+	if (spi->daisy_chain_devs == NULL)
+		return 0;
+
+	if (message->is_dma_mapped) {
+		dev_err(&spi->dev,
+			"DMA mapped transfer is not support when on daisy chain");
+		return -EINVAL;
+	}
+
+	if (!list_is_singular(&message->transfers)) {
+		dev_err(&spi->dev,
+			"Mutliple transfer segments are not support when on daisy chain");
+		return -EINVAL;
+	}
+
+	message->daisy_chain_transfers = message->transfers;
+	INIT_LIST_HEAD(&message->transfers);
+
+	list_for_each_entry(spi_chain_dev, spi->daisy_chain_devs, devices) {
+		if (spi_chain_dev->spi == spi) {
+			tr = list_first_entry(&message->daisy_chain_transfers,
+					      struct spi_transfer,
+					      transfer_list);
+
+			//check if mode is not being changed
+			if (tr->tx_nbits)
+				switch (spi->mode &
+					(SPI_TX_DUAL | SPI_TX_QUAD)) {
+				case 0:
+					if (!(tr->tx_nbits & SPI_NBITS_SINGLE))
+						goto err_tx_mode;
+					break;
+
+				case SPI_TX_DUAL:
+					if (!(tr->tx_nbits & SPI_NBITS_DUAL))
+						goto err_tx_mode;
+					break;
+
+				case SPI_TX_QUAD:
+					if (!(tr->tx_nbits & SPI_NBITS_QUAD))
+						goto err_tx_mode;
+					break;
+
+				default:
+					goto err_tx_mode;
+				}
+
+			if (tr->rx_nbits)
+				switch (spi->mode &
+					(SPI_RX_DUAL | SPI_RX_QUAD)) {
+				case 0:
+					if (!(tr->rx_nbits & SPI_NBITS_SINGLE))
+						goto err_rx_mode;
+					break;
+
+				case SPI_RX_DUAL:
+					if (tr->rx_nbits & SPI_NBITS_DUAL)
+						goto err_rx_mode;
+					break;
+				case SPI_RX_QUAD:
+					if (tr->rx_nbits & SPI_NBITS_QUAD)
+						goto err_rx_mode;
+					break;
+				default:
+					goto err_rx_mode;
+				}
+
+			//check if frequency is not being changed
+			if (tr->speed_hz && tr->speed_hz != spi->max_speed_hz) {
+				dev_err(&spi->dev,
+					"Change of SPI frequency not supported when on daisy chain");
+				return -EINVAL;
+			}
+
+			//daisy chain operations has a regular length
+			if (tr->len == spi_chain_dev->no_operation.len) {
+				tr->bits_per_word = spi_chain_dev->no_operation
+							    .bits_per_word;
+				tr->cs_change = 0;
+
+				list_add_tail(&tr->transfer_list,
+					      &message->transfers);
+			}
+			//daisy chain operation has different than regular length
+			else {
+				if (tr->len > spi_chain_dev->no_operation.len) {
+					dev_err(&spi->dev,
+						"Transmission not supported");
+					return -EINVAL;
+				}
+
+				ntr = kzalloc(sizeof(*ntr), GFP_KERNEL);
+
+				if (!ntr)
+					return -ENOMEM;
+
+				message->daisy_chain_new_transfer = ntr;
+
+				ntr->len = spi_chain_dev->no_operation.len;
+				ntr->bits_per_word = spi_chain_dev->no_operation
+							     .bits_per_word;
+
+				//copy tx buffer
+				if (tr->tx_buf) {
+					ntr->tx_buf =
+						kmalloc(ntr->len, GFP_KERNEL);
+					if (!ntr->tx_buf) {
+						rc = -ENOMEM;
+						goto err_out;
+					}
+
+					//The daisy-chain padding is assumed to be right-justified,
+					//so unused tx bits are transferred first
+					memcpy((void *)((char *)ntr->tx_buf +
+							ntr->len - tr->len),
+					       tr->tx_buf, tr->len);
+				}
+
+				//allocate rx buffer
+				if (tr->rx_buf) {
+					ntr->rx_buf =
+						kmalloc(ntr->len, GFP_KERNEL);
+					if (!ntr->rx_buf) {
+						rc = -ENOMEM;
+						goto err_out;
+					}
+				}
+
+				list_add_tail(&ntr->transfer_list,
+					      &message->transfers);
+			}
+		} else
+			list_add_tail(
+				&spi_chain_dev->no_operation.transfer_list,
+				&message->transfers);
+	}
+
+	return 0;
+
+err_out:
+	kfree(ntr->tx_buf);
+	kfree(ntr->rx_buf);
+	kfree(ntr);
+	return rc;
+
+err_tx_mode:
+	dev_err(&spi->dev, "Unsupported tx mode on daisy chain");
+	return -EINVAL;
+
+err_rx_mode:
+	dev_err(&spi->dev, "Unsupported rx mode on daisy chain");
+	return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(spi_daisy_chain_message_pre);
+
+/**
+ * spi_daisy_chain_message_post - generate SPI daisy chain message
+ * @spi: device with which data will be exchanged
+ * @message: describes the data transfers
+ *
+ * The call checks if the SPI device is part of daisy chain. If so, it
+ * removes no-operation codes of unaddressd device of a given chain.
+ **/
+
+void spi_daisy_chain_message_post(struct spi_device *spi,
+				  struct spi_message *message)
+{
+	struct spi_transfer *tr;
+
+	//the device is not part of a daisy-chain
+	if (spi->daisy_chain_devs == NULL)
+		return;
+
+	if (message->daisy_chain_new_transfer) {
+		tr = list_first_entry(&message->daisy_chain_transfers,
+				      struct spi_transfer, transfer_list);
+		if (tr->rx_buf)
+			//The daisy-chain padding is assumed to be right-justified,
+			//so unused rx bits were received first and can be skipped
+			memcpy((void *)tr->rx_buf,
+			       (char *)message->daisy_chain_new_transfer->rx_buf +
+				       message->daisy_chain_new_transfer->len -
+				       tr->len,
+			       tr->len);
+
+		kfree(message->daisy_chain_new_transfer->tx_buf);
+		kfree(message->daisy_chain_new_transfer->rx_buf);
+		kfree(message->daisy_chain_new_transfer);
+	}
+
+	list_del(&message->transfers);
+
+	message->transfers = message->daisy_chain_transfers;
+}
+EXPORT_SYMBOL_GPL(spi_daisy_chain_message_post);
+
+void spi_daisy_chain_clean(struct list_head *daisy_chain_devs)
+{
+	struct spi_device *spi_dev;
+	struct spi_daisy_chain_device *spi_chain_dev;
+
+	list_for_each_entry(spi_chain_dev, daisy_chain_devs, devices) {
+		spi_dev = spi_chain_dev->spi;
+		spi_dev_put(spi_dev);
+		kfree(spi_chain_dev->no_operation.tx_buf);
+		kfree(spi_chain_dev);
+	}
+	list_del(daisy_chain_devs);
+	kfree(daisy_chain_devs);
+}
+
+static int spi_daisy_chain_driver_probe(struct spi_device *spi)
+{
+	struct device_node *nc;
+	struct spi_device *spi_dev;
+	struct spi_daisy_chain_device *spi_chain_dev;
+	struct spi_transfer *no_operation;
+	int w_size;
+	int rc;
+
+	//Initialise the SPI daisy-chain queue
+	spi->daisy_chain_devs =
+		kzalloc(sizeof(*spi->daisy_chain_devs), GFP_KERNEL);
+	if (!spi->daisy_chain_devs)
+		return -ENOMEM;
+
+	INIT_LIST_HEAD(spi->daisy_chain_devs);
+
+	for_each_available_child_of_node(spi->dev.of_node, nc) {
+		if (of_node_test_and_set_flag(nc, OF_POPULATED))
+			continue;
+
+		spi_chain_dev = kzalloc(sizeof(*spi_chain_dev), GFP_KERNEL);
+		if (!spi_chain_dev) {
+			rc = -ENOMEM;
+			goto err_out;
+		}
+
+		////////////////////////////////////////////////////////////
+		//extract daisy-chain no-operation command from devicetree
+		////////////////////////////////////////////////////////////
+		no_operation = &spi_chain_dev->no_operation;
+		if (of_property_read_u8(nc, "spi-daisy-chain-bits_per_word",
+					&no_operation->bits_per_word)) {
+			no_operation->bits_per_word = 8;
+		}
+
+		if (no_operation->bits_per_word > 32) {
+			dev_err(&spi->dev,
+				"device %pOF: 'spi-daisy-chain-bits_per_word' property can't by higher than 32",
+				nc);
+			rc = -EINVAL;
+			goto err_out_spi_chain;
+		}
+
+		if (of_property_read_u32(nc, "spi-daisy-chain-len",
+					 &no_operation->len)) {
+			dev_err(&spi->dev,
+				"device %pOF doesn't define 'spi-daisy-chain-len' property",
+				nc);
+			rc = -EINVAL;
+			goto err_out_spi_chain;
+		}
+
+		// SPI transfer length should be multiple of SPI word size
+		// where SPI word size should be power-of-two multiple
+		if (no_operation->bits_per_word <= 8)
+			w_size = 1;
+		else if (no_operation->bits_per_word <= 16)
+			w_size = 2;
+		else
+			w_size = 4;
+
+		/* No partial transfers accepted */
+		if (no_operation->len % w_size) {
+			rc = -EINVAL;
+			dev_err(&spi->dev,
+				"no partial transfers accepted (propeties 'spi-daisy-chain-len' and  spi-daisy-chain-bits_per_word of device %pOF",
+				nc);
+			rc = -EINVAL;
+			goto err_out_spi_chain;
+		}
+
+		no_operation->tx_buf = kmalloc(no_operation->len, GFP_KERNEL);
+		if (!no_operation->tx_buf) {
+			rc = -ENOMEM;
+			goto err_out_spi_chain;
+		}
+
+		if (of_property_read_u8_array(nc, "spi-daisy-chain-noop",
+					      (void *)no_operation->tx_buf,
+					      no_operation->len)) {
+			dev_err(&spi->dev,
+				"device %pOF doesn't define 'spi-daisy-chain-noop' property",
+				nc);
+			rc = -EINVAL;
+			goto err_out_tx_buf;
+		}
+
+		////////////////////////////
+		//allocate a new SPI device
+		////////////////////////////
+		spi_dev = spi_alloc_device(spi->controller);
+		if (!spi_dev) {
+			dev_err(&spi->dev, "spi_device alloc error for %pOF\n",
+				nc);
+			rc = -ENOMEM;
+			goto err_out_tx_buf;
+		}
+		spi_chain_dev->spi = spi_dev;
+		spi_dev->daisy_chain_devs = spi->daisy_chain_devs;
+
+		//select device driver
+		rc = of_modalias_node(nc, spi_dev->modalias,
+				      sizeof(spi_dev->modalias));
+		if (rc < 0) {
+			dev_err(&spi->dev, "cannot find modalias for %pOF\n",
+				nc);
+			goto err_out_spi_dev;
+		}
+
+		//store a pointer to the node in the device structure
+		of_node_get(nc);
+		spi_dev->dev.of_node = nc;
+
+		//add the SPI device to the chain
+		list_add_tail(&spi_chain_dev->devices, spi->daisy_chain_devs);
+	}
+
+	//////////////////////
+	//add all SPI devices
+	//////////////////////
+	list_for_each_entry(spi_chain_dev, spi->daisy_chain_devs, devices) {
+		spi_dev = spi_chain_dev->spi;
+
+		//All devices on the chain share settings of the daisy-chain node
+		//The individual settings of the SPI nodes are ignored
+		spi_dev->mode = spi->mode;
+		spi_dev->chip_select = spi->chip_select;
+		spi_dev->max_speed_hz = spi->max_speed_hz;
+
+		//Register the new device
+		rc = spi_add_device(spi_dev);
+		if (rc) {
+			dev_err(&spi->dev,
+				"spi_device register error on daisy chain %pOF\n",
+				spi_dev->dev.of_node);
+			of_node_put(nc);
+			goto err_out;
+		}
+	}
+
+	return 0;
+
+err_out_spi_dev:
+	spi_dev_put(spi_chain_dev->spi);
+err_out_tx_buf:
+	kfree(spi_chain_dev->no_operation.tx_buf);
+err_out_spi_chain:
+	kfree(spi_chain_dev);
+err_out:
+	spi_daisy_chain_clean(spi->daisy_chain_devs);
+	return rc;
+}
+
+static int spi_daisy_chain_driver_remove(struct spi_device *spi)
+{
+	spi_daisy_chain_clean(spi->daisy_chain_devs);
+	return 0;
+}
+
+static const struct of_device_id spi_daisy_chain_of_match[] = {
+	{
+		.compatible = "spi,daisy_chain",
+	},
+	{}
+};
+MODULE_DEVICE_TABLE(of, spi_daisy_chain_of_match);
+
+static struct spi_driver spi_daisy_chain_driver = {
+	.probe = spi_daisy_chain_driver_probe,
+	.remove = spi_daisy_chain_driver_remove,
+	.driver = {
+			.name = "daisy_chain",
+			.of_match_table = spi_daisy_chain_of_match,
+	},
+};
+module_spi_driver(spi_daisy_chain_driver);
+
+MODULE_AUTHOR("Adrian Fiergolski <Adrian.Fiergolski@fastree3d.com>");
+MODULE_DESCRIPTION("Driver handling SPI daisy chain");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("spi:daisy_chain");
diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index 8158e281f354..96c8ce572e2f 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -18,6 +18,7 @@ 
 #include <linux/mod_devicetable.h>
 #include <linux/spi/spi.h>
 #include <linux/spi/spi-mem.h>
+#include <linux/spi/spi-daisy_chain.h>
 #include <linux/of_gpio.h>
 #include <linux/gpio/consumer.h>
 #include <linux/pm_runtime.h>
@@ -522,14 +523,34 @@  EXPORT_SYMBOL_GPL(spi_alloc_device);
 static void spi_dev_set_name(struct spi_device *spi)
 {
 	struct acpi_device *adev = ACPI_COMPANION(&spi->dev);
+#ifdef CONFIG_SPI_DAISY_CHAIN
+	struct spi_daisy_chain_device *spi_chain_dev;
+	int spi_dev_index;
+#endif
 
 	if (adev) {
 		dev_set_name(&spi->dev, "spi-%s", acpi_dev_name(adev));
 		return;
 	}
 
-	dev_set_name(&spi->dev, "%s.%u", dev_name(&spi->controller->dev),
-		     spi->chip_select);
+#ifdef CONFIG_SPI_DAISY_CHAIN
+	if (spi->daisy_chain_devs != NULL) {
+		spi_dev_index = 0;
+		list_for_each_entry(spi_chain_dev, spi->daisy_chain_devs,
+				    devices){
+			if (spi_chain_dev->spi == spi)
+				break;
+			spi_dev_index++;
+		}
+
+		dev_set_name(&spi->dev, "%s.%u.%u",
+			     dev_name(&spi->controller->dev), spi->chip_select,
+			     spi_dev_index);
+
+	} else
+#endif
+		dev_set_name(&spi->dev, "%s.%u",
+			     dev_name(&spi->controller->dev), spi->chip_select);
 }
 
 static int spi_dev_check(struct device *dev, void *data)
@@ -573,15 +594,27 @@  int spi_add_device(struct spi_device *spi)
 	 * chipselect **BEFORE** we call setup(), else we'll trash
 	 * its configuration.  Lock against concurrent add() calls.
 	 */
-	mutex_lock(&spi_add_lock);
+#ifdef CONFIG_SPI_DAISY_CHAIN
+	/* Do not lock the controller when registering the daisy_chain driver
+	 * as the last one wouldn't be able to register its subnodes
+	 */
+	if (strcmp((const char *)spi->dev.of_node->name, "daisy_chain") == 0)
+#endif
+		mutex_lock(&spi_add_lock);
 
-	status = bus_for_each_dev(&spi_bus_type, NULL, spi, spi_dev_check);
-	if (status) {
-		dev_err(dev, "chipselect %d already in use\n",
+#ifdef CONFIG_SPI_DAISY_CHAIN
+	if (spi->daisy_chain_devs == NULL) {
+#endif
+		status = bus_for_each_dev(&spi_bus_type, NULL, spi,
+					  spi_dev_check);
+		if (status) {
+			dev_err(dev, "chipselect %d already in use\n",
 				spi->chip_select);
-		goto done;
+			goto done;
+		}
+#ifdef CONFIG_SPI_DAISY_CHAIN
 	}
-
+#endif
 	/* Descriptors take precedence */
 	if (ctlr->cs_gpiods)
 		spi->cs_gpiod = ctlr->cs_gpiods[spi->chip_select];
@@ -608,7 +641,10 @@  int spi_add_device(struct spi_device *spi)
 		dev_dbg(dev, "registered child %s\n", dev_name(&spi->dev));
 
 done:
-	mutex_unlock(&spi_add_lock);
+#ifdef CONFIG_SPI_DAISY_CHAIN
+	if (strcmp((const char *)spi->dev.of_node->name, "daisy_chain") == 0)
+#endif
+		mutex_unlock(&spi_add_lock);
 	return status;
 }
 EXPORT_SYMBOL_GPL(spi_add_device);
@@ -3694,6 +3730,12 @@  static int __spi_sync(struct spi_device *spi, struct spi_message *message)
 	SPI_STATISTICS_INCREMENT_FIELD(&ctlr->statistics, spi_sync);
 	SPI_STATISTICS_INCREMENT_FIELD(&spi->statistics, spi_sync);
 
+#ifdef CONFIG_SPI_DAISY_CHAIN
+	status = spi_daisy_chain_message_pre(spi, message);
+	if (status < 0)
+		return status;
+#endif
+
 	/* If we're not using the legacy transfer method then we will
 	 * try to transfer in the calling context so special case.
 	 * This code would be less tricky if we could remove the
@@ -3727,6 +3769,11 @@  static int __spi_sync(struct spi_device *spi, struct spi_message *message)
 		status = message->status;
 	}
 	message->context = NULL;
+
+#ifdef CONFIG_SPI_DAISY_CHAIN
+	spi_daisy_chain_message_post(spi, message);
+#endif
+
 	return status;
 }
 
diff --git a/include/linux/spi/spi-daisy_chain.h b/include/linux/spi/spi-daisy_chain.h
new file mode 100644
index 000000000000..8967292863b7
--- /dev/null
+++ b/include/linux/spi/spi-daisy_chain.h
@@ -0,0 +1,32 @@ 
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * A driver handling the SPI daisy chaines.
+ *
+ * Copyright (C) 2020 Fastree3D
+ *	Adrian Fiergolski <Adrian.Fiergolski@fastree3d.com>
+ */
+
+#ifndef __LINUX_SPI_DAISY_CHAIN_H
+#define __LINUX_SPI_DAISY_CHAIN_H
+
+#include <linux/mutex.h>
+#include <linux/types.h>
+
+/**
+ * struct spi_daisy_chain - interface to a SPI device on a daisy chain
+ * @spi: SPI device on the daisy chain
+ * @no_operation: no-opeartion SPI message for the given device
+ * @devices: list of SPI devices sharing the given daisy chain
+ **/
+struct spi_daisy_chain_device {
+	struct spi_device *spi;
+	struct spi_transfer no_operation;
+	struct list_head devices;
+};
+
+extern int spi_daisy_chain_message_pre(struct spi_device *spi,
+				       struct spi_message *message);
+extern void spi_daisy_chain_message_post(struct spi_device *spi,
+					 struct spi_message *message);
+
+#endif /* __LINUX_SPI_DAISY_CHAIN_H */
diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h
index aac57b5b7c21..bdc6973606dd 100644
--- a/include/linux/spi/spi.h
+++ b/include/linux/spi/spi.h
@@ -111,6 +111,9 @@  extern int spi_delay_exec(struct spi_delay *_delay, struct spi_transfer *xfer);
  * @dev: Driver model representation of the device.
  * @controller: SPI controller used with the device.
  * @master: Copy of controller, for backwards compatibility.
+ * @daisy_chain_devs: list of all SPI devices on the daisy chain
+ *      used by the given SPI device.
+ *      Handled by the SPI daisy chain driver.
  * @max_speed_hz: Maximum clock rate to be used with this chip
  *	(on this board); may be changed by the device's driver.
  *	The spi_transfer.speed_hz can override this for each transfer.
@@ -160,6 +163,9 @@  struct spi_device {
 	struct device		dev;
 	struct spi_controller	*controller;
 	struct spi_controller	*master;	/* compatibility layer */
+#ifdef CONFIG_SPI_DAISY_CHAIN
+	struct list_head        *daisy_chain_devs;
+#endif
 	u32			max_speed_hz;
 	u8			chip_select;
 	u8			bits_per_word;
@@ -945,6 +951,11 @@  struct spi_transfer {
 /**
  * struct spi_message - one multi-segment SPI transaction
  * @transfers: list of transfer segments in this transaction
+ * @daisy_chain_transfers: head of the original transfers queue.
+ *      Handled by the SPI daisy chain driver.
+ * @daisy_chain_new_transfer: pointer to an extra SPI transfer,
+ *      in case it had to be created.
+ *      Handled by the SPI daisy chain driver.
  * @spi: SPI device to which the transaction is queued
  * @is_dma_mapped: if true, the caller provided both dma and cpu virtual
  *	addresses for each transfer buffer
@@ -975,7 +986,11 @@  struct spi_transfer {
 struct spi_message {
 	struct list_head	transfers;
 
-	struct spi_device	*spi;
+#ifdef CONFIG_SPI_DAISY_CHAIN
+	struct list_head daisy_chain_transfers;
+	struct spi_transfer *daisy_chain_new_transfer;
+#endif
+	struct spi_device *spi;
 
 	unsigned		is_dma_mapped:1;