2010-06-19 08:08:24 +04:00
/*
* Copyright ( C ) 2010 , Lars - Peter Clausen < lars @ metafoo . de >
*
* 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 .
*
* 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 0213 9 , USA .
*
*/
# include <linux/platform_device.h>
# include <linux/clk.h>
# include <linux/regulator/consumer.h>
struct jz4740_ohci_hcd {
struct ohci_hcd ohci_hcd ;
struct regulator * vbus ;
bool vbus_enabled ;
struct clk * clk ;
} ;
static inline struct jz4740_ohci_hcd * hcd_to_jz4740_hcd ( struct usb_hcd * hcd )
{
return ( struct jz4740_ohci_hcd * ) ( hcd - > hcd_priv ) ;
}
static inline struct usb_hcd * jz4740_hcd_to_hcd ( struct jz4740_ohci_hcd * jz4740_ohci )
{
return container_of ( ( void * ) jz4740_ohci , struct usb_hcd , hcd_priv ) ;
}
static int ohci_jz4740_start ( struct usb_hcd * hcd )
{
struct ohci_hcd * ohci = hcd_to_ohci ( hcd ) ;
int ret ;
ret = ohci_init ( ohci ) ;
if ( ret < 0 )
return ret ;
ohci - > num_ports = 1 ;
ret = ohci_run ( ohci ) ;
if ( ret < 0 ) {
dev_err ( hcd - > self . controller , " Can not start %s " ,
hcd - > self . bus_name ) ;
ohci_stop ( hcd ) ;
return ret ;
}
return 0 ;
}
static int ohci_jz4740_set_vbus_power ( struct jz4740_ohci_hcd * jz4740_ohci ,
bool enabled )
{
int ret = 0 ;
if ( ! jz4740_ohci - > vbus )
return 0 ;
if ( enabled & & ! jz4740_ohci - > vbus_enabled ) {
ret = regulator_enable ( jz4740_ohci - > vbus ) ;
if ( ret )
dev_err ( jz4740_hcd_to_hcd ( jz4740_ohci ) - > self . controller ,
" Could not power vbus \n " ) ;
} else if ( ! enabled & & jz4740_ohci - > vbus_enabled ) {
ret = regulator_disable ( jz4740_ohci - > vbus ) ;
}
if ( ret = = 0 )
jz4740_ohci - > vbus_enabled = enabled ;
return ret ;
}
static int ohci_jz4740_hub_control ( struct usb_hcd * hcd , u16 typeReq , u16 wValue ,
u16 wIndex , char * buf , u16 wLength )
{
struct jz4740_ohci_hcd * jz4740_ohci = hcd_to_jz4740_hcd ( hcd ) ;
int ret ;
switch ( typeReq ) {
case SetHubFeature :
if ( wValue = = USB_PORT_FEAT_POWER )
ret = ohci_jz4740_set_vbus_power ( jz4740_ohci , true ) ;
break ;
case ClearHubFeature :
if ( wValue = = USB_PORT_FEAT_POWER )
ret = ohci_jz4740_set_vbus_power ( jz4740_ohci , false ) ;
break ;
}
if ( ret )
return ret ;
return ohci_hub_control ( hcd , typeReq , wValue , wIndex , buf , wLength ) ;
}
static const struct hc_driver ohci_jz4740_hc_driver = {
. description = hcd_name ,
. product_desc = " JZ4740 OHCI " ,
. hcd_priv_size = sizeof ( struct jz4740_ohci_hcd ) ,
/*
* generic hardware linkage
*/
. irq = ohci_irq ,
. flags = HCD_USB11 | HCD_MEMORY ,
/*
* basic lifecycle operations
*/
. start = ohci_jz4740_start ,
. stop = ohci_stop ,
. shutdown = ohci_shutdown ,
/*
* managing i / o requests and associated device resources
*/
. urb_enqueue = ohci_urb_enqueue ,
. urb_dequeue = ohci_urb_dequeue ,
. endpoint_disable = ohci_endpoint_disable ,
/*
* scheduling support
*/
. get_frame_number = ohci_get_frame ,
/*
* root hub support
*/
. hub_status_data = ohci_hub_status_data ,
. hub_control = ohci_jz4740_hub_control ,
# ifdef CONFIG_PM
. bus_suspend = ohci_bus_suspend ,
. bus_resume = ohci_bus_resume ,
# endif
. start_port_reset = ohci_start_port_reset ,
} ;
2012-11-19 22:21:48 +04:00
static int jz4740_ohci_probe ( struct platform_device * pdev )
2010-06-19 08:08:24 +04:00
{
int ret ;
struct usb_hcd * hcd ;
struct jz4740_ohci_hcd * jz4740_ohci ;
struct resource * res ;
int irq ;
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
if ( ! res ) {
dev_err ( & pdev - > dev , " Failed to get platform resource \n " ) ;
return - ENOENT ;
}
irq = platform_get_irq ( pdev , 0 ) ;
if ( irq < 0 ) {
dev_err ( & pdev - > dev , " Failed to get platform irq \n " ) ;
return irq ;
}
hcd = usb_create_hcd ( & ohci_jz4740_hc_driver , & pdev - > dev , " jz4740 " ) ;
if ( ! hcd ) {
dev_err ( & pdev - > dev , " Failed to create hcd. \n " ) ;
return - ENOMEM ;
}
jz4740_ohci = hcd_to_jz4740_hcd ( hcd ) ;
res = request_mem_region ( res - > start , resource_size ( res ) , hcd_name ) ;
if ( ! res ) {
dev_err ( & pdev - > dev , " Failed to request mem region. \n " ) ;
ret = - EBUSY ;
goto err_free ;
}
hcd - > rsrc_start = res - > start ;
hcd - > rsrc_len = resource_size ( res ) ;
hcd - > regs = ioremap ( res - > start , resource_size ( res ) ) ;
if ( ! hcd - > regs ) {
dev_err ( & pdev - > dev , " Failed to ioremap registers. \n " ) ;
ret = - EBUSY ;
goto err_release_mem ;
}
jz4740_ohci - > clk = clk_get ( & pdev - > dev , " uhc " ) ;
if ( IS_ERR ( jz4740_ohci - > clk ) ) {
ret = PTR_ERR ( jz4740_ohci - > clk ) ;
dev_err ( & pdev - > dev , " Failed to get clock: %d \n " , ret ) ;
goto err_iounmap ;
}
jz4740_ohci - > vbus = regulator_get ( & pdev - > dev , " vbus " ) ;
if ( IS_ERR ( jz4740_ohci - > vbus ) )
jz4740_ohci - > vbus = NULL ;
clk_set_rate ( jz4740_ohci - > clk , 48000000 ) ;
clk_enable ( jz4740_ohci - > clk ) ;
if ( jz4740_ohci - > vbus )
ohci_jz4740_set_vbus_power ( jz4740_ohci , true ) ;
platform_set_drvdata ( pdev , hcd ) ;
ohci_hcd_init ( hcd_to_ohci ( hcd ) ) ;
ret = usb_add_hcd ( hcd , irq , 0 ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Failed to add hcd: %d \n " , ret ) ;
goto err_disable ;
}
return 0 ;
err_disable :
if ( jz4740_ohci - > vbus ) {
regulator_disable ( jz4740_ohci - > vbus ) ;
regulator_put ( jz4740_ohci - > vbus ) ;
}
clk_disable ( jz4740_ohci - > clk ) ;
clk_put ( jz4740_ohci - > clk ) ;
err_iounmap :
iounmap ( hcd - > regs ) ;
err_release_mem :
release_mem_region ( res - > start , resource_size ( res ) ) ;
err_free :
usb_put_hcd ( hcd ) ;
return ret ;
}
2012-11-19 22:26:20 +04:00
static int jz4740_ohci_remove ( struct platform_device * pdev )
2010-06-19 08:08:24 +04:00
{
struct usb_hcd * hcd = platform_get_drvdata ( pdev ) ;
struct jz4740_ohci_hcd * jz4740_ohci = hcd_to_jz4740_hcd ( hcd ) ;
usb_remove_hcd ( hcd ) ;
if ( jz4740_ohci - > vbus ) {
regulator_disable ( jz4740_ohci - > vbus ) ;
regulator_put ( jz4740_ohci - > vbus ) ;
}
clk_disable ( jz4740_ohci - > clk ) ;
clk_put ( jz4740_ohci - > clk ) ;
iounmap ( hcd - > regs ) ;
release_mem_region ( hcd - > rsrc_start , hcd - > rsrc_len ) ;
usb_put_hcd ( hcd ) ;
return 0 ;
}
static struct platform_driver ohci_hcd_jz4740_driver = {
. probe = jz4740_ohci_probe ,
2012-11-19 22:21:08 +04:00
. remove = jz4740_ohci_remove ,
2010-06-19 08:08:24 +04:00
. driver = {
. name = " jz4740-ohci " ,
. owner = THIS_MODULE ,
} ,
} ;
2010-11-08 00:14:31 +03:00
MODULE_ALIAS ( " platform:jz4740-ohci " ) ;