2018-05-06 21:58:06 +00:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
2008-11-28 12:20:46 +00:00
|
|
|
/*-
|
|
|
|
* Copyright (c) 2007-2008, Juniper Networks, Inc.
|
2008-12-10 16:55:19 +00:00
|
|
|
* Copyright (c) 2008, Excito Elektronik i Skåne AB
|
2008-12-13 21:51:58 +00:00
|
|
|
* Copyright (c) 2008, Michael Trimarchi <trimarchimichael@yahoo.it>
|
|
|
|
*
|
2008-11-28 12:20:46 +00:00
|
|
|
* All rights reserved.
|
|
|
|
*/
|
|
|
|
#include <common.h>
|
2019-11-14 19:57:39 +00:00
|
|
|
#include <cpu_func.h>
|
2015-03-25 18:22:29 +00:00
|
|
|
#include <dm.h>
|
2013-03-06 14:08:31 +00:00
|
|
|
#include <errno.h>
|
2020-05-10 17:40:05 +00:00
|
|
|
#include <log.h>
|
2008-12-10 16:55:19 +00:00
|
|
|
#include <asm/byteorder.h>
|
2020-05-10 17:39:56 +00:00
|
|
|
#include <asm/cache.h>
|
2012-09-06 06:00:13 +00:00
|
|
|
#include <asm/unaligned.h>
|
2008-11-28 12:20:46 +00:00
|
|
|
#include <usb.h>
|
|
|
|
#include <asm/io.h>
|
2008-12-10 16:55:19 +00:00
|
|
|
#include <malloc.h>
|
2015-09-02 23:24:58 +00:00
|
|
|
#include <memalign.h>
|
2010-11-26 14:43:28 +00:00
|
|
|
#include <watchdog.h>
|
2020-02-03 14:36:16 +00:00
|
|
|
#include <dm/device_compat.h>
|
2013-03-06 14:08:31 +00:00
|
|
|
#include <linux/compiler.h>
|
2020-05-10 17:40:11 +00:00
|
|
|
#include <linux/delay.h>
|
2009-04-03 10:46:58 +00:00
|
|
|
|
|
|
|
#include "ehci.h"
|
2008-11-28 12:20:46 +00:00
|
|
|
|
2012-09-25 22:14:35 +00:00
|
|
|
#ifndef CONFIG_USB_MAX_CONTROLLER_COUNT
|
|
|
|
#define CONFIG_USB_MAX_CONTROLLER_COUNT 1
|
|
|
|
#endif
|
2008-11-28 12:20:46 +00:00
|
|
|
|
2013-09-24 17:53:07 +00:00
|
|
|
/*
|
|
|
|
* EHCI spec page 20 says that the HC may take up to 16 uFrames (= 4ms) to halt.
|
|
|
|
* Let's time out after 8 to have a little safety margin on top of that.
|
|
|
|
*/
|
|
|
|
#define HCHALT_TIMEOUT (8 * 1000)
|
|
|
|
|
2018-11-21 07:43:56 +00:00
|
|
|
#if !CONFIG_IS_ENABLED(DM_USB)
|
2013-07-10 01:16:31 +00:00
|
|
|
static struct ehci_ctrl ehcic[CONFIG_USB_MAX_CONTROLLER_COUNT];
|
2015-03-25 18:22:29 +00:00
|
|
|
#endif
|
2012-07-15 22:14:24 +00:00
|
|
|
|
|
|
|
#define ALIGN_END_ADDR(type, ptr, size) \
|
2015-03-17 20:46:37 +00:00
|
|
|
((unsigned long)(ptr) + roundup((size) * sizeof(type), USB_DMA_MINALIGN))
|
2008-11-28 12:20:46 +00:00
|
|
|
|
2008-12-10 16:55:19 +00:00
|
|
|
static struct descriptor {
|
|
|
|
struct usb_hub_descriptor hub;
|
|
|
|
struct usb_device_descriptor device;
|
|
|
|
struct usb_linux_config_descriptor config;
|
|
|
|
struct usb_linux_interface_descriptor interface;
|
|
|
|
struct usb_endpoint_descriptor endpoint;
|
|
|
|
} __attribute__ ((packed)) descriptor = {
|
|
|
|
{
|
|
|
|
0x8, /* bDescLength */
|
|
|
|
0x29, /* bDescriptorType: hub descriptor */
|
|
|
|
2, /* bNrPorts -- runtime modified */
|
|
|
|
0, /* wHubCharacteristics */
|
2011-12-05 22:52:22 +00:00
|
|
|
10, /* bPwrOn2PwrGood */
|
2008-12-10 16:55:19 +00:00
|
|
|
0, /* bHubCntrCurrent */
|
2017-07-19 13:50:00 +00:00
|
|
|
{ /* Device removable */
|
|
|
|
} /* at most 7 ports! XXX */
|
2008-12-10 16:55:19 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
0x12, /* bLength */
|
|
|
|
1, /* bDescriptorType: UDESC_DEVICE */
|
2010-02-27 18:29:42 +00:00
|
|
|
cpu_to_le16(0x0200), /* bcdUSB: v2.0 */
|
2008-12-10 16:55:19 +00:00
|
|
|
9, /* bDeviceClass: UDCLASS_HUB */
|
|
|
|
0, /* bDeviceSubClass: UDSUBCLASS_HUB */
|
|
|
|
1, /* bDeviceProtocol: UDPROTO_HSHUBSTT */
|
|
|
|
64, /* bMaxPacketSize: 64 bytes */
|
|
|
|
0x0000, /* idVendor */
|
|
|
|
0x0000, /* idProduct */
|
2010-02-27 18:29:42 +00:00
|
|
|
cpu_to_le16(0x0100), /* bcdDevice */
|
2008-12-10 16:55:19 +00:00
|
|
|
1, /* iManufacturer */
|
|
|
|
2, /* iProduct */
|
|
|
|
0, /* iSerialNumber */
|
|
|
|
1 /* bNumConfigurations: 1 */
|
|
|
|
},
|
|
|
|
{
|
|
|
|
0x9,
|
|
|
|
2, /* bDescriptorType: UDESC_CONFIG */
|
|
|
|
cpu_to_le16(0x19),
|
|
|
|
1, /* bNumInterface */
|
|
|
|
1, /* bConfigurationValue */
|
|
|
|
0, /* iConfiguration */
|
|
|
|
0x40, /* bmAttributes: UC_SELF_POWER */
|
|
|
|
0 /* bMaxPower */
|
|
|
|
},
|
|
|
|
{
|
|
|
|
0x9, /* bLength */
|
|
|
|
4, /* bDescriptorType: UDESC_INTERFACE */
|
|
|
|
0, /* bInterfaceNumber */
|
|
|
|
0, /* bAlternateSetting */
|
|
|
|
1, /* bNumEndpoints */
|
|
|
|
9, /* bInterfaceClass: UICLASS_HUB */
|
|
|
|
0, /* bInterfaceSubClass: UISUBCLASS_HUB */
|
|
|
|
0, /* bInterfaceProtocol: UIPROTO_HSHUBSTT */
|
|
|
|
0 /* iInterface */
|
|
|
|
},
|
|
|
|
{
|
|
|
|
0x7, /* bLength */
|
|
|
|
5, /* bDescriptorType: UDESC_ENDPOINT */
|
|
|
|
0x81, /* bEndpointAddress:
|
|
|
|
* UE_DIR_IN | EHCI_INTR_ENDPT
|
|
|
|
*/
|
|
|
|
3, /* bmAttributes: UE_INTERRUPT */
|
2009-10-31 17:37:38 +00:00
|
|
|
8, /* wMaxPacketSize */
|
2008-12-10 16:55:19 +00:00
|
|
|
255 /* bInterval */
|
|
|
|
},
|
2008-11-28 12:20:46 +00:00
|
|
|
};
|
|
|
|
|
2021-10-09 13:27:32 +00:00
|
|
|
#if defined(CONFIG_USB_EHCI_IS_TDI)
|
2008-12-13 21:51:58 +00:00
|
|
|
#define ehci_is_TDI() (1)
|
|
|
|
#else
|
|
|
|
#define ehci_is_TDI() (0)
|
|
|
|
#endif
|
|
|
|
|
2015-03-25 18:22:25 +00:00
|
|
|
static struct ehci_ctrl *ehci_get_ctrl(struct usb_device *udev)
|
|
|
|
{
|
2018-11-21 07:43:56 +00:00
|
|
|
#if CONFIG_IS_ENABLED(DM_USB)
|
2015-05-05 09:54:33 +00:00
|
|
|
return dev_get_priv(usb_get_bus(udev->dev));
|
2015-03-25 18:22:29 +00:00
|
|
|
#else
|
2015-03-25 18:22:25 +00:00
|
|
|
return udev->controller;
|
2015-03-25 18:22:29 +00:00
|
|
|
#endif
|
2015-03-25 18:22:25 +00:00
|
|
|
}
|
|
|
|
|
2015-03-25 18:22:27 +00:00
|
|
|
static int ehci_get_port_speed(struct ehci_ctrl *ctrl, uint32_t reg)
|
2013-03-27 00:52:32 +00:00
|
|
|
{
|
|
|
|
return PORTSC_PSPD(reg);
|
|
|
|
}
|
|
|
|
|
2015-03-25 18:22:27 +00:00
|
|
|
static void ehci_set_usbmode(struct ehci_ctrl *ctrl)
|
2013-03-27 00:52:32 +00:00
|
|
|
{
|
|
|
|
uint32_t tmp;
|
|
|
|
uint32_t *reg_ptr;
|
|
|
|
|
2015-03-25 18:22:23 +00:00
|
|
|
reg_ptr = (uint32_t *)((u8 *)&ctrl->hcor->or_usbcmd + USBMODE);
|
2013-03-27 00:52:32 +00:00
|
|
|
tmp = ehci_readl(reg_ptr);
|
|
|
|
tmp |= USBMODE_CM_HC;
|
|
|
|
#if defined(CONFIG_EHCI_MMIO_BIG_ENDIAN)
|
|
|
|
tmp |= USBMODE_BE;
|
2016-01-23 20:04:46 +00:00
|
|
|
#else
|
|
|
|
tmp &= ~USBMODE_BE;
|
2013-03-27 00:52:32 +00:00
|
|
|
#endif
|
|
|
|
ehci_writel(reg_ptr, tmp);
|
|
|
|
}
|
|
|
|
|
2015-03-25 18:22:27 +00:00
|
|
|
static void ehci_powerup_fixup(struct ehci_ctrl *ctrl, uint32_t *status_reg,
|
2015-03-25 18:22:21 +00:00
|
|
|
uint32_t *reg)
|
2011-07-11 00:37:01 +00:00
|
|
|
{
|
|
|
|
mdelay(50);
|
|
|
|
}
|
|
|
|
|
2015-03-25 18:22:27 +00:00
|
|
|
static uint32_t *ehci_get_portsc_register(struct ehci_ctrl *ctrl, int port)
|
2015-03-25 18:22:17 +00:00
|
|
|
{
|
2017-07-19 13:50:05 +00:00
|
|
|
int max_ports = HCS_N_PORTS(ehci_readl(&ctrl->hccr->cr_hcsparams));
|
|
|
|
|
|
|
|
if (port < 0 || port >= max_ports) {
|
2015-03-25 18:22:17 +00:00
|
|
|
/* Printing the message would cause a scan failure! */
|
2017-07-19 13:50:05 +00:00
|
|
|
debug("The request port(%u) exceeds maximum port number\n",
|
|
|
|
port);
|
2015-03-25 18:22:17 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2015-03-25 18:22:24 +00:00
|
|
|
return (uint32_t *)&ctrl->hcor->or_portsc[port];
|
2015-03-25 18:22:17 +00:00
|
|
|
}
|
|
|
|
|
2008-12-31 09:33:22 +00:00
|
|
|
static int handshake(uint32_t *ptr, uint32_t mask, uint32_t done, int usec)
|
2008-12-10 16:55:19 +00:00
|
|
|
{
|
2008-12-11 12:43:55 +00:00
|
|
|
uint32_t result;
|
|
|
|
do {
|
|
|
|
result = ehci_readl(ptr);
|
2010-10-22 12:23:00 +00:00
|
|
|
udelay(5);
|
2008-12-11 12:43:55 +00:00
|
|
|
if (result == ~(uint32_t)0)
|
|
|
|
return -1;
|
|
|
|
result &= mask;
|
|
|
|
if (result == done)
|
|
|
|
return 0;
|
2008-12-31 09:33:22 +00:00
|
|
|
usec--;
|
|
|
|
} while (usec > 0);
|
2008-12-11 12:43:55 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2015-03-25 18:22:28 +00:00
|
|
|
static int ehci_reset(struct ehci_ctrl *ctrl)
|
2008-12-11 12:43:55 +00:00
|
|
|
{
|
|
|
|
uint32_t cmd;
|
|
|
|
int ret = 0;
|
|
|
|
|
2015-03-25 18:22:28 +00:00
|
|
|
cmd = ehci_readl(&ctrl->hcor->or_usbcmd);
|
2010-11-26 14:44:00 +00:00
|
|
|
cmd = (cmd & ~CMD_RUN) | CMD_RESET;
|
2015-03-25 18:22:28 +00:00
|
|
|
ehci_writel(&ctrl->hcor->or_usbcmd, cmd);
|
|
|
|
ret = handshake((uint32_t *)&ctrl->hcor->or_usbcmd,
|
2012-09-25 22:14:35 +00:00
|
|
|
CMD_RESET, 0, 250 * 1000);
|
2008-12-11 12:43:55 +00:00
|
|
|
if (ret < 0) {
|
|
|
|
printf("EHCI fail to reset\n");
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2013-03-27 00:52:32 +00:00
|
|
|
if (ehci_is_TDI())
|
2015-03-25 18:22:28 +00:00
|
|
|
ctrl->ops.set_usb_mode(ctrl);
|
2012-02-27 10:52:47 +00:00
|
|
|
|
|
|
|
#ifdef CONFIG_USB_EHCI_TXFIFO_THRESH
|
2015-03-25 18:22:28 +00:00
|
|
|
cmd = ehci_readl(&ctrl->hcor->or_txfilltuning);
|
2012-08-10 16:22:11 +00:00
|
|
|
cmd &= ~TXFIFO_THRESH_MASK;
|
2012-02-27 10:52:47 +00:00
|
|
|
cmd |= TXFIFO_THRESH(CONFIG_USB_EHCI_TXFIFO_THRESH);
|
2015-03-25 18:22:28 +00:00
|
|
|
ehci_writel(&ctrl->hcor->or_txfilltuning, cmd);
|
2012-02-27 10:52:47 +00:00
|
|
|
#endif
|
2008-12-11 12:43:55 +00:00
|
|
|
out:
|
|
|
|
return ret;
|
2008-12-10 16:55:19 +00:00
|
|
|
}
|
2008-11-28 12:20:46 +00:00
|
|
|
|
2013-09-24 17:53:07 +00:00
|
|
|
static int ehci_shutdown(struct ehci_ctrl *ctrl)
|
|
|
|
{
|
|
|
|
int i, ret = 0;
|
|
|
|
uint32_t cmd, reg;
|
2017-07-19 13:50:05 +00:00
|
|
|
int max_ports = HCS_N_PORTS(ehci_readl(&ctrl->hccr->cr_hcsparams));
|
2013-09-24 17:53:07 +00:00
|
|
|
|
|
|
|
cmd = ehci_readl(&ctrl->hcor->or_usbcmd);
|
2016-06-15 05:15:46 +00:00
|
|
|
/* If not run, directly return */
|
|
|
|
if (!(cmd & CMD_RUN))
|
|
|
|
return 0;
|
2013-09-24 17:53:07 +00:00
|
|
|
cmd &= ~(CMD_PSE | CMD_ASE);
|
|
|
|
ehci_writel(&ctrl->hcor->or_usbcmd, cmd);
|
|
|
|
ret = handshake(&ctrl->hcor->or_usbsts, STS_ASS | STS_PSS, 0,
|
|
|
|
100 * 1000);
|
|
|
|
|
|
|
|
if (!ret) {
|
2017-07-19 13:50:05 +00:00
|
|
|
for (i = 0; i < max_ports; i++) {
|
2013-09-24 17:53:07 +00:00
|
|
|
reg = ehci_readl(&ctrl->hcor->or_portsc[i]);
|
|
|
|
reg |= EHCI_PS_SUSP;
|
|
|
|
ehci_writel(&ctrl->hcor->or_portsc[i], reg);
|
|
|
|
}
|
|
|
|
|
|
|
|
cmd &= ~CMD_RUN;
|
|
|
|
ehci_writel(&ctrl->hcor->or_usbcmd, cmd);
|
|
|
|
ret = handshake(&ctrl->hcor->or_usbsts, STS_HALT, STS_HALT,
|
|
|
|
HCHALT_TIMEOUT);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ret)
|
|
|
|
puts("EHCI failed to shut down host controller.\n");
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2008-11-28 12:20:46 +00:00
|
|
|
static int ehci_td_buffer(struct qTD *td, void *buf, size_t sz)
|
|
|
|
{
|
2012-04-09 02:07:46 +00:00
|
|
|
uint32_t delta, next;
|
2016-02-26 18:23:27 +00:00
|
|
|
unsigned long addr = (unsigned long)buf;
|
2008-11-28 12:20:46 +00:00
|
|
|
int idx;
|
|
|
|
|
2012-07-15 04:43:49 +00:00
|
|
|
if (addr != ALIGN(addr, ARCH_DMA_MINALIGN))
|
2012-04-09 02:07:46 +00:00
|
|
|
debug("EHCI-HCD: Misaligned buffer address (%p)\n", buf);
|
|
|
|
|
2012-07-15 04:43:49 +00:00
|
|
|
flush_dcache_range(addr, ALIGN(addr + sz, ARCH_DMA_MINALIGN));
|
|
|
|
|
2008-11-28 12:20:46 +00:00
|
|
|
idx = 0;
|
2012-07-19 20:16:38 +00:00
|
|
|
while (idx < QT_BUFFER_CNT) {
|
2016-01-23 20:04:46 +00:00
|
|
|
td->qt_buffer[idx] = cpu_to_hc32(virt_to_phys((void *)addr));
|
2010-10-19 14:13:15 +00:00
|
|
|
td->qt_buffer_hi[idx] = 0;
|
2012-08-10 16:22:11 +00:00
|
|
|
next = (addr + EHCI_PAGE_SIZE) & ~(EHCI_PAGE_SIZE - 1);
|
2008-11-28 12:20:46 +00:00
|
|
|
delta = next - addr;
|
|
|
|
if (delta >= sz)
|
|
|
|
break;
|
|
|
|
sz -= delta;
|
|
|
|
addr = next;
|
|
|
|
idx++;
|
|
|
|
}
|
|
|
|
|
2012-07-19 20:16:38 +00:00
|
|
|
if (idx == QT_BUFFER_CNT) {
|
2015-03-17 20:46:37 +00:00
|
|
|
printf("out of buffer pointers (%zu bytes left)\n", sz);
|
2008-11-28 12:20:46 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-11-06 13:48:20 +00:00
|
|
|
static inline u8 ehci_encode_speed(enum usb_device_speed speed)
|
|
|
|
{
|
|
|
|
#define QH_HIGH_SPEED 2
|
|
|
|
#define QH_FULL_SPEED 0
|
|
|
|
#define QH_LOW_SPEED 1
|
|
|
|
if (speed == USB_SPEED_HIGH)
|
|
|
|
return QH_HIGH_SPEED;
|
|
|
|
if (speed == USB_SPEED_LOW)
|
|
|
|
return QH_LOW_SPEED;
|
|
|
|
return QH_FULL_SPEED;
|
|
|
|
}
|
|
|
|
|
2015-03-25 18:22:29 +00:00
|
|
|
static void ehci_update_endpt2_dev_n_port(struct usb_device *udev,
|
2014-09-20 14:51:22 +00:00
|
|
|
struct QH *qh)
|
|
|
|
{
|
2015-12-22 00:21:03 +00:00
|
|
|
uint8_t portnr = 0;
|
|
|
|
uint8_t hubaddr = 0;
|
2014-09-20 14:51:22 +00:00
|
|
|
|
2015-03-25 18:22:29 +00:00
|
|
|
if (udev->speed != USB_SPEED_LOW && udev->speed != USB_SPEED_FULL)
|
2014-09-20 14:51:22 +00:00
|
|
|
return;
|
|
|
|
|
2015-12-22 00:21:03 +00:00
|
|
|
usb_find_usb2_hub_address_port(udev, &hubaddr, &portnr);
|
2014-09-20 14:51:22 +00:00
|
|
|
|
2015-12-22 00:21:03 +00:00
|
|
|
qh->qh_endpt2 |= cpu_to_hc32(QH_ENDPT2_PORTNUM(portnr) |
|
|
|
|
QH_ENDPT2_HUBADDR(hubaddr));
|
2014-09-20 14:51:22 +00:00
|
|
|
}
|
|
|
|
|
usb: Keep async schedule running only across mass storage xfers
Rather than keeping the asynchronous schedule running always, keep it
running only across USB mass storage transfers for now, as it seems
that keeping it running all the time interferes with certain control
transfers during device enumeration.
Note that running the async schedule all the time should not be an
issue, especially on EHCI HCD, as that one implements most of the
transfers using async schedule.
Note that we have usb_disable_asynch(), which however is utterly broken.
The usb_disable_asynch() blocks the USB core from doing async transfers
by setting a global flag. The async schedule should however be disabled
per USB controller. Moreover, setting a global flag does not prevent the
controller from using the async schedule, which e.g. the EHCI HCD does.
This patch implements additional callback to the controller, which
permits it to lock the async schedule and keep it running across
multiple transfers. Once the schedule is unlocked, it must also be
disabled. This thus prevents the async schedule from running outside
of the USB mass storage transfers.
Signed-off-by: Marek Vasut <marek.vasut+renesas@gmail.com>
Cc: Lukasz Majewski <lukma@denx.de>
Cc: Tom Rini <trini@konsulko.com>
Tested-by: Tom Rini <trini@konsulko.com> [omap3_beagle, previously failing]
2020-04-06 12:29:44 +00:00
|
|
|
static int ehci_enable_async(struct ehci_ctrl *ctrl)
|
|
|
|
{
|
|
|
|
u32 cmd;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
/* Enable async. schedule. */
|
|
|
|
cmd = ehci_readl(&ctrl->hcor->or_usbcmd);
|
|
|
|
if (cmd & CMD_ASE)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
cmd |= CMD_ASE;
|
|
|
|
ehci_writel(&ctrl->hcor->or_usbcmd, cmd);
|
|
|
|
|
|
|
|
ret = handshake((uint32_t *)&ctrl->hcor->or_usbsts, STS_ASS, STS_ASS,
|
|
|
|
100 * 1000);
|
|
|
|
if (ret < 0)
|
|
|
|
printf("EHCI fail timeout STS_ASS set\n");
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ehci_disable_async(struct ehci_ctrl *ctrl)
|
|
|
|
{
|
|
|
|
u32 cmd;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (ctrl->async_locked)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* Disable async schedule. */
|
|
|
|
cmd = ehci_readl(&ctrl->hcor->or_usbcmd);
|
|
|
|
if (!(cmd & CMD_ASE))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
cmd &= ~CMD_ASE;
|
|
|
|
ehci_writel(&ctrl->hcor->or_usbcmd, cmd);
|
|
|
|
|
|
|
|
ret = handshake((uint32_t *)&ctrl->hcor->or_usbsts, STS_ASS, 0,
|
|
|
|
100 * 1000);
|
|
|
|
if (ret < 0)
|
|
|
|
printf("EHCI fail timeout STS_ASS reset\n");
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2021-03-09 03:26:57 +00:00
|
|
|
static int ehci_iaa_cycle(struct ehci_ctrl *ctrl)
|
|
|
|
{
|
|
|
|
u32 cmd, status;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
/* Enable Interrupt on Async Advance Doorbell. */
|
|
|
|
cmd = ehci_readl(&ctrl->hcor->or_usbcmd);
|
|
|
|
cmd |= CMD_IAAD;
|
|
|
|
ehci_writel(&ctrl->hcor->or_usbcmd, cmd);
|
|
|
|
|
|
|
|
ret = handshake(&ctrl->hcor->or_usbsts, STS_IAA, STS_IAA,
|
|
|
|
10 * 1000); /* 10ms timeout */
|
|
|
|
if (ret < 0)
|
|
|
|
printf("EHCI fail timeout STS_IAA set\n");
|
|
|
|
|
|
|
|
status = ehci_readl(&ctrl->hcor->or_usbsts);
|
|
|
|
if (status & STS_IAA)
|
|
|
|
ehci_writel(&ctrl->hcor->or_usbsts, STS_IAA);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2008-11-28 12:20:46 +00:00
|
|
|
static int
|
|
|
|
ehci_submit_async(struct usb_device *dev, unsigned long pipe, void *buffer,
|
|
|
|
int length, struct devrequest *req)
|
|
|
|
{
|
2012-07-15 22:14:24 +00:00
|
|
|
ALLOC_ALIGN_BUFFER(struct QH, qh, 1, USB_DMA_MINALIGN);
|
2012-08-10 16:22:32 +00:00
|
|
|
struct qTD *qtd;
|
|
|
|
int qtd_count = 0;
|
2012-04-08 21:32:05 +00:00
|
|
|
int qtd_counter = 0;
|
2008-11-28 12:20:46 +00:00
|
|
|
volatile struct qTD *vtd;
|
|
|
|
unsigned long ts;
|
|
|
|
uint32_t *tdp;
|
usb: ehci-hcd: Keep async schedule running
Profiling the EHCI driver shows a significant performance problem in
ehci_submit_async(). Specifically, this function keeps enabling and
disabling async schedule back and forth for every single transaction.
However, enabling/disabling the async schedule does not take effect
immediatelly, but instead may take up to 1 mS (8 uFrames) to complete.
This impacts USB storage significantly, esp. since the recent reduction
of maximum transfer size to support more USB storage devices. This in
turn results in sharp increase in the number of ehci_submit_async()
calls. Since one USB storage BBB transfer does three such calls and
the maximum transfer size is 120 kiB, the overhead is 6 mS per 120 kiB,
which is unacceptable.
However, this overhead can be removed simply by keeping the async
schedule running. Specifically, the first transfer starts the async
schedule and then each and every subsequent transfer only adds a new
QH into that schedule, waits until the QH is completed and does NOT
disable the async schedule. The async schedule is stopped only by
shutting down the controller, which must happen before moving out
of U-Boot, otherwise the controller will corrupt memory.
Signed-off-by: Marek Vasut <marek.vasut+renesas@gmail.com>
Cc: Bin Meng <bmeng.cn@gmail.com>
Cc: Simon Glass <sjg@chromium.org>
2019-10-06 14:13:38 +00:00
|
|
|
uint32_t endpt, maxpacket, token, usbsts, qhtoken;
|
2008-11-28 12:20:46 +00:00
|
|
|
uint32_t c, toggle;
|
2011-02-07 22:42:16 +00:00
|
|
|
int timeout;
|
2008-12-31 09:33:22 +00:00
|
|
|
int ret = 0;
|
2015-03-25 18:22:25 +00:00
|
|
|
struct ehci_ctrl *ctrl = ehci_get_ctrl(dev);
|
2008-11-28 12:20:46 +00:00
|
|
|
|
2008-12-10 16:55:19 +00:00
|
|
|
debug("dev=%p, pipe=%lx, buffer=%p, length=%d, req=%p\n", dev, pipe,
|
2008-11-28 12:20:46 +00:00
|
|
|
buffer, length, req);
|
|
|
|
if (req != NULL)
|
2008-12-10 16:55:19 +00:00
|
|
|
debug("req=%u (%#x), type=%u (%#x), value=%u (%#x), index=%u\n",
|
2008-11-28 12:20:46 +00:00
|
|
|
req->request, req->request,
|
|
|
|
req->requesttype, req->requesttype,
|
|
|
|
le16_to_cpu(req->value), le16_to_cpu(req->value),
|
2008-12-10 16:55:19 +00:00
|
|
|
le16_to_cpu(req->index));
|
2008-11-28 12:20:46 +00:00
|
|
|
|
2012-08-10 16:27:23 +00:00
|
|
|
#define PKT_ALIGN 512
|
2012-08-10 16:22:32 +00:00
|
|
|
/*
|
|
|
|
* The USB transfer is split into qTD transfers. Eeach qTD transfer is
|
|
|
|
* described by a transfer descriptor (the qTD). The qTDs form a linked
|
|
|
|
* list with a queue head (QH).
|
|
|
|
*
|
|
|
|
* Each qTD transfer starts with a new USB packet, i.e. a packet cannot
|
|
|
|
* have its beginning in a qTD transfer and its end in the following
|
|
|
|
* one, so the qTD transfer lengths have to be chosen accordingly.
|
|
|
|
*
|
|
|
|
* Each qTD transfer uses up to QT_BUFFER_CNT data buffers, mapped to
|
|
|
|
* single pages. The first data buffer can start at any offset within a
|
|
|
|
* page (not considering the cache-line alignment issues), while the
|
|
|
|
* following buffers must be page-aligned. There is no alignment
|
|
|
|
* constraint on the size of a qTD transfer.
|
|
|
|
*/
|
|
|
|
if (req != NULL)
|
|
|
|
/* 1 qTD will be needed for SETUP, and 1 for ACK. */
|
|
|
|
qtd_count += 1 + 1;
|
|
|
|
if (length > 0 || req == NULL) {
|
|
|
|
/*
|
|
|
|
* Determine the qTD transfer size that will be used for the
|
2012-08-10 16:27:23 +00:00
|
|
|
* data payload (not considering the first qTD transfer, which
|
|
|
|
* may be longer or shorter, and the final one, which may be
|
|
|
|
* shorter).
|
2012-08-10 16:22:32 +00:00
|
|
|
*
|
|
|
|
* In order to keep each packet within a qTD transfer, the qTD
|
2012-08-10 16:27:23 +00:00
|
|
|
* transfer size is aligned to PKT_ALIGN, which is a multiple of
|
|
|
|
* wMaxPacketSize (except in some cases for interrupt transfers,
|
|
|
|
* see comment in submit_int_msg()).
|
2012-08-10 16:22:32 +00:00
|
|
|
*
|
2012-08-10 16:27:23 +00:00
|
|
|
* By default, i.e. if the input buffer is aligned to PKT_ALIGN,
|
2012-08-10 16:22:32 +00:00
|
|
|
* QT_BUFFER_CNT full pages will be used.
|
|
|
|
*/
|
|
|
|
int xfr_sz = QT_BUFFER_CNT;
|
|
|
|
/*
|
2012-08-10 16:27:23 +00:00
|
|
|
* However, if the input buffer is not aligned to PKT_ALIGN, the
|
|
|
|
* qTD transfer size will be one page shorter, and the first qTD
|
2012-08-10 16:22:32 +00:00
|
|
|
* data buffer of each transfer will be page-unaligned.
|
|
|
|
*/
|
2015-03-17 20:46:37 +00:00
|
|
|
if ((unsigned long)buffer & (PKT_ALIGN - 1))
|
2012-08-10 16:22:32 +00:00
|
|
|
xfr_sz--;
|
|
|
|
/* Convert the qTD transfer size to bytes. */
|
|
|
|
xfr_sz *= EHCI_PAGE_SIZE;
|
|
|
|
/*
|
2012-08-10 16:27:23 +00:00
|
|
|
* Approximate by excess the number of qTDs that will be
|
|
|
|
* required for the data payload. The exact formula is way more
|
|
|
|
* complicated and saves at most 2 qTDs, i.e. a total of 128
|
|
|
|
* bytes.
|
2012-08-10 16:22:32 +00:00
|
|
|
*/
|
2012-08-10 16:27:23 +00:00
|
|
|
qtd_count += 2 + length / xfr_sz;
|
2012-08-10 16:22:32 +00:00
|
|
|
}
|
|
|
|
/*
|
2012-08-10 16:27:23 +00:00
|
|
|
* Threshold value based on the worst-case total size of the allocated qTDs for
|
|
|
|
* a mass-storage transfer of 65535 blocks of 512 bytes.
|
2012-08-10 16:22:32 +00:00
|
|
|
*/
|
2012-08-10 16:27:23 +00:00
|
|
|
#if CONFIG_SYS_MALLOC_LEN <= 64 + 128 * 1024
|
2012-08-10 16:22:32 +00:00
|
|
|
#warning CONFIG_SYS_MALLOC_LEN may be too small for EHCI
|
|
|
|
#endif
|
|
|
|
qtd = memalign(USB_DMA_MINALIGN, qtd_count * sizeof(struct qTD));
|
|
|
|
if (qtd == NULL) {
|
|
|
|
printf("unable to allocate TDs\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2012-07-15 22:14:24 +00:00
|
|
|
memset(qh, 0, sizeof(struct QH));
|
2012-08-10 16:22:32 +00:00
|
|
|
memset(qtd, 0, qtd_count * sizeof(*qtd));
|
2012-04-08 21:32:05 +00:00
|
|
|
|
2012-04-09 02:07:46 +00:00
|
|
|
toggle = usb_gettoggle(dev, usb_pipeendpoint(pipe), usb_pipeout(pipe));
|
|
|
|
|
2012-04-09 02:13:00 +00:00
|
|
|
/*
|
|
|
|
* Setup QH (3.6 in ehci-r10.pdf)
|
|
|
|
*
|
|
|
|
* qh_link ................. 03-00 H
|
|
|
|
* qh_endpt1 ............... 07-04 H
|
|
|
|
* qh_endpt2 ............... 0B-08 H
|
|
|
|
* - qh_curtd
|
|
|
|
* qh_overlay.qt_next ...... 13-10 H
|
|
|
|
* - qh_overlay.qt_altnext
|
|
|
|
*/
|
2016-01-23 20:04:46 +00:00
|
|
|
qh->qh_link = cpu_to_hc32(virt_to_phys(&ctrl->qh_list) | QH_LINK_TYPE_QH);
|
2012-11-06 13:48:20 +00:00
|
|
|
c = (dev->speed != USB_SPEED_HIGH) && !usb_pipeendpoint(pipe);
|
2012-08-10 16:27:23 +00:00
|
|
|
maxpacket = usb_maxpacket(dev, pipe);
|
2012-08-10 16:22:11 +00:00
|
|
|
endpt = QH_ENDPT1_RL(8) | QH_ENDPT1_C(c) |
|
2012-08-10 16:27:23 +00:00
|
|
|
QH_ENDPT1_MAXPKTLEN(maxpacket) | QH_ENDPT1_H(0) |
|
2012-08-10 16:22:11 +00:00
|
|
|
QH_ENDPT1_DTC(QH_ENDPT1_DTC_DT_FROM_QTD) |
|
|
|
|
QH_ENDPT1_ENDPT(usb_pipeendpoint(pipe)) | QH_ENDPT1_I(0) |
|
|
|
|
QH_ENDPT1_DEVADDR(usb_pipedevice(pipe));
|
2018-10-04 07:03:53 +00:00
|
|
|
|
|
|
|
/* Force FS for fsl HS quirk */
|
|
|
|
if (!ctrl->has_fsl_erratum_a005275)
|
|
|
|
endpt |= QH_ENDPT1_EPS(ehci_encode_speed(dev->speed));
|
|
|
|
else
|
|
|
|
endpt |= QH_ENDPT1_EPS(ehci_encode_speed(QH_FULL_SPEED));
|
|
|
|
|
2012-07-15 22:14:24 +00:00
|
|
|
qh->qh_endpt1 = cpu_to_hc32(endpt);
|
2014-09-20 14:51:22 +00:00
|
|
|
endpt = QH_ENDPT2_MULT(1) | QH_ENDPT2_UFCMASK(0) | QH_ENDPT2_UFSMASK(0);
|
2012-07-15 22:14:24 +00:00
|
|
|
qh->qh_endpt2 = cpu_to_hc32(endpt);
|
2014-09-20 14:51:22 +00:00
|
|
|
ehci_update_endpt2_dev_n_port(dev, qh);
|
2012-07-15 22:14:24 +00:00
|
|
|
qh->qh_overlay.qt_next = cpu_to_hc32(QT_NEXT_TERMINATE);
|
2014-02-07 16:53:50 +00:00
|
|
|
qh->qh_overlay.qt_altnext = cpu_to_hc32(QT_NEXT_TERMINATE);
|
2008-11-28 12:20:46 +00:00
|
|
|
|
2012-07-15 22:14:24 +00:00
|
|
|
tdp = &qh->qh_overlay.qt_next;
|
2008-11-28 12:20:46 +00:00
|
|
|
if (req != NULL) {
|
2012-04-09 02:13:00 +00:00
|
|
|
/*
|
|
|
|
* Setup request qTD (3.5 in ehci-r10.pdf)
|
|
|
|
*
|
|
|
|
* qt_next ................ 03-00 H
|
|
|
|
* qt_altnext ............. 07-04 H
|
|
|
|
* qt_token ............... 0B-08 H
|
|
|
|
*
|
|
|
|
* [ buffer, buffer_hi ] loaded with "req".
|
|
|
|
*/
|
2012-04-08 21:32:05 +00:00
|
|
|
qtd[qtd_counter].qt_next = cpu_to_hc32(QT_NEXT_TERMINATE);
|
|
|
|
qtd[qtd_counter].qt_altnext = cpu_to_hc32(QT_NEXT_TERMINATE);
|
2012-08-10 16:22:11 +00:00
|
|
|
token = QT_TOKEN_DT(0) | QT_TOKEN_TOTALBYTES(sizeof(*req)) |
|
|
|
|
QT_TOKEN_IOC(0) | QT_TOKEN_CPAGE(0) | QT_TOKEN_CERR(3) |
|
|
|
|
QT_TOKEN_PID(QT_TOKEN_PID_SETUP) |
|
|
|
|
QT_TOKEN_STATUS(QT_TOKEN_STATUS_ACTIVE);
|
2012-04-08 21:32:05 +00:00
|
|
|
qtd[qtd_counter].qt_token = cpu_to_hc32(token);
|
2012-08-10 16:22:11 +00:00
|
|
|
if (ehci_td_buffer(&qtd[qtd_counter], req, sizeof(*req))) {
|
|
|
|
printf("unable to construct SETUP TD\n");
|
2008-11-28 12:20:46 +00:00
|
|
|
goto fail;
|
|
|
|
}
|
2012-04-09 02:13:00 +00:00
|
|
|
/* Update previous qTD! */
|
2016-01-23 20:04:46 +00:00
|
|
|
*tdp = cpu_to_hc32(virt_to_phys(&qtd[qtd_counter]));
|
2012-04-08 21:32:05 +00:00
|
|
|
tdp = &qtd[qtd_counter++].qt_next;
|
2008-11-28 12:20:46 +00:00
|
|
|
toggle = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (length > 0 || req == NULL) {
|
2012-08-10 16:22:32 +00:00
|
|
|
uint8_t *buf_ptr = buffer;
|
|
|
|
int left_length = length;
|
|
|
|
|
|
|
|
do {
|
|
|
|
/*
|
|
|
|
* Determine the size of this qTD transfer. By default,
|
|
|
|
* QT_BUFFER_CNT full pages can be used.
|
|
|
|
*/
|
|
|
|
int xfr_bytes = QT_BUFFER_CNT * EHCI_PAGE_SIZE;
|
|
|
|
/*
|
|
|
|
* However, if the input buffer is not page-aligned, the
|
|
|
|
* portion of the first page before the buffer start
|
|
|
|
* offset within that page is unusable.
|
|
|
|
*/
|
2015-03-17 20:46:37 +00:00
|
|
|
xfr_bytes -= (unsigned long)buf_ptr & (EHCI_PAGE_SIZE - 1);
|
2012-08-10 16:22:32 +00:00
|
|
|
/*
|
|
|
|
* In order to keep each packet within a qTD transfer,
|
2012-08-10 16:27:23 +00:00
|
|
|
* align the qTD transfer size to PKT_ALIGN.
|
2012-08-10 16:22:32 +00:00
|
|
|
*/
|
2012-08-10 16:27:23 +00:00
|
|
|
xfr_bytes &= ~(PKT_ALIGN - 1);
|
2012-08-10 16:22:32 +00:00
|
|
|
/*
|
|
|
|
* This transfer may be shorter than the available qTD
|
|
|
|
* transfer size that has just been computed.
|
|
|
|
*/
|
|
|
|
xfr_bytes = min(xfr_bytes, left_length);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Setup request qTD (3.5 in ehci-r10.pdf)
|
|
|
|
*
|
|
|
|
* qt_next ................ 03-00 H
|
|
|
|
* qt_altnext ............. 07-04 H
|
|
|
|
* qt_token ............... 0B-08 H
|
|
|
|
*
|
|
|
|
* [ buffer, buffer_hi ] loaded with "buffer".
|
|
|
|
*/
|
|
|
|
qtd[qtd_counter].qt_next =
|
|
|
|
cpu_to_hc32(QT_NEXT_TERMINATE);
|
|
|
|
qtd[qtd_counter].qt_altnext =
|
|
|
|
cpu_to_hc32(QT_NEXT_TERMINATE);
|
|
|
|
token = QT_TOKEN_DT(toggle) |
|
|
|
|
QT_TOKEN_TOTALBYTES(xfr_bytes) |
|
|
|
|
QT_TOKEN_IOC(req == NULL) | QT_TOKEN_CPAGE(0) |
|
|
|
|
QT_TOKEN_CERR(3) |
|
|
|
|
QT_TOKEN_PID(usb_pipein(pipe) ?
|
|
|
|
QT_TOKEN_PID_IN : QT_TOKEN_PID_OUT) |
|
|
|
|
QT_TOKEN_STATUS(QT_TOKEN_STATUS_ACTIVE);
|
|
|
|
qtd[qtd_counter].qt_token = cpu_to_hc32(token);
|
|
|
|
if (ehci_td_buffer(&qtd[qtd_counter], buf_ptr,
|
|
|
|
xfr_bytes)) {
|
|
|
|
printf("unable to construct DATA TD\n");
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
/* Update previous qTD! */
|
2016-01-23 20:04:46 +00:00
|
|
|
*tdp = cpu_to_hc32(virt_to_phys(&qtd[qtd_counter]));
|
2012-08-10 16:22:32 +00:00
|
|
|
tdp = &qtd[qtd_counter++].qt_next;
|
2012-08-10 16:27:23 +00:00
|
|
|
/*
|
|
|
|
* Data toggle has to be adjusted since the qTD transfer
|
|
|
|
* size is not always an even multiple of
|
|
|
|
* wMaxPacketSize.
|
|
|
|
*/
|
|
|
|
if ((xfr_bytes / maxpacket) & 1)
|
|
|
|
toggle ^= 1;
|
2012-08-10 16:22:32 +00:00
|
|
|
buf_ptr += xfr_bytes;
|
|
|
|
left_length -= xfr_bytes;
|
|
|
|
} while (left_length > 0);
|
2008-11-28 12:20:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (req != NULL) {
|
2012-04-09 02:13:00 +00:00
|
|
|
/*
|
|
|
|
* Setup request qTD (3.5 in ehci-r10.pdf)
|
|
|
|
*
|
|
|
|
* qt_next ................ 03-00 H
|
|
|
|
* qt_altnext ............. 07-04 H
|
|
|
|
* qt_token ............... 0B-08 H
|
|
|
|
*/
|
2012-04-08 21:32:05 +00:00
|
|
|
qtd[qtd_counter].qt_next = cpu_to_hc32(QT_NEXT_TERMINATE);
|
|
|
|
qtd[qtd_counter].qt_altnext = cpu_to_hc32(QT_NEXT_TERMINATE);
|
2012-08-10 16:27:23 +00:00
|
|
|
token = QT_TOKEN_DT(1) | QT_TOKEN_TOTALBYTES(0) |
|
2012-08-10 16:22:11 +00:00
|
|
|
QT_TOKEN_IOC(1) | QT_TOKEN_CPAGE(0) | QT_TOKEN_CERR(3) |
|
|
|
|
QT_TOKEN_PID(usb_pipein(pipe) ?
|
|
|
|
QT_TOKEN_PID_OUT : QT_TOKEN_PID_IN) |
|
|
|
|
QT_TOKEN_STATUS(QT_TOKEN_STATUS_ACTIVE);
|
2012-04-08 21:32:05 +00:00
|
|
|
qtd[qtd_counter].qt_token = cpu_to_hc32(token);
|
2012-04-09 02:13:00 +00:00
|
|
|
/* Update previous qTD! */
|
2016-01-23 20:04:46 +00:00
|
|
|
*tdp = cpu_to_hc32(virt_to_phys(&qtd[qtd_counter]));
|
2012-04-08 21:32:05 +00:00
|
|
|
tdp = &qtd[qtd_counter++].qt_next;
|
2008-11-28 12:20:46 +00:00
|
|
|
}
|
|
|
|
|
2016-01-23 20:04:46 +00:00
|
|
|
ctrl->qh_list.qh_link = cpu_to_hc32(virt_to_phys(qh) | QH_LINK_TYPE_QH);
|
2008-11-28 12:20:46 +00:00
|
|
|
|
2009-01-21 16:12:19 +00:00
|
|
|
/* Flush dcache */
|
2015-03-17 20:46:37 +00:00
|
|
|
flush_dcache_range((unsigned long)&ctrl->qh_list,
|
2012-09-25 22:14:35 +00:00
|
|
|
ALIGN_END_ADDR(struct QH, &ctrl->qh_list, 1));
|
2015-03-17 20:46:37 +00:00
|
|
|
flush_dcache_range((unsigned long)qh, ALIGN_END_ADDR(struct QH, qh, 1));
|
|
|
|
flush_dcache_range((unsigned long)qtd,
|
2012-08-10 16:22:32 +00:00
|
|
|
ALIGN_END_ADDR(struct qTD, qtd, qtd_count));
|
2009-01-21 16:12:19 +00:00
|
|
|
|
2012-09-25 22:14:35 +00:00
|
|
|
usbsts = ehci_readl(&ctrl->hcor->or_usbsts);
|
|
|
|
ehci_writel(&ctrl->hcor->or_usbsts, (usbsts & 0x3f));
|
2008-11-28 12:20:46 +00:00
|
|
|
|
usb: Keep async schedule running only across mass storage xfers
Rather than keeping the asynchronous schedule running always, keep it
running only across USB mass storage transfers for now, as it seems
that keeping it running all the time interferes with certain control
transfers during device enumeration.
Note that running the async schedule all the time should not be an
issue, especially on EHCI HCD, as that one implements most of the
transfers using async schedule.
Note that we have usb_disable_asynch(), which however is utterly broken.
The usb_disable_asynch() blocks the USB core from doing async transfers
by setting a global flag. The async schedule should however be disabled
per USB controller. Moreover, setting a global flag does not prevent the
controller from using the async schedule, which e.g. the EHCI HCD does.
This patch implements additional callback to the controller, which
permits it to lock the async schedule and keep it running across
multiple transfers. Once the schedule is unlocked, it must also be
disabled. This thus prevents the async schedule from running outside
of the USB mass storage transfers.
Signed-off-by: Marek Vasut <marek.vasut+renesas@gmail.com>
Cc: Lukasz Majewski <lukma@denx.de>
Cc: Tom Rini <trini@konsulko.com>
Tested-by: Tom Rini <trini@konsulko.com> [omap3_beagle, previously failing]
2020-04-06 12:29:44 +00:00
|
|
|
ret = ehci_enable_async(ctrl);
|
|
|
|
if (ret)
|
|
|
|
goto fail;
|
2008-11-28 12:20:46 +00:00
|
|
|
|
|
|
|
/* Wait for TDs to be processed. */
|
|
|
|
ts = get_timer(0);
|
2012-04-08 21:32:05 +00:00
|
|
|
vtd = &qtd[qtd_counter - 1];
|
2011-02-07 22:42:16 +00:00
|
|
|
timeout = USB_TIMEOUT_MS(pipe);
|
2008-11-28 12:20:46 +00:00
|
|
|
do {
|
2009-01-21 16:12:19 +00:00
|
|
|
/* Invalidate dcache */
|
2015-03-17 20:46:37 +00:00
|
|
|
invalidate_dcache_range((unsigned long)&ctrl->qh_list,
|
2012-09-25 22:14:35 +00:00
|
|
|
ALIGN_END_ADDR(struct QH, &ctrl->qh_list, 1));
|
2015-03-17 20:46:37 +00:00
|
|
|
invalidate_dcache_range((unsigned long)qh,
|
2012-07-15 22:14:24 +00:00
|
|
|
ALIGN_END_ADDR(struct QH, qh, 1));
|
2015-03-17 20:46:37 +00:00
|
|
|
invalidate_dcache_range((unsigned long)qtd,
|
2012-08-10 16:22:32 +00:00
|
|
|
ALIGN_END_ADDR(struct qTD, qtd, qtd_count));
|
2012-04-09 02:07:46 +00:00
|
|
|
|
2008-12-10 16:55:19 +00:00
|
|
|
token = hc32_to_cpu(vtd->qt_token);
|
2012-08-10 16:22:11 +00:00
|
|
|
if (!(QT_TOKEN_GET_STATUS(token) & QT_TOKEN_STATUS_ACTIVE))
|
2008-11-28 12:20:46 +00:00
|
|
|
break;
|
2010-11-26 14:43:28 +00:00
|
|
|
WATCHDOG_RESET();
|
2011-02-07 22:42:16 +00:00
|
|
|
} while (get_timer(ts) < timeout);
|
usb: ehci-hcd: Keep async schedule running
Profiling the EHCI driver shows a significant performance problem in
ehci_submit_async(). Specifically, this function keeps enabling and
disabling async schedule back and forth for every single transaction.
However, enabling/disabling the async schedule does not take effect
immediatelly, but instead may take up to 1 mS (8 uFrames) to complete.
This impacts USB storage significantly, esp. since the recent reduction
of maximum transfer size to support more USB storage devices. This in
turn results in sharp increase in the number of ehci_submit_async()
calls. Since one USB storage BBB transfer does three such calls and
the maximum transfer size is 120 kiB, the overhead is 6 mS per 120 kiB,
which is unacceptable.
However, this overhead can be removed simply by keeping the async
schedule running. Specifically, the first transfer starts the async
schedule and then each and every subsequent transfer only adds a new
QH into that schedule, waits until the QH is completed and does NOT
disable the async schedule. The async schedule is stopped only by
shutting down the controller, which must happen before moving out
of U-Boot, otherwise the controller will corrupt memory.
Signed-off-by: Marek Vasut <marek.vasut+renesas@gmail.com>
Cc: Bin Meng <bmeng.cn@gmail.com>
Cc: Simon Glass <sjg@chromium.org>
2019-10-06 14:13:38 +00:00
|
|
|
qhtoken = hc32_to_cpu(qh->qh_overlay.qt_token);
|
|
|
|
|
|
|
|
ctrl->qh_list.qh_link = cpu_to_hc32(virt_to_phys(&ctrl->qh_list) | QH_LINK_TYPE_QH);
|
|
|
|
flush_dcache_range((unsigned long)&ctrl->qh_list,
|
|
|
|
ALIGN_END_ADDR(struct QH, &ctrl->qh_list, 1));
|
2011-02-07 22:42:16 +00:00
|
|
|
|
2021-03-09 03:26:57 +00:00
|
|
|
/* Set IAAD, poll IAA */
|
|
|
|
ret = ehci_iaa_cycle(ctrl);
|
|
|
|
if (ret)
|
|
|
|
goto fail;
|
|
|
|
|
2012-07-15 04:43:49 +00:00
|
|
|
/*
|
|
|
|
* Invalidate the memory area occupied by buffer
|
|
|
|
* Don't try to fix the buffer alignment, if it isn't properly
|
|
|
|
* aligned it's upper layer's fault so let invalidate_dcache_range()
|
|
|
|
* vow about it. But we have to fix the length as it's actual
|
|
|
|
* transfer length and can be unaligned. This is potentially
|
|
|
|
* dangerous operation, it's responsibility of the calling
|
|
|
|
* code to make sure enough space is reserved.
|
|
|
|
*/
|
2017-11-17 14:28:36 +00:00
|
|
|
if (buffer != NULL && length > 0)
|
|
|
|
invalidate_dcache_range((unsigned long)buffer,
|
|
|
|
ALIGN((unsigned long)buffer + length, ARCH_DMA_MINALIGN));
|
2012-04-09 02:07:46 +00:00
|
|
|
|
2011-02-07 22:42:16 +00:00
|
|
|
/* Check that the TD processing happened */
|
2012-08-10 16:22:11 +00:00
|
|
|
if (QT_TOKEN_GET_STATUS(token) & QT_TOKEN_STATUS_ACTIVE)
|
2011-02-07 22:42:16 +00:00
|
|
|
printf("EHCI timed out on TD - token=%#x\n", token);
|
2008-11-28 12:20:46 +00:00
|
|
|
|
usb: Keep async schedule running only across mass storage xfers
Rather than keeping the asynchronous schedule running always, keep it
running only across USB mass storage transfers for now, as it seems
that keeping it running all the time interferes with certain control
transfers during device enumeration.
Note that running the async schedule all the time should not be an
issue, especially on EHCI HCD, as that one implements most of the
transfers using async schedule.
Note that we have usb_disable_asynch(), which however is utterly broken.
The usb_disable_asynch() blocks the USB core from doing async transfers
by setting a global flag. The async schedule should however be disabled
per USB controller. Moreover, setting a global flag does not prevent the
controller from using the async schedule, which e.g. the EHCI HCD does.
This patch implements additional callback to the controller, which
permits it to lock the async schedule and keep it running across
multiple transfers. Once the schedule is unlocked, it must also be
disabled. This thus prevents the async schedule from running outside
of the USB mass storage transfers.
Signed-off-by: Marek Vasut <marek.vasut+renesas@gmail.com>
Cc: Lukasz Majewski <lukma@denx.de>
Cc: Tom Rini <trini@konsulko.com>
Tested-by: Tom Rini <trini@konsulko.com> [omap3_beagle, previously failing]
2020-04-06 12:29:44 +00:00
|
|
|
ret = ehci_disable_async(ctrl);
|
|
|
|
if (ret)
|
|
|
|
goto fail;
|
|
|
|
|
usb: ehci-hcd: Keep async schedule running
Profiling the EHCI driver shows a significant performance problem in
ehci_submit_async(). Specifically, this function keeps enabling and
disabling async schedule back and forth for every single transaction.
However, enabling/disabling the async schedule does not take effect
immediatelly, but instead may take up to 1 mS (8 uFrames) to complete.
This impacts USB storage significantly, esp. since the recent reduction
of maximum transfer size to support more USB storage devices. This in
turn results in sharp increase in the number of ehci_submit_async()
calls. Since one USB storage BBB transfer does three such calls and
the maximum transfer size is 120 kiB, the overhead is 6 mS per 120 kiB,
which is unacceptable.
However, this overhead can be removed simply by keeping the async
schedule running. Specifically, the first transfer starts the async
schedule and then each and every subsequent transfer only adds a new
QH into that schedule, waits until the QH is completed and does NOT
disable the async schedule. The async schedule is stopped only by
shutting down the controller, which must happen before moving out
of U-Boot, otherwise the controller will corrupt memory.
Signed-off-by: Marek Vasut <marek.vasut+renesas@gmail.com>
Cc: Bin Meng <bmeng.cn@gmail.com>
Cc: Simon Glass <sjg@chromium.org>
2019-10-06 14:13:38 +00:00
|
|
|
if (!(QT_TOKEN_GET_STATUS(qhtoken) & QT_TOKEN_STATUS_ACTIVE)) {
|
|
|
|
debug("TOKEN=%#x\n", qhtoken);
|
|
|
|
switch (QT_TOKEN_GET_STATUS(qhtoken) &
|
2012-08-10 16:22:11 +00:00
|
|
|
~(QT_TOKEN_STATUS_SPLITXSTATE | QT_TOKEN_STATUS_PERR)) {
|
2008-11-28 12:20:46 +00:00
|
|
|
case 0:
|
usb: ehci-hcd: Keep async schedule running
Profiling the EHCI driver shows a significant performance problem in
ehci_submit_async(). Specifically, this function keeps enabling and
disabling async schedule back and forth for every single transaction.
However, enabling/disabling the async schedule does not take effect
immediatelly, but instead may take up to 1 mS (8 uFrames) to complete.
This impacts USB storage significantly, esp. since the recent reduction
of maximum transfer size to support more USB storage devices. This in
turn results in sharp increase in the number of ehci_submit_async()
calls. Since one USB storage BBB transfer does three such calls and
the maximum transfer size is 120 kiB, the overhead is 6 mS per 120 kiB,
which is unacceptable.
However, this overhead can be removed simply by keeping the async
schedule running. Specifically, the first transfer starts the async
schedule and then each and every subsequent transfer only adds a new
QH into that schedule, waits until the QH is completed and does NOT
disable the async schedule. The async schedule is stopped only by
shutting down the controller, which must happen before moving out
of U-Boot, otherwise the controller will corrupt memory.
Signed-off-by: Marek Vasut <marek.vasut+renesas@gmail.com>
Cc: Bin Meng <bmeng.cn@gmail.com>
Cc: Simon Glass <sjg@chromium.org>
2019-10-06 14:13:38 +00:00
|
|
|
toggle = QT_TOKEN_GET_DT(qhtoken);
|
2008-11-28 12:20:46 +00:00
|
|
|
usb_settoggle(dev, usb_pipeendpoint(pipe),
|
|
|
|
usb_pipeout(pipe), toggle);
|
|
|
|
dev->status = 0;
|
|
|
|
break;
|
2012-08-10 16:22:11 +00:00
|
|
|
case QT_TOKEN_STATUS_HALTED:
|
2008-11-28 12:20:46 +00:00
|
|
|
dev->status = USB_ST_STALLED;
|
|
|
|
break;
|
2012-08-10 16:22:11 +00:00
|
|
|
case QT_TOKEN_STATUS_ACTIVE | QT_TOKEN_STATUS_DATBUFERR:
|
|
|
|
case QT_TOKEN_STATUS_DATBUFERR:
|
2008-11-28 12:20:46 +00:00
|
|
|
dev->status = USB_ST_BUF_ERR;
|
|
|
|
break;
|
2012-08-10 16:22:11 +00:00
|
|
|
case QT_TOKEN_STATUS_HALTED | QT_TOKEN_STATUS_BABBLEDET:
|
|
|
|
case QT_TOKEN_STATUS_BABBLEDET:
|
2008-11-28 12:20:46 +00:00
|
|
|
dev->status = USB_ST_BABBLE_DET;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
dev->status = USB_ST_CRC_ERR;
|
usb: ehci-hcd: Keep async schedule running
Profiling the EHCI driver shows a significant performance problem in
ehci_submit_async(). Specifically, this function keeps enabling and
disabling async schedule back and forth for every single transaction.
However, enabling/disabling the async schedule does not take effect
immediatelly, but instead may take up to 1 mS (8 uFrames) to complete.
This impacts USB storage significantly, esp. since the recent reduction
of maximum transfer size to support more USB storage devices. This in
turn results in sharp increase in the number of ehci_submit_async()
calls. Since one USB storage BBB transfer does three such calls and
the maximum transfer size is 120 kiB, the overhead is 6 mS per 120 kiB,
which is unacceptable.
However, this overhead can be removed simply by keeping the async
schedule running. Specifically, the first transfer starts the async
schedule and then each and every subsequent transfer only adds a new
QH into that schedule, waits until the QH is completed and does NOT
disable the async schedule. The async schedule is stopped only by
shutting down the controller, which must happen before moving out
of U-Boot, otherwise the controller will corrupt memory.
Signed-off-by: Marek Vasut <marek.vasut+renesas@gmail.com>
Cc: Bin Meng <bmeng.cn@gmail.com>
Cc: Simon Glass <sjg@chromium.org>
2019-10-06 14:13:38 +00:00
|
|
|
if (QT_TOKEN_GET_STATUS(qhtoken) & QT_TOKEN_STATUS_HALTED)
|
2010-11-02 10:47:29 +00:00
|
|
|
dev->status |= USB_ST_STALLED;
|
2008-11-28 12:20:46 +00:00
|
|
|
break;
|
|
|
|
}
|
usb: ehci-hcd: Keep async schedule running
Profiling the EHCI driver shows a significant performance problem in
ehci_submit_async(). Specifically, this function keeps enabling and
disabling async schedule back and forth for every single transaction.
However, enabling/disabling the async schedule does not take effect
immediatelly, but instead may take up to 1 mS (8 uFrames) to complete.
This impacts USB storage significantly, esp. since the recent reduction
of maximum transfer size to support more USB storage devices. This in
turn results in sharp increase in the number of ehci_submit_async()
calls. Since one USB storage BBB transfer does three such calls and
the maximum transfer size is 120 kiB, the overhead is 6 mS per 120 kiB,
which is unacceptable.
However, this overhead can be removed simply by keeping the async
schedule running. Specifically, the first transfer starts the async
schedule and then each and every subsequent transfer only adds a new
QH into that schedule, waits until the QH is completed and does NOT
disable the async schedule. The async schedule is stopped only by
shutting down the controller, which must happen before moving out
of U-Boot, otherwise the controller will corrupt memory.
Signed-off-by: Marek Vasut <marek.vasut+renesas@gmail.com>
Cc: Bin Meng <bmeng.cn@gmail.com>
Cc: Simon Glass <sjg@chromium.org>
2019-10-06 14:13:38 +00:00
|
|
|
dev->act_len = length - QT_TOKEN_GET_TOTALBYTES(qhtoken);
|
2008-11-28 12:20:46 +00:00
|
|
|
} else {
|
|
|
|
dev->act_len = 0;
|
2013-05-15 07:29:23 +00:00
|
|
|
#ifndef CONFIG_USB_EHCI_FARADAY
|
2008-12-10 16:55:19 +00:00
|
|
|
debug("dev=%u, usbsts=%#x, p[1]=%#x, p[2]=%#x\n",
|
2012-09-25 22:14:35 +00:00
|
|
|
dev->devnum, ehci_readl(&ctrl->hcor->or_usbsts),
|
|
|
|
ehci_readl(&ctrl->hcor->or_portsc[0]),
|
|
|
|
ehci_readl(&ctrl->hcor->or_portsc[1]));
|
2013-05-15 07:29:23 +00:00
|
|
|
#endif
|
2008-11-28 12:20:46 +00:00
|
|
|
}
|
|
|
|
|
2012-08-10 16:22:32 +00:00
|
|
|
free(qtd);
|
2008-11-28 12:20:46 +00:00
|
|
|
return (dev->status != USB_ST_NOT_PROC) ? 0 : -1;
|
|
|
|
|
|
|
|
fail:
|
2012-08-10 16:22:32 +00:00
|
|
|
free(qtd);
|
2008-11-28 12:20:46 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2015-03-25 18:22:25 +00:00
|
|
|
static int ehci_submit_root(struct usb_device *dev, unsigned long pipe,
|
|
|
|
void *buffer, int length, struct devrequest *req)
|
2008-11-28 12:20:46 +00:00
|
|
|
{
|
|
|
|
uint8_t tmpbuf[4];
|
|
|
|
u16 typeReq;
|
2008-12-10 16:55:19 +00:00
|
|
|
void *srcptr = NULL;
|
2008-11-28 12:20:46 +00:00
|
|
|
int len, srclen;
|
|
|
|
uint32_t reg;
|
2008-12-13 21:51:58 +00:00
|
|
|
uint32_t *status_reg;
|
2013-02-28 18:08:40 +00:00
|
|
|
int port = le16_to_cpu(req->index) & 0xff;
|
2015-03-25 18:22:25 +00:00
|
|
|
struct ehci_ctrl *ctrl = ehci_get_ctrl(dev);
|
2008-11-28 12:20:46 +00:00
|
|
|
|
|
|
|
srclen = 0;
|
|
|
|
|
2008-12-10 16:55:19 +00:00
|
|
|
debug("req=%u (%#x), type=%u (%#x), value=%u, index=%u\n",
|
2008-11-28 12:20:46 +00:00
|
|
|
req->request, req->request,
|
|
|
|
req->requesttype, req->requesttype,
|
|
|
|
le16_to_cpu(req->value), le16_to_cpu(req->index));
|
|
|
|
|
2009-07-17 14:26:30 +00:00
|
|
|
typeReq = req->request | req->requesttype << 8;
|
2008-11-28 12:20:46 +00:00
|
|
|
|
2013-05-15 07:29:20 +00:00
|
|
|
switch (typeReq) {
|
|
|
|
case USB_REQ_GET_STATUS | ((USB_RT_PORT | USB_DIR_IN) << 8):
|
|
|
|
case USB_REQ_SET_FEATURE | ((USB_DIR_OUT | USB_RT_PORT) << 8):
|
|
|
|
case USB_REQ_CLEAR_FEATURE | ((USB_DIR_OUT | USB_RT_PORT) << 8):
|
2015-03-25 18:22:27 +00:00
|
|
|
status_reg = ctrl->ops.get_portsc_register(ctrl, port - 1);
|
2013-05-15 07:29:21 +00:00
|
|
|
if (!status_reg)
|
2013-05-15 07:29:20 +00:00
|
|
|
return -1;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
status_reg = NULL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2009-07-17 14:26:30 +00:00
|
|
|
switch (typeReq) {
|
2008-11-28 12:20:46 +00:00
|
|
|
case DeviceRequest | USB_REQ_GET_DESCRIPTOR:
|
|
|
|
switch (le16_to_cpu(req->value) >> 8) {
|
|
|
|
case USB_DT_DEVICE:
|
2008-12-10 16:55:19 +00:00
|
|
|
debug("USB_DT_DEVICE request\n");
|
|
|
|
srcptr = &descriptor.device;
|
2012-08-10 16:22:11 +00:00
|
|
|
srclen = descriptor.device.bLength;
|
2008-11-28 12:20:46 +00:00
|
|
|
break;
|
|
|
|
case USB_DT_CONFIG:
|
2008-12-10 16:55:19 +00:00
|
|
|
debug("USB_DT_CONFIG config\n");
|
|
|
|
srcptr = &descriptor.config;
|
2012-08-10 16:22:11 +00:00
|
|
|
srclen = descriptor.config.bLength +
|
|
|
|
descriptor.interface.bLength +
|
|
|
|
descriptor.endpoint.bLength;
|
2008-11-28 12:20:46 +00:00
|
|
|
break;
|
|
|
|
case USB_DT_STRING:
|
2008-12-10 16:55:19 +00:00
|
|
|
debug("USB_DT_STRING config\n");
|
2008-11-28 12:20:46 +00:00
|
|
|
switch (le16_to_cpu(req->value) & 0xff) {
|
|
|
|
case 0: /* Language */
|
|
|
|
srcptr = "\4\3\1\0";
|
|
|
|
srclen = 4;
|
|
|
|
break;
|
|
|
|
case 1: /* Vendor */
|
|
|
|
srcptr = "\16\3u\0-\0b\0o\0o\0t\0";
|
|
|
|
srclen = 14;
|
|
|
|
break;
|
|
|
|
case 2: /* Product */
|
|
|
|
srcptr = "\52\3E\0H\0C\0I\0 "
|
|
|
|
"\0H\0o\0s\0t\0 "
|
|
|
|
"\0C\0o\0n\0t\0r\0o\0l\0l\0e\0r\0";
|
|
|
|
srclen = 42;
|
|
|
|
break;
|
|
|
|
default:
|
2008-12-10 16:55:19 +00:00
|
|
|
debug("unknown value DT_STRING %x\n",
|
|
|
|
le16_to_cpu(req->value));
|
2008-11-28 12:20:46 +00:00
|
|
|
goto unknown;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
2008-12-10 16:55:19 +00:00
|
|
|
debug("unknown value %x\n", le16_to_cpu(req->value));
|
2008-11-28 12:20:46 +00:00
|
|
|
goto unknown;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case USB_REQ_GET_DESCRIPTOR | ((USB_DIR_IN | USB_RT_HUB) << 8):
|
|
|
|
switch (le16_to_cpu(req->value) >> 8) {
|
|
|
|
case USB_DT_HUB:
|
2008-12-10 16:55:19 +00:00
|
|
|
debug("USB_DT_HUB config\n");
|
|
|
|
srcptr = &descriptor.hub;
|
2012-08-10 16:22:11 +00:00
|
|
|
srclen = descriptor.hub.bLength;
|
2008-11-28 12:20:46 +00:00
|
|
|
break;
|
|
|
|
default:
|
2008-12-10 16:55:19 +00:00
|
|
|
debug("unknown value %x\n", le16_to_cpu(req->value));
|
2008-11-28 12:20:46 +00:00
|
|
|
goto unknown;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case USB_REQ_SET_ADDRESS | (USB_RECIP_DEVICE << 8):
|
2008-12-10 16:55:19 +00:00
|
|
|
debug("USB_REQ_SET_ADDRESS\n");
|
2012-09-25 22:14:35 +00:00
|
|
|
ctrl->rootdev = le16_to_cpu(req->value);
|
2008-11-28 12:20:46 +00:00
|
|
|
break;
|
|
|
|
case DeviceOutRequest | USB_REQ_SET_CONFIGURATION:
|
2008-12-10 16:55:19 +00:00
|
|
|
debug("USB_REQ_SET_CONFIGURATION\n");
|
2008-11-28 12:20:46 +00:00
|
|
|
/* Nothing to do */
|
|
|
|
break;
|
|
|
|
case USB_REQ_GET_STATUS | ((USB_DIR_IN | USB_RT_HUB) << 8):
|
|
|
|
tmpbuf[0] = 1; /* USB_STATUS_SELFPOWERED */
|
|
|
|
tmpbuf[1] = 0;
|
|
|
|
srcptr = tmpbuf;
|
|
|
|
srclen = 2;
|
|
|
|
break;
|
2008-12-10 16:55:19 +00:00
|
|
|
case USB_REQ_GET_STATUS | ((USB_RT_PORT | USB_DIR_IN) << 8):
|
2008-11-28 12:20:46 +00:00
|
|
|
memset(tmpbuf, 0, 4);
|
2008-12-13 21:51:58 +00:00
|
|
|
reg = ehci_readl(status_reg);
|
2008-11-28 12:20:46 +00:00
|
|
|
if (reg & EHCI_PS_CS)
|
|
|
|
tmpbuf[0] |= USB_PORT_STAT_CONNECTION;
|
|
|
|
if (reg & EHCI_PS_PE)
|
|
|
|
tmpbuf[0] |= USB_PORT_STAT_ENABLE;
|
|
|
|
if (reg & EHCI_PS_SUSP)
|
|
|
|
tmpbuf[0] |= USB_PORT_STAT_SUSPEND;
|
|
|
|
if (reg & EHCI_PS_OCA)
|
|
|
|
tmpbuf[0] |= USB_PORT_STAT_OVERCURRENT;
|
2010-02-27 18:33:21 +00:00
|
|
|
if (reg & EHCI_PS_PR)
|
|
|
|
tmpbuf[0] |= USB_PORT_STAT_RESET;
|
2008-11-28 12:20:46 +00:00
|
|
|
if (reg & EHCI_PS_PP)
|
|
|
|
tmpbuf[1] |= USB_PORT_STAT_POWER >> 8;
|
2009-01-21 16:12:01 +00:00
|
|
|
|
|
|
|
if (ehci_is_TDI()) {
|
2015-03-25 18:22:27 +00:00
|
|
|
switch (ctrl->ops.get_port_speed(ctrl, reg)) {
|
2012-08-10 16:22:11 +00:00
|
|
|
case PORTSC_PSPD_FS:
|
2009-01-21 16:12:01 +00:00
|
|
|
break;
|
2012-08-10 16:22:11 +00:00
|
|
|
case PORTSC_PSPD_LS:
|
2009-01-21 16:12:01 +00:00
|
|
|
tmpbuf[1] |= USB_PORT_STAT_LOW_SPEED >> 8;
|
|
|
|
break;
|
2012-08-10 16:22:11 +00:00
|
|
|
case PORTSC_PSPD_HS:
|
2009-01-21 16:12:01 +00:00
|
|
|
default:
|
|
|
|
tmpbuf[1] |= USB_PORT_STAT_HIGH_SPEED >> 8;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
tmpbuf[1] |= USB_PORT_STAT_HIGH_SPEED >> 8;
|
|
|
|
}
|
2008-11-28 12:20:46 +00:00
|
|
|
|
|
|
|
if (reg & EHCI_PS_CSC)
|
|
|
|
tmpbuf[2] |= USB_PORT_STAT_C_CONNECTION;
|
|
|
|
if (reg & EHCI_PS_PEC)
|
|
|
|
tmpbuf[2] |= USB_PORT_STAT_C_ENABLE;
|
|
|
|
if (reg & EHCI_PS_OCC)
|
|
|
|
tmpbuf[2] |= USB_PORT_STAT_C_OVERCURRENT;
|
2013-02-28 18:08:40 +00:00
|
|
|
if (ctrl->portreset & (1 << port))
|
2008-11-28 12:20:46 +00:00
|
|
|
tmpbuf[2] |= USB_PORT_STAT_C_RESET;
|
2008-12-13 21:51:58 +00:00
|
|
|
|
2008-11-28 12:20:46 +00:00
|
|
|
srcptr = tmpbuf;
|
|
|
|
srclen = 4;
|
|
|
|
break;
|
2008-12-10 16:55:19 +00:00
|
|
|
case USB_REQ_SET_FEATURE | ((USB_DIR_OUT | USB_RT_PORT) << 8):
|
2008-12-13 21:51:58 +00:00
|
|
|
reg = ehci_readl(status_reg);
|
2008-11-28 12:20:46 +00:00
|
|
|
reg &= ~EHCI_PS_CLEAR;
|
|
|
|
switch (le16_to_cpu(req->value)) {
|
2008-12-11 12:43:55 +00:00
|
|
|
case USB_PORT_FEAT_ENABLE:
|
|
|
|
reg |= EHCI_PS_PE;
|
2008-12-13 21:51:58 +00:00
|
|
|
ehci_writel(status_reg, reg);
|
2008-12-11 12:43:55 +00:00
|
|
|
break;
|
2008-11-28 12:20:46 +00:00
|
|
|
case USB_PORT_FEAT_POWER:
|
2012-09-25 22:14:35 +00:00
|
|
|
if (HCS_PPC(ehci_readl(&ctrl->hccr->cr_hcsparams))) {
|
2008-12-13 21:51:58 +00:00
|
|
|
reg |= EHCI_PS_PP;
|
|
|
|
ehci_writel(status_reg, reg);
|
|
|
|
}
|
2008-11-28 12:20:46 +00:00
|
|
|
break;
|
|
|
|
case USB_PORT_FEAT_RESET:
|
2008-12-13 21:51:58 +00:00
|
|
|
if ((reg & (EHCI_PS_PE | EHCI_PS_CS)) == EHCI_PS_CS &&
|
|
|
|
!ehci_is_TDI() &&
|
|
|
|
EHCI_PS_IS_LOWSPEED(reg)) {
|
2008-11-28 12:20:46 +00:00
|
|
|
/* Low speed device, give up ownership. */
|
2008-12-13 21:51:58 +00:00
|
|
|
debug("port %d low speed --> companion\n",
|
2013-02-28 18:08:40 +00:00
|
|
|
port - 1);
|
2008-11-28 12:20:46 +00:00
|
|
|
reg |= EHCI_PS_PO;
|
2008-12-13 21:51:58 +00:00
|
|
|
ehci_writel(status_reg, reg);
|
2015-05-10 12:10:16 +00:00
|
|
|
return -ENXIO;
|
2008-12-13 21:51:58 +00:00
|
|
|
} else {
|
2010-02-27 18:33:21 +00:00
|
|
|
int ret;
|
|
|
|
|
2018-10-04 07:03:53 +00:00
|
|
|
/* Disable chirp for HS erratum */
|
|
|
|
if (ctrl->has_fsl_erratum_a005275)
|
|
|
|
reg |= PORTSC_FSL_PFSC;
|
|
|
|
|
2008-12-13 21:51:58 +00:00
|
|
|
reg |= EHCI_PS_PR;
|
|
|
|
reg &= ~EHCI_PS_PE;
|
|
|
|
ehci_writel(status_reg, reg);
|
|
|
|
/*
|
|
|
|
* caller must wait, then call GetPortStatus
|
|
|
|
* usb 2.0 specification say 50 ms resets on
|
|
|
|
* root
|
|
|
|
*/
|
2015-03-25 18:22:27 +00:00
|
|
|
ctrl->ops.powerup_fixup(ctrl, status_reg, ®);
|
2011-07-11 00:37:01 +00:00
|
|
|
|
2010-01-06 21:34:04 +00:00
|
|
|
ehci_writel(status_reg, reg & ~EHCI_PS_PR);
|
2010-02-27 18:33:21 +00:00
|
|
|
/*
|
|
|
|
* A host controller must terminate the reset
|
|
|
|
* and stabilize the state of the port within
|
|
|
|
* 2 milliseconds
|
|
|
|
*/
|
|
|
|
ret = handshake(status_reg, EHCI_PS_PR, 0,
|
|
|
|
2 * 1000);
|
2015-05-10 12:10:13 +00:00
|
|
|
if (!ret) {
|
|
|
|
reg = ehci_readl(status_reg);
|
|
|
|
if ((reg & (EHCI_PS_PE | EHCI_PS_CS))
|
|
|
|
== EHCI_PS_CS && !ehci_is_TDI()) {
|
|
|
|
debug("port %d full speed --> companion\n", port - 1);
|
|
|
|
reg &= ~EHCI_PS_CLEAR;
|
|
|
|
reg |= EHCI_PS_PO;
|
|
|
|
ehci_writel(status_reg, reg);
|
2015-05-10 12:10:16 +00:00
|
|
|
return -ENXIO;
|
2015-05-10 12:10:13 +00:00
|
|
|
} else {
|
|
|
|
ctrl->portreset |= 1 << port;
|
|
|
|
}
|
|
|
|
} else {
|
2010-02-27 18:33:21 +00:00
|
|
|
printf("port(%d) reset error\n",
|
2013-02-28 18:08:40 +00:00
|
|
|
port - 1);
|
2015-05-10 12:10:13 +00:00
|
|
|
}
|
2008-11-28 12:20:46 +00:00
|
|
|
}
|
|
|
|
break;
|
2013-02-28 18:08:40 +00:00
|
|
|
case USB_PORT_FEAT_TEST:
|
2013-09-24 17:53:07 +00:00
|
|
|
ehci_shutdown(ctrl);
|
2013-02-28 18:08:40 +00:00
|
|
|
reg &= ~(0xf << 16);
|
|
|
|
reg |= ((le16_to_cpu(req->index) >> 8) & 0xf) << 16;
|
|
|
|
ehci_writel(status_reg, reg);
|
|
|
|
break;
|
2008-11-28 12:20:46 +00:00
|
|
|
default:
|
2008-12-10 16:55:19 +00:00
|
|
|
debug("unknown feature %x\n", le16_to_cpu(req->value));
|
2008-11-28 12:20:46 +00:00
|
|
|
goto unknown;
|
|
|
|
}
|
2008-12-13 21:51:58 +00:00
|
|
|
/* unblock posted writes */
|
2012-09-25 22:14:35 +00:00
|
|
|
(void) ehci_readl(&ctrl->hcor->or_usbcmd);
|
2008-11-28 12:20:46 +00:00
|
|
|
break;
|
2008-12-10 16:55:19 +00:00
|
|
|
case USB_REQ_CLEAR_FEATURE | ((USB_DIR_OUT | USB_RT_PORT) << 8):
|
2008-12-13 21:51:58 +00:00
|
|
|
reg = ehci_readl(status_reg);
|
2013-05-11 02:49:00 +00:00
|
|
|
reg &= ~EHCI_PS_CLEAR;
|
2008-11-28 12:20:46 +00:00
|
|
|
switch (le16_to_cpu(req->value)) {
|
|
|
|
case USB_PORT_FEAT_ENABLE:
|
|
|
|
reg &= ~EHCI_PS_PE;
|
|
|
|
break;
|
2008-12-13 21:51:58 +00:00
|
|
|
case USB_PORT_FEAT_C_ENABLE:
|
2013-05-11 02:49:00 +00:00
|
|
|
reg |= EHCI_PS_PE;
|
2008-12-13 21:51:58 +00:00
|
|
|
break;
|
|
|
|
case USB_PORT_FEAT_POWER:
|
2012-09-25 22:14:35 +00:00
|
|
|
if (HCS_PPC(ehci_readl(&ctrl->hccr->cr_hcsparams)))
|
2013-05-11 02:49:00 +00:00
|
|
|
reg &= ~EHCI_PS_PP;
|
|
|
|
break;
|
2008-11-28 12:20:46 +00:00
|
|
|
case USB_PORT_FEAT_C_CONNECTION:
|
2013-05-11 02:49:00 +00:00
|
|
|
reg |= EHCI_PS_CSC;
|
2008-11-28 12:20:46 +00:00
|
|
|
break;
|
2008-12-11 12:43:55 +00:00
|
|
|
case USB_PORT_FEAT_OVER_CURRENT:
|
2013-05-11 02:49:00 +00:00
|
|
|
reg |= EHCI_PS_OCC;
|
2008-12-11 12:43:55 +00:00
|
|
|
break;
|
2008-11-28 12:20:46 +00:00
|
|
|
case USB_PORT_FEAT_C_RESET:
|
2013-02-28 18:08:40 +00:00
|
|
|
ctrl->portreset &= ~(1 << port);
|
2008-11-28 12:20:46 +00:00
|
|
|
break;
|
|
|
|
default:
|
2008-12-10 16:55:19 +00:00
|
|
|
debug("unknown feature %x\n", le16_to_cpu(req->value));
|
2008-11-28 12:20:46 +00:00
|
|
|
goto unknown;
|
|
|
|
}
|
2008-12-13 21:51:58 +00:00
|
|
|
ehci_writel(status_reg, reg);
|
|
|
|
/* unblock posted write */
|
2012-09-25 22:14:35 +00:00
|
|
|
(void) ehci_readl(&ctrl->hcor->or_usbcmd);
|
2008-11-28 12:20:46 +00:00
|
|
|
break;
|
|
|
|
default:
|
2008-12-10 16:55:19 +00:00
|
|
|
debug("Unknown request\n");
|
2008-11-28 12:20:46 +00:00
|
|
|
goto unknown;
|
|
|
|
}
|
|
|
|
|
usb: replace wait_ms() with mdelay()
Common code has a mdelay() func, so use that instead of the usb-specific
wait_ms() func. This also fixes the build errors:
ohci-hcd.c: In function 'submit_common_msg':
/usr/local/src/u-boot/blackfin/include/usb.h:202:44: sorry, unimplemented: inlining failed in call to 'wait_ms': function body not available
ohci-hcd.c:1519:9: sorry, unimplemented: called from here
/usr/local/src/u-boot/blackfin/include/usb.h:202:44: sorry, unimplemented: inlining failed in call to 'wait_ms': function body not available
ohci-hcd.c:1816:10: sorry, unimplemented: called from here
/usr/local/src/u-boot/blackfin/include/usb.h:202:44: sorry, unimplemented: inlining failed in call to 'wait_ms': function body not available
ohci-hcd.c:1827:10: sorry, unimplemented: called from here
/usr/local/src/u-boot/blackfin/include/usb.h:202:44: sorry, unimplemented: inlining failed in call to 'wait_ms': function body not available
ohci-hcd.c:1844:10: sorry, unimplemented: called from here
/usr/local/src/u-boot/blackfin/include/usb.h:202:44: sorry, unimplemented: inlining failed in call to 'wait_ms': function body not available
ohci-hcd.c:1563:11: sorry, unimplemented: called from here
/usr/local/src/u-boot/blackfin/include/usb.h:202:44: sorry, unimplemented: inlining failed in call to 'wait_ms': function body not available
ohci-hcd.c:1583:9: sorry, unimplemented: called from here
make[1]: *** [ohci-hcd.o] Error 1
Signed-off-by: Mike Frysinger <vapier@gentoo.org>
Acked-by: Marek Vasut <marex@denx.de>
2012-03-05 13:47:00 +00:00
|
|
|
mdelay(1);
|
linux/kernel.h: sync min, max, min3, max3 macros with Linux
U-Boot has never cared about the type when we get max/min of two
values, but Linux Kernel does. This commit gets min, max, min3, max3
macros synced with the kernel introducing type checks.
Many of references of those macros must be fixed to suppress warnings.
We have two options:
- Use min, max, min3, max3 only when the arguments have the same type
(or add casts to the arguments)
- Use min_t/max_t instead with the appropriate type for the first
argument
Signed-off-by: Masahiro Yamada <yamada.m@jp.panasonic.com>
Acked-by: Pavel Machek <pavel@denx.de>
Acked-by: Lukasz Majewski <l.majewski@samsung.com>
Tested-by: Lukasz Majewski <l.majewski@samsung.com>
[trini: Fixup arch/blackfin/lib/string.c]
Signed-off-by: Tom Rini <trini@ti.com>
2014-11-06 18:03:31 +00:00
|
|
|
len = min3(srclen, (int)le16_to_cpu(req->length), length);
|
2008-11-28 12:20:46 +00:00
|
|
|
if (srcptr != NULL && len > 0)
|
|
|
|
memcpy(buffer, srcptr, len);
|
2008-12-10 16:55:19 +00:00
|
|
|
else
|
|
|
|
debug("Len is 0\n");
|
|
|
|
|
2008-11-28 12:20:46 +00:00
|
|
|
dev->act_len = len;
|
|
|
|
dev->status = 0;
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
unknown:
|
2008-12-10 16:55:19 +00:00
|
|
|
debug("requesttype=%x, request=%x, value=%x, index=%x, length=%x\n",
|
2008-11-28 12:20:46 +00:00
|
|
|
req->requesttype, req->request, le16_to_cpu(req->value),
|
|
|
|
le16_to_cpu(req->index), le16_to_cpu(req->length));
|
|
|
|
|
|
|
|
dev->act_len = 0;
|
|
|
|
dev->status = USB_ST_STALLED;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2017-06-22 07:35:14 +00:00
|
|
|
static const struct ehci_ops default_ehci_ops = {
|
2015-03-25 18:22:27 +00:00
|
|
|
.set_usb_mode = ehci_set_usbmode,
|
|
|
|
.get_port_speed = ehci_get_port_speed,
|
|
|
|
.powerup_fixup = ehci_powerup_fixup,
|
|
|
|
.get_portsc_register = ehci_get_portsc_register,
|
|
|
|
};
|
|
|
|
|
|
|
|
static void ehci_setup_ops(struct ehci_ctrl *ctrl, const struct ehci_ops *ops)
|
|
|
|
{
|
|
|
|
if (!ops) {
|
|
|
|
ctrl->ops = default_ehci_ops;
|
|
|
|
} else {
|
|
|
|
ctrl->ops = *ops;
|
|
|
|
if (!ctrl->ops.set_usb_mode)
|
|
|
|
ctrl->ops.set_usb_mode = ehci_set_usbmode;
|
|
|
|
if (!ctrl->ops.get_port_speed)
|
|
|
|
ctrl->ops.get_port_speed = ehci_get_port_speed;
|
|
|
|
if (!ctrl->ops.powerup_fixup)
|
|
|
|
ctrl->ops.powerup_fixup = ehci_powerup_fixup;
|
|
|
|
if (!ctrl->ops.get_portsc_register)
|
|
|
|
ctrl->ops.get_portsc_register =
|
|
|
|
ehci_get_portsc_register;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-21 07:43:56 +00:00
|
|
|
#if !CONFIG_IS_ENABLED(DM_USB)
|
2015-03-25 18:22:27 +00:00
|
|
|
void ehci_set_controller_priv(int index, void *priv, const struct ehci_ops *ops)
|
2015-03-25 18:22:19 +00:00
|
|
|
{
|
2015-03-25 18:22:27 +00:00
|
|
|
struct ehci_ctrl *ctrl = &ehcic[index];
|
|
|
|
|
|
|
|
ctrl->priv = priv;
|
|
|
|
ehci_setup_ops(ctrl, ops);
|
2015-03-25 18:22:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void *ehci_get_controller_priv(int index)
|
|
|
|
{
|
|
|
|
return ehcic[index].priv;
|
|
|
|
}
|
2015-03-25 18:22:29 +00:00
|
|
|
#endif
|
2015-03-25 18:22:19 +00:00
|
|
|
|
2015-03-25 18:22:26 +00:00
|
|
|
static int ehci_common_init(struct ehci_ctrl *ctrl, uint tweaks)
|
2008-11-28 12:20:46 +00:00
|
|
|
{
|
2012-09-25 22:14:35 +00:00
|
|
|
struct QH *qh_list;
|
2013-03-06 14:08:31 +00:00
|
|
|
struct QH *periodic;
|
2015-03-25 18:22:26 +00:00
|
|
|
uint32_t reg;
|
|
|
|
uint32_t cmd;
|
2013-03-06 14:08:31 +00:00
|
|
|
int i;
|
2008-12-11 12:43:55 +00:00
|
|
|
|
2012-12-13 01:55:22 +00:00
|
|
|
/* Set the high address word (aka segment) for 64-bit controller */
|
2015-03-25 18:22:26 +00:00
|
|
|
if (ehci_readl(&ctrl->hccr->cr_hccparams) & 1)
|
|
|
|
ehci_writel(&ctrl->hcor->or_ctrldssegment, 0);
|
2009-01-21 16:12:10 +00:00
|
|
|
|
2015-03-25 18:22:26 +00:00
|
|
|
qh_list = &ctrl->qh_list;
|
2012-09-25 22:14:35 +00:00
|
|
|
|
2008-11-28 12:20:46 +00:00
|
|
|
/* Set head of reclaim list */
|
2012-07-15 22:14:24 +00:00
|
|
|
memset(qh_list, 0, sizeof(*qh_list));
|
2016-01-23 20:04:46 +00:00
|
|
|
qh_list->qh_link = cpu_to_hc32(virt_to_phys(qh_list) | QH_LINK_TYPE_QH);
|
2012-08-10 16:22:11 +00:00
|
|
|
qh_list->qh_endpt1 = cpu_to_hc32(QH_ENDPT1_H(1) |
|
|
|
|
QH_ENDPT1_EPS(USB_SPEED_HIGH));
|
2012-07-15 22:14:24 +00:00
|
|
|
qh_list->qh_overlay.qt_next = cpu_to_hc32(QT_NEXT_TERMINATE);
|
|
|
|
qh_list->qh_overlay.qt_altnext = cpu_to_hc32(QT_NEXT_TERMINATE);
|
2012-08-10 16:22:11 +00:00
|
|
|
qh_list->qh_overlay.qt_token =
|
|
|
|
cpu_to_hc32(QT_TOKEN_STATUS(QT_TOKEN_STATUS_HALTED));
|
2008-11-28 12:20:46 +00:00
|
|
|
|
2015-03-17 20:46:37 +00:00
|
|
|
flush_dcache_range((unsigned long)qh_list,
|
2013-05-24 21:03:17 +00:00
|
|
|
ALIGN_END_ADDR(struct QH, qh_list, 1));
|
|
|
|
|
2013-03-06 14:08:31 +00:00
|
|
|
/* Set async. queue head pointer. */
|
2016-01-23 20:04:46 +00:00
|
|
|
ehci_writel(&ctrl->hcor->or_asynclistaddr, virt_to_phys(qh_list));
|
2013-03-06 14:08:31 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Set up periodic list
|
|
|
|
* Step 1: Parent QH for all periodic transfers.
|
|
|
|
*/
|
2015-03-25 18:22:26 +00:00
|
|
|
ctrl->periodic_schedules = 0;
|
|
|
|
periodic = &ctrl->periodic_queue;
|
2013-03-06 14:08:31 +00:00
|
|
|
memset(periodic, 0, sizeof(*periodic));
|
|
|
|
periodic->qh_link = cpu_to_hc32(QH_LINK_TERMINATE);
|
|
|
|
periodic->qh_overlay.qt_next = cpu_to_hc32(QT_NEXT_TERMINATE);
|
|
|
|
periodic->qh_overlay.qt_altnext = cpu_to_hc32(QT_NEXT_TERMINATE);
|
|
|
|
|
2015-03-17 20:46:37 +00:00
|
|
|
flush_dcache_range((unsigned long)periodic,
|
2013-05-24 21:03:17 +00:00
|
|
|
ALIGN_END_ADDR(struct QH, periodic, 1));
|
|
|
|
|
2013-03-06 14:08:31 +00:00
|
|
|
/*
|
|
|
|
* Step 2: Setup frame-list: Every microframe, USB tries the same list.
|
|
|
|
* In particular, device specifications on polling frequency
|
|
|
|
* are disregarded. Keyboards seem to send NAK/NYet reliably
|
|
|
|
* when polled with an empty buffer.
|
|
|
|
*
|
|
|
|
* Split Transactions will be spread across microframes using
|
|
|
|
* S-mask and C-mask.
|
|
|
|
*/
|
2015-03-25 18:22:26 +00:00
|
|
|
if (ctrl->periodic_list == NULL)
|
|
|
|
ctrl->periodic_list = memalign(4096, 1024 * 4);
|
2013-07-29 10:27:40 +00:00
|
|
|
|
2015-03-25 18:22:26 +00:00
|
|
|
if (!ctrl->periodic_list)
|
2013-03-06 14:08:31 +00:00
|
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < 1024; i++) {
|
2015-03-25 18:22:26 +00:00
|
|
|
ctrl->periodic_list[i] = cpu_to_hc32((unsigned long)periodic
|
2014-04-10 12:29:45 +00:00
|
|
|
| QH_LINK_TYPE_QH);
|
2013-03-06 14:08:31 +00:00
|
|
|
}
|
|
|
|
|
2015-03-25 18:22:26 +00:00
|
|
|
flush_dcache_range((unsigned long)ctrl->periodic_list,
|
|
|
|
ALIGN_END_ADDR(uint32_t, ctrl->periodic_list,
|
2013-05-24 21:03:17 +00:00
|
|
|
1024));
|
|
|
|
|
2013-03-06 14:08:31 +00:00
|
|
|
/* Set periodic list base address */
|
2015-03-25 18:22:26 +00:00
|
|
|
ehci_writel(&ctrl->hcor->or_periodiclistbase,
|
|
|
|
(unsigned long)ctrl->periodic_list);
|
2013-03-06 14:08:31 +00:00
|
|
|
|
2015-03-25 18:22:26 +00:00
|
|
|
reg = ehci_readl(&ctrl->hccr->cr_hcsparams);
|
2008-12-11 12:43:55 +00:00
|
|
|
descriptor.hub.bNbrPorts = HCS_N_PORTS(reg);
|
2012-09-27 22:26:19 +00:00
|
|
|
debug("Register %x NbrPorts %d\n", reg, descriptor.hub.bNbrPorts);
|
2008-12-13 21:51:58 +00:00
|
|
|
/* Port Indicators */
|
|
|
|
if (HCS_INDICATOR(reg))
|
2012-09-06 06:00:13 +00:00
|
|
|
put_unaligned(get_unaligned(&descriptor.hub.wHubCharacteristics)
|
|
|
|
| 0x80, &descriptor.hub.wHubCharacteristics);
|
2008-12-13 21:51:58 +00:00
|
|
|
/* Port Power Control */
|
|
|
|
if (HCS_PPC(reg))
|
2012-09-06 06:00:13 +00:00
|
|
|
put_unaligned(get_unaligned(&descriptor.hub.wHubCharacteristics)
|
|
|
|
| 0x01, &descriptor.hub.wHubCharacteristics);
|
2008-11-28 12:20:46 +00:00
|
|
|
|
|
|
|
/* Start the host controller. */
|
2015-03-25 18:22:26 +00:00
|
|
|
cmd = ehci_readl(&ctrl->hcor->or_usbcmd);
|
2009-02-11 23:08:39 +00:00
|
|
|
/*
|
|
|
|
* Philips, Intel, and maybe others need CMD_RUN before the
|
|
|
|
* root hub will detect new devices (why?); NEC doesn't
|
|
|
|
*/
|
2008-12-11 12:43:55 +00:00
|
|
|
cmd &= ~(CMD_LRESET|CMD_IAAD|CMD_PSE|CMD_ASE|CMD_RESET);
|
|
|
|
cmd |= CMD_RUN;
|
2015-03-25 18:22:26 +00:00
|
|
|
ehci_writel(&ctrl->hcor->or_usbcmd, cmd);
|
2008-12-11 12:43:55 +00:00
|
|
|
|
2015-03-25 18:22:26 +00:00
|
|
|
if (!(tweaks & EHCI_TWEAK_NO_INIT_CF)) {
|
|
|
|
/* take control over the ports */
|
|
|
|
cmd = ehci_readl(&ctrl->hcor->or_configflag);
|
|
|
|
cmd |= FLAG_CF;
|
|
|
|
ehci_writel(&ctrl->hcor->or_configflag, cmd);
|
|
|
|
}
|
2013-05-15 07:29:23 +00:00
|
|
|
|
2008-12-13 21:51:58 +00:00
|
|
|
/* unblock posted write */
|
2015-03-25 18:22:26 +00:00
|
|
|
cmd = ehci_readl(&ctrl->hcor->or_usbcmd);
|
usb: replace wait_ms() with mdelay()
Common code has a mdelay() func, so use that instead of the usb-specific
wait_ms() func. This also fixes the build errors:
ohci-hcd.c: In function 'submit_common_msg':
/usr/local/src/u-boot/blackfin/include/usb.h:202:44: sorry, unimplemented: inlining failed in call to 'wait_ms': function body not available
ohci-hcd.c:1519:9: sorry, unimplemented: called from here
/usr/local/src/u-boot/blackfin/include/usb.h:202:44: sorry, unimplemented: inlining failed in call to 'wait_ms': function body not available
ohci-hcd.c:1816:10: sorry, unimplemented: called from here
/usr/local/src/u-boot/blackfin/include/usb.h:202:44: sorry, unimplemented: inlining failed in call to 'wait_ms': function body not available
ohci-hcd.c:1827:10: sorry, unimplemented: called from here
/usr/local/src/u-boot/blackfin/include/usb.h:202:44: sorry, unimplemented: inlining failed in call to 'wait_ms': function body not available
ohci-hcd.c:1844:10: sorry, unimplemented: called from here
/usr/local/src/u-boot/blackfin/include/usb.h:202:44: sorry, unimplemented: inlining failed in call to 'wait_ms': function body not available
ohci-hcd.c:1563:11: sorry, unimplemented: called from here
/usr/local/src/u-boot/blackfin/include/usb.h:202:44: sorry, unimplemented: inlining failed in call to 'wait_ms': function body not available
ohci-hcd.c:1583:9: sorry, unimplemented: called from here
make[1]: *** [ohci-hcd.o] Error 1
Signed-off-by: Mike Frysinger <vapier@gentoo.org>
Acked-by: Marek Vasut <marex@denx.de>
2012-03-05 13:47:00 +00:00
|
|
|
mdelay(5);
|
2015-03-25 18:22:26 +00:00
|
|
|
reg = HC_VERSION(ehci_readl(&ctrl->hccr->cr_capbase));
|
2008-12-13 21:51:58 +00:00
|
|
|
printf("USB EHCI %x.%02x\n", reg >> 8, reg & 0xff);
|
2008-11-28 12:20:46 +00:00
|
|
|
|
2015-03-25 18:22:26 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-11-21 07:43:56 +00:00
|
|
|
#if !CONFIG_IS_ENABLED(DM_USB)
|
2015-03-25 18:22:26 +00:00
|
|
|
int usb_lowlevel_stop(int index)
|
|
|
|
{
|
|
|
|
ehci_shutdown(&ehcic[index]);
|
|
|
|
return ehci_hcd_stop(index);
|
|
|
|
}
|
|
|
|
|
|
|
|
int usb_lowlevel_init(int index, enum usb_init_type init, void **controller)
|
|
|
|
{
|
|
|
|
struct ehci_ctrl *ctrl = &ehcic[index];
|
|
|
|
uint tweaks = 0;
|
|
|
|
int rc;
|
|
|
|
|
2015-03-25 18:22:27 +00:00
|
|
|
/**
|
|
|
|
* Set ops to default_ehci_ops, ehci_hcd_init should call
|
|
|
|
* ehci_set_controller_priv to change any of these function pointers.
|
|
|
|
*/
|
|
|
|
ctrl->ops = default_ehci_ops;
|
|
|
|
|
2015-03-25 18:22:26 +00:00
|
|
|
rc = ehci_hcd_init(index, init, &ctrl->hccr, &ctrl->hcor);
|
|
|
|
if (rc)
|
|
|
|
return rc;
|
2017-11-20 18:33:39 +00:00
|
|
|
if (!ctrl->hccr || !ctrl->hcor)
|
|
|
|
return -1;
|
2015-03-25 18:22:26 +00:00
|
|
|
if (init == USB_INIT_DEVICE)
|
|
|
|
goto done;
|
|
|
|
|
|
|
|
/* EHCI spec section 4.1 */
|
2015-03-25 18:22:28 +00:00
|
|
|
if (ehci_reset(ctrl))
|
2015-03-25 18:22:26 +00:00
|
|
|
return -1;
|
|
|
|
|
|
|
|
#if defined(CONFIG_EHCI_HCD_INIT_AFTER_RESET)
|
|
|
|
rc = ehci_hcd_init(index, init, &ctrl->hccr, &ctrl->hcor);
|
|
|
|
if (rc)
|
|
|
|
return rc;
|
|
|
|
#endif
|
|
|
|
#ifdef CONFIG_USB_EHCI_FARADAY
|
|
|
|
tweaks |= EHCI_TWEAK_NO_INIT_CF;
|
|
|
|
#endif
|
|
|
|
rc = ehci_common_init(ctrl, tweaks);
|
|
|
|
if (rc)
|
|
|
|
return rc;
|
|
|
|
|
|
|
|
ctrl->rootdev = 0;
|
2013-10-10 22:27:57 +00:00
|
|
|
done:
|
2012-09-25 22:14:35 +00:00
|
|
|
*controller = &ehcic[index];
|
2008-11-28 12:20:46 +00:00
|
|
|
return 0;
|
|
|
|
}
|
2015-03-25 18:22:29 +00:00
|
|
|
#endif
|
2008-11-28 12:20:46 +00:00
|
|
|
|
2015-03-25 18:22:25 +00:00
|
|
|
static int _ehci_submit_bulk_msg(struct usb_device *dev, unsigned long pipe,
|
|
|
|
void *buffer, int length)
|
2008-11-28 12:20:46 +00:00
|
|
|
{
|
|
|
|
|
|
|
|
if (usb_pipetype(pipe) != PIPE_BULK) {
|
|
|
|
debug("non-bulk pipe (type=%lu)", usb_pipetype(pipe));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return ehci_submit_async(dev, pipe, buffer, length, NULL);
|
|
|
|
}
|
|
|
|
|
2015-03-25 18:22:25 +00:00
|
|
|
static int _ehci_submit_control_msg(struct usb_device *dev, unsigned long pipe,
|
|
|
|
void *buffer, int length,
|
|
|
|
struct devrequest *setup)
|
2008-11-28 12:20:46 +00:00
|
|
|
{
|
2015-03-25 18:22:25 +00:00
|
|
|
struct ehci_ctrl *ctrl = ehci_get_ctrl(dev);
|
2008-11-28 12:20:46 +00:00
|
|
|
|
|
|
|
if (usb_pipetype(pipe) != PIPE_CONTROL) {
|
|
|
|
debug("non-control pipe (type=%lu)", usb_pipetype(pipe));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2012-09-25 22:14:35 +00:00
|
|
|
if (usb_pipedevice(pipe) == ctrl->rootdev) {
|
|
|
|
if (!ctrl->rootdev)
|
2008-11-28 12:20:46 +00:00
|
|
|
dev->speed = USB_SPEED_HIGH;
|
|
|
|
return ehci_submit_root(dev, pipe, buffer, length, setup);
|
|
|
|
}
|
|
|
|
return ehci_submit_async(dev, pipe, buffer, length, setup);
|
|
|
|
}
|
|
|
|
|
2013-03-06 14:08:31 +00:00
|
|
|
struct int_queue {
|
2014-09-24 12:06:05 +00:00
|
|
|
int elementsize;
|
2015-06-18 20:34:33 +00:00
|
|
|
unsigned long pipe;
|
2013-03-06 14:08:31 +00:00
|
|
|
struct QH *first;
|
|
|
|
struct QH *current;
|
|
|
|
struct QH *last;
|
|
|
|
struct qTD *tds;
|
|
|
|
};
|
|
|
|
|
2015-03-17 20:46:37 +00:00
|
|
|
#define NEXT_QH(qh) (struct QH *)((unsigned long)hc32_to_cpu((qh)->qh_link) & ~0x1f)
|
2013-03-06 14:08:31 +00:00
|
|
|
|
|
|
|
static int
|
|
|
|
enable_periodic(struct ehci_ctrl *ctrl)
|
|
|
|
{
|
|
|
|
uint32_t cmd;
|
|
|
|
struct ehci_hcor *hcor = ctrl->hcor;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
cmd = ehci_readl(&hcor->or_usbcmd);
|
|
|
|
cmd |= CMD_PSE;
|
|
|
|
ehci_writel(&hcor->or_usbcmd, cmd);
|
|
|
|
|
|
|
|
ret = handshake((uint32_t *)&hcor->or_usbsts,
|
|
|
|
STS_PSS, STS_PSS, 100 * 1000);
|
|
|
|
if (ret < 0) {
|
|
|
|
printf("EHCI failed: timeout when enabling periodic list\n");
|
|
|
|
return -ETIMEDOUT;
|
|
|
|
}
|
|
|
|
udelay(1000);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
disable_periodic(struct ehci_ctrl *ctrl)
|
|
|
|
{
|
|
|
|
uint32_t cmd;
|
|
|
|
struct ehci_hcor *hcor = ctrl->hcor;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
cmd = ehci_readl(&hcor->or_usbcmd);
|
|
|
|
cmd &= ~CMD_PSE;
|
|
|
|
ehci_writel(&hcor->or_usbcmd, cmd);
|
|
|
|
|
|
|
|
ret = handshake((uint32_t *)&hcor->or_usbsts,
|
|
|
|
STS_PSS, 0, 100 * 1000);
|
|
|
|
if (ret < 0) {
|
|
|
|
printf("EHCI failed: timeout when disabling periodic list\n");
|
|
|
|
return -ETIMEDOUT;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-05-11 18:43:52 +00:00
|
|
|
static struct int_queue *_ehci_create_int_queue(struct usb_device *dev,
|
|
|
|
unsigned long pipe, int queuesize, int elementsize,
|
|
|
|
void *buffer, int interval)
|
2013-03-06 14:08:31 +00:00
|
|
|
{
|
2015-03-25 18:22:25 +00:00
|
|
|
struct ehci_ctrl *ctrl = ehci_get_ctrl(dev);
|
2013-03-06 14:08:31 +00:00
|
|
|
struct int_queue *result = NULL;
|
2015-06-18 20:34:33 +00:00
|
|
|
uint32_t i, toggle;
|
2013-03-06 14:08:31 +00:00
|
|
|
|
2014-09-24 12:06:04 +00:00
|
|
|
/*
|
|
|
|
* Interrupt transfers requiring several transactions are not supported
|
|
|
|
* because bInterval is ignored.
|
|
|
|
*
|
|
|
|
* Also, ehci_submit_async() relies on wMaxPacketSize being a power of 2
|
|
|
|
* <= PKT_ALIGN if several qTDs are required, while the USB
|
|
|
|
* specification does not constrain this for interrupt transfers. That
|
|
|
|
* means that ehci_submit_async() would support interrupt transfers
|
|
|
|
* requiring several transactions only as long as the transfer size does
|
|
|
|
* not require more than a single qTD.
|
|
|
|
*/
|
|
|
|
if (elementsize > usb_maxpacket(dev, pipe)) {
|
|
|
|
printf("%s: xfers requiring several transactions are not supported.\n",
|
|
|
|
__func__);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2013-03-06 14:08:31 +00:00
|
|
|
debug("Enter create_int_queue\n");
|
|
|
|
if (usb_pipetype(pipe) != PIPE_INTERRUPT) {
|
|
|
|
debug("non-interrupt pipe (type=%lu)", usb_pipetype(pipe));
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* limit to 4 full pages worth of data -
|
|
|
|
* we can safely fit them in a single TD,
|
|
|
|
* no matter the alignment
|
|
|
|
*/
|
|
|
|
if (elementsize >= 16384) {
|
|
|
|
debug("too large elements for interrupt transfers\n");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
result = malloc(sizeof(*result));
|
|
|
|
if (!result) {
|
|
|
|
debug("ehci intr queue: out of memory\n");
|
|
|
|
goto fail1;
|
|
|
|
}
|
2014-09-24 12:06:05 +00:00
|
|
|
result->elementsize = elementsize;
|
2015-06-18 20:34:33 +00:00
|
|
|
result->pipe = pipe;
|
2014-02-06 20:13:06 +00:00
|
|
|
result->first = memalign(USB_DMA_MINALIGN,
|
|
|
|
sizeof(struct QH) * queuesize);
|
2013-03-06 14:08:31 +00:00
|
|
|
if (!result->first) {
|
|
|
|
debug("ehci intr queue: out of memory\n");
|
|
|
|
goto fail2;
|
|
|
|
}
|
|
|
|
result->current = result->first;
|
|
|
|
result->last = result->first + queuesize - 1;
|
2014-02-06 20:13:06 +00:00
|
|
|
result->tds = memalign(USB_DMA_MINALIGN,
|
|
|
|
sizeof(struct qTD) * queuesize);
|
2013-03-06 14:08:31 +00:00
|
|
|
if (!result->tds) {
|
|
|
|
debug("ehci intr queue: out of memory\n");
|
|
|
|
goto fail3;
|
|
|
|
}
|
|
|
|
memset(result->first, 0, sizeof(struct QH) * queuesize);
|
|
|
|
memset(result->tds, 0, sizeof(struct qTD) * queuesize);
|
|
|
|
|
2015-06-18 20:34:33 +00:00
|
|
|
toggle = usb_gettoggle(dev, usb_pipeendpoint(pipe), usb_pipeout(pipe));
|
|
|
|
|
2013-03-06 14:08:31 +00:00
|
|
|
for (i = 0; i < queuesize; i++) {
|
|
|
|
struct QH *qh = result->first + i;
|
|
|
|
struct qTD *td = result->tds + i;
|
|
|
|
void **buf = &qh->buffer;
|
|
|
|
|
2015-03-17 20:46:37 +00:00
|
|
|
qh->qh_link = cpu_to_hc32((unsigned long)(qh+1) | QH_LINK_TYPE_QH);
|
2013-03-06 14:08:31 +00:00
|
|
|
if (i == queuesize - 1)
|
2014-04-10 12:29:45 +00:00
|
|
|
qh->qh_link = cpu_to_hc32(QH_LINK_TERMINATE);
|
2013-03-06 14:08:31 +00:00
|
|
|
|
2015-03-17 20:46:37 +00:00
|
|
|
qh->qh_overlay.qt_next = cpu_to_hc32((unsigned long)td);
|
2014-04-10 12:29:45 +00:00
|
|
|
qh->qh_overlay.qt_altnext = cpu_to_hc32(QT_NEXT_TERMINATE);
|
|
|
|
qh->qh_endpt1 =
|
|
|
|
cpu_to_hc32((0 << 28) | /* No NAK reload (ehci 4.9) */
|
2013-03-06 14:08:31 +00:00
|
|
|
(usb_maxpacket(dev, pipe) << 16) | /* MPS */
|
|
|
|
(1 << 14) |
|
|
|
|
QH_ENDPT1_EPS(ehci_encode_speed(dev->speed)) |
|
|
|
|
(usb_pipeendpoint(pipe) << 8) | /* Endpoint Number */
|
2014-04-10 12:29:45 +00:00
|
|
|
(usb_pipedevice(pipe) << 0));
|
|
|
|
qh->qh_endpt2 = cpu_to_hc32((1 << 30) | /* 1 Tx per mframe */
|
|
|
|
(1 << 0)); /* S-mask: microframe 0 */
|
2013-03-06 14:08:31 +00:00
|
|
|
if (dev->speed == USB_SPEED_LOW ||
|
|
|
|
dev->speed == USB_SPEED_FULL) {
|
2014-09-20 14:51:22 +00:00
|
|
|
/* C-mask: microframes 2-4 */
|
|
|
|
qh->qh_endpt2 |= cpu_to_hc32((0x1c << 8));
|
2013-03-06 14:08:31 +00:00
|
|
|
}
|
2014-09-20 14:51:22 +00:00
|
|
|
ehci_update_endpt2_dev_n_port(dev, qh);
|
2013-03-06 14:08:31 +00:00
|
|
|
|
2014-04-10 12:29:45 +00:00
|
|
|
td->qt_next = cpu_to_hc32(QT_NEXT_TERMINATE);
|
|
|
|
td->qt_altnext = cpu_to_hc32(QT_NEXT_TERMINATE);
|
2013-03-06 14:08:31 +00:00
|
|
|
debug("communication direction is '%s'\n",
|
|
|
|
usb_pipein(pipe) ? "in" : "out");
|
2015-06-18 20:34:33 +00:00
|
|
|
td->qt_token = cpu_to_hc32(
|
|
|
|
QT_TOKEN_DT(toggle) |
|
|
|
|
(elementsize << 16) |
|
2013-03-06 14:08:31 +00:00
|
|
|
((usb_pipein(pipe) ? 1 : 0) << 8) | /* IN/OUT token */
|
2014-04-10 12:29:45 +00:00
|
|
|
0x80); /* active */
|
|
|
|
td->qt_buffer[0] =
|
2015-03-17 20:46:37 +00:00
|
|
|
cpu_to_hc32((unsigned long)buffer + i * elementsize);
|
2014-04-10 12:29:45 +00:00
|
|
|
td->qt_buffer[1] =
|
|
|
|
cpu_to_hc32((td->qt_buffer[0] + 0x1000) & ~0xfff);
|
|
|
|
td->qt_buffer[2] =
|
|
|
|
cpu_to_hc32((td->qt_buffer[0] + 0x2000) & ~0xfff);
|
|
|
|
td->qt_buffer[3] =
|
|
|
|
cpu_to_hc32((td->qt_buffer[0] + 0x3000) & ~0xfff);
|
|
|
|
td->qt_buffer[4] =
|
|
|
|
cpu_to_hc32((td->qt_buffer[0] + 0x4000) & ~0xfff);
|
2013-03-06 14:08:31 +00:00
|
|
|
|
|
|
|
*buf = buffer + i * elementsize;
|
2015-06-18 20:34:33 +00:00
|
|
|
toggle ^= 1;
|
2013-03-06 14:08:31 +00:00
|
|
|
}
|
|
|
|
|
2015-03-17 20:46:37 +00:00
|
|
|
flush_dcache_range((unsigned long)buffer,
|
2013-05-24 21:03:17 +00:00
|
|
|
ALIGN_END_ADDR(char, buffer,
|
|
|
|
queuesize * elementsize));
|
2015-03-17 20:46:37 +00:00
|
|
|
flush_dcache_range((unsigned long)result->first,
|
2013-05-24 21:03:17 +00:00
|
|
|
ALIGN_END_ADDR(struct QH, result->first,
|
|
|
|
queuesize));
|
2015-03-17 20:46:37 +00:00
|
|
|
flush_dcache_range((unsigned long)result->tds,
|
2013-05-24 21:03:17 +00:00
|
|
|
ALIGN_END_ADDR(struct qTD, result->tds,
|
|
|
|
queuesize));
|
|
|
|
|
2014-09-24 12:06:03 +00:00
|
|
|
if (ctrl->periodic_schedules > 0) {
|
|
|
|
if (disable_periodic(ctrl) < 0) {
|
|
|
|
debug("FATAL: periodic should never fail, but did");
|
|
|
|
goto fail3;
|
|
|
|
}
|
2013-03-06 14:08:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* hook up to periodic list */
|
|
|
|
struct QH *list = &ctrl->periodic_queue;
|
|
|
|
result->last->qh_link = list->qh_link;
|
2015-03-17 20:46:37 +00:00
|
|
|
list->qh_link = cpu_to_hc32((unsigned long)result->first | QH_LINK_TYPE_QH);
|
2013-03-06 14:08:31 +00:00
|
|
|
|
2015-03-17 20:46:37 +00:00
|
|
|
flush_dcache_range((unsigned long)result->last,
|
2013-05-24 21:03:17 +00:00
|
|
|
ALIGN_END_ADDR(struct QH, result->last, 1));
|
2015-03-17 20:46:37 +00:00
|
|
|
flush_dcache_range((unsigned long)list,
|
2013-05-24 21:03:17 +00:00
|
|
|
ALIGN_END_ADDR(struct QH, list, 1));
|
|
|
|
|
2013-03-06 14:08:31 +00:00
|
|
|
if (enable_periodic(ctrl) < 0) {
|
|
|
|
debug("FATAL: periodic should never fail, but did");
|
|
|
|
goto fail3;
|
|
|
|
}
|
2014-09-20 14:51:25 +00:00
|
|
|
ctrl->periodic_schedules++;
|
2013-03-06 14:08:31 +00:00
|
|
|
|
|
|
|
debug("Exit create_int_queue\n");
|
|
|
|
return result;
|
|
|
|
fail3:
|
2020-04-19 10:02:28 +00:00
|
|
|
free(result->tds);
|
2013-03-06 14:08:31 +00:00
|
|
|
fail2:
|
2020-04-19 10:02:28 +00:00
|
|
|
free(result->first);
|
|
|
|
free(result);
|
2013-03-06 14:08:31 +00:00
|
|
|
fail1:
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2015-05-11 18:43:52 +00:00
|
|
|
static void *_ehci_poll_int_queue(struct usb_device *dev,
|
|
|
|
struct int_queue *queue)
|
2013-03-06 14:08:31 +00:00
|
|
|
{
|
|
|
|
struct QH *cur = queue->current;
|
2014-09-20 14:51:24 +00:00
|
|
|
struct qTD *cur_td;
|
2015-06-18 20:34:33 +00:00
|
|
|
uint32_t token, toggle;
|
|
|
|
unsigned long pipe = queue->pipe;
|
2013-03-06 14:08:31 +00:00
|
|
|
|
|
|
|
/* depleted queue */
|
|
|
|
if (cur == NULL) {
|
|
|
|
debug("Exit poll_int_queue with completed queue\n");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
/* still active */
|
2014-09-20 14:51:24 +00:00
|
|
|
cur_td = &queue->tds[queue->current - queue->first];
|
2015-03-17 20:46:37 +00:00
|
|
|
invalidate_dcache_range((unsigned long)cur_td,
|
2014-09-20 14:51:24 +00:00
|
|
|
ALIGN_END_ADDR(struct qTD, cur_td, 1));
|
2015-06-18 20:34:33 +00:00
|
|
|
token = hc32_to_cpu(cur_td->qt_token);
|
|
|
|
if (QT_TOKEN_GET_STATUS(token) & QT_TOKEN_STATUS_ACTIVE) {
|
|
|
|
debug("Exit poll_int_queue with no completed intr transfer. token is %x\n", token);
|
2013-03-06 14:08:31 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
2015-06-18 20:34:33 +00:00
|
|
|
|
|
|
|
toggle = QT_TOKEN_GET_DT(token);
|
|
|
|
usb_settoggle(dev, usb_pipeendpoint(pipe), usb_pipeout(pipe), toggle);
|
|
|
|
|
2013-03-06 14:08:31 +00:00
|
|
|
if (!(cur->qh_link & QH_LINK_TERMINATE))
|
|
|
|
queue->current++;
|
|
|
|
else
|
|
|
|
queue->current = NULL;
|
2014-09-24 12:06:05 +00:00
|
|
|
|
2015-03-17 20:46:37 +00:00
|
|
|
invalidate_dcache_range((unsigned long)cur->buffer,
|
2014-09-24 12:06:05 +00:00
|
|
|
ALIGN_END_ADDR(char, cur->buffer,
|
|
|
|
queue->elementsize));
|
|
|
|
|
2014-09-20 14:51:24 +00:00
|
|
|
debug("Exit poll_int_queue with completed intr transfer. token is %x at %p (first at %p)\n",
|
2015-06-18 20:34:33 +00:00
|
|
|
token, cur, queue->first);
|
2013-03-06 14:08:31 +00:00
|
|
|
return cur->buffer;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Do not free buffers associated with QHs, they're owned by someone else */
|
2015-05-11 18:43:52 +00:00
|
|
|
static int _ehci_destroy_int_queue(struct usb_device *dev,
|
|
|
|
struct int_queue *queue)
|
2013-03-06 14:08:31 +00:00
|
|
|
{
|
2015-03-25 18:22:25 +00:00
|
|
|
struct ehci_ctrl *ctrl = ehci_get_ctrl(dev);
|
2013-03-06 14:08:31 +00:00
|
|
|
int result = -1;
|
|
|
|
unsigned long timeout;
|
|
|
|
|
|
|
|
if (disable_periodic(ctrl) < 0) {
|
|
|
|
debug("FATAL: periodic should never fail, but did");
|
|
|
|
goto out;
|
|
|
|
}
|
2014-09-20 14:51:25 +00:00
|
|
|
ctrl->periodic_schedules--;
|
2013-03-06 14:08:31 +00:00
|
|
|
|
|
|
|
struct QH *cur = &ctrl->periodic_queue;
|
|
|
|
timeout = get_timer(0) + 500; /* abort after 500ms */
|
2014-04-10 12:29:45 +00:00
|
|
|
while (!(cur->qh_link & cpu_to_hc32(QH_LINK_TERMINATE))) {
|
2013-03-06 14:08:31 +00:00
|
|
|
debug("considering %p, with qh_link %x\n", cur, cur->qh_link);
|
|
|
|
if (NEXT_QH(cur) == queue->first) {
|
|
|
|
debug("found candidate. removing from chain\n");
|
|
|
|
cur->qh_link = queue->last->qh_link;
|
2015-03-17 20:46:37 +00:00
|
|
|
flush_dcache_range((unsigned long)cur,
|
2014-09-20 14:51:23 +00:00
|
|
|
ALIGN_END_ADDR(struct QH, cur, 1));
|
2013-03-06 14:08:31 +00:00
|
|
|
result = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
cur = NEXT_QH(cur);
|
|
|
|
if (get_timer(0) > timeout) {
|
|
|
|
printf("Timeout destroying interrupt endpoint queue\n");
|
|
|
|
result = -1;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-09-20 14:51:25 +00:00
|
|
|
if (ctrl->periodic_schedules > 0) {
|
2013-03-06 14:08:31 +00:00
|
|
|
result = enable_periodic(ctrl);
|
|
|
|
if (result < 0)
|
|
|
|
debug("FATAL: periodic should never fail, but did");
|
|
|
|
}
|
|
|
|
|
|
|
|
out:
|
|
|
|
free(queue->tds);
|
|
|
|
free(queue->first);
|
|
|
|
free(queue);
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2015-03-25 18:22:25 +00:00
|
|
|
static int _ehci_submit_int_msg(struct usb_device *dev, unsigned long pipe,
|
2019-08-18 08:55:27 +00:00
|
|
|
void *buffer, int length, int interval,
|
|
|
|
bool nonblock)
|
2008-11-28 12:20:46 +00:00
|
|
|
{
|
2013-03-06 14:08:31 +00:00
|
|
|
void *backbuffer;
|
|
|
|
struct int_queue *queue;
|
|
|
|
unsigned long timeout;
|
|
|
|
int result = 0, ret;
|
|
|
|
|
2008-11-28 12:20:46 +00:00
|
|
|
debug("dev=%p, pipe=%lu, buffer=%p, length=%d, interval=%d",
|
|
|
|
dev, pipe, buffer, length, interval);
|
2012-08-09 21:50:44 +00:00
|
|
|
|
2015-05-11 18:43:52 +00:00
|
|
|
queue = _ehci_create_int_queue(dev, pipe, 1, length, buffer, interval);
|
2014-09-24 12:06:04 +00:00
|
|
|
if (!queue)
|
|
|
|
return -1;
|
2013-03-06 14:08:31 +00:00
|
|
|
|
|
|
|
timeout = get_timer(0) + USB_TIMEOUT_MS(pipe);
|
2015-05-11 18:43:52 +00:00
|
|
|
while ((backbuffer = _ehci_poll_int_queue(dev, queue)) == NULL)
|
2013-03-06 14:08:31 +00:00
|
|
|
if (get_timer(0) > timeout) {
|
|
|
|
printf("Timeout poll on interrupt endpoint\n");
|
|
|
|
result = -ETIMEDOUT;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (backbuffer != buffer) {
|
2015-03-17 20:46:37 +00:00
|
|
|
debug("got wrong buffer back (%p instead of %p)\n",
|
|
|
|
backbuffer, buffer);
|
2013-03-06 14:08:31 +00:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2015-05-11 18:43:52 +00:00
|
|
|
ret = _ehci_destroy_int_queue(dev, queue);
|
2013-03-06 14:08:31 +00:00
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
/* everything worked out fine */
|
|
|
|
return result;
|
2011-09-25 19:07:56 +00:00
|
|
|
}
|
2015-03-25 18:22:25 +00:00
|
|
|
|
usb: Keep async schedule running only across mass storage xfers
Rather than keeping the asynchronous schedule running always, keep it
running only across USB mass storage transfers for now, as it seems
that keeping it running all the time interferes with certain control
transfers during device enumeration.
Note that running the async schedule all the time should not be an
issue, especially on EHCI HCD, as that one implements most of the
transfers using async schedule.
Note that we have usb_disable_asynch(), which however is utterly broken.
The usb_disable_asynch() blocks the USB core from doing async transfers
by setting a global flag. The async schedule should however be disabled
per USB controller. Moreover, setting a global flag does not prevent the
controller from using the async schedule, which e.g. the EHCI HCD does.
This patch implements additional callback to the controller, which
permits it to lock the async schedule and keep it running across
multiple transfers. Once the schedule is unlocked, it must also be
disabled. This thus prevents the async schedule from running outside
of the USB mass storage transfers.
Signed-off-by: Marek Vasut <marek.vasut+renesas@gmail.com>
Cc: Lukasz Majewski <lukma@denx.de>
Cc: Tom Rini <trini@konsulko.com>
Tested-by: Tom Rini <trini@konsulko.com> [omap3_beagle, previously failing]
2020-04-06 12:29:44 +00:00
|
|
|
static int _ehci_lock_async(struct ehci_ctrl *ctrl, int lock)
|
|
|
|
{
|
|
|
|
ctrl->async_locked = lock;
|
|
|
|
|
|
|
|
if (lock)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
return ehci_disable_async(ctrl);
|
|
|
|
}
|
|
|
|
|
2018-11-21 07:43:56 +00:00
|
|
|
#if !CONFIG_IS_ENABLED(DM_USB)
|
2015-03-25 18:22:25 +00:00
|
|
|
int submit_bulk_msg(struct usb_device *dev, unsigned long pipe,
|
|
|
|
void *buffer, int length)
|
|
|
|
{
|
|
|
|
return _ehci_submit_bulk_msg(dev, pipe, buffer, length);
|
|
|
|
}
|
|
|
|
|
|
|
|
int submit_control_msg(struct usb_device *dev, unsigned long pipe, void *buffer,
|
|
|
|
int length, struct devrequest *setup)
|
|
|
|
{
|
|
|
|
return _ehci_submit_control_msg(dev, pipe, buffer, length, setup);
|
|
|
|
}
|
|
|
|
|
|
|
|
int submit_int_msg(struct usb_device *dev, unsigned long pipe,
|
2019-08-18 08:55:27 +00:00
|
|
|
void *buffer, int length, int interval, bool nonblock)
|
2015-03-25 18:22:25 +00:00
|
|
|
{
|
2019-08-18 08:55:27 +00:00
|
|
|
return _ehci_submit_int_msg(dev, pipe, buffer, length, interval,
|
|
|
|
nonblock);
|
2015-03-25 18:22:25 +00:00
|
|
|
}
|
2015-05-11 18:43:52 +00:00
|
|
|
|
|
|
|
struct int_queue *create_int_queue(struct usb_device *dev,
|
|
|
|
unsigned long pipe, int queuesize, int elementsize,
|
|
|
|
void *buffer, int interval)
|
|
|
|
{
|
|
|
|
return _ehci_create_int_queue(dev, pipe, queuesize, elementsize,
|
|
|
|
buffer, interval);
|
|
|
|
}
|
|
|
|
|
|
|
|
void *poll_int_queue(struct usb_device *dev, struct int_queue *queue)
|
|
|
|
{
|
|
|
|
return _ehci_poll_int_queue(dev, queue);
|
|
|
|
}
|
|
|
|
|
|
|
|
int destroy_int_queue(struct usb_device *dev, struct int_queue *queue)
|
|
|
|
{
|
|
|
|
return _ehci_destroy_int_queue(dev, queue);
|
|
|
|
}
|
usb: Keep async schedule running only across mass storage xfers
Rather than keeping the asynchronous schedule running always, keep it
running only across USB mass storage transfers for now, as it seems
that keeping it running all the time interferes with certain control
transfers during device enumeration.
Note that running the async schedule all the time should not be an
issue, especially on EHCI HCD, as that one implements most of the
transfers using async schedule.
Note that we have usb_disable_asynch(), which however is utterly broken.
The usb_disable_asynch() blocks the USB core from doing async transfers
by setting a global flag. The async schedule should however be disabled
per USB controller. Moreover, setting a global flag does not prevent the
controller from using the async schedule, which e.g. the EHCI HCD does.
This patch implements additional callback to the controller, which
permits it to lock the async schedule and keep it running across
multiple transfers. Once the schedule is unlocked, it must also be
disabled. This thus prevents the async schedule from running outside
of the USB mass storage transfers.
Signed-off-by: Marek Vasut <marek.vasut+renesas@gmail.com>
Cc: Lukasz Majewski <lukma@denx.de>
Cc: Tom Rini <trini@konsulko.com>
Tested-by: Tom Rini <trini@konsulko.com> [omap3_beagle, previously failing]
2020-04-06 12:29:44 +00:00
|
|
|
|
|
|
|
int usb_lock_async(struct usb_device *dev, int lock)
|
|
|
|
{
|
|
|
|
struct ehci_ctrl *ctrl = ehci_get_ctrl(dev);
|
|
|
|
|
|
|
|
return _ehci_lock_async(ctrl, lock);
|
|
|
|
}
|
2015-03-25 18:22:29 +00:00
|
|
|
#endif
|
|
|
|
|
2018-11-21 07:43:56 +00:00
|
|
|
#if CONFIG_IS_ENABLED(DM_USB)
|
2015-03-25 18:22:29 +00:00
|
|
|
static int ehci_submit_control_msg(struct udevice *dev, struct usb_device *udev,
|
|
|
|
unsigned long pipe, void *buffer, int length,
|
|
|
|
struct devrequest *setup)
|
|
|
|
{
|
|
|
|
debug("%s: dev='%s', udev=%p, udev->dev='%s', portnr=%d\n", __func__,
|
|
|
|
dev->name, udev, udev->dev->name, udev->portnr);
|
|
|
|
|
|
|
|
return _ehci_submit_control_msg(udev, pipe, buffer, length, setup);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ehci_submit_bulk_msg(struct udevice *dev, struct usb_device *udev,
|
|
|
|
unsigned long pipe, void *buffer, int length)
|
|
|
|
{
|
|
|
|
debug("%s: dev='%s', udev=%p\n", __func__, dev->name, udev);
|
|
|
|
return _ehci_submit_bulk_msg(udev, pipe, buffer, length);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ehci_submit_int_msg(struct udevice *dev, struct usb_device *udev,
|
|
|
|
unsigned long pipe, void *buffer, int length,
|
2019-08-18 08:55:27 +00:00
|
|
|
int interval, bool nonblock)
|
2015-03-25 18:22:29 +00:00
|
|
|
{
|
|
|
|
debug("%s: dev='%s', udev=%p\n", __func__, dev->name, udev);
|
2019-08-18 08:55:27 +00:00
|
|
|
return _ehci_submit_int_msg(udev, pipe, buffer, length, interval,
|
|
|
|
nonblock);
|
2015-03-25 18:22:29 +00:00
|
|
|
}
|
|
|
|
|
2015-05-10 12:10:18 +00:00
|
|
|
static struct int_queue *ehci_create_int_queue(struct udevice *dev,
|
|
|
|
struct usb_device *udev, unsigned long pipe, int queuesize,
|
|
|
|
int elementsize, void *buffer, int interval)
|
|
|
|
{
|
|
|
|
debug("%s: dev='%s', udev=%p\n", __func__, dev->name, udev);
|
|
|
|
return _ehci_create_int_queue(udev, pipe, queuesize, elementsize,
|
|
|
|
buffer, interval);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void *ehci_poll_int_queue(struct udevice *dev, struct usb_device *udev,
|
|
|
|
struct int_queue *queue)
|
|
|
|
{
|
|
|
|
debug("%s: dev='%s', udev=%p\n", __func__, dev->name, udev);
|
|
|
|
return _ehci_poll_int_queue(udev, queue);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ehci_destroy_int_queue(struct udevice *dev, struct usb_device *udev,
|
|
|
|
struct int_queue *queue)
|
|
|
|
{
|
|
|
|
debug("%s: dev='%s', udev=%p\n", __func__, dev->name, udev);
|
|
|
|
return _ehci_destroy_int_queue(udev, queue);
|
|
|
|
}
|
|
|
|
|
2017-09-07 13:13:19 +00:00
|
|
|
static int ehci_get_max_xfer_size(struct udevice *dev, size_t *size)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* EHCD can handle any transfer length as long as there is enough
|
|
|
|
* free heap space left, hence set the theoretical max number here.
|
|
|
|
*/
|
|
|
|
*size = SIZE_MAX;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
usb: Keep async schedule running only across mass storage xfers
Rather than keeping the asynchronous schedule running always, keep it
running only across USB mass storage transfers for now, as it seems
that keeping it running all the time interferes with certain control
transfers during device enumeration.
Note that running the async schedule all the time should not be an
issue, especially on EHCI HCD, as that one implements most of the
transfers using async schedule.
Note that we have usb_disable_asynch(), which however is utterly broken.
The usb_disable_asynch() blocks the USB core from doing async transfers
by setting a global flag. The async schedule should however be disabled
per USB controller. Moreover, setting a global flag does not prevent the
controller from using the async schedule, which e.g. the EHCI HCD does.
This patch implements additional callback to the controller, which
permits it to lock the async schedule and keep it running across
multiple transfers. Once the schedule is unlocked, it must also be
disabled. This thus prevents the async schedule from running outside
of the USB mass storage transfers.
Signed-off-by: Marek Vasut <marek.vasut+renesas@gmail.com>
Cc: Lukasz Majewski <lukma@denx.de>
Cc: Tom Rini <trini@konsulko.com>
Tested-by: Tom Rini <trini@konsulko.com> [omap3_beagle, previously failing]
2020-04-06 12:29:44 +00:00
|
|
|
static int ehci_lock_async(struct udevice *dev, int lock)
|
|
|
|
{
|
|
|
|
struct ehci_ctrl *ctrl = dev_get_priv(dev);
|
|
|
|
|
|
|
|
return _ehci_lock_async(ctrl, lock);
|
|
|
|
}
|
|
|
|
|
2015-03-25 18:22:29 +00:00
|
|
|
int ehci_register(struct udevice *dev, struct ehci_hccr *hccr,
|
|
|
|
struct ehci_hcor *hcor, const struct ehci_ops *ops,
|
|
|
|
uint tweaks, enum usb_init_type init)
|
|
|
|
{
|
2015-05-05 09:54:35 +00:00
|
|
|
struct usb_bus_priv *priv = dev_get_uclass_priv(dev);
|
2015-03-25 18:22:29 +00:00
|
|
|
struct ehci_ctrl *ctrl = dev_get_priv(dev);
|
2017-11-20 18:33:39 +00:00
|
|
|
int ret = -1;
|
2015-03-25 18:22:29 +00:00
|
|
|
|
|
|
|
debug("%s: dev='%s', ctrl=%p, hccr=%p, hcor=%p, init=%d\n", __func__,
|
|
|
|
dev->name, ctrl, hccr, hcor, init);
|
|
|
|
|
2017-11-20 18:33:39 +00:00
|
|
|
if (!ctrl || !hccr || !hcor)
|
|
|
|
goto err;
|
|
|
|
|
2015-05-05 09:54:35 +00:00
|
|
|
priv->desc_before_addr = true;
|
|
|
|
|
2015-03-25 18:22:29 +00:00
|
|
|
ehci_setup_ops(ctrl, ops);
|
|
|
|
ctrl->hccr = hccr;
|
|
|
|
ctrl->hcor = hcor;
|
|
|
|
ctrl->priv = ctrl;
|
|
|
|
|
2015-08-20 23:38:05 +00:00
|
|
|
ctrl->init = init;
|
|
|
|
if (ctrl->init == USB_INIT_DEVICE)
|
2015-03-25 18:22:29 +00:00
|
|
|
goto done;
|
2015-08-20 23:38:05 +00:00
|
|
|
|
2015-03-25 18:22:29 +00:00
|
|
|
ret = ehci_reset(ctrl);
|
|
|
|
if (ret)
|
|
|
|
goto err;
|
|
|
|
|
2016-04-03 11:38:26 +00:00
|
|
|
if (ctrl->ops.init_after_reset) {
|
|
|
|
ret = ctrl->ops.init_after_reset(ctrl);
|
2016-03-31 21:12:17 +00:00
|
|
|
if (ret)
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
2015-03-25 18:22:29 +00:00
|
|
|
ret = ehci_common_init(ctrl, tweaks);
|
|
|
|
if (ret)
|
|
|
|
goto err;
|
|
|
|
done:
|
|
|
|
return 0;
|
|
|
|
err:
|
|
|
|
free(ctrl);
|
|
|
|
debug("%s: failed, ret=%d\n", __func__, ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
int ehci_deregister(struct udevice *dev)
|
|
|
|
{
|
|
|
|
struct ehci_ctrl *ctrl = dev_get_priv(dev);
|
|
|
|
|
2015-08-20 23:38:05 +00:00
|
|
|
if (ctrl->init == USB_INIT_DEVICE)
|
|
|
|
return 0;
|
|
|
|
|
2015-03-25 18:22:29 +00:00
|
|
|
ehci_shutdown(ctrl);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct dm_usb_ops ehci_usb_ops = {
|
|
|
|
.control = ehci_submit_control_msg,
|
|
|
|
.bulk = ehci_submit_bulk_msg,
|
|
|
|
.interrupt = ehci_submit_int_msg,
|
2015-05-10 12:10:18 +00:00
|
|
|
.create_int_queue = ehci_create_int_queue,
|
|
|
|
.poll_int_queue = ehci_poll_int_queue,
|
|
|
|
.destroy_int_queue = ehci_destroy_int_queue,
|
2017-09-07 13:13:19 +00:00
|
|
|
.get_max_xfer_size = ehci_get_max_xfer_size,
|
usb: Keep async schedule running only across mass storage xfers
Rather than keeping the asynchronous schedule running always, keep it
running only across USB mass storage transfers for now, as it seems
that keeping it running all the time interferes with certain control
transfers during device enumeration.
Note that running the async schedule all the time should not be an
issue, especially on EHCI HCD, as that one implements most of the
transfers using async schedule.
Note that we have usb_disable_asynch(), which however is utterly broken.
The usb_disable_asynch() blocks the USB core from doing async transfers
by setting a global flag. The async schedule should however be disabled
per USB controller. Moreover, setting a global flag does not prevent the
controller from using the async schedule, which e.g. the EHCI HCD does.
This patch implements additional callback to the controller, which
permits it to lock the async schedule and keep it running across
multiple transfers. Once the schedule is unlocked, it must also be
disabled. This thus prevents the async schedule from running outside
of the USB mass storage transfers.
Signed-off-by: Marek Vasut <marek.vasut+renesas@gmail.com>
Cc: Lukasz Majewski <lukma@denx.de>
Cc: Tom Rini <trini@konsulko.com>
Tested-by: Tom Rini <trini@konsulko.com> [omap3_beagle, previously failing]
2020-04-06 12:29:44 +00:00
|
|
|
.lock_async = ehci_lock_async,
|
2015-03-25 18:22:29 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
#endif
|
2018-08-08 12:29:55 +00:00
|
|
|
|
|
|
|
#ifdef CONFIG_PHY
|
|
|
|
int ehci_setup_phy(struct udevice *dev, struct phy *phy, int index)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (!phy)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
ret = generic_phy_get_by_index(dev, index, phy);
|
|
|
|
if (ret) {
|
|
|
|
if (ret != -ENOENT) {
|
|
|
|
dev_err(dev, "failed to get usb phy\n");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
ret = generic_phy_init(phy);
|
|
|
|
if (ret) {
|
2020-07-03 15:36:43 +00:00
|
|
|
dev_dbg(dev, "failed to init usb phy\n");
|
2018-08-08 12:29:55 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = generic_phy_power_on(phy);
|
|
|
|
if (ret) {
|
2020-07-03 15:36:43 +00:00
|
|
|
dev_dbg(dev, "failed to power on usb phy\n");
|
2018-08-08 12:29:55 +00:00
|
|
|
return generic_phy_exit(phy);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int ehci_shutdown_phy(struct udevice *dev, struct phy *phy)
|
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
if (!phy)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (generic_phy_valid(phy)) {
|
|
|
|
ret = generic_phy_power_off(phy);
|
|
|
|
if (ret) {
|
2020-07-03 15:36:43 +00:00
|
|
|
dev_dbg(dev, "failed to power off usb phy\n");
|
2018-08-08 12:29:55 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = generic_phy_exit(phy);
|
|
|
|
if (ret) {
|
2020-07-03 15:36:43 +00:00
|
|
|
dev_dbg(dev, "failed to power off usb phy\n");
|
2018-08-08 12:29:55 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
int ehci_setup_phy(struct udevice *dev, struct phy *phy, int index)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int ehci_shutdown_phy(struct udevice *dev, struct phy *phy)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#endif
|