Merge "pineapple: Add USB modules to modules.list.msm.pineapple"

This commit is contained in:
qctecmdr 2022-04-28 16:21:12 -07:00 committed by Gerrit - the friendly Code Review server
commit 82665d760a
16 changed files with 11494 additions and 0 deletions

View File

@ -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

View File

@ -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

View File

@ -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 <linux/ipc_logging.h>
/*
* 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 */

View File

@ -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 <linux/moduleparam.h>
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);
}

File diff suppressed because it is too large Load Diff

View File

@ -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 <linux/kernel.h>
#include <linux/module.h>
#include <linux/kprobes.h>
#include <linux/irq.h>
#include <linux/irqdesc.h>
#include <linux/sched.h>
#include <linux/usb/dwc3-msm.h>
#include <linux/usb/composite.h>
#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]);
}

View File

@ -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

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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 <linux/module.h>
#include <linux/kernel.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/regulator/consumer.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>
#include <linux/usb/phy.h>
#include <linux/usb/dwc3-msm.h>
#include <linux/reset.h>
#include <linux/debugfs.h>
#include <linux/qcom_scm.h>
#include <linux/types.h>
#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");

File diff suppressed because it is too large Load Diff

View File

@ -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 <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/delay.h>
#include <linux/usb/phy.h>
/* 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 <data,addr> 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");

288
include/linux/ipc_logging.h Normal file
View File

@ -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 <linux/errno.h>
#include <linux/types.h>
#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

View File

@ -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 <linux/pm_runtime.h>
#include <linux/scatterlist.h>
#include <linux/usb/gadget.h>
/* 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 */

View File

@ -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 <linux/list.h>
#include <linux/device.h>
#include <linux/of.h>
/*
* 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 */

View File

@ -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