[PATCH v4 23/24] usb: chipidea: add host role

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This adds EHCI host support to the chipidea driver. We want it to be
part of the hdrc driver and not a standalone (sub-)driver module, as
the structure of ehci-hcd.c suggests, so for chipidea controller we
hack it to not provide platform-related code, but only the ehci hcd.

The ehci-platform driver won't work for us here too, because the
controller uses the same registers for both device and host mode and
also otg-related bits, so it's not really possible to put ehci registers
into a separate resource.

This is not a pretty solution, but the alternative is exporting symbols
from the chipidea driver to a ehci-chipidea driver and doing all the
module refcounting.

Signed-off-by: Alexander Shishkin <alexander.shishkin@xxxxxxxxxxxxxxx>
Cc: Alan Stern <stern@xxxxxxxxxxxxxxxxxxx>
---
 drivers/usb/chipidea/Kconfig  |    6 ++
 drivers/usb/chipidea/Makefile |    1 +
 drivers/usb/chipidea/ci.h     |    9 ++-
 drivers/usb/chipidea/core.c   |   15 +++-
 drivers/usb/chipidea/host.c   |  170 +++++++++++++++++++++++++++++++++++++++++
 drivers/usb/chipidea/host.h   |   17 +++++
 drivers/usb/chipidea/udc.c    |    8 +-
 drivers/usb/host/ehci-hcd.c   |    8 ++
 8 files changed, 225 insertions(+), 9 deletions(-)
 create mode 100644 drivers/usb/chipidea/host.c
 create mode 100644 drivers/usb/chipidea/host.h

diff --git a/drivers/usb/chipidea/Kconfig b/drivers/usb/chipidea/Kconfig
index 553c197..8855000 100644
--- a/drivers/usb/chipidea/Kconfig
+++ b/drivers/usb/chipidea/Kconfig
@@ -18,6 +18,12 @@ config USB_CHIPIDEA_UDC
 	  Say Y here to enable device controller functionality of the
 	  ChipIdea driver.
 
+config USB_CHIPIDEA_HOST
+	bool "ChipIdea device controller"
+	help
+	  Say Y here to enable host controller functionality of the
+	  ChipIdea driver.
+
 config USB_CHIPIDEA_DEBUG
 	bool "ChipIdea driver debug"
 	help
diff --git a/drivers/usb/chipidea/Makefile b/drivers/usb/chipidea/Makefile
index a8279aa..cc34937 100644
--- a/drivers/usb/chipidea/Makefile
+++ b/drivers/usb/chipidea/Makefile
@@ -2,6 +2,7 @@ obj-$(CONFIG_USB_CHIPIDEA)		+= ci_hdrc.o
 
 ci_hdrc-y				:= core.o
 ci_hdrc-$(CONFIG_USB_CHIPIDEA_UDC)	+= udc.o
+ci_hdrc-$(CONFIG_USB_CHIPIDEA_HOST)	+= host.o
 ci_hdrc-$(CONFIG_USB_CHIPIDEA_DEBUG)	+= debug.o
 
 ifneq ($(CONFIG_PCI),)
diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h
index 1ae25eb..e9774b6 100644
--- a/drivers/usb/chipidea/ci.h
+++ b/drivers/usb/chipidea/ci.h
@@ -17,6 +17,8 @@
 #define __DRIVERS_USB_CHIPIDEA_CI_H
 
 #include <linux/list.h>
+#include <linux/interrupt.h>
+#include <linux/usb.h>
 #include <linux/usb/gadget.h>
 #include "udc.h"
 
@@ -49,6 +51,7 @@ struct ci_role_driver {
 /**
  * struct hw_bank - hardware register mapping representation
  * @lpm: set if the device is LPM capable
+ * @phys: physical address of the controller's registers
  * @abs: absolute address of the beginning of register window
  * @cap: capability registers
  * @op: operational registers
@@ -57,6 +60,7 @@ struct ci_role_driver {
  */
 struct hw_bank {
 	unsigned	lpm;
+	resource_size_t	phys;
 	void __iomem	*abs;
 	void __iomem	*cap;
 	void __iomem	*op;
@@ -92,6 +96,7 @@ struct hw_bank {
  * @udc_driver: platform specific information supplied by parent device
  * @vbus_active: is VBUS active
  * @transceiver: pointer to USB PHY, if any
+ * @hcd: pointer to usb_hcd for ehci host driver
  */
 struct ci13xxx {
 	struct device			*dev;
@@ -123,6 +128,7 @@ struct ci13xxx {
 	struct ci13xxx_udc_driver	*udc_driver;
 	int				vbus_active;
 	struct usb_phy			*transceiver;
+	struct usb_hcd			*hcd;
 };
 
 static inline struct ci_role_driver *ci_role(struct ci13xxx *ci)
@@ -197,6 +203,7 @@ enum ci13xxx_regs {
 /* DCCPARAMS */
 #define DCCPARAMS_DEN         (0x1F << 0)
 #define DCCPARAMS_DC          BIT(7)
+#define DCCPARAMS_HC          BIT(8)
 
 /* TESTMODE */
 #define TESTMODE_FORCE        BIT(0)
@@ -338,7 +345,7 @@ static inline u32 hw_test_and_write(struct ci13xxx *udc, enum ci13xxx_regs reg,
 	return (val & mask) >> ffs_nr(mask);
 }
 
-int hw_device_reset(struct ci13xxx *ci);
+int hw_device_reset(struct ci13xxx *ci, u32 mode);
 
 int hw_port_test_set(struct ci13xxx *udc, u8 mode);
 
diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c
index 4f64711..fce3a35 100644
--- a/drivers/usb/chipidea/core.c
+++ b/drivers/usb/chipidea/core.c
@@ -69,6 +69,7 @@
 
 #include "ci.h"
 #include "udc.h"
+#include "host.h"
 #include "debug.h"
 
 /* Controller register map */
@@ -187,7 +188,7 @@ static int hw_device_init(struct ci13xxx *ci, void __iomem *base)
   *
  * This function returns an error code
  */
-int hw_device_reset(struct ci13xxx *ci)
+int hw_device_reset(struct ci13xxx *ci, u32 mode)
 {
 	/* should flush & stop before reset */
 	hw_write(ci, OP_ENDPTFLUSH, ~0, ~0);
@@ -207,12 +208,12 @@ int hw_device_reset(struct ci13xxx *ci)
 
 	/* USBMODE should be configured step by step */
 	hw_write(ci, OP_USBMODE, USBMODE_CM, USBMODE_CM_IDLE);
-	hw_write(ci, OP_USBMODE, USBMODE_CM, USBMODE_CM_DEVICE);
+	hw_write(ci, OP_USBMODE, USBMODE_CM, mode);
 	/* HW >= 2.3 */
 	hw_write(ci, OP_USBMODE, USBMODE_SLOM, USBMODE_SLOM);
 
-	if (hw_read(ci, OP_USBMODE, USBMODE_CM) != USBMODE_CM_DEVICE) {
-		pr_err("cannot enter in device mode");
+	if (hw_read(ci, OP_USBMODE, USBMODE_CM) != mode) {
+		pr_err("cannot enter in %s mode", ci_role(ci)->name);
 		pr_err("lpm = %i", ci->hw_bank.lpm);
 		return -ENODEV;
 	}
@@ -343,6 +344,8 @@ static int __devinit ci_hdrc_probe(struct platform_device *pdev)
 		return -ENODEV;
 	}
 
+	ci->hw_bank.phys = res->start;
+
 	ci->irq = platform_get_irq(pdev, 0);
 	if (ci->irq < 0) {
 		dev_err(dev, "missing IRQ\n");
@@ -357,6 +360,10 @@ static int __devinit ci_hdrc_probe(struct platform_device *pdev)
 	}
 
 	/* initialize role(s) before the interrupt is requested */
+	ret = ci_hdrc_host_init(ci);
+	if (ret)
+		dev_info(dev, "doesn't support host\n");
+
 	ret = ci_hdrc_gadget_init(ci);
 	if (ret)
 		dev_info(dev, "doesn't support gadget\n");
diff --git a/drivers/usb/chipidea/host.c b/drivers/usb/chipidea/host.c
new file mode 100644
index 0000000..126753c
--- /dev/null
+++ b/drivers/usb/chipidea/host.c
@@ -0,0 +1,170 @@
+/*
+ * host.c - ChipIdea USB host controller driver
+ *
+ * Copyright (c) 2012 Intel Corporation
+ *
+ * Author: Alexander Shishkin
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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/kernel.h>
+#include <linux/usb.h>
+#include <linux/usb/hcd.h>
+#include <linux/usb/chipidea.h>
+
+#define CHIPIDEA_EHCI
+#include "../host/ehci-hcd.c"
+
+#include "ci.h"
+#include "host.h"
+
+static int ci_ehci_setup(struct usb_hcd *hcd)
+{
+	struct ehci_hcd *ehci = hcd_to_ehci(hcd);
+	int ret;
+
+	ret = ehci_halt(ehci);
+	if (ret)
+		return ret;
+
+	ret = ehci_init(hcd);
+	if (ret)
+		return ret;
+
+	ehci_reset(ehci);
+
+	return ret;
+}
+
+static const struct hc_driver ci_ehci_hc_driver = {
+	.description	= "ehci_hcd",
+	.product_desc	= "ChipIdea HDRC EHCI",
+	.hcd_priv_size	= sizeof(struct ehci_hcd),
+
+	/*
+	 * generic hardware linkage
+	 */
+	.irq	= ehci_irq,
+	.flags	= HCD_MEMORY | HCD_USB2,
+
+	/*
+	 * basic lifecycle operations
+	 */
+	.reset		= ci_ehci_setup,
+	.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,
+};
+
+static irqreturn_t host_irq(struct ci13xxx *ci)
+{
+	return usb_hcd_irq(ci->irq, ci->hcd);
+}
+
+static int host_start(struct ci13xxx *ci)
+{
+	struct usb_hcd *hcd;
+	struct ehci_hcd *ehci;
+	int ret;
+
+	if (usb_disabled())
+		return -ENODEV;
+
+	hcd = usb_create_hcd(&ci_ehci_hc_driver, ci->dev, dev_name(ci->dev));
+	if (!hcd)
+		return -ENOMEM;
+
+	dev_set_drvdata(ci->dev, ci);
+	hcd->rsrc_start = ci->hw_bank.phys;
+	hcd->rsrc_len = ci->hw_bank.size;
+	hcd->regs = ci->hw_bank.abs;
+	hcd->has_tt = 1;
+
+	ehci = hcd_to_ehci(hcd);
+	ehci->caps = ci->hw_bank.cap;
+	ehci->regs = ci->hw_bank.cap +
+		HC_LENGTH(ehci, ehci_readl(ehci, &ehci->caps->hc_capbase));
+	ehci->hcs_params = ehci_readl(ehci, &ehci->caps->hcs_params);
+	ehci->has_hostpc = 1;
+	ehci->sbrn = 0x20;
+
+	ret = hw_device_reset(ci, USBMODE_CM_HOST);
+	if (ret)
+		goto rm_hcd;
+
+	ret = usb_add_hcd(hcd, 0, 0);
+	if (ret)
+		goto rm_hcd;
+
+	ci->hcd = hcd;
+
+	return ret;
+rm_hcd:
+	usb_remove_hcd(hcd);
+	return ret;
+}
+
+static void host_stop(struct ci13xxx *ci)
+{
+	struct usb_hcd *hcd = ci->hcd;
+
+	usb_remove_hcd(hcd);
+	usb_put_hcd(hcd);
+}
+
+int ci_hdrc_host_init(struct ci13xxx *ci)
+{
+	struct ci_role_driver *rdrv;
+
+	if (!hw_read(ci, CAP_DCCPARAMS, DCCPARAMS_HC))
+		return -ENXIO;
+
+	rdrv = devm_kzalloc(ci->dev, sizeof(struct ci_role_driver), GFP_KERNEL);
+	if (!rdrv)
+		return -ENOMEM;
+
+	rdrv->start	= host_start;
+	rdrv->stop	= host_stop;
+	rdrv->irq	= host_irq;
+	rdrv->name	= "host";
+	ci->roles[CI_ROLE_HOST] = rdrv;
+
+	return 0;
+}
diff --git a/drivers/usb/chipidea/host.h b/drivers/usb/chipidea/host.h
new file mode 100644
index 0000000..761fb1f
--- /dev/null
+++ b/drivers/usb/chipidea/host.h
@@ -0,0 +1,17 @@
+#ifndef __DRIVERS_USB_CHIPIDEA_HOST_H
+#define __DRIVERS_USB_CHIPIDEA_HOST_H
+
+#ifdef CONFIG_USB_CHIPIDEA_HOST
+
+int ci_hdrc_host_init(struct ci13xxx *ci);
+
+#else
+
+static inline int ci_hdrc_host_init(struct ci13xxx *ci)
+{
+	return -ENXIO;
+}
+
+#endif
+
+#endif /* __DRIVERS_USB_CHIPIDEA_HOST_H */
diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c
index 5074d3c..b1ef41d 100644
--- a/drivers/usb/chipidea/udc.c
+++ b/drivers/usb/chipidea/udc.c
@@ -1,5 +1,5 @@
 /*
- * udc.h - ChipIdea UDC driver
+ * udc.c - ChipIdea UDC driver
  *
  * Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved.
  *
@@ -1453,7 +1453,7 @@ static int ci13xxx_vbus_session(struct usb_gadget *_gadget, int is_active)
 	if (gadget_ready) {
 		if (is_active) {
 			pm_runtime_get_sync(&_gadget->dev);
-			hw_device_reset(udc);
+			hw_device_reset(udc, USBMODE_CM_DEVICE);
 			hw_device_state(udc, udc->ep0out->qh.dma);
 		} else {
 			hw_device_state(udc, 0);
@@ -1603,7 +1603,7 @@ static int ci13xxx_start(struct usb_gadget *gadget,
 	if (udc->udc_driver->flags & CI13XXX_PULLUP_ON_VBUS) {
 		if (udc->vbus_active) {
 			if (udc->udc_driver->flags & CI13XXX_REGS_SHARED)
-				hw_device_reset(udc);
+				hw_device_reset(udc, USBMODE_CM_DEVICE);
 		} else {
 			pm_runtime_put_sync(&udc->gadget.dev);
 			goto done;
@@ -1790,7 +1790,7 @@ static int udc_start(struct ci13xxx *udc)
 	}
 
 	if (!(udc->udc_driver->flags & CI13XXX_REGS_SHARED)) {
-		retval = hw_device_reset(udc);
+		retval = hw_device_reset(udc, USBMODE_CM_DEVICE);
 		if (retval)
 			goto put_transceiver;
 	}
diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
index de1e689..5cb775b 100644
--- a/drivers/usb/host/ehci-hcd.c
+++ b/drivers/usb/host/ehci-hcd.c
@@ -1246,6 +1246,13 @@ static int ehci_get_frame (struct usb_hcd *hcd)
 }
 
 /*-------------------------------------------------------------------------*/
+/*
+ * The EHCI in ChipIdea HDRC cannot be a separate module or device,
+ * because its registers (and irq) are shared between host/gadget/otg
+ * functions  and in order to facilitate role switching we cannot
+ * give the ehci driver exclusive access to those.
+ */
+#ifndef CHIPIDEA_EHCI
 
 MODULE_DESCRIPTION(DRIVER_DESC);
 MODULE_AUTHOR (DRIVER_AUTHOR);
@@ -1504,3 +1511,4 @@ static void __exit ehci_hcd_cleanup(void)
 }
 module_exit(ehci_hcd_cleanup);
 
+#endif /* CHIPIDEA_EHCI */
-- 
1.7.10

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux