2014-11-13 14:47:46 +03:00
/*
* USB cluster support for Armada 375 platform .
*
* Copyright ( C ) 2014 Marvell
*
* Gregory CLEMENT < gregory . clement @ free - electrons . com >
*
* This file is licensed under the terms of the GNU General Public
* License version 2 or later . This program is licensed " as is "
* without any warranty of any kind , whether express or implied .
*
* Armada 375 comes with an USB2 host and device controller and an
* USB3 controller . The USB cluster control register allows to manage
* common features of both USB controllers .
*/
# include <dt-bindings/phy/phy.h>
# include <linux/init.h>
# include <linux/io.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/of_address.h>
# include <linux/phy/phy.h>
# include <linux/platform_device.h>
# define USB2_PHY_CONFIG_DISABLE BIT(0)
struct armada375_cluster_phy {
struct phy * phy ;
void __iomem * reg ;
bool use_usb3 ;
int phy_provided ;
} ;
static int armada375_usb_phy_init ( struct phy * phy )
{
struct armada375_cluster_phy * cluster_phy ;
u32 reg ;
2015-02-26 02:24:49 +03:00
cluster_phy = phy_get_drvdata ( phy ) ;
2014-11-13 14:47:46 +03:00
if ( ! cluster_phy )
return - ENODEV ;
reg = readl ( cluster_phy - > reg ) ;
if ( cluster_phy - > use_usb3 )
reg | = USB2_PHY_CONFIG_DISABLE ;
else
reg & = ~ USB2_PHY_CONFIG_DISABLE ;
writel ( reg , cluster_phy - > reg ) ;
return 0 ;
}
2015-07-15 10:33:51 +03:00
static const struct phy_ops armada375_usb_phy_ops = {
2014-11-13 14:47:46 +03:00
. init = armada375_usb_phy_init ,
. owner = THIS_MODULE ,
} ;
/*
* Only one controller can use this PHY . We shouldn ' t have the case
* when two controllers want to use this PHY . But if this case occurs
* then we provide a phy to the first one and return an error for the
* next one . This error has also to be an error returned by
* devm_phy_optional_get ( ) so different from ENODEV for USB2 . In the
* USB3 case it still optional and we use ENODEV .
*/
static struct phy * armada375_usb_phy_xlate ( struct device * dev ,
struct of_phandle_args * args )
{
struct armada375_cluster_phy * cluster_phy = dev_get_drvdata ( dev ) ;
if ( ! cluster_phy )
return ERR_PTR ( - ENODEV ) ;
/*
* Either the phy had never been requested and then the first
* usb claiming it can get it , or it had already been
* requested in this case , we only allow to use it with the
* same configuration .
*/
if ( WARN_ON ( ( cluster_phy - > phy_provided ! = PHY_NONE ) & &
( cluster_phy - > phy_provided ! = args - > args [ 0 ] ) ) ) {
dev_err ( dev , " This PHY has already been provided! \n " ) ;
dev_err ( dev , " Check your device tree, only one controller can use it \n . " ) ;
if ( args - > args [ 0 ] = = PHY_TYPE_USB2 )
return ERR_PTR ( - EBUSY ) ;
else
return ERR_PTR ( - ENODEV ) ;
}
if ( args - > args [ 0 ] = = PHY_TYPE_USB2 )
cluster_phy - > use_usb3 = false ;
else if ( args - > args [ 0 ] = = PHY_TYPE_USB3 )
cluster_phy - > use_usb3 = true ;
else {
dev_err ( dev , " Invalid PHY mode \n " ) ;
return ERR_PTR ( - ENODEV ) ;
}
/* Store which phy mode is used for next test */
cluster_phy - > phy_provided = args - > args [ 0 ] ;
return cluster_phy - > phy ;
}
static int armada375_usb_phy_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct phy * phy ;
struct phy_provider * phy_provider ;
void __iomem * usb_cluster_base ;
struct resource * res ;
struct armada375_cluster_phy * cluster_phy ;
cluster_phy = devm_kzalloc ( dev , sizeof ( * cluster_phy ) , GFP_KERNEL ) ;
if ( ! cluster_phy )
return - ENOMEM ;
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
usb_cluster_base = devm_ioremap_resource ( & pdev - > dev , res ) ;
2015-01-14 04:10:00 +03:00
if ( IS_ERR ( usb_cluster_base ) )
return PTR_ERR ( usb_cluster_base ) ;
2014-11-13 14:47:46 +03:00
phy = devm_phy_create ( dev , NULL , & armada375_usb_phy_ops ) ;
if ( IS_ERR ( phy ) ) {
dev_err ( dev , " failed to create PHY \n " ) ;
return PTR_ERR ( phy ) ;
}
cluster_phy - > phy = phy ;
cluster_phy - > reg = usb_cluster_base ;
dev_set_drvdata ( dev , cluster_phy ) ;
2015-02-26 02:24:49 +03:00
phy_set_drvdata ( phy , cluster_phy ) ;
2014-11-13 14:47:46 +03:00
phy_provider = devm_of_phy_provider_register ( & pdev - > dev ,
armada375_usb_phy_xlate ) ;
return PTR_ERR_OR_ZERO ( phy_provider ) ;
}
static const struct of_device_id of_usb_cluster_table [ ] = {
{ . compatible = " marvell,armada-375-usb-cluster " , } ,
{ /* end of list */ } ,
} ;
MODULE_DEVICE_TABLE ( of , of_usb_cluster_table ) ;
static struct platform_driver armada375_usb_phy_driver = {
. probe = armada375_usb_phy_probe ,
. driver = {
. of_match_table = of_usb_cluster_table ,
. name = " armada-375-usb-cluster " ,
}
} ;
module_platform_driver ( armada375_usb_phy_driver ) ;
MODULE_DESCRIPTION ( " Armada 375 USB cluster driver " ) ;
MODULE_AUTHOR ( " Gregory CLEMENT <gregory.clement@free-electrons.com> " ) ;
MODULE_LICENSE ( " GPL " ) ;