Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 57e06c11 authored by Alan Stern's avatar Alan Stern Committed by Greg Kroah-Hartman
Browse files

EHCI: force high-speed devices to run at full speed



This patch (as710) adds a sysfs class-device attribute file named
"companion" for EHCI controllers.  The file contains a list of port
numbers that are dedicated to the companion controller; by writing a
port number to the file the user can force a high-speed device
attached directly to the computer to run at full speed.  (As far as I
know it is not possible to do this for a device attached to an
external hub.)  A port is removed from the file by writing the
negative of its port number.

Several users have asked for this facility and it seems like a useful
thing to have.  Every now and then one runs across a device which
behaves much better at full speed than at high speed.

Signed-off-by: default avatarAlan Stern <stern@rowland.harvard.edu>
Cc: David Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent 625b5c9a
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -388,6 +388,7 @@ static void ehci_stop (struct usb_hcd *hcd)
	/* let companion controllers work when we aren't */
	ehci_writel(ehci, 0, &ehci->regs->configured_flag);

	remove_companion_file(ehci);
	remove_debug_files (ehci);

	/* root hub is shut down separately (first, when possible) */
@@ -563,6 +564,7 @@ static int ehci_run (struct usb_hcd *hcd)
	 * since the class device isn't created that early.
	 */
	create_debug_files(ehci);
	create_companion_file(ehci);

	return 0;
}
+107 −0
Original line number Diff line number Diff line
@@ -188,6 +188,103 @@ static int ehci_bus_resume (struct usb_hcd *hcd)

#endif	/* CONFIG_PM */

/*-------------------------------------------------------------------------*/

/* Display the ports dedicated to the companion controller */
static ssize_t show_companion(struct class_device *class_dev, char *buf)
{
	struct ehci_hcd		*ehci;
	int			nports, index, n;
	int			count = PAGE_SIZE;
	char			*ptr = buf;

	ehci = hcd_to_ehci(bus_to_hcd(class_get_devdata(class_dev)));
	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;
}

/*
 * 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 class_device *class_dev,
		const char *buf, size_t count)
{
	struct ehci_hcd		*ehci;
	int			portnum, new_owner, try;
	u32 __iomem		*status_reg;
	u32			port_status;

	ehci = hcd_to_ehci(bus_to_hcd(class_get_devdata(class_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;
	status_reg = &ehci->regs->port_status[--portnum];
	if (new_owner)
		set_bit(portnum, &ehci->companion_ports);
	else
		clear_bit(portnum, &ehci->companion_ports);

	/*
	 * 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);
	}
	return count;
}
static CLASS_DEVICE_ATTR(companion, 0644, show_companion, store_companion);

static inline void create_companion_file(struct ehci_hcd *ehci)
{
	int	i;

	/* with integrated TT there is no companion! */
	if (!ehci_is_TDI(ehci))
		i = class_device_create_file(ehci_to_hcd(ehci)->self.class_dev,
				&class_device_attr_companion);
}

static inline void remove_companion_file(struct ehci_hcd *ehci)
{
	/* with integrated TT there is no companion! */
	if (!ehci_is_TDI(ehci))
		class_device_remove_file(ehci_to_hcd(ehci)->self.class_dev,
				&class_device_attr_companion);
}


/*-------------------------------------------------------------------------*/

static int check_reset_complete (
@@ -504,6 +601,16 @@ static int ehci_hub_control (
					ehci_readl(ehci, status_reg));
		}

		/* 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);
		}

		/*
		 * Even if OWNER is set, there's no harm letting khubd
		 * see the wPortStatus values (they should all be 0 except
+5 −1
Original line number Diff line number Diff line
@@ -74,7 +74,11 @@ struct ehci_hcd { /* one per controller */

	/* per root hub port */
	unsigned long		reset_done [EHCI_MAX_ROOT_PORTS];
	unsigned long		bus_suspended;
	/* bit vectors (one bit per port) */
	unsigned long		bus_suspended;		/* which ports were
			already suspended at the start of a bus suspend */
	unsigned long		companion_ports;	/* which ports are
			dedicated to the companion controller */

	/* per-HC memory pools (could be per-bus, but ...) */
	struct dma_pool		*qh_pool;	/* qh per active urb */