2019-03-25 10:39:41 +01:00
// SPDX-License-Identifier: GPL-2.0
/*
* Amlogic G12A USB3 + PCIE Combo PHY driver
*
* Copyright ( C ) 2017 Amlogic , Inc . All rights reserved
* Copyright ( C ) 2019 BayLibre , SAS
* Author : Neil Armstrong < narmstrong @ baylibre . com >
*/
# include <linux/bitfield.h>
# include <linux/bitops.h>
# include <linux/clk.h>
# include <linux/module.h>
# include <linux/of_device.h>
# include <linux/phy/phy.h>
# include <linux/regmap.h>
# include <linux/reset.h>
# include <linux/platform_device.h>
# include <dt-bindings/phy/phy.h>
# define PHY_R0 0x00
# define PHY_R0_PCIE_POWER_STATE GENMASK(4, 0)
# define PHY_R0_PCIE_USB3_SWITCH GENMASK(6, 5)
# define PHY_R1 0x04
# define PHY_R1_PHY_TX1_TERM_OFFSET GENMASK(4, 0)
# define PHY_R1_PHY_TX0_TERM_OFFSET GENMASK(9, 5)
# define PHY_R1_PHY_RX1_EQ GENMASK(12, 10)
# define PHY_R1_PHY_RX0_EQ GENMASK(15, 13)
# define PHY_R1_PHY_LOS_LEVEL GENMASK(20, 16)
# define PHY_R1_PHY_LOS_BIAS GENMASK(23, 21)
# define PHY_R1_PHY_REF_CLKDIV2 BIT(24)
# define PHY_R1_PHY_MPLL_MULTIPLIER GENMASK(31, 25)
# define PHY_R2 0x08
# define PHY_R2_PCS_TX_DEEMPH_GEN2_6DB GENMASK(5, 0)
# define PHY_R2_PCS_TX_DEEMPH_GEN2_3P5DB GENMASK(11, 6)
# define PHY_R2_PCS_TX_DEEMPH_GEN1 GENMASK(17, 12)
# define PHY_R2_PHY_TX_VBOOST_LVL GENMASK(20, 18)
# define PHY_R4 0x10
# define PHY_R4_PHY_CR_WRITE BIT(0)
# define PHY_R4_PHY_CR_READ BIT(1)
# define PHY_R4_PHY_CR_DATA_IN GENMASK(17, 2)
# define PHY_R4_PHY_CR_CAP_DATA BIT(18)
# define PHY_R4_PHY_CR_CAP_ADDR BIT(19)
# define PHY_R5 0x14
# define PHY_R5_PHY_CR_DATA_OUT GENMASK(15, 0)
# define PHY_R5_PHY_CR_ACK BIT(16)
# define PHY_R5_PHY_BS_OUT BIT(17)
2019-09-16 14:50:20 +02:00
# define PCIE_RESET_DELAY 500
2019-03-25 10:39:41 +01:00
struct phy_g12a_usb3_pcie_priv {
struct regmap * regmap ;
struct regmap * regmap_cr ;
struct clk * clk_ref ;
struct reset_control * reset ;
struct phy * phy ;
unsigned int mode ;
} ;
static const struct regmap_config phy_g12a_usb3_pcie_regmap_conf = {
. reg_bits = 8 ,
. val_bits = 32 ,
. reg_stride = 4 ,
. max_register = PHY_R5 ,
} ;
static int phy_g12a_usb3_pcie_cr_bus_addr ( struct phy_g12a_usb3_pcie_priv * priv ,
unsigned int addr )
{
unsigned int val , reg ;
int ret ;
reg = FIELD_PREP ( PHY_R4_PHY_CR_DATA_IN , addr ) ;
regmap_write ( priv - > regmap , PHY_R4 , reg ) ;
regmap_write ( priv - > regmap , PHY_R4 , reg ) ;
regmap_write ( priv - > regmap , PHY_R4 , reg | PHY_R4_PHY_CR_CAP_ADDR ) ;
ret = regmap_read_poll_timeout ( priv - > regmap , PHY_R5 , val ,
( val & PHY_R5_PHY_CR_ACK ) ,
5 , 1000 ) ;
if ( ret )
return ret ;
regmap_write ( priv - > regmap , PHY_R4 , reg ) ;
ret = regmap_read_poll_timeout ( priv - > regmap , PHY_R5 , val ,
! ( val & PHY_R5_PHY_CR_ACK ) ,
5 , 1000 ) ;
if ( ret )
return ret ;
return 0 ;
}
static int phy_g12a_usb3_pcie_cr_bus_read ( void * context , unsigned int addr ,
unsigned int * data )
{
struct phy_g12a_usb3_pcie_priv * priv = context ;
unsigned int val ;
int ret ;
ret = phy_g12a_usb3_pcie_cr_bus_addr ( priv , addr ) ;
if ( ret )
return ret ;
regmap_write ( priv - > regmap , PHY_R4 , 0 ) ;
regmap_write ( priv - > regmap , PHY_R4 , PHY_R4_PHY_CR_READ ) ;
ret = regmap_read_poll_timeout ( priv - > regmap , PHY_R5 , val ,
( val & PHY_R5_PHY_CR_ACK ) ,
5 , 1000 ) ;
if ( ret )
return ret ;
* data = FIELD_GET ( PHY_R5_PHY_CR_DATA_OUT , val ) ;
regmap_write ( priv - > regmap , PHY_R4 , 0 ) ;
ret = regmap_read_poll_timeout ( priv - > regmap , PHY_R5 , val ,
! ( val & PHY_R5_PHY_CR_ACK ) ,
5 , 1000 ) ;
if ( ret )
return ret ;
return 0 ;
}
static int phy_g12a_usb3_pcie_cr_bus_write ( void * context , unsigned int addr ,
unsigned int data )
{
struct phy_g12a_usb3_pcie_priv * priv = context ;
unsigned int val , reg ;
int ret ;
ret = phy_g12a_usb3_pcie_cr_bus_addr ( priv , addr ) ;
if ( ret )
return ret ;
reg = FIELD_PREP ( PHY_R4_PHY_CR_DATA_IN , data ) ;
regmap_write ( priv - > regmap , PHY_R4 , reg ) ;
regmap_write ( priv - > regmap , PHY_R4 , reg ) ;
regmap_write ( priv - > regmap , PHY_R4 , reg | PHY_R4_PHY_CR_CAP_DATA ) ;
ret = regmap_read_poll_timeout ( priv - > regmap , PHY_R5 , val ,
( val & PHY_R5_PHY_CR_ACK ) ,
5 , 1000 ) ;
if ( ret )
return ret ;
regmap_write ( priv - > regmap , PHY_R4 , reg ) ;
ret = regmap_read_poll_timeout ( priv - > regmap , PHY_R5 , val ,
( val & PHY_R5_PHY_CR_ACK ) = = 0 ,
5 , 1000 ) ;
if ( ret )
return ret ;
regmap_write ( priv - > regmap , PHY_R4 , reg ) ;
regmap_write ( priv - > regmap , PHY_R4 , reg | PHY_R4_PHY_CR_WRITE ) ;
ret = regmap_read_poll_timeout ( priv - > regmap , PHY_R5 , val ,
( val & PHY_R5_PHY_CR_ACK ) ,
5 , 1000 ) ;
if ( ret )
return ret ;
regmap_write ( priv - > regmap , PHY_R4 , reg ) ;
ret = regmap_read_poll_timeout ( priv - > regmap , PHY_R5 , val ,
( val & PHY_R5_PHY_CR_ACK ) = = 0 ,
5 , 1000 ) ;
if ( ret )
return ret ;
return 0 ;
}
static const struct regmap_config phy_g12a_usb3_pcie_cr_regmap_conf = {
. reg_bits = 16 ,
. val_bits = 16 ,
. reg_read = phy_g12a_usb3_pcie_cr_bus_read ,
. reg_write = phy_g12a_usb3_pcie_cr_bus_write ,
. max_register = 0xffff ,
2019-06-05 11:02:15 +02:00
. disable_locking = true ,
2019-03-25 10:39:41 +01:00
} ;
static int phy_g12a_usb3_init ( struct phy * phy )
{
struct phy_g12a_usb3_pcie_priv * priv = phy_get_drvdata ( phy ) ;
int data , ret ;
2019-09-16 14:50:20 +02:00
ret = reset_control_reset ( priv - > reset ) ;
if ( ret )
return ret ;
2019-03-25 10:39:41 +01:00
/* Switch PHY to USB3 */
/* TODO figure out how to handle when PCIe was set in the bootloader */
regmap_update_bits ( priv - > regmap , PHY_R0 ,
PHY_R0_PCIE_USB3_SWITCH ,
PHY_R0_PCIE_USB3_SWITCH ) ;
/*
* WORKAROUND : There is SSPHY suspend bug due to
* which USB enumerates
* in HS mode instead of SS mode . Workaround it by asserting
* LANE0 . TX_ALT_BLOCK . EN_ALT_BUS to enable TX to use alt bus
* mode
*/
ret = regmap_update_bits ( priv - > regmap_cr , 0x102d , BIT ( 7 ) , BIT ( 7 ) ) ;
if ( ret )
return ret ;
ret = regmap_update_bits ( priv - > regmap_cr , 0x1010 , 0xff0 , 20 ) ;
if ( ret )
return ret ;
/*
* Fix RX Equalization setting as follows
* LANE0 . RX_OVRD_IN_HI . RX_EQ_EN set to 0
* LANE0 . RX_OVRD_IN_HI . RX_EQ_EN_OVRD set to 1
* LANE0 . RX_OVRD_IN_HI . RX_EQ set to 3
* LANE0 . RX_OVRD_IN_HI . RX_EQ_OVRD set to 1
*/
ret = regmap_read ( priv - > regmap_cr , 0x1006 , & data ) ;
if ( ret )
return ret ;
data & = ~ BIT ( 6 ) ;
data | = BIT ( 7 ) ;
data & = ~ ( 0x7 < < 8 ) ;
data | = ( 0x3 < < 8 ) ;
data | = ( 1 < < 11 ) ;
ret = regmap_write ( priv - > regmap_cr , 0x1006 , data ) ;
if ( ret )
return ret ;
/*
* Set EQ and TX launch amplitudes as follows
* LANE0 . TX_OVRD_DRV_LO . PREEMPH set to 22
* LANE0 . TX_OVRD_DRV_LO . AMPLITUDE set to 127
* LANE0 . TX_OVRD_DRV_LO . EN set to 1.
*/
ret = regmap_read ( priv - > regmap_cr , 0x1002 , & data ) ;
if ( ret )
return ret ;
data & = ~ 0x3f80 ;
data | = ( 0x16 < < 7 ) ;
data & = ~ 0x7f ;
data | = ( 0x7f | BIT ( 14 ) ) ;
ret = regmap_write ( priv - > regmap_cr , 0x1002 , data ) ;
if ( ret )
return ret ;
/* MPLL_LOOP_CTL.PROP_CNTRL = 8 */
ret = regmap_update_bits ( priv - > regmap_cr , 0x30 , 0xf < < 4 , 8 < < 4 ) ;
if ( ret )
return ret ;
regmap_update_bits ( priv - > regmap , PHY_R2 ,
PHY_R2_PHY_TX_VBOOST_LVL ,
FIELD_PREP ( PHY_R2_PHY_TX_VBOOST_LVL , 0x4 ) ) ;
regmap_update_bits ( priv - > regmap , PHY_R1 ,
PHY_R1_PHY_LOS_BIAS | PHY_R1_PHY_LOS_LEVEL ,
FIELD_PREP ( PHY_R1_PHY_LOS_BIAS , 4 ) |
FIELD_PREP ( PHY_R1_PHY_LOS_LEVEL , 9 ) ) ;
return 0 ;
}
2019-09-16 14:50:20 +02:00
static int phy_g12a_usb3_pcie_power_on ( struct phy * phy )
{
struct phy_g12a_usb3_pcie_priv * priv = phy_get_drvdata ( phy ) ;
if ( priv - > mode = = PHY_TYPE_USB3 )
return 0 ;
regmap_update_bits ( priv - > regmap , PHY_R0 ,
PHY_R0_PCIE_POWER_STATE ,
FIELD_PREP ( PHY_R0_PCIE_POWER_STATE , 0x1c ) ) ;
return 0 ;
}
static int phy_g12a_usb3_pcie_power_off ( struct phy * phy )
{
struct phy_g12a_usb3_pcie_priv * priv = phy_get_drvdata ( phy ) ;
if ( priv - > mode = = PHY_TYPE_USB3 )
return 0 ;
regmap_update_bits ( priv - > regmap , PHY_R0 ,
PHY_R0_PCIE_POWER_STATE ,
FIELD_PREP ( PHY_R0_PCIE_POWER_STATE , 0x1d ) ) ;
return 0 ;
}
static int phy_g12a_usb3_pcie_reset ( struct phy * phy )
2019-03-25 10:39:41 +01:00
{
struct phy_g12a_usb3_pcie_priv * priv = phy_get_drvdata ( phy ) ;
int ret ;
2019-09-16 14:50:20 +02:00
if ( priv - > mode = = PHY_TYPE_USB3 )
return 0 ;
ret = reset_control_assert ( priv - > reset ) ;
2019-03-25 10:39:41 +01:00
if ( ret )
return ret ;
2019-09-16 14:50:20 +02:00
udelay ( PCIE_RESET_DELAY ) ;
ret = reset_control_deassert ( priv - > reset ) ;
if ( ret )
return ret ;
udelay ( PCIE_RESET_DELAY ) ;
return 0 ;
}
static int phy_g12a_usb3_pcie_init ( struct phy * phy )
{
struct phy_g12a_usb3_pcie_priv * priv = phy_get_drvdata ( phy ) ;
2019-03-25 10:39:41 +01:00
if ( priv - > mode = = PHY_TYPE_USB3 )
return phy_g12a_usb3_init ( phy ) ;
return 0 ;
}
static int phy_g12a_usb3_pcie_exit ( struct phy * phy )
{
struct phy_g12a_usb3_pcie_priv * priv = phy_get_drvdata ( phy ) ;
2019-09-16 14:50:20 +02:00
if ( priv - > mode = = PHY_TYPE_USB3 )
return reset_control_reset ( priv - > reset ) ;
return 0 ;
2019-03-25 10:39:41 +01:00
}
static struct phy * phy_g12a_usb3_pcie_xlate ( struct device * dev ,
struct of_phandle_args * args )
{
struct phy_g12a_usb3_pcie_priv * priv = dev_get_drvdata ( dev ) ;
unsigned int mode ;
if ( args - > args_count < 1 ) {
dev_err ( dev , " invalid number of arguments \n " ) ;
return ERR_PTR ( - EINVAL ) ;
}
mode = args - > args [ 0 ] ;
if ( mode ! = PHY_TYPE_USB3 & & mode ! = PHY_TYPE_PCIE ) {
dev_err ( dev , " invalid phy mode select argument \n " ) ;
return ERR_PTR ( - EINVAL ) ;
}
priv - > mode = mode ;
return priv - > phy ;
}
static const struct phy_ops phy_g12a_usb3_pcie_ops = {
. init = phy_g12a_usb3_pcie_init ,
. exit = phy_g12a_usb3_pcie_exit ,
2019-09-16 14:50:20 +02:00
. power_on = phy_g12a_usb3_pcie_power_on ,
. power_off = phy_g12a_usb3_pcie_power_off ,
. reset = phy_g12a_usb3_pcie_reset ,
2019-03-25 10:39:41 +01:00
. owner = THIS_MODULE ,
} ;
static int phy_g12a_usb3_pcie_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct device_node * np = dev - > of_node ;
struct phy_g12a_usb3_pcie_priv * priv ;
struct phy_provider * phy_provider ;
void __iomem * base ;
priv = devm_kzalloc ( dev , sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
2020-11-06 14:08:35 +08:00
base = devm_platform_ioremap_resource ( pdev , 0 ) ;
2019-03-25 10:39:41 +01:00
if ( IS_ERR ( base ) )
return PTR_ERR ( base ) ;
priv - > regmap = devm_regmap_init_mmio ( dev , base ,
& phy_g12a_usb3_pcie_regmap_conf ) ;
if ( IS_ERR ( priv - > regmap ) )
return PTR_ERR ( priv - > regmap ) ;
priv - > regmap_cr = devm_regmap_init ( dev , NULL , priv ,
& phy_g12a_usb3_pcie_cr_regmap_conf ) ;
if ( IS_ERR ( priv - > regmap_cr ) )
return PTR_ERR ( priv - > regmap_cr ) ;
2022-08-30 19:16:07 +02:00
priv - > clk_ref = devm_clk_get_enabled ( dev , " ref_clk " ) ;
2019-03-25 10:39:41 +01:00
if ( IS_ERR ( priv - > clk_ref ) )
return PTR_ERR ( priv - > clk_ref ) ;
2020-11-18 10:36:35 +08:00
priv - > reset = devm_reset_control_array_get_exclusive ( dev ) ;
2022-08-30 19:16:07 +02:00
if ( IS_ERR ( priv - > reset ) )
return PTR_ERR ( priv - > reset ) ;
2019-03-25 10:39:41 +01:00
priv - > phy = devm_phy_create ( dev , np , & phy_g12a_usb3_pcie_ops ) ;
2022-08-30 19:16:07 +02:00
if ( IS_ERR ( priv - > phy ) )
return dev_err_probe ( dev , PTR_ERR ( priv - > phy ) , " failed to create PHY \n " ) ;
2019-03-25 10:39:41 +01:00
phy_set_drvdata ( priv - > phy , priv ) ;
dev_set_drvdata ( dev , priv ) ;
phy_provider = devm_of_phy_provider_register ( dev ,
phy_g12a_usb3_pcie_xlate ) ;
2022-08-30 19:16:07 +02:00
return PTR_ERR_OR_ZERO ( phy_provider ) ;
2019-03-25 10:39:41 +01:00
}
static const struct of_device_id phy_g12a_usb3_pcie_of_match [ ] = {
{ . compatible = " amlogic,g12a-usb3-pcie-phy " , } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , phy_g12a_usb3_pcie_of_match ) ;
static struct platform_driver phy_g12a_usb3_pcie_driver = {
. probe = phy_g12a_usb3_pcie_probe ,
. driver = {
. name = " phy-g12a-usb3-pcie " ,
. of_match_table = phy_g12a_usb3_pcie_of_match ,
} ,
} ;
module_platform_driver ( phy_g12a_usb3_pcie_driver ) ;
MODULE_AUTHOR ( " Neil Armstrong <narmstrong@baylibre.com> " ) ;
MODULE_DESCRIPTION ( " Amlogic G12A USB3 + PCIE Combo PHY driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;