diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig index c483f28b695d..14e6db3acc3f 100644 --- a/drivers/usb/dwc3/Kconfig +++ b/drivers/usb/dwc3/Kconfig @@ -159,4 +159,13 @@ config USB_DWC3_XILINX This driver handles both ZynqMP and Versal SoC operations. Say 'Y' or 'M' if you have one such device. +config USB_DWC3_MSM + tristate "QTI MSM Platforms" + depends on ARCH_QCOM || COMPILE_TEST + help + Applicable to QTI MSM Platforms with DesignWare Core + USB3 IP. + Driver supports host, device and dual-role modes of operation. + Say 'Y' or 'M' if you have one such device. + endif diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile index 2d499de6f66a..b38de36640c3 100644 --- a/drivers/usb/dwc3/Makefile +++ b/drivers/usb/dwc3/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 # define_trace.h needs to know how to find our header CFLAGS_trace.o := -I$(src) +CFLAGS_dwc3-msm-core.o := -I$(srctree)/drivers/usb/host obj-$(CONFIG_USB_DWC3) += dwc3.o @@ -53,3 +54,8 @@ obj-$(CONFIG_USB_DWC3_ST) += dwc3-st.o obj-$(CONFIG_USB_DWC3_QCOM) += dwc3-qcom.o obj-$(CONFIG_USB_DWC3_IMX8MP) += dwc3-imx8mp.o obj-$(CONFIG_USB_DWC3_XILINX) += dwc3-xilinx.o +obj-$(CONFIG_USB_DWC3_MSM) += dwc3-msm.o +dwc3-msm-y := dwc3-msm-core.o debug_ipc.o +ifneq ($(CONFIG_ARM64),) +dwc3-msm-y += dwc3-msm-ops.o +endif diff --git a/drivers/usb/dwc3/debug-ipc.h b/drivers/usb/dwc3/debug-ipc.h new file mode 100644 index 000000000000..63899d91d3ba --- /dev/null +++ b/drivers/usb/dwc3/debug-ipc.h @@ -0,0 +1,107 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2015-2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef __DWC3_DEBUG_IPC_H +#define __DWC3_DEBUG_IPC_H + +#include "core.h" +#include "debug.h" +#include + +/* + * NOTE: Make sure to have mdwc as local variable in function before using + * below macros. + */ +#define dbg_event(ep_num, name, status) \ + dwc3_dbg_print(mdwc->dwc_ipc_log_ctxt, ep_num, name, status, "") + +#define dbg_print(ep_num, name, status, extra) \ + dwc3_dbg_print(mdwc->dwc_ipc_log_ctxt, ep_num, name, status, extra) + +#define dbg_print_reg(name, reg) \ + dwc3_dbg_print_reg(mdwc->dwc_ipc_log_ctxt, name, reg) + +#define dbg_done(ep_num, count, status) \ + dwc3_dbg_done(mdwc->dwc_ipc_log_ctxt, ep_num, count, status) + +#define dbg_queue(ep_num, req, status) \ + dwc3_dbg_queue(mdwc->dwc_ipc_log_ctxt, ep_num, req, status) + +#define dbg_setup(ep_num, req) \ + dwc3_dbg_setup(mdwc->dwc_ipc_log_ctxt, ep_num, req) + +#define dbg_ep_queue(ep_num, req) \ + dwc3_dbg_dma_queue(mdwc->dwc_ipc_log_ctxt, ep_num, req) + +#define dbg_ep_dequeue(ep_num, req) \ + dwc3_dbg_dma_dequeue(mdwc->dwc_ipc_log_ctxt, ep_num, req) + +#define dbg_ep_unmap(ep_num, req) \ + dwc3_dbg_dma_unmap(mdwc->dwc_dma_ipc_log_ctxt, ep_num, req) + +#define dbg_ep_map(ep_num, req) \ + dwc3_dbg_dma_map(mdwc->dwc_dma_ipc_log_ctxt, ep_num, req) + +#define dbg_log_string(fmt, ...) \ + ipc_log_string(mdwc->dwc_ipc_log_ctxt,\ + "%s: " fmt, __func__, ##__VA_ARGS__) + +#define dbg_trace_ctrl_req(ctrl) \ + dwc3_dbg_trace_log_ctrl(dwc_trace_ipc_log_ctxt, ctrl) + +#define dbg_trace_ep_queue(req) \ + dwc3_dbg_trace_log_request(dwc_trace_ipc_log_ctxt, req, "dbg_ep_queue") + +#define dbg_trace_ep_dequeue(req) \ + dwc3_dbg_trace_log_request(dwc_trace_ipc_log_ctxt, req, "dbg_ep_dequeue") + +#define dbg_trace_gadget_giveback(req) \ + dwc3_dbg_trace_log_request(dwc_trace_ipc_log_ctxt, req, "dbg_gadget_giveback") + +#define dbg_trace_gadget_ep_cmd(dep, cmd, params, cmd_status) \ + dwc3_dbg_trace_ep_cmd(dwc_trace_ipc_log_ctxt, dep, cmd, params, cmd_status) + +#define dbg_trace_trb_prepare(dep, event) \ + dwc3_dbg_trace_trb_complete(dwc_trace_ipc_log_ctxt, dep, trb, "dbg_prepare") + +#define dbg_trace_trb_complete(dep, event) \ + dwc3_dbg_trace_trb_complete(dwc_trace_ipc_log_ctxt, dep, trb, "dbg_complete") + +#define dbg_trace_event(event, dwc) \ + dwc3_dbg_trace_event(dwc_trace_ipc_log_ctxt, event, dwc) + +void dwc3_dbg_trace_log_ctrl(void *log_ctxt, struct usb_ctrlrequest *ctrl); +void dwc3_dbg_trace_log_request(void *log_ctxt, struct dwc3_request *req, + char *tag); +void dwc3_dbg_trace_ep_cmd(void *log_ctxt, struct dwc3_ep *dep, + unsigned int cmd, + struct dwc3_gadget_ep_cmd_params *params, + int cmd_status); +void dwc3_dbg_trace_trb_complete(void *log_ctxt, struct dwc3_ep *dep, + struct dwc3_trb *trb, char *tag); +void dwc3_dbg_trace_event(void *log_ctxt, u32 event, struct dwc3 *dwc); +void dwc3_dbg_print(void *log_ctxt, u8 ep_num, + const char *name, int status, const char *extra); +void dwc3_dbg_done(void *log_ctxt, u8 ep_num, + const u32 count, int status); +void dwc3_dbg_event(void *log_ctxt, u8 ep_num, + const char *name, int status); +void dwc3_dbg_queue(void *log_ctxt, u8 ep_num, + const struct usb_request *req, int status); +void dwc3_dbg_setup(void *log_ctxt, u8 ep_num, + const struct usb_ctrlrequest *req); +void dwc3_dbg_print_reg(void *log_ctxt, + const char *name, int reg); +void dwc3_dbg_dma_queue(void *log_ctxt, u8 ep_num, + struct dwc3_request *req); +void dwc3_dbg_dma_dequeue(void *log_ctxt, u8 ep_num, + struct dwc3_request *req); +void dwc3_dbg_dma_map(void *log_ctxt, u8 ep_num, + struct dwc3_request *req); +void dwc3_dbg_dma_unmap(void *log_ctxt, u8 ep_num, + struct dwc3_request *req); + +#endif /* __DWC3_DEBUG_IPC_H */ diff --git a/drivers/usb/dwc3/debug_ipc.c b/drivers/usb/dwc3/debug_ipc.c new file mode 100644 index 000000000000..7cecbe368747 --- /dev/null +++ b/drivers/usb/dwc3/debug_ipc.c @@ -0,0 +1,283 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2018-2021 The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include "debug-ipc.h" + +#include + +static unsigned int ep_addr_rxdbg_mask = 1; +module_param(ep_addr_rxdbg_mask, uint, 0644); +static unsigned int ep_addr_txdbg_mask = 1; +module_param(ep_addr_txdbg_mask, uint, 0644); + +static int allow_dbg_print(u8 ep_num) +{ + int dir, num; + + /* allow bus wide events */ + if (ep_num == 0xff) + return 1; + + dir = ep_num & 0x1; + num = ep_num >> 1; + num = 1 << num; + + if (dir && (num & ep_addr_txdbg_mask)) + return 1; + if (!dir && (num & ep_addr_rxdbg_mask)) + return 1; + + return 0; +} + +void dwc3_dbg_trace_log_ctrl(void *log_ctxt, struct usb_ctrlrequest *ctrl) +{ + char *ctrl_req_str; + + if (ctrl == NULL) + return; + + ctrl_req_str = kzalloc(DWC3_MSG_MAX, GFP_ATOMIC); + if (!ctrl_req_str) + return; + + usb_decode_ctrl(ctrl_req_str, DWC3_MSG_MAX, ctrl->bRequestType, + ctrl->bRequest, le16_to_cpu(ctrl->wValue), + le16_to_cpu(ctrl->wIndex), + le16_to_cpu(ctrl->wLength)); + ipc_log_string(log_ctxt, "dbg_trace_log_ctrl: %s", ctrl_req_str); + kfree(ctrl_req_str); +} + +void dwc3_dbg_trace_log_request(void *log_ctxt, struct dwc3_request *req, + char *tag) +{ + struct dwc3_ep *dep; + + if (req == NULL) + return; + + dep = req->dep; + ipc_log_string(log_ctxt, "%s: %s: req %p length %u/%u %s%s%s ==> %d", + tag, dep->name, req, req->request.actual, + req->request.length, + req->request.zero ? "Z" : "z", + req->request.short_not_ok ? "S" : "s", + req->request.no_interrupt ? "i" : "I", + req->request.status); +} + +void dwc3_dbg_trace_ep_cmd(void *log_ctxt, struct dwc3_ep *dep, + unsigned int cmd, + struct dwc3_gadget_ep_cmd_params *params, + int cmd_status) +{ + ipc_log_string(log_ctxt, + "dbg_send_ep_cmd: %s: cmd '%s' [%x] params %08x %08x %08x --> status: %s", + dep->name, dwc3_gadget_ep_cmd_string(cmd), cmd, params->param0, + params->param1, params->param2, dwc3_ep_cmd_status_string(cmd_status)); +} + +void dwc3_dbg_trace_trb_complete(void *log_ctxt, struct dwc3_ep *dep, + struct dwc3_trb *trb, char *tag) +{ + char *s; + int pcm = ((trb->size >> 24) & 3) + 1; + + switch (usb_endpoint_type(dep->endpoint.desc)) { + case USB_ENDPOINT_XFER_INT: + case USB_ENDPOINT_XFER_ISOC: + switch (pcm) { + case 1: + s = "1x "; + break; + case 2: + s = "2x "; + break; + case 3: + default: + s = "3x "; + break; + } + break; + default: + s = ""; + } + + ipc_log_string(log_ctxt, + "%s: %s: trb %p (E%d:D%d) buf %08x%08x sz %s%d ctrl %08x (%c%c%c%c:%c%c:%s)", + tag, dep->name, trb, dep->trb_enqueue, + dep->trb_dequeue, trb->bph, trb->bpl, s, trb->size, trb->ctrl, + trb->ctrl & DWC3_TRB_CTRL_HWO ? 'H' : 'h', + trb->ctrl & DWC3_TRB_CTRL_LST ? 'L' : 'l', + trb->ctrl & DWC3_TRB_CTRL_CHN ? 'C' : 'c', + trb->ctrl & DWC3_TRB_CTRL_CSP ? 'S' : 's', + trb->ctrl & DWC3_TRB_CTRL_ISP_IMI ? 'S' : 's', + trb->ctrl & DWC3_TRB_CTRL_IOC ? 'C' : 'c', + dwc3_trb_type_string(DWC3_TRBCTL_TYPE(trb->ctrl))); +} + +void dwc3_dbg_trace_event(void *log_ctxt, u32 event, struct dwc3 *dwc) +{ + char *event_str; + + event_str = kzalloc(DWC3_MSG_MAX, GFP_ATOMIC); + if (!event_str) + return; + + ipc_log_string(log_ctxt, "event (%08x): %s", event, + dwc3_decode_event(event_str, DWC3_MSG_MAX, + event, dwc->ep0state)); + kfree(event_str); +} + +/** + * dwc3_dbg_print: prints the common part of the event + * @addr: endpoint address + * @name: event name + * @status: status + * @extra: extra information + * @dwc3: pointer to struct dwc3 + */ +void dwc3_dbg_print(void *log_ctxt, u8 ep_num, const char *name, + int status, const char *extra) +{ + if (!allow_dbg_print(ep_num)) + return; + + if (name == NULL) + return; + + ipc_log_string(log_ctxt, "%02X %-25.25s %4i ?\t%s", + ep_num, name, status, extra); +} + +/** + * dwc3_dbg_done: prints a DONE event + * @addr: endpoint address + * @td: transfer descriptor + * @status: status + * @dwc3: pointer to struct dwc3 + */ +void dwc3_dbg_done(void *log_ctxt, u8 ep_num, + const u32 count, int status) +{ + if (!allow_dbg_print(ep_num)) + return; + + ipc_log_string(log_ctxt, "%02X %-25.25s %4i ?\t%d", + ep_num, "DONE", status, count); +} + +/** + * dwc3_dbg_event: prints a generic event + * @addr: endpoint address + * @name: event name + * @status: status + */ +void dwc3_dbg_event(void *log_ctxt, u8 ep_num, const char *name, int status) +{ + if (!allow_dbg_print(ep_num)) + return; + + if (name != NULL) + dwc3_dbg_print(log_ctxt, ep_num, name, status, ""); +} + +/* + * dwc3_dbg_queue: prints a QUEUE event + * @addr: endpoint address + * @req: USB request + * @status: status + */ +void dwc3_dbg_queue(void *log_ctxt, u8 ep_num, + const struct usb_request *req, int status) +{ + if (!allow_dbg_print(ep_num)) + return; + + if (req != NULL) { + ipc_log_string(log_ctxt, + "%02X %-25.25s %4i ?\t%d %d", ep_num, "QUEUE", status, + !req->no_interrupt, req->length); + } +} + +/** + * dwc3_dbg_setup: prints a SETUP event + * @addr: endpoint address + * @req: setup request + */ +void dwc3_dbg_setup(void *log_ctxt, u8 ep_num, + const struct usb_ctrlrequest *req) +{ + if (!allow_dbg_print(ep_num)) + return; + + if (req != NULL) { + ipc_log_string(log_ctxt, + "%02X %-25.25s ?\t%02X %02X %04X %04X %d", + ep_num, "SETUP", req->bRequestType, + req->bRequest, le16_to_cpu(req->wValue), + le16_to_cpu(req->wIndex), le16_to_cpu(req->wLength)); + } +} + +/** + * dwc3_dbg_print_reg: prints a reg value + * @name: reg name + * @reg: reg value to be printed + */ +void dwc3_dbg_print_reg(void *log_ctxt, const char *name, int reg) +{ + if (name == NULL) + return; + + ipc_log_string(log_ctxt, "%s = 0x%08x", name, reg); +} + +void dwc3_dbg_dma_unmap(void *log_ctxt, u8 ep_num, struct dwc3_request *req) +{ + if (ep_num < 2) + return; + ipc_log_string(log_ctxt, + "%02X-%-3.3s %-25.25s 0x%pK %pad %u %pad %s", ep_num >> 1, + ep_num & 1 ? "IN":"OUT", "UNMAP", &req->request, + &req->request.dma, req->request.length, &req->trb_dma, + req->trb->ctrl & DWC3_TRB_CTRL_HWO ? "HWO" : ""); +} + +void dwc3_dbg_dma_map(void *log_ctxt, u8 ep_num, struct dwc3_request *req) +{ + if (ep_num < 2) + return; + + ipc_log_string(log_ctxt, + "%02X-%-3.3s %-25.25s 0x%pK %pad %u %pad", ep_num >> 1, + ep_num & 1 ? "IN":"OUT", "MAP", &req->request, + &req->request.dma, req->request.length, &req->trb_dma); +} + +void dwc3_dbg_dma_dequeue(void *log_ctxt, u8 ep_num, struct dwc3_request *req) +{ + if (ep_num < 2) + return; + + ipc_log_string(log_ctxt, + "%02X-%-3.3s %-25.25s 0x%pK %pad %pad", ep_num >> 1, + ep_num & 1 ? "IN":"OUT", "DEQUEUE", &req->request, + &req->request.dma, &req->trb_dma); +} + +void dwc3_dbg_dma_queue(void *log_ctxt, u8 ep_num, struct dwc3_request *req) +{ + if (ep_num < 2) + return; + + ipc_log_string(log_ctxt, + "%02X-%-3.3s %-25.25s 0x%pK", ep_num >> 1, + ep_num & 1 ? "IN":"OUT", "QUEUE", &req->request); +} diff --git a/drivers/usb/dwc3/dwc3-msm-core.c b/drivers/usb/dwc3/dwc3-msm-core.c new file mode 100644 index 000000000000..0b6213b68a1b --- /dev/null +++ b/drivers/usb/dwc3/dwc3-msm-core.c @@ -0,0 +1,6647 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2012-2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "gadget.h" +#include "debug.h" +#include "xhci.h" +#include "debug-ipc.h" + +#define NUM_LOG_PAGES 12 + +/* time out to wait for USB cable status notification (in ms)*/ +#define SM_INIT_TIMEOUT 30000 + +#define DWC3_GUCTL1_IP_GAP_ADD_ON(n) ((n) << 21) +#define DWC3_GUCTL1_IP_GAP_ADD_ON_MASK DWC3_GUCTL1_IP_GAP_ADD_ON(7) +#define DWC3_GUCTL1_L1_SUSP_THRLD_EN_FOR_HOST BIT(8) +#define DWC3_GUCTL3_USB20_RETRY_DISABLE BIT(16) +#define DWC3_GUSB3PIPECTL_DISRXDETU3 BIT(22) + +#define DWC3_LLUCTL 0xd024 +/* Force Gen1 speed on Gen2 link */ +#define DWC3_LLUCTL_FORCE_GEN1 BIT(10) + +#define DWC31_LINK_GDBGLTSSM 0xd050 +#define DWC3_GDBGLTSSM_LINKSTATE_MASK (0xF << 22) + +#define DWC31_LINK_LU3LFPSRXTIM(n) (0xd010 + ((n) * 0x80)) +#define GEN2_U3_EXIT_RSP_RX_CLK(n) ((n) << 16) +#define GEN2_U3_EXIT_RSP_RX_CLK_MASK GEN2_U3_EXIT_RSP_RX_CLK(0xff) +#define GEN1_U3_EXIT_RSP_RX_CLK(n) (n) +#define GEN1_U3_EXIT_RSP_RX_CLK_MASK GEN1_U3_EXIT_RSP_RX_CLK(0xff) + +/* AHB2PHY register offsets */ +#define PERIPH_SS_AHB2PHY_TOP_CFG 0x10 + +/* AHB2PHY read/write waite value */ +#define ONE_READ_WRITE_WAIT 0x11 + +/* XHCI registers */ +#define USB3_HCSPARAMS1 (0x4) +#define USB3_PORTSC (0x420) + +/** + * USB QSCRATCH Hardware registers + * + */ +#define QSCRATCH_REG_OFFSET (0x000F8800) +#define QSCRATCH_GENERAL_CFG (QSCRATCH_REG_OFFSET + 0x08) +#define CGCTL_REG (QSCRATCH_REG_OFFSET + 0x28) +#define PWR_EVNT_IRQ_STAT_REG (QSCRATCH_REG_OFFSET + 0x58) +#define PWR_EVNT_IRQ_MASK_REG (QSCRATCH_REG_OFFSET + 0x5C) +#define EXTRA_INP_REG (QSCRATCH_REG_OFFSET + 0x1e4) + +#define PWR_EVNT_POWERDOWN_IN_P3_MASK BIT(2) +#define PWR_EVNT_POWERDOWN_OUT_P3_MASK BIT(3) +#define PWR_EVNT_LPM_IN_L2_MASK BIT(4) +#define PWR_EVNT_LPM_OUT_L2_MASK BIT(5) +#define PWR_EVNT_LPM_OUT_RX_ELECIDLE_IRQ_MASK BIT(12) +#define PWR_EVNT_LPM_OUT_L1_MASK BIT(13) + +#define EXTRA_INP_SS_DISABLE BIT(5) + +/* QSCRATCH_GENERAL_CFG register bit offset */ +#define PIPE_UTMI_CLK_SEL BIT(0) +#define PIPE3_PHYSTATUS_SW BIT(3) +#define PIPE_UTMI_CLK_DIS BIT(8) + +#define HS_PHY_CTRL_REG (QSCRATCH_REG_OFFSET + 0x10) +#define UTMI_OTG_VBUS_VALID BIT(20) +#define SW_SESSVLD_SEL BIT(28) + +#define SS_PHY_CTRL_REG (QSCRATCH_REG_OFFSET + 0x30) +#define LANE0_PWR_PRESENT BIT(24) + +/* USB DBM Hardware registers */ +#define DBM_REG_OFFSET 0xF8000 + +/* DBM_GEN_CFG */ +#define DBM_EN_USB3 0x00000001 + +/* DBM_EP_CFG */ +#define DBM_EN_EP 0x00000001 +#define USB3_EPNUM 0x0000003E +#define DBM_BAM_PIPE_NUM 0x000000C0 +#define DBM_PRODUCER 0x00000100 +#define DBM_DISABLE_WB 0x00000200 +#define DBM_INT_RAM_ACC 0x00000400 + +/* DBM_DATA_FIFO_SIZE */ +#define DBM_DATA_FIFO_SIZE_MASK 0x0000ffff + +/* DBM_GEVNTSIZ */ +#define DBM_GEVNTSIZ_MASK 0x0000ffff + +/* DBM_DBG_CNFG */ +#define DBM_ENABLE_IOC_MASK 0x0000000f + +/* DBM_SOFT_RESET */ +#define DBM_SFT_RST_EP0 0x00000001 +#define DBM_SFT_RST_EP1 0x00000002 +#define DBM_SFT_RST_EP2 0x00000004 +#define DBM_SFT_RST_EP3 0x00000008 +#define DBM_SFT_RST_EPS_MASK 0x0000000F +#define DBM_SFT_RST_MASK 0x80000000 +#define DBM_EN_MASK 0x00000002 + +/* DBM TRB configurations */ +#define DBM_TRB_BIT 0x80000000 +#define DBM_TRB_DATA_SRC 0x40000000 +#define DBM_TRB_DMA 0x20000000 +#define DBM_TRB_EP_NUM(ep) (ep<<24) + +/* GSI related registers */ +#define GSI_TRB_ADDR_BIT_53_MASK (1 << 21) +#define GSI_TRB_ADDR_BIT_55_MASK (1 << 23) + +#define GSI_GENERAL_CFG_REG(reg) (QSCRATCH_REG_OFFSET + \ + reg[GENERAL_CFG_REG]) +#define GSI_RESTART_DBL_PNTR_MASK BIT(20) +#define GSI_CLK_EN_MASK BIT(12) +#define BLOCK_GSI_WR_GO_MASK BIT(1) +#define GSI_EN_MASK BIT(0) + +#define GSI_DBL_ADDR_L(reg, n) (QSCRATCH_REG_OFFSET + \ + reg[DBL_ADDR_L] + (n*4)) +#define GSI_DBL_ADDR_H(reg, n) (QSCRATCH_REG_OFFSET + \ + reg[DBL_ADDR_H] + (n*4)) +#define GSI_RING_BASE_ADDR_L(reg, n) (QSCRATCH_REG_OFFSET + \ + reg[RING_BASE_ADDR_L] + (n*4)) +#define GSI_RING_BASE_ADDR_H(reg, n) (QSCRATCH_REG_OFFSET + \ + reg[RING_BASE_ADDR_H] + (n*4)) + +#define GSI_IF_STS(reg) (QSCRATCH_REG_OFFSET + reg[IF_STS]) +#define GSI_WR_CTRL_STATE_MASK BIT(15) + +#define DWC3_GEVNTCOUNT_EVNTINTRPTMASK (1 << 31) +#define DWC3_GEVNTADRHI_EVNTADRHI_GSI_EN(n) (n << 22) +#define DWC3_GEVNTADRHI_EVNTADRHI_GSI_IDX(n) (n << 16) +#define DWC3_GEVENT_TYPE_GSI 0x3 + +/* BAM pipe mask */ +#define MSM_PIPE_ID_MASK (0x1F) + +/* EBC/LPC Configuration */ +#define LPC_SCAN_MASK (QSCRATCH_REG_OFFSET + 0x200) +#define LPC_REG (QSCRATCH_REG_OFFSET + 0x204) + +#define LPC_SPEED_INDICATOR BIT(0) +#define LPC_SSP_MODE BIT(1) +#define LPC_BUS_CLK_EN BIT(12) + +#define USB30_MODE_SEL_REG (QSCRATCH_REG_OFFSET + 0x210) +#define USB30_QDSS_MODE_SEL BIT(0) + +#define USB30_QDSS_CONFIG_REG (QSCRATCH_REG_OFFSET + 0x214) + +#define DWC3_DEPCFG_EBC_MODE BIT(15) + +#define DWC3_DEPCFG_RETRY BIT(15) +#define DWC3_DEPCFG_TRB_WB BIT(14) + +enum dbm_reg { + DBM_EP_CFG, + DBM_DATA_FIFO, + DBM_DATA_FIFO_SIZE, + DBM_DATA_FIFO_EN, + DBM_GEVNTADR, + DBM_GEVNTSIZ, + DBM_DBG_CNFG, + DBM_HW_TRB0_EP, + DBM_HW_TRB1_EP, + DBM_HW_TRB2_EP, + DBM_HW_TRB3_EP, + DBM_PIPE_CFG, + DBM_DISABLE_UPDXFER, + DBM_SOFT_RESET, + DBM_GEN_CFG, + DBM_GEVNTADR_LSB, + DBM_GEVNTADR_MSB, + DBM_DATA_FIFO_LSB, + DBM_DATA_FIFO_MSB, + DBM_DATA_FIFO_ADDR_EN, + DBM_DATA_FIFO_SIZE_EN, +}; + +struct dbm_reg_data { + u32 offset; + unsigned int ep_mult; +}; + +#define DBM_1_4_NUM_EP 4 +#define DBM_1_5_NUM_EP 8 + +static const struct dbm_reg_data dbm_1_4_regtable[] = { + [DBM_EP_CFG] = { 0x0000, 0x4 }, + [DBM_DATA_FIFO] = { 0x0010, 0x4 }, + [DBM_DATA_FIFO_SIZE] = { 0x0020, 0x4 }, + [DBM_DATA_FIFO_EN] = { 0x0030, 0x0 }, + [DBM_GEVNTADR] = { 0x0034, 0x0 }, + [DBM_GEVNTSIZ] = { 0x0038, 0x0 }, + [DBM_DBG_CNFG] = { 0x003C, 0x0 }, + [DBM_HW_TRB0_EP] = { 0x0040, 0x4 }, + [DBM_HW_TRB1_EP] = { 0x0050, 0x4 }, + [DBM_HW_TRB2_EP] = { 0x0060, 0x4 }, + [DBM_HW_TRB3_EP] = { 0x0070, 0x4 }, + [DBM_PIPE_CFG] = { 0x0080, 0x0 }, + [DBM_SOFT_RESET] = { 0x0084, 0x0 }, + [DBM_GEN_CFG] = { 0x0088, 0x0 }, + [DBM_GEVNTADR_LSB] = { 0x0098, 0x0 }, + [DBM_GEVNTADR_MSB] = { 0x009C, 0x0 }, + [DBM_DATA_FIFO_LSB] = { 0x00A0, 0x8 }, + [DBM_DATA_FIFO_MSB] = { 0x00A4, 0x8 }, +}; + +static const struct dbm_reg_data dbm_1_5_regtable[] = { + [DBM_EP_CFG] = { 0x0000, 0x4 }, + [DBM_DATA_FIFO] = { 0x0280, 0x4 }, + [DBM_DATA_FIFO_SIZE] = { 0x0080, 0x4 }, + [DBM_DATA_FIFO_EN] = { 0x026C, 0x0 }, + [DBM_GEVNTADR] = { 0x0270, 0x0 }, + [DBM_GEVNTSIZ] = { 0x0268, 0x0 }, + [DBM_DBG_CNFG] = { 0x0208, 0x0 }, + [DBM_HW_TRB0_EP] = { 0x0220, 0x4 }, + [DBM_HW_TRB1_EP] = { 0x0230, 0x4 }, + [DBM_HW_TRB2_EP] = { 0x0240, 0x4 }, + [DBM_HW_TRB3_EP] = { 0x0250, 0x4 }, + [DBM_PIPE_CFG] = { 0x0274, 0x0 }, + [DBM_DISABLE_UPDXFER] = { 0x0298, 0x0 }, + [DBM_SOFT_RESET] = { 0x020C, 0x0 }, + [DBM_GEN_CFG] = { 0x0210, 0x0 }, + [DBM_GEVNTADR_LSB] = { 0x0260, 0x0 }, + [DBM_GEVNTADR_MSB] = { 0x0264, 0x0 }, + [DBM_DATA_FIFO_LSB] = { 0x0100, 0x8 }, + [DBM_DATA_FIFO_MSB] = { 0x0104, 0x8 }, + [DBM_DATA_FIFO_ADDR_EN] = { 0x0200, 0x0 }, + [DBM_DATA_FIFO_SIZE_EN] = { 0x0204, 0x0 }, +}; + +enum usb_gsi_reg { + GENERAL_CFG_REG, + DBL_ADDR_L, + DBL_ADDR_H, + RING_BASE_ADDR_L, + RING_BASE_ADDR_H, + IF_STS, + GSI_REG_MAX, +}; + +struct dwc3_hw_ep { + struct dwc3_ep *dep; + enum usb_hw_ep_mode mode; + struct dwc3_trb *ebc_trb_pool; + u8 dbm_ep_num; + int num_trbs; + + unsigned long flags; +#define DWC3_MSM_HW_EP_TRANSFER_STARTED BIT(0) +}; + +struct dwc3_msm_req_complete { + struct list_head list_item; + struct usb_request *req; + void (*orig_complete)(struct usb_ep *ep, + struct usb_request *req); +}; + +enum dwc3_drd_state { + DRD_STATE_UNDEFINED = 0, + + DRD_STATE_IDLE, + DRD_STATE_PERIPHERAL, + DRD_STATE_PERIPHERAL_SUSPEND, + + DRD_STATE_HOST, +}; + +static const char *const state_names[] = { + [DRD_STATE_UNDEFINED] = "undefined", + [DRD_STATE_IDLE] = "idle", + [DRD_STATE_PERIPHERAL] = "peripheral", + [DRD_STATE_PERIPHERAL_SUSPEND] = "peripheral_suspend", + [DRD_STATE_HOST] = "host", +}; + +static const char *const usb_dr_modes[] = { + [USB_DR_MODE_UNKNOWN] = "", + [USB_DR_MODE_HOST] = "host", + [USB_DR_MODE_PERIPHERAL] = "peripheral", + [USB_DR_MODE_OTG] = "otg", +}; + +static const char *dwc3_drd_state_string(enum dwc3_drd_state state) +{ + if (state < 0 || state >= ARRAY_SIZE(state_names)) + return "UNKNOWN"; + + return state_names[state]; +} + +enum dwc3_id_state { + DWC3_ID_GROUND = 0, + DWC3_ID_FLOAT, +}; + +enum msm_usb_irq { + HS_PHY_IRQ, + PWR_EVNT_IRQ, + DP_HS_PHY_IRQ, + DM_HS_PHY_IRQ, + SS_PHY_IRQ, + USB_MAX_IRQ +}; + +enum bus_vote { + BUS_VOTE_NONE, + BUS_VOTE_NOMINAL, + BUS_VOTE_SVS, + BUS_VOTE_MIN, + BUS_VOTE_MAX +}; + +static const char * const icc_path_names[] = { + "usb-ddr", "usb-ipa", "ddr-usb", +}; + +static const struct { + u32 avg, peak; +} bus_vote_values[BUS_VOTE_MAX][3] = { + /* usb_ddr avg/peak, usb_ipa avg/peak, apps_usb avg/peak */ + [BUS_VOTE_NONE] = { {0, 0}, {0, 0}, {0, 0} }, + [BUS_VOTE_NOMINAL] = { {1000000, 1250000}, {0, 2400}, {0, 40000}, }, + [BUS_VOTE_SVS] = { {240000, 700000}, {0, 2400}, {0, 40000}, }, + [BUS_VOTE_MIN] = { {1, 1}, {1, 1}, {1, 1}, }, +}; + +struct usb_irq_info { + const char *name; + unsigned long irq_type; + bool required; +}; + +static const struct usb_irq_info usb_irq_info[USB_MAX_IRQ] = { + { "hs_phy_irq", + IRQF_TRIGGER_HIGH | IRQF_ONESHOT | IRQ_TYPE_LEVEL_HIGH | + IRQF_EARLY_RESUME, + false, + }, + { "pwr_event_irq", + IRQF_TRIGGER_HIGH | IRQF_ONESHOT | IRQ_TYPE_LEVEL_HIGH | + IRQF_EARLY_RESUME, + true, + }, + { "dp_hs_phy_irq", + IRQF_TRIGGER_RISING | IRQF_ONESHOT | IRQF_EARLY_RESUME, + false, + }, + { "dm_hs_phy_irq", + IRQF_TRIGGER_RISING | IRQF_ONESHOT | IRQF_EARLY_RESUME, + false, + }, + { "ss_phy_irq", + IRQF_TRIGGER_HIGH | IRQF_ONESHOT | IRQ_TYPE_LEVEL_HIGH | + IRQF_EARLY_RESUME, + false, + }, +}; + +struct usb_irq { + int irq; + bool enable; +}; + +static const char * const gsi_op_strings[] = { + "EP_CONFIG", "START_XFER", "STORE_DBL_INFO", + "ENABLE_GSI", "UPDATE_XFER", "RING_DB", + "END_XFER", "GET_CH_INFO", "GET_XFER_IDX", "PREPARE_TRBS", + "FREE_TRBS", "SET_CLR_BLOCK_DBL", "CHECK_FOR_SUSP", + "EP_DISABLE" }; + +static const char * const usb_role_strings[] = { + "NONE", + "HOST", + "DEVICE" +}; + +static const char *const speed_names[] = { + [USB_SPEED_UNKNOWN] = "UNKNOWN", + [USB_SPEED_LOW] = "low-speed", + [USB_SPEED_FULL] = "full-speed", + [USB_SPEED_HIGH] = "high-speed", + [USB_SPEED_WIRELESS] = "wireless", + [USB_SPEED_SUPER] = "super-speed", + [USB_SPEED_SUPER_PLUS] = "super-speed-plus", +}; + +struct dwc3_msm; + +struct extcon_nb { + struct extcon_dev *edev; + struct dwc3_msm *mdwc; + int idx; + struct notifier_block vbus_nb; + struct notifier_block id_nb; +}; + +/* Input bits to state machine (mdwc->inputs) */ + +#define ID 0 +#define B_SESS_VLD 1 +#define B_SUSPEND 2 +#define WAIT_FOR_LPM 3 + +#define PM_QOS_SAMPLE_SEC 2 +#define PM_QOS_THRESHOLD 400 + +struct dwc3_msm { + struct device *dev; + void __iomem *base; + void __iomem *ahb2phy_base; + phys_addr_t reg_phys; + struct platform_device *dwc3; + struct dma_iommu_mapping *iommu_map; + const struct usb_ep_ops *original_ep_ops[DWC3_ENDPOINTS_NUM]; + struct list_head req_complete_list; + struct clk *xo_clk; + struct clk *core_clk; + long core_clk_rate; + long core_clk_rate_hs; + long core_clk_rate_disconnected; + struct clk *iface_clk; + struct clk *sleep_clk; + struct clk *utmi_clk; + unsigned int utmi_clk_rate; + struct clk *utmi_clk_src; + struct clk *bus_aggr_clk; + struct clk *noc_aggr_clk; + struct clk *cfg_ahb_clk; + struct reset_control *core_reset; + struct regulator *dwc3_gdsc; + + struct usb_phy *hs_phy, *ss_phy; + struct usb_redriver *redriver; + + const struct dbm_reg_data *dbm_reg_table; + int dbm_num_eps; + bool dbm_is_1p4; + + bool resume_pending; + atomic_t pm_suspended; + struct usb_irq wakeup_irq[USB_MAX_IRQ]; + int core_irq; + unsigned int irq_cnt; + struct work_struct resume_work; + struct work_struct restart_usb_work; + bool in_restart; + struct workqueue_struct *dwc3_wq; + struct workqueue_struct *sm_usb_wq; + struct delayed_work sm_work; + unsigned long inputs; + enum dwc3_drd_state drd_state; + enum usb_dr_mode dr_mode; + enum bus_vote default_bus_vote; + enum bus_vote override_bus_vote; + struct icc_path *icc_paths[3]; + bool in_host_mode; + bool in_device_mode; + enum usb_device_speed max_rh_port_speed; + bool perf_mode; + bool check_eud_state; + bool vbus_active; + bool eud_active; + bool suspend; + bool use_pdc_interrupts; + enum dwc3_id_state id_state; + bool use_pwr_event_for_wakeup; + bool host_poweroff_in_pm_suspend; + bool disable_host_ssphy_powerdown; + unsigned long lpm_flags; + unsigned int vbus_draw; +#define MDWC3_SS_PHY_SUSPEND BIT(0) +#define MDWC3_ASYNC_IRQ_WAKE_CAPABILITY BIT(1) +#define MDWC3_POWER_COLLAPSE BIT(2) + + struct extcon_nb *extcon; + int ext_idx; + struct notifier_block host_nb; + + u32 ip; + atomic_t in_p3; + atomic_t in_lpm; + unsigned int lpm_to_suspend_delay; + struct dev_pm_ops *dwc3_pm_ops; + struct dev_pm_ops *xhci_pm_ops; + + u32 num_gsi_event_buffers; + struct dwc3_event_buffer **gsi_ev_buff; + int pm_qos_latency; + struct pm_qos_request pm_qos_req_dma; + struct delayed_work perf_vote_work; + struct mutex suspend_resume_mutex; + struct mutex role_switch_mutex; + + enum usb_device_speed override_usb_speed; + enum usb_device_speed max_hw_supp_speed; + u32 *gsi_reg; + int gsi_reg_offset_cnt; + + struct notifier_block dpdm_nb; + struct regulator *dpdm_reg; + + u64 dummy_gsi_db; + dma_addr_t dummy_gsi_db_dma; + + struct usb_role_switch *role_switch; + struct usb_role_switch *dwc3_drd_sw; + bool ss_release_called; + int orientation_override; + +#define MAX_ERROR_RECOVERY_TRIES 3 + bool err_evt_seen; + int retries_on_error; + + void *dwc_ipc_log_ctxt; + void *dwc_dma_ipc_log_ctxt; + + struct dwc3_hw_ep hw_eps[DWC3_ENDPOINTS_NUM]; + phys_addr_t ebc_desc_addr; + bool dis_sending_cm_l1_quirk; + bool use_eusb2_phy; + bool force_gen1; +}; + +#define USB_HSPHY_3P3_VOL_MIN 3050000 /* uV */ +#define USB_HSPHY_3P3_VOL_MAX 3300000 /* uV */ +#define USB_HSPHY_3P3_HPM_LOAD 16000 /* uA */ + +#define USB_HSPHY_1P8_VOL_MIN 1800000 /* uV */ +#define USB_HSPHY_1P8_VOL_MAX 1800000 /* uV */ +#define USB_HSPHY_1P8_HPM_LOAD 19000 /* uA */ + +#define USB_SSPHY_1P8_VOL_MIN 1800000 /* uV */ +#define USB_SSPHY_1P8_VOL_MAX 1800000 /* uV */ +#define USB_SSPHY_1P8_HPM_LOAD 23000 /* uA */ + +/* unfortunately, dwc3 core doesn't manage multiple dwc3 instances for trace */ +void *dwc_trace_ipc_log_ctxt; + +static void dwc3_pwr_event_handler(struct dwc3_msm *mdwc); + +static inline void dwc3_msm_ep_writel(void __iomem *base, u32 offset, u32 value) +{ + writel_relaxed(value, base + offset - DWC3_GLOBALS_REGS_START); + + /* Ensure writes to DWC3 ep registers are completed */ + mb(); +} + +static inline u32 dwc3_msm_ep_readl(void __iomem *base, u32 offset) +{ + u32 value; + + /* + * We requested the mem region starting from the Globals address + * space, see dwc3_probe in core.c. + * However, the offsets are given starting from xHCI address space. + */ + value = readl_relaxed(base + offset - DWC3_GLOBALS_REGS_START); + return value; +} + +static inline dma_addr_t dwc3_trb_dma_offset(struct dwc3_ep *dep, + struct dwc3_trb *trb) +{ + u32 offset = (char *) trb - (char *) dep->trb_pool; + + return dep->trb_pool_dma + offset; +} + +static int dwc3_alloc_trb_pool(struct dwc3_ep *dep) +{ + struct dwc3 *dwc = dep->dwc; + + if (dep->trb_pool) + return 0; + + dep->trb_pool = dma_alloc_coherent(dwc->sysdev, + sizeof(struct dwc3_trb) * DWC3_TRB_NUM, + &dep->trb_pool_dma, GFP_KERNEL); + if (!dep->trb_pool) { + dev_err(dep->dwc->dev, "failed to allocate trb pool for %s\n", + dep->name); + return -ENOMEM; + } + + return 0; +} + +static void dwc3_free_trb_pool(struct dwc3_ep *dep) +{ + struct dwc3 *dwc = dep->dwc; + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + + /* + * Clean up ep ring to avoid getting xferInProgress due to stale trbs + * with HWO bit set from previous composition when update transfer cmd + * is issued. + */ + if (dep->number > 1 && dep->trb_pool && dep->trb_pool_dma) { + memset(&dep->trb_pool[0], 0, + sizeof(struct dwc3_trb) * DWC3_TRB_NUM); + dbg_event(dep->number, "Clr_TRB", 0); + dev_info(dwc->dev, "Clr_TRB ring of %s\n", dep->name); + + dma_free_coherent(dwc->sysdev, + sizeof(struct dwc3_trb) * DWC3_TRB_NUM, + dep->trb_pool, dep->trb_pool_dma); + dep->trb_pool = NULL; + dep->trb_pool_dma = 0; + } +} + +static enum usb_device_speed dwc3_msm_get_max_speed(struct dwc3_msm *mdwc) +{ + struct dwc3 *dwc; + + if (!mdwc->dwc3) + return mdwc->override_usb_speed; + + dwc = platform_get_drvdata(mdwc->dwc3); + return dwc->maximum_speed; +} + +static unsigned int dwc3_msm_set_max_speed(struct dwc3_msm *mdwc, enum usb_device_speed spd) +{ + struct dwc3 *dwc; + + if (spd == USB_SPEED_UNKNOWN || spd >= mdwc->max_hw_supp_speed) + spd = mdwc->max_hw_supp_speed; + + if (!mdwc->dwc3) { + mdwc->override_usb_speed = spd; + return 0; + } + + dwc = platform_get_drvdata(mdwc->dwc3); + dwc->maximum_speed = spd; + + return 0; +} + +static irqreturn_t dwc3_msm_core_irq(int irq, void *data) +{ + struct dwc3_msm *mdwc = data; + + mdwc->irq_cnt++; + return IRQ_NONE; +} + +/** + * + * Read register with debug info. + * + * @base - DWC3 base virtual address. + * @offset - register offset. + * + * @return u32 + */ +static inline u32 dwc3_msm_read_reg(void __iomem *base, u32 offset) +{ + u32 val = ioread32(base + offset); + return val; +} + +/** + * Read register masked field with debug info. + * + * @base - DWC3 base virtual address. + * @offset - register offset. + * @mask - register bitmask. + * + * @return u32 + */ +static inline u32 dwc3_msm_read_reg_field(void __iomem *base, + u32 offset, + const u32 mask) +{ + u32 shift = __ffs(mask); + u32 val = ioread32(base + offset); + + val &= mask; /* clear other bits */ + val >>= shift; + return val; +} + +/** + * + * Write register with debug info. + * + * @base - DWC3 base virtual address. + * @offset - register offset. + * @val - value to write. + * + */ +static inline void dwc3_msm_write_reg(void __iomem *base, u32 offset, u32 val) +{ + iowrite32(val, base + offset); +} + +/** + * Write register masked field with debug info. + * + * @base - DWC3 base virtual address. + * @offset - register offset. + * @mask - register bitmask. + * @val - value to write. + * + */ +static inline void dwc3_msm_write_reg_field(void __iomem *base, u32 offset, + const u32 mask, u32 val) +{ + u32 shift = __ffs(mask); + u32 tmp = ioread32(base + offset); + + tmp &= ~mask; /* clear written bits */ + val = tmp | (val << shift); + iowrite32(val, base + offset); + + /* Read back to make sure that previous write goes through */ + ioread32(base + offset); +} + +/** + * dwc3_core_calc_tx_fifo_size - calculates the txfifo size value + * @dwc: pointer to the DWC3 context + * @nfifos: number of fifos to calculate for + * + * Calculates the size value based on the equation below: + * + * fifo_size = mult * ((max_packet + mdwidth)/mdwidth + 1) + 1 + * + * The max packet size is set to 1024, as the txfifo requirements mainly apply + * to super speed USB use cases. However, it is safe to overestimate the fifo + * allocations for other scenarios, i.e. high speed USB. + */ +static int dwc3_core_calc_tx_fifo_size(struct dwc3 *dwc, int mult) +{ + int max_packet = 1024; + int fifo_size; + int mdwidth; + + mdwidth = dwc3_mdwidth(dwc); + + /* MDWIDTH is represented in bits, we need it in bytes */ + mdwidth >>= 3; + + fifo_size = mult * ((max_packet + mdwidth) / mdwidth) + 1; + return fifo_size; +} + +/* + * dwc3_core_resize_tx_fifos - reallocate fifo spaces for current use-case + * @dwc: pointer to our context structure + * + * This function will a best effort FIFO allocation in order + * to improve FIFO usage and throughput, while still allowing + * us to enable as many endpoints as possible. + * + * Keep in mind that this operation will be highly dependent + * on the configured size for RAM1 - which contains TxFifo -, + * the amount of endpoints enabled on coreConsultant tool, and + * the width of the Master Bus. + * + * In general, FIFO depths are represented with the following equation: + * + * fifo_size = mult * ((max_packet + mdwidth)/mdwidth + 1) + 1 + * + * Conversions can be done to the equation to derive the number of packets that + * will fit to a particular FIFO size value. + */ +static int dwc3_core_resize_tx_fifos(struct dwc3_ep *dep) +{ + struct dwc3 *dwc = dep->dwc; + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + int fifo_0_start; + int ram1_depth; + int fifo_size; + int min_depth; + int num_in_ep; + int remaining; + int num_fifos = 1; + int fifo; + int tmp; + + if (!dwc->do_fifo_resize) + return 0; + + /* resize IN endpoints except ep0 */ + if (!usb_endpoint_dir_in(dep->endpoint.desc) || dep->number <= 1) + return 0; + + ram1_depth = DWC3_RAM1_DEPTH(dwc->hwparams.hwparams7); + + if ((dep->endpoint.maxburst > 1 && + usb_endpoint_xfer_bulk(dep->endpoint.desc)) || + usb_endpoint_xfer_isoc(dep->endpoint.desc)) + num_fifos = 3; + + if (dep->endpoint.maxburst > 6 && + usb_endpoint_xfer_bulk(dep->endpoint.desc) && DWC3_IP_IS(DWC31)) + num_fifos = dwc->tx_fifo_resize_max_num; + + /* FIFO size for a single buffer */ + fifo = dwc3_core_calc_tx_fifo_size(dwc, 1); + + /* Calculate the number of remaining EPs w/o any FIFO */ + num_in_ep = dwc->max_cfg_eps; + num_in_ep -= dwc->num_ep_resized; + + /* Reserve at least one FIFO for the number of IN EPs */ + min_depth = num_in_ep * (fifo + 1); + remaining = ram1_depth - min_depth - dwc->last_fifo_depth; + remaining = max_t(int, 0, remaining); + /* + * We've already reserved 1 FIFO per EP, so check what we can fit in + * addition to it. If there is not enough remaining space, allocate + * all the remaining space to the EP. + */ + fifo_size = (num_fifos - 1) * fifo; + if (remaining < fifo_size) + fifo_size = remaining; + + fifo_size += fifo; + /* Last increment according to the TX FIFO size equation */ + fifo_size++; + + /* Check if TXFIFOs start at non-zero addr */ + tmp = dwc3_msm_read_reg(mdwc->base, DWC3_GTXFIFOSIZ(0)); + fifo_0_start = DWC3_GTXFIFOSIZ_TXFSTADDR(tmp); + + fifo_size |= (fifo_0_start + (dwc->last_fifo_depth << 16)); + if (DWC3_IP_IS(DWC3)) + dwc->last_fifo_depth += DWC3_GTXFIFOSIZ_TXFDEP(fifo_size); + else + dwc->last_fifo_depth += DWC31_GTXFIFOSIZ_TXFDEP(fifo_size); + + /* Check fifo size allocation doesn't exceed available RAM size. */ + if (dwc->last_fifo_depth >= ram1_depth) { + dev_err(dwc->dev, "Fifosize(%d) > RAM size(%d) %s depth:%d\n", + dwc->last_fifo_depth, ram1_depth, + dep->endpoint.name, fifo_size); + if (DWC3_IP_IS(DWC3)) + fifo_size = DWC3_GTXFIFOSIZ_TXFDEP(fifo_size); + else + fifo_size = DWC31_GTXFIFOSIZ_TXFDEP(fifo_size); + + dwc->last_fifo_depth -= fifo_size; + return -ENOMEM; + } + + dwc3_msm_write_reg(mdwc->base, DWC3_GTXFIFOSIZ(dep->number >> 1), fifo_size); + dwc->num_ep_resized++; + + return 0; +} + +/** + * Write DBM register masked field with debug info. + * + * @dbm - DBM specific data + * @reg - DBM register, used to look up the offset value + * @ep - endpoint number + * @mask - register bitmask. + * @val - value to write. + */ +static inline void msm_dbm_write_ep_reg_field(struct dwc3_msm *mdwc, + enum dbm_reg reg, int ep, + const u32 mask, u32 val) +{ + u32 offset = DBM_REG_OFFSET + mdwc->dbm_reg_table[reg].offset + + (mdwc->dbm_reg_table[reg].ep_mult * ep); + dwc3_msm_write_reg_field(mdwc->base, offset, mask, val); +} + +#define msm_dbm_write_reg_field(d, r, m, v) \ + msm_dbm_write_ep_reg_field(d, r, 0, m, v) + +/** + * Read DBM register with debug info. + * + * @dbm - DBM specific data + * @reg - DBM register, used to look up the offset value + * @ep - endpoint number + * + * @return u32 + */ +static inline u32 msm_dbm_read_ep_reg(struct dwc3_msm *mdwc, + enum dbm_reg reg, int ep) +{ + u32 offset = DBM_REG_OFFSET + mdwc->dbm_reg_table[reg].offset + + (mdwc->dbm_reg_table[reg].ep_mult * ep); + return dwc3_msm_read_reg(mdwc->base, offset); +} + +#define msm_dbm_read_reg(d, r) msm_dbm_read_ep_reg(d, r, 0) + +/** + * Write DBM register with debug info. + * + * @dbm - DBM specific data + * @reg - DBM register, used to look up the offset value + * @ep - endpoint number + */ +static inline void msm_dbm_write_ep_reg(struct dwc3_msm *mdwc, enum dbm_reg reg, + int ep, u32 val) +{ + u32 offset = DBM_REG_OFFSET + mdwc->dbm_reg_table[reg].offset + + (mdwc->dbm_reg_table[reg].ep_mult * ep); + dwc3_msm_write_reg(mdwc->base, offset, val); +} + +#define msm_dbm_write_reg(d, r, v) msm_dbm_write_ep_reg(d, r, 0, v) + +/** + * Return DBM EP number according to usb endpoint number. + */ +static int find_matching_dbm_ep(struct dwc3_msm *mdwc, u8 usb_ep) +{ + if (mdwc->hw_eps[usb_ep].mode == USB_EP_BAM) + return mdwc->hw_eps[usb_ep].dbm_ep_num; + + dev_dbg(mdwc->dev, "%s: No DBM EP matches USB EP %d\n", + __func__, usb_ep); + return -ENODEV; /* Not found */ +} + +/** + * Return number of configured DBM endpoints. + */ +static int dbm_get_num_of_eps_configured(struct dwc3_msm *mdwc) +{ + int i; + int count = 0; + + for (i = 0; i < DWC3_ENDPOINTS_NUM; i++) + if (mdwc->hw_eps[i].mode == USB_EP_BAM) + count++; + + return count; +} + +static bool dwc3_msm_is_ss_rhport_connected(struct dwc3_msm *mdwc) +{ + int i, num_ports; + u32 reg; + + reg = dwc3_msm_read_reg(mdwc->base, USB3_HCSPARAMS1); + num_ports = HCS_MAX_PORTS(reg); + + for (i = 0; i < num_ports; i++) { + reg = dwc3_msm_read_reg(mdwc->base, USB3_PORTSC + i*0x10); + if ((reg & PORT_CONNECT) && DEV_SUPERSPEED_ANY(reg)) + return true; + } + + return false; +} + +static bool dwc3_msm_is_host_superspeed(struct dwc3_msm *mdwc) +{ + int i, num_ports; + u32 reg; + + reg = dwc3_msm_read_reg(mdwc->base, USB3_HCSPARAMS1); + num_ports = HCS_MAX_PORTS(reg); + + for (i = 0; i < num_ports; i++) { + reg = dwc3_msm_read_reg(mdwc->base, USB3_PORTSC + i*0x10); + if ((reg & PORT_PE) && DEV_SUPERSPEED_ANY(reg)) + return true; + } + + return false; +} + +static inline bool dwc3_msm_is_dev_superspeed(struct dwc3_msm *mdwc) +{ + u8 speed; + + speed = dwc3_msm_read_reg(mdwc->base, DWC3_DSTS) & DWC3_DSTS_CONNECTSPD; + if ((speed == DWC3_DSTS_SUPERSPEED) || + (speed == DWC3_DSTS_SUPERSPEED_PLUS)) + return true; + + return false; +} + +static inline bool dwc3_msm_is_superspeed(struct dwc3_msm *mdwc) +{ + if (mdwc->in_host_mode) + return dwc3_msm_is_host_superspeed(mdwc); + + return dwc3_msm_is_dev_superspeed(mdwc); +} + +static int dwc3_msm_dbm_disable_updxfer(struct dwc3 *dwc, u8 usb_ep) +{ + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + u32 data; + int dbm_ep; + + dbm_ep = find_matching_dbm_ep(mdwc, usb_ep); + if (dbm_ep < 0) + return dbm_ep; + + data = msm_dbm_read_reg(mdwc, DBM_DISABLE_UPDXFER); + data |= (0x1 << dbm_ep); + msm_dbm_write_reg(mdwc, DBM_DISABLE_UPDXFER, data); + + return 0; +} + +static int dwc3_core_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned int cmd, + struct dwc3_gadget_ep_cmd_params *params) +{ + const struct usb_endpoint_descriptor *desc = dep->endpoint.desc; + struct dwc3 *dwc = dep->dwc; + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + u32 timeout = 5000; + u32 saved_config = 0; + u32 reg; + + int cmd_status = 0; + int ret = -EINVAL; + + /* + * When operating in USB 2.0 speeds (HS/FS), if GUSB2PHYCFG.ENBLSLPM or + * GUSB2PHYCFG.SUSPHY is set, it must be cleared before issuing an + * endpoint command. + * + * Save and clear both GUSB2PHYCFG.ENBLSLPM and GUSB2PHYCFG.SUSPHY + * settings. Restore them after the command is completed. + * + * DWC_usb3 3.30a and DWC_usb31 1.90a programming guide section 3.2.2 + */ + if (dwc->gadget->speed <= USB_SPEED_HIGH) { + reg = dwc3_msm_read_reg(mdwc->base, DWC3_GUSB2PHYCFG(0)); + if (unlikely(reg & DWC3_GUSB2PHYCFG_SUSPHY)) { + saved_config |= DWC3_GUSB2PHYCFG_SUSPHY; + reg &= ~DWC3_GUSB2PHYCFG_SUSPHY; + } + + if (reg & DWC3_GUSB2PHYCFG_ENBLSLPM) { + saved_config |= DWC3_GUSB2PHYCFG_ENBLSLPM; + reg &= ~DWC3_GUSB2PHYCFG_ENBLSLPM; + } + + if (saved_config) + dwc3_msm_write_reg(mdwc->base, DWC3_GUSB2PHYCFG(0), reg); + } + + dwc3_msm_ep_writel(dep->regs, DWC3_DEPCMDPAR0, params->param0); + dwc3_msm_ep_writel(dep->regs, DWC3_DEPCMDPAR1, params->param1); + dwc3_msm_ep_writel(dep->regs, DWC3_DEPCMDPAR2, params->param2); + + /* + * Synopsys Databook 2.60a states in section 6.3.2.5.6 of that if we're + * not relying on XferNotReady, we can make use of a special "No + * Response Update Transfer" command where we should clear both CmdAct + * and CmdIOC bits. + * + * With this, we don't need to wait for command completion and can + * straight away issue further commands to the endpoint. + * + * NOTICE: We're making an assumption that control endpoints will never + * make use of Update Transfer command. This is a safe assumption + * because we can never have more than one request at a time with + * Control Endpoints. If anybody changes that assumption, this chunk + * needs to be updated accordingly. + */ + if (DWC3_DEPCMD_CMD(cmd) == DWC3_DEPCMD_UPDATETRANSFER && + !usb_endpoint_xfer_isoc(desc)) + cmd &= ~(DWC3_DEPCMD_CMDIOC | DWC3_DEPCMD_CMDACT); + else + cmd |= DWC3_DEPCMD_CMDACT; + + dwc3_msm_ep_writel(dep->regs, DWC3_DEPCMD, cmd); + do { + reg = dwc3_msm_ep_readl(dep->regs, DWC3_DEPCMD); + if (!(reg & DWC3_DEPCMD_CMDACT)) { + cmd_status = DWC3_DEPCMD_STATUS(reg); + + switch (cmd_status) { + case 0: + ret = 0; + break; + case DEPEVT_TRANSFER_NO_RESOURCE: + dev_WARN(dwc->dev, "No resource for %s\n", + dep->name); + ret = -EINVAL; + break; + case DEPEVT_TRANSFER_BUS_EXPIRY: + /* + * SW issues START TRANSFER command to + * isochronous ep with future frame interval. If + * future interval time has already passed when + * core receives the command, it will respond + * with an error status of 'Bus Expiry'. + * + * Instead of always returning -EINVAL, let's + * give a hint to the gadget driver that this is + * the case by returning -EAGAIN. + */ + ret = -EAGAIN; + break; + default: + dev_WARN(dwc->dev, "UNKNOWN cmd status\n"); + } + + break; + } + } while (--timeout); + + if (timeout == 0) { + ret = -ETIMEDOUT; + cmd_status = -ETIMEDOUT; + } + + if (DWC3_DEPCMD_CMD(cmd) == DWC3_DEPCMD_STARTTRANSFER) { + if (ret == 0) + mdwc->hw_eps[dep->number].flags |= + DWC3_MSM_HW_EP_TRANSFER_STARTED; + + if (ret != -ETIMEDOUT) { + u32 res_id; + + res_id = dwc3_msm_ep_readl(dep->regs, DWC3_DEPCMD); + dep->resource_index = DWC3_DEPCMD_GET_RSC_IDX(res_id); + } + } + + if (saved_config) { + reg = dwc3_msm_read_reg(mdwc->base, DWC3_GUSB2PHYCFG(0)); + reg |= saved_config; + dwc3_msm_write_reg(mdwc->base, DWC3_GUSB2PHYCFG(0), reg); + } + + return ret; +} + +static void dwc3_core_stop_active_transfer(struct dwc3_ep *dep, bool force) +{ + struct dwc3 *dwc = dep->dwc; + struct dwc3_gadget_ep_cmd_params params; + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + u32 cmd; + int ret; + + if (!(mdwc->hw_eps[dep->number].flags & + DWC3_MSM_HW_EP_TRANSFER_STARTED)) + return; + + dwc3_msm_notify_event(dwc, DWC3_CONTROLLER_NOTIFY_DISABLE_UPDXFER, + dep->number); + + /* + * NOTICE: We are violating what the Databook says about the + * EndTransfer command. Ideally we would _always_ wait for the + * EndTransfer Command Completion IRQ, but that's causing too + * much trouble synchronizing between us and gadget driver. + * + * We have discussed this with the IP Provider and it was + * suggested to giveback all requests here. + * + * Note also that a similar handling was tested by Synopsys + * (thanks a lot Paul) and nothing bad has come out of it. + * In short, what we're doing is issuing EndTransfer with + * CMDIOC bit set and delay kicking transfer until the + * EndTransfer command had completed. + * + * As of IP version 3.10a of the DWC_usb3 IP, the controller + * supports a mode to work around the above limitation. The + * software can poll the CMDACT bit in the DEPCMD register + * after issuing a EndTransfer command. This mode is enabled + * by writing GUCTL2[14]. This polling is already done in the + * dwc3_core_send_gadget_ep_cmd() function so if the mode is + * enabled, the EndTransfer command will have completed upon + * returning from this function. + * + * This mode is NOT available on the DWC_usb31 IP. + */ + + cmd = DWC3_DEPCMD_ENDTRANSFER; + cmd |= force ? DWC3_DEPCMD_HIPRI_FORCERM : 0; + cmd |= DWC3_DEPCMD_PARAM(dep->resource_index); + memset(¶ms, 0, sizeof(params)); + ret = dwc3_core_send_gadget_ep_cmd(dep, cmd, ¶ms); + WARN_ON_ONCE(ret); + dep->resource_index = 0; + + if (DWC3_IP_IS(DWC31) || DWC3_VER_IS_PRIOR(DWC3, 310A)) + udelay(100); + /* + * The END_TRANSFER command will cause the controller to generate a + * NoStream Event, and it's not due to the host DP NoStream rejection. + * Ignore the next NoStream event. + */ + if (dep->stream_capable) + dep->flags |= DWC3_EP_IGNORE_NEXT_NOSTREAM; + + mdwc->hw_eps[dep->number].flags &= ~DWC3_MSM_HW_EP_TRANSFER_STARTED; +} + +int dwc3_core_stop_hw_active_transfers(struct dwc3 *dwc) +{ + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + int i; + + for (i = 0; i < DWC3_ENDPOINTS_NUM; i++) + if (mdwc->hw_eps[i].mode == USB_EP_GSI) + dwc3_core_stop_active_transfer(dwc->eps[i], true); + + return 0; +} + +#if IS_ENABLED(CONFIG_USB_DWC3_GADGET) || IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE) +/** + * Configure the DBM with the BAM's data fifo. + * This function is called by the USB BAM Driver + * upon initialization. + * + * @ep - pointer to usb endpoint. + * @addr - address of data fifo. + * @size - size of data fifo. + * + */ +int msm_data_fifo_config(struct usb_ep *ep, unsigned long addr, + u32 size, u8 dbm_ep) +{ + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + u32 lo = lower_32_bits(addr); + u32 hi = upper_32_bits(addr); + + if (dbm_ep >= DBM_1_5_NUM_EP) { + dev_err(mdwc->dev, "Invalid DBM EP num:%d\n", dbm_ep); + return -EINVAL; + } + + mdwc->hw_eps[dep->number].dbm_ep_num = dbm_ep; + + if (!mdwc->dbm_is_1p4 || sizeof(addr) > sizeof(u32)) { + msm_dbm_write_ep_reg(mdwc, DBM_DATA_FIFO_LSB, dbm_ep, lo); + msm_dbm_write_ep_reg(mdwc, DBM_DATA_FIFO_MSB, dbm_ep, hi); + } else { + msm_dbm_write_ep_reg(mdwc, DBM_DATA_FIFO, dbm_ep, addr); + } + + msm_dbm_write_ep_reg_field(mdwc, DBM_DATA_FIFO_SIZE, dbm_ep, + DBM_DATA_FIFO_SIZE_MASK, size); + + return 0; +} +EXPORT_SYMBOL(msm_data_fifo_config); + +static int dbm_ep_unconfig(struct dwc3_msm *mdwc, u8 usb_ep); + +/** + * Configure the DBM with the USB3 core event buffer. + * This function is called by the SNPS UDC upon initialization. + * + * @addr - address of the event buffer. + * @size - size of the event buffer. + * + */ +static int dbm_event_buffer_config(struct dwc3_msm *mdwc, u32 addr_lo, + u32 addr_hi, int size) +{ + dev_dbg(mdwc->dev, "Configuring event buffer\n"); + + if (size < 0) { + dev_err(mdwc->dev, "Invalid size %d\n", size); + return -EINVAL; + } + + /* In case event buffer is already configured, Do nothing. */ + if (msm_dbm_read_reg(mdwc, DBM_GEVNTSIZ)) + return 0; + + if (!mdwc->dbm_is_1p4 || sizeof(phys_addr_t) > sizeof(u32)) { + msm_dbm_write_reg(mdwc, DBM_GEVNTADR_LSB, addr_lo); + msm_dbm_write_reg(mdwc, DBM_GEVNTADR_MSB, addr_hi); + } else { + msm_dbm_write_reg(mdwc, DBM_GEVNTADR, addr_lo); + } + + msm_dbm_write_reg_field(mdwc, DBM_GEVNTSIZ, DBM_GEVNTSIZ_MASK, size); + + return 0; +} + +/** + * Cleanups for msm endpoint on request complete. + * + * Also call original request complete. + * + * @usb_ep - pointer to usb_ep instance. + * @request - pointer to usb_request instance. + * + * @return int - 0 on success, negative on error. + */ +static void dwc3_msm_req_complete_func(struct usb_ep *ep, + struct usb_request *request) +{ + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + struct dwc3_msm_req_complete *req_complete = NULL; + + /* Find original request complete function and remove it from list */ + list_for_each_entry(req_complete, &mdwc->req_complete_list, list_item) { + if (req_complete->req == request) + break; + } + if (!req_complete || req_complete->req != request) { + dev_err(dep->dwc->dev, "%s: could not find the request\n", + __func__); + return; + } + list_del(&req_complete->list_item); + + /* + * Release another one TRB to the pool since DBM queue took 2 TRBs + * (normal and link), and the dwc3/gadget.c :: dwc3_gadget_giveback + * released only one. + */ + dep->trb_dequeue++; + + /* Unconfigure dbm ep */ + dbm_ep_unconfig(mdwc, dep->number); + + /* + * If this is the last endpoint we unconfigured, than reset also + * the event buffers; unless unconfiguring the ep due to lpm, + * in which case the event buffer only gets reset during the + * block reset. + */ + if (dbm_get_num_of_eps_configured(mdwc) == 0) + dbm_event_buffer_config(mdwc, 0, 0, 0); + + /* + * Call original complete function, notice that dwc->lock is already + * taken by the caller of this function (dwc3_gadget_giveback()). + */ + request->complete = req_complete->orig_complete; + if (request->complete) + request->complete(ep, request); + + kfree(req_complete); +} + +/** + * Reset the DBM endpoint which is linked to the given USB endpoint. + * This function is called by the function driver upon events + * such as transfer aborting, USB re-enumeration and USB + * disconnection. + * + * @usb_ep - pointer to usb_ep instance. + * + * @return int - 0 on success, negative on error. + */ +int msm_dwc3_reset_dbm_ep(struct usb_ep *ep) +{ + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + int dbm_ep; + + dbm_ep = find_matching_dbm_ep(mdwc, dep->number); + if (dbm_ep < 0) + return dbm_ep; + + dev_dbg(mdwc->dev, "Resetting endpoint %d, DBM ep %d\n", dep->number, + dbm_ep); + + /* Reset the dbm endpoint */ + msm_dbm_write_reg_field(mdwc, DBM_SOFT_RESET, + DBM_SFT_RST_EPS_MASK & 1 << dbm_ep, 1); + + /* + * The necessary delay between asserting and deasserting the dbm ep + * reset is based on the number of active endpoints. If there is more + * than one endpoint, a 1 msec delay is required. Otherwise, a shorter + * delay will suffice. + */ + if (dbm_get_num_of_eps_configured(mdwc) > 1) + usleep_range(1000, 1200); + else + udelay(10); + + msm_dbm_write_reg_field(mdwc, DBM_SOFT_RESET, + DBM_SFT_RST_EPS_MASK & 1 << dbm_ep, 0); + + return 0; +} +EXPORT_SYMBOL(msm_dwc3_reset_dbm_ep); + +static int __dwc3_msm_ebc_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req) +{ + struct dwc3_gadget_ep_cmd_params params; + u32 cmd, param1; + int ret = 0; + + req->status = DWC3_REQUEST_STATUS_STARTED; + list_add_tail(&req->list, &dep->started_list); + if (dep->direction) + param1 = 0x0; + else + param1 = 0x200; + + /* Now start the transfer */ + memset(¶ms, 0, sizeof(params)); + params.param0 = 0x8000; /* TDAddr High */ + params.param1 = param1; /* DAddr Low */ + + cmd = DWC3_DEPCMD_STARTTRANSFER; + ret = dwc3_core_send_gadget_ep_cmd(dep, cmd, ¶ms); + if (ret < 0) { + dev_dbg(dep->dwc->dev, + "%s: failed to send STARTTRANSFER command\n", + __func__); + + list_del(&req->list); + return ret; + } + + return ret; +} + +/** + * Helper function. + * See the header of the dwc3_msm_ep_queue function. + * + * @dwc3_ep - pointer to dwc3_ep instance. + * @req - pointer to dwc3_request instance. + * + * @return int - 0 on success, negative on error. + */ +static int __dwc3_msm_dbm_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req) +{ + struct dwc3 *dwc = dep->dwc; + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + struct dwc3_trb *trb; + struct dwc3_trb *trb_link; + struct dwc3_gadget_ep_cmd_params params; + u32 cmd; + int ret = 0, size; + + /* We push the request to the dep->started_list list to indicate that + * this request is issued with start transfer. The request will be out + * from this list in 2 cases. The first is that the transfer will be + * completed (not if the transfer is endless using a circular TRBs with + * link TRB). The second case is an option to do stop stransfer, this + * can be initiated by the function driver when calling dequeue. + */ + req->status = DWC3_REQUEST_STATUS_STARTED; + list_add_tail(&req->list, &dep->started_list); + + size = dwc3_msm_read_reg(mdwc->base, DWC3_GEVNTSIZ(0)); + dbm_event_buffer_config(mdwc, + dwc3_msm_read_reg(mdwc->base, DWC3_GEVNTADRLO(0)), + dwc3_msm_read_reg(mdwc->base, DWC3_GEVNTADRHI(0)), + DWC3_GEVNTSIZ_SIZE(size)); + + /* First, prepare a normal TRB, point to the fake buffer */ + trb = &dep->trb_pool[dep->trb_enqueue]; + if (++dep->trb_enqueue == (DWC3_TRB_NUM - 1)) + dep->trb_enqueue = 0; + memset(trb, 0, sizeof(*trb)); + + req->trb = trb; + req->num_trbs++; + trb->bph = DBM_TRB_BIT | DBM_TRB_DMA | DBM_TRB_EP_NUM(dep->number); + trb->size = DWC3_TRB_SIZE_LENGTH(req->request.length); + trb->ctrl = DWC3_TRBCTL_NORMAL | DWC3_TRB_CTRL_HWO | + DWC3_TRB_CTRL_CHN | (req->direction ? 0 : DWC3_TRB_CTRL_CSP); + req->trb_dma = dwc3_trb_dma_offset(dep, trb); + + /* Second, prepare a Link TRB that points to the first TRB*/ + trb_link = &dep->trb_pool[dep->trb_enqueue]; + if (++dep->trb_enqueue == (DWC3_TRB_NUM - 1)) + dep->trb_enqueue = 0; + memset(trb_link, 0, sizeof(*trb_link)); + + trb_link->bpl = lower_32_bits(req->trb_dma); + trb_link->bph = DBM_TRB_BIT | + DBM_TRB_DMA | DBM_TRB_EP_NUM(dep->number); + trb_link->size = 0; + trb_link->ctrl = DWC3_TRBCTL_LINK_TRB | DWC3_TRB_CTRL_HWO; + + /* + * Now start the transfer + */ + memset(¶ms, 0, sizeof(params)); + params.param0 = 0; /* TDAddr High */ + params.param1 = lower_32_bits(req->trb_dma); /* DAddr Low */ + + /* DBM requires IOC to be set */ + cmd = DWC3_DEPCMD_STARTTRANSFER | DWC3_DEPCMD_CMDIOC; + ret = dwc3_core_send_gadget_ep_cmd(dep, cmd, ¶ms); + if (ret < 0) { + dev_dbg(dep->dwc->dev, + "%s: failed to send STARTTRANSFER command\n", + __func__); + + list_del(&req->list); + return ret; + } + + msm_dbm_write_reg(mdwc, DBM_GEN_CFG, + dwc3_msm_is_superspeed(mdwc) ? 1 : 0); + + return ret; +} + +/** + * Queue a usb request to the DBM endpoint. + * This function should be called after the endpoint + * was enabled by the ep_enable. + * + * This function prepares special structure of TRBs which + * is familiar with the DBM HW, so it will possible to use + * this endpoint in DBM mode. + * + * The TRBs prepared by this function, is one normal TRB + * which point to a fake buffer, followed by a link TRB + * that points to the first TRB. + * + * The API of this function follow the regular API of + * usb_ep_queue (see usb_ep_ops in include/linuk/usb/gadget.h). + * + * @usb_ep - pointer to usb_ep instance. + * @request - pointer to usb_request instance. + * @gfp_flags - possible flags. + * + * @return int - 0 on success, negative on error. + */ +static int dwc3_msm_ep_queue(struct usb_ep *ep, + struct usb_request *request, gfp_t gfp_flags) +{ + struct dwc3_request *req = to_dwc3_request(request); + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + struct dwc3_msm_req_complete *req_complete; + unsigned long flags; + int ret = 0; + + /* + * We must obtain the lock of the dwc3 core driver, + * including disabling interrupts, so we will be sure + * that we are the only ones that configure the HW device + * core and ensure that we queuing the request will finish + * as soon as possible so we will release back the lock. + */ + spin_lock_irqsave(&dwc->lock, flags); + if (!dep->endpoint.desc) { + dev_err(mdwc->dev, + "%s: trying to queue request %pK to disabled ep %s\n", + __func__, request, ep->name); + spin_unlock_irqrestore(&dwc->lock, flags); + return -EPERM; + } + + if (!mdwc->original_ep_ops[dep->number]) { + dev_err(mdwc->dev, + "ep [%s,%d] was unconfigured as msm endpoint\n", + ep->name, dep->number); + spin_unlock_irqrestore(&dwc->lock, flags); + return -EINVAL; + } + + if (!request) { + dev_err(mdwc->dev, "%s: request is NULL\n", __func__); + spin_unlock_irqrestore(&dwc->lock, flags); + return -EINVAL; + } + + /* HW restriction regarding TRB size (8KB) */ + if (mdwc->hw_eps[dep->number].mode == USB_EP_BAM && req->request.length < 0x2000) { + dev_err(mdwc->dev, "%s: Min TRB size is 8KB\n", __func__); + spin_unlock_irqrestore(&dwc->lock, flags); + return -EINVAL; + } + + if (dep->number == 0 || dep->number == 1) { + dev_err(mdwc->dev, + "%s: trying to queue dbm request %pK to ep %s\n", + __func__, request, ep->name); + spin_unlock_irqrestore(&dwc->lock, flags); + return -EPERM; + } + + if (dep->trb_dequeue != dep->trb_enqueue + || !list_empty(&dep->pending_list) + || !list_empty(&dep->started_list)) { + dev_err(mdwc->dev, + "%s: trying to queue dbm request %pK tp ep %s\n", + __func__, request, ep->name); + spin_unlock_irqrestore(&dwc->lock, flags); + return -EPERM; + } + dep->trb_dequeue = 0; + dep->trb_enqueue = 0; + + /* + * Override req->complete function, but before doing that, + * store it's original pointer in the req_complete_list. + */ + req_complete = kzalloc(sizeof(*req_complete), gfp_flags); + + if (!req_complete) { + spin_unlock_irqrestore(&dwc->lock, flags); + return -ENOMEM; + } + + req_complete->req = request; + req_complete->orig_complete = request->complete; + list_add_tail(&req_complete->list_item, &mdwc->req_complete_list); + request->complete = dwc3_msm_req_complete_func; + + dev_vdbg(dwc->dev, "%s: queuing request %pK to ep %s length %d\n", + __func__, request, ep->name, request->length); + + if (mdwc->hw_eps[dep->number].mode == USB_EP_EBC) + ret = __dwc3_msm_ebc_ep_queue(dep, req); + else + ret = __dwc3_msm_dbm_ep_queue(dep, req); + if (ret < 0) { + dev_err(mdwc->dev, + "error %d after queuing %s req\n", ret, + mdwc->hw_eps[dep->number].mode == USB_EP_EBC ? "ebc" : "dbm"); + goto err; + } + + spin_unlock_irqrestore(&dwc->lock, flags); + + return 0; + +err: + spin_unlock_irqrestore(&dwc->lock, flags); + kfree(req_complete); + return ret; +} + +/** + * dwc3_msm_depcfg_params - Set depcfg parameters for MSM eps + * @ep: Endpoint being configured + * @params: depcmd param being passed to the controller + * + * Initializes the dwc3_gadget_ep_cmd_params structure being passed as part of + * the depcfg command. This API is explicitly used for initializing the params + * for MSM specific HW endpoints. + * + * Supported EP types: + * - USB GSI + * - USB BAM + * - USB EBC + */ +static void dwc3_msm_depcfg_params(struct usb_ep *ep, struct dwc3_gadget_ep_cmd_params *params) +{ + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + const struct usb_endpoint_descriptor *desc = ep->desc; + const struct usb_ss_ep_comp_descriptor *comp_desc = ep->comp_desc; + + params->param0 = DWC3_DEPCFG_EP_TYPE(usb_endpoint_type(desc)) + | DWC3_DEPCFG_MAX_PACKET_SIZE(usb_endpoint_maxp(desc)); + + /* Burst size is only needed in SuperSpeed mode */ + if (dwc->gadget->speed >= USB_SPEED_SUPER) { + u32 burst = dep->endpoint.maxburst; + + params->param0 |= DWC3_DEPCFG_BURST_SIZE(burst - 1); + } + + if (usb_ss_max_streams(comp_desc) && usb_endpoint_xfer_bulk(desc)) { + params->param1 |= DWC3_DEPCFG_STREAM_CAPABLE + | DWC3_DEPCFG_STREAM_EVENT_EN; + dep->stream_capable = true; + } + + /* Set EP number */ + params->param1 |= DWC3_DEPCFG_EP_NUMBER(dep->number); + if (dep->direction) + params->param0 |= DWC3_DEPCFG_FIFO_NUMBER(dep->number >> 1); + + params->param0 |= DWC3_DEPCFG_ACTION_INIT; + + if (mdwc->hw_eps[dep->number].mode == USB_EP_GSI) { + /* Enable XferInProgress and XferComplete Interrupts */ + params->param1 |= DWC3_DEPCFG_XFER_COMPLETE_EN; + params->param1 |= DWC3_DEPCFG_XFER_IN_PROGRESS_EN; + params->param1 |= DWC3_DEPCFG_FIFO_ERROR_EN; + } else if (mdwc->hw_eps[dep->number].mode == USB_EP_EBC) { + params->param1 |= DWC3_DEPCFG_RETRY | DWC3_DEPCFG_TRB_WB; + params->param0 |= DWC3_DEPCFG_EBC_MODE; + } +} + +static int dwc3_msm_set_ep_config(struct dwc3_ep *dep, unsigned int action) +{ + const struct usb_ss_ep_comp_descriptor *comp_desc; + const struct usb_endpoint_descriptor *desc; + struct dwc3_gadget_ep_cmd_params params; + struct usb_ep *ep = &dep->endpoint; + + comp_desc = dep->endpoint.comp_desc; + desc = dep->endpoint.desc; + + memset(¶ms, 0x00, sizeof(params)); + dwc3_msm_depcfg_params(ep, ¶ms); + + return dwc3_core_send_gadget_ep_cmd(dep, DWC3_DEPCMD_SETEPCONFIG, ¶ms); +} + +static int __dwc3_msm_ep_enable(struct dwc3_ep *dep, unsigned int action) +{ + struct dwc3 *dwc = dep->dwc; + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + const struct usb_endpoint_descriptor *desc = dep->endpoint.desc; + u32 reg; + int ret; + + ret = dwc3_msm_set_ep_config(dep, action); + if (ret) { + dev_err(dwc->dev, "set_ep_config() failed for %s\n", dep->name); + return ret; + } + + if (!(dep->flags & DWC3_EP_ENABLED)) { + struct dwc3_trb *trb_st_hw; + struct dwc3_trb *trb_link; + + dwc3_core_resize_tx_fifos(dep); + + dep->type = usb_endpoint_type(desc); + dep->flags |= DWC3_EP_ENABLED; + + reg = dwc3_msm_read_reg(mdwc->base, DWC3_DALEPENA); + reg |= DWC3_DALEPENA_EP(dep->number); + dwc3_msm_write_reg(mdwc->base, DWC3_DALEPENA, reg); + + /* Initialize the TRB ring */ + dep->trb_dequeue = 0; + dep->trb_enqueue = 0; + memset(dep->trb_pool, 0, + sizeof(struct dwc3_trb) * DWC3_TRB_NUM); + + /* Link TRB. The HWO bit is never reset */ + trb_st_hw = &dep->trb_pool[0]; + + trb_link = &dep->trb_pool[DWC3_TRB_NUM - 1]; + trb_link->bpl = lower_32_bits(dwc3_trb_dma_offset(dep, trb_st_hw)); + trb_link->bph = upper_32_bits(dwc3_trb_dma_offset(dep, trb_st_hw)); + trb_link->ctrl |= DWC3_TRBCTL_LINK_TRB; + trb_link->ctrl |= DWC3_TRB_CTRL_HWO; + } + + return 0; +} + +static int dwc3_msm_ep_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + struct dwc3_ep *dep; + struct dwc3 *dwc; + struct dwc3_msm *mdwc; + unsigned long flags; + int ret; + + if (!ep || !desc || desc->bDescriptorType != USB_DT_ENDPOINT) { + pr_debug("dwc3: invalid parameters\n"); + return -EINVAL; + } + + if (!desc->wMaxPacketSize) { + pr_debug("dwc3: missing wMaxPacketSize\n"); + return -EINVAL; + } + + dep = to_dwc3_ep(ep); + dwc = dep->dwc; + mdwc = dev_get_drvdata(dwc->dev->parent); + + if (dev_WARN_ONCE(dwc->dev, dep->flags & DWC3_EP_ENABLED, + "%s is already enabled\n", + dep->name)) + return 0; + + if (pm_runtime_suspended(dwc->sysdev)) { + dev_err(dwc->dev, "fail ep_enable %s device is into LPM\n", + dep->name); + return -EINVAL; + } + + spin_lock_irqsave(&dwc->lock, flags); + ret = __dwc3_msm_ep_enable(dep, DWC3_DEPCFG_ACTION_INIT); + dbg_event(dep->number, "ENABLE", ret); + spin_unlock_irqrestore(&dwc->lock, flags); + + return ret; +} + +/** + * Returns XferRscIndex for the EP. This is stored at StartXfer GSI EP OP + * + * @usb_ep - pointer to usb_ep instance. + * + * @return int - XferRscIndex + */ +static inline int gsi_get_xfer_index(struct usb_ep *ep) +{ + struct dwc3_ep *dep = to_dwc3_ep(ep); + + return dep->resource_index; +} + +/** + * Fills up the GSI channel information needed in call to IPA driver + * for GSI channel creation. + * + * @usb_ep - pointer to usb_ep instance. + * @ch_info - output parameter with requested channel info + */ +static void gsi_get_channel_info(struct usb_ep *ep, + struct gsi_channel_info *ch_info) +{ + struct dwc3_ep *dep = to_dwc3_ep(ep); + int last_trb_index = 0; + struct dwc3 *dwc = dep->dwc; + struct usb_gsi_request *request = ch_info->ch_req; + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + + /* Provide physical USB addresses for DEPCMD and GEVENTCNT registers */ + ch_info->depcmd_low_addr = (u32)(mdwc->reg_phys + + DWC3_DEP_BASE(dep->number) + DWC3_DEPCMD); + + ch_info->depcmd_hi_addr = 0; + + ch_info->xfer_ring_base_addr = dwc3_trb_dma_offset(dep, + &dep->trb_pool[0]); + /* Convert to multipled of 1KB */ + ch_info->const_buffer_size = request->buf_len/1024; + + /* IN direction */ + if (dep->direction) { + /* + * Multiply by size of each TRB for xfer_ring_len in bytes. + * 2n + 2 TRBs as per GSI h/w requirement. n Xfer TRBs + 1 + * extra Xfer TRB followed by n ZLP TRBs + 1 LINK TRB. + */ + ch_info->xfer_ring_len = (2 * request->num_bufs + 2) * 0x10; + last_trb_index = 2 * request->num_bufs + 2; + } else { /* OUT direction */ + /* + * Multiply by size of each TRB for xfer_ring_len in bytes. + * n + 1 TRBs as per GSI h/w requirement. n Xfer TRBs + 1 + * LINK TRB. + */ + ch_info->xfer_ring_len = (request->num_bufs + 2) * 0x10; + last_trb_index = request->num_bufs + 2; + } + + /* Store last 16 bits of LINK TRB address as per GSI hw requirement */ + ch_info->last_trb_addr = (dwc3_trb_dma_offset(dep, + &dep->trb_pool[last_trb_index - 1]) & 0x0000FFFF); + ch_info->gevntcount_low_addr = (u32)(mdwc->reg_phys + + DWC3_GEVNTCOUNT(request->ep_intr_num)); + ch_info->gevntcount_hi_addr = 0; + + dev_dbg(dwc->dev, + "depcmd_laddr=%x last_trb_addr=%x gevtcnt_laddr=%x gevtcnt_haddr=%x", + ch_info->depcmd_low_addr, ch_info->last_trb_addr, + ch_info->gevntcount_low_addr, ch_info->gevntcount_hi_addr); +} + +/** + * Perform StartXfer on GSI EP. Stores XferRscIndex. + * + * @usb_ep - pointer to usb_ep instance. + * + * @return int - 0 on success + */ +static int gsi_startxfer_for_ep(struct usb_ep *ep, struct usb_gsi_request *req) +{ + int ret; + struct dwc3_gadget_ep_cmd_params params; + u32 cmd; + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + + memset(¶ms, 0, sizeof(params)); + params.param0 = GSI_TRB_ADDR_BIT_53_MASK | GSI_TRB_ADDR_BIT_55_MASK; + params.param0 |= (req->ep_intr_num << 16); + params.param1 = lower_32_bits(dwc3_trb_dma_offset(dep, + &dep->trb_pool[0])); + cmd = DWC3_DEPCMD_STARTTRANSFER; + cmd |= DWC3_DEPCMD_PARAM(0); + ret = dwc3_core_send_gadget_ep_cmd(dep, cmd, ¶ms); + + if (ret < 0) + dev_dbg(dwc->dev, "Fail StrtXfr on GSI EP#%d\n", dep->number); + dev_dbg(dwc->dev, "XferRsc = %x", dep->resource_index); + return ret; +} + +/** + * Store Ring Base and Doorbell Address for GSI EP + * for GSI channel creation. + * + * @usb_ep - pointer to usb_ep instance. + * @request - USB GSI request to get Doorbell address obtained from IPA driver + */ +static void gsi_store_ringbase_dbl_info(struct usb_ep *ep, + struct usb_gsi_request *request) +{ + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + int n = request->ep_intr_num - 1; + + dwc3_msm_write_reg(mdwc->base, GSI_RING_BASE_ADDR_L(mdwc->gsi_reg, n), + dwc3_trb_dma_offset(dep, &dep->trb_pool[0])); + + if (!request->mapped_db_reg_phs_addr_lsb) { + request->mapped_db_reg_phs_addr_lsb = + dma_map_resource(dwc->sysdev, + (phys_addr_t)request->db_reg_phs_addr_lsb, + PAGE_SIZE, DMA_BIDIRECTIONAL, 0); + if (dma_mapping_error(dwc->sysdev, + request->mapped_db_reg_phs_addr_lsb)) + dev_err(mdwc->dev, "mapping error for db_reg_phs_addr_lsb\n"); + } + + dev_dbg(mdwc->dev, "ep:%s dbl_addr_lsb:%x mapped_dbl_addr_lsb:%llx\n", + ep->name, request->db_reg_phs_addr_lsb, + (unsigned long long)request->mapped_db_reg_phs_addr_lsb); + + dbg_log_string("ep:%s dbl_addr_lsb:%x mapped_addr:%llx\n", + ep->name, request->db_reg_phs_addr_lsb, + (unsigned long long)request->mapped_db_reg_phs_addr_lsb); + + /* + * Replace dummy doorbell address with real one as IPA connection + * is setup now and GSI must be ready to handle doorbell updates. + */ + dwc3_msm_write_reg_field(mdwc->base, GSI_DBL_ADDR_H(mdwc->gsi_reg, n), + ~0x0, 0x0); + + dwc3_msm_write_reg(mdwc->base, GSI_DBL_ADDR_L(mdwc->gsi_reg, n), + (u32)request->mapped_db_reg_phs_addr_lsb); + dev_dbg(mdwc->dev, "Ring Base Addr %d: %x (LSB)\n", n, + dwc3_msm_read_reg(mdwc->base, + GSI_RING_BASE_ADDR_L(mdwc->gsi_reg, n))); + dev_dbg(mdwc->dev, "GSI DB Addr %d: %x (LSB)\n", n, + dwc3_msm_read_reg(mdwc->base, + GSI_DBL_ADDR_L(mdwc->gsi_reg, n))); +} + +/** + * Rings Doorbell for GSI Channel + * + * @usb_ep - pointer to usb_ep instance. + * @request - pointer to GSI request. This is used to pass in the + * address of the GSI doorbell obtained from IPA driver + */ +static void gsi_ring_db(struct usb_ep *ep, struct usb_gsi_request *request) +{ + void __iomem *gsi_dbl_address_lsb; + void __iomem *gsi_dbl_address_msb; + dma_addr_t offset; + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + int num_trbs = (dep->direction) ? (2 * (request->num_bufs) + 2) + : (request->num_bufs + 2); + + gsi_dbl_address_lsb = ioremap(request->db_reg_phs_addr_lsb, + sizeof(u32)); + if (!gsi_dbl_address_lsb) { + dev_err(mdwc->dev, "Failed to map GSI DBL address LSB 0x%x\n", + request->db_reg_phs_addr_lsb); + return; + } + + gsi_dbl_address_msb = ioremap(request->db_reg_phs_addr_msb, + sizeof(u32)); + if (!gsi_dbl_address_msb) { + dev_err(mdwc->dev, "Failed to map GSI DBL address MSB 0x%x\n", + request->db_reg_phs_addr_msb); + iounmap(gsi_dbl_address_lsb); + return; + } + + offset = dwc3_trb_dma_offset(dep, &dep->trb_pool[num_trbs-1]); + dev_dbg(mdwc->dev, "Writing link TRB addr: %pa to %pK (%x) for ep:%s\n", + &offset, gsi_dbl_address_lsb, request->db_reg_phs_addr_lsb, + ep->name); + + writel_relaxed(offset, gsi_dbl_address_lsb); + writel_relaxed(0, gsi_dbl_address_msb); + + iounmap(gsi_dbl_address_lsb); + iounmap(gsi_dbl_address_msb); +} + +/** + * Sets HWO bit for TRBs and performs UpdateXfer for OUT EP. + * + * @usb_ep - pointer to usb_ep instance. + * @request - pointer to GSI request. Used to determine num of TRBs for OUT EP. + * + * @return int - 0 on success + */ +static int gsi_updatexfer_for_ep(struct usb_ep *ep, + struct usb_gsi_request *request) +{ + int i; + int ret; + u32 cmd; + int num_trbs = request->num_bufs + 1; + struct dwc3_trb *trb; + struct dwc3_gadget_ep_cmd_params params; + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + + for (i = 0; i < num_trbs - 1; i++) { + trb = &dep->trb_pool[i]; + trb->ctrl |= DWC3_TRB_CTRL_HWO; + } + + memset(¶ms, 0, sizeof(params)); + cmd = DWC3_DEPCMD_UPDATETRANSFER; + cmd |= DWC3_DEPCMD_PARAM(dep->resource_index); + ret = dwc3_core_send_gadget_ep_cmd(dep, cmd, ¶ms); + if (ret < 0) + dev_dbg(dwc->dev, "UpdateXfr fail on GSI EP#%d\n", dep->number); + return ret; +} + +/** + * Allocates Buffers and TRBs. Configures TRBs for GSI EPs. + * + * @usb_ep - pointer to usb_ep instance. + * @request - pointer to GSI request. + * + * @return int - 0 on success + */ +static int gsi_prepare_trbs(struct usb_ep *ep, struct usb_gsi_request *req) +{ + int i = 0; + size_t len; + dma_addr_t buffer_addr; + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + struct dwc3_trb *trb; + int num_trbs = (dep->direction) ? (2 * (req->num_bufs) + 2) + : (req->num_bufs + 2); + struct scatterlist *sg; + struct sg_table *sgt; + + /* Allocate TRB buffers */ + + len = req->buf_len * req->num_bufs; + req->buf_base_addr = dma_alloc_coherent(dwc->sysdev, len, &req->dma, + GFP_KERNEL); + if (!req->buf_base_addr) + return -ENOMEM; + + dma_get_sgtable(dwc->sysdev, &req->sgt_data_buff, req->buf_base_addr, + req->dma, len); + + buffer_addr = req->dma; + dbg_log_string("TRB buffer_addr = %pad buf_len = %zu\n", &buffer_addr, + req->buf_len); + + /* Allocate and configgure TRBs */ + + dep->trb_pool = dma_alloc_coherent(dwc->sysdev, + num_trbs * sizeof(struct dwc3_trb), + &dep->trb_pool_dma, GFP_KERNEL); + + if (!dep->trb_pool) + goto free_trb_buffer; + + mdwc->hw_eps[dep->number].num_trbs = num_trbs; + dma_get_sgtable(dwc->sysdev, &req->sgt_trb_xfer_ring, dep->trb_pool, + dep->trb_pool_dma, num_trbs * sizeof(struct dwc3_trb)); + + sgt = &req->sgt_trb_xfer_ring; + dev_dbg(dwc->dev, "%s(): trb_pool:%pK trb_pool_dma:%lx\n", + __func__, dep->trb_pool, (unsigned long)dep->trb_pool_dma); + + for_each_sg(sgt->sgl, sg, sgt->nents, i) + dev_dbg(dwc->dev, + "%i: page_link:%lx offset:%x length:%x address:%lx\n", + i, sg->page_link, sg->offset, sg->length, + (unsigned long)sg->dma_address); + + /* IN direction */ + if (dep->direction) { + for (i = 0; i < num_trbs ; i++) { + trb = &dep->trb_pool[i]; + memset(trb, 0, sizeof(*trb)); + /* Set up first n+1 TRBs for ZLPs */ + if (i < (req->num_bufs + 1)) { + trb->bpl = 0; + trb->bph = 0; + trb->size = 0; + trb->ctrl = DWC3_TRBCTL_NORMAL + | DWC3_TRB_CTRL_IOC; + continue; + } + + /* Setup n TRBs pointing to valid buffers */ + trb->bpl = lower_32_bits(buffer_addr); + trb->bph = 0; + trb->size = 0; + trb->ctrl = DWC3_TRBCTL_NORMAL + | DWC3_TRB_CTRL_IOC; + buffer_addr += req->buf_len; + + /* Set up the Link TRB at the end */ + if (i == (num_trbs - 1)) { + trb->bpl = dwc3_trb_dma_offset(dep, + &dep->trb_pool[0]); + trb->bph = (1 << 23) | (1 << 21) + | (req->ep_intr_num << 16); + trb->size = 0; + trb->ctrl = DWC3_TRBCTL_LINK_TRB + | DWC3_TRB_CTRL_HWO; + } + } + } else { /* OUT direction */ + + for (i = 0; i < num_trbs ; i++) { + + trb = &dep->trb_pool[i]; + memset(trb, 0, sizeof(*trb)); + /* Setup LINK TRB to start with TRB ring */ + if (i == 0) { + trb->bpl = dwc3_trb_dma_offset(dep, + &dep->trb_pool[1]); + trb->ctrl = DWC3_TRBCTL_LINK_TRB; + } else if (i == (num_trbs - 1)) { + /* Set up the Link TRB at the end */ + trb->bpl = dwc3_trb_dma_offset(dep, + &dep->trb_pool[0]); + trb->bph = (1 << 23) | (1 << 21) + | (req->ep_intr_num << 16); + trb->ctrl = DWC3_TRBCTL_LINK_TRB + | DWC3_TRB_CTRL_HWO; + } else { + trb->bpl = lower_32_bits(buffer_addr); + trb->size = req->buf_len; + buffer_addr += req->buf_len; + trb->ctrl = DWC3_TRBCTL_NORMAL + | DWC3_TRB_CTRL_IOC + | DWC3_TRB_CTRL_CSP + | DWC3_TRB_CTRL_ISP_IMI; + } + } + } + + dev_dbg(dwc->dev, "%s: Initialized TRB Ring for %s\n", + __func__, dep->name); + trb = &dep->trb_pool[0]; + if (trb) { + for (i = 0; i < num_trbs; i++) { + dev_dbg(dwc->dev, + "TRB %d: ADDR:%lx bpl:%x bph:%x sz:%x ctl:%x\n", + i, (unsigned long)dwc3_trb_dma_offset(dep, + &dep->trb_pool[i]), trb->bpl, trb->bph, + trb->size, trb->ctrl); + trb++; + } + } + + return 0; + +free_trb_buffer: + dma_free_coherent(dwc->sysdev, len, req->buf_base_addr, req->dma); + req->buf_base_addr = NULL; + sg_free_table(&req->sgt_data_buff); + return -ENOMEM; +} + +/** + * Frees TRBs and buffers for GSI EPs. + * + * @usb_ep - pointer to usb_ep instance. + * + */ +static void gsi_free_trbs(struct usb_ep *ep, struct usb_gsi_request *req) +{ + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + + if (mdwc->hw_eps[dep->number].mode != USB_EP_GSI) + return; + + /* Free TRBs and TRB pool for EP */ + if (dep->trb_pool_dma) { + dma_free_coherent(dwc->sysdev, + mdwc->hw_eps[dep->number].num_trbs * sizeof(struct dwc3_trb), + dep->trb_pool, + dep->trb_pool_dma); + dep->trb_pool = NULL; + dep->trb_pool_dma = 0; + } + sg_free_table(&req->sgt_trb_xfer_ring); + + /* free TRB buffers */ + dma_free_coherent(dwc->sysdev, req->buf_len * req->num_bufs, + req->buf_base_addr, req->dma); + req->buf_base_addr = NULL; + sg_free_table(&req->sgt_data_buff); +} +/** + * Configures GSI EPs. For GSI EPs we need to set interrupter numbers. + * + * @usb_ep - pointer to usb_ep instance. + * @request - pointer to GSI request. + */ +static void gsi_configure_ep(struct usb_ep *ep, struct usb_gsi_request *request) +{ + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + struct dwc3_gadget_ep_cmd_params params; + const struct usb_endpoint_descriptor *desc = ep->desc; + const struct usb_ss_ep_comp_descriptor *comp_desc = ep->comp_desc; + int n = request->ep_intr_num - 1; + u32 reg; + + /* setup dummy doorbell as IPA connection isn't setup yet */ + dwc3_msm_write_reg_field(mdwc->base, GSI_DBL_ADDR_H(mdwc->gsi_reg, n), + ~0x0, (u32)((u64)mdwc->dummy_gsi_db_dma >> 32)); + + dwc3_msm_write_reg_field(mdwc->base, GSI_DBL_ADDR_L(mdwc->gsi_reg, n), + ~0x0, (u32)mdwc->dummy_gsi_db_dma); + dev_dbg(mdwc->dev, "Dummy DB Addr %pK: %pad\n", + &mdwc->dummy_gsi_db, &mdwc->dummy_gsi_db_dma); + + memset(¶ms, 0x00, sizeof(params)); + + dwc3_msm_depcfg_params(ep, ¶ms); + + /* Set interrupter number for GSI endpoints */ + params.param1 |= DWC3_DEPCFG_INT_NUM(request->ep_intr_num); + + dev_dbg(mdwc->dev, "Set EP config to params = %x %x %x, for %s\n", + params.param0, params.param1, params.param2, dep->name); + + dwc3_core_send_gadget_ep_cmd(dep, DWC3_DEPCMD_SETEPCONFIG, ¶ms); + + if (!(dep->flags & DWC3_EP_ENABLED)) { + dwc3_core_resize_tx_fifos(dep); + dep->endpoint.desc = desc; + dep->endpoint.comp_desc = comp_desc; + dep->type = usb_endpoint_type(desc); + dep->flags |= DWC3_EP_ENABLED; + reg = dwc3_msm_read_reg(mdwc->base, DWC3_DALEPENA); + reg |= DWC3_DALEPENA_EP(dep->number); + dwc3_msm_write_reg(mdwc->base, DWC3_DALEPENA, reg); + } + +} + +/** + * Enables USB wrapper for GSI + * + * @usb_ep - pointer to usb_ep instance. + */ +static void gsi_enable(struct usb_ep *ep) +{ + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + + dwc3_msm_write_reg_field(mdwc->base, GSI_GENERAL_CFG_REG(mdwc->gsi_reg), + GSI_CLK_EN_MASK, 1); + dwc3_msm_write_reg_field(mdwc->base, GSI_GENERAL_CFG_REG(mdwc->gsi_reg), + GSI_RESTART_DBL_PNTR_MASK, 1); + dwc3_msm_write_reg_field(mdwc->base, GSI_GENERAL_CFG_REG(mdwc->gsi_reg), + GSI_RESTART_DBL_PNTR_MASK, 0); + dev_dbg(mdwc->dev, "%s: Enable GSI\n", __func__); + dwc3_msm_write_reg_field(mdwc->base, GSI_GENERAL_CFG_REG(mdwc->gsi_reg), + GSI_EN_MASK, 1); +} + +/** + * Block or allow doorbell towards GSI + * + * @usb_ep - pointer to usb_ep instance. + * @request - pointer to GSI request. In this case num_bufs is used as a bool + * to set or clear the doorbell bit + */ +static void gsi_set_clear_dbell(struct usb_ep *ep, + bool block_db) +{ + + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + + dwc3_msm_write_reg_field(mdwc->base, GSI_GENERAL_CFG_REG(mdwc->gsi_reg), + BLOCK_GSI_WR_GO_MASK, block_db); +} + +/** + * Performs necessary checks before stopping GSI channels + * + * @usb_ep - pointer to usb_ep instance to access DWC3 regs + */ +static bool gsi_check_ready_to_suspend(struct dwc3_msm *mdwc) +{ + u32 timeout = 1500; + + while (dwc3_msm_read_reg_field(mdwc->base, + GSI_IF_STS(mdwc->gsi_reg), GSI_WR_CTRL_STATE_MASK)) { + if (!timeout--) { + dev_err(mdwc->dev, + "Unable to suspend GSI ch. WR_CTRL_STATE != 0\n"); + return false; + } + } + + return true; +} + +static inline const char *gsi_op_to_string(unsigned int op) +{ + if (op < ARRAY_SIZE(gsi_op_strings)) + return gsi_op_strings[op]; + + return "Invalid"; +} + +/** + * Performs GSI operations or GSI EP related operations. + * + * @usb_ep - pointer to usb_ep instance. + * @op_data - pointer to opcode related data. + * @op - GSI related or GSI EP related op code. + * + * @return int - 0 on success, negative on error. + * Also returns XferRscIdx for GSI_EP_OP_GET_XFER_IDX. + */ +int usb_gsi_ep_op(struct usb_ep *ep, void *op_data, enum gsi_ep_op op) +{ + u32 ret = 0; + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + struct usb_gsi_request *request; + struct gsi_channel_info *ch_info; + bool block_db; + unsigned long flags; + + dbg_log_string("%s(%d):%s", ep->name, dep->number >> 1, + gsi_op_to_string(op)); + + switch (op) { + case GSI_EP_OP_PREPARE_TRBS: + if (!dwc->pullups_connected) { + dbg_log_string("No Pullup\n"); + return -ESHUTDOWN; + } + + dwc3_free_trb_pool(dep); + request = (struct usb_gsi_request *)op_data; + ret = gsi_prepare_trbs(ep, request); + break; + case GSI_EP_OP_FREE_TRBS: + request = (struct usb_gsi_request *)op_data; + gsi_free_trbs(ep, request); + dwc3_alloc_trb_pool(dep); + break; + case GSI_EP_OP_CONFIG: + if (!dwc->pullups_connected) { + dbg_log_string("No Pullup\n"); + return -ESHUTDOWN; + } + + request = (struct usb_gsi_request *)op_data; + spin_lock_irqsave(&dwc->lock, flags); + gsi_configure_ep(ep, request); + spin_unlock_irqrestore(&dwc->lock, flags); + break; + case GSI_EP_OP_STARTXFER: + if (!dwc->pullups_connected) { + dbg_log_string("No Pullup\n"); + return -ESHUTDOWN; + } + + request = (struct usb_gsi_request *)op_data; + spin_lock_irqsave(&dwc->lock, flags); + ret = gsi_startxfer_for_ep(ep, request); + spin_unlock_irqrestore(&dwc->lock, flags); + break; + case GSI_EP_OP_GET_XFER_IDX: + ret = gsi_get_xfer_index(ep); + break; + case GSI_EP_OP_STORE_DBL_INFO: + request = (struct usb_gsi_request *)op_data; + gsi_store_ringbase_dbl_info(ep, request); + break; + case GSI_EP_OP_ENABLE_GSI: + if (!dwc->pullups_connected) { + dbg_log_string("No Pullup\n"); + return -ESHUTDOWN; + } + + gsi_enable(ep); + break; + case GSI_EP_OP_GET_CH_INFO: + ch_info = (struct gsi_channel_info *)op_data; + gsi_get_channel_info(ep, ch_info); + break; + case GSI_EP_OP_RING_DB: + if (!dwc->pullups_connected) { + dbg_log_string("No Pullup\n"); + return -ESHUTDOWN; + } + + request = (struct usb_gsi_request *)op_data; + gsi_ring_db(ep, request); + break; + case GSI_EP_OP_UPDATEXFER: + request = (struct usb_gsi_request *)op_data; + spin_lock_irqsave(&dwc->lock, flags); + ret = gsi_updatexfer_for_ep(ep, request); + spin_unlock_irqrestore(&dwc->lock, flags); + break; + case GSI_EP_OP_ENDXFER: + spin_lock_irqsave(&dwc->lock, flags); + dwc3_core_stop_active_transfer(dep, true); + spin_unlock_irqrestore(&dwc->lock, flags); + break; + case GSI_EP_OP_SET_CLR_BLOCK_DBL: + block_db = *((bool *)op_data); + gsi_set_clear_dbell(ep, block_db); + break; + case GSI_EP_OP_CHECK_FOR_SUSPEND: + ret = gsi_check_ready_to_suspend(mdwc); + break; + case GSI_EP_OP_DISABLE: + /* + * Explicitly stop active transfers, as DWC3 core no longer + * knows about the DEP flags for GSI based EPs. + */ + spin_lock_irqsave(&dwc->lock, flags); + dwc3_core_stop_active_transfer(dep, true); + spin_unlock_irqrestore(&dwc->lock, flags); + ret = ep->ops->disable(ep); + break; + default: + dev_err(mdwc->dev, "%s: Invalid opcode GSI EP\n", __func__); + } + + return ret; +} +EXPORT_SYMBOL(usb_gsi_ep_op); + +/** + * Configure a USB DBM ep to work in BAM mode. + * + * @usb_ep - USB physical EP number. + * @producer - producer/consumer. + * @disable_wb - disable write back to system memory. + * @internal_mem - use internal USB memory for data fifo. + * @ioc - enable interrupt on completion. + * + * @return int - DBM ep number. + */ +static int dbm_ep_config(struct dwc3_msm *mdwc, u8 usb_ep, u8 bam_pipe, + bool producer, bool disable_wb, bool internal_mem, bool ioc) +{ + int dbm_ep; + u32 ep_cfg; + u32 data; + + dev_dbg(mdwc->dev, "Configuring DBM ep\n"); + + dbm_ep = find_matching_dbm_ep(mdwc, usb_ep); + if (dbm_ep < 0) + return dbm_ep; + + /* Due to HW issue, EP 7 can be set as IN EP only */ + if (!mdwc->dbm_is_1p4 && dbm_ep == 7 && producer) { + pr_err("last DBM EP can't be OUT EP\n"); + return -ENODEV; + } + + /* Set ioc bit for dbm_ep if needed */ + msm_dbm_write_reg_field(mdwc, DBM_DBG_CNFG, + DBM_ENABLE_IOC_MASK & 1 << dbm_ep, ioc ? 1 : 0); + + ep_cfg = (producer ? DBM_PRODUCER : 0) | + (disable_wb ? DBM_DISABLE_WB : 0) | + (internal_mem ? DBM_INT_RAM_ACC : 0); + + msm_dbm_write_ep_reg_field(mdwc, DBM_EP_CFG, dbm_ep, + DBM_PRODUCER | DBM_DISABLE_WB | DBM_INT_RAM_ACC, ep_cfg >> 8); + + msm_dbm_write_ep_reg_field(mdwc, DBM_EP_CFG, dbm_ep, USB3_EPNUM, + usb_ep); + + if (mdwc->dbm_is_1p4) { + msm_dbm_write_ep_reg_field(mdwc, DBM_EP_CFG, dbm_ep, + DBM_BAM_PIPE_NUM, bam_pipe); + msm_dbm_write_reg_field(mdwc, DBM_PIPE_CFG, 0x000000ff, 0xe4); + } + + msm_dbm_write_ep_reg_field(mdwc, DBM_EP_CFG, dbm_ep, DBM_EN_EP, 1); + + data = msm_dbm_read_reg(mdwc, DBM_DISABLE_UPDXFER); + data &= ~(0x1 << dbm_ep); + msm_dbm_write_reg(mdwc, DBM_DISABLE_UPDXFER, data); + + return dbm_ep; +} + +static int msm_ep_clear_ebc_trbs(struct usb_ep *ep) +{ + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + struct dwc3_hw_ep *edep; + + edep = &mdwc->hw_eps[dep->number]; + if (edep->ebc_trb_pool) { + memunmap(edep->ebc_trb_pool); + edep->ebc_trb_pool = NULL; + } + + return 0; +} + +static int msm_ep_setup_ebc_trbs(struct usb_ep *ep, struct usb_request *req) +{ + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + struct dwc3_hw_ep *edep; + struct dwc3_trb *trb; + u32 desc_offset = 0, scan_offset = 0x4000, phys_base; + int i, num_trbs; + + if (!mdwc->ebc_desc_addr) { + dev_err(mdwc->dev, "%s: ebc_desc_addr not specified\n", __func__); + return -EINVAL; + } + + if (!dep->direction) { + desc_offset = 0x200; + scan_offset = 0x8000; + } + + edep = &mdwc->hw_eps[dep->number]; + phys_base = mdwc->ebc_desc_addr + desc_offset; + num_trbs = req->length / EBC_TRB_SIZE; + mdwc->hw_eps[dep->number].num_trbs = num_trbs; + edep->ebc_trb_pool = memremap(phys_base, + num_trbs * sizeof(struct dwc3_trb), + MEMREMAP_WT); + + for (i = 0; i < num_trbs; i++) { + trb = &edep->ebc_trb_pool[i]; + memset(trb, 0, sizeof(*trb)); + + /* Setup n TRBs pointing to valid buffers */ + trb->bpl = scan_offset; + trb->bph = 0x8000; + trb->size = EBC_TRB_SIZE; + trb->ctrl = DWC3_TRBCTL_NORMAL | DWC3_TRB_CTRL_CHN | + DWC3_TRB_CTRL_HWO; + if (i == (num_trbs-1)) { + trb->bpl = desc_offset; + trb->bph = 0x8000; + trb->size = 0; + trb->ctrl = DWC3_TRBCTL_LINK_TRB | DWC3_TRB_CTRL_HWO; + } + scan_offset += trb->size; + } + + return 0; +} + +static int ebc_ep_config(struct usb_ep *ep, struct usb_request *request) +{ + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + u32 reg, ep_num; + int ret; + + reg = dwc3_msm_read_reg(mdwc->base, LPC_REG); + + switch (dwc3_msm_read_reg(mdwc->base, DWC3_DSTS) & DWC3_DSTS_CONNECTSPD) { + case DWC3_DSTS_SUPERSPEED_PLUS: + reg |= LPC_SSP_MODE; + break; + case DWC3_DSTS_SUPERSPEED: + reg |= LPC_SPEED_INDICATOR; + break; + default: + reg &= ~(LPC_SSP_MODE | LPC_SPEED_INDICATOR); + break; + } + + dwc3_msm_write_reg(mdwc->base, LPC_REG, reg); + ret = msm_ep_setup_ebc_trbs(ep, request); + if (ret < 0) { + dev_err(mdwc->dev, "error %d setting up ebc trbs\n", ret); + return ret; + } + + ep_num = !dep->direction ? dep->number + 15 : + dep->number >> 1; + reg = dwc3_msm_read_reg(mdwc->base, LPC_SCAN_MASK); + reg |= BIT(ep_num); + dwc3_msm_write_reg(mdwc->base, LPC_SCAN_MASK, reg); + + reg = dwc3_msm_read_reg(mdwc->base, LPC_REG); + reg |= LPC_BUS_CLK_EN; + dwc3_msm_write_reg(mdwc->base, LPC_REG, reg); + + reg = dwc3_msm_read_reg(mdwc->base, USB30_MODE_SEL_REG); + reg |= USB30_QDSS_MODE_SEL; + dwc3_msm_write_reg(mdwc->base, USB30_MODE_SEL_REG, reg); + + return 0; +} + +/** + * Configure MSM endpoint. + * This function do specific configurations + * to an endpoint which need specific implementaion + * in the MSM architecture. + * + * This function should be called by usb function/class + * layer which need a support from the specific MSM HW + * which wrap the USB3 core. (like EBC or DBM specific endpoints) + * + * @ep - a pointer to some usb_ep instance + * + * @return int - 0 on success, negetive on error. + */ +int msm_ep_config(struct usb_ep *ep, struct usb_request *request, u32 bam_opts) +{ + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + int ret = 0; + unsigned long flags; + + spin_lock_irqsave(&dwc->lock, flags); + + if (mdwc->hw_eps[dep->number].mode == USB_EP_EBC) { + ret = ebc_ep_config(ep, request); + if (ret < 0) { + dev_err(mdwc->dev, + "error %d after calling ebc_ep_config\n", ret); + spin_unlock_irqrestore(&dwc->lock, flags); + return ret; + } + } else { + /* Configure the DBM endpoint if required. */ + ret = dbm_ep_config(mdwc, dep->number, + bam_opts & MSM_PIPE_ID_MASK, + bam_opts & MSM_PRODUCER, + bam_opts & MSM_DISABLE_WB, + bam_opts & MSM_INTERNAL_MEM, + bam_opts & MSM_ETD_IOC); + if (ret < 0) { + dev_err(mdwc->dev, + "error %d after calling dbm_ep_config\n", ret); + spin_unlock_irqrestore(&dwc->lock, flags); + return ret; + } + } + mdwc->hw_eps[dep->number].dep = dep; + spin_unlock_irqrestore(&dwc->lock, flags); + + return 0; +} +EXPORT_SYMBOL(msm_ep_config); + +static int dbm_ep_unconfig(struct dwc3_msm *mdwc, u8 usb_ep) +{ + int dbm_ep; + u32 data; + + dev_dbg(mdwc->dev, "Unconfiguring DB ep\n"); + + dbm_ep = find_matching_dbm_ep(mdwc, usb_ep); + if (dbm_ep < 0) + return dbm_ep; + + mdwc->hw_eps[usb_ep].dbm_ep_num = 0; + data = msm_dbm_read_ep_reg(mdwc, DBM_EP_CFG, dbm_ep); + data &= (~0x1); + msm_dbm_write_ep_reg(mdwc, DBM_EP_CFG, dbm_ep, data); + + /* + * ep_soft_reset is not required during disconnect as pipe reset on + * next connect will take care of the same. + */ + return 0; +} + +/** + * Un-configure MSM endpoint. + * Tear down configurations done in the + * dwc3_msm_ep_config function. + * + * @ep - a pointer to some usb_ep instance + * + * @return int - 0 on success, negative on error. + */ +int msm_ep_unconfig(struct usb_ep *ep) +{ + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + unsigned long flags; + u32 reg, ep_num; + + spin_lock_irqsave(&dwc->lock, flags); + if (mdwc->hw_eps[dep->number].mode == USB_EP_EBC) { + ep_num = !dep->direction ? dep->number + 15 : + dep->number >> 1; + reg = dwc3_msm_read_reg(mdwc->base, LPC_SCAN_MASK); + reg &= ~BIT(ep_num); + dwc3_msm_write_reg(mdwc->base, LPC_SCAN_MASK, reg); + + dwc3_msm_write_reg(mdwc->base, LPC_SCAN_MASK, 0); + reg = dwc3_msm_read_reg(mdwc->base, LPC_REG); + reg &= ~LPC_BUS_CLK_EN; + + dwc3_msm_write_reg(mdwc->base, LPC_REG, reg); + msm_ep_clear_ebc_trbs(ep); + } else { + if (dep->trb_dequeue == dep->trb_enqueue && + list_empty(&dep->pending_list) && + list_empty(&dep->started_list)) { + dev_dbg(mdwc->dev, + "%s: request is not queued, disable DBM ep for ep %s\n", + __func__, ep->name); + /* Unconfigure dbm ep */ + dbm_ep_unconfig(mdwc, dep->number); + + + /* + * If this is the last endpoint we unconfigured, than reset also + * the event buffers; unless unconfiguring the ep due to lpm, + * in which case the event buffer only gets reset during the + * block reset. + */ + if (dbm_get_num_of_eps_configured(mdwc) == 0) + dbm_event_buffer_config(mdwc, 0, 0, 0); + } + } + + mdwc->hw_eps[dep->number].dep = 0; + spin_unlock_irqrestore(&dwc->lock, flags); + + return 0; +} +EXPORT_SYMBOL(msm_ep_unconfig); + +/** + * msm_ep_clear_ops - Restore default endpoint operations + * @ep: The endpoint to restore + * + * Resets the usb endpoint operations to the default callbacks previously saved + * when calling msm_ep_update_ops. + */ +int msm_ep_clear_ops(struct usb_ep *ep) +{ + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + struct usb_ep_ops *old_ep_ops; + unsigned long flags; + + spin_lock_irqsave(&dwc->lock, flags); + + /* Restore original ep ops */ + if (!mdwc->original_ep_ops[dep->number]) { + spin_unlock_irqrestore(&dwc->lock, flags); + dev_err(mdwc->dev, + "ep [%s,%d] was not configured as msm endpoint\n", + ep->name, dep->number); + return -EINVAL; + } + old_ep_ops = (struct usb_ep_ops *)ep->ops; + ep->ops = mdwc->original_ep_ops[dep->number]; + mdwc->original_ep_ops[dep->number] = NULL; + kfree(old_ep_ops); + + spin_unlock_irqrestore(&dwc->lock, flags); + return 0; +} +EXPORT_SYMBOL(msm_ep_clear_ops); + +/** + * msm_ep_update_ops - Override default USB ep ops w/ MSM specific ops + * @ep: The endpoint to override + * + * Replaces the default endpoint operations with MSM specific operations for + * handling HW based endpoints, such as DBM or EBC eps. This does not depend + * on calling msm_ep_config beforehand. + */ +int msm_ep_update_ops(struct usb_ep *ep) +{ + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + struct usb_ep_ops *new_ep_ops; + unsigned long flags; + + spin_lock_irqsave(&dwc->lock, flags); + + /* Save original ep ops for future restore*/ + if (mdwc->original_ep_ops[dep->number]) { + spin_unlock_irqrestore(&dwc->lock, flags); + dev_err(mdwc->dev, + "ep [%s,%d] already configured as msm endpoint\n", + ep->name, dep->number); + return -EPERM; + } + mdwc->original_ep_ops[dep->number] = ep->ops; + + /* Set new usb ops as we like */ + new_ep_ops = kzalloc(sizeof(struct usb_ep_ops), GFP_ATOMIC); + if (!new_ep_ops) { + spin_unlock_irqrestore(&dwc->lock, flags); + return -ENOMEM; + } + + (*new_ep_ops) = (*ep->ops); + new_ep_ops->queue = dwc3_msm_ep_queue; + new_ep_ops->enable = dwc3_msm_ep_enable; + + ep->ops = new_ep_ops; + + spin_unlock_irqrestore(&dwc->lock, flags); + + return 0; +} +EXPORT_SYMBOL(msm_ep_update_ops); + +int msm_ep_set_mode(struct usb_ep *ep, enum usb_hw_ep_mode mode) +{ + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + + /* Reset MSM HW ep parameters for subsequent uses */ + if (mode == USB_EP_NONE) + memset(&mdwc->hw_eps[dep->number], 0, + sizeof(mdwc->hw_eps[dep->number])); + + mdwc->hw_eps[dep->number].mode = mode; + + return 0; +} +EXPORT_SYMBOL(msm_ep_set_mode); + +#endif /* (CONFIG_USB_DWC3_GADGET) || (CONFIG_USB_DWC3_DUAL_ROLE) */ + +static void dwc3_resume_work(struct work_struct *w); + +static void dwc3_restart_usb_work(struct work_struct *w) +{ + struct dwc3_msm *mdwc = container_of(w, struct dwc3_msm, + restart_usb_work); + unsigned int timeout = 50; + + if (atomic_read(&mdwc->in_lpm) || mdwc->dr_mode != USB_DR_MODE_OTG) { + dev_dbg(mdwc->dev, "%s failed!!!\n", __func__); + return; + } + + /* guard against concurrent VBUS handling */ + mdwc->in_restart = true; + + if (!mdwc->vbus_active) { + dev_dbg(mdwc->dev, "%s bailing out in disconnect\n", __func__); + mdwc->err_evt_seen = false; + mdwc->in_restart = false; + return; + } + + dbg_event(0xFF, "RestartUSB", 0); + /* Reset active USB connection */ + dwc3_resume_work(&mdwc->resume_work); + + /* Make sure disconnect is processed before sending connect */ + while (--timeout && !pm_runtime_suspended(mdwc->dev)) + msleep(20); + + if (!timeout) { + dev_dbg(mdwc->dev, + "Not in LPM after disconnect, forcing suspend...\n"); + dbg_event(0xFF, "ReStart:RT SUSP", + atomic_read(&mdwc->dev->power.usage_count)); + pm_runtime_suspend(mdwc->dev); + } + + mdwc->in_restart = false; + /* Force reconnect only if cable is still connected */ + if (mdwc->vbus_active) + dwc3_resume_work(&mdwc->resume_work); + + mdwc->err_evt_seen = false; + flush_delayed_work(&mdwc->sm_work); +} + +/* + * Config Global Distributed Switch Controller (GDSC) + * to support controller power collapse + */ +static int dwc3_msm_config_gdsc(struct dwc3_msm *mdwc, int on) +{ + int ret; + + if (IS_ERR_OR_NULL(mdwc->dwc3_gdsc)) + return -EPERM; + + if (on) { + ret = regulator_enable(mdwc->dwc3_gdsc); + if (ret) { + dev_err(mdwc->dev, "unable to enable usb3 gdsc\n"); + return ret; + } + + qcom_clk_set_flags(mdwc->core_clk, CLKFLAG_RETAIN_MEM); + } else { + qcom_clk_set_flags(mdwc->core_clk, CLKFLAG_NORETAIN_MEM); + ret = regulator_disable(mdwc->dwc3_gdsc); + if (ret) { + dev_err(mdwc->dev, "unable to disable usb3 gdsc\n"); + return ret; + } + } + + return ret; +} + +static int dwc3_msm_link_clk_reset(struct dwc3_msm *mdwc, bool assert) +{ + int ret = 0; + + if (assert) { + disable_irq(mdwc->wakeup_irq[PWR_EVNT_IRQ].irq); + /* Using asynchronous block reset to the hardware */ + dev_dbg(mdwc->dev, "block_reset ASSERT\n"); + clk_disable_unprepare(mdwc->utmi_clk); + clk_disable_unprepare(mdwc->sleep_clk); + clk_disable_unprepare(mdwc->core_clk); + clk_disable_unprepare(mdwc->iface_clk); + ret = reset_control_assert(mdwc->core_reset); + if (ret) + dev_err(mdwc->dev, "dwc3 core_reset assert failed\n"); + } else { + dev_dbg(mdwc->dev, "block_reset DEASSERT\n"); + ret = reset_control_deassert(mdwc->core_reset); + if (ret) + dev_err(mdwc->dev, "dwc3 core_reset deassert failed\n"); + ndelay(200); + clk_prepare_enable(mdwc->iface_clk); + clk_prepare_enable(mdwc->core_clk); + clk_prepare_enable(mdwc->sleep_clk); + clk_prepare_enable(mdwc->utmi_clk); + enable_irq(mdwc->wakeup_irq[PWR_EVNT_IRQ].irq); + } + + return ret; +} + +static void dwc3_gsi_event_buf_alloc(struct dwc3 *dwc) +{ + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + struct dwc3_event_buffer *evt; + int i; + + if (!mdwc->num_gsi_event_buffers) + return; + + mdwc->gsi_ev_buff = devm_kzalloc(dwc->dev, + sizeof(*dwc->ev_buf) * mdwc->num_gsi_event_buffers, + GFP_KERNEL); + if (!mdwc->gsi_ev_buff) { + dev_err(dwc->dev, "can't allocate gsi_ev_buff\n"); + return; + } + + for (i = 0; i < mdwc->num_gsi_event_buffers; i++) { + + evt = devm_kzalloc(dwc->dev, sizeof(*evt), GFP_KERNEL); + if (!evt) + return; + evt->dwc = dwc; + evt->length = DWC3_EVENT_BUFFERS_SIZE; + evt->buf = dma_alloc_coherent(dwc->sysdev, + DWC3_EVENT_BUFFERS_SIZE, + &evt->dma, GFP_KERNEL); + if (!evt->buf) { + dev_err(dwc->dev, + "can't allocate gsi_evt_buf(%d)\n", i); + return; + } + mdwc->gsi_ev_buff[i] = evt; + } + /* + * Set-up dummy buffer to use as doorbell while IPA GSI + * connection is in progress. + */ + mdwc->dummy_gsi_db_dma = dma_map_single(dwc->sysdev, + &mdwc->dummy_gsi_db, + sizeof(mdwc->dummy_gsi_db), + DMA_FROM_DEVICE); + + if (dma_mapping_error(dwc->sysdev, mdwc->dummy_gsi_db_dma)) { + dev_err(dwc->dev, "failed to map dummy doorbell buffer\n"); + mdwc->dummy_gsi_db_dma = (dma_addr_t)NULL; + } +} + +static void dwc3_msm_switch_utmi(struct dwc3_msm *mdwc, int enable) +{ + u32 reg; + + dwc3_msm_write_reg(mdwc->base, QSCRATCH_GENERAL_CFG, + dwc3_msm_read_reg(mdwc->base, + QSCRATCH_GENERAL_CFG) + | PIPE_UTMI_CLK_DIS); + + udelay(5); + + reg = dwc3_msm_read_reg(mdwc->base, QSCRATCH_GENERAL_CFG); + if (enable) + reg |= (PIPE_UTMI_CLK_SEL | PIPE3_PHYSTATUS_SW); + else + reg &= ~(PIPE_UTMI_CLK_SEL | PIPE3_PHYSTATUS_SW); + dwc3_msm_write_reg(mdwc->base, QSCRATCH_GENERAL_CFG, reg); + + udelay(5); + + dwc3_msm_write_reg(mdwc->base, QSCRATCH_GENERAL_CFG, + dwc3_msm_read_reg(mdwc->base, + QSCRATCH_GENERAL_CFG) + & ~PIPE_UTMI_CLK_DIS); +} + +static void dwc3_msm_set_clk_sel(struct dwc3_msm *mdwc) +{ + /* + * Below sequence is used when controller is working without + * having ssphy and only USB high/full speed is supported. + */ + if (dwc3_msm_get_max_speed(mdwc) == USB_SPEED_HIGH || + dwc3_msm_get_max_speed(mdwc) == USB_SPEED_FULL) + dwc3_msm_switch_utmi(mdwc, 1); +} + +void dwc3_msm_notify_event(struct dwc3 *dwc, + enum dwc3_notify_event event, unsigned int value) +{ + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + struct dwc3_event_buffer *evt; + u32 reg; + int i; + + switch (event) { + case DWC3_CONTROLLER_ERROR_EVENT: + dev_info(mdwc->dev, + "DWC3_CONTROLLER_ERROR_EVENT received\n"); + + dwc3_msm_write_reg(mdwc->base, DWC3_DEVTEN, 0x00); + + /* prevent core from generating interrupts until recovery */ + reg = dwc3_msm_read_reg(mdwc->base, DWC3_GCTL); + reg |= DWC3_GCTL_CORESOFTRESET; + dwc3_msm_write_reg(mdwc->base, DWC3_GCTL, reg); + + /* + * If core could not recover after MAX_ERROR_RECOVERY_TRIES + * skip the restart USB work and keep the core in softreset + * state + */ + if (mdwc->retries_on_error < MAX_ERROR_RECOVERY_TRIES) + schedule_work(&mdwc->restart_usb_work); + break; + case DWC3_CONTROLLER_CONNDONE_EVENT: + dev_dbg(mdwc->dev, "DWC3_CONTROLLER_CONNDONE_EVENT received\n"); + /* + * Add power event if the dbm indicates coming out of L1 by + * interrupt + */ + if (!mdwc->dbm_is_1p4) + dwc3_msm_write_reg_field(mdwc->base, + PWR_EVNT_IRQ_MASK_REG, + PWR_EVNT_LPM_OUT_L1_MASK, 1); + + atomic_set(&mdwc->in_lpm, 0); + break; + case DWC3_GSI_EVT_BUF_ALLOC: + dev_dbg(mdwc->dev, "DWC3_GSI_EVT_BUF_ALLOC\n"); + dwc3_gsi_event_buf_alloc(dwc); + break; + case DWC3_CONTROLLER_PULLUP_ENTER: + dev_dbg(mdwc->dev, "DWC3_CONTROLLER_PULLUP_ENTER %d\n", value); + /* ignore pullup when role switch from device to host */ + if (mdwc->vbus_active) + usb_redriver_gadget_pullup_enter(mdwc->redriver, value); + break; + case DWC3_CONTROLLER_PULLUP_EXIT: + dev_dbg(mdwc->dev, "DWC3_CONTROLLER_PULLUP_EXIT %d\n", value); + /* ignore pullup when role switch from device to host */ + if (mdwc->vbus_active) + usb_redriver_gadget_pullup_exit(mdwc->redriver, value); + break; + case DWC3_GSI_EVT_BUF_SETUP: + dev_dbg(mdwc->dev, "DWC3_GSI_EVT_BUF_SETUP\n"); + for (i = 0; i < mdwc->num_gsi_event_buffers; i++) { + evt = mdwc->gsi_ev_buff[i]; + if (!evt) + break; + + dev_dbg(mdwc->dev, "Evt buf %pK dma %08llx length %d\n", + evt->buf, (unsigned long long) evt->dma, + evt->length); + memset(evt->buf, 0, evt->length); + evt->lpos = 0; + /* + * Primary event buffer is programmed with registers + * DWC3_GEVNT*(0). Hence use DWC3_GEVNT*(i+1) to + * program USB GSI related event buffer with DWC3 + * controller. + */ + dwc3_msm_write_reg(mdwc->base, DWC3_GEVNTADRLO((i+1)), + lower_32_bits(evt->dma)); + dwc3_msm_write_reg(mdwc->base, DWC3_GEVNTADRHI((i+1)), + DWC3_GEVNTADRHI_EVNTADRHI_GSI_EN( + DWC3_GEVENT_TYPE_GSI) | + DWC3_GEVNTADRHI_EVNTADRHI_GSI_IDX((i+1))); + dwc3_msm_write_reg(mdwc->base, DWC3_GEVNTSIZ((i+1)), + DWC3_GEVNTCOUNT_EVNTINTRPTMASK | + ((evt->length) & 0xffff)); + dwc3_msm_write_reg(mdwc->base, + DWC3_GEVNTCOUNT((i+1)), 0); + } + break; + case DWC3_GSI_EVT_BUF_CLEANUP: + dev_dbg(mdwc->dev, "DWC3_GSI_EVT_BUF_CLEANUP\n"); + if (!mdwc->gsi_ev_buff) + break; + + for (i = 0; i < mdwc->num_gsi_event_buffers; i++) { + evt = mdwc->gsi_ev_buff[i]; + evt->lpos = 0; + /* + * Primary event buffer is programmed with registers + * DWC3_GEVNT*(0). Hence use DWC3_GEVNT*(i+1) to + * program USB GSI related event buffer with DWC3 + * controller. + */ + dwc3_msm_write_reg(mdwc->base, + DWC3_GEVNTADRLO((i+1)), 0); + dwc3_msm_write_reg(mdwc->base, + DWC3_GEVNTADRHI((i+1)), 0); + dwc3_msm_write_reg(mdwc->base, DWC3_GEVNTSIZ((i+1)), + DWC3_GEVNTSIZ_INTMASK | + DWC3_GEVNTSIZ_SIZE((i+1))); + dwc3_msm_write_reg(mdwc->base, + DWC3_GEVNTCOUNT((i+1)), 0); + } + break; + case DWC3_GSI_EVT_BUF_FREE: + dev_dbg(mdwc->dev, "DWC3_GSI_EVT_BUF_FREE\n"); + if (!mdwc->gsi_ev_buff) + break; + + for (i = 0; i < mdwc->num_gsi_event_buffers; i++) { + evt = mdwc->gsi_ev_buff[i]; + if (evt) + dma_free_coherent(dwc->sysdev, evt->length, + evt->buf, evt->dma); + } + if (mdwc->dummy_gsi_db_dma) { + dma_unmap_single(dwc->sysdev, mdwc->dummy_gsi_db_dma, + sizeof(mdwc->dummy_gsi_db), + DMA_FROM_DEVICE); + mdwc->dummy_gsi_db_dma = (dma_addr_t)NULL; + } + break; + case DWC3_GSI_EVT_BUF_CLEAR: + dev_dbg(mdwc->dev, "DWC3_GSI_EVT_BUF_CLEAR\n"); + for (i = 0; i < mdwc->num_gsi_event_buffers; i++) { + reg = dwc3_msm_read_reg(mdwc->base, + DWC3_GEVNTCOUNT((i+1))); + reg &= DWC3_GEVNTCOUNT_MASK; + dwc3_msm_write_reg(mdwc->base, + DWC3_GEVNTCOUNT((i+1)), reg); + dbg_log_string("remaining EVNTCOUNT(%d)=%d", i+1, reg); + } + break; + case DWC3_CONTROLLER_NOTIFY_DISABLE_UPDXFER: + dwc3_msm_dbm_disable_updxfer(dwc, value); + break; + case DWC3_CONTROLLER_NOTIFY_CLEAR_DB: + dev_dbg(mdwc->dev, "DWC3_CONTROLLER_NOTIFY_CLEAR_DB\n"); + if (mdwc->gsi_reg) { + dwc3_msm_write_reg_field(mdwc->base, + GSI_GENERAL_CFG_REG(mdwc->gsi_reg), + BLOCK_GSI_WR_GO_MASK, true); + dwc3_msm_write_reg_field(mdwc->base, + GSI_GENERAL_CFG_REG(mdwc->gsi_reg), + GSI_EN_MASK, 0); + } + break; + default: + dev_dbg(mdwc->dev, "unknown dwc3 event\n"); + break; + } +} + +static void dwc3_msm_block_reset(struct dwc3_msm *mdwc, bool core_reset) +{ + int ret = 0; + + if (core_reset) { + ret = dwc3_msm_link_clk_reset(mdwc, 1); + if (ret) + return; + + usleep_range(1000, 1200); + ret = dwc3_msm_link_clk_reset(mdwc, 0); + if (ret) + return; + + usleep_range(10000, 12000); + } + + /* Reset the DBM */ + msm_dbm_write_reg_field(mdwc, DBM_SOFT_RESET, DBM_SFT_RST_MASK, 1); + usleep_range(1000, 1200); + msm_dbm_write_reg_field(mdwc, DBM_SOFT_RESET, DBM_SFT_RST_MASK, 0); + + /* enable DBM */ + dwc3_msm_write_reg_field(mdwc->base, QSCRATCH_GENERAL_CFG, + DBM_EN_MASK, 0x1); + if (!mdwc->dbm_is_1p4) { + msm_dbm_write_reg(mdwc, DBM_DATA_FIFO_ADDR_EN, 0xFF); + msm_dbm_write_reg(mdwc, DBM_DATA_FIFO_SIZE_EN, 0xFF); + } +} + +static void mdwc3_dis_sending_cm_l1(struct dwc3_msm *mdwc) +{ + u32 val; + + val = dwc3_msm_read_reg(mdwc->base, DWC3_GUSB2PHYCFG(0)); + dwc3_msm_write_reg(mdwc->base, DWC3_GUSB2PHYCFG(0), + val | DWC3_GUSB2PHYCFG_SUSPHY); +} + +static void dwc3_en_sleep_mode(struct dwc3_msm *mdwc) +{ + u32 reg; + + reg = dwc3_msm_read_reg(mdwc->base, DWC3_GUSB2PHYCFG(0)); + reg |= DWC3_GUSB2PHYCFG_ENBLSLPM; + dwc3_msm_write_reg(mdwc->base, DWC3_GUSB2PHYCFG(0), reg); + + reg = dwc3_msm_read_reg(mdwc->base, DWC3_GUCTL1); + reg |= DWC3_GUCTL1_L1_SUSP_THRLD_EN_FOR_HOST; + dwc3_msm_write_reg(mdwc->base, DWC3_GUCTL1, reg); +} + +static void dwc3_dis_sleep_mode(struct dwc3_msm *mdwc) +{ + u32 reg; + + reg = dwc3_msm_read_reg(mdwc->base, DWC3_GUSB2PHYCFG(0)); + reg &= ~DWC3_GUSB2PHYCFG_ENBLSLPM; + dwc3_msm_write_reg(mdwc->base, DWC3_GUSB2PHYCFG(0), reg); + + reg = dwc3_msm_read_reg(mdwc->base, DWC3_GUCTL1); + reg &= ~DWC3_GUCTL1_L1_SUSP_THRLD_EN_FOR_HOST; + dwc3_msm_write_reg(mdwc->base, DWC3_GUCTL1, reg); +} + + +/* Force Gen1 speed on Gen2 controller if required */ +static void dwc3_force_gen1(struct dwc3_msm *mdwc) +{ + struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3); + + if (mdwc->force_gen1 && DWC3_IP_IS(DWC31)) + dwc3_msm_write_reg_field(mdwc->base, DWC3_LLUCTL, DWC3_LLUCTL_FORCE_GEN1, 1); +} + +static void dwc3_msm_power_collapse_por(struct dwc3_msm *mdwc) +{ + struct dwc3 *dwc = NULL; + u32 val; + + if (!mdwc->dwc3) + return; + + dwc = platform_get_drvdata(mdwc->dwc3); + + /* Get initial P3 status and enable IN_P3 event */ + if (mdwc->ip == DWC31_IP) + val = dwc3_msm_read_reg_field(mdwc->base, + DWC31_LINK_GDBGLTSSM, + DWC3_GDBGLTSSM_LINKSTATE_MASK); + else + val = dwc3_msm_read_reg_field(mdwc->base, + DWC3_GDBGLTSSM, DWC3_GDBGLTSSM_LINKSTATE_MASK); + atomic_set(&mdwc->in_p3, val == DWC3_LINK_STATE_U3); + dwc3_msm_write_reg_field(mdwc->base, PWR_EVNT_IRQ_MASK_REG, + PWR_EVNT_POWERDOWN_IN_P3_MASK, 1); + + /* Set the core in host mode if it was in host mode during pm_suspend */ + if (mdwc->in_host_mode) { + dwc3_msm_write_reg_field(mdwc->base, DWC3_GCTL, + DWC3_GCTL_PRTCAPDIR(DWC3_GCTL_PRTCAP_OTG), + DWC3_GCTL_PRTCAPDIR(DWC3_GCTL_PRTCAP_HOST)); + if (!dwc->dis_enblslpm_quirk) + dwc3_en_sleep_mode(mdwc); + + if (mdwc->dis_sending_cm_l1_quirk) + mdwc3_dis_sending_cm_l1(mdwc); + } + + dwc3_force_gen1(mdwc); +} + +static int dwc3_msm_prepare_suspend(struct dwc3_msm *mdwc, bool ignore_p3_state) +{ + unsigned long timeout; + u32 reg = 0; + + if (!mdwc->in_host_mode && !mdwc->in_device_mode) + return 0; + + if (!ignore_p3_state && (dwc3_msm_is_superspeed(mdwc) && + !mdwc->in_restart)) { + if (!atomic_read(&mdwc->in_p3)) { + dev_err(mdwc->dev, "Not in P3,aborting LPM sequence\n"); + return -EBUSY; + } + } + + /* Clear previous L2 events */ + dwc3_msm_write_reg(mdwc->base, PWR_EVNT_IRQ_STAT_REG, + PWR_EVNT_LPM_IN_L2_MASK | PWR_EVNT_LPM_OUT_L2_MASK); + + /* Prepare HSPHY for suspend */ + reg = dwc3_msm_read_reg(mdwc->base, DWC3_GUSB2PHYCFG(0)); + dwc3_msm_write_reg(mdwc->base, DWC3_GUSB2PHYCFG(0), + reg | DWC3_GUSB2PHYCFG_ENBLSLPM | DWC3_GUSB2PHYCFG_SUSPHY); + + /* Wait for PHY to go into L2 */ + timeout = jiffies + msecs_to_jiffies(5); + while (!time_after(jiffies, timeout)) { + reg = dwc3_msm_read_reg(mdwc->base, PWR_EVNT_IRQ_STAT_REG); + if (reg & PWR_EVNT_LPM_IN_L2_MASK) + break; + } + if (!(reg & PWR_EVNT_LPM_IN_L2_MASK)) + dev_err(mdwc->dev, "could not transition HS PHY to L2\n"); + + /* Clear L2 event bit */ + dwc3_msm_write_reg(mdwc->base, PWR_EVNT_IRQ_STAT_REG, + PWR_EVNT_LPM_IN_L2_MASK); + + return 0; +} + +static void dwc3_set_phy_speed_flags(struct dwc3_msm *mdwc) +{ + struct dwc3 *dwc; + int i, num_ports; + u32 reg; + + if (!mdwc->dwc3) + return; + + dwc = platform_get_drvdata(mdwc->dwc3); + + mdwc->hs_phy->flags &= ~(PHY_HSFS_MODE | PHY_LS_MODE); + if (mdwc->in_host_mode) { + reg = dwc3_msm_read_reg(mdwc->base, USB3_HCSPARAMS1); + num_ports = HCS_MAX_PORTS(reg); + for (i = 0; i < num_ports; i++) { + reg = dwc3_msm_read_reg(mdwc->base, + USB3_PORTSC + i*0x10); + if (reg & PORT_CONNECT) { + if (DEV_HIGHSPEED(reg) || DEV_FULLSPEED(reg)) + mdwc->hs_phy->flags |= PHY_HSFS_MODE; + else if (DEV_LOWSPEED(reg)) + mdwc->hs_phy->flags |= PHY_LS_MODE; + } + } + } else if (mdwc->drd_state == DRD_STATE_PERIPHERAL_SUSPEND) { + if (dwc->gadget->speed == USB_SPEED_HIGH || + dwc->gadget->speed == USB_SPEED_FULL) + mdwc->hs_phy->flags |= PHY_HSFS_MODE; + else if (dwc->gadget->speed == USB_SPEED_LOW) + mdwc->hs_phy->flags |= PHY_LS_MODE; + } +} + +static void dwc3_set_ssphy_orientation_flag(struct dwc3_msm *mdwc) +{ + union extcon_property_value val; + struct extcon_dev *edev = NULL; + unsigned int extcon_id; + int ret, orientation; + + mdwc->ss_phy->flags &= ~(PHY_LANE_A | PHY_LANE_B); + + if (mdwc->orientation_override) { + mdwc->ss_phy->flags |= mdwc->orientation_override; + } else if (usb_redriver_has_orientation(mdwc->redriver)) { + orientation = usb_redriver_get_orientation(mdwc->redriver); + if (orientation == ORIENTATION_CC1) + mdwc->ss_phy->flags |= PHY_LANE_A; + else + mdwc->ss_phy->flags |= PHY_LANE_B; + } else { + if (mdwc->extcon && mdwc->vbus_active && !mdwc->in_restart) { + extcon_id = EXTCON_USB; + edev = mdwc->extcon[mdwc->ext_idx].edev; + } else if (mdwc->extcon && mdwc->id_state == DWC3_ID_GROUND) { + extcon_id = EXTCON_USB_HOST; + edev = mdwc->extcon[mdwc->ext_idx].edev; + } + + if (edev && extcon_get_state(edev, extcon_id)) { + ret = extcon_get_property(edev, extcon_id, + EXTCON_PROP_USB_TYPEC_POLARITY, &val); + if (ret == 0) + mdwc->ss_phy->flags |= val.intval ? + PHY_LANE_B : PHY_LANE_A; + } + } + + dbg_event(0xFF, "ss_flag", mdwc->ss_phy->flags); +} + +static void msm_dwc3_perf_vote_update(struct dwc3_msm *mdwc, + bool perf_mode); + +static void configure_usb_wakeup_interrupt(struct dwc3_msm *mdwc, + struct usb_irq *uirq, unsigned int polarity, bool enable) +{ + if (uirq && enable && !uirq->enable) { + dbg_event(0xFF, "PDC_IRQ_EN", uirq->irq); + dbg_event(0xFF, "PDC_IRQ_POL", polarity); + /* clear any pending interrupt */ + irq_set_irqchip_state(uirq->irq, IRQCHIP_STATE_PENDING, 0); + irq_set_irq_type(uirq->irq, polarity); + enable_irq_wake(uirq->irq); + enable_irq(uirq->irq); + uirq->enable = true; + } + + if (uirq && !enable && uirq->enable) { + dbg_event(0xFF, "PDC_IRQ_DIS", uirq->irq); + disable_irq_wake(uirq->irq); + disable_irq_nosync(uirq->irq); + uirq->enable = false; + } +} + +static void enable_usb_pdc_interrupt(struct dwc3_msm *mdwc, bool enable) +{ + if (!enable) + goto disable_usb_irq; + + if (mdwc->hs_phy->flags & PHY_LS_MODE) { + /* + * According to eUSB2 spec, eDP line will be pulled high for remote + * wakeup scenario for LS connected device in host mode. On disconnect + * during bus-suspend case, irrespective of the speed of the connected + * device, both eDM and eDP line will be pulled high (XeSE1). + */ + if (mdwc->use_eusb2_phy) + configure_usb_wakeup_interrupt(mdwc, + &mdwc->wakeup_irq[DP_HS_PHY_IRQ], + IRQ_TYPE_EDGE_RISING, enable); + else + configure_usb_wakeup_interrupt(mdwc, + &mdwc->wakeup_irq[DM_HS_PHY_IRQ], + IRQ_TYPE_EDGE_FALLING, enable); + + } else if (mdwc->hs_phy->flags & PHY_HSFS_MODE) { + /* + * According to eUSB2 spec, eDM line will be pulled high for remote + * wakeup scenario for HS/FS connected device in host mode. On disconnect + * during bus-suspend case, irrespective of the speed of the connected + * device, both eDM and eDP line will be pulled high (XeSE1). + */ + if (mdwc->use_eusb2_phy) + configure_usb_wakeup_interrupt(mdwc, + &mdwc->wakeup_irq[DM_HS_PHY_IRQ], + IRQ_TYPE_EDGE_RISING, enable); + else + configure_usb_wakeup_interrupt(mdwc, + &mdwc->wakeup_irq[DP_HS_PHY_IRQ], + IRQ_TYPE_EDGE_FALLING, enable); + + } else { + /* When in host mode, with no device connected, set the HS + * to level high triggered. This is to ensure device connection + * is seen, if device pulls up DP before the suspend routine + * configures the PDC IRQs, leading it to miss the rising edge. + */ + configure_usb_wakeup_interrupt(mdwc, + &mdwc->wakeup_irq[DP_HS_PHY_IRQ], + mdwc->in_host_mode ? + (IRQF_TRIGGER_HIGH | IRQ_TYPE_LEVEL_HIGH) : + IRQ_TYPE_EDGE_RISING, true); + configure_usb_wakeup_interrupt(mdwc, + &mdwc->wakeup_irq[DM_HS_PHY_IRQ], + mdwc->in_host_mode ? + (IRQF_TRIGGER_HIGH | IRQ_TYPE_LEVEL_HIGH) : + IRQ_TYPE_EDGE_RISING, true); + } + + configure_usb_wakeup_interrupt(mdwc, + &mdwc->wakeup_irq[SS_PHY_IRQ], + IRQF_TRIGGER_HIGH | IRQ_TYPE_LEVEL_HIGH, enable); + return; + +disable_usb_irq: + configure_usb_wakeup_interrupt(mdwc, + &mdwc->wakeup_irq[DP_HS_PHY_IRQ], 0, enable); + configure_usb_wakeup_interrupt(mdwc, + &mdwc->wakeup_irq[DM_HS_PHY_IRQ], 0, enable); + configure_usb_wakeup_interrupt(mdwc, + &mdwc->wakeup_irq[SS_PHY_IRQ], 0, enable); +} + +static void configure_nonpdc_usb_interrupt(struct dwc3_msm *mdwc, + struct usb_irq *uirq, bool enable) +{ + if (uirq && enable && !uirq->enable) { + dbg_event(0xFF, "IRQ_EN", uirq->irq); + enable_irq_wake(uirq->irq); + enable_irq(uirq->irq); + uirq->enable = true; + } + + if (uirq && !enable && uirq->enable) { + dbg_event(0xFF, "IRQ_DIS", uirq->irq); + disable_irq_wake(uirq->irq); + disable_irq_nosync(uirq->irq); + uirq->enable = false; + } +} + +static void dwc3_msm_set_ss_pwr_events(struct dwc3_msm *mdwc, bool on) +{ + u32 irq_mask, irq_stat; + + irq_stat = dwc3_msm_read_reg(mdwc->base, PWR_EVNT_IRQ_STAT_REG); + + /* clear pending interrupts */ + dwc3_msm_write_reg(mdwc->base, PWR_EVNT_IRQ_STAT_REG, irq_stat); + + irq_mask = dwc3_msm_read_reg(mdwc->base, PWR_EVNT_IRQ_MASK_REG); + + if (on) + irq_mask |= (PWR_EVNT_POWERDOWN_OUT_P3_MASK | + PWR_EVNT_LPM_OUT_RX_ELECIDLE_IRQ_MASK); + else + irq_mask &= ~(PWR_EVNT_POWERDOWN_OUT_P3_MASK | + PWR_EVNT_LPM_OUT_RX_ELECIDLE_IRQ_MASK); + + dwc3_msm_write_reg(mdwc->base, PWR_EVNT_IRQ_MASK_REG, irq_mask); +} + +static int dwc3_msm_update_bus_bw(struct dwc3_msm *mdwc, enum bus_vote bv) +{ + int i, ret = 0; + unsigned int bv_index = mdwc->override_bus_vote ?: bv; + + dbg_event(0xFF, "bus_vote_start", bv); + + /* On some platforms SVS does not have separate vote. + * Vote for nominal if svs usecase does not exist. + * If the request is to set the bus_vote to _NONE, + * set it to _NONE irrespective of the requested vote + * from userspace. + */ + if (bv_index >= BUS_VOTE_MAX) + bv_index = BUS_VOTE_MAX - 1; + else if (bv_index < BUS_VOTE_NONE) + bv_index = BUS_VOTE_NONE; + + for (i = 0; i < ARRAY_SIZE(mdwc->icc_paths); i++) { + ret = icc_set_bw(mdwc->icc_paths[i], + bus_vote_values[bv_index][i].avg, + bus_vote_values[bv_index][i].peak); + if (ret) + dev_err(mdwc->dev, "bus bw voting path:%s bv:%d failed %d\n", + icc_path_names[i], bv_index, ret); + } + + dbg_event(0xFF, "bus_vote_end", bv_index); + + return ret; +} + +/** + * dwc3_clk_enable_disable - helper function for enabling or disabling clocks + * in dwc3_msm_resume() or dwc3_msm_suspend respectively. + * + * @mdwc: Pointer to the mdwc structure. + * @enable: enable/disable clocks + * + * Returns 0 on success otherwise negative errno. + */ +static int dwc3_clk_enable_disable(struct dwc3_msm *mdwc, bool enable, bool toggle_sleep) +{ + int ret = 0; + long core_clk_rate; + + if (!enable) + goto disable_bus_aggr_clk; + + /* Vote for TCXO while waking up USB HSPHY */ + ret = clk_prepare_enable(mdwc->xo_clk); + if (ret < 0) { + dev_err(mdwc->dev, "%s failed to vote TCXO buffer%d\n", + __func__, ret); + return ret; + } + if (toggle_sleep) { + ret = clk_prepare_enable(mdwc->sleep_clk); + if (ret < 0) { + dev_err(mdwc->dev, "%s: sleep_clk enable failed\n", + __func__); + goto disable_xo_clk; + } + } + + /* + * Enable clocks + * Turned ON iface_clk before core_clk due to FSM depedency. + */ + ret = clk_prepare_enable(mdwc->iface_clk); + if (ret < 0) { + dev_err(mdwc->dev, "%s: iface_clk enable failed\n", __func__); + goto disable_sleep_clk; + } + ret = clk_prepare_enable(mdwc->noc_aggr_clk); + if (ret < 0) { + dev_err(mdwc->dev, "%s: noc_aggr_clk enable failed\n", __func__); + goto disable_iface_clk; + } + + core_clk_rate = mdwc->core_clk_rate_disconnected; + if (mdwc->in_host_mode && mdwc->max_rh_port_speed == USB_SPEED_HIGH) + core_clk_rate = mdwc->core_clk_rate_hs; + else if (!(mdwc->lpm_flags & MDWC3_POWER_COLLAPSE)) + core_clk_rate = mdwc->core_clk_rate; + + dev_dbg(mdwc->dev, "%s: set core clk rate %ld\n", __func__, + core_clk_rate); + clk_set_rate(mdwc->core_clk, core_clk_rate); + ret = clk_prepare_enable(mdwc->core_clk); + if (ret < 0) { + dev_err(mdwc->dev, "%s: core_clk enable failed\n", __func__); + goto disable_noc_aggr_clk; + } + ret = clk_prepare_enable(mdwc->utmi_clk); + if (ret < 0) { + dev_err(mdwc->dev, "%s: utmi_clk enable failed\n", __func__); + goto disable_core_clk; + } + ret = clk_prepare_enable(mdwc->bus_aggr_clk); + if (ret < 0) { + dev_err(mdwc->dev, "%s: bus_aggr_clk enable failed\n", __func__); + goto disable_utmi_clk; + } + + return 0; + + /* Disable clocks */ +disable_bus_aggr_clk: + clk_disable_unprepare(mdwc->bus_aggr_clk); +disable_utmi_clk: + clk_disable_unprepare(mdwc->utmi_clk); +disable_core_clk: + clk_set_rate(mdwc->core_clk, 19200000); + clk_disable_unprepare(mdwc->core_clk); +disable_noc_aggr_clk: + clk_disable_unprepare(mdwc->noc_aggr_clk); +disable_iface_clk: + /* + * Disable iface_clk only after core_clk as core_clk has FSM + * depedency on iface_clk. Hence iface_clk should be turned off + * after core_clk is turned off. + */ + clk_disable_unprepare(mdwc->iface_clk); +disable_sleep_clk: + if (toggle_sleep) + clk_disable_unprepare(mdwc->sleep_clk); +disable_xo_clk: + /* USB PHY no more requires TCXO */ + clk_disable_unprepare(mdwc->xo_clk); + + return ret; +} + +static int dwc3_msm_suspend(struct dwc3_msm *mdwc, bool force_power_collapse) +{ + int ret; + struct dwc3 *dwc = NULL; + struct dwc3_event_buffer *evt; + struct usb_irq *uirq; + bool can_suspend_ssphy, no_active_ss; + + if (mdwc->dwc3) + dwc = platform_get_drvdata(mdwc->dwc3); + + mutex_lock(&mdwc->suspend_resume_mutex); + if (atomic_read(&mdwc->in_lpm)) { + dev_dbg(mdwc->dev, "%s: Already suspended\n", __func__); + mutex_unlock(&mdwc->suspend_resume_mutex); + return 0; + } + + cancel_delayed_work_sync(&mdwc->perf_vote_work); + msm_dwc3_perf_vote_update(mdwc, false); + if (dwc) { + if (!mdwc->in_host_mode) { + evt = dwc->ev_buf; + if ((evt->flags & DWC3_EVENT_PENDING)) { + dev_dbg(mdwc->dev, + "%s: %d device events pending, abort suspend\n", + __func__, evt->count / 4); + mutex_unlock(&mdwc->suspend_resume_mutex); + return -EBUSY; + } + } + + /* + * Check if device is not in CONFIGURED state + * then check controller state of L2 and break + * LPM sequence. Check this for device bus suspend case. + */ + if ((mdwc->dr_mode == USB_DR_MODE_OTG && + mdwc->drd_state == DRD_STATE_PERIPHERAL_SUSPEND) && + (dwc->gadget->state != USB_STATE_CONFIGURED)) { + pr_err("%s(): Trying to go in LPM with state:%d\n", + __func__, dwc->gadget->state); + pr_err("%s(): LPM is not performed.\n", __func__); + mutex_unlock(&mdwc->suspend_resume_mutex); + return -EBUSY; + } + } + + if (!mdwc->vbus_active && mdwc->dr_mode == USB_DR_MODE_OTG && + mdwc->drd_state == DRD_STATE_PERIPHERAL) { + /* + * In some cases, the pm_runtime_suspend may be called by + * usb_bam when there is pending lpm flag. However, if this is + * done when cable was disconnected and otg state has not + * yet changed to IDLE, then it means OTG state machine + * is running and we race against it. So cancel LPM for now, + * and OTG state machine will go for LPM later, after completing + * transition to IDLE state. + */ + dev_dbg(mdwc->dev, + "%s: cable disconnected while not in idle otg state\n", + __func__); + mutex_unlock(&mdwc->suspend_resume_mutex); + return -EBUSY; + } + + + + ret = dwc3_msm_prepare_suspend(mdwc, force_power_collapse); + if (ret) { + mutex_unlock(&mdwc->suspend_resume_mutex); + return ret; + } + + /* Disable core irq */ + if (mdwc->core_irq) + disable_irq(mdwc->core_irq); + + /* disable power event irq, hs and ss phy irq is used as wake up src */ + disable_irq(mdwc->wakeup_irq[PWR_EVNT_IRQ].irq); + + dwc3_set_phy_speed_flags(mdwc); + /* Suspend HS PHY */ + usb_phy_set_suspend(mdwc->hs_phy, 1); + + /* + * Synopsys Superspeed PHY does not support ss_phy_irq, so to detect + * any wakeup events in host mode PHY cannot be suspended. + * This Superspeed PHY can be suspended only in the following cases: + * 1. The core is not in host mode + * 2. A Highspeed device is connected but not a Superspeed device + */ + no_active_ss = (!mdwc->in_host_mode) || (mdwc->in_host_mode && + ((mdwc->hs_phy->flags & (PHY_HSFS_MODE | PHY_LS_MODE)) && + !dwc3_msm_is_superspeed(mdwc))); + can_suspend_ssphy = dwc3_msm_get_max_speed(mdwc) >= USB_SPEED_SUPER && + (!mdwc->use_pwr_event_for_wakeup || no_active_ss); + /* Suspend SS PHY */ + if (can_suspend_ssphy) { + if (mdwc->in_host_mode) { + u32 reg = dwc3_msm_read_reg(mdwc->base, + DWC3_GUSB3PIPECTL(0)); + + reg |= DWC3_GUSB3PIPECTL_DISRXDETU3; + dwc3_msm_write_reg(mdwc->base, DWC3_GUSB3PIPECTL(0), + reg); + } + /* indicate phy about SS mode */ + if (dwc3_msm_is_superspeed(mdwc)) + mdwc->ss_phy->flags |= DEVICE_IN_SS_MODE; + usb_phy_set_suspend(mdwc->ss_phy, 1); + mdwc->lpm_flags |= MDWC3_SS_PHY_SUSPEND; + } else if (mdwc->use_pwr_event_for_wakeup) { + dwc3_msm_set_ss_pwr_events(mdwc, true); + enable_irq(mdwc->wakeup_irq[PWR_EVNT_IRQ].irq); + } + + /* make sure above writes are completed before turning off clocks */ + wmb(); + + if (!(mdwc->in_host_mode || mdwc->in_device_mode) || + mdwc->in_restart || force_power_collapse) + mdwc->lpm_flags |= MDWC3_POWER_COLLAPSE; + + /* Disable clocks */ + dwc3_clk_enable_disable(mdwc, false, mdwc->lpm_flags & MDWC3_POWER_COLLAPSE); + + /* Perform controller power collapse */ + if (mdwc->lpm_flags & MDWC3_POWER_COLLAPSE) { + dev_dbg(mdwc->dev, "%s: power collapse\n", __func__); + dwc3_msm_config_gdsc(mdwc, 0); + } + + dwc3_msm_update_bus_bw(mdwc, BUS_VOTE_NONE); + + /* + * release wakeup source with timeout to defer system suspend to + * handle case where on USB cable disconnect, SUSPEND and DISCONNECT + * event is received. + */ + if (mdwc->lpm_to_suspend_delay) { + dev_dbg(mdwc->dev, "defer suspend with %d(msecs)\n", + mdwc->lpm_to_suspend_delay); + pm_wakeup_event(mdwc->dev, mdwc->lpm_to_suspend_delay); + } else { + pm_relax(mdwc->dev); + } + + atomic_set(&mdwc->in_lpm, 1); + + /* + * with DCP or during cable disconnect, we dont require wakeup + * using HS_PHY_IRQ or SS_PHY_IRQ. Hence enable wakeup only in + * case of host bus suspend and device bus suspend. + */ + if (mdwc->in_device_mode || mdwc->in_host_mode) { + if (mdwc->use_pdc_interrupts) { + enable_usb_pdc_interrupt(mdwc, true); + } else { + uirq = &mdwc->wakeup_irq[HS_PHY_IRQ]; + configure_nonpdc_usb_interrupt(mdwc, uirq, true); + uirq = &mdwc->wakeup_irq[SS_PHY_IRQ]; + configure_nonpdc_usb_interrupt(mdwc, uirq, true); + } + mdwc->lpm_flags |= MDWC3_ASYNC_IRQ_WAKE_CAPABILITY; + } + + dev_info(mdwc->dev, "DWC3 in low power mode\n"); + dbg_event(0xFF, "Ctl Sus", atomic_read(&mdwc->in_lpm)); + + /* kick_sm if it is waiting for lpm sequence to finish */ + if (test_and_clear_bit(WAIT_FOR_LPM, &mdwc->inputs)) + queue_delayed_work(mdwc->sm_usb_wq, &mdwc->sm_work, 0); + + mutex_unlock(&mdwc->suspend_resume_mutex); + + return 0; +} + +static int dwc3_msm_resume(struct dwc3_msm *mdwc) +{ + int ret; + struct dwc3 *dwc = NULL; + struct usb_irq *uirq; + + if (mdwc->dwc3) + dwc = platform_get_drvdata(mdwc->dwc3); + + dev_dbg(mdwc->dev, "%s: exiting lpm\n", __func__); + + /* + * If h/w exited LPM without any events, ensure + * h/w is reset before processing any new events. + */ + if (!mdwc->vbus_active && mdwc->id_state) + set_bit(WAIT_FOR_LPM, &mdwc->inputs); + + mutex_lock(&mdwc->suspend_resume_mutex); + if (!atomic_read(&mdwc->in_lpm)) { + dev_dbg(mdwc->dev, "%s: Already resumed\n", __func__); + mutex_unlock(&mdwc->suspend_resume_mutex); + return 0; + } + + pm_stay_awake(mdwc->dev); + + if (mdwc->in_host_mode && mdwc->max_rh_port_speed == USB_SPEED_HIGH) + dwc3_msm_update_bus_bw(mdwc, BUS_VOTE_SVS); + else + dwc3_msm_update_bus_bw(mdwc, mdwc->default_bus_vote); + + /* Restore controller power collapse */ + if (mdwc->lpm_flags & MDWC3_POWER_COLLAPSE) { + dev_dbg(mdwc->dev, "%s: exit power collapse\n", __func__); + ret = dwc3_msm_config_gdsc(mdwc, 1); + if (ret < 0) + goto error; + ret = reset_control_assert(mdwc->core_reset); + if (ret) + dev_err(mdwc->dev, "%s:core_reset assert failed\n", + __func__); + /* HW requires a short delay for reset to take place properly */ + usleep_range(1000, 1200); + ret = reset_control_deassert(mdwc->core_reset); + if (ret) + dev_err(mdwc->dev, "%s:core_reset deassert failed\n", + __func__); + } + + ret = dwc3_clk_enable_disable(mdwc, true, mdwc->lpm_flags & MDWC3_POWER_COLLAPSE); + if (ret < 0) { + /* Perform controller power collapse */ + if (mdwc->lpm_flags & MDWC3_POWER_COLLAPSE) + dwc3_msm_config_gdsc(mdwc, 0); + goto error; + } + + /* + * Disable any wakeup events that were enabled if pwr_event_irq + * is used as wakeup interrupt. + */ + if (mdwc->use_pwr_event_for_wakeup && + !(mdwc->lpm_flags & MDWC3_SS_PHY_SUSPEND)) { + disable_irq_nosync(mdwc->wakeup_irq[PWR_EVNT_IRQ].irq); + dwc3_msm_set_ss_pwr_events(mdwc, false); + } + + /* Resume SS PHY */ + if (dwc3_msm_get_max_speed(mdwc) >= USB_SPEED_SUPER && + mdwc->lpm_flags & MDWC3_SS_PHY_SUSPEND) { + dwc3_set_ssphy_orientation_flag(mdwc); + if (!mdwc->in_host_mode || mdwc->disable_host_ssphy_powerdown || + (mdwc->in_host_mode && mdwc->max_rh_port_speed != USB_SPEED_HIGH)) + usb_phy_set_suspend(mdwc->ss_phy, 0); + + mdwc->ss_phy->flags &= ~DEVICE_IN_SS_MODE; + mdwc->lpm_flags &= ~MDWC3_SS_PHY_SUSPEND; + + if (mdwc->in_host_mode) { + u32 reg = dwc3_msm_read_reg(mdwc->base, + DWC3_GUSB3PIPECTL(0)); + + reg &= ~DWC3_GUSB3PIPECTL_DISRXDETU3; + dwc3_msm_write_reg(mdwc->base, DWC3_GUSB3PIPECTL(0), + reg); + } + } + + mdwc->hs_phy->flags &= ~(PHY_HSFS_MODE | PHY_LS_MODE); + /* Resume HS PHY */ + usb_phy_set_suspend(mdwc->hs_phy, 0); + + /* Recover from controller power collapse */ + if (mdwc->lpm_flags & MDWC3_POWER_COLLAPSE) { + dev_dbg(mdwc->dev, "%s: exit power collapse\n", __func__); + + dwc3_msm_power_collapse_por(mdwc); + + mdwc->lpm_flags &= ~MDWC3_POWER_COLLAPSE; + } + + atomic_set(&mdwc->in_lpm, 0); + + /* enable power evt irq for IN P3 detection */ + enable_irq(mdwc->wakeup_irq[PWR_EVNT_IRQ].irq); + + /* Disable HSPHY auto suspend */ + dwc3_msm_write_reg(mdwc->base, DWC3_GUSB2PHYCFG(0), + dwc3_msm_read_reg(mdwc->base, DWC3_GUSB2PHYCFG(0)) & + ~DWC3_GUSB2PHYCFG_SUSPHY); + + /* Disable wakeup capable for HS_PHY IRQ & SS_PHY_IRQ if enabled */ + if (mdwc->lpm_flags & MDWC3_ASYNC_IRQ_WAKE_CAPABILITY) { + if (mdwc->use_pdc_interrupts) { + enable_usb_pdc_interrupt(mdwc, false); + } else { + uirq = &mdwc->wakeup_irq[HS_PHY_IRQ]; + configure_nonpdc_usb_interrupt(mdwc, uirq, false); + uirq = &mdwc->wakeup_irq[SS_PHY_IRQ]; + configure_nonpdc_usb_interrupt(mdwc, uirq, false); + } + mdwc->lpm_flags &= ~MDWC3_ASYNC_IRQ_WAKE_CAPABILITY; + } + + dev_info(mdwc->dev, "DWC3 exited from low power mode\n"); + + /* Enable core irq */ + if (mdwc->core_irq) + enable_irq(mdwc->core_irq); + + /* + * Handle other power events that could not have been handled during + * Low Power Mode + */ + dwc3_pwr_event_handler(mdwc); + + if (cpu_latency_qos_request_active(&mdwc->pm_qos_req_dma)) + schedule_delayed_work(&mdwc->perf_vote_work, + msecs_to_jiffies(1000 * PM_QOS_SAMPLE_SEC)); + + dwc3_msm_set_clk_sel(mdwc); + + dbg_event(0xFF, "Ctl Res", atomic_read(&mdwc->in_lpm)); + mutex_unlock(&mdwc->suspend_resume_mutex); + + return 0; + +error: + dwc3_msm_update_bus_bw(mdwc, BUS_VOTE_NONE); + pm_relax(mdwc->dev); + clear_bit(WAIT_FOR_LPM, &mdwc->inputs); + mutex_unlock(&mdwc->suspend_resume_mutex); + return ret; +} + +/** + * dwc3_ext_event_notify - callback to handle events from external transceiver + * + * Returns 0 on success + */ +static void dwc3_ext_event_notify(struct dwc3_msm *mdwc) +{ + /* Flush processing any pending events before handling new ones */ + flush_delayed_work(&mdwc->sm_work); + + dbg_log_string("enter: mdwc->inputs:%lx hs_phy_flags:%x\n", + mdwc->inputs, mdwc->hs_phy->flags); + if (mdwc->id_state == DWC3_ID_FLOAT) { + dbg_log_string("XCVR: ID set\n"); + set_bit(ID, &mdwc->inputs); + } else { + dbg_log_string("XCVR: ID clear\n"); + clear_bit(ID, &mdwc->inputs); + } + + if (mdwc->vbus_active && !mdwc->in_restart) { + if (mdwc->hs_phy->flags & EUD_SPOOF_DISCONNECT) { + dbg_log_string("XCVR: BSV clear\n"); + clear_bit(B_SESS_VLD, &mdwc->inputs); + } else { + dbg_log_string("XCVR: BSV set\n"); + set_bit(B_SESS_VLD, &mdwc->inputs); + } + } else { + dbg_log_string("XCVR: BSV clear\n"); + clear_bit(B_SESS_VLD, &mdwc->inputs); + } + + if (mdwc->suspend) { + dbg_log_string("XCVR: SUSP set\n"); + set_bit(B_SUSPEND, &mdwc->inputs); + } else { + dbg_log_string("XCVR: SUSP clear\n"); + clear_bit(B_SUSPEND, &mdwc->inputs); + } + + if (mdwc->check_eud_state && mdwc->vbus_active) { + mdwc->hs_phy->flags &= + ~(EUD_SPOOF_CONNECT | EUD_SPOOF_DISCONNECT); + dbg_log_string("eud: state:%d active:%d hs_phy_flags:0x%x\n", + mdwc->check_eud_state, mdwc->eud_active, + mdwc->hs_phy->flags); + if (mdwc->eud_active) { + mdwc->hs_phy->flags |= EUD_SPOOF_CONNECT; + dbg_log_string("EUD: XCVR: BSV set\n"); + set_bit(B_SESS_VLD, &mdwc->inputs); + } else { + mdwc->hs_phy->flags |= EUD_SPOOF_DISCONNECT; + dbg_log_string("EUD: XCVR: BSV clear\n"); + clear_bit(B_SESS_VLD, &mdwc->inputs); + } + + mdwc->check_eud_state = false; + } + + + dbg_log_string("eud: state:%d active:%d hs_phy_flags:0x%x\n", + mdwc->check_eud_state, mdwc->eud_active, mdwc->hs_phy->flags); + + /* handle case of USB cable disconnect after USB spoof disconnect */ + if (!mdwc->vbus_active && + (mdwc->hs_phy->flags & EUD_SPOOF_DISCONNECT)) { + mdwc->hs_phy->flags &= ~EUD_SPOOF_DISCONNECT; + mdwc->hs_phy->flags |= PHY_SUS_OVERRIDE; + usb_phy_set_suspend(mdwc->hs_phy, 1); + mdwc->hs_phy->flags &= ~PHY_SUS_OVERRIDE; + return; + } + + if (dwc3_msm_get_max_speed(mdwc) >= USB_SPEED_SUPER) { + if (mdwc->vbus_active || mdwc->id_state == DWC3_ID_GROUND) { + dwc3_set_ssphy_orientation_flag(mdwc); + usb_redriver_notify_connect(mdwc->redriver, + mdwc->ss_phy->flags & PHY_LANE_A ? + ORIENTATION_CC1 : ORIENTATION_CC2); + } else { + usb_redriver_notify_disconnect(mdwc->redriver); + } + } + + dbg_log_string("exit: mdwc->inputs:%lx\n", mdwc->inputs); + queue_delayed_work(mdwc->sm_usb_wq, &mdwc->sm_work, 0); +} + +static void dwc3_resume_work(struct work_struct *w) +{ + struct dwc3_msm *mdwc = container_of(w, struct dwc3_msm, resume_work); + struct dwc3 *dwc = NULL; + union extcon_property_value val; + unsigned int extcon_id; + struct extcon_dev *edev = NULL; + const char *edev_name; + char *eud_str; + int ret = 0; + + if (mdwc->dwc3) + dwc = platform_get_drvdata(mdwc->dwc3); + + dev_dbg(mdwc->dev, "%s: dwc3 resume work\n", __func__); + dbg_log_string("resume_work: ext_idx:%d\n", mdwc->ext_idx); + if (mdwc->extcon && mdwc->vbus_active && !mdwc->in_restart) { + extcon_id = EXTCON_USB; + edev = mdwc->extcon[mdwc->ext_idx].edev; + } else if (mdwc->extcon && mdwc->id_state == DWC3_ID_GROUND) { + extcon_id = EXTCON_USB_HOST; + edev = mdwc->extcon[mdwc->ext_idx].edev; + } + + if (edev) { + edev_name = extcon_get_edev_name(edev); + dbg_log_string("edev:%s\n", edev_name); + /* Skip querying speed and cc_state for EUD edev */ + eud_str = strnstr(edev_name, "eud", strlen(edev_name)); + if (eud_str) + goto skip_update; + } + + /* + * Do not override speed for consistency as if not present, then + * there is a chance w/ 4LN DP USB data disable case for the DCFG + * programmed w/ SSUSB w/o QMP PHY initialized. Functionally + * DP mode will still operate as should. + */ + if (!mdwc->ss_release_called) + dwc3_msm_set_max_speed(mdwc, mdwc->max_hw_supp_speed); + if (edev && extcon_get_state(edev, extcon_id)) { + ret = extcon_get_property(edev, extcon_id, + EXTCON_PROP_USB_SS, &val); + + if (!ret && val.intval == 0) + dwc3_msm_set_max_speed(mdwc, USB_SPEED_HIGH); + } + + if (dwc3_msm_get_max_speed(mdwc) >= USB_SPEED_SUPER) + dwc3_set_ssphy_orientation_flag(mdwc); + +skip_update: + dbg_log_string("max_speed:%d hw_supp_speed:%d override_speed:%d", + dwc3_msm_get_max_speed(mdwc), mdwc->max_hw_supp_speed, + mdwc->override_usb_speed); + if (mdwc->override_usb_speed && + mdwc->override_usb_speed <= dwc3_msm_get_max_speed(mdwc)) { + dwc3_msm_set_max_speed(mdwc, mdwc->override_usb_speed); + } + + dbg_event(0xFF, "speed", dwc3_msm_get_max_speed(mdwc)); + + /* + * Skip scheduling sm work if no work is pending. When boot-up + * with USB cable connected, usb state m/c is skipped to avoid + * any changes to dp/dm lines. As PM supsend and resume can + * happen while charger is connected, scheduling sm work during + * pm resume will reset the controller and phy which might impact + * dp/dm lines (and charging voltage). + */ + if (mdwc->drd_state == DRD_STATE_UNDEFINED && + !edev && !mdwc->resume_pending) + return; + /* + * exit LPM first to meet resume timeline from device side. + * resume_pending flag would prevent calling + * dwc3_msm_resume() in case we are here due to system + * wide resume without usb cable connected. This flag is set + * only in case of power event irq in lpm. + */ + if (mdwc->resume_pending) { + dwc3_msm_resume(mdwc); + mdwc->resume_pending = false; + } + + if (atomic_read(&mdwc->pm_suspended)) { + dbg_event(0xFF, "RWrk PMSus", 0); + /* let pm resume kick in resume work later */ + return; + } + dwc3_ext_event_notify(mdwc); +} + +static void dwc3_pwr_event_handler(struct dwc3_msm *mdwc) +{ + struct dwc3 *dwc = NULL; + u32 irq_stat, irq_clear = 0; + + if (!mdwc->dwc3) + return; + dwc = platform_get_drvdata(mdwc->dwc3); + + irq_stat = dwc3_msm_read_reg(mdwc->base, PWR_EVNT_IRQ_STAT_REG); + dev_dbg(mdwc->dev, "%s irq_stat=%X\n", __func__, irq_stat); + + /* Check for P3 events */ + if ((irq_stat & PWR_EVNT_POWERDOWN_OUT_P3_MASK) && + (irq_stat & PWR_EVNT_POWERDOWN_IN_P3_MASK)) { + u32 ls; + + /* Can't tell if entered or exit P3, so check LINKSTATE */ + if (!DWC3_IP_IS(DWC3)) + ls = dwc3_msm_read_reg_field(mdwc->base, + DWC31_LINK_GDBGLTSSM, + DWC3_GDBGLTSSM_LINKSTATE_MASK); + else + ls = dwc3_msm_read_reg_field(mdwc->base, + DWC3_GDBGLTSSM, DWC3_GDBGLTSSM_LINKSTATE_MASK); + dev_dbg(mdwc->dev, "%s link state = 0x%04x\n", __func__, ls); + atomic_set(&mdwc->in_p3, ls == DWC3_LINK_STATE_U3); + + irq_stat &= ~(PWR_EVNT_POWERDOWN_OUT_P3_MASK | + PWR_EVNT_POWERDOWN_IN_P3_MASK); + irq_clear |= (PWR_EVNT_POWERDOWN_OUT_P3_MASK | + PWR_EVNT_POWERDOWN_IN_P3_MASK); + } else if (irq_stat & PWR_EVNT_POWERDOWN_OUT_P3_MASK) { + atomic_set(&mdwc->in_p3, 0); + irq_stat &= ~PWR_EVNT_POWERDOWN_OUT_P3_MASK; + irq_clear |= PWR_EVNT_POWERDOWN_OUT_P3_MASK; + } else if (irq_stat & PWR_EVNT_POWERDOWN_IN_P3_MASK) { + atomic_set(&mdwc->in_p3, 1); + irq_stat &= ~PWR_EVNT_POWERDOWN_IN_P3_MASK; + irq_clear |= PWR_EVNT_POWERDOWN_IN_P3_MASK; + } + + /* Handle exit from L1 events */ + if (irq_stat & PWR_EVNT_LPM_OUT_L1_MASK) { + dev_dbg(mdwc->dev, "%s: handling PWR_EVNT_LPM_OUT_L1_MASK\n", + __func__); + if (!mdwc->in_host_mode) { + if (usb_gadget_wakeup(dwc->gadget)) + dev_err(mdwc->dev, "%s failed to take dwc out of L1\n", + __func__); + } + irq_stat &= ~PWR_EVNT_LPM_OUT_L1_MASK; + irq_clear |= PWR_EVNT_LPM_OUT_L1_MASK; + } + + /* Unhandled events */ + if (irq_stat) + dev_dbg(mdwc->dev, "%s: unexpected PWR_EVNT, irq_stat=%X\n", + __func__, irq_stat); + + dwc3_msm_write_reg(mdwc->base, PWR_EVNT_IRQ_STAT_REG, irq_clear); +} + +static irqreturn_t msm_dwc3_pwr_irq_thread(int irq, void *_mdwc) +{ + struct dwc3_msm *mdwc = _mdwc; + + if (atomic_read(&mdwc->in_lpm)) + dwc3_resume_work(&mdwc->resume_work); + else + dwc3_pwr_event_handler(mdwc); + + dbg_event(0xFF, "PWR IRQ", atomic_read(&mdwc->in_lpm)); + return IRQ_HANDLED; +} + +static irqreturn_t msm_dwc3_pwr_irq(int irq, void *data) +{ + struct dwc3_msm *mdwc = data; + + dev_dbg(mdwc->dev, "%s received\n", __func__); + /* + * When in Low Power Mode, can't read PWR_EVNT_IRQ_STAT_REG to acertain + * which interrupts have been triggered, as the clocks are disabled. + * Resume controller by waking up pwr event irq thread.After re-enabling + * clocks, dwc3_msm_resume will call dwc3_pwr_event_handler to handle + * all other power events. + */ + if (atomic_read(&mdwc->in_lpm)) { + /* set this to call dwc3_msm_resume() */ + mdwc->resume_pending = true; + return IRQ_WAKE_THREAD; + } + + dwc3_pwr_event_handler(mdwc); + return IRQ_HANDLED; +} + +static void dwc3_otg_sm_work(struct work_struct *w); + +static int dwc3_msm_get_clk_gdsc(struct dwc3_msm *mdwc) +{ + int ret; + + mdwc->dwc3_gdsc = devm_regulator_get(mdwc->dev, "USB3_GDSC"); + if (IS_ERR(mdwc->dwc3_gdsc)) { + if (PTR_ERR(mdwc->dwc3_gdsc) == -EPROBE_DEFER) + return PTR_ERR(mdwc->dwc3_gdsc); + mdwc->dwc3_gdsc = NULL; + } + + mdwc->xo_clk = devm_clk_get(mdwc->dev, "xo"); + if (IS_ERR(mdwc->xo_clk)) + mdwc->xo_clk = NULL; + clk_set_rate(mdwc->xo_clk, 19200000); + + mdwc->iface_clk = devm_clk_get(mdwc->dev, "iface_clk"); + if (IS_ERR(mdwc->iface_clk)) { + dev_err(mdwc->dev, "failed to get iface_clk\n"); + ret = PTR_ERR(mdwc->iface_clk); + return ret; + } + + /* + * DWC3 Core requires its CORE CLK (aka master / bus clk) to + * run at 125Mhz in SSUSB mode and >60MHZ for HSUSB mode. + * On newer platform it can run at 150MHz as well. + */ + mdwc->core_clk = devm_clk_get(mdwc->dev, "core_clk"); + if (IS_ERR(mdwc->core_clk)) { + dev_err(mdwc->dev, "failed to get core_clk\n"); + ret = PTR_ERR(mdwc->core_clk); + return ret; + } + + mdwc->core_reset = devm_reset_control_get(mdwc->dev, "core_reset"); + if (IS_ERR(mdwc->core_reset)) { + dev_err(mdwc->dev, "failed to get core_reset\n"); + return PTR_ERR(mdwc->core_reset); + } + + if (of_property_read_u32(mdwc->dev->of_node, "qcom,core-clk-rate", + (u32 *)&mdwc->core_clk_rate)) { + dev_err(mdwc->dev, "USB core-clk-rate is not present\n"); + return -EINVAL; + } + + mdwc->core_clk_rate = clk_round_rate(mdwc->core_clk, + mdwc->core_clk_rate); + dev_dbg(mdwc->dev, "USB core frequency = %ld\n", + mdwc->core_clk_rate); + + if (of_property_read_u32(mdwc->dev->of_node, "qcom,core-clk-rate-hs", + (u32 *)&mdwc->core_clk_rate_hs)) { + dev_dbg(mdwc->dev, "USB core-clk-rate-hs is not present\n"); + mdwc->core_clk_rate_hs = mdwc->core_clk_rate; + } + + if (of_property_read_u32(mdwc->dev->of_node, "qcom,core-clk-rate-disconnected", + (u32 *)&mdwc->core_clk_rate_disconnected)) { + dev_dbg(mdwc->dev, "USB core-clk-rate-disconnected is not present\n"); + mdwc->core_clk_rate_disconnected = mdwc->core_clk_rate; + } + + ret = clk_set_rate(mdwc->core_clk, mdwc->core_clk_rate_disconnected); + if (ret) + dev_err(mdwc->dev, "fail to set core_clk freq:%d\n", ret); + + mdwc->sleep_clk = devm_clk_get(mdwc->dev, "sleep_clk"); + if (IS_ERR(mdwc->sleep_clk)) { + dev_err(mdwc->dev, "failed to get sleep_clk\n"); + ret = PTR_ERR(mdwc->sleep_clk); + return ret; + } + + clk_set_rate(mdwc->sleep_clk, 32000); + mdwc->utmi_clk_rate = 19200000; + mdwc->utmi_clk = devm_clk_get(mdwc->dev, "utmi_clk"); + if (IS_ERR(mdwc->utmi_clk)) { + dev_err(mdwc->dev, "failed to get utmi_clk\n"); + ret = PTR_ERR(mdwc->utmi_clk); + return ret; + } + + clk_set_rate(mdwc->utmi_clk, mdwc->utmi_clk_rate); + mdwc->bus_aggr_clk = devm_clk_get(mdwc->dev, "bus_aggr_clk"); + if (IS_ERR(mdwc->bus_aggr_clk)) + mdwc->bus_aggr_clk = NULL; + + mdwc->noc_aggr_clk = devm_clk_get(mdwc->dev, "noc_aggr_clk"); + if (IS_ERR(mdwc->noc_aggr_clk)) + mdwc->noc_aggr_clk = NULL; + + if (of_property_match_string(mdwc->dev->of_node, + "clock-names", "cfg_ahb_clk") >= 0) { + mdwc->cfg_ahb_clk = devm_clk_get(mdwc->dev, "cfg_ahb_clk"); + if (IS_ERR(mdwc->cfg_ahb_clk)) { + ret = PTR_ERR(mdwc->cfg_ahb_clk); + mdwc->cfg_ahb_clk = NULL; + if (ret != -EPROBE_DEFER) + dev_err(mdwc->dev, + "failed to get cfg_ahb_clk ret %d\n", + ret); + return ret; + } + } + + return 0; +} + +static int dwc3_msm_id_notifier(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct dwc3 *dwc; + struct extcon_dev *edev = ptr; + struct extcon_nb *enb = container_of(nb, struct extcon_nb, id_nb); + struct dwc3_msm *mdwc = enb->mdwc; + enum dwc3_id_state id; + + if (!edev || !mdwc) + return NOTIFY_DONE; + + dwc = platform_get_drvdata(mdwc->dwc3); + + dbg_event(0xFF, "extcon idx", enb->idx); + + id = event ? DWC3_ID_GROUND : DWC3_ID_FLOAT; + + if (mdwc->id_state == id) + return NOTIFY_DONE; + + mdwc->ext_idx = enb->idx; + + dev_dbg(mdwc->dev, "host:%ld (id:%d) event received\n", event, id); + + mdwc->id_state = id; + dbg_event(0xFF, "id_state", mdwc->id_state); + queue_work(mdwc->dwc3_wq, &mdwc->resume_work); + + return NOTIFY_DONE; +} + + +static int dwc3_msm_vbus_notifier(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct dwc3 *dwc = NULL; + struct extcon_dev *edev = ptr; + struct extcon_nb *enb = container_of(nb, struct extcon_nb, vbus_nb); + struct dwc3_msm *mdwc = enb->mdwc; + char *eud_str; + const char *edev_name; + + if (!edev || !mdwc) + return NOTIFY_DONE; + + if (mdwc->dwc3) + dwc = platform_get_drvdata(mdwc->dwc3); + + dbg_event(0xFF, "extcon idx", enb->idx); + dev_dbg(mdwc->dev, "vbus:%ld event received\n", event); + edev_name = extcon_get_edev_name(edev); + dbg_log_string("edev:%s\n", edev_name); + + /* detect USB spoof disconnect/connect notification with EUD device */ + eud_str = strnstr(edev_name, "eud", strlen(edev_name)); + if (eud_str) { + if (mdwc->eud_active == event) + return NOTIFY_DONE; + mdwc->eud_active = event; + mdwc->check_eud_state = true; + } else { + if (mdwc->vbus_active == event) + return NOTIFY_DONE; + mdwc->vbus_active = event; + } + + mdwc->ext_idx = enb->idx; + if (mdwc->dr_mode == USB_DR_MODE_OTG && !mdwc->in_restart) + queue_work(mdwc->dwc3_wq, &mdwc->resume_work); + + return NOTIFY_DONE; +} + +static int dwc3_msm_extcon_register(struct dwc3_msm *mdwc) +{ + struct device_node *node = mdwc->dev->of_node; + struct extcon_dev *edev; + int idx, extcon_cnt, ret = 0; + bool check_vbus_state, check_id_state, phandle_found = false; + + extcon_cnt = of_count_phandle_with_args(node, "extcon", NULL); + if (extcon_cnt < 0) { + dev_err(mdwc->dev, "of_count_phandle_with_args failed\n"); + return -ENODEV; + } + + mdwc->extcon = devm_kcalloc(mdwc->dev, extcon_cnt, + sizeof(*mdwc->extcon), GFP_KERNEL); + if (!mdwc->extcon) + return -ENOMEM; + + for (idx = 0; idx < extcon_cnt; idx++) { + edev = extcon_get_edev_by_phandle(mdwc->dev, idx); + if (IS_ERR(edev) && PTR_ERR(edev) != -ENODEV) + return PTR_ERR(edev); + + if (IS_ERR_OR_NULL(edev)) + continue; + + check_vbus_state = check_id_state = true; + phandle_found = true; + + mdwc->extcon[idx].mdwc = mdwc; + mdwc->extcon[idx].edev = edev; + mdwc->extcon[idx].idx = idx; + + mdwc->extcon[idx].vbus_nb.notifier_call = + dwc3_msm_vbus_notifier; + ret = extcon_register_notifier(edev, EXTCON_USB, + &mdwc->extcon[idx].vbus_nb); + if (ret < 0) + check_vbus_state = false; + + mdwc->extcon[idx].id_nb.notifier_call = dwc3_msm_id_notifier; + ret = extcon_register_notifier(edev, EXTCON_USB_HOST, + &mdwc->extcon[idx].id_nb); + if (ret < 0) + check_id_state = false; + + /* Update initial VBUS/ID state */ + if (check_vbus_state && extcon_get_state(edev, EXTCON_USB)) + dwc3_msm_vbus_notifier(&mdwc->extcon[idx].vbus_nb, + true, edev); + else if (check_id_state && + extcon_get_state(edev, EXTCON_USB_HOST)) + dwc3_msm_id_notifier(&mdwc->extcon[idx].id_nb, + true, edev); + } + + if (!phandle_found) { + dev_err(mdwc->dev, "no extcon device found\n"); + return -ENODEV; + } + + return 0; +} + +static inline const char *dwc3_msm_usb_role_string(enum usb_role role) +{ + if (role < ARRAY_SIZE(usb_role_strings)) + return usb_role_strings[role]; + + return "Invalid"; +} + + +static enum usb_role dwc3_msm_usb_get_role(struct usb_role_switch *sw) +{ + struct dwc3_msm *mdwc = usb_role_switch_get_drvdata(sw); + enum usb_role role; + + if (mdwc->vbus_active) + role = USB_ROLE_DEVICE; + else if (mdwc->id_state == DWC3_ID_GROUND) + role = USB_ROLE_HOST; + else + role = USB_ROLE_NONE; + + dbg_log_string("get_role:%s\n", dwc3_msm_usb_role_string(role)); + return role; +} + +static int dwc3_msm_usb_set_role(struct usb_role_switch *sw, enum usb_role role) +{ + struct dwc3_msm *mdwc = usb_role_switch_get_drvdata(sw); + enum usb_role cur_role = USB_ROLE_NONE; + + mutex_lock(&mdwc->role_switch_mutex); + cur_role = dwc3_msm_usb_get_role(sw); + + switch (role) { + case USB_ROLE_HOST: + mdwc->vbus_active = false; + mdwc->id_state = DWC3_ID_GROUND; + break; + + case USB_ROLE_DEVICE: + mdwc->vbus_active = true; + mdwc->id_state = DWC3_ID_FLOAT; + break; + + case USB_ROLE_NONE: + mdwc->vbus_active = false; + mdwc->id_state = DWC3_ID_FLOAT; + break; + } + mutex_unlock(&mdwc->role_switch_mutex); + + dbg_log_string("cur_role:%s new_role:%s\n", dwc3_msm_usb_role_string(cur_role), + dwc3_msm_usb_role_string(role)); + + /* + * For boot up without USB cable connected case, don't check + * previous role value to allow resetting USB controller and + * PHYs. + */ + if (mdwc->drd_state != DRD_STATE_UNDEFINED && cur_role == role) { + dbg_log_string("no USB role change"); + return 0; + } + + dwc3_ext_event_notify(mdwc); + return 0; +} + +static ssize_t orientation_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct dwc3_msm *mdwc = dev_get_drvdata(dev); + + if (mdwc->orientation_override == PHY_LANE_A) + return scnprintf(buf, PAGE_SIZE, "A\n"); + if (mdwc->orientation_override == PHY_LANE_B) + return scnprintf(buf, PAGE_SIZE, "B\n"); + + return scnprintf(buf, PAGE_SIZE, "none\n"); +} + +static ssize_t orientation_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct dwc3_msm *mdwc = dev_get_drvdata(dev); + + if (sysfs_streq(buf, "A")) + mdwc->orientation_override = PHY_LANE_A; + else if (sysfs_streq(buf, "B")) + mdwc->orientation_override = PHY_LANE_B; + else + mdwc->orientation_override = 0; + + return count; +} + +static DEVICE_ATTR_RW(orientation); + +static ssize_t mode_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct dwc3_msm *mdwc = dev_get_drvdata(dev); + + if (mdwc->vbus_active) + return scnprintf(buf, PAGE_SIZE, "peripheral\n"); + if (mdwc->id_state == DWC3_ID_GROUND) + return scnprintf(buf, PAGE_SIZE, "host\n"); + + return scnprintf(buf, PAGE_SIZE, "none\n"); +} + +static ssize_t mode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct dwc3_msm *mdwc = dev_get_drvdata(dev); + + if (sysfs_streq(buf, "peripheral")) { + mdwc->vbus_active = true; + mdwc->id_state = DWC3_ID_FLOAT; + } else if (sysfs_streq(buf, "host")) { + mdwc->vbus_active = false; + mdwc->id_state = DWC3_ID_GROUND; + } else { + mdwc->vbus_active = false; + mdwc->id_state = DWC3_ID_FLOAT; + } + + dwc3_ext_event_notify(mdwc); + + return count; +} + +static DEVICE_ATTR_RW(mode); +static void msm_dwc3_perf_vote_work(struct work_struct *w); + +/* This node only shows max speed supported dwc3 and it should be + * same as what is reported in udc/core.c max_speed node. For current + * operating gadget speed, query current_speed node which is implemented + * by udc/core.c + */ +static ssize_t speed_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct dwc3_msm *mdwc = dev_get_drvdata(dev); + + return scnprintf(buf, PAGE_SIZE, "%s\n", + usb_speed_string(dwc3_msm_get_max_speed(mdwc))); +} + +static ssize_t speed_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct dwc3_msm *mdwc = dev_get_drvdata(dev); + enum usb_device_speed req_speed = USB_SPEED_UNKNOWN; + + /* DEVSPD can only have values SS(0x4), HS(0x0) and FS(0x1). + * per 3.20a data book. Allow only these settings. Note that, + * xhci does not support full-speed only mode. + */ + if (sysfs_streq(buf, "full")) + req_speed = USB_SPEED_FULL; + else if (sysfs_streq(buf, "high")) + req_speed = USB_SPEED_HIGH; + else if (sysfs_streq(buf, "super")) + req_speed = USB_SPEED_SUPER; + else if (sysfs_streq(buf, "ssp")) + req_speed = USB_SPEED_SUPER_PLUS; + else + return -EINVAL; + + /* restart usb only works for device mode. Perform manual cable + * plug in/out for host mode restart. + */ + if (req_speed != dwc3_msm_get_max_speed(mdwc) && + req_speed <= mdwc->max_hw_supp_speed) { + mdwc->override_usb_speed = req_speed; + schedule_work(&mdwc->restart_usb_work); + } else if (req_speed >= mdwc->max_hw_supp_speed) { + mdwc->override_usb_speed = 0; + } + + return count; +} +static DEVICE_ATTR_RW(speed); + +static ssize_t bus_vote_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct dwc3_msm *mdwc = dev_get_drvdata(dev); + + if (mdwc->override_bus_vote == BUS_VOTE_MIN) + return scnprintf(buf, PAGE_SIZE, "%s\n", + "Fixed bus vote: min"); + else if (mdwc->override_bus_vote == BUS_VOTE_MAX) + return scnprintf(buf, PAGE_SIZE, "%s\n", + "Fixed bus vote: max"); + else + return scnprintf(buf, PAGE_SIZE, "%s\n", + "Do not have fixed bus vote"); +} + +static ssize_t bus_vote_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct dwc3_msm *mdwc = dev_get_drvdata(dev); + bool bv_fixed = false; + enum bus_vote bv; + + if (sysfs_streq(buf, "min")) { + bv_fixed = true; + mdwc->override_bus_vote = BUS_VOTE_MIN; + } else if (sysfs_streq(buf, "max")) { + bv_fixed = true; + mdwc->override_bus_vote = BUS_VOTE_MAX; + } else if (sysfs_streq(buf, "cancel")) { + bv_fixed = false; + mdwc->override_bus_vote = BUS_VOTE_NONE; + } else { + dev_err(dev, "min/max/cancel only.\n"); + return -EINVAL; + } + + /* Update bus vote value only when not suspend */ + if (!atomic_read(&mdwc->in_lpm)) { + if (bv_fixed) + bv = mdwc->override_bus_vote; + else if (mdwc->in_host_mode + && (mdwc->max_rh_port_speed == USB_SPEED_HIGH)) + bv = BUS_VOTE_SVS; + else + bv = mdwc->default_bus_vote; + + dwc3_msm_update_bus_bw(mdwc, bv); + } + + return count; +} +static DEVICE_ATTR_RW(bus_vote); + +static struct attribute *dwc3_msm_attrs[] = { + &dev_attr_orientation.attr, + &dev_attr_mode.attr, + &dev_attr_speed.attr, + &dev_attr_bus_vote.attr, + NULL +}; +ATTRIBUTE_GROUPS(dwc3_msm); + +static int dwc_dpdm_cb(struct notifier_block *nb, unsigned long evt, void *p) +{ + struct dwc3_msm *mdwc = container_of(nb, struct dwc3_msm, dpdm_nb); + + switch (evt) { + case REGULATOR_EVENT_ENABLE: + dev_dbg(mdwc->dev, "%s: enable state:%s\n", __func__, + dwc3_drd_state_string(mdwc->drd_state)); + break; + case REGULATOR_EVENT_DISABLE: + dev_dbg(mdwc->dev, "%s: disable state:%s\n", __func__, + dwc3_drd_state_string(mdwc->drd_state)); + if (mdwc->drd_state == DRD_STATE_UNDEFINED) + queue_delayed_work(mdwc->sm_usb_wq, &mdwc->sm_work, 0); + break; + default: + dev_dbg(mdwc->dev, "%s: unknown event state:%s\n", __func__, + dwc3_drd_state_string(mdwc->drd_state)); + break; + } + + return NOTIFY_OK; +} + +static void dwc3_init_dbm(struct dwc3_msm *mdwc) +{ + const char *dbm_ver; + int ret; + + ret = of_property_read_string(mdwc->dev->of_node, "qcom,dbm-version", + &dbm_ver); + if (!ret && !strcmp(dbm_ver, "1.4")) { + mdwc->dbm_reg_table = dbm_1_4_regtable; + mdwc->dbm_num_eps = DBM_1_4_NUM_EP; + mdwc->dbm_is_1p4 = true; + } else { + /* default to v1.5 register layout */ + mdwc->dbm_reg_table = dbm_1_5_regtable; + mdwc->dbm_num_eps = DBM_1_5_NUM_EP; + } +} + +static int dwc3_start_stop_host(struct dwc3_msm *mdwc, bool start) +{ + if (start) { + dbg_log_string("start host mode"); + mdwc->id_state = DWC3_ID_GROUND; + mdwc->vbus_active = false; + } else { + dbg_log_string("stop_host_mode started"); + mdwc->id_state = DWC3_ID_FLOAT; + mdwc->vbus_active = false; + } + + dwc3_ext_event_notify(mdwc); + + if (!start) { + /* + * Block runtime PM during draining of the WQ, as if RPM suspend + * occurs, it can not queue work to sm_usb_wq. (as only chain + * queuing is allowed) + */ + pm_runtime_get(&mdwc->dwc3->dev); + + flush_work(&mdwc->resume_work); + flush_workqueue(mdwc->sm_usb_wq); + + pm_runtime_put(&mdwc->dwc3->dev); + while (test_bit(WAIT_FOR_LPM, &mdwc->inputs)) + msleep(20); + + dbg_log_string("stop_host_mode completed"); + if (mdwc->id_state == DWC3_ID_GROUND) + return -EBUSY; + } + + return 0; +} + +static int dwc3_start_stop_device(struct dwc3_msm *mdwc, bool start) +{ + if (start) { + dbg_log_string("start device mode"); + mdwc->id_state = DWC3_ID_FLOAT; + mdwc->vbus_active = true; + } else { + dbg_log_string("stop device mode"); + mdwc->id_state = DWC3_ID_FLOAT; + mdwc->vbus_active = false; + } + + dwc3_ext_event_notify(mdwc); + + if (!start) { + /* + * Block runtime PM during draining of the WQ, as if RPM suspend + * occurs, it can not queue work to sm_usb_wq. (as only chain + * queuing is allowed) + */ + pm_runtime_get(&mdwc->dwc3->dev); + + flush_work(&mdwc->resume_work); + flush_workqueue(mdwc->sm_usb_wq); + + pm_runtime_put(&mdwc->dwc3->dev); + + while (test_bit(WAIT_FOR_LPM, &mdwc->inputs)) + msleep(20); + + dbg_log_string("stop_device_mode completed"); + if (mdwc->vbus_active) + return -EBUSY; + } + + return 0; +} + +static void dwc3_msm_clear_dp_only_params(struct dwc3_msm *mdwc) +{ + mdwc->ss_release_called = false; + mdwc->ss_phy->flags &= ~PHY_DP_MODE; + dwc3_msm_set_max_speed(mdwc, USB_SPEED_UNKNOWN); +} + +static void dwc3_msm_set_dp_only_params(struct dwc3_msm *mdwc) +{ + usb_redriver_release_lanes(mdwc->redriver, 4); + + /* restart USB host mode into high speed */ + mdwc->ss_release_called = true; + dwc3_msm_set_max_speed(mdwc, USB_SPEED_HIGH); + mdwc->ss_phy->flags |= PHY_DP_MODE; +} + +int dwc3_msm_set_dp_mode(struct device *dev, bool dp_connected, int lanes) +{ + struct dwc3_msm *mdwc = dev_get_drvdata(dev); + int ret = 0; + + if (!mdwc || !mdwc->dwc3) { + dev_err(dev, "dwc3-msm is not initialized yet.\n"); + return -EAGAIN; + } + + /* flush any pending work */ + flush_work(&mdwc->resume_work); + flush_workqueue(mdwc->sm_usb_wq); + + if (!dp_connected) { + dbg_event(0xFF, "DP not connected", 0); + + mdwc->ss_release_called = false; + /* + * Special case for HOST mode, as we need to ensure that the DWC3 + * max speed is set before moving back into gadget/device mode. + * This is because, dwc3_gadget_init() will set the max speed + * for the USB gadget driver. + */ + if (mdwc->drd_state == DRD_STATE_HOST) + dwc3_start_stop_host(mdwc, false); + else + dwc3_msm_clear_dp_only_params(mdwc); + + mdwc->ss_phy->flags &= ~PHY_USB_DP_CONCURRENT_MODE; + + return 0; + } + + dbg_event(0xFF, "Set DP mode", lanes); + + if (lanes == 2) { + usb_redriver_release_lanes(mdwc->redriver, 2); + pm_runtime_get_sync(&mdwc->dwc3->dev); + mdwc->ss_phy->flags |= PHY_USB_DP_CONCURRENT_MODE; + pm_runtime_put_sync(&mdwc->dwc3->dev); + return 0; + } + + /* flush any pending work */ + flush_work(&mdwc->resume_work); + flush_workqueue(mdwc->sm_usb_wq); + + mutex_lock(&mdwc->role_switch_mutex); + if (mdwc->id_state == DWC3_ID_GROUND) { + /* stop USB host mode */ + ret = dwc3_start_stop_host(mdwc, false); + if (ret) + goto exit; + + dwc3_msm_set_dp_only_params(mdwc); + dwc3_start_stop_host(mdwc, true); + } else if (mdwc->vbus_active) { + /* stop USB device mode */ + ret = dwc3_start_stop_device(mdwc, false); + if (ret) + goto exit; + + dwc3_msm_set_dp_only_params(mdwc); + dwc3_start_stop_device(mdwc, true); + } else { + if (mdwc->in_host_mode || mdwc->in_device_mode) { + ret = -EBUSY; + goto exit; + } + + dbg_log_string("USB is not active.\n"); + dwc3_msm_set_dp_only_params(mdwc); + } + +exit: + mutex_unlock(&mdwc->role_switch_mutex); + return ret; +} +EXPORT_SYMBOL(dwc3_msm_set_dp_mode); + +int dwc3_msm_release_ss_lane(struct device *dev) +{ + return dwc3_msm_set_dp_mode(dev, true, 4); +} +EXPORT_SYMBOL(dwc3_msm_release_ss_lane); + +static int dwc3_msm_debug_init(struct dwc3_msm *mdwc) +{ + char ipc_log_ctx_name[40]; + + mdwc->dwc_ipc_log_ctxt = ipc_log_context_create(NUM_LOG_PAGES, + dev_name(mdwc->dev), 0); + if (!mdwc->dwc_ipc_log_ctxt) + dev_err(mdwc->dev, "Error getting ipc_log_ctxt\n"); + + snprintf(ipc_log_ctx_name, sizeof(ipc_log_ctx_name), + "%s.ep_events", dev_name(mdwc->dev)); + mdwc->dwc_dma_ipc_log_ctxt = ipc_log_context_create(2 * NUM_LOG_PAGES, + ipc_log_ctx_name, 0); + if (!mdwc->dwc_dma_ipc_log_ctxt) + dev_err(mdwc->dev, "Error getting ipc_log_ctxt for ep_events\n"); + + snprintf(ipc_log_ctx_name, sizeof(ipc_log_ctx_name), + "%s.trace", dev_name(mdwc->dev)); + dwc_trace_ipc_log_ctxt = ipc_log_context_create(3 * NUM_LOG_PAGES, + ipc_log_ctx_name, 0); + if (!dwc_trace_ipc_log_ctxt) + dev_err(mdwc->dev, "Error getting trace_ipc_log_ctxt for ep_events\n"); + + return 0; +} + +static void dwc3_host_complete(struct device *dev); +static int dwc3_host_prepare(struct device *dev); +static int dwc3_core_prepare(struct device *dev); +static void dwc3_core_complete(struct device *dev); + +static void dwc3_msm_override_pm_ops(struct device *dev, struct dev_pm_ops *pm_ops, + bool is_host) +{ + (*pm_ops) = (*dev->driver->pm); + pm_ops->prepare = is_host ? dwc3_host_prepare : dwc3_core_prepare; + pm_ops->complete = is_host ? dwc3_host_complete : dwc3_core_complete; + dev->driver->pm = pm_ops; +} + +static int dwc3_msm_core_init(struct dwc3_msm *mdwc) +{ + struct device_node *node = mdwc->dev->of_node, *dwc3_node; + struct dwc3 *dwc; + int ret = 0; + u32 val; + + if (mdwc->dwc3) + return 0; + + ret = usb_phy_init(mdwc->hs_phy); + if (ret) { + dev_err(mdwc->dev, "failed to init HS PHY\n"); + goto err; + } + + if (dwc3_msm_get_max_speed(mdwc) >= USB_SPEED_SUPER) { + ret = usb_phy_init(mdwc->ss_phy); + if (ret) { + dev_err(mdwc->dev, "failed to init SS PHY\n"); + goto err; + } + } + + /* Assumes dwc3 is the first DT child of dwc3-msm */ + dwc3_node = of_get_next_available_child(node, NULL); + if (!dwc3_node) { + dev_err(mdwc->dev, "failed to find dwc3 child\n"); + ret = -ENODEV; + goto err; + } + + ret = of_platform_populate(node, NULL, NULL, mdwc->dev); + if (ret) { + dev_err(mdwc->dev, + "failed to add create dwc3 core\n"); + of_node_put(dwc3_node); + goto err; + } + + mdwc->dwc3 = of_find_device_by_node(dwc3_node); + of_node_put(dwc3_node); + if (!mdwc->dwc3) { + dev_err(mdwc->dev, "failed to get dwc3 platform device\n"); + ret = -ENODEV; + goto depopulate; + } + + dwc = platform_get_drvdata(mdwc->dwc3); + if (!dwc) { + dev_err(mdwc->dev, "Failed to get dwc3 device\n"); + mdwc->dwc3 = NULL; + ret = -ENODEV; + goto depopulate; + } + + if (mdwc->override_usb_speed && + mdwc->override_usb_speed <= dwc3_msm_get_max_speed(mdwc)) { + dwc3_msm_set_max_speed(mdwc, mdwc->override_usb_speed); + } + + /* + * On platforms with SS PHY that do not support ss_phy_irq for wakeup + * events, use pwr_event_irq for wakeup events in superspeed mode. + */ + mdwc->use_pwr_event_for_wakeup = dwc3_msm_get_max_speed(mdwc) >= USB_SPEED_SUPER + && !mdwc->wakeup_irq[SS_PHY_IRQ].irq; + + if (of_property_read_bool(node, "qcom,host-poweroff-in-pm-suspend")) { + mdwc->host_poweroff_in_pm_suspend = true; + dev_info(mdwc->dev, "%s: Core power collapse on host PM suspend\n", + __func__); + } + + mdwc->dwc3_drd_sw = usb_role_switch_find_by_fwnode(dev_fwnode(dwc->dev)); + if (IS_ERR(mdwc->dwc3_drd_sw)) { + dev_err(mdwc->dev, "failed to find dwc3 drd role switch\n"); + ret = PTR_ERR(mdwc->dwc3_drd_sw); + goto depopulate; + } + + mdwc->dwc3_pm_ops = kzalloc(sizeof(struct dev_pm_ops), GFP_ATOMIC); + if (!mdwc->dwc3_pm_ops) + goto depopulate; + + dwc3_msm_override_pm_ops(dwc->dev, mdwc->dwc3_pm_ops, false); + + mdwc->xhci_pm_ops = kzalloc(sizeof(struct dev_pm_ops), GFP_ATOMIC); + if (!mdwc->xhci_pm_ops) + goto free_dwc_pm_ops; + + val = dwc3_msm_read_reg(mdwc->base, DWC3_GSNPSID); + mdwc->ip = DWC3_GSNPS_ID(val); + + dwc3_force_gen1(mdwc); + + dwc3_msm_notify_event(dwc, DWC3_GSI_EVT_BUF_ALLOC, 0); + pm_runtime_set_autosuspend_delay(dwc->dev, 0); + pm_runtime_allow(dwc->dev); + + return 0; + +free_dwc_pm_ops: + kfree(mdwc->dwc3_pm_ops); + +depopulate: + of_platform_depopulate(mdwc->dev); + +err: + return ret; +} + +static int dwc3_msm_parse_core_params(struct dwc3_msm *mdwc, struct device_node *dwc3_node) +{ + int ret; + const char *prop_string; + + ret = of_property_read_string(dwc3_node, "maximum-speed", &prop_string); + if (!ret) + ret = match_string(speed_names, ARRAY_SIZE(speed_names), prop_string); + mdwc->max_hw_supp_speed = (ret < 0) ? USB_SPEED_UNKNOWN : ret; + dwc3_msm_set_max_speed(mdwc, mdwc->max_hw_supp_speed); + + ret = of_property_read_string(dwc3_node, "dr_mode", &prop_string); + if (!ret) + ret = match_string(usb_dr_modes, ARRAY_SIZE(usb_dr_modes), prop_string); + mdwc->dr_mode = (ret < 0) ? USB_DR_MODE_UNKNOWN : ret; + + mdwc->core_irq = of_irq_get(dwc3_node, 0); + if (mdwc->core_irq > 0) { + ret = devm_request_irq(mdwc->dev, mdwc->core_irq, dwc3_msm_core_irq, + IRQF_SHARED, "dwc3-msm", mdwc); + if (ret) { + dev_err(mdwc->dev, "failed to request irq #%d --> %d\n", + mdwc->core_irq, ret); + return ret; + } + disable_irq(mdwc->core_irq); + } + + return ret; +} + +static int dwc3_msm_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node, *dwc3_node, *phy_node; + struct device *dev = &pdev->dev; + struct dwc3_msm *mdwc; + struct resource *res; + int ret = 0, size = 0, i; + u32 val; + + mdwc = devm_kzalloc(&pdev->dev, sizeof(*mdwc), GFP_KERNEL); + if (!mdwc) + return -ENOMEM; + + platform_set_drvdata(pdev, mdwc); + mdwc->dev = &pdev->dev; + + INIT_LIST_HEAD(&mdwc->req_complete_list); + INIT_WORK(&mdwc->resume_work, dwc3_resume_work); + INIT_WORK(&mdwc->restart_usb_work, dwc3_restart_usb_work); + INIT_DELAYED_WORK(&mdwc->sm_work, dwc3_otg_sm_work); + INIT_DELAYED_WORK(&mdwc->perf_vote_work, msm_dwc3_perf_vote_work); + + dwc3_msm_debug_init(mdwc); + + mdwc->dwc3_wq = alloc_ordered_workqueue("dwc3_wq", 0); + if (!mdwc->dwc3_wq) { + pr_err("%s: Unable to create workqueue dwc3_wq\n", __func__); + return -ENOMEM; + } + + /* + * Create an ordered freezable workqueue for sm_work so that it gets + * scheduled only after pm_resume has happened completely. This helps + * in avoiding race conditions between xhci_plat_resume and + * xhci_runtime_resume and also between hcd disconnect and xhci_resume. + */ + mdwc->sm_usb_wq = alloc_ordered_workqueue("k_sm_usb", WQ_FREEZABLE); + if (!mdwc->sm_usb_wq) { + destroy_workqueue(mdwc->dwc3_wq); + return -ENOMEM; + } + + /* redriver may not probe, check it at start here */ + mdwc->redriver = usb_get_redriver_by_phandle(node, "ssusb_redriver", 0); + if (IS_ERR(mdwc->redriver)) { + ret = PTR_ERR(mdwc->redriver); + mdwc->redriver = NULL; + goto err; + } + + /* Get all clks and gdsc reference */ + ret = dwc3_msm_get_clk_gdsc(mdwc); + if (ret) { + dev_err(&pdev->dev, "error getting clock or gdsc.\n"); + goto err; + } + + mdwc->id_state = DWC3_ID_FLOAT; + set_bit(ID, &mdwc->inputs); + + ret = of_property_read_u32(node, "qcom,lpm-to-suspend-delay-ms", + &mdwc->lpm_to_suspend_delay); + if (ret) { + dev_dbg(&pdev->dev, "setting lpm_to_suspend_delay to zero.\n"); + mdwc->lpm_to_suspend_delay = 0; + } + + for (i = 0; i < USB_MAX_IRQ; i++) { + mdwc->wakeup_irq[i].irq = platform_get_irq_byname(pdev, + usb_irq_info[i].name); + if (mdwc->wakeup_irq[i].irq < 0) { + /* pwr_evnt_irq is only mandatory irq */ + if (usb_irq_info[i].required) { + dev_err(&pdev->dev, "get_irq for %s failed\n\n", + usb_irq_info[i].name); + ret = -EINVAL; + goto err; + } + mdwc->wakeup_irq[i].irq = 0; + } else { + irq_set_status_flags(mdwc->wakeup_irq[i].irq, + IRQ_NOAUTOEN); + + ret = devm_request_threaded_irq(&pdev->dev, + mdwc->wakeup_irq[i].irq, + msm_dwc3_pwr_irq, + msm_dwc3_pwr_irq_thread, + usb_irq_info[i].irq_type, + usb_irq_info[i].name, mdwc); + if (ret) { + dev_err(&pdev->dev, "irq req %s failed: %d\n\n", + usb_irq_info[i].name, ret); + goto err; + } + } + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "core_base"); + if (!res) { + dev_err(&pdev->dev, "missing memory base resource\n"); + ret = -ENODEV; + goto err; + } + + mdwc->reg_phys = res->start; + mdwc->base = devm_ioremap(&pdev->dev, res->start, + resource_size(res)); + if (!mdwc->base) { + dev_err(&pdev->dev, "ioremap failed\n"); + ret = -ENODEV; + goto err; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "ahb2phy_base"); + if (res) { + mdwc->ahb2phy_base = devm_ioremap(&pdev->dev, + res->start, resource_size(res)); + if (IS_ERR_OR_NULL(mdwc->ahb2phy_base)) { + dev_err(dev, "couldn't find ahb2phy_base addr.\n"); + mdwc->ahb2phy_base = NULL; + } else { + /* + * On some targets cfg_ahb_clk depends upon usb gdsc + * regulator. If cfg_ahb_clk is enabled without + * turning on usb gdsc regulator clk is stuck off. + */ + dwc3_msm_config_gdsc(mdwc, 1); + clk_prepare_enable(mdwc->cfg_ahb_clk); + /* Configure AHB2PHY for one wait state read/write*/ + val = readl_relaxed(mdwc->ahb2phy_base + + PERIPH_SS_AHB2PHY_TOP_CFG); + if (val != ONE_READ_WRITE_WAIT) { + writel_relaxed(ONE_READ_WRITE_WAIT, + mdwc->ahb2phy_base + + PERIPH_SS_AHB2PHY_TOP_CFG); + /* complete above write before using USB PHY */ + mb(); + } + clk_disable_unprepare(mdwc->cfg_ahb_clk); + dwc3_msm_config_gdsc(mdwc, 0); + } + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ebc_desc"); + if (res) + mdwc->ebc_desc_addr = res->start; + + dwc3_init_dbm(mdwc); + + /* Add power event if the dbm indicates coming out of L1 by interrupt */ + if (!mdwc->dbm_is_1p4) { + if (!mdwc->wakeup_irq[PWR_EVNT_IRQ].irq) { + dev_err(&pdev->dev, + "need pwr_event_irq exiting L1\n"); + ret = -EINVAL; + goto err; + } + } + + ret = of_property_read_u32(node, "qcom,num-gsi-evt-buffs", + &mdwc->num_gsi_event_buffers); + + if (mdwc->num_gsi_event_buffers) { + of_get_property(node, "qcom,gsi-reg-offset", &size); + if (size) { + mdwc->gsi_reg = devm_kzalloc(dev, size, GFP_KERNEL); + if (!mdwc->gsi_reg) + return -ENOMEM; + + mdwc->gsi_reg_offset_cnt = + (size / sizeof(*mdwc->gsi_reg)); + if (mdwc->gsi_reg_offset_cnt != GSI_REG_MAX) { + dev_err(dev, "invalid reg offset count\n"); + return -EINVAL; + } + + of_property_read_u32_array(dev->of_node, + "qcom,gsi-reg-offset", mdwc->gsi_reg, + mdwc->gsi_reg_offset_cnt); + } else { + dev_err(dev, "err provide qcom,gsi-reg-offset\n"); + return -EINVAL; + } + } + + mdwc->use_pdc_interrupts = of_property_read_bool(node, + "qcom,use-pdc-interrupts"); + + mdwc->use_eusb2_phy = of_property_read_bool(node, "qcom,use-eusb2-phy"); + mdwc->disable_host_ssphy_powerdown = of_property_read_bool(node, + "qcom,disable-host-ssphy-powerdown"); + + if (dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64))) { + dev_err(&pdev->dev, "setting DMA mask to 64 failed.\n"); + if (dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32))) { + dev_err(&pdev->dev, "setting DMA mask to 32 failed.\n"); + ret = -EOPNOTSUPP; + goto err; + } + } + + mdwc->dis_sending_cm_l1_quirk = of_property_read_bool(node, + "qcom,dis-sending-cm-l1-quirk"); + + /* Assumes dwc3 is the first DT child of dwc3-msm */ + dwc3_node = of_get_next_available_child(node, NULL); + if (!dwc3_node) { + dev_err(&pdev->dev, "failed to find dwc3 child\n"); + ret = -ENODEV; + goto err; + } + + dwc3_msm_parse_core_params(mdwc, dwc3_node); + + phy_node = of_parse_phandle(dwc3_node, "usb-phy", 0); + mdwc->hs_phy = devm_usb_get_phy_by_node(mdwc->dev, phy_node, NULL); + if (IS_ERR(mdwc->hs_phy)) { + dev_err(mdwc->dev, "unable to get hsphy device\n"); + ret = PTR_ERR(mdwc->hs_phy); + goto err; + } + + phy_node = of_parse_phandle(dwc3_node, "usb-phy", 1); + mdwc->ss_phy = devm_usb_get_phy_by_node(mdwc->dev, phy_node, NULL); + if (IS_ERR(mdwc->ss_phy)) { + dev_err(mdwc->dev, "unable to get ssphy device\n"); + ret = PTR_ERR(mdwc->ss_phy); + goto err; + } + + /* use default as nominal bus voting */ + mdwc->default_bus_vote = BUS_VOTE_NOMINAL; + ret = of_property_read_u32(node, "qcom,default-bus-vote", + &mdwc->default_bus_vote); + + if (mdwc->default_bus_vote >= BUS_VOTE_MAX) + mdwc->default_bus_vote = BUS_VOTE_MAX - 1; + else if (mdwc->default_bus_vote < BUS_VOTE_NONE) + mdwc->default_bus_vote = BUS_VOTE_NONE; + + for (i = 0; i < ARRAY_SIZE(mdwc->icc_paths); i++) { + mdwc->icc_paths[i] = of_icc_get(&pdev->dev, icc_path_names[i]); + if (IS_ERR(mdwc->icc_paths[i])) + mdwc->icc_paths[i] = NULL; + } + + /* + * Clocks and regulators will not be turned on until the first time + * runtime PM resume is called. This is to allow for booting up with + * charger already connected so as not to disturb PHY line states. + */ + mdwc->lpm_flags = MDWC3_POWER_COLLAPSE | MDWC3_SS_PHY_SUSPEND; + atomic_set(&mdwc->in_lpm, 1); + pm_runtime_set_autosuspend_delay(mdwc->dev, 1000); + pm_runtime_use_autosuspend(mdwc->dev); + device_init_wakeup(mdwc->dev, 1); + + if (of_property_read_bool(node, "qcom,disable-dev-mode-pm")) + pm_runtime_get_noresume(mdwc->dev); + + ret = of_property_read_u32(node, "qcom,pm-qos-latency", + &mdwc->pm_qos_latency); + if (ret) { + dev_dbg(&pdev->dev, "setting pm-qos-latency to zero.\n"); + mdwc->pm_qos_latency = 0; + } + + mutex_init(&mdwc->suspend_resume_mutex); + mutex_init(&mdwc->role_switch_mutex); + + mdwc->force_gen1 = of_property_read_bool(node, "qcom,force-gen1"); + + if (of_property_read_bool(node, "usb-role-switch")) { + struct usb_role_switch_desc role_desc = { + .set = dwc3_msm_usb_set_role, + .get = dwc3_msm_usb_get_role, + .driver_data = mdwc, + .allow_userspace_control = true, + }; + + role_desc.fwnode = dev_fwnode(&pdev->dev); + mdwc->role_switch = usb_role_switch_register(mdwc->dev, + &role_desc); + if (IS_ERR(mdwc->role_switch)) { + ret = PTR_ERR(mdwc->role_switch); + goto put_dwc3; + } + } + + if (of_property_read_bool(node, "extcon")) { + ret = dwc3_msm_extcon_register(mdwc); + if (ret) + goto put_dwc3; + + /* + * dpdm regulator will be turned on to perform apsd + * (automatic power source detection). dpdm regulator is + * used to float (or high-z) dp/dm lines. Do not reset + * controller/phy if regulator is turned on. + * if dpdm is not present controller can be reset + * as this controller may not be used for charger detection. + */ + mdwc->dpdm_reg = devm_regulator_get_optional(&pdev->dev, + "dpdm"); + if (IS_ERR(mdwc->dpdm_reg)) { + dev_dbg(mdwc->dev, "assume cable is not connected\n"); + mdwc->dpdm_reg = NULL; + } + + if (!mdwc->vbus_active && mdwc->dpdm_reg && + regulator_is_enabled(mdwc->dpdm_reg)) { + mdwc->dpdm_nb.notifier_call = dwc_dpdm_cb; + regulator_register_notifier(mdwc->dpdm_reg, + &mdwc->dpdm_nb); + } else { + if (!mdwc->role_switch) + queue_delayed_work(mdwc->sm_usb_wq, + &mdwc->sm_work, 0); + } + } + + if (!mdwc->role_switch && !mdwc->extcon) { + switch (mdwc->dr_mode) { + case USB_DR_MODE_OTG: + if (of_property_read_bool(node, + "qcom,default-mode-host")) { + dev_dbg(mdwc->dev, "%s: start host mode\n", + __func__); + mdwc->id_state = DWC3_ID_GROUND; + } else if (of_property_read_bool(node, + "qcom,default-mode-none")) { + dev_dbg(mdwc->dev, "%s: stay in none mode\n", + __func__); + } else { + dev_dbg(mdwc->dev, "%s: start peripheral mode\n", + __func__); + mdwc->vbus_active = true; + } + break; + case USB_DR_MODE_HOST: + mdwc->id_state = DWC3_ID_GROUND; + break; + case USB_DR_MODE_PERIPHERAL: + fallthrough; + default: + mdwc->vbus_active = true; + break; + } + + dwc3_ext_event_notify(mdwc); + } + + return 0; + +put_dwc3: + usb_role_switch_unregister(mdwc->role_switch); + for (i = 0; i < ARRAY_SIZE(mdwc->icc_paths); i++) + icc_put(mdwc->icc_paths[i]); + +err: + destroy_workqueue(mdwc->sm_usb_wq); + destroy_workqueue(mdwc->dwc3_wq); + usb_put_redriver(mdwc->redriver); + return ret; +} + +static int dwc3_msm_remove(struct platform_device *pdev) +{ + struct dwc3_msm *mdwc = platform_get_drvdata(pdev); + struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3); + int i, ret_pm; + + usb_role_switch_unregister(mdwc->role_switch); + + if (mdwc->dpdm_nb.notifier_call) { + regulator_unregister_notifier(mdwc->dpdm_reg, &mdwc->dpdm_nb); + mdwc->dpdm_nb.notifier_call = NULL; + } + + /* + * In case of system suspend, pm_runtime_get_sync fails. + * Hence turn ON the clocks manually. + */ + ret_pm = pm_runtime_get_sync(mdwc->dev); + dbg_event(0xFF, "Remov gsyn", ret_pm); + if (ret_pm < 0) { + dev_err(mdwc->dev, + "pm_runtime_get_sync failed with %d\n", ret_pm); + clk_prepare_enable(mdwc->noc_aggr_clk); + clk_prepare_enable(mdwc->utmi_clk); + clk_prepare_enable(mdwc->core_clk); + clk_prepare_enable(mdwc->iface_clk); + clk_prepare_enable(mdwc->sleep_clk); + clk_prepare_enable(mdwc->bus_aggr_clk); + clk_prepare_enable(mdwc->xo_clk); + } + + cancel_delayed_work_sync(&mdwc->perf_vote_work); + msm_dwc3_perf_vote_update(mdwc, false); + cancel_delayed_work_sync(&mdwc->sm_work); + + if (mdwc->hs_phy) + mdwc->hs_phy->flags &= ~PHY_HOST_MODE; + dwc3_msm_notify_event(dwc, DWC3_GSI_EVT_BUF_FREE, 0); + platform_device_put(mdwc->dwc3); + of_platform_depopulate(&pdev->dev); + + usb_put_redriver(mdwc->redriver); + + dbg_event(0xFF, "Remov put", 0); + pm_runtime_disable(mdwc->dev); + pm_runtime_barrier(mdwc->dev); + pm_runtime_put_sync(mdwc->dev); + pm_runtime_set_suspended(mdwc->dev); + device_wakeup_disable(mdwc->dev); + + for (i = 0; i < ARRAY_SIZE(mdwc->icc_paths); i++) + icc_put(mdwc->icc_paths[i]); + + if (mdwc->wakeup_irq[HS_PHY_IRQ].irq) + disable_irq(mdwc->wakeup_irq[HS_PHY_IRQ].irq); + if (mdwc->wakeup_irq[DP_HS_PHY_IRQ].irq) + disable_irq(mdwc->wakeup_irq[DP_HS_PHY_IRQ].irq); + if (mdwc->wakeup_irq[DM_HS_PHY_IRQ].irq) + disable_irq(mdwc->wakeup_irq[DM_HS_PHY_IRQ].irq); + if (mdwc->wakeup_irq[SS_PHY_IRQ].irq) + disable_irq(mdwc->wakeup_irq[SS_PHY_IRQ].irq); + disable_irq(mdwc->wakeup_irq[PWR_EVNT_IRQ].irq); + + clk_disable_unprepare(mdwc->utmi_clk); + clk_set_rate(mdwc->core_clk, 19200000); + clk_disable_unprepare(mdwc->core_clk); + clk_disable_unprepare(mdwc->iface_clk); + clk_disable_unprepare(mdwc->sleep_clk); + clk_disable_unprepare(mdwc->xo_clk); + + dwc3_msm_config_gdsc(mdwc, 0); + + destroy_workqueue(mdwc->sm_usb_wq); + destroy_workqueue(mdwc->dwc3_wq); + + ipc_log_context_destroy(mdwc->dwc_ipc_log_ctxt); + mdwc->dwc_ipc_log_ctxt = NULL; + ipc_log_context_destroy(mdwc->dwc_dma_ipc_log_ctxt); + mdwc->dwc_dma_ipc_log_ctxt = NULL; + + kfree(mdwc->xhci_pm_ops); + kfree(mdwc->dwc3_pm_ops); + + return 0; +} + +static int dwc3_msm_host_ss_powerdown(struct dwc3_msm *mdwc) +{ + u32 reg; + + if (mdwc->disable_host_ssphy_powerdown || + dwc3_msm_get_max_speed(mdwc) < USB_SPEED_SUPER) + return 0; + + reg = dwc3_msm_read_reg(mdwc->base, EXTRA_INP_REG); + reg |= EXTRA_INP_SS_DISABLE; + dwc3_msm_write_reg(mdwc->base, EXTRA_INP_REG, reg); + dwc3_msm_switch_utmi(mdwc, 1); + + usb_phy_notify_disconnect(mdwc->ss_phy, + USB_SPEED_SUPER); + usb_phy_set_suspend(mdwc->ss_phy, 1); + + return 0; +} + +static int dwc3_msm_host_ss_powerup(struct dwc3_msm *mdwc) +{ + u32 reg; + + if (mdwc->disable_host_ssphy_powerdown || + dwc3_msm_get_max_speed(mdwc) < USB_SPEED_SUPER) + return 0; + + usb_phy_set_suspend(mdwc->ss_phy, 0); + usb_phy_notify_connect(mdwc->ss_phy, + USB_SPEED_SUPER); + + dwc3_msm_switch_utmi(mdwc, 0); + reg = dwc3_msm_read_reg(mdwc->base, EXTRA_INP_REG); + reg &= ~EXTRA_INP_SS_DISABLE; + dwc3_msm_write_reg(mdwc->base, EXTRA_INP_REG, reg); + + return 0; +} + +static int dwc3_msm_host_notifier(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct dwc3_msm *mdwc = container_of(nb, struct dwc3_msm, host_nb); + struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3); + struct usb_device *udev = ptr; + + if (event != USB_DEVICE_ADD && event != USB_DEVICE_REMOVE) + return NOTIFY_DONE; + + /* + * Regardless of where the device is in the host tree, the USB generic + * device's PM resume/suspend should be skipped. Otherwise, the USB + * generic device will end up with failures during PM resume, as XHCI + * relies on the DWC3 MSM to issue the PM runtime resume to wake up + * the entire host device chain. + */ + if (event == USB_DEVICE_ADD) + dev_pm_syscore_device(&udev->dev, true); + /* + * For direct-attach devices, new udev is direct child of root hub + * i.e. dwc -> xhci -> root_hub -> udev + * root_hub's udev->parent==NULL, so traverse struct device hierarchy + */ + if (udev->parent && !udev->parent->parent && + udev->dev.parent->parent == &dwc->xhci->dev) { + if (event == USB_DEVICE_ADD) { + if (!dwc3_msm_is_ss_rhport_connected(mdwc)) { + /* + * Core clock rate can be reduced only if root + * hub SS port is not enabled/connected. + */ + clk_set_rate(mdwc->core_clk, + mdwc->core_clk_rate_hs); + dev_dbg(mdwc->dev, + "set hs core clk rate %ld\n", + mdwc->core_clk_rate_hs); + mdwc->max_rh_port_speed = USB_SPEED_HIGH; + dwc3_msm_update_bus_bw(mdwc, BUS_VOTE_SVS); + dwc3_msm_host_ss_powerdown(mdwc); + } else { + if (mdwc->max_rh_port_speed < USB_SPEED_SUPER) + dwc3_msm_host_ss_powerup(mdwc); + mdwc->max_rh_port_speed = USB_SPEED_SUPER; + } + } else { + /* set rate back to default core clk rate */ + clk_set_rate(mdwc->core_clk, mdwc->core_clk_rate); + dev_dbg(mdwc->dev, "set core clk rate %ld\n", + mdwc->core_clk_rate); + mdwc->max_rh_port_speed = USB_SPEED_UNKNOWN; + dwc3_msm_update_bus_bw(mdwc, mdwc->default_bus_vote); + dwc3_msm_host_ss_powerup(mdwc); + + if (udev->parent->speed >= USB_SPEED_SUPER) + usb_redriver_host_powercycle(mdwc->redriver); + } + } else if (!udev->parent) { + /* USB root hub device */ + if (event == USB_DEVICE_ADD) { + pm_runtime_use_autosuspend(&udev->dev); + pm_runtime_set_autosuspend_delay(&udev->dev, 1000); + } + } + + return NOTIFY_DONE; +} + +static void msm_dwc3_perf_vote_update(struct dwc3_msm *mdwc, bool perf_mode) +{ + int latency = mdwc->pm_qos_latency; + + mdwc->irq_cnt = 0; + + if ((mdwc->perf_mode == perf_mode) || !latency) + return; + + if (perf_mode) + cpu_latency_qos_update_request(&mdwc->pm_qos_req_dma, latency); + else + cpu_latency_qos_update_request(&mdwc->pm_qos_req_dma, + PM_QOS_DEFAULT_VALUE); + + mdwc->perf_mode = perf_mode; + pr_debug("%s: latency updated to: %d\n", __func__, + perf_mode ? latency : PM_QOS_DEFAULT_VALUE); +} + +static void msm_dwc3_perf_vote_work(struct work_struct *w) +{ + struct dwc3_msm *mdwc = container_of(w, struct dwc3_msm, + perf_vote_work.work); + bool in_perf_mode = false; + + if (mdwc->irq_cnt >= PM_QOS_THRESHOLD) + in_perf_mode = true; + + pr_debug("%s: in_perf_mode:%u, interrupts in last sample:%u\n", + __func__, in_perf_mode, mdwc->irq_cnt); + + msm_dwc3_perf_vote_update(mdwc, in_perf_mode); + schedule_delayed_work(&mdwc->perf_vote_work, + msecs_to_jiffies(1000 * PM_QOS_SAMPLE_SEC)); +} + +/** + * dwc3_otg_start_host - helper function for starting/stopping the host + * controller driver. + * + * @mdwc: Pointer to the dwc3_msm structure. + * @on: start / stop the host controller driver. + * + * Returns 0 on success otherwise negative errno. + */ +static int dwc3_otg_start_host(struct dwc3_msm *mdwc, int on) +{ + int ret = 0; + struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3); + + if (on) { + dev_dbg(mdwc->dev, "%s: turn on host\n", __func__); + mdwc->hs_phy->flags |= PHY_HOST_MODE; + dbg_event(0xFF, "hs_phy_flag:%x", mdwc->hs_phy->flags); + ret = pm_runtime_resume_and_get(mdwc->dev); + if (ret < 0) { + dev_err(mdwc->dev, "%s: pm_runtime_resume_and_get failed\n", __func__); + mdwc->hs_phy->flags &= ~PHY_HOST_MODE; + pm_runtime_set_suspended(mdwc->dev); + return ret; + } + dbg_event(0xFF, "StrtHost gync", + atomic_read(&mdwc->dev->power.usage_count)); + clk_set_rate(mdwc->core_clk, mdwc->core_clk_rate); + if (dwc3_msm_get_max_speed(mdwc) >= USB_SPEED_SUPER) { + mdwc->ss_phy->flags |= PHY_HOST_MODE; + usb_phy_notify_connect(mdwc->ss_phy, + USB_SPEED_SUPER); + } + + usb_phy_notify_connect(mdwc->hs_phy, USB_SPEED_HIGH); + mdwc->host_nb.notifier_call = dwc3_msm_host_notifier; + usb_register_notify(&mdwc->host_nb); + + if (!dwc->dis_enblslpm_quirk) + dwc3_en_sleep_mode(mdwc); + + if (mdwc->dis_sending_cm_l1_quirk) + mdwc3_dis_sending_cm_l1(mdwc); + + /* + * Increase Inter-packet delay by 1 UTMI clock cycle (EL_23). + * + * STAR 9001346572: Host: When a Single USB 2.0 Endpoint Receives NAKs Continuously, + * Host Stops Transfers to Other Endpoints. When an active endpoint that is not + * currently cached in the host controller is chosen to be cached to the same cache + * index as the endpoint that receives NAK, The endpoint that receives the NAK + * responses would be in continuous retry mode that would prevent it from getting + * evicted out of the host controller cache. This would prevent the new endpoint to + * get into the endpoint cache and therefore service to this endpoint is not done. + * The workaround is to disable lower layer LSP retrying the USB2.0 NAKed transfer. + * Forcing this to LSP upper layer allows next EP to evict the stuck EP from cache. + */ + if (DWC3_VER_IS_WITHIN(DWC31, 170A, ANY)) { + dwc3_msm_write_reg_field(mdwc->base, DWC3_GUCTL1, + DWC3_GUCTL1_IP_GAP_ADD_ON_MASK, 1); + + dwc3_msm_write_reg_field(mdwc->base, DWC3_GUCTL3, + DWC3_GUCTL3_USB20_RETRY_DISABLE, 1); + } + + usb_role_switch_set_role(mdwc->dwc3_drd_sw, USB_ROLE_HOST); + if (dwc->dr_mode == USB_DR_MODE_OTG) + flush_work(&dwc->drd_work); + dwc3_msm_override_pm_ops(&dwc->xhci->dev, mdwc->xhci_pm_ops, true); + mdwc->in_host_mode = true; + pm_runtime_use_autosuspend(&dwc->xhci->dev); + pm_runtime_set_autosuspend_delay(&dwc->xhci->dev, 0); + pm_runtime_allow(&dwc->xhci->dev); + pm_runtime_mark_last_busy(&dwc->xhci->dev); + + dwc3_msm_write_reg_field(mdwc->base, DWC3_GUSB3PIPECTL(0), + DWC3_GUSB3PIPECTL_SUSPHY, 1); + + /* Reduce the U3 exit handshake timer from 8us to approximately + * 300ns to avoid lfps handshake interoperability issues + */ + if (DWC3_VER_IS(DWC31, 170A)) { + dwc3_msm_write_reg_field(mdwc->base, + DWC31_LINK_LU3LFPSRXTIM(0), + GEN2_U3_EXIT_RSP_RX_CLK_MASK, 6); + dwc3_msm_write_reg_field(mdwc->base, + DWC31_LINK_LU3LFPSRXTIM(0), + GEN1_U3_EXIT_RSP_RX_CLK_MASK, 5); + dev_dbg(mdwc->dev, "LU3:%08x\n", + dwc3_msm_read_reg(mdwc->base, + DWC31_LINK_LU3LFPSRXTIM(0))); + } + + /* xHCI should have incremented child count as necessary */ + dbg_event(0xFF, "StrtHost psync", + atomic_read(&mdwc->dev->power.usage_count)); + pm_runtime_mark_last_busy(mdwc->dev); + pm_runtime_put_sync_autosuspend(mdwc->dev); + cpu_latency_qos_add_request(&mdwc->pm_qos_req_dma, + PM_QOS_DEFAULT_VALUE); + /* start in perf mode for better performance initially */ + msm_dwc3_perf_vote_update(mdwc, true); + schedule_delayed_work(&mdwc->perf_vote_work, + msecs_to_jiffies(1000 * PM_QOS_SAMPLE_SEC)); + } else { + dev_dbg(mdwc->dev, "%s: turn off host\n", __func__); + + cancel_delayed_work_sync(&mdwc->perf_vote_work); + msm_dwc3_perf_vote_update(mdwc, false); + cpu_latency_qos_remove_request(&mdwc->pm_qos_req_dma); + + ret = pm_runtime_resume_and_get(mdwc->dev); + if (ret < 0) { + dev_err(mdwc->dev, "%s: pm_runtime_resume_and_get failed\n", __func__); + pm_runtime_set_suspended(mdwc->dev); + return ret; + } + dbg_event(0xFF, "StopHost gsync", + atomic_read(&mdwc->dev->power.usage_count)); + + /* + * Able to set max speed back to UNKNOWN as clk mux still set to + * UTMI during dwc3_msm_resume(). Need to ensure max speed is + * reset before dwc3_gadget_init() is called. Otherwise, USB + * gadget will be set to HS only. + */ + if (!mdwc->ss_release_called) { + dwc3_msm_host_ss_powerup(mdwc); + dwc3_msm_clear_dp_only_params(mdwc); + } + + usb_role_switch_set_role(mdwc->dwc3_drd_sw, USB_ROLE_DEVICE); + if (dwc->dr_mode == USB_DR_MODE_OTG) + flush_work(&dwc->drd_work); + + usb_phy_notify_disconnect(mdwc->hs_phy, USB_SPEED_HIGH); + if (mdwc->ss_phy->flags & PHY_HOST_MODE) { + usb_phy_notify_disconnect(mdwc->ss_phy, + USB_SPEED_SUPER); + mdwc->ss_phy->flags &= ~PHY_HOST_MODE; + } + + mdwc->hs_phy->flags &= ~PHY_HOST_MODE; + usb_unregister_notify(&mdwc->host_nb); + + dwc3_msm_write_reg_field(mdwc->base, DWC3_GUSB3PIPECTL(0), + DWC3_GUSB3PIPECTL_SUSPHY, 0); + mdwc->in_host_mode = false; + + /* wait for LPM, to ensure h/w is reset after stop_host */ + set_bit(WAIT_FOR_LPM, &mdwc->inputs); + + pm_runtime_put_sync_suspend(&mdwc->dwc3->dev); + dbg_event(0xFF, "StopHost psync", + atomic_read(&mdwc->dev->power.usage_count)); + } + + return 0; +} + +static void dwc3_override_vbus_status(struct dwc3_msm *mdwc, bool vbus_present) +{ + /* Update OTG VBUS Valid from HSPHY to controller */ + dwc3_msm_write_reg_field(mdwc->base, HS_PHY_CTRL_REG, + UTMI_OTG_VBUS_VALID, !!vbus_present); + + /* Update VBUS Valid from SSPHY to controller */ + if (vbus_present) { + /* Update only if Super Speed is supported */ + if (dwc3_msm_get_max_speed(mdwc) >= USB_SPEED_SUPER) + dwc3_msm_write_reg_field(mdwc->base, SS_PHY_CTRL_REG, + LANE0_PWR_PRESENT, 1); + } else { + dwc3_msm_write_reg_field(mdwc->base, SS_PHY_CTRL_REG, + LANE0_PWR_PRESENT, 0); + } +} + +/** + * dwc3_otg_start_peripheral - bind/unbind the peripheral controller. + * + * @mdwc: Pointer to the dwc3_msm structure. + * @on: Turn ON/OFF the gadget. + * + * Returns 0 on success otherwise negative errno. + */ +static int dwc3_otg_start_peripheral(struct dwc3_msm *mdwc, int on) +{ + struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3); + int timeout = 1000; + int ret; + + ret = pm_runtime_resume_and_get(mdwc->dev); + if (ret < 0) { + dev_err(mdwc->dev, "%s: pm_runtime_resume_and_get failed\n", __func__); + pm_runtime_set_suspended(mdwc->dev); + return ret; + } + dbg_event(0xFF, "StrtGdgt gsync", + atomic_read(&mdwc->dev->power.usage_count)); + + if (on) { + dev_dbg(mdwc->dev, "%s: turn on gadget\n", __func__); + + pm_runtime_get_sync(&mdwc->dwc3->dev); + /* + * Ensure DWC3 DRD switch routine is complete before continuing. + * The DWC3 DRD sequence will execute a global and core soft + * reset during mode switching. DWC3 MSM needs to avoid setting + * up the GSI related resources until that is completed. + */ + if (dwc->dr_mode == USB_DR_MODE_OTG) + flush_work(&dwc->drd_work); + dwc3_msm_notify_event(dwc, DWC3_GSI_EVT_BUF_SETUP, 0); + + dwc3_override_vbus_status(mdwc, true); + if (dwc3_msm_get_max_speed(mdwc) >= USB_SPEED_SUPER) + usb_phy_notify_connect(mdwc->ss_phy, USB_SPEED_SUPER); + + usb_phy_notify_connect(mdwc->hs_phy, USB_SPEED_HIGH); + + /* + * Core reset is not required during start peripheral. Only + * DBM reset is required, hence perform only DBM reset here. + */ + dwc3_msm_block_reset(mdwc, false); + dwc3_dis_sleep_mode(mdwc); + mdwc->in_device_mode = true; + + /* Reduce the U3 exit handshake timer from 8us to approximately + * 300ns to avoid lfps handshake interoperability issues + */ + if (DWC3_VER_IS(DWC31, 170A)) { + dwc3_msm_write_reg_field(mdwc->base, + DWC31_LINK_LU3LFPSRXTIM(0), + GEN2_U3_EXIT_RSP_RX_CLK_MASK, 6); + dwc3_msm_write_reg_field(mdwc->base, + DWC31_LINK_LU3LFPSRXTIM(0), + GEN1_U3_EXIT_RSP_RX_CLK_MASK, 5); + dev_dbg(mdwc->dev, "LU3:%08x\n", + dwc3_msm_read_reg(mdwc->base, + DWC31_LINK_LU3LFPSRXTIM(0))); + } + + usb_role_switch_set_role(mdwc->dwc3_drd_sw, USB_ROLE_DEVICE); + cpu_latency_qos_add_request(&mdwc->pm_qos_req_dma, + PM_QOS_DEFAULT_VALUE); + clk_set_rate(mdwc->core_clk, mdwc->core_clk_rate); + /* start in perf mode for better performance initially */ + msm_dwc3_perf_vote_update(mdwc, true); + schedule_delayed_work(&mdwc->perf_vote_work, + msecs_to_jiffies(1000 * PM_QOS_SAMPLE_SEC)); + } else { + dev_dbg(mdwc->dev, "%s: turn off gadget\n", __func__); + cancel_delayed_work_sync(&mdwc->perf_vote_work); + msm_dwc3_perf_vote_update(mdwc, false); + cpu_latency_qos_remove_request(&mdwc->pm_qos_req_dma); + + dwc3_override_vbus_status(mdwc, false); + mdwc->in_device_mode = false; + usb_phy_notify_disconnect(mdwc->hs_phy, USB_SPEED_HIGH); + usb_phy_notify_disconnect(mdwc->ss_phy, USB_SPEED_SUPER); + + dwc3_msm_notify_event(dwc, DWC3_GSI_EVT_BUF_CLEAR, 0); + dwc3_override_vbus_status(mdwc, false); + dwc3_msm_write_reg_field(mdwc->base, DWC3_GUSB3PIPECTL(0), + DWC3_GUSB3PIPECTL_SUSPHY, 0); + + /* + * DWC3 core runtime PM may return an error during the put sync + * and nothing else can trigger idle after this point. If EBUSY + * is detected (i.e. dwc->connected == TRUE) then wait for the + * connected flag to turn FALSE (set to false during disconnect + * or pullup disable), and retry suspend again. + */ + ret = pm_runtime_put_sync(&mdwc->dwc3->dev); + if (ret == -EBUSY) { + while (--timeout && dwc->connected) + msleep(20); + dbg_event(0xFF, "StopGdgt connected", dwc->connected); + pm_runtime_suspend(&mdwc->dwc3->dev); + } + + /* wait for LPM, to ensure h/w is reset after stop_peripheral */ + set_bit(WAIT_FOR_LPM, &mdwc->inputs); + } + + pm_runtime_put_sync(mdwc->dev); + dbg_event(0xFF, "StopGdgt psync", + atomic_read(&mdwc->dev->power.usage_count)); + + return 0; +} + +/** + * dwc3_otg_sm_work - workqueue function. + * + * @w: Pointer to the dwc3 otg workqueue + * + * NOTE: After any change in drd_state, we must reschdule the state machine. + */ +static void dwc3_otg_sm_work(struct work_struct *w) +{ + struct dwc3_msm *mdwc = container_of(w, struct dwc3_msm, sm_work.work); + struct dwc3 *dwc = NULL; + bool work = false; + int ret = 0; + unsigned long delay = 0; + const char *state; + + if (mdwc->dwc3) + dwc = platform_get_drvdata(mdwc->dwc3); + + if (!dwc && mdwc->drd_state != DRD_STATE_UNDEFINED) { + dev_err(mdwc->dev, "dwc is NULL.\n"); + return; + } + + state = dwc3_drd_state_string(mdwc->drd_state); + dev_dbg(mdwc->dev, "%s state\n", state); + dbg_event(0xFF, state, 0); + + /* Check OTG state */ + switch (mdwc->drd_state) { + case DRD_STATE_UNDEFINED: + if (mdwc->dpdm_nb.notifier_call) { + regulator_unregister_notifier(mdwc->dpdm_reg, + &mdwc->dpdm_nb); + mdwc->dpdm_nb.notifier_call = NULL; + } + + pm_runtime_enable(mdwc->dev); + ret = pm_runtime_resume_and_get(mdwc->dev); + if (ret < 0) { + dev_err(mdwc->dev, "%s: pm_runtime_resume_and_get failed\n", __func__); + pm_runtime_set_suspended(mdwc->dev); + break; + } + ret = dwc3_msm_core_init(mdwc); + if (ret) { + dbg_event(0xFF, "core_init failed", ret); + pm_runtime_put_sync_suspend(mdwc->dev); + pm_runtime_disable(mdwc->dev); + work = true; + break; + } + + mdwc->drd_state = DRD_STATE_IDLE; + + /* put controller and phy in suspend if no cable connected */ + if (test_bit(ID, &mdwc->inputs) && + !test_bit(B_SESS_VLD, &mdwc->inputs)) { + dbg_event(0xFF, "undef_id_!bsv", 0); + /* + * might not suspend immediately if dwc child has a + * pending autosuspend timer + */ + pm_runtime_put_sync(mdwc->dev); + dbg_event(0xFF, "Undef NoUSB", + atomic_read(&mdwc->dev->power.usage_count)); + break; + } + + /* + * decrement runtime PM refcount as start_peripheral() and + * start_host() below each call get_sync() again + */ + pm_runtime_put_noidle(mdwc->dev); + dbg_event(0xFF, "Exit UNDEF", 0); + fallthrough; + case DRD_STATE_IDLE: + if (test_bit(WAIT_FOR_LPM, &mdwc->inputs)) { + dev_dbg(mdwc->dev, "still not in lpm, wait.\n"); + dbg_event(0xFF, "WAIT_FOR_LPM", 0); + break; + } + + if (!test_bit(ID, &mdwc->inputs)) { + dev_dbg(mdwc->dev, "!id\n"); + mdwc->drd_state = DRD_STATE_HOST; + + ret = dwc3_otg_start_host(mdwc, 1); + if (ret) { + dev_err(mdwc->dev, "unable to start host\n"); + mdwc->drd_state = DRD_STATE_IDLE; + goto ret; + } + } else if (test_bit(B_SESS_VLD, &mdwc->inputs)) { + dev_dbg(mdwc->dev, "b_sess_vld\n"); + /* + * Increment pm usage count upon cable connect. Count + * is decremented in DRD_STATE_PERIPHERAL state on + * cable disconnect or in bus suspend. + */ + ret = pm_runtime_resume_and_get(mdwc->dev); + if (ret < 0) { + dev_err(mdwc->dev, "%s: pm_runtime_resume_and_get failed\n", + __func__); + pm_runtime_set_suspended(mdwc->dev); + break; + } + dbg_event(0xFF, "BIDLE gsync", + atomic_read(&mdwc->dev->power.usage_count)); + dwc3_otg_start_peripheral(mdwc, 1); + mdwc->drd_state = DRD_STATE_PERIPHERAL; + work = true; + } else { + usb_phy_set_power(mdwc->hs_phy, 0); + dev_dbg(mdwc->dev, "Cable disconnected\n"); + } + break; + + case DRD_STATE_PERIPHERAL: + if (!test_bit(B_SESS_VLD, &mdwc->inputs) || + !test_bit(ID, &mdwc->inputs)) { + dev_dbg(mdwc->dev, "!id || !bsv\n"); + mdwc->drd_state = DRD_STATE_IDLE; + dwc3_otg_start_peripheral(mdwc, 0); + /* + * Decrement pm usage count upon cable disconnect + * which was incremented upon cable connect in + * DRD_STATE_IDLE state + */ + pm_runtime_put_sync_suspend(mdwc->dev); + dbg_event(0xFF, "!BSV psync", + atomic_read(&mdwc->dev->power.usage_count)); + work = true; + } else if (test_bit(B_SUSPEND, &mdwc->inputs) && + test_bit(B_SESS_VLD, &mdwc->inputs)) { + dev_dbg(mdwc->dev, "BPER bsv && susp\n"); + mdwc->drd_state = DRD_STATE_PERIPHERAL_SUSPEND; + /* + * Decrement pm usage count upon bus suspend. + * Count was incremented either upon cable + * connect in DRD_STATE_IDLE or host + * initiated resume after bus suspend in + * DRD_STATE_PERIPHERAL_SUSPEND state + */ + pm_runtime_mark_last_busy(mdwc->dev); + pm_runtime_put_autosuspend(mdwc->dev); + dbg_event(0xFF, "SUSP put", + atomic_read(&mdwc->dev->power.usage_count)); + } + break; + + case DRD_STATE_PERIPHERAL_SUSPEND: + if (!test_bit(B_SESS_VLD, &mdwc->inputs)) { + dev_dbg(mdwc->dev, "BSUSP: !bsv\n"); + mdwc->drd_state = DRD_STATE_IDLE; + dwc3_otg_start_peripheral(mdwc, 0); + } else if (!test_bit(B_SUSPEND, &mdwc->inputs)) { + dev_dbg(mdwc->dev, "BSUSP !susp\n"); + mdwc->drd_state = DRD_STATE_PERIPHERAL; + /* + * Increment pm usage count upon host + * initiated resume. Count was decremented + * upon bus suspend in + * DRD_STATE_PERIPHERAL state. + */ + ret = pm_runtime_resume_and_get(mdwc->dev); + if (ret < 0) { + dev_err(mdwc->dev, "%s: pm_runtime_resume_and_get failed\n" + , __func__); + pm_runtime_set_suspended(mdwc->dev); + break; + } + dbg_event(0xFF, "!SUSP gsync", + atomic_read(&mdwc->dev->power.usage_count)); + } + break; + + case DRD_STATE_HOST: + if (test_bit(ID, &mdwc->inputs)) { + dev_dbg(mdwc->dev, "id\n"); + dwc3_otg_start_host(mdwc, 0); + mdwc->drd_state = DRD_STATE_IDLE; + work = true; + } else if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_HOST) { + dev_dbg(mdwc->dev, "still in a_host state. Resuming root hub.\n"); + dbg_event(0xFF, "XHCIResume", 0); + ret = pm_runtime_resume(&dwc->xhci->dev); + if (ret > 0) { + dbg_event(0xFF, "RT active", + hrtimer_active(&dwc->xhci->dev.power.suspend_timer)); + pm_request_idle(&dwc->xhci->dev); + } + } + break; + + default: + dev_err(mdwc->dev, "%s: invalid otg-state\n", __func__); + + } + + if (work) + queue_delayed_work(mdwc->sm_usb_wq, &mdwc->sm_work, delay); + +ret: + return; +} + +#ifdef CONFIG_PM_SLEEP +static int dwc3_msm_pm_suspend(struct device *dev) +{ + int ret = 0; + struct dwc3_msm *mdwc = dev_get_drvdata(dev); + struct dwc3 *dwc = NULL; + + if (mdwc->dwc3) { + dwc = platform_get_drvdata(mdwc->dwc3); + + dev_dbg(dev, "dwc3-msm PM suspend\n"); + dbg_event(0xFF, "PM Sus", 0); + + /* + * Check if pm_suspend can proceed irrespective of runtimePM state of + * host. + */ + if (!mdwc->host_poweroff_in_pm_suspend || !mdwc->in_host_mode) { + if (!atomic_read(&mdwc->in_lpm)) { + dev_err(mdwc->dev, "Abort PM suspend!! (USB is outside LPM)\n"); + return -EBUSY; + } + + atomic_set(&mdwc->pm_suspended, 1); + + return 0; + } + + /* + * PHYs also need to be power collapsed, so call notify_disconnect + * before suspend to ensure it. + */ + usb_phy_notify_disconnect(mdwc->hs_phy, USB_SPEED_HIGH); + mdwc->hs_phy->flags &= ~PHY_HOST_MODE; + usb_phy_notify_disconnect(mdwc->ss_phy, USB_SPEED_SUPER); + mdwc->ss_phy->flags &= ~PHY_HOST_MODE; + } + + /* + * Power collapse the core. Hence call dwc3_msm_suspend with + * 'force_power_collapse' set to 'true'. + */ + ret = dwc3_msm_suspend(mdwc, true); + if (!ret) + atomic_set(&mdwc->pm_suspended, 1); + + return ret; +} + +static int dwc3_msm_pm_resume(struct device *dev) +{ + int ret; + struct dwc3_msm *mdwc = dev_get_drvdata(dev); + + dev_dbg(dev, "dwc3-msm PM resume\n"); + dbg_event(0xFF, "PM Res", 0); + + atomic_set(&mdwc->pm_suspended, 0); + + /* Let DWC3 core complete determine if resume is needed */ + if (!mdwc->in_host_mode) + return 0; + + /* Resume dwc to avoid unclocked access by xhci_plat_resume */ + ret = dwc3_msm_resume(mdwc); + if (ret) { + dev_err(mdwc->dev, "%s: dwc3_msm_resume failed\n", __func__); + atomic_set(&mdwc->pm_suspended, 1); + return ret; + } + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + if (mdwc->host_poweroff_in_pm_suspend && mdwc->in_host_mode) { + /* Restore PHY flags if hibernated in host mode */ + mdwc->hs_phy->flags |= PHY_HOST_MODE; + usb_phy_notify_connect(mdwc->hs_phy, USB_SPEED_HIGH); + if (dwc3_msm_get_max_speed(mdwc) >= USB_SPEED_SUPER) { + mdwc->ss_phy->flags |= PHY_HOST_MODE; + usb_phy_notify_connect(mdwc->ss_phy, + USB_SPEED_SUPER); + } + } + /* kick in otg state machine */ + queue_work(mdwc->dwc3_wq, &mdwc->resume_work); + + return 0; +} + +static void dwc3_host_complete(struct device *dev) +{ + int ret = 0; + + if (dev->power.direct_complete) { + ret = pm_runtime_resume(dev); + if (ret < 0) { + dev_err(dev, "failed to runtime resume, ret %d\n", ret); + return; + } + } +} + +static int dwc3_host_prepare(struct device *dev) +{ + /* + * It is recommended to use the PM prepare callback to handle situations + * where the device is already runtime suspended, in order to avoid + * executing the PM suspend callback (duplicate suspend). When the + * prepare callback returns a positive value, the PM core will set the + * direct_complete parameter to true, and avoid calling the PM suspend + * and PM resume callbacks, and allowing the driver to issue a resume + * using PM runtime instead. (within the complete() callback) + */ + if (pm_runtime_enabled(dev) && pm_runtime_suspended(dev)) + return 1; + + return 0; +} + +static int dwc3_core_prepare(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + + dbg_event(0xFF, "Core PM prepare", pm_runtime_suspended(dev)); + /* + * It is recommended to use the PM prepare callback to handle situations + * where the device is already runtime suspended, in order to avoid + * executing the PM suspend callback (duplicate suspend). When the + * prepare callback returns a positive value, the PM core will set the + * direct_complete parameter to true, and avoid calling the PM suspend + * and PM resume callbacks, and allowing the driver to issue a resume + * using PM runtime instead. (within the complete() callback) + */ + if (pm_runtime_enabled(dev) && pm_runtime_suspended(dev)) + return 1; + + if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_DEVICE && + !pm_runtime_suspended(dev)) { + dev_info(dev, "%s: peripheral mode still active\n", __func__); + return -EBUSY; + } + + return 0; +} + +static void dwc3_core_complete(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + + /* + * In the PM devices documentation, while leaving system suspend when + * the device is in the RPM suspended state, it is recommended to use + * the direct_complete flag to determine if an explicit runtime resume + * needs to be executed. However, in DWC3 MSM case, we can allow changes + * to cable status, or XHCI status to wake up the DWC3 core. + */ + dbg_event(0xFF, "Core PM complete", dev->power.direct_complete); +} +#endif + +#ifdef CONFIG_PM +static int dwc3_msm_runtime_idle(struct device *dev) +{ + struct dwc3_msm *mdwc = dev_get_drvdata(dev); + + dev_dbg(dev, "DWC3-msm runtime idle\n"); + dbg_event(0xFF, "RT Idle", 0); + + return 0; +} + +static int dwc3_msm_runtime_suspend(struct device *dev) +{ + struct dwc3_msm *mdwc = dev_get_drvdata(dev); + struct dwc3 *dwc = NULL; + + if (mdwc->dwc3) + dwc = platform_get_drvdata(mdwc->dwc3); + + dev_dbg(dev, "DWC3-msm runtime suspend\n"); + dbg_event(0xFF, "RT Sus", 0); + + if (dwc) + device_init_wakeup(dwc->dev, false); + + return dwc3_msm_suspend(mdwc, false); +} + +static int dwc3_msm_runtime_resume(struct device *dev) +{ + struct dwc3_msm *mdwc = dev_get_drvdata(dev); + + dev_dbg(dev, "DWC3-msm runtime resume\n"); + dbg_event(0xFF, "RT Res", 0); + + return dwc3_msm_resume(mdwc); +} +#endif + +static const struct dev_pm_ops dwc3_msm_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(dwc3_msm_pm_suspend, dwc3_msm_pm_resume) + SET_RUNTIME_PM_OPS(dwc3_msm_runtime_suspend, dwc3_msm_runtime_resume, + dwc3_msm_runtime_idle) +}; + +static const struct of_device_id of_dwc3_matach[] = { + { + .compatible = "qcom,dwc-usb3-msm", + }, + { }, +}; +MODULE_DEVICE_TABLE(of, of_dwc3_matach); + +static struct platform_driver dwc3_msm_driver = { + .probe = dwc3_msm_probe, + .remove = dwc3_msm_remove, + .driver = { + .name = "msm-dwc3", + .pm = &dwc3_msm_dev_pm_ops, + .of_match_table = of_dwc3_matach, + .dev_groups = dwc3_msm_groups, + }, +}; + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("DesignWare USB3 MSM Glue Layer"); +MODULE_SOFTDEP("pre: phy-generic phy-msm-snps-hs phy-msm-ssusb-qmp eud"); + +static int dwc3_msm_init(void) +{ + dwc3_msm_kretprobe_init(); + return platform_driver_register(&dwc3_msm_driver); +} +module_init(dwc3_msm_init); + +static void __exit dwc3_msm_exit(void) +{ + platform_driver_unregister(&dwc3_msm_driver); + dwc3_msm_kretprobe_exit(); +} +module_exit(dwc3_msm_exit); diff --git a/drivers/usb/dwc3/dwc3-msm-ops.c b/drivers/usb/dwc3/dwc3-msm-ops.c new file mode 100644 index 000000000000..f0b58df42b66 --- /dev/null +++ b/drivers/usb/dwc3/dwc3-msm-ops.c @@ -0,0 +1,258 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2012-2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "core.h" +#include "debug-ipc.h" +#include "gadget.h" + +struct kprobe_data { + struct dwc3 *dwc; + int xi0; +}; + +static int entry_dwc3_gadget_run_stop(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct dwc3 *dwc = (struct dwc3 *)regs->regs[0]; + int is_on = (int)regs->regs[1]; + + if (is_on) { + /* + * DWC3 gadget IRQ uses a threaded handler which normally runs + * at SCHED_FIFO priority. If it gets busy processing a high + * volume of events (usually EP events due to heavy traffic) it + * can potentially starve non-RT taks from running and trigger + * RT throttling in the scheduler; on some build configs this + * will panic. So lower the thread's priority to run as non-RT + * (with a nice value equivalent to a high-priority workqueue). + * It has been found to not have noticeable performance impact. + */ + struct irq_desc *irq_desc = irq_to_desc(dwc->irq_gadget); + struct irqaction *action = irq_desc ? irq_desc->action : NULL; + + for ( ; action != NULL; action = action->next) { + if (action->thread) { + dev_info(dwc->dev, "Set IRQ thread:%s pid:%d to SCHED_NORMAL prio\n", + action->thread->comm, action->thread->pid); + sched_set_normal(action->thread, MIN_NICE); + break; + } + } + } else { + dwc3_core_stop_hw_active_transfers(dwc); + dwc3_msm_notify_event(dwc, DWC3_GSI_EVT_BUF_CLEAR, 0); + dwc3_msm_notify_event(dwc, DWC3_CONTROLLER_NOTIFY_CLEAR_DB, 0); + } + + return 0; +} + +static int entry_dwc3_send_gadget_ep_cmd(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct dwc3_ep *dep = (struct dwc3_ep *)regs->regs[0]; + unsigned int cmd = (unsigned int)regs->regs[1]; + struct dwc3 *dwc = dep->dwc; + + if (cmd == DWC3_DEPCMD_ENDTRANSFER) + dwc3_msm_notify_event(dwc, + DWC3_CONTROLLER_NOTIFY_DISABLE_UPDXFER, + dep->number); + + return 0; +} + +static int entry_dwc3_gadget_reset_interrupt(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct dwc3 *dwc = (struct dwc3 *)regs->regs[0]; + + dwc3_msm_notify_event(dwc, DWC3_CONTROLLER_NOTIFY_CLEAR_DB, 0); + return 0; +} + +static int entry_dwc3_gadget_conndone_interrupt(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct kprobe_data *data = (struct kprobe_data *)ri->data; + + data->dwc = (struct dwc3 *)regs->regs[0]; + return 0; +} + +static int exit_dwc3_gadget_conndone_interrupt(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct kprobe_data *data = (struct kprobe_data *)ri->data; + + dwc3_msm_notify_event(data->dwc, DWC3_CONTROLLER_CONNDONE_EVENT, 0); + + return 0; +} + +static int entry_dwc3_gadget_pullup(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct kprobe_data *data = (struct kprobe_data *)ri->data; + struct usb_gadget *g = (struct usb_gadget *)regs->regs[0]; + + data->dwc = gadget_to_dwc(g); + data->xi0 = (int)regs->regs[1]; + dwc3_msm_notify_event(data->dwc, DWC3_CONTROLLER_PULLUP_ENTER, + data->xi0); + + return 0; +} + +static int exit_dwc3_gadget_pullup(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct kprobe_data *data = (struct kprobe_data *)ri->data; + + dwc3_msm_notify_event(data->dwc, DWC3_CONTROLLER_PULLUP_EXIT, + data->xi0); + + return 0; +} + +static int entry_trace_dwc3_ctrl_req(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct usb_ctrlrequest *ctrl = (struct usb_ctrlrequest *)regs->regs[0]; + + dbg_trace_ctrl_req(ctrl); + + return 0; +} + +static int entry_trace_dwc3_ep_queue(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct dwc3_request *req = (struct dwc3_request *)regs->regs[0]; + + dbg_trace_ep_queue(req); + + return 0; +} + +static int entry_trace_dwc3_ep_dequeue(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct dwc3_request *req = (struct dwc3_request *)regs->regs[0]; + + dbg_trace_ep_dequeue(req); + + return 0; +} + +static int entry_trace_dwc3_gadget_giveback(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct dwc3_request *req = (struct dwc3_request *)regs->regs[0]; + + dbg_trace_gadget_giveback(req); + + return 0; +} + +static int entry_trace_dwc3_gadget_ep_cmd(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct dwc3_ep *dep = (struct dwc3_ep *)regs->regs[0]; + unsigned int cmd = regs->regs[1]; + struct dwc3_gadget_ep_cmd_params *param = (struct dwc3_gadget_ep_cmd_params *)regs->regs[2]; + int cmd_status = regs->regs[3]; + + dbg_trace_gadget_ep_cmd(dep, cmd, param, cmd_status); + + return 0; +} + +static int entry_trace_dwc3_prepare_trb(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + struct dwc3_ep *dep = (struct dwc3_ep *)regs->regs[0]; + struct dwc3_trb *trb = (struct dwc3_trb *)regs->regs[1]; + + dbg_trace_trb_prepare(dep, trb); + + return 0; +} + +static int entry_trace_dwc3_event(struct kretprobe_instance *ri, + struct pt_regs *regs) +{ + u32 event = regs->regs[0]; + struct dwc3 *dwc = (struct dwc3 *)regs->regs[1]; + + dbg_trace_event(event, dwc); + + return 0; +} + +#define ENTRY_EXIT(name) {\ + .handler = exit_##name,\ + .entry_handler = entry_##name,\ + .data_size = sizeof(struct kprobe_data),\ + .maxactive = 8,\ + .kp.symbol_name = #name,\ +} + +#define ENTRY(name) {\ + .entry_handler = entry_##name,\ + .data_size = sizeof(struct kprobe_data),\ + .maxactive = 8,\ + .kp.symbol_name = #name,\ +} + +static struct kretprobe dwc3_msm_probes[] = { + ENTRY(dwc3_gadget_run_stop), + ENTRY(dwc3_send_gadget_ep_cmd), + ENTRY(dwc3_gadget_reset_interrupt), + ENTRY_EXIT(dwc3_gadget_conndone_interrupt), + ENTRY_EXIT(dwc3_gadget_pullup), + ENTRY(trace_dwc3_ctrl_req), + ENTRY(trace_dwc3_ep_queue), + ENTRY(trace_dwc3_ep_dequeue), + ENTRY(trace_dwc3_gadget_giveback), + ENTRY(trace_dwc3_gadget_ep_cmd), + ENTRY(trace_dwc3_prepare_trb), + ENTRY(trace_dwc3_event), +}; + + +int dwc3_msm_kretprobe_init(void) +{ + int ret; + int i; + + for (i = 0; i < ARRAY_SIZE(dwc3_msm_probes) ; i++) { + ret = register_kretprobe(&dwc3_msm_probes[i]); + if (ret < 0) { + pr_err("register_kretprobe failed, returned %d\n", ret); + return ret; + } + } + + return 0; +} + +void dwc3_msm_kretprobe_exit(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(dwc3_msm_probes); i++) + unregister_kretprobe(&dwc3_msm_probes[i]); +} + diff --git a/drivers/usb/phy/Kconfig b/drivers/usb/phy/Kconfig index 52eebcb88c1f..1051e0a3a3b7 100644 --- a/drivers/usb/phy/Kconfig +++ b/drivers/usb/phy/Kconfig @@ -192,4 +192,47 @@ config JZ4770_PHY This driver provides PHY support for the USB controller found on the JZ-series and X-series SoCs from Ingenic. +config USB_QCOM_EMU_PHY + tristate "Qualcomm Technologies, Inc. emulation USB PHY driver" + depends on ARCH_QCOM || COMPILE_TEST + select USB_PHY + help + Enable this to support the USB transceiver used on + Qualcomm Technologies, Inc. emulation platforms. It simply performs + PHY initialization given a basic register write sequence. + + To compile this driver as a module, choose M here: the + module will be called phy-qcom-emu. + +config USB_MSM_SSPHY_QMP + tristate "MSM SSUSB QMP PHY Driver" + depends on ARCH_QCOM || COMPILE_TEST + select USB_PHY + help + Enable this to support the SuperSpeed USB transceiver on MSM chips. + This driver supports the PHY which uses the QSCRATCH-based register + set for its control sequences, normally paired with newer DWC3-based + SuperSpeed controllers. + +config MSM_HSUSB_PHY + tristate "MSM HSUSB PHY Driver" + depends on ARCH_QCOM || COMPILE_TEST + select USB_PHY + help + Enable this to support the HSUSB PHY on MSM chips. This driver supports + the high-speed PHY which is usually paired with either the ChipIdea or + Synopsys DWC3 USB IPs on MSM SOCs. This driver expects to configure the + PHY with a dedicated register I/O memory region. + +config USB_MSM_EUSB2_PHY + tristate "MSM EUSB2 PHY Driver" + depends on ARCH_QCOM || COMPILE_TEST + select USB_PHY + help + Enable this to support the USB EUSB2 PHY on MSM chips. This driver + supports reset and initialization sequence, and also perform required + set of operations with used repeater for USB HS/FS/LS functionality. + + To compile this driver as a module, choose M here. + endmenu diff --git a/drivers/usb/phy/Makefile b/drivers/usb/phy/Makefile index b352bdbe8712..3715d0ba0b65 100644 --- a/drivers/usb/phy/Makefile +++ b/drivers/usb/phy/Makefile @@ -25,3 +25,7 @@ obj-$(CONFIG_USB_ULPI) += phy-ulpi.o obj-$(CONFIG_USB_ULPI_VIEWPORT) += phy-ulpi-viewport.o obj-$(CONFIG_KEYSTONE_USB_PHY) += phy-keystone.o obj-$(CONFIG_JZ4770_PHY) += phy-jz4770.o +obj-$(CONFIG_USB_QCOM_EMU_PHY) += phy-qcom-emu.o +obj-$(CONFIG_USB_MSM_SSPHY_QMP) += phy-msm-ssusb-qmp.o +obj-$(CONFIG_MSM_HSUSB_PHY) += phy-msm-snps-hs.o +obj-$(CONFIG_USB_MSM_EUSB2_PHY) += phy-msm-snps-eusb2.o diff --git a/drivers/usb/phy/phy-msm-snps-eusb2.c b/drivers/usb/phy/phy-msm-snps-eusb2.c new file mode 100644 index 000000000000..e5dc1e51c49e --- /dev/null +++ b/drivers/usb/phy/phy-msm-snps-eusb2.c @@ -0,0 +1,1088 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2021-2022, Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#define pr_fmt(fmt) "eusb2_phy: %s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define USB_PHY_UTMI_CTRL0 (0x3c) +#define OPMODE_MASK (0x3 << 3) +#define OPMODE_NONDRIVING (0x1 << 3) +#define SLEEPM BIT(0) + +#define USB_PHY_UTMI_CTRL5 (0x50) +#define POR BIT(1) + +#define USB_PHY_HS_PHY_CTRL_COMMON0 (0x54) +#define PHY_ENABLE BIT(0) +#define SIDDQ_SEL BIT(1) +#define SIDDQ BIT(2) +#define RETENABLEN BIT(3) +#define FSEL (0x7 << 4) +#define FSEL_19_2_MHZ_VAL (0x0 << 4) +#define FSEL_38_4_MHZ_VAL (0x4 << 4) + +#define USB_PHY_CFG_CTRL_1 (0x58) +#define PHY_CFG_PLL_CPBIAS_CNTRL (0xfe) +#define PHY_CFG_PLL_CPBIAS_CNTRL_SHIFT (0x1) + +#define USB_PHY_CFG_CTRL_2 (0x5c) +#define PHY_CFG_PLL_FB_DIV_7_0 (0xff) +#define DIV_7_0_19_2_MHZ_VAL (0x90) +#define DIV_7_0_38_4_MHZ_VAL (0xc8) + +#define USB_PHY_CFG_CTRL_3 (0x60) +#define PHY_CFG_PLL_FB_DIV_11_8 (0xf) +#define DIV_11_8_19_2_MHZ_VAL (0x1) +#define DIV_11_8_38_4_MHZ_VAL (0x0) + +#define PHY_CFG_PLL_REF_DIV (0xf << 4) +#define PLL_REF_DIV_VAL (0x0) + +#define USB_PHY_HS_PHY_CTRL2 (0x64) +#define VBUSVLDEXT0 BIT(0) +#define USB2_SUSPEND_N BIT(2) +#define USB2_SUSPEND_N_SEL BIT(3) +#define VBUS_DET_EXT_SEL BIT(4) + +#define USB_PHY_CFG_CTRL_4 (0x68) +#define PHY_CFG_PLL_GMP_CNTRL (0x3) +#define PHY_CFG_PLL_GMP_CNTRL_SHIFT (0x0) +#define PHY_CFG_PLL_INT_CNTRL (0xfc) +#define PHY_CFG_PLL_INT_CNTRL_SHIFT (0x2) + +#define USB_PHY_CFG_CTRL_5 (0x6c) +#define PHY_CFG_PLL_PROP_CNTRL (0x1f) +#define PHY_CFG_PLL_PROP_CNTRL_SHIFT (0x0) +#define PHY_CFG_PLL_VREF_TUNE (0x3 << 6) +#define PHY_CFG_PLL_VREF_TUNE_SHIFT (6) + +#define USB_PHY_CFG_CTRL_6 (0x70) +#define PHY_CFG_PLL_VCO_CNTRL (0x7) +#define PHY_CFG_PLL_VCO_CNTRL_SHIFT (0x0) + +#define USB_PHY_CFG_CTRL_7 (0x74) + +#define USB_PHY_CFG_CTRL_8 (0x78) +#define PHY_CFG_TX_FSLS_VREF_TUNE (0x3) +#define PHY_CFG_TX_FSLS_VREG_BYPASS BIT(2) +#define PHY_CFG_TX_HS_VREF_TUNE (0x7 << 3) +#define PHY_CFG_TX_HS_VREF_TUNE_SHIFT (0x3) +#define PHY_CFG_TX_HS_XV_TUNE (0x3 << 6) +#define PHY_CFG_TX_HS_XV_TUNE_SHIFT (6) + +#define USB_PHY_CFG_CTRL_9 (0x7c) +#define PHY_CFG_TX_PREEMP_TUNE (0x7) +#define PHY_CFG_TX_PREEMP_TUNE_SHIFT (0x0) +#define PHY_CFG_TX_RES_TUNE (0x3 << 3) +#define PHY_CFG_TX_RES_TUNE_SHIFT (0x3) +#define PHY_CFG_TX_RISE_TUNE (0x3 << 5) +#define PHY_CFG_TX_RISE_TUNE_SHIFT (0x5) +#define PHY_CFG_RCAL_BYPASS BIT(7) +#define PHY_CFG_RCAL_BYPASS_SHIFT (0x7) + +#define USB_PHY_CFG_CTRL_10 (0x80) + +#define USB_PHY_CFG0 (0x94) +#define DATAPATH_CTRL_OVERRIDE_EN BIT(0) +#define CMN_CTRL_OVERRIDE_EN BIT(1) + +#define UTMI_PHY_CMN_CTRL0 (0x98) +#define TESTBURNIN BIT(6) + +#define USB_PHY_FSEL_SEL (0xb8) +#define FSEL_SEL BIT(0) + +#define USB_PHY_APB_ACCESS_CMD (0x130) +#define RW_ACCESS BIT(0) +#define APB_START_CMD BIT(1) +#define APB_LOGIC_RESET BIT(2) + +#define USB_PHY_APB_ACCESS_STATUS (0x134) +#define ACCESS_DONE BIT(0) +#define TIMED_OUT BIT(1) +#define ACCESS_ERROR BIT(2) +#define ACCESS_IN_PROGRESS BIT(3) + +#define USB_PHY_APB_ADDRESS (0x138) +#define APB_REG_ADDR (0xff) + +#define USB_PHY_APB_WRDATA_LSB (0x13c) +#define APB_REG_WRDATA_7_0 (0xf) + +#define USB_PHY_APB_WRDATA_MSB (0x140) +#define APB_REG_WRDATA_15_8 (0xf0) + +#define USB_PHY_APB_RDDATA_LSB (0x144) +#define APB_REG_RDDATA_7_0 (0xf) + +#define USB_PHY_APB_RDDATA_MSB (0x148) +#define APB_REG_RDDATA_15_8 (0xf0) + +/* EUD CSR field */ +#define EUD_EN2 BIT(0) + +/* VIOCTL_EUD_DETECT register based EUD_DETECT field */ +#define EUD_DETECT BIT(0) + +#define USB_HSPHY_1P2_VOL_MIN 1200000 /* uV */ +#define USB_HSPHY_1P2_VOL_MAX 1200000 /* uV */ +#define USB_HSPHY_1P2_HPM_LOAD 5905 /* uA */ +#define USB_HSPHY_VDD_HPM_LOAD 7757 /* uA */ + +struct msm_eusb2_phy { + struct usb_phy phy; + void __iomem *base; + + /* EUD related parameters */ + phys_addr_t eud_reg; + void __iomem *eud_enable_reg; + void __iomem *eud_detect_reg; + bool re_enable_eud; + + struct clk *ref_clk_src; + struct clk *ref_clk; + struct reset_control *phy_reset; + + struct regulator *vdd; + struct regulator *vdda12; + int vdd_levels[3]; /* none, low, high */ + + bool clocks_enabled; + bool power_enabled; + bool suspended; + bool cable_connected; + + struct power_supply *usb_psy; + unsigned int vbus_draw; + struct work_struct vbus_draw_work; + + int *param_override_seq; + int param_override_seq_cnt; + + /* debugfs entries */ + struct dentry *root; + u8 tx_pre_emphasis; + u8 tx_rise_fall_time; + u8 tx_src_impedence; + u8 tx_dc_vref; + u8 tx_xv; + + struct usb_repeater *ur; +}; + +static inline bool is_eud_debug_mode_active(struct msm_eusb2_phy *phy) +{ + if (phy->eud_enable_reg && + (readl_relaxed(phy->eud_enable_reg) & EUD_EN2)) + return true; + + return false; +} + +static void msm_eusb2_phy_clocks(struct msm_eusb2_phy *phy, bool on) +{ + dev_dbg(phy->phy.dev, "clocks_enabled:%d on:%d\n", + phy->clocks_enabled, on); + + if (phy->clocks_enabled == on) + return; + + if (on) { + clk_prepare_enable(phy->ref_clk_src); + clk_prepare_enable(phy->ref_clk); + } else { + clk_disable_unprepare(phy->ref_clk); + clk_disable_unprepare(phy->ref_clk_src); + } + + phy->clocks_enabled = on; +} + +static void msm_eusb2_phy_update_eud_detect(struct msm_eusb2_phy *phy, bool set) +{ + if (set) + writel_relaxed(EUD_DETECT, phy->eud_detect_reg); + else + writel_relaxed(readl_relaxed(phy->eud_detect_reg) & ~EUD_DETECT, + phy->eud_detect_reg); +} + +static int msm_eusb2_phy_power(struct msm_eusb2_phy *phy, bool on) +{ + int ret = 0; + + dev_dbg(phy->phy.dev, "turn %s regulators. power_enabled:%d\n", + on ? "on" : "off", phy->power_enabled); + + if (phy->power_enabled == on) + return 0; + + if (!on) + goto clear_eud_det; + + ret = regulator_set_load(phy->vdd, USB_HSPHY_VDD_HPM_LOAD); + if (ret < 0) { + dev_err(phy->phy.dev, "Unable to set HPM of vdd:%d\n", ret); + goto err_vdd; + } + + ret = regulator_set_voltage(phy->vdd, phy->vdd_levels[1], + phy->vdd_levels[2]); + if (ret) { + dev_err(phy->phy.dev, "Unable to set voltage for hsusb vdd\n"); + goto put_vdd_lpm; + } + + ret = regulator_enable(phy->vdd); + if (ret) { + dev_err(phy->phy.dev, "Unable to enable VDD\n"); + goto unconfig_vdd; + } + + ret = regulator_set_load(phy->vdda12, USB_HSPHY_1P2_HPM_LOAD); + if (ret < 0) { + dev_err(phy->phy.dev, "Unable to set HPM of vdda12:%d\n", ret); + goto disable_vdd; + } + + ret = regulator_set_voltage(phy->vdda12, USB_HSPHY_1P2_VOL_MIN, + USB_HSPHY_1P2_VOL_MAX); + if (ret) { + dev_err(phy->phy.dev, + "Unable to set voltage for vdda12:%d\n", ret); + goto put_vdda12_lpm; + } + + ret = regulator_enable(phy->vdda12); + if (ret) { + dev_err(phy->phy.dev, "Unable to enable vdda12:%d\n", ret); + goto unset_vdda12; + } + + /* Make sure all the writes are processed before setting EUD_DETECT */ + mb(); + /* Set eud_detect_reg after powering on eUSB PHY rails to bring EUD out of reset */ + msm_eusb2_phy_update_eud_detect(phy, true); + + phy->power_enabled = true; + pr_debug("eUSB2_PHY's regulators are turned ON.\n"); + return ret; + +clear_eud_det: + /* Clear eud_detect_reg to put EUD in reset */ + msm_eusb2_phy_update_eud_detect(phy, false); + + /* Make sure clearing EUD_DETECT is completed before turning off the regulators */ + mb(); + + ret = regulator_disable(phy->vdda12); + if (ret) + dev_err(phy->phy.dev, "Unable to disable vdda12:%d\n", ret); + +unset_vdda12: + ret = regulator_set_voltage(phy->vdda12, 0, USB_HSPHY_1P2_VOL_MAX); + if (ret) + dev_err(phy->phy.dev, + "Unable to set (0) voltage for vdda12:%d\n", ret); + +put_vdda12_lpm: + ret = regulator_set_load(phy->vdda12, 0); + if (ret < 0) + dev_err(phy->phy.dev, "Unable to set LPM of vdda12\n"); + +disable_vdd: + ret = regulator_disable(phy->vdd); + if (ret) + dev_err(phy->phy.dev, "Unable to disable vdd:%d\n", ret); + +unconfig_vdd: + ret = regulator_set_voltage(phy->vdd, phy->vdd_levels[0], + phy->vdd_levels[2]); + if (ret) + dev_err(phy->phy.dev, "unable to set voltage for hsusb vdd\n"); + +put_vdd_lpm: + ret = regulator_set_load(phy->vdd, 0); + if (ret < 0) + dev_err(phy->phy.dev, "Unable to set LPM of vdd\n"); + + /* case handling when regulator turning on failed */ + if (!phy->power_enabled) + return -EINVAL; + +err_vdd: + phy->power_enabled = false; + dev_dbg(phy->phy.dev, "eusb2_PHY's regulators are turned OFF.\n"); + return ret; +} + +static void msm_eusb2_write_readback(void __iomem *base, u32 offset, + const u32 mask, u32 val) +{ + u32 write_val, tmp = readl_relaxed(base + offset); + + tmp &= ~mask; + write_val = tmp | val; + + writel_relaxed(write_val, base + offset); + + /* Read back to see if val was written */ + tmp = readl_relaxed(base + offset); + tmp &= mask; + + if (tmp != val) + pr_err("write: %x to offset: %x FAILED\n", val, offset); +} + +static void eusb2_phy_reset_seq(struct msm_eusb2_phy *phy) +{ + writel(APB_LOGIC_RESET, phy->base + USB_PHY_APB_ACCESS_CMD); + writel(0x00, phy->base + USB_PHY_APB_ACCESS_CMD); +} + +#define APB_ACCESS_TIMEOUT 10 /* in us */ +#define APB_ACCESS_POLL_DELAY 1 /* in us */ +#define APB_READ_ACCESS_DONE 1 + +static int eusb2_phy_apb_cmd_wait(struct msm_eusb2_phy *phy) +{ + int ret; + u8 val; /* to read cmd status */ + + /* poll for cmd completion */ + ret = readl_poll_timeout(phy->base + USB_PHY_APB_ACCESS_STATUS, + val, APB_READ_ACCESS_DONE, + APB_ACCESS_POLL_DELAY, APB_ACCESS_TIMEOUT); + if (ret < 0) { + dev_err(phy->phy.dev, "APB_ACCESS_STAUS(%x) timeout\n", val); + eusb2_phy_reset_seq(phy); + return val; + } + + return 0; +} + +static void eusb2_phy_apb_reg_write(struct msm_eusb2_phy *phy, + u8 reg_index, u16 val) +{ + int ret; + + /* program register index to update requested register */ + writel(reg_index, phy->base + USB_PHY_APB_ADDRESS); + + /* value to be program */ + writel(((val >> 8) & 0xF), phy->base + USB_PHY_APB_WRDATA_MSB); + writel((val & 0xF), phy->base + USB_PHY_APB_WRDATA_LSB); + + /* send cmd to update reg_index with above programmed value */ + writel(RW_ACCESS | APB_START_CMD, phy->base + USB_PHY_APB_ACCESS_CMD); + + /* poll for cmd completion */ + ret = eusb2_phy_apb_cmd_wait(phy); + if (ret) { + dev_err(phy->phy.dev, "APB reg(%x) write failed\n", reg_index); + return; + } + + /* write access completed */ + writel(0x0, phy->base + USB_PHY_APB_ACCESS_CMD); + dev_info(phy->phy.dev, "APB reg(%x) updated with %x\n", reg_index, val); +} + +static void eusb2_phy_apb_reg_read(struct msm_eusb2_phy *phy, u8 reg_index) +{ + int ret; + u32 rddata_lsb; + u32 rddata_msb; + + /* program register which is required to read */ + writel(reg_index, phy->base + USB_PHY_APB_ADDRESS); + + /* send cmd to read reg_index based register value */ + writel(APB_START_CMD, phy->base + USB_PHY_APB_ACCESS_CMD); + + /* poll for cmd completion */ + ret = eusb2_phy_apb_cmd_wait(phy); + if (ret) { + dev_err(phy->phy.dev, "APB reg(%x) read failed\n", reg_index); + return; + } + + /* read data of reg_index register */ + rddata_lsb = readl(phy->base + USB_PHY_APB_RDDATA_LSB); + rddata_msb = readl(phy->base + USB_PHY_APB_RDDATA_MSB); + + /* read access completed */ + writel(0x0, phy->base + USB_PHY_APB_ACCESS_CMD); + dev_info(phy->phy.dev, "APB reg(%x) read success, val:%x\n", + reg_index, ((rddata_msb << 8) | rddata_lsb)); +} + +static int apb_reg_rw_open(struct inode *inode, struct file *file) +{ + return single_open(file, NULL, inode->i_private); +} + +static ssize_t apb_reg_rw_write(struct file *file, + const char __user *ubuf, size_t count, loff_t *ppos) +{ + struct seq_file *s = file->private_data; + struct msm_eusb2_phy *phy = s->private; + char buf[32]; + bool read; + char *field, *p; + u8 reg_index; + u16 val; + + if (!phy->cable_connected) { + dev_err(phy->phy.dev, "eUSB2 PHY is not out of reset.\n"); + return -EINVAL; + } + + if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) + return -EFAULT; + + /* + * For reading APB register: read e.g. read 0x33 + * For writing value into APB register: write + * e.g. write 0xff 0xd + * Valid range for offset: 0x00 - 0xFF + * Valid range for value: 0x0000 - 0xFFFF + */ + p = buf; + field = strsep(&p, " "); + if (!field || !*field) + goto err_out; + + if (strcmp(field, "read") == 0) + read = true; + else if (strcmp(field, "write") == 0) + read = false; + else + goto err_out; + + /* get register offset */ + field = strsep(&p, " "); + if (!field) { + dev_err(phy->phy.dev, "Provide register offset\n"); + goto err_out; + } + + if (kstrtou8(field, 16, ®_index)) { + dev_err(phy->phy.dev, "Invalid register offset\n"); + goto err_out; + } + + /* read apb register */ + if (read) { + eusb2_phy_apb_reg_read(phy, reg_index); + goto done; + } + + /* get value to update register for write */ + field = strsep(&p, " "); + if (!field) { + dev_err(phy->phy.dev, "Provide value to update register\n"); + goto err_out; + } + + if (kstrtou16(field, 16, &val)) { + dev_err(phy->phy.dev, "Invalid value\n"); + goto err_out; + } + + eusb2_phy_apb_reg_write(phy, reg_index, val); + goto done; + +err_out: + pr_info("Error: enter proper values to read OR write APB register.\n"); + pr_info("To read APB register: read \n"); + pr_info("To write APB register: write \n"); + pr_info("Valid range for offset: 0x00 - 0xFF\n"); + pr_info("Valid range for value: 0x0000 - 0xFFFF\n"); + +done: + return count; +} + +static const struct file_operations apb_reg_rw_fops = { + .open = apb_reg_rw_open, + .write = apb_reg_rw_write, + .release = single_release, +}; + +static void msm_eusb2_phy_reset(struct msm_eusb2_phy *phy) +{ + int ret; + + ret = reset_control_assert(phy->phy_reset); + if (ret) + dev_err(phy->phy.dev, "phy reset assert failed\n"); + + usleep_range(100, 150); + + ret = reset_control_deassert(phy->phy_reset); + if (ret) + dev_err(phy->phy.dev, "phy reset deassert failed\n"); +} + +static void eusb2_phy_write_seq(void __iomem *base, u32 *seq, int cnt) +{ + int i; + + pr_debug("Seq count:%d\n", cnt); + for (i = 0; i < cnt; i = i+2) { + pr_debug("write 0x%02x to 0x%02x\n", seq[i], seq[i+1]); + writel_relaxed(seq[i], base + seq[i+1]); + } +} + +static void msm_eusb2_parameter_override(struct msm_eusb2_phy *phy) +{ + /* default parameters: tx pre-emphasis */ + msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_9, + PHY_CFG_TX_PREEMP_TUNE, (0 << PHY_CFG_TX_PREEMP_TUNE_SHIFT)); + + /* tx rise/fall time */ + msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_9, + PHY_CFG_TX_RISE_TUNE, (0x2 << PHY_CFG_TX_RISE_TUNE_SHIFT)); + + /* source impedance adjustment */ + msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_9, + PHY_CFG_TX_RES_TUNE, (0x1 << PHY_CFG_TX_RES_TUNE_SHIFT)); + + /* dc voltage level adjustement */ + msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_8, + PHY_CFG_TX_HS_VREF_TUNE, (0x3 << PHY_CFG_TX_HS_VREF_TUNE_SHIFT)); + + /* transmitter HS crossover adjustement */ + msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_8, + PHY_CFG_TX_HS_XV_TUNE, (0x0 << PHY_CFG_TX_HS_XV_TUNE_SHIFT)); + + /* override init sequence using devicetree based values */ + eusb2_phy_write_seq(phy->base, phy->param_override_seq, + phy->param_override_seq_cnt); + + /* override tune params using debugfs based values */ + if (phy->tx_pre_emphasis && phy->tx_pre_emphasis <= 7) + msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_9, + PHY_CFG_TX_PREEMP_TUNE, + (phy->tx_pre_emphasis << PHY_CFG_TX_PREEMP_TUNE_SHIFT)); + + if (phy->tx_rise_fall_time && phy->tx_rise_fall_time <= 4) + msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_9, + PHY_CFG_TX_RISE_TUNE, + (phy->tx_rise_fall_time << PHY_CFG_TX_RISE_TUNE_SHIFT)); + + if (phy->tx_src_impedence && phy->tx_src_impedence <= 4) + msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_9, + PHY_CFG_TX_RES_TUNE, + (phy->tx_src_impedence << PHY_CFG_TX_RES_TUNE_SHIFT)); + + if (phy->tx_dc_vref && phy->tx_dc_vref <= 7) + msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_8, + PHY_CFG_TX_HS_VREF_TUNE, + (phy->tx_dc_vref << PHY_CFG_TX_HS_VREF_TUNE_SHIFT)); + + if (phy->tx_xv && phy->tx_xv <= 4) + msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_8, + PHY_CFG_TX_HS_XV_TUNE, + (phy->tx_xv << PHY_CFG_TX_HS_XV_TUNE_SHIFT)); +} + +static void msm_eusb2_ref_clk_init(struct usb_phy *uphy) +{ + unsigned long ref_clk_freq; + struct msm_eusb2_phy *phy = container_of(uphy, struct msm_eusb2_phy, phy); + + ref_clk_freq = clk_get_rate(phy->ref_clk_src); + switch (ref_clk_freq) { + case 19200000: + msm_eusb2_write_readback(phy->base, USB_PHY_HS_PHY_CTRL_COMMON0, + FSEL, FSEL_19_2_MHZ_VAL); + + msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_2, + PHY_CFG_PLL_FB_DIV_7_0, DIV_7_0_19_2_MHZ_VAL); + + msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_3, + PHY_CFG_PLL_FB_DIV_11_8, DIV_11_8_19_2_MHZ_VAL); + break; + case 38400000: + msm_eusb2_write_readback(phy->base, USB_PHY_HS_PHY_CTRL_COMMON0, + FSEL, FSEL_38_4_MHZ_VAL); + + msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_2, + PHY_CFG_PLL_FB_DIV_7_0, DIV_7_0_38_4_MHZ_VAL); + + msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_3, + PHY_CFG_PLL_FB_DIV_11_8, DIV_11_8_38_4_MHZ_VAL); + break; + default: + dev_err(uphy->dev, "unsupported ref_clk_freq:%lu\n", + ref_clk_freq); + } + + msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_3, + PHY_CFG_PLL_REF_DIV, PLL_REF_DIV_VAL); +} + +static int msm_eusb2_repeater_reset_and_init(struct msm_eusb2_phy *phy) +{ + int ret; + + if (phy->ur) + phy->ur->flags = phy->phy.flags; + + ret = usb_repeater_powerup(phy->ur); + if (ret) + dev_err(phy->phy.dev, "repeater powerup failed.\n"); + + ret = usb_repeater_reset(phy->ur, true); + if (ret) + dev_err(phy->phy.dev, "repeater reset failed.\n"); + + ret = usb_repeater_init(phy->ur); + if (ret) + dev_err(phy->phy.dev, "repeater init failed.\n"); + + return ret; +} + +static int msm_eusb2_phy_init(struct usb_phy *uphy) +{ + struct msm_eusb2_phy *phy = container_of(uphy, struct msm_eusb2_phy, phy); + int ret; + + dev_dbg(uphy->dev, "phy_flags:%x\n", phy->phy.flags); + if (is_eud_debug_mode_active(phy)) { + /* if in host mode, disable EUD debug mode */ + if (phy->phy.flags & PHY_HOST_MODE) { + qcom_scm_io_writel(phy->eud_reg, 0x0); + phy->re_enable_eud = true; + } else { + msm_eusb2_phy_power(phy, true); + return msm_eusb2_repeater_reset_and_init(phy); + } + } + + ret = msm_eusb2_phy_power(phy, true); + if (ret) + return ret; + + /* Bring eUSB2 repeater out of reset and initialized before eUSB2 PHY */ + ret = msm_eusb2_repeater_reset_and_init(phy); + if (ret) + return ret; + + msm_eusb2_phy_clocks(phy, true); + + msm_eusb2_phy_reset(phy); + + msm_eusb2_write_readback(phy->base, USB_PHY_CFG0, + CMN_CTRL_OVERRIDE_EN, CMN_CTRL_OVERRIDE_EN); + + msm_eusb2_write_readback(phy->base, USB_PHY_UTMI_CTRL5, POR, POR); + + msm_eusb2_write_readback(phy->base, USB_PHY_HS_PHY_CTRL_COMMON0, + PHY_ENABLE | RETENABLEN, PHY_ENABLE | RETENABLEN); + + msm_eusb2_write_readback(phy->base, USB_PHY_APB_ACCESS_CMD, + APB_LOGIC_RESET, APB_LOGIC_RESET); + + msm_eusb2_write_readback(phy->base, UTMI_PHY_CMN_CTRL0, TESTBURNIN, 0); + + msm_eusb2_write_readback(phy->base, USB_PHY_FSEL_SEL, + FSEL_SEL, FSEL_SEL); + + /* update ref_clk related registers */ + msm_eusb2_ref_clk_init(uphy); + + msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_1, + PHY_CFG_PLL_CPBIAS_CNTRL, + (0x1 << PHY_CFG_PLL_CPBIAS_CNTRL_SHIFT)); + + msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_4, + PHY_CFG_PLL_INT_CNTRL, + (0x8 << PHY_CFG_PLL_INT_CNTRL_SHIFT)); + + msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_4, + PHY_CFG_PLL_GMP_CNTRL, + (0x1 << PHY_CFG_PLL_GMP_CNTRL_SHIFT)); + + msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_5, + PHY_CFG_PLL_PROP_CNTRL, + (0x10 << PHY_CFG_PLL_PROP_CNTRL_SHIFT)); + + msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_6, + PHY_CFG_PLL_VCO_CNTRL, + (0x0 << PHY_CFG_PLL_VCO_CNTRL_SHIFT)); + + msm_eusb2_write_readback(phy->base, USB_PHY_CFG_CTRL_5, + PHY_CFG_PLL_VREF_TUNE, + (0x1 << PHY_CFG_PLL_VREF_TUNE_SHIFT)); + + msm_eusb2_write_readback(phy->base, USB_PHY_HS_PHY_CTRL2, + VBUS_DET_EXT_SEL, VBUS_DET_EXT_SEL); + + /* set parameter override if needed */ + msm_eusb2_parameter_override(phy); + + msm_eusb2_write_readback(phy->base, USB_PHY_HS_PHY_CTRL2, + USB2_SUSPEND_N_SEL | USB2_SUSPEND_N, + USB2_SUSPEND_N_SEL | USB2_SUSPEND_N); + + msm_eusb2_write_readback(phy->base, USB_PHY_UTMI_CTRL0, SLEEPM, SLEEPM); + + msm_eusb2_write_readback(phy->base, USB_PHY_HS_PHY_CTRL_COMMON0, + SIDDQ_SEL, SIDDQ_SEL); + + msm_eusb2_write_readback(phy->base, USB_PHY_HS_PHY_CTRL_COMMON0, + SIDDQ, 0); + + msm_eusb2_write_readback(phy->base, USB_PHY_UTMI_CTRL5, POR, 0); + + msm_eusb2_write_readback(phy->base, USB_PHY_HS_PHY_CTRL2, + USB2_SUSPEND_N_SEL, 0); + return 0; +} + +static int msm_eusb2_phy_set_suspend(struct usb_phy *uphy, int suspend) +{ + struct msm_eusb2_phy *phy = container_of(uphy, struct msm_eusb2_phy, phy); + + if (phy->suspended && suspend) { + dev_dbg(uphy->dev, "USB PHY is already suspended\n"); + return 0; + } + + dev_dbg(uphy->dev, "phy->flags:0x%x\n", phy->phy.flags); + if (suspend) { + /* Bus suspend handling */ + if (phy->cable_connected || + (phy->phy.flags & PHY_HOST_MODE)) { + msm_eusb2_phy_clocks(phy, false); + goto suspend_exit; + } + + /* Cable disconnect handling */ + if (phy->re_enable_eud) { + dev_dbg(uphy->dev, "re-enabling EUD\n"); + qcom_scm_io_writel(phy->eud_reg, 0x1); + phy->re_enable_eud = false; + } + + /* With EUD spoof disconnect, keep clk and ldos on */ + if (phy->phy.flags & EUD_SPOOF_DISCONNECT) + goto suspend_exit; + + msm_eusb2_phy_clocks(phy, false); + msm_eusb2_phy_power(phy, false); + + /* Hold repeater into reset after powering down PHY */ + usb_repeater_reset(phy->ur, false); + usb_repeater_powerdown(phy->ur); + } else { + /* Bus resume and cable connect handling */ + msm_eusb2_phy_clocks(phy, true); + } + +suspend_exit: + phy->suspended = !!suspend; + return 0; +} + +static int msm_eusb2_phy_notify_connect(struct usb_phy *uphy, + enum usb_device_speed speed) +{ + struct msm_eusb2_phy *phy = container_of(uphy, struct msm_eusb2_phy, phy); + + phy->cable_connected = true; + return 0; +} + +static int msm_eusb2_phy_notify_disconnect(struct usb_phy *uphy, + enum usb_device_speed speed) +{ + struct msm_eusb2_phy *phy = container_of(uphy, struct msm_eusb2_phy, phy); + + phy->cable_connected = false; + return 0; +} + +static void msm_eusb2_phy_vbus_draw_work(struct work_struct *w) +{ + struct msm_eusb2_phy *phy = container_of(w, struct msm_eusb2_phy, + vbus_draw_work); + union power_supply_propval val = {0}; + int ret; + + if (!phy->usb_psy) { + phy->usb_psy = power_supply_get_by_name("usb"); + if (!phy->usb_psy) { + dev_err(phy->phy.dev, "Could not get usb psy\n"); + return; + } + } + + dev_info(phy->phy.dev, "Avail curr from USB = %u\n", phy->vbus_draw); + /* Set max current limit in uA */ + val.intval = 1000 * phy->vbus_draw; + ret = power_supply_set_property(phy->usb_psy, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, &val); + if (ret) { + dev_dbg(phy->phy.dev, "Error setting ICL:(%d)\n", ret); + return; + } +} + +static int msm_eusb2_phy_set_power(struct usb_phy *uphy, unsigned int mA) +{ + struct msm_eusb2_phy *phy = container_of(uphy, struct msm_eusb2_phy, phy); + + phy->vbus_draw = mA; + schedule_work(&phy->vbus_draw_work); + + return 0; +} + +static void msm_eusb2_phy_create_debugfs(struct msm_eusb2_phy *phy) +{ + phy->root = debugfs_create_dir(dev_name(phy->phy.dev), NULL); + debugfs_create_x8("tx_pre_emphasis", 0644, phy->root, + &phy->tx_pre_emphasis); + debugfs_create_x8("tx_rise_fall_time", 0644, phy->root, + &phy->tx_rise_fall_time); + debugfs_create_x8("tx_src_imp", 0644, phy->root, + &phy->tx_src_impedence); + debugfs_create_x8("tx_dc_vref", 0644, phy->root, &phy->tx_dc_vref); + debugfs_create_x8("tx_xv", 0644, phy->root, &phy->tx_xv); + + debugfs_create_file("apb_reg_rw", 0200, phy->root, phy, + &apb_reg_rw_fops); +} + +static int msm_eusb2_phy_probe(struct platform_device *pdev) +{ + struct msm_eusb2_phy *phy; + struct device *dev = &pdev->dev; + struct resource *res; + int ret = 0; + struct usb_repeater *ur = NULL; + + phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); + if (!phy) { + ret = -ENOMEM; + goto err_ret; + } + + ur = devm_usb_get_repeater_by_phandle(dev, "usb-repeater", 0); + if (IS_ERR(ur)) { + ret = PTR_ERR(ur); + goto err_ret; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "eusb2_phy_base"); + if (!res) { + dev_err(dev, "missing eusb2phy memory resource\n"); + ret = -ENODEV; + goto err_ret; + } + + phy->base = devm_ioremap_resource(dev, res); + if (IS_ERR(phy->base)) { + dev_err(dev, "ioremap failed\n"); + ret = -ENODEV; + goto err_ret; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "eud_enable_reg"); + if (res) { + phy->eud_enable_reg = devm_ioremap_resource(dev, res); + if (IS_ERR(phy->eud_enable_reg)) { + ret = PTR_ERR(phy->eud_enable_reg); + dev_err(dev, "eud_enable_reg ioremap err:%d\n", ret); + goto err_ret; + } + phy->eud_reg = res->start; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "eud_detect_reg"); + if (!res) { + dev_err(dev, "missing eud_detect register address\n"); + ret = -ENODEV; + goto err_ret; + } + + phy->eud_detect_reg = devm_ioremap_resource(dev, res); + if (IS_ERR(phy->eud_detect_reg)) { + ret = PTR_ERR(phy->eud_detect_reg); + dev_err(dev, "eud_detect_reg ioremap err:%d\n", ret); + goto err_ret; + } + + phy->ref_clk_src = devm_clk_get(dev, "ref_clk_src"); + if (IS_ERR(phy->ref_clk_src)) { + dev_err(dev, "clk get failed for ref_clk_src\n"); + ret = PTR_ERR(phy->ref_clk_src); + goto err_ret; + } + + phy->ref_clk = devm_clk_get(dev, "ref_clk"); + if (IS_ERR(phy->ref_clk)) { + dev_err(dev, "clk get failed for ref_clk\n"); + ret = PTR_ERR(phy->ref_clk); + goto err_ret; + } + + phy->phy_reset = devm_reset_control_get(dev, "phy_reset"); + if (IS_ERR(phy->phy_reset)) { + ret = PTR_ERR(phy->phy_reset); + goto err_ret; + } + + ret = of_property_read_u32_array(dev->of_node, "qcom,vdd-voltage-level", + (u32 *) phy->vdd_levels, + ARRAY_SIZE(phy->vdd_levels)); + if (ret) { + dev_err(dev, "error reading qcom,vdd-voltage-level property\n"); + goto err_ret; + } + + phy->vdd = devm_regulator_get(dev, "vdd"); + if (IS_ERR(phy->vdd)) { + dev_err(dev, "unable to get vdd supply\n"); + ret = PTR_ERR(phy->vdd); + goto err_ret; + } + + phy->vdda12 = devm_regulator_get(dev, "vdda12"); + if (IS_ERR(phy->vdda12)) { + dev_err(dev, "unable to get vdda12 supply\n"); + ret = PTR_ERR(phy->vdda12); + goto err_ret; + } + + phy->param_override_seq_cnt = of_property_count_elems_of_size( + dev->of_node, "qcom,param-override-seq", + sizeof(*phy->param_override_seq)); + if (phy->param_override_seq_cnt % 2) { + dev_err(dev, "invalid param_override_seq_len\n"); + ret = -EINVAL; + goto err_ret; + } + + if (phy->param_override_seq_cnt > 0) { + phy->param_override_seq = devm_kcalloc(dev, + phy->param_override_seq_cnt, + sizeof(*phy->param_override_seq), + GFP_KERNEL); + if (!phy->param_override_seq) { + ret = -ENOMEM; + goto err_ret; + } + + ret = of_property_read_u32_array(dev->of_node, + "qcom,param-override-seq", + phy->param_override_seq, + phy->param_override_seq_cnt); + if (ret) { + dev_err(dev, "qcom,param-override-seq read failed %d\n", + ret); + goto err_ret; + } + } + + phy->ur = ur; + phy->phy.dev = dev; + platform_set_drvdata(pdev, phy); + + phy->phy.init = msm_eusb2_phy_init; + phy->phy.set_suspend = msm_eusb2_phy_set_suspend; + phy->phy.notify_connect = msm_eusb2_phy_notify_connect; + phy->phy.notify_disconnect = msm_eusb2_phy_notify_disconnect; + phy->phy.set_power = msm_eusb2_phy_set_power; + phy->phy.type = USB_PHY_TYPE_USB2; + + ret = usb_add_phy_dev(&phy->phy); + if (ret) + goto err_ret; + + INIT_WORK(&phy->vbus_draw_work, msm_eusb2_phy_vbus_draw_work); + msm_eusb2_phy_create_debugfs(phy); + + /* + * EUD may be enable in boot loader and to keep EUD session alive across + * kernel boot till USB phy driver is initialized based on cable status, + * keep LDOs on here. + */ + if (is_eud_debug_mode_active(phy)) + msm_eusb2_phy_power(phy, true); + + return 0; + +err_ret: + return ret; +} + +static int msm_eusb2_phy_remove(struct platform_device *pdev) +{ + struct msm_eusb2_phy *phy = platform_get_drvdata(pdev); + + if (!phy) + return 0; + + flush_work(&phy->vbus_draw_work); + if (phy->usb_psy) + power_supply_put(phy->usb_psy); + + debugfs_remove_recursive(phy->root); + usb_remove_phy(&phy->phy); + clk_disable_unprepare(phy->ref_clk); + clk_disable_unprepare(phy->ref_clk_src); + msm_eusb2_phy_clocks(phy, false); + msm_eusb2_phy_power(phy, false); + return 0; +} + +static const struct of_device_id msm_usb_id_table[] = { + { + .compatible = "qcom,usb-snps-eusb2-phy", + }, + { }, +}; +MODULE_DEVICE_TABLE(of, msm_usb_id_table); + +static struct platform_driver msm_eusb2_phy_driver = { + .probe = msm_eusb2_phy_probe, + .remove = msm_eusb2_phy_remove, + .driver = { + .name = "msm_eusb2_phy", + .of_match_table = of_match_ptr(msm_usb_id_table), + }, +}; + +module_platform_driver(msm_eusb2_phy_driver); +MODULE_DESCRIPTION("MSM USB eUSB2 PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/phy/phy-msm-snps-hs.c b/drivers/usb/phy/phy-msm-snps-hs.c new file mode 100644 index 000000000000..0a55035cd702 --- /dev/null +++ b/drivers/usb/phy/phy-msm-snps-hs.c @@ -0,0 +1,939 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2017-2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define USB2_PHY_USB_PHY_UTMI_CTRL0 (0x3c) +#define OPMODE_MASK (0x3 << 3) +#define OPMODE_NONDRIVING (0x1 << 3) +#define SLEEPM BIT(0) + +#define USB2_PHY_USB_PHY_UTMI_CTRL5 (0x50) +#define POR BIT(1) + +#define USB2_PHY_USB_PHY_HS_PHY_CTRL_COMMON0 (0x54) +#define SIDDQ BIT(2) +#define RETENABLEN BIT(3) +#define FSEL_MASK (0x7 << 4) +#define FSEL_DEFAULT (0x3 << 4) + +#define USB2_PHY_USB_PHY_HS_PHY_CTRL_COMMON1 (0x58) +#define VBUSVLDEXTSEL0 BIT(4) +#define PLLBTUNE BIT(5) + +#define USB2_PHY_USB_PHY_HS_PHY_CTRL_COMMON2 (0x5c) +#define VREGBYPASS BIT(0) + +#define USB2_PHY_USB_PHY_HS_PHY_CTRL1 (0x60) +#define VBUSVLDEXT0 BIT(0) + +#define USB2_PHY_USB_PHY_HS_PHY_CTRL2 (0x64) +#define USB2_AUTO_RESUME BIT(0) +#define USB2_SUSPEND_N BIT(2) +#define USB2_SUSPEND_N_SEL BIT(3) + +#define USB2_PHY_USB_PHY_CFG0 (0x94) +#define UTMI_PHY_DATAPATH_CTRL_OVERRIDE_EN BIT(0) +#define UTMI_PHY_CMN_CTRL_OVERRIDE_EN BIT(1) + +#define USB2_PHY_USB_PHY_REFCLK_CTRL (0xa0) +#define REFCLK_SEL_MASK (0x3 << 0) +#define REFCLK_SEL_DEFAULT (0x2 << 0) + +#define USB2_PHY_USB_PHY_PWRDOWN_CTRL (0xa4) +#define PWRDOWN_B BIT(0) + +#define USB2PHY_USB_PHY_RTUNE_SEL (0xb4) +#define RTUNE_SEL BIT(0) + +#define TXPREEMPAMPTUNE0(x) (x << 6) +#define TXPREEMPAMPTUNE0_MASK (BIT(7) | BIT(6)) +#define USB2PHY_USB_PHY_PARAMETER_OVERRIDE_X0 0x6c +#define USB2PHY_USB_PHY_PARAMETER_OVERRIDE_X1 0x70 +#define USB2PHY_USB_PHY_PARAMETER_OVERRIDE_X2 0x74 +#define USB2PHY_USB_PHY_PARAMETER_OVERRIDE_X3 0x78 +#define TXVREFTUNE0_MASK 0xF +#define PARAM_OVRD_MASK 0xFF + +#define USB_HSPHY_3P3_VOL_MIN 3050000 /* uV */ +#define USB_HSPHY_3P3_VOL_MAX 3300000 /* uV */ +#define USB_HSPHY_3P3_HPM_LOAD 16000 /* uA */ +#define USB_HSPHY_3P3_VOL_FSHOST 3150000 /* uV */ + +#define USB_HSPHY_1P8_VOL_MIN 1704000 /* uV */ +#define USB_HSPHY_1P8_VOL_MAX 1800000 /* uV */ +#define USB_HSPHY_1P8_HPM_LOAD 19000 /* uA */ + +#define USB_HSPHY_VDD_HPM_LOAD 30000 /* uA */ + +struct msm_hsphy { + struct usb_phy phy; + void __iomem *base; + phys_addr_t eud_reg; + void __iomem *eud_enable_reg; + bool re_enable_eud; + + struct clk *ref_clk_src; + struct clk *cfg_ahb_clk; + struct reset_control *phy_reset; + + struct regulator *vdd; + struct regulator *vdda33; + struct regulator *vdda18; + int vdd_levels[3]; /* none, low, high */ + + bool clocks_enabled; + bool power_enabled; + bool suspended; + bool cable_connected; + bool dpdm_enable; + + int *param_override_seq; + int param_override_seq_cnt; + + void __iomem *phy_rcal_reg; + u32 rcal_mask; + + struct mutex phy_lock; + struct regulator_desc dpdm_rdesc; + struct regulator_dev *dpdm_rdev; + + struct power_supply *usb_psy; + unsigned int vbus_draw; + struct work_struct vbus_draw_work; + + /* debugfs entries */ + struct dentry *root; + u8 txvref_tune0; + u8 pre_emphasis; + u8 param_ovrd0; + u8 param_ovrd1; + u8 param_ovrd2; + u8 param_ovrd3; +}; + +static void msm_hsphy_enable_clocks(struct msm_hsphy *phy, bool on) +{ + dev_dbg(phy->phy.dev, "%s(): clocks_enabled:%d on:%d\n", + __func__, phy->clocks_enabled, on); + + if (!phy->clocks_enabled && on) { + clk_prepare_enable(phy->ref_clk_src); + + if (phy->cfg_ahb_clk) + clk_prepare_enable(phy->cfg_ahb_clk); + + phy->clocks_enabled = true; + } + + if (phy->clocks_enabled && !on) { + if (phy->cfg_ahb_clk) + clk_disable_unprepare(phy->cfg_ahb_clk); + + clk_disable_unprepare(phy->ref_clk_src); + phy->clocks_enabled = false; + } + +} + +static int msm_hsphy_enable_power(struct msm_hsphy *phy, bool on) +{ + int ret = 0; + + dev_dbg(phy->phy.dev, "%s turn %s regulators. power_enabled:%d\n", + __func__, on ? "on" : "off", phy->power_enabled); + + if (phy->power_enabled == on) { + dev_dbg(phy->phy.dev, "PHYs' regulators are already ON.\n"); + return 0; + } + + if (!on) + goto disable_vdda33; + + ret = regulator_set_load(phy->vdd, USB_HSPHY_VDD_HPM_LOAD); + if (ret < 0) { + dev_err(phy->phy.dev, "Unable to set HPM of vdd:%d\n", ret); + goto err_vdd; + } + + ret = regulator_set_voltage(phy->vdd, phy->vdd_levels[1], + phy->vdd_levels[2]); + if (ret) { + dev_err(phy->phy.dev, "unable to set voltage for hsusb vdd\n"); + goto put_vdd_lpm; + } + + ret = regulator_enable(phy->vdd); + if (ret) { + dev_err(phy->phy.dev, "Unable to enable VDD\n"); + goto unconfig_vdd; + } + + ret = regulator_set_load(phy->vdda18, USB_HSPHY_1P8_HPM_LOAD); + if (ret < 0) { + dev_err(phy->phy.dev, "Unable to set HPM of vdda18:%d\n", ret); + goto disable_vdd; + } + + ret = regulator_set_voltage(phy->vdda18, USB_HSPHY_1P8_VOL_MIN, + USB_HSPHY_1P8_VOL_MAX); + if (ret) { + dev_err(phy->phy.dev, + "Unable to set voltage for vdda18:%d\n", ret); + goto put_vdda18_lpm; + } + + ret = regulator_enable(phy->vdda18); + if (ret) { + dev_err(phy->phy.dev, "Unable to enable vdda18:%d\n", ret); + goto unset_vdda18; + } + + ret = regulator_set_load(phy->vdda33, USB_HSPHY_3P3_HPM_LOAD); + if (ret < 0) { + dev_err(phy->phy.dev, "Unable to set HPM of vdda33:%d\n", ret); + goto disable_vdda18; + } + + ret = regulator_set_voltage(phy->vdda33, USB_HSPHY_3P3_VOL_MIN, + USB_HSPHY_3P3_VOL_MAX); + if (ret) { + dev_err(phy->phy.dev, + "Unable to set voltage for vdda33:%d\n", ret); + goto put_vdda33_lpm; + } + + ret = regulator_enable(phy->vdda33); + if (ret) { + dev_err(phy->phy.dev, "Unable to enable vdda33:%d\n", ret); + goto unset_vdd33; + } + + phy->power_enabled = true; + + pr_debug("%s(): HSUSB PHY's regulators are turned ON.\n", __func__); + return ret; + +disable_vdda33: + ret = regulator_disable(phy->vdda33); + if (ret) + dev_err(phy->phy.dev, "Unable to disable vdda33:%d\n", ret); + +unset_vdd33: + ret = regulator_set_voltage(phy->vdda33, 0, USB_HSPHY_3P3_VOL_MAX); + if (ret) + dev_err(phy->phy.dev, + "Unable to set (0) voltage for vdda33:%d\n", ret); + +put_vdda33_lpm: + ret = regulator_set_load(phy->vdda33, 0); + if (ret < 0) + dev_err(phy->phy.dev, "Unable to set (0) HPM of vdda33\n"); + +disable_vdda18: + ret = regulator_disable(phy->vdda18); + if (ret) + dev_err(phy->phy.dev, "Unable to disable vdda18:%d\n", ret); + +unset_vdda18: + ret = regulator_set_voltage(phy->vdda18, 0, USB_HSPHY_1P8_VOL_MAX); + if (ret) + dev_err(phy->phy.dev, + "Unable to set (0) voltage for vdda18:%d\n", ret); + +put_vdda18_lpm: + ret = regulator_set_load(phy->vdda18, 0); + if (ret < 0) + dev_err(phy->phy.dev, "Unable to set LPM of vdda18\n"); + +disable_vdd: + ret = regulator_disable(phy->vdd); + if (ret) + dev_err(phy->phy.dev, "Unable to disable vdd:%d\n", ret); + +unconfig_vdd: + ret = regulator_set_voltage(phy->vdd, phy->vdd_levels[0], + phy->vdd_levels[2]); + if (ret) + dev_err(phy->phy.dev, "unable to set voltage for hsusb vdd\n"); + +put_vdd_lpm: + ret = regulator_set_load(phy->vdd, 0); + if (ret < 0) + dev_err(phy->phy.dev, "Unable to set LPM of vdd\n"); +err_vdd: + phy->power_enabled = false; + dev_dbg(phy->phy.dev, "HSUSB PHY's regulators are turned OFF.\n"); + return ret; +} + +static void msm_usb_write_readback(void __iomem *base, u32 offset, + const u32 mask, u32 val) +{ + u32 write_val, tmp = readl_relaxed(base + offset); + + tmp &= ~mask; /* retain other bits */ + write_val = tmp | val; + + writel_relaxed(write_val, base + offset); + + /* Read back to see if val was written */ + tmp = readl_relaxed(base + offset); + tmp &= mask; /* clear other bits */ + + if (tmp != val) + pr_err("%s: write: %x to QSCRATCH: %x FAILED\n", + __func__, val, offset); +} + +static void msm_hsphy_reset(struct msm_hsphy *phy) +{ + int ret; + + ret = reset_control_assert(phy->phy_reset); + if (ret) + dev_err(phy->phy.dev, "%s: phy_reset assert failed\n", + __func__); + usleep_range(100, 150); + + ret = reset_control_deassert(phy->phy_reset); + if (ret) + dev_err(phy->phy.dev, "%s: phy_reset deassert failed\n", + __func__); +} + +static void hsusb_phy_write_seq(void __iomem *base, u32 *seq, int cnt, + unsigned long delay) +{ + int i; + + pr_debug("Seq count:%d\n", cnt); + for (i = 0; i < cnt; i = i+2) { + pr_debug("write 0x%02x to 0x%02x\n", seq[i], seq[i+1]); + writel_relaxed(seq[i], base + seq[i+1]); + if (delay) + usleep_range(delay, (delay + 2000)); + } +} + +#define EUD_EN2 BIT(0) +static int msm_hsphy_init(struct usb_phy *uphy) +{ + struct msm_hsphy *phy = container_of(uphy, struct msm_hsphy, phy); + int ret; + u32 rcal_code = 0, eud_csr_reg = 0; + + dev_dbg(uphy->dev, "%s phy_flags:0x%x\n", __func__, phy->phy.flags); + if (phy->eud_enable_reg) { + eud_csr_reg = readl_relaxed(phy->eud_enable_reg); + if (eud_csr_reg & EUD_EN2) { + dev_dbg(phy->phy.dev, "csr:0x%x eud is enabled\n", + eud_csr_reg); + /* if in host mode, disable EUD */ + if (phy->phy.flags & PHY_HOST_MODE) { + qcom_scm_io_writel(phy->eud_reg, 0x0); + phy->re_enable_eud = true; + } else { + ret = msm_hsphy_enable_power(phy, true); + return ret; + } + } + } + + ret = msm_hsphy_enable_power(phy, true); + if (ret) + return ret; + + msm_hsphy_enable_clocks(phy, true); + + msm_hsphy_reset(phy); + + msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_CFG0, + UTMI_PHY_CMN_CTRL_OVERRIDE_EN, + UTMI_PHY_CMN_CTRL_OVERRIDE_EN); + + msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_UTMI_CTRL5, + POR, POR); + + msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_HS_PHY_CTRL_COMMON0, + FSEL_MASK, 0); + + msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_HS_PHY_CTRL_COMMON1, + PLLBTUNE, PLLBTUNE); + + msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_REFCLK_CTRL, + REFCLK_SEL_MASK, REFCLK_SEL_DEFAULT); + + msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_HS_PHY_CTRL_COMMON1, + VBUSVLDEXTSEL0, VBUSVLDEXTSEL0); + + msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_HS_PHY_CTRL1, + VBUSVLDEXT0, VBUSVLDEXT0); + + /* set parameter ovrride if needed */ + if (phy->param_override_seq) + hsusb_phy_write_seq(phy->base, phy->param_override_seq, + phy->param_override_seq_cnt, 0); + + if (phy->pre_emphasis) { + u8 val = TXPREEMPAMPTUNE0(phy->pre_emphasis) & + TXPREEMPAMPTUNE0_MASK; + if (val) + msm_usb_write_readback(phy->base, + USB2PHY_USB_PHY_PARAMETER_OVERRIDE_X1, + TXPREEMPAMPTUNE0_MASK, val); + } + + if (phy->txvref_tune0) { + u8 val = phy->txvref_tune0 & TXVREFTUNE0_MASK; + + msm_usb_write_readback(phy->base, + USB2PHY_USB_PHY_PARAMETER_OVERRIDE_X1, + TXVREFTUNE0_MASK, val); + } + + if (phy->param_ovrd0) { + msm_usb_write_readback(phy->base, + USB2PHY_USB_PHY_PARAMETER_OVERRIDE_X0, + PARAM_OVRD_MASK, phy->param_ovrd0); + } + + if (phy->param_ovrd1) { + msm_usb_write_readback(phy->base, + USB2PHY_USB_PHY_PARAMETER_OVERRIDE_X1, + PARAM_OVRD_MASK, phy->param_ovrd1); + } + + if (phy->param_ovrd2) { + msm_usb_write_readback(phy->base, + USB2PHY_USB_PHY_PARAMETER_OVERRIDE_X2, + PARAM_OVRD_MASK, phy->param_ovrd2); + } + + if (phy->param_ovrd3) { + msm_usb_write_readback(phy->base, + USB2PHY_USB_PHY_PARAMETER_OVERRIDE_X3, + PARAM_OVRD_MASK, phy->param_ovrd3); + } + + dev_dbg(uphy->dev, "x0:%08x x1:%08x x2:%08x x3:%08x\n", + readl_relaxed(phy->base + USB2PHY_USB_PHY_PARAMETER_OVERRIDE_X0), + readl_relaxed(phy->base + USB2PHY_USB_PHY_PARAMETER_OVERRIDE_X1), + readl_relaxed(phy->base + USB2PHY_USB_PHY_PARAMETER_OVERRIDE_X2), + readl_relaxed(phy->base + USB2PHY_USB_PHY_PARAMETER_OVERRIDE_X3)); + + if (phy->phy_rcal_reg) { + rcal_code = readl_relaxed(phy->phy_rcal_reg) & phy->rcal_mask; + + dev_dbg(uphy->dev, "rcal_mask:%08x reg:%pK code:%08x\n", + phy->rcal_mask, phy->phy_rcal_reg, rcal_code); + } + + msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_HS_PHY_CTRL_COMMON2, + VREGBYPASS, VREGBYPASS); + + msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_HS_PHY_CTRL2, + USB2_SUSPEND_N_SEL | USB2_SUSPEND_N, + USB2_SUSPEND_N_SEL | USB2_SUSPEND_N); + + msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_UTMI_CTRL0, + SLEEPM, SLEEPM); + + msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_HS_PHY_CTRL_COMMON0, + SIDDQ, 0); + + msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_UTMI_CTRL5, + POR, 0); + + msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_HS_PHY_CTRL2, + USB2_SUSPEND_N_SEL, 0); + + msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_CFG0, + UTMI_PHY_CMN_CTRL_OVERRIDE_EN, 0); + + return 0; +} + +static int msm_hsphy_set_suspend(struct usb_phy *uphy, int suspend) +{ + struct msm_hsphy *phy = container_of(uphy, struct msm_hsphy, phy); + + if (phy->suspended && suspend) { + if (phy->phy.flags & PHY_SUS_OVERRIDE) + goto suspend; + + dev_dbg(uphy->dev, "%s: USB PHY is already suspended\n", + __func__); + return 0; + } + +suspend: + if (suspend) { /* Bus suspend */ + if (phy->cable_connected) { + /* Enable auto-resume functionality during host mode + * bus suspend with some FS/HS peripheral connected. + */ + if ((phy->phy.flags & PHY_HOST_MODE) && + (phy->phy.flags & PHY_HSFS_MODE)) { + /* Enable auto-resume functionality by pulsing + * signal + */ + msm_usb_write_readback(phy->base, + USB2_PHY_USB_PHY_HS_PHY_CTRL2, + USB2_AUTO_RESUME, USB2_AUTO_RESUME); + usleep_range(500, 1000); + msm_usb_write_readback(phy->base, + USB2_PHY_USB_PHY_HS_PHY_CTRL2, + USB2_AUTO_RESUME, 0); + } + msm_hsphy_enable_clocks(phy, false); + } else {/* Cable disconnect */ + mutex_lock(&phy->phy_lock); + dev_dbg(uphy->dev, "phy->flags:0x%x\n", phy->phy.flags); + if (phy->re_enable_eud) { + dev_dbg(uphy->dev, "re-enabling EUD\n"); + qcom_scm_io_writel(phy->eud_reg, 0x1); + phy->re_enable_eud = false; + } + + if (!phy->dpdm_enable) { + if (!(phy->phy.flags & EUD_SPOOF_DISCONNECT)) { + dev_dbg(uphy->dev, "turning off clocks/ldo\n"); + msm_usb_write_readback(phy->base, + USB2_PHY_USB_PHY_PWRDOWN_CTRL, + PWRDOWN_B, 0); + msm_hsphy_enable_clocks(phy, false); + msm_hsphy_enable_power(phy, false); + } + } else { + dev_dbg(uphy->dev, "dpdm reg still active. Keep clocks/ldo ON\n"); + } + mutex_unlock(&phy->phy_lock); + } + phy->suspended = true; + } else { /* Bus resume and cable connect */ + msm_hsphy_enable_clocks(phy, true); + phy->suspended = false; + } + + return 0; +} + +static int msm_hsphy_notify_connect(struct usb_phy *uphy, + enum usb_device_speed speed) +{ + struct msm_hsphy *phy = container_of(uphy, struct msm_hsphy, phy); + + phy->cable_connected = true; + + return 0; +} + +static int msm_hsphy_notify_disconnect(struct usb_phy *uphy, + enum usb_device_speed speed) +{ + struct msm_hsphy *phy = container_of(uphy, struct msm_hsphy, phy); + + phy->cable_connected = false; + + return 0; +} + +static void msm_hsphy_vbus_draw_work(struct work_struct *w) +{ + struct msm_hsphy *phy = container_of(w, struct msm_hsphy, + vbus_draw_work); + union power_supply_propval val = {0}; + int ret; + + if (!phy->usb_psy) { + phy->usb_psy = power_supply_get_by_name("usb"); + if (!phy->usb_psy) { + dev_err(phy->phy.dev, "Could not get usb psy\n"); + return; + } + } + + dev_info(phy->phy.dev, "Avail curr from USB = %u\n", phy->vbus_draw); + + /* Set max current limit in uA */ + val.intval = 1000 * phy->vbus_draw; + ret = power_supply_set_property(phy->usb_psy, POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, &val); + if (ret) { + dev_dbg(phy->phy.dev, "Error (%d) setting input current limit\n", ret); + return; + } +} + +static int msm_hsphy_set_power(struct usb_phy *uphy, unsigned int mA) +{ + struct msm_hsphy *phy = container_of(uphy, struct msm_hsphy, phy); + + phy->vbus_draw = mA; + schedule_work(&phy->vbus_draw_work); + + return 0; +} + +static int msm_hsphy_dpdm_regulator_enable(struct regulator_dev *rdev) +{ + int ret = 0; + struct msm_hsphy *phy = rdev_get_drvdata(rdev); + + dev_dbg(phy->phy.dev, "%s dpdm_enable:%d\n", + __func__, phy->dpdm_enable); + + if (phy->eud_enable_reg && readl_relaxed(phy->eud_enable_reg)) { + dev_err(phy->phy.dev, "eud is enabled\n"); + return 0; + } + + mutex_lock(&phy->phy_lock); + if (!phy->dpdm_enable) { + ret = msm_hsphy_enable_power(phy, true); + if (ret) { + mutex_unlock(&phy->phy_lock); + return ret; + } + + msm_hsphy_enable_clocks(phy, true); + + msm_hsphy_reset(phy); + + /* + * For PMIC charger detection, place PHY in UTMI non-driving + * mode which leaves Dp and Dm lines in high-Z state. + */ + msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_HS_PHY_CTRL2, + USB2_SUSPEND_N_SEL | USB2_SUSPEND_N, + USB2_SUSPEND_N_SEL | USB2_SUSPEND_N); + msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_UTMI_CTRL0, + OPMODE_MASK, OPMODE_NONDRIVING); + msm_usb_write_readback(phy->base, USB2_PHY_USB_PHY_CFG0, + UTMI_PHY_DATAPATH_CTRL_OVERRIDE_EN, + UTMI_PHY_DATAPATH_CTRL_OVERRIDE_EN); + + phy->dpdm_enable = true; + } + mutex_unlock(&phy->phy_lock); + + return ret; +} + +static int msm_hsphy_dpdm_regulator_disable(struct regulator_dev *rdev) +{ + int ret = 0; + struct msm_hsphy *phy = rdev_get_drvdata(rdev); + + dev_dbg(phy->phy.dev, "%s dpdm_enable:%d\n", + __func__, phy->dpdm_enable); + + mutex_lock(&phy->phy_lock); + if (phy->dpdm_enable) { + if (!phy->cable_connected) { + msm_hsphy_enable_clocks(phy, false); + ret = msm_hsphy_enable_power(phy, false); + if (ret < 0) { + mutex_unlock(&phy->phy_lock); + return ret; + } + } + phy->dpdm_enable = false; + } + mutex_unlock(&phy->phy_lock); + + return ret; +} + +static int msm_hsphy_dpdm_regulator_is_enabled(struct regulator_dev *rdev) +{ + struct msm_hsphy *phy = rdev_get_drvdata(rdev); + + dev_dbg(phy->phy.dev, "%s dpdm_enable:%d\n", + __func__, phy->dpdm_enable); + + return phy->dpdm_enable; +} + +static const struct regulator_ops msm_hsphy_dpdm_regulator_ops = { + .enable = msm_hsphy_dpdm_regulator_enable, + .disable = msm_hsphy_dpdm_regulator_disable, + .is_enabled = msm_hsphy_dpdm_regulator_is_enabled, +}; + +static int msm_hsphy_regulator_init(struct msm_hsphy *phy) +{ + struct device *dev = phy->phy.dev; + struct regulator_config cfg = {}; + struct regulator_init_data *init_data; + + init_data = devm_kzalloc(dev, sizeof(*init_data), GFP_KERNEL); + if (!init_data) + return -ENOMEM; + + init_data->constraints.valid_ops_mask |= REGULATOR_CHANGE_STATUS; + phy->dpdm_rdesc.owner = THIS_MODULE; + phy->dpdm_rdesc.type = REGULATOR_VOLTAGE; + phy->dpdm_rdesc.ops = &msm_hsphy_dpdm_regulator_ops; + phy->dpdm_rdesc.name = kbasename(dev->of_node->full_name); + + cfg.dev = dev; + cfg.init_data = init_data; + cfg.driver_data = phy; + cfg.of_node = dev->of_node; + + phy->dpdm_rdev = devm_regulator_register(dev, &phy->dpdm_rdesc, &cfg); + return PTR_ERR_OR_ZERO(phy->dpdm_rdev); +} + +static void msm_hsphy_create_debugfs(struct msm_hsphy *phy) +{ + phy->root = debugfs_create_dir(dev_name(phy->phy.dev), NULL); + debugfs_create_x8("pre_emphasis", 0644, phy->root, &phy->pre_emphasis); + debugfs_create_x8("txvref_tune0", 0644, phy->root, &phy->txvref_tune0); + debugfs_create_x8("param_ovrd0", 0644, phy->root, &phy->param_ovrd0); + debugfs_create_x8("param_ovrd1", 0644, phy->root, &phy->param_ovrd1); + debugfs_create_x8("param_ovrd2", 0644, phy->root, &phy->param_ovrd2); + debugfs_create_x8("param_ovrd3", 0644, phy->root, &phy->param_ovrd3); +} + +static int msm_hsphy_probe(struct platform_device *pdev) +{ + struct msm_hsphy *phy; + struct device *dev = &pdev->dev; + struct resource *res; + int ret = 0; + + phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); + if (!phy) { + ret = -ENOMEM; + goto err_ret; + } + + phy->phy.dev = dev; + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "hsusb_phy_base"); + if (!res) { + dev_err(dev, "missing memory base resource\n"); + ret = -ENODEV; + goto err_ret; + } + + phy->base = devm_ioremap_resource(dev, res); + if (IS_ERR(phy->base)) { + dev_err(dev, "ioremap failed\n"); + ret = -ENODEV; + goto err_ret; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "phy_rcal_reg"); + if (res) { + phy->phy_rcal_reg = devm_ioremap(dev, + res->start, resource_size(res)); + if (IS_ERR(phy->phy_rcal_reg)) { + dev_err(dev, "couldn't ioremap phy_rcal_reg\n"); + phy->phy_rcal_reg = NULL; + } + if (of_property_read_u32(dev->of_node, + "qcom,rcal-mask", &phy->rcal_mask)) { + dev_err(dev, "unable to read phy rcal mask\n"); + phy->phy_rcal_reg = NULL; + } + dev_dbg(dev, "rcal_mask:%08x reg:%pK\n", phy->rcal_mask, + phy->phy_rcal_reg); + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "eud_enable_reg"); + if (res) { + phy->eud_enable_reg = devm_ioremap_resource(dev, res); + if (IS_ERR(phy->eud_enable_reg)) { + dev_err(dev, "err getting eud_enable_reg address\n"); + return PTR_ERR(phy->eud_enable_reg); + } + phy->eud_reg = res->start; + } + + /* ref_clk_src is needed irrespective of SE_CLK or DIFF_CLK usage */ + phy->ref_clk_src = devm_clk_get(dev, "ref_clk_src"); + if (IS_ERR(phy->ref_clk_src)) { + dev_dbg(dev, "clk get failed for ref_clk_src\n"); + ret = PTR_ERR(phy->ref_clk_src); + return ret; + } + + if (of_property_match_string(pdev->dev.of_node, + "clock-names", "cfg_ahb_clk") >= 0) { + phy->cfg_ahb_clk = devm_clk_get(dev, "cfg_ahb_clk"); + if (IS_ERR(phy->cfg_ahb_clk)) { + ret = PTR_ERR(phy->cfg_ahb_clk); + if (ret != -EPROBE_DEFER) + dev_err(dev, + "clk get failed for cfg_ahb_clk ret %d\n", ret); + return ret; + } + } + + phy->phy_reset = devm_reset_control_get(dev, "phy_reset"); + if (IS_ERR(phy->phy_reset)) + return PTR_ERR(phy->phy_reset); + + phy->param_override_seq_cnt = of_property_count_elems_of_size( + dev->of_node, + "qcom,param-override-seq", + sizeof(*phy->param_override_seq)); + if (phy->param_override_seq_cnt > 0) { + phy->param_override_seq = devm_kcalloc(dev, + phy->param_override_seq_cnt, + sizeof(*phy->param_override_seq), + GFP_KERNEL); + if (!phy->param_override_seq) + return -ENOMEM; + + if (phy->param_override_seq_cnt % 2) { + dev_err(dev, "invalid param_override_seq_len\n"); + return -EINVAL; + } + + ret = of_property_read_u32_array(dev->of_node, + "qcom,param-override-seq", + phy->param_override_seq, + phy->param_override_seq_cnt); + if (ret) { + dev_err(dev, "qcom,param-override-seq read failed %d\n", + ret); + return ret; + } + } + + ret = of_property_read_u32_array(dev->of_node, "qcom,vdd-voltage-level", + (u32 *) phy->vdd_levels, + ARRAY_SIZE(phy->vdd_levels)); + if (ret) { + dev_err(dev, "error reading qcom,vdd-voltage-level property\n"); + goto err_ret; + } + + + phy->vdd = devm_regulator_get(dev, "vdd"); + if (IS_ERR(phy->vdd)) { + dev_err(dev, "unable to get vdd supply\n"); + ret = PTR_ERR(phy->vdd); + goto err_ret; + } + + phy->vdda33 = devm_regulator_get(dev, "vdda33"); + if (IS_ERR(phy->vdda33)) { + dev_err(dev, "unable to get vdda33 supply\n"); + ret = PTR_ERR(phy->vdda33); + goto err_ret; + } + + phy->vdda18 = devm_regulator_get(dev, "vdda18"); + if (IS_ERR(phy->vdda18)) { + dev_err(dev, "unable to get vdda18 supply\n"); + ret = PTR_ERR(phy->vdda18); + goto err_ret; + } + + mutex_init(&phy->phy_lock); + platform_set_drvdata(pdev, phy); + + phy->phy.init = msm_hsphy_init; + phy->phy.set_suspend = msm_hsphy_set_suspend; + phy->phy.notify_connect = msm_hsphy_notify_connect; + phy->phy.notify_disconnect = msm_hsphy_notify_disconnect; + phy->phy.set_power = msm_hsphy_set_power; + phy->phy.type = USB_PHY_TYPE_USB2; + + ret = usb_add_phy_dev(&phy->phy); + if (ret) + return ret; + + ret = msm_hsphy_regulator_init(phy); + if (ret) { + usb_remove_phy(&phy->phy); + return ret; + } + + INIT_WORK(&phy->vbus_draw_work, msm_hsphy_vbus_draw_work); + msm_hsphy_create_debugfs(phy); + + /* + * EUD may be enable in boot loader and to keep EUD session alive across + * kernel boot till USB phy driver is initialized based on cable status, + * keep LDOs on here. + */ + if (phy->eud_enable_reg && readl_relaxed(phy->eud_enable_reg)) + msm_hsphy_enable_power(phy, true); + return 0; + +err_ret: + return ret; +} + +static int msm_hsphy_remove(struct platform_device *pdev) +{ + struct msm_hsphy *phy = platform_get_drvdata(pdev); + + if (!phy) + return 0; + + if (phy->usb_psy) + power_supply_put(phy->usb_psy); + + debugfs_remove_recursive(phy->root); + + usb_remove_phy(&phy->phy); + clk_disable_unprepare(phy->ref_clk_src); + + msm_hsphy_enable_clocks(phy, false); + msm_hsphy_enable_power(phy, false); + return 0; +} + +static const struct of_device_id msm_usb_id_table[] = { + { + .compatible = "qcom,usb-hsphy-snps-femto", + }, + { }, +}; +MODULE_DEVICE_TABLE(of, msm_usb_id_table); + +static struct platform_driver msm_hsphy_driver = { + .probe = msm_hsphy_probe, + .remove = msm_hsphy_remove, + .driver = { + .name = "msm-usb-hsphy", + .of_match_table = of_match_ptr(msm_usb_id_table), + }, +}; + +module_platform_driver(msm_hsphy_driver); + +MODULE_DESCRIPTION("MSM USB HS PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/phy/phy-msm-ssusb-qmp.c b/drivers/usb/phy/phy-msm-ssusb-qmp.c new file mode 100644 index 000000000000..da49d5cd8964 --- /dev/null +++ b/drivers/usb/phy/phy-msm-ssusb-qmp.c @@ -0,0 +1,1169 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2013-2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum core_ldo_levels { + CORE_LEVEL_NONE = 0, + CORE_LEVEL_MIN, + CORE_LEVEL_MAX, +}; + +#define INIT_MAX_TIME_USEC 1000 + +/* default CORE votlage and load values */ +#define USB_SSPHY_1P2_VOL_MIN 1200000 /* uV */ +#define USB_SSPHY_1P2_VOL_MAX 1200000 /* uV */ +#define USB_SSPHY_HPM_LOAD 30000 /* uA */ + +/* USB3PHY_PCIE_USB3_PCS_PCS_STATUS bit */ +#define PHYSTATUS BIT(6) + +/* PCIE_USB3_PHY_AUTONOMOUS_MODE_CTRL bits */ +#define ARCVR_DTCT_EN BIT(0) +#define ALFPS_DTCT_EN BIT(1) +#define ARCVR_DTCT_EVENT_SEL BIT(4) + +/* + * register bits + * PCIE_USB3_PHY_PCS_MISC_TYPEC_CTRL - for QMP USB PHY + * USB3_DP_COM_PHY_MODE_CTRL - for QMP USB DP Combo PHY + */ + +/* 0 - selects Lane A. 1 - selects Lane B */ +#define SW_PORTSELECT BIT(0) +/* port select mux: 1 - sw control. 0 - HW control*/ +#define SW_PORTSELECT_MX BIT(1) + +/* USB3_DP_PHY_USB3_DP_COM_SWI_CTRL bits */ + +/* LANE related register read/write with USB3 */ +#define USB3_SWI_ACT_ACCESS_EN BIT(0) +/* LANE related register read/write with DP */ +#define DP_SWI_ACT_ACCESS_EN BIT(1) + +/* USB3_DP_COM_RESET_OVRD_CTRL bits */ + +/* DP PHY soft reset */ +#define SW_DPPHY_RESET BIT(0) +/* mux to select DP PHY reset control, 0:HW control, 1: software reset */ +#define SW_DPPHY_RESET_MUX BIT(1) +/* USB3 PHY soft reset */ +#define SW_USB3PHY_RESET BIT(2) +/* mux to select USB3 PHY reset control, 0:HW control, 1: software reset */ +#define SW_USB3PHY_RESET_MUX BIT(3) + +/* USB3_DP_COM_PHY_MODE_CTRL bits */ +#define USB3_MODE BIT(0) /* enables USB3 mode */ +#define DP_MODE BIT(1) /* enables DP mode */ +#define USB3_DP_COMBO_MODE (USB3_MODE | DP_MODE) /*enables combo mode */ + +enum qmp_phy_rev_reg { + USB3_PHY_PCS_STATUS, + USB3_PHY_AUTONOMOUS_MODE_CTRL, + USB3_PHY_LFPS_RXTERM_IRQ_CLEAR, + USB3_PHY_POWER_DOWN_CONTROL, + USB3_PHY_SW_RESET, + USB3_PHY_START, + + /* TypeC port select configuration (optional) */ + USB3_PHY_PCS_MISC_TYPEC_CTRL, + + /* USB DP Combo PHY related */ + USB3_DP_COM_POWER_DOWN_CTRL, + USB3_DP_COM_SW_RESET, + USB3_DP_COM_RESET_OVRD_CTRL, + USB3_DP_COM_PHY_MODE_CTRL, + USB3_DP_COM_TYPEC_CTRL, + USB3_PCS_MISC_CLAMP_ENABLE, + USB3_PHY_REG_MAX, +}; + +enum qmp_phy_type { + USB3, + USB3_OR_DP, + USB3_AND_DP, +}; + +/* reg values to write */ +struct qmp_reg_val { + u32 offset; + u32 val; + u32 delay; +}; + +struct msm_ssphy_qmp { + struct usb_phy phy; + void __iomem *base; + void __iomem *vls_clamp_reg; + void __iomem *pcs_clamp_enable_reg; + void __iomem *tcsr_usb3_dp_phymode; + + struct regulator *vdd; + int vdd_levels[3]; /* none, low, high */ + int vdd_max_uA; + struct regulator *core_ldo; + int core_voltage_levels[3]; + int core_max_uA; + struct regulator *usb3_dp_phy_gdsc; + struct clk *ref_clk_src; + struct clk *ref_clk; + struct clk *aux_clk; + struct clk *com_aux_clk; + struct clk *cfg_ahb_clk; + struct clk *pipe_clk; + struct clk *pipe_clk_mux; + struct clk *pipe_clk_ext_src; + struct reset_control *phy_reset; + struct reset_control *phy_phy_reset; + struct reset_control *global_phy_reset; + bool power_enabled; + bool clk_enabled; + bool cable_connected; + bool in_suspend; + u32 *phy_reg; /* revision based offset */ + int reg_offset_cnt; + u32 *qmp_phy_init_seq; + int init_seq_len; + enum qmp_phy_type phy_type; +}; + +static const struct of_device_id msm_usb_id_table[] = { + { + .compatible = "qcom,usb-ssphy-qmp", + }, + { + .compatible = "qcom,usb-ssphy-qmp-v1", + }, + { + .compatible = "qcom,usb-ssphy-qmp-v2", + }, + { + .compatible = "qcom,usb-ssphy-qmp-dp-combo", + }, + { + .compatible = "qcom,usb-ssphy-qmp-usb3-or-dp", + }, + { }, +}; +MODULE_DEVICE_TABLE(of, msm_usb_id_table); + +static void usb_qmp_powerup_phy(struct msm_ssphy_qmp *phy); +static void msm_ssphy_qmp_enable_clks(struct msm_ssphy_qmp *phy, bool on); +static int msm_ssphy_qmp_reset(struct usb_phy *uphy); +static int msm_ssphy_qmp_dp_combo_reset(struct usb_phy *uphy); + +static inline char *get_cable_status_str(struct msm_ssphy_qmp *phy) +{ + return phy->cable_connected ? "connected" : "disconnected"; +} + +static void msm_ssusb_qmp_clr_lfps_rxterm_int(struct msm_ssphy_qmp *phy) +{ + writel_relaxed(1, phy->base + + phy->phy_reg[USB3_PHY_LFPS_RXTERM_IRQ_CLEAR]); + /* flush the previous write before next write */ + wmb(); + writel_relaxed(0, phy->base + + phy->phy_reg[USB3_PHY_LFPS_RXTERM_IRQ_CLEAR]); +} + +static void msm_ssusb_qmp_clamp_enable(struct msm_ssphy_qmp *phy, bool val) +{ + switch (phy->phy_type) { + case USB3_AND_DP: + writel_relaxed(!val, phy->base + + phy->phy_reg[USB3_PCS_MISC_CLAMP_ENABLE]); + break; + case USB3_OR_DP: + case USB3: + if (phy->vls_clamp_reg) + writel_relaxed(!!val, phy->vls_clamp_reg); + if (phy->pcs_clamp_enable_reg) + writel_relaxed(!val, phy->pcs_clamp_enable_reg); + break; + default: + break; + } +} + +static void msm_ssusb_qmp_enable_autonomous(struct msm_ssphy_qmp *phy, + int enable) +{ + u32 val; + unsigned int autonomous_mode_offset = + phy->phy_reg[USB3_PHY_AUTONOMOUS_MODE_CTRL]; + + dev_dbg(phy->phy.dev, "enabling QMP autonomous mode with cable %s\n", + get_cable_status_str(phy)); + + if (enable) { + msm_ssusb_qmp_clr_lfps_rxterm_int(phy); + val = readl_relaxed(phy->base + autonomous_mode_offset); + val |= ARCVR_DTCT_EN; + if (phy->phy.flags & DEVICE_IN_SS_MODE) { + val |= ALFPS_DTCT_EN; + val &= ~ARCVR_DTCT_EVENT_SEL; + } else { + val &= ~ALFPS_DTCT_EN; + val |= ARCVR_DTCT_EVENT_SEL; + } + writel_relaxed(val, phy->base + autonomous_mode_offset); + msm_ssusb_qmp_clamp_enable(phy, true); + } else { + msm_ssusb_qmp_clamp_enable(phy, false); + writel_relaxed(0, phy->base + autonomous_mode_offset); + msm_ssusb_qmp_clr_lfps_rxterm_int(phy); + } +} + +static int msm_ssusb_qmp_gdsc(struct msm_ssphy_qmp *phy, bool on) +{ + int ret; + + if (IS_ERR_OR_NULL(phy->usb3_dp_phy_gdsc)) + return 0; + + if (on) + ret = regulator_enable(phy->usb3_dp_phy_gdsc); + else + ret = regulator_disable(phy->usb3_dp_phy_gdsc); + + if (ret) + dev_err(phy->phy.dev, "err:%d fail to %s usb3_dp_phy_gdsc\n", + ret, on ? "enable" : "disable"); + return ret; +} + +static int msm_ssusb_qmp_ldo_enable(struct msm_ssphy_qmp *phy, int on) +{ + int min, rc = 0; + + dev_dbg(phy->phy.dev, "reg (%s)\n", on ? "HPM" : "LPM"); + + if (phy->power_enabled == on) { + dev_dbg(phy->phy.dev, "PHYs' regulators status %d\n", + phy->power_enabled); + return 0; + } + + phy->power_enabled = on; + + min = on ? 1 : 0; /* low or none? */ + + if (!on) + goto disable_regulators; + + rc = msm_ssusb_qmp_gdsc(phy, true); + if (rc < 0) + return rc; + + rc = regulator_set_load(phy->vdd, phy->vdd_max_uA); + if (rc < 0) { + dev_err(phy->phy.dev, "Unable to set HPM of %s\n", "vdd"); + goto put_gdsc; + } + + rc = regulator_set_voltage(phy->vdd, phy->vdd_levels[min], + phy->vdd_levels[2]); + if (rc) { + dev_err(phy->phy.dev, "Unable to set voltage for %s\n", "vdd"); + goto put_vdd_lpm; + } + + dev_dbg(phy->phy.dev, "min_vol:%d max_vol:%d\n", + phy->vdd_levels[min], phy->vdd_levels[2]); + + rc = regulator_enable(phy->vdd); + if (rc) { + dev_err(phy->phy.dev, "Unable to enable %s\n", "vdd"); + goto unconfig_vdd; + } + + rc = regulator_set_load(phy->core_ldo, phy->core_max_uA); + if (rc < 0) { + dev_err(phy->phy.dev, "Unable to set HPM of %s\n", "core_ldo"); + goto disable_vdd; + } + + rc = regulator_set_voltage(phy->core_ldo, + phy->core_voltage_levels[CORE_LEVEL_MIN], + phy->core_voltage_levels[CORE_LEVEL_MAX]); + if (rc) { + dev_err(phy->phy.dev, "Unable to set voltage for %s\n", + "core_ldo"); + goto put_core_ldo_lpm; + } + + rc = regulator_enable(phy->core_ldo); + if (rc) { + dev_err(phy->phy.dev, "Unable to enable %s\n", "core_ldo"); + goto unset_core_ldo; + } + + return 0; + +disable_regulators: + rc = regulator_disable(phy->core_ldo); + if (rc) + dev_err(phy->phy.dev, "Unable to disable %s\n", "core_ldo"); + +unset_core_ldo: + rc = regulator_set_voltage(phy->core_ldo, + phy->core_voltage_levels[CORE_LEVEL_NONE], + phy->core_voltage_levels[CORE_LEVEL_MAX]); + if (rc) + dev_err(phy->phy.dev, "Unable to set voltage for %s\n", + "core_ldo"); + +put_core_ldo_lpm: + rc = regulator_set_load(phy->core_ldo, 0); + if (rc < 0) + dev_err(phy->phy.dev, "Unable to set LPM of %s\n", "core_ldo"); + +disable_vdd: + rc = regulator_disable(phy->vdd); + if (rc) + dev_err(phy->phy.dev, "Unable to disable %s\n", "vdd"); + +unconfig_vdd: + rc = regulator_set_voltage(phy->vdd, phy->vdd_levels[min], + phy->vdd_levels[2]); + if (rc) + dev_err(phy->phy.dev, "Unable to set voltage for %s\n", "vdd"); + +put_vdd_lpm: + rc = regulator_set_load(phy->vdd, 0); + if (rc < 0) + dev_err(phy->phy.dev, "Unable to set LPM of %s\n", "vdd"); + +put_gdsc: + rc = msm_ssusb_qmp_gdsc(phy, false); + return rc < 0 ? rc : 0; +} + +static int configure_phy_regs(struct usb_phy *uphy, + const struct qmp_reg_val *reg) +{ + struct msm_ssphy_qmp *phy = container_of(uphy, struct msm_ssphy_qmp, + phy); + + if (!reg) { + dev_err(uphy->dev, "NULL PHY configuration\n"); + return -EINVAL; + } + + while (reg->offset != -1) { + writel_relaxed(reg->val, phy->base + reg->offset); + if (reg->delay) + usleep_range(reg->delay, reg->delay + 10); + reg++; + } + return 0; +} + +static void msm_ssphy_qmp_setmode(struct msm_ssphy_qmp *phy, u32 mode) +{ + mode = mode & USB3_DP_COMBO_MODE; + + writel_relaxed(mode, + phy->base + phy->phy_reg[USB3_DP_COM_PHY_MODE_CTRL]); + + /* flush the write by reading it */ + readl_relaxed(phy->base + phy->phy_reg[USB3_DP_COM_PHY_MODE_CTRL]); +} + +static void usb_qmp_update_portselect_phymode(struct msm_ssphy_qmp *phy) +{ + int val; + + /* perform lane selection */ + val = -EINVAL; + if (phy->phy.flags & PHY_LANE_A) + val = SW_PORTSELECT_MX; + else if (phy->phy.flags & PHY_LANE_B) + val = SW_PORTSELECT | SW_PORTSELECT_MX; + + /* PHY must be powered up before updating portselect and phymode. */ + usb_qmp_powerup_phy(phy); + + switch (phy->phy_type) { + case USB3_AND_DP: + writel_relaxed(0x01, + phy->base + phy->phy_reg[USB3_DP_COM_SW_RESET]); + writel_relaxed(0x00, + phy->base + phy->phy_reg[USB3_DP_COM_SW_RESET]); + + if (!(phy->phy.flags & PHY_USB_DP_CONCURRENT_MODE)) + /* override hardware control for reset of qmp phy */ + writel_relaxed(SW_DPPHY_RESET_MUX | SW_DPPHY_RESET | + SW_USB3PHY_RESET_MUX | SW_USB3PHY_RESET, + phy->base + phy->phy_reg[USB3_DP_COM_RESET_OVRD_CTRL]); + + /* update port select */ + if (val > 0) { + dev_err(phy->phy.dev, + "USB DP QMP PHY: Update TYPEC CTRL(%d)\n", val); + writel_relaxed(val, phy->base + + phy->phy_reg[USB3_DP_COM_TYPEC_CTRL]); + } + +#ifdef CONFIG_PINCTRL + /* + * if there is no default pinctrl state for orientation, + * it need external module provide SW orientation info, + * report an error if there is no such info. + */ + if (val < 0 && !phy->phy.dev->pins) + dev_err(phy->phy.dev, + "USB DP QMP PHY: NO SW PORTSELECT\n"); +#endif + + if (!(phy->phy.flags & PHY_USB_DP_CONCURRENT_MODE)) { + msm_ssphy_qmp_setmode(phy, USB3_DP_COMBO_MODE); + + /* bring both USB and DP PHYs PCS block out of reset */ + writel_relaxed(0x00, phy->base + + phy->phy_reg[USB3_DP_COM_RESET_OVRD_CTRL]); + } + break; + case USB3_OR_DP: + if (val > 0) { + dev_err(phy->phy.dev, + "USB QMP PHY: Update TYPEC CTRL(%d)\n", val); + writel_relaxed(val, phy->base + + phy->phy_reg[USB3_PHY_PCS_MISC_TYPEC_CTRL]); + } + break; + default: + dev_dbg(phy->phy.dev, "no portselect for phy type %d\n", + phy->phy_type); + break; + } + + /* Make sure above selection and reset sequence is gone through */ + mb(); +} + +static void usb_qmp_powerup_phy(struct msm_ssphy_qmp *phy) +{ + switch (phy->phy_type) { + case USB3_AND_DP: + /* power up USB3 and DP common logic block */ + writel_relaxed(0x01, + phy->base + phy->phy_reg[USB3_DP_COM_POWER_DOWN_CTRL]); + + /* + * Don't write 0x0 to DP_COM_SW_RESET here as portselect and + * phymode operation needs DP_COM_SW_RESET as 0x1. + * msm_ssphy_qmp_init() writes 0x0 to DP_COM_SW_RESET before + * initializing PHY. + */ + fallthrough; + case USB3_OR_DP: + case USB3: + /* power up USB3 PHY */ + writel_relaxed(0x01, + phy->base + phy->phy_reg[USB3_PHY_POWER_DOWN_CONTROL]); + break; + default: + dev_err(phy->phy.dev, "phy_powerup: Unknown USB QMP PHY type\n"); + break; + } + + /* Make sure that above write completed to power up PHY */ + mb(); +} + +/* SSPHY Initialization */ +static int msm_ssphy_qmp_init(struct usb_phy *uphy) +{ + struct msm_ssphy_qmp *phy = container_of(uphy, struct msm_ssphy_qmp, + phy); + int ret; + unsigned int init_timeout_usec = INIT_MAX_TIME_USEC; + const struct qmp_reg_val *reg = NULL; + + dev_dbg(uphy->dev, "Initializing QMP phy\n"); + + if (uphy->flags & PHY_DP_MODE) { + dev_info(uphy->dev, "QMP PHY currently in DP mode\n"); + return -EBUSY; + } + + ret = msm_ssusb_qmp_ldo_enable(phy, 1); + if (ret) { + dev_err(phy->phy.dev, + "msm_ssusb_qmp_ldo_enable(1) failed, ret=%d\n", + ret); + return ret; + } + + msm_ssphy_qmp_enable_clks(phy, true); + + if (phy->phy_type == USB3_AND_DP) + ret = msm_ssphy_qmp_dp_combo_reset(&phy->phy); + else + ret = msm_ssphy_qmp_reset(&phy->phy); + + /* select appropriate port select and PHY mode if applicable */ + usb_qmp_update_portselect_phymode(phy); + + /* power up PHY */ + usb_qmp_powerup_phy(phy); + + reg = (struct qmp_reg_val *)phy->qmp_phy_init_seq; + + /* Main configuration */ + ret = configure_phy_regs(uphy, reg); + if (ret) { + dev_err(uphy->dev, "Failed the main PHY configuration\n"); + goto fail; + } + + /* perform software reset of PCS/Serdes */ + writel_relaxed(0x00, phy->base + phy->phy_reg[USB3_PHY_SW_RESET]); + /* start PCS/Serdes to operation mode */ + writel_relaxed(0x03, phy->base + phy->phy_reg[USB3_PHY_START]); + + /* Make sure above write completed to bring PHY out of reset */ + mb(); + + /* Wait for PHY initialization to be done */ + do { + if (readl_relaxed(phy->base + + phy->phy_reg[USB3_PHY_PCS_STATUS]) & PHYSTATUS) + usleep_range(1, 2); + else + break; + } while (--init_timeout_usec); + + if (!init_timeout_usec) { + dev_err(uphy->dev, "QMP PHY initialization timeout\n"); + dev_err(uphy->dev, "USB3_PHY_PCS_STATUS:%x\n", + readl_relaxed(phy->base + + phy->phy_reg[USB3_PHY_PCS_STATUS])); + ret = -EBUSY; + goto fail; + } + + return ret; +fail: + phy->in_suspend = true; + writel_relaxed(0x00, + phy->base + phy->phy_reg[USB3_PHY_POWER_DOWN_CONTROL]); + msm_ssphy_qmp_enable_clks(phy, false); + msm_ssusb_qmp_ldo_enable(phy, 0); + + return ret; +} + +static int msm_ssphy_qmp_dp_combo_reset(struct usb_phy *uphy) +{ + struct msm_ssphy_qmp *phy = container_of(uphy, struct msm_ssphy_qmp, + phy); + int ret = 0; + + if (phy->phy.flags & PHY_USB_DP_CONCURRENT_MODE) { + dev_dbg(uphy->dev, "Resetting USB part of QMP phy\n"); + + /* Assert USB3 PHY CSR reset */ + ret = reset_control_assert(phy->phy_reset); + if (ret) { + dev_err(uphy->dev, "phy_reset assert failed\n"); + goto exit; + } + + /* Deassert USB3 PHY CSR reset */ + ret = reset_control_deassert(phy->phy_reset); + if (ret) { + dev_err(uphy->dev, "phy_reset deassert failed\n"); + goto exit; + } + return 0; + } + + dev_dbg(uphy->dev, "Global reset of QMP DP combo phy\n"); + /* Assert global PHY reset */ + ret = reset_control_assert(phy->global_phy_reset); + if (ret) { + dev_err(uphy->dev, "global_phy_reset assert failed\n"); + goto exit; + } + + /* Assert QMP USB PHY reset */ + ret = reset_control_assert(phy->phy_reset); + if (ret) { + dev_err(uphy->dev, "phy_reset assert failed\n"); + goto exit; + } + + /* De-Assert QMP USB PHY reset */ + ret = reset_control_deassert(phy->phy_reset); + if (ret) + dev_err(uphy->dev, "phy_reset deassert failed\n"); + + /* De-Assert global PHY reset */ + ret = reset_control_deassert(phy->global_phy_reset); + if (ret) + dev_err(uphy->dev, "global_phy_reset deassert failed\n"); + +exit: + return ret; +} + +static int msm_ssphy_qmp_reset(struct usb_phy *uphy) +{ + struct msm_ssphy_qmp *phy = container_of(uphy, struct msm_ssphy_qmp, + phy); + int ret; + + dev_dbg(uphy->dev, "Resetting QMP phy\n"); + + /* Assert USB3 PHY reset */ + ret = reset_control_assert(phy->phy_phy_reset); + if (ret) { + dev_err(uphy->dev, "phy_phy_reset assert failed\n"); + goto exit; + } + + /* Assert USB3 PHY CSR reset */ + ret = reset_control_assert(phy->phy_reset); + if (ret) { + dev_err(uphy->dev, "phy_reset assert failed\n"); + goto deassert_phy_phy_reset; + } + + /* select usb3 phy mode */ + if (phy->tcsr_usb3_dp_phymode) + writel_relaxed(0x0, phy->tcsr_usb3_dp_phymode); + + /* Deassert USB3 PHY CSR reset */ + ret = reset_control_deassert(phy->phy_reset); + if (ret) { + dev_err(uphy->dev, "phy_reset deassert failed\n"); + goto deassert_phy_phy_reset; + } + + /* Deassert USB3 PHY reset */ + ret = reset_control_deassert(phy->phy_phy_reset); + if (ret) { + dev_err(uphy->dev, "phy_phy_reset deassert failed\n"); + goto exit; + } + + return 0; + +deassert_phy_phy_reset: + ret = reset_control_deassert(phy->phy_phy_reset); + if (ret) + dev_err(uphy->dev, "phy_phy_reset deassert failed\n"); +exit: + phy->in_suspend = false; + + return ret; +} + +static int msm_ssphy_power_enable(struct msm_ssphy_qmp *phy, bool on) +{ + bool host = phy->phy.flags & PHY_HOST_MODE; + int ret = 0; + + /* + * Turn off the phy's LDOs when cable is disconnected for device mode + * with external vbus_id indication. + */ + if (!host && !phy->cable_connected) { + if (on) { + ret = msm_ssusb_qmp_ldo_enable(phy, 1); + if (ret) + dev_err(phy->phy.dev, + "msm_ssusb_qmp_ldo_enable(1) failed, ret=%d\n", + ret); + } else { + ret = msm_ssusb_qmp_ldo_enable(phy, 0); + if (ret) + dev_err(phy->phy.dev, + "msm_ssusb_qmp_ldo_enable(0) failed, ret=%d\n", + ret); + } + } + + return ret; +} + +/** + * Performs QMP PHY suspend/resume functionality. + * + * @uphy - usb phy pointer. + * @suspend - to enable suspend or not. 1 - suspend, 0 - resume + * + */ +static int msm_ssphy_qmp_set_suspend(struct usb_phy *uphy, int suspend) +{ + struct msm_ssphy_qmp *phy = container_of(uphy, struct msm_ssphy_qmp, + phy); + + dev_dbg(uphy->dev, "QMP PHY set_suspend for %s called with cable %s\n", + (suspend ? "suspend" : "resume"), + get_cable_status_str(phy)); + + if (phy->in_suspend == suspend) { + dev_dbg(uphy->dev, "%s: USB PHY is already %s.\n", + __func__, (suspend ? "suspended" : "resumed")); + return 0; + } + + if (suspend) { + if (phy->cable_connected) { + msm_ssusb_qmp_enable_autonomous(phy, 1); + } else { + /* Reset phy mode to USB only if DP not connected */ + if (phy->phy_type == USB3_AND_DP && + !((uphy->flags & PHY_DP_MODE) || + (uphy->flags & PHY_USB_DP_CONCURRENT_MODE))) + msm_ssphy_qmp_setmode(phy, USB3_MODE); + writel_relaxed(0x00, + phy->base + phy->phy_reg[USB3_PHY_POWER_DOWN_CONTROL]); + } + + /* Make sure above write completed with PHY */ + wmb(); + + msm_ssphy_qmp_enable_clks(phy, false); + phy->in_suspend = true; + msm_ssphy_power_enable(phy, 0); + dev_dbg(uphy->dev, "QMP PHY is suspend\n"); + } else { + if (uphy->flags & PHY_DP_MODE) { + dev_info(uphy->dev, "QMP PHY currently in DP mode\n"); + return -EBUSY; + } + + msm_ssphy_power_enable(phy, 1); + msm_ssphy_qmp_enable_clks(phy, true); + if (!phy->cable_connected) { + writel_relaxed(0x01, + phy->base + phy->phy_reg[USB3_PHY_POWER_DOWN_CONTROL]); + } else { + msm_ssusb_qmp_enable_autonomous(phy, 0); + } + + /* Make sure that above write completed with PHY */ + wmb(); + + phy->in_suspend = false; + dev_dbg(uphy->dev, "QMP PHY is resumed\n"); + } + + return 0; +} + +static int msm_ssphy_qmp_notify_connect(struct usb_phy *uphy, + enum usb_device_speed speed) +{ + struct msm_ssphy_qmp *phy = container_of(uphy, struct msm_ssphy_qmp, + phy); + + dev_dbg(uphy->dev, "QMP phy connect notification\n"); + phy->cable_connected = true; + atomic_notifier_call_chain(&uphy->notifier, 1, uphy); + + return 0; +} + +static int msm_ssphy_qmp_notify_disconnect(struct usb_phy *uphy, + enum usb_device_speed speed) +{ + struct msm_ssphy_qmp *phy = container_of(uphy, struct msm_ssphy_qmp, + phy); + + atomic_notifier_call_chain(&uphy->notifier, 0, uphy); + if (phy->phy.flags & PHY_HOST_MODE) { + writel_relaxed(0x00, + phy->base + phy->phy_reg[USB3_PHY_POWER_DOWN_CONTROL]); + readl_relaxed(phy->base + phy->phy_reg[USB3_PHY_POWER_DOWN_CONTROL]); + } + + dev_dbg(uphy->dev, "QMP phy disconnect notification\n"); + dev_dbg(uphy->dev, " cable_connected=%d\n", phy->cable_connected); + phy->cable_connected = false; + + return 0; +} + +static int msm_ssphy_qmp_get_clks(struct msm_ssphy_qmp *phy, struct device *dev) +{ + int ret = 0; + + phy->aux_clk = devm_clk_get(dev, "aux_clk"); + if (IS_ERR(phy->aux_clk)) { + ret = PTR_ERR(phy->aux_clk); + phy->aux_clk = NULL; + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to get aux_clk\n"); + goto err; + } + clk_set_rate(phy->aux_clk, clk_round_rate(phy->aux_clk, ULONG_MAX)); + + if (of_property_match_string(dev->of_node, + "clock-names", "cfg_ahb_clk") >= 0) { + phy->cfg_ahb_clk = devm_clk_get(dev, "cfg_ahb_clk"); + if (IS_ERR(phy->cfg_ahb_clk)) { + ret = PTR_ERR(phy->cfg_ahb_clk); + if (ret != -EPROBE_DEFER) + dev_err(dev, + "failed to get cfg_ahb_clk ret %d\n", ret); + goto err; + } + } + + phy->pipe_clk = devm_clk_get(dev, "pipe_clk"); + if (IS_ERR(phy->pipe_clk)) { + ret = PTR_ERR(phy->pipe_clk); + phy->pipe_clk = NULL; + if (ret != -EPROBE_DEFER) + dev_err(dev, "failed to get pipe_clk\n"); + goto err; + } + + phy->pipe_clk_mux = devm_clk_get(dev, "pipe_clk_mux"); + if (IS_ERR(phy->pipe_clk_mux)) + phy->pipe_clk_mux = NULL; + + phy->pipe_clk_ext_src = devm_clk_get(dev, "pipe_clk_ext_src"); + if (IS_ERR(phy->pipe_clk_ext_src)) + phy->pipe_clk_ext_src = NULL; + + phy->ref_clk_src = devm_clk_get(dev, "ref_clk_src"); + if (IS_ERR(phy->ref_clk_src)) + phy->ref_clk_src = NULL; + + phy->ref_clk = devm_clk_get(dev, "ref_clk"); + if (IS_ERR(phy->ref_clk)) + phy->ref_clk = NULL; + + if (of_property_match_string(dev->of_node, + "clock-names", "com_aux_clk") >= 0) { + phy->com_aux_clk = devm_clk_get(dev, "com_aux_clk"); + if (IS_ERR(phy->com_aux_clk)) { + ret = PTR_ERR(phy->com_aux_clk); + if (ret != -EPROBE_DEFER) + dev_err(dev, + "failed to get com_aux_clk ret %d\n", ret); + goto err; + } + } + +err: + return ret; +} + +static void msm_ssphy_qmp_enable_clks(struct msm_ssphy_qmp *phy, bool on) +{ + dev_dbg(phy->phy.dev, "%s(): clk_enabled:%d on:%d\n", __func__, + phy->clk_enabled, on); + + if (!phy->clk_enabled && on) { + if (phy->ref_clk_src) + clk_prepare_enable(phy->ref_clk_src); + + if (phy->ref_clk) + clk_prepare_enable(phy->ref_clk); + + if (phy->com_aux_clk) + clk_prepare_enable(phy->com_aux_clk); + + clk_prepare_enable(phy->aux_clk); + if (phy->cfg_ahb_clk) + clk_prepare_enable(phy->cfg_ahb_clk); + + //select PHY pipe clock + clk_set_parent(phy->pipe_clk_mux, phy->pipe_clk_ext_src); + clk_prepare_enable(phy->pipe_clk); + phy->clk_enabled = true; + } + + if (phy->clk_enabled && !on) { + clk_disable_unprepare(phy->pipe_clk); + + //select XO instead of PHY pipe clock + clk_set_parent(phy->pipe_clk_mux, phy->ref_clk_src); + + if (phy->cfg_ahb_clk) + clk_disable_unprepare(phy->cfg_ahb_clk); + + clk_disable_unprepare(phy->aux_clk); + if (phy->com_aux_clk) + clk_disable_unprepare(phy->com_aux_clk); + + if (phy->ref_clk) + clk_disable_unprepare(phy->ref_clk); + + if (phy->ref_clk_src) + clk_disable_unprepare(phy->ref_clk_src); + + phy->clk_enabled = false; + } +} + +static int msm_ssphy_qmp_probe(struct platform_device *pdev) +{ + struct msm_ssphy_qmp *phy; + struct device *dev = &pdev->dev; + struct resource *res; + int ret = 0, size = 0, len; + + phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL); + if (!phy) + return -ENOMEM; + + phy->phy_type = USB3; + if (of_device_is_compatible(dev->of_node, + "qcom,usb-ssphy-qmp-dp-combo")) + phy->phy_type = USB3_AND_DP; + + if (of_device_is_compatible(dev->of_node, + "qcom,usb-ssphy-qmp-usb3-or-dp")) + phy->phy_type = USB3_OR_DP; + + ret = msm_ssphy_qmp_get_clks(phy, dev); + if (ret) + goto err; + + phy->phy_reset = devm_reset_control_get(dev, "phy_reset"); + if (IS_ERR(phy->phy_reset)) { + ret = PTR_ERR(phy->phy_reset); + dev_dbg(dev, "failed to get phy_reset\n"); + goto err; + } + + if (phy->phy_type == USB3_AND_DP) { + phy->global_phy_reset = devm_reset_control_get(dev, + "global_phy_reset"); + if (IS_ERR(phy->global_phy_reset)) { + ret = PTR_ERR(phy->global_phy_reset); + dev_dbg(dev, "failed to get global_phy_reset\n"); + goto err; + } + } else { + phy->phy_phy_reset = devm_reset_control_get(dev, + "phy_phy_reset"); + if (IS_ERR(phy->phy_phy_reset)) { + ret = PTR_ERR(phy->phy_phy_reset); + dev_dbg(dev, "failed to get phy_phy_reset\n"); + goto err; + } + } + + of_get_property(dev->of_node, "qcom,qmp-phy-reg-offset", &size); + if (size) { + phy->phy_reg = devm_kzalloc(dev, size, GFP_KERNEL); + if (phy->phy_reg) { + phy->reg_offset_cnt = (size / sizeof(*phy->phy_reg)); + if (phy->reg_offset_cnt > USB3_PHY_REG_MAX) { + dev_err(dev, "invalid reg offset count\n"); + return -EINVAL; + } + + of_property_read_u32_array(dev->of_node, + "qcom,qmp-phy-reg-offset", + phy->phy_reg, phy->reg_offset_cnt); + } else { + dev_err(dev, "err mem alloc for qmp_phy_reg_offset\n"); + return -ENOMEM; + } + } else { + dev_err(dev, "err provide qcom,qmp-phy-reg-offset\n"); + return -EINVAL; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "qmp_phy_base"); + if (!res) { + dev_err(dev, "failed getting qmp_phy_base\n"); + return -ENODEV; + } + + /* + * For USB QMP DP combo PHY, common set of registers shall be accessed + * by DP driver as well. + */ + phy->base = devm_ioremap(dev, res->start, resource_size(res)); + if (IS_ERR_OR_NULL(phy->base)) { + ret = PTR_ERR(phy->base); + goto err; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "vls_clamp_reg"); + if (res) { + phy->vls_clamp_reg = devm_ioremap_resource(dev, res); + if (IS_ERR(phy->vls_clamp_reg)) { + dev_err(dev, "err getting vls_clamp_reg address\n"); + return PTR_ERR(phy->vls_clamp_reg); + } + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "pcs_clamp_enable_reg"); + if (res) { + phy->pcs_clamp_enable_reg = devm_ioremap_resource(dev, res); + if (IS_ERR(phy->pcs_clamp_enable_reg)) { + dev_err(dev, "err getting pcs_clamp_enable_reg address.\n"); + return PTR_ERR(phy->pcs_clamp_enable_reg); + } + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "tcsr_usb3_dp_phymode"); + if (res) { + phy->tcsr_usb3_dp_phymode = devm_ioremap_resource(dev, res); + if (IS_ERR(phy->tcsr_usb3_dp_phymode)) { + dev_err(dev, "err getting tcsr_usb3_dp_phymode addr\n"); + return PTR_ERR(phy->tcsr_usb3_dp_phymode); + } + } + + of_get_property(dev->of_node, "qcom,qmp-phy-init-seq", &size); + if (size) { + if (size % sizeof(*phy->qmp_phy_init_seq)) { + dev_err(dev, "invalid init_seq_len\n"); + return -EINVAL; + } + + phy->qmp_phy_init_seq = devm_kzalloc(dev, size, GFP_KERNEL); + if (!phy->qmp_phy_init_seq) + return -ENOMEM; + + phy->init_seq_len = (size / sizeof(*phy->qmp_phy_init_seq)); + of_property_read_u32_array(dev->of_node, + "qcom,qmp-phy-init-seq", + phy->qmp_phy_init_seq, + phy->init_seq_len); + } else { + dev_err(dev, "error need qmp-phy-init-seq\n"); + return -EINVAL; + } + + /* Set default core voltage values */ + phy->core_voltage_levels[CORE_LEVEL_NONE] = 0; + phy->core_voltage_levels[CORE_LEVEL_MIN] = USB_SSPHY_1P2_VOL_MIN; + phy->core_voltage_levels[CORE_LEVEL_MAX] = USB_SSPHY_1P2_VOL_MAX; + + if (of_get_property(dev->of_node, "qcom,core-voltage-level", &len) && + len == sizeof(phy->core_voltage_levels)) { + ret = of_property_read_u32_array(dev->of_node, + "qcom,core-voltage-level", + (u32 *)phy->core_voltage_levels, + len / sizeof(u32)); + if (ret) { + dev_err(dev, "err qcom,core-voltage-level property\n"); + goto err; + } + } + + if (of_property_read_s32(dev->of_node, "qcom,core-max-load-uA", + &phy->core_max_uA) || !phy->core_max_uA) + phy->core_max_uA = USB_SSPHY_HPM_LOAD; + + if (of_get_property(dev->of_node, "qcom,vdd-voltage-level", &len) && + len == sizeof(phy->vdd_levels)) { + ret = of_property_read_u32_array(dev->of_node, + "qcom,vdd-voltage-level", + (u32 *) phy->vdd_levels, + len / sizeof(u32)); + if (ret) { + dev_err(dev, "err qcom,vdd-voltage-level property\n"); + goto err; + } + } else { + ret = -EINVAL; + dev_err(dev, "error invalid inputs for vdd-voltage-level\n"); + goto err; + } + + if (of_property_read_s32(dev->of_node, "qcom,vdd-max-load-uA", + &phy->vdd_max_uA) || !phy->vdd_max_uA) + phy->vdd_max_uA = USB_SSPHY_HPM_LOAD; + + phy->vdd = devm_regulator_get(dev, "vdd"); + if (IS_ERR(phy->vdd)) { + dev_err(dev, "unable to get vdd supply\n"); + ret = PTR_ERR(phy->vdd); + goto err; + } + + phy->core_ldo = devm_regulator_get(dev, "core"); + if (IS_ERR(phy->core_ldo)) { + dev_err(dev, "unable to get core ldo supply\n"); + ret = PTR_ERR(phy->core_ldo); + goto err; + } + + phy->usb3_dp_phy_gdsc = devm_regulator_get(dev, "usb3_dp_phy_gdsc"); + if (IS_ERR(phy->usb3_dp_phy_gdsc)) { + ret = PTR_ERR(phy->usb3_dp_phy_gdsc); + if (ret != -ENODEV) { + dev_err(dev, "fail to get usb3_dp_phy_gdsc(%d)\n", ret); + return ret; + } + dev_err(dev, "usb3_dp_phy_gdsc optional regulator missing\n"); + } + + platform_set_drvdata(pdev, phy); + + phy->phy.dev = dev; + phy->phy.init = msm_ssphy_qmp_init; + phy->phy.set_suspend = msm_ssphy_qmp_set_suspend; + phy->phy.notify_connect = msm_ssphy_qmp_notify_connect; + phy->phy.notify_disconnect = msm_ssphy_qmp_notify_disconnect; + + ret = usb_add_phy_dev(&phy->phy); + +err: + return ret; +} + +static int msm_ssphy_qmp_remove(struct platform_device *pdev) +{ + struct msm_ssphy_qmp *phy = platform_get_drvdata(pdev); + + if (!phy) + return 0; + + usb_remove_phy(&phy->phy); + msm_ssphy_qmp_enable_clks(phy, false); + msm_ssusb_qmp_ldo_enable(phy, 0); + return 0; +} + +static struct platform_driver msm_ssphy_qmp_driver = { + .probe = msm_ssphy_qmp_probe, + .remove = msm_ssphy_qmp_remove, + .driver = { + .name = "msm-usb-ssphy-qmp", + .of_match_table = of_match_ptr(msm_usb_id_table), + }, +}; + +module_platform_driver(msm_ssphy_qmp_driver); + +MODULE_DESCRIPTION("MSM USB SS QMP PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/phy/phy-qcom-emu.c b/drivers/usb/phy/phy-qcom-emu.c new file mode 100644 index 000000000000..9c270ffea03d --- /dev/null +++ b/drivers/usb/phy/phy-qcom-emu.c @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2014-2018, 2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include + +/* QSCRATCH registers */ +#define HS_PHY_CTRL_REG 0x10 +#define SW_SESSVLD_SEL BIT(28) + +/* HSPHY registers */ +#define HS2_LOCAL_RESET_REG_ADDR 0x04 +#define HS2_CLK_STATUS_ADDR 0x10 +#define HS2_CLK_STATUS_SEL_ADDR 0x14 +#define HS2_USB30_CTRL_ADDR 0x34 +#define HS2_USB30_PHY_POWER_OFF BIT(25) + +struct qcusb_emu_phy { + struct usb_phy phy; + struct device *dev; + void __iomem *base; + void __iomem *qscratch_base; + int *emu_init_seq; + int emu_init_seq_len; +}; + +static int qcusb_emu_phy_init(struct usb_phy *phy) +{ + struct qcusb_emu_phy *qphy = container_of(phy, + struct qcusb_emu_phy, phy); + u32 tmp; + int i; + + /* reset everything */ + writel_relaxed(0xffffffff, qphy->base + HS2_LOCAL_RESET_REG_ADDR); + usleep_range(10000, 12000); + + /* power down HS phy */ + tmp = readl_relaxed(qphy->base + HS2_USB30_CTRL_ADDR) | + HS2_USB30_PHY_POWER_OFF; + writel_relaxed(tmp, qphy->base + HS2_USB30_CTRL_ADDR); + usleep_range(10000, 12000); + + /* power up HS phy */ + tmp = readl_relaxed(qphy->base + HS2_USB30_CTRL_ADDR) & + (~HS2_USB30_PHY_POWER_OFF); + writel_relaxed(tmp, qphy->base + HS2_USB30_CTRL_ADDR); + usleep_range(10000, 12000); + + writel_relaxed(0xfffffff3, qphy->base + HS2_LOCAL_RESET_REG_ADDR); + usleep_range(10000, 12000); + + /* put phy out of reset */ + writel_relaxed(0xfffffff0, qphy->base + HS2_LOCAL_RESET_REG_ADDR); + usleep_range(10000, 12000); + + /* selection of HS phy clock MMCM value */ + for (i = 0; i < qphy->emu_init_seq_len; i = i+2) { + dev_dbg(phy->dev, "write 0x%02x to 0x%02x\n", + qphy->emu_init_seq[i], qphy->emu_init_seq[i+1]); + writel_relaxed(qphy->emu_init_seq[i], + qphy->base + qphy->emu_init_seq[i+1]); + /* 10ms to ensure write propagates across bus */ + usleep_range(10000, 12000); + } + + /* clear other reset */ + writel_relaxed(0x0, qphy->base + HS2_LOCAL_RESET_REG_ADDR); + usleep_range(10000, 12000); + + /* clock select to read UTMI/ULPI clock */ + writel_relaxed(0x9, qphy->base + HS2_CLK_STATUS_SEL_ADDR); + usleep_range(10000, 12000); + dev_info(phy->dev, "PHY UTMI/ULPI CLK frequency:%d MHz\n", + (readl_relaxed(qphy->base + HS2_CLK_STATUS_ADDR) / 1000)); + + if (qphy->qscratch_base) { + /* Use UTMI VBUS signal from HW */ + tmp = readl_relaxed(qphy->qscratch_base + HS_PHY_CTRL_REG); + tmp &= ~SW_SESSVLD_SEL; + writel_relaxed(tmp, qphy->qscratch_base + HS_PHY_CTRL_REG); + } + + return 0; +} + +static int qcusb_emu_phy_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct qcusb_emu_phy *qphy; + struct resource *res; + int ret, size; + + qphy = devm_kzalloc(dev, sizeof(*qphy), GFP_KERNEL); + if (!qphy) + return -ENOMEM; + + qphy->phy.dev = dev; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + qphy->base = devm_ioremap_resource(dev, res); + if (IS_ERR(qphy->base)) + return PTR_ERR(qphy->base); + + of_get_property(dev->of_node, "qcom,emu-init-seq", &size); + if (!size) { + dev_err(dev, "emu-init-seq not specified\n"); + return -EINVAL; + } + + qphy->emu_init_seq = devm_kzalloc(dev, size, GFP_KERNEL); + if (!qphy->emu_init_seq) + return -ENOMEM; + + qphy->emu_init_seq_len = (size / sizeof(*qphy->emu_init_seq)); + if (qphy->emu_init_seq_len % 2) { + dev_err(dev, "invalid emu_init_seq_len, must be in pairs\n"); + return -EINVAL; + } + + ret = of_property_read_u32_array(dev->of_node, "qcom,emu-init-seq", + qphy->emu_init_seq, qphy->emu_init_seq_len); + if (ret) { + dev_err(dev, "could not read emu-init-seq, returned %d\n", ret); + return ret; + } + + platform_set_drvdata(pdev, qphy); + + qphy->phy.label = "qcom-usb-emu-phy"; + qphy->phy.init = qcusb_emu_phy_init; + qphy->phy.type = USB_PHY_TYPE_USB2; + + ret = usb_add_phy_dev(&qphy->phy); + if (ret) + return ret; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "qscratch_base"); + if (res) { + qphy->qscratch_base = devm_ioremap(dev, res->start, + resource_size(res)); + if (IS_ERR(qphy->qscratch_base)) { + dev_dbg(dev, "error mapping qscratch\n"); + qphy->qscratch_base = NULL; + } + } + + return 0; +} + +static int qcusb_emu_phy_remove(struct platform_device *pdev) +{ + struct qcusb_emu_phy *qcphy = platform_get_drvdata(pdev); + + usb_remove_phy(&qcphy->phy); + + return 0; +} + +static const struct of_device_id emu_phy_dt_ids[] = { + { .compatible = "qcom,usb-emu-phy" }, + { } +}; + +MODULE_DEVICE_TABLE(of, emu_phy_dt_ids); + +static struct platform_driver qcusb_emu_phy_driver = { + .probe = qcusb_emu_phy_probe, + .remove = qcusb_emu_phy_remove, + .driver = { + .name = "usb_emu_phy", + .of_match_table = emu_phy_dt_ids, + }, +}; + +module_platform_driver(qcusb_emu_phy_driver); + +MODULE_DESCRIPTION("Qualcomm Technologies, Inc. USB Emulation PHY driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/ipc_logging.h b/include/linux/ipc_logging.h new file mode 100644 index 000000000000..0c10e11a9816 --- /dev/null +++ b/include/linux/ipc_logging.h @@ -0,0 +1,288 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2012-2015,2017-2021 The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef _IPC_LOGGING_H +#define _IPC_LOGGING_H + +#include +#include + +#define MAX_MSG_SIZE 255 + +enum { + TSV_TYPE_MSG_START = 1, + TSV_TYPE_SKB = TSV_TYPE_MSG_START, + TSV_TYPE_STRING, + TSV_TYPE_MSG_END = TSV_TYPE_STRING, +}; + +struct tsv_header { + unsigned char type; + unsigned char size; /* size of data field */ +}; + +struct encode_context { + struct tsv_header hdr; + char buff[MAX_MSG_SIZE]; + int offset; +}; + +struct decode_context { + int output_format; /* 0 = debugfs */ + char *buff; /* output buffer */ + int size; /* size of output buffer */ +}; + +#if IS_ENABLED(CONFIG_IPC_LOGGING) +/* + * ipc_log_context_create: Create a debug log context + * Should not be called from atomic context + * + * @max_num_pages: Number of pages of logging space required (max. 10) + * @mod_name : Name of the directory entry under DEBUGFS + * @feature_version : First 16 bit for version number of user-defined message + * formats and next 16 bit for enabling minidump + * + * returns context id on success, NULL on failure + */ +void *ipc_log_context_create(int max_num_pages, const char *modname, + uint32_t feature_version); + +/* + * msg_encode_start: Start encoding a log message + * + * @ectxt: Temporary storage to hold the encoded message + * @type: Root event type defined by the module which is logging + */ +void msg_encode_start(struct encode_context *ectxt, uint32_t type); + +/* + * tsv_timestamp_write: Writes the current timestamp count + * + * @ectxt: Context initialized by calling msg_encode_start() + */ +int tsv_timestamp_write(struct encode_context *ectxt); + +/* + * tsv_qtimer_write: Writes the current QTimer timestamp count + * + * @ectxt: Context initialized by calling msg_encode_start() + */ +int tsv_qtimer_write(struct encode_context *ectxt); + +/* + * tsv_pointer_write: Writes a data pointer + * + * @ectxt: Context initialized by calling msg_encode_start() + * @pointer: Pointer value to write + */ +int tsv_pointer_write(struct encode_context *ectxt, void *pointer); + +/* + * tsv_int32_write: Writes a 32-bit integer value + * + * @ectxt: Context initialized by calling msg_encode_start() + * @n: Integer to write + */ +int tsv_int32_write(struct encode_context *ectxt, int32_t n); + +/* + * tsv_byte_array_write: Writes a byte array + * + * @ectxt: Context initialized by calling msg_encode_start() + * @data: Pointer to byte array + * @data_size: Size of byte array + */ +int tsv_byte_array_write(struct encode_context *ectxt, + void *data, int data_size); + +/* + * msg_encode_end: Complete the message encode process + * + * @ectxt: Temporary storage which holds the encoded message + */ +void msg_encode_end(struct encode_context *ectxt); + +/* + * ipc_log_write: Commits message to logging ring buffer + * + * @ctxt: Logging context + * @ectxt: Temporary storage which holds the encoded message + */ +void ipc_log_write(void *ctxt, struct encode_context *ectxt); + +/* + * ipc_log_string: Helper function to log a string + * + * @ilctxt: Debug Log Context created using ipc_log_context_create() + * @fmt: Data specified using format specifiers + */ +int ipc_log_string(void *ilctxt, const char *fmt, ...) __printf(2, 3); + +/** + * ipc_log_extract - Reads and deserializes log + * + * @ilctxt: logging context + * @buff: buffer to receive the data + * @size: size of the buffer + * @returns: 0 if no data read; >0 number of bytes read; < 0 error + * + * If no data is available to be read, then the ilctxt::read_avail + * completion is reinitialized. This allows clients to block + * until new log data is save. + */ +int ipc_log_extract(void *ilctxt, char *buff, int size); + +/* + * Print a string to decode context. + * @dctxt Decode context + * @args printf args + */ +#define IPC_SPRINTF_DECODE(dctxt, args...) \ +do { \ + int i; \ + i = scnprintf(dctxt->buff, dctxt->size, args); \ + dctxt->buff += i; \ + dctxt->size -= i; \ +} while (0) + +/* + * tsv_timestamp_read: Reads a timestamp + * + * @ectxt: Context retrieved by reading from log space + * @dctxt: Temporary storage to hold the decoded message + * @format: Output format while dumping through DEBUGFS + */ +void tsv_timestamp_read(struct encode_context *ectxt, + struct decode_context *dctxt, const char *format); + +/* + * tsv_qtimer_read: Reads a QTimer timestamp + * + * @ectxt: Context retrieved by reading from log space + * @dctxt: Temporary storage to hold the decoded message + * @format: Output format while dumping through DEBUGFS + */ +void tsv_qtimer_read(struct encode_context *ectxt, + struct decode_context *dctxt, const char *format); + +/* + * tsv_pointer_read: Reads a data pointer + * + * @ectxt: Context retrieved by reading from log space + * @dctxt: Temporary storage to hold the decoded message + * @format: Output format while dumping through DEBUGFS + */ +void tsv_pointer_read(struct encode_context *ectxt, + struct decode_context *dctxt, const char *format); + +/* + * tsv_int32_read: Reads a 32-bit integer value + * + * @ectxt: Context retrieved by reading from log space + * @dctxt: Temporary storage to hold the decoded message + * @format: Output format while dumping through DEBUGFS + */ +int32_t tsv_int32_read(struct encode_context *ectxt, + struct decode_context *dctxt, const char *format); + +/* + * tsv_byte_array_read: Reads a byte array + * + * @ectxt: Context retrieved by reading from log space + * @dctxt: Temporary storage to hold the decoded message + * @format: Output format while dumping through DEBUGFS + */ +void tsv_byte_array_read(struct encode_context *ectxt, + struct decode_context *dctxt, const char *format); + +/* + * add_deserialization_func: Register a deserialization function to + * unpack the subevents of a main event + * + * @ctxt: Debug log context to which the deserialization function has + * to be registered + * @type: Main/Root event, defined by the module which is logging, to + * which this deserialization function has to be registered. + * @dfune: Deserialization function to be registered + * + * return 0 on success, -ve value on FAILURE + */ +int add_deserialization_func(void *ctxt, int type, + void (*dfunc)(struct encode_context *, + struct decode_context *)); + +/* + * ipc_log_context_destroy: Destroy debug log context + * + * @ctxt: debug log context created by calling ipc_log_context_create API. + */ +int ipc_log_context_destroy(void *ctxt); + +#else + +static inline void *ipc_log_context_create(int max_num_pages, + const char *modname, uint32_t feature_version) +{ return NULL; } + +static inline void msg_encode_start(struct encode_context *ectxt, + uint32_t type) { } + +static inline int tsv_timestamp_write(struct encode_context *ectxt) +{ return -EINVAL; } + +static inline int tsv_qtimer_write(struct encode_context *ectxt) +{ return -EINVAL; } + +static inline int tsv_pointer_write(struct encode_context *ectxt, void *pointer) +{ return -EINVAL; } + +static inline int tsv_int32_write(struct encode_context *ectxt, int32_t n) +{ return -EINVAL; } + +static inline int tsv_byte_array_write(struct encode_context *ectxt, + void *data, int data_size) +{ return -EINVAL; } + +static inline void msg_encode_end(struct encode_context *ectxt) { } + +static inline void ipc_log_write(void *ctxt, struct encode_context *ectxt) { } + +static inline int ipc_log_string(void *ilctxt, const char *fmt, ...) +{ return -EINVAL; } + +static inline int ipc_log_extract(void *ilctxt, char *buff, int size) +{ return -EINVAL; } + +#define IPC_SPRINTF_DECODE(dctxt, args...) do { } while (0) + +static inline void tsv_timestamp_read(struct encode_context *ectxt, + struct decode_context *dctxt, const char *format) { } + +static inline void tsv_qtimer_read(struct encode_context *ectxt, + struct decode_context *dctxt, const char *format) { } + +static inline void tsv_pointer_read(struct encode_context *ectxt, + struct decode_context *dctxt, const char *format) { } + +static inline int32_t tsv_int32_read(struct encode_context *ectxt, + struct decode_context *dctxt, const char *format) +{ return 0; } + +static inline void tsv_byte_array_read(struct encode_context *ectxt, + struct decode_context *dctxt, const char *format) { } + +static inline int add_deserialization_func(void *ctxt, int type, + void (*dfunc)(struct encode_context *, + struct decode_context *)) +{ return 0; } + +static inline int ipc_log_context_destroy(void *ctxt) +{ return 0; } + +#endif + +#endif diff --git a/include/linux/usb/dwc3-msm.h b/include/linux/usb/dwc3-msm.h new file mode 100644 index 000000000000..936692417ed7 --- /dev/null +++ b/include/linux/usb/dwc3-msm.h @@ -0,0 +1,335 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2019-2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef __LINUX_USB_DWC3_MSM_H +#define __LINUX_USB_DWC3_MSM_H + +#include +#include +#include + +/* used for struct usb_phy flags */ +#define PHY_HOST_MODE BIT(0) +#define DEVICE_IN_SS_MODE BIT(1) +#define PHY_LANE_A BIT(2) +#define PHY_LANE_B BIT(3) +#define PHY_HSFS_MODE BIT(4) +#define PHY_LS_MODE BIT(5) +#define EUD_SPOOF_DISCONNECT BIT(6) +#define EUD_SPOOF_CONNECT BIT(7) +#define PHY_SUS_OVERRIDE BIT(8) +#define PHY_DP_MODE BIT(9) +#define PHY_USB_DP_CONCURRENT_MODE BIT(10) + +/* + * The following are bit fields describing the USB BAM options. + * These bit fields are set by function drivers that wish to queue + * usb_requests with sps/bam parameters. + */ +#define MSM_TX_PIPE_ID_OFS (16) +#define MSM_SPS_MODE BIT(5) +#define MSM_IS_FINITE_TRANSFER BIT(6) +#define MSM_PRODUCER BIT(7) +#define MSM_DISABLE_WB BIT(8) +#define MSM_ETD_IOC BIT(9) +#define MSM_INTERNAL_MEM BIT(10) +#define MSM_VENDOR_ID BIT(16) + +/* EBC TRB parameters */ +#define EBC_TRB_SIZE 16384 + +/* Operations codes for GSI enabled EPs */ +enum gsi_ep_op { + GSI_EP_OP_CONFIG = 0, + GSI_EP_OP_STARTXFER, + GSI_EP_OP_STORE_DBL_INFO, + GSI_EP_OP_ENABLE_GSI, + GSI_EP_OP_UPDATEXFER, + GSI_EP_OP_RING_DB, + GSI_EP_OP_ENDXFER, + GSI_EP_OP_GET_CH_INFO, + GSI_EP_OP_GET_XFER_IDX, + GSI_EP_OP_PREPARE_TRBS, + GSI_EP_OP_FREE_TRBS, + GSI_EP_OP_SET_CLR_BLOCK_DBL, + GSI_EP_OP_CHECK_FOR_SUSPEND, + GSI_EP_OP_DISABLE, +}; + +enum usb_hw_ep_mode { + USB_EP_NONE, + USB_EP_BAM, + USB_EP_GSI, + USB_EP_EBC, +}; + +enum dwc3_notify_event { + DWC3_CONTROLLER_ERROR_EVENT, + DWC3_CONTROLLER_RESET_EVENT, + DWC3_CONTROLLER_POST_RESET_EVENT, + DWC3_CORE_PM_SUSPEND_EVENT, + DWC3_CORE_PM_RESUME_EVENT, + DWC3_CONTROLLER_CONNDONE_EVENT, + DWC3_CONTROLLER_NOTIFY_OTG_EVENT, + DWC3_CONTROLLER_NOTIFY_DISABLE_UPDXFER, + DWC3_CONTROLLER_PULLUP_ENTER, + DWC3_CONTROLLER_PULLUP_EXIT, + + /* USB GSI event buffer related notification */ + DWC3_GSI_EVT_BUF_ALLOC, + DWC3_GSI_EVT_BUF_SETUP, + DWC3_GSI_EVT_BUF_CLEANUP, + DWC3_GSI_EVT_BUF_CLEAR, + DWC3_GSI_EVT_BUF_FREE, + DWC3_CONTROLLER_NOTIFY_CLEAR_DB, +}; + +/* + * @buf_base_addr: Base pointer to buffer allocated for each GSI enabled EP. + * TRBs point to buffers that are split from this pool. The size of the + * buffer is num_bufs times buf_len. num_bufs and buf_len are determined + based on desired performance and aggregation size. + * @dma: DMA address corresponding to buf_base_addr. + * @num_bufs: Number of buffers associated with the GSI enabled EP. This + * corresponds to the number of non-zlp TRBs allocated for the EP. + * The value is determined based on desired performance for the EP. + * @buf_len: Size of each individual buffer is determined based on aggregation + * negotiated as per the protocol. In case of no aggregation supported by + * the protocol, we use default values. + * @db_reg_phs_addr_lsb: IPA channel doorbell register's physical address LSB + * @mapped_db_reg_phs_addr_lsb: doorbell LSB IOVA address mapped with IOMMU + * @db_reg_phs_addr_msb: IPA channel doorbell register's physical address MSB + * @ep_intr_num: Interrupter number for EP. + */ +struct usb_gsi_request { + void *buf_base_addr; + dma_addr_t dma; + size_t num_bufs; + size_t buf_len; + u32 db_reg_phs_addr_lsb; + dma_addr_t mapped_db_reg_phs_addr_lsb; + u32 db_reg_phs_addr_msb; + u8 ep_intr_num; + struct sg_table sgt_trb_xfer_ring; + struct sg_table sgt_data_buff; +}; + +/* + * @last_trb_addr: Address (LSB - based on alignment restrictions) of + * last TRB in queue. Used to identify rollover case. + * @const_buffer_size: TRB buffer size in KB (similar to IPA aggregation + * configuration). Must be aligned to Max USB Packet Size. + * Should be 1 <= const_buffer_size <= 31. + * @depcmd_low_addr: Used by GSI hardware to write "Update Transfer" cmd + * @depcmd_hi_addr: Used to write "Update Transfer" command. + * @gevntcount_low_addr: GEVNCOUNT low address for GSI hardware to read and + * clear processed events. + * @gevntcount_hi_addr: GEVNCOUNT high address. + * @xfer_ring_len: length of transfer ring in bytes (must be integral + * multiple of TRB size - 16B for xDCI). + * @xfer_ring_base_addr: physical base address of transfer ring. Address must + * be aligned to xfer_ring_len rounded to power of two. + * @ch_req: Used to pass request specific info for certain operations on GSI EP + */ +struct gsi_channel_info { + u16 last_trb_addr; + u8 const_buffer_size; + u32 depcmd_low_addr; + u8 depcmd_hi_addr; + u32 gevntcount_low_addr; + u8 gevntcount_hi_addr; + u16 xfer_ring_len; + u64 xfer_ring_base_addr; + struct usb_gsi_request *ch_req; +}; + +struct dwc3; +extern void *dwc_trace_ipc_log_ctxt; + +/** + * usb_gadget_autopm_get - increment PM-usage counter of usb gadget's parent + * device. + * @gadget: usb gadget whose parent device counter is incremented + * + * This routine should be called by function driver when it wants to use + * gadget's parent device and needs to guarantee that it is not suspended. In + * addition, the routine prevents subsequent autosuspends of gadget's parent + * device. However if the autoresume fails then the counter is re-decremented. + * + * This routine can run only in process context. + */ +static inline int usb_gadget_autopm_get(struct usb_gadget *gadget) +{ + int status = -ENODEV; + + if (!gadget || !gadget->dev.parent) + return status; + + status = pm_runtime_get_sync(gadget->dev.parent); + if (status < 0) + pm_runtime_put_sync(gadget->dev.parent); + + if (status > 0) + status = 0; + return status; +} + +/** + * usb_gadget_autopm_get_async - increment PM-usage counter of usb gadget's + * parent device. + * @gadget: usb gadget whose parent device counter is incremented + * + * This routine increments @gadget parent device PM usage counter and queue an + * autoresume request if the device is suspended. It does not autoresume device + * directly (it only queues a request). After a successful call, the device may + * not yet be resumed. + * + * This routine can run in atomic context. + */ +static inline int usb_gadget_autopm_get_async(struct usb_gadget *gadget) +{ + int status = -ENODEV; + + if (!gadget || !gadget->dev.parent) + return status; + + status = pm_runtime_get(gadget->dev.parent); + if (status < 0 && status != -EINPROGRESS) + pm_runtime_put_noidle(gadget->dev.parent); + + if (status > 0 || status == -EINPROGRESS) + status = 0; + return status; +} + +/** + * usb_gadget_autopm_get_noresume - increment PM-usage counter of usb gadget's + * parent device. + * @gadget: usb gadget whose parent device counter is incremented + * + * This routine increments PM-usage count of @gadget parent device but does not + * carry out an autoresume. + * + * This routine can run in atomic context. + */ +static inline void usb_gadget_autopm_get_noresume(struct usb_gadget *gadget) +{ + if (gadget && gadget->dev.parent) + pm_runtime_get_noresume(gadget->dev.parent); +} + +/** + * usb_gadget_autopm_put - decrement PM-usage counter of usb gadget's parent + * device. + * @gadget: usb gadget whose parent device counter is decremented. + * + * This routine should be called by function driver when it is finished using + * @gadget parent device and wants to allow it to autosuspend. It decrements + * PM-usage counter of @gadget parent device, when the counter reaches 0, a + * delayed autosuspend request is attempted. + * + * This routine can run only in process context. + */ +static inline void usb_gadget_autopm_put(struct usb_gadget *gadget) +{ + if (gadget && gadget->dev.parent) + pm_runtime_put_sync(gadget->dev.parent); +} + +/** + * usb_gadget_autopm_put_async - decrement PM-usage counter of usb gadget's + * parent device. + * @gadget: usb gadget whose parent device counter is decremented. + * + * This routine decrements PM-usage counter of @gadget parent device and + * schedules a delayed autosuspend request if the counter is <= 0. + * + * This routine can run in atomic context. + */ +static inline void usb_gadget_autopm_put_async(struct usb_gadget *gadget) +{ + if (gadget && gadget->dev.parent) + pm_runtime_put(gadget->dev.parent); +} + +/** + * usb_gadget_autopm_put_no_suspend - decrement PM-usage counter of usb gadget +'s + * parent device. + * @gadget: usb gadget whose parent device counter is decremented. + * + * This routine decrements PM-usage counter of @gadget parent device but does + * not carry out an autosuspend. + * + * This routine can run in atomic context. + */ +static inline void usb_gadget_autopm_put_no_suspend(struct usb_gadget *gadget) +{ + if (gadget && gadget->dev.parent) + pm_runtime_put_noidle(gadget->dev.parent); +} + +#if IS_ENABLED(CONFIG_USB_DWC3_MSM) +void dwc3_msm_notify_event(struct dwc3 *dwc, + enum dwc3_notify_event event, unsigned int value); +int usb_gsi_ep_op(struct usb_ep *ep, void *op_data, enum gsi_ep_op op); +int msm_ep_config(struct usb_ep *ep, struct usb_request *request, u32 bam_opts); +int msm_ep_unconfig(struct usb_ep *ep); +void dwc3_tx_fifo_resize_request(struct usb_ep *ep, bool qdss_enable); +int msm_data_fifo_config(struct usb_ep *ep, unsigned long addr, u32 size, + u8 dst_pipe_idx); +int msm_dwc3_reset_dbm_ep(struct usb_ep *ep); +int dwc3_msm_set_dp_mode(struct device *dev, bool connected, int lanes); +int dwc3_msm_release_ss_lane(struct device *dev); +int msm_ep_update_ops(struct usb_ep *ep); +int msm_ep_clear_ops(struct usb_ep *ep); +int msm_ep_set_mode(struct usb_ep *ep, enum usb_hw_ep_mode mode); +int dwc3_core_stop_hw_active_transfers(struct dwc3 *dwc); +#else +void dwc3_msm_notify_event(struct dwc3 *dwc, + enum dwc3_notify_event event, unsigned int value) +{ } +static inline int usb_gsi_ep_op(struct usb_ep *ep, void *op_data, + enum gsi_ep_op op) +{ return 0; } +static inline int msm_data_fifo_config(struct usb_ep *ep, unsigned long addr, + u32 size, u8 dst_pipe_idx) +{ return -ENODEV; } +static inline int msm_ep_config(struct usb_ep *ep, struct usb_request *request, + u32 bam_opts) +{ return -ENODEV; } +static inline int msm_ep_unconfig(struct usb_ep *ep) +{ return -ENODEV; } +static inline void dwc3_tx_fifo_resize_request(struct usb_ep *ep, + bool qdss_enable) +{ } +static inline bool msm_dwc3_reset_ep_after_lpm(struct usb_gadget *gadget) +{ return false; } +static inline int dwc3_msm_set_dp_mode(struct device *dev, bool connected, int lanes) +{ return -ENODEV; } +static inline int dwc3_msm_release_ss_lane(struct device *dev) +{ return -ENODEV; } +int msm_ep_update_ops(struct usb_ep *ep) +{ return -ENODEV; } +int msm_ep_clear_ops(struct usb_ep *ep) +{ return -ENODEV; } +int msm_ep_set_mode(struct usb_ep *ep, enum usb_hw_ep_mode mode) +{ return -ENODEV; } +inline int dwc3_core_stop_hw_active_transfers(struct dwc3 *dwc) +{ return 0; } +#endif + +#ifdef CONFIG_ARM64 +int dwc3_msm_kretprobe_init(void); +void dwc3_msm_kretprobe_exit(void); +#else +int dwc3_msm_kretprobe_init(void) +{ return 0; } +void dwc3_msm_kretprobe_exit(void) +{ } +#endif + +#endif /* __LINUX_USB_DWC3_MSM_H */ diff --git a/include/linux/usb/redriver.h b/include/linux/usb/redriver.h new file mode 100644 index 000000000000..37a56370f1c9 --- /dev/null +++ b/include/linux/usb/redriver.h @@ -0,0 +1,127 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#ifndef __LINUX_USB_REDRIVER_H +#define __LINUX_USB_REDRIVER_H + +#include +#include +#include + +/* + * design rules, + * [a] assume pullup operation happen in kretprobe. + * in kretprobe function, mutex lock is not allowed; + * in kretprobe function, schedule_work() is allowed; + * [b] this is core driver which service lower redriver and upper user. + * [c] redriver must probe early than user, or not user will defer probe. + * [d] redriver can rmmod only when there is no user bind to it. + * [e] if user rmmod, redirver will change to default state. + * [f] if redriver module insmod after new change and build, + * user module also need insmod to work. + * [g] when a redriver probe, set to disable state, all control from user. + * as ssphy have no eud function which don't need to keep working. + * [h] user should be ssphy, but current user is dwc3, + * as seem some redriver have termination issue, + * it need to do pullup operation from controller driver. + */ + + +#define ORIENTATION_CC1 0 +#define ORIENTATION_CC2 1 + +/** + * struct usb_redriver - present a redriver chip + * @list: link all redriver chips + * @of_node: redriver chip device tree node + * @release_usb_lanes: put redriver into 2/4 lanes display mode + * @notify_connect: cable connect + * @notify_disconnect: cable disconnect + * @orientation_get: report orientation to user if orientation source shared + * @gadget_pullup_enter: operation when enter gadget pullup function + * @gadget_pullup_exit: operation when exit gadget pullup function + * @host_power_cycle: workaround for host otg case + * @unbind, change to default state when user unbind it + * @has_orientation, provide orientation from chip driver or not + * @bounded, bound to user or not + */ +struct usb_redriver { + struct list_head list; + struct device_node *of_node; + + int (*release_usb_lanes)(struct usb_redriver *ur, int num); + int (*notify_connect)(struct usb_redriver *ur, int ort); + int (*notify_disconnect)(struct usb_redriver *ur); + int (*get_orientation)(struct usb_redriver *ur); + int (*gadget_pullup_enter)(struct usb_redriver *ur, int is_on); + int (*gadget_pullup_exit)(struct usb_redriver *ur, int is_on); + int (*host_powercycle)(struct usb_redriver *ur); + void (*unbind)(struct usb_redriver *ur); + + bool has_orientation; + bool bounded; +}; + +#if IS_ENABLED(CONFIG_USB_REDRIVER) +int usb_add_redriver(struct usb_redriver *ur); +int usb_remove_redriver(struct usb_redriver *ur); + +struct usb_redriver *usb_get_redriver_by_phandle( + const struct device_node *np, + const char *phandle_name, int index); +void usb_put_redriver(struct usb_redriver *ur); +void usb_redriver_release_lanes(struct usb_redriver *ur, int num); +void usb_redriver_notify_connect(struct usb_redriver *ur, int ort); +void usb_redriver_notify_disconnect(struct usb_redriver *ur); +int usb_redriver_get_orientation(struct usb_redriver *ur); +void usb_redriver_gadget_pullup_enter(struct usb_redriver *ur, int is_on); +void usb_redriver_gadget_pullup_exit(struct usb_redriver *ur, int is_on); +void usb_redriver_host_powercycle(struct usb_redriver *ur); + +#else + +static inline int usb_add_redriver(struct usb_redriver *ur) +{ + return 0; +} + +static inline struct usb_redriver *usb_get_redriver_by_phandle( + const struct device_node *np, + const char *phandle_name, int index) +{ + return NULL; +} + +static inline int usb_remove_redriver(struct usb_redriver *ur) +{ + return 0; +} + +static inline int usb_redriver_get_orientation(struct usb_redriver *ur) +{ + return -1; +} + +#define usb_put_redriver(ur) do {} while (0) + +#define usb_redriver_release_lanes(ur, num) do {} while (0) +#define usb_redriver_notify_connect(ur, ort) do {} while (0) +#define usb_redriver_notify_disconnect(ur) do {} while (0) +#define usb_redriver_gadget_pullup_enter(ur, is_on) do {} while (0) +#define usb_redriver_gadget_pullup_exit(ur, is_on) do {} while (0) +#define usb_redriver_host_powercycle(ur) do {} while (0) + +#endif + +static inline bool usb_redriver_has_orientation(struct usb_redriver *ur) +{ + if (ur && ur->has_orientation) + return true; + + return false; +} + +#endif /*__LINUX_USB_REDRIVER_H */ diff --git a/modules.list.msm.pineapple b/modules.list.msm.pineapple index 1c7c94d7d176..5adc37461bda 100644 --- a/modules.list.msm.pineapple +++ b/modules.list.msm.pineapple @@ -8,3 +8,6 @@ stub-regulator.ko phy-qcom-ufs.ko phy-qcom-ufs-qrbtc-sdm845.ko ufs_qcom.ko +dwc3-msm.ko +phy-qcom-emu.ko +phy-generic.ko