2005-04-17 00:20:36 +02:00
|
|
|
/*
|
2005-05-07 22:21:50 +02:00
|
|
|
* Copyright (C) 2001-2004 by David Brownell
|
2006-08-30 23:50:06 +02:00
|
|
|
*
|
2005-04-17 00:20:36 +02:00
|
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
|
|
* under the terms of the GNU General Public License as published by the
|
|
|
|
* Free Software Foundation; either version 2 of the License, or (at your
|
|
|
|
* option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful, but
|
|
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
|
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
|
|
* for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
|
|
* Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* this file is part of ehci-hcd.c */
|
|
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* EHCI Root Hub ... the nonsharable stuff
|
|
|
|
*
|
|
|
|
* Registers don't need cpu_to_le32, that happens transparently
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
|
2008-04-14 18:17:10 +02:00
|
|
|
#define PORT_WAKE_BITS (PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E)
|
|
|
|
|
2008-04-18 17:11:26 +02:00
|
|
|
#ifdef CONFIG_PM
|
|
|
|
|
2007-05-04 17:52:40 +02:00
|
|
|
static int ehci_hub_control(
|
|
|
|
struct usb_hcd *hcd,
|
|
|
|
u16 typeReq,
|
|
|
|
u16 wValue,
|
|
|
|
u16 wIndex,
|
|
|
|
char *buf,
|
|
|
|
u16 wLength
|
|
|
|
);
|
|
|
|
|
|
|
|
/* After a power loss, ports that were owned by the companion must be
|
|
|
|
* reset so that the companion can still own them.
|
|
|
|
*/
|
|
|
|
static void ehci_handover_companion_ports(struct ehci_hcd *ehci)
|
|
|
|
{
|
|
|
|
u32 __iomem *reg;
|
|
|
|
u32 status;
|
|
|
|
int port;
|
|
|
|
__le32 buf;
|
|
|
|
struct usb_hcd *hcd = ehci_to_hcd(ehci);
|
|
|
|
|
|
|
|
if (!ehci->owned_ports)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* Give the connections some time to appear */
|
|
|
|
msleep(20);
|
|
|
|
|
|
|
|
port = HCS_N_PORTS(ehci->hcs_params);
|
|
|
|
while (port--) {
|
|
|
|
if (test_bit(port, &ehci->owned_ports)) {
|
|
|
|
reg = &ehci->regs->port_status[port];
|
2007-05-04 17:55:31 +02:00
|
|
|
status = ehci_readl(ehci, reg) & ~PORT_RWC_BITS;
|
2007-05-04 17:52:40 +02:00
|
|
|
|
|
|
|
/* Port already owned by companion? */
|
|
|
|
if (status & PORT_OWNER)
|
|
|
|
clear_bit(port, &ehci->owned_ports);
|
2007-05-04 17:55:31 +02:00
|
|
|
else if (test_bit(port, &ehci->companion_ports))
|
|
|
|
ehci_writel(ehci, status & ~PORT_PE, reg);
|
2007-05-04 17:52:40 +02:00
|
|
|
else
|
|
|
|
ehci_hub_control(hcd, SetPortFeature,
|
|
|
|
USB_PORT_FEAT_RESET, port + 1,
|
|
|
|
NULL, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!ehci->owned_ports)
|
|
|
|
return;
|
|
|
|
msleep(90); /* Wait for resets to complete */
|
|
|
|
|
|
|
|
port = HCS_N_PORTS(ehci->hcs_params);
|
|
|
|
while (port--) {
|
|
|
|
if (test_bit(port, &ehci->owned_ports)) {
|
|
|
|
ehci_hub_control(hcd, GetPortStatus,
|
|
|
|
0, port + 1,
|
|
|
|
(char *) &buf, sizeof(buf));
|
|
|
|
|
|
|
|
/* The companion should now own the port,
|
|
|
|
* but if something went wrong the port must not
|
|
|
|
* remain enabled.
|
|
|
|
*/
|
|
|
|
reg = &ehci->regs->port_status[port];
|
|
|
|
status = ehci_readl(ehci, reg) & ~PORT_RWC_BITS;
|
|
|
|
if (status & PORT_OWNER)
|
|
|
|
ehci_writel(ehci, status | PORT_CSC, reg);
|
|
|
|
else {
|
|
|
|
ehci_dbg(ehci, "failed handover port %d: %x\n",
|
|
|
|
port + 1, status);
|
|
|
|
ehci_writel(ehci, status & ~PORT_PE, reg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ehci->owned_ports = 0;
|
|
|
|
}
|
|
|
|
|
2010-05-13 00:21:35 +02:00
|
|
|
static void ehci_adjust_port_wakeup_flags(struct ehci_hcd *ehci,
|
|
|
|
bool suspending)
|
|
|
|
{
|
|
|
|
int port;
|
|
|
|
u32 temp;
|
|
|
|
|
|
|
|
/* If remote wakeup is enabled for the root hub but disabled
|
|
|
|
* for the controller, we must adjust all the port wakeup flags
|
|
|
|
* when the controller is suspended or resumed. In all other
|
|
|
|
* cases they don't need to be changed.
|
|
|
|
*/
|
|
|
|
if (!ehci_to_hcd(ehci)->self.root_hub->do_remote_wakeup ||
|
|
|
|
device_may_wakeup(ehci_to_hcd(ehci)->self.controller))
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* clear phy low-power mode before changing wakeup flags */
|
|
|
|
if (ehci->has_hostpc) {
|
|
|
|
port = HCS_N_PORTS(ehci->hcs_params);
|
|
|
|
while (port--) {
|
|
|
|
u32 __iomem *hostpc_reg;
|
|
|
|
|
|
|
|
hostpc_reg = (u32 __iomem *)((u8 *) ehci->regs
|
|
|
|
+ HOSTPC0 + 4 * port);
|
|
|
|
temp = ehci_readl(ehci, hostpc_reg);
|
|
|
|
ehci_writel(ehci, temp & ~HOSTPC_PHCD, hostpc_reg);
|
|
|
|
}
|
|
|
|
msleep(5);
|
|
|
|
}
|
|
|
|
|
|
|
|
port = HCS_N_PORTS(ehci->hcs_params);
|
|
|
|
while (port--) {
|
|
|
|
u32 __iomem *reg = &ehci->regs->port_status[port];
|
|
|
|
u32 t1 = ehci_readl(ehci, reg) & ~PORT_RWC_BITS;
|
|
|
|
u32 t2 = t1 & ~PORT_WAKE_BITS;
|
|
|
|
|
|
|
|
/* If we are suspending the controller, clear the flags.
|
|
|
|
* If we are resuming the controller, set the wakeup flags.
|
|
|
|
*/
|
|
|
|
if (!suspending) {
|
|
|
|
if (t1 & PORT_CONNECT)
|
|
|
|
t2 |= PORT_WKOC_E | PORT_WKDISC_E;
|
|
|
|
else
|
|
|
|
t2 |= PORT_WKOC_E | PORT_WKCONN_E;
|
|
|
|
}
|
|
|
|
ehci_vdbg(ehci, "port %d, %08x -> %08x\n",
|
|
|
|
port + 1, t1, t2);
|
|
|
|
ehci_writel(ehci, t2, reg);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* enter phy low-power mode again */
|
|
|
|
if (ehci->has_hostpc) {
|
|
|
|
port = HCS_N_PORTS(ehci->hcs_params);
|
|
|
|
while (port--) {
|
|
|
|
u32 __iomem *hostpc_reg;
|
|
|
|
|
|
|
|
hostpc_reg = (u32 __iomem *)((u8 *) ehci->regs
|
|
|
|
+ HOSTPC0 + 4 * port);
|
|
|
|
temp = ehci_readl(ehci, hostpc_reg);
|
|
|
|
ehci_writel(ehci, temp | HOSTPC_PHCD, hostpc_reg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2005-10-13 23:08:02 +02:00
|
|
|
static int ehci_bus_suspend (struct usb_hcd *hcd)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
|
|
|
struct ehci_hcd *ehci = hcd_to_ehci (hcd);
|
|
|
|
int port;
|
2006-11-09 20:42:16 +01:00
|
|
|
int mask;
|
2010-05-13 00:21:35 +02:00
|
|
|
int changed;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2007-02-01 22:09:59 +01:00
|
|
|
ehci_dbg(ehci, "suspend root hub\n");
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
if (time_before (jiffies, ehci->next_statechange))
|
|
|
|
msleep(5);
|
2008-01-10 22:43:15 +01:00
|
|
|
del_timer_sync(&ehci->watchdog);
|
|
|
|
del_timer_sync(&ehci->iaa_watchdog);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
spin_lock_irq (&ehci->lock);
|
|
|
|
|
2010-01-08 17:18:20 +01:00
|
|
|
/* Once the controller is stopped, port resumes that are already
|
|
|
|
* in progress won't complete. Hence if remote wakeup is enabled
|
|
|
|
* for the root hub and any ports are in the middle of a resume or
|
|
|
|
* remote wakeup, we must fail the suspend.
|
|
|
|
*/
|
|
|
|
if (hcd->self.root_hub->do_remote_wakeup) {
|
|
|
|
port = HCS_N_PORTS(ehci->hcs_params);
|
|
|
|
while (port--) {
|
|
|
|
if (ehci->reset_done[port] != 0) {
|
|
|
|
spin_unlock_irq(&ehci->lock);
|
|
|
|
ehci_dbg(ehci, "suspend failed because "
|
|
|
|
"port %d is resuming\n",
|
|
|
|
port + 1);
|
|
|
|
return -EBUSY;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
/* stop schedules, clean any completed work */
|
|
|
|
if (HC_IS_RUNNING(hcd->state)) {
|
|
|
|
ehci_quiesce (ehci);
|
|
|
|
hcd->state = HC_STATE_QUIESCING;
|
|
|
|
}
|
2006-12-14 20:54:08 +01:00
|
|
|
ehci->command = ehci_readl(ehci, &ehci->regs->command);
|
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers
Maintain a per-CPU global "struct pt_regs *" variable which can be used instead
of passing regs around manually through all ~1800 interrupt handlers in the
Linux kernel.
The regs pointer is used in few places, but it potentially costs both stack
space and code to pass it around. On the FRV arch, removing the regs parameter
from all the genirq function results in a 20% speed up of the IRQ exit path
(ie: from leaving timer_interrupt() to leaving do_IRQ()).
Where appropriate, an arch may override the generic storage facility and do
something different with the variable. On FRV, for instance, the address is
maintained in GR28 at all times inside the kernel as part of general exception
handling.
Having looked over the code, it appears that the parameter may be handed down
through up to twenty or so layers of functions. Consider a USB character
device attached to a USB hub, attached to a USB controller that posts its
interrupts through a cascaded auxiliary interrupt controller. A character
device driver may want to pass regs to the sysrq handler through the input
layer which adds another few layers of parameter passing.
I've build this code with allyesconfig for x86_64 and i386. I've runtested the
main part of the code on FRV and i386, though I can't test most of the drivers.
I've also done partial conversion for powerpc and MIPS - these at least compile
with minimal configurations.
This will affect all archs. Mostly the changes should be relatively easy.
Take do_IRQ(), store the regs pointer at the beginning, saving the old one:
struct pt_regs *old_regs = set_irq_regs(regs);
And put the old one back at the end:
set_irq_regs(old_regs);
Don't pass regs through to generic_handle_irq() or __do_IRQ().
In timer_interrupt(), this sort of change will be necessary:
- update_process_times(user_mode(regs));
- profile_tick(CPU_PROFILING, regs);
+ update_process_times(user_mode(get_irq_regs()));
+ profile_tick(CPU_PROFILING);
I'd like to move update_process_times()'s use of get_irq_regs() into itself,
except that i386, alone of the archs, uses something other than user_mode().
Some notes on the interrupt handling in the drivers:
(*) input_dev() is now gone entirely. The regs pointer is no longer stored in
the input_dev struct.
(*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does
something different depending on whether it's been supplied with a regs
pointer or not.
(*) Various IRQ handler function pointers have been moved to type
irq_handler_t.
Signed-Off-By: David Howells <dhowells@redhat.com>
(cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 15:55:46 +02:00
|
|
|
ehci_work(ehci);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2006-11-09 20:42:16 +01:00
|
|
|
/* Unlike other USB host controller types, EHCI doesn't have
|
|
|
|
* any notion of "global" or bus-wide suspend. The driver has
|
|
|
|
* to manually suspend all the active unsuspended ports, and
|
|
|
|
* then manually resume them in the bus_resume() routine.
|
|
|
|
*/
|
|
|
|
ehci->bus_suspended = 0;
|
2007-05-04 17:52:40 +02:00
|
|
|
ehci->owned_ports = 0;
|
2010-05-13 00:21:35 +02:00
|
|
|
changed = 0;
|
2010-01-08 17:18:20 +01:00
|
|
|
port = HCS_N_PORTS(ehci->hcs_params);
|
2005-04-17 00:20:36 +02:00
|
|
|
while (port--) {
|
|
|
|
u32 __iomem *reg = &ehci->regs->port_status [port];
|
2006-12-14 20:54:08 +01:00
|
|
|
u32 t1 = ehci_readl(ehci, reg) & ~PORT_RWC_BITS;
|
2010-05-13 00:21:35 +02:00
|
|
|
u32 t2 = t1 & ~PORT_WAKE_BITS;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2006-11-09 20:42:16 +01:00
|
|
|
/* keep track of which ports we suspend */
|
2007-05-04 17:52:40 +02:00
|
|
|
if (t1 & PORT_OWNER)
|
|
|
|
set_bit(port, &ehci->owned_ports);
|
|
|
|
else if ((t1 & PORT_PE) && !(t1 & PORT_SUSPEND)) {
|
2005-04-17 00:20:36 +02:00
|
|
|
t2 |= PORT_SUSPEND;
|
2006-11-09 20:42:16 +01:00
|
|
|
set_bit(port, &ehci->bus_suspended);
|
|
|
|
}
|
|
|
|
|
2010-05-13 00:21:35 +02:00
|
|
|
/* enable remote wakeup on all ports, if told to do so */
|
2009-07-13 06:41:20 +02:00
|
|
|
if (hcd->self.root_hub->do_remote_wakeup) {
|
|
|
|
/* only enable appropriate wake bits, otherwise the
|
|
|
|
* hardware can not go phy low power mode. If a race
|
|
|
|
* condition happens here(connection change during bits
|
|
|
|
* set), the port change detection will finally fix it.
|
|
|
|
*/
|
2010-05-13 00:21:35 +02:00
|
|
|
if (t1 & PORT_CONNECT)
|
2009-07-13 06:41:20 +02:00
|
|
|
t2 |= PORT_WKOC_E | PORT_WKDISC_E;
|
2010-05-13 00:21:35 +02:00
|
|
|
else
|
2009-07-13 06:41:20 +02:00
|
|
|
t2 |= PORT_WKOC_E | PORT_WKCONN_E;
|
2010-05-13 00:21:35 +02:00
|
|
|
}
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
if (t1 != t2) {
|
|
|
|
ehci_vdbg (ehci, "port %d, %08x -> %08x\n",
|
|
|
|
port + 1, t1, t2);
|
2006-12-14 20:54:08 +01:00
|
|
|
ehci_writel(ehci, t2, reg);
|
2010-05-13 00:21:35 +02:00
|
|
|
changed = 1;
|
|
|
|
}
|
|
|
|
}
|
2009-07-13 06:41:20 +02:00
|
|
|
|
2010-05-13 00:21:35 +02:00
|
|
|
if (changed && ehci->has_hostpc) {
|
|
|
|
spin_unlock_irq(&ehci->lock);
|
|
|
|
msleep(5); /* 5 ms for HCD to enter low-power mode */
|
|
|
|
spin_lock_irq(&ehci->lock);
|
|
|
|
|
|
|
|
port = HCS_N_PORTS(ehci->hcs_params);
|
|
|
|
while (port--) {
|
|
|
|
u32 __iomem *hostpc_reg;
|
|
|
|
u32 t3;
|
|
|
|
|
|
|
|
hostpc_reg = (u32 __iomem *)((u8 *) ehci->regs
|
|
|
|
+ HOSTPC0 + 4 * port);
|
|
|
|
t3 = ehci_readl(ehci, hostpc_reg);
|
|
|
|
ehci_writel(ehci, t3 | HOSTPC_PHCD, hostpc_reg);
|
|
|
|
t3 = ehci_readl(ehci, hostpc_reg);
|
|
|
|
ehci_dbg(ehci, "Port %d phy low-power mode %s\n",
|
2009-07-13 06:41:20 +02:00
|
|
|
port, (t3 & HOSTPC_PHCD) ?
|
|
|
|
"succeeded" : "failed");
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-01-10 17:14:53 +01:00
|
|
|
/* Apparently some devices need a >= 1-uframe delay here */
|
|
|
|
if (ehci->bus_suspended)
|
|
|
|
udelay(150);
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
/* turn off now-idle HC */
|
|
|
|
ehci_halt (ehci);
|
|
|
|
hcd->state = HC_STATE_SUSPENDED;
|
|
|
|
|
2008-04-02 22:40:20 +02:00
|
|
|
if (ehci->reclaim)
|
|
|
|
end_unlink_async(ehci);
|
|
|
|
|
2006-11-09 20:42:16 +01:00
|
|
|
/* allow remote wakeup */
|
|
|
|
mask = INTR_MASK;
|
2008-04-14 18:17:10 +02:00
|
|
|
if (!hcd->self.root_hub->do_remote_wakeup)
|
2006-11-09 20:42:16 +01:00
|
|
|
mask &= ~STS_PCD;
|
2006-12-14 20:54:08 +01:00
|
|
|
ehci_writel(ehci, mask, &ehci->regs->intr_enable);
|
|
|
|
ehci_readl(ehci, &ehci->regs->intr_enable);
|
2006-11-09 20:42:16 +01:00
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
ehci->next_statechange = jiffies + msecs_to_jiffies(10);
|
|
|
|
spin_unlock_irq (&ehci->lock);
|
2009-08-12 17:57:59 +02:00
|
|
|
|
|
|
|
/* ehci_work() may have re-enabled the watchdog timer, which we do not
|
|
|
|
* want, and so we must delete any pending watchdog timer events.
|
|
|
|
*/
|
|
|
|
del_timer_sync(&ehci->watchdog);
|
2005-04-17 00:20:36 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* caller has locked the root hub, and should reset/reinit on error */
|
2005-10-13 23:08:02 +02:00
|
|
|
static int ehci_bus_resume (struct usb_hcd *hcd)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
|
|
|
struct ehci_hcd *ehci = hcd_to_ehci (hcd);
|
|
|
|
u32 temp;
|
2007-05-04 17:52:40 +02:00
|
|
|
u32 power_okay;
|
2005-04-17 00:20:36 +02:00
|
|
|
int i;
|
2008-10-24 20:11:30 +02:00
|
|
|
u8 resume_needed = 0;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
if (time_before (jiffies, ehci->next_statechange))
|
|
|
|
msleep(5);
|
|
|
|
spin_lock_irq (&ehci->lock);
|
2010-06-22 22:39:10 +02:00
|
|
|
if (!HCD_HW_ACCESSIBLE(hcd)) {
|
2007-06-21 22:25:35 +02:00
|
|
|
spin_unlock_irq(&ehci->lock);
|
|
|
|
return -ESHUTDOWN;
|
|
|
|
}
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2009-08-20 22:39:58 +02:00
|
|
|
if (unlikely(ehci->debug)) {
|
2009-09-24 01:32:44 +02:00
|
|
|
if (!dbgp_reset_prep())
|
2009-08-20 22:39:58 +02:00
|
|
|
ehci->debug = NULL;
|
|
|
|
else
|
|
|
|
dbgp_external_startup();
|
|
|
|
}
|
|
|
|
|
2005-11-24 00:45:28 +01:00
|
|
|
/* Ideally and we've got a real resume here, and no port's power
|
|
|
|
* was lost. (For PCI, that means Vaux was maintained.) But we
|
|
|
|
* could instead be restoring a swsusp snapshot -- so that BIOS was
|
|
|
|
* the last user of the controller, not reset/pm hardware keeping
|
|
|
|
* state we gave to it.
|
|
|
|
*/
|
2007-05-04 17:52:40 +02:00
|
|
|
power_okay = ehci_readl(ehci, &ehci->regs->intr_enable);
|
|
|
|
ehci_dbg(ehci, "resume root hub%s\n",
|
|
|
|
power_okay ? "" : " after power loss");
|
2005-11-24 00:45:28 +01:00
|
|
|
|
2006-11-09 20:42:16 +01:00
|
|
|
/* at least some APM implementations will try to deliver
|
|
|
|
* IRQs right away, so delay them until we're ready.
|
|
|
|
*/
|
2006-12-14 20:54:08 +01:00
|
|
|
ehci_writel(ehci, 0, &ehci->regs->intr_enable);
|
2006-11-09 20:42:16 +01:00
|
|
|
|
|
|
|
/* re-init operational registers */
|
2006-12-14 20:54:08 +01:00
|
|
|
ehci_writel(ehci, 0, &ehci->regs->segment);
|
|
|
|
ehci_writel(ehci, ehci->periodic_dma, &ehci->regs->frame_list);
|
|
|
|
ehci_writel(ehci, (u32) ehci->async->qh_dma, &ehci->regs->async_next);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
/* restore CMD_RUN, framelist size, and irq threshold */
|
2006-12-14 20:54:08 +01:00
|
|
|
ehci_writel(ehci, ehci->command, &ehci->regs->command);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2007-03-15 20:54:30 +01:00
|
|
|
/* Some controller/firmware combinations need a delay during which
|
|
|
|
* they set up the port statuses. See Bugzilla #8190. */
|
2008-10-24 20:11:30 +02:00
|
|
|
spin_unlock_irq(&ehci->lock);
|
|
|
|
msleep(8);
|
|
|
|
spin_lock_irq(&ehci->lock);
|
2007-03-15 20:54:30 +01:00
|
|
|
|
2010-05-13 00:21:35 +02:00
|
|
|
/* clear phy low-power mode before resume */
|
|
|
|
if (ehci->bus_suspended && ehci->has_hostpc) {
|
|
|
|
i = HCS_N_PORTS(ehci->hcs_params);
|
|
|
|
while (i--) {
|
|
|
|
if (test_bit(i, &ehci->bus_suspended)) {
|
|
|
|
u32 __iomem *hostpc_reg;
|
|
|
|
|
|
|
|
hostpc_reg = (u32 __iomem *)((u8 *) ehci->regs
|
|
|
|
+ HOSTPC0 + 4 * i);
|
|
|
|
temp = ehci_readl(ehci, hostpc_reg);
|
|
|
|
ehci_writel(ehci, temp & ~HOSTPC_PHCD,
|
|
|
|
hostpc_reg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
spin_unlock_irq(&ehci->lock);
|
|
|
|
msleep(5);
|
|
|
|
spin_lock_irq(&ehci->lock);
|
|
|
|
}
|
|
|
|
|
2006-11-09 20:42:16 +01:00
|
|
|
/* manually resume the ports we suspended during bus_suspend() */
|
2005-04-17 00:20:36 +02:00
|
|
|
i = HCS_N_PORTS (ehci->hcs_params);
|
|
|
|
while (i--) {
|
2006-12-14 20:54:08 +01:00
|
|
|
temp = ehci_readl(ehci, &ehci->regs->port_status [i]);
|
2008-04-14 18:17:10 +02:00
|
|
|
temp &= ~(PORT_RWC_BITS | PORT_WAKE_BITS);
|
2006-11-09 20:42:16 +01:00
|
|
|
if (test_bit(i, &ehci->bus_suspended) &&
|
2008-10-24 20:11:30 +02:00
|
|
|
(temp & PORT_SUSPEND)) {
|
2005-04-17 00:20:36 +02:00
|
|
|
temp |= PORT_RESUME;
|
2008-10-24 20:11:30 +02:00
|
|
|
resume_needed = 1;
|
|
|
|
}
|
2006-12-14 20:54:08 +01:00
|
|
|
ehci_writel(ehci, temp, &ehci->regs->port_status [i]);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
2008-10-24 20:11:30 +02:00
|
|
|
|
|
|
|
/* msleep for 20ms only if code is trying to resume port */
|
|
|
|
if (resume_needed) {
|
|
|
|
spin_unlock_irq(&ehci->lock);
|
|
|
|
msleep(20);
|
|
|
|
spin_lock_irq(&ehci->lock);
|
|
|
|
}
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
i = HCS_N_PORTS (ehci->hcs_params);
|
|
|
|
while (i--) {
|
2006-12-14 20:54:08 +01:00
|
|
|
temp = ehci_readl(ehci, &ehci->regs->port_status [i]);
|
2006-11-09 20:42:16 +01:00
|
|
|
if (test_bit(i, &ehci->bus_suspended) &&
|
|
|
|
(temp & PORT_SUSPEND)) {
|
|
|
|
temp &= ~(PORT_RWC_BITS | PORT_RESUME);
|
2006-12-14 20:54:08 +01:00
|
|
|
ehci_writel(ehci, temp, &ehci->regs->port_status [i]);
|
2006-11-09 20:42:16 +01:00
|
|
|
ehci_vdbg (ehci, "resumed port %d\n", i + 1);
|
|
|
|
}
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
2006-12-14 20:54:08 +01:00
|
|
|
(void) ehci_readl(ehci, &ehci->regs->command);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
/* maybe re-activate the schedule(s) */
|
|
|
|
temp = 0;
|
|
|
|
if (ehci->async->qh_next.qh)
|
|
|
|
temp |= CMD_ASE;
|
|
|
|
if (ehci->periodic_sched)
|
|
|
|
temp |= CMD_PSE;
|
|
|
|
if (temp) {
|
|
|
|
ehci->command |= temp;
|
2006-12-14 20:54:08 +01:00
|
|
|
ehci_writel(ehci, ehci->command, &ehci->regs->command);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
ehci->next_statechange = jiffies + msecs_to_jiffies(5);
|
|
|
|
hcd->state = HC_STATE_RUNNING;
|
|
|
|
|
|
|
|
/* Now we can safely re-enable irqs */
|
2006-12-14 20:54:08 +01:00
|
|
|
ehci_writel(ehci, INTR_MASK, &ehci->regs->intr_enable);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
spin_unlock_irq (&ehci->lock);
|
2008-03-03 21:15:36 +01:00
|
|
|
ehci_handover_companion_ports(ehci);
|
2005-04-17 00:20:36 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
2005-10-13 23:08:02 +02:00
|
|
|
#define ehci_bus_suspend NULL
|
|
|
|
#define ehci_bus_resume NULL
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
#endif /* CONFIG_PM */
|
|
|
|
|
2007-01-16 17:59:45 +01:00
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
/* Display the ports dedicated to the companion controller */
|
2007-09-11 23:07:31 +02:00
|
|
|
static ssize_t show_companion(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf)
|
2007-01-16 17:59:45 +01:00
|
|
|
{
|
|
|
|
struct ehci_hcd *ehci;
|
|
|
|
int nports, index, n;
|
|
|
|
int count = PAGE_SIZE;
|
|
|
|
char *ptr = buf;
|
|
|
|
|
2007-09-11 23:07:31 +02:00
|
|
|
ehci = hcd_to_ehci(bus_to_hcd(dev_get_drvdata(dev)));
|
2007-01-16 17:59:45 +01:00
|
|
|
nports = HCS_N_PORTS(ehci->hcs_params);
|
|
|
|
|
|
|
|
for (index = 0; index < nports; ++index) {
|
|
|
|
if (test_bit(index, &ehci->companion_ports)) {
|
|
|
|
n = scnprintf(ptr, count, "%d\n", index + 1);
|
|
|
|
ptr += n;
|
|
|
|
count -= n;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ptr - buf;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2007-11-21 21:28:14 +01:00
|
|
|
* Sets the owner of a port
|
2007-01-16 17:59:45 +01:00
|
|
|
*/
|
2007-11-21 21:28:14 +01:00
|
|
|
static void set_owner(struct ehci_hcd *ehci, int portnum, int new_owner)
|
2007-01-16 17:59:45 +01:00
|
|
|
{
|
|
|
|
u32 __iomem *status_reg;
|
|
|
|
u32 port_status;
|
2007-11-21 21:28:14 +01:00
|
|
|
int try;
|
2007-01-16 17:59:45 +01:00
|
|
|
|
2007-11-21 21:28:14 +01:00
|
|
|
status_reg = &ehci->regs->port_status[portnum];
|
2007-01-16 17:59:45 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
* The controller won't set the OWNER bit if the port is
|
|
|
|
* enabled, so this loop will sometimes require at least two
|
|
|
|
* iterations: one to disable the port and one to set OWNER.
|
|
|
|
*/
|
|
|
|
for (try = 4; try > 0; --try) {
|
|
|
|
spin_lock_irq(&ehci->lock);
|
|
|
|
port_status = ehci_readl(ehci, status_reg);
|
|
|
|
if ((port_status & PORT_OWNER) == new_owner
|
|
|
|
|| (port_status & (PORT_OWNER | PORT_CONNECT))
|
|
|
|
== 0)
|
|
|
|
try = 0;
|
|
|
|
else {
|
|
|
|
port_status ^= PORT_OWNER;
|
|
|
|
port_status &= ~(PORT_PE | PORT_RWC_BITS);
|
|
|
|
ehci_writel(ehci, port_status, status_reg);
|
|
|
|
}
|
|
|
|
spin_unlock_irq(&ehci->lock);
|
|
|
|
if (try > 1)
|
|
|
|
msleep(5);
|
|
|
|
}
|
2007-11-21 21:28:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Dedicate or undedicate a port to the companion controller.
|
|
|
|
* Syntax is "[-]portnum", where a leading '-' sign means
|
|
|
|
* return control of the port to the EHCI controller.
|
|
|
|
*/
|
|
|
|
static ssize_t store_companion(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
const char *buf, size_t count)
|
|
|
|
{
|
|
|
|
struct ehci_hcd *ehci;
|
|
|
|
int portnum, new_owner;
|
|
|
|
|
|
|
|
ehci = hcd_to_ehci(bus_to_hcd(dev_get_drvdata(dev)));
|
|
|
|
new_owner = PORT_OWNER; /* Owned by companion */
|
|
|
|
if (sscanf(buf, "%d", &portnum) != 1)
|
|
|
|
return -EINVAL;
|
|
|
|
if (portnum < 0) {
|
|
|
|
portnum = - portnum;
|
|
|
|
new_owner = 0; /* Owned by EHCI */
|
|
|
|
}
|
|
|
|
if (portnum <= 0 || portnum > HCS_N_PORTS(ehci->hcs_params))
|
|
|
|
return -ENOENT;
|
|
|
|
portnum--;
|
|
|
|
if (new_owner)
|
|
|
|
set_bit(portnum, &ehci->companion_ports);
|
|
|
|
else
|
|
|
|
clear_bit(portnum, &ehci->companion_ports);
|
|
|
|
set_owner(ehci, portnum, new_owner);
|
2007-01-16 17:59:45 +01:00
|
|
|
return count;
|
|
|
|
}
|
2007-09-11 23:07:31 +02:00
|
|
|
static DEVICE_ATTR(companion, 0644, show_companion, store_companion);
|
2007-01-16 17:59:45 +01:00
|
|
|
|
|
|
|
static inline void create_companion_file(struct ehci_hcd *ehci)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
/* with integrated TT there is no companion! */
|
|
|
|
if (!ehci_is_TDI(ehci))
|
2009-04-27 22:15:38 +02:00
|
|
|
i = device_create_file(ehci_to_hcd(ehci)->self.controller,
|
2007-09-11 23:07:31 +02:00
|
|
|
&dev_attr_companion);
|
2007-01-16 17:59:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static inline void remove_companion_file(struct ehci_hcd *ehci)
|
|
|
|
{
|
|
|
|
/* with integrated TT there is no companion! */
|
|
|
|
if (!ehci_is_TDI(ehci))
|
2009-04-27 22:15:38 +02:00
|
|
|
device_remove_file(ehci_to_hcd(ehci)->self.controller,
|
2007-09-11 23:07:31 +02:00
|
|
|
&dev_attr_companion);
|
2007-01-16 17:59:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
static int check_reset_complete (
|
|
|
|
struct ehci_hcd *ehci,
|
|
|
|
int index,
|
2007-01-16 17:58:00 +01:00
|
|
|
u32 __iomem *status_reg,
|
2005-04-17 00:20:36 +02:00
|
|
|
int port_status
|
|
|
|
) {
|
2008-01-24 21:39:43 +01:00
|
|
|
if (!(port_status & PORT_CONNECT))
|
2005-04-17 00:20:36 +02:00
|
|
|
return port_status;
|
|
|
|
|
|
|
|
/* if reset finished and it's still not enabled -- handoff */
|
|
|
|
if (!(port_status & PORT_PE)) {
|
|
|
|
|
|
|
|
/* with integrated TT, there's nobody to hand it to! */
|
|
|
|
if (ehci_is_TDI(ehci)) {
|
|
|
|
ehci_dbg (ehci,
|
|
|
|
"Failed to enable port %d on root hub TT\n",
|
|
|
|
index+1);
|
|
|
|
return port_status;
|
|
|
|
}
|
|
|
|
|
|
|
|
ehci_dbg (ehci, "port %d full speed --> companion\n",
|
|
|
|
index + 1);
|
|
|
|
|
|
|
|
// what happens if HCS_N_CC(params) == 0 ?
|
|
|
|
port_status |= PORT_OWNER;
|
2005-08-31 19:55:38 +02:00
|
|
|
port_status &= ~PORT_RWC_BITS;
|
2007-01-16 17:58:00 +01:00
|
|
|
ehci_writel(ehci, port_status, status_reg);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
USB: powerpc: Workaround for the PPC440EPX USBH_23 errata [take 3]
A published errata for ppc440epx states, that when running Linux with
both EHCI and OHCI modules loaded, the EHCI module experiences a fatal
error when a high-speed device is connected to the USB2.0, and
functions normally if OHCI module is not loaded.
There used to be recommendation to use only hi-speed or full-speed
devices with specific conditions, when respective module was unloaded.
Later, it was observed that ohci suspend is enough to keep things
going, and it was turned into workaround, as explained below.
Quote from original descriprion:
The 440EPx USB 2.0 Host controller is an EHCI compliant controller. In
USB 2.0 Host controllers, each EHCI controller has one or more companion
controllers, which may be OHCI or UHCI. An USB 2.0 Host controller will
contain one or more ports. For each port, only one of the controllers
is connected at any one time. In the 440EPx, there is only one OHCI
companion controller, and only one USB 2.0 Host port.
All ports on an USB 2.0 controller default to the companion
controller. If you load only an ohci driver, it will have control of
the ports and any deviceplugged in will operate, although high speed
devices will be forced to operate at full speed. When an ehci driver
is loaded, it explicitly takes control of the ports. If there is a
device connected, and / or every time there is a new device connected,
the ehci driver determines if the device is high speed or not. If it
is high speed, the driver retains control of the port. If it is not,
the driver explicitly gives the companion controller control of the
port.
The is a software workaround that uses
Initial version of the software workaround was posted to
linux-usb-devel:
http://www.mail-archive.com/linux-usb-devel@lists.sourceforge.net/msg54019.html
and later available from amcc.com:
http://www.amcc.com/Embedded/Downloads/download.html?cat=1&family=15&ins=2
The patch below is generally based on the latter, but reworked to
powerpc/of_device USB drivers, and uses a few devicetree inquiries to
get rid of (some) hardcoded defines.
Signed-off-by: Vitaly Bordug <vitb@kernel.crashing.org>
Signed-off-by: Stefan Roese <sr@denx.de>
Cc: David Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
2008-11-09 19:43:30 +01:00
|
|
|
/* ensure 440EPX ohci controller state is operational */
|
|
|
|
if (ehci->has_amcc_usb23)
|
|
|
|
set_ohci_hcfs(ehci, 1);
|
|
|
|
} else {
|
2005-04-17 00:20:36 +02:00
|
|
|
ehci_dbg (ehci, "port %d high speed\n", index + 1);
|
USB: powerpc: Workaround for the PPC440EPX USBH_23 errata [take 3]
A published errata for ppc440epx states, that when running Linux with
both EHCI and OHCI modules loaded, the EHCI module experiences a fatal
error when a high-speed device is connected to the USB2.0, and
functions normally if OHCI module is not loaded.
There used to be recommendation to use only hi-speed or full-speed
devices with specific conditions, when respective module was unloaded.
Later, it was observed that ohci suspend is enough to keep things
going, and it was turned into workaround, as explained below.
Quote from original descriprion:
The 440EPx USB 2.0 Host controller is an EHCI compliant controller. In
USB 2.0 Host controllers, each EHCI controller has one or more companion
controllers, which may be OHCI or UHCI. An USB 2.0 Host controller will
contain one or more ports. For each port, only one of the controllers
is connected at any one time. In the 440EPx, there is only one OHCI
companion controller, and only one USB 2.0 Host port.
All ports on an USB 2.0 controller default to the companion
controller. If you load only an ohci driver, it will have control of
the ports and any deviceplugged in will operate, although high speed
devices will be forced to operate at full speed. When an ehci driver
is loaded, it explicitly takes control of the ports. If there is a
device connected, and / or every time there is a new device connected,
the ehci driver determines if the device is high speed or not. If it
is high speed, the driver retains control of the port. If it is not,
the driver explicitly gives the companion controller control of the
port.
The is a software workaround that uses
Initial version of the software workaround was posted to
linux-usb-devel:
http://www.mail-archive.com/linux-usb-devel@lists.sourceforge.net/msg54019.html
and later available from amcc.com:
http://www.amcc.com/Embedded/Downloads/download.html?cat=1&family=15&ins=2
The patch below is generally based on the latter, but reworked to
powerpc/of_device USB drivers, and uses a few devicetree inquiries to
get rid of (some) hardcoded defines.
Signed-off-by: Vitaly Bordug <vitb@kernel.crashing.org>
Signed-off-by: Stefan Roese <sr@denx.de>
Cc: David Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
2008-11-09 19:43:30 +01:00
|
|
|
/* ensure 440EPx ohci controller state is suspended */
|
|
|
|
if (ehci->has_amcc_usb23)
|
|
|
|
set_ohci_hcfs(ehci, 0);
|
|
|
|
}
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
return port_status;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
|
|
/* build "status change" packet (one or two bytes) from HC registers */
|
|
|
|
|
|
|
|
static int
|
|
|
|
ehci_hub_status_data (struct usb_hcd *hcd, char *buf)
|
|
|
|
{
|
|
|
|
struct ehci_hcd *ehci = hcd_to_ehci (hcd);
|
|
|
|
u32 temp, status = 0;
|
2006-11-17 08:34:58 +01:00
|
|
|
u32 mask;
|
2005-04-17 00:20:36 +02:00
|
|
|
int ports, i, retval = 1;
|
|
|
|
unsigned long flags;
|
2010-06-04 09:47:56 +02:00
|
|
|
u32 ppcd = 0;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
/* if !USB_SUSPEND, root hub timers won't get shut down ... */
|
|
|
|
if (!HC_IS_RUNNING(hcd->state))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* init status to no-changes */
|
|
|
|
buf [0] = 0;
|
|
|
|
ports = HCS_N_PORTS (ehci->hcs_params);
|
|
|
|
if (ports > 7) {
|
|
|
|
buf [1] = 0;
|
|
|
|
retval++;
|
|
|
|
}
|
2006-08-30 23:50:06 +02:00
|
|
|
|
2006-11-17 08:34:58 +01:00
|
|
|
/* Some boards (mostly VIA?) report bogus overcurrent indications,
|
|
|
|
* causing massive log spam unless we completely ignore them. It
|
2007-10-19 23:10:43 +02:00
|
|
|
* may be relevant that VIA VT8235 controllers, where PORT_POWER is
|
2006-11-17 08:34:58 +01:00
|
|
|
* always set, seem to clear PORT_OCC and PORT_CSC when writing to
|
|
|
|
* PORT_POWER; that's surprising, but maybe within-spec.
|
|
|
|
*/
|
|
|
|
if (!ignore_oc)
|
|
|
|
mask = PORT_CSC | PORT_PEC | PORT_OCC;
|
|
|
|
else
|
|
|
|
mask = PORT_CSC | PORT_PEC;
|
|
|
|
// PORT_RESUME from hardware ~= PORT_STAT_C_SUSPEND
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
/* no hub change reports (bit 0) for now (power, ...) */
|
|
|
|
|
|
|
|
/* port N changes (bit N)? */
|
|
|
|
spin_lock_irqsave (&ehci->lock, flags);
|
2010-06-04 09:47:56 +02:00
|
|
|
|
|
|
|
/* get per-port change detect bits */
|
|
|
|
if (ehci->has_ppcd)
|
|
|
|
ppcd = ehci_readl(ehci, &ehci->regs->status) >> 16;
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
for (i = 0; i < ports; i++) {
|
2010-06-04 09:47:56 +02:00
|
|
|
/* leverage per-port change bits feature */
|
|
|
|
if (ehci->has_ppcd && !(ppcd & (1 << i)))
|
|
|
|
continue;
|
2006-12-14 20:54:08 +01:00
|
|
|
temp = ehci_readl(ehci, &ehci->regs->port_status [i]);
|
2007-01-16 17:58:47 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Return status information even for ports with OWNER set.
|
|
|
|
* Otherwise khubd wouldn't see the disconnect event when a
|
|
|
|
* high-speed device is switched over to the companion
|
|
|
|
* controller by the user.
|
|
|
|
*/
|
|
|
|
|
2008-10-06 17:25:53 +02:00
|
|
|
if ((temp & mask) != 0 || test_bit(i, &ehci->port_c_suspend)
|
|
|
|
|| (ehci->reset_done[i] && time_after_eq(
|
|
|
|
jiffies, ehci->reset_done[i]))) {
|
2005-04-17 00:20:36 +02:00
|
|
|
if (i < 7)
|
|
|
|
buf [0] |= 1 << (i + 1);
|
|
|
|
else
|
|
|
|
buf [1] |= 1 << (i - 7);
|
|
|
|
status = STS_PCD;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* FIXME autosuspend idle root hubs */
|
|
|
|
spin_unlock_irqrestore (&ehci->lock, flags);
|
|
|
|
return status ? retval : 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
static void
|
|
|
|
ehci_hub_descriptor (
|
|
|
|
struct ehci_hcd *ehci,
|
|
|
|
struct usb_hub_descriptor *desc
|
|
|
|
) {
|
|
|
|
int ports = HCS_N_PORTS (ehci->hcs_params);
|
|
|
|
u16 temp;
|
|
|
|
|
|
|
|
desc->bDescriptorType = 0x29;
|
|
|
|
desc->bPwrOn2PwrGood = 10; /* ehci 1.0, 2.3.9 says 20ms max */
|
|
|
|
desc->bHubContrCurrent = 0;
|
|
|
|
|
|
|
|
desc->bNbrPorts = ports;
|
|
|
|
temp = 1 + (ports / 8);
|
|
|
|
desc->bDescLength = 7 + 2 * temp;
|
|
|
|
|
|
|
|
/* two bitmaps: ports removable, and usb 1.0 legacy PortPwrCtrlMask */
|
|
|
|
memset (&desc->bitmap [0], 0, temp);
|
|
|
|
memset (&desc->bitmap [temp], 0xff, temp);
|
|
|
|
|
|
|
|
temp = 0x0008; /* per-port overcurrent reporting */
|
|
|
|
if (HCS_PPC (ehci->hcs_params))
|
|
|
|
temp |= 0x0001; /* per-port power control */
|
2005-04-09 18:00:29 +02:00
|
|
|
else
|
|
|
|
temp |= 0x0002; /* no power switching */
|
2005-04-17 00:20:36 +02:00
|
|
|
#if 0
|
|
|
|
// re-enable when we support USB_PORT_FEAT_INDICATOR below.
|
|
|
|
if (HCS_INDICATOR (ehci->hcs_params))
|
|
|
|
temp |= 0x0080; /* per-port indicators (LEDs) */
|
|
|
|
#endif
|
2008-04-28 08:00:16 +02:00
|
|
|
desc->wHubCharacteristics = cpu_to_le16(temp);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
static int ehci_hub_control (
|
|
|
|
struct usb_hcd *hcd,
|
|
|
|
u16 typeReq,
|
|
|
|
u16 wValue,
|
|
|
|
u16 wIndex,
|
|
|
|
char *buf,
|
|
|
|
u16 wLength
|
|
|
|
) {
|
|
|
|
struct ehci_hcd *ehci = hcd_to_ehci (hcd);
|
|
|
|
int ports = HCS_N_PORTS (ehci->hcs_params);
|
2007-05-04 17:52:40 +02:00
|
|
|
u32 __iomem *status_reg = &ehci->regs->port_status[
|
|
|
|
(wIndex & 0xff) - 1];
|
2009-07-13 06:41:20 +02:00
|
|
|
u32 __iomem *hostpc_reg = NULL;
|
|
|
|
u32 temp, temp1, status;
|
2005-04-17 00:20:36 +02:00
|
|
|
unsigned long flags;
|
|
|
|
int retval = 0;
|
2006-11-17 08:56:15 +01:00
|
|
|
unsigned selector;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* FIXME: support SetPortFeatures USB_PORT_FEAT_INDICATOR.
|
|
|
|
* HCS_INDICATOR may say we can change LEDs to off/amber/green.
|
|
|
|
* (track current state ourselves) ... blink for diagnostics,
|
|
|
|
* power, "this is the one", etc. EHCI spec supports this.
|
|
|
|
*/
|
|
|
|
|
2009-07-13 06:41:20 +02:00
|
|
|
if (ehci->has_hostpc)
|
|
|
|
hostpc_reg = (u32 __iomem *)((u8 *)ehci->regs
|
|
|
|
+ HOSTPC0 + 4 * ((wIndex & 0xff) - 1));
|
2005-04-17 00:20:36 +02:00
|
|
|
spin_lock_irqsave (&ehci->lock, flags);
|
|
|
|
switch (typeReq) {
|
|
|
|
case ClearHubFeature:
|
|
|
|
switch (wValue) {
|
|
|
|
case C_HUB_LOCAL_POWER:
|
|
|
|
case C_HUB_OVER_CURRENT:
|
|
|
|
/* no hub-wide feature/status flags */
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case ClearPortFeature:
|
|
|
|
if (!wIndex || wIndex > ports)
|
|
|
|
goto error;
|
|
|
|
wIndex--;
|
2007-01-16 17:58:00 +01:00
|
|
|
temp = ehci_readl(ehci, status_reg);
|
2007-01-16 17:58:47 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Even if OWNER is set, so the port is owned by the
|
|
|
|
* companion controller, khubd needs to be able to clear
|
|
|
|
* the port-change status bits (especially
|
2010-03-04 23:05:08 +01:00
|
|
|
* USB_PORT_STAT_C_CONNECTION).
|
2007-01-16 17:58:47 +01:00
|
|
|
*/
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
switch (wValue) {
|
|
|
|
case USB_PORT_FEAT_ENABLE:
|
2007-01-16 17:58:00 +01:00
|
|
|
ehci_writel(ehci, temp & ~PORT_PE, status_reg);
|
2005-04-17 00:20:36 +02:00
|
|
|
break;
|
|
|
|
case USB_PORT_FEAT_C_ENABLE:
|
2006-12-14 20:54:08 +01:00
|
|
|
ehci_writel(ehci, (temp & ~PORT_RWC_BITS) | PORT_PEC,
|
2007-01-16 17:58:00 +01:00
|
|
|
status_reg);
|
2005-04-17 00:20:36 +02:00
|
|
|
break;
|
|
|
|
case USB_PORT_FEAT_SUSPEND:
|
|
|
|
if (temp & PORT_RESET)
|
|
|
|
goto error;
|
2006-01-20 22:55:14 +01:00
|
|
|
if (ehci->no_selective_suspend)
|
|
|
|
break;
|
2010-05-13 00:21:35 +02:00
|
|
|
if (!(temp & PORT_SUSPEND))
|
|
|
|
break;
|
|
|
|
if ((temp & PORT_PE) == 0)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
/* clear phy low-power mode before resume */
|
|
|
|
if (hostpc_reg) {
|
|
|
|
temp1 = ehci_readl(ehci, hostpc_reg);
|
|
|
|
ehci_writel(ehci, temp1 & ~HOSTPC_PHCD,
|
2010-05-10 05:17:49 +02:00
|
|
|
hostpc_reg);
|
2010-05-13 00:21:35 +02:00
|
|
|
spin_unlock_irqrestore(&ehci->lock, flags);
|
|
|
|
msleep(5);/* wait to leave low-power mode */
|
|
|
|
spin_lock_irqsave(&ehci->lock, flags);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
2010-05-13 00:21:35 +02:00
|
|
|
/* resume signaling for 20 msec */
|
|
|
|
temp &= ~(PORT_RWC_BITS | PORT_WAKE_BITS);
|
|
|
|
ehci_writel(ehci, temp | PORT_RESUME, status_reg);
|
|
|
|
ehci->reset_done[wIndex] = jiffies
|
|
|
|
+ msecs_to_jiffies(20);
|
2005-04-17 00:20:36 +02:00
|
|
|
break;
|
|
|
|
case USB_PORT_FEAT_C_SUSPEND:
|
2008-05-20 22:58:58 +02:00
|
|
|
clear_bit(wIndex, &ehci->port_c_suspend);
|
2005-04-17 00:20:36 +02:00
|
|
|
break;
|
|
|
|
case USB_PORT_FEAT_POWER:
|
|
|
|
if (HCS_PPC (ehci->hcs_params))
|
2006-12-14 20:54:08 +01:00
|
|
|
ehci_writel(ehci,
|
|
|
|
temp & ~(PORT_RWC_BITS | PORT_POWER),
|
2007-01-16 17:58:00 +01:00
|
|
|
status_reg);
|
2005-04-17 00:20:36 +02:00
|
|
|
break;
|
|
|
|
case USB_PORT_FEAT_C_CONNECTION:
|
2010-06-04 09:47:55 +02:00
|
|
|
if (ehci->has_lpm) {
|
|
|
|
/* clear PORTSC bits on disconnect */
|
|
|
|
temp &= ~PORT_LPM;
|
|
|
|
temp &= ~PORT_DEV_ADDR;
|
|
|
|
}
|
2006-12-14 20:54:08 +01:00
|
|
|
ehci_writel(ehci, (temp & ~PORT_RWC_BITS) | PORT_CSC,
|
2007-01-16 17:58:00 +01:00
|
|
|
status_reg);
|
2005-04-17 00:20:36 +02:00
|
|
|
break;
|
|
|
|
case USB_PORT_FEAT_C_OVER_CURRENT:
|
2006-12-14 20:54:08 +01:00
|
|
|
ehci_writel(ehci, (temp & ~PORT_RWC_BITS) | PORT_OCC,
|
2007-01-16 17:58:00 +01:00
|
|
|
status_reg);
|
2005-04-17 00:20:36 +02:00
|
|
|
break;
|
|
|
|
case USB_PORT_FEAT_C_RESET:
|
|
|
|
/* GetPortStatus clears reset */
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
goto error;
|
|
|
|
}
|
2006-12-14 20:54:08 +01:00
|
|
|
ehci_readl(ehci, &ehci->regs->command); /* unblock posted write */
|
2005-04-17 00:20:36 +02:00
|
|
|
break;
|
|
|
|
case GetHubDescriptor:
|
|
|
|
ehci_hub_descriptor (ehci, (struct usb_hub_descriptor *)
|
|
|
|
buf);
|
|
|
|
break;
|
|
|
|
case GetHubStatus:
|
|
|
|
/* no hub-wide feature/status flags */
|
|
|
|
memset (buf, 0, 4);
|
|
|
|
//cpu_to_le32s ((u32 *) buf);
|
|
|
|
break;
|
|
|
|
case GetPortStatus:
|
|
|
|
if (!wIndex || wIndex > ports)
|
|
|
|
goto error;
|
|
|
|
wIndex--;
|
|
|
|
status = 0;
|
2007-01-16 17:58:00 +01:00
|
|
|
temp = ehci_readl(ehci, status_reg);
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
// wPortChange bits
|
|
|
|
if (temp & PORT_CSC)
|
2010-03-04 23:05:08 +01:00
|
|
|
status |= USB_PORT_STAT_C_CONNECTION << 16;
|
2005-04-17 00:20:36 +02:00
|
|
|
if (temp & PORT_PEC)
|
2010-03-04 23:05:08 +01:00
|
|
|
status |= USB_PORT_STAT_C_ENABLE << 16;
|
2007-05-30 20:04:48 +02:00
|
|
|
|
|
|
|
if ((temp & PORT_OCC) && !ignore_oc){
|
2010-03-04 23:05:08 +01:00
|
|
|
status |= USB_PORT_STAT_C_OVERCURRENT << 16;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2007-05-30 20:04:48 +02:00
|
|
|
/*
|
|
|
|
* Hubs should disable port power on over-current.
|
|
|
|
* However, not all EHCI implementations do this
|
|
|
|
* automatically, even if they _do_ support per-port
|
|
|
|
* power switching; they're allowed to just limit the
|
|
|
|
* current. khubd will turn the power back on.
|
|
|
|
*/
|
|
|
|
if (HCS_PPC (ehci->hcs_params)){
|
|
|
|
ehci_writel(ehci,
|
|
|
|
temp & ~(PORT_RWC_BITS | PORT_POWER),
|
|
|
|
status_reg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
/* whoever resumes must GetPortStatus to complete it!! */
|
2007-01-22 22:08:53 +01:00
|
|
|
if (temp & PORT_RESUME) {
|
|
|
|
|
|
|
|
/* Remote Wakeup received? */
|
|
|
|
if (!ehci->reset_done[wIndex]) {
|
|
|
|
/* resume signaling for 20 msec */
|
|
|
|
ehci->reset_done[wIndex] = jiffies
|
|
|
|
+ msecs_to_jiffies(20);
|
|
|
|
/* check the port again */
|
|
|
|
mod_timer(&ehci_to_hcd(ehci)->rh_timer,
|
|
|
|
ehci->reset_done[wIndex]);
|
|
|
|
}
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2007-01-22 22:08:53 +01:00
|
|
|
/* resume completed? */
|
|
|
|
else if (time_after_eq(jiffies,
|
|
|
|
ehci->reset_done[wIndex])) {
|
2008-10-06 17:25:53 +02:00
|
|
|
clear_bit(wIndex, &ehci->suspended_ports);
|
2008-05-20 22:58:58 +02:00
|
|
|
set_bit(wIndex, &ehci->port_c_suspend);
|
2007-01-22 22:08:53 +01:00
|
|
|
ehci->reset_done[wIndex] = 0;
|
|
|
|
|
|
|
|
/* stop resume signaling */
|
|
|
|
temp = ehci_readl(ehci, status_reg);
|
|
|
|
ehci_writel(ehci,
|
2007-01-16 17:58:00 +01:00
|
|
|
temp & ~(PORT_RWC_BITS | PORT_RESUME),
|
|
|
|
status_reg);
|
2007-01-22 22:08:53 +01:00
|
|
|
retval = handshake(ehci, status_reg,
|
2006-12-14 20:54:08 +01:00
|
|
|
PORT_RESUME, 0, 2000 /* 2msec */);
|
2007-01-22 22:08:53 +01:00
|
|
|
if (retval != 0) {
|
|
|
|
ehci_err(ehci,
|
|
|
|
"port %d resume error %d\n",
|
|
|
|
wIndex + 1, retval);
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
temp &= ~(PORT_SUSPEND|PORT_RESUME|(3<<10));
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* whoever resets must GetPortStatus to complete it!! */
|
|
|
|
if ((temp & PORT_RESET)
|
2007-01-22 22:08:53 +01:00
|
|
|
&& time_after_eq(jiffies,
|
|
|
|
ehci->reset_done[wIndex])) {
|
2010-03-04 23:05:08 +01:00
|
|
|
status |= USB_PORT_STAT_C_RESET << 16;
|
2005-04-17 00:20:36 +02:00
|
|
|
ehci->reset_done [wIndex] = 0;
|
|
|
|
|
|
|
|
/* force reset to complete */
|
2006-12-14 20:54:08 +01:00
|
|
|
ehci_writel(ehci, temp & ~(PORT_RWC_BITS | PORT_RESET),
|
2007-01-16 17:58:00 +01:00
|
|
|
status_reg);
|
2005-06-13 16:15:28 +02:00
|
|
|
/* REVISIT: some hardware needs 550+ usec to clear
|
|
|
|
* this bit; seems too long to spin routinely...
|
|
|
|
*/
|
2007-01-16 17:58:00 +01:00
|
|
|
retval = handshake(ehci, status_reg,
|
2010-04-13 18:13:15 +02:00
|
|
|
PORT_RESET, 0, 1000);
|
2005-04-17 00:20:36 +02:00
|
|
|
if (retval != 0) {
|
|
|
|
ehci_err (ehci, "port %d reset error %d\n",
|
|
|
|
wIndex + 1, retval);
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* see what we found out */
|
2007-01-16 17:58:00 +01:00
|
|
|
temp = check_reset_complete (ehci, wIndex, status_reg,
|
|
|
|
ehci_readl(ehci, status_reg));
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
2008-10-06 17:25:53 +02:00
|
|
|
if (!(temp & (PORT_RESUME|PORT_RESET)))
|
|
|
|
ehci->reset_done[wIndex] = 0;
|
|
|
|
|
2007-01-16 17:59:45 +01:00
|
|
|
/* transfer dedicated ports to the companion hc */
|
|
|
|
if ((temp & PORT_CONNECT) &&
|
|
|
|
test_bit(wIndex, &ehci->companion_ports)) {
|
|
|
|
temp &= ~PORT_RWC_BITS;
|
|
|
|
temp |= PORT_OWNER;
|
|
|
|
ehci_writel(ehci, temp, status_reg);
|
|
|
|
ehci_dbg(ehci, "port %d --> companion\n", wIndex + 1);
|
|
|
|
temp = ehci_readl(ehci, status_reg);
|
|
|
|
}
|
|
|
|
|
2007-01-16 17:58:47 +01:00
|
|
|
/*
|
|
|
|
* Even if OWNER is set, there's no harm letting khubd
|
|
|
|
* see the wPortStatus values (they should all be 0 except
|
|
|
|
* for PORT_POWER anyway).
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (temp & PORT_CONNECT) {
|
2010-03-04 23:05:08 +01:00
|
|
|
status |= USB_PORT_STAT_CONNECTION;
|
2007-01-16 17:58:47 +01:00
|
|
|
// status may be from integrated TT
|
2009-07-13 06:41:20 +02:00
|
|
|
if (ehci->has_hostpc) {
|
|
|
|
temp1 = ehci_readl(ehci, hostpc_reg);
|
|
|
|
status |= ehci_port_speed(ehci, temp1);
|
|
|
|
} else
|
|
|
|
status |= ehci_port_speed(ehci, temp);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
2007-01-16 17:58:47 +01:00
|
|
|
if (temp & PORT_PE)
|
2010-03-04 23:05:08 +01:00
|
|
|
status |= USB_PORT_STAT_ENABLE;
|
2008-10-06 17:25:53 +02:00
|
|
|
|
|
|
|
/* maybe the port was unsuspended without our knowledge */
|
|
|
|
if (temp & (PORT_SUSPEND|PORT_RESUME)) {
|
2010-03-04 23:05:08 +01:00
|
|
|
status |= USB_PORT_STAT_SUSPEND;
|
2008-10-06 17:25:53 +02:00
|
|
|
} else if (test_bit(wIndex, &ehci->suspended_ports)) {
|
|
|
|
clear_bit(wIndex, &ehci->suspended_ports);
|
|
|
|
ehci->reset_done[wIndex] = 0;
|
|
|
|
if (temp & PORT_PE)
|
|
|
|
set_bit(wIndex, &ehci->port_c_suspend);
|
|
|
|
}
|
|
|
|
|
2007-01-16 17:58:47 +01:00
|
|
|
if (temp & PORT_OC)
|
2010-03-04 23:05:08 +01:00
|
|
|
status |= USB_PORT_STAT_OVERCURRENT;
|
2007-01-16 17:58:47 +01:00
|
|
|
if (temp & PORT_RESET)
|
2010-03-04 23:05:08 +01:00
|
|
|
status |= USB_PORT_STAT_RESET;
|
2007-01-16 17:58:47 +01:00
|
|
|
if (temp & PORT_POWER)
|
2010-03-04 23:05:08 +01:00
|
|
|
status |= USB_PORT_STAT_POWER;
|
2008-05-20 22:58:58 +02:00
|
|
|
if (test_bit(wIndex, &ehci->port_c_suspend))
|
2010-03-04 23:05:08 +01:00
|
|
|
status |= USB_PORT_STAT_C_SUSPEND << 16;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
2008-02-01 20:42:05 +01:00
|
|
|
#ifndef VERBOSE_DEBUG
|
2005-04-17 00:20:36 +02:00
|
|
|
if (status & ~0xffff) /* only if wPortChange is interesting */
|
|
|
|
#endif
|
|
|
|
dbg_port (ehci, "GetStatus", wIndex + 1, temp);
|
2008-04-29 10:03:40 +02:00
|
|
|
put_unaligned_le32(status, buf);
|
2005-04-17 00:20:36 +02:00
|
|
|
break;
|
|
|
|
case SetHubFeature:
|
|
|
|
switch (wValue) {
|
|
|
|
case C_HUB_LOCAL_POWER:
|
|
|
|
case C_HUB_OVER_CURRENT:
|
|
|
|
/* no hub-wide feature/status flags */
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case SetPortFeature:
|
2006-11-17 08:56:15 +01:00
|
|
|
selector = wIndex >> 8;
|
|
|
|
wIndex &= 0xff;
|
2009-08-20 22:39:54 +02:00
|
|
|
if (unlikely(ehci->debug)) {
|
|
|
|
/* If the debug port is active any port
|
|
|
|
* feature requests should get denied */
|
|
|
|
if (wIndex == HCS_DEBUG_PORT(ehci->hcs_params) &&
|
|
|
|
(readl(&ehci->debug->control) & DBGP_ENABLED)) {
|
|
|
|
retval = -ENODEV;
|
|
|
|
goto error_exit;
|
|
|
|
}
|
|
|
|
}
|
2005-04-17 00:20:36 +02:00
|
|
|
if (!wIndex || wIndex > ports)
|
|
|
|
goto error;
|
|
|
|
wIndex--;
|
2007-01-16 17:58:00 +01:00
|
|
|
temp = ehci_readl(ehci, status_reg);
|
2005-04-17 00:20:36 +02:00
|
|
|
if (temp & PORT_OWNER)
|
|
|
|
break;
|
|
|
|
|
2005-08-31 19:55:38 +02:00
|
|
|
temp &= ~PORT_RWC_BITS;
|
2005-04-17 00:20:36 +02:00
|
|
|
switch (wValue) {
|
|
|
|
case USB_PORT_FEAT_SUSPEND:
|
2006-01-20 22:55:14 +01:00
|
|
|
if (ehci->no_selective_suspend)
|
|
|
|
break;
|
2005-04-17 00:20:36 +02:00
|
|
|
if ((temp & PORT_PE) == 0
|
|
|
|
|| (temp & PORT_RESET) != 0)
|
|
|
|
goto error;
|
2010-01-19 09:31:31 +01:00
|
|
|
|
2009-07-13 06:41:20 +02:00
|
|
|
/* After above check the port must be connected.
|
|
|
|
* Set appropriate bit thus could put phy into low power
|
|
|
|
* mode if we have hostpc feature
|
|
|
|
*/
|
2010-01-19 09:31:31 +01:00
|
|
|
temp &= ~PORT_WKCONN_E;
|
|
|
|
temp |= PORT_WKDISC_E | PORT_WKOC_E;
|
|
|
|
ehci_writel(ehci, temp | PORT_SUSPEND, status_reg);
|
2009-07-13 06:41:20 +02:00
|
|
|
if (hostpc_reg) {
|
2010-01-19 09:31:31 +01:00
|
|
|
spin_unlock_irqrestore(&ehci->lock, flags);
|
2009-07-13 06:41:20 +02:00
|
|
|
msleep(5);/* 5ms for HCD enter low pwr mode */
|
2010-01-19 09:31:31 +01:00
|
|
|
spin_lock_irqsave(&ehci->lock, flags);
|
2009-07-13 06:41:20 +02:00
|
|
|
temp1 = ehci_readl(ehci, hostpc_reg);
|
|
|
|
ehci_writel(ehci, temp1 | HOSTPC_PHCD,
|
|
|
|
hostpc_reg);
|
|
|
|
temp1 = ehci_readl(ehci, hostpc_reg);
|
|
|
|
ehci_dbg(ehci, "Port%d phy low pwr mode %s\n",
|
|
|
|
wIndex, (temp1 & HOSTPC_PHCD) ?
|
|
|
|
"succeeded" : "failed");
|
|
|
|
}
|
2008-10-06 17:25:53 +02:00
|
|
|
set_bit(wIndex, &ehci->suspended_ports);
|
2005-04-17 00:20:36 +02:00
|
|
|
break;
|
|
|
|
case USB_PORT_FEAT_POWER:
|
|
|
|
if (HCS_PPC (ehci->hcs_params))
|
2006-12-14 20:54:08 +01:00
|
|
|
ehci_writel(ehci, temp | PORT_POWER,
|
2007-01-16 17:58:00 +01:00
|
|
|
status_reg);
|
2005-04-17 00:20:36 +02:00
|
|
|
break;
|
|
|
|
case USB_PORT_FEAT_RESET:
|
|
|
|
if (temp & PORT_RESUME)
|
|
|
|
goto error;
|
|
|
|
/* line status bits may report this as low speed,
|
|
|
|
* which can be fine if this root hub has a
|
|
|
|
* transaction translator built in.
|
|
|
|
*/
|
|
|
|
if ((temp & (PORT_PE|PORT_CONNECT)) == PORT_CONNECT
|
|
|
|
&& !ehci_is_TDI(ehci)
|
|
|
|
&& PORT_USB11 (temp)) {
|
|
|
|
ehci_dbg (ehci,
|
|
|
|
"port %d low speed --> companion\n",
|
|
|
|
wIndex + 1);
|
|
|
|
temp |= PORT_OWNER;
|
|
|
|
} else {
|
|
|
|
ehci_vdbg (ehci, "port %d reset\n", wIndex + 1);
|
|
|
|
temp |= PORT_RESET;
|
|
|
|
temp &= ~PORT_PE;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* caller must wait, then call GetPortStatus
|
|
|
|
* usb 2.0 spec says 50 ms resets on root
|
|
|
|
*/
|
|
|
|
ehci->reset_done [wIndex] = jiffies
|
|
|
|
+ msecs_to_jiffies (50);
|
|
|
|
}
|
2007-01-16 17:58:00 +01:00
|
|
|
ehci_writel(ehci, temp, status_reg);
|
2005-04-17 00:20:36 +02:00
|
|
|
break;
|
2006-11-17 08:56:15 +01:00
|
|
|
|
|
|
|
/* For downstream facing ports (these): one hub port is put
|
|
|
|
* into test mode according to USB2 11.24.2.13, then the hub
|
|
|
|
* must be reset (which for root hub now means rmmod+modprobe,
|
|
|
|
* or else system reboot). See EHCI 2.3.9 and 4.14 for info
|
|
|
|
* about the EHCI-specific stuff.
|
|
|
|
*/
|
|
|
|
case USB_PORT_FEAT_TEST:
|
|
|
|
if (!selector || selector > 5)
|
|
|
|
goto error;
|
|
|
|
ehci_quiesce(ehci);
|
|
|
|
ehci_halt(ehci);
|
|
|
|
temp |= selector << 16;
|
2007-01-16 17:58:00 +01:00
|
|
|
ehci_writel(ehci, temp, status_reg);
|
2006-11-17 08:56:15 +01:00
|
|
|
break;
|
|
|
|
|
2005-04-17 00:20:36 +02:00
|
|
|
default:
|
|
|
|
goto error;
|
|
|
|
}
|
2006-12-14 20:54:08 +01:00
|
|
|
ehci_readl(ehci, &ehci->regs->command); /* unblock posted writes */
|
2005-04-17 00:20:36 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
error:
|
|
|
|
/* "stall" on error */
|
|
|
|
retval = -EPIPE;
|
|
|
|
}
|
2009-08-20 22:39:54 +02:00
|
|
|
error_exit:
|
2005-04-17 00:20:36 +02:00
|
|
|
spin_unlock_irqrestore (&ehci->lock, flags);
|
|
|
|
return retval;
|
|
|
|
}
|
2007-11-21 21:28:14 +01:00
|
|
|
|
|
|
|
static void ehci_relinquish_port(struct usb_hcd *hcd, int portnum)
|
|
|
|
{
|
|
|
|
struct ehci_hcd *ehci = hcd_to_ehci(hcd);
|
|
|
|
|
|
|
|
if (ehci_is_TDI(ehci))
|
|
|
|
return;
|
|
|
|
set_owner(ehci, --portnum, PORT_OWNER);
|
|
|
|
}
|
|
|
|
|
2008-05-20 22:58:29 +02:00
|
|
|
static int ehci_port_handed_over(struct usb_hcd *hcd, int portnum)
|
|
|
|
{
|
|
|
|
struct ehci_hcd *ehci = hcd_to_ehci(hcd);
|
|
|
|
u32 __iomem *reg;
|
|
|
|
|
|
|
|
if (ehci_is_TDI(ehci))
|
|
|
|
return 0;
|
|
|
|
reg = &ehci->regs->port_status[portnum - 1];
|
|
|
|
return ehci_readl(ehci, reg) & PORT_OWNER;
|
|
|
|
}
|