2012-09-06 18:57:06 +04:00
/*
* omap - usb2 . c - USB PHY , talking to musb controller in OMAP .
*
* Copyright ( C ) 2012 Texas Instruments Incorporated - http : //www.ti.com
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* Author : Kishon Vijay Abraham I < kishon @ ti . com >
*
* 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 .
*
*/
# include <linux/module.h>
# include <linux/platform_device.h>
# include <linux/slab.h>
# include <linux/of.h>
# include <linux/io.h>
2014-03-03 15:38:14 +04:00
# include <linux/phy/omap_usb.h>
2012-09-06 18:57:06 +04:00
# include <linux/usb/phy_companion.h>
# include <linux/clk.h>
# include <linux/err.h>
# include <linux/pm_runtime.h>
# include <linux/delay.h>
2014-03-06 18:38:37 +04:00
# include <linux/phy/omap_control_phy.h>
2013-09-27 10:23:26 +04:00
# include <linux/phy/phy.h>
2013-10-03 19:12:32 +04:00
# include <linux/of_platform.h>
2012-09-06 18:57:06 +04:00
2014-03-06 16:41:53 +04:00
# define USB2PHY_DISCON_BYP_LATCH (1 << 31)
# define USB2PHY_ANA_CONFIG1 0x4c
2012-09-06 18:57:06 +04:00
/**
* omap_usb2_set_comparator - links the comparator present in the sytem with
* this phy
* @ comparator - the companion phy ( comparator ) for this phy
*
* The phy companion driver should call this API passing the phy_companion
* filled with set_vbus and start_srp to be used by usb phy .
*
* For use by phy companion driver
*/
int omap_usb2_set_comparator ( struct phy_companion * comparator )
{
struct omap_usb * phy ;
struct usb_phy * x = usb_get_phy ( USB_PHY_TYPE_USB2 ) ;
if ( IS_ERR ( x ) )
return - ENODEV ;
phy = phy_to_omapusb ( x ) ;
phy - > comparator = comparator ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( omap_usb2_set_comparator ) ;
static int omap_usb_set_vbus ( struct usb_otg * otg , bool enabled )
{
struct omap_usb * phy = phy_to_omapusb ( otg - > phy ) ;
if ( ! phy - > comparator )
return - ENODEV ;
return phy - > comparator - > set_vbus ( phy - > comparator , enabled ) ;
}
static int omap_usb_start_srp ( struct usb_otg * otg )
{
struct omap_usb * phy = phy_to_omapusb ( otg - > phy ) ;
if ( ! phy - > comparator )
return - ENODEV ;
return phy - > comparator - > start_srp ( phy - > comparator ) ;
}
static int omap_usb_set_host ( struct usb_otg * otg , struct usb_bus * host )
{
struct usb_phy * phy = otg - > phy ;
otg - > host = host ;
if ( ! host )
phy - > state = OTG_STATE_UNDEFINED ;
return 0 ;
}
static int omap_usb_set_peripheral ( struct usb_otg * otg ,
struct usb_gadget * gadget )
{
struct usb_phy * phy = otg - > phy ;
otg - > gadget = gadget ;
if ( ! gadget )
phy - > state = OTG_STATE_UNDEFINED ;
return 0 ;
}
2013-09-27 10:23:26 +04:00
static int omap_usb_power_off ( struct phy * x )
{
struct omap_usb * phy = phy_get_drvdata ( x ) ;
2014-03-06 18:38:37 +04:00
omap_control_phy_power ( phy - > control_dev , 0 ) ;
2013-09-27 10:23:26 +04:00
return 0 ;
}
static int omap_usb_power_on ( struct phy * x )
{
struct omap_usb * phy = phy_get_drvdata ( x ) ;
2014-03-06 18:38:37 +04:00
omap_control_phy_power ( phy - > control_dev , 1 ) ;
2013-09-27 10:23:26 +04:00
return 0 ;
}
2014-03-06 16:41:53 +04:00
static int omap_usb_init ( struct phy * x )
{
struct omap_usb * phy = phy_get_drvdata ( x ) ;
u32 val ;
if ( phy - > flags & OMAP_USB2_CALIBRATE_FALSE_DISCONNECT ) {
/*
*
* Reduce the sensitivity of internal PHY by enabling the
* DISCON_BYP_LATCH of the USB2PHY_ANA_CONFIG1 register . This
* resolves issues with certain devices which can otherwise
* be prone to false disconnects .
*
*/
val = omap_usb_readl ( phy - > phy_base , USB2PHY_ANA_CONFIG1 ) ;
val | = USB2PHY_DISCON_BYP_LATCH ;
omap_usb_writel ( phy - > phy_base , USB2PHY_ANA_CONFIG1 , val ) ;
}
return 0 ;
}
2013-09-27 10:23:26 +04:00
static struct phy_ops ops = {
2014-03-06 16:41:53 +04:00
. init = omap_usb_init ,
2013-09-27 10:23:26 +04:00
. power_on = omap_usb_power_on ,
. power_off = omap_usb_power_off ,
. owner = THIS_MODULE ,
} ;
2014-03-06 16:41:52 +04:00
# ifdef CONFIG_OF
static const struct usb_phy_data omap_usb2_data = {
. label = " omap_usb2 " ,
. flags = OMAP_USB2_HAS_START_SRP | OMAP_USB2_HAS_SET_VBUS ,
} ;
2014-03-06 16:41:54 +04:00
static const struct usb_phy_data omap5_usb2_data = {
. label = " omap5_usb2 " ,
. flags = 0 ,
} ;
2014-03-06 16:41:53 +04:00
static const struct usb_phy_data dra7x_usb2_data = {
. label = " dra7x_usb2 " ,
. flags = OMAP_USB2_CALIBRATE_FALSE_DISCONNECT ,
} ;
2014-03-06 16:41:52 +04:00
static const struct usb_phy_data am437x_usb2_data = {
. label = " am437x_usb2 " ,
. flags = 0 ,
} ;
static const struct of_device_id omap_usb2_id_table [ ] = {
{
. compatible = " ti,omap-usb2 " ,
. data = & omap_usb2_data ,
} ,
2014-03-06 16:41:54 +04:00
{
. compatible = " ti,omap5-usb2 " ,
. data = & omap5_usb2_data ,
} ,
2014-03-06 16:41:53 +04:00
{
. compatible = " ti,dra7x-usb2 " ,
. data = & dra7x_usb2_data ,
} ,
2014-03-06 16:41:52 +04:00
{
. compatible = " ti,am437x-usb2 " ,
. data = & am437x_usb2_data ,
} ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , omap_usb2_id_table ) ;
# endif
2012-11-19 22:21:48 +04:00
static int omap_usb2_probe ( struct platform_device * pdev )
2012-09-06 18:57:06 +04:00
{
2013-10-03 19:12:32 +04:00
struct omap_usb * phy ;
struct phy * generic_phy ;
2014-03-06 16:41:53 +04:00
struct resource * res ;
2013-10-03 19:12:32 +04:00
struct phy_provider * phy_provider ;
struct usb_otg * otg ;
struct device_node * node = pdev - > dev . of_node ;
struct device_node * control_node ;
struct platform_device * control_pdev ;
2014-03-06 16:41:52 +04:00
const struct of_device_id * of_id ;
struct usb_phy_data * phy_data ;
of_id = of_match_device ( of_match_ptr ( omap_usb2_id_table ) , & pdev - > dev ) ;
2013-10-03 19:12:32 +04:00
2014-03-06 16:41:52 +04:00
if ( ! of_id )
2013-10-03 19:12:32 +04:00
return - EINVAL ;
2012-09-06 18:57:06 +04:00
2014-03-06 16:41:52 +04:00
phy_data = ( struct usb_phy_data * ) of_id - > data ;
2012-09-06 18:57:06 +04:00
phy = devm_kzalloc ( & pdev - > dev , sizeof ( * phy ) , GFP_KERNEL ) ;
2014-08-15 16:40:10 +04:00
if ( ! phy )
2012-09-06 18:57:06 +04:00
return - ENOMEM ;
otg = devm_kzalloc ( & pdev - > dev , sizeof ( * otg ) , GFP_KERNEL ) ;
2014-08-15 16:40:10 +04:00
if ( ! otg )
2012-09-06 18:57:06 +04:00
return - ENOMEM ;
phy - > dev = & pdev - > dev ;
phy - > phy . dev = phy - > dev ;
2014-03-06 16:41:52 +04:00
phy - > phy . label = phy_data - > label ;
2012-09-06 18:57:06 +04:00
phy - > phy . otg = otg ;
2013-01-25 06:33:24 +04:00
phy - > phy . type = USB_PHY_TYPE_USB2 ;
2012-09-06 18:57:06 +04:00
2014-03-06 16:41:53 +04:00
if ( phy_data - > flags & OMAP_USB2_CALIBRATE_FALSE_DISCONNECT ) {
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
phy - > phy_base = devm_ioremap_resource ( & pdev - > dev , res ) ;
2014-07-10 10:25:00 +04:00
if ( IS_ERR ( phy - > phy_base ) )
return PTR_ERR ( phy - > phy_base ) ;
2014-03-06 16:41:53 +04:00
phy - > flags | = OMAP_USB2_CALIBRATE_FALSE_DISCONNECT ;
}
2013-10-03 19:12:32 +04:00
control_node = of_parse_phandle ( node , " ctrl-module " , 0 ) ;
if ( ! control_node ) {
dev_err ( & pdev - > dev , " Failed to get control device phandle \n " ) ;
return - EINVAL ;
2012-09-06 18:57:06 +04:00
}
2013-10-03 19:12:32 +04:00
control_pdev = of_find_device_by_node ( control_node ) ;
if ( ! control_pdev ) {
dev_err ( & pdev - > dev , " Failed to get control device \n " ) ;
return - EINVAL ;
}
phy - > control_dev = & control_pdev - > dev ;
2014-03-06 18:38:37 +04:00
omap_control_phy_power ( phy - > control_dev , 0 ) ;
2012-09-06 18:57:06 +04:00
otg - > set_host = omap_usb_set_host ;
otg - > set_peripheral = omap_usb_set_peripheral ;
2014-03-06 16:41:52 +04:00
if ( phy_data - > flags & OMAP_USB2_HAS_SET_VBUS )
otg - > set_vbus = omap_usb_set_vbus ;
if ( phy_data - > flags & OMAP_USB2_HAS_START_SRP )
otg - > start_srp = omap_usb_start_srp ;
2012-09-06 18:57:06 +04:00
otg - > phy = & phy - > phy ;
2013-09-27 10:23:26 +04:00
platform_set_drvdata ( pdev , phy ) ;
2014-07-14 14:25:02 +04:00
generic_phy = devm_phy_create ( phy - > dev , NULL , & ops , NULL ) ;
2013-09-27 10:23:26 +04:00
if ( IS_ERR ( generic_phy ) )
return PTR_ERR ( generic_phy ) ;
phy_set_drvdata ( generic_phy , phy ) ;
2014-07-10 10:25:03 +04:00
pm_runtime_enable ( phy - > dev ) ;
2014-02-17 12:59:25 +04:00
phy_provider = devm_of_phy_provider_register ( phy - > dev ,
of_phy_simple_xlate ) ;
2014-07-10 10:25:03 +04:00
if ( IS_ERR ( phy_provider ) ) {
pm_runtime_disable ( phy - > dev ) ;
2014-02-17 12:59:25 +04:00
return PTR_ERR ( phy_provider ) ;
2014-07-10 10:25:03 +04:00
}
2014-02-17 12:59:25 +04:00
2014-05-05 13:54:40 +04:00
phy - > wkupclk = devm_clk_get ( phy - > dev , " wkupclk " ) ;
2012-09-06 18:57:06 +04:00
if ( IS_ERR ( phy - > wkupclk ) ) {
2014-05-05 13:54:40 +04:00
dev_warn ( & pdev - > dev , " unable to get wkupclk, trying old name \n " ) ;
phy - > wkupclk = devm_clk_get ( phy - > dev , " usb_phy_cm_clk32k " ) ;
if ( IS_ERR ( phy - > wkupclk ) ) {
dev_err ( & pdev - > dev , " unable to get usb_phy_cm_clk32k \n " ) ;
return PTR_ERR ( phy - > wkupclk ) ;
} else {
dev_warn ( & pdev - > dev ,
" found usb_phy_cm_clk32k, please fix DTS \n " ) ;
}
2012-09-06 18:57:06 +04:00
}
clk_prepare ( phy - > wkupclk ) ;
2014-05-05 13:54:40 +04:00
phy - > optclk = devm_clk_get ( phy - > dev , " refclk " ) ;
if ( IS_ERR ( phy - > optclk ) ) {
dev_dbg ( & pdev - > dev , " unable to get refclk, trying old name \n " ) ;
phy - > optclk = devm_clk_get ( phy - > dev , " usb_otg_ss_refclk960m " ) ;
if ( IS_ERR ( phy - > optclk ) ) {
dev_dbg ( & pdev - > dev ,
" unable to get usb_otg_ss_refclk960m \n " ) ;
} else {
dev_warn ( & pdev - > dev ,
" found usb_otg_ss_refclk960m, please fix DTS \n " ) ;
}
} else {
2013-01-25 06:51:49 +04:00
clk_prepare ( phy - > optclk ) ;
2014-05-05 13:54:40 +04:00
}
2013-01-25 06:51:49 +04:00
2013-01-25 06:33:24 +04:00
usb_add_phy_dev ( & phy - > phy ) ;
2012-09-06 18:57:06 +04:00
return 0 ;
}
2012-11-19 22:26:20 +04:00
static int omap_usb2_remove ( struct platform_device * pdev )
2012-09-06 18:57:06 +04:00
{
struct omap_usb * phy = platform_get_drvdata ( pdev ) ;
clk_unprepare ( phy - > wkupclk ) ;
2013-01-25 06:51:49 +04:00
if ( ! IS_ERR ( phy - > optclk ) )
clk_unprepare ( phy - > optclk ) ;
2012-09-06 18:57:06 +04:00
usb_remove_phy ( & phy - > phy ) ;
2014-07-10 10:25:03 +04:00
pm_runtime_disable ( phy - > dev ) ;
2012-09-06 18:57:06 +04:00
return 0 ;
}
# ifdef CONFIG_PM_RUNTIME
static int omap_usb2_runtime_suspend ( struct device * dev )
{
struct platform_device * pdev = to_platform_device ( dev ) ;
struct omap_usb * phy = platform_get_drvdata ( pdev ) ;
clk_disable ( phy - > wkupclk ) ;
2013-01-25 06:51:49 +04:00
if ( ! IS_ERR ( phy - > optclk ) )
clk_disable ( phy - > optclk ) ;
2012-09-06 18:57:06 +04:00
return 0 ;
}
static int omap_usb2_runtime_resume ( struct device * dev )
{
struct platform_device * pdev = to_platform_device ( dev ) ;
struct omap_usb * phy = platform_get_drvdata ( pdev ) ;
2013-08-21 12:41:22 +04:00
int ret ;
2012-09-06 18:57:06 +04:00
ret = clk_enable ( phy - > wkupclk ) ;
2013-01-25 06:51:49 +04:00
if ( ret < 0 ) {
2012-09-06 18:57:06 +04:00
dev_err ( phy - > dev , " Failed to enable wkupclk %d \n " , ret ) ;
2013-01-25 06:51:49 +04:00
goto err0 ;
}
if ( ! IS_ERR ( phy - > optclk ) ) {
ret = clk_enable ( phy - > optclk ) ;
if ( ret < 0 ) {
dev_err ( phy - > dev , " Failed to enable optclk %d \n " , ret ) ;
goto err1 ;
}
}
return 0 ;
err1 :
clk_disable ( phy - > wkupclk ) ;
2012-09-06 18:57:06 +04:00
2013-01-25 06:51:49 +04:00
err0 :
2012-09-06 18:57:06 +04:00
return ret ;
}
static const struct dev_pm_ops omap_usb2_pm_ops = {
SET_RUNTIME_PM_OPS ( omap_usb2_runtime_suspend , omap_usb2_runtime_resume ,
NULL )
} ;
# define DEV_PM_OPS (&omap_usb2_pm_ops)
# else
# define DEV_PM_OPS NULL
# endif
static struct platform_driver omap_usb2_driver = {
. probe = omap_usb2_probe ,
2012-11-19 22:21:08 +04:00
. remove = omap_usb2_remove ,
2012-09-06 18:57:06 +04:00
. driver = {
. name = " omap-usb2 " ,
. pm = DEV_PM_OPS ,
. of_match_table = of_match_ptr ( omap_usb2_id_table ) ,
} ,
} ;
module_platform_driver ( omap_usb2_driver ) ;
MODULE_ALIAS ( " platform: omap_usb2 " ) ;
MODULE_AUTHOR ( " Texas Instruments Inc. " ) ;
MODULE_DESCRIPTION ( " OMAP USB2 phy driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;