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>
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 " ;
2013-11-27 05:58:01 +04:00
static const struct dwc2_core_params params_bcm2835 = {
. otg_cap = 0 , /* HNP/SRP capable */
. otg_ver = 0 , /* 1.3 */
. dma_enable = 1 ,
. dma_desc_enable = 0 ,
. speed = 0 , /* High Speed */
. enable_dynamic_fifo = 1 ,
. en_multiple_tx_fifo = 1 ,
. host_rx_fifo_size = 774 , /* 774 DWORDs */
. host_nperio_tx_fifo_size = 256 , /* 256 DWORDs */
. host_perio_tx_fifo_size = 512 , /* 512 DWORDs */
. max_transfer_size = 65535 ,
. max_packet_count = 511 ,
. host_channels = 8 ,
. phy_type = 1 , /* UTMI */
. phy_utmi_width = 8 , /* 8 bits */
. phy_ulpi_ddr = 0 , /* Single */
. phy_ulpi_ext_vbus = 0 ,
. i2c_enable = 0 ,
. ulpi_fs_ls = 0 ,
. host_support_fs_ls_low_power = 0 ,
. host_ls_low_power_phy_clk = 0 , /* 48 MHz */
. ts_dline = 0 ,
. reload_ctl = 0 ,
. ahbcfg = 0x10 ,
2013-12-04 07:56:05 +04:00
. uframe_sched = 0 ,
2015-04-29 23:09:04 +03:00
. external_id_pin_ctl = - 1 ,
2015-04-29 23:09:19 +03:00
. hibernation = - 1 ,
2013-11-27 05:58:01 +04:00
} ;
2014-08-08 07:55:57 +04:00
static const struct dwc2_core_params params_rk3066 = {
. otg_cap = 2 , /* non-HNP/non-SRP */
. otg_ver = - 1 ,
. dma_enable = - 1 ,
. dma_desc_enable = 0 ,
. speed = - 1 ,
. enable_dynamic_fifo = 1 ,
. en_multiple_tx_fifo = - 1 ,
. host_rx_fifo_size = 520 , /* 520 DWORDs */
. host_nperio_tx_fifo_size = 128 , /* 128 DWORDs */
. host_perio_tx_fifo_size = 256 , /* 256 DWORDs */
. max_transfer_size = 65535 ,
. max_packet_count = - 1 ,
. host_channels = - 1 ,
. phy_type = - 1 ,
. phy_utmi_width = - 1 ,
. phy_ulpi_ddr = - 1 ,
. phy_ulpi_ext_vbus = - 1 ,
. i2c_enable = - 1 ,
. ulpi_fs_ls = - 1 ,
. host_support_fs_ls_low_power = - 1 ,
. host_ls_low_power_phy_clk = - 1 ,
. ts_dline = - 1 ,
. reload_ctl = - 1 ,
2015-10-21 02:33:53 +03:00
. ahbcfg = GAHBCFG_HBSTLEN_INCR16 < <
GAHBCFG_HBSTLEN_SHIFT ,
2014-08-08 07:55:57 +04:00
. uframe_sched = - 1 ,
2015-04-29 23:09:04 +03:00
. external_id_pin_ctl = - 1 ,
2015-04-29 23:09:19 +03:00
. hibernation = - 1 ,
2014-08-08 07:55:57 +04:00
} ;
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 ;
ret = clk_prepare_enable ( hsotg - > clk ) ;
if ( ret )
return ret ;
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 ;
clk_disable_unprepare ( hsotg - > clk ) ;
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 ;
/* 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 ) ) {
hsotg - > phy = NULL ;
hsotg - > uphy = devm_usb_get_phy ( hsotg - > dev , USB_PHY_TYPE_USB2 ) ;
if ( IS_ERR ( hsotg - > uphy ) )
hsotg - > uphy = NULL ;
else
hsotg - > plat = dev_get_platdata ( hsotg - > dev ) ;
}
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 ) ;
2013-04-23 01:00:19 +04:00
return 0 ;
}
2013-11-27 05:58:01 +04:00
static const struct of_device_id dwc2_of_match_table [ ] = {
{ . compatible = " brcm,bcm2835-usb " , . data = & params_bcm2835 } ,
2014-08-08 07:55:57 +04:00
{ . compatible = " rockchip,rk3066-usb " , . data = & params_rk3066 } ,
2013-11-27 05:58:01 +04:00
{ . compatible = " snps,dwc2 " , . data = NULL } ,
2014-11-11 20:13:34 +03:00
{ . compatible = " samsung,s3c6400-hsotg " , . data = NULL } ,
2013-11-27 05:58:01 +04:00
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , dwc2_of_match_table ) ;
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 )
{
2013-11-27 05:58:01 +04:00
const struct of_device_id * match ;
const struct dwc2_core_params * params ;
struct dwc2_core_params defparams ;
2013-04-23 01:00:19 +04:00
struct dwc2_hsotg * hsotg ;
struct resource * res ;
int retval ;
int irq ;
2013-11-27 05:58:01 +04:00
match = of_match_device ( dwc2_of_match_table , & dev - > dev ) ;
if ( match & & match - > data ) {
params = match - > data ;
} else {
/* Default all params to autodetect */
dwc2_set_all_params ( & defparams , - 1 ) ;
params = & defparams ;
2014-05-07 17:30:33 +04:00
/*
* Disable descriptor dma mode by default as the HW can support
* it , but does not support it for SPLIT transactions .
*/
defparams . dma_desc_enable = 0 ;
2013-11-27 05:58:01 +04:00
}
2013-04-23 01:00:19 +04:00
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
irq = platform_get_irq ( dev , 0 ) ;
if ( irq < 0 ) {
dev_err ( & dev - > dev , " missing IRQ resource \n " ) ;
2013-10-26 21:42:19 +04:00
return irq ;
2013-04-23 01:00:19 +04:00
}
2014-11-11 20:13:37 +03:00
dev_dbg ( hsotg - > dev , " registering common handler for irq%d \n " ,
irq ) ;
retval = devm_request_irq ( hsotg - > dev , irq ,
dwc2_handle_common_intr , IRQF_SHARED ,
dev_name ( hsotg - > dev ) , hsotg ) ;
if ( retval )
return retval ;
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-09-21 11:14:34 +03:00
hsotg - > dr_mode = usb_get_dr_mode ( & dev - > dev ) ;
2015-09-29 13:08:18 +03:00
if ( IS_ENABLED ( CONFIG_USB_DWC2_HOST ) & &
hsotg - > dr_mode ! = USB_DR_MODE_HOST ) {
hsotg - > dr_mode = USB_DR_MODE_HOST ;
dev_warn ( hsotg - > dev ,
" Configuration mismatch. Forcing host mode \n " ) ;
} else if ( IS_ENABLED ( CONFIG_USB_DWC2_PERIPHERAL ) & &
hsotg - > dr_mode ! = USB_DR_MODE_PERIPHERAL ) {
hsotg - > dr_mode = USB_DR_MODE_PERIPHERAL ;
dev_warn ( hsotg - > dev ,
" Configuration mismatch. Forcing peripheral mode \n " ) ;
}
2014-08-06 05:01:50 +04:00
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-04-29 23:09:05 +03:00
hsotg - > core_params = devm_kzalloc ( & dev - > dev ,
sizeof ( * hsotg - > core_params ) , GFP_KERNEL ) ;
if ( ! hsotg - > core_params )
return - ENOMEM ;
dwc2_set_all_params ( hsotg - > core_params , - 1 ) ;
2015-10-14 09:52:29 +03:00
retval = dwc2_lowlevel_hw_enable ( hsotg ) ;
if ( retval )
return retval ;
/* Detect config values from hardware */
retval = dwc2_get_hwparams ( hsotg ) ;
if ( retval )
goto error ;
2015-04-29 23:09:05 +03:00
/* Validate parameter values */
dwc2_set_parameters ( hsotg , params ) ;
2015-03-10 15:41:10 +03:00
if ( hsotg - > dr_mode ! = USB_DR_MODE_HOST ) {
retval = dwc2_gadget_init ( hsotg , irq ) ;
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-04-29 23:09:05 +03:00
retval = dwc2_hcd_init ( 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 ,
} ;
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 " ) ;