Adding upstream version 6.12.33.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
This commit is contained in:
parent
89eabb05c2
commit
79d69e5050
86698 changed files with 39662057 additions and 0 deletions
133
drivers/usb/cdns3/Kconfig
Normal file
133
drivers/usb/cdns3/Kconfig
Normal file
|
@ -0,0 +1,133 @@
|
|||
config USB_CDNS_SUPPORT
|
||||
tristate "Cadence USB Support"
|
||||
depends on USB_SUPPORT && (USB || USB_GADGET) && HAS_DMA
|
||||
select USB_XHCI_PLATFORM if USB_XHCI_HCD
|
||||
select USB_ROLE_SWITCH
|
||||
help
|
||||
Say Y here if your system has a Cadence USBSS or USBSSP
|
||||
dual-role controller.
|
||||
It supports: dual-role switch, Host-only, and Peripheral-only.
|
||||
|
||||
config USB_CDNS_HOST
|
||||
bool
|
||||
|
||||
if USB_CDNS_SUPPORT
|
||||
|
||||
config USB_CDNS3
|
||||
tristate "Cadence USB3 Dual-Role Controller"
|
||||
depends on USB_CDNS_SUPPORT
|
||||
help
|
||||
Say Y here if your system has a Cadence USB3 dual-role controller.
|
||||
It supports: dual-role switch, Host-only, and Peripheral-only.
|
||||
|
||||
If you choose to build this driver is a dynamically linked
|
||||
as module, the module will be called cdns3.ko.
|
||||
endif
|
||||
|
||||
if USB_CDNS3
|
||||
|
||||
config USB_CDNS3_GADGET
|
||||
bool "Cadence USB3 device controller"
|
||||
depends on USB_GADGET=y || USB_GADGET=USB_CDNS3
|
||||
help
|
||||
Say Y here to enable device controller functionality of the
|
||||
Cadence USBSS-DEV driver.
|
||||
|
||||
This controller supports FF, HS and SS mode. It doesn't support
|
||||
LS and SSP mode.
|
||||
|
||||
config USB_CDNS3_HOST
|
||||
bool "Cadence USB3 host controller"
|
||||
depends on USB=y || USB=USB_CDNS3
|
||||
select USB_CDNS_HOST
|
||||
help
|
||||
Say Y here to enable host controller functionality of the
|
||||
Cadence driver.
|
||||
|
||||
Host controller is compliant with XHCI so it will use
|
||||
standard XHCI driver.
|
||||
|
||||
config USB_CDNS3_PCI_WRAP
|
||||
tristate "Cadence USB3 support on PCIe-based platforms"
|
||||
depends on USB_PCI && ACPI
|
||||
default USB_CDNS3
|
||||
help
|
||||
If you're using the USBSS Core IP with a PCIe, please say
|
||||
'Y' or 'M' here.
|
||||
|
||||
If you choose to build this driver as module it will
|
||||
be dynamically linked and module will be called cdns3-pci.ko
|
||||
|
||||
config USB_CDNS3_TI
|
||||
tristate "Cadence USB3 support on TI platforms"
|
||||
depends on ARCH_K3 || COMPILE_TEST
|
||||
default USB_CDNS3
|
||||
help
|
||||
Say 'Y' or 'M' here if you are building for Texas Instruments
|
||||
platforms that contain Cadence USB3 controller core.
|
||||
|
||||
e.g. J721e.
|
||||
|
||||
config USB_CDNS3_IMX
|
||||
tristate "Cadence USB3 support on NXP i.MX platforms"
|
||||
depends on ARCH_MXC || COMPILE_TEST
|
||||
default USB_CDNS3
|
||||
help
|
||||
Say 'Y' or 'M' here if you are building for NXP i.MX
|
||||
platforms that contain Cadence USB3 controller core.
|
||||
|
||||
For example, imx8qm and imx8qxp.
|
||||
|
||||
config USB_CDNS3_STARFIVE
|
||||
tristate "Cadence USB3 support on StarFive SoC platforms"
|
||||
depends on ARCH_STARFIVE || COMPILE_TEST
|
||||
help
|
||||
Say 'Y' or 'M' here if you are building for StarFive SoCs
|
||||
platforms that contain Cadence USB3 controller core.
|
||||
|
||||
e.g. JH7110.
|
||||
|
||||
If you choose to build this driver as module it will
|
||||
be dynamically linked and module will be called cdns3-starfive.ko
|
||||
endif
|
||||
|
||||
if USB_CDNS_SUPPORT
|
||||
|
||||
config USB_CDNSP_PCI
|
||||
tristate "Cadence CDNSP Dual-Role Controller"
|
||||
depends on USB_CDNS_SUPPORT && USB_PCI && ACPI
|
||||
help
|
||||
Say Y here if your system has a Cadence CDNSP dual-role controller.
|
||||
It supports: dual-role switch Host-only, and Peripheral-only.
|
||||
|
||||
If you choose to build this driver is a dynamically linked
|
||||
module, the module will be called cdnsp.ko.
|
||||
endif
|
||||
|
||||
if USB_CDNSP_PCI
|
||||
|
||||
config USB_CDNSP_GADGET
|
||||
bool "Cadence CDNSP device controller"
|
||||
depends on USB_GADGET=y || USB_GADGET=USB_CDNSP_PCI
|
||||
help
|
||||
Say Y here to enable device controller functionality of the
|
||||
Cadence CDNSP-DEV driver.
|
||||
|
||||
Cadence CDNSP Device Controller in device mode is
|
||||
very similar to XHCI controller. Therefore some algorithms
|
||||
used has been taken from host driver.
|
||||
This controller supports FF, HS, SS and SSP mode.
|
||||
It doesn't support LS.
|
||||
|
||||
config USB_CDNSP_HOST
|
||||
bool "Cadence CDNSP host controller"
|
||||
depends on USB=y || USB=USB_CDNSP_PCI
|
||||
select USB_CDNS_HOST
|
||||
help
|
||||
Say Y here to enable host controller functionality of the
|
||||
Cadence driver.
|
||||
|
||||
Host controller is compliant with XHCI so it uses
|
||||
standard XHCI driver.
|
||||
|
||||
endif
|
44
drivers/usb/cdns3/Makefile
Normal file
44
drivers/usb/cdns3/Makefile
Normal file
|
@ -0,0 +1,44 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
# define_trace.h needs to know how to find our header
|
||||
CFLAGS_cdns3-trace.o := -I$(src)
|
||||
CFLAGS_cdnsp-trace.o := -I$(src)
|
||||
|
||||
cdns-usb-common-y := core.o drd.o
|
||||
cdns3-y := cdns3-plat.o
|
||||
|
||||
ifeq ($(CONFIG_USB),m)
|
||||
obj-m += cdns-usb-common.o
|
||||
obj-m += cdns3.o
|
||||
else
|
||||
obj-$(CONFIG_USB_CDNS_SUPPORT) += cdns-usb-common.o
|
||||
obj-$(CONFIG_USB_CDNS3) += cdns3.o
|
||||
endif
|
||||
|
||||
cdns-usb-common-$(CONFIG_USB_CDNS_HOST) += host.o
|
||||
cdns3-$(CONFIG_USB_CDNS3_GADGET) += cdns3-gadget.o cdns3-ep0.o
|
||||
|
||||
ifneq ($(CONFIG_USB_CDNS3_GADGET),)
|
||||
cdns3-$(CONFIG_TRACING) += cdns3-trace.o
|
||||
endif
|
||||
|
||||
obj-$(CONFIG_USB_CDNS3_PCI_WRAP) += cdns3-pci-wrap.o
|
||||
obj-$(CONFIG_USB_CDNS3_TI) += cdns3-ti.o
|
||||
obj-$(CONFIG_USB_CDNS3_IMX) += cdns3-imx.o
|
||||
obj-$(CONFIG_USB_CDNS3_STARFIVE) += cdns3-starfive.o
|
||||
|
||||
cdnsp-udc-pci-y := cdnsp-pci.o
|
||||
|
||||
ifdef CONFIG_USB_CDNSP_PCI
|
||||
ifeq ($(CONFIG_USB),m)
|
||||
obj-m += cdnsp-udc-pci.o
|
||||
else
|
||||
obj-$(CONFIG_USB_CDNSP_PCI) += cdnsp-udc-pci.o
|
||||
endif
|
||||
endif
|
||||
|
||||
cdnsp-udc-pci-$(CONFIG_USB_CDNSP_GADGET) += cdnsp-ring.o cdnsp-gadget.o \
|
||||
cdnsp-mem.o cdnsp-ep0.o
|
||||
|
||||
ifneq ($(CONFIG_USB_CDNSP_GADGET),)
|
||||
cdnsp-udc-pci-$(CONFIG_TRACING) += cdnsp-trace.o
|
||||
endif
|
157
drivers/usb/cdns3/cdns3-debug.h
Normal file
157
drivers/usb/cdns3/cdns3-debug.h
Normal file
|
@ -0,0 +1,157 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Cadence USBSS DRD Driver.
|
||||
* Debug header file.
|
||||
*
|
||||
* Copyright (C) 2018-2019 Cadence.
|
||||
*
|
||||
* Author: Pawel Laszczak <pawell@cadence.com>
|
||||
*/
|
||||
#ifndef __LINUX_CDNS3_DEBUG
|
||||
#define __LINUX_CDNS3_DEBUG
|
||||
|
||||
#include "core.h"
|
||||
|
||||
static inline char *cdns3_decode_usb_irq(char *str,
|
||||
enum usb_device_speed speed,
|
||||
u32 usb_ists)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = sprintf(str, "IRQ %08x = ", usb_ists);
|
||||
|
||||
if (usb_ists & (USB_ISTS_CON2I | USB_ISTS_CONI)) {
|
||||
ret += sprintf(str + ret, "Connection %s\n",
|
||||
usb_speed_string(speed));
|
||||
}
|
||||
if (usb_ists & USB_ISTS_DIS2I || usb_ists & USB_ISTS_DISI)
|
||||
ret += sprintf(str + ret, "Disconnection ");
|
||||
if (usb_ists & USB_ISTS_L2ENTI)
|
||||
ret += sprintf(str + ret, "suspended ");
|
||||
if (usb_ists & USB_ISTS_L1ENTI)
|
||||
ret += sprintf(str + ret, "L1 enter ");
|
||||
if (usb_ists & USB_ISTS_L1EXTI)
|
||||
ret += sprintf(str + ret, "L1 exit ");
|
||||
if (usb_ists & USB_ISTS_L2ENTI)
|
||||
ret += sprintf(str + ret, "L2 enter ");
|
||||
if (usb_ists & USB_ISTS_L2EXTI)
|
||||
ret += sprintf(str + ret, "L2 exit ");
|
||||
if (usb_ists & USB_ISTS_U3EXTI)
|
||||
ret += sprintf(str + ret, "U3 exit ");
|
||||
if (usb_ists & USB_ISTS_UWRESI)
|
||||
ret += sprintf(str + ret, "Warm Reset ");
|
||||
if (usb_ists & USB_ISTS_UHRESI)
|
||||
ret += sprintf(str + ret, "Hot Reset ");
|
||||
if (usb_ists & USB_ISTS_U2RESI)
|
||||
ret += sprintf(str + ret, "Reset");
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
static inline char *cdns3_decode_ep_irq(char *str,
|
||||
u32 ep_sts,
|
||||
const char *ep_name)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = sprintf(str, "IRQ for %s: %08x ", ep_name, ep_sts);
|
||||
|
||||
if (ep_sts & EP_STS_SETUP)
|
||||
ret += sprintf(str + ret, "SETUP ");
|
||||
if (ep_sts & EP_STS_IOC)
|
||||
ret += sprintf(str + ret, "IOC ");
|
||||
if (ep_sts & EP_STS_ISP)
|
||||
ret += sprintf(str + ret, "ISP ");
|
||||
if (ep_sts & EP_STS_DESCMIS)
|
||||
ret += sprintf(str + ret, "DESCMIS ");
|
||||
if (ep_sts & EP_STS_STREAMR)
|
||||
ret += sprintf(str + ret, "STREAMR ");
|
||||
if (ep_sts & EP_STS_MD_EXIT)
|
||||
ret += sprintf(str + ret, "MD_EXIT ");
|
||||
if (ep_sts & EP_STS_TRBERR)
|
||||
ret += sprintf(str + ret, "TRBERR ");
|
||||
if (ep_sts & EP_STS_NRDY)
|
||||
ret += sprintf(str + ret, "NRDY ");
|
||||
if (ep_sts & EP_STS_PRIME)
|
||||
ret += sprintf(str + ret, "PRIME ");
|
||||
if (ep_sts & EP_STS_SIDERR)
|
||||
ret += sprintf(str + ret, "SIDERRT ");
|
||||
if (ep_sts & EP_STS_OUTSMM)
|
||||
ret += sprintf(str + ret, "OUTSMM ");
|
||||
if (ep_sts & EP_STS_ISOERR)
|
||||
ret += sprintf(str + ret, "ISOERR ");
|
||||
if (ep_sts & EP_STS_IOT)
|
||||
ret += sprintf(str + ret, "IOT ");
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
static inline char *cdns3_decode_epx_irq(char *str,
|
||||
char *ep_name,
|
||||
u32 ep_sts)
|
||||
{
|
||||
return cdns3_decode_ep_irq(str, ep_sts, ep_name);
|
||||
}
|
||||
|
||||
static inline char *cdns3_decode_ep0_irq(char *str,
|
||||
int dir,
|
||||
u32 ep_sts)
|
||||
{
|
||||
return cdns3_decode_ep_irq(str, ep_sts,
|
||||
dir ? "ep0IN" : "ep0OUT");
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug a transfer ring.
|
||||
*
|
||||
* Prints out all TRBs in the endpoint ring, even those after the Link TRB.
|
||||
*.
|
||||
*/
|
||||
static inline char *cdns3_dbg_ring(struct cdns3_endpoint *priv_ep, char *str)
|
||||
{
|
||||
dma_addr_t addr = priv_ep->trb_pool_dma;
|
||||
struct cdns3_trb *trb;
|
||||
int trb_per_sector;
|
||||
int ret = 0;
|
||||
int i;
|
||||
|
||||
trb_per_sector = GET_TRBS_PER_SEGMENT(priv_ep->type);
|
||||
|
||||
trb = &priv_ep->trb_pool[priv_ep->dequeue];
|
||||
ret += sprintf(str + ret, "\n\t\tRing contents for %s:", priv_ep->name);
|
||||
|
||||
ret += sprintf(str + ret,
|
||||
"\n\t\tRing deq index: %d, trb: %p (virt), 0x%llx (dma)\n",
|
||||
priv_ep->dequeue, trb,
|
||||
(unsigned long long)cdns3_trb_virt_to_dma(priv_ep, trb));
|
||||
|
||||
trb = &priv_ep->trb_pool[priv_ep->enqueue];
|
||||
ret += sprintf(str + ret,
|
||||
"\t\tRing enq index: %d, trb: %p (virt), 0x%llx (dma)\n",
|
||||
priv_ep->enqueue, trb,
|
||||
(unsigned long long)cdns3_trb_virt_to_dma(priv_ep, trb));
|
||||
|
||||
ret += sprintf(str + ret,
|
||||
"\t\tfree trbs: %d, CCS=%d, PCS=%d\n",
|
||||
priv_ep->free_trbs, priv_ep->ccs, priv_ep->pcs);
|
||||
|
||||
if (trb_per_sector > TRBS_PER_SEGMENT) {
|
||||
sprintf(str + ret, "\t\tTransfer ring %d too big\n",
|
||||
trb_per_sector);
|
||||
return str;
|
||||
}
|
||||
|
||||
for (i = 0; i < trb_per_sector; ++i) {
|
||||
trb = &priv_ep->trb_pool[i];
|
||||
ret += sprintf(str + ret,
|
||||
"\t\t@%pad %08x %08x %08x\n", &addr,
|
||||
le32_to_cpu(trb->buffer),
|
||||
le32_to_cpu(trb->length),
|
||||
le32_to_cpu(trb->control));
|
||||
addr += sizeof(*trb);
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
#endif /*__LINUX_CDNS3_DEBUG*/
|
895
drivers/usb/cdns3/cdns3-ep0.c
Normal file
895
drivers/usb/cdns3/cdns3-ep0.c
Normal file
|
@ -0,0 +1,895 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Cadence USBSS DRD Driver - gadget side.
|
||||
*
|
||||
* Copyright (C) 2018 Cadence Design Systems.
|
||||
* Copyright (C) 2017-2018 NXP
|
||||
*
|
||||
* Authors: Pawel Jez <pjez@cadence.com>,
|
||||
* Pawel Laszczak <pawell@cadence.com>
|
||||
* Peter Chen <peter.chen@nxp.com>
|
||||
*/
|
||||
|
||||
#include <linux/usb/composite.h>
|
||||
#include <linux/iopoll.h>
|
||||
|
||||
#include "cdns3-gadget.h"
|
||||
#include "cdns3-trace.h"
|
||||
|
||||
static struct usb_endpoint_descriptor cdns3_gadget_ep0_desc = {
|
||||
.bLength = USB_DT_ENDPOINT_SIZE,
|
||||
.bDescriptorType = USB_DT_ENDPOINT,
|
||||
.bmAttributes = USB_ENDPOINT_XFER_CONTROL,
|
||||
};
|
||||
|
||||
/**
|
||||
* cdns3_ep0_run_transfer - Do transfer on default endpoint hardware
|
||||
* @priv_dev: extended gadget object
|
||||
* @dma_addr: physical address where data is/will be stored
|
||||
* @length: data length
|
||||
* @erdy: set it to 1 when ERDY packet should be sent -
|
||||
* exit from flow control state
|
||||
* @zlp: add zero length packet
|
||||
*/
|
||||
static void cdns3_ep0_run_transfer(struct cdns3_device *priv_dev,
|
||||
dma_addr_t dma_addr,
|
||||
unsigned int length, int erdy, int zlp)
|
||||
{
|
||||
struct cdns3_usb_regs __iomem *regs = priv_dev->regs;
|
||||
struct cdns3_endpoint *priv_ep = priv_dev->eps[0];
|
||||
|
||||
priv_ep->trb_pool[0].buffer = cpu_to_le32(TRB_BUFFER(dma_addr));
|
||||
priv_ep->trb_pool[0].length = cpu_to_le32(TRB_LEN(length));
|
||||
|
||||
if (zlp) {
|
||||
priv_ep->trb_pool[0].control = cpu_to_le32(TRB_CYCLE | TRB_TYPE(TRB_NORMAL));
|
||||
priv_ep->trb_pool[1].buffer = cpu_to_le32(TRB_BUFFER(dma_addr));
|
||||
priv_ep->trb_pool[1].length = cpu_to_le32(TRB_LEN(0));
|
||||
priv_ep->trb_pool[1].control = cpu_to_le32(TRB_CYCLE | TRB_IOC |
|
||||
TRB_TYPE(TRB_NORMAL));
|
||||
} else {
|
||||
priv_ep->trb_pool[0].control = cpu_to_le32(TRB_CYCLE | TRB_IOC |
|
||||
TRB_TYPE(TRB_NORMAL));
|
||||
priv_ep->trb_pool[1].control = 0;
|
||||
}
|
||||
|
||||
trace_cdns3_prepare_trb(priv_ep, priv_ep->trb_pool);
|
||||
|
||||
cdns3_select_ep(priv_dev, priv_dev->ep0_data_dir);
|
||||
|
||||
writel(EP_STS_TRBERR, ®s->ep_sts);
|
||||
writel(EP_TRADDR_TRADDR(priv_ep->trb_pool_dma), ®s->ep_traddr);
|
||||
trace_cdns3_doorbell_ep0(priv_dev->ep0_data_dir ? "ep0in" : "ep0out",
|
||||
readl(®s->ep_traddr));
|
||||
|
||||
/* TRB should be prepared before starting transfer. */
|
||||
writel(EP_CMD_DRDY, ®s->ep_cmd);
|
||||
|
||||
/* Resume controller before arming transfer. */
|
||||
__cdns3_gadget_wakeup(priv_dev);
|
||||
|
||||
if (erdy)
|
||||
writel(EP_CMD_ERDY, &priv_dev->regs->ep_cmd);
|
||||
}
|
||||
|
||||
/**
|
||||
* cdns3_ep0_delegate_req - Returns status of handling setup packet
|
||||
* Setup is handled by gadget driver
|
||||
* @priv_dev: extended gadget object
|
||||
* @ctrl_req: pointer to received setup packet
|
||||
*
|
||||
* Returns zero on success or negative value on failure
|
||||
*/
|
||||
static int cdns3_ep0_delegate_req(struct cdns3_device *priv_dev,
|
||||
struct usb_ctrlrequest *ctrl_req)
|
||||
{
|
||||
int ret;
|
||||
|
||||
spin_unlock(&priv_dev->lock);
|
||||
priv_dev->setup_pending = 1;
|
||||
ret = priv_dev->gadget_driver->setup(&priv_dev->gadget, ctrl_req);
|
||||
priv_dev->setup_pending = 0;
|
||||
spin_lock(&priv_dev->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void cdns3_prepare_setup_packet(struct cdns3_device *priv_dev)
|
||||
{
|
||||
priv_dev->ep0_data_dir = 0;
|
||||
priv_dev->ep0_stage = CDNS3_SETUP_STAGE;
|
||||
cdns3_ep0_run_transfer(priv_dev, priv_dev->setup_dma,
|
||||
sizeof(struct usb_ctrlrequest), 0, 0);
|
||||
}
|
||||
|
||||
static void cdns3_ep0_complete_setup(struct cdns3_device *priv_dev,
|
||||
u8 send_stall, u8 send_erdy)
|
||||
{
|
||||
struct cdns3_endpoint *priv_ep = priv_dev->eps[0];
|
||||
struct usb_request *request;
|
||||
|
||||
request = cdns3_next_request(&priv_ep->pending_req_list);
|
||||
if (request)
|
||||
list_del_init(&request->list);
|
||||
|
||||
if (send_stall) {
|
||||
trace_cdns3_halt(priv_ep, send_stall, 0);
|
||||
/* set_stall on ep0 */
|
||||
cdns3_select_ep(priv_dev, 0x00);
|
||||
writel(EP_CMD_SSTALL, &priv_dev->regs->ep_cmd);
|
||||
} else {
|
||||
cdns3_prepare_setup_packet(priv_dev);
|
||||
}
|
||||
|
||||
priv_dev->ep0_stage = CDNS3_SETUP_STAGE;
|
||||
writel((send_erdy ? EP_CMD_ERDY : 0) | EP_CMD_REQ_CMPL,
|
||||
&priv_dev->regs->ep_cmd);
|
||||
}
|
||||
|
||||
/**
|
||||
* cdns3_req_ep0_set_configuration - Handling of SET_CONFIG standard USB request
|
||||
* @priv_dev: extended gadget object
|
||||
* @ctrl_req: pointer to received setup packet
|
||||
*
|
||||
* Returns 0 if success, USB_GADGET_DELAYED_STATUS on deferred status stage,
|
||||
* error code on error
|
||||
*/
|
||||
static int cdns3_req_ep0_set_configuration(struct cdns3_device *priv_dev,
|
||||
struct usb_ctrlrequest *ctrl_req)
|
||||
{
|
||||
enum usb_device_state device_state = priv_dev->gadget.state;
|
||||
u32 config = le16_to_cpu(ctrl_req->wValue);
|
||||
int result = 0;
|
||||
|
||||
switch (device_state) {
|
||||
case USB_STATE_ADDRESS:
|
||||
result = cdns3_ep0_delegate_req(priv_dev, ctrl_req);
|
||||
|
||||
if (result || !config)
|
||||
goto reset_config;
|
||||
|
||||
break;
|
||||
case USB_STATE_CONFIGURED:
|
||||
result = cdns3_ep0_delegate_req(priv_dev, ctrl_req);
|
||||
if (!config && !result)
|
||||
goto reset_config;
|
||||
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
reset_config:
|
||||
if (result != USB_GADGET_DELAYED_STATUS)
|
||||
cdns3_hw_reset_eps_config(priv_dev);
|
||||
|
||||
usb_gadget_set_state(&priv_dev->gadget,
|
||||
USB_STATE_ADDRESS);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* cdns3_req_ep0_set_address - Handling of SET_ADDRESS standard USB request
|
||||
* @priv_dev: extended gadget object
|
||||
* @ctrl_req: pointer to received setup packet
|
||||
*
|
||||
* Returns 0 if success, error code on error
|
||||
*/
|
||||
static int cdns3_req_ep0_set_address(struct cdns3_device *priv_dev,
|
||||
struct usb_ctrlrequest *ctrl_req)
|
||||
{
|
||||
enum usb_device_state device_state = priv_dev->gadget.state;
|
||||
u32 reg;
|
||||
u32 addr;
|
||||
|
||||
addr = le16_to_cpu(ctrl_req->wValue);
|
||||
|
||||
if (addr > USB_DEVICE_MAX_ADDRESS) {
|
||||
dev_err(priv_dev->dev,
|
||||
"Device address (%d) cannot be greater than %d\n",
|
||||
addr, USB_DEVICE_MAX_ADDRESS);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (device_state == USB_STATE_CONFIGURED) {
|
||||
dev_err(priv_dev->dev,
|
||||
"can't set_address from configured state\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
reg = readl(&priv_dev->regs->usb_cmd);
|
||||
|
||||
writel(reg | USB_CMD_FADDR(addr) | USB_CMD_SET_ADDR,
|
||||
&priv_dev->regs->usb_cmd);
|
||||
|
||||
usb_gadget_set_state(&priv_dev->gadget,
|
||||
(addr ? USB_STATE_ADDRESS : USB_STATE_DEFAULT));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* cdns3_req_ep0_get_status - Handling of GET_STATUS standard USB request
|
||||
* @priv_dev: extended gadget object
|
||||
* @ctrl: pointer to received setup packet
|
||||
*
|
||||
* Returns 0 if success, error code on error
|
||||
*/
|
||||
static int cdns3_req_ep0_get_status(struct cdns3_device *priv_dev,
|
||||
struct usb_ctrlrequest *ctrl)
|
||||
{
|
||||
struct cdns3_endpoint *priv_ep;
|
||||
__le16 *response_pkt;
|
||||
u16 usb_status = 0;
|
||||
u32 recip;
|
||||
u8 index;
|
||||
|
||||
recip = ctrl->bRequestType & USB_RECIP_MASK;
|
||||
|
||||
switch (recip) {
|
||||
case USB_RECIP_DEVICE:
|
||||
/* self powered */
|
||||
if (priv_dev->is_selfpowered)
|
||||
usb_status = BIT(USB_DEVICE_SELF_POWERED);
|
||||
|
||||
if (priv_dev->wake_up_flag)
|
||||
usb_status |= BIT(USB_DEVICE_REMOTE_WAKEUP);
|
||||
|
||||
if (priv_dev->gadget.speed != USB_SPEED_SUPER)
|
||||
break;
|
||||
|
||||
if (priv_dev->u1_allowed)
|
||||
usb_status |= BIT(USB_DEV_STAT_U1_ENABLED);
|
||||
|
||||
if (priv_dev->u2_allowed)
|
||||
usb_status |= BIT(USB_DEV_STAT_U2_ENABLED);
|
||||
|
||||
break;
|
||||
case USB_RECIP_INTERFACE:
|
||||
return cdns3_ep0_delegate_req(priv_dev, ctrl);
|
||||
case USB_RECIP_ENDPOINT:
|
||||
index = cdns3_ep_addr_to_index(le16_to_cpu(ctrl->wIndex));
|
||||
priv_ep = priv_dev->eps[index];
|
||||
|
||||
/* check if endpoint is stalled or stall is pending */
|
||||
cdns3_select_ep(priv_dev, le16_to_cpu(ctrl->wIndex));
|
||||
if (EP_STS_STALL(readl(&priv_dev->regs->ep_sts)) ||
|
||||
(priv_ep->flags & EP_STALL_PENDING))
|
||||
usb_status = BIT(USB_ENDPOINT_HALT);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
response_pkt = (__le16 *)priv_dev->setup_buf;
|
||||
*response_pkt = cpu_to_le16(usb_status);
|
||||
|
||||
cdns3_ep0_run_transfer(priv_dev, priv_dev->setup_dma,
|
||||
sizeof(*response_pkt), 1, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cdns3_ep0_feature_handle_device(struct cdns3_device *priv_dev,
|
||||
struct usb_ctrlrequest *ctrl,
|
||||
int set)
|
||||
{
|
||||
enum usb_device_state state;
|
||||
enum usb_device_speed speed;
|
||||
int ret = 0;
|
||||
u32 wValue;
|
||||
u16 tmode;
|
||||
|
||||
wValue = le16_to_cpu(ctrl->wValue);
|
||||
state = priv_dev->gadget.state;
|
||||
speed = priv_dev->gadget.speed;
|
||||
|
||||
switch (wValue) {
|
||||
case USB_DEVICE_REMOTE_WAKEUP:
|
||||
priv_dev->wake_up_flag = !!set;
|
||||
break;
|
||||
case USB_DEVICE_U1_ENABLE:
|
||||
if (state != USB_STATE_CONFIGURED || speed != USB_SPEED_SUPER)
|
||||
return -EINVAL;
|
||||
|
||||
priv_dev->u1_allowed = !!set;
|
||||
break;
|
||||
case USB_DEVICE_U2_ENABLE:
|
||||
if (state != USB_STATE_CONFIGURED || speed != USB_SPEED_SUPER)
|
||||
return -EINVAL;
|
||||
|
||||
priv_dev->u2_allowed = !!set;
|
||||
break;
|
||||
case USB_DEVICE_LTM_ENABLE:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
case USB_DEVICE_TEST_MODE:
|
||||
if (state != USB_STATE_CONFIGURED || speed > USB_SPEED_HIGH)
|
||||
return -EINVAL;
|
||||
|
||||
tmode = le16_to_cpu(ctrl->wIndex);
|
||||
|
||||
if (!set || (tmode & 0xff) != 0)
|
||||
return -EINVAL;
|
||||
|
||||
tmode >>= 8;
|
||||
switch (tmode) {
|
||||
case USB_TEST_J:
|
||||
case USB_TEST_K:
|
||||
case USB_TEST_SE0_NAK:
|
||||
case USB_TEST_PACKET:
|
||||
cdns3_set_register_bit(&priv_dev->regs->usb_cmd,
|
||||
USB_CMD_STMODE |
|
||||
USB_STS_TMODE_SEL(tmode - 1));
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cdns3_ep0_feature_handle_intf(struct cdns3_device *priv_dev,
|
||||
struct usb_ctrlrequest *ctrl,
|
||||
int set)
|
||||
{
|
||||
u32 wValue;
|
||||
int ret = 0;
|
||||
|
||||
wValue = le16_to_cpu(ctrl->wValue);
|
||||
|
||||
switch (wValue) {
|
||||
case USB_INTRF_FUNC_SUSPEND:
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cdns3_ep0_feature_handle_endpoint(struct cdns3_device *priv_dev,
|
||||
struct usb_ctrlrequest *ctrl,
|
||||
int set)
|
||||
{
|
||||
struct cdns3_endpoint *priv_ep;
|
||||
int ret = 0;
|
||||
u8 index;
|
||||
|
||||
if (le16_to_cpu(ctrl->wValue) != USB_ENDPOINT_HALT)
|
||||
return -EINVAL;
|
||||
|
||||
if (!(le16_to_cpu(ctrl->wIndex) & ~USB_DIR_IN))
|
||||
return 0;
|
||||
|
||||
index = cdns3_ep_addr_to_index(le16_to_cpu(ctrl->wIndex));
|
||||
priv_ep = priv_dev->eps[index];
|
||||
|
||||
cdns3_select_ep(priv_dev, le16_to_cpu(ctrl->wIndex));
|
||||
|
||||
if (set)
|
||||
__cdns3_gadget_ep_set_halt(priv_ep);
|
||||
else if (!(priv_ep->flags & EP_WEDGE))
|
||||
ret = __cdns3_gadget_ep_clear_halt(priv_ep);
|
||||
|
||||
cdns3_select_ep(priv_dev, 0x00);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* cdns3_req_ep0_handle_feature -
|
||||
* Handling of GET/SET_FEATURE standard USB request
|
||||
*
|
||||
* @priv_dev: extended gadget object
|
||||
* @ctrl: pointer to received setup packet
|
||||
* @set: must be set to 1 for SET_FEATURE request
|
||||
*
|
||||
* Returns 0 if success, error code on error
|
||||
*/
|
||||
static int cdns3_req_ep0_handle_feature(struct cdns3_device *priv_dev,
|
||||
struct usb_ctrlrequest *ctrl,
|
||||
int set)
|
||||
{
|
||||
int ret = 0;
|
||||
u32 recip;
|
||||
|
||||
recip = ctrl->bRequestType & USB_RECIP_MASK;
|
||||
|
||||
switch (recip) {
|
||||
case USB_RECIP_DEVICE:
|
||||
ret = cdns3_ep0_feature_handle_device(priv_dev, ctrl, set);
|
||||
break;
|
||||
case USB_RECIP_INTERFACE:
|
||||
ret = cdns3_ep0_feature_handle_intf(priv_dev, ctrl, set);
|
||||
break;
|
||||
case USB_RECIP_ENDPOINT:
|
||||
ret = cdns3_ep0_feature_handle_endpoint(priv_dev, ctrl, set);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* cdns3_req_ep0_set_sel - Handling of SET_SEL standard USB request
|
||||
* @priv_dev: extended gadget object
|
||||
* @ctrl_req: pointer to received setup packet
|
||||
*
|
||||
* Returns 0 if success, error code on error
|
||||
*/
|
||||
static int cdns3_req_ep0_set_sel(struct cdns3_device *priv_dev,
|
||||
struct usb_ctrlrequest *ctrl_req)
|
||||
{
|
||||
if (priv_dev->gadget.state < USB_STATE_ADDRESS)
|
||||
return -EINVAL;
|
||||
|
||||
if (le16_to_cpu(ctrl_req->wLength) != 6) {
|
||||
dev_err(priv_dev->dev, "Set SEL should be 6 bytes, got %d\n",
|
||||
ctrl_req->wLength);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
cdns3_ep0_run_transfer(priv_dev, priv_dev->setup_dma, 6, 1, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* cdns3_req_ep0_set_isoch_delay -
|
||||
* Handling of GET_ISOCH_DELAY standard USB request
|
||||
* @priv_dev: extended gadget object
|
||||
* @ctrl_req: pointer to received setup packet
|
||||
*
|
||||
* Returns 0 if success, error code on error
|
||||
*/
|
||||
static int cdns3_req_ep0_set_isoch_delay(struct cdns3_device *priv_dev,
|
||||
struct usb_ctrlrequest *ctrl_req)
|
||||
{
|
||||
if (ctrl_req->wIndex || ctrl_req->wLength)
|
||||
return -EINVAL;
|
||||
|
||||
priv_dev->isoch_delay = le16_to_cpu(ctrl_req->wValue);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* cdns3_ep0_standard_request - Handling standard USB requests
|
||||
* @priv_dev: extended gadget object
|
||||
* @ctrl_req: pointer to received setup packet
|
||||
*
|
||||
* Returns 0 if success, error code on error
|
||||
*/
|
||||
static int cdns3_ep0_standard_request(struct cdns3_device *priv_dev,
|
||||
struct usb_ctrlrequest *ctrl_req)
|
||||
{
|
||||
int ret;
|
||||
|
||||
switch (ctrl_req->bRequest) {
|
||||
case USB_REQ_SET_ADDRESS:
|
||||
ret = cdns3_req_ep0_set_address(priv_dev, ctrl_req);
|
||||
break;
|
||||
case USB_REQ_SET_CONFIGURATION:
|
||||
ret = cdns3_req_ep0_set_configuration(priv_dev, ctrl_req);
|
||||
break;
|
||||
case USB_REQ_GET_STATUS:
|
||||
ret = cdns3_req_ep0_get_status(priv_dev, ctrl_req);
|
||||
break;
|
||||
case USB_REQ_CLEAR_FEATURE:
|
||||
ret = cdns3_req_ep0_handle_feature(priv_dev, ctrl_req, 0);
|
||||
break;
|
||||
case USB_REQ_SET_FEATURE:
|
||||
ret = cdns3_req_ep0_handle_feature(priv_dev, ctrl_req, 1);
|
||||
break;
|
||||
case USB_REQ_SET_SEL:
|
||||
ret = cdns3_req_ep0_set_sel(priv_dev, ctrl_req);
|
||||
break;
|
||||
case USB_REQ_SET_ISOCH_DELAY:
|
||||
ret = cdns3_req_ep0_set_isoch_delay(priv_dev, ctrl_req);
|
||||
break;
|
||||
default:
|
||||
ret = cdns3_ep0_delegate_req(priv_dev, ctrl_req);
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __pending_setup_status_handler(struct cdns3_device *priv_dev)
|
||||
{
|
||||
struct usb_request *request = priv_dev->pending_status_request;
|
||||
|
||||
if (priv_dev->status_completion_no_call && request &&
|
||||
request->complete) {
|
||||
request->complete(&priv_dev->eps[0]->endpoint, request);
|
||||
priv_dev->status_completion_no_call = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void cdns3_pending_setup_status_handler(struct work_struct *work)
|
||||
{
|
||||
struct cdns3_device *priv_dev = container_of(work, struct cdns3_device,
|
||||
pending_status_wq);
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&priv_dev->lock, flags);
|
||||
__pending_setup_status_handler(priv_dev);
|
||||
spin_unlock_irqrestore(&priv_dev->lock, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* cdns3_ep0_setup_phase - Handling setup USB requests
|
||||
* @priv_dev: extended gadget object
|
||||
*/
|
||||
static void cdns3_ep0_setup_phase(struct cdns3_device *priv_dev)
|
||||
{
|
||||
struct usb_ctrlrequest *ctrl = priv_dev->setup_buf;
|
||||
struct cdns3_endpoint *priv_ep = priv_dev->eps[0];
|
||||
int result;
|
||||
|
||||
priv_dev->ep0_data_dir = ctrl->bRequestType & USB_DIR_IN;
|
||||
|
||||
trace_cdns3_ctrl_req(ctrl);
|
||||
|
||||
if (!list_empty(&priv_ep->pending_req_list)) {
|
||||
struct usb_request *request;
|
||||
|
||||
request = cdns3_next_request(&priv_ep->pending_req_list);
|
||||
priv_ep->dir = priv_dev->ep0_data_dir;
|
||||
cdns3_gadget_giveback(priv_ep, to_cdns3_request(request),
|
||||
-ECONNRESET);
|
||||
}
|
||||
|
||||
if (le16_to_cpu(ctrl->wLength))
|
||||
priv_dev->ep0_stage = CDNS3_DATA_STAGE;
|
||||
else
|
||||
priv_dev->ep0_stage = CDNS3_STATUS_STAGE;
|
||||
|
||||
if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD)
|
||||
result = cdns3_ep0_standard_request(priv_dev, ctrl);
|
||||
else
|
||||
result = cdns3_ep0_delegate_req(priv_dev, ctrl);
|
||||
|
||||
if (result == USB_GADGET_DELAYED_STATUS)
|
||||
return;
|
||||
|
||||
if (result < 0)
|
||||
cdns3_ep0_complete_setup(priv_dev, 1, 1);
|
||||
else if (priv_dev->ep0_stage == CDNS3_STATUS_STAGE)
|
||||
cdns3_ep0_complete_setup(priv_dev, 0, 1);
|
||||
}
|
||||
|
||||
static void cdns3_transfer_completed(struct cdns3_device *priv_dev)
|
||||
{
|
||||
struct cdns3_endpoint *priv_ep = priv_dev->eps[0];
|
||||
|
||||
if (!list_empty(&priv_ep->pending_req_list)) {
|
||||
struct usb_request *request;
|
||||
|
||||
trace_cdns3_complete_trb(priv_ep, priv_ep->trb_pool);
|
||||
request = cdns3_next_request(&priv_ep->pending_req_list);
|
||||
|
||||
request->actual =
|
||||
TRB_LEN(le32_to_cpu(priv_ep->trb_pool->length));
|
||||
|
||||
priv_ep->dir = priv_dev->ep0_data_dir;
|
||||
cdns3_gadget_giveback(priv_ep, to_cdns3_request(request), 0);
|
||||
}
|
||||
|
||||
cdns3_ep0_complete_setup(priv_dev, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* cdns3_check_new_setup - Check if controller receive new SETUP packet.
|
||||
* @priv_dev: extended gadget object
|
||||
*
|
||||
* The SETUP packet can be kept in on-chip memory or in system memory.
|
||||
*/
|
||||
static bool cdns3_check_new_setup(struct cdns3_device *priv_dev)
|
||||
{
|
||||
u32 ep_sts_reg;
|
||||
|
||||
cdns3_select_ep(priv_dev, USB_DIR_OUT);
|
||||
ep_sts_reg = readl(&priv_dev->regs->ep_sts);
|
||||
|
||||
return !!(ep_sts_reg & (EP_STS_SETUP | EP_STS_STPWAIT));
|
||||
}
|
||||
|
||||
/**
|
||||
* cdns3_check_ep0_interrupt_proceed - Processes interrupt related to endpoint 0
|
||||
* @priv_dev: extended gadget object
|
||||
* @dir: USB_DIR_IN for IN direction, USB_DIR_OUT for OUT direction
|
||||
*/
|
||||
void cdns3_check_ep0_interrupt_proceed(struct cdns3_device *priv_dev, int dir)
|
||||
{
|
||||
u32 ep_sts_reg;
|
||||
|
||||
cdns3_select_ep(priv_dev, dir);
|
||||
|
||||
ep_sts_reg = readl(&priv_dev->regs->ep_sts);
|
||||
writel(ep_sts_reg, &priv_dev->regs->ep_sts);
|
||||
|
||||
trace_cdns3_ep0_irq(priv_dev, ep_sts_reg);
|
||||
|
||||
__pending_setup_status_handler(priv_dev);
|
||||
|
||||
if (ep_sts_reg & EP_STS_SETUP)
|
||||
priv_dev->wait_for_setup = 1;
|
||||
|
||||
if (priv_dev->wait_for_setup && ep_sts_reg & EP_STS_IOC) {
|
||||
priv_dev->wait_for_setup = 0;
|
||||
cdns3_ep0_setup_phase(priv_dev);
|
||||
} else if ((ep_sts_reg & EP_STS_IOC) || (ep_sts_reg & EP_STS_ISP)) {
|
||||
priv_dev->ep0_data_dir = dir;
|
||||
cdns3_transfer_completed(priv_dev);
|
||||
}
|
||||
|
||||
if (ep_sts_reg & EP_STS_DESCMIS) {
|
||||
if (dir == 0 && !priv_dev->setup_pending)
|
||||
cdns3_prepare_setup_packet(priv_dev);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* cdns3_gadget_ep0_enable
|
||||
* @ep: pointer to endpoint zero object
|
||||
* @desc: pointer to usb endpoint descriptor
|
||||
*
|
||||
* Function shouldn't be called by gadget driver,
|
||||
* endpoint 0 is allways active
|
||||
*/
|
||||
static int cdns3_gadget_ep0_enable(struct usb_ep *ep,
|
||||
const struct usb_endpoint_descriptor *desc)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* cdns3_gadget_ep0_disable
|
||||
* @ep: pointer to endpoint zero object
|
||||
*
|
||||
* Function shouldn't be called by gadget driver,
|
||||
* endpoint 0 is allways active
|
||||
*/
|
||||
static int cdns3_gadget_ep0_disable(struct usb_ep *ep)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* cdns3_gadget_ep0_set_halt
|
||||
* @ep: pointer to endpoint zero object
|
||||
* @value: 1 for set stall, 0 for clear stall
|
||||
*
|
||||
* Returns 0
|
||||
*/
|
||||
static int cdns3_gadget_ep0_set_halt(struct usb_ep *ep, int value)
|
||||
{
|
||||
/* TODO */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* cdns3_gadget_ep0_queue - Transfer data on endpoint zero
|
||||
* @ep: pointer to endpoint zero object
|
||||
* @request: pointer to request object
|
||||
* @gfp_flags: gfp flags
|
||||
*
|
||||
* Returns 0 on success, error code elsewhere
|
||||
*/
|
||||
static int cdns3_gadget_ep0_queue(struct usb_ep *ep,
|
||||
struct usb_request *request,
|
||||
gfp_t gfp_flags)
|
||||
{
|
||||
struct cdns3_endpoint *priv_ep = ep_to_cdns3_ep(ep);
|
||||
struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
|
||||
unsigned long flags;
|
||||
int ret = 0;
|
||||
u8 zlp = 0;
|
||||
int i;
|
||||
|
||||
spin_lock_irqsave(&priv_dev->lock, flags);
|
||||
trace_cdns3_ep0_queue(priv_dev, request);
|
||||
|
||||
/* cancel the request if controller receive new SETUP packet. */
|
||||
if (cdns3_check_new_setup(priv_dev)) {
|
||||
spin_unlock_irqrestore(&priv_dev->lock, flags);
|
||||
return -ECONNRESET;
|
||||
}
|
||||
|
||||
/* send STATUS stage. Should be called only for SET_CONFIGURATION */
|
||||
if (priv_dev->ep0_stage == CDNS3_STATUS_STAGE) {
|
||||
u32 val;
|
||||
|
||||
cdns3_select_ep(priv_dev, 0x00);
|
||||
|
||||
/*
|
||||
* Configure all non-control EPs which are not enabled by class driver
|
||||
*/
|
||||
for (i = 0; i < CDNS3_ENDPOINTS_MAX_COUNT; i++) {
|
||||
priv_ep = priv_dev->eps[i];
|
||||
if (priv_ep && priv_ep->flags & EP_CLAIMED &&
|
||||
!(priv_ep->flags & EP_ENABLED))
|
||||
cdns3_ep_config(priv_ep, 0);
|
||||
}
|
||||
|
||||
cdns3_set_hw_configuration(priv_dev);
|
||||
cdns3_ep0_complete_setup(priv_dev, 0, 1);
|
||||
/* wait until configuration set */
|
||||
ret = readl_poll_timeout_atomic(&priv_dev->regs->usb_sts, val,
|
||||
val & USB_STS_CFGSTS_MASK, 1, 100);
|
||||
if (ret == -ETIMEDOUT)
|
||||
dev_warn(priv_dev->dev, "timeout for waiting configuration set\n");
|
||||
|
||||
request->actual = 0;
|
||||
priv_dev->status_completion_no_call = true;
|
||||
priv_dev->pending_status_request = request;
|
||||
usb_gadget_set_state(&priv_dev->gadget, USB_STATE_CONFIGURED);
|
||||
spin_unlock_irqrestore(&priv_dev->lock, flags);
|
||||
|
||||
/*
|
||||
* Since there is no completion interrupt for status stage,
|
||||
* it needs to call ->completion in software after
|
||||
* ep0_queue is back.
|
||||
*/
|
||||
queue_work(system_freezable_wq, &priv_dev->pending_status_wq);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!list_empty(&priv_ep->pending_req_list)) {
|
||||
dev_err(priv_dev->dev,
|
||||
"can't handle multiple requests for ep0\n");
|
||||
spin_unlock_irqrestore(&priv_dev->lock, flags);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
ret = usb_gadget_map_request_by_dev(priv_dev->sysdev, request,
|
||||
priv_dev->ep0_data_dir);
|
||||
if (ret) {
|
||||
spin_unlock_irqrestore(&priv_dev->lock, flags);
|
||||
dev_err(priv_dev->dev, "failed to map request\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
request->status = -EINPROGRESS;
|
||||
list_add_tail(&request->list, &priv_ep->pending_req_list);
|
||||
|
||||
if (request->zero && request->length &&
|
||||
(request->length % ep->maxpacket == 0))
|
||||
zlp = 1;
|
||||
|
||||
cdns3_ep0_run_transfer(priv_dev, request->dma, request->length, 1, zlp);
|
||||
|
||||
spin_unlock_irqrestore(&priv_dev->lock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* cdns3_gadget_ep_set_wedge - Set wedge on selected endpoint
|
||||
* @ep: endpoint object
|
||||
*
|
||||
* Returns 0
|
||||
*/
|
||||
int cdns3_gadget_ep_set_wedge(struct usb_ep *ep)
|
||||
{
|
||||
struct cdns3_endpoint *priv_ep = ep_to_cdns3_ep(ep);
|
||||
struct cdns3_device *priv_dev = priv_ep->cdns3_dev;
|
||||
|
||||
dev_dbg(priv_dev->dev, "Wedge for %s\n", ep->name);
|
||||
cdns3_gadget_ep_set_halt(ep, 1);
|
||||
priv_ep->flags |= EP_WEDGE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct usb_ep_ops cdns3_gadget_ep0_ops = {
|
||||
.enable = cdns3_gadget_ep0_enable,
|
||||
.disable = cdns3_gadget_ep0_disable,
|
||||
.alloc_request = cdns3_gadget_ep_alloc_request,
|
||||
.free_request = cdns3_gadget_ep_free_request,
|
||||
.queue = cdns3_gadget_ep0_queue,
|
||||
.dequeue = cdns3_gadget_ep_dequeue,
|
||||
.set_halt = cdns3_gadget_ep0_set_halt,
|
||||
.set_wedge = cdns3_gadget_ep_set_wedge,
|
||||
};
|
||||
|
||||
/**
|
||||
* cdns3_ep0_config - Configures default endpoint
|
||||
* @priv_dev: extended gadget object
|
||||
*
|
||||
* Functions sets parameters: maximal packet size and enables interrupts
|
||||
*/
|
||||
void cdns3_ep0_config(struct cdns3_device *priv_dev)
|
||||
{
|
||||
struct cdns3_usb_regs __iomem *regs;
|
||||
struct cdns3_endpoint *priv_ep;
|
||||
u32 max_packet_size = 64;
|
||||
u32 ep_cfg;
|
||||
|
||||
regs = priv_dev->regs;
|
||||
|
||||
if (priv_dev->gadget.speed == USB_SPEED_SUPER)
|
||||
max_packet_size = 512;
|
||||
|
||||
priv_ep = priv_dev->eps[0];
|
||||
|
||||
if (!list_empty(&priv_ep->pending_req_list)) {
|
||||
struct usb_request *request;
|
||||
|
||||
request = cdns3_next_request(&priv_ep->pending_req_list);
|
||||
list_del_init(&request->list);
|
||||
}
|
||||
|
||||
priv_dev->u1_allowed = 0;
|
||||
priv_dev->u2_allowed = 0;
|
||||
|
||||
priv_dev->gadget.ep0->maxpacket = max_packet_size;
|
||||
cdns3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(max_packet_size);
|
||||
|
||||
/* init ep out */
|
||||
cdns3_select_ep(priv_dev, USB_DIR_OUT);
|
||||
|
||||
if (priv_dev->dev_ver >= DEV_VER_V3) {
|
||||
cdns3_set_register_bit(&priv_dev->regs->dtrans,
|
||||
BIT(0) | BIT(16));
|
||||
cdns3_set_register_bit(&priv_dev->regs->tdl_from_trb,
|
||||
BIT(0) | BIT(16));
|
||||
}
|
||||
|
||||
ep_cfg = EP_CFG_ENABLE | EP_CFG_MAXPKTSIZE(max_packet_size);
|
||||
|
||||
if (!(priv_ep->flags & EP_CONFIGURED))
|
||||
writel(ep_cfg, ®s->ep_cfg);
|
||||
|
||||
writel(EP_STS_EN_SETUPEN | EP_STS_EN_DESCMISEN | EP_STS_EN_TRBERREN,
|
||||
®s->ep_sts_en);
|
||||
|
||||
/* init ep in */
|
||||
cdns3_select_ep(priv_dev, USB_DIR_IN);
|
||||
|
||||
if (!(priv_ep->flags & EP_CONFIGURED))
|
||||
writel(ep_cfg, ®s->ep_cfg);
|
||||
|
||||
priv_ep->flags |= EP_CONFIGURED;
|
||||
|
||||
writel(EP_STS_EN_SETUPEN | EP_STS_EN_TRBERREN, ®s->ep_sts_en);
|
||||
|
||||
cdns3_set_register_bit(®s->usb_conf, USB_CONF_U1DS | USB_CONF_U2DS);
|
||||
}
|
||||
|
||||
/**
|
||||
* cdns3_init_ep0 - Initializes software endpoint 0 of gadget
|
||||
* @priv_dev: extended gadget object
|
||||
* @priv_ep: extended endpoint object
|
||||
*
|
||||
* Returns 0 on success else error code.
|
||||
*/
|
||||
int cdns3_init_ep0(struct cdns3_device *priv_dev,
|
||||
struct cdns3_endpoint *priv_ep)
|
||||
{
|
||||
sprintf(priv_ep->name, "ep0");
|
||||
|
||||
/* fill linux fields */
|
||||
priv_ep->endpoint.ops = &cdns3_gadget_ep0_ops;
|
||||
priv_ep->endpoint.maxburst = 1;
|
||||
usb_ep_set_maxpacket_limit(&priv_ep->endpoint,
|
||||
CDNS3_EP0_MAX_PACKET_LIMIT);
|
||||
priv_ep->endpoint.address = 0;
|
||||
priv_ep->endpoint.caps.type_control = 1;
|
||||
priv_ep->endpoint.caps.dir_in = 1;
|
||||
priv_ep->endpoint.caps.dir_out = 1;
|
||||
priv_ep->endpoint.name = priv_ep->name;
|
||||
priv_ep->endpoint.desc = &cdns3_gadget_ep0_desc;
|
||||
priv_dev->gadget.ep0 = &priv_ep->endpoint;
|
||||
priv_ep->type = USB_ENDPOINT_XFER_CONTROL;
|
||||
|
||||
return cdns3_allocate_trb_pool(priv_ep);
|
||||
}
|
3511
drivers/usb/cdns3/cdns3-gadget.c
Normal file
3511
drivers/usb/cdns3/cdns3-gadget.c
Normal file
File diff suppressed because it is too large
Load diff
1377
drivers/usb/cdns3/cdns3-gadget.h
Normal file
1377
drivers/usb/cdns3/cdns3-gadget.h
Normal file
File diff suppressed because it is too large
Load diff
437
drivers/usb/cdns3/cdns3-imx.c
Normal file
437
drivers/usb/cdns3/cdns3-imx.c
Normal file
|
@ -0,0 +1,437 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* cdns3-imx.c - NXP i.MX specific Glue layer for Cadence USB Controller
|
||||
*
|
||||
* Copyright (C) 2019 NXP
|
||||
*/
|
||||
|
||||
#include <linux/bits.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include "core.h"
|
||||
|
||||
#define USB3_CORE_CTRL1 0x00
|
||||
#define USB3_CORE_CTRL2 0x04
|
||||
#define USB3_INT_REG 0x08
|
||||
#define USB3_CORE_STATUS 0x0c
|
||||
#define XHCI_DEBUG_LINK_ST 0x10
|
||||
#define XHCI_DEBUG_BUS 0x14
|
||||
#define USB3_SSPHY_CTRL1 0x40
|
||||
#define USB3_SSPHY_CTRL2 0x44
|
||||
#define USB3_SSPHY_STATUS 0x4c
|
||||
#define USB2_PHY_CTRL1 0x50
|
||||
#define USB2_PHY_CTRL2 0x54
|
||||
#define USB2_PHY_STATUS 0x5c
|
||||
|
||||
/* Register bits definition */
|
||||
|
||||
/* USB3_CORE_CTRL1 */
|
||||
#define SW_RESET_MASK GENMASK(31, 26)
|
||||
#define PWR_SW_RESET BIT(31)
|
||||
#define APB_SW_RESET BIT(30)
|
||||
#define AXI_SW_RESET BIT(29)
|
||||
#define RW_SW_RESET BIT(28)
|
||||
#define PHY_SW_RESET BIT(27)
|
||||
#define PHYAHB_SW_RESET BIT(26)
|
||||
#define ALL_SW_RESET (PWR_SW_RESET | APB_SW_RESET | AXI_SW_RESET | \
|
||||
RW_SW_RESET | PHY_SW_RESET | PHYAHB_SW_RESET)
|
||||
#define OC_DISABLE BIT(9)
|
||||
#define MDCTRL_CLK_SEL BIT(7)
|
||||
#define MODE_STRAP_MASK (0x7)
|
||||
#define DEV_MODE (1 << 2)
|
||||
#define HOST_MODE (1 << 1)
|
||||
#define OTG_MODE (1 << 0)
|
||||
|
||||
/* USB3_INT_REG */
|
||||
#define CLK_125_REQ BIT(29)
|
||||
#define LPM_CLK_REQ BIT(28)
|
||||
#define DEVU3_WAEKUP_EN BIT(14)
|
||||
#define OTG_WAKEUP_EN BIT(12)
|
||||
#define DEV_INT_EN (3 << 8) /* DEV INT b9:8 */
|
||||
#define HOST_INT1_EN (1 << 0) /* HOST INT b7:0 */
|
||||
|
||||
/* USB3_CORE_STATUS */
|
||||
#define MDCTRL_CLK_STATUS BIT(15)
|
||||
#define DEV_POWER_ON_READY BIT(13)
|
||||
#define HOST_POWER_ON_READY BIT(12)
|
||||
|
||||
/* USB3_SSPHY_STATUS */
|
||||
#define CLK_VALID_MASK (0x3f << 26)
|
||||
#define CLK_VALID_COMPARE_BITS (0xf << 28)
|
||||
#define PHY_REFCLK_REQ (1 << 0)
|
||||
|
||||
/* OTG registers definition */
|
||||
#define OTGSTS 0x4
|
||||
/* OTGSTS */
|
||||
#define OTG_NRDY BIT(11)
|
||||
|
||||
/* xHCI registers definition */
|
||||
#define XECP_PM_PMCSR 0x8018
|
||||
#define XECP_AUX_CTRL_REG1 0x8120
|
||||
|
||||
/* Register bits definition */
|
||||
/* XECP_AUX_CTRL_REG1 */
|
||||
#define CFG_RXDET_P3_EN BIT(15)
|
||||
|
||||
/* XECP_PM_PMCSR */
|
||||
#define PS_MASK GENMASK(1, 0)
|
||||
#define PS_D0 0
|
||||
#define PS_D1 1
|
||||
|
||||
struct cdns_imx {
|
||||
struct device *dev;
|
||||
void __iomem *noncore;
|
||||
struct clk_bulk_data *clks;
|
||||
int num_clks;
|
||||
struct platform_device *cdns3_pdev;
|
||||
};
|
||||
|
||||
static inline u32 cdns_imx_readl(struct cdns_imx *data, u32 offset)
|
||||
{
|
||||
return readl(data->noncore + offset);
|
||||
}
|
||||
|
||||
static inline void cdns_imx_writel(struct cdns_imx *data, u32 offset, u32 value)
|
||||
{
|
||||
writel(value, data->noncore + offset);
|
||||
}
|
||||
|
||||
static const struct clk_bulk_data imx_cdns3_core_clks[] = {
|
||||
{ .id = "lpm" },
|
||||
{ .id = "bus" },
|
||||
{ .id = "aclk" },
|
||||
{ .id = "ipg" },
|
||||
{ .id = "core" },
|
||||
};
|
||||
|
||||
static int cdns_imx_noncore_init(struct cdns_imx *data)
|
||||
{
|
||||
u32 value;
|
||||
int ret;
|
||||
struct device *dev = data->dev;
|
||||
|
||||
cdns_imx_writel(data, USB3_SSPHY_STATUS, CLK_VALID_MASK);
|
||||
udelay(1);
|
||||
ret = readl_poll_timeout(data->noncore + USB3_SSPHY_STATUS, value,
|
||||
(value & CLK_VALID_COMPARE_BITS) == CLK_VALID_COMPARE_BITS,
|
||||
10, 100000);
|
||||
if (ret) {
|
||||
dev_err(dev, "wait clkvld timeout\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
value = cdns_imx_readl(data, USB3_CORE_CTRL1);
|
||||
value |= ALL_SW_RESET;
|
||||
cdns_imx_writel(data, USB3_CORE_CTRL1, value);
|
||||
udelay(1);
|
||||
|
||||
value = cdns_imx_readl(data, USB3_CORE_CTRL1);
|
||||
value = (value & ~MODE_STRAP_MASK) | OTG_MODE | OC_DISABLE;
|
||||
cdns_imx_writel(data, USB3_CORE_CTRL1, value);
|
||||
|
||||
value = cdns_imx_readl(data, USB3_INT_REG);
|
||||
value |= HOST_INT1_EN | DEV_INT_EN;
|
||||
cdns_imx_writel(data, USB3_INT_REG, value);
|
||||
|
||||
value = cdns_imx_readl(data, USB3_CORE_CTRL1);
|
||||
value &= ~ALL_SW_RESET;
|
||||
cdns_imx_writel(data, USB3_CORE_CTRL1, value);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cdns_imx_platform_suspend(struct device *dev,
|
||||
bool suspend, bool wakeup);
|
||||
static struct cdns3_platform_data cdns_imx_pdata = {
|
||||
.platform_suspend = cdns_imx_platform_suspend,
|
||||
.quirks = CDNS3_DEFAULT_PM_RUNTIME_ALLOW,
|
||||
};
|
||||
|
||||
static const struct of_dev_auxdata cdns_imx_auxdata[] = {
|
||||
{
|
||||
.compatible = "cdns,usb3",
|
||||
.platform_data = &cdns_imx_pdata,
|
||||
},
|
||||
{},
|
||||
};
|
||||
|
||||
static int cdns_imx_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct device_node *node = dev->of_node;
|
||||
struct cdns_imx *data;
|
||||
int ret;
|
||||
|
||||
if (!node)
|
||||
return -ENODEV;
|
||||
|
||||
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(pdev, data);
|
||||
data->dev = dev;
|
||||
data->noncore = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(data->noncore)) {
|
||||
dev_err(dev, "can't map IOMEM resource\n");
|
||||
return PTR_ERR(data->noncore);
|
||||
}
|
||||
|
||||
data->num_clks = ARRAY_SIZE(imx_cdns3_core_clks);
|
||||
data->clks = devm_kmemdup(dev, imx_cdns3_core_clks,
|
||||
sizeof(imx_cdns3_core_clks), GFP_KERNEL);
|
||||
if (!data->clks)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = devm_clk_bulk_get(dev, data->num_clks, data->clks);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = clk_bulk_prepare_enable(data->num_clks, data->clks);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = cdns_imx_noncore_init(data);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
ret = of_platform_populate(node, NULL, cdns_imx_auxdata, dev);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to create children: %d\n", ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
device_set_wakeup_capable(dev, true);
|
||||
pm_runtime_set_active(dev);
|
||||
pm_runtime_enable(dev);
|
||||
|
||||
return ret;
|
||||
err:
|
||||
clk_bulk_disable_unprepare(data->num_clks, data->clks);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void cdns_imx_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct cdns_imx *data = dev_get_drvdata(dev);
|
||||
|
||||
pm_runtime_get_sync(dev);
|
||||
of_platform_depopulate(dev);
|
||||
clk_bulk_disable_unprepare(data->num_clks, data->clks);
|
||||
pm_runtime_disable(dev);
|
||||
pm_runtime_put_noidle(dev);
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static void cdns3_set_wakeup(struct cdns_imx *data, bool enable)
|
||||
{
|
||||
u32 value;
|
||||
|
||||
value = cdns_imx_readl(data, USB3_INT_REG);
|
||||
if (enable)
|
||||
value |= OTG_WAKEUP_EN | DEVU3_WAEKUP_EN;
|
||||
else
|
||||
value &= ~(OTG_WAKEUP_EN | DEVU3_WAEKUP_EN);
|
||||
|
||||
cdns_imx_writel(data, USB3_INT_REG, value);
|
||||
}
|
||||
|
||||
static int cdns_imx_platform_suspend(struct device *dev,
|
||||
bool suspend, bool wakeup)
|
||||
{
|
||||
struct cdns *cdns = dev_get_drvdata(dev);
|
||||
struct device *parent = dev->parent;
|
||||
struct cdns_imx *data = dev_get_drvdata(parent);
|
||||
void __iomem *otg_regs = (void __iomem *)(cdns->otg_regs);
|
||||
void __iomem *xhci_regs = cdns->xhci_regs;
|
||||
u32 value;
|
||||
int ret = 0;
|
||||
|
||||
if (cdns->role != USB_ROLE_HOST)
|
||||
return 0;
|
||||
|
||||
if (suspend) {
|
||||
/* SW request low power when all usb ports allow to it ??? */
|
||||
value = readl(xhci_regs + XECP_PM_PMCSR);
|
||||
value &= ~PS_MASK;
|
||||
value |= PS_D1;
|
||||
writel(value, xhci_regs + XECP_PM_PMCSR);
|
||||
|
||||
/* mdctrl_clk_sel */
|
||||
value = cdns_imx_readl(data, USB3_CORE_CTRL1);
|
||||
value |= MDCTRL_CLK_SEL;
|
||||
cdns_imx_writel(data, USB3_CORE_CTRL1, value);
|
||||
|
||||
/* wait for mdctrl_clk_status */
|
||||
value = cdns_imx_readl(data, USB3_CORE_STATUS);
|
||||
ret = readl_poll_timeout(data->noncore + USB3_CORE_STATUS, value,
|
||||
(value & MDCTRL_CLK_STATUS) == MDCTRL_CLK_STATUS,
|
||||
10, 100000);
|
||||
if (ret)
|
||||
dev_warn(parent, "wait mdctrl_clk_status timeout\n");
|
||||
|
||||
/* wait lpm_clk_req to be 0 */
|
||||
value = cdns_imx_readl(data, USB3_INT_REG);
|
||||
ret = readl_poll_timeout(data->noncore + USB3_INT_REG, value,
|
||||
(value & LPM_CLK_REQ) != LPM_CLK_REQ,
|
||||
10, 100000);
|
||||
if (ret)
|
||||
dev_warn(parent, "wait lpm_clk_req timeout\n");
|
||||
|
||||
/* wait phy_refclk_req to be 0 */
|
||||
value = cdns_imx_readl(data, USB3_SSPHY_STATUS);
|
||||
ret = readl_poll_timeout(data->noncore + USB3_SSPHY_STATUS, value,
|
||||
(value & PHY_REFCLK_REQ) != PHY_REFCLK_REQ,
|
||||
10, 100000);
|
||||
if (ret)
|
||||
dev_warn(parent, "wait phy_refclk_req timeout\n");
|
||||
|
||||
cdns3_set_wakeup(data, wakeup);
|
||||
} else {
|
||||
cdns3_set_wakeup(data, false);
|
||||
|
||||
/* SW request D0 */
|
||||
value = readl(xhci_regs + XECP_PM_PMCSR);
|
||||
value &= ~PS_MASK;
|
||||
value |= PS_D0;
|
||||
writel(value, xhci_regs + XECP_PM_PMCSR);
|
||||
|
||||
/* clr CFG_RXDET_P3_EN */
|
||||
value = readl(xhci_regs + XECP_AUX_CTRL_REG1);
|
||||
value &= ~CFG_RXDET_P3_EN;
|
||||
writel(value, xhci_regs + XECP_AUX_CTRL_REG1);
|
||||
|
||||
/* clear mdctrl_clk_sel */
|
||||
value = cdns_imx_readl(data, USB3_CORE_CTRL1);
|
||||
value &= ~MDCTRL_CLK_SEL;
|
||||
cdns_imx_writel(data, USB3_CORE_CTRL1, value);
|
||||
|
||||
/* wait CLK_125_REQ to be 1 */
|
||||
value = cdns_imx_readl(data, USB3_INT_REG);
|
||||
ret = readl_poll_timeout(data->noncore + USB3_INT_REG, value,
|
||||
(value & CLK_125_REQ) == CLK_125_REQ,
|
||||
10, 100000);
|
||||
if (ret)
|
||||
dev_warn(parent, "wait CLK_125_REQ timeout\n");
|
||||
|
||||
/* wait for mdctrl_clk_status is cleared */
|
||||
value = cdns_imx_readl(data, USB3_CORE_STATUS);
|
||||
ret = readl_poll_timeout(data->noncore + USB3_CORE_STATUS, value,
|
||||
(value & MDCTRL_CLK_STATUS) != MDCTRL_CLK_STATUS,
|
||||
10, 100000);
|
||||
if (ret)
|
||||
dev_warn(parent, "wait mdctrl_clk_status cleared timeout\n");
|
||||
|
||||
/* Wait until OTG_NRDY is 0 */
|
||||
value = readl(otg_regs + OTGSTS);
|
||||
ret = readl_poll_timeout(otg_regs + OTGSTS, value,
|
||||
(value & OTG_NRDY) != OTG_NRDY,
|
||||
10, 100000);
|
||||
if (ret)
|
||||
dev_warn(parent, "wait OTG ready timeout\n");
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
static int cdns_imx_resume(struct device *dev)
|
||||
{
|
||||
struct cdns_imx *data = dev_get_drvdata(dev);
|
||||
|
||||
return clk_bulk_prepare_enable(data->num_clks, data->clks);
|
||||
}
|
||||
|
||||
static int cdns_imx_suspend(struct device *dev)
|
||||
{
|
||||
struct cdns_imx *data = dev_get_drvdata(dev);
|
||||
|
||||
clk_bulk_disable_unprepare(data->num_clks, data->clks);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Indicate if the controller was power lost before */
|
||||
static inline bool cdns_imx_is_power_lost(struct cdns_imx *data)
|
||||
{
|
||||
u32 value;
|
||||
|
||||
value = cdns_imx_readl(data, USB3_CORE_CTRL1);
|
||||
if ((value & SW_RESET_MASK) == ALL_SW_RESET)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
static int __maybe_unused cdns_imx_system_suspend(struct device *dev)
|
||||
{
|
||||
pm_runtime_put_sync(dev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused cdns_imx_system_resume(struct device *dev)
|
||||
{
|
||||
struct cdns_imx *data = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
|
||||
ret = pm_runtime_resume_and_get(dev);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Could not get runtime PM.\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (cdns_imx_is_power_lost(data)) {
|
||||
dev_dbg(dev, "resume from power lost\n");
|
||||
ret = cdns_imx_noncore_init(data);
|
||||
if (ret)
|
||||
cdns_imx_suspend(dev);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#else
|
||||
static int cdns_imx_platform_suspend(struct device *dev,
|
||||
bool suspend, bool wakeup)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* CONFIG_PM */
|
||||
|
||||
static const struct dev_pm_ops cdns_imx_pm_ops = {
|
||||
SET_RUNTIME_PM_OPS(cdns_imx_suspend, cdns_imx_resume, NULL)
|
||||
SET_SYSTEM_SLEEP_PM_OPS(cdns_imx_system_suspend, cdns_imx_system_resume)
|
||||
};
|
||||
|
||||
static const struct of_device_id cdns_imx_of_match[] = {
|
||||
{ .compatible = "fsl,imx8qm-usb3", },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, cdns_imx_of_match);
|
||||
|
||||
static struct platform_driver cdns_imx_driver = {
|
||||
.probe = cdns_imx_probe,
|
||||
.remove_new = cdns_imx_remove,
|
||||
.driver = {
|
||||
.name = "cdns3-imx",
|
||||
.of_match_table = cdns_imx_of_match,
|
||||
.pm = &cdns_imx_pm_ops,
|
||||
},
|
||||
};
|
||||
module_platform_driver(cdns_imx_driver);
|
||||
|
||||
MODULE_ALIAS("platform:cdns3-imx");
|
||||
MODULE_AUTHOR("Peter Chen <peter.chen@nxp.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("Cadence USB3 i.MX Glue Layer");
|
208
drivers/usb/cdns3/cdns3-pci-wrap.c
Normal file
208
drivers/usb/cdns3/cdns3-pci-wrap.c
Normal file
|
@ -0,0 +1,208 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Cadence USBSS PCI Glue driver
|
||||
*
|
||||
* Copyright (C) 2018-2019 Cadence.
|
||||
*
|
||||
* Author: Pawel Laszczak <pawell@cadence.com>
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
struct cdns3_wrap {
|
||||
struct platform_device *plat_dev;
|
||||
struct resource dev_res[6];
|
||||
int devfn;
|
||||
};
|
||||
|
||||
#define RES_IRQ_HOST_ID 0
|
||||
#define RES_IRQ_PERIPHERAL_ID 1
|
||||
#define RES_IRQ_OTG_ID 2
|
||||
#define RES_HOST_ID 3
|
||||
#define RES_DEV_ID 4
|
||||
#define RES_DRD_ID 5
|
||||
|
||||
#define PCI_BAR_HOST 0
|
||||
#define PCI_BAR_DEV 2
|
||||
#define PCI_BAR_OTG 0
|
||||
|
||||
#define PCI_DEV_FN_HOST_DEVICE 0
|
||||
#define PCI_DEV_FN_OTG 1
|
||||
|
||||
#define PCI_DRIVER_NAME "cdns3-pci-usbss"
|
||||
#define PLAT_DRIVER_NAME "cdns-usb3"
|
||||
|
||||
#define PCI_DEVICE_ID_CDNS_USB3 0x0100
|
||||
|
||||
static struct pci_dev *cdns3_get_second_fun(struct pci_dev *pdev)
|
||||
{
|
||||
struct pci_dev *func;
|
||||
|
||||
/*
|
||||
* Gets the second function.
|
||||
* It's little tricky, but this platform has two function.
|
||||
* The fist keeps resources for Host/Device while the second
|
||||
* keeps resources for DRD/OTG.
|
||||
*/
|
||||
func = pci_get_device(pdev->vendor, pdev->device, NULL);
|
||||
if (unlikely(!func))
|
||||
return NULL;
|
||||
|
||||
if (func->devfn == pdev->devfn) {
|
||||
func = pci_get_device(pdev->vendor, pdev->device, func);
|
||||
if (unlikely(!func))
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (func->devfn != PCI_DEV_FN_HOST_DEVICE &&
|
||||
func->devfn != PCI_DEV_FN_OTG) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return func;
|
||||
}
|
||||
|
||||
static int cdns3_pci_probe(struct pci_dev *pdev,
|
||||
const struct pci_device_id *id)
|
||||
{
|
||||
struct platform_device_info plat_info;
|
||||
struct cdns3_wrap *wrap;
|
||||
struct resource *res;
|
||||
struct pci_dev *func;
|
||||
int err;
|
||||
|
||||
/*
|
||||
* for GADGET/HOST PCI (devfn) function number is 0,
|
||||
* for OTG PCI (devfn) function number is 1
|
||||
*/
|
||||
if (!id || (pdev->devfn != PCI_DEV_FN_HOST_DEVICE &&
|
||||
pdev->devfn != PCI_DEV_FN_OTG))
|
||||
return -EINVAL;
|
||||
|
||||
func = cdns3_get_second_fun(pdev);
|
||||
if (unlikely(!func))
|
||||
return -EINVAL;
|
||||
|
||||
err = pcim_enable_device(pdev);
|
||||
if (err) {
|
||||
dev_err(&pdev->dev, "Enabling PCI device has failed %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
pci_set_master(pdev);
|
||||
|
||||
if (pci_is_enabled(func)) {
|
||||
wrap = pci_get_drvdata(func);
|
||||
} else {
|
||||
wrap = kzalloc(sizeof(*wrap), GFP_KERNEL);
|
||||
if (!wrap) {
|
||||
pci_disable_device(pdev);
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
|
||||
res = wrap->dev_res;
|
||||
|
||||
if (pdev->devfn == PCI_DEV_FN_HOST_DEVICE) {
|
||||
/* function 0: host(BAR_0) + device(BAR_1).*/
|
||||
dev_dbg(&pdev->dev, "Initialize Device resources\n");
|
||||
res[RES_DEV_ID].start = pci_resource_start(pdev, PCI_BAR_DEV);
|
||||
res[RES_DEV_ID].end = pci_resource_end(pdev, PCI_BAR_DEV);
|
||||
res[RES_DEV_ID].name = "dev";
|
||||
res[RES_DEV_ID].flags = IORESOURCE_MEM;
|
||||
dev_dbg(&pdev->dev, "USBSS-DEV physical base addr: %pa\n",
|
||||
&res[RES_DEV_ID].start);
|
||||
|
||||
res[RES_HOST_ID].start = pci_resource_start(pdev, PCI_BAR_HOST);
|
||||
res[RES_HOST_ID].end = pci_resource_end(pdev, PCI_BAR_HOST);
|
||||
res[RES_HOST_ID].name = "xhci";
|
||||
res[RES_HOST_ID].flags = IORESOURCE_MEM;
|
||||
dev_dbg(&pdev->dev, "USBSS-XHCI physical base addr: %pa\n",
|
||||
&res[RES_HOST_ID].start);
|
||||
|
||||
/* Interrupt for XHCI */
|
||||
wrap->dev_res[RES_IRQ_HOST_ID].start = pdev->irq;
|
||||
wrap->dev_res[RES_IRQ_HOST_ID].name = "host";
|
||||
wrap->dev_res[RES_IRQ_HOST_ID].flags = IORESOURCE_IRQ;
|
||||
|
||||
/* Interrupt device. It's the same as for HOST. */
|
||||
wrap->dev_res[RES_IRQ_PERIPHERAL_ID].start = pdev->irq;
|
||||
wrap->dev_res[RES_IRQ_PERIPHERAL_ID].name = "peripheral";
|
||||
wrap->dev_res[RES_IRQ_PERIPHERAL_ID].flags = IORESOURCE_IRQ;
|
||||
} else {
|
||||
res[RES_DRD_ID].start = pci_resource_start(pdev, PCI_BAR_OTG);
|
||||
res[RES_DRD_ID].end = pci_resource_end(pdev, PCI_BAR_OTG);
|
||||
res[RES_DRD_ID].name = "otg";
|
||||
res[RES_DRD_ID].flags = IORESOURCE_MEM;
|
||||
dev_dbg(&pdev->dev, "USBSS-DRD physical base addr: %pa\n",
|
||||
&res[RES_DRD_ID].start);
|
||||
|
||||
/* Interrupt for OTG/DRD. */
|
||||
wrap->dev_res[RES_IRQ_OTG_ID].start = pdev->irq;
|
||||
wrap->dev_res[RES_IRQ_OTG_ID].name = "otg";
|
||||
wrap->dev_res[RES_IRQ_OTG_ID].flags = IORESOURCE_IRQ;
|
||||
}
|
||||
|
||||
if (pci_is_enabled(func)) {
|
||||
/* set up platform device info */
|
||||
memset(&plat_info, 0, sizeof(plat_info));
|
||||
plat_info.parent = &pdev->dev;
|
||||
plat_info.fwnode = pdev->dev.fwnode;
|
||||
plat_info.name = PLAT_DRIVER_NAME;
|
||||
plat_info.id = pdev->devfn;
|
||||
wrap->devfn = pdev->devfn;
|
||||
plat_info.res = wrap->dev_res;
|
||||
plat_info.num_res = ARRAY_SIZE(wrap->dev_res);
|
||||
plat_info.dma_mask = pdev->dma_mask;
|
||||
/* register platform device */
|
||||
wrap->plat_dev = platform_device_register_full(&plat_info);
|
||||
if (IS_ERR(wrap->plat_dev)) {
|
||||
pci_disable_device(pdev);
|
||||
err = PTR_ERR(wrap->plat_dev);
|
||||
kfree(wrap);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
pci_set_drvdata(pdev, wrap);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void cdns3_pci_remove(struct pci_dev *pdev)
|
||||
{
|
||||
struct cdns3_wrap *wrap;
|
||||
struct pci_dev *func;
|
||||
|
||||
func = cdns3_get_second_fun(pdev);
|
||||
|
||||
wrap = (struct cdns3_wrap *)pci_get_drvdata(pdev);
|
||||
if (wrap->devfn == pdev->devfn)
|
||||
platform_device_unregister(wrap->plat_dev);
|
||||
|
||||
if (!pci_is_enabled(func))
|
||||
kfree(wrap);
|
||||
}
|
||||
|
||||
static const struct pci_device_id cdns3_pci_ids[] = {
|
||||
{ PCI_VDEVICE(CDNS, PCI_DEVICE_ID_CDNS_USB3) },
|
||||
{ 0, }
|
||||
};
|
||||
|
||||
static struct pci_driver cdns3_pci_driver = {
|
||||
.name = PCI_DRIVER_NAME,
|
||||
.id_table = cdns3_pci_ids,
|
||||
.probe = cdns3_pci_probe,
|
||||
.remove = cdns3_pci_remove,
|
||||
};
|
||||
|
||||
module_pci_driver(cdns3_pci_driver);
|
||||
MODULE_DEVICE_TABLE(pci, cdns3_pci_ids);
|
||||
|
||||
MODULE_AUTHOR("Pawel Laszczak <pawell@cadence.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("Cadence USBSS PCI wrapper");
|
343
drivers/usb/cdns3/cdns3-plat.c
Normal file
343
drivers/usb/cdns3/cdns3-plat.c
Normal file
|
@ -0,0 +1,343 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Cadence USBSS DRD Driver.
|
||||
*
|
||||
* Copyright (C) 2018-2020 Cadence.
|
||||
* Copyright (C) 2017-2018 NXP
|
||||
* Copyright (C) 2019 Texas Instruments
|
||||
*
|
||||
*
|
||||
* Author: Peter Chen <peter.chen@nxp.com>
|
||||
* Pawel Laszczak <pawell@cadence.com>
|
||||
* Roger Quadros <rogerq@ti.com>
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
|
||||
#include "core.h"
|
||||
#include "gadget-export.h"
|
||||
#include "drd.h"
|
||||
|
||||
static int set_phy_power_on(struct cdns *cdns)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = phy_power_on(cdns->usb2_phy);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = phy_power_on(cdns->usb3_phy);
|
||||
if (ret)
|
||||
phy_power_off(cdns->usb2_phy);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void set_phy_power_off(struct cdns *cdns)
|
||||
{
|
||||
phy_power_off(cdns->usb3_phy);
|
||||
phy_power_off(cdns->usb2_phy);
|
||||
}
|
||||
|
||||
/**
|
||||
* cdns3_plat_probe - probe for cdns3 core device
|
||||
* @pdev: Pointer to cdns3 core platform device
|
||||
*
|
||||
* Returns 0 on success otherwise negative errno
|
||||
*/
|
||||
static int cdns3_plat_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct resource *res;
|
||||
struct cdns *cdns;
|
||||
void __iomem *regs;
|
||||
int ret;
|
||||
|
||||
cdns = devm_kzalloc(dev, sizeof(*cdns), GFP_KERNEL);
|
||||
if (!cdns)
|
||||
return -ENOMEM;
|
||||
|
||||
cdns->dev = dev;
|
||||
cdns->pdata = dev_get_platdata(dev);
|
||||
|
||||
platform_set_drvdata(pdev, cdns);
|
||||
|
||||
ret = platform_get_irq_byname(pdev, "host");
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
cdns->xhci_res[0].start = ret;
|
||||
cdns->xhci_res[0].end = ret;
|
||||
cdns->xhci_res[0].flags = IORESOURCE_IRQ | irq_get_trigger_type(ret);
|
||||
cdns->xhci_res[0].name = "host";
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "xhci");
|
||||
if (!res) {
|
||||
dev_err(dev, "couldn't get xhci resource\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
cdns->xhci_res[1] = *res;
|
||||
|
||||
cdns->dev_irq = platform_get_irq_byname(pdev, "peripheral");
|
||||
|
||||
if (cdns->dev_irq < 0)
|
||||
return dev_err_probe(dev, cdns->dev_irq,
|
||||
"Failed to get peripheral IRQ\n");
|
||||
|
||||
regs = devm_platform_ioremap_resource_byname(pdev, "dev");
|
||||
if (IS_ERR(regs))
|
||||
return dev_err_probe(dev, PTR_ERR(regs),
|
||||
"Failed to get dev base\n");
|
||||
|
||||
cdns->dev_regs = regs;
|
||||
|
||||
cdns->otg_irq = platform_get_irq_byname(pdev, "otg");
|
||||
if (cdns->otg_irq < 0)
|
||||
return dev_err_probe(dev, cdns->otg_irq,
|
||||
"Failed to get otg IRQ\n");
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "otg");
|
||||
if (!res) {
|
||||
dev_err(dev, "couldn't get otg resource\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
cdns->phyrst_a_enable = device_property_read_bool(dev, "cdns,phyrst-a-enable");
|
||||
|
||||
cdns->otg_res = *res;
|
||||
|
||||
cdns->wakeup_irq = platform_get_irq_byname_optional(pdev, "wakeup");
|
||||
if (cdns->wakeup_irq == -EPROBE_DEFER)
|
||||
return cdns->wakeup_irq;
|
||||
|
||||
if (cdns->wakeup_irq < 0) {
|
||||
dev_dbg(dev, "couldn't get wakeup irq\n");
|
||||
cdns->wakeup_irq = 0x0;
|
||||
}
|
||||
|
||||
cdns->usb2_phy = devm_phy_optional_get(dev, "cdns3,usb2-phy");
|
||||
if (IS_ERR(cdns->usb2_phy))
|
||||
return dev_err_probe(dev, PTR_ERR(cdns->usb2_phy),
|
||||
"Failed to get cdn3,usb2-phy\n");
|
||||
|
||||
ret = phy_init(cdns->usb2_phy);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
cdns->usb3_phy = devm_phy_optional_get(dev, "cdns3,usb3-phy");
|
||||
if (IS_ERR(cdns->usb3_phy))
|
||||
return dev_err_probe(dev, PTR_ERR(cdns->usb3_phy),
|
||||
"Failed to get cdn3,usb3-phy\n");
|
||||
|
||||
ret = phy_init(cdns->usb3_phy);
|
||||
if (ret)
|
||||
goto err_phy3_init;
|
||||
|
||||
ret = set_phy_power_on(cdns);
|
||||
if (ret)
|
||||
goto err_phy_power_on;
|
||||
|
||||
cdns->gadget_init = cdns3_gadget_init;
|
||||
|
||||
ret = cdns_init(cdns);
|
||||
if (ret)
|
||||
goto err_cdns_init;
|
||||
|
||||
device_set_wakeup_capable(dev, true);
|
||||
pm_runtime_set_active(dev);
|
||||
pm_runtime_enable(dev);
|
||||
if (!(cdns->pdata && (cdns->pdata->quirks & CDNS3_DEFAULT_PM_RUNTIME_ALLOW)))
|
||||
pm_runtime_forbid(dev);
|
||||
|
||||
/*
|
||||
* The controller needs less time between bus and controller suspend,
|
||||
* and we also needs a small delay to avoid frequently entering low
|
||||
* power mode.
|
||||
*/
|
||||
pm_runtime_set_autosuspend_delay(dev, 20);
|
||||
pm_runtime_mark_last_busy(dev);
|
||||
pm_runtime_use_autosuspend(dev);
|
||||
|
||||
return 0;
|
||||
|
||||
err_cdns_init:
|
||||
set_phy_power_off(cdns);
|
||||
err_phy_power_on:
|
||||
phy_exit(cdns->usb3_phy);
|
||||
err_phy3_init:
|
||||
phy_exit(cdns->usb2_phy);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* cdns3_plat_remove() - unbind drd driver and clean up
|
||||
* @pdev: Pointer to Linux platform device
|
||||
*
|
||||
* Returns 0 on success otherwise negative errno
|
||||
*/
|
||||
static void cdns3_plat_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct cdns *cdns = platform_get_drvdata(pdev);
|
||||
struct device *dev = cdns->dev;
|
||||
|
||||
pm_runtime_get_sync(dev);
|
||||
pm_runtime_disable(dev);
|
||||
pm_runtime_put_noidle(dev);
|
||||
cdns_remove(cdns);
|
||||
set_phy_power_off(cdns);
|
||||
phy_exit(cdns->usb2_phy);
|
||||
phy_exit(cdns->usb3_phy);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
|
||||
static int cdns3_set_platform_suspend(struct device *dev,
|
||||
bool suspend, bool wakeup)
|
||||
{
|
||||
struct cdns *cdns = dev_get_drvdata(dev);
|
||||
int ret = 0;
|
||||
|
||||
if (cdns->pdata && cdns->pdata->platform_suspend)
|
||||
ret = cdns->pdata->platform_suspend(dev, suspend, wakeup);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cdns3_controller_suspend(struct device *dev, pm_message_t msg)
|
||||
{
|
||||
struct cdns *cdns = dev_get_drvdata(dev);
|
||||
bool wakeup;
|
||||
unsigned long flags;
|
||||
|
||||
if (cdns->in_lpm)
|
||||
return 0;
|
||||
|
||||
if (PMSG_IS_AUTO(msg))
|
||||
wakeup = true;
|
||||
else
|
||||
wakeup = device_may_wakeup(dev);
|
||||
|
||||
cdns3_set_platform_suspend(cdns->dev, true, wakeup);
|
||||
set_phy_power_off(cdns);
|
||||
spin_lock_irqsave(&cdns->lock, flags);
|
||||
cdns->in_lpm = true;
|
||||
spin_unlock_irqrestore(&cdns->lock, flags);
|
||||
dev_dbg(cdns->dev, "%s ends\n", __func__);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cdns3_controller_resume(struct device *dev, pm_message_t msg)
|
||||
{
|
||||
struct cdns *cdns = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
unsigned long flags;
|
||||
|
||||
if (!cdns->in_lpm)
|
||||
return 0;
|
||||
|
||||
if (cdns_power_is_lost(cdns)) {
|
||||
phy_exit(cdns->usb2_phy);
|
||||
ret = phy_init(cdns->usb2_phy);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
phy_exit(cdns->usb3_phy);
|
||||
ret = phy_init(cdns->usb3_phy);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = set_phy_power_on(cdns);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
cdns3_set_platform_suspend(cdns->dev, false, false);
|
||||
|
||||
spin_lock_irqsave(&cdns->lock, flags);
|
||||
cdns_resume(cdns);
|
||||
cdns->in_lpm = false;
|
||||
spin_unlock_irqrestore(&cdns->lock, flags);
|
||||
cdns_set_active(cdns, !PMSG_IS_AUTO(msg));
|
||||
if (cdns->wakeup_pending) {
|
||||
cdns->wakeup_pending = false;
|
||||
enable_irq(cdns->wakeup_irq);
|
||||
}
|
||||
dev_dbg(cdns->dev, "%s ends\n", __func__);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cdns3_plat_runtime_suspend(struct device *dev)
|
||||
{
|
||||
return cdns3_controller_suspend(dev, PMSG_AUTO_SUSPEND);
|
||||
}
|
||||
|
||||
static int cdns3_plat_runtime_resume(struct device *dev)
|
||||
{
|
||||
return cdns3_controller_resume(dev, PMSG_AUTO_RESUME);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
|
||||
static int cdns3_plat_suspend(struct device *dev)
|
||||
{
|
||||
struct cdns *cdns = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
|
||||
cdns_suspend(cdns);
|
||||
|
||||
ret = cdns3_controller_suspend(dev, PMSG_SUSPEND);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (device_may_wakeup(dev) && cdns->wakeup_irq)
|
||||
enable_irq_wake(cdns->wakeup_irq);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cdns3_plat_resume(struct device *dev)
|
||||
{
|
||||
return cdns3_controller_resume(dev, PMSG_RESUME);
|
||||
}
|
||||
#endif /* CONFIG_PM_SLEEP */
|
||||
#endif /* CONFIG_PM */
|
||||
|
||||
static const struct dev_pm_ops cdns3_pm_ops = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(cdns3_plat_suspend, cdns3_plat_resume)
|
||||
SET_RUNTIME_PM_OPS(cdns3_plat_runtime_suspend,
|
||||
cdns3_plat_runtime_resume, NULL)
|
||||
};
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id of_cdns3_match[] = {
|
||||
{ .compatible = "cdns,usb3" },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, of_cdns3_match);
|
||||
#endif
|
||||
|
||||
static struct platform_driver cdns3_driver = {
|
||||
.probe = cdns3_plat_probe,
|
||||
.remove_new = cdns3_plat_remove,
|
||||
.driver = {
|
||||
.name = "cdns-usb3",
|
||||
.of_match_table = of_match_ptr(of_cdns3_match),
|
||||
.pm = &cdns3_pm_ops,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(cdns3_driver);
|
||||
|
||||
MODULE_ALIAS("platform:cdns3");
|
||||
MODULE_AUTHOR("Pawel Laszczak <pawell@cadence.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("Cadence USB3 DRD Controller Driver");
|
244
drivers/usb/cdns3/cdns3-starfive.c
Normal file
244
drivers/usb/cdns3/cdns3-starfive.c
Normal file
|
@ -0,0 +1,244 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* cdns3-starfive.c - StarFive specific Glue layer for Cadence USB Controller
|
||||
*
|
||||
* Copyright (C) 2023 StarFive Technology Co., Ltd.
|
||||
*
|
||||
* Author: Minda Chen <minda.chen@starfivetech.com>
|
||||
*/
|
||||
|
||||
#include <linux/bits.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/reset.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/usb/otg.h>
|
||||
#include "core.h"
|
||||
|
||||
#define USB_STRAP_HOST BIT(17)
|
||||
#define USB_STRAP_DEVICE BIT(18)
|
||||
#define USB_STRAP_MASK GENMASK(18, 16)
|
||||
|
||||
#define USB_SUSPENDM_HOST BIT(19)
|
||||
#define USB_SUSPENDM_MASK BIT(19)
|
||||
|
||||
#define USB_MISC_CFG_MASK GENMASK(23, 20)
|
||||
#define USB_SUSPENDM_BYPS BIT(20)
|
||||
#define USB_PLL_EN BIT(22)
|
||||
#define USB_REFCLK_MODE BIT(23)
|
||||
|
||||
struct cdns_starfive {
|
||||
struct device *dev;
|
||||
struct regmap *stg_syscon;
|
||||
struct reset_control *resets;
|
||||
struct clk_bulk_data *clks;
|
||||
int num_clks;
|
||||
u32 stg_usb_mode;
|
||||
};
|
||||
|
||||
static void cdns_mode_init(struct platform_device *pdev,
|
||||
struct cdns_starfive *data)
|
||||
{
|
||||
enum usb_dr_mode mode;
|
||||
|
||||
regmap_update_bits(data->stg_syscon, data->stg_usb_mode,
|
||||
USB_MISC_CFG_MASK,
|
||||
USB_SUSPENDM_BYPS | USB_PLL_EN | USB_REFCLK_MODE);
|
||||
|
||||
/* dr mode setting */
|
||||
mode = usb_get_dr_mode(&pdev->dev);
|
||||
|
||||
switch (mode) {
|
||||
case USB_DR_MODE_HOST:
|
||||
regmap_update_bits(data->stg_syscon,
|
||||
data->stg_usb_mode,
|
||||
USB_STRAP_MASK,
|
||||
USB_STRAP_HOST);
|
||||
regmap_update_bits(data->stg_syscon,
|
||||
data->stg_usb_mode,
|
||||
USB_SUSPENDM_MASK,
|
||||
USB_SUSPENDM_HOST);
|
||||
break;
|
||||
|
||||
case USB_DR_MODE_PERIPHERAL:
|
||||
regmap_update_bits(data->stg_syscon, data->stg_usb_mode,
|
||||
USB_STRAP_MASK, USB_STRAP_DEVICE);
|
||||
regmap_update_bits(data->stg_syscon, data->stg_usb_mode,
|
||||
USB_SUSPENDM_MASK, 0);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int cdns_clk_rst_init(struct cdns_starfive *data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = clk_bulk_prepare_enable(data->num_clks, data->clks);
|
||||
if (ret)
|
||||
return dev_err_probe(data->dev, ret,
|
||||
"failed to enable clocks\n");
|
||||
|
||||
ret = reset_control_deassert(data->resets);
|
||||
if (ret) {
|
||||
dev_err(data->dev, "failed to reset clocks\n");
|
||||
goto err_clk_init;
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
err_clk_init:
|
||||
clk_bulk_disable_unprepare(data->num_clks, data->clks);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void cdns_clk_rst_deinit(struct cdns_starfive *data)
|
||||
{
|
||||
reset_control_assert(data->resets);
|
||||
clk_bulk_disable_unprepare(data->num_clks, data->clks);
|
||||
}
|
||||
|
||||
static int cdns_starfive_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct cdns_starfive *data;
|
||||
unsigned int args;
|
||||
int ret;
|
||||
|
||||
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
data->dev = dev;
|
||||
|
||||
data->stg_syscon =
|
||||
syscon_regmap_lookup_by_phandle_args(pdev->dev.of_node,
|
||||
"starfive,stg-syscon", 1, &args);
|
||||
|
||||
if (IS_ERR(data->stg_syscon))
|
||||
return dev_err_probe(dev, PTR_ERR(data->stg_syscon),
|
||||
"Failed to parse starfive,stg-syscon\n");
|
||||
|
||||
data->stg_usb_mode = args;
|
||||
|
||||
data->num_clks = devm_clk_bulk_get_all(data->dev, &data->clks);
|
||||
if (data->num_clks < 0)
|
||||
return dev_err_probe(data->dev, -ENODEV,
|
||||
"Failed to get clocks\n");
|
||||
|
||||
data->resets = devm_reset_control_array_get_exclusive(data->dev);
|
||||
if (IS_ERR(data->resets))
|
||||
return dev_err_probe(data->dev, PTR_ERR(data->resets),
|
||||
"Failed to get resets");
|
||||
|
||||
cdns_mode_init(pdev, data);
|
||||
ret = cdns_clk_rst_init(data);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = of_platform_populate(dev->of_node, NULL, NULL, dev);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to create children\n");
|
||||
cdns_clk_rst_deinit(data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
device_set_wakeup_capable(dev, true);
|
||||
pm_runtime_set_active(dev);
|
||||
pm_runtime_enable(dev);
|
||||
platform_set_drvdata(pdev, data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cdns_starfive_remove_core(struct device *dev, void *c)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
|
||||
platform_device_unregister(pdev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void cdns_starfive_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct cdns_starfive *data = dev_get_drvdata(dev);
|
||||
|
||||
pm_runtime_get_sync(dev);
|
||||
device_for_each_child(dev, NULL, cdns_starfive_remove_core);
|
||||
|
||||
pm_runtime_disable(dev);
|
||||
pm_runtime_put_noidle(dev);
|
||||
cdns_clk_rst_deinit(data);
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int cdns_starfive_runtime_resume(struct device *dev)
|
||||
{
|
||||
struct cdns_starfive *data = dev_get_drvdata(dev);
|
||||
|
||||
return clk_bulk_prepare_enable(data->num_clks, data->clks);
|
||||
}
|
||||
|
||||
static int cdns_starfive_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct cdns_starfive *data = dev_get_drvdata(dev);
|
||||
|
||||
clk_bulk_disable_unprepare(data->num_clks, data->clks);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int cdns_starfive_resume(struct device *dev)
|
||||
{
|
||||
struct cdns_starfive *data = dev_get_drvdata(dev);
|
||||
|
||||
return cdns_clk_rst_init(data);
|
||||
}
|
||||
|
||||
static int cdns_starfive_suspend(struct device *dev)
|
||||
{
|
||||
struct cdns_starfive *data = dev_get_drvdata(dev);
|
||||
|
||||
cdns_clk_rst_deinit(data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
static const struct dev_pm_ops cdns_starfive_pm_ops = {
|
||||
SET_RUNTIME_PM_OPS(cdns_starfive_runtime_suspend,
|
||||
cdns_starfive_runtime_resume, NULL)
|
||||
SET_SYSTEM_SLEEP_PM_OPS(cdns_starfive_suspend, cdns_starfive_resume)
|
||||
};
|
||||
|
||||
static const struct of_device_id cdns_starfive_of_match[] = {
|
||||
{ .compatible = "starfive,jh7110-usb", },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, cdns_starfive_of_match);
|
||||
|
||||
static struct platform_driver cdns_starfive_driver = {
|
||||
.probe = cdns_starfive_probe,
|
||||
.remove_new = cdns_starfive_remove,
|
||||
.driver = {
|
||||
.name = "cdns3-starfive",
|
||||
.of_match_table = cdns_starfive_of_match,
|
||||
.pm = &cdns_starfive_pm_ops,
|
||||
},
|
||||
};
|
||||
module_platform_driver(cdns_starfive_driver);
|
||||
|
||||
MODULE_ALIAS("platform:cdns3-starfive");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("Cadence USB3 StarFive Glue Layer");
|
248
drivers/usb/cdns3/cdns3-ti.c
Normal file
248
drivers/usb/cdns3/cdns3-ti.c
Normal file
|
@ -0,0 +1,248 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* cdns3-ti.c - TI specific Glue layer for Cadence USB Controller
|
||||
*
|
||||
* Copyright (C) 2019 Texas Instruments Incorporated - https://www.ti.com
|
||||
*/
|
||||
|
||||
#include <linux/bits.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/property.h>
|
||||
#include "core.h"
|
||||
|
||||
/* USB Wrapper register offsets */
|
||||
#define USBSS_PID 0x0
|
||||
#define USBSS_W1 0x4
|
||||
#define USBSS_STATIC_CONFIG 0x8
|
||||
#define USBSS_PHY_TEST 0xc
|
||||
#define USBSS_DEBUG_CTRL 0x10
|
||||
#define USBSS_DEBUG_INFO 0x14
|
||||
#define USBSS_DEBUG_LINK_STATE 0x18
|
||||
#define USBSS_DEVICE_CTRL 0x1c
|
||||
|
||||
/* Wrapper 1 register bits */
|
||||
#define USBSS_W1_PWRUP_RST BIT(0)
|
||||
#define USBSS_W1_OVERCURRENT_SEL BIT(8)
|
||||
#define USBSS_W1_MODESTRAP_SEL BIT(9)
|
||||
#define USBSS_W1_OVERCURRENT BIT(16)
|
||||
#define USBSS_W1_MODESTRAP_MASK GENMASK(18, 17)
|
||||
#define USBSS_W1_MODESTRAP_SHIFT 17
|
||||
#define USBSS_W1_USB2_ONLY BIT(19)
|
||||
|
||||
/* Static config register bits */
|
||||
#define USBSS1_STATIC_PLL_REF_SEL_MASK GENMASK(8, 5)
|
||||
#define USBSS1_STATIC_PLL_REF_SEL_SHIFT 5
|
||||
#define USBSS1_STATIC_LOOPBACK_MODE_MASK GENMASK(4, 3)
|
||||
#define USBSS1_STATIC_LOOPBACK_MODE_SHIFT 3
|
||||
#define USBSS1_STATIC_VBUS_SEL_MASK GENMASK(2, 1)
|
||||
#define USBSS1_STATIC_VBUS_SEL_SHIFT 1
|
||||
#define USBSS1_STATIC_LANE_REVERSE BIT(0)
|
||||
|
||||
/* Modestrap modes */
|
||||
enum modestrap_mode { USBSS_MODESTRAP_MODE_NONE,
|
||||
USBSS_MODESTRAP_MODE_HOST,
|
||||
USBSS_MODESTRAP_MODE_PERIPHERAL};
|
||||
|
||||
struct cdns_ti {
|
||||
struct device *dev;
|
||||
void __iomem *usbss;
|
||||
unsigned usb2_only:1;
|
||||
unsigned vbus_divider:1;
|
||||
struct clk *usb2_refclk;
|
||||
struct clk *lpm_clk;
|
||||
};
|
||||
|
||||
static const int cdns_ti_rate_table[] = { /* in KHZ */
|
||||
9600,
|
||||
10000,
|
||||
12000,
|
||||
19200,
|
||||
20000,
|
||||
24000,
|
||||
25000,
|
||||
26000,
|
||||
38400,
|
||||
40000,
|
||||
58000,
|
||||
50000,
|
||||
52000,
|
||||
};
|
||||
|
||||
static inline u32 cdns_ti_readl(struct cdns_ti *data, u32 offset)
|
||||
{
|
||||
return readl(data->usbss + offset);
|
||||
}
|
||||
|
||||
static inline void cdns_ti_writel(struct cdns_ti *data, u32 offset, u32 value)
|
||||
{
|
||||
writel(value, data->usbss + offset);
|
||||
}
|
||||
|
||||
static struct cdns3_platform_data cdns_ti_pdata = {
|
||||
.quirks = CDNS3_DRD_SUSPEND_RESIDENCY_ENABLE, /* Errata i2409 */
|
||||
};
|
||||
|
||||
static const struct of_dev_auxdata cdns_ti_auxdata[] = {
|
||||
{
|
||||
.compatible = "cdns,usb3",
|
||||
.platform_data = &cdns_ti_pdata,
|
||||
},
|
||||
{},
|
||||
};
|
||||
|
||||
static int cdns_ti_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct device_node *node = pdev->dev.of_node;
|
||||
struct cdns_ti *data;
|
||||
int error;
|
||||
u32 reg;
|
||||
int rate_code, i;
|
||||
unsigned long rate;
|
||||
|
||||
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(pdev, data);
|
||||
|
||||
data->dev = dev;
|
||||
|
||||
data->usbss = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(data->usbss)) {
|
||||
dev_err(dev, "can't map IOMEM resource\n");
|
||||
return PTR_ERR(data->usbss);
|
||||
}
|
||||
|
||||
data->usb2_refclk = devm_clk_get(dev, "ref");
|
||||
if (IS_ERR(data->usb2_refclk)) {
|
||||
dev_err(dev, "can't get usb2_refclk\n");
|
||||
return PTR_ERR(data->usb2_refclk);
|
||||
}
|
||||
|
||||
data->lpm_clk = devm_clk_get(dev, "lpm");
|
||||
if (IS_ERR(data->lpm_clk)) {
|
||||
dev_err(dev, "can't get lpm_clk\n");
|
||||
return PTR_ERR(data->lpm_clk);
|
||||
}
|
||||
|
||||
rate = clk_get_rate(data->usb2_refclk);
|
||||
rate /= 1000; /* To KHz */
|
||||
for (i = 0; i < ARRAY_SIZE(cdns_ti_rate_table); i++) {
|
||||
if (cdns_ti_rate_table[i] == rate)
|
||||
break;
|
||||
}
|
||||
|
||||
if (i == ARRAY_SIZE(cdns_ti_rate_table)) {
|
||||
dev_err(dev, "unsupported usb2_refclk rate: %lu KHz\n", rate);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
rate_code = i;
|
||||
|
||||
pm_runtime_enable(dev);
|
||||
error = pm_runtime_get_sync(dev);
|
||||
if (error < 0) {
|
||||
dev_err(dev, "pm_runtime_get_sync failed: %d\n", error);
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* assert RESET */
|
||||
reg = cdns_ti_readl(data, USBSS_W1);
|
||||
reg &= ~USBSS_W1_PWRUP_RST;
|
||||
cdns_ti_writel(data, USBSS_W1, reg);
|
||||
|
||||
/* set static config */
|
||||
reg = cdns_ti_readl(data, USBSS_STATIC_CONFIG);
|
||||
reg &= ~USBSS1_STATIC_PLL_REF_SEL_MASK;
|
||||
reg |= rate_code << USBSS1_STATIC_PLL_REF_SEL_SHIFT;
|
||||
|
||||
reg &= ~USBSS1_STATIC_VBUS_SEL_MASK;
|
||||
data->vbus_divider = device_property_read_bool(dev, "ti,vbus-divider");
|
||||
if (data->vbus_divider)
|
||||
reg |= 1 << USBSS1_STATIC_VBUS_SEL_SHIFT;
|
||||
|
||||
cdns_ti_writel(data, USBSS_STATIC_CONFIG, reg);
|
||||
reg = cdns_ti_readl(data, USBSS_STATIC_CONFIG);
|
||||
|
||||
/* set USB2_ONLY mode if requested */
|
||||
reg = cdns_ti_readl(data, USBSS_W1);
|
||||
data->usb2_only = device_property_read_bool(dev, "ti,usb2-only");
|
||||
if (data->usb2_only)
|
||||
reg |= USBSS_W1_USB2_ONLY;
|
||||
|
||||
/* set default modestrap */
|
||||
reg |= USBSS_W1_MODESTRAP_SEL;
|
||||
reg &= ~USBSS_W1_MODESTRAP_MASK;
|
||||
reg |= USBSS_MODESTRAP_MODE_NONE << USBSS_W1_MODESTRAP_SHIFT;
|
||||
cdns_ti_writel(data, USBSS_W1, reg);
|
||||
|
||||
/* de-assert RESET */
|
||||
reg |= USBSS_W1_PWRUP_RST;
|
||||
cdns_ti_writel(data, USBSS_W1, reg);
|
||||
|
||||
error = of_platform_populate(node, NULL, cdns_ti_auxdata, dev);
|
||||
if (error) {
|
||||
dev_err(dev, "failed to create children: %d\n", error);
|
||||
goto err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
pm_runtime_put_sync(data->dev);
|
||||
pm_runtime_disable(data->dev);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static int cdns_ti_remove_core(struct device *dev, void *c)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
|
||||
platform_device_unregister(pdev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void cdns_ti_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
|
||||
device_for_each_child(dev, NULL, cdns_ti_remove_core);
|
||||
pm_runtime_put_sync(dev);
|
||||
pm_runtime_disable(dev);
|
||||
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
}
|
||||
|
||||
static const struct of_device_id cdns_ti_of_match[] = {
|
||||
{ .compatible = "ti,j721e-usb", },
|
||||
{ .compatible = "ti,am64-usb", },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, cdns_ti_of_match);
|
||||
|
||||
static struct platform_driver cdns_ti_driver = {
|
||||
.probe = cdns_ti_probe,
|
||||
.remove_new = cdns_ti_remove,
|
||||
.driver = {
|
||||
.name = "cdns3-ti",
|
||||
.of_match_table = cdns_ti_of_match,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(cdns_ti_driver);
|
||||
|
||||
MODULE_ALIAS("platform:cdns3-ti");
|
||||
MODULE_AUTHOR("Roger Quadros <rogerq@ti.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("Cadence USB3 TI Glue Layer");
|
11
drivers/usb/cdns3/cdns3-trace.c
Normal file
11
drivers/usb/cdns3/cdns3-trace.c
Normal file
|
@ -0,0 +1,11 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* USBSS device controller driver Trace Support
|
||||
*
|
||||
* Copyright (C) 2018-2019 Cadence.
|
||||
*
|
||||
* Author: Pawel Laszczak <pawell@cadence.com>
|
||||
*/
|
||||
|
||||
#define CREATE_TRACE_POINTS
|
||||
#include "cdns3-trace.h"
|
557
drivers/usb/cdns3/cdns3-trace.h
Normal file
557
drivers/usb/cdns3/cdns3-trace.h
Normal file
|
@ -0,0 +1,557 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* USBSS device controller driver.
|
||||
* Trace support header file.
|
||||
*
|
||||
* Copyright (C) 2018-2019 Cadence.
|
||||
*
|
||||
* Author: Pawel Laszczak <pawell@cadence.com>
|
||||
*/
|
||||
|
||||
#undef TRACE_SYSTEM
|
||||
#define TRACE_SYSTEM cdns3
|
||||
|
||||
#if !defined(__LINUX_CDNS3_TRACE) || defined(TRACE_HEADER_MULTI_READ)
|
||||
#define __LINUX_CDNS3_TRACE
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/tracepoint.h>
|
||||
#include <asm/byteorder.h>
|
||||
#include <linux/usb/ch9.h>
|
||||
#include "core.h"
|
||||
#include "cdns3-gadget.h"
|
||||
#include "cdns3-debug.h"
|
||||
|
||||
#define CDNS3_MSG_MAX 500
|
||||
|
||||
TRACE_EVENT(cdns3_halt,
|
||||
TP_PROTO(struct cdns3_endpoint *ep_priv, u8 halt, u8 flush),
|
||||
TP_ARGS(ep_priv, halt, flush),
|
||||
TP_STRUCT__entry(
|
||||
__string(name, ep_priv->name)
|
||||
__field(u8, halt)
|
||||
__field(u8, flush)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__assign_str(name);
|
||||
__entry->halt = halt;
|
||||
__entry->flush = flush;
|
||||
),
|
||||
TP_printk("Halt %s for %s: %s", __entry->flush ? " and flush" : "",
|
||||
__get_str(name), __entry->halt ? "set" : "cleared")
|
||||
);
|
||||
|
||||
TRACE_EVENT(cdns3_wa1,
|
||||
TP_PROTO(struct cdns3_endpoint *ep_priv, char *msg),
|
||||
TP_ARGS(ep_priv, msg),
|
||||
TP_STRUCT__entry(
|
||||
__string(ep_name, ep_priv->name)
|
||||
__string(msg, msg)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__assign_str(ep_name);
|
||||
__assign_str(msg);
|
||||
),
|
||||
TP_printk("WA1: %s %s", __get_str(ep_name), __get_str(msg))
|
||||
);
|
||||
|
||||
TRACE_EVENT(cdns3_wa2,
|
||||
TP_PROTO(struct cdns3_endpoint *ep_priv, char *msg),
|
||||
TP_ARGS(ep_priv, msg),
|
||||
TP_STRUCT__entry(
|
||||
__string(ep_name, ep_priv->name)
|
||||
__string(msg, msg)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__assign_str(ep_name);
|
||||
__assign_str(msg);
|
||||
),
|
||||
TP_printk("WA2: %s %s", __get_str(ep_name), __get_str(msg))
|
||||
);
|
||||
|
||||
DECLARE_EVENT_CLASS(cdns3_log_doorbell,
|
||||
TP_PROTO(const char *ep_name, u32 ep_trbaddr),
|
||||
TP_ARGS(ep_name, ep_trbaddr),
|
||||
TP_STRUCT__entry(
|
||||
__string(name, ep_name)
|
||||
__field(u32, ep_trbaddr)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__assign_str(name);
|
||||
__entry->ep_trbaddr = ep_trbaddr;
|
||||
),
|
||||
TP_printk("%s, ep_trbaddr %08x", __get_str(name),
|
||||
__entry->ep_trbaddr)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdns3_log_doorbell, cdns3_doorbell_ep0,
|
||||
TP_PROTO(const char *ep_name, u32 ep_trbaddr),
|
||||
TP_ARGS(ep_name, ep_trbaddr)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdns3_log_doorbell, cdns3_doorbell_epx,
|
||||
TP_PROTO(const char *ep_name, u32 ep_trbaddr),
|
||||
TP_ARGS(ep_name, ep_trbaddr)
|
||||
);
|
||||
|
||||
DECLARE_EVENT_CLASS(cdns3_log_usb_irq,
|
||||
TP_PROTO(struct cdns3_device *priv_dev, u32 usb_ists),
|
||||
TP_ARGS(priv_dev, usb_ists),
|
||||
TP_STRUCT__entry(
|
||||
__field(enum usb_device_speed, speed)
|
||||
__field(u32, usb_ists)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__entry->speed = cdns3_get_speed(priv_dev);
|
||||
__entry->usb_ists = usb_ists;
|
||||
),
|
||||
TP_printk("%s", cdns3_decode_usb_irq(__get_buf(CDNS3_MSG_MAX), __entry->speed,
|
||||
__entry->usb_ists))
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdns3_log_usb_irq, cdns3_usb_irq,
|
||||
TP_PROTO(struct cdns3_device *priv_dev, u32 usb_ists),
|
||||
TP_ARGS(priv_dev, usb_ists)
|
||||
);
|
||||
|
||||
DECLARE_EVENT_CLASS(cdns3_log_epx_irq,
|
||||
TP_PROTO(struct cdns3_device *priv_dev, struct cdns3_endpoint *priv_ep),
|
||||
TP_ARGS(priv_dev, priv_ep),
|
||||
TP_STRUCT__entry(
|
||||
__string(ep_name, priv_ep->name)
|
||||
__field(u32, ep_sts)
|
||||
__field(u32, ep_traddr)
|
||||
__field(u32, ep_last_sid)
|
||||
__field(u32, use_streams)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__assign_str(ep_name);
|
||||
__entry->ep_sts = readl(&priv_dev->regs->ep_sts);
|
||||
__entry->ep_traddr = readl(&priv_dev->regs->ep_traddr);
|
||||
__entry->ep_last_sid = priv_ep->last_stream_id;
|
||||
__entry->use_streams = priv_ep->use_streams;
|
||||
),
|
||||
TP_printk("%s, ep_traddr: %08x ep_last_sid: %08x use_streams: %d",
|
||||
cdns3_decode_epx_irq(__get_buf(CDNS3_MSG_MAX),
|
||||
__get_str(ep_name),
|
||||
__entry->ep_sts),
|
||||
__entry->ep_traddr,
|
||||
__entry->ep_last_sid,
|
||||
__entry->use_streams)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdns3_log_epx_irq, cdns3_epx_irq,
|
||||
TP_PROTO(struct cdns3_device *priv_dev, struct cdns3_endpoint *priv_ep),
|
||||
TP_ARGS(priv_dev, priv_ep)
|
||||
);
|
||||
|
||||
DECLARE_EVENT_CLASS(cdns3_log_ep0_irq,
|
||||
TP_PROTO(struct cdns3_device *priv_dev, u32 ep_sts),
|
||||
TP_ARGS(priv_dev, ep_sts),
|
||||
TP_STRUCT__entry(
|
||||
__field(int, ep_dir)
|
||||
__field(u32, ep_sts)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__entry->ep_dir = priv_dev->selected_ep;
|
||||
__entry->ep_sts = ep_sts;
|
||||
),
|
||||
TP_printk("%s", cdns3_decode_ep0_irq(__get_buf(CDNS3_MSG_MAX),
|
||||
__entry->ep_dir,
|
||||
__entry->ep_sts))
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdns3_log_ep0_irq, cdns3_ep0_irq,
|
||||
TP_PROTO(struct cdns3_device *priv_dev, u32 ep_sts),
|
||||
TP_ARGS(priv_dev, ep_sts)
|
||||
);
|
||||
|
||||
DECLARE_EVENT_CLASS(cdns3_log_ctrl,
|
||||
TP_PROTO(struct usb_ctrlrequest *ctrl),
|
||||
TP_ARGS(ctrl),
|
||||
TP_STRUCT__entry(
|
||||
__field(u8, bRequestType)
|
||||
__field(u8, bRequest)
|
||||
__field(u16, wValue)
|
||||
__field(u16, wIndex)
|
||||
__field(u16, wLength)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__entry->bRequestType = ctrl->bRequestType;
|
||||
__entry->bRequest = ctrl->bRequest;
|
||||
__entry->wValue = le16_to_cpu(ctrl->wValue);
|
||||
__entry->wIndex = le16_to_cpu(ctrl->wIndex);
|
||||
__entry->wLength = le16_to_cpu(ctrl->wLength);
|
||||
),
|
||||
TP_printk("%s", usb_decode_ctrl(__get_buf(CDNS3_MSG_MAX), CDNS3_MSG_MAX,
|
||||
__entry->bRequestType,
|
||||
__entry->bRequest, __entry->wValue,
|
||||
__entry->wIndex, __entry->wLength)
|
||||
)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdns3_log_ctrl, cdns3_ctrl_req,
|
||||
TP_PROTO(struct usb_ctrlrequest *ctrl),
|
||||
TP_ARGS(ctrl)
|
||||
);
|
||||
|
||||
DECLARE_EVENT_CLASS(cdns3_log_request,
|
||||
TP_PROTO(struct cdns3_request *req),
|
||||
TP_ARGS(req),
|
||||
TP_STRUCT__entry(
|
||||
__string(name, req->priv_ep->name)
|
||||
__field(struct cdns3_request *, req)
|
||||
__field(void *, buf)
|
||||
__field(unsigned int, actual)
|
||||
__field(unsigned int, length)
|
||||
__field(int, status)
|
||||
__field(int, zero)
|
||||
__field(int, short_not_ok)
|
||||
__field(int, no_interrupt)
|
||||
__field(int, start_trb)
|
||||
__field(int, end_trb)
|
||||
__field(int, flags)
|
||||
__field(unsigned int, stream_id)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__assign_str(name);
|
||||
__entry->req = req;
|
||||
__entry->buf = req->request.buf;
|
||||
__entry->actual = req->request.actual;
|
||||
__entry->length = req->request.length;
|
||||
__entry->status = req->request.status;
|
||||
__entry->zero = req->request.zero;
|
||||
__entry->short_not_ok = req->request.short_not_ok;
|
||||
__entry->no_interrupt = req->request.no_interrupt;
|
||||
__entry->start_trb = req->start_trb;
|
||||
__entry->end_trb = req->end_trb;
|
||||
__entry->flags = req->flags;
|
||||
__entry->stream_id = req->request.stream_id;
|
||||
),
|
||||
TP_printk("%s: req: %p, req buff %p, length: %u/%u %s%s%s, status: %d,"
|
||||
" trb: [start:%d, end:%d], flags:%x SID: %u",
|
||||
__get_str(name), __entry->req, __entry->buf, __entry->actual,
|
||||
__entry->length,
|
||||
__entry->zero ? "Z" : "z",
|
||||
__entry->short_not_ok ? "S" : "s",
|
||||
__entry->no_interrupt ? "I" : "i",
|
||||
__entry->status,
|
||||
__entry->start_trb,
|
||||
__entry->end_trb,
|
||||
__entry->flags,
|
||||
__entry->stream_id
|
||||
)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdns3_log_request, cdns3_alloc_request,
|
||||
TP_PROTO(struct cdns3_request *req),
|
||||
TP_ARGS(req)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdns3_log_request, cdns3_free_request,
|
||||
TP_PROTO(struct cdns3_request *req),
|
||||
TP_ARGS(req)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdns3_log_request, cdns3_ep_queue,
|
||||
TP_PROTO(struct cdns3_request *req),
|
||||
TP_ARGS(req)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdns3_log_request, cdns3_ep_dequeue,
|
||||
TP_PROTO(struct cdns3_request *req),
|
||||
TP_ARGS(req)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdns3_log_request, cdns3_gadget_giveback,
|
||||
TP_PROTO(struct cdns3_request *req),
|
||||
TP_ARGS(req)
|
||||
);
|
||||
|
||||
TRACE_EVENT(cdns3_ep0_queue,
|
||||
TP_PROTO(struct cdns3_device *dev_priv, struct usb_request *request),
|
||||
TP_ARGS(dev_priv, request),
|
||||
TP_STRUCT__entry(
|
||||
__field(int, dir)
|
||||
__field(int, length)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__entry->dir = dev_priv->ep0_data_dir;
|
||||
__entry->length = request->length;
|
||||
),
|
||||
TP_printk("Queue to ep0%s length: %u", __entry->dir ? "in" : "out",
|
||||
__entry->length)
|
||||
);
|
||||
|
||||
DECLARE_EVENT_CLASS(cdns3_stream_split_transfer_len,
|
||||
TP_PROTO(struct cdns3_request *req),
|
||||
TP_ARGS(req),
|
||||
TP_STRUCT__entry(
|
||||
__string(name, req->priv_ep->name)
|
||||
__field(struct cdns3_request *, req)
|
||||
__field(unsigned int, length)
|
||||
__field(unsigned int, actual)
|
||||
__field(unsigned int, stream_id)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__assign_str(name);
|
||||
__entry->req = req;
|
||||
__entry->actual = req->request.length;
|
||||
__entry->length = req->request.actual;
|
||||
__entry->stream_id = req->request.stream_id;
|
||||
),
|
||||
TP_printk("%s: req: %p,request length: %u actual length: %u SID: %u",
|
||||
__get_str(name), __entry->req, __entry->length,
|
||||
__entry->actual, __entry->stream_id)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdns3_stream_split_transfer_len, cdns3_stream_transfer_split,
|
||||
TP_PROTO(struct cdns3_request *req),
|
||||
TP_ARGS(req)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdns3_stream_split_transfer_len,
|
||||
cdns3_stream_transfer_split_next_part,
|
||||
TP_PROTO(struct cdns3_request *req),
|
||||
TP_ARGS(req)
|
||||
);
|
||||
|
||||
DECLARE_EVENT_CLASS(cdns3_log_aligned_request,
|
||||
TP_PROTO(struct cdns3_request *priv_req),
|
||||
TP_ARGS(priv_req),
|
||||
TP_STRUCT__entry(
|
||||
__string(name, priv_req->priv_ep->name)
|
||||
__field(struct usb_request *, req)
|
||||
__field(void *, buf)
|
||||
__field(dma_addr_t, dma)
|
||||
__field(void *, aligned_buf)
|
||||
__field(dma_addr_t, aligned_dma)
|
||||
__field(u32, aligned_buf_size)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__assign_str(name);
|
||||
__entry->req = &priv_req->request;
|
||||
__entry->buf = priv_req->request.buf;
|
||||
__entry->dma = priv_req->request.dma;
|
||||
__entry->aligned_buf = priv_req->aligned_buf->buf;
|
||||
__entry->aligned_dma = priv_req->aligned_buf->dma;
|
||||
__entry->aligned_buf_size = priv_req->aligned_buf->size;
|
||||
),
|
||||
TP_printk("%s: req: %p, req buf %p, dma %pad a_buf %p a_dma %pad, size %d",
|
||||
__get_str(name), __entry->req, __entry->buf, &__entry->dma,
|
||||
__entry->aligned_buf, &__entry->aligned_dma,
|
||||
__entry->aligned_buf_size
|
||||
)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdns3_log_aligned_request, cdns3_free_aligned_request,
|
||||
TP_PROTO(struct cdns3_request *req),
|
||||
TP_ARGS(req)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdns3_log_aligned_request, cdns3_prepare_aligned_request,
|
||||
TP_PROTO(struct cdns3_request *req),
|
||||
TP_ARGS(req)
|
||||
);
|
||||
|
||||
DECLARE_EVENT_CLASS(cdns3_log_map_request,
|
||||
TP_PROTO(struct cdns3_request *priv_req),
|
||||
TP_ARGS(priv_req),
|
||||
TP_STRUCT__entry(
|
||||
__string(name, priv_req->priv_ep->name)
|
||||
__field(struct usb_request *, req)
|
||||
__field(void *, buf)
|
||||
__field(dma_addr_t, dma)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__assign_str(name);
|
||||
__entry->req = &priv_req->request;
|
||||
__entry->buf = priv_req->request.buf;
|
||||
__entry->dma = priv_req->request.dma;
|
||||
),
|
||||
TP_printk("%s: req: %p, req buf %p, dma %p",
|
||||
__get_str(name), __entry->req, __entry->buf, &__entry->dma
|
||||
)
|
||||
);
|
||||
DEFINE_EVENT(cdns3_log_map_request, cdns3_map_request,
|
||||
TP_PROTO(struct cdns3_request *req),
|
||||
TP_ARGS(req)
|
||||
);
|
||||
DEFINE_EVENT(cdns3_log_map_request, cdns3_mapped_request,
|
||||
TP_PROTO(struct cdns3_request *req),
|
||||
TP_ARGS(req)
|
||||
);
|
||||
|
||||
DECLARE_EVENT_CLASS(cdns3_log_trb,
|
||||
TP_PROTO(struct cdns3_endpoint *priv_ep, struct cdns3_trb *trb),
|
||||
TP_ARGS(priv_ep, trb),
|
||||
TP_STRUCT__entry(
|
||||
__string(name, priv_ep->name)
|
||||
__field(struct cdns3_trb *, trb)
|
||||
__field(u32, buffer)
|
||||
__field(u32, length)
|
||||
__field(u32, control)
|
||||
__field(u32, type)
|
||||
__field(unsigned int, last_stream_id)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__assign_str(name);
|
||||
__entry->trb = trb;
|
||||
__entry->buffer = le32_to_cpu(trb->buffer);
|
||||
__entry->length = le32_to_cpu(trb->length);
|
||||
__entry->control = le32_to_cpu(trb->control);
|
||||
__entry->type = usb_endpoint_type(priv_ep->endpoint.desc);
|
||||
__entry->last_stream_id = priv_ep->last_stream_id;
|
||||
),
|
||||
TP_printk("%s: trb %p, dma buf: 0x%08x, size: %ld, burst: %d ctrl: 0x%08x (%s%s%s%s%s%s%s) SID:%lu LAST_SID:%u",
|
||||
__get_str(name), __entry->trb, __entry->buffer,
|
||||
TRB_LEN(__entry->length),
|
||||
(u8)TRB_BURST_LEN_GET(__entry->length),
|
||||
__entry->control,
|
||||
__entry->control & TRB_CYCLE ? "C=1, " : "C=0, ",
|
||||
__entry->control & TRB_TOGGLE ? "T=1, " : "T=0, ",
|
||||
__entry->control & TRB_ISP ? "ISP, " : "",
|
||||
__entry->control & TRB_FIFO_MODE ? "FIFO, " : "",
|
||||
__entry->control & TRB_CHAIN ? "CHAIN, " : "",
|
||||
__entry->control & TRB_IOC ? "IOC, " : "",
|
||||
TRB_FIELD_TO_TYPE(__entry->control) == TRB_NORMAL ? "Normal" : "LINK",
|
||||
TRB_FIELD_TO_STREAMID(__entry->control),
|
||||
__entry->last_stream_id
|
||||
)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdns3_log_trb, cdns3_prepare_trb,
|
||||
TP_PROTO(struct cdns3_endpoint *priv_ep, struct cdns3_trb *trb),
|
||||
TP_ARGS(priv_ep, trb)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdns3_log_trb, cdns3_complete_trb,
|
||||
TP_PROTO(struct cdns3_endpoint *priv_ep, struct cdns3_trb *trb),
|
||||
TP_ARGS(priv_ep, trb)
|
||||
);
|
||||
|
||||
DECLARE_EVENT_CLASS(cdns3_log_ring,
|
||||
TP_PROTO(struct cdns3_endpoint *priv_ep),
|
||||
TP_ARGS(priv_ep),
|
||||
TP_STRUCT__entry(
|
||||
__dynamic_array(char, buffer,
|
||||
GET_TRBS_PER_SEGMENT(priv_ep->type) > TRBS_PER_SEGMENT ?
|
||||
CDNS3_MSG_MAX :
|
||||
(GET_TRBS_PER_SEGMENT(priv_ep->type) * 65) + CDNS3_MSG_MAX)
|
||||
),
|
||||
TP_fast_assign(
|
||||
cdns3_dbg_ring(priv_ep, __get_str(buffer));
|
||||
),
|
||||
|
||||
TP_printk("%s", __get_str(buffer))
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdns3_log_ring, cdns3_ring,
|
||||
TP_PROTO(struct cdns3_endpoint *priv_ep),
|
||||
TP_ARGS(priv_ep)
|
||||
);
|
||||
|
||||
DECLARE_EVENT_CLASS(cdns3_log_ep,
|
||||
TP_PROTO(struct cdns3_endpoint *priv_ep),
|
||||
TP_ARGS(priv_ep),
|
||||
TP_STRUCT__entry(
|
||||
__string(name, priv_ep->name)
|
||||
__field(unsigned int, maxpacket)
|
||||
__field(unsigned int, maxpacket_limit)
|
||||
__field(unsigned int, max_streams)
|
||||
__field(unsigned int, use_streams)
|
||||
__field(unsigned int, maxburst)
|
||||
__field(unsigned int, flags)
|
||||
__field(unsigned int, dir)
|
||||
__field(u8, enqueue)
|
||||
__field(u8, dequeue)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__assign_str(name);
|
||||
__entry->maxpacket = priv_ep->endpoint.maxpacket;
|
||||
__entry->maxpacket_limit = priv_ep->endpoint.maxpacket_limit;
|
||||
__entry->max_streams = priv_ep->endpoint.max_streams;
|
||||
__entry->use_streams = priv_ep->use_streams;
|
||||
__entry->maxburst = priv_ep->endpoint.maxburst;
|
||||
__entry->flags = priv_ep->flags;
|
||||
__entry->dir = priv_ep->dir;
|
||||
__entry->enqueue = priv_ep->enqueue;
|
||||
__entry->dequeue = priv_ep->dequeue;
|
||||
),
|
||||
TP_printk("%s: mps: %d/%d. streams: %d, stream enable: %d, burst: %d, "
|
||||
"enq idx: %d, deq idx: %d, flags %s%s%s%s%s%s%s%s, dir: %s",
|
||||
__get_str(name), __entry->maxpacket,
|
||||
__entry->maxpacket_limit, __entry->max_streams,
|
||||
__entry->use_streams,
|
||||
__entry->maxburst, __entry->enqueue,
|
||||
__entry->dequeue,
|
||||
__entry->flags & EP_ENABLED ? "EN | " : "",
|
||||
__entry->flags & EP_STALLED ? "STALLED | " : "",
|
||||
__entry->flags & EP_WEDGE ? "WEDGE | " : "",
|
||||
__entry->flags & EP_TRANSFER_STARTED ? "STARTED | " : "",
|
||||
__entry->flags & EP_UPDATE_EP_TRBADDR ? "UPD TRB | " : "",
|
||||
__entry->flags & EP_PENDING_REQUEST ? "REQ PEN | " : "",
|
||||
__entry->flags & EP_RING_FULL ? "RING FULL |" : "",
|
||||
__entry->flags & EP_CLAIMED ? "CLAIMED " : "",
|
||||
__entry->dir ? "IN" : "OUT"
|
||||
)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdns3_log_ep, cdns3_gadget_ep_enable,
|
||||
TP_PROTO(struct cdns3_endpoint *priv_ep),
|
||||
TP_ARGS(priv_ep)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdns3_log_ep, cdns3_gadget_ep_disable,
|
||||
TP_PROTO(struct cdns3_endpoint *priv_ep),
|
||||
TP_ARGS(priv_ep)
|
||||
);
|
||||
|
||||
DECLARE_EVENT_CLASS(cdns3_log_request_handled,
|
||||
TP_PROTO(struct cdns3_request *priv_req, int current_index,
|
||||
int handled),
|
||||
TP_ARGS(priv_req, current_index, handled),
|
||||
TP_STRUCT__entry(
|
||||
__field(struct cdns3_request *, priv_req)
|
||||
__field(unsigned int, dma_position)
|
||||
__field(unsigned int, handled)
|
||||
__field(unsigned int, dequeue_idx)
|
||||
__field(unsigned int, enqueue_idx)
|
||||
__field(unsigned int, start_trb)
|
||||
__field(unsigned int, end_trb)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__entry->priv_req = priv_req;
|
||||
__entry->dma_position = current_index;
|
||||
__entry->handled = handled;
|
||||
__entry->dequeue_idx = priv_req->priv_ep->dequeue;
|
||||
__entry->enqueue_idx = priv_req->priv_ep->enqueue;
|
||||
__entry->start_trb = priv_req->start_trb;
|
||||
__entry->end_trb = priv_req->end_trb;
|
||||
),
|
||||
TP_printk("Req: %p %s, DMA pos: %d, ep deq: %d, ep enq: %d,"
|
||||
" start trb: %d, end trb: %d",
|
||||
__entry->priv_req,
|
||||
__entry->handled ? "handled" : "not handled",
|
||||
__entry->dma_position, __entry->dequeue_idx,
|
||||
__entry->enqueue_idx, __entry->start_trb,
|
||||
__entry->end_trb
|
||||
)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdns3_log_request_handled, cdns3_request_handled,
|
||||
TP_PROTO(struct cdns3_request *priv_req, int current_index,
|
||||
int handled),
|
||||
TP_ARGS(priv_req, current_index, handled)
|
||||
);
|
||||
#endif /* __LINUX_CDNS3_TRACE */
|
||||
|
||||
/* this part must be outside header guard */
|
||||
|
||||
#undef TRACE_INCLUDE_PATH
|
||||
#define TRACE_INCLUDE_PATH .
|
||||
|
||||
#undef TRACE_INCLUDE_FILE
|
||||
#define TRACE_INCLUDE_FILE cdns3-trace
|
||||
|
||||
#include <trace/define_trace.h>
|
583
drivers/usb/cdns3/cdnsp-debug.h
Normal file
583
drivers/usb/cdns3/cdnsp-debug.h
Normal file
|
@ -0,0 +1,583 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Cadence CDNSP DRD Driver.
|
||||
*
|
||||
* Copyright (C) 2020 Cadence.
|
||||
*
|
||||
* Author: Pawel Laszczak <pawell@cadence.com>
|
||||
*
|
||||
*/
|
||||
#ifndef __LINUX_CDNSP_DEBUG
|
||||
#define __LINUX_CDNSP_DEBUG
|
||||
|
||||
static inline const char *cdnsp_trb_comp_code_string(u8 status)
|
||||
{
|
||||
switch (status) {
|
||||
case COMP_INVALID:
|
||||
return "Invalid";
|
||||
case COMP_SUCCESS:
|
||||
return "Success";
|
||||
case COMP_DATA_BUFFER_ERROR:
|
||||
return "Data Buffer Error";
|
||||
case COMP_BABBLE_DETECTED_ERROR:
|
||||
return "Babble Detected";
|
||||
case COMP_TRB_ERROR:
|
||||
return "TRB Error";
|
||||
case COMP_RESOURCE_ERROR:
|
||||
return "Resource Error";
|
||||
case COMP_NO_SLOTS_AVAILABLE_ERROR:
|
||||
return "No Slots Available Error";
|
||||
case COMP_INVALID_STREAM_TYPE_ERROR:
|
||||
return "Invalid Stream Type Error";
|
||||
case COMP_SLOT_NOT_ENABLED_ERROR:
|
||||
return "Slot Not Enabled Error";
|
||||
case COMP_ENDPOINT_NOT_ENABLED_ERROR:
|
||||
return "Endpoint Not Enabled Error";
|
||||
case COMP_SHORT_PACKET:
|
||||
return "Short Packet";
|
||||
case COMP_RING_UNDERRUN:
|
||||
return "Ring Underrun";
|
||||
case COMP_RING_OVERRUN:
|
||||
return "Ring Overrun";
|
||||
case COMP_VF_EVENT_RING_FULL_ERROR:
|
||||
return "VF Event Ring Full Error";
|
||||
case COMP_PARAMETER_ERROR:
|
||||
return "Parameter Error";
|
||||
case COMP_CONTEXT_STATE_ERROR:
|
||||
return "Context State Error";
|
||||
case COMP_EVENT_RING_FULL_ERROR:
|
||||
return "Event Ring Full Error";
|
||||
case COMP_INCOMPATIBLE_DEVICE_ERROR:
|
||||
return "Incompatible Device Error";
|
||||
case COMP_MISSED_SERVICE_ERROR:
|
||||
return "Missed Service Error";
|
||||
case COMP_COMMAND_RING_STOPPED:
|
||||
return "Command Ring Stopped";
|
||||
case COMP_COMMAND_ABORTED:
|
||||
return "Command Aborted";
|
||||
case COMP_STOPPED:
|
||||
return "Stopped";
|
||||
case COMP_STOPPED_LENGTH_INVALID:
|
||||
return "Stopped - Length Invalid";
|
||||
case COMP_STOPPED_SHORT_PACKET:
|
||||
return "Stopped - Short Packet";
|
||||
case COMP_MAX_EXIT_LATENCY_TOO_LARGE_ERROR:
|
||||
return "Max Exit Latency Too Large Error";
|
||||
case COMP_ISOCH_BUFFER_OVERRUN:
|
||||
return "Isoch Buffer Overrun";
|
||||
case COMP_EVENT_LOST_ERROR:
|
||||
return "Event Lost Error";
|
||||
case COMP_UNDEFINED_ERROR:
|
||||
return "Undefined Error";
|
||||
case COMP_INVALID_STREAM_ID_ERROR:
|
||||
return "Invalid Stream ID Error";
|
||||
default:
|
||||
return "Unknown!!";
|
||||
}
|
||||
}
|
||||
|
||||
static inline const char *cdnsp_trb_type_string(u8 type)
|
||||
{
|
||||
switch (type) {
|
||||
case TRB_NORMAL:
|
||||
return "Normal";
|
||||
case TRB_SETUP:
|
||||
return "Setup Stage";
|
||||
case TRB_DATA:
|
||||
return "Data Stage";
|
||||
case TRB_STATUS:
|
||||
return "Status Stage";
|
||||
case TRB_ISOC:
|
||||
return "Isoch";
|
||||
case TRB_LINK:
|
||||
return "Link";
|
||||
case TRB_EVENT_DATA:
|
||||
return "Event Data";
|
||||
case TRB_TR_NOOP:
|
||||
return "No-Op";
|
||||
case TRB_ENABLE_SLOT:
|
||||
return "Enable Slot Command";
|
||||
case TRB_DISABLE_SLOT:
|
||||
return "Disable Slot Command";
|
||||
case TRB_ADDR_DEV:
|
||||
return "Address Device Command";
|
||||
case TRB_CONFIG_EP:
|
||||
return "Configure Endpoint Command";
|
||||
case TRB_EVAL_CONTEXT:
|
||||
return "Evaluate Context Command";
|
||||
case TRB_RESET_EP:
|
||||
return "Reset Endpoint Command";
|
||||
case TRB_STOP_RING:
|
||||
return "Stop Ring Command";
|
||||
case TRB_SET_DEQ:
|
||||
return "Set TR Dequeue Pointer Command";
|
||||
case TRB_RESET_DEV:
|
||||
return "Reset Device Command";
|
||||
case TRB_FORCE_HEADER:
|
||||
return "Force Header Command";
|
||||
case TRB_CMD_NOOP:
|
||||
return "No-Op Command";
|
||||
case TRB_TRANSFER:
|
||||
return "Transfer Event";
|
||||
case TRB_COMPLETION:
|
||||
return "Command Completion Event";
|
||||
case TRB_PORT_STATUS:
|
||||
return "Port Status Change Event";
|
||||
case TRB_HC_EVENT:
|
||||
return "Device Controller Event";
|
||||
case TRB_MFINDEX_WRAP:
|
||||
return "MFINDEX Wrap Event";
|
||||
case TRB_ENDPOINT_NRDY:
|
||||
return "Endpoint Not ready";
|
||||
case TRB_HALT_ENDPOINT:
|
||||
return "Halt Endpoint";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
static inline const char *cdnsp_ring_type_string(enum cdnsp_ring_type type)
|
||||
{
|
||||
switch (type) {
|
||||
case TYPE_CTRL:
|
||||
return "CTRL";
|
||||
case TYPE_ISOC:
|
||||
return "ISOC";
|
||||
case TYPE_BULK:
|
||||
return "BULK";
|
||||
case TYPE_INTR:
|
||||
return "INTR";
|
||||
case TYPE_STREAM:
|
||||
return "STREAM";
|
||||
case TYPE_COMMAND:
|
||||
return "CMD";
|
||||
case TYPE_EVENT:
|
||||
return "EVENT";
|
||||
}
|
||||
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
static inline char *cdnsp_slot_state_string(u32 state)
|
||||
{
|
||||
switch (state) {
|
||||
case SLOT_STATE_ENABLED:
|
||||
return "enabled/disabled";
|
||||
case SLOT_STATE_DEFAULT:
|
||||
return "default";
|
||||
case SLOT_STATE_ADDRESSED:
|
||||
return "addressed";
|
||||
case SLOT_STATE_CONFIGURED:
|
||||
return "configured";
|
||||
default:
|
||||
return "reserved";
|
||||
}
|
||||
}
|
||||
|
||||
static inline const char *cdnsp_decode_trb(char *str, size_t size, u32 field0,
|
||||
u32 field1, u32 field2, u32 field3)
|
||||
{
|
||||
int ep_id = TRB_TO_EP_INDEX(field3) - 1;
|
||||
int type = TRB_FIELD_TO_TYPE(field3);
|
||||
unsigned int ep_num;
|
||||
int ret;
|
||||
u32 temp;
|
||||
|
||||
ep_num = DIV_ROUND_UP(ep_id, 2);
|
||||
|
||||
switch (type) {
|
||||
case TRB_LINK:
|
||||
ret = scnprintf(str, size,
|
||||
"LINK %08x%08x intr %ld type '%s' flags %c:%c:%c:%c",
|
||||
field1, field0, GET_INTR_TARGET(field2),
|
||||
cdnsp_trb_type_string(type),
|
||||
field3 & TRB_IOC ? 'I' : 'i',
|
||||
field3 & TRB_CHAIN ? 'C' : 'c',
|
||||
field3 & TRB_TC ? 'T' : 't',
|
||||
field3 & TRB_CYCLE ? 'C' : 'c');
|
||||
break;
|
||||
case TRB_TRANSFER:
|
||||
case TRB_COMPLETION:
|
||||
case TRB_PORT_STATUS:
|
||||
case TRB_HC_EVENT:
|
||||
ret = scnprintf(str, size,
|
||||
"ep%d%s(%d) type '%s' TRB %08x%08x status '%s'"
|
||||
" len %ld slot %ld flags %c:%c",
|
||||
ep_num, ep_id % 2 ? "out" : "in",
|
||||
TRB_TO_EP_INDEX(field3),
|
||||
cdnsp_trb_type_string(type), field1, field0,
|
||||
cdnsp_trb_comp_code_string(GET_COMP_CODE(field2)),
|
||||
EVENT_TRB_LEN(field2), TRB_TO_SLOT_ID(field3),
|
||||
field3 & EVENT_DATA ? 'E' : 'e',
|
||||
field3 & TRB_CYCLE ? 'C' : 'c');
|
||||
break;
|
||||
case TRB_MFINDEX_WRAP:
|
||||
ret = scnprintf(str, size, "%s: flags %c",
|
||||
cdnsp_trb_type_string(type),
|
||||
field3 & TRB_CYCLE ? 'C' : 'c');
|
||||
break;
|
||||
case TRB_SETUP:
|
||||
ret = scnprintf(str, size,
|
||||
"type '%s' bRequestType %02x bRequest %02x "
|
||||
"wValue %02x%02x wIndex %02x%02x wLength %d "
|
||||
"length %ld TD size %ld intr %ld Setup ID %ld "
|
||||
"flags %c:%c:%c",
|
||||
cdnsp_trb_type_string(type),
|
||||
field0 & 0xff,
|
||||
(field0 & 0xff00) >> 8,
|
||||
(field0 & 0xff000000) >> 24,
|
||||
(field0 & 0xff0000) >> 16,
|
||||
(field1 & 0xff00) >> 8,
|
||||
field1 & 0xff,
|
||||
(field1 & 0xff000000) >> 16 |
|
||||
(field1 & 0xff0000) >> 16,
|
||||
TRB_LEN(field2), GET_TD_SIZE(field2),
|
||||
GET_INTR_TARGET(field2),
|
||||
TRB_SETUPID_TO_TYPE(field3),
|
||||
field3 & TRB_IDT ? 'D' : 'd',
|
||||
field3 & TRB_IOC ? 'I' : 'i',
|
||||
field3 & TRB_CYCLE ? 'C' : 'c');
|
||||
break;
|
||||
case TRB_DATA:
|
||||
ret = scnprintf(str, size,
|
||||
"type '%s' Buffer %08x%08x length %ld TD size %ld "
|
||||
"intr %ld flags %c:%c:%c:%c:%c:%c:%c",
|
||||
cdnsp_trb_type_string(type),
|
||||
field1, field0, TRB_LEN(field2),
|
||||
GET_TD_SIZE(field2),
|
||||
GET_INTR_TARGET(field2),
|
||||
field3 & TRB_IDT ? 'D' : 'i',
|
||||
field3 & TRB_IOC ? 'I' : 'i',
|
||||
field3 & TRB_CHAIN ? 'C' : 'c',
|
||||
field3 & TRB_NO_SNOOP ? 'S' : 's',
|
||||
field3 & TRB_ISP ? 'I' : 'i',
|
||||
field3 & TRB_ENT ? 'E' : 'e',
|
||||
field3 & TRB_CYCLE ? 'C' : 'c');
|
||||
break;
|
||||
case TRB_STATUS:
|
||||
ret = scnprintf(str, size,
|
||||
"Buffer %08x%08x length %ld TD size %ld intr"
|
||||
"%ld type '%s' flags %c:%c:%c:%c",
|
||||
field1, field0, TRB_LEN(field2),
|
||||
GET_TD_SIZE(field2),
|
||||
GET_INTR_TARGET(field2),
|
||||
cdnsp_trb_type_string(type),
|
||||
field3 & TRB_IOC ? 'I' : 'i',
|
||||
field3 & TRB_CHAIN ? 'C' : 'c',
|
||||
field3 & TRB_ENT ? 'E' : 'e',
|
||||
field3 & TRB_CYCLE ? 'C' : 'c');
|
||||
break;
|
||||
case TRB_NORMAL:
|
||||
case TRB_ISOC:
|
||||
case TRB_EVENT_DATA:
|
||||
case TRB_TR_NOOP:
|
||||
ret = scnprintf(str, size,
|
||||
"type '%s' Buffer %08x%08x length %ld "
|
||||
"TD size %ld intr %ld "
|
||||
"flags %c:%c:%c:%c:%c:%c:%c:%c:%c",
|
||||
cdnsp_trb_type_string(type),
|
||||
field1, field0, TRB_LEN(field2),
|
||||
GET_TD_SIZE(field2),
|
||||
GET_INTR_TARGET(field2),
|
||||
field3 & TRB_BEI ? 'B' : 'b',
|
||||
field3 & TRB_IDT ? 'T' : 't',
|
||||
field3 & TRB_IOC ? 'I' : 'i',
|
||||
field3 & TRB_CHAIN ? 'C' : 'c',
|
||||
field3 & TRB_NO_SNOOP ? 'S' : 's',
|
||||
field3 & TRB_ISP ? 'I' : 'i',
|
||||
field3 & TRB_ENT ? 'E' : 'e',
|
||||
field3 & TRB_CYCLE ? 'C' : 'c',
|
||||
!(field3 & TRB_EVENT_INVALIDATE) ? 'V' : 'v');
|
||||
break;
|
||||
case TRB_CMD_NOOP:
|
||||
case TRB_ENABLE_SLOT:
|
||||
ret = scnprintf(str, size, "%s: flags %c",
|
||||
cdnsp_trb_type_string(type),
|
||||
field3 & TRB_CYCLE ? 'C' : 'c');
|
||||
break;
|
||||
case TRB_DISABLE_SLOT:
|
||||
ret = scnprintf(str, size, "%s: slot %ld flags %c",
|
||||
cdnsp_trb_type_string(type),
|
||||
TRB_TO_SLOT_ID(field3),
|
||||
field3 & TRB_CYCLE ? 'C' : 'c');
|
||||
break;
|
||||
case TRB_ADDR_DEV:
|
||||
ret = scnprintf(str, size,
|
||||
"%s: ctx %08x%08x slot %ld flags %c:%c",
|
||||
cdnsp_trb_type_string(type), field1, field0,
|
||||
TRB_TO_SLOT_ID(field3),
|
||||
field3 & TRB_BSR ? 'B' : 'b',
|
||||
field3 & TRB_CYCLE ? 'C' : 'c');
|
||||
break;
|
||||
case TRB_CONFIG_EP:
|
||||
ret = scnprintf(str, size,
|
||||
"%s: ctx %08x%08x slot %ld flags %c:%c",
|
||||
cdnsp_trb_type_string(type), field1, field0,
|
||||
TRB_TO_SLOT_ID(field3),
|
||||
field3 & TRB_DC ? 'D' : 'd',
|
||||
field3 & TRB_CYCLE ? 'C' : 'c');
|
||||
break;
|
||||
case TRB_EVAL_CONTEXT:
|
||||
ret = scnprintf(str, size,
|
||||
"%s: ctx %08x%08x slot %ld flags %c",
|
||||
cdnsp_trb_type_string(type), field1, field0,
|
||||
TRB_TO_SLOT_ID(field3),
|
||||
field3 & TRB_CYCLE ? 'C' : 'c');
|
||||
break;
|
||||
case TRB_RESET_EP:
|
||||
case TRB_HALT_ENDPOINT:
|
||||
ret = scnprintf(str, size,
|
||||
"%s: ep%d%s(%d) ctx %08x%08x slot %ld flags %c",
|
||||
cdnsp_trb_type_string(type),
|
||||
ep_num, ep_id % 2 ? "out" : "in",
|
||||
TRB_TO_EP_INDEX(field3), field1, field0,
|
||||
TRB_TO_SLOT_ID(field3),
|
||||
field3 & TRB_CYCLE ? 'C' : 'c');
|
||||
break;
|
||||
case TRB_STOP_RING:
|
||||
ret = scnprintf(str, size,
|
||||
"%s: ep%d%s(%d) slot %ld sp %d flags %c",
|
||||
cdnsp_trb_type_string(type),
|
||||
ep_num, ep_id % 2 ? "out" : "in",
|
||||
TRB_TO_EP_INDEX(field3),
|
||||
TRB_TO_SLOT_ID(field3),
|
||||
TRB_TO_SUSPEND_PORT(field3),
|
||||
field3 & TRB_CYCLE ? 'C' : 'c');
|
||||
break;
|
||||
case TRB_SET_DEQ:
|
||||
ret = scnprintf(str, size,
|
||||
"%s: ep%d%s(%d) deq %08x%08x stream %ld slot %ld flags %c",
|
||||
cdnsp_trb_type_string(type),
|
||||
ep_num, ep_id % 2 ? "out" : "in",
|
||||
TRB_TO_EP_INDEX(field3), field1, field0,
|
||||
TRB_TO_STREAM_ID(field2),
|
||||
TRB_TO_SLOT_ID(field3),
|
||||
field3 & TRB_CYCLE ? 'C' : 'c');
|
||||
break;
|
||||
case TRB_RESET_DEV:
|
||||
ret = scnprintf(str, size, "%s: slot %ld flags %c",
|
||||
cdnsp_trb_type_string(type),
|
||||
TRB_TO_SLOT_ID(field3),
|
||||
field3 & TRB_CYCLE ? 'C' : 'c');
|
||||
break;
|
||||
case TRB_ENDPOINT_NRDY:
|
||||
temp = TRB_TO_HOST_STREAM(field2);
|
||||
|
||||
ret = scnprintf(str, size,
|
||||
"%s: ep%d%s(%d) H_SID %x%s%s D_SID %lx flags %c:%c",
|
||||
cdnsp_trb_type_string(type),
|
||||
ep_num, ep_id % 2 ? "out" : "in",
|
||||
TRB_TO_EP_INDEX(field3), temp,
|
||||
temp == STREAM_PRIME_ACK ? "(PRIME)" : "",
|
||||
temp == STREAM_REJECTED ? "(REJECTED)" : "",
|
||||
TRB_TO_DEV_STREAM(field0),
|
||||
field3 & TRB_STAT ? 'S' : 's',
|
||||
field3 & TRB_CYCLE ? 'C' : 'c');
|
||||
break;
|
||||
default:
|
||||
ret = scnprintf(str, size,
|
||||
"type '%s' -> raw %08x %08x %08x %08x",
|
||||
cdnsp_trb_type_string(type),
|
||||
field0, field1, field2, field3);
|
||||
}
|
||||
|
||||
if (ret == size - 1)
|
||||
pr_info("CDNSP: buffer may be truncated.\n");
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
static inline const char *cdnsp_decode_slot_context(u32 info, u32 info2,
|
||||
u32 int_target, u32 state)
|
||||
{
|
||||
static char str[1024];
|
||||
int ret = 0;
|
||||
u32 speed;
|
||||
char *s;
|
||||
|
||||
speed = info & DEV_SPEED;
|
||||
|
||||
switch (speed) {
|
||||
case SLOT_SPEED_FS:
|
||||
s = "full-speed";
|
||||
break;
|
||||
case SLOT_SPEED_HS:
|
||||
s = "high-speed";
|
||||
break;
|
||||
case SLOT_SPEED_SS:
|
||||
s = "super-speed";
|
||||
break;
|
||||
case SLOT_SPEED_SSP:
|
||||
s = "super-speed plus";
|
||||
break;
|
||||
default:
|
||||
s = "UNKNOWN speed";
|
||||
}
|
||||
|
||||
ret = sprintf(str, "%s Ctx Entries %d",
|
||||
s, (info & LAST_CTX_MASK) >> 27);
|
||||
|
||||
ret += sprintf(str + ret, " [Intr %ld] Addr %ld State %s",
|
||||
GET_INTR_TARGET(int_target), state & DEV_ADDR_MASK,
|
||||
cdnsp_slot_state_string(GET_SLOT_STATE(state)));
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
static inline const char *cdnsp_portsc_link_state_string(u32 portsc)
|
||||
{
|
||||
switch (portsc & PORT_PLS_MASK) {
|
||||
case XDEV_U0:
|
||||
return "U0";
|
||||
case XDEV_U1:
|
||||
return "U1";
|
||||
case XDEV_U2:
|
||||
return "U2";
|
||||
case XDEV_U3:
|
||||
return "U3";
|
||||
case XDEV_DISABLED:
|
||||
return "Disabled";
|
||||
case XDEV_RXDETECT:
|
||||
return "RxDetect";
|
||||
case XDEV_INACTIVE:
|
||||
return "Inactive";
|
||||
case XDEV_POLLING:
|
||||
return "Polling";
|
||||
case XDEV_RECOVERY:
|
||||
return "Recovery";
|
||||
case XDEV_HOT_RESET:
|
||||
return "Hot Reset";
|
||||
case XDEV_COMP_MODE:
|
||||
return "Compliance mode";
|
||||
case XDEV_TEST_MODE:
|
||||
return "Test mode";
|
||||
case XDEV_RESUME:
|
||||
return "Resume";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
static inline const char *cdnsp_decode_portsc(char *str, size_t size,
|
||||
u32 portsc)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = scnprintf(str, size, "%s %s %s Link:%s PortSpeed:%d ",
|
||||
portsc & PORT_POWER ? "Powered" : "Powered-off",
|
||||
portsc & PORT_CONNECT ? "Connected" : "Not-connected",
|
||||
portsc & PORT_PED ? "Enabled" : "Disabled",
|
||||
cdnsp_portsc_link_state_string(portsc),
|
||||
DEV_PORT_SPEED(portsc));
|
||||
|
||||
if (portsc & PORT_RESET)
|
||||
ret += scnprintf(str + ret, size - ret, "In-Reset ");
|
||||
|
||||
ret += scnprintf(str + ret, size - ret, "Change: ");
|
||||
if (portsc & PORT_CSC)
|
||||
ret += scnprintf(str + ret, size - ret, "CSC ");
|
||||
if (portsc & PORT_WRC)
|
||||
ret += scnprintf(str + ret, size - ret, "WRC ");
|
||||
if (portsc & PORT_RC)
|
||||
ret += scnprintf(str + ret, size - ret, "PRC ");
|
||||
if (portsc & PORT_PLC)
|
||||
ret += scnprintf(str + ret, size - ret, "PLC ");
|
||||
if (portsc & PORT_CEC)
|
||||
ret += scnprintf(str + ret, size - ret, "CEC ");
|
||||
ret += scnprintf(str + ret, size - ret, "Wake: ");
|
||||
if (portsc & PORT_WKCONN_E)
|
||||
ret += scnprintf(str + ret, size - ret, "WCE ");
|
||||
if (portsc & PORT_WKDISC_E)
|
||||
ret += scnprintf(str + ret, size - ret, "WDE ");
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
static inline const char *cdnsp_ep_state_string(u8 state)
|
||||
{
|
||||
switch (state) {
|
||||
case EP_STATE_DISABLED:
|
||||
return "disabled";
|
||||
case EP_STATE_RUNNING:
|
||||
return "running";
|
||||
case EP_STATE_HALTED:
|
||||
return "halted";
|
||||
case EP_STATE_STOPPED:
|
||||
return "stopped";
|
||||
case EP_STATE_ERROR:
|
||||
return "error";
|
||||
default:
|
||||
return "INVALID";
|
||||
}
|
||||
}
|
||||
|
||||
static inline const char *cdnsp_ep_type_string(u8 type)
|
||||
{
|
||||
switch (type) {
|
||||
case ISOC_OUT_EP:
|
||||
return "Isoc OUT";
|
||||
case BULK_OUT_EP:
|
||||
return "Bulk OUT";
|
||||
case INT_OUT_EP:
|
||||
return "Int OUT";
|
||||
case CTRL_EP:
|
||||
return "Ctrl";
|
||||
case ISOC_IN_EP:
|
||||
return "Isoc IN";
|
||||
case BULK_IN_EP:
|
||||
return "Bulk IN";
|
||||
case INT_IN_EP:
|
||||
return "Int IN";
|
||||
default:
|
||||
return "INVALID";
|
||||
}
|
||||
}
|
||||
|
||||
static inline const char *cdnsp_decode_ep_context(char *str, size_t size,
|
||||
u32 info, u32 info2,
|
||||
u64 deq, u32 tx_info)
|
||||
{
|
||||
u8 max_pstr, ep_state, interval, ep_type, burst, cerr, mult;
|
||||
bool lsa, hid;
|
||||
u16 maxp, avg;
|
||||
u32 esit;
|
||||
int ret;
|
||||
|
||||
esit = CTX_TO_MAX_ESIT_PAYLOAD_HI(info) << 16 |
|
||||
CTX_TO_MAX_ESIT_PAYLOAD_LO(tx_info);
|
||||
|
||||
ep_state = info & EP_STATE_MASK;
|
||||
max_pstr = CTX_TO_EP_MAXPSTREAMS(info);
|
||||
interval = CTX_TO_EP_INTERVAL(info);
|
||||
mult = CTX_TO_EP_MULT(info) + 1;
|
||||
lsa = !!(info & EP_HAS_LSA);
|
||||
|
||||
cerr = (info2 & (3 << 1)) >> 1;
|
||||
ep_type = CTX_TO_EP_TYPE(info2);
|
||||
hid = !!(info2 & (1 << 7));
|
||||
burst = CTX_TO_MAX_BURST(info2);
|
||||
maxp = MAX_PACKET_DECODED(info2);
|
||||
|
||||
avg = EP_AVG_TRB_LENGTH(tx_info);
|
||||
|
||||
ret = scnprintf(str, size, "State %s mult %d max P. Streams %d %s",
|
||||
cdnsp_ep_state_string(ep_state), mult,
|
||||
max_pstr, lsa ? "LSA " : "");
|
||||
|
||||
ret += scnprintf(str + ret, size - ret,
|
||||
"interval %d us max ESIT payload %d CErr %d ",
|
||||
(1 << interval) * 125, esit, cerr);
|
||||
|
||||
ret += scnprintf(str + ret, size - ret,
|
||||
"Type %s %sburst %d maxp %d deq %016llx ",
|
||||
cdnsp_ep_type_string(ep_type), hid ? "HID" : "",
|
||||
burst, maxp, deq);
|
||||
|
||||
ret += scnprintf(str + ret, size - ret, "avg trb len %d", avg);
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
#endif /*__LINUX_CDNSP_DEBUG*/
|
471
drivers/usb/cdns3/cdnsp-ep0.c
Normal file
471
drivers/usb/cdns3/cdnsp-ep0.c
Normal file
|
@ -0,0 +1,471 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Cadence CDNSP DRD Driver.
|
||||
*
|
||||
* Copyright (C) 2020 Cadence.
|
||||
*
|
||||
* Author: Pawel Laszczak <pawell@cadence.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/usb/composite.h>
|
||||
#include <linux/usb/gadget.h>
|
||||
#include <linux/list.h>
|
||||
|
||||
#include "cdnsp-gadget.h"
|
||||
#include "cdnsp-trace.h"
|
||||
|
||||
static void cdnsp_ep0_stall(struct cdnsp_device *pdev)
|
||||
{
|
||||
struct cdnsp_request *preq;
|
||||
struct cdnsp_ep *pep;
|
||||
|
||||
pep = &pdev->eps[0];
|
||||
preq = next_request(&pep->pending_list);
|
||||
|
||||
if (pdev->three_stage_setup) {
|
||||
cdnsp_halt_endpoint(pdev, pep, true);
|
||||
|
||||
if (preq)
|
||||
cdnsp_gadget_giveback(pep, preq, -ECONNRESET);
|
||||
} else {
|
||||
pep->ep_state |= EP0_HALTED_STATUS;
|
||||
|
||||
if (preq)
|
||||
list_del(&preq->list);
|
||||
|
||||
cdnsp_status_stage(pdev);
|
||||
}
|
||||
}
|
||||
|
||||
static int cdnsp_ep0_delegate_req(struct cdnsp_device *pdev,
|
||||
struct usb_ctrlrequest *ctrl)
|
||||
{
|
||||
int ret;
|
||||
|
||||
spin_unlock(&pdev->lock);
|
||||
ret = pdev->gadget_driver->setup(&pdev->gadget, ctrl);
|
||||
spin_lock(&pdev->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cdnsp_ep0_set_config(struct cdnsp_device *pdev,
|
||||
struct usb_ctrlrequest *ctrl)
|
||||
{
|
||||
enum usb_device_state state = pdev->gadget.state;
|
||||
u32 cfg;
|
||||
int ret;
|
||||
|
||||
cfg = le16_to_cpu(ctrl->wValue);
|
||||
|
||||
switch (state) {
|
||||
case USB_STATE_ADDRESS:
|
||||
trace_cdnsp_ep0_set_config("from Address state");
|
||||
break;
|
||||
case USB_STATE_CONFIGURED:
|
||||
trace_cdnsp_ep0_set_config("from Configured state");
|
||||
break;
|
||||
default:
|
||||
dev_err(pdev->dev, "Set Configuration - bad device state\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = cdnsp_ep0_delegate_req(pdev, ctrl);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!cfg)
|
||||
usb_gadget_set_state(&pdev->gadget, USB_STATE_ADDRESS);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cdnsp_ep0_set_address(struct cdnsp_device *pdev,
|
||||
struct usb_ctrlrequest *ctrl)
|
||||
{
|
||||
enum usb_device_state state = pdev->gadget.state;
|
||||
struct cdnsp_slot_ctx *slot_ctx;
|
||||
unsigned int slot_state;
|
||||
int ret;
|
||||
u32 addr;
|
||||
|
||||
addr = le16_to_cpu(ctrl->wValue);
|
||||
|
||||
if (addr > 127) {
|
||||
dev_err(pdev->dev, "Invalid device address %d\n", addr);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
slot_ctx = cdnsp_get_slot_ctx(&pdev->out_ctx);
|
||||
|
||||
if (state == USB_STATE_CONFIGURED) {
|
||||
dev_err(pdev->dev, "Can't Set Address from Configured State\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
pdev->device_address = le16_to_cpu(ctrl->wValue);
|
||||
|
||||
slot_ctx = cdnsp_get_slot_ctx(&pdev->out_ctx);
|
||||
slot_state = GET_SLOT_STATE(le32_to_cpu(slot_ctx->dev_state));
|
||||
if (slot_state == SLOT_STATE_ADDRESSED)
|
||||
cdnsp_reset_device(pdev);
|
||||
|
||||
/*set device address*/
|
||||
ret = cdnsp_setup_device(pdev, SETUP_CONTEXT_ADDRESS);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (addr)
|
||||
usb_gadget_set_state(&pdev->gadget, USB_STATE_ADDRESS);
|
||||
else
|
||||
usb_gadget_set_state(&pdev->gadget, USB_STATE_DEFAULT);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cdnsp_status_stage(struct cdnsp_device *pdev)
|
||||
{
|
||||
pdev->ep0_stage = CDNSP_STATUS_STAGE;
|
||||
pdev->ep0_preq.request.length = 0;
|
||||
|
||||
return cdnsp_ep_enqueue(pdev->ep0_preq.pep, &pdev->ep0_preq);
|
||||
}
|
||||
|
||||
static int cdnsp_w_index_to_ep_index(u16 wIndex)
|
||||
{
|
||||
if (!(wIndex & USB_ENDPOINT_NUMBER_MASK))
|
||||
return 0;
|
||||
|
||||
return ((wIndex & USB_ENDPOINT_NUMBER_MASK) * 2) +
|
||||
(wIndex & USB_ENDPOINT_DIR_MASK ? 1 : 0) - 1;
|
||||
}
|
||||
|
||||
static int cdnsp_ep0_handle_status(struct cdnsp_device *pdev,
|
||||
struct usb_ctrlrequest *ctrl)
|
||||
{
|
||||
struct cdnsp_ep *pep;
|
||||
__le16 *response;
|
||||
int ep_sts = 0;
|
||||
u16 status = 0;
|
||||
u32 recipient;
|
||||
|
||||
recipient = ctrl->bRequestType & USB_RECIP_MASK;
|
||||
|
||||
switch (recipient) {
|
||||
case USB_RECIP_DEVICE:
|
||||
status = pdev->gadget.is_selfpowered;
|
||||
status |= pdev->may_wakeup << USB_DEVICE_REMOTE_WAKEUP;
|
||||
|
||||
if (pdev->gadget.speed >= USB_SPEED_SUPER) {
|
||||
status |= pdev->u1_allowed << USB_DEV_STAT_U1_ENABLED;
|
||||
status |= pdev->u2_allowed << USB_DEV_STAT_U2_ENABLED;
|
||||
}
|
||||
break;
|
||||
case USB_RECIP_INTERFACE:
|
||||
/*
|
||||
* Function Remote Wake Capable D0
|
||||
* Function Remote Wakeup D1
|
||||
*/
|
||||
return cdnsp_ep0_delegate_req(pdev, ctrl);
|
||||
case USB_RECIP_ENDPOINT:
|
||||
ep_sts = cdnsp_w_index_to_ep_index(le16_to_cpu(ctrl->wIndex));
|
||||
pep = &pdev->eps[ep_sts];
|
||||
ep_sts = GET_EP_CTX_STATE(pep->out_ctx);
|
||||
|
||||
/* check if endpoint is stalled */
|
||||
if (ep_sts == EP_STATE_HALTED)
|
||||
status = BIT(USB_ENDPOINT_HALT);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
response = (__le16 *)pdev->setup_buf;
|
||||
*response = cpu_to_le16(status);
|
||||
|
||||
pdev->ep0_preq.request.length = sizeof(*response);
|
||||
pdev->ep0_preq.request.buf = pdev->setup_buf;
|
||||
|
||||
return cdnsp_ep_enqueue(pdev->ep0_preq.pep, &pdev->ep0_preq);
|
||||
}
|
||||
|
||||
static void cdnsp_enter_test_mode(struct cdnsp_device *pdev)
|
||||
{
|
||||
u32 temp;
|
||||
|
||||
temp = readl(&pdev->active_port->regs->portpmsc) & ~GENMASK(31, 28);
|
||||
temp |= PORT_TEST_MODE(pdev->test_mode);
|
||||
writel(temp, &pdev->active_port->regs->portpmsc);
|
||||
}
|
||||
|
||||
static int cdnsp_ep0_handle_feature_device(struct cdnsp_device *pdev,
|
||||
struct usb_ctrlrequest *ctrl,
|
||||
int set)
|
||||
{
|
||||
enum usb_device_state state;
|
||||
enum usb_device_speed speed;
|
||||
u16 tmode;
|
||||
|
||||
state = pdev->gadget.state;
|
||||
speed = pdev->gadget.speed;
|
||||
|
||||
switch (le16_to_cpu(ctrl->wValue)) {
|
||||
case USB_DEVICE_REMOTE_WAKEUP:
|
||||
pdev->may_wakeup = !!set;
|
||||
trace_cdnsp_may_wakeup(set);
|
||||
break;
|
||||
case USB_DEVICE_U1_ENABLE:
|
||||
if (state != USB_STATE_CONFIGURED || speed < USB_SPEED_SUPER)
|
||||
return -EINVAL;
|
||||
|
||||
pdev->u1_allowed = !!set;
|
||||
trace_cdnsp_u1(set);
|
||||
break;
|
||||
case USB_DEVICE_U2_ENABLE:
|
||||
if (state != USB_STATE_CONFIGURED || speed < USB_SPEED_SUPER)
|
||||
return -EINVAL;
|
||||
|
||||
pdev->u2_allowed = !!set;
|
||||
trace_cdnsp_u2(set);
|
||||
break;
|
||||
case USB_DEVICE_LTM_ENABLE:
|
||||
return -EINVAL;
|
||||
case USB_DEVICE_TEST_MODE:
|
||||
if (state != USB_STATE_CONFIGURED || speed > USB_SPEED_HIGH)
|
||||
return -EINVAL;
|
||||
|
||||
tmode = le16_to_cpu(ctrl->wIndex);
|
||||
|
||||
if (!set || (tmode & 0xff) != 0)
|
||||
return -EINVAL;
|
||||
|
||||
tmode = tmode >> 8;
|
||||
|
||||
if (tmode > USB_TEST_FORCE_ENABLE || tmode < USB_TEST_J)
|
||||
return -EINVAL;
|
||||
|
||||
pdev->test_mode = tmode;
|
||||
|
||||
/*
|
||||
* Test mode must be set before Status Stage but controller
|
||||
* will start testing sequence after Status Stage.
|
||||
*/
|
||||
cdnsp_enter_test_mode(pdev);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cdnsp_ep0_handle_feature_intf(struct cdnsp_device *pdev,
|
||||
struct usb_ctrlrequest *ctrl,
|
||||
int set)
|
||||
{
|
||||
u16 wValue, wIndex;
|
||||
int ret;
|
||||
|
||||
wValue = le16_to_cpu(ctrl->wValue);
|
||||
wIndex = le16_to_cpu(ctrl->wIndex);
|
||||
|
||||
switch (wValue) {
|
||||
case USB_INTRF_FUNC_SUSPEND:
|
||||
ret = cdnsp_ep0_delegate_req(pdev, ctrl);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* Remote wakeup is enabled when any function within a device
|
||||
* is enabled for function remote wakeup.
|
||||
*/
|
||||
if (wIndex & USB_INTRF_FUNC_SUSPEND_RW)
|
||||
pdev->may_wakeup++;
|
||||
else
|
||||
if (pdev->may_wakeup > 0)
|
||||
pdev->may_wakeup--;
|
||||
|
||||
return 0;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cdnsp_ep0_handle_feature_endpoint(struct cdnsp_device *pdev,
|
||||
struct usb_ctrlrequest *ctrl,
|
||||
int set)
|
||||
{
|
||||
struct cdnsp_ep *pep;
|
||||
u16 wValue;
|
||||
|
||||
wValue = le16_to_cpu(ctrl->wValue);
|
||||
pep = &pdev->eps[cdnsp_w_index_to_ep_index(le16_to_cpu(ctrl->wIndex))];
|
||||
|
||||
switch (wValue) {
|
||||
case USB_ENDPOINT_HALT:
|
||||
if (!set && (pep->ep_state & EP_WEDGE)) {
|
||||
/* Resets Sequence Number */
|
||||
cdnsp_halt_endpoint(pdev, pep, 0);
|
||||
cdnsp_halt_endpoint(pdev, pep, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
return cdnsp_halt_endpoint(pdev, pep, set);
|
||||
default:
|
||||
dev_warn(pdev->dev, "WARN Incorrect wValue %04x\n", wValue);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cdnsp_ep0_handle_feature(struct cdnsp_device *pdev,
|
||||
struct usb_ctrlrequest *ctrl,
|
||||
int set)
|
||||
{
|
||||
switch (ctrl->bRequestType & USB_RECIP_MASK) {
|
||||
case USB_RECIP_DEVICE:
|
||||
return cdnsp_ep0_handle_feature_device(pdev, ctrl, set);
|
||||
case USB_RECIP_INTERFACE:
|
||||
return cdnsp_ep0_handle_feature_intf(pdev, ctrl, set);
|
||||
case USB_RECIP_ENDPOINT:
|
||||
return cdnsp_ep0_handle_feature_endpoint(pdev, ctrl, set);
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int cdnsp_ep0_set_sel(struct cdnsp_device *pdev,
|
||||
struct usb_ctrlrequest *ctrl)
|
||||
{
|
||||
enum usb_device_state state = pdev->gadget.state;
|
||||
u16 wLength;
|
||||
|
||||
if (state == USB_STATE_DEFAULT)
|
||||
return -EINVAL;
|
||||
|
||||
wLength = le16_to_cpu(ctrl->wLength);
|
||||
|
||||
if (wLength != 6) {
|
||||
dev_err(pdev->dev, "Set SEL should be 6 bytes, got %d\n",
|
||||
wLength);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* To handle Set SEL we need to receive 6 bytes from Host. So let's
|
||||
* queue a usb_request for 6 bytes.
|
||||
*/
|
||||
pdev->ep0_preq.request.length = 6;
|
||||
pdev->ep0_preq.request.buf = pdev->setup_buf;
|
||||
|
||||
return cdnsp_ep_enqueue(pdev->ep0_preq.pep, &pdev->ep0_preq);
|
||||
}
|
||||
|
||||
static int cdnsp_ep0_set_isoch_delay(struct cdnsp_device *pdev,
|
||||
struct usb_ctrlrequest *ctrl)
|
||||
{
|
||||
if (le16_to_cpu(ctrl->wIndex) || le16_to_cpu(ctrl->wLength))
|
||||
return -EINVAL;
|
||||
|
||||
pdev->gadget.isoch_delay = le16_to_cpu(ctrl->wValue);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cdnsp_ep0_std_request(struct cdnsp_device *pdev,
|
||||
struct usb_ctrlrequest *ctrl)
|
||||
{
|
||||
int ret;
|
||||
|
||||
switch (ctrl->bRequest) {
|
||||
case USB_REQ_GET_STATUS:
|
||||
ret = cdnsp_ep0_handle_status(pdev, ctrl);
|
||||
break;
|
||||
case USB_REQ_CLEAR_FEATURE:
|
||||
ret = cdnsp_ep0_handle_feature(pdev, ctrl, 0);
|
||||
break;
|
||||
case USB_REQ_SET_FEATURE:
|
||||
ret = cdnsp_ep0_handle_feature(pdev, ctrl, 1);
|
||||
break;
|
||||
case USB_REQ_SET_ADDRESS:
|
||||
ret = cdnsp_ep0_set_address(pdev, ctrl);
|
||||
break;
|
||||
case USB_REQ_SET_CONFIGURATION:
|
||||
ret = cdnsp_ep0_set_config(pdev, ctrl);
|
||||
break;
|
||||
case USB_REQ_SET_SEL:
|
||||
ret = cdnsp_ep0_set_sel(pdev, ctrl);
|
||||
break;
|
||||
case USB_REQ_SET_ISOCH_DELAY:
|
||||
ret = cdnsp_ep0_set_isoch_delay(pdev, ctrl);
|
||||
break;
|
||||
default:
|
||||
ret = cdnsp_ep0_delegate_req(pdev, ctrl);
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void cdnsp_setup_analyze(struct cdnsp_device *pdev)
|
||||
{
|
||||
struct usb_ctrlrequest *ctrl = &pdev->setup;
|
||||
int ret = -EINVAL;
|
||||
u16 len;
|
||||
|
||||
trace_cdnsp_ctrl_req(ctrl);
|
||||
|
||||
if (!pdev->gadget_driver)
|
||||
goto out;
|
||||
|
||||
if (pdev->gadget.state == USB_STATE_NOTATTACHED) {
|
||||
dev_err(pdev->dev, "ERR: Setup detected in unattached state\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Restore the ep0 to Stopped/Running state. */
|
||||
if (pdev->eps[0].ep_state & EP_HALTED) {
|
||||
trace_cdnsp_ep0_halted("Restore to normal state");
|
||||
cdnsp_halt_endpoint(pdev, &pdev->eps[0], 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Finishing previous SETUP transfer by removing request from
|
||||
* list and informing upper layer
|
||||
*/
|
||||
if (!list_empty(&pdev->eps[0].pending_list)) {
|
||||
struct cdnsp_request *req;
|
||||
|
||||
trace_cdnsp_ep0_request("Remove previous");
|
||||
req = next_request(&pdev->eps[0].pending_list);
|
||||
cdnsp_ep_dequeue(&pdev->eps[0], req);
|
||||
}
|
||||
|
||||
len = le16_to_cpu(ctrl->wLength);
|
||||
if (!len) {
|
||||
pdev->three_stage_setup = false;
|
||||
pdev->ep0_expect_in = false;
|
||||
} else {
|
||||
pdev->three_stage_setup = true;
|
||||
pdev->ep0_expect_in = !!(ctrl->bRequestType & USB_DIR_IN);
|
||||
}
|
||||
|
||||
if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD)
|
||||
ret = cdnsp_ep0_std_request(pdev, ctrl);
|
||||
else
|
||||
ret = cdnsp_ep0_delegate_req(pdev, ctrl);
|
||||
|
||||
if (ret == USB_GADGET_DELAYED_STATUS) {
|
||||
trace_cdnsp_ep0_status_stage("delayed");
|
||||
return;
|
||||
}
|
||||
out:
|
||||
if (ret < 0)
|
||||
cdnsp_ep0_stall(pdev);
|
||||
else if (!len && pdev->ep0_stage != CDNSP_STATUS_STAGE)
|
||||
cdnsp_status_stage(pdev);
|
||||
}
|
2056
drivers/usb/cdns3/cdnsp-gadget.c
Normal file
2056
drivers/usb/cdns3/cdnsp-gadget.c
Normal file
File diff suppressed because it is too large
Load diff
1606
drivers/usb/cdns3/cdnsp-gadget.h
Normal file
1606
drivers/usb/cdns3/cdnsp-gadget.h
Normal file
File diff suppressed because it is too large
Load diff
1337
drivers/usb/cdns3/cdnsp-mem.c
Normal file
1337
drivers/usb/cdns3/cdnsp-mem.c
Normal file
File diff suppressed because it is too large
Load diff
257
drivers/usb/cdns3/cdnsp-pci.c
Normal file
257
drivers/usb/cdns3/cdnsp-pci.c
Normal file
|
@ -0,0 +1,257 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Cadence PCI Glue driver.
|
||||
*
|
||||
* Copyright (C) 2019 Cadence.
|
||||
*
|
||||
* Author: Pawel Laszczak <pawell@cadence.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/pci.h>
|
||||
|
||||
#include "core.h"
|
||||
#include "gadget-export.h"
|
||||
|
||||
#define PCI_BAR_HOST 0
|
||||
#define PCI_BAR_OTG 0
|
||||
#define PCI_BAR_DEV 2
|
||||
|
||||
#define PCI_DEV_FN_HOST_DEVICE 0
|
||||
#define PCI_DEV_FN_OTG 1
|
||||
|
||||
#define PCI_DRIVER_NAME "cdns-pci-usbssp"
|
||||
#define PLAT_DRIVER_NAME "cdns-usbssp"
|
||||
|
||||
#define PCI_DEVICE_ID_CDNS_USB3 0x0100
|
||||
#define PCI_DEVICE_ID_CDNS_UDC 0x0200
|
||||
|
||||
#define PCI_CLASS_SERIAL_USB_CDNS_USB3 (PCI_CLASS_SERIAL_USB << 8 | 0x80)
|
||||
#define PCI_CLASS_SERIAL_USB_CDNS_UDC PCI_CLASS_SERIAL_USB_DEVICE
|
||||
|
||||
#define CHICKEN_APB_TIMEOUT_VALUE 0x1C20
|
||||
|
||||
static struct pci_dev *cdnsp_get_second_fun(struct pci_dev *pdev)
|
||||
{
|
||||
/*
|
||||
* Gets the second function.
|
||||
* Platform has two function. The fist keeps resources for
|
||||
* Host/Device while the secon keeps resources for DRD/OTG.
|
||||
*/
|
||||
if (pdev->device == PCI_DEVICE_ID_CDNS_UDC)
|
||||
return pci_get_device(pdev->vendor, PCI_DEVICE_ID_CDNS_USB3, NULL);
|
||||
if (pdev->device == PCI_DEVICE_ID_CDNS_USB3)
|
||||
return pci_get_device(pdev->vendor, PCI_DEVICE_ID_CDNS_UDC, NULL);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int cdnsp_pci_probe(struct pci_dev *pdev,
|
||||
const struct pci_device_id *id)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct pci_dev *func;
|
||||
struct resource *res;
|
||||
struct cdns *cdnsp;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* For GADGET/HOST PCI (devfn) function number is 0,
|
||||
* for OTG PCI (devfn) function number is 1.
|
||||
*/
|
||||
if (!id || (pdev->devfn != PCI_DEV_FN_HOST_DEVICE &&
|
||||
pdev->devfn != PCI_DEV_FN_OTG))
|
||||
return -EINVAL;
|
||||
|
||||
func = cdnsp_get_second_fun(pdev);
|
||||
if (!func)
|
||||
return -EINVAL;
|
||||
|
||||
if (func->class == PCI_CLASS_SERIAL_USB_XHCI ||
|
||||
pdev->class == PCI_CLASS_SERIAL_USB_XHCI) {
|
||||
ret = -EINVAL;
|
||||
goto put_pci;
|
||||
}
|
||||
|
||||
ret = pcim_enable_device(pdev);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Enabling PCI device has failed %d\n", ret);
|
||||
goto put_pci;
|
||||
}
|
||||
|
||||
pci_set_master(pdev);
|
||||
if (pci_is_enabled(func)) {
|
||||
cdnsp = pci_get_drvdata(func);
|
||||
} else {
|
||||
cdnsp = kzalloc(sizeof(*cdnsp), GFP_KERNEL);
|
||||
if (!cdnsp) {
|
||||
ret = -ENOMEM;
|
||||
goto disable_pci;
|
||||
}
|
||||
}
|
||||
|
||||
/* For GADGET device function number is 0. */
|
||||
if (pdev->devfn == 0) {
|
||||
resource_size_t rsrc_start, rsrc_len;
|
||||
|
||||
/* Function 0: host(BAR_0) + device(BAR_1).*/
|
||||
dev_dbg(dev, "Initialize resources\n");
|
||||
rsrc_start = pci_resource_start(pdev, PCI_BAR_DEV);
|
||||
rsrc_len = pci_resource_len(pdev, PCI_BAR_DEV);
|
||||
res = devm_request_mem_region(dev, rsrc_start, rsrc_len, "dev");
|
||||
if (!res) {
|
||||
dev_dbg(dev, "controller already in use\n");
|
||||
ret = -EBUSY;
|
||||
goto free_cdnsp;
|
||||
}
|
||||
|
||||
cdnsp->dev_regs = devm_ioremap(dev, rsrc_start, rsrc_len);
|
||||
if (!cdnsp->dev_regs) {
|
||||
dev_dbg(dev, "error mapping memory\n");
|
||||
ret = -EFAULT;
|
||||
goto free_cdnsp;
|
||||
}
|
||||
|
||||
cdnsp->dev_irq = pdev->irq;
|
||||
dev_dbg(dev, "USBSS-DEV physical base addr: %pa\n",
|
||||
&rsrc_start);
|
||||
|
||||
res = &cdnsp->xhci_res[0];
|
||||
res->start = pci_resource_start(pdev, PCI_BAR_HOST);
|
||||
res->end = pci_resource_end(pdev, PCI_BAR_HOST);
|
||||
res->name = "xhci";
|
||||
res->flags = IORESOURCE_MEM;
|
||||
dev_dbg(dev, "USBSS-XHCI physical base addr: %pa\n",
|
||||
&res->start);
|
||||
|
||||
/* Interrupt for XHCI, */
|
||||
res = &cdnsp->xhci_res[1];
|
||||
res->start = pdev->irq;
|
||||
res->name = "host";
|
||||
res->flags = IORESOURCE_IRQ;
|
||||
} else {
|
||||
res = &cdnsp->otg_res;
|
||||
res->start = pci_resource_start(pdev, PCI_BAR_OTG);
|
||||
res->end = pci_resource_end(pdev, PCI_BAR_OTG);
|
||||
res->name = "otg";
|
||||
res->flags = IORESOURCE_MEM;
|
||||
dev_dbg(dev, "CDNSP-DRD physical base addr: %pa\n",
|
||||
&res->start);
|
||||
|
||||
/* Interrupt for OTG/DRD. */
|
||||
cdnsp->otg_irq = pdev->irq;
|
||||
}
|
||||
|
||||
/*
|
||||
* Cadence PCI based platform require some longer timeout for APB
|
||||
* to fixes domain clock synchronization issue after resuming
|
||||
* controller from L1 state.
|
||||
*/
|
||||
cdnsp->override_apb_timeout = CHICKEN_APB_TIMEOUT_VALUE;
|
||||
pci_set_drvdata(pdev, cdnsp);
|
||||
|
||||
if (pci_is_enabled(func)) {
|
||||
cdnsp->dev = dev;
|
||||
cdnsp->gadget_init = cdnsp_gadget_init;
|
||||
|
||||
ret = cdns_init(cdnsp);
|
||||
if (ret)
|
||||
goto free_cdnsp;
|
||||
}
|
||||
|
||||
device_wakeup_enable(&pdev->dev);
|
||||
if (pci_dev_run_wake(pdev))
|
||||
pm_runtime_put_noidle(&pdev->dev);
|
||||
|
||||
return 0;
|
||||
|
||||
free_cdnsp:
|
||||
if (!pci_is_enabled(func))
|
||||
kfree(cdnsp);
|
||||
|
||||
disable_pci:
|
||||
pci_disable_device(pdev);
|
||||
|
||||
put_pci:
|
||||
pci_dev_put(func);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void cdnsp_pci_remove(struct pci_dev *pdev)
|
||||
{
|
||||
struct cdns *cdnsp;
|
||||
struct pci_dev *func;
|
||||
|
||||
func = cdnsp_get_second_fun(pdev);
|
||||
cdnsp = (struct cdns *)pci_get_drvdata(pdev);
|
||||
|
||||
if (pci_dev_run_wake(pdev))
|
||||
pm_runtime_get_noresume(&pdev->dev);
|
||||
|
||||
if (pci_is_enabled(func)) {
|
||||
cdns_remove(cdnsp);
|
||||
} else {
|
||||
kfree(cdnsp);
|
||||
}
|
||||
|
||||
pci_dev_put(func);
|
||||
}
|
||||
|
||||
static int __maybe_unused cdnsp_pci_suspend(struct device *dev)
|
||||
{
|
||||
struct cdns *cdns = dev_get_drvdata(dev);
|
||||
|
||||
return cdns_suspend(cdns);
|
||||
}
|
||||
|
||||
static int __maybe_unused cdnsp_pci_resume(struct device *dev)
|
||||
{
|
||||
struct cdns *cdns = dev_get_drvdata(dev);
|
||||
unsigned long flags;
|
||||
int ret;
|
||||
|
||||
spin_lock_irqsave(&cdns->lock, flags);
|
||||
ret = cdns_resume(cdns);
|
||||
spin_unlock_irqrestore(&cdns->lock, flags);
|
||||
cdns_set_active(cdns, 1);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops cdnsp_pci_pm_ops = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(cdnsp_pci_suspend, cdnsp_pci_resume)
|
||||
};
|
||||
|
||||
static const struct pci_device_id cdnsp_pci_ids[] = {
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_CDNS, PCI_DEVICE_ID_CDNS_UDC),
|
||||
.class = PCI_CLASS_SERIAL_USB_CDNS_UDC },
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_CDNS, PCI_DEVICE_ID_CDNS_UDC),
|
||||
.class = PCI_CLASS_SERIAL_USB_CDNS_USB3 },
|
||||
{ PCI_DEVICE(PCI_VENDOR_ID_CDNS, PCI_DEVICE_ID_CDNS_USB3),
|
||||
.class = PCI_CLASS_SERIAL_USB_CDNS_USB3 },
|
||||
{ 0, }
|
||||
};
|
||||
|
||||
static struct pci_driver cdnsp_pci_driver = {
|
||||
.name = "cdnsp-pci",
|
||||
.id_table = cdnsp_pci_ids,
|
||||
.probe = cdnsp_pci_probe,
|
||||
.remove = cdnsp_pci_remove,
|
||||
.driver = {
|
||||
.pm = &cdnsp_pci_pm_ops,
|
||||
}
|
||||
};
|
||||
|
||||
module_pci_driver(cdnsp_pci_driver);
|
||||
MODULE_DEVICE_TABLE(pci, cdnsp_pci_ids);
|
||||
|
||||
MODULE_ALIAS("pci:cdnsp");
|
||||
MODULE_AUTHOR("Pawel Laszczak <pawell@cadence.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_DESCRIPTION("Cadence CDNSP PCI driver");
|
2501
drivers/usb/cdns3/cdnsp-ring.c
Normal file
2501
drivers/usb/cdns3/cdnsp-ring.c
Normal file
File diff suppressed because it is too large
Load diff
12
drivers/usb/cdns3/cdnsp-trace.c
Normal file
12
drivers/usb/cdns3/cdnsp-trace.c
Normal file
|
@ -0,0 +1,12 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Cadence CDNSP DRD Driver.
|
||||
*
|
||||
* Copyright (C) 2020 Cadence.
|
||||
*
|
||||
* Author: Pawel Laszczak <pawell@cadence.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#define CREATE_TRACE_POINTS
|
||||
#include "cdnsp-trace.h"
|
826
drivers/usb/cdns3/cdnsp-trace.h
Normal file
826
drivers/usb/cdns3/cdnsp-trace.h
Normal file
|
@ -0,0 +1,826 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Cadence CDNSP DRD Driver.
|
||||
* Trace support header file
|
||||
*
|
||||
* Copyright (C) 2020 Cadence.
|
||||
*
|
||||
* Author: Pawel Laszczak <pawell@cadence.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#undef TRACE_SYSTEM
|
||||
#define TRACE_SYSTEM cdnsp-dev
|
||||
|
||||
/*
|
||||
* The TRACE_SYSTEM_VAR defaults to TRACE_SYSTEM, but must be a
|
||||
* legitimate C variable. It is not exported to user space.
|
||||
*/
|
||||
#undef TRACE_SYSTEM_VAR
|
||||
#define TRACE_SYSTEM_VAR cdnsp_dev
|
||||
|
||||
#if !defined(__CDNSP_DEV_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
|
||||
#define __CDNSP_DEV_TRACE_H
|
||||
|
||||
#include <linux/tracepoint.h>
|
||||
#include "cdnsp-gadget.h"
|
||||
#include "cdnsp-debug.h"
|
||||
|
||||
/*
|
||||
* There is limitation for single buffer size in TRACEPOINT subsystem.
|
||||
* By default TRACE_BUF_SIZE is 1024, so no all data will be logged.
|
||||
* To show more data this must be increased. In most cases the default
|
||||
* value is sufficient.
|
||||
*/
|
||||
#define CDNSP_MSG_MAX 500
|
||||
|
||||
DECLARE_EVENT_CLASS(cdnsp_log_ep,
|
||||
TP_PROTO(struct cdnsp_ep *pep, u32 stream_id),
|
||||
TP_ARGS(pep, stream_id),
|
||||
TP_STRUCT__entry(
|
||||
__string(name, pep->name)
|
||||
__field(unsigned int, state)
|
||||
__field(u32, stream_id)
|
||||
__field(u8, enabled)
|
||||
__field(unsigned int, num_streams)
|
||||
__field(int, td_count)
|
||||
__field(u8, first_prime_det)
|
||||
__field(u8, drbls_count)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__assign_str(name);
|
||||
__entry->state = pep->ep_state;
|
||||
__entry->stream_id = stream_id;
|
||||
__entry->enabled = pep->ep_state & EP_HAS_STREAMS;
|
||||
__entry->num_streams = pep->stream_info.num_streams;
|
||||
__entry->td_count = pep->stream_info.td_count;
|
||||
__entry->first_prime_det = pep->stream_info.first_prime_det;
|
||||
__entry->drbls_count = pep->stream_info.drbls_count;
|
||||
),
|
||||
TP_printk("%s: SID: %08x, ep state: %x, stream: enabled: %d num %d "
|
||||
"tds %d, first prime: %d drbls %d",
|
||||
__get_str(name), __entry->stream_id, __entry->state,
|
||||
__entry->enabled, __entry->num_streams, __entry->td_count,
|
||||
__entry->first_prime_det, __entry->drbls_count)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_ep, cdnsp_tr_drbl,
|
||||
TP_PROTO(struct cdnsp_ep *pep, u32 stream_id),
|
||||
TP_ARGS(pep, stream_id)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_ep, cdnsp_wait_for_prime,
|
||||
TP_PROTO(struct cdnsp_ep *pep, u32 stream_id),
|
||||
TP_ARGS(pep, stream_id)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_ep, cdnsp_ep_list_empty_with_skip,
|
||||
TP_PROTO(struct cdnsp_ep *pep, u32 stream_id),
|
||||
TP_ARGS(pep, stream_id)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_ep, cdnsp_ep_enable_end,
|
||||
TP_PROTO(struct cdnsp_ep *pep, u32 stream_id),
|
||||
TP_ARGS(pep, stream_id)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_ep, cdnsp_ep_disable_end,
|
||||
TP_PROTO(struct cdnsp_ep *pep, u32 stream_id),
|
||||
TP_ARGS(pep, stream_id)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_ep, cdnsp_ep_busy_try_halt_again,
|
||||
TP_PROTO(struct cdnsp_ep *pep, u32 stream_id),
|
||||
TP_ARGS(pep, stream_id)
|
||||
);
|
||||
|
||||
DECLARE_EVENT_CLASS(cdnsp_log_enable_disable,
|
||||
TP_PROTO(int set),
|
||||
TP_ARGS(set),
|
||||
TP_STRUCT__entry(
|
||||
__field(int, set)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__entry->set = set;
|
||||
),
|
||||
TP_printk("%s", __entry->set ? "enabled" : "disabled")
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_enable_disable, cdnsp_pullup,
|
||||
TP_PROTO(int set),
|
||||
TP_ARGS(set)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_enable_disable, cdnsp_u1,
|
||||
TP_PROTO(int set),
|
||||
TP_ARGS(set)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_enable_disable, cdnsp_u2,
|
||||
TP_PROTO(int set),
|
||||
TP_ARGS(set)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_enable_disable, cdnsp_lpm,
|
||||
TP_PROTO(int set),
|
||||
TP_ARGS(set)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_enable_disable, cdnsp_may_wakeup,
|
||||
TP_PROTO(int set),
|
||||
TP_ARGS(set)
|
||||
);
|
||||
|
||||
DECLARE_EVENT_CLASS(cdnsp_log_simple,
|
||||
TP_PROTO(char *msg),
|
||||
TP_ARGS(msg),
|
||||
TP_STRUCT__entry(
|
||||
__string(text, msg)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__assign_str(text);
|
||||
),
|
||||
TP_printk("%s", __get_str(text))
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_simple, cdnsp_exit,
|
||||
TP_PROTO(char *msg),
|
||||
TP_ARGS(msg)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_simple, cdnsp_init,
|
||||
TP_PROTO(char *msg),
|
||||
TP_ARGS(msg)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_simple, cdnsp_slot_id,
|
||||
TP_PROTO(char *msg),
|
||||
TP_ARGS(msg)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_simple, cdnsp_no_room_on_ring,
|
||||
TP_PROTO(char *msg),
|
||||
TP_ARGS(msg)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_simple, cdnsp_ep0_status_stage,
|
||||
TP_PROTO(char *msg),
|
||||
TP_ARGS(msg)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_simple, cdnsp_ep0_request,
|
||||
TP_PROTO(char *msg),
|
||||
TP_ARGS(msg)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_simple, cdnsp_ep0_set_config,
|
||||
TP_PROTO(char *msg),
|
||||
TP_ARGS(msg)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_simple, cdnsp_ep0_halted,
|
||||
TP_PROTO(char *msg),
|
||||
TP_ARGS(msg)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_simple, cdnsp_ep_halt,
|
||||
TP_PROTO(char *msg),
|
||||
TP_ARGS(msg)
|
||||
);
|
||||
|
||||
TRACE_EVENT(cdnsp_looking_trb_in_td,
|
||||
TP_PROTO(dma_addr_t suspect, dma_addr_t trb_start, dma_addr_t trb_end,
|
||||
dma_addr_t curr_seg, dma_addr_t end_seg),
|
||||
TP_ARGS(suspect, trb_start, trb_end, curr_seg, end_seg),
|
||||
TP_STRUCT__entry(
|
||||
__field(dma_addr_t, suspect)
|
||||
__field(dma_addr_t, trb_start)
|
||||
__field(dma_addr_t, trb_end)
|
||||
__field(dma_addr_t, curr_seg)
|
||||
__field(dma_addr_t, end_seg)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__entry->suspect = suspect;
|
||||
__entry->trb_start = trb_start;
|
||||
__entry->trb_end = trb_end;
|
||||
__entry->curr_seg = curr_seg;
|
||||
__entry->end_seg = end_seg;
|
||||
),
|
||||
TP_printk("DMA: suspect event: %pad, trb-start: %pad, trb-end %pad, "
|
||||
"seg-start %pad, seg-end %pad",
|
||||
&__entry->suspect, &__entry->trb_start, &__entry->trb_end,
|
||||
&__entry->curr_seg, &__entry->end_seg)
|
||||
);
|
||||
|
||||
TRACE_EVENT(cdnsp_port_info,
|
||||
TP_PROTO(__le32 __iomem *addr, u32 offset, u32 count, u32 rev),
|
||||
TP_ARGS(addr, offset, count, rev),
|
||||
TP_STRUCT__entry(
|
||||
__field(__le32 __iomem *, addr)
|
||||
__field(u32, offset)
|
||||
__field(u32, count)
|
||||
__field(u32, rev)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__entry->addr = addr;
|
||||
__entry->offset = offset;
|
||||
__entry->count = count;
|
||||
__entry->rev = rev;
|
||||
),
|
||||
TP_printk("Ext Cap %p, port offset = %u, count = %u, rev = 0x%x",
|
||||
__entry->addr, __entry->offset, __entry->count, __entry->rev)
|
||||
);
|
||||
|
||||
DECLARE_EVENT_CLASS(cdnsp_log_deq_state,
|
||||
TP_PROTO(struct cdnsp_dequeue_state *state),
|
||||
TP_ARGS(state),
|
||||
TP_STRUCT__entry(
|
||||
__field(int, new_cycle_state)
|
||||
__field(struct cdnsp_segment *, new_deq_seg)
|
||||
__field(dma_addr_t, deq_seg_dma)
|
||||
__field(union cdnsp_trb *, new_deq_ptr)
|
||||
__field(dma_addr_t, deq_ptr_dma)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__entry->new_cycle_state = state->new_cycle_state;
|
||||
__entry->new_deq_seg = state->new_deq_seg;
|
||||
__entry->deq_seg_dma = state->new_deq_seg->dma;
|
||||
__entry->new_deq_ptr = state->new_deq_ptr,
|
||||
__entry->deq_ptr_dma = cdnsp_trb_virt_to_dma(state->new_deq_seg,
|
||||
state->new_deq_ptr);
|
||||
),
|
||||
TP_printk("New cycle state = 0x%x, New dequeue segment = %p (0x%pad dma), "
|
||||
"New dequeue pointer = %p (0x%pad dma)",
|
||||
__entry->new_cycle_state, __entry->new_deq_seg,
|
||||
&__entry->deq_seg_dma, __entry->new_deq_ptr,
|
||||
&__entry->deq_ptr_dma
|
||||
)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_deq_state, cdnsp_new_deq_state,
|
||||
TP_PROTO(struct cdnsp_dequeue_state *state),
|
||||
TP_ARGS(state)
|
||||
);
|
||||
|
||||
DECLARE_EVENT_CLASS(cdnsp_log_ctrl,
|
||||
TP_PROTO(struct usb_ctrlrequest *ctrl),
|
||||
TP_ARGS(ctrl),
|
||||
TP_STRUCT__entry(
|
||||
__field(u8, bRequestType)
|
||||
__field(u8, bRequest)
|
||||
__field(u16, wValue)
|
||||
__field(u16, wIndex)
|
||||
__field(u16, wLength)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__entry->bRequestType = ctrl->bRequestType;
|
||||
__entry->bRequest = ctrl->bRequest;
|
||||
__entry->wValue = le16_to_cpu(ctrl->wValue);
|
||||
__entry->wIndex = le16_to_cpu(ctrl->wIndex);
|
||||
__entry->wLength = le16_to_cpu(ctrl->wLength);
|
||||
),
|
||||
TP_printk("%s", usb_decode_ctrl(__get_buf(CDNSP_MSG_MAX), CDNSP_MSG_MAX,
|
||||
__entry->bRequestType,
|
||||
__entry->bRequest, __entry->wValue,
|
||||
__entry->wIndex, __entry->wLength)
|
||||
)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_ctrl, cdnsp_ctrl_req,
|
||||
TP_PROTO(struct usb_ctrlrequest *ctrl),
|
||||
TP_ARGS(ctrl)
|
||||
);
|
||||
|
||||
DECLARE_EVENT_CLASS(cdnsp_log_bounce,
|
||||
TP_PROTO(struct cdnsp_request *preq, u32 new_buf_len, u32 offset,
|
||||
dma_addr_t dma, unsigned int unalign),
|
||||
TP_ARGS(preq, new_buf_len, offset, dma, unalign),
|
||||
TP_STRUCT__entry(
|
||||
__string(name, preq->pep->name)
|
||||
__field(u32, new_buf_len)
|
||||
__field(u32, offset)
|
||||
__field(dma_addr_t, dma)
|
||||
__field(unsigned int, unalign)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__assign_str(name);
|
||||
__entry->new_buf_len = new_buf_len;
|
||||
__entry->offset = offset;
|
||||
__entry->dma = dma;
|
||||
__entry->unalign = unalign;
|
||||
),
|
||||
TP_printk("%s buf len %d, offset %d, dma %pad, unalign %d",
|
||||
__get_str(name), __entry->new_buf_len,
|
||||
__entry->offset, &__entry->dma, __entry->unalign
|
||||
)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_bounce, cdnsp_bounce_align_td_split,
|
||||
TP_PROTO(struct cdnsp_request *preq, u32 new_buf_len, u32 offset,
|
||||
dma_addr_t dma, unsigned int unalign),
|
||||
TP_ARGS(preq, new_buf_len, offset, dma, unalign)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_bounce, cdnsp_bounce_map,
|
||||
TP_PROTO(struct cdnsp_request *preq, u32 new_buf_len, u32 offset,
|
||||
dma_addr_t dma, unsigned int unalign),
|
||||
TP_ARGS(preq, new_buf_len, offset, dma, unalign)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_bounce, cdnsp_bounce_unmap,
|
||||
TP_PROTO(struct cdnsp_request *preq, u32 new_buf_len, u32 offset,
|
||||
dma_addr_t dma, unsigned int unalign),
|
||||
TP_ARGS(preq, new_buf_len, offset, dma, unalign)
|
||||
);
|
||||
|
||||
DECLARE_EVENT_CLASS(cdnsp_log_trb,
|
||||
TP_PROTO(struct cdnsp_ring *ring, struct cdnsp_generic_trb *trb),
|
||||
TP_ARGS(ring, trb),
|
||||
TP_STRUCT__entry(
|
||||
__field(u32, type)
|
||||
__field(u32, field0)
|
||||
__field(u32, field1)
|
||||
__field(u32, field2)
|
||||
__field(u32, field3)
|
||||
__field(union cdnsp_trb *, trb)
|
||||
__field(dma_addr_t, trb_dma)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__entry->type = ring->type;
|
||||
__entry->field0 = le32_to_cpu(trb->field[0]);
|
||||
__entry->field1 = le32_to_cpu(trb->field[1]);
|
||||
__entry->field2 = le32_to_cpu(trb->field[2]);
|
||||
__entry->field3 = le32_to_cpu(trb->field[3]);
|
||||
__entry->trb = (union cdnsp_trb *)trb;
|
||||
__entry->trb_dma = cdnsp_trb_virt_to_dma(ring->deq_seg,
|
||||
(union cdnsp_trb *)trb);
|
||||
|
||||
),
|
||||
TP_printk("%s: %s trb: %p(%pad)", cdnsp_ring_type_string(__entry->type),
|
||||
cdnsp_decode_trb(__get_buf(CDNSP_MSG_MAX), CDNSP_MSG_MAX,
|
||||
__entry->field0, __entry->field1,
|
||||
__entry->field2, __entry->field3),
|
||||
__entry->trb, &__entry->trb_dma
|
||||
)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_trb, cdnsp_handle_event,
|
||||
TP_PROTO(struct cdnsp_ring *ring, struct cdnsp_generic_trb *trb),
|
||||
TP_ARGS(ring, trb)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_trb, cdnsp_trb_without_td,
|
||||
TP_PROTO(struct cdnsp_ring *ring, struct cdnsp_generic_trb *trb),
|
||||
TP_ARGS(ring, trb)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_trb, cdnsp_handle_command,
|
||||
TP_PROTO(struct cdnsp_ring *ring, struct cdnsp_generic_trb *trb),
|
||||
TP_ARGS(ring, trb)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_trb, cdnsp_handle_transfer,
|
||||
TP_PROTO(struct cdnsp_ring *ring, struct cdnsp_generic_trb *trb),
|
||||
TP_ARGS(ring, trb)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_trb, cdnsp_queue_trb,
|
||||
TP_PROTO(struct cdnsp_ring *ring, struct cdnsp_generic_trb *trb),
|
||||
TP_ARGS(ring, trb)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_trb, cdnsp_cmd_wait_for_compl,
|
||||
TP_PROTO(struct cdnsp_ring *ring, struct cdnsp_generic_trb *trb),
|
||||
TP_ARGS(ring, trb)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_trb, cdnsp_cmd_timeout,
|
||||
TP_PROTO(struct cdnsp_ring *ring, struct cdnsp_generic_trb *trb),
|
||||
TP_ARGS(ring, trb)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_trb, cdnsp_defered_event,
|
||||
TP_PROTO(struct cdnsp_ring *ring, struct cdnsp_generic_trb *trb),
|
||||
TP_ARGS(ring, trb)
|
||||
);
|
||||
|
||||
DECLARE_EVENT_CLASS(cdnsp_log_pdev,
|
||||
TP_PROTO(struct cdnsp_device *pdev),
|
||||
TP_ARGS(pdev),
|
||||
TP_STRUCT__entry(
|
||||
__field(struct cdnsp_device *, pdev)
|
||||
__field(struct usb_gadget *, gadget)
|
||||
__field(dma_addr_t, out_ctx)
|
||||
__field(dma_addr_t, in_ctx)
|
||||
__field(u8, port_num)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__entry->pdev = pdev;
|
||||
__entry->gadget = &pdev->gadget;
|
||||
__entry->in_ctx = pdev->in_ctx.dma;
|
||||
__entry->out_ctx = pdev->out_ctx.dma;
|
||||
__entry->port_num = pdev->active_port ?
|
||||
pdev->active_port->port_num : 0xFF;
|
||||
),
|
||||
TP_printk("pdev %p gadget %p ctx %pad | %pad, port %d ",
|
||||
__entry->pdev, __entry->gadget, &__entry->in_ctx,
|
||||
&__entry->out_ctx, __entry->port_num
|
||||
)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_pdev, cdnsp_alloc_priv_device,
|
||||
TP_PROTO(struct cdnsp_device *vdev),
|
||||
TP_ARGS(vdev)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_pdev, cdnsp_free_priv_device,
|
||||
TP_PROTO(struct cdnsp_device *vdev),
|
||||
TP_ARGS(vdev)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_pdev, cdnsp_setup_device,
|
||||
TP_PROTO(struct cdnsp_device *vdev),
|
||||
TP_ARGS(vdev)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_pdev, cdnsp_setup_addressable_priv_device,
|
||||
TP_PROTO(struct cdnsp_device *vdev),
|
||||
TP_ARGS(vdev)
|
||||
);
|
||||
|
||||
DECLARE_EVENT_CLASS(cdnsp_log_request,
|
||||
TP_PROTO(struct cdnsp_request *req),
|
||||
TP_ARGS(req),
|
||||
TP_STRUCT__entry(
|
||||
__string(name, req->pep->name)
|
||||
__field(struct usb_request *, request)
|
||||
__field(struct cdnsp_request *, preq)
|
||||
__field(void *, buf)
|
||||
__field(unsigned int, actual)
|
||||
__field(unsigned int, length)
|
||||
__field(int, status)
|
||||
__field(dma_addr_t, dma)
|
||||
__field(unsigned int, stream_id)
|
||||
__field(unsigned int, zero)
|
||||
__field(unsigned int, short_not_ok)
|
||||
__field(unsigned int, no_interrupt)
|
||||
__field(struct scatterlist*, sg)
|
||||
__field(unsigned int, num_sgs)
|
||||
__field(unsigned int, num_mapped_sgs)
|
||||
|
||||
),
|
||||
TP_fast_assign(
|
||||
__assign_str(name);
|
||||
__entry->request = &req->request;
|
||||
__entry->preq = req;
|
||||
__entry->buf = req->request.buf;
|
||||
__entry->actual = req->request.actual;
|
||||
__entry->length = req->request.length;
|
||||
__entry->status = req->request.status;
|
||||
__entry->dma = req->request.dma;
|
||||
__entry->stream_id = req->request.stream_id;
|
||||
__entry->zero = req->request.zero;
|
||||
__entry->short_not_ok = req->request.short_not_ok;
|
||||
__entry->no_interrupt = req->request.no_interrupt;
|
||||
__entry->sg = req->request.sg;
|
||||
__entry->num_sgs = req->request.num_sgs;
|
||||
__entry->num_mapped_sgs = req->request.num_mapped_sgs;
|
||||
),
|
||||
TP_printk("%s; req U:%p/P:%p, req buf %p, length %u/%u, status %d, "
|
||||
"buf dma (%pad), SID %u, %s%s%s, sg %p, num_sg %d,"
|
||||
" num_m_sg %d",
|
||||
__get_str(name), __entry->request, __entry->preq,
|
||||
__entry->buf, __entry->actual, __entry->length,
|
||||
__entry->status, &__entry->dma,
|
||||
__entry->stream_id, __entry->zero ? "Z" : "z",
|
||||
__entry->short_not_ok ? "S" : "s",
|
||||
__entry->no_interrupt ? "I" : "i",
|
||||
__entry->sg, __entry->num_sgs, __entry->num_mapped_sgs
|
||||
)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_request, cdnsp_request_enqueue,
|
||||
TP_PROTO(struct cdnsp_request *req),
|
||||
TP_ARGS(req)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_request, cdnsp_request_enqueue_busy,
|
||||
TP_PROTO(struct cdnsp_request *req),
|
||||
TP_ARGS(req)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_request, cdnsp_request_enqueue_error,
|
||||
TP_PROTO(struct cdnsp_request *req),
|
||||
TP_ARGS(req)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_request, cdnsp_request_dequeue,
|
||||
TP_PROTO(struct cdnsp_request *req),
|
||||
TP_ARGS(req)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_request, cdnsp_request_giveback,
|
||||
TP_PROTO(struct cdnsp_request *req),
|
||||
TP_ARGS(req)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_request, cdnsp_alloc_request,
|
||||
TP_PROTO(struct cdnsp_request *req),
|
||||
TP_ARGS(req)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_request, cdnsp_free_request,
|
||||
TP_PROTO(struct cdnsp_request *req),
|
||||
TP_ARGS(req)
|
||||
);
|
||||
|
||||
DECLARE_EVENT_CLASS(cdnsp_log_ep_ctx,
|
||||
TP_PROTO(struct cdnsp_ep_ctx *ctx),
|
||||
TP_ARGS(ctx),
|
||||
TP_STRUCT__entry(
|
||||
__field(u32, info)
|
||||
__field(u32, info2)
|
||||
__field(u64, deq)
|
||||
__field(u32, tx_info)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__entry->info = le32_to_cpu(ctx->ep_info);
|
||||
__entry->info2 = le32_to_cpu(ctx->ep_info2);
|
||||
__entry->deq = le64_to_cpu(ctx->deq);
|
||||
__entry->tx_info = le32_to_cpu(ctx->tx_info);
|
||||
),
|
||||
TP_printk("%s", cdnsp_decode_ep_context(__get_buf(CDNSP_MSG_MAX), CDNSP_MSG_MAX,
|
||||
__entry->info, __entry->info2,
|
||||
__entry->deq, __entry->tx_info)
|
||||
)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_ep_ctx, cdnsp_ep_disabled,
|
||||
TP_PROTO(struct cdnsp_ep_ctx *ctx),
|
||||
TP_ARGS(ctx)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_ep_ctx, cdnsp_ep_stopped_or_disabled,
|
||||
TP_PROTO(struct cdnsp_ep_ctx *ctx),
|
||||
TP_ARGS(ctx)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_ep_ctx, cdnsp_remove_request,
|
||||
TP_PROTO(struct cdnsp_ep_ctx *ctx),
|
||||
TP_ARGS(ctx)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_ep_ctx, cdnsp_handle_cmd_stop_ep,
|
||||
TP_PROTO(struct cdnsp_ep_ctx *ctx),
|
||||
TP_ARGS(ctx)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_ep_ctx, cdnsp_handle_cmd_flush_ep,
|
||||
TP_PROTO(struct cdnsp_ep_ctx *ctx),
|
||||
TP_ARGS(ctx)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_ep_ctx, cdnsp_handle_cmd_set_deq_ep,
|
||||
TP_PROTO(struct cdnsp_ep_ctx *ctx),
|
||||
TP_ARGS(ctx)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_ep_ctx, cdnsp_handle_cmd_reset_ep,
|
||||
TP_PROTO(struct cdnsp_ep_ctx *ctx),
|
||||
TP_ARGS(ctx)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_ep_ctx, cdnsp_handle_cmd_config_ep,
|
||||
TP_PROTO(struct cdnsp_ep_ctx *ctx),
|
||||
TP_ARGS(ctx)
|
||||
);
|
||||
|
||||
DECLARE_EVENT_CLASS(cdnsp_log_slot_ctx,
|
||||
TP_PROTO(struct cdnsp_slot_ctx *ctx),
|
||||
TP_ARGS(ctx),
|
||||
TP_STRUCT__entry(
|
||||
__field(u32, info)
|
||||
__field(u32, info2)
|
||||
__field(u32, int_target)
|
||||
__field(u32, state)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__entry->info = le32_to_cpu(ctx->dev_info);
|
||||
__entry->info2 = le32_to_cpu(ctx->dev_port);
|
||||
__entry->int_target = le32_to_cpu(ctx->int_target);
|
||||
__entry->state = le32_to_cpu(ctx->dev_state);
|
||||
),
|
||||
TP_printk("%s", cdnsp_decode_slot_context(__entry->info,
|
||||
__entry->info2,
|
||||
__entry->int_target,
|
||||
__entry->state)
|
||||
)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_slot_ctx, cdnsp_slot_already_in_default,
|
||||
TP_PROTO(struct cdnsp_slot_ctx *ctx),
|
||||
TP_ARGS(ctx)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_slot_ctx, cdnsp_handle_cmd_enable_slot,
|
||||
TP_PROTO(struct cdnsp_slot_ctx *ctx),
|
||||
TP_ARGS(ctx)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_slot_ctx, cdnsp_handle_cmd_disable_slot,
|
||||
TP_PROTO(struct cdnsp_slot_ctx *ctx),
|
||||
TP_ARGS(ctx)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_slot_ctx, cdnsp_reset_device,
|
||||
TP_PROTO(struct cdnsp_slot_ctx *ctx),
|
||||
TP_ARGS(ctx)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_slot_ctx, cdnsp_setup_device_slot,
|
||||
TP_PROTO(struct cdnsp_slot_ctx *ctx),
|
||||
TP_ARGS(ctx)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_slot_ctx, cdnsp_handle_cmd_addr_dev,
|
||||
TP_PROTO(struct cdnsp_slot_ctx *ctx),
|
||||
TP_ARGS(ctx)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_slot_ctx, cdnsp_handle_cmd_reset_dev,
|
||||
TP_PROTO(struct cdnsp_slot_ctx *ctx),
|
||||
TP_ARGS(ctx)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_slot_ctx, cdnsp_handle_cmd_set_deq,
|
||||
TP_PROTO(struct cdnsp_slot_ctx *ctx),
|
||||
TP_ARGS(ctx)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_slot_ctx, cdnsp_configure_endpoint,
|
||||
TP_PROTO(struct cdnsp_slot_ctx *ctx),
|
||||
TP_ARGS(ctx)
|
||||
);
|
||||
|
||||
DECLARE_EVENT_CLASS(cdnsp_log_td_info,
|
||||
TP_PROTO(struct cdnsp_request *preq),
|
||||
TP_ARGS(preq),
|
||||
TP_STRUCT__entry(
|
||||
__string(name, preq->pep->name)
|
||||
__field(struct usb_request *, request)
|
||||
__field(struct cdnsp_request *, preq)
|
||||
__field(union cdnsp_trb *, first_trb)
|
||||
__field(union cdnsp_trb *, last_trb)
|
||||
__field(dma_addr_t, trb_dma)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__assign_str(name);
|
||||
__entry->request = &preq->request;
|
||||
__entry->preq = preq;
|
||||
__entry->first_trb = preq->td.first_trb;
|
||||
__entry->last_trb = preq->td.last_trb;
|
||||
__entry->trb_dma = cdnsp_trb_virt_to_dma(preq->td.start_seg,
|
||||
preq->td.first_trb)
|
||||
),
|
||||
TP_printk("%s req/preq: %p/%p, first trb %p[vir]/%pad(dma), last trb %p",
|
||||
__get_str(name), __entry->request, __entry->preq,
|
||||
__entry->first_trb, &__entry->trb_dma,
|
||||
__entry->last_trb
|
||||
)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_td_info, cdnsp_remove_request_td,
|
||||
TP_PROTO(struct cdnsp_request *preq),
|
||||
TP_ARGS(preq)
|
||||
);
|
||||
|
||||
DECLARE_EVENT_CLASS(cdnsp_log_ring,
|
||||
TP_PROTO(struct cdnsp_ring *ring),
|
||||
TP_ARGS(ring),
|
||||
TP_STRUCT__entry(
|
||||
__field(u32, type)
|
||||
__field(void *, ring)
|
||||
__field(dma_addr_t, enq)
|
||||
__field(dma_addr_t, deq)
|
||||
__field(dma_addr_t, enq_seg)
|
||||
__field(dma_addr_t, deq_seg)
|
||||
__field(unsigned int, num_segs)
|
||||
__field(unsigned int, stream_id)
|
||||
__field(unsigned int, cycle_state)
|
||||
__field(unsigned int, num_trbs_free)
|
||||
__field(unsigned int, bounce_buf_len)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__entry->ring = ring;
|
||||
__entry->type = ring->type;
|
||||
__entry->num_segs = ring->num_segs;
|
||||
__entry->stream_id = ring->stream_id;
|
||||
__entry->enq_seg = ring->enq_seg->dma;
|
||||
__entry->deq_seg = ring->deq_seg->dma;
|
||||
__entry->cycle_state = ring->cycle_state;
|
||||
__entry->num_trbs_free = ring->num_trbs_free;
|
||||
__entry->bounce_buf_len = ring->bounce_buf_len;
|
||||
__entry->enq = cdnsp_trb_virt_to_dma(ring->enq_seg,
|
||||
ring->enqueue);
|
||||
__entry->deq = cdnsp_trb_virt_to_dma(ring->deq_seg,
|
||||
ring->dequeue);
|
||||
),
|
||||
TP_printk("%s %p: enq %pad(%pad) deq %pad(%pad) segs %d stream %d"
|
||||
" free_trbs %d bounce %d cycle %d",
|
||||
cdnsp_ring_type_string(__entry->type), __entry->ring,
|
||||
&__entry->enq, &__entry->enq_seg,
|
||||
&__entry->deq, &__entry->deq_seg,
|
||||
__entry->num_segs,
|
||||
__entry->stream_id,
|
||||
__entry->num_trbs_free,
|
||||
__entry->bounce_buf_len,
|
||||
__entry->cycle_state
|
||||
)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_ring, cdnsp_ring_alloc,
|
||||
TP_PROTO(struct cdnsp_ring *ring),
|
||||
TP_ARGS(ring)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_ring, cdnsp_ring_free,
|
||||
TP_PROTO(struct cdnsp_ring *ring),
|
||||
TP_ARGS(ring)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_ring, cdnsp_set_stream_ring,
|
||||
TP_PROTO(struct cdnsp_ring *ring),
|
||||
TP_ARGS(ring)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_ring, cdnsp_ring_expansion,
|
||||
TP_PROTO(struct cdnsp_ring *ring),
|
||||
TP_ARGS(ring)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_ring, cdnsp_inc_enq,
|
||||
TP_PROTO(struct cdnsp_ring *ring),
|
||||
TP_ARGS(ring)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_ring, cdnsp_inc_deq,
|
||||
TP_PROTO(struct cdnsp_ring *ring),
|
||||
TP_ARGS(ring)
|
||||
);
|
||||
|
||||
DECLARE_EVENT_CLASS(cdnsp_log_portsc,
|
||||
TP_PROTO(u32 portnum, u32 portsc),
|
||||
TP_ARGS(portnum, portsc),
|
||||
TP_STRUCT__entry(
|
||||
__field(u32, portnum)
|
||||
__field(u32, portsc)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__entry->portnum = portnum;
|
||||
__entry->portsc = portsc;
|
||||
),
|
||||
TP_printk("port-%d: %s",
|
||||
__entry->portnum,
|
||||
cdnsp_decode_portsc(__get_buf(CDNSP_MSG_MAX), CDNSP_MSG_MAX,
|
||||
__entry->portsc)
|
||||
)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_portsc, cdnsp_handle_port_status,
|
||||
TP_PROTO(u32 portnum, u32 portsc),
|
||||
TP_ARGS(portnum, portsc)
|
||||
);
|
||||
|
||||
DEFINE_EVENT(cdnsp_log_portsc, cdnsp_link_state_changed,
|
||||
TP_PROTO(u32 portnum, u32 portsc),
|
||||
TP_ARGS(portnum, portsc)
|
||||
);
|
||||
|
||||
TRACE_EVENT(cdnsp_stream_number,
|
||||
TP_PROTO(struct cdnsp_ep *pep, int num_stream_ctxs, int num_streams),
|
||||
TP_ARGS(pep, num_stream_ctxs, num_streams),
|
||||
TP_STRUCT__entry(
|
||||
__string(name, pep->name)
|
||||
__field(int, num_stream_ctxs)
|
||||
__field(int, num_streams)
|
||||
),
|
||||
TP_fast_assign(
|
||||
__entry->num_stream_ctxs = num_stream_ctxs;
|
||||
__entry->num_streams = num_streams;
|
||||
),
|
||||
TP_printk("%s Need %u stream ctx entries for %u stream IDs.",
|
||||
__get_str(name), __entry->num_stream_ctxs,
|
||||
__entry->num_streams)
|
||||
);
|
||||
|
||||
#endif /* __CDNSP_TRACE_H */
|
||||
|
||||
/* this part must be outside header guard */
|
||||
|
||||
#undef TRACE_INCLUDE_PATH
|
||||
#define TRACE_INCLUDE_PATH .
|
||||
|
||||
#undef TRACE_INCLUDE_FILE
|
||||
#define TRACE_INCLUDE_FILE cdnsp-trace
|
||||
|
||||
#include <trace/define_trace.h>
|
581
drivers/usb/cdns3/core.c
Normal file
581
drivers/usb/cdns3/core.c
Normal file
|
@ -0,0 +1,581 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Cadence USBSS and USBSSP DRD Driver.
|
||||
*
|
||||
* Copyright (C) 2018-2019 Cadence.
|
||||
* Copyright (C) 2017-2018 NXP
|
||||
* Copyright (C) 2019 Texas Instruments
|
||||
*
|
||||
* Author: Peter Chen <peter.chen@nxp.com>
|
||||
* Pawel Laszczak <pawell@cadence.com>
|
||||
* Roger Quadros <rogerq@ti.com>
|
||||
*/
|
||||
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
|
||||
#include "core.h"
|
||||
#include "host-export.h"
|
||||
#include "drd.h"
|
||||
|
||||
static int cdns_idle_init(struct cdns *cdns);
|
||||
|
||||
static int cdns_role_start(struct cdns *cdns, enum usb_role role)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (WARN_ON(role > USB_ROLE_DEVICE))
|
||||
return 0;
|
||||
|
||||
mutex_lock(&cdns->mutex);
|
||||
cdns->role = role;
|
||||
mutex_unlock(&cdns->mutex);
|
||||
|
||||
if (!cdns->roles[role])
|
||||
return -ENXIO;
|
||||
|
||||
if (cdns->roles[role]->state == CDNS_ROLE_STATE_ACTIVE)
|
||||
return 0;
|
||||
|
||||
mutex_lock(&cdns->mutex);
|
||||
ret = cdns->roles[role]->start(cdns);
|
||||
if (!ret)
|
||||
cdns->roles[role]->state = CDNS_ROLE_STATE_ACTIVE;
|
||||
mutex_unlock(&cdns->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void cdns_role_stop(struct cdns *cdns)
|
||||
{
|
||||
enum usb_role role = cdns->role;
|
||||
|
||||
if (WARN_ON(role > USB_ROLE_DEVICE))
|
||||
return;
|
||||
|
||||
if (cdns->roles[role]->state == CDNS_ROLE_STATE_INACTIVE)
|
||||
return;
|
||||
|
||||
mutex_lock(&cdns->mutex);
|
||||
cdns->roles[role]->stop(cdns);
|
||||
cdns->roles[role]->state = CDNS_ROLE_STATE_INACTIVE;
|
||||
mutex_unlock(&cdns->mutex);
|
||||
}
|
||||
|
||||
static void cdns_exit_roles(struct cdns *cdns)
|
||||
{
|
||||
cdns_role_stop(cdns);
|
||||
cdns_drd_exit(cdns);
|
||||
}
|
||||
|
||||
/**
|
||||
* cdns_core_init_role - initialize role of operation
|
||||
* @cdns: Pointer to cdns structure
|
||||
*
|
||||
* Returns 0 on success otherwise negative errno
|
||||
*/
|
||||
static int cdns_core_init_role(struct cdns *cdns)
|
||||
{
|
||||
struct device *dev = cdns->dev;
|
||||
enum usb_dr_mode best_dr_mode;
|
||||
enum usb_dr_mode dr_mode;
|
||||
int ret;
|
||||
|
||||
dr_mode = usb_get_dr_mode(dev);
|
||||
cdns->role = USB_ROLE_NONE;
|
||||
|
||||
/*
|
||||
* If driver can't read mode by means of usb_get_dr_mode function then
|
||||
* chooses mode according with Kernel configuration. This setting
|
||||
* can be restricted later depending on strap pin configuration.
|
||||
*/
|
||||
if (dr_mode == USB_DR_MODE_UNKNOWN) {
|
||||
if (cdns->version == CDNSP_CONTROLLER_V2) {
|
||||
if (IS_ENABLED(CONFIG_USB_CDNSP_HOST) &&
|
||||
IS_ENABLED(CONFIG_USB_CDNSP_GADGET))
|
||||
dr_mode = USB_DR_MODE_OTG;
|
||||
else if (IS_ENABLED(CONFIG_USB_CDNSP_HOST))
|
||||
dr_mode = USB_DR_MODE_HOST;
|
||||
else if (IS_ENABLED(CONFIG_USB_CDNSP_GADGET))
|
||||
dr_mode = USB_DR_MODE_PERIPHERAL;
|
||||
} else {
|
||||
if (IS_ENABLED(CONFIG_USB_CDNS3_HOST) &&
|
||||
IS_ENABLED(CONFIG_USB_CDNS3_GADGET))
|
||||
dr_mode = USB_DR_MODE_OTG;
|
||||
else if (IS_ENABLED(CONFIG_USB_CDNS3_HOST))
|
||||
dr_mode = USB_DR_MODE_HOST;
|
||||
else if (IS_ENABLED(CONFIG_USB_CDNS3_GADGET))
|
||||
dr_mode = USB_DR_MODE_PERIPHERAL;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* At this point cdns->dr_mode contains strap configuration.
|
||||
* Driver try update this setting considering kernel configuration
|
||||
*/
|
||||
best_dr_mode = cdns->dr_mode;
|
||||
|
||||
ret = cdns_idle_init(cdns);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (dr_mode == USB_DR_MODE_OTG) {
|
||||
best_dr_mode = cdns->dr_mode;
|
||||
} else if (cdns->dr_mode == USB_DR_MODE_OTG) {
|
||||
best_dr_mode = dr_mode;
|
||||
} else if (cdns->dr_mode != dr_mode) {
|
||||
dev_err(dev, "Incorrect DRD configuration\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dr_mode = best_dr_mode;
|
||||
|
||||
if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) {
|
||||
if ((cdns->version == CDNSP_CONTROLLER_V2 &&
|
||||
IS_ENABLED(CONFIG_USB_CDNSP_HOST)) ||
|
||||
(cdns->version < CDNSP_CONTROLLER_V2 &&
|
||||
IS_ENABLED(CONFIG_USB_CDNS3_HOST)))
|
||||
ret = cdns_host_init(cdns);
|
||||
else
|
||||
ret = -ENXIO;
|
||||
|
||||
if (ret) {
|
||||
dev_err(dev, "Host initialization failed with %d\n",
|
||||
ret);
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_PERIPHERAL) {
|
||||
if (cdns->gadget_init)
|
||||
ret = cdns->gadget_init(cdns);
|
||||
else
|
||||
ret = -ENXIO;
|
||||
|
||||
if (ret) {
|
||||
dev_err(dev, "Device initialization failed with %d\n",
|
||||
ret);
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
cdns->dr_mode = dr_mode;
|
||||
|
||||
ret = cdns_drd_update_mode(cdns);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
/* Initialize idle role to start with */
|
||||
ret = cdns_role_start(cdns, USB_ROLE_NONE);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
switch (cdns->dr_mode) {
|
||||
case USB_DR_MODE_OTG:
|
||||
ret = cdns_hw_role_switch(cdns);
|
||||
if (ret)
|
||||
goto err;
|
||||
break;
|
||||
case USB_DR_MODE_PERIPHERAL:
|
||||
ret = cdns_role_start(cdns, USB_ROLE_DEVICE);
|
||||
if (ret)
|
||||
goto err;
|
||||
break;
|
||||
case USB_DR_MODE_HOST:
|
||||
ret = cdns_role_start(cdns, USB_ROLE_HOST);
|
||||
if (ret)
|
||||
goto err;
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
err:
|
||||
cdns_exit_roles(cdns);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* cdns_hw_role_state_machine - role switch state machine based on hw events.
|
||||
* @cdns: Pointer to controller structure.
|
||||
*
|
||||
* Returns next role to be entered based on hw events.
|
||||
*/
|
||||
static enum usb_role cdns_hw_role_state_machine(struct cdns *cdns)
|
||||
{
|
||||
enum usb_role role = USB_ROLE_NONE;
|
||||
int id, vbus;
|
||||
|
||||
if (cdns->dr_mode != USB_DR_MODE_OTG) {
|
||||
if (cdns_is_host(cdns))
|
||||
role = USB_ROLE_HOST;
|
||||
if (cdns_is_device(cdns))
|
||||
role = USB_ROLE_DEVICE;
|
||||
|
||||
return role;
|
||||
}
|
||||
|
||||
id = cdns_get_id(cdns);
|
||||
vbus = cdns_get_vbus(cdns);
|
||||
|
||||
/*
|
||||
* Role change state machine
|
||||
* Inputs: ID, VBUS
|
||||
* Previous state: cdns->role
|
||||
* Next state: role
|
||||
*/
|
||||
role = cdns->role;
|
||||
|
||||
switch (role) {
|
||||
case USB_ROLE_NONE:
|
||||
/*
|
||||
* Driver treats USB_ROLE_NONE synonymous to IDLE state from
|
||||
* controller specification.
|
||||
*/
|
||||
if (!id)
|
||||
role = USB_ROLE_HOST;
|
||||
else if (vbus)
|
||||
role = USB_ROLE_DEVICE;
|
||||
break;
|
||||
case USB_ROLE_HOST: /* from HOST, we can only change to NONE */
|
||||
if (id)
|
||||
role = USB_ROLE_NONE;
|
||||
break;
|
||||
case USB_ROLE_DEVICE: /* from GADGET, we can only change to NONE*/
|
||||
if (!vbus)
|
||||
role = USB_ROLE_NONE;
|
||||
break;
|
||||
}
|
||||
|
||||
dev_dbg(cdns->dev, "role %d -> %d\n", cdns->role, role);
|
||||
|
||||
return role;
|
||||
}
|
||||
|
||||
static int cdns_idle_role_start(struct cdns *cdns)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void cdns_idle_role_stop(struct cdns *cdns)
|
||||
{
|
||||
/* Program Lane swap and bring PHY out of RESET */
|
||||
phy_reset(cdns->usb3_phy);
|
||||
}
|
||||
|
||||
static int cdns_idle_init(struct cdns *cdns)
|
||||
{
|
||||
struct cdns_role_driver *rdrv;
|
||||
|
||||
rdrv = devm_kzalloc(cdns->dev, sizeof(*rdrv), GFP_KERNEL);
|
||||
if (!rdrv)
|
||||
return -ENOMEM;
|
||||
|
||||
rdrv->start = cdns_idle_role_start;
|
||||
rdrv->stop = cdns_idle_role_stop;
|
||||
rdrv->state = CDNS_ROLE_STATE_INACTIVE;
|
||||
rdrv->suspend = NULL;
|
||||
rdrv->resume = NULL;
|
||||
rdrv->name = "idle";
|
||||
|
||||
cdns->roles[USB_ROLE_NONE] = rdrv;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* cdns_hw_role_switch - switch roles based on HW state
|
||||
* @cdns: controller
|
||||
*/
|
||||
int cdns_hw_role_switch(struct cdns *cdns)
|
||||
{
|
||||
enum usb_role real_role, current_role;
|
||||
int ret = 0;
|
||||
|
||||
/* Depends on role switch class */
|
||||
if (cdns->role_sw)
|
||||
return 0;
|
||||
|
||||
pm_runtime_get_sync(cdns->dev);
|
||||
|
||||
current_role = cdns->role;
|
||||
real_role = cdns_hw_role_state_machine(cdns);
|
||||
|
||||
/* Do nothing if nothing changed */
|
||||
if (current_role == real_role)
|
||||
goto exit;
|
||||
|
||||
cdns_role_stop(cdns);
|
||||
|
||||
dev_dbg(cdns->dev, "Switching role %d -> %d", current_role, real_role);
|
||||
|
||||
ret = cdns_role_start(cdns, real_role);
|
||||
if (ret) {
|
||||
/* Back to current role */
|
||||
dev_err(cdns->dev, "set %d has failed, back to %d\n",
|
||||
real_role, current_role);
|
||||
ret = cdns_role_start(cdns, current_role);
|
||||
if (ret)
|
||||
dev_err(cdns->dev, "back to %d failed too\n",
|
||||
current_role);
|
||||
}
|
||||
exit:
|
||||
pm_runtime_put_sync(cdns->dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* cdns_role_get - get current role of controller.
|
||||
*
|
||||
* @sw: pointer to USB role switch structure
|
||||
*
|
||||
* Returns role
|
||||
*/
|
||||
static enum usb_role cdns_role_get(struct usb_role_switch *sw)
|
||||
{
|
||||
struct cdns *cdns = usb_role_switch_get_drvdata(sw);
|
||||
|
||||
return cdns->role;
|
||||
}
|
||||
|
||||
/**
|
||||
* cdns_role_set - set current role of controller.
|
||||
*
|
||||
* @sw: pointer to USB role switch structure
|
||||
* @role: the previous role
|
||||
* Handles below events:
|
||||
* - Role switch for dual-role devices
|
||||
* - USB_ROLE_GADGET <--> USB_ROLE_NONE for peripheral-only devices
|
||||
*/
|
||||
static int cdns_role_set(struct usb_role_switch *sw, enum usb_role role)
|
||||
{
|
||||
struct cdns *cdns = usb_role_switch_get_drvdata(sw);
|
||||
int ret = 0;
|
||||
|
||||
pm_runtime_get_sync(cdns->dev);
|
||||
|
||||
if (cdns->role == role)
|
||||
goto pm_put;
|
||||
|
||||
if (cdns->dr_mode == USB_DR_MODE_HOST) {
|
||||
switch (role) {
|
||||
case USB_ROLE_NONE:
|
||||
case USB_ROLE_HOST:
|
||||
break;
|
||||
default:
|
||||
goto pm_put;
|
||||
}
|
||||
}
|
||||
|
||||
if (cdns->dr_mode == USB_DR_MODE_PERIPHERAL) {
|
||||
switch (role) {
|
||||
case USB_ROLE_NONE:
|
||||
case USB_ROLE_DEVICE:
|
||||
break;
|
||||
default:
|
||||
goto pm_put;
|
||||
}
|
||||
}
|
||||
|
||||
cdns_role_stop(cdns);
|
||||
ret = cdns_role_start(cdns, role);
|
||||
if (ret)
|
||||
dev_err(cdns->dev, "set role %d has failed\n", role);
|
||||
|
||||
pm_put:
|
||||
pm_runtime_put_sync(cdns->dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* cdns_wakeup_irq - interrupt handler for wakeup events
|
||||
* @irq: irq number for cdns3/cdnsp core device
|
||||
* @data: structure of cdns
|
||||
*
|
||||
* Returns IRQ_HANDLED or IRQ_NONE
|
||||
*/
|
||||
static irqreturn_t cdns_wakeup_irq(int irq, void *data)
|
||||
{
|
||||
struct cdns *cdns = data;
|
||||
|
||||
if (cdns->in_lpm) {
|
||||
disable_irq_nosync(irq);
|
||||
cdns->wakeup_pending = true;
|
||||
if ((cdns->role == USB_ROLE_HOST) && cdns->host_dev)
|
||||
pm_request_resume(&cdns->host_dev->dev);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* cdns_init - probe for cdns3/cdnsp core device
|
||||
* @cdns: Pointer to cdns structure.
|
||||
*
|
||||
* Returns 0 on success otherwise negative errno
|
||||
*/
|
||||
int cdns_init(struct cdns *cdns)
|
||||
{
|
||||
struct device *dev = cdns->dev;
|
||||
int ret;
|
||||
|
||||
ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32));
|
||||
if (ret) {
|
||||
dev_err(dev, "error setting dma mask: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
mutex_init(&cdns->mutex);
|
||||
|
||||
if (device_property_read_bool(dev, "usb-role-switch")) {
|
||||
struct usb_role_switch_desc sw_desc = { };
|
||||
|
||||
sw_desc.set = cdns_role_set;
|
||||
sw_desc.get = cdns_role_get;
|
||||
sw_desc.allow_userspace_control = true;
|
||||
sw_desc.driver_data = cdns;
|
||||
sw_desc.fwnode = dev->fwnode;
|
||||
|
||||
cdns->role_sw = usb_role_switch_register(dev, &sw_desc);
|
||||
if (IS_ERR(cdns->role_sw)) {
|
||||
dev_warn(dev, "Unable to register Role Switch\n");
|
||||
return PTR_ERR(cdns->role_sw);
|
||||
}
|
||||
}
|
||||
|
||||
if (cdns->wakeup_irq) {
|
||||
ret = devm_request_irq(cdns->dev, cdns->wakeup_irq,
|
||||
cdns_wakeup_irq,
|
||||
IRQF_SHARED,
|
||||
dev_name(cdns->dev), cdns);
|
||||
|
||||
if (ret) {
|
||||
dev_err(cdns->dev, "couldn't register wakeup irq handler\n");
|
||||
goto role_switch_unregister;
|
||||
}
|
||||
}
|
||||
|
||||
ret = cdns_drd_init(cdns);
|
||||
if (ret)
|
||||
goto init_failed;
|
||||
|
||||
ret = cdns_core_init_role(cdns);
|
||||
if (ret)
|
||||
goto init_failed;
|
||||
|
||||
spin_lock_init(&cdns->lock);
|
||||
|
||||
dev_dbg(dev, "Cadence USB3 core: probe succeed\n");
|
||||
|
||||
return 0;
|
||||
init_failed:
|
||||
cdns_drd_exit(cdns);
|
||||
role_switch_unregister:
|
||||
if (cdns->role_sw)
|
||||
usb_role_switch_unregister(cdns->role_sw);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cdns_init);
|
||||
|
||||
/**
|
||||
* cdns_remove - unbind drd driver and clean up
|
||||
* @cdns: Pointer to cdns structure.
|
||||
*
|
||||
* Returns 0 on success otherwise negative errno
|
||||
*/
|
||||
int cdns_remove(struct cdns *cdns)
|
||||
{
|
||||
cdns_exit_roles(cdns);
|
||||
usb_role_switch_unregister(cdns->role_sw);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cdns_remove);
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
int cdns_suspend(struct cdns *cdns)
|
||||
{
|
||||
struct device *dev = cdns->dev;
|
||||
unsigned long flags;
|
||||
|
||||
if (pm_runtime_status_suspended(dev))
|
||||
pm_runtime_resume(dev);
|
||||
|
||||
if (cdns->roles[cdns->role]->suspend) {
|
||||
spin_lock_irqsave(&cdns->lock, flags);
|
||||
cdns->roles[cdns->role]->suspend(cdns, false);
|
||||
spin_unlock_irqrestore(&cdns->lock, flags);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cdns_suspend);
|
||||
|
||||
int cdns_resume(struct cdns *cdns)
|
||||
{
|
||||
enum usb_role real_role;
|
||||
bool role_changed = false;
|
||||
int ret = 0;
|
||||
|
||||
if (cdns_power_is_lost(cdns)) {
|
||||
if (cdns->role_sw) {
|
||||
cdns->role = cdns_role_get(cdns->role_sw);
|
||||
} else {
|
||||
real_role = cdns_hw_role_state_machine(cdns);
|
||||
if (real_role != cdns->role) {
|
||||
ret = cdns_hw_role_switch(cdns);
|
||||
if (ret)
|
||||
return ret;
|
||||
role_changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!role_changed) {
|
||||
if (cdns->role == USB_ROLE_HOST)
|
||||
ret = cdns_drd_host_on(cdns);
|
||||
else if (cdns->role == USB_ROLE_DEVICE)
|
||||
ret = cdns_drd_gadget_on(cdns);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (cdns->roles[cdns->role]->resume)
|
||||
cdns->roles[cdns->role]->resume(cdns, cdns_power_is_lost(cdns));
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cdns_resume);
|
||||
|
||||
void cdns_set_active(struct cdns *cdns, u8 set_active)
|
||||
{
|
||||
struct device *dev = cdns->dev;
|
||||
|
||||
if (set_active) {
|
||||
pm_runtime_disable(dev);
|
||||
pm_runtime_set_active(dev);
|
||||
pm_runtime_enable(dev);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cdns_set_active);
|
||||
#endif /* CONFIG_PM_SLEEP */
|
||||
|
||||
MODULE_AUTHOR("Peter Chen <peter.chen@nxp.com>");
|
||||
MODULE_AUTHOR("Pawel Laszczak <pawell@cadence.com>");
|
||||
MODULE_AUTHOR("Roger Quadros <rogerq@ti.com>");
|
||||
MODULE_DESCRIPTION("Cadence USBSS and USBSSP DRD Driver");
|
||||
MODULE_LICENSE("GPL");
|
142
drivers/usb/cdns3/core.h
Normal file
142
drivers/usb/cdns3/core.h
Normal file
|
@ -0,0 +1,142 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Cadence USBSS and USBSSP DRD Header File.
|
||||
*
|
||||
* Copyright (C) 2017-2018 NXP
|
||||
* Copyright (C) 2018-2019 Cadence.
|
||||
*
|
||||
* Authors: Peter Chen <peter.chen@nxp.com>
|
||||
* Pawel Laszczak <pawell@cadence.com>
|
||||
*/
|
||||
#ifndef __LINUX_CDNS3_CORE_H
|
||||
#define __LINUX_CDNS3_CORE_H
|
||||
|
||||
#include <linux/usb/otg.h>
|
||||
#include <linux/usb/role.h>
|
||||
|
||||
struct cdns;
|
||||
|
||||
/**
|
||||
* struct cdns_role_driver - host/gadget role driver
|
||||
* @start: start this role
|
||||
* @stop: stop this role
|
||||
* @suspend: suspend callback for this role
|
||||
* @resume: resume callback for this role
|
||||
* @irq: irq handler for this role
|
||||
* @name: role name string (host/gadget)
|
||||
* @state: current state
|
||||
*/
|
||||
struct cdns_role_driver {
|
||||
int (*start)(struct cdns *cdns);
|
||||
void (*stop)(struct cdns *cdns);
|
||||
int (*suspend)(struct cdns *cdns, bool do_wakeup);
|
||||
int (*resume)(struct cdns *cdns, bool hibernated);
|
||||
const char *name;
|
||||
#define CDNS_ROLE_STATE_INACTIVE 0
|
||||
#define CDNS_ROLE_STATE_ACTIVE 1
|
||||
int state;
|
||||
};
|
||||
|
||||
#define CDNS_XHCI_RESOURCES_NUM 2
|
||||
|
||||
struct cdns3_platform_data {
|
||||
int (*platform_suspend)(struct device *dev,
|
||||
bool suspend, bool wakeup);
|
||||
unsigned long quirks;
|
||||
#define CDNS3_DEFAULT_PM_RUNTIME_ALLOW BIT(0)
|
||||
#define CDNS3_DRD_SUSPEND_RESIDENCY_ENABLE BIT(1)
|
||||
};
|
||||
|
||||
/**
|
||||
* struct cdns - Representation of Cadence USB3 DRD controller.
|
||||
* @dev: pointer to Cadence device struct
|
||||
* @xhci_regs: pointer to base of xhci registers
|
||||
* @xhci_res: the resource for xhci
|
||||
* @dev_regs: pointer to base of dev registers
|
||||
* @otg_res: the resource for otg
|
||||
* @otg_v0_regs: pointer to base of v0 otg registers
|
||||
* @otg_v1_regs: pointer to base of v1 otg registers
|
||||
* @otg_cdnsp_regs: pointer to base of CDNSP otg registers
|
||||
* @otg_regs: pointer to base of otg registers
|
||||
* @otg_irq_regs: pointer to interrupt registers
|
||||
* @otg_irq: irq number for otg controller
|
||||
* @dev_irq: irq number for device controller
|
||||
* @wakeup_irq: irq number for wakeup event, it is optional
|
||||
* @roles: array of supported roles for this controller
|
||||
* @role: current role
|
||||
* @host_dev: the child host device pointer for cdns core
|
||||
* @gadget_dev: the child gadget device pointer
|
||||
* @usb2_phy: pointer to USB2 PHY
|
||||
* @usb3_phy: pointer to USB3 PHY
|
||||
* @mutex: the mutex for concurrent code at driver
|
||||
* @dr_mode: supported mode of operation it can be only Host, only Device
|
||||
* or OTG mode that allow to switch between Device and Host mode.
|
||||
* This field based on firmware setting, kernel configuration
|
||||
* and hardware configuration.
|
||||
* @role_sw: pointer to role switch object.
|
||||
* @in_lpm: indicate the controller is in low power mode
|
||||
* @wakeup_pending: wakeup interrupt pending
|
||||
* @pdata: platform data from glue layer
|
||||
* @lock: spinlock structure
|
||||
* @xhci_plat_data: xhci private data structure pointer
|
||||
* @override_apb_timeout: hold value of APB timeout. For value 0 the default
|
||||
* value in CHICKEN_BITS_3 will be preserved.
|
||||
* @gadget_init: pointer to gadget initialization function
|
||||
*/
|
||||
struct cdns {
|
||||
struct device *dev;
|
||||
void __iomem *xhci_regs;
|
||||
struct resource xhci_res[CDNS_XHCI_RESOURCES_NUM];
|
||||
struct cdns3_usb_regs __iomem *dev_regs;
|
||||
|
||||
struct resource otg_res;
|
||||
struct cdns3_otg_legacy_regs __iomem *otg_v0_regs;
|
||||
struct cdns3_otg_regs __iomem *otg_v1_regs;
|
||||
struct cdnsp_otg_regs __iomem *otg_cdnsp_regs;
|
||||
struct cdns_otg_common_regs __iomem *otg_regs;
|
||||
struct cdns_otg_irq_regs __iomem *otg_irq_regs;
|
||||
#define CDNS3_CONTROLLER_V0 0
|
||||
#define CDNS3_CONTROLLER_V1 1
|
||||
#define CDNSP_CONTROLLER_V2 2
|
||||
u32 version;
|
||||
bool phyrst_a_enable;
|
||||
|
||||
int otg_irq;
|
||||
int dev_irq;
|
||||
int wakeup_irq;
|
||||
struct cdns_role_driver *roles[USB_ROLE_DEVICE + 1];
|
||||
enum usb_role role;
|
||||
struct platform_device *host_dev;
|
||||
void *gadget_dev;
|
||||
struct phy *usb2_phy;
|
||||
struct phy *usb3_phy;
|
||||
/* mutext used in workqueue*/
|
||||
struct mutex mutex;
|
||||
enum usb_dr_mode dr_mode;
|
||||
struct usb_role_switch *role_sw;
|
||||
bool in_lpm;
|
||||
bool wakeup_pending;
|
||||
struct cdns3_platform_data *pdata;
|
||||
spinlock_t lock;
|
||||
struct xhci_plat_priv *xhci_plat_data;
|
||||
u32 override_apb_timeout;
|
||||
|
||||
int (*gadget_init)(struct cdns *cdns);
|
||||
};
|
||||
|
||||
int cdns_hw_role_switch(struct cdns *cdns);
|
||||
int cdns_init(struct cdns *cdns);
|
||||
int cdns_remove(struct cdns *cdns);
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
int cdns_resume(struct cdns *cdns);
|
||||
int cdns_suspend(struct cdns *cdns);
|
||||
void cdns_set_active(struct cdns *cdns, u8 set_active);
|
||||
#else /* CONFIG_PM_SLEEP */
|
||||
static inline int cdns_resume(struct cdns *cdns)
|
||||
{ return 0; }
|
||||
static inline void cdns_set_active(struct cdns *cdns, u8 set_active) { }
|
||||
static inline int cdns_suspend(struct cdns *cdns)
|
||||
{ return 0; }
|
||||
#endif /* CONFIG_PM_SLEEP */
|
||||
#endif /* __LINUX_CDNS3_CORE_H */
|
512
drivers/usb/cdns3/drd.c
Normal file
512
drivers/usb/cdns3/drd.c
Normal file
|
@ -0,0 +1,512 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Cadence USBSS and USBSSP DRD Driver.
|
||||
*
|
||||
* Copyright (C) 2018-2020 Cadence.
|
||||
* Copyright (C) 2019 Texas Instruments
|
||||
*
|
||||
* Author: Pawel Laszczak <pawell@cadence.com>
|
||||
* Roger Quadros <rogerq@ti.com>
|
||||
*
|
||||
*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/usb/otg.h>
|
||||
|
||||
#include "drd.h"
|
||||
#include "core.h"
|
||||
|
||||
/**
|
||||
* cdns_set_mode - change mode of OTG Core
|
||||
* @cdns: pointer to context structure
|
||||
* @mode: selected mode from cdns_role
|
||||
*
|
||||
* Returns 0 on success otherwise negative errno
|
||||
*/
|
||||
static int cdns_set_mode(struct cdns *cdns, enum usb_dr_mode mode)
|
||||
{
|
||||
void __iomem *override_reg;
|
||||
u32 reg;
|
||||
|
||||
switch (mode) {
|
||||
case USB_DR_MODE_PERIPHERAL:
|
||||
break;
|
||||
case USB_DR_MODE_HOST:
|
||||
break;
|
||||
case USB_DR_MODE_OTG:
|
||||
dev_dbg(cdns->dev, "Set controller to OTG mode\n");
|
||||
|
||||
if (cdns->version == CDNSP_CONTROLLER_V2)
|
||||
override_reg = &cdns->otg_cdnsp_regs->override;
|
||||
else if (cdns->version == CDNS3_CONTROLLER_V1)
|
||||
override_reg = &cdns->otg_v1_regs->override;
|
||||
else
|
||||
override_reg = &cdns->otg_v0_regs->ctrl1;
|
||||
|
||||
reg = readl(override_reg);
|
||||
|
||||
if (cdns->version != CDNS3_CONTROLLER_V0)
|
||||
reg |= OVERRIDE_IDPULLUP;
|
||||
else
|
||||
reg |= OVERRIDE_IDPULLUP_V0;
|
||||
|
||||
writel(reg, override_reg);
|
||||
|
||||
if (cdns->version == CDNS3_CONTROLLER_V1) {
|
||||
/*
|
||||
* Enable work around feature built into the
|
||||
* controller to address issue with RX Sensitivity
|
||||
* est (EL_17) for USB2 PHY. The issue only occures
|
||||
* for 0x0002450D controller version.
|
||||
*/
|
||||
if (cdns->phyrst_a_enable) {
|
||||
reg = readl(&cdns->otg_v1_regs->phyrst_cfg);
|
||||
reg |= PHYRST_CFG_PHYRST_A_ENABLE;
|
||||
writel(reg, &cdns->otg_v1_regs->phyrst_cfg);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Hardware specification says: "ID_VALUE must be valid within
|
||||
* 50ms after idpullup is set to '1" so driver must wait
|
||||
* 50ms before reading this pin.
|
||||
*/
|
||||
usleep_range(50000, 60000);
|
||||
break;
|
||||
default:
|
||||
dev_err(cdns->dev, "Unsupported mode of operation %d\n", mode);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cdns_get_id(struct cdns *cdns)
|
||||
{
|
||||
int id;
|
||||
|
||||
id = readl(&cdns->otg_regs->sts) & OTGSTS_ID_VALUE;
|
||||
dev_dbg(cdns->dev, "OTG ID: %d", id);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
int cdns_get_vbus(struct cdns *cdns)
|
||||
{
|
||||
int vbus;
|
||||
|
||||
vbus = !!(readl(&cdns->otg_regs->sts) & OTGSTS_VBUS_VALID);
|
||||
dev_dbg(cdns->dev, "OTG VBUS: %d", vbus);
|
||||
|
||||
return vbus;
|
||||
}
|
||||
|
||||
void cdns_clear_vbus(struct cdns *cdns)
|
||||
{
|
||||
u32 reg;
|
||||
|
||||
if (cdns->version != CDNSP_CONTROLLER_V2)
|
||||
return;
|
||||
|
||||
reg = readl(&cdns->otg_cdnsp_regs->override);
|
||||
reg |= OVERRIDE_SESS_VLD_SEL;
|
||||
writel(reg, &cdns->otg_cdnsp_regs->override);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cdns_clear_vbus);
|
||||
|
||||
void cdns_set_vbus(struct cdns *cdns)
|
||||
{
|
||||
u32 reg;
|
||||
|
||||
if (cdns->version != CDNSP_CONTROLLER_V2)
|
||||
return;
|
||||
|
||||
reg = readl(&cdns->otg_cdnsp_regs->override);
|
||||
reg &= ~OVERRIDE_SESS_VLD_SEL;
|
||||
writel(reg, &cdns->otg_cdnsp_regs->override);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cdns_set_vbus);
|
||||
|
||||
bool cdns_is_host(struct cdns *cdns)
|
||||
{
|
||||
if (cdns->dr_mode == USB_DR_MODE_HOST)
|
||||
return true;
|
||||
else if (cdns_get_id(cdns) == CDNS3_ID_HOST)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool cdns_is_device(struct cdns *cdns)
|
||||
{
|
||||
if (cdns->dr_mode == USB_DR_MODE_PERIPHERAL)
|
||||
return true;
|
||||
else if (cdns->dr_mode == USB_DR_MODE_OTG)
|
||||
if (cdns_get_id(cdns) == CDNS3_ID_PERIPHERAL)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* cdns_otg_disable_irq - Disable all OTG interrupts
|
||||
* @cdns: Pointer to controller context structure
|
||||
*/
|
||||
static void cdns_otg_disable_irq(struct cdns *cdns)
|
||||
{
|
||||
if (cdns->version)
|
||||
writel(0, &cdns->otg_irq_regs->ien);
|
||||
}
|
||||
|
||||
/**
|
||||
* cdns_otg_enable_irq - enable id and sess_valid interrupts
|
||||
* @cdns: Pointer to controller context structure
|
||||
*/
|
||||
static void cdns_otg_enable_irq(struct cdns *cdns)
|
||||
{
|
||||
writel(OTGIEN_ID_CHANGE_INT | OTGIEN_VBUSVALID_RISE_INT |
|
||||
OTGIEN_VBUSVALID_FALL_INT, &cdns->otg_irq_regs->ien);
|
||||
}
|
||||
|
||||
/**
|
||||
* cdns_drd_host_on - start host.
|
||||
* @cdns: Pointer to controller context structure.
|
||||
*
|
||||
* Returns 0 on success otherwise negative errno.
|
||||
*/
|
||||
int cdns_drd_host_on(struct cdns *cdns)
|
||||
{
|
||||
u32 val, ready_bit;
|
||||
int ret;
|
||||
|
||||
/* Enable host mode. */
|
||||
writel(OTGCMD_HOST_BUS_REQ | OTGCMD_OTG_DIS,
|
||||
&cdns->otg_regs->cmd);
|
||||
|
||||
if (cdns->version == CDNSP_CONTROLLER_V2)
|
||||
ready_bit = OTGSTS_CDNSP_XHCI_READY;
|
||||
else
|
||||
ready_bit = OTGSTS_CDNS3_XHCI_READY;
|
||||
|
||||
dev_dbg(cdns->dev, "Waiting till Host mode is turned on\n");
|
||||
ret = readl_poll_timeout_atomic(&cdns->otg_regs->sts, val,
|
||||
val & ready_bit, 1, 100000);
|
||||
|
||||
if (ret)
|
||||
dev_err(cdns->dev, "timeout waiting for xhci_ready\n");
|
||||
|
||||
phy_set_mode(cdns->usb2_phy, PHY_MODE_USB_HOST);
|
||||
phy_set_mode(cdns->usb3_phy, PHY_MODE_USB_HOST);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* cdns_drd_host_off - stop host.
|
||||
* @cdns: Pointer to controller context structure.
|
||||
*/
|
||||
void cdns_drd_host_off(struct cdns *cdns)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
writel(OTGCMD_HOST_BUS_DROP | OTGCMD_DEV_BUS_DROP |
|
||||
OTGCMD_DEV_POWER_OFF | OTGCMD_HOST_POWER_OFF,
|
||||
&cdns->otg_regs->cmd);
|
||||
|
||||
/* Waiting till H_IDLE state.*/
|
||||
readl_poll_timeout_atomic(&cdns->otg_regs->state, val,
|
||||
!(val & OTGSTATE_HOST_STATE_MASK),
|
||||
1, 2000000);
|
||||
phy_set_mode(cdns->usb2_phy, PHY_MODE_INVALID);
|
||||
phy_set_mode(cdns->usb3_phy, PHY_MODE_INVALID);
|
||||
}
|
||||
|
||||
/**
|
||||
* cdns_drd_gadget_on - start gadget.
|
||||
* @cdns: Pointer to controller context structure.
|
||||
*
|
||||
* Returns 0 on success otherwise negative errno
|
||||
*/
|
||||
int cdns_drd_gadget_on(struct cdns *cdns)
|
||||
{
|
||||
u32 reg = OTGCMD_OTG_DIS;
|
||||
u32 ready_bit;
|
||||
int ret, val;
|
||||
|
||||
/* switch OTG core */
|
||||
writel(OTGCMD_DEV_BUS_REQ | reg, &cdns->otg_regs->cmd);
|
||||
|
||||
dev_dbg(cdns->dev, "Waiting till Device mode is turned on\n");
|
||||
|
||||
if (cdns->version == CDNSP_CONTROLLER_V2)
|
||||
ready_bit = OTGSTS_CDNSP_DEV_READY;
|
||||
else
|
||||
ready_bit = OTGSTS_CDNS3_DEV_READY;
|
||||
|
||||
ret = readl_poll_timeout_atomic(&cdns->otg_regs->sts, val,
|
||||
val & ready_bit, 1, 100000);
|
||||
if (ret) {
|
||||
dev_err(cdns->dev, "timeout waiting for dev_ready\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
phy_set_mode(cdns->usb2_phy, PHY_MODE_USB_DEVICE);
|
||||
phy_set_mode(cdns->usb3_phy, PHY_MODE_USB_DEVICE);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cdns_drd_gadget_on);
|
||||
|
||||
/**
|
||||
* cdns_drd_gadget_off - stop gadget.
|
||||
* @cdns: Pointer to controller context structure.
|
||||
*/
|
||||
void cdns_drd_gadget_off(struct cdns *cdns)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
/*
|
||||
* Driver should wait at least 10us after disabling Device
|
||||
* before turning-off Device (DEV_BUS_DROP).
|
||||
*/
|
||||
usleep_range(20, 30);
|
||||
writel(OTGCMD_HOST_BUS_DROP | OTGCMD_DEV_BUS_DROP |
|
||||
OTGCMD_DEV_POWER_OFF | OTGCMD_HOST_POWER_OFF,
|
||||
&cdns->otg_regs->cmd);
|
||||
/* Waiting till DEV_IDLE state.*/
|
||||
readl_poll_timeout_atomic(&cdns->otg_regs->state, val,
|
||||
!(val & OTGSTATE_DEV_STATE_MASK),
|
||||
1, 2000000);
|
||||
phy_set_mode(cdns->usb2_phy, PHY_MODE_INVALID);
|
||||
phy_set_mode(cdns->usb3_phy, PHY_MODE_INVALID);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cdns_drd_gadget_off);
|
||||
|
||||
/**
|
||||
* cdns_init_otg_mode - initialize drd controller
|
||||
* @cdns: Pointer to controller context structure
|
||||
*
|
||||
* Returns 0 on success otherwise negative errno
|
||||
*/
|
||||
static int cdns_init_otg_mode(struct cdns *cdns)
|
||||
{
|
||||
int ret;
|
||||
|
||||
cdns_otg_disable_irq(cdns);
|
||||
/* clear all interrupts */
|
||||
writel(~0, &cdns->otg_irq_regs->ivect);
|
||||
|
||||
ret = cdns_set_mode(cdns, USB_DR_MODE_OTG);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
cdns_otg_enable_irq(cdns);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* cdns_drd_update_mode - initialize mode of operation
|
||||
* @cdns: Pointer to controller context structure
|
||||
*
|
||||
* Returns 0 on success otherwise negative errno
|
||||
*/
|
||||
int cdns_drd_update_mode(struct cdns *cdns)
|
||||
{
|
||||
int ret;
|
||||
|
||||
switch (cdns->dr_mode) {
|
||||
case USB_DR_MODE_PERIPHERAL:
|
||||
ret = cdns_set_mode(cdns, USB_DR_MODE_PERIPHERAL);
|
||||
break;
|
||||
case USB_DR_MODE_HOST:
|
||||
ret = cdns_set_mode(cdns, USB_DR_MODE_HOST);
|
||||
break;
|
||||
case USB_DR_MODE_OTG:
|
||||
ret = cdns_init_otg_mode(cdns);
|
||||
break;
|
||||
default:
|
||||
dev_err(cdns->dev, "Unsupported mode of operation %d\n",
|
||||
cdns->dr_mode);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static irqreturn_t cdns_drd_thread_irq(int irq, void *data)
|
||||
{
|
||||
struct cdns *cdns = data;
|
||||
|
||||
cdns_hw_role_switch(cdns);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/**
|
||||
* cdns_drd_irq - interrupt handler for OTG events
|
||||
*
|
||||
* @irq: irq number for cdns core device
|
||||
* @data: structure of cdns
|
||||
*
|
||||
* Returns IRQ_HANDLED or IRQ_NONE
|
||||
*/
|
||||
static irqreturn_t cdns_drd_irq(int irq, void *data)
|
||||
{
|
||||
irqreturn_t ret = IRQ_NONE;
|
||||
struct cdns *cdns = data;
|
||||
u32 reg;
|
||||
|
||||
if (cdns->dr_mode != USB_DR_MODE_OTG)
|
||||
return IRQ_NONE;
|
||||
|
||||
if (cdns->in_lpm)
|
||||
return ret;
|
||||
|
||||
reg = readl(&cdns->otg_irq_regs->ivect);
|
||||
|
||||
if (!reg)
|
||||
return IRQ_NONE;
|
||||
|
||||
if (reg & OTGIEN_ID_CHANGE_INT) {
|
||||
dev_dbg(cdns->dev, "OTG IRQ: new ID: %d\n",
|
||||
cdns_get_id(cdns));
|
||||
|
||||
ret = IRQ_WAKE_THREAD;
|
||||
}
|
||||
|
||||
if (reg & (OTGIEN_VBUSVALID_RISE_INT | OTGIEN_VBUSVALID_FALL_INT)) {
|
||||
dev_dbg(cdns->dev, "OTG IRQ: new VBUS: %d\n",
|
||||
cdns_get_vbus(cdns));
|
||||
|
||||
ret = IRQ_WAKE_THREAD;
|
||||
}
|
||||
|
||||
writel(~0, &cdns->otg_irq_regs->ivect);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int cdns_drd_init(struct cdns *cdns)
|
||||
{
|
||||
void __iomem *regs;
|
||||
u32 state, reg;
|
||||
int ret;
|
||||
|
||||
regs = devm_ioremap_resource(cdns->dev, &cdns->otg_res);
|
||||
if (IS_ERR(regs))
|
||||
return PTR_ERR(regs);
|
||||
|
||||
/* Detection of DRD version. Controller has been released
|
||||
* in three versions. All are very similar and are software compatible,
|
||||
* but they have same changes in register maps.
|
||||
* The first register in oldest version is command register and it's
|
||||
* read only. Driver should read 0 from it. On the other hand, in v1
|
||||
* and v2 the first register contains device ID number which is not
|
||||
* set to 0. Driver uses this fact to detect the proper version of
|
||||
* controller.
|
||||
*/
|
||||
cdns->otg_v0_regs = regs;
|
||||
if (!readl(&cdns->otg_v0_regs->cmd)) {
|
||||
cdns->version = CDNS3_CONTROLLER_V0;
|
||||
cdns->otg_v1_regs = NULL;
|
||||
cdns->otg_cdnsp_regs = NULL;
|
||||
cdns->otg_regs = regs;
|
||||
cdns->otg_irq_regs = (struct cdns_otg_irq_regs __iomem *)
|
||||
&cdns->otg_v0_regs->ien;
|
||||
writel(1, &cdns->otg_v0_regs->simulate);
|
||||
dev_dbg(cdns->dev, "DRD version v0 (%08x)\n",
|
||||
readl(&cdns->otg_v0_regs->version));
|
||||
} else {
|
||||
cdns->otg_v0_regs = NULL;
|
||||
cdns->otg_v1_regs = regs;
|
||||
cdns->otg_cdnsp_regs = regs;
|
||||
|
||||
cdns->otg_regs = (void __iomem *)&cdns->otg_v1_regs->cmd;
|
||||
|
||||
state = readl(&cdns->otg_cdnsp_regs->did);
|
||||
|
||||
if (OTG_CDNSP_CHECK_DID(state)) {
|
||||
cdns->otg_irq_regs = (struct cdns_otg_irq_regs __iomem *)
|
||||
&cdns->otg_cdnsp_regs->ien;
|
||||
cdns->version = CDNSP_CONTROLLER_V2;
|
||||
} else if (OTG_CDNS3_CHECK_DID(state)) {
|
||||
cdns->otg_irq_regs = (struct cdns_otg_irq_regs __iomem *)
|
||||
&cdns->otg_v1_regs->ien;
|
||||
writel(1, &cdns->otg_v1_regs->simulate);
|
||||
|
||||
if (cdns->pdata &&
|
||||
(cdns->pdata->quirks & CDNS3_DRD_SUSPEND_RESIDENCY_ENABLE)) {
|
||||
reg = readl(&cdns->otg_v1_regs->susp_ctrl);
|
||||
reg |= SUSP_CTRL_SUSPEND_RESIDENCY_ENABLE;
|
||||
writel(reg, &cdns->otg_v1_regs->susp_ctrl);
|
||||
}
|
||||
|
||||
cdns->version = CDNS3_CONTROLLER_V1;
|
||||
} else {
|
||||
dev_err(cdns->dev, "not supported DID=0x%08x\n", state);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dev_dbg(cdns->dev, "DRD version v1 (ID: %08x, rev: %08x)\n",
|
||||
readl(&cdns->otg_v1_regs->did),
|
||||
readl(&cdns->otg_v1_regs->rid));
|
||||
}
|
||||
|
||||
state = OTGSTS_STRAP(readl(&cdns->otg_regs->sts));
|
||||
|
||||
/* Update dr_mode according to STRAP configuration. */
|
||||
cdns->dr_mode = USB_DR_MODE_OTG;
|
||||
|
||||
if ((cdns->version == CDNSP_CONTROLLER_V2 &&
|
||||
state == OTGSTS_CDNSP_STRAP_HOST) ||
|
||||
(cdns->version != CDNSP_CONTROLLER_V2 &&
|
||||
state == OTGSTS_STRAP_HOST)) {
|
||||
dev_dbg(cdns->dev, "Controller strapped to HOST\n");
|
||||
cdns->dr_mode = USB_DR_MODE_HOST;
|
||||
} else if ((cdns->version == CDNSP_CONTROLLER_V2 &&
|
||||
state == OTGSTS_CDNSP_STRAP_GADGET) ||
|
||||
(cdns->version != CDNSP_CONTROLLER_V2 &&
|
||||
state == OTGSTS_STRAP_GADGET)) {
|
||||
dev_dbg(cdns->dev, "Controller strapped to PERIPHERAL\n");
|
||||
cdns->dr_mode = USB_DR_MODE_PERIPHERAL;
|
||||
}
|
||||
|
||||
ret = devm_request_threaded_irq(cdns->dev, cdns->otg_irq,
|
||||
cdns_drd_irq,
|
||||
cdns_drd_thread_irq,
|
||||
IRQF_SHARED,
|
||||
dev_name(cdns->dev), cdns);
|
||||
if (ret) {
|
||||
dev_err(cdns->dev, "couldn't get otg_irq\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
state = readl(&cdns->otg_regs->sts);
|
||||
if (OTGSTS_OTG_NRDY(state)) {
|
||||
dev_err(cdns->dev, "Cadence USB3 OTG device not ready\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cdns_drd_exit(struct cdns *cdns)
|
||||
{
|
||||
cdns_otg_disable_irq(cdns);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Indicate the cdns3 core was power lost before */
|
||||
bool cdns_power_is_lost(struct cdns *cdns)
|
||||
{
|
||||
if (cdns->version == CDNS3_CONTROLLER_V0) {
|
||||
if (!(readl(&cdns->otg_v0_regs->simulate) & BIT(0)))
|
||||
return true;
|
||||
} else {
|
||||
if (!(readl(&cdns->otg_v1_regs->simulate) & BIT(0)))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cdns_power_is_lost);
|
226
drivers/usb/cdns3/drd.h
Normal file
226
drivers/usb/cdns3/drd.h
Normal file
|
@ -0,0 +1,226 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Cadence USB3 and USBSSP DRD header file.
|
||||
*
|
||||
* Copyright (C) 2018-2020 Cadence.
|
||||
*
|
||||
* Author: Pawel Laszczak <pawell@cadence.com>
|
||||
*/
|
||||
#ifndef __LINUX_CDNS3_DRD
|
||||
#define __LINUX_CDNS3_DRD
|
||||
|
||||
#include <linux/usb/otg.h>
|
||||
#include "core.h"
|
||||
|
||||
/* DRD register interface for version v1 of cdns3 driver. */
|
||||
struct cdns3_otg_regs {
|
||||
__le32 did;
|
||||
__le32 rid;
|
||||
__le32 capabilities;
|
||||
__le32 reserved1;
|
||||
__le32 cmd;
|
||||
__le32 sts;
|
||||
__le32 state;
|
||||
__le32 reserved2;
|
||||
__le32 ien;
|
||||
__le32 ivect;
|
||||
__le32 refclk;
|
||||
__le32 tmr;
|
||||
__le32 reserved3[4];
|
||||
__le32 simulate;
|
||||
__le32 override;
|
||||
__le32 susp_ctrl;
|
||||
__le32 phyrst_cfg;
|
||||
__le32 anasts;
|
||||
__le32 adp_ramp_time;
|
||||
__le32 ctrl1;
|
||||
__le32 ctrl2;
|
||||
};
|
||||
|
||||
/* DRD register interface for version v0 of cdns3 driver. */
|
||||
struct cdns3_otg_legacy_regs {
|
||||
__le32 cmd;
|
||||
__le32 sts;
|
||||
__le32 state;
|
||||
__le32 refclk;
|
||||
__le32 ien;
|
||||
__le32 ivect;
|
||||
__le32 reserved1[3];
|
||||
__le32 tmr;
|
||||
__le32 reserved2[2];
|
||||
__le32 version;
|
||||
__le32 capabilities;
|
||||
__le32 reserved3[2];
|
||||
__le32 simulate;
|
||||
__le32 reserved4[5];
|
||||
__le32 ctrl1;
|
||||
};
|
||||
|
||||
/* DRD register interface for cdnsp driver */
|
||||
struct cdnsp_otg_regs {
|
||||
__le32 did;
|
||||
__le32 rid;
|
||||
__le32 cfgs1;
|
||||
__le32 cfgs2;
|
||||
__le32 cmd;
|
||||
__le32 sts;
|
||||
__le32 state;
|
||||
__le32 ien;
|
||||
__le32 ivect;
|
||||
__le32 tmr;
|
||||
__le32 simulate;
|
||||
__le32 adpbc_sts;
|
||||
__le32 adp_ramp_time;
|
||||
__le32 adpbc_ctrl1;
|
||||
__le32 adpbc_ctrl2;
|
||||
__le32 override;
|
||||
__le32 vbusvalid_dbnc_cfg;
|
||||
__le32 sessvalid_dbnc_cfg;
|
||||
__le32 susp_timing_ctrl;
|
||||
};
|
||||
|
||||
/* CDNSP driver supports 0x000403xx Cadence USB controller family. */
|
||||
#define OTG_CDNSP_CHECK_DID(did) (((did) & GENMASK(31, 8)) == 0x00040300)
|
||||
|
||||
/* CDNS3 driver supports 0x000402xx Cadence USB controller family. */
|
||||
#define OTG_CDNS3_CHECK_DID(did) (((did) & GENMASK(31, 8)) == 0x00040200)
|
||||
|
||||
/*
|
||||
* Common registers interface for both CDNS3 and CDNSP version of DRD.
|
||||
*/
|
||||
struct cdns_otg_common_regs {
|
||||
__le32 cmd;
|
||||
__le32 sts;
|
||||
__le32 state;
|
||||
};
|
||||
|
||||
/*
|
||||
* Interrupt related registers. This registers are mapped in different
|
||||
* location for CDNSP controller.
|
||||
*/
|
||||
struct cdns_otg_irq_regs {
|
||||
__le32 ien;
|
||||
__le32 ivect;
|
||||
};
|
||||
|
||||
/* CDNS_RID - bitmasks */
|
||||
#define CDNS_RID(p) ((p) & GENMASK(15, 0))
|
||||
|
||||
/* CDNS_VID - bitmasks */
|
||||
#define CDNS_DID(p) ((p) & GENMASK(31, 0))
|
||||
|
||||
/* OTGCMD - bitmasks */
|
||||
/* "Request the bus for Device mode. */
|
||||
#define OTGCMD_DEV_BUS_REQ BIT(0)
|
||||
/* Request the bus for Host mode */
|
||||
#define OTGCMD_HOST_BUS_REQ BIT(1)
|
||||
/* Enable OTG mode. */
|
||||
#define OTGCMD_OTG_EN BIT(2)
|
||||
/* Disable OTG mode */
|
||||
#define OTGCMD_OTG_DIS BIT(3)
|
||||
/*"Configure OTG as A-Device. */
|
||||
#define OTGCMD_A_DEV_EN BIT(4)
|
||||
/*"Configure OTG as A-Device. */
|
||||
#define OTGCMD_A_DEV_DIS BIT(5)
|
||||
/* Drop the bus for Device mod e. */
|
||||
#define OTGCMD_DEV_BUS_DROP BIT(8)
|
||||
/* Drop the bus for Host mode*/
|
||||
#define OTGCMD_HOST_BUS_DROP BIT(9)
|
||||
/* Power Down USBSS-DEV - only for CDNS3.*/
|
||||
#define OTGCMD_DEV_POWER_OFF BIT(11)
|
||||
/* Power Down CDNSXHCI - only for CDNS3. */
|
||||
#define OTGCMD_HOST_POWER_OFF BIT(12)
|
||||
|
||||
/* OTGIEN - bitmasks */
|
||||
/* ID change interrupt enable */
|
||||
#define OTGIEN_ID_CHANGE_INT BIT(0)
|
||||
/* Vbusvalid fall detected interrupt enable.*/
|
||||
#define OTGIEN_VBUSVALID_RISE_INT BIT(4)
|
||||
/* Vbusvalid fall detected interrupt enable */
|
||||
#define OTGIEN_VBUSVALID_FALL_INT BIT(5)
|
||||
|
||||
/* OTGSTS - bitmasks */
|
||||
/*
|
||||
* Current value of the ID pin. It is only valid when idpullup in
|
||||
* OTGCTRL1_TYPE register is set to '1'.
|
||||
*/
|
||||
#define OTGSTS_ID_VALUE BIT(0)
|
||||
/* Current value of the vbus_valid */
|
||||
#define OTGSTS_VBUS_VALID BIT(1)
|
||||
/* Current value of the b_sess_vld */
|
||||
#define OTGSTS_SESSION_VALID BIT(2)
|
||||
/*Device mode is active*/
|
||||
#define OTGSTS_DEV_ACTIVE BIT(3)
|
||||
/* Host mode is active. */
|
||||
#define OTGSTS_HOST_ACTIVE BIT(4)
|
||||
/* OTG Controller not ready. */
|
||||
#define OTGSTS_OTG_NRDY_MASK BIT(11)
|
||||
#define OTGSTS_OTG_NRDY(p) ((p) & OTGSTS_OTG_NRDY_MASK)
|
||||
/*
|
||||
* Value of the strap pins for:
|
||||
* CDNS3:
|
||||
* 000 - no default configuration
|
||||
* 010 - Controller initiall configured as Host
|
||||
* 100 - Controller initially configured as Device
|
||||
* CDNSP:
|
||||
* 000 - No default configuration.
|
||||
* 010 - Controller initiall configured as Host.
|
||||
* 100 - Controller initially configured as Device.
|
||||
*/
|
||||
#define OTGSTS_STRAP(p) (((p) & GENMASK(14, 12)) >> 12)
|
||||
#define OTGSTS_STRAP_NO_DEFAULT_CFG 0x00
|
||||
#define OTGSTS_STRAP_HOST_OTG 0x01
|
||||
#define OTGSTS_STRAP_HOST 0x02
|
||||
#define OTGSTS_STRAP_GADGET 0x04
|
||||
#define OTGSTS_CDNSP_STRAP_HOST 0x01
|
||||
#define OTGSTS_CDNSP_STRAP_GADGET 0x02
|
||||
|
||||
/* Host mode is turned on. */
|
||||
#define OTGSTS_CDNS3_XHCI_READY BIT(26)
|
||||
#define OTGSTS_CDNSP_XHCI_READY BIT(27)
|
||||
|
||||
/* "Device mode is turned on .*/
|
||||
#define OTGSTS_CDNS3_DEV_READY BIT(27)
|
||||
#define OTGSTS_CDNSP_DEV_READY BIT(26)
|
||||
|
||||
/* OTGSTATE- bitmasks */
|
||||
#define OTGSTATE_DEV_STATE_MASK GENMASK(2, 0)
|
||||
#define OTGSTATE_HOST_STATE_MASK GENMASK(5, 3)
|
||||
#define OTGSTATE_HOST_STATE_IDLE 0x0
|
||||
#define OTGSTATE_HOST_STATE_VBUS_FALL 0x7
|
||||
#define OTGSTATE_HOST_STATE(p) (((p) & OTGSTATE_HOST_STATE_MASK) >> 3)
|
||||
|
||||
/* OTGREFCLK - bitmasks */
|
||||
#define OTGREFCLK_STB_CLK_SWITCH_EN BIT(31)
|
||||
|
||||
/* SUPS_CTRL - bitmasks */
|
||||
#define SUSP_CTRL_SUSPEND_RESIDENCY_ENABLE BIT(17)
|
||||
|
||||
/* OVERRIDE - bitmasks */
|
||||
#define OVERRIDE_IDPULLUP BIT(0)
|
||||
/* Only for CDNS3_CONTROLLER_V0 version */
|
||||
#define OVERRIDE_IDPULLUP_V0 BIT(24)
|
||||
/* Vbusvalid/Sesvalid override select. */
|
||||
#define OVERRIDE_SESS_VLD_SEL BIT(10)
|
||||
|
||||
/* PHYRST_CFG - bitmasks */
|
||||
#define PHYRST_CFG_PHYRST_A_ENABLE BIT(0)
|
||||
|
||||
#define CDNS3_ID_PERIPHERAL 1
|
||||
#define CDNS3_ID_HOST 0
|
||||
|
||||
bool cdns_is_host(struct cdns *cdns);
|
||||
bool cdns_is_device(struct cdns *cdns);
|
||||
int cdns_get_id(struct cdns *cdns);
|
||||
int cdns_get_vbus(struct cdns *cdns);
|
||||
void cdns_clear_vbus(struct cdns *cdns);
|
||||
void cdns_set_vbus(struct cdns *cdns);
|
||||
int cdns_drd_init(struct cdns *cdns);
|
||||
int cdns_drd_exit(struct cdns *cdns);
|
||||
int cdns_drd_update_mode(struct cdns *cdns);
|
||||
int cdns_drd_gadget_on(struct cdns *cdns);
|
||||
void cdns_drd_gadget_off(struct cdns *cdns);
|
||||
int cdns_drd_host_on(struct cdns *cdns);
|
||||
void cdns_drd_host_off(struct cdns *cdns);
|
||||
bool cdns_power_is_lost(struct cdns *cdns);
|
||||
#endif /* __LINUX_CDNS3_DRD */
|
37
drivers/usb/cdns3/gadget-export.h
Normal file
37
drivers/usb/cdns3/gadget-export.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Cadence USBSS and USBSSP DRD Driver - Gadget Export APIs.
|
||||
*
|
||||
* Copyright (C) 2017 NXP
|
||||
* Copyright (C) 2017-2018 NXP
|
||||
*
|
||||
* Authors: Peter Chen <peter.chen@nxp.com>
|
||||
*/
|
||||
#ifndef __LINUX_CDNS3_GADGET_EXPORT
|
||||
#define __LINUX_CDNS3_GADGET_EXPORT
|
||||
|
||||
#if IS_ENABLED(CONFIG_USB_CDNSP_GADGET)
|
||||
|
||||
int cdnsp_gadget_init(struct cdns *cdns);
|
||||
#else
|
||||
|
||||
static inline int cdnsp_gadget_init(struct cdns *cdns)
|
||||
{
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
#endif /* CONFIG_USB_CDNSP_GADGET */
|
||||
|
||||
#if IS_ENABLED(CONFIG_USB_CDNS3_GADGET)
|
||||
|
||||
int cdns3_gadget_init(struct cdns *cdns);
|
||||
#else
|
||||
|
||||
static inline int cdns3_gadget_init(struct cdns *cdns)
|
||||
{
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
#endif /* CONFIG_USB_CDNS3_GADGET */
|
||||
|
||||
#endif /* __LINUX_CDNS3_GADGET_EXPORT */
|
27
drivers/usb/cdns3/host-export.h
Normal file
27
drivers/usb/cdns3/host-export.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Cadence USBSS and USBSSP DRD Driver - Host Export APIs
|
||||
*
|
||||
* Copyright (C) 2017-2018 NXP
|
||||
*
|
||||
* Authors: Peter Chen <peter.chen@nxp.com>
|
||||
*/
|
||||
#ifndef __LINUX_CDNS3_HOST_EXPORT
|
||||
#define __LINUX_CDNS3_HOST_EXPORT
|
||||
|
||||
#if IS_ENABLED(CONFIG_USB_CDNS_HOST)
|
||||
|
||||
int cdns_host_init(struct cdns *cdns);
|
||||
|
||||
#else
|
||||
|
||||
static inline int cdns_host_init(struct cdns *cdns)
|
||||
{
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
static inline void cdns_host_exit(struct cdns *cdns) { }
|
||||
|
||||
#endif /* USB_CDNS_HOST */
|
||||
|
||||
#endif /* __LINUX_CDNS3_HOST_EXPORT */
|
157
drivers/usb/cdns3/host.c
Normal file
157
drivers/usb/cdns3/host.c
Normal file
|
@ -0,0 +1,157 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Cadence USBSS and USBSSP DRD Driver - host side
|
||||
*
|
||||
* Copyright (C) 2018-2019 Cadence Design Systems.
|
||||
* Copyright (C) 2017-2018 NXP
|
||||
*
|
||||
* Authors: Peter Chen <peter.chen@nxp.com>
|
||||
* Pawel Laszczak <pawell@cadence.com>
|
||||
*/
|
||||
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include "core.h"
|
||||
#include "drd.h"
|
||||
#include "host-export.h"
|
||||
#include <linux/usb/hcd.h>
|
||||
#include "../host/xhci.h"
|
||||
#include "../host/xhci-plat.h"
|
||||
|
||||
/*
|
||||
* The XECP_PORT_CAP_REG and XECP_AUX_CTRL_REG1 exist only
|
||||
* in Cadence USB3 dual-role controller, so it can't be used
|
||||
* with Cadence CDNSP dual-role controller.
|
||||
*/
|
||||
#define XECP_PORT_CAP_REG 0x8000
|
||||
#define XECP_AUX_CTRL_REG1 0x8120
|
||||
|
||||
#define CFG_RXDET_P3_EN BIT(15)
|
||||
#define LPM_2_STB_SWITCH_EN BIT(25)
|
||||
|
||||
static void xhci_cdns3_plat_start(struct usb_hcd *hcd)
|
||||
{
|
||||
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
|
||||
u32 value;
|
||||
|
||||
/* set usbcmd.EU3S */
|
||||
value = readl(&xhci->op_regs->command);
|
||||
value |= CMD_PM_INDEX;
|
||||
writel(value, &xhci->op_regs->command);
|
||||
|
||||
if (hcd->regs) {
|
||||
value = readl(hcd->regs + XECP_AUX_CTRL_REG1);
|
||||
value |= CFG_RXDET_P3_EN;
|
||||
writel(value, hcd->regs + XECP_AUX_CTRL_REG1);
|
||||
|
||||
value = readl(hcd->regs + XECP_PORT_CAP_REG);
|
||||
value |= LPM_2_STB_SWITCH_EN;
|
||||
writel(value, hcd->regs + XECP_PORT_CAP_REG);
|
||||
}
|
||||
}
|
||||
|
||||
static int xhci_cdns3_resume_quirk(struct usb_hcd *hcd)
|
||||
{
|
||||
xhci_cdns3_plat_start(hcd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct xhci_plat_priv xhci_plat_cdns3_xhci = {
|
||||
.quirks = XHCI_SKIP_PHY_INIT | XHCI_AVOID_BEI,
|
||||
.plat_start = xhci_cdns3_plat_start,
|
||||
.resume_quirk = xhci_cdns3_resume_quirk,
|
||||
};
|
||||
|
||||
static const struct xhci_plat_priv xhci_plat_cdnsp_xhci = {
|
||||
.quirks = XHCI_CDNS_SCTX_QUIRK,
|
||||
};
|
||||
|
||||
static int __cdns_host_init(struct cdns *cdns)
|
||||
{
|
||||
struct platform_device *xhci;
|
||||
int ret;
|
||||
struct usb_hcd *hcd;
|
||||
|
||||
cdns_drd_host_on(cdns);
|
||||
|
||||
xhci = platform_device_alloc("xhci-hcd", PLATFORM_DEVID_AUTO);
|
||||
if (!xhci) {
|
||||
dev_err(cdns->dev, "couldn't allocate xHCI device\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
xhci->dev.parent = cdns->dev;
|
||||
cdns->host_dev = xhci;
|
||||
|
||||
ret = platform_device_add_resources(xhci, cdns->xhci_res,
|
||||
CDNS_XHCI_RESOURCES_NUM);
|
||||
if (ret) {
|
||||
dev_err(cdns->dev, "couldn't add resources to xHCI device\n");
|
||||
goto err1;
|
||||
}
|
||||
|
||||
if (cdns->version < CDNSP_CONTROLLER_V2)
|
||||
cdns->xhci_plat_data = kmemdup(&xhci_plat_cdns3_xhci,
|
||||
sizeof(struct xhci_plat_priv), GFP_KERNEL);
|
||||
else
|
||||
cdns->xhci_plat_data = kmemdup(&xhci_plat_cdnsp_xhci,
|
||||
sizeof(struct xhci_plat_priv), GFP_KERNEL);
|
||||
|
||||
if (!cdns->xhci_plat_data) {
|
||||
ret = -ENOMEM;
|
||||
goto err1;
|
||||
}
|
||||
|
||||
if (cdns->pdata && (cdns->pdata->quirks & CDNS3_DEFAULT_PM_RUNTIME_ALLOW))
|
||||
cdns->xhci_plat_data->quirks |= XHCI_DEFAULT_PM_RUNTIME_ALLOW;
|
||||
|
||||
ret = platform_device_add_data(xhci, cdns->xhci_plat_data,
|
||||
sizeof(struct xhci_plat_priv));
|
||||
if (ret)
|
||||
goto free_memory;
|
||||
|
||||
ret = platform_device_add(xhci);
|
||||
if (ret) {
|
||||
dev_err(cdns->dev, "failed to register xHCI device\n");
|
||||
goto free_memory;
|
||||
}
|
||||
|
||||
/* Glue needs to access xHCI region register for Power management */
|
||||
hcd = platform_get_drvdata(xhci);
|
||||
if (hcd)
|
||||
cdns->xhci_regs = hcd->regs;
|
||||
|
||||
return 0;
|
||||
|
||||
free_memory:
|
||||
kfree(cdns->xhci_plat_data);
|
||||
err1:
|
||||
platform_device_put(xhci);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void cdns_host_exit(struct cdns *cdns)
|
||||
{
|
||||
kfree(cdns->xhci_plat_data);
|
||||
platform_device_unregister(cdns->host_dev);
|
||||
cdns->host_dev = NULL;
|
||||
cdns_drd_host_off(cdns);
|
||||
}
|
||||
|
||||
int cdns_host_init(struct cdns *cdns)
|
||||
{
|
||||
struct cdns_role_driver *rdrv;
|
||||
|
||||
rdrv = devm_kzalloc(cdns->dev, sizeof(*rdrv), GFP_KERNEL);
|
||||
if (!rdrv)
|
||||
return -ENOMEM;
|
||||
|
||||
rdrv->start = __cdns_host_init;
|
||||
rdrv->stop = cdns_host_exit;
|
||||
rdrv->state = CDNS_ROLE_STATE_INACTIVE;
|
||||
rdrv->name = "host";
|
||||
|
||||
cdns->roles[USB_ROLE_HOST] = rdrv;
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue