diff mbox series

[v6,3/7] misc: Add support for LAN966x PCI device

Message ID 20240930121601.172216-4-herve.codina@bootlin.com (mailing list archive)
State Superseded
Headers show
Series Add support for the LAN966x PCI device using a DT overlay | expand

Checks

Context Check Description
netdev/tree_selection success Not a local patch

Commit Message

Herve Codina Sept. 30, 2024, 12:15 p.m. UTC
Add a PCI driver that handles the LAN966x PCI device using a device-tree
overlay. This overlay is applied to the PCI device DT node and allows to
describe components that are present in the device.

The memory from the device-tree is remapped to the BAR memory thanks to
"ranges" properties computed at runtime by the PCI core during the PCI
enumeration.

The PCI device itself acts as an interrupt controller and is used as the
parent of the internal LAN966x interrupt controller to route the
interrupts to the assigned PCI INTx interrupt.

Signed-off-by: Herve Codina <herve.codina@bootlin.com>
Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
 drivers/misc/Kconfig          |  24 ++++
 drivers/misc/Makefile         |   3 +
 drivers/misc/lan966x_pci.c    | 215 ++++++++++++++++++++++++++++++++++
 drivers/misc/lan966x_pci.dtso | 161 +++++++++++++++++++++++++
 drivers/pci/quirks.c          |   1 +
 5 files changed, 404 insertions(+)
 create mode 100644 drivers/misc/lan966x_pci.c
 create mode 100644 drivers/misc/lan966x_pci.dtso

Comments

Arnd Bergmann Oct. 2, 2024, 11:08 a.m. UTC | #1
On Mon, Sep 30, 2024, at 12:15, Herve Codina wrote:

> +			pci-ep-bus@0 {
> +				compatible = "simple-bus";
> +				#address-cells = <1>;
> +				#size-cells = <1>;
> +
> +				/*
> +				 * map @0xe2000000 (32MB) to BAR0 (CPU)
> +				 * map @0xe0000000 (16MB) to BAR1 (AMBA)
> +				 */
> +				ranges = <0xe2000000 0x00 0x00 0x00 0x2000000
> +				          0xe0000000 0x01 0x00 0x00 0x1000000>;

I was wondering about how this fits into the PCI DT
binding, is this a child of the PCI device, or does the
"pci-ep-bus" refer to the PCI device itself?

Where do the "0x01 0x00 0x00" and "0x00 0x00 0x00" addresses
come from? Shouldn't those be "02000010 0x00 0x00" and
"02000014 0x00 0x00" to refer to the first and second
relocatable 32-bit memory BAR?

     Arnd
Herve Codina Oct. 2, 2024, 12:41 p.m. UTC | #2
Hi Arnd,

On Wed, 02 Oct 2024 11:08:15 +0000
"Arnd Bergmann" <arnd@arndb.de> wrote:

> On Mon, Sep 30, 2024, at 12:15, Herve Codina wrote:
> 
> > +			pci-ep-bus@0 {
> > +				compatible = "simple-bus";
> > +				#address-cells = <1>;
> > +				#size-cells = <1>;
> > +
> > +				/*
> > +				 * map @0xe2000000 (32MB) to BAR0 (CPU)
> > +				 * map @0xe0000000 (16MB) to BAR1 (AMBA)
> > +				 */
> > +				ranges = <0xe2000000 0x00 0x00 0x00 0x2000000
> > +				          0xe0000000 0x01 0x00 0x00 0x1000000>;  
> 
> I was wondering about how this fits into the PCI DT
> binding, is this a child of the PCI device, or does the
> "pci-ep-bus" refer to the PCI device itself?

This is a child of the PCI device.
The overlay is applied at the PCI device node and so, the pci-ep-bus is
a child of the PCI device node.

> 
> Where do the "0x01 0x00 0x00" and "0x00 0x00 0x00" addresses
> come from? Shouldn't those be "02000010 0x00 0x00" and
> "02000014 0x00 0x00" to refer to the first and second
> relocatable 32-bit memory BAR?

These addresses are built dynamically by the PCI core during the PCI scan.
  https://elixir.bootlin.com/linux/v6.11/source/drivers/pci/of_property.c#L101
They are use to reference the BARs.
0x00 for BAR0, 0x01 for BAR1, ...

The full DT, once PCI device are present, scanned and the overlay applied,
looks like the following:
--- 8< ---
	pcie@d0070000 {
		/* Node present on the base device tree */
		compatible = "marvell,armada-3700-pcie";
		#address-cells = <0x03>;
		#size-cells = <0x02>;
		ranges = <0x82000000 0x00 0xe8000000 0x00 0xe8000000 0x00 0x7f00000
			  0x81000000 0x00 0x00 0x00 0xefff0000 0x00 0x10000>;
		device_type = "pci";
		...

		pci@0,0 {
			/*
			 * Node created at runtime during the PCI scan
			 * This node is PCI bridge (class 604)
			 */
			#address-cells = <0x03>;
			#size-cells = <0x02>;
			device_type = "pci";
			compatible = "pci11ab,100\0pciclass,060400\0pciclass,0604";
			ranges = <0x82000000 0x00 0xe8000000 0x82000000 0x00 0xe8000000 0x00 0x4400000>;
			...

			dev@0,0 {
				/*
				 * Node created at runtime during the
				 * PCI scan. This is my LAN966x PCI device.
				 */
				#address-cells = <0x03>;
				interrupts = <0x01>;
				#size-cells = <0x02>;
				compatible = "pci1055,9660\0pciclass,020000\0pciclass,0200";

				/*
				 * Ranges items allow to reference BAR0,
				 * BAR1, ... from children nodes.
				 * The property is created by the PCI core
				 * during the PCI bus scan.
				 */
				ranges = <0x00 0x00 0x00 0x82010000 0x00 0xe8000000 0x00 0x2000000
					  0x01 0x00 0x00 0x82010000 0x00 0xea000000 0x00 0x1000000
					  0x02 0x00 0x00 0x82010000 0x00 0xeb000000 0x00 0x800000
					  0x03 0x00 0x00 0x82010000 0x00 0xeb800000 0x00 0x800000
					  0x04 0x00 0x00 0x82010000 0x00 0xec000000 0x00 0x20000
					  0x05 0x00 0x00 0x82010000 0x00 0xec020000 0x00 0x2000>;
				...

				pci-ep-bus@0 {
					/* Node added by the overlay */
					#address-cells = <0x01>;
					#size-cells = <0x01>;
					compatible = "simple-bus";

					/*
					 * Remap 0xe2000000 to BAR0 and
					 * 0xe0000000 to BAR1
					 */
					ranges = <0xe2000000 0x00 0x00 0x00 0x2000000
						  0xe0000000 0x01 0x00 0x00 0x1000000>;
					...

					mdio@e200413c {
						#address-cells = <0x01>;
						resets = <0x25 0x00>;
						#size-cells = <0x00>;
						compatible = "microchip,lan966x-miim";
						reg = <0xe200413c 0x24 0xe2010020 0x04>;
						...
--- 8< ---

Hope this full picture helped to understand the address translations
involved.

Best regards,
Hervé
Arnd Bergmann Oct. 2, 2024, 2:31 p.m. UTC | #3
On Wed, Oct 2, 2024, at 12:41, Herve Codina wrote:
> On Wed, 02 Oct 2024 11:08:15 +0000
> "Arnd Bergmann" <arnd@arndb.de> wrote:
>> On Mon, Sep 30, 2024, at 12:15, Herve Codina wrote:
>> 
>> > +			pci-ep-bus@0 {
>> > +				compatible = "simple-bus";
>> > +				#address-cells = <1>;
>> > +				#size-cells = <1>;
>> > +
>> > +				/*
>> > +				 * map @0xe2000000 (32MB) to BAR0 (CPU)
>> > +				 * map @0xe0000000 (16MB) to BAR1 (AMBA)
>> > +				 */
>> > +				ranges = <0xe2000000 0x00 0x00 0x00 0x2000000
>> > +				          0xe0000000 0x01 0x00 0x00 0x1000000>;  
>> 
>> I was wondering about how this fits into the PCI DT
>> binding, is this a child of the PCI device, or does the
>> "pci-ep-bus" refer to the PCI device itself?
>
> This is a child of the PCI device.
> The overlay is applied at the PCI device node and so, the pci-ep-bus is
> a child of the PCI device node.

Ok

> 				/*
> 				 * Ranges items allow to reference BAR0,
> 				 * BAR1, ... from children nodes.
> 				 * The property is created by the PCI core
> 				 * during the PCI bus scan.
> 				 */
> 				ranges = <0x00 0x00 0x00 0x82010000 0x00 0xe8000000 0x00 0x2000000
> 					  0x01 0x00 0x00 0x82010000 0x00 0xea000000 0x00 0x1000000
> 					  0x02 0x00 0x00 0x82010000 0x00 0xeb000000 0x00 0x800000

>
> Hope this full picture helped to understand the address translations
> involved.

Right, that makes a lot of sense now, I wasn't aware of those
range properties getting set. Now I have a new question though:

Is this designed to work both on hosts using devicetree and on
those not using it? If this is used on devicetree on a board
that has a hardwired lan966x, we may want to include the
overlay contents in the board dts file itself in order to
describe any possible connections between the lan966x chip
and other onboard components such as additional GPIOs or
ethernet PHY chips, right?

      Arnd
Herve Codina Oct. 2, 2024, 4:02 p.m. UTC | #4
On Wed, 02 Oct 2024 14:31:13 +0000
"Arnd Bergmann" <arnd@arndb.de> wrote:

> On Wed, Oct 2, 2024, at 12:41, Herve Codina wrote:
> > On Wed, 02 Oct 2024 11:08:15 +0000
> > "Arnd Bergmann" <arnd@arndb.de> wrote:  
> >> On Mon, Sep 30, 2024, at 12:15, Herve Codina wrote:
> >>   
> >> > +			pci-ep-bus@0 {
> >> > +				compatible = "simple-bus";
> >> > +				#address-cells = <1>;
> >> > +				#size-cells = <1>;
> >> > +
> >> > +				/*
> >> > +				 * map @0xe2000000 (32MB) to BAR0 (CPU)
> >> > +				 * map @0xe0000000 (16MB) to BAR1 (AMBA)
> >> > +				 */
> >> > +				ranges = <0xe2000000 0x00 0x00 0x00 0x2000000
> >> > +				          0xe0000000 0x01 0x00 0x00 0x1000000>;    
> >> 
> >> I was wondering about how this fits into the PCI DT
> >> binding, is this a child of the PCI device, or does the
> >> "pci-ep-bus" refer to the PCI device itself?  
> >
> > This is a child of the PCI device.
> > The overlay is applied at the PCI device node and so, the pci-ep-bus is
> > a child of the PCI device node.  
> 
> Ok
> 
> > 				/*
> > 				 * Ranges items allow to reference BAR0,
> > 				 * BAR1, ... from children nodes.
> > 				 * The property is created by the PCI core
> > 				 * during the PCI bus scan.
> > 				 */
> > 				ranges = <0x00 0x00 0x00 0x82010000 0x00 0xe8000000 0x00 0x2000000
> > 					  0x01 0x00 0x00 0x82010000 0x00 0xea000000 0x00 0x1000000
> > 					  0x02 0x00 0x00 0x82010000 0x00 0xeb000000 0x00 0x800000  
> 
> >
> > Hope this full picture helped to understand the address translations
> > involved.  
> 
> Right, that makes a lot of sense now, I wasn't aware of those
> range properties getting set. Now I have a new question though:
> 
> Is this designed to work both on hosts using devicetree and on
> those not using it? If this is used on devicetree on a board
> that has a hardwired lan966x, we may want to include the
> overlay contents in the board dts file itself in order to
> describe any possible connections between the lan966x chip
> and other onboard components such as additional GPIOs or
> ethernet PHY chips, right?
> 
>       Arnd

On host with the base hardware described without device-tree (ACPI on
x86 for instance), I have a couple of patches not yet sent upstream.
With those patches, I have a the LAN966x PCI board working on x86.
I plan to send them as soon as this series is applied.

Rob said that before looking at ACPI, we need to have a working system
on DT based systems.
  https://lore.kernel.org/all/CAL_JsqKNC1Qv+fucobnzoXmxUYNockWR=BbGhds2tNAYZWqgOA@mail.gmail.com/

If hardwired on a board, the same LAN966x PCI driver could be used.
A possible improvement of the driver could be to request the overlay
from the user-space using request_firmware().
With that, the overlay can be extended with specific onboard parts the
LAN966x PCI device is connected to.

This improvement can be done later when the use case appears.

Best regards,
Hervé
diff mbox series

Patch

diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 3fe7e2a9bd29..8e5b06ac9b6f 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -610,6 +610,30 @@  config MARVELL_CN10K_DPI
 	  To compile this driver as a module, choose M here: the module
 	  will be called mrvl_cn10k_dpi.
 
+config MCHP_LAN966X_PCI
+	tristate "Microchip LAN966x PCIe Support"
+	depends on PCI
+	select OF
+	select OF_OVERLAY
+	select IRQ_DOMAIN
+	help
+	  This enables the support for the LAN966x PCIe device.
+	  This is used to drive the LAN966x PCIe device from the host system
+	  to which it is connected.
+
+	  This driver uses an overlay to load other drivers to support for
+	  LAN966x internal components.
+	  Even if this driver does not depend on these other drivers, in order
+	  to have a fully functional board, the following drivers are needed:
+	    - fixed-clock (COMMON_CLK)
+	    - lan966x-oic (LAN966X_OIC)
+	    - lan966x-cpu-syscon (MFD_SYSCON)
+	    - lan966x-switch-reset (RESET_MCHP_SPARX5)
+	    - lan966x-pinctrl (PINCTRL_OCELOT)
+	    - lan966x-serdes (PHY_LAN966X_SERDES)
+	    - lan966x-miim (MDIO_MSCC_MIIM)
+	    - lan966x-switch (LAN966X_SWITCH)
+
 source "drivers/misc/c2port/Kconfig"
 source "drivers/misc/eeprom/Kconfig"
 source "drivers/misc/cb710/Kconfig"
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index a9f94525e181..885b22989580 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -71,4 +71,7 @@  obj-$(CONFIG_TPS6594_ESM)	+= tps6594-esm.o
 obj-$(CONFIG_TPS6594_PFSM)	+= tps6594-pfsm.o
 obj-$(CONFIG_NSM)		+= nsm.o
 obj-$(CONFIG_MARVELL_CN10K_DPI)	+= mrvl_cn10k_dpi.o
+lan966x-pci-objs		:= lan966x_pci.o
+lan966x-pci-objs		+= lan966x_pci.dtbo.o
+obj-$(CONFIG_MCHP_LAN966X_PCI)	+= lan966x-pci.o
 obj-y				+= keba/
diff --git a/drivers/misc/lan966x_pci.c b/drivers/misc/lan966x_pci.c
new file mode 100644
index 000000000000..9c79b58137e5
--- /dev/null
+++ b/drivers/misc/lan966x_pci.c
@@ -0,0 +1,215 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Microchip LAN966x PCI driver
+ *
+ * Copyright (c) 2024 Microchip Technology Inc. and its subsidiaries.
+ *
+ * Authors:
+ *	Clément Léger <clement.leger@bootlin.com>
+ *	Hervé Codina <herve.codina@bootlin.com>
+ */
+
+#include <linux/device.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/pci.h>
+#include <linux/pci_ids.h>
+#include <linux/slab.h>
+
+/* Embedded dtbo symbols created by cmd_wrap_S_dtb in scripts/Makefile.lib */
+extern char __dtbo_lan966x_pci_begin[];
+extern char __dtbo_lan966x_pci_end[];
+
+struct pci_dev_intr_ctrl {
+	struct pci_dev *pci_dev;
+	struct irq_domain *irq_domain;
+	int irq;
+};
+
+static int pci_dev_irq_domain_map(struct irq_domain *d, unsigned int virq, irq_hw_number_t hw)
+{
+	irq_set_chip_and_handler(virq, &dummy_irq_chip, handle_simple_irq);
+	return 0;
+}
+
+static const struct irq_domain_ops pci_dev_irq_domain_ops = {
+	.map = pci_dev_irq_domain_map,
+	.xlate = irq_domain_xlate_onecell,
+};
+
+static irqreturn_t pci_dev_irq_handler(int irq, void *data)
+{
+	struct pci_dev_intr_ctrl *intr_ctrl = data;
+	int ret;
+
+	ret = generic_handle_domain_irq(intr_ctrl->irq_domain, 0);
+	return ret ? IRQ_NONE : IRQ_HANDLED;
+}
+
+static struct pci_dev_intr_ctrl *pci_dev_create_intr_ctrl(struct pci_dev *pdev)
+{
+	struct pci_dev_intr_ctrl *intr_ctrl __free(kfree) = NULL;
+	struct fwnode_handle *fwnode;
+	int ret;
+
+	fwnode = dev_fwnode(&pdev->dev);
+	if (!fwnode)
+		return ERR_PTR(-ENODEV);
+
+	intr_ctrl = kmalloc(sizeof(*intr_ctrl), GFP_KERNEL);
+	if (!intr_ctrl)
+		return ERR_PTR(-ENOMEM);
+
+	intr_ctrl->pci_dev = pdev;
+
+	intr_ctrl->irq_domain = irq_domain_create_linear(fwnode, 1, &pci_dev_irq_domain_ops,
+							 intr_ctrl);
+	if (!intr_ctrl->irq_domain) {
+		pci_err(pdev, "Failed to create irqdomain\n");
+		return ERR_PTR(-ENOMEM);
+	}
+
+	ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_INTX);
+	if (ret < 0) {
+		pci_err(pdev, "Unable alloc irq vector (%d)\n", ret);
+		goto err_remove_domain;
+	}
+	intr_ctrl->irq = pci_irq_vector(pdev, 0);
+	ret = request_irq(intr_ctrl->irq, pci_dev_irq_handler, IRQF_SHARED,
+			  pci_name(pdev), intr_ctrl);
+	if (ret) {
+		pci_err(pdev, "Unable to request irq %d (%d)\n", intr_ctrl->irq, ret);
+		goto err_free_irq_vector;
+	}
+
+	return_ptr(intr_ctrl);
+
+err_free_irq_vector:
+	pci_free_irq_vectors(pdev);
+err_remove_domain:
+	irq_domain_remove(intr_ctrl->irq_domain);
+	return ERR_PTR(ret);
+}
+
+static void pci_dev_remove_intr_ctrl(struct pci_dev_intr_ctrl *intr_ctrl)
+{
+	free_irq(intr_ctrl->irq, intr_ctrl);
+	pci_free_irq_vectors(intr_ctrl->pci_dev);
+	irq_dispose_mapping(irq_find_mapping(intr_ctrl->irq_domain, 0));
+	irq_domain_remove(intr_ctrl->irq_domain);
+	kfree(intr_ctrl);
+}
+
+static void devm_pci_dev_remove_intr_ctrl(void *intr_ctrl)
+{
+	pci_dev_remove_intr_ctrl(intr_ctrl);
+}
+
+static int devm_pci_dev_create_intr_ctrl(struct pci_dev *pdev)
+{
+	struct pci_dev_intr_ctrl *intr_ctrl;
+
+	intr_ctrl = pci_dev_create_intr_ctrl(pdev);
+	if (IS_ERR(intr_ctrl))
+		return PTR_ERR(intr_ctrl);
+
+	return devm_add_action_or_reset(&pdev->dev, devm_pci_dev_remove_intr_ctrl, intr_ctrl);
+}
+
+struct lan966x_pci {
+	struct device *dev;
+	int ovcs_id;
+};
+
+static int lan966x_pci_load_overlay(struct lan966x_pci *data)
+{
+	u32 dtbo_size = __dtbo_lan966x_pci_end - __dtbo_lan966x_pci_begin;
+	void *dtbo_start = __dtbo_lan966x_pci_begin;
+
+	return of_overlay_fdt_apply(dtbo_start, dtbo_size, &data->ovcs_id, dev_of_node(data->dev));
+}
+
+static void lan966x_pci_unload_overlay(struct lan966x_pci *data)
+{
+	of_overlay_remove(&data->ovcs_id);
+}
+
+static int lan966x_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+	struct device *dev = &pdev->dev;
+	struct lan966x_pci *data;
+	int ret;
+
+	/*
+	 * On ACPI system, fwnode can point to the ACPI node.
+	 * This driver needs an of_node to be used as the device-tree overlay
+	 * target. This of_node should be set by the PCI core if it succeeds in
+	 * creating it (CONFIG_PCI_DYNAMIC_OF_NODES feature).
+	 * Check here for the validity of this of_node.
+	 */
+	if (!dev_of_node(dev))
+		return dev_err_probe(dev, -EINVAL, "Missing of_node for device\n");
+
+	/* Need to be done before devm_pci_dev_create_intr_ctrl.
+	 * It allocates an IRQ and so pdev->irq is updated.
+	 */
+	ret = pcim_enable_device(pdev);
+	if (ret)
+		return ret;
+
+	ret = devm_pci_dev_create_intr_ctrl(pdev);
+	if (ret)
+		return ret;
+
+	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	pci_set_drvdata(pdev, data);
+	data->dev = dev;
+
+	ret = lan966x_pci_load_overlay(data);
+	if (ret)
+		return ret;
+
+	pci_set_master(pdev);
+
+	ret = of_platform_default_populate(dev_of_node(dev), NULL, dev);
+	if (ret)
+		goto err_unload_overlay;
+
+	return 0;
+
+err_unload_overlay:
+	lan966x_pci_unload_overlay(data);
+	return ret;
+}
+
+static void lan966x_pci_remove(struct pci_dev *pdev)
+{
+	struct lan966x_pci *data = pci_get_drvdata(pdev);
+
+	of_platform_depopulate(data->dev);
+
+	lan966x_pci_unload_overlay(data);
+}
+
+static struct pci_device_id lan966x_pci_ids[] = {
+	{ PCI_DEVICE(PCI_VENDOR_ID_EFAR, 0x9660) },
+	{ }
+};
+MODULE_DEVICE_TABLE(pci, lan966x_pci_ids);
+
+static struct pci_driver lan966x_pci_driver = {
+	.name = "mchp_lan966x_pci",
+	.id_table = lan966x_pci_ids,
+	.probe = lan966x_pci_probe,
+	.remove = lan966x_pci_remove,
+};
+module_pci_driver(lan966x_pci_driver);
+
+MODULE_AUTHOR("Herve Codina <herve.codina@bootlin.com>");
+MODULE_DESCRIPTION("Microchip LAN966x PCI driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/misc/lan966x_pci.dtso b/drivers/misc/lan966x_pci.dtso
new file mode 100644
index 000000000000..db57b3a43fe9
--- /dev/null
+++ b/drivers/misc/lan966x_pci.dtso
@@ -0,0 +1,161 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2022 Microchip UNG
+ */
+
+#include <dt-bindings/clock/microchip,lan966x.h>
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/interrupt-controller/irq.h>
+#include <dt-bindings/mfd/atmel-flexcom.h>
+#include <dt-bindings/phy/phy-lan966x-serdes.h>
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	fragment@0 {
+		target-path="";
+		__overlay__ {
+			#address-cells = <3>;
+			#size-cells = <2>;
+
+			pci-ep-bus@0 {
+				compatible = "simple-bus";
+				#address-cells = <1>;
+				#size-cells = <1>;
+
+				/*
+				 * map @0xe2000000 (32MB) to BAR0 (CPU)
+				 * map @0xe0000000 (16MB) to BAR1 (AMBA)
+				 */
+				ranges = <0xe2000000 0x00 0x00 0x00 0x2000000
+				          0xe0000000 0x01 0x00 0x00 0x1000000>;
+
+				oic: oic@e00c0120 {
+					compatible = "microchip,lan966x-oic";
+					#interrupt-cells = <2>;
+					interrupt-controller;
+					interrupts = <0>; /* PCI INTx assigned interrupt */
+					reg = <0xe00c0120 0x190>;
+				};
+
+				cpu_clk: cpu_clk {
+					compatible = "fixed-clock";
+					#clock-cells = <0>;
+					clock-frequency = <600000000>;  // CPU clock = 600MHz
+				};
+
+				ddr_clk: ddr_clk {
+					compatible = "fixed-clock";
+					#clock-cells = <0>;
+					clock-frequency = <30000000>;  // Fabric clock = 30MHz
+				};
+
+				sys_clk: sys_clk {
+					compatible = "fixed-clock";
+					#clock-cells = <0>;
+					clock-frequency = <15625000>;  // System clock = 15.625MHz
+				};
+
+				reset: reset@e200400c {
+					compatible = "microchip,lan966x-switch-reset";
+					reg = <0xe200400c 0x4>, <0xe00c0000 0xa8>;
+					reg-names = "gcb","cpu";
+					#reset-cells = <1>;
+				};
+
+				gpio: pinctrl@e2004064 {
+					compatible = "microchip,lan966x-pinctrl";
+					reg = <0xe2004064 0xb4>,
+					      <0xe2010024 0x138>;
+					resets = <&reset 0>;
+					reset-names = "switch";
+					gpio-controller;
+					#gpio-cells = <2>;
+					gpio-ranges = <&gpio 0 0 78>;
+					interrupt-parent = <&oic>;
+					interrupt-controller;
+					interrupts = <17 IRQ_TYPE_LEVEL_HIGH>;
+					#interrupt-cells = <2>;
+
+					tod_pins: tod_pins {
+						pins = "GPIO_36";
+						function = "ptpsync_1";
+					};
+
+					fc0_a_pins: fcb4-i2c-pins {
+						/* RXD, TXD */
+						pins = "GPIO_9", "GPIO_10";
+						function = "fc0_a";
+					};
+
+				};
+
+				serdes: serdes@e202c000 {
+					compatible = "microchip,lan966x-serdes";
+					reg = <0xe202c000 0x9c>,
+					      <0xe2004010 0x4>;
+					#phy-cells = <2>;
+				};
+
+				mdio1: mdio@e200413c {
+					#address-cells = <1>;
+					#size-cells = <0>;
+					compatible = "microchip,lan966x-miim";
+					reg = <0xe200413c 0x24>,
+					      <0xe2010020 0x4>;
+
+					resets = <&reset 0>;
+					reset-names = "switch";
+
+					lan966x_phy0: ethernet-lan966x_phy@1 {
+						reg = <1>;
+					};
+
+					lan966x_phy1: ethernet-lan966x_phy@2 {
+						reg = <2>;
+					};
+				};
+
+				switch: switch@e0000000 {
+					compatible = "microchip,lan966x-switch";
+					reg = <0xe0000000 0x0100000>,
+					      <0xe2000000 0x0800000>;
+					reg-names = "cpu", "gcb";
+
+					interrupt-parent = <&oic>;
+					interrupts = <12 IRQ_TYPE_LEVEL_HIGH>,
+						     <9 IRQ_TYPE_LEVEL_HIGH>;
+					interrupt-names = "xtr", "ana";
+
+					resets = <&reset 0>;
+					reset-names = "switch";
+
+					pinctrl-names = "default";
+					pinctrl-0 = <&tod_pins>;
+
+					ethernet-ports {
+						#address-cells = <1>;
+						#size-cells = <0>;
+
+						port0: port@0 {
+							phy-handle = <&lan966x_phy0>;
+
+							reg = <0>;
+							phy-mode = "gmii";
+							phys = <&serdes 0 CU(0)>;
+						};
+
+						port1: port@1 {
+							phy-handle = <&lan966x_phy1>;
+
+							reg = <1>;
+							phy-mode = "gmii";
+							phys = <&serdes 1 CU(1)>;
+						};
+					};
+				};
+			};
+		};
+	};
+};
diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c
index dccb60c1d9cc..41dec625ed7b 100644
--- a/drivers/pci/quirks.c
+++ b/drivers/pci/quirks.c
@@ -6266,6 +6266,7 @@  DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0xa76e, dpc_log_size);
 DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_XILINX, 0x5020, of_pci_make_dev_node);
 DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_XILINX, 0x5021, of_pci_make_dev_node);
 DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_REDHAT, 0x0005, of_pci_make_dev_node);
+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_EFAR, 0x9660, of_pci_make_dev_node);
 
 /*
  * Devices known to require a longer delay before first config space access