summaryrefslogtreecommitdiffstats
path: root/drivers/usb/phy/phy-generic.c
diff options
context:
space:
mode:
authorFuad Tabba <fuad.tabba@linux.dev>2026-09-22 19:14:30 +0100
committerWill Deacon <will@kernel.org>2026-09-23 12:34:30 +0000
commit2bc6b218717b9d08f466f88209251d54bc09b207 (patch)
treecb91c941eccb15f94cbec4e96aa3a9c8736b5d66 /drivers/usb/phy/phy-generic.c
downloadlinux-stable-2bc6b218717b9d08f466f88209251d54bc09b207.tar.gz
linux-stable-2bc6b218717b9d08f466f88209251d54bc09b207.zip
arm64/boot: Disable trapping of PMZR_EL0 writes to EL2grafted
__init_el2_fgt2() writes one mask to both HDFGRTR2_EL2 and HDFGWTR2_EL2. PMZR_EL0 is write-only, so its trap bit, nPMZR_EL0, exists only in HDFGWTR2_EL2 and is therefore never set: a PMZR_EL0 write from the host traps to EL2, where the nVHE hypervisor has no handler and BUG()s. The kernel never writes PMZR_EL0, but kernel.perf_user_access=1 has the PMU driver set PMUSERENR_EL0.UEN for a task with a user-read event, so a write from EL0 reaches the trap and takes the host down without a panic message. Accumulate the HDFGWTR2_EL2 bits separately, as __init_el2_fgt() already does for HDFGWTR_EL2, and set nPMZR_EL0 with the other FEAT_PMUv3p9 bits. Fixes: 858c7bfcb35e1 ("arm64/boot: Enable EL2 requirements for FEAT_PMUv3p9") Cc: stable@vger.kernel.org Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev> Reviewed-by: Anshuman Khandual <anshuman.khandual@arm.com> Reviewed-by: Oliver Upton <oupton@kernel.org> Signed-off-by: Will Deacon <will@kernel.org>
Diffstat (limited to 'drivers/usb/phy/phy-generic.c')
-rw-r--r--drivers/usb/phy/phy-generic.c348
1 files changed, 348 insertions, 0 deletions
diff --git a/drivers/usb/phy/phy-generic.c b/drivers/usb/phy/phy-generic.c
new file mode 100644
index 000000000..de26b3023
--- /dev/null
+++ b/drivers/usb/phy/phy-generic.c
@@ -0,0 +1,348 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * NOP USB transceiver for all USB transceiver which are either built-in
+ * into USB IP or which are mostly autonomous.
+ *
+ * Copyright (C) 2009 Texas Instruments Inc
+ * Author: Ajay Kumar Gupta <ajay.gupta@ti.com>
+ *
+ * Current status:
+ * This provides a "nop" transceiver for PHYs which are
+ * autonomous such as isp1504, isp1707, etc.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/usb/gadget.h>
+#include <linux/usb/otg.h>
+#include <linux/usb/usb_phy_generic.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+#include <linux/regulator/consumer.h>
+#include <linux/property.h>
+#include <linux/gpio/consumer.h>
+#include <linux/delay.h>
+
+#include "phy-generic.h"
+
+#define VBUS_IRQ_FLAGS \
+ (IRQF_SHARED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | \
+ IRQF_ONESHOT)
+
+struct platform_device *usb_phy_generic_register(void)
+{
+ return platform_device_register_simple("usb_phy_generic",
+ PLATFORM_DEVID_AUTO, NULL, 0);
+}
+EXPORT_SYMBOL_GPL(usb_phy_generic_register);
+
+void usb_phy_generic_unregister(struct platform_device *pdev)
+{
+ platform_device_unregister(pdev);
+}
+EXPORT_SYMBOL_GPL(usb_phy_generic_unregister);
+
+static int nop_set_suspend(struct usb_phy *x, int suspend)
+{
+ struct usb_phy_generic *nop = dev_get_drvdata(x->dev);
+ int ret = 0;
+
+ if (suspend) {
+ clk_disable_unprepare(nop->clk);
+ if (!IS_ERR(nop->vcc) && !device_may_wakeup(x->dev))
+ ret = regulator_disable(nop->vcc);
+ } else {
+ if (!IS_ERR(nop->vcc) && !device_may_wakeup(x->dev))
+ ret = regulator_enable(nop->vcc);
+ clk_prepare_enable(nop->clk);
+ }
+
+ return ret;
+}
+
+static void nop_reset(struct usb_phy_generic *nop)
+{
+ if (!nop->gpiod_reset)
+ return;
+
+ gpiod_set_value_cansleep(nop->gpiod_reset, 1);
+ usleep_range(10000, 20000);
+ gpiod_set_value_cansleep(nop->gpiod_reset, 0);
+ usleep_range(10000, 30000);
+}
+
+/* interface to regulator framework */
+static int nop_set_vbus(struct usb_otg *otg, bool enable)
+{
+ int ret = 0;
+ struct usb_phy_generic *nop = dev_get_drvdata(otg->usb_phy->dev);
+
+ if (!nop->vbus_draw)
+ return 0;
+
+ if (enable && !nop->vbus_draw_enabled) {
+ ret = regulator_enable(nop->vbus_draw);
+ if (ret)
+ nop->vbus_draw_enabled = false;
+ else
+ nop->vbus_draw_enabled = true;
+
+ } else if (!enable && nop->vbus_draw_enabled) {
+ ret = regulator_disable(nop->vbus_draw);
+ nop->vbus_draw_enabled = false;
+ }
+ return ret;
+}
+
+
+static irqreturn_t nop_gpio_vbus_thread(int irq, void *data)
+{
+ struct usb_phy_generic *nop = data;
+ struct usb_otg *otg = nop->phy.otg;
+ int vbus, status;
+
+ vbus = gpiod_get_value(nop->gpiod_vbus);
+ if ((vbus ^ nop->vbus) == 0)
+ return IRQ_HANDLED;
+ nop->vbus = vbus;
+
+ if (vbus) {
+ status = USB_EVENT_VBUS;
+ otg->state = OTG_STATE_B_PERIPHERAL;
+ nop->phy.last_event = status;
+
+ atomic_notifier_call_chain(&nop->phy.notifier, status,
+ otg->gadget);
+ } else {
+ status = USB_EVENT_NONE;
+ otg->state = OTG_STATE_B_IDLE;
+ nop->phy.last_event = status;
+
+ atomic_notifier_call_chain(&nop->phy.notifier, status,
+ otg->gadget);
+ }
+ return IRQ_HANDLED;
+}
+
+int usb_gen_phy_init(struct usb_phy *phy)
+{
+ struct usb_phy_generic *nop = dev_get_drvdata(phy->dev);
+ int ret;
+
+ if (!IS_ERR(nop->vcc)) {
+ if (regulator_enable(nop->vcc))
+ dev_err(phy->dev, "Failed to enable power\n");
+ }
+
+ ret = clk_prepare_enable(nop->clk);
+ if (ret)
+ return ret;
+
+ nop_reset(nop);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(usb_gen_phy_init);
+
+void usb_gen_phy_shutdown(struct usb_phy *phy)
+{
+ struct usb_phy_generic *nop = dev_get_drvdata(phy->dev);
+
+ gpiod_set_value_cansleep(nop->gpiod_reset, 1);
+
+ clk_disable_unprepare(nop->clk);
+
+ if (!IS_ERR(nop->vcc)) {
+ if (regulator_disable(nop->vcc))
+ dev_err(phy->dev, "Failed to disable power\n");
+ }
+}
+EXPORT_SYMBOL_GPL(usb_gen_phy_shutdown);
+
+static int nop_set_peripheral(struct usb_otg *otg, struct usb_gadget *gadget)
+{
+ if (!otg)
+ return -ENODEV;
+
+ if (!gadget) {
+ otg->gadget = NULL;
+ return -ENODEV;
+ }
+
+ otg->gadget = gadget;
+ if (otg->state == OTG_STATE_B_PERIPHERAL)
+ atomic_notifier_call_chain(&otg->usb_phy->notifier,
+ USB_EVENT_VBUS, otg->gadget);
+ else
+ otg->state = OTG_STATE_B_IDLE;
+ return 0;
+}
+
+static int nop_set_host(struct usb_otg *otg, struct usb_bus *host)
+{
+ if (!otg)
+ return -ENODEV;
+
+ if (!host) {
+ otg->host = NULL;
+ return -ENODEV;
+ }
+
+ otg->host = host;
+ return 0;
+}
+
+int usb_phy_gen_create_phy(struct device *dev, struct usb_phy_generic *nop)
+{
+ enum usb_phy_type type = USB_PHY_TYPE_USB2;
+ int err = 0;
+ u32 clk_rate = 0;
+
+ device_property_read_u32(dev, "clock-frequency", &clk_rate);
+ nop->gpiod_reset = devm_gpiod_get_optional(dev, "reset",
+ GPIOD_ASIS);
+ err = PTR_ERR_OR_ZERO(nop->gpiod_reset);
+ if (!err) {
+ nop->gpiod_vbus = devm_gpiod_get_optional(dev,
+ "vbus-detect",
+ GPIOD_ASIS);
+ err = PTR_ERR_OR_ZERO(nop->gpiod_vbus);
+ }
+
+ if (err)
+ return dev_err_probe(dev, err,
+ "Error requesting RESET or VBUS GPIO\n");
+ if (nop->gpiod_reset)
+ gpiod_direction_output(nop->gpiod_reset, 1);
+
+ nop->phy.otg = devm_kzalloc(dev, sizeof(*nop->phy.otg),
+ GFP_KERNEL);
+ if (!nop->phy.otg)
+ return -ENOMEM;
+
+ nop->clk = devm_clk_get_optional(dev, "main_clk");
+ if (IS_ERR(nop->clk))
+ return dev_err_probe(dev, PTR_ERR(nop->clk),
+ "Can't get phy clock\n");
+
+ if (clk_rate) {
+ err = clk_set_rate(nop->clk, clk_rate);
+ if (err)
+ return dev_err_probe(dev, err,
+ "Error setting clock rate\n");
+ }
+
+ nop->vcc = devm_regulator_get_optional(dev, "vcc");
+ if (IS_ERR(nop->vcc) && PTR_ERR(nop->vcc) != -ENODEV)
+ return dev_err_probe(dev, PTR_ERR(nop->vcc),
+ "could not get vcc regulator\n");
+
+ nop->vbus_draw = devm_regulator_get_exclusive(dev, "vbus");
+ if (PTR_ERR(nop->vbus_draw) == -ENODEV)
+ nop->vbus_draw = NULL;
+ if (IS_ERR(nop->vbus_draw))
+ return dev_err_probe(dev, PTR_ERR(nop->vbus_draw),
+ "could not get vbus regulator\n");
+
+ nop->dev = dev;
+ nop->phy.dev = nop->dev;
+ nop->phy.label = "nop-xceiv";
+ nop->phy.set_suspend = nop_set_suspend;
+ nop->phy.type = type;
+
+ nop->phy.otg->state = OTG_STATE_UNDEFINED;
+ nop->phy.otg->usb_phy = &nop->phy;
+ nop->phy.otg->set_host = nop_set_host;
+ nop->phy.otg->set_peripheral = nop_set_peripheral;
+ nop->phy.otg->set_vbus = nop_set_vbus;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(usb_phy_gen_create_phy);
+
+static int usb_phy_generic_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct usb_phy_generic *nop;
+ int err;
+
+ nop = devm_kzalloc(dev, sizeof(*nop), GFP_KERNEL);
+ if (!nop)
+ return -ENOMEM;
+
+ err = usb_phy_gen_create_phy(dev, nop);
+ if (err)
+ return err;
+
+ if (nop->gpiod_vbus) {
+ err = devm_request_threaded_irq(dev,
+ gpiod_to_irq(nop->gpiod_vbus),
+ NULL, nop_gpio_vbus_thread,
+ VBUS_IRQ_FLAGS, "vbus_detect",
+ nop);
+ if (err)
+ return dev_err_probe(dev, err, "can't request irq %i\n",
+ gpiod_to_irq(nop->gpiod_vbus));
+
+ nop->phy.otg->state = gpiod_get_value(nop->gpiod_vbus) ?
+ OTG_STATE_B_PERIPHERAL : OTG_STATE_B_IDLE;
+ }
+
+ nop->phy.init = usb_gen_phy_init;
+ nop->phy.shutdown = usb_gen_phy_shutdown;
+
+ err = usb_add_phy_dev(&nop->phy);
+ if (err)
+ return dev_err_probe(dev, err, "can't register transceiver\n");
+
+ platform_set_drvdata(pdev, nop);
+
+ device_set_wakeup_capable(dev,
+ device_property_read_bool(dev, "wakeup-source"));
+
+ return 0;
+}
+
+static void usb_phy_generic_remove(struct platform_device *pdev)
+{
+ struct usb_phy_generic *nop = platform_get_drvdata(pdev);
+
+ usb_remove_phy(&nop->phy);
+
+ if (nop->vbus_draw && nop->vbus_draw_enabled)
+ regulator_disable(nop->vbus_draw);
+}
+
+static const struct of_device_id nop_xceiv_dt_ids[] = {
+ { .compatible = "usb-nop-xceiv" },
+ { }
+};
+
+MODULE_DEVICE_TABLE(of, nop_xceiv_dt_ids);
+
+static struct platform_driver usb_phy_generic_driver = {
+ .probe = usb_phy_generic_probe,
+ .remove = usb_phy_generic_remove,
+ .driver = {
+ .name = "usb_phy_generic",
+ .of_match_table = nop_xceiv_dt_ids,
+ },
+};
+
+static int __init usb_phy_generic_init(void)
+{
+ return platform_driver_register(&usb_phy_generic_driver);
+}
+subsys_initcall(usb_phy_generic_init);
+
+static void __exit usb_phy_generic_exit(void)
+{
+ platform_driver_unregister(&usb_phy_generic_driver);
+}
+module_exit(usb_phy_generic_exit);
+
+MODULE_ALIAS("platform:usb_phy_generic");
+MODULE_AUTHOR("Texas Instruments Inc");
+MODULE_DESCRIPTION("NOP USB Transceiver driver");
+MODULE_LICENSE("GPL");