2010-12-07 17:53:56 +05:30
/* ehci-msm.c - HSUSB Host Controller Driver Implementation
*
2011-02-18 17:30:11 +05:30
* Copyright ( c ) 2008 - 2011 , Code Aurora Forum . All rights reserved .
2010-12-07 17:53:56 +05:30
*
* Partly derived from ehci - fsl . c and ehci - hcd . c
* Copyright ( c ) 2000 - 2004 by David Brownell
* Copyright ( c ) 2005 MontaVista Software
*
* All source code in this file is licensed under the following license except
* where indicated .
*
* 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 , you can find it at http : //www.fsf.org
*/
# include <linux/platform_device.h>
# include <linux/clk.h>
# include <linux/err.h>
2010-12-07 17:53:57 +05:30
# include <linux/pm_runtime.h>
2010-12-07 17:53:56 +05:30
# include <linux/usb/otg.h>
# include <linux/usb/msm_hsusb_hw.h>
# define MSM_USB_BASE (hcd->regs)
static struct otg_transceiver * otg ;
static int ehci_msm_reset ( struct usb_hcd * hcd )
{
struct ehci_hcd * ehci = hcd_to_ehci ( hcd ) ;
int retval ;
ehci - > caps = USB_CAPLENGTH ;
ehci - > regs = USB_CAPLENGTH +
HC_LENGTH ( ehci_readl ( ehci , & ehci - > caps - > hc_capbase ) ) ;
2011-02-18 17:30:11 +05:30
dbg_hcs_params ( ehci , " reset " ) ;
dbg_hcc_params ( ehci , " reset " ) ;
2010-12-07 17:53:56 +05:30
/* cache the data to minimize the chip reads*/
ehci - > hcs_params = ehci_readl ( ehci , & ehci - > caps - > hcs_params ) ;
hcd - > has_tt = 1 ;
ehci - > sbrn = HCD_USB2 ;
2011-02-15 18:41:54 +01:00
retval = ehci_halt ( ehci ) ;
if ( retval )
return retval ;
2010-12-07 17:53:56 +05:30
/* data structure init */
retval = ehci_init ( hcd ) ;
if ( retval )
return retval ;
retval = ehci_reset ( ehci ) ;
if ( retval )
return retval ;
/* bursts of unspecified length. */
writel ( 0 , USB_AHBBURST ) ;
/* Use the AHB transactor */
writel ( 0 , USB_AHBMODE ) ;
/* Disable streaming mode and select host mode */
writel ( 0x13 , USB_USBMODE ) ;
ehci_port_power ( ehci , 1 ) ;
return 0 ;
}
static struct hc_driver msm_hc_driver = {
. description = hcd_name ,
. product_desc = " Qualcomm On-Chip EHCI Host Controller " ,
. hcd_priv_size = sizeof ( struct ehci_hcd ) ,
/*
* generic hardware linkage
*/
. irq = ehci_irq ,
. flags = HCD_USB2 | HCD_MEMORY ,
. reset = ehci_msm_reset ,
2011-02-15 18:41:54 +01:00
. start = ehci_run ,
2010-12-07 17:53:56 +05:30
. 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 ,
. clear_tt_buffer_complete = ehci_clear_tt_buffer_complete ,
/*
* scheduling support
*/
. get_frame_number = ehci_get_frame ,
/*
* root hub support
*/
. hub_status_data = ehci_hub_status_data ,
. hub_control = ehci_hub_control ,
. relinquish_port = ehci_relinquish_port ,
. port_handed_over = ehci_port_handed_over ,
/*
* PM support
*/
. bus_suspend = ehci_bus_suspend ,
. bus_resume = ehci_bus_resume ,
} ;
static int ehci_msm_probe ( struct platform_device * pdev )
{
struct usb_hcd * hcd ;
struct resource * res ;
int ret ;
dev_dbg ( & pdev - > dev , " ehci_msm proble \n " ) ;
hcd = usb_create_hcd ( & msm_hc_driver , & pdev - > dev , dev_name ( & pdev - > dev ) ) ;
if ( ! hcd ) {
dev_err ( & pdev - > dev , " Unable to create HCD \n " ) ;
return - ENOMEM ;
}
hcd - > irq = platform_get_irq ( pdev , 0 ) ;
if ( hcd - > irq < 0 ) {
dev_err ( & pdev - > dev , " Unable to get IRQ resource \n " ) ;
ret = hcd - > irq ;
goto put_hcd ;
}
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
if ( ! res ) {
dev_err ( & pdev - > dev , " Unable to get memory resource \n " ) ;
ret = - ENODEV ;
goto put_hcd ;
}
hcd - > rsrc_start = res - > start ;
hcd - > rsrc_len = resource_size ( res ) ;
hcd - > regs = ioremap ( hcd - > rsrc_start , hcd - > rsrc_len ) ;
if ( ! hcd - > regs ) {
dev_err ( & pdev - > dev , " ioremap failed \n " ) ;
ret = - ENOMEM ;
goto put_hcd ;
}
/*
* OTG driver takes care of PHY initialization , clock management ,
2010-12-07 17:53:57 +05:30
* powering up VBUS , mapping of registers address space and power
* management .
2010-12-07 17:53:56 +05:30
*/
otg = otg_get_transceiver ( ) ;
if ( ! otg ) {
dev_err ( & pdev - > dev , " unable to find transceiver \n " ) ;
ret = - ENODEV ;
goto unmap ;
}
ret = otg_set_host ( otg , & hcd - > self ) ;
if ( ret < 0 ) {
dev_err ( & pdev - > dev , " unable to register with transceiver \n " ) ;
goto put_transceiver ;
}
device_init_wakeup ( & pdev - > dev , 1 ) ;
2010-12-07 17:53:57 +05:30
/*
* OTG device parent of HCD takes care of putting
* hardware into low power mode .
*/
pm_runtime_no_callbacks ( & pdev - > dev ) ;
pm_runtime_enable ( & pdev - > dev ) ;
2010-12-07 17:53:56 +05:30
return 0 ;
put_transceiver :
otg_put_transceiver ( otg ) ;
unmap :
iounmap ( hcd - > regs ) ;
put_hcd :
usb_put_hcd ( hcd ) ;
return ret ;
}
static int __devexit ehci_msm_remove ( struct platform_device * pdev )
{
struct usb_hcd * hcd = platform_get_drvdata ( pdev ) ;
device_init_wakeup ( & pdev - > dev , 0 ) ;
2010-12-07 17:53:57 +05:30
pm_runtime_disable ( & pdev - > dev ) ;
pm_runtime_set_suspended ( & pdev - > dev ) ;
2010-12-07 17:53:56 +05:30
otg_set_host ( otg , NULL ) ;
otg_put_transceiver ( otg ) ;
usb_put_hcd ( hcd ) ;
return 0 ;
}
2010-12-07 17:53:57 +05:30
# ifdef CONFIG_PM
static int ehci_msm_pm_suspend ( struct device * dev )
{
struct usb_hcd * hcd = dev_get_drvdata ( dev ) ;
bool wakeup = device_may_wakeup ( dev ) ;
dev_dbg ( dev , " ehci-msm PM suspend \n " ) ;
/*
* EHCI helper function has also the same check before manipulating
* port wakeup flags . We do check here the same condition before
* calling the same helper function to avoid bringing hardware
* from Low power mode when there is no need for adjusting port
* wakeup flags .
*/
if ( hcd - > self . root_hub - > do_remote_wakeup & & ! wakeup ) {
pm_runtime_resume ( dev ) ;
ehci_prepare_ports_for_controller_suspend ( hcd_to_ehci ( hcd ) ,
wakeup ) ;
}
return 0 ;
}
static int ehci_msm_pm_resume ( struct device * dev )
{
struct usb_hcd * hcd = dev_get_drvdata ( dev ) ;
dev_dbg ( dev , " ehci-msm PM resume \n " ) ;
ehci_prepare_ports_for_controller_resume ( hcd_to_ehci ( hcd ) ) ;
return 0 ;
}
# else
# define ehci_msm_pm_suspend NULL
# define ehci_msm_pm_resume NULL
# endif
static const struct dev_pm_ops ehci_msm_dev_pm_ops = {
. suspend = ehci_msm_pm_suspend ,
. resume = ehci_msm_pm_resume ,
} ;
2010-12-07 17:53:56 +05:30
static struct platform_driver ehci_msm_driver = {
. probe = ehci_msm_probe ,
. remove = __devexit_p ( ehci_msm_remove ) ,
. driver = {
. name = " msm_hsusb_host " ,
2010-12-07 17:53:57 +05:30
. pm = & ehci_msm_dev_pm_ops ,
2010-12-07 17:53:56 +05:30
} ,
} ;