lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite for Android: free password hash cracker in your pocket
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Date:	Mon,  1 Apr 2013 07:53:00 +0000
From:	Yuan-Hsin Chen <yuanlmm@...il.com>
To:	gregkh@...uxfoundation.org, stern@...land.harvard.edu,
	sarah.a.sharp@...ux.intel.com, balbi@...com, Julia.Lawall@...6.fr
Cc:	linux-usb@...r.kernel.org, linux-kernel@...r.kernel.org,
	ratbert.chuang@...il.com, john453@...aday-tech.com,
	Yuan-Hsin Chen <yhchen@...aday-tech.com>
Subject: [PATCH v2] usb host: Faraday FUSBH200 HCD driver

FUSBH200 is an ehci-like controller with some differences.
First, register layout of FUSBH200 is incompatible with EHCI.
We use a quirk flag and modify struct ehci_regs with anonymous
union and struct to make sure driver of FUSBH200 and EHCI could
coexist. Furthermore, FUSBH200 is lack of siTDs which means iTDs
are used for both HS and FS ISO transfer.

Signed-off-by: Yuan-Hsin Chen <yhchen@...aday-tech.com>
---

Note that anonymous union and struct might not be supported
in earlier version of gcc

v2:
use ehci-platform.c
use anonymous union and struct
add is_fusbh200 to struct ehci_hcd

 drivers/usb/host/Kconfig         |    9 ++
 drivers/usb/host/ehci-fusbh200.c |  223 ++++++++++++++++++++++++++++++++++++++
 drivers/usb/host/ehci-hcd.c      |   18 +++-
 drivers/usb/host/ehci-hub.c      |   30 +++---
 drivers/usb/host/ehci-mem.c      |    3 +-
 drivers/usb/host/ehci-sched.c    |   33 ++++--
 drivers/usb/host/ehci.h          |   27 +++++-
 include/linux/usb/ehci_def.h     |   58 +++++++---
 8 files changed, 351 insertions(+), 50 deletions(-)
 create mode 100644 drivers/usb/host/ehci-fusbh200.c

diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
index 3f1431d..42b8768 100644
--- a/drivers/usb/host/Kconfig
+++ b/drivers/usb/host/Kconfig
@@ -157,6 +157,15 @@ config USB_EHCI_HCD_OMAP
 	  Enables support for the on-chip EHCI controller on
 	  OMAP3 and later chips.
 
+config USB_EHCI_HCD_FUSBH200
+	bool "Faraday FUSBH200 HCD support"
+	depends on USB_EHCI_HCD && ARCH_FARADAY
+	select USB_EHCI_ROOT_HUB_TT
+	default y
+	---help---
+	  The USB HCD Driver of Faraday FUSBH200 is designed to
+	  meet USB2.0 EHCI specification with minor modification
+
 config USB_EHCI_MSM
 	bool "Support for MSM on-chip EHCI USB controller"
 	depends on USB_EHCI_HCD && ARCH_MSM
diff --git a/drivers/usb/host/ehci-fusbh200.c b/drivers/usb/host/ehci-fusbh200.c
new file mode 100644
index 0000000..1d5fd14
--- /dev/null
+++ b/drivers/usb/host/ehci-fusbh200.c
@@ -0,0 +1,223 @@
+/*
+ * ehci-fusbh200.c - driver for Faraday USB2.0 Host FUSBH200
+ *
+ * Copyright (c) 2013 Yuan-Hsin Chen <yhchen@...aday-tech>
+ * Copyright (c) 2013 Po-Yu Chuang <ratbert.chuang@...il.com>
+ * Copyright (c) 2010-2012 Feng-Hsin Chiang <john453@...aday-tech>
+ *
+ * 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.
+ */
+
+#include <linux/platform_device.h>
+
+static const struct hc_driver ehci_fusbh200_hc_driver = {
+	.description 		= hcd_name,
+	.product_desc 		= "Faraday USB2.0 Host Controller",
+	.hcd_priv_size 		= sizeof(struct ehci_hcd),
+
+	/*
+	 * generic hardware linkage
+	 */
+	.irq 			= ehci_irq,
+	.flags 			= HCD_MEMORY | HCD_USB2,
+
+	/*
+	 * basic lifecycle operations
+	 */
+	.reset 			= ehci_init,
+	.start 			= ehci_run,
+	.stop 			= ehci_stop,
+	.shutdown 		= ehci_shutdown,
+
+	/*
+	 * managing i/o requests and associated device resources
+	 */
+	.urb_enqueue 		= ehci_urb_enqueue,
+	.urb_dequeue 		= ehci_urb_dequeue,
+	.endpoint_disable 	= ehci_endpoint_disable,
+	.endpoint_reset 	= ehci_endpoint_reset,
+
+	/*
+	 * scheduling support
+	 */
+	.get_frame_number 	= ehci_get_frame,
+
+	/*
+	 * root hub support
+	 */
+	.hub_status_data 	= ehci_hub_status_data,
+	.hub_control 		= ehci_hub_control,
+	.bus_suspend 		= ehci_bus_suspend,
+	.bus_resume 		= ehci_bus_resume,
+
+	.relinquish_port 	= ehci_relinquish_port,
+	.port_handed_over 	= ehci_port_handed_over,
+
+	.clear_tt_buffer_complete = ehci_clear_tt_buffer_complete,
+};
+
+void fusbh200_init(struct ehci_hcd *ehci)
+{
+	u32 reg;
+
+	reg = ehci_readl(ehci, &ehci->regs->bmcsr);
+	reg |= BMCSR_INT_POLARITY;
+	reg &= ~BMCSR_VBUS_OFF;
+	ehci_writel(ehci, reg, &ehci->regs->bmcsr);
+
+	reg = ehci_readl(ehci, &ehci->regs->bmier);
+	ehci_writel(ehci, reg | BMIER_OVC_EN | BMIER_VBUS_ERR_EN,
+		&ehci->regs->bmier);
+}
+
+/**
+ * ehci_hcd_fusbh200_probe - initialize faraday FUSBH200 HCDs
+ *
+ * Allocates basic resources for this USB host controller, and
+ * then invokes the start() method for the HCD associated with it
+ * through the hotplug entry's driver_data.
+ */
+static int ehci_hcd_fusbh200_probe(struct platform_device *pdev)
+{
+	struct device			*dev = &pdev->dev;
+	struct usb_hcd 			*hcd;
+	struct resource			*res;
+	int 				irq;
+	int 				retval = -ENODEV;
+	struct ehci_hcd 		*ehci;
+
+	if (usb_disabled())
+		return -ENODEV;
+
+	pdev->dev.power.power_state = PMSG_ON;
+
+	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+	if (!res) {
+		dev_err(dev,
+			"Found HC with no IRQ. Check %s setup!\n",
+			dev_name(dev));
+		return -ENODEV;
+	}
+
+	irq = res->start;
+
+	hcd = usb_create_hcd(&ehci_fusbh200_hc_driver, dev,
+			dev_name(dev));
+	if (!hcd) {
+		dev_err(dev, "failed to create hcd with err %d\n", retval);
+		retval = -ENOMEM;
+		goto fail_create_hcd;
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(dev,
+			"Found HC with no register addr. Check %s setup!\n",
+			dev_name(dev));
+		retval = -ENODEV;
+		goto fail_request_resource;
+	}
+
+	hcd->rsrc_start = res->start;
+	hcd->rsrc_len = resource_size(res);
+	hcd->has_tt = 1;
+
+	if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len,
+				ehci_fusbh200_hc_driver.description)) {
+		dev_dbg(dev, "controller already in use\n");
+		retval = -EBUSY;
+		goto fail_request_resource;
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_IO, 0);
+	if (!res) {
+		dev_err(dev,
+			"Found HC with no register addr. Check %s setup!\n",
+			dev_name(dev));
+		retval = -ENODEV;
+		goto fail_request_resource;
+	}
+
+	hcd->regs = ioremap_nocache(res->start, resource_size(res));
+	if (hcd->regs == NULL) {
+		dev_dbg(dev, "error mapping memory\n");
+		retval = -EFAULT;
+		goto fail_ioremap;
+	}
+
+	ehci = hcd_to_ehci(hcd);
+
+	ehci->caps = hcd->regs;
+	ehci->is_fusbh200 = 1;
+
+	retval = ehci_setup(hcd);
+	if (retval)
+		return retval;
+
+	fusbh200_init(ehci);
+
+	ehci_port_power(ehci, 0);
+
+	retval = usb_add_hcd(hcd, irq, IRQF_SHARED);
+	if (retval) {
+		dev_err(dev, "failed to add hcd with err %d\n", retval);
+		goto fail_add_hcd;
+	}
+
+	return retval;
+
+fail_add_hcd:
+	iounmap(hcd->regs);
+fail_ioremap:
+	release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
+fail_request_resource:
+	usb_put_hcd(hcd);
+fail_create_hcd:
+	dev_err(dev, "init %s fail, %d\n", dev_name(dev), retval);
+	return retval;
+}
+
+/**
+ * ehci_hcd_fusbh200_remove - shutdown processing for EHCI HCDs
+ * @dev: USB Host Controller being removed
+ *
+ * Reverses the effect of fotg2xx_usb_hcd_probe(), first invoking
+ * the HCD's stop() method.  It is always called from a thread
+ * context, normally "rmmod", "apmd", or something similar.
+ */
+int ehci_hcd_fusbh200_remove(struct platform_device *pdev)
+{
+	struct device *dev	= &pdev->dev;
+	struct usb_hcd *hcd	= dev_get_drvdata(dev);
+
+	if (!hcd)
+		return 0;
+
+	usb_remove_hcd(hcd);
+	iounmap(hcd->regs);
+	release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
+	usb_put_hcd(hcd);
+	platform_set_drvdata(pdev, NULL);
+
+	return 0;
+}
+
+struct platform_driver ehci_hcd_fusbh200_driver = {
+	.driver = {
+		.name   = "fusbh200",
+	},
+	.probe  = ehci_hcd_fusbh200_probe,
+	.remove = ehci_hcd_fusbh200_remove,
+};
diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
index 6bf6c42..ce87c01 100644
--- a/drivers/usb/host/ehci-hcd.c
+++ b/drivers/usb/host/ehci-hcd.c
@@ -247,7 +247,7 @@ static int ehci_reset (struct ehci_hcd *ehci)
 	if (retval)
 		return retval;
 
-	if (ehci_is_TDI(ehci))
+	if (ehci_is_TDI(ehci) && !ehci_is_fusbh200(ehci))
 		tdi_reset (ehci);
 
 	if (ehci->debug)
@@ -326,11 +326,15 @@ static void ehci_silence_controller(struct ehci_hcd *ehci)
 	ehci->rh_state = EHCI_RH_HALTED;
 	ehci_turn_off_all_ports(ehci);
 
+	if (ehci_is_fusbh200(ehci))
+		goto out;
+
 	/* make BIOS/etc use companion controller during reboot */
 	ehci_writel(ehci, 0, &ehci->regs->configured_flag);
 
 	/* unblock posted writes */
 	ehci_readl(ehci, &ehci->regs->configured_flag);
+out:
 	spin_unlock_irq(&ehci->lock);
 }
 
@@ -634,7 +638,8 @@ static int ehci_run (struct usb_hcd *hcd)
 	 */
 	down_write(&ehci_cf_port_reset_rwsem);
 	ehci->rh_state = EHCI_RH_RUNNING;
-	ehci_writel(ehci, FLAG_CF, &ehci->regs->configured_flag);
+	if (!ehci_is_fusbh200(ehci))
+		ehci_writel(ehci, FLAG_CF, &ehci->regs->configured_flag);
 	ehci_readl(ehci, &ehci->regs->command);	/* unblock posted writes */
 	msleep(5);
 	up_write(&ehci_cf_port_reset_rwsem);
@@ -684,7 +689,7 @@ static int ehci_setup(struct usb_hcd *hcd)
 	if (retval)
 		return retval;
 
-	if (ehci_is_TDI(ehci))
+	if (ehci_is_TDI(ehci) && !ehci_is_fusbh200(ehci))
 		tdi_reset(ehci);
 
 	ehci_reset(ehci);
@@ -887,7 +892,7 @@ static int ehci_urb_enqueue (
 		return intr_submit(ehci, urb, &qtd_list, mem_flags);
 
 	case PIPE_ISOCHRONOUS:
-		if (urb->dev->speed == USB_SPEED_HIGH)
+		if (urb->dev->speed == USB_SPEED_HIGH || ehci_is_fusbh200(ehci))
 			return itd_submit (ehci, urb, mem_flags);
 		else
 			return sitd_submit (ehci, urb, mem_flags);
@@ -1339,6 +1344,11 @@ MODULE_LICENSE ("GPL");
 #define PLATFORM_DRIVER		ehci_platform_driver
 #endif
 
+#ifdef CONFIG_USB_EHCI_HCD_FUSBH200
+#include "ehci-fusbh200.c"
+#define PLATFORM_DRIVER		ehci_hcd_fusbh200_driver
+#endif
+
 #if !defined(PCI_DRIVER) && !defined(PLATFORM_DRIVER) && \
     !defined(PS3_SYSTEM_BUS_DRIVER) && !defined(OF_PLATFORM_DRIVER) && \
     !defined(XILINX_OF_PLATFORM_DRIVER)
diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c
index 914ce93..c374d6a 100644
--- a/drivers/usb/host/ehci-hub.c
+++ b/drivers/usb/host/ehci-hub.c
@@ -63,7 +63,7 @@ static void ehci_handover_companion_ports(struct ehci_hcd *ehci)
 	port = HCS_N_PORTS(ehci->hcs_params);
 	while (port--) {
 		if (test_bit(port, &ehci->owned_ports)) {
-			reg = &ehci->regs->port_status[port];
+			reg = ehci_portsc_addr(ehci, port);
 			status = ehci_readl(ehci, reg) & ~PORT_RWC_BITS;
 
 			/* Port already owned by companion? */
@@ -100,7 +100,7 @@ static void ehci_handover_companion_ports(struct ehci_hcd *ehci)
 			 * but if something went wrong the port must not
 			 * remain enabled.
 			 */
-			reg = &ehci->regs->port_status[port];
+			reg = ehci_portsc_addr(ehci, port)
 			status = ehci_readl(ehci, reg) & ~PORT_RWC_BITS;
 			if (status & PORT_OWNER)
 				ehci_writel(ehci, status | PORT_CSC, reg);
@@ -131,7 +131,7 @@ static int ehci_port_change(struct ehci_hcd *ehci)
 	 */
 
 	while (i--)
-		if (ehci_readl(ehci, &ehci->regs->port_status[i]) & PORT_CSC)
+		if (ehci_readl(ehci, ehci_portsc_addr(ehci, i)) & PORT_CSC)
 			return 1;
 
 	return 0;
@@ -169,7 +169,7 @@ static void ehci_adjust_port_wakeup_flags(struct ehci_hcd *ehci,
 
 	port = HCS_N_PORTS(ehci->hcs_params);
 	while (port--) {
-		u32 __iomem	*reg = &ehci->regs->port_status[port];
+		u32 __iomem	*reg = ehci_portsc_addr(ehci, port);
 		u32		t1 = ehci_readl(ehci, reg) & ~PORT_RWC_BITS;
 		u32		t2 = t1 & ~PORT_WAKE_BITS;
 
@@ -247,7 +247,7 @@ static int ehci_bus_suspend (struct usb_hcd *hcd)
 	changed = 0;
 	port = HCS_N_PORTS(ehci->hcs_params);
 	while (port--) {
-		u32 __iomem	*reg = &ehci->regs->port_status [port];
+		u32 __iomem	*reg = ehci_portsc_addr(ehci, port);
 		u32		t1 = ehci_readl(ehci, reg) & ~PORT_RWC_BITS;
 		u32		t2 = t1 & ~PORT_WAKE_BITS;
 
@@ -415,14 +415,14 @@ static int ehci_bus_resume (struct usb_hcd *hcd)
 	/* manually resume the ports we suspended during bus_suspend() */
 	i = HCS_N_PORTS (ehci->hcs_params);
 	while (i--) {
-		temp = ehci_readl(ehci, &ehci->regs->port_status [i]);
+		temp = ehci_readl(ehci, ehci_portsc_addr(ehci, i));
 		temp &= ~(PORT_RWC_BITS | PORT_WAKE_BITS);
 		if (test_bit(i, &ehci->bus_suspended) &&
 				(temp & PORT_SUSPEND)) {
 			temp |= PORT_RESUME;
 			set_bit(i, &resume_needed);
 		}
-		ehci_writel(ehci, temp, &ehci->regs->port_status [i]);
+		ehci_writel(ehci, temp, ehci_portsc_addr(ehci, i));
 	}
 
 	/* msleep for 20ms only if code is trying to resume port */
@@ -436,10 +436,10 @@ static int ehci_bus_resume (struct usb_hcd *hcd)
 
 	i = HCS_N_PORTS (ehci->hcs_params);
 	while (i--) {
-		temp = ehci_readl(ehci, &ehci->regs->port_status [i]);
+		temp = ehci_readl(ehci, ehci_portsc_addr(ehci, i));
 		if (test_bit(i, &resume_needed)) {
 			temp &= ~(PORT_RWC_BITS | PORT_RESUME);
-			ehci_writel(ehci, temp, &ehci->regs->port_status [i]);
+			ehci_writel(ehci, temp, ehci_portsc_addr(ehci, i));
 			ehci_vdbg (ehci, "resumed port %d\n", i + 1);
 		}
 	}
@@ -482,7 +482,7 @@ static void set_owner(struct ehci_hcd *ehci, int portnum, int new_owner)
 	u32			port_status;
 	int 			try;
 
-	status_reg = &ehci->regs->port_status[portnum];
+	status_reg = ehci_portsc_addr(ehci, portnum);
 
 	/*
 	 * The controller won't set the OWNER bit if the port is
@@ -604,7 +604,7 @@ ehci_hub_status_data (struct usb_hcd *hcd, char *buf)
 		/* leverage per-port change bits feature */
 		if (ehci->has_ppcd && !(ppcd & (1 << i)))
 			continue;
-		temp = ehci_readl(ehci, &ehci->regs->port_status [i]);
+		temp = ehci_readl(ehci, ehci_portsc_addr(ehci, i));
 
 		/*
 		 * Return status information even for ports with OWNER set.
@@ -675,8 +675,7 @@ static int ehci_hub_control (
 ) {
 	struct ehci_hcd	*ehci = hcd_to_ehci (hcd);
 	int		ports = HCS_N_PORTS (ehci->hcs_params);
-	u32 __iomem	*status_reg = &ehci->regs->port_status[
-				(wIndex & 0xff) - 1];
+	u32 __iomem	*status_reg = ehci_portsc_addr(ehci, (wIndex & 0xff) - 1);
 	u32 __iomem	*hostpc_reg = &ehci->regs->hostpc[(wIndex & 0xff) - 1];
 	u32		temp, temp1, status;
 	unsigned long	flags;
@@ -1054,8 +1053,7 @@ static int ehci_hub_control (
 
 			/* Put all enabled ports into suspend */
 			while (ports--) {
-				u32 __iomem *sreg =
-						&ehci->regs->port_status[ports];
+				u32 __iomem *sreg = ehci_portsc_addr(ehci, ports);
 
 				temp = ehci_readl(ehci, sreg) & ~PORT_RWC_BITS;
 				if (temp & PORT_PE)
@@ -1106,6 +1104,6 @@ static int __maybe_unused ehci_port_handed_over(struct usb_hcd *hcd,
 
 	if (ehci_is_TDI(ehci))
 		return 0;
-	reg = &ehci->regs->port_status[portnum - 1];
+	reg = ehci_portsc_addr(ehci, portnum - 1);
 	return ehci_readl(ehci, reg) & PORT_OWNER;
 }
diff --git a/drivers/usb/host/ehci-mem.c b/drivers/usb/host/ehci-mem.c
index ef2c3a1..fd7e298 100644
--- a/drivers/usb/host/ehci-mem.c
+++ b/drivers/usb/host/ehci-mem.c
@@ -159,6 +159,7 @@ static void ehci_mem_cleanup (struct ehci_hcd *ehci)
 static int ehci_mem_init (struct ehci_hcd *ehci, gfp_t flags)
 {
 	int i;
+	unsigned int itd_align = ehci_is_fusbh200(ehci) ? 64 : 32;
 
 	/* QTDs for control/bulk/intr transfers */
 	ehci->qtd_pool = dma_pool_create ("ehci_qtd",
@@ -188,7 +189,7 @@ static int ehci_mem_init (struct ehci_hcd *ehci, gfp_t flags)
 	ehci->itd_pool = dma_pool_create ("ehci_itd",
 			ehci_to_hcd(ehci)->self.controller,
 			sizeof (struct ehci_itd),
-			32 /* byte alignment (for hw parts) */,
+			itd_align /* byte alignment (for hw parts) */,
 			4096 /* can't cross 4K */);
 	if (!ehci->itd_pool) {
 		goto fail;
diff --git a/drivers/usb/host/ehci-sched.c b/drivers/usb/host/ehci-sched.c
index 7cf3da7..29b7c40 100644
--- a/drivers/usb/host/ehci-sched.c
+++ b/drivers/usb/host/ehci-sched.c
@@ -1005,11 +1005,9 @@ iso_stream_init (
 	}
 
 	/* knows about ITD vs SITD */
-	if (dev->speed == USB_SPEED_HIGH) {
+	if (dev->speed == USB_SPEED_HIGH || ehci_is_fusbh200(ehci)) {
 		unsigned multi = hb_mult(maxp);
 
-		stream->highspeed = 1;
-
 		maxp = max_packet(maxp);
 		buf1 |= maxp;
 		maxp *= multi;
@@ -1021,10 +1019,17 @@ iso_stream_init (
 		/* usbfs wants to report the average usecs per frame tied up
 		 * when transfers on this endpoint are scheduled ...
 		 */
-		stream->usecs = HS_USECS_ISO (maxp);
+		if (dev->speed == USB_SPEED_FULL) {
+			interval <<= 3;
+			stream->usecs = NS_TO_US(usb_calc_bus_time(dev->speed,
+					is_input, 1, maxp));
+			stream->usecs /= 8;
+		} else {
+			stream->highspeed = 1;
+			stream->usecs = HS_USECS_ISO (maxp);
+		}
 		bandwidth = stream->usecs * 8;
 		bandwidth /= interval;
-
 	} else {
 		u32		addr;
 		int		think_time;
@@ -1377,7 +1382,7 @@ iso_stream_schedule (
 
 	period = urb->interval;
 	span = sched->span;
-	if (!stream->highspeed) {
+	if (!stream->highspeed && !ehci_is_fusbh200(ehci)) {
 		period <<= 3;
 		span <<= 3;
 	}
@@ -1449,7 +1454,7 @@ iso_stream_schedule (
 		do {
 			start--;
 			/* check schedule: enough space? */
-			if (stream->highspeed) {
+			if (stream->highspeed || ehci_is_fusbh200(ehci)) {
 				if (itd_slot_ok(ehci, mod, start,
 						stream->usecs, period))
 					done = 1;
@@ -1699,12 +1704,12 @@ static bool itd_complete(struct ehci_hcd *ehci, struct ehci_itd *itd)
 
 			/* HC need not update length with this error */
 			if (!(t & EHCI_ISOC_BABBLE)) {
-				desc->actual_length = EHCI_ITD_LENGTH(t);
+				desc->actual_length = ehci_itdlen(ehci, urb, desc, t);
 				urb->actual_length += desc->actual_length;
 			}
 		} else if (likely ((t & EHCI_ISOC_ACTIVE) == 0)) {
 			desc->status = 0;
-			desc->actual_length = EHCI_ITD_LENGTH(t);
+			desc->actual_length = ehci_itdlen(ehci, urb, desc, t);
 			urb->actual_length += desc->actual_length;
 		} else {
 			/* URB was too late */
@@ -1777,9 +1782,13 @@ static int itd_submit (struct ehci_hcd *ehci, struct urb *urb,
 		return -ENOMEM;
 	}
 	if (unlikely (urb->interval != stream->interval)) {
-		ehci_dbg (ehci, "can't change iso interval %d --> %d\n",
-			stream->interval, urb->interval);
-		goto done;
+		if (!ehci_is_fusbh200(ehci) ||
+		    (ehci_is_fusbh200(ehci) &&
+		     ehci_port_speed(ehci, 0) == USB_PORT_STAT_HIGH_SPEED)) {
+			ehci_dbg (ehci, "can't change iso interval %d --> %d\n",
+				stream->interval, urb->interval);
+			goto done;
+		}
 	}
 
 #ifdef EHCI_URB_TRACE
diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h
index da07d98..fa9028d 100644
--- a/drivers/usb/host/ehci.h
+++ b/drivers/usb/host/ehci.h
@@ -197,6 +197,7 @@ struct ehci_hcd {			/* one per controller */
 	unsigned		use_dummy_qh:1;	/* AMD Frame List table quirk*/
 	unsigned		has_synopsys_hc_bug:1; /* Synopsys HC */
 	unsigned		frame_index_bug:1; /* MosChip (AKA NetMos) */
+	unsigned		is_fusbh200:1; /* Faraday FUSBH200 */
 
 	/* required for usb32 quirk */
 	#define OHCI_CTRL_HCFS          (3 << 6)
@@ -578,6 +579,8 @@ struct ehci_fstn {
 
 /*-------------------------------------------------------------------------*/
 
+#define	ehci_is_fusbh200(e)		((e)->is_fusbh200)
+
 #ifdef CONFIG_USB_EHCI_ROOT_HUB_TT
 
 /*
@@ -589,12 +592,23 @@ struct ehci_fstn {
 
 #define	ehci_is_TDI(e)			(ehci_to_hcd(e)->has_tt)
 
+static inline unsigned int
+ehci_get_speed(struct ehci_hcd *ehci, unsigned int portsc)
+{
+	if (ehci_is_fusbh200(ehci))
+		return (readl(&ehci->regs->bmcsr)
+			& BMCSR_HOST_SPD_TYP) >> 9;
+	else
+		return (portsc >> (ehci->has_hostpc ?
+			25 : 26)) & 3;
+}
+
 /* Returns the speed of a device attached to a port on the root hub. */
 static inline unsigned int
 ehci_port_speed(struct ehci_hcd *ehci, unsigned int portsc)
 {
 	if (ehci_is_TDI(ehci)) {
-		switch ((portsc >> (ehci->has_hostpc ? 25 : 26)) & 3) {
+		switch (ehci_get_speed(ehci, portsc)) {
 		case 0:
 			return 0;
 		case 1:
@@ -776,6 +790,17 @@ static inline unsigned ehci_read_frame_index(struct ehci_hcd *ehci)
 
 #endif
 
+#define ehci_portsc_addr(ehci, port) ({			\
+	ehci_is_fusbh200(ehci) ?			\
+	&(ehci)->regs->fusbh200_portsc :		\
+	&(ehci)->regs->port_status[(port)];		\
+})
+
+#define ehci_itdlen(ehci, urb, desc, t) ({			\
+	ehci_is_fusbh200(ehci) && usb_pipein((urb)->pipe) ?	\
+	(desc)->length - EHCI_ITD_LENGTH(t) :			\
+	EHCI_ITD_LENGTH(t);					\
+})
 /*-------------------------------------------------------------------------*/
 
 #ifndef DEBUG
diff --git a/include/linux/usb/ehci_def.h b/include/linux/usb/ehci_def.h
index daec99a..41f9c55 100644
--- a/include/linux/usb/ehci_def.h
+++ b/include/linux/usb/ehci_def.h
@@ -111,20 +111,22 @@ struct ehci_regs {
 	/* ASYNCLISTADDR: offset 0x18 */
 	u32		async_next;	/* address of next async queue head */
 
-	u32		reserved1[2];
+	union {
+		struct {
+			u32	reserved1[2];
 
-	/* TXFILLTUNING: offset 0x24 */
-	u32		txfill_tuning;	/* TX FIFO Tuning register */
+			/* TXFILLTUNING: offset 0x24 */
+			u32	txfill_tuning;	/* TX FIFO Tuning register */
 #define TXFIFO_DEFAULT	(8<<16)		/* FIFO burst threshold 8 */
 
-	u32		reserved2[6];
+			u32	reserved2[6];
 
-	/* CONFIGFLAG: offset 0x40 */
-	u32		configured_flag;
+			/* CONFIGFLAG: offset 0x40 */
+			u32	configured_flag;
 #define FLAG_CF		(1<<0)		/* true: we'll support "high speed" */
 
-	/* PORTSC: offset 0x44 */
-	u32		port_status[0];	/* up to N_PORTS */
+			/* PORTSC: offset 0x44 */
+			u32	port_status[0];	/* up to N_PORTS */
 /* EHCI 1.1 addendum */
 #define PORTSC_SUSPEND_STS_ACK 0
 #define PORTSC_SUSPEND_STS_NYET 1
@@ -162,32 +164,56 @@ struct ehci_regs {
 #define PORT_CONNECT	(1<<0)		/* device connected */
 #define PORT_RWC_BITS   (PORT_CSC | PORT_PEC | PORT_OCC)
 
-	u32		reserved3[9];
+			u32	reserved3[9];
 
 	/* USBMODE: offset 0x68 */
-	u32		usbmode;	/* USB Device mode */
+			u32	usbmode;	/* USB Device mode */
 #define USBMODE_SDIS	(1<<3)		/* Stream disable */
 #define USBMODE_BE	(1<<2)		/* BE/LE endianness select */
 #define USBMODE_CM_HC	(3<<0)		/* host controller mode */
 #define USBMODE_CM_IDLE	(0<<0)		/* idle state */
 
-	u32		reserved4[6];
+			u32	reserved4[6];
 
 /* Moorestown has some non-standard registers, partially due to the fact that
  * its EHCI controller has both TT and LPM support. HOSTPCx are extensions to
  * PORTSCx
  */
-	/* HOSTPC: offset 0x84 */
-	u32		hostpc[1];	/* HOSTPC extension */
+			/* HOSTPC: offset 0x84 */
+			u32	hostpc[1];	/* HOSTPC extension */
 #define HOSTPC_PHCD	(1<<22)		/* Phy clock disable */
 #define HOSTPC_PSPD	(3<<25)		/* Port speed detection */
 
-	u32		reserved5[16];
+			u32	reserved5[16];
 
-	/* USBMODE_EX: offset 0xc8 */
-	u32		usbmode_ex;	/* USB Device mode extension */
+			/* USBMODE_EX: offset 0xc8 */
+			u32	usbmode_ex;	/* USB Device mode extension */
 #define USBMODE_EX_VBPS	(1<<5)		/* VBus Power Select On */
 #define USBMODE_EX_HC	(3<<0)		/* host controller mode */
+		};
+		struct {
+			u32	reserved6;
+
+			/* PORTSC: offset 0x20 */
+			u32	fusbh200_portsc;
+
+			u32	reserved7[3];
+
+			/* BMCSR: offset 0x30 */
+			u32	bmcsr; /* Bus Moniter Control/Status Register */
+#define BMCSR_HOST_SPD_TYP	(3<<9)
+#define BMCSR_VBUS_OFF		(1<<4)
+#define BMCSR_INT_POLARITY	(1<<3)
+
+			/* BMISR: offset 0x34 */
+			u32	bmisr; /* Bus Moniter Interrupt Status Register*/
+
+			/* BMIER: offset 0x38 */
+			u32	bmier; /* Bus Moniter Interrupt Enable Register */
+#define BMIER_OVC_EN		(1<<1)
+#define BMIER_VBUS_ERR_EN	(1<<0)
+		};
+	};
 };
 
 /* Appendix C, Debug port ... intended for use with special "debug devices"
-- 
1.7.4.1

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ