2014-03-06 15:16:48 +04:00
/*
* Samsung SoC USB 1.1 / 2.0 PHY driver
*
* Copyright ( C ) 2013 Samsung Electronics Co . , Ltd .
* Author : Kamil Debski < k . debski @ samsung . com >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*/
# include <linux/clk.h>
# include <linux/mfd/syscon.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/of_address.h>
# include <linux/phy/phy.h>
# include <linux/platform_device.h>
# include <linux/spinlock.h>
# include "phy-samsung-usb2.h"
static int samsung_usb2_phy_power_on ( struct phy * phy )
{
struct samsung_usb2_phy_instance * inst = phy_get_drvdata ( phy ) ;
struct samsung_usb2_phy_driver * drv = inst - > drv ;
int ret ;
dev_dbg ( drv - > dev , " Request to power_on \" %s \" usb phy \n " ,
inst - > cfg - > label ) ;
ret = clk_prepare_enable ( drv - > clk ) ;
if ( ret )
goto err_main_clk ;
ret = clk_prepare_enable ( drv - > ref_clk ) ;
if ( ret )
goto err_instance_clk ;
if ( inst - > cfg - > power_on ) {
spin_lock ( & drv - > lock ) ;
ret = inst - > cfg - > power_on ( inst ) ;
spin_unlock ( & drv - > lock ) ;
}
return 0 ;
err_instance_clk :
clk_disable_unprepare ( drv - > clk ) ;
err_main_clk :
return ret ;
}
static int samsung_usb2_phy_power_off ( struct phy * phy )
{
struct samsung_usb2_phy_instance * inst = phy_get_drvdata ( phy ) ;
struct samsung_usb2_phy_driver * drv = inst - > drv ;
int ret = 0 ;
dev_dbg ( drv - > dev , " Request to power_off \" %s \" usb phy \n " ,
inst - > cfg - > label ) ;
if ( inst - > cfg - > power_off ) {
spin_lock ( & drv - > lock ) ;
ret = inst - > cfg - > power_off ( inst ) ;
spin_unlock ( & drv - > lock ) ;
}
clk_disable_unprepare ( drv - > ref_clk ) ;
clk_disable_unprepare ( drv - > clk ) ;
return ret ;
}
static struct phy_ops samsung_usb2_phy_ops = {
. power_on = samsung_usb2_phy_power_on ,
. power_off = samsung_usb2_phy_power_off ,
. owner = THIS_MODULE ,
} ;
static struct phy * samsung_usb2_phy_xlate ( struct device * dev ,
struct of_phandle_args * args )
{
struct samsung_usb2_phy_driver * drv ;
drv = dev_get_drvdata ( dev ) ;
if ( ! drv )
return ERR_PTR ( - EINVAL ) ;
if ( WARN_ON ( args - > args [ 0 ] > = drv - > cfg - > num_phys ) )
return ERR_PTR ( - ENODEV ) ;
return drv - > instances [ args - > args [ 0 ] ] . phy ;
}
static const struct of_device_id samsung_usb2_phy_of_match [ ] = {
# ifdef CONFIG_PHY_EXYNOS4210_USB2
{
. compatible = " samsung,exynos4210-usb2-phy " ,
. data = & exynos4210_usb2_phy_config ,
} ,
# endif
# ifdef CONFIG_PHY_EXYNOS4X12_USB2
{
. compatible = " samsung,exynos4x12-usb2-phy " ,
. data = & exynos4x12_usb2_phy_config ,
} ,
2014-03-06 15:16:49 +04:00
# endif
# ifdef CONFIG_PHY_EXYNOS5250_USB2
{
. compatible = " samsung,exynos5250-usb2-phy " ,
. data = & exynos5250_usb2_phy_config ,
} ,
2014-03-06 15:16:48 +04:00
# endif
{ } ,
} ;
static int samsung_usb2_phy_probe ( struct platform_device * pdev )
{
const struct of_device_id * match ;
const struct samsung_usb2_phy_config * cfg ;
struct device * dev = & pdev - > dev ;
struct phy_provider * phy_provider ;
struct resource * mem ;
struct samsung_usb2_phy_driver * drv ;
int i , ret ;
if ( ! pdev - > dev . of_node ) {
dev_err ( dev , " This driver is required to be instantiated from device tree \n " ) ;
return - EINVAL ;
}
match = of_match_node ( samsung_usb2_phy_of_match , pdev - > dev . of_node ) ;
if ( ! match ) {
dev_err ( dev , " of_match_node() failed \n " ) ;
return - EINVAL ;
}
cfg = match - > data ;
drv = devm_kzalloc ( dev , sizeof ( struct samsung_usb2_phy_driver ) +
cfg - > num_phys * sizeof ( struct samsung_usb2_phy_instance ) ,
GFP_KERNEL ) ;
if ( ! drv )
return - ENOMEM ;
dev_set_drvdata ( dev , drv ) ;
spin_lock_init ( & drv - > lock ) ;
drv - > cfg = cfg ;
drv - > dev = dev ;
mem = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
drv - > reg_phy = devm_ioremap_resource ( dev , mem ) ;
if ( IS_ERR ( drv - > reg_phy ) ) {
dev_err ( dev , " Failed to map register memory (phy) \n " ) ;
return PTR_ERR ( drv - > reg_phy ) ;
}
drv - > reg_pmu = syscon_regmap_lookup_by_phandle ( pdev - > dev . of_node ,
" samsung,pmureg-phandle " ) ;
if ( IS_ERR ( drv - > reg_pmu ) ) {
dev_err ( dev , " Failed to map PMU registers (via syscon) \n " ) ;
return PTR_ERR ( drv - > reg_pmu ) ;
}
if ( drv - > cfg - > has_mode_switch ) {
drv - > reg_sys = syscon_regmap_lookup_by_phandle (
pdev - > dev . of_node , " samsung,sysreg-phandle " ) ;
if ( IS_ERR ( drv - > reg_sys ) ) {
dev_err ( dev , " Failed to map system registers (via syscon) \n " ) ;
return PTR_ERR ( drv - > reg_sys ) ;
}
}
drv - > clk = devm_clk_get ( dev , " phy " ) ;
if ( IS_ERR ( drv - > clk ) ) {
dev_err ( dev , " Failed to get clock of phy controller \n " ) ;
return PTR_ERR ( drv - > clk ) ;
}
drv - > ref_clk = devm_clk_get ( dev , " ref " ) ;
if ( IS_ERR ( drv - > ref_clk ) ) {
dev_err ( dev , " Failed to get reference clock for the phy controller \n " ) ;
return PTR_ERR ( drv - > ref_clk ) ;
}
drv - > ref_rate = clk_get_rate ( drv - > ref_clk ) ;
if ( drv - > cfg - > rate_to_clk ) {
ret = drv - > cfg - > rate_to_clk ( drv - > ref_rate , & drv - > ref_reg_val ) ;
if ( ret )
return ret ;
}
for ( i = 0 ; i < drv - > cfg - > num_phys ; i + + ) {
char * label = drv - > cfg - > phys [ i ] . label ;
struct samsung_usb2_phy_instance * p = & drv - > instances [ i ] ;
dev_dbg ( dev , " Creating phy \" %s \" \n " , label ) ;
p - > phy = devm_phy_create ( dev , & samsung_usb2_phy_ops , NULL ) ;
if ( IS_ERR ( p - > phy ) ) {
dev_err ( drv - > dev , " Failed to create usb2_phy \" %s \" \n " ,
label ) ;
return PTR_ERR ( p - > phy ) ;
}
p - > cfg = & drv - > cfg - > phys [ i ] ;
p - > drv = drv ;
phy_set_bus_width ( p - > phy , 8 ) ;
phy_set_drvdata ( p - > phy , p ) ;
}
phy_provider = devm_of_phy_provider_register ( dev ,
samsung_usb2_phy_xlate ) ;
if ( IS_ERR ( phy_provider ) ) {
dev_err ( drv - > dev , " Failed to register phy provider \n " ) ;
return PTR_ERR ( phy_provider ) ;
}
return 0 ;
}
static struct platform_driver samsung_usb2_phy_driver = {
. probe = samsung_usb2_phy_probe ,
. driver = {
. of_match_table = samsung_usb2_phy_of_match ,
. name = " samsung-usb2-phy " ,
. owner = THIS_MODULE ,
}
} ;
module_platform_driver ( samsung_usb2_phy_driver ) ;
MODULE_DESCRIPTION ( " Samsung S5P/EXYNOS SoC USB PHY driver " ) ;
MODULE_AUTHOR ( " Kamil Debski <k.debski@samsung.com> " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_ALIAS ( " platform:samsung-usb2-phy " ) ;