2012-07-07 22:56:48 +08:00
/*
* Copyright 2012 Freescale Semiconductor , Inc .
* Copyright ( C ) 2012 Marek Vasut < marex @ denx . de >
* on behalf of DENX Software Engineering GmbH
*
* The code contained herein is licensed under the GNU General Public
* License . You may obtain a copy of the GNU General Public License
* Version 2 or later at the following locations :
*
* http : //www.opensource.org/licenses/gpl-license.html
* http : //www.gnu.org/copyleft/gpl.html
*/
# include <linux/module.h>
# include <linux/of_platform.h>
# include <linux/of_gpio.h>
# include <linux/platform_device.h>
# include <linux/pm_runtime.h>
# include <linux/dma-mapping.h>
# include <linux/usb/chipidea.h>
# include <linux/clk.h>
# include "ci.h"
2013-06-24 14:46:36 +03:00
# include "ci_hdrc_imx.h"
2012-07-07 22:56:48 +08:00
2014-01-10 13:51:28 +08:00
struct ci_hdrc_imx_platform_flag {
unsigned int flags ;
2015-02-11 12:44:47 +08:00
bool runtime_pm ;
2014-01-10 13:51:28 +08:00
} ;
static const struct ci_hdrc_imx_platform_flag imx27_usb_data = {
} ;
static const struct ci_hdrc_imx_platform_flag imx28_usb_data = {
2015-02-11 12:45:01 +08:00
. flags = CI_HDRC_IMX28_WRITE_FIX |
CI_HDRC_TURN_VBUS_EARLY_ON ,
2014-01-10 13:51:28 +08:00
} ;
2015-02-11 12:44:47 +08:00
static const struct ci_hdrc_imx_platform_flag imx6q_usb_data = {
2015-02-11 12:45:01 +08:00
. flags = CI_HDRC_SUPPORTS_RUNTIME_PM |
CI_HDRC_TURN_VBUS_EARLY_ON ,
2015-02-11 12:44:47 +08:00
} ;
static const struct ci_hdrc_imx_platform_flag imx6sl_usb_data = {
2015-02-11 12:45:01 +08:00
. flags = CI_HDRC_SUPPORTS_RUNTIME_PM |
CI_HDRC_TURN_VBUS_EARLY_ON ,
2015-02-11 12:44:47 +08:00
} ;
static const struct ci_hdrc_imx_platform_flag imx6sx_usb_data = {
2015-02-11 12:45:01 +08:00
. flags = CI_HDRC_SUPPORTS_RUNTIME_PM |
CI_HDRC_TURN_VBUS_EARLY_ON ,
2015-02-11 12:44:47 +08:00
} ;
2014-01-10 13:51:28 +08:00
static const struct of_device_id ci_hdrc_imx_dt_ids [ ] = {
{ . compatible = " fsl,imx28-usb " , . data = & imx28_usb_data } ,
{ . compatible = " fsl,imx27-usb " , . data = & imx27_usb_data } ,
2015-02-11 12:44:47 +08:00
{ . compatible = " fsl,imx6q-usb " , . data = & imx6q_usb_data } ,
{ . compatible = " fsl,imx6sl-usb " , . data = & imx6sl_usb_data } ,
{ . compatible = " fsl,imx6sx-usb " , . data = & imx6sl_usb_data } ,
2014-01-10 13:51:28 +08:00
{ /* sentinel */ }
} ;
MODULE_DEVICE_TABLE ( of , ci_hdrc_imx_dt_ids ) ;
2013-06-24 14:46:36 +03:00
struct ci_hdrc_imx_data {
2012-07-07 22:56:48 +08:00
struct usb_phy * phy ;
struct platform_device * ci_pdev ;
struct clk * clk ;
2013-08-14 12:44:16 +03:00
struct imx_usbmisc_data * usbmisc_data ;
2015-02-11 12:44:47 +08:00
bool supports_runtime_pm ;
bool in_lpm ;
2012-07-07 22:56:48 +08:00
} ;
2012-09-12 14:58:05 +03:00
/* Common functions shared by usbmisc drivers */
2013-08-14 12:44:16 +03:00
static struct imx_usbmisc_data * usbmisc_get_init_data ( struct device * dev )
2012-09-12 14:58:05 +03:00
{
2014-09-22 08:14:15 +08:00
struct platform_device * misc_pdev ;
2012-09-12 14:58:05 +03:00
struct device_node * np = dev - > of_node ;
struct of_phandle_args args ;
2013-08-14 12:44:16 +03:00
struct imx_usbmisc_data * data ;
2012-09-12 14:58:05 +03:00
int ret ;
2013-08-14 12:44:16 +03:00
/*
* In case the fsl , usbmisc property is not present this device doesn ' t
* need usbmisc . Return NULL ( which is no error here )
*/
if ( ! of_get_property ( np , " fsl,usbmisc " , NULL ) )
return NULL ;
data = devm_kzalloc ( dev , sizeof ( * data ) , GFP_KERNEL ) ;
if ( ! data )
return ERR_PTR ( - ENOMEM ) ;
2012-09-12 14:58:05 +03:00
ret = of_parse_phandle_with_args ( np , " fsl,usbmisc " , " #index-cells " ,
0 , & args ) ;
if ( ret ) {
dev_err ( dev , " Failed to parse property fsl,usbmisc, errno %d \n " ,
ret ) ;
2013-08-14 12:44:16 +03:00
return ERR_PTR ( ret ) ;
2012-09-12 14:58:05 +03:00
}
2013-08-14 12:44:16 +03:00
data - > index = args . args [ 0 ] ;
2014-09-22 08:14:15 +08:00
misc_pdev = of_find_device_by_node ( args . np ) ;
2012-09-12 14:58:05 +03:00
of_node_put ( args . np ) ;
2014-09-22 08:14:15 +08:00
if ( ! misc_pdev )
return ERR_PTR ( - EPROBE_DEFER ) ;
data - > dev = & misc_pdev - > dev ;
2012-09-12 14:58:05 +03:00
if ( of_find_property ( np , " disable-over-current " , NULL ) )
2013-08-14 12:44:16 +03:00
data - > disable_oc = 1 ;
2012-09-12 14:58:05 +03:00
2013-03-30 12:54:01 +02:00
if ( of_find_property ( np , " external-vbus-divider " , NULL ) )
2013-08-14 12:44:16 +03:00
data - > evdo = 1 ;
2013-03-30 12:54:01 +02:00
2013-08-14 12:44:16 +03:00
return data ;
2012-09-12 14:58:05 +03:00
}
/* End of common functions shared by usbmisc drivers*/
2013-06-24 14:46:36 +03:00
static int ci_hdrc_imx_probe ( struct platform_device * pdev )
2012-07-07 22:56:48 +08:00
{
2013-06-24 14:46:36 +03:00
struct ci_hdrc_imx_data * data ;
struct ci_hdrc_platform_data pdata = {
2014-03-11 13:47:38 +08:00
. name = dev_name ( & pdev - > dev ) ,
2013-06-13 17:59:58 +03:00
. capoffset = DEF_CAPOFFSET ,
2014-11-26 13:44:31 +08:00
. flags = CI_HDRC_DISABLE_STREAMING ,
2013-06-13 17:59:58 +03:00
} ;
2012-07-07 22:56:48 +08:00
int ret ;
2014-01-10 13:51:28 +08:00
const struct of_device_id * of_id =
of_match_device ( ci_hdrc_imx_dt_ids , & pdev - > dev ) ;
const struct ci_hdrc_imx_platform_flag * imx_platform_flag = of_id - > data ;
2012-07-07 22:56:48 +08:00
data = devm_kzalloc ( & pdev - > dev , sizeof ( * data ) , GFP_KERNEL ) ;
2014-11-26 13:44:24 +08:00
if ( ! data )
2012-07-07 22:56:48 +08:00
return - ENOMEM ;
2013-08-14 12:44:16 +03:00
data - > usbmisc_data = usbmisc_get_init_data ( & pdev - > dev ) ;
if ( IS_ERR ( data - > usbmisc_data ) )
return PTR_ERR ( data - > usbmisc_data ) ;
2012-07-07 22:56:48 +08:00
data - > clk = devm_clk_get ( & pdev - > dev , NULL ) ;
if ( IS_ERR ( data - > clk ) ) {
dev_err ( & pdev - > dev ,
" Failed to get clock, err=%ld \n " , PTR_ERR ( data - > clk ) ) ;
return PTR_ERR ( data - > clk ) ;
}
ret = clk_prepare_enable ( data - > clk ) ;
if ( ret ) {
dev_err ( & pdev - > dev ,
" Failed to prepare or enable clock, err=%d \n " , ret ) ;
return ret ;
}
2013-06-25 12:58:05 +03:00
data - > phy = devm_usb_get_phy_by_phandle ( & pdev - > dev , " fsl,usbphy " , 0 ) ;
2013-09-24 12:47:54 +08:00
if ( IS_ERR ( data - > phy ) ) {
ret = PTR_ERR ( data - > phy ) ;
2014-07-22 10:09:43 +08:00
/* Return -EINVAL if no usbphy is available */
if ( ret = = - ENODEV )
ret = - EINVAL ;
2013-06-13 18:00:00 +03:00
goto err_clk ;
2012-07-07 22:56:48 +08:00
}
2014-10-30 18:41:16 +01:00
pdata . usb_phy = data - > phy ;
2015-02-11 12:44:42 +08:00
pdata . flags | = imx_platform_flag - > flags ;
2015-02-11 12:44:47 +08:00
if ( pdata . flags & CI_HDRC_SUPPORTS_RUNTIME_PM )
data - > supports_runtime_pm = true ;
2014-01-10 13:51:28 +08:00
2013-06-27 12:36:37 +01:00
ret = dma_coerce_mask_and_coherent ( & pdev - > dev , DMA_BIT_MASK ( 32 ) ) ;
2013-06-10 16:28:49 +01:00
if ( ret )
goto err_clk ;
2012-09-12 14:58:05 +03:00
2015-02-11 12:44:44 +08:00
ret = imx_usbmisc_init ( data - > usbmisc_data ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " usbmisc init failed, ret=%d \n " , ret ) ;
goto err_clk ;
2012-09-12 14:58:05 +03:00
}
2013-06-24 14:46:36 +03:00
data - > ci_pdev = ci_hdrc_add_device ( & pdev - > dev ,
2012-07-07 22:56:48 +08:00
pdev - > resource , pdev - > num_resources ,
2013-06-13 17:59:58 +03:00
& pdata ) ;
2013-06-13 17:59:48 +03:00
if ( IS_ERR ( data - > ci_pdev ) ) {
ret = PTR_ERR ( data - > ci_pdev ) ;
2012-07-07 22:56:48 +08:00
dev_err ( & pdev - > dev ,
" Can't register ci_hdrc platform device, err=%d \n " ,
ret ) ;
2013-09-24 12:47:54 +08:00
goto err_clk ;
2012-07-07 22:56:48 +08:00
}
2015-02-11 12:44:44 +08:00
ret = imx_usbmisc_init_post ( data - > usbmisc_data ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " usbmisc post failed, ret=%d \n " , ret ) ;
goto disable_device ;
2013-03-30 12:54:01 +02:00
}
2012-07-07 22:56:48 +08:00
platform_set_drvdata ( pdev , data ) ;
2015-02-11 12:44:47 +08:00
if ( data - > supports_runtime_pm ) {
pm_runtime_set_active ( & pdev - > dev ) ;
pm_runtime_enable ( & pdev - > dev ) ;
}
2012-07-07 22:56:48 +08:00
2015-02-11 12:44:49 +08:00
device_set_wakeup_capable ( & pdev - > dev , true ) ;
2012-07-07 22:56:48 +08:00
return 0 ;
2013-06-13 17:59:48 +03:00
disable_device :
2013-06-24 14:46:36 +03:00
ci_hdrc_remove_device ( data - > ci_pdev ) ;
2013-06-13 18:00:00 +03:00
err_clk :
2012-07-07 22:56:48 +08:00
clk_disable_unprepare ( data - > clk ) ;
return ret ;
}
2013-06-24 14:46:36 +03:00
static int ci_hdrc_imx_remove ( struct platform_device * pdev )
2012-07-07 22:56:48 +08:00
{
2013-06-24 14:46:36 +03:00
struct ci_hdrc_imx_data * data = platform_get_drvdata ( pdev ) ;
2012-07-07 22:56:48 +08:00
2015-02-11 12:44:47 +08:00
if ( data - > supports_runtime_pm ) {
pm_runtime_get_sync ( & pdev - > dev ) ;
pm_runtime_disable ( & pdev - > dev ) ;
pm_runtime_put_noidle ( & pdev - > dev ) ;
}
2013-06-24 14:46:36 +03:00
ci_hdrc_remove_device ( data - > ci_pdev ) ;
2012-07-07 22:56:48 +08:00
clk_disable_unprepare ( data - > clk ) ;
return 0 ;
}
2015-02-11 12:44:47 +08:00
# ifdef CONFIG_PM
2014-11-26 13:44:30 +08:00
static int imx_controller_suspend ( struct device * dev )
{
struct ci_hdrc_imx_data * data = dev_get_drvdata ( dev ) ;
dev_dbg ( dev , " at %s \n " , __func__ ) ;
clk_disable_unprepare ( data - > clk ) ;
2015-02-11 12:44:47 +08:00
data - > in_lpm = true ;
2014-11-26 13:44:30 +08:00
return 0 ;
}
static int imx_controller_resume ( struct device * dev )
{
struct ci_hdrc_imx_data * data = dev_get_drvdata ( dev ) ;
2015-02-11 12:44:47 +08:00
int ret = 0 ;
2014-11-26 13:44:30 +08:00
dev_dbg ( dev , " at %s \n " , __func__ ) ;
2015-02-11 12:44:47 +08:00
if ( ! data - > in_lpm ) {
WARN_ON ( 1 ) ;
return 0 ;
}
ret = clk_prepare_enable ( data - > clk ) ;
if ( ret )
return ret ;
data - > in_lpm = false ;
ret = imx_usbmisc_set_wakeup ( data - > usbmisc_data , false ) ;
if ( ret ) {
dev_err ( dev , " usbmisc set_wakeup failed, ret=%d \n " , ret ) ;
goto clk_disable ;
}
return 0 ;
clk_disable :
clk_disable_unprepare ( data - > clk ) ;
return ret ;
2014-11-26 13:44:30 +08:00
}
2015-02-11 12:44:47 +08:00
# ifdef CONFIG_PM_SLEEP
2014-11-26 13:44:30 +08:00
static int ci_hdrc_imx_suspend ( struct device * dev )
{
2015-02-11 12:44:49 +08:00
int ret ;
2015-02-11 12:44:47 +08:00
struct ci_hdrc_imx_data * data = dev_get_drvdata ( dev ) ;
if ( data - > in_lpm )
/* The core's suspend doesn't run */
return 0 ;
2015-02-11 12:44:49 +08:00
if ( device_may_wakeup ( dev ) ) {
ret = imx_usbmisc_set_wakeup ( data - > usbmisc_data , true ) ;
if ( ret ) {
dev_err ( dev , " usbmisc set_wakeup failed, ret=%d \n " ,
ret ) ;
return ret ;
}
}
2014-11-26 13:44:30 +08:00
return imx_controller_suspend ( dev ) ;
}
static int ci_hdrc_imx_resume ( struct device * dev )
{
2015-02-11 12:44:47 +08:00
struct ci_hdrc_imx_data * data = dev_get_drvdata ( dev ) ;
int ret ;
ret = imx_controller_resume ( dev ) ;
if ( ! ret & & data - > supports_runtime_pm ) {
pm_runtime_disable ( dev ) ;
pm_runtime_set_active ( dev ) ;
pm_runtime_enable ( dev ) ;
}
return ret ;
2014-11-26 13:44:30 +08:00
}
# endif /* CONFIG_PM_SLEEP */
2015-02-11 12:44:47 +08:00
static int ci_hdrc_imx_runtime_suspend ( struct device * dev )
{
struct ci_hdrc_imx_data * data = dev_get_drvdata ( dev ) ;
int ret ;
if ( data - > in_lpm ) {
WARN_ON ( 1 ) ;
return 0 ;
}
ret = imx_usbmisc_set_wakeup ( data - > usbmisc_data , true ) ;
if ( ret ) {
dev_err ( dev , " usbmisc set_wakeup failed, ret=%d \n " , ret ) ;
return ret ;
}
return imx_controller_suspend ( dev ) ;
}
static int ci_hdrc_imx_runtime_resume ( struct device * dev )
{
return imx_controller_resume ( dev ) ;
}
# endif /* CONFIG_PM */
2014-11-26 13:44:30 +08:00
static const struct dev_pm_ops ci_hdrc_imx_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS ( ci_hdrc_imx_suspend , ci_hdrc_imx_resume )
2015-02-11 12:44:47 +08:00
SET_RUNTIME_PM_OPS ( ci_hdrc_imx_runtime_suspend ,
ci_hdrc_imx_runtime_resume , NULL )
2014-11-26 13:44:30 +08:00
} ;
2013-06-24 14:46:36 +03:00
static struct platform_driver ci_hdrc_imx_driver = {
. probe = ci_hdrc_imx_probe ,
. remove = ci_hdrc_imx_remove ,
2012-07-07 22:56:48 +08:00
. driver = {
. name = " imx_usb " ,
2013-06-24 14:46:36 +03:00
. of_match_table = ci_hdrc_imx_dt_ids ,
2014-11-26 13:44:30 +08:00
. pm = & ci_hdrc_imx_pm_ops ,
2012-07-07 22:56:48 +08:00
} ,
} ;
2013-06-24 14:46:36 +03:00
module_platform_driver ( ci_hdrc_imx_driver ) ;
2012-07-07 22:56:48 +08:00
MODULE_ALIAS ( " platform:imx-usb " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
2013-06-24 14:46:36 +03:00
MODULE_DESCRIPTION ( " CI HDRC i.MX USB binding " ) ;
2012-07-07 22:56:48 +08:00
MODULE_AUTHOR ( " Marek Vasut <marex@denx.de> " ) ;
MODULE_AUTHOR ( " Richard Zhao <richard.zhao@freescale.com> " ) ;