2013-04-23 01:00:19 +04:00
/*
* platform . c - DesignWare HS OTG Controller platform driver
*
* Copyright ( C ) Matthijs Kooijman < matthijs @ stdin . nl >
*
* Redistribution and use in source and binary forms , with or without
* modification , are permitted provided that the following conditions
* are met :
* 1. Redistributions of source code must retain the above copyright
* notice , this list of conditions , and the following disclaimer ,
* without modification .
* 2. Redistributions in binary form must reproduce the above copyright
* notice , this list of conditions and the following disclaimer in the
* documentation and / or other materials provided with the distribution .
* 3. The names of the above - listed copyright holders may not be used
* to endorse or promote products derived from this software without
* specific prior written permission .
*
* ALTERNATIVELY , this software may be distributed under the terms of the
* GNU General Public License ( " GPL " ) as published by the Free Software
* Foundation ; either version 2 of the License , or ( at your option ) any
* later version .
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS " AS
* IS " AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED . IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT , INDIRECT , INCIDENTAL , SPECIAL ,
* EXEMPLARY , OR CONSEQUENTIAL DAMAGES ( INCLUDING , BUT NOT LIMITED TO ,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES ; LOSS OF USE , DATA , OR
* PROFITS ; OR BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY , WHETHER IN CONTRACT , STRICT LIABILITY , OR TORT ( INCLUDING
* NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE .
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/slab.h>
2015-10-14 09:52:29 +03:00
# include <linux/clk.h>
2013-04-23 01:00:19 +04:00
# include <linux/device.h>
# include <linux/dma-mapping.h>
2013-11-27 05:58:01 +04:00
# include <linux/of_device.h>
2014-11-21 17:14:48 +03:00
# include <linux/mutex.h>
2013-04-23 01:00:19 +04:00
# include <linux/platform_device.h>
2015-10-14 09:52:29 +03:00
# include <linux/phy/phy.h>
# include <linux/platform_data/s3c-hsotg.h>
2016-08-10 16:53:34 +03:00
# include <linux/reset.h>
2013-04-23 01:00:19 +04:00
2014-08-06 05:01:50 +04:00
# include <linux/usb/of.h>
2013-04-23 01:00:19 +04:00
# include "core.h"
# include "hcd.h"
2015-04-29 23:08:59 +03:00
# include "debug.h"
2013-04-23 01:00:19 +04:00
static const char dwc2_driver_name [ ] = " dwc2 " ;
2015-12-17 22:16:31 +03:00
/*
* Check the dr_mode against the module configuration and hardware
* capabilities .
*
* The hardware , module , and dr_mode , can each be set to host , device ,
* or otg . Check that all these values are compatible and adjust the
* value of dr_mode if possible .
*
* actual
* HW MOD dr_mode dr_mode
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* HST HST any : HST
* HST DEV any : - - -
* HST OTG any : HST
*
* DEV HST any : - - -
* DEV DEV any : DEV
* DEV OTG any : DEV
*
* OTG HST any : HST
* OTG DEV any : DEV
* OTG OTG any : dr_mode
*/
static int dwc2_get_dr_mode ( struct dwc2_hsotg * hsotg )
{
enum usb_dr_mode mode ;
hsotg - > dr_mode = usb_get_dr_mode ( hsotg - > dev ) ;
if ( hsotg - > dr_mode = = USB_DR_MODE_UNKNOWN )
hsotg - > dr_mode = USB_DR_MODE_OTG ;
mode = hsotg - > dr_mode ;
if ( dwc2_hw_is_device ( hsotg ) ) {
if ( IS_ENABLED ( CONFIG_USB_DWC2_HOST ) ) {
dev_err ( hsotg - > dev ,
" Controller does not support host mode. \n " ) ;
return - EINVAL ;
}
mode = USB_DR_MODE_PERIPHERAL ;
} else if ( dwc2_hw_is_host ( hsotg ) ) {
if ( IS_ENABLED ( CONFIG_USB_DWC2_PERIPHERAL ) ) {
dev_err ( hsotg - > dev ,
" Controller does not support device mode. \n " ) ;
return - EINVAL ;
}
mode = USB_DR_MODE_HOST ;
} else {
if ( IS_ENABLED ( CONFIG_USB_DWC2_HOST ) )
mode = USB_DR_MODE_HOST ;
else if ( IS_ENABLED ( CONFIG_USB_DWC2_PERIPHERAL ) )
mode = USB_DR_MODE_PERIPHERAL ;
}
if ( mode ! = hsotg - > dr_mode ) {
dev_warn ( hsotg - > dev ,
" Configuration mismatch. dr_mode forced to %s \n " ,
mode = = USB_DR_MODE_HOST ? " host " : " device " ) ;
hsotg - > dr_mode = mode ;
}
return 0 ;
}
2015-10-14 09:52:29 +03:00
static int __dwc2_lowlevel_hw_enable ( struct dwc2_hsotg * hsotg )
{
struct platform_device * pdev = to_platform_device ( hsotg - > dev ) ;
int ret ;
ret = regulator_bulk_enable ( ARRAY_SIZE ( hsotg - > supplies ) ,
hsotg - > supplies ) ;
if ( ret )
return ret ;
2015-11-13 20:02:12 +03:00
if ( hsotg - > clk ) {
ret = clk_prepare_enable ( hsotg - > clk ) ;
if ( ret )
return ret ;
}
2015-10-14 09:52:29 +03:00
if ( hsotg - > uphy )
ret = usb_phy_init ( hsotg - > uphy ) ;
else if ( hsotg - > plat & & hsotg - > plat - > phy_init )
ret = hsotg - > plat - > phy_init ( pdev , hsotg - > plat - > phy_type ) ;
else {
ret = phy_power_on ( hsotg - > phy ) ;
if ( ret = = 0 )
ret = phy_init ( hsotg - > phy ) ;
}
return ret ;
}
/**
* dwc2_lowlevel_hw_enable - enable platform lowlevel hw resources
* @ hsotg : The driver state
*
* A wrapper for platform code responsible for controlling
* low - level USB platform resources ( phy , clock , regulators )
*/
int dwc2_lowlevel_hw_enable ( struct dwc2_hsotg * hsotg )
{
int ret = __dwc2_lowlevel_hw_enable ( hsotg ) ;
if ( ret = = 0 )
hsotg - > ll_hw_enabled = true ;
return ret ;
}
static int __dwc2_lowlevel_hw_disable ( struct dwc2_hsotg * hsotg )
{
struct platform_device * pdev = to_platform_device ( hsotg - > dev ) ;
int ret = 0 ;
if ( hsotg - > uphy )
usb_phy_shutdown ( hsotg - > uphy ) ;
else if ( hsotg - > plat & & hsotg - > plat - > phy_exit )
ret = hsotg - > plat - > phy_exit ( pdev , hsotg - > plat - > phy_type ) ;
else {
ret = phy_exit ( hsotg - > phy ) ;
if ( ret = = 0 )
ret = phy_power_off ( hsotg - > phy ) ;
}
if ( ret )
return ret ;
2015-11-13 20:02:12 +03:00
if ( hsotg - > clk )
clk_disable_unprepare ( hsotg - > clk ) ;
2015-10-14 09:52:29 +03:00
ret = regulator_bulk_disable ( ARRAY_SIZE ( hsotg - > supplies ) ,
hsotg - > supplies ) ;
return ret ;
}
/**
* dwc2_lowlevel_hw_disable - disable platform lowlevel hw resources
* @ hsotg : The driver state
*
* A wrapper for platform code responsible for controlling
* low - level USB platform resources ( phy , clock , regulators )
*/
int dwc2_lowlevel_hw_disable ( struct dwc2_hsotg * hsotg )
{
int ret = __dwc2_lowlevel_hw_disable ( hsotg ) ;
if ( ret = = 0 )
hsotg - > ll_hw_enabled = false ;
return ret ;
}
static int dwc2_lowlevel_hw_init ( struct dwc2_hsotg * hsotg )
{
int i , ret ;
2016-08-10 16:53:34 +03:00
hsotg - > reset = devm_reset_control_get_optional ( hsotg - > dev , " dwc2 " ) ;
if ( IS_ERR ( hsotg - > reset ) ) {
ret = PTR_ERR ( hsotg - > reset ) ;
switch ( ret ) {
case - ENOENT :
case - ENOTSUPP :
hsotg - > reset = NULL ;
break ;
default :
dev_err ( hsotg - > dev , " error getting reset control %d \n " ,
ret ) ;
return ret ;
}
}
if ( hsotg - > reset )
reset_control_deassert ( hsotg - > reset ) ;
2015-10-14 09:52:29 +03:00
/* Set default UTMI width */
hsotg - > phyif = GUSBCFG_PHYIF16 ;
/*
* Attempt to find a generic PHY , then look for an old style
* USB PHY and then fall back to pdata
*/
hsotg - > phy = devm_phy_get ( hsotg - > dev , " usb2-phy " ) ;
if ( IS_ERR ( hsotg - > phy ) ) {
2015-11-13 20:02:11 +03:00
ret = PTR_ERR ( hsotg - > phy ) ;
switch ( ret ) {
case - ENODEV :
case - ENOSYS :
hsotg - > phy = NULL ;
break ;
case - EPROBE_DEFER :
return ret ;
default :
dev_err ( hsotg - > dev , " error getting phy %d \n " , ret ) ;
return ret ;
}
}
if ( ! hsotg - > phy ) {
2015-10-14 09:52:29 +03:00
hsotg - > uphy = devm_usb_get_phy ( hsotg - > dev , USB_PHY_TYPE_USB2 ) ;
2015-11-13 20:02:11 +03:00
if ( IS_ERR ( hsotg - > uphy ) ) {
ret = PTR_ERR ( hsotg - > uphy ) ;
switch ( ret ) {
case - ENODEV :
case - ENXIO :
hsotg - > uphy = NULL ;
break ;
case - EPROBE_DEFER :
return ret ;
default :
dev_err ( hsotg - > dev , " error getting usb phy %d \n " ,
ret ) ;
return ret ;
}
}
2015-10-14 09:52:29 +03:00
}
2015-11-13 20:02:11 +03:00
hsotg - > plat = dev_get_platdata ( hsotg - > dev ) ;
2015-10-14 09:52:29 +03:00
if ( hsotg - > phy ) {
/*
* If using the generic PHY framework , check if the PHY bus
* width is 8 - bit and set the phyif appropriately .
*/
if ( phy_get_bus_width ( hsotg - > phy ) = = 8 )
hsotg - > phyif = GUSBCFG_PHYIF8 ;
}
/* Clock */
hsotg - > clk = devm_clk_get ( hsotg - > dev , " otg " ) ;
if ( IS_ERR ( hsotg - > clk ) ) {
hsotg - > clk = NULL ;
dev_dbg ( hsotg - > dev , " cannot get otg clock \n " ) ;
}
/* Regulators */
for ( i = 0 ; i < ARRAY_SIZE ( hsotg - > supplies ) ; i + + )
hsotg - > supplies [ i ] . supply = dwc2_hsotg_supply_names [ i ] ;
ret = devm_regulator_bulk_get ( hsotg - > dev , ARRAY_SIZE ( hsotg - > supplies ) ,
hsotg - > supplies ) ;
if ( ret ) {
dev_err ( hsotg - > dev , " failed to request supplies: %d \n " , ret ) ;
return ret ;
}
return 0 ;
}
2013-04-23 01:00:19 +04:00
/**
* dwc2_driver_remove ( ) - Called when the DWC_otg core is unregistered with the
* DWC_otg driver
*
* @ dev : Platform device
*
* This routine is called , for example , when the rmmod command is executed . The
* device may or may not be electrically present . If it is present , the driver
* stops device processing . Any resources used on behalf of this device are
* freed .
*/
static int dwc2_driver_remove ( struct platform_device * dev )
{
struct dwc2_hsotg * hsotg = platform_get_drvdata ( dev ) ;
2015-04-29 23:08:59 +03:00
dwc2_debugfs_exit ( hsotg ) ;
2015-03-10 15:41:10 +03:00
if ( hsotg - > hcd_enabled )
dwc2_hcd_remove ( hsotg ) ;
if ( hsotg - > gadget_enabled )
2015-08-07 02:11:54 +03:00
dwc2_hsotg_remove ( hsotg ) ;
2013-04-23 01:00:19 +04:00
2015-10-14 09:52:29 +03:00
if ( hsotg - > ll_hw_enabled )
dwc2_lowlevel_hw_disable ( hsotg ) ;
2016-08-10 16:53:34 +03:00
if ( hsotg - > reset )
reset_control_assert ( hsotg - > reset ) ;
2013-04-23 01:00:19 +04:00
return 0 ;
}
2015-12-18 21:30:59 +03:00
/**
* dwc2_driver_shutdown ( ) - Called on device shutdown
*
* @ dev : Platform device
*
* In specific conditions ( involving usb hubs ) dwc2 devices can create a
* lot of interrupts , even to the point of overwhelming devices running
* at low frequencies . Some devices need to do special clock handling
* at shutdown - time which may bring the system clock below the threshold
* of being able to handle the dwc2 interrupts . Disabling dwc2 - irqs
* prevents reboots / poweroffs from getting stuck in such cases .
*/
static void dwc2_driver_shutdown ( struct platform_device * dev )
{
struct dwc2_hsotg * hsotg = platform_get_drvdata ( dev ) ;
disable_irq ( hsotg - > irq ) ;
}
2013-04-23 01:00:19 +04:00
/**
* dwc2_driver_probe ( ) - Called when the DWC_otg core is bound to the DWC_otg
* driver
*
* @ dev : Platform device
*
* This routine creates the driver components required to control the device
* ( core , HCD , and PCD ) and initializes the device . The driver components are
* stored in a dwc2_hsotg structure . A reference to the dwc2_hsotg is saved
* in the device private data . This allows the driver to access the dwc2_hsotg
* structure on subsequent calls to driver methods for this device .
*/
static int dwc2_driver_probe ( struct platform_device * dev )
{
struct dwc2_hsotg * hsotg ;
struct resource * res ;
int retval ;
hsotg = devm_kzalloc ( & dev - > dev , sizeof ( * hsotg ) , GFP_KERNEL ) ;
if ( ! hsotg )
return - ENOMEM ;
hsotg - > dev = & dev - > dev ;
2013-05-17 12:52:55 +04:00
/*
* Use reasonable defaults so platforms don ' t have to provide these .
*/
if ( ! dev - > dev . dma_mask )
dev - > dev . dma_mask = & dev - > dev . coherent_dma_mask ;
2013-06-10 19:56:16 +04:00
retval = dma_set_coherent_mask ( & dev - > dev , DMA_BIT_MASK ( 32 ) ) ;
if ( retval )
return retval ;
2013-05-17 12:52:55 +04:00
2013-04-23 01:00:19 +04:00
res = platform_get_resource ( dev , IORESOURCE_MEM , 0 ) ;
hsotg - > regs = devm_ioremap_resource ( & dev - > dev , res ) ;
if ( IS_ERR ( hsotg - > regs ) )
return PTR_ERR ( hsotg - > regs ) ;
dev_dbg ( & dev - > dev , " mapped PA %08lx to VA %p \n " ,
( unsigned long ) res - > start , hsotg - > regs ) ;
2015-10-14 09:52:29 +03:00
retval = dwc2_lowlevel_hw_init ( hsotg ) ;
2015-04-29 23:09:05 +03:00
if ( retval )
return retval ;
2015-10-14 09:52:29 +03:00
spin_lock_init ( & hsotg - > lock ) ;
2015-12-18 21:30:59 +03:00
hsotg - > irq = platform_get_irq ( dev , 0 ) ;
if ( hsotg - > irq < 0 ) {
2015-11-13 00:08:34 +03:00
dev_err ( & dev - > dev , " missing IRQ resource \n " ) ;
2015-12-18 21:30:59 +03:00
return hsotg - > irq ;
2015-11-13 00:08:34 +03:00
}
dev_dbg ( hsotg - > dev , " registering common handler for irq%d \n " ,
2015-12-18 21:30:59 +03:00
hsotg - > irq ) ;
retval = devm_request_irq ( hsotg - > dev , hsotg - > irq ,
2015-11-13 00:08:34 +03:00
dwc2_handle_common_intr , IRQF_SHARED ,
dev_name ( hsotg - > dev ) , hsotg ) ;
if ( retval )
return retval ;
2015-10-14 09:52:29 +03:00
retval = dwc2_lowlevel_hw_enable ( hsotg ) ;
if ( retval )
return retval ;
2015-12-17 22:16:31 +03:00
retval = dwc2_get_dr_mode ( hsotg ) ;
if ( retval )
2016-04-28 06:20:56 +03:00
goto error ;
2015-12-17 22:16:31 +03:00
2016-01-12 03:32:14 +03:00
/*
* Reset before dwc2_get_hwparams ( ) then it could get power - on real
* reset value form registers .
*/
dwc2_core_reset_and_force_dr_mode ( hsotg ) ;
/* Detect config values from hardware */
2015-10-14 09:52:29 +03:00
retval = dwc2_get_hwparams ( hsotg ) ;
if ( retval )
goto error ;
2015-12-17 22:18:27 +03:00
dwc2_force_dr_mode ( hsotg ) ;
2015-12-17 22:16:58 +03:00
2016-11-04 03:55:55 +03:00
retval = dwc2_init_params ( hsotg ) ;
if ( retval )
goto error ;
2015-03-10 15:41:10 +03:00
if ( hsotg - > dr_mode ! = USB_DR_MODE_HOST ) {
2015-12-18 21:30:59 +03:00
retval = dwc2_gadget_init ( hsotg , hsotg - > irq ) ;
2015-03-10 15:41:10 +03:00
if ( retval )
2015-10-14 09:52:29 +03:00
goto error ;
2015-03-10 15:41:10 +03:00
hsotg - > gadget_enabled = 1 ;
}
if ( hsotg - > dr_mode ! = USB_DR_MODE_PERIPHERAL ) {
2015-12-18 21:30:59 +03:00
retval = dwc2_hcd_init ( hsotg , hsotg - > irq ) ;
2015-03-10 15:41:10 +03:00
if ( retval ) {
if ( hsotg - > gadget_enabled )
2015-08-07 02:11:54 +03:00
dwc2_hsotg_remove ( hsotg ) ;
2015-10-14 09:52:29 +03:00
goto error ;
2015-03-10 15:41:10 +03:00
}
hsotg - > hcd_enabled = 1 ;
}
2013-04-23 01:00:19 +04:00
platform_set_drvdata ( dev , hsotg ) ;
2015-04-29 23:08:59 +03:00
dwc2_debugfs_init ( hsotg ) ;
2015-10-14 09:52:29 +03:00
/* Gadget code manages lowlevel hw on its own */
if ( hsotg - > dr_mode = = USB_DR_MODE_PERIPHERAL )
dwc2_lowlevel_hw_disable ( hsotg ) ;
return 0 ;
error :
dwc2_lowlevel_hw_disable ( hsotg ) ;
2013-04-23 01:00:19 +04:00
return retval ;
}
2014-11-19 17:37:53 +03:00
static int __maybe_unused dwc2_suspend ( struct device * dev )
2014-11-11 20:13:34 +03:00
{
2014-11-11 20:13:35 +03:00
struct dwc2_hsotg * dwc2 = dev_get_drvdata ( dev ) ;
2014-11-11 20:13:34 +03:00
int ret = 0 ;
2015-10-14 09:52:29 +03:00
if ( dwc2_is_device_mode ( dwc2 ) )
dwc2_hsotg_suspend ( dwc2 ) ;
if ( dwc2 - > ll_hw_enabled )
ret = __dwc2_lowlevel_hw_disable ( dwc2 ) ;
2014-12-08 12:46:26 +03:00
2014-11-11 20:13:34 +03:00
return ret ;
}
2014-11-19 17:37:53 +03:00
static int __maybe_unused dwc2_resume ( struct device * dev )
2014-11-11 20:13:34 +03:00
{
2014-11-11 20:13:35 +03:00
struct dwc2_hsotg * dwc2 = dev_get_drvdata ( dev ) ;
2014-11-11 20:13:34 +03:00
int ret = 0 ;
2015-10-14 09:52:29 +03:00
if ( dwc2 - > ll_hw_enabled ) {
ret = __dwc2_lowlevel_hw_enable ( dwc2 ) ;
if ( ret )
return ret ;
}
if ( dwc2_is_device_mode ( dwc2 ) )
2015-08-07 02:11:54 +03:00
ret = dwc2_hsotg_resume ( dwc2 ) ;
2014-12-08 12:46:26 +03:00
2014-11-11 20:13:34 +03:00
return ret ;
}
2014-11-11 20:13:35 +03:00
static const struct dev_pm_ops dwc2_dev_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS ( dwc2_suspend , dwc2_resume )
} ;
2013-04-23 01:00:19 +04:00
static struct platform_driver dwc2_platform_driver = {
. driver = {
2013-11-12 23:07:19 +04:00
. name = dwc2_driver_name ,
2013-04-23 01:00:19 +04:00
. of_match_table = dwc2_of_match_table ,
2014-11-11 20:13:35 +03:00
. pm = & dwc2_dev_pm_ops ,
2013-04-23 01:00:19 +04:00
} ,
. probe = dwc2_driver_probe ,
. remove = dwc2_driver_remove ,
2015-12-18 21:30:59 +03:00
. shutdown = dwc2_driver_shutdown ,
2013-04-23 01:00:19 +04:00
} ;
module_platform_driver ( dwc2_platform_driver ) ;
MODULE_DESCRIPTION ( " DESIGNWARE HS OTG Platform Glue " ) ;
MODULE_AUTHOR ( " Matthijs Kooijman <matthijs@stdin.nl> " ) ;
MODULE_LICENSE ( " Dual BSD/GPL " ) ;