2014-07-22 23:27:14 +04:00
/*
* Renesas R - Car Gen2 PHY driver
*
* Copyright ( C ) 2014 Renesas Solutions Corp .
* Copyright ( C ) 2014 Cogent Embedded , Inc .
*
* 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/delay.h>
# include <linux/io.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/phy/phy.h>
# include <linux/platform_device.h>
# include <linux/spinlock.h>
2015-08-26 14:52:46 +03:00
# include <linux/atomic.h>
2014-07-22 23:27:14 +04:00
# define USBHS_LPSTS 0x02
# define USBHS_UGCTRL 0x80
# define USBHS_UGCTRL2 0x84
2015-04-02 11:01:11 +03:00
# define USBHS_UGSTS 0x88 /* From technical update */
2014-07-22 23:27:14 +04:00
/* Low Power Status register (LPSTS) */
# define USBHS_LPSTS_SUSPM 0x4000
/* USB General control register (UGCTRL) */
# define USBHS_UGCTRL_CONNECT 0x00000004
# define USBHS_UGCTRL_PLLRESET 0x00000001
/* USB General control register 2 (UGCTRL2) */
# define USBHS_UGCTRL2_USB2SEL 0x80000000
# define USBHS_UGCTRL2_USB2SEL_PCI 0x00000000
# define USBHS_UGCTRL2_USB2SEL_USB30 0x80000000
# define USBHS_UGCTRL2_USB0SEL 0x00000030
# define USBHS_UGCTRL2_USB0SEL_PCI 0x00000010
# define USBHS_UGCTRL2_USB0SEL_HS_USB 0x00000030
/* USB General status register (UGSTS) */
2015-04-02 11:01:11 +03:00
# define USBHS_UGSTS_LOCK 0x00000100 /* From technical update */
2014-07-22 23:27:14 +04:00
# define PHYS_PER_CHANNEL 2
struct rcar_gen2_phy {
struct phy * phy ;
struct rcar_gen2_channel * channel ;
int number ;
u32 select_value ;
} ;
struct rcar_gen2_channel {
struct device_node * of_node ;
struct rcar_gen2_phy_driver * drv ;
struct rcar_gen2_phy phys [ PHYS_PER_CHANNEL ] ;
int selected_phy ;
u32 select_mask ;
} ;
struct rcar_gen2_phy_driver {
void __iomem * base ;
struct clk * clk ;
spinlock_t lock ;
int num_channels ;
struct rcar_gen2_channel * channels ;
} ;
static int rcar_gen2_phy_init ( struct phy * p )
{
struct rcar_gen2_phy * phy = phy_get_drvdata ( p ) ;
struct rcar_gen2_channel * channel = phy - > channel ;
struct rcar_gen2_phy_driver * drv = channel - > drv ;
unsigned long flags ;
u32 ugctrl2 ;
/*
* Try to acquire exclusive access to PHY . The first driver calling
* phy_init ( ) on a given channel wins , and all attempts to use another
* PHY on this channel will fail until phy_exit ( ) is called by the first
* driver . Achieving this with cmpxcgh ( ) should be SMP - safe .
*/
if ( cmpxchg ( & channel - > selected_phy , - 1 , phy - > number ) ! = - 1 )
return - EBUSY ;
clk_prepare_enable ( drv - > clk ) ;
spin_lock_irqsave ( & drv - > lock , flags ) ;
ugctrl2 = readl ( drv - > base + USBHS_UGCTRL2 ) ;
ugctrl2 & = ~ channel - > select_mask ;
ugctrl2 | = phy - > select_value ;
writel ( ugctrl2 , drv - > base + USBHS_UGCTRL2 ) ;
spin_unlock_irqrestore ( & drv - > lock , flags ) ;
return 0 ;
}
static int rcar_gen2_phy_exit ( struct phy * p )
{
struct rcar_gen2_phy * phy = phy_get_drvdata ( p ) ;
struct rcar_gen2_channel * channel = phy - > channel ;
clk_disable_unprepare ( channel - > drv - > clk ) ;
channel - > selected_phy = - 1 ;
return 0 ;
}
static int rcar_gen2_phy_power_on ( struct phy * p )
{
struct rcar_gen2_phy * phy = phy_get_drvdata ( p ) ;
struct rcar_gen2_phy_driver * drv = phy - > channel - > drv ;
void __iomem * base = drv - > base ;
unsigned long flags ;
u32 value ;
int err = 0 , i ;
/* Skip if it's not USBHS */
if ( phy - > select_value ! = USBHS_UGCTRL2_USB0SEL_HS_USB )
return 0 ;
spin_lock_irqsave ( & drv - > lock , flags ) ;
/* Power on USBHS PHY */
value = readl ( base + USBHS_UGCTRL ) ;
value & = ~ USBHS_UGCTRL_PLLRESET ;
writel ( value , base + USBHS_UGCTRL ) ;
value = readw ( base + USBHS_LPSTS ) ;
value | = USBHS_LPSTS_SUSPM ;
writew ( value , base + USBHS_LPSTS ) ;
for ( i = 0 ; i < 20 ; i + + ) {
value = readl ( base + USBHS_UGSTS ) ;
if ( ( value & USBHS_UGSTS_LOCK ) = = USBHS_UGSTS_LOCK ) {
value = readl ( base + USBHS_UGCTRL ) ;
value | = USBHS_UGCTRL_CONNECT ;
writel ( value , base + USBHS_UGCTRL ) ;
goto out ;
}
udelay ( 1 ) ;
}
/* Timed out waiting for the PLL lock */
err = - ETIMEDOUT ;
out :
spin_unlock_irqrestore ( & drv - > lock , flags ) ;
return err ;
}
static int rcar_gen2_phy_power_off ( struct phy * p )
{
struct rcar_gen2_phy * phy = phy_get_drvdata ( p ) ;
struct rcar_gen2_phy_driver * drv = phy - > channel - > drv ;
void __iomem * base = drv - > base ;
unsigned long flags ;
u32 value ;
/* Skip if it's not USBHS */
if ( phy - > select_value ! = USBHS_UGCTRL2_USB0SEL_HS_USB )
return 0 ;
spin_lock_irqsave ( & drv - > lock , flags ) ;
/* Power off USBHS PHY */
value = readl ( base + USBHS_UGCTRL ) ;
value & = ~ USBHS_UGCTRL_CONNECT ;
writel ( value , base + USBHS_UGCTRL ) ;
value = readw ( base + USBHS_LPSTS ) ;
value & = ~ USBHS_LPSTS_SUSPM ;
writew ( value , base + USBHS_LPSTS ) ;
value = readl ( base + USBHS_UGCTRL ) ;
value | = USBHS_UGCTRL_PLLRESET ;
writel ( value , base + USBHS_UGCTRL ) ;
spin_unlock_irqrestore ( & drv - > lock , flags ) ;
return 0 ;
}
2015-07-15 10:33:51 +03:00
static const struct phy_ops rcar_gen2_phy_ops = {
2014-07-22 23:27:14 +04:00
. init = rcar_gen2_phy_init ,
. exit = rcar_gen2_phy_exit ,
. power_on = rcar_gen2_phy_power_on ,
. power_off = rcar_gen2_phy_power_off ,
. owner = THIS_MODULE ,
} ;
static const struct of_device_id rcar_gen2_phy_match_table [ ] = {
{ . compatible = " renesas,usb-phy-r8a7790 " } ,
{ . compatible = " renesas,usb-phy-r8a7791 " } ,
2015-05-18 12:33:47 +03:00
{ . compatible = " renesas,usb-phy-r8a7794 " } ,
2014-07-22 23:27:14 +04:00
{ }
} ;
MODULE_DEVICE_TABLE ( of , rcar_gen2_phy_match_table ) ;
static struct phy * rcar_gen2_phy_xlate ( struct device * dev ,
struct of_phandle_args * args )
{
struct rcar_gen2_phy_driver * drv ;
struct device_node * np = args - > np ;
int i ;
drv = dev_get_drvdata ( dev ) ;
if ( ! drv )
return ERR_PTR ( - EINVAL ) ;
for ( i = 0 ; i < drv - > num_channels ; i + + ) {
if ( np = = drv - > channels [ i ] . of_node )
break ;
}
if ( i > = drv - > num_channels | | args - > args [ 0 ] > = 2 )
return ERR_PTR ( - ENODEV ) ;
return drv - > channels [ i ] . phys [ args - > args [ 0 ] ] . phy ;
}
static const u32 select_mask [ ] = {
[ 0 ] = USBHS_UGCTRL2_USB0SEL ,
[ 2 ] = USBHS_UGCTRL2_USB2SEL ,
} ;
static const u32 select_value [ ] [ PHYS_PER_CHANNEL ] = {
[ 0 ] = { USBHS_UGCTRL2_USB0SEL_PCI , USBHS_UGCTRL2_USB0SEL_HS_USB } ,
[ 2 ] = { USBHS_UGCTRL2_USB2SEL_PCI , USBHS_UGCTRL2_USB2SEL_USB30 } ,
} ;
static int rcar_gen2_phy_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct rcar_gen2_phy_driver * drv ;
struct phy_provider * provider ;
struct device_node * np ;
struct resource * res ;
void __iomem * base ;
struct clk * clk ;
int i = 0 ;
if ( ! dev - > of_node ) {
dev_err ( dev ,
" This driver is required to be instantiated from device tree \n " ) ;
return - EINVAL ;
}
clk = devm_clk_get ( dev , " usbhs " ) ;
if ( IS_ERR ( clk ) ) {
dev_err ( dev , " Can't get USBHS clock \n " ) ;
return PTR_ERR ( clk ) ;
}
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
base = devm_ioremap_resource ( dev , res ) ;
if ( IS_ERR ( base ) )
return PTR_ERR ( base ) ;
drv = devm_kzalloc ( dev , sizeof ( * drv ) , GFP_KERNEL ) ;
if ( ! drv )
return - ENOMEM ;
spin_lock_init ( & drv - > lock ) ;
drv - > clk = clk ;
drv - > base = base ;
drv - > num_channels = of_get_child_count ( dev - > of_node ) ;
drv - > channels = devm_kcalloc ( dev , drv - > num_channels ,
sizeof ( struct rcar_gen2_channel ) ,
GFP_KERNEL ) ;
if ( ! drv - > channels )
return - ENOMEM ;
for_each_child_of_node ( dev - > of_node , np ) {
struct rcar_gen2_channel * channel = drv - > channels + i ;
u32 channel_num ;
int error , n ;
channel - > of_node = np ;
channel - > drv = drv ;
channel - > selected_phy = - 1 ;
error = of_property_read_u32 ( np , " reg " , & channel_num ) ;
if ( error | | channel_num > 2 ) {
dev_err ( dev , " Invalid \" reg \" property \n " ) ;
return error ;
}
channel - > select_mask = select_mask [ channel_num ] ;
for ( n = 0 ; n < PHYS_PER_CHANNEL ; n + + ) {
struct rcar_gen2_phy * phy = & channel - > phys [ n ] ;
phy - > channel = channel ;
phy - > number = n ;
phy - > select_value = select_value [ channel_num ] [ n ] ;
phy - > phy = devm_phy_create ( dev , NULL ,
2014-11-19 18:28:21 +03:00
& rcar_gen2_phy_ops ) ;
2014-07-22 23:27:14 +04:00
if ( IS_ERR ( phy - > phy ) ) {
dev_err ( dev , " Failed to create PHY \n " ) ;
return PTR_ERR ( phy - > phy ) ;
}
phy_set_drvdata ( phy - > phy , phy ) ;
}
i + + ;
}
provider = devm_of_phy_provider_register ( dev , rcar_gen2_phy_xlate ) ;
if ( IS_ERR ( provider ) ) {
dev_err ( dev , " Failed to register PHY provider \n " ) ;
return PTR_ERR ( provider ) ;
}
dev_set_drvdata ( dev , drv ) ;
return 0 ;
}
static struct platform_driver rcar_gen2_phy_driver = {
. driver = {
. name = " phy_rcar_gen2 " ,
. of_match_table = rcar_gen2_phy_match_table ,
} ,
. probe = rcar_gen2_phy_probe ,
} ;
module_platform_driver ( rcar_gen2_phy_driver ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_DESCRIPTION ( " Renesas R-Car Gen2 PHY " ) ;
MODULE_AUTHOR ( " Sergei Shtylyov <sergei.shtylyov@cogentembedded.com> " ) ;