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