2011-12-20 09:20:23 +04:00
/*
* Copyright ( C ) 2011 Marvell International Ltd . All rights reserved .
* Author : Chao Xie < chao . xie @ marvell . com >
* Neil Zhang < zhangwm @ marvell . com >
*
* 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 .
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/platform_device.h>
# include <linux/clk.h>
2012-06-26 16:10:32 +04:00
# include <linux/err.h>
2011-12-20 09:20:23 +04:00
# include <linux/usb/otg.h>
# include <linux/platform_data/mv_usb.h>
# define CAPLENGTH_MASK (0xff)
struct ehci_hcd_mv {
struct usb_hcd * hcd ;
/* Which mode does this ehci running OTG/Host ? */
int mode ;
void __iomem * phy_regs ;
void __iomem * cap_regs ;
void __iomem * op_regs ;
2012-02-13 15:24:02 +04:00
struct usb_phy * otg ;
2011-12-20 09:20:23 +04:00
struct mv_usb_platform_data * pdata ;
2013-03-25 11:06:54 +04:00
struct clk * clk ;
2011-12-20 09:20:23 +04:00
} ;
static void ehci_clock_enable ( struct ehci_hcd_mv * ehci_mv )
{
2013-03-25 11:06:54 +04:00
clk_prepare_enable ( ehci_mv - > clk ) ;
2011-12-20 09:20:23 +04:00
}
static void ehci_clock_disable ( struct ehci_hcd_mv * ehci_mv )
{
2013-03-25 11:06:54 +04:00
clk_disable_unprepare ( ehci_mv - > clk ) ;
2011-12-20 09:20:23 +04:00
}
static int mv_ehci_enable ( struct ehci_hcd_mv * ehci_mv )
{
int retval ;
ehci_clock_enable ( ehci_mv ) ;
if ( ehci_mv - > pdata - > phy_init ) {
retval = ehci_mv - > pdata - > phy_init ( ehci_mv - > phy_regs ) ;
if ( retval )
return retval ;
}
return 0 ;
}
static void mv_ehci_disable ( struct ehci_hcd_mv * ehci_mv )
{
if ( ehci_mv - > pdata - > phy_deinit )
ehci_mv - > pdata - > phy_deinit ( ehci_mv - > phy_regs ) ;
ehci_clock_disable ( ehci_mv ) ;
}
static int mv_ehci_reset ( struct usb_hcd * hcd )
{
struct device * dev = hcd - > self . controller ;
struct ehci_hcd_mv * ehci_mv = dev_get_drvdata ( dev ) ;
int retval ;
if ( ehci_mv = = NULL ) {
dev_err ( dev , " Can not find private ehci data \n " ) ;
return - ENODEV ;
}
hcd - > has_tt = 1 ;
2012-07-09 23:55:14 +04:00
retval = ehci_setup ( hcd ) ;
if ( retval )
dev_err ( dev , " ehci_setup failed %d \n " , retval ) ;
2011-12-20 09:20:23 +04:00
2012-07-09 23:55:14 +04:00
return retval ;
2011-12-20 09:20:23 +04:00
}
static const struct hc_driver mv_ehci_hc_driver = {
. description = hcd_name ,
. product_desc = " Marvell EHCI " ,
. hcd_priv_size = sizeof ( struct ehci_hcd ) ,
/*
* generic hardware linkage
*/
. irq = ehci_irq ,
2013-09-13 00:58:42 +04:00
. flags = HCD_MEMORY | HCD_USB2 ,
2011-12-20 09:20:23 +04:00
/*
* basic lifecycle operations
*/
. reset = mv_ehci_reset ,
. 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 ,
. 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 ,
. bus_suspend = ehci_bus_suspend ,
. bus_resume = ehci_bus_resume ,
} ;
static int mv_ehci_probe ( struct platform_device * pdev )
{
2013-07-30 14:59:40 +04:00
struct mv_usb_platform_data * pdata = dev_get_platdata ( & pdev - > dev ) ;
2011-12-20 09:20:23 +04:00
struct usb_hcd * hcd ;
struct ehci_hcd * ehci ;
struct ehci_hcd_mv * ehci_mv ;
struct resource * r ;
2013-03-25 11:06:54 +04:00
int retval = - ENODEV ;
2011-12-20 09:20:23 +04:00
u32 offset ;
if ( ! pdata ) {
dev_err ( & pdev - > dev , " missing platform_data \n " ) ;
return - ENODEV ;
}
if ( usb_disabled ( ) )
return - ENODEV ;
hcd = usb_create_hcd ( & mv_ehci_hc_driver , & pdev - > dev , " mv ehci " ) ;
if ( ! hcd )
return - ENOMEM ;
2013-03-25 11:06:54 +04:00
ehci_mv = devm_kzalloc ( & pdev - > dev , sizeof ( * ehci_mv ) , GFP_KERNEL ) ;
2011-12-20 09:20:23 +04:00
if ( ehci_mv = = NULL ) {
dev_err ( & pdev - > dev , " cannot allocate ehci_hcd_mv \n " ) ;
retval = - ENOMEM ;
goto err_put_hcd ;
}
platform_set_drvdata ( pdev , ehci_mv ) ;
ehci_mv - > pdata = pdata ;
ehci_mv - > hcd = hcd ;
2013-03-25 11:06:54 +04:00
ehci_mv - > clk = devm_clk_get ( & pdev - > dev , NULL ) ;
if ( IS_ERR ( ehci_mv - > clk ) ) {
dev_err ( & pdev - > dev , " error getting clock \n " ) ;
retval = PTR_ERR ( ehci_mv - > clk ) ;
2013-05-06 14:05:54 +04:00
goto err_put_hcd ;
2011-12-20 09:20:23 +04:00
}
r = platform_get_resource_byname ( pdev , IORESOURCE_MEM , " phyregs " ) ;
if ( r = = NULL ) {
dev_err ( & pdev - > dev , " no phy I/O memory resource defined \n " ) ;
retval = - ENODEV ;
2013-05-06 14:05:54 +04:00
goto err_put_hcd ;
2011-12-20 09:20:23 +04:00
}
2012-07-29 23:46:12 +04:00
ehci_mv - > phy_regs = devm_ioremap ( & pdev - > dev , r - > start ,
resource_size ( r ) ) ;
2011-12-20 09:20:23 +04:00
if ( ehci_mv - > phy_regs = = 0 ) {
dev_err ( & pdev - > dev , " failed to map phy I/O memory \n " ) ;
retval = - EFAULT ;
2013-05-06 14:05:54 +04:00
goto err_put_hcd ;
2011-12-20 09:20:23 +04:00
}
r = platform_get_resource_byname ( pdev , IORESOURCE_MEM , " capregs " ) ;
if ( ! r ) {
dev_err ( & pdev - > dev , " no I/O memory resource defined \n " ) ;
retval = - ENODEV ;
2013-05-06 14:05:54 +04:00
goto err_put_hcd ;
2011-12-20 09:20:23 +04:00
}
2012-07-29 23:46:12 +04:00
ehci_mv - > cap_regs = devm_ioremap ( & pdev - > dev , r - > start ,
resource_size ( r ) ) ;
2011-12-20 09:20:23 +04:00
if ( ehci_mv - > cap_regs = = NULL ) {
dev_err ( & pdev - > dev , " failed to map I/O memory \n " ) ;
retval = - EFAULT ;
2013-05-06 14:05:54 +04:00
goto err_put_hcd ;
2011-12-20 09:20:23 +04:00
}
retval = mv_ehci_enable ( ehci_mv ) ;
if ( retval ) {
dev_err ( & pdev - > dev , " init phy error %d \n " , retval ) ;
2013-05-06 14:05:54 +04:00
goto err_put_hcd ;
2011-12-20 09:20:23 +04:00
}
offset = readl ( ehci_mv - > cap_regs ) & CAPLENGTH_MASK ;
ehci_mv - > op_regs =
( void __iomem * ) ( ( unsigned long ) ehci_mv - > cap_regs + offset ) ;
hcd - > rsrc_start = r - > start ;
2013-03-10 17:52:02 +04:00
hcd - > rsrc_len = resource_size ( r ) ;
2011-12-20 09:20:23 +04:00
hcd - > regs = ehci_mv - > op_regs ;
hcd - > irq = platform_get_irq ( pdev , 0 ) ;
if ( ! hcd - > irq ) {
dev_err ( & pdev - > dev , " Cannot get irq. " ) ;
retval = - ENODEV ;
goto err_disable_clk ;
}
ehci = hcd_to_ehci ( hcd ) ;
ehci - > caps = ( struct ehci_caps * ) ehci_mv - > cap_regs ;
ehci_mv - > mode = pdata - > mode ;
if ( ehci_mv - > mode = = MV_USB_MODE_OTG ) {
2012-07-29 23:46:12 +04:00
ehci_mv - > otg = devm_usb_get_phy ( & pdev - > dev , USB_PHY_TYPE_USB2 ) ;
2013-03-15 13:03:30 +04:00
if ( IS_ERR ( ehci_mv - > otg ) ) {
retval = PTR_ERR ( ehci_mv - > otg ) ;
if ( retval = = - ENXIO )
dev_info ( & pdev - > dev , " MV_USB_MODE_OTG "
" must have CONFIG_USB_PHY enabled \n " ) ;
else
dev_err ( & pdev - > dev ,
" unable to find transceiver \n " ) ;
2011-12-20 09:20:23 +04:00
goto err_disable_clk ;
}
2012-02-13 15:24:20 +04:00
retval = otg_set_host ( ehci_mv - > otg - > otg , & hcd - > self ) ;
2011-12-20 09:20:23 +04:00
if ( retval < 0 ) {
dev_err ( & pdev - > dev ,
" unable to register with transceiver \n " ) ;
retval = - ENODEV ;
2012-07-29 23:46:12 +04:00
goto err_disable_clk ;
2011-12-20 09:20:23 +04:00
}
/* otg will enable clock before use as host */
mv_ehci_disable ( ehci_mv ) ;
} else {
if ( pdata - > set_vbus )
pdata - > set_vbus ( 1 ) ;
retval = usb_add_hcd ( hcd , hcd - > irq , IRQF_SHARED ) ;
if ( retval ) {
dev_err ( & pdev - > dev ,
" failed to add hcd with err %d \n " , retval ) ;
goto err_set_vbus ;
}
}
if ( pdata - > private_init )
pdata - > private_init ( ehci_mv - > op_regs , ehci_mv - > phy_regs ) ;
dev_info ( & pdev - > dev ,
" successful find EHCI device with regs 0x%p irq %d "
" working in %s mode \n " , hcd - > regs , hcd - > irq ,
ehci_mv - > mode = = MV_USB_MODE_OTG ? " OTG " : " Host " ) ;
return 0 ;
err_set_vbus :
if ( pdata - > set_vbus )
pdata - > set_vbus ( 0 ) ;
err_disable_clk :
mv_ehci_disable ( ehci_mv ) ;
err_put_hcd :
usb_put_hcd ( hcd ) ;
return retval ;
}
static int mv_ehci_remove ( struct platform_device * pdev )
{
struct ehci_hcd_mv * ehci_mv = platform_get_drvdata ( pdev ) ;
struct usb_hcd * hcd = ehci_mv - > hcd ;
if ( hcd - > rh_registered )
usb_remove_hcd ( hcd ) ;
2012-07-29 23:46:12 +04:00
if ( ! IS_ERR_OR_NULL ( ehci_mv - > otg ) )
2012-02-13 15:24:20 +04:00
otg_set_host ( ehci_mv - > otg - > otg , NULL ) ;
2011-12-20 09:20:23 +04:00
if ( ehci_mv - > mode = = MV_USB_MODE_HOST ) {
if ( ehci_mv - > pdata - > set_vbus )
ehci_mv - > pdata - > set_vbus ( 0 ) ;
mv_ehci_disable ( ehci_mv ) ;
}
usb_put_hcd ( hcd ) ;
return 0 ;
}
MODULE_ALIAS ( " mv-ehci " ) ;
static const struct platform_device_id ehci_id_table [ ] = {
{ " pxa-u2oehci " , PXA_U2OEHCI } ,
{ " pxa-sph " , PXA_SPH } ,
{ " mmp3-hsic " , MMP3_HSIC } ,
{ " mmp3-fsic " , MMP3_FSIC } ,
{ } ,
} ;
static void mv_ehci_shutdown ( struct platform_device * pdev )
{
struct ehci_hcd_mv * ehci_mv = platform_get_drvdata ( pdev ) ;
struct usb_hcd * hcd = ehci_mv - > hcd ;
if ( ! hcd - > rh_registered )
return ;
if ( hcd - > driver - > shutdown )
hcd - > driver - > shutdown ( hcd ) ;
}
static struct platform_driver ehci_mv_driver = {
. probe = mv_ehci_probe ,
. remove = mv_ehci_remove ,
. shutdown = mv_ehci_shutdown ,
. driver = {
. name = " mv-ehci " ,
. bus = & platform_bus_type ,
} ,
. id_table = ehci_id_table ,
} ;