2017-11-03 13:28:30 +03:00
// SPDX-License-Identifier: GPL-1.0+
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 .
*/
2006-01-07 19:15:52 +03:00
# include <linux/clk.h>
2013-09-21 15:08:43 +04:00
# include <linux/io.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/platform_device.h>
2012-08-24 17:22:12 +04:00
# include <linux/platform_data/usb-ohci-s3c2410.h>
2013-09-21 15:08:43 +04:00
# include <linux/usb.h>
# include <linux/usb/hcd.h>
# include "ohci.h"
2005-07-29 23:18:03 +04:00
# define valid_port(idx) ((idx) == 1 || (idx) == 2)
/* clock device associated with the hcd */
2013-09-21 15:08:43 +04:00
# define DRIVER_DESC "OHCI S3C2410 driver"
static const char hcd_name [ ] = " ohci-s3c2410 " ;
2005-07-29 23:18:03 +04:00
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
2016-12-03 06:46:55 +03:00
static struct hc_driver __read_mostly ohci_s3c2410_hc_driver ;
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
{
2013-07-30 14:59:40 +04:00
return dev_get_platdata ( hcd - > self . controller ) ;
2005-07-29 23:18:03 +04:00
}
static void s3c2410_start_hc ( struct platform_device * dev , struct usb_hcd * hcd )
{
2013-07-30 14:59:40 +04:00
struct s3c2410_hcd_info * info = dev_get_platdata ( & dev - > dev ) ;
2005-07-29 23:18:03 +04:00
dev_dbg ( & dev - > dev , " s3c2410_start_hc: \n " ) ;
2006-04-02 04:45:00 +04:00
2013-08-25 21:00:37 +04:00
clk_prepare_enable ( usb_clk ) ;
2006-04-02 04:45:00 +04:00
mdelay ( 2 ) ; /* let the bus clock stabilise */
2013-08-25 21:00:37 +04:00
clk_prepare_enable ( clk ) ;
2005-07-29 23:18:03 +04:00
if ( info ! = NULL ) {
info - > hcd = hcd ;
info - > report_oc = s3c2410_hcd_oc ;
2011-05-04 11:45:47 +04:00
if ( info - > enable_oc ! = NULL )
2005-07-29 23:18:03 +04:00
( info - > enable_oc ) ( info , 1 ) ;
}
}
static void s3c2410_stop_hc ( struct platform_device * dev )
{
2013-07-30 14:59:40 +04:00
struct s3c2410_hcd_info * info = dev_get_platdata ( & dev - > dev ) ;
2005-07-29 23:18:03 +04:00
dev_dbg ( & dev - > dev , " s3c2410_stop_hc: \n " ) ;
if ( info ! = NULL ) {
info - > report_oc = NULL ;
info - > hcd = NULL ;
2011-05-04 11:45:47 +04:00
if ( info - > enable_oc ! = NULL )
2005-07-29 23:18:03 +04:00
( info - > enable_oc ) ( info , 0 ) ;
}
2013-08-25 21:00:37 +04:00
clk_disable_unprepare ( clk ) ;
clk_disable_unprepare ( 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
2011-05-04 11:45:47 +04:00
ohci_s3c2410_hub_status_data ( struct usb_hcd * hcd , char * buf )
2005-07-29 23:18:03 +04:00
{
struct s3c2410_hcd_info * info = to_s3c2410_info ( hcd ) ;
struct s3c2410_hcd_port * port ;
int orig ;
int portno ;
2014-04-16 20:00:09 +04:00
orig = ohci_hub_status_data ( hcd , buf ) ;
2005-07-29 23:18:03 +04:00
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 .
*/
2011-05-04 11:45:47 +04:00
static int ohci_s3c2410_hub_control (
2005-07-29 23:18:03 +04:00
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 ) {
2014-04-16 20:00:09 +04:00
ret = ohci_hub_control ( hcd , typeReq , wValue ,
2005-07-29 23:18:03 +04:00
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 " ) ;
2011-05-04 11:45:47 +04:00
if ( valid_port ( wIndex ) )
2005-07-29 23:18:03 +04:00
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 ;
}
2014-04-16 20:00:09 +04:00
ret = ohci_hub_control ( hcd , typeReq , wValue , wIndex , buf , wLength ) ;
2005-07-29 23:18:03 +04:00
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 ) ;
2015-01-19 01:44:12 +03:00
desc - > wHubCharacteristics | = cpu_to_le16 (
HUB_CHAR_INDV_PORT_LPSM ) ;
2005-07-29 23:18:03 +04:00
if ( info - > enable_oc ) {
2011-05-04 11:45:47 +04:00
desc - > wHubCharacteristics & = ~ cpu_to_le16 (
HUB_CHAR_OCPM ) ;
desc - > wHubCharacteristics | = cpu_to_le16 (
2015-01-19 01:44:12 +03:00
HUB_CHAR_INDV_PORT_OCPM ) ;
2005-07-29 23:18:03 +04:00
}
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 ) ) {
2011-05-04 11:45:47 +04:00
if ( info - > port [ wIndex - 1 ] . oc_changed )
2005-07-29 23:18:03 +04:00
* data | = cpu_to_le32 ( RH_PS_OCIC ) ;
2011-05-04 11:45:47 +04:00
if ( info - > port [ wIndex - 1 ] . oc_status )
2005-07-29 23:18:03 +04:00
* 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 */
/*
2016-12-03 06:46:55 +03:00
* ohci_hcd_s3c2410_remove - shutdown processing for HCD
2005-07-29 23:18:03 +04:00
* @ dev : USB Host Controller being removed
* Context : ! in_interrupt ( )
*
2016-12-03 06:46:55 +03:00
* Reverses the effect of ohci_hcd_3c2410_probe ( ) , first invoking
2005-07-29 23:18:03 +04:00
* the HCD ' s stop ( ) method . It is always called from a thread
* context , normally " rmmod " , " apmd " , or something similar .
*
*/
2016-12-03 06:46:55 +03:00
static int
ohci_hcd_s3c2410_remove ( struct platform_device * dev )
2005-07-29 23:18:03 +04:00
{
2016-12-03 06:46:55 +03:00
struct usb_hcd * hcd = platform_get_drvdata ( dev ) ;
2005-07-29 23:18:03 +04:00
usb_remove_hcd ( hcd ) ;
s3c2410_stop_hc ( dev ) ;
usb_put_hcd ( hcd ) ;
2016-12-03 06:46:55 +03:00
return 0 ;
2005-07-29 23:18:03 +04:00
}
/**
2016-12-03 06:46:55 +03:00
* ohci_hcd_s3c2410_probe - initialize S3C2410 - based HCDs
2005-07-29 23:18:03 +04:00
* 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 .
*
*/
2016-12-03 06:46:55 +03:00
static int ohci_hcd_s3c2410_probe ( struct platform_device * dev )
2005-07-29 23:18:03 +04:00
{
struct usb_hcd * hcd = NULL ;
2013-07-30 14:59:40 +04:00
struct s3c2410_hcd_info * info = dev_get_platdata ( & dev - > dev ) ;
2005-07-29 23:18:03 +04:00
int retval ;
2013-07-30 14:59:40 +04:00
s3c2410_usb_set_power ( info , 1 , 1 ) ;
s3c2410_usb_set_power ( info , 2 , 1 ) ;
2005-07-29 23:18:03 +04:00
2016-12-03 06:46:55 +03:00
hcd = usb_create_hcd ( & ohci_s3c2410_hc_driver , & dev - > dev , " s3c24xx " ) ;
2005-07-29 23:18:03 +04:00
if ( hcd = = NULL )
return - ENOMEM ;
hcd - > rsrc_start = dev - > resource [ 0 ] . start ;
2011-04-14 16:09:16 +04:00
hcd - > rsrc_len = resource_size ( & dev - > resource [ 0 ] ) ;
2005-07-29 23:18:03 +04:00
2013-01-21 14:09:22 +04:00
hcd - > regs = devm_ioremap_resource ( & dev - > dev , & dev - > resource [ 0 ] ) ;
if ( IS_ERR ( hcd - > regs ) ) {
retval = PTR_ERR ( hcd - > regs ) ;
2006-04-02 04:45:00 +04:00
goto err_put ;
2005-07-29 23:18:03 +04:00
}
2012-10-08 06:28:25 +04:00
clk = devm_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 " ) ;
2011-05-05 03:46:07 +04:00
retval = PTR_ERR ( clk ) ;
2012-10-08 06:28:25 +04:00
goto err_put ;
2006-04-02 04:45:00 +04:00
}
2012-10-08 06:28:25 +04:00
usb_clk = devm_clk_get ( & dev - > dev , " usb-bus-host " ) ;
2006-04-02 04:45:00 +04:00
if ( IS_ERR ( usb_clk ) ) {
2009-02-27 02:03:15 +03:00
dev_err ( & dev - > dev , " cannot get usb-bus-host clock \n " ) ;
2011-05-05 03:46:07 +04:00
retval = PTR_ERR ( usb_clk ) ;
2012-10-08 06:28:25 +04:00
goto err_put ;
2005-07-29 23:18:03 +04:00
}
s3c2410_start_hc ( dev , hcd ) ;
2011-09-07 12:10:52 +04:00
retval = usb_add_hcd ( hcd , dev - > resource [ 1 ] . start , 0 ) ;
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
2013-11-05 06:46:02 +04:00
device_wakeup_enable ( hcd - > self . controller ) ;
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 ) ;
2006-04-02 04:45:00 +04:00
err_put :
2005-07-29 23:18:03 +04:00
usb_put_hcd ( hcd ) ;
return retval ;
}
/*-------------------------------------------------------------------------*/
2011-11-28 10:56:06 +04:00
# ifdef CONFIG_PM
static int ohci_hcd_s3c2410_drv_suspend ( struct device * dev )
{
struct usb_hcd * hcd = dev_get_drvdata ( dev ) ;
struct platform_device * pdev = to_platform_device ( dev ) ;
2013-11-13 16:10:18 +04:00
bool do_wakeup = device_may_wakeup ( dev ) ;
2011-11-28 10:56:06 +04:00
int rc = 0 ;
2013-11-13 16:10:18 +04:00
rc = ohci_suspend ( hcd , do_wakeup ) ;
if ( rc )
return rc ;
2011-11-28 10:56:06 +04:00
s3c2410_stop_hc ( pdev ) ;
return rc ;
}
static int ohci_hcd_s3c2410_drv_resume ( struct device * dev )
{
struct usb_hcd * hcd = dev_get_drvdata ( dev ) ;
struct platform_device * pdev = to_platform_device ( dev ) ;
s3c2410_start_hc ( pdev , hcd ) ;
2012-10-08 17:11:29 +04:00
ohci_resume ( hcd , false ) ;
2011-11-28 10:56:06 +04:00
return 0 ;
}
# else
# define ohci_hcd_s3c2410_drv_suspend NULL
# define ohci_hcd_s3c2410_drv_resume NULL
# endif
static const struct dev_pm_ops ohci_hcd_s3c2410_pm_ops = {
. suspend = ohci_hcd_s3c2410_drv_suspend ,
. resume = ohci_hcd_s3c2410_drv_resume ,
} ;
2016-11-25 17:47:29 +03:00
static const struct of_device_id ohci_hcd_s3c2410_dt_ids [ ] = {
{ . compatible = " samsung,s3c2410-ohci " } ,
{ /* sentinel */ }
} ;
MODULE_DEVICE_TABLE ( of , ohci_hcd_s3c2410_dt_ids ) ;
2005-11-10 01:32:44 +03:00
static struct platform_driver ohci_hcd_s3c2410_driver = {
2016-12-03 06:46:55 +03:00
. probe = ohci_hcd_s3c2410_probe ,
. remove = ohci_hcd_s3c2410_remove ,
2006-12-05 14:18:31 +03:00
. shutdown = usb_hcd_platform_shutdown ,
2005-11-10 01:32:44 +03:00
. driver = {
. name = " s3c2410-ohci " ,
2011-11-28 10:56:06 +04:00
. pm = & ohci_hcd_s3c2410_pm_ops ,
2016-11-25 17:47:29 +03:00
. of_match_table = ohci_hcd_s3c2410_dt_ids ,
2005-11-10 01:32:44 +03:00
} ,
2005-07-29 23:18:03 +04:00
} ;
2013-09-21 15:08:43 +04:00
static int __init ohci_s3c2410_init ( void )
{
if ( usb_disabled ( ) )
return - ENODEV ;
pr_info ( " %s: " DRIVER_DESC " \n " , hcd_name ) ;
ohci_init_driver ( & ohci_s3c2410_hc_driver , NULL ) ;
/*
* The Samsung HW has some unusual quirks , which require
* Sumsung - specific workarounds . We override certain hc_driver
* functions here to achieve that . We explicitly do not enhance
* ohci_driver_overrides to allow this more easily , since this
* is an unusual case , and we don ' t want to encourage others to
* override these functions by making it too easy .
*/
ohci_s3c2410_hc_driver . hub_status_data = ohci_s3c2410_hub_status_data ;
ohci_s3c2410_hc_driver . hub_control = ohci_s3c2410_hub_control ;
return platform_driver_register ( & ohci_hcd_s3c2410_driver ) ;
}
module_init ( ohci_s3c2410_init ) ;
static void __exit ohci_s3c2410_cleanup ( void )
{
platform_driver_unregister ( & ohci_hcd_s3c2410_driver ) ;
}
module_exit ( ohci_s3c2410_cleanup ) ;
MODULE_DESCRIPTION ( DRIVER_DESC ) ;
MODULE_LICENSE ( " GPL " ) ;
2008-04-11 08:29:22 +04:00
MODULE_ALIAS ( " platform:s3c2410-ohci " ) ;