Merge branch 'master' of git://git.denx.de/u-boot-usb

- Assorted fixes
This commit is contained in:
Tom Rini 2019-11-08 14:05:07 -05:00
commit a4b7485e2f
28 changed files with 6859 additions and 7 deletions

View File

@ -732,6 +732,7 @@ libs-$(CONFIG_SYS_FSL_DDR) += drivers/ddr/fsl/
libs-$(CONFIG_SYS_FSL_MMDC) += drivers/ddr/fsl/
libs-$(CONFIG_$(SPL_)ALTERA_SDRAM) += drivers/ddr/altera/
libs-y += drivers/serial/
libs-y += drivers/usb/cdns3/
libs-y += drivers/usb/dwc3/
libs-y += drivers/usb/common/
libs-y += drivers/usb/emul/

View File

@ -68,6 +68,8 @@ config SPL_DM_USB_GADGET
source "drivers/usb/host/Kconfig"
source "drivers/usb/cdns3/Kconfig"
source "drivers/usb/dwc3/Kconfig"
source "drivers/usb/musb/Kconfig"

58
drivers/usb/cdns3/Kconfig Normal file
View File

@ -0,0 +1,58 @@
config USB_CDNS3
tristate "Cadence USB3 Dual-Role Controller"
depends on USB_HOST || USB_GADGET
help
Say Y here if your system has a Cadence USB3 dual-role controller.
It supports: Host-only, and Peripheral-only.
if USB_CDNS3
config USB_CDNS3_GADGET
bool "Cadence USB3 device controller"
depends on USB_GADGET
select USB_GADGET_DUALSPEED
help
Say Y here to enable device controller functionality of the
Cadence USBSS-DEV driver.
This controller supports FF and HS mode. It doesn't support
LS and SSP mode.
config USB_CDNS3_HOST
bool "Cadence USB3 host controller"
depends on USB_XHCI_HCD
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 SPL_USB_CDNS3_GADGET
bool "SPL support for Cadence USB3 device controller"
depends on SPL_USB_GADGET
select USB_GADGET_DUALSPEED
help
Say Y here to enable device controller functionality of the
Cadence USBSS-DEV driver in SPL.
This controller supports FF and HS mode. It doesn't support
LS and SSP mode.
config SPL_USB_CDNS3_HOST
bool "Cadence USB3 host controller"
depends on USB_XHCI_HCD && SPL_USB_HOST_SUPPORT
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_TI
tristate "Cadence USB3 support on TI platforms"
default USB_CDNS3
help
Say 'Y' here if you are building for Texas Instruments
platforms that contain Cadence USB3 controller core. E.g.: J721e.
endif

View File

@ -0,0 +1,11 @@
# SPDX-License-Identifier: GPL-2.0
cdns3-y := core.o drd.o
obj-$(CONFIG_USB_CDNS3) += cdns3.o
cdns3-$(CONFIG_$(SPL_)USB_CDNS3_GADGET) += gadget.o ep0.o
cdns3-$(CONFIG_$(SPL_)USB_CDNS3_HOST) += host.o
obj-$(CONFIG_USB_CDNS3_TI) += cdns3-ti.o

View File

@ -0,0 +1,193 @@
// SPDX-License-Identifier: GPL-2.0
/**
* cdns_ti-ti.c - TI specific Glue layer for Cadence USB Controller
*
* Copyright (C) 2019 Texas Instruments Incorporated - http://www.ti.com
*/
#include <common.h>
#include <asm-generic/io.h>
#include <clk.h>
#include <dm.h>
#include <linux/io.h>
#include <linux/usb/otg.h>
#include <malloc.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 udevice *dev;
void __iomem *usbss;
int usb2_only:1;
int 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 int cdns_ti_probe(struct udevice *dev)
{
struct cdns_ti *data = dev_get_platdata(dev);
struct clk usb2_refclk;
int modestrap_mode;
unsigned long rate;
int rate_code, i;
u32 reg;
int ret;
data->dev = dev;
data->usbss = dev_remap_addr_index(dev, 0);
if (!data->usbss)
return -EINVAL;
ret = clk_get_by_name(dev, "usb2_refclk", &usb2_refclk);
if (ret) {
dev_err(dev, "Failed to get usb2_refclk\n");
return ret;
}
rate = clk_get_rate(&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;
/* 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 = dev_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 = dev_read_bool(dev, "ti,usb2-only");
if (data->usb2_only)
reg |= USBSS_W1_USB2_ONLY;
/* set modestrap */
if (dev_read_bool(dev, "ti,modestrap-host"))
modestrap_mode = USBSS_MODESTRAP_MODE_HOST;
else if (dev_read_bool(dev, "ti,modestrap-peripheral"))
modestrap_mode = USBSS_MODESTRAP_MODE_PERIPHERAL;
else
modestrap_mode = USBSS_MODESTRAP_MODE_NONE;
reg |= USBSS_W1_MODESTRAP_SEL;
reg &= ~USBSS_W1_MODESTRAP_MASK;
reg |= modestrap_mode << 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);
return 0;
}
static int cdns_ti_remove(struct udevice *dev)
{
struct cdns_ti *data = dev_get_platdata(dev);
u32 reg;
/* put device back to RESET*/
reg = cdns_ti_readl(data, USBSS_W1);
reg &= ~USBSS_W1_PWRUP_RST;
cdns_ti_writel(data, USBSS_W1, reg);
return 0;
}
static const struct udevice_id cdns_ti_of_match[] = {
{ .compatible = "ti,j721e-usb", },
{},
};
U_BOOT_DRIVER(cdns_ti) = {
.name = "cdns-ti",
.id = UCLASS_NOP,
.of_match = cdns_ti_of_match,
.bind = cdns3_bind,
.probe = cdns_ti_probe,
.remove = cdns_ti_remove,
.platdata_auto_alloc_size = sizeof(struct cdns_ti),
.flags = DM_FLAG_OS_PREPARE,
};

498
drivers/usb/cdns3/core.c Normal file
View File

@ -0,0 +1,498 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Cadence USBSS 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 <common.h>
#include <dm.h>
#include <dm/device-internal.h>
#include <dm/lists.h>
#include <linux/kernel.h>
#include <linux/io.h>
#include <usb.h>
#include "../host/xhci.h"
#include "core.h"
#include "host-export.h"
#include "gadget-export.h"
#include "drd.h"
static int cdns3_idle_init(struct cdns3 *cdns);
struct cdns3_host_priv {
struct xhci_ctrl xhci_ctrl;
struct cdns3 cdns;
};
struct cdns3_gadget_priv {
struct cdns3 cdns;
};
static inline
struct cdns3_role_driver *cdns3_get_current_role_driver(struct cdns3 *cdns)
{
WARN_ON(!cdns->roles[cdns->role]);
return cdns->roles[cdns->role];
}
static int cdns3_role_start(struct cdns3 *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 == CDNS3_ROLE_STATE_ACTIVE)
return 0;
mutex_lock(&cdns->mutex);
ret = cdns->roles[role]->start(cdns);
if (!ret)
cdns->roles[role]->state = CDNS3_ROLE_STATE_ACTIVE;
mutex_unlock(&cdns->mutex);
return ret;
}
static void cdns3_role_stop(struct cdns3 *cdns)
{
enum usb_role role = cdns->role;
if (WARN_ON(role > USB_ROLE_DEVICE))
return;
if (cdns->roles[role]->state == CDNS3_ROLE_STATE_INACTIVE)
return;
mutex_lock(&cdns->mutex);
cdns->roles[role]->stop(cdns);
cdns->roles[role]->state = CDNS3_ROLE_STATE_INACTIVE;
mutex_unlock(&cdns->mutex);
}
static void cdns3_exit_roles(struct cdns3 *cdns)
{
cdns3_role_stop(cdns);
cdns3_drd_exit(cdns);
}
static enum usb_role cdsn3_hw_role_state_machine(struct cdns3 *cdns);
/**
* cdns3_core_init_role - initialize role of operation
* @cdns: Pointer to cdns3 structure
*
* Returns 0 on success otherwise negative errno
*/
static int cdns3_core_init_role(struct cdns3 *cdns)
{
struct udevice *dev = cdns->dev;
enum usb_dr_mode best_dr_mode;
enum usb_dr_mode dr_mode;
int ret = 0;
dr_mode = usb_get_dr_mode(dev_of_offset(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 (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 = cdns3_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 defined(CONFIG_SPL_USB_HOST_SUPPORT) || !defined(CONFIG_SPL_BUILD)
if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_HOST) {
ret = cdns3_host_init(cdns);
if (ret) {
dev_err(dev, "Host initialization failed with %d\n",
ret);
goto err;
}
}
#endif
#if CONFIG_IS_ENABLED(DM_USB_GADGET)
if (dr_mode == USB_DR_MODE_OTG || dr_mode == USB_DR_MODE_PERIPHERAL) {
ret = cdns3_gadget_init(cdns);
if (ret) {
dev_err(dev, "Device initialization failed with %d\n",
ret);
goto err;
}
}
#endif
cdns->dr_mode = dr_mode;
ret = cdns3_drd_update_mode(cdns);
if (ret)
goto err;
if (cdns->dr_mode != USB_DR_MODE_OTG) {
ret = cdns3_hw_role_switch(cdns);
if (ret)
goto err;
}
return ret;
err:
cdns3_exit_roles(cdns);
return ret;
}
/**
* cdsn3_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 cdsn3_hw_role_state_machine(struct cdns3 *cdns)
{
enum usb_role role;
int id, vbus;
if (cdns->dr_mode != USB_DR_MODE_OTG)
goto not_otg;
id = cdns3_get_id(cdns);
vbus = cdns3_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;
not_otg:
if (cdns3_is_host(cdns))
role = USB_ROLE_HOST;
if (cdns3_is_device(cdns))
role = USB_ROLE_DEVICE;
return role;
}
static int cdns3_idle_role_start(struct cdns3 *cdns)
{
return 0;
}
static void cdns3_idle_role_stop(struct cdns3 *cdns)
{
/* Program Lane swap and bring PHY out of RESET */
generic_phy_reset(&cdns->usb3_phy);
}
static int cdns3_idle_init(struct cdns3 *cdns)
{
struct cdns3_role_driver *rdrv;
rdrv = devm_kzalloc(cdns->dev, sizeof(*rdrv), GFP_KERNEL);
if (!rdrv)
return -ENOMEM;
rdrv->start = cdns3_idle_role_start;
rdrv->stop = cdns3_idle_role_stop;
rdrv->state = CDNS3_ROLE_STATE_INACTIVE;
rdrv->suspend = NULL;
rdrv->resume = NULL;
rdrv->name = "idle";
cdns->roles[USB_ROLE_NONE] = rdrv;
return 0;
}
/**
* cdns3_hw_role_switch - switch roles based on HW state
* @cdns3: controller
*/
int cdns3_hw_role_switch(struct cdns3 *cdns)
{
enum usb_role real_role, current_role;
int ret = 0;
/* Do nothing if role based on syfs. */
if (cdns->role_override)
return 0;
current_role = cdns->role;
real_role = cdsn3_hw_role_state_machine(cdns);
/* Do nothing if nothing changed */
if (current_role == real_role)
goto exit;
cdns3_role_stop(cdns);
dev_dbg(cdns->dev, "Switching role %d -> %d", current_role, real_role);
ret = cdns3_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 = cdns3_role_start(cdns, current_role);
if (ret)
dev_err(cdns->dev, "back to %d failed too\n",
current_role);
}
exit:
return ret;
}
static int cdns3_probe(struct cdns3 *cdns)
{
struct udevice *dev = cdns->dev;
int ret;
cdns->xhci_regs = dev_remap_addr_name(dev, "xhci");
if (!cdns->xhci_regs)
return -EINVAL;
cdns->dev_regs = dev_remap_addr_name(dev, "dev");
if (!cdns->dev_regs)
return -EINVAL;
mutex_init(&cdns->mutex);
ret = generic_phy_get_by_name(dev, "cdns3,usb2-phy", &cdns->usb2_phy);
if (ret)
dev_warn(dev, "Unable to get USB2 phy (ret %d)\n", ret);
ret = generic_phy_init(&cdns->usb2_phy);
if (ret)
return ret;
ret = generic_phy_get_by_name(dev, "cdns3,usb3-phy", &cdns->usb3_phy);
if (ret)
dev_warn(dev, "Unable to get USB3 phy (ret %d)\n", ret);
ret = generic_phy_init(&cdns->usb3_phy);
if (ret)
return ret;
ret = generic_phy_power_on(&cdns->usb2_phy);
if (ret)
return ret;
ret = generic_phy_power_on(&cdns->usb3_phy);
if (ret)
return ret;
ret = cdns3_drd_init(cdns);
if (ret)
return ret;
ret = cdns3_core_init_role(cdns);
if (ret)
return ret;
dev_dbg(dev, "Cadence USB3 core: probe succeed\n");
return 0;
}
static int cdns3_remove(struct cdns3 *cdns)
{
cdns3_exit_roles(cdns);
generic_phy_power_off(&cdns->usb2_phy);
generic_phy_power_off(&cdns->usb3_phy);
generic_phy_exit(&cdns->usb2_phy);
generic_phy_exit(&cdns->usb3_phy);
return 0;
}
static const struct udevice_id cdns3_ids[] = {
{ .compatible = "cdns,usb3" },
{ },
};
int cdns3_bind(struct udevice *parent)
{
int from = dev_of_offset(parent);
const void *fdt = gd->fdt_blob;
enum usb_dr_mode dr_mode;
struct udevice *dev;
const char *driver;
const char *name;
int node;
int ret;
node = fdt_node_offset_by_compatible(fdt, from, "cdns,usb3");
if (node < 0) {
ret = -ENODEV;
goto fail;
}
name = fdt_get_name(fdt, node, NULL);
dr_mode = usb_get_dr_mode(node);
switch (dr_mode) {
#if defined(CONFIG_SPL_USB_HOST_SUPPORT) || \
(!defined(CONFIG_SPL_BUILD) && defined(CONFIG_USB_HOST))
case USB_DR_MODE_HOST:
debug("%s: dr_mode: HOST\n", __func__);
driver = "cdns-usb3-host";
break;
#endif
#if CONFIG_IS_ENABLED(DM_USB_GADGET)
case USB_DR_MODE_PERIPHERAL:
debug("%s: dr_mode: PERIPHERAL\n", __func__);
driver = "cdns-usb3-peripheral";
break;
#endif
default:
printf("%s: unsupported dr_mode\n", __func__);
ret = -ENODEV;
goto fail;
};
ret = device_bind_driver_to_node(parent, driver, name,
offset_to_ofnode(node), &dev);
if (ret) {
printf("%s: not able to bind usb device mode\n",
__func__);
goto fail;
}
return 0;
fail:
/* do not return an error: failing to bind would hang the board */
return 0;
}
#if CONFIG_IS_ENABLED(DM_USB_GADGET)
static int cdns3_gadget_probe(struct udevice *dev)
{
struct cdns3_gadget_priv *priv = dev_get_priv(dev);
struct cdns3 *cdns = &priv->cdns;
cdns->dev = dev;
return cdns3_probe(cdns);
}
static int cdns3_gadget_remove(struct udevice *dev)
{
struct cdns3_gadget_priv *priv = dev_get_priv(dev);
struct cdns3 *cdns = &priv->cdns;
return cdns3_remove(cdns);
}
U_BOOT_DRIVER(cdns_usb3_peripheral) = {
.name = "cdns-usb3-peripheral",
.id = UCLASS_USB_GADGET_GENERIC,
.of_match = cdns3_ids,
.probe = cdns3_gadget_probe,
.remove = cdns3_gadget_remove,
.priv_auto_alloc_size = sizeof(struct cdns3_gadget_priv),
.flags = DM_FLAG_ALLOC_PRIV_DMA,
};
#endif
#if defined(CONFIG_SPL_USB_HOST_SUPPORT) || \
(!defined(CONFIG_SPL_BUILD) && defined(CONFIG_USB_HOST))
static int cdns3_host_probe(struct udevice *dev)
{
struct cdns3_host_priv *priv = dev_get_priv(dev);
struct cdns3 *cdns = &priv->cdns;
cdns->dev = dev;
return cdns3_probe(cdns);
}
static int cdns3_host_remove(struct udevice *dev)
{
struct cdns3_host_priv *priv = dev_get_priv(dev);
struct cdns3 *cdns = &priv->cdns;
return cdns3_remove(cdns);
}
U_BOOT_DRIVER(cdns_usb3_host) = {
.name = "cdns-usb3-host",
.id = UCLASS_USB,
.of_match = cdns3_ids,
.probe = cdns3_host_probe,
.remove = cdns3_host_remove,
.priv_auto_alloc_size = sizeof(struct cdns3_host_priv),
.ops = &xhci_usb_ops,
.flags = DM_FLAG_ALLOC_PRIV_DMA,
};
#endif

108
drivers/usb/cdns3/core.h Normal file
View File

@ -0,0 +1,108 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Cadence USBSS 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>
*/
#include <linux/compiler.h>
#include <linux/types.h>
#include <linux/usb/otg.h>
#include <generic-phy.h>
#ifndef __LINUX_CDNS3_CORE_H
#define __LINUX_CDNS3_CORE_H
enum usb_role {
USB_ROLE_NONE,
USB_ROLE_HOST,
USB_ROLE_DEVICE,
};
struct cdns3;
/**
* struct cdns3_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 cdns3_role_driver {
int (*start)(struct cdns3 *cdns);
void (*stop)(struct cdns3 *cdns);
int (*suspend)(struct cdns3 *cdns, bool do_wakeup);
int (*resume)(struct cdns3 *cdns, bool hibernated);
const char *name;
#define CDNS3_ROLE_STATE_INACTIVE 0
#define CDNS3_ROLE_STATE_ACTIVE 1
int state;
};
#define CDNS3_XHCI_RESOURCES_NUM 2
/**
* struct cdns3 - Representation of Cadence USB3 DRD controller.
* @dev: pointer to Cadence device struct
* @xhci_regs: pointer to base of xhci registers
* @dev_regs: pointer to base of dev registers
* @otg_v0_regs: pointer to base of v0 otg registers
* @otg_v1_regs: pointer to base of v1 otg registers
* @otg_regs: pointer to base of otg registers
* @otg_irq: irq number for otg controller
* @dev_irq: irq number for device controller
* @roles: array of supported roles for this controller
* @role: current role
* @host_dev: the child host device pointer for cdns3 core
* @gadget_dev: the child gadget device pointer for cdns3 core
* @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.
* @role_override: set 1 if role rely on SW.
*/
struct cdns3 {
struct udevice *dev;
void __iomem *xhci_regs;
struct cdns3_usb_regs __iomem *dev_regs;
struct cdns3_otg_legacy_regs *otg_v0_regs;
struct cdns3_otg_regs *otg_v1_regs;
struct cdns3_otg_common_regs *otg_regs;
#define CDNS3_CONTROLLER_V0 0
#define CDNS3_CONTROLLER_V1 1
u32 version;
int otg_irq;
int dev_irq;
struct cdns3_role_driver *roles[USB_ROLE_DEVICE + 1];
enum usb_role role;
struct cdns3_device *gadget_dev;
struct phy usb2_phy;
struct phy usb3_phy;
/* mutext used in workqueue*/
struct mutex mutex;
enum usb_dr_mode dr_mode;
int role_override;
};
int cdns3_hw_role_switch(struct cdns3 *cdns);
/**
* cdns3_bind - generic bind function
* @parent - pointer to parent udevice of which cdns3 USB controller
* node is child of
*
* return 0 on success, negative errno otherwise
*/
int cdns3_bind(struct udevice *dev);
#endif /* __LINUX_CDNS3_CORE_H */

162
drivers/usb/cdns3/debug.h Normal file
View File

@ -0,0 +1,162 @@
/* 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"
#include "gadget.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,
struct cdns3_trb *ring, 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)
trb_per_sector = TRBS_PER_SEGMENT;
if (trb_per_sector > TRBS_PER_SEGMENT) {
sprintf(str + ret, "\t\tTo big transfer ring %d\n",
trb_per_sector);
return str;
}
for (i = 0; i < trb_per_sector; ++i) {
trb = &ring[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*/

301
drivers/usb/cdns3/drd.c Normal file
View File

@ -0,0 +1,301 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Cadence USBSS DRD Driver.
*
* Copyright (C) 2018-2019 Cadence.
* Copyright (C) 2019 Texas Instruments
*
* Author: Pawel Laszczak <pawell@cadence.com>
* Roger Quadros <rogerq@ti.com>
*
*
*/
#include <dm.h>
#include <linux/delay.h>
#include <linux/iopoll.h>
#include <linux/kernel.h>
#include <linux/usb/otg.h>
#include "gadget.h"
#include "drd.h"
#include "core.h"
#define readl_poll_timeout_atomic readl_poll_timeout
#define usleep_range(a, b) udelay((b))
/**
* cdns3_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
*/
int cdns3_set_mode(struct cdns3 *cdns, enum usb_dr_mode mode)
{
int ret = 0;
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 == CDNS3_CONTROLLER_V1) {
reg = readl(&cdns->otg_v1_regs->override);
reg |= OVERRIDE_IDPULLUP;
writel(reg, &cdns->otg_v1_regs->override);
} else {
reg = readl(&cdns->otg_v0_regs->ctrl1);
reg |= OVERRIDE_IDPULLUP_V0;
writel(reg, &cdns->otg_v0_regs->ctrl1);
}
/*
* 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 ret;
}
int cdns3_get_id(struct cdns3 *cdns)
{
int id;
id = readl(&cdns->otg_regs->sts) & OTGSTS_ID_VALUE;
dev_dbg(cdns->dev, "OTG ID: %d", id);
return id;
}
int cdns3_get_vbus(struct cdns3 *cdns)
{
int vbus;
vbus = !!(readl(&cdns->otg_regs->sts) & OTGSTS_VBUS_VALID);
dev_dbg(cdns->dev, "OTG VBUS: %d", vbus);
return vbus;
}
int cdns3_is_host(struct cdns3 *cdns)
{
if (cdns->dr_mode == USB_DR_MODE_HOST)
return 1;
else if (!cdns3_get_id(cdns))
return 1;
return 0;
}
int cdns3_is_device(struct cdns3 *cdns)
{
if (cdns->dr_mode == USB_DR_MODE_PERIPHERAL)
return 1;
else if (cdns->dr_mode == USB_DR_MODE_OTG)
if (cdns3_get_id(cdns))
return 1;
return 0;
}
/**
* cdns3_drd_switch_host - start/stop host
* @cdns: Pointer to controller context structure
* @on: 1 for start, 0 for stop
*
* Returns 0 on success otherwise negative errno
*/
int cdns3_drd_switch_host(struct cdns3 *cdns, int on)
{
int ret, val;
u32 reg = OTGCMD_OTG_DIS;
/* switch OTG core */
if (on) {
writel(OTGCMD_HOST_BUS_REQ | reg, &cdns->otg_regs->cmd);
dev_dbg(cdns->dev, "Waiting till Host mode is turned on\n");
ret = readl_poll_timeout_atomic(&cdns->otg_regs->sts, val,
val & OTGSTS_XHCI_READY,
100000);
if (ret) {
dev_err(cdns->dev, "timeout waiting for xhci_ready\n");
return ret;
}
} else {
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),
2000000);
}
return 0;
}
/**
* cdns3_drd_switch_gadget - start/stop gadget
* @cdns: Pointer to controller context structure
* @on: 1 for start, 0 for stop
*
* Returns 0 on success otherwise negative errno
*/
int cdns3_drd_switch_gadget(struct cdns3 *cdns, int on)
{
int ret, val;
u32 reg = OTGCMD_OTG_DIS;
/* switch OTG core */
if (on) {
writel(OTGCMD_DEV_BUS_REQ | reg, &cdns->otg_regs->cmd);
dev_dbg(cdns->dev, "Waiting till Device mode is turned on\n");
ret = readl_poll_timeout_atomic(&cdns->otg_regs->sts, val,
val & OTGSTS_DEV_READY,
100000);
if (ret) {
dev_err(cdns->dev, "timeout waiting for dev_ready\n");
return ret;
}
} else {
/*
* 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),
2000000);
}
return 0;
}
/**
* cdns3_init_otg_mode - initialize drd controller
* @cdns: Pointer to controller context structure
*
* Returns 0 on success otherwise negative errno
*/
static int cdns3_init_otg_mode(struct cdns3 *cdns)
{
int ret = 0;
/* clear all interrupts */
writel(~0, &cdns->otg_regs->ivect);
ret = cdns3_set_mode(cdns, USB_DR_MODE_OTG);
if (ret)
return ret;
return ret;
}
/**
* cdns3_drd_update_mode - initialize mode of operation
* @cdns: Pointer to controller context structure
*
* Returns 0 on success otherwise negative errno
*/
int cdns3_drd_update_mode(struct cdns3 *cdns)
{
int ret = 0;
switch (cdns->dr_mode) {
case USB_DR_MODE_PERIPHERAL:
ret = cdns3_set_mode(cdns, USB_DR_MODE_PERIPHERAL);
break;
case USB_DR_MODE_HOST:
ret = cdns3_set_mode(cdns, USB_DR_MODE_HOST);
break;
case USB_DR_MODE_OTG:
ret = cdns3_init_otg_mode(cdns);
break;
default:
dev_err(cdns->dev, "Unsupported mode of operation %d\n",
cdns->dr_mode);
return -EINVAL;
}
return ret;
}
int cdns3_drd_init(struct cdns3 *cdns)
{
void __iomem *regs;
int ret = 0;
u32 state;
regs = dev_remap_addr_name(cdns->dev, "otg");
if (!regs)
return -EINVAL;
/* Detection of DRD version. Controller has been released
* in two versions. Both are similar, but they have same changes
* in register maps.
* The first register in old version is command register and it's read
* only, so driver should read 0 from it. On the other hand, in v1
* 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_regs = regs;
writel(1, &cdns->otg_v0_regs->simulate);
dev_info(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_regs = (void *)&cdns->otg_v1_regs->cmd;
cdns->version = CDNS3_CONTROLLER_V1;
writel(1, &cdns->otg_v1_regs->simulate);
dev_info(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 (state == OTGSTS_STRAP_HOST) {
dev_dbg(cdns->dev, "Controller strapped to HOST\n");
cdns->dr_mode = USB_DR_MODE_HOST;
} else if (state == OTGSTS_STRAP_GADGET) {
dev_dbg(cdns->dev, "Controller strapped to PERIPHERAL\n");
cdns->dr_mode = USB_DR_MODE_PERIPHERAL;
}
state = readl(&cdns->otg_regs->sts);
if (OTGSTS_OTG_NRDY(state) != 0) {
dev_err(cdns->dev, "Cadence USB3 OTG device not ready\n");
return -ENODEV;
}
return ret;
}
int cdns3_drd_exit(struct cdns3 *cdns)
{
return 0;
}

166
drivers/usb/cdns3/drd.h Normal file
View File

@ -0,0 +1,166 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Cadence USB3 DRD header file.
*
* Copyright (C) 2018-2019 Cadence.
*
* Author: Pawel Laszczak <pawell@cadence.com>
*/
#ifndef __LINUX_CDNS3_DRD
#define __LINUX_CDNS3_DRD
#include <linux/types.h>
#include <linux/usb/otg.h>
#include "core.h"
/* DRD register interface for version v1. */
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 reserved4;
__le32 anasts;
__le32 adp_ramp_time;
__le32 ctrl1;
__le32 ctrl2;
};
/* DRD register interface for version v0. */
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;
};
/*
* Common registers interface for both version of DRD.
*/
struct cdns3_otg_common_regs {
__le32 cmd;
__le32 sts;
__le32 state;
__le32 different1;
__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. */
#define OTGCMD_DEV_POWER_OFF BIT(11)
/* Power Down CDNSXHCI. */
#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.
* 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
/* Host mode is turned on. */
#define OTGSTS_XHCI_READY BIT(26)
/* "Device mode is turned on .*/
#define OTGSTS_DEV_READY BIT(27)
/* 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)
/* OVERRIDE - bitmasks */
#define OVERRIDE_IDPULLUP BIT(0)
/* Only for CDNS3_CONTROLLER_V0 version */
#define OVERRIDE_IDPULLUP_V0 BIT(24)
int cdns3_is_host(struct cdns3 *cdns);
int cdns3_is_device(struct cdns3 *cdns);
int cdns3_get_id(struct cdns3 *cdns);
int cdns3_get_vbus(struct cdns3 *cdns);
int cdns3_drd_init(struct cdns3 *cdns);
int cdns3_drd_exit(struct cdns3 *cdns);
int cdns3_drd_update_mode(struct cdns3 *cdns);
int cdns3_drd_switch_gadget(struct cdns3 *cdns, int on);
int cdns3_drd_switch_host(struct cdns3 *cdns, int on);
#endif /* __LINUX_CDNS3_DRD */

910
drivers/usb/cdns3/ep0.c Normal file
View File

@ -0,0 +1,910 @@
// 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 "gadget.h"
#include "trace.h"
#define readl_poll_timeout_atomic readl_poll_timeout
#define usleep_range(a, b) udelay((b))
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
*/
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 = TRB_BUFFER(dma_addr);
priv_ep->trb_pool[0].length = TRB_LEN(length);
if (zlp) {
priv_ep->trb_pool[0].control = TRB_CYCLE | TRB_TYPE(TRB_NORMAL);
priv_ep->trb_pool[1].buffer = TRB_BUFFER(dma_addr);
priv_ep->trb_pool[1].length = TRB_LEN(0);
priv_ep->trb_pool[1].control = TRB_CYCLE | TRB_IOC |
TRB_TYPE(TRB_NORMAL);
} else {
priv_ep->trb_pool[0].control = TRB_CYCLE | TRB_IOC |
TRB_TYPE(TRB_NORMAL);
priv_ep->trb_pool[1].control = 0;
}
/* Flush both TRBs */
flush_dcache_range((unsigned long)priv_ep->trb_pool,
(unsigned long)priv_ep->trb_pool +
ROUND(sizeof(struct cdns3_trb) * 2,
CONFIG_SYS_CACHELINE_SIZE));
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_allow_enable_l1(priv_dev, 1);
}
/**
* 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;
struct cdns3_endpoint *priv_ep;
u32 config = le16_to_cpu(ctrl_req->wValue);
int result = 0;
int i;
switch (device_state) {
case USB_STATE_ADDRESS:
/* Configure non-control EPs */
for (i = 0; i < CDNS3_ENDPOINTS_MAX_COUNT; i++) {
priv_ep = priv_dev->eps[i];
if (!priv_ep)
continue;
if (priv_ep->flags & EP_CLAIMED)
cdns3_ep_config(priv_ep);
}
result = cdns3_ep0_delegate_req(priv_dev, ctrl_req);
if (result)
return result;
if (config) {
cdns3_set_hw_configuration(priv_dev);
} else {
cdns3_hw_reset_eps_config(priv_dev);
usb_gadget_set_state(&priv_dev->gadget,
USB_STATE_ADDRESS);
}
break;
case USB_STATE_CONFIGURED:
result = cdns3_ep0_delegate_req(priv_dev, ctrl_req);
if (!config && !result) {
cdns3_hw_reset_eps_config(priv_dev);
usb_gadget_set_state(&priv_dev->gadget,
USB_STATE_ADDRESS);
}
break;
default:
result = -EINVAL;
}
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_req: 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)
{
__le16 *response_pkt;
u16 usb_status = 0;
u32 recip;
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:
/* check if endpoint is stalled */
cdns3_select_ep(priv_dev, ctrl->wIndex);
if (EP_STS_STALL(readl(&priv_dev->regs->ep_sts)))
usb_status = BIT(USB_ENDPOINT_HALT);
break;
default:
return -EINVAL;
}
response_pkt = (__le16 *)priv_dev->setup_buf;
*response_pkt = cpu_to_le16(usb_status);
/* Flush setup response */
flush_dcache_range((unsigned long)priv_dev->setup_buf,
(unsigned long)priv_dev->setup_buf +
ROUND(sizeof(struct usb_ctrlrequest),
CONFIG_SYS_CACHELINE_SIZE));
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;
u16 tmode;
state = priv_dev->gadget.state;
speed = priv_dev->gadget.speed;
switch (ctrl->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;
switch (tmode >> 8) {
case TEST_J:
case TEST_K:
case TEST_SE0_NAK:
case TEST_PACKET:
cdns3_ep0_complete_setup(priv_dev, 0, 1);
/**
* Little delay to give the controller some time
* for sending status stage.
* This time should be less then 3ms.
*/
usleep_range(1000, 2000);
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 (!(ctrl->wIndex & ~USB_DIR_IN))
return 0;
index = cdns3_ep_addr_to_index(ctrl->wIndex);
priv_ep = priv_dev->eps[index];
cdns3_select_ep(priv_dev, 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_req: 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 (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 = 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);
/* Invalidate TRB before accessing it */
invalidate_dcache_range((unsigned long)priv_ep->trb_pool,
(unsigned long)priv_ep->trb_pool +
ROUND(sizeof(struct cdns3_trb),
CONFIG_SYS_CACHELINE_SIZE));
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, 0 | 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_allow_enable_l1(priv_dev, 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
* 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
* 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 erdy_sent = 0;
int ret = 0;
u8 zlp = 0;
trace_cdns3_ep0_queue(priv_dev, request);
/* cancel the request if controller receive new SETUP packet. */
if (cdns3_check_new_setup(priv_dev))
return -ECONNRESET;
/* send STATUS stage. Should be called only for SET_CONFIGURATION */
if (priv_dev->ep0_stage == CDNS3_STATUS_STAGE) {
spin_lock_irqsave(&priv_dev->lock, flags);
cdns3_select_ep(priv_dev, 0x00);
erdy_sent = !priv_dev->hw_configured_flag;
cdns3_set_hw_configuration(priv_dev);
if (!erdy_sent)
cdns3_ep0_complete_setup(priv_dev, 0, 1);
cdns3_allow_enable_l1(priv_dev, 1);
request->actual = 0;
priv_dev->status_completion_no_call = true;
priv_dev->pending_status_request = request;
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.
*/
#ifndef __UBOOT__
queue_work(system_freezable_wq, &priv_dev->pending_status_wq);
#else
__pending_setup_status_handler(priv_dev);
#endif
return 0;
}
spin_lock_irqsave(&priv_dev->lock, flags);
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(&priv_dev->gadget, 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);
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;
}
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;
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));
}
writel(EP_CFG_ENABLE | EP_CFG_MAXPKTSIZE(max_packet_size),
&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);
writel(EP_CFG_ENABLE | EP_CFG_MAXPKTSIZE(max_packet_size),
&regs->ep_cfg);
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
* @ep_priv: 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);
#ifndef __UBOOT__
priv_ep->endpoint.address = 0;
#endif
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);
}

View File

@ -0,0 +1,28 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Cadence USBSS 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
#ifdef CONFIG_USB_CDNS3_GADGET
int cdns3_gadget_init(struct cdns3 *cdns);
void cdns3_gadget_exit(struct cdns3 *cdns);
#else
static inline int cdns3_gadget_init(struct cdns3 *cdns)
{
return -ENXIO;
}
static inline void cdns3_gadget_exit(struct cdns3 *cdns) { }
#endif
#endif /* __LINUX_CDNS3_GADGET_EXPORT */

2760
drivers/usb/cdns3/gadget.c Normal file

File diff suppressed because it is too large Load Diff

1338
drivers/usb/cdns3/gadget.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,28 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Cadence USBSS 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
#ifdef CONFIG_USB_CDNS3_HOST
int cdns3_host_init(struct cdns3 *cdns);
void cdns3_host_exit(struct cdns3 *cdns);
#else
static inline int cdns3_host_init(struct cdns3 *cdns)
{
return -ENXIO;
}
static inline void cdns3_host_exit(struct cdns3 *cdns) { }
#endif /* CONFIG_USB_CDNS3_HOST */
#endif /* __LINUX_CDNS3_HOST_EXPORT */

55
drivers/usb/cdns3/host.c Normal file
View File

@ -0,0 +1,55 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Cadence USBSS 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 <dm.h>
#include <linux/compat.h>
#include <usb.h>
#include "../host/xhci.h"
#include "core.h"
#include "drd.h"
static int __cdns3_host_init(struct cdns3 *cdns)
{
struct xhci_hcor *hcor;
struct xhci_hccr *hccr;
cdns3_drd_switch_host(cdns, 1);
hccr = (struct xhci_hccr *)cdns->xhci_regs;
hcor = (struct xhci_hcor *)(cdns->xhci_regs +
HC_LENGTH(xhci_readl(&(hccr)->cr_capbase)));
return xhci_register(cdns->dev, hccr, hcor);
}
static void cdns3_host_exit(struct cdns3 *cdns)
{
xhci_deregister(cdns->dev);
cdns3_drd_switch_host(cdns, 0);
}
int cdns3_host_init(struct cdns3 *cdns)
{
struct cdns3_role_driver *rdrv;
rdrv = devm_kzalloc(cdns->dev, sizeof(*rdrv), GFP_KERNEL);
if (!rdrv)
return -ENOMEM;
rdrv->start = __cdns3_host_init;
rdrv->stop = cdns3_host_exit;
rdrv->state = CDNS3_ROLE_STATE_INACTIVE;
rdrv->name = "host";
cdns->roles[USB_ROLE_HOST] = rdrv;
return 0;
}

11
drivers/usb/cdns3/trace.c Normal file
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 "trace.h"

26
drivers/usb/cdns3/trace.h Normal file
View File

@ -0,0 +1,26 @@
/* SPDX-License-Identifier: GPL-2.0 */
#define trace_cdns3_prepare_trb(a, b)
#define trace_cdns3_doorbell_ep0(a, b)
#define trace_cdns3_ctrl_req(a)
#define trace_cdns3_complete_trb(a, b)
#define trace_cdns3_ep0_irq(a, b)
#define trace_cdns3_gadget_giveback(a)
#define trace_cdns3_free_aligned_request(a)
#define trace_cdns3_prepare_aligned_request(a)
#define trace_cdns3_ring(a)
#define trace_cdns3_doorbell_epx(a, b)
#define trace_cdns3_request_handled(a, b, c)
#define trace_cdns3_epx_irq(a, b)
#define trace_cdns3_usb_irq(a, b)
#define trace_cdns3_alloc_request(a)
#define trace_cdns3_free_request(a)
#define trace_cdns3_gadget_ep_enable(a)
#define trace_cdns3_gadget_ep_disable(a)
#define trace_cdns3_ep0_queue(a, b)
#define trace_cdns3_ep0_dequeue(a)
#define trace_cdns3_ep_queue(a)
#define trace_cdns3_ep_dequeue(a)
#define trace_cdns3_halt(a, b, c)
#define trace_cdns3_wa1(a, b)
#define trace_cdns3_wa2(a, b)

View File

@ -688,6 +688,57 @@ static void composite_setup_complete(struct usb_ep *ep, struct usb_request *req)
req->status, req->actual, req->length);
}
static int bos_desc(struct usb_composite_dev *cdev)
{
struct usb_ext_cap_descriptor *usb_ext;
struct usb_bos_descriptor *bos = cdev->req->buf;
bos->bLength = USB_DT_BOS_SIZE;
bos->bDescriptorType = USB_DT_BOS;
bos->wTotalLength = cpu_to_le16(USB_DT_BOS_SIZE);
bos->bNumDeviceCaps = 0;
/*
* A SuperSpeed device shall include the USB2.0 extension descriptor
* and shall support LPM when operating in USB2.0 HS mode.
*/
usb_ext = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
bos->bNumDeviceCaps++;
le16_add_cpu(&bos->wTotalLength, USB_DT_USB_EXT_CAP_SIZE);
usb_ext->bLength = USB_DT_USB_EXT_CAP_SIZE;
usb_ext->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
usb_ext->bDevCapabilityType = USB_CAP_TYPE_EXT;
usb_ext->bmAttributes =
cpu_to_le32(USB_LPM_SUPPORT | USB_BESL_SUPPORT);
/*
* The Superspeed USB Capability descriptor shall be implemented
* by all SuperSpeed devices.
*/
if (gadget_is_superspeed(cdev->gadget)) {
struct usb_ss_cap_descriptor *ss_cap;
ss_cap = cdev->req->buf + le16_to_cpu(bos->wTotalLength);
bos->bNumDeviceCaps++;
le16_add_cpu(&bos->wTotalLength, USB_DT_USB_SS_CAP_SIZE);
ss_cap->bLength = USB_DT_USB_SS_CAP_SIZE;
ss_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY;
ss_cap->bDevCapabilityType = USB_SS_CAP_TYPE;
ss_cap->bmAttributes = 0; /* LTM is not supported yet */
ss_cap->wSpeedSupported =
cpu_to_le16(USB_LOW_SPEED_OPERATION |
USB_FULL_SPEED_OPERATION |
USB_HIGH_SPEED_OPERATION |
USB_5GBPS_OPERATION);
ss_cap->bFunctionalitySupport = USB_LOW_SPEED_OPERATION;
ss_cap->bU1devExitLat = USB_DEFAULT_U1_DEV_EXIT_LAT;
ss_cap->bU2DevExitLat =
cpu_to_le16(USB_DEFAULT_U2_DEV_EXIT_LAT);
}
return le16_to_cpu(bos->wTotalLength);
}
/*
* The setup() callback implements all the ep0 functionality that's
* not handled lower down, in hardware or the hardware driver(like
@ -776,12 +827,10 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
value = min(w_length, (u16) value);
break;
case USB_DT_BOS:
/*
* The USB compliance test (USB 2.0 Command Verifier)
* issues this request. We should not run into the
* default path here. But return for now until
* the superspeed support is added.
*/
if (gadget_is_superspeed(cdev->gadget))
value = bos_desc(cdev);
if (value >= 0)
value = min(w_length, (u16)value);
break;
default:
goto unknown;

View File

@ -282,6 +282,9 @@ struct usb_ep *usb_ep_autoconfig(
return ep;
}
if (gadget->ops->match_ep)
ep = gadget->ops->match_ep(gadget, desc, NULL);
/* Second, look at endpoints until an unclaimed one looks usable */
list_for_each_entry(ep, &gadget->ep_list, ep_list) {
if (ep_matches(gadget, ep, desc))

View File

@ -149,6 +149,12 @@
#define gadget_is_dwc3(g) 0
#endif
#ifdef CONFIG_USB_CDNS3_GADGET
#define gadget_is_cdns3(g) (!strcmp("cdns3-gadget", (g)->name))
#else
#define gadget_is_cdns3(g) 0
#endif
/**
* usb_gadget_controller_number - support bcdDevice id convention
* @gadget: the controller being driven
@ -208,5 +214,7 @@ static inline int usb_gadget_controller_number(struct usb_gadget *gadget)
return 0x22;
else if (gadget_is_dwc3(gadget))
return 0x23;
else if (gadget_is_cdns3(gadget))
return 0x24;
return -ENOENT;
}

View File

@ -267,6 +267,27 @@ EXPORT_SYMBOL_GPL(usb_del_gadget_udc);
/* ------------------------------------------------------------------------- */
/**
* usb_gadget_udc_set_speed - tells usb device controller speed supported by
* current driver
* @udc: The device we want to set maximum speed
* @speed: The maximum speed to allowed to run
*
* This call is issued by the UDC Class driver before calling
* usb_gadget_udc_start() in order to make sure that we don't try to
* connect on speeds the gadget driver doesn't support.
*/
static inline void usb_gadget_udc_set_speed(struct usb_udc *udc,
enum usb_device_speed speed)
{
if (udc->gadget->ops->udc_set_speed) {
enum usb_device_speed s;
s = min(speed, udc->gadget->max_speed);
udc->gadget->ops->udc_set_speed(udc->gadget, s);
}
}
static int udc_bind_to_driver(struct usb_udc *udc, struct usb_gadget_driver *driver)
{
int ret;
@ -276,6 +297,8 @@ static int udc_bind_to_driver(struct usb_udc *udc, struct usb_gadget_driver *dri
udc->driver = driver;
usb_gadget_udc_set_speed(udc, driver->speed);
ret = driver->bind(udc->gadget);
if (ret)
goto err1;

View File

@ -827,7 +827,7 @@ int xhci_ctrl_tx(struct usb_device *udev, unsigned long pipe,
field |= 0x1;
/* xHCI 1.0 6.4.1.2.1: Transfer Type field */
if (HC_VERSION(xhci_readl(&ctrl->hccr->cr_capbase)) == 0x100) {
if (HC_VERSION(xhci_readl(&ctrl->hccr->cr_capbase)) >= 0x100) {
if (length > 0) {
if (req->requesttype & USB_DIR_IN)
field |= (TRB_DATA_IN << TRB_TX_TYPE_SHIFT);

View File

@ -20,4 +20,65 @@ static inline void bitmap_zero(unsigned long *dst, int nbits)
}
}
static inline unsigned long
find_next_bit(const unsigned long *addr, unsigned long size,
unsigned long offset)
{
const unsigned long *p = addr + BIT_WORD(offset);
unsigned long result = offset & ~(BITS_PER_LONG - 1);
unsigned long tmp;
if (offset >= size)
return size;
size -= result;
offset %= BITS_PER_LONG;
if (offset) {
tmp = *(p++);
tmp &= (~0UL << offset);
if (size < BITS_PER_LONG)
goto found_first;
if (tmp)
goto found_middle;
size -= BITS_PER_LONG;
result += BITS_PER_LONG;
}
while (size & ~(BITS_PER_LONG - 1)) {
tmp = *(p++);
if ((tmp))
goto found_middle;
result += BITS_PER_LONG;
size -= BITS_PER_LONG;
}
if (!size)
return result;
tmp = *p;
found_first:
tmp &= (~0UL >> (BITS_PER_LONG - size));
if (tmp == 0UL) /* Are any bits set? */
return result + size; /* Nope. */
found_middle:
return result + __ffs(tmp);
}
/*
* Find the first set bit in a memory region.
*/
static inline unsigned long find_first_bit(const unsigned long *addr, unsigned long size)
{
unsigned long idx;
for (idx = 0; idx * BITS_PER_LONG < size; idx++) {
if (addr[idx])
return min(idx * BITS_PER_LONG + __ffs(addr[idx]), size);
}
return size;
}
#define for_each_set_bit(bit, addr, size) \
for ((bit) = find_first_bit((addr), (size)); \
(bit) < (size); \
(bit) = find_next_bit((addr), (size), (bit) + 1))
#endif /* __LINUX_BITMAP_H */

View File

@ -348,6 +348,20 @@ static inline void list_splice_tail_init(struct list_head *list,
#define list_last_entry(ptr, type, member) \
list_entry((ptr)->prev, type, member)
/**
* list_first_entry_or_null - get the first element from a list
* @ptr: the list head to take the element from.
* @type: the type of the struct this is embedded in.
* @member: the name of the list_head within the struct.
*
* Note that if the list is empty, it returns NULL.
*/
#define list_first_entry_or_null(ptr, type, member) ({ \
struct list_head *head__ = (ptr); \
struct list_head *pos__ = READ_ONCE(head__->next); \
pos__ != head__ ? list_entry(pos__, type, member) : NULL; \
})
/**
* list_for_each - iterate over a list
* @pos: the &struct list_head to use as a loop cursor.

View File

@ -878,6 +878,9 @@ struct usb_ss_cap_descriptor { /* Link Power Management */
__le16 bU2DevExitLat;
} __attribute__((packed));
#define USB_DEFAULT_U1_DEV_EXIT_LAT 0x01 /* Less then 1 microsec */
#define USB_DEFAULT_U2_DEV_EXIT_LAT 0x01F4 /* Less then 500 microsec */
#define USB_DT_USB_SS_CAP_SIZE 10
/*

View File

@ -129,11 +129,30 @@ struct usb_ep_ops {
void (*fifo_flush) (struct usb_ep *ep);
};
/**
* struct usb_ep_caps - endpoint capabilities description
* @type_control:Endpoint supports control type (reserved for ep0).
* @type_iso:Endpoint supports isochronous transfers.
* @type_bulk:Endpoint supports bulk transfers.
* @type_int:Endpoint supports interrupt transfers.
* @dir_in:Endpoint supports IN direction.
* @dir_out:Endpoint supports OUT direction.
*/
struct usb_ep_caps {
unsigned type_control:1;
unsigned type_iso:1;
unsigned type_bulk:1;
unsigned type_int:1;
unsigned dir_in:1;
unsigned dir_out:1;
};
/**
* struct usb_ep - device side representation of USB endpoint
* @name:identifier for the endpoint, such as "ep-a" or "ep9in-bulk"
* @ops: Function pointers used to access hardware-specific operations.
* @ep_list:the gadget's ep_list holds all of its endpoints
* @caps:The structure describing types and directions supported by endoint.
* @maxpacket:The maximum packet size used on this endpoint. The initial
* value can sometimes be reduced (hardware allowing), according to
* the endpoint descriptor used to configure the endpoint.
@ -159,6 +178,7 @@ struct usb_ep {
const char *name;
const struct usb_ep_ops *ops;
struct list_head ep_list;
struct usb_ep_caps caps;
unsigned maxpacket:16;
unsigned maxpacket_limit:16;
unsigned max_streams:16;
@ -447,6 +467,11 @@ struct usb_gadget_ops {
int (*udc_start)(struct usb_gadget *,
struct usb_gadget_driver *);
int (*udc_stop)(struct usb_gadget *);
struct usb_ep *(*match_ep)(struct usb_gadget *,
struct usb_endpoint_descriptor *,
struct usb_ss_ep_comp_descriptor *);
void (*udc_set_speed)(struct usb_gadget *gadget,
enum usb_device_speed);
};
/**
@ -566,6 +591,15 @@ static inline int gadget_is_otg(struct usb_gadget *g)
#endif
}
/**
* gadget_is_superspeed() - return true if the hardware handles superspeed
* @g: controller that might support superspeed
*/
static inline int gadget_is_superspeed(struct usb_gadget *g)
{
return g->max_speed >= USB_SPEED_SUPER;
}
/**
* usb_gadget_frame_number - returns the current frame number
* @gadget: controller that reports the frame number

View File

@ -88,6 +88,7 @@ endif
libs-y += drivers/
libs-$(CONFIG_SPL_USB_GADGET) += drivers/usb/dwc3/
libs-$(CONFIG_SPL_USB_GADGET) += drivers/usb/cdns3/
libs-y += dts/
libs-y += fs/
libs-$(CONFIG_SPL_POST_MEM_SUPPORT) += post/drivers/