usb: Add Cadence USB3 host and gadget driver
Add support for USB3 host and gadget driver. This is a direct sync of Linux kernel Cadence USB stack that from v5.4-rc1 release. Driver has been modified so that it compiles without errors against U-Boot code base. Features not required for U-Boot such as scatter-gather DMA and OTG interrupt handling has been dropped. Signed-off-by: Jean-Jacques Hiblot <jjhiblot@ti.com> [jjhiblot@ti.com: Add PHY support] Signed-off-by: Vignesh Raghavendra <vigneshr@ti.com>
This commit is contained in:
parent
8d94e184ff
commit
7e91f6ccdc
18 changed files with 6433 additions and 0 deletions
1
Makefile
1
Makefile
|
@ -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/
|
||||
|
|
|
@ -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"
|
||||
|
|
51
drivers/usb/cdns3/Kconfig
Normal file
51
drivers/usb/cdns3/Kconfig
Normal file
|
@ -0,0 +1,51 @@
|
|||
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.
|
||||
endif
|
9
drivers/usb/cdns3/Makefile
Normal file
9
drivers/usb/cdns3/Makefile
Normal file
|
@ -0,0 +1,9 @@
|
|||
# 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
|
498
drivers/usb/cdns3/core.c
Normal file
498
drivers/usb/cdns3/core.c
Normal 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
108
drivers/usb/cdns3/core.h
Normal 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
162
drivers/usb/cdns3/debug.h
Normal 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
301
drivers/usb/cdns3/drd.c
Normal 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
166
drivers/usb/cdns3/drd.h
Normal 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
910
drivers/usb/cdns3/ep0.c
Normal 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, ®s->ep_sts);
|
||||
writel(EP_TRADDR_TRADDR(priv_ep->trb_pool_dma), ®s->ep_traddr);
|
||||
trace_cdns3_doorbell_ep0(priv_dev->ep0_data_dir ? "ep0in" : "ep0out",
|
||||
readl(®s->ep_traddr));
|
||||
|
||||
/* TRB should be prepared before starting transfer. */
|
||||
writel(EP_CMD_DRDY, ®s->ep_cmd);
|
||||
|
||||
/* Resume controller before arming transfer. */
|
||||
__cdns3_gadget_wakeup(priv_dev);
|
||||
|
||||
if (erdy)
|
||||
writel(EP_CMD_ERDY, &priv_dev->regs->ep_cmd);
|
||||
}
|
||||
|
||||
/**
|
||||
* cdns3_ep0_delegate_req - Returns status of handling setup packet
|
||||
* Setup is handled by gadget driver
|
||||
* @priv_dev: extended gadget object
|
||||
* @ctrl_req: pointer to received setup packet
|
||||
*
|
||||
* Returns zero on success or negative value on failure
|
||||
*/
|
||||
static int cdns3_ep0_delegate_req(struct cdns3_device *priv_dev,
|
||||
struct usb_ctrlrequest *ctrl_req)
|
||||
{
|
||||
int ret;
|
||||
|
||||
spin_unlock(&priv_dev->lock);
|
||||
priv_dev->setup_pending = 1;
|
||||
ret = priv_dev->gadget_driver->setup(&priv_dev->gadget, ctrl_req);
|
||||
priv_dev->setup_pending = 0;
|
||||
spin_lock(&priv_dev->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void cdns3_prepare_setup_packet(struct cdns3_device *priv_dev)
|
||||
{
|
||||
priv_dev->ep0_data_dir = 0;
|
||||
priv_dev->ep0_stage = CDNS3_SETUP_STAGE;
|
||||
cdns3_ep0_run_transfer(priv_dev, priv_dev->setup_dma,
|
||||
sizeof(struct usb_ctrlrequest), 0, 0);
|
||||
}
|
||||
|
||||
static void cdns3_ep0_complete_setup(struct cdns3_device *priv_dev,
|
||||
u8 send_stall, u8 send_erdy)
|
||||
{
|
||||
struct cdns3_endpoint *priv_ep = priv_dev->eps[0];
|
||||
struct usb_request *request;
|
||||
|
||||
request = cdns3_next_request(&priv_ep->pending_req_list);
|
||||
if (request)
|
||||
list_del_init(&request->list);
|
||||
|
||||
if (send_stall) {
|
||||
trace_cdns3_halt(priv_ep, send_stall, 0);
|
||||
/* set_stall on ep0 */
|
||||
cdns3_select_ep(priv_dev, 0x00);
|
||||
writel(EP_CMD_SSTALL, &priv_dev->regs->ep_cmd);
|
||||
} else {
|
||||
cdns3_prepare_setup_packet(priv_dev);
|
||||
}
|
||||
|
||||
priv_dev->ep0_stage = CDNS3_SETUP_STAGE;
|
||||
writel((send_erdy ? EP_CMD_ERDY : 0) | EP_CMD_REQ_CMPL,
|
||||
&priv_dev->regs->ep_cmd);
|
||||
|
||||
cdns3_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),
|
||||
®s->ep_cfg);
|
||||
|
||||
writel(EP_STS_EN_SETUPEN | EP_STS_EN_DESCMISEN | EP_STS_EN_TRBERREN,
|
||||
®s->ep_sts_en);
|
||||
|
||||
/* init ep in */
|
||||
cdns3_select_ep(priv_dev, USB_DIR_IN);
|
||||
|
||||
writel(EP_CFG_ENABLE | EP_CFG_MAXPKTSIZE(max_packet_size),
|
||||
®s->ep_cfg);
|
||||
|
||||
writel(EP_STS_EN_SETUPEN | EP_STS_EN_TRBERREN, ®s->ep_sts_en);
|
||||
|
||||
cdns3_set_register_bit(®s->usb_conf, USB_CONF_U1DS | USB_CONF_U2DS);
|
||||
}
|
||||
|
||||
/**
|
||||
* cdns3_init_ep0 Initializes software endpoint 0 of gadget
|
||||
* @priv_dev: extended gadget object
|
||||
* @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);
|
||||
}
|
28
drivers/usb/cdns3/gadget-export.h
Normal file
28
drivers/usb/cdns3/gadget-export.h
Normal 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 */
|
2738
drivers/usb/cdns3/gadget.c
Normal file
2738
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
1338
drivers/usb/cdns3/gadget.h
Normal file
File diff suppressed because it is too large
Load diff
28
drivers/usb/cdns3/host-export.h
Normal file
28
drivers/usb/cdns3/host-export.h
Normal 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
55
drivers/usb/cdns3/host.c
Normal 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
11
drivers/usb/cdns3/trace.c
Normal file
|
@ -0,0 +1,11 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* USBSS device controller driver Trace Support
|
||||
*
|
||||
* Copyright (C) 2018-2019 Cadence.
|
||||
*
|
||||
* Author: Pawel Laszczak <pawell@cadence.com>
|
||||
*/
|
||||
|
||||
#define CREATE_TRACE_POINTS
|
||||
#include "trace.h"
|
26
drivers/usb/cdns3/trace.h
Normal file
26
drivers/usb/cdns3/trace.h
Normal 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)
|
|
@ -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/
|
||||
|
|
Loading…
Reference in a new issue