2005-07-29 23:18:03 +04:00
/*
* OHCI HCD ( Host Controller Driver ) for USB .
*
* ( C ) Copyright 1999 Roman Weissgaerber < weissg @ vienna . at >
* ( C ) Copyright 2000 - 2002 David Brownell < dbrownell @ users . sourceforge . net >
* ( C ) Copyright 2002 Hewlett - Packard Company
*
* USB Bus Glue for Samsung S3C2410
*
* Written by Christopher Hoover < ch @ hpl . hp . com >
2008-07-11 04:30:46 +04:00
* Based on fragments of previous driver by Russell King et al .
2005-07-29 23:18:03 +04:00
*
* Modified for S3C2410 from ohci - sa1111 . c , ohci - omap . c and ohci - lh7a40 . c
* by Ben Dooks , < ben @ simtec . co . uk >
* Copyright ( C ) 2004 Simtec Electronics
*
* Thanks to basprog @ mail . ru for updates to newer kernels
*
* This file is licenced under the GPL .
*/
2005-10-29 22:07:23 +04:00
# include <linux/platform_device.h>
2006-01-07 19:15:52 +03:00
# include <linux/clk.h>
2005-10-29 22:07:23 +04:00
2008-08-05 19:14:15 +04:00
# include <mach/hardware.h>
# include <mach/usb-control.h>
2005-07-29 23:18:03 +04:00
# define valid_port(idx) ((idx) == 1 || (idx) == 2)
/* clock device associated with the hcd */
static struct clk * clk ;
2006-04-02 04:45:00 +04:00
static struct clk * usb_clk ;
2005-07-29 23:18:03 +04:00
/* forward definitions */
static void s3c2410_hcd_oc ( struct s3c2410_hcd_info * info , int port_oc ) ;
/* conversion functions */
2006-03-22 01:54:47 +03:00
static struct s3c2410_hcd_info * to_s3c2410_info ( struct usb_hcd * hcd )
2005-07-29 23:18:03 +04:00
{
return hcd - > self . controller - > platform_data ;
}
static void s3c2410_start_hc ( struct platform_device * dev , struct usb_hcd * hcd )
{
struct s3c2410_hcd_info * info = dev - > dev . platform_data ;
dev_dbg ( & dev - > dev , " s3c2410_start_hc: \n " ) ;
2006-04-02 04:45:00 +04:00
clk_enable ( usb_clk ) ;
mdelay ( 2 ) ; /* let the bus clock stabilise */
2005-07-29 23:18:03 +04:00
clk_enable ( clk ) ;
if ( info ! = NULL ) {
info - > hcd = hcd ;
info - > report_oc = s3c2410_hcd_oc ;
if ( info - > enable_oc ! = NULL ) {
( info - > enable_oc ) ( info , 1 ) ;
}
}
}
static void s3c2410_stop_hc ( struct platform_device * dev )
{
struct s3c2410_hcd_info * info = dev - > dev . platform_data ;
dev_dbg ( & dev - > dev , " s3c2410_stop_hc: \n " ) ;
if ( info ! = NULL ) {
info - > report_oc = NULL ;
info - > hcd = NULL ;
if ( info - > enable_oc ! = NULL ) {
( info - > enable_oc ) ( info , 0 ) ;
}
}
clk_disable ( clk ) ;
2006-04-02 04:45:00 +04:00
clk_disable ( usb_clk ) ;
2005-07-29 23:18:03 +04:00
}
/* ohci_s3c2410_hub_status_data
*
* update the status data from the hub with anything that
* has been detected by our system
*/
static int
ohci_s3c2410_hub_status_data ( struct usb_hcd * hcd , char * buf )
{
struct s3c2410_hcd_info * info = to_s3c2410_info ( hcd ) ;
struct s3c2410_hcd_port * port ;
int orig ;
int portno ;
orig = ohci_hub_status_data ( hcd , buf ) ;
if ( info = = NULL )
return orig ;
port = & info - > port [ 0 ] ;
/* mark any changed port as changed */
for ( portno = 0 ; portno < 2 ; port + + , portno + + ) {
if ( port - > oc_changed = = 1 & &
port - > flags & S3C_HCDFLG_USED ) {
dev_dbg ( hcd - > self . controller ,
" oc change on port %d \n " , portno ) ;
if ( orig < 1 )
orig = 1 ;
buf [ 0 ] | = 1 < < ( portno + 1 ) ;
}
}
return orig ;
}
/* s3c2410_usb_set_power
*
* configure the power on a port , by calling the platform device
* routine registered with the platform device
*/
static void s3c2410_usb_set_power ( struct s3c2410_hcd_info * info ,
int port , int to )
{
if ( info = = NULL )
return ;
if ( info - > power_control ! = NULL ) {
info - > port [ port - 1 ] . power = to ;
2005-08-09 18:04:00 +04:00
( info - > power_control ) ( port - 1 , to ) ;
2005-07-29 23:18:03 +04:00
}
}
/* ohci_s3c2410_hub_control
*
* look at control requests to the hub , and see if we need
* to take any action or over - ride the results from the
* request .
*/
static int ohci_s3c2410_hub_control (
struct usb_hcd * hcd ,
u16 typeReq ,
u16 wValue ,
u16 wIndex ,
char * buf ,
u16 wLength )
{
struct s3c2410_hcd_info * info = to_s3c2410_info ( hcd ) ;
struct usb_hub_descriptor * desc ;
int ret = - EINVAL ;
u32 * data = ( u32 * ) buf ;
dev_dbg ( hcd - > self . controller ,
" s3c2410_hub_control(%p,0x%04x,0x%04x,0x%04x,%p,%04x) \n " ,
hcd , typeReq , wValue , wIndex , buf , wLength ) ;
2006-03-28 13:56:53 +04:00
/* if we are only an humble host without any special capabilities
2005-07-29 23:18:03 +04:00
* process the request straight away and exit */
if ( info = = NULL ) {
ret = ohci_hub_control ( hcd , typeReq , wValue ,
wIndex , buf , wLength ) ;
goto out ;
}
/* check the request to see if it needs handling */
switch ( typeReq ) {
case SetPortFeature :
if ( wValue = = USB_PORT_FEAT_POWER ) {
dev_dbg ( hcd - > self . controller , " SetPortFeat: POWER \n " ) ;
s3c2410_usb_set_power ( info , wIndex , 1 ) ;
goto out ;
}
break ;
case ClearPortFeature :
switch ( wValue ) {
case USB_PORT_FEAT_C_OVER_CURRENT :
dev_dbg ( hcd - > self . controller ,
" ClearPortFeature: C_OVER_CURRENT \n " ) ;
if ( valid_port ( wIndex ) ) {
info - > port [ wIndex - 1 ] . oc_changed = 0 ;
info - > port [ wIndex - 1 ] . oc_status = 0 ;
}
goto out ;
case USB_PORT_FEAT_OVER_CURRENT :
dev_dbg ( hcd - > self . controller ,
" ClearPortFeature: OVER_CURRENT \n " ) ;
if ( valid_port ( wIndex ) ) {
info - > port [ wIndex - 1 ] . oc_status = 0 ;
}
goto out ;
case USB_PORT_FEAT_POWER :
dev_dbg ( hcd - > self . controller ,
" ClearPortFeature: POWER \n " ) ;
if ( valid_port ( wIndex ) ) {
s3c2410_usb_set_power ( info , wIndex , 0 ) ;
return 0 ;
}
}
break ;
}
ret = ohci_hub_control ( hcd , typeReq , wValue , wIndex , buf , wLength ) ;
if ( ret )
goto out ;
switch ( typeReq ) {
case GetHubDescriptor :
/* update the hub's descriptor */
desc = ( struct usb_hub_descriptor * ) buf ;
if ( info - > power_control = = NULL )
return ret ;
dev_dbg ( hcd - > self . controller , " wHubCharacteristics 0x%04x \n " ,
desc - > wHubCharacteristics ) ;
/* remove the old configurations for power-switching, and
* over - current protection , and insert our new configuration
*/
desc - > wHubCharacteristics & = ~ cpu_to_le16 ( HUB_CHAR_LPSM ) ;
desc - > wHubCharacteristics | = cpu_to_le16 ( 0x0001 ) ;
if ( info - > enable_oc ) {
desc - > wHubCharacteristics & = ~ cpu_to_le16 ( HUB_CHAR_OCPM ) ;
desc - > wHubCharacteristics | = cpu_to_le16 ( 0x0008 | 0x0001 ) ;
}
dev_dbg ( hcd - > self . controller , " wHubCharacteristics after 0x%04x \n " ,
desc - > wHubCharacteristics ) ;
return ret ;
case GetPortStatus :
/* check port status */
dev_dbg ( hcd - > self . controller , " GetPortStatus(%d) \n " , wIndex ) ;
if ( valid_port ( wIndex ) ) {
if ( info - > port [ wIndex - 1 ] . oc_changed ) {
* data | = cpu_to_le32 ( RH_PS_OCIC ) ;
}
if ( info - > port [ wIndex - 1 ] . oc_status ) {
* data | = cpu_to_le32 ( RH_PS_POCI ) ;
}
}
}
out :
return ret ;
}
/* s3c2410_hcd_oc
*
* handle an over - current report
*/
static void s3c2410_hcd_oc ( struct s3c2410_hcd_info * info , int port_oc )
{
struct s3c2410_hcd_port * port ;
struct usb_hcd * hcd ;
unsigned long flags ;
int portno ;
if ( info = = NULL )
return ;
port = & info - > port [ 0 ] ;
hcd = info - > hcd ;
local_irq_save ( flags ) ;
for ( portno = 0 ; portno < 2 ; port + + , portno + + ) {
if ( port_oc & ( 1 < < portno ) & &
port - > flags & S3C_HCDFLG_USED ) {
port - > oc_status = 1 ;
port - > oc_changed = 1 ;
/* ok, once over-current is detected,
the port needs to be powered down */
s3c2410_usb_set_power ( info , portno + 1 , 0 ) ;
}
}
local_irq_restore ( flags ) ;
}
/* may be called without controller electrically present */
/* may be called with controller, bus, and devices active */
/*
* usb_hcd_s3c2410_remove - shutdown processing for HCD
* @ dev : USB Host Controller being removed
* Context : ! in_interrupt ( )
*
* Reverses the effect of usb_hcd_3c2410_probe ( ) , first invoking
* the HCD ' s stop ( ) method . It is always called from a thread
* context , normally " rmmod " , " apmd " , or something similar .
*
*/
2006-03-22 01:54:47 +03:00
static void
usb_hcd_s3c2410_remove ( struct usb_hcd * hcd , struct platform_device * dev )
2005-07-29 23:18:03 +04:00
{
usb_remove_hcd ( hcd ) ;
s3c2410_stop_hc ( dev ) ;
iounmap ( hcd - > regs ) ;
release_mem_region ( hcd - > rsrc_start , hcd - > rsrc_len ) ;
usb_put_hcd ( hcd ) ;
}
/**
* usb_hcd_s3c2410_probe - initialize S3C2410 - based HCDs
* Context : ! in_interrupt ( )
*
* Allocates basic resources for this USB host controller , and
* then invokes the start ( ) method for the HCD associated with it
* through the hotplug entry ' s driver_data .
*
*/
2006-03-22 01:54:47 +03:00
static int usb_hcd_s3c2410_probe ( const struct hc_driver * driver ,
struct platform_device * dev )
2005-07-29 23:18:03 +04:00
{
struct usb_hcd * hcd = NULL ;
int retval ;
s3c2410_usb_set_power ( dev - > dev . platform_data , 1 , 1 ) ;
2005-08-09 18:04:00 +04:00
s3c2410_usb_set_power ( dev - > dev . platform_data , 2 , 1 ) ;
2005-07-29 23:18:03 +04:00
hcd = usb_create_hcd ( driver , & dev - > dev , " s3c24xx " ) ;
if ( hcd = = NULL )
return - ENOMEM ;
hcd - > rsrc_start = dev - > resource [ 0 ] . start ;
hcd - > rsrc_len = dev - > resource [ 0 ] . end - dev - > resource [ 0 ] . start + 1 ;
if ( ! request_mem_region ( hcd - > rsrc_start , hcd - > rsrc_len , hcd_name ) ) {
2007-10-18 14:06:30 +04:00
dev_err ( & dev - > dev , " request_mem_region failed \n " ) ;
2005-07-29 23:18:03 +04:00
retval = - EBUSY ;
2006-04-02 04:45:00 +04:00
goto err_put ;
2005-07-29 23:18:03 +04:00
}
2006-04-02 04:45:00 +04:00
clk = clk_get ( & dev - > dev , " usb-host " ) ;
2005-07-29 23:18:03 +04:00
if ( IS_ERR ( clk ) ) {
dev_err ( & dev - > dev , " cannot get usb-host clock \n " ) ;
retval = - ENOENT ;
2006-04-02 04:45:00 +04:00
goto err_mem ;
}
2006-06-27 02:36:07 +04:00
usb_clk = clk_get ( & dev - > dev , " usb-bus-host " ) ;
2006-04-02 04:45:00 +04:00
if ( IS_ERR ( usb_clk ) ) {
dev_err ( & dev - > dev , " cannot get usb-host clock \n " ) ;
retval = - ENOENT ;
goto err_clk ;
2005-07-29 23:18:03 +04:00
}
s3c2410_start_hc ( dev , hcd ) ;
hcd - > regs = ioremap ( hcd - > rsrc_start , hcd - > rsrc_len ) ;
if ( ! hcd - > regs ) {
dev_err ( & dev - > dev , " ioremap failed \n " ) ;
retval = - ENOMEM ;
2006-04-02 04:45:00 +04:00
goto err_ioremap ;
2005-07-29 23:18:03 +04:00
}
ohci_hcd_init ( hcd_to_ohci ( hcd ) ) ;
2006-07-02 06:29:44 +04:00
retval = usb_add_hcd ( hcd , dev - > resource [ 1 ] . start , IRQF_DISABLED ) ;
2005-07-29 23:18:03 +04:00
if ( retval ! = 0 )
2006-04-02 04:45:00 +04:00
goto err_ioremap ;
2005-07-29 23:18:03 +04:00
return 0 ;
2006-04-02 04:45:00 +04:00
err_ioremap :
2005-07-29 23:18:03 +04:00
s3c2410_stop_hc ( dev ) ;
iounmap ( hcd - > regs ) ;
2006-04-02 04:45:00 +04:00
clk_put ( usb_clk ) ;
err_clk :
2005-07-29 23:18:03 +04:00
clk_put ( clk ) ;
2006-04-02 04:45:00 +04:00
err_mem :
2005-07-29 23:18:03 +04:00
release_mem_region ( hcd - > rsrc_start , hcd - > rsrc_len ) ;
2006-04-02 04:45:00 +04:00
err_put :
2005-07-29 23:18:03 +04:00
usb_put_hcd ( hcd ) ;
return retval ;
}
/*-------------------------------------------------------------------------*/
static int
ohci_s3c2410_start ( struct usb_hcd * hcd )
{
struct ohci_hcd * ohci = hcd_to_ohci ( hcd ) ;
int ret ;
if ( ( ret = ohci_init ( ohci ) ) < 0 )
return ret ;
if ( ( ret = ohci_run ( ohci ) ) < 0 ) {
err ( " can't start %s " , hcd - > self . bus_name ) ;
ohci_stop ( hcd ) ;
return ret ;
}
return 0 ;
}
static const struct hc_driver ohci_s3c2410_hc_driver = {
. description = hcd_name ,
. product_desc = " S3C24XX OHCI " ,
. hcd_priv_size = sizeof ( struct ohci_hcd ) ,
/*
* generic hardware linkage
*/
. irq = ohci_irq ,
. flags = HCD_USB11 | HCD_MEMORY ,
/*
* basic lifecycle operations
*/
. start = ohci_s3c2410_start ,
. stop = ohci_stop ,
2006-12-05 14:18:31 +03:00
. shutdown = ohci_shutdown ,
2005-07-29 23:18:03 +04:00
/*
* 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_s3c2410_hub_status_data ,
. hub_control = ohci_s3c2410_hub_control ,
2005-09-14 06:59:11 +04:00
# ifdef CONFIG_PM
2005-10-14 01:08:02 +04:00
. bus_suspend = ohci_bus_suspend ,
. bus_resume = ohci_bus_resume ,
2005-07-29 23:18:03 +04:00
# endif
2005-09-23 09:32:11 +04:00
. start_port_reset = ohci_start_port_reset ,
2005-07-29 23:18:03 +04:00
} ;
/* device driver */
2005-11-10 01:32:44 +03:00
static int ohci_hcd_s3c2410_drv_probe ( struct platform_device * pdev )
2005-07-29 23:18:03 +04:00
{
return usb_hcd_s3c2410_probe ( & ohci_s3c2410_hc_driver , pdev ) ;
}
2005-11-10 01:32:44 +03:00
static int ohci_hcd_s3c2410_drv_remove ( struct platform_device * pdev )
2005-07-29 23:18:03 +04:00
{
2005-11-10 01:32:44 +03:00
struct usb_hcd * hcd = platform_get_drvdata ( pdev ) ;
2005-07-29 23:18:03 +04:00
usb_hcd_s3c2410_remove ( hcd , pdev ) ;
return 0 ;
}
2005-11-10 01:32:44 +03:00
static struct platform_driver ohci_hcd_s3c2410_driver = {
2005-07-29 23:18:03 +04:00
. probe = ohci_hcd_s3c2410_drv_probe ,
. remove = ohci_hcd_s3c2410_drv_remove ,
2006-12-05 14:18:31 +03:00
. shutdown = usb_hcd_platform_shutdown ,
2005-07-29 23:18:03 +04:00
/*.suspend = ohci_hcd_s3c2410_drv_suspend, */
/*.resume = ohci_hcd_s3c2410_drv_resume, */
2005-11-10 01:32:44 +03:00
. driver = {
. owner = THIS_MODULE ,
. name = " s3c2410-ohci " ,
} ,
2005-07-29 23:18:03 +04:00
} ;
2008-04-11 08:29:22 +04:00
MODULE_ALIAS ( " platform:s3c2410-ohci " ) ;