-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
usb: host: mips: sead3: USB Host controller support for SEAD-3 platform.
Add EHCI driver for MIPS SEAD-3 development platform. Signed-off-by: Chris Dearman <chris@mips.com> Signed-off-by: Steven J. Hill <sjhill@mips.com> Acked-by: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
- Loading branch information
Steven J. Hill
authored and
Greg Kroah-Hartman
committed
Apr 20, 2012
1 parent
1f6155f
commit c256667
Showing
3 changed files
with
274 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,266 @@ | ||
/* | ||
* MIPS CI13320A EHCI Host Controller driver | ||
* Based on "ehci-au1xxx.c" by K.Boge <karsten.boge@amd.com> | ||
* | ||
* Copyright (C) 2012 MIPS Technologies, Inc. | ||
* | ||
* 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 int ehci_sead3_setup(struct usb_hcd *hcd) | ||
{ | ||
int ret; | ||
struct ehci_hcd *ehci = hcd_to_ehci(hcd); | ||
|
||
ehci->caps = hcd->regs + 0x100; | ||
|
||
ret = ehci_setup(hcd); | ||
if (ret) | ||
return ret; | ||
|
||
ehci->need_io_watchdog = 0; | ||
|
||
#ifdef __BIG_ENDIAN | ||
ehci->big_endian_mmio = 1; | ||
ehci->big_endian_desc = 1; | ||
#endif | ||
|
||
/* Set burst length to 16 words. */ | ||
ehci_writel(ehci, 0x1010, &ehci->regs->reserved[1]); | ||
|
||
return ret; | ||
} | ||
|
||
const struct hc_driver ehci_sead3_hc_driver = { | ||
.description = hcd_name, | ||
.product_desc = "SEAD-3 EHCI", | ||
.hcd_priv_size = sizeof(struct ehci_hcd), | ||
|
||
/* | ||
* generic hardware linkage | ||
*/ | ||
.irq = ehci_irq, | ||
.flags = HCD_MEMORY | HCD_USB2, | ||
|
||
/* | ||
* basic lifecycle operations | ||
* | ||
*/ | ||
.reset = ehci_sead3_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 int ehci_hcd_sead3_drv_probe(struct platform_device *pdev) | ||
{ | ||
struct usb_hcd *hcd; | ||
struct resource *res; | ||
int ret; | ||
|
||
if (usb_disabled()) | ||
return -ENODEV; | ||
|
||
if (pdev->resource[1].flags != IORESOURCE_IRQ) { | ||
pr_debug("resource[1] is not IORESOURCE_IRQ"); | ||
return -ENOMEM; | ||
} | ||
hcd = usb_create_hcd(&ehci_sead3_hc_driver, &pdev->dev, "SEAD-3"); | ||
if (!hcd) | ||
return -ENOMEM; | ||
|
||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
hcd->rsrc_start = res->start; | ||
hcd->rsrc_len = resource_size(res); | ||
|
||
if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) { | ||
pr_debug("request_mem_region failed"); | ||
ret = -EBUSY; | ||
goto err1; | ||
} | ||
|
||
hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len); | ||
if (!hcd->regs) { | ||
pr_debug("ioremap failed"); | ||
ret = -ENOMEM; | ||
goto err2; | ||
} | ||
|
||
/* Root hub has integrated TT. */ | ||
hcd->has_tt = 1; | ||
|
||
ret = usb_add_hcd(hcd, pdev->resource[1].start, | ||
IRQF_SHARED); | ||
if (ret == 0) { | ||
platform_set_drvdata(pdev, hcd); | ||
return ret; | ||
} | ||
|
||
iounmap(hcd->regs); | ||
err2: | ||
release_mem_region(hcd->rsrc_start, hcd->rsrc_len); | ||
err1: | ||
usb_put_hcd(hcd); | ||
return ret; | ||
} | ||
|
||
static int ehci_hcd_sead3_drv_remove(struct platform_device *pdev) | ||
{ | ||
struct usb_hcd *hcd = platform_get_drvdata(pdev); | ||
|
||
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; | ||
} | ||
|
||
#ifdef CONFIG_PM | ||
static int ehci_hcd_sead3_drv_suspend(struct device *dev) | ||
{ | ||
struct usb_hcd *hcd = dev_get_drvdata(dev); | ||
struct ehci_hcd *ehci = hcd_to_ehci(hcd); | ||
unsigned long flags; | ||
int rc = 0; | ||
|
||
if (time_before(jiffies, ehci->next_statechange)) | ||
msleep(20); | ||
|
||
/* Root hub was already suspended. Disable irq emission and | ||
* mark HW unaccessible. The PM and USB cores make sure that | ||
* the root hub is either suspended or stopped. | ||
*/ | ||
ehci_prepare_ports_for_controller_suspend(ehci, device_may_wakeup(dev)); | ||
spin_lock_irqsave(&ehci->lock, flags); | ||
ehci_writel(ehci, 0, &ehci->regs->intr_enable); | ||
(void)ehci_readl(ehci, &ehci->regs->intr_enable); | ||
|
||
clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); | ||
spin_unlock_irqrestore(&ehci->lock, flags); | ||
|
||
/* could save FLADJ in case of Vaux power loss | ||
* ... we'd only use it to handle clock skew | ||
*/ | ||
|
||
return rc; | ||
} | ||
|
||
static int ehci_hcd_sead3_drv_resume(struct device *dev) | ||
{ | ||
struct usb_hcd *hcd = dev_get_drvdata(dev); | ||
struct ehci_hcd *ehci = hcd_to_ehci(hcd); | ||
|
||
/* maybe restore FLADJ. */ | ||
|
||
if (time_before(jiffies, ehci->next_statechange)) | ||
msleep(100); | ||
|
||
/* Mark hardware accessible again as we are out of D3 state by now */ | ||
set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); | ||
|
||
/* If CF is still set, we maintained PCI Vaux power. | ||
* Just undo the effect of ehci_pci_suspend(). | ||
*/ | ||
if (ehci_readl(ehci, &ehci->regs->configured_flag) == FLAG_CF) { | ||
int mask = INTR_MASK; | ||
|
||
ehci_prepare_ports_for_controller_resume(ehci); | ||
if (!hcd->self.root_hub->do_remote_wakeup) | ||
mask &= ~STS_PCD; | ||
ehci_writel(ehci, mask, &ehci->regs->intr_enable); | ||
ehci_readl(ehci, &ehci->regs->intr_enable); | ||
return 0; | ||
} | ||
|
||
ehci_dbg(ehci, "lost power, restarting\n"); | ||
usb_root_hub_lost_power(hcd->self.root_hub); | ||
|
||
/* Else reset, to cope with power loss or flush-to-storage | ||
* style "resume" having let BIOS kick in during reboot. | ||
*/ | ||
(void) ehci_halt(ehci); | ||
(void) ehci_reset(ehci); | ||
|
||
/* emptying the schedule aborts any urbs */ | ||
spin_lock_irq(&ehci->lock); | ||
if (ehci->reclaim) | ||
end_unlink_async(ehci); | ||
ehci_work(ehci); | ||
spin_unlock_irq(&ehci->lock); | ||
|
||
ehci_writel(ehci, ehci->command, &ehci->regs->command); | ||
ehci_writel(ehci, FLAG_CF, &ehci->regs->configured_flag); | ||
ehci_readl(ehci, &ehci->regs->command); /* unblock posted writes */ | ||
|
||
/* here we "know" root ports should always stay powered */ | ||
ehci_port_power(ehci, 1); | ||
|
||
ehci->rh_state = EHCI_RH_SUSPENDED; | ||
|
||
return 0; | ||
} | ||
|
||
static const struct dev_pm_ops sead3_ehci_pmops = { | ||
.suspend = ehci_hcd_sead3_drv_suspend, | ||
.resume = ehci_hcd_sead3_drv_resume, | ||
}; | ||
|
||
#define SEAD3_EHCI_PMOPS (&sead3_ehci_pmops) | ||
|
||
#else | ||
#define SEAD3_EHCI_PMOPS NULL | ||
#endif | ||
|
||
static struct platform_driver ehci_hcd_sead3_driver = { | ||
.probe = ehci_hcd_sead3_drv_probe, | ||
.remove = ehci_hcd_sead3_drv_remove, | ||
.shutdown = usb_hcd_platform_shutdown, | ||
.driver = { | ||
.name = "sead3-ehci", | ||
.owner = THIS_MODULE, | ||
.pm = SEAD3_EHCI_PMOPS, | ||
} | ||
}; | ||
|
||
MODULE_ALIAS("platform:sead3-ehci"); |