diff options
author | Alexander Shishkin <alexander.shishkin@linux.intel.com> | 2012-05-11 18:25:54 +0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2012-05-12 03:56:15 +0400 |
commit | eb70e5ab8f95a81283623c03d2c99dfc59fcb319 (patch) | |
tree | fc20c9d10484171490cf6b69f7f5b6f934803366 /drivers/usb | |
parent | 758fc9860c19eceb56e5886a5225db623c521971 (diff) | |
download | linux-eb70e5ab8f95a81283623c03d2c99dfc59fcb319.tar.xz |
usb: chipidea: add host role
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@linux.intel.com>
Cc: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/usb')
-rw-r--r-- | drivers/usb/chipidea/Kconfig | 6 | ||||
-rw-r--r-- | drivers/usb/chipidea/Makefile | 1 | ||||
-rw-r--r-- | drivers/usb/chipidea/bits.h | 1 | ||||
-rw-r--r-- | drivers/usb/chipidea/ci.h | 7 | ||||
-rw-r--r-- | drivers/usb/chipidea/core.c | 15 | ||||
-rw-r--r-- | drivers/usb/chipidea/host.c | 158 | ||||
-rw-r--r-- | drivers/usb/chipidea/host.h | 17 | ||||
-rw-r--r-- | drivers/usb/chipidea/udc.c | 8 | ||||
-rw-r--r-- | drivers/usb/host/ehci-hcd.c | 8 |
9 files changed, 212 insertions, 9 deletions
diff --git a/drivers/usb/chipidea/Kconfig b/drivers/usb/chipidea/Kconfig index 553c1976a66e..fd36dc8b889b 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 host 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 a8279aac6a4a..cc3493769724 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/bits.h b/drivers/usb/chipidea/bits.h index 44b3e254b6a5..050de8562a04 100644 --- a/drivers/usb/chipidea/bits.h +++ b/drivers/usb/chipidea/bits.h @@ -21,6 +21,7 @@ /* DCCPARAMS */ #define DCCPARAMS_DEN (0x1F << 0) #define DCCPARAMS_DC BIT(7) +#define DCCPARAMS_HC BIT(8) /* TESTMODE */ #define TESTMODE_FORCE BIT(0) diff --git a/drivers/usb/chipidea/ci.h b/drivers/usb/chipidea/ci.h index 0ab83411d800..c605acc5568a 100644 --- a/drivers/usb/chipidea/ci.h +++ b/drivers/usb/chipidea/ci.h @@ -15,6 +15,7 @@ #include <linux/list.h> #include <linux/irqreturn.h> +#include <linux/usb.h> #include <linux/usb/gadget.h> /****************************************************************************** @@ -84,6 +85,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 @@ -92,6 +94,7 @@ struct ci_role_driver { */ struct hw_bank { unsigned lpm; + resource_size_t phys; void __iomem *abs; void __iomem *cap; void __iomem *op; @@ -128,6 +131,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; @@ -160,6 +164,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) @@ -302,7 +307,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 *ci, u8 mode); diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index 3d48c9be6923..f568b8e86cee 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -70,6 +70,7 @@ #include "ci.h" #include "udc.h" #include "bits.h" +#include "host.h" #include "debug.h" /* Controller register map */ @@ -215,7 +216,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); @@ -235,12 +236,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_DC); + 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_DC) { - 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; } @@ -371,6 +372,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"); @@ -385,6 +388,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 000000000000..8c8362c89a8c --- /dev/null +++ b/drivers/usb/chipidea/host.c @@ -0,0 +1,158 @@ +/* + * 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 "bits.h" +#include "host.h" + +static int ci_ehci_setup(struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + int ret; + + hcd->has_tt = 1; + + ret = ehci_setup(hcd); + if (ret) + return ret; + + ehci_port_power(ehci, 0); + + 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->has_hostpc = ci->hw_bank.lpm; + + ret = usb_add_hcd(hcd, 0, 0); + if (ret) + usb_remove_hcd(hcd); + else + ci->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 000000000000..761fb1fd6d99 --- /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 290946d618d8..fb0a91158006 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. * @@ -1396,7 +1396,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_DC); hw_device_state(udc, udc->ep0out->qh.dma); } else { hw_device_state(udc, 0); @@ -1540,7 +1540,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_DC); } else { pm_runtime_put_sync(&udc->gadget.dev); goto done; @@ -1720,7 +1720,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_DC); if (retval) goto put_transceiver; } diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index de1e689d3df0..5cb775b1802d 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 */ |