Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+237
View File
@@ -0,0 +1,237 @@
# SPDX-License-Identifier: GPL-2.0
config USB_DWC3
tristate "DesignWare USB3 DRD Core Support"
depends on (USB || USB_GADGET) && HAS_DMA
depends on (EXTCON || EXTCON=n)
select USB_XHCI_PLATFORM if USB_XHCI_HCD
select USB_ROLE_SWITCH if USB_DWC3_DUAL_ROLE
help
Say Y or M here if your system has a Dual Role SuperSpeed
USB controller based on the DesignWare USB3 IP Core.
If you choose to build this driver as a dynamically linked
module, the module will be called dwc3.ko.
if USB_DWC3
config USB_DWC3_ULPI
bool "Register ULPI PHY Interface"
depends on USB_ULPI_BUS=y || USB_ULPI_BUS=USB_DWC3
help
Select this if you have ULPI type PHY attached to your DWC3
controller.
choice
prompt "DWC3 Mode Selection"
default USB_DWC3_DUAL_ROLE if (USB && USB_GADGET)
default USB_DWC3_HOST if (USB && !USB_GADGET)
default USB_DWC3_GADGET if (!USB && USB_GADGET)
config USB_DWC3_HOST
bool "Host only mode"
depends on USB=y || USB=USB_DWC3
help
Select this when you want to use DWC3 in host mode only,
thereby the gadget feature will be regressed.
config USB_DWC3_GADGET
bool "Gadget only mode"
depends on USB_GADGET=y || USB_GADGET=USB_DWC3
help
Select this when you want to use DWC3 in gadget mode only,
thereby the host feature will be regressed.
config USB_DWC3_DUAL_ROLE
bool "Dual Role mode"
depends on ((USB=y || USB=USB_DWC3) && (USB_GADGET=y || USB_GADGET=USB_DWC3))
help
This is the default mode of working of DWC3 controller where
both host and gadget features are enabled.
endchoice
comment "Platform Glue Driver Support"
config USB_DWC3_OMAP
tristate "Texas Instruments OMAP5 and similar Platforms"
depends on ARCH_OMAP2PLUS || COMPILE_TEST
depends on EXTCON || !EXTCON
depends on OF
default USB_DWC3
help
Some platforms from Texas Instruments like OMAP5, DRA7xxx and
AM437x use this IP for USB2/3 functionality.
Say 'Y' or 'M' here if you have one such device
config USB_DWC3_EXYNOS
tristate "Samsung Exynos SoC Platform"
depends on (ARCH_EXYNOS || COMPILE_TEST) && OF
default USB_DWC3
help
Recent Samsung Exynos SoCs (Exynos5250, Exynos5410, Exynos542x,
Exynos5800, Exynos5433, Exynos7) ship with one DesignWare Core USB3
IP inside, say 'Y' or 'M' if you have one such device.
config USB_DWC3_PCI
tristate "PCIe-based Platforms"
depends on USB_PCI && ACPI
default USB_DWC3
help
If you're using the DesignWare Core IP with a PCIe (but not HAPS
platform), please say 'Y' or 'M' here.
config USB_DWC3_HAPS
tristate "Synopsys PCIe-based HAPS Platforms"
depends on USB_PCI
default USB_DWC3
help
If you're using the DesignWare Core IP with a Synopsys PCIe HAPS
platform, please say 'Y' or 'M' here.
config USB_DWC3_KEYSTONE
tristate "Texas Instruments Keystone2/AM654 Platforms"
depends on ARCH_KEYSTONE || ARCH_K3 || COMPILE_TEST
default USB_DWC3
help
Support of USB2/3 functionality in TI Keystone2 and AM654 platforms.
Say 'Y' or 'M' here if you have one such device
config USB_DWC3_MESON_G12A
tristate "Amlogic Meson G12A Platforms"
depends on OF && COMMON_CLK
depends on ARCH_MESON || COMPILE_TEST
default USB_DWC3
select USB_ROLE_SWITCH
select REGMAP_MMIO
help
Support USB2/3 functionality in Amlogic G12A platforms.
Say 'Y' or 'M' if you have one such device.
config USB_DWC3_OF_SIMPLE
tristate "Generic OF Simple Glue Layer"
depends on OF && COMMON_CLK
default USB_DWC3
help
Support USB2/3 functionality in simple SoC integrations.
Currently supports Xilinx and Qualcomm DWC USB3 IP.
Say 'Y' or 'M' if you have one such device.
config USB_DWC3_ST
tristate "STMicroelectronics Platforms"
depends on (ARCH_STI || COMPILE_TEST) && OF
default USB_DWC3
help
STMicroelectronics SoCs with one DesignWare Core USB3 IP
inside (i.e. STiH407).
Say 'Y' or 'M' if you have one such device.
config USB_DWC3_QCOM
tristate "Qualcomm Platform"
depends on ARCH_QCOM || COMPILE_TEST
depends on EXTCON || !EXTCON
depends on OF
default USB_DWC3
help
Some Qualcomm SoCs use DesignWare Core IP for USB2/3
functionality.
This driver also handles Qscratch wrapper which is needed
for peripheral mode support.
Say 'Y' or 'M' if you have one such device.
config USB_DWC3_IMX8MP
tristate "NXP iMX8MP Platform"
depends on OF && COMMON_CLK
depends on (ARCH_MXC && ARM64) || COMPILE_TEST
default USB_DWC3
help
NXP iMX8M Plus SoC use DesignWare Core IP for USB2/3
functionality.
Say 'Y' or 'M' if you have one such device.
config USB_DWC3_IMX
tristate "NXP iMX Platform"
depends on OF && COMMON_CLK
depends on (ARCH_MXC && ARM64) || COMPILE_TEST
default USB_DWC3
help
NXP iMX SoC use DesignWare Core IP for USB2/3
functionality.
This driver also handles the wakeup feature outside
of DesignWare Core.
Say 'Y' or 'M' if you have one such device.
config USB_DWC3_XILINX
tristate "Xilinx Platforms"
depends on (ARCH_ZYNQMP || COMPILE_TEST) && OF
default USB_DWC3
help
Support Xilinx SoCs with DesignWare Core USB3 IP.
This driver handles ZynqMP SoC operations.
Say 'Y' or 'M' if you have one such device.
config USB_DWC3_AM62
tristate "Texas Instruments AM62 Platforms"
depends on ARCH_K3 || COMPILE_TEST
default USB_DWC3
help
Support TI's AM62 platforms with DesignWare Core USB3 IP.
The Designware Core USB3 IP is programmed to operate in
in USB 2.0 mode only.
Say 'Y' or 'M' here if you have one such device
config USB_DWC3_OCTEON
tristate "Cavium Octeon Platforms"
depends on CAVIUM_OCTEON_SOC || COMPILE_TEST
default USB_DWC3
help
Support Cavium Octeon platforms with DesignWare Core USB3 IP.
Only the host mode is currently supported.
Say 'Y' or 'M' here if you have one such device.
config USB_DWC3_RTK
tristate "Realtek DWC3 Platform Driver"
depends on OF && ARCH_REALTEK
default USB_DWC3
select USB_ROLE_SWITCH
help
RTK DHC RTD SoCs with DesignWare Core USB3 IP inside,
and IP Core configured for USB 2.0 and USB 3.0 in host
or dual-role mode.
Say 'Y' or 'M' if you have such device.
config USB_DWC3_GENERIC_PLAT
tristate "DWC3 Generic Platform Driver"
depends on OF && COMMON_CLK
default USB_DWC3
help
Support USB3 functionality in simple SoC integrations.
Currently supports SpacemiT DWC USB3. Platforms using
dwc3-of-simple can easily switch to dwc3-generic by flattening
the dwc3 child node in the device tree.
Say 'Y' or 'M' here if your platform integrates DWC3 in a similar way.
config USB_DWC3_APPLE
tristate "Apple Silicon DWC3 Platform Driver"
depends on OF && ARCH_APPLE
default USB_DWC3
select USB_ROLE_SWITCH
help
Support Apple Silicon SoCs with DesignWare Core USB3 IP.
The DesignWare Core USB3 IP has to be used in dual-role
mode on these machines.
Say 'Y' or 'M' if you have such device.
config USB_DWC3_GOOGLE
tristate "Google Platform"
help
Support the DesignWare Core USB3 IP found on Google Tensor SoCs,
starting with the G5 generation (Laguna). This driver includes
support for hibernation in host mode.
Say 'Y' or 'M' if you have one such device.
To compile this driver as a module, choose M here: the
module will be called dwc3-google.ko.
endif
+63
View File
@@ -0,0 +1,63 @@
# SPDX-License-Identifier: GPL-2.0
# define_trace.h needs to know how to find our header
CFLAGS_trace.o := -I$(src)
obj-$(CONFIG_USB_DWC3) += dwc3.o
dwc3-y := core.o
ifneq ($(CONFIG_TRACING),)
dwc3-y += trace.o
endif
ifneq ($(filter y,$(CONFIG_USB_DWC3_HOST) $(CONFIG_USB_DWC3_DUAL_ROLE)),)
dwc3-y += host.o
endif
ifneq ($(filter y,$(CONFIG_USB_DWC3_GADGET) $(CONFIG_USB_DWC3_DUAL_ROLE)),)
dwc3-y += gadget.o ep0.o
endif
ifneq ($(CONFIG_USB_DWC3_DUAL_ROLE),)
dwc3-y += drd.o
endif
ifneq ($(CONFIG_USB_DWC3_ULPI),)
dwc3-y += ulpi.o
endif
ifneq ($(CONFIG_DEBUG_FS),)
dwc3-y += debugfs.o
endif
##
# Platform-specific glue layers go here
#
# NOTICE: Make sure your glue layer doesn't depend on anything
# which is arch-specific and that it compiles on all situations.
#
# We want to keep this requirement in order to be able to compile
# the entire driver (with all its glue layers) on several architectures
# and make sure it compiles fine. This will also help with allmodconfig
# and allyesconfig builds.
##
obj-$(CONFIG_USB_DWC3_AM62) += dwc3-am62.o
obj-$(CONFIG_USB_DWC3_APPLE) += dwc3-apple.o
obj-$(CONFIG_USB_DWC3_OMAP) += dwc3-omap.o
obj-$(CONFIG_USB_DWC3_EXYNOS) += dwc3-exynos.o
obj-$(CONFIG_USB_DWC3_PCI) += dwc3-pci.o
obj-$(CONFIG_USB_DWC3_HAPS) += dwc3-haps.o
obj-$(CONFIG_USB_DWC3_KEYSTONE) += dwc3-keystone.o
obj-$(CONFIG_USB_DWC3_MESON_G12A) += dwc3-meson-g12a.o
obj-$(CONFIG_USB_DWC3_OF_SIMPLE) += dwc3-of-simple.o
obj-$(CONFIG_USB_DWC3_ST) += dwc3-st.o
obj-$(CONFIG_USB_DWC3_QCOM) += dwc3-qcom.o
obj-$(CONFIG_USB_DWC3_QCOM) += dwc3-qcom-legacy.o
obj-$(CONFIG_USB_DWC3_IMX8MP) += dwc3-imx8mp.o
obj-$(CONFIG_USB_DWC3_IMX) += dwc3-imx.o
obj-$(CONFIG_USB_DWC3_XILINX) += dwc3-xilinx.o
obj-$(CONFIG_USB_DWC3_OCTEON) += dwc3-octeon.o
obj-$(CONFIG_USB_DWC3_RTK) += dwc3-rtk.o
obj-$(CONFIG_USB_DWC3_GENERIC_PLAT) += dwc3-generic-plat.o
obj-$(CONFIG_USB_DWC3_GOOGLE) += dwc3-google.o
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+450
View File
@@ -0,0 +1,450 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* debug.h - DesignWare USB3 DRD Controller Debug Header
*
* Copyright (C) 2010-2011 Texas Instruments Incorporated - https://www.ti.com
*
* Authors: Felipe Balbi <balbi@ti.com>,
* Sebastian Andrzej Siewior <bigeasy@linutronix.de>
*/
#ifndef __DWC3_DEBUG_H
#define __DWC3_DEBUG_H
#include "core.h"
/**
* dwc3_mode_string - returns mode name
* @mode: GCTL.PrtCapDir value
*/
static inline const char *dwc3_mode_string(u32 mode)
{
switch (mode) {
case DWC3_GCTL_PRTCAP_HOST:
return "host";
case DWC3_GCTL_PRTCAP_DEVICE:
return "device";
case DWC3_GCTL_PRTCAP_OTG:
return "otg";
default:
return "UNKNOWN";
}
}
/**
* dwc3_gadget_ep_cmd_string - returns endpoint command string
* @cmd: command code
*/
static inline const char *
dwc3_gadget_ep_cmd_string(u8 cmd)
{
switch (cmd) {
case DWC3_DEPCMD_DEPSTARTCFG:
return "Start New Configuration";
case DWC3_DEPCMD_ENDTRANSFER:
return "End Transfer";
case DWC3_DEPCMD_UPDATETRANSFER:
return "Update Transfer";
case DWC3_DEPCMD_STARTTRANSFER:
return "Start Transfer";
case DWC3_DEPCMD_CLEARSTALL:
return "Clear Stall";
case DWC3_DEPCMD_SETSTALL:
return "Set Stall";
case DWC3_DEPCMD_GETEPSTATE:
return "Get Endpoint State";
case DWC3_DEPCMD_SETTRANSFRESOURCE:
return "Set Endpoint Transfer Resource";
case DWC3_DEPCMD_SETEPCONFIG:
return "Set Endpoint Configuration";
default:
return "UNKNOWN command";
}
}
/**
* dwc3_gadget_generic_cmd_string - returns generic command string
* @cmd: command code
*/
static inline const char *
dwc3_gadget_generic_cmd_string(u8 cmd)
{
switch (cmd) {
case DWC3_DGCMD_SET_LMP:
return "Set LMP";
case DWC3_DGCMD_SET_PERIODIC_PAR:
return "Set Periodic Parameters";
case DWC3_DGCMD_XMIT_FUNCTION:
return "Transmit Function Wake Device Notification";
case DWC3_DGCMD_SET_SCRATCHPAD_ADDR_LO:
return "Set Scratchpad Buffer Array Address Lo";
case DWC3_DGCMD_SET_SCRATCHPAD_ADDR_HI:
return "Set Scratchpad Buffer Array Address Hi";
case DWC3_DGCMD_SELECTED_FIFO_FLUSH:
return "Selected FIFO Flush";
case DWC3_DGCMD_ALL_FIFO_FLUSH:
return "All FIFO Flush";
case DWC3_DGCMD_SET_ENDPOINT_NRDY:
return "Set Endpoint NRDY";
case DWC3_DGCMD_SET_ENDPOINT_PRIME:
return "Set Endpoint Prime";
case DWC3_DGCMD_RUN_SOC_BUS_LOOPBACK:
return "Run SoC Bus Loopback Test";
case DWC3_DGCMD_DEV_NOTIFICATION:
return "Device Notification";
default:
return "UNKNOWN";
}
}
/**
* dwc3_gadget_link_string - returns link name
* @link_state: link state code
*/
static inline const char *
dwc3_gadget_link_string(enum dwc3_link_state link_state)
{
switch (link_state) {
case DWC3_LINK_STATE_U0:
return "U0";
case DWC3_LINK_STATE_U1:
return "U1";
case DWC3_LINK_STATE_U2:
return "U2";
case DWC3_LINK_STATE_U3:
return "U3";
case DWC3_LINK_STATE_SS_DIS:
return "SS.Disabled";
case DWC3_LINK_STATE_RX_DET:
return "RX.Detect";
case DWC3_LINK_STATE_SS_INACT:
return "SS.Inactive";
case DWC3_LINK_STATE_POLL:
return "Polling";
case DWC3_LINK_STATE_RECOV:
return "Recovery";
case DWC3_LINK_STATE_HRESET:
return "Hot Reset";
case DWC3_LINK_STATE_CMPLY:
return "Compliance";
case DWC3_LINK_STATE_LPBK:
return "Loopback";
case DWC3_LINK_STATE_RESET:
return "Reset";
case DWC3_LINK_STATE_RESUME:
return "Resume";
default:
return "UNKNOWN link state";
}
}
/**
* dwc3_gadget_hs_link_string - returns highspeed and below link name
* @link_state: link state code
*/
static inline const char *
dwc3_gadget_hs_link_string(enum dwc3_link_state link_state)
{
switch (link_state) {
case DWC3_LINK_STATE_U0:
return "On";
case DWC3_LINK_STATE_U2:
return "Sleep";
case DWC3_LINK_STATE_U3:
return "Suspend";
case DWC3_LINK_STATE_SS_DIS:
return "Disconnected";
case DWC3_LINK_STATE_RX_DET:
return "Early Suspend";
case DWC3_LINK_STATE_RECOV:
return "Recovery";
case DWC3_LINK_STATE_RESET:
return "Reset";
case DWC3_LINK_STATE_RESUME:
return "Resume";
default:
return "UNKNOWN link state";
}
}
/**
* dwc3_trb_type_string - returns TRB type as a string
* @type: the type of the TRB
*/
static inline const char *dwc3_trb_type_string(unsigned int type)
{
switch (type) {
case DWC3_TRBCTL_NORMAL:
return "normal";
case DWC3_TRBCTL_CONTROL_SETUP:
return "setup";
case DWC3_TRBCTL_CONTROL_STATUS2:
return "status2";
case DWC3_TRBCTL_CONTROL_STATUS3:
return "status3";
case DWC3_TRBCTL_CONTROL_DATA:
return "data";
case DWC3_TRBCTL_ISOCHRONOUS_FIRST:
return "isoc-first";
case DWC3_TRBCTL_ISOCHRONOUS:
return "isoc";
case DWC3_TRBCTL_LINK_TRB:
return "link";
default:
return "UNKNOWN";
}
}
static inline const char *dwc3_ep0_state_string(enum dwc3_ep0_state state)
{
switch (state) {
case EP0_UNCONNECTED:
return "Unconnected";
case EP0_SETUP_PHASE:
return "Setup Phase";
case EP0_DATA_PHASE:
return "Data Phase";
case EP0_STATUS_PHASE:
return "Status Phase";
default:
return "UNKNOWN";
}
}
/**
* dwc3_gadget_event_string - returns event name
* @event: the event code
*/
static inline const char *dwc3_gadget_event_string(char *str, size_t size,
const struct dwc3_event_devt *event)
{
enum dwc3_link_state state = event->event_info & DWC3_LINK_STATE_MASK;
switch (event->type) {
case DWC3_DEVICE_EVENT_DISCONNECT:
snprintf(str, size, "Disconnect: [%s]",
dwc3_gadget_link_string(state));
break;
case DWC3_DEVICE_EVENT_RESET:
snprintf(str, size, "Reset [%s]",
dwc3_gadget_link_string(state));
break;
case DWC3_DEVICE_EVENT_CONNECT_DONE:
snprintf(str, size, "Connection Done [%s]",
dwc3_gadget_link_string(state));
break;
case DWC3_DEVICE_EVENT_LINK_STATUS_CHANGE:
snprintf(str, size, "Link Change [%s]",
dwc3_gadget_link_string(state));
break;
case DWC3_DEVICE_EVENT_WAKEUP:
snprintf(str, size, "WakeUp [%s]",
dwc3_gadget_link_string(state));
break;
case DWC3_DEVICE_EVENT_SUSPEND:
snprintf(str, size, "Suspend [%s]",
dwc3_gadget_link_string(state));
break;
case DWC3_DEVICE_EVENT_SOF:
snprintf(str, size, "Start-Of-Frame [%s]",
dwc3_gadget_link_string(state));
break;
case DWC3_DEVICE_EVENT_ERRATIC_ERROR:
snprintf(str, size, "Erratic Error [%s]",
dwc3_gadget_link_string(state));
break;
case DWC3_DEVICE_EVENT_CMD_CMPL:
snprintf(str, size, "Command Complete [%s]",
dwc3_gadget_link_string(state));
break;
case DWC3_DEVICE_EVENT_OVERFLOW:
snprintf(str, size, "Overflow [%s]",
dwc3_gadget_link_string(state));
break;
default:
snprintf(str, size, "UNKNOWN");
}
return str;
}
/**
* dwc3_ep_event_string - returns event name
* @event: then event code
*/
static inline const char *dwc3_ep_event_string(char *str, size_t size,
const struct dwc3_event_depevt *event, u32 ep0state)
{
u8 epnum = event->endpoint_number;
size_t len;
int status;
len = scnprintf(str, size, "ep%d%s: ", epnum >> 1,
(epnum & 1) ? "in" : "out");
status = event->status;
switch (event->endpoint_event) {
case DWC3_DEPEVT_XFERCOMPLETE:
len += scnprintf(str + len, size - len,
"Transfer Complete (%c%c%c)",
status & DEPEVT_STATUS_SHORT ? 'S' : 's',
status & DEPEVT_STATUS_IOC ? 'I' : 'i',
status & DEPEVT_STATUS_LST ? 'L' : 'l');
if (epnum <= 1)
scnprintf(str + len, size - len, " [%s]",
dwc3_ep0_state_string(ep0state));
break;
case DWC3_DEPEVT_XFERINPROGRESS:
scnprintf(str + len, size - len,
"Transfer In Progress [%08x] (%c%c%c)",
event->parameters,
status & DEPEVT_STATUS_SHORT ? 'S' : 's',
status & DEPEVT_STATUS_IOC ? 'I' : 'i',
status & DEPEVT_STATUS_LST ? 'M' : 'm');
break;
case DWC3_DEPEVT_XFERNOTREADY:
len += scnprintf(str + len, size - len,
"Transfer Not Ready [%08x]%s",
event->parameters,
status & DEPEVT_STATUS_TRANSFER_ACTIVE ?
" (Active)" : " (Not Active)");
/* Control Endpoints */
if (epnum <= 1) {
int phase = DEPEVT_STATUS_CONTROL_PHASE(event->status);
switch (phase) {
case DEPEVT_STATUS_CONTROL_DATA:
scnprintf(str + len, size - len,
" [Data Phase]");
break;
case DEPEVT_STATUS_CONTROL_STATUS:
scnprintf(str + len, size - len,
" [Status Phase]");
}
}
break;
case DWC3_DEPEVT_RXTXFIFOEVT:
scnprintf(str + len, size - len, "FIFO");
break;
case DWC3_DEPEVT_STREAMEVT:
status = event->status;
switch (status) {
case DEPEVT_STREAMEVT_FOUND:
scnprintf(str + len, size - len, " Stream %d Found",
event->parameters);
break;
case DEPEVT_STREAMEVT_NOTFOUND:
default:
scnprintf(str + len, size - len, " Stream Not Found");
break;
}
break;
case DWC3_DEPEVT_EPCMDCMPLT:
scnprintf(str + len, size - len, "Endpoint Command Complete");
break;
default:
scnprintf(str + len, size - len, "UNKNOWN");
}
return str;
}
/**
* dwc3_gadget_event_type_string - return event name
* @event: the event code
*/
static inline const char *dwc3_gadget_event_type_string(u8 event)
{
switch (event) {
case DWC3_DEVICE_EVENT_DISCONNECT:
return "Disconnect";
case DWC3_DEVICE_EVENT_RESET:
return "Reset";
case DWC3_DEVICE_EVENT_CONNECT_DONE:
return "Connect Done";
case DWC3_DEVICE_EVENT_LINK_STATUS_CHANGE:
return "Link Status Change";
case DWC3_DEVICE_EVENT_WAKEUP:
return "Wake-Up";
case DWC3_DEVICE_EVENT_HIBER_REQ:
return "Hibernation";
case DWC3_DEVICE_EVENT_SUSPEND:
return "Suspend";
case DWC3_DEVICE_EVENT_SOF:
return "Start of Frame";
case DWC3_DEVICE_EVENT_ERRATIC_ERROR:
return "Erratic Error";
case DWC3_DEVICE_EVENT_CMD_CMPL:
return "Command Complete";
case DWC3_DEVICE_EVENT_OVERFLOW:
return "Overflow";
default:
return "UNKNOWN";
}
}
static inline const char *dwc3_decode_event(char *str, size_t size, u32 event,
u32 ep0state)
{
union dwc3_event evt;
memcpy(&evt, &event, sizeof(event));
if (evt.type.is_devspec)
return dwc3_gadget_event_string(str, size, &evt.devt);
else
return dwc3_ep_event_string(str, size, &evt.depevt, ep0state);
}
static inline const char *dwc3_ep_cmd_status_string(int status)
{
switch (status) {
case -ETIMEDOUT:
return "Timed Out";
case 0:
return "Successful";
case DEPEVT_TRANSFER_NO_RESOURCE:
return "No Resource";
case DEPEVT_TRANSFER_BUS_EXPIRY:
return "Bus Expiry";
default:
return "UNKNOWN";
}
}
static inline const char *dwc3_gadget_generic_cmd_status_string(int status)
{
switch (status) {
case -ETIMEDOUT:
return "Timed Out";
case 0:
return "Successful";
case 1:
return "Error";
default:
return "UNKNOWN";
}
}
#ifdef CONFIG_DEBUG_FS
extern void dwc3_debugfs_create_endpoint_dir(struct dwc3_ep *dep);
extern void dwc3_debugfs_remove_endpoint_dir(struct dwc3_ep *dep);
extern void dwc3_debugfs_init(struct dwc3 *d);
extern void dwc3_debugfs_exit(struct dwc3 *d);
#else
static inline void dwc3_debugfs_create_endpoint_dir(struct dwc3_ep *dep)
{ }
static inline void dwc3_debugfs_remove_endpoint_dir(struct dwc3_ep *dep)
{ }
static inline void dwc3_debugfs_init(struct dwc3 *d)
{ }
static inline void dwc3_debugfs_exit(struct dwc3 *d)
{ }
#endif
#endif /* __DWC3_DEBUG_H */
File diff suppressed because it is too large Load Diff
+631
View File
@@ -0,0 +1,631 @@
// SPDX-License-Identifier: GPL-2.0
/*
* drd.c - DesignWare USB3 DRD Controller Dual-role support
*
* Copyright (C) 2017 Texas Instruments Incorporated - https://www.ti.com
*
* Authors: Roger Quadros <rogerq@ti.com>
*/
#include <linux/extcon.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include "debug.h"
#include "core.h"
#include "gadget.h"
static void dwc3_otg_disable_events(struct dwc3 *dwc, u32 disable_mask)
{
u32 reg = dwc3_readl(dwc, DWC3_OEVTEN);
reg &= ~(disable_mask);
dwc3_writel(dwc, DWC3_OEVTEN, reg);
}
static void dwc3_otg_enable_events(struct dwc3 *dwc, u32 enable_mask)
{
u32 reg = dwc3_readl(dwc, DWC3_OEVTEN);
reg |= (enable_mask);
dwc3_writel(dwc, DWC3_OEVTEN, reg);
}
static void dwc3_otg_clear_events(struct dwc3 *dwc)
{
u32 reg = dwc3_readl(dwc, DWC3_OEVT);
dwc3_writel(dwc, DWC3_OEVTEN, reg);
}
#define DWC3_OTG_ALL_EVENTS (DWC3_OEVTEN_XHCIRUNSTPSETEN | \
DWC3_OEVTEN_DEVRUNSTPSETEN | DWC3_OEVTEN_HIBENTRYEN | \
DWC3_OEVTEN_CONIDSTSCHNGEN | DWC3_OEVTEN_HRRCONFNOTIFEN | \
DWC3_OEVTEN_HRRINITNOTIFEN | DWC3_OEVTEN_ADEVIDLEEN | \
DWC3_OEVTEN_ADEVBHOSTENDEN | DWC3_OEVTEN_ADEVHOSTEN | \
DWC3_OEVTEN_ADEVHNPCHNGEN | DWC3_OEVTEN_ADEVSRPDETEN | \
DWC3_OEVTEN_ADEVSESSENDDETEN | DWC3_OEVTEN_BDEVBHOSTENDEN | \
DWC3_OEVTEN_BDEVHNPCHNGEN | DWC3_OEVTEN_BDEVSESSVLDDETEN | \
DWC3_OEVTEN_BDEVVBUSCHNGEN)
static irqreturn_t dwc3_otg_thread_irq(int irq, void *_dwc)
{
struct dwc3 *dwc = _dwc;
spin_lock(&dwc->lock);
if (dwc->otg_restart_host) {
dwc3_otg_host_init(dwc);
dwc->otg_restart_host = false;
}
spin_unlock(&dwc->lock);
dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_OTG);
return IRQ_HANDLED;
}
static irqreturn_t dwc3_otg_irq(int irq, void *_dwc)
{
u32 reg;
struct dwc3 *dwc = _dwc;
irqreturn_t ret = IRQ_NONE;
reg = dwc3_readl(dwc, DWC3_OEVT);
if (reg) {
/* ignore non OTG events, we can't disable them in OEVTEN */
if (!(reg & DWC3_OTG_ALL_EVENTS)) {
dwc3_writel(dwc, DWC3_OEVT, reg);
return IRQ_NONE;
}
if (dwc->current_otg_role == DWC3_OTG_ROLE_HOST &&
!(reg & DWC3_OEVT_DEVICEMODE))
dwc->otg_restart_host = true;
dwc3_writel(dwc, DWC3_OEVT, reg);
ret = IRQ_WAKE_THREAD;
}
return ret;
}
static void dwc3_otgregs_init(struct dwc3 *dwc)
{
u32 reg;
/*
* Prevent host/device reset from resetting OTG core.
* If we don't do this then xhci_reset (USBCMD.HCRST) will reset
* the signal outputs sent to the PHY, the OTG FSM logic of the
* core and also the resets to the VBUS filters inside the core.
*/
reg = dwc3_readl(dwc, DWC3_OCFG);
reg |= DWC3_OCFG_SFTRSTMASK;
dwc3_writel(dwc, DWC3_OCFG, reg);
/* Disable hibernation for simplicity */
reg = dwc3_readl(dwc, DWC3_GCTL);
reg &= ~DWC3_GCTL_GBLHIBERNATIONEN;
dwc3_writel(dwc, DWC3_GCTL, reg);
/*
* Initialize OTG registers as per
* Figure 11-4 OTG Driver Overall Programming Flow
*/
/* OCFG.SRPCap = 0, OCFG.HNPCap = 0 */
reg = dwc3_readl(dwc, DWC3_OCFG);
reg &= ~(DWC3_OCFG_SRPCAP | DWC3_OCFG_HNPCAP);
dwc3_writel(dwc, DWC3_OCFG, reg);
/* OEVT = FFFF */
dwc3_otg_clear_events(dwc);
/* OEVTEN = 0 */
dwc3_otg_disable_events(dwc, DWC3_OTG_ALL_EVENTS);
/* OEVTEN.ConIDStsChngEn = 1. Instead we enable all events */
dwc3_otg_enable_events(dwc, DWC3_OTG_ALL_EVENTS);
/*
* OCTL.PeriMode = 1, OCTL.DevSetHNPEn = 0, OCTL.HstSetHNPEn = 0,
* OCTL.HNPReq = 0
*/
reg = dwc3_readl(dwc, DWC3_OCTL);
reg |= DWC3_OCTL_PERIMODE;
reg &= ~(DWC3_OCTL_DEVSETHNPEN | DWC3_OCTL_HSTSETHNPEN |
DWC3_OCTL_HNPREQ);
dwc3_writel(dwc, DWC3_OCTL, reg);
}
static int dwc3_otg_get_irq(struct dwc3 *dwc)
{
struct platform_device *dwc3_pdev = to_platform_device(dwc->dev);
int irq;
irq = platform_get_irq_byname_optional(dwc3_pdev, "otg");
if (irq > 0)
goto out;
if (irq == -EPROBE_DEFER)
goto out;
irq = platform_get_irq_byname_optional(dwc3_pdev, "dwc_usb3");
if (irq > 0)
goto out;
if (irq == -EPROBE_DEFER)
goto out;
irq = platform_get_irq(dwc3_pdev, 0);
if (irq > 0)
goto out;
if (!irq)
irq = -EINVAL;
out:
return irq;
}
void dwc3_otg_init(struct dwc3 *dwc)
{
u32 reg;
/*
* As per Figure 11-4 OTG Driver Overall Programming Flow,
* block "Initialize GCTL for OTG operation".
*/
/* GCTL.PrtCapDir=2'b11 */
dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_OTG, true);
/* GUSB2PHYCFG0.SusPHY=0 */
reg = dwc3_readl(dwc, DWC3_GUSB2PHYCFG(0));
reg &= ~DWC3_GUSB2PHYCFG_SUSPHY;
dwc3_writel(dwc, DWC3_GUSB2PHYCFG(0), reg);
/* Initialize OTG registers */
dwc3_otgregs_init(dwc);
}
void dwc3_otg_exit(struct dwc3 *dwc)
{
/* disable all OTG IRQs */
dwc3_otg_disable_events(dwc, DWC3_OTG_ALL_EVENTS);
/* clear all events */
dwc3_otg_clear_events(dwc);
}
/* should be called before Host controller driver is started */
void dwc3_otg_host_init(struct dwc3 *dwc)
{
u32 reg;
/* As per Figure 11-10 A-Device Flow Diagram */
/* OCFG.HNPCap = 0, OCFG.SRPCap = 0. Already 0 */
/*
* OCTL.PeriMode=0, OCTL.TermSelDLPulse = 0,
* OCTL.DevSetHNPEn = 0, OCTL.HstSetHNPEn = 0
*/
reg = dwc3_readl(dwc, DWC3_OCTL);
reg &= ~(DWC3_OCTL_PERIMODE | DWC3_OCTL_TERMSELIDPULSE |
DWC3_OCTL_DEVSETHNPEN | DWC3_OCTL_HSTSETHNPEN);
dwc3_writel(dwc, DWC3_OCTL, reg);
/*
* OCFG.DisPrtPwrCutoff = 0/1
*/
reg = dwc3_readl(dwc, DWC3_OCFG);
reg &= ~DWC3_OCFG_DISPWRCUTTOFF;
dwc3_writel(dwc, DWC3_OCFG, reg);
/*
* OCFG.SRPCap = 1, OCFG.HNPCap = GHWPARAMS6.HNP_CAP
* We don't want SRP/HNP for simple dual-role so leave
* these disabled.
*/
/*
* OEVTEN.OTGADevHostEvntEn = 1
* OEVTEN.OTGADevSessEndDetEvntEn = 1
* We don't want HNP/role-swap so leave these disabled.
*/
/* GUSB2PHYCFG.ULPIAutoRes = 1/0, GUSB2PHYCFG.SusPHY = 1 */
if (!dwc->dis_u2_susphy_quirk) {
reg = dwc3_readl(dwc, DWC3_GUSB2PHYCFG(0));
reg |= DWC3_GUSB2PHYCFG_SUSPHY;
dwc3_writel(dwc, DWC3_GUSB2PHYCFG(0), reg);
}
/* Set Port Power to enable VBUS: OCTL.PrtPwrCtl = 1 */
reg = dwc3_readl(dwc, DWC3_OCTL);
reg |= DWC3_OCTL_PRTPWRCTL;
dwc3_writel(dwc, DWC3_OCTL, reg);
}
/* should be called after Host controller driver is stopped */
static void dwc3_otg_host_exit(struct dwc3 *dwc)
{
u32 reg;
/*
* Exit from A-device flow as per
* Figure 11-4 OTG Driver Overall Programming Flow
*/
/*
* OEVTEN.OTGADevBHostEndEvntEn=0, OEVTEN.OTGADevHNPChngEvntEn=0
* OEVTEN.OTGADevSessEndDetEvntEn=0,
* OEVTEN.OTGADevHostEvntEn = 0
* But we don't disable any OTG events
*/
/* OCTL.HstSetHNPEn = 0, OCTL.PrtPwrCtl=0 */
reg = dwc3_readl(dwc, DWC3_OCTL);
reg &= ~(DWC3_OCTL_HSTSETHNPEN | DWC3_OCTL_PRTPWRCTL);
dwc3_writel(dwc, DWC3_OCTL, reg);
}
/* should be called before the gadget controller driver is started */
static void dwc3_otg_device_init(struct dwc3 *dwc)
{
u32 reg;
/* As per Figure 11-20 B-Device Flow Diagram */
/*
* OCFG.HNPCap = GHWPARAMS6.HNP_CAP, OCFG.SRPCap = 1
* but we keep them 0 for simple dual-role operation.
*/
reg = dwc3_readl(dwc, DWC3_OCFG);
/* OCFG.OTGSftRstMsk = 0/1 */
reg |= DWC3_OCFG_SFTRSTMASK;
dwc3_writel(dwc, DWC3_OCFG, reg);
/*
* OCTL.PeriMode = 1
* OCTL.TermSelDLPulse = 0/1, OCTL.HNPReq = 0
* OCTL.DevSetHNPEn = 0, OCTL.HstSetHNPEn = 0
*/
reg = dwc3_readl(dwc, DWC3_OCTL);
reg |= DWC3_OCTL_PERIMODE;
reg &= ~(DWC3_OCTL_TERMSELIDPULSE | DWC3_OCTL_HNPREQ |
DWC3_OCTL_DEVSETHNPEN | DWC3_OCTL_HSTSETHNPEN);
dwc3_writel(dwc, DWC3_OCTL, reg);
/* OEVTEN.OTGBDevSesVldDetEvntEn = 1 */
dwc3_otg_enable_events(dwc, DWC3_OEVTEN_BDEVSESSVLDDETEN);
/* GUSB2PHYCFG.ULPIAutoRes = 0, GUSB2PHYCFG0.SusPHY = 1 */
if (!dwc->dis_u2_susphy_quirk) {
reg = dwc3_readl(dwc, DWC3_GUSB2PHYCFG(0));
reg |= DWC3_GUSB2PHYCFG_SUSPHY;
dwc3_writel(dwc, DWC3_GUSB2PHYCFG(0), reg);
}
/* GCTL.GblHibernationEn = 0. Already 0. */
}
/* should be called after the gadget controller driver is stopped */
static void dwc3_otg_device_exit(struct dwc3 *dwc)
{
u32 reg;
/*
* Exit from B-device flow as per
* Figure 11-4 OTG Driver Overall Programming Flow
*/
/*
* OEVTEN.OTGBDevHNPChngEvntEn = 0
* OEVTEN.OTGBDevVBusChngEvntEn = 0
* OEVTEN.OTGBDevBHostEndEvntEn = 0
*/
dwc3_otg_disable_events(dwc, DWC3_OEVTEN_BDEVHNPCHNGEN |
DWC3_OEVTEN_BDEVVBUSCHNGEN |
DWC3_OEVTEN_BDEVBHOSTENDEN);
/* OCTL.DevSetHNPEn = 0, OCTL.HNPReq = 0, OCTL.PeriMode=1 */
reg = dwc3_readl(dwc, DWC3_OCTL);
reg &= ~(DWC3_OCTL_DEVSETHNPEN | DWC3_OCTL_HNPREQ);
reg |= DWC3_OCTL_PERIMODE;
dwc3_writel(dwc, DWC3_OCTL, reg);
}
void dwc3_otg_update(struct dwc3 *dwc, bool ignore_idstatus)
{
int ret;
u32 reg;
int id;
unsigned long flags;
int i;
if (dwc->dr_mode != USB_DR_MODE_OTG)
return;
/* don't do anything if debug user changed role to not OTG */
if (dwc->current_dr_role != DWC3_GCTL_PRTCAP_OTG)
return;
if (!ignore_idstatus) {
reg = dwc3_readl(dwc, DWC3_OSTS);
id = !!(reg & DWC3_OSTS_CONIDSTS);
dwc->desired_otg_role = id ? DWC3_OTG_ROLE_DEVICE :
DWC3_OTG_ROLE_HOST;
}
if (dwc->desired_otg_role == dwc->current_otg_role)
return;
switch (dwc->current_otg_role) {
case DWC3_OTG_ROLE_HOST:
dwc3_host_exit(dwc);
spin_lock_irqsave(&dwc->lock, flags);
dwc3_otg_host_exit(dwc);
spin_unlock_irqrestore(&dwc->lock, flags);
break;
case DWC3_OTG_ROLE_DEVICE:
dwc3_gadget_exit(dwc);
spin_lock_irqsave(&dwc->lock, flags);
dwc3_event_buffers_cleanup(dwc);
dwc3_otg_device_exit(dwc);
spin_unlock_irqrestore(&dwc->lock, flags);
break;
default:
break;
}
spin_lock_irqsave(&dwc->lock, flags);
dwc->current_otg_role = dwc->desired_otg_role;
spin_unlock_irqrestore(&dwc->lock, flags);
switch (dwc->desired_otg_role) {
case DWC3_OTG_ROLE_HOST:
spin_lock_irqsave(&dwc->lock, flags);
dwc3_otgregs_init(dwc);
dwc3_otg_host_init(dwc);
spin_unlock_irqrestore(&dwc->lock, flags);
dwc3_pre_set_role(dwc, USB_ROLE_HOST);
ret = dwc3_host_init(dwc);
if (ret) {
dev_err(dwc->dev, "failed to initialize host\n");
} else {
if (dwc->usb2_phy)
otg_set_vbus(dwc->usb2_phy->otg, true);
for (i = 0; i < dwc->num_usb2_ports; i++) {
if (dwc->usb2_generic_phy[i]) {
phy_set_mode(dwc->usb2_generic_phy[i],
PHY_MODE_USB_HOST);
}
}
}
break;
case DWC3_OTG_ROLE_DEVICE:
spin_lock_irqsave(&dwc->lock, flags);
dwc3_otgregs_init(dwc);
dwc3_otg_device_init(dwc);
dwc3_event_buffers_setup(dwc);
spin_unlock_irqrestore(&dwc->lock, flags);
if (dwc->usb2_phy)
otg_set_vbus(dwc->usb2_phy->otg, false);
if (dwc->usb2_generic_phy[0])
phy_set_mode(dwc->usb2_generic_phy[0], PHY_MODE_USB_DEVICE);
dwc3_pre_set_role(dwc, USB_ROLE_DEVICE);
ret = dwc3_gadget_init(dwc);
if (ret)
dev_err(dwc->dev, "failed to initialize peripheral\n");
break;
default:
break;
}
}
static void dwc3_drd_update(struct dwc3 *dwc)
{
int id;
if (dwc->edev) {
id = extcon_get_state(dwc->edev, EXTCON_USB_HOST);
if (id < 0)
id = 0;
dwc3_set_mode(dwc, id ?
DWC3_GCTL_PRTCAP_HOST :
DWC3_GCTL_PRTCAP_DEVICE);
}
}
static int dwc3_drd_notifier(struct notifier_block *nb,
unsigned long event, void *ptr)
{
struct dwc3 *dwc = container_of(nb, struct dwc3, edev_nb);
u32 mode = event ? DWC3_GCTL_PRTCAP_HOST : DWC3_GCTL_PRTCAP_DEVICE;
enum usb_role role = mode == DWC3_GCTL_PRTCAP_HOST ?
USB_ROLE_HOST : USB_ROLE_DEVICE;
dwc3_pre_set_role(dwc, role);
dwc3_set_mode(dwc, mode);
return NOTIFY_DONE;
}
#if IS_ENABLED(CONFIG_USB_ROLE_SWITCH)
#define ROLE_SWITCH 1
static int dwc3_usb_role_switch_set(struct usb_role_switch *sw,
enum usb_role role)
{
struct dwc3 *dwc = usb_role_switch_get_drvdata(sw);
u32 mode;
switch (role) {
case USB_ROLE_HOST:
mode = DWC3_GCTL_PRTCAP_HOST;
break;
case USB_ROLE_DEVICE:
mode = DWC3_GCTL_PRTCAP_DEVICE;
break;
default:
if (dwc->role_switch_default_mode == USB_DR_MODE_HOST)
mode = DWC3_GCTL_PRTCAP_HOST;
else
mode = DWC3_GCTL_PRTCAP_DEVICE;
break;
}
dwc3_pre_set_role(dwc, role);
dwc3_set_mode(dwc, mode);
return 0;
}
static enum usb_role dwc3_usb_role_switch_get(struct usb_role_switch *sw)
{
struct dwc3 *dwc = usb_role_switch_get_drvdata(sw);
unsigned long flags;
enum usb_role role;
spin_lock_irqsave(&dwc->lock, flags);
switch (dwc->current_dr_role) {
case DWC3_GCTL_PRTCAP_HOST:
role = USB_ROLE_HOST;
break;
case DWC3_GCTL_PRTCAP_DEVICE:
role = USB_ROLE_DEVICE;
break;
case DWC3_GCTL_PRTCAP_OTG:
role = dwc->current_otg_role;
break;
default:
if (dwc->role_switch_default_mode == USB_DR_MODE_HOST)
role = USB_ROLE_HOST;
else
role = USB_ROLE_DEVICE;
break;
}
spin_unlock_irqrestore(&dwc->lock, flags);
return role;
}
static int dwc3_setup_role_switch(struct dwc3 *dwc)
{
struct usb_role_switch_desc dwc3_role_switch = {NULL};
u32 mode;
dwc->role_switch_default_mode = usb_get_role_switch_default_mode(dwc->dev);
if (dwc->role_switch_default_mode == USB_DR_MODE_HOST) {
mode = DWC3_GCTL_PRTCAP_HOST;
} else {
dwc->role_switch_default_mode = USB_DR_MODE_PERIPHERAL;
mode = DWC3_GCTL_PRTCAP_DEVICE;
}
dwc3_set_mode(dwc, mode);
dwc3_role_switch.fwnode = dev_fwnode(dwc->dev);
dwc3_role_switch.set = dwc3_usb_role_switch_set;
dwc3_role_switch.get = dwc3_usb_role_switch_get;
dwc3_role_switch.driver_data = dwc;
dwc3_role_switch.allow_userspace_control = true;
dwc->role_sw = usb_role_switch_register(dwc->dev, &dwc3_role_switch);
if (IS_ERR(dwc->role_sw))
return PTR_ERR(dwc->role_sw);
if (dwc->dev->of_node) {
/* 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
#define ROLE_SWITCH 0
#define dwc3_setup_role_switch(x) 0
#endif
int dwc3_drd_init(struct dwc3 *dwc)
{
int ret, irq;
if (ROLE_SWITCH &&
device_property_read_bool(dwc->dev, "usb-role-switch"))
return dwc3_setup_role_switch(dwc);
if (dwc->edev) {
dwc->edev_nb.notifier_call = dwc3_drd_notifier;
ret = extcon_register_notifier(dwc->edev, EXTCON_USB_HOST,
&dwc->edev_nb);
if (ret < 0) {
dev_err(dwc->dev, "couldn't register cable notifier\n");
return ret;
}
dwc3_drd_update(dwc);
} else {
dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_OTG, true);
/* use OTG block to get ID event */
irq = dwc3_otg_get_irq(dwc);
if (irq < 0)
return irq;
dwc->otg_irq = irq;
/* disable all OTG IRQs */
dwc3_otg_disable_events(dwc, DWC3_OTG_ALL_EVENTS);
/* clear all events */
dwc3_otg_clear_events(dwc);
ret = request_threaded_irq(dwc->otg_irq, dwc3_otg_irq,
dwc3_otg_thread_irq,
IRQF_SHARED, "dwc3-otg", dwc);
if (ret) {
dev_err(dwc->dev, "failed to request irq #%d --> %d\n",
dwc->otg_irq, ret);
ret = -ENODEV;
return ret;
}
dwc3_otg_init(dwc);
dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_OTG);
}
return 0;
}
void dwc3_drd_exit(struct dwc3 *dwc)
{
unsigned long flags;
if (dwc->role_sw)
usb_role_switch_unregister(dwc->role_sw);
if (dwc->edev)
extcon_unregister_notifier(dwc->edev, EXTCON_USB_HOST,
&dwc->edev_nb);
cancel_work_sync(&dwc->drd_work);
/* debug user might have changed role, clean based on current role */
switch (dwc->current_dr_role) {
case DWC3_GCTL_PRTCAP_HOST:
dwc3_host_exit(dwc);
break;
case DWC3_GCTL_PRTCAP_DEVICE:
dwc3_gadget_exit(dwc);
dwc3_event_buffers_cleanup(dwc);
break;
case DWC3_GCTL_PRTCAP_OTG:
dwc3_otg_exit(dwc);
spin_lock_irqsave(&dwc->lock, flags);
dwc->desired_otg_role = DWC3_OTG_ROLE_IDLE;
spin_unlock_irqrestore(&dwc->lock, flags);
dwc3_otg_update(dwc, 1);
break;
default:
break;
}
if (dwc->otg_irq)
free_irq(dwc->otg_irq, dwc);
}
+414
View File
@@ -0,0 +1,414 @@
// SPDX-License-Identifier: GPL-2.0
/*
* dwc3-am62.c - TI specific Glue layer for AM62 DWC3 USB Controller
*
* Copyright (C) 2022 Texas Instruments Incorporated - https://www.ti.com
*/
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/mfd/syscon.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/pm_runtime.h>
#include <linux/clk.h>
#include <linux/regmap.h>
#include <linux/pinctrl/consumer.h>
#include "core.h"
/* USB WRAPPER register offsets */
#define USBSS_PID 0x0
#define USBSS_OVERCURRENT_CTRL 0x4
#define USBSS_PHY_CONFIG 0x8
#define USBSS_PHY_TEST 0xc
#define USBSS_CORE_STAT 0x14
#define USBSS_HOST_VBUS_CTRL 0x18
#define USBSS_MODE_CONTROL 0x1c
#define USBSS_WAKEUP_CONFIG 0x30
#define USBSS_WAKEUP_STAT 0x34
#define USBSS_OVERRIDE_CONFIG 0x38
#define USBSS_IRQ_MISC_STATUS_RAW 0x430
#define USBSS_IRQ_MISC_STATUS 0x434
#define USBSS_IRQ_MISC_ENABLE_SET 0x438
#define USBSS_IRQ_MISC_ENABLE_CLR 0x43c
#define USBSS_IRQ_MISC_EOI 0x440
#define USBSS_INTR_TEST 0x490
#define USBSS_VBUS_FILTER 0x614
#define USBSS_VBUS_STAT 0x618
#define USBSS_DEBUG_CFG 0x708
#define USBSS_DEBUG_DATA 0x70c
#define USBSS_HOST_HUB_CTRL 0x714
/* PHY CONFIG register bits */
#define USBSS_PHY_VBUS_SEL_MASK GENMASK(2, 1)
#define USBSS_PHY_VBUS_SEL_SHIFT 1
#define USBSS_PHY_LANE_REVERSE BIT(0)
/* CORE STAT register bits */
#define USBSS_CORE_OPERATIONAL_MODE_MASK GENMASK(13, 12)
#define USBSS_CORE_OPERATIONAL_MODE_SHIFT 12
/* MODE CONTROL register bits */
#define USBSS_MODE_VALID BIT(0)
/* WAKEUP CONFIG register bits */
#define USBSS_WAKEUP_CFG_OVERCURRENT_EN BIT(3)
#define USBSS_WAKEUP_CFG_LINESTATE_EN BIT(2)
#define USBSS_WAKEUP_CFG_SESSVALID_EN BIT(1)
#define USBSS_WAKEUP_CFG_VBUSVALID_EN BIT(0)
#define USBSS_WAKEUP_CFG_ALL (USBSS_WAKEUP_CFG_VBUSVALID_EN | \
USBSS_WAKEUP_CFG_SESSVALID_EN | \
USBSS_WAKEUP_CFG_LINESTATE_EN | \
USBSS_WAKEUP_CFG_OVERCURRENT_EN)
#define USBSS_WAKEUP_CFG_NONE 0
/* WAKEUP STAT register bits */
#define USBSS_WAKEUP_STAT_OVERCURRENT BIT(4)
#define USBSS_WAKEUP_STAT_LINESTATE BIT(3)
#define USBSS_WAKEUP_STAT_SESSVALID BIT(2)
#define USBSS_WAKEUP_STAT_VBUSVALID BIT(1)
#define USBSS_WAKEUP_STAT_CLR BIT(0)
/* IRQ_MISC_STATUS_RAW register bits */
#define USBSS_IRQ_MISC_RAW_VBUSVALID BIT(22)
#define USBSS_IRQ_MISC_RAW_SESSVALID BIT(20)
/* IRQ_MISC_STATUS register bits */
#define USBSS_IRQ_MISC_VBUSVALID BIT(22)
#define USBSS_IRQ_MISC_SESSVALID BIT(20)
/* IRQ_MISC_ENABLE_SET register bits */
#define USBSS_IRQ_MISC_ENABLE_SET_VBUSVALID BIT(22)
#define USBSS_IRQ_MISC_ENABLE_SET_SESSVALID BIT(20)
/* IRQ_MISC_ENABLE_CLR register bits */
#define USBSS_IRQ_MISC_ENABLE_CLR_VBUSVALID BIT(22)
#define USBSS_IRQ_MISC_ENABLE_CLR_SESSVALID BIT(20)
/* IRQ_MISC_EOI register bits */
#define USBSS_IRQ_MISC_EOI_VECTOR BIT(0)
/* VBUS_STAT register bits */
#define USBSS_VBUS_STAT_SESSVALID BIT(2)
#define USBSS_VBUS_STAT_VBUSVALID BIT(0)
/* USB_PHY_CTRL register bits in CTRL_MMR */
#define PHY_CORE_VOLTAGE_MASK BIT(31)
#define PHY_PLL_REFCLK_MASK GENMASK(3, 0)
/* USB PHY2 register offsets */
#define USB_PHY_PLL_REG12 0x130
#define USB_PHY_PLL_LDO_REF_EN BIT(5)
#define USB_PHY_PLL_LDO_REF_EN_EN BIT(4)
#define DWC3_AM62_AUTOSUSPEND_DELAY 100
#define USBSS_DEBUG_CFG_OFF 0x0
#define USBSS_DEBUG_CFG_DISABLED 0x7
struct dwc3_am62 {
struct device *dev;
void __iomem *usbss;
struct clk *usb2_refclk;
int rate_code;
struct regmap *syscon;
unsigned int offset;
unsigned int vbus_divider;
u32 wakeup_stat;
void __iomem *phy_regs;
};
static const int dwc3_ti_rate_table[] = { /* in KHZ */
9600,
10000,
12000,
19200,
20000,
24000,
25000,
26000,
38400,
40000,
58000,
50000,
52000,
};
static inline u32 dwc3_ti_readl(struct dwc3_am62 *am62, u32 offset)
{
return readl((am62->usbss) + offset);
}
static inline void dwc3_ti_writel(struct dwc3_am62 *am62, u32 offset, u32 value)
{
writel(value, (am62->usbss) + offset);
}
static int phy_syscon_pll_refclk(struct dwc3_am62 *am62)
{
struct device *dev = am62->dev;
struct device_node *node = dev->of_node;
struct regmap *syscon;
int ret;
syscon = syscon_regmap_lookup_by_phandle_args(node, "ti,syscon-phy-pll-refclk",
1, &am62->offset);
if (IS_ERR(syscon)) {
dev_err(dev, "unable to get ti,syscon-phy-pll-refclk regmap\n");
return PTR_ERR(syscon);
}
am62->syscon = syscon;
/* Core voltage. PHY_CORE_VOLTAGE bit Recommended to be 0 always */
ret = regmap_update_bits(am62->syscon, am62->offset, PHY_CORE_VOLTAGE_MASK, 0);
if (ret) {
dev_err(dev, "failed to set phy core voltage\n");
return ret;
}
ret = regmap_update_bits(am62->syscon, am62->offset, PHY_PLL_REFCLK_MASK, am62->rate_code);
if (ret) {
dev_err(dev, "failed to set phy pll reference clock rate\n");
return ret;
}
return 0;
}
static int dwc3_ti_init(struct dwc3_am62 *am62)
{
int ret;
u32 reg;
/* Read the syscon property and set the rate code */
ret = phy_syscon_pll_refclk(am62);
if (ret)
return ret;
/* Workaround Errata i2409 */
if (am62->phy_regs) {
reg = readl(am62->phy_regs + USB_PHY_PLL_REG12);
reg |= USB_PHY_PLL_LDO_REF_EN | USB_PHY_PLL_LDO_REF_EN_EN;
writel(reg, am62->phy_regs + USB_PHY_PLL_REG12);
}
/* VBUS divider select */
reg = dwc3_ti_readl(am62, USBSS_PHY_CONFIG);
if (am62->vbus_divider)
reg |= 1 << USBSS_PHY_VBUS_SEL_SHIFT;
dwc3_ti_writel(am62, USBSS_PHY_CONFIG, reg);
clk_prepare_enable(am62->usb2_refclk);
/* Set mode valid bit to indicate role is valid */
reg = dwc3_ti_readl(am62, USBSS_MODE_CONTROL);
reg |= USBSS_MODE_VALID;
dwc3_ti_writel(am62, USBSS_MODE_CONTROL, reg);
return 0;
}
static int dwc3_ti_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *node = pdev->dev.of_node;
struct dwc3_am62 *am62;
unsigned long rate;
int i, ret;
am62 = devm_kzalloc(dev, sizeof(*am62), GFP_KERNEL);
if (!am62)
return -ENOMEM;
am62->dev = dev;
platform_set_drvdata(pdev, am62);
am62->usbss = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(am62->usbss)) {
dev_err(dev, "can't map IOMEM resource\n");
return PTR_ERR(am62->usbss);
}
am62->usb2_refclk = devm_clk_get(dev, "ref");
if (IS_ERR(am62->usb2_refclk)) {
dev_err(dev, "can't get usb2_refclk\n");
return PTR_ERR(am62->usb2_refclk);
}
/* Calculate the rate code */
rate = clk_get_rate(am62->usb2_refclk);
rate /= 1000; // To KHz
for (i = 0; i < ARRAY_SIZE(dwc3_ti_rate_table); i++) {
if (dwc3_ti_rate_table[i] == rate)
break;
}
if (i == ARRAY_SIZE(dwc3_ti_rate_table)) {
dev_err(dev, "unsupported usb2_refclk rate: %lu KHz\n", rate);
return -EINVAL;
}
am62->rate_code = i;
am62->phy_regs = devm_platform_ioremap_resource(pdev, 1);
if (IS_ERR(am62->phy_regs)) {
dev_err(dev, "can't map PHY IOMEM resource. Won't apply i2409 fix.\n");
am62->phy_regs = NULL;
}
am62->vbus_divider = device_property_read_bool(dev, "ti,vbus-divider");
ret = dwc3_ti_init(am62);
if (ret)
return ret;
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
/*
* Don't ignore its dependencies with its children
*/
pm_suspend_ignore_children(dev, false);
pm_runtime_get_noresume(dev);
ret = of_platform_populate(node, NULL, NULL, dev);
if (ret) {
dev_err(dev, "failed to create dwc3 core: %d\n", ret);
goto err_pm_disable;
}
/* Device has capability to wakeup system from sleep */
device_set_wakeup_capable(dev, true);
ret = device_wakeup_enable(dev);
if (ret)
dev_err(dev, "couldn't enable device as a wakeup source: %d\n", ret);
/* Setting up autosuspend */
pm_runtime_set_autosuspend_delay(dev, DWC3_AM62_AUTOSUSPEND_DELAY);
pm_runtime_use_autosuspend(dev);
pm_runtime_put_autosuspend(dev);
return 0;
err_pm_disable:
clk_disable_unprepare(am62->usb2_refclk);
pm_runtime_disable(dev);
pm_runtime_set_suspended(dev);
return ret;
}
static void dwc3_ti_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct dwc3_am62 *am62 = platform_get_drvdata(pdev);
u32 reg;
pm_runtime_get_sync(dev);
device_init_wakeup(dev, false);
of_platform_depopulate(dev);
/* Clear mode valid bit */
reg = dwc3_ti_readl(am62, USBSS_MODE_CONTROL);
reg &= ~USBSS_MODE_VALID;
dwc3_ti_writel(am62, USBSS_MODE_CONTROL, reg);
pm_runtime_put_sync(dev);
pm_runtime_disable(dev);
pm_runtime_dont_use_autosuspend(dev);
pm_runtime_set_suspended(dev);
}
#ifdef CONFIG_PM
static int dwc3_ti_suspend_common(struct device *dev)
{
struct dwc3_am62 *am62 = dev_get_drvdata(dev);
u32 reg, current_prtcap_dir;
if (device_may_wakeup(dev)) {
reg = dwc3_ti_readl(am62, USBSS_CORE_STAT);
current_prtcap_dir = (reg & USBSS_CORE_OPERATIONAL_MODE_MASK)
>> USBSS_CORE_OPERATIONAL_MODE_SHIFT;
/* Set wakeup config enable bits */
reg = dwc3_ti_readl(am62, USBSS_WAKEUP_CONFIG);
if (current_prtcap_dir == DWC3_GCTL_PRTCAP_HOST) {
reg = USBSS_WAKEUP_CFG_LINESTATE_EN | USBSS_WAKEUP_CFG_OVERCURRENT_EN;
} else {
reg = USBSS_WAKEUP_CFG_VBUSVALID_EN | USBSS_WAKEUP_CFG_SESSVALID_EN;
/*
* Enable LINESTATE wake up only if connected to bus
* and in U2/L3 state else it causes spurious wake-up.
*/
}
dwc3_ti_writel(am62, USBSS_WAKEUP_CONFIG, reg);
/* clear wakeup status so we know what caused the wake up */
dwc3_ti_writel(am62, USBSS_WAKEUP_STAT, USBSS_WAKEUP_STAT_CLR);
}
/* just to track if module resets on suspend */
dwc3_ti_writel(am62, USBSS_DEBUG_CFG, USBSS_DEBUG_CFG_DISABLED);
clk_disable_unprepare(am62->usb2_refclk);
return 0;
}
static int dwc3_ti_resume_common(struct device *dev)
{
struct dwc3_am62 *am62 = dev_get_drvdata(dev);
u32 reg;
reg = dwc3_ti_readl(am62, USBSS_DEBUG_CFG);
if (reg != USBSS_DEBUG_CFG_DISABLED) {
/* lost power/context */
dwc3_ti_init(am62);
} else {
dwc3_ti_writel(am62, USBSS_DEBUG_CFG, USBSS_DEBUG_CFG_OFF);
clk_prepare_enable(am62->usb2_refclk);
}
if (device_may_wakeup(dev)) {
/* Clear wakeup config enable bits */
dwc3_ti_writel(am62, USBSS_WAKEUP_CONFIG, USBSS_WAKEUP_CFG_NONE);
}
reg = dwc3_ti_readl(am62, USBSS_WAKEUP_STAT);
am62->wakeup_stat = reg;
return 0;
}
static UNIVERSAL_DEV_PM_OPS(dwc3_ti_pm_ops, dwc3_ti_suspend_common,
dwc3_ti_resume_common, NULL);
#define DEV_PM_OPS (&dwc3_ti_pm_ops)
#else
#define DEV_PM_OPS NULL
#endif /* CONFIG_PM */
static const struct of_device_id dwc3_ti_of_match[] = {
{ .compatible = "ti,am62-usb"},
{},
};
MODULE_DEVICE_TABLE(of, dwc3_ti_of_match);
static struct platform_driver dwc3_ti_driver = {
.probe = dwc3_ti_probe,
.remove = dwc3_ti_remove,
.driver = {
.name = "dwc3-am62",
.pm = DEV_PM_OPS,
.of_match_table = dwc3_ti_of_match,
},
};
module_platform_driver(dwc3_ti_driver);
MODULE_ALIAS("platform:dwc3-am62");
MODULE_AUTHOR("Aswath Govindraju <a-govindraju@ti.com>");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("DesignWare USB3 TI Glue Layer");
+523
View File
@@ -0,0 +1,523 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Apple Silicon DWC3 Glue driver
* Copyright (C) The Asahi Linux Contributors
*
* Based on:
* - dwc3-qcom.c Copyright (c) 2018, The Linux Foundation. All rights reserved.
* - dwc3-of-simple.c Copyright (c) 2015 Texas Instruments Incorporated - https://www.ti.com
*/
#include <linux/of.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/reset.h>
#include "glue.h"
/*
* This platform requires a very specific sequence of operations to bring up dwc3 and its USB3 PHY:
*
* 1) The PHY itself has to be brought up; for this we need to know the mode (USB3,
* USB3+DisplayPort, USB4, etc) and the lane orientation. This happens through typec_mux_set.
* 2) DWC3 has to be brought up but we must not touch the gadget area or start xhci yet.
* 3) The PHY bring-up has to be finalized and dwc3's PIPE interface has to be switched to the
* USB3 PHY, this is done inside phy_set_mode.
* 4) We can now initialize xhci or gadget mode.
*
* We can switch 1 and 2 but 3 has to happen after (1 and 2) and 4 has to happen after 3.
*
* And then to bring this all down again:
*
* 1) DWC3 has to exit host or gadget mode and must no longer touch those registers
* 2) The PHY has to switch dwc3's PIPE interface back to the dummy backend
* 3) The PHY itself can be shut down, this happens from typec_mux_set
*
* We also can't transition the PHY from one mode to another while dwc3 is up and running (this is
* slightly wrong, some transitions are possible, others aren't but because we have no documentation
* for this I'd rather play it safe).
*
* After both the PHY and dwc3 are initialized we will only ever see a single "new device connected"
* event. If we just keep them running only the first device plugged in will ever work. XHCI's port
* status register actually does show the correct state but no interrupt ever comes in. In gadget
* mode we don't even get a USBDisconnected event and everything looks like there's still something
* connected on the other end.
* This can be partially explained because the USB2 D+/D- lines are connected through a stateful
* eUSB2 repeater which in turn is controlled by a variant of the TI TPS6598x USB PD chip which
* resets the repeater out-of-band everytime the CC lines are (dis)connected. This then requires a
* PHY reset to make sure the PHY and the eUSB2 repeater state are synchronized again.
*
* And to make this all extra fun: If we get the order of some of this wrong either the port is just
* broken until a phy+dwc3 reset, or it's broken until a full SoC reset (likely because we can't
* reset some parts of the PHY), or some watchdog kicks in after a few seconds and forces a full SoC
* reset (mostly seen this with USB4/Thunderbolt but there's clearly some watchdog that hates
* invalid states).
*
* Hence there's really no good way to keep dwc3 fully up and running after we disconnect a cable
* because then we can't shut down the PHY anymore. And if we kept the PHY running in whatever mode
* it was until the next cable is connected we'd need to tear it all down and bring it back up again
* anyway to detect and use the next device.
*
* Instead, we just shut down everything when a cable is disconnected and transition to
* DWC3_APPLE_NO_CABLE.
* During initial probe we don't have any information about the connected cable and can't bring up
* the PHY properly and thus also can't fully bring up dwc3. Instead, we just keep everything off
* and defer the first dwc3 probe until we get the first cable connected event. Until then we stay
* in DWC3_APPLE_PROBE_PENDING.
* Once a cable is connected we then keep track of the controller mode here by transitioning to
* DWC3_APPLE_HOST or DWC3_APPLE_DEVICE.
*/
enum dwc3_apple_state {
DWC3_APPLE_PROBE_PENDING, /* Before first cable connection, dwc3_core_probe not called */
DWC3_APPLE_NO_CABLE, /* No cable connected, dwc3 suspended after dwc3_core_exit */
DWC3_APPLE_HOST, /* Cable connected, dwc3 in host mode */
DWC3_APPLE_DEVICE, /* Cable connected, dwc3 in device mode */
};
/**
* struct dwc3_apple - Apple-specific DWC3 USB controller
* @dwc: Core DWC3 structure
* @dev: Pointer to the device structure
* @mmio_resource: Resource to be passed to dwc3_core_probe
* @apple_regs: Apple-specific DWC3 registers
* @reset: Reset control
* @role_sw: USB role switch
* @lock: Mutex for synchronizing access
* @state: Current state of the controller, see documentation for the enum for details
*/
struct dwc3_apple {
struct dwc3 dwc;
struct device *dev;
struct resource *mmio_resource;
void __iomem *apple_regs;
struct reset_control *reset;
struct usb_role_switch *role_sw;
struct mutex lock;
enum dwc3_apple_state state;
};
#define to_dwc3_apple(d) container_of((d), struct dwc3_apple, dwc)
/*
* Apple Silicon dwc3 vendor-specific registers
*
* These registers were identified by tracing XNU's memory access patterns and correlating them with
* debug output over serial to determine their names. We don't exactly know what these do but
* without these USB3 devices sometimes don't work.
*/
#define APPLE_DWC3_REGS_START 0xcd00
#define APPLE_DWC3_REGS_END 0xcdff
#define APPLE_DWC3_CIO_LFPS_OFFSET 0xcd38
#define APPLE_DWC3_CIO_LFPS_OFFSET_VALUE 0xf800f80
#define APPLE_DWC3_CIO_BW_NGT_OFFSET 0xcd3c
#define APPLE_DWC3_CIO_BW_NGT_OFFSET_VALUE 0xfc00fc0
#define APPLE_DWC3_CIO_LINK_TIMER 0xcd40
#define APPLE_DWC3_CIO_PENDING_HP_TIMER GENMASK(23, 16)
#define APPLE_DWC3_CIO_PENDING_HP_TIMER_VALUE 0x14
#define APPLE_DWC3_CIO_PM_LC_TIMER GENMASK(15, 8)
#define APPLE_DWC3_CIO_PM_LC_TIMER_VALUE 0xa
#define APPLE_DWC3_CIO_PM_ENTRY_TIMER GENMASK(7, 0)
#define APPLE_DWC3_CIO_PM_ENTRY_TIMER_VALUE 0x10
static inline void dwc3_apple_writel(struct dwc3_apple *appledwc, u32 offset, u32 value)
{
writel(value, appledwc->apple_regs + offset - APPLE_DWC3_REGS_START);
}
static inline u32 dwc3_apple_readl(struct dwc3_apple *appledwc, u32 offset)
{
return readl(appledwc->apple_regs + offset - APPLE_DWC3_REGS_START);
}
static inline void dwc3_apple_mask(struct dwc3_apple *appledwc, u32 offset, u32 mask, u32 value)
{
u32 reg;
reg = dwc3_apple_readl(appledwc, offset);
reg &= ~mask;
reg |= value;
dwc3_apple_writel(appledwc, offset, reg);
}
static void dwc3_apple_setup_cio(struct dwc3_apple *appledwc)
{
dwc3_apple_writel(appledwc, APPLE_DWC3_CIO_LFPS_OFFSET, APPLE_DWC3_CIO_LFPS_OFFSET_VALUE);
dwc3_apple_writel(appledwc, APPLE_DWC3_CIO_BW_NGT_OFFSET,
APPLE_DWC3_CIO_BW_NGT_OFFSET_VALUE);
dwc3_apple_mask(appledwc, APPLE_DWC3_CIO_LINK_TIMER, APPLE_DWC3_CIO_PENDING_HP_TIMER,
FIELD_PREP(APPLE_DWC3_CIO_PENDING_HP_TIMER,
APPLE_DWC3_CIO_PENDING_HP_TIMER_VALUE));
dwc3_apple_mask(appledwc, APPLE_DWC3_CIO_LINK_TIMER, APPLE_DWC3_CIO_PM_LC_TIMER,
FIELD_PREP(APPLE_DWC3_CIO_PM_LC_TIMER, APPLE_DWC3_CIO_PM_LC_TIMER_VALUE));
dwc3_apple_mask(appledwc, APPLE_DWC3_CIO_LINK_TIMER, APPLE_DWC3_CIO_PM_ENTRY_TIMER,
FIELD_PREP(APPLE_DWC3_CIO_PM_ENTRY_TIMER,
APPLE_DWC3_CIO_PM_ENTRY_TIMER_VALUE));
}
static void dwc3_apple_set_ptrcap(struct dwc3_apple *appledwc, u32 mode)
{
guard(spinlock_irqsave)(&appledwc->dwc.lock);
dwc3_set_prtcap(&appledwc->dwc, mode, false);
}
static int dwc3_apple_core_probe(struct dwc3_apple *appledwc)
{
struct dwc3_probe_data probe_data = {};
int ret;
lockdep_assert_held(&appledwc->lock);
WARN_ON_ONCE(appledwc->state != DWC3_APPLE_PROBE_PENDING);
appledwc->dwc.dev = appledwc->dev;
probe_data.dwc = &appledwc->dwc;
probe_data.res = appledwc->mmio_resource;
probe_data.ignore_clocks_and_resets = true;
probe_data.skip_core_init_mode = true;
probe_data.properties = DWC3_DEFAULT_PROPERTIES;
ret = dwc3_core_probe(&probe_data);
if (ret)
return ret;
appledwc->state = DWC3_APPLE_NO_CABLE;
return 0;
}
static int dwc3_apple_core_init(struct dwc3_apple *appledwc)
{
int ret;
lockdep_assert_held(&appledwc->lock);
switch (appledwc->state) {
case DWC3_APPLE_PROBE_PENDING:
ret = dwc3_apple_core_probe(appledwc);
if (ret)
dev_err(appledwc->dev, "Failed to probe DWC3 Core, err=%d\n", ret);
break;
case DWC3_APPLE_NO_CABLE:
ret = dwc3_core_init(&appledwc->dwc);
if (ret)
dev_err(appledwc->dev, "Failed to initialize DWC3 Core, err=%d\n", ret);
break;
default:
/* Unreachable unless there's a bug in this driver */
WARN_ON_ONCE(1);
ret = -EINVAL;
break;
}
return ret;
}
static int dwc3_apple_init(struct dwc3_apple *appledwc, enum dwc3_apple_state state)
{
int ret, ret_reset;
lockdep_assert_held(&appledwc->lock);
/*
* The USB2 PHY on this platform must be configured for host or device mode while it is
* still powered off and before dwc3 tries to access it. Otherwise, the new configuration
* will sometimes only take affect after the *next* time dwc3 is brought up which causes
* the connected device to just not work.
* The USB3 PHY must be configured later after dwc3 has already been initialized.
*/
switch (state) {
case DWC3_APPLE_HOST:
phy_set_mode(appledwc->dwc.usb2_generic_phy[0], PHY_MODE_USB_HOST);
break;
case DWC3_APPLE_DEVICE:
phy_set_mode(appledwc->dwc.usb2_generic_phy[0], PHY_MODE_USB_DEVICE);
break;
default:
/* Unreachable unless there's a bug in this driver */
return -EINVAL;
}
ret = reset_control_deassert(appledwc->reset);
if (ret) {
dev_err(appledwc->dev, "Failed to deassert reset, err=%d\n", ret);
return ret;
}
ret = dwc3_apple_core_init(appledwc);
if (ret)
goto reset_assert;
/*
* Now that the core is initialized and already went through dwc3_core_soft_reset we can
* configure some unknown Apple-specific settings and then bring up xhci or gadget mode.
*/
dwc3_apple_setup_cio(appledwc);
switch (state) {
case DWC3_APPLE_HOST:
appledwc->dwc.dr_mode = USB_DR_MODE_HOST;
dwc3_apple_set_ptrcap(appledwc, DWC3_GCTL_PRTCAP_HOST);
/*
* This platform requires SUSPHY to be enabled here already in order to properly
* configure the PHY and switch dwc3's PIPE interface to USB3 PHY. The USB2 PHY
* has already been configured to the correct mode earlier.
*/
dwc3_enable_susphy(&appledwc->dwc, true);
phy_set_mode(appledwc->dwc.usb3_generic_phy[0], PHY_MODE_USB_HOST);
ret = dwc3_host_init(&appledwc->dwc);
if (ret) {
dev_err(appledwc->dev, "Failed to initialize host, ret=%d\n", ret);
goto core_exit;
}
break;
case DWC3_APPLE_DEVICE:
appledwc->dwc.dr_mode = USB_DR_MODE_PERIPHERAL;
dwc3_apple_set_ptrcap(appledwc, DWC3_GCTL_PRTCAP_DEVICE);
/*
* This platform requires SUSPHY to be enabled here already in order to properly
* configure the PHY and switch dwc3's PIPE interface to USB3 PHY. The USB2 PHY
* has already been configured to the correct mode earlier.
*/
dwc3_enable_susphy(&appledwc->dwc, true);
phy_set_mode(appledwc->dwc.usb3_generic_phy[0], PHY_MODE_USB_DEVICE);
ret = dwc3_gadget_init(&appledwc->dwc);
if (ret) {
dev_err(appledwc->dev, "Failed to initialize gadget, ret=%d\n", ret);
goto core_exit;
}
break;
default:
/* Unreachable unless there's a bug in this driver */
WARN_ON_ONCE(1);
ret = -EINVAL;
goto core_exit;
}
appledwc->state = state;
return 0;
core_exit:
dwc3_core_exit(&appledwc->dwc);
reset_assert:
ret_reset = reset_control_assert(appledwc->reset);
if (ret_reset)
dev_warn(appledwc->dev, "Failed to assert reset, err=%d\n", ret_reset);
return ret;
}
static int dwc3_apple_exit(struct dwc3_apple *appledwc)
{
int ret = 0;
lockdep_assert_held(&appledwc->lock);
switch (appledwc->state) {
case DWC3_APPLE_PROBE_PENDING:
case DWC3_APPLE_NO_CABLE:
/* Nothing to do if we're already off */
return 0;
case DWC3_APPLE_DEVICE:
dwc3_gadget_exit(&appledwc->dwc);
break;
case DWC3_APPLE_HOST:
dwc3_host_exit(&appledwc->dwc);
break;
}
/*
* This platform requires SUSPHY to be enabled in order to properly power down the PHY
* and switch dwc3's PIPE interface back to a dummy PHY (i.e. no USB3 support and USB2 via
* a different PHY connected through ULPI).
*/
dwc3_enable_susphy(&appledwc->dwc, true);
dwc3_core_exit(&appledwc->dwc);
appledwc->state = DWC3_APPLE_NO_CABLE;
ret = reset_control_assert(appledwc->reset);
if (ret) {
dev_err(appledwc->dev, "Failed to assert reset, err=%d\n", ret);
return ret;
}
return 0;
}
static int dwc3_usb_role_switch_set(struct usb_role_switch *sw, enum usb_role role)
{
struct dwc3_apple *appledwc = usb_role_switch_get_drvdata(sw);
int ret;
guard(mutex)(&appledwc->lock);
/*
* Skip role switches if appledwc is already in the desired state. The
* USB-C port controller on M2 and M1/M2 Pro/Max/Ultra devices issues
* additional interrupts which results in usb_role_switch_set_role()
* calls with the current role.
* Ignore those calls here to ensure the USB-C port controller and
* appledwc are in a consistent state.
* This matches the behaviour in __dwc3_set_mode().
* Do no handle USB_ROLE_NONE for DWC3_APPLE_NO_CABLE and
* DWC3_APPLE_PROBE_PENDING since that is no-op anyway.
*/
if (appledwc->state == DWC3_APPLE_HOST && role == USB_ROLE_HOST)
return 0;
if (appledwc->state == DWC3_APPLE_DEVICE && role == USB_ROLE_DEVICE)
return 0;
/*
* We need to tear all of dwc3 down and re-initialize it every time a cable is
* connected or disconnected or when the mode changes. See the documentation for enum
* dwc3_apple_state for details.
*/
ret = dwc3_apple_exit(appledwc);
if (ret)
return ret;
switch (role) {
case USB_ROLE_NONE:
/* Nothing to do if no cable is connected */
return 0;
case USB_ROLE_HOST:
return dwc3_apple_init(appledwc, DWC3_APPLE_HOST);
case USB_ROLE_DEVICE:
return dwc3_apple_init(appledwc, DWC3_APPLE_DEVICE);
default:
dev_err(appledwc->dev, "Invalid target role: %d\n", role);
return -EINVAL;
}
}
static enum usb_role dwc3_usb_role_switch_get(struct usb_role_switch *sw)
{
struct dwc3_apple *appledwc = usb_role_switch_get_drvdata(sw);
guard(mutex)(&appledwc->lock);
switch (appledwc->state) {
case DWC3_APPLE_HOST:
return USB_ROLE_HOST;
case DWC3_APPLE_DEVICE:
return USB_ROLE_DEVICE;
case DWC3_APPLE_NO_CABLE:
case DWC3_APPLE_PROBE_PENDING:
return USB_ROLE_NONE;
default:
/* Unreachable unless there's a bug in this driver */
dev_err(appledwc->dev, "Invalid internal state: %d\n", appledwc->state);
return USB_ROLE_NONE;
}
}
static int dwc3_apple_setup_role_switch(struct dwc3_apple *appledwc)
{
struct usb_role_switch_desc dwc3_role_switch = { NULL };
dwc3_role_switch.fwnode = dev_fwnode(appledwc->dev);
dwc3_role_switch.set = dwc3_usb_role_switch_set;
dwc3_role_switch.get = dwc3_usb_role_switch_get;
dwc3_role_switch.driver_data = appledwc;
appledwc->role_sw = usb_role_switch_register(appledwc->dev, &dwc3_role_switch);
if (IS_ERR(appledwc->role_sw))
return PTR_ERR(appledwc->role_sw);
return 0;
}
static int dwc3_apple_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct dwc3_apple *appledwc;
int ret;
appledwc = devm_kzalloc(&pdev->dev, sizeof(*appledwc), GFP_KERNEL);
if (!appledwc)
return -ENOMEM;
appledwc->dev = &pdev->dev;
mutex_init(&appledwc->lock);
appledwc->reset = devm_reset_control_get_exclusive(dev, NULL);
if (IS_ERR(appledwc->reset))
return dev_err_probe(&pdev->dev, PTR_ERR(appledwc->reset),
"Failed to get reset control\n");
ret = reset_control_assert(appledwc->reset);
if (ret) {
dev_err(&pdev->dev, "Failed to assert reset, err=%d\n", ret);
return ret;
}
appledwc->mmio_resource = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dwc3-core");
if (!appledwc->mmio_resource) {
dev_err(dev, "Failed to get DWC3 MMIO\n");
return -EINVAL;
}
appledwc->apple_regs = devm_platform_ioremap_resource_byname(pdev, "dwc3-apple");
if (IS_ERR(appledwc->apple_regs))
return dev_err_probe(dev, PTR_ERR(appledwc->apple_regs),
"Failed to map Apple-specific MMIO\n");
/*
* On this platform, DWC3 can only be brought up after parts of the PHY have been
* initialized with knowledge of the target mode and cable orientation from typec_set_mux.
* Since this has not happened here we cannot setup DWC3 yet and instead defer this until
* the first cable is connected. See the documentation for enum dwc3_apple_state for
* details.
*/
appledwc->state = DWC3_APPLE_PROBE_PENDING;
ret = dwc3_apple_setup_role_switch(appledwc);
if (ret)
return dev_err_probe(&pdev->dev, ret, "Failed to setup role switch\n");
return 0;
}
static void dwc3_apple_remove(struct platform_device *pdev)
{
struct dwc3 *dwc = platform_get_drvdata(pdev);
struct dwc3_apple *appledwc = to_dwc3_apple(dwc);
guard(mutex)(&appledwc->lock);
usb_role_switch_unregister(appledwc->role_sw);
/*
* If we're still in DWC3_APPLE_PROBE_PENDING we never got any cable connected event and
* dwc3_core_probe was never called and there's hence no need to call dwc3_core_remove.
* dwc3_apple_exit can be called unconditionally because it checks the state itself.
*/
dwc3_apple_exit(appledwc);
if (appledwc->state != DWC3_APPLE_PROBE_PENDING)
dwc3_core_remove(&appledwc->dwc);
}
static const struct of_device_id dwc3_apple_of_match[] = {
{ .compatible = "apple,t8103-dwc3" },
{}
};
MODULE_DEVICE_TABLE(of, dwc3_apple_of_match);
static struct platform_driver dwc3_apple_driver = {
.probe = dwc3_apple_probe,
.remove = dwc3_apple_remove,
.driver = {
.name = "dwc3-apple",
.of_match_table = dwc3_apple_of_match,
},
};
module_platform_driver(dwc3_apple_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Sven Peter <sven@kernel.org>");
MODULE_DESCRIPTION("DesignWare DWC3 Apple Silicon Glue Driver");
+285
View File
@@ -0,0 +1,285 @@
// SPDX-License-Identifier: GPL-2.0
/*
* dwc3-exynos.c - Samsung Exynos DWC3 Specific Glue layer
*
* Copyright (c) 2012 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* Author: Anton Tikhomirov <av.tikhomirov@samsung.com>
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/regulator/consumer.h>
#define DWC3_EXYNOS_MAX_CLOCKS 4
struct dwc3_exynos_driverdata {
const char *clk_names[DWC3_EXYNOS_MAX_CLOCKS];
int num_clks;
int suspend_clk_idx;
};
struct dwc3_exynos {
struct device *dev;
const char **clk_names;
struct clk *clks[DWC3_EXYNOS_MAX_CLOCKS];
int num_clks;
int suspend_clk_idx;
struct regulator *vdd33;
struct regulator *vdd10;
};
static int dwc3_exynos_probe(struct platform_device *pdev)
{
struct dwc3_exynos *exynos;
struct device *dev = &pdev->dev;
struct device_node *node = dev->of_node;
const struct dwc3_exynos_driverdata *driver_data;
int i, ret;
exynos = devm_kzalloc(dev, sizeof(*exynos), GFP_KERNEL);
if (!exynos)
return -ENOMEM;
driver_data = of_device_get_match_data(dev);
exynos->dev = dev;
exynos->num_clks = driver_data->num_clks;
exynos->clk_names = (const char **)driver_data->clk_names;
exynos->suspend_clk_idx = driver_data->suspend_clk_idx;
platform_set_drvdata(pdev, exynos);
for (i = 0; i < exynos->num_clks; i++) {
exynos->clks[i] = devm_clk_get(dev, exynos->clk_names[i]);
if (IS_ERR(exynos->clks[i])) {
dev_err(dev, "failed to get clock: %s\n",
exynos->clk_names[i]);
return PTR_ERR(exynos->clks[i]);
}
}
for (i = 0; i < exynos->num_clks; i++) {
ret = clk_prepare_enable(exynos->clks[i]);
if (ret) {
while (i-- > 0)
clk_disable_unprepare(exynos->clks[i]);
return ret;
}
}
if (exynos->suspend_clk_idx >= 0)
clk_prepare_enable(exynos->clks[exynos->suspend_clk_idx]);
exynos->vdd33 = devm_regulator_get(dev, "vdd33");
if (IS_ERR(exynos->vdd33)) {
ret = PTR_ERR(exynos->vdd33);
goto vdd33_err;
}
ret = regulator_enable(exynos->vdd33);
if (ret) {
dev_err(dev, "Failed to enable VDD33 supply\n");
goto vdd33_err;
}
exynos->vdd10 = devm_regulator_get(dev, "vdd10");
if (IS_ERR(exynos->vdd10)) {
ret = PTR_ERR(exynos->vdd10);
goto vdd10_err;
}
ret = regulator_enable(exynos->vdd10);
if (ret) {
dev_err(dev, "Failed to enable VDD10 supply\n");
goto vdd10_err;
}
if (node) {
ret = of_platform_populate(node, NULL, NULL, dev);
if (ret) {
dev_err(dev, "failed to add dwc3 core\n");
goto populate_err;
}
} else {
dev_err(dev, "no device node, failed to add dwc3 core\n");
ret = -ENODEV;
goto populate_err;
}
return 0;
populate_err:
regulator_disable(exynos->vdd10);
vdd10_err:
regulator_disable(exynos->vdd33);
vdd33_err:
for (i = exynos->num_clks - 1; i >= 0; i--)
clk_disable_unprepare(exynos->clks[i]);
if (exynos->suspend_clk_idx >= 0)
clk_disable_unprepare(exynos->clks[exynos->suspend_clk_idx]);
return ret;
}
static void dwc3_exynos_remove(struct platform_device *pdev)
{
struct dwc3_exynos *exynos = platform_get_drvdata(pdev);
int i;
of_platform_depopulate(&pdev->dev);
for (i = exynos->num_clks - 1; i >= 0; i--)
clk_disable_unprepare(exynos->clks[i]);
if (exynos->suspend_clk_idx >= 0)
clk_disable_unprepare(exynos->clks[exynos->suspend_clk_idx]);
regulator_disable(exynos->vdd33);
regulator_disable(exynos->vdd10);
}
static const struct dwc3_exynos_driverdata exynos2200_drvdata = {
.clk_names = { "link_aclk" },
.num_clks = 1,
.suspend_clk_idx = -1,
};
static const struct dwc3_exynos_driverdata exynos5250_drvdata = {
.clk_names = { "usbdrd30" },
.num_clks = 1,
.suspend_clk_idx = -1,
};
static const struct dwc3_exynos_driverdata exynos5433_drvdata = {
.clk_names = { "aclk", "susp_clk", "pipe_pclk", "phyclk" },
.num_clks = 4,
.suspend_clk_idx = 1,
};
static const struct dwc3_exynos_driverdata exynos7_drvdata = {
.clk_names = { "usbdrd30", "usbdrd30_susp_clk", "usbdrd30_axius_clk" },
.num_clks = 3,
.suspend_clk_idx = 1,
};
static const struct dwc3_exynos_driverdata exynos7870_drvdata = {
.clk_names = { "bus_early", "ref", "ctrl" },
.num_clks = 3,
.suspend_clk_idx = -1,
};
static const struct dwc3_exynos_driverdata exynos850_drvdata = {
.clk_names = { "bus_early", "ref" },
.num_clks = 2,
.suspend_clk_idx = -1,
};
static const struct dwc3_exynos_driverdata gs101_drvdata = {
.clk_names = { "bus_early", "susp_clk", "link_aclk", "link_pclk" },
.num_clks = 4,
.suspend_clk_idx = 1,
};
static const struct dwc3_exynos_driverdata exynosautov920_drvdata = {
.clk_names = { "ref", "susp_clk"},
.num_clks = 2,
.suspend_clk_idx = 1,
};
static const struct of_device_id exynos_dwc3_match[] = {
{
.compatible = "samsung,exynos2200-dwusb3",
.data = &exynos2200_drvdata,
}, {
.compatible = "samsung,exynos5250-dwusb3",
.data = &exynos5250_drvdata,
}, {
.compatible = "samsung,exynos5433-dwusb3",
.data = &exynos5433_drvdata,
}, {
.compatible = "samsung,exynos7-dwusb3",
.data = &exynos7_drvdata,
}, {
.compatible = "samsung,exynos7870-dwusb3",
.data = &exynos7870_drvdata,
}, {
.compatible = "samsung,exynos850-dwusb3",
.data = &exynos850_drvdata,
}, {
.compatible = "samsung,exynosautov920-dwusb3",
.data = &exynosautov920_drvdata,
}, {
.compatible = "google,gs101-dwusb3",
.data = &gs101_drvdata,
}, {
}
};
MODULE_DEVICE_TABLE(of, exynos_dwc3_match);
static int dwc3_exynos_suspend(struct device *dev)
{
struct dwc3_exynos *exynos = dev_get_drvdata(dev);
int i;
for (i = exynos->num_clks - 1; i >= 0; i--)
clk_disable_unprepare(exynos->clks[i]);
regulator_disable(exynos->vdd33);
regulator_disable(exynos->vdd10);
return 0;
}
static int dwc3_exynos_resume(struct device *dev)
{
struct dwc3_exynos *exynos = dev_get_drvdata(dev);
int i, ret;
ret = regulator_enable(exynos->vdd33);
if (ret) {
dev_err(dev, "Failed to enable VDD33 supply\n");
return ret;
}
ret = regulator_enable(exynos->vdd10);
if (ret) {
dev_err(dev, "Failed to enable VDD10 supply\n");
return ret;
}
for (i = 0; i < exynos->num_clks; i++) {
ret = clk_prepare_enable(exynos->clks[i]);
if (ret) {
while (i-- > 0)
clk_disable_unprepare(exynos->clks[i]);
return ret;
}
}
return 0;
}
static DEFINE_SIMPLE_DEV_PM_OPS(dwc3_exynos_dev_pm_ops,
dwc3_exynos_suspend, dwc3_exynos_resume);
static struct platform_driver dwc3_exynos_driver = {
.probe = dwc3_exynos_probe,
.remove = dwc3_exynos_remove,
.driver = {
.name = "exynos-dwc3",
.of_match_table = exynos_dwc3_match,
.pm = pm_sleep_ptr(&dwc3_exynos_dev_pm_ops),
},
};
module_platform_driver(dwc3_exynos_driver);
MODULE_AUTHOR("Anton Tikhomirov <av.tikhomirov@samsung.com>");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("DesignWare USB3 Exynos Glue Layer");
@@ -0,0 +1,256 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* dwc3-generic-plat.c - DesignWare USB3 generic platform driver
*
* Copyright (C) 2025 Ze Huang <huang.ze@linux.dev>
*
* Inspired by dwc3-qcom.c and dwc3-of-simple.c
*/
#include <linux/clk.h>
#include <linux/platform_device.h>
#include <linux/reset.h>
#include <linux/regmap.h>
#include <linux/mfd/syscon.h>
#include <linux/regulator/consumer.h>
#include <linux/usb/otg.h>
#include "glue.h"
#define EIC7700_HSP_BUS_FILTER_EN BIT(0)
#define EIC7700_HSP_BUS_CLKEN_GM BIT(9)
#define EIC7700_HSP_BUS_CLKEN_GS BIT(16)
#define EIC7700_HSP_AXI_LP_XM_CSYSREQ BIT(0)
#define EIC7700_HSP_AXI_LP_XS_CSYSREQ BIT(16)
struct dwc3_generic {
struct device *dev;
struct dwc3 dwc;
struct clk_bulk_data *clks;
int num_clocks;
struct reset_control *resets;
};
struct dwc3_generic_config {
int (*init)(struct dwc3_generic *dwc3g);
struct dwc3_properties properties;
};
#define to_dwc3_generic(d) container_of((d), struct dwc3_generic, dwc)
static void dwc3_generic_reset_control_assert(void *data)
{
reset_control_assert(data);
}
static int dwc3_eic7700_init(struct dwc3_generic *dwc3g)
{
struct device *dev = dwc3g->dev;
struct regmap *regmap;
u32 hsp_usb_axi_lp;
u32 hsp_usb_bus;
u32 args[2];
u32 val;
regmap = syscon_regmap_lookup_by_phandle_args(dev->of_node,
"eswin,hsp-sp-csr",
ARRAY_SIZE(args), args);
if (IS_ERR(regmap)) {
dev_err(dev, "No hsp-sp-csr phandle specified\n");
return PTR_ERR(regmap);
}
hsp_usb_bus = args[0];
hsp_usb_axi_lp = args[1];
regmap_read(regmap, hsp_usb_bus, &val);
regmap_write(regmap, hsp_usb_bus, val | EIC7700_HSP_BUS_FILTER_EN |
EIC7700_HSP_BUS_CLKEN_GM | EIC7700_HSP_BUS_CLKEN_GS);
regmap_write(regmap, hsp_usb_axi_lp, EIC7700_HSP_AXI_LP_XM_CSYSREQ |
EIC7700_HSP_AXI_LP_XS_CSYSREQ);
return 0;
}
static int dwc3_spacemit_k1_init(struct dwc3_generic *dwc3g)
{
struct device *dev = dwc3g->dev;
if (usb_get_dr_mode(dev) == USB_DR_MODE_HOST) {
int ret = devm_regulator_get_enable_optional(dev, "vbus");
if (ret && ret != -ENODEV)
return dev_err_probe(dev, ret, "failed to enable VBUS\n");
}
return 0;
}
static int dwc3_generic_probe(struct platform_device *pdev)
{
const struct dwc3_generic_config *plat_config;
struct dwc3_probe_data probe_data = {};
struct device *dev = &pdev->dev;
struct dwc3_generic *dwc3g;
struct resource *res;
int ret;
dwc3g = devm_kzalloc(dev, sizeof(*dwc3g), GFP_KERNEL);
if (!dwc3g)
return -ENOMEM;
dwc3g->dev = dev;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "missing memory resource\n");
return -ENODEV;
}
dwc3g->resets = devm_reset_control_array_get_optional_exclusive(dev);
if (IS_ERR(dwc3g->resets))
return dev_err_probe(dev, PTR_ERR(dwc3g->resets), "failed to get resets\n");
ret = reset_control_assert(dwc3g->resets);
if (ret)
return dev_err_probe(dev, ret, "failed to assert resets\n");
/* Not strict timing, just for safety */
udelay(2);
ret = reset_control_deassert(dwc3g->resets);
if (ret)
return dev_err_probe(dev, ret, "failed to deassert resets\n");
ret = devm_add_action_or_reset(dev, dwc3_generic_reset_control_assert, dwc3g->resets);
if (ret)
return ret;
ret = devm_clk_bulk_get_all_enabled(dwc3g->dev, &dwc3g->clks);
if (ret < 0)
return dev_err_probe(dev, ret, "failed to get clocks\n");
dwc3g->num_clocks = ret;
dwc3g->dwc.dev = dev;
probe_data.dwc = &dwc3g->dwc;
probe_data.res = res;
probe_data.ignore_clocks_and_resets = true;
plat_config = of_device_get_match_data(dev);
if (!plat_config) {
probe_data.properties = DWC3_DEFAULT_PROPERTIES;
goto core_probe;
}
probe_data.properties = plat_config->properties;
if (plat_config->init) {
ret = plat_config->init(dwc3g);
if (ret)
return dev_err_probe(dev, ret,
"failed to init platform\n");
}
core_probe:
ret = dwc3_core_probe(&probe_data);
if (ret)
return dev_err_probe(dev, ret, "failed to register DWC3 Core\n");
return 0;
}
static void dwc3_generic_remove(struct platform_device *pdev)
{
struct dwc3 *dwc = platform_get_drvdata(pdev);
dwc3_core_remove(dwc);
}
static int dwc3_generic_suspend(struct device *dev)
{
struct dwc3 *dwc = dev_get_drvdata(dev);
struct dwc3_generic *dwc3g = to_dwc3_generic(dwc);
int ret;
ret = dwc3_pm_suspend(dwc);
if (ret)
return ret;
clk_bulk_disable_unprepare(dwc3g->num_clocks, dwc3g->clks);
return 0;
}
static int dwc3_generic_resume(struct device *dev)
{
struct dwc3 *dwc = dev_get_drvdata(dev);
struct dwc3_generic *dwc3g = to_dwc3_generic(dwc);
int ret;
ret = clk_bulk_prepare_enable(dwc3g->num_clocks, dwc3g->clks);
if (ret)
return ret;
ret = dwc3_pm_resume(dwc);
if (ret)
return ret;
return 0;
}
static int dwc3_generic_runtime_suspend(struct device *dev)
{
return dwc3_runtime_suspend(dev_get_drvdata(dev));
}
static int dwc3_generic_runtime_resume(struct device *dev)
{
return dwc3_runtime_resume(dev_get_drvdata(dev));
}
static int dwc3_generic_runtime_idle(struct device *dev)
{
return dwc3_runtime_idle(dev_get_drvdata(dev));
}
static const struct dev_pm_ops dwc3_generic_dev_pm_ops = {
SYSTEM_SLEEP_PM_OPS(dwc3_generic_suspend, dwc3_generic_resume)
RUNTIME_PM_OPS(dwc3_generic_runtime_suspend, dwc3_generic_runtime_resume,
dwc3_generic_runtime_idle)
};
static const struct dwc3_generic_config spacemit_k1_dwc3 = {
.init = dwc3_spacemit_k1_init,
.properties = DWC3_DEFAULT_PROPERTIES,
};
static const struct dwc3_generic_config fsl_ls1028_dwc3 = {
.properties.gsbuscfg0_reqinfo = 0x2222,
};
static const struct dwc3_generic_config eic7700_dwc3 = {
.init = dwc3_eic7700_init,
.properties = DWC3_DEFAULT_PROPERTIES,
};
static const struct of_device_id dwc3_generic_of_match[] = {
{ .compatible = "spacemit,k1-dwc3", &spacemit_k1_dwc3},
{ .compatible = "spacemit,k3-dwc3", },
{ .compatible = "fsl,ls1028a-dwc3", &fsl_ls1028_dwc3},
{ .compatible = "eswin,eic7700-dwc3", &eic7700_dwc3},
{ .compatible = "starfive,jhb100-dwc3", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, dwc3_generic_of_match);
static struct platform_driver dwc3_generic_driver = {
.probe = dwc3_generic_probe,
.remove = dwc3_generic_remove,
.driver = {
.name = "dwc3-generic-plat",
.of_match_table = dwc3_generic_of_match,
.pm = pm_ptr(&dwc3_generic_dev_pm_ops),
},
};
module_platform_driver(dwc3_generic_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("DesignWare USB3 generic platform driver");
+626
View File
@@ -0,0 +1,626 @@
// SPDX-License-Identifier: GPL-2.0
/*
* dwc3-google.c - Google DWC3 Specific Glue Layer
*
* Copyright (c) 2025, Google LLC
* Author: Roy Luo <royluo@google.com>
*/
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/iopoll.h>
#include <linux/irq.h>
#include <linux/kernel.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_domain.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include "core.h"
#include "glue.h"
/* HOST CFG registers */
#define HC_STATUS_OFFSET 0x0
#define HC_STATUS_CURRENT_POWER_STATE_U2PMU GENMASK(1, 0)
#define HC_STATUS_CURRENT_POWER_STATE_U3PMU GENMASK(4, 3)
#define HOST_CFG1_OFFSET 0x4
#define HOST_CFG1_PME_EN BIT(3)
#define HOST_CFG1_PM_POWER_STATE_REQUEST GENMASK(5, 4)
#define HOST_CFG1_PM_POWER_STATE_D0 0x0
#define HOST_CFG1_PM_POWER_STATE_D3 0x3
/* USBINT registers */
#define USBINT_CFG1_OFFSET 0x0
#define USBINT_CFG1_USBDRD_PME_GEN_U2P_INTR_MSK BIT(2)
#define USBINT_CFG1_USBDRD_PME_GEN_U3P_INTR_MSK BIT(3)
#define USBINT_CFG1_USBDRD_PME_GEN_U2P_INTR_INT_EN BIT(8)
#define USBINT_CFG1_USBDRD_PME_GEN_U3P_INTR_INT_EN BIT(9)
#define USBINT_CFG1_USBDRD_PME_GEN_U2_INTR_CLR BIT(14)
#define USBINT_CFG1_USBDRD_PME_GEN_U3_INTR_CLR BIT(15)
#define USBINT_STATUS_OFFSET 0x4
#define USBINT_STATUS_USBDRD_PME_GEN_U2P_INTR_STS_RAW BIT(2)
#define USBINT_STATUS_USBDRD_PME_GEN_U3P_INTR_STS_RAW BIT(3)
#define USBCS_TOP_CTRL_CFG1_OFFSET 0xc
#define USBCS_TOP_CTRL_CFG1_USB2ONLY_MODE BIT(5)
#define DWC3_GOOGLE_MAX_RESETS 4
struct dwc3_google {
struct device *dev;
struct dwc3 dwc;
struct clk_bulk_data *clks;
int num_clks;
struct reset_control_bulk_data rsts[DWC3_GOOGLE_MAX_RESETS];
int num_rsts;
struct reset_control *non_sticky_rst;
struct device *usb_psw_pd;
struct device_link *usb_psw_pd_dl;
struct notifier_block usb_psw_pd_nb;
struct device *usb_top_pd;
struct device_link *usb_top_pd_dl;
struct regmap *usb_cfg_regmap;
unsigned int host_cfg_offset;
unsigned int usbint_cfg_offset;
int hs_pme_irq;
int ss_pme_irq;
bool is_usb2only;
bool is_hibernation;
};
#define to_dwc3_google(d) container_of_const((d), struct dwc3_google, dwc)
static int dwc3_google_rst_init(struct dwc3_google *google)
{
int ret;
google->num_rsts = 4;
google->rsts[0].id = "non_sticky";
google->rsts[1].id = "sticky";
google->rsts[2].id = "drd_bus";
google->rsts[3].id = "top";
ret = devm_reset_control_bulk_get_exclusive(google->dev,
google->num_rsts,
google->rsts);
if (ret < 0)
return ret;
google->non_sticky_rst = google->rsts[0].rstc;
return 0;
}
static int dwc3_google_set_pmu_state(struct dwc3_google *google, int state)
{
u32 reg;
int ret;
regmap_read(google->usb_cfg_regmap,
google->host_cfg_offset + HOST_CFG1_OFFSET, &reg);
FIELD_MODIFY(HOST_CFG1_PM_POWER_STATE_REQUEST, &reg, state);
reg |= HOST_CFG1_PME_EN;
regmap_write(google->usb_cfg_regmap,
google->host_cfg_offset + HOST_CFG1_OFFSET, reg);
ret = regmap_read_poll_timeout(google->usb_cfg_regmap,
google->host_cfg_offset + HC_STATUS_OFFSET, reg,
(FIELD_GET(HC_STATUS_CURRENT_POWER_STATE_U2PMU,
reg) == state &&
FIELD_GET(HC_STATUS_CURRENT_POWER_STATE_U3PMU,
reg) == state),
10, 10000);
if (ret)
dev_err(google->dev, "failed to set PMU state %d\n", state);
return ret;
}
/*
* Clear pme interrupts and report their status.
* The hardware requires write-1 then write-0 sequence to clear the interrupt bits.
*/
static u32 dwc3_google_clear_pme_irqs(struct dwc3_google *google)
{
u32 irq_status, reg_set, reg_clear;
regmap_read(google->usb_cfg_regmap,
google->usbint_cfg_offset + USBINT_STATUS_OFFSET, &irq_status);
irq_status &= (USBINT_STATUS_USBDRD_PME_GEN_U2P_INTR_STS_RAW |
USBINT_STATUS_USBDRD_PME_GEN_U3P_INTR_STS_RAW);
if (!irq_status)
return irq_status;
regmap_read(google->usb_cfg_regmap,
google->usbint_cfg_offset + USBINT_CFG1_OFFSET, &reg_set);
reg_clear = reg_set;
if (irq_status & USBINT_STATUS_USBDRD_PME_GEN_U2P_INTR_STS_RAW) {
reg_set |= USBINT_CFG1_USBDRD_PME_GEN_U2_INTR_CLR;
reg_clear &= ~USBINT_CFG1_USBDRD_PME_GEN_U2_INTR_CLR;
}
if (irq_status & USBINT_STATUS_USBDRD_PME_GEN_U3P_INTR_STS_RAW) {
reg_set |= USBINT_CFG1_USBDRD_PME_GEN_U3_INTR_CLR;
reg_clear &= ~USBINT_CFG1_USBDRD_PME_GEN_U3_INTR_CLR;
}
regmap_write(google->usb_cfg_regmap,
google->usbint_cfg_offset + USBINT_CFG1_OFFSET, reg_set);
regmap_write(google->usb_cfg_regmap,
google->usbint_cfg_offset + USBINT_CFG1_OFFSET, reg_clear);
return irq_status;
}
static void dwc3_google_enable_pme_irq(struct dwc3_google *google)
{
u32 reg;
regmap_read(google->usb_cfg_regmap,
google->usbint_cfg_offset + USBINT_CFG1_OFFSET, &reg);
reg &= ~(USBINT_CFG1_USBDRD_PME_GEN_U2P_INTR_MSK |
USBINT_CFG1_USBDRD_PME_GEN_U3P_INTR_MSK);
reg |= (USBINT_CFG1_USBDRD_PME_GEN_U2P_INTR_INT_EN |
USBINT_CFG1_USBDRD_PME_GEN_U3P_INTR_INT_EN);
regmap_write(google->usb_cfg_regmap,
google->usbint_cfg_offset + USBINT_CFG1_OFFSET, reg);
enable_irq(google->hs_pme_irq);
enable_irq(google->ss_pme_irq);
enable_irq_wake(google->hs_pme_irq);
enable_irq_wake(google->ss_pme_irq);
}
static void dwc3_google_disable_pme_irq(struct dwc3_google *google)
{
u32 reg;
regmap_read(google->usb_cfg_regmap,
google->usbint_cfg_offset + USBINT_CFG1_OFFSET, &reg);
reg &= ~(USBINT_CFG1_USBDRD_PME_GEN_U2P_INTR_INT_EN |
USBINT_CFG1_USBDRD_PME_GEN_U3P_INTR_INT_EN);
reg |= (USBINT_CFG1_USBDRD_PME_GEN_U2P_INTR_MSK |
USBINT_CFG1_USBDRD_PME_GEN_U3P_INTR_MSK);
regmap_write(google->usb_cfg_regmap,
google->usbint_cfg_offset + USBINT_CFG1_OFFSET, reg);
disable_irq_wake(google->hs_pme_irq);
disable_irq_wake(google->ss_pme_irq);
disable_irq_nosync(google->hs_pme_irq);
disable_irq_nosync(google->ss_pme_irq);
}
static irqreturn_t dwc3_google_resume_irq(int irq, void *data)
{
struct dwc3_google *google = data;
struct dwc3 *dwc = &google->dwc;
u32 irq_status, dr_role;
irq_status = dwc3_google_clear_pme_irqs(google);
dr_role = dwc->current_dr_role;
if (!irq_status || !google->is_hibernation ||
dr_role != DWC3_GCTL_PRTCAP_HOST) {
dev_dbg(google->dev, "spurious pme irq %d, hibernation %d, dr_role %u\n",
irq, google->is_hibernation, dr_role);
return IRQ_HANDLED;
}
if (dwc->xhci)
pm_runtime_resume(&dwc->xhci->dev);
return IRQ_HANDLED;
}
static int dwc3_google_request_irq(struct dwc3_google *google, struct platform_device *pdev,
const char *irq_name, const char *req_name)
{
int ret;
int irq;
irq = platform_get_irq_byname(pdev, irq_name);
if (irq < 0)
return irq;
irq_set_status_flags(irq, IRQ_NOAUTOEN);
ret = devm_request_threaded_irq(google->dev, irq, NULL,
dwc3_google_resume_irq,
IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
req_name, google);
if (ret < 0) {
dev_err(google->dev, "failed to request irq %s\n", req_name);
return ret;
}
return irq;
}
static int dwc3_google_usb_psw_pd_notifier(struct notifier_block *nb, unsigned long action, void *d)
{
struct dwc3_google *google = container_of(nb, struct dwc3_google, usb_psw_pd_nb);
int ret;
if (!google->is_hibernation)
return NOTIFY_OK;
if (action == GENPD_NOTIFY_OFF) {
dev_dbg(google->dev, "enter D3 power state\n");
dwc3_google_set_pmu_state(google, HOST_CFG1_PM_POWER_STATE_D3);
ret = reset_control_assert(google->non_sticky_rst);
if (ret)
dev_err(google->dev, "non sticky reset assert failed: %d\n", ret);
} else if (action == GENPD_NOTIFY_ON) {
dev_dbg(google->dev, "enter D0 power state\n");
dwc3_google_clear_pme_irqs(google);
ret = reset_control_deassert(google->non_sticky_rst);
if (ret)
dev_err(google->dev, "non sticky reset deassert failed: %d\n", ret);
dwc3_google_set_pmu_state(google, HOST_CFG1_PM_POWER_STATE_D0);
}
return NOTIFY_OK;
}
static void dwc3_google_pm_domain_deinit(struct dwc3_google *google)
{
if (google->usb_top_pd_dl)
device_link_del(google->usb_top_pd_dl);
if (!IS_ERR_OR_NULL(google->usb_top_pd)) {
device_set_wakeup_capable(google->usb_top_pd, false);
dev_pm_domain_detach(google->usb_top_pd, true);
}
if (google->usb_psw_pd_dl)
device_link_del(google->usb_psw_pd_dl);
if (!IS_ERR_OR_NULL(google->usb_psw_pd)) {
dev_pm_genpd_remove_notifier(google->usb_psw_pd);
dev_pm_domain_detach(google->usb_psw_pd, true);
}
}
static int dwc3_google_pm_domain_init(struct dwc3_google *google)
{
int ret;
/*
* Establish PM RUNTIME link between dwc dev and its power domain usb_psw_pd,
* register notifier block to handle hibernation.
*/
google->usb_psw_pd = dev_pm_domain_attach_by_name(google->dev, "psw");
if (IS_ERR_OR_NULL(google->usb_psw_pd)) {
dev_err(google->dev, "failed to get psw pd");
ret = google->usb_psw_pd ? PTR_ERR(google->usb_psw_pd) : -ENODATA;
return ret;
}
google->usb_psw_pd_nb.notifier_call = dwc3_google_usb_psw_pd_notifier;
ret = dev_pm_genpd_add_notifier(google->usb_psw_pd, &google->usb_psw_pd_nb);
if (ret) {
dev_err(google->dev, "failed to add psw pd notifier");
goto err;
}
google->usb_psw_pd_dl = device_link_add(google->dev, google->usb_psw_pd,
DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME |
DL_FLAG_RPM_ACTIVE);
if (!google->usb_psw_pd_dl) {
dev_err(google->usb_psw_pd, "failed to add device link");
ret = -ENODEV;
goto err;
}
/*
* usb_top_pd is the parent power domain of usb_psw_pd. Keeping usb_top_pd on
* while usb_psw_pd is off places the controller in a power-gated state,
* essential for hibernation. Acquire a handle to usb_top_pd and sets it as
* wakeup-capable to allow the domain to be left on during system suspend.
*/
google->usb_top_pd = dev_pm_domain_attach_by_name(google->dev, "top");
if (IS_ERR_OR_NULL(google->usb_top_pd)) {
dev_err(google->dev, "failed to get top pd");
ret = google->usb_top_pd ? PTR_ERR(google->usb_top_pd) : -ENODATA;
goto err;
}
device_set_wakeup_capable(google->usb_top_pd, true);
google->usb_top_pd_dl = device_link_add(google->dev, google->usb_top_pd,
DL_FLAG_STATELESS);
if (!google->usb_top_pd_dl) {
dev_err(google->usb_top_pd, "failed to add device link");
ret = -ENODEV;
goto err;
}
return 0;
err:
dwc3_google_pm_domain_deinit(google);
return ret;
}
static void dwc3_google_program_usb2only(struct dwc3_google *google)
{
u32 reg;
regmap_read(google->usb_cfg_regmap,
google->usbint_cfg_offset + USBCS_TOP_CTRL_CFG1_OFFSET, &reg);
reg |= USBCS_TOP_CTRL_CFG1_USB2ONLY_MODE;
regmap_write(google->usb_cfg_regmap,
google->usbint_cfg_offset + USBCS_TOP_CTRL_CFG1_OFFSET, reg);
}
static int dwc3_google_probe(struct platform_device *pdev)
{
struct dwc3_probe_data probe_data = {};
struct device *dev = &pdev->dev;
struct dwc3_google *google;
struct resource *res;
int ret;
u32 args[2];
google = devm_kzalloc(&pdev->dev, sizeof(*google), GFP_KERNEL);
if (!google)
return -ENOMEM;
google->dev = &pdev->dev;
ret = dwc3_google_pm_domain_init(google);
if (ret < 0)
return dev_err_probe(dev, ret, "failed to init pdom\n");
google->usb_cfg_regmap =
syscon_regmap_lookup_by_phandle_args(dev->of_node,
"google,usb-cfg-csr",
ARRAY_SIZE(args), args);
if (IS_ERR(google->usb_cfg_regmap)) {
ret = dev_err_probe(dev, PTR_ERR(google->usb_cfg_regmap),
"invalid usb cfg csr\n");
goto err_deinit_pdom;
}
google->host_cfg_offset = args[0];
google->usbint_cfg_offset = args[1];
if (device_property_match_string(dev, "phy-names", "usb3-phy") < 0) {
google->is_usb2only = true;
dwc3_google_program_usb2only(google);
}
ret = devm_clk_bulk_get_all_enabled(dev, &google->clks);
if (ret < 0) {
ret = dev_err_probe(dev, ret, "failed to get and enable clks\n");
goto err_deinit_pdom;
}
google->num_clks = ret;
ret = dwc3_google_rst_init(google);
if (ret) {
ret = dev_err_probe(dev, ret, "failed to get resets\n");
goto err_deinit_pdom;
}
ret = reset_control_bulk_deassert(google->num_rsts, google->rsts);
if (ret) {
ret = dev_err_probe(dev, ret, "failed to deassert rsts\n");
goto err_deinit_pdom;
}
ret = dwc3_google_request_irq(google, pdev, "hs_pme", "USB HS wakeup");
if (ret < 0) {
ret = dev_err_probe(dev, ret, "failed to request hs pme irq");
goto err_reset_assert;
}
google->hs_pme_irq = ret;
ret = dwc3_google_request_irq(google, pdev, "ss_pme", "USB SS wakeup");
if (ret < 0) {
ret = dev_err_probe(dev, ret, "failed to request ss pme irq");
goto err_reset_assert;
}
google->ss_pme_irq = ret;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
ret = dev_err_probe(dev, -ENODEV, "invalid memory\n");
goto err_reset_assert;
}
device_init_wakeup(dev, true);
google->dwc.dev = dev;
probe_data.dwc = &google->dwc;
probe_data.res = res;
probe_data.ignore_clocks_and_resets = true;
ret = dwc3_core_probe(&probe_data);
if (ret) {
ret = dev_err_probe(dev, ret, "failed to register DWC3 Core\n");
goto err_reset_assert;
}
return 0;
err_reset_assert:
reset_control_bulk_assert(google->num_rsts, google->rsts);
err_deinit_pdom:
dwc3_google_pm_domain_deinit(google);
return ret;
}
static void dwc3_google_remove(struct platform_device *pdev)
{
struct dwc3 *dwc = platform_get_drvdata(pdev);
struct dwc3_google *google = to_dwc3_google(dwc);
dwc3_core_remove(&google->dwc);
reset_control_bulk_assert(google->num_rsts, google->rsts);
dwc3_google_pm_domain_deinit(google);
}
static int dwc3_google_suspend(struct dwc3_google *google, pm_message_t msg)
{
if (pm_runtime_suspended(google->dev))
return 0;
if (google->dwc.current_dr_role == DWC3_GCTL_PRTCAP_HOST) {
/*
* Follow dwc3_suspend_common() guidelines for deciding between
* a full teardown and hibernation.
*/
if (PMSG_IS_AUTO(msg) || device_may_wakeup(google->dev)) {
dev_dbg(google->dev, "enter hibernation");
pm_runtime_get_sync(google->usb_top_pd);
device_wakeup_enable(google->usb_top_pd);
dwc3_google_enable_pme_irq(google);
google->is_hibernation = true;
return 0;
}
}
reset_control_bulk_assert(google->num_rsts, google->rsts);
clk_bulk_disable_unprepare(google->num_clks, google->clks);
return 0;
}
static int dwc3_google_resume(struct dwc3_google *google, pm_message_t msg)
{
int ret;
if (google->is_hibernation) {
dev_dbg(google->dev, "exit hibernation");
dwc3_google_disable_pme_irq(google);
device_wakeup_disable(google->usb_top_pd);
pm_runtime_put_sync(google->usb_top_pd);
google->is_hibernation = false;
return 0;
}
if (google->is_usb2only)
dwc3_google_program_usb2only(google);
ret = clk_bulk_prepare_enable(google->num_clks, google->clks);
if (ret)
return ret;
ret = reset_control_bulk_deassert(google->num_rsts, google->rsts);
if (ret) {
clk_bulk_disable_unprepare(google->num_clks, google->clks);
return ret;
}
return 0;
}
static int dwc3_google_pm_suspend(struct device *dev)
{
struct dwc3 *dwc = dev_get_drvdata(dev);
struct dwc3_google *google = to_dwc3_google(dwc);
int ret;
ret = dwc3_pm_suspend(&google->dwc);
if (ret)
return ret;
return dwc3_google_suspend(google, PMSG_SUSPEND);
}
static int dwc3_google_pm_resume(struct device *dev)
{
struct dwc3 *dwc = dev_get_drvdata(dev);
struct dwc3_google *google = to_dwc3_google(dwc);
int ret;
ret = dwc3_google_resume(google, PMSG_RESUME);
if (ret)
return ret;
return dwc3_pm_resume(&google->dwc);
}
static void dwc3_google_complete(struct device *dev)
{
struct dwc3 *dwc = dev_get_drvdata(dev);
dwc3_pm_complete(dwc);
}
static int dwc3_google_prepare(struct device *dev)
{
struct dwc3 *dwc = dev_get_drvdata(dev);
return dwc3_pm_prepare(dwc);
}
static int dwc3_google_runtime_suspend(struct device *dev)
{
struct dwc3 *dwc = dev_get_drvdata(dev);
struct dwc3_google *google = to_dwc3_google(dwc);
int ret;
ret = dwc3_runtime_suspend(&google->dwc);
if (ret)
return ret;
return dwc3_google_suspend(google, PMSG_AUTO_SUSPEND);
}
static int dwc3_google_runtime_resume(struct device *dev)
{
struct dwc3 *dwc = dev_get_drvdata(dev);
struct dwc3_google *google = to_dwc3_google(dwc);
int ret;
ret = dwc3_google_resume(google, PMSG_AUTO_RESUME);
if (ret)
return ret;
return dwc3_runtime_resume(&google->dwc);
}
static int dwc3_google_runtime_idle(struct device *dev)
{
return dwc3_runtime_idle(dev_get_drvdata(dev));
}
static const struct dev_pm_ops dwc3_google_dev_pm_ops = {
SYSTEM_SLEEP_PM_OPS(dwc3_google_pm_suspend, dwc3_google_pm_resume)
RUNTIME_PM_OPS(dwc3_google_runtime_suspend, dwc3_google_runtime_resume,
dwc3_google_runtime_idle)
.complete = pm_sleep_ptr(dwc3_google_complete),
.prepare = pm_sleep_ptr(dwc3_google_prepare),
};
static const struct of_device_id dwc3_google_of_match[] = {
{ .compatible = "google,lga-dwc3" },
{ }
};
MODULE_DEVICE_TABLE(of, dwc3_google_of_match);
static struct platform_driver dwc3_google_driver = {
.probe = dwc3_google_probe,
.remove = dwc3_google_remove,
.driver = {
.name = "dwc3-google",
.pm = pm_ptr(&dwc3_google_dev_pm_ops),
.of_match_table = dwc3_google_of_match,
},
};
module_platform_driver(dwc3_google_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("DesignWare DWC3 Google Glue Driver");
+148
View File
@@ -0,0 +1,148 @@
// SPDX-License-Identifier: GPL-2.0
/*
* dwc3-haps.c - Synopsys HAPS PCI Specific glue layer
*
* Copyright (C) 2018 Synopsys, Inc.
*
* Authors: Thinh Nguyen <thinhn@synopsys.com>,
* John Youn <johnyoun@synopsys.com>
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/property.h>
/**
* struct dwc3_haps - Driver private structure
* @dwc3: child dwc3 platform_device
* @pci: our link to PCI bus
*/
struct dwc3_haps {
struct platform_device *dwc3;
struct pci_dev *pci;
};
static const struct property_entry initial_properties[] = {
PROPERTY_ENTRY_BOOL("snps,usb3_lpm_capable"),
PROPERTY_ENTRY_BOOL("snps,has-lpm-erratum"),
PROPERTY_ENTRY_BOOL("snps,dis_enblslpm_quirk"),
PROPERTY_ENTRY_BOOL("linux,sysdev_is_parent"),
{ },
};
static const struct software_node dwc3_haps_swnode = {
.properties = initial_properties,
};
static int dwc3_haps_probe(struct pci_dev *pci,
const struct pci_device_id *id)
{
struct dwc3_haps *dwc;
struct device *dev = &pci->dev;
struct resource res[2];
int ret;
ret = pcim_enable_device(pci);
if (ret) {
dev_err(dev, "failed to enable pci device\n");
return -ENODEV;
}
pci_set_master(pci);
dwc = devm_kzalloc(dev, sizeof(*dwc), GFP_KERNEL);
if (!dwc)
return -ENOMEM;
dwc->dwc3 = platform_device_alloc("dwc3", PLATFORM_DEVID_AUTO);
if (!dwc->dwc3)
return -ENOMEM;
memset(res, 0x00, sizeof(struct resource) * ARRAY_SIZE(res));
res[0].start = pci_resource_start(pci, 0);
res[0].end = pci_resource_end(pci, 0);
res[0].name = "dwc_usb3";
res[0].flags = IORESOURCE_MEM;
res[1].start = pci->irq;
res[1].name = "dwc_usb3";
res[1].flags = IORESOURCE_IRQ;
ret = platform_device_add_resources(dwc->dwc3, res, ARRAY_SIZE(res));
if (ret) {
dev_err(dev, "couldn't add resources to dwc3 device\n");
goto err;
}
dwc->pci = pci;
dwc->dwc3->dev.parent = dev;
ret = device_add_software_node(&dwc->dwc3->dev, &dwc3_haps_swnode);
if (ret)
goto err;
ret = platform_device_add(dwc->dwc3);
if (ret) {
dev_err(dev, "failed to register dwc3 device\n");
goto err;
}
pci_set_drvdata(pci, dwc);
return 0;
err:
device_remove_software_node(&dwc->dwc3->dev);
platform_device_put(dwc->dwc3);
return ret;
}
static void dwc3_haps_remove(struct pci_dev *pci)
{
struct dwc3_haps *dwc = pci_get_drvdata(pci);
device_remove_software_node(&dwc->dwc3->dev);
platform_device_unregister(dwc->dwc3);
}
static const struct pci_device_id dwc3_haps_id_table[] = {
{
PCI_DEVICE(PCI_VENDOR_ID_SYNOPSYS,
PCI_DEVICE_ID_SYNOPSYS_HAPSUSB3),
/*
* i.MX6QP and i.MX7D platform use a PCIe controller with the
* same VID and PID as this USB controller. The system may
* incorrectly match this driver to that PCIe controller. To
* workaround this, specifically use class type USB to prevent
* incorrect driver matching.
*/
.class = (PCI_CLASS_SERIAL_USB << 8),
.class_mask = 0xffff00,
},
{
PCI_DEVICE(PCI_VENDOR_ID_SYNOPSYS,
PCI_DEVICE_ID_SYNOPSYS_HAPSUSB3_AXI),
},
{
PCI_DEVICE(PCI_VENDOR_ID_SYNOPSYS,
PCI_DEVICE_ID_SYNOPSYS_HAPSUSB31),
},
{ } /* Terminating Entry */
};
MODULE_DEVICE_TABLE(pci, dwc3_haps_id_table);
static struct pci_driver dwc3_haps_driver = {
.name = "dwc3-haps",
.id_table = dwc3_haps_id_table,
.probe = dwc3_haps_probe,
.remove = dwc3_haps_remove,
};
MODULE_AUTHOR("Thinh Nguyen <thinhn@synopsys.com>");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Synopsys HAPS PCI Glue Layer");
module_pci_driver(dwc3_haps_driver);
+448
View File
@@ -0,0 +1,448 @@
// SPDX-License-Identifier: GPL-2.0
/*
* dwc3-imx.c - NXP i.MX Soc USB3 Specific Glue layer
*
* Copyright 2026 NXP
*/
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include "core.h"
#include "glue.h"
/* USB wakeup registers */
#define USB_WAKEUP_CTRL 0x00
/* Global wakeup interrupt enable, also used to clear interrupt */
#define USB_WAKEUP_EN BIT(31)
/* Wakeup from connect or disconnect, only for superspeed */
#define USB_WAKEUP_SS_CONN BIT(5)
/* 0 select vbus_valid, 1 select sessvld */
#define USB_WAKEUP_VBUS_SRC_SESS_VAL BIT(4)
/* Enable signal for wake up from u3 state */
#define USB_WAKEUP_U3_EN BIT(3)
/* Enable signal for wake up from id change */
#define USB_WAKEUP_ID_EN BIT(2)
/* Enable signal for wake up from vbus change */
#define USB_WAKEUP_VBUS_EN BIT(1)
/* Enable signal for wake up from dp/dm change */
#define USB_WAKEUP_DPDM_EN BIT(0)
#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_imx {
struct dwc3 dwc;
struct device *dev;
void __iomem *blkctl_base;
void __iomem *glue_base;
struct clk *hsio_clk;
struct clk *suspend_clk;
int irq;
bool pm_suspended;
bool wakeup_pending;
unsigned permanent_attached:1;
unsigned disable_pwr_ctrl:1;
unsigned overcur_active_low:1;
unsigned power_active_low:1;
};
#define to_dwc3_imx(d) container_of((d), struct dwc3_imx, dwc)
static void dwc3_imx_get_property(struct dwc3_imx *dwc_imx)
{
struct device *dev = dwc_imx->dev;
dwc_imx->permanent_attached =
device_property_read_bool(dev, "fsl,permanently-attached");
dwc_imx->disable_pwr_ctrl =
device_property_read_bool(dev, "fsl,disable-port-power-control");
dwc_imx->overcur_active_low =
device_property_read_bool(dev, "fsl,over-current-active-low");
dwc_imx->power_active_low =
device_property_read_bool(dev, "fsl,power-active-low");
}
static void dwc3_imx_configure_glue(struct dwc3_imx *dwc_imx)
{
u32 value;
if (!dwc_imx->glue_base)
return;
value = readl(dwc_imx->glue_base + USB_CTRL0);
if (dwc_imx->permanent_attached)
value |= USB_CTRL0_USB2_FIXED | USB_CTRL0_USB3_FIXED;
else
value &= ~(USB_CTRL0_USB2_FIXED | USB_CTRL0_USB3_FIXED);
if (dwc_imx->disable_pwr_ctrl)
value &= ~USB_CTRL0_PORTPWR_EN;
else
value |= USB_CTRL0_PORTPWR_EN;
writel(value, dwc_imx->glue_base + USB_CTRL0);
value = readl(dwc_imx->glue_base + USB_CTRL1);
if (dwc_imx->overcur_active_low)
value |= USB_CTRL1_OC_POLARITY;
else
value &= ~USB_CTRL1_OC_POLARITY;
if (dwc_imx->power_active_low)
value |= USB_CTRL1_PWR_POLARITY;
else
value &= ~USB_CTRL1_PWR_POLARITY;
writel(value, dwc_imx->glue_base + USB_CTRL1);
}
static void dwc3_imx_wakeup_enable(struct dwc3_imx *dwc_imx, pm_message_t msg)
{
struct dwc3 *dwc = &dwc_imx->dwc;
u32 val;
val = readl(dwc_imx->blkctl_base + USB_WAKEUP_CTRL);
if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_HOST && dwc->xhci) {
val |= USB_WAKEUP_EN | USB_WAKEUP_DPDM_EN;
if (PMSG_IS_AUTO(msg))
val |= USB_WAKEUP_SS_CONN | USB_WAKEUP_U3_EN;
} else {
val |= USB_WAKEUP_EN | USB_WAKEUP_VBUS_EN |
USB_WAKEUP_VBUS_SRC_SESS_VAL;
}
writel(val, dwc_imx->blkctl_base + USB_WAKEUP_CTRL);
}
static void dwc3_imx_wakeup_disable(struct dwc3_imx *dwc_imx)
{
u32 val;
val = readl(dwc_imx->blkctl_base + USB_WAKEUP_CTRL);
val &= ~(USB_WAKEUP_EN | USB_WAKEUP_EN_MASK);
writel(val, dwc_imx->blkctl_base + USB_WAKEUP_CTRL);
}
static irqreturn_t dwc3_imx_interrupt(int irq, void *data)
{
struct dwc3_imx *dwc_imx = data;
struct dwc3 *dwc = &dwc_imx->dwc;
if (!dwc_imx->pm_suspended)
return IRQ_HANDLED;
disable_irq_nosync(dwc_imx->irq);
dwc_imx->wakeup_pending = true;
if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_HOST && dwc->xhci)
pm_runtime_resume(&dwc->xhci->dev);
else if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_DEVICE)
pm_runtime_get(dwc->dev);
return IRQ_HANDLED;
}
static void dwc3_imx_pre_set_role(struct dwc3 *dwc, enum usb_role role)
{
if (role == USB_ROLE_HOST)
/*
* For xhci host, we need disable dwc core auto
* suspend, because during this auto suspend delay(5s),
* xhci host RUN_STOP is cleared and wakeup is not
* enabled, if device is inserted, xhci host can't
* response the connection.
*/
pm_runtime_dont_use_autosuspend(dwc->dev);
else
pm_runtime_use_autosuspend(dwc->dev);
}
static struct dwc3_glue_ops dwc3_imx_glue_ops = {
.pre_set_role = dwc3_imx_pre_set_role,
};
static const struct property_entry dwc3_imx_properties[] = {
PROPERTY_ENTRY_BOOL("xhci-missing-cas-quirk"),
PROPERTY_ENTRY_BOOL("xhci-skip-phy-init-quirk"),
{},
};
static const struct software_node dwc3_imx_swnode = {
.properties = dwc3_imx_properties,
};
static int dwc3_imx_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct dwc3_imx *dwc_imx;
struct dwc3 *dwc;
struct resource *res;
const char *irq_name;
struct dwc3_probe_data probe_data = {};
int ret, irq;
dwc_imx = devm_kzalloc(dev, sizeof(*dwc_imx), GFP_KERNEL);
if (!dwc_imx)
return -ENOMEM;
platform_set_drvdata(pdev, dwc_imx);
dwc_imx->dev = dev;
dwc3_imx_get_property(dwc_imx);
dwc_imx->blkctl_base = devm_platform_ioremap_resource_byname(pdev, "blkctl");
if (IS_ERR(dwc_imx->blkctl_base))
return PTR_ERR(dwc_imx->blkctl_base);
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "glue");
if (!res) {
dev_warn(dev, "Base address for glue layer missing\n");
} else {
dwc_imx->glue_base = devm_ioremap_resource(dev, res);
if (IS_ERR(dwc_imx->glue_base))
return PTR_ERR(dwc_imx->glue_base);
}
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "core");
if (!res)
return dev_err_probe(dev, -ENODEV, "missing core memory resource\n");
dwc_imx->hsio_clk = devm_clk_get_enabled(dev, "hsio");
if (IS_ERR(dwc_imx->hsio_clk))
return dev_err_probe(dev, PTR_ERR(dwc_imx->hsio_clk),
"Failed to get hsio clk\n");
dwc_imx->suspend_clk = devm_clk_get_enabled(dev, "suspend");
if (IS_ERR(dwc_imx->suspend_clk))
return dev_err_probe(dev, PTR_ERR(dwc_imx->suspend_clk),
"Failed to get suspend clk\n");
irq = platform_get_irq_byname(pdev, "wakeup");
if (irq < 0)
return irq;
dwc_imx->irq = irq;
irq_name = devm_kasprintf(dev, GFP_KERNEL, "%s:wakeup", dev_name(dev));
if (!irq_name)
return dev_err_probe(dev, -ENOMEM, "failed to create irq_name\n");
ret = devm_request_threaded_irq(dev, irq, NULL, dwc3_imx_interrupt,
IRQF_ONESHOT | IRQF_NO_AUTOEN,
irq_name, dwc_imx);
if (ret)
return dev_err_probe(dev, ret, "failed to request IRQ #%d\n", irq);
ret = device_add_software_node(dev, &dwc3_imx_swnode);
if (ret)
return dev_err_probe(dev, ret, "failed to add software node\n");
dwc3_imx_configure_glue(dwc_imx);
dwc = &dwc_imx->dwc;
dwc->dev = dev;
dwc->glue_ops = &dwc3_imx_glue_ops;
probe_data.res = res;
probe_data.dwc = dwc;
probe_data.properties = DWC3_DEFAULT_PROPERTIES;
probe_data.properties.needs_full_reinit = true;
ret = dwc3_core_probe(&probe_data);
if (ret) {
device_remove_software_node(dev);
return ret;
}
device_set_wakeup_capable(dev, true);
return 0;
}
static void dwc3_imx_remove(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct dwc3 *dwc = dev_get_drvdata(dev);
dwc3_core_remove(dwc);
device_remove_software_node(dev);
}
static void dwc3_imx_suspend(struct dwc3_imx *dwc_imx, pm_message_t msg)
{
if (dwc_imx->pm_suspended)
return;
if (PMSG_IS_AUTO(msg) || device_may_wakeup(dwc_imx->dev))
dwc3_imx_wakeup_enable(dwc_imx, msg);
enable_irq(dwc_imx->irq);
dwc_imx->pm_suspended = true;
}
static void dwc3_imx_resume(struct dwc3_imx *dwc_imx, pm_message_t msg)
{
struct dwc3 *dwc = &dwc_imx->dwc;
if (!dwc_imx->pm_suspended)
return;
dwc_imx->pm_suspended = false;
if (!dwc_imx->wakeup_pending)
disable_irq_nosync(dwc_imx->irq);
dwc3_imx_wakeup_disable(dwc_imx);
/* Upon power loss any previous configuration is lost, restore it */
dwc3_imx_configure_glue(dwc_imx);
if (dwc_imx->wakeup_pending) {
dwc_imx->wakeup_pending = false;
if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_DEVICE)
pm_runtime_put_autosuspend(dwc->dev);
else
/*
* Add wait for xhci switch from suspend
* clock to normal clock to detect connection.
*/
usleep_range(9000, 10000);
}
}
static int dwc3_imx_runtime_suspend(struct device *dev)
{
struct dwc3 *dwc = dev_get_drvdata(dev);
struct dwc3_imx *dwc_imx = to_dwc3_imx(dwc);
int ret;
ret = dwc3_runtime_suspend(dwc);
if (ret)
return ret;
dwc3_imx_suspend(dwc_imx, PMSG_AUTO_SUSPEND);
return 0;
}
static int dwc3_imx_runtime_resume(struct device *dev)
{
struct dwc3 *dwc = dev_get_drvdata(dev);
struct dwc3_imx *dwc_imx = to_dwc3_imx(dwc);
dwc3_imx_resume(dwc_imx, PMSG_AUTO_RESUME);
return dwc3_runtime_resume(dwc);
}
static int dwc3_imx_runtime_idle(struct device *dev)
{
return dwc3_runtime_idle(dev_get_drvdata(dev));
}
static int dwc3_imx_pm_suspend(struct device *dev)
{
struct dwc3 *dwc = dev_get_drvdata(dev);
struct dwc3_imx *dwc_imx = to_dwc3_imx(dwc);
int ret;
ret = dwc3_pm_suspend(dwc);
if (ret)
return ret;
dwc3_imx_suspend(dwc_imx, PMSG_SUSPEND);
if (device_may_wakeup(dev)) {
enable_irq_wake(dwc_imx->irq);
device_set_out_band_wakeup(dev);
} else {
clk_disable_unprepare(dwc_imx->suspend_clk);
}
clk_disable_unprepare(dwc_imx->hsio_clk);
return 0;
}
static int dwc3_imx_pm_resume(struct device *dev)
{
struct dwc3 *dwc = dev_get_drvdata(dev);
struct dwc3_imx *dwc_imx = to_dwc3_imx(dwc);
int ret;
if (device_may_wakeup(dwc_imx->dev)) {
disable_irq_wake(dwc_imx->irq);
} else {
ret = clk_prepare_enable(dwc_imx->suspend_clk);
if (ret)
return ret;
}
ret = clk_prepare_enable(dwc_imx->hsio_clk);
if (ret) {
clk_disable_unprepare(dwc_imx->suspend_clk);
return ret;
}
dwc3_imx_resume(dwc_imx, PMSG_RESUME);
ret = dwc3_pm_resume(dwc);
if (ret)
return ret;
return 0;
}
static void dwc3_imx_complete(struct device *dev)
{
dwc3_pm_complete(dev_get_drvdata(dev));
}
static int dwc3_imx_prepare(struct device *dev)
{
return dwc3_pm_prepare(dev_get_drvdata(dev));
}
static const struct dev_pm_ops dwc3_imx_dev_pm_ops = {
SYSTEM_SLEEP_PM_OPS(dwc3_imx_pm_suspend, dwc3_imx_pm_resume)
RUNTIME_PM_OPS(dwc3_imx_runtime_suspend, dwc3_imx_runtime_resume,
dwc3_imx_runtime_idle)
.complete = pm_sleep_ptr(dwc3_imx_complete),
.prepare = pm_sleep_ptr(dwc3_imx_prepare),
};
static const struct of_device_id dwc3_imx_of_match[] = {
{ .compatible = "nxp,imx8mp-dwc3", },
{},
};
MODULE_DEVICE_TABLE(of, dwc3_imx_of_match);
static struct platform_driver dwc3_imx_driver = {
.probe = dwc3_imx_probe,
.remove = dwc3_imx_remove,
.driver = {
.name = "imx-dwc3",
.pm = pm_ptr(&dwc3_imx_dev_pm_ops),
.of_match_table = dwc3_imx_of_match,
},
};
module_platform_driver(dwc3_imx_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("DesignWare USB3 i.MX Glue Layer");
+458
View File
@@ -0,0 +1,458 @@
// SPDX-License-Identifier: GPL-2.0
/*
* dwc3-imx8mp.c - NXP imx8mp Specific Glue layer
*
* Copyright (c) 2020 NXP.
*/
#include <linux/cleanup.h>
#include <linux/clk.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include "core.h"
/* USB wakeup registers */
#define USB_WAKEUP_CTRL 0x00
/* Global wakeup interrupt enable, also used to clear interrupt */
#define USB_WAKEUP_EN BIT(31)
/* Wakeup from connect or disconnect, only for superspeed */
#define USB_WAKEUP_SS_CONN BIT(5)
/* 0 select vbus_valid, 1 select sessvld */
#define USB_WAKEUP_VBUS_SRC_SESS_VAL BIT(4)
/* Enable signal for wake up from u3 state */
#define USB_WAKEUP_U3_EN BIT(3)
/* Enable signal for wake up from id change */
#define USB_WAKEUP_ID_EN BIT(2)
/* Enable signal for wake up from vbus change */
#define USB_WAKEUP_VBUS_EN BIT(1)
/* Enable signal for wake up from dp/dm change */
#define USB_WAKEUP_DPDM_EN BIT(0)
#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_pdev;
void __iomem *hsio_blk_base;
void __iomem *glue_base;
struct clk *hsio_clk;
struct clk *suspend_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,
pm_message_t msg)
{
struct dwc3 *dwc3 = platform_get_drvdata(dwc3_imx->dwc3_pdev);
u32 val;
if (!dwc3)
return;
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_DPDM_EN;
if (PMSG_IS_AUTO(msg))
val |= USB_WAKEUP_SS_CONN | USB_WAKEUP_U3_EN;
} else {
val |= USB_WAKEUP_EN | USB_WAKEUP_VBUS_EN |
USB_WAKEUP_VBUS_SRC_SESS_VAL;
}
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->hsio_blk_base + USB_WAKEUP_CTRL);
val &= ~(USB_WAKEUP_EN | USB_WAKEUP_EN_MASK);
writel(val, dwc3_imx->hsio_blk_base + USB_WAKEUP_CTRL);
}
static const struct property_entry dwc3_imx8mp_properties[] = {
PROPERTY_ENTRY_BOOL("xhci-missing-cas-quirk"),
PROPERTY_ENTRY_BOOL("xhci-skip-phy-init-quirk"),
{},
};
static const struct software_node dwc3_imx8mp_swnode = {
.properties = dwc3_imx8mp_properties,
};
static irqreturn_t dwc3_imx8mp_interrupt(int irq, void *_dwc3_imx)
{
struct dwc3_imx8mp *dwc3_imx = _dwc3_imx;
struct dwc3 *dwc = platform_get_drvdata(dwc3_imx->dwc3_pdev);
if (!dwc3_imx->pm_suspended)
return IRQ_HANDLED;
disable_irq_nosync(dwc3_imx->irq);
dwc3_imx->wakeup_pending = true;
if ((dwc->current_dr_role == DWC3_GCTL_PRTCAP_HOST) && dwc->xhci)
pm_runtime_resume(&dwc->xhci->dev);
else if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_DEVICE)
pm_runtime_get(dwc->dev);
return IRQ_HANDLED;
}
static void dwc3_imx_pre_set_role(struct dwc3 *dwc, enum usb_role role)
{
if (role == USB_ROLE_HOST)
/*
* For xhci host, we need disable dwc core auto
* suspend, because during this auto suspend delay(5s),
* xhci host RUN_STOP is cleared and wakeup is not
* enabled, if device is inserted, xhci host can't
* response the connection.
*/
pm_runtime_dont_use_autosuspend(dwc->dev);
else
pm_runtime_use_autosuspend(dwc->dev);
}
struct dwc3_glue_ops dwc3_imx_glue_ops = {
.pre_set_role = dwc3_imx_pre_set_role,
};
static int dwc3_imx8mp_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *node = dev->of_node;
struct dwc3_imx8mp *dwc3_imx;
struct dwc3 *dwc3;
struct resource *res;
int err, irq;
if (!node) {
dev_err(dev, "device node not found\n");
return -EINVAL;
}
dwc3_imx = devm_kzalloc(dev, sizeof(*dwc3_imx), GFP_KERNEL);
if (!dwc3_imx)
return -ENOMEM;
platform_set_drvdata(pdev, dwc3_imx);
dwc3_imx->dev = dev;
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);
}
dwc3_imx->hsio_clk = devm_clk_get_enabled(dev, "hsio");
if (IS_ERR(dwc3_imx->hsio_clk))
return dev_err_probe(dev, PTR_ERR(dwc3_imx->hsio_clk),
"Failed to get hsio clk\n");
dwc3_imx->suspend_clk = devm_clk_get_enabled(dev, "suspend");
if (IS_ERR(dwc3_imx->suspend_clk))
return dev_err_probe(dev, PTR_ERR(dwc3_imx->suspend_clk),
"Failed to get suspend clk\n");
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
dwc3_imx->irq = irq;
struct device_node *dwc3_np __free(device_node) = of_get_compatible_child(node,
"snps,dwc3");
if (!dwc3_np)
return dev_err_probe(dev, -ENODEV, "failed to find dwc3 core child\n");
imx8mp_configure_glue(dwc3_imx);
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
err = pm_runtime_get_sync(dev);
if (err < 0)
goto disable_rpm;
err = device_add_software_node(dev, &dwc3_imx8mp_swnode);
if (err) {
err = -ENODEV;
dev_err(dev, "failed to add software node\n");
goto disable_rpm;
}
err = of_platform_populate(node, NULL, NULL, dev);
if (err) {
dev_err(&pdev->dev, "failed to create dwc3 core\n");
goto remove_swnode;
}
dwc3_imx->dwc3_pdev = of_find_device_by_node(dwc3_np);
if (!dwc3_imx->dwc3_pdev) {
dev_err(dev, "failed to get dwc3 platform device\n");
err = -ENODEV;
goto depopulate;
}
dwc3 = platform_get_drvdata(dwc3_imx->dwc3_pdev);
if (!dwc3) {
err = dev_err_probe(dev, -EPROBE_DEFER, "failed to get dwc3 platform data\n");
goto put_dwc3;
}
dwc3->glue_ops = &dwc3_imx_glue_ops;
if (dwc3->dr_mode == USB_DR_MODE_HOST)
pm_runtime_dont_use_autosuspend(dwc3->dev);
err = devm_request_threaded_irq(dev, irq, NULL, dwc3_imx8mp_interrupt,
IRQF_ONESHOT, dev_name(dev), dwc3_imx);
if (err) {
dev_err(dev, "failed to request IRQ #%d --> %d\n", irq, err);
goto put_dwc3;
}
device_set_wakeup_capable(dev, true);
pm_runtime_put(dev);
return 0;
put_dwc3:
put_device(&dwc3_imx->dwc3_pdev->dev);
depopulate:
of_platform_depopulate(dev);
remove_swnode:
device_remove_software_node(dev);
disable_rpm:
pm_runtime_disable(dev);
pm_runtime_put_noidle(dev);
return err;
}
static void dwc3_imx8mp_remove(struct platform_device *pdev)
{
struct dwc3_imx8mp *dwc3_imx = platform_get_drvdata(pdev);
struct device *dev = &pdev->dev;
put_device(&dwc3_imx->dwc3_pdev->dev);
pm_runtime_get_sync(dev);
of_platform_depopulate(dev);
device_remove_software_node(dev);
pm_runtime_disable(dev);
pm_runtime_put_noidle(dev);
}
static int dwc3_imx8mp_suspend(struct dwc3_imx8mp *dwc3_imx, pm_message_t msg)
{
if (dwc3_imx->pm_suspended)
return 0;
/* Wakeup enable */
if (PMSG_IS_AUTO(msg) || device_may_wakeup(dwc3_imx->dev))
dwc3_imx8mp_wakeup_enable(dwc3_imx, msg);
dwc3_imx->pm_suspended = true;
return 0;
}
static int dwc3_imx8mp_resume(struct dwc3_imx8mp *dwc3_imx, pm_message_t msg)
{
struct dwc3 *dwc = platform_get_drvdata(dwc3_imx->dwc3_pdev);
int ret = 0;
if (!dwc3_imx->pm_suspended)
return 0;
/* Wakeup disable */
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) {
pm_runtime_put_autosuspend(dwc->dev);
} else {
/*
* Add wait for xhci switch from suspend
* clock to normal clock to detect connection.
*/
usleep_range(9000, 10000);
}
enable_irq(dwc3_imx->irq);
}
return ret;
}
static int dwc3_imx8mp_pm_suspend(struct device *dev)
{
struct dwc3_imx8mp *dwc3_imx = dev_get_drvdata(dev);
int ret;
ret = dwc3_imx8mp_suspend(dwc3_imx, PMSG_SUSPEND);
if (device_may_wakeup(dwc3_imx->dev)) {
enable_irq_wake(dwc3_imx->irq);
if (device_is_compatible(dev, "fsl,imx95-dwc3"))
device_set_out_band_wakeup(dev);
} else {
clk_disable_unprepare(dwc3_imx->suspend_clk);
}
clk_disable_unprepare(dwc3_imx->hsio_clk);
dev_dbg(dev, "dwc3 imx8mp pm suspend.\n");
return ret;
}
static int dwc3_imx8mp_pm_resume(struct device *dev)
{
struct dwc3_imx8mp *dwc3_imx = dev_get_drvdata(dev);
int ret;
if (device_may_wakeup(dwc3_imx->dev)) {
disable_irq_wake(dwc3_imx->irq);
} else {
ret = clk_prepare_enable(dwc3_imx->suspend_clk);
if (ret)
return ret;
}
ret = clk_prepare_enable(dwc3_imx->hsio_clk);
if (ret) {
clk_disable_unprepare(dwc3_imx->suspend_clk);
return ret;
}
ret = dwc3_imx8mp_resume(dwc3_imx, PMSG_RESUME);
pm_runtime_disable(dev);
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
dev_dbg(dev, "dwc3 imx8mp pm resume.\n");
return ret;
}
static int dwc3_imx8mp_runtime_suspend(struct device *dev)
{
struct dwc3_imx8mp *dwc3_imx = dev_get_drvdata(dev);
dev_dbg(dev, "dwc3 imx8mp runtime suspend.\n");
return dwc3_imx8mp_suspend(dwc3_imx, PMSG_AUTO_SUSPEND);
}
static int dwc3_imx8mp_runtime_resume(struct device *dev)
{
struct dwc3_imx8mp *dwc3_imx = dev_get_drvdata(dev);
dev_dbg(dev, "dwc3 imx8mp runtime resume.\n");
return dwc3_imx8mp_resume(dwc3_imx, PMSG_AUTO_RESUME);
}
static const struct dev_pm_ops dwc3_imx8mp_dev_pm_ops = {
SYSTEM_SLEEP_PM_OPS(dwc3_imx8mp_pm_suspend, dwc3_imx8mp_pm_resume)
RUNTIME_PM_OPS(dwc3_imx8mp_runtime_suspend, dwc3_imx8mp_runtime_resume,
NULL)
};
static const struct of_device_id dwc3_imx8mp_of_match[] = {
{ .compatible = "fsl,imx8mp-dwc3", },
{},
};
MODULE_DEVICE_TABLE(of, dwc3_imx8mp_of_match);
static struct platform_driver dwc3_imx8mp_driver = {
.probe = dwc3_imx8mp_probe,
.remove = dwc3_imx8mp_remove,
.driver = {
.name = "imx8mp-dwc3",
.pm = pm_ptr(&dwc3_imx8mp_dev_pm_ops),
.of_match_table = dwc3_imx8mp_of_match,
},
};
module_platform_driver(dwc3_imx8mp_driver);
MODULE_ALIAS("platform:imx8mp-dwc3");
MODULE_AUTHOR("jun.li@nxp.com");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("DesignWare USB3 imx8mp Glue Layer");
@@ -0,0 +1,223 @@
// SPDX-License-Identifier: GPL-2.0
/*
* dwc3-keystone.c - Keystone Specific Glue layer
*
* Copyright (C) 2010-2013 Texas Instruments Incorporated - https://www.ti.com
*
* Author: WingMan Kwok <w-kwok2@ti.com>
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/phy/phy.h>
#include <linux/pm_runtime.h>
/* USBSS register offsets */
#define USBSS_REVISION 0x0000
#define USBSS_SYSCONFIG 0x0010
#define USBSS_IRQ_EOI 0x0018
#define USBSS_IRQSTATUS_RAW_0 0x0020
#define USBSS_IRQSTATUS_0 0x0024
#define USBSS_IRQENABLE_SET_0 0x0028
#define USBSS_IRQENABLE_CLR_0 0x002c
/* IRQ register bits */
#define USBSS_IRQ_EOI_LINE(n) BIT(n)
#define USBSS_IRQ_EVENT_ST BIT(0)
#define USBSS_IRQ_COREIRQ_EN BIT(0)
#define USBSS_IRQ_COREIRQ_CLR BIT(0)
struct dwc3_keystone {
struct device *dev;
void __iomem *usbss;
struct phy *usb3_phy;
};
static inline u32 kdwc3_readl(void __iomem *base, u32 offset)
{
return readl(base + offset);
}
static inline void kdwc3_writel(void __iomem *base, u32 offset, u32 value)
{
writel(value, base + offset);
}
static void kdwc3_enable_irqs(struct dwc3_keystone *kdwc)
{
u32 val;
val = kdwc3_readl(kdwc->usbss, USBSS_IRQENABLE_SET_0);
val |= USBSS_IRQ_COREIRQ_EN;
kdwc3_writel(kdwc->usbss, USBSS_IRQENABLE_SET_0, val);
}
static void kdwc3_disable_irqs(struct dwc3_keystone *kdwc)
{
u32 val;
val = kdwc3_readl(kdwc->usbss, USBSS_IRQENABLE_SET_0);
val &= ~USBSS_IRQ_COREIRQ_EN;
kdwc3_writel(kdwc->usbss, USBSS_IRQENABLE_SET_0, val);
}
static irqreturn_t dwc3_keystone_interrupt(int irq, void *_kdwc)
{
struct dwc3_keystone *kdwc = _kdwc;
kdwc3_writel(kdwc->usbss, USBSS_IRQENABLE_CLR_0, USBSS_IRQ_COREIRQ_CLR);
kdwc3_writel(kdwc->usbss, USBSS_IRQSTATUS_0, USBSS_IRQ_EVENT_ST);
kdwc3_writel(kdwc->usbss, USBSS_IRQENABLE_SET_0, USBSS_IRQ_COREIRQ_EN);
kdwc3_writel(kdwc->usbss, USBSS_IRQ_EOI, USBSS_IRQ_EOI_LINE(0));
return IRQ_HANDLED;
}
static int kdwc3_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *node = pdev->dev.of_node;
struct dwc3_keystone *kdwc;
int error, irq;
kdwc = devm_kzalloc(dev, sizeof(*kdwc), GFP_KERNEL);
if (!kdwc)
return -ENOMEM;
platform_set_drvdata(pdev, kdwc);
kdwc->dev = dev;
kdwc->usbss = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(kdwc->usbss))
return PTR_ERR(kdwc->usbss);
/* PSC dependency on AM65 needs SERDES0 to be powered before USB0 */
kdwc->usb3_phy = devm_phy_optional_get(dev, "usb3-phy");
if (IS_ERR(kdwc->usb3_phy))
return dev_err_probe(dev, PTR_ERR(kdwc->usb3_phy), "couldn't get usb3 phy\n");
phy_pm_runtime_get_sync(kdwc->usb3_phy);
error = phy_reset(kdwc->usb3_phy);
if (error < 0) {
dev_err(dev, "usb3 phy reset failed: %d\n", error);
return error;
}
error = phy_init(kdwc->usb3_phy);
if (error < 0) {
dev_err(dev, "usb3 phy init failed: %d\n", error);
return error;
}
error = phy_power_on(kdwc->usb3_phy);
if (error < 0) {
dev_err(dev, "usb3 phy power on failed: %d\n", error);
phy_exit(kdwc->usb3_phy);
return error;
}
pm_runtime_enable(kdwc->dev);
error = pm_runtime_get_sync(kdwc->dev);
if (error < 0) {
dev_err(kdwc->dev, "pm_runtime_get_sync failed, error %d\n",
error);
goto err_irq;
}
/* IRQ processing not required currently for AM65 */
if (of_device_is_compatible(node, "ti,am654-dwc3"))
goto skip_irq;
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
error = irq;
goto err_irq;
}
error = devm_request_irq(dev, irq, dwc3_keystone_interrupt, IRQF_SHARED,
dev_name(dev), kdwc);
if (error) {
dev_err(dev, "failed to request IRQ #%d --> %d\n",
irq, error);
goto err_irq;
}
kdwc3_enable_irqs(kdwc);
skip_irq:
error = of_platform_populate(node, NULL, NULL, dev);
if (error) {
dev_err(&pdev->dev, "failed to create dwc3 core\n");
goto err_core;
}
return 0;
err_core:
kdwc3_disable_irqs(kdwc);
err_irq:
pm_runtime_put_sync(kdwc->dev);
pm_runtime_disable(kdwc->dev);
phy_power_off(kdwc->usb3_phy);
phy_exit(kdwc->usb3_phy);
phy_pm_runtime_put_sync(kdwc->usb3_phy);
return error;
}
static int kdwc3_remove_core(struct device *dev, void *c)
{
struct platform_device *pdev = to_platform_device(dev);
platform_device_unregister(pdev);
return 0;
}
static void kdwc3_remove(struct platform_device *pdev)
{
struct dwc3_keystone *kdwc = platform_get_drvdata(pdev);
struct device_node *node = pdev->dev.of_node;
if (!of_device_is_compatible(node, "ti,am654-dwc3"))
kdwc3_disable_irqs(kdwc);
device_for_each_child(&pdev->dev, NULL, kdwc3_remove_core);
pm_runtime_put_sync(kdwc->dev);
pm_runtime_disable(kdwc->dev);
phy_power_off(kdwc->usb3_phy);
phy_exit(kdwc->usb3_phy);
phy_pm_runtime_put_sync(kdwc->usb3_phy);
}
static const struct of_device_id kdwc3_of_match[] = {
{ .compatible = "ti,keystone-dwc3", },
{ .compatible = "ti,am654-dwc3" },
{},
};
MODULE_DEVICE_TABLE(of, kdwc3_of_match);
static struct platform_driver kdwc3_driver = {
.probe = kdwc3_probe,
.remove = kdwc3_remove,
.driver = {
.name = "keystone-dwc3",
.of_match_table = kdwc3_of_match,
},
};
module_platform_driver(kdwc3_driver);
MODULE_ALIAS("platform:keystone-dwc3");
MODULE_AUTHOR("WingMan Kwok <w-kwok2@ti.com>");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("DesignWare USB3 KEYSTONE Glue Layer");
@@ -0,0 +1,989 @@
// SPDX-License-Identifier: GPL-2.0
/*
* USB Glue for Amlogic G12A SoCs
*
* Copyright (c) 2019 BayLibre, SAS
* Author: Neil Armstrong <narmstrong@baylibre.com>
*/
/*
* The USB is organized with a glue around the DWC3 Controller IP as :
* - Control registers for each USB2 Ports
* - Control registers for the USB PHY layer
* - SuperSpeed PHY can be enabled only if port is used
* - Dynamic OTG switching with ID change interrupt
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/bitfield.h>
#include <linux/bitops.h>
#include <linux/reset.h>
#include <linux/phy/phy.h>
#include <linux/usb/otg.h>
#include <linux/usb/role.h>
#include <linux/regulator/consumer.h>
/* USB2 Ports Control Registers, offsets are per-port */
#define U2P_REG_SIZE 0x20
#define U2P_R0 0x0
#define U2P_R0_HOST_DEVICE BIT(0)
#define U2P_R0_POWER_OK BIT(1)
#define U2P_R0_HAST_MODE BIT(2)
#define U2P_R0_POWER_ON_RESET BIT(3)
#define U2P_R0_ID_PULLUP BIT(4)
#define U2P_R0_DRV_VBUS BIT(5)
#define U2P_R1 0x4
#define U2P_R1_PHY_READY BIT(0)
#define U2P_R1_ID_DIG BIT(1)
#define U2P_R1_OTG_SESSION_VALID BIT(2)
#define U2P_R1_VBUS_VALID BIT(3)
/* USB Glue Control Registers */
#define G12A_GLUE_OFFSET 0x80
#define USB_R0 0x00
#define USB_R0_P30_LANE0_TX2RX_LOOPBACK BIT(17)
#define USB_R0_P30_LANE0_EXT_PCLK_REQ BIT(18)
#define USB_R0_P30_PCS_RX_LOS_MASK_VAL_MASK GENMASK(28, 19)
#define USB_R0_U2D_SS_SCALEDOWN_MODE_MASK GENMASK(30, 29)
#define USB_R0_U2D_ACT BIT(31)
#define USB_R1 0x04
#define USB_R1_U3H_BIGENDIAN_GS BIT(0)
#define USB_R1_U3H_PME_ENABLE BIT(1)
#define USB_R1_U3H_HUB_PORT_OVERCURRENT_MASK GENMASK(4, 2)
#define USB_R1_U3H_HUB_PORT_PERM_ATTACH_MASK GENMASK(9, 7)
#define USB_R1_U3H_HOST_U2_PORT_DISABLE_MASK GENMASK(13, 12)
#define USB_R1_U3H_HOST_U3_PORT_DISABLE BIT(16)
#define USB_R1_U3H_HOST_PORT_POWER_CONTROL_PRESENT BIT(17)
#define USB_R1_U3H_HOST_MSI_ENABLE BIT(18)
#define USB_R1_U3H_FLADJ_30MHZ_REG_MASK GENMASK(24, 19)
#define USB_R1_P30_PCS_TX_SWING_FULL_MASK GENMASK(31, 25)
#define USB_R2 0x08
#define USB_R2_P30_PCS_TX_DEEMPH_3P5DB_MASK GENMASK(25, 20)
#define USB_R2_P30_PCS_TX_DEEMPH_6DB_MASK GENMASK(31, 26)
#define USB_R3 0x0c
#define USB_R3_P30_SSC_ENABLE BIT(0)
#define USB_R3_P30_SSC_RANGE_MASK GENMASK(3, 1)
#define USB_R3_P30_SSC_REF_CLK_SEL_MASK GENMASK(12, 4)
#define USB_R3_P30_REF_SSP_EN BIT(13)
#define USB_R4 0x10
#define USB_R4_P21_PORT_RESET_0 BIT(0)
#define USB_R4_P21_SLEEP_M0 BIT(1)
#define USB_R4_MEM_PD_MASK GENMASK(3, 2)
#define USB_R4_P21_ONLY BIT(4)
#define USB_R5 0x14
#define USB_R5_ID_DIG_SYNC BIT(0)
#define USB_R5_ID_DIG_REG BIT(1)
#define USB_R5_ID_DIG_CFG_MASK GENMASK(3, 2)
#define USB_R5_ID_DIG_EN_0 BIT(4)
#define USB_R5_ID_DIG_EN_1 BIT(5)
#define USB_R5_ID_DIG_CURR BIT(6)
#define USB_R5_ID_DIG_IRQ BIT(7)
#define USB_R5_ID_DIG_TH_MASK GENMASK(15, 8)
#define USB_R5_ID_DIG_CNT_MASK GENMASK(23, 16)
#define PHY_COUNT 3
#define USB2_OTG_PHY 1
static struct clk_bulk_data meson_gxl_clocks[] = {
{ .id = "usb_ctrl" },
{ .id = "ddr" },
};
static struct clk_bulk_data meson_g12a_clocks[] = {
{ .id = NULL },
};
static struct clk_bulk_data meson_a1_clocks[] = {
{ .id = "usb_ctrl" },
{ .id = "usb_bus" },
{ .id = "xtal_usb_ctrl" },
};
static const char * const meson_gxm_phy_names[] = {
"usb2-phy0", "usb2-phy1", "usb2-phy2",
};
static const char * const meson_g12a_phy_names[] = {
"usb2-phy0", "usb2-phy1", "usb3-phy0",
};
/*
* Amlogic A1 has a single physical PHY, in slot 1, but still has the
* two U2 PHY controls register blocks like G12A.
* AXG has the similar scheme, thus needs the same tweak.
* Handling the first PHY on slot 1 would need a large amount of code
* changes, and the current management is generic enough to handle it
* correctly when only the "usb2-phy1" phy is specified on-par with the
* DT bindings.
*/
static const char * const meson_a1_phy_names[] = {
"usb2-phy0", "usb2-phy1"
};
struct dwc3_meson_g12a;
struct dwc3_meson_g12a_drvdata {
bool otg_phy_host_port_disable;
struct clk_bulk_data *clks;
int num_clks;
const char * const *phy_names;
int num_phys;
int (*setup_regmaps)(struct dwc3_meson_g12a *priv, void __iomem *base);
int (*usb2_init_phy)(struct dwc3_meson_g12a *priv, int i,
enum phy_mode mode);
int (*set_phy_mode)(struct dwc3_meson_g12a *priv, int i,
enum phy_mode mode);
int (*usb_init)(struct dwc3_meson_g12a *priv);
int (*usb_post_init)(struct dwc3_meson_g12a *priv);
};
static int dwc3_meson_gxl_setup_regmaps(struct dwc3_meson_g12a *priv,
void __iomem *base);
static int dwc3_meson_g12a_setup_regmaps(struct dwc3_meson_g12a *priv,
void __iomem *base);
static int dwc3_meson_g12a_usb2_init_phy(struct dwc3_meson_g12a *priv, int i,
enum phy_mode mode);
static int dwc3_meson_gxl_usb2_init_phy(struct dwc3_meson_g12a *priv, int i,
enum phy_mode mode);
static int dwc3_meson_g12a_set_phy_mode(struct dwc3_meson_g12a *priv,
int i, enum phy_mode mode);
static int dwc3_meson_gxl_set_phy_mode(struct dwc3_meson_g12a *priv,
int i, enum phy_mode mode);
static int dwc3_meson_g12a_usb_init(struct dwc3_meson_g12a *priv);
static int dwc3_meson_gxl_usb_init(struct dwc3_meson_g12a *priv);
static int dwc3_meson_gxl_usb_post_init(struct dwc3_meson_g12a *priv);
/*
* For GXL and GXM SoCs:
* USB Phy muxing between the DWC2 Device controller and the DWC3 Host
* controller is buggy when switching from Device to Host when USB port
* is unpopulated, it causes the DWC3 to hard crash.
* When populated (including OTG switching with ID pin), the switch works
* like a charm like on the G12A platforms.
* In order to still switch from Host to Device on an USB Type-A port,
* an U2_PORT_DISABLE bit has been added to disconnect the DWC3 Host
* controller from the port, but when used the DWC3 controller must be
* reset to recover usage of the port.
*/
static const struct dwc3_meson_g12a_drvdata gxl_drvdata = {
.otg_phy_host_port_disable = true,
.clks = meson_gxl_clocks,
.num_clks = ARRAY_SIZE(meson_g12a_clocks),
.phy_names = meson_a1_phy_names,
.num_phys = ARRAY_SIZE(meson_a1_phy_names),
.setup_regmaps = dwc3_meson_gxl_setup_regmaps,
.usb2_init_phy = dwc3_meson_gxl_usb2_init_phy,
.set_phy_mode = dwc3_meson_gxl_set_phy_mode,
.usb_init = dwc3_meson_gxl_usb_init,
.usb_post_init = dwc3_meson_gxl_usb_post_init,
};
static const struct dwc3_meson_g12a_drvdata gxm_drvdata = {
.otg_phy_host_port_disable = true,
.clks = meson_gxl_clocks,
.num_clks = ARRAY_SIZE(meson_g12a_clocks),
.phy_names = meson_gxm_phy_names,
.num_phys = ARRAY_SIZE(meson_gxm_phy_names),
.setup_regmaps = dwc3_meson_gxl_setup_regmaps,
.usb2_init_phy = dwc3_meson_gxl_usb2_init_phy,
.set_phy_mode = dwc3_meson_gxl_set_phy_mode,
.usb_init = dwc3_meson_gxl_usb_init,
.usb_post_init = dwc3_meson_gxl_usb_post_init,
};
static const struct dwc3_meson_g12a_drvdata axg_drvdata = {
.clks = meson_gxl_clocks,
.num_clks = ARRAY_SIZE(meson_gxl_clocks),
.phy_names = meson_a1_phy_names,
.num_phys = ARRAY_SIZE(meson_a1_phy_names),
.setup_regmaps = dwc3_meson_gxl_setup_regmaps,
.usb2_init_phy = dwc3_meson_gxl_usb2_init_phy,
.set_phy_mode = dwc3_meson_gxl_set_phy_mode,
.usb_init = dwc3_meson_g12a_usb_init,
.usb_post_init = dwc3_meson_gxl_usb_post_init,
};
static const struct dwc3_meson_g12a_drvdata g12a_drvdata = {
.clks = meson_g12a_clocks,
.num_clks = ARRAY_SIZE(meson_g12a_clocks),
.phy_names = meson_g12a_phy_names,
.num_phys = ARRAY_SIZE(meson_g12a_phy_names),
.setup_regmaps = dwc3_meson_g12a_setup_regmaps,
.usb2_init_phy = dwc3_meson_g12a_usb2_init_phy,
.set_phy_mode = dwc3_meson_g12a_set_phy_mode,
.usb_init = dwc3_meson_g12a_usb_init,
};
static const struct dwc3_meson_g12a_drvdata a1_drvdata = {
.clks = meson_a1_clocks,
.num_clks = ARRAY_SIZE(meson_a1_clocks),
.phy_names = meson_a1_phy_names,
.num_phys = ARRAY_SIZE(meson_a1_phy_names),
.setup_regmaps = dwc3_meson_g12a_setup_regmaps,
.usb2_init_phy = dwc3_meson_g12a_usb2_init_phy,
.set_phy_mode = dwc3_meson_g12a_set_phy_mode,
.usb_init = dwc3_meson_g12a_usb_init,
};
struct dwc3_meson_g12a {
struct device *dev;
struct regmap *u2p_regmap[PHY_COUNT];
struct regmap *usb_glue_regmap;
struct reset_control *reset;
struct phy *phys[PHY_COUNT];
enum usb_dr_mode otg_mode;
enum phy_mode otg_phy_mode;
unsigned int usb2_ports;
unsigned int usb3_ports;
struct regulator *vbus;
struct usb_role_switch_desc switch_desc;
struct usb_role_switch *role_switch;
const struct dwc3_meson_g12a_drvdata *drvdata;
};
static int dwc3_meson_gxl_set_phy_mode(struct dwc3_meson_g12a *priv,
int i, enum phy_mode mode)
{
return phy_set_mode(priv->phys[i], mode);
}
static int dwc3_meson_gxl_usb2_init_phy(struct dwc3_meson_g12a *priv, int i,
enum phy_mode mode)
{
/* On GXL PHY must be started in device mode for DWC2 init */
return priv->drvdata->set_phy_mode(priv, i,
(i == USB2_OTG_PHY) ? PHY_MODE_USB_DEVICE
: PHY_MODE_USB_HOST);
}
static int dwc3_meson_g12a_set_phy_mode(struct dwc3_meson_g12a *priv,
int i, enum phy_mode mode)
{
if (mode == PHY_MODE_USB_HOST)
regmap_update_bits(priv->u2p_regmap[i], U2P_R0,
U2P_R0_HOST_DEVICE,
U2P_R0_HOST_DEVICE);
else
regmap_update_bits(priv->u2p_regmap[i], U2P_R0,
U2P_R0_HOST_DEVICE, 0);
return 0;
}
static int dwc3_meson_g12a_usb2_init_phy(struct dwc3_meson_g12a *priv, int i,
enum phy_mode mode)
{
int ret;
regmap_update_bits(priv->u2p_regmap[i], U2P_R0,
U2P_R0_POWER_ON_RESET,
U2P_R0_POWER_ON_RESET);
if (i == USB2_OTG_PHY) {
regmap_update_bits(priv->u2p_regmap[i], U2P_R0,
U2P_R0_ID_PULLUP | U2P_R0_DRV_VBUS,
U2P_R0_ID_PULLUP | U2P_R0_DRV_VBUS);
ret = priv->drvdata->set_phy_mode(priv, i, mode);
} else
ret = priv->drvdata->set_phy_mode(priv, i,
PHY_MODE_USB_HOST);
if (ret)
return ret;
regmap_update_bits(priv->u2p_regmap[i], U2P_R0,
U2P_R0_POWER_ON_RESET, 0);
return 0;
}
static int dwc3_meson_g12a_usb2_init(struct dwc3_meson_g12a *priv,
enum phy_mode mode)
{
int i, ret;
for (i = 0; i < priv->drvdata->num_phys; ++i) {
if (!priv->phys[i])
continue;
if (!strstr(priv->drvdata->phy_names[i], "usb2"))
continue;
ret = priv->drvdata->usb2_init_phy(priv, i, mode);
if (ret)
return ret;
}
return 0;
}
static void dwc3_meson_g12a_usb3_init(struct dwc3_meson_g12a *priv)
{
regmap_update_bits(priv->usb_glue_regmap, USB_R3,
USB_R3_P30_SSC_RANGE_MASK |
USB_R3_P30_REF_SSP_EN,
USB_R3_P30_SSC_ENABLE |
FIELD_PREP(USB_R3_P30_SSC_RANGE_MASK, 2) |
USB_R3_P30_REF_SSP_EN);
udelay(2);
regmap_update_bits(priv->usb_glue_regmap, USB_R2,
USB_R2_P30_PCS_TX_DEEMPH_3P5DB_MASK,
FIELD_PREP(USB_R2_P30_PCS_TX_DEEMPH_3P5DB_MASK, 0x15));
regmap_update_bits(priv->usb_glue_regmap, USB_R2,
USB_R2_P30_PCS_TX_DEEMPH_6DB_MASK,
FIELD_PREP(USB_R2_P30_PCS_TX_DEEMPH_6DB_MASK, 0x20));
udelay(2);
regmap_update_bits(priv->usb_glue_regmap, USB_R1,
USB_R1_U3H_HOST_PORT_POWER_CONTROL_PRESENT,
USB_R1_U3H_HOST_PORT_POWER_CONTROL_PRESENT);
regmap_update_bits(priv->usb_glue_regmap, USB_R1,
USB_R1_P30_PCS_TX_SWING_FULL_MASK,
FIELD_PREP(USB_R1_P30_PCS_TX_SWING_FULL_MASK, 127));
}
static void dwc3_meson_g12a_usb_otg_apply_mode(struct dwc3_meson_g12a *priv,
enum phy_mode mode)
{
if (mode == PHY_MODE_USB_DEVICE) {
if (priv->otg_mode != USB_DR_MODE_OTG &&
priv->drvdata->otg_phy_host_port_disable)
/* Isolate the OTG PHY port from the Host Controller */
regmap_update_bits(priv->usb_glue_regmap, USB_R1,
USB_R1_U3H_HOST_U2_PORT_DISABLE_MASK,
FIELD_PREP(USB_R1_U3H_HOST_U2_PORT_DISABLE_MASK,
BIT(USB2_OTG_PHY)));
regmap_update_bits(priv->usb_glue_regmap, USB_R0,
USB_R0_U2D_ACT, USB_R0_U2D_ACT);
regmap_update_bits(priv->usb_glue_regmap, USB_R0,
USB_R0_U2D_SS_SCALEDOWN_MODE_MASK, 0);
regmap_update_bits(priv->usb_glue_regmap, USB_R4,
USB_R4_P21_SLEEP_M0, USB_R4_P21_SLEEP_M0);
} else {
if (priv->otg_mode != USB_DR_MODE_OTG &&
priv->drvdata->otg_phy_host_port_disable) {
regmap_update_bits(priv->usb_glue_regmap, USB_R1,
USB_R1_U3H_HOST_U2_PORT_DISABLE_MASK, 0);
msleep(500);
}
regmap_update_bits(priv->usb_glue_regmap, USB_R0,
USB_R0_U2D_ACT, 0);
regmap_update_bits(priv->usb_glue_regmap, USB_R4,
USB_R4_P21_SLEEP_M0, 0);
}
}
static int dwc3_meson_g12a_usb_init_glue(struct dwc3_meson_g12a *priv,
enum phy_mode mode)
{
int ret;
ret = dwc3_meson_g12a_usb2_init(priv, mode);
if (ret)
return ret;
regmap_update_bits(priv->usb_glue_regmap, USB_R1,
USB_R1_U3H_FLADJ_30MHZ_REG_MASK,
FIELD_PREP(USB_R1_U3H_FLADJ_30MHZ_REG_MASK, 0x20));
regmap_update_bits(priv->usb_glue_regmap, USB_R5,
USB_R5_ID_DIG_EN_0,
USB_R5_ID_DIG_EN_0);
regmap_update_bits(priv->usb_glue_regmap, USB_R5,
USB_R5_ID_DIG_EN_1,
USB_R5_ID_DIG_EN_1);
regmap_update_bits(priv->usb_glue_regmap, USB_R5,
USB_R5_ID_DIG_TH_MASK,
FIELD_PREP(USB_R5_ID_DIG_TH_MASK, 0xff));
/* If we have an actual SuperSpeed port, initialize it */
if (priv->usb3_ports)
dwc3_meson_g12a_usb3_init(priv);
dwc3_meson_g12a_usb_otg_apply_mode(priv, mode);
return 0;
}
static const struct regmap_config phy_meson_g12a_usb_glue_regmap_conf = {
.name = "usb-glue",
.reg_bits = 8,
.val_bits = 32,
.reg_stride = 4,
.max_register = USB_R5,
};
static int dwc3_meson_g12a_get_phys(struct dwc3_meson_g12a *priv)
{
const char *phy_name;
int i;
for (i = 0 ; i < priv->drvdata->num_phys ; ++i) {
phy_name = priv->drvdata->phy_names[i];
priv->phys[i] = devm_phy_optional_get(priv->dev, phy_name);
if (!priv->phys[i])
continue;
if (IS_ERR(priv->phys[i]))
return PTR_ERR(priv->phys[i]);
if (strstr(phy_name, "usb3"))
priv->usb3_ports++;
else
priv->usb2_ports++;
}
dev_info(priv->dev, "USB2 ports: %d\n", priv->usb2_ports);
dev_info(priv->dev, "USB3 ports: %d\n", priv->usb3_ports);
return 0;
}
static enum phy_mode dwc3_meson_g12a_get_id(struct dwc3_meson_g12a *priv)
{
u32 reg;
regmap_read(priv->usb_glue_regmap, USB_R5, &reg);
if (reg & (USB_R5_ID_DIG_SYNC | USB_R5_ID_DIG_REG))
return PHY_MODE_USB_DEVICE;
return PHY_MODE_USB_HOST;
}
static int dwc3_meson_g12a_otg_mode_set(struct dwc3_meson_g12a *priv,
enum phy_mode mode)
{
int ret;
if (!priv->phys[USB2_OTG_PHY])
return -EINVAL;
if (mode == PHY_MODE_USB_HOST)
dev_info(priv->dev, "switching to Host Mode\n");
else
dev_info(priv->dev, "switching to Device Mode\n");
if (priv->vbus) {
if (mode == PHY_MODE_USB_DEVICE)
ret = regulator_disable(priv->vbus);
else
ret = regulator_enable(priv->vbus);
if (ret)
return ret;
}
priv->otg_phy_mode = mode;
ret = priv->drvdata->set_phy_mode(priv, USB2_OTG_PHY, mode);
if (ret)
return ret;
dwc3_meson_g12a_usb_otg_apply_mode(priv, mode);
return 0;
}
static int dwc3_meson_g12a_role_set(struct usb_role_switch *sw,
enum usb_role role)
{
struct dwc3_meson_g12a *priv = usb_role_switch_get_drvdata(sw);
enum phy_mode mode;
if (role == USB_ROLE_NONE)
return 0;
mode = (role == USB_ROLE_HOST) ? PHY_MODE_USB_HOST
: PHY_MODE_USB_DEVICE;
if (mode == priv->otg_phy_mode)
return 0;
if (priv->drvdata->otg_phy_host_port_disable)
dev_warn_once(priv->dev, "Broken manual OTG switch\n");
return dwc3_meson_g12a_otg_mode_set(priv, mode);
}
static enum usb_role dwc3_meson_g12a_role_get(struct usb_role_switch *sw)
{
struct dwc3_meson_g12a *priv = usb_role_switch_get_drvdata(sw);
return priv->otg_phy_mode == PHY_MODE_USB_HOST ?
USB_ROLE_HOST : USB_ROLE_DEVICE;
}
static irqreturn_t dwc3_meson_g12a_irq_thread(int irq, void *data)
{
struct dwc3_meson_g12a *priv = data;
enum phy_mode otg_id;
otg_id = dwc3_meson_g12a_get_id(priv);
if (otg_id != priv->otg_phy_mode) {
if (dwc3_meson_g12a_otg_mode_set(priv, otg_id))
dev_warn(priv->dev, "Failed to switch OTG mode\n");
}
regmap_update_bits(priv->usb_glue_regmap, USB_R5,
USB_R5_ID_DIG_IRQ, 0);
return IRQ_HANDLED;
}
static struct device *dwc3_meson_g12_find_child(struct device *dev,
const char *compatible)
{
struct platform_device *pdev;
struct device_node *np;
np = of_get_compatible_child(dev->of_node, compatible);
if (!np)
return NULL;
pdev = of_find_device_by_node(np);
of_node_put(np);
if (!pdev)
return NULL;
return &pdev->dev;
}
static int dwc3_meson_g12a_otg_init(struct platform_device *pdev,
struct dwc3_meson_g12a *priv)
{
enum phy_mode otg_id;
int ret, irq;
struct device *dev = &pdev->dev;
if (priv->otg_mode == USB_DR_MODE_OTG) {
/* Ack irq before registering */
regmap_update_bits(priv->usb_glue_regmap, USB_R5,
USB_R5_ID_DIG_IRQ, 0);
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
dwc3_meson_g12a_irq_thread,
IRQF_ONESHOT, pdev->name, priv);
if (ret)
return ret;
}
/* Setup OTG mode corresponding to the ID pin */
if (priv->otg_mode == USB_DR_MODE_OTG) {
otg_id = dwc3_meson_g12a_get_id(priv);
if (otg_id != priv->otg_phy_mode) {
if (dwc3_meson_g12a_otg_mode_set(priv, otg_id))
dev_warn(dev, "Failed to switch OTG mode\n");
}
}
/* Setup role switcher */
priv->switch_desc.usb2_port = dwc3_meson_g12_find_child(dev,
"snps,dwc3");
priv->switch_desc.udc = dwc3_meson_g12_find_child(dev, "snps,dwc2");
priv->switch_desc.allow_userspace_control = true;
priv->switch_desc.set = dwc3_meson_g12a_role_set;
priv->switch_desc.get = dwc3_meson_g12a_role_get;
priv->switch_desc.driver_data = priv;
priv->role_switch = usb_role_switch_register(dev, &priv->switch_desc);
if (IS_ERR(priv->role_switch))
dev_warn(dev, "Unable to register Role Switch\n");
return 0;
}
static int dwc3_meson_gxl_setup_regmaps(struct dwc3_meson_g12a *priv,
void __iomem *base)
{
/* GXL controls the PHY mode in the PHY registers unlike G12A */
priv->usb_glue_regmap = devm_regmap_init_mmio(priv->dev, base,
&phy_meson_g12a_usb_glue_regmap_conf);
return PTR_ERR_OR_ZERO(priv->usb_glue_regmap);
}
static int dwc3_meson_g12a_setup_regmaps(struct dwc3_meson_g12a *priv,
void __iomem *base)
{
int i;
priv->usb_glue_regmap = devm_regmap_init_mmio(priv->dev,
base + G12A_GLUE_OFFSET,
&phy_meson_g12a_usb_glue_regmap_conf);
if (IS_ERR(priv->usb_glue_regmap))
return PTR_ERR(priv->usb_glue_regmap);
/* Create a regmap for each USB2 PHY control register set */
for (i = 0; i < priv->drvdata->num_phys; i++) {
struct regmap_config u2p_regmap_config = {
.reg_bits = 8,
.val_bits = 32,
.reg_stride = 4,
.max_register = U2P_R1,
};
if (!strstr(priv->drvdata->phy_names[i], "usb2"))
continue;
u2p_regmap_config.name = devm_kasprintf(priv->dev, GFP_KERNEL,
"u2p-%d", i);
if (!u2p_regmap_config.name)
return -ENOMEM;
priv->u2p_regmap[i] = devm_regmap_init_mmio(priv->dev,
base + (i * U2P_REG_SIZE),
&u2p_regmap_config);
if (IS_ERR(priv->u2p_regmap[i]))
return PTR_ERR(priv->u2p_regmap[i]);
}
return 0;
}
static int dwc3_meson_g12a_usb_init(struct dwc3_meson_g12a *priv)
{
return dwc3_meson_g12a_usb_init_glue(priv, priv->otg_phy_mode);
}
static int dwc3_meson_gxl_usb_init(struct dwc3_meson_g12a *priv)
{
return dwc3_meson_g12a_usb_init_glue(priv, PHY_MODE_USB_DEVICE);
}
static int dwc3_meson_gxl_usb_post_init(struct dwc3_meson_g12a *priv)
{
int ret;
ret = priv->drvdata->set_phy_mode(priv, USB2_OTG_PHY,
priv->otg_phy_mode);
if (ret)
return ret;
dwc3_meson_g12a_usb_otg_apply_mode(priv, priv->otg_phy_mode);
return 0;
}
static int dwc3_meson_g12a_probe(struct platform_device *pdev)
{
struct dwc3_meson_g12a *priv;
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
void __iomem *base;
int ret, i;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(base))
return PTR_ERR(base);
priv->drvdata = of_device_get_match_data(&pdev->dev);
priv->dev = dev;
priv->vbus = devm_regulator_get_optional(dev, "vbus");
if (IS_ERR(priv->vbus)) {
if (PTR_ERR(priv->vbus) == -EPROBE_DEFER)
return PTR_ERR(priv->vbus);
priv->vbus = NULL;
}
ret = devm_clk_bulk_get(dev,
priv->drvdata->num_clks,
priv->drvdata->clks);
if (ret)
return ret;
ret = clk_bulk_prepare_enable(priv->drvdata->num_clks,
priv->drvdata->clks);
if (ret)
return ret;
platform_set_drvdata(pdev, priv);
priv->reset = devm_reset_control_get_shared(dev, NULL);
if (IS_ERR(priv->reset)) {
ret = PTR_ERR(priv->reset);
dev_err(dev, "failed to get device reset, err=%d\n", ret);
goto err_disable_clks;
}
ret = reset_control_reset(priv->reset);
if (ret)
goto err_disable_clks;
ret = dwc3_meson_g12a_get_phys(priv);
if (ret)
goto err_rearm;
ret = priv->drvdata->setup_regmaps(priv, base);
if (ret)
goto err_rearm;
if (priv->vbus) {
ret = regulator_enable(priv->vbus);
if (ret)
goto err_rearm;
}
/* Get dr_mode */
priv->otg_mode = usb_get_dr_mode(dev);
if (priv->otg_mode == USB_DR_MODE_PERIPHERAL)
priv->otg_phy_mode = PHY_MODE_USB_DEVICE;
else
priv->otg_phy_mode = PHY_MODE_USB_HOST;
ret = priv->drvdata->usb_init(priv);
if (ret)
goto err_disable_regulator;
/* Init PHYs */
for (i = 0 ; i < PHY_COUNT ; ++i) {
ret = phy_init(priv->phys[i]);
if (ret)
goto err_disable_regulator;
}
/* Set PHY Power */
for (i = 0 ; i < PHY_COUNT ; ++i) {
ret = phy_power_on(priv->phys[i]);
if (ret)
goto err_phys_exit;
}
if (priv->drvdata->usb_post_init) {
ret = priv->drvdata->usb_post_init(priv);
if (ret)
goto err_phys_power;
}
ret = of_platform_populate(np, NULL, NULL, dev);
if (ret)
goto err_phys_power;
ret = dwc3_meson_g12a_otg_init(pdev, priv);
if (ret)
goto err_plat_depopulate;
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
pm_runtime_get_sync(dev);
return 0;
err_plat_depopulate:
of_platform_depopulate(dev);
err_phys_power:
for (i = 0 ; i < PHY_COUNT ; ++i)
phy_power_off(priv->phys[i]);
err_phys_exit:
for (i = 0 ; i < PHY_COUNT ; ++i)
phy_exit(priv->phys[i]);
err_disable_regulator:
if (priv->vbus)
regulator_disable(priv->vbus);
err_rearm:
reset_control_rearm(priv->reset);
err_disable_clks:
clk_bulk_disable_unprepare(priv->drvdata->num_clks,
priv->drvdata->clks);
return ret;
}
static void dwc3_meson_g12a_remove(struct platform_device *pdev)
{
struct dwc3_meson_g12a *priv = platform_get_drvdata(pdev);
struct device *dev = &pdev->dev;
int i;
usb_role_switch_unregister(priv->role_switch);
put_device(priv->switch_desc.udc);
put_device(priv->switch_desc.usb2_port);
of_platform_depopulate(dev);
for (i = 0 ; i < PHY_COUNT ; ++i) {
phy_power_off(priv->phys[i]);
phy_exit(priv->phys[i]);
}
pm_runtime_disable(dev);
pm_runtime_put_noidle(dev);
pm_runtime_set_suspended(dev);
reset_control_rearm(priv->reset);
clk_bulk_disable_unprepare(priv->drvdata->num_clks,
priv->drvdata->clks);
}
static int __maybe_unused dwc3_meson_g12a_runtime_suspend(struct device *dev)
{
struct dwc3_meson_g12a *priv = dev_get_drvdata(dev);
clk_bulk_disable_unprepare(priv->drvdata->num_clks,
priv->drvdata->clks);
return 0;
}
static int __maybe_unused dwc3_meson_g12a_runtime_resume(struct device *dev)
{
struct dwc3_meson_g12a *priv = dev_get_drvdata(dev);
return clk_bulk_prepare_enable(priv->drvdata->num_clks,
priv->drvdata->clks);
}
static int __maybe_unused dwc3_meson_g12a_suspend(struct device *dev)
{
struct dwc3_meson_g12a *priv = dev_get_drvdata(dev);
int i, ret;
if (priv->vbus && priv->otg_phy_mode == PHY_MODE_USB_HOST) {
ret = regulator_disable(priv->vbus);
if (ret)
return ret;
}
for (i = 0 ; i < PHY_COUNT ; ++i) {
phy_power_off(priv->phys[i]);
phy_exit(priv->phys[i]);
}
reset_control_rearm(priv->reset);
return 0;
}
static int __maybe_unused dwc3_meson_g12a_resume(struct device *dev)
{
struct dwc3_meson_g12a *priv = dev_get_drvdata(dev);
int i, ret;
ret = reset_control_reset(priv->reset);
if (ret)
return ret;
ret = priv->drvdata->usb_init(priv);
if (ret)
goto err_rearm;
/* Init PHYs */
for (i = 0 ; i < PHY_COUNT ; ++i) {
ret = phy_init(priv->phys[i]);
if (ret)
goto err_rearm;
}
/* Set PHY Power */
for (i = 0 ; i < PHY_COUNT ; ++i) {
ret = phy_power_on(priv->phys[i]);
if (ret)
goto err_rearm;
}
if (priv->vbus && priv->otg_phy_mode == PHY_MODE_USB_HOST) {
ret = regulator_enable(priv->vbus);
if (ret)
goto err_rearm;
}
if (priv->drvdata->usb_post_init) {
ret = priv->drvdata->usb_post_init(priv);
if (ret)
goto err_rearm;
}
return 0;
err_rearm:
reset_control_rearm(priv->reset);
return ret;
}
static const struct dev_pm_ops dwc3_meson_g12a_dev_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(dwc3_meson_g12a_suspend, dwc3_meson_g12a_resume)
SET_RUNTIME_PM_OPS(dwc3_meson_g12a_runtime_suspend,
dwc3_meson_g12a_runtime_resume, NULL)
};
static const struct of_device_id dwc3_meson_g12a_match[] = {
{
.compatible = "amlogic,meson-gxl-usb-ctrl",
.data = &gxl_drvdata,
},
{
.compatible = "amlogic,meson-gxm-usb-ctrl",
.data = &gxm_drvdata,
},
{
.compatible = "amlogic,meson-axg-usb-ctrl",
.data = &axg_drvdata,
},
{
.compatible = "amlogic,meson-g12a-usb-ctrl",
.data = &g12a_drvdata,
},
{
.compatible = "amlogic,meson-a1-usb-ctrl",
.data = &a1_drvdata,
},
{ /* Sentinel */ }
};
MODULE_DEVICE_TABLE(of, dwc3_meson_g12a_match);
static struct platform_driver dwc3_meson_g12a_driver = {
.probe = dwc3_meson_g12a_probe,
.remove = dwc3_meson_g12a_remove,
.driver = {
.name = "dwc3-meson-g12a",
.of_match_table = dwc3_meson_g12a_match,
.pm = &dwc3_meson_g12a_dev_pm_ops,
},
};
module_platform_driver(dwc3_meson_g12a_driver);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Amlogic Meson G12A USB Glue Layer");
MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
+530
View File
@@ -0,0 +1,530 @@
// SPDX-License-Identifier: GPL-2.0
/*
* DWC3 glue for Cavium Octeon III SOCs.
*
* Copyright (C) 2010-2017 Cavium Networks
* Copyright (C) 2023 RACOM s.r.o.
*/
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
/*
* USB Control Register
*/
#define USBDRD_UCTL_CTL 0x00
/* BIST fast-clear mode select. A BIST run with this bit set
* clears all entries in USBH RAMs to 0x0.
*/
# define USBDRD_UCTL_CTL_CLEAR_BIST BIT_ULL(63)
/* 1 = Start BIST and cleared by hardware */
# define USBDRD_UCTL_CTL_START_BIST BIT_ULL(62)
/* Reference clock select for SuperSpeed and HighSpeed PLLs:
* 0x0 = Both PLLs use DLMC_REF_CLK0 for reference clock
* 0x1 = Both PLLs use DLMC_REF_CLK1 for reference clock
* 0x2 = SuperSpeed PLL uses DLMC_REF_CLK0 for reference clock &
* HighSpeed PLL uses PLL_REF_CLK for reference clck
* 0x3 = SuperSpeed PLL uses DLMC_REF_CLK1 for reference clock &
* HighSpeed PLL uses PLL_REF_CLK for reference clck
*/
# define USBDRD_UCTL_CTL_REF_CLK_SEL GENMASK_ULL(61, 60)
/* 1 = Spread-spectrum clock enable, 0 = SS clock disable */
# define USBDRD_UCTL_CTL_SSC_EN BIT_ULL(59)
/* Spread-spectrum clock modulation range:
* 0x0 = -4980 ppm downspread
* 0x1 = -4492 ppm downspread
* 0x2 = -4003 ppm downspread
* 0x3 - 0x7 = Reserved
*/
# define USBDRD_UCTL_CTL_SSC_RANGE GENMASK_ULL(58, 56)
/* Enable non-standard oscillator frequencies:
* [55:53] = modules -1
* [52:47] = 2's complement push amount, 0 = Feature disabled
*/
# define USBDRD_UCTL_CTL_SSC_REF_CLK_SEL GENMASK_ULL(55, 47)
/* Reference clock multiplier for non-standard frequencies:
* 0x19 = 100MHz on DLMC_REF_CLK* if REF_CLK_SEL = 0x0 or 0x1
* 0x28 = 125MHz on DLMC_REF_CLK* if REF_CLK_SEL = 0x0 or 0x1
* 0x32 = 50MHz on DLMC_REF_CLK* if REF_CLK_SEL = 0x0 or 0x1
* Other Values = Reserved
*/
# define USBDRD_UCTL_CTL_MPLL_MULTIPLIER GENMASK_ULL(46, 40)
/* Enable reference clock to prescaler for SuperSpeed functionality.
* Should always be set to "1"
*/
# define USBDRD_UCTL_CTL_REF_SSP_EN BIT_ULL(39)
/* Divide the reference clock by 2 before entering the
* REF_CLK_FSEL divider:
* If REF_CLK_SEL = 0x0 or 0x1, then only 0x0 is legal
* If REF_CLK_SEL = 0x2 or 0x3, then:
* 0x1 = DLMC_REF_CLK* is 125MHz
* 0x0 = DLMC_REF_CLK* is another supported frequency
*/
# define USBDRD_UCTL_CTL_REF_CLK_DIV2 BIT_ULL(38)
/* Select reference clock freqnuency for both PLL blocks:
* 0x27 = REF_CLK_SEL is 0x0 or 0x1
* 0x07 = REF_CLK_SEL is 0x2 or 0x3
*/
# define USBDRD_UCTL_CTL_REF_CLK_FSEL GENMASK_ULL(37, 32)
/* Controller clock enable. */
# define USBDRD_UCTL_CTL_H_CLK_EN BIT_ULL(30)
/* Select bypass input to controller clock divider:
* 0x0 = Use divided coprocessor clock from H_CLKDIV
* 0x1 = Use clock from GPIO pins
*/
# define USBDRD_UCTL_CTL_H_CLK_BYP_SEL BIT_ULL(29)
/* Reset controller clock divider. */
# define USBDRD_UCTL_CTL_H_CLKDIV_RST BIT_ULL(28)
/* Clock divider select:
* 0x0 = divide by 1
* 0x1 = divide by 2
* 0x2 = divide by 4
* 0x3 = divide by 6
* 0x4 = divide by 8
* 0x5 = divide by 16
* 0x6 = divide by 24
* 0x7 = divide by 32
*/
# define USBDRD_UCTL_CTL_H_CLKDIV_SEL GENMASK_ULL(26, 24)
/* USB3 port permanently attached: 0x0 = No, 0x1 = Yes */
# define USBDRD_UCTL_CTL_USB3_PORT_PERM_ATTACH BIT_ULL(21)
/* USB2 port permanently attached: 0x0 = No, 0x1 = Yes */
# define USBDRD_UCTL_CTL_USB2_PORT_PERM_ATTACH BIT_ULL(20)
/* Disable SuperSpeed PHY: 0x0 = No, 0x1 = Yes */
# define USBDRD_UCTL_CTL_USB3_PORT_DISABLE BIT_ULL(18)
/* Disable HighSpeed PHY: 0x0 = No, 0x1 = Yes */
# define USBDRD_UCTL_CTL_USB2_PORT_DISABLE BIT_ULL(16)
/* Enable PHY SuperSpeed block power: 0x0 = No, 0x1 = Yes */
# define USBDRD_UCTL_CTL_SS_POWER_EN BIT_ULL(14)
/* Enable PHY HighSpeed block power: 0x0 = No, 0x1 = Yes */
# define USBDRD_UCTL_CTL_HS_POWER_EN BIT_ULL(12)
/* Enable USB UCTL interface clock: 0xx = No, 0x1 = Yes */
# define USBDRD_UCTL_CTL_CSCLK_EN BIT_ULL(4)
/* Controller mode: 0x0 = Host, 0x1 = Device */
# define USBDRD_UCTL_CTL_DRD_MODE BIT_ULL(3)
/* PHY reset */
# define USBDRD_UCTL_CTL_UPHY_RST BIT_ULL(2)
/* Software reset UAHC */
# define USBDRD_UCTL_CTL_UAHC_RST BIT_ULL(1)
/* Software resets UCTL */
# define USBDRD_UCTL_CTL_UCTL_RST BIT_ULL(0)
#define USBDRD_UCTL_BIST_STATUS 0x08
#define USBDRD_UCTL_SPARE0 0x10
#define USBDRD_UCTL_INTSTAT 0x30
#define USBDRD_UCTL_PORT_CFG_HS(port) (0x40 + (0x20 * port))
#define USBDRD_UCTL_PORT_CFG_SS(port) (0x48 + (0x20 * port))
#define USBDRD_UCTL_PORT_CR_DBG_CFG(port) (0x50 + (0x20 * port))
#define USBDRD_UCTL_PORT_CR_DBG_STATUS(port) (0x58 + (0x20 * port))
/*
* UCTL Configuration Register
*/
#define USBDRD_UCTL_HOST_CFG 0xe0
/* Indicates minimum value of all received BELT values */
# define USBDRD_UCTL_HOST_CFG_HOST_CURRENT_BELT GENMASK_ULL(59, 48)
/* HS jitter adjustment */
# define USBDRD_UCTL_HOST_CFG_FLA GENMASK_ULL(37, 32)
/* Bus-master enable: 0x0 = Disabled (stall DMAs), 0x1 = enabled */
# define USBDRD_UCTL_HOST_CFG_BME BIT_ULL(28)
/* Overcurrent protection enable: 0x0 = unavailable, 0x1 = available */
# define USBDRD_UCTL_HOST_OCI_EN BIT_ULL(27)
/* Overcurrent sene selection:
* 0x0 = Overcurrent indication from off-chip is active-low
* 0x1 = Overcurrent indication from off-chip is active-high
*/
# define USBDRD_UCTL_HOST_OCI_ACTIVE_HIGH_EN BIT_ULL(26)
/* Port power control enable: 0x0 = unavailable, 0x1 = available */
# define USBDRD_UCTL_HOST_PPC_EN BIT_ULL(25)
/* Port power control sense selection:
* 0x0 = Port power to off-chip is active-low
* 0x1 = Port power to off-chip is active-high
*/
# define USBDRD_UCTL_HOST_PPC_ACTIVE_HIGH_EN BIT_ULL(24)
/*
* UCTL Shim Features Register
*/
#define USBDRD_UCTL_SHIM_CFG 0xe8
/* Out-of-bound UAHC register access: 0 = read, 1 = write */
# define USBDRD_UCTL_SHIM_CFG_XS_NCB_OOB_WRN BIT_ULL(63)
/* SRCID error log for out-of-bound UAHC register access:
* [59:58] = chipID
* [57] = Request source: 0 = core, 1 = NCB-device
* [56:51] = Core/NCB-device number, [56] always 0 for NCB devices
* [50:48] = SubID
*/
# define USBDRD_UCTL_SHIM_CFG_XS_NCB_OOB_OSRC GENMASK_ULL(59, 48)
/* Error log for bad UAHC DMA access: 0 = Read log, 1 = Write log */
# define USBDRD_UCTL_SHIM_CFG_XM_BAD_DMA_WRN BIT_ULL(47)
/* Encoded error type for bad UAHC DMA */
# define USBDRD_UCTL_SHIM_CFG_XM_BAD_DMA_TYPE GENMASK_ULL(43, 40)
/* Select the IOI read command used by DMA accesses */
# define USBDRD_UCTL_SHIM_CFG_DMA_READ_CMD BIT_ULL(12)
/* Select endian format for DMA accesses to the L2C:
* 0x0 = Little endian
* 0x1 = Big endian
* 0x2 = Reserved
* 0x3 = Reserved
*/
# define USBDRD_UCTL_SHIM_CFG_DMA_ENDIAN_MODE GENMASK_ULL(9, 8)
/* Select endian format for IOI CSR access to UAHC:
* 0x0 = Little endian
* 0x1 = Big endian
* 0x2 = Reserved
* 0x3 = Reserved
*/
# define USBDRD_UCTL_SHIM_CFG_CSR_ENDIAN_MODE GENMASK_ULL(1, 0)
#define USBDRD_UCTL_ECC 0xf0
#define USBDRD_UCTL_SPARE1 0xf8
struct dwc3_octeon {
struct device *dev;
void __iomem *base;
};
#define DWC3_GPIO_POWER_NONE (-1)
#ifdef CONFIG_CAVIUM_OCTEON_SOC
#include <asm/octeon/octeon.h>
static inline uint64_t dwc3_octeon_readq(void __iomem *addr)
{
return cvmx_readq_csr(addr);
}
static inline void dwc3_octeon_writeq(void __iomem *base, uint64_t val)
{
cvmx_writeq_csr(base, val);
}
static void dwc3_octeon_config_gpio(int index, int gpio)
{
union cvmx_gpio_bit_cfgx gpio_bit;
if ((OCTEON_IS_MODEL(OCTEON_CN73XX) ||
OCTEON_IS_MODEL(OCTEON_CNF75XX))
&& gpio <= 31) {
gpio_bit.u64 = cvmx_read_csr(CVMX_GPIO_BIT_CFGX(gpio));
gpio_bit.s.tx_oe = 1;
gpio_bit.s.output_sel = (index == 0 ? 0x14 : 0x15);
cvmx_write_csr(CVMX_GPIO_BIT_CFGX(gpio), gpio_bit.u64);
} else if (gpio <= 15) {
gpio_bit.u64 = cvmx_read_csr(CVMX_GPIO_BIT_CFGX(gpio));
gpio_bit.s.tx_oe = 1;
gpio_bit.s.output_sel = (index == 0 ? 0x14 : 0x19);
cvmx_write_csr(CVMX_GPIO_BIT_CFGX(gpio), gpio_bit.u64);
} else {
gpio_bit.u64 = cvmx_read_csr(CVMX_GPIO_XBIT_CFGX(gpio));
gpio_bit.s.tx_oe = 1;
gpio_bit.s.output_sel = (index == 0 ? 0x14 : 0x19);
cvmx_write_csr(CVMX_GPIO_XBIT_CFGX(gpio), gpio_bit.u64);
}
}
#else
static inline uint64_t dwc3_octeon_readq(void __iomem *addr)
{
return 0;
}
static inline void dwc3_octeon_writeq(void __iomem *base, uint64_t val) { }
static inline void dwc3_octeon_config_gpio(int index, int gpio) { }
static uint64_t octeon_get_io_clock_rate(void)
{
return 150000000;
}
#endif
static int dwc3_octeon_get_divider(void)
{
static const uint8_t clk_div[] = { 1, 2, 4, 6, 8, 16, 24, 32 };
int div = 0;
while (div < ARRAY_SIZE(clk_div)) {
uint64_t rate = octeon_get_io_clock_rate() / clk_div[div];
if (rate <= 300000000 && rate >= 150000000)
return div;
div++;
}
return -EINVAL;
}
static int dwc3_octeon_setup(struct dwc3_octeon *octeon,
int ref_clk_sel, int ref_clk_fsel, int mpll_mul,
int power_gpio, int power_active_low)
{
u64 val;
int div;
struct device *dev = octeon->dev;
void __iomem *uctl_ctl_reg = octeon->base + USBDRD_UCTL_CTL;
void __iomem *uctl_host_cfg_reg = octeon->base + USBDRD_UCTL_HOST_CFG;
/*
* Step 1: Wait for all voltages to be stable...that surely
* happened before starting the kernel. SKIP
*/
/* Step 2: Select GPIO for overcurrent indication, if desired. SKIP */
/* Step 3: Assert all resets. */
val = dwc3_octeon_readq(uctl_ctl_reg);
val |= USBDRD_UCTL_CTL_UPHY_RST |
USBDRD_UCTL_CTL_UAHC_RST |
USBDRD_UCTL_CTL_UCTL_RST;
dwc3_octeon_writeq(uctl_ctl_reg, val);
/* Step 4a: Reset the clock dividers. */
val = dwc3_octeon_readq(uctl_ctl_reg);
val |= USBDRD_UCTL_CTL_H_CLKDIV_RST;
dwc3_octeon_writeq(uctl_ctl_reg, val);
/* Step 4b: Select controller clock frequency. */
div = dwc3_octeon_get_divider();
if (div < 0) {
dev_err(dev, "clock divider invalid\n");
return div;
}
val = dwc3_octeon_readq(uctl_ctl_reg);
FIELD_MODIFY(USBDRD_UCTL_CTL_H_CLKDIV_SEL, &val, div);
val |= USBDRD_UCTL_CTL_H_CLK_EN;
dwc3_octeon_writeq(uctl_ctl_reg, val);
val = dwc3_octeon_readq(uctl_ctl_reg);
if ((div != FIELD_GET(USBDRD_UCTL_CTL_H_CLKDIV_SEL, val)) ||
(!(FIELD_GET(USBDRD_UCTL_CTL_H_CLK_EN, val)))) {
dev_err(dev, "clock init failure (UCTL_CTL=%016llx)\n", val);
return -EINVAL;
}
/* Step 4c: Deassert the controller clock divider reset. */
val &= ~USBDRD_UCTL_CTL_H_CLKDIV_RST;
dwc3_octeon_writeq(uctl_ctl_reg, val);
/* Step 5a: Reference clock configuration. */
val = dwc3_octeon_readq(uctl_ctl_reg);
val &= ~USBDRD_UCTL_CTL_REF_CLK_DIV2;
FIELD_MODIFY(USBDRD_UCTL_CTL_REF_CLK_SEL, &val, ref_clk_sel);
FIELD_MODIFY(USBDRD_UCTL_CTL_REF_CLK_FSEL, &val, ref_clk_fsel);
FIELD_MODIFY(USBDRD_UCTL_CTL_MPLL_MULTIPLIER, &val, mpll_mul);
/* Step 5b: Configure and enable spread-spectrum for SuperSpeed. */
val |= USBDRD_UCTL_CTL_SSC_EN;
/* Step 5c: Enable SuperSpeed. */
val |= USBDRD_UCTL_CTL_REF_SSP_EN;
/* Step 5d: Configure PHYs. SKIP */
/* Step 6a & 6b: Power up PHYs. */
val |= USBDRD_UCTL_CTL_HS_POWER_EN;
val |= USBDRD_UCTL_CTL_SS_POWER_EN;
dwc3_octeon_writeq(uctl_ctl_reg, val);
/* Step 7: Wait 10 controller-clock cycles to take effect. */
udelay(10);
/* Step 8a: Deassert UCTL reset signal. */
val = dwc3_octeon_readq(uctl_ctl_reg);
val &= ~USBDRD_UCTL_CTL_UCTL_RST;
dwc3_octeon_writeq(uctl_ctl_reg, val);
/* Step 8b: Wait 10 controller-clock cycles. */
udelay(10);
/* Step 8c: Setup power control. */
val = dwc3_octeon_readq(uctl_host_cfg_reg);
val |= USBDRD_UCTL_HOST_PPC_EN;
if (power_gpio == DWC3_GPIO_POWER_NONE) {
val &= ~USBDRD_UCTL_HOST_PPC_EN;
} else {
val |= USBDRD_UCTL_HOST_PPC_EN;
dwc3_octeon_config_gpio(((__force uintptr_t)octeon->base >> 24) & 1,
power_gpio);
dev_dbg(dev, "power control is using gpio%d\n", power_gpio);
}
if (power_active_low)
val &= ~USBDRD_UCTL_HOST_PPC_ACTIVE_HIGH_EN;
else
val |= USBDRD_UCTL_HOST_PPC_ACTIVE_HIGH_EN;
dwc3_octeon_writeq(uctl_host_cfg_reg, val);
/* Step 8d: Deassert UAHC reset signal. */
val = dwc3_octeon_readq(uctl_ctl_reg);
val &= ~USBDRD_UCTL_CTL_UAHC_RST;
dwc3_octeon_writeq(uctl_ctl_reg, val);
/* Step 8e: Wait 10 controller-clock cycles. */
udelay(10);
/* Step 9: Enable conditional coprocessor clock of UCTL. */
val = dwc3_octeon_readq(uctl_ctl_reg);
val |= USBDRD_UCTL_CTL_CSCLK_EN;
dwc3_octeon_writeq(uctl_ctl_reg, val);
/*Step 10: Set for host mode only. */
val = dwc3_octeon_readq(uctl_ctl_reg);
val &= ~USBDRD_UCTL_CTL_DRD_MODE;
dwc3_octeon_writeq(uctl_ctl_reg, val);
return 0;
}
static void dwc3_octeon_set_endian_mode(struct dwc3_octeon *octeon)
{
u64 val;
void __iomem *uctl_shim_cfg_reg = octeon->base + USBDRD_UCTL_SHIM_CFG;
val = dwc3_octeon_readq(uctl_shim_cfg_reg);
val &= ~USBDRD_UCTL_SHIM_CFG_DMA_ENDIAN_MODE;
val &= ~USBDRD_UCTL_SHIM_CFG_CSR_ENDIAN_MODE;
#ifdef __BIG_ENDIAN
val |= FIELD_PREP(USBDRD_UCTL_SHIM_CFG_DMA_ENDIAN_MODE, 1);
val |= FIELD_PREP(USBDRD_UCTL_SHIM_CFG_CSR_ENDIAN_MODE, 1);
#endif
dwc3_octeon_writeq(uctl_shim_cfg_reg, val);
}
static void dwc3_octeon_phy_reset(struct dwc3_octeon *octeon)
{
u64 val;
void __iomem *uctl_ctl_reg = octeon->base + USBDRD_UCTL_CTL;
val = dwc3_octeon_readq(uctl_ctl_reg);
val &= ~USBDRD_UCTL_CTL_UPHY_RST;
dwc3_octeon_writeq(uctl_ctl_reg, val);
}
static int dwc3_octeon_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *node = dev->of_node;
struct dwc3_octeon *octeon;
const char *hs_clock_type, *ss_clock_type;
int ref_clk_sel, ref_clk_fsel, mpll_mul;
int power_active_low, power_gpio;
int err, len;
u32 clock_rate, gpio_pwr[3];
if (of_property_read_u32(node, "refclk-frequency", &clock_rate)) {
dev_err(dev, "No UCTL \"refclk-frequency\"\n");
return -EINVAL;
}
if (of_property_read_string(node, "refclk-type-ss", &ss_clock_type)) {
dev_err(dev, "No UCTL \"refclk-type-ss\"\n");
return -EINVAL;
}
if (of_property_read_string(node, "refclk-type-hs", &hs_clock_type)) {
dev_err(dev, "No UCTL \"refclk-type-hs\"\n");
return -EINVAL;
}
ref_clk_sel = 2;
if (strcmp("dlmc_ref_clk0", ss_clock_type) == 0) {
if (strcmp(hs_clock_type, "dlmc_ref_clk0") == 0)
ref_clk_sel = 0;
else if (strcmp(hs_clock_type, "pll_ref_clk"))
dev_warn(dev, "Invalid HS clock type %s, using pll_ref_clk instead\n",
hs_clock_type);
} else if (strcmp(ss_clock_type, "dlmc_ref_clk1") == 0) {
if (strcmp(hs_clock_type, "dlmc_ref_clk1") == 0) {
ref_clk_sel = 1;
} else {
ref_clk_sel = 3;
if (strcmp(hs_clock_type, "pll_ref_clk"))
dev_warn(dev, "Invalid HS clock type %s, using pll_ref_clk instead\n",
hs_clock_type);
}
} else {
dev_warn(dev, "Invalid SS clock type %s, using dlmc_ref_clk0 instead\n",
ss_clock_type);
}
ref_clk_fsel = 0x07;
switch (clock_rate) {
default:
dev_warn(dev, "Invalid ref_clk %u, using 100000000 instead\n",
clock_rate);
fallthrough;
case 100000000:
mpll_mul = 0x19;
if (ref_clk_sel < 2)
ref_clk_fsel = 0x27;
break;
case 50000000:
mpll_mul = 0x32;
break;
case 125000000:
mpll_mul = 0x28;
break;
}
power_gpio = DWC3_GPIO_POWER_NONE;
power_active_low = 0;
len = of_property_read_variable_u32_array(node, "power", gpio_pwr, 2, 3);
if (len > 0) {
if (len == 3)
power_active_low = gpio_pwr[2] & 0x01;
power_gpio = gpio_pwr[1];
}
octeon = devm_kzalloc(dev, sizeof(*octeon), GFP_KERNEL);
if (!octeon)
return -ENOMEM;
octeon->dev = dev;
octeon->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(octeon->base))
return PTR_ERR(octeon->base);
err = dwc3_octeon_setup(octeon, ref_clk_sel, ref_clk_fsel, mpll_mul,
power_gpio, power_active_low);
if (err)
return err;
dwc3_octeon_set_endian_mode(octeon);
dwc3_octeon_phy_reset(octeon);
platform_set_drvdata(pdev, octeon);
return of_platform_populate(node, NULL, NULL, dev);
}
static void dwc3_octeon_remove(struct platform_device *pdev)
{
struct dwc3_octeon *octeon = platform_get_drvdata(pdev);
of_platform_depopulate(octeon->dev);
}
static const struct of_device_id dwc3_octeon_of_match[] = {
{ .compatible = "cavium,octeon-7130-usb-uctl" },
{ },
};
MODULE_DEVICE_TABLE(of, dwc3_octeon_of_match);
static struct platform_driver dwc3_octeon_driver = {
.probe = dwc3_octeon_probe,
.remove = dwc3_octeon_remove,
.driver = {
.name = "dwc3-octeon",
.of_match_table = dwc3_octeon_of_match,
},
};
module_platform_driver(dwc3_octeon_driver);
MODULE_ALIAS("platform:dwc3-octeon");
MODULE_AUTHOR("Ladislav Michl <ladis@linux-mips.org>");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("DesignWare USB3 OCTEON III Glue Layer");
@@ -0,0 +1,196 @@
// SPDX-License-Identifier: GPL-2.0
/*
* dwc3-of-simple.c - OF glue layer for simple integrations
*
* Copyright (c) 2015 Texas Instruments Incorporated - https://www.ti.com
*
* Author: Felipe Balbi <balbi@ti.com>
*
* This is a combination of the old dwc3-qcom.c by Ivan T. Ivanov
* <iivanov@mm-sol.com> and the original patch adding support for Xilinx' SoC
* by Subbaraya Sundeep Bhatta <subbaraya.sundeep.bhatta@xilinx.com>
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/clk.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/pm_runtime.h>
#include <linux/reset.h>
struct dwc3_of_simple {
struct device *dev;
struct clk_bulk_data *clks;
int num_clocks;
struct reset_control *resets;
bool need_reset;
};
static int dwc3_of_simple_probe(struct platform_device *pdev)
{
struct dwc3_of_simple *simple;
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
int ret;
simple = devm_kzalloc(dev, sizeof(*simple), GFP_KERNEL);
if (!simple)
return -ENOMEM;
platform_set_drvdata(pdev, simple);
simple->dev = dev;
/*
* Some controllers need to toggle the usb3-otg reset before trying to
* initialize the PHY, otherwise the PHY times out.
*/
if (of_device_is_compatible(np, "rockchip,rk3399-dwc3"))
simple->need_reset = true;
simple->resets = of_reset_control_array_get_optional_exclusive(np);
if (IS_ERR(simple->resets)) {
ret = PTR_ERR(simple->resets);
dev_err(dev, "failed to get device resets, err=%d\n", ret);
return ret;
}
ret = reset_control_deassert(simple->resets);
if (ret)
goto err_resetc_put;
ret = clk_bulk_get_all(simple->dev, &simple->clks);
if (ret < 0)
goto err_resetc_assert;
simple->num_clocks = ret;
ret = clk_bulk_prepare_enable(simple->num_clocks, simple->clks);
if (ret)
goto err_clk_put_all;
ret = of_platform_populate(np, NULL, NULL, dev);
if (ret)
goto err_clk_disable;
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
pm_runtime_get_sync(dev);
return 0;
err_clk_disable:
clk_bulk_disable_unprepare(simple->num_clocks, simple->clks);
err_clk_put_all:
clk_bulk_put_all(simple->num_clocks, simple->clks);
err_resetc_assert:
reset_control_assert(simple->resets);
err_resetc_put:
reset_control_put(simple->resets);
return ret;
}
static void __dwc3_of_simple_teardown(struct dwc3_of_simple *simple)
{
of_platform_depopulate(simple->dev);
clk_bulk_disable_unprepare(simple->num_clocks, simple->clks);
clk_bulk_put_all(simple->num_clocks, simple->clks);
simple->num_clocks = 0;
reset_control_assert(simple->resets);
reset_control_put(simple->resets);
pm_runtime_disable(simple->dev);
pm_runtime_put_noidle(simple->dev);
pm_runtime_set_suspended(simple->dev);
}
static void dwc3_of_simple_remove(struct platform_device *pdev)
{
struct dwc3_of_simple *simple = platform_get_drvdata(pdev);
__dwc3_of_simple_teardown(simple);
}
static void dwc3_of_simple_shutdown(struct platform_device *pdev)
{
struct dwc3_of_simple *simple = platform_get_drvdata(pdev);
__dwc3_of_simple_teardown(simple);
}
static int __maybe_unused dwc3_of_simple_runtime_suspend(struct device *dev)
{
struct dwc3_of_simple *simple = dev_get_drvdata(dev);
clk_bulk_disable(simple->num_clocks, simple->clks);
return 0;
}
static int __maybe_unused dwc3_of_simple_runtime_resume(struct device *dev)
{
struct dwc3_of_simple *simple = dev_get_drvdata(dev);
return clk_bulk_enable(simple->num_clocks, simple->clks);
}
static int __maybe_unused dwc3_of_simple_suspend(struct device *dev)
{
struct dwc3_of_simple *simple = dev_get_drvdata(dev);
if (simple->need_reset)
reset_control_assert(simple->resets);
return 0;
}
static int __maybe_unused dwc3_of_simple_resume(struct device *dev)
{
struct dwc3_of_simple *simple = dev_get_drvdata(dev);
if (simple->need_reset)
reset_control_deassert(simple->resets);
return 0;
}
static const struct dev_pm_ops dwc3_of_simple_dev_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(dwc3_of_simple_suspend, dwc3_of_simple_resume)
SET_RUNTIME_PM_OPS(dwc3_of_simple_runtime_suspend,
dwc3_of_simple_runtime_resume, NULL)
};
static const struct of_device_id of_dwc3_simple_match[] = {
{ .compatible = "rockchip,rk3399-dwc3" },
{ .compatible = "sprd,sc9860-dwc3" },
{ .compatible = "allwinner,sun50i-h6-dwc3" },
{ .compatible = "hisilicon,hi3670-dwc3" },
{ .compatible = "hisilicon,hi3798mv200-dwc3" },
{ .compatible = "intel,keembay-dwc3" },
{ /* Sentinel */ }
};
MODULE_DEVICE_TABLE(of, of_dwc3_simple_match);
static struct platform_driver dwc3_of_simple_driver = {
.probe = dwc3_of_simple_probe,
.remove = dwc3_of_simple_remove,
.shutdown = dwc3_of_simple_shutdown,
.driver = {
.name = "dwc3-of-simple",
.of_match_table = of_dwc3_simple_match,
.pm = &dwc3_of_simple_dev_pm_ops,
},
};
module_platform_driver(dwc3_of_simple_driver);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("DesignWare USB3 OF Simple Glue Layer");
MODULE_AUTHOR("Felipe Balbi <balbi@ti.com>");
+626
View File
@@ -0,0 +1,626 @@
// SPDX-License-Identifier: GPL-2.0
/*
* dwc3-omap.c - OMAP Specific Glue layer
*
* Copyright (C) 2010-2011 Texas Instruments Incorporated - https://www.ti.com
*
* Authors: Felipe Balbi <balbi@ti.com>,
* Sebastian Andrzej Siewior <bigeasy@linutronix.de>
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/dma-mapping.h>
#include <linux/ioport.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/extcon.h>
#include <linux/regulator/consumer.h>
#include <linux/usb/otg.h>
/*
* All these registers belong to OMAP's Wrapper around the
* DesignWare USB3 Core.
*/
#define USBOTGSS_REVISION 0x0000
#define USBOTGSS_SYSCONFIG 0x0010
#define USBOTGSS_IRQ_EOI 0x0020
#define USBOTGSS_EOI_OFFSET 0x0008
#define USBOTGSS_IRQSTATUS_RAW_0 0x0024
#define USBOTGSS_IRQSTATUS_0 0x0028
#define USBOTGSS_IRQENABLE_SET_0 0x002c
#define USBOTGSS_IRQENABLE_CLR_0 0x0030
#define USBOTGSS_IRQ0_OFFSET 0x0004
#define USBOTGSS_IRQSTATUS_RAW_1 0x0030
#define USBOTGSS_IRQSTATUS_1 0x0034
#define USBOTGSS_IRQENABLE_SET_1 0x0038
#define USBOTGSS_IRQENABLE_CLR_1 0x003c
#define USBOTGSS_IRQSTATUS_RAW_2 0x0040
#define USBOTGSS_IRQSTATUS_2 0x0044
#define USBOTGSS_IRQENABLE_SET_2 0x0048
#define USBOTGSS_IRQENABLE_CLR_2 0x004c
#define USBOTGSS_IRQSTATUS_RAW_3 0x0050
#define USBOTGSS_IRQSTATUS_3 0x0054
#define USBOTGSS_IRQENABLE_SET_3 0x0058
#define USBOTGSS_IRQENABLE_CLR_3 0x005c
#define USBOTGSS_IRQSTATUS_EOI_MISC 0x0030
#define USBOTGSS_IRQSTATUS_RAW_MISC 0x0034
#define USBOTGSS_IRQSTATUS_MISC 0x0038
#define USBOTGSS_IRQENABLE_SET_MISC 0x003c
#define USBOTGSS_IRQENABLE_CLR_MISC 0x0040
#define USBOTGSS_IRQMISC_OFFSET 0x03fc
#define USBOTGSS_UTMI_OTG_STATUS 0x0080
#define USBOTGSS_UTMI_OTG_CTRL 0x0084
#define USBOTGSS_UTMI_OTG_OFFSET 0x0480
#define USBOTGSS_TXFIFO_DEPTH 0x0508
#define USBOTGSS_RXFIFO_DEPTH 0x050c
#define USBOTGSS_MMRAM_OFFSET 0x0100
#define USBOTGSS_FLADJ 0x0104
#define USBOTGSS_DEBUG_CFG 0x0108
#define USBOTGSS_DEBUG_DATA 0x010c
#define USBOTGSS_DEV_EBC_EN 0x0110
#define USBOTGSS_DEBUG_OFFSET 0x0600
/* SYSCONFIG REGISTER */
#define USBOTGSS_SYSCONFIG_DMADISABLE BIT(16)
/* IRQ_EOI REGISTER */
#define USBOTGSS_IRQ_EOI_LINE_NUMBER BIT(0)
/* IRQS0 BITS */
#define USBOTGSS_IRQO_COREIRQ_ST BIT(0)
/* IRQMISC BITS */
#define USBOTGSS_IRQMISC_DMADISABLECLR BIT(17)
#define USBOTGSS_IRQMISC_OEVT BIT(16)
#define USBOTGSS_IRQMISC_DRVVBUS_RISE BIT(13)
#define USBOTGSS_IRQMISC_CHRGVBUS_RISE BIT(12)
#define USBOTGSS_IRQMISC_DISCHRGVBUS_RISE BIT(11)
#define USBOTGSS_IRQMISC_IDPULLUP_RISE BIT(8)
#define USBOTGSS_IRQMISC_DRVVBUS_FALL BIT(5)
#define USBOTGSS_IRQMISC_CHRGVBUS_FALL BIT(4)
#define USBOTGSS_IRQMISC_DISCHRGVBUS_FALL BIT(3)
#define USBOTGSS_IRQMISC_IDPULLUP_FALL BIT(0)
/* UTMI_OTG_STATUS REGISTER */
#define USBOTGSS_UTMI_OTG_STATUS_DRVVBUS BIT(5)
#define USBOTGSS_UTMI_OTG_STATUS_CHRGVBUS BIT(4)
#define USBOTGSS_UTMI_OTG_STATUS_DISCHRGVBUS BIT(3)
#define USBOTGSS_UTMI_OTG_STATUS_IDPULLUP BIT(0)
/* UTMI_OTG_CTRL REGISTER */
#define USBOTGSS_UTMI_OTG_CTRL_SW_MODE BIT(31)
#define USBOTGSS_UTMI_OTG_CTRL_POWERPRESENT BIT(9)
#define USBOTGSS_UTMI_OTG_CTRL_TXBITSTUFFENABLE BIT(8)
#define USBOTGSS_UTMI_OTG_CTRL_IDDIG BIT(4)
#define USBOTGSS_UTMI_OTG_CTRL_SESSEND BIT(3)
#define USBOTGSS_UTMI_OTG_CTRL_SESSVALID BIT(2)
#define USBOTGSS_UTMI_OTG_CTRL_VBUSVALID BIT(1)
enum dwc3_omap_utmi_mode {
DWC3_OMAP_UTMI_MODE_UNKNOWN = 0,
DWC3_OMAP_UTMI_MODE_HW,
DWC3_OMAP_UTMI_MODE_SW,
};
struct dwc3_omap {
struct device *dev;
int irq;
void __iomem *base;
u32 utmi_otg_ctrl;
u32 utmi_otg_offset;
u32 irqmisc_offset;
u32 irq_eoi_offset;
u32 debug_offset;
u32 irq0_offset;
struct extcon_dev *edev;
struct notifier_block vbus_nb;
struct notifier_block id_nb;
struct regulator *vbus_reg;
};
enum omap_dwc3_vbus_id_status {
OMAP_DWC3_ID_FLOAT,
OMAP_DWC3_ID_GROUND,
OMAP_DWC3_VBUS_OFF,
OMAP_DWC3_VBUS_VALID,
};
static inline u32 dwc3_omap_readl(void __iomem *base, u32 offset)
{
return readl(base + offset);
}
static inline void dwc3_omap_writel(void __iomem *base, u32 offset, u32 value)
{
writel(value, base + offset);
}
static u32 dwc3_omap_read_utmi_ctrl(struct dwc3_omap *omap)
{
return dwc3_omap_readl(omap->base, USBOTGSS_UTMI_OTG_CTRL +
omap->utmi_otg_offset);
}
static void dwc3_omap_write_utmi_ctrl(struct dwc3_omap *omap, u32 value)
{
dwc3_omap_writel(omap->base, USBOTGSS_UTMI_OTG_CTRL +
omap->utmi_otg_offset, value);
}
static u32 dwc3_omap_read_irq0_status(struct dwc3_omap *omap)
{
return dwc3_omap_readl(omap->base, USBOTGSS_IRQSTATUS_RAW_0 -
omap->irq0_offset);
}
static void dwc3_omap_write_irq0_status(struct dwc3_omap *omap, u32 value)
{
dwc3_omap_writel(omap->base, USBOTGSS_IRQSTATUS_0 -
omap->irq0_offset, value);
}
static u32 dwc3_omap_read_irqmisc_status(struct dwc3_omap *omap)
{
return dwc3_omap_readl(omap->base, USBOTGSS_IRQSTATUS_RAW_MISC +
omap->irqmisc_offset);
}
static void dwc3_omap_write_irqmisc_status(struct dwc3_omap *omap, u32 value)
{
dwc3_omap_writel(omap->base, USBOTGSS_IRQSTATUS_MISC +
omap->irqmisc_offset, value);
}
static void dwc3_omap_write_irqmisc_set(struct dwc3_omap *omap, u32 value)
{
dwc3_omap_writel(omap->base, USBOTGSS_IRQENABLE_SET_MISC +
omap->irqmisc_offset, value);
}
static void dwc3_omap_write_irq0_set(struct dwc3_omap *omap, u32 value)
{
dwc3_omap_writel(omap->base, USBOTGSS_IRQENABLE_SET_0 -
omap->irq0_offset, value);
}
static void dwc3_omap_write_irqmisc_clr(struct dwc3_omap *omap, u32 value)
{
dwc3_omap_writel(omap->base, USBOTGSS_IRQENABLE_CLR_MISC +
omap->irqmisc_offset, value);
}
static void dwc3_omap_write_irq0_clr(struct dwc3_omap *omap, u32 value)
{
dwc3_omap_writel(omap->base, USBOTGSS_IRQENABLE_CLR_0 -
omap->irq0_offset, value);
}
static void dwc3_omap_set_mailbox(struct dwc3_omap *omap,
enum omap_dwc3_vbus_id_status status)
{
int ret;
u32 val;
switch (status) {
case OMAP_DWC3_ID_GROUND:
if (omap->vbus_reg) {
ret = regulator_enable(omap->vbus_reg);
if (ret) {
dev_err(omap->dev, "regulator enable failed\n");
return;
}
}
val = dwc3_omap_read_utmi_ctrl(omap);
val &= ~USBOTGSS_UTMI_OTG_CTRL_IDDIG;
dwc3_omap_write_utmi_ctrl(omap, val);
break;
case OMAP_DWC3_VBUS_VALID:
val = dwc3_omap_read_utmi_ctrl(omap);
val &= ~USBOTGSS_UTMI_OTG_CTRL_SESSEND;
val |= USBOTGSS_UTMI_OTG_CTRL_VBUSVALID
| USBOTGSS_UTMI_OTG_CTRL_SESSVALID;
dwc3_omap_write_utmi_ctrl(omap, val);
break;
case OMAP_DWC3_ID_FLOAT:
if (omap->vbus_reg && regulator_is_enabled(omap->vbus_reg))
regulator_disable(omap->vbus_reg);
val = dwc3_omap_read_utmi_ctrl(omap);
val |= USBOTGSS_UTMI_OTG_CTRL_IDDIG;
dwc3_omap_write_utmi_ctrl(omap, val);
break;
case OMAP_DWC3_VBUS_OFF:
val = dwc3_omap_read_utmi_ctrl(omap);
val &= ~(USBOTGSS_UTMI_OTG_CTRL_SESSVALID
| USBOTGSS_UTMI_OTG_CTRL_VBUSVALID);
val |= USBOTGSS_UTMI_OTG_CTRL_SESSEND;
dwc3_omap_write_utmi_ctrl(omap, val);
break;
default:
dev_WARN(omap->dev, "invalid state\n");
}
}
static void dwc3_omap_enable_irqs(struct dwc3_omap *omap);
static void dwc3_omap_disable_irqs(struct dwc3_omap *omap);
static irqreturn_t dwc3_omap_interrupt(int irq, void *_omap)
{
struct dwc3_omap *omap = _omap;
if (dwc3_omap_read_irqmisc_status(omap) ||
dwc3_omap_read_irq0_status(omap)) {
/* mask irqs */
dwc3_omap_disable_irqs(omap);
return IRQ_WAKE_THREAD;
}
return IRQ_NONE;
}
static irqreturn_t dwc3_omap_interrupt_thread(int irq, void *_omap)
{
struct dwc3_omap *omap = _omap;
u32 reg;
/* clear irq status flags */
reg = dwc3_omap_read_irqmisc_status(omap);
dwc3_omap_write_irqmisc_status(omap, reg);
reg = dwc3_omap_read_irq0_status(omap);
dwc3_omap_write_irq0_status(omap, reg);
/* unmask irqs */
dwc3_omap_enable_irqs(omap);
return IRQ_HANDLED;
}
static void dwc3_omap_enable_irqs(struct dwc3_omap *omap)
{
u32 reg;
/* enable all IRQs */
reg = USBOTGSS_IRQO_COREIRQ_ST;
dwc3_omap_write_irq0_set(omap, reg);
reg = (USBOTGSS_IRQMISC_OEVT |
USBOTGSS_IRQMISC_DRVVBUS_RISE |
USBOTGSS_IRQMISC_CHRGVBUS_RISE |
USBOTGSS_IRQMISC_DISCHRGVBUS_RISE |
USBOTGSS_IRQMISC_IDPULLUP_RISE |
USBOTGSS_IRQMISC_DRVVBUS_FALL |
USBOTGSS_IRQMISC_CHRGVBUS_FALL |
USBOTGSS_IRQMISC_DISCHRGVBUS_FALL |
USBOTGSS_IRQMISC_IDPULLUP_FALL);
dwc3_omap_write_irqmisc_set(omap, reg);
}
static void dwc3_omap_disable_irqs(struct dwc3_omap *omap)
{
u32 reg;
/* disable all IRQs */
reg = USBOTGSS_IRQO_COREIRQ_ST;
dwc3_omap_write_irq0_clr(omap, reg);
reg = (USBOTGSS_IRQMISC_OEVT |
USBOTGSS_IRQMISC_DRVVBUS_RISE |
USBOTGSS_IRQMISC_CHRGVBUS_RISE |
USBOTGSS_IRQMISC_DISCHRGVBUS_RISE |
USBOTGSS_IRQMISC_IDPULLUP_RISE |
USBOTGSS_IRQMISC_DRVVBUS_FALL |
USBOTGSS_IRQMISC_CHRGVBUS_FALL |
USBOTGSS_IRQMISC_DISCHRGVBUS_FALL |
USBOTGSS_IRQMISC_IDPULLUP_FALL);
dwc3_omap_write_irqmisc_clr(omap, reg);
}
static int dwc3_omap_id_notifier(struct notifier_block *nb,
unsigned long event, void *ptr)
{
struct dwc3_omap *omap = container_of(nb, struct dwc3_omap, id_nb);
if (event)
dwc3_omap_set_mailbox(omap, OMAP_DWC3_ID_GROUND);
else
dwc3_omap_set_mailbox(omap, OMAP_DWC3_ID_FLOAT);
return NOTIFY_DONE;
}
static int dwc3_omap_vbus_notifier(struct notifier_block *nb,
unsigned long event, void *ptr)
{
struct dwc3_omap *omap = container_of(nb, struct dwc3_omap, vbus_nb);
if (event)
dwc3_omap_set_mailbox(omap, OMAP_DWC3_VBUS_VALID);
else
dwc3_omap_set_mailbox(omap, OMAP_DWC3_VBUS_OFF);
return NOTIFY_DONE;
}
static void dwc3_omap_map_offset(struct dwc3_omap *omap)
{
struct device_node *node = omap->dev->of_node;
/*
* Differentiate between OMAP5 and AM437x.
*
* For OMAP5(ES2.0) and AM437x wrapper revision is same, even
* though there are changes in wrapper register offsets.
*
* Using dt compatible to differentiate AM437x.
*/
if (of_device_is_compatible(node, "ti,am437x-dwc3")) {
omap->irq_eoi_offset = USBOTGSS_EOI_OFFSET;
omap->irq0_offset = USBOTGSS_IRQ0_OFFSET;
omap->irqmisc_offset = USBOTGSS_IRQMISC_OFFSET;
omap->utmi_otg_offset = USBOTGSS_UTMI_OTG_OFFSET;
omap->debug_offset = USBOTGSS_DEBUG_OFFSET;
}
}
static void dwc3_omap_set_utmi_mode(struct dwc3_omap *omap)
{
u32 reg;
struct device_node *node = omap->dev->of_node;
u32 utmi_mode = 0;
reg = dwc3_omap_read_utmi_ctrl(omap);
of_property_read_u32(node, "utmi-mode", &utmi_mode);
switch (utmi_mode) {
case DWC3_OMAP_UTMI_MODE_SW:
reg |= USBOTGSS_UTMI_OTG_CTRL_SW_MODE;
break;
case DWC3_OMAP_UTMI_MODE_HW:
reg &= ~USBOTGSS_UTMI_OTG_CTRL_SW_MODE;
break;
default:
dev_WARN(omap->dev, "UNKNOWN utmi mode %d\n", utmi_mode);
}
dwc3_omap_write_utmi_ctrl(omap, reg);
}
static int dwc3_omap_extcon_register(struct dwc3_omap *omap)
{
int ret;
struct device_node *node = omap->dev->of_node;
struct extcon_dev *edev;
if (of_property_present(node, "extcon")) {
edev = extcon_get_edev_by_phandle(omap->dev, 0);
if (IS_ERR(edev)) {
dev_vdbg(omap->dev, "couldn't get extcon device\n");
return -EPROBE_DEFER;
}
omap->vbus_nb.notifier_call = dwc3_omap_vbus_notifier;
ret = devm_extcon_register_notifier(omap->dev, edev,
EXTCON_USB, &omap->vbus_nb);
if (ret < 0)
dev_vdbg(omap->dev, "failed to register notifier for USB\n");
omap->id_nb.notifier_call = dwc3_omap_id_notifier;
ret = devm_extcon_register_notifier(omap->dev, edev,
EXTCON_USB_HOST, &omap->id_nb);
if (ret < 0)
dev_vdbg(omap->dev, "failed to register notifier for USB-HOST\n");
if (extcon_get_state(edev, EXTCON_USB) == true)
dwc3_omap_set_mailbox(omap, OMAP_DWC3_VBUS_VALID);
else
dwc3_omap_set_mailbox(omap, OMAP_DWC3_VBUS_OFF);
if (extcon_get_state(edev, EXTCON_USB_HOST) == true)
dwc3_omap_set_mailbox(omap, OMAP_DWC3_ID_GROUND);
else
dwc3_omap_set_mailbox(omap, OMAP_DWC3_ID_FLOAT);
omap->edev = edev;
}
return 0;
}
static int dwc3_omap_probe(struct platform_device *pdev)
{
struct device_node *node = pdev->dev.of_node;
struct dwc3_omap *omap;
struct device *dev = &pdev->dev;
struct regulator *vbus_reg;
int ret;
int irq;
void __iomem *base;
if (!node) {
dev_err(dev, "device node not found\n");
return -EINVAL;
}
omap = devm_kzalloc(dev, sizeof(*omap), GFP_KERNEL);
if (!omap)
return -ENOMEM;
platform_set_drvdata(pdev, omap);
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(base))
return PTR_ERR(base);
vbus_reg = devm_regulator_get_optional(dev, "vbus");
if (IS_ERR(vbus_reg)) {
if (PTR_ERR(vbus_reg) != -ENODEV)
return dev_err_probe(dev, PTR_ERR(vbus_reg), "vbus init failed\n");
vbus_reg = NULL;
}
omap->dev = dev;
omap->irq = irq;
omap->base = base;
omap->vbus_reg = vbus_reg;
pm_runtime_enable(dev);
ret = pm_runtime_get_sync(dev);
if (ret < 0) {
dev_err(dev, "get_sync failed with err %d\n", ret);
goto err1;
}
dwc3_omap_map_offset(omap);
dwc3_omap_set_utmi_mode(omap);
ret = dwc3_omap_extcon_register(omap);
if (ret < 0)
goto err1;
ret = of_platform_populate(node, NULL, NULL, dev);
if (ret) {
dev_err(&pdev->dev, "failed to create dwc3 core\n");
goto err1;
}
ret = devm_request_threaded_irq(dev, omap->irq, dwc3_omap_interrupt,
dwc3_omap_interrupt_thread, IRQF_SHARED,
"dwc3-omap", omap);
if (ret) {
dev_err(dev, "failed to request IRQ #%d --> %d\n",
omap->irq, ret);
goto err2;
}
dwc3_omap_enable_irqs(omap);
return 0;
err2:
of_platform_depopulate(dev);
err1:
pm_runtime_put_sync(dev);
pm_runtime_disable(dev);
return ret;
}
static void dwc3_omap_remove(struct platform_device *pdev)
{
struct dwc3_omap *omap = platform_get_drvdata(pdev);
dwc3_omap_disable_irqs(omap);
disable_irq(omap->irq);
of_platform_depopulate(omap->dev);
pm_runtime_put_sync(&pdev->dev);
pm_runtime_disable(&pdev->dev);
}
static const struct of_device_id of_dwc3_match[] = {
{
.compatible = "ti,dwc3"
},
{
.compatible = "ti,am437x-dwc3"
},
{ },
};
MODULE_DEVICE_TABLE(of, of_dwc3_match);
#ifdef CONFIG_PM_SLEEP
static int dwc3_omap_suspend(struct device *dev)
{
struct dwc3_omap *omap = dev_get_drvdata(dev);
omap->utmi_otg_ctrl = dwc3_omap_read_utmi_ctrl(omap);
dwc3_omap_disable_irqs(omap);
return 0;
}
static int dwc3_omap_resume(struct device *dev)
{
struct dwc3_omap *omap = dev_get_drvdata(dev);
dwc3_omap_write_utmi_ctrl(omap, omap->utmi_otg_ctrl);
dwc3_omap_enable_irqs(omap);
pm_runtime_disable(dev);
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
return 0;
}
static void dwc3_omap_complete(struct device *dev)
{
struct dwc3_omap *omap = dev_get_drvdata(dev);
if (extcon_get_state(omap->edev, EXTCON_USB))
dwc3_omap_set_mailbox(omap, OMAP_DWC3_VBUS_VALID);
else
dwc3_omap_set_mailbox(omap, OMAP_DWC3_VBUS_OFF);
if (extcon_get_state(omap->edev, EXTCON_USB_HOST))
dwc3_omap_set_mailbox(omap, OMAP_DWC3_ID_GROUND);
else
dwc3_omap_set_mailbox(omap, OMAP_DWC3_ID_FLOAT);
}
static const struct dev_pm_ops dwc3_omap_dev_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(dwc3_omap_suspend, dwc3_omap_resume)
.complete = dwc3_omap_complete,
};
#define DEV_PM_OPS (&dwc3_omap_dev_pm_ops)
#else
#define DEV_PM_OPS NULL
#endif /* CONFIG_PM_SLEEP */
static struct platform_driver dwc3_omap_driver = {
.probe = dwc3_omap_probe,
.remove = dwc3_omap_remove,
.driver = {
.name = "omap-dwc3",
.of_match_table = of_dwc3_match,
.pm = DEV_PM_OPS,
},
};
module_platform_driver(dwc3_omap_driver);
MODULE_ALIAS("platform:omap-dwc3");
MODULE_AUTHOR("Felipe Balbi <balbi@ti.com>");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("DesignWare USB3 OMAP Glue Layer");
+552
View File
@@ -0,0 +1,552 @@
// SPDX-License-Identifier: GPL-2.0
/*
* dwc3-pci.c - PCI Specific glue layer
*
* Copyright (C) 2010-2011 Texas Instruments Incorporated - https://www.ti.com
*
* Authors: Felipe Balbi <balbi@ti.com>,
* Sebastian Andrzej Siewior <bigeasy@linutronix.de>
*/
#include <linux/dmi.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/pci.h>
#include <linux/workqueue.h>
#include <linux/pm_runtime.h>
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>
#include <linux/gpio/machine.h>
#include <linux/acpi.h>
#include <linux/delay.h>
#define PCI_DEVICE_ID_INTEL_CMLLP 0x02ee
#define PCI_DEVICE_ID_INTEL_CMLH 0x06ee
#define PCI_DEVICE_ID_INTEL_BXT 0x0aaa
#define PCI_DEVICE_ID_INTEL_BYT 0x0f37
#define PCI_DEVICE_ID_INTEL_MRFLD 0x119e
#define PCI_DEVICE_ID_INTEL_BXT_M 0x1aaa
#define PCI_DEVICE_ID_INTEL_BSW 0x22b7
#define PCI_DEVICE_ID_INTEL_GLK 0x31aa
#define PCI_DEVICE_ID_INTEL_ICLLP 0x34ee
#define PCI_DEVICE_ID_INTEL_TGPH 0x43ee
#define PCI_DEVICE_ID_INTEL_ADL 0x460e
#define PCI_DEVICE_ID_INTEL_ADLN 0x465e
#define PCI_DEVICE_ID_INTEL_EHL 0x4b7e
#define PCI_DEVICE_ID_INTEL_WCL 0x4d7e
#define PCI_DEVICE_ID_INTEL_JSP 0x4dee
#define PCI_DEVICE_ID_INTEL_ADL_PCH 0x51ee
#define PCI_DEVICE_ID_INTEL_ADLN_PCH 0x54ee
#define PCI_DEVICE_ID_INTEL_APL 0x5aaa
#define PCI_DEVICE_ID_INTEL_NVLS_PCH 0x6e6f
#define PCI_DEVICE_ID_INTEL_ARLH_PCH 0x777e
#define PCI_DEVICE_ID_INTEL_RPLS 0x7a61
#define PCI_DEVICE_ID_INTEL_MTL 0x7e7e
#define PCI_DEVICE_ID_INTEL_ADLS 0x7ae1
#define PCI_DEVICE_ID_INTEL_MTLM 0x7eb1
#define PCI_DEVICE_ID_INTEL_MTLP 0x7ec1
#define PCI_DEVICE_ID_INTEL_MTLS 0x7f6f
#define PCI_DEVICE_ID_INTEL_TGL 0x9a15
#define PCI_DEVICE_ID_INTEL_SPTLP 0x9d30
#define PCI_DEVICE_ID_INTEL_CNPLP 0x9dee
#define PCI_DEVICE_ID_INTEL_TGPLP 0xa0ee
#define PCI_DEVICE_ID_INTEL_SPTH 0xa130
#define PCI_DEVICE_ID_INTEL_KBP 0xa2b0
#define PCI_DEVICE_ID_INTEL_CNPH 0xa36e
#define PCI_DEVICE_ID_INTEL_CNPV 0xa3b0
#define PCI_DEVICE_ID_INTEL_RPL 0xa70e
#define PCI_DEVICE_ID_INTEL_NVLH 0xd37f
#define PCI_DEVICE_ID_INTEL_PTLH 0xe332
#define PCI_DEVICE_ID_INTEL_PTLH_PCH 0xe37e
#define PCI_DEVICE_ID_INTEL_PTLU 0xe432
#define PCI_DEVICE_ID_INTEL_PTLU_PCH 0xe47e
#define PCI_DEVICE_ID_AMD_MR 0x163a
#define PCI_INTEL_BXT_DSM_GUID "732b85d5-b7a7-4a1b-9ba0-4bbd00ffd511"
#define PCI_INTEL_BXT_FUNC_PMU_PWR 4
#define PCI_INTEL_BXT_STATE_D0 0
#define PCI_INTEL_BXT_STATE_D3 3
#define GP_RWBAR 1
#define GP_RWREG1 0xa0
#define GP_RWREG1_ULPI_REFCLK_DISABLE (1 << 17)
/**
* struct dwc3_pci - Driver private structure
* @dwc3: child dwc3 platform_device
* @pci: our link to PCI bus
* @guid: _DSM GUID
* @has_dsm_for_pm: true for devices which need to run _DSM on runtime PM
* @wakeup_work: work for asynchronous resume
*/
struct dwc3_pci {
struct platform_device *dwc3;
struct pci_dev *pci;
guid_t guid;
unsigned int has_dsm_for_pm:1;
struct work_struct wakeup_work;
};
static const struct acpi_gpio_params reset_gpios = { 0, 0, false };
static const struct acpi_gpio_params cs_gpios = { 1, 0, false };
static const struct acpi_gpio_mapping acpi_dwc3_byt_gpios[] = {
{ "reset-gpios", &reset_gpios, 1 },
{ "cs-gpios", &cs_gpios, 1 },
{ },
};
static struct gpiod_lookup_table platform_bytcr_gpios = {
.dev_id = "0000:00:16.0",
.table = {
GPIO_LOOKUP("INT33FC:00", 54, "cs", GPIO_ACTIVE_HIGH),
GPIO_LOOKUP("INT33FC:02", 14, "reset", GPIO_ACTIVE_HIGH),
{}
},
};
static int dwc3_byt_enable_ulpi_refclock(struct pci_dev *pci)
{
void __iomem *reg;
u32 value;
reg = pcim_iomap(pci, GP_RWBAR, 0);
if (!reg)
return -ENOMEM;
value = readl(reg + GP_RWREG1);
if (!(value & GP_RWREG1_ULPI_REFCLK_DISABLE))
goto unmap; /* ULPI refclk already enabled */
value &= ~GP_RWREG1_ULPI_REFCLK_DISABLE;
writel(value, reg + GP_RWREG1);
/* This comes from the Intel Android x86 tree w/o any explanation */
msleep(100);
unmap:
pcim_iounmap(pci, reg);
return 0;
}
static const struct property_entry dwc3_pci_intel_properties[] = {
PROPERTY_ENTRY_STRING("dr_mode", "peripheral"),
PROPERTY_ENTRY_BOOL("linux,sysdev_is_parent"),
{}
};
static const struct property_entry dwc3_pci_intel_phy_charger_detect_properties[] = {
PROPERTY_ENTRY_STRING("dr_mode", "peripheral"),
PROPERTY_ENTRY_BOOL("snps,dis_u2_susphy_quirk"),
PROPERTY_ENTRY_BOOL("linux,phy_charger_detect"),
PROPERTY_ENTRY_BOOL("linux,sysdev_is_parent"),
{}
};
static const struct property_entry dwc3_pci_intel_byt_properties[] = {
PROPERTY_ENTRY_STRING("dr_mode", "peripheral"),
PROPERTY_ENTRY_BOOL("snps,dis_u2_susphy_quirk"),
PROPERTY_ENTRY_BOOL("linux,sysdev_is_parent"),
{}
};
/*
* Intel Merrifield SoC uses these endpoints for tracing and they cannot
* be re-allocated if being used because the side band flow control signals
* are hard wired to certain endpoints:
* - 1 High BW Bulk IN (IN#1) (RTIT)
* - 1 1KB BW Bulk IN (IN#8) + 1 1KB BW Bulk OUT (Run Control) (OUT#8)
*/
static const u8 dwc3_pci_mrfld_reserved_endpoints[] = { 3, 16, 17 };
static const struct property_entry dwc3_pci_mrfld_properties[] = {
PROPERTY_ENTRY_STRING("dr_mode", "otg"),
PROPERTY_ENTRY_STRING("linux,extcon-name", "mrfld_bcove_pwrsrc"),
PROPERTY_ENTRY_BOOL("snps,dis_u3_susphy_quirk"),
PROPERTY_ENTRY_BOOL("snps,dis_u2_susphy_quirk"),
PROPERTY_ENTRY_U8_ARRAY("snps,reserved-endpoints", dwc3_pci_mrfld_reserved_endpoints),
PROPERTY_ENTRY_BOOL("snps,usb2-gadget-lpm-disable"),
PROPERTY_ENTRY_BOOL("linux,sysdev_is_parent"),
{}
};
static const struct property_entry dwc3_pci_amd_properties[] = {
PROPERTY_ENTRY_BOOL("snps,has-lpm-erratum"),
PROPERTY_ENTRY_U8("snps,lpm-nyet-threshold", 0xf),
PROPERTY_ENTRY_BOOL("snps,u2exit_lfps_quirk"),
PROPERTY_ENTRY_BOOL("snps,u2ss_inp3_quirk"),
PROPERTY_ENTRY_BOOL("snps,req_p1p2p3_quirk"),
PROPERTY_ENTRY_BOOL("snps,del_p1p2p3_quirk"),
PROPERTY_ENTRY_BOOL("snps,del_phy_power_chg_quirk"),
PROPERTY_ENTRY_BOOL("snps,lfps_filter_quirk"),
PROPERTY_ENTRY_BOOL("snps,rx_detect_poll_quirk"),
PROPERTY_ENTRY_BOOL("snps,tx_de_emphasis_quirk"),
PROPERTY_ENTRY_U8("snps,tx_de_emphasis", 1),
/* FIXME these quirks should be removed when AMD NL tapes out */
PROPERTY_ENTRY_BOOL("snps,disable_scramble_quirk"),
PROPERTY_ENTRY_BOOL("snps,dis_u3_susphy_quirk"),
PROPERTY_ENTRY_BOOL("snps,dis_u2_susphy_quirk"),
PROPERTY_ENTRY_BOOL("linux,sysdev_is_parent"),
{}
};
static const struct property_entry dwc3_pci_mr_properties[] = {
PROPERTY_ENTRY_STRING("dr_mode", "otg"),
PROPERTY_ENTRY_BOOL("usb-role-switch"),
PROPERTY_ENTRY_STRING("role-switch-default-mode", "host"),
PROPERTY_ENTRY_BOOL("linux,sysdev_is_parent"),
{}
};
static const struct software_node dwc3_pci_intel_swnode = {
.properties = dwc3_pci_intel_properties,
};
static const struct software_node dwc3_pci_intel_phy_charger_detect_swnode = {
.properties = dwc3_pci_intel_phy_charger_detect_properties,
};
static const struct software_node dwc3_pci_intel_byt_swnode = {
.properties = dwc3_pci_intel_byt_properties,
};
static const struct software_node dwc3_pci_intel_mrfld_swnode = {
.properties = dwc3_pci_mrfld_properties,
};
static const struct software_node dwc3_pci_amd_swnode = {
.properties = dwc3_pci_amd_properties,
};
static const struct software_node dwc3_pci_amd_mr_swnode = {
.properties = dwc3_pci_mr_properties,
};
static int dwc3_pci_quirks(struct dwc3_pci *dwc,
const struct software_node *swnode)
{
struct pci_dev *pdev = dwc->pci;
if (pdev->vendor == PCI_VENDOR_ID_INTEL) {
if (pdev->device == PCI_DEVICE_ID_INTEL_BXT ||
pdev->device == PCI_DEVICE_ID_INTEL_BXT_M ||
pdev->device == PCI_DEVICE_ID_INTEL_EHL) {
guid_parse(PCI_INTEL_BXT_DSM_GUID, &dwc->guid);
dwc->has_dsm_for_pm = true;
}
if (pdev->device == PCI_DEVICE_ID_INTEL_BYT) {
struct gpio_desc *gpio;
const char *bios_ver;
int ret;
/* On BYT the FW does not always enable the refclock */
ret = dwc3_byt_enable_ulpi_refclock(pdev);
if (ret)
return ret;
ret = devm_acpi_dev_add_driver_gpios(&pdev->dev,
acpi_dwc3_byt_gpios);
if (ret)
dev_dbg(&pdev->dev, "failed to add mapping table\n");
/*
* A lot of BYT devices lack ACPI resource entries for
* the GPIOs. If the ACPI entry for the GPIO controller
* is present add a fallback mapping to the reference
* design GPIOs which all boards seem to use.
*/
if (acpi_dev_present("INT33FC", NULL, -1))
gpiod_add_lookup_table(&platform_bytcr_gpios);
/*
* These GPIOs will turn on the USB2 PHY. Note that we have to
* put the gpio descriptors again here because the phy driver
* might want to grab them, too.
*/
gpio = gpiod_get_optional(&pdev->dev, "cs", GPIOD_OUT_LOW);
if (IS_ERR(gpio))
return PTR_ERR(gpio);
gpiod_set_value_cansleep(gpio, 1);
gpiod_put(gpio);
gpio = gpiod_get_optional(&pdev->dev, "reset", GPIOD_OUT_LOW);
if (IS_ERR(gpio))
return PTR_ERR(gpio);
if (gpio) {
gpiod_set_value_cansleep(gpio, 1);
gpiod_put(gpio);
usleep_range(10000, 11000);
}
/*
* Make the pdev name predictable (only 1 DWC3 on BYT)
* and patch the phy dev-name into the lookup table so
* that the phy-driver can get the GPIOs.
*/
dwc->dwc3->id = PLATFORM_DEVID_NONE;
platform_bytcr_gpios.dev_id = "dwc3.ulpi";
/*
* Some Android tablets with a Crystal Cove PMIC
* (INT33FD), rely on the TUSB1211 phy for charger
* detection. These can be identified by them _not_
* using the standard ACPI battery and ac drivers.
*/
bios_ver = dmi_get_system_info(DMI_BIOS_VERSION);
if (acpi_dev_present("INT33FD", "1", 2) &&
acpi_quirk_skip_acpi_ac_and_battery() &&
/* Lenovo Yoga Tablet 2 Pro 1380 uses LC824206XA instead */
!(bios_ver &&
strstarts(bios_ver, "BLADE_21.X64.0005.R00.1504101516"))) {
dev_info(&pdev->dev, "Using TUSB1211 phy for charger detection\n");
swnode = &dwc3_pci_intel_phy_charger_detect_swnode;
}
}
}
return device_add_software_node(&dwc->dwc3->dev, swnode);
}
#ifdef CONFIG_PM
static void dwc3_pci_resume_work(struct work_struct *work)
{
struct dwc3_pci *dwc = container_of(work, struct dwc3_pci, wakeup_work);
struct platform_device *dwc3 = dwc->dwc3;
int ret;
ret = pm_runtime_get_sync(&dwc3->dev);
if (ret < 0) {
pm_runtime_put_sync_autosuspend(&dwc3->dev);
return;
}
pm_runtime_put_sync_autosuspend(&dwc3->dev);
}
#endif
static int dwc3_pci_probe(struct pci_dev *pci, const struct pci_device_id *id)
{
struct dwc3_pci *dwc;
struct resource res[2];
int ret;
struct device *dev = &pci->dev;
ret = pcim_enable_device(pci);
if (ret) {
dev_err(dev, "failed to enable pci device\n");
return -ENODEV;
}
pci_set_master(pci);
dwc = devm_kzalloc(dev, sizeof(*dwc), GFP_KERNEL);
if (!dwc)
return -ENOMEM;
dwc->dwc3 = platform_device_alloc("dwc3", PLATFORM_DEVID_AUTO);
if (!dwc->dwc3)
return -ENOMEM;
memset(res, 0x00, sizeof(struct resource) * ARRAY_SIZE(res));
res[0].start = pci_resource_start(pci, 0);
res[0].end = pci_resource_end(pci, 0);
res[0].name = "dwc_usb3";
res[0].flags = IORESOURCE_MEM;
res[1].start = pci->irq;
res[1].name = "dwc_usb3";
res[1].flags = IORESOURCE_IRQ;
ret = platform_device_add_resources(dwc->dwc3, res, ARRAY_SIZE(res));
if (ret) {
dev_err(dev, "couldn't add resources to dwc3 device\n");
goto err;
}
dwc->pci = pci;
dwc->dwc3->dev.parent = dev;
ACPI_COMPANION_SET(&dwc->dwc3->dev, ACPI_COMPANION(dev));
ret = dwc3_pci_quirks(dwc, (void *)id->driver_data);
if (ret)
goto err;
ret = platform_device_add(dwc->dwc3);
if (ret) {
dev_err(dev, "failed to register dwc3 device\n");
goto err;
}
device_init_wakeup(dev, true);
pci_set_drvdata(pci, dwc);
pm_runtime_put(dev);
#ifdef CONFIG_PM
INIT_WORK(&dwc->wakeup_work, dwc3_pci_resume_work);
#endif
return 0;
err:
device_remove_software_node(&dwc->dwc3->dev);
platform_device_put(dwc->dwc3);
return ret;
}
static void dwc3_pci_remove(struct pci_dev *pci)
{
struct dwc3_pci *dwc = pci_get_drvdata(pci);
struct pci_dev *pdev = dwc->pci;
if (pdev->device == PCI_DEVICE_ID_INTEL_BYT)
gpiod_remove_lookup_table(&platform_bytcr_gpios);
#ifdef CONFIG_PM
cancel_work_sync(&dwc->wakeup_work);
#endif
device_init_wakeup(&pci->dev, false);
pm_runtime_get(&pci->dev);
device_remove_software_node(&dwc->dwc3->dev);
platform_device_unregister(dwc->dwc3);
}
static const struct pci_device_id dwc3_pci_id_table[] = {
{ PCI_DEVICE_DATA(INTEL, CMLLP, &dwc3_pci_intel_swnode) },
{ PCI_DEVICE_DATA(INTEL, CMLH, &dwc3_pci_intel_swnode) },
{ PCI_DEVICE_DATA(INTEL, BXT, &dwc3_pci_intel_swnode) },
{ PCI_DEVICE_DATA(INTEL, BYT, &dwc3_pci_intel_byt_swnode) },
{ PCI_DEVICE_DATA(INTEL, MRFLD, &dwc3_pci_intel_mrfld_swnode) },
{ PCI_DEVICE_DATA(INTEL, BXT_M, &dwc3_pci_intel_swnode) },
{ PCI_DEVICE_DATA(INTEL, BSW, &dwc3_pci_intel_swnode) },
{ PCI_DEVICE_DATA(INTEL, GLK, &dwc3_pci_intel_swnode) },
{ PCI_DEVICE_DATA(INTEL, ICLLP, &dwc3_pci_intel_swnode) },
{ PCI_DEVICE_DATA(INTEL, TGPH, &dwc3_pci_intel_swnode) },
{ PCI_DEVICE_DATA(INTEL, ADL, &dwc3_pci_intel_swnode) },
{ PCI_DEVICE_DATA(INTEL, ADLN, &dwc3_pci_intel_swnode) },
{ PCI_DEVICE_DATA(INTEL, EHL, &dwc3_pci_intel_swnode) },
{ PCI_DEVICE_DATA(INTEL, WCL, &dwc3_pci_intel_swnode) },
{ PCI_DEVICE_DATA(INTEL, JSP, &dwc3_pci_intel_swnode) },
{ PCI_DEVICE_DATA(INTEL, ADL_PCH, &dwc3_pci_intel_swnode) },
{ PCI_DEVICE_DATA(INTEL, ADLN_PCH, &dwc3_pci_intel_swnode) },
{ PCI_DEVICE_DATA(INTEL, APL, &dwc3_pci_intel_swnode) },
{ PCI_DEVICE_DATA(INTEL, NVLS_PCH, &dwc3_pci_intel_swnode) },
{ PCI_DEVICE_DATA(INTEL, ARLH_PCH, &dwc3_pci_intel_swnode) },
{ PCI_DEVICE_DATA(INTEL, RPLS, &dwc3_pci_intel_swnode) },
{ PCI_DEVICE_DATA(INTEL, MTL, &dwc3_pci_intel_swnode) },
{ PCI_DEVICE_DATA(INTEL, ADLS, &dwc3_pci_intel_swnode) },
{ PCI_DEVICE_DATA(INTEL, MTLM, &dwc3_pci_intel_swnode) },
{ PCI_DEVICE_DATA(INTEL, MTLP, &dwc3_pci_intel_swnode) },
{ PCI_DEVICE_DATA(INTEL, MTLS, &dwc3_pci_intel_swnode) },
{ PCI_DEVICE_DATA(INTEL, TGL, &dwc3_pci_intel_swnode) },
{ PCI_DEVICE_DATA(INTEL, SPTLP, &dwc3_pci_intel_swnode) },
{ PCI_DEVICE_DATA(INTEL, CNPLP, &dwc3_pci_intel_swnode) },
{ PCI_DEVICE_DATA(INTEL, TGPLP, &dwc3_pci_intel_swnode) },
{ PCI_DEVICE_DATA(INTEL, SPTH, &dwc3_pci_intel_swnode) },
{ PCI_DEVICE_DATA(INTEL, KBP, &dwc3_pci_intel_swnode) },
{ PCI_DEVICE_DATA(INTEL, CNPH, &dwc3_pci_intel_swnode) },
{ PCI_DEVICE_DATA(INTEL, CNPV, &dwc3_pci_intel_swnode) },
{ PCI_DEVICE_DATA(INTEL, RPL, &dwc3_pci_intel_swnode) },
{ PCI_DEVICE_DATA(INTEL, NVLH, &dwc3_pci_intel_swnode) },
{ PCI_DEVICE_DATA(INTEL, PTLH, &dwc3_pci_intel_swnode) },
{ PCI_DEVICE_DATA(INTEL, PTLH_PCH, &dwc3_pci_intel_swnode) },
{ PCI_DEVICE_DATA(INTEL, PTLU, &dwc3_pci_intel_swnode) },
{ PCI_DEVICE_DATA(INTEL, PTLU_PCH, &dwc3_pci_intel_swnode) },
{ PCI_DEVICE_DATA(AMD, NL_USB, &dwc3_pci_amd_swnode) },
{ PCI_DEVICE_DATA(AMD, MR, &dwc3_pci_amd_mr_swnode) },
{ } /* Terminating Entry */
};
MODULE_DEVICE_TABLE(pci, dwc3_pci_id_table);
#if defined(CONFIG_PM) || defined(CONFIG_PM_SLEEP)
static int dwc3_pci_dsm(struct dwc3_pci *dwc, int param)
{
union acpi_object *obj;
union acpi_object tmp;
union acpi_object argv4 = ACPI_INIT_DSM_ARGV4(1, &tmp);
if (!dwc->has_dsm_for_pm)
return 0;
tmp.type = ACPI_TYPE_INTEGER;
tmp.integer.value = param;
obj = acpi_evaluate_dsm(ACPI_HANDLE(&dwc->pci->dev), &dwc->guid,
1, PCI_INTEL_BXT_FUNC_PMU_PWR, &argv4);
if (!obj) {
dev_err(&dwc->pci->dev, "failed to evaluate _DSM\n");
return -EIO;
}
ACPI_FREE(obj);
return 0;
}
#endif /* CONFIG_PM || CONFIG_PM_SLEEP */
#ifdef CONFIG_PM
static int dwc3_pci_runtime_suspend(struct device *dev)
{
struct dwc3_pci *dwc = dev_get_drvdata(dev);
if (device_can_wakeup(dev))
return dwc3_pci_dsm(dwc, PCI_INTEL_BXT_STATE_D3);
return -EBUSY;
}
static int dwc3_pci_runtime_resume(struct device *dev)
{
struct dwc3_pci *dwc = dev_get_drvdata(dev);
int ret;
ret = dwc3_pci_dsm(dwc, PCI_INTEL_BXT_STATE_D0);
if (ret)
return ret;
queue_work(pm_wq, &dwc->wakeup_work);
return 0;
}
#endif /* CONFIG_PM */
#ifdef CONFIG_PM_SLEEP
static int dwc3_pci_suspend(struct device *dev)
{
struct dwc3_pci *dwc = dev_get_drvdata(dev);
return dwc3_pci_dsm(dwc, PCI_INTEL_BXT_STATE_D3);
}
static int dwc3_pci_resume(struct device *dev)
{
struct dwc3_pci *dwc = dev_get_drvdata(dev);
return dwc3_pci_dsm(dwc, PCI_INTEL_BXT_STATE_D0);
}
#endif /* CONFIG_PM_SLEEP */
static const struct dev_pm_ops dwc3_pci_dev_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(dwc3_pci_suspend, dwc3_pci_resume)
SET_RUNTIME_PM_OPS(dwc3_pci_runtime_suspend, dwc3_pci_runtime_resume,
NULL)
};
static struct pci_driver dwc3_pci_driver = {
.name = "dwc3-pci",
.id_table = dwc3_pci_id_table,
.probe = dwc3_pci_probe,
.remove = dwc3_pci_remove,
.driver = {
.pm = &dwc3_pci_dev_pm_ops,
}
};
MODULE_AUTHOR("Felipe Balbi <balbi@ti.com>");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("DesignWare USB3 PCI Glue Layer");
module_pci_driver(dwc3_pci_driver);
@@ -0,0 +1,935 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2018, The Linux Foundation. All rights reserved.
*
* Inspired by dwc3-of-simple.c
*/
#include <linux/cleanup.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/clk.h>
#include <linux/irq.h>
#include <linux/of_clk.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/extcon.h>
#include <linux/interconnect.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/phy/phy.h>
#include <linux/usb/of.h>
#include <linux/reset.h>
#include <linux/iopoll.h>
#include <linux/usb/hcd.h>
#include <linux/usb.h>
#include "core.h"
/* USB QSCRATCH Hardware registers */
#define QSCRATCH_HS_PHY_CTRL 0x10
#define UTMI_OTG_VBUS_VALID BIT(20)
#define SW_SESSVLD_SEL BIT(28)
#define QSCRATCH_SS_PHY_CTRL 0x30
#define LANE0_PWR_PRESENT BIT(24)
#define QSCRATCH_GENERAL_CFG 0x08
#define PIPE_UTMI_CLK_SEL BIT(0)
#define PIPE3_PHYSTATUS_SW BIT(3)
#define PIPE_UTMI_CLK_DIS BIT(8)
#define PWR_EVNT_LPM_IN_L2_MASK BIT(4)
#define PWR_EVNT_LPM_OUT_L2_MASK BIT(5)
#define SDM845_QSCRATCH_BASE_OFFSET 0xf8800
#define SDM845_QSCRATCH_SIZE 0x400
#define SDM845_DWC3_CORE_SIZE 0xcd00
/* Interconnect path bandwidths in MBps */
#define USB_MEMORY_AVG_HS_BW MBps_to_icc(240)
#define USB_MEMORY_PEAK_HS_BW MBps_to_icc(700)
#define USB_MEMORY_AVG_SS_BW MBps_to_icc(1000)
#define USB_MEMORY_PEAK_SS_BW MBps_to_icc(2500)
#define APPS_USB_AVG_BW 0
#define APPS_USB_PEAK_BW MBps_to_icc(40)
/* Qualcomm SoCs with multiport support has up to 4 ports */
#define DWC3_QCOM_MAX_PORTS 4
static const u32 pwr_evnt_irq_stat_reg[DWC3_QCOM_MAX_PORTS] = {
0x58,
0x1dc,
0x228,
0x238,
};
struct dwc3_qcom_port {
int qusb2_phy_irq;
int dp_hs_phy_irq;
int dm_hs_phy_irq;
int ss_phy_irq;
enum usb_device_speed usb2_speed;
};
struct dwc3_qcom {
struct device *dev;
void __iomem *qscratch_base;
struct platform_device *dwc3;
struct clk **clks;
int num_clocks;
struct reset_control *resets;
struct dwc3_qcom_port ports[DWC3_QCOM_MAX_PORTS];
u8 num_ports;
struct extcon_dev *edev;
struct extcon_dev *host_edev;
struct notifier_block vbus_nb;
struct notifier_block host_nb;
enum usb_dr_mode mode;
bool is_suspended;
bool pm_suspended;
struct icc_path *icc_path_ddr;
struct icc_path *icc_path_apps;
};
static inline void dwc3_qcom_setbits(void __iomem *base, u32 offset, u32 val)
{
u32 reg;
reg = readl(base + offset);
reg |= val;
writel(reg, base + offset);
/* ensure that above write is through */
readl(base + offset);
}
static inline void dwc3_qcom_clrbits(void __iomem *base, u32 offset, u32 val)
{
u32 reg;
reg = readl(base + offset);
reg &= ~val;
writel(reg, base + offset);
/* ensure that above write is through */
readl(base + offset);
}
static void dwc3_qcom_vbus_override_enable(struct dwc3_qcom *qcom, bool enable)
{
if (enable) {
dwc3_qcom_setbits(qcom->qscratch_base, QSCRATCH_SS_PHY_CTRL,
LANE0_PWR_PRESENT);
dwc3_qcom_setbits(qcom->qscratch_base, QSCRATCH_HS_PHY_CTRL,
UTMI_OTG_VBUS_VALID | SW_SESSVLD_SEL);
} else {
dwc3_qcom_clrbits(qcom->qscratch_base, QSCRATCH_SS_PHY_CTRL,
LANE0_PWR_PRESENT);
dwc3_qcom_clrbits(qcom->qscratch_base, QSCRATCH_HS_PHY_CTRL,
UTMI_OTG_VBUS_VALID | SW_SESSVLD_SEL);
}
}
static int dwc3_qcom_vbus_notifier(struct notifier_block *nb,
unsigned long event, void *ptr)
{
struct dwc3_qcom *qcom = container_of(nb, struct dwc3_qcom, vbus_nb);
/* enable vbus override for device mode */
dwc3_qcom_vbus_override_enable(qcom, event);
qcom->mode = event ? USB_DR_MODE_PERIPHERAL : USB_DR_MODE_HOST;
return NOTIFY_DONE;
}
static int dwc3_qcom_host_notifier(struct notifier_block *nb,
unsigned long event, void *ptr)
{
struct dwc3_qcom *qcom = container_of(nb, struct dwc3_qcom, host_nb);
/* disable vbus override in host mode */
dwc3_qcom_vbus_override_enable(qcom, !event);
qcom->mode = event ? USB_DR_MODE_HOST : USB_DR_MODE_PERIPHERAL;
return NOTIFY_DONE;
}
static int dwc3_qcom_register_extcon(struct dwc3_qcom *qcom)
{
struct device *dev = qcom->dev;
struct extcon_dev *host_edev;
int ret;
if (!of_property_present(dev->of_node, "extcon"))
return 0;
qcom->edev = extcon_get_edev_by_phandle(dev, 0);
if (IS_ERR(qcom->edev))
return dev_err_probe(dev, PTR_ERR(qcom->edev),
"Failed to get extcon\n");
qcom->vbus_nb.notifier_call = dwc3_qcom_vbus_notifier;
qcom->host_edev = extcon_get_edev_by_phandle(dev, 1);
if (IS_ERR(qcom->host_edev))
qcom->host_edev = NULL;
ret = devm_extcon_register_notifier(dev, qcom->edev, EXTCON_USB,
&qcom->vbus_nb);
if (ret < 0) {
dev_err(dev, "VBUS notifier register failed\n");
return ret;
}
if (qcom->host_edev)
host_edev = qcom->host_edev;
else
host_edev = qcom->edev;
qcom->host_nb.notifier_call = dwc3_qcom_host_notifier;
ret = devm_extcon_register_notifier(dev, host_edev, EXTCON_USB_HOST,
&qcom->host_nb);
if (ret < 0) {
dev_err(dev, "Host notifier register failed\n");
return ret;
}
/* Update initial VBUS override based on extcon state */
if (extcon_get_state(qcom->edev, EXTCON_USB) ||
!extcon_get_state(host_edev, EXTCON_USB_HOST))
dwc3_qcom_vbus_notifier(&qcom->vbus_nb, true, qcom->edev);
else
dwc3_qcom_vbus_notifier(&qcom->vbus_nb, false, qcom->edev);
return 0;
}
static int dwc3_qcom_interconnect_enable(struct dwc3_qcom *qcom)
{
int ret;
ret = icc_enable(qcom->icc_path_ddr);
if (ret)
return ret;
ret = icc_enable(qcom->icc_path_apps);
if (ret)
icc_disable(qcom->icc_path_ddr);
return ret;
}
static int dwc3_qcom_interconnect_disable(struct dwc3_qcom *qcom)
{
int ret;
ret = icc_disable(qcom->icc_path_ddr);
if (ret)
return ret;
ret = icc_disable(qcom->icc_path_apps);
if (ret)
icc_enable(qcom->icc_path_ddr);
return ret;
}
/**
* dwc3_qcom_interconnect_init() - Get interconnect path handles
* and set bandwidth.
* @qcom: Pointer to the concerned usb core.
*
*/
static int dwc3_qcom_interconnect_init(struct dwc3_qcom *qcom)
{
enum usb_device_speed max_speed;
struct device *dev = qcom->dev;
int ret;
qcom->icc_path_ddr = of_icc_get(dev, "usb-ddr");
if (IS_ERR(qcom->icc_path_ddr)) {
return dev_err_probe(dev, PTR_ERR(qcom->icc_path_ddr),
"failed to get usb-ddr path\n");
}
qcom->icc_path_apps = of_icc_get(dev, "apps-usb");
if (IS_ERR(qcom->icc_path_apps)) {
ret = dev_err_probe(dev, PTR_ERR(qcom->icc_path_apps),
"failed to get apps-usb path\n");
goto put_path_ddr;
}
max_speed = usb_get_maximum_speed(&qcom->dwc3->dev);
if (max_speed >= USB_SPEED_SUPER || max_speed == USB_SPEED_UNKNOWN) {
ret = icc_set_bw(qcom->icc_path_ddr,
USB_MEMORY_AVG_SS_BW, USB_MEMORY_PEAK_SS_BW);
} else {
ret = icc_set_bw(qcom->icc_path_ddr,
USB_MEMORY_AVG_HS_BW, USB_MEMORY_PEAK_HS_BW);
}
if (ret) {
dev_err(dev, "failed to set bandwidth for usb-ddr path: %d\n", ret);
goto put_path_apps;
}
ret = icc_set_bw(qcom->icc_path_apps, APPS_USB_AVG_BW, APPS_USB_PEAK_BW);
if (ret) {
dev_err(dev, "failed to set bandwidth for apps-usb path: %d\n", ret);
goto put_path_apps;
}
return 0;
put_path_apps:
icc_put(qcom->icc_path_apps);
put_path_ddr:
icc_put(qcom->icc_path_ddr);
return ret;
}
/**
* dwc3_qcom_interconnect_exit() - Release interconnect path handles
* @qcom: Pointer to the concerned usb core.
*
* This function is used to release interconnect path handle.
*/
static void dwc3_qcom_interconnect_exit(struct dwc3_qcom *qcom)
{
icc_put(qcom->icc_path_ddr);
icc_put(qcom->icc_path_apps);
}
/* Only usable in contexts where the role can not change. */
static bool dwc3_qcom_is_host(struct dwc3_qcom *qcom)
{
struct dwc3 *dwc;
/*
* FIXME: Fix this layering violation.
*/
dwc = platform_get_drvdata(qcom->dwc3);
/* Core driver may not have probed yet. */
if (!dwc)
return false;
return dwc->xhci;
}
static enum usb_device_speed dwc3_qcom_read_usb2_speed(struct dwc3_qcom *qcom, int port_index)
{
struct dwc3 *dwc = platform_get_drvdata(qcom->dwc3);
struct usb_device *udev;
struct usb_hcd __maybe_unused *hcd;
/*
* FIXME: Fix this layering violation.
*/
hcd = platform_get_drvdata(dwc->xhci);
#ifdef CONFIG_USB
udev = usb_hub_find_child(hcd->self.root_hub, port_index + 1);
#else
udev = NULL;
#endif
if (!udev)
return USB_SPEED_UNKNOWN;
return udev->speed;
}
static void dwc3_qcom_enable_wakeup_irq(int irq, unsigned int polarity)
{
if (!irq)
return;
if (polarity)
irq_set_irq_type(irq, polarity);
enable_irq(irq);
enable_irq_wake(irq);
}
static void dwc3_qcom_disable_wakeup_irq(int irq)
{
if (!irq)
return;
disable_irq_wake(irq);
disable_irq_nosync(irq);
}
static void dwc3_qcom_disable_port_interrupts(struct dwc3_qcom_port *port)
{
dwc3_qcom_disable_wakeup_irq(port->qusb2_phy_irq);
if (port->usb2_speed == USB_SPEED_LOW) {
dwc3_qcom_disable_wakeup_irq(port->dm_hs_phy_irq);
} else if ((port->usb2_speed == USB_SPEED_HIGH) ||
(port->usb2_speed == USB_SPEED_FULL)) {
dwc3_qcom_disable_wakeup_irq(port->dp_hs_phy_irq);
} else {
dwc3_qcom_disable_wakeup_irq(port->dp_hs_phy_irq);
dwc3_qcom_disable_wakeup_irq(port->dm_hs_phy_irq);
}
dwc3_qcom_disable_wakeup_irq(port->ss_phy_irq);
}
static void dwc3_qcom_enable_port_interrupts(struct dwc3_qcom_port *port)
{
dwc3_qcom_enable_wakeup_irq(port->qusb2_phy_irq, 0);
/*
* Configure DP/DM line interrupts based on the USB2 device attached to
* the root hub port. When HS/FS device is connected, configure the DP line
* as falling edge to detect both disconnect and remote wakeup scenarios. When
* LS device is connected, configure DM line as falling edge to detect both
* disconnect and remote wakeup. When no device is connected, configure both
* DP and DM lines as rising edge to detect HS/HS/LS device connect scenario.
*/
if (port->usb2_speed == USB_SPEED_LOW) {
dwc3_qcom_enable_wakeup_irq(port->dm_hs_phy_irq,
IRQ_TYPE_EDGE_FALLING);
} else if ((port->usb2_speed == USB_SPEED_HIGH) ||
(port->usb2_speed == USB_SPEED_FULL)) {
dwc3_qcom_enable_wakeup_irq(port->dp_hs_phy_irq,
IRQ_TYPE_EDGE_FALLING);
} else {
dwc3_qcom_enable_wakeup_irq(port->dp_hs_phy_irq,
IRQ_TYPE_EDGE_RISING);
dwc3_qcom_enable_wakeup_irq(port->dm_hs_phy_irq,
IRQ_TYPE_EDGE_RISING);
}
dwc3_qcom_enable_wakeup_irq(port->ss_phy_irq, 0);
}
static void dwc3_qcom_disable_interrupts(struct dwc3_qcom *qcom)
{
int i;
for (i = 0; i < qcom->num_ports; i++)
dwc3_qcom_disable_port_interrupts(&qcom->ports[i]);
}
static void dwc3_qcom_enable_interrupts(struct dwc3_qcom *qcom)
{
int i;
for (i = 0; i < qcom->num_ports; i++)
dwc3_qcom_enable_port_interrupts(&qcom->ports[i]);
}
static int dwc3_qcom_suspend(struct dwc3_qcom *qcom, bool wakeup)
{
u32 val;
int i, ret;
if (qcom->is_suspended)
return 0;
for (i = 0; i < qcom->num_ports; i++) {
val = readl(qcom->qscratch_base + pwr_evnt_irq_stat_reg[i]);
if (!(val & PWR_EVNT_LPM_IN_L2_MASK))
dev_err(qcom->dev, "port-%d HS-PHY not in L2\n", i + 1);
}
for (i = qcom->num_clocks - 1; i >= 0; i--)
clk_disable_unprepare(qcom->clks[i]);
ret = dwc3_qcom_interconnect_disable(qcom);
if (ret)
dev_warn(qcom->dev, "failed to disable interconnect: %d\n", ret);
/*
* The role is stable during suspend as role switching is done from a
* freezable workqueue.
*/
if (dwc3_qcom_is_host(qcom) && wakeup) {
for (i = 0; i < qcom->num_ports; i++)
qcom->ports[i].usb2_speed = dwc3_qcom_read_usb2_speed(qcom, i);
dwc3_qcom_enable_interrupts(qcom);
}
qcom->is_suspended = true;
return 0;
}
static int dwc3_qcom_resume(struct dwc3_qcom *qcom, bool wakeup)
{
int ret;
int i;
if (!qcom->is_suspended)
return 0;
if (dwc3_qcom_is_host(qcom) && wakeup)
dwc3_qcom_disable_interrupts(qcom);
for (i = 0; i < qcom->num_clocks; i++) {
ret = clk_prepare_enable(qcom->clks[i]);
if (ret < 0) {
while (--i >= 0)
clk_disable_unprepare(qcom->clks[i]);
return ret;
}
}
ret = dwc3_qcom_interconnect_enable(qcom);
if (ret)
dev_warn(qcom->dev, "failed to enable interconnect: %d\n", ret);
/* Clear existing events from PHY related to L2 in/out */
for (i = 0; i < qcom->num_ports; i++) {
dwc3_qcom_setbits(qcom->qscratch_base,
pwr_evnt_irq_stat_reg[i],
PWR_EVNT_LPM_IN_L2_MASK | PWR_EVNT_LPM_OUT_L2_MASK);
}
qcom->is_suspended = false;
return 0;
}
static irqreturn_t qcom_dwc3_resume_irq(int irq, void *data)
{
struct dwc3_qcom *qcom = data;
struct dwc3 *dwc = platform_get_drvdata(qcom->dwc3);
/* If pm_suspended then let pm_resume take care of resuming h/w */
if (qcom->pm_suspended)
return IRQ_HANDLED;
/*
* This is safe as role switching is done from a freezable workqueue
* and the wakeup interrupts are disabled as part of resume.
*/
if (dwc3_qcom_is_host(qcom))
pm_runtime_resume(&dwc->xhci->dev);
return IRQ_HANDLED;
}
static void dwc3_qcom_select_utmi_clk(struct dwc3_qcom *qcom)
{
/* Configure dwc3 to use UTMI clock as PIPE clock not present */
dwc3_qcom_setbits(qcom->qscratch_base, QSCRATCH_GENERAL_CFG,
PIPE_UTMI_CLK_DIS);
usleep_range(100, 1000);
dwc3_qcom_setbits(qcom->qscratch_base, QSCRATCH_GENERAL_CFG,
PIPE_UTMI_CLK_SEL | PIPE3_PHYSTATUS_SW);
usleep_range(100, 1000);
dwc3_qcom_clrbits(qcom->qscratch_base, QSCRATCH_GENERAL_CFG,
PIPE_UTMI_CLK_DIS);
}
static int dwc3_qcom_request_irq(struct dwc3_qcom *qcom, int irq,
const char *name)
{
int ret;
/* Keep wakeup interrupts disabled until suspend */
ret = devm_request_threaded_irq(qcom->dev, irq, NULL,
qcom_dwc3_resume_irq,
IRQF_ONESHOT | IRQF_NO_AUTOEN,
name, qcom);
if (ret)
dev_err(qcom->dev, "failed to request irq %s: %d\n", name, ret);
return ret;
}
static int dwc3_qcom_setup_port_irq(struct platform_device *pdev, int port_index, bool is_multiport)
{
struct dwc3_qcom *qcom = platform_get_drvdata(pdev);
const char *irq_name;
int irq;
int ret;
if (is_multiport)
irq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "dp_hs_phy_%d", port_index + 1);
else
irq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "dp_hs_phy_irq");
if (!irq_name)
return -ENOMEM;
irq = platform_get_irq_byname_optional(pdev, irq_name);
if (irq > 0) {
ret = dwc3_qcom_request_irq(qcom, irq, irq_name);
if (ret)
return ret;
qcom->ports[port_index].dp_hs_phy_irq = irq;
}
if (is_multiport)
irq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "dm_hs_phy_%d", port_index + 1);
else
irq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "dm_hs_phy_irq");
if (!irq_name)
return -ENOMEM;
irq = platform_get_irq_byname_optional(pdev, irq_name);
if (irq > 0) {
ret = dwc3_qcom_request_irq(qcom, irq, irq_name);
if (ret)
return ret;
qcom->ports[port_index].dm_hs_phy_irq = irq;
}
if (is_multiport)
irq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "ss_phy_%d", port_index + 1);
else
irq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "ss_phy_irq");
if (!irq_name)
return -ENOMEM;
irq = platform_get_irq_byname_optional(pdev, irq_name);
if (irq > 0) {
ret = dwc3_qcom_request_irq(qcom, irq, irq_name);
if (ret)
return ret;
qcom->ports[port_index].ss_phy_irq = irq;
}
if (is_multiport)
return 0;
irq = platform_get_irq_byname_optional(pdev, "qusb2_phy");
if (irq > 0) {
ret = dwc3_qcom_request_irq(qcom, irq, "qusb2_phy");
if (ret)
return ret;
qcom->ports[port_index].qusb2_phy_irq = irq;
}
return 0;
}
static int dwc3_qcom_find_num_ports(struct platform_device *pdev)
{
char irq_name[14];
int port_num;
int irq;
irq = platform_get_irq_byname_optional(pdev, "dp_hs_phy_1");
if (irq <= 0)
return 1;
for (port_num = 2; port_num <= DWC3_QCOM_MAX_PORTS; port_num++) {
sprintf(irq_name, "dp_hs_phy_%d", port_num);
irq = platform_get_irq_byname_optional(pdev, irq_name);
if (irq <= 0)
return port_num - 1;
}
return DWC3_QCOM_MAX_PORTS;
}
static int dwc3_qcom_setup_irq(struct platform_device *pdev)
{
struct dwc3_qcom *qcom = platform_get_drvdata(pdev);
bool is_multiport;
int ret;
int i;
qcom->num_ports = dwc3_qcom_find_num_ports(pdev);
is_multiport = (qcom->num_ports > 1);
for (i = 0; i < qcom->num_ports; i++) {
ret = dwc3_qcom_setup_port_irq(pdev, i, is_multiport);
if (ret)
return ret;
}
return 0;
}
static int dwc3_qcom_clk_init(struct dwc3_qcom *qcom, int count)
{
struct device *dev = qcom->dev;
struct device_node *np = dev->of_node;
int i;
if (!np || !count)
return 0;
if (count < 0)
return count;
qcom->num_clocks = count;
qcom->clks = devm_kcalloc(dev, qcom->num_clocks,
sizeof(struct clk *), GFP_KERNEL);
if (!qcom->clks)
return -ENOMEM;
for (i = 0; i < qcom->num_clocks; i++) {
struct clk *clk;
int ret;
clk = of_clk_get(np, i);
if (IS_ERR(clk)) {
while (--i >= 0)
clk_put(qcom->clks[i]);
return PTR_ERR(clk);
}
ret = clk_prepare_enable(clk);
if (ret < 0) {
while (--i >= 0) {
clk_disable_unprepare(qcom->clks[i]);
clk_put(qcom->clks[i]);
}
clk_put(clk);
return ret;
}
qcom->clks[i] = clk;
}
return 0;
}
static int dwc3_qcom_of_register_core(struct platform_device *pdev)
{
struct dwc3_qcom *qcom = platform_get_drvdata(pdev);
struct device_node *np = pdev->dev.of_node;
struct device *dev = &pdev->dev;
int ret;
struct device_node *dwc3_np __free(device_node) = of_get_compatible_child(np,
"snps,dwc3");
if (!dwc3_np) {
dev_err(dev, "failed to find dwc3 core child\n");
return -ENODEV;
}
ret = of_platform_populate(np, NULL, NULL, dev);
if (ret) {
dev_err(dev, "failed to register dwc3 core - %d\n", ret);
return ret;
}
qcom->dwc3 = of_find_device_by_node(dwc3_np);
if (!qcom->dwc3) {
ret = -ENODEV;
dev_err(dev, "failed to get dwc3 platform device\n");
of_platform_depopulate(dev);
}
return ret;
}
static int dwc3_qcom_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct device *dev = &pdev->dev;
struct dwc3_qcom *qcom;
int ret, i;
bool ignore_pipe_clk;
bool wakeup_source;
qcom = devm_kzalloc(&pdev->dev, sizeof(*qcom), GFP_KERNEL);
if (!qcom)
return -ENOMEM;
platform_set_drvdata(pdev, qcom);
qcom->dev = &pdev->dev;
qcom->resets = devm_reset_control_array_get_optional_exclusive(dev);
if (IS_ERR(qcom->resets)) {
return dev_err_probe(&pdev->dev, PTR_ERR(qcom->resets),
"failed to get resets\n");
}
ret = reset_control_assert(qcom->resets);
if (ret) {
dev_err(&pdev->dev, "failed to assert resets, err=%d\n", ret);
return ret;
}
usleep_range(10, 1000);
ret = reset_control_deassert(qcom->resets);
if (ret) {
dev_err(&pdev->dev, "failed to deassert resets, err=%d\n", ret);
goto reset_assert;
}
ret = dwc3_qcom_clk_init(qcom, of_clk_get_parent_count(np));
if (ret) {
dev_err_probe(dev, ret, "failed to get clocks\n");
goto reset_assert;
}
qcom->qscratch_base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(qcom->qscratch_base)) {
ret = PTR_ERR(qcom->qscratch_base);
goto clk_disable;
}
ret = dwc3_qcom_setup_irq(pdev);
if (ret) {
dev_err(dev, "failed to setup IRQs, err=%d\n", ret);
goto clk_disable;
}
/*
* Disable pipe_clk requirement if specified. Used when dwc3
* operates without SSPHY and only HS/FS/LS modes are supported.
*/
ignore_pipe_clk = device_property_read_bool(dev,
"qcom,select-utmi-as-pipe-clk");
if (ignore_pipe_clk)
dwc3_qcom_select_utmi_clk(qcom);
ret = dwc3_qcom_of_register_core(pdev);
if (ret) {
dev_err(dev, "failed to register DWC3 Core, err=%d\n", ret);
goto clk_disable;
}
ret = dwc3_qcom_interconnect_init(qcom);
if (ret)
goto depopulate;
qcom->mode = usb_get_dr_mode(&qcom->dwc3->dev);
/* enable vbus override for device mode */
if (qcom->mode != USB_DR_MODE_HOST)
dwc3_qcom_vbus_override_enable(qcom, true);
/* register extcon to override sw_vbus on Vbus change later */
ret = dwc3_qcom_register_extcon(qcom);
if (ret)
goto interconnect_exit;
wakeup_source = of_property_read_bool(dev->of_node, "wakeup-source");
device_init_wakeup(&pdev->dev, wakeup_source);
device_init_wakeup(&qcom->dwc3->dev, wakeup_source);
qcom->is_suspended = false;
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
pm_runtime_forbid(dev);
return 0;
interconnect_exit:
dwc3_qcom_interconnect_exit(qcom);
depopulate:
of_platform_depopulate(&pdev->dev);
platform_device_put(qcom->dwc3);
clk_disable:
for (i = qcom->num_clocks - 1; i >= 0; i--) {
clk_disable_unprepare(qcom->clks[i]);
clk_put(qcom->clks[i]);
}
reset_assert:
reset_control_assert(qcom->resets);
return ret;
}
static void dwc3_qcom_remove(struct platform_device *pdev)
{
struct dwc3_qcom *qcom = platform_get_drvdata(pdev);
struct device *dev = &pdev->dev;
int i;
of_platform_depopulate(&pdev->dev);
platform_device_put(qcom->dwc3);
for (i = qcom->num_clocks - 1; i >= 0; i--) {
clk_disable_unprepare(qcom->clks[i]);
clk_put(qcom->clks[i]);
}
qcom->num_clocks = 0;
dwc3_qcom_interconnect_exit(qcom);
reset_control_assert(qcom->resets);
pm_runtime_allow(dev);
pm_runtime_disable(dev);
}
static int __maybe_unused dwc3_qcom_pm_suspend(struct device *dev)
{
struct dwc3_qcom *qcom = dev_get_drvdata(dev);
bool wakeup = device_may_wakeup(dev);
int ret;
ret = dwc3_qcom_suspend(qcom, wakeup);
if (ret)
return ret;
qcom->pm_suspended = true;
return 0;
}
static int __maybe_unused dwc3_qcom_pm_resume(struct device *dev)
{
struct dwc3_qcom *qcom = dev_get_drvdata(dev);
bool wakeup = device_may_wakeup(dev);
int ret;
ret = dwc3_qcom_resume(qcom, wakeup);
if (ret)
return ret;
qcom->pm_suspended = false;
return 0;
}
static int __maybe_unused dwc3_qcom_runtime_suspend(struct device *dev)
{
struct dwc3_qcom *qcom = dev_get_drvdata(dev);
return dwc3_qcom_suspend(qcom, true);
}
static int __maybe_unused dwc3_qcom_runtime_resume(struct device *dev)
{
struct dwc3_qcom *qcom = dev_get_drvdata(dev);
return dwc3_qcom_resume(qcom, true);
}
static const struct dev_pm_ops dwc3_qcom_dev_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(dwc3_qcom_pm_suspend, dwc3_qcom_pm_resume)
SET_RUNTIME_PM_OPS(dwc3_qcom_runtime_suspend, dwc3_qcom_runtime_resume,
NULL)
};
static const struct of_device_id dwc3_qcom_of_match[] = {
{ .compatible = "qcom,dwc3" },
{ }
};
MODULE_DEVICE_TABLE(of, dwc3_qcom_of_match);
static struct platform_driver dwc3_qcom_driver = {
.probe = dwc3_qcom_probe,
.remove = dwc3_qcom_remove,
.driver = {
.name = "dwc3-qcom-legacy",
.pm = &dwc3_qcom_dev_pm_ops,
.of_match_table = dwc3_qcom_of_match,
},
};
module_platform_driver(dwc3_qcom_driver);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("DesignWare DWC3 QCOM legacy glue Driver");
+861
View File
@@ -0,0 +1,861 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2018, The Linux Foundation. All rights reserved.
*
* Inspired by dwc3-of-simple.c
*/
#include <linux/io.h>
#include <linux/of.h>
#include <linux/clk.h>
#include <linux/irq.h>
#include <linux/of_clk.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/interconnect.h>
#include <linux/platform_device.h>
#include <linux/phy/phy.h>
#include <linux/usb/of.h>
#include <linux/reset.h>
#include <linux/iopoll.h>
#include <linux/usb/hcd.h>
#include <linux/usb.h>
#include "core.h"
#include "glue.h"
/* USB QSCRATCH Hardware registers */
#define QSCRATCH_HS_PHY_CTRL 0x10
#define UTMI_OTG_VBUS_VALID BIT(20)
#define SW_SESSVLD_SEL BIT(28)
#define QSCRATCH_SS_PHY_CTRL 0x30
#define LANE0_PWR_PRESENT BIT(24)
#define QSCRATCH_GENERAL_CFG 0x08
#define PIPE_UTMI_CLK_SEL BIT(0)
#define PIPE3_PHYSTATUS_SW BIT(3)
#define PIPE_UTMI_CLK_DIS BIT(8)
#define PWR_EVNT_LPM_IN_L2_MASK BIT(4)
#define PWR_EVNT_LPM_OUT_L2_MASK BIT(5)
#define SDM845_QSCRATCH_BASE_OFFSET 0xf8800
#define SDM845_QSCRATCH_SIZE 0x400
#define SDM845_DWC3_CORE_SIZE 0xcd00
/* Interconnect path bandwidths in MBps */
#define USB_MEMORY_AVG_HS_BW MBps_to_icc(240)
#define USB_MEMORY_PEAK_HS_BW MBps_to_icc(700)
#define USB_MEMORY_AVG_SS_BW MBps_to_icc(1000)
#define USB_MEMORY_PEAK_SS_BW MBps_to_icc(2500)
#define APPS_USB_AVG_BW 0
#define APPS_USB_PEAK_BW MBps_to_icc(40)
/* Qualcomm SoCs with multiport support has up to 4 ports */
#define DWC3_QCOM_MAX_PORTS 4
static const u32 pwr_evnt_irq_stat_reg[DWC3_QCOM_MAX_PORTS] = {
0x58,
0x1dc,
0x228,
0x238,
};
struct dwc3_qcom_port {
int qusb2_phy_irq;
int dp_hs_phy_irq;
int dm_hs_phy_irq;
int ss_phy_irq;
enum usb_device_speed usb2_speed;
};
struct dwc3_qcom {
struct device *dev;
void __iomem *qscratch_base;
struct dwc3 dwc;
struct clk_bulk_data *clks;
int num_clocks;
struct reset_control *resets;
struct dwc3_qcom_port ports[DWC3_QCOM_MAX_PORTS];
u8 num_ports;
enum usb_dr_mode mode;
bool is_suspended;
bool pm_suspended;
struct icc_path *icc_path_ddr;
struct icc_path *icc_path_apps;
enum usb_role current_role;
};
#define to_dwc3_qcom(d) container_of((d), struct dwc3_qcom, dwc)
static inline void dwc3_qcom_setbits(void __iomem *base, u32 offset, u32 val)
{
u32 reg;
reg = readl(base + offset);
reg |= val;
writel(reg, base + offset);
/* ensure that above write is through */
readl(base + offset);
}
static inline void dwc3_qcom_clrbits(void __iomem *base, u32 offset, u32 val)
{
u32 reg;
reg = readl(base + offset);
reg &= ~val;
writel(reg, base + offset);
/* ensure that above write is through */
readl(base + offset);
}
static void dwc3_qcom_vbus_override_enable(struct dwc3_qcom *qcom, bool enable)
{
if (enable) {
dwc3_qcom_setbits(qcom->qscratch_base, QSCRATCH_SS_PHY_CTRL,
LANE0_PWR_PRESENT);
dwc3_qcom_setbits(qcom->qscratch_base, QSCRATCH_HS_PHY_CTRL,
UTMI_OTG_VBUS_VALID | SW_SESSVLD_SEL);
} else {
dwc3_qcom_clrbits(qcom->qscratch_base, QSCRATCH_SS_PHY_CTRL,
LANE0_PWR_PRESENT);
dwc3_qcom_clrbits(qcom->qscratch_base, QSCRATCH_HS_PHY_CTRL,
UTMI_OTG_VBUS_VALID | SW_SESSVLD_SEL);
}
}
static int dwc3_qcom_interconnect_enable(struct dwc3_qcom *qcom)
{
int ret;
ret = icc_enable(qcom->icc_path_ddr);
if (ret)
return ret;
ret = icc_enable(qcom->icc_path_apps);
if (ret)
icc_disable(qcom->icc_path_ddr);
return ret;
}
static int dwc3_qcom_interconnect_disable(struct dwc3_qcom *qcom)
{
int ret;
ret = icc_disable(qcom->icc_path_ddr);
if (ret)
return ret;
ret = icc_disable(qcom->icc_path_apps);
if (ret)
icc_enable(qcom->icc_path_ddr);
return ret;
}
/**
* dwc3_qcom_interconnect_init() - Get interconnect path handles
* and set bandwidth.
* @qcom: Pointer to the concerned usb core.
*
*/
static int dwc3_qcom_interconnect_init(struct dwc3_qcom *qcom)
{
enum usb_device_speed max_speed;
struct device *dev = qcom->dev;
int ret;
qcom->icc_path_ddr = of_icc_get(dev, "usb-ddr");
if (IS_ERR(qcom->icc_path_ddr)) {
return dev_err_probe(dev, PTR_ERR(qcom->icc_path_ddr),
"failed to get usb-ddr path\n");
}
qcom->icc_path_apps = of_icc_get(dev, "apps-usb");
if (IS_ERR(qcom->icc_path_apps)) {
ret = dev_err_probe(dev, PTR_ERR(qcom->icc_path_apps),
"failed to get apps-usb path\n");
goto put_path_ddr;
}
max_speed = usb_get_maximum_speed(qcom->dwc.dev);
if (max_speed >= USB_SPEED_SUPER || max_speed == USB_SPEED_UNKNOWN) {
ret = icc_set_bw(qcom->icc_path_ddr,
USB_MEMORY_AVG_SS_BW, USB_MEMORY_PEAK_SS_BW);
} else {
ret = icc_set_bw(qcom->icc_path_ddr,
USB_MEMORY_AVG_HS_BW, USB_MEMORY_PEAK_HS_BW);
}
if (ret) {
dev_err(dev, "failed to set bandwidth for usb-ddr path: %d\n", ret);
goto put_path_apps;
}
ret = icc_set_bw(qcom->icc_path_apps, APPS_USB_AVG_BW, APPS_USB_PEAK_BW);
if (ret) {
dev_err(dev, "failed to set bandwidth for apps-usb path: %d\n", ret);
goto put_path_apps;
}
return 0;
put_path_apps:
icc_put(qcom->icc_path_apps);
put_path_ddr:
icc_put(qcom->icc_path_ddr);
return ret;
}
/**
* dwc3_qcom_interconnect_exit() - Release interconnect path handles
* @qcom: Pointer to the concerned usb core.
*
* This function is used to release interconnect path handle.
*/
static void dwc3_qcom_interconnect_exit(struct dwc3_qcom *qcom)
{
icc_put(qcom->icc_path_ddr);
icc_put(qcom->icc_path_apps);
}
/* Only usable in contexts where the role can not change. */
static bool dwc3_qcom_is_host(struct dwc3_qcom *qcom)
{
return qcom->dwc.xhci;
}
static enum usb_device_speed dwc3_qcom_read_usb2_speed(struct dwc3_qcom *qcom, int port_index)
{
struct usb_device *udev;
struct usb_hcd __maybe_unused *hcd;
struct dwc3 *dwc = &qcom->dwc;
/*
* FIXME: Fix this layering violation.
*/
hcd = platform_get_drvdata(dwc->xhci);
#ifdef CONFIG_USB
udev = usb_hub_find_child(hcd->self.root_hub, port_index + 1);
#else
udev = NULL;
#endif
if (!udev)
return USB_SPEED_UNKNOWN;
return udev->speed;
}
static void dwc3_qcom_enable_wakeup_irq(int irq, unsigned int polarity)
{
if (!irq)
return;
if (polarity)
irq_set_irq_type(irq, polarity);
enable_irq(irq);
enable_irq_wake(irq);
}
static void dwc3_qcom_disable_wakeup_irq(int irq)
{
if (!irq)
return;
disable_irq_wake(irq);
disable_irq_nosync(irq);
}
static void dwc3_qcom_disable_port_interrupts(struct dwc3_qcom_port *port)
{
dwc3_qcom_disable_wakeup_irq(port->qusb2_phy_irq);
if (port->usb2_speed == USB_SPEED_LOW) {
dwc3_qcom_disable_wakeup_irq(port->dm_hs_phy_irq);
} else if ((port->usb2_speed == USB_SPEED_HIGH) ||
(port->usb2_speed == USB_SPEED_FULL)) {
dwc3_qcom_disable_wakeup_irq(port->dp_hs_phy_irq);
} else {
dwc3_qcom_disable_wakeup_irq(port->dp_hs_phy_irq);
dwc3_qcom_disable_wakeup_irq(port->dm_hs_phy_irq);
}
dwc3_qcom_disable_wakeup_irq(port->ss_phy_irq);
}
static void dwc3_qcom_enable_port_interrupts(struct dwc3_qcom_port *port)
{
dwc3_qcom_enable_wakeup_irq(port->qusb2_phy_irq, 0);
/*
* Configure DP/DM line interrupts based on the USB2 device attached to
* the root hub port. When HS/FS device is connected, configure the DP line
* as falling edge to detect both disconnect and remote wakeup scenarios. When
* LS device is connected, configure DM line as falling edge to detect both
* disconnect and remote wakeup. When no device is connected, configure both
* DP and DM lines as rising edge to detect HS/HS/LS device connect scenario.
*/
if (port->usb2_speed == USB_SPEED_LOW) {
dwc3_qcom_enable_wakeup_irq(port->dm_hs_phy_irq,
IRQ_TYPE_EDGE_FALLING);
} else if ((port->usb2_speed == USB_SPEED_HIGH) ||
(port->usb2_speed == USB_SPEED_FULL)) {
dwc3_qcom_enable_wakeup_irq(port->dp_hs_phy_irq,
IRQ_TYPE_EDGE_FALLING);
} else {
dwc3_qcom_enable_wakeup_irq(port->dp_hs_phy_irq,
IRQ_TYPE_EDGE_RISING);
dwc3_qcom_enable_wakeup_irq(port->dm_hs_phy_irq,
IRQ_TYPE_EDGE_RISING);
}
dwc3_qcom_enable_wakeup_irq(port->ss_phy_irq, 0);
}
static void dwc3_qcom_disable_interrupts(struct dwc3_qcom *qcom)
{
int i;
for (i = 0; i < qcom->num_ports; i++)
dwc3_qcom_disable_port_interrupts(&qcom->ports[i]);
}
static void dwc3_qcom_enable_interrupts(struct dwc3_qcom *qcom)
{
int i;
for (i = 0; i < qcom->num_ports; i++)
dwc3_qcom_enable_port_interrupts(&qcom->ports[i]);
}
static int dwc3_qcom_suspend(struct dwc3_qcom *qcom, bool wakeup)
{
u32 val;
int i, ret;
if (qcom->is_suspended)
return 0;
for (i = 0; i < qcom->num_ports; i++) {
val = readl(qcom->qscratch_base + pwr_evnt_irq_stat_reg[i]);
if (!(val & PWR_EVNT_LPM_IN_L2_MASK))
dev_err(qcom->dev, "port-%d HS-PHY not in L2\n", i + 1);
}
clk_bulk_disable_unprepare(qcom->num_clocks, qcom->clks);
ret = dwc3_qcom_interconnect_disable(qcom);
if (ret)
dev_warn(qcom->dev, "failed to disable interconnect: %d\n", ret);
/*
* The role is stable during suspend as role switching is done from a
* freezable workqueue.
*/
if (dwc3_qcom_is_host(qcom) && wakeup) {
for (i = 0; i < qcom->num_ports; i++)
qcom->ports[i].usb2_speed = dwc3_qcom_read_usb2_speed(qcom, i);
dwc3_qcom_enable_interrupts(qcom);
}
qcom->is_suspended = true;
return 0;
}
static int dwc3_qcom_resume(struct dwc3_qcom *qcom, bool wakeup)
{
int ret;
int i;
if (!qcom->is_suspended)
return 0;
if (dwc3_qcom_is_host(qcom) && wakeup)
dwc3_qcom_disable_interrupts(qcom);
ret = clk_bulk_prepare_enable(qcom->num_clocks, qcom->clks);
if (ret < 0)
return ret;
ret = dwc3_qcom_interconnect_enable(qcom);
if (ret)
dev_warn(qcom->dev, "failed to enable interconnect: %d\n", ret);
/* Clear existing events from PHY related to L2 in/out */
for (i = 0; i < qcom->num_ports; i++) {
dwc3_qcom_setbits(qcom->qscratch_base,
pwr_evnt_irq_stat_reg[i],
PWR_EVNT_LPM_IN_L2_MASK | PWR_EVNT_LPM_OUT_L2_MASK);
}
qcom->is_suspended = false;
return 0;
}
static irqreturn_t qcom_dwc3_resume_irq(int irq, void *data)
{
struct dwc3_qcom *qcom = data;
struct dwc3 *dwc = &qcom->dwc;
/* If pm_suspended then let pm_resume take care of resuming h/w */
if (qcom->pm_suspended)
return IRQ_HANDLED;
/*
* This is safe as role switching is done from a freezable workqueue
* and the wakeup interrupts are disabled as part of resume.
*/
if (dwc3_qcom_is_host(qcom))
pm_runtime_resume(&dwc->xhci->dev);
return IRQ_HANDLED;
}
static void dwc3_qcom_select_utmi_clk(struct dwc3_qcom *qcom)
{
/* Configure dwc3 to use UTMI clock as PIPE clock not present */
dwc3_qcom_setbits(qcom->qscratch_base, QSCRATCH_GENERAL_CFG,
PIPE_UTMI_CLK_DIS);
usleep_range(100, 1000);
dwc3_qcom_setbits(qcom->qscratch_base, QSCRATCH_GENERAL_CFG,
PIPE_UTMI_CLK_SEL | PIPE3_PHYSTATUS_SW);
usleep_range(100, 1000);
dwc3_qcom_clrbits(qcom->qscratch_base, QSCRATCH_GENERAL_CFG,
PIPE_UTMI_CLK_DIS);
}
static int dwc3_qcom_request_irq(struct dwc3_qcom *qcom, int irq,
const char *name)
{
int ret;
/* Keep wakeup interrupts disabled until suspend */
ret = devm_request_threaded_irq(qcom->dev, irq, NULL,
qcom_dwc3_resume_irq,
IRQF_ONESHOT | IRQF_NO_AUTOEN,
name, qcom);
if (ret)
dev_err(qcom->dev, "failed to request irq %s: %d\n", name, ret);
return ret;
}
static int dwc3_qcom_setup_port_irq(struct dwc3_qcom *qcom,
struct platform_device *pdev,
int port_index, bool is_multiport)
{
const char *irq_name;
int irq;
int ret;
if (is_multiport)
irq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "dp_hs_phy_%d", port_index + 1);
else
irq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "dp_hs_phy_irq");
if (!irq_name)
return -ENOMEM;
irq = platform_get_irq_byname_optional(pdev, irq_name);
if (irq > 0) {
ret = dwc3_qcom_request_irq(qcom, irq, irq_name);
if (ret)
return ret;
qcom->ports[port_index].dp_hs_phy_irq = irq;
}
if (is_multiport)
irq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "dm_hs_phy_%d", port_index + 1);
else
irq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "dm_hs_phy_irq");
if (!irq_name)
return -ENOMEM;
irq = platform_get_irq_byname_optional(pdev, irq_name);
if (irq > 0) {
ret = dwc3_qcom_request_irq(qcom, irq, irq_name);
if (ret)
return ret;
qcom->ports[port_index].dm_hs_phy_irq = irq;
}
if (is_multiport)
irq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "ss_phy_%d", port_index + 1);
else
irq_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "ss_phy_irq");
if (!irq_name)
return -ENOMEM;
irq = platform_get_irq_byname_optional(pdev, irq_name);
if (irq > 0) {
ret = dwc3_qcom_request_irq(qcom, irq, irq_name);
if (ret)
return ret;
qcom->ports[port_index].ss_phy_irq = irq;
}
if (is_multiport)
return 0;
irq = platform_get_irq_byname_optional(pdev, "qusb2_phy");
if (irq > 0) {
ret = dwc3_qcom_request_irq(qcom, irq, "qusb2_phy");
if (ret)
return ret;
qcom->ports[port_index].qusb2_phy_irq = irq;
}
return 0;
}
static int dwc3_qcom_find_num_ports(struct platform_device *pdev)
{
char irq_name[14];
int port_num;
int irq;
irq = platform_get_irq_byname_optional(pdev, "dp_hs_phy_1");
if (irq < 0)
return 1;
for (port_num = 2; port_num <= DWC3_QCOM_MAX_PORTS; port_num++) {
sprintf(irq_name, "dp_hs_phy_%d", port_num);
irq = platform_get_irq_byname_optional(pdev, irq_name);
if (irq < 0)
return port_num - 1;
}
return DWC3_QCOM_MAX_PORTS;
}
static int dwc3_qcom_setup_irq(struct dwc3_qcom *qcom, struct platform_device *pdev)
{
bool is_multiport;
int ret;
int i;
qcom->num_ports = dwc3_qcom_find_num_ports(pdev);
is_multiport = (qcom->num_ports > 1);
for (i = 0; i < qcom->num_ports; i++) {
ret = dwc3_qcom_setup_port_irq(qcom, pdev, i, is_multiport);
if (ret)
return ret;
}
return 0;
}
static void dwc3_qcom_set_role_notifier(struct dwc3 *dwc, enum usb_role next_role)
{
struct dwc3_qcom *qcom = to_dwc3_qcom(dwc);
if (qcom->current_role == next_role)
return;
if (pm_runtime_resume_and_get(qcom->dev)) {
dev_dbg(qcom->dev, "Failed to resume device\n");
return;
}
if (qcom->current_role == USB_ROLE_DEVICE)
dwc3_qcom_vbus_override_enable(qcom, false);
else if (qcom->current_role != USB_ROLE_DEVICE)
dwc3_qcom_vbus_override_enable(qcom, true);
pm_runtime_mark_last_busy(qcom->dev);
pm_runtime_put_sync(qcom->dev);
/*
* Current role changes via usb_role_switch_set_role callback protected
* internally by mutex lock.
*/
qcom->current_role = next_role;
}
static void dwc3_qcom_run_stop_notifier(struct dwc3 *dwc, bool is_on)
{
struct dwc3_qcom *qcom = to_dwc3_qcom(dwc);
/*
* When autosuspend is enabled and controller goes to suspend
* after removing UDC from userspace, the next UDC write needs
* setting of QSCRATCH VBUS_VALID to "1" to generate a connect
* done event.
*/
if (!is_on)
return;
dwc3_qcom_vbus_override_enable(qcom, true);
pm_runtime_mark_last_busy(qcom->dev);
}
struct dwc3_glue_ops dwc3_qcom_glue_ops = {
.pre_set_role = dwc3_qcom_set_role_notifier,
.pre_run_stop = dwc3_qcom_run_stop_notifier,
};
static int dwc3_qcom_probe(struct platform_device *pdev)
{
struct dwc3_probe_data probe_data = {};
struct device *dev = &pdev->dev;
struct dwc3_qcom *qcom;
struct resource res;
struct resource *r;
int ret;
bool ignore_pipe_clk;
bool wakeup_source;
qcom = devm_kzalloc(&pdev->dev, sizeof(*qcom), GFP_KERNEL);
if (!qcom)
return -ENOMEM;
qcom->dev = &pdev->dev;
qcom->resets = devm_reset_control_array_get_optional_exclusive(dev);
if (IS_ERR(qcom->resets)) {
return dev_err_probe(&pdev->dev, PTR_ERR(qcom->resets),
"failed to get resets\n");
}
ret = devm_clk_bulk_get_all(&pdev->dev, &qcom->clks);
if (ret < 0)
return dev_err_probe(dev, ret, "failed to get clocks\n");
qcom->num_clocks = ret;
ret = reset_control_assert(qcom->resets);
if (ret) {
dev_err(&pdev->dev, "failed to assert resets, err=%d\n", ret);
return ret;
}
usleep_range(10, 1000);
ret = reset_control_deassert(qcom->resets);
if (ret) {
dev_err(&pdev->dev, "failed to deassert resets, err=%d\n", ret);
return ret;
}
ret = clk_bulk_prepare_enable(qcom->num_clocks, qcom->clks);
if (ret < 0)
return ret;
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!r) {
ret = -EINVAL;
goto clk_disable;
}
res = *r;
res.end = res.start + SDM845_QSCRATCH_BASE_OFFSET;
qcom->qscratch_base = devm_ioremap(dev, res.end, SDM845_QSCRATCH_SIZE);
if (!qcom->qscratch_base) {
dev_err(dev, "failed to map qscratch region\n");
ret = -ENOMEM;
goto clk_disable;
}
ret = dwc3_qcom_setup_irq(qcom, pdev);
if (ret) {
dev_err(dev, "failed to setup IRQs, err=%d\n", ret);
goto clk_disable;
}
/*
* Disable pipe_clk requirement if specified. Used when dwc3
* operates without SSPHY and only HS/FS/LS modes are supported.
*/
ignore_pipe_clk = device_property_read_bool(dev,
"qcom,select-utmi-as-pipe-clk");
if (ignore_pipe_clk)
dwc3_qcom_select_utmi_clk(qcom);
qcom->mode = usb_get_dr_mode(dev);
if (qcom->mode == USB_DR_MODE_HOST) {
qcom->current_role = USB_ROLE_HOST;
} else if (qcom->mode == USB_DR_MODE_PERIPHERAL) {
qcom->current_role = USB_ROLE_DEVICE;
dwc3_qcom_vbus_override_enable(qcom, true);
} else {
if ((device_property_read_bool(dev, "usb-role-switch")) &&
(usb_get_role_switch_default_mode(dev) == USB_DR_MODE_HOST))
qcom->current_role = USB_ROLE_HOST;
else
qcom->current_role = USB_ROLE_DEVICE;
}
qcom->dwc.glue_ops = &dwc3_qcom_glue_ops;
qcom->dwc.dev = dev;
probe_data.dwc = &qcom->dwc;
probe_data.res = &res;
probe_data.ignore_clocks_and_resets = true;
probe_data.properties = DWC3_DEFAULT_PROPERTIES;
ret = dwc3_core_probe(&probe_data);
if (ret) {
ret = dev_err_probe(dev, ret, "failed to register DWC3 Core\n");
goto clk_disable;
}
ret = dwc3_qcom_interconnect_init(qcom);
if (ret)
goto remove_core;
wakeup_source = of_property_read_bool(dev->of_node, "wakeup-source");
device_init_wakeup(&pdev->dev, wakeup_source);
qcom->is_suspended = false;
return 0;
remove_core:
dwc3_core_remove(&qcom->dwc);
clk_disable:
clk_bulk_disable_unprepare(qcom->num_clocks, qcom->clks);
return ret;
}
static void dwc3_qcom_remove(struct platform_device *pdev)
{
struct dwc3 *dwc = platform_get_drvdata(pdev);
struct dwc3_qcom *qcom = to_dwc3_qcom(dwc);
if (pm_runtime_resume_and_get(qcom->dev) < 0)
return;
dwc3_core_remove(&qcom->dwc);
clk_bulk_disable_unprepare(qcom->num_clocks, qcom->clks);
dwc3_qcom_interconnect_exit(qcom);
pm_runtime_put_noidle(qcom->dev);
}
static int dwc3_qcom_pm_suspend(struct device *dev)
{
struct dwc3 *dwc = dev_get_drvdata(dev);
struct dwc3_qcom *qcom = to_dwc3_qcom(dwc);
bool wakeup = device_may_wakeup(dev);
int ret;
ret = dwc3_pm_suspend(&qcom->dwc);
if (ret)
return ret;
ret = dwc3_qcom_suspend(qcom, wakeup);
if (ret)
return ret;
qcom->pm_suspended = true;
return 0;
}
static int dwc3_qcom_pm_resume(struct device *dev)
{
struct dwc3 *dwc = dev_get_drvdata(dev);
struct dwc3_qcom *qcom = to_dwc3_qcom(dwc);
bool wakeup = device_may_wakeup(dev);
int ret;
ret = dwc3_qcom_resume(qcom, wakeup);
if (ret)
return ret;
qcom->pm_suspended = false;
ret = dwc3_pm_resume(&qcom->dwc);
if (ret)
return ret;
return 0;
}
static void dwc3_qcom_complete(struct device *dev)
{
struct dwc3 *dwc = dev_get_drvdata(dev);
dwc3_pm_complete(dwc);
}
static int dwc3_qcom_prepare(struct device *dev)
{
struct dwc3 *dwc = dev_get_drvdata(dev);
return dwc3_pm_prepare(dwc);
}
static int dwc3_qcom_runtime_suspend(struct device *dev)
{
struct dwc3 *dwc = dev_get_drvdata(dev);
struct dwc3_qcom *qcom = to_dwc3_qcom(dwc);
int ret;
ret = dwc3_runtime_suspend(&qcom->dwc);
if (ret)
return ret;
return dwc3_qcom_suspend(qcom, true);
}
static int dwc3_qcom_runtime_resume(struct device *dev)
{
struct dwc3 *dwc = dev_get_drvdata(dev);
struct dwc3_qcom *qcom = to_dwc3_qcom(dwc);
int ret;
ret = dwc3_qcom_resume(qcom, true);
if (ret)
return ret;
return dwc3_runtime_resume(&qcom->dwc);
}
static int dwc3_qcom_runtime_idle(struct device *dev)
{
return dwc3_runtime_idle(dev_get_drvdata(dev));
}
static const struct dev_pm_ops dwc3_qcom_dev_pm_ops = {
SYSTEM_SLEEP_PM_OPS(dwc3_qcom_pm_suspend, dwc3_qcom_pm_resume)
RUNTIME_PM_OPS(dwc3_qcom_runtime_suspend, dwc3_qcom_runtime_resume,
dwc3_qcom_runtime_idle)
.complete = pm_sleep_ptr(dwc3_qcom_complete),
.prepare = pm_sleep_ptr(dwc3_qcom_prepare),
};
static const struct of_device_id dwc3_qcom_of_match[] = {
{ .compatible = "qcom,snps-dwc3" },
{ }
};
MODULE_DEVICE_TABLE(of, dwc3_qcom_of_match);
static struct platform_driver dwc3_qcom_driver = {
.probe = dwc3_qcom_probe,
.remove = dwc3_qcom_remove,
.shutdown = dwc3_qcom_remove,
.driver = {
.name = "dwc3-qcom",
.pm = pm_ptr(&dwc3_qcom_dev_pm_ops),
.of_match_table = dwc3_qcom_of_match,
},
};
module_platform_driver(dwc3_qcom_driver);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("DesignWare DWC3 QCOM Glue Driver");
+459
View File
@@ -0,0 +1,459 @@
// SPDX-License-Identifier: GPL-2.0
/*
* dwc3-rtk.c - Realtek DWC3 Specific Glue layer
*
* Copyright (C) 2023 Realtek Semiconductor Corporation
*
*/
#include <linux/cleanup.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/suspend.h>
#include <linux/sys_soc.h>
#include <linux/usb/otg.h>
#include <linux/usb/of.h>
#include <linux/usb/role.h>
#include "core.h"
#define WRAP_CTR_REG 0x0
#define DISABLE_MULTI_REQ BIT(1)
#define DESC_R2W_MULTI_DISABLE BIT(9)
#define FORCE_PIPE3_PHY_STATUS_TO_0 BIT(13)
#define WRAP_USB2_PHY_UTMI_REG 0x8
#define TXHSVM_EN BIT(3)
#define WRAP_PHY_PIPE_REG 0xC
#define RESET_DISABLE_PIPE3_P0 BIT(0)
#define CLOCK_ENABLE_FOR_PIPE3_PCLK BIT(1)
#define WRAP_USB_HMAC_CTR0_REG 0x60
#define U3PORT_DIS BIT(8)
#define WRAP_USB2_PHY_REG 0x70
#define USB2_PHY_EN_PHY_PLL_PORT0 BIT(12)
#define USB2_PHY_EN_PHY_PLL_PORT1 BIT(13)
#define USB2_PHY_SWITCH_MASK 0x707
#define USB2_PHY_SWITCH_DEVICE 0x0
#define USB2_PHY_SWITCH_HOST 0x606
#define WRAP_APHY_REG 0x128
#define USB3_MBIAS_ENABLE BIT(1)
/* pm control */
#define WRAP_USB_DBUS_PWR_CTRL_REG 0x160
#define USB_DBUS_PWR_CTRL_REG 0x0
#define DBUS_PWR_CTRL_EN BIT(0)
struct dwc3_rtk {
struct device *dev;
void __iomem *regs;
size_t regs_size;
void __iomem *pm_base;
struct dwc3 *dwc;
enum usb_role cur_role;
struct usb_role_switch *role_switch;
};
static void switch_usb2_role(struct dwc3_rtk *rtk, enum usb_role role)
{
void __iomem *reg;
int val;
reg = rtk->regs + WRAP_USB2_PHY_REG;
val = ~USB2_PHY_SWITCH_MASK & readl(reg);
switch (role) {
case USB_ROLE_DEVICE:
writel(USB2_PHY_SWITCH_DEVICE | val, reg);
break;
case USB_ROLE_HOST:
writel(USB2_PHY_SWITCH_HOST | val, reg);
break;
default:
dev_dbg(rtk->dev, "%s: role=%d\n", __func__, role);
break;
}
}
static void switch_dwc3_role(struct dwc3_rtk *rtk, enum usb_role role)
{
if (!rtk->dwc->role_sw)
return;
usb_role_switch_set_role(rtk->dwc->role_sw, role);
}
static enum usb_role dwc3_rtk_get_role(struct dwc3_rtk *rtk)
{
enum usb_role role;
role = rtk->cur_role;
if (rtk->dwc && rtk->dwc->role_sw)
role = usb_role_switch_get_role(rtk->dwc->role_sw);
else
dev_dbg(rtk->dev, "%s not usb_role_switch role=%d\n", __func__, role);
return role;
}
static void dwc3_rtk_set_role(struct dwc3_rtk *rtk, enum usb_role role)
{
rtk->cur_role = role;
switch_dwc3_role(rtk, role);
mdelay(10);
switch_usb2_role(rtk, role);
}
#if IS_ENABLED(CONFIG_USB_ROLE_SWITCH)
static int dwc3_usb_role_switch_set(struct usb_role_switch *sw, enum usb_role role)
{
struct dwc3_rtk *rtk = usb_role_switch_get_drvdata(sw);
dwc3_rtk_set_role(rtk, role);
return 0;
}
static enum usb_role dwc3_usb_role_switch_get(struct usb_role_switch *sw)
{
struct dwc3_rtk *rtk = usb_role_switch_get_drvdata(sw);
return dwc3_rtk_get_role(rtk);
}
static int dwc3_rtk_setup_role_switch(struct dwc3_rtk *rtk)
{
struct usb_role_switch_desc dwc3_role_switch = {NULL};
dwc3_role_switch.name = dev_name(rtk->dev);
dwc3_role_switch.driver_data = rtk;
dwc3_role_switch.allow_userspace_control = true;
dwc3_role_switch.fwnode = dev_fwnode(rtk->dev);
dwc3_role_switch.set = dwc3_usb_role_switch_set;
dwc3_role_switch.get = dwc3_usb_role_switch_get;
rtk->role_switch = usb_role_switch_register(rtk->dev, &dwc3_role_switch);
if (IS_ERR(rtk->role_switch))
return PTR_ERR(rtk->role_switch);
return 0;
}
static int dwc3_rtk_remove_role_switch(struct dwc3_rtk *rtk)
{
if (rtk->role_switch)
usb_role_switch_unregister(rtk->role_switch);
rtk->role_switch = NULL;
return 0;
}
#else
#define dwc3_rtk_setup_role_switch(x) 0
#define dwc3_rtk_remove_role_switch(x) 0
#endif
static const char *const speed_names[] = {
[USB_SPEED_UNKNOWN] = "UNKNOWN",
[USB_SPEED_LOW] = "low-speed",
[USB_SPEED_FULL] = "full-speed",
[USB_SPEED_HIGH] = "high-speed",
[USB_SPEED_WIRELESS] = "wireless",
[USB_SPEED_SUPER] = "super-speed",
[USB_SPEED_SUPER_PLUS] = "super-speed-plus",
};
static enum usb_device_speed __get_dwc3_maximum_speed(struct device_node *np)
{
const char *maximum_speed;
int ret;
struct device_node *dwc3_np __free(device_node) = of_get_compatible_child(np,
"snps,dwc3");
if (!dwc3_np)
return USB_SPEED_UNKNOWN;
ret = of_property_read_string(dwc3_np, "maximum-speed", &maximum_speed);
if (ret < 0)
return USB_SPEED_UNKNOWN;
ret = match_string(speed_names, ARRAY_SIZE(speed_names), maximum_speed);
return (ret < 0) ? USB_SPEED_UNKNOWN : ret;
}
static int dwc3_rtk_init(struct dwc3_rtk *rtk)
{
struct device *dev = rtk->dev;
void __iomem *reg;
int val;
enum usb_device_speed maximum_speed;
const struct soc_device_attribute rtk_soc_kylin_a00[] = {
{ .family = "Realtek Kylin", .revision = "A00", },
{ /* empty */ } };
const struct soc_device_attribute rtk_soc_hercules[] = {
{ .family = "Realtek Hercules", }, { /* empty */ } };
const struct soc_device_attribute rtk_soc_thor[] = {
{ .family = "Realtek Thor", }, { /* empty */ } };
if (soc_device_match(rtk_soc_kylin_a00)) {
reg = rtk->regs + WRAP_CTR_REG;
val = readl(reg);
writel(DISABLE_MULTI_REQ | val, reg);
dev_info(dev, "[bug fixed] 1295/1296 A00: add workaround to disable multiple request for D-Bus");
}
if (soc_device_match(rtk_soc_hercules)) {
reg = rtk->regs + WRAP_USB2_PHY_REG;
val = readl(reg);
writel(USB2_PHY_EN_PHY_PLL_PORT1 | val, reg);
dev_info(dev, "[bug fixed] 1395 add workaround to disable usb2 port 2 suspend!");
}
reg = rtk->regs + WRAP_USB2_PHY_UTMI_REG;
val = readl(reg);
writel(TXHSVM_EN | val, reg);
maximum_speed = __get_dwc3_maximum_speed(dev->of_node);
if (maximum_speed != USB_SPEED_UNKNOWN && maximum_speed <= USB_SPEED_HIGH) {
if (soc_device_match(rtk_soc_thor)) {
reg = rtk->regs + WRAP_USB_HMAC_CTR0_REG;
val = readl(reg);
writel(U3PORT_DIS | val, reg);
} else {
reg = rtk->regs + WRAP_CTR_REG;
val = readl(reg);
writel(FORCE_PIPE3_PHY_STATUS_TO_0 | val, reg);
reg = rtk->regs + WRAP_PHY_PIPE_REG;
val = ~CLOCK_ENABLE_FOR_PIPE3_PCLK & readl(reg);
writel(RESET_DISABLE_PIPE3_P0 | val, reg);
reg = rtk->regs + WRAP_USB_HMAC_CTR0_REG;
val = readl(reg);
writel(U3PORT_DIS | val, reg);
reg = rtk->regs + WRAP_APHY_REG;
val = readl(reg);
writel(~USB3_MBIAS_ENABLE & val, reg);
dev_dbg(rtk->dev, "%s: disable usb 3.0 phy\n", __func__);
}
}
reg = rtk->regs + WRAP_CTR_REG;
val = readl(reg);
writel(DESC_R2W_MULTI_DISABLE | val, reg);
/* Set phy Dp/Dm initial state to host mode to avoid the Dp glitch */
reg = rtk->regs + WRAP_USB2_PHY_REG;
val = ~USB2_PHY_SWITCH_MASK & readl(reg);
writel(USB2_PHY_SWITCH_HOST | val, reg);
if (rtk->pm_base) {
reg = rtk->pm_base + USB_DBUS_PWR_CTRL_REG;
val = DBUS_PWR_CTRL_EN | readl(reg);
writel(val, reg);
}
return 0;
}
static int dwc3_rtk_probe_dwc3_core(struct dwc3_rtk *rtk)
{
struct device *dev = rtk->dev;
struct device_node *node = dev->of_node;
struct platform_device *dwc3_pdev;
struct device *dwc3_dev;
enum usb_dr_mode dr_mode;
int ret = 0;
ret = dwc3_rtk_init(rtk);
if (ret)
return -EINVAL;
ret = of_platform_populate(node, NULL, NULL, dev);
if (ret) {
dev_err(dev, "failed to add dwc3 core\n");
return ret;
}
struct device_node *dwc3_node __free(device_node) = of_get_compatible_child(node,
"snps,dwc3");
if (!dwc3_node) {
dev_err(dev, "failed to find dwc3 core node\n");
ret = -ENODEV;
goto depopulate;
}
dwc3_pdev = of_find_device_by_node(dwc3_node);
if (!dwc3_pdev) {
dev_err(dev, "failed to find dwc3 core platform_device\n");
ret = -ENODEV;
goto depopulate;
}
dwc3_dev = &dwc3_pdev->dev;
rtk->dwc = platform_get_drvdata(dwc3_pdev);
if (!rtk->dwc) {
dev_err(dev, "failed to find dwc3 core\n");
ret = -ENODEV;
goto err_pdev_put;
}
dr_mode = usb_get_dr_mode(dwc3_dev);
if (dr_mode != rtk->dwc->dr_mode) {
dev_info(dev, "dts set dr_mode=%d, but dwc3 set dr_mode=%d\n",
dr_mode, rtk->dwc->dr_mode);
dr_mode = rtk->dwc->dr_mode;
}
switch (dr_mode) {
case USB_DR_MODE_PERIPHERAL:
rtk->cur_role = USB_ROLE_DEVICE;
break;
case USB_DR_MODE_HOST:
rtk->cur_role = USB_ROLE_HOST;
break;
default:
dev_dbg(rtk->dev, "%s: dr_mode=%d\n", __func__, dr_mode);
break;
}
if (device_property_read_bool(dwc3_dev, "usb-role-switch")) {
ret = dwc3_rtk_setup_role_switch(rtk);
if (ret) {
dev_err(dev, "dwc3_rtk_setup_role_switch fail=%d\n", ret);
goto err_pdev_put;
}
rtk->cur_role = dwc3_rtk_get_role(rtk);
}
switch_usb2_role(rtk, rtk->cur_role);
platform_device_put(dwc3_pdev);
return 0;
err_pdev_put:
platform_device_put(dwc3_pdev);
depopulate:
of_platform_depopulate(dev);
return ret;
}
static int dwc3_rtk_probe(struct platform_device *pdev)
{
struct dwc3_rtk *rtk;
struct device *dev = &pdev->dev;
struct resource *res;
void __iomem *regs;
rtk = devm_kzalloc(dev, sizeof(*rtk), GFP_KERNEL);
if (!rtk)
return -ENOMEM;
platform_set_drvdata(pdev, rtk);
rtk->dev = dev;
regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
if (IS_ERR(regs))
return PTR_ERR(regs);
rtk->regs = regs;
rtk->regs_size = resource_size(res);
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
if (res) {
rtk->pm_base = devm_ioremap_resource(dev, res);
if (IS_ERR(rtk->pm_base))
return PTR_ERR(rtk->pm_base);
}
return dwc3_rtk_probe_dwc3_core(rtk);
}
static void dwc3_rtk_remove(struct platform_device *pdev)
{
struct dwc3_rtk *rtk = platform_get_drvdata(pdev);
rtk->dwc = NULL;
dwc3_rtk_remove_role_switch(rtk);
of_platform_depopulate(rtk->dev);
}
static void dwc3_rtk_shutdown(struct platform_device *pdev)
{
struct dwc3_rtk *rtk = platform_get_drvdata(pdev);
of_platform_depopulate(rtk->dev);
}
static const struct of_device_id rtk_dwc3_match[] = {
{ .compatible = "realtek,rtd-dwc3" },
{},
};
MODULE_DEVICE_TABLE(of, rtk_dwc3_match);
#ifdef CONFIG_PM_SLEEP
static int dwc3_rtk_suspend(struct device *dev)
{
return 0;
}
static int dwc3_rtk_resume(struct device *dev)
{
struct dwc3_rtk *rtk = dev_get_drvdata(dev);
dwc3_rtk_init(rtk);
switch_usb2_role(rtk, rtk->cur_role);
/* runtime set active to reflect active state. */
pm_runtime_disable(dev);
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
return 0;
}
static const struct dev_pm_ops dwc3_rtk_dev_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(dwc3_rtk_suspend, dwc3_rtk_resume)
};
#define DEV_PM_OPS (&dwc3_rtk_dev_pm_ops)
#else
#define DEV_PM_OPS NULL
#endif /* CONFIG_PM_SLEEP */
static struct platform_driver dwc3_rtk_driver = {
.probe = dwc3_rtk_probe,
.remove = dwc3_rtk_remove,
.driver = {
.name = "rtk-dwc3",
.of_match_table = rtk_dwc3_match,
.pm = DEV_PM_OPS,
},
.shutdown = dwc3_rtk_shutdown,
};
module_platform_driver(dwc3_rtk_driver);
MODULE_AUTHOR("Stanley Chang <stanley_chang@realtek.com>");
MODULE_DESCRIPTION("DesignWare USB3 Realtek Glue Layer");
MODULE_ALIAS("platform:rtk-dwc3");
MODULE_LICENSE("GPL");
MODULE_SOFTDEP("pre: phy_rtk_usb2 phy_rtk_usb3");
+369
View File
@@ -0,0 +1,369 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* dwc3-st.c Support for dwc3 platform devices on ST Microelectronics platforms
*
* This is a small driver for the dwc3 to provide the glue logic
* to configure the controller. Tested on STi platforms.
*
* Copyright (C) 2014 Stmicroelectronics
*
* Author: Giuseppe Cavallaro <peppe.cavallaro@st.com>
* Contributors: Aymen Bouattay <aymen.bouattay@st.com>
* Peter Griffin <peter.griffin@linaro.org>
*
* Inspired by dwc3-omap.c and dwc3-exynos.c.
*/
#include <linux/cleanup.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/kernel.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include <linux/pinctrl/consumer.h>
#include <linux/usb/of.h>
#include "core.h"
#include "io.h"
/* glue registers */
#define CLKRST_CTRL 0x00
#define AUX_CLK_EN BIT(0)
#define SW_PIPEW_RESET_N BIT(4)
#define EXT_CFG_RESET_N BIT(8)
/*
* 1'b0 : The host controller complies with the xHCI revision 0.96
* 1'b1 : The host controller complies with the xHCI revision 1.0
*/
#define XHCI_REVISION BIT(12)
#define USB2_VBUS_MNGMNT_SEL1 0x2C
/*
* For all fields in USB2_VBUS_MNGMNT_SEL1
* 2’b00 : Override value from Reg 0x30 is selected
* 2’b01 : utmiotg_<signal_name> from usb3_top is selected
* 2’b10 : pipew_<signal_name> from PIPEW instance is selected
* 2’b11 : value is 1'b0
*/
#define USB2_VBUS_REG30 0x0
#define USB2_VBUS_UTMIOTG 0x1
#define USB2_VBUS_PIPEW 0x2
#define USB2_VBUS_ZERO 0x3
#define SEL_OVERRIDE_VBUSVALID(n) (n << 0)
#define SEL_OVERRIDE_POWERPRESENT(n) (n << 4)
#define SEL_OVERRIDE_BVALID(n) (n << 8)
/* Static DRD configuration */
#define USB3_CONTROL_MASK 0xf77
#define USB3_DEVICE_NOT_HOST BIT(0)
#define USB3_FORCE_VBUSVALID BIT(1)
#define USB3_DELAY_VBUSVALID BIT(2)
#define USB3_SEL_FORCE_OPMODE BIT(4)
#define USB3_FORCE_OPMODE(n) (n << 5)
#define USB3_SEL_FORCE_DPPULLDOWN2 BIT(8)
#define USB3_FORCE_DPPULLDOWN2 BIT(9)
#define USB3_SEL_FORCE_DMPULLDOWN2 BIT(10)
#define USB3_FORCE_DMPULLDOWN2 BIT(11)
/**
* struct st_dwc3 - dwc3-st driver private structure
* @dev: device pointer
* @glue_base: ioaddr for the glue registers
* @regmap: regmap pointer for getting syscfg
* @syscfg_reg_off: usb syscfg control offset
* @dr_mode: drd static host/device config
* @rstc_pwrdn: rest controller for powerdown signal
* @rstc_rst: reset controller for softreset signal
*/
struct st_dwc3 {
struct device *dev;
void __iomem *glue_base;
struct regmap *regmap;
int syscfg_reg_off;
enum usb_dr_mode dr_mode;
struct reset_control *rstc_pwrdn;
struct reset_control *rstc_rst;
};
static inline u32 st_dwc3_readl(void __iomem *base, u32 offset)
{
return readl_relaxed(base + offset);
}
static inline void st_dwc3_writel(void __iomem *base, u32 offset, u32 value)
{
writel_relaxed(value, base + offset);
}
/**
* st_dwc3_drd_init: program the port
* @dwc3_data: driver private structure
* Description: this function is to program the port as either host or device
* according to the static configuration passed from devicetree.
* OTG and dual role are not yet supported!
*/
static int st_dwc3_drd_init(struct st_dwc3 *dwc3_data)
{
u32 val;
int err;
err = regmap_read(dwc3_data->regmap, dwc3_data->syscfg_reg_off, &val);
if (err)
return err;
val &= USB3_CONTROL_MASK;
switch (dwc3_data->dr_mode) {
case USB_DR_MODE_PERIPHERAL:
val &= ~(USB3_DELAY_VBUSVALID
| USB3_SEL_FORCE_OPMODE | USB3_FORCE_OPMODE(0x3)
| USB3_SEL_FORCE_DPPULLDOWN2 | USB3_FORCE_DPPULLDOWN2
| USB3_SEL_FORCE_DMPULLDOWN2 | USB3_FORCE_DMPULLDOWN2);
/*
* USB3_PORT2_FORCE_VBUSVALID When '1' and when
* USB3_PORT2_DEVICE_NOT_HOST = 1, forces VBUSVLDEXT2 input
* of the pico PHY to 1.
*/
val |= USB3_DEVICE_NOT_HOST | USB3_FORCE_VBUSVALID;
break;
case USB_DR_MODE_HOST:
val &= ~(USB3_DEVICE_NOT_HOST | USB3_FORCE_VBUSVALID
| USB3_SEL_FORCE_OPMODE | USB3_FORCE_OPMODE(0x3)
| USB3_SEL_FORCE_DPPULLDOWN2 | USB3_FORCE_DPPULLDOWN2
| USB3_SEL_FORCE_DMPULLDOWN2 | USB3_FORCE_DMPULLDOWN2);
/*
* USB3_DELAY_VBUSVALID is ANDed with USB_C_VBUSVALID. Thus,
* when set to ‘0‘, it can delay the arrival of VBUSVALID
* information to VBUSVLDEXT2 input of the pico PHY.
* We don't want to do that so we set the bit to '1'.
*/
val |= USB3_DELAY_VBUSVALID;
break;
default:
dev_err(dwc3_data->dev, "Unsupported mode of operation %d\n",
dwc3_data->dr_mode);
return -EINVAL;
}
return regmap_write(dwc3_data->regmap, dwc3_data->syscfg_reg_off, val);
}
/**
* st_dwc3_init: init the controller via glue logic
* @dwc3_data: driver private structure
*/
static void st_dwc3_init(struct st_dwc3 *dwc3_data)
{
u32 reg = st_dwc3_readl(dwc3_data->glue_base, CLKRST_CTRL);
reg |= AUX_CLK_EN | EXT_CFG_RESET_N | XHCI_REVISION;
reg &= ~SW_PIPEW_RESET_N;
st_dwc3_writel(dwc3_data->glue_base, CLKRST_CTRL, reg);
/* configure mux for vbus, powerpresent and bvalid signals */
reg = st_dwc3_readl(dwc3_data->glue_base, USB2_VBUS_MNGMNT_SEL1);
reg |= SEL_OVERRIDE_VBUSVALID(USB2_VBUS_UTMIOTG) |
SEL_OVERRIDE_POWERPRESENT(USB2_VBUS_UTMIOTG) |
SEL_OVERRIDE_BVALID(USB2_VBUS_UTMIOTG);
st_dwc3_writel(dwc3_data->glue_base, USB2_VBUS_MNGMNT_SEL1, reg);
reg = st_dwc3_readl(dwc3_data->glue_base, CLKRST_CTRL);
reg |= SW_PIPEW_RESET_N;
st_dwc3_writel(dwc3_data->glue_base, CLKRST_CTRL, reg);
}
static int st_dwc3_probe(struct platform_device *pdev)
{
struct st_dwc3 *dwc3_data;
struct resource *res;
struct device *dev = &pdev->dev;
struct device_node *node = dev->of_node;
struct platform_device *child_pdev;
struct regmap *regmap;
int ret;
dwc3_data = devm_kzalloc(dev, sizeof(*dwc3_data), GFP_KERNEL);
if (!dwc3_data)
return -ENOMEM;
dwc3_data->glue_base =
devm_platform_ioremap_resource_byname(pdev, "reg-glue");
if (IS_ERR(dwc3_data->glue_base))
return PTR_ERR(dwc3_data->glue_base);
regmap = syscon_regmap_lookup_by_phandle(node, "st,syscfg");
if (IS_ERR(regmap))
return PTR_ERR(regmap);
dwc3_data->dev = dev;
dwc3_data->regmap = regmap;
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "syscfg-reg");
if (!res)
return -ENXIO;
dwc3_data->syscfg_reg_off = res->start;
dev_vdbg(dev, "glue-logic addr 0x%p, syscfg-reg offset 0x%x\n",
dwc3_data->glue_base, dwc3_data->syscfg_reg_off);
struct device_node *child __free(device_node) = of_get_compatible_child(node,
"snps,dwc3");
if (!child) {
dev_err(dev, "failed to find dwc3 core node\n");
return -ENODEV;
}
dwc3_data->rstc_pwrdn =
devm_reset_control_get_exclusive(dev, "powerdown");
if (IS_ERR(dwc3_data->rstc_pwrdn))
return dev_err_probe(dev, PTR_ERR(dwc3_data->rstc_pwrdn),
"could not get power controller\n");
/* Manage PowerDown */
reset_control_deassert(dwc3_data->rstc_pwrdn);
dwc3_data->rstc_rst =
devm_reset_control_get_shared(dev, "softreset");
if (IS_ERR(dwc3_data->rstc_rst)) {
ret = dev_err_probe(dev, PTR_ERR(dwc3_data->rstc_rst),
"could not get reset controller\n");
goto undo_powerdown;
}
/* Manage SoftReset */
reset_control_deassert(dwc3_data->rstc_rst);
/* Allocate and initialize the core */
ret = of_platform_populate(node, NULL, NULL, dev);
if (ret) {
dev_err(dev, "failed to add dwc3 core\n");
goto undo_softreset;
}
child_pdev = of_find_device_by_node(child);
if (!child_pdev) {
dev_err(dev, "failed to find dwc3 core device\n");
ret = -ENODEV;
goto depopulate;
}
dwc3_data->dr_mode = usb_get_dr_mode(&child_pdev->dev);
platform_device_put(child_pdev);
/*
* Configure the USB port as device or host according to the static
* configuration passed from DT.
* DRD is the only mode currently supported so this will be enhanced
* as soon as OTG is available.
*/
ret = st_dwc3_drd_init(dwc3_data);
if (ret) {
dev_err(dev, "drd initialisation failed\n");
goto depopulate;
}
/* ST glue logic init */
st_dwc3_init(dwc3_data);
platform_set_drvdata(pdev, dwc3_data);
return 0;
depopulate:
of_platform_depopulate(dev);
undo_softreset:
reset_control_assert(dwc3_data->rstc_rst);
undo_powerdown:
reset_control_assert(dwc3_data->rstc_pwrdn);
return ret;
}
static void st_dwc3_remove(struct platform_device *pdev)
{
struct st_dwc3 *dwc3_data = platform_get_drvdata(pdev);
of_platform_depopulate(&pdev->dev);
reset_control_assert(dwc3_data->rstc_pwrdn);
reset_control_assert(dwc3_data->rstc_rst);
}
static int st_dwc3_suspend(struct device *dev)
{
struct st_dwc3 *dwc3_data = dev_get_drvdata(dev);
reset_control_assert(dwc3_data->rstc_pwrdn);
reset_control_assert(dwc3_data->rstc_rst);
pinctrl_pm_select_sleep_state(dev);
return 0;
}
static int st_dwc3_resume(struct device *dev)
{
struct st_dwc3 *dwc3_data = dev_get_drvdata(dev);
int ret;
pinctrl_pm_select_default_state(dev);
reset_control_deassert(dwc3_data->rstc_pwrdn);
reset_control_deassert(dwc3_data->rstc_rst);
ret = st_dwc3_drd_init(dwc3_data);
if (ret) {
dev_err(dev, "drd initialisation failed\n");
return ret;
}
/* ST glue logic init */
st_dwc3_init(dwc3_data);
return 0;
}
static DEFINE_SIMPLE_DEV_PM_OPS(st_dwc3_dev_pm_ops, st_dwc3_suspend, st_dwc3_resume);
static const struct of_device_id st_dwc3_match[] = {
{ .compatible = "st,stih407-dwc3" },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, st_dwc3_match);
static struct platform_driver st_dwc3_driver = {
.probe = st_dwc3_probe,
.remove = st_dwc3_remove,
.driver = {
.name = "usb-st-dwc3",
.of_match_table = st_dwc3_match,
.pm = pm_sleep_ptr(&st_dwc3_dev_pm_ops),
},
};
module_platform_driver(st_dwc3_driver);
MODULE_AUTHOR("Giuseppe Cavallaro <peppe.cavallaro@st.com>");
MODULE_DESCRIPTION("DesignWare USB3 STi Glue Layer");
MODULE_LICENSE("GPL v2");
+447
View File
@@ -0,0 +1,447 @@
// SPDX-License-Identifier: GPL-2.0
/*
* dwc3-xilinx.c - Xilinx DWC3 controller specific glue driver
*
* Authors: Manish Narani <manish.narani@xilinx.com>
* Anurag Kumar Vulisha <anurag.kumar.vulisha@xilinx.com>
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/clk.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/gpio/consumer.h>
#include <linux/of_platform.h>
#include <linux/pm_runtime.h>
#include <linux/reset.h>
#include <linux/of_address.h>
#include <linux/delay.h>
#include <linux/firmware/xlnx-zynqmp.h>
#include <linux/io.h>
#include <linux/phy/phy.h>
/* USB phy reset mask register */
#define XLNX_USB_PHY_RST_EN 0x001C
#define XLNX_PHY_RST_MASK 0x1
/* Xilinx USB 3.0 IP Register */
#define XLNX_USB_TRAFFIC_ROUTE_CONFIG 0x005C
#define XLNX_USB_TRAFFIC_ROUTE_FPD 0x1
/* USB 2.0 IP Register */
#define XLNX_USB2_TRAFFIC_ROUTE_CONFIG 0x0044
#define XLNX_USB_FPD_PIPE_CLK 0x7c
#define PIPE_CLK_DESELECT 1
#define PIPE_CLK_SELECT 0
#define XLNX_USB_FPD_POWER_PRSNT 0x80
#define FPD_POWER_PRSNT_OPTION BIT(0)
struct dwc3_xlnx {
int num_clocks;
struct clk_bulk_data *clks;
struct device *dev;
void __iomem *regs;
int (*pltfm_init)(struct dwc3_xlnx *data);
struct phy *usb3_phy;
};
static void dwc3_xlnx_mask_phy_rst(struct dwc3_xlnx *priv_data, bool mask)
{
u32 reg;
/*
* Enable or disable ULPI PHY reset from USB Controller.
* This does not actually reset the phy, but just controls
* whether USB controller can or cannot reset ULPI PHY.
*/
reg = readl(priv_data->regs + XLNX_USB_PHY_RST_EN);
if (mask)
reg &= ~XLNX_PHY_RST_MASK;
else
reg |= XLNX_PHY_RST_MASK;
writel(reg, priv_data->regs + XLNX_USB_PHY_RST_EN);
}
static void dwc3_xlnx_set_coherency(struct dwc3_xlnx *priv_data, u32 coherency_offset)
{
struct device *dev = priv_data->dev;
u32 reg;
/*
* This routes the USB DMA traffic to go through FPD path instead
* of reaching DDR directly. This traffic routing is needed to
* make SMMU and CCI work with USB DMA.
*/
if (of_dma_is_coherent(dev->of_node) || device_iommu_mapped(dev)) {
reg = readl(priv_data->regs + coherency_offset);
reg |= XLNX_USB_TRAFFIC_ROUTE_FPD;
writel(reg, priv_data->regs + coherency_offset);
}
}
static int dwc3_xlnx_init_versal(struct dwc3_xlnx *priv_data)
{
struct device *dev = priv_data->dev;
struct reset_control *crst;
int ret;
crst = devm_reset_control_get_exclusive(dev, NULL);
if (IS_ERR(crst))
return dev_err_probe(dev, PTR_ERR(crst), "failed to get reset signal\n");
dwc3_xlnx_mask_phy_rst(priv_data, false);
/* Assert and De-assert reset */
ret = reset_control_assert(crst);
if (ret < 0) {
dev_err_probe(dev, ret, "failed to assert Reset\n");
return ret;
}
ret = reset_control_deassert(crst);
if (ret < 0) {
dev_err_probe(dev, ret, "failed to De-assert Reset\n");
return ret;
}
dwc3_xlnx_mask_phy_rst(priv_data, true);
dwc3_xlnx_set_coherency(priv_data, XLNX_USB2_TRAFFIC_ROUTE_CONFIG);
return 0;
}
static int dwc3_xlnx_init_zynqmp(struct dwc3_xlnx *priv_data)
{
struct device *dev = priv_data->dev;
struct reset_control *crst, *hibrst, *apbrst;
struct gpio_desc *reset_gpio;
int ret = 0;
priv_data->usb3_phy = devm_phy_optional_get(dev, "usb3-phy");
if (IS_ERR(priv_data->usb3_phy)) {
ret = PTR_ERR(priv_data->usb3_phy);
dev_err_probe(dev, ret,
"failed to get USB3 PHY\n");
goto err;
}
crst = devm_reset_control_get_exclusive(dev, "usb_crst");
if (IS_ERR(crst)) {
ret = PTR_ERR(crst);
dev_err_probe(dev, ret,
"failed to get core reset signal\n");
goto err;
}
hibrst = devm_reset_control_get_exclusive(dev, "usb_hibrst");
if (IS_ERR(hibrst)) {
ret = PTR_ERR(hibrst);
dev_err_probe(dev, ret,
"failed to get hibernation reset signal\n");
goto err;
}
apbrst = devm_reset_control_get_exclusive(dev, "usb_apbrst");
if (IS_ERR(apbrst)) {
ret = PTR_ERR(apbrst);
dev_err_probe(dev, ret,
"failed to get APB reset signal\n");
goto err;
}
/*
* Asserting the core resets is not required unless a USB3 PHY is used.
* They may also break the configuration if USB3 is actually in use but
* the usb3-phy entry is missing from the device tree. Therefore, skip
* a full reset cycle and just deassert the resets if the phy is
* absent.
*/
if (priv_data->usb3_phy) {
ret = reset_control_assert(crst);
if (ret < 0) {
dev_err(dev, "Failed to assert core reset\n");
goto err;
}
ret = reset_control_assert(hibrst);
if (ret < 0) {
dev_err(dev, "Failed to assert hibernation reset\n");
goto err;
}
ret = reset_control_assert(apbrst);
if (ret < 0) {
dev_err(dev, "Failed to assert APB reset\n");
goto err;
}
}
ret = phy_init(priv_data->usb3_phy);
if (ret < 0)
goto err;
ret = reset_control_deassert(apbrst);
if (ret < 0) {
dev_err(dev, "Failed to release APB reset\n");
goto err_phy_exit;
}
if (priv_data->usb3_phy) {
/* Set PIPE Power Present signal in FPD Power Present Register*/
writel(FPD_POWER_PRSNT_OPTION, priv_data->regs + XLNX_USB_FPD_POWER_PRSNT);
/* Set the PIPE Clock Select bit in FPD PIPE Clock register */
writel(PIPE_CLK_SELECT, priv_data->regs + XLNX_USB_FPD_PIPE_CLK);
} else {
/* Deselect the PIPE Clock Select bit in FPD PIPE Clock register */
writel(PIPE_CLK_DESELECT, priv_data->regs + XLNX_USB_FPD_PIPE_CLK);
}
ret = reset_control_deassert(crst);
if (ret < 0) {
dev_err(dev, "Failed to release core reset\n");
goto err_phy_exit;
}
ret = reset_control_deassert(hibrst);
if (ret < 0) {
dev_err(dev, "Failed to release hibernation reset\n");
goto err_phy_exit;
}
ret = phy_power_on(priv_data->usb3_phy);
if (ret < 0)
goto err_phy_exit;
/* ulpi reset via gpio-modepin or gpio-framework driver */
reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
if (IS_ERR(reset_gpio)) {
ret = PTR_ERR(reset_gpio);
goto err_phy_power_off;
}
if (reset_gpio) {
usleep_range(5000, 10000);
gpiod_set_value_cansleep(reset_gpio, 0);
usleep_range(5000, 10000);
}
dwc3_xlnx_set_coherency(priv_data, XLNX_USB_TRAFFIC_ROUTE_CONFIG);
return 0;
err_phy_power_off:
phy_power_off(priv_data->usb3_phy);
err_phy_exit:
phy_exit(priv_data->usb3_phy);
err:
return ret;
}
static const struct of_device_id dwc3_xlnx_of_match[] = {
{
.compatible = "xlnx,zynqmp-dwc3",
.data = &dwc3_xlnx_init_zynqmp,
},
{
.compatible = "xlnx,versal-dwc3",
.data = &dwc3_xlnx_init_versal,
},
{ /* Sentinel */ }
};
MODULE_DEVICE_TABLE(of, dwc3_xlnx_of_match);
static int dwc3_set_swnode(struct device *dev)
{
struct device_node *np = dev->of_node, *dwc3_np;
struct property_entry props[2];
int prop_idx = 0, ret = 0;
dwc3_np = of_get_compatible_child(np, "snps,dwc3");
if (!dwc3_np) {
ret = -ENODEV;
dev_err(dev, "failed to find dwc3 core child\n");
return ret;
}
memset(props, 0, sizeof(struct property_entry) * ARRAY_SIZE(props));
if (of_dma_is_coherent(dwc3_np))
props[prop_idx++] = PROPERTY_ENTRY_U16("snps,gsbuscfg0-reqinfo",
0xffff);
of_node_put(dwc3_np);
if (prop_idx)
ret = device_create_managed_software_node(dev, props, NULL);
return ret;
}
static int dwc3_xlnx_probe(struct platform_device *pdev)
{
struct dwc3_xlnx *priv_data;
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
const struct of_device_id *match;
void __iomem *regs;
int ret;
priv_data = devm_kzalloc(dev, sizeof(*priv_data), GFP_KERNEL);
if (!priv_data)
return -ENOMEM;
regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(regs))
return dev_err_probe(dev, PTR_ERR(regs), "failed to map registers\n");
match = of_match_node(dwc3_xlnx_of_match, pdev->dev.of_node);
priv_data->pltfm_init = match->data;
priv_data->regs = regs;
priv_data->dev = dev;
platform_set_drvdata(pdev, priv_data);
ret = devm_clk_bulk_get_all(priv_data->dev, &priv_data->clks);
if (ret < 0)
return ret;
priv_data->num_clocks = ret;
ret = clk_bulk_prepare_enable(priv_data->num_clocks, priv_data->clks);
if (ret)
return ret;
ret = priv_data->pltfm_init(priv_data);
if (ret)
goto err_clk_put;
ret = dwc3_set_swnode(dev);
if (ret)
goto err_clk_put;
ret = of_platform_populate(np, NULL, NULL, dev);
if (ret)
goto err_clk_put;
pm_runtime_set_active(dev);
ret = devm_pm_runtime_enable(dev);
if (ret < 0)
goto err_pm_set_suspended;
pm_suspend_ignore_children(dev, false);
ret = pm_runtime_resume_and_get(dev);
if (ret < 0)
goto err_pm_set_suspended;
return 0;
err_pm_set_suspended:
of_platform_depopulate(dev);
pm_runtime_set_suspended(dev);
err_clk_put:
clk_bulk_disable_unprepare(priv_data->num_clocks, priv_data->clks);
return ret;
}
static void dwc3_xlnx_remove(struct platform_device *pdev)
{
struct dwc3_xlnx *priv_data = platform_get_drvdata(pdev);
struct device *dev = &pdev->dev;
of_platform_depopulate(dev);
clk_bulk_disable_unprepare(priv_data->num_clocks, priv_data->clks);
priv_data->num_clocks = 0;
pm_runtime_put_noidle(dev);
pm_runtime_set_suspended(dev);
}
static int __maybe_unused dwc3_xlnx_runtime_suspend(struct device *dev)
{
struct dwc3_xlnx *priv_data = dev_get_drvdata(dev);
clk_bulk_disable(priv_data->num_clocks, priv_data->clks);
return 0;
}
static int __maybe_unused dwc3_xlnx_runtime_resume(struct device *dev)
{
struct dwc3_xlnx *priv_data = dev_get_drvdata(dev);
return clk_bulk_enable(priv_data->num_clocks, priv_data->clks);
}
static int __maybe_unused dwc3_xlnx_runtime_idle(struct device *dev)
{
pm_runtime_autosuspend(dev);
return 0;
}
static int __maybe_unused dwc3_xlnx_suspend(struct device *dev)
{
struct dwc3_xlnx *priv_data = dev_get_drvdata(dev);
phy_exit(priv_data->usb3_phy);
/* Disable the clocks */
clk_bulk_disable(priv_data->num_clocks, priv_data->clks);
return 0;
}
static int __maybe_unused dwc3_xlnx_resume(struct device *dev)
{
struct dwc3_xlnx *priv_data = dev_get_drvdata(dev);
int ret;
ret = clk_bulk_enable(priv_data->num_clocks, priv_data->clks);
if (ret)
return ret;
ret = phy_init(priv_data->usb3_phy);
if (ret < 0)
return ret;
ret = phy_power_on(priv_data->usb3_phy);
if (ret < 0) {
phy_exit(priv_data->usb3_phy);
return ret;
}
return 0;
}
static const struct dev_pm_ops dwc3_xlnx_dev_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(dwc3_xlnx_suspend, dwc3_xlnx_resume)
SET_RUNTIME_PM_OPS(dwc3_xlnx_runtime_suspend,
dwc3_xlnx_runtime_resume, dwc3_xlnx_runtime_idle)
};
static struct platform_driver dwc3_xlnx_driver = {
.probe = dwc3_xlnx_probe,
.remove = dwc3_xlnx_remove,
.shutdown = dwc3_xlnx_remove,
.driver = {
.name = "dwc3-xilinx",
.of_match_table = dwc3_xlnx_of_match,
.pm = &dwc3_xlnx_dev_pm_ops,
},
};
module_platform_driver(dwc3_xlnx_driver);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Xilinx DWC3 controller specific glue driver");
MODULE_AUTHOR("Manish Narani <manish.narani@xilinx.com>");
MODULE_AUTHOR("Anurag Kumar Vulisha <anurag.kumar.vulisha@xilinx.com>");
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+153
View File
@@ -0,0 +1,153 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* gadget.h - DesignWare USB3 DRD Gadget Header
*
* Copyright (C) 2010-2011 Texas Instruments Incorporated - https://www.ti.com
*
* Authors: Felipe Balbi <balbi@ti.com>,
* Sebastian Andrzej Siewior <bigeasy@linutronix.de>
*/
#ifndef __DRIVERS_USB_DWC3_GADGET_H
#define __DRIVERS_USB_DWC3_GADGET_H
#include <linux/list.h>
#include <linux/usb/gadget.h>
#include "io.h"
struct dwc3;
#define to_dwc3_ep(ep) (container_of(ep, struct dwc3_ep, endpoint))
#define gadget_to_dwc(g) (dev_get_platdata(&g->dev))
/* DEPCFG parameter 1 */
#define DWC3_DEPCFG_INT_NUM(n) (((n) & 0x1f) << 0)
#define DWC3_DEPCFG_XFER_COMPLETE_EN BIT(8)
#define DWC3_DEPCFG_XFER_IN_PROGRESS_EN BIT(9)
#define DWC3_DEPCFG_XFER_NOT_READY_EN BIT(10)
#define DWC3_DEPCFG_FIFO_ERROR_EN BIT(11)
#define DWC3_DEPCFG_STREAM_EVENT_EN BIT(13)
#define DWC3_DEPCFG_BINTERVAL_M1(n) (((n) & 0xff) << 16)
#define DWC3_DEPCFG_STREAM_CAPABLE BIT(24)
#define DWC3_DEPCFG_EP_NUMBER(n) (((n) & 0x1f) << 25)
#define DWC3_DEPCFG_BULK_BASED BIT(30)
#define DWC3_DEPCFG_FIFO_BASED BIT(31)
/* DEPCFG parameter 0 */
#define DWC3_DEPCFG_EP_TYPE(n) (((n) & 0x3) << 1)
#define DWC3_DEPCFG_MAX_PACKET_SIZE(n) (((n) & 0x7ff) << 3)
#define DWC3_DEPCFG_FIFO_NUMBER(n) (((n) & 0x1f) << 17)
#define DWC3_DEPCFG_BURST_SIZE(n) (((n) & 0xf) << 22)
#define DWC3_DEPCFG_DATA_SEQ_NUM(n) ((n) << 26)
/* This applies for core versions earlier than 1.94a */
#define DWC3_DEPCFG_IGN_SEQ_NUM BIT(31)
/* These apply for core versions 1.94a and later */
#define DWC3_DEPCFG_ACTION_INIT (0 << 30)
#define DWC3_DEPCFG_ACTION_RESTORE BIT(30)
#define DWC3_DEPCFG_ACTION_MODIFY (2 << 30)
/* DEPXFERCFG parameter 0 */
#define DWC3_DEPXFERCFG_NUM_XFER_RES(n) ((n) & 0xffff)
/* U1 Device exit Latency */
#define DWC3_DEFAULT_U1_DEV_EXIT_LAT 0x0A /* Less then 10 microsec */
/* U2 Device exit Latency */
#define DWC3_DEFAULT_U2_DEV_EXIT_LAT 0x1FF /* Less then 511 microsec */
/* Frame/Microframe Number Mask */
#define DWC3_FRNUMBER_MASK 0x3fff
/* -------------------------------------------------------------------------- */
#define to_dwc3_request(r) (container_of(r, struct dwc3_request, request))
/**
* next_request - gets the next request on the given list
* @list: the request list to operate on
*
* Caller should take care of locking. This function return %NULL or the first
* request available on @list.
*/
static inline struct dwc3_request *next_request(struct list_head *list)
{
return list_first_entry_or_null(list, struct dwc3_request, list);
}
/**
* dwc3_gadget_move_started_request - move @req to the started_list
* @req: the request to be moved
*
* Caller should take care of locking. This function will move @req from its
* current list to the endpoint's started_list.
*/
static inline void dwc3_gadget_move_started_request(struct dwc3_request *req)
{
struct dwc3_ep *dep = req->dep;
req->status = DWC3_REQUEST_STATUS_STARTED;
list_move_tail(&req->list, &dep->started_list);
}
/**
* dwc3_gadget_move_cancelled_request - move @req to the cancelled_list
* @req: the request to be moved
* @reason: cancelled reason for the dwc3 request
*
* Caller should take care of locking. This function will move @req from its
* current list to the endpoint's cancelled_list.
*/
static inline void dwc3_gadget_move_cancelled_request(struct dwc3_request *req,
unsigned int reason)
{
struct dwc3_ep *dep = req->dep;
req->status = reason;
list_move_tail(&req->list, &dep->cancelled_list);
}
void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req,
int status);
void dwc3_ep0_interrupt(struct dwc3 *dwc,
const struct dwc3_event_depevt *event);
void dwc3_ep0_out_start(struct dwc3 *dwc);
void dwc3_ep0_end_control_data(struct dwc3 *dwc, struct dwc3_ep *dep);
void dwc3_ep0_stall_and_restart(struct dwc3 *dwc);
int __dwc3_gadget_ep0_set_halt(struct usb_ep *ep, int value);
int dwc3_gadget_ep0_set_halt(struct usb_ep *ep, int value);
int dwc3_gadget_ep0_queue(struct usb_ep *ep, struct usb_request *request,
gfp_t gfp_flags);
int __dwc3_gadget_ep_set_halt(struct dwc3_ep *dep, int value, int protocol);
void dwc3_ep0_send_delayed_status(struct dwc3 *dwc);
void dwc3_stop_active_transfer(struct dwc3_ep *dep, bool force, bool interrupt);
int dwc3_gadget_start_config(struct dwc3 *dwc, unsigned int resource_index);
/**
* dwc3_gadget_ep_get_transfer_index - Gets transfer index from HW
* @dep: dwc3 endpoint
*
* Caller should take care of locking. Returns the transfer resource
* index for a given endpoint.
*/
static inline void dwc3_gadget_ep_get_transfer_index(struct dwc3_ep *dep)
{
u32 res_id;
res_id = dwc3_readl(dep->dwc, DWC3_DEPCMD(dep->number));
dep->resource_index = DWC3_DEPCMD_GET_RSC_IDX(res_id);
}
/**
* dwc3_gadget_dctl_write_safe - write to DCTL safe from link state change
* @dwc: pointer to our context structure
* @value: value to write to DCTL
*
* Use this function when doing read-modify-write to DCTL. It will not
* send link state change request.
*/
static inline void dwc3_gadget_dctl_write_safe(struct dwc3 *dwc, u32 value)
{
value &= ~DWC3_DCTL_ULSTCHNGREQ_MASK;
dwc3_writel(dwc, DWC3_DCTL, value);
}
#endif /* __DRIVERS_USB_DWC3_GADGET_H */
+196
View File
@@ -0,0 +1,196 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* glue.h - DesignWare USB3 DRD glue header
*/
#ifndef __DRIVERS_USB_DWC3_GLUE_H
#define __DRIVERS_USB_DWC3_GLUE_H
#include <linux/types.h>
#include "core.h"
/**
* dwc3_properties: DWC3 core properties
* @gsbuscfg0_reqinfo: Value to be programmed in the GSBUSCFG0.REQINFO field
* @needs_full_reinit: indicate the controller may not remain power during system
* pm and need full initialization
*/
struct dwc3_properties {
u32 gsbuscfg0_reqinfo;
unsigned needs_full_reinit:1;
};
#define DWC3_DEFAULT_PROPERTIES ((struct dwc3_properties){ \
.gsbuscfg0_reqinfo = DWC3_GSBUSCFG0_REQINFO_UNSPECIFIED, \
})
/**
* dwc3_probe_data: Initialization parameters passed to dwc3_core_probe()
* @dwc: Reference to dwc3 context structure
* @res: resource for the DWC3 core mmio region
* @ignore_clocks_and_resets: clocks and resets defined for the device should
* be ignored by the DWC3 core, as they are managed by the glue
* @skip_core_init_mode: Skip the finial initialization of the target mode, as
* it must be managed by the glue
* @properties: dwc3 software manage properties
*/
struct dwc3_probe_data {
struct dwc3 *dwc;
struct resource *res;
bool ignore_clocks_and_resets;
bool skip_core_init_mode;
struct dwc3_properties properties;
};
/**
* dwc3_core_probe - Initialize the core dwc3 driver
* @data: Initialization and configuration parameters for the controller
*
* Initializes the DesignWare USB3 core driver by setting up resources,
* registering interrupts, performing hardware setup, and preparing
* the controller for operation in the appropriate mode (host, gadget,
* or OTG). This is the main initialization function called by glue
* layer drivers to set up the core controller.
*
* Return: 0 on success, negative error code on failure
*/
int dwc3_core_probe(const struct dwc3_probe_data *data);
/**
* dwc3_core_remove - Deinitialize and remove the core dwc3 driver
* @dwc: Pointer to DWC3 controller context
*
* Cleans up resources and disables the dwc3 core driver. This should be called
* during driver removal or when the glue layer needs to shut down the
* controller completely.
*/
void dwc3_core_remove(struct dwc3 *dwc);
/*
* The following callbacks are provided for glue drivers to call from their
* own pm callbacks provided in struct dev_pm_ops. Glue drivers can perform
* platform-specific work before or after calling these functions and delegate
* the core suspend/resume operations to the core driver.
*/
int dwc3_runtime_suspend(struct dwc3 *dwc);
int dwc3_runtime_resume(struct dwc3 *dwc);
int dwc3_runtime_idle(struct dwc3 *dwc);
int dwc3_pm_suspend(struct dwc3 *dwc);
int dwc3_pm_resume(struct dwc3 *dwc);
void dwc3_pm_complete(struct dwc3 *dwc);
int dwc3_pm_prepare(struct dwc3 *dwc);
/* All of the following functions must only be used with skip_core_init_mode */
/**
* dwc3_core_init - Initialize DWC3 core hardware
* @dwc: Pointer to DWC3 controller context
*
* Configures and initializes the core hardware, usually done by dwc3_core_probe.
* This function is provided for platforms that use skip_core_init_mode and need
* to finalize the core initialization after some platform-specific setup.
* It must only be called when using skip_core_init_mode and before
* dwc3_host_init or dwc3_gadget_init.
*
* Return: 0 on success, negative error code on failure
*/
int dwc3_core_init(struct dwc3 *dwc);
/**
* dwc3_core_exit - Shut down DWC3 core hardware
* @dwc: Pointer to DWC3 controller context
*
* Disables and cleans up the core hardware state. This is usually handled
* internally by dwc3 and must only be called when using skip_core_init_mode
* and only after dwc3_core_init. Afterwards, dwc3_core_init may be called
* again.
*/
void dwc3_core_exit(struct dwc3 *dwc);
/**
* dwc3_host_init - Initialize host mode operation
* @dwc: Pointer to DWC3 controller context
*
* Initializes the controller for USB host mode operation, usually done by
* dwc3_core_probe or from within the dwc3 USB role switch callback.
* This function is provided for platforms that use skip_core_init_mode and need
* to finalize the host initialization after some platform-specific setup.
* It must not be called before dwc3_core_init or when skip_core_init_mode is
* not used. It must also not be called when gadget or host mode has already
* been initialized.
*
* Return: 0 on success, negative error code on failure
*/
int dwc3_host_init(struct dwc3 *dwc);
/**
* dwc3_host_exit - Shut down host mode operation
* @dwc: Pointer to DWC3 controller context
*
* Disables and cleans up host mode resources, usually done by
* the dwc3 USB role switch callback before switching controller mode.
* It must only be called when skip_core_init_mode is used and only after
* dwc3_host_init.
*/
void dwc3_host_exit(struct dwc3 *dwc);
/**
* dwc3_gadget_init - Initialize gadget mode operation
* @dwc: Pointer to DWC3 controller context
*
* Initializes the controller for USB gadget mode operation, usually done by
* dwc3_core_probe or from within the dwc3 USB role switch callback. This
* function is provided for platforms that use skip_core_init_mode and need to
* finalize the gadget initialization after some platform-specific setup.
* It must not be called before dwc3_core_init or when skip_core_init_mode is
* not used. It must also not be called when gadget or host mode has already
* been initialized.
*
* Return: 0 on success, negative error code on failure
*/
int dwc3_gadget_init(struct dwc3 *dwc);
/**
* dwc3_gadget_exit - Shut down gadget mode operation
* @dwc: Pointer to DWC3 controller context
*
* Disables and cleans up gadget mode resources, usually done by
* the dwc3 USB role switch callback before switching controller mode.
* It must only be called when skip_core_init_mode is used and only after
* dwc3_gadget_init.
*/
void dwc3_gadget_exit(struct dwc3 *dwc);
/**
* dwc3_enable_susphy - Control SUSPHY status for all USB ports
* @dwc: Pointer to DWC3 controller context
* @enable: True to enable SUSPHY, false to disable
*
* Enables or disables the USB3 PHY SUSPEND and USB2 PHY SUSPHY feature for
* all available ports.
* This is usually handled by the dwc3 core code and should only be used
* when skip_core_init_mode is used and the glue layer needs to manage SUSPHY
* settings itself, e.g., due to platform-specific requirements during mode
* switches.
*/
void dwc3_enable_susphy(struct dwc3 *dwc, bool enable);
/**
* dwc3_set_prtcap - Set the USB controller PRTCAP mode
* @dwc: Pointer to DWC3 controller context
* @mode: Target mode, must be one of DWC3_GCTL_PRTCAP_{HOST,DEVICE,OTG}
* @ignore_susphy: If true, skip disabling the SUSPHY and keep the current state
*
* Updates PRTCAP of the controller and current_dr_role inside the dwc3
* structure. For DRD controllers, this also disables SUSPHY unless explicitly
* told to skip via the ignore_susphy parameter.
*
* This is usually handled by the dwc3 core code and should only be used
* when skip_core_init_mode is used and the glue layer needs to manage mode
* transitions itself due to platform-specific requirements. It must be called
* with the correct mode before calling dwc3_host_init or dwc3_gadget_init.
*/
void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode, bool ignore_susphy);
#endif
+234
View File
@@ -0,0 +1,234 @@
// SPDX-License-Identifier: GPL-2.0
/*
* host.c - DesignWare USB3 DRD Controller Host Glue
*
* Copyright (C) 2011 Texas Instruments Incorporated - https://www.ti.com
*
* Authors: Felipe Balbi <balbi@ti.com>,
*/
#include <linux/irq.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/usb.h>
#include <linux/usb/hcd.h>
#include "../host/xhci-port.h"
#include "../host/xhci-ext-caps.h"
#include "../host/xhci-caps.h"
#include "../host/xhci-plat.h"
#include "core.h"
#define XHCI_HCSPARAMS1 0x4
#define XHCI_PORTSC_BASE 0x400
/**
* dwc3_power_off_all_roothub_ports - Power off all Root hub ports
* @dwc: Pointer to our controller context structure
*/
static void dwc3_power_off_all_roothub_ports(struct dwc3 *dwc)
{
void __iomem *xhci_regs;
u32 op_regs_base;
int port_num;
u32 offset;
u32 reg;
int i;
/* xhci regs are not mapped yet, do it temporarily here */
if (dwc->xhci_resources[0].start) {
if (dwc->xhci_resources[0].flags & IORESOURCE_MEM_NONPOSTED)
xhci_regs = ioremap_np(dwc->xhci_resources[0].start, DWC3_XHCI_REGS_END);
else
xhci_regs = ioremap(dwc->xhci_resources[0].start, DWC3_XHCI_REGS_END);
if (!xhci_regs) {
dev_err(dwc->dev, "Failed to ioremap xhci_regs\n");
return;
}
op_regs_base = HC_LENGTH(readl(xhci_regs));
reg = readl(xhci_regs + XHCI_HCSPARAMS1);
port_num = HCS_MAX_PORTS(reg);
for (i = 1; i <= port_num; i++) {
offset = op_regs_base + XHCI_PORTSC_BASE + 0x10 * (i - 1);
reg = readl(xhci_regs + offset);
reg &= ~PORT_POWER;
writel(reg, xhci_regs + offset);
}
iounmap(xhci_regs);
} else {
dev_err(dwc->dev, "xhci base reg invalid\n");
}
}
static void dwc3_xhci_plat_start(struct usb_hcd *hcd)
{
struct platform_device *pdev;
struct dwc3 *dwc;
if (!usb_hcd_is_primary_hcd(hcd))
return;
pdev = to_platform_device(hcd->self.controller);
dwc = dev_get_drvdata(pdev->dev.parent);
dwc3_enable_susphy(dwc, true);
}
static const struct xhci_plat_priv dwc3_xhci_plat_quirk = {
.plat_start = dwc3_xhci_plat_start,
};
static void dwc3_host_fill_xhci_irq_res(struct dwc3 *dwc,
int irq, char *name)
{
struct platform_device *pdev = to_platform_device(dwc->dev);
struct device_node *np = dev_of_node(&pdev->dev);
dwc->xhci_resources[1].start = irq;
dwc->xhci_resources[1].end = irq;
dwc->xhci_resources[1].flags = IORESOURCE_IRQ | irq_get_trigger_type(irq);
if (!name && np)
dwc->xhci_resources[1].name = of_node_full_name(pdev->dev.of_node);
else
dwc->xhci_resources[1].name = name;
}
static int dwc3_host_get_irq(struct dwc3 *dwc)
{
struct platform_device *dwc3_pdev = to_platform_device(dwc->dev);
int irq;
irq = platform_get_irq_byname_optional(dwc3_pdev, "host");
if (irq > 0) {
dwc3_host_fill_xhci_irq_res(dwc, irq, "host");
goto out;
}
if (irq == -EPROBE_DEFER)
goto out;
irq = platform_get_irq_byname_optional(dwc3_pdev, "dwc_usb3");
if (irq > 0) {
dwc3_host_fill_xhci_irq_res(dwc, irq, "dwc_usb3");
goto out;
}
if (irq == -EPROBE_DEFER)
goto out;
irq = platform_get_irq(dwc3_pdev, 0);
if (irq > 0)
dwc3_host_fill_xhci_irq_res(dwc, irq, NULL);
out:
return irq;
}
int dwc3_host_init(struct dwc3 *dwc)
{
struct property_entry props[6];
struct platform_device *xhci;
int ret, irq;
int prop_idx = 0;
/*
* Some platforms need to power off all Root hub ports immediately after DWC3 set to host
* mode to avoid VBUS glitch happen when xhci get reset later.
*/
dwc3_power_off_all_roothub_ports(dwc);
irq = dwc3_host_get_irq(dwc);
if (irq < 0)
return irq;
xhci = platform_device_alloc("xhci-hcd", PLATFORM_DEVID_AUTO);
if (!xhci) {
dev_err(dwc->dev, "couldn't allocate xHCI device\n");
return -ENOMEM;
}
xhci->dev.parent = dwc->dev;
dwc->xhci = xhci;
ret = platform_device_add_resources(xhci, dwc->xhci_resources,
DWC3_XHCI_RESOURCES_NUM);
if (ret) {
dev_err(dwc->dev, "couldn't add resources to xHCI device\n");
goto err;
}
memset(props, 0, sizeof(struct property_entry) * ARRAY_SIZE(props));
props[prop_idx++] = PROPERTY_ENTRY_BOOL("xhci-sg-trb-cache-size-quirk");
props[prop_idx++] = PROPERTY_ENTRY_BOOL("write-64-hi-lo-quirk");
if (dwc->usb3_lpm_capable)
props[prop_idx++] = PROPERTY_ENTRY_BOOL("usb3-lpm-capable");
if (dwc->usb2_lpm_disable)
props[prop_idx++] = PROPERTY_ENTRY_BOOL("usb2-lpm-disable");
/**
* WORKAROUND: dwc3 revisions <=3.00a have a limitation
* where Port Disable command doesn't work.
*
* The suggested workaround is that we avoid Port Disable
* completely.
*
* This following flag tells XHCI to do just that.
*/
if (DWC3_VER_IS_WITHIN(DWC3, ANY, 300A))
props[prop_idx++] = PROPERTY_ENTRY_BOOL("quirk-broken-port-ped");
props[prop_idx++] = PROPERTY_ENTRY_U16("num-hc-interrupters",
dwc->num_hc_interrupters);
if (prop_idx) {
ret = device_create_managed_software_node(&xhci->dev, props, NULL);
if (ret) {
dev_err(dwc->dev, "failed to add properties to xHCI\n");
goto err;
}
}
ret = platform_device_add_data(xhci, &dwc3_xhci_plat_quirk,
sizeof(struct xhci_plat_priv));
if (ret)
goto err;
ret = platform_device_add(xhci);
if (ret) {
dev_err(dwc->dev, "failed to register xHCI device\n");
goto err;
}
if (dwc->sys_wakeup) {
/* Restore wakeup setting if switched from device */
device_wakeup_enable(dwc->sysdev);
/* Pass on wakeup setting to the new xhci platform device */
device_init_wakeup(&xhci->dev, true);
}
return 0;
err:
platform_device_put(xhci);
return ret;
}
EXPORT_SYMBOL_GPL(dwc3_host_init);
void dwc3_host_exit(struct dwc3 *dwc)
{
if (dwc->sys_wakeup)
device_init_wakeup(&dwc->xhci->dev, false);
dwc3_enable_susphy(dwc, true);
platform_device_unregister(dwc->xhci);
dwc->xhci = NULL;
}
EXPORT_SYMBOL_GPL(dwc3_host_exit);
+60
View File
@@ -0,0 +1,60 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* io.h - DesignWare USB3 DRD IO Header
*
* Copyright (C) 2010-2011 Texas Instruments Incorporated - https://www.ti.com
*
* Authors: Felipe Balbi <balbi@ti.com>,
* Sebastian Andrzej Siewior <bigeasy@linutronix.de>
*/
#ifndef __DRIVERS_USB_DWC3_IO_H
#define __DRIVERS_USB_DWC3_IO_H
#include <linux/io.h>
#include "trace.h"
#include "debug.h"
#include "core.h"
static inline u32 dwc3_readl(struct dwc3 *dwc, u32 offset)
{
u32 value;
void __iomem *base = dwc->regs;
/*
* We requested the mem region starting from the Globals address
* space, see dwc3_probe in core.c.
* However, the offsets are given starting from xHCI address space.
*/
value = readl(base + offset - DWC3_GLOBALS_REGS_START);
/*
* When tracing we want to make it easy to find the correct address on
* documentation, so we revert it back to the proper addresses, the
* same way they are described on SNPS documentation
*/
trace_dwc3_readl(dwc, base - DWC3_GLOBALS_REGS_START, offset, value);
return value;
}
static inline void dwc3_writel(struct dwc3 *dwc, u32 offset, u32 value)
{
void __iomem *base = dwc->regs;
/*
* We requested the mem region starting from the Globals address
* space, see dwc3_probe in core.c.
* However, the offsets are given starting from xHCI address space.
*/
writel(value, base + offset - DWC3_GLOBALS_REGS_START);
/*
* When tracing we want to make it easy to find the correct address on
* documentation, so we revert it back to the proper addresses, the
* same way they are described on SNPS documentation
*/
trace_dwc3_writel(dwc, base - DWC3_GLOBALS_REGS_START, offset, value);
}
#endif /* __DRIVERS_USB_DWC3_IO_H */
+11
View File
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: GPL-2.0
/*
* trace.c - DesignWare USB3 DRD Controller Trace Support
*
* Copyright (C) 2014 Texas Instruments Incorporated - https://www.ti.com
*
* Author: Felipe Balbi <balbi@ti.com>
*/
#define CREATE_TRACE_POINTS
#include "trace.h"
+387
View File
@@ -0,0 +1,387 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* trace.h - DesignWare USB3 DRD Controller Trace Support
*
* Copyright (C) 2014 Texas Instruments Incorporated - https://www.ti.com
*
* Author: Felipe Balbi <balbi@ti.com>
*/
#undef TRACE_SYSTEM
#define TRACE_SYSTEM dwc3
#if !defined(__DWC3_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
#define __DWC3_TRACE_H
#include <linux/types.h>
#include <linux/tracepoint.h>
#include <asm/byteorder.h>
#include "core.h"
#include "debug.h"
DECLARE_EVENT_CLASS(dwc3_log_set_prtcap,
TP_PROTO(struct dwc3 *dwc, u32 mode),
TP_ARGS(dwc, mode),
TP_STRUCT__entry(
__field(phys_addr_t, base_address)
__field(u32, mode)
),
TP_fast_assign(
__entry->base_address = dwc->xhci_resources[0].start;
__entry->mode = mode;
),
TP_printk("%pa: mode %s", &__entry->base_address, dwc3_mode_string(__entry->mode))
);
DEFINE_EVENT(dwc3_log_set_prtcap, dwc3_set_prtcap,
TP_PROTO(struct dwc3 *dwc, u32 mode),
TP_ARGS(dwc, mode)
);
DECLARE_EVENT_CLASS(dwc3_log_io,
TP_PROTO(struct dwc3 *dwc, void *base, u32 offset, u32 value),
TP_ARGS(dwc, base, offset, value),
TP_STRUCT__entry(
__field(phys_addr_t, base_address)
__field(void *, base)
__field(u32, offset)
__field(u32, value)
),
TP_fast_assign(
__entry->base_address = dwc->xhci_resources[0].start;
__entry->base = base;
__entry->offset = offset;
__entry->value = value;
),
TP_printk("%pa: addr %p offset %04x value %08x",
&__entry->base_address,
__entry->base + __entry->offset,
__entry->offset,
__entry->value)
);
DEFINE_EVENT(dwc3_log_io, dwc3_readl,
TP_PROTO(struct dwc3 *dwc, void __iomem *base, u32 offset, u32 value),
TP_ARGS(dwc, base, offset, value)
);
DEFINE_EVENT(dwc3_log_io, dwc3_writel,
TP_PROTO(struct dwc3 *dwc, void __iomem *base, u32 offset, u32 value),
TP_ARGS(dwc, base, offset, value)
);
DECLARE_EVENT_CLASS(dwc3_log_event,
TP_PROTO(u32 event, struct dwc3 *dwc),
TP_ARGS(event, dwc),
TP_STRUCT__entry(
__field(phys_addr_t, base_address)
__field(u32, event)
__field(u32, ep0state)
),
TP_fast_assign(
__entry->base_address = dwc->xhci_resources[0].start;
__entry->event = event;
__entry->ep0state = dwc->ep0state;
),
TP_printk("%pa: event (%08x): %s", &__entry->base_address, __entry->event,
dwc3_decode_event(__get_buf(DWC3_MSG_MAX), DWC3_MSG_MAX,
__entry->event, __entry->ep0state))
);
DEFINE_EVENT(dwc3_log_event, dwc3_event,
TP_PROTO(u32 event, struct dwc3 *dwc),
TP_ARGS(event, dwc)
);
DECLARE_EVENT_CLASS(dwc3_log_ctrl,
TP_PROTO(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl),
TP_ARGS(dwc, ctrl),
TP_STRUCT__entry(
__field(phys_addr_t, base_address)
__field(__u8, bRequestType)
__field(__u8, bRequest)
__field(__u16, wValue)
__field(__u16, wIndex)
__field(__u16, wLength)
),
TP_fast_assign(
__entry->base_address = dwc->xhci_resources[0].start;
__entry->bRequestType = ctrl->bRequestType;
__entry->bRequest = ctrl->bRequest;
__entry->wValue = le16_to_cpu(ctrl->wValue);
__entry->wIndex = le16_to_cpu(ctrl->wIndex);
__entry->wLength = le16_to_cpu(ctrl->wLength);
),
TP_printk("%pa: %s", &__entry->base_address, usb_decode_ctrl(__get_buf(DWC3_MSG_MAX),
DWC3_MSG_MAX,
__entry->bRequestType,
__entry->bRequest, __entry->wValue,
__entry->wIndex, __entry->wLength)
)
);
DEFINE_EVENT(dwc3_log_ctrl, dwc3_ctrl_req,
TP_PROTO(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl),
TP_ARGS(dwc, ctrl)
);
DECLARE_EVENT_CLASS(dwc3_log_request,
TP_PROTO(struct dwc3_request *req),
TP_ARGS(req),
TP_STRUCT__entry(
__field(phys_addr_t, base_address)
__string(name, req->dep->name)
__field(struct dwc3_request *, req)
__field(unsigned int, actual)
__field(unsigned int, length)
__field(int, status)
__field(int, zero)
__field(int, short_not_ok)
__field(int, no_interrupt)
),
TP_fast_assign(
__entry->base_address = req->dep->dwc->xhci_resources[0].start;
__entry->req = req;
__entry->actual = req->request.actual;
__entry->length = req->request.length;
__entry->status = req->request.status;
__entry->zero = req->request.zero;
__entry->short_not_ok = req->request.short_not_ok;
__entry->no_interrupt = req->request.no_interrupt;
),
TP_printk("%pa: %s: req %p length %u/%u %s%s%s ==> %d",
&__entry->base_address,
__get_str(name), __entry->req,
__entry->actual, __entry->length,
__entry->zero ? "Z" : "z",
__entry->short_not_ok ? "S" : "s",
__entry->no_interrupt ? "i" : "I",
__entry->status
)
);
DEFINE_EVENT(dwc3_log_request, dwc3_alloc_request,
TP_PROTO(struct dwc3_request *req),
TP_ARGS(req)
);
DEFINE_EVENT(dwc3_log_request, dwc3_free_request,
TP_PROTO(struct dwc3_request *req),
TP_ARGS(req)
);
DEFINE_EVENT(dwc3_log_request, dwc3_ep_queue,
TP_PROTO(struct dwc3_request *req),
TP_ARGS(req)
);
DEFINE_EVENT(dwc3_log_request, dwc3_ep_dequeue,
TP_PROTO(struct dwc3_request *req),
TP_ARGS(req)
);
DEFINE_EVENT(dwc3_log_request, dwc3_gadget_giveback,
TP_PROTO(struct dwc3_request *req),
TP_ARGS(req)
);
DECLARE_EVENT_CLASS(dwc3_log_generic_cmd,
TP_PROTO(struct dwc3 *dwc, unsigned int cmd, u32 param, int status),
TP_ARGS(dwc, cmd, param, status),
TP_STRUCT__entry(
__field(phys_addr_t, base_address)
__field(unsigned int, cmd)
__field(u32, param)
__field(int, status)
),
TP_fast_assign(
__entry->base_address = dwc->xhci_resources[0].start;
__entry->cmd = cmd;
__entry->param = param;
__entry->status = status;
),
TP_printk("%pa: cmd '%s' [%x] param %08x --> status: %s",
&__entry->base_address, dwc3_gadget_generic_cmd_string(__entry->cmd),
__entry->cmd, __entry->param,
dwc3_gadget_generic_cmd_status_string(__entry->status)
)
);
DEFINE_EVENT(dwc3_log_generic_cmd, dwc3_gadget_generic_cmd,
TP_PROTO(struct dwc3 *dwc, unsigned int cmd, u32 param, int status),
TP_ARGS(dwc, cmd, param, status)
);
DECLARE_EVENT_CLASS(dwc3_log_gadget_ep_cmd,
TP_PROTO(struct dwc3_ep *dep, unsigned int cmd,
struct dwc3_gadget_ep_cmd_params *params, int cmd_status),
TP_ARGS(dep, cmd, params, cmd_status),
TP_STRUCT__entry(
__field(phys_addr_t, base_address)
__string(name, dep->name)
__field(unsigned int, cmd)
__field(u32, param0)
__field(u32, param1)
__field(u32, param2)
__field(int, cmd_status)
),
TP_fast_assign(
__entry->base_address = dep->dwc->xhci_resources[0].start;
__assign_str(name);
__entry->cmd = cmd;
__entry->param0 = params->param0;
__entry->param1 = params->param1;
__entry->param2 = params->param2;
__entry->cmd_status = cmd_status;
),
TP_printk("%pa: %s: cmd '%s' [%x] params %08x %08x %08x --> status: %s",
&__entry->base_address, __get_str(name),
dwc3_gadget_ep_cmd_string(__entry->cmd),
__entry->cmd, __entry->param0,
__entry->param1, __entry->param2,
dwc3_ep_cmd_status_string(__entry->cmd_status)
)
);
DEFINE_EVENT(dwc3_log_gadget_ep_cmd, dwc3_gadget_ep_cmd,
TP_PROTO(struct dwc3_ep *dep, unsigned int cmd,
struct dwc3_gadget_ep_cmd_params *params, int cmd_status),
TP_ARGS(dep, cmd, params, cmd_status)
);
DECLARE_EVENT_CLASS(dwc3_log_trb,
TP_PROTO(struct dwc3_ep *dep, struct dwc3_trb *trb),
TP_ARGS(dep, trb),
TP_STRUCT__entry(
__field(phys_addr_t, base_address)
__string(name, dep->name)
__field(struct dwc3_trb *, trb)
__field(u32, bpl)
__field(u32, bph)
__field(u32, size)
__field(u32, ctrl)
__field(u32, type)
__field(u32, enqueue)
__field(u32, dequeue)
),
TP_fast_assign(
__entry->base_address = dep->dwc->xhci_resources[0].start;
__assign_str(name);
__entry->trb = trb;
__entry->bpl = trb->bpl;
__entry->bph = trb->bph;
__entry->size = trb->size;
__entry->ctrl = trb->ctrl;
__entry->type = usb_endpoint_type(dep->endpoint.desc);
__entry->enqueue = dep->trb_enqueue;
__entry->dequeue = dep->trb_dequeue;
),
TP_printk("%pa: %s: trb %p (E%d:D%d) buf %08x%08x size %s%d ctrl %08x sofn %08x (%c%c%c%c:%c%c:%s)",
&__entry->base_address, __get_str(name), __entry->trb, __entry->enqueue,
__entry->dequeue, __entry->bph, __entry->bpl,
({char *s;
int pcm = ((__entry->size >> 24) & 3) + 1;
switch (__entry->type) {
case USB_ENDPOINT_XFER_INT:
case USB_ENDPOINT_XFER_ISOC:
switch (pcm) {
case 1:
s = "1x ";
break;
case 2:
s = "2x ";
break;
case 3:
default:
s = "3x ";
break;
}
break;
default:
s = "";
} s; }),
DWC3_TRB_SIZE_LENGTH(__entry->size), __entry->ctrl,
DWC3_TRB_CTRL_GET_SID_SOFN(__entry->ctrl),
__entry->ctrl & DWC3_TRB_CTRL_HWO ? 'H' : 'h',
__entry->ctrl & DWC3_TRB_CTRL_LST ? 'L' : 'l',
__entry->ctrl & DWC3_TRB_CTRL_CHN ? 'C' : 'c',
__entry->ctrl & DWC3_TRB_CTRL_CSP ? 'S' : 's',
__entry->ctrl & DWC3_TRB_CTRL_ISP_IMI ? 'S' : 's',
__entry->ctrl & DWC3_TRB_CTRL_IOC ? 'C' : 'c',
dwc3_trb_type_string(DWC3_TRBCTL_TYPE(__entry->ctrl))
)
);
DEFINE_EVENT(dwc3_log_trb, dwc3_prepare_trb,
TP_PROTO(struct dwc3_ep *dep, struct dwc3_trb *trb),
TP_ARGS(dep, trb)
);
DEFINE_EVENT(dwc3_log_trb, dwc3_complete_trb,
TP_PROTO(struct dwc3_ep *dep, struct dwc3_trb *trb),
TP_ARGS(dep, trb)
);
DECLARE_EVENT_CLASS(dwc3_log_ep,
TP_PROTO(struct dwc3_ep *dep),
TP_ARGS(dep),
TP_STRUCT__entry(
__field(phys_addr_t, base_address)
__string(name, dep->name)
__field(unsigned int, maxpacket)
__field(unsigned int, maxpacket_limit)
__field(unsigned int, max_streams)
__field(unsigned int, maxburst)
__field(unsigned int, flags)
__field(unsigned int, direction)
__field(u8, trb_enqueue)
__field(u8, trb_dequeue)
),
TP_fast_assign(
__entry->base_address = dep->dwc->xhci_resources[0].start;
__assign_str(name);
__entry->maxpacket = dep->endpoint.maxpacket;
__entry->maxpacket_limit = dep->endpoint.maxpacket_limit;
__entry->max_streams = dep->endpoint.max_streams;
__entry->maxburst = dep->endpoint.maxburst;
__entry->flags = dep->flags;
__entry->direction = dep->direction;
__entry->trb_enqueue = dep->trb_enqueue;
__entry->trb_dequeue = dep->trb_dequeue;
),
TP_printk("%pa: %s: mps %d/%d streams %d burst %d ring %d/%d flags %c:%c%c%c%c:%c",
&__entry->base_address, __get_str(name), __entry->maxpacket,
__entry->maxpacket_limit, __entry->max_streams,
__entry->maxburst, __entry->trb_enqueue,
__entry->trb_dequeue,
__entry->flags & DWC3_EP_ENABLED ? 'E' : 'e',
__entry->flags & DWC3_EP_STALL ? 'S' : 's',
__entry->flags & DWC3_EP_WEDGE ? 'W' : 'w',
__entry->flags & DWC3_EP_TRANSFER_STARTED ? 'B' : 'b',
__entry->flags & DWC3_EP_PENDING_REQUEST ? 'P' : 'p',
__entry->direction ? '<' : '>'
)
);
DEFINE_EVENT(dwc3_log_ep, dwc3_gadget_ep_enable,
TP_PROTO(struct dwc3_ep *dep),
TP_ARGS(dep)
);
DEFINE_EVENT(dwc3_log_ep, dwc3_gadget_ep_disable,
TP_PROTO(struct dwc3_ep *dep),
TP_ARGS(dep)
);
#endif /* __DWC3_TRACE_H */
/* this part has to be here */
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH .
#undef TRACE_INCLUDE_FILE
#define TRACE_INCLUDE_FILE trace
#include <trace/define_trace.h>
+129
View File
@@ -0,0 +1,129 @@
// SPDX-License-Identifier: GPL-2.0
/*
* ulpi.c - DesignWare USB3 Controller's ULPI PHY interface
*
* Copyright (C) 2015 Intel Corporation
*
* Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
*/
#include <linux/delay.h>
#include <linux/time64.h>
#include <linux/ulpi/regs.h>
#include <linux/ulpi/driver.h>
#include "core.h"
#include "io.h"
#define USB_VENDOR_MICROCHIP 0x0424
#define DWC3_ULPI_ADDR(a) \
((a >= ULPI_EXT_VENDOR_SPECIFIC) ? \
DWC3_GUSB2PHYACC_ADDR(ULPI_ACCESS_EXTENDED) | \
DWC3_GUSB2PHYACC_EXTEND_ADDR(a) : DWC3_GUSB2PHYACC_ADDR(a))
#define DWC3_ULPI_BASE_DELAY DIV_ROUND_UP(NSEC_PER_SEC, 60000000L)
static int dwc3_ulpi_busyloop(struct dwc3 *dwc, u8 addr, bool read)
{
unsigned long ns = 5L * DWC3_ULPI_BASE_DELAY;
unsigned int count = 10000;
u32 reg;
if (addr >= ULPI_EXT_VENDOR_SPECIFIC)
ns += DWC3_ULPI_BASE_DELAY;
if (read)
ns += DWC3_ULPI_BASE_DELAY;
reg = dwc3_readl(dwc, DWC3_GUSB2PHYCFG(0));
if (reg & DWC3_GUSB2PHYCFG_SUSPHY)
usleep_range(1000, 1200);
while (count--) {
ndelay(ns);
reg = dwc3_readl(dwc, DWC3_GUSB2PHYACC(0));
if (reg & DWC3_GUSB2PHYACC_DONE)
return 0;
cpu_relax();
}
return -ETIMEDOUT;
}
static int dwc3_ulpi_read(struct device *dev, u8 addr)
{
struct dwc3 *dwc = dev_get_drvdata(dev);
u32 reg;
int ret;
reg = DWC3_GUSB2PHYACC_NEWREGREQ | DWC3_ULPI_ADDR(addr);
dwc3_writel(dwc, DWC3_GUSB2PHYACC(0), reg);
ret = dwc3_ulpi_busyloop(dwc, addr, true);
if (ret)
return ret;
reg = dwc3_readl(dwc, DWC3_GUSB2PHYACC(0));
return DWC3_GUSB2PHYACC_DATA(reg);
}
static int dwc3_ulpi_write(struct device *dev, u8 addr, u8 val)
{
struct dwc3 *dwc = dev_get_drvdata(dev);
u32 reg;
reg = DWC3_GUSB2PHYACC_NEWREGREQ | DWC3_ULPI_ADDR(addr);
reg |= DWC3_GUSB2PHYACC_WRITE | val;
dwc3_writel(dwc, DWC3_GUSB2PHYACC(0), reg);
return dwc3_ulpi_busyloop(dwc, addr, false);
}
static const struct ulpi_ops dwc3_ulpi_ops = {
.read = dwc3_ulpi_read,
.write = dwc3_ulpi_write,
};
static void dwc3_ulpi_detect_config(struct dwc3 *dwc)
{
struct ulpi *ulpi = dwc->ulpi;
switch (ulpi->id.vendor) {
case USB_VENDOR_MICROCHIP:
switch (ulpi->id.product) {
case 0x0009:
/* Microchip USB3340 ULPI PHY */
dwc->enable_usb2_transceiver_delay = true;
break;
default:
break;
}
break;
default:
break;
}
}
int dwc3_ulpi_init(struct dwc3 *dwc)
{
/* Register the interface */
dwc->ulpi = ulpi_register_interface(dwc->dev, &dwc3_ulpi_ops);
if (IS_ERR(dwc->ulpi)) {
dev_err(dwc->dev, "failed to register ULPI interface");
return PTR_ERR(dwc->ulpi);
}
dwc3_ulpi_detect_config(dwc);
return 0;
}
void dwc3_ulpi_exit(struct dwc3 *dwc)
{
if (dwc->ulpi) {
ulpi_unregister_interface(dwc->ulpi);
dwc->ulpi = NULL;
}
}