Skip to content

Commit

Permalink
usb: chipidea: add host role
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Alexander Shishkin authored and Greg Kroah-Hartman committed May 11, 2012
1 parent 758fc98 commit eb70e5a
Show file tree
Hide file tree
Showing 9 changed files with 212 additions and 9 deletions.
6 changes: 6 additions & 0 deletions drivers/usb/chipidea/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions drivers/usb/chipidea/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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),)
Expand Down
1 change: 1 addition & 0 deletions drivers/usb/chipidea/bits.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
7 changes: 6 additions & 1 deletion drivers/usb/chipidea/ci.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

#include <linux/list.h>
#include <linux/irqreturn.h>
#include <linux/usb.h>
#include <linux/usb/gadget.h>

/******************************************************************************
Expand Down Expand Up @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);

Expand Down
15 changes: 11 additions & 4 deletions drivers/usb/chipidea/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
#include "ci.h"
#include "udc.h"
#include "bits.h"
#include "host.h"
#include "debug.h"

/* Controller register map */
Expand Down Expand Up @@ -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);
Expand All @@ -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;
}
Expand Down Expand Up @@ -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");
Expand All @@ -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");
Expand Down
158 changes: 158 additions & 0 deletions drivers/usb/chipidea/host.c
Original file line number Diff line number Diff line change
@@ -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;
}
17 changes: 17 additions & 0 deletions drivers/usb/chipidea/host.h
Original file line number Diff line number Diff line change
@@ -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 */
8 changes: 4 additions & 4 deletions drivers/usb/chipidea/udc.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* udc.h - ChipIdea UDC driver
* udc.c - ChipIdea UDC driver
*
* Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved.
*
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down
8 changes: 8 additions & 0 deletions drivers/usb/host/ehci-hcd.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -1504,3 +1511,4 @@ static void __exit ehci_hcd_cleanup(void)
}
module_exit(ehci_hcd_cleanup);

#endif /* CHIPIDEA_EHCI */

0 comments on commit eb70e5a

Please sign in to comment.