diff mbox series

[2/3] irqchip: Add the Sophgo SG2042 MSI interrupt controller

Message ID 8076fe2af9f2b007a42c986ed193ba50ff674bfa.1731296803.git.unicorn_wang@outlook.com (mailing list archive)
State New
Headers show
Series irqchip: Add Sophgo SG2042 MSI controller | expand

Checks

Context Check Description
conchuod/vmtest-for-next-PR success PR summary
conchuod/patch-2-test-1 success .github/scripts/patches/tests/build_rv32_defconfig.sh took 102.10s
conchuod/patch-2-test-2 success .github/scripts/patches/tests/build_rv64_clang_allmodconfig.sh took 972.96s
conchuod/patch-2-test-3 success .github/scripts/patches/tests/build_rv64_gcc_allmodconfig.sh took 1146.74s
conchuod/patch-2-test-4 success .github/scripts/patches/tests/build_rv64_nommu_k210_defconfig.sh took 15.19s
conchuod/patch-2-test-5 success .github/scripts/patches/tests/build_rv64_nommu_virt_defconfig.sh took 16.57s
conchuod/patch-2-test-6 warning .github/scripts/patches/tests/checkpatch.sh took 0.99s
conchuod/patch-2-test-7 success .github/scripts/patches/tests/dtb_warn_rv64.sh took 36.02s
conchuod/patch-2-test-8 success .github/scripts/patches/tests/header_inline.sh took 0.00s
conchuod/patch-2-test-9 success .github/scripts/patches/tests/kdoc.sh took 0.42s
conchuod/patch-2-test-10 success .github/scripts/patches/tests/module_param.sh took 0.01s
conchuod/patch-2-test-11 success .github/scripts/patches/tests/verify_fixes.sh took 0.00s
conchuod/patch-2-test-12 success .github/scripts/patches/tests/verify_signedoff.sh took 0.02s

Commit Message

Chen Wang Nov. 11, 2024, 4:01 a.m. UTC
From: Chen Wang <unicorn_wang@outlook.com>

Add driver for Sophgo SG2042 MSI interrupt controller.

Signed-off-by: Chen Wang <unicorn_wang@outlook.com>
---
 drivers/irqchip/Kconfig          |   8 +
 drivers/irqchip/Makefile         |   1 +
 drivers/irqchip/irq-sg2042-msi.c | 255 +++++++++++++++++++++++++++++++
 3 files changed, 264 insertions(+)
 create mode 100644 drivers/irqchip/irq-sg2042-msi.c

Comments

Thomas Gleixner Nov. 13, 2024, 6:14 a.m. UTC | #1
On Mon, Nov 11 2024 at 12:01, Chen Wang wrote:
> +struct sg2042_msi_data {
> +	void __iomem	*reg_clr; /* clear reg, see TRM, 10.1.33, GP_INTR0_CLR */

Please make these tail comments tabular aligned so they actually stand
out.

  https://www.kernel.org/doc/html/latest/process/maintainer-tip.html#comment-style

> +
> +	u64		doorbell_addr; /* see TRM, 10.1.32, GP_INTR0_SET */
> +
> +	u32		irq_first; /* The vector number that MSIs starts */
> +	u32		num_irqs;  /* The number of vectors for MSIs */
> +
> +	unsigned long	*msi_map;
> +	struct mutex	msi_map_lock; /* lock for msi_map */
> +};
> +
> +static int sg2042_msi_allocate_hwirq(struct sg2042_msi_data *priv, int num_req)
> +{
> +	int first;
> +
> +	mutex_lock(&priv->msi_map_lock);

Please use

       guard(mutex)(&priv->msi_map_lock);

which removes all the mutex_unlock() hackery and boils this down

> +
> +	first = bitmap_find_free_region(priv->msi_map, priv->num_irqs,
> +					get_count_order(num_req));
> +	if (first < 0) {
> +		mutex_unlock(&priv->msi_map_lock);
> +		return -ENOSPC;
> +	}
> +
> +	mutex_unlock(&priv->msi_map_lock);
> +
> +	return priv->irq_first + first;

to

	guard(mutex)(&priv->msi_map_lock);
	first = bitmap_find_free_region(priv->msi_map, priv->num_irqs,
					get_count_order(num_req));
	return first >= 0 ? priv->irq_first + first : -ENOSPC;

See?

> +}
> +
> +static void sg2042_msi_free_hwirq(struct sg2042_msi_data *priv,
> +				  int hwirq, int num_req)
> +{
> +	int first = hwirq - priv->irq_first;
> +
> +	mutex_lock(&priv->msi_map_lock);

Ditto.

> +	bitmap_release_region(priv->msi_map, first, get_count_order(num_req));
> +	mutex_unlock(&priv->msi_map_lock);
> +}

> +static void sg2042_msi_irq_compose_msi_msg(struct irq_data *data,
> +					   struct msi_msg *msg)
> +{
> +	struct sg2042_msi_data *priv = irq_data_get_irq_chip_data(data);
> +
> +	msg->address_hi = upper_32_bits(priv->doorbell_addr);
> +	msg->address_lo = lower_32_bits(priv->doorbell_addr);
> +	msg->data = 1 << (data->hwirq - priv->irq_first);
> +
> +	pr_debug("%s hwirq[%d]: address_hi[%#x], address_lo[%#x], data[%#x]\n",
> +		 __func__,

No point in having this line break. You have 100 characters. Please fix
this all over the place.

> +		 (int)data->hwirq, msg->address_hi, msg->address_lo, msg->data);

(int) ? Why can't you use the proper conversion specifier instead of %d?

> +static int sg2042_msi_middle_domain_alloc(struct irq_domain *domain,
> +					  unsigned int virq,
> +					  unsigned int nr_irqs, void *args)
> +{
> +	struct sg2042_msi_data *priv = domain->host_data;
> +	int hwirq, err, i;
> +
> +	hwirq = sg2042_msi_allocate_hwirq(priv, nr_irqs);
> +	if (hwirq < 0)
> +		return hwirq;
> +
> +	for (i = 0; i < nr_irqs; i++) {
> +		err = sg2042_msi_parent_domain_alloc(domain, virq + i, hwirq + i);
> +		if (err)
> +			goto err_hwirq;
> +
> +		pr_debug("%s: virq[%d], hwirq[%d]\n",
> +			 __func__, virq + i, (int)hwirq + i);

No line break required.

> +		irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i,
> +					      &sg2042_msi_middle_irq_chip, priv);
> +	}

> +static int sg2042_msi_init_domains(struct sg2042_msi_data *priv,
> +				   struct device_node *node)
> +{
> +	struct irq_domain *plic_domain, *middle_domain;
> +	struct device_node *plic_node;
> +	struct fwnode_handle *fwnode = of_node_to_fwnode(node);

https://www.kernel.org/doc/html/latest/process/maintainer-tip.html#variable-declarations

> +	if (!of_find_property(node, "interrupt-parent", NULL)) {
> +		pr_err("Can't find interrupt-parent!\n");
> +		return -EINVAL;
> +	}
> +
> +	plic_node = of_irq_find_parent(node);
> +	if (!plic_node) {
> +		pr_err("Failed to find the PLIC node!\n");
> +		return -ENXIO;
> +	}
> +
> +	plic_domain = irq_find_host(plic_node);
> +	of_node_put(plic_node);
> +	if (!plic_domain) {
> +		pr_err("Failed to find the PLIC domain\n");
> +		return -ENXIO;
> +	}
> +
> +	middle_domain = irq_domain_create_hierarchy(plic_domain, 0, priv->num_irqs,
> +						    fwnode,
> +						    &pch_msi_middle_domain_ops,
> +						    priv);

So now you have created a domain. How is that supposed to be used by the
PCI layer?

> +	if (!middle_domain) {
> +		pr_err("Failed to create the MSI middle domain\n");
> +		return -ENOMEM;
> +	}
> +
> +	return 0;
> +}

> +static int sg2042_msi_probe(struct platform_device *pdev)
> +{

....

> +	data->msi_map = bitmap_zalloc(data->num_irqs, GFP_KERNEL);
> +	if (!data->msi_map)
> +		return -ENOMEM;
> +
> +	return sg2042_msi_init_domains(data, pdev->dev.of_node);

In case of error this leaks data->msi_map, no?

> +static struct platform_driver sg2042_msi_driver = {
> +	.driver = {
> +		.name = "sg2042-msi",
> +		.of_match_table = of_match_ptr(sg2042_msi_of_match),
> +	},
> +	.probe = sg2042_msi_probe,
> +};

Please see the documentation I pointed you to above and search for
struct initializers.

Thanks,

        tglx
Chen Wang Nov. 13, 2024, 6:43 a.m. UTC | #2
On 2024/11/13 14:14, Thomas Gleixner wrote:
> On Mon, Nov 11 2024 at 12:01, Chen Wang wrote:
>> +struct sg2042_msi_data {
>> +	void __iomem	*reg_clr; /* clear reg, see TRM, 10.1.33, GP_INTR0_CLR */
> Please make these tail comments tabular aligned so they actually stand
> out.
>
>    https://www.kernel.org/doc/html/latest/process/maintainer-tip.html#comment-style
Got, will fix this.
>> +
>> +	u64		doorbell_addr; /* see TRM, 10.1.32, GP_INTR0_SET */
>> +
>> +	u32		irq_first; /* The vector number that MSIs starts */
>> +	u32		num_irqs;  /* The number of vectors for MSIs */
>> +
>> +	unsigned long	*msi_map;
>> +	struct mutex	msi_map_lock; /* lock for msi_map */
>> +};
>> +
>> +static int sg2042_msi_allocate_hwirq(struct sg2042_msi_data *priv, int num_req)
>> +{
>> +	int first;
>> +
>> +	mutex_lock(&priv->msi_map_lock);
> Please use
>
>         guard(mutex)(&priv->msi_map_lock);
>
> which removes all the mutex_unlock() hackery and boils this down
Thanks, will double check.
>
>> +
>> +	first = bitmap_find_free_region(priv->msi_map, priv->num_irqs,
>> +					get_count_order(num_req));
>> +	if (first < 0) {
>> +		mutex_unlock(&priv->msi_map_lock);
>> +		return -ENOSPC;
>> +	}
>> +
>> +	mutex_unlock(&priv->msi_map_lock);
>> +
>> +	return priv->irq_first + first;
> to
>
> 	guard(mutex)(&priv->msi_map_lock);
> 	first = bitmap_find_free_region(priv->msi_map, priv->num_irqs,
> 					get_count_order(num_req));
> 	return first >= 0 ? priv->irq_first + first : -ENOSPC;
>
> See?
>
>> +}
>> +
>> +static void sg2042_msi_free_hwirq(struct sg2042_msi_data *priv,
>> +				  int hwirq, int num_req)
>> +{
>> +	int first = hwirq - priv->irq_first;
>> +
>> +	mutex_lock(&priv->msi_map_lock);
> Ditto.
>
>> +	bitmap_release_region(priv->msi_map, first, get_count_order(num_req));
>> +	mutex_unlock(&priv->msi_map_lock);
>> +}
>> +static void sg2042_msi_irq_compose_msi_msg(struct irq_data *data,
>> +					   struct msi_msg *msg)
>> +{
>> +	struct sg2042_msi_data *priv = irq_data_get_irq_chip_data(data);
>> +
>> +	msg->address_hi = upper_32_bits(priv->doorbell_addr);
>> +	msg->address_lo = lower_32_bits(priv->doorbell_addr);
>> +	msg->data = 1 << (data->hwirq - priv->irq_first);
>> +
>> +	pr_debug("%s hwirq[%d]: address_hi[%#x], address_lo[%#x], data[%#x]\n",
>> +		 __func__,
> No point in having this line break. You have 100 characters. Please fix
> this all over the place.
Got.
>
>> +		 (int)data->hwirq, msg->address_hi, msg->address_lo, msg->data);
> (int) ? Why can't you use the proper conversion specifier instead of %d?
Will double-check.
>
>> +static int sg2042_msi_middle_domain_alloc(struct irq_domain *domain,
>> +					  unsigned int virq,
>> +					  unsigned int nr_irqs, void *args)
>> +{
>> +	struct sg2042_msi_data *priv = domain->host_data;
>> +	int hwirq, err, i;
>> +
>> +	hwirq = sg2042_msi_allocate_hwirq(priv, nr_irqs);
>> +	if (hwirq < 0)
>> +		return hwirq;
>> +
>> +	for (i = 0; i < nr_irqs; i++) {
>> +		err = sg2042_msi_parent_domain_alloc(domain, virq + i, hwirq + i);
>> +		if (err)
>> +			goto err_hwirq;
>> +
>> +		pr_debug("%s: virq[%d], hwirq[%d]\n",
>> +			 __func__, virq + i, (int)hwirq + i);
> No line break required.
>
>> +		irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i,
>> +					      &sg2042_msi_middle_irq_chip, priv);
>> +	}
>> +static int sg2042_msi_init_domains(struct sg2042_msi_data *priv,
>> +				   struct device_node *node)
>> +{
>> +	struct irq_domain *plic_domain, *middle_domain;
>> +	struct device_node *plic_node;
>> +	struct fwnode_handle *fwnode = of_node_to_fwnode(node);
> https://www.kernel.org/doc/html/latest/process/maintainer-tip.html#variable-declarations
Thanks, will double-check.
>> +	if (!of_find_property(node, "interrupt-parent", NULL)) {
>> +		pr_err("Can't find interrupt-parent!\n");
>> +		return -EINVAL;
>> +	}
>> +
>> +	plic_node = of_irq_find_parent(node);
>> +	if (!plic_node) {
>> +		pr_err("Failed to find the PLIC node!\n");
>> +		return -ENXIO;
>> +	}
>> +
>> +	plic_domain = irq_find_host(plic_node);
>> +	of_node_put(plic_node);
>> +	if (!plic_domain) {
>> +		pr_err("Failed to find the PLIC domain\n");
>> +		return -ENXIO;
>> +	}
>> +
>> +	middle_domain = irq_domain_create_hierarchy(plic_domain, 0, priv->num_irqs,
>> +						    fwnode,
>> +						    &pch_msi_middle_domain_ops,
>> +						    priv);
> So now you have created a domain. How is that supposed to be used by the
> PCI layer?

Here I create the domain and attached it to the fwnode. In PCI driver, 
it can set this msi controller as its ""interrupt-parent" and find the 
domain attached as below:

static int pcie_probe(struct platform_device *pdev)
{
     struct device *dev = &pdev->dev;
     parent_node = of_irq_find_parent(dev->of_node);
     parent_domain = irq_find_host(parent_node);
     ...
}

>> +	if (!middle_domain) {
>> +		pr_err("Failed to create the MSI middle domain\n");
>> +		return -ENOMEM;
>> +	}
>> +
>> +	return 0;
>> +}
>> +static int sg2042_msi_probe(struct platform_device *pdev)
>> +{
> ....
>
>> +	data->msi_map = bitmap_zalloc(data->num_irqs, GFP_KERNEL);
>> +	if (!data->msi_map)
>> +		return -ENOMEM;
>> +
>> +	return sg2042_msi_init_domains(data, pdev->dev.of_node);
> In case of error this leaks data->msi_map, no?
Thanks, I will correct this.
>> +static struct platform_driver sg2042_msi_driver = {
>> +	.driver = {
>> +		.name = "sg2042-msi",
>> +		.of_match_table = of_match_ptr(sg2042_msi_of_match),
>> +	},
>> +	.probe = sg2042_msi_probe,
>> +};
> Please see the documentation I pointed you to above and search for
> struct initializers.
>
> Thanks,
>
>          tglx
Thomas Gleixner Nov. 13, 2024, 3:31 p.m. UTC | #3
On Wed, Nov 13 2024 at 14:43, Chen Wang wrote:
> On 2024/11/13 14:14, Thomas Gleixner wrote:
>>> +
>>> +	middle_domain = irq_domain_create_hierarchy(plic_domain, 0, priv->num_irqs,
>>> +						    fwnode,
>>> +						    &pch_msi_middle_domain_ops,
>>> +						    priv);
>> So now you have created a domain. How is that supposed to be used by the
>> PCI layer?
>
> Here I create the domain and attached it to the fwnode. In PCI driver, 
> it can set this msi controller as its ""interrupt-parent" and find the 
> domain attached as below:
>
> static int pcie_probe(struct platform_device *pdev)
> {
>      struct device *dev = &pdev->dev;
>      parent_node = of_irq_find_parent(dev->of_node);
>      parent_domain = irq_find_host(parent_node);
>      ...
> }

I assume you then want to create a global PCI/MSI domain via
pci_msi_create_irq_domain(), right?

That's not the preferred way to do that. Any new implementation should
use the MSI parent model, where each PCI device creates it's own per
device MSI domain with the MSI interrupt controller as parent
domain.

There is a library with helper functions, irq-msi-lib.[ch]. See
gicv2m_allocate_domains() or pch_msi_init_domains() for reference.

Thanks

        tglx
Chen Wang Nov. 14, 2024, 12:20 a.m. UTC | #4
On 2024/11/13 23:31, Thomas Gleixner wrote:
> On Wed, Nov 13 2024 at 14:43, Chen Wang wrote:
>> On 2024/11/13 14:14, Thomas Gleixner wrote:
>>>> +
>>>> +	middle_domain = irq_domain_create_hierarchy(plic_domain, 0, priv->num_irqs,
>>>> +						    fwnode,
>>>> +						    &pch_msi_middle_domain_ops,
>>>> +						    priv);
>>> So now you have created a domain. How is that supposed to be used by the
>>> PCI layer?
>> Here I create the domain and attached it to the fwnode. In PCI driver,
>> it can set this msi controller as its ""interrupt-parent" and find the
>> domain attached as below:
>>
>> static int pcie_probe(struct platform_device *pdev)
>> {
>>       struct device *dev = &pdev->dev;
>>       parent_node = of_irq_find_parent(dev->of_node);
>>       parent_domain = irq_find_host(parent_node);
>>       ...
>> }
> I assume you then want to create a global PCI/MSI domain via
> pci_msi_create_irq_domain(), right?
Yes, I am writing another pcie driver, which will call 
pci_msi_create_irq_domain() to create a child domain of this middle_domain.
>
> That's not the preferred way to do that. Any new implementation should
> use the MSI parent model, where each PCI device creates it's own per
> device MSI domain with the MSI interrupt controller as parent
> domain.
>
> There is a library with helper functions, irq-msi-lib.[ch]. See
> gicv2m_allocate_domains() or pch_msi_init_domains() for reference.

Thanks, I will check this out.

Regards,

Chen

>
> Thanks
>
>          tglx
diff mbox series

Patch

diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index d82bcab233a1..76a38a4d62eb 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -729,6 +729,14 @@  config MCHP_EIC
 	help
 	  Support for Microchip External Interrupt Controller.
 
+config SOPHGO_SG2042_MSI
+	bool "Sophgo SG2042 MSI controller"
+	depends on ARCH_SOPHGO || COMPILE_TEST
+	help
+	  Support for the Sophgo SG2042 MSI Controller.
+	  This on-chip interrupt controller enables MSI sources to be
+	  routed to the primary PLIC controller on SoC.
+
 config SUNPLUS_SP7021_INTC
 	bool "Sunplus SP7021 interrupt controller" if COMPILE_TEST
 	default SOC_SP7021
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index e3679ec2b9f7..53617890268a 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -125,4 +125,5 @@  obj-$(CONFIG_WPCM450_AIC)		+= irq-wpcm450-aic.o
 obj-$(CONFIG_IRQ_IDT3243X)		+= irq-idt3243x.o
 obj-$(CONFIG_APPLE_AIC)			+= irq-apple-aic.o
 obj-$(CONFIG_MCHP_EIC)			+= irq-mchp-eic.o
+obj-$(CONFIG_SOPHGO_SG2042_MSI)		+= irq-sg2042-msi.o
 obj-$(CONFIG_SUNPLUS_SP7021_INTC)	+= irq-sp7021-intc.o
diff --git a/drivers/irqchip/irq-sg2042-msi.c b/drivers/irqchip/irq-sg2042-msi.c
new file mode 100644
index 000000000000..79449f974ed5
--- /dev/null
+++ b/drivers/irqchip/irq-sg2042-msi.c
@@ -0,0 +1,255 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * SG2042 MSI Controller
+ *
+ * Copyright (C) 2024 Sophgo Technology Inc.
+ * Copyright (C) 2024 Chen Wang <unicorn_wang@outlook.com>
+ */
+
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/irqdomain.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/msi.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/of_pci.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+struct sg2042_msi_data {
+	void __iomem	*reg_clr; /* clear reg, see TRM, 10.1.33, GP_INTR0_CLR */
+
+	u64		doorbell_addr; /* see TRM, 10.1.32, GP_INTR0_SET */
+
+	u32		irq_first; /* The vector number that MSIs starts */
+	u32		num_irqs;  /* The number of vectors for MSIs */
+
+	unsigned long	*msi_map;
+	struct mutex	msi_map_lock; /* lock for msi_map */
+};
+
+static int sg2042_msi_allocate_hwirq(struct sg2042_msi_data *priv, int num_req)
+{
+	int first;
+
+	mutex_lock(&priv->msi_map_lock);
+
+	first = bitmap_find_free_region(priv->msi_map, priv->num_irqs,
+					get_count_order(num_req));
+	if (first < 0) {
+		mutex_unlock(&priv->msi_map_lock);
+		return -ENOSPC;
+	}
+
+	mutex_unlock(&priv->msi_map_lock);
+
+	return priv->irq_first + first;
+}
+
+static void sg2042_msi_free_hwirq(struct sg2042_msi_data *priv,
+				  int hwirq, int num_req)
+{
+	int first = hwirq - priv->irq_first;
+
+	mutex_lock(&priv->msi_map_lock);
+	bitmap_release_region(priv->msi_map, first, get_count_order(num_req));
+	mutex_unlock(&priv->msi_map_lock);
+}
+
+static void sg2042_msi_irq_ack(struct irq_data *d)
+{
+	struct sg2042_msi_data *data  = irq_data_get_irq_chip_data(d);
+	int bit_off = d->hwirq - data->irq_first;
+
+	writel(1 << bit_off, (unsigned int *)data->reg_clr);
+
+	irq_chip_ack_parent(d);
+}
+
+static void sg2042_msi_irq_compose_msi_msg(struct irq_data *data,
+					   struct msi_msg *msg)
+{
+	struct sg2042_msi_data *priv = irq_data_get_irq_chip_data(data);
+
+	msg->address_hi = upper_32_bits(priv->doorbell_addr);
+	msg->address_lo = lower_32_bits(priv->doorbell_addr);
+	msg->data = 1 << (data->hwirq - priv->irq_first);
+
+	pr_debug("%s hwirq[%d]: address_hi[%#x], address_lo[%#x], data[%#x]\n",
+		 __func__,
+		 (int)data->hwirq, msg->address_hi, msg->address_lo, msg->data);
+}
+
+static struct irq_chip sg2042_msi_middle_irq_chip = {
+	.name			= "SG2042 MSI",
+	.irq_ack		= sg2042_msi_irq_ack,
+	.irq_mask		= irq_chip_mask_parent,
+	.irq_unmask		= irq_chip_unmask_parent,
+#ifdef CONFIG_SMP
+	.irq_set_affinity	= irq_chip_set_affinity_parent,
+#endif
+	.irq_compose_msi_msg	= sg2042_msi_irq_compose_msi_msg,
+};
+
+static int sg2042_msi_parent_domain_alloc(struct irq_domain *domain,
+					  unsigned int virq, int hwirq)
+{
+	struct irq_fwspec fwspec;
+	struct irq_data *d;
+	int ret;
+
+	fwspec.fwnode = domain->parent->fwnode;
+	fwspec.param_count = 2;
+	fwspec.param[0] = hwirq;
+	fwspec.param[1] = IRQ_TYPE_EDGE_RISING;
+
+	ret = irq_domain_alloc_irqs_parent(domain, virq, 1, &fwspec);
+	if (ret)
+		return ret;
+
+	d = irq_domain_get_irq_data(domain->parent, virq);
+	return d->chip->irq_set_type(d, IRQ_TYPE_EDGE_RISING);
+}
+
+static int sg2042_msi_middle_domain_alloc(struct irq_domain *domain,
+					  unsigned int virq,
+					  unsigned int nr_irqs, void *args)
+{
+	struct sg2042_msi_data *priv = domain->host_data;
+	int hwirq, err, i;
+
+	hwirq = sg2042_msi_allocate_hwirq(priv, nr_irqs);
+	if (hwirq < 0)
+		return hwirq;
+
+	for (i = 0; i < nr_irqs; i++) {
+		err = sg2042_msi_parent_domain_alloc(domain, virq + i, hwirq + i);
+		if (err)
+			goto err_hwirq;
+
+		pr_debug("%s: virq[%d], hwirq[%d]\n",
+			 __func__, virq + i, (int)hwirq + i);
+
+		irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i,
+					      &sg2042_msi_middle_irq_chip, priv);
+	}
+
+	return 0;
+
+err_hwirq:
+	sg2042_msi_free_hwirq(priv, hwirq, nr_irqs);
+	irq_domain_free_irqs_parent(domain, virq, i);
+
+	return err;
+}
+
+static void sg2042_msi_middle_domain_free(struct irq_domain *domain,
+					  unsigned int virq,
+					  unsigned int nr_irqs)
+{
+	struct irq_data *d = irq_domain_get_irq_data(domain, virq);
+	struct sg2042_msi_data *priv = irq_data_get_irq_chip_data(d);
+
+	irq_domain_free_irqs_parent(domain, virq, nr_irqs);
+	sg2042_msi_free_hwirq(priv, d->hwirq, nr_irqs);
+}
+
+static const struct irq_domain_ops pch_msi_middle_domain_ops = {
+	.alloc	= sg2042_msi_middle_domain_alloc,
+	.free	= sg2042_msi_middle_domain_free,
+};
+
+static int sg2042_msi_init_domains(struct sg2042_msi_data *priv,
+				   struct device_node *node)
+{
+	struct irq_domain *plic_domain, *middle_domain;
+	struct device_node *plic_node;
+	struct fwnode_handle *fwnode = of_node_to_fwnode(node);
+
+	if (!of_find_property(node, "interrupt-parent", NULL)) {
+		pr_err("Can't find interrupt-parent!\n");
+		return -EINVAL;
+	}
+
+	plic_node = of_irq_find_parent(node);
+	if (!plic_node) {
+		pr_err("Failed to find the PLIC node!\n");
+		return -ENXIO;
+	}
+
+	plic_domain = irq_find_host(plic_node);
+	of_node_put(plic_node);
+	if (!plic_domain) {
+		pr_err("Failed to find the PLIC domain\n");
+		return -ENXIO;
+	}
+
+	middle_domain = irq_domain_create_hierarchy(plic_domain, 0, priv->num_irqs,
+						    fwnode,
+						    &pch_msi_middle_domain_ops,
+						    priv);
+	if (!middle_domain) {
+		pr_err("Failed to create the MSI middle domain\n");
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+static int sg2042_msi_probe(struct platform_device *pdev)
+{
+	struct sg2042_msi_data *data;
+
+	data = devm_kzalloc(&pdev->dev, sizeof(struct sg2042_msi_data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->reg_clr = devm_platform_ioremap_resource_byname(pdev, "clr");
+	if (IS_ERR(data->reg_clr)) {
+		dev_err(&pdev->dev, "Failed to map clear register\n");
+		return PTR_ERR(data->reg_clr);
+	}
+
+	if (of_property_read_u64(pdev->dev.of_node, "sophgo,msi-doorbell-addr",
+				 &data->doorbell_addr)) {
+		dev_err(&pdev->dev, "Unable to parse MSI doorbell addr\n");
+		return -EINVAL;
+	}
+
+	if (of_property_read_u32(pdev->dev.of_node, "sophgo,msi-base-vec",
+				 &data->irq_first)) {
+		dev_err(&pdev->dev, "Unable to parse MSI vec base\n");
+		return -EINVAL;
+	}
+
+	if (of_property_read_u32(pdev->dev.of_node, "sophgo,msi-num-vecs",
+				 &data->num_irqs)) {
+		dev_err(&pdev->dev, "Unable to parse MSI vec number\n");
+		return -EINVAL;
+	}
+
+	mutex_init(&data->msi_map_lock);
+
+	data->msi_map = bitmap_zalloc(data->num_irqs, GFP_KERNEL);
+	if (!data->msi_map)
+		return -ENOMEM;
+
+	return sg2042_msi_init_domains(data, pdev->dev.of_node);
+}
+
+static const struct of_device_id sg2042_msi_of_match[] = {
+	{ .compatible = "sophgo,sg2042-msi" },
+	{}
+};
+
+static struct platform_driver sg2042_msi_driver = {
+	.driver = {
+		.name = "sg2042-msi",
+		.of_match_table = of_match_ptr(sg2042_msi_of_match),
+	},
+	.probe = sg2042_msi_probe,
+};
+builtin_platform_driver(sg2042_msi_driver);