Unverified Commit 3d829045 authored by Pawel Laszczak's avatar Pawel Laszczak Committed by Peter Chen
Browse files

usb: cdnsp: cdns3 Add main part of Cadence USBSSP DRD Driver



This patch introduces the main part of Cadence USBSSP DRD driver
to Linux kernel.
To reduce the patch size a little bit, the header file gadget.h was
intentionally added as separate patch.

The Cadence USBSSP DRD Controller is a highly configurable IP Core which
can be instantiated as Dual-Role Device (DRD), Peripheral Only and
Host Only (XHCI)configurations.

The current driver has been validated with FPGA platform. We have
support for PCIe bus, which is used on FPGA prototyping.

The host side of USBSS DRD controller is compliant with XHCI.
The architecture for device side is almost the same as for host side,
and most of the XHCI specification can be used to understand how
this controller operates.

Signed-off-by: default avatarPawel Laszczak <pawell@cadence.com>
Signed-off-by: default avatarPeter Chen <peter.chen@nxp.com>
parent e93e58d2
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -13,7 +13,9 @@ obj-$(CONFIG_USB_DWC3) += dwc3/
obj-$(CONFIG_USB_DWC2)		+= dwc2/
obj-$(CONFIG_USB_ISP1760)	+= isp1760/

obj-$(CONFIG_USB_CDNS_SUPPORT)	+= cdns3/
obj-$(CONFIG_USB_CDNS3)		+= cdns3/
obj-$(CONFIG_USB_CDNSP_PCI)	+= cdns3/

obj-$(CONFIG_USB_MON)		+= mon/
obj-$(CONFIG_USB_MTU3)		+= mtu3/
+56 −8
Original line number Diff line number Diff line
config CDNS_USB_COMMON
	tristate
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 CDNS_USB_HOST
config USB_CDNS_HOST
	bool

if USB_CDNS_SUPPORT

config USB_CDNS3
	tristate "Cadence USB3 Dual-Role Controller"
	depends on USB_SUPPORT && (USB || USB_GADGET) && HAS_DMA
	select USB_XHCI_PLATFORM if USB_XHCI_HCD
	select USB_ROLE_SWITCH
	select CDNS_USB_COMMON
	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

@@ -32,7 +39,7 @@ config USB_CDNS3_GADGET
config USB_CDNS3_HOST
	bool "Cadence USB3 host controller"
	depends on USB=y || USB=USB_CDNS3
	select CDNS_USB_HOST
	select USB_CDNS_HOST
	help
	  Say Y here to enable host controller functionality of the
	  Cadence driver.
@@ -72,3 +79,44 @@ config USB_CDNS3_IMX
	  For example, imx8qm and imx8qxp.

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
+16 −11
Original line number Diff line number Diff line
@@ -6,9 +6,9 @@ cdns-usb-common-y := core.o drd.o
cdns3-y						:= cdns3-plat.o

obj-$(CONFIG_USB_CDNS3)				+= cdns3.o
obj-$(CONFIG_CDNS_USB_COMMON)		+= cdns-usb-common.o
obj-$(CONFIG_USB_CDNS_SUPPORT)			+= cdns-usb-common.o

cdns-usb-common-$(CONFIG_CDNS_USB_HOST) += host.o
cdns-usb-common-$(CONFIG_USB_CDNS_HOST) 	+= host.o
cdns3-$(CONFIG_USB_CDNS3_GADGET)		+= gadget.o ep0.o

ifneq ($(CONFIG_USB_CDNS3_GADGET),)
@@ -18,3 +18,8 @@ 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

cdnsp-udc-pci-y					:= cdnsp-pci.o
obj-$(CONFIG_USB_CDNSP_PCI) 			+= cdnsp-udc-pci.o
cdnsp-udc-pci-$(CONFIG_USB_CDNSP_GADGET)	+= cdnsp-ring.o cdnsp-gadget.o \
						   cdnsp-mem.o cdnsp-ep0.o
+477 −0
Original line number Diff line number Diff line
// 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"

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:
		break;
	case USB_STATE_CONFIGURED:
		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(__le32  wIndex)
{
	wIndex = le32_to_cpu(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:
		pep = &pdev->eps[cdnsp_w_index_to_ep_index(ctrl->wIndex)];
		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;
		break;
	case USB_DEVICE_U1_ENABLE:
		if (state != USB_STATE_CONFIGURED || speed < USB_SPEED_SUPER)
			return -EINVAL;

		pdev->u1_allowed = !!set;
		break;
	case USB_DEVICE_U2_ENABLE:
		if (state != USB_STATE_CONFIGURED || speed < USB_SPEED_SUPER)
			return -EINVAL;

		pdev->u2_allowed = !!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;
	u32 wValue;

	wValue = le16_to_cpu(ctrl->wValue);
	pep = &pdev->eps[cdnsp_w_index_to_ep_index(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;
	case USB_REQ_SET_INTERFACE:
		/*
		 * Add request into pending list to block sending status stage
		 * by libcomposite.
		 */
		list_add_tail(&pdev->ep0_preq.list,
			      &pdev->ep0_preq.pep->pending_list);

		ret = cdnsp_ep0_delegate_req(pdev, ctrl);
		if (ret == -EBUSY)
			ret = 0;

		list_del(&pdev->ep0_preq.list);
		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 = 0;
	__le16 len;

	if (!pdev->gadget_driver)
		goto out;

	if (pdev->gadget.state == USB_STATE_NOTATTACHED) {
		dev_err(pdev->dev, "ERR: Setup detected in unattached state\n");
		ret = -EINVAL;
		goto out;
	}

	/* Restore the ep0 to Stopped/Running state. */
	if (pdev->eps[0].ep_state & EP_HALTED)
		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;

		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 (!len)
		pdev->ep0_stage = CDNSP_STATUS_STAGE;

	if (ret == USB_GADGET_DELAYED_STATUS)
		return;
out:
	if (ret < 0)
		cdnsp_ep0_stall(pdev);
	else if (pdev->ep0_stage == CDNSP_STATUS_STAGE)
		cdnsp_status_stage(pdev);
}
+1954 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading