Skip to content
This repository has been archived by the owner on Jan 23, 2025. It is now read-only.

Device Tree

Noralf Trønnes edited this page Oct 30, 2022 · 2 revisions

In order to use the board with kernel drivers, the kernel needs to know about the devices that are connected.

For SPI there's no easy way to do this like it is for the other busses the board supports (see tests for how it's done).

The Multifunction Devices kernel subsystem offers two ways to add such SPI devices:

  • Device Tree
  • ACPI

drivers/mfd/dln2.c has acpi_match set in the mfd_cell structure so that should be supported.

However of_compatible is not set, so Device Tree is not supported.

The following is my hack to make it work for SPI and some notes I've made along the way.

The notes have been resurrected from various files 10 months after the fact so don't expect it to work for your use case as-is. The work was done on the downstream Raspberry Pi kernel version 5.10.63.

Support Device Tree

diff --git a/drivers/mfd/dln2.c b/drivers/mfd/dln2.c
index 852129ea0766..217a58ff2aad 100644
--- a/drivers/mfd/dln2.c
+++ b/drivers/mfd/dln2.c
@@ -9,6 +9,8 @@
  *  Copyright (c) 2010-2011 Ericsson AB
  */
 
+#define DEBUG
+
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/types.h>
@@ -39,7 +41,7 @@ struct dln2_response {
 #define CMD_GET_DEVICE_SN		DLN2_GENERIC_CMD(0x31)
 
 #define DLN2_HW_ID			0x200
-#define DLN2_USB_TIMEOUT		200	/* in ms */
+#define DLN2_USB_TIMEOUT		2000 //200	/* in ms */
 #define DLN2_MAX_RX_SLOTS		16
 #define DLN2_MAX_URBS			16
 #define DLN2_RX_BUF_SIZE		512
@@ -710,6 +712,7 @@ static const struct mfd_cell dln2_devs[] = {
 	},
 	{
 		.name = "dln2-spi",
+		.of_compatible = "diolan,dln2-spi",
 		.acpi_match = &dln2_acpi_match_spi,
 		.platform_data = &dln2_pdata_spi,
 		.pdata_size = sizeof(struct dln2_platform_data),
@@ -777,6 +780,7 @@ static int dln2_probe(struct usb_interface *interface,
 	int ret;
 	int i, j;
 
+dev_info(dev, "%s: of_node=%px '%s'\n", __func__, dev->of_node, of_node_full_name(dev->of_node));
 	if (hostif->desc.bInterfaceNumber != 0 ||
 	    hostif->desc.bNumEndpoints < 2)
 		return -ENODEV;
diff --git a/drivers/mfd/mfd-core.c b/drivers/mfd/mfd-core.c
index fc00aaccb5f7..c4bc0b3481ec 100644
--- a/drivers/mfd/mfd-core.c
+++ b/drivers/mfd/mfd-core.c
@@ -148,6 +148,7 @@ static int mfd_match_of_node_to_dev(struct platform_device *pdev,
 		return -EAGAIN;
 
 allocate_of_node:
+/*
 	of_entry = kzalloc(sizeof(*of_entry), GFP_KERNEL);
 	if (!of_entry)
 		return -ENOMEM;
@@ -155,6 +156,7 @@ static int mfd_match_of_node_to_dev(struct platform_device *pdev,
 	of_entry->dev = &pdev->dev;
 	of_entry->np = np;
 	list_add_tail(&of_entry->list, &mfd_of_node_list);
+*/
 
 	pdev->dev.of_node = np;
 	pdev->dev.fwnode = &np->fwnode;
@@ -205,8 +207,12 @@ static int mfd_add_device(struct device *parent, int id,
 	if (ret < 0)
 		goto fail_res;
 
+	dev_info(parent, "%s: parent->of_node=%s cell->name=%s cell->of_compatible=%s\n",
+		__func__, of_node_full_name(parent->of_node), cell->name, cell->of_compatible);
+
 	if (IS_ENABLED(CONFIG_OF) && parent->of_node && cell->of_compatible) {
 		for_each_child_of_node(parent->of_node, np) {
+			dev_info(parent, "    %s\n", of_node_full_name(np));
 			if (of_device_is_compatible(np, cell->of_compatible)) {
 				/* Ignore 'disabled' devices error free */
 				if (!of_device_is_available(np)) {
diff --git a/drivers/spi/spi-dln2.c b/drivers/spi/spi-dln2.c
index 9a4d942fafcf..830d1dbc6bf4 100644
--- a/drivers/spi/spi-dln2.c
+++ b/drivers/spi/spi-dln2.c
@@ -5,6 +5,8 @@
  * Copyright (c) 2014 Intel Corporation
  */
 
+#define DEBUG
+
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/platform_device.h>
@@ -683,6 +685,8 @@ static int dln2_spi_probe(struct platform_device *pdev)
 	struct device *dev = &pdev->dev;
 	int ret;
 
+	dev_info(dev, "%s: of_node=%pOF\n", __func__, dev->of_node);
+
 	master = spi_alloc_master(&pdev->dev, sizeof(*dln2));
 	if (!master)
 		return -ENOMEM;
@@ -864,9 +868,16 @@ static const struct dev_pm_ops dln2_spi_pm = {
 			   dln2_spi_runtime_resume, NULL)
 };
 
+static const struct of_device_id dln2_spi_of_match[] = {
+	{ .compatible = "diolan,dln2-spi", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, dln2_spi_of_match);
+
 static struct platform_driver spi_dln2_driver = {
 	.driver = {
 		.name	= "dln2-spi",
+		.of_match_table = dln2_spi_of_match,
 		.pm	= &dln2_spi_pm,
 	},
 	.probe		= dln2_spi_probe,

This is the Device Tree overlay I used on a Raspberry Pi:

/dts-v1/;
/plugin/;

/ {
	compatible = "brcm,bcm2835";

	fragment@0 {
		target = <&pcie0>;
		__overlay__ {
			// Existing node: PCI host bridge
			pci@1,0 {
				// Existing node: xHCI Host Controller
				usb@1,0 {
					// USB2.0 Hub
					usb1@1 {
						compatible = "usb2109,3431";
						reg = <1>;
						#address-cells = <1>;
						#size-cells = <0>;

						// DLN2 USB Device
						dln2@3 {
							compatible = "usba257,2013";
							reg = <3>;
							#address-cells = <1>;
							#size-cells = <0>;

							// DLN2 USB interface
							dln2_intf@0 {
								reg = <0 1>;
								#address-cells = <2>;
								#size-cells = <0>;

								dln2_spi: dln2-spi {
									compatible = "diolan,dln2-spi";

									eeprom@0 {
										compatible = "atmel,at25";
										reg = <0>;
										spi-max-frequency = <1000000>;

										pagesize = <128>;
										size = <65536>;
										address-width = <16>;
									};
								};
							};
						};
					};
				};
			};
		};
	};
};

I used this to figure out how to write that overlay:

diff --git a/drivers/pci/of.c b/drivers/pci/of.c
index ac24cd5439a9..b62057de45bf 100644
--- a/drivers/pci/of.c
+++ b/drivers/pci/of.c
@@ -13,17 +13,22 @@
 #include <linux/of_irq.h>
 #include <linux/of_address.h>
 #include <linux/of_pci.h>
+#include <linux/of.h>
 #include "pci.h"
 
 #ifdef CONFIG_PCI
 void pci_set_of_node(struct pci_dev *dev)
 {
-	if (!dev->bus->dev.of_node)
+	if (!dev->bus->dev.of_node) {
+		dev_info(&dev->dev, "%s: dev->bus->dev.of_node=NULL\n", __func__);
 		return;
+	}
 	dev->dev.of_node = of_pci_find_child_device(dev->bus->dev.of_node,
 						    dev->devfn);
 	if (dev->dev.of_node)
 		dev->dev.fwnode = &dev->dev.of_node->fwnode;
+
+	dev_info(&dev->dev, "%s: of_node=%px '%s'\n", __func__, dev->dev.of_node, of_node_full_name(dev->dev.of_node));
 }
 
 void pci_release_of_node(struct pci_dev *dev)
@@ -49,6 +54,8 @@ void pci_set_bus_of_node(struct pci_bus *bus)
 
 	if (bus->dev.of_node)
 		bus->dev.fwnode = &bus->dev.of_node->fwnode;
+
+	dev_info(&bus->dev, "%s: of_node=%px '%s' (bus->self=%px)\n", __func__, bus->dev.of_node, of_node_full_name(bus->dev.of_node), bus->self);
 }
 
 void pci_release_bus_of_node(struct pci_bus *bus)
diff --git a/drivers/usb/core/of.c b/drivers/usb/core/of.c
index 617e92569b2c..a7492aed5185 100644
--- a/drivers/usb/core/of.c
+++ b/drivers/usb/core/of.c
@@ -27,14 +27,21 @@ struct device_node *usb_of_get_device_node(struct usb_device *hub, int port1)
 	struct device_node *node;
 	u32 reg;
 
+	dev_info(&hub->dev, "%s(port1=%d):\n", __func__, port1);
+
 	for_each_child_of_node(hub->dev.of_node, node) {
 		if (of_property_read_u32(node, "reg", &reg))
 			continue;
 
-		if (reg == port1)
+		dev_info(&hub->dev, "    reg=%u\n", reg);
+
+		if (reg == port1) {
+			dev_info(&hub->dev, "    node=%px '%s'\n", node, node->full_name);
 			return node;
+		}
 	}
 
+	dev_info(&hub->dev, "    node=NULL\n");
 	return NULL;
 }
 EXPORT_SYMBOL_GPL(usb_of_get_device_node);
@@ -59,19 +66,24 @@ bool usb_of_has_combined_node(struct usb_device *udev)
 	struct usb_device_descriptor *ddesc = &udev->descriptor;
 	struct usb_config_descriptor *cdesc;
 
-	if (!udev->dev.of_node)
+	if (!udev->dev.of_node) {
+		dev_info(&udev->dev, "%s: false (!of_node)\n", __func__);
 		return false;
+	}
 
 	switch (ddesc->bDeviceClass) {
 	case USB_CLASS_PER_INTERFACE:
 	case USB_CLASS_HUB:
 		if (ddesc->bNumConfigurations == 1) {
 			cdesc = &udev->config->desc;
-			if (cdesc->bNumInterfaces == 1)
+			if (cdesc->bNumInterfaces == 1) {
+				dev_info(&udev->dev, "%s: true '%s'\n", __func__, of_node_full_name(udev->dev.of_node));
 				return true;
+			}
 		}
 	}
 
+	dev_info(&udev->dev, "%s: false '%s'\n", __func__, of_node_full_name(udev->dev.of_node));
 	return false;
 }
 EXPORT_SYMBOL_GPL(usb_of_has_combined_node);
@@ -94,14 +106,21 @@ usb_of_get_interface_node(struct usb_device *udev, u8 config, u8 ifnum)
 	struct device_node *node;
 	u32 reg[2];
 
+	dev_info(&udev->dev, "%s(config=%u, ifnum=%u): '%s'\n", __func__, config, ifnum, of_node_full_name(udev->dev.of_node));
+
 	for_each_child_of_node(udev->dev.of_node, node) {
 		if (of_property_read_u32_array(node, "reg", reg, 2))
 			continue;
 
-		if (reg[0] == ifnum && reg[1] == config)
+		dev_info(&udev->dev, "    reg[0]=%u reg[1]=%u\n", reg[0], reg[1]);
+
+		if (reg[0] == ifnum && reg[1] == config) {
+			dev_info(&udev->dev, "    node=%px '%s'\n", node, node->full_name);
 			return node;
+		}
 	}
 
+	dev_info(&udev->dev, "    node=NULL\n");
 	return NULL;
 }
 EXPORT_SYMBOL_GPL(usb_of_get_interface_node);

Some notes from a text file I wrote at some point:

https://elixir.bootlin.com/linux/latest/source/Documentation/devicetree/bindings/pci/pci.txt https://elixir.bootlin.com/linux/latest/source/Documentation/devicetree/bindings/usb/usb-device.yaml

usb_of_has_combined_node() kicks in when there's only one interface. result: interface->dev.of_node == usb->dev.of_node

DT without overlay, pci@1,0 and usb@1,0 are auto created:

$ dtc -I fs /proc/device-tree | more

                pcie@7d500000 {
                        compatible = "brcm,bcm2711-pcie";
                        device_type = "pci";
                        interrupt-map-mask = < 0x00 0x00 0x00 0x07 >;
                        ranges = < 0x2000000 0x00 0xc0000000 0x06 0x00 0x00 0x40000000 >;
                        #interrupt-cells = < 0x01 >;
                        #address-cells = < 0x03 >;
                        interrupts = < 0x00 0x94 0x04 0x00 0x94 0x04 >;
                        interrupt-map = < 0x00 0x00 0x00 0x01 0x01 0x00 0x8f 0x04 >;
                        #size-cells = < 0x02 >;
                        phandle = < 0x2c >;
                        msi-parent = < 0x2c >;
                        reg = < 0x00 0x7d500000 0x00 0x9310 >;
                        dma-ranges = < 0x2000000 0x04 0x00 0x00 0x00 0x00 0xc0000000 >;
                        brcm,enable-ssc;
                        interrupt-names = "pcie\0msi";
                        msi-controller;

                        pci@1,0 {
                                ranges;
                                #address-cells = < 0x03 >;
                                #size-cells = < 0x02 >;
                                reg = < 0x00 0x00 0x00 0x00 0x00 >;

                                usb@1,0 {
                                        resets = < 0x2d 0x00 >;
                                        reg = < 0x10000 0x00 0x00 0x00 0x00 >;
                                };
                        };
                };

With overlay:

                pcie@7d500000 {
                        compatible = "brcm,bcm2711-pcie";
                        device_type = "pci";
                        interrupt-map-mask = < 0x00 0x00 0x00 0x07 >;
                        ranges = < 0x2000000 0x00 0xc0000000 0x06 0x00 0x00 0x40000000 >;
                        #interrupt-cells = < 0x01 >;
                        #address-cells = < 0x03 >;
                        interrupts = < 0x00 0x94 0x04 0x00 0x94 0x04 >;
                        interrupt-map = < 0x00 0x00 0x00 0x01 0x01 0x00 0x8f 0x04 >;
                        #size-cells = < 0x02 >;
                        phandle = < 0x2c >;
                        msi-parent = < 0x2c >;
                        reg = < 0x00 0x7d500000 0x00 0x9310 >;
                        dma-ranges = < 0x2000000 0x04 0x00 0x00 0x00 0x00 0xc0000000 >;
                        brcm,enable-ssc;
                        interrupt-names = "pcie\0msi";
                        msi-controller;

                        pci@1,0 {
                                hello = < 0x01 >;
                                ranges;
                                #address-cells = < 0x03 >;
                                #size-cells = < 0x02 >;
                                reg = < 0x00 0x00 0x00 0x00 0x00 >;

                                usb@1,0 {
                                        resets = < 0x2d 0x00 >;
                                        reg = < 0x10000 0x00 0x00 0x00 0x00 >;
                                        world = < 0x01 >;

                                        usb1@1 {
                                                compatible = "usb2109,3431";
                                                #address-cells = < 0x01 >;
                                                #size-cells = < 0x00 >;
                                                reg = < 0x01 >;

                                                dln2@3 {
                                                        compatible = "usba257,2013";
                                                        #address-cells = < 0x01 >;
                                                        #size-cells = < 0x00 >;
                                                        reg = < 0x03 >;

                                                        dln2_intf@0 {
                                                                #address-cells = < 0x02 >;
                                                                #size-cells = < 0x00 >;
                                                                reg = < 0x00 0x01 >;

                                                                dln2-spi {
                                                                        compatible = "diolan,dln2-spi";
                                                                        phandle = < 0xe4 >;

                                                                        eeprom@0 {
                                                                                compatible = "atmel,at25";
                                                                                address-width = < 0x10 >;
                                                                                size = < 0x10000 >;
                                                                                pagesize = < 0x80 >;
                                                                                reg = < 0x00 >;
                                                                                spi-max-frequency = < 0xf4240 >;
                                                                        };
                                                                };
                                                        };
                                                };
                                        };
                                };
                        };
                };

Kernel log from an instrumented kernel:

[    1.263861] brcm-pcie fd500000.pcie: host bridge /scb/pcie@7d500000 ranges:
[    1.263892] brcm-pcie fd500000.pcie:   No bus range found for /scb/pcie@7d500000, using [bus 00-ff]
[    1.263972] brcm-pcie fd500000.pcie:      MEM 0x0600000000..0x063fffffff -> 0x00c0000000
[    1.264061] brcm-pcie fd500000.pcie:   IB MEM 0x0000000000..0x00bfffffff -> 0x0400000000
[    1.316870] brcm-pcie fd500000.pcie: link up, 5.0 GT/s PCIe x1 (SSC)
[    1.317121]  (null): pci_set_bus_of_node: of_node=effd1d04 'pcie@7d500000' (bus->self=00000000)
[    1.317253] brcm-pcie fd500000.pcie: PCI host bridge to bus 0000:00
[    1.317273] pci_bus 0000:00: root bus resource [bus 00-ff]
[    1.317295] pci_bus 0000:00: root bus resource [mem 0x600000000-0x63fffffff] (bus address [0xc0000000-0xffffffff])
[    1.317325]  (null): pci_set_of_node: of_node=effd20a4 'pci@1,0'
[    1.317400] pci 0000:00:00.0: [14e4:2711] type 01 class 0x060400
[    1.317641] pci 0000:00:00.0: PME# supported from D0 D3hot
[    1.321286] PCI: bus0: Fast back to back transfers disabled
[    1.321334] pci_bus 0000:01: pci_set_bus_of_node: of_node=effd20a4 'pci@1,0' (bus->self=c1b8a800)
[    1.321532]  (null): pci_set_of_node: of_node=effd2228 'usb@1,0'
[    1.321683] pci 0000:01:00.0: [1106:3483] type 00 class 0x0c0330
[    1.321810] pci 0000:01:00.0: reg 0x10: [mem 0x00000000-0x00000fff 64bit]
[    1.322245] pci 0000:01:00.0: PME# supported from D0 D3cold
[    1.325909] PCI: bus1: Fast back to back transfers disabled
[    1.326006] pci 0000:00:00.0: BAR 8: assigned [mem 0x600000000-0x6000fffff]
[    1.326030] pci 0000:01:00.0: BAR 0: assigned [mem 0x600000000-0x600000fff 64bit]
[    1.326114] pci 0000:00:00.0: PCI bridge to [bus 01]
[    1.326140] pci 0000:00:00.0:   bridge window [mem 0x600000000-0x6000fffff]
[    1.326544] pcieport 0000:00:00.0: enabling device (0140 -> 0142)
[    1.326783] pcieport 0000:00:00.0: PME: Signaling with IRQ 61

[    1.527755] xhci_hcd 0000:01:00.0: enabling device (0140 -> 0142)
[    1.527933] xhci_hcd 0000:01:00.0: xHCI Host Controller
[    1.527969] xhci_hcd 0000:01:00.0: new USB bus registered, assigned bus number 1
[    1.530683] xhci_hcd 0000:01:00.0: hcc params 0x002841eb hci version 0x100 quirks 0x0000060000000890
[    1.531994] usb usb1: New USB device found, idVendor=1d6b, idProduct=0002, bcdDevice= 5.10
[    1.532013] usb usb1: New USB device strings: Mfr=3, Product=2, SerialNumber=1
[    1.532030] usb usb1: Product: xHCI Host Controller
[    1.532047] usb usb1: Manufacturer: Linux 5.10.63-v7l+ xhci-hcd
[    1.532063] usb usb1: SerialNumber: 0000:01:00.0
[    1.532553] usb usb1: usb_of_has_combined_node: true 'usb@1,0'
[    1.532801] hub 1-0:1.0: USB hub found
[    1.532899] hub 1-0:1.0: 1 port detected
[    1.533557] xhci_hcd 0000:01:00.0: xHCI Host Controller
[    1.533584] xhci_hcd 0000:01:00.0: new USB bus registered, assigned bus number 2
[    1.533611] xhci_hcd 0000:01:00.0: Host supports USB 3.0 SuperSpeed
[    1.534122] usb usb2: New USB device found, idVendor=1d6b, idProduct=0003, bcdDevice= 5.10
[    1.534141] usb usb2: New USB device strings: Mfr=3, Product=2, SerialNumber=1
[    1.534158] usb usb2: Product: xHCI Host Controller
[    1.534175] usb usb2: Manufacturer: Linux 5.10.63-v7l+ xhci-hcd
[    1.534192] usb usb2: SerialNumber: 0000:01:00.0
[    1.534626] usb usb2: usb_of_has_combined_node: true 'usb@1,0'
[    1.534980] hub 2-0:1.0: USB hub found
[    1.535053] hub 2-0:1.0: 4 ports detected

[    1.665649] usb usb1: usb_of_get_device_node(port1=1):
[    1.665674] usb usb1:     reg=1
[    1.665693] usb usb1:     node=effd234c 'usb1@1'

[    1.814774] usb 1-1: new high-speed USB device number 2 using xhci_hcd

[    1.997468] usb 1-1: New USB device found, idVendor=2109, idProduct=3431, bcdDevice= 4.21
[    1.997488] usb 1-1: New USB device strings: Mfr=0, Product=1, SerialNumber=0
[    1.997505] usb 1-1: Product: USB2.0 Hub
[    1.999172] usb 1-1: usb_of_has_combined_node: true 'usb1@1'
[    1.999613] hub 1-1:1.0: USB hub found
[    1.999926] hub 1-1:1.0: 4 ports detected

[    2.225981] usb 1-1: usb_of_get_device_node(port1=3):
[    2.226008] usb 1-1:     reg=3
[    2.226026] usb 1-1:     node=effd24a4 'dln2@3'

[    2.344840] usb 1-1.3: new full-speed USB device number 3 using xhci_hcd

[    2.495617] usb 1-1.3: New USB device found, idVendor=a257, idProduct=2013, bcdDevice= 0.00
[    2.495640] usb 1-1.3: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[    2.495658] usb 1-1.3: Product: Pico I/O Board
[    2.495675] usb 1-1.3: Manufacturer: Raspberry Pi
[    2.495692] usb 1-1.3: SerialNumber: E66038282339B435
[    2.498229] usb 1-1.3: usb_of_has_combined_node: false 'dln2@3'
[    2.498254] usb 1-1.3: usb_of_get_interface_node(config=1, ifnum=0): 'dln2@3'
[    2.498282] usb 1-1.3:     reg[0]=0 reg[1]=1
[    2.498301] usb 1-1.3:     node=effd25fc 'dln2_intf@0'

[    5.954344] dln2 1-1.3:1.0: dln2_probe: of_node=effd25fc 'dln2_intf@0'
[    5.956467] dln2 1-1.3:1.0: Diolan DLN2 serial 590984245
[    5.956499] dln2 1-1.3:1.0: mfd_add_device: parent->of_node=dln2_intf@0 cell->name=dln2-gpio cell->of_compatible=(null)
[    5.956870] dln2 1-1.3:1.0: mfd_add_device: parent->of_node=dln2_intf@0 cell->name=dln2-i2c cell->of_compatible=(null)
[    5.957163] dln2 1-1.3:1.0: mfd_add_device: parent->of_node=dln2_intf@0 cell->name=dln2-spi cell->of_compatible=diolan,dln2-spi
[    5.957184] dln2 1-1.3:1.0:     dln2-spi
[    5.958681] dln2 1-1.3:1.0: mfd_add_device: parent->of_node=dln2_intf@0 cell->name=dln2-adc cell->of_compatible=(null)
[    5.959694] usbcore: registered new interface driver dln2
[    6.017204] dln2-spi dln2-spi.2.auto: DMA mask not set
[    6.017322] dln2-spi dln2-spi.2.auto: dln2_spi_probe: of_node=effd272c 'dln2-spi'
[    6.294562] spi_master spi7: Failed to power device: -22
Clone this wiki locally