diff options
Diffstat (limited to 'drivers/usb')
66 files changed, 11497 insertions, 722 deletions
diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig index 0103f777b97a..c055561e84aa 100644 --- a/drivers/usb/Kconfig +++ b/drivers/usb/Kconfig @@ -113,6 +113,8 @@ source "drivers/usb/chipidea/Kconfig" source "drivers/usb/isp1760/Kconfig" +source "drivers/usb/cdns3/Kconfig" + comment "USB port drivers" if USB @@ -158,6 +160,8 @@ source "drivers/usb/phy/Kconfig" source "drivers/usb/gadget/Kconfig" +source "drivers/usb/typec/Kconfig" + config USB_LED_TRIG bool "USB LED Triggers" depends on LEDS_CLASS && LEDS_TRIGGERS diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile index dca78565eb55..cbdfe47f011c 100644 --- a/drivers/usb/Makefile +++ b/drivers/usb/Makefile @@ -10,6 +10,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_MON) += mon/ @@ -61,3 +62,5 @@ obj-$(CONFIG_USB_GADGET) += gadget/ obj-$(CONFIG_USB_COMMON) += common/ obj-$(CONFIG_USBIP_CORE) += usbip/ + +obj-$(CONFIG_TYPEC) += typec/ diff --git a/drivers/usb/cdns3/Kconfig b/drivers/usb/cdns3/Kconfig new file mode 100644 index 000000000000..165afdb99d15 --- /dev/null +++ b/drivers/usb/cdns3/Kconfig @@ -0,0 +1,26 @@ +config USB_CDNS3 + tristate "Cadence USB3 Dual-Role Controller" + 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. + + 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 + help + Say Y here to enable device controller functionality of the + cadence usb3 driver. + +config USB_CDNS3_HOST + bool "Cadence USB3 host controller" + depends on USB_XHCI_HCD + help + Say Y here to enable host controller functionality of the + cadence usb3 driver. +endif diff --git a/drivers/usb/cdns3/Makefile b/drivers/usb/cdns3/Makefile new file mode 100644 index 000000000000..7328cb9fcc89 --- /dev/null +++ b/drivers/usb/cdns3/Makefile @@ -0,0 +1,5 @@ +obj-$(CONFIG_USB_CDNS3) += cdns3.o + +cdns3-y := core.o +cdns3-$(CONFIG_USB_CDNS3_GADGET) += gadget.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..886632265e98 --- /dev/null +++ b/drivers/usb/cdns3/cdns3-nxp-reg-def.h @@ -0,0 +1,172 @@ +/** + * 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_D0 (1 << 0) +#endif /* __DRIVERS_USB_CDNS3_NXP_H */ diff --git a/drivers/usb/cdns3/core.c b/drivers/usb/cdns3/core.c new file mode 100644 index 000000000000..df68728f1de3 --- /dev/null +++ b/drivers/usb/cdns3/core.c @@ -0,0 +1,1014 @@ +/** + * core.c - Cadence USB3 DRD Controller Core file + * + * Copyright 2017 NXP + * + * Authors: Peter Chen <peter.chen@nxp.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/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 "cdns3-nxp-reg-def.h" +#include "core.h" +#include "host-export.h" +#include "gadget-export.h" + +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 cdns_set_role(struct cdns3 *cdns, enum cdns3_roles role) +{ + u32 value; + int timeout_us = 100000; + + if (role == CDNS3_ROLE_END) + 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); + } + + if (timeout_us <= 0) + dev_err(cdns->dev, "wait xhci_power_on_ready timeout\n"); + + 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 enum cdns3_roles cdns3_get_role(struct cdns3 *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; + } +} + +/** + * cdns3_core_init_role - initialize role of operation + * @cdns: Pointer to cdns3 structure + * + * Returns 0 on success otherwise negative errno + */ +static int cdns3_core_init_role(struct cdns3 *cdns) +{ + struct device *dev = cdns->dev; + enum usb_dr_mode dr_mode = usb_get_dr_mode(dev); + + 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) { + 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) { + if (cdns3_gadget_init(cdns)) + dev_info(dev, "doesn't support gadget\n"); + } + + if (!cdns->roles[CDNS3_ROLE_HOST] && !cdns->roles[CDNS3_ROLE_GADGET]) { + dev_err(dev, "no supported roles\n"); + return -ENODEV; + } + + return 0; +} + +/** + * cdns3_irq - interrupt handler for cdns3 core device + * + * @irq: irq number for cdns3 core device + * @data: structure of cdns3 + * + * Returns IRQ_HANDLED or IRQ_NONE + */ +static irqreturn_t cdns3_irq(int irq, void *data) +{ + 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; + } + + /* Handle device/host interrupt */ + if (cdns->role != CDNS3_ROLE_END) + ret = cdns3_role(cdns)->irq(cdns); + + return ret; +} + +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 int cdns3_prepare_enable_clks(struct device *dev) +{ + struct cdns3 *cdns = dev_get_drvdata(dev); + int i, j, ret = 0; + + 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; + } + } + + return ret; +err: + for (j = i; j > 0; j--) + clk_disable_unprepare(cdns->cdns3_clks[j - 1]); + + return ret; +} + +static void cdns3_disable_unprepare_clks(struct device *dev) +{ + struct cdns3 *cdns = dev_get_drvdata(dev); + int i; + + for (i = CDNS3_NUM_OF_CLKS - 1; i >= 0; i--) + clk_disable_unprepare(cdns->cdns3_clks[i]); +} + +static void cdns3_remove_roles(struct cdns3 *cdns) +{ + cdns3_gadget_remove(cdns); + cdns3_host_remove(cdns); +} + +static int cdns3_do_role_switch(struct cdns3 *cdns, enum cdns3_roles role) +{ + int ret = 0; + enum cdns3_roles current_role; + + dev_dbg(cdns->dev, "current role is %d, switch to %d\n", + cdns->role, role); + + if (cdns->role == role) + return 0; + + pm_runtime_get_sync(cdns->dev); + current_role = cdns->role; + 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; + } + + 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", + role, current_role); + cdns_set_role(cdns, current_role); + ret = cdns3_role_start(cdns, current_role); + } + + pm_runtime_put_sync(cdns->dev); + return ret; +} + +/** + * cdns3_role_switch - work queue handler for role switch + * + * @work: work queue item structure + * + * Handles below events: + * - Role switch for dual-role devices + * - CDNS3_ROLE_GADGET <--> CDNS3_ROLE_END for peripheral-only devices + */ +static void cdns3_role_switch(struct work_struct *work) +{ + 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; + } + + if (device) + cdns3_do_role_switch(cdns, CDNS3_ROLE_GADGET); + else + cdns3_do_role_switch(cdns, CDNS3_ROLE_END); +} + +static int cdns3_extcon_notifier(struct notifier_block *nb, unsigned long event, + void *ptr) +{ + struct cdns3 *cdns = container_of(nb, struct cdns3, extcon_nb); + + queue_work(system_freezable_wq, &cdns->role_switch_wq); + + return NOTIFY_DONE; +} + +static int cdns3_register_extcon(struct cdns3 *cdns) +{ + struct extcon_dev *extcon; + struct device *dev = cdns->dev; + int ret; + + 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); + + 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; + } + + 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; + } + + return 0; +} + +/** + * cdns3_probe - probe for cdns3 core device + * @pdev: Pointer to cdns3 core platform device + * + * Returns 0 on success otherwise negative errno + */ +static int cdns3_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct cdns3 *cdns; + void __iomem *regs; + int ret; + + cdns = devm_kzalloc(dev, sizeof(*cdns), GFP_KERNEL); + if (!cdns) + return -ENOMEM; + + cdns->dev = dev; + platform_set_drvdata(pdev, cdns); + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + dev_err(dev, "missing IRQ\n"); + return -ENODEV; + } + cdns->irq = res->start; + + /* + * 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; + + 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(pdev, IORESOURCE_MEM, 2); + regs = devm_ioremap_resource(dev, res); + if (IS_ERR(regs)) + return PTR_ERR(regs); + cdns->dev_regs = regs; + + 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; + + 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; + + ret = cdns3_prepare_enable_clks(dev); + if (ret) + return ret; + + 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 = usb_phy_init(cdns->usbphy); + if (ret) + goto err1; + + ret = cdns3_core_init_role(cdns); + if (ret) + goto err2; + + 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 = 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 = devm_request_irq(dev, cdns->irq, cdns3_irq, IRQF_SHARED, + dev_name(dev), cdns); + if (ret) + 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 + * power mode. + */ + pm_runtime_set_autosuspend_delay(dev, 20); + pm_runtime_mark_last_busy(dev); + pm_runtime_use_autosuspend(dev); + dev_dbg(dev, "Cadence USB3 core: probe succeed\n"); + + return 0; + +err4: + cdns3_role_stop(cdns); +err3: + cdns3_remove_roles(cdns); +err2: + usb_phy_shutdown(cdns->usbphy); +err1: + cdns3_disable_unprepare_clks(dev); + return ret; +} + +/** + * cdns3_remove - unbind our drd driver and clean up + * @pdev: Pointer to Linux platform device + * + * Returns 0 on success otherwise negative errno + */ +static int cdns3_remove(struct platform_device *pdev) +{ + struct cdns3 *cdns = platform_get_drvdata(pdev); + + pm_runtime_get_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + pm_runtime_put_noidle(&pdev->dev); + cdns3_remove_roles(cdns); + usb_phy_shutdown(cdns->usbphy); + cdns3_disable_unprepare_clks(&pdev->dev); + + return 0; +} + +#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 void 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; + + if (cdns->role != CDNS3_ROLE_HOST) + return; + + disable_irq(cdns->irq); + if (suspend) { + value = readl(otg_regs + OTGREFCLK); + value |= OTG_STB_CLK_SWITCH_EN; + writel(value, otg_regs + OTGREFCLK); + + value = readl(xhci_regs + XECP_PORT_CAP_REG); + value |= LPM_2_STB_SWITCH_EN; + writel(value, xhci_regs + XECP_PORT_CAP_REG); + if (cdns3_role(cdns)->suspend) + cdns3_role(cdns)->suspend(cdns, wakeup); + + /* + * SW should ensure LPM_2_STB_SWITCH_EN and RXDET_IN_P3_32KHZ + * are aligned before setting 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); + /* SW request low power when all usb ports allow to it ??? */ + value = readl(xhci_regs + XECP_PM_PMCSR); + value |= PS_D0; + 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_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"); + } + enable_irq(cdns->irq); +} + +#ifdef CONFIG_PM_SLEEP +static int cdns3_suspend(struct device *dev) +{ + struct cdns3 *cdns = dev_get_drvdata(dev); + bool wakeup = device_may_wakeup(dev); + u32 value; + + dev_dbg(dev, "at %s\n", __func__); + + if (pm_runtime_status_suspended(dev)) + pm_runtime_resume(dev); + + if (cdns->role == CDNS3_ROLE_HOST) + cdns3_enter_suspend(cdns, true, wakeup); + else if (cdns->role == CDNS3_ROLE_GADGET) { + /* When at device mode, always 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); + } + + if (wakeup) + enable_irq_wake(cdns->irq); + + usb_phy_set_suspend(cdns->usbphy, 1); + cdns3_disable_unprepare_clks(dev); + cdns->in_lpm = true; + + return 0; +} + +static int cdns3_resume(struct device *dev) +{ + struct cdns3 *cdns = dev_get_drvdata(dev); + int ret; + bool power_lost; + + 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; + + 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 { + 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 /* CONFIG_PM_SLEEP */ +static int cdns3_runtime_suspend(struct device *dev) +{ + struct cdns3 *cdns = dev_get_drvdata(dev); + + dev_dbg(dev, "at the begin of %s\n", __func__); + if (cdns->in_lpm) { + WARN_ON(1); + return 0; + } + + cdns3_enter_suspend(cdns, true, true); + usb_phy_set_suspend(cdns->usbphy, 1); + cdns3_disable_unprepare_clks(dev); + cdns->in_lpm = true; + + dev_dbg(dev, "at the end of %s\n", __func__); + + return 0; +} + +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); + 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) +}; + +static struct platform_driver cdns3_driver = { + .probe = cdns3_probe, + .remove = cdns3_remove, + .driver = { + .name = "cdns-usb3", + .of_match_table = of_match_ptr(of_cdns3_match), + .pm = &cdns3_pm_ops, + }, +}; + +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("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 new file mode 100644 index 000000000000..738dbfa836cf --- /dev/null +++ b/drivers/usb/cdns3/core.h @@ -0,0 +1,131 @@ +/** + * core.h - Cadence USB3 DRD Controller Core header file + * + * Copyright 2017 NXP + * + * Authors: Peter Chen <peter.chen@nxp.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/>. + */ + +#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 + * @start: start this role + * @stop: stop this role + * @suspend: suspend callback for this role + * @resume: resume callback for this role + * @irq: irq handler for this role + * @name: role name string (host/gadget) + */ +struct cdns3_role_driver { + 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 *); + const char *name; +}; + +#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 + * @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 + * @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 + * @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 + */ +struct cdns3 { + struct device *dev; + void __iomem *xhci_regs; + struct resource *xhci_res; + struct usbss_dev_register_block_type __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 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; +}; + +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]; +} + +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; + + mutex_lock(&cdns->mutex); + cdns->roles[role]->stop(cdns); + cdns->role = CDNS3_ROLE_END; + mutex_unlock(&cdns->mutex); +} + +#endif /* __DRIVERS_USB_CDNS3_CORE_H */ 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/gadget-export.h b/drivers/usb/cdns3/gadget-export.h new file mode 100644 index 000000000000..e085ed38ea05 --- /dev/null +++ b/drivers/usb/cdns3/gadget-export.h @@ -0,0 +1,36 @@ +/* + * gadget-export.h - Gadget 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 __CDNS3_GADGET_EXPORT_H +#define __CDNS3_GADGET_EXPORT_H + +#ifdef CONFIG_USB_CDNS3_GADGET + +int cdns3_gadget_init(struct cdns3 *cdns); +void cdns3_gadget_remove(struct cdns3 *cdns); +#else + +static inline int cdns3_gadget_init(struct cdns3 *cdns) +{ + return -ENXIO; +} + +static inline void cdns3_gadget_remove(struct cdns3 *cdns) +{ + +} + +#endif + +#endif /* __CDNS3_GADGET_EXPORT_H */ diff --git a/drivers/usb/cdns3/gadget.c b/drivers/usb/cdns3/gadget.c new file mode 100644 index 000000000000..b3d81aabe9c3 --- /dev/null +++ b/drivers/usb/cdns3/gadget.c @@ -0,0 +1,2526 @@ +/** + * gadget.c - Cadence USB3 Device Core file + * + * Copyright (C) 2016 Cadence Design Systems - http://www.cadence.com + * Copyright 2017 NXP + * + * Authors: Pawel Jez <pjez@cadence.com>, + * Konrad Kociolek <konrad@cadence.com> + * 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 + */ + +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/dma-mapping.h> +#include <linux/pm_runtime.h> +#include <linux/usb/composite.h> +#include <linux/of_platform.h> +#include <linux/usb/gadget.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/byteorder/generic.h> +#include <linux/ctype.h> + +#include "core.h" +#include "gadget-export.h" +#include "gadget.h" +#include "io.h" + +/*-------------------------------------------------------------------------*/ +/* Function declarations */ + +static void select_ep(struct usb_ss_dev *usb_ss, u32 ep); +static int usb_ss_allocate_trb_pool(struct usb_ss_endpoint *usb_ss_ep); +static void cdns_ep_stall_flush(struct usb_ss_endpoint *usb_ss_ep); +static void cdns_ep0_config(struct usb_ss_dev *usb_ss); +static void cdns_gadget_unconfig(struct usb_ss_dev *usb_ss); +static void cdns_ep0_run_transfer(struct usb_ss_dev *usb_ss, + dma_addr_t dma_addr, unsigned int length, int erdy); +static int cdns_ep_run_transfer(struct usb_ss_endpoint *usb_ss_ep); +static int cdns_get_setup_ret(struct usb_ss_dev *usb_ss, + struct usb_ctrlrequest *ctrl_req); +static int cdns_req_ep0_set_address(struct usb_ss_dev *usb_ss, + struct usb_ctrlrequest *ctrl_req); +static int cdns_req_ep0_get_status(struct usb_ss_dev *usb_ss, + struct usb_ctrlrequest *ctrl_req); +static int cdns_req_ep0_handle_feature(struct usb_ss_dev *usb_ss, + struct usb_ctrlrequest *ctrl_req, int set); +static int cdns_req_ep0_set_sel(struct usb_ss_dev *usb_ss, + struct usb_ctrlrequest *ctrl_req); +static int cdns_req_ep0_set_isoch_delay(struct usb_ss_dev *usb_ss, + struct usb_ctrlrequest *ctrl_req); +static int cdns_req_ep0_set_configuration(struct usb_ss_dev *usb_ss, + struct usb_ctrlrequest *ctrl_req); +static int cdns_ep0_standard_request(struct usb_ss_dev *usb_ss, + struct usb_ctrlrequest *ctrl_req); +static void cdns_ep0_setup_phase(struct usb_ss_dev *usb_ss); +static int cdns_check_ep_interrupt_proceed(struct usb_ss_endpoint *usb_ss_ep); +static void cdns_check_ep0_interrupt_proceed(struct usb_ss_dev *usb_ss, + int dir); +static void cdns_check_usb_interrupt_proceed(struct usb_ss_dev *usb_ss, + u32 usb_ists); +static int usb_ss_gadget_ep0_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc); +static int usb_ss_gadget_ep0_disable(struct usb_ep *ep); +static int usb_ss_gadget_ep0_set_halt(struct usb_ep *ep, int value); +static int usb_ss_gadget_ep0_queue(struct usb_ep *ep, + struct usb_request *request, gfp_t gfp_flags); +static int usb_ss_gadget_ep_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc); +static int usb_ss_gadget_ep_disable(struct usb_ep *ep); +static struct usb_request *usb_ss_gadget_ep_alloc_request(struct usb_ep *ep, + gfp_t gfp_flags); +static void usb_ss_gadget_ep_free_request(struct usb_ep *ep, + struct usb_request *request); +static int usb_ss_gadget_ep_queue(struct usb_ep *ep, + struct usb_request *request, gfp_t gfp_flags); +static int usb_ss_gadget_ep_dequeue(struct usb_ep *ep, + struct usb_request *request); +static int usb_ss_gadget_ep_set_halt(struct usb_ep *ep, int value); +static int usb_ss_gadget_ep_set_wedge(struct usb_ep *ep); +static int usb_ss_gadget_get_frame(struct usb_gadget *gadget); +static int usb_ss_gadget_wakeup(struct usb_gadget *gadget); +static int usb_ss_gadget_set_selfpowered(struct usb_gadget *gadget, + int is_selfpowered); +static int usb_ss_gadget_pullup(struct usb_gadget *gadget, int is_on); +static int usb_ss_gadget_udc_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver); +static int usb_ss_gadget_udc_stop(struct usb_gadget *gadget); +static int usb_ss_init_ep(struct usb_ss_dev *usb_ss); +static int usb_ss_init_ep0(struct usb_ss_dev *usb_ss); +static void __cdns3_gadget_start(struct usb_ss_dev *usb_ss); +static void cdns_prepare_setup_packet(struct usb_ss_dev *usb_ss); +static void cdns_ep_config(struct usb_ss_endpoint *usb_ss_ep); +static void cdns_enable_l1(struct usb_ss_dev *usb_ss, int enable); +static void __pending_setup_status_handler(struct usb_ss_dev *usb_ss); + +static struct usb_endpoint_descriptor cdns3_gadget_ep0_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_CONTROL, +}; + +static u32 gadget_readl(struct usb_ss_dev *usb_ss, uint32_t __iomem *reg) +{ + return cdns_readl(reg); +} + +static void gadget_writel(struct usb_ss_dev *usb_ss, + uint32_t __iomem *reg, u32 value) +{ + cdns_writel(reg, value); +} + +/** + * next_request - returns next request from list + * @list: list containing requests + * + * Returns request or NULL if no requests in list + */ +static struct usb_request *next_request(struct list_head *list) +{ + if (list_empty(list)) + return NULL; + return list_first_entry(list, struct usb_request, list); +} + +/** + * wait_reg_bit - Read reg and compare until equal to specific value + * @reg: the register address to read + * @value: the value to compare + * @wait_value: 0 or 1 + * @timeout_ms: timeout value in milliseconds, must be larger than 1 + * + * Returns -ETIMEDOUT if timeout occurs + */ +static int wait_reg_bit(struct usb_ss_dev *usb_ss, u32 __iomem *reg, + u32 value, int wait_value, int timeout_ms) +{ + u32 temp; + + WARN_ON(timeout_ms <= 0); + timeout_ms *= 100; + temp = cdns_readl(reg); + while (timeout_ms-- > 0) { + if (!!(temp & value) == wait_value) + return 0; + temp = cdns_readl(reg); + udelay(10); + } + + dev_err(&usb_ss->dev, "wait register timeout %s\n", __func__); + return -ETIMEDOUT; +} + +static int wait_reg_bit_set(struct usb_ss_dev *usb_ss, u32 __iomem *reg, + u32 value, int timeout_ms) +{ + return wait_reg_bit(usb_ss, reg, value, 1, timeout_ms); +} + +static int wait_reg_bit_clear(struct usb_ss_dev *usb_ss, u32 __iomem *reg, + u32 value, int timeout_ms) +{ + return wait_reg_bit(usb_ss, reg, value, 0, timeout_ms); +} + +/** + * select_ep - selects endpoint + * @usb_ss: extended gadget object + * @ep: endpoint address + */ +static void select_ep(struct usb_ss_dev *usb_ss, u32 ep) +{ + if (!usb_ss || !usb_ss->regs) { + dev_err(&usb_ss->dev, "Failed to select endpoint!\n"); + return; + } + + gadget_writel(usb_ss, &usb_ss->regs->ep_sel, ep); +} + +/** + * usb_ss_allocate_trb_pool - Allocates TRB's pool for selected endpoint + * @usb_ss_ep: extended endpoint object + * + * Function will return 0 on success or -ENOMEM on allocation error + */ +static int usb_ss_allocate_trb_pool(struct usb_ss_endpoint *usb_ss_ep) +{ + struct usb_ss_dev *usb_ss = usb_ss_ep->usb_ss; + + if (!usb_ss_ep->trb_pool) { + usb_ss_ep->trb_pool = dma_zalloc_coherent(usb_ss->sysdev, + sizeof(struct usb_ss_trb) * USB_SS_TRBS_NUM, + &usb_ss_ep->trb_pool_dma, GFP_DMA); + if (!usb_ss_ep->trb_pool) + return -ENOMEM; + } + + if (!usb_ss_ep->cpu_addr) { + usb_ss_ep->cpu_addr = dma_alloc_coherent(usb_ss->sysdev, + CDNS3_UNALIGNED_BUF_SIZE, + &usb_ss_ep->dma_addr, GFP_DMA); + if (!usb_ss_ep->cpu_addr) + return -ENOMEM; + } + + return 0; +} + +/** + * cdns_data_flush - do flush data at onchip buffer + * @usb_ss_ep: extended endpoint object + * + * Endpoint must be selected before call to this function + * + * Returns zero on success or negative value on failure + */ +static int cdns_data_flush(struct usb_ss_endpoint *usb_ss_ep) +{ + struct usb_ss_dev *usb_ss = usb_ss_ep->usb_ss; + + gadget_writel(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__DFLUSH__MASK); + /* wait for DFLUSH cleared */ + return wait_reg_bit_clear(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__DFLUSH__MASK, 100); +} + +/** + * cdns_ep_stall_flush - Stalls and flushes selected endpoint + * @usb_ss_ep: extended endpoint object + * + * Endpoint must be selected before call to this function + */ +static void cdns_ep_stall_flush(struct usb_ss_endpoint *usb_ss_ep) +{ + struct usb_ss_dev *usb_ss = usb_ss_ep->usb_ss; + + gadget_writel(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__DFLUSH__MASK | EP_CMD__ERDY__MASK | + EP_CMD__SSTALL__MASK); + + /* wait for DFLUSH cleared */ + wait_reg_bit_clear(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__DFLUSH__MASK, 100); + usb_ss_ep->stalled_flag = 1; +} + +/** + * cdns_ep0_config - Configures default endpoint + * @usb_ss: extended gadget object + * + * Functions sets parameters: maximal packet size and enables interrupts + */ +static void cdns_ep0_config(struct usb_ss_dev *usb_ss) +{ + u32 reg, max_packet_size = 0; + + switch (usb_ss->gadget.speed) { + case USB_SPEED_UNKNOWN: + max_packet_size = ENDPOINT_MAX_PACKET_SIZE_0; + usb_ss->gadget.ep0->maxpacket = ENDPOINT_MAX_PACKET_SIZE_0; + cdns3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(0); + break; + + case USB_SPEED_LOW: + max_packet_size = ENDPOINT_MAX_PACKET_SIZE_8; + usb_ss->gadget.ep0->maxpacket = ENDPOINT_MAX_PACKET_SIZE_8; + cdns3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(8); + break; + + case USB_SPEED_FULL: + max_packet_size = ENDPOINT_MAX_PACKET_SIZE_64; + usb_ss->gadget.ep0->maxpacket = ENDPOINT_MAX_PACKET_SIZE_64; + cdns3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(64); + break; + + case USB_SPEED_HIGH: + max_packet_size = ENDPOINT_MAX_PACKET_SIZE_64; + usb_ss->gadget.ep0->maxpacket = ENDPOINT_MAX_PACKET_SIZE_64; + cdns3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(64); + break; + + case USB_SPEED_WIRELESS: + max_packet_size = ENDPOINT_MAX_PACKET_SIZE_64; + usb_ss->gadget.ep0->maxpacket = ENDPOINT_MAX_PACKET_SIZE_64; + cdns3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(64); + break; + + case USB_SPEED_SUPER: + max_packet_size = ENDPOINT_MAX_PACKET_SIZE_512; + usb_ss->gadget.ep0->maxpacket = ENDPOINT_MAX_PACKET_SIZE_512; + cdns3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512); + break; + + case USB_SPEED_SUPER_PLUS: + dev_warn(&usb_ss->dev, "USB 3.1 is not supported\n"); + usb_ss->gadget.ep0->maxpacket = ENDPOINT_MAX_PACKET_SIZE_512; + cdns3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512); + max_packet_size = ENDPOINT_MAX_PACKET_SIZE_512; + break; + } + + /* init ep out */ + select_ep(usb_ss, USB_DIR_OUT); + + gadget_writel(usb_ss, &usb_ss->regs->ep_cfg, + EP_CFG__ENABLE__MASK | + EP_CFG__MAXPKTSIZE__WRITE(max_packet_size)); + gadget_writel(usb_ss, &usb_ss->regs->ep_sts_en, + EP_STS_EN__SETUPEN__MASK | + EP_STS_EN__DESCMISEN__MASK | + EP_STS_EN__TRBERREN__MASK); + + /* init ep in */ + select_ep(usb_ss, USB_DIR_IN); + + gadget_writel(usb_ss, &usb_ss->regs->ep_cfg, + EP_CFG__ENABLE__MASK | + EP_CFG__MAXPKTSIZE__WRITE(max_packet_size)); + gadget_writel(usb_ss, &usb_ss->regs->ep_sts_en, + EP_STS_EN__SETUPEN__MASK | + EP_STS_EN__TRBERREN__MASK); + + reg = gadget_readl(usb_ss, &usb_ss->regs->usb_conf); + reg |= USB_CONF__U1DS__MASK | USB_CONF__U2DS__MASK; + gadget_writel(usb_ss, &usb_ss->regs->usb_conf, reg); + + cdns_prepare_setup_packet(usb_ss); +} + +/** + * cdns_gadget_unconfig - Unconfigures device controller + * @usb_ss: extended gadget object + */ +static void cdns_gadget_unconfig(struct usb_ss_dev *usb_ss) +{ + /* RESET CONFIGURATION */ + gadget_writel(usb_ss, &usb_ss->regs->usb_conf, + USB_CONF__CFGRST__MASK); + + cdns_enable_l1(usb_ss, 0); + usb_ss->hw_configured_flag = 0; + usb_ss->onchip_mem_allocated_size = 0; + usb_ss->out_mem_is_allocated = 0; +} + +/** + * cdns_ep0_run_transfer - Do transfer on default endpoint hardware + * @usb_ss: extended gadget object + * @dma_addr: physical address where data is/will be stored + * @length: data length + * @erdy: set it to 1 when ERDY packet should be sent - + * exit from flow control state + */ +static void cdns_ep0_run_transfer(struct usb_ss_dev *usb_ss, + dma_addr_t dma_addr, unsigned int length, int erdy) +{ + usb_ss->trb_ep0[0] = TRB_SET_DATA_BUFFER_POINTER(dma_addr); + usb_ss->trb_ep0[1] = TRB_SET_TRANSFER_LENGTH((u32)length); + usb_ss->trb_ep0[2] = TRB_SET_CYCLE_BIT | + TRB_SET_INT_ON_COMPLETION | TRB_TYPE_NORMAL; + + dev_dbg(&usb_ss->dev, "DRBL(%02X)\n", + usb_ss->ep0_data_dir ? USB_DIR_IN : USB_DIR_OUT); + + select_ep(usb_ss, usb_ss->ep0_data_dir + ? USB_DIR_IN : USB_DIR_OUT); + + gadget_writel(usb_ss, &usb_ss->regs->ep_traddr, + EP_TRADDR__TRADDR__WRITE(usb_ss->trb_ep0_dma)); + gadget_writel(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__DRDY__MASK); /* drbl */ + + if (erdy) + gadget_writel(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__ERDY__MASK); +} + +/** + * cdns_ep_run_transfer - Do transfer on no-default endpoint hardware + * @usb_ss_ep: extended endpoint object + * + * Returns zero on success or negative value on failure + */ +static int cdns_ep_run_transfer(struct usb_ss_endpoint *usb_ss_ep) +{ + dma_addr_t trb_dma; + struct usb_request *request = next_request(&usb_ss_ep->request_list); + struct usb_ss_dev *usb_ss = usb_ss_ep->usb_ss; + int sg_iter = 0; + struct usb_ss_trb *trb; + + if (request == NULL) + return -EINVAL; + + if (request->num_sgs > USB_SS_TRBS_NUM) + return -EINVAL; + + dev_dbg(&usb_ss->dev, "DRBL(%02X)\n", + usb_ss_ep->endpoint.desc->bEndpointAddress); + + usb_ss_ep->hw_pending_flag = 1; + trb_dma = request->dma; + + /* must allocate buffer aligned to 8 */ + if ((request->dma % ADDR_MODULO_8)) { + if (request->length <= CDNS3_UNALIGNED_BUF_SIZE) { + memcpy(usb_ss_ep->cpu_addr, request->buf, + request->length); + trb_dma = usb_ss_ep->dma_addr; + } else { + return -ENOMEM; + } + } + + trb = usb_ss_ep->trb_pool; + + do { + /* fill TRB */ + trb->offset0 = TRB_SET_DATA_BUFFER_POINTER(request->num_sgs == 0 + ? trb_dma : request->sg[sg_iter].dma_address); + + trb->offset4 = TRB_SET_BURST_LENGTH(16) | + TRB_SET_TRANSFER_LENGTH(request->num_sgs == 0 ? + request->length : request->sg[sg_iter].length); + + trb->offset8 = TRB_SET_CYCLE_BIT + | TRB_SET_INT_ON_COMPLETION + | TRB_SET_INT_ON_SHORT_PACKET + | TRB_TYPE_NORMAL; + + ++sg_iter; + ++trb; + + } while (sg_iter < request->num_sgs); + + /* arm transfer on selected endpoint */ + select_ep(usb_ss_ep->usb_ss, + usb_ss_ep->endpoint.desc->bEndpointAddress); + gadget_writel(usb_ss, &usb_ss->regs->ep_traddr, + EP_TRADDR__TRADDR__WRITE(usb_ss_ep->trb_pool_dma)); + gadget_writel(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__DRDY__MASK); /* DRDY */ + return 0; +} + +/** + * cdns_get_setup_ret - Returns status of handling setup packet + * Setup is handled by gadget driver + * @usb_ss: extended gadget object + * @ctrl_req: pointer to received setup packet + * + * Returns zero on success or negative value on failure + */ +static int cdns_get_setup_ret(struct usb_ss_dev *usb_ss, + struct usb_ctrlrequest *ctrl_req) +{ + int ret; + + spin_unlock(&usb_ss->lock); + usb_ss->setup_pending = 1; + ret = usb_ss->gadget_driver->setup(&usb_ss->gadget, ctrl_req); + usb_ss->setup_pending = 0; + spin_lock(&usb_ss->lock); + return ret; +} + +static void cdns_prepare_setup_packet(struct usb_ss_dev *usb_ss) +{ + usb_ss->ep0_data_dir = 0; + cdns_ep0_run_transfer(usb_ss, usb_ss->setup_dma, 8, 0); +} + +/** + * cdns_req_ep0_set_address - Handling of SET_ADDRESS standard USB request + * @usb_ss: extended gadget object + * @ctrl_req: pointer to received setup packet + * + * Returns 0 if success, error code on error + */ +static int cdns_req_ep0_set_address(struct usb_ss_dev *usb_ss, + struct usb_ctrlrequest *ctrl_req) +{ + enum usb_device_state device_state = usb_ss->gadget.state; + u32 reg; + u32 addr; + + addr = le16_to_cpu(ctrl_req->wValue); + + if (addr > DEVICE_ADDRESS_MAX) { + dev_err(&usb_ss->dev, + "Device address (%d) cannot be greater than %d\n", + addr, DEVICE_ADDRESS_MAX); + return -EINVAL; + } + + if (device_state == USB_STATE_CONFIGURED) { + dev_err(&usb_ss->dev, "USB device already configured\n"); + return -EINVAL; + } + + reg = gadget_readl(usb_ss, &usb_ss->regs->usb_cmd); + + gadget_writel(usb_ss, &usb_ss->regs->usb_cmd, reg + | USB_CMD__FADDR__WRITE(addr) + | USB_CMD__SET_ADDR__MASK); + + usb_gadget_set_state(&usb_ss->gadget, + (addr ? USB_STATE_ADDRESS : USB_STATE_DEFAULT)); + + cdns_prepare_setup_packet(usb_ss); + + gadget_writel(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__ERDY__MASK | EP_CMD__REQ_CMPL__MASK); + return 0; +} + +/** + * cdns_req_ep0_get_status - Handling of GET_STATUS standard USB request + * @usb_ss: extended gadget object + * @ctrl_req: pointer to received setup packet + * + * Returns 0 if success, error code on error + */ +static int cdns_req_ep0_get_status(struct usb_ss_dev *usb_ss, + struct usb_ctrlrequest *ctrl_req) +{ + u16 usb_status = 0; + unsigned int length = 2; + u32 recip = ctrl_req->bRequestType & USB_RECIP_MASK; + u32 reg; + + switch (recip) { + + case USB_RECIP_DEVICE: + /* handling otg features */ + if (ctrl_req->wIndex == OTG_STS_SELECTOR) { + length = 1; + usb_status = usb_ss->gadget.host_request_flag; + } else { + + reg = gadget_readl(usb_ss, &usb_ss->regs->usb_sts); + + if (reg & USB_STS__U1ENS__MASK) + usb_status |= 1uL << USB_DEV_STAT_U1_ENABLED; + + if (reg & USB_STS__U2ENS__MASK) + usb_status |= 1uL << USB_DEV_STAT_U2_ENABLED; + + if (usb_ss->wake_up_flag) + usb_status |= 1uL << USB_DEVICE_REMOTE_WAKEUP; + + /* self powered */ + usb_status |= usb_ss->gadget.is_selfpowered; + } + break; + + case USB_RECIP_INTERFACE: + return cdns_get_setup_ret(usb_ss, ctrl_req); + + case USB_RECIP_ENDPOINT: + /* check if endpoint is stalled */ + select_ep(usb_ss, ctrl_req->wIndex); + if (gadget_readl(usb_ss, &usb_ss->regs->ep_sts) + & EP_STS__STALL__MASK) + usb_status = 1; + break; + + default: + return -EINVAL; + } + + *(u16 *)usb_ss->setup = cpu_to_le16(usb_status); + + usb_ss->actual_ep0_request = NULL; + cdns_ep0_run_transfer(usb_ss, usb_ss->setup_dma, length, 1); + return 0; +} + +/** + * cdns_req_ep0_handle_feature - + * Handling of GET/SET_FEATURE standard USB request + * + * @usb_ss: extended gadget object + * @ctrl_req: pointer to received setup packet + * @set: must be set to 1 for SET_FEATURE request + * + * Returns 0 if success, error code on error + */ +static int cdns_req_ep0_handle_feature(struct usb_ss_dev *usb_ss, + struct usb_ctrlrequest *ctrl_req, int set) +{ + u32 recip = ctrl_req->bRequestType & USB_RECIP_MASK; + struct usb_ss_endpoint *usb_ss_ep; + u32 reg; + u8 tmode = 0; + int ret = 0; + + switch (recip) { + case USB_RECIP_DEVICE: + switch (ctrl_req->wValue) { + case USB_DEVICE_U1_ENABLE: + if (usb_ss->gadget.state != USB_STATE_CONFIGURED) + return -EINVAL; + if (usb_ss->gadget.speed != USB_SPEED_SUPER) + return -EINVAL; + + reg = gadget_readl(usb_ss, &usb_ss->regs->usb_conf); + if (set) + /* set U1EN */ + reg |= USB_CONF__U1EN__MASK; + else + /* set U1 disable */ + reg |= USB_CONF__U1DS__MASK; + gadget_writel(usb_ss, &usb_ss->regs->usb_conf, reg); + break; + case USB_DEVICE_U2_ENABLE: + if (usb_ss->gadget.state != USB_STATE_CONFIGURED) + return -EINVAL; + if (usb_ss->gadget.speed != USB_SPEED_SUPER) + return -EINVAL; + + reg = gadget_readl(usb_ss, &usb_ss->regs->usb_conf); + if (set) + /* set U2EN */ + reg |= USB_CONF__U2EN__MASK; + else + /* set U2 disable */ + reg |= USB_CONF__U2DS__MASK; + gadget_writel(usb_ss, &usb_ss->regs->usb_conf, reg); + break; + case USB_DEVICE_A_ALT_HNP_SUPPORT: + break; + case USB_DEVICE_A_HNP_SUPPORT: + break; + case USB_DEVICE_B_HNP_ENABLE: + if (!usb_ss->gadget.b_hnp_enable && set) + usb_ss->gadget.b_hnp_enable = 1; + break; + case USB_DEVICE_REMOTE_WAKEUP: + usb_ss->wake_up_flag = !!set; + break; + case USB_DEVICE_TEST_MODE: + if (usb_ss->gadget.state != USB_STATE_CONFIGURED) + return -EINVAL; + if (usb_ss->gadget.speed != USB_SPEED_HIGH && + usb_ss->gadget.speed != USB_SPEED_FULL) + return -EINVAL; + if (ctrl_req->wLength != 0 || + ctrl_req->bRequestType & USB_DIR_IN) { + dev_err(&usb_ss->dev, "req is error\n"); + return -EINVAL; + } + tmode = le16_to_cpu(ctrl_req->wIndex) >> 8; + switch (tmode) { + case TEST_J: + case TEST_K: + case TEST_SE0_NAK: + case TEST_PACKET: + reg = gadget_readl(usb_ss, + &usb_ss->regs->usb_cmd); + tmode -= 1; + reg |= USB_CMD__STMODE | + USB_CMD__TMODE_SEL(tmode); + gadget_writel(usb_ss, &usb_ss->regs->usb_cmd, + reg); + dev_info(&usb_ss->dev, + "set test mode, val=0x%x", reg); + break; + default: + return -EINVAL; + } + break; + + default: + return -EINVAL; + + } + break; + case USB_RECIP_INTERFACE: + return cdns_get_setup_ret(usb_ss, ctrl_req); + case USB_RECIP_ENDPOINT: + select_ep(usb_ss, ctrl_req->wIndex); + if (set) { + /* set stall */ + gadget_writel(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__SSTALL__MASK); + + /* handle non zero endpoint software endpoint */ + if (ctrl_req->wIndex & 0x7F) { + usb_ss_ep = usb_ss->eps[CAST_EP_ADDR_TO_INDEX( + ctrl_req->wIndex)]; + usb_ss_ep->stalled_flag = 1; + } + } else { + struct usb_request *request; + + if (ctrl_req->wIndex & 0x7F) { + if (usb_ss->eps[CAST_EP_ADDR_TO_INDEX( + ctrl_req->wIndex)]->wedge_flag) + goto jmp_wedge; + } + + /* clear stall */ + gadget_writel(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__CSTALL__MASK | EP_CMD__EPRST__MASK); + /* wait for EPRST cleared */ + ret = wait_reg_bit_clear(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__EPRST__MASK, 100); + + /* handle non zero endpoint software endpoint */ + if (ctrl_req->wIndex & 0x7F) { + usb_ss_ep = usb_ss->eps[CAST_EP_ADDR_TO_INDEX( + ctrl_req->wIndex)]; + usb_ss_ep->stalled_flag = 0; + + request = next_request( + &usb_ss_ep->request_list); + if (request) + cdns_ep_run_transfer(usb_ss_ep); + } + } +jmp_wedge: + select_ep(usb_ss, 0x00); + break; + + default: + return -EINVAL; + } + + gadget_writel(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__ERDY__MASK | EP_CMD__REQ_CMPL__MASK); + return ret; +} + +/** + * cdns_req_ep0_set_sel - Handling of SET_SEL standard USB request + * @usb_ss: extended gadget object + * @ctrl_req: pointer to received setup packet + * + * Returns 0 if success, error code on error + */ +static int cdns_req_ep0_set_sel(struct usb_ss_dev *usb_ss, + struct usb_ctrlrequest *ctrl_req) +{ + if (usb_ss->gadget.state < USB_STATE_ADDRESS) + return -EINVAL; + + if (ctrl_req->wLength != 6) { + dev_err(&usb_ss->dev, "Set SEL should be 6 bytes, got %d\n", + ctrl_req->wLength); + return -EINVAL; + } + + usb_ss->ep0_data_dir = 0; + usb_ss->actual_ep0_request = NULL; + cdns_ep0_run_transfer(usb_ss, usb_ss->setup_dma, 6, 1); + return 0; +} + +/** + * cdns_req_ep0_set_isoch_delay - + * Handling of GET_ISOCH_DELAY standard USB request + * @usb_ss: extended gadget object + * @ctrl_req: pointer to received setup packet + * + * Returns 0 if success, error code on error + */ +static int cdns_req_ep0_set_isoch_delay(struct usb_ss_dev *usb_ss, + struct usb_ctrlrequest *ctrl_req) +{ + if (ctrl_req->wIndex || ctrl_req->wLength) + return -EINVAL; + + usb_ss->isoch_delay = ctrl_req->wValue; + gadget_writel(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__ERDY__MASK | EP_CMD__REQ_CMPL__MASK); + return 0; +} + +static void cdns_enable_l1(struct usb_ss_dev *usb_ss, int enable) +{ + if (enable) + gadget_writel(usb_ss, &usb_ss->regs->usb_conf, + USB_CONF__L1EN__MASK); + else + gadget_writel(usb_ss, &usb_ss->regs->usb_conf, + USB_CONF__L1DS__MASK); +} + +/** + * cdns_req_ep0_set_configuration - Handling of SET_CONFIG standard USB request + * @usb_ss: extended gadget object + * @ctrl_req: pointer to received setup packet + * + * Returns 0 if success, 0x7FFF on deferred status stage, error code on error + */ +static int cdns_req_ep0_set_configuration(struct usb_ss_dev *usb_ss, + struct usb_ctrlrequest *ctrl_req) +{ + enum usb_device_state device_state = usb_ss->gadget.state; + u32 config = le16_to_cpu(ctrl_req->wValue); + struct usb_ep *ep; + struct usb_ss_endpoint *usb_ss_ep, *temp_ss_ep; + int i, result = 0; + + switch (device_state) { + case USB_STATE_ADDRESS: + /* Configure non-control EPs */ + list_for_each_entry_safe(usb_ss_ep, temp_ss_ep, + &usb_ss->ep_match_list, ep_match_pending_list) + cdns_ep_config(usb_ss_ep); + + result = cdns_get_setup_ret(usb_ss, ctrl_req); + + if (result != 0) + return result; + + if (config) { + if (!usb_ss->hw_configured_flag) { + /* SET CONFIGURATION */ + gadget_writel(usb_ss, &usb_ss->regs->usb_conf, + USB_CONF__CFGSET__MASK); + gadget_writel(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__ERDY__MASK | + EP_CMD__REQ_CMPL__MASK); + /* wait until configuration set */ + result = wait_reg_bit_set(usb_ss, + &usb_ss->regs->usb_sts, + USB_STS__CFGSTS__MASK, 100); + usb_ss->hw_configured_flag = 1; + cdns_enable_l1(usb_ss, 1); + + list_for_each_entry(ep, + &usb_ss->gadget.ep_list, + ep_list) { + if (ep->enabled) + cdns_ep_run_transfer( + to_usb_ss_ep(ep)); + } + } + } else { + cdns_gadget_unconfig(usb_ss); + for (i = 0; i < usb_ss->ep_nums; i++) + usb_ss->eps[i]->endpoint.enabled = 0; + usb_gadget_set_state(&usb_ss->gadget, + USB_STATE_ADDRESS); + } + break; + case USB_STATE_CONFIGURED: + result = cdns_get_setup_ret(usb_ss, ctrl_req); + if (!config && !result) { + cdns_gadget_unconfig(usb_ss); + for (i = 0; i < usb_ss->ep_nums; i++) + usb_ss->eps[i]->endpoint.enabled = 0; + usb_gadget_set_state(&usb_ss->gadget, + USB_STATE_ADDRESS); + } + break; + default: + result = -EINVAL; + } + + return result; +} + +/** + * cdns_ep0_standard_request - Handling standard USB requests + * @usb_ss: extended gadget object + * @ctrl_req: pointer to received setup packet + * + * Returns 0 if success, error code on error + */ +static int cdns_ep0_standard_request(struct usb_ss_dev *usb_ss, + struct usb_ctrlrequest *ctrl_req) +{ + switch (ctrl_req->bRequest) { + case USB_REQ_SET_ADDRESS: + return cdns_req_ep0_set_address(usb_ss, ctrl_req); + case USB_REQ_SET_CONFIGURATION: + return cdns_req_ep0_set_configuration(usb_ss, ctrl_req); + case USB_REQ_GET_STATUS: + return cdns_req_ep0_get_status(usb_ss, ctrl_req); + case USB_REQ_CLEAR_FEATURE: + return cdns_req_ep0_handle_feature(usb_ss, ctrl_req, 0); + case USB_REQ_SET_FEATURE: + return cdns_req_ep0_handle_feature(usb_ss, ctrl_req, 1); + case USB_REQ_SET_SEL: + return cdns_req_ep0_set_sel(usb_ss, ctrl_req); + case USB_REQ_SET_ISOCH_DELAY: + return cdns_req_ep0_set_isoch_delay(usb_ss, ctrl_req); + default: + return cdns_get_setup_ret(usb_ss, ctrl_req); + } +} + +/** + * cdns_ep0_setup_phase - Handling setup USB requests + * @usb_ss: extended gadget object + */ +static void cdns_ep0_setup_phase(struct usb_ss_dev *usb_ss) +{ + int result; + struct usb_ctrlrequest *ctrl_req = + (struct usb_ctrlrequest *)usb_ss->setup; + + if ((ctrl_req->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) + result = cdns_ep0_standard_request(usb_ss, ctrl_req); + else + result = cdns_get_setup_ret(usb_ss, ctrl_req); + + if (result != 0 && result != USB_GADGET_DELAYED_STATUS) { + dev_dbg(&usb_ss->dev, "STALL(00) %d\n", result); + /* set_stall on ep0 */ + select_ep(usb_ss, 0x00); + gadget_writel(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__SSTALL__MASK); + gadget_writel(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__ERDY__MASK | EP_CMD__REQ_CMPL__MASK); + } +} + +/** + * cdns_check_ep_interrupt_proceed - Processes interrupt related to endpoint + * @usb_ss_ep: extended endpoint object + * + * Returns 0 + */ +static int cdns_check_ep_interrupt_proceed(struct usb_ss_endpoint *usb_ss_ep) +{ + struct usb_ss_dev *usb_ss = usb_ss_ep->usb_ss; + struct usb_request *request; + u32 ep_sts_reg; + + select_ep(usb_ss, usb_ss_ep->endpoint.address); + ep_sts_reg = gadget_readl(usb_ss, &usb_ss->regs->ep_sts); + + dev_dbg(&usb_ss->dev, "EP_STS: %08X\n", ep_sts_reg); + + if (ep_sts_reg & EP_STS__TRBERR__MASK) { + gadget_writel(usb_ss, + &usb_ss->regs->ep_sts, EP_STS__TRBERR__MASK); + + dev_dbg(&usb_ss->dev, "TRBERR(%02X)\n", + usb_ss_ep->endpoint.desc->bEndpointAddress); + } + + if (ep_sts_reg & EP_STS__ISOERR__MASK) { + gadget_writel(usb_ss, + &usb_ss->regs->ep_sts, EP_STS__ISOERR__MASK); + dev_dbg(&usb_ss->dev, "ISOERR(%02X)\n", + usb_ss_ep->endpoint.desc->bEndpointAddress); + } + + if (ep_sts_reg & EP_STS__OUTSMM__MASK) { + gadget_writel(usb_ss, &usb_ss->regs->ep_sts, + EP_STS__OUTSMM__MASK); + dev_dbg(&usb_ss->dev, "OUTSMM(%02X)\n", + usb_ss_ep->endpoint.desc->bEndpointAddress); + } + + if (ep_sts_reg & EP_STS__NRDY__MASK) { + gadget_writel(usb_ss, + &usb_ss->regs->ep_sts, EP_STS__NRDY__MASK); + dev_dbg(&usb_ss->dev, "NRDY(%02X)\n", + usb_ss_ep->endpoint.desc->bEndpointAddress); + } + + if ((ep_sts_reg & EP_STS__IOC__MASK) + || (ep_sts_reg & EP_STS__ISP__MASK)) { + gadget_writel(usb_ss, &usb_ss->regs->ep_sts, + EP_STS__IOC__MASK | EP_STS__ISP__MASK); + + /* get just completed request */ + request = next_request(&usb_ss_ep->request_list); + if (!request) + return 0; + + if ((request->dma % ADDR_MODULO_8) && + (usb_ss_ep->dir == USB_DIR_OUT)) + memcpy(request->buf, usb_ss_ep->cpu_addr, + request->length); + + usb_gadget_unmap_request_by_dev(usb_ss->sysdev, request, + usb_ss_ep->endpoint.desc->bEndpointAddress + & ENDPOINT_DIR_MASK); + + request->status = 0; + request->actual = + le32_to_cpu(((u32 *) usb_ss_ep->trb_pool)[1]) + & ACTUAL_TRANSFERRED_BYTES_MASK; + + dev_dbg(&usb_ss->dev, "IOC(%02X) %d\n", + usb_ss_ep->endpoint.desc->bEndpointAddress, + request->actual); + + list_del(&request->list); + + usb_ss_ep->hw_pending_flag = 0; + if (request->complete) { + spin_unlock(&usb_ss->lock); + usb_gadget_giveback_request(&usb_ss_ep->endpoint, + request); + spin_lock(&usb_ss->lock); + } + + if (request->buf == usb_ss->zlp_buf) + kfree(request); + + /* handle deferred STALL */ + if (usb_ss_ep->stalled_flag) { + cdns_ep_stall_flush(usb_ss_ep); + return 0; + } + + /* exit if hardware transfer already started */ + if (usb_ss_ep->hw_pending_flag) + return 0; + + /* if any request queued run it! */ + if (!list_empty(&usb_ss_ep->request_list)) + cdns_ep_run_transfer(usb_ss_ep); + } + + if (ep_sts_reg & EP_STS__DESCMIS__MASK) { + gadget_writel(usb_ss, + &usb_ss->regs->ep_sts, EP_STS__DESCMIS__MASK); + dev_dbg(&usb_ss->dev, "DESCMIS(%02X)\n", + usb_ss_ep->endpoint.desc->bEndpointAddress); + } + + return 0; +} + +/** + * cdns_check_ep0_interrupt_proceed - Processes interrupt related to endpoint 0 + * @usb_ss: extended gadget object + * @dir: 1 for IN direction, 0 for OUT direction + */ +static void cdns_check_ep0_interrupt_proceed(struct usb_ss_dev *usb_ss, int dir) +{ + u32 ep_sts_reg; + int i; + + select_ep(usb_ss, 0 | (dir ? USB_DIR_IN : USB_DIR_OUT)); + ep_sts_reg = gadget_readl(usb_ss, &usb_ss->regs->ep_sts); + + dev_dbg(&usb_ss->dev, "EP_STS: %08X\n", ep_sts_reg); + + __pending_setup_status_handler(usb_ss); + + if ((ep_sts_reg & EP_STS__SETUP__MASK) && (dir == 0)) { + dev_dbg(&usb_ss->dev, "SETUP(%02X)\n", 0x00); + + gadget_writel(usb_ss, &usb_ss->regs->ep_sts, + EP_STS__SETUP__MASK | + EP_STS__IOC__MASK | EP_STS__ISP__MASK); + + dev_dbg(&usb_ss->dev, "SETUP: "); + for (i = 0; i < 8; i++) + dev_dbg(&usb_ss->dev, "%02X ", usb_ss->setup[i]); + dev_dbg(&usb_ss->dev, "\nSTATE: %d\n", usb_ss->gadget.state); + usb_ss->ep0_data_dir = usb_ss->setup[0] & USB_DIR_IN; + cdns_ep0_setup_phase(usb_ss); + ep_sts_reg &= ~(EP_STS__SETUP__MASK | + EP_STS__IOC__MASK | + EP_STS__ISP__MASK); + } + + if (ep_sts_reg & EP_STS__TRBERR__MASK) { + gadget_writel(usb_ss, + &usb_ss->regs->ep_sts, EP_STS__TRBERR__MASK); + dev_dbg(&usb_ss->dev, "TRBERR(%02X)\n", + dir ? USB_DIR_IN : USB_DIR_OUT); + } + + if (ep_sts_reg & EP_STS__DESCMIS__MASK) { + gadget_writel(usb_ss, + &usb_ss->regs->ep_sts, EP_STS__DESCMIS__MASK); + + dev_dbg(&usb_ss->dev, "DESCMIS(%02X)\n", + dir ? USB_DIR_IN : USB_DIR_OUT); + + if (dir == 0 && !usb_ss->setup_pending) { + usb_ss->ep0_data_dir = 0; + cdns_ep0_run_transfer(usb_ss, + usb_ss->setup_dma, 8, 0); + } + } + + if ((ep_sts_reg & EP_STS__IOC__MASK) + || (ep_sts_reg & EP_STS__ISP__MASK)) { + gadget_writel(usb_ss, + &usb_ss->regs->ep_sts, EP_STS__IOC__MASK); + if (usb_ss->actual_ep0_request) { + usb_gadget_unmap_request_by_dev(usb_ss->sysdev, + usb_ss->actual_ep0_request, + usb_ss->ep0_data_dir); + + usb_ss->actual_ep0_request->actual = + le32_to_cpu((usb_ss->trb_ep0)[1]) + & ACTUAL_TRANSFERRED_BYTES_MASK; + + dev_dbg(&usb_ss->dev, "IOC(%02X) %d\n", + dir ? USB_DIR_IN : USB_DIR_OUT, + usb_ss->actual_ep0_request->actual); + list_del_init(&usb_ss->actual_ep0_request->list); + } + + if (usb_ss->actual_ep0_request + && usb_ss->actual_ep0_request->complete) { + spin_unlock(&usb_ss->lock); + usb_ss->actual_ep0_request->complete(usb_ss->gadget.ep0, + usb_ss->actual_ep0_request); + spin_lock(&usb_ss->lock); + } + cdns_prepare_setup_packet(usb_ss); + gadget_writel(usb_ss, + &usb_ss->regs->ep_cmd, EP_CMD__REQ_CMPL__MASK); + } +} + +/** + * cdns_check_usb_interrupt_proceed - Processes interrupt related to device + * @usb_ss: extended gadget object + * @usb_ists: bitmap representation of device's reported interrupts + * (usb_ists register value) + */ +static void cdns_check_usb_interrupt_proceed(struct usb_ss_dev *usb_ss, + u32 usb_ists) +{ + int interrupt_bit = ffs(usb_ists) - 1; + int speed; + u32 val; + + dev_dbg(&usb_ss->dev, "USB interrupt detected\n"); + + switch (interrupt_bit) { + case USB_ISTS__CON2I__SHIFT: + /* FS/HS Connection detected */ + dev_dbg(&usb_ss->dev, + "[Interrupt] FS/HS Connection detected\n"); + val = gadget_readl(usb_ss, &usb_ss->regs->usb_sts); + speed = USB_STS__USBSPEED__READ(val); + if (speed == USB_SPEED_WIRELESS) + speed = USB_SPEED_SUPER; + dev_dbg(&usb_ss->dev, "Speed value: %s (%d), usbsts:0x%x\n", + usb_speed_string(speed), speed, val); + usb_ss->gadget.speed = speed; + usb_ss->is_connected = 1; + usb_gadget_set_state(&usb_ss->gadget, USB_STATE_POWERED); + cdns_ep0_config(usb_ss); + break; + case USB_ISTS__CONI__SHIFT: + /* SS Connection detected */ + dev_dbg(&usb_ss->dev, "[Interrupt] SS Connection detected\n"); + val = gadget_readl(usb_ss, &usb_ss->regs->usb_sts); + speed = USB_STS__USBSPEED__READ(val); + if (speed == USB_SPEED_WIRELESS) + speed = USB_SPEED_SUPER; + dev_dbg(&usb_ss->dev, "Speed value: %s (%d), usbsts:0x%x\n", + usb_speed_string(speed), speed, val); + usb_ss->gadget.speed = speed; + usb_ss->is_connected = 1; + usb_gadget_set_state(&usb_ss->gadget, USB_STATE_POWERED); + cdns_ep0_config(usb_ss); + break; + case USB_ISTS__DIS2I__SHIFT: + case USB_ISTS__DISI__SHIFT: + /* SS Disconnection detected */ + val = gadget_readl(usb_ss, &usb_ss->regs->usb_sts); + dev_dbg(&usb_ss->dev, + "[Interrupt] Disconnection detected: usbsts:0x%x\n", + val); + if (usb_ss->gadget_driver + && usb_ss->gadget_driver->disconnect) { + + spin_unlock(&usb_ss->lock); + usb_ss->gadget_driver->disconnect(&usb_ss->gadget); + spin_lock(&usb_ss->lock); + } + usb_ss->gadget.speed = USB_SPEED_UNKNOWN; + usb_gadget_set_state(&usb_ss->gadget, USB_STATE_NOTATTACHED); + usb_ss->is_connected = 0; + cdns_gadget_unconfig(usb_ss); + break; + case USB_ISTS__L2ENTI__SHIFT: + dev_dbg(&usb_ss->dev, + "[Interrupt] Device suspended\n"); + break; + case USB_ISTS__L2EXTI__SHIFT: + dev_dbg(&usb_ss->dev, "[Interrupt] L2 exit detected\n"); + /* + * Exit from standby mode + * on L2 exit (Suspend in HS/FS or SS) + */ + break; + case USB_ISTS__U3EXTI__SHIFT: + /* + * Exit from standby mode + * on U3 exit (Suspend in HS/FS or SS) + */ + dev_dbg(&usb_ss->dev, "[Interrupt] U3 exit detected\n"); + break; + + /* resets cases */ + case USB_ISTS__UWRESI__SHIFT: + case USB_ISTS__UHRESI__SHIFT: + case USB_ISTS__U2RESI__SHIFT: + dev_dbg(&usb_ss->dev, "[Interrupt] Reset detected\n"); + speed = USB_STS__USBSPEED__READ( + gadget_readl(usb_ss, &usb_ss->regs->usb_sts)); + if (speed == USB_SPEED_WIRELESS) + speed = USB_SPEED_SUPER; + usb_gadget_set_state(&usb_ss->gadget, USB_STATE_DEFAULT); + usb_ss->gadget.speed = speed; + cdns_gadget_unconfig(usb_ss); + cdns_ep0_config(usb_ss); + break; + default: + break; + } + + /* Clear interrupt bit */ + gadget_writel(usb_ss, &usb_ss->regs->usb_ists, (1uL << interrupt_bit)); +} + +/** + * cdns_irq_handler - irq line interrupt handler + * @cdns: cdns3 instance + * + * Returns IRQ_HANDLED when interrupt raised by USBSS_DEV, + * IRQ_NONE when interrupt raised by other device connected + * to the irq line + */ +static irqreturn_t cdns_irq_handler_thread(struct cdns3 *cdns) +{ + struct usb_ss_dev *usb_ss = + container_of(cdns->gadget_dev, struct usb_ss_dev, dev); + u32 reg; + enum irqreturn ret = IRQ_NONE; + unsigned long flags; + + spin_lock_irqsave(&usb_ss->lock, flags); + + /* check USB device interrupt */ + reg = gadget_readl(usb_ss, &usb_ss->regs->usb_ists); + if (reg) { + dev_dbg(&usb_ss->dev, "usb_ists: %08X\n", reg); + cdns_check_usb_interrupt_proceed(usb_ss, reg); + ret = IRQ_HANDLED; + } + + /* check endpoint interrupt */ + reg = gadget_readl(usb_ss, &usb_ss->regs->ep_ists); + if (reg != 0) { + dev_dbg(&usb_ss->dev, "ep_ists: %08X\n", reg); + } else { + if (gadget_readl(usb_ss, &usb_ss->regs->usb_sts) & + USB_STS__CFGSTS__MASK) + ret = IRQ_HANDLED; + goto irqend; + } + + /* handle default endpoint OUT */ + if (reg & EP_ISTS__EOUT0__MASK) { + cdns_check_ep0_interrupt_proceed(usb_ss, 0); + ret = IRQ_HANDLED; + } + + /* handle default endpoint IN */ + if (reg & EP_ISTS__EIN0__MASK) { + cdns_check_ep0_interrupt_proceed(usb_ss, 1); + ret = IRQ_HANDLED; + } + + /* check if interrupt from non default endpoint, if no exit */ + reg &= ~(EP_ISTS__EOUT0__MASK | EP_ISTS__EIN0__MASK); + if (!reg) + goto irqend; + + do { + unsigned int bit_pos = ffs(reg); + u32 bit_mask = 1 << (bit_pos - 1); + + dev_dbg(&usb_ss->dev, "Interrupt on index: %d bitmask %08X\n", + CAST_EP_REG_POS_TO_INDEX(bit_pos), bit_mask); + cdns_check_ep_interrupt_proceed( + usb_ss->eps[CAST_EP_REG_POS_TO_INDEX(bit_pos)]); + reg &= ~bit_mask; + ret = IRQ_HANDLED; + } while (reg); + +irqend: + spin_unlock_irqrestore(&usb_ss->lock, flags); + return ret; +} + +/** + * usb_ss_gadget_ep0_enable + * Function shouldn't be called by gadget driver, + * endpoint 0 is allways active + */ +static int usb_ss_gadget_ep0_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + return -EINVAL; +} + +/** + * usb_ss_gadget_ep0_disable + * Function shouldn't be called by gadget driver, + * endpoint 0 is allways active + */ +static int usb_ss_gadget_ep0_disable(struct usb_ep *ep) +{ + return -EINVAL; +} + +/** + * usb_ss_gadget_ep0_set_halt + * @ep: pointer to endpoint zero object + * @value: 1 for set stall, 0 for clear stall + * + * Returns 0 + */ +static int usb_ss_gadget_ep0_set_halt(struct usb_ep *ep, int value) +{ + /* TODO */ + return 0; +} + +static void __pending_setup_status_handler(struct usb_ss_dev *usb_ss) +{ + struct usb_request *request = usb_ss->pending_status_request; + + if (usb_ss->status_completion_no_call && request && request->complete) { + request->complete(usb_ss->gadget.ep0, request); + usb_ss->status_completion_no_call = 0; + } +} + +static void pending_setup_status_handler(struct work_struct *work) +{ + struct usb_ss_dev *usb_ss = container_of(work, struct usb_ss_dev, + pending_status_wq); + unsigned long flags; + + spin_lock_irqsave(&usb_ss->lock, flags); + __pending_setup_status_handler(usb_ss); + spin_unlock_irqrestore(&usb_ss->lock, flags); +} + +/** + * usb_ss_gadget_ep0_queue Transfer data on endpoint zero + * @ep: pointer to endpoint zero object + * @request: pointer to request object + * @gfp_flags: gfp flags + * + * Returns 0 on success, error code elsewhere + */ +static int usb_ss_gadget_ep0_queue(struct usb_ep *ep, + struct usb_request *request, gfp_t gfp_flags) +{ + int ret = 0; + unsigned long flags; + int erdy_sent = 0; + /* get extended endpoint */ + struct usb_ss_endpoint *usb_ss_ep = + to_usb_ss_ep(ep); + struct usb_ss_dev *usb_ss = usb_ss_ep->usb_ss; + + dev_dbg(&usb_ss->dev, "QUEUE(%02X) %d\n", + usb_ss->ep0_data_dir ? USB_DIR_IN : USB_DIR_OUT, + request->length); + + /* send STATUS stage */ + if (request->length == 0 && request->zero == 0) { + spin_lock_irqsave(&usb_ss->lock, flags); + select_ep(usb_ss, 0x00); + if (!usb_ss->hw_configured_flag) { + gadget_writel(usb_ss, &usb_ss->regs->usb_conf, + USB_CONF__CFGSET__MASK); /* SET CONFIGURATION */ + gadget_writel(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__ERDY__MASK | EP_CMD__REQ_CMPL__MASK); + /* wait until configuration set */ + ret = wait_reg_bit_set(usb_ss, &usb_ss->regs->usb_sts, + USB_STS__CFGSTS__MASK, 100); + erdy_sent = 1; + usb_ss->hw_configured_flag = 1; + cdns_enable_l1(usb_ss, 1); + + list_for_each_entry(ep, + &usb_ss->gadget.ep_list, + ep_list) { + + if (ep->enabled) + cdns_ep_run_transfer( + to_usb_ss_ep(ep)); + } + } + if (!erdy_sent) + gadget_writel(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__ERDY__MASK | EP_CMD__REQ_CMPL__MASK); + + cdns_prepare_setup_packet(usb_ss); + request->actual = 0; + usb_ss->status_completion_no_call = true; + usb_ss->pending_status_request = request; + spin_unlock_irqrestore(&usb_ss->lock, flags); + /* + * Since there is no completion interrupt for status stage, + * it needs to call ->completion in software after + * ep0_queue is back. + */ + queue_work(system_freezable_wq, &usb_ss->pending_status_wq); + return 0; + } + + spin_lock_irqsave(&usb_ss->lock, flags); + if (!list_empty(&usb_ss_ep->request_list)) { + dev_err(&usb_ss->dev, + "can't handle multiple requests for ep0\n"); + spin_unlock_irqrestore(&usb_ss->lock, flags); + return -EOPNOTSUPP; + } + + ret = usb_gadget_map_request_by_dev(usb_ss->sysdev, request, + usb_ss->ep0_data_dir); + if (ret) { + spin_unlock_irqrestore(&usb_ss->lock, flags); + dev_err(&usb_ss->dev, "failed to map request\n"); + return -EINVAL; + } + + usb_ss->actual_ep0_request = request; + cdns_ep0_run_transfer(usb_ss, request->dma, request->length, 1); + list_add_tail(&request->list, &usb_ss_ep->request_list); + spin_unlock_irqrestore(&usb_ss->lock, flags); + return ret; +} +/** + * ep_onchip_buffer_alloc - Try to allocate onchip buf for EP + * + * The real allocation will occur during write to EP_CFG register, + * this function is used to check if the 'size' allocation is allowed. + * + * @usb_ss: extended gadget object + * @size: the size (KB) for EP would like to allocate + * @is_in: the direction for EP + * + * Return 0 if the later allocation is allowed or negative value on failure + */ + +static int ep_onchip_buffer_alloc(struct usb_ss_dev *usb_ss, + int size, int is_in) +{ + if (is_in) { + usb_ss->onchip_mem_allocated_size += size; + } else if (!usb_ss->out_mem_is_allocated) { + /* ALL OUT EPs are shared the same chunk onchip memory */ + usb_ss->onchip_mem_allocated_size += size; + usb_ss->out_mem_is_allocated = 1; + } + + if (usb_ss->onchip_mem_allocated_size > CDNS3_ONCHIP_BUF_SIZE) { + usb_ss->onchip_mem_allocated_size -= size; + return -EPERM; + } else { + return 0; + } +} + +/** + * cdns_ep_config Configure hardware endpoint + * @usb_ss_ep: extended endpoint object + */ +static void cdns_ep_config(struct usb_ss_endpoint *usb_ss_ep) +{ + struct usb_ss_dev *usb_ss = usb_ss_ep->usb_ss; + u32 ep_cfg = 0; + u32 max_packet_size = 0; + u32 bEndpointAddress = usb_ss_ep->num | usb_ss_ep->dir; + u32 interrupt_mask = 0; + int is_in = !!usb_ss_ep->dir; + bool is_iso_ep = (usb_ss_ep->type == USB_ENDPOINT_XFER_ISOC); + int default_buf_size = CDNS3_EP_BUF_SIZE; + + dev_dbg(&usb_ss->dev, "%s: %s addr=0x%x\n", __func__, + usb_ss_ep->name, bEndpointAddress); + + if (is_iso_ep) { + ep_cfg = EP_CFG__EPTYPE__WRITE(USB_ENDPOINT_XFER_ISOC); + interrupt_mask = INTERRUPT_MASK; + } else { + ep_cfg = EP_CFG__EPTYPE__WRITE(USB_ENDPOINT_XFER_BULK); + } + + switch (usb_ss->gadget.speed) { + case USB_SPEED_UNKNOWN: + max_packet_size = ENDPOINT_MAX_PACKET_SIZE_0; + break; + case USB_SPEED_LOW: + max_packet_size = ENDPOINT_MAX_PACKET_SIZE_8; + break; + case USB_SPEED_FULL: + max_packet_size = (is_iso_ep ? + ENDPOINT_MAX_PACKET_SIZE_1023 : + ENDPOINT_MAX_PACKET_SIZE_64); + break; + case USB_SPEED_HIGH: + max_packet_size = (is_iso_ep ? + ENDPOINT_MAX_PACKET_SIZE_1024 : + ENDPOINT_MAX_PACKET_SIZE_512); + break; + case USB_SPEED_WIRELESS: + max_packet_size = ENDPOINT_MAX_PACKET_SIZE_512; + break; + case USB_SPEED_SUPER: + max_packet_size = ENDPOINT_MAX_PACKET_SIZE_1024; + break; + case USB_SPEED_SUPER_PLUS: + dev_warn(&usb_ss->dev, "USB 3.1 is not supported\n"); + max_packet_size = ENDPOINT_MAX_PACKET_SIZE_1024; + break; + } + + if (ep_onchip_buffer_alloc(usb_ss, default_buf_size, is_in)) { + dev_err(&usb_ss->dev, "onchip mem is full, ep is invalid\n"); + return; + } + + ep_cfg |= EP_CFG__MAXPKTSIZE__WRITE(max_packet_size) | + EP_CFG__BUFFERING__WRITE(default_buf_size - 1) | + EP_CFG__MAXBURST__WRITE(usb_ss_ep->endpoint.maxburst); + + select_ep(usb_ss, bEndpointAddress); + gadget_writel(usb_ss, &usb_ss->regs->ep_cfg, ep_cfg); + gadget_writel(usb_ss, &usb_ss->regs->ep_sts_en, + EP_STS_EN__TRBERREN__MASK | interrupt_mask); + + /* enable interrupt for selected endpoint */ + ep_cfg = gadget_readl(usb_ss, &usb_ss->regs->ep_ien); + ep_cfg |= CAST_EP_ADDR_TO_BIT_POS(bEndpointAddress); + gadget_writel(usb_ss, &usb_ss->regs->ep_ien, ep_cfg); +} + +/** + * usb_ss_gadget_ep_enable Enable endpoint + * @ep: endpoint object + * @desc: endpoint descriptor + * + * Returns 0 on success, error code elsewhere + */ +static int usb_ss_gadget_ep_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + struct usb_ss_endpoint *usb_ss_ep; + struct usb_ss_dev *usb_ss; + unsigned long flags; + int ret; + u32 ep_cfg; + + usb_ss_ep = to_usb_ss_ep(ep); + usb_ss = usb_ss_ep->usb_ss; + + if (!ep || !desc || desc->bDescriptorType != USB_DT_ENDPOINT) { + dev_err(&usb_ss->dev, "usb-ss: invalid parameters\n"); + return -EINVAL; + } + + if (!desc->wMaxPacketSize) { + dev_err(&usb_ss->dev, "usb-ss: missing wMaxPacketSize\n"); + return -EINVAL; + } + + ret = usb_ss_allocate_trb_pool(usb_ss_ep); + if (ret) + return ret; + + dev_dbg(&usb_ss->dev, "Enabling endpoint: %s, addr=0x%x\n", + ep->name, desc->bEndpointAddress); + spin_lock_irqsave(&usb_ss->lock, flags); + select_ep(usb_ss, desc->bEndpointAddress); + gadget_writel(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__EPRST__MASK); + ret = wait_reg_bit_clear(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__EPRST__MASK, 100); + ep_cfg = gadget_readl(usb_ss, &usb_ss->regs->ep_cfg); + ep_cfg |= EP_CFG__ENABLE__MASK; + gadget_writel(usb_ss, &usb_ss->regs->ep_cfg, ep_cfg); + + ep->enabled = 1; + ep->desc = desc; + usb_ss_ep->hw_pending_flag = 0; + usb_ss_ep->stalled_flag = 0; + spin_unlock_irqrestore(&usb_ss->lock, flags); + return 0; +} + +/* Find correct direction for HW endpoint according to description */ +static int ep_dir_is_correct(struct usb_endpoint_descriptor *desc, + struct usb_ss_endpoint *usb_ss_ep) +{ + return (usb_ss_ep->endpoint.caps.dir_in && + !!(desc->bEndpointAddress & USB_DIR_IN)) + || (usb_ss_ep->endpoint.caps.dir_out + && ((desc->bEndpointAddress & 0x80) == USB_DIR_OUT)); +} + +static struct usb_ss_endpoint *find_available_ss_ep( + struct usb_ss_dev *usb_ss, + struct usb_endpoint_descriptor *desc) +{ + struct usb_ep *ep; + struct usb_ss_endpoint *usb_ss_ep; + + list_for_each_entry(ep, &usb_ss->gadget.ep_list, ep_list) { + unsigned long num; + int ret; + /* ep name pattern likes epXin or epXout */ + char c[2] = {ep->name[2], '\0'}; + + ret = kstrtoul(c, 10, &num); + if (ret) + return ERR_PTR(ret); + + usb_ss_ep = to_usb_ss_ep(ep); + if (ep_dir_is_correct(desc, usb_ss_ep)) { + if (!usb_ss_ep->used) { + usb_ss_ep->num = num; + usb_ss_ep->used = true; + return usb_ss_ep; + } + } + } + return ERR_PTR(-ENOENT); +} + +static struct usb_ep *usb_ss_gadget_match_ep(struct usb_gadget *gadget, + struct usb_endpoint_descriptor *desc, + struct usb_ss_ep_comp_descriptor *comp_desc) +{ + struct usb_ss_dev *usb_ss = gadget_to_usb_ss(gadget); + struct usb_ss_endpoint *usb_ss_ep; + unsigned long flags; + + usb_ss_ep = find_available_ss_ep(usb_ss, desc); + if (IS_ERR(usb_ss_ep)) { + dev_err(&usb_ss->dev, "no available ep\n"); + return NULL; + } + + dev_dbg(&usb_ss->dev, "match endpoint: %s\n", usb_ss_ep->name); + spin_lock_irqsave(&usb_ss->lock, flags); + usb_ss_ep->endpoint.desc = desc; + usb_ss_ep->dir = usb_endpoint_dir_in(desc) ? USB_DIR_IN : USB_DIR_OUT; + usb_ss_ep->type = usb_endpoint_type(desc); + + list_add_tail(&usb_ss_ep->ep_match_pending_list, + &usb_ss->ep_match_list); + spin_unlock_irqrestore(&usb_ss->lock, flags); + return &usb_ss_ep->endpoint; +} + +static void usb_ss_free_trb_pool(struct usb_ss_endpoint *usb_ss_ep) +{ + struct usb_ss_dev *usb_ss = usb_ss_ep->usb_ss; + + if (usb_ss_ep->trb_pool) { + dma_free_coherent(usb_ss->sysdev, + sizeof(struct usb_ss_trb) * USB_SS_TRBS_NUM, + usb_ss_ep->trb_pool, usb_ss_ep->trb_pool_dma); + usb_ss_ep->trb_pool = NULL; + } + + if (usb_ss_ep->cpu_addr) { + dma_free_coherent(usb_ss->sysdev, CDNS3_UNALIGNED_BUF_SIZE, + usb_ss_ep->cpu_addr, usb_ss_ep->dma_addr); + usb_ss_ep->cpu_addr = NULL; + } +} + +/** + * usb_ss_gadget_ep_disable Disable endpoint + * @ep: endpoint object + * + * Returns 0 on success, error code elsewhere + */ +static int usb_ss_gadget_ep_disable(struct usb_ep *ep) +{ + struct usb_ss_endpoint *usb_ss_ep; + struct usb_ss_dev *usb_ss; + unsigned long flags; + int ret = 0; + struct usb_request *request; + u32 ep_cfg; + + if (!ep) { + pr_debug("usb-ss: invalid parameters\n"); + return -EINVAL; + } + + usb_ss_ep = to_usb_ss_ep(ep); + usb_ss = usb_ss_ep->usb_ss; + + spin_lock_irqsave(&usb_ss->lock, flags); + if (!usb_ss->start_gadget) { + dev_dbg(&usb_ss->dev, + "Disabling endpoint at disconnection: %s\n", ep->name); + spin_unlock_irqrestore(&usb_ss->lock, flags); + return 0; + } + + dev_dbg(&usb_ss->dev, + "Disabling endpoint: %s\n", ep->name); + + select_ep(usb_ss, ep->desc->bEndpointAddress); + ret = cdns_data_flush(usb_ss_ep); + while (!list_empty(&usb_ss_ep->request_list)) { + + request = next_request(&usb_ss_ep->request_list); + usb_gadget_unmap_request_by_dev(usb_ss->sysdev, request, + ep->desc->bEndpointAddress & USB_DIR_IN); + request->status = -ESHUTDOWN; + list_del(&request->list); + spin_unlock(&usb_ss->lock); + usb_gadget_giveback_request(ep, request); + spin_lock(&usb_ss->lock); + } + + ep_cfg = gadget_readl(usb_ss, &usb_ss->regs->ep_cfg); + ep_cfg &= ~EP_CFG__ENABLE__MASK; + gadget_writel(usb_ss, &usb_ss->regs->ep_cfg, ep_cfg); + ep->desc = NULL; + ep->enabled = 0; + + spin_unlock_irqrestore(&usb_ss->lock, flags); + + return ret; +} + +/** + * usb_ss_gadget_ep_alloc_request Allocates request + * @ep: endpoint object associated with request + * @gfp_flags: gfp flags + * + * Returns allocated request address, NULL on allocation error + */ +static struct usb_request *usb_ss_gadget_ep_alloc_request(struct usb_ep *ep, + gfp_t gfp_flags) +{ + struct usb_request *request; + + request = kzalloc(sizeof(struct usb_request), gfp_flags); + if (!request) + return NULL; + + return request; +} + +/** + * usb_ss_gadget_ep_free_request Free memory occupied by request + * @ep: endpoint object associated with request + * @request: request to free memory + */ +static void usb_ss_gadget_ep_free_request(struct usb_ep *ep, + struct usb_request *request) +{ + kfree(request); +} + +/** + * usb_ss_gadget_ep_queue Transfer data on endpoint + * @ep: endpoint object + * @request: request object + * @gfp_flags: gfp flags + * + * Returns 0 on success, error code elsewhere + */ +static int __usb_ss_gadget_ep_queue(struct usb_ep *ep, + struct usb_request *request, gfp_t gfp_flags) +{ + struct usb_ss_endpoint *usb_ss_ep = + to_usb_ss_ep(ep); + struct usb_ss_dev *usb_ss = usb_ss_ep->usb_ss; + int ret = 0; + int empty_list = 0; + + request->actual = 0; + request->status = -EINPROGRESS; + + dev_dbg(&usb_ss->dev, + "Queuing endpoint: %s\n", usb_ss_ep->name); + + dev_dbg(&usb_ss->dev, "QUEUE(%02X) %d\n", + ep->desc->bEndpointAddress, request->length); + + ret = usb_gadget_map_request_by_dev(usb_ss->sysdev, request, + ep->desc->bEndpointAddress & USB_DIR_IN); + + if (ret) + return ret; + + empty_list = list_empty(&usb_ss_ep->request_list); + list_add_tail(&request->list, &usb_ss_ep->request_list); + + if (!usb_ss->hw_configured_flag) + return 0; + + if (empty_list) { + if (!usb_ss_ep->stalled_flag) + ret = cdns_ep_run_transfer(usb_ss_ep); + } + + return ret; +} + +static int usb_ss_gadget_ep_queue(struct usb_ep *ep, + struct usb_request *request, gfp_t gfp_flags) +{ + struct usb_ss_endpoint *usb_ss_ep = to_usb_ss_ep(ep); + struct usb_ss_dev *usb_ss = usb_ss_ep->usb_ss; + struct usb_request *zlp_request; + unsigned long flags; + int ret; + + spin_lock_irqsave(&usb_ss->lock, flags); + + ret = __usb_ss_gadget_ep_queue(ep, request, gfp_flags); + if (ret == 0 && request->zero && request->length && + (request->length % ep->maxpacket == 0)) { + zlp_request = usb_ss_gadget_ep_alloc_request(ep, GFP_ATOMIC); + zlp_request->length = 0; + zlp_request->buf = usb_ss->zlp_buf; + + dev_dbg(&usb_ss->dev, "Queuing ZLP for endpoint: %s\n", + usb_ss_ep->name); + ret = __usb_ss_gadget_ep_queue(ep, zlp_request, gfp_flags); + } + + spin_unlock_irqrestore(&usb_ss->lock, flags); + return ret; +} + +/** + * usb_ss_gadget_ep_dequeue Remove request from transfer queue + * @ep: endpoint object associated with request + * @request: request object + * + * Returns 0 on success, error code elsewhere + */ +static int usb_ss_gadget_ep_dequeue(struct usb_ep *ep, + struct usb_request *request) +{ + struct usb_ss_endpoint *usb_ss_ep = + to_usb_ss_ep(ep); + struct usb_ss_dev *usb_ss = usb_ss_ep->usb_ss; + unsigned long flags; + struct usb_request *req, *req_temp; + int ret = 0; + + if (ep == NULL || request == NULL || ep->desc == NULL) + return -EINVAL; + + spin_lock_irqsave(&usb_ss->lock, flags); + dev_dbg(&usb_ss->dev, "DEQUEUE(%02X) %d\n", + ep->address, request->length); + + select_ep(usb_ss, ep->desc->bEndpointAddress); + if (usb_ss->start_gadget) + ret = cdns_data_flush(usb_ss_ep); + + list_for_each_entry_safe(req, req_temp, + &usb_ss_ep->request_list, list) { + if (request == req) { + request->status = -ECONNRESET; + usb_gadget_unmap_request_by_dev(usb_ss->sysdev, request, + ep->address & USB_DIR_IN); + list_del_init(&request->list); + if (request->complete) { + spin_unlock(&usb_ss->lock); + usb_gadget_giveback_request + (&usb_ss_ep->endpoint, request); + spin_lock(&usb_ss->lock); + } + break; + } + } + + spin_unlock_irqrestore(&usb_ss->lock, flags); + return ret; +} + +/** + * usb_ss_gadget_ep_set_halt Sets/clears stall on selected endpoint + * @ep: endpoint object to set/clear stall on + * @value: 1 for set stall, 0 for clear stall + * + * Returns 0 on success, error code elsewhere + */ +static int usb_ss_gadget_ep_set_halt(struct usb_ep *ep, int value) +{ + struct usb_ss_endpoint *usb_ss_ep = + to_usb_ss_ep(ep); + struct usb_ss_dev *usb_ss = usb_ss_ep->usb_ss; + unsigned long flags; + int ret = 0; + + /* return error when endpoint disabled */ + if (!ep->enabled) + return -EPERM; + + /* if actual transfer is pending defer setting stall on this endpoint */ + if (usb_ss_ep->hw_pending_flag && value) { + usb_ss_ep->stalled_flag = 1; + return 0; + } + + dev_dbg(&usb_ss->dev, "HALT(%02X) %d\n", ep->address, value); + + spin_lock_irqsave(&usb_ss->lock, flags); + + select_ep(usb_ss, ep->desc->bEndpointAddress); + if (value) { + cdns_ep_stall_flush(usb_ss_ep); + } else { + /* + * TODO: + * epp->wedgeFlag = 0; + */ + usb_ss_ep->wedge_flag = 0; + gadget_writel(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__CSTALL__MASK | EP_CMD__EPRST__MASK); + /* wait for EPRST cleared */ + ret = wait_reg_bit_clear(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__EPRST__MASK, 100); + usb_ss_ep->stalled_flag = 0; + } + usb_ss_ep->hw_pending_flag = 0; + spin_unlock_irqrestore(&usb_ss->lock, flags); + + return ret; +} + +/** + * usb_ss_gadget_ep_set_wedge Set wedge on selected endpoint + * @ep: endpoint object + * + * Returns 0 + */ +static int usb_ss_gadget_ep_set_wedge(struct usb_ep *ep) +{ + struct usb_ss_endpoint *usb_ss_ep = to_usb_ss_ep(ep); + struct usb_ss_dev *usb_ss = usb_ss_ep->usb_ss; + + dev_dbg(&usb_ss->dev, "WEDGE(%02X)\n", ep->address); + usb_ss_gadget_ep_set_halt(ep, 1); + usb_ss_ep->wedge_flag = 1; + return 0; +} + +static const struct usb_ep_ops usb_ss_gadget_ep0_ops = { + .enable = usb_ss_gadget_ep0_enable, + .disable = usb_ss_gadget_ep0_disable, + .alloc_request = usb_ss_gadget_ep_alloc_request, + .free_request = usb_ss_gadget_ep_free_request, + .queue = usb_ss_gadget_ep0_queue, + .dequeue = usb_ss_gadget_ep_dequeue, + .set_halt = usb_ss_gadget_ep0_set_halt, + .set_wedge = usb_ss_gadget_ep_set_wedge, +}; + +static const struct usb_ep_ops usb_ss_gadget_ep_ops = { + .enable = usb_ss_gadget_ep_enable, + .disable = usb_ss_gadget_ep_disable, + .alloc_request = usb_ss_gadget_ep_alloc_request, + .free_request = usb_ss_gadget_ep_free_request, + .queue = usb_ss_gadget_ep_queue, + .dequeue = usb_ss_gadget_ep_dequeue, + .set_halt = usb_ss_gadget_ep_set_halt, + .set_wedge = usb_ss_gadget_ep_set_wedge, +}; + +/** + * usb_ss_gadget_get_frame Returns number of actual ITP frame + * @gadget: gadget object + * + * Returns number of actual ITP frame + */ +static int usb_ss_gadget_get_frame(struct usb_gadget *gadget) +{ + struct usb_ss_dev *usb_ss = gadget_to_usb_ss(gadget); + + dev_dbg(&usb_ss->dev, "usb_ss_gadget_get_frame\n"); + return gadget_readl(usb_ss, &usb_ss->regs->usb_iptn); +} + +static int usb_ss_gadget_wakeup(struct usb_gadget *gadget) +{ + struct usb_ss_dev *usb_ss = gadget_to_usb_ss(gadget); + + dev_dbg(&usb_ss->dev, "usb_ss_gadget_wakeup\n"); + return 0; +} + +static int usb_ss_gadget_set_selfpowered(struct usb_gadget *gadget, + int is_selfpowered) +{ + struct usb_ss_dev *usb_ss = gadget_to_usb_ss(gadget); + unsigned long flags; + + dev_dbg(&usb_ss->dev, "usb_ss_gadget_set_selfpowered: %d\n", + is_selfpowered); + + spin_lock_irqsave(&usb_ss->lock, flags); + gadget->is_selfpowered = !!is_selfpowered; + spin_unlock_irqrestore(&usb_ss->lock, flags); + return 0; +} + +static int usb_ss_gadget_pullup(struct usb_gadget *gadget, int is_on) +{ + struct usb_ss_dev *usb_ss = gadget_to_usb_ss(gadget); + + if (!usb_ss->start_gadget) + return 0; + + dev_dbg(&usb_ss->dev, "usb_ss_gadget_pullup: %d\n", is_on); + + if (is_on) + gadget_writel(usb_ss, &usb_ss->regs->usb_conf, + USB_CONF__DEVEN__MASK); + else + gadget_writel(usb_ss, &usb_ss->regs->usb_conf, + USB_CONF__DEVDS__MASK); + + return 0; +} + +/** + * usb_ss_gadget_udc_start Gadget start + * @gadget: gadget object + * @driver: driver which operates on this gadget + * + * Returns 0 on success, error code elsewhere + */ +static int usb_ss_gadget_udc_start(struct usb_gadget *gadget, + struct usb_gadget_driver *driver) +{ + struct usb_ss_dev *usb_ss = gadget_to_usb_ss(gadget); + unsigned long flags; + + if (usb_ss->gadget_driver) { + dev_err(&usb_ss->dev, "%s is already bound to %s\n", + usb_ss->gadget.name, + usb_ss->gadget_driver->driver.name); + return -EBUSY; + } + + dev_dbg(&usb_ss->dev, "%s begins\n", __func__); + + spin_lock_irqsave(&usb_ss->lock, flags); + usb_ss->gadget_driver = driver; + if (!usb_ss->start_gadget) { + spin_unlock_irqrestore(&usb_ss->lock, flags); + return 0; + } + + __cdns3_gadget_start(usb_ss); + spin_unlock_irqrestore(&usb_ss->lock, flags); + dev_dbg(&usb_ss->dev, "%s ends\n", __func__); + return 0; +} + +/** + * usb_ss_gadget_udc_stop Stops gadget + * @gadget: gadget object + * + * Returns 0 + */ +static int usb_ss_gadget_udc_stop(struct usb_gadget *gadget) +{ + struct usb_ss_dev *usb_ss = gadget_to_usb_ss(gadget); + struct usb_ep *ep; + struct usb_ss_endpoint *usb_ss_ep, *temp_ss_ep; + int i; + u32 bEndpointAddress; + int ret = 0; + + usb_ss->gadget_driver = NULL; + list_for_each_entry_safe(usb_ss_ep, temp_ss_ep, + &usb_ss->ep_match_list, ep_match_pending_list) { + list_del(&usb_ss_ep->ep_match_pending_list); + usb_ss_ep->used = false; + } + + usb_ss->onchip_mem_allocated_size = 0; + usb_ss->out_mem_is_allocated = 0; + usb_ss->gadget.speed = USB_SPEED_UNKNOWN; + for (i = 0; i < usb_ss->ep_nums ; i++) + usb_ss_free_trb_pool(usb_ss->eps[i]); + + if (!usb_ss->start_gadget) + return 0; + + list_for_each_entry(ep, &usb_ss->gadget.ep_list, ep_list) { + usb_ss_ep = to_usb_ss_ep(ep); + bEndpointAddress = usb_ss_ep->num | usb_ss_ep->dir; + select_ep(usb_ss, bEndpointAddress); + gadget_writel(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__EPRST__MASK); + ret = wait_reg_bit_clear(usb_ss, &usb_ss->regs->ep_cmd, + EP_CMD__EPRST__MASK, 100); + } + + /* disable interrupt for device */ + gadget_writel(usb_ss, &usb_ss->regs->usb_ien, 0); + gadget_writel(usb_ss, &usb_ss->regs->usb_conf, USB_CONF__DEVDS__MASK); + + return ret; +} + +static const struct usb_gadget_ops usb_ss_gadget_ops = { + .get_frame = usb_ss_gadget_get_frame, + .wakeup = usb_ss_gadget_wakeup, + .set_selfpowered = usb_ss_gadget_set_selfpowered, + .pullup = usb_ss_gadget_pullup, + .udc_start = usb_ss_gadget_udc_start, + .udc_stop = usb_ss_gadget_udc_stop, + .match_ep = usb_ss_gadget_match_ep, +}; + +/** + * usb_ss_init_ep Initializes software endpoints of gadget + * @usb_ss: extended gadget object + * + * Returns 0 on success, error code elsewhere + */ +static int usb_ss_init_ep(struct usb_ss_dev *usb_ss) +{ + struct usb_ss_endpoint *usb_ss_ep; + u32 ep_enabled_reg, iso_ep_reg, bulk_ep_reg; + int i; + int ep_reg_pos, ep_dir, ep_number; + int found_endpoints = 0; + + /* Read it from USB_CAP3 to USB_CAP5 */ + ep_enabled_reg = 0x00ff00ff; + iso_ep_reg = 0x00fe00fe; + bulk_ep_reg = 0x00fe00fe; + + dev_dbg(&usb_ss->dev, "Initializing non-zero endpoints\n"); + + for (i = 0; i < USB_SS_ENDPOINTS_MAX_COUNT; i++) { + ep_number = (i / 2) + 1; + ep_dir = i % 2; + ep_reg_pos = (16 * ep_dir) + ep_number; + + if (!(ep_enabled_reg & (1uL << ep_reg_pos))) + continue; + + /* create empty endpoint object */ + usb_ss_ep = devm_kzalloc(&usb_ss->dev, sizeof(*usb_ss_ep), + GFP_KERNEL); + if (!usb_ss_ep) + return -ENOMEM; + + /* set parent of endpoint object */ + usb_ss_ep->usb_ss = usb_ss; + + /* set index of endpoint in endpoints container */ + usb_ss->eps[found_endpoints++] = usb_ss_ep; + + /* set name of endpoint */ + snprintf(usb_ss_ep->name, sizeof(usb_ss_ep->name), "ep%d%s", + ep_number, !!ep_dir ? "in" : "out"); + usb_ss_ep->endpoint.name = usb_ss_ep->name; + dev_dbg(&usb_ss->dev, "Initializing endpoint: %s\n", + usb_ss_ep->name); + + usb_ep_set_maxpacket_limit(&usb_ss_ep->endpoint, + ENDPOINT_MAX_PACKET_LIMIT); + usb_ss_ep->endpoint.max_streams = ENDPOINT_MAX_STREAMS; + usb_ss_ep->endpoint.ops = &usb_ss_gadget_ep_ops; + if (ep_dir) + usb_ss_ep->endpoint.caps.dir_in = 1; + else + usb_ss_ep->endpoint.caps.dir_out = 1; + + /* check endpoint type */ + if (iso_ep_reg & (1uL << ep_reg_pos)) + usb_ss_ep->endpoint.caps.type_iso = 1; + + if (bulk_ep_reg & (1uL << ep_reg_pos)) { + usb_ss_ep->endpoint.caps.type_bulk = 1; + usb_ss_ep->endpoint.caps.type_int = 1; + usb_ss_ep->endpoint.maxburst = CDNS3_EP_BUF_SIZE - 1; + } + + list_add_tail(&usb_ss_ep->endpoint.ep_list, + &usb_ss->gadget.ep_list); + INIT_LIST_HEAD(&usb_ss_ep->request_list); + INIT_LIST_HEAD(&usb_ss_ep->ep_match_pending_list); + } + + usb_ss->ep_nums = found_endpoints; + return 0; +} + +/** + * usb_ss_init_ep0 Initializes software endpoint 0 of gadget + * @usb_ss: extended gadget object + * + * Returns 0 on success, error code elsewhere + */ +static int usb_ss_init_ep0(struct usb_ss_dev *usb_ss) +{ + struct usb_ss_endpoint *ep0; + + dev_dbg(&usb_ss->dev, "Initializing EP0\n"); + ep0 = devm_kzalloc(&usb_ss->dev, sizeof(struct usb_ss_endpoint), + GFP_KERNEL); + + if (!ep0) + return -ENOMEM; + + /* fill CDNS fields */ + ep0->usb_ss = usb_ss; + sprintf(ep0->name, "ep0"); + + /* fill linux fields */ + ep0->endpoint.ops = &usb_ss_gadget_ep0_ops; + ep0->endpoint.maxburst = 1; + usb_ep_set_maxpacket_limit(&ep0->endpoint, ENDPOINT0_MAX_PACKET_LIMIT); + ep0->endpoint.address = 0; + ep0->endpoint.enabled = 1; + ep0->endpoint.caps.type_control = 1; + ep0->endpoint.caps.dir_in = 1; + ep0->endpoint.caps.dir_out = 1; + ep0->endpoint.name = ep0->name; + ep0->endpoint.desc = &cdns3_gadget_ep0_desc; + usb_ss->gadget.ep0 = &ep0->endpoint; + INIT_LIST_HEAD(&ep0->request_list); + + return 0; +} + +static void cdns3_gadget_release(struct device *dev) +{ + struct usb_ss_dev *usb_ss = container_of(dev, struct usb_ss_dev, dev); + + dev_dbg(dev, "releasing '%s'\n", dev_name(dev)); + kfree(usb_ss); +} + +static int __cdns3_gadget_init(struct cdns3 *cdns) +{ + struct usb_ss_dev *usb_ss; + int ret; + struct device *dev; + + usb_ss = kzalloc(sizeof(*usb_ss), GFP_KERNEL); + if (!usb_ss) + return -ENOMEM; + + dev = &usb_ss->dev; + dev->release = cdns3_gadget_release; + dev->parent = cdns->dev; + dev_set_name(dev, "gadget-cdns3"); + cdns->gadget_dev = dev; + usb_ss->sysdev = cdns->dev; + ret = device_register(dev); + if (ret) + goto err1; + + usb_ss->regs = cdns->dev_regs; + + /* fill gadget fields */ + usb_ss->gadget.ops = &usb_ss_gadget_ops; + usb_ss->gadget.max_speed = USB_SPEED_SUPER; + usb_ss->gadget.speed = USB_SPEED_UNKNOWN; + usb_ss->gadget.name = "usb-ss-gadget"; + usb_ss->gadget.sg_supported = 1; + usb_ss->is_connected = 0; + spin_lock_init(&usb_ss->lock); + INIT_WORK(&usb_ss->pending_status_wq, pending_setup_status_handler); + + usb_ss->in_standby_mode = 1; + + /* initialize endpoint container */ + INIT_LIST_HEAD(&usb_ss->gadget.ep_list); + INIT_LIST_HEAD(&usb_ss->ep_match_list); + ret = usb_ss_init_ep0(usb_ss); + if (ret) { + dev_err(dev, "Failed to create endpoint 0\n"); + ret = -ENOMEM; + goto err2; + } + + ret = usb_ss_init_ep(usb_ss); + if (ret) { + dev_err(dev, "Failed to create non zero endpoints\n"); + ret = -ENOMEM; + goto err2; + } + + /* allocate memory for default endpoint TRB */ + usb_ss->trb_ep0 = (u32 *)dma_alloc_coherent(usb_ss->sysdev, 20, + &usb_ss->trb_ep0_dma, GFP_DMA); + if (!usb_ss->trb_ep0) { + dev_err(dev, "Failed to allocate memory for ep0 TRB\n"); + ret = -ENOMEM; + goto err2; + } + + /* allocate memory for setup packet buffer */ + usb_ss->setup = (u8 *)dma_alloc_coherent(usb_ss->sysdev, 8, + &usb_ss->setup_dma, + GFP_DMA); + if (!usb_ss->setup) { + dev_err(dev, "Failed to allocate memory for SETUP buffer\n"); + ret = -ENOMEM; + goto err3; + } + + /* add USB gadget device */ + ret = usb_add_gadget_udc(&usb_ss->dev, &usb_ss->gadget); + if (ret < 0) { + dev_err(dev, "Failed to register USB device controller\n"); + goto err4; + } + + usb_ss->zlp_buf = kzalloc(ENDPOINT_ZLP_BUF_SIZE, GFP_KERNEL); + if (!usb_ss->zlp_buf) { + ret = -ENOMEM; + goto err4; + } + + return 0; +err4: + dma_free_coherent(usb_ss->sysdev, 8, usb_ss->setup, + usb_ss->setup_dma); +err3: + dma_free_coherent(usb_ss->sysdev, 20, usb_ss->trb_ep0, + usb_ss->trb_ep0_dma); +err2: + device_del(dev); +err1: + put_device(dev); + cdns->gadget_dev = NULL; + return ret; +} + +/** + * cdns3_gadget_remove: parent must call this to remove UDC + * + * cdns: cdns3 instance + * + */ +void cdns3_gadget_remove(struct cdns3 *cdns) +{ + struct usb_ss_dev *usb_ss; + + if (!cdns->roles[CDNS3_ROLE_GADGET]) + return; + + usb_ss = container_of(cdns->gadget_dev, struct usb_ss_dev, dev); + usb_del_gadget_udc(&usb_ss->gadget); + dma_free_coherent(usb_ss->sysdev, 8, usb_ss->setup, usb_ss->setup_dma); + dma_free_coherent(usb_ss->sysdev, 20, usb_ss->trb_ep0, + usb_ss->trb_ep0_dma); + device_unregister(cdns->gadget_dev); + cdns->gadget_dev = NULL; + kfree(usb_ss->zlp_buf); +} + +static void __cdns3_gadget_start(struct usb_ss_dev *usb_ss) +{ + + /* configure endpoint 0 hardware */ + cdns_ep0_config(usb_ss); + + /* enable interrupts for endpoint 0 (in and out) */ + gadget_writel(usb_ss, &usb_ss->regs->ep_ien, + EP_IEN__EOUTEN0__MASK | EP_IEN__EINEN0__MASK); + + /* enable interrupt for device */ + gadget_writel(usb_ss, &usb_ss->regs->usb_ien, + USB_IEN__U2RESIEN__MASK + | USB_ISTS__DIS2I__MASK + | USB_IEN__CON2IEN__MASK + | USB_IEN__UHRESIEN__MASK + | USB_IEN__UWRESIEN__MASK + | USB_IEN__DISIEN__MASK + | USB_IEN__CONIEN__MASK + | USB_IEN__U3EXTIEN__MASK + | USB_IEN__L2ENTIEN__MASK + | USB_IEN__L2EXTIEN__MASK); + + gadget_writel(usb_ss, &usb_ss->regs->usb_conf, + USB_CONF__CLK2OFFDS__MASK + /* | USB_CONF__USB3DIS__MASK */ + | USB_CONF__L1DS__MASK); + + gadget_writel(usb_ss, &usb_ss->regs->usb_conf, + USB_CONF__U1DS__MASK + | USB_CONF__U2DS__MASK + ); + + gadget_writel(usb_ss, &usb_ss->regs->usb_conf, USB_CONF__DEVEN__MASK); + + gadget_writel(usb_ss, &usb_ss->regs->dbg_link1, + DBG_LINK1__LFPS_MIN_GEN_U1_EXIT_SET__MASK | + DBG_LINK1__LFPS_MIN_GEN_U1_EXIT__WRITE(0x3C)); +} + +static int cdns3_gadget_start(struct cdns3 *cdns) +{ + struct usb_ss_dev *usb_ss = container_of(cdns->gadget_dev, + struct usb_ss_dev, dev); + unsigned long flags; + + dev_dbg(&usb_ss->dev, "%s begins\n", __func__); + + pm_runtime_get_sync(cdns->dev); + spin_lock_irqsave(&usb_ss->lock, flags); + usb_ss->start_gadget = 1; + if (!usb_ss->gadget_driver) { + spin_unlock_irqrestore(&usb_ss->lock, flags); + return 0; + } + + __cdns3_gadget_start(usb_ss); + usb_ss->in_standby_mode = 0; + spin_unlock_irqrestore(&usb_ss->lock, flags); + dev_dbg(&usb_ss->dev, "%s ends\n", __func__); + return 0; +} + +static void __cdns3_gadget_stop(struct cdns3 *cdns) +{ + struct usb_ss_dev *usb_ss; + unsigned long flags; + + usb_ss = container_of(cdns->gadget_dev, struct usb_ss_dev, dev); + if (usb_ss->gadget_driver) + usb_ss->gadget_driver->disconnect(&usb_ss->gadget); + usb_gadget_disconnect(&usb_ss->gadget); + spin_lock_irqsave(&usb_ss->lock, flags); + usb_ss->gadget.speed = USB_SPEED_UNKNOWN; + /* disable interrupt for device */ + gadget_writel(usb_ss, &usb_ss->regs->usb_ien, 0); + gadget_writel(usb_ss, &usb_ss->regs->usb_conf, USB_CONF__DEVDS__MASK); + usb_ss->start_gadget = 0; + spin_unlock_irqrestore(&usb_ss->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 usb_ss_dev *usb_ss = container_of(cdns->gadget_dev, + struct usb_ss_dev, dev); + unsigned long flags; + + spin_lock_irqsave(&usb_ss->lock, flags); + usb_ss->start_gadget = 1; + if (!usb_ss->gadget_driver) { + spin_unlock_irqrestore(&usb_ss->lock, flags); + return 0; + } + + __cdns3_gadget_start(usb_ss); + usb_ss->in_standby_mode = 0; + spin_unlock_irqrestore(&usb_ss->lock, flags); + return 0; +} + +/** + * cdns3_gadget_init - initialize device structure + * + * cdns: cdns3 instance + * + * This function initializes the gadget. + */ +int cdns3_gadget_init(struct cdns3 *cdns) +{ + struct cdns3_role_driver *rdrv; + + rdrv = devm_kzalloc(cdns->dev, sizeof(*rdrv), GFP_KERNEL); + if (!rdrv) + return -ENOMEM; + + rdrv->start = cdns3_gadget_start; + rdrv->stop = cdns3_gadget_stop; + rdrv->suspend = cdns3_gadget_suspend; + rdrv->resume = cdns3_gadget_resume; + rdrv->irq = cdns_irq_handler_thread; + rdrv->name = "gadget"; + cdns->roles[CDNS3_ROLE_GADGET] = rdrv; + return __cdns3_gadget_init(cdns); +} diff --git a/drivers/usb/cdns3/gadget.h b/drivers/usb/cdns3/gadget.h new file mode 100644 index 000000000000..f0a576d8a4ea --- /dev/null +++ b/drivers/usb/cdns3/gadget.h @@ -0,0 +1,225 @@ +/** + * gadget.h - Cadence USB3 device Controller Core file + * + * Copyright (C) 2016 Cadence Design Systems - http://www.cadence.com + * Copyright 2017 NXP + * + * Authors: Pawel Jez <pjez@cadence.com>, + * Konrad Kociolek <konrad@cadence.com>, + * 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 __DRIVERS_CDNS3_GADGET +#define __DRIVERS_CDNS3_GADGET + +#include "dev-regs-map.h" + +#if IS_ENABLED(CONFIG_USB_CDNS_MISC) +#include "cdns_misc.h" +#endif + +#define gadget_to_usb_ss(g) \ + (container_of(g, struct usb_ss_dev, gadget)) + +#define to_usb_ss_ep(ep) \ + (container_of(ep, struct usb_ss_endpoint, endpoint)) + +#define ep_to_usb_ss_ep(ep) \ + (container_of(ep, struct usb_ss_endpoint, endpoint)) + +/*-------------------------------------------------------------------------*/ +/* TRB macros */ + +/* Common TRB fields */ +#define TRB_SET_CYCLE_BIT 1uL +#define TRB_SET_CHAIN_BIT 0x10 + +/* offset 0 */ +#define TRB_DATA_BUFFER_POINTER_MASK 0xFFFFFFFF +#define TRB_SET_DATA_BUFFER_POINTER(p) (p & TRB_DATA_BUFFER_POINTER_MASK) + +/* offset 4 */ +#define TRB_TRANSFER_LENGTH_MASK 0x1FFFF +#define TRB_SET_TRANSFER_LENGTH(l) (l & TRB_TRANSFER_LENGTH_MASK) + +#define TRB_BURST_LENGTH_MASK 0xFF +#define TRB_SET_BURST_LENGTH(l) ((l & TRB_BURST_LENGTH_MASK) << 24) + +/* offset 8 */ +#define TRB_SET_INT_ON_SHORT_PACKET 0x04 +#define TRB_SET_FIFO_MODE 0x08 +#define TRB_SET_INT_ON_COMPLETION 0x20 + +#define TRB_TYPE_NORMAL 0x400 + +#define TRB_STREAM_ID_MASK 0xFFFF +#define TRB_SET_STREAM_ID(sid) ((sid & TRB_STREAM_ID_MASK) << 16) + +/*-------------------------------------------------------------------------*/ +/* Driver numeric constants */ + + +#define DEVICE_ADDRESS_MAX 127 + +/* Endpoint init values */ +#define ENDPOINT_MAX_PACKET_LIMIT 1024 +#define ENDPOINT_MAX_STREAMS 15 + +#define ENDPOINT0_MAX_PACKET_LIMIT 512 + +/* All endpoints except EP0 */ +#define USB_SS_ENDPOINTS_MAX_COUNT 30 + +#define USB_SS_TRBS_NUM 32 + +/* Standby mode */ +#define STB_CLK_SWITCH_DONE_MASK 0x200 +#define STB_CLK_SWITCH_EN_MASK 0x100 +#define STB_CLK_SWITCH_EN_SHIFT 8 + +#define ENDPOINT_MAX_PACKET_SIZE_0 0 +#define ENDPOINT_MAX_PACKET_SIZE_8 8 +#define ENDPOINT_MAX_PACKET_SIZE_64 64 +#define ENDPOINT_MAX_PACKET_SIZE_512 512 +#define ENDPOINT_MAX_PACKET_SIZE_1023 1023 +#define ENDPOINT_MAX_PACKET_SIZE_1024 1024 + +#define SS_LINK_STATE_U3 3 +#define FSHS_LPM_STATE_L2 2 + +#define ADDR_MODULO_8 8 + +#define INTERRUPT_MASK 0xFFFFFFFF + +#define ACTUAL_TRANSFERRED_BYTES_MASK 0x1FFFF + +#define ENDPOINT_DIR_MASK 0x80 + +#define ENDPOINT_ZLP_BUF_SIZE 1024 +/*-------------------------------------------------------------------------*/ + +/** + * IS_REG_REQUIRING_ACTIVE_REF_CLOCK - Macro checks if desired + * register requires active clock, it involves such registers as: + * EP_CFG, EP_TR_ADDR, EP_CMD, EP_SEL, USB_CONF + * @usb_ss: extended gadget object + * @reg: register address + */ +#define IS_REG_REQUIRING_ACTIVE_REF_CLOCK(usb_ss, reg) (!reg || \ + (reg >= &usb_ss->regs->ep_sel && reg <= &usb_ss->regs->ep_cmd)) + +/** + * CAST_EP_REG_POS_TO_INDEX - Macro converts bit position of ep_ists register to + * index of endpoint object in usb_ss_dev.eps[] container + * @i: bit position of endpoint for which endpoint object is required + * + * Remember that endpoint container doesn't contain default endpoint + */ +#define CAST_EP_REG_POS_TO_INDEX(i) (((i) / 16) + ((((i) % 16) - 2) * 2)) + +/** + * CAST_EP_ADDR_TO_INDEX - Macro converts endpoint address to + * index of endpoint object in usb_ss_dev.eps[] container + * @ep_addr: endpoint address for which endpoint object is required + * + * Remember that endpoint container doesn't contain default endpoint + */ +#define CAST_EP_ADDR_TO_INDEX(ep_addr) \ + (((ep_addr & 0x7F) - 1) + ((ep_addr & 0x80) ? 1 : 0)) + +/** + * CAST_EP_ADDR_TO_BIT_POS - Macro converts endpoint address to + * bit position in ep_ists register + * @ep_addr: endpoint address for which bit position is required + * + * Remember that endpoint container doesn't contain default endpoint + */ +#define CAST_EP_ADDR_TO_BIT_POS(ep_addr) \ + (((uint32_t)1 << (ep_addr & 0x7F)) << ((ep_addr & 0x80) ? 16 : 0)) + + +#define CAST_INDEX_TO_EP_ADDR(index) \ + ((index / 2 + 1) | ((index % 2) ? 0x80 : 0x00)) + +/* 18KB is the total size, and 2KB is used for EP0 and configuration */ +#define CDNS3_ONCHIP_BUF_SIZE 16 /* KB */ +#define CDNS3_EP_BUF_SIZE 2 /* KB */ +#define CDNS3_UNALIGNED_BUF_SIZE 16384 /* Bytes */ +/*-------------------------------------------------------------------------*/ +/* Used structs */ + +struct usb_ss_trb { + u32 offset0; + u32 offset4; + u32 offset8; +}; + +struct usb_ss_dev; + +struct usb_ss_endpoint { + struct usb_ep endpoint; + struct list_head request_list; + struct list_head ep_match_pending_list; + + struct usb_ss_trb *trb_pool; + dma_addr_t trb_pool_dma; + + struct usb_ss_dev *usb_ss; + char name[20]; + int hw_pending_flag; + int stalled_flag; + int wedge_flag; + void *cpu_addr; + dma_addr_t dma_addr; + u8 dir; + u8 num; + u8 type; + bool used; +}; + +struct usb_ss_dev { + struct device dev; + struct usbss_dev_register_block_type __iomem *regs; + + struct usb_gadget gadget; + struct usb_gadget_driver *gadget_driver; + + dma_addr_t setup_dma; + dma_addr_t trb_ep0_dma; + u32 *trb_ep0; + u8 *setup; + void *zlp_buf; + + struct usb_ss_endpoint *eps[USB_SS_ENDPOINTS_MAX_COUNT]; + int ep_nums; + struct usb_request *actual_ep0_request; + int ep0_data_dir; + int hw_configured_flag; + int wake_up_flag; + u16 isoch_delay; + spinlock_t lock; + + unsigned is_connected:1; + unsigned in_standby_mode:1; + unsigned status_completion_no_call:1; + + u32 usb_ien; + u32 ep_ien; + int setup_pending; + struct device *sysdev; + bool start_gadget; /* The device mode is enabled */ + struct list_head ep_match_list; + int onchip_mem_allocated_size; /* KB */ + /* Memory is allocated for OUT */ + int out_mem_is_allocated:1; + struct work_struct pending_status_wq; + struct usb_request *pending_status_request; +}; + +#endif /* __DRIVERS_CDNS3_GADGET */ diff --git a/drivers/usb/cdns3/host-export.h b/drivers/usb/cdns3/host-export.h new file mode 100644 index 000000000000..a981d5cf3658 --- /dev/null +++ b/drivers/usb/cdns3/host-export.h @@ -0,0 +1,43 @@ +/* + * 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 __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 + +static inline int cdns3_host_init(struct cdns3 *cdns) +{ + return -ENXIO; +} + +static inline void cdns3_host_remove(struct cdns3 *cdns) +{ + +} + +static inline void cdns3_host_driver_init(void) +{ + +} + +#endif /* CONFIG_USB_CDNS3_HOST */ + +#endif /* __DRIVERS_USB_CDNS3_HOST_H */ diff --git a/drivers/usb/cdns3/host.c b/drivers/usb/cdns3/host.c new file mode 100644 index 000000000000..4ed97339a724 --- /dev/null +++ b/drivers/usb/cdns3/host.c @@ -0,0 +1,259 @@ +/* + * host.c - Cadence USB3 host controller driver + * + * 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 + */ + +#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 "host-export.h" + +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_CDNS_HOST); +} + +static int xhci_cdns3_setup(struct usb_hcd *hcd) +{ + 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; +} + +static const struct xhci_driver_overrides xhci_cdns3_overrides __initconst = { + .extra_priv_size = sizeof(struct xhci_hcd), + .reset = xhci_cdns3_setup, +}; + +struct cdns3_host { + struct device dev; + struct usb_hcd *hcd; +}; + +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; + + dev = &host->dev; + dev->release = cdns3_host_release; + dev->parent = cdns->dev; + dev_set_name(dev, "xhci-cdns3"); + cdns->host_dev = dev; + 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)); + + /* If setting 64-bit DMA mask fails, fall back to 32-bit DMA mask */ + if (ret) { + 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); + + 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->quirks = XHCI_SKIP_ACCESS_RESERVED_REG; + 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: + put_device(dev); + cdns->host_dev = NULL; + return ret; +} + +static void cdns3_host_stop(struct cdns3 *cdns) +{ + struct device *dev = cdns->host_dev; + struct usb_hcd *hcd; + struct xhci_hcd *xhci; + + if (dev) { + hcd = dev_get_drvdata(dev); + xhci = hcd_to_xhci(hcd); + usb_remove_hcd(xhci->shared_hcd); + usb_remove_hcd(hcd); + synchronize_irq(cdns->irq); + usb_put_hcd(xhci->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; + + if (!dev) + return 0; + + xhci = hcd_to_xhci(dev_get_drvdata(dev)); + return xhci_suspend(xhci, do_wakeup); +} + +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) +{ + struct cdns3_role_driver *rdrv; + + rdrv = devm_kzalloc(cdns->dev, sizeof(*rdrv), GFP_KERNEL); + if (!rdrv) + return -ENOMEM; + + 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[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/chipidea/ci.h b/drivers/usb/chipidea/ci.h index 05bc4d631cb9..33c99e2fc8fe 100644 --- a/drivers/usb/chipidea/ci.h +++ b/drivers/usb/chipidea/ci.h @@ -125,12 +125,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; }; @@ -174,6 +178,7 @@ struct hw_bank { * @td_pool: allocation pool for transfer descriptors * @gadget: device side representation for peripheral controller * @driver: gadget driver + * @resume_state: save the state of gadget suspend from * @hw_ep_max: total number of endpoints supported by hardware * @ci_hw_ep: array of endpoints * @ep0_dir: ep0 direction @@ -193,12 +198,14 @@ struct hw_bank { * @debugfs: root dentry for this controller in debugfs * @id_event: indicates there is an id event, and handled at ci_otg_work * @b_sess_valid_event: indicates there is a vbus event, and handled + * @vbus_glitch_check_event: check if vbus change is a glitch * at ci_otg_work * @imx28_write_fix: Freescale imx28 needs swp instruction for writing * @supports_runtime_pm: if runtime pm is supported * @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 */ struct ci_hdrc { struct device *dev; @@ -222,6 +229,7 @@ struct ci_hdrc { struct usb_gadget gadget; struct usb_gadget_driver *driver; + enum usb_device_state resume_state; unsigned hw_ep_max; struct ci_hw_ep ci_hw_ep[ENDPT_MAX]; u32 ep0_dir; @@ -243,11 +251,25 @@ struct ci_hdrc { struct dentry *debugfs; bool id_event; bool b_sess_valid_event; + bool vbus_glitch_check_event; bool imx28_write_fix; bool supports_runtime_pm; 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 mutex mutex; }; static inline struct ci_role_driver *ci_role(struct ci_hdrc *ci) @@ -267,9 +289,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, + USB_MODE_HOST); + else + usb_phy_set_mode(ci->usb_phy, + USB_MODE_DEVICE); + } + + return 0; } static inline void ci_role_stop(struct ci_hdrc *ci) @@ -282,6 +316,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, USB_MODE_NONE); } /** @@ -429,6 +466,7 @@ int hw_port_test_set(struct ci_hdrc *ci, u8 mode); u8 hw_port_test_get(struct ci_hdrc *ci); void ci_platform_configure(struct ci_hdrc *ci); +int hw_controller_reset(struct ci_hdrc *ci); int dbg_create_files(struct ci_hdrc *ci); diff --git a/drivers/usb/chipidea/ci_hdrc_imx.c b/drivers/usb/chipidea/ci_hdrc_imx.c index 099179457f60..53f48cc2eeb5 100644 --- a/drivers/usb/chipidea/ci_hdrc_imx.c +++ b/drivers/usb/chipidea/ci_hdrc_imx.c @@ -1,5 +1,6 @@ /* - * Copyright 2012 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 * @@ -19,6 +20,13 @@ #include <linux/dma-mapping.h> #include <linux/usb/chipidea.h> #include <linux/clk.h> +#include <linux/of_device.h> +#include <linux/regmap.h> +#include <linux/mfd/syscon.h> +#include <linux/regulator/consumer.h> +#include <linux/busfreq-imx.h> +#include <linux/pm_qos.h> +#include <linux/usb/of.h> #include "ci.h" #include "ci_hdrc_imx.h" @@ -40,25 +48,29 @@ static const struct ci_hdrc_imx_platform_flag imx27_usb_data = { static const struct ci_hdrc_imx_platform_flag imx28_usb_data = { .flags = CI_HDRC_IMX28_WRITE_FIX | CI_HDRC_TURN_VBUS_EARLY_ON | - CI_HDRC_DISABLE_STREAMING, + CI_HDRC_DISABLE_STREAMING | + CI_HDRC_IMX_EHCI_QUIRK, }; static const struct ci_hdrc_imx_platform_flag imx6q_usb_data = { .flags = CI_HDRC_SUPPORTS_RUNTIME_PM | CI_HDRC_TURN_VBUS_EARLY_ON | - CI_HDRC_DISABLE_STREAMING, + CI_HDRC_DISABLE_STREAMING | + CI_HDRC_IMX_EHCI_QUIRK, }; static const struct ci_hdrc_imx_platform_flag imx6sl_usb_data = { .flags = CI_HDRC_SUPPORTS_RUNTIME_PM | CI_HDRC_TURN_VBUS_EARLY_ON | - CI_HDRC_DISABLE_HOST_STREAMING, + CI_HDRC_DISABLE_HOST_STREAMING | + CI_HDRC_IMX_EHCI_QUIRK, }; static const struct ci_hdrc_imx_platform_flag imx6sx_usb_data = { .flags = CI_HDRC_SUPPORTS_RUNTIME_PM | CI_HDRC_TURN_VBUS_EARLY_ON | - CI_HDRC_DISABLE_HOST_STREAMING, + CI_HDRC_DISABLE_HOST_STREAMING | + CI_HDRC_IMX_EHCI_QUIRK, }; static const struct ci_hdrc_imx_platform_flag imx6ul_usb_data = { @@ -70,6 +82,16 @@ static const struct ci_hdrc_imx_platform_flag imx7d_usb_data = { .flags = CI_HDRC_SUPPORTS_RUNTIME_PM, }; +static const struct ci_hdrc_imx_platform_flag imx7ulp_usb_data = { + .flags = CI_HDRC_SUPPORTS_RUNTIME_PM | + CI_HDRC_IMX_EHCI_QUIRK | + CI_HDRC_PMQOS, +}; + +static const struct ci_hdrc_imx_platform_flag imx8qm_usb_data = { + .flags = CI_HDRC_SUPPORTS_RUNTIME_PM, +}; + static const struct of_device_id ci_hdrc_imx_dt_ids[] = { { .compatible = "fsl,imx23-usb", .data = &imx23_usb_data}, { .compatible = "fsl,imx28-usb", .data = &imx28_usb_data}, @@ -79,6 +101,8 @@ static const struct of_device_id ci_hdrc_imx_dt_ids[] = { { .compatible = "fsl,imx6sx-usb", .data = &imx6sx_usb_data}, { .compatible = "fsl,imx6ul-usb", .data = &imx6ul_usb_data}, { .compatible = "fsl,imx7d-usb", .data = &imx7d_usb_data}, + { .compatible = "fsl,imx7ulp-usb", .data = &imx7ulp_usb_data}, + { .compatible = "fsl,imx8qm-usb", .data = &imx8qm_usb_data}, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, ci_hdrc_imx_dt_ids); @@ -90,12 +114,30 @@ struct ci_hdrc_imx_data { struct imx_usbmisc_data *usbmisc_data; bool supports_runtime_pm; bool in_lpm; + bool imx_usb_charger_detection; + struct usb_charger charger; + struct regmap *anatop; + struct pinctrl *pinctrl; + struct pinctrl_state *pinctrl_hsic_active; + struct regulator *hsic_pad_regulator; + const struct ci_hdrc_imx_platform_flag *data; /* SoC before i.mx6 (except imx23/imx28) needs three clks */ bool need_three_clks; struct clk *clk_ipg; struct clk *clk_ahb; struct clk *clk_per; /* --------------------------------- */ + struct pm_qos_request pm_qos_req; +}; + +static char *imx_usb_charger_supplied_to[] = { + "imx_usb_charger", +}; + +static enum power_supply_property imx_usb_charger_power_props[] = { + POWER_SUPPLY_PROP_PRESENT, /* Charger detected */ + POWER_SUPPLY_PROP_ONLINE, /* VBUS online */ + POWER_SUPPLY_PROP_CURRENT_MAX, /* Maximum current in mA */ }; /* Common functions shared by usbmisc drivers */ @@ -143,9 +185,32 @@ static struct imx_usbmisc_data *usbmisc_get_init_data(struct device *dev) if (of_find_property(np, "over-current-active-high", NULL)) data->oc_polarity = 1; + if (of_find_property(np, "power-polarity-active-high", NULL)) + data->pwr_polarity = 1; + if (of_find_property(np, "external-vbus-divider", NULL)) data->evdo = 1; + if (of_find_property(np, "osc-clkgate-delay", NULL)) { + ret = of_property_read_u32(np, "osc-clkgate-delay", + &data->osc_clkgate_delay); + if (ret) { + dev_err(dev, + "failed to get osc-clkgate-delay value\n"); + return ERR_PTR(ret); + } + /* + * 0 <= osc_clkgate_delay <=7 + * - 0x0 (default) is 0.5ms, + * - 0x1-0x7: 1-7ms + */ + if (data->osc_clkgate_delay > 7) { + dev_err(dev, + "value of osc-clkgate-delay is incorrect\n"); + return ERR_PTR(-EINVAL); + } + } + return data; } @@ -247,41 +312,212 @@ static void imx_disable_unprepare_clks(struct device *dev) } } +static int ci_hdrc_imx_notify_event(struct ci_hdrc *ci, unsigned event) +{ + struct device *dev = ci->dev->parent; + struct ci_hdrc_imx_data *data = dev_get_drvdata(dev); + int ret = 0; + + switch (event) { + case CI_HDRC_CONTROLLER_VBUS_EVENT: + if (data->usbmisc_data && ci->vbus_active) { + if (data->imx_usb_charger_detection) { + ret = imx_usbmisc_charger_detection( + data->usbmisc_data, true); + if (!ret && data->charger.psy_desc.type != + POWER_SUPPLY_TYPE_USB) + ret = CI_HDRC_NOTIFY_RET_DEFER_EVENT; + } + } else if (data->usbmisc_data && !ci->vbus_active) { + if (data->imx_usb_charger_detection) + ret = imx_usbmisc_charger_detection( + data->usbmisc_data, false); + } + break; + case CI_HDRC_CONTROLLER_CHARGER_POST_EVENT: + if (!data->imx_usb_charger_detection) + return ret; + imx_usbmisc_charger_secondary_detection(data->usbmisc_data); + break; + case CI_HDRC_IMX_HSIC_ACTIVE_EVENT: + if (!IS_ERR(data->pinctrl) && + !IS_ERR(data->pinctrl_hsic_active)) { + ret = pinctrl_select_state(data->pinctrl, + data->pinctrl_hsic_active); + if (ret) + dev_err(dev, + "hsic_active select failed, err=%d\n", + ret); + return ret; + } + break; + case CI_HDRC_IMX_HSIC_SUSPEND_EVENT: + if (data->usbmisc_data) { + ret = imx_usbmisc_hsic_set_connect(data->usbmisc_data); + if (ret) + dev_err(dev, + "hsic_set_connect failed, err=%d\n", + ret); + return ret; + } + break; + case CI_HDRC_IMX_TERM_SELECT_OVERRIDE_FS: + if (data->usbmisc_data) + return imx_usbmisc_term_select_override( + data->usbmisc_data, true, 1); + break; + case CI_HDRC_IMX_TERM_SELECT_OVERRIDE_OFF: + if (data->usbmisc_data) + return imx_usbmisc_term_select_override( + data->usbmisc_data, false, 0); + break; + default: + dev_dbg(dev, "unknown event\n"); + } + + return ret; +} + +static int imx_usb_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct usb_charger *charger = + container_of(psy->desc, struct usb_charger, psy_desc); + + switch (psp) { + case POWER_SUPPLY_PROP_PRESENT: + val->intval = charger->present; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = charger->online; + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + val->intval = charger->max_current; + break; + default: + return -EINVAL; + } + return 0; +} + +/* + * imx_usb_register_charger - register a USB charger + * @charger: the charger to be initialized + * @name: name for the power supply + + * Registers a power supply for the charger. The USB Controller + * driver will call this after filling struct usb_charger. + */ +static int imx_usb_register_charger(struct usb_charger *charger, + const char *name) +{ + struct power_supply_desc *desc = &charger->psy_desc; + + if (!charger->dev) + return -EINVAL; + + if (name) + desc->name = name; + else + desc->name = "imx_usb_charger"; + + charger->bc = BATTERY_CHARGING_SPEC_1_2; + mutex_init(&charger->lock); + + desc->type = POWER_SUPPLY_TYPE_MAINS; + desc->properties = imx_usb_charger_power_props; + desc->num_properties = ARRAY_SIZE(imx_usb_charger_power_props); + desc->get_property = imx_usb_charger_get_property; + + charger->psy = devm_power_supply_register(charger->dev, + &charger->psy_desc, NULL); + if (IS_ERR(charger->psy)) + return PTR_ERR(charger->psy); + + charger->psy->supplied_to = imx_usb_charger_supplied_to; + charger->psy->num_supplicants = sizeof(imx_usb_charger_supplied_to) + / sizeof(char *); + + return 0; +} + static int ci_hdrc_imx_probe(struct platform_device *pdev) { struct ci_hdrc_imx_data *data; struct ci_hdrc_platform_data pdata = { .name = dev_name(&pdev->dev), .capoffset = DEF_CAPOFFSET, + .notify_event = ci_hdrc_imx_notify_event, }; int ret; const struct of_device_id *of_id; const struct ci_hdrc_imx_platform_flag *imx_platform_flag; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct pinctrl_state *pinctrl_hsic_idle; - of_id = of_match_device(ci_hdrc_imx_dt_ids, &pdev->dev); + of_id = of_match_device(ci_hdrc_imx_dt_ids, dev); if (!of_id) return -ENODEV; imx_platform_flag = of_id->data; - data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; platform_set_drvdata(pdev, data); - data->usbmisc_data = usbmisc_get_init_data(&pdev->dev); + + data->data = imx_platform_flag; + pdata.flags |= imx_platform_flag->flags; + data->usbmisc_data = usbmisc_get_init_data(dev); if (IS_ERR(data->usbmisc_data)) return PTR_ERR(data->usbmisc_data); - ret = imx_get_clks(&pdev->dev); + data->pinctrl = devm_pinctrl_get(dev); + if (IS_ERR(data->pinctrl)) { + dev_dbg(dev, "pinctrl get failed, err=%ld\n", + PTR_ERR(data->pinctrl)); + } else { + pinctrl_hsic_idle = pinctrl_lookup_state(data->pinctrl, "idle"); + if (IS_ERR(pinctrl_hsic_idle)) { + dev_dbg(dev, + "pinctrl_hsic_idle lookup failed, err=%ld\n", + PTR_ERR(pinctrl_hsic_idle)); + } else { + ret = pinctrl_select_state(data->pinctrl, + pinctrl_hsic_idle); + if (ret) { + dev_err(dev, + "hsic_idle select failed, err=%d\n", + ret); + return ret; + } + } + + data->pinctrl_hsic_active = pinctrl_lookup_state(data->pinctrl, + "active"); + if (IS_ERR(data->pinctrl_hsic_active)) + dev_dbg(dev, + "pinctrl_hsic_active lookup failed, err=%ld\n", + PTR_ERR(data->pinctrl_hsic_active)); + } + + ret = imx_get_clks(dev); if (ret) return ret; - ret = imx_prepare_enable_clks(&pdev->dev); + request_bus_freq(BUS_FREQ_HIGH); + if (pdata.flags & CI_HDRC_PMQOS) + pm_qos_add_request(&data->pm_qos_req, + PM_QOS_CPU_DMA_LATENCY, 0); + + ret = imx_prepare_enable_clks(dev); if (ret) - return ret; + goto err_bus_freq; - data->phy = devm_usb_get_phy_by_phandle(&pdev->dev, "fsl,usbphy", 0); + data->phy = devm_usb_get_phy_by_phandle(dev, "fsl,usbphy", 0); if (IS_ERR(data->phy)) { ret = PTR_ERR(data->phy); /* Return -EINVAL if no usbphy is available */ @@ -291,46 +527,119 @@ static int ci_hdrc_imx_probe(struct platform_device *pdev) } pdata.usb_phy = data->phy; - pdata.flags |= imx_platform_flag->flags; 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; + } + + if (of_usb_get_phy_mode(dev->of_node) == USBPHY_INTERFACE_MODE_HSIC) { + pdata.flags |= CI_HDRC_IMX_IS_HSIC; + data->usbmisc_data->hsic = 1; + data->hsic_pad_regulator = devm_regulator_get(dev, "pad"); + if (PTR_ERR(data->hsic_pad_regulator) == -EPROBE_DEFER) { + ret = -EPROBE_DEFER; + goto err_clk; + } 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)); + ret = PTR_ERR(data->hsic_pad_regulator); + goto err_clk; + } + + if (data->hsic_pad_regulator) { + ret = regulator_enable(data->hsic_pad_regulator); + if (ret) { + dev_err(dev, + "Fail to enable hsic pad regulator\n"); + goto err_clk; + } + } + } + + if (of_find_property(np, "fsl,anatop", NULL) && data->usbmisc_data) { + data->anatop = syscon_regmap_lookup_by_phandle(np, + "fsl,anatop"); + if (IS_ERR(data->anatop)) { + dev_dbg(dev, "failed to find regmap for anatop\n"); + ret = PTR_ERR(data->anatop); + goto disable_hsic_regulator; + } + data->usbmisc_data->anatop = data->anatop; + } + + if (of_find_property(np, "imx-usb-charger-detection", NULL) && + data->usbmisc_data) { + data->imx_usb_charger_detection = true; + data->charger.dev = dev; + data->usbmisc_data->charger = &data->charger; + ret = imx_usb_register_charger(&data->charger, + "imx_usb_charger"); + if (ret && ret != -ENODEV) + goto disable_hsic_regulator; + if (!ret) + dev_dbg(dev, "USB Charger is created\n"); + } + ret = imx_usbmisc_init(data->usbmisc_data); if (ret) { - dev_err(&pdev->dev, "usbmisc init failed, ret=%d\n", ret); - goto err_clk; + dev_err(dev, "usbmisc init failed, ret=%d\n", ret); + goto disable_hsic_regulator; } - data->ci_pdev = ci_hdrc_add_device(&pdev->dev, + data->ci_pdev = ci_hdrc_add_device(dev, pdev->resource, pdev->num_resources, &pdata); if (IS_ERR(data->ci_pdev)) { ret = PTR_ERR(data->ci_pdev); if (ret != -EPROBE_DEFER) - dev_err(&pdev->dev, + dev_err(dev, "ci_hdrc_add_device failed, err=%d\n", ret); - goto err_clk; + goto disable_hsic_regulator; } ret = imx_usbmisc_init_post(data->usbmisc_data); if (ret) { - dev_err(&pdev->dev, "usbmisc post failed, ret=%d\n", ret); + dev_err(dev, "usbmisc post failed, ret=%d\n", ret); goto disable_device; } + ret = imx_usbmisc_set_wakeup(data->usbmisc_data, false); + if (ret) { + dev_err(dev, "usbmisc set_wakeup failed, ret=%d\n", ret); + goto disable_device; + } + + /* usbmisc needs to know dr mode to choose wakeup setting */ + if (data->usbmisc_data) + data->usbmisc_data->available_role = + ci_hdrc_query_available_role(data->ci_pdev); + if (data->supports_runtime_pm) { - pm_runtime_set_active(&pdev->dev); - pm_runtime_enable(&pdev->dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); } - device_set_wakeup_capable(&pdev->dev, true); + device_set_wakeup_capable(dev, true); return 0; disable_device: ci_hdrc_remove_device(data->ci_pdev); +disable_hsic_regulator: + if (data->hsic_pad_regulator) + ret = regulator_disable(data->hsic_pad_regulator); err_clk: imx_disable_unprepare_clks(&pdev->dev); +err_bus_freq: + if (pdata.flags & CI_HDRC_PMQOS) + pm_qos_remove_request(&data->pm_qos_req); + release_bus_freq(BUS_FREQ_HIGH); return ret; } @@ -345,6 +654,11 @@ static int ci_hdrc_imx_remove(struct platform_device *pdev) } ci_hdrc_remove_device(data->ci_pdev); imx_disable_unprepare_clks(&pdev->dev); + if (data->data->flags & CI_HDRC_PMQOS) + pm_qos_remove_request(&data->pm_qos_req); + release_bus_freq(BUS_FREQ_HIGH); + if (data->hsic_pad_regulator) + regulator_disable(data->hsic_pad_regulator); return 0; } @@ -358,10 +672,23 @@ static void ci_hdrc_imx_shutdown(struct platform_device *pdev) static int imx_controller_suspend(struct device *dev) { struct ci_hdrc_imx_data *data = dev_get_drvdata(dev); + int ret; dev_dbg(dev, "at %s\n", __func__); + if (data->usbmisc_data) { + ret = imx_usbmisc_hsic_set_clk(data->usbmisc_data, false); + if (ret) { + dev_err(dev, + "usbmisc hsic_set_clk failed, ret=%d\n", ret); + return ret; + } + } + imx_disable_unprepare_clks(dev); + if (data->data->flags & CI_HDRC_PMQOS) + pm_qos_remove_request(&data->pm_qos_req); + release_bus_freq(BUS_FREQ_HIGH); data->in_lpm = true; return 0; @@ -374,27 +701,51 @@ static int imx_controller_resume(struct device *dev) dev_dbg(dev, "at %s\n", __func__); - if (!data->in_lpm) { - WARN_ON(1); + if (!data->in_lpm) return 0; - } + request_bus_freq(BUS_FREQ_HIGH); + if (data->data->flags & CI_HDRC_PMQOS) + pm_qos_add_request(&data->pm_qos_req, + PM_QOS_CPU_DMA_LATENCY, 0); ret = imx_prepare_enable_clks(dev); if (ret) - return ret; + goto err_bus_freq; data->in_lpm = false; + ret = imx_usbmisc_power_lost_check(data->usbmisc_data); + /* re-init if resume from power lost */ + if (ret > 0) { + ret = imx_usbmisc_init(data->usbmisc_data); + if (ret) { + dev_err(dev, "usbmisc init failed, ret=%d\n", ret); + goto clk_disable; + } + } + ret = imx_usbmisc_set_wakeup(data->usbmisc_data, false); if (ret) { dev_err(dev, "usbmisc set_wakeup 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); +err_bus_freq: + if (data->data->flags & CI_HDRC_PMQOS) + pm_qos_remove_request(&data->pm_qos_req); + release_bus_freq(BUS_FREQ_HIGH); return ret; } @@ -442,10 +793,8 @@ static int 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) { diff --git a/drivers/usb/chipidea/ci_hdrc_imx.h b/drivers/usb/chipidea/ci_hdrc_imx.h index 409aa5ca8dda..8d86a9dbdfe0 100644 --- a/drivers/usb/chipidea/ci_hdrc_imx.h +++ b/drivers/usb/chipidea/ci_hdrc_imx.h @@ -1,5 +1,5 @@ /* - * Copyright 2012 Freescale Semiconductor, Inc. + * Copyright 2012-2015 Freescale Semiconductor, Inc. * * The code contained herein is licensed under the GNU General Public * License. You may obtain a copy of the GNU General Public License @@ -11,18 +11,61 @@ #ifndef __DRIVER_USB_CHIPIDEA_CI_HDRC_IMX_H #define __DRIVER_USB_CHIPIDEA_CI_HDRC_IMX_H +#include <linux/usb/otg.h> +#include <linux/power_supply.h> + +enum battery_charging_spec { + BATTERY_CHARGING_SPEC_NONE = 0, + BATTERY_CHARGING_SPEC_UNKNOWN, + BATTERY_CHARGING_SPEC_1_0, + BATTERY_CHARGING_SPEC_1_1, + BATTERY_CHARGING_SPEC_1_2, +}; + +struct usb_charger { + /* USB controller */ + struct device *dev; + struct power_supply *psy; + struct power_supply_desc psy_desc; + struct mutex lock; + + /* Compliant with Battery Charging Specification version (if any) */ + enum battery_charging_spec bc; + + /* properties */ + unsigned present:1; + unsigned online:1; + unsigned max_current; +}; struct imx_usbmisc_data { struct device *dev; int index; + struct regmap *anatop; + struct usb_charger *charger; unsigned int disable_oc:1; /* over current detect disabled */ unsigned int oc_polarity:1; /* over current polarity if oc enabled */ + unsigned int pwr_polarity:1; /* polarity of enable vbus from pmic */ unsigned int evdo:1; /* set external vbus divider option */ + unsigned int hsic:1; /* HSIC controlller */ + /* + * Specifies the delay between powering up the xtal 24MHz clock + * and release the clock to the digital logic inside the analog block + */ + unsigned int osc_clkgate_delay; + enum usb_dr_mode available_role; }; int imx_usbmisc_init(struct imx_usbmisc_data *); int imx_usbmisc_init_post(struct imx_usbmisc_data *); int imx_usbmisc_set_wakeup(struct imx_usbmisc_data *, bool); +int imx_usbmisc_charger_detection(struct imx_usbmisc_data *data, bool connect); +int imx_usbmisc_charger_secondary_detection(struct imx_usbmisc_data *data); +int imx_usbmisc_power_lost_check(struct imx_usbmisc_data *); +int imx_usbmisc_hsic_set_connect(struct imx_usbmisc_data *); +int imx_usbmisc_hsic_set_clk(struct imx_usbmisc_data *, bool); +int imx_usbmisc_term_select_override(struct imx_usbmisc_data *data, + bool enable, int val); #endif /* __DRIVER_USB_CHIPIDEA_CI_HDRC_IMX_H */ diff --git a/drivers/usb/chipidea/ci_hdrc_msm.c b/drivers/usb/chipidea/ci_hdrc_msm.c index 37591a4b1346..6d5cf973dba6 100644 --- a/drivers/usb/chipidea/ci_hdrc_msm.c +++ b/drivers/usb/chipidea/ci_hdrc_msm.c @@ -17,7 +17,7 @@ #define MSM_USB_BASE (ci->hw_bank.abs) -static void ci_hdrc_msm_notify_event(struct ci_hdrc *ci, unsigned event) +static int ci_hdrc_msm_notify_event(struct ci_hdrc *ci, unsigned event) { struct device *dev = ci->gadget.dev.parent; @@ -40,6 +40,8 @@ static void ci_hdrc_msm_notify_event(struct ci_hdrc *ci, unsigned event) dev_dbg(dev, "unknown ci_hdrc event\n"); break; } + + return 0; } static struct ci_hdrc_platform_data ci_hdrc_msm_platdata = { diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index 64c6af2c8559..a9b3795a150b 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -461,7 +461,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; @@ -555,7 +555,7 @@ static irqreturn_t ci_irq(int irq, void *data) * and disconnection events. */ if (ci->is_otg && (otgsc & OTGSC_BSVIE) && (otgsc & OTGSC_BSVIS)) { - ci->b_sess_valid_event = true; + ci->vbus_glitch_check_event = true; /* Clear BSV irq */ hw_write_otgsc(ci, OTGSC_BSVIS, OTGSC_BSVIS); ci_otg_queue_work(ci); @@ -569,35 +569,14 @@ static irqreturn_t ci_irq(int irq, void *data) return ret; } -static int ci_vbus_notifier(struct notifier_block *nb, unsigned long event, - void *ptr) +static int ci_cable_notifier(struct notifier_block *nb, unsigned long event, + void *ptr) { - struct ci_hdrc_cable *vbus = container_of(nb, struct ci_hdrc_cable, nb); - struct ci_hdrc *ci = vbus->ci; + struct ci_hdrc_cable *cbl = container_of(nb, struct ci_hdrc_cable, nb); + struct ci_hdrc *ci = cbl->ci; - if (event) - vbus->state = true; - else - vbus->state = false; - - vbus->changed = true; - - ci_irq(ci->irq, ci); - return NOTIFY_DONE; -} - -static int ci_id_notifier(struct notifier_block *nb, unsigned long event, - void *ptr) -{ - struct ci_hdrc_cable *id = container_of(nb, struct ci_hdrc_cable, nb); - struct ci_hdrc *ci = id->ci; - - if (event) - id->state = false; - else - id->state = true; - - id->changed = true; + cbl->connected = event; + cbl->changed = true; ci_irq(ci->irq, ci); return NOTIFY_DONE; @@ -706,27 +685,27 @@ static int ci_get_platdata(struct device *dev, } cable = &platdata->vbus_extcon; - cable->nb.notifier_call = ci_vbus_notifier; + cable->nb.notifier_call = ci_cable_notifier; cable->edev = ext_vbus; if (!IS_ERR(ext_vbus)) { - ret = extcon_get_cable_state_(cable->edev, EXTCON_USB); + ret = extcon_get_state(cable->edev, EXTCON_USB); if (ret) - cable->state = true; + cable->connected = true; else - cable->state = false; + cable->connected = false; } cable = &platdata->id_extcon; - cable->nb.notifier_call = ci_id_notifier; + cable->nb.notifier_call = ci_cable_notifier; cable->edev = ext_id; if (!IS_ERR(ext_id)) { - ret = extcon_get_cable_state_(cable->edev, EXTCON_USB_HOST); + ret = extcon_get_state(cable->edev, EXTCON_USB_HOST); if (ret) - cable->state = false; + cable->connected = true; else - cable->state = true; + cable->connected = false; } return 0; } @@ -738,9 +717,9 @@ static int ci_extcon_register(struct ci_hdrc *ci) id = &ci->platdata->id_extcon; id->ci = ci; - if (!IS_ERR(id->edev)) { - ret = extcon_register_notifier(id->edev, EXTCON_USB_HOST, - &id->nb); + if (!IS_ERR_OR_NULL(id->edev)) { + ret = devm_extcon_register_notifier(ci->dev, id->edev, + EXTCON_USB_HOST, &id->nb); if (ret < 0) { dev_err(ci->dev, "register ID failed\n"); return ret; @@ -749,12 +728,10 @@ static int ci_extcon_register(struct ci_hdrc *ci) vbus = &ci->platdata->vbus_extcon; vbus->ci = ci; - if (!IS_ERR(vbus->edev)) { - ret = extcon_register_notifier(vbus->edev, EXTCON_USB, - &vbus->nb); + if (!IS_ERR_OR_NULL(vbus->edev)) { + ret = devm_extcon_register_notifier(ci->dev, vbus->edev, + EXTCON_USB, &vbus->nb); if (ret < 0) { - extcon_unregister_notifier(id->edev, EXTCON_USB_HOST, - &id->nb); dev_err(ci->dev, "register VBUS failed\n"); return ret; } @@ -763,20 +740,6 @@ static int ci_extcon_register(struct ci_hdrc *ci) return 0; } -static void ci_extcon_unregister(struct ci_hdrc *ci) -{ - struct ci_hdrc_cable *cable; - - cable = &ci->platdata->id_extcon; - if (!IS_ERR(cable->edev)) - extcon_unregister_notifier(cable->edev, EXTCON_USB_HOST, - &cable->nb); - - cable = &ci->platdata->vbus_extcon; - if (!IS_ERR(cable->edev)) - extcon_unregister_notifier(cable->edev, EXTCON_USB, &cable->nb); -} - static DEFINE_IDA(ci_ida); struct platform_device *ci_hdrc_add_device(struct device *dev, @@ -801,9 +764,6 @@ struct platform_device *ci_hdrc_add_device(struct device *dev, } pdev->dev.parent = dev; - pdev->dev.dma_mask = dev->dma_mask; - pdev->dev.dma_parms = dev->dma_parms; - dma_set_coherent_mask(&pdev->dev, dev->coherent_dma_mask); ret = platform_device_add_resources(pdev, res, nres); if (ret) @@ -835,12 +795,39 @@ 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); - ci_hdrc_host_destroy(ci); if (ci->is_otg && ci->roles[CI_ROLE_GADGET]) ci_hdrc_otg_destroy(ci); + ci_hdrc_gadget_destroy(ci); + ci_hdrc_host_destroy(ci); } static void ci_get_otg_capable(struct ci_hdrc *ci) @@ -859,6 +846,104 @@ static void ci_get_otg_capable(struct ci_hdrc *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); + + pm_runtime_get_sync(ci->dev); + if (!ci_otg_is_fsm_mode(ci)) + ci_start_new_role(ci); + else + ci_hdrc_otg_fsm_restart(ci); + pm_runtime_put_sync(ci->dev); + enable_irq(ci->irq); +} + +static ssize_t ci_role_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ci_hdrc *ci = dev_get_drvdata(dev); + + return sprintf(buf, "%s\n", ci_role(ci)->name); +} + +static ssize_t ci_role_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t n) +{ + struct ci_hdrc *ci = dev_get_drvdata(dev); + enum ci_role role; + int ret; + + if (!(ci->roles[CI_ROLE_HOST] && ci->roles[CI_ROLE_GADGET])) { + dev_warn(dev, "Current configuration is not dual-role, quit\n"); + return -EPERM; + } + + for (role = CI_ROLE_HOST; role < CI_ROLE_END; role++) + if (!strncmp(buf, ci->roles[role]->name, + strlen(ci->roles[role]->name))) + break; + + if (role == CI_ROLE_END || role == ci->role) + return -EINVAL; + + pm_runtime_get_sync(dev); + disable_irq(ci->irq); + ci_role_stop(ci); + ret = ci_role_start(ci, role); + if (!ret && ci->role == CI_ROLE_GADGET) + ci_handle_vbus_change(ci); + enable_irq(ci->irq); + pm_runtime_put_sync(dev); + + return (ret == 0) ? n : ret; +} +static DEVICE_ATTR(role, 0644, ci_role_show, ci_role_store); + +static struct attribute *ci_attrs[] = { + &dev_attr_role.attr, + NULL, +}; + +static struct attribute_group ci_attr_group = { + .attrs = ci_attrs, +}; + static int ci_hdrc_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -971,30 +1056,12 @@ static int ci_hdrc_probe(struct platform_device *pdev) } } - if (ci->roles[CI_ROLE_HOST] && ci->roles[CI_ROLE_GADGET]) { - if (ci->is_otg) { - ci->role = ci_otg_role(ci); - /* Enable ID change irq */ - hw_write_otgsc(ci, OTGSC_IDIE, OTGSC_IDIE); - } 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. - */ - ci->role = CI_ROLE_GADGET; - } - } else { - ci->role = ci->roles[CI_ROLE_HOST] - ? CI_ROLE_HOST - : CI_ROLE_GADGET; - } + ci->role = ci_get_role(ci); + /* only update vbus status for peripheral */ + if (ci->role == CI_ROLE_GADGET) + ci_handle_vbus_connected(ci); if (!ci_otg_is_fsm_mode(ci)) { - /* only update vbus status for peripheral */ - if (ci->role == CI_ROLE_GADGET) - ci_handle_vbus_change(ci); - ret = ci_role_start(ci, ci->role); if (ret) { dev_err(dev, "can't start %s role\n", @@ -1026,11 +1093,22 @@ static int ci_hdrc_probe(struct platform_device *pdev) device_set_wakeup_capable(&pdev->dev, true); + /* Init workqueue for controller power lost handling */ + INIT_WORK(&ci->power_lost_work, ci_power_lost_work); + mutex_init(&ci->mutex); + ret = dbg_create_files(ci); - if (!ret) - return 0; + if (ret) + goto stop; + + ret = sysfs_create_group(&dev->kobj, &ci_attr_group); + if (ret) + goto remove_debug; - ci_extcon_unregister(ci); + return 0; + +remove_debug: + dbg_remove_files(ci); stop: if (ci->is_otg && ci->roles[CI_ROLE_GADGET]) ci_hdrc_otg_destroy(ci); @@ -1055,7 +1133,7 @@ static int ci_hdrc_remove(struct platform_device *pdev) } dbg_remove_files(ci); - ci_extcon_unregister(ci); + sysfs_remove_group(&ci->dev->kobj, &ci_attr_group); ci_role_destroy(ci); ci_hdrc_enter_lpm(ci, true); ci_usb_phy_exit(ci); @@ -1081,13 +1159,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); } } @@ -1103,16 +1178,37 @@ static void ci_controller_suspend(struct ci_hdrc *ci) enable_irq(ci->irq); } +/* + * Handle the wakeup interrupt triggered by extcon connector + * We need to call ci_irq again for extcon since the first + * interrupt (wakeup int) only let the controller be out of + * low power mode, but not handle any interrupts. + */ +static void ci_extcon_wakeup_int(struct ci_hdrc *ci) +{ + struct ci_hdrc_cable *cable_id, *cable_vbus; + u32 otgsc = hw_read_otgsc(ci, ~0); + + cable_id = &ci->platdata->id_extcon; + cable_vbus = &ci->platdata->vbus_extcon; + + if (!IS_ERR(cable_id->edev) && ci->is_otg && + (otgsc & OTGSC_IDIE) && (otgsc & OTGSC_IDIS)) + ci_irq(ci->irq, ci); + + if (!IS_ERR(cable_vbus->edev) && ci->is_otg && + (otgsc & OTGSC_BSVIE) && (otgsc & OTGSC_BSVIS)) + ci_irq(ci->irq, ci); +} + static int ci_controller_resume(struct device *dev) { struct ci_hdrc *ci = dev_get_drvdata(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); if (ci->usb_phy) { @@ -1129,6 +1225,7 @@ static int ci_controller_resume(struct device *dev) enable_irq(ci->irq); if (ci_otg_is_fsm_mode(ci)) ci_otg_fsm_wakeup_by_srp(ci); + ci_extcon_wakeup_int(ci); } return 0; @@ -1155,6 +1252,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); @@ -1171,8 +1272,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); @@ -1180,6 +1291,21 @@ 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) { + disable_irq_nosync(ci->irq); + schedule_work(&ci->power_lost_work); + } + if (ci->supports_runtime_pm) { pm_runtime_disable(dev); pm_runtime_set_active(dev); @@ -1196,10 +1322,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 111b0e0b8698..4b687bb8db0c 100644 --- a/drivers/usb/chipidea/host.c +++ b/drivers/usb/chipidea/host.c @@ -25,6 +25,7 @@ #include <linux/usb/hcd.h> #include <linux/usb/chipidea.h> #include <linux/regulator/consumer.h> +#include <linux/imx_gpc.h> #include "../host/ehci.h" @@ -34,11 +35,29 @@ 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); +static int (*orig_hub_control)(struct usb_hcd *hcd, + u16 typeReq, u16 wValue, u16 wIndex, + char *buf, u16 wLength); struct ehci_ci_priv { struct regulator *reg_vbus; }; +/* This function is used to override WKCN, WKDN, and WKOC */ +static void ci_ehci_override_wakeup_flag(struct ehci_hcd *ehci, + u32 __iomem *reg, u32 flags, bool set) +{ + u32 val = ehci_readl(ehci, reg); + + if (set) + val |= flags; + else + val &= ~flags; + + ehci_writel(ehci, val, reg); +} + static int ehci_ci_portpower(struct usb_hcd *hcd, int portnum, bool enable) { struct ehci_hcd *ehci = hcd_to_ehci(hcd); @@ -101,9 +120,164 @@ static const struct ehci_driver_overrides ehci_ci_overrides = { .reset = ehci_ci_reset, }; +static int ci_imx_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; +} + +#ifdef CONFIG_USB_OTG + +static int ci_start_port_reset(struct usb_hcd *hcd, unsigned port) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + u32 __iomem *reg; + u32 status; + + if (!port) + return -EINVAL; + port--; + /* start port reset before HNP protocol time out */ + reg = &ehci->regs->port_status[port]; + status = ehci_readl(ehci, reg); + if (!(status & PORT_CONNECT)) + return -ENODEV; + + /* khubd will finish the reset later */ + if (ehci_is_TDI(ehci)) + ehci_writel(ehci, status | (PORT_RESET & ~PORT_RWC_BITS), reg); + else + ehci_writel(ehci, status | PORT_RESET, reg); + + return 0; +} + +#else + +#define ci_start_port_reset NULL + +#endif + +/* The below code is based on tegra ehci driver */ +static int ci_imx_ehci_hub_control( + struct usb_hcd *hcd, + u16 typeReq, + u16 wValue, + u16 wIndex, + char *buf, + u16 wLength +) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + u32 __iomem *status_reg; + u32 temp; + unsigned long flags; + int retval = 0; + struct device *dev = hcd->self.controller; + struct ci_hdrc *ci = dev_get_drvdata(dev); + + status_reg = &ehci->regs->port_status[(wIndex & 0xff) - 1]; + + spin_lock_irqsave(&ehci->lock, flags); + + if (typeReq == SetPortFeature && wValue == USB_PORT_FEAT_SUSPEND) { + temp = ehci_readl(ehci, status_reg); + if ((temp & PORT_PE) == 0 || (temp & PORT_RESET) != 0) { + retval = -EPIPE; + goto done; + } + + temp &= ~(PORT_RWC_BITS | PORT_WKCONN_E); + temp |= PORT_WKDISC_E | PORT_WKOC_E; + ehci_writel(ehci, temp | PORT_SUSPEND, status_reg); + + /* + * If a transaction is in progress, there may be a delay in + * suspending the port. Poll until the port is suspended. + */ + if (ehci_handshake(ehci, status_reg, PORT_SUSPEND, + PORT_SUSPEND, 5000)) + ehci_err(ehci, "timeout waiting for SUSPEND\n"); + + if (ci->platdata->flags & CI_HDRC_IMX_IS_HSIC) { + if (ci->platdata->notify_event) + ci->platdata->notify_event + (ci, CI_HDRC_IMX_HSIC_SUSPEND_EVENT); + ci_ehci_override_wakeup_flag(ehci, status_reg, + PORT_WKDISC_E | PORT_WKCONN_E, false); + } + + 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; + } + + /* + * After resume has finished, it needs do some post resume + * operation for some SoCs. + */ + else if (typeReq == ClearPortFeature && + wValue == USB_PORT_FEAT_C_SUSPEND) { + + /* 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); + + /* Handle the hub control events here */ + return orig_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength); +done: + spin_unlock_irqrestore(&ehci->lock, flags); + return retval; +} + 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) @@ -116,7 +290,8 @@ static int host_start(struct ci_hdrc *ci) if (usb_disabled()) return -ENODEV; - hcd = usb_create_hcd(&ci_ehci_hc_driver, ci->dev, dev_name(ci->dev)); + hcd = __usb_create_hcd(&ci_ehci_hc_driver, ci->dev->parent, + ci->dev, dev_name(ci->dev), NULL); if (!hcd) return -ENOMEM; @@ -156,6 +331,13 @@ static int host_start(struct ci_hdrc *ci) } } + if (ci_otg_is_fsm_mode(ci)) { + if (ci->fsm.id && ci->fsm.otg->state <= OTG_STATE_B_HOST) + hcd->self.is_b_host = 1; + else + hcd->self.is_b_host = 0; + } + ret = usb_add_hcd(hcd, 0, 0); if (ret) { goto disable_reg; @@ -165,11 +347,17 @@ static int host_start(struct ci_hdrc *ci) ci->hcd = hcd; if (ci_otg_is_fsm_mode(ci)) { + hcd->self.otg_fsm = &ci->fsm; otg->host = &hcd->self; hcd->self.otg_port = 1; } } + if (ci->platdata->notify_event && + (ci->platdata->flags & CI_HDRC_IMX_IS_HSIC)) + ci->platdata->notify_event + (ci, CI_HDRC_IMX_HSIC_ACTIVE_EVENT); + return ret; disable_reg: @@ -194,16 +382,123 @@ static void host_stop(struct ci_hdrc *ci) if (ci->platdata->reg_vbus && !ci_otg_is_fsm_mode(ci) && (ci->platdata->flags & CI_HDRC_TURN_VBUS_EARLY_ON)) regulator_disable(ci->platdata->reg_vbus); + if (hcd->self.is_b_host) + hcd->self.is_b_host = 0; } ci->hcd = NULL; ci->otg.host = NULL; } +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) +{ + if (ci_hdrc_host_has_device(ci)) + imx_gpc_mf_request_on(ci->irq, 1); + + ci_hdrc_host_save_for_power_lost(ci); +} + +static void ci_hdrc_host_resume(struct ci_hdrc *ci, bool power_lost) +{ + imx_gpc_mf_request_on(ci->irq, 0); + + 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); + } } static int ci_ehci_bus_suspend(struct usb_hcd *hcd) @@ -211,6 +506,8 @@ static int ci_ehci_bus_suspend(struct usb_hcd *hcd) struct ehci_hcd *ehci = hcd_to_ehci(hcd); int port; u32 tmp; + struct device *dev = hcd->self.controller; + struct ci_hdrc *ci = dev_get_drvdata(dev); int ret = orig_bus_suspend(hcd); @@ -240,6 +537,30 @@ static int ci_ehci_bus_suspend(struct usb_hcd *hcd) * It needs a short delay between set RS bit and PHCD. */ 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"); + + if (ci->platdata->flags & CI_HDRC_IMX_IS_HSIC) + ci_ehci_override_wakeup_flag(ehci, reg, + PORT_WKDISC_E | PORT_WKCONN_E, false); + + 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; } } @@ -261,6 +582,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; @@ -271,5 +594,11 @@ 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; + orig_hub_control = ci_ehci_hc_driver.hub_control; + ci_ehci_hc_driver.bus_suspend = ci_ehci_bus_suspend; + ci_ehci_hc_driver.bus_resume = ci_imx_ehci_bus_resume; + ci_ehci_hc_driver.hub_control = ci_imx_ehci_hub_control; + ci_ehci_hc_driver.start_port_reset = ci_start_port_reset; } diff --git a/drivers/usb/chipidea/host.h b/drivers/usb/chipidea/host.h index 0f12f131bdd3..4dfb59a05a1a 100644 --- a/drivers/usb/chipidea/host.h +++ b/drivers/usb/chipidea/host.h @@ -6,6 +6,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 @@ -19,11 +20,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 f36a1ac3bfbd..9a17e04f4bfc 100644 --- a/drivers/usb/chipidea/otg.c +++ b/drivers/usb/chipidea/otg.c @@ -1,7 +1,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 * @@ -23,6 +24,7 @@ #include "bits.h" #include "otg.h" #include "otg_fsm.h" +#include "host.h" /** * hw_read_otgsc returns otgsc register bits value. @@ -44,7 +46,7 @@ u32 hw_read_otgsc(struct ci_hdrc *ci, u32 mask) else val &= ~OTGSC_BSVIS; - if (cable->state) + if (cable->connected) val |= OTGSC_BSV; else val &= ~OTGSC_BSV; @@ -62,10 +64,10 @@ u32 hw_read_otgsc(struct ci_hdrc *ci, u32 mask) else val &= ~OTGSC_IDIS; - if (cable->state) - val |= OTGSC_ID; + if (cable->connected) + val &= ~OTGSC_ID; /* host */ else - val &= ~OTGSC_ID; + val |= OTGSC_ID; /* device */ if (cable->enabled) val |= OTGSC_IDIE; @@ -129,6 +131,46 @@ enum ci_role ci_otg_role(struct ci_hdrc *ci) return role; } +/* + * Handling vbus glitch + * We only need to consider glitch for without usb connection, + * With usb connection, we consider it as real disconnection. + * + * If the vbus can't be kept above B session valid for timeout value, + * we think it is a vbus glitch, otherwise it's a valid vbus. + */ +#define CI_VBUS_CONNECT_TIMEOUT_MS 300 +static int ci_is_vbus_glitch(struct ci_hdrc *ci) +{ + int i; + + for (i = 0; i < CI_VBUS_CONNECT_TIMEOUT_MS/20; i++) { + if (hw_read_otgsc(ci, OTGSC_AVV)) { + return 0; + } else if (!hw_read_otgsc(ci, OTGSC_BSV)) { + dev_warn(ci->dev, "there is a vbus glitch\n"); + return 1; + } + msleep(20); + } + + return 0; +} + +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) && !ci_is_vbus_glitch(ci)) + usb_gadget_vbus_connect(&ci->gadget); +} + void ci_handle_vbus_change(struct ci_hdrc *ci) { if (!ci->is_otg) @@ -165,10 +207,12 @@ 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; + 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); @@ -191,7 +235,34 @@ static void ci_handle_id_switch(struct ci_hdrc *ci) if (role == CI_ROLE_GADGET) ci_handle_vbus_change(ci); } + mutex_unlock(&ci->mutex); +} + +static void ci_handle_vbus_glitch(struct ci_hdrc *ci) +{ + bool valid_vbus_change = false; + + if (hw_read_otgsc(ci, OTGSC_BSV)) { + if (!ci_is_vbus_glitch(ci)) { + if (ci_otg_is_fsm_mode(ci)) { + ci->fsm.b_sess_vld = 1; + ci->fsm.b_ssend_srp = 0; + otg_del_timer(&ci->fsm, B_SSEND_SRP); + otg_del_timer(&ci->fsm, B_SRP_FAIL); + } + valid_vbus_change = true; + } + } else { + if (ci->vbus_active && !ci_otg_is_fsm_mode(ci)) + valid_vbus_change = true; + } + + if (valid_vbus_change) { + ci->b_sess_valid_event = true; + ci_otg_queue_work(ci); + } } + /** * ci_otg_work - perform otg (vbus/id) event handle * @work: work struct @@ -200,6 +271,15 @@ static void ci_otg_work(struct work_struct *work) { struct ci_hdrc *ci = container_of(work, struct ci_hdrc, work); + if (ci->vbus_glitch_check_event) { + ci->vbus_glitch_check_event = false; + pm_runtime_get_sync(ci->dev); + ci_handle_vbus_glitch(ci); + pm_runtime_put_sync(ci->dev); + enable_irq(ci->irq); + return; + } + if (ci_otg_is_fsm_mode(ci) && !ci_otg_fsm_work(ci)) { enable_irq(ci->irq); return; @@ -245,13 +325,14 @@ int ci_hdrc_otg_init(struct ci_hdrc *ci) */ void ci_hdrc_otg_destroy(struct ci_hdrc *ci) { + /* Disable all OTG irq and clear status */ + hw_write_otgsc(ci, OTGSC_INT_EN_BITS | OTGSC_INT_STATUS_BITS, + OTGSC_INT_STATUS_BITS); if (ci->wq) { flush_workqueue(ci->wq); destroy_workqueue(ci->wq); + ci->wq = NULL; } - /* Disable all OTG irq and clear status */ - hw_write_otgsc(ci, OTGSC_INT_EN_BITS | OTGSC_INT_STATUS_BITS, - OTGSC_INT_STATUS_BITS); if (ci_otg_is_fsm_mode(ci)) ci_hdrc_otg_fsm_remove(ci); } diff --git a/drivers/usb/chipidea/otg.h b/drivers/usb/chipidea/otg.h index 9ecb598e48f0..95fa6c2b675a 100644 --- a/drivers/usb/chipidea/otg.h +++ b/drivers/usb/chipidea/otg.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2014 Freescale Semiconductor, Inc. + * Copyright (C) 2013-2015 Freescale Semiconductor, Inc. * * Author: Peter Chen * @@ -17,10 +17,17 @@ 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); - queue_work(ci->wq, &ci->work); + if (ci->wq) { + disable_irq_nosync(ci->irq); + if (!queue_work(ci->wq, &ci->work)) + enable_irq(ci->irq); + } else { + WARN_ON(!ci->wq); + } } #endif /* __DRIVERS_USB_CHIPIDEA_OTG_H */ diff --git a/drivers/usb/chipidea/otg_fsm.c b/drivers/usb/chipidea/otg_fsm.c index de8e22ec3902..05ff5f155e96 100644 --- a/drivers/usb/chipidea/otg_fsm.c +++ b/drivers/usb/chipidea/otg_fsm.c @@ -1,7 +1,7 @@ /* * otg_fsm.c - ChipIdea USB IP core OTG FSM driver * - * Copyright (C) 2014 Freescale Semiconductor, Inc. + * Copyright (C) 2014-2015 Freescale Semiconductor, Inc. * * Author: Jun Li * @@ -28,7 +28,10 @@ #include "ci.h" #include "bits.h" #include "otg.h" +#include "udc.h" #include "otg_fsm.h" +#include "udc.h" +#include "host.h" /* Add for otg: interact with user space app */ static ssize_t @@ -215,6 +218,11 @@ static unsigned otg_timer_ms[] = { 0, TB_DATA_PLS, TB_SSEND_SRP, + TA_DP_END, + TA_TST_MAINT, + TB_SRP_REQD, + TB_TST_SUSP, + 0, }; /* @@ -300,6 +308,7 @@ static int a_wait_vfall_tmout(struct ci_hdrc *ci) static int a_wait_bcon_tmout(struct ci_hdrc *ci) { ci->fsm.a_wait_bcon_tmout = 1; + dev_warn(ci->dev, "Device No Response\n"); return 0; } @@ -312,6 +321,7 @@ static int a_aidl_bdis_tmout(struct ci_hdrc *ci) static int b_ase0_brst_tmout(struct ci_hdrc *ci) { ci->fsm.b_ase0_brst_tmout = 1; + dev_warn(ci->dev, "Device No Response\n"); return 0; } @@ -336,6 +346,7 @@ static int b_se0_srp_tmout(struct ci_hdrc *ci) static int b_srp_fail_tmout(struct ci_hdrc *ci) { ci->fsm.b_srp_done = 1; + dev_warn(ci->dev, "Device No Response\n"); return 1; } @@ -360,6 +371,57 @@ 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; +} + +static int a_tst_maint_tmout(struct ci_hdrc *ci) +{ + ci->fsm.tst_maint = 0; + if (ci->fsm.otg_vbus_off) { + ci->fsm.otg_vbus_off = 0; + dev_dbg(ci->dev, + "test device does not disconnect, end the session!\n"); + } + + /* End the session */ + ci->fsm.a_bus_req = 0; + ci->fsm.a_bus_drop = 1; + return 0; +} + +/* + * otg_srp_reqd feature + * After A(PET) turn off vbus, B(UUT) should start this timer to do SRP + * when the timer expires. + */ +static int b_srp_reqd_tmout(struct ci_hdrc *ci) +{ + ci->fsm.otg_srp_reqd = 0; + if (ci->fsm.otg->state == OTG_STATE_B_IDLE) { + ci->fsm.b_bus_req = 1; + return 0; + } + return 1; +} + +/* + * otg_hnp_reqd feature + * After B(UUT) switch to host, B should hand host role back + * to A(PET) within TB_TST_SUSP after setting configuration. + */ +static int b_tst_susp_tmout(struct ci_hdrc *ci) +{ + if (ci->fsm.otg->state == OTG_STATE_B_HOST) { + ci->fsm.b_bus_req = 0; + return 0; + } + return 1; +} + /* * Keep this list in the same order as timers indexed * by enum otg_fsm_timer in include/linux/usb/otg-fsm.h @@ -377,6 +439,11 @@ 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 */ + a_tst_maint_tmout, /* A_TST_MAINT */ + b_srp_reqd_tmout, /* B_SRP_REQD */ + b_tst_susp_tmout, /* B_TST_SUSP */ + NULL, /* HNP_POLLING */ }; /* @@ -463,6 +530,9 @@ static void ci_otg_drv_vbus(struct otg_fsm *fsm, int on) struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm); if (on) { + ci->platdata->notify_event(ci, + CI_HDRC_IMX_TERM_SELECT_OVERRIDE_OFF); + /* Enable power power */ hw_write(ci, OP_PORTSC, PORTSC_W1C_BITS | PORTSC_PP, PORTSC_PP); @@ -486,6 +556,7 @@ static void ci_otg_drv_vbus(struct otg_fsm *fsm, int on) fsm->a_bus_drop = 1; fsm->a_bus_req = 0; + fsm->b_conn = 0; } } @@ -562,11 +633,16 @@ static int ci_otg_start_host(struct otg_fsm *fsm, int on) static int ci_otg_start_gadget(struct otg_fsm *fsm, int on) { struct ci_hdrc *ci = container_of(fsm, struct ci_hdrc, fsm); + unsigned long flags; + int gadget_ready = 0; - if (on) - usb_gadget_vbus_connect(&ci->gadget); - else - usb_gadget_vbus_disconnect(&ci->gadget); + spin_lock_irqsave(&ci->lock, flags); + ci->vbus_active = on; + if (ci->driver) + gadget_ready = 1; + spin_unlock_irqrestore(&ci->lock, flags); + if (gadget_ready) + ci_hdrc_gadget_connect(&ci->gadget, on); return 0; } @@ -584,13 +660,26 @@ static struct otg_fsm_ops ci_otg_ops = { int ci_otg_fsm_work(struct ci_hdrc *ci) { - /* - * Don't do fsm transition for B device - * when there is no gadget class driver - */ - if (ci->fsm.id && !(ci->driver) && - ci->fsm.otg->state < OTG_STATE_A_IDLE) - return 0; + if (ci->fsm.id && ci->fsm.otg->state < OTG_STATE_A_IDLE) { + unsigned long flags; + + /* Charger detection */ + spin_lock_irqsave(&ci->lock, flags); + if (ci->b_sess_valid_event) { + ci->b_sess_valid_event = false; + ci->vbus_active = ci->fsm.b_sess_vld; + spin_unlock_irqrestore(&ci->lock, flags); + ci_usb_charger_connect(ci, ci->fsm.b_sess_vld); + spin_lock_irqsave(&ci->lock, flags); + } + spin_unlock_irqrestore(&ci->lock, flags); + /* + * Don't do fsm transition for B device if gadget + * driver is not binded. + */ + if (!ci->driver) + return 0; + } pm_runtime_get_sync(ci->dev); if (otg_statemachine(&ci->fsm)) { @@ -612,10 +701,14 @@ int ci_otg_fsm_work(struct ci_hdrc *ci) PORTSC_PP, 0); hw_write_otgsc(ci, OTGSC_DPIS, OTGSC_DPIS); hw_write_otgsc(ci, OTGSC_DPIE, OTGSC_DPIE); + /* FS termination override if needed */ + ci->platdata->notify_event(ci, + CI_HDRC_IMX_TERM_SELECT_OVERRIDE_FS); } if (ci->id_event) ci->id_event = false; } else if (ci->fsm.otg->state == OTG_STATE_B_IDLE) { + ci->fsm.b_sess_vld = hw_read_otgsc(ci, OTGSC_BSV); if (ci->fsm.b_sess_vld) { ci->fsm.power_up = 0; /* @@ -624,7 +717,8 @@ int ci_otg_fsm_work(struct ci_hdrc *ci) */ ci_otg_queue_work(ci); } - } else if (ci->fsm.otg->state == OTG_STATE_A_HOST) { + } else if (ci->fsm.otg->state == OTG_STATE_A_HOST || + ci->fsm.otg->state == OTG_STATE_A_WAIT_VFALL) { pm_runtime_mark_last_busy(ci->dev); pm_runtime_put_autosuspend(ci->dev); return 0; @@ -678,25 +772,16 @@ static void ci_otg_fsm_event(struct ci_hdrc *ci) } break; case OTG_STATE_A_PERIPHERAL: - if (intr_sts & USBi_SLI) { - fsm->b_bus_suspend = 1; + if (intr_sts & USBi_SLI) /* * Init a timer to know how long this suspend * will continue, if time out, indicates B no longer * wants to be host role */ ci_otg_add_timer(ci, A_BIDL_ADIS); - } - if (intr_sts & USBi_URI) + if (intr_sts & (USBi_URI | USBi_PCI)) ci_otg_del_timer(ci, A_BIDL_ADIS); - - if (intr_sts & USBi_PCI) { - if (fsm->b_bus_suspend == 1) { - ci_otg_del_timer(ci, A_BIDL_ADIS); - fsm->b_bus_suspend = 0; - } - } break; case OTG_STATE_A_SUSPEND: if ((intr_sts & USBi_PCI) && !port_conn) { @@ -713,6 +798,15 @@ static void ci_otg_fsm_event(struct ci_hdrc *ci) case OTG_STATE_A_HOST: if ((intr_sts & USBi_PCI) && !port_conn) { fsm->b_conn = 0; + if (fsm->tst_maint) { + ci_otg_del_timer(ci, A_TST_MAINT); + if (fsm->otg_vbus_off) { + fsm->a_bus_req = 0; + fsm->a_bus_drop = 1; + fsm->otg_vbus_off = 0; + } + fsm->tst_maint = 0; + } ci_otg_queue_work(ci); } break; @@ -746,26 +840,36 @@ 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->platdata->notify_event(ci, + CI_HDRC_IMX_TERM_SELECT_OVERRIDE_OFF); + 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) { fsm->a_bus_drop = 0; fsm->a_bus_req = 1; ci->id_event = true; + } else { + /* + * Disable term select override and data pulse + * for B device. + */ + ci->platdata->notify_event(ci, + CI_HDRC_IMX_TERM_SELECT_OVERRIDE_OFF); } } else if (otg_int_src & OTGSC_BSVIS) { hw_write_otgsc(ci, OTGSC_BSVIS, OTGSC_BSVIS); - if (otgsc & OTGSC_BSV) { - fsm->b_sess_vld = 1; - ci_otg_del_timer(ci, B_SSEND_SRP); - ci_otg_del_timer(ci, B_SRP_FAIL); - fsm->b_ssend_srp = 0; - } else { + if (!(otgsc & OTGSC_BSV) && fsm->b_sess_vld) { + ci->b_sess_valid_event = true; fsm->b_sess_vld = 0; if (fsm->id) ci_otg_add_timer(ci, B_SSEND_SRP); + if (fsm->b_bus_req) + fsm->b_bus_req = 0; + if (fsm->otg_srp_reqd) + ci_otg_add_timer(ci, B_SRP_REQD); + } else { + ci->vbus_glitch_check_event = true; } } else if (otg_int_src & OTGSC_AVVIS) { hw_write_otgsc(ci, OTGSC_AVVIS, OTGSC_AVVIS); @@ -802,6 +906,7 @@ int ci_hdrc_otg_fsm_init(struct ci_hdrc *ci) ci->otg.gadget = &ci->gadget; ci->fsm.otg = &ci->otg; ci->fsm.power_up = 1; + ci->fsm.hnp_polling = 1; ci->fsm.id = hw_read_otgsc(ci, OTGSC_ID) ? 1 : 0; ci->fsm.otg->state = OTG_STATE_UNDEFINED; ci->fsm.ops = &ci_otg_ops; @@ -844,5 +949,56 @@ int ci_hdrc_otg_fsm_init(struct ci_hdrc *ci) void ci_hdrc_otg_fsm_remove(struct ci_hdrc *ci) { + enum otg_fsm_timer i; + + mutex_lock(&ci->fsm.lock); + ci->fsm.otg->state = OTG_STATE_UNDEFINED; + mutex_unlock(&ci->fsm.lock); + + for (i = 0; i < NUM_OTG_FSM_TIMERS; i++) + otg_del_timer(&ci->fsm, i); + + ci->enabled_otg_timer_bits = 0; + + /* Turn off vbus if vbus is on */ + if (ci->fsm.drv_vbus) + otg_drv_vbus(&ci->fsm, 0); + sysfs_remove_group(&ci->dev->kobj, &inputs_attr_group); } + +/* Restart OTG fsm if resume from power lost */ +void ci_hdrc_otg_fsm_restart(struct ci_hdrc *ci) +{ + struct otg_fsm *fsm = &ci->fsm; + int id_status = fsm->id; + + /* Update fsm if power lost in peripheral state */ + if (ci->fsm.otg->state == OTG_STATE_B_PERIPHERAL) { + fsm->b_sess_vld = 0; + otg_statemachine(fsm); + } + + hw_write_otgsc(ci, OTGSC_IDIE, OTGSC_IDIE); + hw_write_otgsc(ci, OTGSC_AVVIE, OTGSC_AVVIE); + + /* Update fsm variables for restart */ + fsm->id = hw_read_otgsc(ci, OTGSC_ID) ? 1 : 0; + if (fsm->id) { + fsm->b_ssend_srp = + hw_read_otgsc(ci, OTGSC_BSV) ? 0 : 1; + fsm->b_sess_vld = + hw_read_otgsc(ci, OTGSC_BSV) ? 1 : 0; + } else if (fsm->id != id_status) { + /* ID changes to be 0 */ + fsm->a_bus_drop = 0; + fsm->a_bus_req = 1; + ci->id_event = true; + } + + if (ci_hdrc_host_has_device(ci) && + !hw_read(ci, OP_PORTSC, PORTSC_CCS)) + fsm->b_conn = 0; + + ci_otg_fsm_work(ci); +} diff --git a/drivers/usb/chipidea/otg_fsm.h b/drivers/usb/chipidea/otg_fsm.h index 6366fe398ba6..ff77d2e1bb64 100644 --- a/drivers/usb/chipidea/otg_fsm.h +++ b/drivers/usb/chipidea/otg_fsm.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 Freescale Semiconductor, Inc. + * Copyright (C) 2014-2015 Freescale Semiconductor, Inc. * * Author: Jun Li * @@ -25,7 +25,7 @@ * ->DC Electrical Timing */ /* Wait for VBUS Fall */ -#define TA_WAIT_VFALL (1000) /* a_wait_vfall: section 7.1.7 +#define TA_WAIT_VFALL (500) /* a_wait_vfall: section 7.1.7 * a_wait_vfall_tmr: section: 7.4.5.2 */ /* Wait for B-Connect */ @@ -38,9 +38,13 @@ * TA_AIDL_BDIS: section 5.5, Table 5-1 */ /* B-Idle to A-Disconnect */ -#define TA_BIDL_ADIS (500) /* TA_BIDL_ADIS: section 5.2.1 - * 500ms is used for B switch to host - * for safe +#define TA_BIDL_ADIS (160) /* TA_BIDL_ADIS: section 5.2.1 + * 155ms ~ 200 ms + */ + +#define TA_DP_END (200) +#define TA_TST_MAINT (9900) /* OTG test device session maintain + * timer, 9.9s~10.1s */ /* @@ -63,6 +67,14 @@ #define TB_SSEND_SRP (1500) /* minimum 1.5 sec, section:5.1.2 */ #define TB_AIDL_BDIS (20) /* 4ms ~ 150ms, section 5.2.1 */ +#define TB_SRP_REQD (2000) /* For otg_srp_reqd to start data + * pulse after A(PET) turn off v-bus + */ + +#define TB_TST_SUSP (20) /* B-dev hand host role back to A-dev + * via suspend bus after set config. + * max: 100ms + */ #if IS_ENABLED(CONFIG_USB_OTG_FSM) @@ -71,6 +83,7 @@ int ci_otg_fsm_work(struct ci_hdrc *ci); irqreturn_t ci_otg_fsm_irq(struct ci_hdrc *ci); void ci_hdrc_otg_fsm_start(struct ci_hdrc *ci); void ci_hdrc_otg_fsm_remove(struct ci_hdrc *ci); +void ci_hdrc_otg_fsm_restart(struct ci_hdrc *ci); #else @@ -99,6 +112,11 @@ static inline void ci_hdrc_otg_fsm_remove(struct ci_hdrc *ci) } +static inline void ci_hdrc_otg_fsm_restart(struct ci_hdrc *ci) +{ + +} + #endif #endif /* __DRIVERS_USB_CHIPIDEA_OTG_FSM_H */ diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c index 6a15b7250e9c..cf24527d24eb 100644 --- a/drivers/usb/chipidea/udc.c +++ b/drivers/usb/chipidea/udc.c @@ -423,7 +423,8 @@ static int _hardware_enqueue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq) hwreq->req.status = -EALREADY; - ret = usb_gadget_map_request(&ci->gadget, &hwreq->req, hwep->dir); + ret = usb_gadget_map_request_by_dev(ci->dev->parent, + &hwreq->req, hwep->dir); if (ret) return ret; @@ -603,7 +604,8 @@ static int _hardware_dequeue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq) list_del_init(&node->td); } - usb_gadget_unmap_request(&hwep->ci->gadget, &hwreq->req, hwep->dir); + usb_gadget_unmap_request_by_dev(hwep->ci->dev->parent, + &hwreq->req, hwep->dir); hwreq->req.actual += actual; @@ -709,12 +711,6 @@ static int _gadget_stop_activity(struct usb_gadget *gadget) struct ci_hdrc *ci = container_of(gadget, struct ci_hdrc, gadget); unsigned long flags; - spin_lock_irqsave(&ci->lock, flags); - ci->gadget.speed = USB_SPEED_UNKNOWN; - ci->remote_wakeup = 0; - ci->suspended = 0; - spin_unlock_irqrestore(&ci->lock, flags); - /* flush all endpoints */ gadget_for_each_ep(ep, gadget) { usb_ep_fifo_flush(ep); @@ -732,6 +728,12 @@ static int _gadget_stop_activity(struct usb_gadget *gadget) ci->status = NULL; } + spin_lock_irqsave(&ci->lock, flags); + ci->gadget.speed = USB_SPEED_UNKNOWN; + ci->remote_wakeup = 0; + ci->suspended = 0; + spin_unlock_irqrestore(&ci->lock, flags); + return 0; } @@ -750,6 +752,11 @@ __acquires(ci->lock) { int retval; + if (ci_otg_is_fsm_mode(ci)) { + ci->fsm.otg_srp_reqd = 0; + ci->fsm.otg_hnp_reqd = 0; + } + spin_unlock(&ci->lock); if (ci->gadget.speed != USB_SPEED_UNKNOWN) usb_gadget_udc_reset(&ci->gadget, ci->driver); @@ -873,7 +880,10 @@ __acquires(hwep->lock) return -ENOMEM; req->complete = isr_get_status_complete; - req->length = 2; + if (setup->wIndex == OTG_STS_SELECTOR) + req->length = 1; + else + req->length = 2; req->buf = kzalloc(req->length, gfp_flags); if (req->buf == NULL) { retval = -ENOMEM; @@ -881,8 +891,16 @@ __acquires(hwep->lock) } if ((setup->bRequestType & USB_RECIP_MASK) == USB_RECIP_DEVICE) { - *(u16 *)req->buf = (ci->remote_wakeup << 1) | - ci->gadget.is_selfpowered; + if ((setup->wIndex == OTG_STS_SELECTOR) && + ci_otg_is_fsm_mode(ci)) { + if (ci->gadget.host_request_flag) + *(u8 *)req->buf = HOST_REQUEST_FLAG; + else + *(u8 *)req->buf = 0; + } else { + *(u16 *)req->buf = (ci->remote_wakeup << 1) | + ci->gadget.is_selfpowered; + } } else if ((setup->bRequestType & USB_RECIP_MASK) \ == USB_RECIP_ENDPOINT) { dir = (le16_to_cpu(setup->wIndex) & USB_ENDPOINT_DIR_MASK) ? @@ -1007,6 +1025,28 @@ static int otg_a_alt_hnp_support(struct ci_hdrc *ci) return isr_setup_status_phase(ci); } +static int otg_srp_reqd(struct ci_hdrc *ci) +{ + if (ci_otg_is_fsm_mode(ci)) { + ci->fsm.otg_srp_reqd = 1; + return isr_setup_status_phase(ci); + } else { + return -ENOTSUPP; + } +} + +static int otg_hnp_reqd(struct ci_hdrc *ci) +{ + if (ci_otg_is_fsm_mode(ci)) { + ci->fsm.otg_hnp_reqd = 1; + ci->fsm.b_bus_req = 1; + ci->gadget.host_request_flag = 1; + return isr_setup_status_phase(ci); + } else { + return -ENOTSUPP; + } +} + /** * isr_setup_packet_handler: setup packet handler * @ci: UDC descriptor @@ -1077,8 +1117,9 @@ __acquires(ci->lock) type != (USB_DIR_IN|USB_RECIP_ENDPOINT) && type != (USB_DIR_IN|USB_RECIP_INTERFACE)) goto delegate; - if (le16_to_cpu(req.wLength) != 2 || - le16_to_cpu(req.wValue) != 0) + if ((le16_to_cpu(req.wLength) != 2 && + le16_to_cpu(req.wLength) != 1) || + le16_to_cpu(req.wValue) != 0) break; err = isr_get_status_response(ci, &req); break; @@ -1129,6 +1170,12 @@ __acquires(ci->lock) err = isr_setup_status_phase( ci); break; + case TEST_OTG_SRP_REQD: + err = otg_srp_reqd(ci); + break; + case TEST_OTG_HNP_REQD: + err = otg_hnp_reqd(ci); + break; default: break; } @@ -1306,6 +1353,10 @@ static int ep_disable(struct usb_ep *ep) return -EBUSY; spin_lock_irqsave(hwep->lock, flags); + if (hwep->ci->gadget.speed == USB_SPEED_UNKNOWN) { + spin_unlock_irqrestore(hwep->lock, flags); + return 0; + } /* only internal SW should disable ctrl endpts */ @@ -1395,6 +1446,10 @@ static int ep_queue(struct usb_ep *ep, struct usb_request *req, return -EINVAL; spin_lock_irqsave(hwep->lock, flags); + if (hwep->ci->gadget.speed == USB_SPEED_UNKNOWN) { + spin_unlock_irqrestore(hwep->lock, flags); + return 0; + } retval = _ep_queue(ep, req, gfp_flags); spin_unlock_irqrestore(hwep->lock, flags); return retval; @@ -1418,8 +1473,8 @@ static int ep_dequeue(struct usb_ep *ep, struct usb_request *req) return -EINVAL; spin_lock_irqsave(hwep->lock, flags); - - hw_ep_flush(hwep->ci, hwep->num, hwep->dir); + if (hwep->ci->gadget.speed != USB_SPEED_UNKNOWN) + hw_ep_flush(hwep->ci, hwep->num, hwep->dir); list_for_each_entry_safe(node, tmpnode, &hwreq->tds, td) { dma_pool_free(hwep->td_pool, node->ptr, node->dma); @@ -1490,6 +1545,10 @@ static void ep_fifo_flush(struct usb_ep *ep) } spin_lock_irqsave(hwep->lock, flags); + if (hwep->ci->gadget.speed == USB_SPEED_UNKNOWN) { + spin_unlock_irqrestore(hwep->lock, flags); + return; + } hw_ep_flush(hwep->ci, hwep->num, hwep->dir); @@ -1527,27 +1586,19 @@ static int ci_udc_vbus_session(struct usb_gadget *_gadget, int is_active) gadget_ready = 1; spin_unlock_irqrestore(&ci->lock, flags); - 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; } @@ -1558,6 +1609,10 @@ static int ci_udc_wakeup(struct usb_gadget *_gadget) int ret = 0; spin_lock_irqsave(&ci->lock, flags); + if (ci->gadget.speed == USB_SPEED_UNKNOWN) { + spin_unlock_irqrestore(&ci->lock, flags); + return 0; + } if (!ci->remote_wakeup) { ret = -EOPNOTSUPP; goto out; @@ -1725,7 +1780,6 @@ static int ci_udc_start(struct usb_gadget *gadget, struct usb_gadget_driver *driver) { struct ci_hdrc *ci = container_of(gadget, struct ci_hdrc, gadget); - unsigned long flags; int retval = -ENOMEM; if (driver->disconnect == NULL) @@ -1745,25 +1799,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) { - spin_lock_irqsave(&ci->lock, flags); - 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); - spin_unlock_irqrestore(&ci->lock, flags); - if (retval) - pm_runtime_put_sync(&ci->gadget.dev); + if (ci->vbus_active) + ci_hdrc_gadget_connect(&ci->gadget, 1); return retval; } @@ -1848,27 +1891,35 @@ 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->suspended && ci->driver->resume) { - spin_unlock(&ci->lock); - ci->driver->resume(&ci->gadget); - spin_lock(&ci->lock); + 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); + ci->driver->resume(&ci->gadget); + spin_lock(&ci->lock); + } ci->suspended = 0; + usb_gadget_set_state(&ci->gadget, + ci->resume_state); } } if (USBi_UI & intr) isr_tr_complete_handler(ci); - if (USBi_SLI & intr) { + if ((USBi_SLI & intr) && !(ci->suspended)) { + ci->suspended = 1; + ci->resume_state = ci->gadget.state; if (ci->gadget.speed != USB_SPEED_UNKNOWN && ci->driver->suspend) { - ci->suspended = 1; spin_unlock(&ci->lock); ci->driver->suspend(&ci->gadget); - usb_gadget_set_state(&ci->gadget, - USB_STATE_SUSPENDED); spin_lock(&ci->lock); } + usb_gadget_set_state(&ci->gadget, + USB_STATE_SUSPENDED); } retval = IRQ_HANDLED; } else { @@ -1902,13 +1953,13 @@ static int udc_start(struct ci_hdrc *ci) INIT_LIST_HEAD(&ci->gadget.ep_list); /* alloc resources */ - ci->qh_pool = dma_pool_create("ci_hw_qh", dev, + ci->qh_pool = dma_pool_create("ci_hw_qh", dev->parent, sizeof(struct ci_hw_qh), 64, CI_HDRC_PAGE_SIZE); if (ci->qh_pool == NULL) return -ENOMEM; - ci->td_pool = dma_pool_create("ci_hw_td", dev, + ci->td_pool = dma_pool_create("ci_hw_td", dev->parent, sizeof(struct ci_hw_td), 64, CI_HDRC_PAGE_SIZE); if (ci->td_pool == NULL) { @@ -1958,10 +2009,74 @@ 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; + + if (is_active) + pm_runtime_get_sync(ci->dev); + + if (ci->platdata->notify_event) { + if (is_active) + hw_write(ci, OP_USBCMD, USBCMD_RS, 0); + + ret = ci->platdata->notify_event(ci, + CI_HDRC_CONTROLLER_VBUS_EVENT); + if (ret == CI_HDRC_NOTIFY_RET_DEFER_EVENT) { + hw_device_reset(ci); + /* Pull up dp */ + hw_write(ci, OP_USBCMD, USBCMD_RS, USBCMD_RS); + ci->platdata->notify_event(ci, + CI_HDRC_CONTROLLER_CHARGER_POST_EVENT); + /* Pull down dp */ + hw_write(ci, OP_USBCMD, USBCMD_RS, 0); + } + } + + if (!is_active) + 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(&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(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(&gadget->dev); + usb_gadget_set_state(gadget, USB_STATE_NOTATTACHED); + } +} + static int udc_id_switch_for_device(struct ci_hdrc *ci) { - if (ci->is_otg) - /* Clear and enable BSV irq */ + if (!ci->is_otg) + return 0; + + /* + * Clear and enable BSV irq for A-device switch to B-device + * (in otg fsm mode, means A_IDLE->B_DILE) due to ID change. + */ + if (!ci_otg_is_fsm_mode(ci) || + ci->fsm.otg->state == OTG_STATE_A_IDLE) hw_write_otgsc(ci, OTGSC_BSVIS | OTGSC_BSVIE, OTGSC_BSVIS | OTGSC_BSVIE); @@ -1970,12 +2085,57 @@ static int udc_id_switch_for_device(struct ci_hdrc *ci) static void udc_id_switch_for_host(struct ci_hdrc *ci) { + if (!ci->is_otg) + return; + /* - * host doesn't care B_SESSION_VALID event - * so clear and disbale BSV irq + * Clear and disbale BSV irq for B-device switch to A-device + * (in otg fsm mode, means B_IDLE->A_IDLE) due to ID change. */ - if (ci->is_otg) + if (!ci_otg_is_fsm_mode(ci) || + ci->fsm.otg->state == OTG_STATE_B_IDLE || + ci->fsm.otg->state == OTG_STATE_UNDEFINED) hw_write_otgsc(ci, OTGSC_BSVIE | OTGSC_BSVIS, OTGSC_BSVIS); + + ci->vbus_active = 0; +} + +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); + } } /** @@ -1999,6 +2159,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 e66df0020bd4..7c3af66aa8f4 100644 --- a/drivers/usb/chipidea/udc.h +++ b/drivers/usb/chipidea/udc.h @@ -85,6 +85,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 @@ -98,6 +100,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 20d02a5e418d..b7d21b052c19 100644 --- a/drivers/usb/chipidea/usbmisc_imx.c +++ b/drivers/usb/chipidea/usbmisc_imx.c @@ -1,5 +1,6 @@ /* - * Copyright 2012 Freescale Semiconductor, Inc. + * Copyright 2012-2016 Freescale Semiconductor, Inc. + * 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 @@ -14,6 +15,8 @@ #include <linux/err.h> #include <linux/io.h> #include <linux/delay.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> #include "ci_hdrc_imx.h" @@ -57,11 +60,24 @@ #define MX6_BM_NON_BURST_SETTING BIT(1) #define MX6_BM_OVER_CUR_DIS BIT(7) #define MX6_BM_OVER_CUR_POLARITY BIT(8) +#define MX6_BM_PRW_POLARITY BIT(9) #define MX6_BM_WAKEUP_ENABLE BIT(10) +#define MX6_BM_UTMI_ON_CLOCK BIT(13) #define MX6_BM_ID_WAKEUP BIT(16) #define MX6_BM_VBUS_WAKEUP BIT(17) #define MX6SX_BM_DPDM_WAKEUP_EN BIT(29) #define MX6_BM_WAKEUP_INTR BIT(31) + +#define MX6_USB_HSIC_CTRL_OFFSET 0x10 +/* Send resume signal without 480Mhz PHY clock */ +#define MX6SX_BM_HSIC_AUTO_RESUME BIT(23) +/* set before portsc.suspendM = 1 */ +#define MX6_BM_HSIC_DEV_CONN BIT(21) +/* HSIC enable */ +#define MX6_BM_HSIC_EN BIT(12) +/* Force HSIC module 480M clock on, even when in Host is in suspend mode */ +#define MX6_BM_HSIC_CLK_ON BIT(11) + #define MX6_USB_OTG1_PHY_CTRL 0x18 /* For imx6dql, it is host-only controller, for later imx6, it is otg's */ #define MX6_USB_OTG2_PHY_CTRL 0x1c @@ -74,12 +90,50 @@ #define VF610_OVER_CUR_DIS BIT(7) #define MX7D_USBNC_USB_CTRL2 0x4 +#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_HSIC_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) #define MX7D_USB_VBUS_WAKEUP_SOURCE_AVALID MX7D_USB_VBUS_WAKEUP_SOURCE(1) #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_TERMSEL_OVERRIDE BIT(4) +#define MX7D_USB_TERMSEL_OVERRIDE_EN BIT(5) + +#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 ANADIG_ANA_MISC0 0x150 +#define ANADIG_ANA_MISC0_SET 0x154 +#define ANADIG_ANA_MISC0_CLK_DELAY(x) ((x >> 26) & 0x7) + +#define ANADIG_USB1_CHRG_DETECT_SET 0x1b4 +#define ANADIG_USB1_CHRG_DETECT_CLR 0x1b8 +#define ANADIG_USB1_CHRG_DETECT_EN_B BIT(20) +#define ANADIG_USB1_CHRG_DETECT_CHK_CHRG_B BIT(19) +#define ANADIG_USB1_CHRG_DETECT_CHK_CONTACT BIT(18) + +#define ANADIG_USB1_VBUS_DET_STAT 0x1c0 +#define ANADIG_USB1_VBUS_DET_STAT_VBUS_VALID BIT(3) + +#define ANADIG_USB1_CHRG_DET_STAT 0x1d0 +#define ANADIG_USB1_CHRG_DET_STAT_DM_STATE BIT(2) +#define ANADIG_USB1_CHRG_DET_STAT_CHRG_DETECTED BIT(1) +#define ANADIG_USB1_CHRG_DET_STAT_PLUG_CONTACT BIT(0) struct usbmisc_ops { /* It's called once when probe a usb device */ @@ -88,6 +142,19 @@ struct usbmisc_ops { int (*post)(struct imx_usbmisc_data *data); /* It's called when we need to enable/disable usb wakeup */ int (*set_wakeup)(struct imx_usbmisc_data *data, bool enabled); + /* usb charger contact and primary detection */ + int (*charger_primary_detection)(struct imx_usbmisc_data *data); + /* usb charger secondary detection */ + int (*charger_secondary_detection)(struct imx_usbmisc_data *data); + /* It's called when system resume from usb power lost */ + int (*power_lost_check)(struct imx_usbmisc_data *data); + /* It's called before setting portsc.suspendM */ + 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); + /* override UTMI termination select */ + int (*term_select_override)(struct imx_usbmisc_data *data, + bool enable, int val); }; struct imx_usbmisc { @@ -96,6 +163,8 @@ struct imx_usbmisc { const struct usbmisc_ops *ops; }; +static struct regulator *vbus_wakeup_reg; + static int usbmisc_imx25_init(struct imx_usbmisc_data *data) { struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); @@ -227,15 +296,86 @@ static int usbmisc_imx53_init(struct imx_usbmisc_data *data) return 0; } +static int usbmisc_imx6_hsic_set_connect(struct imx_usbmisc_data *data) +{ + unsigned long flags; + u32 val, offset; + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + int ret = 0; + + spin_lock_irqsave(&usbmisc->lock, flags); + if (data->index == 2 || data->index == 3) { + offset = (data->index - 2) * 4; + } else if (data->index == 0) { + /* + * For controllers later than imx7d (imx7d is included), + * each controller has its own non core register region. + * And the controllers before than imx7d, the 1st controller + * is not HSIC controller. + */ + offset = 0; + } else { + dev_err(data->dev, "index is error for usbmisc\n"); + offset = 0; + ret = -EINVAL; + } + + val = readl(usbmisc->base + MX6_USB_HSIC_CTRL_OFFSET + offset); + if (!(val & MX6_BM_HSIC_DEV_CONN)) + writel(val | MX6_BM_HSIC_DEV_CONN, + usbmisc->base + MX6_USB_HSIC_CTRL_OFFSET + offset); + spin_unlock_irqrestore(&usbmisc->lock, flags); + + return ret; +} + +static int usbmisc_imx6_hsic_set_clk(struct imx_usbmisc_data *data, bool on) +{ + unsigned long flags; + u32 val, offset; + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + int ret = 0; + + spin_lock_irqsave(&usbmisc->lock, flags); + if (data->index == 2 || data->index == 3) { + offset = (data->index - 2) * 4; + } else if (data->index == 0) { + offset = 0; + } else { + dev_err(data->dev, "index is error for usbmisc\n"); + offset = 0; + ret = -EINVAL; + } + + val = readl(usbmisc->base + MX6_USB_HSIC_CTRL_OFFSET + offset); + val |= MX6_BM_HSIC_EN | MX6_BM_HSIC_CLK_ON; + if (on) + val |= MX6_BM_HSIC_CLK_ON; + else + val &= ~MX6_BM_HSIC_CLK_ON; + writel(val, usbmisc->base + MX6_USB_HSIC_CTRL_OFFSET + offset); + spin_unlock_irqrestore(&usbmisc->lock, flags); + + return 0; +} + +static u32 imx6q_finalize_wakeup_setting(struct imx_usbmisc_data *data) +{ + if (data->available_role == USB_DR_MODE_PERIPHERAL) + return MX6_BM_VBUS_WAKEUP; + else if (data->available_role == USB_DR_MODE_OTG) + return MX6_BM_VBUS_WAKEUP | MX6_BM_ID_WAKEUP; + + return 0; +} + 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, wakeup_setting = MX6_BM_WAKEUP_ENABLE; if (data->index > 3) return -EINVAL; @@ -243,15 +383,20 @@ static int usbmisc_imx6q_set_wakeup spin_lock_irqsave(&usbmisc->lock, flags); val = readl(usbmisc->base + data->index * 4); if (enabled) { - val |= wakeup_setting; - writel(val, usbmisc->base + data->index * 4); + wakeup_setting |= imx6q_finalize_wakeup_setting(data); + writel(val | wakeup_setting, usbmisc->base + data->index * 4); + spin_unlock_irqrestore(&usbmisc->lock, flags); + if (vbus_wakeup_reg) + ret = regulator_enable(vbus_wakeup_reg); } else { if (val & MX6_BM_WAKEUP_INTR) pr_debug("wakeup int at ci_hdrc.%d\n", data->index); - val &= ~wakeup_setting; - writel(val, usbmisc->base + data->index * 4); + wakeup_setting |= MX6_BM_VBUS_WAKEUP | MX6_BM_ID_WAKEUP; + writel(val & ~wakeup_setting, 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); } - spin_unlock_irqrestore(&usbmisc->lock, flags); return ret; } @@ -260,7 +405,7 @@ static int usbmisc_imx6q_init(struct imx_usbmisc_data *data) { struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); unsigned long flags; - u32 reg; + u32 reg, val; if (data->index > 3) return -EINVAL; @@ -281,6 +426,27 @@ static int usbmisc_imx6q_init(struct imx_usbmisc_data *data) writel(reg | MX6_BM_NON_BURST_SETTING, usbmisc->base + data->index * 4); + /* For HSIC controller */ + if (data->index == 2 || data->index == 3) { + val = readl(usbmisc->base + data->index * 4); + writel(val | MX6_BM_UTMI_ON_CLOCK, + usbmisc->base + data->index * 4); + val = readl(usbmisc->base + MX6_USB_HSIC_CTRL_OFFSET + + (data->index - 2) * 4); + val |= MX6_BM_HSIC_EN | MX6_BM_HSIC_CLK_ON; + writel(val, usbmisc->base + MX6_USB_HSIC_CTRL_OFFSET + + (data->index - 2) * 4); + + /* + * Need to add delay to wait 24M OSC to be stable, + * It is board specific. + */ + regmap_read(data->anatop, ANADIG_ANA_MISC0, &val); + /* 0 <= data->osc_clkgate_delay <= 7 */ + if (data->osc_clkgate_delay > ANADIG_ANA_MISC0_CLK_DELAY(val)) + regmap_write(data->anatop, ANADIG_ANA_MISC0_SET, + (data->osc_clkgate_delay) << 26); + } spin_unlock_irqrestore(&usbmisc->lock, flags); usbmisc_imx6q_set_wakeup(data, false); @@ -297,9 +463,9 @@ static int usbmisc_imx6sx_init(struct imx_usbmisc_data *data) usbmisc_imx6q_init(data); + spin_lock_irqsave(&usbmisc->lock, flags); if (data->index == 0 || data->index == 1) { reg = usbmisc->base + MX6_USB_OTG1_PHY_CTRL + data->index * 4; - spin_lock_irqsave(&usbmisc->lock, flags); /* Set vbus wakeup source as bvalid */ val = readl(reg); writel(val | MX6SX_USB_VBUS_WAKEUP_SOURCE_BVALID, reg); @@ -310,9 +476,18 @@ static int usbmisc_imx6sx_init(struct imx_usbmisc_data *data) val = readl(usbmisc->base + data->index * 4); writel(val & ~MX6SX_BM_DPDM_WAKEUP_EN, usbmisc->base + data->index * 4); - spin_unlock_irqrestore(&usbmisc->lock, flags); } + /* For HSIC controller */ + if (data->index == 2) { + val = readl(usbmisc->base + MX6_USB_HSIC_CTRL_OFFSET + + (data->index - 2) * 4); + val |= MX6SX_BM_HSIC_AUTO_RESUME; + writel(val, usbmisc->base + MX6_USB_HSIC_CTRL_OFFSET + + (data->index - 2) * 4); + } + spin_unlock_irqrestore(&usbmisc->lock, flags); + return 0; } @@ -342,16 +517,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); + u32 wakeup_setting = MX6_BM_WAKEUP_ENABLE; spin_lock_irqsave(&usbmisc->lock, flags); val = readl(usbmisc->base); if (enabled) { + wakeup_setting |= imx6q_finalize_wakeup_setting(data); writel(val | wakeup_setting, usbmisc->base); } else { if (val & MX6_BM_WAKEUP_INTR) dev_dbg(data->dev, "wakeup int\n"); + wakeup_setting |= MX6_BM_VBUS_WAKEUP | MX6_BM_ID_WAKEUP; writel(val & ~wakeup_setting, usbmisc->base); } spin_unlock_irqrestore(&usbmisc->lock, flags); @@ -376,12 +552,33 @@ static int usbmisc_imx7d_init(struct imx_usbmisc_data *data) /* High active */ reg &= ~(MX6_BM_OVER_CUR_DIS | MX6_BM_OVER_CUR_POLARITY); } + + if (data->pwr_polarity) + reg |= MX6_BM_PRW_POLARITY; + writel(reg, usbmisc->base); + /* SoC non-burst setting */ + reg = readl(usbmisc->base); + writel(reg | MX6_BM_NON_BURST_SETTING, 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); + + 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); + + reg = readl(usbmisc->base + MX7D_USBNC_USB_CTRL2); + writel(reg | MX7D_USBNC_HSIC_AUTO_RESUME, + usbmisc->base + MX7D_USBNC_USB_CTRL2); + } spin_unlock_irqrestore(&usbmisc->lock, flags); usbmisc_imx7d_set_wakeup(data, false); @@ -389,6 +586,371 @@ 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; +} + + +/***************************************************************************/ +/* imx usb charger detecton */ +/***************************************************************************/ +static void usb_charger_is_present(struct usb_charger *charger, bool present) +{ + if (present) + charger->present = 1; + else + charger->present = 0; + + power_supply_changed(charger->psy); + sysfs_notify(&charger->psy->dev.kobj, NULL, "present"); +} + +static void imx6_disable_charger_detector(struct imx_usbmisc_data *data) +{ + struct regmap *regmap = data->anatop; + + regmap_write(regmap, ANADIG_USB1_CHRG_DETECT_SET, + ANADIG_USB1_CHRG_DETECT_EN_B | + ANADIG_USB1_CHRG_DETECT_CHK_CHRG_B); +} + +static int imx6_charger_data_contact_detect(struct imx_usbmisc_data *data) +{ + struct regmap *regmap = data->anatop; + struct usb_charger *charger = data->charger; + u32 val; + int i, data_pin_contact_count = 0; + + /* check if vbus is valid */ + regmap_read(regmap, ANADIG_USB1_VBUS_DET_STAT, &val); + if (!(val & ANADIG_USB1_VBUS_DET_STAT_VBUS_VALID)) { + dev_err(charger->dev, "vbus is error\n"); + return -EINVAL; + } + + /* Enable charger detector */ + regmap_write(regmap, ANADIG_USB1_CHRG_DETECT_CLR, + ANADIG_USB1_CHRG_DETECT_EN_B); + /* + * - Do not check whether a charger is connected to the USB port + * - Check whether the USB plug has been in contact with each other + */ + regmap_write(regmap, ANADIG_USB1_CHRG_DETECT_SET, + ANADIG_USB1_CHRG_DETECT_CHK_CONTACT | + ANADIG_USB1_CHRG_DETECT_CHK_CHRG_B); + + /* Check if plug is connected */ + for (i = 0; i < 100; i = i + 1) { + regmap_read(regmap, ANADIG_USB1_CHRG_DET_STAT, &val); + if (val & ANADIG_USB1_CHRG_DET_STAT_PLUG_CONTACT) { + data_pin_contact_count++; + if (data_pin_contact_count > 5) + /* Data pin makes contact */ + break; + else + usleep_range(5000, 10000); + } else { + data_pin_contact_count = 0; + usleep_range(5000, 6000); + } + } + + if (i == 100) { + dev_err(charger->dev, + "VBUS is coming from a dedicated power supply.\n"); + imx6_disable_charger_detector(data); + return -ENXIO; + } + + return 0; +} + +static int imx6_charger_primary_detection(struct imx_usbmisc_data *data) +{ + struct regmap *regmap = data->anatop; + struct usb_charger *charger = data->charger; + u32 val; + int ret; + + ret = imx6_charger_data_contact_detect(data); + if (ret) + return ret; + + /* + * - Do check whether a charger is connected to the USB port + * - Do not Check whether the USB plug has been in contact with + * each other + */ + regmap_write(regmap, ANADIG_USB1_CHRG_DETECT_CLR, + ANADIG_USB1_CHRG_DETECT_CHK_CONTACT | + ANADIG_USB1_CHRG_DETECT_CHK_CHRG_B); + + msleep(100); + + /* Check if it is a charger */ + regmap_read(regmap, ANADIG_USB1_CHRG_DET_STAT, &val); + if (!(val & ANADIG_USB1_CHRG_DET_STAT_CHRG_DETECTED)) { + dev_dbg(charger->dev, "It is a stardard downstream port\n"); + charger->psy_desc.type = POWER_SUPPLY_TYPE_USB; + charger->max_current = 500; + } + + imx6_disable_charger_detector(data); + return 0; +} + +/* + * It must be called after dp is pulled up (from USB controller driver), + * That is used to differentiate DCP and CDP + */ +int imx6_charger_secondary_detection(struct imx_usbmisc_data *data) +{ + struct regmap *regmap = data->anatop; + struct usb_charger *charger = data->charger; + int val; + + msleep(80); + + mutex_lock(&charger->lock); + regmap_read(regmap, ANADIG_USB1_CHRG_DET_STAT, &val); + if (val & ANADIG_USB1_CHRG_DET_STAT_DM_STATE) { + dev_dbg(charger->dev, "It is a dedicate charging port\n"); + charger->psy_desc.type = POWER_SUPPLY_TYPE_USB_DCP; + charger->max_current = 1500; + } else { + dev_dbg(charger->dev, "It is a charging downstream port\n"); + charger->psy_desc.type = POWER_SUPPLY_TYPE_USB_CDP; + charger->max_current = 900; + } + + usb_charger_is_present(charger, true); + mutex_unlock(&charger->lock); + + return 0; +} + +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 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); + struct usb_charger *charger = data->charger; + unsigned long flags; + u32 val; + int i, data_pin_contact_count = 0; + + spin_lock_irqsave(&usbmisc->lock, flags); + + /* 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(charger->dev, "vbus is error\n"); + spin_unlock_irqrestore(&usbmisc->lock, flags); + return -EINVAL; + } + + /* + * - Do not check whether a charger is connected to the USB port + * - Check whether the USB plug has been in contact with each other + */ + 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); + + /* Check if plug is connected */ + 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; + else + usleep_range(5000, 10000); + } else { + data_pin_contact_count = 0; + usleep_range(5000, 6000); + } + } + + if (i == 100) { + dev_err(charger->dev, + "VBUS is coming from a dedicated power supply.\n"); + imx7_disable_charger_detector(data); + 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_charger *charger = data->charger; + unsigned long flags; + u32 val; + int ret; + + ret = imx7d_charger_data_contact_detect(data); + if (ret) + return ret; + + spin_lock_irqsave(&usbmisc->lock, flags); + /* Set OPMODE to be non-driving mode */ + 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); + + /* + * - Do check whether a charger is connected to the USB port + * - Do not Check whether the USB plug has been in contact with + * each other + */ + 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, + usbmisc->base + MX7D_USB_OTG_PHY_CFG2); + spin_unlock_irqrestore(&usbmisc->lock, flags); + + usleep_range(1000, 2000); + + /* Check if it is a charger */ + val = readl(usbmisc->base + MX7D_USB_OTG_PHY_STATUS); + if (!(val & MX7D_USB_OTG_PHY_STATUS_CHRGDET)) { + dev_dbg(charger->dev, "It is a stardard downstream port\n"); + charger->psy_desc.type = POWER_SUPPLY_TYPE_USB; + charger->max_current = 500; + } + + imx7_disable_charger_detector(data); + + return 0; +} + +/* + * It must be called after dp is pulled up (from USB controller driver), + * That is used to differentiate DCP and CDP + */ +int imx7d_charger_secondary_detection(struct imx_usbmisc_data *data) +{ + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + struct usb_charger *charger = data->charger; + int val; + + msleep(80); + + mutex_lock(&charger->lock); + val = readl(usbmisc->base + MX7D_USB_OTG_PHY_STATUS); + if (val & MX7D_USB_OTG_PHY_STATUS_LINE_STATE1) { + dev_dbg(charger->dev, "It is a dedicate charging port\n"); + charger->psy_desc.type = POWER_SUPPLY_TYPE_USB_DCP; + charger->max_current = 1500; + } else { + dev_dbg(charger->dev, "It is a charging downstream port\n"); + charger->psy_desc.type = POWER_SUPPLY_TYPE_USB_CDP; + charger->max_current = 900; + } + + usb_charger_is_present(charger, true); + mutex_unlock(&charger->lock); + + return 0; +} + +static int usbmisc_term_select_override(struct imx_usbmisc_data *data, + bool enable, int val) +{ + struct imx_usbmisc *usbmisc = dev_get_drvdata(data->dev); + unsigned long flags; + u32 reg; + + spin_lock_irqsave(&usbmisc->lock, flags); + + reg = readl(usbmisc->base + MX7D_USBNC_USB_CTRL2); + if (enable) { + if (val) + writel(reg | MX7D_USB_TERMSEL_OVERRIDE, + usbmisc->base + MX7D_USBNC_USB_CTRL2); + else + writel(reg & ~MX7D_USB_TERMSEL_OVERRIDE, + usbmisc->base + MX7D_USBNC_USB_CTRL2); + + reg = readl(usbmisc->base + MX7D_USBNC_USB_CTRL2); + writel(reg | MX7D_USB_TERMSEL_OVERRIDE_EN, + usbmisc->base + MX7D_USBNC_USB_CTRL2); + } else { + writel(reg & ~MX7D_USB_TERMSEL_OVERRIDE_EN, + usbmisc->base + MX7D_USBNC_USB_CTRL2); + } + + spin_unlock_irqrestore(&usbmisc->lock, flags); + + return 0; +} + static const struct usbmisc_ops imx25_usbmisc_ops = { .init = usbmisc_imx25_init, .post = usbmisc_imx25_post, @@ -405,6 +967,10 @@ static const struct usbmisc_ops imx53_usbmisc_ops = { static const struct usbmisc_ops imx6q_usbmisc_ops = { .set_wakeup = usbmisc_imx6q_set_wakeup, .init = usbmisc_imx6q_init, + .charger_primary_detection = imx6_charger_primary_detection, + .charger_secondary_detection = imx6_charger_secondary_detection, + .hsic_set_connect = usbmisc_imx6_hsic_set_connect, + .hsic_set_clk = usbmisc_imx6_hsic_set_clk, }; static const struct usbmisc_ops vf610_usbmisc_ops = { @@ -414,11 +980,30 @@ static const struct usbmisc_ops vf610_usbmisc_ops = { static const struct usbmisc_ops imx6sx_usbmisc_ops = { .set_wakeup = usbmisc_imx6q_set_wakeup, .init = usbmisc_imx6sx_init, + .charger_primary_detection = imx6_charger_primary_detection, + .charger_secondary_detection = imx6_charger_secondary_detection, + .power_lost_check = usbmisc_imx6sx_power_lost_check, + .hsic_set_connect = usbmisc_imx6_hsic_set_connect, + .hsic_set_clk = usbmisc_imx6_hsic_set_clk, }; static const struct usbmisc_ops imx7d_usbmisc_ops = { .init = usbmisc_imx7d_init, .set_wakeup = usbmisc_imx7d_set_wakeup, + .power_lost_check = usbmisc_imx7d_power_lost_check, + .charger_primary_detection = imx7d_charger_primary_detection, + .charger_secondary_detection = imx7d_charger_secondary_detection, + .term_select_override = usbmisc_term_select_override, + .hsic_set_connect = usbmisc_imx6_hsic_set_connect, + .hsic_set_clk = usbmisc_imx6_hsic_set_clk, +}; + +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, }; int imx_usbmisc_init(struct imx_usbmisc_data *data) @@ -463,6 +1048,115 @@ int imx_usbmisc_set_wakeup(struct imx_usbmisc_data *data, bool enabled) } EXPORT_SYMBOL_GPL(imx_usbmisc_set_wakeup); +int imx_usbmisc_charger_detection(struct imx_usbmisc_data *data, bool connect) +{ + struct imx_usbmisc *usbmisc; + struct usb_charger *charger; + int ret = 0; + + if (!data) + return -EINVAL; + + charger = data->charger; + usbmisc = dev_get_drvdata(data->dev); + if (!usbmisc->ops->charger_primary_detection) + return -ENOTSUPP; + + mutex_lock(&charger->lock); + if (connect) { + charger->online = 1; + ret = usbmisc->ops->charger_primary_detection(data); + if (ret) { + dev_err(charger->dev, + "Error occurs during detection: %d\n", + ret); + } else { + if (charger->psy_desc.type == POWER_SUPPLY_TYPE_USB) + usb_charger_is_present(charger, true); + } + } else { + charger->online = 0; + charger->max_current = 0; + charger->psy_desc.type = POWER_SUPPLY_TYPE_MAINS; + + usb_charger_is_present(charger, false); + } + mutex_unlock(&charger->lock); + return ret; +} +EXPORT_SYMBOL_GPL(imx_usbmisc_charger_detection); + +int imx_usbmisc_charger_secondary_detection(struct imx_usbmisc_data *data) +{ + struct imx_usbmisc *usbmisc; + + if (!data) + return 0; + + usbmisc = dev_get_drvdata(data->dev); + if (!usbmisc->ops->charger_secondary_detection) + return 0; + return usbmisc->ops->charger_secondary_detection(data); +} +EXPORT_SYMBOL_GPL(imx_usbmisc_charger_secondary_detection); + +int imx_usbmisc_power_lost_check(struct imx_usbmisc_data *data) +{ + struct imx_usbmisc *usbmisc; + + if (!data) + return 0; + + usbmisc = dev_get_drvdata(data->dev); + if (!usbmisc->ops->power_lost_check) + return 0; + return usbmisc->ops->power_lost_check(data); +} +EXPORT_SYMBOL_GPL(imx_usbmisc_power_lost_check); + +int imx_usbmisc_hsic_set_connect(struct imx_usbmisc_data *data) +{ + struct imx_usbmisc *usbmisc; + + if (!data) + return 0; + + usbmisc = dev_get_drvdata(data->dev); + if (!usbmisc->ops->hsic_set_connect || !data->hsic) + return 0; + return usbmisc->ops->hsic_set_connect(data); +} +EXPORT_SYMBOL_GPL(imx_usbmisc_hsic_set_connect); + +int imx_usbmisc_hsic_set_clk(struct imx_usbmisc_data *data, bool on) +{ + struct imx_usbmisc *usbmisc; + + if (!data) + return 0; + + usbmisc = dev_get_drvdata(data->dev); + if (!usbmisc->ops->hsic_set_clk || !data->hsic) + return 0; + return usbmisc->ops->hsic_set_clk(data, on); +} +EXPORT_SYMBOL_GPL(imx_usbmisc_hsic_set_clk); + +int imx_usbmisc_term_select_override(struct imx_usbmisc_data *data, + bool enable, int val) +{ + struct imx_usbmisc *usbmisc; + + if (!data) + return 0; + + usbmisc = dev_get_drvdata(data->dev); + if (!usbmisc->ops->term_select_override) + return 0; + return usbmisc->ops->term_select_override(data, enable, val); +} +EXPORT_SYMBOL_GPL(imx_usbmisc_term_select_override); + static const struct of_device_id usbmisc_imx_dt_ids[] = { { .compatible = "fsl,imx25-usbmisc", @@ -504,6 +1198,10 @@ static const struct of_device_id usbmisc_imx_dt_ids[] = { .compatible = "fsl,imx7d-usbmisc", .data = &imx7d_usbmisc_ops, }, + { + .compatible = "fsl,imx7ulp-usbmisc", + .data = &imx7ulp_usbmisc_ops, + }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, usbmisc_imx_dt_ids); @@ -532,6 +1230,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(&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/common/usb-otg-fsm.c b/drivers/usb/common/usb-otg-fsm.c index 2f537bbdda09..23009e3d8ca9 100644 --- a/drivers/usb/common/usb-otg-fsm.c +++ b/drivers/usb/common/usb-otg-fsm.c @@ -68,6 +68,7 @@ static void otg_leave_state(struct otg_fsm *fsm, enum usb_otg_state old_state) switch (old_state) { case OTG_STATE_B_IDLE: otg_del_timer(fsm, B_SE0_SRP); + otg_del_timer(fsm, B_SRP_FAIL); fsm->b_se0_srp = 0; fsm->adp_sns = 0; fsm->adp_prb = 0; @@ -85,6 +86,11 @@ static void otg_leave_state(struct otg_fsm *fsm, enum usb_otg_state old_state) fsm->b_ase0_brst_tmout = 0; break; case OTG_STATE_B_HOST: + if (fsm->otg_hnp_reqd) { + fsm->otg_hnp_reqd = 0; + fsm->b_bus_req = 0; + } + fsm->a_conn = 0; break; case OTG_STATE_A_IDLE: fsm->adp_prb = 0; @@ -131,8 +137,10 @@ static void otg_hnp_polling_work(struct work_struct *work) enum usb_otg_state state = fsm->otg->state; u8 flag; int retval; + struct usb_otg_descriptor *desc = NULL; - if (state != OTG_STATE_A_HOST && state != OTG_STATE_B_HOST) + if ((state != OTG_STATE_A_HOST || !fsm->b_hnp_enable) && + state != OTG_STATE_B_HOST) return; udev = usb_hub_find_child(fsm->otg->host->root_hub, 1); @@ -142,6 +150,31 @@ static void otg_hnp_polling_work(struct work_struct *work) return; } + if (udev->state != USB_STATE_CONFIGURED) { + dev_dbg(&udev->dev, "the B dev is not resumed!\n"); + schedule_delayed_work(&fsm->hnp_polling_work, + msecs_to_jiffies(T_HOST_REQ_POLL)); + return; + } + + /* + * Legacy otg test device does not support HNP polling, + * start HNP directly for legacy otg test device. + */ + if (fsm->tst_maint && + (__usb_get_extra_descriptor(udev->rawdescriptors[0], + le16_to_cpu(udev->config[0].desc.wTotalLength), + USB_DT_OTG, (void **) &desc) == 0)) { + /* shorter bLength of OTG 1.3 or earlier */ + if (desc->bLength < 5) { + fsm->a_bus_req = 0; + fsm->tst_maint = 0; + otg_del_timer(fsm, A_TST_MAINT); + *fsm->host_req_flag = HOST_REQUEST_FLAG; + return; + } + } + *fsm->host_req_flag = 0; /* Get host request flag from connected USB device */ retval = usb_control_msg(udev, @@ -183,6 +216,11 @@ static void otg_hnp_polling_work(struct work_struct *work) fsm->otg->host->b_hnp_enable = 1; } fsm->a_bus_req = 0; + if (fsm->tst_maint) { + fsm->tst_maint = 0; + fsm->otg_vbus_off = 0; + otg_del_timer(fsm, A_TST_MAINT); + } } else if (state == OTG_STATE_B_HOST) { fsm->b_bus_req = 0; } @@ -224,6 +262,10 @@ static int otg_set_state(struct otg_fsm *fsm, enum usb_otg_state new_state) otg_start_adp_sns(fsm); otg_set_protocol(fsm, PROTO_UNDEF); otg_add_timer(fsm, B_SE0_SRP); + if (fsm->otg_hnp_reqd) { + fsm->otg_hnp_reqd = 0; + fsm->b_bus_req = 0; + } break; case OTG_STATE_B_SRP_INIT: otg_start_pulse(fsm); @@ -236,6 +278,7 @@ static int otg_set_state(struct otg_fsm *fsm, enum usb_otg_state new_state) otg_loc_sof(fsm, 0); otg_set_protocol(fsm, PROTO_GADGET); otg_loc_conn(fsm, 1); + fsm->b_bus_req = 0; break; case OTG_STATE_B_WAIT_ACON: otg_chrg_vbus(fsm, 0); @@ -250,8 +293,6 @@ static int otg_set_state(struct otg_fsm *fsm, enum usb_otg_state new_state) otg_loc_conn(fsm, 0); otg_loc_sof(fsm, 1); otg_set_protocol(fsm, PROTO_HOST); - usb_bus_start_enum(fsm->otg->host, - fsm->otg->host->otg_port); otg_start_hnp_polling(fsm); break; case OTG_STATE_A_IDLE: @@ -406,8 +447,7 @@ int otg_statemachine(struct otg_fsm *fsm) case OTG_STATE_A_HOST: if (fsm->id || fsm->a_bus_drop) otg_set_state(fsm, OTG_STATE_A_WAIT_VFALL); - else if ((!fsm->a_bus_req || fsm->a_suspend_req_inf) && - fsm->otg->host->b_hnp_enable) + else if (!fsm->a_bus_req || fsm->a_suspend_req_inf) otg_set_state(fsm, OTG_STATE_A_SUSPEND); else if (!fsm->b_conn) otg_set_state(fsm, OTG_STATE_A_WAIT_BCON); @@ -415,9 +455,9 @@ int otg_statemachine(struct otg_fsm *fsm) otg_set_state(fsm, OTG_STATE_A_VBUS_ERR); break; case OTG_STATE_A_SUSPEND: - if (!fsm->b_conn && fsm->otg->host->b_hnp_enable) + if (!fsm->b_conn && fsm->a_set_b_hnp_en) otg_set_state(fsm, OTG_STATE_A_PERIPHERAL); - else if (!fsm->b_conn && !fsm->otg->host->b_hnp_enable) + else if (!fsm->b_conn && !fsm->a_set_b_hnp_en) otg_set_state(fsm, OTG_STATE_A_WAIT_BCON); else if (fsm->a_bus_req || fsm->b_bus_resume) otg_set_state(fsm, OTG_STATE_A_HOST); diff --git a/drivers/usb/core/buffer.c b/drivers/usb/core/buffer.c index 98e39f91723a..a6cd44a711cf 100644 --- a/drivers/usb/core/buffer.c +++ b/drivers/usb/core/buffer.c @@ -63,7 +63,7 @@ int hcd_buffer_create(struct usb_hcd *hcd) int i, size; if (!IS_ENABLED(CONFIG_HAS_DMA) || - (!hcd->self.controller->dma_mask && + (!is_device_dma_capable(hcd->self.sysdev) && !(hcd->driver->flags & HCD_LOCAL_MEM))) return 0; @@ -72,7 +72,7 @@ int hcd_buffer_create(struct usb_hcd *hcd) if (!size) continue; snprintf(name, sizeof(name), "buffer-%d", size); - hcd->pool[i] = dma_pool_create(name, hcd->self.controller, + hcd->pool[i] = dma_pool_create(name, hcd->self.sysdev, size, size, 0); if (!hcd->pool[i]) { hcd_buffer_destroy(hcd); @@ -127,7 +127,7 @@ void *hcd_buffer_alloc( /* some USB hosts just use PIO */ if (!IS_ENABLED(CONFIG_HAS_DMA) || - (!bus->controller->dma_mask && + (!is_device_dma_capable(bus->sysdev) && !(hcd->driver->flags & HCD_LOCAL_MEM))) { *dma = ~(dma_addr_t) 0; return kmalloc(size, mem_flags); @@ -137,7 +137,7 @@ void *hcd_buffer_alloc( if (size <= pool_max[i]) return dma_pool_alloc(hcd->pool[i], mem_flags, dma); } - return dma_alloc_coherent(hcd->self.controller, size, dma, mem_flags); + return dma_alloc_coherent(hcd->self.sysdev, size, dma, mem_flags); } void hcd_buffer_free( @@ -154,7 +154,7 @@ void hcd_buffer_free( return; if (!IS_ENABLED(CONFIG_HAS_DMA) || - (!bus->controller->dma_mask && + (!is_device_dma_capable(bus->sysdev) && !(hcd->driver->flags & HCD_LOCAL_MEM))) { kfree(addr); return; @@ -166,5 +166,5 @@ void hcd_buffer_free( return; } } - dma_free_coherent(hcd->self.controller, size, addr, dma); + dma_free_coherent(hcd->self.sysdev, size, addr, dma); } diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index bdb0d7a08ff9..0f7ef2b2e0a6 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -1076,6 +1076,7 @@ static void usb_deregister_bus (struct usb_bus *bus) static int register_root_hub(struct usb_hcd *hcd) { struct device *parent_dev = hcd->self.controller; + struct device *sysdev = hcd->self.sysdev; struct usb_device *usb_dev = hcd->self.root_hub; const int devnum = 1; int retval; @@ -1122,7 +1123,7 @@ static int register_root_hub(struct usb_hcd *hcd) /* Did the HC die before the root hub was registered? */ if (HCD_DEAD(hcd)) usb_hc_died (hcd); /* This time clean up */ - usb_dev->dev.of_node = parent_dev->of_node; + usb_dev->dev.of_node = sysdev->of_node; } mutex_unlock(&usb_bus_idr_lock); @@ -1435,7 +1436,7 @@ void usb_hcd_unmap_urb_setup_for_dma(struct usb_hcd *hcd, struct urb *urb) { if (IS_ENABLED(CONFIG_HAS_DMA) && (urb->transfer_flags & URB_SETUP_MAP_SINGLE)) - dma_unmap_single(hcd->self.controller, + dma_unmap_single(hcd->self.sysdev, urb->setup_dma, sizeof(struct usb_ctrlrequest), DMA_TO_DEVICE); @@ -1468,19 +1469,19 @@ void usb_hcd_unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb) dir = usb_urb_dir_in(urb) ? DMA_FROM_DEVICE : DMA_TO_DEVICE; if (IS_ENABLED(CONFIG_HAS_DMA) && (urb->transfer_flags & URB_DMA_MAP_SG)) - dma_unmap_sg(hcd->self.controller, + dma_unmap_sg(hcd->self.sysdev, urb->sg, urb->num_sgs, dir); else if (IS_ENABLED(CONFIG_HAS_DMA) && (urb->transfer_flags & URB_DMA_MAP_PAGE)) - dma_unmap_page(hcd->self.controller, + dma_unmap_page(hcd->self.sysdev, urb->transfer_dma, urb->transfer_buffer_length, dir); else if (IS_ENABLED(CONFIG_HAS_DMA) && (urb->transfer_flags & URB_DMA_MAP_SINGLE)) - dma_unmap_single(hcd->self.controller, + dma_unmap_single(hcd->self.sysdev, urb->transfer_dma, urb->transfer_buffer_length, dir); @@ -1523,11 +1524,11 @@ int usb_hcd_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb, return ret; if (IS_ENABLED(CONFIG_HAS_DMA) && hcd->self.uses_dma) { urb->setup_dma = dma_map_single( - hcd->self.controller, + hcd->self.sysdev, urb->setup_packet, sizeof(struct usb_ctrlrequest), DMA_TO_DEVICE); - if (dma_mapping_error(hcd->self.controller, + if (dma_mapping_error(hcd->self.sysdev, urb->setup_dma)) return -EAGAIN; urb->transfer_flags |= URB_SETUP_MAP_SINGLE; @@ -1558,7 +1559,7 @@ int usb_hcd_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb, } n = dma_map_sg( - hcd->self.controller, + hcd->self.sysdev, urb->sg, urb->num_sgs, dir); @@ -1573,12 +1574,12 @@ int usb_hcd_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb, } else if (urb->sg) { struct scatterlist *sg = urb->sg; urb->transfer_dma = dma_map_page( - hcd->self.controller, + hcd->self.sysdev, sg_page(sg), sg->offset, urb->transfer_buffer_length, dir); - if (dma_mapping_error(hcd->self.controller, + if (dma_mapping_error(hcd->self.sysdev, urb->transfer_dma)) ret = -EAGAIN; else @@ -1588,11 +1589,11 @@ int usb_hcd_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb, ret = -EAGAIN; } else { urb->transfer_dma = dma_map_single( - hcd->self.controller, + hcd->self.sysdev, urb->transfer_buffer, urb->transfer_buffer_length, dir); - if (dma_mapping_error(hcd->self.controller, + if (dma_mapping_error(hcd->self.sysdev, urb->transfer_dma)) ret = -EAGAIN; else @@ -2232,6 +2233,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 @@ -2501,24 +2636,8 @@ static void init_giveback_urb_bh(struct giveback_urb_bh *bh) tasklet_init(&bh->bh, usb_giveback_urb_bh, (unsigned long)bh); } -/** - * usb_create_shared_hcd - create and initialize an HCD structure - * @driver: HC driver that will use this hcd - * @dev: device for this HC, stored in hcd->self.controller - * @bus_name: value to store in hcd->self.bus_name - * @primary_hcd: a pointer to the usb_hcd structure that is sharing the - * PCI device. Only allocate certain resources for the primary HCD - * Context: !in_interrupt() - * - * Allocate a struct usb_hcd, with extra space at the end for the - * HC driver's private data. Initialize the generic members of the - * hcd structure. - * - * Return: On success, a pointer to the created and initialized HCD structure. - * On failure (e.g. if memory is unavailable), %NULL. - */ -struct usb_hcd *usb_create_shared_hcd(const struct hc_driver *driver, - struct device *dev, const char *bus_name, +struct usb_hcd *__usb_create_hcd(const struct hc_driver *driver, + struct device *sysdev, struct device *dev, const char *bus_name, struct usb_hcd *primary_hcd) { struct usb_hcd *hcd; @@ -2560,8 +2679,9 @@ struct usb_hcd *usb_create_shared_hcd(const struct hc_driver *driver, usb_bus_init(&hcd->self); hcd->self.controller = dev; + hcd->self.sysdev = sysdev; hcd->self.bus_name = bus_name; - hcd->self.uses_dma = (dev->dma_mask != NULL); + hcd->self.uses_dma = (sysdev->dma_mask != NULL); init_timer(&hcd->rh_timer); hcd->rh_timer.function = rh_timer_func; @@ -2576,6 +2696,30 @@ struct usb_hcd *usb_create_shared_hcd(const struct hc_driver *driver, "USB Host Controller"; return hcd; } +EXPORT_SYMBOL_GPL(__usb_create_hcd); + +/** + * usb_create_shared_hcd - create and initialize an HCD structure + * @driver: HC driver that will use this hcd + * @dev: device for this HC, stored in hcd->self.controller + * @bus_name: value to store in hcd->self.bus_name + * @primary_hcd: a pointer to the usb_hcd structure that is sharing the + * PCI device. Only allocate certain resources for the primary HCD + * Context: !in_interrupt() + * + * Allocate a struct usb_hcd, with extra space at the end for the + * HC driver's private data. Initialize the generic members of the + * hcd structure. + * + * Return: On success, a pointer to the created and initialized HCD structure. + * On failure (e.g. if memory is unavailable), %NULL. + */ +struct usb_hcd *usb_create_shared_hcd(const struct hc_driver *driver, + struct device *dev, const char *bus_name, + struct usb_hcd *primary_hcd) +{ + return __usb_create_hcd(driver, dev, dev, bus_name, primary_hcd); +} EXPORT_SYMBOL_GPL(usb_create_shared_hcd); /** @@ -2595,7 +2739,7 @@ EXPORT_SYMBOL_GPL(usb_create_shared_hcd); struct usb_hcd *usb_create_hcd(const struct hc_driver *driver, struct device *dev, const char *bus_name) { - return usb_create_shared_hcd(driver, dev, bus_name, NULL); + return __usb_create_hcd(driver, dev, dev, bus_name, NULL); } EXPORT_SYMBOL_GPL(usb_create_hcd); @@ -2722,7 +2866,7 @@ int usb_add_hcd(struct usb_hcd *hcd, struct usb_device *rhdev; if (IS_ENABLED(CONFIG_USB_PHY) && !hcd->usb_phy) { - struct usb_phy *phy = usb_get_phy_dev(hcd->self.controller, 0); + struct usb_phy *phy = usb_get_phy_dev(hcd->self.sysdev, 0); if (IS_ERR(phy)) { retval = PTR_ERR(phy); @@ -2740,7 +2884,7 @@ int usb_add_hcd(struct usb_hcd *hcd, } if (IS_ENABLED(CONFIG_GENERIC_PHY) && !hcd->phy) { - struct phy *phy = phy_get(hcd->self.controller, "usb"); + struct phy *phy = phy_get(hcd->self.sysdev, "usb"); if (IS_ERR(phy)) { retval = PTR_ERR(phy); @@ -2788,7 +2932,7 @@ int usb_add_hcd(struct usb_hcd *hcd, */ retval = hcd_buffer_create(hcd); if (retval != 0) { - dev_dbg(hcd->self.controller, "pool alloc failed\n"); + dev_dbg(hcd->self.sysdev, "pool alloc failed\n"); goto err_create_buf; } @@ -2798,7 +2942,7 @@ int usb_add_hcd(struct usb_hcd *hcd, rhdev = usb_alloc_dev(NULL, &hcd->self, 0); if (rhdev == NULL) { - dev_err(hcd->self.controller, "unable to allocate root hub\n"); + dev_err(hcd->self.sysdev, "unable to allocate root hub\n"); retval = -ENOMEM; goto err_allocate_root_hub; } diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 7aee55244b4a..6436ca2f5d3e 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -21,6 +21,7 @@ #include <linux/usbdevice_fs.h> #include <linux/usb/hcd.h> #include <linux/usb/otg.h> +#include <linux/usb/otg-fsm.h> #include <linux/usb/quirks.h> #include <linux/workqueue.h> #include <linux/mutex.h> @@ -1113,6 +1114,10 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type) need_debounce_delay = true; usb_clear_port_feature(hub->hdev, port1, USB_PORT_FEAT_C_CONNECTION); +#ifdef CONFIG_USB_OTG + if (hdev->bus->is_b_host) + usb_bus_start_enum(hdev->bus, port1); +#endif } if (portchange & USB_PORT_STAT_C_ENABLE) { need_debounce_delay = true; @@ -2207,9 +2212,9 @@ static inline void announce_device(struct usb_device *udev) { } */ static int usb_enumerate_device_otg(struct usb_device *udev) { +#ifdef CONFIG_USB_OTG int err = 0; -#ifdef CONFIG_USB_OTG /* * OTG-aware devices on OTG-capable root hubs may be able to use SRP, * to wake us after we've powered off VBUS; and HNP, switching roles @@ -2250,6 +2255,12 @@ static int usb_enumerate_device_otg(struct usb_device *udev) err); bus->b_hnp_enable = 0; } + + if (bus->otg_fsm) { + bus->otg_fsm->b_hnp_enable = 1; + if (bus->b_hnp_enable) + bus->otg_fsm->a_set_b_hnp_en = 1; + } } else if (desc->bLength == sizeof (struct usb_otg_descriptor)) { /* Set a_alt_hnp_support for legacy otg device */ @@ -2266,7 +2277,7 @@ static int usb_enumerate_device_otg(struct usb_device *udev) } } #endif - return err; + return 0; } @@ -2288,6 +2299,7 @@ static int usb_enumerate_device(struct usb_device *udev) { int err; struct usb_hcd *hcd = bus_to_hcd(udev->bus); + struct otg_fsm *fsm = udev->bus->otg_fsm; if (udev->config == NULL) { err = usb_get_configuration(udev); @@ -2319,8 +2331,10 @@ static int usb_enumerate_device(struct usb_device *udev) err = usb_port_suspend(udev, PMSG_AUTO_SUSPEND); if (err < 0) dev_dbg(&udev->dev, "HNP fail, %d\n", err); + return -ENOTSUPP; + } else if (!fsm || !fsm->b_hnp_enable || !fsm->hnp_polling) { + return -ENOTSUPP; } - return -ENOTSUPP; } usb_detect_interface_quirks(udev); @@ -4364,6 +4378,7 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1, enum usb_device_speed oldspeed = udev->speed; const char *speed; int devnum = udev->devnum; + const char *driver_name; /* root hub ports have a slightly longer reset period * (from USB 2.0 spec, section 7.1.7.5) @@ -4431,11 +4446,23 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1, else speed = usb_speed_string(udev->speed); + /* + * The controller driver may be NULL if the controller device + * is the middle device between platform device and roothub. + * This middle device may not need a device driver due to + * all hardware control can be at platform device driver, this + * platform device is usually a dual-role USB controller device. + */ + if (udev->bus->controller->driver) + driver_name = udev->bus->controller->driver->name; + else + driver_name = udev->bus->sysdev->driver->name; + if (udev->speed < USB_SPEED_SUPER) dev_info(&udev->dev, "%s %s USB device number %d using %s\n", (udev->config) ? "reset" : "new", speed, - devnum, udev->bus->controller->driver->name); + devnum, driver_name); /* Set up TT records, if needed */ if (hdev->tt) { @@ -4537,7 +4564,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; @@ -4569,7 +4597,7 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1, "%s SuperSpeed%s USB device number %d using %s\n", (udev->config) ? "reset" : "new", (udev->speed == USB_SPEED_SUPER_PLUS) ? "Plus" : "", - devnum, udev->bus->controller->driver->name); + devnum, driver_name); } /* cope with hardware quirkiness: diff --git a/drivers/usb/core/otg_whitelist.h b/drivers/usb/core/otg_whitelist.h index 085049d37d7a..68b4f7e80b47 100644 --- a/drivers/usb/core/otg_whitelist.h +++ b/drivers/usb/core/otg_whitelist.h @@ -17,35 +17,64 @@ */ 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 +#define TEST_OTG_TEST_DEVICE_SUPPORT 0x0200 +{ 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) }, +{ USB_DEVICE(0x1a0a, TEST_OTG_TEST_DEVICE_SUPPORT) }, #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; @@ -60,6 +89,19 @@ static int is_targeted(struct usb_device *dev) le16_to_cpu(dev->descriptor.idProduct) == 0x0200)) return 1; + /* Unknown Device Not Supporting HNP */ + if ((le16_to_cpu(dev->descriptor.idVendor) == 0x1a0a && + le16_to_cpu(dev->descriptor.idProduct) == 0x0201)) { + dev_warn(&dev->dev, "Unsupported Device\n"); + return 0; + } + /* Unknown Device Supporting HNP */ + if ((le16_to_cpu(dev->descriptor.idVendor) == 0x1a0a && + le16_to_cpu(dev->descriptor.idProduct) == 0x0202)) { + dev_warn(&dev->dev, "Device no Responding\n"); + return 0; + } + /* NOTE: can't use usb_match_id() since interface caches * aren't set up yet. this is cut/paste from that code. */ @@ -94,6 +136,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/core/usb.c b/drivers/usb/core/usb.c index eaf1c3b06f02..e1ab778e774a 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -452,9 +452,9 @@ struct usb_device *usb_alloc_dev(struct usb_device *parent, * Note: calling dma_set_mask() on a USB device would set the * mask for the entire HCD, so don't do that. */ - dev->dev.dma_mask = bus->controller->dma_mask; - dev->dev.dma_pfn_offset = bus->controller->dma_pfn_offset; - set_dev_node(&dev->dev, dev_to_node(bus->controller)); + dev->dev.dma_mask = bus->sysdev->dma_mask; + dev->dev.dma_pfn_offset = bus->sysdev->dma_pfn_offset; + set_dev_node(&dev->dev, dev_to_node(bus->sysdev)); dev->state = USB_STATE_ATTACHED; dev->lpm_disable_count = 1; atomic_set(&dev->urbnum, 0); @@ -802,7 +802,7 @@ struct urb *usb_buffer_map(struct urb *urb) if (!urb || !urb->dev || !(bus = urb->dev->bus) - || !(controller = bus->controller)) + || !(controller = bus->sysdev)) return NULL; if (controller->dma_mask) { @@ -840,7 +840,7 @@ void usb_buffer_dmasync(struct urb *urb) || !(urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP) || !urb->dev || !(bus = urb->dev->bus) - || !(controller = bus->controller)) + || !(controller = bus->sysdev)) return; if (controller->dma_mask) { @@ -874,7 +874,7 @@ void usb_buffer_unmap(struct urb *urb) || !(urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP) || !urb->dev || !(bus = urb->dev->bus) - || !(controller = bus->controller)) + || !(controller = bus->sysdev)) return; if (controller->dma_mask) { @@ -924,7 +924,7 @@ int usb_buffer_map_sg(const struct usb_device *dev, int is_in, if (!dev || !(bus = dev->bus) - || !(controller = bus->controller) + || !(controller = bus->sysdev) || !controller->dma_mask) return -EINVAL; @@ -960,7 +960,7 @@ void usb_buffer_dmasync_sg(const struct usb_device *dev, int is_in, if (!dev || !(bus = dev->bus) - || !(controller = bus->controller) + || !(controller = bus->sysdev) || !controller->dma_mask) return; @@ -988,7 +988,7 @@ void usb_buffer_unmap_sg(const struct usb_device *dev, int is_in, if (!dev || !(bus = dev->bus) - || !(controller = bus->controller) + || !(controller = bus->sysdev) || !controller->dma_mask) return; diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig index b97cde76914d..3a0481b93118 100644 --- a/drivers/usb/dwc3/Kconfig +++ b/drivers/usb/dwc3/Kconfig @@ -41,6 +41,7 @@ config USB_DWC3_GADGET config USB_DWC3_DUAL_ROLE bool "Dual Role mode" depends on ((USB=y || USB=USB_DWC3) && (USB_GADGET=y || USB_GADGET=USB_DWC3)) + depends on (EXTCON=y || EXTCON=USB_DWC3) help This is the default mode of working of DWC3 controller where both host and gadget features are enabled. diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile index 22420e17d68b..e2a3b265b066 100644 --- a/drivers/usb/dwc3/Makefile +++ b/drivers/usb/dwc3/Makefile @@ -13,6 +13,10 @@ ifneq ($(filter y,$(CONFIG_USB_DWC3_GADGET) $(CONFIG_USB_DWC3_DUAL_ROLE)),) dwc3-y += gadget.o ep0.o endif +ifneq ($(CONFIG_USB_DWC3_DUAL_ROLE),) + dwc3-y += drd.o +endif + ifneq ($(CONFIG_USB_DWC3_ULPI),) dwc3-y += ulpi.o endif diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 53b26e978d90..f4f2a1337dd4 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -100,7 +100,10 @@ static int dwc3_get_dr_mode(struct dwc3 *dwc) return 0; } -void dwc3_set_mode(struct dwc3 *dwc, u32 mode) +static void dwc3_event_buffers_cleanup(struct dwc3 *dwc); +static int dwc3_event_buffers_setup(struct dwc3 *dwc); + +static void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode) { u32 reg; @@ -110,6 +113,93 @@ void dwc3_set_mode(struct dwc3 *dwc, u32 mode) dwc3_writel(dwc->regs, DWC3_GCTL, reg); } +static void __dwc3_set_mode(struct work_struct *work) +{ + struct dwc3 *dwc = work_to_dwc(work); + unsigned long flags; + int ret; + + if (!dwc->desired_dr_role) + return; + + if (dwc->desired_dr_role == dwc->current_dr_role) + return; + + if (dwc->dr_mode != USB_DR_MODE_OTG) + return; + + switch (dwc->current_dr_role) { + case DWC3_GCTL_PRTCAP_HOST: + dwc3_host_exit(dwc); + break; + case DWC3_GCTL_PRTCAP_DEVICE: + dwc3_gadget_exit(dwc); + dwc3_event_buffers_cleanup(dwc); + break; + default: + break; + } + + spin_lock_irqsave(&dwc->lock, flags); + + dwc3_set_prtcap(dwc, dwc->desired_dr_role); + + dwc->current_dr_role = dwc->desired_dr_role; + + spin_unlock_irqrestore(&dwc->lock, flags); + + switch (dwc->desired_dr_role) { + case DWC3_GCTL_PRTCAP_HOST: + ret = dwc3_host_init(dwc); + if (ret) + dev_err(dwc->dev, "failed to initialize host\n"); + break; + case DWC3_GCTL_PRTCAP_DEVICE: + dwc3_event_buffers_setup(dwc); + ret = dwc3_gadget_init(dwc); + if (ret) + dev_err(dwc->dev, "failed to initialize peripheral\n"); + break; + default: + break; + } +} + +void dwc3_set_mode(struct dwc3 *dwc, u32 mode) +{ + unsigned long flags; + + spin_lock_irqsave(&dwc->lock, flags); + dwc->desired_dr_role = mode; + spin_unlock_irqrestore(&dwc->lock, flags); + + queue_work(system_freezable_wq, &dwc->drd_work); +} + +static int dwc3_set_suspend_clk(struct dwc3 *dwc) +{ + u32 reg, scale; + + /* + * DWC3_GCTL.PWRDNSCALE: The USB3 suspend_clk input replaces + * pipe3_rx_pclk as a clock source to a small part of the USB3 + * core that operates when the SS PHY is in its lowest power + * (P3) state, and therefore does not provide a clock. + * The Power Down Scale field specifies how many suspend_clk + * periods fit into a 16 kHz clock period. When performing the + * division, round up the remainder. + */ + if (!device_property_read_u32(dwc->dev, "snps,power-down-scale", + &scale)) { + reg = dwc3_readl(dwc->regs, DWC3_GCTL); + reg &= ~(DWC3_GCTL_PWRDNSCALE_MASK); + reg |= DWC3_GCTL_PWRDNSCALE(scale); + dwc3_writel(dwc->regs, DWC3_GCTL, reg); + } + + return 0; +} + u32 dwc3_core_fifo_space(struct dwc3_ep *dep, u8 type) { struct dwc3 *dwc = dep->dwc; @@ -663,6 +753,9 @@ static int dwc3_core_init(struct dwc3 *dwc) if (ret) goto err0; + /* Set suspend_clk */ + dwc3_set_suspend_clk(dwc); + ret = dwc3_phy_setup(dwc); if (ret) goto err0; @@ -760,21 +853,6 @@ static int dwc3_core_init(struct dwc3 *dwc) goto err4; } - switch (dwc->dr_mode) { - case USB_DR_MODE_PERIPHERAL: - dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE); - break; - case USB_DR_MODE_HOST: - dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_HOST); - break; - case USB_DR_MODE_OTG: - dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_OTG); - break; - default: - dev_warn(dwc->dev, "Unsupported mode %d\n", dwc->dr_mode); - break; - } - /* * ENDXFER polling is available on version 3.10a and later of * the DWC_usb3 controller. It is NOT available in the @@ -882,6 +960,8 @@ static int dwc3_core_init_mode(struct dwc3 *dwc) switch (dwc->dr_mode) { case USB_DR_MODE_PERIPHERAL: + dwc->current_dr_role = DWC3_GCTL_PRTCAP_DEVICE; + dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_DEVICE); ret = dwc3_gadget_init(dwc); if (ret) { if (ret != -EPROBE_DEFER) @@ -890,6 +970,8 @@ static int dwc3_core_init_mode(struct dwc3 *dwc) } break; case USB_DR_MODE_HOST: + dwc->current_dr_role = DWC3_GCTL_PRTCAP_HOST; + dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_HOST); ret = dwc3_host_init(dwc); if (ret) { if (ret != -EPROBE_DEFER) @@ -898,17 +980,11 @@ static int dwc3_core_init_mode(struct dwc3 *dwc) } break; case USB_DR_MODE_OTG: - ret = dwc3_host_init(dwc); - if (ret) { - if (ret != -EPROBE_DEFER) - dev_err(dev, "failed to initialize host\n"); - return ret; - } - - ret = dwc3_gadget_init(dwc); + INIT_WORK(&dwc->drd_work, __dwc3_set_mode); + ret = dwc3_drd_init(dwc); if (ret) { if (ret != -EPROBE_DEFER) - dev_err(dev, "failed to initialize gadget\n"); + dev_err(dev, "failed to initialize dual-role\n"); return ret; } break; @@ -930,8 +1006,7 @@ static void dwc3_core_exit_mode(struct dwc3 *dwc) dwc3_host_exit(dwc); break; case USB_DR_MODE_OTG: - dwc3_host_exit(dwc); - dwc3_gadget_exit(dwc); + dwc3_drd_exit(dwc); break; default: /* do nothing */ @@ -1061,6 +1136,19 @@ static int dwc3_probe(struct platform_device *pdev) dwc->hird_threshold = hird_threshold | (dwc->is_utmi_l1_suspend << 4); + 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 */ + ret = of_usb_update_otg_caps(dev->of_node, + &dwc->otg_caps); + if (ret) + goto err0; + } + platform_set_drvdata(pdev, dwc); dwc3_cache_hwparams(dwc); @@ -1205,21 +1293,19 @@ static int dwc3_suspend_common(struct dwc3 *dwc) { unsigned long flags; - switch (dwc->dr_mode) { - case USB_DR_MODE_PERIPHERAL: - case USB_DR_MODE_OTG: + switch (dwc->current_dr_role) { + case DWC3_GCTL_PRTCAP_DEVICE: spin_lock_irqsave(&dwc->lock, flags); dwc3_gadget_suspend(dwc); spin_unlock_irqrestore(&dwc->lock, flags); + dwc3_core_exit(dwc); break; - case USB_DR_MODE_HOST: + case DWC3_GCTL_PRTCAP_HOST: default: /* do nothing */ break; } - dwc3_core_exit(dwc); - return 0; } @@ -1228,18 +1314,17 @@ static int dwc3_resume_common(struct dwc3 *dwc) unsigned long flags; int ret; - ret = dwc3_core_init(dwc); - if (ret) - return ret; + switch (dwc->current_dr_role) { + case DWC3_GCTL_PRTCAP_DEVICE: + ret = dwc3_core_init(dwc); + if (ret) + return ret; - switch (dwc->dr_mode) { - case USB_DR_MODE_PERIPHERAL: - case USB_DR_MODE_OTG: spin_lock_irqsave(&dwc->lock, flags); dwc3_gadget_resume(dwc); spin_unlock_irqrestore(&dwc->lock, flags); - /* FALLTHROUGH */ - case USB_DR_MODE_HOST: + break; + case DWC3_GCTL_PRTCAP_HOST: default: /* do nothing */ break; @@ -1250,7 +1335,7 @@ static int dwc3_resume_common(struct dwc3 *dwc) static int dwc3_runtime_checks(struct dwc3 *dwc) { - switch (dwc->dr_mode) { + switch (dwc->current_dr_role) { case USB_DR_MODE_PERIPHERAL: case USB_DR_MODE_OTG: if (dwc->connected) @@ -1293,12 +1378,11 @@ static int dwc3_runtime_resume(struct device *dev) if (ret) return ret; - switch (dwc->dr_mode) { - case USB_DR_MODE_PERIPHERAL: - case USB_DR_MODE_OTG: + switch (dwc->current_dr_role) { + case DWC3_GCTL_PRTCAP_DEVICE: dwc3_gadget_process_pending_events(dwc); break; - case USB_DR_MODE_HOST: + case DWC3_GCTL_PRTCAP_HOST: default: /* do nothing */ break; @@ -1314,13 +1398,12 @@ static int dwc3_runtime_idle(struct device *dev) { struct dwc3 *dwc = dev_get_drvdata(dev); - switch (dwc->dr_mode) { - case USB_DR_MODE_PERIPHERAL: - case USB_DR_MODE_OTG: + switch (dwc->current_dr_role) { + case DWC3_GCTL_PRTCAP_DEVICE: if (dwc3_runtime_checks(dwc)) return -EBUSY; break; - case USB_DR_MODE_HOST: + case DWC3_GCTL_PRTCAP_HOST: default: /* do nothing */ break; diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index affb3d757310..c621a0fbc9a7 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -26,6 +26,7 @@ #include <linux/dma-mapping.h> #include <linux/mm.h> #include <linux/debugfs.h> +#include <linux/wait.h> #include <linux/usb/ch9.h> #include <linux/usb/gadget.h> @@ -176,6 +177,7 @@ /* Global Configuration Register */ #define DWC3_GCTL_PWRDNSCALE(n) ((n) << 19) +#define DWC3_GCTL_PWRDNSCALE_MASK DWC3_GCTL_PWRDNSCALE(0x1fff) #define DWC3_GCTL_U2RSTECN (1 << 16) #define DWC3_GCTL_RAMCLKSEL(x) (((x) & DWC3_GCTL_CLK_MASK) << 6) #define DWC3_GCTL_CLK_BUS (0) @@ -451,6 +453,8 @@ #define DWC3_DEPCMD_SETTRANSFRESOURCE (0x02 << 0) #define DWC3_DEPCMD_SETEPCONFIG (0x01 << 0) +#define DWC3_DEPCMD_CMD(x) ((x) & 0xf) + /* The EP number goes 0..31 so ep0 is always out and ep1 is always in */ #define DWC3_DALEPENA_EP(n) (1 << n) @@ -500,6 +504,7 @@ struct dwc3_event_buffer { * @endpoint: usb endpoint * @pending_list: list of pending requests for this endpoint * @started_list: list of started requests on this endpoint + * @wait_end_transfer: wait_queue_head_t for waiting on End Transfer complete * @lock: spinlock for endpoint request queue traversal * @regs: pointer to first endpoint register * @trb_pool: array of transaction buffers @@ -525,6 +530,8 @@ struct dwc3_ep { struct list_head pending_list; struct list_head started_list; + wait_queue_head_t wait_end_transfer; + spinlock_t lock; void __iomem *regs; @@ -541,6 +548,8 @@ struct dwc3_ep { #define DWC3_EP_BUSY (1 << 4) #define DWC3_EP_PENDING_REQUEST (1 << 5) #define DWC3_EP_MISSED_ISOC (1 << 6) +#define DWC3_EP_END_TRANSFER_PENDING (1 << 7) +#define DWC3_EP_TRANSFER_STARTED (1 << 8) /* This last one is specific to EP0 */ #define DWC3_EP0_DIR_IN (1 << 31) @@ -740,6 +749,7 @@ struct dwc3_scratchpad_array { /** * struct dwc3 - representation of our controller * @ctrl_req: usb control request which is used for ep0 + * @drd_work - workqueue used for role swapping * @ep0_trb: trb which is used for the ctrl_req * @ep0_bounce: bounce buffer for ep0 * @zlp_buf: used when request->zero is set @@ -764,6 +774,10 @@ struct dwc3_scratchpad_array { * @maximum_speed: maximum speed requested (mainly for testing purposes) * @revision: revision register contents * @dr_mode: requested mode of operation + * @current_dr_role: current role of operation when in dual-role mode + * @desired_dr_role: desired role of operation when in dual-role mode + * @edev: extcon handle + * @edev_nb: extcon notifier * @hsphy_mode: UTMI phy mode, one of following: * - USBPHY_INTERFACE_MODE_UTMI * - USBPHY_INTERFACE_MODE_UTMIW @@ -834,9 +848,11 @@ struct dwc3_scratchpad_array { * 1 - -3.5dB de-emphasis * 2 - No de-emphasis * 3 - Reserved + * @otg_caps: the OTG capabilities from hardware point */ struct dwc3 { struct usb_ctrlrequest *ctrl_req; + struct work_struct drd_work; struct dwc3_trb *ep0_trb; void *ep0_bounce; void *zlp_buf; @@ -874,6 +890,10 @@ struct dwc3 { size_t regs_size; enum usb_dr_mode dr_mode; + u32 current_dr_role; + u32 desired_dr_role; + struct extcon_dev *edev; + struct notifier_block edev_nb; enum usb_phy_interface hsphy_mode; u32 fladj; @@ -979,9 +999,10 @@ struct dwc3 { unsigned tx_de_emphasis_quirk:1; unsigned tx_de_emphasis:2; + struct usb_otg_caps otg_caps; }; -/* -------------------------------------------------------------------------- */ +#define work_to_dwc(w) (container_of((w), struct dwc3, drd_work)) /* -------------------------------------------------------------------------- */ @@ -1046,6 +1067,9 @@ struct dwc3_event_depevt { #define DEPEVT_TRANSFER_BUS_EXPIRY 2 u32 parameters:16; + +/* For Command Complete Events */ +#define DEPEVT_PARAMETER_CMD(n) (((n) & (0xf << 8)) >> 8) } __packed; /** @@ -1180,6 +1204,16 @@ static inline int dwc3_send_gadget_generic_command(struct dwc3 *dwc, { return 0; } #endif +#if IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE) +int dwc3_drd_init(struct dwc3 *dwc); +void dwc3_drd_exit(struct dwc3 *dwc); +#else +static inline int dwc3_drd_init(struct dwc3 *dwc) +{ return 0; } +static inline void dwc3_drd_exit(struct dwc3 *dwc) +{ } +#endif + /* power management interface */ #if !IS_ENABLED(CONFIG_USB_DWC3_HOST) int dwc3_gadget_suspend(struct dwc3 *dwc); diff --git a/drivers/usb/dwc3/debugfs.c b/drivers/usb/dwc3/debugfs.c index 31926dda43c9..ef7dccb55c47 100644 --- a/drivers/usb/dwc3/debugfs.c +++ b/drivers/usb/dwc3/debugfs.c @@ -319,7 +319,6 @@ static ssize_t dwc3_mode_write(struct file *file, { struct seq_file *s = file->private_data; struct dwc3 *dwc = s->private; - unsigned long flags; u32 mode = 0; char buf[32]; @@ -327,19 +326,16 @@ static ssize_t dwc3_mode_write(struct file *file, return -EFAULT; if (!strncmp(buf, "host", 4)) - mode |= DWC3_GCTL_PRTCAP_HOST; + mode = DWC3_GCTL_PRTCAP_HOST; if (!strncmp(buf, "device", 6)) - mode |= DWC3_GCTL_PRTCAP_DEVICE; + mode = DWC3_GCTL_PRTCAP_DEVICE; if (!strncmp(buf, "otg", 3)) - mode |= DWC3_GCTL_PRTCAP_OTG; + mode = DWC3_GCTL_PRTCAP_OTG; + + dwc3_set_mode(dwc, mode); - if (mode) { - spin_lock_irqsave(&dwc->lock, flags); - dwc3_set_mode(dwc, mode); - spin_unlock_irqrestore(&dwc->lock, flags); - } return count; } diff --git a/drivers/usb/dwc3/drd.c b/drivers/usb/dwc3/drd.c new file mode 100644 index 000000000000..2765c51c7ef5 --- /dev/null +++ b/drivers/usb/dwc3/drd.c @@ -0,0 +1,85 @@ +/** + * drd.c - DesignWare USB3 DRD Controller Dual-role support + * + * Copyright (C) 2017 Texas Instruments Incorporated - http://www.ti.com + * + * Authors: Roger Quadros <rogerq@ti.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/extcon.h> + +#include "debug.h" +#include "core.h" +#include "gadget.h" + +static void dwc3_drd_update(struct dwc3 *dwc) +{ + int id; + + id = extcon_get_state(dwc->edev, EXTCON_USB_HOST); + if (id < 0) + id = 0; + + dwc3_set_mode(dwc, id ? + DWC3_GCTL_PRTCAP_HOST : + DWC3_GCTL_PRTCAP_DEVICE); +} + +static int dwc3_drd_notifier(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct dwc3 *dwc = container_of(nb, struct dwc3, edev_nb); + + dwc3_set_mode(dwc, event ? + DWC3_GCTL_PRTCAP_HOST : + DWC3_GCTL_PRTCAP_DEVICE); + + return NOTIFY_DONE; +} + +int dwc3_drd_init(struct dwc3 *dwc) +{ + int ret; + + if (dwc->dev->of_node) { + if (of_property_read_bool(dwc->dev->of_node, "extcon")) + dwc->edev = extcon_get_edev_by_phandle(dwc->dev, 0); + + if (IS_ERR(dwc->edev)) + return PTR_ERR(dwc->edev); + + dwc->edev_nb.notifier_call = dwc3_drd_notifier; + ret = extcon_register_notifier(dwc->edev, EXTCON_USB_HOST, + &dwc->edev_nb); + if (ret < 0) { + dev_err(dwc->dev, "couldn't register cable notifier\n"); + return ret; + } + } + + dwc3_drd_update(dwc); + + return 0; +} + +void dwc3_drd_exit(struct dwc3 *dwc) +{ + extcon_unregister_notifier(dwc->edev, EXTCON_USB_HOST, + &dwc->edev_nb); + + dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE); + flush_work(&dwc->drd_work); + dwc3_gadget_exit(dwc); +} diff --git a/drivers/usb/dwc3/dwc3-of-simple.c b/drivers/usb/dwc3/dwc3-of-simple.c index 58526932d2b6..29433c80be10 100644 --- a/drivers/usb/dwc3/dwc3-of-simple.c +++ b/drivers/usb/dwc3/dwc3-of-simple.c @@ -180,6 +180,7 @@ static const struct of_device_id of_dwc3_simple_match[] = { { .compatible = "rockchip,rk3399-dwc3" }, { .compatible = "xlnx,zynqmp-dwc3" }, { .compatible = "cavium,octeon-7130-usb-uctl" }, + { .compatible = "fsl, imx8mq-dwc3" }, { /* Sentinel */ } }; MODULE_DEVICE_TABLE(of, of_dwc3_simple_match); diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 26efe8c7535f..31a07649d99f 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -26,6 +26,7 @@ #include <linux/io.h> #include <linux/list.h> #include <linux/dma-mapping.h> +#include <linux/busfreq-imx.h> #include <linux/usb/ch9.h> #include <linux/usb/gadget.h> @@ -247,7 +248,11 @@ int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned cmd, struct dwc3_gadget_ep_cmd_params *params) { struct dwc3 *dwc = dep->dwc; - u32 timeout = 1000; + /* + * FIXME need check why 500 times check + * is not enough. + */ + u32 timeout = 20000; u32 reg; int cmd_status = 0; @@ -271,7 +276,7 @@ int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned cmd, } } - if (cmd == DWC3_DEPCMD_STARTTRANSFER) { + if (DWC3_DEPCMD_CMD(cmd) == DWC3_DEPCMD_STARTTRANSFER) { int needs_wakeup; needs_wakeup = (dwc->link_state == DWC3_LINK_STATE_U1 || @@ -331,6 +336,20 @@ int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned cmd, trace_dwc3_gadget_ep_cmd(dep, cmd, params, cmd_status); + if (ret == 0) { + switch (DWC3_DEPCMD_CMD(cmd)) { + case DWC3_DEPCMD_STARTTRANSFER: + dep->flags |= DWC3_EP_TRANSFER_STARTED; + break; + case DWC3_DEPCMD_ENDTRANSFER: + dep->flags &= ~DWC3_EP_TRANSFER_STARTED; + break; + default: + /* nothing */ + break; + } + } + if (unlikely(susphy)) { reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0)); reg |= DWC3_GUSB2PHYCFG_SUSPHY; @@ -584,11 +603,14 @@ static int __dwc3_gadget_ep_enable(struct dwc3_ep *dep, dep->comp_desc = comp_desc; dep->type = usb_endpoint_type(desc); dep->flags |= DWC3_EP_ENABLED; + dep->flags &= ~DWC3_EP_END_TRANSFER_PENDING; reg = dwc3_readl(dwc->regs, DWC3_DALEPENA); reg |= DWC3_DALEPENA_EP(dep->number); dwc3_writel(dwc->regs, DWC3_DALEPENA, reg); + init_waitqueue_head(&dep->wait_end_transfer); + if (usb_endpoint_xfer_control(desc)) return 0; @@ -661,7 +683,7 @@ static int __dwc3_gadget_ep_disable(struct dwc3_ep *dep) dep->endpoint.desc = NULL; dep->comp_desc = NULL; dep->type = 0; - dep->flags = 0; + dep->flags &= DWC3_EP_END_TRANSFER_PENDING; return 0; } @@ -1060,6 +1082,14 @@ static int __dwc3_gadget_kick_transfer(struct dwc3_ep *dep, u16 cmd_param) return 0; } +static int __dwc3_gadget_get_frame(struct dwc3 *dwc) +{ + u32 reg; + + reg = dwc3_readl(dwc->regs, DWC3_DSTS); + return DWC3_DSTS_SOFFN(reg); +} + static void __dwc3_gadget_start_isoc(struct dwc3 *dwc, struct dwc3_ep *dep, u32 cur_uf) { @@ -1137,12 +1167,27 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req) * errors which will force us issue EndTransfer command. */ if (usb_endpoint_xfer_isoc(dep->endpoint.desc)) { - if ((dep->flags & DWC3_EP_PENDING_REQUEST) && - list_empty(&dep->started_list)) { - dwc3_stop_active_transfer(dwc, dep->number, true); - dep->flags = DWC3_EP_ENABLED; + if ((dep->flags & DWC3_EP_PENDING_REQUEST)) { + if (dep->flags & DWC3_EP_TRANSFER_STARTED) { + dwc3_stop_active_transfer(dwc, dep->number, true); + dep->flags = DWC3_EP_ENABLED; + } else { + u32 cur_uf; + + cur_uf = __dwc3_gadget_get_frame(dwc); + __dwc3_gadget_start_isoc(dwc, dep, cur_uf); + } + return 0; } - return 0; + + if ((dep->flags & DWC3_EP_BUSY) && + !(dep->flags & DWC3_EP_MISSED_ISOC)) { + WARN_ON_ONCE(!dep->resource_index); + ret = __dwc3_gadget_kick_transfer(dep, + dep->resource_index); + } + + goto out; } if (!dwc3_calc_trbs_left(dep)) @@ -1153,6 +1198,7 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req) dwc3_trace(trace_dwc3_gadget, "%s: failed to kick transfers", dep->name); +out: if (ret == -EBUSY) ret = 0; @@ -1243,6 +1289,52 @@ static int dwc3_gadget_ep_dequeue(struct usb_ep *ep, if (r == req) { /* wait until it is processed */ dwc3_stop_active_transfer(dwc, dep->number, true); + + /* + * If request was already started, this means we had to + * stop the transfer. With that we also need to ignore + * all TRBs used by the request, however TRBs can only + * be modified after completion of END_TRANSFER + * command. So what we do here is that we wait for + * END_TRANSFER completion and only after that, we jump + * over TRBs by clearing HWO and incrementing dequeue + * pointer. + * + * Note that we have 2 possible types of transfers here: + * + * i) Linear buffer request + * ii) SG-list based request + * + * SG-list based requests will have r->num_pending_sgs + * set to a valid number (> 0). Linear requests, + * normally use a single TRB. + * + * All of these cases need to be taken into + * consideration so we don't mess up our TRB ring + * pointers. + */ + wait_event_lock_irq(dep->wait_end_transfer, + !(dep->flags & DWC3_EP_END_TRANSFER_PENDING), + dwc->lock); + + if (!r->trb) + goto out1; + + if (r->num_pending_sgs) { + struct dwc3_trb *trb; + int i = 0; + + for (i = 0; i < r->num_pending_sgs; i++) { + trb = r->trb + i; + trb->ctrl &= ~DWC3_TRB_CTRL_HWO; + dwc3_ep_inc_deq(dep); + } + } else { + struct dwc3_trb *trb = r->trb; + + trb->ctrl &= ~DWC3_TRB_CTRL_HWO; + dwc3_ep_inc_deq(dep); + } goto out1; } dev_err(dwc->dev, "request %pK was not queued to %s\n", @@ -1253,6 +1345,7 @@ static int dwc3_gadget_ep_dequeue(struct usb_ep *ep, out1: /* giveback the request */ + dep->queued_requests--; dwc3_gadget_giveback(dep, req, -ECONNRESET); out0: @@ -1280,9 +1373,6 @@ int __dwc3_gadget_ep_set_halt(struct dwc3_ep *dep, int value, int protocol) unsigned transfer_in_flight; unsigned started; - if (dep->flags & DWC3_EP_STALL) - return 0; - if (dep->number > 1) trb = dwc3_ep_prev_trb(dep, dep->trb_enqueue); else @@ -1307,8 +1397,6 @@ int __dwc3_gadget_ep_set_halt(struct dwc3_ep *dep, int value, int protocol) else dep->flags |= DWC3_EP_STALL; } else { - if (!(dep->flags & DWC3_EP_STALL)) - return 0; ret = dwc3_send_clear_stall_ep_cmd(dep); if (ret) @@ -1391,10 +1479,8 @@ static const struct usb_ep_ops dwc3_gadget_ep_ops = { static int dwc3_gadget_get_frame(struct usb_gadget *g) { struct dwc3 *dwc = gadget_to_dwc(g); - u32 reg; - reg = dwc3_readl(dwc->regs, DWC3_DSTS); - return DWC3_DSTS_SOFFN(reg); + return __dwc3_gadget_get_frame(dwc); } static int __dwc3_gadget_wakeup(struct dwc3 *dwc) @@ -1768,9 +1854,6 @@ err0: static void __dwc3_gadget_stop(struct dwc3 *dwc) { - if (pm_runtime_suspended(dwc->dev)) - return; - dwc3_gadget_disable_irq(dwc); __dwc3_gadget_ep_disable(dwc->eps[0]); __dwc3_gadget_ep_disable(dwc->eps[1]); @@ -1780,9 +1863,30 @@ static int dwc3_gadget_stop(struct usb_gadget *g) { struct dwc3 *dwc = gadget_to_dwc(g); unsigned long flags; + int epnum; spin_lock_irqsave(&dwc->lock, flags); + + if (pm_runtime_suspended(dwc->dev)) + goto out; + __dwc3_gadget_stop(dwc); + + for (epnum = 2; epnum < DWC3_ENDPOINTS_NUM; epnum++) { + struct dwc3_ep *dep = dwc->eps[epnum]; + + if (!dep) + continue; + + if (!(dep->flags & DWC3_EP_END_TRANSFER_PENDING)) + continue; + + wait_event_lock_irq(dep->wait_end_transfer, + !(dep->flags & DWC3_EP_END_TRANSFER_PENDING), + dwc->lock); + } + +out: dwc->gadget_driver = NULL; spin_unlock_irqrestore(&dwc->lock, flags); @@ -2156,10 +2260,12 @@ static void dwc3_endpoint_interrupt(struct dwc3 *dwc, { struct dwc3_ep *dep; u8 epnum = event->endpoint_number; + u8 cmd; dep = dwc->eps[epnum]; - if (!(dep->flags & DWC3_EP_ENABLED)) + if (!(dep->flags & DWC3_EP_ENABLED) && + !(dep->flags & DWC3_EP_END_TRANSFER_PENDING)) return; if (epnum == 0 || epnum == 1) { @@ -2227,11 +2333,15 @@ static void dwc3_endpoint_interrupt(struct dwc3 *dwc, "unable to find suitable stream"); } break; - case DWC3_DEPEVT_RXTXFIFOEVT: - dwc3_trace(trace_dwc3_gadget, "%s FIFO Overrun", dep->name); - break; case DWC3_DEPEVT_EPCMDCMPLT: - dwc3_trace(trace_dwc3_gadget, "Endpoint Command Complete"); + cmd = DEPEVT_PARAMETER_CMD(event->parameters); + + if (cmd == DWC3_DEPCMD_ENDTRANSFER) { + dep->flags &= ~DWC3_EP_END_TRANSFER_PENDING; + wake_up(&dep->wait_end_transfer); + } + break; + case DWC3_DEPEVT_RXTXFIFOEVT: break; } } @@ -2284,7 +2394,8 @@ static void dwc3_stop_active_transfer(struct dwc3 *dwc, u32 epnum, bool force) dep = dwc->eps[epnum]; - if (!dep->resource_index) + if ((dep->flags & DWC3_EP_END_TRANSFER_PENDING) || + !dep->resource_index) return; /* @@ -2328,8 +2439,10 @@ static void dwc3_stop_active_transfer(struct dwc3 *dwc, u32 epnum, bool force) dep->resource_index = 0; dep->flags &= ~DWC3_EP_BUSY; - if (dwc3_is_usb31(dwc) || dwc->revision < DWC3_REVISION_310A) + if (dwc3_is_usb31(dwc) || dwc->revision < DWC3_REVISION_310A) { + dep->flags |= DWC3_EP_END_TRANSFER_PENDING; udelay(100); + } } static void dwc3_stop_active_transfers(struct dwc3 *dwc) @@ -2389,14 +2502,20 @@ static void dwc3_gadget_disconnect_interrupt(struct dwc3 *dwc) dwc->setup_packet_pending = false; usb_gadget_set_state(&dwc->gadget, USB_STATE_NOTATTACHED); - dwc->connected = false; + if (dwc->connected) { + release_bus_freq(BUS_FREQ_HIGH); + dwc->connected = false; + } } static void dwc3_gadget_reset_interrupt(struct dwc3 *dwc) { u32 reg; - dwc->connected = true; + if (!dwc->connected) { + request_bus_freq(BUS_FREQ_HIGH); + dwc->connected = true; + } /* * WORKAROUND: DWC3 revisions <1.88a have an issue which @@ -3000,7 +3119,10 @@ int dwc3_gadget_init(struct dwc3 *dwc) dwc->gadget.speed = USB_SPEED_UNKNOWN; dwc->gadget.sg_supported = true; dwc->gadget.name = "dwc3-gadget"; - dwc->gadget.is_otg = dwc->dr_mode == USB_DR_MODE_OTG; + 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/gadget/Kconfig b/drivers/usb/gadget/Kconfig index f3ee80ece682..9f4b89b3d37d 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -337,6 +337,12 @@ config USB_CONFIGFS_MASS_STORAGE device (in much the same way as the "loop" device driver), specified as a module parameter or sysfs option. +config FSL_UTP + bool "UTP over Storage Gadget" + depends on USB_CONFIGFS_MASS_STORAGE + help + Freescale's extension to MSC protocol + config USB_CONFIGFS_F_LB_SS bool "Loopback and sourcesink function (for testing)" depends on USB_CONFIGFS diff --git a/drivers/usb/gadget/function/f_mass_storage.c b/drivers/usb/gadget/function/f_mass_storage.c index d2fc237cd87a..e23150fb50e6 100644 --- a/drivers/usb/gadget/function/f_mass_storage.c +++ b/drivers/usb/gadget/function/f_mass_storage.c @@ -329,8 +329,15 @@ struct fsg_dev { struct usb_ep *bulk_in; struct usb_ep *bulk_out; +#ifdef CONFIG_FSL_UTP + void *utp; +#endif }; +#ifdef CONFIG_FSL_UTP +#include "fsl_updater.h" +#endif + static inline int __fsg_is_set(struct fsg_common *common, const char *func, unsigned line) { @@ -1148,6 +1155,15 @@ static int do_request_sense(struct fsg_common *common, struct fsg_buffhd *bh) } #endif +#ifdef CONFIG_FSL_UTP + if (is_utp_device(common->fsg) && + utp_get_sense(common->fsg) == 0) { + /* got the sense from the UTP */ + sd = UTP_CTX(common->fsg)->sd; + sdinfo = UTP_CTX(common->fsg)->sdinfo; + valid = 0; + } else +#endif if (!curlun) { /* Unsupported LUNs are okay */ common->bad_lun_okay = 1; sd = SS_LOGICAL_UNIT_NOT_SUPPORTED; @@ -1169,6 +1185,10 @@ static int do_request_sense(struct fsg_common *common, struct fsg_buffhd *bh) buf[7] = 18 - 8; /* Additional sense length */ buf[12] = ASC(sd); buf[13] = ASCQ(sd); +#ifdef CONFIG_FSL_UTP + if (is_utp_device(common->fsg)) + put_unaligned_be32(UTP_CTX(common->fsg)->sdinfo_h, &buf[8]); +#endif return 18; } @@ -1662,7 +1682,20 @@ static int send_status(struct fsg_common *common) sd = SS_INVALID_COMMAND; } else if (sd != SS_NO_SENSE) { DBG(common, "sending command-failure status\n"); - status = US_BULK_STAT_FAIL; +#ifdef CONFIG_FSL_UTP + /* + * mfgtool host frequently reset bus during transfer + * - the response in csw to request sense will be 1 + * due to UTP change some storage information + * - host will reset the bus if response to request sense is 1 + * - change the response to 0 if CONFIG_FSL_UTP is defined + */ + if (is_utp_device(common->fsg)) + status = US_BULK_STAT_OK; + else +#endif + status = US_BULK_STAT_FAIL; + VDBG(common, " sense data: SK x%02x, ASC x%02x, ASCQ x%02x;" " info x%x\n", SK(sd), ASC(sd), ASCQ(sd), sdinfo); @@ -1853,6 +1886,14 @@ static int do_scsi_command(struct fsg_common *common) common->phase_error = 0; common->short_packet_received = 0; +#ifdef CONFIG_FSL_UTP + if (is_utp_device(common->fsg)) + reply = utp_handle_message(common->fsg, common->cmnd, reply); + + if (reply != -EINVAL) + return reply; +#endif + down_read(&common->filesem); /* We're using the backing file */ switch (common->cmnd[0]) { @@ -2522,7 +2563,12 @@ static int fsg_main_thread(void *common_) * pointers. That way we can pass a kernel pointer to a routine * that expects a __user pointer and it will work okay. */ +#ifdef CONFIG_FSL_UTP + if (!is_utp_device(common->fsg)) + set_fs(get_ds()); +#else set_fs(get_ds()); +#endif /* The main loop */ while (common->state != FSG_STATE_TERMINATED) { @@ -2988,6 +3034,10 @@ static void fsg_common_release(struct kref *ref) /*-------------------------------------------------------------------------*/ +#ifdef CONFIG_FSL_UTP +#include "fsl_updater.c" +#endif + static int fsg_bind(struct usb_configuration *c, struct usb_function *f) { struct fsg_dev *fsg = fsg_from_func(f); @@ -3039,6 +3089,11 @@ static int fsg_bind(struct usb_configuration *c, struct usb_function *f) fsg_intf_desc.bInterfaceNumber = i; fsg->interface_number = i; +#ifdef CONFIG_FSL_UTP + if (is_utp_device(fsg)) + utp_init(fsg); +#endif + /* Find all the endpoints we will use */ ep = usb_ep_autoconfig(gadget, &fsg_fs_bulk_in_desc); if (!ep) @@ -3102,6 +3157,12 @@ static void fsg_unbind(struct usb_configuration *c, struct usb_function *f) } usb_free_all_descriptors(&fsg->function); + +#ifdef CONFIG_FSL_UTP + if (is_utp_device(common->fsg)) + utp_exit(fsg); +#endif + } static inline struct fsg_lun_opts *to_fsg_lun_opts(struct config_item *item) diff --git a/drivers/usb/gadget/function/fsl_updater.c b/drivers/usb/gadget/function/fsl_updater.c new file mode 100644 index 000000000000..59ec7479bd71 --- /dev/null +++ b/drivers/usb/gadget/function/fsl_updater.c @@ -0,0 +1,647 @@ +/* + * Freescale UUT driver + * + * Copyright 2008-2016 Freescale Semiconductor, Inc. + * Copyright 2008-2009 Embedded Alley Solutions, Inc All Rights Reserved. + * 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 + */ + +static bool is_utp_device(struct fsg_dev *fsg) +{ + struct usb_device_descriptor *pdesc; + + if (!fsg || !fsg->common || !fsg->common->cdev) + return false; + + pdesc = &fsg->common->cdev->desc; + if (pdesc->idVendor == UTP_IDVENDOR && + pdesc->idProduct == UTP_IDPRODUCT) + return true; + + return false; +} + +static u64 get_be64(u8 *buf) +{ + return ((u64)get_unaligned_be32(buf) << 32) | + get_unaligned_be32(buf + 4); +} + +static DEFINE_IDA(utp_ida); + +static int utp_init(struct fsg_dev *fsg) +{ + struct utp_context *utp; + int index; + + utp = vzalloc(sizeof(struct utp_context)); + if (!utp) + return -EIO; + + init_waitqueue_head(&utp->wq); + init_waitqueue_head(&utp->list_full_wq); + + INIT_LIST_HEAD(&utp->read); + INIT_LIST_HEAD(&utp->write); + mutex_init(&utp->lock); + + /* the max message is 64KB */ + utp->buffer = vmalloc(0x10000); + if (!utp->buffer) { + vfree(utp); + return -EIO; + } + + utp->utp_version = 0x1ull; + fsg->utp = utp; + utp->utp_dev.minor = MISC_DYNAMIC_MINOR; + utp->utp_dev.fops = &utp_fops; + + index = ida_simple_get(&utp_ida, 0, 0, GFP_KERNEL); + if (index == 0) + snprintf(utp->utp_name, 8, "utp"); + else + snprintf(utp->utp_name, 8, "utp%d", index); + + utp->utp_dev.name = utp->utp_name; + return misc_register(&utp->utp_dev); +} + +static void utp_exit(struct fsg_dev *fsg) +{ + struct utp_context *utp; + utp = UTP_CTX(fsg); + + misc_deregister(&utp->utp_dev); + + vfree(utp->buffer); + vfree(utp); +} + +static struct utp_user_data *utp_user_data_alloc(size_t size) +{ + struct utp_user_data *uud; + + uud = vmalloc(size + sizeof(*uud)); + if (!uud) + return uud; + memset(uud, 0, size + sizeof(*uud)); + uud->data.size = size + sizeof(uud->data); + INIT_LIST_HEAD(&uud->link); + return uud; +} + +static void utp_user_data_free(struct utp_context *utp, struct utp_user_data *uud) +{ + mutex_lock(&utp->lock); + list_del(&uud->link); + mutex_unlock(&utp->lock); + vfree(uud); +} + +/* Get the number of element for list */ +static u32 count_list(struct utp_context *utp, struct list_head *l) +{ + u32 count = 0; + struct list_head *tmp; + + mutex_lock(&utp->lock); + list_for_each(tmp, l) { + count++; + } + mutex_unlock(&utp->lock); + + return count; +} + +/* The routine will not go on if utp_context.queue is empty */ +#define WAIT_ACTIVITY(utp, queue) \ + wait_event_interruptible(utp->wq, !list_empty(&utp->queue)) + +/* Called by userspace program (uuc) */ +static ssize_t utp_file_read(struct file *file, + char __user *buf, + size_t size, + loff_t *off) +{ + struct utp_user_data *uud; + size_t size_to_put; + int free = 0; + + struct miscdevice *misc = (struct miscdevice *)file->private_data; + struct utp_context *utp = container_of(misc, struct utp_context, utp_dev); + + WAIT_ACTIVITY(utp, read); + + mutex_lock(&utp->lock); + uud = list_first_entry(&utp->read, struct utp_user_data, link); + mutex_unlock(&utp->lock); + size_to_put = uud->data.size; + + if (size >= size_to_put) + free = !0; + if (copy_to_user(buf, &uud->data, size_to_put)) { + printk(KERN_INFO "[ %s ] copy error\n", __func__); + return -EACCES; + } + if (free) + utp_user_data_free(utp, uud); + else { + pr_info("sizeof = %zd, size = %zd\n", + sizeof(uud->data), + uud->data.size); + + pr_err("Will not free utp_user_data, because buffer size = %zd need to put %zd\n", + size, size_to_put); + } + + /* + * The user program has already finished data process, + * go on getting data from the host + */ + wake_up(&utp->list_full_wq); + + return size_to_put; +} + +static ssize_t utp_file_write(struct file *file, const char __user *buf, + size_t size, loff_t *off) +{ + struct utp_user_data *uud; + + struct miscdevice *misc = (struct miscdevice *)file->private_data; + struct utp_context *utp = container_of(misc, struct utp_context, utp_dev); + + if (size < sizeof(uud->data)) + return -EINVAL; + uud = utp_user_data_alloc(size); + if (uud == NULL) + return -ENOMEM; + if (copy_from_user(&uud->data, buf, size)) { + printk(KERN_INFO "[ %s ] copy error!\n", __func__); + vfree(uud); + return -EACCES; + } + mutex_lock(&utp->lock); + list_add_tail(&uud->link, &utp->write); + /* Go on EXEC routine process */ + wake_up(&utp->wq); + mutex_unlock(&utp->lock); + return size; +} + +/* + * uuc should change to use soc bus infrastructure to soc information + * /sys/devices/soc0/soc_id + * this function can be removed. + */ +static long +utp_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int cpu_id = 0; + + switch (cmd) { + case UTP_GET_CPU_ID: + return put_user(cpu_id, (int __user *)arg); + default: + return -ENOIOCTLCMD; + } +} + +/* Will be called when the host wants to get the sense data */ +static int utp_get_sense(struct fsg_dev *fsg) +{ + if (UTP_CTX(fsg)->processed == 0) + return -1; + + UTP_CTX(fsg)->processed = 0; + return 0; +} + +static int utp_do_read(struct fsg_dev *fsg, void *data, size_t size) +{ + struct fsg_buffhd *bh; + int rc; + u32 amount_left; + unsigned int amount; + + /* Get the starting Logical Block Address and check that it's + * not too big */ + + amount_left = size; + if (unlikely(amount_left == 0)) + return -EIO; /* No default reply*/ + + pr_debug("%s: sending %zd\n", __func__, size); + for (;;) { + /* Figure out how much we need to read: + * Try to read the remaining amount. + * But don't read more than the buffer size. + * And don't try to read past the end of the file. + * Finally, if we're not at a page boundary, don't read past + * the next page. + * If this means reading 0 then we were asked to read past + * the end of file. */ + amount = min((unsigned int) amount_left, FSG_BUFLEN); + + /* Wait for the next buffer to become available */ + bh = fsg->common->next_buffhd_to_fill; + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(fsg->common, true); + if (rc) + return rc; + } + + /* If we were asked to read past the end of file, + * end with an empty buffer. */ + if (amount == 0) { + bh->inreq->length = 0; + bh->state = BUF_STATE_FULL; + break; + } + + /* Perform the read */ + pr_info("Copied to %p, %d bytes started from %zd\n", + bh->buf, amount, size - amount_left); + /* from upt buffer to file_storeage buffer */ + memcpy(bh->buf, data + size - amount_left, amount); + amount_left -= amount; + fsg->common->residue -= amount; + + bh->inreq->length = amount; + bh->state = BUF_STATE_FULL; + + /* Send this buffer and go read some more */ + bh->inreq->zero = 0; + + /* USB Physical transfer: Data from device to host */ + start_transfer(fsg, fsg->bulk_in, bh->inreq, + &bh->inreq_busy, &bh->state); + + fsg->common->next_buffhd_to_fill = bh->next; + + if (amount_left <= 0) + break; + } + + return size - amount_left; +} + +static int utp_do_write(struct fsg_dev *fsg, void *data, size_t size) +{ + struct fsg_buffhd *bh; + int get_some_more; + u32 amount_left_to_req, amount_left_to_write; + unsigned int amount; + int rc; + loff_t offset; + + /* Carry out the file writes */ + get_some_more = 1; + amount_left_to_req = amount_left_to_write = size; + + if (unlikely(amount_left_to_write == 0)) + return -EIO; + + offset = 0; + while (amount_left_to_write > 0) { + + /* Queue a request for more data from the host */ + bh = fsg->common->next_buffhd_to_fill; + if (bh->state == BUF_STATE_EMPTY && get_some_more) { + + /* Figure out how much we want to get: + * Try to get the remaining amount. + * But don't get more than the buffer size. + * And don't try to go past the end of the file. + * If we're not at a page boundary, + * don't go past the next page. + * If this means getting 0, then we were asked + * to write past the end of file. + * Finally, round down to a block boundary. */ + amount = min(amount_left_to_req, FSG_BUFLEN); + + if (amount == 0) { + get_some_more = 0; + /* cry now */ + continue; + } + + /* Get the next buffer */ + amount_left_to_req -= amount; + if (amount_left_to_req == 0) + get_some_more = 0; + + /* amount is always divisible by 512, hence by + * the bulk-out maxpacket size */ + set_bulk_out_req_length(fsg->common, bh, amount); + bh->outreq->short_not_ok = 1; + start_transfer(fsg, fsg->bulk_out, bh->outreq, + &bh->outreq_busy, &bh->state); + fsg->common->next_buffhd_to_fill = bh->next; + continue; + } + + /* Write the received data to the backing file */ + bh = fsg->common->next_buffhd_to_drain; + if (bh->state == BUF_STATE_EMPTY && !get_some_more) + break; /* We stopped early */ + if (bh->state == BUF_STATE_FULL) { + smp_rmb(); + fsg->common->next_buffhd_to_drain = bh->next; + bh->state = BUF_STATE_EMPTY; + + /* Did something go wrong with the transfer? */ + if (bh->outreq->status != 0) + /* cry again, COMMUNICATION_FAILURE */ + break; + + amount = bh->outreq->actual; + + /* Perform the write */ + memcpy(data + offset, bh->buf, amount); + + offset += amount; + if (signal_pending(current)) + return -EINTR; /* Interrupted!*/ + amount_left_to_write -= amount; + fsg->common->residue -= amount; + + /* Did the host decide to stop early? */ + if (bh->outreq->actual != bh->outreq->length) { + fsg->common->short_packet_received = 1; + break; + } + continue; + } + + /* Wait for something to happen */ + rc = sleep_thread(fsg->common, true); + if (rc) + return rc; + } + + return -EIO; +} + +static inline void utp_set_sense(struct fsg_dev *fsg, u16 code, u64 reply) +{ + UTP_CTX(fsg)->processed = true; + UTP_CTX(fsg)->sdinfo = reply & 0xFFFFFFFF; + UTP_CTX(fsg)->sdinfo_h = (reply >> 32) & 0xFFFFFFFF; + UTP_CTX(fsg)->sd = (UTP_SENSE_KEY << 16) | code; +} + +static void utp_poll(struct fsg_dev *fsg) +{ + struct utp_context *ctx = UTP_CTX(fsg); + struct utp_user_data *uud = NULL; + + mutex_lock(&ctx->lock); + if (!list_empty(&ctx->write)) + uud = list_first_entry(&ctx->write, struct utp_user_data, link); + mutex_unlock(&ctx->lock); + + if (uud) { + if (uud->data.flags & UTP_FLAG_STATUS) { + printk(KERN_WARNING "%s: exit with status %d\n", + __func__, uud->data.status); + UTP_SS_EXIT(fsg, uud->data.status); + } else if (uud->data.flags & UTP_FLAG_REPORT_BUSY) { + UTP_SS_BUSY(fsg, --ctx->counter); + } else { + printk("%s: pass returned.\n", __func__); + UTP_SS_PASS(fsg); + } + utp_user_data_free(ctx, uud); + } else { + if (ctx->cur_state & UTP_FLAG_DATA) { + if (count_list(ctx, &ctx->read) < 7) { + pr_debug("%s: pass returned in POLL stage. \n", __func__); + UTP_SS_PASS(fsg); + ctx->cur_state = 0; + return; + } + } + UTP_SS_BUSY(fsg, --ctx->counter); + } +} + +static int utp_exec(struct fsg_dev *fsg, + char *command, + int cmdsize, + unsigned long long payload) +{ + struct utp_user_data *uud = NULL, *uud2r; + struct utp_context *ctx = UTP_CTX(fsg); + + ctx->counter = 0xFFFF; + uud2r = utp_user_data_alloc(cmdsize + 1); + if (!uud2r) + return -ENOMEM; + uud2r->data.flags = UTP_FLAG_COMMAND; + uud2r->data.payload = payload; + strncpy(uud2r->data.command, command, cmdsize); + + mutex_lock(&ctx->lock); + list_add_tail(&uud2r->link, &ctx->read); + mutex_unlock(&ctx->lock); + /* wake up the read routine */ + wake_up(&ctx->wq); + + if (command[0] == '!') /* there will be no response */ + return 0; + + /* + * the user program (uuc) will return utp_message + * and add list to write list + */ + WAIT_ACTIVITY(ctx, write); + + mutex_lock(&ctx->lock); + if (!list_empty(&ctx->write)) { + uud = list_first_entry(&ctx->write, struct utp_user_data, link); +#ifdef DEBUG + pr_info("UUD:\n\tFlags = %02X\n", uud->data.flags); + if (uud->data.flags & UTP_FLAG_DATA) { + pr_info("\tbufsize = %d\n", uud->data.bufsize); + print_hex_dump(KERN_DEBUG, "\t", DUMP_PREFIX_NONE, + 16, 2, uud->data.data, uud->data.bufsize, true); + } + if (uud->data.flags & UTP_FLAG_REPORT_BUSY) + pr_info("\tBUSY\n"); +#endif + } else { + pr_err("UTP write list is empty.\n"); + mutex_unlock(&ctx->lock); + return 0; + } + mutex_unlock(&ctx->lock); + + if (uud->data.flags & UTP_FLAG_DATA) { + memcpy(ctx->buffer, uud->data.data, uud->data.bufsize); + UTP_SS_SIZE(fsg, uud->data.bufsize); + } else if (uud->data.flags & UTP_FLAG_REPORT_BUSY) { + UTP_SS_BUSY(fsg, ctx->counter); + } else if (uud->data.flags & UTP_FLAG_STATUS) { + printk(KERN_WARNING "%s: exit with status %d\n", __func__, + uud->data.status); + UTP_SS_EXIT(fsg, uud->data.status); + } else { + pr_debug("%s: pass returned in EXEC stage. \n", __func__); + UTP_SS_PASS(fsg); + } + utp_user_data_free(ctx, uud); + return 0; +} + +static int utp_send_status(struct fsg_dev *fsg) +{ + struct fsg_buffhd *bh; + u8 status = US_BULK_STAT_OK; + struct bulk_cs_wrap *csw; + int rc; + + /* Wait for the next buffer to become available */ + bh = fsg->common->next_buffhd_to_fill; + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(fsg->common, true); + if (rc) + return rc; + } + + if (fsg->common->phase_error) { + DBG(fsg, "sending phase-error status\n"); + status = US_BULK_STAT_PHASE; + + } else if ((UTP_CTX(fsg)->sd & 0xFFFF) != UTP_REPLY_PASS) { + status = US_BULK_STAT_FAIL; + } + + csw = bh->buf; + + /* Store and send the Bulk-only CSW */ + csw->Signature = __constant_cpu_to_le32(US_BULK_CS_SIGN); + csw->Tag = fsg->common->tag; + csw->Residue = cpu_to_le32(fsg->common->residue); + csw->Status = status; + + bh->inreq->length = US_BULK_CS_WRAP_LEN; + bh->inreq->zero = 0; + start_transfer(fsg, fsg->bulk_in, bh->inreq, + &bh->inreq_busy, &bh->state); + fsg->common->next_buffhd_to_fill = bh->next; + return 0; +} + +static int utp_handle_message(struct fsg_dev *fsg, + char *cdb_data, + int default_reply) +{ + struct utp_msg *m = (struct utp_msg *)cdb_data; + void *data = NULL; + int r; + struct utp_user_data *uud2r; + unsigned long long param; + unsigned long tag; + + if (m->f0 != 0xF0) + return default_reply; + + tag = get_unaligned_be32((void *)&m->utp_msg_tag); + param = get_be64((void *)&m->param); + pr_debug("Type 0x%x, tag 0x%08lx, param %llx\n", + m->utp_msg_type, tag, param); + + switch ((enum utp_msg_type)m->utp_msg_type) { + + case UTP_POLL: + if (get_be64((void *)&m->param) == 1) { + pr_debug("%s: version request\n", __func__); + UTP_SS_EXIT(fsg, UTP_CTX(fsg)->utp_version); + break; + } + utp_poll(fsg); + break; + case UTP_EXEC: + pr_debug("%s: EXEC\n", __func__); + data = vmalloc(fsg->common->data_size); + memset(data, 0, fsg->common->data_size); + /* copy data from usb buffer to utp buffer */ + utp_do_write(fsg, data, fsg->common->data_size); + utp_exec(fsg, data, fsg->common->data_size, param); + vfree(data); + break; + case UTP_GET: /* data from device to host */ + pr_debug("%s: GET, %d bytes\n", __func__, + fsg->common->data_size); + r = utp_do_read(fsg, UTP_CTX(fsg)->buffer, + fsg->common->data_size); + UTP_SS_PASS(fsg); + break; + case UTP_PUT: + UTP_CTX(fsg)->cur_state = UTP_FLAG_DATA; + pr_debug("%s: PUT, Received %d bytes\n", __func__, fsg->common->data_size);/* data from host to device */ + uud2r = utp_user_data_alloc(fsg->common->data_size); + if (!uud2r) + return -ENOMEM; + uud2r->data.bufsize = fsg->common->data_size; + uud2r->data.flags = UTP_FLAG_DATA; + utp_do_write(fsg, uud2r->data.data, fsg->common->data_size); + /* don't know what will be written */ + mutex_lock(&UTP_CTX(fsg)->lock); + list_add_tail(&uud2r->link, &UTP_CTX(fsg)->read); + mutex_unlock(&UTP_CTX(fsg)->lock); + wake_up(&UTP_CTX(fsg)->wq); + /* + * Return PASS or FAIL according to uuc's status + * Please open it if need to check uuc's status + * and use another version uuc + */ +#if 0 + struct utp_user_data *uud = NULL; + struct utp_context *ctx; + WAIT_ACTIVITY(write); + ctx = UTP_CTX(fsg); + mutex_lock(&ctx->lock); + + if (!list_empty(&ctx->write)) + uud = list_first_entry(&ctx->write, + struct utp_user_data, link); + + mutex_unlock(&ctx->lock); + if (uud) { + if (uud->data.flags & UTP_FLAG_STATUS) { + printk(KERN_WARNING "%s: exit with status %d\n", + __func__, uud->data.status); + UTP_SS_EXIT(fsg, uud->data.status); + } else { + pr_debug("%s: pass\n", __func__); + UTP_SS_PASS(fsg); + } + utp_user_data_free(uud); + } else{ + UTP_SS_PASS(fsg); + } +#endif + if (count_list(UTP_CTX(fsg), &UTP_CTX(fsg)->read) < 7) { + UTP_CTX(fsg)->cur_state = 0; + UTP_SS_PASS(fsg); + } else + UTP_SS_BUSY(fsg, UTP_CTX(fsg)->counter); + + break; + } + + utp_send_status(fsg); + return -1; +} diff --git a/drivers/usb/gadget/function/fsl_updater.h b/drivers/usb/gadget/function/fsl_updater.h new file mode 100644 index 000000000000..044beb0e2114 --- /dev/null +++ b/drivers/usb/gadget/function/fsl_updater.h @@ -0,0 +1,152 @@ +/* + * Freescale UUT driver + * + * Copyright 2008-2016 Freescale Semiconductor, Inc. + * Copyright 2008-2009 Embedded Alley Solutions, Inc All Rights Reserved. + * 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 __FSL_UPDATER_H +#define __FSL_UPDATER_H + +#include <linux/miscdevice.h> +#include <linux/list.h> +#include <linux/vmalloc.h> +#include <linux/ioctl.h> +/* #include <mach/hardware.h> */ +struct utp_context; + +static int utp_init(struct fsg_dev *fsg); +static void utp_exit(struct fsg_dev *fsg); +static ssize_t utp_file_read(struct file *file, + char __user *buf, + size_t size, + loff_t *off); + +static ssize_t utp_file_write(struct file *file, + const char __user *buf, + size_t size, + loff_t *off); + +static bool is_utp_device(struct fsg_dev *fsg); +static long utp_ioctl(struct file *file, + unsigned int cmd, unsigned long arg); +static struct utp_user_data *utp_user_data_alloc(size_t size); +static void utp_user_data_free(struct utp_context *utp, struct utp_user_data *uud); +static int utp_get_sense(struct fsg_dev *fsg); +static int utp_do_read(struct fsg_dev *fsg, void *data, size_t size); +static int utp_do_write(struct fsg_dev *fsg, void *data, size_t size); +static inline void utp_set_sense(struct fsg_dev *fsg, u16 code, u64 reply); +static int utp_handle_message(struct fsg_dev *fsg, + char *cdb_data, + int default_reply); + +#define UTP_REPLY_PASS 0 +#define UTP_REPLY_EXIT 0x8001 +#define UTP_REPLY_BUSY 0x8002 +#define UTP_REPLY_SIZE 0x8003 +#define UTP_SENSE_KEY 9 + +#define UTP_MINOR 222 +/* MISC_DYNAMIC_MINOR would be better, but... */ + +#define UTP_IDVENDOR 0x066F +#define UTP_IDPRODUCT 0x37FF + +#define UTP_COMMAND_SIZE 80 + +#define UTP_SS_EXIT(fsg, r) utp_set_sense(fsg, UTP_REPLY_EXIT, (u64)r) +#define UTP_SS_PASS(fsg) utp_set_sense(fsg, UTP_REPLY_PASS, 0) +#define UTP_SS_BUSY(fsg, r) utp_set_sense(fsg, UTP_REPLY_BUSY, (u64)r) +#define UTP_SS_SIZE(fsg, r) utp_set_sense(fsg, UTP_REPLY_SIZE, (u64)r) + +#define UTP_IOCTL_BASE 'U' +#define UTP_GET_CPU_ID _IOR(UTP_IOCTL_BASE, 0, int) +/* the structure of utp message which is mapped to 16-byte SCSI CBW's CDB */ +#pragma pack(1) +struct utp_msg { + u8 f0; + u8 utp_msg_type; + u32 utp_msg_tag; + union { + struct { + u32 param_lsb; + u32 param_msb; + }; + u64 param; + }; +}; + +enum utp_msg_type { + UTP_POLL = 0, + UTP_EXEC, + UTP_GET, + UTP_PUT, +}; + +struct utp_context { + wait_queue_head_t wq; + wait_queue_head_t list_full_wq; + struct mutex lock; + struct list_head read; + struct list_head write; + u32 sd, sdinfo, sdinfo_h; /* sense data */ + int processed; + u8 *buffer; + u32 counter; + u64 utp_version; + u32 cur_state; + struct miscdevice utp_dev; + char utp_name[8]; +}; + +static const struct file_operations utp_fops = { + .open = nonseekable_open, + .read = utp_file_read, + .write = utp_file_write, + /* .ioctl = utp_ioctl, */ + .unlocked_ioctl = utp_ioctl, +}; + +#define UTP_FLAG_COMMAND 0x00000001 +#define UTP_FLAG_DATA 0x00000002 +#define UTP_FLAG_STATUS 0x00000004 +#define UTP_FLAG_REPORT_BUSY 0x10000000 +struct utp_message { + u32 flags; + size_t size; + union { + struct { + u64 payload; + char command[1]; + }; + struct { + size_t bufsize; + u8 data[1]; + }; + u32 status; + }; +}; + +struct utp_user_data { + struct list_head link; + struct utp_message data; +}; +#pragma pack() + +static inline struct utp_context *UTP_CTX(struct fsg_dev *fsg) +{ + return (struct utp_context *)fsg->utp; +} + +#endif /* __FSL_UPDATER_H */ + diff --git a/drivers/usb/gadget/udc/core.c b/drivers/usb/gadget/udc/core.c index 139f6cce30b1..53abf7e7875b 100644 --- a/drivers/usb/gadget/udc/core.c +++ b/drivers/usb/gadget/udc/core.c @@ -1274,6 +1274,7 @@ void usb_del_gadget_udc(struct usb_gadget *gadget) flush_work(&gadget->work); device_unregister(&udc->dev); device_unregister(&gadget->dev); + memset(&gadget->dev, 0x00, sizeof(gadget->dev)); } EXPORT_SYMBOL_GPL(usb_del_gadget_udc); diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index 063064801ceb..f75423664003 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -93,7 +93,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"); @@ -1244,6 +1244,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 74f62d68f013..9de412272dea 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c @@ -722,145 +722,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.controller, - urb->setup_packet, - sizeof(struct usb_ctrlrequest), - DMA_TO_DEVICE); - urb->transfer_dma = dma_map_single( - hcd->self.controller, - 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, @@ -935,7 +796,7 @@ int ehci_hub_control( break; #ifdef CONFIG_USB_OTG if ((hcd->self.otg_port == (wIndex + 1)) - && hcd->self.b_hnp_enable) { + && hcd->self.b_hnp_enable && !hcd->self.otg_fsm) { otg_start_hnp(hcd->usb_phy->otg); break; } diff --git a/drivers/usb/host/ehci-mem.c b/drivers/usb/host/ehci-mem.c index 4de43011df23..9b7e63977215 100644 --- a/drivers/usb/host/ehci-mem.c +++ b/drivers/usb/host/ehci-mem.c @@ -138,7 +138,7 @@ static void ehci_mem_cleanup (struct ehci_hcd *ehci) ehci->sitd_pool = NULL; if (ehci->periodic) - dma_free_coherent (ehci_to_hcd(ehci)->self.controller, + dma_free_coherent(ehci_to_hcd(ehci)->self.sysdev, ehci->periodic_size * sizeof (u32), ehci->periodic, ehci->periodic_dma); ehci->periodic = NULL; @@ -155,7 +155,7 @@ static int ehci_mem_init (struct ehci_hcd *ehci, gfp_t flags) /* QTDs for control/bulk/intr transfers */ ehci->qtd_pool = dma_pool_create ("ehci_qtd", - ehci_to_hcd(ehci)->self.controller, + ehci_to_hcd(ehci)->self.sysdev, sizeof (struct ehci_qtd), 32 /* byte alignment (for hw parts) */, 4096 /* can't cross 4K */); @@ -165,7 +165,7 @@ static int ehci_mem_init (struct ehci_hcd *ehci, gfp_t flags) /* QHs for control/bulk/intr transfers */ ehci->qh_pool = dma_pool_create ("ehci_qh", - ehci_to_hcd(ehci)->self.controller, + ehci_to_hcd(ehci)->self.sysdev, sizeof(struct ehci_qh_hw), 32 /* byte alignment (for hw parts) */, 4096 /* can't cross 4K */); @@ -179,7 +179,7 @@ static int ehci_mem_init (struct ehci_hcd *ehci, gfp_t flags) /* ITD for high speed ISO transfers */ ehci->itd_pool = dma_pool_create ("ehci_itd", - ehci_to_hcd(ehci)->self.controller, + ehci_to_hcd(ehci)->self.sysdev, sizeof (struct ehci_itd), 32 /* byte alignment (for hw parts) */, 4096 /* can't cross 4K */); @@ -189,7 +189,7 @@ static int ehci_mem_init (struct ehci_hcd *ehci, gfp_t flags) /* SITD for full/low speed split ISO transfers */ ehci->sitd_pool = dma_pool_create ("ehci_sitd", - ehci_to_hcd(ehci)->self.controller, + ehci_to_hcd(ehci)->self.sysdev, sizeof (struct ehci_sitd), 32 /* byte alignment (for hw parts) */, 4096 /* can't cross 4K */); @@ -199,7 +199,7 @@ static int ehci_mem_init (struct ehci_hcd *ehci, gfp_t flags) /* Hardware periodic table */ ehci->periodic = (__le32 *) - dma_alloc_coherent (ehci_to_hcd(ehci)->self.controller, + dma_alloc_coherent(ehci_to_hcd(ehci)->self.sysdev, ehci->periodic_size * sizeof(__le32), &ehci->periodic_dma, flags); if (ehci->periodic == NULL) { diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c index eca3710d8fc4..27b53f4386bd 100644 --- a/drivers/usb/host/ehci-q.c +++ b/drivers/usb/host/ehci-q.c @@ -1169,7 +1169,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 @@ -1203,10 +1203,10 @@ static int submit_single_step_set_feature( * 15 secs after the setup */ if (is_setup) { - /* SETUP pid */ + /* SETUP pid, and interrupt after SETUP completion */ qtd_fill(ehci, qtd, urb->setup_dma, sizeof(struct usb_ctrlrequest), - token | (2 /* "setup" */ << 8), 8); + QTD_IOC | token | (2 /* "setup" */ << 8), 8); submit_async(ehci, urb, &qtd_list, GFP_ATOMIC); return 0; /*Return now; we shall come back after 15 seconds*/ @@ -1243,12 +1243,8 @@ static int submit_single_step_set_feature( qtd_prev->hw_next = QTD_NEXT(ehci, qtd->qtd_dma); list_add_tail(&qtd->qtd_list, head); - /* dont fill any data in such packets */ - qtd_fill(ehci, qtd, 0, 0, token, 0); - - /* by default, enable interrupt on urb completion */ - if (likely(!(urb->transfer_flags & URB_NO_INTERRUPT))) - qtd->hw_token |= cpu_to_hc32(ehci, QTD_IOC); + /* Interrupt after STATUS completion */ + qtd_fill(ehci, qtd, 0, 0, token | QTD_IOC, 0); submit_async(ehci, urb, &qtd_list, GFP_KERNEL); diff --git a/drivers/usb/host/xhci-dbg.c b/drivers/usb/host/xhci-dbg.c index 3425154baf8b..faecbfbeb874 100644 --- a/drivers/usb/host/xhci-dbg.c +++ b/drivers/usb/host/xhci-dbg.c @@ -240,6 +240,9 @@ void xhci_print_run_regs(struct xhci_hcd *xhci) xhci_dbg(xhci, " %p: Microframe index = 0x%x\n", &xhci->run_regs->microframe_index, (unsigned int) temp); + if (xhci->quirks & XHCI_SKIP_ACCESS_RESERVED_REG) + return; + for (i = 0; i < 7; ++i) { temp = readl(&xhci->run_regs->rsvd[i]); if (temp != XHCI_INIT_VALUE) @@ -253,7 +256,8 @@ void xhci_print_registers(struct xhci_hcd *xhci) { xhci_print_cap_regs(xhci); xhci_print_op_regs(xhci); - xhci_print_ports(xhci); + if (!(xhci->quirks & XHCI_SKIP_ACCESS_RESERVED_REG)) + xhci_print_ports(xhci); } void xhci_print_trb_offsets(struct xhci_hcd *xhci, union xhci_trb *trb) diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index 0722f75f1d6a..638fe817c85f 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -558,6 +558,121 @@ static int xhci_get_ports(struct usb_hcd *hcd, __le32 __iomem ***port_array) return max_ports; } +static __le32 __iomem *xhci_get_port_io_addr(struct usb_hcd *hcd, int index) +{ + __le32 __iomem **port_array; + + xhci_get_ports(hcd, &port_array); + return port_array[index]; +} + +/* + * xhci_set_port_power() must be called with xhci->lock held. + * It will release and re-aquire the lock while calling ACPI + * method. + */ +static void xhci_set_port_power(struct xhci_hcd *xhci, struct usb_hcd *hcd, + u16 index, bool on, unsigned long *flags) +{ + __le32 __iomem *addr; + u32 temp; + + addr = xhci_get_port_io_addr(hcd, index); + temp = readl(addr); + temp = xhci_port_state_to_neutral(temp); + if (on) { + /* Power on */ + writel(temp | PORT_POWER, addr); + temp = readl(addr); + xhci_dbg(xhci, "set port power, actual port %d status = 0x%x\n", + index, temp); + } else { + /* Power off */ + writel(temp & ~PORT_POWER, addr); + } + + spin_unlock_irqrestore(&xhci->lock, *flags); + temp = usb_acpi_power_manageable(hcd->self.root_hub, + index); + if (temp) + usb_acpi_set_power_state(hcd->self.root_hub, + index, on); + spin_lock_irqsave(&xhci->lock, *flags); +} + +static void xhci_port_set_test_mode(struct xhci_hcd *xhci, + u16 test_mode, u16 wIndex) +{ + u32 temp; + __le32 __iomem *addr; + + /* xhci only supports test mode for usb2 ports, i.e. xhci->main_hcd */ + addr = xhci_get_port_io_addr(xhci->main_hcd, wIndex); + temp = readl(addr + PORTPMSC); + temp |= test_mode << PORT_TEST_MODE_SHIFT; + writel(temp, addr + PORTPMSC); + xhci->test_mode = test_mode; + if (test_mode == TEST_FORCE_EN) + xhci_start(xhci); +} + +static int xhci_enter_test_mode(struct xhci_hcd *xhci, + u16 test_mode, u16 wIndex, unsigned long *flags) +{ + int i, retval; + + /* Disable all Device Slots */ + xhci_dbg(xhci, "Disable all slots\n"); + spin_unlock_irqrestore(&xhci->lock, *flags); + for (i = 1; i <= HCS_MAX_SLOTS(xhci->hcs_params1); i++) { + retval = xhci_disable_slot(xhci, NULL, i); + if (retval) + xhci_err(xhci, "Failed to disable slot %d, %d. Enter test mode anyway\n", + i, retval); + } + spin_lock_irqsave(&xhci->lock, *flags); + /* Put all ports to the Disable state by clear PP */ + xhci_dbg(xhci, "Disable all port (PP = 0)\n"); + /* Power off USB3 ports*/ + for (i = 0; i < xhci->num_usb3_ports; i++) + xhci_set_port_power(xhci, xhci->shared_hcd, i, false, flags); + /* Power off USB2 ports*/ + for (i = 0; i < xhci->num_usb2_ports; i++) + xhci_set_port_power(xhci, xhci->main_hcd, i, false, flags); + /* Stop the controller */ + xhci_dbg(xhci, "Stop controller\n"); + retval = xhci_halt(xhci); + if (retval) + return retval; + /* Disable runtime PM for test mode */ + pm_runtime_forbid(xhci_to_hcd(xhci)->self.controller); + /* Set PORTPMSC.PTC field to enter selected test mode */ + /* Port is selected by wIndex. port_id = wIndex + 1 */ + xhci_dbg(xhci, "Enter Test Mode: %d, Port_id=%d\n", + test_mode, wIndex + 1); + xhci_port_set_test_mode(xhci, test_mode, wIndex); + return retval; +} + +static int xhci_exit_test_mode(struct xhci_hcd *xhci) +{ + int retval; + + if (!xhci->test_mode) { + xhci_err(xhci, "Not in test mode, do nothing.\n"); + return 0; + } + if (xhci->test_mode == TEST_FORCE_EN && + !(xhci->xhc_state & XHCI_STATE_HALTED)) { + retval = xhci_halt(xhci); + if (retval) + return retval; + } + pm_runtime_allow(xhci_to_hcd(xhci)->self.controller); + xhci->test_mode = 0; + return xhci_reset(xhci); +} + void xhci_set_link_state(struct xhci_hcd *xhci, __le32 __iomem **port_array, int port_id, u32 link_state) { @@ -916,6 +1031,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, u16 link_state = 0; u16 wake_mask = 0; u16 timeout = 0; + u16 test_mode = 0; max_ports = xhci_get_ports(hcd, &port_array); bus_state = &xhci->bus_state[hcd_index(hcd)]; @@ -989,6 +1105,8 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, link_state = (wIndex & 0xff00) >> 3; if (wValue == USB_PORT_FEAT_REMOTE_WAKE_MASK) wake_mask = wIndex & 0xff00; + if (wValue == USB_PORT_FEAT_TEST) + test_mode = (wIndex & 0xff00) >> 8; /* The MSB of wIndex is the U1/U2 timeout */ timeout = (wIndex & 0xff00) >> 8; wIndex &= 0xff; @@ -1114,18 +1232,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, * However, hub_wq will ignore the roothub events until * the roothub is registered. */ - writel(temp | PORT_POWER, port_array[wIndex]); - - temp = readl(port_array[wIndex]); - xhci_dbg(xhci, "set port power, actual port %d status = 0x%x\n", wIndex, temp); - - spin_unlock_irqrestore(&xhci->lock, flags); - temp = usb_acpi_power_manageable(hcd->self.root_hub, - wIndex); - if (temp) - usb_acpi_set_power_state(hcd->self.root_hub, - wIndex, true); - spin_lock_irqsave(&xhci->lock, flags); + xhci_set_port_power(xhci, hcd, wIndex, true, &flags); break; case USB_PORT_FEAT_RESET: temp = (temp | PORT_RESET); @@ -1164,6 +1271,24 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, temp |= PORT_U2_TIMEOUT(timeout); writel(temp, port_array[wIndex] + PORTPMSC); break; + case USB_PORT_FEAT_TEST: + /* 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, + &flags); + break; default: goto error; } @@ -1229,15 +1354,10 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, port_array[wIndex], temp); break; case USB_PORT_FEAT_POWER: - writel(temp & ~PORT_POWER, port_array[wIndex]); - - spin_unlock_irqrestore(&xhci->lock, flags); - temp = usb_acpi_power_manageable(hcd->self.root_hub, - wIndex); - if (temp) - usb_acpi_set_power_state(hcd->self.root_hub, - wIndex, false); - spin_lock_irqsave(&xhci->lock, flags); + xhci_set_port_power(xhci, hcd, wIndex, false, &flags); + break; + case USB_PORT_FEAT_TEST: + retval = xhci_exit_test_mode(xhci); break; default: goto error; diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 7199e400fbac..436e787d95b4 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -586,7 +586,7 @@ static void xhci_free_stream_ctx(struct xhci_hcd *xhci, unsigned int num_stream_ctxs, struct xhci_stream_ctx *stream_ctx, dma_addr_t dma) { - struct device *dev = xhci_to_hcd(xhci)->self.controller; + struct device *dev = xhci_to_hcd(xhci)->self.sysdev; size_t size = sizeof(struct xhci_stream_ctx) * num_stream_ctxs; if (size > MEDIUM_STREAM_ARRAY_SIZE) @@ -614,7 +614,7 @@ static struct xhci_stream_ctx *xhci_alloc_stream_ctx(struct xhci_hcd *xhci, unsigned int num_stream_ctxs, dma_addr_t *dma, gfp_t mem_flags) { - struct device *dev = xhci_to_hcd(xhci)->self.controller; + struct device *dev = xhci_to_hcd(xhci)->self.sysdev; size_t size = sizeof(struct xhci_stream_ctx) * num_stream_ctxs; if (size > MEDIUM_STREAM_ARRAY_SIZE) @@ -1706,7 +1706,7 @@ void xhci_slot_copy(struct xhci_hcd *xhci, static int scratchpad_alloc(struct xhci_hcd *xhci, gfp_t flags) { int i; - struct device *dev = xhci_to_hcd(xhci)->self.controller; + struct device *dev = xhci_to_hcd(xhci)->self.sysdev; int num_sp = HCS_MAX_SCRATCHPAD(xhci->hcs_params2); xhci_dbg_trace(xhci, trace_xhci_dbg_init, @@ -1729,36 +1729,27 @@ static int scratchpad_alloc(struct xhci_hcd *xhci, gfp_t flags) if (!xhci->scratchpad->sp_buffers) goto fail_sp3; - xhci->scratchpad->sp_dma_buffers = - kzalloc(sizeof(dma_addr_t) * num_sp, flags); - - if (!xhci->scratchpad->sp_dma_buffers) - goto fail_sp4; - xhci->dcbaa->dev_context_ptrs[0] = cpu_to_le64(xhci->scratchpad->sp_dma); for (i = 0; i < num_sp; i++) { dma_addr_t dma; void *buf = dma_zalloc_coherent(dev, xhci->page_size, &dma, flags); if (!buf) - goto fail_sp5; + goto fail_sp4; xhci->scratchpad->sp_array[i] = dma; xhci->scratchpad->sp_buffers[i] = buf; - xhci->scratchpad->sp_dma_buffers[i] = dma; } return 0; - fail_sp5: + fail_sp4: for (i = i - 1; i >= 0; i--) { dma_free_coherent(dev, xhci->page_size, xhci->scratchpad->sp_buffers[i], - xhci->scratchpad->sp_dma_buffers[i]); + xhci->scratchpad->sp_array[i]); } - kfree(xhci->scratchpad->sp_dma_buffers); - fail_sp4: kfree(xhci->scratchpad->sp_buffers); fail_sp3: @@ -1778,7 +1769,7 @@ static void scratchpad_free(struct xhci_hcd *xhci) { int num_sp; int i; - struct device *dev = xhci_to_hcd(xhci)->self.controller; + struct device *dev = xhci_to_hcd(xhci)->self.sysdev; if (!xhci->scratchpad) return; @@ -1788,9 +1779,8 @@ static void scratchpad_free(struct xhci_hcd *xhci) for (i = 0; i < num_sp; i++) { dma_free_coherent(dev, xhci->page_size, xhci->scratchpad->sp_buffers[i], - xhci->scratchpad->sp_dma_buffers[i]); + xhci->scratchpad->sp_array[i]); } - kfree(xhci->scratchpad->sp_dma_buffers); kfree(xhci->scratchpad->sp_buffers); dma_free_coherent(dev, num_sp * sizeof(u64), xhci->scratchpad->sp_array, @@ -1854,7 +1844,7 @@ void xhci_free_command(struct xhci_hcd *xhci, void xhci_mem_cleanup(struct xhci_hcd *xhci) { - struct device *dev = xhci_to_hcd(xhci)->self.controller; + struct device *dev = xhci_to_hcd(xhci)->self.sysdev; int size; int i, j, num_ports; @@ -2399,7 +2389,7 @@ static int xhci_setup_port_arrays(struct xhci_hcd *xhci, gfp_t flags) int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) { dma_addr_t dma; - struct device *dev = xhci_to_hcd(xhci)->self.controller; + struct device *dev = xhci_to_hcd(xhci)->self.sysdev; unsigned int val, val2; u64 val_64; struct xhci_segment *seg; @@ -2445,7 +2435,7 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) writel(val, &xhci->op_regs->config_reg); /* - * Section 5.4.8 - doorbell array must be + * xHCI section 5.4.6 - doorbell array must be * "physically contiguous and 64-byte (cache line) aligned". */ xhci->dcbaa = dma_alloc_coherent(dev, sizeof(*xhci->dcbaa), &dma, diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c index ca8b0b1ae37d..248cdaa62397 100644 --- a/drivers/usb/host/xhci-plat.c +++ b/drivers/usb/host/xhci-plat.c @@ -14,11 +14,14 @@ #include <linux/clk.h> #include <linux/dma-mapping.h> #include <linux/module.h> +#include <linux/pci.h> #include <linux/of.h> #include <linux/platform_device.h> #include <linux/usb/phy.h> #include <linux/slab.h> #include <linux/acpi.h> +#include <linux/busfreq-imx.h> +#include <linux/usb/of.h> #include "xhci.h" #include "xhci-plat.h" @@ -139,6 +142,7 @@ static int xhci_plat_probe(struct platform_device *pdev) { const struct of_device_id *match; const struct hc_driver *driver; + struct device *sysdev; struct xhci_hcd *xhci; struct resource *res; struct usb_hcd *hcd; @@ -155,24 +159,47 @@ static int xhci_plat_probe(struct platform_device *pdev) if (irq < 0) return irq; + /* + * sysdev must point to a device that is known to the system firmware + * or PCI hardware. We handle these three cases here: + * 1. xhci_plat comes from firmware + * 2. xhci_plat is child of a device from firmware (dwc3-plat) + * 3. xhci_plat is grandchild of a pci device (dwc3-pci) + */ + sysdev = &pdev->dev; + if (sysdev->parent && !sysdev->of_node && sysdev->parent->of_node) + sysdev = sysdev->parent; +#ifdef CONFIG_PCI + else if (sysdev->parent && sysdev->parent->parent && + sysdev->parent->parent->bus == &pci_bus_type) + sysdev = sysdev->parent->parent; +#endif + /* Try to set 64-bit DMA first */ - if (WARN_ON(!pdev->dev.dma_mask)) + if (WARN_ON(!sysdev->dma_mask)) /* Platform did not initialize dma_mask */ - ret = dma_coerce_mask_and_coherent(&pdev->dev, + ret = dma_coerce_mask_and_coherent(sysdev, DMA_BIT_MASK(64)); else - ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); + ret = dma_set_mask_and_coherent(sysdev, DMA_BIT_MASK(64)); /* If seting 64-bit DMA mask fails, fall back to 32-bit DMA mask */ if (ret) { - ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); + ret = dma_set_mask_and_coherent(sysdev, DMA_BIT_MASK(32)); if (ret) return ret; } - hcd = usb_create_hcd(driver, &pdev->dev, dev_name(&pdev->dev)); - if (!hcd) - return -ENOMEM; + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + pm_runtime_get_noresume(&pdev->dev); + + hcd = __usb_create_hcd(driver, sysdev, &pdev->dev, + dev_name(&pdev->dev), NULL); + if (!hcd) { + ret = -ENOMEM; + goto disable_runtime; + } res = platform_get_resource(pdev, IORESOURCE_MEM, 0); hcd->regs = devm_ioremap_resource(&pdev->dev, res); @@ -213,20 +240,23 @@ static int xhci_plat_probe(struct platform_device *pdev) xhci->clk = clk; xhci->main_hcd = hcd; - xhci->shared_hcd = usb_create_shared_hcd(driver, &pdev->dev, + xhci->shared_hcd = __usb_create_hcd(driver, sysdev, &pdev->dev, dev_name(&pdev->dev), hcd); if (!xhci->shared_hcd) { ret = -ENOMEM; goto disable_clk; } - if (device_property_read_bool(&pdev->dev, "usb3-lpm-capable")) + if (device_property_read_bool(sysdev, "usb3-lpm-capable")) xhci->quirks |= XHCI_LPM_SUPPORT; + if (device_property_read_bool(sysdev, "usb3-resume-missing-cas")) + xhci->quirks |= XHCI_MISSING_CAS; + if (device_property_read_bool(&pdev->dev, "quirk-broken-port-ped")) xhci->quirks |= XHCI_BROKEN_PORT_PED; - hcd->usb_phy = devm_usb_get_phy_by_phandle(&pdev->dev, "usb-phy", 0); + hcd->usb_phy = devm_usb_get_phy_by_phandle(sysdev, "usb-phy", 0); if (IS_ERR(hcd->usb_phy)) { ret = PTR_ERR(hcd->usb_phy); if (ret == -EPROBE_DEFER) @@ -238,6 +268,10 @@ static int xhci_plat_probe(struct platform_device *pdev) goto put_usb3_hcd; } + request_bus_freq(BUS_FREQ_HIGH); + hcd->tpl_support = of_usb_host_tpl_support(sysdev->of_node); + xhci->shared_hcd->tpl_support = hcd->tpl_support; + ret = usb_add_hcd(hcd, irq, IRQF_SHARED); if (ret) goto disable_usb_phy; @@ -249,6 +283,9 @@ static int xhci_plat_probe(struct platform_device *pdev) if (ret) goto dealloc_usb2_hcd; + device_enable_async_suspend(&pdev->dev); + pm_runtime_put_noidle(&pdev->dev); + return 0; @@ -257,6 +294,7 @@ dealloc_usb2_hcd: disable_usb_phy: usb_phy_shutdown(hcd->usb_phy); + release_bus_freq(BUS_FREQ_HIGH); put_usb3_hcd: usb_put_hcd(xhci->shared_hcd); @@ -268,6 +306,10 @@ disable_clk: put_hcd: usb_put_hcd(hcd); +disable_runtime: + pm_runtime_put_noidle(&pdev->dev); + pm_runtime_disable(&pdev->dev); + return ret; } @@ -289,6 +331,12 @@ static int xhci_plat_remove(struct platform_device *dev) clk_disable_unprepare(clk); usb_put_hcd(hcd); + if (!pm_runtime_suspended(&dev->dev)) + release_bus_freq(BUS_FREQ_HIGH); + + pm_runtime_set_suspended(&dev->dev); + pm_runtime_disable(&dev->dev); + return 0; } @@ -316,14 +364,29 @@ static int xhci_plat_resume(struct device *dev) return xhci_resume(xhci, 0); } +#endif /* CONFIG_PM_SLEEP */ + +#ifdef CONFIG_PM +static int xhci_plat_runtime_suspend(struct device *dev) +{ + release_bus_freq(BUS_FREQ_HIGH); + return 0; +} + +static int xhci_plat_runtime_resume(struct device *dev) +{ + request_bus_freq(BUS_FREQ_HIGH); + return 0; +} +#endif /* CONFIG_PM */ static const struct dev_pm_ops xhci_plat_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(xhci_plat_suspend, xhci_plat_resume) + + SET_RUNTIME_PM_OPS(xhci_plat_runtime_suspend, + xhci_plat_runtime_resume, + NULL) }; -#define DEV_PM_OPS (&xhci_plat_pm_ops) -#else -#define DEV_PM_OPS NULL -#endif /* CONFIG_PM */ static const struct acpi_device_id usb_xhci_acpi_match[] = { /* XHCI-compliant USB Controller */ @@ -337,7 +400,7 @@ static struct platform_driver usb_xhci_driver = { .remove = xhci_plat_remove, .driver = { .name = "xhci-hcd", - .pm = DEV_PM_OPS, + .pm = &xhci_plat_pm_ops, .of_match_table = of_match_ptr(usb_xhci_of_match), .acpi_match_table = ACPI_PTR(usb_xhci_acpi_match), }, diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 89a14d5f6ad8..806816401f59 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -1968,9 +1968,7 @@ static int process_ctrl_td(struct xhci_hcd *xhci, struct xhci_td *td, switch (trb_comp_code) { case COMP_SUCCESS: if (event_trb == ep_ring->dequeue) { - xhci_warn(xhci, "WARN: Success on ctrl setup TRB " - "without IOC set??\n"); - *status = -ESHUTDOWN; + *status = 0; } else if (event_trb != td->last_trb) { xhci_warn(xhci, "WARN: Success on ctrl data TRB " "without IOC set??\n"); @@ -2770,12 +2768,9 @@ hw_died: */ status |= STS_EINT; writel(status, &xhci->op_regs->status); - /* FIXME when MSI-X is supported and there are multiple vectors */ - /* Clear the MSI-X event interrupt status */ - if (hcd->irq) { + if (!hcd->msi_enabled) { u32 irq_pending; - /* Acknowledge the PCI interrupt */ irq_pending = readl(&xhci->ir_set->irq_pending); irq_pending |= IMAN_IP; writel(irq_pending, &xhci->ir_set->irq_pending); @@ -3488,6 +3483,135 @@ 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 = kzalloc(sizeof(struct xhci_td), GFP_KERNEL); + if (!td) { + kfree(urb_priv); + return -ENOMEM; + } + + urb_priv->td[0] = td; + urb_priv->length = 1; + urb_priv->td_cnt = 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 e9f5f9c32b49..dbf7bd4582e1 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -125,7 +125,7 @@ int xhci_halt(struct xhci_hcd *xhci) /* * Set the run bit and wait for the host to be running. */ -static int xhci_start(struct xhci_hcd *xhci) +int xhci_start(struct xhci_hcd *xhci) { u32 temp; int ret; @@ -184,7 +184,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, @@ -234,6 +234,9 @@ static int xhci_free_msi(struct xhci_hcd *xhci) static int xhci_setup_msi(struct xhci_hcd *xhci) { int ret; + /* + * TODO:Check with MSI Soc for sysdev + */ struct pci_dev *pdev = to_pci_dev(xhci_to_hcd(xhci)->self.controller); ret = pci_enable_msi(pdev); @@ -260,7 +263,7 @@ static int xhci_setup_msi(struct xhci_hcd *xhci) */ static void xhci_free_irq(struct xhci_hcd *xhci) { - struct pci_dev *pdev = to_pci_dev(xhci_to_hcd(xhci)->self.controller); + struct pci_dev *pdev = to_pci_dev(xhci_to_hcd(xhci)->self.sysdev); int ret; /* return if using legacy interrupt */ @@ -395,9 +398,10 @@ static int xhci_try_enable_msi(struct usb_hcd *hcd) /* fall back to msi*/ ret = xhci_setup_msi(xhci); - if (!ret) - /* hcd->irq is 0, we have MSI */ + if (!ret) { + hcd->msi_enabled = 1; return 0; + } if (!pdev->irq) { xhci_err(xhci, "No msi-x/msi found and no IRQ in BIOS\n"); @@ -746,7 +750,7 @@ void xhci_shutdown(struct usb_hcd *hcd) struct xhci_hcd *xhci = hcd_to_xhci(hcd); if (xhci->quirks & XHCI_SPURIOUS_REBOOT) - usb_disable_xhci_ports(to_pci_dev(hcd->self.controller)); + usb_disable_xhci_ports(to_pci_dev(hcd->self.sysdev)); spin_lock_irq(&xhci->lock); xhci_halt(xhci); @@ -763,7 +767,7 @@ void xhci_shutdown(struct usb_hcd *hcd) /* Yet another workaround for spurious wakeups at shutdown with HSW */ if (xhci->quirks & XHCI_SPURIOUS_WAKEUP) - pci_set_power_state(to_pci_dev(hcd->self.controller), PCI_D3hot); + pci_set_power_state(to_pci_dev(hcd->self.sysdev), PCI_D3hot); } #ifdef CONFIG_PM @@ -3619,8 +3623,6 @@ void xhci_free_dev(struct usb_hcd *hcd, struct usb_device *udev) { struct xhci_hcd *xhci = hcd_to_xhci(hcd); struct xhci_virt_device *virt_dev; - unsigned long flags; - u32 state; int i, ret; struct xhci_command *command; @@ -3655,6 +3657,29 @@ void xhci_free_dev(struct usb_hcd *hcd, struct usb_device *udev) del_timer_sync(&virt_dev->eps[i].stop_cmd_timer); } + xhci_disable_slot(xhci, command, udev->slot_id); + /* + * Event command completion handler will free any data structures + * associated with the slot. XXX Can free sleep? + */ +} + +int xhci_disable_slot(struct xhci_hcd *xhci, struct xhci_command *command, + u32 slot_id) +{ + unsigned long flags; + u32 state; + int ret = 0; + struct xhci_virt_device *virt_dev; + + virt_dev = xhci->devs[slot_id]; + if (!virt_dev) + return -EINVAL; + if (!command) + command = xhci_alloc_command(xhci, false, false, GFP_KERNEL); + if (!command) + return -ENOMEM; + spin_lock_irqsave(&xhci->lock, flags); virt_dev->udev = NULL; @@ -3663,25 +3688,22 @@ void xhci_free_dev(struct usb_hcd *hcd, struct usb_device *udev) state = readl(&xhci->op_regs->status); if (state == 0xffffffff || (xhci->xhc_state & XHCI_STATE_DYING) || (xhci->xhc_state & XHCI_STATE_HALTED)) { - xhci_free_virt_device(xhci, udev->slot_id); + xhci_free_virt_device(xhci, slot_id); spin_unlock_irqrestore(&xhci->lock, flags); kfree(command); - return; + return ret; } - if (xhci_queue_slot_control(xhci, command, TRB_DISABLE_SLOT, - udev->slot_id)) { + ret = xhci_queue_slot_control(xhci, command, TRB_DISABLE_SLOT, + slot_id); + if (ret) { spin_unlock_irqrestore(&xhci->lock, flags); xhci_dbg(xhci, "FIXME: allocate a command ring segment\n"); - return; + return ret; } xhci_ring_cmd_db(xhci); spin_unlock_irqrestore(&xhci->lock, flags); - - /* - * Event command completion handler will free any data structures - * associated with the slot. XXX Can free sleep? - */ + return ret; } /* @@ -3789,14 +3811,10 @@ int xhci_alloc_dev(struct usb_hcd *hcd, struct usb_device *udev) disable_slot: /* Disable slot, if we can do it without mem alloc */ - spin_lock_irqsave(&xhci->lock, flags); + kfree(command->completion); command->completion = NULL; command->status = 0; - if (!xhci_queue_slot_control(xhci, command, TRB_DISABLE_SLOT, - udev->slot_id)) - xhci_ring_cmd_db(xhci); - spin_unlock_irqrestore(&xhci->lock, flags); - return 0; + return xhci_disable_slot(xhci, command, udev->slot_id); } /* @@ -4869,7 +4887,11 @@ int xhci_get_frame(struct usb_hcd *hcd) int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks) { struct xhci_hcd *xhci; - struct device *dev = hcd->self.controller; + /* + * TODO: Check with DWC3 clients for sysdev according to + * quirks + */ + struct device *dev = hcd->self.sysdev; int retval; /* Accept arbitrarily long scatter-gather lists */ @@ -5050,6 +5072,7 @@ static const struct hc_driver xhci_hc_driver = { .enable_usb3_lpm_timeout = xhci_enable_usb3_lpm_timeout, .disable_usb3_lpm_timeout = xhci_disable_usb3_lpm_timeout, .find_raw_port_number = xhci_find_raw_port_number, + .submit_single_step_set_feature = xhci_submit_single_step_set_feature, }; void xhci_init_driver(struct hc_driver *drv, diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index b9181281aa9e..a6dd4d97a179 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -429,6 +429,7 @@ struct xhci_op_regs { #define PORT_L1DS_MASK (0xff << 8) #define PORT_L1DS(p) (((p) & 0xff) << 8) #define PORT_HLE (1 << 16) +#define PORT_TEST_MODE_SHIFT 28 /* USB3 Protocol PORTLI Port Link Information */ #define PORT_RX_LANES(p) (((p) >> 16) & 0xf) @@ -1444,7 +1445,6 @@ struct xhci_scratchpad { u64 *sp_array; dma_addr_t sp_dma; void **sp_buffers; - dma_addr_t *sp_dma_buffers; }; struct urb_priv { @@ -1666,6 +1666,8 @@ struct xhci_hcd { #define XHCI_LIMIT_ENDPOINT_INTERVAL_7 (1 << 26) /* Reserved. It was XHCI_U2_DISABLE_WAKE */ #define XHCI_ASMEDIA_MODIFY_FLOWCONTROL (1 << 28) +#define XHCI_SKIP_ACCESS_RESERVED_REG (1 << 29) +#define XHCI_CDNS_HOST (1 << 30) unsigned int num_active_eps; unsigned int limit_active_eps; @@ -1691,6 +1693,7 @@ struct xhci_hcd { /* Compliance Mode Recovery Data */ struct timer_list comp_mode_recovery_timer; u32 port_status_u0; + u16 test_mode; /* Compliance Mode Timer Triggered every 2 seconds */ #define COMP_MODE_RCVRY_MSECS 2000 @@ -1858,6 +1861,7 @@ typedef void (*xhci_get_quirks_t)(struct device *, struct xhci_hcd *); int xhci_handshake(void __iomem *ptr, u32 mask, u32 done, int usec); void xhci_quiesce(struct xhci_hcd *xhci); int xhci_halt(struct xhci_hcd *xhci); +int xhci_start(struct xhci_hcd *xhci); int xhci_reset(struct xhci_hcd *xhci); int xhci_init(struct usb_hcd *hcd); int xhci_run(struct usb_hcd *hcd); @@ -1866,6 +1870,8 @@ void xhci_shutdown(struct usb_hcd *hcd); int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks); void xhci_init_driver(struct hc_driver *drv, const struct xhci_driver_overrides *over); +int xhci_disable_slot(struct xhci_hcd *xhci, + struct xhci_command *command, u32 slot_id); #ifdef CONFIG_PM int xhci_suspend(struct xhci_hcd *xhci, bool do_wakeup); @@ -1971,6 +1977,16 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, u16 wIndex, char *buf, u16 wLength); int xhci_hub_status_data(struct usb_hcd *hcd, char *buf); int xhci_find_raw_port_number(struct usb_hcd *hcd, int port1); +#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/misc/ehset.c b/drivers/usb/misc/ehset.c index c31b4a33e6bb..f54473cd25b3 100644 --- a/drivers/usb/misc/ehset.c +++ b/drivers/usb/misc/ehset.c @@ -17,6 +17,7 @@ #include <linux/slab.h> #include <linux/usb.h> #include <linux/usb/ch11.h> +#include <linux/usb/otg-fsm.h> #define TEST_SE0_NAK_PID 0x0101 #define TEST_J_PID 0x0102 @@ -25,6 +26,7 @@ #define TEST_HS_HOST_PORT_SUSPEND_RESUME 0x0106 #define TEST_SINGLE_STEP_GET_DEV_DESC 0x0107 #define TEST_SINGLE_STEP_SET_FEATURE 0x0108 +#define TEST_OTG_TEST_DEVICE_SUPPORT 0x0200 static int ehset_probe(struct usb_interface *intf, const struct usb_device_id *id) @@ -35,6 +37,7 @@ static int ehset_probe(struct usb_interface *intf, struct usb_device_descriptor *buf; u8 portnum = dev->portnum; u16 test_pid = le16_to_cpu(dev->descriptor.idProduct); + struct otg_fsm *fsm = dev->bus->otg_fsm; switch (test_pid) { case TEST_SE0_NAK_PID: @@ -115,6 +118,27 @@ static int ehset_probe(struct usb_interface *intf, NULL, 0, 60 * 1000); break; + case TEST_OTG_TEST_DEVICE_SUPPORT: + if (!fsm) + return ret; + + /* B host enumerate test device */ + if (dev->bus->is_b_host) { + otg_add_timer(fsm, B_TST_SUSP); + ret = 0; + break; + } + + mutex_lock(&fsm->lock); + fsm->tst_maint = 1; + if (le16_to_cpu(dev->descriptor.bcdDevice) & 0x1) + fsm->otg_vbus_off = 1; + else + fsm->otg_vbus_off = 0; + mutex_unlock(&fsm->lock); + otg_add_timer(fsm, A_TST_MAINT); + ret = 0; + break; default: dev_err(&intf->dev, "%s: unsupported PID: 0x%x\n", __func__, test_pid); @@ -135,6 +159,7 @@ static const struct usb_device_id ehset_id_table[] = { { 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) }, + { USB_DEVICE(0x1a0a, TEST_OTG_TEST_DEVICE_SUPPORT) }, { } /* Terminating entry */ }; MODULE_DEVICE_TABLE(usb, ehset_id_table); diff --git a/drivers/usb/phy/Kconfig b/drivers/usb/phy/Kconfig index 125cea1c3c8d..2c60f0cab568 100644 --- a/drivers/usb/phy/Kconfig +++ b/drivers/usb/phy/Kconfig @@ -187,7 +187,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-generic.c b/drivers/usb/phy/phy-generic.c index 8311ba2968cd..cdc6f862dcdb 100644 --- a/drivers/usb/phy/phy-generic.c +++ b/drivers/usb/phy/phy-generic.c @@ -59,6 +59,16 @@ EXPORT_SYMBOL_GPL(usb_phy_generic_unregister); static int nop_set_suspend(struct usb_phy *x, int suspend) { + struct usb_phy_generic *nop = dev_get_drvdata(x->dev); + + if (IS_ERR(nop->clk)) + return 0; + + if (suspend) + clk_disable_unprepare(nop->clk); + else + clk_prepare_enable(nop->clk); + return 0; } diff --git a/drivers/usb/phy/phy-mxs-usb.c b/drivers/usb/phy/phy-mxs-usb.c index 0e2f1a36d315..92b94341b1b4 100644 --- a/drivers/usb/phy/phy-mxs-usb.c +++ b/drivers/usb/phy/phy-mxs-usb.c @@ -1,5 +1,6 @@ /* - * 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 * @@ -23,9 +24,11 @@ #include <linux/of_device.h> #include <linux/regmap.h> #include <linux/mfd/syscon.h> +#include <linux/regulator/consumer.h> #define DRIVER_NAME "mxs_phy" +/* Register Macro */ #define HW_USBPHY_PWD 0x00 #define HW_USBPHY_TX 0x10 #define HW_USBPHY_CTRL 0x30 @@ -43,6 +46,11 @@ #define GM_USBPHY_TX_TXCAL45DN(x) (((x) & 0xf) << 8) #define GM_USBPHY_TX_D_CAL(x) (((x) & 0xf) << 0) +/* imx7ulp */ +#define HW_USBPHY_PLL_SIC 0xa4 +#define HW_USBPHY_PLL_SIC_SET 0xa4 +#define HW_USBPHY_PLL_SIC_CLR 0xa8 + #define BM_USBPHY_CTRL_SFTRST BIT(31) #define BM_USBPHY_CTRL_CLKGATE BIT(30) #define BM_USBPHY_CTRL_OTG_ID_VALUE BIT(27) @@ -61,8 +69,20 @@ #define BM_USBPHY_IP_FIX (BIT(17) | BIT(18)) #define BM_USBPHY_DEBUG_CLKGATE BIT(30) +/* imx7ulp */ +#define BM_USBPHY_PLL_LOCK BIT(31) +#define BM_USBPHY_PLL_REG_ENABLE BIT(21) +#define BM_USBPHY_PLL_BYPASS BIT(16) +#define BM_USBPHY_PLL_POWER BIT(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 @@ -94,6 +114,11 @@ #define BM_ANADIG_USB2_MISC_RX_VPIN_FS BIT(29) #define BM_ANADIG_USB2_MISC_RX_VMIN_FS BIT(28) +#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) + #define to_mxs_phy(p) container_of((p), struct mxs_phy, phy) /* Do disconnection between PHY and controller without vbus */ @@ -126,6 +151,16 @@ #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) + struct mxs_phy_data { unsigned int flags; }; @@ -137,12 +172,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 = { @@ -151,14 +188,21 @@ 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 = { }; static const struct of_device_id mxs_phy_dt_ids[] = { + { .compatible = "fsl,imx7ulp-usbphy", .data = &imx7ulp_phy_data, }, + { .compatible = "fsl,imx6ul-usbphy", .data = &imx6sx_phy_data, }, { .compatible = "fsl,imx6sx-usbphy", .data = &imx6sx_phy_data, }, { .compatible = "fsl,imx6sl-usbphy", .data = &imx6sl_phy_data, }, { .compatible = "fsl,imx6q-usbphy", .data = &imx6q_phy_data, }, @@ -177,6 +221,9 @@ struct mxs_phy { 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; }; static inline bool is_imx6q_phy(struct mxs_phy *mxs_phy) @@ -189,6 +236,16 @@ static inline bool is_imx6sl_phy(struct mxs_phy *mxs_phy) return mxs_phy->data == &imx6sl_phy_data; } +static inline bool is_imx6ul_phy(struct mxs_phy *mxs_phy) +{ + return mxs_phy->data == &imx6ul_phy_data; +} + +static inline bool is_imx7ulp_phy(struct mxs_phy *mxs_phy) +{ + return mxs_phy->data == &imx7ulp_phy_data; +} + /* * PHY needs some 32K cycles to switch from 32K clock to * bus (such as AHB/AXI, etc) clock. @@ -212,14 +269,69 @@ static void mxs_phy_tx_init(struct mxs_phy *mxs_phy) } } +static int wait_for_pll_lock(const void __iomem *base) +{ + int loop_count = 100; + + /* Wait for PLL to lock */ + do { + if (readl(base + HW_USBPHY_PLL_SIC) & BM_USBPHY_PLL_LOCK) + break; + usleep_range(100, 150); + } while (loop_count-- > 0); + + return readl(base + HW_USBPHY_PLL_SIC) & BM_USBPHY_PLL_LOCK + ? 0 : -ETIMEDOUT; +} + +static int mxs_phy_pll_enable(void __iomem *base, bool enable) +{ + int ret = 0; + + if (enable) { + writel(BM_USBPHY_PLL_REG_ENABLE, base + HW_USBPHY_PLL_SIC_SET); + writel(BM_USBPHY_PLL_BYPASS, base + HW_USBPHY_PLL_SIC_CLR); + writel(BM_USBPHY_PLL_POWER, base + HW_USBPHY_PLL_SIC_SET); + ret = wait_for_pll_lock(base); + if (ret) + return ret; + writel(BM_USBPHY_PLL_EN_USB_CLKS, base + + HW_USBPHY_PLL_SIC_SET); + } else { + writel(BM_USBPHY_PLL_EN_USB_CLKS, base + + HW_USBPHY_PLL_SIC_CLR); + writel(BM_USBPHY_PLL_POWER, base + HW_USBPHY_PLL_SIC_CLR); + writel(BM_USBPHY_PLL_BYPASS, base + HW_USBPHY_PLL_SIC_SET); + writel(BM_USBPHY_PLL_REG_ENABLE, base + HW_USBPHY_PLL_SIC_CLR); + } + + return ret; +} + static int mxs_phy_hw_init(struct mxs_phy *mxs_phy) { int ret; void __iomem *base = mxs_phy->phy.io_priv; + if (is_imx7ulp_phy(mxs_phy)) { + ret = mxs_phy_pll_enable(base, true); + if (ret) + return ret; + } + ret = stmp_reset_block(base + HW_USBPHY_CTRL); if (ret) - return 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); + goto disable_pll; + } + } /* Power up the PHY */ writel(0, base + HW_USBPHY_PWD); @@ -244,6 +356,11 @@ static int mxs_phy_hw_init(struct mxs_phy *mxs_phy) mxs_phy_tx_init(mxs_phy); return 0; + +disable_pll: + if (is_imx7ulp_phy(mxs_phy)) + mxs_phy_pll_enable(base, false); + return ret; } /* Return true if the vbus is there */ @@ -301,21 +418,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)) @@ -327,7 +433,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 != USB_MODE_HOST) || + (last_event == USB_EVENT_VBUS))) __mxs_phy_disconnect_line(mxs_phy, true); else __mxs_phy_disconnect_line(mxs_phy, false); @@ -365,6 +472,9 @@ static void mxs_phy_shutdown(struct usb_phy *phy) writel(BM_USBPHY_CTRL_CLKGATE, phy->io_priv + HW_USBPHY_CTRL_SET); + if (mxs_phy->phy_3p0) + regulator_disable(mxs_phy->phy_3p0); + clk_disable_unprepare(mxs_phy->clk); } @@ -418,14 +528,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); @@ -479,6 +624,61 @@ static int mxs_phy_on_disconnect(struct usb_phy *phy, return 0; } +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_probe(struct platform_device *pdev) { struct resource *res; @@ -556,6 +756,8 @@ static int mxs_phy_probe(struct platform_device *pdev) if (ret < 0) dev_dbg(&pdev->dev, "failed to get alias id, errno %d\n", ret); mxs_phy->port_id = ret; + mxs_phy->clk = clk; + mxs_phy->data = of_id->data; mxs_phy->phy.io_priv = base; mxs_phy->phy.dev = &pdev->dev; @@ -567,9 +769,28 @@ 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.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->clk = clk; - mxs_phy->data = of_id->data; + 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); @@ -590,18 +811,30 @@ static int mxs_phy_remove(struct platform_device *pdev) #ifdef CONFIG_PM_SLEEP 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) diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig new file mode 100644 index 000000000000..17792f9114c6 --- /dev/null +++ b/drivers/usb/typec/Kconfig @@ -0,0 +1,7 @@ + +menu "USB Power Delivery and Type-C drivers" + +config TYPEC + tristate + +endmenu diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile new file mode 100644 index 000000000000..1012a8bed6d5 --- /dev/null +++ b/drivers/usb/typec/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_TYPEC) += typec.o diff --git a/drivers/usb/typec/typec.c b/drivers/usb/typec/typec.c new file mode 100644 index 000000000000..25b745173f7c --- /dev/null +++ b/drivers/usb/typec/typec.c @@ -0,0 +1,1310 @@ +/* + * USB Type-C Connector Class + * + * Copyright (C) 2017, Intel Corporation + * Author: Heikki Krogerus <heikki.krogerus@linux.intel.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 as + * published by the Free Software Foundation. + */ + +#include <linux/device.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/usb/typec.h> + +struct typec_mode { + int index; + u32 vdo; + char *desc; + enum typec_port_type roles; + + struct typec_altmode *alt_mode; + + unsigned int active:1; + + char group_name[6]; + struct attribute_group group; + struct attribute *attrs[5]; + struct device_attribute vdo_attr; + struct device_attribute desc_attr; + struct device_attribute active_attr; + struct device_attribute roles_attr; +}; + +struct typec_altmode { + struct device dev; + u16 svid; + int n_modes; + struct typec_mode modes[ALTMODE_MAX_MODES]; + const struct attribute_group *mode_groups[ALTMODE_MAX_MODES]; +}; + +struct typec_plug { + struct device dev; + enum typec_plug_index index; +}; + +struct typec_cable { + struct device dev; + enum typec_plug_type type; + struct usb_pd_identity *identity; + unsigned int active:1; +}; + +struct typec_partner { + struct device dev; + unsigned int usb_pd:1; + struct usb_pd_identity *identity; + enum typec_accessory accessory; +}; + +struct typec_port { + unsigned int id; + struct device dev; + + int prefer_role; + enum typec_data_role data_role; + enum typec_role pwr_role; + enum typec_role vconn_role; + enum typec_pwr_opmode pwr_opmode; + + const struct typec_capability *cap; +}; + +#define to_typec_port(_dev_) container_of(_dev_, struct typec_port, dev) +#define to_typec_plug(_dev_) container_of(_dev_, struct typec_plug, dev) +#define to_typec_cable(_dev_) container_of(_dev_, struct typec_cable, dev) +#define to_typec_partner(_dev_) container_of(_dev_, struct typec_partner, dev) +#define to_altmode(_dev_) container_of(_dev_, struct typec_altmode, dev) + +static const struct device_type typec_partner_dev_type; +static const struct device_type typec_cable_dev_type; +static const struct device_type typec_plug_dev_type; +static const struct device_type typec_port_dev_type; + +#define is_typec_partner(_dev_) (_dev_->type == &typec_partner_dev_type) +#define is_typec_cable(_dev_) (_dev_->type == &typec_cable_dev_type) +#define is_typec_plug(_dev_) (_dev_->type == &typec_plug_dev_type) +#define is_typec_port(_dev_) (_dev_->type == &typec_port_dev_type) + +static DEFINE_IDA(typec_index_ida); +static struct class *typec_class; + +/* Common attributes */ + +static const char * const typec_accessory_modes[] = { + [TYPEC_ACCESSORY_NONE] = "none", + [TYPEC_ACCESSORY_AUDIO] = "analog_audio", + [TYPEC_ACCESSORY_DEBUG] = "debug", +}; + +static const char *const typec_port_types[] = { + [TYPEC_PORT_DFP] = "dfp", + [TYPEC_PORT_UFP] = "ufp", + [TYPEC_PORT_DRP] = "drp", +}; + +static struct usb_pd_identity *get_pd_identity(struct device *dev) +{ + if (is_typec_partner(dev)) { + struct typec_partner *partner = to_typec_partner(dev); + + return partner->identity; + } else if (is_typec_cable(dev)) { + struct typec_cable *cable = to_typec_cable(dev); + + return cable->identity; + } + return NULL; +} + +static ssize_t id_header_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_pd_identity *id = get_pd_identity(dev); + + return sprintf(buf, "0x%08x\n", id->id_header); +} +static DEVICE_ATTR_RO(id_header); + +static ssize_t cert_stat_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_pd_identity *id = get_pd_identity(dev); + + return sprintf(buf, "0x%08x\n", id->cert_stat); +} +static DEVICE_ATTR_RO(cert_stat); + +static ssize_t product_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_pd_identity *id = get_pd_identity(dev); + + return sprintf(buf, "0x%08x\n", id->product); +} +static DEVICE_ATTR_RO(product); + +static struct attribute *usb_pd_id_attrs[] = { + &dev_attr_id_header.attr, + &dev_attr_cert_stat.attr, + &dev_attr_product.attr, + NULL +}; + +static const struct attribute_group usb_pd_id_group = { + .name = "identity", + .attrs = usb_pd_id_attrs, +}; + +static const struct attribute_group *usb_pd_id_groups[] = { + &usb_pd_id_group, + NULL, +}; + +static void typec_report_identity(struct device *dev) +{ + sysfs_notify(&dev->kobj, "identity", "id_header"); + sysfs_notify(&dev->kobj, "identity", "cert_stat"); + sysfs_notify(&dev->kobj, "identity", "product"); +} + +/* ------------------------------------------------------------------------- */ +/* Alternate Modes */ + +/** + * typec_altmode_update_active - Report Enter/Exit mode + * @alt: Handle to the alternate mode + * @mode: Mode index + * @active: True when the mode has been entered + * + * If a partner or cable plug executes Enter/Exit Mode command successfully, the + * drivers use this routine to report the updated state of the mode. + */ +void typec_altmode_update_active(struct typec_altmode *alt, int mode, + bool active) +{ + struct typec_mode *m = &alt->modes[mode]; + char dir[6]; + + if (m->active == active) + return; + + m->active = active; + snprintf(dir, sizeof(dir), "mode%d", mode); + sysfs_notify(&alt->dev.kobj, dir, "active"); + kobject_uevent(&alt->dev.kobj, KOBJ_CHANGE); +} +EXPORT_SYMBOL_GPL(typec_altmode_update_active); + +/** + * typec_altmode2port - Alternate Mode to USB Type-C port + * @alt: The Alternate Mode + * + * Returns handle to the port that a cable plug or partner with @alt is + * connected to. + */ +struct typec_port *typec_altmode2port(struct typec_altmode *alt) +{ + if (is_typec_plug(alt->dev.parent)) + return to_typec_port(alt->dev.parent->parent->parent); + if (is_typec_partner(alt->dev.parent)) + return to_typec_port(alt->dev.parent->parent); + if (is_typec_port(alt->dev.parent)) + return to_typec_port(alt->dev.parent); + + return NULL; +} +EXPORT_SYMBOL_GPL(typec_altmode2port); + +static ssize_t +typec_altmode_vdo_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct typec_mode *mode = container_of(attr, struct typec_mode, + vdo_attr); + + return sprintf(buf, "0x%08x\n", mode->vdo); +} + +static ssize_t +typec_altmode_desc_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct typec_mode *mode = container_of(attr, struct typec_mode, + desc_attr); + + return sprintf(buf, "%s\n", mode->desc ? mode->desc : ""); +} + +static ssize_t +typec_altmode_active_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct typec_mode *mode = container_of(attr, struct typec_mode, + active_attr); + + return sprintf(buf, "%s\n", mode->active ? "yes" : "no"); +} + +static ssize_t +typec_altmode_active_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct typec_mode *mode = container_of(attr, struct typec_mode, + active_attr); + struct typec_port *port = typec_altmode2port(mode->alt_mode); + bool activate; + int ret; + + if (!port->cap->activate_mode) + return -EOPNOTSUPP; + + ret = kstrtobool(buf, &activate); + if (ret) + return ret; + + ret = port->cap->activate_mode(port->cap, mode->index, activate); + if (ret) + return ret; + + return size; +} + +static ssize_t +typec_altmode_roles_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct typec_mode *mode = container_of(attr, struct typec_mode, + roles_attr); + ssize_t ret; + + switch (mode->roles) { + case TYPEC_PORT_DFP: + ret = sprintf(buf, "source\n"); + break; + case TYPEC_PORT_UFP: + ret = sprintf(buf, "sink\n"); + break; + case TYPEC_PORT_DRP: + default: + ret = sprintf(buf, "source sink\n"); + break; + } + return ret; +} + +static void typec_init_modes(struct typec_altmode *alt, + struct typec_mode_desc *desc, bool is_port) +{ + int i; + + for (i = 0; i < alt->n_modes; i++, desc++) { + struct typec_mode *mode = &alt->modes[i]; + + /* Not considering the human readable description critical */ + mode->desc = kstrdup(desc->desc, GFP_KERNEL); + if (desc->desc && !mode->desc) + dev_err(&alt->dev, "failed to copy mode%d desc\n", i); + + mode->alt_mode = alt; + mode->vdo = desc->vdo; + mode->roles = desc->roles; + mode->index = desc->index; + sprintf(mode->group_name, "mode%d", desc->index); + + sysfs_attr_init(&mode->vdo_attr.attr); + mode->vdo_attr.attr.name = "vdo"; + mode->vdo_attr.attr.mode = 0444; + mode->vdo_attr.show = typec_altmode_vdo_show; + + sysfs_attr_init(&mode->desc_attr.attr); + mode->desc_attr.attr.name = "description"; + mode->desc_attr.attr.mode = 0444; + mode->desc_attr.show = typec_altmode_desc_show; + + sysfs_attr_init(&mode->active_attr.attr); + mode->active_attr.attr.name = "active"; + mode->active_attr.attr.mode = 0644; + mode->active_attr.show = typec_altmode_active_show; + mode->active_attr.store = typec_altmode_active_store; + + mode->attrs[0] = &mode->vdo_attr.attr; + mode->attrs[1] = &mode->desc_attr.attr; + mode->attrs[2] = &mode->active_attr.attr; + + /* With ports, list the roles that the mode is supported with */ + if (is_port) { + sysfs_attr_init(&mode->roles_attr.attr); + mode->roles_attr.attr.name = "supported_roles"; + mode->roles_attr.attr.mode = 0444; + mode->roles_attr.show = typec_altmode_roles_show; + + mode->attrs[3] = &mode->roles_attr.attr; + } + + mode->group.attrs = mode->attrs; + mode->group.name = mode->group_name; + + alt->mode_groups[i] = &mode->group; + } +} + +static ssize_t svid_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct typec_altmode *alt = to_altmode(dev); + + return sprintf(buf, "%04x\n", alt->svid); +} +static DEVICE_ATTR_RO(svid); + +static struct attribute *typec_altmode_attrs[] = { + &dev_attr_svid.attr, + NULL +}; +ATTRIBUTE_GROUPS(typec_altmode); + +static void typec_altmode_release(struct device *dev) +{ + struct typec_altmode *alt = to_altmode(dev); + int i; + + for (i = 0; i < alt->n_modes; i++) + kfree(alt->modes[i].desc); + kfree(alt); +} + +static const struct device_type typec_altmode_dev_type = { + .name = "typec_alternate_mode", + .groups = typec_altmode_groups, + .release = typec_altmode_release, +}; + +static struct typec_altmode * +typec_register_altmode(struct device *parent, struct typec_altmode_desc *desc) +{ + struct typec_altmode *alt; + int ret; + + alt = kzalloc(sizeof(*alt), GFP_KERNEL); + if (!alt) + return NULL; + + alt->svid = desc->svid; + alt->n_modes = desc->n_modes; + typec_init_modes(alt, desc->modes, is_typec_port(parent)); + + alt->dev.parent = parent; + alt->dev.groups = alt->mode_groups; + alt->dev.type = &typec_altmode_dev_type; + dev_set_name(&alt->dev, "svid-%04x", alt->svid); + + ret = device_register(&alt->dev); + if (ret) { + dev_err(parent, "failed to register alternate mode (%d)\n", + ret); + put_device(&alt->dev); + return NULL; + } + + return alt; +} + +/** + * typec_unregister_altmode - Unregister Alternate Mode + * @alt: The alternate mode to be unregistered + * + * Unregister device created with typec_partner_register_altmode(), + * typec_plug_register_altmode() or typec_port_register_altmode(). + */ +void typec_unregister_altmode(struct typec_altmode *alt) +{ + if (alt) + device_unregister(&alt->dev); +} +EXPORT_SYMBOL_GPL(typec_unregister_altmode); + +/* ------------------------------------------------------------------------- */ +/* Type-C Partners */ + +static ssize_t accessory_mode_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct typec_partner *p = to_typec_partner(dev); + + return sprintf(buf, "%s\n", typec_accessory_modes[p->accessory]); +} +static DEVICE_ATTR_RO(accessory_mode); + +static ssize_t supports_usb_power_delivery_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct typec_partner *p = to_typec_partner(dev); + + return sprintf(buf, "%s\n", p->usb_pd ? "yes" : "no"); +} +static DEVICE_ATTR_RO(supports_usb_power_delivery); + +static struct attribute *typec_partner_attrs[] = { + &dev_attr_accessory_mode.attr, + &dev_attr_supports_usb_power_delivery.attr, + NULL +}; +ATTRIBUTE_GROUPS(typec_partner); + +static void typec_partner_release(struct device *dev) +{ + struct typec_partner *partner = to_typec_partner(dev); + + kfree(partner); +} + +static const struct device_type typec_partner_dev_type = { + .name = "typec_partner", + .groups = typec_partner_groups, + .release = typec_partner_release, +}; + +/** + * typec_partner_set_identity - Report result from Discover Identity command + * @partner: The partner updated identity values + * + * This routine is used to report that the result of Discover Identity USB power + * delivery command has become available. + */ +int typec_partner_set_identity(struct typec_partner *partner) +{ + if (!partner->identity) + return -EINVAL; + + typec_report_identity(&partner->dev); + return 0; +} +EXPORT_SYMBOL_GPL(typec_partner_set_identity); + +/** + * typec_partner_register_altmode - Register USB Type-C Partner Alternate Mode + * @partner: USB Type-C Partner that supports the alternate mode + * @desc: Description of the alternate mode + * + * This routine is used to register each alternate mode individually that + * @partner has listed in response to Discover SVIDs command. The modes for a + * SVID listed in response to Discover Modes command need to be listed in an + * array in @desc. + * + * Returns handle to the alternate mode on success or NULL on failure. + */ +struct typec_altmode * +typec_partner_register_altmode(struct typec_partner *partner, + struct typec_altmode_desc *desc) +{ + return typec_register_altmode(&partner->dev, desc); +} +EXPORT_SYMBOL_GPL(typec_partner_register_altmode); + +/** + * typec_register_partner - Register a USB Type-C Partner + * @port: The USB Type-C Port the partner is connected to + * @desc: Description of the partner + * + * Registers a device for USB Type-C Partner described in @desc. + * + * Returns handle to the partner on success or NULL on failure. + */ +struct typec_partner *typec_register_partner(struct typec_port *port, + struct typec_partner_desc *desc) +{ + struct typec_partner *partner; + int ret; + + partner = kzalloc(sizeof(*partner), GFP_KERNEL); + if (!partner) + return NULL; + + partner->usb_pd = desc->usb_pd; + partner->accessory = desc->accessory; + + if (desc->identity) { + /* + * Creating directory for the identity only if the driver is + * able to provide data to it. + */ + partner->dev.groups = usb_pd_id_groups; + partner->identity = desc->identity; + } + + partner->dev.class = typec_class; + partner->dev.parent = &port->dev; + partner->dev.type = &typec_partner_dev_type; + dev_set_name(&partner->dev, "%s-partner", dev_name(&port->dev)); + + ret = device_register(&partner->dev); + if (ret) { + dev_err(&port->dev, "failed to register partner (%d)\n", ret); + put_device(&partner->dev); + return NULL; + } + + return partner; +} +EXPORT_SYMBOL_GPL(typec_register_partner); + +/** + * typec_unregister_partner - Unregister a USB Type-C Partner + * @partner: The partner to be unregistered + * + * Unregister device created with typec_register_partner(). + */ +void typec_unregister_partner(struct typec_partner *partner) +{ + if (partner) + device_unregister(&partner->dev); +} +EXPORT_SYMBOL_GPL(typec_unregister_partner); + +/* ------------------------------------------------------------------------- */ +/* Type-C Cable Plugs */ + +static void typec_plug_release(struct device *dev) +{ + struct typec_plug *plug = to_typec_plug(dev); + + kfree(plug); +} + +static const struct device_type typec_plug_dev_type = { + .name = "typec_plug", + .release = typec_plug_release, +}; + +/** + * typec_plug_register_altmode - Register USB Type-C Cable Plug Alternate Mode + * @plug: USB Type-C Cable Plug that supports the alternate mode + * @desc: Description of the alternate mode + * + * This routine is used to register each alternate mode individually that @plug + * has listed in response to Discover SVIDs command. The modes for a SVID that + * the plug lists in response to Discover Modes command need to be listed in an + * array in @desc. + * + * Returns handle to the alternate mode on success or NULL on failure. + */ +struct typec_altmode * +typec_plug_register_altmode(struct typec_plug *plug, + struct typec_altmode_desc *desc) +{ + return typec_register_altmode(&plug->dev, desc); +} +EXPORT_SYMBOL_GPL(typec_plug_register_altmode); + +/** + * typec_register_plug - Register a USB Type-C Cable Plug + * @cable: USB Type-C Cable with the plug + * @desc: Description of the cable plug + * + * Registers a device for USB Type-C Cable Plug described in @desc. A USB Type-C + * Cable Plug represents a plug with electronics in it that can response to USB + * Power Delivery SOP Prime or SOP Double Prime packages. + * + * Returns handle to the cable plug on success or NULL on failure. + */ +struct typec_plug *typec_register_plug(struct typec_cable *cable, + struct typec_plug_desc *desc) +{ + struct typec_plug *plug; + char name[8]; + int ret; + + plug = kzalloc(sizeof(*plug), GFP_KERNEL); + if (!plug) + return NULL; + + sprintf(name, "plug%d", desc->index); + + plug->index = desc->index; + plug->dev.class = typec_class; + plug->dev.parent = &cable->dev; + plug->dev.type = &typec_plug_dev_type; + dev_set_name(&plug->dev, "%s-%s", dev_name(cable->dev.parent), name); + + ret = device_register(&plug->dev); + if (ret) { + dev_err(&cable->dev, "failed to register plug (%d)\n", ret); + put_device(&plug->dev); + return NULL; + } + + return plug; +} +EXPORT_SYMBOL_GPL(typec_register_plug); + +/** + * typec_unregister_plug - Unregister a USB Type-C Cable Plug + * @plug: The cable plug to be unregistered + * + * Unregister device created with typec_register_plug(). + */ +void typec_unregister_plug(struct typec_plug *plug) +{ + if (plug) + device_unregister(&plug->dev); +} +EXPORT_SYMBOL_GPL(typec_unregister_plug); + +/* Type-C Cables */ + +static ssize_t +type_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct typec_cable *cable = to_typec_cable(dev); + + return sprintf(buf, "%s\n", cable->active ? "active" : "passive"); +} +static DEVICE_ATTR_RO(type); + +static const char * const typec_plug_types[] = { + [USB_PLUG_NONE] = "unknown", + [USB_PLUG_TYPE_A] = "type-a", + [USB_PLUG_TYPE_B] = "type-b", + [USB_PLUG_TYPE_C] = "type-c", + [USB_PLUG_CAPTIVE] = "captive", +}; + +static ssize_t plug_type_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct typec_cable *cable = to_typec_cable(dev); + + return sprintf(buf, "%s\n", typec_plug_types[cable->type]); +} +static DEVICE_ATTR_RO(plug_type); + +static struct attribute *typec_cable_attrs[] = { + &dev_attr_type.attr, + &dev_attr_plug_type.attr, + NULL +}; +ATTRIBUTE_GROUPS(typec_cable); + +static void typec_cable_release(struct device *dev) +{ + struct typec_cable *cable = to_typec_cable(dev); + + kfree(cable); +} + +static const struct device_type typec_cable_dev_type = { + .name = "typec_cable", + .groups = typec_cable_groups, + .release = typec_cable_release, +}; + +/** + * typec_cable_set_identity - Report result from Discover Identity command + * @cable: The cable updated identity values + * + * This routine is used to report that the result of Discover Identity USB power + * delivery command has become available. + */ +int typec_cable_set_identity(struct typec_cable *cable) +{ + if (!cable->identity) + return -EINVAL; + + typec_report_identity(&cable->dev); + return 0; +} +EXPORT_SYMBOL_GPL(typec_cable_set_identity); + +/** + * typec_register_cable - Register a USB Type-C Cable + * @port: The USB Type-C Port the cable is connected to + * @desc: Description of the cable + * + * Registers a device for USB Type-C Cable described in @desc. The cable will be + * parent for the optional cable plug devises. + * + * Returns handle to the cable on success or NULL on failure. + */ +struct typec_cable *typec_register_cable(struct typec_port *port, + struct typec_cable_desc *desc) +{ + struct typec_cable *cable; + int ret; + + cable = kzalloc(sizeof(*cable), GFP_KERNEL); + if (!cable) + return NULL; + + cable->type = desc->type; + cable->active = desc->active; + + if (desc->identity) { + /* + * Creating directory for the identity only if the driver is + * able to provide data to it. + */ + cable->dev.groups = usb_pd_id_groups; + cable->identity = desc->identity; + } + + cable->dev.class = typec_class; + cable->dev.parent = &port->dev; + cable->dev.type = &typec_cable_dev_type; + dev_set_name(&cable->dev, "%s-cable", dev_name(&port->dev)); + + ret = device_register(&cable->dev); + if (ret) { + dev_err(&port->dev, "failed to register cable (%d)\n", ret); + put_device(&cable->dev); + return NULL; + } + + return cable; +} +EXPORT_SYMBOL_GPL(typec_register_cable); + +/** + * typec_unregister_cable - Unregister a USB Type-C Cable + * @cable: The cable to be unregistered + * + * Unregister device created with typec_register_cable(). + */ +void typec_unregister_cable(struct typec_cable *cable) +{ + if (cable) + device_unregister(&cable->dev); +} +EXPORT_SYMBOL_GPL(typec_unregister_cable); + +/* ------------------------------------------------------------------------- */ +/* USB Type-C ports */ + +static const char * const typec_roles[] = { + [TYPEC_SINK] = "sink", + [TYPEC_SOURCE] = "source", +}; + +static const char * const typec_data_roles[] = { + [TYPEC_DEVICE] = "device", + [TYPEC_HOST] = "host", +}; + +static ssize_t +preferred_role_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct typec_port *port = to_typec_port(dev); + int role; + int ret; + + if (port->cap->type != TYPEC_PORT_DRP) { + dev_dbg(dev, "Preferred role only supported with DRP ports\n"); + return -EOPNOTSUPP; + } + + if (!port->cap->try_role) { + dev_dbg(dev, "Setting preferred role not supported\n"); + return -EOPNOTSUPP; + } + + role = sysfs_match_string(typec_roles, buf); + if (role < 0) { + if (sysfs_streq(buf, "none")) + role = TYPEC_NO_PREFERRED_ROLE; + else + return -EINVAL; + } + + ret = port->cap->try_role(port->cap, role); + if (ret) + return ret; + + port->prefer_role = role; + return size; +} + +static ssize_t +preferred_role_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct typec_port *port = to_typec_port(dev); + + if (port->cap->type != TYPEC_PORT_DRP) + return 0; + + if (port->prefer_role < 0) + return 0; + + return sprintf(buf, "%s\n", typec_roles[port->prefer_role]); +} +static DEVICE_ATTR_RW(preferred_role); + +static ssize_t data_role_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct typec_port *port = to_typec_port(dev); + int ret; + + if (port->cap->type != TYPEC_PORT_DRP) { + dev_dbg(dev, "data role swap only supported with DRP ports\n"); + return -EOPNOTSUPP; + } + + if (!port->cap->dr_set) { + dev_dbg(dev, "data role swapping not supported\n"); + return -EOPNOTSUPP; + } + + ret = sysfs_match_string(typec_data_roles, buf); + if (ret < 0) + return ret; + + ret = port->cap->dr_set(port->cap, ret); + if (ret) + return ret; + + return size; +} + +static ssize_t data_role_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct typec_port *port = to_typec_port(dev); + + if (port->cap->type == TYPEC_PORT_DRP) + return sprintf(buf, "%s\n", port->data_role == TYPEC_HOST ? + "[host] device" : "host [device]"); + + return sprintf(buf, "[%s]\n", typec_data_roles[port->data_role]); +} +static DEVICE_ATTR_RW(data_role); + +static ssize_t power_role_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct typec_port *port = to_typec_port(dev); + int ret = size; + + if (!port->cap->pd_revision) { + dev_dbg(dev, "USB Power Delivery not supported\n"); + return -EOPNOTSUPP; + } + + if (!port->cap->pr_set) { + dev_dbg(dev, "power role swapping not supported\n"); + return -EOPNOTSUPP; + } + + if (port->pwr_opmode != TYPEC_PWR_MODE_PD) { + dev_dbg(dev, "partner unable to swap power role\n"); + return -EIO; + } + + ret = sysfs_match_string(typec_roles, buf); + if (ret < 0) + return ret; + + ret = port->cap->pr_set(port->cap, ret); + if (ret) + return ret; + + return size; +} + +static ssize_t power_role_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct typec_port *port = to_typec_port(dev); + + if (port->cap->type == TYPEC_PORT_DRP) + return sprintf(buf, "%s\n", port->pwr_role == TYPEC_SOURCE ? + "[source] sink" : "source [sink]"); + + return sprintf(buf, "[%s]\n", typec_roles[port->pwr_role]); +} +static DEVICE_ATTR_RW(power_role); + +static const char * const typec_pwr_opmodes[] = { + [TYPEC_PWR_MODE_USB] = "default", + [TYPEC_PWR_MODE_1_5A] = "1.5A", + [TYPEC_PWR_MODE_3_0A] = "3.0A", + [TYPEC_PWR_MODE_PD] = "usb_power_delivery", +}; + +static ssize_t power_operation_mode_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct typec_port *port = to_typec_port(dev); + + return sprintf(buf, "%s\n", typec_pwr_opmodes[port->pwr_opmode]); +} +static DEVICE_ATTR_RO(power_operation_mode); + +static ssize_t vconn_source_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct typec_port *port = to_typec_port(dev); + bool source; + int ret; + + if (!port->cap->pd_revision) { + dev_dbg(dev, "VCONN swap depends on USB Power Delivery\n"); + return -EOPNOTSUPP; + } + + if (!port->cap->vconn_set) { + dev_dbg(dev, "VCONN swapping not supported\n"); + return -EOPNOTSUPP; + } + + ret = kstrtobool(buf, &source); + if (ret) + return ret; + + ret = port->cap->vconn_set(port->cap, (enum typec_role)source); + if (ret) + return ret; + + return size; +} + +static ssize_t vconn_source_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct typec_port *port = to_typec_port(dev); + + return sprintf(buf, "%s\n", + port->vconn_role == TYPEC_SOURCE ? "yes" : "no"); +} +static DEVICE_ATTR_RW(vconn_source); + +static ssize_t supported_accessory_modes_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct typec_port *port = to_typec_port(dev); + ssize_t ret = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(port->cap->accessory); i++) { + if (port->cap->accessory[i]) + ret += sprintf(buf + ret, "%s ", + typec_accessory_modes[port->cap->accessory[i]]); + } + + if (!ret) + return sprintf(buf, "none\n"); + + buf[ret - 1] = '\n'; + + return ret; +} +static DEVICE_ATTR_RO(supported_accessory_modes); + +static ssize_t usb_typec_revision_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct typec_port *port = to_typec_port(dev); + u16 rev = port->cap->revision; + + return sprintf(buf, "%d.%d\n", (rev >> 8) & 0xff, (rev >> 4) & 0xf); +} +static DEVICE_ATTR_RO(usb_typec_revision); + +static ssize_t usb_power_delivery_revision_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct typec_port *p = to_typec_port(dev); + + return sprintf(buf, "%d\n", (p->cap->pd_revision >> 8) & 0xff); +} +static DEVICE_ATTR_RO(usb_power_delivery_revision); + +static struct attribute *typec_attrs[] = { + &dev_attr_data_role.attr, + &dev_attr_power_operation_mode.attr, + &dev_attr_power_role.attr, + &dev_attr_preferred_role.attr, + &dev_attr_supported_accessory_modes.attr, + &dev_attr_usb_power_delivery_revision.attr, + &dev_attr_usb_typec_revision.attr, + &dev_attr_vconn_source.attr, + NULL, +}; +ATTRIBUTE_GROUPS(typec); + +static int typec_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + int ret; + + ret = add_uevent_var(env, "TYPEC_PORT=%s", dev_name(dev)); + if (ret) + dev_err(dev, "failed to add uevent TYPEC_PORT\n"); + + return ret; +} + +static void typec_release(struct device *dev) +{ + struct typec_port *port = to_typec_port(dev); + + ida_simple_remove(&typec_index_ida, port->id); + kfree(port); +} + +static const struct device_type typec_port_dev_type = { + .name = "typec_port", + .groups = typec_groups, + .uevent = typec_uevent, + .release = typec_release, +}; + +static enum typec_port_type typec_get_port_type_from_string(const char *str) +{ + int ret; + + ret = match_string(typec_port_types, ARRAY_SIZE(typec_port_types), str); + return (ret < 0) ? TYPEC_PORT_TYPE_UNKNOWN : ret; +} + +enum typec_port_type typec_get_port_type(struct device *dev) +{ + const char *port_type; + int err; + + err = device_property_read_string(dev, "port-type", &port_type); + if (err < 0) + return TYPEC_PORT_TYPE_UNKNOWN; + + return typec_get_port_type_from_string(port_type); +} +EXPORT_SYMBOL_GPL(typec_get_port_type); + +static enum typec_role typec_get_power_role_from_string(const char *str) +{ + int ret; + + ret = match_string(typec_roles, ARRAY_SIZE(typec_roles), str); + return (ret < 0) ? TYPEC_ROLE_UNKNOWN : ret; +} + +enum typec_role typec_get_power_role(struct device *dev) +{ + const char *power_role; + int err; + + err = device_property_read_string(dev, "default-role", &power_role); + if (err < 0) + return TYPEC_ROLE_UNKNOWN; + + return typec_get_power_role_from_string(power_role); +} +EXPORT_SYMBOL_GPL(typec_get_power_role); +/* --------------------------------------- */ +/* Driver callbacks to report role updates */ + +/** + * typec_set_data_role - Report data role change + * @port: The USB Type-C Port where the role was changed + * @role: The new data role + * + * This routine is used by the port drivers to report data role changes. + */ +void typec_set_data_role(struct typec_port *port, enum typec_data_role role) +{ + if (port->data_role == role) + return; + + port->data_role = role; + sysfs_notify(&port->dev.kobj, NULL, "data_role"); + kobject_uevent(&port->dev.kobj, KOBJ_CHANGE); +} +EXPORT_SYMBOL_GPL(typec_set_data_role); + +/** + * typec_set_pwr_role - Report power role change + * @port: The USB Type-C Port where the role was changed + * @role: The new data role + * + * This routine is used by the port drivers to report power role changes. + */ +void typec_set_pwr_role(struct typec_port *port, enum typec_role role) +{ + if (port->pwr_role == role) + return; + + port->pwr_role = role; + sysfs_notify(&port->dev.kobj, NULL, "power_role"); + kobject_uevent(&port->dev.kobj, KOBJ_CHANGE); +} +EXPORT_SYMBOL_GPL(typec_set_pwr_role); + +/** + * typec_set_pwr_role - Report VCONN source change + * @port: The USB Type-C Port which VCONN role changed + * @role: Source when @port is sourcing VCONN, or Sink when it's not + * + * This routine is used by the port drivers to report if the VCONN source is + * changes. + */ +void typec_set_vconn_role(struct typec_port *port, enum typec_role role) +{ + if (port->vconn_role == role) + return; + + port->vconn_role = role; + sysfs_notify(&port->dev.kobj, NULL, "vconn_source"); + kobject_uevent(&port->dev.kobj, KOBJ_CHANGE); +} +EXPORT_SYMBOL_GPL(typec_set_vconn_role); + +/** + * typec_set_pwr_opmode - Report changed power operation mode + * @port: The USB Type-C Port where the mode was changed + * @opmode: New power operation mode + * + * This routine is used by the port drivers to report changed power operation + * mode in @port. The modes are USB (default), 1.5A, 3.0A as defined in USB + * Type-C specification, and "USB Power Delivery" when the power levels are + * negotiated with methods defined in USB Power Delivery specification. + */ +void typec_set_pwr_opmode(struct typec_port *port, + enum typec_pwr_opmode opmode) +{ + if (port->pwr_opmode == opmode) + return; + + port->pwr_opmode = opmode; + sysfs_notify(&port->dev.kobj, NULL, "power_operation_mode"); + kobject_uevent(&port->dev.kobj, KOBJ_CHANGE); +} +EXPORT_SYMBOL_GPL(typec_set_pwr_opmode); + +/* --------------------------------------- */ + +/** + * typec_port_register_altmode - Register USB Type-C Port Alternate Mode + * @port: USB Type-C Port that supports the alternate mode + * @desc: Description of the alternate mode + * + * This routine is used to register an alternate mode that @port is capable of + * supporting. + * + * Returns handle to the alternate mode on success or NULL on failure. + */ +struct typec_altmode * +typec_port_register_altmode(struct typec_port *port, + struct typec_altmode_desc *desc) +{ + return typec_register_altmode(&port->dev, desc); +} +EXPORT_SYMBOL_GPL(typec_port_register_altmode); + +/** + * typec_register_port - Register a USB Type-C Port + * @parent: Parent device + * @cap: Description of the port + * + * Registers a device for USB Type-C Port described in @cap. + * + * Returns handle to the port on success or NULL on failure. + */ +struct typec_port *typec_register_port(struct device *parent, + const struct typec_capability *cap) +{ + struct typec_port *port; + int role; + int ret; + int id; + + port = kzalloc(sizeof(*port), GFP_KERNEL); + if (!port) + return NULL; + + id = ida_simple_get(&typec_index_ida, 0, 0, GFP_KERNEL); + if (id < 0) { + kfree(port); + return NULL; + } + + if (cap->type == TYPEC_PORT_DFP) + role = TYPEC_SOURCE; + else if (cap->type == TYPEC_PORT_UFP) + role = TYPEC_SINK; + else + role = cap->prefer_role; + + if (role == TYPEC_SOURCE) { + port->data_role = TYPEC_HOST; + port->pwr_role = TYPEC_SOURCE; + port->vconn_role = TYPEC_SOURCE; + } else { + port->data_role = TYPEC_DEVICE; + port->pwr_role = TYPEC_SINK; + port->vconn_role = TYPEC_SINK; + } + + port->id = id; + port->cap = cap; + port->prefer_role = cap->prefer_role; + + port->dev.class = typec_class; + port->dev.parent = parent; + port->dev.fwnode = cap->fwnode; + port->dev.type = &typec_port_dev_type; + dev_set_name(&port->dev, "port%d", id); + + ret = device_register(&port->dev); + if (ret) { + dev_err(parent, "failed to register port (%d)\n", ret); + put_device(&port->dev); + return NULL; + } + + return port; +} +EXPORT_SYMBOL_GPL(typec_register_port); + +/** + * typec_unregister_port - Unregister a USB Type-C Port + * @port: The port to be unregistered + * + * Unregister device created with typec_register_port(). + */ +void typec_unregister_port(struct typec_port *port) +{ + if (port) + device_unregister(&port->dev); +} +EXPORT_SYMBOL_GPL(typec_unregister_port); + +static int __init typec_init(void) +{ + typec_class = class_create(THIS_MODULE, "typec"); + return PTR_ERR_OR_ZERO(typec_class); +} +subsys_initcall(typec_init); + +static void __exit typec_exit(void) +{ + class_destroy(typec_class); + ida_destroy(&typec_index_ida); +} +module_exit(typec_exit); + +MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@linux.intel.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("USB Type-C Connector Class"); |