diff options
Diffstat (limited to 'drivers/usb')
-rw-r--r-- | drivers/usb/chipidea/host.c | 97 | ||||
-rw-r--r-- | drivers/usb/core/Makefile | 4 | ||||
-rw-r--r-- | drivers/usb/core/hub.c | 11 | ||||
-rw-r--r-- | drivers/usb/core/hub.h | 1 | ||||
-rw-r--r-- | drivers/usb/dwc3/drd.c | 13 | ||||
-rw-r--r-- | drivers/usb/dwc3/dwc3-imx8mp.c | 104 | ||||
-rw-r--r-- | drivers/usb/gadget/function/f_ncm.c | 2 | ||||
-rw-r--r-- | drivers/usb/misc/Kconfig | 16 | ||||
-rw-r--r-- | drivers/usb/misc/Makefile | 1 | ||||
-rw-r--r-- | drivers/usb/misc/onboard_usb_hub.c | 489 | ||||
-rw-r--r-- | drivers/usb/misc/onboard_usb_hub.h | 75 | ||||
-rw-r--r-- | drivers/usb/misc/onboard_usb_hub_pdevs.c | 142 | ||||
-rw-r--r-- | drivers/usb/misc/usb3503.c | 62 | ||||
-rw-r--r-- | drivers/usb/typec/mux/gpio-switch.c | 2 |
14 files changed, 898 insertions, 121 deletions
diff --git a/drivers/usb/chipidea/host.c b/drivers/usb/chipidea/host.c index 02d3a9de6255..bec90385067b 100644 --- a/drivers/usb/chipidea/host.c +++ b/drivers/usb/chipidea/host.c @@ -490,89 +490,6 @@ static void ci_hdrc_unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb) ci_hdrc_free_dma_aligned_buffer(urb, true); } -static void ci_hdrc_host_save_for_power_lost(struct ci_hdrc *ci) -{ - struct ehci_hcd *ehci; - - if (!ci->hcd) - return; - - ehci = hcd_to_ehci(ci->hcd); - /* save EHCI registers */ - ci->pm_usbmode = ehci_readl(ehci, &ehci->regs->usbmode); - ci->pm_command = ehci_readl(ehci, &ehci->regs->command); - ci->pm_command &= ~CMD_RUN; - ci->pm_status = ehci_readl(ehci, &ehci->regs->status); - ci->pm_intr_enable = ehci_readl(ehci, &ehci->regs->intr_enable); - ci->pm_frame_index = ehci_readl(ehci, &ehci->regs->frame_index); - ci->pm_segment = ehci_readl(ehci, &ehci->regs->segment); - ci->pm_frame_list = ehci_readl(ehci, &ehci->regs->frame_list); - ci->pm_async_next = ehci_readl(ehci, &ehci->regs->async_next); - ci->pm_configured_flag = - ehci_readl(ehci, &ehci->regs->configured_flag); - ci->pm_portsc = ehci_readl(ehci, &ehci->regs->port_status[0]); -} - -static void ci_hdrc_host_restore_from_power_lost(struct ci_hdrc *ci) -{ - struct ehci_hcd *ehci; - unsigned long flags; - u32 tmp; - int step_ms; - /* - * If the vbus is off during system suspend, most of devices will pull - * DP up within 200ms when they see vbus, set 1000ms for safety. - */ - int timeout_ms = 1000; - - if (!ci->hcd) - return; - - hw_controller_reset(ci); - - ehci = hcd_to_ehci(ci->hcd); - spin_lock_irqsave(&ehci->lock, flags); - /* Restore EHCI registers */ - ehci_writel(ehci, ci->pm_usbmode, &ehci->regs->usbmode); - ehci_writel(ehci, ci->pm_portsc, &ehci->regs->port_status[0]); - ehci_writel(ehci, ci->pm_command, &ehci->regs->command); - ehci_writel(ehci, ci->pm_intr_enable, &ehci->regs->intr_enable); - ehci_writel(ehci, ci->pm_frame_index, &ehci->regs->frame_index); - ehci_writel(ehci, ci->pm_segment, &ehci->regs->segment); - ehci_writel(ehci, ci->pm_frame_list, &ehci->regs->frame_list); - ehci_writel(ehci, ci->pm_async_next, &ehci->regs->async_next); - ehci_writel(ehci, ci->pm_configured_flag, - &ehci->regs->configured_flag); - /* Restore the PHY's connect notifier setting */ - if (ci->pm_portsc & PORTSC_HSP) - usb_phy_notify_connect(ci->usb_phy, USB_SPEED_HIGH); - - tmp = ehci_readl(ehci, &ehci->regs->command); - tmp |= CMD_RUN; - ehci_writel(ehci, tmp, &ehci->regs->command); - spin_unlock_irqrestore(&ehci->lock, flags); - - if (!(ci->pm_portsc & PORTSC_CCS)) - return; - - for (step_ms = 0; step_ms < timeout_ms; step_ms += 25) { - if (ehci_readl(ehci, &ehci->regs->port_status[0]) & PORTSC_CCS) - break; - msleep(25); - } -} - -static void ci_hdrc_host_suspend(struct ci_hdrc *ci) -{ - ci_hdrc_host_save_for_power_lost(ci); -} - -static void ci_hdrc_host_resume(struct ci_hdrc *ci, bool power_lost) -{ - if (power_lost) - ci_hdrc_host_restore_from_power_lost(ci); -} - static int ci_ehci_bus_resume(struct usb_hcd *hcd) { struct ehci_hcd *ehci = hcd_to_ehci(hcd); @@ -603,6 +520,18 @@ static int ci_ehci_bus_resume(struct usb_hcd *hcd) return 0; } +#ifdef CONFIG_PM_SLEEP +static void ci_hdrc_host_suspend(struct ci_hdrc *ci) +{ + ehci_suspend(ci->hcd, device_may_wakeup(ci->dev)); +} + +static void ci_hdrc_host_resume(struct ci_hdrc *ci, bool power_lost) +{ + ehci_resume(ci->hcd, power_lost); +} +#endif + int ci_hdrc_host_init(struct ci_hdrc *ci) { struct ci_role_driver *rdrv; @@ -616,8 +545,10 @@ int ci_hdrc_host_init(struct ci_hdrc *ci) rdrv->start = host_start; rdrv->stop = host_stop; +#ifdef CONFIG_PM_SLEEP rdrv->suspend = ci_hdrc_host_suspend; rdrv->resume = ci_hdrc_host_resume; +#endif rdrv->irq = host_irq; rdrv->name = "host"; ci->roles[CI_ROLE_HOST] = rdrv; diff --git a/drivers/usb/core/Makefile b/drivers/usb/core/Makefile index 18e874b0441e..7d338e9c0657 100644 --- a/drivers/usb/core/Makefile +++ b/drivers/usb/core/Makefile @@ -12,6 +12,10 @@ usbcore-$(CONFIG_OF) += of.o usbcore-$(CONFIG_USB_PCI) += hcd-pci.o usbcore-$(CONFIG_ACPI) += usb-acpi.o +ifdef CONFIG_USB_ONBOARD_HUB +usbcore-y += ../misc/onboard_usb_hub_pdevs.o +endif + obj-$(CONFIG_USB) += usbcore.o obj-$(CONFIG_USB_LEDS_TRIGGER_USBPORT) += ledtrig-usbport.o diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index b931eed517ed..f23b689cc995 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -23,6 +23,7 @@ #include <linux/usb.h> #include <linux/usbdevice_fs.h> #include <linux/usb/hcd.h> +#include <linux/usb/onboard_hub.h> #include <linux/usb/otg.h> #include <linux/usb/quirks.h> #include <linux/workqueue.h> @@ -1772,7 +1773,9 @@ static void hub_disconnect(struct usb_interface *intf) if (hub->quirk_disable_autosuspend) usb_autopm_put_interface(intf); - hub_put(hub); + onboard_hub_destroy_pdevs(&hub->onboard_hub_devs); + + kref_put(&hub->kref, hub_release); } static bool hub_descriptor_is_sane(struct usb_host_interface *desc) @@ -1900,6 +1903,7 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id) INIT_DELAYED_WORK(&hub->leds, led_work); INIT_DELAYED_WORK(&hub->init_work, NULL); INIT_WORK(&hub->events, hub_event); + INIT_LIST_HEAD(&hub->onboard_hub_devs); spin_lock_init(&hub->irq_urb_lock); timer_setup(&hub->irq_urb_retry, hub_retry_irq_urb, 0); usb_get_intf(intf); @@ -1920,8 +1924,11 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id) usb_autopm_get_interface_no_resume(intf); } - if (hub_configure(hub, &desc->endpoint[0].desc) >= 0) + if (hub_configure(hub, &desc->endpoint[0].desc) >= 0) { + onboard_hub_create_pdevs(hdev, &hub->onboard_hub_devs); + return 0; + } hub_disconnect(intf); return -ENODEV; diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h index dd049bc85f88..b86f08034f38 100644 --- a/drivers/usb/core/hub.h +++ b/drivers/usb/core/hub.h @@ -73,6 +73,7 @@ struct usb_hub { spinlock_t irq_urb_lock; struct timer_list irq_urb_retry; struct usb_port **ports; + struct list_head onboard_hub_devs; }; /** diff --git a/drivers/usb/dwc3/drd.c b/drivers/usb/dwc3/drd.c index f60fa8e6c922..b16eced93ddb 100644 --- a/drivers/usb/dwc3/drd.c +++ b/drivers/usb/dwc3/drd.c @@ -9,6 +9,7 @@ #include <linux/extcon.h> #include <linux/of_graph.h> +#include <linux/of_platform.h> #include <linux/platform_device.h> #include <linux/property.h> @@ -564,6 +565,18 @@ static int dwc3_setup_role_switch(struct dwc3 *dwc) if (IS_ERR(dwc->role_sw)) return PTR_ERR(dwc->role_sw); + if (IS_ENABLED(CONFIG_OF)) { + /* populate connector entry */ + int ret = devm_of_platform_populate(dwc->dev); + + if (ret) { + usb_role_switch_unregister(dwc->role_sw); + dwc->role_sw = NULL; + dev_err(dwc->dev, "DWC3 platform devices creation failed: %i\n", ret); + return ret; + } + } + return 0; } #else diff --git a/drivers/usb/dwc3/dwc3-imx8mp.c b/drivers/usb/dwc3/dwc3-imx8mp.c index 84aaf81ca91d..aba726df3556 100644 --- a/drivers/usb/dwc3/dwc3-imx8mp.c +++ b/drivers/usb/dwc3/dwc3-imx8mp.c @@ -39,17 +39,66 @@ #define USB_WAKEUP_EN_MASK GENMASK(5, 0) +/* USB glue registers */ +#define USB_CTRL0 0x00 +#define USB_CTRL1 0x04 + +#define USB_CTRL0_PORTPWR_EN BIT(12) /* 1 - PPC enabled (default) */ +#define USB_CTRL0_USB3_FIXED BIT(22) /* 1 - USB3 permanent attached */ +#define USB_CTRL0_USB2_FIXED BIT(23) /* 1 - USB2 permanent attached */ + +#define USB_CTRL1_OC_POLARITY BIT(16) /* 0 - HIGH / 1 - LOW */ +#define USB_CTRL1_PWR_POLARITY BIT(17) /* 0 - HIGH / 1 - LOW */ + struct dwc3_imx8mp { struct device *dev; struct platform_device *dwc3; + void __iomem *hsio_blk_base; void __iomem *glue_base; struct clk *hsio_clk; struct clk *suspend_clk; + struct clk *bus_early_clk; int irq; bool pm_suspended; bool wakeup_pending; }; +static void imx8mp_configure_glue(struct dwc3_imx8mp *dwc3_imx) +{ + struct device *dev = dwc3_imx->dev; + u32 value; + + if (!dwc3_imx->glue_base) + return; + + value = readl(dwc3_imx->glue_base + USB_CTRL0); + + if (device_property_read_bool(dev, "fsl,permanently-attached")) + value |= (USB_CTRL0_USB2_FIXED | USB_CTRL0_USB3_FIXED); + else + value &= ~(USB_CTRL0_USB2_FIXED | USB_CTRL0_USB3_FIXED); + + if (device_property_read_bool(dev, "fsl,disable-port-power-control")) + value &= ~(USB_CTRL0_PORTPWR_EN); + else + value |= USB_CTRL0_PORTPWR_EN; + + writel(value, dwc3_imx->glue_base + USB_CTRL0); + + value = readl(dwc3_imx->glue_base + USB_CTRL1); + if (device_property_read_bool(dev, "fsl,over-current-active-low")) + value |= USB_CTRL1_OC_POLARITY; + else + value &= ~USB_CTRL1_OC_POLARITY; + + if (device_property_read_bool(dev, "fsl,power-active-low")) + value |= USB_CTRL1_PWR_POLARITY; + else + value &= ~USB_CTRL1_PWR_POLARITY; + + writel(value, dwc3_imx->glue_base + USB_CTRL1); +} + static void dwc3_imx8mp_wakeup_enable(struct dwc3_imx8mp *dwc3_imx) { struct dwc3 *dwc3 = platform_get_drvdata(dwc3_imx->dwc3); @@ -58,7 +107,7 @@ static void dwc3_imx8mp_wakeup_enable(struct dwc3_imx8mp *dwc3_imx) if (!dwc3) return; - val = readl(dwc3_imx->glue_base + USB_WAKEUP_CTRL); + val = readl(dwc3_imx->hsio_blk_base + USB_WAKEUP_CTRL); if ((dwc3->current_dr_role == DWC3_GCTL_PRTCAP_HOST) && dwc3->xhci) val |= USB_WAKEUP_EN | USB_WAKEUP_SS_CONN | @@ -67,16 +116,16 @@ static void dwc3_imx8mp_wakeup_enable(struct dwc3_imx8mp *dwc3_imx) val |= USB_WAKEUP_EN | USB_WAKEUP_VBUS_EN | USB_WAKEUP_VBUS_SRC_SESS_VAL; - writel(val, dwc3_imx->glue_base + USB_WAKEUP_CTRL); + writel(val, dwc3_imx->hsio_blk_base + USB_WAKEUP_CTRL); } static void dwc3_imx8mp_wakeup_disable(struct dwc3_imx8mp *dwc3_imx) { u32 val; - val = readl(dwc3_imx->glue_base + USB_WAKEUP_CTRL); + val = readl(dwc3_imx->hsio_blk_base + USB_WAKEUP_CTRL); val &= ~(USB_WAKEUP_EN | USB_WAKEUP_EN_MASK); - writel(val, dwc3_imx->glue_base + USB_WAKEUP_CTRL); + writel(val, dwc3_imx->hsio_blk_base + USB_WAKEUP_CTRL); } static irqreturn_t dwc3_imx8mp_interrupt(int irq, void *_dwc3_imx) @@ -143,6 +192,7 @@ static int dwc3_imx8mp_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; struct device_node *dwc3_np, *node = dev->of_node; struct dwc3_imx8mp *dwc3_imx; + struct resource *res; int err, irq; if (!node) { @@ -158,22 +208,45 @@ static int dwc3_imx8mp_probe(struct platform_device *pdev) dwc3_imx->dev = dev; - dwc3_imx->glue_base = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(dwc3_imx->glue_base)) - return PTR_ERR(dwc3_imx->glue_base); + dwc3_imx->hsio_blk_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(dwc3_imx->hsio_blk_base)) + return PTR_ERR(dwc3_imx->hsio_blk_base); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!res) { + dev_warn(dev, "Base address for glue layer missing. Continuing without, some features are missing though."); + } else { + dwc3_imx->glue_base = devm_ioremap_resource(dev, res); + if (IS_ERR(dwc3_imx->glue_base)) + return PTR_ERR(dwc3_imx->glue_base); + } request_bus_freq(BUS_FREQ_HIGH); + + dwc3_imx->bus_early_clk = devm_clk_get(dev, "bus_early"); + if (IS_ERR(dwc3_imx->bus_early_clk)) { + err = PTR_ERR(dwc3_imx->bus_early_clk); + dev_err(dev, "Failed to get bus_early_clk clk, err=%d\n", err); + goto rel_high_bus; + } + + err = clk_prepare_enable(dwc3_imx->bus_early_clk); + if (err) { + dev_err(dev, "Failed to enable bus_early_clk clk, err=%d\n", err); + goto rel_high_bus; + } + dwc3_imx->hsio_clk = devm_clk_get(dev, "hsio"); if (IS_ERR(dwc3_imx->hsio_clk)) { err = PTR_ERR(dwc3_imx->hsio_clk); dev_err(dev, "Failed to get hsio clk, err=%d\n", err); - goto rel_high_bus; + goto disable_bus_early_clk_clk; } err = clk_prepare_enable(dwc3_imx->hsio_clk); if (err) { dev_err(dev, "Failed to enable hsio clk, err=%d\n", err); - return err; + goto disable_bus_early_clk_clk; } dwc3_imx->suspend_clk = devm_clk_get(dev, "suspend"); @@ -196,6 +269,8 @@ static int dwc3_imx8mp_probe(struct platform_device *pdev) } dwc3_imx->irq = irq; + imx8mp_configure_glue(dwc3_imx); + pm_runtime_set_active(dev); pm_runtime_enable(dev); err = pm_runtime_get_sync(dev); @@ -246,6 +321,8 @@ disable_clks: clk_disable_unprepare(dwc3_imx->suspend_clk); disable_hsio_clk: clk_disable_unprepare(dwc3_imx->hsio_clk); +disable_bus_early_clk_clk: + clk_disable_unprepare(dwc3_imx->bus_early_clk); rel_high_bus: release_bus_freq(BUS_FREQ_HIGH); @@ -262,6 +339,7 @@ static int dwc3_imx8mp_remove(struct platform_device *pdev) clk_disable_unprepare(dwc3_imx->suspend_clk); clk_disable_unprepare(dwc3_imx->hsio_clk); + clk_disable_unprepare(dwc3_imx->bus_early_clk); release_bus_freq(BUS_FREQ_HIGH); pm_runtime_disable(dev); pm_runtime_put_noidle(dev); @@ -300,6 +378,9 @@ static int __maybe_unused dwc3_imx8mp_resume(struct dwc3_imx8mp *dwc3_imx, dwc3_imx8mp_wakeup_disable(dwc3_imx); dwc3_imx->pm_suspended = false; + /* Upon power loss any previous configuration is lost, restore it */ + imx8mp_configure_glue(dwc3_imx); + if (dwc3_imx->wakeup_pending) { dwc3_imx->wakeup_pending = false; if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_DEVICE) { @@ -331,6 +412,7 @@ static int __maybe_unused dwc3_imx8mp_pm_suspend(struct device *dev) clk_disable_unprepare(dwc3_imx->suspend_clk); clk_disable_unprepare(dwc3_imx->hsio_clk); + clk_disable_unprepare(dwc3_imx->bus_early_clk); dev_dbg(dev, "dwc3 imx8mp pm suspend.\n"); return ret; @@ -341,6 +423,10 @@ static int __maybe_unused dwc3_imx8mp_pm_resume(struct device *dev) struct dwc3_imx8mp *dwc3_imx = dev_get_drvdata(dev); int ret; + ret = clk_prepare_enable(dwc3_imx->bus_early_clk); + if (ret) + return ret; + if (device_may_wakeup(dwc3_imx->dev)) { disable_irq_wake(dwc3_imx->irq); } else { diff --git a/drivers/usb/gadget/function/f_ncm.c b/drivers/usb/gadget/function/f_ncm.c index 00995d65b54c..300ab1842d96 100644 --- a/drivers/usb/gadget/function/f_ncm.c +++ b/drivers/usb/gadget/function/f_ncm.c @@ -113,7 +113,7 @@ static inline unsigned ncm_bitrate(struct usb_gadget *g) * Smaller packets are not likely to be trying to maximize the * throughput and will be mstly sending smaller infrequent frames. */ -#define TX_MAX_NUM_DPE 32 +#define TX_MAX_NUM_DPE 4 /* Delay for the transmit to wait before sending an unfilled NTB frame. */ #define TX_TIMEOUT_NSECS 300000 diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig index 8f1144359012..2a10d4e3dbb6 100644 --- a/drivers/usb/misc/Kconfig +++ b/drivers/usb/misc/Kconfig @@ -284,3 +284,19 @@ config BRCM_USB_PINMAP This option enables support for remapping some USB external signals, which are typically on dedicated pins on the chip, to any gpio. + +config USB_ONBOARD_HUB + tristate "Onboard USB hub support" + depends on OF || COMPILE_TEST + help + Say Y here if you want to support discrete onboard USB hubs that + don't require an additional control bus for initialization, but + need some non-trivial form of initialization, such as enabling a + power regulator. An example for such a hub is the Realtek + RTS5411. + + This driver can be used as a module but its state (module vs + builtin) must match the state of the USB subsystem. Enabling + this config will enable the driver and it will automatically + match the state of the USB subsystem. If this driver is a + module it will be called onboard_usb_hub. diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile index 5f4e598573ab..2c5aec6f1b26 100644 --- a/drivers/usb/misc/Makefile +++ b/drivers/usb/misc/Makefile @@ -32,3 +32,4 @@ obj-$(CONFIG_USB_CHAOSKEY) += chaoskey.o obj-$(CONFIG_USB_SISUSBVGA) += sisusbvga/ obj-$(CONFIG_USB_LINK_LAYER_TEST) += lvstest.o obj-$(CONFIG_BRCM_USB_PINMAP) += brcmstb-usb-pinmap.o +obj-$(CONFIG_USB_ONBOARD_HUB) += onboard_usb_hub.o diff --git a/drivers/usb/misc/onboard_usb_hub.c b/drivers/usb/misc/onboard_usb_hub.c new file mode 100644 index 000000000000..5c3a479ce998 --- /dev/null +++ b/drivers/usb/misc/onboard_usb_hub.c @@ -0,0 +1,489 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for onboard USB hubs + * + * Copyright (c) 2022, Google LLC + */ + +#include <linux/device.h> +#include <linux/export.h> +#include <linux/gpio/consumer.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> +#include <linux/suspend.h> +#include <linux/sysfs.h> +#include <linux/usb.h> +#include <linux/usb/hcd.h> +#include <linux/usb/onboard_hub.h> +#include <linux/workqueue.h> + +#include "onboard_usb_hub.h" + +/* + * Use generic names, as the actual names might differ between hubs. If a new + * hub requires more than the currently supported supplies, add a new one here. + */ +static const char * const supply_names[] = { + "vdd", + "vdd2", +}; + +#define MAX_SUPPLIES ARRAY_SIZE(supply_names) + +static void onboard_hub_attach_usb_driver(struct work_struct *work); + +static struct usb_device_driver onboard_hub_usbdev_driver; +static DECLARE_WORK(attach_usb_driver_work, onboard_hub_attach_usb_driver); + +/************************** Platform driver **************************/ + +struct usbdev_node { + struct usb_device *udev; + struct list_head list; +}; + +struct onboard_hub { + struct regulator_bulk_data supplies[MAX_SUPPLIES]; + struct device *dev; + const struct onboard_hub_pdata *pdata; + struct gpio_desc *reset_gpio; + bool always_powered_in_suspend; + bool is_powered_on; + bool going_away; + struct list_head udev_list; + struct mutex lock; +}; + +static int onboard_hub_power_on(struct onboard_hub *hub) +{ + int err; + + err = regulator_bulk_enable(hub->pdata->num_supplies, hub->supplies); + if (err) { + dev_err(hub->dev, "failed to enable supplies: %d\n", err); + return err; + } + + fsleep(hub->pdata->reset_us); + gpiod_set_value_cansleep(hub->reset_gpio, 0); + + hub->is_powered_on = true; + + return 0; +} + +static int onboard_hub_power_off(struct onboard_hub *hub) +{ + int err; + + gpiod_set_value_cansleep(hub->reset_gpio, 1); + + err = regulator_bulk_disable(hub->pdata->num_supplies, hub->supplies); + if (err) { + dev_err(hub->dev, "failed to disable supplies: %d\n", err); + return err; + } + + hub->is_powered_on = false; + + return 0; +} + +static int __maybe_unused onboard_hub_suspend(struct device *dev) +{ + struct onboard_hub *hub = dev_get_drvdata(dev); + struct usbdev_node *node; + bool power_off = true; + + if (hub->always_powered_in_suspend) + return 0; + + mutex_lock(&hub->lock); + + list_for_each_entry(node, &hub->udev_list, list) { + if (!device_may_wakeup(node->udev->bus->controller)) + continue; + + if (usb_wakeup_enabled_descendants(node->udev)) { + power_off = false; + break; + } + } + + mutex_unlock(&hub->lock); + + if (!power_off) + return 0; + + return onboard_hub_power_off(hub); +} + +static int __maybe_unused onboard_hub_resume(struct device *dev) +{ + struct onboard_hub *hub = dev_get_drvdata(dev); + + if (hub->is_powered_on) + return 0; + + return onboard_hub_power_on(hub); +} + +static inline void get_udev_link_name(const struct usb_device *udev, char *buf, size_t size) +{ + snprintf(buf, size, "usb_dev.%s", dev_name(&udev->dev)); +} + +static int onboard_hub_add_usbdev(struct onboard_hub *hub, struct usb_device *udev) +{ + struct usbdev_node *node; + char link_name[64]; + int err; + + mutex_lock(&hub->lock); + + if (hub->going_away) { + err = -EINVAL; + goto error; + } + + node = kzalloc(sizeof(*node), GFP_KERNEL); + if (!node) { + err = -ENOMEM; + goto error; + } + + node->udev = udev; + + list_add(&node->list, &hub->udev_list); + + mutex_unlock(&hub->lock); + + get_udev_link_name(udev, link_name, sizeof(link_name)); + WARN_ON(sysfs_create_link(&hub->dev->kobj, &udev->dev.kobj, link_name)); + + return 0; + +error: + mutex_unlock(&hub->lock); + + return err; +} + +static void onboard_hub_remove_usbdev(struct onboard_hub *hub, const struct usb_device *udev) +{ + struct usbdev_node *node; + char link_name[64]; + + get_udev_link_name(udev, link_name, sizeof(link_name)); + sysfs_remove_link(&hub->dev->kobj, link_name); + + mutex_lock(&hub->lock); + + list_for_each_entry(node, &hub->udev_list, list) { + if (node->udev == udev) { + list_del(&node->list); + kfree(node); + break; + } + } + + mutex_unlock(&hub->lock); +} + +static ssize_t always_powered_in_suspend_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + const struct onboard_hub *hub = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%d\n", hub->always_powered_in_suspend); +} + +static ssize_t always_powered_in_suspend_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct onboard_hub *hub = dev_get_drvdata(dev); + bool val; + int ret; + + ret = kstrtobool(buf, &val); + if (ret < 0) + return ret; + + hub->always_powered_in_suspend = val; + + return count; +} +static DEVICE_ATTR_RW(always_powered_in_suspend); + +static struct attribute *onboard_hub_attrs[] = { + &dev_attr_always_powered_in_suspend.attr, + NULL, +}; +ATTRIBUTE_GROUPS(onboard_hub); + +static void onboard_hub_attach_usb_driver(struct work_struct *work) +{ + int err; + + err = driver_attach(&onboard_hub_usbdev_driver.drvwrap.driver); + if (err) + pr_err("Failed to attach USB driver: %d\n", err); +} + +static int onboard_hub_probe(struct platform_device *pdev) +{ + const struct of_device_id *of_id; + struct device *dev = &pdev->dev; + struct onboard_hub *hub; + unsigned int i; + int err; + + hub = devm_kzalloc(dev, sizeof(*hub), GFP_KERNEL); + if (!hub) + return -ENOMEM; + + of_id = of_match_device(onboard_hub_match, &pdev->dev); + if (!of_id) + return -ENODEV; + + hub->pdata = of_id->data; + if (!hub->pdata) + return -EINVAL; + + if (hub->pdata->num_supplies > MAX_SUPPLIES) + return dev_err_probe(dev, -EINVAL, "max %zu supplies supported!\n", + MAX_SUPPLIES); + + for (i = 0; i < hub->pdata->num_supplies; i++) + hub->supplies[i].supply = supply_names[i]; + + err = devm_regulator_bulk_get(dev, hub->pdata->num_supplies, hub->supplies); + if (err) { + dev_err(dev, "Failed to get regulator supplies: %d\n", err); + return err; + } + + hub->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(hub->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(hub->reset_gpio), "failed to get reset GPIO\n"); + + hub->dev = dev; + mutex_init(&hub->lock); + INIT_LIST_HEAD(&hub->udev_list); + + dev_set_drvdata(dev, hub); + + err = onboard_hub_power_on(hub); + if (err) + return err; + + /* + * The USB driver might have been detached from the USB devices by + * onboard_hub_remove() (e.g. through an 'unbind' by userspace), + * make sure to re-attach it if needed. + * + * This needs to be done deferred to avoid self-deadlocks on systems + * with nested onboard hubs. + */ + schedule_work(&attach_usb_driver_work); + + return 0; +} + +static void onboard_hub_remove(struct platform_device *pdev) +{ + struct onboard_hub *hub = dev_get_drvdata(&pdev->dev); + struct usbdev_node *node; + struct usb_device *udev; + + hub->going_away = true; + + mutex_lock(&hub->lock); + + /* unbind the USB devices to avoid dangling references to this device */ + while (!list_empty(&hub->udev_list)) { + node = list_first_entry(&hub->udev_list, struct usbdev_node, list); + udev = node->udev; + + /* + * Unbinding the driver will call onboard_hub_remove_usbdev(), + * which acquires hub->lock. We must release the lock first. + */ + get_device(&udev->dev); + mutex_unlock(&hub->lock); + device_release_driver(&udev->dev); + put_device(&udev->dev); + mutex_lock(&hub->lock); + } + + mutex_unlock(&hub->lock); + + onboard_hub_power_off(hub); +} + +MODULE_DEVICE_TABLE(of, onboard_hub_match); + +static const struct dev_pm_ops __maybe_unused onboard_hub_pm_ops = { + SET_LATE_SYSTEM_SLEEP_PM_OPS(onboard_hub_suspend, onboard_hub_resume) +}; + +static struct platform_driver onboard_hub_driver = { + .probe = onboard_hub_probe, + .remove_new = onboard_hub_remove, + + .driver = { + .name = "onboard-usb-hub", + .of_match_table = onboard_hub_match, + .pm = pm_ptr(&onboard_hub_pm_ops), + .dev_groups = onboard_hub_groups, + }, +}; + +/************************** USB driver **************************/ + +#define VENDOR_ID_CYPRESS 0x04b4 +#define VENDOR_ID_GENESYS 0x05e3 +#define VENDOR_ID_MICROCHIP 0x0424 +#define VENDOR_ID_REALTEK 0x0bda +#define VENDOR_ID_TI 0x0451 +#define VENDOR_ID_VIA 0x2109 + +/* + * Returns the onboard_hub platform device that is associated with the USB + * device passed as parameter. + */ +static struct onboard_hub *_find_onboard_hub(struct device *dev) +{ + struct platform_device *pdev; + struct device_node *np; + struct onboard_hub *hub; + + pdev = of_find_device_by_node(dev->of_node); + if (!pdev) { + np = of_parse_phandle(dev->of_node, "peer-hub", 0); + if (!np) { + dev_err(dev, "failed to find device node for peer hub\n"); + return ERR_PTR(-EINVAL); + } + + pdev = of_find_device_by_node(np); + of_node_put(np); + + if (!pdev) + return ERR_PTR(-ENODEV); + } + + hub = dev_get_drvdata(&pdev->dev); + put_device(&pdev->dev); + + /* + * The presence of drvdata ('hub') indicates that the platform driver + * finished probing. This handles the case where (conceivably) we could + * be running at the exact same time as the platform driver's probe. If + * we detect the race we request probe deferral and we'll come back and + * try again. + */ + if (!hub) + return ERR_PTR(-EPROBE_DEFER); + + return hub; +} + +static int onboard_hub_usbdev_probe(struct usb_device *udev) +{ + struct device *dev = &udev->dev; + struct onboard_hub *hub; + int err; + + /* ignore supported hubs without device tree node */ + if (!dev->of_node) + return -ENODEV; + + hub = _find_onboard_hub(dev); + if (IS_ERR(hub)) + return PTR_ERR(hub); + + dev_set_drvdata(dev, hub); + + err = onboard_hub_add_usbdev(hub, udev); + if (err) + return err; + + return 0; +} + +static void onboard_hub_usbdev_disconnect(struct usb_device *udev) +{ + struct onboard_hub *hub = dev_get_drvdata(&udev->dev); + + onboard_hub_remove_usbdev(hub, udev); +} + +static const struct usb_device_id onboard_hub_id_table[] = { + { USB_DEVICE(VENDOR_ID_CYPRESS, 0x6504) }, /* CYUSB33{0,1,2}x/CYUSB230x 3.0 */ + { USB_DEVICE(VENDOR_ID_CYPRESS, 0x6506) }, /* CYUSB33{0,1,2}x/CYUSB230x 2.0 */ + { USB_DEVICE(VENDOR_ID_GENESYS, 0x0608) }, /* Genesys Logic GL850G USB 2.0 */ + { USB_DEVICE(VENDOR_ID_GENESYS, 0x0610) }, /* Genesys Logic GL852G USB 2.0 */ + { USB_DEVICE(VENDOR_ID_GENESYS, 0x0620) }, /* Genesys Logic GL3523 USB 3.1 */ + { USB_DEVICE(VENDOR_ID_MICROCHIP, 0x2514) }, /* USB2514B USB 2.0 */ + { USB_DEVICE(VENDOR_ID_MICROCHIP, 0x2517) }, /* USB2517 USB 2.0 */ + { USB_DEVICE(VENDOR_ID_MICROCHIP, 0x2744) }, /* USB5744 USB 2.0 */ + { USB_DEVICE(VENDOR_ID_MICROCHIP, 0x5744) }, /* USB5744 USB 3.0 */ + { USB_DEVICE(VENDOR_ID_REALTEK, 0x0411) }, /* RTS5411 USB 3.1 */ + { USB_DEVICE(VENDOR_ID_REALTEK, 0x5411) }, /* RTS5411 USB 2.1 */ + { USB_DEVICE(VENDOR_ID_REALTEK, 0x0414) }, /* RTS5414 USB 3.2 */ + { USB_DEVICE(VENDOR_ID_REALTEK, 0x5414) }, /* RTS5414 USB 2.1 */ + { USB_DEVICE(VENDOR_ID_TI, 0x8140) }, /* TI USB8041 3.0 */ + { USB_DEVICE(VENDOR_ID_TI, 0x8142) }, /* TI USB8041 2.0 */ + { USB_DEVICE(VENDOR_ID_VIA, 0x0817) }, /* VIA VL817 3.1 */ + { USB_DEVICE(VENDOR_ID_VIA, 0x2817) }, /* VIA VL817 2.0 */ + {} +}; +MODULE_DEVICE_TABLE(usb, onboard_hub_id_table); + +static struct usb_device_driver onboard_hub_usbdev_driver = { + .name = "onboard-usb-hub", + .probe = onboard_hub_usbdev_probe, + .disconnect = onboard_hub_usbdev_disconnect, + .generic_subclass = 1, + .supports_autosuspend = 1, + .id_table = onboard_hub_id_table, +}; + +static int __init onboard_hub_init(void) +{ + int ret; + + ret = usb_register_device_driver(&onboard_hub_usbdev_driver, THIS_MODULE); + if (ret) + return ret; + + ret = platform_driver_register(&onboard_hub_driver); + if (ret) + usb_deregister_device_driver(&onboard_hub_usbdev_driver); + + return ret; +} +module_init(onboard_hub_init); + +static void __exit onboard_hub_exit(void) +{ + usb_deregister_device_driver(&onboard_hub_usbdev_driver); + platform_driver_unregister(&onboard_hub_driver); + + cancel_work_sync(&attach_usb_driver_work); +} +module_exit(onboard_hub_exit); + +MODULE_AUTHOR("Matthias Kaehlcke <mka@chromium.org>"); +MODULE_DESCRIPTION("Driver for discrete onboard USB hubs"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/misc/onboard_usb_hub.h b/drivers/usb/misc/onboard_usb_hub.h new file mode 100644 index 000000000000..2419a9777b78 --- /dev/null +++ b/drivers/usb/misc/onboard_usb_hub.h @@ -0,0 +1,75 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2022, Google LLC + */ + +#ifndef _USB_MISC_ONBOARD_USB_HUB_H +#define _USB_MISC_ONBOARD_USB_HUB_H + +struct onboard_hub_pdata { + unsigned long reset_us; /* reset pulse width in us */ + unsigned int num_supplies; /* number of supplies */ +}; + +static const struct onboard_hub_pdata microchip_usb424_data = { + .reset_us = 1, + .num_supplies = 1, +}; + +static const struct onboard_hub_pdata microchip_usb5744_data = { + .reset_us = 0, + .num_supplies = 2, +}; + +static const struct onboard_hub_pdata realtek_rts5411_data = { + .reset_us = 0, + .num_supplies = 1, +}; + +static const struct onboard_hub_pdata ti_tusb8041_data = { + .reset_us = 3000, + .num_supplies = 1, +}; + +static const struct onboard_hub_pdata cypress_hx3_data = { + .reset_us = 10000, + .num_supplies = 2, +}; + +static const struct onboard_hub_pdata genesys_gl850g_data = { + .reset_us = 3, + .num_supplies = 1, +}; + +static const struct onboard_hub_pdata genesys_gl852g_data = { + .reset_us = 50, + .num_supplies = 1, +}; + +static const struct onboard_hub_pdata vialab_vl817_data = { + .reset_us = 10, + .num_supplies = 1, +}; + +static const struct of_device_id onboard_hub_match[] = { + { .compatible = "usb424,2514", .data = µchip_usb424_data, }, + { .compatible = "usb424,2517", .data = µchip_usb424_data, }, + { .compatible = "usb424,2744", .data = µchip_usb5744_data, }, + { .compatible = "usb424,5744", .data = µchip_usb5744_data, }, + { .compatible = "usb451,8140", .data = &ti_tusb8041_data, }, + { .compatible = "usb451,8142", .data = &ti_tusb8041_data, }, + { .compatible = "usb4b4,6504", .data = &cypress_hx3_data, }, + { .compatible = "usb4b4,6506", .data = &cypress_hx3_data, }, + { .compatible = "usb5e3,608", .data = &genesys_gl850g_data, }, + { .compatible = "usb5e3,610", .data = &genesys_gl852g_data, }, + { .compatible = "usb5e3,620", .data = &genesys_gl852g_data, }, + { .compatible = "usbbda,411", .data = &realtek_rts5411_data, }, + { .compatible = "usbbda,5411", .data = &realtek_rts5411_data, }, + { .compatible = "usbbda,414", .data = &realtek_rts5411_data, }, + { .compatible = "usbbda,5414", .data = &realtek_rts5411_data, }, + { .compatible = "usb2109,817", .data = &vialab_vl817_data, }, + { .compatible = "usb2109,2817", .data = &vialab_vl817_data, }, + {} +}; + +#endif /* _USB_MISC_ONBOARD_USB_HUB_H */ diff --git a/drivers/usb/misc/onboard_usb_hub_pdevs.c b/drivers/usb/misc/onboard_usb_hub_pdevs.c new file mode 100644 index 000000000000..a0a5f719129f --- /dev/null +++ b/drivers/usb/misc/onboard_usb_hub_pdevs.c @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * API for creating and destroying USB onboard hub platform devices + * + * Copyright (c) 2022, Google LLC + */ + +#include <linux/device.h> +#include <linux/export.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/usb.h> +#include <linux/usb/hcd.h> +#include <linux/usb/of.h> + +#include "onboard_usb_hub.h" + +struct pdev_list_entry { + struct platform_device *pdev; + struct list_head node; +}; + +static bool of_is_onboard_usb_hub(const struct device_node *np) +{ + return !!of_match_node(onboard_hub_match, np); +} + +/** + * onboard_hub_create_pdevs -- create platform devices for onboard USB hubs + * @parent_hub : parent hub to scan for connected onboard hubs + * @pdev_list : list of onboard hub platform devices owned by the parent hub + * + * Creates a platform device for each supported onboard hub that is connected to + * the given parent hub. The platform device is in charge of initializing the + * hub (enable regulators, take the hub out of reset, ...) and can optionally + * control whether the hub remains powered during system suspend or not. + * + * To keep track of the platform devices they are added to a list that is owned + * by the parent hub. + * + * Some background about the logic in this function, which can be a bit hard + * to follow: + * + * Root hubs don't have dedicated device tree nodes, but use the node of their + * HCD. The primary and secondary HCD are usually represented by a single DT + * node. That means the root hubs of the primary and secondary HCD share the + * same device tree node (the HCD node). As a result this function can be called + * twice with the same DT node for root hubs. We only want to create a single + * platform device for each physical onboard hub, hence for root hubs the loop + * is only executed for the root hub of the primary HCD. Since the function + * scans through all child nodes it still creates pdevs for onboard hubs + * connected to the root hub of the secondary HCD if needed. + * + * Further there must be only one platform device for onboard hubs with a peer + * hub (the hub is a single physical device). To achieve this two measures are + * taken: pdevs for onboard hubs with a peer are only created when the function + * is called on behalf of the parent hub that is connected to the primary HCD + * (directly or through other hubs). For onboard hubs connected to root hubs + * the function processes the nodes of both peers. A platform device is only + * created if the peer hub doesn't have one already. + */ +void onboard_hub_create_pdevs(struct usb_device *parent_hub, struct list_head *pdev_list) +{ + int i; + struct usb_hcd *hcd = bus_to_hcd(parent_hub->bus); + struct device_node *np, *npc; + struct platform_device *pdev; + struct pdev_list_entry *pdle; + + if (!parent_hub->dev.of_node) + return; + + if (!parent_hub->parent && !usb_hcd_is_primary_hcd(hcd)) + return; + + for (i = 1; i <= parent_hub->maxchild; i++) { + np = usb_of_get_device_node(parent_hub, i); + if (!np) + continue; + + if (!of_is_onboard_usb_hub(np)) + goto node_put; + + npc = of_parse_phandle(np, "peer-hub", 0); + if (npc) { + if (!usb_hcd_is_primary_hcd(hcd)) { + of_node_put(npc); + goto node_put; + } + + pdev = of_find_device_by_node(npc); + of_node_put(npc); + + if (pdev) { + put_device(&pdev->dev); + goto node_put; + } + } + + pdev = of_platform_device_create(np, NULL, &parent_hub->dev); + if (!pdev) { + dev_err(&parent_hub->dev, + "failed to create platform device for onboard hub '%pOF'\n", np); + goto node_put; + } + + pdle = kzalloc(sizeof(*pdle), GFP_KERNEL); + if (!pdle) { + of_platform_device_destroy(&pdev->dev, NULL); + goto node_put; + } + + pdle->pdev = pdev; + list_add(&pdle->node, pdev_list); + +node_put: + of_node_put(np); + } +} +EXPORT_SYMBOL_GPL(onboard_hub_create_pdevs); + +/** + * onboard_hub_destroy_pdevs -- free resources of onboard hub platform devices + * @pdev_list : list of onboard hub platform devices + * + * Destroys the platform devices in the given list and frees the memory associated + * with the list entry. + */ +void onboard_hub_destroy_pdevs(struct list_head *pdev_list) +{ + struct pdev_list_entry *pdle, *tmp; + + list_for_each_entry_safe(pdle, tmp, pdev_list, node) { + list_del(&pdle->node); + of_platform_device_destroy(&pdle->pdev->dev, NULL); + kfree(pdle); + } +} +EXPORT_SYMBOL_GPL(onboard_hub_destroy_pdevs); diff --git a/drivers/usb/misc/usb3503.c b/drivers/usb/misc/usb3503.c index 330f494cd158..8204a242d59e 100644 --- a/drivers/usb/misc/usb3503.c +++ b/drivers/usb/misc/usb3503.c @@ -46,34 +46,18 @@ struct usb3503 { struct device *dev; struct clk *clk; u8 port_off_mask; + struct gpio_desc *bypass; struct gpio_desc *intn; struct gpio_desc *reset; struct gpio_desc *connect; bool secondary_ref_clk; }; -static int usb3503_reset(struct usb3503 *hub, int state) -{ - if (!state && hub->connect) - gpiod_set_value_cansleep(hub->connect, 0); - - if (hub->reset) - gpiod_set_value_cansleep(hub->reset, !state); - - /* Wait T_HUBINIT == 4ms for hub logic to stabilize */ - if (state) - usleep_range(4000, 10000); - - return 0; -} - static int usb3503_connect(struct usb3503 *hub) { struct device *dev = hub->dev; int err; - usb3503_reset(hub, 1); - if (hub->regmap) { /* SP_ILOCK: set connect_n, config_n for config */ err = regmap_write(hub->regmap, USB3503_SP_ILOCK, @@ -126,25 +110,46 @@ static int usb3503_connect(struct usb3503 *hub) static int usb3503_switch_mode(struct usb3503 *hub, enum usb3503_mode mode) { struct device *dev = hub->dev; - int err = 0; + int rst, bypass, conn; switch (mode) { case USB3503_MODE_HUB: - err = usb3503_connect(hub); + conn = 1; + rst = 0; + bypass = 0; break; - case USB3503_MODE_STANDBY: - usb3503_reset(hub, 0); + conn = 0; + rst = 1; + bypass = 1; dev_info(dev, "switched to STANDBY mode\n"); break; - + case USB3503_MODE_BYPASS: + conn = 0; + rst = 0; + bypass = 1; + break; default: dev_err(dev, "unknown mode is requested\n"); - err = -EINVAL; - break; + return -EINVAL; + } + + if (!conn && hub->connect) + gpiod_set_value_cansleep(hub->connect, 0); + + if (hub->reset) + gpiod_set_value_cansleep(hub->reset, rst); + + if (hub->bypass) + gpiod_set_value_cansleep(hub->bypass, bypass); + + if (conn) { + /* Wait T_HUBINIT == 4ms for hub logic to stabilize */ + usleep_range(4000, 10000); + return usb3503_connect(hub); } - return err; + return 0; } static const struct regmap_config usb3503_regmap_config = { @@ -247,6 +252,12 @@ static int usb3503_probe(struct usb3503 *hub) if (hub->connect) gpiod_set_consumer_name(hub->connect, "usb3503 connect"); + hub->bypass = devm_gpiod_get_optional(dev, "bypass", GPIOD_OUT_HIGH); + if (IS_ERR(hub->bypass)) + return PTR_ERR(hub->bypass); + if (hub->bypass) + gpiod_set_consumer_name(hub->bypass, "usb3503 bypass"); + hub->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); if (IS_ERR(hub->reset)) return PTR_ERR(hub->reset); @@ -378,6 +389,7 @@ MODULE_DEVICE_TABLE(i2c, usb3503_id); static const struct of_device_id usb3503_of_match[] = { { .compatible = "smsc,usb3503", }, { .compatible = "smsc,usb3503a", }, + { .compatible = "smsc,usb3803", }, {}, }; MODULE_DEVICE_TABLE(of, usb3503_of_match); diff --git a/drivers/usb/typec/mux/gpio-switch.c b/drivers/usb/typec/mux/gpio-switch.c index 6029e224a848..d86696010a42 100644 --- a/drivers/usb/typec/mux/gpio-switch.c +++ b/drivers/usb/typec/mux/gpio-switch.c @@ -12,8 +12,8 @@ #include <linux/mutex.h> #include <linux/kernel.h> #include <linux/platform_device.h> +#include <linux/gpio/consumer.h> #include <linux/of.h> -#include <linux/of_gpio.h> #include <linux/usb/typec_mux.h> struct gpio_typec_switch { |