Message ID | 8076fe2af9f2b007a42c986ed193ba50ff674bfa.1731296803.git.unicorn_wang@outlook.com (mailing list archive) |
---|---|
State | New |
Headers | show |
Series | irqchip: Add Sophgo SG2042 MSI controller | expand |
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
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
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
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 --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);