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 >
*
2013-06-30 14:15:11 +03:00
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 of
* the License as published by the Free Software Foundation .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
2012-02-15 17:04:56 +09:00
*/
# include <linux/module.h>
# include <linux/kernel.h>
# include <linux/slab.h>
# include <linux/platform_device.h>
# include <linux/clk.h>
2012-07-19 14:01:10 +03:00
# include <linux/usb/otg.h>
2014-04-16 15:28:32 -05:00
# include <linux/usb/usb_phy_generic.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
struct dwc3_exynos {
2012-07-19 14:01:10 +03:00
struct platform_device * usb2_phy ;
struct platform_device * usb3_phy ;
2012-02-15 17:04:56 +09:00
struct device * dev ;
struct clk * clk ;
2014-11-21 19:05:46 +05:30
struct clk * susp_clk ;
2014-11-21 19:05:47 +05:30
struct clk * axius_clk ;
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
} ;
2012-11-19 13:21:48 -05:00
static int dwc3_exynos_register_phys ( struct dwc3_exynos * exynos )
2012-07-19 14:01:10 +03:00
{
2014-04-16 15:20:44 -05:00
struct usb_phy_generic_platform_data pdata ;
2012-07-19 14:01:10 +03:00
struct platform_device * pdev ;
int ret ;
memset ( & pdata , 0x00 , sizeof ( pdata ) ) ;
2014-04-16 15:20:44 -05:00
pdev = platform_device_alloc ( " usb_phy_generic " , PLATFORM_DEVID_AUTO ) ;
2012-07-19 14:01:10 +03:00
if ( ! pdev )
return - ENOMEM ;
exynos - > usb2_phy = pdev ;
pdata . type = USB_PHY_TYPE_USB2 ;
2013-12-18 16:41:25 +02:00
pdata . gpio_reset = - 1 ;
2012-07-19 14:01:10 +03:00
ret = platform_device_add_data ( exynos - > usb2_phy , & pdata , sizeof ( pdata ) ) ;
if ( ret )
goto err1 ;
2014-04-16 15:20:44 -05:00
pdev = platform_device_alloc ( " usb_phy_generic " , PLATFORM_DEVID_AUTO ) ;
2012-07-19 14:01:10 +03:00
if ( ! pdev ) {
ret = - ENOMEM ;
goto err1 ;
}
exynos - > usb3_phy = pdev ;
pdata . type = USB_PHY_TYPE_USB3 ;
ret = platform_device_add_data ( exynos - > usb3_phy , & pdata , sizeof ( pdata ) ) ;
if ( ret )
goto err2 ;
ret = platform_device_add ( exynos - > usb2_phy ) ;
if ( ret )
goto err2 ;
ret = platform_device_add ( exynos - > usb3_phy ) ;
if ( ret )
goto err3 ;
return 0 ;
err3 :
platform_device_del ( exynos - > usb2_phy ) ;
err2 :
platform_device_put ( exynos - > usb3_phy ) ;
err1 :
platform_device_put ( exynos - > usb2_phy ) ;
return ret ;
}
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 ;
2012-02-15 17:04:56 +09:00
2014-05-15 15:53:32 +03:00
int 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
platform_set_drvdata ( pdev , exynos ) ;
2014-11-21 19:05:45 +05:30
exynos - > dev = dev ;
exynos - > clk = devm_clk_get ( dev , " usbdrd30 " ) ;
if ( IS_ERR ( exynos - > clk ) ) {
2012-11-13 11:20:49 +09:00
dev_err ( dev , " couldn't get clock \n " ) ;
2014-05-15 15:53:32 +03:00
return - EINVAL ;
2012-02-15 17:04:56 +09:00
}
2017-06-12 16:23:31 +05:30
ret = clk_prepare_enable ( exynos - > clk ) ;
if ( ret )
return ret ;
2012-02-15 17:04:56 +09:00
2014-11-21 19:05:46 +05:30
exynos - > susp_clk = devm_clk_get ( dev , " usbdrd30_susp_clk " ) ;
2017-01-10 14:20:59 -07:00
if ( IS_ERR ( exynos - > susp_clk ) )
2014-11-21 19:05:46 +05:30
exynos - > susp_clk = NULL ;
2017-06-12 16:23:31 +05:30
ret = clk_prepare_enable ( exynos - > susp_clk ) ;
if ( ret )
goto susp_clk_err ;
2014-11-21 19:05:46 +05:30
2014-11-21 19:05:47 +05:30
if ( of_device_is_compatible ( node , " samsung,exynos7-dwusb3 " ) ) {
exynos - > axius_clk = devm_clk_get ( dev , " usbdrd30_axius_clk " ) ;
if ( IS_ERR ( exynos - > axius_clk ) ) {
dev_err ( dev , " no AXI UpScaler clk specified \n " ) ;
2017-01-10 16:05:28 -07:00
ret = - ENODEV ;
goto axius_clk_err ;
2014-11-21 19:05:47 +05:30
}
2017-06-12 16:23:31 +05:30
ret = clk_prepare_enable ( exynos - > axius_clk ) ;
if ( ret )
goto axius_clk_err ;
2014-11-21 19:05:47 +05:30
} else {
exynos - > axius_clk = NULL ;
}
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
}
2016-05-24 20:13:15 +02:00
ret = dwc3_exynos_register_phys ( exynos ) ;
if ( ret ) {
dev_err ( dev , " couldn't register PHYs \n " ) ;
2017-01-30 12:25:04 -07:00
goto phys_err ;
2016-05-24 20:13:15 +02:00
}
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 :
2016-05-24 20:13:15 +02:00
platform_device_unregister ( exynos - > usb2_phy ) ;
platform_device_unregister ( exynos - > usb3_phy ) ;
2017-01-30 12:25:04 -07:00
phys_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 :
2014-11-21 19:05:47 +05:30
clk_disable_unprepare ( exynos - > axius_clk ) ;
2017-01-10 16:05:28 -07:00
axius_clk_err :
2014-11-21 19:05:46 +05:30
clk_disable_unprepare ( exynos - > susp_clk ) ;
2017-06-12 16:23:31 +05:30
susp_clk_err :
2014-11-21 19:05:45 +05:30
clk_disable_unprepare ( exynos - > clk ) ;
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 ) ;
2013-05-24 14:30:16 +08:00
device_for_each_child ( & pdev - > dev , NULL , dwc3_exynos_remove_child ) ;
2012-07-19 14:01:10 +03:00
platform_device_unregister ( exynos - > usb2_phy ) ;
platform_device_unregister ( exynos - > usb3_phy ) ;
2012-02-15 17:04:56 +09:00
2014-11-21 19:05:47 +05:30
clk_disable_unprepare ( exynos - > axius_clk ) ;
2014-11-21 19:05:46 +05:30
clk_disable_unprepare ( exynos - > susp_clk ) ;
2013-03-14 16:14:58 +05:30
clk_disable_unprepare ( exynos - > clk ) ;
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 ;
}
2012-11-03 18:00:27 +05:30
static const struct of_device_id exynos_dwc3_match [ ] = {
2013-01-24 19:15:30 +05:30
{ . compatible = " samsung,exynos5250-dwusb3 " } ,
2014-11-21 19:05:47 +05:30
{ . compatible = " samsung,exynos7-dwusb3 " } ,
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 ) ;
2014-11-21 19:05:47 +05:30
clk_disable ( exynos - > axius_clk ) ;
2012-10-16 15:15:38 +05:30
clk_disable ( exynos - > clk ) ;
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 ) ;
2014-04-21 17:46:44 +05:30
int ret ;
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
clk_enable ( exynos - > clk ) ;
2014-11-21 19:05:47 +05:30
clk_enable ( exynos - > axius_clk ) ;
2012-10-16 15:15:38 +05:30
/* runtime set active to reflect active state. */
pm_runtime_disable ( dev ) ;
pm_runtime_set_active ( dev ) ;
pm_runtime_enable ( dev ) ;
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 " ) ;