1
0
Fork 0

Adding upstream version 6.12.33.

Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
This commit is contained in:
Daniel Baumann 2025-06-22 12:14:28 +02:00
parent 89eabb05c2
commit 79d69e5050
Signed by: daniel.baumann
GPG key ID: BCC918A2ABD66424
86698 changed files with 39662057 additions and 0 deletions

133
drivers/usb/cdns3/Kconfig Normal file
View 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

View 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

View 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*/

View 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, &regs->ep_sts);
writel(EP_TRADDR_TRADDR(priv_ep->trb_pool_dma), &regs->ep_traddr);
trace_cdns3_doorbell_ep0(priv_dev->ep0_data_dir ? "ep0in" : "ep0out",
readl(&regs->ep_traddr));
/* TRB should be prepared before starting transfer. */
writel(EP_CMD_DRDY, &regs->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, &regs->ep_cfg);
writel(EP_STS_EN_SETUPEN | EP_STS_EN_DESCMISEN | EP_STS_EN_TRBERREN,
&regs->ep_sts_en);
/* init ep in */
cdns3_select_ep(priv_dev, USB_DIR_IN);
if (!(priv_ep->flags & EP_CONFIGURED))
writel(ep_cfg, &regs->ep_cfg);
priv_ep->flags |= EP_CONFIGURED;
writel(EP_STS_EN_SETUPEN | EP_STS_EN_TRBERREN, &regs->ep_sts_en);
cdns3_set_register_bit(&regs->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);
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View 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");

View 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");

View 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");

View 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");

View 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");

View 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"

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

View 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*/

View 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);
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View 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");

File diff suppressed because it is too large Load diff

View 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"

View 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
View 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
View 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
View 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
View 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 */

View 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 */

View 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
View 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;
}