diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2020-10-15 09:51:18 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2020-10-15 09:51:18 -0700 |
commit | c6dbef7307629cce855aa6b482b60cbf7777ed88 (patch) | |
tree | d8d13630bd57090173ad9f0c5f8e3bfb392964c9 /drivers/usb/dwc2/drd.c | |
parent | ade7afe3e606f9f6ff0e6deefce140157f75540b (diff) | |
parent | 93578a25d4e21603518daf27a5f9caa4bf79de68 (diff) |
Merge tag 'usb-5.10-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb
Pull USB/PHY/Thunderbolt driver updates from Greg KH:
"Here is the big set of USB, PHY, and Thunderbolt driver updates for
5.10-rc1.
Lots of tiny different things for these subsystems are in here,
including:
- phy driver updates
- thunderbolt / USB 4 updates and additions
- USB gadget driver updates
- xhci fixes and updates
- typec driver additions and updates
- api conversions to various drivers for core kernel api changes
- new USB control message functions to make it harder to get wrong,
as found by syzbot (took 2 tries to get it right)
- lots of tiny USB driver fixes and updates all over the place
All of these have been in linux-next for a while, with the exception
of the last "obviously correct" patch that updated a FALLTHROUGH
comment that got merged last weekend"
* tag 'usb-5.10-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb: (374 commits)
usb: musb: gadget: Use fallthrough pseudo-keyword
usb: typec: Add QCOM PMIC typec detection driver
USB: serial: option: add Cellient MPL200 card
usb: typec: tcpci_maxim: Add support for Sink FRS
usb: typec: tcpci: Implement callbacks for FRS
usb: typec: tcpm: Add support for Sink Fast Role SWAP(FRS)
usb: typec: tcpci_maxim: Chip level TCPC driver
usb: typec: tcpci: Add set_vbus tcpci callback
usb: typec: tcpci: Add a getter method to retrieve tcpm_port reference
usbip: vhci_hcd: fix calling usb_hcd_giveback_urb() with irqs enabled
usb: cdc-acm: add quirk to blacklist ETAS ES58X devices
USB: serial: ftdi_sio: use cur_altsetting for consistency
USB: serial: option: Add Telit FT980-KS composition
USB: core: remove polling for /sys/kernel/debug/usb/devices
usb: typec: add support for STUSB160x Type-C controller family
usb: typec: add typec_find_pwr_opmode
usb: typec: hd3ss3220: Use OF graph API to get the connector fwnode
dt-bindings: usb: renesas,usb3-peri: Document HS and SS data bus
dt-bindings: usb: convert ti,hd3ss3220 bindings to json-schema
usb: dwc2: Fix INTR OUT transfers in DDMA mode.
...
Diffstat (limited to 'drivers/usb/dwc2/drd.c')
-rw-r--r-- | drivers/usb/dwc2/drd.c | 180 |
1 files changed, 180 insertions, 0 deletions
diff --git a/drivers/usb/dwc2/drd.c b/drivers/usb/dwc2/drd.c new file mode 100644 index 000000000000..2d4176f5788e --- /dev/null +++ b/drivers/usb/dwc2/drd.c @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * drd.c - DesignWare USB2 DRD Controller Dual-role support + * + * Copyright (C) 2020 STMicroelectronics + * + * Author(s): Amelie Delaunay <amelie.delaunay@st.com> + */ + +#include <linux/iopoll.h> +#include <linux/platform_device.h> +#include <linux/usb/role.h> +#include "core.h" + +static void dwc2_ovr_init(struct dwc2_hsotg *hsotg) +{ + unsigned long flags; + u32 gotgctl; + + spin_lock_irqsave(&hsotg->lock, flags); + + gotgctl = dwc2_readl(hsotg, GOTGCTL); + gotgctl |= GOTGCTL_BVALOEN | GOTGCTL_AVALOEN | GOTGCTL_VBVALOEN; + gotgctl |= GOTGCTL_DBNCE_FLTR_BYPASS; + gotgctl &= ~(GOTGCTL_BVALOVAL | GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL); + dwc2_writel(hsotg, gotgctl, GOTGCTL); + + dwc2_force_mode(hsotg, false); + + spin_unlock_irqrestore(&hsotg->lock, flags); +} + +static int dwc2_ovr_avalid(struct dwc2_hsotg *hsotg, bool valid) +{ + u32 gotgctl = dwc2_readl(hsotg, GOTGCTL); + + /* Check if A-Session is already in the right state */ + if ((valid && (gotgctl & GOTGCTL_ASESVLD)) || + (!valid && !(gotgctl & GOTGCTL_ASESVLD))) + return -EALREADY; + + if (valid) + gotgctl |= GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL; + else + gotgctl &= ~(GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL); + dwc2_writel(hsotg, gotgctl, GOTGCTL); + + return 0; +} + +static int dwc2_ovr_bvalid(struct dwc2_hsotg *hsotg, bool valid) +{ + u32 gotgctl = dwc2_readl(hsotg, GOTGCTL); + + /* Check if B-Session is already in the right state */ + if ((valid && (gotgctl & GOTGCTL_BSESVLD)) || + (!valid && !(gotgctl & GOTGCTL_BSESVLD))) + return -EALREADY; + + if (valid) + gotgctl |= GOTGCTL_BVALOVAL | GOTGCTL_VBVALOVAL; + else + gotgctl &= ~(GOTGCTL_BVALOVAL | GOTGCTL_VBVALOVAL); + dwc2_writel(hsotg, gotgctl, GOTGCTL); + + return 0; +} + +static int dwc2_drd_role_sw_set(struct usb_role_switch *sw, enum usb_role role) +{ + struct dwc2_hsotg *hsotg = usb_role_switch_get_drvdata(sw); + unsigned long flags; + int already = 0; + + /* Skip session not in line with dr_mode */ + if ((role == USB_ROLE_DEVICE && hsotg->dr_mode == USB_DR_MODE_HOST) || + (role == USB_ROLE_HOST && hsotg->dr_mode == USB_DR_MODE_PERIPHERAL)) + return -EINVAL; + +#if IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) || \ + IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE) + /* Skip session if core is in test mode */ + if (role == USB_ROLE_NONE && hsotg->test_mode) { + dev_dbg(hsotg->dev, "Core is in test mode\n"); + return -EBUSY; + } +#endif + + spin_lock_irqsave(&hsotg->lock, flags); + + if (role == USB_ROLE_HOST) { + already = dwc2_ovr_avalid(hsotg, true); + } else if (role == USB_ROLE_DEVICE) { + already = dwc2_ovr_bvalid(hsotg, true); + /* This clear DCTL.SFTDISCON bit */ + dwc2_hsotg_core_connect(hsotg); + } else { + if (dwc2_is_device_mode(hsotg)) { + if (!dwc2_ovr_bvalid(hsotg, false)) + /* This set DCTL.SFTDISCON bit */ + dwc2_hsotg_core_disconnect(hsotg); + } else { + dwc2_ovr_avalid(hsotg, false); + } + } + + spin_unlock_irqrestore(&hsotg->lock, flags); + + if (!already && hsotg->dr_mode == USB_DR_MODE_OTG) + /* This will raise a Connector ID Status Change Interrupt */ + dwc2_force_mode(hsotg, role == USB_ROLE_HOST); + + dev_dbg(hsotg->dev, "%s-session valid\n", + role == USB_ROLE_NONE ? "No" : + role == USB_ROLE_HOST ? "A" : "B"); + + return 0; +} + +int dwc2_drd_init(struct dwc2_hsotg *hsotg) +{ + struct usb_role_switch_desc role_sw_desc = {0}; + struct usb_role_switch *role_sw; + int ret; + + if (!device_property_read_bool(hsotg->dev, "usb-role-switch")) + return 0; + + role_sw_desc.driver_data = hsotg; + role_sw_desc.fwnode = dev_fwnode(hsotg->dev); + role_sw_desc.set = dwc2_drd_role_sw_set; + role_sw_desc.allow_userspace_control = true; + + role_sw = usb_role_switch_register(hsotg->dev, &role_sw_desc); + if (IS_ERR(role_sw)) { + ret = PTR_ERR(role_sw); + dev_err(hsotg->dev, + "failed to register role switch: %d\n", ret); + return ret; + } + + hsotg->role_sw = role_sw; + + /* Enable override and initialize values */ + dwc2_ovr_init(hsotg); + + return 0; +} + +void dwc2_drd_suspend(struct dwc2_hsotg *hsotg) +{ + u32 gintsts, gintmsk; + + if (hsotg->role_sw && !hsotg->params.external_id_pin_ctl) { + gintmsk = dwc2_readl(hsotg, GINTMSK); + gintmsk &= ~GINTSTS_CONIDSTSCHNG; + dwc2_writel(hsotg, gintmsk, GINTMSK); + gintsts = dwc2_readl(hsotg, GINTSTS); + dwc2_writel(hsotg, gintsts | GINTSTS_CONIDSTSCHNG, GINTSTS); + } +} + +void dwc2_drd_resume(struct dwc2_hsotg *hsotg) +{ + u32 gintsts, gintmsk; + + if (hsotg->role_sw && !hsotg->params.external_id_pin_ctl) { + gintsts = dwc2_readl(hsotg, GINTSTS); + dwc2_writel(hsotg, gintsts | GINTSTS_CONIDSTSCHNG, GINTSTS); + gintmsk = dwc2_readl(hsotg, GINTMSK); + gintmsk |= GINTSTS_CONIDSTSCHNG; + dwc2_writel(hsotg, gintmsk, GINTMSK); + } +} + +void dwc2_drd_exit(struct dwc2_hsotg *hsotg) +{ + if (hsotg->role_sw) + usb_role_switch_unregister(hsotg->role_sw); +} |