2015-03-14 06:57:16 +03:00
/*
* Allwinner sun9i USB phy driver
*
* Copyright ( C ) 2014 - 2015 Chen - Yu Tsai < wens @ csie . org >
*
* Based on phy - sun4i - usb . c from
* Hans de Goede < hdegoede @ redhat . com >
*
* and code from
* Allwinner Technology Co . , Ltd . < www . allwinnertech . 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 .
*
* 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/clk.h>
# include <linux/err.h>
# include <linux/io.h>
# include <linux/module.h>
# include <linux/phy/phy.h>
# include <linux/usb/of.h>
# include <linux/platform_device.h>
# include <linux/reset.h>
# define SUNXI_AHB_INCR16_BURST_EN BIT(11)
# define SUNXI_AHB_INCR8_BURST_EN BIT(10)
# define SUNXI_AHB_INCR4_BURST_EN BIT(9)
# define SUNXI_AHB_INCRX_ALIGN_EN BIT(8)
# define SUNXI_ULPI_BYPASS_EN BIT(0)
/* usb1 HSIC specific bits */
# define SUNXI_EHCI_HS_FORCE BIT(20)
# define SUNXI_HSIC_CONNECT_DET BIT(17)
# define SUNXI_HSIC_CONNECT_INT BIT(16)
# define SUNXI_HSIC BIT(1)
struct sun9i_usb_phy {
struct phy * phy ;
void __iomem * pmu ;
struct reset_control * reset ;
struct clk * clk ;
struct clk * hsic_clk ;
enum usb_phy_interface type ;
} ;
static void sun9i_usb_phy_passby ( struct sun9i_usb_phy * phy , int enable )
{
u32 bits , reg_value ;
bits = SUNXI_AHB_INCR16_BURST_EN | SUNXI_AHB_INCR8_BURST_EN |
SUNXI_AHB_INCR4_BURST_EN | SUNXI_AHB_INCRX_ALIGN_EN |
SUNXI_ULPI_BYPASS_EN ;
if ( phy - > type = = USBPHY_INTERFACE_MODE_HSIC )
bits | = SUNXI_HSIC | SUNXI_EHCI_HS_FORCE |
SUNXI_HSIC_CONNECT_DET | SUNXI_HSIC_CONNECT_INT ;
reg_value = readl ( phy - > pmu ) ;
if ( enable )
reg_value | = bits ;
else
reg_value & = ~ bits ;
writel ( reg_value , phy - > pmu ) ;
}
static int sun9i_usb_phy_init ( struct phy * _phy )
{
struct sun9i_usb_phy * phy = phy_get_drvdata ( _phy ) ;
int ret ;
ret = clk_prepare_enable ( phy - > clk ) ;
if ( ret )
goto err_clk ;
ret = clk_prepare_enable ( phy - > hsic_clk ) ;
if ( ret )
goto err_hsic_clk ;
ret = reset_control_deassert ( phy - > reset ) ;
if ( ret )
goto err_reset ;
sun9i_usb_phy_passby ( phy , 1 ) ;
return 0 ;
err_reset :
clk_disable_unprepare ( phy - > hsic_clk ) ;
err_hsic_clk :
clk_disable_unprepare ( phy - > clk ) ;
err_clk :
return ret ;
}
static int sun9i_usb_phy_exit ( struct phy * _phy )
{
struct sun9i_usb_phy * phy = phy_get_drvdata ( _phy ) ;
sun9i_usb_phy_passby ( phy , 0 ) ;
reset_control_assert ( phy - > reset ) ;
clk_disable_unprepare ( phy - > hsic_clk ) ;
clk_disable_unprepare ( phy - > clk ) ;
return 0 ;
}
2015-07-15 10:33:51 +03:00
static const struct phy_ops sun9i_usb_phy_ops = {
2015-03-14 06:57:16 +03:00
. init = sun9i_usb_phy_init ,
. exit = sun9i_usb_phy_exit ,
. owner = THIS_MODULE ,
} ;
static int sun9i_usb_phy_probe ( struct platform_device * pdev )
{
struct sun9i_usb_phy * phy ;
struct device * dev = & pdev - > dev ;
struct device_node * np = dev - > of_node ;
struct phy_provider * phy_provider ;
struct resource * res ;
phy = devm_kzalloc ( dev , sizeof ( * phy ) , GFP_KERNEL ) ;
if ( ! phy )
return - ENOMEM ;
phy - > type = of_usb_get_phy_mode ( np ) ;
if ( phy - > type = = USBPHY_INTERFACE_MODE_HSIC ) {
phy - > clk = devm_clk_get ( dev , " hsic_480M " ) ;
if ( IS_ERR ( phy - > clk ) ) {
dev_err ( dev , " failed to get hsic_480M clock \n " ) ;
return PTR_ERR ( phy - > clk ) ;
}
phy - > hsic_clk = devm_clk_get ( dev , " hsic_12M " ) ;
if ( IS_ERR ( phy - > clk ) ) {
dev_err ( dev , " failed to get hsic_12M clock \n " ) ;
return PTR_ERR ( phy - > clk ) ;
}
phy - > reset = devm_reset_control_get ( dev , " hsic " ) ;
if ( IS_ERR ( phy - > reset ) ) {
dev_err ( dev , " failed to get reset control \n " ) ;
return PTR_ERR ( phy - > reset ) ;
}
} else {
phy - > clk = devm_clk_get ( dev , " phy " ) ;
if ( IS_ERR ( phy - > clk ) ) {
dev_err ( dev , " failed to get phy clock \n " ) ;
return PTR_ERR ( phy - > clk ) ;
}
phy - > reset = devm_reset_control_get ( dev , " phy " ) ;
if ( IS_ERR ( phy - > reset ) ) {
dev_err ( dev , " failed to get reset control \n " ) ;
return PTR_ERR ( phy - > reset ) ;
}
}
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
phy - > pmu = devm_ioremap_resource ( dev , res ) ;
if ( IS_ERR ( phy - > pmu ) )
return PTR_ERR ( phy - > pmu ) ;
phy - > phy = devm_phy_create ( dev , NULL , & sun9i_usb_phy_ops ) ;
if ( IS_ERR ( phy - > phy ) ) {
dev_err ( dev , " failed to create PHY \n " ) ;
return PTR_ERR ( phy - > phy ) ;
}
phy_set_drvdata ( phy - > phy , phy ) ;
phy_provider = devm_of_phy_provider_register ( dev , of_phy_simple_xlate ) ;
return PTR_ERR_OR_ZERO ( phy_provider ) ;
}
static const struct of_device_id sun9i_usb_phy_of_match [ ] = {
{ . compatible = " allwinner,sun9i-a80-usb-phy " } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , sun9i_usb_phy_of_match ) ;
static struct platform_driver sun9i_usb_phy_driver = {
. probe = sun9i_usb_phy_probe ,
. driver = {
. of_match_table = sun9i_usb_phy_of_match ,
. name = " sun9i-usb-phy " ,
}
} ;
module_platform_driver ( sun9i_usb_phy_driver ) ;
MODULE_DESCRIPTION ( " Allwinner sun9i USB phy driver " ) ;
MODULE_AUTHOR ( " Chen-Yu Tsai <wens@csie.org> " ) ;
MODULE_LICENSE ( " GPL " ) ;