2017-11-03 11:28:30 +01:00
// SPDX-License-Identifier: GPL-2.0
2012-02-15 17:04:56 +09:00
/**
* dwc3 - exynos . c - Samsung EXYNOS DWC3 Specific Glue layer
*
* Copyright ( c ) 2012 Samsung Electronics Co . , Ltd .
* http : //www.samsung.com
*
* Author : Anton Tikhomirov < av . tikhomirov @ samsung . com >
*/
# include <linux/module.h>
# include <linux/kernel.h>
# include <linux/slab.h>
# include <linux/platform_device.h>
# include <linux/clk.h>
2012-11-03 18:00:27 +05:30
# include <linux/of.h>
2013-03-14 18:09:49 +05:30
# include <linux/of_platform.h>
2014-04-21 17:46:44 +05:30
# include <linux/regulator/consumer.h>
2012-02-15 17:04:56 +09:00
2018-09-18 10:16:51 +02:00
# define DWC3_EXYNOS_MAX_CLOCKS 4
struct dwc3_exynos_driverdata {
const char * clk_names [ DWC3_EXYNOS_MAX_CLOCKS ] ;
int num_clks ;
int suspend_clk_idx ;
} ;
2012-02-15 17:04:56 +09:00
struct dwc3_exynos {
struct device * dev ;
2018-09-18 10:16:51 +02:00
const char * * clk_names ;
struct clk * clks [ DWC3_EXYNOS_MAX_CLOCKS ] ;
int num_clks ;
int suspend_clk_idx ;
2014-11-21 19:05:46 +05:30
2014-04-21 17:46:44 +05:30
struct regulator * vdd33 ;
struct regulator * vdd10 ;
2012-02-15 17:04:56 +09:00
} ;
2013-03-14 18:09:49 +05:30
static int dwc3_exynos_remove_child ( struct device * dev , void * unused )
{
struct platform_device * pdev = to_platform_device ( dev ) ;
platform_device_unregister ( pdev ) ;
return 0 ;
}
2012-11-19 13:21:48 -05:00
static int dwc3_exynos_probe ( struct platform_device * pdev )
2012-02-15 17:04:56 +09:00
{
struct dwc3_exynos * exynos ;
2012-11-13 11:20:49 +09:00
struct device * dev = & pdev - > dev ;
2013-03-14 18:09:49 +05:30
struct device_node * node = dev - > of_node ;
2018-09-18 10:16:51 +02:00
const struct dwc3_exynos_driverdata * driver_data ;
int i , ret ;
2012-02-15 17:04:56 +09:00
2012-11-13 11:20:49 +09:00
exynos = devm_kzalloc ( dev , sizeof ( * exynos ) , GFP_KERNEL ) ;
2014-07-17 12:45:11 +09:00
if ( ! exynos )
2014-05-15 15:53:32 +03:00
return - ENOMEM ;
2012-02-15 17:04:56 +09:00
2018-09-18 10:16:51 +02:00
driver_data = of_device_get_match_data ( dev ) ;
exynos - > dev = dev ;
exynos - > num_clks = driver_data - > num_clks ;
exynos - > clk_names = ( const char * * ) driver_data - > clk_names ;
exynos - > suspend_clk_idx = driver_data - > suspend_clk_idx ;
2012-02-15 17:04:56 +09:00
2018-09-18 10:16:51 +02:00
platform_set_drvdata ( pdev , exynos ) ;
2014-11-21 19:05:45 +05:30
2018-09-18 10:16:51 +02:00
for ( i = 0 ; i < exynos - > num_clks ; i + + ) {
exynos - > clks [ i ] = devm_clk_get ( dev , exynos - > clk_names [ i ] ) ;
if ( IS_ERR ( exynos - > clks [ i ] ) ) {
dev_err ( dev , " failed to get clock: %s \n " ,
exynos - > clk_names [ i ] ) ;
return PTR_ERR ( exynos - > clks [ i ] ) ;
}
2012-02-15 17:04:56 +09:00
}
2018-09-18 10:16:51 +02:00
for ( i = 0 ; i < exynos - > num_clks ; i + + ) {
ret = clk_prepare_enable ( exynos - > clks [ i ] ) ;
if ( ret ) {
2019-01-22 00:23:50 +03:00
while ( i - - > 0 )
2018-09-18 10:16:51 +02:00
clk_disable_unprepare ( exynos - > clks [ i ] ) ;
return ret ;
2014-11-21 19:05:47 +05:30
}
}
2018-09-18 10:16:51 +02:00
if ( exynos - > suspend_clk_idx > = 0 )
clk_prepare_enable ( exynos - > clks [ exynos - > suspend_clk_idx ] ) ;
2014-04-21 17:46:44 +05:30
exynos - > vdd33 = devm_regulator_get ( dev , " vdd33 " ) ;
if ( IS_ERR ( exynos - > vdd33 ) ) {
ret = PTR_ERR ( exynos - > vdd33 ) ;
2017-01-30 12:25:04 -07:00
goto vdd33_err ;
2014-04-21 17:46:44 +05:30
}
ret = regulator_enable ( exynos - > vdd33 ) ;
if ( ret ) {
dev_err ( dev , " Failed to enable VDD33 supply \n " ) ;
2017-01-30 12:25:04 -07:00
goto vdd33_err ;
2014-04-21 17:46:44 +05:30
}
exynos - > vdd10 = devm_regulator_get ( dev , " vdd10 " ) ;
if ( IS_ERR ( exynos - > vdd10 ) ) {
ret = PTR_ERR ( exynos - > vdd10 ) ;
2017-01-30 12:25:04 -07:00
goto vdd10_err ;
2014-04-21 17:46:44 +05:30
}
ret = regulator_enable ( exynos - > vdd10 ) ;
if ( ret ) {
dev_err ( dev , " Failed to enable VDD10 supply \n " ) ;
2017-01-30 12:25:04 -07:00
goto vdd10_err ;
2014-04-21 17:46:44 +05:30
}
2013-03-14 18:09:49 +05:30
if ( node ) {
ret = of_platform_populate ( node , NULL , NULL , dev ) ;
if ( ret ) {
dev_err ( dev , " failed to add dwc3 core \n " ) ;
2017-01-30 12:25:04 -07:00
goto populate_err ;
2013-03-14 18:09:49 +05:30
}
} else {
dev_err ( dev , " no device node, failed to add dwc3 core \n " ) ;
ret = - ENODEV ;
2017-01-30 12:25:04 -07:00
goto populate_err ;
2012-02-15 17:04:56 +09:00
}
return 0 ;
2017-01-30 12:25:04 -07:00
populate_err :
2014-04-21 17:46:44 +05:30
regulator_disable ( exynos - > vdd10 ) ;
2017-01-30 12:25:04 -07:00
vdd10_err :
2014-04-21 17:46:44 +05:30
regulator_disable ( exynos - > vdd33 ) ;
2017-01-30 12:25:04 -07:00
vdd33_err :
2018-09-18 10:16:51 +02:00
for ( i = exynos - > num_clks - 1 ; i > = 0 ; i - - )
clk_disable_unprepare ( exynos - > clks [ i ] ) ;
if ( exynos - > suspend_clk_idx > = 0 )
clk_disable_unprepare ( exynos - > clks [ exynos - > suspend_clk_idx ] ) ;
2012-02-15 17:04:56 +09:00
return ret ;
}
2012-11-19 13:26:20 -05:00
static int dwc3_exynos_remove ( struct platform_device * pdev )
2012-02-15 17:04:56 +09:00
{
struct dwc3_exynos * exynos = platform_get_drvdata ( pdev ) ;
2018-09-18 10:16:51 +02:00
int i ;
2012-02-15 17:04:56 +09:00
2013-05-24 14:30:16 +08:00
device_for_each_child ( & pdev - > dev , NULL , dwc3_exynos_remove_child ) ;
2012-02-15 17:04:56 +09:00
2018-09-18 10:16:51 +02:00
for ( i = exynos - > num_clks - 1 ; i > = 0 ; i - - )
clk_disable_unprepare ( exynos - > clks [ i ] ) ;
if ( exynos - > suspend_clk_idx > = 0 )
clk_disable_unprepare ( exynos - > clks [ exynos - > suspend_clk_idx ] ) ;
2012-02-15 17:04:56 +09:00
2014-04-21 17:46:44 +05:30
regulator_disable ( exynos - > vdd33 ) ;
regulator_disable ( exynos - > vdd10 ) ;
2012-02-15 17:04:56 +09:00
return 0 ;
}
2018-09-18 10:16:51 +02:00
static const struct dwc3_exynos_driverdata exynos5250_drvdata = {
. clk_names = { " usbdrd30 " } ,
. num_clks = 1 ,
. suspend_clk_idx = - 1 ,
} ;
2018-09-18 10:16:52 +02:00
static const struct dwc3_exynos_driverdata exynos5433_drvdata = {
. clk_names = { " aclk " , " susp_clk " , " pipe_pclk " , " phyclk " } ,
. num_clks = 4 ,
. suspend_clk_idx = 1 ,
} ;
2018-09-18 10:16:51 +02:00
static const struct dwc3_exynos_driverdata exynos7_drvdata = {
. clk_names = { " usbdrd30 " , " usbdrd30_susp_clk " , " usbdrd30_axius_clk " } ,
. num_clks = 3 ,
. suspend_clk_idx = 1 ,
} ;
2012-11-03 18:00:27 +05:30
static const struct of_device_id exynos_dwc3_match [ ] = {
2018-09-18 10:16:51 +02:00
{
. compatible = " samsung,exynos5250-dwusb3 " ,
. data = & exynos5250_drvdata ,
2018-09-18 10:16:52 +02:00
} , {
. compatible = " samsung,exynos5433-dwusb3 " ,
. data = & exynos5433_drvdata ,
2018-09-18 10:16:51 +02:00
} , {
. compatible = " samsung,exynos7-dwusb3 " ,
. data = & exynos7_drvdata ,
} , {
}
2012-11-03 18:00:27 +05:30
} ;
MODULE_DEVICE_TABLE ( of , exynos_dwc3_match ) ;
2013-03-26 01:52:48 +00:00
# ifdef CONFIG_PM_SLEEP
2012-10-16 15:15:38 +05:30
static int dwc3_exynos_suspend ( struct device * dev )
{
struct dwc3_exynos * exynos = dev_get_drvdata ( dev ) ;
2018-09-18 10:16:51 +02:00
int i ;
2012-10-16 15:15:38 +05:30
2018-09-18 10:16:51 +02:00
for ( i = exynos - > num_clks - 1 ; i > = 0 ; i - - )
clk_disable_unprepare ( exynos - > clks [ i ] ) ;
2012-10-16 15:15:38 +05:30
2014-04-21 17:46:44 +05:30
regulator_disable ( exynos - > vdd33 ) ;
regulator_disable ( exynos - > vdd10 ) ;
2012-10-16 15:15:38 +05:30
return 0 ;
}
static int dwc3_exynos_resume ( struct device * dev )
{
struct dwc3_exynos * exynos = dev_get_drvdata ( dev ) ;
2018-09-18 10:16:51 +02:00
int i , ret ;
2014-04-21 17:46:44 +05:30
ret = regulator_enable ( exynos - > vdd33 ) ;
if ( ret ) {
dev_err ( dev , " Failed to enable VDD33 supply \n " ) ;
return ret ;
}
ret = regulator_enable ( exynos - > vdd10 ) ;
if ( ret ) {
dev_err ( dev , " Failed to enable VDD10 supply \n " ) ;
return ret ;
}
2012-10-16 15:15:38 +05:30
2018-09-18 10:16:51 +02:00
for ( i = 0 ; i < exynos - > num_clks ; i + + ) {
ret = clk_prepare_enable ( exynos - > clks [ i ] ) ;
if ( ret ) {
2019-01-22 00:23:50 +03:00
while ( i - - > 0 )
2018-09-18 10:16:51 +02:00
clk_disable_unprepare ( exynos - > clks [ i ] ) ;
return ret ;
}
}
2012-10-16 15:15:38 +05:30
return 0 ;
}
static const struct dev_pm_ops dwc3_exynos_dev_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS ( dwc3_exynos_suspend , dwc3_exynos_resume )
} ;
# define DEV_PM_OPS (&dwc3_exynos_dev_pm_ops)
# else
# define DEV_PM_OPS NULL
2013-03-26 01:52:48 +00:00
# endif /* CONFIG_PM_SLEEP */
2012-10-16 15:15:38 +05:30
2012-02-15 17:04:56 +09:00
static struct platform_driver dwc3_exynos_driver = {
. probe = dwc3_exynos_probe ,
2012-11-19 13:21:08 -05:00
. remove = dwc3_exynos_remove ,
2012-02-15 17:04:56 +09:00
. driver = {
. name = " exynos-dwc3 " ,
2014-11-04 11:01:47 +09:00
. of_match_table = exynos_dwc3_match ,
2012-10-16 15:15:38 +05:30
. pm = DEV_PM_OPS ,
2012-02-15 17:04:56 +09:00
} ,
} ;
module_platform_driver ( dwc3_exynos_driver ) ;
MODULE_AUTHOR ( " Anton Tikhomirov <av.tikhomirov@samsung.com> " ) ;
2013-06-30 14:15:11 +03:00
MODULE_LICENSE ( " GPL v2 " ) ;
2012-02-15 17:04:56 +09:00
MODULE_DESCRIPTION ( " DesignWare USB3 EXYNOS Glue Layer " ) ;