diff options
author | Dong Aisheng <aisheng.dong@nxp.com> | 2019-12-02 18:05:31 +0800 |
---|---|---|
committer | Dong Aisheng <aisheng.dong@nxp.com> | 2019-12-02 18:05:31 +0800 |
commit | 210428cc3751c57bedc69e55567b326d5e5ce08a (patch) | |
tree | 287c4dadcf7ef69df98538cd2ced0c8f6a56235c | |
parent | 3e2e5e55509651952ff06ef12db50d6b6d4cb1da (diff) | |
parent | 8baeb551e70c8c9d5c1f5f3fd1c1ac541838ba82 (diff) |
Merge branch 'usb/next' into next
* usb/next: (188 commits)
LF-252 usb: cdns3: gadget: fix the issue for DMA scatter buffer list
usb: dwc3: Add cache type configuration support
usb: dwc3: Add chip-specific compatible string
MLK-22675 usb: dwc3: host: disable park mode
MLK-22878 usb: cdns3: gadget: add imx8qxp C0 support
...
72 files changed, 5804 insertions, 2923 deletions
diff --git a/Documentation/ABI/testing/sysfs-platform-cadence-usb3 b/Documentation/ABI/testing/sysfs-platform-cadence-usb3 new file mode 100644 index 000000000000..c969518dcc30 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-platform-cadence-usb3 @@ -0,0 +1,9 @@ +What: /sys/bus/platform/devices/5b110000.usb3/role +Date: Jan 2019 +Contact: Peter Chen <peter.chen@nxp.com> +Description: + It returns string "gadget", "host" and "none" when read it, + it indicates current controller role. + + It will do role switch when write "gadget" or "host" to it. + Only controller at dual-role configuration supports writing. diff --git a/Documentation/ABI/testing/usb-charger-uevent b/Documentation/ABI/testing/usb-charger-uevent new file mode 100644 index 000000000000..93ffd3a54a7f --- /dev/null +++ b/Documentation/ABI/testing/usb-charger-uevent @@ -0,0 +1,45 @@ +What: Raise a uevent when a USB charger is inserted or removed +Date: 2019-11-11 +KernelVersion: 5.5 +Contact: linux-usb@vger.kernel.org +Description: There are two USB charger states: + USB_CHARGER_ABSENT + USB_CHARGER_PRESENT + There are five USB charger types: + USB_CHARGER_UNKNOWN_TYPE + USB_CHARGER_SDP_TYPE + USB_CHARGER_CDP_TYPE + USB_CHARGER_DCP_TYPE + USB_CHARGER_ACA_TYPE + + Here are two examples taken using udevadm monitor -p when + USB charger is online: + UDEV [227.425096] change /devices/soc0/usbphynop1 (platform) + ACTION=change + DEVPATH=/devices/soc0/usbphynop1 + DRIVER=usb_phy_generic + MODALIAS=of:Nusbphynop1T(null)Cusb-nop-xceiv + OF_COMPATIBLE_0=usb-nop-xceiv + OF_COMPATIBLE_N=1 + OF_FULLNAME=/usbphynop1 + OF_NAME=usbphynop1 + SEQNUM=2493 + SUBSYSTEM=platform + USB_CHARGER_STATE=USB_CHARGER_PRESENT + USB_CHARGER_TYPE=USB_CHARGER_SDP_TYPE + USEC_INITIALIZED=227422826 + + USB charger is offline: + KERNEL[229.533933] change /devices/soc0/usbphynop1 (platform) + ACTION=change + DEVPATH=/devices/soc0/usbphynop1 + DRIVER=usb_phy_generic + MODALIAS=of:Nusbphynop1T(null)Cusb-nop-xceiv + OF_COMPATIBLE_0=usb-nop-xceiv + OF_COMPATIBLE_N=1 + OF_FULLNAME=/usbphynop1 + OF_NAME=usbphynop1 + SEQNUM=2494 + SUBSYSTEM=platform + USB_CHARGER_STATE=USB_CHARGER_ABSENT + USB_CHARGER_TYPE=USB_CHARGER_UNKNOWN_TYPE diff --git a/Documentation/devicetree/bindings/usb/cdns-usb3.txt b/Documentation/devicetree/bindings/usb/cdns-usb3.txt index b7dc606d37b5..13daee1b053b 100644 --- a/Documentation/devicetree/bindings/usb/cdns-usb3.txt +++ b/Documentation/devicetree/bindings/usb/cdns-usb3.txt @@ -1,45 +1,39 @@ -Binding for the Cadence USBSS-DRD controller +* Cadence USB3 Controller Required properties: - - reg: Physical base address and size of the controller's register areas. - Controller has 3 different regions: - - HOST registers area - - DEVICE registers area - - OTG/DRD registers area - - reg-names - register memory area names: - "xhci" - for HOST registers space - "dev" - for DEVICE registers space - "otg" - for OTG/DRD registers space - - compatible: Should contain: "cdns,usb3" - - interrupts: Interrupts used by cdns3 controller: - "host" - interrupt used by XHCI driver. - "peripheral" - interrupt used by device driver - "otg" - interrupt used by DRD/OTG part of driver +- compatible: "Cadence,usb3"; +- reg: base address and length of the registers +- interrupts: interrupt for the USB controller +- interrupt-parent: the interrupt parent for this module +- clocks: reference to the USB clock +- clock-names: the name of clocks +- cdns3,usbphy: reference to the USB PHY Optional properties: - - maximum-speed : valid arguments are "super-speed", "high-speed" and - "full-speed"; refer to usb/generic.txt - - dr_mode: Should be one of "host", "peripheral" or "otg". - - phys: reference to the USB PHY - - phy-names: from the *Generic PHY* bindings; - Supported names are: - - cdns3,usb2-phy - - cdns3,usb3-phy +- dr_mode: One of "host", "peripheral" or "otg". Defaults to "otg" +- extcon: extcon phandler for cdns3 device +- power-domains: the power domain for cdns3 controller and phy - - cdns,on-chip-buff-size : size of memory intended as internal memory for endpoints - buffers expressed in KB +Examples: -Example: - usb@f3000000 { - compatible = "cdns,usb3"; - interrupts = <GIC_USB_IRQ 7 IRQ_TYPE_LEVEL_HIGH>, - <GIC_USB_IRQ 7 IRQ_TYPE_LEVEL_HIGH>, - <GIC_USB_IRQ 8 IRQ_TYPE_LEVEL_HIGH>; - interrupt-names = "host", "peripheral", "otg"; - reg = <0xf3000000 0x10000>, /* memory area for HOST registers */ - <0xf3010000 0x10000>, /* memory area for DEVICE registers */ - <0xf3020000 0x10000>; /* memory area for OTG/DRD registers */ - reg-names = "xhci", "dev", "otg"; - phys = <&usb2_phy>, <&usb3_phy>; - phy-names = "cdns3,usb2-phy", "cnds3,usb3-phy"; - }; +usbotg3: cdns3@5b110000 { + compatible = "Cadence,usb3"; + reg = <0x0 0x5B110000 0x0 0x10000>, + <0x0 0x5B130000 0x0 0x10000>, + <0x0 0x5B140000 0x0 0x10000>, + <0x0 0x5B160000 0x0 0x40000>; + interrupt-parent = <&gic>; + interrupts = <GIC_SPI 271 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clk IMX8QM_USB3_LPM_CLK>, + <&clk IMX8QM_USB3_BUS_CLK>, + <&clk IMX8QM_USB3_ACLK>, + <&clk IMX8QM_USB3_IPG_CLK>, + <&clk IMX8QM_USB3_CORE_PCLK>; + clock-names = "usb3_lpm_clk", "usb3_bus_clk", "usb3_aclk", + "usb3_ipg_clk", "usb3_core_pclk"; + power-domains = <&pd_conn_usb2>; + cdns3,usbphy = <&usbphynop1>; + dr_mode = "otg"; + extcon = <&typec_ptn5150>; + status = "disabled"; +}; diff --git a/Documentation/devicetree/bindings/usb/ci-hdrc-usb2.txt b/Documentation/devicetree/bindings/usb/ci-hdrc-usb2.txt index cfc9f40ab641..c2d72c8fe6a0 100644 --- a/Documentation/devicetree/bindings/usb/ci-hdrc-usb2.txt +++ b/Documentation/devicetree/bindings/usb/ci-hdrc-usb2.txt @@ -86,6 +86,7 @@ Optional properties: case, the "idle" state needs to pull down the data and strobe pin and the "active" state needs to pull up the strobe pin. - pinctrl-n: alternate pin modes +- ci-disable-lpm: Some chipidea hardware need to disable low power mode i.mx specific properties - fsl,usbmisc: phandler of non-core register device, with one diff --git a/Documentation/devicetree/bindings/usb/dwc3.txt b/Documentation/devicetree/bindings/usb/dwc3.txt index 66780a47ad85..45043c4c54ae 100644 --- a/Documentation/devicetree/bindings/usb/dwc3.txt +++ b/Documentation/devicetree/bindings/usb/dwc3.txt @@ -4,7 +4,15 @@ DWC3- USB3 CONTROLLER. Complies to the generic USB binding properties as described in 'usb/generic.txt' Required properties: - - compatible: must be "snps,dwc3" + - compatible: must be "snps,dwc3" and (if applicable) may contain a + chip-specific compatible string in front of it to allow dwc3 driver be + able to update cache type configuration accordingly, otherwise + Layerscape SoC will encounter USB init failure when adding property + dma-coherent on device tree. + Example: + * "fsl,layerscape-dwc3", "snps,dwc3" + * "snps,dwc3" + - reg : Address and length of the register set for the device - interrupts: Interrupts used by the dwc3 controller. - clock-names: should contain "ref", "bus_early", "suspend" @@ -109,6 +117,10 @@ Optional properties: more than one value, which means undefined length INCR burst type enabled. The values can be 1, 4, 8, 16, 32, 64, 128 and 256. + - snps,host-vbus-glitches: Power off all Root Hub ports immediately after + setting host mode to avoid vbus (negative) glitch happen in later + xhci reset. And the vbus will back to 5V automatically when reset done. + - in addition all properties from usb-xhci.txt from the current directory are supported as well diff --git a/Documentation/devicetree/bindings/usb/typec-switch-gpio.txt b/Documentation/devicetree/bindings/usb/typec-switch-gpio.txt new file mode 100644 index 000000000000..4ef76cfdf3d5 --- /dev/null +++ b/Documentation/devicetree/bindings/usb/typec-switch-gpio.txt @@ -0,0 +1,30 @@ +Typec orientation switch via a GPIO +----------------------------------- + +Required properties: +- compatible: should be set one of following: + - "nxp,ptn36043" for NXP Type-C SuperSpeed active switch. + +- gpios: the GPIO used to switch the super speed active channel, + GPIO_ACTIVE_HIGH: GPIO state high for cc1; + GPIO_ACTIVE_LOW: GPIO state low for cc1. +- orientation-switch: must be present. + +Required sub-node: +- port: specify the remote endpoint of typec switch consumer. + +Example: + +ptn36043 { + compatible = "nxp,ptn36043"; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_ss_sel>; + gpios = <&gpio3 15 GPIO_ACTIVE_HIGH>; + orientation-switch; + + port { + usb3_data_ss: endpoint { + remote-endpoint = <&typec_con_ss>; + }; + }; +}; diff --git a/Documentation/devicetree/bindings/usb/usb-xhci.txt b/Documentation/devicetree/bindings/usb/usb-xhci.txt index b49b819571f9..170c56477c97 100644 --- a/Documentation/devicetree/bindings/usb/usb-xhci.txt +++ b/Documentation/devicetree/bindings/usb/usb-xhci.txt @@ -42,6 +42,8 @@ Optional properties: - quirk-broken-port-ped: set if the controller has broken port disable mechanism - imod-interval-ns: default interrupt moderation interval is 5000ns - phys : see usb-hcd.yaml in the current directory + - usb3-resume-missing-cas: set if the CAS(Cold Attach Status) may lose in case + device plugged in while system sleep. additionally the properties from usb-hcd.yaml (in the current directory) are supported. diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig index 275568abc670..18747e6b86cd 100644 --- a/drivers/usb/Kconfig +++ b/drivers/usb/Kconfig @@ -125,6 +125,8 @@ source "drivers/usb/chipidea/Kconfig" source "drivers/usb/isp1760/Kconfig" +source "drivers/usb/cdns3/Kconfig" + comment "USB port drivers" if USB diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile index 1c1c1d659394..b3f08b8303f6 100644 --- a/drivers/usb/Makefile +++ b/drivers/usb/Makefile @@ -12,8 +12,7 @@ obj-$(CONFIG_USB_SUPPORT) += phy/ obj-$(CONFIG_USB_DWC3) += dwc3/ obj-$(CONFIG_USB_DWC2) += dwc2/ obj-$(CONFIG_USB_ISP1760) += isp1760/ - -obj-$(CONFIG_USB_CDNS3) += cdns3/ +obj-$(CONFIG_USB_CDNS3) += cdns3/ obj-$(CONFIG_USB_MON) += mon/ obj-$(CONFIG_USB_MTU3) += mtu3/ diff --git a/drivers/usb/cdns3/Kconfig b/drivers/usb/cdns3/Kconfig index d0331613a355..165afdb99d15 100644 --- a/drivers/usb/cdns3/Kconfig +++ b/drivers/usb/cdns3/Kconfig @@ -1,46 +1,26 @@ config USB_CDNS3 tristate "Cadence USB3 Dual-Role Controller" - depends on USB_SUPPORT && (USB || USB_GADGET) && HAS_DMA - select USB_XHCI_PLATFORM if USB_XHCI_HCD - select USB_ROLE_SWITCH + depends on ((USB_XHCI_HCD && USB_GADGET) || (USB_XHCI_HCD && !USB_GADGET) || (!USB_XHCI_HCD && USB_GADGET)) && HAS_DMA + select EXTCON help - Say Y here if your system has a Cadence USB3 dual-role controller. - It supports: dual-role switch, Host-only, and Peripheral-only. + Say Y here if your system has a cadence USB3 dual-role controller. + It supports: dual-role switch Host-only, and Peripheral-only. - If you choose to build this driver is a dynamically linked - as module, the module will be called cdns3.ko. + When compiled dynamically, the module will be called cdns3.ko. if USB_CDNS3 config USB_CDNS3_GADGET bool "Cadence USB3 device controller" - depends on USB_GADGET=y || USB_GADGET=USB_CDNS3 + depends on USB_GADGET help Say Y here to enable device controller functionality of the - Cadence USBSS-DEV driver. - - This controller supports FF, HS and SS mode. It doesn't support - LS and SSP mode. + cadence usb3 driver. config USB_CDNS3_HOST bool "Cadence USB3 host controller" - depends on USB=y || USB=USB_CDNS3 + depends on USB_XHCI_HCD help Say Y here to enable host controller functionality of the - Cadence driver. - - Host controller is compliant with XHCI so it will use - standard XHCI driver. - -config USB_CDNS3_PCI_WRAP - tristate "Cadence USB3 support on PCIe-based platforms" - depends on USB_PCI && ACPI - default USB_CDNS3 - help - If you're using the USBSS Core IP with a PCIe, please say - 'Y' or 'M' here. - - If you choose to build this driver as module it will - be dynamically linked and module will be called cdns3-pci.ko - + cadence usb3 driver. endif diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile index a703547350bb..0c3813da2d1a 100644 --- a/drivers/usb/cdns3/Makefile +++ b/drivers/usb/cdns3/Makefile @@ -1,16 +1,8 @@ -# SPDX-License-Identifier: GPL-2.0 # define_trace.h needs to know how to find our header -CFLAGS_trace.o := -I$(src) +CFLAGS_trace.o := -I$(src) -cdns3-y := core.o drd.o +obj-$(CONFIG_USB_CDNS3) += cdns3.o -obj-$(CONFIG_USB_CDNS3) += cdns3.o -cdns3-$(CONFIG_USB_CDNS3_GADGET) += gadget.o ep0.o - -ifneq ($(CONFIG_USB_CDNS3_GADGET),) -cdns3-$(CONFIG_TRACING) += trace.o -endif - -cdns3-$(CONFIG_USB_CDNS3_HOST) += host.o - -obj-$(CONFIG_USB_CDNS3_PCI_WRAP) += cdns3-pci-wrap.o +cdns3-y := core.o +cdns3-$(CONFIG_USB_CDNS3_GADGET) += gadget.o ep0.o trace.o +cdns3-$(CONFIG_USB_CDNS3_HOST) += host.o diff --git a/drivers/usb/cdns3/cdns3-nxp-reg-def.h b/drivers/usb/cdns3/cdns3-nxp-reg-def.h new file mode 100644 index 000000000000..4ac73005c023 --- /dev/null +++ b/drivers/usb/cdns3/cdns3-nxp-reg-def.h @@ -0,0 +1,174 @@ +/** + * cdns3-nxp-reg-def.h - nxp wrap layer register definition + * + * Copyright 2017 NXP + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 of + * the License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __DRIVERS_USB_CDNS3_NXP_H +#define __DRIVERS_USB_CDNS3_NXP_H + +#define USB3_CORE_CTRL1 0x00 +#define USB3_CORE_CTRL2 0x04 +#define USB3_INT_REG 0x08 +#define USB3_CORE_STATUS 0x0c +#define XHCI_DEBUG_LINK_ST 0x10 +#define XHCI_DEBUG_BUS 0x14 +#define USB3_SSPHY_CTRL1 0x40 +#define USB3_SSPHY_CTRL2 0x44 +#define USB3_SSPHY_STATUS 0x4c +#define USB2_PHY_CTRL1 0x50 +#define USB2_PHY_CTRL2 0x54 +#define USB2_PHY_STATUS 0x5c + +/* Register bits definition */ + +/* USB3_CORE_CTRL1 */ +#define SW_RESET_MASK (0x3f << 26) +#define PWR_SW_RESET (1 << 31) +#define APB_SW_RESET (1 << 30) +#define AXI_SW_RESET (1 << 29) +#define RW_SW_RESET (1 << 28) +#define PHY_SW_RESET (1 << 27) +#define PHYAHB_SW_RESET (1 << 26) +#define ALL_SW_RESET (PWR_SW_RESET | APB_SW_RESET | AXI_SW_RESET | \ + RW_SW_RESET | PHY_SW_RESET | PHYAHB_SW_RESET) +#define OC_DISABLE (1 << 9) +#define MDCTRL_CLK_SEL (1 << 7) +#define MODE_STRAP_MASK (0x7) +#define DEV_MODE (1 << 2) +#define HOST_MODE (1 << 1) +#define OTG_MODE (1 << 0) + +/* USB3_INT_REG */ +#define CLK_125_REQ (1 << 29) +#define LPM_CLK_REQ (1 << 28) +#define DEVU3_WAEKUP_EN (1 << 14) +#define OTG_WAKEUP_EN (1 << 12) +#define DEV_INT_EN (3 << 8) /* DEV INT b9:8 */ +#define HOST_INT1_EN (1 << 0) /* HOST INT b7:0 */ + +/* USB3_CORE_STATUS */ +#define MDCTRL_CLK_STATUS (1 << 15) +#define DEV_POWER_ON_READY (1 << 13) +#define HOST_POWER_ON_READY (1 << 12) + +/* USB3_SSPHY_STATUS */ +#define PHY_REFCLK_REQ (1 << 0) + + +/* PHY register definition */ +#define PHY_PMA_CMN_CTRL1 (0xC800 * 4) +#define TB_ADDR_CMN_DIAG_HSCLK_SEL (0x01e0 * 4) +#define TB_ADDR_CMN_PLL0_VCOCAL_INIT_TMR (0x0084 * 4) +#define TB_ADDR_CMN_PLL0_VCOCAL_ITER_TMR (0x0085 * 4) +#define TB_ADDR_CMN_PLL0_INTDIV (0x0094 * 4) +#define TB_ADDR_CMN_PLL0_FRACDIV (0x0095 * 4) +#define TB_ADDR_CMN_PLL0_HIGH_THR (0x0096 * 4) +#define TB_ADDR_CMN_PLL0_SS_CTRL1 (0x0098 * 4) +#define TB_ADDR_CMN_PLL0_SS_CTRL2 (0x0099 * 4) +#define TB_ADDR_CMN_PLL0_DSM_DIAG (0x0097 * 4) +#define TB_ADDR_CMN_DIAG_PLL0_OVRD (0x01c2 * 4) +#define TB_ADDR_CMN_DIAG_PLL0_FBH_OVRD (0x01c0 * 4) +#define TB_ADDR_CMN_DIAG_PLL0_FBL_OVRD (0x01c1 * 4) +#define TB_ADDR_CMN_DIAG_PLL0_V2I_TUNE (0x01C5 * 4) +#define TB_ADDR_CMN_DIAG_PLL0_CP_TUNE (0x01C6 * 4) +#define TB_ADDR_CMN_DIAG_PLL0_LF_PROG (0x01C7 * 4) +#define TB_ADDR_CMN_DIAG_PLL0_TEST_MODE (0x01c4 * 4) +#define TB_ADDR_CMN_PSM_CLK_CTRL (0x0061 * 4) +#define TB_ADDR_XCVR_DIAG_RX_LANE_CAL_RST_TMR (0x40ea * 4) +#define TB_ADDR_XCVR_PSM_RCTRL (0x4001 * 4) +#define TB_ADDR_TX_PSC_A0 (0x4100 * 4) +#define TB_ADDR_TX_PSC_A1 (0x4101 * 4) +#define TB_ADDR_TX_PSC_A2 (0x4102 * 4) +#define TB_ADDR_TX_PSC_A3 (0x4103 * 4) +#define TB_ADDR_TX_DIAG_ECTRL_OVRD (0x41f5 * 4) +#define TB_ADDR_TX_PSC_CAL (0x4106 * 4) +#define TB_ADDR_TX_PSC_RDY (0x4107 * 4) +#define TB_ADDR_RX_PSC_A0 (0x8000 * 4) +#define TB_ADDR_RX_PSC_A1 (0x8001 * 4) +#define TB_ADDR_RX_PSC_A2 (0x8002 * 4) +#define TB_ADDR_RX_PSC_A3 (0x8003 * 4) +#define TB_ADDR_RX_PSC_CAL (0x8006 * 4) +#define TB_ADDR_RX_PSC_RDY (0x8007 * 4) +#define TB_ADDR_TX_TXCC_MGNLS_MULT_000 (0x4058 * 4) +#define TB_ADDR_TX_DIAG_BGREF_PREDRV_DELAY (0x41e7 * 4) +#define TB_ADDR_RX_SLC_CU_ITER_TMR (0x80e3 * 4) +#define TB_ADDR_RX_SIGDET_HL_FILT_TMR (0x8090 * 4) +#define TB_ADDR_RX_SAMP_DAC_CTRL (0x8058 * 4) +#define TB_ADDR_RX_DIAG_SIGDET_TUNE (0x81dc * 4) +#define TB_ADDR_RX_DIAG_LFPSDET_TUNE2 (0x81df * 4) +#define TB_ADDR_RX_DIAG_BS_TM (0x81f5 * 4) +#define TB_ADDR_RX_DIAG_DFE_CTRL1 (0x81d3 * 4) +#define TB_ADDR_RX_DIAG_ILL_IQE_TRIM4 (0x81c7 * 4) +#define TB_ADDR_RX_DIAG_ILL_E_TRIM0 (0x81c2 * 4) +#define TB_ADDR_RX_DIAG_ILL_IQ_TRIM0 (0x81c1 * 4) +#define TB_ADDR_RX_DIAG_ILL_IQE_TRIM6 (0x81c9 * 4) +#define TB_ADDR_RX_DIAG_RXFE_TM3 (0x81f8 * 4) +#define TB_ADDR_RX_DIAG_RXFE_TM4 (0x81f9 * 4) +#define TB_ADDR_RX_DIAG_LFPSDET_TUNE (0x81dd * 4) +#define TB_ADDR_RX_DIAG_DFE_CTRL3 (0x81d5 * 4) +#define TB_ADDR_RX_DIAG_SC2C_DELAY (0x81e1 * 4) +#define TB_ADDR_RX_REE_VGA_GAIN_NODFE (0x81bf * 4) +#define TB_ADDR_XCVR_PSM_CAL_TMR (0x4002 * 4) +#define TB_ADDR_XCVR_PSM_A0BYP_TMR (0x4004 * 4) +#define TB_ADDR_XCVR_PSM_A0IN_TMR (0x4003 * 4) +#define TB_ADDR_XCVR_PSM_A1IN_TMR (0x4005 * 4) +#define TB_ADDR_XCVR_PSM_A2IN_TMR (0x4006 * 4) +#define TB_ADDR_XCVR_PSM_A3IN_TMR (0x4007 * 4) +#define TB_ADDR_XCVR_PSM_A4IN_TMR (0x4008 * 4) +#define TB_ADDR_XCVR_PSM_A5IN_TMR (0x4009 * 4) +#define TB_ADDR_XCVR_PSM_A0OUT_TMR (0x400a * 4) +#define TB_ADDR_XCVR_PSM_A1OUT_TMR (0x400b * 4) +#define TB_ADDR_XCVR_PSM_A2OUT_TMR (0x400c * 4) +#define TB_ADDR_XCVR_PSM_A3OUT_TMR (0x400d * 4) +#define TB_ADDR_XCVR_PSM_A4OUT_TMR (0x400e * 4) +#define TB_ADDR_XCVR_PSM_A5OUT_TMR (0x400f * 4) +#define TB_ADDR_TX_RCVDET_EN_TMR (0x4122 * 4) +#define TB_ADDR_TX_RCVDET_ST_TMR (0x4123 * 4) +#define TB_ADDR_XCVR_DIAG_LANE_FCM_EN_MGN_TMR (0x40f2 * 4) +#define TB_ADDR_TX_RCVDETSC_CTRL (0x4124 * 4) + +/* Register bits definition */ + +/* TB_ADDR_TX_RCVDETSC_CTRL */ +#define RXDET_IN_P3_32KHZ (1 << 0) + +/* OTG registers definition */ +#define OTGSTS 0x4 +#define OTGREFCLK 0xc + +/* Register bits definition */ +/* OTGSTS */ +#define OTG_NRDY (1 << 11) +/* OTGREFCLK */ +#define OTG_STB_CLK_SWITCH_EN (1 << 31) + +/* xHCI registers definition */ +#define XECP_PORT_CAP_REG 0x8000 +#define XECP_PM_PMCSR 0x8018 +#define XECP_AUX_CTRL_REG1 0x8120 + +/* Register bits definition */ +/* XECP_PORT_CAP_REG */ +#define LPM_2_STB_SWITCH_EN (1 << 25) + +/* XECP_AUX_CTRL_REG1 */ +#define CFG_RXDET_P3_EN (1 << 15) + +/* XECP_PM_PMCSR */ +#define PS_MASK (3 << 0) +#define PS_D0 0 +#define PS_D1 (1 << 0) +#endif /* __DRIVERS_USB_CDNS3_NXP_H */ diff --git a/drivers/usb/cdns3/cdns3-pci-wrap.c b/drivers/usb/cdns3/cdns3-pci-wrap.c deleted file mode 100644 index b0a29efe7d31..000000000000 --- a/drivers/usb/cdns3/cdns3-pci-wrap.c +++ /dev/null @@ -1,204 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Cadence USBSS PCI Glue driver - * - * Copyright (C) 2018-2019 Cadence. - * - * Author: Pawel Laszczak <pawell@cadence.com> - */ - -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/pci.h> -#include <linux/platform_device.h> -#include <linux/dma-mapping.h> -#include <linux/slab.h> - -struct cdns3_wrap { - struct platform_device *plat_dev; - struct resource dev_res[6]; - int devfn; -}; - -#define RES_IRQ_HOST_ID 0 -#define RES_IRQ_PERIPHERAL_ID 1 -#define RES_IRQ_OTG_ID 2 -#define RES_HOST_ID 3 -#define RES_DEV_ID 4 -#define RES_DRD_ID 5 - -#define PCI_BAR_HOST 0 -#define PCI_BAR_DEV 2 -#define PCI_BAR_OTG 0 - -#define PCI_DEV_FN_HOST_DEVICE 0 -#define PCI_DEV_FN_OTG 1 - -#define PCI_DRIVER_NAME "cdns3-pci-usbss" -#define PLAT_DRIVER_NAME "cdns-usb3" - -#define CDNS_VENDOR_ID 0x17cd -#define CDNS_DEVICE_ID 0x0100 - -static struct pci_dev *cdns3_get_second_fun(struct pci_dev *pdev) -{ - struct pci_dev *func; - - /* - * Gets the second function. - * It's little tricky, but this platform has two function. - * The fist keeps resources for Host/Device while the second - * keeps resources for DRD/OTG. - */ - func = pci_get_device(pdev->vendor, pdev->device, NULL); - if (unlikely(!func)) - return NULL; - - if (func->devfn == pdev->devfn) { - func = pci_get_device(pdev->vendor, pdev->device, func); - if (unlikely(!func)) - return NULL; - } - - return func; -} - -static int cdns3_pci_probe(struct pci_dev *pdev, - const struct pci_device_id *id) -{ - struct platform_device_info plat_info; - struct cdns3_wrap *wrap; - struct resource *res; - struct pci_dev *func; - int err; - - /* - * for GADGET/HOST PCI (devfn) function number is 0, - * for OTG PCI (devfn) function number is 1 - */ - if (!id || (pdev->devfn != PCI_DEV_FN_HOST_DEVICE && - pdev->devfn != PCI_DEV_FN_OTG)) - return -EINVAL; - - func = cdns3_get_second_fun(pdev); - if (unlikely(!func)) - return -EINVAL; - - err = pcim_enable_device(pdev); - if (err) { - dev_err(&pdev->dev, "Enabling PCI device has failed %d\n", err); - return err; - } - - pci_set_master(pdev); - - if (pci_is_enabled(func)) { - wrap = pci_get_drvdata(func); - } else { - wrap = kzalloc(sizeof(*wrap), GFP_KERNEL); - if (!wrap) { - pci_disable_device(pdev); - return -ENOMEM; - } - } - - res = wrap->dev_res; - - if (pdev->devfn == PCI_DEV_FN_HOST_DEVICE) { - /* function 0: host(BAR_0) + device(BAR_1).*/ - dev_dbg(&pdev->dev, "Initialize Device resources\n"); - res[RES_DEV_ID].start = pci_resource_start(pdev, PCI_BAR_DEV); - res[RES_DEV_ID].end = pci_resource_end(pdev, PCI_BAR_DEV); - res[RES_DEV_ID].name = "dev"; - res[RES_DEV_ID].flags = IORESOURCE_MEM; - dev_dbg(&pdev->dev, "USBSS-DEV physical base addr: %pa\n", - &res[RES_DEV_ID].start); - - res[RES_HOST_ID].start = pci_resource_start(pdev, PCI_BAR_HOST); - res[RES_HOST_ID].end = pci_resource_end(pdev, PCI_BAR_HOST); - res[RES_HOST_ID].name = "xhci"; - res[RES_HOST_ID].flags = IORESOURCE_MEM; - dev_dbg(&pdev->dev, "USBSS-XHCI physical base addr: %pa\n", - &res[RES_HOST_ID].start); - - /* Interrupt for XHCI */ - wrap->dev_res[RES_IRQ_HOST_ID].start = pdev->irq; - wrap->dev_res[RES_IRQ_HOST_ID].name = "host"; - wrap->dev_res[RES_IRQ_HOST_ID].flags = IORESOURCE_IRQ; - - /* Interrupt device. It's the same as for HOST. */ - wrap->dev_res[RES_IRQ_PERIPHERAL_ID].start = pdev->irq; - wrap->dev_res[RES_IRQ_PERIPHERAL_ID].name = "peripheral"; - wrap->dev_res[RES_IRQ_PERIPHERAL_ID].flags = IORESOURCE_IRQ; - } else { - res[RES_DRD_ID].start = pci_resource_start(pdev, PCI_BAR_OTG); - res[RES_DRD_ID].end = pci_resource_end(pdev, PCI_BAR_OTG); - res[RES_DRD_ID].name = "otg"; - res[RES_DRD_ID].flags = IORESOURCE_MEM; - dev_dbg(&pdev->dev, "USBSS-DRD physical base addr: %pa\n", - &res[RES_DRD_ID].start); - - /* Interrupt for OTG/DRD. */ - wrap->dev_res[RES_IRQ_OTG_ID].start = pdev->irq; - wrap->dev_res[RES_IRQ_OTG_ID].name = "otg"; - wrap->dev_res[RES_IRQ_OTG_ID].flags = IORESOURCE_IRQ; - } - - if (pci_is_enabled(func)) { - /* set up platform device info */ - memset(&plat_info, 0, sizeof(plat_info)); - plat_info.parent = &pdev->dev; - plat_info.fwnode = pdev->dev.fwnode; - plat_info.name = PLAT_DRIVER_NAME; - plat_info.id = pdev->devfn; - wrap->devfn = pdev->devfn; - plat_info.res = wrap->dev_res; - plat_info.num_res = ARRAY_SIZE(wrap->dev_res); - plat_info.dma_mask = pdev->dma_mask; - /* register platform device */ - wrap->plat_dev = platform_device_register_full(&plat_info); - if (IS_ERR(wrap->plat_dev)) { - pci_disable_device(pdev); - err = PTR_ERR(wrap->plat_dev); - kfree(wrap); - return err; - } - } - - pci_set_drvdata(pdev, wrap); - return err; -} - -static void cdns3_pci_remove(struct pci_dev *pdev) -{ - struct cdns3_wrap *wrap; - struct pci_dev *func; - - func = cdns3_get_second_fun(pdev); - - wrap = (struct cdns3_wrap *)pci_get_drvdata(pdev); - if (wrap->devfn == pdev->devfn) - platform_device_unregister(wrap->plat_dev); - - if (!pci_is_enabled(func)) - kfree(wrap); -} - -static const struct pci_device_id cdns3_pci_ids[] = { - { PCI_DEVICE(CDNS_VENDOR_ID, CDNS_DEVICE_ID), }, - { 0, } -}; - -static struct pci_driver cdns3_pci_driver = { - .name = PCI_DRIVER_NAME, - .id_table = cdns3_pci_ids, - .probe = cdns3_pci_probe, - .remove = cdns3_pci_remove, -}; - -module_pci_driver(cdns3_pci_driver); -MODULE_DEVICE_TABLE(pci, cdns3_pci_ids); - -MODULE_AUTHOR("Pawel Laszczak <pawell@cadence.com>"); -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("Cadence USBSS PCI wrapperr"); diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c index c2123ef8d8a3..c201d00ac194 100644 --- a/drivers/usb/cdns3/core.c +++ b/drivers/usb/cdns3/core.c @@ -1,89 +1,289 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Cadence USBSS DRD Driver. +/** + * core.c - Cadence USB3 DRD Controller Core file + * + * Copyright 2017-2019 NXP + * + * Authors: Peter Chen <peter.chen@nxp.com> * - * Copyright (C) 2018-2019 Cadence. - * Copyright (C) 2017-2018 NXP - * Copyright (C) 2019 Texas Instruments + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 of + * the License as published by the Free Software Foundation. * - * Author: Peter Chen <peter.chen@nxp.com> - * Pawel Laszczak <pawell@cadence.com> - * Roger Quadros <rogerq@ti.com> + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#include <linux/dma-mapping.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/platform_device.h> #include <linux/interrupt.h> +#include <linux/of.h> +#include <linux/of_platform.h> #include <linux/io.h> +#include <linux/clk.h> +#include <linux/usb/of.h> +#include <linux/usb/phy.h> +#include <linux/extcon.h> #include <linux/pm_runtime.h> -#include "gadget.h" +#include "cdns3-nxp-reg-def.h" #include "core.h" #include "host-export.h" #include "gadget-export.h" -#include "drd.h" - -static int cdns3_idle_init(struct cdns3 *cdns); -static inline -struct cdns3_role_driver *cdns3_get_current_role_driver(struct cdns3 *cdns) +/** + * cdns3_handshake - spin reading until handshake completes or fails + * @ptr: address of device controller register to be read + * @mask: bits to look at in result of read + * @done: value of those bits when handshake succeeds + * @usec: timeout in microseconds + * + * Returns negative errno, or zero on success + * + * Success happens when the "mask" bits have the specified value (hardware + * handshake done). There are two failure modes: "usec" have passed (major + * hardware flakeout), or the register reads as all-ones (hardware removed). + */ +int cdns3_handshake(void __iomem *ptr, u32 mask, u32 done, int usec) { - WARN_ON(!cdns->roles[cdns->role]); - return cdns->roles[cdns->role]; -} + u32 result; -static int cdns3_role_start(struct cdns3 *cdns, enum usb_role role) -{ - int ret; + do { + result = readl(ptr); + if (result == ~(u32)0) /* card removed */ + return -ENODEV; - if (WARN_ON(role > USB_ROLE_DEVICE)) - return 0; - - mutex_lock(&cdns->mutex); - cdns->role = role; - mutex_unlock(&cdns->mutex); - - if (!cdns->roles[role]) - return -ENXIO; + result &= mask; + if (result == done) + return 0; - if (cdns->roles[role]->state == CDNS3_ROLE_STATE_ACTIVE) - return 0; + udelay(1); + usec--; + } while (usec > 0); - mutex_lock(&cdns->mutex); - ret = cdns->roles[role]->start(cdns); - if (!ret) - cdns->roles[role]->state = CDNS3_ROLE_STATE_ACTIVE; - mutex_unlock(&cdns->mutex); + return -ETIMEDOUT; +} - return ret; +static void cdns3_usb_phy_init(void __iomem *regs) +{ + u32 value; + + pr_debug("begin of %s\n", __func__); + + writel(0x0830, regs + PHY_PMA_CMN_CTRL1); + writel(0x10, regs + TB_ADDR_CMN_DIAG_HSCLK_SEL); + writel(0x00F0, regs + TB_ADDR_CMN_PLL0_VCOCAL_INIT_TMR); + writel(0x0018, regs + TB_ADDR_CMN_PLL0_VCOCAL_ITER_TMR); + writel(0x00D0, regs + TB_ADDR_CMN_PLL0_INTDIV); + writel(0x4aaa, regs + TB_ADDR_CMN_PLL0_FRACDIV); + writel(0x0034, regs + TB_ADDR_CMN_PLL0_HIGH_THR); + writel(0x1ee, regs + TB_ADDR_CMN_PLL0_SS_CTRL1); + writel(0x7F03, regs + TB_ADDR_CMN_PLL0_SS_CTRL2); + writel(0x0020, regs + TB_ADDR_CMN_PLL0_DSM_DIAG); + writel(0x0000, regs + TB_ADDR_CMN_DIAG_PLL0_OVRD); + writel(0x0000, regs + TB_ADDR_CMN_DIAG_PLL0_FBH_OVRD); + writel(0x0000, regs + TB_ADDR_CMN_DIAG_PLL0_FBL_OVRD); + writel(0x0007, regs + TB_ADDR_CMN_DIAG_PLL0_V2I_TUNE); + writel(0x0027, regs + TB_ADDR_CMN_DIAG_PLL0_CP_TUNE); + writel(0x0008, regs + TB_ADDR_CMN_DIAG_PLL0_LF_PROG); + writel(0x0022, regs + TB_ADDR_CMN_DIAG_PLL0_TEST_MODE); + writel(0x000a, regs + TB_ADDR_CMN_PSM_CLK_CTRL); + writel(0x139, regs + TB_ADDR_XCVR_DIAG_RX_LANE_CAL_RST_TMR); + writel(0xbefc, regs + TB_ADDR_XCVR_PSM_RCTRL); + + writel(0x7799, regs + TB_ADDR_TX_PSC_A0); + writel(0x7798, regs + TB_ADDR_TX_PSC_A1); + writel(0x509b, regs + TB_ADDR_TX_PSC_A2); + writel(0x3, regs + TB_ADDR_TX_DIAG_ECTRL_OVRD); + writel(0x509b, regs + TB_ADDR_TX_PSC_A3); + writel(0x2090, regs + TB_ADDR_TX_PSC_CAL); + writel(0x2090, regs + TB_ADDR_TX_PSC_RDY); + + writel(0xA6FD, regs + TB_ADDR_RX_PSC_A0); + writel(0xA6FD, regs + TB_ADDR_RX_PSC_A1); + writel(0xA410, regs + TB_ADDR_RX_PSC_A2); + writel(0x2410, regs + TB_ADDR_RX_PSC_A3); + + writel(0x23FF, regs + TB_ADDR_RX_PSC_CAL); + writel(0x2010, regs + TB_ADDR_RX_PSC_RDY); + + writel(0x0020, regs + TB_ADDR_TX_TXCC_MGNLS_MULT_000); + writel(0x00ff, regs + TB_ADDR_TX_DIAG_BGREF_PREDRV_DELAY); + writel(0x0002, regs + TB_ADDR_RX_SLC_CU_ITER_TMR); + writel(0x0013, regs + TB_ADDR_RX_SIGDET_HL_FILT_TMR); + writel(0x0000, regs + TB_ADDR_RX_SAMP_DAC_CTRL); + writel(0x1004, regs + TB_ADDR_RX_DIAG_SIGDET_TUNE); + writel(0x4041, regs + TB_ADDR_RX_DIAG_LFPSDET_TUNE2); + writel(0x0480, regs + TB_ADDR_RX_DIAG_BS_TM); + writel(0x8006, regs + TB_ADDR_RX_DIAG_DFE_CTRL1); + writel(0x003f, regs + TB_ADDR_RX_DIAG_ILL_IQE_TRIM4); + writel(0x543f, regs + TB_ADDR_RX_DIAG_ILL_E_TRIM0); + writel(0x543f, regs + TB_ADDR_RX_DIAG_ILL_IQ_TRIM0); + writel(0x0000, regs + TB_ADDR_RX_DIAG_ILL_IQE_TRIM6); + writel(0x8000, regs + TB_ADDR_RX_DIAG_RXFE_TM3); + writel(0x0003, regs + TB_ADDR_RX_DIAG_RXFE_TM4); + writel(0x2408, regs + TB_ADDR_RX_DIAG_LFPSDET_TUNE); + writel(0x05ca, regs + TB_ADDR_RX_DIAG_DFE_CTRL3); + writel(0x0258, regs + TB_ADDR_RX_DIAG_SC2C_DELAY); + writel(0x1fff, regs + TB_ADDR_RX_REE_VGA_GAIN_NODFE); + + writel(0x02c6, regs + TB_ADDR_XCVR_PSM_CAL_TMR); + writel(0x0002, regs + TB_ADDR_XCVR_PSM_A0BYP_TMR); + writel(0x02c6, regs + TB_ADDR_XCVR_PSM_A0IN_TMR); + writel(0x0010, regs + TB_ADDR_XCVR_PSM_A1IN_TMR); + writel(0x0010, regs + TB_ADDR_XCVR_PSM_A2IN_TMR); + writel(0x0010, regs + TB_ADDR_XCVR_PSM_A3IN_TMR); + writel(0x0010, regs + TB_ADDR_XCVR_PSM_A4IN_TMR); + writel(0x0010, regs + TB_ADDR_XCVR_PSM_A5IN_TMR); + + writel(0x0002, regs + TB_ADDR_XCVR_PSM_A0OUT_TMR); + writel(0x0002, regs + TB_ADDR_XCVR_PSM_A1OUT_TMR); + writel(0x0002, regs + TB_ADDR_XCVR_PSM_A2OUT_TMR); + writel(0x0002, regs + TB_ADDR_XCVR_PSM_A3OUT_TMR); + writel(0x0002, regs + TB_ADDR_XCVR_PSM_A4OUT_TMR); + writel(0x0002, regs + TB_ADDR_XCVR_PSM_A5OUT_TMR); + + /* Change rx detect parameter */ + writel(0x960, regs + TB_ADDR_TX_RCVDET_EN_TMR); + writel(0x01e0, regs + TB_ADDR_TX_RCVDET_ST_TMR); + writel(0x0090, regs + TB_ADDR_XCVR_DIAG_LANE_FCM_EN_MGN_TMR); + + /* RXDET_IN_P3_32KHZ, Receiver detect slow clock enable */ + value = readl(regs + TB_ADDR_TX_RCVDETSC_CTRL); + value |= RXDET_IN_P3_32KHZ; + writel(value, regs + TB_ADDR_TX_RCVDETSC_CTRL); + + udelay(10); + + pr_debug("end of %s\n", __func__); } -static void cdns3_role_stop(struct cdns3 *cdns) +static void cdns_set_role(struct cdns3 *cdns, enum cdns3_roles role) { - enum usb_role role = cdns->role; + u32 value; + int timeout_us = 100000; + void __iomem *xhci_regs = cdns->xhci_regs; - if (WARN_ON(role > USB_ROLE_DEVICE)) + if (role == CDNS3_ROLE_END) return; - if (cdns->roles[role]->state == CDNS3_ROLE_STATE_INACTIVE) - return; + /* Wait clk value */ + value = readl(cdns->none_core_regs + USB3_SSPHY_STATUS); + writel(value, cdns->none_core_regs + USB3_SSPHY_STATUS); + udelay(1); + value = readl(cdns->none_core_regs + USB3_SSPHY_STATUS); + while ((value & 0xf0000000) != 0xf0000000 && timeout_us-- > 0) { + value = readl(cdns->none_core_regs + USB3_SSPHY_STATUS); + dev_dbg(cdns->dev, "clkvld:0x%x\n", value); + udelay(1); + } + + if (timeout_us <= 0) + dev_err(cdns->dev, "wait clkvld timeout\n"); + + /* Set all Reset bits */ + value = readl(cdns->none_core_regs + USB3_CORE_CTRL1); + value |= ALL_SW_RESET; + writel(value, cdns->none_core_regs + USB3_CORE_CTRL1); + udelay(1); + + if (role == CDNS3_ROLE_HOST) { + value = readl(cdns->none_core_regs + USB3_CORE_CTRL1); + value = (value & ~MODE_STRAP_MASK) | HOST_MODE | OC_DISABLE; + writel(value, cdns->none_core_regs + USB3_CORE_CTRL1); + value &= ~PHYAHB_SW_RESET; + writel(value, cdns->none_core_regs + USB3_CORE_CTRL1); + mdelay(1); + cdns3_usb_phy_init(cdns->phy_regs); + /* Force B Session Valid as 1 */ + writel(0x0060, cdns->phy_regs + 0x380a4); + mdelay(1); + + value = readl(cdns->none_core_regs + USB3_INT_REG); + value |= HOST_INT1_EN; + writel(value, cdns->none_core_regs + USB3_INT_REG); + + value = readl(cdns->none_core_regs + USB3_CORE_CTRL1); + value &= ~ALL_SW_RESET; + writel(value, cdns->none_core_regs + USB3_CORE_CTRL1); + + dev_dbg(cdns->dev, "wait xhci_power_on_ready\n"); + + value = readl(cdns->none_core_regs + USB3_CORE_STATUS); + timeout_us = 100000; + while (!(value & HOST_POWER_ON_READY) && timeout_us-- > 0) { + value = readl(cdns->none_core_regs + USB3_CORE_STATUS); + udelay(1); + } - mutex_lock(&cdns->mutex); - cdns->roles[role]->stop(cdns); - cdns->roles[role]->state = CDNS3_ROLE_STATE_INACTIVE; - mutex_unlock(&cdns->mutex); + if (timeout_us <= 0) + dev_err(cdns->dev, "wait xhci_power_on_ready timeout\n"); + + value = readl(xhci_regs + XECP_PORT_CAP_REG); + value |= LPM_2_STB_SWITCH_EN; + writel(value, xhci_regs + XECP_PORT_CAP_REG); + + mdelay(1); + + dev_dbg(cdns->dev, "switch to host role successfully\n"); + } else { /* gadget mode */ + value = readl(cdns->none_core_regs + USB3_CORE_CTRL1); + value = (value & ~MODE_STRAP_MASK) | DEV_MODE; + writel(value, cdns->none_core_regs + USB3_CORE_CTRL1); + value &= ~PHYAHB_SW_RESET; + writel(value, cdns->none_core_regs + USB3_CORE_CTRL1); + + cdns3_usb_phy_init(cdns->phy_regs); + /* Force B Session Valid as 1 */ + writel(0x0060, cdns->phy_regs + 0x380a4); + value = readl(cdns->none_core_regs + USB3_INT_REG); + value |= DEV_INT_EN; + writel(value, cdns->none_core_regs + USB3_INT_REG); + + value = readl(cdns->none_core_regs + USB3_CORE_CTRL1); + value &= ~ALL_SW_RESET; + writel(value, cdns->none_core_regs + USB3_CORE_CTRL1); + + dev_dbg(cdns->dev, "wait gadget_power_on_ready\n"); + + value = readl(cdns->none_core_regs + USB3_CORE_STATUS); + timeout_us = 100000; + while (!(value & DEV_POWER_ON_READY) && timeout_us-- > 0) { + value = readl(cdns->none_core_regs + USB3_CORE_STATUS); + udelay(1); + } + + if (timeout_us <= 0) + dev_err(cdns->dev, + "wait gadget_power_on_ready timeout\n"); + + mdelay(1); + + dev_dbg(cdns->dev, "switch to gadget role successfully\n"); + } } -static void cdns3_exit_roles(struct cdns3 *cdns) +static enum cdns3_roles cdns3_get_role(struct cdns3 *cdns) { - cdns3_role_stop(cdns); - cdns3_drd_exit(cdns); + if (cdns->roles[CDNS3_ROLE_HOST] && cdns->roles[CDNS3_ROLE_GADGET]) { + if (extcon_get_state(cdns->extcon, EXTCON_USB_HOST)) + return CDNS3_ROLE_HOST; + else if (extcon_get_state(cdns->extcon, EXTCON_USB)) + return CDNS3_ROLE_GADGET; + else + return CDNS3_ROLE_END; + } else { + return cdns->roles[CDNS3_ROLE_HOST] + ? CDNS3_ROLE_HOST + : CDNS3_ROLE_GADGET; + } } -static enum usb_role cdsn3_hw_role_state_machine(struct cdns3 *cdns); - /** * cdns3_core_init_role - initialize role of operation * @cdns: Pointer to cdns3 structure @@ -93,340 +293,303 @@ static enum usb_role cdsn3_hw_role_state_machine(struct cdns3 *cdns); static int cdns3_core_init_role(struct cdns3 *cdns) { struct device *dev = cdns->dev; - enum usb_dr_mode best_dr_mode; - enum usb_dr_mode dr_mode; - int ret = 0; - - dr_mode = usb_get_dr_mode(dev); - cdns->role = USB_ROLE_NONE; - - /* - * If driver can't read mode by means of usb_get_dr_mode function then - * chooses mode according with Kernel configuration. This setting - * can be restricted later depending on strap pin configuration. - */ - if (dr_mode == USB_DR_MODE_UNKNOWN) { - if (IS_ENABLED(CONFIG_USB_CDNS3_HOST) && - IS_ENABLED(CONFIG_USB_CDNS3_GADGET)) - dr_mode = USB_DR_MODE_OTG; - else if (IS_ENABLED(CONFIG_USB_CDNS3_HOST)) - dr_mode = USB_DR_MODE_HOST; - else if (IS_ENABLED(CONFIG_USB_CDNS3_GADGET)) - dr_mode = USB_DR_MODE_PERIPHERAL; - } - - /* - * At this point cdns->dr_mode contains strap configuration. - * Driver try update this setting considering kernel configuration - */ - best_dr_mode = cdns->dr_mode; - - ret = cdns3_idle_init(cdns); - if (ret) - return ret; - - if (dr_mode == USB_DR_MODE_OTG) { - best_dr_mode = cdns->dr_mode; - } else if (cdns->dr_mode == USB_DR_MODE_OTG) { - best_dr_mode = dr_mode; - } else if (cdns->dr_mode != dr_mode) { - dev_err(dev, "Incorrect DRD configuration\n"); - return -EINVAL; - } + enum usb_dr_mode dr_mode = usb_get_dr_mode(dev); - dr_mode = best_dr_mode; + cdns->role = CDNS3_ROLE_END; + if (dr_mode == USB_DR_MODE_UNKNOWN) + dr_mode = USB_DR_MODE_OTG; if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) { - ret = cdns3_host_init(cdns); - if (ret) { - dev_err(dev, "Host initialization failed with %d\n", - ret); - goto err; - } + if (cdns3_host_init(cdns)) + dev_info(dev, "doesn't support host\n"); } if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_PERIPHERAL) { - ret = cdns3_gadget_init(cdns); - if (ret) { - dev_err(dev, "Device initialization failed with %d\n", - ret); - goto err; - } + if (cdns3_gadget_init(cdns)) + dev_info(dev, "doesn't support gadget\n"); } - cdns->dr_mode = dr_mode; - - ret = cdns3_drd_update_mode(cdns); - if (ret) - goto err; - - /* Initialize idle role to start with */ - ret = cdns3_role_start(cdns, USB_ROLE_NONE); - if (ret) - goto err; - - switch (cdns->dr_mode) { - case USB_DR_MODE_OTG: - ret = cdns3_hw_role_switch(cdns); - if (ret) - goto err; - break; - case USB_DR_MODE_PERIPHERAL: - ret = cdns3_role_start(cdns, USB_ROLE_DEVICE); - if (ret) - goto err; - break; - case USB_DR_MODE_HOST: - ret = cdns3_role_start(cdns, USB_ROLE_HOST); - if (ret) - goto err; - break; - default: - ret = -EINVAL; - goto err; + if (!cdns->roles[CDNS3_ROLE_HOST] && !cdns->roles[CDNS3_ROLE_GADGET]) { + dev_err(dev, "no supported roles\n"); + return -ENODEV; } - return ret; -err: - cdns3_exit_roles(cdns); - return ret; + return 0; } /** - * cdsn3_hw_role_state_machine - role switch state machine based on hw events. - * @cdns: Pointer to controller structure. + * cdns3_irq - interrupt handler for cdns3 core device + * + * @irq: irq number for cdns3 core device + * @data: structure of cdns3 * - * Returns next role to be entered based on hw events. + * Returns IRQ_HANDLED or IRQ_NONE */ -static enum usb_role cdsn3_hw_role_state_machine(struct cdns3 *cdns) +static irqreturn_t cdns3_irq(int irq, void *data) { - enum usb_role role; - int id, vbus; + struct cdns3 *cdns = data; + irqreturn_t ret = IRQ_NONE; + + if (cdns->in_lpm) { + disable_irq_nosync(cdns->irq); + cdns->wakeup_int = true; + pm_runtime_get(cdns->dev); + return IRQ_HANDLED; + } - if (cdns->dr_mode != USB_DR_MODE_OTG) - goto not_otg; + /* Handle device/host interrupt */ + if (cdns->role != CDNS3_ROLE_END) + ret = cdns3_role(cdns)->irq(cdns); - id = cdns3_get_id(cdns); - vbus = cdns3_get_vbus(cdns); + return ret; +} - /* - * Role change state machine - * Inputs: ID, VBUS - * Previous state: cdns->role - * Next state: role - */ - role = cdns->role; +static irqreturn_t cdns3_thread_irq(int irq, void *data) +{ + struct cdns3 *cdns = data; + irqreturn_t ret = IRQ_NONE; - switch (role) { - case USB_ROLE_NONE: - /* - * Driver treats USB_ROLE_NONE synonymous to IDLE state from - * controller specification. - */ - if (!id) - role = USB_ROLE_HOST; - else if (vbus) - role = USB_ROLE_DEVICE; - break; - case USB_ROLE_HOST: /* from HOST, we can only change to NONE */ - if (id) - role = USB_ROLE_NONE; - break; - case USB_ROLE_DEVICE: /* from GADGET, we can only change to NONE*/ - if (!vbus) - role = USB_ROLE_NONE; - break; - } - - dev_dbg(cdns->dev, "role %d -> %d\n", cdns->role, role); - - return role; - -not_otg: - if (cdns3_is_host(cdns)) - role = USB_ROLE_HOST; - if (cdns3_is_device(cdns)) - role = USB_ROLE_DEVICE; - - return role; + /* Handle device/host interrupt */ + if (cdns->role != CDNS3_ROLE_END && cdns3_role(cdns)->thread_irq) + ret = cdns3_role(cdns)->thread_irq(cdns); + + return ret; } -static int cdns3_idle_role_start(struct cdns3 *cdns) +static int cdns3_get_clks(struct device *dev) { + struct cdns3 *cdns = dev_get_drvdata(dev); + int ret = 0; + + cdns->cdns3_clks[0] = devm_clk_get(dev, "usb3_lpm_clk"); + if (IS_ERR(cdns->cdns3_clks[0])) { + ret = PTR_ERR(cdns->cdns3_clks[0]); + dev_err(dev, "Failed to get usb3_lpm_clk, err=%d\n", ret); + return ret; + } + + cdns->cdns3_clks[1] = devm_clk_get(dev, "usb3_bus_clk"); + if (IS_ERR(cdns->cdns3_clks[1])) { + ret = PTR_ERR(cdns->cdns3_clks[1]); + dev_err(dev, "Failed to get usb3_bus_clk, err=%d\n", ret); + return ret; + } + + cdns->cdns3_clks[2] = devm_clk_get(dev, "usb3_aclk"); + if (IS_ERR(cdns->cdns3_clks[2])) { + ret = PTR_ERR(cdns->cdns3_clks[2]); + dev_err(dev, "Failed to get usb3_aclk, err=%d\n", ret); + return ret; + } + + cdns->cdns3_clks[3] = devm_clk_get(dev, "usb3_ipg_clk"); + if (IS_ERR(cdns->cdns3_clks[3])) { + ret = PTR_ERR(cdns->cdns3_clks[3]); + dev_err(dev, "Failed to get usb3_ipg_clk, err=%d\n", ret); + return ret; + } + + cdns->cdns3_clks[4] = devm_clk_get(dev, "usb3_core_pclk"); + if (IS_ERR(cdns->cdns3_clks[4])) { + ret = PTR_ERR(cdns->cdns3_clks[4]); + dev_err(dev, "Failed to get usb3_core_pclk, err=%d\n", ret); + return ret; + } + return 0; } -static void cdns3_idle_role_stop(struct cdns3 *cdns) +static int cdns3_prepare_enable_clks(struct device *dev) { - /* Program Lane swap and bring PHY out of RESET */ - phy_reset(cdns->usb3_phy); -} + struct cdns3 *cdns = dev_get_drvdata(dev); + int i, j, ret = 0; -static int cdns3_idle_init(struct cdns3 *cdns) -{ - struct cdns3_role_driver *rdrv; + for (i = 0; i < CDNS3_NUM_OF_CLKS; i++) { + ret = clk_prepare_enable(cdns->cdns3_clks[i]); + if (ret) { + dev_err(dev, + "Failed to prepare/enable cdns3 clk, err=%d\n", + ret); + goto err; + } + } - rdrv = devm_kzalloc(cdns->dev, sizeof(*rdrv), GFP_KERNEL); - if (!rdrv) - return -ENOMEM; + return ret; +err: + for (j = i; j > 0; j--) + clk_disable_unprepare(cdns->cdns3_clks[j - 1]); - rdrv->start = cdns3_idle_role_start; - rdrv->stop = cdns3_idle_role_stop; - rdrv->state = CDNS3_ROLE_STATE_INACTIVE; - rdrv->suspend = NULL; - rdrv->resume = NULL; - rdrv->name = "idle"; + return ret; +} - cdns->roles[USB_ROLE_NONE] = rdrv; +static void cdns3_disable_unprepare_clks(struct device *dev) +{ + struct cdns3 *cdns = dev_get_drvdata(dev); + int i; - return 0; + for (i = CDNS3_NUM_OF_CLKS - 1; i >= 0; i--) + clk_disable_unprepare(cdns->cdns3_clks[i]); } -/** - * cdns3_hw_role_switch - switch roles based on HW state - * @cdns3: controller - */ -int cdns3_hw_role_switch(struct cdns3 *cdns) +static void cdns3_remove_roles(struct cdns3 *cdns) +{ + cdns3_gadget_exit(cdns); + cdns3_host_remove(cdns); +} + +static int cdns3_do_role_switch(struct cdns3 *cdns, enum cdns3_roles role) { - enum usb_role real_role, current_role; int ret = 0; + enum cdns3_roles current_role; + + dev_dbg(cdns->dev, "current role is %d, switch to %d\n", + cdns->role, role); - /* Do nothing if role based on syfs. */ - if (cdns->role_override) + if (cdns->role == role) return 0; pm_runtime_get_sync(cdns->dev); - current_role = cdns->role; - real_role = cdsn3_hw_role_state_machine(cdns); - - /* Do nothing if nothing changed */ - if (current_role == real_role) - goto exit; - cdns3_role_stop(cdns); + if (role == CDNS3_ROLE_END) { + /* Force B Session Valid as 0 */ + writel(0x0040, cdns->phy_regs + 0x380a4); + pm_runtime_put_sync(cdns->dev); + return 0; + } - dev_dbg(cdns->dev, "Switching role %d -> %d", current_role, real_role); - - ret = cdns3_role_start(cdns, real_role); + cdns_set_role(cdns, role); + ret = cdns3_role_start(cdns, role); if (ret) { /* Back to current role */ dev_err(cdns->dev, "set %d has failed, back to %d\n", - real_role, current_role); + role, current_role); + cdns_set_role(cdns, current_role); ret = cdns3_role_start(cdns, current_role); - if (ret) - dev_err(cdns->dev, "back to %d failed too\n", - current_role); } -exit: + pm_runtime_put_sync(cdns->dev); return ret; } /** - * cdsn3_role_get - get current role of controller. + * cdns3_role_switch - work queue handler for role switch * - * @dev: Pointer to device structure + * @work: work queue item structure * - * Returns role + * Handles below events: + * - Role switch for dual-role devices + * - CDNS3_ROLE_GADGET <--> CDNS3_ROLE_END for peripheral-only devices */ -static enum usb_role cdns3_role_get(struct device *dev) +static void cdns3_role_switch(struct work_struct *work) { - struct cdns3 *cdns = dev_get_drvdata(dev); + struct cdns3 *cdns = container_of(work, struct cdns3, + role_switch_wq); + bool device, host; + + host = extcon_get_state(cdns->extcon, EXTCON_USB_HOST); + device = extcon_get_state(cdns->extcon, EXTCON_USB); + + if (host) { + if (cdns->roles[CDNS3_ROLE_HOST]) + cdns3_do_role_switch(cdns, CDNS3_ROLE_HOST); + return; + } - return cdns->role; + if (device) + cdns3_do_role_switch(cdns, CDNS3_ROLE_GADGET); + else + cdns3_do_role_switch(cdns, CDNS3_ROLE_END); } -/** - * cdns3_role_set - set current role of controller. - * - * @dev: pointer to device object - * @role - the previous role - * Handles below events: - * - Role switch for dual-role devices - * - USB_ROLE_GADGET <--> USB_ROLE_NONE for peripheral-only devices - */ -static int cdns3_role_set(struct device *dev, enum usb_role role) +static int cdns3_extcon_notifier(struct notifier_block *nb, unsigned long event, + void *ptr) { - struct cdns3 *cdns = dev_get_drvdata(dev); - int ret = 0; + struct cdns3 *cdns = container_of(nb, struct cdns3, extcon_nb); - pm_runtime_get_sync(cdns->dev); + queue_work(system_freezable_wq, &cdns->role_switch_wq); - /* - * FIXME: switch role framework should be extended to meet - * requirements. Driver assumes that role can be controlled - * by SW or HW. Temporary workaround is to use USB_ROLE_NONE to - * switch from SW to HW control. - * - * For dr_mode == USB_DR_MODE_OTG: - * if user sets USB_ROLE_HOST or USB_ROLE_DEVICE then driver - * sets role_override flag and forces that role. - * if user sets USB_ROLE_NONE, driver clears role_override and lets - * HW state machine take over. - * - * For dr_mode != USB_DR_MODE_OTG: - * Assumptions: - * 1. Restricted user control between NONE and dr_mode. - * 2. Driver doesn't need to rely on role_override flag. - * 3. Driver needs to ensure that HW state machine is never called - * if dr_mode != USB_DR_MODE_OTG. - */ - if (role == USB_ROLE_NONE) - cdns->role_override = 0; - else - cdns->role_override = 1; + return NOTIFY_DONE; +} - /* - * HW state might have changed so driver need to trigger - * HW state machine if dr_mode == USB_DR_MODE_OTG. - */ - if (!cdns->role_override && cdns->dr_mode == USB_DR_MODE_OTG) { - cdns3_hw_role_switch(cdns); - goto pm_put; - } +static int cdns3_register_extcon(struct cdns3 *cdns) +{ + struct extcon_dev *extcon; + struct device *dev = cdns->dev; + int ret; - if (cdns->role == role) - goto pm_put; + if (of_property_read_bool(dev->of_node, "extcon")) { + extcon = extcon_get_edev_by_phandle(dev, 0); + if (IS_ERR(extcon)) + return PTR_ERR(extcon); - if (cdns->dr_mode == USB_DR_MODE_HOST) { - switch (role) { - case USB_ROLE_NONE: - case USB_ROLE_HOST: - break; - default: - ret = -EPERM; - goto pm_put; + ret = devm_extcon_register_notifier(dev, extcon, + EXTCON_USB_HOST, &cdns->extcon_nb); + if (ret < 0) { + dev_err(dev, "register Host Connector failed\n"); + return ret; } - } - if (cdns->dr_mode == USB_DR_MODE_PERIPHERAL) { - switch (role) { - case USB_ROLE_NONE: - case USB_ROLE_DEVICE: - break; - default: - ret = -EPERM; - goto pm_put; + ret = devm_extcon_register_notifier(dev, extcon, + EXTCON_USB, &cdns->extcon_nb); + if (ret < 0) { + dev_err(dev, "register Device Connector failed\n"); + return ret; } + + cdns->extcon = extcon; + cdns->extcon_nb.notifier_call = cdns3_extcon_notifier; } - cdns3_role_stop(cdns); - ret = cdns3_role_start(cdns, role); - if (ret) { - dev_err(cdns->dev, "set role %d has failed\n", role); - ret = -EPERM; + return 0; +} + +static ssize_t cdns3_role_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct cdns3 *cdns = dev_get_drvdata(dev); + + if (cdns->role != CDNS3_ROLE_END) + return sprintf(buf, "%s\n", cdns3_role(cdns)->name); + else + return sprintf(buf, "%s\n", "none"); +} + +static ssize_t cdns3_role_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t n) +{ + struct cdns3 *cdns = dev_get_drvdata(dev); + enum cdns3_roles role; + int ret; + + if (!(cdns->roles[CDNS3_ROLE_HOST] && cdns->roles[CDNS3_ROLE_GADGET])) { + dev_warn(dev, "Current configuration is not dual-role, quit\n"); + return -EPERM; } -pm_put: - pm_runtime_put_sync(cdns->dev); - return ret; + for (role = CDNS3_ROLE_HOST; role <= CDNS3_ROLE_GADGET; role++) + if (!strncmp(buf, cdns->roles[role]->name, + strlen(cdns->roles[role]->name))) + break; + + if (role == cdns->role) + return -EINVAL; + + disable_irq(cdns->irq); + ret = cdns3_do_role_switch(cdns, role); + enable_irq(cdns->irq); + + return (ret == 0) ? n : ret; } +static DEVICE_ATTR(role, 0644, cdns3_role_show, cdns3_role_store); + +static struct attribute *cdns3_attrs[] = { + &dev_attr_role.attr, + NULL, +}; -static const struct usb_role_switch_desc cdns3_switch_desc = { - .set = cdns3_role_set, - .get = cdns3_role_get, - .allow_userspace_control = true, +static const struct attribute_group cdns3_attr_group = { + .attrs = cdns3_attrs, }; /** @@ -443,111 +606,113 @@ static int cdns3_probe(struct platform_device *pdev) void __iomem *regs; int ret; - ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); - if (ret) { - dev_err(dev, "error setting dma mask: %d\n", ret); - return -ENODEV; - } - cdns = devm_kzalloc(dev, sizeof(*cdns), GFP_KERNEL); if (!cdns) return -ENOMEM; cdns->dev = dev; - platform_set_drvdata(pdev, cdns); - res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "host"); + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); if (!res) { - dev_err(dev, "missing host IRQ\n"); + dev_err(dev, "missing IRQ\n"); return -ENODEV; } + cdns->irq = res->start; - cdns->xhci_res[0] = *res; - - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "xhci"); - if (!res) { - dev_err(dev, "couldn't get xhci resource\n"); - return -ENXIO; - } - - cdns->xhci_res[1] = *res; - - cdns->dev_irq = platform_get_irq_byname(pdev, "peripheral"); - if (cdns->dev_irq == -EPROBE_DEFER) - return cdns->dev_irq; + /* + * Request memory region + * region-0: nxp wrap registers + * region-1: xHCI + * region-2: Peripheral + * region-3: PHY registers + * region-4: OTG registers + */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + regs = devm_ioremap_resource(dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + cdns->none_core_regs = regs; - if (cdns->dev_irq < 0) - dev_err(dev, "couldn't get peripheral irq\n"); + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + regs = devm_ioremap_resource(dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + cdns->xhci_regs = regs; + cdns->xhci_res = res; - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dev"); + res = platform_get_resource(pdev, IORESOURCE_MEM, 2); regs = devm_ioremap_resource(dev, res); if (IS_ERR(regs)) return PTR_ERR(regs); cdns->dev_regs = regs; - cdns->otg_irq = platform_get_irq_byname(pdev, "otg"); - if (cdns->otg_irq == -EPROBE_DEFER) - return cdns->otg_irq; - - if (cdns->otg_irq < 0) { - dev_err(dev, "couldn't get otg irq\n"); - return cdns->otg_irq; - } - - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "otg"); - if (!res) { - dev_err(dev, "couldn't get otg resource\n"); - return -ENXIO; - } + res = platform_get_resource(pdev, IORESOURCE_MEM, 3); + regs = devm_ioremap_resource(dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + cdns->phy_regs = regs; - cdns->otg_res = *res; + res = platform_get_resource(pdev, IORESOURCE_MEM, 4); + regs = devm_ioremap_resource(dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + cdns->otg_regs = regs; mutex_init(&cdns->mutex); + ret = cdns3_get_clks(dev); + if (ret) + return ret; - cdns->usb2_phy = devm_phy_optional_get(dev, "cdns3,usb2-phy"); - if (IS_ERR(cdns->usb2_phy)) - return PTR_ERR(cdns->usb2_phy); - - ret = phy_init(cdns->usb2_phy); + ret = cdns3_prepare_enable_clks(dev); if (ret) return ret; - cdns->usb3_phy = devm_phy_optional_get(dev, "cdns3,usb3-phy"); - if (IS_ERR(cdns->usb3_phy)) - return PTR_ERR(cdns->usb3_phy); + cdns->usbphy = devm_usb_get_phy_by_phandle(dev, "cdns3,usbphy", 0); + if (IS_ERR(cdns->usbphy)) { + ret = PTR_ERR(cdns->usbphy); + if (ret == -ENODEV) + ret = -EINVAL; + goto err1; + } - ret = phy_init(cdns->usb3_phy); + ret = usb_phy_init(cdns->usbphy); if (ret) goto err1; - ret = phy_power_on(cdns->usb2_phy); + ret = cdns3_core_init_role(cdns); if (ret) goto err2; - ret = phy_power_on(cdns->usb3_phy); - if (ret) - goto err3; + if (cdns->roles[CDNS3_ROLE_GADGET]) { + INIT_WORK(&cdns->role_switch_wq, cdns3_role_switch); + ret = cdns3_register_extcon(cdns); + if (ret) + goto err3; + } - cdns->role_sw = usb_role_switch_register(dev, &cdns3_switch_desc); - if (IS_ERR(cdns->role_sw)) { - ret = PTR_ERR(cdns->role_sw); - dev_warn(dev, "Unable to register Role Switch\n"); - goto err4; + cdns->role = cdns3_get_role(cdns); + dev_dbg(dev, "the init role is %d\n", cdns->role); + cdns_set_role(cdns, cdns->role); + ret = cdns3_role_start(cdns, cdns->role); + if (ret) { + dev_err(dev, "can't start %s role\n", + cdns3_role(cdns)->name); + goto err3; } - ret = cdns3_drd_init(cdns); + ret = devm_request_threaded_irq(dev, cdns->irq, cdns3_irq, + cdns3_thread_irq, IRQF_SHARED, dev_name(dev), cdns); if (ret) - goto err5; + goto err4; - ret = cdns3_core_init_role(cdns); + ret = sysfs_create_group(&dev->kobj, &cdns3_attr_group); if (ret) - goto err5; + goto err4; device_set_wakeup_capable(dev, true); pm_runtime_set_active(dev); pm_runtime_enable(dev); - /* * The controller needs less time between bus and controller suspend, * and we also needs a small delay to avoid frequently entering low @@ -559,24 +724,20 @@ static int cdns3_probe(struct platform_device *pdev) dev_dbg(dev, "Cadence USB3 core: probe succeed\n"); return 0; -err5: - cdns3_drd_exit(cdns); - usb_role_switch_unregister(cdns->role_sw); -err4: - phy_power_off(cdns->usb3_phy); +err4: + cdns3_role_stop(cdns); err3: - phy_power_off(cdns->usb2_phy); + cdns3_remove_roles(cdns); err2: - phy_exit(cdns->usb3_phy); + usb_phy_shutdown(cdns->usbphy); err1: - phy_exit(cdns->usb2_phy); - + cdns3_disable_unprepare_clks(dev); return ret; } /** - * cdns3_remove - unbind drd driver and clean up + * cdns3_remove - unbind our drd driver and clean up * @pdev: Pointer to Linux platform device * * Returns 0 on success otherwise negative errno @@ -584,75 +745,363 @@ err1: static int cdns3_remove(struct platform_device *pdev) { struct cdns3 *cdns = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + + pm_runtime_get_sync(dev); + pm_runtime_disable(dev); + pm_runtime_put_noidle(dev); + sysfs_remove_group(&dev->kobj, &cdns3_attr_group); + cdns3_remove_roles(cdns); + usb_phy_shutdown(cdns->usbphy); + cdns3_disable_unprepare_clks(dev); - pm_runtime_get_sync(&pdev->dev); - pm_runtime_disable(&pdev->dev); - pm_runtime_put_noidle(&pdev->dev); - cdns3_exit_roles(cdns); - usb_role_switch_unregister(cdns->role_sw); - phy_power_off(cdns->usb2_phy); - phy_power_off(cdns->usb3_phy); - phy_exit(cdns->usb2_phy); - phy_exit(cdns->usb3_phy); return 0; } -#ifdef CONFIG_PM_SLEEP +#ifdef CONFIG_OF +static const struct of_device_id of_cdns3_match[] = { + { .compatible = "Cadence,usb3" }, + { }, +}; +MODULE_DEVICE_TABLE(of, of_cdns3_match); +#endif + +#ifdef CONFIG_PM +static inline bool controller_power_is_lost(struct cdns3 *cdns) +{ + u32 value; + + value = readl(cdns->none_core_regs + USB3_CORE_CTRL1); + if ((value & SW_RESET_MASK) == ALL_SW_RESET) + return true; + else + return false; +} + +static void cdns3_set_wakeup(void *none_core_regs, bool enable) +{ + u32 value; + + if (enable) { + /* Enable wakeup and phy_refclk_req */ + value = readl(none_core_regs + USB3_INT_REG); + value |= OTG_WAKEUP_EN | DEVU3_WAEKUP_EN; + writel(value, none_core_regs + USB3_INT_REG); + } else { + /* disable wakeup and phy_refclk_req */ + value = readl(none_core_regs + USB3_INT_REG); + value &= ~(OTG_WAKEUP_EN | DEVU3_WAEKUP_EN); + writel(value, none_core_regs + USB3_INT_REG); + } +} + +static int cdns3_enter_suspend(struct cdns3 *cdns, bool suspend, bool wakeup) +{ + void __iomem *otg_regs = cdns->otg_regs; + void __iomem *xhci_regs = cdns->xhci_regs; + void __iomem *none_core_regs = cdns->none_core_regs; + u32 value; + int timeout_us = 100000; + int ret = 0; + + if (cdns->role == CDNS3_ROLE_GADGET) { + if (suspend) { + /* When at device mode, set controller at reset mode */ + value = readl(cdns->none_core_regs + USB3_CORE_CTRL1); + value |= ALL_SW_RESET; + writel(value, cdns->none_core_regs + USB3_CORE_CTRL1); + } + return 0; + } else if (cdns->role == CDNS3_ROLE_END) { + return 0; + } + + if (suspend) { + if (cdns3_role(cdns)->suspend) + ret = cdns3_role(cdns)->suspend(cdns, wakeup); + + if (ret) + return ret; + + /* SW request low power when all usb ports allow to it ??? */ + value = readl(xhci_regs + XECP_PM_PMCSR); + value &= ~PS_MASK; + value |= PS_D1; + writel(value, xhci_regs + XECP_PM_PMCSR); + + /* mdctrl_clk_sel */ + value = readl(none_core_regs + USB3_CORE_CTRL1); + value |= MDCTRL_CLK_SEL; + writel(value, none_core_regs + USB3_CORE_CTRL1); + + /* wait for mdctrl_clk_status */ + value = readl(none_core_regs + USB3_CORE_STATUS); + while (!(value & MDCTRL_CLK_STATUS) && timeout_us-- > 0) { + value = readl(none_core_regs + USB3_CORE_STATUS); + udelay(1); + } + + if (timeout_us <= 0) + dev_err(cdns->dev, "wait mdctrl_clk_status timeout\n"); + + dev_dbg(cdns->dev, "mdctrl_clk_status is set\n"); + + /* wait lpm_clk_req to be 0 */ + value = readl(none_core_regs + USB3_INT_REG); + timeout_us = 100000; + while ((value & LPM_CLK_REQ) && timeout_us-- > 0) { + value = readl(none_core_regs + USB3_INT_REG); + udelay(1); + } + + if (timeout_us <= 0) + dev_err(cdns->dev, "wait lpm_clk_req timeout\n"); + + dev_dbg(cdns->dev, "lpm_clk_req cleared\n"); + + /* wait phy_refclk_req to be 0 */ + value = readl(none_core_regs + USB3_SSPHY_STATUS); + timeout_us = 100000; + while ((value & PHY_REFCLK_REQ) && timeout_us-- > 0) { + value = readl(none_core_regs + USB3_SSPHY_STATUS); + udelay(1); + } + if (timeout_us <= 0) + dev_err(cdns->dev, "wait phy_refclk_req timeout\n"); + + dev_dbg(cdns->dev, "phy_refclk_req cleared\n"); + cdns3_set_wakeup(none_core_regs, true); + } else { + value = readl(none_core_regs + USB3_INT_REG); + /* wait CLK_125_REQ to be 1 */ + value = readl(none_core_regs + USB3_INT_REG); + while (!(value & CLK_125_REQ) && timeout_us-- > 0) { + value = readl(none_core_regs + USB3_INT_REG); + udelay(1); + } + + cdns3_set_wakeup(none_core_regs, false); + + /* SW request D0 */ + value = readl(xhci_regs + XECP_PM_PMCSR); + value &= ~PS_MASK; + value |= PS_D0; + writel(value, xhci_regs + XECP_PM_PMCSR); + + /* clr CFG_RXDET_P3_EN */ + value = readl(xhci_regs + XECP_AUX_CTRL_REG1); + value &= ~CFG_RXDET_P3_EN; + writel(value, xhci_regs + XECP_AUX_CTRL_REG1); + + /* clear mdctrl_clk_sel */ + value = readl(none_core_regs + USB3_CORE_CTRL1); + value &= ~MDCTRL_CLK_SEL; + writel(value, none_core_regs + USB3_CORE_CTRL1); + + /* wait for mdctrl_clk_status is cleared */ + value = readl(none_core_regs + USB3_CORE_STATUS); + timeout_us = 100000; + while ((value & MDCTRL_CLK_STATUS) && timeout_us-- > 0) { + value = readl(none_core_regs + USB3_CORE_STATUS); + udelay(1); + } + + if (timeout_us <= 0) + dev_err(cdns->dev, "wait mdctrl_clk_status timeout\n"); + + dev_dbg(cdns->dev, "mdctrl_clk_status cleared\n"); + + /* Wait until OTG_NRDY is 0 */ + value = readl(otg_regs + OTGSTS); + timeout_us = 100000; + while ((value & OTG_NRDY) && timeout_us-- > 0) { + value = readl(otg_regs + OTGSTS); + udelay(1); + } + + if (timeout_us <= 0) + dev_err(cdns->dev, "wait OTG ready timeout\n"); + + value = readl(none_core_regs + USB3_CORE_STATUS); + timeout_us = 100000; + while (!(value & HOST_POWER_ON_READY) && timeout_us-- > 0) { + value = readl(none_core_regs + USB3_CORE_STATUS); + udelay(1); + } + + if (timeout_us <= 0) + dev_err(cdns->dev, "wait xhci_power_on_ready timeout\n"); + } + + return ret; +} + +static int cdns3_controller_suspend(struct cdns3 *cdns, bool wakeup) +{ + int ret = 0; + + disable_irq(cdns->irq); + ret = cdns3_enter_suspend(cdns, true, wakeup); + if (ret) { + enable_irq(cdns->irq); + return ret; + } + + usb_phy_set_suspend(cdns->usbphy, 1); + cdns->in_lpm = true; + enable_irq(cdns->irq); + return ret; +} + +#ifdef CONFIG_PM_SLEEP static int cdns3_suspend(struct device *dev) { struct cdns3 *cdns = dev_get_drvdata(dev); - unsigned long flags; + bool wakeup = device_may_wakeup(dev); + int ret; - if (cdns->role == USB_ROLE_HOST) - return 0; + dev_dbg(dev, "at %s\n", __func__); if (pm_runtime_status_suspended(dev)) pm_runtime_resume(dev); - if (cdns->roles[cdns->role]->suspend) { - spin_lock_irqsave(&cdns->gadget_dev->lock, flags); - cdns->roles[cdns->role]->suspend(cdns, false); - spin_unlock_irqrestore(&cdns->gadget_dev->lock, flags); - } + ret = cdns3_controller_suspend(cdns, wakeup); + if (ret) + return ret; - return 0; + cdns3_disable_unprepare_clks(dev); + if (wakeup) + enable_irq_wake(cdns->irq); + + return ret; } static int cdns3_resume(struct device *dev) { struct cdns3 *cdns = dev_get_drvdata(dev); - unsigned long flags; + int ret; + bool power_lost; - if (cdns->role == USB_ROLE_HOST) + dev_dbg(dev, "at %s\n", __func__); + if (!cdns->in_lpm) { + WARN_ON(1); return 0; + } + + ret = cdns3_prepare_enable_clks(dev); + if (ret) + return ret; - if (cdns->roles[cdns->role]->resume) { - spin_lock_irqsave(&cdns->gadget_dev->lock, flags); - cdns->roles[cdns->role]->resume(cdns, false); - spin_unlock_irqrestore(&cdns->gadget_dev->lock, flags); + usb_phy_set_suspend(cdns->usbphy, 0); + cdns->in_lpm = false; + if (device_may_wakeup(dev)) + disable_irq_wake(cdns->irq); + power_lost = controller_power_is_lost(cdns); + if (power_lost) { + dev_dbg(dev, "power is lost, the role is %d\n", cdns->role); + cdns_set_role(cdns, cdns->role); + if ((cdns->role != CDNS3_ROLE_END) + && cdns3_role(cdns)->resume) { + /* Force B Session Valid as 1 */ + writel(0x0060, cdns->phy_regs + 0x380a4); + cdns3_role(cdns)->resume(cdns, true); + } + } else { + /* At resume path, never return error */ + cdns3_enter_suspend(cdns, false, false); + if (cdns->wakeup_int) { + cdns->wakeup_int = false; + pm_runtime_mark_last_busy(cdns->dev); + pm_runtime_put_autosuspend(cdns->dev); + enable_irq(cdns->irq); + } + + if ((cdns->role != CDNS3_ROLE_END) && cdns3_role(cdns)->resume) + cdns3_role(cdns)->resume(cdns, false); } pm_runtime_disable(dev); pm_runtime_set_active(dev); pm_runtime_enable(dev); + if (cdns->role == CDNS3_ROLE_HOST) { + /* + * There is no PM APIs for cdns->host_dev, we can only do + * it at its parent PM APIs + */ + pm_runtime_disable(cdns->host_dev); + pm_runtime_set_active(cdns->host_dev); + pm_runtime_enable(cdns->host_dev); + } + + dev_dbg(dev, "at end of %s\n", __func__); + return 0; } -#endif +#endif /* CONFIG_PM_SLEEP */ +static int cdns3_runtime_suspend(struct device *dev) +{ + struct cdns3 *cdns = dev_get_drvdata(dev); + int ret; + + dev_dbg(dev, "at the begin of %s\n", __func__); + if (cdns->in_lpm) { + WARN_ON(1); + return 0; + } + + ret = cdns3_controller_suspend(cdns, true); + if (ret) + return ret; + cdns3_disable_unprepare_clks(dev); + + dev_dbg(dev, "at the end of %s\n", __func__); + + return ret; +} + +static int cdns3_runtime_resume(struct device *dev) +{ + struct cdns3 *cdns = dev_get_drvdata(dev); + int ret; + + if (!cdns->in_lpm) { + WARN_ON(1); + return 0; + } + + ret = cdns3_prepare_enable_clks(dev); + if (ret) + return ret; + + usb_phy_set_suspend(cdns->usbphy, 0); + /* At resume path, never return error */ + cdns3_enter_suspend(cdns, false, false); + cdns->in_lpm = 0; + + if (cdns->role == CDNS3_ROLE_HOST) { + if (cdns->wakeup_int) { + cdns->wakeup_int = false; + pm_runtime_mark_last_busy(cdns->dev); + pm_runtime_put_autosuspend(cdns->dev); + enable_irq(cdns->irq); + } + + if ((cdns->role != CDNS3_ROLE_END) && cdns3_role(cdns)->resume) + cdns3_role(cdns)->resume(cdns, false); + } + + dev_dbg(dev, "at %s\n", __func__); + return 0; +} +#endif /* CONFIG_PM */ static const struct dev_pm_ops cdns3_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(cdns3_suspend, cdns3_resume) + SET_RUNTIME_PM_OPS(cdns3_runtime_suspend, cdns3_runtime_resume, NULL) }; -#ifdef CONFIG_OF -static const struct of_device_id of_cdns3_match[] = { - { .compatible = "cdns,usb3" }, - { }, -}; -MODULE_DEVICE_TABLE(of, of_cdns3_match); -#endif - static struct platform_driver cdns3_driver = { .probe = cdns3_probe, .remove = cdns3_remove, @@ -663,9 +1112,20 @@ static struct platform_driver cdns3_driver = { }, }; -module_platform_driver(cdns3_driver); +static int __init cdns3_driver_platform_register(void) +{ + cdns3_host_driver_init(); + return platform_driver_register(&cdns3_driver); +} +module_init(cdns3_driver_platform_register); + +static void __exit cdns3_driver_platform_unregister(void) +{ + platform_driver_unregister(&cdns3_driver); +} +module_exit(cdns3_driver_platform_unregister); -MODULE_ALIAS("platform:cdns3"); -MODULE_AUTHOR("Pawel Laszczak <pawell@cadence.com>"); +MODULE_ALIAS("platform:cdns-usb3"); +MODULE_AUTHOR("Peter Chen <peter.chen@nxp.com>"); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("Cadence USB3 DRD Controller Driver"); diff --git a/drivers/usb/cdns3/core.h b/drivers/usb/cdns3/core.h index 969eb94de204..a6bd1cfb4c7d 100644 --- a/drivers/usb/cdns3/core.h +++ b/drivers/usb/cdns3/core.h @@ -1,20 +1,32 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * Cadence USBSS DRD Header File. +/** + * core.h - Cadence USB3 DRD Controller Core header file * - * Copyright (C) 2017-2018 NXP - * Copyright (C) 2018-2019 Cadence. + * Copyright 2017-2019 NXP * * Authors: Peter Chen <peter.chen@nxp.com> - * Pawel Laszczak <pawell@cadence.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 of + * the License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -#include <linux/usb/otg.h> -#include <linux/usb/role.h> -#ifndef __LINUX_CDNS3_CORE_H -#define __LINUX_CDNS3_CORE_H +#ifndef __DRIVERS_USB_CDNS3_CORE_H +#define __DRIVERS_USB_CDNS3_CORE_H struct cdns3; +enum cdns3_roles { + CDNS3_ROLE_HOST = 0, + CDNS3_ROLE_GADGET, + CDNS3_ROLE_END, +}; /** * struct cdns3_role_driver - host/gadget role driver @@ -23,76 +35,100 @@ struct cdns3; * @suspend: suspend callback for this role * @resume: resume callback for this role * @irq: irq handler for this role + * @thread_irq: thread irq handler for this role * @name: role name string (host/gadget) - * @state: current state */ struct cdns3_role_driver { - int (*start)(struct cdns3 *cdns); - void (*stop)(struct cdns3 *cdns); - int (*suspend)(struct cdns3 *cdns, bool do_wakeup); - int (*resume)(struct cdns3 *cdns, bool hibernated); + int (*start)(struct cdns3 *); + void (*stop)(struct cdns3 *); + int (*suspend)(struct cdns3 *, bool do_wakeup); + int (*resume)(struct cdns3 *, bool hibernated); + irqreturn_t (*irq)(struct cdns3 *); + irqreturn_t (*thread_irq)(struct cdns3 *); const char *name; -#define CDNS3_ROLE_STATE_INACTIVE 0 -#define CDNS3_ROLE_STATE_ACTIVE 1 - int state; }; -#define CDNS3_XHCI_RESOURCES_NUM 2 +#define CDNS3_NUM_OF_CLKS 5 /** * struct cdns3 - Representation of Cadence USB3 DRD controller. * @dev: pointer to Cadence device struct * @xhci_regs: pointer to base of xhci registers * @xhci_res: the resource for xhci * @dev_regs: pointer to base of dev registers - * @otg_res: the resource for otg - * @otg_v0_regs: pointer to base of v0 otg registers - * @otg_v1_regs: pointer to base of v1 otg registers + * @none_core_regs: pointer to base of nxp wrapper registers + * @phy_regs: pointer to base of phy registers * @otg_regs: pointer to base of otg registers - * @otg_irq: irq number for otg controller - * @dev_irq: irq number for device controller + * @irq: irq number for controller * @roles: array of supported roles for this controller * @role: current role * @host_dev: the child host device pointer for cdns3 core * @gadget_dev: the child gadget device pointer for cdns3 core - * @usb2_phy: pointer to USB2 PHY - * @usb3_phy: pointer to USB3 PHY + * @usbphy: usbphy for this controller + * @cdns3_clks: Clock pointer array for cdns3 core + * @extcon: Type-C extern connector + * @extcon_nb: notifier block for Type-C extern connector + * @role_switch_wq: work queue item for role switch + * @in_lpm: the controller in low power mode + * @wakeup_int: the wakeup interrupt * @mutex: the mutex for concurrent code at driver - * @dr_mode: supported mode of operation it can be only Host, only Device - * or OTG mode that allow to switch between Device and Host mode. - * This field based on firmware setting, kernel configuration - * and hardware configuration. - * @role_sw: pointer to role switch object. - * @role_override: set 1 if role rely on SW. */ struct cdns3 { - struct device *dev; - void __iomem *xhci_regs; - struct resource xhci_res[CDNS3_XHCI_RESOURCES_NUM]; - struct cdns3_usb_regs __iomem *dev_regs; + struct device *dev; + void __iomem *xhci_regs; + struct resource *xhci_res; + struct cdns3_usb_regs __iomem *dev_regs; + void __iomem *none_core_regs; + void __iomem *phy_regs; + void __iomem *otg_regs; + int irq; + struct cdns3_role_driver *roles[CDNS3_ROLE_END]; + enum cdns3_roles role; + struct device *host_dev; + struct cdns3_device *gadget_dev; + struct usb_phy *usbphy; + struct clk *cdns3_clks[CDNS3_NUM_OF_CLKS]; + struct extcon_dev *extcon; + struct notifier_block extcon_nb; + struct work_struct role_switch_wq; + bool in_lpm; + bool wakeup_int; + struct mutex mutex; +}; - struct resource otg_res; - struct cdns3_otg_legacy_regs *otg_v0_regs; - struct cdns3_otg_regs *otg_v1_regs; - struct cdns3_otg_common_regs *otg_regs; -#define CDNS3_CONTROLLER_V0 0 -#define CDNS3_CONTROLLER_V1 1 - u32 version; +static inline struct cdns3_role_driver *cdns3_role(struct cdns3 *cdns) +{ + WARN_ON(cdns->role >= CDNS3_ROLE_END || !cdns->roles[cdns->role]); + return cdns->roles[cdns->role]; +} - int otg_irq; - int dev_irq; - struct cdns3_role_driver *roles[USB_ROLE_DEVICE + 1]; - enum usb_role role; - struct platform_device *host_dev; - struct cdns3_device *gadget_dev; - struct phy *usb2_phy; - struct phy *usb3_phy; - /* mutext used in workqueue*/ - struct mutex mutex; - enum usb_dr_mode dr_mode; - struct usb_role_switch *role_sw; - int role_override; -}; +static inline int cdns3_role_start(struct cdns3 *cdns, enum cdns3_roles role) +{ + int ret; + if (role >= CDNS3_ROLE_END) + return 0; + + if (!cdns->roles[role]) + return -ENXIO; + + mutex_lock(&cdns->mutex); + cdns->role = role; + ret = cdns->roles[role]->start(cdns); + mutex_unlock(&cdns->mutex); + return ret; +} + +static inline void cdns3_role_stop(struct cdns3 *cdns) +{ + enum cdns3_roles role = cdns->role; + + if (role == CDNS3_ROLE_END) + return; -int cdns3_hw_role_switch(struct cdns3 *cdns); + mutex_lock(&cdns->mutex); + cdns->roles[role]->stop(cdns); + cdns->role = CDNS3_ROLE_END; + mutex_unlock(&cdns->mutex); +} +int cdns3_handshake(void __iomem *ptr, u32 mask, u32 done, int usec); -#endif /* __LINUX_CDNS3_CORE_H */ +#endif /* __DRIVERS_USB_CDNS3_CORE_H */ diff --git a/drivers/usb/cdns3/debug.h b/drivers/usb/cdns3/debug.h index 2c9afbfe988b..8193676dc9f3 100644 --- a/drivers/usb/cdns3/debug.h +++ b/drivers/usb/cdns3/debug.h @@ -10,8 +10,6 @@ #ifndef __LINUX_CDNS3_DEBUG #define __LINUX_CDNS3_DEBUG -#include "core.h" - static inline char *cdns3_decode_usb_irq(char *str, enum usb_device_speed speed, u32 usb_ists) @@ -28,12 +26,7 @@ static inline char *cdns3_decode_usb_irq(char *str, ret += sprintf(str + ret, "Disconnection "); if (usb_ists & USB_ISTS_L2ENTI) ret += sprintf(str + ret, "suspended "); - if (usb_ists & USB_ISTS_L1ENTI) - ret += sprintf(str + ret, "L1 enter "); - if (usb_ists & USB_ISTS_L1EXTI) - ret += sprintf(str + ret, "L1 exit "); - if (usb_ists & USB_ISTS_L2ENTI) - ret += sprintf(str + ret, "L2 enter "); + if (usb_ists & USB_ISTS_L2EXTI) ret += sprintf(str + ret, "L2 exit "); if (usb_ists & USB_ISTS_U3EXTI) @@ -158,4 +151,5 @@ static inline char *cdns3_dbg_ring(struct cdns3_endpoint *priv_ep, return str; } +void cdns3_dbg(struct cdns3_device *priv_dev, const char *fmt, ...); #endif /*__LINUX_CDNS3_DEBUG*/ diff --git a/drivers/usb/cdns3/dev-regs-macro.h b/drivers/usb/cdns3/dev-regs-macro.h new file mode 100644 index 000000000000..5b307623afec --- /dev/null +++ b/drivers/usb/cdns3/dev-regs-macro.h @@ -0,0 +1,894 @@ +/** + * dev-regs-macro.h - Cadence USB3 Device register definition + * + * Copyright (C) 2016 Cadence Design Systems - http://www.cadence.com + * Copyright 2017 NXP + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef __REG_USBSS_DEV_ADDR_MAP_MACRO_H__ +#define __REG_USBSS_DEV_ADDR_MAP_MACRO_H__ + + +/* macros for BlueprintGlobalNameSpace::USB_CONF */ +#ifndef __USB_CONF_MACRO__ +#define __USB_CONF_MACRO__ + +/* macros for field CFGRST */ +#define USB_CONF__CFGRST__MASK 0x00000001U +#define USB_CONF__CFGSET__MASK 0x00000002U +#define USB_CONF__USB3DIS__MASK 0x00000008U +#define USB_CONF__DEVEN__MASK 0x00004000U +#define USB_CONF__DEVDS__MASK 0x00008000U +#define USB_CONF__L1EN__MASK 0x00010000U +#define USB_CONF__L1DS__MASK 0x00020000U +#define USB_CONF__CLK2OFFDS__MASK 0x00080000U +#define USB_CONF__U1EN__MASK 0x01000000U +#define USB_CONF__U1DS__MASK 0x02000000U +#define USB_CONF__U2EN__MASK 0x04000000U +#define USB_CONF__U2DS__MASK 0x08000000U +#endif /* __USB_CONF_MACRO__ */ + + +/* macros for usbss_dev_register_block.usb_conf */ +#ifndef __USB_STS_MACRO__ +#define __USB_STS_MACRO__ + +/* macros for field CFGSTS */ +#define USB_STS__CFGSTS__MASK 0x00000001U +#define USB_STS__USBSPEED__READ(src) (((uint32_t)(src) & 0x00000070U) >> 4) + +/* macros for field ENDIAN_MIRROR */ +#define USB_STS__LPMST__READ(src) (((uint32_t)(src) & 0x000c0000U) >> 18) + +/* macros for field USB2CONS */ +#define USB_STS__U1ENS__MASK 0x01000000U +#define USB_STS__U2ENS__MASK 0x02000000U +#define USB_STS__LST__READ(src) (((uint32_t)(src) & 0x3c000000U) >> 26) + +/* macros for field DMAOFF */ +#endif /* __USB_STS_MACRO__ */ + + +/* macros for usbss_dev_register_block.usb_sts */ +#ifndef __USB_CMD_MACRO__ +#define __USB_CMD_MACRO__ + +/* macros for field SET_ADDR */ +#define USB_CMD__SET_ADDR__MASK 0x00000001U +#define USB_CMD__STMODE 0x00000200U +#define USB_CMD__TMODE_SEL(x) (x << 10) +#define USB_CMD__FADDR__WRITE(src) (((uint32_t)(src) << 1) & 0x000000feU) +#endif /* __USB_CMD_MACRO__ */ + + +/* macros for usbss_dev_register_block.usb_cmd */ +#ifndef __USB_ITPN_MACRO__ +#define __USB_ITPN_MACRO__ + +/* macros for field ITPN */ +#endif /* __USB_ITPN_MACRO__ */ + + +/* macros for usbss_dev_register_block.usb_iptn */ +#ifndef __USB_LPM_MACRO__ +#define __USB_LPM_MACRO__ + +/* macros for field HIRD */ +#endif /* __USB_LPM_MACRO__ */ + + +/* macros for usbss_dev_register_block.usb_lpm */ +#ifndef __USB_IEN_MACRO__ +#define __USB_IEN_MACRO__ + +/* macros for field CONIEN */ +#define USB_IEN__CONIEN__MASK 0x00000001U +#define USB_IEN__DISIEN__MASK 0x00000002U +#define USB_IEN__UWRESIEN__MASK 0x00000004U +#define USB_IEN__UHRESIEN__MASK 0x00000008U +#define USB_IEN__U3EXTIEN__MASK 0x00000020U +#define USB_IEN__CON2IEN__MASK 0x00010000U +#define USB_IEN__U2RESIEN__MASK 0x00040000U +#define USB_IEN__L2ENTIEN__MASK 0x00100000U +#define USB_IEN__L2EXTIEN__MASK 0x00200000U +#endif /* __USB_IEN_MACRO__ */ + + +/* macros for usbss_dev_register_block.usb_ien */ +#ifndef __USB_ISTS_MACRO__ +#define __USB_ISTS_MACRO__ + +/* macros for field CONI */ +#define USB_ISTS__CONI__SHIFT 0 +#define USB_ISTS__DISI__SHIFT 1 +#define USB_ISTS__UWRESI__SHIFT 2 +#define USB_ISTS__UHRESI__SHIFT 3 +#define USB_ISTS__U3EXTI__SHIFT 5 +#define USB_ISTS__CON2I__SHIFT 16 +#define USB_ISTS__DIS2I__SHIFT 17 +#define USB_ISTS__DIS2I__MASK 0x00020000U +#define USB_ISTS__U2RESI__SHIFT 18 +#define USB_ISTS__L2ENTI__SHIFT 20 +#define USB_ISTS__L2EXTI__SHIFT 21 +#endif /* __USB_ISTS_MACRO__ */ + + +/* macros for usbss_dev_register_block.usb_ists */ +#ifndef __EP_SEL_MACRO__ +#define __EP_SEL_MACRO__ + +/* macros for field EPNO */ +#endif /* __EP_SEL_MACRO__ */ + + +/* macros for usbss_dev_register_block.ep_sel */ +#ifndef __EP_TRADDR_MACRO__ +#define __EP_TRADDR_MACRO__ + +/* macros for field TRADDR */ +#define EP_TRADDR__TRADDR__WRITE(src) ((uint32_t)(src) & 0xffffffffU) +#endif /* __EP_TRADDR_MACRO__ */ + + +/* macros for usbss_dev_register_block.ep_traddr */ +#ifndef __EP_CFG_MACRO__ +#define __EP_CFG_MACRO__ + +/* macros for field ENABLE */ +#define EP_CFG__ENABLE__MASK 0x00000001U +#define EP_CFG__EPTYPE__WRITE(src) (((uint32_t)(src) << 1) & 0x00000006U) +#define EP_CFG__MAXBURST__WRITE(src) (((uint32_t)(src) << 8) & 0x00000f00U) +#define EP_CFG__MAXPKTSIZE__WRITE(src) (((uint32_t)(src) << 16) & 0x07ff0000U) +#define EP_CFG__BUFFERING__WRITE(src) (((uint32_t)(src) << 27) & 0xf8000000U) +#endif /* __EP_CFG_MACRO__ */ + + +/* macros for usbss_dev_register_block.ep_cfg */ +#ifndef __EP_CMD_MACRO__ +#define __EP_CMD_MACRO__ + +/* macros for field EPRST */ +#define EP_CMD__EPRST__MASK 0x00000001U +#define EP_CMD__SSTALL__MASK 0x00000002U +#define EP_CMD__CSTALL__MASK 0x00000004U +#define EP_CMD__ERDY__MASK 0x00000008U +#define EP_CMD__REQ_CMPL__MASK 0x00000020U +#define EP_CMD__DRDY__MASK 0x00000040U +#define EP_CMD__DFLUSH__MASK 0x00000080U +#endif /* __EP_CMD_MACRO__ */ + + +/* macros for usbss_dev_register_block.ep_cmd */ +#ifndef __EP_STS_MACRO__ +#define __EP_STS_MACRO__ + +/* macros for field SETUP */ +#define EP_STS__SETUP__MASK 0x00000001U +#define EP_STS__STALL__MASK 0x00000002U +#define EP_STS__IOC__MASK 0x00000004U +#define EP_STS__ISP__MASK 0x00000008U +#define EP_STS__DESCMIS__MASK 0x00000010U +#define EP_STS__TRBERR__MASK 0x00000080U +#define EP_STS__NRDY__MASK 0x00000100U +#define EP_STS__DBUSY__MASK 0x00000200U +#define EP_STS__OUTSMM__MASK 0x00004000U +#define EP_STS__ISOERR__MASK 0x00008000U +#endif /* __EP_STS_MACRO__ */ + + +/* macros for usbss_dev_register_block.ep_sts */ +#ifndef __EP_STS_SID_MACRO__ +#define __EP_STS_SID_MACRO__ + +/* macros for field SID */ +#endif /* __EP_STS_SID_MACRO__ */ + + +/* macros for usbss_dev_register_block.ep_sts_sid */ +#ifndef __EP_STS_EN_MACRO__ +#define __EP_STS_EN_MACRO__ + +/* macros for field SETUPEN */ +#define EP_STS_EN__SETUPEN__MASK 0x00000001U +#define EP_STS_EN__DESCMISEN__MASK 0x00000010U +#define EP_STS_EN__TRBERREN__MASK 0x00000080U +#endif /* __EP_STS_EN_MACRO__ */ + + +/* macros for usbss_dev_register_block.ep_sts_en */ +#ifndef __DRBL_MACRO__ +#define __DRBL_MACRO__ + +/* macros for field DRBL0O */ +#endif /* __DRBL_MACRO__ */ + + +/* macros for usbss_dev_register_block.drbl */ +#ifndef __EP_IEN_MACRO__ +#define __EP_IEN_MACRO__ + +/* macros for field EOUTEN0 */ +#define EP_IEN__EOUTEN0__MASK 0x00000001U +#define EP_IEN__EINEN0__MASK 0x00010000U +#endif /* __EP_IEN_MACRO__ */ + + +/* macros for usbss_dev_register_block.ep_ien */ +#ifndef __EP_ISTS_MACRO__ +#define __EP_ISTS_MACRO__ + +/* macros for field EOUT0 */ +#define EP_ISTS__EOUT0__MASK 0x00000001U +#define EP_ISTS__EIN0__MASK 0x00010000U +#endif /* __EP_ISTS_MACRO__ */ + + +/* macros for usbss_dev_register_block.ep_ists */ +#ifndef __USB_PWR_MACRO__ +#define __USB_PWR_MACRO__ + +/* macros for field PSO_EN */ +#endif /* __USB_PWR_MACRO__ */ + + +/* macros for usbss_dev_register_block.usb_pwr */ +#ifndef __USB_CONF2_MACRO__ +#define __USB_CONF2_MACRO__ + +/* macros for field AHB_RETRY_EN */ +#endif /* __USB_CONF2_MACRO__ */ + + +/* macros for usbss_dev_register_block.usb_conf2 */ +#ifndef __USB_CAP1_MACRO__ +#define __USB_CAP1_MACRO__ + +/* macros for field SFR_TYPE */ +#endif /* __USB_CAP1_MACRO__ */ + + +/* macros for usbss_dev_register_block.usb_cap1 */ +#ifndef __USB_CAP2_MACRO__ +#define __USB_CAP2_MACRO__ + +/* macros for field ACTUAL_MEM_SIZE */ +#endif /* __USB_CAP2_MACRO__ */ + + +/* macros for usbss_dev_register_block.usb_cap2 */ +#ifndef __USB_CAP3_MACRO__ +#define __USB_CAP3_MACRO__ + +/* macros for field EPOUT_N */ +#endif /* __USB_CAP3_MACRO__ */ + + +/* macros for usbss_dev_register_block.usb_cap3 */ +#ifndef __USB_CAP4_MACRO__ +#define __USB_CAP4_MACRO__ + +/* macros for field EPOUTI_N */ +#endif /* __USB_CAP4_MACRO__ */ + + +/* macros for usbss_dev_register_block.usb_cap4 */ +#ifndef __USB_CAP5_MACRO__ +#define __USB_CAP5_MACRO__ + +/* macros for field EPOUTI_N */ +#endif /* __USB_CAP5_MACRO__ */ + + +/* macros for usbss_dev_register_block.usb_cap5 */ +#ifndef __USB_CAP6_MACRO__ +#define __USB_CAP6_MACRO__ + +/* macros for field VERSION */ +#endif /* __USB_CAP6_MACRO__ */ + + +/* macros for usbss_dev_register_block.usb_cap6 */ +#ifndef __USB_CPKT1_MACRO__ +#define __USB_CPKT1_MACRO__ + +/* macros for field CPKT1 */ +#endif /* __USB_CPKT1_MACRO__ */ + + +/* macros for usbss_dev_register_block.usb_cpkt1 */ +#ifndef __USB_CPKT2_MACRO__ +#define __USB_CPKT2_MACRO__ + +/* macros for field CPKT2 */ +#endif /* __USB_CPKT2_MACRO__ */ + + +/* macros for usbss_dev_register_block.usb_cpkt2 */ +#ifndef __USB_CPKT3_MACRO__ +#define __USB_CPKT3_MACRO__ + +/* macros for field CPKT3 */ +#endif /* __USB_CPKT3_MACRO__ */ + + +/* macros for usbss_dev_register_block.usb_cpkt3 */ +#ifndef __CFG_REG1_MACRO__ +#define __CFG_REG1_MACRO__ + +/* macros for field DEBOUNCER_CNT */ +#endif /* __CFG_REG1_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg1 */ +#ifndef __DBG_LINK1_MACRO__ +#define __DBG_LINK1_MACRO__ + +/* macros for field LFPS_MIN_DET_U1_EXIT */ +#define DBG_LINK1__LFPS_MIN_GEN_U1_EXIT__WRITE(src) \ + (((uint32_t)(src)\ + << 8) & 0x0000ff00U) +#define DBG_LINK1__LFPS_MIN_GEN_U1_EXIT_SET__MASK 0x02000000U +#endif /* __DBG_LINK1_MACRO__ */ + + +/* macros for usbss_dev_register_block.dbg_link1 */ +#ifndef __DBG_LINK2_MACRO__ +#define __DBG_LINK2_MACRO__ + +/* macros for field RXEQTR_AVAL */ +#endif /* __DBG_LINK2_MACRO__ */ + + +/* macros for usbss_dev_register_block.dbg_link2 */ +#ifndef __CFG_REG4_MACRO__ +#define __CFG_REG4_MACRO__ + +/* macros for field RXDETECT_QUIET_TIMEOUT */ +#endif /* __CFG_REG4_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg4 */ +#ifndef __CFG_REG5_MACRO__ +#define __CFG_REG5_MACRO__ + +/* macros for field U3_HDSK_FAIL_TIMEOUT */ +#endif /* __CFG_REG5_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg5 */ +#ifndef __CFG_REG6_MACRO__ +#define __CFG_REG6_MACRO__ + +/* macros for field SSINACTIVE_QUIET_TIMEOUT */ +#endif /* __CFG_REG6_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg6 */ +#ifndef __CFG_REG7_MACRO__ +#define __CFG_REG7_MACRO__ + +/* macros for field POLLING_LFPS_TIMEOUT */ +#endif /* __CFG_REG7_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg7 */ +#ifndef __CFG_REG8_MACRO__ +#define __CFG_REG8_MACRO__ + +/* macros for field POLLING_ACTIVE_TIMEOUT */ +#endif /* __CFG_REG8_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg8 */ +#ifndef __CFG_REG9_MACRO__ +#define __CFG_REG9_MACRO__ + +/* macros for field POLLING_IDLE_TIMEOUT */ +#endif /* __CFG_REG9_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg9 */ +#ifndef __CFG_REG10_MACRO__ +#define __CFG_REG10_MACRO__ + +/* macros for field POLLING_CONF_TIMEOUT */ +#endif /* __CFG_REG10_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg10 */ +#ifndef __CFG_REG11_MACRO__ +#define __CFG_REG11_MACRO__ + +/* macros for field RECOVERY_ACTIVE_TIMEOUT */ +#endif /* __CFG_REG11_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg11 */ +#ifndef __CFG_REG12_MACRO__ +#define __CFG_REG12_MACRO__ + +/* macros for field RECOVERY_CONF_TIMEOUT */ +#endif /* __CFG_REG12_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg12 */ +#ifndef __CFG_REG13_MACRO__ +#define __CFG_REG13_MACRO__ + +/* macros for field RECOVERY_IDLE_TIMEOUT */ +#endif /* __CFG_REG13_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg13 */ +#ifndef __CFG_REG14_MACRO__ +#define __CFG_REG14_MACRO__ + +/* macros for field HOTRESET_ACTIVE_TIMEOUT */ +#endif /* __CFG_REG14_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg14 */ +#ifndef __CFG_REG15_MACRO__ +#define __CFG_REG15_MACRO__ + +/* macros for field HOTRESET_EXIT_TIMEOUT */ +#endif /* __CFG_REG15_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg15 */ +#ifndef __CFG_REG16_MACRO__ +#define __CFG_REG16_MACRO__ + +/* macros for field LFPS_PING_REPEAT */ +#endif /* __CFG_REG16_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg16 */ +#ifndef __CFG_REG17_MACRO__ +#define __CFG_REG17_MACRO__ + +/* macros for field PENDING_HP_TIMEOUT */ +#endif /* __CFG_REG17_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg17 */ +#ifndef __CFG_REG18_MACRO__ +#define __CFG_REG18_MACRO__ + +/* macros for field CREDIT_HP_TIMEOUT */ +#endif /* __CFG_REG18_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg18 */ +#ifndef __CFG_REG19_MACRO__ +#define __CFG_REG19_MACRO__ + +/* macros for field LUP_TIMEOUT */ +#endif /* __CFG_REG19_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg19 */ +#ifndef __CFG_REG20_MACRO__ +#define __CFG_REG20_MACRO__ + +/* macros for field LDN_TIMEOUT */ +#endif /* __CFG_REG20_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg20 */ +#ifndef __CFG_REG21_MACRO__ +#define __CFG_REG21_MACRO__ + +/* macros for field PM_LC_TIMEOUT */ +#endif /* __CFG_REG21_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg21 */ +#ifndef __CFG_REG22_MACRO__ +#define __CFG_REG22_MACRO__ + +/* macros for field PM_ENTRY_TIMEOUT */ +#endif /* __CFG_REG22_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg22 */ +#ifndef __CFG_REG23_MACRO__ +#define __CFG_REG23_MACRO__ + +/* macros for field UX_EXIT_TIMEOUT */ +#endif /* __CFG_REG23_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg23 */ +#ifndef __CFG_REG24_MACRO__ +#define __CFG_REG24_MACRO__ + +/* macros for field LFPS_DET_RESET_MIN */ +#endif /* __CFG_REG24_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg24 */ +#ifndef __CFG_REG25_MACRO__ +#define __CFG_REG25_MACRO__ + +/* macros for field LFPS_DET_RESET_MAX */ +#endif /* __CFG_REG25_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg25 */ +#ifndef __CFG_REG26_MACRO__ +#define __CFG_REG26_MACRO__ + +/* macros for field LFPS_DET_POLLING_MIN */ +#endif /* __CFG_REG26_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg26 */ +#ifndef __CFG_REG27_MACRO__ +#define __CFG_REG27_MACRO__ + +/* macros for field LFPS_DET_POLLING_MAX */ +#endif /* __CFG_REG27_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg27 */ +#ifndef __CFG_REG28_MACRO__ +#define __CFG_REG28_MACRO__ + +/* macros for field LFPS_DET_PING_MIN */ +#endif /* __CFG_REG28_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg28 */ +#ifndef __CFG_REG29_MACRO__ +#define __CFG_REG29_MACRO__ + +/* macros for field LFPS_DET_PING_MAX */ +#endif /* __CFG_REG29_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg29 */ +#ifndef __CFG_REG30_MACRO__ +#define __CFG_REG30_MACRO__ + +/* macros for field LFPS_DET_U1EXIT_MIN */ +#endif /* __CFG_REG30_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg30 */ +#ifndef __CFG_REG31_MACRO__ +#define __CFG_REG31_MACRO__ + +/* macros for field LFPS_DET_U1EXIT_MAX */ +#endif /* __CFG_REG31_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg31 */ +#ifndef __CFG_REG32_MACRO__ +#define __CFG_REG32_MACRO__ + +/* macros for field LFPS_DET_U2EXIT_MIN */ +#endif /* __CFG_REG32_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg32 */ +#ifndef __CFG_REG33_MACRO__ +#define __CFG_REG33_MACRO__ + +/* macros for field LFPS_DET_U2EXIT_MAX */ +#endif /* __CFG_REG33_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg33 */ +#ifndef __CFG_REG34_MACRO__ +#define __CFG_REG34_MACRO__ + +/* macros for field LFPS_DET_U3EXIT_MIN */ +#endif /* __CFG_REG34_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg34 */ +#ifndef __CFG_REG35_MACRO__ +#define __CFG_REG35_MACRO__ + +/* macros for field LFPS_DET_U3EXIT_MAX */ +#endif /* __CFG_REG35_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg35 */ +#ifndef __CFG_REG36_MACRO__ +#define __CFG_REG36_MACRO__ + +/* macros for field LFPS_GEN_PING */ +#endif /* __CFG_REG36_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg36 */ +#ifndef __CFG_REG37_MACRO__ +#define __CFG_REG37_MACRO__ + +/* macros for field LFPS_GEN_POLLING */ +#endif /* __CFG_REG37_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg37 */ +#ifndef __CFG_REG38_MACRO__ +#define __CFG_REG38_MACRO__ + +/* macros for field LFPS_GEN_U1EXIT */ +#endif /* __CFG_REG38_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg38 */ +#ifndef __CFG_REG39_MACRO__ +#define __CFG_REG39_MACRO__ + +/* macros for field LFPS_GEN_U3EXIT */ +#endif /* __CFG_REG39_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg39 */ +#ifndef __CFG_REG40_MACRO__ +#define __CFG_REG40_MACRO__ + +/* macros for field LFPS_MIN_GEN_U1EXIT */ +#endif /* __CFG_REG40_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg40 */ +#ifndef __CFG_REG41_MACRO__ +#define __CFG_REG41_MACRO__ + +/* macros for field LFPS_MIN_GEN_U2EXIT */ +#endif /* __CFG_REG41_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg41 */ +#ifndef __CFG_REG42_MACRO__ +#define __CFG_REG42_MACRO__ + +/* macros for field LFPS_POLLING_REPEAT */ +#endif /* __CFG_REG42_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg42 */ +#ifndef __CFG_REG43_MACRO__ +#define __CFG_REG43_MACRO__ + +/* macros for field LFPS_POLLING_MAX_TREPEAT */ +#endif /* __CFG_REG43_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg43 */ +#ifndef __CFG_REG44_MACRO__ +#define __CFG_REG44_MACRO__ + +/* macros for field LFPS_POLLING_MIN_TREPEAT */ +#endif /* __CFG_REG44_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg44 */ +#ifndef __CFG_REG45_MACRO__ +#define __CFG_REG45_MACRO__ + +/* macros for field ITP_WAKEUP_TIMEOUT */ +#endif /* __CFG_REG45_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg45 */ +#ifndef __CFG_REG46_MACRO__ +#define __CFG_REG46_MACRO__ + +/* macros for field TSEQ_QUANTITY */ +#endif /* __CFG_REG46_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg46 */ +#ifndef __CFG_REG47_MACRO__ +#define __CFG_REG47_MACRO__ + +/* macros for field ERDY_TIMEOUT_CNT */ +#endif /* __CFG_REG47_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg47 */ +#ifndef __CFG_REG48_MACRO__ +#define __CFG_REG48_MACRO__ + +/* macros for field TWTRSTFS_J_CNT */ +#endif /* __CFG_REG48_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg48 */ +#ifndef __CFG_REG49_MACRO__ +#define __CFG_REG49_MACRO__ + +/* macros for field TUCH_CNT */ +#endif /* __CFG_REG49_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg49 */ +#ifndef __CFG_REG50_MACRO__ +#define __CFG_REG50_MACRO__ + +/* macros for field TWAITCHK_CNT */ +#endif /* __CFG_REG50_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg50 */ +#ifndef __CFG_REG51_MACRO__ +#define __CFG_REG51_MACRO__ + +/* macros for field TWTFS_CNT */ +#endif /* __CFG_REG51_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg51 */ +#ifndef __CFG_REG52_MACRO__ +#define __CFG_REG52_MACRO__ + +/* macros for field TWTREV_CNT */ +#endif /* __CFG_REG52_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg52 */ +#ifndef __CFG_REG53_MACRO__ +#define __CFG_REG53_MACRO__ + +/* macros for field TWTRSTHS_CNT */ +#endif /* __CFG_REG53_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg53 */ +#ifndef __CFG_REG54_MACRO__ +#define __CFG_REG54_MACRO__ + +/* macros for field TWTRSM_CNT */ +#endif /* __CFG_REG54_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg54 */ +#ifndef __CFG_REG55_MACRO__ +#define __CFG_REG55_MACRO__ + +/* macros for field TDRSMUP_CNT */ +#endif /* __CFG_REG55_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg55 */ +#ifndef __CFG_REG56_MACRO__ +#define __CFG_REG56_MACRO__ + +/* macros for field TOUTHS_CNT */ +#endif /* __CFG_REG56_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg56 */ +#ifndef __CFG_REG57_MACRO__ +#define __CFG_REG57_MACRO__ + +/* macros for field LFPS_DEB_WIDTH */ +#endif /* __CFG_REG57_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg57 */ +#ifndef __CFG_REG58_MACRO__ +#define __CFG_REG58_MACRO__ + +/* macros for field LFPS_GEN_U2EXIT */ +#endif /* __CFG_REG58_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg58 */ +#ifndef __CFG_REG59_MACRO__ +#define __CFG_REG59_MACRO__ + +/* macros for field LFPS_MIN_GEN_U3EXIT */ +#endif /* __CFG_REG59_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg59 */ +#ifndef __CFG_REG60_MACRO__ +#define __CFG_REG60_MACRO__ + +/* macros for field PORT_CONFIG_TIMEOUT */ +#endif /* __CFG_REG60_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg60 */ +#ifndef __CFG_REG61_MACRO__ +#define __CFG_REG61_MACRO__ + +/* macros for field LFPS_POL_LFPS_TO_RXEQ */ +#endif /* __CFG_REG61_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg61 */ +#ifndef __CFG_REG62_MACRO__ +#define __CFG_REG62_MACRO__ + +/* macros for field PHY_TX_LATENCY */ +#endif /* __CFG_REG62_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg62 */ +#ifndef __CFG_REG63_MACRO__ +#define __CFG_REG63_MACRO__ + +/* macros for field U2_INACTIVITY_TMOUT */ +#endif /* __CFG_REG63_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg63 */ +#ifndef __CFG_REG64_MACRO__ +#define __CFG_REG64_MACRO__ + +/* macros for field TFILTSE0 */ +#endif /* __CFG_REG64_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg64 */ +#ifndef __CFG_REG65_MACRO__ +#define __CFG_REG65_MACRO__ + +/* macros for field TFILT */ +#endif /* __CFG_REG65_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg65 */ +#ifndef __CFG_REG66_MACRO__ +#define __CFG_REG66_MACRO__ + +/* macros for field TWTRSTFS_SE0 */ +#endif /* __CFG_REG66_MACRO__ */ + + +/* macros for usbss_dev_register_block.cfg_reg66 */ +#ifndef __DMA_AXI_CTRL_MACRO__ +#define __DMA_AXI_CTRL_MACRO__ + +/* macros for field MAWPROT */ +#endif /* __DMA_AXI_CTRL_MACRO__ */ + + +/* macros for usbss_dev_register_block.dma_axi_ctrl */ +#ifndef __DMA_AXI_ID_MACRO__ +#define __DMA_AXI_ID_MACRO__ + +/* macros for field MAW_ID */ +#endif /* __DMA_AXI_ID_MACRO__ */ + + +/* macros for usbss_dev_register_block.dma_axi_id */ +#ifndef __DMA_AXI_CAP_MACRO__ +#define __DMA_AXI_CAP_MACRO__ + +/* macros for field RESERVED0 */ +#endif /* __DMA_AXI_CAP_MACRO__ */ + + +/* macros for usbss_dev_register_block.dma_axi_cap */ +#ifndef __DMA_AXI_CTRL0_MACRO__ +#define __DMA_AXI_CTRL0_MACRO__ + +/* macros for field B_MAX */ +#endif /* __DMA_AXI_CTRL0_MACRO__ */ + + +/* macros for usbss_dev_register_block.dma_axi_ctrl0 */ +#ifndef __DMA_AXI_CTRL1_MACRO__ +#define __DMA_AXI_CTRL1_MACRO__ + +/* macros for field ROT */ +#endif /* __DMA_AXI_CTRL1_MACRO__ */ + + +/* macros for usbss_dev_register_block.dma_axi_ctrl1 */ +#endif /* __REG_USBSS_DEV_ADDR_MAP_MACRO_H__ */ diff --git a/drivers/usb/cdns3/dev-regs-map.h b/drivers/usb/cdns3/dev-regs-map.h new file mode 100644 index 000000000000..ef9cfe2ff342 --- /dev/null +++ b/drivers/usb/cdns3/dev-regs-map.h @@ -0,0 +1,126 @@ +/** + * dev-regs-map.h - Cadence USB3 Device register map definition + * + * Copyright (C) 2016 Cadence Design Systems - http://www.cadence.com + * Copyright 2017 NXP + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + + +#ifndef __REG_USBSS_DEV_ADDR_MAP_H__ +#define __REG_USBSS_DEV_ADDR_MAP_H__ + +#include "dev-regs-macro.h" + +struct usbss_dev_register_block_type { + uint32_t usb_conf; /* 0x0 - 0x4 */ + uint32_t usb_sts; /* 0x4 - 0x8 */ + uint32_t usb_cmd; /* 0x8 - 0xc */ + uint32_t usb_iptn; /* 0xc - 0x10 */ + uint32_t usb_lpm; /* 0x10 - 0x14 */ + uint32_t usb_ien; /* 0x14 - 0x18 */ + uint32_t usb_ists; /* 0x18 - 0x1c */ + uint32_t ep_sel; /* 0x1c - 0x20 */ + uint32_t ep_traddr; /* 0x20 - 0x24 */ + uint32_t ep_cfg; /* 0x24 - 0x28 */ + uint32_t ep_cmd; /* 0x28 - 0x2c */ + uint32_t ep_sts; /* 0x2c - 0x30 */ + uint32_t ep_sts_sid; /* 0x30 - 0x34 */ + uint32_t ep_sts_en; /* 0x34 - 0x38 */ + uint32_t drbl; /* 0x38 - 0x3c */ + uint32_t ep_ien; /* 0x3c - 0x40 */ + uint32_t ep_ists; /* 0x40 - 0x44 */ + uint32_t usb_pwr; /* 0x44 - 0x48 */ + uint32_t usb_conf2; /* 0x48 - 0x4c */ + uint32_t usb_cap1; /* 0x4c - 0x50 */ + uint32_t usb_cap2; /* 0x50 - 0x54 */ + uint32_t usb_cap3; /* 0x54 - 0x58 */ + uint32_t usb_cap4; /* 0x58 - 0x5c */ + uint32_t usb_cap5; /* 0x5c - 0x60 */ + uint32_t PAD2_73; /* 0x60 - 0x64 */ + uint32_t usb_cpkt1; /* 0x64 - 0x68 */ + uint32_t usb_cpkt2; /* 0x68 - 0x6c */ + uint32_t usb_cpkt3; /* 0x6c - 0x70 */ + char pad__0[0x90]; /* 0x70 - 0x100 */ + uint32_t PAD2_78; /* 0x100 - 0x104 */ + uint32_t dbg_link1; /* 0x104 - 0x108 */ + uint32_t PAD2_80; /* 0x108 - 0x10c */ + uint32_t PAD2_81; /* 0x10c - 0x110 */ + uint32_t PAD2_82; /* 0x110 - 0x114 */ + uint32_t PAD2_83; /* 0x114 - 0x118 */ + uint32_t PAD2_84; /* 0x118 - 0x11c */ + uint32_t PAD2_85; /* 0x11c - 0x120 */ + uint32_t PAD2_86; /* 0x120 - 0x124 */ + uint32_t PAD2_87; /* 0x124 - 0x128 */ + uint32_t PAD2_88; /* 0x128 - 0x12c */ + uint32_t PAD2_89; /* 0x12c - 0x130 */ + uint32_t PAD2_90; /* 0x130 - 0x134 */ + uint32_t PAD2_91; /* 0x134 - 0x138 */ + uint32_t PAD2_92; /* 0x138 - 0x13c */ + uint32_t PAD2_93; /* 0x13c - 0x140 */ + uint32_t PAD2_94; /* 0x140 - 0x144 */ + uint32_t PAD2_95; /* 0x144 - 0x148 */ + uint32_t PAD2_96; /* 0x148 - 0x14c */ + uint32_t PAD2_97; /* 0x14c - 0x150 */ + uint32_t PAD2_98; /* 0x150 - 0x154 */ + uint32_t PAD2_99; /* 0x154 - 0x158 */ + uint32_t PAD2_100; /* 0x158 - 0x15c */ + uint32_t PAD2_101; /* 0x15c - 0x160 */ + uint32_t PAD2_102; /* 0x160 - 0x164 */ + uint32_t PAD2_103; /* 0x164 - 0x168 */ + uint32_t PAD2_104; /* 0x168 - 0x16c */ + uint32_t PAD2_105; /* 0x16c - 0x170 */ + uint32_t PAD2_106; /* 0x170 - 0x174 */ + uint32_t PAD2_107; /* 0x174 - 0x178 */ + uint32_t PAD2_108; /* 0x178 - 0x17c */ + uint32_t PAD2_109; /* 0x17c - 0x180 */ + uint32_t PAD2_110; /* 0x180 - 0x184 */ + uint32_t PAD2_111; /* 0x184 - 0x188 */ + uint32_t PAD2_112; /* 0x188 - 0x18c */ + char pad__1[0x20]; /* 0x18c - 0x1ac */ + uint32_t PAD2_114; /* 0x1ac - 0x1b0 */ + uint32_t PAD2_115; /* 0x1b0 - 0x1b4 */ + uint32_t PAD2_116; /* 0x1b4 - 0x1b8 */ + uint32_t PAD2_117; /* 0x1b8 - 0x1bc */ + uint32_t PAD2_118; /* 0x1bc - 0x1c0 */ + uint32_t PAD2_119; /* 0x1c0 - 0x1c4 */ + uint32_t PAD2_120; /* 0x1c4 - 0x1c8 */ + uint32_t PAD2_121; /* 0x1c8 - 0x1cc */ + uint32_t PAD2_122; /* 0x1cc - 0x1d0 */ + uint32_t PAD2_123; /* 0x1d0 - 0x1d4 */ + uint32_t PAD2_124; /* 0x1d4 - 0x1d8 */ + uint32_t PAD2_125; /* 0x1d8 - 0x1dc */ + uint32_t PAD2_126; /* 0x1dc - 0x1e0 */ + uint32_t PAD2_127; /* 0x1e0 - 0x1e4 */ + uint32_t PAD2_128; /* 0x1e4 - 0x1e8 */ + uint32_t PAD2_129; /* 0x1e8 - 0x1ec */ + uint32_t PAD2_130; /* 0x1ec - 0x1f0 */ + uint32_t PAD2_131; /* 0x1f0 - 0x1f4 */ + uint32_t PAD2_132; /* 0x1f4 - 0x1f8 */ + uint32_t PAD2_133; /* 0x1f8 - 0x1fc */ + uint32_t PAD2_134; /* 0x1fc - 0x200 */ + uint32_t PAD2_135; /* 0x200 - 0x204 */ + uint32_t PAD2_136; /* 0x204 - 0x208 */ + uint32_t PAD2_137; /* 0x208 - 0x20c */ + uint32_t PAD2_138; /* 0x20c - 0x210 */ + uint32_t PAD2_139; /* 0x210 - 0x214 */ + uint32_t PAD2_140; /* 0x214 - 0x218 */ + uint32_t PAD2_141; /* 0x218 - 0x21c */ + uint32_t PAD2_142; /* 0x21c - 0x220 */ + uint32_t PAD2_143; /* 0x220 - 0x224 */ + uint32_t PAD2_144; /* 0x224 - 0x228 */ + char pad__2[0xd8]; /* 0x228 - 0x300 */ + uint32_t dma_axi_ctrl; /* 0x300 - 0x304 */ + uint32_t PAD2_147; /* 0x304 - 0x308 */ + uint32_t PAD2_148; /* 0x308 - 0x30c */ + uint32_t PAD2_149; /* 0x30c - 0x310 */ + uint32_t PAD2_150; /* 0x310 - 0x314 */ +}; + +#endif /* __REG_USBSS_DEV_ADDR_MAP_H__ */ diff --git a/drivers/usb/cdns3/drd.c b/drivers/usb/cdns3/drd.c deleted file mode 100644 index 16ad485f0b69..000000000000 --- a/drivers/usb/cdns3/drd.c +++ /dev/null @@ -1,381 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Cadence USBSS DRD Driver. - * - * Copyright (C) 2018-2019 Cadence. - * Copyright (C) 2019 Texas Instruments - * - * Author: Pawel Laszczak <pawell@cadence.com> - * Roger Quadros <rogerq@ti.com> - * - * - */ -#include <linux/kernel.h> -#include <linux/interrupt.h> -#include <linux/delay.h> -#include <linux/iopoll.h> -#include <linux/usb/otg.h> - -#include "gadget.h" -#include "drd.h" -#include "core.h" - -/** - * cdns3_set_mode - change mode of OTG Core - * @cdns: pointer to context structure - * @mode: selected mode from cdns_role - * - * Returns 0 on success otherwise negative errno - */ -int cdns3_set_mode(struct cdns3 *cdns, enum usb_dr_mode mode) -{ - int ret = 0; - u32 reg; - - switch (mode) { - case USB_DR_MODE_PERIPHERAL: - break; - case USB_DR_MODE_HOST: - break; - case USB_DR_MODE_OTG: - dev_dbg(cdns->dev, "Set controller to OTG mode\n"); - if (cdns->version == CDNS3_CONTROLLER_V1) { - reg = readl(&cdns->otg_v1_regs->override); - reg |= OVERRIDE_IDPULLUP; - writel(reg, &cdns->otg_v1_regs->override); - } else { - reg = readl(&cdns->otg_v0_regs->ctrl1); - reg |= OVERRIDE_IDPULLUP_V0; - writel(reg, &cdns->otg_v0_regs->ctrl1); - } - - /* - * Hardware specification says: "ID_VALUE must be valid within - * 50ms after idpullup is set to '1" so driver must wait - * 50ms before reading this pin. - */ - usleep_range(50000, 60000); - break; - default: - dev_err(cdns->dev, "Unsupported mode of operation %d\n", mode); - return -EINVAL; - } - - return ret; -} - -int cdns3_get_id(struct cdns3 *cdns) -{ - int id; - - id = readl(&cdns->otg_regs->sts) & OTGSTS_ID_VALUE; - dev_dbg(cdns->dev, "OTG ID: %d", id); - - return id; -} - -int cdns3_get_vbus(struct cdns3 *cdns) -{ - int vbus; - - vbus = !!(readl(&cdns->otg_regs->sts) & OTGSTS_VBUS_VALID); - dev_dbg(cdns->dev, "OTG VBUS: %d", vbus); - - return vbus; -} - -int cdns3_is_host(struct cdns3 *cdns) -{ - if (cdns->dr_mode == USB_DR_MODE_HOST) - return 1; - else if (!cdns3_get_id(cdns)) - return 1; - - return 0; -} - -int cdns3_is_device(struct cdns3 *cdns) -{ - if (cdns->dr_mode == USB_DR_MODE_PERIPHERAL) - return 1; - else if (cdns->dr_mode == USB_DR_MODE_OTG) - if (cdns3_get_id(cdns)) - return 1; - - return 0; -} - -/** - * cdns3_otg_disable_irq - Disable all OTG interrupts - * @cdns: Pointer to controller context structure - */ -static void cdns3_otg_disable_irq(struct cdns3 *cdns) -{ - writel(0, &cdns->otg_regs->ien); -} - -/** - * cdns3_otg_enable_irq - enable id and sess_valid interrupts - * @cdns: Pointer to controller context structure - */ -static void cdns3_otg_enable_irq(struct cdns3 *cdns) -{ - writel(OTGIEN_ID_CHANGE_INT | OTGIEN_VBUSVALID_RISE_INT | - OTGIEN_VBUSVALID_FALL_INT, &cdns->otg_regs->ien); -} - -/** - * cdns3_drd_switch_host - start/stop host - * @cdns: Pointer to controller context structure - * @on: 1 for start, 0 for stop - * - * Returns 0 on success otherwise negative errno - */ -int cdns3_drd_switch_host(struct cdns3 *cdns, int on) -{ - int ret, val; - u32 reg = OTGCMD_OTG_DIS; - - /* switch OTG core */ - if (on) { - writel(OTGCMD_HOST_BUS_REQ | reg, &cdns->otg_regs->cmd); - - dev_dbg(cdns->dev, "Waiting till Host mode is turned on\n"); - ret = readl_poll_timeout_atomic(&cdns->otg_regs->sts, val, - val & OTGSTS_XHCI_READY, - 1, 100000); - if (ret) { - dev_err(cdns->dev, "timeout waiting for xhci_ready\n"); - return ret; - } - } else { - writel(OTGCMD_HOST_BUS_DROP | OTGCMD_DEV_BUS_DROP | - OTGCMD_DEV_POWER_OFF | OTGCMD_HOST_POWER_OFF, - &cdns->otg_regs->cmd); - /* Waiting till H_IDLE state.*/ - readl_poll_timeout_atomic(&cdns->otg_regs->state, val, - !(val & OTGSTATE_HOST_STATE_MASK), - 1, 2000000); - } - - return 0; -} - -/** - * cdns3_drd_switch_gadget - start/stop gadget - * @cdns: Pointer to controller context structure - * @on: 1 for start, 0 for stop - * - * Returns 0 on success otherwise negative errno - */ -int cdns3_drd_switch_gadget(struct cdns3 *cdns, int on) -{ - int ret, val; - u32 reg = OTGCMD_OTG_DIS; - - /* switch OTG core */ - if (on) { - writel(OTGCMD_DEV_BUS_REQ | reg, &cdns->otg_regs->cmd); - - dev_dbg(cdns->dev, "Waiting till Device mode is turned on\n"); - - ret = readl_poll_timeout_atomic(&cdns->otg_regs->sts, val, - val & OTGSTS_DEV_READY, - 1, 100000); - if (ret) { - dev_err(cdns->dev, "timeout waiting for dev_ready\n"); - return ret; - } - } else { - /* - * driver should wait at least 10us after disabling Device - * before turning-off Device (DEV_BUS_DROP) - */ - usleep_range(20, 30); - writel(OTGCMD_HOST_BUS_DROP | OTGCMD_DEV_BUS_DROP | - OTGCMD_DEV_POWER_OFF | OTGCMD_HOST_POWER_OFF, - &cdns->otg_regs->cmd); - /* Waiting till DEV_IDLE state.*/ - readl_poll_timeout_atomic(&cdns->otg_regs->state, val, - !(val & OTGSTATE_DEV_STATE_MASK), - 1, 2000000); - } - - return 0; -} - -/** - * cdns3_init_otg_mode - initialize drd controller - * @cdns: Pointer to controller context structure - * - * Returns 0 on success otherwise negative errno - */ -static int cdns3_init_otg_mode(struct cdns3 *cdns) -{ - int ret = 0; - - cdns3_otg_disable_irq(cdns); - /* clear all interrupts */ - writel(~0, &cdns->otg_regs->ivect); - - ret = cdns3_set_mode(cdns, USB_DR_MODE_OTG); - if (ret) - return ret; - - cdns3_otg_enable_irq(cdns); - return ret; -} - -/** - * cdns3_drd_update_mode - initialize mode of operation - * @cdns: Pointer to controller context structure - * - * Returns 0 on success otherwise negative errno - */ -int cdns3_drd_update_mode(struct cdns3 *cdns) -{ - int ret = 0; - - switch (cdns->dr_mode) { - case USB_DR_MODE_PERIPHERAL: - ret = cdns3_set_mode(cdns, USB_DR_MODE_PERIPHERAL); - break; - case USB_DR_MODE_HOST: - ret = cdns3_set_mode(cdns, USB_DR_MODE_HOST); - break; - case USB_DR_MODE_OTG: - ret = cdns3_init_otg_mode(cdns); - break; - default: - dev_err(cdns->dev, "Unsupported mode of operation %d\n", - cdns->dr_mode); - return -EINVAL; - } - - return ret; -} - -static irqreturn_t cdns3_drd_thread_irq(int irq, void *data) -{ - struct cdns3 *cdns = data; - - cdns3_hw_role_switch(cdns); - - return IRQ_HANDLED; -} - -/** - * cdns3_drd_irq - interrupt handler for OTG events - * - * @irq: irq number for cdns3 core device - * @data: structure of cdns3 - * - * Returns IRQ_HANDLED or IRQ_NONE - */ -static irqreturn_t cdns3_drd_irq(int irq, void *data) -{ - irqreturn_t ret = IRQ_NONE; - struct cdns3 *cdns = data; - u32 reg; - - if (cdns->dr_mode != USB_DR_MODE_OTG) - return ret; - - reg = readl(&cdns->otg_regs->ivect); - - if (!reg) - return ret; - - if (reg & OTGIEN_ID_CHANGE_INT) { - dev_dbg(cdns->dev, "OTG IRQ: new ID: %d\n", - cdns3_get_id(cdns)); - - ret = IRQ_WAKE_THREAD; - } - - if (reg & (OTGIEN_VBUSVALID_RISE_INT | OTGIEN_VBUSVALID_FALL_INT)) { - dev_dbg(cdns->dev, "OTG IRQ: new VBUS: %d\n", - cdns3_get_vbus(cdns)); - - ret = IRQ_WAKE_THREAD; - } - - writel(~0, &cdns->otg_regs->ivect); - return ret; -} - -int cdns3_drd_init(struct cdns3 *cdns) -{ - void __iomem *regs; - int ret = 0; - u32 state; - - regs = devm_ioremap_resource(cdns->dev, &cdns->otg_res); - if (IS_ERR(regs)) - return PTR_ERR(regs); - - /* Detection of DRD version. Controller has been released - * in two versions. Both are similar, but they have same changes - * in register maps. - * The first register in old version is command register and it's read - * only, so driver should read 0 from it. On the other hand, in v1 - * the first register contains device ID number which is not set to 0. - * Driver uses this fact to detect the proper version of - * controller. - */ - cdns->otg_v0_regs = regs; - if (!readl(&cdns->otg_v0_regs->cmd)) { - cdns->version = CDNS3_CONTROLLER_V0; - cdns->otg_v1_regs = NULL; - cdns->otg_regs = regs; - writel(1, &cdns->otg_v0_regs->simulate); - dev_info(cdns->dev, "DRD version v0 (%08x)\n", - readl(&cdns->otg_v0_regs->version)); - } else { - cdns->otg_v0_regs = NULL; - cdns->otg_v1_regs = regs; - cdns->otg_regs = (void *)&cdns->otg_v1_regs->cmd; - cdns->version = CDNS3_CONTROLLER_V1; - writel(1, &cdns->otg_v1_regs->simulate); - dev_info(cdns->dev, "DRD version v1 (ID: %08x, rev: %08x)\n", - readl(&cdns->otg_v1_regs->did), - readl(&cdns->otg_v1_regs->rid)); - } - - state = OTGSTS_STRAP(readl(&cdns->otg_regs->sts)); - - /* Update dr_mode according to STRAP configuration. */ - cdns->dr_mode = USB_DR_MODE_OTG; - if (state == OTGSTS_STRAP_HOST) { - dev_dbg(cdns->dev, "Controller strapped to HOST\n"); - cdns->dr_mode = USB_DR_MODE_HOST; - } else if (state == OTGSTS_STRAP_GADGET) { - dev_dbg(cdns->dev, "Controller strapped to PERIPHERAL\n"); - cdns->dr_mode = USB_DR_MODE_PERIPHERAL; - } - - ret = devm_request_threaded_irq(cdns->dev, cdns->otg_irq, - cdns3_drd_irq, - cdns3_drd_thread_irq, - IRQF_SHARED, - dev_name(cdns->dev), cdns); - - if (ret) { - dev_err(cdns->dev, "couldn't get otg_irq\n"); - return ret; - } - - state = readl(&cdns->otg_regs->sts); - if (OTGSTS_OTG_NRDY(state) != 0) { - dev_err(cdns->dev, "Cadence USB3 OTG device not ready\n"); - return -ENODEV; - } - - return ret; -} - -int cdns3_drd_exit(struct cdns3 *cdns) -{ - cdns3_otg_disable_irq(cdns); - return 0; -} diff --git a/drivers/usb/cdns3/drd.h b/drivers/usb/cdns3/drd.h deleted file mode 100644 index 04e01c4d2377..000000000000 --- a/drivers/usb/cdns3/drd.h +++ /dev/null @@ -1,167 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * Cadence USB3 DRD header file. - * - * Copyright (C) 2018-2019 Cadence. - * - * Author: Pawel Laszczak <pawell@cadence.com> - */ -#ifndef __LINUX_CDNS3_DRD -#define __LINUX_CDNS3_DRD - -#include <linux/usb/otg.h> -#include <linux/phy/phy.h> -#include "core.h" - -/* DRD register interface for version v1. */ -struct cdns3_otg_regs { - __le32 did; - __le32 rid; - __le32 capabilities; - __le32 reserved1; - __le32 cmd; - __le32 sts; - __le32 state; - __le32 reserved2; - __le32 ien; - __le32 ivect; - __le32 refclk; - __le32 tmr; - __le32 reserved3[4]; - __le32 simulate; - __le32 override; - __le32 susp_ctrl; - __le32 reserved4; - __le32 anasts; - __le32 adp_ramp_time; - __le32 ctrl1; - __le32 ctrl2; -}; - -/* DRD register interface for version v0. */ -struct cdns3_otg_legacy_regs { - __le32 cmd; - __le32 sts; - __le32 state; - __le32 refclk; - __le32 ien; - __le32 ivect; - __le32 reserved1[3]; - __le32 tmr; - __le32 reserved2[2]; - __le32 version; - __le32 capabilities; - __le32 reserved3[2]; - __le32 simulate; - __le32 reserved4[5]; - __le32 ctrl1; -}; - -/* - * Common registers interface for both version of DRD. - */ -struct cdns3_otg_common_regs { - __le32 cmd; - __le32 sts; - __le32 state; - __le32 different1; - __le32 ien; - __le32 ivect; -}; - -/* CDNS_RID - bitmasks */ -#define CDNS_RID(p) ((p) & GENMASK(15, 0)) - -/* CDNS_VID - bitmasks */ -#define CDNS_DID(p) ((p) & GENMASK(31, 0)) - -/* OTGCMD - bitmasks */ -/* "Request the bus for Device mode. */ -#define OTGCMD_DEV_BUS_REQ BIT(0) -/* Request the bus for Host mode */ -#define OTGCMD_HOST_BUS_REQ BIT(1) -/* Enable OTG mode. */ -#define OTGCMD_OTG_EN BIT(2) -/* Disable OTG mode */ -#define OTGCMD_OTG_DIS BIT(3) -/*"Configure OTG as A-Device. */ -#define OTGCMD_A_DEV_EN BIT(4) -/*"Configure OTG as A-Device. */ -#define OTGCMD_A_DEV_DIS BIT(5) -/* Drop the bus for Device mod e. */ -#define OTGCMD_DEV_BUS_DROP BIT(8) -/* Drop the bus for Host mode*/ -#define OTGCMD_HOST_BUS_DROP BIT(9) -/* Power Down USBSS-DEV. */ -#define OTGCMD_DEV_POWER_OFF BIT(11) -/* Power Down CDNSXHCI. */ -#define OTGCMD_HOST_POWER_OFF BIT(12) - -/* OTGIEN - bitmasks */ -/* ID change interrupt enable */ -#define OTGIEN_ID_CHANGE_INT BIT(0) -/* Vbusvalid fall detected interrupt enable.*/ -#define OTGIEN_VBUSVALID_RISE_INT BIT(4) -/* Vbusvalid fall detected interrupt enable */ -#define OTGIEN_VBUSVALID_FALL_INT BIT(5) - -/* OTGSTS - bitmasks */ -/* - * Current value of the ID pin. It is only valid when idpullup in - * OTGCTRL1_TYPE register is set to '1'. - */ -#define OTGSTS_ID_VALUE BIT(0) -/* Current value of the vbus_valid */ -#define OTGSTS_VBUS_VALID BIT(1) -/* Current value of the b_sess_vld */ -#define OTGSTS_SESSION_VALID BIT(2) -/*Device mode is active*/ -#define OTGSTS_DEV_ACTIVE BIT(3) -/* Host mode is active. */ -#define OTGSTS_HOST_ACTIVE BIT(4) -/* OTG Controller not ready. */ -#define OTGSTS_OTG_NRDY_MASK BIT(11) -#define OTGSTS_OTG_NRDY(p) ((p) & OTGSTS_OTG_NRDY_MASK) -/* - * Value of the strap pins. - * 000 - no default configuration - * 010 - Controller initiall configured as Host - * 100 - Controller initially configured as Device - */ -#define OTGSTS_STRAP(p) (((p) & GENMASK(14, 12)) >> 12) -#define OTGSTS_STRAP_NO_DEFAULT_CFG 0x00 -#define OTGSTS_STRAP_HOST_OTG 0x01 -#define OTGSTS_STRAP_HOST 0x02 -#define OTGSTS_STRAP_GADGET 0x04 -/* Host mode is turned on. */ -#define OTGSTS_XHCI_READY BIT(26) -/* "Device mode is turned on .*/ -#define OTGSTS_DEV_READY BIT(27) - -/* OTGSTATE- bitmasks */ -#define OTGSTATE_DEV_STATE_MASK GENMASK(2, 0) -#define OTGSTATE_HOST_STATE_MASK GENMASK(5, 3) -#define OTGSTATE_HOST_STATE_IDLE 0x0 -#define OTGSTATE_HOST_STATE_VBUS_FALL 0x7 -#define OTGSTATE_HOST_STATE(p) (((p) & OTGSTATE_HOST_STATE_MASK) >> 3) - -/* OTGREFCLK - bitmasks */ -#define OTGREFCLK_STB_CLK_SWITCH_EN BIT(31) - -/* OVERRIDE - bitmasks */ -#define OVERRIDE_IDPULLUP BIT(0) -/* Only for CDNS3_CONTROLLER_V0 version */ -#define OVERRIDE_IDPULLUP_V0 BIT(24) - -int cdns3_is_host(struct cdns3 *cdns); -int cdns3_is_device(struct cdns3 *cdns); -int cdns3_get_id(struct cdns3 *cdns); -int cdns3_get_vbus(struct cdns3 *cdns); -int cdns3_drd_init(struct cdns3 *cdns); -int cdns3_drd_exit(struct cdns3 *cdns); -int cdns3_drd_update_mode(struct cdns3 *cdns); -int cdns3_drd_switch_gadget(struct cdns3 *cdns, int on); -int cdns3_drd_switch_host(struct cdns3 *cdns, int on); -int cdns3_set_mode(struct cdns3 *cdns, enum usb_dr_mode mode); - -#endif /* __LINUX_CDNS3_DRD */ diff --git a/drivers/usb/cdns3/ep0.c b/drivers/usb/cdns3/ep0.c index e71240b386b4..09ab1a87a267 100644 --- a/drivers/usb/cdns3/ep0.c +++ b/drivers/usb/cdns3/ep0.c @@ -7,14 +7,16 @@ * * Authors: Pawel Jez <pjez@cadence.com>, * Pawel Laszczak <pawell@cadence.com> - * Peter Chen <peter.chen@nxp.com> + * Peter Chen <peter.chen@nxp.com> */ +#include <linux/delay.h> +#include <linux/interrupt.h> #include <linux/usb/composite.h> -#include <linux/iopoll.h> #include "gadget.h" #include "trace.h" +#include "core.h" static struct usb_endpoint_descriptor cdns3_gadget_ep0_desc = { .bLength = USB_DT_ENDPOINT_SIZE, @@ -32,25 +34,14 @@ static struct usb_endpoint_descriptor cdns3_gadget_ep0_desc = { */ static void cdns3_ep0_run_transfer(struct cdns3_device *priv_dev, dma_addr_t dma_addr, - unsigned int length, int erdy, int zlp) + unsigned int length, int erdy) { struct cdns3_usb_regs __iomem *regs = priv_dev->regs; struct cdns3_endpoint *priv_ep = priv_dev->eps[0]; - priv_ep->trb_pool[0].buffer = TRB_BUFFER(dma_addr); - priv_ep->trb_pool[0].length = TRB_LEN(length); - - if (zlp) { - priv_ep->trb_pool[0].control = TRB_CYCLE | TRB_TYPE(TRB_NORMAL); - priv_ep->trb_pool[1].buffer = TRB_BUFFER(dma_addr); - priv_ep->trb_pool[1].length = TRB_LEN(0); - priv_ep->trb_pool[1].control = TRB_CYCLE | TRB_IOC | - TRB_TYPE(TRB_NORMAL); - } else { - priv_ep->trb_pool[0].control = TRB_CYCLE | TRB_IOC | - TRB_TYPE(TRB_NORMAL); - priv_ep->trb_pool[1].control = 0; - } + priv_ep->trb_pool->buffer = TRB_BUFFER(dma_addr); + priv_ep->trb_pool->length = TRB_LEN(length); + priv_ep->trb_pool->control = TRB_CYCLE | TRB_IOC | TRB_TYPE(TRB_NORMAL); trace_cdns3_prepare_trb(priv_ep, priv_ep->trb_pool); @@ -61,12 +52,9 @@ static void cdns3_ep0_run_transfer(struct cdns3_device *priv_dev, trace_cdns3_doorbell_ep0(priv_dev->ep0_data_dir ? "ep0in" : "ep0out", readl(®s->ep_traddr)); - /* TRB should be prepared before starting transfer. */ + /* TRB should be prepared before starting transfer */ writel(EP_CMD_DRDY, ®s->ep_cmd); - /* Resume controller before arming transfer. */ - __cdns3_gadget_wakeup(priv_dev); - if (erdy) writel(EP_CMD_ERDY, &priv_dev->regs->ep_cmd); } @@ -82,11 +70,14 @@ static void cdns3_ep0_run_transfer(struct cdns3_device *priv_dev, static int cdns3_ep0_delegate_req(struct cdns3_device *priv_dev, struct usb_ctrlrequest *ctrl_req) { - int ret; + int ret = 0; spin_unlock(&priv_dev->lock); priv_dev->setup_pending = 1; - ret = priv_dev->gadget_driver->setup(&priv_dev->gadget, ctrl_req); + if (priv_dev->gadget_driver && priv_dev->gadget_driver->setup + && get_gadget_data(&priv_dev->gadget)) + ret = priv_dev->gadget_driver->setup(&priv_dev->gadget, + ctrl_req); priv_dev->setup_pending = 0; spin_lock(&priv_dev->lock); return ret; @@ -97,7 +88,7 @@ static void cdns3_prepare_setup_packet(struct cdns3_device *priv_dev) priv_dev->ep0_data_dir = 0; priv_dev->ep0_stage = CDNS3_SETUP_STAGE; cdns3_ep0_run_transfer(priv_dev, priv_dev->setup_dma, - sizeof(struct usb_ctrlrequest), 0, 0); + sizeof(struct usb_ctrlrequest), 0); } static void cdns3_ep0_complete_setup(struct cdns3_device *priv_dev, @@ -111,7 +102,7 @@ static void cdns3_ep0_complete_setup(struct cdns3_device *priv_dev, list_del_init(&request->list); if (send_stall) { - trace_cdns3_halt(priv_ep, send_stall, 0); + cdns3_dbg(priv_ep->cdns3_dev, "STALL for ep0\n"); /* set_stall on ep0 */ cdns3_select_ep(priv_dev, 0x00); writel(EP_CMD_SSTALL, &priv_dev->regs->ep_cmd); @@ -122,8 +113,6 @@ static void cdns3_ep0_complete_setup(struct cdns3_device *priv_dev, priv_dev->ep0_stage = CDNS3_SETUP_STAGE; writel((send_erdy ? EP_CMD_ERDY : 0) | EP_CMD_REQ_CMPL, &priv_dev->regs->ep_cmd); - - cdns3_allow_enable_l1(priv_dev, 1); } /** @@ -234,11 +223,10 @@ static int cdns3_req_ep0_set_address(struct cdns3_device *priv_dev, static int cdns3_req_ep0_get_status(struct cdns3_device *priv_dev, struct usb_ctrlrequest *ctrl) { - struct cdns3_endpoint *priv_ep; __le16 *response_pkt; u16 usb_status = 0; u32 recip; - u8 index; + u32 reg; recip = ctrl->bRequestType & USB_RECIP_MASK; @@ -254,6 +242,8 @@ static int cdns3_req_ep0_get_status(struct cdns3_device *priv_dev, if (priv_dev->gadget.speed != USB_SPEED_SUPER) break; + reg = readl(&priv_dev->regs->usb_sts); + if (priv_dev->u1_allowed) usb_status |= BIT(USB_DEV_STAT_U1_ENABLED); @@ -264,13 +254,9 @@ static int cdns3_req_ep0_get_status(struct cdns3_device *priv_dev, case USB_RECIP_INTERFACE: return cdns3_ep0_delegate_req(priv_dev, ctrl); case USB_RECIP_ENDPOINT: - index = cdns3_ep_addr_to_index(ctrl->wIndex); - priv_ep = priv_dev->eps[index]; - - /* check if endpoint is stalled or stall is pending */ + /* check if endpoint is stalled */ cdns3_select_ep(priv_dev, ctrl->wIndex); - if (EP_STS_STALL(readl(&priv_dev->regs->ep_sts)) || - (priv_ep->flags & EP_STALL_PENDING)) + if (EP_STS_STALL(readl(&priv_dev->regs->ep_sts))) usb_status = BIT(USB_ENDPOINT_HALT); break; default: @@ -281,7 +267,7 @@ static int cdns3_req_ep0_get_status(struct cdns3_device *priv_dev, *response_pkt = cpu_to_le16(usb_status); cdns3_ep0_run_transfer(priv_dev, priv_dev->setup_dma, - sizeof(*response_pkt), 1, 0); + sizeof(*response_pkt), 1); return 0; } @@ -293,13 +279,15 @@ static int cdns3_ep0_feature_handle_device(struct cdns3_device *priv_dev, enum usb_device_speed speed; int ret = 0; u32 wValue; + u32 wIndex; u16 tmode; wValue = le16_to_cpu(ctrl->wValue); + wIndex = le16_to_cpu(ctrl->wIndex); state = priv_dev->gadget.state; speed = priv_dev->gadget.speed; - switch (wValue) { + switch (ctrl->wValue) { case USB_DEVICE_REMOTE_WAKEUP: priv_dev->wake_up_flag = !!set; break; @@ -338,7 +326,7 @@ static int cdns3_ep0_feature_handle_device(struct cdns3_device *priv_dev, * for sending status stage. * This time should be less then 3ms. */ - mdelay(1); + usleep_range(1000, 2000); cdns3_set_register_bit(&priv_dev->regs->usb_cmd, USB_CMD_STMODE | USB_STS_TMODE_SEL(tmode - 1)); @@ -392,12 +380,40 @@ static int cdns3_ep0_feature_handle_endpoint(struct cdns3_device *priv_dev, cdns3_select_ep(priv_dev, ctrl->wIndex); - if (set) - __cdns3_gadget_ep_set_halt(priv_ep); - else if (!(priv_ep->flags & EP_WEDGE)) - ret = __cdns3_gadget_ep_clear_halt(priv_ep); + if (set) { + cdns3_dbg(priv_ep->cdns3_dev, "Stall endpoint %s\n", + priv_ep->name); + writel(EP_CMD_SSTALL, &priv_dev->regs->ep_cmd); + priv_ep->flags |= EP_STALL; + } else { + struct usb_request *request; + + if (priv_dev->eps[index]->flags & EP_WEDGE) { + cdns3_select_ep(priv_dev, 0x00); + return 0; + } + + cdns3_dbg(priv_ep->cdns3_dev, "Clear Stalled endpoint %s\n", + priv_ep->name); + + writel(EP_CMD_CSTALL | EP_CMD_EPRST, &priv_dev->regs->ep_cmd); + + /* wait for EPRST cleared */ + ret = cdns3_handshake(&priv_dev->regs->ep_cmd, + EP_CMD_EPRST, 0, 100); + if (ret) + return -EINVAL; + + priv_ep->flags &= ~EP_STALL; + + request = cdns3_next_request(&priv_ep->pending_req_list); + if (request) { + cdns3_dbg(priv_ep->cdns3_dev, "Resume transfer for %s\n", + priv_ep->name); - cdns3_select_ep(priv_dev, 0x00); + cdns3_rearm_transfer(priv_ep, 1); + } + } return ret; } @@ -457,7 +473,7 @@ static int cdns3_req_ep0_set_sel(struct cdns3_device *priv_dev, return -EINVAL; } - cdns3_ep0_run_transfer(priv_dev, priv_dev->setup_dma, 6, 1, 0); + cdns3_ep0_run_transfer(priv_dev, priv_dev->setup_dma, 6, 1); return 0; } @@ -545,6 +561,30 @@ void cdns3_pending_setup_status_handler(struct work_struct *work) } /** + * cdns3_gadget_ep_giveback - call struct usb_request's ->complete callback + * @priv_ep: The endpoint to whom the request belongs to + * @priv_req: The request we're giving back + * @status: completion code for the request + * + * Must be called with controller's lock held and interrupts disabled. This + * function will unmap @req and call its ->complete() callback to notify upper + * layers that it has completed. + */ + +void cdns3_gadget_ep0_giveback(struct cdns3_device *priv_dev, + int status) +{ + struct cdns3_endpoint *priv_ep; + struct usb_request *request; + + priv_ep = priv_dev->eps[0]; + request = cdns3_next_request(&priv_ep->pending_req_list); + + priv_ep->dir = priv_dev->ep0_data_dir; + cdns3_gadget_giveback(priv_ep, to_cdns3_request(request), status); +} + +/** * cdns3_ep0_setup_phase - Handling setup USB requests * @priv_dev: extended gadget object */ @@ -600,6 +640,10 @@ static void cdns3_transfer_completed(struct cdns3_device *priv_dev) TRB_LEN(le32_to_cpu(priv_ep->trb_pool->length)); priv_ep->dir = priv_dev->ep0_data_dir; + if (request->zero && request->length && priv_ep->dir + && (request->length % priv_dev->gadget.ep0->maxpacket == 0)) + cdns3_ep0_run_transfer(priv_dev, request->dma, 0, 1); + cdns3_gadget_giveback(priv_ep, to_cdns3_request(request), 0); } @@ -640,12 +684,7 @@ void cdns3_check_ep0_interrupt_proceed(struct cdns3_device *priv_dev, int dir) __pending_setup_status_handler(priv_dev); - if (ep_sts_reg & EP_STS_SETUP) - priv_dev->wait_for_setup = 1; - - if (priv_dev->wait_for_setup && ep_sts_reg & EP_STS_IOC) { - priv_dev->wait_for_setup = 0; - cdns3_allow_enable_l1(priv_dev, 0); + if ((ep_sts_reg & EP_STS_SETUP)) { cdns3_ep0_setup_phase(priv_dev); } else if ((ep_sts_reg & EP_STS_IOC) || (ep_sts_reg & EP_STS_ISP)) { priv_dev->ep0_data_dir = dir; @@ -709,9 +748,10 @@ static int cdns3_gadget_ep0_queue(struct usb_ep *ep, unsigned long flags; int erdy_sent = 0; int ret = 0; - u8 zlp = 0; - trace_cdns3_ep0_queue(priv_dev, request); + cdns3_dbg(priv_ep->cdns3_dev, "Queue to Ep0%s L: %d\n", + priv_dev->ep0_data_dir ? "IN" : "OUT", + request->length); /* cancel the request if controller receive new SETUP packet. */ if (cdns3_check_new_setup(priv_dev)) @@ -728,8 +768,6 @@ static int cdns3_gadget_ep0_queue(struct usb_ep *ep, if (!erdy_sent) cdns3_ep0_complete_setup(priv_dev, 0, 1); - cdns3_allow_enable_l1(priv_dev, 1); - request->actual = 0; priv_dev->status_completion_no_call = true; priv_dev->pending_status_request = request; @@ -762,13 +800,7 @@ static int cdns3_gadget_ep0_queue(struct usb_ep *ep, request->status = -EINPROGRESS; list_add_tail(&request->list, &priv_ep->pending_req_list); - - if (request->zero && request->length && - (request->length % ep->maxpacket == 0)) - zlp = 1; - - cdns3_ep0_run_transfer(priv_dev, request->dma, request->length, 1, zlp); - + cdns3_ep0_run_transfer(priv_dev, request->dma, request->length, 1); spin_unlock_irqrestore(&priv_dev->lock, flags); return ret; @@ -838,13 +870,6 @@ void cdns3_ep0_config(struct cdns3_device *priv_dev) /* init ep out */ cdns3_select_ep(priv_dev, USB_DIR_OUT); - if (priv_dev->dev_ver >= DEV_VER_V3) { - cdns3_set_register_bit(&priv_dev->regs->dtrans, - BIT(0) | BIT(16)); - cdns3_set_register_bit(&priv_dev->regs->tdl_from_trb, - BIT(0) | BIT(16)); - } - writel(EP_CFG_ENABLE | EP_CFG_MAXPKTSIZE(max_packet_size), ®s->ep_cfg); diff --git a/drivers/usb/cdns3/gadget-export.h b/drivers/usb/cdns3/gadget-export.h index 577469eee961..7d8692409483 100644 --- a/drivers/usb/cdns3/gadget-export.h +++ b/drivers/usb/cdns3/gadget-export.h @@ -2,8 +2,7 @@ /* * Cadence USBSS DRD Driver - Gadget Export APIs. * - * Copyright (C) 2017 NXP - * Copyright (C) 2017-2018 NXP + * Copyright (C) 2017-2019 NXP * * Authors: Peter Chen <peter.chen@nxp.com> */ diff --git a/drivers/usb/cdns3/gadget.c b/drivers/usb/cdns3/gadget.c index 4c1e75509303..f34bd7de4fc5 100644 --- a/drivers/usb/cdns3/gadget.c +++ b/drivers/usb/cdns3/gadget.c @@ -3,11 +3,11 @@ * Cadence USBSS DRD Driver - gadget side. * * Copyright (C) 2018-2019 Cadence Design Systems. - * Copyright (C) 2017-2018 NXP + * Copyright (C) 2017-2019 NXP * * Authors: Pawel Jez <pjez@cadence.com>, * Pawel Laszczak <pawell@cadence.com> - * Peter Chen <peter.chen@nxp.com> + * Peter Chen <peter.chen@nxp.com> */ /* @@ -26,8 +26,6 @@ * as valid during adding next TRB only if DMA is stopped or at TRBERR * interrupt. * - * Issue has been fixed in DEV_VER_V3 version of controller. - * * Work around 2: * Controller for OUT endpoints has shared on-chip buffers for all incoming * packets, including ep0out. It's FIFO buffer, so packets must be handle by DMA @@ -45,32 +43,44 @@ * use this workaround only for these endpoint. * * Driver use limited number of buffer. This number can be set by macro - * CDNS3_WA2_NUM_BUFFERS. + * CDNS_WA2_NUM_BUFFERS. * * Such blocking situation was observed on ACM gadget. For this function * host send OUT data packet but ACM function is not prepared for this packet. * It's cause that buffer placed in on chip memory block transfer to other * endpoints. * - * Issue has been fixed in DEV_VER_V2 version of controller. + * It's limitation of controller but maybe this issues should be fixed in + * function driver. * + * This work around can be disabled/enabled by means of quirk_internal_buffer + * module parameter. By default feature is enabled. It can has impact to + * transfer performance and in most case this feature can be disabled. */ #include <linux/dma-mapping.h> +#include <linux/interrupt.h> #include <linux/usb/gadget.h> +#include <linux/pm_runtime.h> #include <linux/module.h> -#include <linux/iopoll.h> #include "core.h" #include "gadget-export.h" #include "gadget.h" #include "trace.h" -#include "drd.h" static int __cdns3_gadget_ep_queue(struct usb_ep *ep, struct usb_request *request, gfp_t gfp_flags); +/* + * Parameter allows to disable/enable handling of work around 2 feature. + * By default this value is enabled. + */ +static bool quirk_internal_buffer = 1; +module_param(quirk_internal_buffer, bool, 0644); +MODULE_PARM_DESC(quirk_internal_buffer, "Disable/enable WA2 algorithm"); + /** * cdns3_set_register_bit - set bit in given register. * @ptr: address of device controller register to be read and changed @@ -93,16 +103,6 @@ u8 cdns3_ep_addr_to_index(u8 ep_addr) return (((ep_addr & 0x7F)) + ((ep_addr & USB_DIR_IN) ? 16 : 0)); } -static int cdns3_get_dma_pos(struct cdns3_device *priv_dev, - struct cdns3_endpoint *priv_ep) -{ - int dma_index; - - dma_index = readl(&priv_dev->regs->ep_traddr) - priv_ep->trb_pool_dma; - - return dma_index / TRB_SIZE; -} - /** * cdns3_next_request - returns next request from list * @list: list containing requests @@ -115,7 +115,7 @@ struct usb_request *cdns3_next_request(struct list_head *list) } /** - * cdns3_next_align_buf - returns next buffer from list + * cdns3_aligned_buf - returns next buffer from list * @list: list containing buffers * * Returns buffer or NULL if no buffers in list @@ -184,9 +184,9 @@ int cdns3_allocate_trb_pool(struct cdns3_endpoint *priv_ep) if (!priv_ep->trb_pool) { priv_ep->trb_pool = dma_alloc_coherent(priv_dev->sysdev, - ring_size, - &priv_ep->trb_pool_dma, - GFP_DMA32 | GFP_ATOMIC); + ring_size, + &priv_ep->trb_pool_dma, + GFP_DMA); if (!priv_ep->trb_pool) return -ENOMEM; } else { @@ -197,10 +197,11 @@ int cdns3_allocate_trb_pool(struct cdns3_endpoint *priv_ep) return 0; priv_ep->num_trbs = ring_size / TRB_SIZE; - /* Initialize the last TRB as Link TRB. */ + /* Initialize the last TRB as Link TRB */ link_trb = (priv_ep->trb_pool + (priv_ep->num_trbs - 1)); link_trb->buffer = TRB_BUFFER(priv_ep->trb_pool_dma); - link_trb->control = TRB_CYCLE | TRB_TYPE(TRB_LINK) | TRB_TOGGLE; + link_trb->control = TRB_CYCLE | TRB_TYPE(TRB_LINK) | + TRB_CHAIN | TRB_TOGGLE; return 0; } @@ -226,18 +227,16 @@ static void cdns3_free_trb_pool(struct cdns3_endpoint *priv_ep) static void cdns3_ep_stall_flush(struct cdns3_endpoint *priv_ep) { struct cdns3_device *priv_dev = priv_ep->cdns3_dev; - int val; - trace_cdns3_halt(priv_ep, 1, 1); + cdns3_dbg(priv_ep->cdns3_dev, "Stall & flush endpoint %s\n", + priv_ep->name); writel(EP_CMD_DFLUSH | EP_CMD_ERDY | EP_CMD_SSTALL, &priv_dev->regs->ep_cmd); /* wait for DFLUSH cleared */ - readl_poll_timeout_atomic(&priv_dev->regs->ep_cmd, val, - !(val & EP_CMD_DFLUSH), 1, 1000); - priv_ep->flags |= EP_STALLED; - priv_ep->flags &= ~EP_STALL_PENDING; + cdns3_handshake(&priv_dev->regs->ep_cmd, EP_CMD_DFLUSH, 0, 1000); + priv_ep->flags |= EP_STALL; } /** @@ -250,9 +249,8 @@ void cdns3_hw_reset_eps_config(struct cdns3_device *priv_dev) cdns3_allow_enable_l1(priv_dev, 0); priv_dev->hw_configured_flag = 0; - priv_dev->onchip_used_size = 0; + priv_dev->onchip_mem_allocated_size = 0; priv_dev->out_mem_is_allocated = 0; - priv_dev->wait_for_setup = 0; } /** @@ -354,11 +352,13 @@ enum usb_device_speed cdns3_get_speed(struct cdns3_device *priv_dev) static int cdns3_start_all_request(struct cdns3_device *priv_dev, struct cdns3_endpoint *priv_ep) { + struct cdns3_request *priv_req; struct usb_request *request; int ret = 0; while (!list_empty(&priv_ep->deferred_req_list)) { request = cdns3_next_request(&priv_ep->deferred_req_list); + priv_req = to_cdns3_request(request); ret = cdns3_ep_run_transfer(priv_ep, request); if (ret) @@ -373,36 +373,24 @@ static int cdns3_start_all_request(struct cdns3_device *priv_dev, return ret; } -/* - * WA2: Set flag for all not ISOC OUT endpoints. If this flag is set - * driver try to detect whether endpoint need additional internal - * buffer for unblocking on-chip FIFO buffer. This flag will be cleared - * if before first DESCMISS interrupt the DMA will be armed. - */ -#define cdns3_wa2_enable_detection(priv_dev, ep_priv, reg) do { \ - if (!priv_ep->dir && priv_ep->type != USB_ENDPOINT_XFER_ISOC) { \ - priv_ep->flags |= EP_QUIRK_EXTRA_BUF_DET; \ - (reg) |= EP_STS_EN_DESCMISEN; \ - } } while (0) - /** - * cdns3_wa2_descmiss_copy_data copy data from internal requests to - * request queued by class driver. + * cdns3_descmiss_copy_data copy data from internal requests to request queued + * by class driver. * @priv_ep: extended endpoint object * @request: request object */ -static void cdns3_wa2_descmiss_copy_data(struct cdns3_endpoint *priv_ep, - struct usb_request *request) +static void cdns3_descmiss_copy_data(struct cdns3_endpoint *priv_ep, + struct usb_request *request) { struct usb_request *descmiss_req; struct cdns3_request *descmiss_priv_req; - while (!list_empty(&priv_ep->wa2_descmiss_req_list)) { + while (!list_empty(&priv_ep->descmiss_req_list)) { int chunk_end; int length; descmiss_priv_req = - cdns3_next_priv_request(&priv_ep->wa2_descmiss_req_list); + cdns3_next_priv_request(&priv_ep->descmiss_req_list); descmiss_req = &descmiss_priv_req->request; /* driver can't touch pending request */ @@ -412,8 +400,6 @@ static void cdns3_wa2_descmiss_copy_data(struct cdns3_endpoint *priv_ep, chunk_end = descmiss_priv_req->flags & REQUEST_INTERNAL_CH; length = request->actual + descmiss_req->actual; - request->status = descmiss_req->status; - if (length <= request->length) { memcpy(&((u8 *)request->buf)[request->actual], descmiss_req->buf, @@ -428,199 +414,12 @@ static void cdns3_wa2_descmiss_copy_data(struct cdns3_endpoint *priv_ep, kfree(descmiss_req->buf); cdns3_gadget_ep_free_request(&priv_ep->endpoint, descmiss_req); - --priv_ep->wa2_counter; if (!chunk_end) break; } } -struct usb_request *cdns3_wa2_gadget_giveback(struct cdns3_device *priv_dev, - struct cdns3_endpoint *priv_ep, - struct cdns3_request *priv_req) -{ - if (priv_ep->flags & EP_QUIRK_EXTRA_BUF_EN && - priv_req->flags & REQUEST_INTERNAL) { - struct usb_request *req; - - req = cdns3_next_request(&priv_ep->deferred_req_list); - - priv_ep->descmis_req = NULL; - - if (!req) - return NULL; - - cdns3_wa2_descmiss_copy_data(priv_ep, req); - if (!(priv_ep->flags & EP_QUIRK_END_TRANSFER) && - req->length != req->actual) { - /* wait for next part of transfer */ - return NULL; - } - - if (req->status == -EINPROGRESS) - req->status = 0; - - list_del_init(&req->list); - cdns3_start_all_request(priv_dev, priv_ep); - return req; - } - - return &priv_req->request; -} - -int cdns3_wa2_gadget_ep_queue(struct cdns3_device *priv_dev, - struct cdns3_endpoint *priv_ep, - struct cdns3_request *priv_req) -{ - int deferred = 0; - - /* - * If transfer was queued before DESCMISS appear than we - * can disable handling of DESCMISS interrupt. Driver assumes that it - * can disable special treatment for this endpoint. - */ - if (priv_ep->flags & EP_QUIRK_EXTRA_BUF_DET) { - u32 reg; - - cdns3_select_ep(priv_dev, priv_ep->num | priv_ep->dir); - priv_ep->flags &= ~EP_QUIRK_EXTRA_BUF_DET; - reg = readl(&priv_dev->regs->ep_sts_en); - reg &= ~EP_STS_EN_DESCMISEN; - trace_cdns3_wa2(priv_ep, "workaround disabled\n"); - writel(reg, &priv_dev->regs->ep_sts_en); - } - - if (priv_ep->flags & EP_QUIRK_EXTRA_BUF_EN) { - u8 pending_empty = list_empty(&priv_ep->pending_req_list); - u8 descmiss_empty = list_empty(&priv_ep->wa2_descmiss_req_list); - - /* - * DESCMISS transfer has been finished, so data will be - * directly copied from internal allocated usb_request - * objects. - */ - if (pending_empty && !descmiss_empty && - !(priv_req->flags & REQUEST_INTERNAL)) { - cdns3_wa2_descmiss_copy_data(priv_ep, - &priv_req->request); - - trace_cdns3_wa2(priv_ep, "get internal stored data"); - - list_add_tail(&priv_req->request.list, - &priv_ep->pending_req_list); - cdns3_gadget_giveback(priv_ep, priv_req, - priv_req->request.status); - - /* - * Intentionally driver returns positive value as - * correct value. It informs that transfer has - * been finished. - */ - return EINPROGRESS; - } - - /* - * Driver will wait for completion DESCMISS transfer, - * before starts new, not DESCMISS transfer. - */ - if (!pending_empty && !descmiss_empty) { - trace_cdns3_wa2(priv_ep, "wait for pending transfer\n"); - deferred = 1; - } - - if (priv_req->flags & REQUEST_INTERNAL) - list_add_tail(&priv_req->list, - &priv_ep->wa2_descmiss_req_list); - } - - return deferred; -} - -static void cdns3_wa2_remove_old_request(struct cdns3_endpoint *priv_ep) -{ - struct cdns3_request *priv_req; - - while (!list_empty(&priv_ep->wa2_descmiss_req_list)) { - u8 chain; - - priv_req = cdns3_next_priv_request(&priv_ep->wa2_descmiss_req_list); - chain = !!(priv_req->flags & REQUEST_INTERNAL_CH); - - trace_cdns3_wa2(priv_ep, "removes eldest request"); - - kfree(priv_req->request.buf); - cdns3_gadget_ep_free_request(&priv_ep->endpoint, - &priv_req->request); - list_del_init(&priv_req->list); - --priv_ep->wa2_counter; - - if (!chain) - break; - } -} - -/** - * cdns3_wa2_descmissing_packet - handles descriptor missing event. - * @priv_dev: extended gadget object - * - * This function is used only for WA2. For more information see Work around 2 - * description. - */ -static void cdns3_wa2_descmissing_packet(struct cdns3_endpoint *priv_ep) -{ - struct cdns3_request *priv_req; - struct usb_request *request; - - if (priv_ep->flags & EP_QUIRK_EXTRA_BUF_DET) { - priv_ep->flags &= ~EP_QUIRK_EXTRA_BUF_DET; - priv_ep->flags |= EP_QUIRK_EXTRA_BUF_EN; - } - - trace_cdns3_wa2(priv_ep, "Description Missing detected\n"); - - if (priv_ep->wa2_counter >= CDNS3_WA2_NUM_BUFFERS) - cdns3_wa2_remove_old_request(priv_ep); - - request = cdns3_gadget_ep_alloc_request(&priv_ep->endpoint, - GFP_ATOMIC); - if (!request) - goto err; - - priv_req = to_cdns3_request(request); - priv_req->flags |= REQUEST_INTERNAL; - - /* if this field is still assigned it indicate that transfer related - * with this request has not been finished yet. Driver in this - * case simply allocate next request and assign flag REQUEST_INTERNAL_CH - * flag to previous one. It will indicate that current request is - * part of the previous one. - */ - if (priv_ep->descmis_req) - priv_ep->descmis_req->flags |= REQUEST_INTERNAL_CH; - - priv_req->request.buf = kzalloc(CDNS3_DESCMIS_BUF_SIZE, - GFP_ATOMIC); - priv_ep->wa2_counter++; - - if (!priv_req->request.buf) { - cdns3_gadget_ep_free_request(&priv_ep->endpoint, request); - goto err; - } - - priv_req->request.length = CDNS3_DESCMIS_BUF_SIZE; - priv_ep->descmis_req = priv_req; - - __cdns3_gadget_ep_queue(&priv_ep->endpoint, - &priv_ep->descmis_req->request, - GFP_ATOMIC); - - return; - -err: - dev_err(priv_ep->cdns3_dev->dev, - "Failed: No sufficient memory for DESCMIS\n"); -} - /** * cdns3_gadget_giveback - call struct usb_request's ->complete callback * @priv_ep: The endpoint to whom the request belongs to @@ -654,13 +453,36 @@ void cdns3_gadget_giveback(struct cdns3_endpoint *priv_ep, priv_req->flags &= ~(REQUEST_PENDING | REQUEST_UNALIGNED); trace_cdns3_gadget_giveback(priv_req); - if (priv_dev->dev_ver < DEV_VER_V2) { - request = cdns3_wa2_gadget_giveback(priv_dev, priv_ep, - priv_req); - if (!request) + /* WA2: */ + if (priv_ep->flags & EP_QUIRK_EXTRA_BUF_EN && + priv_req->flags & REQUEST_INTERNAL) { + struct usb_request *req; + + req = cdns3_next_request(&priv_ep->deferred_req_list); + request = req; + priv_ep->descmis_req = NULL; + + if (!req) return; + + cdns3_descmiss_copy_data(priv_ep, req); + if (!(priv_ep->flags & EP_QUIRK_END_TRANSFER) && + req->length != req->actual) { + /* wait for next part of transfer */ + return; + } + + if (req->status == -EINPROGRESS) + req->status = 0; + + list_del_init(&req->list); + cdns3_start_all_request(priv_dev, priv_ep); } + /* Start all not pending request */ + if (priv_ep->flags & EP_RING_FULL) + cdns3_start_all_request(priv_dev, priv_ep); + if (request->complete) { spin_unlock(&priv_dev->lock); usb_gadget_giveback_request(&priv_ep->endpoint, @@ -674,10 +496,11 @@ void cdns3_gadget_giveback(struct cdns3_endpoint *priv_ep, void cdns3_wa1_restore_cycle_bit(struct cdns3_endpoint *priv_ep) { + struct cdns3_device *priv_dev = priv_ep->cdns3_dev; + /* Work around for stale data address in TRB*/ if (priv_ep->wa1_set) { - trace_cdns3_wa1(priv_ep, "restore cycle bit"); - + cdns3_dbg(priv_dev, "WA1: update cycle bit\n"); priv_ep->wa1_set = 0; priv_ep->wa1_trb_index = 0xFFFF; if (priv_ep->wa1_cycle_bit) { @@ -690,35 +513,6 @@ void cdns3_wa1_restore_cycle_bit(struct cdns3_endpoint *priv_ep) } } -static void cdns3_free_aligned_request_buf(struct work_struct *work) -{ - struct cdns3_device *priv_dev = container_of(work, struct cdns3_device, - aligned_buf_wq); - struct cdns3_aligned_buf *buf, *tmp; - unsigned long flags; - - spin_lock_irqsave(&priv_dev->lock, flags); - - list_for_each_entry_safe(buf, tmp, &priv_dev->aligned_buf_list, list) { - if (!buf->in_use) { - list_del(&buf->list); - - /* - * Re-enable interrupts to free DMA capable memory. - * Driver can't free this memory with disabled - * interrupts. - */ - spin_unlock_irqrestore(&priv_dev->lock, flags); - dma_free_coherent(priv_dev->sysdev, buf->size, - buf->buf, buf->dma); - kfree(buf); - spin_lock_irqsave(&priv_dev->lock, flags); - } - } - - spin_unlock_irqrestore(&priv_dev->lock, flags); -} - static int cdns3_prepare_aligned_request_buf(struct cdns3_request *priv_req) { struct cdns3_endpoint *priv_ep = priv_req->priv_ep; @@ -750,8 +544,7 @@ static int cdns3_prepare_aligned_request_buf(struct cdns3_request *priv_req) if (priv_req->aligned_buf) { trace_cdns3_free_aligned_request(priv_req); priv_req->aligned_buf->in_use = 0; - queue_work(system_freezable_wq, - &priv_dev->aligned_buf_wq); + priv_dev->run_garbage_colector = 1; } buf->in_use = 1; @@ -772,41 +565,6 @@ static int cdns3_prepare_aligned_request_buf(struct cdns3_request *priv_req) return 0; } -static int cdns3_wa1_update_guard(struct cdns3_endpoint *priv_ep, - struct cdns3_trb *trb) -{ - struct cdns3_device *priv_dev = priv_ep->cdns3_dev; - - if (!priv_ep->wa1_set) { - u32 doorbell; - - doorbell = !!(readl(&priv_dev->regs->ep_cmd) & EP_CMD_DRDY); - - if (doorbell) { - priv_ep->wa1_cycle_bit = priv_ep->pcs ? TRB_CYCLE : 0; - priv_ep->wa1_set = 1; - priv_ep->wa1_trb = trb; - priv_ep->wa1_trb_index = priv_ep->enqueue; - trace_cdns3_wa1(priv_ep, "set guard"); - return 0; - } - } - return 1; -} - -static void cdns3_wa1_tray_restore_cycle_bit(struct cdns3_device *priv_dev, - struct cdns3_endpoint *priv_ep) -{ - int dma_index; - u32 doorbell; - - doorbell = !!(readl(&priv_dev->regs->ep_cmd) & EP_CMD_DRDY); - dma_index = cdns3_get_dma_pos(priv_dev, priv_ep); - - if (!doorbell || dma_index != priv_ep->wa1_trb_index) - cdns3_wa1_restore_cycle_bit(priv_ep); -} - /** * cdns3_ep_run_transfer - start transfer on no-default endpoint hardware * @priv_ep: endpoint object @@ -820,8 +578,11 @@ int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep, struct cdns3_request *priv_req; struct cdns3_trb *trb; dma_addr_t trb_dma; + int prev_enqueue; u32 togle_pcs = 1; int sg_iter = 0; + int dma_index; + u32 doorbell; int num_trb; int address; u32 control; @@ -851,68 +612,54 @@ int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep, trb = priv_ep->trb_pool + priv_ep->enqueue; priv_req->start_trb = priv_ep->enqueue; priv_req->trb = trb; - - cdns3_select_ep(priv_ep->cdns3_dev, address); + prev_enqueue = priv_ep->enqueue; /* prepare ring */ if ((priv_ep->enqueue + num_trb) >= (priv_ep->num_trbs - 1)) { - struct cdns3_trb *link_trb; - int doorbell, dma_index; - u32 ch_bit = 0; - - doorbell = !!(readl(&priv_dev->regs->ep_cmd) & EP_CMD_DRDY); - dma_index = cdns3_get_dma_pos(priv_dev, priv_ep); - - /* Driver can't update LINK TRB if it is current processed. */ - if (doorbell && dma_index == priv_ep->num_trbs - 1) { - priv_ep->flags |= EP_DEFERRED_DRDY; - return -ENOBUFS; - } - /*updating C bt in Link TRB before starting DMA*/ - link_trb = priv_ep->trb_pool + (priv_ep->num_trbs - 1); - /* - * For TRs size equal 2 enabling TRB_CHAIN for epXin causes - * that DMA stuck at the LINK TRB. - * On the other hand, removing TRB_CHAIN for longer TRs for - * epXout cause that DMA stuck after handling LINK TRB. - * To eliminate this strange behavioral driver set TRB_CHAIN - * bit only for TR size > 2. - */ - if (priv_ep->type == USB_ENDPOINT_XFER_ISOC || - TRBS_PER_SEGMENT > 2) - ch_bit = TRB_CHAIN; - + struct cdns3_trb *link_trb = priv_ep->trb_pool + + (priv_ep->num_trbs - 1); link_trb->control = ((priv_ep->pcs) ? TRB_CYCLE : 0) | - TRB_TYPE(TRB_LINK) | TRB_TOGGLE | ch_bit; + TRB_TYPE(TRB_LINK) | TRB_CHAIN | + TRB_TOGGLE; } - if (priv_dev->dev_ver <= DEV_VER_V2) - togle_pcs = cdns3_wa1_update_guard(priv_ep, trb); + /* arm transfer on selected endpoint */ + cdns3_select_ep(priv_ep->cdns3_dev, address); + + doorbell = !!(readl(&priv_dev->regs->ep_cmd) & EP_CMD_DRDY); + + if (!priv_ep->wa1_set) { + if (doorbell) { + priv_ep->wa1_cycle_bit = priv_ep->pcs ? TRB_CYCLE : 0; + priv_ep->wa1_set = 1; + priv_ep->wa1_trb = trb; + priv_ep->wa1_trb_index = priv_ep->enqueue; + togle_pcs = 0; + cdns3_dbg(priv_dev, "WA1 set guard\n"); + } + } /* set incorrect Cycle Bit for first trb*/ control = priv_ep->pcs ? 0 : TRB_CYCLE; - do { u32 length; - u16 td_size = 0; + u8 td_size = 0; /* fill TRB */ control |= TRB_TYPE(TRB_NORMAL); trb->buffer = TRB_BUFFER(request->num_sgs == 0 ? trb_dma : request->sg[sg_iter].dma_address); - if (likely(!request->num_sgs)) length = request->length; else length = request->sg[sg_iter].length; - if (likely(priv_dev->dev_ver >= DEV_VER_V2)) + if (priv_dev->dev_ver == DEV_VER_V2) td_size = DIV_ROUND_UP(length, priv_ep->endpoint.maxpacket); - trb->length = TRB_BURST_LEN(priv_ep->trb_burst_size) | - TRB_LEN(length); + trb->length = TRB_BURST_LEN(16) | TRB_LEN(length); if (priv_dev->gadget.speed == USB_SPEED_SUPER) trb->length |= TRB_TDL_SS_SIZE(td_size); else @@ -935,12 +682,9 @@ int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep, control |= pcs | TRB_IOC | TRB_ISP; } - if (sg_iter) - trb->control = control; - else - priv_req->trb->control = control; - + trb->control = control; control = 0; + ++sg_iter; priv_req->end_trb = priv_ep->enqueue; cdns3_ep_inc_enq(priv_ep); @@ -951,6 +695,7 @@ int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep, priv_req->flags |= REQUEST_PENDING; + /* give the TD to the consumer*/ if (sg_iter == 1) trb->control |= TRB_IOC | TRB_ISP; @@ -959,12 +704,18 @@ int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep, */ wmb(); - /* give the TD to the consumer*/ if (togle_pcs) - trb->control = trb->control ^ 1; + trb->control = trb->control ^ 1; - if (priv_dev->dev_ver <= DEV_VER_V2) - cdns3_wa1_tray_restore_cycle_bit(priv_dev, priv_ep); + doorbell = !!(readl(&priv_dev->regs->ep_cmd) & EP_CMD_DRDY); + dma_index = (readl(&priv_dev->regs->ep_traddr) - + priv_ep->trb_pool_dma) / TRB_SIZE; + + cdns3_dbg(priv_dev, "dorbel %d, dma_index %d, prev_enqueu %d", + doorbell, dma_index, prev_enqueue); + + if (!doorbell || dma_index != priv_ep->wa1_trb_index) + cdns3_wa1_restore_cycle_bit(priv_ep); trace_cdns3_prepare_trb(priv_ep, priv_req->trb); @@ -979,26 +730,17 @@ int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep, * enabling endpoint. */ if (priv_ep->flags & EP_UPDATE_EP_TRBADDR) { - /* - * Until SW is not ready to handle the OUT transfer the ISO OUT - * Endpoint should be disabled (EP_CFG.ENABLE = 0). - * EP_CFG_ENABLE must be set before updating ep_traddr. - */ - if (priv_ep->type == USB_ENDPOINT_XFER_ISOC && !priv_ep->dir && - !(priv_ep->flags & EP_QUIRK_ISO_OUT_EN)) { - priv_ep->flags |= EP_QUIRK_ISO_OUT_EN; - cdns3_set_register_bit(&priv_dev->regs->ep_cfg, - EP_CFG_ENABLE); - } - writel(EP_TRADDR_TRADDR(priv_ep->trb_pool_dma + priv_req->start_trb * TRB_SIZE), - &priv_dev->regs->ep_traddr); + &priv_dev->regs->ep_traddr); + + cdns3_dbg(priv_ep->cdns3_dev, "Update ep_trbaddr for %s to %08x\n", + priv_ep->name, readl(&priv_dev->regs->ep_traddr)); priv_ep->flags &= ~EP_UPDATE_EP_TRBADDR; } - if (!priv_ep->wa1_set && !(priv_ep->flags & EP_STALLED)) { + if (!priv_ep->wa1_set && !(priv_ep->flags & EP_STALL)) { trace_cdns3_ring(priv_ep); /*clearing TRBERR and EP_STS_DESCMIS before seting DRDY*/ writel(EP_STS_TRBERR | EP_STS_DESCMIS, &priv_dev->regs->ep_sts); @@ -1007,9 +749,6 @@ int cdns3_ep_run_transfer(struct cdns3_endpoint *priv_ep, readl(&priv_dev->regs->ep_traddr)); } - /* WORKAROUND for transition to L0 */ - __cdns3_gadget_wakeup(priv_dev); - return 0; } @@ -1017,7 +756,7 @@ void cdns3_set_hw_configuration(struct cdns3_device *priv_dev) { struct cdns3_endpoint *priv_ep; struct usb_ep *ep; - int val; + int result = 0; if (priv_dev->hw_configured_flag) return; @@ -1029,10 +768,11 @@ void cdns3_set_hw_configuration(struct cdns3_device *priv_dev) USB_CONF_U1EN | USB_CONF_U2EN); /* wait until configuration set */ - readl_poll_timeout_atomic(&priv_dev->regs->usb_sts, val, - val & USB_STS_CFGSTS_MASK, 1, 100); + result = cdns3_handshake(&priv_dev->regs->usb_sts, + USB_STS_CFGSTS_MASK, 1, 100); priv_dev->hw_configured_flag = 1; +// cdns3_allow_enable_l1(priv_dev, 1); list_for_each_entry(ep, &priv_dev->gadget.ep_list, ep_list) { if (ep->enabled) { @@ -1066,9 +806,6 @@ void cdns3_set_hw_configuration(struct cdns3_device *priv_dev) * some rules: * 1. priv_ep->dequeue never exceed current_index. * 2 priv_ep->enqueue never exceed priv_ep->dequeue - * 3. exception: priv_ep->enqueue == priv_ep->dequeue - * and priv_ep->free_trbs is zero. - * This case indicate that TR is full. * * Then We can split recognition into two parts: * Case 1 - priv_ep->dequeue < current_index @@ -1092,29 +829,16 @@ static bool cdns3_request_handled(struct cdns3_endpoint *priv_ep, struct cdns3_trb *trb = priv_req->trb; int current_index = 0; int handled = 0; - int doorbell; - current_index = cdns3_get_dma_pos(priv_dev, priv_ep); - doorbell = !!(readl(&priv_dev->regs->ep_cmd) & EP_CMD_DRDY); + current_index = (readl(&priv_dev->regs->ep_traddr) - + priv_ep->trb_pool_dma) / TRB_SIZE; trb = &priv_ep->trb_pool[priv_req->start_trb]; if ((trb->control & TRB_CYCLE) != priv_ep->ccs) goto finish; - if (doorbell == 1 && current_index == priv_ep->dequeue) - goto finish; - - /* The corner case for TRBS_PER_SEGMENT equal 2). */ - if (TRBS_PER_SEGMENT == 2 && priv_ep->type != USB_ENDPOINT_XFER_ISOC) { - handled = 1; - goto finish; - } - - if (priv_ep->enqueue == priv_ep->dequeue && - priv_ep->free_trbs == 0) { - handled = 1; - } else if (priv_ep->dequeue < current_index) { + if (priv_ep->dequeue < current_index) { if ((current_index == (priv_ep->num_trbs - 1)) && !priv_ep->dequeue) goto finish; @@ -1145,21 +869,13 @@ static void cdns3_transfer_completed(struct cdns3_device *priv_dev, request = cdns3_next_request(&priv_ep->pending_req_list); priv_req = to_cdns3_request(request); - trb = priv_ep->trb_pool + priv_ep->dequeue; - - /* Request was dequeued and TRB was changed to TRB_LINK. */ - if (TRB_FIELD_TO_TYPE(trb->control) == TRB_LINK) { - trace_cdns3_complete_trb(priv_ep, trb); - cdns3_move_deq_to_next_trb(priv_req); - } - /* Re-select endpoint. It could be changed by other CPU during * handling usb_gadget_giveback_request. */ cdns3_select_ep(priv_dev, priv_ep->endpoint.address); if (!cdns3_request_handled(priv_ep, priv_req)) - goto prepare_next_td; + return; trb = priv_ep->trb_pool + priv_ep->dequeue; trace_cdns3_complete_trb(priv_ep, trb); @@ -1172,17 +888,8 @@ static void cdns3_transfer_completed(struct cdns3_device *priv_dev, request->actual = TRB_LEN(le32_to_cpu(trb->length)); cdns3_move_deq_to_next_trb(priv_req); cdns3_gadget_giveback(priv_ep, priv_req, 0); - - if (priv_ep->type != USB_ENDPOINT_XFER_ISOC && - TRBS_PER_SEGMENT == 2) - break; } priv_ep->flags &= ~EP_PENDING_REQUEST; - -prepare_next_td: - if (!(priv_ep->flags & EP_STALLED) && - !(priv_ep->flags & EP_STALL_PENDING)) - cdns3_start_all_request(priv_dev, priv_ep); } void cdns3_rearm_transfer(struct cdns3_endpoint *priv_ep, u8 rearm) @@ -1198,14 +905,65 @@ void cdns3_rearm_transfer(struct cdns3_endpoint *priv_ep, u8 rearm) wmb(); writel(EP_CMD_DRDY, &priv_dev->regs->ep_cmd); - __cdns3_gadget_wakeup(priv_dev); - trace_cdns3_doorbell_epx(priv_ep->name, readl(&priv_dev->regs->ep_traddr)); } } /** + * cdns3_descmissing_packet - handles descriptor missing event. + * @priv_dev: extended gadget object + * + * This function is used only for WA2. For more information see Work around 2 + * description. + */ +static int cdns3_descmissing_packet(struct cdns3_endpoint *priv_ep) +{ + struct cdns3_request *priv_req; + struct usb_request *request; + + if (priv_ep->flags & EP_QUIRK_EXTRA_BUF_DET) { + priv_ep->flags &= ~EP_QUIRK_EXTRA_BUF_DET; + priv_ep->flags |= EP_QUIRK_EXTRA_BUF_EN; + } + + cdns3_dbg(priv_ep->cdns3_dev, "WA2: Description Missing detected\n"); + + request = cdns3_gadget_ep_alloc_request(&priv_ep->endpoint, + GFP_ATOMIC); + if (!request) + return -ENOMEM; + + priv_req = to_cdns3_request(request); + priv_req->flags |= REQUEST_INTERNAL; + + /* if this field is still assigned it indicate that transfer related + * with this request has not been finished yet. Driver in this + * case simply allocate next request and assign flag REQUEST_INTERNAL_CH + * flag to previous one. It will indicate that current request is + * part of the previous one. + */ + if (priv_ep->descmis_req) + priv_ep->descmis_req->flags |= REQUEST_INTERNAL_CH; + + priv_req->request.buf = kzalloc(CDNS3_DESCMIS_BUF_SIZE, + GFP_ATOMIC); + if (!priv_req->request.buf) { + cdns3_gadget_ep_free_request(&priv_ep->endpoint, request); + return -ENOMEM; + } + + priv_req->request.length = CDNS3_DESCMIS_BUF_SIZE; + priv_ep->descmis_req = priv_req; + + __cdns3_gadget_ep_queue(&priv_ep->endpoint, + &priv_ep->descmis_req->request, + GFP_ATOMIC); + + return 0; +} + +/** * cdns3_check_ep_interrupt_proceed - Processes interrupt related to endpoint * @priv_ep: endpoint object * @@ -1224,12 +982,6 @@ static int cdns3_check_ep_interrupt_proceed(struct cdns3_endpoint *priv_ep) writel(ep_sts_reg, &priv_dev->regs->ep_sts); if (ep_sts_reg & EP_STS_TRBERR) { - if (priv_ep->flags & EP_STALL_PENDING && - !(ep_sts_reg & EP_STS_DESCMIS && - priv_dev->dev_ver < DEV_VER_V2)) { - cdns3_ep_stall_flush(priv_ep); - } - /* * For isochronous transfer driver completes request on * IOC or on TRBERR. IOC appears only when device receive @@ -1238,25 +990,10 @@ static int cdns3_check_ep_interrupt_proceed(struct cdns3_endpoint *priv_ep) * on TRBERR event. */ if (priv_ep->type == USB_ENDPOINT_XFER_ISOC && - !priv_ep->wa1_set) { - if (!priv_ep->dir) { - u32 ep_cfg = readl(&priv_dev->regs->ep_cfg); - - ep_cfg &= ~EP_CFG_ENABLE; - writel(ep_cfg, &priv_dev->regs->ep_cfg); - priv_ep->flags &= ~EP_QUIRK_ISO_OUT_EN; - } + !priv_ep->wa1_set) cdns3_transfer_completed(priv_dev, priv_ep); - } else if (!(priv_ep->flags & EP_STALLED) && - !(priv_ep->flags & EP_STALL_PENDING)) { - if (priv_ep->flags & EP_DEFERRED_DRDY) { - priv_ep->flags &= ~EP_DEFERRED_DRDY; - cdns3_start_all_request(priv_dev, priv_ep); - } else { - cdns3_rearm_transfer(priv_ep, - priv_ep->wa1_set); - } - } + else + cdns3_rearm_transfer(priv_ep, priv_ep->wa1_set); } if ((ep_sts_reg & EP_STS_IOC) || (ep_sts_reg & EP_STS_ISP)) { @@ -1276,20 +1013,16 @@ static int cdns3_check_ep_interrupt_proceed(struct cdns3_endpoint *priv_ep) * priv_ep->flags & EP_QUIRK_EXTRA_BUF_EN. * In other cases this interrupt will be disabled/ */ - if (ep_sts_reg & EP_STS_DESCMIS && priv_dev->dev_ver < DEV_VER_V2 && - !(priv_ep->flags & EP_STALLED)) - cdns3_wa2_descmissing_packet(priv_ep); + if (ep_sts_reg & EP_STS_DESCMIS) { + int err; - return 0; -} - -static void cdns3_disconnect_gadget(struct cdns3_device *priv_dev) -{ - if (priv_dev->gadget_driver && priv_dev->gadget_driver->disconnect) { - spin_unlock(&priv_dev->lock); - priv_dev->gadget_driver->disconnect(&priv_dev->gadget); - spin_lock(&priv_dev->lock); + err = cdns3_descmissing_packet(priv_ep); + if (err) + dev_err(priv_dev->dev, + "Failed: No sufficient memory for DESCMIS\n"); } + + return 0; } /** @@ -1304,16 +1037,6 @@ static void cdns3_check_usb_interrupt_proceed(struct cdns3_device *priv_dev, int speed = 0; trace_cdns3_usb_irq(priv_dev, usb_ists); - if (usb_ists & USB_ISTS_L1ENTI) { - /* - * WORKAROUND: CDNS3 controller has issue with hardware resuming - * from L1. To fix it, if any DMA transfer is pending driver - * must starts driving resume signal immediately. - */ - if (readl(&priv_dev->regs->drbl)) - __cdns3_gadget_wakeup(priv_dev); - } - /* Connection detected */ if (usb_ists & (USB_ISTS_CON2I | USB_ISTS_CONI)) { speed = cdns3_get_speed(priv_dev); @@ -1324,63 +1047,56 @@ static void cdns3_check_usb_interrupt_proceed(struct cdns3_device *priv_dev, /* Disconnection detected */ if (usb_ists & (USB_ISTS_DIS2I | USB_ISTS_DISI)) { - cdns3_disconnect_gadget(priv_dev); - priv_dev->gadget.speed = USB_SPEED_UNKNOWN; - usb_gadget_set_state(&priv_dev->gadget, USB_STATE_NOTATTACHED); - cdns3_hw_reset_eps_config(priv_dev); - } - - if (usb_ists & (USB_ISTS_L2ENTI | USB_ISTS_U3ENTI)) { if (priv_dev->gadget_driver && - priv_dev->gadget_driver->suspend) { + priv_dev->gadget_driver->disconnect && + priv_dev->gadget.state == + USB_STATE_CONFIGURED) { spin_unlock(&priv_dev->lock); - priv_dev->gadget_driver->suspend(&priv_dev->gadget); + priv_dev->gadget_driver->disconnect(&priv_dev->gadget); spin_lock(&priv_dev->lock); } - } - if (usb_ists & (USB_ISTS_L2EXTI | USB_ISTS_U3EXTI)) { - if (priv_dev->gadget_driver && - priv_dev->gadget_driver->resume) { - spin_unlock(&priv_dev->lock); - priv_dev->gadget_driver->resume(&priv_dev->gadget); - spin_lock(&priv_dev->lock); - } + priv_dev->gadget.speed = USB_SPEED_UNKNOWN; + usb_gadget_set_state(&priv_dev->gadget, USB_STATE_NOTATTACHED); + cdns3_hw_reset_eps_config(priv_dev); } /* reset*/ if (usb_ists & (USB_ISTS_UWRESI | USB_ISTS_UHRESI | USB_ISTS_U2RESI)) { - if (priv_dev->gadget_driver) { + if (priv_dev->gadget_driver && + priv_dev->gadget_driver->reset && + priv_dev->gadget.state == + USB_STATE_CONFIGURED) { spin_unlock(&priv_dev->lock); - usb_gadget_udc_reset(&priv_dev->gadget, - priv_dev->gadget_driver); + priv_dev->gadget_driver->reset(&priv_dev->gadget); spin_lock(&priv_dev->lock); - - /*read again to check the actual speed*/ - speed = cdns3_get_speed(priv_dev); - priv_dev->gadget.speed = speed; - cdns3_hw_reset_eps_config(priv_dev); - cdns3_ep0_config(priv_dev); } + + /*read again to check the actual speed*/ + speed = cdns3_get_speed(priv_dev); + usb_gadget_set_state(&priv_dev->gadget, USB_STATE_DEFAULT); + priv_dev->gadget.speed = speed; + cdns3_hw_reset_eps_config(priv_dev); + cdns3_ep0_config(priv_dev); } } /** * cdns3_device_irq_handler- interrupt handler for device part of controller * - * @irq: irq number for cdns3 core device - * @data: structure of cdns3 + * @cdns: structure of cdns3 * * Returns IRQ_HANDLED or IRQ_NONE */ -static irqreturn_t cdns3_device_irq_handler(int irq, void *data) +static irqreturn_t cdns3_device_irq_handler(struct cdns3 *cdns) { struct cdns3_device *priv_dev; - struct cdns3 *cdns = data; irqreturn_t ret = IRQ_NONE; + unsigned long flags; u32 reg; priv_dev = cdns->gadget_dev; + spin_lock_irqsave(&priv_dev->lock, flags); /* check USB device interrupt */ reg = readl(&priv_dev->regs->usb_ists); @@ -1400,11 +1116,16 @@ static irqreturn_t cdns3_device_irq_handler(int irq, void *data) /* check endpoint interrupt */ reg = readl(&priv_dev->regs->ep_ists); + if (reg) { - writel(0, &priv_dev->regs->ep_ien); + priv_dev->shadow_ep_en |= reg; + reg = ~reg & readl(&priv_dev->regs->ep_ien); + /* mask deferred interrupt. */ + writel(reg, &priv_dev->regs->ep_ien); ret = IRQ_WAKE_THREAD; } + spin_unlock_irqrestore(&priv_dev->lock, flags); return ret; } @@ -1417,12 +1138,12 @@ static irqreturn_t cdns3_device_irq_handler(int irq, void *data) * * Returns IRQ_HANDLED or IRQ_NONE */ -static irqreturn_t cdns3_device_thread_irq_handler(int irq, void *data) +static irqreturn_t cdns3_device_thread_irq_handler(struct cdns3 *cdns) { struct cdns3_device *priv_dev; - struct cdns3 *cdns = data; irqreturn_t ret = IRQ_NONE; unsigned long flags; + u32 ep_ien; int bit; u32 reg; @@ -1458,14 +1179,38 @@ static irqreturn_t cdns3_device_thread_irq_handler(int irq, void *data) for_each_set_bit(bit, (unsigned long *)®, sizeof(u32) * BITS_PER_BYTE) { + priv_dev->shadow_ep_en |= BIT(bit); cdns3_check_ep_interrupt_proceed(priv_dev->eps[bit]); ret = IRQ_HANDLED; } + if (priv_dev->run_garbage_colector) { + struct cdns3_aligned_buf *buf, *tmp; + + list_for_each_entry_safe(buf, tmp, &priv_dev->aligned_buf_list, + list) { + if (!buf->in_use) { + list_del(&buf->list); + + spin_unlock_irqrestore(&priv_dev->lock, flags); + dma_free_coherent(priv_dev->sysdev, buf->size, + buf->buf, + buf->dma); + spin_lock_irqsave(&priv_dev->lock, flags); + + kfree(buf); + } + } + + priv_dev->run_garbage_colector = 0; + } + irqend: - writel(~0, &priv_dev->regs->ep_ien); + ep_ien = readl(&priv_dev->regs->ep_ien) | priv_dev->shadow_ep_en; + priv_dev->shadow_ep_en = 0; + /* Unmask all handled EP interrupts */ + writel(ep_ien, &priv_dev->regs->ep_ien); spin_unlock_irqrestore(&priv_dev->lock, flags); - return ret; } @@ -1477,76 +1222,26 @@ irqend: * * @priv_dev: extended gadget object * @size: the size (KB) for EP would like to allocate - * @is_in: endpoint direction * * Return 0 if the required size can met or negative value on failure */ +#define CDNS3_ONCHIP_BUF_SIZE 16 static int cdns3_ep_onchip_buffer_reserve(struct cdns3_device *priv_dev, int size, int is_in) { - int remained; - - /* 2KB are reserved for EP0*/ - remained = priv_dev->onchip_buffers - priv_dev->onchip_used_size - 2; - if (is_in) { - if (remained < size) - return -EPERM; - - priv_dev->onchip_used_size += size; - } else { - int required; - - /** - * ALL OUT EPs are shared the same chunk onchip memory, so - * driver checks if it already has assigned enough buffers - */ - if (priv_dev->out_mem_is_allocated >= size) - return 0; - - required = size - priv_dev->out_mem_is_allocated; - - if (required > remained) - return -EPERM; - - priv_dev->out_mem_is_allocated += required; - priv_dev->onchip_used_size += required; + priv_dev->onchip_mem_allocated_size += size; + } else if (!priv_dev->out_mem_is_allocated) { + /* ALL OUT EPs are shared the same chunk onchip memory */ + priv_dev->onchip_mem_allocated_size += size; + priv_dev->out_mem_is_allocated = 1; } - return 0; -} - -void cdns3_configure_dmult(struct cdns3_device *priv_dev, - struct cdns3_endpoint *priv_ep) -{ - struct cdns3_usb_regs __iomem *regs = priv_dev->regs; - - /* For dev_ver > DEV_VER_V2 DMULT is configured per endpoint */ - if (priv_dev->dev_ver <= DEV_VER_V2) - writel(USB_CONF_DMULT, ®s->usb_conf); - - if (priv_dev->dev_ver == DEV_VER_V2) - writel(USB_CONF2_EN_TDL_TRB, ®s->usb_conf2); - - if (priv_dev->dev_ver >= DEV_VER_V3 && priv_ep) { - u32 mask; - - if (priv_ep->dir) - mask = BIT(priv_ep->num + 16); - else - mask = BIT(priv_ep->num); - - if (priv_ep->type != USB_ENDPOINT_XFER_ISOC) { - cdns3_set_register_bit(®s->tdl_from_trb, mask); - cdns3_set_register_bit(®s->tdl_beh, mask); - cdns3_set_register_bit(®s->tdl_beh2, mask); - cdns3_set_register_bit(®s->dma_adv_td, mask); - } - - if (priv_ep->type == USB_ENDPOINT_XFER_ISOC && !priv_ep->dir) - cdns3_set_register_bit(®s->tdl_from_trb, mask); - - cdns3_set_register_bit(®s->dtrans, mask); + if (priv_dev->onchip_mem_allocated_size > CDNS3_ONCHIP_BUF_SIZE) { + priv_dev->onchip_mem_allocated_size -= size; + return -EPERM; + } else { + return 0; } } @@ -1560,30 +1255,21 @@ void cdns3_ep_config(struct cdns3_endpoint *priv_ep) struct cdns3_device *priv_dev = priv_ep->cdns3_dev; u32 bEndpointAddress = priv_ep->num | priv_ep->dir; u32 max_packet_size = 0; + u8 buffering; u8 maxburst = 0; u32 ep_cfg = 0; - u8 buffering; u8 mult = 0; int ret; buffering = CDNS3_EP_BUF_SIZE - 1; - cdns3_configure_dmult(priv_dev, priv_ep); - switch (priv_ep->type) { case USB_ENDPOINT_XFER_INT: - ep_cfg = EP_CFG_EPTYPE(USB_ENDPOINT_XFER_INT); - - if ((priv_dev->dev_ver == DEV_VER_V2 && !priv_ep->dir) || - priv_dev->dev_ver > DEV_VER_V2) - ep_cfg |= EP_CFG_TDL_CHK; - break; case USB_ENDPOINT_XFER_BULK: - ep_cfg = EP_CFG_EPTYPE(USB_ENDPOINT_XFER_BULK); - - if ((priv_dev->dev_ver == DEV_VER_V2 && !priv_ep->dir) || - priv_dev->dev_ver > DEV_VER_V2) + ep_cfg = EP_CFG_EPTYPE(priv_ep->type); + if (priv_dev->dev_ver == DEV_VER_V2 && !priv_ep->dir) ep_cfg |= EP_CFG_TDL_CHK; + break; default: ep_cfg = EP_CFG_EPTYPE(USB_ENDPOINT_XFER_ISOC); @@ -1625,8 +1311,8 @@ void cdns3_ep_config(struct cdns3_endpoint *priv_ep) else priv_ep->trb_burst_size = 16; - ret = cdns3_ep_onchip_buffer_reserve(priv_dev, buffering + 1, - !!priv_ep->dir); + ret = cdns3_ep_onchip_buffer_reserve(priv_dev, buffering, + !!priv_ep->dir); if (ret) { dev_err(priv_dev->dev, "onchip mem is full, ep is invalid\n"); return; @@ -1762,6 +1448,7 @@ void cdns3_gadget_ep_free_request(struct usb_ep *ep, priv_req->aligned_buf->in_use = 0; trace_cdns3_free_request(priv_req); + request = NULL; kfree(priv_req); } @@ -1780,9 +1467,7 @@ static int cdns3_gadget_ep_enable(struct usb_ep *ep, u32 reg = EP_STS_EN_TRBERREN; u32 bEndpointAddress; unsigned long flags; - int enable = 1; int ret; - int val; priv_ep = ep_to_cdns3_ep(ep); priv_dev = priv_ep->cdns3_dev; @@ -1828,42 +1513,30 @@ static int cdns3_gadget_ep_enable(struct usb_ep *ep, writel(EP_CMD_EPRST, &priv_dev->regs->ep_cmd); - ret = readl_poll_timeout_atomic(&priv_dev->regs->ep_cmd, val, - !(val & (EP_CMD_CSTALL | EP_CMD_EPRST)), - 1, 1000); - - if (unlikely(ret)) { - cdns3_free_trb_pool(priv_ep); - ret = -EINVAL; - goto exit; - } + ret = cdns3_handshake(&priv_dev->regs->ep_cmd, + EP_CMD_CSTALL | EP_CMD_EPRST, 0, 1000); /* enable interrupt for selected endpoint */ cdns3_set_register_bit(&priv_dev->regs->ep_ien, BIT(cdns3_ep_addr_to_index(bEndpointAddress))); - - if (priv_dev->dev_ver < DEV_VER_V2) - cdns3_wa2_enable_detection(priv_dev, priv_ep, reg); - - writel(reg, &priv_dev->regs->ep_sts_en); - /* - * For some versions of controller at some point during ISO OUT traffic - * DMA reads Transfer Ring for the EP which has never got doorbell. - * This issue was detected only on simulation, but to avoid this issue - * driver add protection against it. To fix it driver enable ISO OUT - * endpoint before setting DRBL. This special treatment of ISO OUT - * endpoints are recommended by controller specification. + * WA2: Set flag for all not ISOC OUT endpoints. If this flag is set + * driver try to detect whether endpoint need additional internal + * buffer for unblocking on-chip FIFO buffer. This flag will be cleared + * if before first DESCMISS interrupt the DMA will be armed. */ - if (priv_ep->type == USB_ENDPOINT_XFER_ISOC && !priv_ep->dir) - enable = 0; + if (quirk_internal_buffer && (priv_dev->dev_ver < DEV_VER_V2)) { + if (!priv_ep->dir && priv_ep->type != USB_ENDPOINT_XFER_ISOC) { + priv_ep->flags |= EP_QUIRK_EXTRA_BUF_DET; + reg |= EP_STS_EN_DESCMISEN; + } + } - if (enable) - cdns3_set_register_bit(&priv_dev->regs->ep_cfg, EP_CFG_ENABLE); + writel(reg, &priv_dev->regs->ep_sts_en); + + cdns3_set_register_bit(&priv_dev->regs->ep_cfg, EP_CFG_ENABLE); ep->desc = desc; - priv_ep->flags &= ~(EP_PENDING_REQUEST | EP_STALLED | EP_STALL_PENDING | - EP_QUIRK_ISO_OUT_EN | EP_QUIRK_EXTRA_BUF_EN); priv_ep->flags |= EP_ENABLED | EP_UPDATE_EP_TRBADDR; priv_ep->wa1_set = 0; priv_ep->enqueue = 0; @@ -1894,7 +1567,6 @@ static int cdns3_gadget_ep_disable(struct usb_ep *ep) unsigned long flags; int ret = 0; u32 ep_cfg; - int val; if (!ep) { pr_err("usbss: invalid parameters\n"); @@ -1904,10 +1576,10 @@ static int cdns3_gadget_ep_disable(struct usb_ep *ep) priv_ep = ep_to_cdns3_ep(ep); priv_dev = priv_ep->cdns3_dev; - if (dev_WARN_ONCE(priv_dev->dev, !(priv_ep->flags & EP_ENABLED), - "%s is already disabled\n", priv_ep->name)) + if (!(priv_ep->flags & EP_ENABLED) || (priv_ep->endpoint.desc == NULL)) return 0; + pm_runtime_get_sync(priv_dev->dev); spin_lock_irqsave(&priv_dev->lock, flags); trace_cdns3_gadget_ep_disable(priv_ep); @@ -1918,22 +1590,10 @@ static int cdns3_gadget_ep_disable(struct usb_ep *ep) ep_cfg &= ~EP_CFG_ENABLE; writel(ep_cfg, &priv_dev->regs->ep_cfg); - /** - * Driver needs some time before resetting endpoint. - * It need waits for clearing DBUSY bit or for timeout expired. - * 10us is enough time for controller to stop transfer. - */ - readl_poll_timeout_atomic(&priv_dev->regs->ep_sts, val, - !(val & EP_STS_DBUSY), 1, 10); writel(EP_CMD_EPRST, &priv_dev->regs->ep_cmd); - readl_poll_timeout_atomic(&priv_dev->regs->ep_cmd, val, - !(val & (EP_CMD_CSTALL | EP_CMD_EPRST)), - 1, 1000); - if (unlikely(ret)) - dev_err(priv_dev->dev, "Timeout: %s resetting failed.\n", - priv_ep->name); - + ret = cdns3_handshake(&priv_dev->regs->ep_cmd, + EP_CMD_CSTALL | EP_CMD_EPRST, 0, 1000); while (!list_empty(&priv_ep->pending_req_list)) { request = cdns3_next_request(&priv_ep->pending_req_list); @@ -1941,14 +1601,12 @@ static int cdns3_gadget_ep_disable(struct usb_ep *ep) -ESHUTDOWN); } - while (!list_empty(&priv_ep->wa2_descmiss_req_list)) { - priv_req = cdns3_next_priv_request(&priv_ep->wa2_descmiss_req_list); - + while (!list_empty(&priv_ep->descmiss_req_list)) { + priv_req = cdns3_next_priv_request(&priv_ep->descmiss_req_list); kfree(priv_req->request.buf); + list_del_init(&priv_req->list); cdns3_gadget_ep_free_request(&priv_ep->endpoint, &priv_req->request); - list_del_init(&priv_req->list); - --priv_ep->wa2_counter; } while (!list_empty(&priv_ep->deferred_req_list)) { @@ -1960,10 +1618,12 @@ static int cdns3_gadget_ep_disable(struct usb_ep *ep) priv_ep->descmis_req = NULL; - ep->desc = NULL; - priv_ep->flags &= ~EP_ENABLED; + priv_ep->endpoint.desc = NULL; + priv_ep->flags = 0; + priv_ep->flags |= EP_CLAIMED; spin_unlock_irqrestore(&priv_dev->lock, flags); + pm_runtime_put_sync(priv_dev->dev); return ret; } @@ -1983,6 +1643,7 @@ static int __cdns3_gadget_ep_queue(struct usb_ep *ep, struct cdns3_endpoint *priv_ep = ep_to_cdns3_ep(ep); struct cdns3_device *priv_dev = priv_ep->cdns3_dev; struct cdns3_request *priv_req; + int deferred = 0; int ret = 0; request->actual = 0; @@ -1990,12 +1651,51 @@ static int __cdns3_gadget_ep_queue(struct usb_ep *ep, priv_req = to_cdns3_request(request); trace_cdns3_ep_queue(priv_req); - if (priv_dev->dev_ver < DEV_VER_V2) { - ret = cdns3_wa2_gadget_ep_queue(priv_dev, priv_ep, - priv_req); + /* + * WA2: if transfer was queued before DESCMISS appear than we + * can disable handling of DESCMISS interrupt. Driver assumes that it + * can disable special treatment for this endpoint. + */ + if (priv_ep->flags & EP_QUIRK_EXTRA_BUF_DET) { + u32 reg; - if (ret == EINPROGRESS) - return 0; + cdns3_select_ep(priv_dev, priv_ep->num | priv_ep->dir); + priv_ep->flags &= ~EP_QUIRK_EXTRA_BUF_DET; + reg = readl(&priv_dev->regs->ep_sts_en); + reg &= ~EP_STS_EN_DESCMISEN; + writel(reg, &priv_dev->regs->ep_sts_en); + } + + /* WA2 */ + if (priv_ep->flags & EP_QUIRK_EXTRA_BUF_EN) { + u8 pending_empty = list_empty(&priv_ep->pending_req_list); + u8 descmiss_empty = list_empty(&priv_ep->descmiss_req_list); + + /* + * DESCMISS transfer has been finished, so data will be + * directly copied from internal allocated usb_request + * objects. + */ + if (pending_empty && !descmiss_empty && + !(priv_req->flags & REQUEST_INTERNAL)) { + cdns3_descmiss_copy_data(priv_ep, request); + list_add_tail(&request->list, + &priv_ep->pending_req_list); + cdns3_gadget_giveback(priv_ep, priv_req, + request->status); + return ret; + } + + /* + * WA2 driver will wait for completion DESCMISS transfer, + * before starts new, not DESCMISS transfer. + */ + if (!pending_empty && !descmiss_empty) + deferred = 1; + + if (priv_req->flags & REQUEST_INTERNAL) + list_add_tail(&priv_req->list, + &priv_ep->descmiss_req_list); } ret = cdns3_prepare_aligned_request_buf(priv_req); @@ -2007,18 +1707,22 @@ static int __cdns3_gadget_ep_queue(struct usb_ep *ep, if (ret) return ret; - list_add_tail(&request->list, &priv_ep->deferred_req_list); - /* * If hardware endpoint configuration has not been set yet then * just queue request in deferred list. Transfer will be started in * cdns3_set_hw_configuration. */ - if (priv_dev->hw_configured_flag && !(priv_ep->flags & EP_STALLED) && - !(priv_ep->flags & EP_STALL_PENDING)) - cdns3_start_all_request(priv_dev, priv_ep); + if (!priv_dev->hw_configured_flag) + deferred = 1; + else + ret = cdns3_ep_run_transfer(priv_ep, request); - return 0; + if (ret || deferred) + list_add_tail(&request->list, &priv_ep->deferred_req_list); + else + list_add_tail(&request->list, &priv_ep->pending_req_list); + + return ret; } static int cdns3_gadget_ep_queue(struct usb_ep *ep, struct usb_request *request, @@ -2036,6 +1740,9 @@ static int cdns3_gadget_ep_queue(struct usb_ep *ep, struct usb_request *request, priv_ep = ep_to_cdns3_ep(ep); priv_dev = priv_ep->cdns3_dev; + if (!priv_ep->endpoint.desc) + return -EINVAL; + spin_lock_irqsave(&priv_dev->lock, flags); ret = __cdns3_gadget_ep_queue(ep, request, gfp_flags); @@ -2075,7 +1782,6 @@ int cdns3_gadget_ep_dequeue(struct usb_ep *ep, struct usb_request *req, *req_temp; struct cdns3_request *priv_req; struct cdns3_trb *link_trb; - u8 req_on_hw_ring = 0; unsigned long flags; int ret = 0; @@ -2092,10 +1798,8 @@ int cdns3_gadget_ep_dequeue(struct usb_ep *ep, list_for_each_entry_safe(req, req_temp, &priv_ep->pending_req_list, list) { - if (request == req) { - req_on_hw_ring = 1; + if (request == req) goto found; - } } list_for_each_entry_safe(req, req_temp, &priv_ep->deferred_req_list, @@ -2107,77 +1811,32 @@ int cdns3_gadget_ep_dequeue(struct usb_ep *ep, goto not_found; found: + + if (priv_ep->wa1_trb == priv_req->trb) + cdns3_wa1_restore_cycle_bit(priv_ep); + link_trb = priv_req->trb; + cdns3_move_deq_to_next_trb(priv_req); + cdns3_gadget_giveback(priv_ep, priv_req, -ECONNRESET); + + /* Update ring */ + request = cdns3_next_request(&priv_ep->deferred_req_list); + if (request) { + priv_req = to_cdns3_request(request); - /* Update ring only if removed request is on pending_req_list list */ - if (req_on_hw_ring) { link_trb->buffer = TRB_BUFFER(priv_ep->trb_pool_dma + (priv_req->start_trb * TRB_SIZE)); link_trb->control = (link_trb->control & TRB_CYCLE) | - TRB_TYPE(TRB_LINK) | TRB_CHAIN; - - if (priv_ep->wa1_trb == priv_req->trb) - cdns3_wa1_restore_cycle_bit(priv_ep); + TRB_TYPE(TRB_LINK) | TRB_CHAIN | TRB_TOGGLE; + } else { + priv_ep->flags |= EP_UPDATE_EP_TRBADDR; } - cdns3_gadget_giveback(priv_ep, priv_req, -ECONNRESET); - not_found: spin_unlock_irqrestore(&priv_dev->lock, flags); - return ret; -} - -/** - * __cdns3_gadget_ep_set_halt Sets stall on selected endpoint - * Should be called after acquiring spin_lock and selecting ep - * @ep: endpoint object to set stall on. - */ -void __cdns3_gadget_ep_set_halt(struct cdns3_endpoint *priv_ep) -{ - struct cdns3_device *priv_dev = priv_ep->cdns3_dev; - - trace_cdns3_halt(priv_ep, 1, 0); - - if (!(priv_ep->flags & EP_STALLED)) { - u32 ep_sts_reg = readl(&priv_dev->regs->ep_sts); - - if (!(ep_sts_reg & EP_STS_DBUSY)) - cdns3_ep_stall_flush(priv_ep); - else - priv_ep->flags |= EP_STALL_PENDING; - } -} - -/** - * __cdns3_gadget_ep_clear_halt Clears stall on selected endpoint - * Should be called after acquiring spin_lock and selecting ep - * @ep: endpoint object to clear stall on - */ -int __cdns3_gadget_ep_clear_halt(struct cdns3_endpoint *priv_ep) -{ - struct cdns3_device *priv_dev = priv_ep->cdns3_dev; - struct usb_request *request; - int ret; - int val; - - trace_cdns3_halt(priv_ep, 0, 0); - - writel(EP_CMD_CSTALL | EP_CMD_EPRST, &priv_dev->regs->ep_cmd); + if (ep == priv_dev->gadget.ep0) + flush_work(&priv_dev->pending_status_wq); - /* wait for EPRST cleared */ - ret = readl_poll_timeout_atomic(&priv_dev->regs->ep_cmd, val, - !(val & EP_CMD_EPRST), 1, 100); - if (ret) - return -EINVAL; - - priv_ep->flags &= ~(EP_STALLED | EP_STALL_PENDING); - - request = cdns3_next_request(&priv_ep->pending_req_list); - - if (request) - cdns3_rearm_transfer(priv_ep, 1); - - cdns3_start_all_request(priv_dev, priv_ep); return ret; } @@ -2201,14 +1860,37 @@ int cdns3_gadget_ep_set_halt(struct usb_ep *ep, int value) spin_lock_irqsave(&priv_dev->lock, flags); cdns3_select_ep(priv_dev, ep->desc->bEndpointAddress); + if (value) { + if (!list_empty(&priv_ep->pending_req_list)) { + ret = -EAGAIN; + goto finish; + } - if (!value) { - priv_ep->flags &= ~EP_WEDGE; - ret = __cdns3_gadget_ep_clear_halt(priv_ep); + cdns3_ep_stall_flush(priv_ep); } else { - __cdns3_gadget_ep_set_halt(priv_ep); + priv_ep->flags &= ~EP_WEDGE; + + cdns3_dbg(priv_ep->cdns3_dev, "Clear stalled endpoint %s\n", + priv_ep->name); + + writel(EP_CMD_CSTALL | EP_CMD_EPRST, &priv_dev->regs->ep_cmd); + + /* wait for EPRST cleared */ + ret = cdns3_handshake(&priv_dev->regs->ep_cmd, + EP_CMD_EPRST, 0, 100); + if (unlikely(ret)) { + dev_err(priv_dev->dev, + "Clearing halt condition failed for %s\n", + priv_ep->name); + goto finish; + + } else { + priv_ep->flags &= ~EP_STALL; + } } + priv_ep->flags &= ~EP_PENDING_REQUEST; +finish: spin_unlock_irqrestore(&priv_dev->lock, flags); return ret; @@ -2240,31 +1922,9 @@ static int cdns3_gadget_get_frame(struct usb_gadget *gadget) return readl(&priv_dev->regs->usb_itpn); } -int __cdns3_gadget_wakeup(struct cdns3_device *priv_dev) -{ - enum usb_device_speed speed; - - speed = cdns3_get_speed(priv_dev); - - if (speed >= USB_SPEED_SUPER) - return 0; - - /* Start driving resume signaling to indicate remote wakeup. */ - writel(USB_CONF_LGO_L0, &priv_dev->regs->usb_conf); - - return 0; -} - static int cdns3_gadget_wakeup(struct usb_gadget *gadget) { - struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget); - unsigned long flags; - int ret = 0; - - spin_lock_irqsave(&priv_dev->lock, flags); - ret = __cdns3_gadget_wakeup(priv_dev); - spin_unlock_irqrestore(&priv_dev->lock, flags); - return ret; + return 0; } static int cdns3_gadget_set_selfpowered(struct usb_gadget *gadget, @@ -2283,6 +1943,9 @@ static int cdns3_gadget_pullup(struct usb_gadget *gadget, int is_on) { struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget); + if (!priv_dev->start_gadget) + return 0; + if (is_on) writel(USB_CONF_DEVEN, &priv_dev->regs->usb_conf); else @@ -2294,19 +1957,20 @@ static int cdns3_gadget_pullup(struct usb_gadget *gadget, int is_on) static void cdns3_gadget_config(struct cdns3_device *priv_dev) { struct cdns3_usb_regs __iomem *regs = priv_dev->regs; - u32 reg; cdns3_ep0_config(priv_dev); /* enable interrupts for endpoint 0 (in and out) */ writel(EP_IEN_EP_OUT0 | EP_IEN_EP_IN0, ®s->ep_ien); + priv_dev->dev_ver = readl(&priv_dev->regs->usb_cap6); + priv_dev->dev_ver = GET_DEV_BASE_VERSION(priv_dev->dev_ver); /* * Driver needs to modify LFPS minimal U1 Exit time for DEV_VER_TI_V1 * revision of controller. */ if (priv_dev->dev_ver == DEV_VER_TI_V1) { - reg = readl(®s->dbg_link1); + u32 reg = readl(®s->dbg_link1); reg &= ~DBG_LINK1_LFPS_MIN_GEN_U1_EXIT_MASK; reg |= DBG_LINK1_LFPS_MIN_GEN_U1_EXIT(0x55) | @@ -2314,21 +1978,14 @@ static void cdns3_gadget_config(struct cdns3_device *priv_dev) writel(reg, ®s->dbg_link1); } - /* - * By default some platforms has set protected access to memory. - * This cause problem with cache, so driver restore non-secure - * access to memory. - */ - reg = readl(®s->dma_axi_ctrl); - reg |= DMA_AXI_CTRL_MARPROT(DMA_AXI_CTRL_NON_SECURE) | - DMA_AXI_CTRL_MAWPROT(DMA_AXI_CTRL_NON_SECURE); - writel(reg, ®s->dma_axi_ctrl); - /* enable generic interrupt*/ writel(USB_IEN_INIT, ®s->usb_ien); writel(USB_CONF_CLK2OFFDS | USB_CONF_L1DS, ®s->usb_conf); + writel(USB_CONF_DMULT, ®s->usb_conf); + if (priv_dev->dev_ver == DEV_VER_V2) + writel(USB_CONF2_EN_TDL_TRB, ®s->usb_conf2); - cdns3_configure_dmult(priv_dev, NULL); + cdns3_gadget_pullup(&priv_dev->gadget, 1); } /** @@ -2343,33 +2000,13 @@ static int cdns3_gadget_udc_start(struct usb_gadget *gadget, { struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget); unsigned long flags; - enum usb_device_speed max_speed = driver->max_speed; + dev_dbg(priv_dev->dev, "%s begins\n", __func__); spin_lock_irqsave(&priv_dev->lock, flags); priv_dev->gadget_driver = driver; - - /* limit speed if necessary */ - max_speed = min(driver->max_speed, gadget->max_speed); - - switch (max_speed) { - case USB_SPEED_FULL: - writel(USB_CONF_SFORCE_FS, &priv_dev->regs->usb_conf); - writel(USB_CONF_USB3DIS, &priv_dev->regs->usb_conf); - break; - case USB_SPEED_HIGH: - writel(USB_CONF_USB3DIS, &priv_dev->regs->usb_conf); - break; - case USB_SPEED_SUPER: - break; - default: - dev_err(priv_dev->dev, - "invalid maximum_speed parameter %d\n", - max_speed); - /* fall through */ - case USB_SPEED_UNKNOWN: - /* default to superspeed */ - max_speed = USB_SPEED_SUPER; - break; + if (!priv_dev->start_gadget) { + spin_unlock_irqrestore(&priv_dev->lock, flags); + return 0; } cdns3_gadget_config(priv_dev); @@ -2387,28 +2024,32 @@ static int cdns3_gadget_udc_stop(struct usb_gadget *gadget) { struct cdns3_device *priv_dev = gadget_to_cdns3_device(gadget); struct cdns3_endpoint *priv_ep; - u32 bEndpointAddress; struct usb_ep *ep; + unsigned long flags; int ret = 0; - int val; + spin_lock_irqsave(&priv_dev->lock, flags); priv_dev->gadget_driver = NULL; - priv_dev->onchip_used_size = 0; + priv_dev->status_completion_no_call = 0; + priv_dev->onchip_mem_allocated_size = 0; priv_dev->out_mem_is_allocated = 0; priv_dev->gadget.speed = USB_SPEED_UNKNOWN; - list_for_each_entry(ep, &priv_dev->gadget.ep_list, ep_list) { priv_ep = ep_to_cdns3_ep(ep); - bEndpointAddress = priv_ep->num | priv_ep->dir; - cdns3_select_ep(priv_dev, bEndpointAddress); - writel(EP_CMD_EPRST, &priv_dev->regs->ep_cmd); - readl_poll_timeout_atomic(&priv_dev->regs->ep_cmd, val, - !(val & EP_CMD_EPRST), 1, 100); - priv_ep->flags &= ~EP_CLAIMED; } + spin_unlock_irqrestore(&priv_dev->lock, flags); + if (!priv_dev->start_gadget) + return ret; + + list_for_each_entry(ep, &priv_dev->gadget.ep_list, ep_list) { + priv_ep = ep_to_cdns3_ep(ep); + usb_ep_disable(ep); + cdns3_free_trb_pool(priv_ep); + } + /* disable interrupt for device */ writel(0, &priv_dev->regs->usb_ien); writel(USB_CONF_DEVDS, &priv_dev->regs->usb_conf); @@ -2430,14 +2071,14 @@ static void cdns3_free_all_eps(struct cdns3_device *priv_dev) { int i; - /* ep0 OUT point to ep0 IN. */ + /*ep0 OUT point to ep0 IN*/ priv_dev->eps[16] = NULL; + cdns3_free_trb_pool(priv_dev->eps[0]); + for (i = 0; i < CDNS3_ENDPOINTS_MAX_COUNT; i++) - if (priv_dev->eps[i]) { - cdns3_free_trb_pool(priv_dev->eps[i]); + if (priv_dev->eps[i]) devm_kfree(priv_dev->dev, priv_dev->eps[i]); - } } /** @@ -2455,9 +2096,9 @@ static int cdns3_init_eps(struct cdns3_device *priv_dev) int ret = 0; int i; - /* Read it from USB_CAP3 to USB_CAP5 */ - ep_enabled_reg = readl(&priv_dev->regs->usb_cap3); - iso_ep_reg = readl(&priv_dev->regs->usb_cap4); + /* Read it from USB_CAP3 and USB_CAP4 */ + ep_enabled_reg = 0x00ff00ff; + iso_ep_reg = 0x00fe00fe; dev_dbg(priv_dev->dev, "Initializing non-zero endpoints\n"); @@ -2476,8 +2117,10 @@ static int cdns3_init_eps(struct cdns3_device *priv_dev) priv_ep = devm_kzalloc(priv_dev->dev, sizeof(*priv_ep), GFP_KERNEL); - if (!priv_ep) + if (!priv_ep) { + ret = -ENOMEM; goto err; + } /* set parent of endpoint object */ priv_ep->cdns3_dev = priv_dev; @@ -2517,14 +2160,14 @@ static int cdns3_init_eps(struct cdns3_device *priv_dev) priv_ep->flags = 0; - dev_info(priv_dev->dev, "Initialized %s support: %s %s\n", + dev_dbg(priv_dev->dev, "Initialized %s support: %s %s\n", priv_ep->name, priv_ep->endpoint.caps.type_bulk ? "BULK, INT" : "", priv_ep->endpoint.caps.type_iso ? "ISO" : ""); INIT_LIST_HEAD(&priv_ep->pending_req_list); INIT_LIST_HEAD(&priv_ep->deferred_req_list); - INIT_LIST_HEAD(&priv_ep->wa2_descmiss_req_list); + INIT_LIST_HEAD(&priv_ep->descmiss_req_list); } return 0; @@ -2533,13 +2176,27 @@ err: return -ENOMEM; } +static void cdns3_gadget_disable(struct cdns3 *cdns) +{ + struct cdns3_device *priv_dev; + + priv_dev = cdns->gadget_dev; + + if (priv_dev->gadget_driver) { + priv_dev->gadget_driver->disconnect(&priv_dev->gadget); + usb_gadget_disconnect(&priv_dev->gadget); + } + + priv_dev->gadget.speed = USB_SPEED_UNKNOWN; +} + void cdns3_gadget_exit(struct cdns3 *cdns) { struct cdns3_device *priv_dev; priv_dev = cdns->gadget_dev; - devm_free_irq(cdns->dev, cdns->dev_irq, cdns); + cdns3_gadget_disable(cdns); pm_runtime_mark_last_busy(cdns->dev); pm_runtime_put_autosuspend(cdns->dev); @@ -2552,6 +2209,7 @@ void cdns3_gadget_exit(struct cdns3 *cdns) struct cdns3_aligned_buf *buf; buf = cdns3_next_align_buf(&priv_dev->aligned_buf_list); + dma_free_coherent(priv_dev->sysdev, buf->size, buf->buf, buf->dma); @@ -2566,14 +2224,32 @@ void cdns3_gadget_exit(struct cdns3 *cdns) kfree(priv_dev->zlp_buf); kfree(priv_dev); cdns->gadget_dev = NULL; - cdns3_drd_switch_gadget(cdns, 0); } static int cdns3_gadget_start(struct cdns3 *cdns) { + struct cdns3_device *priv_dev = cdns->gadget_dev; + unsigned long flags; + + pm_runtime_get_sync(cdns->dev); + spin_lock_irqsave(&priv_dev->lock, flags); + priv_dev->start_gadget = 1; + if (!priv_dev->gadget_driver) { + spin_unlock_irqrestore(&priv_dev->lock, flags); + return 0; + } + + cdns3_gadget_config(priv_dev); + spin_unlock_irqrestore(&priv_dev->lock, flags); + + return 0; +} + +static int __cdns3_gadget_init(struct cdns3 *cdns) +{ struct cdns3_device *priv_dev; u32 max_speed; - int ret; + int ret = 0; priv_dev = kzalloc(sizeof(*priv_dev), GFP_KERNEL); if (!priv_dev) @@ -2584,18 +2260,6 @@ static int cdns3_gadget_start(struct cdns3 *cdns) priv_dev->dev = cdns->dev; priv_dev->regs = cdns->dev_regs; - device_property_read_u16(priv_dev->dev, "cdns,on-chip-buff-size", - &priv_dev->onchip_buffers); - - if (priv_dev->onchip_buffers <= 0) { - u32 reg = readl(&priv_dev->regs->usb_cap2); - - priv_dev->onchip_buffers = USB_CAP2_ACTUAL_MEM_SIZE(reg); - } - - if (!priv_dev->onchip_buffers) - priv_dev->onchip_buffers = 256; - max_speed = usb_get_maximum_speed(cdns->dev); /* Check the maximum_speed parameter */ @@ -2626,13 +2290,11 @@ static int cdns3_gadget_start(struct cdns3 *cdns) INIT_WORK(&priv_dev->pending_status_wq, cdns3_pending_setup_status_handler); - INIT_WORK(&priv_dev->aligned_buf_wq, - cdns3_free_aligned_request_buf); - /* initialize endpoint container */ INIT_LIST_HEAD(&priv_dev->gadget.ep_list); INIT_LIST_HEAD(&priv_dev->aligned_buf_list); + pm_runtime_get_sync(cdns->dev); ret = cdns3_init_eps(priv_dev); if (ret) { dev_err(priv_dev->dev, "Failed to create endpoints\n"); @@ -2643,21 +2305,11 @@ static int cdns3_gadget_start(struct cdns3 *cdns) priv_dev->setup_buf = dma_alloc_coherent(priv_dev->sysdev, 8, &priv_dev->setup_dma, GFP_DMA); if (!priv_dev->setup_buf) { + dev_err(priv_dev->dev, "Failed to allocate memory for SETUP buffer\n"); ret = -ENOMEM; goto err2; } - priv_dev->dev_ver = readl(&priv_dev->regs->usb_cap6); - - dev_dbg(priv_dev->dev, "Device Controller version: %08x\n", - readl(&priv_dev->regs->usb_cap6)); - dev_dbg(priv_dev->dev, "USB Capabilities:: %08x\n", - readl(&priv_dev->regs->usb_cap1)); - dev_dbg(priv_dev->dev, "On-Chip memory configuration: %08x\n", - readl(&priv_dev->regs->usb_cap2)); - - priv_dev->dev_ver = GET_DEV_BASE_VERSION(priv_dev->dev_ver); - priv_dev->zlp_buf = kzalloc(CDNS3_EP_ZLP_BUF_SIZE, GFP_KERNEL); if (!priv_dev->zlp_buf) { ret = -ENOMEM; @@ -2672,6 +2324,11 @@ static int cdns3_gadget_start(struct cdns3 *cdns) goto err4; } + if (ret) + goto err4; + + + pm_runtime_put_sync(cdns->dev); return 0; err4: kfree(priv_dev->zlp_buf); @@ -2682,70 +2339,54 @@ err2: cdns3_free_all_eps(priv_dev); err1: cdns->gadget_dev = NULL; + pm_runtime_put_sync(cdns->dev); return ret; } -static int __cdns3_gadget_init(struct cdns3 *cdns) -{ - int ret = 0; - - /* Ensure 32-bit DMA Mask in case we switched back from Host mode */ - ret = dma_set_mask_and_coherent(cdns->dev, DMA_BIT_MASK(32)); - if (ret) { - dev_err(cdns->dev, "Failed to set dma mask: %d\n", ret); - return ret; - } - - cdns3_drd_switch_gadget(cdns, 1); - pm_runtime_get_sync(cdns->dev); - - ret = cdns3_gadget_start(cdns); - if (ret) - return ret; - - /* - * Because interrupt line can be shared with other components in - * driver it can't use IRQF_ONESHOT flag here. - */ - ret = devm_request_threaded_irq(cdns->dev, cdns->dev_irq, - cdns3_device_irq_handler, - cdns3_device_thread_irq_handler, - IRQF_SHARED, dev_name(cdns->dev), cdns); - - if (ret) - goto err0; - - return 0; -err0: - cdns3_gadget_exit(cdns); - return ret; -} - -static int cdns3_gadget_suspend(struct cdns3 *cdns, bool do_wakeup) +static void __cdns3_gadget_stop(struct cdns3 *cdns) { struct cdns3_device *priv_dev = cdns->gadget_dev; + unsigned long flags; - cdns3_disconnect_gadget(priv_dev); - - priv_dev->gadget.speed = USB_SPEED_UNKNOWN; + cdns3_gadget_disable(cdns); + spin_lock_irqsave(&priv_dev->lock, flags); usb_gadget_set_state(&priv_dev->gadget, USB_STATE_NOTATTACHED); - cdns3_hw_reset_eps_config(priv_dev); - /* disable interrupt for device */ writel(0, &priv_dev->regs->usb_ien); + writel(USB_CONF_DEVDS, &priv_dev->regs->usb_conf); + priv_dev->start_gadget = 0; + spin_unlock_irqrestore(&priv_dev->lock, flags); +} +static void cdns3_gadget_stop(struct cdns3 *cdns) +{ + if (cdns->role == CDNS3_ROLE_GADGET) + __cdns3_gadget_stop(cdns); + pm_runtime_mark_last_busy(cdns->dev); + pm_runtime_put_autosuspend(cdns->dev); +} + +static int cdns3_gadget_suspend(struct cdns3 *cdns, bool do_wakeup) +{ + __cdns3_gadget_stop(cdns); return 0; } static int cdns3_gadget_resume(struct cdns3 *cdns, bool hibernated) { struct cdns3_device *priv_dev = cdns->gadget_dev; + unsigned long flags; + + spin_lock_irqsave(&priv_dev->lock, flags); + priv_dev->start_gadget = 1; - if (!priv_dev->gadget_driver) + if (!priv_dev->gadget_driver) { + spin_unlock_irqrestore(&priv_dev->lock, flags); return 0; + } cdns3_gadget_config(priv_dev); - + spin_unlock_irqrestore(&priv_dev->lock, flags); return 0; } @@ -2764,13 +2405,14 @@ int cdns3_gadget_init(struct cdns3 *cdns) if (!rdrv) return -ENOMEM; - rdrv->start = __cdns3_gadget_init; - rdrv->stop = cdns3_gadget_exit; + rdrv->start = cdns3_gadget_start; + rdrv->stop = cdns3_gadget_stop; rdrv->suspend = cdns3_gadget_suspend; rdrv->resume = cdns3_gadget_resume; - rdrv->state = CDNS3_ROLE_STATE_INACTIVE; + rdrv->irq = cdns3_device_irq_handler; + rdrv->thread_irq = cdns3_device_thread_irq_handler; rdrv->name = "gadget"; - cdns->roles[USB_ROLE_DEVICE] = rdrv; + cdns->roles[CDNS3_ROLE_GADGET] = rdrv; - return 0; + return __cdns3_gadget_init(cdns); } diff --git a/drivers/usb/cdns3/gadget.h b/drivers/usb/cdns3/gadget.h index bc4024041ef2..bd470afa25ce 100644 --- a/drivers/usb/cdns3/gadget.h +++ b/drivers/usb/cdns3/gadget.h @@ -3,7 +3,7 @@ * USBSS device controller driver header file * * Copyright (C) 2018-2019 Cadence. - * Copyright (C) 2017-2018 NXP + * Copyright (C) 2017-2019 NXP * * Author: Pawel Laszczak <pawell@cadence.com> * Pawel Jez <pjez@cadence.com> @@ -20,52 +20,42 @@ /** * struct cdns3_usb_regs - device controller registers. - * @usb_conf: Global Configuration. - * @usb_sts: Global Status. - * @usb_cmd: Global Command. - * @usb_itpn: ITP/SOF number. - * @usb_lpm: Global Command. - * @usb_ien: USB Interrupt Enable. - * @usb_ists: USB Interrupt Status. - * @ep_sel: Endpoint Select. - * @ep_traddr: Endpoint Transfer Ring Address. - * @ep_cfg: Endpoint Configuration. - * @ep_cmd: Endpoint Command. - * @ep_sts: Endpoint Status. - * @ep_sts_sid: Endpoint Status. - * @ep_sts_en: Endpoint Status Enable. - * @drbl: Doorbell. - * @ep_ien: EP Interrupt Enable. - * @ep_ists: EP Interrupt Status. - * @usb_pwr: Global Power Configuration. - * @usb_conf2: Global Configuration 2. - * @usb_cap1: Capability 1. - * @usb_cap2: Capability 2. - * @usb_cap3: Capability 3. - * @usb_cap4: Capability 4. - * @usb_cap5: Capability 5. - * @usb_cap6: Capability 6. - * @usb_cpkt1: Custom Packet 1. - * @usb_cpkt2: Custom Packet 2. - * @usb_cpkt3: Custom Packet 3. - * @ep_dma_ext_addr: Upper address for DMA operations. - * @buf_addr: Address for On-chip Buffer operations. - * @buf_data: Data for On-chip Buffer operations. - * @buf_ctrl: On-chip Buffer Access Control. - * @dtrans: DMA Transfer Mode. - * @tdl_from_trb: Source of TD Configuration. - * @tdl_beh: TDL Behavior Configuration. - * @ep_tdl: Endpoint TDL. - * @tdl_beh2: TDL Behavior 2 Configuration. - * @dma_adv_td: DMA Advance TD Configuration. + * @usb_conf: Global Configuration Register. + * @usb_sts: Global Status Register. + * @usb_cmd: Global Command Register. + * @usb_itpn: ITP/SOF number Register. + * @usb_lpm: Global Command Register. + * @usb_ien: USB Interrupt Enable Register. + * @usb_ists: USB Interrupt Status Register. + * @ep_sel: Endpoint Select Register. + * @ep_traddr: Endpoint Transfer Ring Address Register. + * @ep_cfg: Endpoint Configuration Register. + * @ep_cmd: Endpoint Command Register. + * @ep_sts: Endpoint Status Register. + * @ep_sts_sid: Endpoint Status Register. + * @ep_sts_en: Endpoint Status Register Enable. + * @drbl: Doorbell Register. + * @ep_ien: EP Interrupt Enable Register. + * @ep_ists: EP Interrupt Status Register. + * @usb_pwr: Global Power Configuration Register. + * @usb_conf2: Global Configuration Register 2. + * @usb_cap1: Capability Register 1. + * @usb_cap2: Capability Register 2. + * @usb_cap3: Capability Register 3. + * @usb_cap4: Capability Register 4. + * @usb_cap5: Capability Register 5. + * @usb_cap6: Capability Register 6. + * @usb_cpkt1: Custom Packet Register 1. + * @usb_cpkt2: Custom Packet Register 2. + * @usb_cpkt3: Custom Packet Register 3. * @reserved1: Reserved. - * @cfg_regs: Configuration. + * @cfg_regs: Configuration registers. * @reserved2: Reserved. - * @dma_axi_ctrl: AXI Control. + * @dma_axi_ctrl: AXI Control register. * @dma_axi_id: AXI ID register. - * @dma_axi_cap: AXI Capability. - * @dma_axi_ctrl0: AXI Control 0. - * @dma_axi_ctrl1: AXI Control 1. + * @dma_axi_cap: AXI Capability register. + * @dma_axi_ctrl0: AXI Control 0 register. + * @dma_axi_ctrl1: AXI Control 1 register. */ struct cdns3_usb_regs { __le32 usb_conf; @@ -96,22 +86,12 @@ struct cdns3_usb_regs { __le32 usb_cpkt1; __le32 usb_cpkt2; __le32 usb_cpkt3; - __le32 ep_dma_ext_addr; - __le32 buf_addr; - __le32 buf_data; - __le32 buf_ctrl; - __le32 dtrans; - __le32 tdl_from_trb; - __le32 tdl_beh; - __le32 ep_tdl; - __le32 tdl_beh2; - __le32 dma_adv_td; - __le32 reserved1[26]; + __le32 reserved1[36]; __le32 cfg_reg1; __le32 dbg_link1; __le32 dbg_link2; __le32 cfg_regs[74]; - __le32 reserved2[51]; + __le32 reserved2[34]; __le32 dma_axi_ctrl; __le32 dma_axi_id; __le32 dma_axi_cap; @@ -138,9 +118,9 @@ struct cdns3_usb_regs { #define USB_CONF_BENDIAN BIT(6) /* Device software reset. */ #define USB_CONF_SWRST BIT(7) -/* Singular DMA transfer mode. Only for VER < DEV_VER_V3*/ +/* Singular DMA transfer mode. */ #define USB_CONF_DSING BIT(8) -/* Multiple DMA transfers mode. Only for VER < DEV_VER_V3 */ +/* Multiple DMA transfers mode. */ #define USB_CONF_DMULT BIT(9) /* DMA clock turn-off enable. */ #define USB_CONF_DMAOFFEN BIT(10) @@ -185,6 +165,19 @@ struct cdns3_usb_regs { #define USB_CONF_LGO_U2 BIT(30) /* SS.Inactive state entry request (used in SS mode) */ #define USB_CONF_LGO_SSINACT BIT(31) +/* USB_CONF2- bitmasks */ +/* + * Writing 1 disables TDL calculation basing on TRB feature in controller + * for DMULT mode. + * Bit supported only for DEV_VER_V2 version. + */ +#define USB_CONF2_DIS_TDL_TRB BIT(1) +/* + * Writing 1 enables TDL calculation basing on TRB feature in controller + * for DMULT mode. + * Bit supported only for DEV_VER_V2 version. + */ +#define USB_CONF2_EN_TDL_TRB BIT(2) /* USB_STS - bitmasks */ /* @@ -212,7 +205,6 @@ struct cdns3_usb_regs { * DMA transfer configuration status. * 0 - single request. * 1 - multiple TRB chain - * Supported only for controller version < DEV_VER_V3 */ #define USB_STS_DTRANS_MASK BIT(3) #define USB_STS_DTRANS(p) ((p) & USB_STS_DTRANS_MASK) @@ -266,13 +258,6 @@ struct cdns3_usb_regs { #define USB_STS_IN_RST_MASK BIT(10) #define USB_STS_IN_RST(p) ((p) & USB_STS_IN_RST_MASK) /* - * Status of the "TDL calculation basing on TRB" feature. - * 0 - disabled - * 1 - enabled - * Supported only for DEV_VER_V2 controller version. - */ -#define USB_STS_TDL_TRB_ENABLED BIT(11) -/* * Device enable Status. * 0 - USB device is disabled (VBUS input is disconnected from internal logic). * 1 - USB device is enabled (VBUS input is connected to the internal logic). @@ -280,7 +265,7 @@ struct cdns3_usb_regs { #define USB_STS_DEVS_MASK BIT(14) #define USB_STS_DEVS(p) ((p) & USB_STS_DEVS_MASK) /* - * Address status. + * DAddress statuss. * 0 - USB device is default state. * 1 - USB device is at least in address state. */ @@ -365,7 +350,7 @@ struct cdns3_usb_regs { #define USB_STS_DMAOFF_MASK BIT(30) #define USB_STS_DMAOFF(p) ((p) & USB_STS_DMAOFF_MASK) /* - * SFR Endian status. + * SFR Endian statuss. * 0 - Little Endian order (default after hardware reset). * 1 - Big Endian order. */ @@ -475,7 +460,7 @@ struct cdns3_usb_regs { #define USB_IEN_INIT (USB_IEN_U2RESIEN | USB_ISTS_DIS2I | USB_IEN_CON2IEN \ | USB_IEN_UHRESIEN | USB_IEN_UWRESIEN | USB_IEN_DISIEN \ | USB_IEN_CONIEN | USB_IEN_U3EXTIEN | USB_IEN_L2ENTIEN \ - | USB_IEN_L2EXTIEN | USB_IEN_L1ENTIEN | USB_IEN_U3ENTIEN) + | USB_IEN_L2EXTIEN) /* USB_ISTS - bitmasks */ /* SS Connection detected. */ @@ -589,20 +574,14 @@ struct cdns3_usb_regs { /* * Transfer Descriptor Length write (used only for Bulk Stream capable * endpoints in SS mode). - * Bit Removed from DEV_VER_V3 controller version. */ #define EP_CMD_STDL BIT(8) -/* - * Transfer Descriptor Length (used only in SS mode for bulk endpoints). - * Bits Removed from DEV_VER_V3 controller version. - */ +/* Transfer Descriptor Length (used only in SS mode for bulk endpoints). */ #define EP_CMD_TDL_MASK GENMASK(15, 9) -#define EP_CMD_TDL_SET(p) (((p) << 9) & EP_CMD_TDL_MASK) -#define EP_CMD_TDL_GET(p) (((p) & EP_CMD_TDL_MASK) >> 9) - +#define EP_CMD_TDL(p) (((p) << 9) & EP_CMD_TDL_MASK) /* ERDY Stream ID value (used in SS mode). */ #define EP_CMD_ERDY_SID_MASK GENMASK(31, 16) -#define EP_CMD_ERDY_SID(p) (((p) << 16) & EP_CMD_ERDY_SID_MASK) +#define EP_CMD_ERDY_SID(p) (((p) << 16) & EP_CMD_SID_MASK) /* EP_STS - bitmasks */ /* Setup transfer complete. */ @@ -623,8 +602,8 @@ struct cdns3_usb_regs { #define EP_STS_TRBERR BIT(7) /* Not ready (used only in SS mode). */ #define EP_STS_NRDY BIT(8) -/* DMA busy bit. */ -#define EP_STS_DBUSY BIT(9) +/* DMA busy. */ +#define EP_STS_DBUSY(p) ((p) & BIT(9)) /* Endpoint Buffer Empty */ #define EP_STS_BUFFEMPTY(p) ((p) & BIT(10)) /* Current Cycle Status */ @@ -702,7 +681,7 @@ struct cdns3_usb_regs { #define EP_ISTS_EP_OUT0 BIT(0) #define EP_ISTS_EP_IN0 BIT(16) -/* USB_PWR- bitmasks */ +/* EP_PWR- bitmasks */ /*Power Shut Off capability enable*/ #define PUSB_PWR_PSO_EN BIT(0) /*Power Shut Off capability disable*/ @@ -721,21 +700,7 @@ struct cdns3_usb_regs { /* This bit informs if Fast Registers Access is enabled. */ #define PUSB_PWR_FST_REG_ACCESS_STAT BIT(30) /* Fast Registers Access Enable. */ -#define PUSB_PWR_FST_REG_ACCESS BIT(31) - -/* USB_CONF2- bitmasks */ -/* - * Writing 1 disables TDL calculation basing on TRB feature in controller - * for DMULT mode. - * Bit supported only for DEV_VER_V2 version. - */ -#define USB_CONF2_DIS_TDL_TRB BIT(1) -/* - * Writing 1 enables TDL calculation basing on TRB feature in controller - * for DMULT mode. - * Bit supported only for DEV_VER_V2 version. - */ -#define USB_CONF2_EN_TDL_TRB BIT(2) +#define PUSB_PWR_FST_REG_ACCESS BIT(31) /* USB_CAP1- bitmasks */ /* @@ -852,13 +817,6 @@ struct cdns3_usb_regs { */ #define USB_CAP1_OTG_READY(p) ((p) & BIT(27)) -/* - * When set, indicates that controller supports automatic internal TDL - * calculation basing on the size provided in TRB (TRB[22:17]) for DMULT mode - * Supported only for DEV_VER_V2 controller version. - */ -#define USB_CAP1_TDL_FROM_TRB(p) ((p) & BIT(28)) - /* USB_CAP2- bitmasks */ /* * The actual size of the connected On-chip RAM memory in kB: @@ -950,13 +908,6 @@ struct cdns3_usb_regs { */ #define DBG_LINK1_LFPS_GEN_PING_SET BIT(27) -/* DMA_AXI_CTRL- bitmasks */ -/* The mawprot pin configuration. */ -#define DMA_AXI_CTRL_MARPROT(p) ((p) & GENMASK(2, 0)) -/* The marprot pin configuration. */ -#define DMA_AXI_CTRL_MAWPROT(p) (((p) & GENMASK(2, 0)) << 16) -#define DMA_AXI_CTRL_NON_SECURE 0x02 - #define gadget_to_cdns3_device(g) (container_of(g, struct cdns3_device, gadget)) #define ep_to_cdns3_ep(ep) (container_of(ep, struct cdns3_endpoint, endpoint)) @@ -965,14 +916,10 @@ struct cdns3_usb_regs { /* * USBSS-DEV DMA interface. */ -#define TRBS_PER_SEGMENT 40 +#define TRBS_PER_SEGMENT 150 #define ISO_MAX_INTERVAL 10 -#if TRBS_PER_SEGMENT < 2 -#error "Incorrect TRBS_PER_SEGMENT. Minimal Transfer Ring size is 2." -#endif - /* *Only for ISOC endpoints - maximum number of TRBs is calculated as * pow(2, bInterval-1) * number of usb requests. It is limitation made by @@ -1021,16 +968,6 @@ struct cdns3_trb { */ #define TRB_TOGGLE BIT(1) -/* - * Short Packet (SP). OUT EPs at DMULT=1 only. Indicates if the TRB was - * processed while USB short packet was received. No more buffers defined by - * the TD will be used. DMA will automatically advance to next TD. - * - Shall be set to 0 by Software when putting TRB on the Transfer Ring - * - Shall be set to 1 by Controller when Short Packet condition for this TRB - * is detected independent if ISP is set or not. - */ -#define TRB_SP BIT(1) - /* Interrupt on short packet*/ #define TRB_ISP BIT(2) /*Setting this bit enables FIFO DMA operation mode*/ @@ -1041,9 +978,7 @@ struct cdns3_trb { #define TRB_IOC BIT(5) /* stream ID bitmasks. */ -#define TRB_STREAM_ID_BITMASK GENMASK(31, 16) -#define TRB_STREAM_ID(p) ((p) << 16) -#define TRB_FIELD_TO_STREAMID(p) (((p) & TRB_STREAM_ID_BITMASK) >> 16) +#define TRB_STREAM_ID(p) ((p) & GENMASK(31, 16)) /* Size of TD expressed in USB packets for HS/FS mode. */ #define TRB_TDL_HS_SIZE(p) (((p) << 16) & GENMASK(31, 16)) @@ -1083,7 +1018,6 @@ struct cdns3_trb { #define CDNS3_EP_ISO_SS_BURST 3 #define CDNS3_MAX_NUM_DESCMISS_BUF 32 #define CDNS3_DESCMIS_BUF_SIZE 2048 /* Bytes */ -#define CDNS3_WA2_NUM_BUFFERS 128 /*-------------------------------------------------------------------------*/ /* Used structs */ @@ -1094,7 +1028,7 @@ struct cdns3_device; * @endpoint: usb endpoint * @pending_req_list: list of requests queuing on transfer ring. * @deferred_req_list: list of requests waiting for queuing on transfer ring. - * @wa2_descmiss_req_list: list of requests internally allocated by driver. + * @descmiss_req_list: list of requests internally allocated by driver (WA2). * @trb_pool: transfer ring - array of transaction buffers * @trb_pool_dma: dma address of transfer ring * @cdns3_dev: device associated with this endpoint @@ -1119,8 +1053,7 @@ struct cdns3_endpoint { struct usb_ep endpoint; struct list_head pending_req_list; struct list_head deferred_req_list; - struct list_head wa2_descmiss_req_list; - int wa2_counter; + struct list_head descmiss_req_list; struct cdns3_trb *trb_pool; dma_addr_t trb_pool_dma; @@ -1129,19 +1062,17 @@ struct cdns3_endpoint { char name[20]; #define EP_ENABLED BIT(0) -#define EP_STALLED BIT(1) -#define EP_STALL_PENDING BIT(2) -#define EP_WEDGE BIT(3) -#define EP_TRANSFER_STARTED BIT(4) -#define EP_UPDATE_EP_TRBADDR BIT(5) -#define EP_PENDING_REQUEST BIT(6) -#define EP_RING_FULL BIT(7) -#define EP_CLAIMED BIT(8) -#define EP_DEFERRED_DRDY BIT(9) -#define EP_QUIRK_ISO_OUT_EN BIT(10) -#define EP_QUIRK_END_TRANSFER BIT(11) -#define EP_QUIRK_EXTRA_BUF_DET BIT(12) -#define EP_QUIRK_EXTRA_BUF_EN BIT(13) +#define EP_STALL BIT(1) +#define EP_WEDGE BIT(2) +#define EP_TRANSFER_STARTED BIT(3) +#define EP_UPDATE_EP_TRBADDR BIT(4) +#define EP_PENDING_REQUEST BIT(5) +#define EP_RING_FULL BIT(6) +#define EP_CLAIMED BIT(7) +#define EP_QUIRK_EXTRA_BUF_DET BIT(8) +#define EP_QUIRK_EXTRA_BUF_EN BIT(9) +#define EP_QUIRK_END_TRANSFER BIT(10) + u32 flags; struct cdns3_request *descmis_req; @@ -1193,7 +1124,7 @@ struct cdns3_aligned_buf { * @aligned_buf: object holds information about aligned buffer associated whit * this endpoint * @flags: flag specifying special usage of request - * @list: used by internally allocated request to add to wa2_descmiss_req_list. + * @list: used by internally allocated request to add to descmiss_req_list. */ struct cdns3_request { struct usb_request request; @@ -1234,7 +1165,8 @@ struct cdns3_request { * @ep0_data_dir: direction for control transfer * @eps: array of pointers to all endpoints with exclusion ep0 * @aligned_buf_list: list of aligned buffers internally allocated by driver - * @aligned_buf_wq: workqueue freeing no longer used aligned buf. + * @run_garbage_colector: infroms that at least one element of aligned_buf_list + * can be freed * @selected_ep: actually selected endpoint. It's used only to improve * performance. * @isoch_delay: value from Set Isoch Delay request. Only valid on SS/SSP. @@ -1246,10 +1178,13 @@ struct cdns3_request { * @wake_up_flag: allow device to remote up the host * @status_completion_no_call: indicate that driver is waiting for status s * stage completion. It's used in deferred SET_CONFIGURATION request. - * @onchip_buffers: number of available on-chip buffers. - * @onchip_used_size: actual size of on-chip memory assigned to endpoints. + * @onchip_mem_allocated_size: actual size of on-chip memory assigned + * to endpoints * @pending_status_wq: workqueue handling status stage for deferred requests. + * @shadow_ep_en: hold information about endpoints that will be enabled + * in soft irq. * @pending_status_request: request for which status stage was deferred + * @start_gadget: the current role is at CDNS3_ROLE_GADGET */ struct cdns3_device { struct device *dev; @@ -1277,12 +1212,11 @@ struct cdns3_device { struct cdns3_endpoint *eps[CDNS3_ENDPOINTS_MAX_COUNT]; struct list_head aligned_buf_list; - struct work_struct aligned_buf_wq; + unsigned run_garbage_colector:1; u32 selected_ep; u16 isoch_delay; - unsigned wait_for_setup:1; unsigned u1_allowed:1; unsigned u2_allowed:1; unsigned is_selfpowered:1; @@ -1290,14 +1224,14 @@ struct cdns3_device { int hw_configured_flag:1; int wake_up_flag:1; unsigned status_completion_no_call:1; - int out_mem_is_allocated; + int out_mem_is_allocated:1; struct work_struct pending_status_wq; struct usb_request *pending_status_request; - + u32 shadow_ep_en; /*in KB */ - u16 onchip_buffers; - u16 onchip_used_size; + int onchip_mem_allocated_size; + unsigned start_gadget:1; }; void cdns3_set_register_bit(void __iomem *ptr, u32 mask); @@ -1317,8 +1251,6 @@ int cdns3_allocate_trb_pool(struct cdns3_endpoint *priv_ep); u8 cdns3_ep_addr_to_index(u8 ep_addr); int cdns3_gadget_ep_set_wedge(struct usb_ep *ep); int cdns3_gadget_ep_set_halt(struct usb_ep *ep, int value); -void __cdns3_gadget_ep_set_halt(struct cdns3_endpoint *priv_ep); -int __cdns3_gadget_ep_clear_halt(struct cdns3_endpoint *priv_ep); struct usb_request *cdns3_gadget_ep_alloc_request(struct usb_ep *ep, gfp_t gfp_flags); void cdns3_gadget_ep_free_request(struct usb_ep *ep, @@ -1333,6 +1265,5 @@ int cdns3_init_ep0(struct cdns3_device *priv_dev, void cdns3_ep0_config(struct cdns3_device *priv_dev); void cdns3_ep_config(struct cdns3_endpoint *priv_ep); void cdns3_check_ep0_interrupt_proceed(struct cdns3_device *priv_dev, int dir); -int __cdns3_gadget_wakeup(struct cdns3_device *priv_dev); #endif /* __LINUX_CDNS3_GADGET */ diff --git a/drivers/usb/cdns3/host-export.h b/drivers/usb/cdns3/host-export.h index ae11810f8826..a981d5cf3658 100644 --- a/drivers/usb/cdns3/host-export.h +++ b/drivers/usb/cdns3/host-export.h @@ -1,17 +1,25 @@ -/* SPDX-License-Identifier: GPL-2.0 */ /* - * Cadence USBSS DRD Driver - Host Export APIs - * - * Copyright (C) 2017-2018 NXP + * host-export.h - Host Export APIs * + * Copyright 2017 NXP * Authors: Peter Chen <peter.chen@nxp.com> + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html */ -#ifndef __LINUX_CDNS3_HOST_EXPORT -#define __LINUX_CDNS3_HOST_EXPORT + +#ifndef __DRIVERS_USB_CDNS3_HOST_H +#define __DRIVERS_USB_CDNS3_HOST_H #ifdef CONFIG_USB_CDNS3_HOST int cdns3_host_init(struct cdns3 *cdns); +void cdns3_host_remove(struct cdns3 *cdns); +void cdns3_host_driver_init(void); #else @@ -20,8 +28,16 @@ static inline int cdns3_host_init(struct cdns3 *cdns) return -ENXIO; } -static inline void cdns3_host_exit(struct cdns3 *cdns) { } +static inline void cdns3_host_remove(struct cdns3 *cdns) +{ + +} + +static inline void cdns3_host_driver_init(void) +{ + +} #endif /* CONFIG_USB_CDNS3_HOST */ -#endif /* __LINUX_CDNS3_HOST_EXPORT */ +#endif /* __DRIVERS_USB_CDNS3_HOST_H */ diff --git a/drivers/usb/cdns3/host.c b/drivers/usb/cdns3/host.c index ad788bf3fe4f..bddaa233913d 100644 --- a/drivers/usb/cdns3/host.c +++ b/drivers/usb/cdns3/host.c @@ -1,59 +1,276 @@ -// SPDX-License-Identifier: GPL-2.0 /* - * Cadence USBSS DRD Driver - host side - * - * Copyright (C) 2018-2019 Cadence Design Systems. - * Copyright (C) 2017-2018 NXP + * host.c - Cadence USB3 host controller driver * + * Copyright 2017 NXP * Authors: Peter Chen <peter.chen@nxp.com> - * Pawel Laszczak <pawell@cadence.com> + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html */ -#include <linux/platform_device.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/dma-mapping.h> +#include <linux/usb.h> +#include <linux/usb/hcd.h> +#include <linux/pm_runtime.h> +#include <linux/usb/of.h> + +#include "../host/xhci.h" + #include "core.h" -#include "drd.h" #include "host-export.h" +#include "cdns3-nxp-reg-def.h" + +#define XHCI_WAKEUP_STATUS (PORT_RC | PORT_PLC) + +static struct hc_driver __read_mostly xhci_cdns3_hc_driver; + +static void xhci_cdns3_quirks(struct device *dev, struct xhci_hcd *xhci) +{ + /* + * As of now platform drivers don't provide MSI support so we ensure + * here that the generic code does not try to make a pci_dev from our + * dev struct in order to setup MSI + */ + xhci->quirks |= (XHCI_PLAT | XHCI_AVOID_BEI | XHCI_CDNS_HOST); +} -static int __cdns3_host_init(struct cdns3 *cdns) +static int xhci_cdns3_setup(struct usb_hcd *hcd) { - struct platform_device *xhci; int ret; + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + u32 command; + + ret = xhci_gen_setup(hcd, xhci_cdns3_quirks); + if (ret) + return ret; + /* set usbcmd.EU3S */ + command = readl(&xhci->op_regs->command); + command |= CMD_PM_INDEX; + writel(command, &xhci->op_regs->command); + + return 0; +} + +struct cdns3_host { + struct device dev; + struct usb_hcd *hcd; + struct cdns3 *cdns; +}; + +static int xhci_cdns3_bus_suspend(struct usb_hcd *hcd) +{ + struct device *dev = hcd->self.controller; + struct cdns3_host *host = container_of(dev, struct cdns3_host, dev); + struct cdns3 *cdns = host->cdns; + void __iomem *xhci_regs = cdns->xhci_regs; + u32 value; + int ret; + + ret = xhci_bus_suspend(hcd); + if (ret) + return ret; - cdns3_drd_switch_host(cdns, 1); + value = readl(xhci_regs + XECP_AUX_CTRL_REG1); + value |= CFG_RXDET_P3_EN; + writel(value, xhci_regs + XECP_AUX_CTRL_REG1); - xhci = platform_device_alloc("xhci-hcd", PLATFORM_DEVID_AUTO); - if (!xhci) { - dev_err(cdns->dev, "couldn't allocate xHCI device\n"); + return 0; +} + +static const struct xhci_driver_overrides xhci_cdns3_overrides __initconst = { + .extra_priv_size = sizeof(struct xhci_hcd), + .reset = xhci_cdns3_setup, + .bus_suspend = xhci_cdns3_bus_suspend, +}; + +static irqreturn_t cdns3_host_irq(struct cdns3 *cdns) +{ + struct device *dev = cdns->host_dev; + struct usb_hcd *hcd; + + if (dev) + hcd = dev_get_drvdata(dev); + else + return IRQ_NONE; + + if (hcd) + return usb_hcd_irq(cdns->irq, hcd); + else + return IRQ_NONE; +} + +static void cdns3_host_release(struct device *dev) +{ + struct cdns3_host *host = container_of(dev, struct cdns3_host, dev); + + dev_dbg(dev, "releasing '%s'\n", dev_name(dev)); + kfree(host); +} + +static int cdns3_host_start(struct cdns3 *cdns) +{ + struct cdns3_host *host; + struct device *dev; + struct device *sysdev; + struct xhci_hcd *xhci; + int ret; + + host = kzalloc(sizeof(*host), GFP_KERNEL); + if (!host) return -ENOMEM; - } - xhci->dev.parent = cdns->dev; - cdns->host_dev = xhci; + dev = &host->dev; + dev->release = cdns3_host_release; + dev->parent = cdns->dev; + dev_set_name(dev, "xhci-cdns3"); + cdns->host_dev = dev; + host->cdns = cdns; + ret = device_register(dev); + if (ret) + goto err1; + + sysdev = cdns->dev; + /* Try to set 64-bit DMA first */ + if (WARN_ON(!sysdev->dma_mask)) + /* Platform did not initialize dma_mask */ + ret = dma_coerce_mask_and_coherent(sysdev, + DMA_BIT_MASK(64)); + else + ret = dma_set_mask_and_coherent(sysdev, DMA_BIT_MASK(64)); - ret = platform_device_add_resources(xhci, cdns->xhci_res, - CDNS3_XHCI_RESOURCES_NUM); + /* If setting 64-bit DMA mask fails, fall back to 32-bit DMA mask */ if (ret) { - dev_err(cdns->dev, "couldn't add resources to xHCI device\n"); - goto err1; + ret = dma_set_mask_and_coherent(sysdev, DMA_BIT_MASK(32)); + if (ret) + return ret; } + pm_runtime_set_active(dev); + pm_runtime_no_callbacks(dev); + pm_runtime_enable(dev); - ret = platform_device_add(xhci); - if (ret) { - dev_err(cdns->dev, "failed to register xHCI device\n"); - goto err1; + host->hcd = __usb_create_hcd(&xhci_cdns3_hc_driver, sysdev, dev, + dev_name(dev), NULL); + if (!host->hcd) { + ret = -ENOMEM; + goto err2; + } + + host->hcd->regs = cdns->xhci_regs; + host->hcd->rsrc_start = cdns->xhci_res->start; + host->hcd->rsrc_len = resource_size(cdns->xhci_res); + + device_wakeup_enable(host->hcd->self.controller); + + xhci = hcd_to_xhci(host->hcd); + + xhci->main_hcd = host->hcd; + xhci->shared_hcd = __usb_create_hcd(&xhci_cdns3_hc_driver, sysdev, dev, + dev_name(dev), host->hcd); + if (!xhci->shared_hcd) { + ret = -ENOMEM; + goto err3; } + host->hcd->tpl_support = of_usb_host_tpl_support(sysdev->of_node); + xhci->shared_hcd->tpl_support = host->hcd->tpl_support; + + ret = usb_add_hcd(host->hcd, 0, IRQF_SHARED); + if (ret) + goto err4; + + ret = usb_add_hcd(xhci->shared_hcd, 0, IRQF_SHARED); + if (ret) + goto err5; + + device_set_wakeup_capable(dev, true); + dev_dbg(dev, "%s ends\n", __func__); return 0; + +err5: + usb_remove_hcd(host->hcd); +err4: + usb_put_hcd(xhci->shared_hcd); +err3: + usb_put_hcd(host->hcd); +err2: + device_del(dev); err1: - platform_device_put(xhci); + put_device(dev); + cdns->host_dev = NULL; return ret; } -static void cdns3_host_exit(struct cdns3 *cdns) +static void cdns3_host_stop(struct cdns3 *cdns) { - platform_device_unregister(cdns->host_dev); - cdns->host_dev = NULL; - cdns3_drd_switch_host(cdns, 0); + struct device *dev = cdns->host_dev; + struct usb_hcd *hcd, *shared_hcd; + struct xhci_hcd *xhci; + + if (dev) { + hcd = dev_get_drvdata(dev); + xhci = hcd_to_xhci(hcd); + shared_hcd = xhci->shared_hcd; + xhci->xhc_state |= XHCI_STATE_REMOVING; + usb_remove_hcd(shared_hcd); + xhci->shared_hcd = NULL; + usb_remove_hcd(hcd); + synchronize_irq(cdns->irq); + usb_put_hcd(shared_hcd); + usb_put_hcd(hcd); + cdns->host_dev = NULL; + pm_runtime_set_suspended(dev); + pm_runtime_disable(dev); + device_del(dev); + put_device(dev); + } +} + +static int cdns3_host_suspend(struct cdns3 *cdns, bool do_wakeup) +{ + struct device *dev = cdns->host_dev; + struct xhci_hcd *xhci; + void __iomem *xhci_regs = cdns->xhci_regs; + u32 portsc_usb2, portsc_usb3; + int ret; + + if (!dev) + return 0; + + xhci = hcd_to_xhci(dev_get_drvdata(dev)); + ret = xhci_suspend(xhci, do_wakeup); + if (ret) + return ret; + + portsc_usb2 = readl(xhci_regs + 0x480); + portsc_usb3 = readl(xhci_regs + 0x490); + if ((portsc_usb2 & XHCI_WAKEUP_STATUS) || + (portsc_usb3 & XHCI_WAKEUP_STATUS)) { + dev_dbg(cdns->dev, "wakeup occurs\n"); + cdns3_role(cdns)->resume(cdns, false); + return -EBUSY; + } + + return ret; +} + +static int cdns3_host_resume(struct cdns3 *cdns, bool hibernated) +{ + struct device *dev = cdns->host_dev; + struct xhci_hcd *xhci; + + if (!dev) + return 0; + + xhci = hcd_to_xhci(dev_get_drvdata(dev)); + return xhci_resume(xhci, hibernated); } int cdns3_host_init(struct cdns3 *cdns) @@ -64,12 +281,23 @@ int cdns3_host_init(struct cdns3 *cdns) if (!rdrv) return -ENOMEM; - rdrv->start = __cdns3_host_init; - rdrv->stop = cdns3_host_exit; - rdrv->state = CDNS3_ROLE_STATE_INACTIVE; + rdrv->start = cdns3_host_start; + rdrv->stop = cdns3_host_stop; + rdrv->irq = cdns3_host_irq; + rdrv->suspend = cdns3_host_suspend; + rdrv->resume = cdns3_host_resume; rdrv->name = "host"; - - cdns->roles[USB_ROLE_HOST] = rdrv; + cdns->roles[CDNS3_ROLE_HOST] = rdrv; return 0; } + +void cdns3_host_remove(struct cdns3 *cdns) +{ + cdns3_host_stop(cdns); +} + +void __init cdns3_host_driver_init(void) +{ + xhci_init_driver(&xhci_cdns3_hc_driver, &xhci_cdns3_overrides); +} diff --git a/drivers/usb/cdns3/io.h b/drivers/usb/cdns3/io.h new file mode 100644 index 000000000000..c16edbf54b8b --- /dev/null +++ b/drivers/usb/cdns3/io.h @@ -0,0 +1,35 @@ +/** + * io.h - Cadence USB3 IO Header + * + * Copyright (C) 2016 Cadence Design Systems - https://www.cadence.com/ + * + * Authors: Rafal Ozieblo <rafalo@cadence.com>, + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef __DRIVERS_USB_CDNS_IO_H +#define __DRIVERS_USB_CDNS_IO_H + +#include <linux/io.h> + +static inline u32 cdns_readl(uint32_t __iomem *reg) +{ + u32 value = 0; + + value = readl(reg); + return value; +} + +static inline void cdns_writel(uint32_t __iomem *reg, u32 value) +{ + writel(value, reg); +} + + +#endif /* __DRIVERS_USB_CDNS_IO_H */ diff --git a/drivers/usb/cdns3/trace.c b/drivers/usb/cdns3/trace.c index 459fa72d9c74..9431eb86d4ff 100644 --- a/drivers/usb/cdns3/trace.c +++ b/drivers/usb/cdns3/trace.c @@ -2,10 +2,22 @@ /* * USBSS device controller driver Trace Support * - * Copyright (C) 2018-2019 Cadence. + * Copyright (C) 2018 Cadence. * * Author: Pawel Laszczak <pawell@cadence.com> */ #define CREATE_TRACE_POINTS #include "trace.h" + +void cdns3_dbg(struct cdns3_device *priv_dev, const char *fmt, ...) +{ + struct va_format vaf; + va_list args; + + va_start(args, fmt); + vaf.fmt = fmt; + vaf.va = &args; + trace_cdns3_log(priv_dev, &vaf); + va_end(args); +} diff --git a/drivers/usb/cdns3/trace.h b/drivers/usb/cdns3/trace.h index e92348c9b4d7..7f852a7e9ab5 100644 --- a/drivers/usb/cdns3/trace.h +++ b/drivers/usb/cdns3/trace.h @@ -3,7 +3,7 @@ * USBSS device controller driver. * Trace support header file. * - * Copyright (C) 2018-2019 Cadence. + * Copyright (C) 2018 Cadence. * * Author: Pawel Laszczak <pawell@cadence.com> */ @@ -18,55 +18,23 @@ #include <linux/tracepoint.h> #include <asm/byteorder.h> #include <linux/usb/ch9.h> -#include "core.h" #include "gadget.h" #include "debug.h" #define CDNS3_MSG_MAX 500 -TRACE_EVENT(cdns3_halt, - TP_PROTO(struct cdns3_endpoint *ep_priv, u8 halt, u8 flush), - TP_ARGS(ep_priv, halt, flush), +TRACE_EVENT(cdns3_log, + TP_PROTO(struct cdns3_device *priv_dev, struct va_format *vaf), + TP_ARGS(priv_dev, vaf), TP_STRUCT__entry( - __string(name, ep_priv->name) - __field(u8, halt) - __field(u8, flush) + __string(name, dev_name(priv_dev->dev)) + __dynamic_array(char, msg, CDNS3_MSG_MAX) ), TP_fast_assign( - __assign_str(name, ep_priv->name); - __entry->halt = halt; - __entry->flush = flush; + __assign_str(name, dev_name(priv_dev->dev)); + vsnprintf(__get_str(msg), CDNS3_MSG_MAX, vaf->fmt, *vaf->va); ), - TP_printk("Halt %s for %s: %s", __entry->flush ? " and flush" : "", - __get_str(name), __entry->halt ? "set" : "cleared") -); - -TRACE_EVENT(cdns3_wa1, - TP_PROTO(struct cdns3_endpoint *ep_priv, char *msg), - TP_ARGS(ep_priv, msg), - TP_STRUCT__entry( - __string(ep_name, ep_priv->name) - __string(msg, msg) - ), - TP_fast_assign( - __assign_str(ep_name, ep_priv->name); - __assign_str(msg, msg); - ), - TP_printk("WA1: %s %s", __get_str(ep_name), __get_str(msg)) -); - -TRACE_EVENT(cdns3_wa2, - TP_PROTO(struct cdns3_endpoint *ep_priv, char *msg), - TP_ARGS(ep_priv, msg), - TP_STRUCT__entry( - __string(ep_name, ep_priv->name) - __string(msg, msg) - ), - TP_fast_assign( - __assign_str(ep_name, ep_priv->name); - __assign_str(msg, msg); - ), - TP_printk("WA2: %s %s", __get_str(ep_name), __get_str(msg)) + TP_printk("%s: %s", __get_str(name), __get_str(msg)) ); DECLARE_EVENT_CLASS(cdns3_log_doorbell, @@ -80,7 +48,7 @@ DECLARE_EVENT_CLASS(cdns3_log_doorbell, __assign_str(name, ep_name); __entry->ep_trbaddr = ep_trbaddr; ), - TP_printk("%s, ep_trbaddr %08x", __get_str(name), + TP_printk("//Ding Dong %s, ep_trbaddr %08x", __get_str(name), __entry->ep_trbaddr) ); @@ -230,9 +198,9 @@ DECLARE_EVENT_CLASS(cdns3_log_request, " trb: [start:%d, end:%d: virt addr %pa], flags:%x ", __get_str(name), __entry->req, __entry->buf, __entry->actual, __entry->length, - __entry->zero ? "Z" : "z", - __entry->short_not_ok ? "S" : "s", - __entry->no_interrupt ? "I" : "i", + __entry->zero ? "zero | " : "", + __entry->short_not_ok ? "short | " : "", + __entry->no_interrupt ? "no int" : "", __entry->status, __entry->start_trb, __entry->end_trb, @@ -266,21 +234,6 @@ DEFINE_EVENT(cdns3_log_request, cdns3_gadget_giveback, TP_ARGS(req) ); -TRACE_EVENT(cdns3_ep0_queue, - TP_PROTO(struct cdns3_device *dev_priv, struct usb_request *request), - TP_ARGS(dev_priv, request), - TP_STRUCT__entry( - __field(int, dir) - __field(int, length) - ), - TP_fast_assign( - __entry->dir = dev_priv->ep0_data_dir; - __entry->length = request->length; - ), - TP_printk("Queue to ep0%s length: %u", __entry->dir ? "in" : "out", - __entry->length) -); - DECLARE_EVENT_CLASS(cdns3_log_aligned_request, TP_PROTO(struct cdns3_request *priv_req), TP_ARGS(priv_req), @@ -302,9 +255,9 @@ DECLARE_EVENT_CLASS(cdns3_log_aligned_request, __entry->aligned_dma = priv_req->aligned_buf->dma; __entry->aligned_buf_size = priv_req->aligned_buf->size; ), - TP_printk("%s: req: %p, req buf %p, dma %pad a_buf %p a_dma %pad, size %d", - __get_str(name), __entry->req, __entry->buf, &__entry->dma, - __entry->aligned_buf, &__entry->aligned_dma, + TP_printk("%s: req: %p, req buf %p, dma %08llx a_buf %p a_dma %08llx, size %d", + __get_str(name), __entry->req, __entry->buf, __entry->dma, + __entry->aligned_buf, __entry->aligned_dma, __entry->aligned_buf_size ) ); @@ -422,7 +375,7 @@ DECLARE_EVENT_CLASS(cdns3_log_ep, __entry->maxburst, __entry->enqueue, __entry->dequeue, __entry->flags & EP_ENABLED ? "EN | " : "", - __entry->flags & EP_STALLED ? "STALLED | " : "", + __entry->flags & EP_STALL ? "STALL | " : "", __entry->flags & EP_WEDGE ? "WEDGE | " : "", __entry->flags & EP_TRANSFER_STARTED ? "STARTED | " : "", __entry->flags & EP_UPDATE_EP_TRBADDR ? "UPD TRB | " : "", diff --git a/drivers/usb/chipidea/bits.h b/drivers/usb/chipidea/bits.h index 98da99510be7..d83ecfab9550 100644 --- a/drivers/usb/chipidea/bits.h +++ b/drivers/usb/chipidea/bits.h @@ -70,6 +70,9 @@ #define PORTSC_FPR BIT(6) #define PORTSC_SUSP BIT(7) #define PORTSC_HSP BIT(9) +#define PORTSC_LS (BIT(11) | BIT(10)) +#define PORTSC_LS_J BIT(11) +#define PORTSC_LS_K BIT(10) #define PORTSC_PP BIT(12) #define PORTSC_PTC (0x0FUL << 16) #define PORTSC_WKCN BIT(20) @@ -78,6 +81,7 @@ #define PORTSC_PFSC BIT(24) #define PORTSC_PTS(d) \ (u32)((((d) & 0x3) << 30) | (((d) & 0x4) ? BIT(25) : 0)) +#define PORT_SPEED_LOW(d) ((((d) >> 26) & 0x3) == 1) #define PORTSC_PTW BIT(28) #define PORTSC_STS BIT(29) diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h index 6911aef500e9..487991ded6d2 100644 --- a/drivers/usb/chipidea/ci.h +++ b/drivers/usb/chipidea/ci.h @@ -126,12 +126,16 @@ enum ci_revision { * @start: start this role * @stop: stop this role * @irq: irq handler for this role + * @suspend: system suspend handler for this role + * @resume: system resume handler for this role * @name: role name string (host/gadget) */ struct ci_role_driver { int (*start)(struct ci_hdrc *); void (*stop)(struct ci_hdrc *); irqreturn_t (*irq)(struct ci_hdrc *); + void (*suspend)(struct ci_hdrc *); + void (*resume)(struct ci_hdrc *, bool power_lost); const char *name; }; @@ -203,6 +207,9 @@ struct hw_bank { * @in_lpm: if the core in low power mode * @wakeup_int: if wakeup interrupt occur * @rev: The revision number for controller + * @mutex: protect code from concorrent running + * @power_lost_work: work item when controller power is lost + * @power_lost_wq: work queue for controller power is lost */ struct ci_hdrc { struct device *dev; @@ -256,6 +263,20 @@ struct ci_hdrc { bool in_lpm; bool wakeup_int; enum ci_revision rev; + /* register save area for suspend&resume */ + u32 pm_command; + u32 pm_status; + u32 pm_intr_enable; + u32 pm_frame_index; + u32 pm_segment; + u32 pm_frame_list; + u32 pm_async_next; + u32 pm_configured_flag; + u32 pm_portsc; + u32 pm_usbmode; + struct work_struct power_lost_work; + struct workqueue_struct *power_lost_wq; + struct mutex mutex; }; static inline struct ci_role_driver *ci_role(struct ci_hdrc *ci) @@ -275,9 +296,21 @@ static inline int ci_role_start(struct ci_hdrc *ci, enum ci_role role) return -ENXIO; ret = ci->roles[role]->start(ci); - if (!ret) - ci->role = role; - return ret; + if (ret) + return ret; + + ci->role = role; + + if (ci->usb_phy) { + if (role == CI_ROLE_HOST) + usb_phy_set_mode(ci->usb_phy, + CUR_USB_MODE_HOST); + else + usb_phy_set_mode(ci->usb_phy, + CUR_USB_MODE_DEVICE); + } + + return 0; } static inline void ci_role_stop(struct ci_hdrc *ci) @@ -290,6 +323,9 @@ static inline void ci_role_stop(struct ci_hdrc *ci) ci->role = CI_ROLE_END; ci->roles[role]->stop(ci); + + if (ci->usb_phy) + usb_phy_set_mode(ci->usb_phy, CUR_USB_MODE_NONE); } static inline enum usb_role ci_role_to_usb_role(struct ci_hdrc *ci) @@ -453,8 +489,10 @@ u8 hw_port_test_get(struct ci_hdrc *ci); void hw_phymode_configure(struct ci_hdrc *ci); void ci_platform_configure(struct ci_hdrc *ci); +int hw_controller_reset(struct ci_hdrc *ci); void dbg_create_files(struct ci_hdrc *ci); void dbg_remove_files(struct ci_hdrc *ci); +void ci_hdrc_enter_lpm(struct ci_hdrc *ci, bool enable); #endif /* __DRIVERS_USB_CHIPIDEA_CI_H */ diff --git a/drivers/usb/chipidea/ci_hdrc_imx.c b/drivers/usb/chipidea/ci_hdrc_imx.c index df8812c30640..48ff355c2255 100644 --- a/drivers/usb/chipidea/ci_hdrc_imx.c +++ b/drivers/usb/chipidea/ci_hdrc_imx.c @@ -14,6 +14,7 @@ #include <linux/clk.h> #include <linux/pinctrl/consumer.h> #include <linux/pm_qos.h> +#include <linux/busfreq-imx.h> #include "ci.h" #include "ci_hdrc_imx.h" @@ -165,6 +166,11 @@ static struct imx_usbmisc_data *usbmisc_get_init_data(struct device *dev) if (of_usb_get_phy_mode(np) == USBPHY_INTERFACE_MODE_ULPI) data->ulpi = 1; + of_property_read_u32(np, "picophy,pre-emp-curr-control", + &data->emp_curr_control); + of_property_read_u32(np, "picophy,dc-vol-level-adjust", + &data->dc_vol_level_adjust); + return data; } @@ -271,14 +277,18 @@ static int ci_hdrc_imx_notify_event(struct ci_hdrc *ci, unsigned int event) struct device *dev = ci->dev->parent; struct ci_hdrc_imx_data *data = dev_get_drvdata(dev); int ret = 0; + struct imx_usbmisc_data *mdata = data->usbmisc_data; switch (event) { case CI_HDRC_IMX_HSIC_ACTIVE_EVENT: - ret = pinctrl_select_state(data->pinctrl, - data->pinctrl_hsic_active); - if (ret) - dev_err(dev, "hsic_active select failed, err=%d\n", - ret); + if (data->pinctrl) { + ret = pinctrl_select_state(data->pinctrl, + data->pinctrl_hsic_active); + if (ret) + dev_err(dev, + "hsic_active select failed, err=%d\n", + ret); + } break; case CI_HDRC_IMX_HSIC_SUSPEND_EVENT: ret = imx_usbmisc_hsic_set_connect(data->usbmisc_data); @@ -286,6 +296,12 @@ static int ci_hdrc_imx_notify_event(struct ci_hdrc *ci, unsigned int event) dev_err(dev, "hsic_set_connect failed, err=%d\n", ret); break; + case CI_HDRC_CONTROLLER_VBUS_EVENT: + if (ci->vbus_active) + ret = imx_usbmisc_charger_detection(mdata, true); + else + ret = imx_usbmisc_charger_detection(mdata, false); + break; default: break; } @@ -306,7 +322,6 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev) const struct ci_hdrc_imx_platform_flag *imx_platform_flag; struct device_node *np = pdev->dev.of_node; struct device *dev = &pdev->dev; - struct pinctrl_state *pinctrl_hsic_idle; of_id = of_match_device(ci_hdrc_imx_dt_ids, dev); if (!of_id) @@ -330,12 +345,42 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev) pdata.flags |= CI_HDRC_IMX_IS_HSIC; data->usbmisc_data->hsic = 1; data->pinctrl = devm_pinctrl_get(dev); - if (IS_ERR(data->pinctrl)) { - dev_err(dev, "pinctrl get failed, err=%ld\n", + if (PTR_ERR(data->pinctrl) == -ENODEV) + data->pinctrl = NULL; + else if (IS_ERR(data->pinctrl)) { + if (PTR_ERR(data->pinctrl) != -EPROBE_DEFER) + dev_err(dev, "pinctrl get failed, err=%ld\n", PTR_ERR(data->pinctrl)); return PTR_ERR(data->pinctrl); } + data->hsic_pad_regulator = + devm_regulator_get_optional(dev, "hsic"); + if (PTR_ERR(data->hsic_pad_regulator) == -ENODEV) { + /* no pad regualator is needed */ + data->hsic_pad_regulator = NULL; + } else if (IS_ERR(data->hsic_pad_regulator)) { + if (PTR_ERR(data->hsic_pad_regulator) != -EPROBE_DEFER) + dev_err(dev, + "Get HSIC pad regulator error: %ld\n", + PTR_ERR(data->hsic_pad_regulator)); + return PTR_ERR(data->hsic_pad_regulator); + } + + if (data->hsic_pad_regulator) { + ret = regulator_enable(data->hsic_pad_regulator); + if (ret) { + dev_err(dev, + "Failed to enable HSIC pad regulator\n"); + return ret; + } + } + } + + /* HSIC pinctrl handling */ + if (data->pinctrl) { + struct pinctrl_state *pinctrl_hsic_idle; + pinctrl_hsic_idle = pinctrl_lookup_state(data->pinctrl, "idle"); if (IS_ERR(pinctrl_hsic_idle)) { dev_err(dev, @@ -358,33 +403,13 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev) PTR_ERR(data->pinctrl_hsic_active)); return PTR_ERR(data->pinctrl_hsic_active); } - - data->hsic_pad_regulator = devm_regulator_get(dev, "hsic"); - if (PTR_ERR(data->hsic_pad_regulator) == -EPROBE_DEFER) { - return -EPROBE_DEFER; - } else if (PTR_ERR(data->hsic_pad_regulator) == -ENODEV) { - /* no pad regualator is needed */ - data->hsic_pad_regulator = NULL; - } else if (IS_ERR(data->hsic_pad_regulator)) { - dev_err(dev, "Get HSIC pad regulator error: %ld\n", - PTR_ERR(data->hsic_pad_regulator)); - return PTR_ERR(data->hsic_pad_regulator); - } - - if (data->hsic_pad_regulator) { - ret = regulator_enable(data->hsic_pad_regulator); - if (ret) { - dev_err(dev, - "Failed to enable HSIC pad regulator\n"); - return ret; - } - } } if (pdata.flags & CI_HDRC_PMQOS) pm_qos_add_request(&data->pm_qos_req, PM_QOS_CPU_DMA_LATENCY, 0); + request_bus_freq(BUS_FREQ_HIGH); ret = imx_get_clks(dev); if (ret) goto disable_hsic_regulator; @@ -404,6 +429,8 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev) } pdata.usb_phy = data->phy; + if (data->usbmisc_data) + data->usbmisc_data->usb_phy = data->phy; if ((of_device_is_compatible(np, "fsl,imx53-usb") || of_device_is_compatible(np, "fsl,imx51-usb")) && pdata.usb_phy && @@ -416,6 +443,11 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev) if (pdata.flags & CI_HDRC_SUPPORTS_RUNTIME_PM) data->supports_runtime_pm = true; + if (of_find_property(np, "ci-disable-lpm", NULL)) { + data->supports_runtime_pm = false; + pdata.flags &= ~CI_HDRC_SUPPORTS_RUNTIME_PM; + } + ret = imx_usbmisc_init(data->usbmisc_data); if (ret) { dev_err(dev, "usbmisc init failed, ret=%d\n", ret); @@ -433,12 +465,24 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev) goto err_clk; } + if (!IS_ERR(pdata.id_extcon.edev) || + of_property_read_bool(np, "usb-role-switch")) + data->usbmisc_data->ext_id = 1; + + if (!IS_ERR(pdata.vbus_extcon.edev) || + of_property_read_bool(np, "usb-role-switch")) + data->usbmisc_data->ext_vbus = 1; + ret = imx_usbmisc_init_post(data->usbmisc_data); if (ret) { dev_err(dev, "usbmisc post failed, ret=%d\n", ret); goto disable_device; } + /* usbmisc needs to know dr mode to choose wakeup setting */ + data->usbmisc_data->available_role = + ci_hdrc_query_available_role(data->ci_pdev); + if (data->supports_runtime_pm) { pm_runtime_set_active(dev); pm_runtime_enable(dev); @@ -453,6 +497,7 @@ disable_device: err_clk: imx_disable_unprepare_clks(dev); disable_hsic_regulator: + release_bus_freq(BUS_FREQ_HIGH); if (data->hsic_pad_regulator) /* don't overwrite original ret (cf. EPROBE_DEFER) */ regulator_disable(data->hsic_pad_regulator); @@ -466,6 +511,10 @@ static int ci_hdrc_imx_remove(struct platform_device *pdev) { struct ci_hdrc_imx_data *data = platform_get_drvdata(pdev); + /* usbmisc needs to know dr mode to choose wakeup setting */ + data->usbmisc_data->available_role = + ci_hdrc_query_available_role(data->ci_pdev); + if (data->supports_runtime_pm) { pm_runtime_get_sync(&pdev->dev); pm_runtime_disable(&pdev->dev); @@ -477,6 +526,7 @@ static int ci_hdrc_imx_remove(struct platform_device *pdev) usb_phy_shutdown(data->phy); if (data->ci_pdev) { imx_disable_unprepare_clks(&pdev->dev); + release_bus_freq(BUS_FREQ_HIGH); if (data->plat_data->flags & CI_HDRC_PMQOS) pm_qos_remove_request(&data->pm_qos_req); if (data->hsic_pad_regulator) @@ -491,20 +541,24 @@ static void ci_hdrc_imx_shutdown(struct platform_device *pdev) ci_hdrc_imx_remove(pdev); } -static int __maybe_unused imx_controller_suspend(struct device *dev) +static int __maybe_unused imx_controller_suspend(struct device *dev, + pm_message_t msg) { struct ci_hdrc_imx_data *data = dev_get_drvdata(dev); int ret = 0; dev_dbg(dev, "at %s\n", __func__); - ret = imx_usbmisc_hsic_set_clk(data->usbmisc_data, false); + ret = imx_usbmisc_suspend(data->usbmisc_data, + PMSG_IS_AUTO(msg) || device_may_wakeup(dev)); if (ret) { - dev_err(dev, "usbmisc hsic_set_clk failed, ret=%d\n", ret); + dev_err(dev, + "usbmisc suspend failed, ret=%d\n", ret); return ret; } imx_disable_unprepare_clks(dev); + release_bus_freq(BUS_FREQ_HIGH); if (data->plat_data->flags & CI_HDRC_PMQOS) pm_qos_remove_request(&data->pm_qos_req); @@ -513,44 +567,37 @@ static int __maybe_unused imx_controller_suspend(struct device *dev) return 0; } -static int __maybe_unused imx_controller_resume(struct device *dev) +static int __maybe_unused imx_controller_resume(struct device *dev, + pm_message_t msg) { struct ci_hdrc_imx_data *data = dev_get_drvdata(dev); int ret = 0; dev_dbg(dev, "at %s\n", __func__); - if (!data->in_lpm) { - WARN_ON(1); + if (!data->in_lpm) return 0; - } if (data->plat_data->flags & CI_HDRC_PMQOS) pm_qos_add_request(&data->pm_qos_req, PM_QOS_CPU_DMA_LATENCY, 0); + request_bus_freq(BUS_FREQ_HIGH); ret = imx_prepare_enable_clks(dev); if (ret) return ret; data->in_lpm = false; - ret = imx_usbmisc_set_wakeup(data->usbmisc_data, false); + ret = imx_usbmisc_resume(data->usbmisc_data, + PMSG_IS_AUTO(msg) || device_may_wakeup(dev)); if (ret) { - dev_err(dev, "usbmisc set_wakeup failed, ret=%d\n", ret); + dev_err(dev, "usbmisc resume failed, ret=%d\n", ret); goto clk_disable; } - ret = imx_usbmisc_hsic_set_clk(data->usbmisc_data, true); - if (ret) { - dev_err(dev, "usbmisc hsic_set_clk failed, ret=%d\n", ret); - goto hsic_set_clk_fail; - } - return 0; -hsic_set_clk_fail: - imx_usbmisc_set_wakeup(data->usbmisc_data, true); clk_disable: imx_disable_unprepare_clks(dev); return ret; @@ -566,16 +613,12 @@ static int __maybe_unused ci_hdrc_imx_suspend(struct device *dev) /* The core's suspend doesn't run */ return 0; - if (device_may_wakeup(dev)) { - ret = imx_usbmisc_set_wakeup(data->usbmisc_data, true); - if (ret) { - dev_err(dev, "usbmisc set_wakeup failed, ret=%d\n", - ret); - return ret; - } - } + ret = imx_controller_suspend(dev, PMSG_SUSPEND); + if (ret) + return ret; - return imx_controller_suspend(dev); + pinctrl_pm_select_sleep_state(dev); + return ret; } static int __maybe_unused ci_hdrc_imx_resume(struct device *dev) @@ -583,7 +626,8 @@ static int __maybe_unused ci_hdrc_imx_resume(struct device *dev) struct ci_hdrc_imx_data *data = dev_get_drvdata(dev); int ret; - ret = imx_controller_resume(dev); + pinctrl_pm_select_default_state(dev); + ret = imx_controller_resume(dev, PMSG_RESUME); if (!ret && data->supports_runtime_pm) { pm_runtime_disable(dev); pm_runtime_set_active(dev); @@ -596,25 +640,16 @@ static int __maybe_unused ci_hdrc_imx_resume(struct device *dev) static int __maybe_unused ci_hdrc_imx_runtime_suspend(struct device *dev) { struct ci_hdrc_imx_data *data = dev_get_drvdata(dev); - int ret; - if (data->in_lpm) { - WARN_ON(1); + if (data->in_lpm) return 0; - } - - ret = imx_usbmisc_set_wakeup(data->usbmisc_data, true); - if (ret) { - dev_err(dev, "usbmisc set_wakeup failed, ret=%d\n", ret); - return ret; - } - return imx_controller_suspend(dev); + return imx_controller_suspend(dev, PMSG_AUTO_SUSPEND); } static int __maybe_unused ci_hdrc_imx_runtime_resume(struct device *dev) { - return imx_controller_resume(dev); + return imx_controller_resume(dev, PMSG_AUTO_RESUME); } static const struct dev_pm_ops ci_hdrc_imx_pm_ops = { diff --git a/drivers/usb/chipidea/ci_hdrc_imx.h b/drivers/usb/chipidea/ci_hdrc_imx.h index c842e03f8767..c910f74474e0 100644 --- a/drivers/usb/chipidea/ci_hdrc_imx.h +++ b/drivers/usb/chipidea/ci_hdrc_imx.h @@ -22,12 +22,19 @@ struct imx_usbmisc_data { unsigned int evdo:1; /* set external vbus divider option */ unsigned int ulpi:1; /* connected to an ULPI phy */ unsigned int hsic:1; /* HSIC controlller */ + enum usb_dr_mode available_role; + unsigned int ext_id:1; /* ID from exteranl event */ + unsigned int ext_vbus:1; /* Vbus from exteranl event */ + int emp_curr_control; + int dc_vol_level_adjust; + struct usb_phy *usb_phy; }; int imx_usbmisc_init(struct imx_usbmisc_data *data); int imx_usbmisc_init_post(struct imx_usbmisc_data *data); -int imx_usbmisc_set_wakeup(struct imx_usbmisc_data *data, bool enabled); int imx_usbmisc_hsic_set_connect(struct imx_usbmisc_data *data); -int imx_usbmisc_hsic_set_clk(struct imx_usbmisc_data *data, bool on); +int imx_usbmisc_charger_detection(struct imx_usbmisc_data *data, bool connect); +int imx_usbmisc_suspend(struct imx_usbmisc_data *data, bool wakeup); +int imx_usbmisc_resume(struct imx_usbmisc_data *data, bool wakeup); #endif /* __DRIVER_USB_CHIPIDEA_CI_HDRC_IMX_H */ diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index 98ee575ee500..7f17da3f9f79 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -220,7 +220,7 @@ static void hw_wait_phy_stable(void) } /* The PHY enters/leaves low power mode */ -static void ci_hdrc_enter_lpm(struct ci_hdrc *ci, bool enable) +void ci_hdrc_enter_lpm(struct ci_hdrc *ci, bool enable) { enum ci_hw_regs reg = ci->hw_bank.lpm ? OP_DEVLC : OP_PORTSC; bool lpm = !!(hw_read(ci, reg, PORTSC_PHCD(ci->hw_bank.lpm))); @@ -475,7 +475,7 @@ void ci_platform_configure(struct ci_hdrc *ci) * * This function returns an error code */ -static int hw_controller_reset(struct ci_hdrc *ci) +int hw_controller_reset(struct ci_hdrc *ci) { int count = 0; @@ -899,6 +899,33 @@ void ci_hdrc_remove_device(struct platform_device *pdev) } EXPORT_SYMBOL_GPL(ci_hdrc_remove_device); +/** + * ci_hdrc_query_available_role: get runtime available operation mode + * + * The glue layer can get current operation mode (host/peripheral/otg) + * This function should be called after ci core device has created. + * + * @pdev: the platform device of ci core. + * + * Return USB_DR_MODE_XXX. + */ +enum usb_dr_mode ci_hdrc_query_available_role(struct platform_device *pdev) +{ + struct ci_hdrc *ci = platform_get_drvdata(pdev); + + if (!ci) + return USB_DR_MODE_UNKNOWN; + if (ci->roles[CI_ROLE_HOST] && ci->roles[CI_ROLE_GADGET]) + return USB_DR_MODE_OTG; + else if (ci->roles[CI_ROLE_HOST]) + return USB_DR_MODE_HOST; + else if (ci->roles[CI_ROLE_GADGET]) + return USB_DR_MODE_PERIPHERAL; + else + return USB_DR_MODE_UNKNOWN; +} +EXPORT_SYMBOL_GPL(ci_hdrc_query_available_role); + static inline void ci_role_destroy(struct ci_hdrc *ci) { ci_hdrc_gadget_destroy(ci); @@ -973,6 +1000,53 @@ static struct attribute *ci_attrs[] = { }; ATTRIBUTE_GROUPS(ci); +static enum ci_role ci_get_role(struct ci_hdrc *ci) +{ + if (ci->roles[CI_ROLE_HOST] && ci->roles[CI_ROLE_GADGET]) { + if (ci->is_otg) { + hw_write_otgsc(ci, OTGSC_IDIE, OTGSC_IDIE); + return ci_otg_role(ci); + } else { + /* + * If the controller is not OTG capable, but support + * role switch, the defalt role is gadget, and the + * user can switch it through debugfs. + */ + return CI_ROLE_GADGET; + } + } else { + return ci->roles[CI_ROLE_HOST] + ? CI_ROLE_HOST + : CI_ROLE_GADGET; + } +} + +static void ci_start_new_role(struct ci_hdrc *ci) +{ + enum ci_role role = ci_get_role(ci); + + if (ci->role != role) { + ci_handle_id_switch(ci); + } else if (role == CI_ROLE_GADGET) { + if (ci->vbus_active) + usb_gadget_vbus_disconnect(&ci->gadget); + ci_handle_vbus_connected(ci); + } +} + +static void ci_power_lost_work(struct work_struct *work) +{ + struct ci_hdrc *ci = container_of(work, struct ci_hdrc, + power_lost_work); + + disable_irq_nosync(ci->irq); + pm_runtime_get_sync(ci->dev); + if (!ci_otg_is_fsm_mode(ci)) + ci_start_new_role(ci); + pm_runtime_put_sync(ci->dev); + enable_irq(ci->irq); +} + static int ci_hdrc_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -1143,11 +1217,15 @@ static int ci_hdrc_probe(struct platform_device *pdev) : CI_ROLE_GADGET; } - if (!ci_otg_is_fsm_mode(ci)) { - /* only update vbus status for peripheral */ - if (ci->role == CI_ROLE_GADGET) - ci_handle_vbus_change(ci); + ci->role = ci_get_role(ci); + /* only update vbus status for peripheral */ + if (ci->role == CI_ROLE_GADGET) { + /* Let DP pull down if it isn't currently */ + hw_write(ci, OP_USBCMD, USBCMD_RS, 0); + ci_handle_vbus_connected(ci); + } + if (!ci_otg_is_fsm_mode(ci)) { ret = ci_role_start(ci, ci->role); if (ret) { dev_err(dev, "can't start %s role\n", @@ -1178,9 +1256,20 @@ static int ci_hdrc_probe(struct platform_device *pdev) device_set_wakeup_capable(&pdev->dev, true); dbg_create_files(ci); + /* Init workqueue for controller power lost handling */ + ci->power_lost_wq = create_freezable_workqueue("ci_power_lost"); + if (!ci->power_lost_wq) { + dev_err(ci->dev, "can't create power_lost workqueue\n"); + goto remove_debug; + } + + INIT_WORK(&ci->power_lost_work, ci_power_lost_work); + mutex_init(&ci->mutex); return 0; +remove_debug: + dbg_remove_files(ci); stop: if (ci->role_switch) usb_role_switch_unregister(ci->role_switch); @@ -1212,6 +1301,8 @@ static int ci_hdrc_remove(struct platform_device *pdev) pm_runtime_put_noidle(&pdev->dev); } + flush_workqueue(ci->power_lost_wq); + destroy_workqueue(ci->power_lost_wq); dbg_remove_files(ci); ci_role_destroy(ci); ci_hdrc_enter_lpm(ci, true); @@ -1239,13 +1330,10 @@ static void ci_otg_fsm_wakeup_by_srp(struct ci_hdrc *ci) { if ((ci->fsm.otg->state == OTG_STATE_A_IDLE) && (ci->fsm.a_bus_drop == 1) && (ci->fsm.a_bus_req == 0)) { - if (!hw_read_otgsc(ci, OTGSC_ID)) { - ci->fsm.a_srp_det = 1; - ci->fsm.a_bus_drop = 0; - } else { + if (!hw_read_otgsc(ci, OTGSC_ID)) + otg_add_timer(&ci->fsm, A_DP_END); + else ci->fsm.id = 1; - } - ci_otg_queue_work(ci); } } @@ -1268,10 +1356,8 @@ static int ci_controller_resume(struct device *dev) dev_dbg(dev, "at %s\n", __func__); - if (!ci->in_lpm) { - WARN_ON(1); + if (!ci->in_lpm) return 0; - } ci_hdrc_enter_lpm(ci, false); @@ -1303,6 +1389,7 @@ static int ci_suspend(struct device *dev) { struct ci_hdrc *ci = dev_get_drvdata(dev); + flush_workqueue(ci->power_lost_wq); if (ci->wq) flush_workqueue(ci->wq); /* @@ -1319,6 +1406,10 @@ static int ci_suspend(struct device *dev) return 0; } + /* Extra routine per role before system suspend */ + if (ci->role != CI_ROLE_END && ci_role(ci)->suspend) + ci_role(ci)->suspend(ci); + if (device_may_wakeup(dev)) { if (ci_otg_is_fsm_mode(ci)) ci_otg_fsm_suspend_for_srp(ci); @@ -1335,8 +1426,18 @@ static int ci_suspend(struct device *dev) static int ci_resume(struct device *dev) { struct ci_hdrc *ci = dev_get_drvdata(dev); + bool power_lost = false; + u32 sample_reg_val; int ret; + /* Check if controller resume from power lost */ + sample_reg_val = hw_read(ci, OP_ENDPTLISTADDR, ~0); + if (sample_reg_val == 0) + power_lost = true; + else if (sample_reg_val == 0xFFFFFFFF) + /* Restore value 0 if it was set for power lost check */ + hw_write(ci, OP_ENDPTLISTADDR, ~0, 0); + if (device_may_wakeup(dev)) disable_irq_wake(ci->irq); @@ -1344,6 +1445,19 @@ static int ci_resume(struct device *dev) if (ret) return ret; + if (power_lost) { + /* shutdown and re-init for phy */ + ci_usb_phy_exit(ci); + ci_usb_phy_init(ci); + } + + /* Extra routine per role after system resume */ + if (ci->role != CI_ROLE_END && ci_role(ci)->resume) + ci_role(ci)->resume(ci, power_lost); + + if (power_lost) + queue_work(ci->power_lost_wq, &ci->power_lost_work); + if (ci->supports_runtime_pm) { pm_runtime_disable(dev); pm_runtime_set_active(dev); @@ -1360,10 +1474,8 @@ static int ci_runtime_suspend(struct device *dev) dev_dbg(dev, "at %s\n", __func__); - if (ci->in_lpm) { - WARN_ON(1); + if (ci->in_lpm) return 0; - } if (ci_otg_is_fsm_mode(ci)) ci_otg_fsm_suspend_for_srp(ci); diff --git a/drivers/usb/chipidea/host.c b/drivers/usb/chipidea/host.c index b45ceb91c735..3026b6b4f1e1 100644 --- a/drivers/usb/chipidea/host.c +++ b/drivers/usb/chipidea/host.c @@ -23,6 +23,7 @@ static struct hc_driver __read_mostly ci_ehci_hc_driver; static int (*orig_bus_suspend)(struct usb_hcd *hcd); +static int (*orig_bus_resume)(struct usb_hcd *hcd); struct ehci_ci_priv { struct regulator *reg_vbus; @@ -99,7 +100,10 @@ static const struct ehci_driver_overrides ehci_ci_overrides = { static irqreturn_t host_irq(struct ci_hdrc *ci) { - return usb_hcd_irq(ci->irq, ci->hcd); + if (ci->hcd) + return usb_hcd_irq(ci->irq, ci->hcd); + else + return IRQ_NONE; } static int host_start(struct ci_hdrc *ci) @@ -213,11 +217,111 @@ static void host_stop(struct ci_hdrc *ci) ci->platdata->pins_default); } +bool ci_hdrc_host_has_device(struct ci_hdrc *ci) +{ + struct usb_device *roothub; + int i; + + if ((ci->role == CI_ROLE_HOST) && ci->hcd) { + roothub = ci->hcd->self.root_hub; + for (i = 0; i < roothub->maxchild; ++i) { + if (usb_hub_find_child(roothub, (i + 1))) + return true; + } + } + return false; +} + +static void ci_hdrc_host_save_for_power_lost(struct ci_hdrc *ci) +{ + struct ehci_hcd *ehci; + + if (!ci->hcd) + return; + + ehci = hcd_to_ehci(ci->hcd); + /* save EHCI registers */ + ci->pm_usbmode = ehci_readl(ehci, &ehci->regs->usbmode); + ci->pm_command = ehci_readl(ehci, &ehci->regs->command); + ci->pm_command &= ~CMD_RUN; + ci->pm_status = ehci_readl(ehci, &ehci->regs->status); + ci->pm_intr_enable = ehci_readl(ehci, &ehci->regs->intr_enable); + ci->pm_frame_index = ehci_readl(ehci, &ehci->regs->frame_index); + ci->pm_segment = ehci_readl(ehci, &ehci->regs->segment); + ci->pm_frame_list = ehci_readl(ehci, &ehci->regs->frame_list); + ci->pm_async_next = ehci_readl(ehci, &ehci->regs->async_next); + ci->pm_configured_flag = + ehci_readl(ehci, &ehci->regs->configured_flag); + ci->pm_portsc = ehci_readl(ehci, &ehci->regs->port_status[0]); +} + +static void ci_hdrc_host_restore_from_power_lost(struct ci_hdrc *ci) +{ + struct ehci_hcd *ehci; + unsigned long flags; + u32 tmp; + int step_ms; + /* + * If the vbus is off during system suspend, most of devices will pull + * DP up within 200ms when they see vbus, set 1000ms for safety. + */ + int timeout_ms = 1000; + + if (!ci->hcd) + return; + + hw_controller_reset(ci); + + ehci = hcd_to_ehci(ci->hcd); + spin_lock_irqsave(&ehci->lock, flags); + /* Restore EHCI registers */ + ehci_writel(ehci, ci->pm_usbmode, &ehci->regs->usbmode); + ehci_writel(ehci, ci->pm_portsc, &ehci->regs->port_status[0]); + ehci_writel(ehci, ci->pm_command, &ehci->regs->command); + ehci_writel(ehci, ci->pm_intr_enable, &ehci->regs->intr_enable); + ehci_writel(ehci, ci->pm_frame_index, &ehci->regs->frame_index); + ehci_writel(ehci, ci->pm_segment, &ehci->regs->segment); + ehci_writel(ehci, ci->pm_frame_list, &ehci->regs->frame_list); + ehci_writel(ehci, ci->pm_async_next, &ehci->regs->async_next); + ehci_writel(ehci, ci->pm_configured_flag, + &ehci->regs->configured_flag); + /* Restore the PHY's connect notifier setting */ + if (ci->pm_portsc & PORTSC_HSP) + usb_phy_notify_connect(ci->usb_phy, USB_SPEED_HIGH); + + tmp = ehci_readl(ehci, &ehci->regs->command); + tmp |= CMD_RUN; + ehci_writel(ehci, tmp, &ehci->regs->command); + spin_unlock_irqrestore(&ehci->lock, flags); + + if (!(ci->pm_portsc & PORTSC_CCS)) + return; + + for (step_ms = 0; step_ms < timeout_ms; step_ms += 25) { + if (ehci_readl(ehci, &ehci->regs->port_status[0]) & PORTSC_CCS) + break; + msleep(25); + } +} + +static void ci_hdrc_host_suspend(struct ci_hdrc *ci) +{ + ci_hdrc_host_save_for_power_lost(ci); +} + +static void ci_hdrc_host_resume(struct ci_hdrc *ci, bool power_lost) +{ + if (power_lost) + ci_hdrc_host_restore_from_power_lost(ci); +} void ci_hdrc_host_destroy(struct ci_hdrc *ci) { - if (ci->role == CI_ROLE_HOST && ci->hcd) + if (ci->role == CI_ROLE_HOST && ci->hcd) { + disable_irq_nosync(ci->irq); host_stop(ci); + enable_irq(ci->irq); + } } /* The below code is based on tegra ehci driver */ @@ -232,7 +336,7 @@ static int ci_ehci_hub_control( { struct ehci_hcd *ehci = hcd_to_ehci(hcd); u32 __iomem *status_reg; - u32 temp; + u32 temp, suspend_line_state; unsigned long flags; int retval = 0; struct device *dev = hcd->self.controller; @@ -261,6 +365,17 @@ static int ci_ehci_hub_control( PORT_SUSPEND, 5000)) ehci_err(ehci, "timeout waiting for SUSPEND\n"); + if (ci->platdata->flags & CI_HDRC_HOST_SUSP_PHY_LPM) { + if (PORT_SPEED_LOW(temp)) + suspend_line_state = PORTSC_LS_K; + else + suspend_line_state = PORTSC_LS_J; + if (!ehci_handshake(ehci, status_reg, PORTSC_LS, + suspend_line_state, 5000)) + ci_hdrc_enter_lpm(ci, true); + } + + if (ci->platdata->flags & CI_HDRC_IMX_IS_HSIC) { if (ci->platdata->notify_event) ci->platdata->notify_event(ci, @@ -271,6 +386,14 @@ static int ci_ehci_hub_control( ehci_writel(ehci, temp, status_reg); } + spin_unlock_irqrestore(&ehci->lock, flags); + if (ehci_port_speed(ehci, temp) == + USB_PORT_STAT_HIGH_SPEED && hcd->usb_phy) { + /* notify the USB PHY */ + usb_phy_notify_suspend(hcd->usb_phy, USB_SPEED_HIGH); + } + spin_lock_irqsave(&ehci->lock, flags); + set_bit((wIndex & 0xff) - 1, &ehci->suspended_ports); goto done; } @@ -284,6 +407,14 @@ static int ci_ehci_hub_control( /* Make sure the resume has finished, it should be finished */ if (ehci_handshake(ehci, status_reg, PORT_RESUME, 0, 25000)) ehci_err(ehci, "timeout waiting for resume\n"); + + temp = ehci_readl(ehci, status_reg); + + if (ehci_port_speed(ehci, temp) == + USB_PORT_STAT_HIGH_SPEED && hcd->usb_phy) { + /* notify the USB PHY */ + usb_phy_notify_resume(hcd->usb_phy, USB_SPEED_HIGH); + } } spin_unlock_irqrestore(&ehci->lock, flags); @@ -331,6 +462,15 @@ static int ci_ehci_bus_suspend(struct usb_hcd *hcd) */ usleep_range(150, 200); /* + * If a transaction is in progress, there may be + * a delay in suspending the port. Poll until the + * port is suspended. + */ + if (test_bit(port, &ehci->bus_suspended) && + ehci_handshake(ehci, reg, PORT_SUSPEND, + PORT_SUSPEND, 5000)) + ehci_err(ehci, "timeout waiting for SUSPEND\n"); + /* * Need to clear WKCN and WKOC for imx HSIC, * otherwise, there will be wakeup event. */ @@ -340,6 +480,15 @@ static int ci_ehci_bus_suspend(struct usb_hcd *hcd) ehci_writel(ehci, tmp, reg); } + if (hcd->usb_phy && test_bit(port, &ehci->bus_suspended) + && (ehci_port_speed(ehci, portsc) == + USB_PORT_STAT_HIGH_SPEED)) + /* + * notify the USB PHY, it is for global + * suspend case. + */ + usb_phy_notify_suspend(hcd->usb_phy, + USB_SPEED_HIGH); break; } } @@ -347,6 +496,36 @@ static int ci_ehci_bus_suspend(struct usb_hcd *hcd) return 0; } +static int ci_ehci_bus_resume(struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + int port; + + int ret = orig_bus_resume(hcd); + + if (ret) + return ret; + + port = HCS_N_PORTS(ehci->hcs_params); + while (port--) { + u32 __iomem *reg = &ehci->regs->port_status[port]; + u32 portsc = ehci_readl(ehci, reg); + /* + * Notify PHY after resume signal has finished, it is + * for global suspend case. + */ + if (hcd->usb_phy + && test_bit(port, &ehci->bus_suspended) + && (portsc & PORT_CONNECT) + && (ehci_port_speed(ehci, portsc) == + USB_PORT_STAT_HIGH_SPEED)) + /* notify the USB PHY */ + usb_phy_notify_resume(hcd->usb_phy, USB_SPEED_HIGH); + } + + return 0; +} + int ci_hdrc_host_init(struct ci_hdrc *ci) { struct ci_role_driver *rdrv; @@ -361,6 +540,8 @@ int ci_hdrc_host_init(struct ci_hdrc *ci) rdrv->start = host_start; rdrv->stop = host_stop; rdrv->irq = host_irq; + rdrv->suspend = ci_hdrc_host_suspend; + rdrv->resume = ci_hdrc_host_resume; rdrv->name = "host"; ci->roles[CI_ROLE_HOST] = rdrv; @@ -371,6 +552,8 @@ void ci_hdrc_host_driver_init(void) { ehci_init_driver(&ci_ehci_hc_driver, &ehci_ci_overrides); orig_bus_suspend = ci_ehci_hc_driver.bus_suspend; + orig_bus_resume = ci_ehci_hc_driver.bus_resume; + ci_ehci_hc_driver.bus_resume = ci_ehci_bus_resume; ci_ehci_hc_driver.bus_suspend = ci_ehci_bus_suspend; ci_ehci_hc_driver.hub_control = ci_ehci_hub_control; } diff --git a/drivers/usb/chipidea/host.h b/drivers/usb/chipidea/host.h index 70112cf0f195..448df8894e26 100644 --- a/drivers/usb/chipidea/host.h +++ b/drivers/usb/chipidea/host.h @@ -7,6 +7,7 @@ int ci_hdrc_host_init(struct ci_hdrc *ci); void ci_hdrc_host_destroy(struct ci_hdrc *ci); void ci_hdrc_host_driver_init(void); +bool ci_hdrc_host_has_device(struct ci_hdrc *ci); #else @@ -20,11 +21,16 @@ static inline void ci_hdrc_host_destroy(struct ci_hdrc *ci) } -static void ci_hdrc_host_driver_init(void) +static inline void ci_hdrc_host_driver_init(void) { } +static inline bool ci_hdrc_host_has_device(struct ci_hdrc *ci) +{ + return false; +} + #endif #endif /* __DRIVERS_USB_CHIPIDEA_HOST_H */ diff --git a/drivers/usb/chipidea/otg.c b/drivers/usb/chipidea/otg.c index fbfb02e05c97..c944f2f905a2 100644 --- a/drivers/usb/chipidea/otg.c +++ b/drivers/usb/chipidea/otg.c @@ -2,7 +2,8 @@ /* * otg.c - ChipIdea USB IP core OTG driver * - * Copyright (C) 2013 Freescale Semiconductor, Inc. + * Copyright (C) 2013-2016 Freescale Semiconductor, Inc. + * Copyright 2017 NXP * * Author: Peter Chen */ @@ -20,6 +21,7 @@ #include "bits.h" #include "otg.h" #include "otg_fsm.h" +#include "host.h" /** * hw_read_otgsc returns otgsc register bits value. @@ -126,6 +128,20 @@ enum ci_role ci_otg_role(struct ci_hdrc *ci) return role; } +void ci_handle_vbus_connected(struct ci_hdrc *ci) +{ + /* + * TODO: if the platform does not supply 5v to udc, or use other way + * to supply 5v, it needs to use other conditions to call + * usb_gadget_vbus_connect. + */ + if (!ci->is_otg) + return; + + if (hw_read_otgsc(ci, OTGSC_BSV)) + usb_gadget_vbus_connect(&ci->gadget); +} + void ci_handle_vbus_change(struct ci_hdrc *ci) { if (!ci->is_otg) @@ -162,10 +178,13 @@ static int hw_wait_vbus_lower_bsv(struct ci_hdrc *ci) return 0; } -static void ci_handle_id_switch(struct ci_hdrc *ci) +void ci_handle_id_switch(struct ci_hdrc *ci) { - enum ci_role role = ci_otg_role(ci); + enum ci_role role; + int ret = 0; + mutex_lock(&ci->mutex); + role = ci_otg_role(ci); if (role != ci->role) { dev_dbg(ci->dev, "switching from %s to %s\n", ci_role(ci)->name, ci->roles[role]->name); @@ -181,13 +200,30 @@ static void ci_handle_id_switch(struct ci_hdrc *ci) * care vbus on the board, since it will not affect * external connector status. */ - hw_wait_vbus_lower_bsv(ci); + ret = hw_wait_vbus_lower_bsv(ci); + else if (ci->vbus_active) + /* + * If the role switch happens(e.g. during + * system sleep), and we lose vbus drop + * event, disconnect gadget for it before + * start host. + */ + usb_gadget_vbus_disconnect(&ci->gadget); ci_role_start(ci, role); /* vbus change may have already occurred */ if (role == CI_ROLE_GADGET) ci_handle_vbus_change(ci); + + /* + * If the role switch happens(e.g. during system + * sleep) and vbus keeps on afterwards, we connect + * gadget as vbus connect event lost. + */ + if (ret == -ETIMEDOUT) + usb_gadget_vbus_connect(&ci->gadget); } + mutex_unlock(&ci->mutex); } /** * ci_otg_work - perform otg (vbus/id) event handle diff --git a/drivers/usb/chipidea/otg.h b/drivers/usb/chipidea/otg.h index 4f8b8179ec96..86a46d2a1821 100644 --- a/drivers/usb/chipidea/otg.h +++ b/drivers/usb/chipidea/otg.h @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Copyright (C) 2013-2014 Freescale Semiconductor, Inc. + * Copyright (C) 2013-2015 Freescale Semiconductor, Inc. * * Author: Peter Chen */ @@ -14,6 +14,8 @@ int ci_hdrc_otg_init(struct ci_hdrc *ci); void ci_hdrc_otg_destroy(struct ci_hdrc *ci); enum ci_role ci_otg_role(struct ci_hdrc *ci); void ci_handle_vbus_change(struct ci_hdrc *ci); +void ci_handle_id_switch(struct ci_hdrc *ci); +void ci_handle_vbus_connected(struct ci_hdrc *ci); static inline void ci_otg_queue_work(struct ci_hdrc *ci) { disable_irq_nosync(ci->irq); diff --git a/drivers/usb/chipidea/otg_fsm.c b/drivers/usb/chipidea/otg_fsm.c index 6ed4b00dba96..17cdf8b08ec3 100644 --- a/drivers/usb/chipidea/otg_fsm.c +++ b/drivers/usb/chipidea/otg_fsm.c @@ -25,6 +25,7 @@ #include "ci.h" #include "bits.h" #include "otg.h" +#include "udc.h" #include "otg_fsm.h" /* Add for otg: interact with user space app */ @@ -211,6 +212,7 @@ static unsigned otg_timer_ms[] = { 0, TB_DATA_PLS, TB_SSEND_SRP, + TA_DP_END, }; /* @@ -356,6 +358,13 @@ static int b_ssend_srp_tmout(struct ci_hdrc *ci) return 1; } +static int a_dp_end_tmout(struct ci_hdrc *ci) +{ + ci->fsm.a_bus_drop = 0; + ci->fsm.a_srp_det = 1; + return 0; +} + /* * Keep this list in the same order as timers indexed * by enum otg_fsm_timer in include/linux/usb/otg-fsm.h @@ -373,6 +382,7 @@ static int (*otg_timer_handlers[])(struct ci_hdrc *) = { NULL, /* A_WAIT_ENUM */ b_data_pls_tmout, /* B_DATA_PLS */ b_ssend_srp_tmout, /* B_SSEND_SRP */ + a_dp_end_tmout, /* A_DP_END */ }; /* @@ -559,10 +569,7 @@ static int ci_otg_start_gadget(struct otg_fsm *fsm, int on) { struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm); - if (on) - usb_gadget_vbus_connect(&ci->gadget); - else - usb_gadget_vbus_disconnect(&ci->gadget); + ci_hdrc_gadget_connect(&ci->gadget, on); return 0; } @@ -742,8 +749,7 @@ irqreturn_t ci_otg_fsm_irq(struct ci_hdrc *ci) if (otg_int_src) { if (otg_int_src & OTGSC_DPIS) { hw_write_otgsc(ci, OTGSC_DPIS, OTGSC_DPIS); - fsm->a_srp_det = 1; - fsm->a_bus_drop = 0; + ci_otg_add_timer(ci, A_DP_END); } else if (otg_int_src & OTGSC_IDIS) { hw_write_otgsc(ci, OTGSC_IDIS, OTGSC_IDIS); if (fsm->id == 0) { diff --git a/drivers/usb/chipidea/otg_fsm.h b/drivers/usb/chipidea/otg_fsm.h index 2b49d29bf2fb..638eec94d783 100644 --- a/drivers/usb/chipidea/otg_fsm.h +++ b/drivers/usb/chipidea/otg_fsm.h @@ -40,6 +40,8 @@ * for safe */ +#define TA_DP_END (200) + /* * B-device timing constants */ diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c index 8f18e7b6cadf..2646737f3969 100644 --- a/drivers/usb/chipidea/udc.c +++ b/drivers/usb/chipidea/udc.c @@ -1540,27 +1540,19 @@ static int ci_udc_vbus_session(struct usb_gadget *_gadget, int is_active) usb_phy_set_charger_state(ci->usb_phy, is_active ? USB_CHARGER_PRESENT : USB_CHARGER_ABSENT); - if (gadget_ready) { - if (is_active) { - pm_runtime_get_sync(&_gadget->dev); - hw_device_reset(ci); - hw_device_state(ci, ci->ep0out->qh.dma); - usb_gadget_set_state(_gadget, USB_STATE_POWERED); - usb_udc_vbus_handler(_gadget, true); - } else { - usb_udc_vbus_handler(_gadget, false); - if (ci->driver) - ci->driver->disconnect(&ci->gadget); - hw_device_state(ci, 0); - if (ci->platdata->notify_event) - ci->platdata->notify_event(ci, - CI_HDRC_CONTROLLER_STOPPED_EVENT); - _gadget_stop_activity(&ci->gadget); - pm_runtime_put_sync(&_gadget->dev); - usb_gadget_set_state(_gadget, USB_STATE_NOTATTACHED); - } + /* Charger Detection */ + ci_usb_charger_connect(ci, is_active); + + if (ci->usb_phy) { + if (is_active) + usb_phy_set_event(ci->usb_phy, USB_EVENT_VBUS); + else + usb_phy_set_event(ci->usb_phy, USB_EVENT_NONE); } + if (gadget_ready) + ci_hdrc_gadget_connect(_gadget, is_active); + return 0; } @@ -1625,12 +1617,12 @@ static int ci_udc_pullup(struct usb_gadget *_gadget, int is_on) if (ci_otg_is_fsm_mode(ci) || ci->role == CI_ROLE_HOST) return 0; - pm_runtime_get_sync(&ci->gadget.dev); + pm_runtime_get_sync(ci->dev); if (is_on) hw_write(ci, OP_USBCMD, USBCMD_RS, USBCMD_RS); else hw_write(ci, OP_USBCMD, USBCMD_RS, 0); - pm_runtime_put_sync(&ci->gadget.dev); + pm_runtime_put_sync(ci->dev); return 0; } @@ -1780,23 +1772,14 @@ static int ci_udc_start(struct usb_gadget *gadget, ci->driver = driver; /* Start otg fsm for B-device */ - if (ci_otg_is_fsm_mode(ci) && ci->fsm.id) { - ci_hdrc_otg_fsm_start(ci); + if (ci_otg_is_fsm_mode(ci)) { + if (ci->fsm.id) + ci_hdrc_otg_fsm_start(ci); return retval; } - pm_runtime_get_sync(&ci->gadget.dev); - if (ci->vbus_active) { - hw_device_reset(ci); - } else { - usb_udc_vbus_handler(&ci->gadget, false); - pm_runtime_put_sync(&ci->gadget.dev); - return retval; - } - - retval = hw_device_state(ci, ci->ep0out->qh.dma); - if (retval) - pm_runtime_put_sync(&ci->gadget.dev); + if (ci->vbus_active) + ci_hdrc_gadget_connect(&ci->gadget, 1); return retval; } @@ -1835,7 +1818,7 @@ static int ci_udc_stop(struct usb_gadget *gadget) CI_HDRC_CONTROLLER_STOPPED_EVENT); _gadget_stop_activity(&ci->gadget); spin_lock_irqsave(&ci->lock, flags); - pm_runtime_put(&ci->gadget.dev); + pm_runtime_put(ci->dev); } ci->driver = NULL; @@ -1881,6 +1864,9 @@ static irqreturn_t udc_irq(struct ci_hdrc *ci) if (USBi_PCI & intr) { ci->gadget.speed = hw_port_is_high_speed(ci) ? USB_SPEED_HIGH : USB_SPEED_FULL; + if (ci->usb_phy) + usb_phy_set_event(ci->usb_phy, + USB_EVENT_ENUMERATED); if (ci->suspended) { if (ci->driver->resume) { spin_unlock(&ci->lock); @@ -1967,9 +1953,6 @@ static int udc_start(struct ci_hdrc *ci) if (retval) goto destroy_eps; - pm_runtime_no_callbacks(&ci->gadget.dev); - pm_runtime_enable(&ci->gadget.dev); - return retval; destroy_eps: @@ -1999,6 +1982,52 @@ void ci_hdrc_gadget_destroy(struct ci_hdrc *ci) dma_pool_destroy(ci->qh_pool); } +int ci_usb_charger_connect(struct ci_hdrc *ci, int is_active) +{ + int ret = 0; + + pm_runtime_get_sync(ci->dev); + + if (ci->usb_phy->charger_detect) { + usb_phy_set_charger_state(ci->usb_phy, is_active ? + USB_CHARGER_PRESENT : USB_CHARGER_ABSENT); + } else if (ci->platdata->notify_event) { + ret = ci->platdata->notify_event(ci, + CI_HDRC_CONTROLLER_VBUS_EVENT); + schedule_work(&ci->usb_phy->chg_work); + } + + pm_runtime_put_sync(ci->dev); + return ret; +} + +/** + * ci_hdrc_gadget_connect: caller make sure gadget driver is binded + */ +void ci_hdrc_gadget_connect(struct usb_gadget *gadget, int is_active) +{ + struct ci_hdrc *ci = container_of(gadget, struct ci_hdrc, gadget); + + if (is_active) { + pm_runtime_get_sync(ci->dev); + hw_device_reset(ci); + hw_device_state(ci, ci->ep0out->qh.dma); + usb_gadget_set_state(gadget, USB_STATE_POWERED); + usb_udc_vbus_handler(gadget, true); + } else { + usb_udc_vbus_handler(gadget, false); + if (ci->driver) + ci->driver->disconnect(gadget); + hw_device_state(ci, 0); + if (ci->platdata->notify_event) + ci->platdata->notify_event(ci, + CI_HDRC_CONTROLLER_STOPPED_EVENT); + _gadget_stop_activity(gadget); + pm_runtime_put_sync(ci->dev); + usb_gadget_set_state(gadget, USB_STATE_NOTATTACHED); + } +} + static int udc_id_switch_for_device(struct ci_hdrc *ci) { if (ci->platdata->pins_device) @@ -2029,6 +2058,44 @@ static void udc_id_switch_for_host(struct ci_hdrc *ci) ci->platdata->pins_default); } +static void udc_suspend_for_power_lost(struct ci_hdrc *ci) +{ + /* + * Set OP_ENDPTLISTADDR to be non-zero for + * checking if controller resume from power lost + * in non-host mode. + */ + if (hw_read(ci, OP_ENDPTLISTADDR, ~0) == 0) + hw_write(ci, OP_ENDPTLISTADDR, ~0, ~0); +} + +/* Power lost with device mode */ +static void udc_resume_from_power_lost(struct ci_hdrc *ci) +{ + if (ci->is_otg) + hw_write_otgsc(ci, OTGSC_BSVIS | OTGSC_BSVIE, + OTGSC_BSVIS | OTGSC_BSVIE); +} + +static void udc_suspend(struct ci_hdrc *ci) +{ + udc_suspend_for_power_lost(ci); + + if (ci->driver && ci->vbus_active && + (ci->gadget.state != USB_STATE_SUSPENDED)) + usb_gadget_disconnect(&ci->gadget); +} + +static void udc_resume(struct ci_hdrc *ci, bool power_lost) +{ + if (power_lost) { + udc_resume_from_power_lost(ci); + } else { + if (ci->driver && ci->vbus_active) + usb_gadget_connect(&ci->gadget); + } +} + /** * ci_hdrc_gadget_init - initialize device related bits * ci: the controller @@ -2050,6 +2117,8 @@ int ci_hdrc_gadget_init(struct ci_hdrc *ci) rdrv->start = udc_id_switch_for_device; rdrv->stop = udc_id_switch_for_host; rdrv->irq = udc_irq; + rdrv->suspend = udc_suspend; + rdrv->resume = udc_resume; rdrv->name = "gadget"; ret = udc_start(ci); diff --git a/drivers/usb/chipidea/udc.h b/drivers/usb/chipidea/udc.h index e023735d94b7..da8529c2684f 100644 --- a/drivers/usb/chipidea/udc.h +++ b/drivers/usb/chipidea/udc.h @@ -82,6 +82,8 @@ struct ci_hw_req { int ci_hdrc_gadget_init(struct ci_hdrc *ci); void ci_hdrc_gadget_destroy(struct ci_hdrc *ci); +int ci_usb_charger_connect(struct ci_hdrc *ci, int is_active); +void ci_hdrc_gadget_connect(struct usb_gadget *gadget, int is_active); #else @@ -95,6 +97,17 @@ static inline void ci_hdrc_gadget_destroy(struct ci_hdrc *ci) } +static inline int ci_usb_charger_connect(struct ci_hdrc *ci, int is_active) +{ + return 0; +} + +static inline void ci_hdrc_gadget_connect(struct usb_gadget *gadget, + int is_active) +{ + +} + #endif #endif /* __DRIVERS_USB_CHIPIDEA_UDC_H */ diff --git a/drivers/usb/chipidea/usbmisc_imx.c b/drivers/usb/chipidea/usbmisc_imx.c index 078c1fdce493..5666061588f5 100644 --- a/drivers/usb/chipidea/usbmisc_imx.c +++ b/drivers/usb/chipidea/usbmisc_imx.c @@ -8,6 +8,8 @@ #include <linux/err.h> #include <linux/io.h> #include <linux/delay.h> +#include <linux/usb/otg.h> +#include <linux/regulator/consumer.h> #include "ci_hdrc_imx.h" @@ -93,6 +95,18 @@ #define VF610_OVER_CUR_DIS BIT(7) #define MX7D_USBNC_USB_CTRL2 0x4 +/* The default DM/DP value is pull-down */ +#define MX7D_USBNC_USB_CTRL2_DM_OVERRIDE_EN BIT(15) +#define MX7D_USBNC_USB_CTRL2_DM_OVERRIDE_VAL BIT(14) +#define MX7D_USBNC_USB_CTRL2_DP_OVERRIDE_EN BIT(13) +#define MX7D_USBNC_USB_CTRL2_DP_OVERRIDE_VAL BIT(12) +#define MX7D_USBNC_USB_CTRL2_DP_DM_MASK (BIT(12) | BIT(13) | \ + BIT(14) | BIT(15)) +#define MX7D_USBNC_USB_CTRL2_OPMODE_OVERRIDE_EN BIT(8) +#define MX7D_USBNC_USB_CTRL2_OPMODE_OVERRIDE_MASK (BIT(7) | BIT(6)) +#define MX7D_USBNC_USB_CTRL2_OPMODE(v) (v << 6) +#define MX7D_USBNC_USB_CTRL2_OPMODE_NON_DRIVING MX7D_USBNC_USB_CTRL2_OPMODE(1) +#define MX7D_USBNC_AUTO_RESUME BIT(2) #define MX7D_USB_VBUS_WAKEUP_SOURCE_MASK 0x3 #define MX7D_USB_VBUS_WAKEUP_SOURCE(v) (v << 0) #define MX7D_USB_VBUS_WAKEUP_SOURCE_VBUS MX7D_USB_VBUS_WAKEUP_SOURCE(0) @@ -100,6 +114,27 @@ #define MX7D_USB_VBUS_WAKEUP_SOURCE_BVALID MX7D_USB_VBUS_WAKEUP_SOURCE(2) #define MX7D_USB_VBUS_WAKEUP_SOURCE_SESS_END MX7D_USB_VBUS_WAKEUP_SOURCE(3) +#define MX7D_USB_OTG_PHY_CFG1 0x30 +#define TXPREEMPAMPTUNE0_BIT 28 +#define TXPREEMPAMPTUNE0_MASK (3 << 28) +#define TXVREFTUNE0_BIT 20 +#define TXVREFTUNE0_MASK (0xf << 20) +#define MX7D_USB_OTG_PHY_CFG2_DRVVBUS0 BIT(16) +#define MX7D_USB_OTG_PHY_CFG2_CHRG_DCDENB BIT(3) +#define MX7D_USB_OTG_PHY_CFG2_CHRG_VDATSRCENB0 BIT(2) +#define MX7D_USB_OTG_PHY_CFG2_CHRG_VDATDETENB0 BIT(1) +#define MX7D_USB_OTG_PHY_CFG2_CHRG_CHRGSEL BIT(0) +#define MX7D_USB_OTG_PHY_CFG2 0x34 + +#define MX7D_USB_OTG_PHY_STATUS 0x3c +#define MX7D_USB_OTG_PHY_STATUS_CHRGDET BIT(29) +#define MX7D_USB_OTG_PHY_STATUS_VBUS_VLD BIT(3) +#define MX7D_USB_OTG_PHY_STATUS_LINE_STATE1 BIT(1) +#define MX7D_USB_OTG_PHY_STATUS_LINE_STATE0 BIT(0) + +#define MX6_USB_OTG_WAKEUP_BITS (MX6_BM_WAKEUP_ENABLE | MX6_BM_VBUS_WAKEUP | \ + MX6_BM_ID_WAKEUP) + struct usbmisc_ops { /* It's called once when probe a usb device */ int (*init)(struct imx_usbmisc_data *data); @@ -111,6 +146,11 @@ struct usbmisc_ops { int (*hsic_set_connect)(struct imx_usbmisc_data *data); /* It's called during suspend/resume */ int (*hsic_set_clk)(struct imx_usbmisc_data *data, bool enabled); + /* It's called when system resume from usb power lost */ + int (*power_lost_check)(struct imx_usbmisc_data *data); + /* usb charger detection */ + int (*charger_detection)(struct imx_usbmisc_data *data); + void (*vbus_comparator_on)(struct imx_usbmisc_data *data, bool on); }; struct imx_usbmisc { @@ -119,6 +159,8 @@ struct imx_usbmisc { const struct usbmisc_ops *ops; }; +static struct regulator *vbus_wakeup_reg; + static inline bool is_imx53_usbmisc(struct imx_usbmisc_data *data); static int usbmisc_imx25_init(struct imx_usbmisc_data *data) @@ -330,15 +372,26 @@ static int usbmisc_imx53_init(struct imx_usbmisc_data *data) return 0; } +static u32 usbmisc_wakeup_setting(struct imx_usbmisc_data *data) +{ + u32 wakeup_setting = MX6_USB_OTG_WAKEUP_BITS; + + if (data->ext_id || data->available_role != USB_DR_MODE_OTG) + wakeup_setting &= ~MX6_BM_ID_WAKEUP; + + if (data->ext_vbus || data->available_role == USB_DR_MODE_HOST) + wakeup_setting &= ~MX6_BM_VBUS_WAKEUP; + + return wakeup_setting; +} + static int usbmisc_imx6q_set_wakeup (struct imx_usbmisc_data *data, bool enabled) { struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); unsigned long flags; - u32 val; - u32 wakeup_setting = (MX6_BM_WAKEUP_ENABLE | - MX6_BM_VBUS_WAKEUP | MX6_BM_ID_WAKEUP); int ret = 0; + u32 val; if (data->index > 3) return -EINVAL; @@ -346,14 +399,22 @@ static int usbmisc_imx6q_set_wakeup spin_lock_irqsave(&usbmisc->lock, flags); val = readl(usbmisc->base + data->index * 4); if (enabled) { - val |= wakeup_setting; + val &= ~MX6_USB_OTG_WAKEUP_BITS; + val |= usbmisc_wakeup_setting(data); + if (vbus_wakeup_reg) { + spin_unlock_irqrestore(&usbmisc->lock, flags); + ret = regulator_enable(vbus_wakeup_reg); + spin_lock_irqsave(&usbmisc->lock, flags); + } } else { if (val & MX6_BM_WAKEUP_INTR) pr_debug("wakeup int at ci_hdrc.%d\n", data->index); - val &= ~wakeup_setting; + val &= ~MX6_USB_OTG_WAKEUP_BITS; } writel(val, usbmisc->base + data->index * 4); spin_unlock_irqrestore(&usbmisc->lock, flags); + if (vbus_wakeup_reg && regulator_is_enabled(vbus_wakeup_reg)) + regulator_disable(vbus_wakeup_reg); return ret; } @@ -547,17 +608,17 @@ static int usbmisc_imx7d_set_wakeup struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); unsigned long flags; u32 val; - u32 wakeup_setting = (MX6_BM_WAKEUP_ENABLE | - MX6_BM_VBUS_WAKEUP | MX6_BM_ID_WAKEUP); spin_lock_irqsave(&usbmisc->lock, flags); val = readl(usbmisc->base); if (enabled) { - writel(val | wakeup_setting, usbmisc->base); + val &= ~MX6_USB_OTG_WAKEUP_BITS; + val |= usbmisc_wakeup_setting(data); + writel(val, usbmisc->base); } else { if (val & MX6_BM_WAKEUP_INTR) dev_dbg(data->dev, "wakeup int\n"); - writel(val & ~wakeup_setting, usbmisc->base); + writel(val & ~MX6_USB_OTG_WAKEUP_BITS, usbmisc->base); } spin_unlock_irqrestore(&usbmisc->lock, flags); @@ -594,10 +655,32 @@ static int usbmisc_imx7d_init(struct imx_usbmisc_data *data) reg |= MX6_BM_PWR_POLARITY; writel(reg, usbmisc->base); - reg = readl(usbmisc->base + MX7D_USBNC_USB_CTRL2); - reg &= ~MX7D_USB_VBUS_WAKEUP_SOURCE_MASK; - writel(reg | MX7D_USB_VBUS_WAKEUP_SOURCE_BVALID, - usbmisc->base + MX7D_USBNC_USB_CTRL2); + /* SoC non-burst setting */ + reg = readl(usbmisc->base); + writel(reg | MX6_BM_NON_BURST_SETTING, usbmisc->base); + + if (!data->hsic) { + reg = readl(usbmisc->base + MX7D_USBNC_USB_CTRL2); + reg &= ~MX7D_USB_VBUS_WAKEUP_SOURCE_MASK; + writel(reg | MX7D_USB_VBUS_WAKEUP_SOURCE_BVALID + | MX7D_USBNC_AUTO_RESUME, + usbmisc->base + MX7D_USBNC_USB_CTRL2); + /* PHY tuning for signal quality */ + reg = readl(usbmisc->base + MX7D_USB_OTG_PHY_CFG1); + if (data->emp_curr_control && data->emp_curr_control <= + (TXPREEMPAMPTUNE0_MASK >> TXPREEMPAMPTUNE0_BIT)) { + reg &= ~TXPREEMPAMPTUNE0_MASK; + reg |= (data->emp_curr_control << TXPREEMPAMPTUNE0_BIT); + } + + if (data->dc_vol_level_adjust && data->dc_vol_level_adjust <= + (TXVREFTUNE0_MASK >> TXVREFTUNE0_BIT)) { + reg &= ~TXVREFTUNE0_MASK; + reg |= (data->dc_vol_level_adjust << TXVREFTUNE0_BIT); + } + + writel(reg, usbmisc->base + MX7D_USB_OTG_PHY_CFG1); + } spin_unlock_irqrestore(&usbmisc->lock, flags); @@ -606,6 +689,315 @@ static int usbmisc_imx7d_init(struct imx_usbmisc_data *data) return 0; } +static int usbmisc_imx7d_power_lost_check(struct imx_usbmisc_data *data) +{ + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + unsigned long flags; + u32 val; + + spin_lock_irqsave(&usbmisc->lock, flags); + val = readl(usbmisc->base); + spin_unlock_irqrestore(&usbmisc->lock, flags); + /* + * Here use a power on reset value to judge + * if the controller experienced a power lost + */ + if (val == 0x30001000) + return 1; + else + return 0; +} + +static int imx7d_charger_secondary_detection(struct imx_usbmisc_data *data) +{ + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + struct usb_phy *usb_phy = data->usb_phy; + int val; + unsigned long flags; + + /* VDM_SRC is connected to D- and IDP_SINK is connected to D+ */ + spin_lock_irqsave(&usbmisc->lock, flags); + val = readl(usbmisc->base + MX7D_USB_OTG_PHY_CFG2); + writel(val | MX7D_USB_OTG_PHY_CFG2_CHRG_VDATSRCENB0 | + MX7D_USB_OTG_PHY_CFG2_CHRG_VDATDETENB0 | + MX7D_USB_OTG_PHY_CFG2_CHRG_CHRGSEL, + usbmisc->base + MX7D_USB_OTG_PHY_CFG2); + spin_unlock_irqrestore(&usbmisc->lock, flags); + + usleep_range(1000, 2000); + + /* + * Per BC 1.2, check voltage of D+: + * DCP: if greater than VDAT_REF; + * CDP: if less than VDAT_REF. + */ + val = readl(usbmisc->base + MX7D_USB_OTG_PHY_STATUS); + if (val & MX7D_USB_OTG_PHY_STATUS_CHRGDET) { + dev_dbg(data->dev, "It is a dedicate charging port\n"); + usb_phy->chg_type = DCP_TYPE; + } else { + dev_dbg(data->dev, "It is a charging downstream port\n"); + usb_phy->chg_type = CDP_TYPE; + } + + return 0; +} + +static void imx7_disable_charger_detector(struct imx_usbmisc_data *data) +{ + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + unsigned long flags; + u32 val; + + spin_lock_irqsave(&usbmisc->lock, flags); + val = readl(usbmisc->base + MX7D_USB_OTG_PHY_CFG2); + val &= ~(MX7D_USB_OTG_PHY_CFG2_CHRG_DCDENB | + MX7D_USB_OTG_PHY_CFG2_CHRG_VDATSRCENB0 | + MX7D_USB_OTG_PHY_CFG2_CHRG_VDATDETENB0 | + MX7D_USB_OTG_PHY_CFG2_CHRG_CHRGSEL); + writel(val, usbmisc->base + MX7D_USB_OTG_PHY_CFG2); + + /* Set OPMODE to be 2'b00 and disable its override */ + val = readl(usbmisc->base + MX7D_USBNC_USB_CTRL2); + val &= ~MX7D_USBNC_USB_CTRL2_OPMODE_OVERRIDE_MASK; + writel(val, usbmisc->base + MX7D_USBNC_USB_CTRL2); + + val = readl(usbmisc->base + MX7D_USBNC_USB_CTRL2); + writel(val & ~MX7D_USBNC_USB_CTRL2_OPMODE_OVERRIDE_EN, + usbmisc->base + MX7D_USBNC_USB_CTRL2); + spin_unlock_irqrestore(&usbmisc->lock, flags); +} + +static int imx7d_charger_data_contact_detect(struct imx_usbmisc_data *data) +{ + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + unsigned long flags; + u32 val; + int i, data_pin_contact_count = 0; + + /* Enable Data Contact Detect (DCD) per the USB BC 1.2 */ + spin_lock_irqsave(&usbmisc->lock, flags); + val = readl(usbmisc->base + MX7D_USB_OTG_PHY_CFG2); + writel(val | MX7D_USB_OTG_PHY_CFG2_CHRG_DCDENB, + usbmisc->base + MX7D_USB_OTG_PHY_CFG2); + spin_unlock_irqrestore(&usbmisc->lock, flags); + + for (i = 0; i < 100; i = i + 1) { + val = readl(usbmisc->base + MX7D_USB_OTG_PHY_STATUS); + if (!(val & MX7D_USB_OTG_PHY_STATUS_LINE_STATE0)) { + if (data_pin_contact_count++ > 5) + /* Data pin makes contact */ + break; + usleep_range(5000, 10000); + } else { + data_pin_contact_count = 0; + usleep_range(5000, 6000); + } + } + + /* Disable DCD after finished data contact check */ + spin_lock_irqsave(&usbmisc->lock, flags); + val = readl(usbmisc->base + MX7D_USB_OTG_PHY_CFG2); + writel(val & ~MX7D_USB_OTG_PHY_CFG2_CHRG_DCDENB, + usbmisc->base + MX7D_USB_OTG_PHY_CFG2); + spin_unlock_irqrestore(&usbmisc->lock, flags); + + if (i == 100) { + dev_err(data->dev, + "VBUS is coming from a dedicated power supply.\n"); + return -ENXIO; + } + + return 0; +} + +static int imx7d_charger_primary_detection(struct imx_usbmisc_data *data) +{ + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + struct usb_phy *usb_phy = data->usb_phy; + unsigned long flags; + u32 val; + + /* VDP_SRC is connected to D+ and IDM_SINK is connected to D- */ + spin_lock_irqsave(&usbmisc->lock, flags); + val = readl(usbmisc->base + MX7D_USB_OTG_PHY_CFG2); + val &= ~MX7D_USB_OTG_PHY_CFG2_CHRG_CHRGSEL; + writel(val | MX7D_USB_OTG_PHY_CFG2_CHRG_VDATSRCENB0 | + MX7D_USB_OTG_PHY_CFG2_CHRG_VDATDETENB0, + usbmisc->base + MX7D_USB_OTG_PHY_CFG2); + spin_unlock_irqrestore(&usbmisc->lock, flags); + + usleep_range(1000, 2000); + + /* Check if D- is less than VDAT_REF to determine an SDP per BC 1.2 */ + val = readl(usbmisc->base + MX7D_USB_OTG_PHY_STATUS); + if (!(val & MX7D_USB_OTG_PHY_STATUS_CHRGDET)) { + dev_dbg(data->dev, "It is a standard downstream port\n"); + usb_phy->chg_type = SDP_TYPE; + } + + return 0; +} + +/** + * Whole charger detection process: + * 1. OPMODE override to be non-driving + * 2. Data contact check + * 3. Primary detection + * 4. Secondary detection + * 5. Disable charger detection + */ +static int imx7d_charger_detection(struct imx_usbmisc_data *data) +{ + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + struct usb_phy *usb_phy = data->usb_phy; + unsigned long flags; + u32 val; + int ret; + + /* Check if vbus is valid */ + val = readl(usbmisc->base + MX7D_USB_OTG_PHY_STATUS); + if (!(val & MX7D_USB_OTG_PHY_STATUS_VBUS_VLD)) { + dev_err(data->dev, "vbus is error\n"); + return -EINVAL; + } + + /* + * Keep OPMODE to be non-driving mode during the whole + * charger detection process. + */ + spin_lock_irqsave(&usbmisc->lock, flags); + val = readl(usbmisc->base + MX7D_USBNC_USB_CTRL2); + val &= ~MX7D_USBNC_USB_CTRL2_OPMODE_OVERRIDE_MASK; + val |= MX7D_USBNC_USB_CTRL2_OPMODE_NON_DRIVING; + writel(val, usbmisc->base + MX7D_USBNC_USB_CTRL2); + + val = readl(usbmisc->base + MX7D_USBNC_USB_CTRL2); + writel(val | MX7D_USBNC_USB_CTRL2_OPMODE_OVERRIDE_EN, + usbmisc->base + MX7D_USBNC_USB_CTRL2); + spin_unlock_irqrestore(&usbmisc->lock, flags); + + ret = imx7d_charger_data_contact_detect(data); + if (ret) + return ret; + + ret = imx7d_charger_primary_detection(data); + if (!ret && usb_phy->chg_type != SDP_TYPE) + ret = imx7d_charger_secondary_detection(data); + + imx7_disable_charger_detector(data); + + return ret; +} + +static void usbmisc_imx7d_vbus_comparator_on(struct imx_usbmisc_data *data, + bool on) +{ + unsigned long flags; + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + u32 val; + + if (data->hsic) + return; + + spin_lock_irqsave(&usbmisc->lock, flags); + /* + * Disable VBUS valid comparator when in suspend mode, + * when OTG is disabled and DRVVBUS0 is asserted case + * the Bandgap circuitry and VBUS Valid comparator are + * still powered, even in Suspend or Sleep mode. + */ + val = readl(usbmisc->base + MX7D_USB_OTG_PHY_CFG2); + if (on) + val |= MX7D_USB_OTG_PHY_CFG2_DRVVBUS0; + else + val &= ~MX7D_USB_OTG_PHY_CFG2_DRVVBUS0; + + writel(val, usbmisc->base + MX7D_USB_OTG_PHY_CFG2); + spin_unlock_irqrestore(&usbmisc->lock, flags); +} + +static int usbmisc_imx6sx_power_lost_check(struct imx_usbmisc_data *data) +{ + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + unsigned long flags; + u32 val; + + spin_lock_irqsave(&usbmisc->lock, flags); + val = readl(usbmisc->base + data->index * 4); + spin_unlock_irqrestore(&usbmisc->lock, flags); + /* + * Here use a power on reset value to judge + * if the controller experienced a power lost + */ + if (val == 0x30001000) + return 1; + else + return 0; +} + +static int usbmisc_imx7ulp_init(struct imx_usbmisc_data *data) +{ + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + unsigned long flags; + u32 reg; + + if (data->index >= 1) + return -EINVAL; + + spin_lock_irqsave(&usbmisc->lock, flags); + reg = readl(usbmisc->base); + if (data->disable_oc) { + reg |= MX6_BM_OVER_CUR_DIS; + } else { + reg &= ~MX6_BM_OVER_CUR_DIS; + + /* + * If the polarity is not configured keep it as setup by the + * bootloader. + */ + if (data->oc_pol_configured && data->oc_pol_active_low) + reg |= MX6_BM_OVER_CUR_POLARITY; + else if (data->oc_pol_configured) + reg &= ~MX6_BM_OVER_CUR_POLARITY; + } + /* If the polarity is not set keep it as setup by the bootlader */ + if (data->pwr_pol == 1) + reg |= MX6_BM_PWR_POLARITY; + writel(reg, usbmisc->base); + + /* SoC non-burst setting */ + reg = readl(usbmisc->base); + writel(reg | MX6_BM_NON_BURST_SETTING, usbmisc->base); + + if (data->hsic) { + reg = readl(usbmisc->base); + writel(reg | MX6_BM_UTMI_ON_CLOCK, usbmisc->base); + + reg = readl(usbmisc->base + MX6_USB_HSIC_CTRL_OFFSET); + reg |= MX6_BM_HSIC_EN | MX6_BM_HSIC_CLK_ON; + writel(reg, usbmisc->base + MX6_USB_HSIC_CTRL_OFFSET); + + /* + * For non-HSIC controller, the autoresume is enabled + * at MXS PHY driver (usbphy_ctrl bit18). + */ + reg = readl(usbmisc->base + MX7D_USBNC_USB_CTRL2); + writel(reg | MX7D_USBNC_AUTO_RESUME, + usbmisc->base + MX7D_USBNC_USB_CTRL2); + } else { + reg = readl(usbmisc->base + MX7D_USBNC_USB_CTRL2); + reg &= ~MX7D_USB_VBUS_WAKEUP_SOURCE_MASK; + writel(reg | MX7D_USB_VBUS_WAKEUP_SOURCE_BVALID, + usbmisc->base + MX7D_USBNC_USB_CTRL2); + } + + spin_unlock_irqrestore(&usbmisc->lock, flags); + + usbmisc_imx7d_set_wakeup(data, false); + + return 0; +} static const struct usbmisc_ops imx25_usbmisc_ops = { .init = usbmisc_imx25_init, .post = usbmisc_imx25_post, @@ -639,11 +1031,23 @@ static const struct usbmisc_ops imx6sx_usbmisc_ops = { .init = usbmisc_imx6sx_init, .hsic_set_connect = usbmisc_imx6_hsic_set_connect, .hsic_set_clk = usbmisc_imx6_hsic_set_clk, + .power_lost_check = usbmisc_imx6sx_power_lost_check, }; static const struct usbmisc_ops imx7d_usbmisc_ops = { + .init = usbmisc_imx7ulp_init, + .set_wakeup = usbmisc_imx7d_set_wakeup, + .power_lost_check = usbmisc_imx7d_power_lost_check, + .charger_detection = imx7d_charger_detection, + .vbus_comparator_on = usbmisc_imx7d_vbus_comparator_on, +}; + +static const struct usbmisc_ops imx7ulp_usbmisc_ops = { .init = usbmisc_imx7d_init, .set_wakeup = usbmisc_imx7d_set_wakeup, + .power_lost_check = usbmisc_imx7d_power_lost_check, + .hsic_set_connect = usbmisc_imx6_hsic_set_connect, + .hsic_set_clk = usbmisc_imx6_hsic_set_clk, }; static inline bool is_imx53_usbmisc(struct imx_usbmisc_data *data) @@ -670,18 +1074,31 @@ EXPORT_SYMBOL_GPL(imx_usbmisc_init); int imx_usbmisc_init_post(struct imx_usbmisc_data *data) { struct imx_usbmisc *usbmisc; + int ret = 0; if (!data) return 0; usbmisc = dev_get_drvdata(data->dev); - if (!usbmisc->ops->post) - return 0; - return usbmisc->ops->post(data); + if (usbmisc->ops->post) + ret = usbmisc->ops->post(data); + if (ret) { + dev_err(data->dev, "post init failed, ret=%d\n", ret); + return ret; + } + + if (usbmisc->ops->set_wakeup) + ret = usbmisc->ops->set_wakeup(data, false); + if (ret) { + dev_err(data->dev, "set_wakeup failed, ret=%d\n", ret); + return ret; + } + + return 0; } EXPORT_SYMBOL_GPL(imx_usbmisc_init_post); -int imx_usbmisc_set_wakeup(struct imx_usbmisc_data *data, bool enabled) +int imx_usbmisc_hsic_set_connect(struct imx_usbmisc_data *data) { struct imx_usbmisc *usbmisc; @@ -689,39 +1106,122 @@ int imx_usbmisc_set_wakeup(struct imx_usbmisc_data *data, bool enabled) return 0; usbmisc = dev_get_drvdata(data->dev); - if (!usbmisc->ops->set_wakeup) + if (!usbmisc->ops->hsic_set_connect || !data->hsic) return 0; - return usbmisc->ops->set_wakeup(data, enabled); + return usbmisc->ops->hsic_set_connect(data); } -EXPORT_SYMBOL_GPL(imx_usbmisc_set_wakeup); +EXPORT_SYMBOL_GPL(imx_usbmisc_hsic_set_connect); -int imx_usbmisc_hsic_set_connect(struct imx_usbmisc_data *data) +int imx_usbmisc_charger_detection(struct imx_usbmisc_data *data, bool connect) { struct imx_usbmisc *usbmisc; + struct usb_phy *usb_phy; + int ret = 0; if (!data) - return 0; + return -EINVAL; usbmisc = dev_get_drvdata(data->dev); - if (!usbmisc->ops->hsic_set_connect || !data->hsic) - return 0; - return usbmisc->ops->hsic_set_connect(data); + usb_phy = data->usb_phy; + if (!usbmisc->ops->charger_detection) + return -ENOTSUPP; + + if (connect) { + ret = usbmisc->ops->charger_detection(data); + if (ret) { + dev_err(data->dev, + "Error occurs during detection: %d\n", + ret); + usb_phy->chg_state = USB_CHARGER_ABSENT; + } else { + usb_phy->chg_state = USB_CHARGER_PRESENT; + } + } else { + usb_phy->chg_state = USB_CHARGER_ABSENT; + usb_phy->chg_type = UNKNOWN_TYPE; + } + return ret; } -EXPORT_SYMBOL_GPL(imx_usbmisc_hsic_set_connect); +EXPORT_SYMBOL_GPL(imx_usbmisc_charger_detection); -int imx_usbmisc_hsic_set_clk(struct imx_usbmisc_data *data, bool on) +int imx_usbmisc_suspend(struct imx_usbmisc_data *data, bool wakeup) { struct imx_usbmisc *usbmisc; + int ret = 0; if (!data) return 0; usbmisc = dev_get_drvdata(data->dev); - if (!usbmisc->ops->hsic_set_clk || !data->hsic) + + if (usbmisc->ops->vbus_comparator_on) + usbmisc->ops->vbus_comparator_on(data, false); + + if (wakeup && usbmisc->ops->set_wakeup) + ret = usbmisc->ops->set_wakeup(data, true); + if (ret) { + dev_err(data->dev, "set_wakeup failed, ret=%d\n", ret); + return ret; + } + + if (usbmisc->ops->hsic_set_clk && data->hsic) + ret = usbmisc->ops->hsic_set_clk(data, false); + if (ret) { + dev_err(data->dev, "set_wakeup failed, ret=%d\n", ret); + return ret; + } + + return ret; +} +EXPORT_SYMBOL_GPL(imx_usbmisc_suspend); + +int imx_usbmisc_resume(struct imx_usbmisc_data *data, bool wakeup) +{ + struct imx_usbmisc *usbmisc; + int ret = 0; + + if (!data) return 0; - return usbmisc->ops->hsic_set_clk(data, on); + + usbmisc = dev_get_drvdata(data->dev); + + if (usbmisc->ops->power_lost_check) + ret = usbmisc->ops->power_lost_check(data); + if (ret > 0) { + /* re-init if resume from power lost */ + ret = imx_usbmisc_init(data); + if (ret) { + dev_err(data->dev, "re-init failed, ret=%d\n", ret); + return ret; + } + } + + if (wakeup && usbmisc->ops->set_wakeup) + ret = usbmisc->ops->set_wakeup(data, false); + if (ret) { + dev_err(data->dev, "set_wakeup failed, ret=%d\n", ret); + return ret; + } + + if (usbmisc->ops->hsic_set_clk && data->hsic) + ret = usbmisc->ops->hsic_set_clk(data, true); + if (ret) { + dev_err(data->dev, "set_wakeup failed, ret=%d\n", ret); + goto hsic_set_clk_fail; + } + + if (usbmisc->ops->vbus_comparator_on) + usbmisc->ops->vbus_comparator_on(data, true); + + return 0; + +hsic_set_clk_fail: + if (wakeup && usbmisc->ops->set_wakeup) + usbmisc->ops->set_wakeup(data, true); + return ret; } -EXPORT_SYMBOL_GPL(imx_usbmisc_hsic_set_clk); +EXPORT_SYMBOL_GPL(imx_usbmisc_resume); + static const struct of_device_id usbmisc_imx_dt_ids[] = { { .compatible = "fsl,imx25-usbmisc", @@ -765,7 +1265,7 @@ static const struct of_device_id usbmisc_imx_dt_ids[] = { }, { .compatible = "fsl,imx7ulp-usbmisc", - .data = &imx7d_usbmisc_ops, + .data = &imx7ulp_usbmisc_ops, }, { /* sentinel */ } }; @@ -793,6 +1293,18 @@ static int usbmisc_imx_probe(struct platform_device *pdev) data->ops = (const struct usbmisc_ops *)of_id->data; platform_set_drvdata(pdev, data); + vbus_wakeup_reg = devm_regulator_get_optional(&pdev->dev, "vbus-wakeup"); + if (PTR_ERR(vbus_wakeup_reg) == -EPROBE_DEFER) + return -EPROBE_DEFER; + else if (PTR_ERR(vbus_wakeup_reg) == -ENODEV) + /* no vbus regualator is needed */ + vbus_wakeup_reg = NULL; + else if (IS_ERR(vbus_wakeup_reg)) { + dev_err(&pdev->dev, "Getting regulator error: %ld\n", + PTR_ERR(vbus_wakeup_reg)); + return PTR_ERR(vbus_wakeup_reg); + } + return 0; } diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index f225eaa98ff8..ec01bf5837c2 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -2104,6 +2104,140 @@ int usb_hcd_get_frame_number (struct usb_device *udev) } /*-------------------------------------------------------------------------*/ +#ifdef CONFIG_USB_HCD_TEST_MODE + +static void usb_ehset_completion(struct urb *urb) +{ + struct completion *done = urb->context; + + complete(done); +} +/* + * Allocate and initialize a control URB. This request will be used by the + * EHSET SINGLE_STEP_SET_FEATURE test in which the DATA and STATUS stages + * of the GetDescriptor request are sent 15 seconds after the SETUP stage. + * Return NULL if failed. + */ +static struct urb *request_single_step_set_feature_urb( + struct usb_device *udev, + void *dr, + void *buf, + struct completion *done +) { + struct urb *urb; + struct usb_hcd *hcd = bus_to_hcd(udev->bus); + struct usb_host_endpoint *ep; + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) + return NULL; + + urb->pipe = usb_rcvctrlpipe(udev, 0); + ep = (usb_pipein(urb->pipe) ? udev->ep_in : udev->ep_out) + [usb_pipeendpoint(urb->pipe)]; + if (!ep) { + usb_free_urb(urb); + return NULL; + } + + urb->ep = ep; + urb->dev = udev; + urb->setup_packet = (void *)dr; + urb->transfer_buffer = buf; + urb->transfer_buffer_length = USB_DT_DEVICE_SIZE; + urb->complete = usb_ehset_completion; + urb->status = -EINPROGRESS; + urb->actual_length = 0; + urb->transfer_flags = URB_DIR_IN; + usb_get_urb(urb); + atomic_inc(&urb->use_count); + atomic_inc(&urb->dev->urbnum); + urb->setup_dma = dma_map_single( + hcd->self.sysdev, + urb->setup_packet, + sizeof(struct usb_ctrlrequest), + DMA_TO_DEVICE); + urb->transfer_dma = dma_map_single( + hcd->self.sysdev, + urb->transfer_buffer, + urb->transfer_buffer_length, + DMA_FROM_DEVICE); + urb->context = done; + return urb; +} + +int ehset_single_step_set_feature(struct usb_hcd *hcd, int port) +{ + int retval = -ENOMEM; + struct usb_ctrlrequest *dr; + struct urb *urb; + struct usb_device *udev; + struct usb_device_descriptor *buf; + DECLARE_COMPLETION_ONSTACK(done); + + /* Obtain udev of the rhub's child port */ + udev = usb_hub_find_child(hcd->self.root_hub, port); + if (!udev) { + dev_err(hcd->self.controller, "No device attached to the RootHub\n"); + return -ENODEV; + } + buf = kmalloc(USB_DT_DEVICE_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + dr = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL); + if (!dr) { + kfree(buf); + return -ENOMEM; + } + + /* Fill Setup packet for GetDescriptor */ + dr->bRequestType = USB_DIR_IN; + dr->bRequest = USB_REQ_GET_DESCRIPTOR; + dr->wValue = cpu_to_le16(USB_DT_DEVICE << 8); + dr->wIndex = 0; + dr->wLength = cpu_to_le16(USB_DT_DEVICE_SIZE); + urb = request_single_step_set_feature_urb(udev, dr, buf, &done); + if (!urb) + goto cleanup; + + /* Submit just the SETUP stage */ + retval = hcd->driver->submit_single_step_set_feature(hcd, urb, 1); + if (retval) + goto out1; + if (!wait_for_completion_timeout(&done, msecs_to_jiffies(2000))) { + usb_kill_urb(urb); + retval = -ETIMEDOUT; + dev_err(hcd->self.controller, + "%s SETUP stage timed out on ep0\n", __func__); + goto out1; + } + msleep(15 * 1000); + + /* Complete remaining DATA and STATUS stages using the same URB */ + urb->status = -EINPROGRESS; + usb_get_urb(urb); + atomic_inc(&urb->use_count); + atomic_inc(&urb->dev->urbnum); + retval = hcd->driver->submit_single_step_set_feature(hcd, urb, 0); + if (!retval && !wait_for_completion_timeout(&done, + msecs_to_jiffies(2000))) { + usb_kill_urb(urb); + retval = -ETIMEDOUT; + dev_err(hcd->self.controller, + "%s IN stage timed out on ep0\n", __func__); + } +out1: + usb_free_urb(urb); +cleanup: + kfree(dr); + kfree(buf); + return retval; +} +EXPORT_SYMBOL_GPL(ehset_single_step_set_feature); +#endif /* CONFIG_USB_HCD_TEST_MODE */ + +/*-------------------------------------------------------------------------*/ #ifdef CONFIG_PM diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 236313f41f4a..c39985ea2eca 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -4705,7 +4705,8 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1, } if (r) { if (r != -ENODEV) - dev_err(&udev->dev, "device descriptor read/64, error %d\n", + dev_err(&udev->dev, + "device no response, device descriptor read/64, error %d\n", r); retval = -EMSGSIZE; continue; diff --git a/drivers/usb/core/otg_whitelist.h b/drivers/usb/core/otg_whitelist.h index 2ae90158ded7..9d0896174628 100644 --- a/drivers/usb/core/otg_whitelist.h +++ b/drivers/usb/core/otg_whitelist.h @@ -13,35 +13,62 @@ */ static struct usb_device_id whitelist_table[] = { - -/* hubs are optional in OTG, but very handy ... */ -{ USB_DEVICE_INFO(USB_CLASS_HUB, 0, 0), }, -{ USB_DEVICE_INFO(USB_CLASS_HUB, 0, 1), }, - -#ifdef CONFIG_USB_PRINTER /* ignoring nonstatic linkage! */ -/* FIXME actually, printers are NOT supposed to use device classes; - * they're supposed to use interface classes... - */ -{ USB_DEVICE_INFO(7, 1, 1) }, -{ USB_DEVICE_INFO(7, 1, 2) }, -{ USB_DEVICE_INFO(7, 1, 3) }, +/* Add FSL i.mx whitelist, the default list is for USB Compliance Test */ +#if defined(CONFIG_USB_EHSET_TEST_FIXTURE) \ + || defined(CONFIG_USB_EHSET_TEST_FIXTURE_MODULE) +#define TEST_SE0_NAK_PID 0x0101 +#define TEST_J_PID 0x0102 +#define TEST_K_PID 0x0103 +#define TEST_PACKET_PID 0x0104 +#define TEST_HS_HOST_PORT_SUSPEND_RESUME 0x0106 +#define TEST_SINGLE_STEP_GET_DEV_DESC 0x0107 +#define TEST_SINGLE_STEP_SET_FEATURE 0x0108 +{ USB_DEVICE(0x1a0a, TEST_SE0_NAK_PID) }, +{ USB_DEVICE(0x1a0a, TEST_J_PID) }, +{ USB_DEVICE(0x1a0a, TEST_K_PID) }, +{ USB_DEVICE(0x1a0a, TEST_PACKET_PID) }, +{ USB_DEVICE(0x1a0a, TEST_HS_HOST_PORT_SUSPEND_RESUME) }, +{ USB_DEVICE(0x1a0a, TEST_SINGLE_STEP_GET_DEV_DESC) }, +{ USB_DEVICE(0x1a0a, TEST_SINGLE_STEP_SET_FEATURE) }, #endif -#ifdef CONFIG_USB_NET_CDCETHER -/* Linux-USB CDC Ethernet gadget */ -{ USB_DEVICE(0x0525, 0xa4a1), }, -/* Linux-USB CDC Ethernet + RNDIS gadget */ -{ USB_DEVICE(0x0525, 0xa4a2), }, -#endif +#define USB_INTERFACE_CLASS_INFO(cl) \ + .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS, \ + .bInterfaceClass = (cl) -#if IS_ENABLED(CONFIG_USB_TEST) -/* gadget zero, for testing */ -{ USB_DEVICE(0x0525, 0xa4a0), }, +{USB_INTERFACE_CLASS_INFO(USB_CLASS_HUB) }, +#if defined(CONFIG_USB_STORAGE) || defined(CONFIG_USB_STORAGE_MODULE) +{USB_INTERFACE_CLASS_INFO(USB_CLASS_MASS_STORAGE) }, +#endif +#if defined(CONFIG_USB_HID) || defined(CONFIG_USB_HID_MODULE) +{USB_INTERFACE_CLASS_INFO(USB_CLASS_HID) }, #endif { } /* Terminating entry */ }; +static bool match_int_class(struct usb_device_id *id, struct usb_device *udev) +{ + struct usb_host_config *c; + int num_configs, i; + + /* Copy the code from generic.c */ + c = udev->config; + num_configs = udev->descriptor.bNumConfigurations; + for (i = 0; i < num_configs; (i++, c++)) { + struct usb_interface_descriptor *desc = NULL; + + /* It's possible that a config has no interfaces! */ + if (c->desc.bNumInterfaces > 0) + desc = &c->intf_cache[0]->altsetting->desc; + + if (desc && (desc->bInterfaceClass == id->bInterfaceClass)) + return true; + } + + return false; +} + static int is_targeted(struct usb_device *dev) { struct usb_device_id *id = whitelist_table; @@ -90,6 +117,10 @@ static int is_targeted(struct usb_device *dev) (id->bDeviceProtocol != dev->descriptor.bDeviceProtocol)) continue; + if ((id->match_flags & USB_DEVICE_ID_MATCH_INT_CLASS) && + (!match_int_class(id, dev))) + continue; + return 1; } diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 97d6ae3c4df2..d1dc2e940af1 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -894,6 +894,73 @@ static void dwc3_set_incr_burst_type(struct dwc3 *dwc) dwc3_writel(dwc->regs, DWC3_GSBUSCFG0, cfg); } +static void dwc3_set_power_down_clk_scale(struct dwc3 *dwc) +{ + u32 reg, scale; + + if (dwc->num_clks == 0) + return; + + /* + * The power down scale field specifies how many suspend_clk + * periods fit into a 16KHz clock period. When performing + * the division, round up the remainder. + */ + scale = DIV_ROUND_UP(clk_get_rate(dwc->clks[2].clk), 16384); + reg = dwc3_readl(dwc->regs, DWC3_GCTL); + reg &= ~(DWC3_GCTL_PWRDNSCALE_MASK); + reg |= DWC3_GCTL_PWRDNSCALE(scale); + dwc3_writel(dwc->regs, DWC3_GCTL, reg); +} + +#ifdef CONFIG_OF +struct dwc3_cache_type { + u8 transfer_type_datard; + u8 transfer_type_descrd; + u8 transfer_type_datawr; + u8 transfer_type_descwr; +}; + +static const struct dwc3_cache_type layerscape_dwc3_cache_type = { + .transfer_type_datard = 2, + .transfer_type_descrd = 2, + .transfer_type_datawr = 2, + .transfer_type_descwr = 2, +}; + +/** + * dwc3_set_cache_type - Configure cache type registers + * @dwc: Pointer to our controller context structure + */ +static void dwc3_set_cache_type(struct dwc3 *dwc) +{ + u32 tmp, reg; + const struct dwc3_cache_type *cache_type = + device_get_match_data(dwc->dev); + + if (cache_type) { + reg = dwc3_readl(dwc->regs, DWC3_GSBUSCFG0); + tmp = reg; + + reg &= ~DWC3_GSBUSCFG0_DATARD(~0); + reg |= DWC3_GSBUSCFG0_DATARD(cache_type->transfer_type_datard); + + reg &= ~DWC3_GSBUSCFG0_DESCRD(~0); + reg |= DWC3_GSBUSCFG0_DESCRD(cache_type->transfer_type_descrd); + + reg &= ~DWC3_GSBUSCFG0_DATAWR(~0); + reg |= DWC3_GSBUSCFG0_DATAWR(cache_type->transfer_type_datawr); + + reg &= ~DWC3_GSBUSCFG0_DESCWR(~0); + reg |= DWC3_GSBUSCFG0_DESCWR(cache_type->transfer_type_descwr); + + if (tmp != reg) + dwc3_writel(dwc->regs, DWC3_GSBUSCFG0, reg); + } +} +#endif + + /** * dwc3_core_init - Low-level initialization of DWC3 Core * @dwc: Pointer to our controller context structure @@ -918,6 +985,8 @@ static int dwc3_core_init(struct dwc3 *dwc) dwc->maximum_speed = USB_SPEED_HIGH; } + dwc3_set_power_down_clk_scale(dwc); + ret = dwc3_phy_setup(dwc); if (ret) goto err0; @@ -952,6 +1021,10 @@ static int dwc3_core_init(struct dwc3 *dwc) dwc3_set_incr_burst_type(dwc); +#ifdef CONFIG_OF + dwc3_set_cache_type(dwc); +#endif + usb_phy_set_suspend(dwc->usb2_phy, 0); usb_phy_set_suspend(dwc->usb3_phy, 0); ret = phy_power_on(dwc->usb2_generic_phy); @@ -1009,6 +1082,21 @@ static int dwc3_core_init(struct dwc3 *dwc) reg |= DWC3_GUCTL_HSTINAUTORETRY; dwc3_writel(dwc->regs, DWC3_GUCTL, reg); + + /* + * Disable Park Mode for super speed: + * Park mode is used in host mode when only a single async + * endpoint is active, but which has a known issue cause + * USB3.0 HC may die when read and write at the same time, + * considering the advantages of this mode are minimal, + * this issue only impacts super speed and exist on all IP + * versions, disable it for SS, Synopsys will release a formal + * STAR 9001415732, and disable it by default in next IP + * release. + */ + reg = dwc3_readl(dwc->regs, DWC3_GUCTL1); + reg |= DWC3_GUCTL1_PARKMODE_DISABLE_SS; + dwc3_writel(dwc->regs, DWC3_GUCTL1, reg); } /* @@ -1236,6 +1324,17 @@ static void dwc3_get_properties(struct dwc3 *dwc) dwc->maximum_speed = usb_get_maximum_speed(dev); dwc->dr_mode = usb_get_dr_mode(dev); + if (dwc->dr_mode == USB_DR_MODE_OTG) { + dwc->otg_caps.otg_rev = 0x0300; + dwc->otg_caps.hnp_support = true; + dwc->otg_caps.srp_support = true; + dwc->otg_caps.adp_support = true; + + /* Update otg capabilities by DT properties */ + of_usb_update_otg_caps(dev->of_node, + &dwc->otg_caps); + } + dwc->hsphy_mode = of_usb_get_phy_mode(dev->of_node); dwc->sysdev_is_parent = device_property_read_bool(dev, @@ -1315,6 +1414,9 @@ static void dwc3_get_properties(struct dwc3 *dwc) dwc->dis_metastability_quirk = device_property_read_bool(dev, "snps,dis_metastability_quirk"); + dwc->host_vbus_glitches = device_property_read_bool(dev, + "snps,host-vbus-glitches"); + dwc->lpm_nyet_threshold = lpm_nyet_threshold; dwc->tx_de_emphasis = tx_de_emphasis; @@ -1837,12 +1939,9 @@ static const struct dev_pm_ops dwc3_dev_pm_ops = { #ifdef CONFIG_OF static const struct of_device_id of_dwc3_match[] = { - { - .compatible = "snps,dwc3" - }, - { - .compatible = "synopsys,dwc3" - }, + { .compatible = "fsl,layerscape-dwc3", .data = &layerscape_dwc3_cache_type, }, + { .compatible = "snps,dwc3" }, + { .compatible = "synopsys,dwc3" }, { }, }; MODULE_DEVICE_TABLE(of, of_dwc3_match); diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index 1c8b349379af..1ea3c50d2586 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -25,6 +25,7 @@ #include <linux/usb/ch9.h> #include <linux/usb/gadget.h> #include <linux/usb/otg.h> +#include <linux/usb/role.h> #include <linux/ulpi/interface.h> #include <linux/phy/phy.h> @@ -165,6 +166,21 @@ /* Bit fields */ /* Global SoC Bus Configuration INCRx Register 0 */ +#ifdef CONFIG_OF +#define DWC3_GSBUSCFG0_DATARD_SHIFT 28 +#define DWC3_GSBUSCFG0_DATARD(n) (((n) & 0xf) \ + << DWC3_GSBUSCFG0_DATARD_SHIFT) +#define DWC3_GSBUSCFG0_DESCRD_SHIFT 24 +#define DWC3_GSBUSCFG0_DESCRD(n) (((n) & 0xf) \ + << DWC3_GSBUSCFG0_DESCRD_SHIFT) +#define DWC3_GSBUSCFG0_DATAWR_SHIFT 20 +#define DWC3_GSBUSCFG0_DATAWR(n) (((n) & 0xf) \ + << DWC3_GSBUSCFG0_DATAWR_SHIFT) +#define DWC3_GSBUSCFG0_DESCWR_SHIFT 16 +#define DWC3_GSBUSCFG0_DESCWR(n) (((n) & 0xf) \ + << DWC3_GSBUSCFG0_DESCWR_SHIFT) +#endif + #define DWC3_GSBUSCFG0_INCR256BRSTENA (1 << 7) /* INCR256 burst */ #define DWC3_GSBUSCFG0_INCR128BRSTENA (1 << 6) /* INCR128 burst */ #define DWC3_GSBUSCFG0_INCR64BRSTENA (1 << 5) /* INCR64 burst */ @@ -223,6 +239,7 @@ /* Global Configuration Register */ #define DWC3_GCTL_PWRDNSCALE(n) ((n) << 19) +#define DWC3_GCTL_PWRDNSCALE_MASK DWC3_GCTL_PWRDNSCALE(0x1fff) #define DWC3_GCTL_U2RSTECN BIT(16) #define DWC3_GCTL_RAMCLKSEL(x) (((x) & DWC3_GCTL_CLK_MASK) << 6) #define DWC3_GCTL_CLK_BUS (0) @@ -251,6 +268,7 @@ /* Global User Control 1 Register */ #define DWC3_GUCTL1_TX_IPGAP_LINECHECK_DIS BIT(28) #define DWC3_GUCTL1_DEV_L1_EXIT_BY_HW BIT(24) +#define DWC3_GUCTL1_PARKMODE_DISABLE_SS BIT(17) /* Global Status Register */ #define DWC3_GSTS_OTG_IP BIT(10) @@ -941,6 +959,7 @@ struct dwc3_scratchpad_array { * @nr_scratch: number of scratch buffers * @u1u2: only used on revisions <1.83a for workaround * @maximum_speed: maximum speed requested (mainly for testing purposes) + * @otg_caps: the OTG capabilities from hardware point * @revision: revision register contents * @version_type: VERSIONTYPE register contents, a sub release of a revision * @dr_mode: requested mode of operation @@ -1029,6 +1048,8 @@ struct dwc3_scratchpad_array { * 2 - No de-emphasis * 3 - Reserved * @dis_metastability_quirk: set to disable metastability quirk. + * @host_vbus_glitches: set to avoid vbus glitch during + * xhci reset. * @imod_interval: set the interrupt moderation interval in 250ns * increments or 0 to disable. */ @@ -1078,6 +1099,7 @@ struct dwc3 { void __iomem *regs; size_t regs_size; + struct usb_role_switch *role_switch; enum usb_dr_mode dr_mode; u32 current_dr_role; u32 desired_dr_role; @@ -1094,6 +1116,7 @@ struct dwc3 { u32 nr_scratch; u32 u1u2; u32 maximum_speed; + struct usb_otg_caps otg_caps; /* * All 3.1 IP version constants are greater than the 3.0 IP @@ -1218,6 +1241,7 @@ struct dwc3 { unsigned tx_de_emphasis:2; unsigned dis_metastability_quirk:1; + unsigned host_vbus_glitches:1; u16 imod_interval; }; diff --git a/drivers/usb/dwc3/drd.c b/drivers/usb/dwc3/drd.c index c946d64142ad..72cbbc2c1bc1 100644 --- a/drivers/usb/dwc3/drd.c +++ b/drivers/usb/dwc3/drd.c @@ -476,6 +476,43 @@ static struct extcon_dev *dwc3_get_extcon(struct dwc3 *dwc) return edev; } +static int dwc3_usb_role_switch_set(struct device *dev, enum usb_role role) +{ + 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: + return 0; + }; + + dwc3_set_mode(dev_get_drvdata(dev), mode); + return 0; +} + +static enum usb_role dwc3_usb_role_switch_get(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + unsigned long flags; + enum usb_role role; + + spin_lock_irqsave(&dwc->lock, flags); + role = dwc->current_dr_role; + spin_unlock_irqrestore(&dwc->lock, flags); + + return role; +} + +static struct usb_role_switch_desc dwc3_role_switch = { + .set = dwc3_usb_role_switch_set, + .get = dwc3_usb_role_switch_get, +}; + int dwc3_drd_init(struct dwc3 *dwc) { int ret, irq; @@ -484,7 +521,13 @@ int dwc3_drd_init(struct dwc3 *dwc) if (IS_ERR(dwc->edev)) return PTR_ERR(dwc->edev); - if (dwc->edev) { + if (device_property_read_bool(dwc->dev, "usb-role-switch")) { + dwc3_role_switch.fwnode = dev_fwnode(dwc->dev); + dwc->role_switch = usb_role_switch_register(dwc->dev, + &dwc3_role_switch); + if (IS_ERR(dwc->role_switch)) + return PTR_ERR(dwc->role_switch); + } else if (dwc->edev) { dwc->edev_nb.notifier_call = dwc3_drd_notifier; ret = extcon_register_notifier(dwc->edev, EXTCON_USB_HOST, &dwc->edev_nb); @@ -531,6 +574,9 @@ void dwc3_drd_exit(struct dwc3 *dwc) { unsigned long flags; + if (dwc->role_switch) + usb_role_switch_unregister(dwc->role_switch); + if (dwc->edev) extcon_unregister_notifier(dwc->edev, EXTCON_USB_HOST, &dwc->edev_nb); diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index a9aba716bf80..8e2bdcf6ed81 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -270,7 +270,7 @@ int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned cmd, { const struct usb_endpoint_descriptor *desc = dep->endpoint.desc; struct dwc3 *dwc = dep->dwc; - u32 timeout = 1000; + u32 timeout = 2000; u32 saved_config = 0; u32 reg; @@ -3343,6 +3343,10 @@ int dwc3_gadget_init(struct dwc3 *dwc) dwc->gadget.sg_supported = true; dwc->gadget.name = "dwc3-gadget"; dwc->gadget.lpm_capable = true; + dwc->gadget.is_otg = (dwc->dr_mode == USB_DR_MODE_OTG) && + (dwc->otg_caps.hnp_support || + dwc->otg_caps.srp_support || + dwc->otg_caps.adp_support); /* * FIXME We might be setting max_speed to <SUPER, however versions diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c index 5567ed2cddbe..cbadceae92e7 100644 --- a/drivers/usb/dwc3/host.c +++ b/drivers/usb/dwc3/host.c @@ -9,8 +9,49 @@ #include <linux/platform_device.h> +#include "../host/xhci.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 + * @dwc3: Pointer to our controller context structure + */ +static void dwc3_power_off_all_roothub_ports(struct dwc3 *dwc) +{ + int i, port_num; + u32 reg, op_regs_base, offset; + void __iomem *xhci_regs; + + /* xhci regs is not mapped yet, do it temperary here */ + if (dwc->xhci_resources[0].start) { + xhci_regs = ioremap(dwc->xhci_resources[0].start, + DWC3_XHCI_REGS_END); + if (IS_ERR(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 int dwc3_host_get_irq(struct dwc3 *dwc) { struct platform_device *dwc3_pdev = to_platform_device(dwc->dev); @@ -50,6 +91,13 @@ int dwc3_host_init(struct dwc3 *dwc) struct platform_device *dwc3_pdev = to_platform_device(dwc->dev); int prop_idx = 0; + /* + * We have to power off all Root hub ports immediately after DWC3 set + * to host mode to avoid VBUS glitch happen when xhci get reset later. + */ + if (dwc->host_vbus_glitches) + dwc3_power_off_all_roothub_ports(dwc); + irq = dwc3_host_get_irq(dwc); if (irq < 0) return irq; diff --git a/drivers/usb/gadget/udc/fsl_udc_core.c b/drivers/usb/gadget/udc/fsl_udc_core.c index 9a05863b2876..980cb1382851 100644 --- a/drivers/usb/gadget/udc/fsl_udc_core.c +++ b/drivers/usb/gadget/udc/fsl_udc_core.c @@ -1052,10 +1052,11 @@ static int fsl_ep_fifo_status(struct usb_ep *_ep) u32 bitmask; struct ep_queue_head *qh; - ep = container_of(_ep, struct fsl_ep, ep); - if (!_ep || (!ep->ep.desc && ep_index(ep) != 0)) + if (!_ep || _ep->desc || !(_ep->desc->bEndpointAddress&0xF)) return -ENODEV; + ep = container_of(_ep, struct fsl_ep, ep); + udc = (struct fsl_udc *)ep->udc; if (!udc->driver || udc->gadget.speed == USB_SPEED_UNKNOWN) @@ -1595,14 +1596,13 @@ static int process_ep_req(struct fsl_udc *udc, int pipe, struct fsl_req *curr_req) { struct ep_td_struct *curr_td; - int td_complete, actual, remaining_length, j, tmp; + int actual, remaining_length, j, tmp; int status = 0; int errors = 0; struct ep_queue_head *curr_qh = &udc->ep_qh[pipe]; int direction = pipe % 2; curr_td = curr_req->head; - td_complete = 0; actual = curr_req->req.length; for (j = 0; j < curr_req->dtd_count; j++) { @@ -1647,11 +1647,9 @@ static int process_ep_req(struct fsl_udc *udc, int pipe, status = -EPROTO; break; } else { - td_complete++; break; } } else { - td_complete++; VDBG("dTD transmitted successful"); } diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index cf2b7ae93b7e..ba8c799b5521 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -80,7 +80,7 @@ module_param (log2_irq_thresh, int, S_IRUGO); MODULE_PARM_DESC (log2_irq_thresh, "log2 IRQ latency, 1-64 microframes"); /* initial park setting: slower than hw default */ -static unsigned park = 0; +static unsigned park = 3; module_param (park, uint, S_IRUGO); MODULE_PARM_DESC (park, "park setting; 1-3 back-to-back async packets"); @@ -1232,6 +1232,10 @@ static const struct hc_driver ehci_hc_driver = { * device support */ .free_dev = ehci_remove_device, +#ifdef CONFIG_USB_HCD_TEST_MODE + /* EH SINGLE_STEP_SET_FEATURE test support */ + .submit_single_step_set_feature = ehci_submit_single_step_set_feature, +#endif }; void ehci_init_driver(struct hc_driver *drv, diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c index ce0eaf7d7c12..fb4463f03b45 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c @@ -725,145 +725,6 @@ ehci_hub_descriptor ( } /*-------------------------------------------------------------------------*/ -#ifdef CONFIG_USB_HCD_TEST_MODE - -#define EHSET_TEST_SINGLE_STEP_SET_FEATURE 0x06 - -static void usb_ehset_completion(struct urb *urb) -{ - struct completion *done = urb->context; - - complete(done); -} -static int submit_single_step_set_feature( - struct usb_hcd *hcd, - struct urb *urb, - int is_setup -); - -/* - * Allocate and initialize a control URB. This request will be used by the - * EHSET SINGLE_STEP_SET_FEATURE test in which the DATA and STATUS stages - * of the GetDescriptor request are sent 15 seconds after the SETUP stage. - * Return NULL if failed. - */ -static struct urb *request_single_step_set_feature_urb( - struct usb_device *udev, - void *dr, - void *buf, - struct completion *done -) { - struct urb *urb; - struct usb_hcd *hcd = bus_to_hcd(udev->bus); - struct usb_host_endpoint *ep; - - urb = usb_alloc_urb(0, GFP_KERNEL); - if (!urb) - return NULL; - - urb->pipe = usb_rcvctrlpipe(udev, 0); - ep = (usb_pipein(urb->pipe) ? udev->ep_in : udev->ep_out) - [usb_pipeendpoint(urb->pipe)]; - if (!ep) { - usb_free_urb(urb); - return NULL; - } - - urb->ep = ep; - urb->dev = udev; - urb->setup_packet = (void *)dr; - urb->transfer_buffer = buf; - urb->transfer_buffer_length = USB_DT_DEVICE_SIZE; - urb->complete = usb_ehset_completion; - urb->status = -EINPROGRESS; - urb->actual_length = 0; - urb->transfer_flags = URB_DIR_IN; - usb_get_urb(urb); - atomic_inc(&urb->use_count); - atomic_inc(&urb->dev->urbnum); - urb->setup_dma = dma_map_single( - hcd->self.sysdev, - urb->setup_packet, - sizeof(struct usb_ctrlrequest), - DMA_TO_DEVICE); - urb->transfer_dma = dma_map_single( - hcd->self.sysdev, - urb->transfer_buffer, - urb->transfer_buffer_length, - DMA_FROM_DEVICE); - urb->context = done; - return urb; -} - -static int ehset_single_step_set_feature(struct usb_hcd *hcd, int port) -{ - int retval = -ENOMEM; - struct usb_ctrlrequest *dr; - struct urb *urb; - struct usb_device *udev; - struct ehci_hcd *ehci = hcd_to_ehci(hcd); - struct usb_device_descriptor *buf; - DECLARE_COMPLETION_ONSTACK(done); - - /* Obtain udev of the rhub's child port */ - udev = usb_hub_find_child(hcd->self.root_hub, port); - if (!udev) { - ehci_err(ehci, "No device attached to the RootHub\n"); - return -ENODEV; - } - buf = kmalloc(USB_DT_DEVICE_SIZE, GFP_KERNEL); - if (!buf) - return -ENOMEM; - - dr = kmalloc(sizeof(struct usb_ctrlrequest), GFP_KERNEL); - if (!dr) { - kfree(buf); - return -ENOMEM; - } - - /* Fill Setup packet for GetDescriptor */ - dr->bRequestType = USB_DIR_IN; - dr->bRequest = USB_REQ_GET_DESCRIPTOR; - dr->wValue = cpu_to_le16(USB_DT_DEVICE << 8); - dr->wIndex = 0; - dr->wLength = cpu_to_le16(USB_DT_DEVICE_SIZE); - urb = request_single_step_set_feature_urb(udev, dr, buf, &done); - if (!urb) - goto cleanup; - - /* Submit just the SETUP stage */ - retval = submit_single_step_set_feature(hcd, urb, 1); - if (retval) - goto out1; - if (!wait_for_completion_timeout(&done, msecs_to_jiffies(2000))) { - usb_kill_urb(urb); - retval = -ETIMEDOUT; - ehci_err(ehci, "%s SETUP stage timed out on ep0\n", __func__); - goto out1; - } - msleep(15 * 1000); - - /* Complete remaining DATA and STATUS stages using the same URB */ - urb->status = -EINPROGRESS; - usb_get_urb(urb); - atomic_inc(&urb->use_count); - atomic_inc(&urb->dev->urbnum); - retval = submit_single_step_set_feature(hcd, urb, 0); - if (!retval && !wait_for_completion_timeout(&done, - msecs_to_jiffies(2000))) { - usb_kill_urb(urb); - retval = -ETIMEDOUT; - ehci_err(ehci, "%s IN stage timed out on ep0\n", __func__); - } -out1: - usb_free_urb(urb); -cleanup: - kfree(dr); - kfree(buf); - return retval; -} -#endif /* CONFIG_USB_HCD_TEST_MODE */ -/*-------------------------------------------------------------------------*/ int ehci_hub_control( struct usb_hcd *hcd, diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c index aa2f77f1506d..6709e5708453 100644 --- a/drivers/usb/host/ehci-q.c +++ b/drivers/usb/host/ehci-q.c @@ -1154,7 +1154,7 @@ submit_async ( * performed; TRUE - SETUP and FALSE - IN+STATUS * Returns 0 if success */ -static int submit_single_step_set_feature( +static int ehci_submit_single_step_set_feature( struct usb_hcd *hcd, struct urb *urb, int is_setup diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index b7d23c438756..e8db6429a4e3 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -1359,6 +1359,15 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, /* 4.19.6 Port Test Modes (USB2 Test Mode) */ if (hcd->speed != HCD_USB2) goto error; +#ifdef CONFIG_USB_HCD_TEST_MODE + if (test_mode == EHSET_TEST_SINGLE_STEP_SET_FEATURE) { + spin_unlock_irqrestore(&xhci->lock, flags); + retval = ehset_single_step_set_feature(hcd, + wIndex + 1); + spin_lock_irqsave(&xhci->lock, flags); + break; + } +#endif if (test_mode > TEST_FORCE_EN || test_mode < TEST_J) goto error; retval = xhci_enter_test_mode(xhci, test_mode, wIndex, @@ -1637,6 +1646,7 @@ retry: spin_unlock_irqrestore(&xhci->lock, flags); return 0; } +EXPORT_SYMBOL(xhci_bus_suspend); /* * Workaround for missing Cold Attach Status (CAS) if device re-plugged in S3. @@ -1654,7 +1664,8 @@ static bool xhci_port_missing_cas_quirk(struct xhci_port *port) return false; if (((portsc & PORT_PLS_MASK) != XDEV_POLLING) && - ((portsc & PORT_PLS_MASK) != XDEV_COMP_MODE)) + ((portsc & PORT_PLS_MASK) != XDEV_COMP_MODE) && + ((portsc & PORT_PLS_MASK) != XDEV_RXDETECT)) return false; /* clear wakeup/change bits, and do a warm port reset */ diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index d90cd5ec09cf..4655016eaf45 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -291,6 +291,10 @@ static int xhci_plat_probe(struct platform_device *pdev) device_property_read_u32(tmpdev, "imod-interval-ns", &xhci->imod_interval); + + if (device_property_read_bool(tmpdev, + "usb3-resume-missing-cas")) + xhci->quirks |= XHCI_MISSING_CAS; } hcd->usb_phy = devm_usb_get_phy_by_phandle(sysdev, "usb-phy", 0); diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index e7aab31fd9a5..8000ad9db792 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -2028,12 +2028,9 @@ static int process_ctrl_td(struct xhci_hcd *xhci, struct xhci_td *td, switch (trb_comp_code) { case COMP_SUCCESS: - if (trb_type != TRB_STATUS) { - xhci_warn(xhci, "WARN: Success on ctrl %s TRB without IOC set?\n", + if (trb_type != TRB_STATUS) + xhci_dbg(xhci, "Success on ctrl %s TRB without IOC set?\n", (trb_type == TRB_DATA) ? "data" : "setup"); - *status = -ESHUTDOWN; - break; - } *status = 0; break; case COMP_SHORT_PACKET: @@ -3519,6 +3516,129 @@ int xhci_queue_ctrl_tx(struct xhci_hcd *xhci, gfp_t mem_flags, return 0; } +#ifdef CONFIG_USB_HCD_TEST_MODE +/* + * This function prepare TRBs and submits them for the + * SINGLE_STEP_SET_FEATURE Test. + * This is done in two parts: first SETUP req for GetDesc is sent then + * 15 seconds later, the IN stage for GetDesc starts to req data from dev + * + * is_setup : argument decides which of the two stage needs to be + * performed; TRUE - SETUP and FALSE - IN+STATUS + * Returns 0 if success + */ +int xhci_submit_single_step_set_feature(struct usb_hcd *hcd, + struct urb *urb, int is_setup) +{ + int slot_id; + unsigned int ep_index; + struct xhci_ring *ep_ring; + int ret; + struct usb_ctrlrequest *setup; + struct xhci_generic_trb *start_trb; + int start_cycle; + u32 field, length_field, remainder; + struct urb_priv *urb_priv; + struct xhci_td *td; + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + + /* urb_priv will be free after transcation has completed */ + urb_priv = kzalloc(sizeof(struct urb_priv) + + sizeof(struct xhci_td), GFP_KERNEL); + if (!urb_priv) + return -ENOMEM; + + td = &urb_priv->td[0]; + urb_priv->num_tds = 1; + urb_priv->num_tds_done = 0; + urb->hcpriv = urb_priv; + + ep_ring = xhci_urb_to_transfer_ring(xhci, urb); + if (!ep_ring) { + ret = -EINVAL; + goto free_priv; + } + + slot_id = urb->dev->slot_id; + ep_index = xhci_get_endpoint_index(&urb->ep->desc); + + setup = (struct usb_ctrlrequest *) urb->setup_packet; + if (is_setup) { + ret = prepare_transfer(xhci, xhci->devs[slot_id], + ep_index, urb->stream_id, + 1, urb, 0, GFP_KERNEL); + if (ret < 0) + goto free_priv; + + start_trb = &ep_ring->enqueue->generic; + start_cycle = ep_ring->cycle_state; + /* Save the DMA address of the last TRB in the TD */ + td->last_trb = ep_ring->enqueue; + field = TRB_IOC | TRB_IDT | TRB_TYPE(TRB_SETUP) | start_cycle; + /* xHCI 1.0/1.1 6.4.1.2.1: Transfer Type field */ + if ((xhci->hci_version >= 0x100) || + (xhci->quirks & XHCI_MTK_HOST)) + field |= TRB_TX_TYPE(TRB_DATA_IN); + + queue_trb(xhci, ep_ring, false, + setup->bRequestType | setup->bRequest << 8 | + le16_to_cpu(setup->wValue) << 16, + le16_to_cpu(setup->wIndex) | + le16_to_cpu(setup->wLength) << 16, + TRB_LEN(8) | TRB_INTR_TARGET(0), + /* Immediate data in pointer */ + field); + giveback_first_trb(xhci, slot_id, ep_index, urb->stream_id, + start_cycle, start_trb); + return 0; + } + + ret = prepare_transfer(xhci, xhci->devs[slot_id], + ep_index, urb->stream_id, + 2, urb, 0, GFP_KERNEL); + if (ret < 0) + goto free_priv; + + start_trb = &ep_ring->enqueue->generic; + start_cycle = ep_ring->cycle_state; + field = TRB_ISP | TRB_TYPE(TRB_DATA); + + remainder = xhci_td_remainder(xhci, 0, + urb->transfer_buffer_length, + urb->transfer_buffer_length, + urb, 1); + + length_field = TRB_LEN(urb->transfer_buffer_length) | + TRB_TD_SIZE(remainder) | + TRB_INTR_TARGET(0); + + if (urb->transfer_buffer_length > 0) { + field |= TRB_DIR_IN; + queue_trb(xhci, ep_ring, true, + lower_32_bits(urb->transfer_dma), + upper_32_bits(urb->transfer_dma), + length_field, + field | ep_ring->cycle_state); + } + + td->last_trb = ep_ring->enqueue; + field = TRB_IOC | TRB_TYPE(TRB_STATUS) | ep_ring->cycle_state; + queue_trb(xhci, ep_ring, false, + 0, + 0, + TRB_INTR_TARGET(0), + field); + + giveback_first_trb(xhci, slot_id, ep_index, 0, + start_cycle, start_trb); + + return 0; +free_priv: + xhci_urb_free_priv(urb_priv); + return ret; +} +#endif /* CONFIG_USB_HCD_TEST_MODE */ + /* * The transfer burst count field of the isochronous TRB defines the number of * bursts that are required to move all packets in this TD. Only SuperSpeed diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 6c17e3fe181a..2a5d71374855 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -193,7 +193,7 @@ int xhci_reset(struct xhci_hcd *xhci) * Without this delay, the subsequent HC register access, * may result in a system hang very rarely. */ - if (xhci->quirks & XHCI_INTEL_HOST) + if (xhci->quirks & (XHCI_INTEL_HOST | XHCI_CDNS_HOST)) udelay(1000); ret = xhci_handshake(&xhci->op_regs->command, @@ -5355,6 +5355,7 @@ static const struct hc_driver xhci_hc_driver = { .disable_usb3_lpm_timeout = xhci_disable_usb3_lpm_timeout, .find_raw_port_number = xhci_find_raw_port_number, .clear_tt_buffer_complete = xhci_clear_tt_buffer_complete, + .submit_single_step_set_feature = xhci_submit_single_step_set_feature, }; void xhci_init_driver(struct hc_driver *drv, @@ -5371,6 +5372,8 @@ void xhci_init_driver(struct hc_driver *drv, drv->reset = over->reset; if (over->start) drv->start = over->start; + if (over->bus_suspend) + drv->bus_suspend = over->bus_suspend; } } EXPORT_SYMBOL_GPL(xhci_init_driver); diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index f9f88626a57a..321cf42f50d9 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1867,6 +1867,7 @@ struct xhci_hcd { #define XHCI_DEFAULT_PM_RUNTIME_ALLOW BIT_ULL(33) #define XHCI_RESET_PLL_ON_DISCONNECT BIT_ULL(34) #define XHCI_SNPS_BROKEN_SUSPEND BIT_ULL(35) +#define XHCI_CDNS_HOST BIT_ULL(36) unsigned int num_active_eps; unsigned int limit_active_eps; @@ -1901,6 +1902,7 @@ struct xhci_driver_overrides { size_t extra_priv_size; int (*reset)(struct usb_hcd *hcd); int (*start)(struct usb_hcd *hcd); + int (*bus_suspend)(struct usb_hcd *hcd); }; #define XHCI_CFC_DELAY 10 @@ -2132,6 +2134,16 @@ int xhci_find_raw_port_number(struct usb_hcd *hcd, int port1); struct xhci_hub *xhci_get_rhub(struct usb_hcd *hcd); void xhci_hc_died(struct xhci_hcd *xhci); +#ifdef CONFIG_USB_HCD_TEST_MODE +int xhci_submit_single_step_set_feature(struct usb_hcd *hcd, + struct urb *urb, int is_setup); +#else +static inline int xhci_submit_single_step_set_feature(struct usb_hcd *hcd, + struct urb *urb, int is_setup) +{ + return 0; +} +#endif #ifdef CONFIG_PM int xhci_bus_suspend(struct usb_hcd *hcd); diff --git a/drivers/usb/phy/Kconfig b/drivers/usb/phy/Kconfig index 24b4f091acb8..4b2002dabcdf 100644 --- a/drivers/usb/phy/Kconfig +++ b/drivers/usb/phy/Kconfig @@ -152,7 +152,7 @@ config USB_MV_OTG config USB_MXS_PHY tristate "Freescale MXS USB PHY support" - depends on ARCH_MXC || ARCH_MXS + depends on ARCH_MXC || ARCH_MXS || ARCH_MXC_ARM64 select STMP_DEVICE select USB_PHY help diff --git a/drivers/usb/phy/phy-mxs-usb.c b/drivers/usb/phy/phy-mxs-usb.c index 70b8c8248caf..299eb6be79b8 100644 --- a/drivers/usb/phy/phy-mxs-usb.c +++ b/drivers/usb/phy/phy-mxs-usb.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0+ /* - * Copyright 2012-2014 Freescale Semiconductor, Inc. + * Copyright 2012-2016 Freescale Semiconductor, Inc. + * Copyright 2017 NXP * Copyright (C) 2012 Marek Vasut <marex@denx.de> * on behalf of DENX Software Engineering GmbH */ @@ -18,6 +19,7 @@ #include <linux/regmap.h> #include <linux/mfd/syscon.h> #include <linux/iopoll.h> +#include <linux/regulator/consumer.h> #define DRIVER_NAME "mxs_phy" @@ -70,6 +72,12 @@ #define BM_USBPHY_PLL_EN_USB_CLKS BIT(6) /* Anatop Registers */ +#define ANADIG_PLL_USB2 0x20 +#define ANADIG_PLL_USB2_SET 0x24 +#define ANADIG_PLL_USB2_CLR 0x28 +#define ANADIG_REG_1P1_SET 0x114 +#define ANADIG_REG_1P1_CLR 0x118 + #define ANADIG_ANA_MISC0 0x150 #define ANADIG_ANA_MISC0_SET 0x154 #define ANADIG_ANA_MISC0_CLR 0x158 @@ -117,6 +125,42 @@ #define BM_ANADIG_USB2_MISC_RX_VPIN_FS BIT(29) #define BM_ANADIG_USB2_MISC_RX_VMIN_FS BIT(28) +/* System Integration Module (SIM) Registers */ +#define SIM_GPR1 0x30 + +#define USB_PHY_VLLS_WAKEUP_EN BIT(0) + +#define BM_ANADIG_REG_1P1_ENABLE_WEAK_LINREG BIT(18) +#define BM_ANADIG_REG_1P1_TRACK_VDD_SOC_CAP BIT(19) + +#define BM_ANADIG_PLL_USB2_HOLD_RING_OFF BIT(11) + +/* DCD module, the offset is 0x800 */ +#define DCD_CONTROL 0x800 +#define DCD_CLOCK (DCD_CONTROL + 0x4) +#define DCD_STATUS (DCD_CONTROL + 0x8) + +#define DCD_CONTROL_SR BIT(25) +#define DCD_CONTROL_START BIT(24) +#define DCD_CONTROL_BC12 BIT(17) +#define DCD_CONTROL_IE BIT(16) +#define DCD_CONTROL_IF BIT(8) +#define DCD_CONTROL_IACK BIT(0) + +#define DCD_CLOCK_MHZ BIT(0) + +#define DCD_STATUS_ACTIVE BIT(22) +#define DCD_STATUS_TO BIT(21) +#define DCD_STATUS_ERR BIT(20) +#define DCD_STATUS_SEQ_STAT (BIT(18) | BIT(19)) +#define DCD_CHG_PORT BIT(19) +#define DCD_CHG_DET (BIT(18) | BIT(19)) +#define DCD_CHG_DPIN BIT(18) +#define DCD_STATUS_SEQ_RES (BIT(16) | BIT(17)) +#define DCD_SDP_PORT BIT(16) +#define DCD_CDP_PORT BIT(17) +#define DCD_DCP_PORT (BIT(16) | BIT(17)) + #define to_mxs_phy(p) container_of((p), struct mxs_phy, phy) /* Do disconnection between PHY and controller without vbus */ @@ -149,6 +193,19 @@ #define MXS_PHY_TX_D_CAL_MIN 79 #define MXS_PHY_TX_D_CAL_MAX 119 +/* + * At some versions, the PHY2's clock is controlled by hardware directly, + * eg, according to PHY's suspend status. In these PHYs, we only need to + * open the clock at the initialization and close it at its shutdown routine. + * It will be benefit for remote wakeup case which needs to send resume + * signal as soon as possible, and in this case, the resume signal can be sent + * out without software interfere. + */ +#define MXS_PHY_HARDWARE_CONTROL_PHY2_CLK BIT(4) + +/* The MXS PHYs which have DCD module for charger detection */ +#define MXS_PHY_HAS_DCD BIT(5) + struct mxs_phy_data { unsigned int flags; }; @@ -160,12 +217,14 @@ static const struct mxs_phy_data imx23_phy_data = { static const struct mxs_phy_data imx6q_phy_data = { .flags = MXS_PHY_SENDING_SOF_TOO_FAST | MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS | - MXS_PHY_NEED_IP_FIX, + MXS_PHY_NEED_IP_FIX | + MXS_PHY_HARDWARE_CONTROL_PHY2_CLK, }; static const struct mxs_phy_data imx6sl_phy_data = { .flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS | - MXS_PHY_NEED_IP_FIX, + MXS_PHY_NEED_IP_FIX | + MXS_PHY_HARDWARE_CONTROL_PHY2_CLK, }; static const struct mxs_phy_data vf610_phy_data = { @@ -174,14 +233,17 @@ static const struct mxs_phy_data vf610_phy_data = { }; static const struct mxs_phy_data imx6sx_phy_data = { - .flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS, + .flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS | + MXS_PHY_HARDWARE_CONTROL_PHY2_CLK, }; static const struct mxs_phy_data imx6ul_phy_data = { - .flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS, + .flags = MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS | + MXS_PHY_HARDWARE_CONTROL_PHY2_CLK, }; static const struct mxs_phy_data imx7ulp_phy_data = { + .flags = MXS_PHY_HAS_DCD, }; static const struct of_device_id mxs_phy_dt_ids[] = { @@ -201,9 +263,14 @@ struct mxs_phy { struct clk *clk; const struct mxs_phy_data *data; struct regmap *regmap_anatop; + struct regmap *regmap_sim; int port_id; u32 tx_reg_set; u32 tx_reg_mask; + struct regulator *phy_3p0; + bool hardware_control_phy2_clk; + enum usb_current_mode mode; + unsigned long clk_rate; }; static inline bool is_imx6q_phy(struct mxs_phy *mxs_phy) @@ -221,6 +288,11 @@ static inline bool is_imx7ulp_phy(struct mxs_phy *mxs_phy) return mxs_phy->data == &imx7ulp_phy_data; } +static inline bool is_imx6ul_phy(struct mxs_phy *mxs_phy) +{ + return mxs_phy->data == &imx6ul_phy_data; +} + /* * PHY needs some 32K cycles to switch from 32K clock to * bus (such as AHB/AXI, etc) clock. @@ -288,6 +360,16 @@ static int mxs_phy_hw_init(struct mxs_phy *mxs_phy) if (ret) goto disable_pll; + if (mxs_phy->phy_3p0) { + ret = regulator_enable(mxs_phy->phy_3p0); + if (ret) { + dev_err(mxs_phy->phy.dev, + "Failed to enable 3p0 regulator, ret=%d\n", + ret); + return ret; + } + } + /* Power up the PHY */ writel(0, base + HW_USBPHY_PWD); @@ -386,21 +468,10 @@ static void __mxs_phy_disconnect_line(struct mxs_phy *mxs_phy, bool disconnect) usleep_range(500, 1000); } -static bool mxs_phy_is_otg_host(struct mxs_phy *mxs_phy) -{ - void __iomem *base = mxs_phy->phy.io_priv; - u32 phyctrl = readl(base + HW_USBPHY_CTRL); - - if (IS_ENABLED(CONFIG_USB_OTG) && - !(phyctrl & BM_USBPHY_CTRL_OTG_ID_VALUE)) - return true; - - return false; -} - static void mxs_phy_disconnect_line(struct mxs_phy *mxs_phy, bool on) { bool vbus_is_on = false; + enum usb_phy_events last_event = mxs_phy->phy.last_event; /* If the SoCs don't need to disconnect line without vbus, quit */ if (!(mxs_phy->data->flags & MXS_PHY_DISCONNECT_LINE_WITHOUT_VBUS)) @@ -412,7 +483,8 @@ static void mxs_phy_disconnect_line(struct mxs_phy *mxs_phy, bool on) vbus_is_on = mxs_phy_get_vbus_status(mxs_phy); - if (on && !vbus_is_on && !mxs_phy_is_otg_host(mxs_phy)) + if (on && ((!vbus_is_on && mxs_phy->mode != CUR_USB_MODE_HOST) || + (last_event == USB_EVENT_VBUS))) __mxs_phy_disconnect_line(mxs_phy, true); else __mxs_phy_disconnect_line(mxs_phy, false); @@ -453,6 +525,9 @@ static void mxs_phy_shutdown(struct usb_phy *phy) if (is_imx7ulp_phy(mxs_phy)) mxs_phy_pll_enable(phy->io_priv, false); + if (mxs_phy->phy_3p0) + regulator_disable(mxs_phy->phy_3p0); + clk_disable_unprepare(mxs_phy->clk); } @@ -506,14 +581,49 @@ static int mxs_phy_suspend(struct usb_phy *x, int suspend) } else { writel(0xffffffff, x->io_priv + HW_USBPHY_PWD); } + + /* + * USB2 PLL use ring VCO, when the PLL power up, the ring + * VCO’s supply also ramp up. There is a possibility that + * the ring VCO start oscillation at multi nodes in this + * phase, especially for VCO which has many stages, then + * the multiwave will be kept until PLL power down. the bit + * hold_ring_off can force the VCO in one determined state + * to avoid the multiwave issue when VCO supply start ramp + * up. + */ + if (mxs_phy->port_id == 1 && mxs_phy->regmap_anatop) + regmap_write(mxs_phy->regmap_anatop, + ANADIG_PLL_USB2_SET, + BM_ANADIG_PLL_USB2_HOLD_RING_OFF); + writel(BM_USBPHY_CTRL_CLKGATE, x->io_priv + HW_USBPHY_CTRL_SET); - clk_disable_unprepare(mxs_phy->clk); + if (!(mxs_phy->port_id == 1 && + mxs_phy->hardware_control_phy2_clk)) + clk_disable_unprepare(mxs_phy->clk); } else { mxs_phy_clock_switch_delay(); - ret = clk_prepare_enable(mxs_phy->clk); - if (ret) - return ret; + if (!(mxs_phy->port_id == 1 && + mxs_phy->hardware_control_phy2_clk)) { + ret = clk_prepare_enable(mxs_phy->clk); + if (ret) + return ret; + } + + /* + * Per IC design's requirement, hold_ring_off bit can be + * cleared 25us after PLL power up and 25us before any USB + * TX/RX. + */ + if (mxs_phy->port_id == 1 && mxs_phy->regmap_anatop) { + udelay(25); + regmap_write(mxs_phy->regmap_anatop, + ANADIG_PLL_USB2_CLR, + BM_ANADIG_PLL_USB2_HOLD_RING_OFF); + udelay(25); + } + writel(BM_USBPHY_CTRL_CLKGATE, x->io_priv + HW_USBPHY_CTRL_CLR); writel(0, x->io_priv + HW_USBPHY_PWD); @@ -708,6 +818,169 @@ static enum usb_charger_type mxs_phy_charger_detect(struct usb_phy *phy) return chgr_type; } +static int mxs_phy_on_suspend(struct usb_phy *phy, + enum usb_device_speed speed) +{ + struct mxs_phy *mxs_phy = to_mxs_phy(phy); + + dev_dbg(phy->dev, "%s device has suspended\n", + (speed == USB_SPEED_HIGH) ? "HS" : "FS/LS"); + + /* delay 4ms to wait bus entering idle */ + usleep_range(4000, 5000); + + if (mxs_phy->data->flags & MXS_PHY_ABNORMAL_IN_SUSPEND) { + writel_relaxed(0xffffffff, phy->io_priv + HW_USBPHY_PWD); + writel_relaxed(0, phy->io_priv + HW_USBPHY_PWD); + } + + if (speed == USB_SPEED_HIGH) + writel_relaxed(BM_USBPHY_CTRL_ENHOSTDISCONDETECT, + phy->io_priv + HW_USBPHY_CTRL_CLR); + + return 0; +} + +/* + * The resume signal must be finished here. + */ +static int mxs_phy_on_resume(struct usb_phy *phy, + enum usb_device_speed speed) +{ + dev_dbg(phy->dev, "%s device has resumed\n", + (speed == USB_SPEED_HIGH) ? "HS" : "FS/LS"); + + if (speed == USB_SPEED_HIGH) { + /* Make sure the device has switched to High-Speed mode */ + udelay(500); + writel_relaxed(BM_USBPHY_CTRL_ENHOSTDISCONDETECT, + phy->io_priv + HW_USBPHY_CTRL_SET); + } + + return 0; +} + +/* + * Set the usb current role for phy. + */ +static int mxs_phy_set_mode(struct usb_phy *phy, + enum usb_current_mode mode) +{ + struct mxs_phy *mxs_phy = to_mxs_phy(phy); + + mxs_phy->mode = mode; + + return 0; +} + +static int mxs_phy_dcd_start(struct mxs_phy *mxs_phy) +{ + void __iomem *base = mxs_phy->phy.io_priv; + u32 value; + + value = readl(base + DCD_CONTROL); + writel(value | DCD_CONTROL_SR, base + DCD_CONTROL); + + if (!mxs_phy->clk_rate) + return -EINVAL; + + value = readl(base + DCD_CONTROL); + writel(((mxs_phy->clk_rate / 1000000) << 2) | DCD_CLOCK_MHZ, + base + DCD_CLOCK); + + value = readl(base + DCD_CONTROL); + value &= ~DCD_CONTROL_IE; + writel(value | DCD_CONTROL_BC12, base + DCD_CONTROL); + + value = readl(base + DCD_CONTROL); + writel(value | DCD_CONTROL_START, base + DCD_CONTROL); + + return 0; +} + +#define DCD_CHARGING_DURTION 1000 /* One second according to BC 1.2 */ +static enum usb_charger_type mxs_phy_dcd_flow(struct usb_phy *phy) +{ + struct mxs_phy *mxs_phy = to_mxs_phy(phy); + void __iomem *base = mxs_phy->phy.io_priv; + u32 value; + int i = 0; + enum usb_charger_type chgr_type; + + if (mxs_phy_dcd_start(mxs_phy)) + return UNKNOWN_TYPE; + + while (i++ <= (DCD_CHARGING_DURTION / 50)) { + value = readl(base + DCD_CONTROL); + if (value & DCD_CONTROL_IF) { + value = readl(base + DCD_STATUS); + if (value & DCD_STATUS_ACTIVE) { + dev_err(phy->dev, "still detecting\n"); + chgr_type = UNKNOWN_TYPE; + break; + } + + if (value & DCD_STATUS_TO) { + dev_err(phy->dev, "detect timeout\n"); + chgr_type = UNKNOWN_TYPE; + break; + } + + if (value & DCD_STATUS_ERR) { + dev_err(phy->dev, "detect error\n"); + chgr_type = UNKNOWN_TYPE; + break; + } + + if ((value & DCD_STATUS_SEQ_STAT) <= DCD_CHG_DPIN) { + dev_err(phy->dev, "error occurs\n"); + chgr_type = UNKNOWN_TYPE; + break; + } + + /* SDP */ + if (((value & DCD_STATUS_SEQ_STAT) == DCD_CHG_PORT) && + ((value & DCD_STATUS_SEQ_RES) + == DCD_SDP_PORT)) { + dev_dbg(phy->dev, "SDP\n"); + chgr_type = SDP_TYPE; + break; + } + + if ((value & DCD_STATUS_SEQ_STAT) == DCD_CHG_DET) { + if ((value & DCD_STATUS_SEQ_RES) == + DCD_CDP_PORT) { + dev_dbg(phy->dev, "CDP\n"); + chgr_type = CDP_TYPE; + break; + } + + if ((value & DCD_STATUS_SEQ_RES) == + DCD_DCP_PORT) { + dev_dbg(phy->dev, "DCP\n"); + chgr_type = DCP_TYPE; + break; + } + } + dev_err(phy->dev, "unknown error occurs\n"); + chgr_type = UNKNOWN_TYPE; + break; + } + msleep(50); + } + + if (i > 20) { + dev_err(phy->dev, "charger detecting timeout\n"); + chgr_type = UNKNOWN_TYPE; + } + + /* disable dcd module */ + readl(base + DCD_STATUS); + writel(DCD_CONTROL_IACK, base + DCD_CONTROL); + writel(DCD_CONTROL_SR, base + DCD_CONTROL); + return chgr_type; +} + static int mxs_phy_probe(struct platform_device *pdev) { struct resource *res; @@ -739,6 +1012,7 @@ static int mxs_phy_probe(struct platform_device *pdev) if (!mxs_phy) return -ENOMEM; + mxs_phy->clk_rate = clk_get_rate(clk); /* Some SoCs don't have anatop registers */ if (of_get_property(np, "fsl,anatop", NULL)) { mxs_phy->regmap_anatop = syscon_regmap_lookup_by_phandle @@ -750,6 +1024,17 @@ static int mxs_phy_probe(struct platform_device *pdev) } } + /* Currently, only imx7ulp has SIM module */ + if (of_get_property(np, "nxp,sim", NULL)) { + mxs_phy->regmap_sim = syscon_regmap_lookup_by_phandle + (np, "nxp,sim"); + if (IS_ERR(mxs_phy->regmap_sim)) { + dev_dbg(&pdev->dev, + "failed to find regmap for sim\n"); + return PTR_ERR(mxs_phy->regmap_sim); + } + } + /* Precompute which bits of the TX register are to be updated, if any */ if (!of_property_read_u32(np, "fsl,tx-cal-45-dn-ohms", &val) && val >= MXS_PHY_TX_CAL45_MIN && val <= MXS_PHY_TX_CAL45_MAX) { @@ -784,6 +1069,8 @@ static int mxs_phy_probe(struct platform_device *pdev) ret = of_alias_get_id(np, "usbphy"); if (ret < 0) dev_dbg(&pdev->dev, "failed to get alias id, errno %d\n", ret); + mxs_phy->clk = clk; + mxs_phy->data = of_id->data; mxs_phy->port_id = ret; mxs_phy->phy.io_priv = base; @@ -796,10 +1083,33 @@ static int mxs_phy_probe(struct platform_device *pdev) mxs_phy->phy.notify_disconnect = mxs_phy_on_disconnect; mxs_phy->phy.type = USB_PHY_TYPE_USB2; mxs_phy->phy.set_wakeup = mxs_phy_set_wakeup; - mxs_phy->phy.charger_detect = mxs_phy_charger_detect; + if (mxs_phy->data->flags & MXS_PHY_HAS_DCD) + mxs_phy->phy.charger_detect = mxs_phy_dcd_flow; + else + mxs_phy->phy.charger_detect = mxs_phy_charger_detect; - mxs_phy->clk = clk; - mxs_phy->data = of_id->data; + mxs_phy->phy.set_mode = mxs_phy_set_mode; + if (mxs_phy->data->flags & MXS_PHY_SENDING_SOF_TOO_FAST) { + mxs_phy->phy.notify_suspend = mxs_phy_on_suspend; + mxs_phy->phy.notify_resume = mxs_phy_on_resume; + } + + mxs_phy->phy_3p0 = devm_regulator_get(&pdev->dev, "phy-3p0"); + if (PTR_ERR(mxs_phy->phy_3p0) == -EPROBE_DEFER) { + return -EPROBE_DEFER; + } else if (PTR_ERR(mxs_phy->phy_3p0) == -ENODEV) { + /* not exist */ + mxs_phy->phy_3p0 = NULL; + } else if (IS_ERR(mxs_phy->phy_3p0)) { + dev_err(&pdev->dev, "Getting regulator error: %ld\n", + PTR_ERR(mxs_phy->phy_3p0)); + return PTR_ERR(mxs_phy->phy_3p0); + } + if (mxs_phy->phy_3p0) + regulator_set_voltage(mxs_phy->phy_3p0, 3200000, 3200000); + + if (mxs_phy->data->flags & MXS_PHY_HARDWARE_CONTROL_PHY2_CLK) + mxs_phy->hardware_control_phy2_clk = true; platform_set_drvdata(pdev, mxs_phy); @@ -818,28 +1128,58 @@ static int mxs_phy_remove(struct platform_device *pdev) } #ifdef CONFIG_PM_SLEEP +static void mxs_phy_wakeup_enable(struct mxs_phy *mxs_phy, bool on) +{ + u32 mask = USB_PHY_VLLS_WAKEUP_EN; + + /* If the SoCs don't have SIM, quit */ + if (!mxs_phy->regmap_sim) + return; + + if (on) { + regmap_update_bits(mxs_phy->regmap_sim, SIM_GPR1, mask, mask); + udelay(500); + } else { + regmap_update_bits(mxs_phy->regmap_sim, SIM_GPR1, mask, 0); + } +} + static void mxs_phy_enable_ldo_in_suspend(struct mxs_phy *mxs_phy, bool on) { - unsigned int reg = on ? ANADIG_ANA_MISC0_SET : ANADIG_ANA_MISC0_CLR; + unsigned int reg; + u32 value; /* If the SoCs don't have anatop, quit */ if (!mxs_phy->regmap_anatop) return; - if (is_imx6q_phy(mxs_phy)) + if (is_imx6q_phy(mxs_phy)) { + reg = on ? ANADIG_ANA_MISC0_SET : ANADIG_ANA_MISC0_CLR; regmap_write(mxs_phy->regmap_anatop, reg, BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG); - else if (is_imx6sl_phy(mxs_phy)) + } else if (is_imx6sl_phy(mxs_phy)) { + reg = on ? ANADIG_ANA_MISC0_SET : ANADIG_ANA_MISC0_CLR; regmap_write(mxs_phy->regmap_anatop, reg, BM_ANADIG_ANA_MISC0_STOP_MODE_CONFIG_SL); + } else if (is_imx6ul_phy(mxs_phy)) { + reg = on ? ANADIG_REG_1P1_SET : ANADIG_REG_1P1_CLR; + value = BM_ANADIG_REG_1P1_ENABLE_WEAK_LINREG | + BM_ANADIG_REG_1P1_TRACK_VDD_SOC_CAP; + if (mxs_phy_get_vbus_status(mxs_phy) && on) + regmap_write(mxs_phy->regmap_anatop, reg, value); + else if (!on) + regmap_write(mxs_phy->regmap_anatop, reg, value); + } } static int mxs_phy_system_suspend(struct device *dev) { struct mxs_phy *mxs_phy = dev_get_drvdata(dev); - if (device_may_wakeup(dev)) + if (device_may_wakeup(dev)) { mxs_phy_enable_ldo_in_suspend(mxs_phy, true); + mxs_phy_wakeup_enable(mxs_phy, true); + } return 0; } @@ -848,8 +1188,10 @@ static int mxs_phy_system_resume(struct device *dev) { struct mxs_phy *mxs_phy = dev_get_drvdata(dev); - if (device_may_wakeup(dev)) + if (device_may_wakeup(dev)) { mxs_phy_enable_ldo_in_suspend(mxs_phy, false); + mxs_phy_wakeup_enable(mxs_phy, false); + } return 0; } diff --git a/drivers/usb/phy/phy.c b/drivers/usb/phy/phy.c index 0277f62739a2..ad2554630889 100644 --- a/drivers/usb/phy/phy.c +++ b/drivers/usb/phy/phy.c @@ -34,6 +34,14 @@ struct phy_devm { struct notifier_block *nb; }; +static const char *const usb_chger_type[] = { + [UNKNOWN_TYPE] = "USB_CHARGER_UNKNOWN_TYPE", + [SDP_TYPE] = "USB_CHARGER_SDP_TYPE", + [CDP_TYPE] = "USB_CHARGER_CDP_TYPE", + [DCP_TYPE] = "USB_CHARGER_DCP_TYPE", + [ACA_TYPE] = "USB_CHARGER_ACA_TYPE", +}; + static struct usb_phy *__usb_find_phy(struct list_head *list, enum usb_phy_type type) { @@ -98,7 +106,8 @@ static void usb_phy_notify_charger_work(struct work_struct *work) { struct usb_phy *usb_phy = container_of(work, struct usb_phy, chg_work); char uchger_state[50] = { 0 }; - char *envp[] = { uchger_state, NULL }; + char uchger_type[50] = { 0 }; + char *envp[] = { uchger_state, uchger_type, NULL }; unsigned int min, max; switch (usb_phy->chg_state) { @@ -122,6 +131,8 @@ static void usb_phy_notify_charger_work(struct work_struct *work) return; } + snprintf(uchger_type, ARRAY_SIZE(uchger_type), + "USB_CHARGER_TYPE=%s", usb_chger_type[usb_phy->chg_type]); kobject_uevent_env(&usb_phy->dev->kobj, KOBJ_CHANGE, envp); } diff --git a/drivers/usb/typec/mux/Kconfig b/drivers/usb/typec/mux/Kconfig index 01ed0d5e10e8..bc7d3c78e556 100644 --- a/drivers/usb/typec/mux/Kconfig +++ b/drivers/usb/typec/mux/Kconfig @@ -9,4 +9,10 @@ config TYPEC_MUX_PI3USB30532 Say Y or M if your system has a Pericom PI3USB30532 Type-C cross switch / mux chip found on some devices with a Type-C port. +config TYPEC_SWITCH_GPIO + tristate "Simple Super Speed Active Switch via GPIO" + help + Say Y or M if your system has a typec super speed channel + switch via a simple GPIO control. + endmenu diff --git a/drivers/usb/typec/mux/Makefile b/drivers/usb/typec/mux/Makefile index 1332e469b8a0..e29377c5ff9a 100644 --- a/drivers/usb/typec/mux/Makefile +++ b/drivers/usb/typec/mux/Makefile @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0 obj-$(CONFIG_TYPEC_MUX_PI3USB30532) += pi3usb30532.o +obj-$(CONFIG_TYPEC_SWITCH_GPIO) += gpio-switch.o diff --git a/drivers/usb/typec/mux/gpio-switch.c b/drivers/usb/typec/mux/gpio-switch.c new file mode 100644 index 000000000000..ef4805cdc575 --- /dev/null +++ b/drivers/usb/typec/mux/gpio-switch.c @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0 +/** + * gpio-switch.c - typec switch via a simple GPIO control. + * + * Copyright 2019 NXP + * Author: Jun Li <jun.li@nxp.com> + * + */ + +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/usb/typec_mux.h> + +struct gpio_typec_switch { + struct typec_switch *sw; + struct mutex lock; + struct gpio_desc *ss_sel; + struct gpio_desc *ss_reset; +}; + +static int switch_gpio_set(struct typec_switch *sw, + enum typec_orientation orientation) +{ + struct gpio_typec_switch *gpio_sw = typec_switch_get_drvdata(sw); + + mutex_lock(&gpio_sw->lock); + + switch (orientation) { + case TYPEC_ORIENTATION_NORMAL: + gpiod_set_value_cansleep(gpio_sw->ss_sel, 1); + break; + case TYPEC_ORIENTATION_REVERSE: + gpiod_set_value_cansleep(gpio_sw->ss_sel, 0); + break; + case TYPEC_ORIENTATION_NONE: + break; + } + + mutex_unlock(&gpio_sw->lock); + + return 0; +} + +static int typec_switch_gpio_probe(struct platform_device *pdev) +{ + struct gpio_typec_switch *gpio_sw; + struct device *dev = &pdev->dev; + struct typec_switch_desc sw_desc; + + gpio_sw = devm_kzalloc(dev, sizeof(*gpio_sw), GFP_KERNEL); + if (!gpio_sw) + return -ENOMEM; + + platform_set_drvdata(pdev, gpio_sw); + + sw_desc.drvdata = gpio_sw; + sw_desc.fwnode = dev->fwnode; + sw_desc.set = switch_gpio_set; + mutex_init(&gpio_sw->lock); + + /* Get the super speed mux reset GPIO, it's optional */ + gpio_sw->ss_reset = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(gpio_sw->ss_reset)) + return PTR_ERR(gpio_sw->ss_reset); + + if (gpio_sw->ss_reset) + usleep_range(700, 1000); + + /* Get the super speed active channel selection GPIO */ + gpio_sw->ss_sel = devm_gpiod_get(dev, "switch", GPIOD_OUT_LOW); + if (IS_ERR(gpio_sw->ss_sel)) + return PTR_ERR(gpio_sw->ss_sel); + + gpio_sw->sw = typec_switch_register(dev, &sw_desc); + if (IS_ERR(gpio_sw->sw)) { + dev_err(dev, "Error registering typec switch: %ld\n", PTR_ERR(gpio_sw->sw)); + return PTR_ERR(gpio_sw->sw); + } + + return 0; +} + +static int typec_switch_gpio_remove(struct platform_device *pdev) +{ + struct gpio_typec_switch *gpio_sw = platform_get_drvdata(pdev); + + typec_switch_unregister(gpio_sw->sw); + + return 0; +} + +static const struct of_device_id of_typec_switch_gpio_match[] = { + { .compatible = "nxp,ptn36043" }, + { .compatible = "nxp,cbtl04gp" }, + { /* Sentinel */ } +}; +MODULE_DEVICE_TABLE(of, of_typec_switch_gpio_match); + +static struct platform_driver typec_switch_gpio_driver = { + .probe = typec_switch_gpio_probe, + .remove = typec_switch_gpio_remove, + .driver = { + .name = "typec-switch-gpio", + .of_match_table = of_typec_switch_gpio_match, + }, +}; + +module_platform_driver(typec_switch_gpio_driver); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("TypeC Super Speed Switch GPIO driver"); +MODULE_AUTHOR("Jun Li <jun.li@nxp.com>"); diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c index c1f7073a56de..4b2f380ca640 100644 --- a/drivers/usb/typec/tcpm/tcpci.c +++ b/drivers/usb/typec/tcpm/tcpci.c @@ -10,6 +10,7 @@ #include <linux/module.h> #include <linux/i2c.h> #include <linux/interrupt.h> +#include <linux/irq.h> #include <linux/property.h> #include <linux/regmap.h> #include <linux/usb/pd.h> @@ -108,9 +109,6 @@ static int tcpci_start_toggling(struct tcpc_dev *tcpc, struct tcpci *tcpci = tcpc_to_tcpci(tcpc); unsigned int reg = TCPC_ROLE_CTRL_DRP; - if (port_type != TYPEC_PORT_DRP) - return -EOPNOTSUPP; - /* Handle vendor drp toggling */ if (tcpci->data->start_drp_toggling) { ret = tcpci->data->start_drp_toggling(tcpci, tcpci->data, cc); @@ -566,6 +564,7 @@ static int tcpci_probe(struct i2c_client *client, if (IS_ERR(chip->tcpci)) return PTR_ERR(chip->tcpci); + irq_set_status_flags(client->irq, IRQ_DISABLE_UNLAZY); err = devm_request_threaded_irq(&client->dev, client->irq, NULL, _tcpci_irq, IRQF_ONESHOT | IRQF_TRIGGER_LOW, @@ -575,6 +574,8 @@ static int tcpci_probe(struct i2c_client *client, return err; } + device_set_wakeup_capable(chip->tcpci->dev, true); + return 0; } @@ -583,10 +584,40 @@ static int tcpci_remove(struct i2c_client *client) struct tcpci_chip *chip = i2c_get_clientdata(client); tcpci_unregister_port(chip->tcpci); + irq_clear_status_flags(client->irq, IRQ_DISABLE_UNLAZY); + + return 0; +} + +static int __maybe_unused tcpci_suspend(struct device *dev) +{ + struct i2c_client *i2c = to_i2c_client(dev); + + if (device_may_wakeup(dev)) + enable_irq_wake(i2c->irq); + else + disable_irq(i2c->irq); return 0; } + +static int __maybe_unused tcpci_resume(struct device *dev) +{ + struct i2c_client *i2c = to_i2c_client(dev); + + if (device_may_wakeup(dev)) + disable_irq_wake(i2c->irq); + else + enable_irq(i2c->irq); + + return 0; +} + +static const struct dev_pm_ops tcpci_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(tcpci_suspend, tcpci_resume) +}; + static const struct i2c_device_id tcpci_id[] = { { "tcpci", 0 }, { } @@ -604,6 +635,7 @@ MODULE_DEVICE_TABLE(of, tcpci_of_match); static struct i2c_driver tcpci_i2c_driver = { .driver = { .name = "tcpci", + .pm = &tcpci_pm_ops, .of_match_table = of_match_ptr(tcpci_of_match), }, .probe = tcpci_probe, diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index 5f61d9977a15..ea68559ce60e 100644 --- a/drivers/usb/typec/tcpm/tcpm.c +++ b/drivers/usb/typec/tcpm/tcpm.c @@ -29,6 +29,8 @@ #include <linux/usb/tcpm.h> #include <linux/usb/typec_altmode.h> #include <linux/workqueue.h> +#include <linux/extcon.h> +#include <linux/extcon-provider.h> #define FOREACH_STATE(S) \ S(INVALID_STATE), \ @@ -139,6 +141,12 @@ static const char * const tcpm_states[] = { FOREACH_STATE(GENERATE_STRING) }; +static const unsigned int tcpm_extcon_cable[] = { + EXTCON_USB_HOST, + EXTCON_USB, + EXTCON_NONE, +}; + enum vdm_states { VDM_STATE_ERR_BUSY = -3, VDM_STATE_ERR_SEND = -2, @@ -193,6 +201,7 @@ struct pd_pps_data { struct tcpm_port { struct device *dev; + struct extcon_dev *edev; struct mutex lock; /* tcpm state machine lock */ struct workqueue_struct *wq; @@ -670,6 +679,20 @@ static int tcpm_mux_set(struct tcpm_port *port, int state, ret = usb_role_switch_set_role(port->role_sw, usb_role); if (ret) return ret; + } else if (port->edev) { + if (usb_role == USB_ROLE_NONE) { + extcon_set_state_sync(port->edev, EXTCON_USB_HOST, + false); + extcon_set_state_sync(port->edev, EXTCON_USB, false); + } else if (usb_role == USB_ROLE_DEVICE) { + extcon_set_state_sync(port->edev, EXTCON_USB_HOST, + false); + extcon_set_state_sync(port->edev, EXTCON_USB, true); + } else { + extcon_set_state_sync(port->edev, EXTCON_USB, false); + extcon_set_state_sync(port->edev, EXTCON_USB_HOST, + true); + } } return typec_set_mode(port->typec_port, state); @@ -2623,6 +2646,8 @@ static int tcpm_src_attach(struct tcpm_port *port) if (port->attached) return 0; + tcpm_set_cc(port, tcpm_rp_cc(port)); + ret = tcpm_set_polarity(port, polarity); if (ret < 0) return ret; @@ -2744,6 +2769,8 @@ static int tcpm_snk_attach(struct tcpm_port *port) if (port->attached) return 0; + tcpm_set_cc(port, TYPEC_CC_RD); + ret = tcpm_set_polarity(port, port->cc2 != TYPEC_CC_OPEN ? TYPEC_POLARITY_CC2 : TYPEC_POLARITY_CC1); if (ret < 0) @@ -3146,7 +3173,11 @@ static void run_state_machine(struct tcpm_port *port) ret = tcpm_snk_attach(port); if (ret < 0) tcpm_set_state(port, SNK_UNATTACHED, 0); - else + else if (port->port_type == TYPEC_PORT_SRC && + port->typec_caps.data == TYPEC_PORT_DRD) { + tcpm_typec_connect(port); + tcpm_log(port, "Keep at SNK_ATTACHED for USB data."); + } else tcpm_set_state(port, SNK_STARTUP, 0); break; case SNK_STARTUP: @@ -4744,7 +4775,7 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) mutex_init(&port->lock); mutex_init(&port->swap_lock); - port->wq = create_singlethread_workqueue(dev_name(dev)); + port->wq = create_freezable_workqueue(dev_name(dev)); if (!port->wq) return ERR_PTR(-ENOMEM); INIT_DELAYED_WORK(&port->state_machine, tcpm_state_machine_work); @@ -4820,6 +4851,19 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) } } + port->edev = devm_extcon_dev_allocate(port->dev, tcpm_extcon_cable); + if (IS_ERR(port->edev)) { + dev_err(port->dev, "failed to allocate extcon dev.\n"); + err = -ENOMEM; + goto out_role_sw_put; + } + + err = devm_extcon_dev_register(port->dev, port->edev); + if (err) { + dev_err(port->dev, "failed to register extcon dev.\n"); + goto out_role_sw_put; + } + mutex_lock(&port->lock); tcpm_init(port); mutex_unlock(&port->lock); @@ -4840,6 +4884,9 @@ void tcpm_unregister_port(struct tcpm_port *port) { int i; + cancel_delayed_work_sync(&port->state_machine); + cancel_delayed_work_sync(&port->vdm_state_machine); + tcpm_reset_port(port); for (i = 0; i < ARRAY_SIZE(port->port_altmode); i++) typec_unregister_altmode(port->port_altmode[i]); diff --git a/include/linux/usb/chipidea.h b/include/linux/usb/chipidea.h index edd89b7c8f18..17e9b62f4660 100644 --- a/include/linux/usb/chipidea.h +++ b/include/linux/usb/chipidea.h @@ -62,11 +62,15 @@ struct ci_hdrc_platform_data { #define CI_HDRC_REQUIRES_ALIGNED_DMA BIT(13) #define CI_HDRC_IMX_IS_HSIC BIT(14) #define CI_HDRC_PMQOS BIT(15) +/* PHY enter low power mode when bus suspend */ +#define CI_HDRC_HOST_SUSP_PHY_LPM BIT(16) enum usb_dr_mode dr_mode; #define CI_HDRC_CONTROLLER_RESET_EVENT 0 #define CI_HDRC_CONTROLLER_STOPPED_EVENT 1 #define CI_HDRC_IMX_HSIC_ACTIVE_EVENT 2 #define CI_HDRC_IMX_HSIC_SUSPEND_EVENT 3 +#define CI_HDRC_CONTROLLER_VBUS_EVENT 4 +#define CI_HDRC_NOTIFY_RET_DEFER_EVENT 5 int (*notify_event) (struct ci_hdrc *ci, unsigned event); struct regulator *reg_vbus; struct usb_otg_caps ci_otg_caps; @@ -99,4 +103,6 @@ struct platform_device *ci_hdrc_add_device(struct device *dev, /* Remove ci hdrc device */ void ci_hdrc_remove_device(struct platform_device *pdev); +/* Get current available role */ +enum usb_dr_mode ci_hdrc_query_available_role(struct platform_device *pdev); #endif diff --git a/include/linux/usb/hcd.h b/include/linux/usb/hcd.h index 712b2a603645..5e2695d3852c 100644 --- a/include/linux/usb/hcd.h +++ b/include/linux/usb/hcd.h @@ -409,7 +409,10 @@ struct hc_driver { int (*find_raw_port_number)(struct usb_hcd *, int); /* Call for power on/off the port if necessary */ int (*port_power)(struct usb_hcd *hcd, int portnum, bool enable); - + /* Call for SINGLE_STEP_SET_FEATURE Test for USB2 EH certification */ +#define EHSET_TEST_SINGLE_STEP_SET_FEATURE 0x06 + int (*submit_single_step_set_feature)(struct usb_hcd *, + struct urb *, int); }; static inline int hcd_giveback_urb_in_bh(struct usb_hcd *hcd) @@ -474,6 +477,14 @@ int usb_hcd_setup_local_mem(struct usb_hcd *hcd, phys_addr_t phys_addr, struct platform_device; extern void usb_hcd_platform_shutdown(struct platform_device *dev); +#ifdef CONFIG_USB_HCD_TEST_MODE +extern int ehset_single_step_set_feature(struct usb_hcd *hcd, int port); +#else +static inline int ehset_single_step_set_feature(struct usb_hcd *hcd, int port) +{ + return 0; +} +#endif /* CONFIG_USB_HCD_TEST_MODE */ #ifdef CONFIG_USB_PCI struct pci_dev; diff --git a/include/linux/usb/otg-fsm.h b/include/linux/usb/otg-fsm.h index e78eb577d0fa..1a0155ff051e 100644 --- a/include/linux/usb/otg-fsm.h +++ b/include/linux/usb/otg-fsm.h @@ -54,6 +54,7 @@ enum otg_fsm_timer { A_WAIT_ENUM, B_DATA_PLS, B_SSEND_SRP, + A_DP_END, NUM_OTG_FSM_TIMERS, }; diff --git a/include/linux/usb/phy.h b/include/linux/usb/phy.h index e4de6bc1f69b..086f95f7b424 100644 --- a/include/linux/usb/phy.h +++ b/include/linux/usb/phy.h @@ -63,6 +63,13 @@ enum usb_otg_state { OTG_STATE_A_VBUS_ERR, }; +/* The usb role of phy to be working with */ +enum usb_current_mode { + CUR_USB_MODE_NONE, + CUR_USB_MODE_HOST, + CUR_USB_MODE_DEVICE, +}; + struct usb_phy; struct usb_otg; @@ -155,6 +162,15 @@ struct usb_phy { * manually detect the charger type. */ enum usb_charger_type (*charger_detect)(struct usb_phy *x); + + int (*notify_suspend)(struct usb_phy *x, + enum usb_device_speed speed); + int (*notify_resume)(struct usb_phy *x, + enum usb_device_speed speed); + + int (*set_mode)(struct usb_phy *x, + enum usb_current_mode mode); + }; /* for board-specific init logic */ @@ -213,6 +229,15 @@ usb_phy_vbus_off(struct usb_phy *x) return x->set_vbus(x, false); } +static inline int +usb_phy_set_mode(struct usb_phy *x, enum usb_current_mode mode) +{ + if (!x || !x->set_mode) + return 0; + + return x->set_mode(x, mode); +} + /* for usb host and peripheral controller drivers */ #if IS_ENABLED(CONFIG_USB_PHY) extern struct usb_phy *usb_get_phy(enum usb_phy_type type); @@ -334,6 +359,24 @@ usb_phy_notify_disconnect(struct usb_phy *x, enum usb_device_speed speed) return 0; } +static inline int usb_phy_notify_suspend + (struct usb_phy *x, enum usb_device_speed speed) +{ + if (x && x->notify_suspend) + return x->notify_suspend(x, speed); + else + return 0; +} + +static inline int usb_phy_notify_resume + (struct usb_phy *x, enum usb_device_speed speed) +{ + if (x && x->notify_resume) + return x->notify_resume(x, speed); + else + return 0; +} + /* notifiers */ static inline int usb_register_notifier(struct usb_phy *x, struct notifier_block *nb) |