diff options
| author | Jinke Han <jinkehan@didiglobal.com> | 2026-09-08 15:37:42 +0800 |
|---|---|---|
| committer | Ingo Molnar <mingo@kernel.org> | 2026-09-17 09:48:42 +0200 |
| commit | a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255 (patch) | |
| tree | 73fef824f9a846cf935869d0dbc5070ef6e2f741 /drivers/soc/microchip | |
| download | linux-stable-a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255.tar.gz linux-stable-a5f7a5bb3b7f28ba7e4fa246775b29a0e5537255.zip | |
x86/kprobes: Fix crash when probing CS CALL instructionsgrafted
When using eBPF to probe CS CALL instructions within a function,
a crash can be triggered.
The eBPF tool probes offset 257 of the __hrtimer_run_queues()
function:
<__hrtimer_run_queues+249>: nopl 0x0(%rax,%rax,1)
<__hrtimer_run_queues+254>: mov %r14,%rdi
<__hrtimer_run_queues+257>: cs call <__x86_indirect_thunk_r12>
<__hrtimer_run_queues+263>: mov %eax,%r12d
<__hrtimer_run_queues+266>: xchg %ax,%ax
<__hrtimer_run_queues+268>: mov %r13,%rdi
Which triggers this crash:
BUG: unable to handle page fault for address: 00000000000f41c9
#PF: supervisor write access in kernel mode
#PF: error_code(0x0002) - not-present page
PGD 0 P4D 0
Oops: 0002 [#1] SMP NOPTI
CPU: 1 PID: 0 Comm: swapper/1 Kdump: loaded Tainted: P
RIP: 0010:__hrtimer_run_queues+0x106/0x230
Note that __hrtimer_run_queues+0x106 is __hrtimer_run_queues+262, which is
at the 6th byte of the above CS CALL instruction. Since the CS CALL
instruction occupies 6 bytes, the exception occurred in the middle of that
call instruction.
The root cause is that when using eBPF tools to probe in the middle of a
function, a kprobe with INT3 is used as the underlying implementation.
During single-step emulation of the original CALL instruction,
int3_emulate_call() assumes that the probed CALL instruction is 5 bytes
long. However, the actual CS-prefixed CALL instruction occupies 6 bytes,
so it constructs an incorrect exception return address. When the CPU
returns from the kprobe handler, the next instruction to be executed is at
the address of the last byte of that CS CALL instruction. Coincidentally,
starting from that address, the CPU fetches and decodes a completely
different instruction, which ultimately triggers a kernel crash.
Fix the issue by using the actual instruction length obtained from
the instruction decoder when constructing the exception return
address, rather than relying on the hardcoded CALL_INSN_SIZE macro.
[ mingo: Refined the changelog ]
Fixes: 6256e668b7af ("x86/kprobes: Use int3 instead of debug trap for single-step")
Suggested-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Signed-off-by: Jinke Han <jinkehan@didiglobal.com>
Signed-off-by: Ingo Molnar <mingo@kernel.org>
Reviewed-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
Acked-by: Yafang Shao <laoar.shao@gmail.com>
Acked-by: Borislav Petkov <bp@alien8.de>
Cc: Peter Zijlstra <peterz@infradead.org>
Link: https://patch.msgid.link/20260908073742.GA10517@didi-ThinkCentre-M920t-N000
Diffstat (limited to 'drivers/soc/microchip')
| -rw-r--r-- | drivers/soc/microchip/Kconfig | 34 | ||||
| -rw-r--r-- | drivers/soc/microchip/Makefile | 3 | ||||
| -rw-r--r-- | drivers/soc/microchip/mpfs-control-scb.c | 40 | ||||
| -rw-r--r-- | drivers/soc/microchip/mpfs-irqmux.c | 181 | ||||
| -rw-r--r-- | drivers/soc/microchip/mpfs-mss-top-sysreg.c | 46 | ||||
| -rw-r--r-- | drivers/soc/microchip/mpfs-sys-controller.c | 282 |
6 files changed, 586 insertions, 0 deletions
diff --git a/drivers/soc/microchip/Kconfig b/drivers/soc/microchip/Kconfig new file mode 100644 index 000000000..af7946741 --- /dev/null +++ b/drivers/soc/microchip/Kconfig @@ -0,0 +1,34 @@ +config POLARFIRE_SOC_IRQ_MUX + bool "Microchip PolarFire SoC's GPIO IRQ Mux" + depends on ARCH_MICROCHIP + select REGMAP + select REGMAP_MMIO + default y + help + Support for the interrupt mux on Polarfire SoC. It sits between + the GPIO controllers and the PLIC, as only 41 interrupts are shared + between 3 GPIO controllers with a total of 70 interrupts. + +config POLARFIRE_SOC_SYS_CTRL + tristate "Microchip PolarFire SoC (MPFS) system controller support" + depends on POLARFIRE_SOC_MAILBOX + depends on MTD + help + This driver adds support for the PolarFire SoC (MPFS) system controller. + + To compile this driver as a module, choose M here. the + module will be called mpfs_system_controller. + + If unsure, say N. + +config POLARFIRE_SOC_SYSCONS + bool "PolarFire SoC (MPFS) syscon drivers" + default y + depends on ARCH_MICROCHIP + select MFD_CORE + help + These drivers add support for the syscons on PolarFire SoC (MPFS). + Without these drivers core parts of the kernel such as clocks + and resets will not function correctly. + + If unsure, and on a PolarFire SoC, say y. diff --git a/drivers/soc/microchip/Makefile b/drivers/soc/microchip/Makefile new file mode 100644 index 000000000..55775db45 --- /dev/null +++ b/drivers/soc/microchip/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_POLARFIRE_SOC_IRQ_MUX) += mpfs-irqmux.o +obj-$(CONFIG_POLARFIRE_SOC_SYS_CTRL) += mpfs-sys-controller.o +obj-$(CONFIG_POLARFIRE_SOC_SYSCONS) += mpfs-control-scb.o mpfs-mss-top-sysreg.o diff --git a/drivers/soc/microchip/mpfs-control-scb.c b/drivers/soc/microchip/mpfs-control-scb.c new file mode 100644 index 000000000..8dda5704a --- /dev/null +++ b/drivers/soc/microchip/mpfs-control-scb.c @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/array_size.h> +#include <linux/of.h> +#include <linux/mfd/core.h> +#include <linux/mfd/syscon.h> +#include <linux/platform_device.h> + +static const struct mfd_cell mpfs_control_scb_devs[] = { + MFD_CELL_NAME("mpfs-tvs"), +}; + +static int mpfs_control_scb_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + + return devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, + mpfs_control_scb_devs, + ARRAY_SIZE(mpfs_control_scb_devs), NULL, 0, + NULL); +} + +static const struct of_device_id mpfs_control_scb_of_match[] = { + { .compatible = "microchip,mpfs-control-scb", }, + {}, +}; +MODULE_DEVICE_TABLE(of, mpfs_control_scb_of_match); + +static struct platform_driver mpfs_control_scb_driver = { + .driver = { + .name = "mpfs-control-scb", + .of_match_table = mpfs_control_scb_of_match, + }, + .probe = mpfs_control_scb_probe, +}; +module_platform_driver(mpfs_control_scb_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Conor Dooley <conor.dooley@microchip.com>"); +MODULE_DESCRIPTION("PolarFire SoC control scb driver"); diff --git a/drivers/soc/microchip/mpfs-irqmux.c b/drivers/soc/microchip/mpfs-irqmux.c new file mode 100644 index 000000000..ae15e913e --- /dev/null +++ b/drivers/soc/microchip/mpfs-irqmux.c @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Largely copied from rzn1_irqmux.c + */ + +#include <linux/bitmap.h> +#include <linux/bitops.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_irq.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#define MPFS_IRQMUX_CR 0x54 +#define MPFS_IRQMUX_NUM_CHILDREN 96 +#define MPFS_IRQMUX_NUM_DIRECT 38 +#define MPFS_IRQMUX_DIRECT_START 13 +#define MPFS_IRQMUX_DIRECT_END 50 +#define MPFS_IRQMUX_NONDIRECT_END 53 + +static int mpfs_irqmux_is_direct_mode(struct device *dev, + const struct of_phandle_args *parent_args) +{ + if (parent_args->args_count != 1) { + dev_err(dev, "Invalid interrupt-map item\n"); + return -EINVAL; + } + + if (parent_args->args[0] < MPFS_IRQMUX_DIRECT_START || + parent_args->args[0] > MPFS_IRQMUX_NONDIRECT_END) { + dev_err(dev, "Invalid interrupt %u\n", parent_args->args[0]); + return -EINVAL; + } + + if (parent_args->args[0] > MPFS_IRQMUX_DIRECT_END) + return 0; + + return 1; +} + +static int mpfs_irqmux_probe(struct platform_device *pdev) +{ + DECLARE_BITMAP(child_done, MPFS_IRQMUX_NUM_CHILDREN) = {}; + DECLARE_BITMAP(parent_done, MPFS_IRQMUX_NUM_DIRECT) = {}; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct of_imap_parser imap_parser; + struct of_imap_item imap_item; + struct regmap *regmap; + int ret, direct_mode, line, controller, gpio, parent_line; + u32 tmp, val = 0, old; + + regmap = device_node_to_regmap(pdev->dev.parent->of_node); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), "Failed to find syscon regmap\n"); + + /* We support only #interrupt-cells = <1> and #address-cells = <0> */ + ret = of_property_read_u32(np, "#interrupt-cells", &tmp); + if (ret) + return ret; + if (tmp != 1) + return -EINVAL; + + ret = of_property_read_u32(np, "#address-cells", &tmp); + if (ret) + return ret; + if (tmp != 0) + return -EINVAL; + + ret = of_imap_parser_init(&imap_parser, np, &imap_item); + if (ret) + return ret; + + for_each_of_imap_item(&imap_parser, &imap_item) { + + direct_mode = mpfs_irqmux_is_direct_mode(dev, &imap_item.parent_args); + if (direct_mode < 0) { + of_node_put(imap_item.parent_args.np); + return direct_mode; + } + + line = imap_item.child_imap[0]; + gpio = line % 32; + controller = line / 32; + + if (controller > 2) { + of_node_put(imap_item.parent_args.np); + dev_err(dev, "child interrupt number too large: %d\n", line); + return -EINVAL; + } + + if (test_and_set_bit(line, child_done)) { + of_node_put(imap_item.parent_args.np); + dev_err(dev, "mux child line %d already defined in interrupt-map\n", + line); + return -EINVAL; + } + + parent_line = imap_item.parent_args.args[0] - MPFS_IRQMUX_DIRECT_START; + if (direct_mode && test_and_set_bit(parent_line, parent_done)) { + of_node_put(imap_item.parent_args.np); + dev_err(dev, "mux parent line %d already defined in interrupt-map\n", + line); + return -EINVAL; + } + + /* + * There are 41 interrupts assigned to GPIOs, of which 38 are "direct". Since the + * mux has 32 bits only, 6 of these exclusive/"direct" interrupts remain. These + * are used by GPIO controller 1's lines 18 to 23. Nothing needs to be done + * for these interrupts. + */ + if (controller == 1 && gpio >= 18) + continue; + + /* + * The mux has a single register, where bits 0 to 13 mux between GPIO controller + * 1's 14 GPIOs and GPIO controller 2's first 14 GPIOs. The remaining bits mux + * between the first 18 GPIOs of controller 1 and the last 18 GPIOS of + * controller 2. If a bit in the mux's control register is set, the + * corresponding interrupt line for GPIO controller 0 or 1 will be put in + * "non-direct" mode. If cleared, the "fabric" controller's will. + * + * Register layout: + * GPIO 1 interrupt line 17 | mux bit 31 | GPIO 2 interrupt line 31 + * ... | ... | ... + * ... | ... | ... + * GPIO 1 interrupt line 0 | mux bit 14 | GPIO 2 interrupt line 14 + * GPIO 0 interrupt line 13 | mux bit 13 | GPIO 2 interrupt line 13 + * ... | ... | ... + * ... | ... | ... + * GPIO 0 interrupt line 0 | mux bit 0 | GPIO 2 interrupt line 0 + * + * As the binding mandates 70 items, one for each GPIO line, there's no need to + * handle anything for GPIO controller 2, since the bit will be set for the + * corresponding line in GPIO controller 0 or 1. + */ + if (controller == 2) + continue; + + /* + * If in direct mode, the bit is cleared, nothing needs to be done as val is zero + * initialised and that's the direct mode setting for GPIO controller 0 and 1. + */ + if (direct_mode) + continue; + + if (controller == 0) + val |= 1U << gpio; + else + val |= 1U << (gpio + 14); + } + + regmap_read(regmap, MPFS_IRQMUX_CR, &old); + regmap_write(regmap, MPFS_IRQMUX_CR, val); + + if (val != old) + dev_info(dev, "firmware mux setting of 0x%x overwritten to 0x%x\n", old, val); + + return 0; +} + +static const struct of_device_id mpfs_irqmux_of_match[] = { + { .compatible = "microchip,mpfs-irqmux", }, + { } +}; +MODULE_DEVICE_TABLE(of, mpfs_irqmux_of_match); + +static struct platform_driver mpfs_irqmux_driver = { + .probe = mpfs_irqmux_probe, + .driver = { + .name = "mpfs_irqmux", + .of_match_table = mpfs_irqmux_of_match, + }, +}; +module_platform_driver(mpfs_irqmux_driver); + +MODULE_AUTHOR("Conor Dooley <conor.dooley@microchip.com>"); +MODULE_DESCRIPTION("Polarfire SoC interrupt mux driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/soc/microchip/mpfs-mss-top-sysreg.c b/drivers/soc/microchip/mpfs-mss-top-sysreg.c new file mode 100644 index 000000000..b0f42b8dd --- /dev/null +++ b/drivers/soc/microchip/mpfs-mss-top-sysreg.c @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/array_size.h> +#include <linux/of.h> +#include <linux/mfd/core.h> +#include <linux/mfd/syscon.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> + +static const struct mfd_cell mpfs_mss_top_sysreg_devs[] = { + MFD_CELL_NAME("mpfs-reset"), +}; + +static int mpfs_mss_top_sysreg_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int ret; + + ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, + mpfs_mss_top_sysreg_devs, + ARRAY_SIZE(mpfs_mss_top_sysreg_devs), NULL, + 0, NULL); + if (ret) + return ret; + + return devm_of_platform_populate(dev); +} + +static const struct of_device_id mpfs_mss_top_sysreg_of_match[] = { + { .compatible = "microchip,mpfs-mss-top-sysreg", }, + {}, +}; +MODULE_DEVICE_TABLE(of, mpfs_mss_top_sysreg_of_match); + +static struct platform_driver mpfs_mss_top_sysreg_driver = { + .driver = { + .name = "mpfs-mss-top-sysreg", + .of_match_table = mpfs_mss_top_sysreg_of_match, + }, + .probe = mpfs_mss_top_sysreg_probe, +}; +module_platform_driver(mpfs_mss_top_sysreg_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Conor Dooley <conor.dooley@microchip.com>"); +MODULE_DESCRIPTION("PolarFire SoC mss top sysreg driver"); diff --git a/drivers/soc/microchip/mpfs-sys-controller.c b/drivers/soc/microchip/mpfs-sys-controller.c new file mode 100644 index 000000000..0400a01b2 --- /dev/null +++ b/drivers/soc/microchip/mpfs-sys-controller.c @@ -0,0 +1,282 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Microchip PolarFire SoC (MPFS) system controller driver + * + * Copyright (c) 2020-2021 Microchip Corporation. All rights reserved. + * + * Author: Conor Dooley <conor.dooley@microchip.com> + * + */ + +#include <linux/slab.h> +#include <linux/kref.h> +#include <linux/module.h> +#include <linux/jiffies.h> +#include <linux/mtd/mtd.h> +#include <linux/spi/spi.h> +#include <linux/interrupt.h> +#include <linux/of.h> +#include <linux/mailbox_client.h> +#include <linux/platform_device.h> +#include <soc/microchip/mpfs.h> + +/* + * This timeout must be long, as some services (example: image authentication) + * take significant time to complete + */ +#define MPFS_SYS_CTRL_TIMEOUT_MS 30000 + +static DEFINE_MUTEX(transaction_lock); + +struct mpfs_sys_controller { + struct mbox_client client; + struct mbox_chan *chan; + struct completion c; + struct mtd_info *flash; + struct kref consumers; +}; + +struct mpfs_syscon_config { + unsigned int nb_subdevs; + struct platform_device *subdevs; +}; + +int mpfs_blocking_transaction(struct mpfs_sys_controller *sys_controller, struct mpfs_mss_msg *msg) +{ + unsigned long timeout = msecs_to_jiffies(MPFS_SYS_CTRL_TIMEOUT_MS); + int ret; + + ret = mutex_lock_interruptible(&transaction_lock); + if (ret) + return ret; + + reinit_completion(&sys_controller->c); + + ret = mbox_send_message(sys_controller->chan, msg); + if (ret < 0) { + dev_warn(sys_controller->client.dev, "MPFS sys controller service timeout\n"); + goto out; + } + + /* + * Unfortunately, the system controller will only deliver an interrupt + * if a service succeeds. mbox_send_message() will block until the busy + * flag is gone. If the busy flag is gone but no interrupt has arrived + * to trigger the rx callback then the service can be deemed to have + * failed. + * The caller can then interrogate msg::response::resp_status to + * determine the cause of the failure. + * mbox_send_message() returns positive integers in the success path, so + * ret needs to be cleared if we do get an interrupt. + */ + if (!wait_for_completion_timeout(&sys_controller->c, timeout)) { + ret = -EBADMSG; + dev_warn(sys_controller->client.dev, + "MPFS sys controller service failed with status: %d\n", + msg->response->resp_status); + } else { + ret = 0; + } + +out: + mutex_unlock(&transaction_lock); + + return ret; +} +EXPORT_SYMBOL(mpfs_blocking_transaction); + +static void mpfs_sys_controller_rx_callback(struct mbox_client *client, void *msg) +{ + struct mpfs_sys_controller *sys_controller = + container_of(client, struct mpfs_sys_controller, client); + + complete(&sys_controller->c); +} + +static void mpfs_sys_controller_delete(struct kref *kref) +{ + struct mpfs_sys_controller *sys_controller = + container_of(kref, struct mpfs_sys_controller, consumers); + + mbox_free_channel(sys_controller->chan); + kfree(sys_controller); +} + +static void mpfs_sys_controller_put(void *data) +{ + struct mpfs_sys_controller *sys_controller = data; + + kref_put(&sys_controller->consumers, mpfs_sys_controller_delete); +} + +struct mtd_info *mpfs_sys_controller_get_flash(struct mpfs_sys_controller *mpfs_client) +{ + return mpfs_client->flash; +} +EXPORT_SYMBOL(mpfs_sys_controller_get_flash); + +static int mpfs_sys_controller_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mpfs_sys_controller *sys_controller; + struct mpfs_syscon_config *of_data; + struct device_node *np; + int i, ret; + + sys_controller = kzalloc_obj(*sys_controller); + if (!sys_controller) + return -ENOMEM; + + np = of_parse_phandle(dev->of_node, "microchip,bitstream-flash", 0); + if (!np) + goto no_flash; + + sys_controller->flash = of_get_mtd_device_by_node(np); + of_node_put(np); + if (IS_ERR(sys_controller->flash)) { + ret = dev_err_probe(dev, PTR_ERR(sys_controller->flash), "Failed to get flash\n"); + goto out_free; + } + +no_flash: + sys_controller->client.dev = dev; + sys_controller->client.rx_callback = mpfs_sys_controller_rx_callback; + sys_controller->client.tx_block = 1U; + sys_controller->client.tx_tout = msecs_to_jiffies(MPFS_SYS_CTRL_TIMEOUT_MS); + + sys_controller->chan = mbox_request_channel(&sys_controller->client, 0); + if (IS_ERR(sys_controller->chan)) { + ret = dev_err_probe(dev, PTR_ERR(sys_controller->chan), + "Failed to get mbox channel\n"); + goto out_free; + } + + init_completion(&sys_controller->c); + kref_init(&sys_controller->consumers); + + platform_set_drvdata(pdev, sys_controller); + + of_data = (struct mpfs_syscon_config *) device_get_match_data(dev); + if (!of_data) { + ret = dev_err_probe(dev, -EINVAL, "Error getting match data\n"); + goto out_free_channel; + } + + for (i = 0; i < of_data->nb_subdevs; i++) { + of_data->subdevs[i].dev.parent = dev; + if (platform_device_register(&of_data->subdevs[i])) + dev_warn(dev, "Error registering sub device %s\n", + of_data->subdevs[i].name); + } + + dev_info(&pdev->dev, "Registered MPFS system controller\n"); + + return 0; + +out_free_channel: + mbox_free_channel(sys_controller->chan); +out_free: + kfree(sys_controller); + return ret; +} + +static void mpfs_sys_controller_remove(struct platform_device *pdev) +{ + struct mpfs_sys_controller *sys_controller = platform_get_drvdata(pdev); + + mpfs_sys_controller_put(sys_controller); +} + +static struct platform_device mpfs_subdevs[] = { + { + .name = "mpfs-rng", + .id = -1, + }, + { + .name = "mpfs-generic-service", + .id = -1, + }, + { + .name = "mpfs-auto-update", + .id = -1, + }, +}; + +static struct platform_device pic64gx_subdevs[] = { + { + .name = "mpfs-rng", + .id = -1, + }, + { + .name = "mpfs-generic-service", + .id = -1, + }, +}; + +static const struct mpfs_syscon_config mpfs_config = { + .nb_subdevs = ARRAY_SIZE(mpfs_subdevs), + .subdevs = mpfs_subdevs, +}; + +static const struct mpfs_syscon_config pic64gx_config = { + .nb_subdevs = ARRAY_SIZE(pic64gx_subdevs), + .subdevs = pic64gx_subdevs, +}; + +static const struct of_device_id mpfs_sys_controller_of_match[] = { + {.compatible = "microchip,mpfs-sys-controller", .data = &mpfs_config}, + {.compatible = "microchip,pic64gx-sys-controller", .data = &pic64gx_config}, + {}, +}; +MODULE_DEVICE_TABLE(of, mpfs_sys_controller_of_match); + +struct mpfs_sys_controller *mpfs_sys_controller_get(struct device *dev) +{ + const struct of_device_id *match; + struct mpfs_sys_controller *sys_controller; + int ret; + + if (!dev->parent) + goto err_no_device; + + match = of_match_node(mpfs_sys_controller_of_match, dev->parent->of_node); + of_node_put(dev->parent->of_node); + if (!match) + goto err_no_device; + + sys_controller = dev_get_drvdata(dev->parent); + if (!sys_controller) + goto err_bad_device; + + if (!kref_get_unless_zero(&sys_controller->consumers)) + goto err_bad_device; + + ret = devm_add_action_or_reset(dev, mpfs_sys_controller_put, sys_controller); + if (ret) + return ERR_PTR(ret); + + return sys_controller; + +err_no_device: + dev_dbg(dev, "Parent device was not an MPFS system controller\n"); + return ERR_PTR(-ENODEV); + +err_bad_device: + dev_dbg(dev, "MPFS system controller found but could not register as a sub device\n"); + return ERR_PTR(-EPROBE_DEFER); +} +EXPORT_SYMBOL(mpfs_sys_controller_get); + +static struct platform_driver mpfs_sys_controller_driver = { + .driver = { + .name = "mpfs-sys-controller", + .of_match_table = mpfs_sys_controller_of_match, + }, + .probe = mpfs_sys_controller_probe, + .remove = mpfs_sys_controller_remove, +}; +module_platform_driver(mpfs_sys_controller_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Conor Dooley <conor.dooley@microchip.com>"); +MODULE_DESCRIPTION("MPFS system controller driver"); |
