Re: SPI daisy chain support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi All,

I have implemented the SPI daisy chain support for kernel 4.19. I am
enclosing the patch.

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. I am able to successfully control them.
Unfortunately, I can't confirm that readout works properly

If you agree with this daisy-chain concept, in order to upstream this
feature, I am willing to merge it with the master (could you navigate me
to branch, repository against which I should do it?).

Regards,

Adrian

On 17.06.2020 14:33, Adrian Fiergolski wrote:
> Hi All,
>
> A quick update: I have started implementation taking into account most
> of the points from our discussion (a separate spi,daisy-chain spi
> device, single device addressed at once, etc.). I have realized that the
> daisy-chain device doesn't need to be a SPI controller and information
> about the chain can be stored in the spi_device struct and then handled
> by the SPI subsystem. Once I have a working implementation, I will share
> it with you for a review.
>
> Regards,
>
> Adrian
>
> On 16.06.2020 12:25, Adrian Fiergolski wrote:
>> Hi Geert and Rob,
>>
>> Thank you for your comments.
>>
>> On 16.06.2020 00:22, Rob Herring wrote:
>>> On Mon, Jun 15, 2020 at 3:30 PM Geert Uytterhoeven <geert@xxxxxxxxxxxxxx> wrote:
>>>> Hi Adrian,
>>>>
>>>> On Mon, Jun 15, 2020 at 6:05 PM Adrian Fiergolski
>>>> <adrian.fiergolski@xxxxxxxxxxxxx> wrote:
>>>>> On 15.06.2020 16:40, Geert Uytterhoeven wrote:
>>>>>> On Mon, Jun 15, 2020 at 4:06 PM Adrian Fiergolski
>>>>>> <adrian.fiergolski@xxxxxxxxxxxxx> wrote:
>>>>>>> Sorry for the typo in the example device tree:
>>>>>>>
>>>>>>> On 15.06.2020 15:57, Adrian Fiergolski wrote:
>>>>>>>> On 15.06.2020 15:07, Geert Uytterhoeven wrote:
>>>>>>>>> On Mon, Jun 15, 2020 at 3:01 PM Adrian Fiergolski
>>>>>>>>> <adrian.fiergolski@xxxxxxxxxxxxx> wrote:
>>>>>>>>>> On 13.06.2020 09:33, Geert Uytterhoeven wrote:
>>>>>>>>>>> On Fri, Jun 12, 2020 at 6:26 PM Adrian Fiergolski
>>>>>>>>>>> <adrian.fiergolski@xxxxxxxxxxxxx> wrote:
>>>>>>>>>>> I have a daisy chain of three ltc2634 slaves (iio/dac/ltc2632.c)
>>>>>>>>>>> connected to a single chip select of the cadence-spi master. I have the
>>>>>>>>>>> impression such a configuration is supported by none of those two
>>>>>>>>>>> drivers. I could try to extend both, however, I haven't found any other
>>>>>>>>>>> SPI driver, where I could find implementation inspiration. Is it
>>>>>>>>>>> supported by kernel?
>>>>>>>>>>>
>>>>>>>>>>> drivers/gpio/gpio-max3191x.c supports "#daisy-chained-devices".
>>>>>>>>>>> drivers/gpio/gpio-74x164.c supports multiple shift registers through the
>>>>>>>>>>> "registers-number" DT property.
>>>>>>>>>>>
>>>>>>>>>>> So both drivers handle this in their SPI slave drivers.
>>>>>>>>>>>
>>>>>>>>>>> Of course this does not handle the mixed case, i.e. daisy-chaining
>>>>>>>>>>> different types of devices.
>>>>>>>>>>>
>>>>>>>>>>> The documentation mentions only about the common 'daisy-chained-devices'
>>>>>>>>>>> property (devicetree/bindings/common-properties.txt). However, in order
>>>>>>>>>>> to try to implement it in the master driver, IMHO, the spi subsystem
>>>>>>>>>>> would need to have a call 'no-operation' to other nodes on the
>>>>>>>>>>> daisy-chain, which are not addressed by the given SPI access. Is there
>>>>>>>>>>> any recommended approach to address this case?
>>>>>>>>>>>
>>>>>>>>>>> Supporting this in a generic way would indeed be nice, as it would mean
>>>>>>>>>>> individual SPI slave drivers no longer have to care about it.
>>>>>>>>>>> However, that may be difficult, as the master needs to known which
>>>>>>>>>>> dummy (no-op) data is safe to shift through the non-addresses SPI slaves.
>>>>>>>>>> In fact, the ultimate solution would be to have it solved at the level
>>>>>>>>>> of the spi subsystem:
>>>>>>>>>>
>>>>>>>>>>   * /spi_device struct/ would contain extra callback which returns msg
>>>>>>>>>>     to be sent for no operation.
>>>>>>>>>>   * spi_device struct would contain a pointer to the list describing the
>>>>>>>>>>     given daisy chain (list of spi_devices on the chain)
>>>>>>>>>>   * /spi_device struct /would contain extra u8 daisy_chain_msg_length
>>>>>>>>>>     indicating length of a command of the addressed device if it's on
>>>>>>>>>>     the daisy chain
>>>>>>>>>>     For example, in case of the ltc2634 device, the regular message
>>>>>>>>>>     consists of 24 bits, but when device is a part of a daisy chain, the
>>>>>>>>>>     messages are 32 bits. This 32 would be stored in
>>>>>>>>>>     /daisy_chain_msg_length./
>>>>>>>>>>   * When /spi_write/ was called (include/linux/spi/spi.h), the
>>>>>>>>>>     /spi_message_init_with_transfer/ would create a msg of length equal
>>>>>>>>>>     to a sum of /daisy_chain_msg_length/ of all devices on the chain.
>>>>>>>>>>     Afterwards, in /spi_message_init_with_transfers/, the actual message
>>>>>>>>>>     would be filled with the command of the addressed device on the
>>>>>>>>>>     chain and no_operation content for all other devices on the chain
>>>>>>>>>>     not being addressed
>>>>>>>>> Sounds good to me.
>>>>>>>>>
>>>>>>>>>>   * I think in such a case, the /daisy-chained-devices /property would
>>>>>>>>>>     be not used, as chains would be build basing on the assigned
>>>>>>>>>>     chipselect (reg property).
>>>>>>>>> So you still have to describe the chain in DT in some way.
>>>>>>>>> As there can be only a single sub node with the same unit address
>>>>>>>>> (= chip select), you probably need a container with that address, which
>>>>>>>>> would contain all devices in the chain, in order (unit addresses 0, 1, ...).
>>>>>>>> Good point. So maybe at the level of the device tree, it could be
>>>>>>>> described like that (based on the spi-cadence example):
>>>>>>>>
>>>>>>>>         spi0: spi@ff040000 {
>>>>>>>>             compatible = "cdns,spi-r1p6";
>>>>>>>>             status = "disabled";
>>>>>>>>             interrupt-parent = <&gic>;
>>>>>>>>             interrupts = <0 19 4>;
>>>>>>>>             reg = <0x0 0xff040000 0x0 0x1000>;
>>>>>>>>             clock-names = "ref_clk", "pclk";
>>>>>>>>             #address-cells = <1>;
>>>>>>>>             #size-cells = <0>;
>>>>>>>>             power-domains = <&zynqmp_firmware PD_SPI_0>;
>>>>>>>>             daisy-chain0 : daisy_chain@0 {
>>>>>>>>                #address-cells = <1>;
>>>>>>>>                #size-cells = <0>;
>>>>>>>>                reg = <0>;
>>>>>>>>                daisy-chained-devices = 2;
>>>>>>>>
>>>>>>>>                dac0: ltc2632@0 {
>>>>>>>>                    compatible = "lltc,ltc2634-l12";
>>>>>>>>                    reg = <0>;
>>>>>>>>                    spi-max-frequency = <1000000>;
>>>>>>>>                };
>>>>>>>>                dac1: ltc2632@1 {
>>>>>>>>                    compatible = "lltc,ltc2634-l12";
>>>>>>>>                    reg = <1>;
>>>>>>>>                    spi-max-frequency = <2000000>;
>>>>>>>>                };
>>>>>>>>            };
>>>>>>>>         };
>>>>>>>         spi0: spi@ff040000 {
>>>>>>>             compatible = "cdns,spi-r1p6";
>>>>>>>             status = "disabled";
>>>>>>>             interrupt-parent = <&gic>;
>>>>>>>             interrupts = <0 19 4>;
>>>>>>>             reg = <0x0 0xff040000 0x0 0x1000>;
>>>>>>>             clock-names = "ref_clk", "pclk";
>>>>>>>             #address-cells = <1>;
>>>>>>>             #size-cells = <0>;
>>>>>>>             power-domains = <&zynqmp_firmware PD_SPI_0>;
>>>>>>>             daisy-chain0 : daisy_chain@0 {
>>>>>>>                #address-cells = <1>;
>>>>>>>                #size-cells = <0>;
>>>>>>>                #daisy-chained-devices = <2>;
>>>>>> You probably want a proper "compatible" value here instead.
>>>>>> I don't think "#daisy-chained-devices" is needed, it can be derived
>>>>>> from the number of subnodes.
>>> This is not how '#daisy-chained-devices' works either. The chained
>>> devices are represented as a single node and a single driver instance
>>> handles multiple physical devices.
>>>
>>> The above looks like mux'ed access rather than creating a 'super'
>>> device. Would you want to program all N chips in one SPI transfer or
>>> it's one device at a time? (We should be careful with the bindings
>>> that we're not encoding that into the binding as that could evolve
>>> with the OS.)
>> Well, I think the described concept of the daisy chain doesn't apply
>> here then. In principle, we can have different SPI devices, where each
>> is served by a different driver. Together, they create a long register
>> through which data is shifted. IMHO, the fact that the given device is
>> on the daisy chain with other devices should be transparent for its SPI
>> device driver and handled at the level of the SPI subsystem: at SPI
>> write/read stage, the SPI subsystem would combine message of a addressed
>> device with the specific no-operation pattern of all other devices being
>> present on the chain (each device may have its specific no-operation
>> pattern).
>>
>> At the moment, I wouldn't try to address a few SPI devices in a single
>> access.
>>
>> As you can see, it's no really a mux access like in case of e.g. I2C:
>> there, we have a physical device performing multiplexing. Once the I2C
>> bus is switched, no extra effort is required in communication between
>> the I2C master and I2C device (slave).
>>
>>>>> compatible = "daisy_chain" or compatible ="simple-bus" would be better?
>>>> Not "simple-bus", as this is not a simple memory-mapped bus.
>>>> I'd use something like "spi,daisy-chain", to be validated by RobH.
>>> Okay, but what makes it generic?
>>>
>>> What happens with power control of the child devices? While I'd think
>>> the only sane design is shared supplies, resets, clocks, etc. (and
>>> daisy-chained-devices certainly assumes that), it's certainly possible
>>> some cross device control is needed.
>>>
>>>>> Both could be caught by of_register_spi_devices to populate the daisy
>>>>> chain. Do you agree that at that level the chip select could be defined?
>>>> Or by a separate SPI device driver that matches against "spi,daisy-chain",
>>>> and parse the subnodes.
>> I have been thinking about it and came to a similar idea. However, I
>> didn't find a straightforward way to associate the SPI devices with the
>> abstract daisy-chain device which would then eventually be linked with
>> the SPI controller. It would a simple and clean implementation then: SPI
>> devices at write/read would call daisy-chain device which would create a
>> full message and pass it to SPI controller. The issue is that the SPI
>> device requires for all calls SPI controller. It implies that SPI
>> daisy-chain would need to be SPI controller as well. However, I think
>> currently the SPI concept in the kernel is fixed: SPI controller <-> SPI
>> device and there is no place to implement SPI controller <-> SPI
>> controller (daisy-chain device) <-> SPI device. Any suggestions on how
>> to address it?
>>
>>>>> The reg properties from the sub-nodes (defining actual spi devices)
>>>>> would be ignored, thus not even needed to be defined.
>>>> They are needed to determine the order in the chain.
>>> Agreed, you need something to address a device. A better address might
>>> be the bit position of the device in the chain. Then this could work
>>> for any mixture of devices that support chaining (though you'd need to
>>> know what's a nop for each device).
>> Well, the idea was that the daisy chain will be populated basing on all
>> sub-nodes and their order in the device tree. If we go for a reg
>> numbering the device in the chain, I think we need then
>> '#daisy-chained-devices' as well, such kernel knows a priori the length
>> of the chain and can issue an error in case some reg values are missing
>> on a given chain.
>>
>> Regards,
>>
>> Adrian
>>
>From d0af2938bafc81ca2d56a92db7647e1e5a454205 Mon Sep 17 00:00:00 2001
From: Adrian Fiergolski <adrian.fiergolski@xxxxxxxxxxxxx>
Date: Fri, 19 Jun 2020 17:10:21 +0200
Subject: [PATCH] Add SPI daisy chain support.

Signed-off-by: Adrian Fiergolski <adrian.fiergolski@xxxxxxxxxxxxx>
---
 .../bindings/spi/spi-daisy_chain.txt          |  62 +++
 Documentation/spi/00-INDEX                    |   2 +
 drivers/spi/Kconfig                           |   5 +
 drivers/spi/Makefile                          |   1 +
 drivers/spi/spi-daisy_chain.c                 | 419 ++++++++++++++++++
 drivers/spi/spi.c                             |  65 ++-
 include/linux/spi/spi-daisy_chain.h           |  32 ++
 include/linux/spi/spi.h                       |  15 +
 10 files changed, 620 insertions(+), 24 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/spi/spi-daisy_chain.txt
 create mode 100644 drivers/spi/spi-daisy_chain.c
 create mode 100644 include/linux/spi/spi-daisy_chain.h

diff --git a/Documentation/devicetree/bindings/spi/spi-daisy_chain.txt b/Documentation/devicetree/bindings/spi/spi-daisy_chain.txt
new file mode 100644
index 000000000000..26e1d2dc4992
--- /dev/null
+++ b/Documentation/devicetree/bindings/spi/spi-daisy_chain.txt
@@ -0,0 +1,62 @@
+spi-daisy_chain : The driver handling SPI daisy chains
+
+Required properties for the daisy chain virtual device:
+- compatible		: Should be "spi,daisy_chain"
+- reg			: Chip select assigned to the chain
+
+Required properties for the SPI devices on a common SPI chain (nodes of
+daisy_chain):
+- spi-daisy-chain-len  : Length (in bytes) of the SPI transfer,
+  		         when the SPI device is part of a device chain.
+- spi-daisy-chain-noop : Byte string of no-operation command which should
+  		         be send when device is not addressed during the
+			 given SPI transfer
+
+Optional properties for the SPI devices on a common SPI chain (nodes of
+daisy_chain):
+- spi-daisy-chain-bits_per_word : no-operation transfers involve
+                                  one or more words; word sizes like
+				  eight or 12 bits are common.
+				  In-memory wordsizes are powers of two
+				  bytes (e.g. 20 bit samples use 32 bits).
+				  If not defined, it is assumed to be 8.
+
+Example:
+
+&spi0 {
+	status = "okay";
+	num-cs = <2>;
+	is-decoded-cs = <0>;
+	
+	daisy_chain0: daisy_chain@0 {
+	        compatible = "spi,daisy_chain";
+		spi-max-frequency = <10000000>;
+		reg = <0>;
+
+		dac0: ltc2632@0 {
+	              compatible = "lltc,ltc2634-l12";
+		      spi-daisy-chain-len = <4>;
+		      spi-daisy-chain-noop = [00 F0 00 00];
+		};
+		dac1: ltc2632@1 {
+	              compatible = "lltc,ltc2634-l12";
+		      spi-daisy-chain-len = <4>;
+		      spi-daisy-chain-noop = [00 F0 00 00];
+		};
+		dac2: ltc2632@2 {
+	              compatible = "lltc,ltc2634-l12";
+		      spi-daisy-chain-len = <4>;
+		      spi-daisy-chain-noop = [00 F0 00 00];
+		};
+	};
+};
+
+The virtual daisy chain device is represented as a regular SPI device. Its nodes
+define physical devices available on the chain. The order of the nodes defines
+the order of the physical devices on the chain: MOSI pin of a device represented
+by first node is the last one on the MOSI daisy chain. The daisy-chain
+functionality is transparent to the drivers of physical devices on the
+chain. All nodes share SPI mode, chip select and max speed of the virtual daisy
+chain device. Once one of the physical device is accessed, the spi-daisy_chain
+driver combines this data with no-operation commands of all other devices on the
+chain.
diff --git a/Documentation/spi/00-INDEX b/Documentation/spi/00-INDEX
index 8e4bb17d70eb..457aec3e68ec 100644
--- a/Documentation/spi/00-INDEX
+++ b/Documentation/spi/00-INDEX
@@ -12,5 +12,7 @@ spi-lm70llp
 	- Connecting an LM70-LLP sensor to the kernel via the SPI subsys.
 spi-sc18is602
 	- NXP SC18IS602/603 I2C-bus to SPI bridge
+spi-daisy_chain
+	- Daisy chain driver
 spi-summary
 	- (Linux) SPI overview. If unsure about SPI or SPI in Linux, start here.
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 2608d941ee03..0d3ea783f305 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -54,6 +54,11 @@ 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"
+	help
+	  Enable this option if you want to enable the SPI daist chain support.
+
 comment "SPI Master Controller Drivers"
 
 config SPI_ALTERA
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index c991bb287567..c4a93b5ae4cb 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_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..ded27deb3c0d
--- /dev/null
+++ b/drivers/spi/spi-daisy_chain.c
@@ -0,0 +1,419 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * A driver taking care of handling SPI daisy chaines.
+ *
+ * Copyright (C) 2020 Fastree3D
+ *	Adrian Fiergolski <Adrian.Fiergolski@xxxxxxxxxxxxx>
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/of_device.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 beeing changed
+			if (tr->tx_nbits)
+				switch (spi->mode &
+					(SPI_TX_DUAL | SPI_TX_QUAD)) {
+				case 0:
+					if (tr->tx_nbits & SPI_NBITS_SINGLE)
+						break;
+				case SPI_TX_DUAL:
+					if (tr->tx_nbits & SPI_NBITS_DUAL)
+						break;
+				case SPI_TX_QUAD:
+					if (tr->tx_nbits & SPI_NBITS_QUAD)
+						break;
+				default:
+					dev_err(&spi->dev,
+						"Unsupported tx mode on daisy chain");
+					return -EINVAL;
+				}
+
+			if (tr->rx_nbits)
+				switch (spi->mode &
+					(SPI_RX_DUAL | SPI_RX_QUAD)) {
+				case 0:
+					if (tr->rx_nbits & SPI_NBITS_SINGLE)
+						break;
+				case SPI_RX_DUAL:
+					if (tr->rx_nbits & SPI_NBITS_DUAL)
+						break;
+				case SPI_RX_QUAD:
+					if (tr->rx_nbits & SPI_NBITS_QUAD)
+						break;
+				default:
+					dev_err(&spi->dev,
+						"Unsupported rx mode on daisy chain");
+					return -EINVAL;
+				}
+
+			//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:
+	if (ntr->tx_buf)
+		kfree(ntr->tx_buf);
+	if (ntr->rx_buf)
+		kfree(ntr->rx_buf);
+	kfree(ntr);
+	return rc;
+}
+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);
+
+		if (message->daisy_chain_new_transfer->tx_buf)
+			kfree(message->daisy_chain_new_transfer->tx_buf);
+
+		if (message->daisy_chain_new_transfer->rx_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@xxxxxxxxxxxxx>");
+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 fd3f1aa79f26..4f32456f8cd0 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -29,6 +29,9 @@
 #include <linux/mod_devicetable.h>
 #include <linux/spi/spi.h>
 #include <linux/spi/spi-mem.h>
+#ifdef	CONFIG_SPI_DAISY_CHAIN
+#include <linux/spi/spi-daisy_chain.h>
+#endif
 #include <linux/of_gpio.h>
 #include <linux/pm_runtime.h>
 #include <linux/pm_domain.h>
@@ -478,14 +481,33 @@ 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;
+			else
+				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)
@@ -529,14 +551,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
 
 	if (ctlr->cs_gpios)
 		spi->cs_gpio = ctlr->cs_gpios[spi->chip_select];
@@ -561,7 +596,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);
@@ -3116,6 +3154,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
@@ -3149,6 +3193,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..beb409b0be2e
--- /dev/null
+++ b/include/linux/spi/spi-daisy_chain.h
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * A driver taking care of handling daisy chaines.
+ *
+ * Copyright (C) 2020 Fastree3D
+ *	Adrian Fiergolski <Adrian.Fiergolski@xxxxxxxxxxxxx>
+ */
+
+#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 b5c84c1507cd..c247d1e2cfb9 100644
--- a/include/linux/spi/spi.h
+++ b/include/linux/spi/spi.h
@@ -102,6 +102,9 @@ void spi_statistics_add_transfer_stats(struct spi_statistics *stats,
  * @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.
@@ -144,6 +147,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;
@@ -821,6 +827,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
@@ -851,6 +862,10 @@ struct spi_transfer {
 struct spi_message {
 	struct list_head	transfers;
 
+#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;
-- 
2.27.0


[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]


  Powered by Linux