2018-05-10 14:10:29 +08:00
// SPDX-License-Identifier: GPL-2.0
/*
* MediaTek USB3 .1 gen2 xsphy Driver
*
* Copyright ( c ) 2018 MediaTek Inc .
* Author : Chunfeng Yun < chunfeng . yun @ mediatek . com >
*
*/
# include <dt-bindings/phy/phy.h>
# include <linux/clk.h>
# include <linux/delay.h>
# include <linux/iopoll.h>
# include <linux/module.h>
# include <linux/of_address.h>
# include <linux/phy/phy.h>
# include <linux/platform_device.h>
2021-12-18 16:28:01 +08:00
# include "phy-mtk-io.h"
2018-05-10 14:10:29 +08:00
/* u2 phy banks */
# define SSUSB_SIFSLV_MISC 0x000
# define SSUSB_SIFSLV_U2FREQ 0x100
# define SSUSB_SIFSLV_U2PHY_COM 0x300
/* u3 phy shared banks */
# define SSPXTP_SIFSLV_DIG_GLB 0x000
# define SSPXTP_SIFSLV_PHYA_GLB 0x100
/* u3 phy banks */
# define SSPXTP_SIFSLV_DIG_LN_TOP 0x000
# define SSPXTP_SIFSLV_DIG_LN_TX0 0x100
# define SSPXTP_SIFSLV_DIG_LN_RX0 0x200
# define SSPXTP_SIFSLV_DIG_LN_DAIF 0x300
# define SSPXTP_SIFSLV_PHYA_LN 0x400
# define XSP_U2FREQ_FMCR0 ((SSUSB_SIFSLV_U2FREQ) + 0x00)
# define P2F_RG_FREQDET_EN BIT(24)
# define P2F_RG_CYCLECNT GENMASK(23, 0)
# define XSP_U2FREQ_MMONR0 ((SSUSB_SIFSLV_U2FREQ) + 0x0c)
# define XSP_U2FREQ_FMMONR1 ((SSUSB_SIFSLV_U2FREQ) + 0x10)
# define P2F_RG_FRCK_EN BIT(8)
# define P2F_USB_FM_VALID BIT(0)
# define XSP_USBPHYACR0 ((SSUSB_SIFSLV_U2PHY_COM) + 0x00)
# define P2A0_RG_INTR_EN BIT(5)
# define XSP_USBPHYACR1 ((SSUSB_SIFSLV_U2PHY_COM) + 0x04)
# define P2A1_RG_INTR_CAL GENMASK(23, 19)
# define P2A1_RG_VRT_SEL GENMASK(14, 12)
# define P2A1_RG_TERM_SEL GENMASK(10, 8)
# define XSP_USBPHYACR5 ((SSUSB_SIFSLV_U2PHY_COM) + 0x014)
# define P2A5_RG_HSTX_SRCAL_EN BIT(15)
# define P2A5_RG_HSTX_SRCTRL GENMASK(14, 12)
# define XSP_USBPHYACR6 ((SSUSB_SIFSLV_U2PHY_COM) + 0x018)
# define P2A6_RG_BC11_SW_EN BIT(23)
# define P2A6_RG_OTG_VBUSCMP_EN BIT(20)
# define XSP_U2PHYDTM1 ((SSUSB_SIFSLV_U2PHY_COM) + 0x06C)
# define P2D_FORCE_IDDIG BIT(9)
# define P2D_RG_VBUSVALID BIT(5)
# define P2D_RG_SESSEND BIT(4)
# define P2D_RG_AVALID BIT(2)
# define P2D_RG_IDDIG BIT(1)
# define SSPXTP_PHYA_GLB_00 ((SSPXTP_SIFSLV_PHYA_GLB) + 0x00)
# define RG_XTP_GLB_BIAS_INTR_CTRL GENMASK(21, 16)
# define SSPXTP_PHYA_LN_04 ((SSPXTP_SIFSLV_PHYA_LN) + 0x04)
# define RG_XTP_LN0_TX_IMPSEL GENMASK(4, 0)
# define SSPXTP_PHYA_LN_14 ((SSPXTP_SIFSLV_PHYA_LN) + 0x014)
# define RG_XTP_LN0_RX_IMPSEL GENMASK(4, 0)
# define XSP_REF_CLK 26 /* MHZ */
# define XSP_SLEW_RATE_COEF 17
# define XSP_SR_COEF_DIVISOR 1000
# define XSP_FM_DET_CYCLE_CNT 1024
struct xsphy_instance {
struct phy * phy ;
void __iomem * port_base ;
struct clk * ref_clk ; /* reference clock of anolog phy */
u32 index ;
u32 type ;
/* only for HQA test */
int efuse_intr ;
int efuse_tx_imp ;
int efuse_rx_imp ;
/* u2 eye diagram */
int eye_src ;
int eye_vrt ;
int eye_term ;
} ;
struct mtk_xsphy {
struct device * dev ;
void __iomem * glb_base ; /* only shared u3 sif */
struct xsphy_instance * * phys ;
int nphys ;
int src_ref_clk ; /* MHZ, reference clock for slew rate calibrate */
int src_coef ; /* coefficient for slew rate calibrate */
} ;
static void u2_phy_slew_rate_calibrate ( struct mtk_xsphy * xsphy ,
struct xsphy_instance * inst )
{
void __iomem * pbase = inst - > port_base ;
int calib_val ;
int fm_out ;
u32 tmp ;
/* use force value */
if ( inst - > eye_src )
return ;
/* enable USB ring oscillator */
2021-12-18 16:28:01 +08:00
mtk_phy_set_bits ( pbase + XSP_USBPHYACR5 , P2A5_RG_HSTX_SRCAL_EN ) ;
2018-05-10 14:10:29 +08:00
udelay ( 1 ) ; /* wait clock stable */
/* enable free run clock */
2021-12-18 16:28:01 +08:00
mtk_phy_set_bits ( pbase + XSP_U2FREQ_FMMONR1 , P2F_RG_FRCK_EN ) ;
2018-05-10 14:10:29 +08:00
/* set cycle count as 1024 */
2022-09-20 17:00:23 +08:00
mtk_phy_update_field ( pbase + XSP_U2FREQ_FMCR0 , P2F_RG_CYCLECNT ,
XSP_FM_DET_CYCLE_CNT ) ;
2018-05-10 14:10:29 +08:00
/* enable frequency meter */
2021-12-18 16:28:01 +08:00
mtk_phy_set_bits ( pbase + XSP_U2FREQ_FMCR0 , P2F_RG_FREQDET_EN ) ;
2018-05-10 14:10:29 +08:00
/* ignore return value */
readl_poll_timeout ( pbase + XSP_U2FREQ_FMMONR1 , tmp ,
( tmp & P2F_USB_FM_VALID ) , 10 , 200 ) ;
fm_out = readl ( pbase + XSP_U2FREQ_MMONR0 ) ;
/* disable frequency meter */
2021-12-18 16:28:01 +08:00
mtk_phy_clear_bits ( pbase + XSP_U2FREQ_FMCR0 , P2F_RG_FREQDET_EN ) ;
2018-05-10 14:10:29 +08:00
/* disable free run clock */
2021-12-18 16:28:01 +08:00
mtk_phy_clear_bits ( pbase + XSP_U2FREQ_FMMONR1 , P2F_RG_FRCK_EN ) ;
2018-05-10 14:10:29 +08:00
if ( fm_out ) {
/* (1024 / FM_OUT) x reference clock frequency x coefficient */
tmp = xsphy - > src_ref_clk * xsphy - > src_coef ;
tmp = ( tmp * XSP_FM_DET_CYCLE_CNT ) / fm_out ;
calib_val = DIV_ROUND_CLOSEST ( tmp , XSP_SR_COEF_DIVISOR ) ;
} else {
/* if FM detection fail, set default value */
calib_val = 3 ;
}
dev_dbg ( xsphy - > dev , " phy.%d, fm_out:%d, calib:%d (clk:%d, coef:%d) \n " ,
inst - > index , fm_out , calib_val ,
xsphy - > src_ref_clk , xsphy - > src_coef ) ;
/* set HS slew rate */
2022-09-20 17:00:23 +08:00
mtk_phy_update_field ( pbase + XSP_USBPHYACR5 , P2A5_RG_HSTX_SRCTRL , calib_val ) ;
2018-05-10 14:10:29 +08:00
/* disable USB ring oscillator */
2021-12-18 16:28:01 +08:00
mtk_phy_clear_bits ( pbase + XSP_USBPHYACR5 , P2A5_RG_HSTX_SRCAL_EN ) ;
2018-05-10 14:10:29 +08:00
}
static void u2_phy_instance_init ( struct mtk_xsphy * xsphy ,
struct xsphy_instance * inst )
{
void __iomem * pbase = inst - > port_base ;
/* DP/DM BC1.1 path Disable */
2021-12-18 16:28:01 +08:00
mtk_phy_clear_bits ( pbase + XSP_USBPHYACR6 , P2A6_RG_BC11_SW_EN ) ;
2018-05-10 14:10:29 +08:00
2021-12-18 16:28:01 +08:00
mtk_phy_set_bits ( pbase + XSP_USBPHYACR0 , P2A0_RG_INTR_EN ) ;
2018-05-10 14:10:29 +08:00
}
static void u2_phy_instance_power_on ( struct mtk_xsphy * xsphy ,
struct xsphy_instance * inst )
{
void __iomem * pbase = inst - > port_base ;
u32 index = inst - > index ;
2021-12-18 16:28:01 +08:00
mtk_phy_set_bits ( pbase + XSP_USBPHYACR6 , P2A6_RG_OTG_VBUSCMP_EN ) ;
2018-05-10 14:10:29 +08:00
2021-12-18 16:28:01 +08:00
mtk_phy_update_bits ( pbase + XSP_U2PHYDTM1 ,
P2D_RG_VBUSVALID | P2D_RG_AVALID | P2D_RG_SESSEND ,
P2D_RG_VBUSVALID | P2D_RG_AVALID ) ;
2018-05-10 14:10:29 +08:00
dev_dbg ( xsphy - > dev , " %s(%d) \n " , __func__ , index ) ;
}
static void u2_phy_instance_power_off ( struct mtk_xsphy * xsphy ,
struct xsphy_instance * inst )
{
void __iomem * pbase = inst - > port_base ;
u32 index = inst - > index ;
2021-12-18 16:28:01 +08:00
mtk_phy_clear_bits ( pbase + XSP_USBPHYACR6 , P2A6_RG_OTG_VBUSCMP_EN ) ;
2018-05-10 14:10:29 +08:00
2021-12-18 16:28:01 +08:00
mtk_phy_update_bits ( pbase + XSP_U2PHYDTM1 ,
P2D_RG_VBUSVALID | P2D_RG_AVALID | P2D_RG_SESSEND ,
P2D_RG_SESSEND ) ;
2018-05-10 14:10:29 +08:00
dev_dbg ( xsphy - > dev , " %s(%d) \n " , __func__ , index ) ;
}
static void u2_phy_instance_set_mode ( struct mtk_xsphy * xsphy ,
struct xsphy_instance * inst ,
enum phy_mode mode )
{
u32 tmp ;
tmp = readl ( inst - > port_base + XSP_U2PHYDTM1 ) ;
switch ( mode ) {
case PHY_MODE_USB_DEVICE :
tmp | = P2D_FORCE_IDDIG | P2D_RG_IDDIG ;
break ;
case PHY_MODE_USB_HOST :
tmp | = P2D_FORCE_IDDIG ;
tmp & = ~ P2D_RG_IDDIG ;
break ;
case PHY_MODE_USB_OTG :
tmp & = ~ ( P2D_FORCE_IDDIG | P2D_RG_IDDIG ) ;
break ;
default :
return ;
}
writel ( tmp , inst - > port_base + XSP_U2PHYDTM1 ) ;
}
static void phy_parse_property ( struct mtk_xsphy * xsphy ,
struct xsphy_instance * inst )
{
struct device * dev = & inst - > phy - > dev ;
switch ( inst - > type ) {
case PHY_TYPE_USB2 :
device_property_read_u32 ( dev , " mediatek,efuse-intr " ,
& inst - > efuse_intr ) ;
device_property_read_u32 ( dev , " mediatek,eye-src " ,
& inst - > eye_src ) ;
device_property_read_u32 ( dev , " mediatek,eye-vrt " ,
& inst - > eye_vrt ) ;
device_property_read_u32 ( dev , " mediatek,eye-term " ,
& inst - > eye_term ) ;
dev_dbg ( dev , " intr:%d, src:%d, vrt:%d, term:%d \n " ,
inst - > efuse_intr , inst - > eye_src ,
inst - > eye_vrt , inst - > eye_term ) ;
break ;
case PHY_TYPE_USB3 :
device_property_read_u32 ( dev , " mediatek,efuse-intr " ,
& inst - > efuse_intr ) ;
device_property_read_u32 ( dev , " mediatek,efuse-tx-imp " ,
& inst - > efuse_tx_imp ) ;
device_property_read_u32 ( dev , " mediatek,efuse-rx-imp " ,
& inst - > efuse_rx_imp ) ;
dev_dbg ( dev , " intr:%d, tx-imp:%d, rx-imp:%d \n " ,
inst - > efuse_intr , inst - > efuse_tx_imp ,
inst - > efuse_rx_imp ) ;
break ;
default :
dev_err ( xsphy - > dev , " incompatible phy type \n " ) ;
return ;
}
}
static void u2_phy_props_set ( struct mtk_xsphy * xsphy ,
struct xsphy_instance * inst )
{
void __iomem * pbase = inst - > port_base ;
2021-12-18 16:28:01 +08:00
if ( inst - > efuse_intr )
2022-09-20 17:00:23 +08:00
mtk_phy_update_field ( pbase + XSP_USBPHYACR1 , P2A1_RG_INTR_CAL ,
inst - > efuse_intr ) ;
2018-05-10 14:10:29 +08:00
2021-12-18 16:28:01 +08:00
if ( inst - > eye_src )
2022-09-20 17:00:23 +08:00
mtk_phy_update_field ( pbase + XSP_USBPHYACR5 , P2A5_RG_HSTX_SRCTRL ,
inst - > eye_src ) ;
2018-05-10 14:10:29 +08:00
2021-12-18 16:28:01 +08:00
if ( inst - > eye_vrt )
2022-09-20 17:00:23 +08:00
mtk_phy_update_field ( pbase + XSP_USBPHYACR1 , P2A1_RG_VRT_SEL ,
inst - > eye_vrt ) ;
2018-05-10 14:10:29 +08:00
2021-12-18 16:28:01 +08:00
if ( inst - > eye_term )
2022-09-20 17:00:23 +08:00
mtk_phy_update_field ( pbase + XSP_USBPHYACR1 , P2A1_RG_TERM_SEL ,
inst - > eye_term ) ;
2018-05-10 14:10:29 +08:00
}
static void u3_phy_props_set ( struct mtk_xsphy * xsphy ,
struct xsphy_instance * inst )
{
void __iomem * pbase = inst - > port_base ;
2021-12-18 16:28:01 +08:00
if ( inst - > efuse_intr )
2022-09-20 17:00:23 +08:00
mtk_phy_update_field ( xsphy - > glb_base + SSPXTP_PHYA_GLB_00 ,
RG_XTP_GLB_BIAS_INTR_CTRL , inst - > efuse_intr ) ;
2018-05-10 14:10:29 +08:00
2021-12-18 16:28:01 +08:00
if ( inst - > efuse_tx_imp )
2022-09-20 17:00:23 +08:00
mtk_phy_update_field ( pbase + SSPXTP_PHYA_LN_04 ,
RG_XTP_LN0_TX_IMPSEL , inst - > efuse_tx_imp ) ;
2018-05-10 14:10:29 +08:00
2021-12-18 16:28:01 +08:00
if ( inst - > efuse_rx_imp )
2022-09-20 17:00:23 +08:00
mtk_phy_update_field ( pbase + SSPXTP_PHYA_LN_14 ,
RG_XTP_LN0_RX_IMPSEL , inst - > efuse_rx_imp ) ;
2018-05-10 14:10:29 +08:00
}
static int mtk_phy_init ( struct phy * phy )
{
struct xsphy_instance * inst = phy_get_drvdata ( phy ) ;
struct mtk_xsphy * xsphy = dev_get_drvdata ( phy - > dev . parent ) ;
int ret ;
ret = clk_prepare_enable ( inst - > ref_clk ) ;
if ( ret ) {
dev_err ( xsphy - > dev , " failed to enable ref_clk \n " ) ;
return ret ;
}
switch ( inst - > type ) {
case PHY_TYPE_USB2 :
u2_phy_instance_init ( xsphy , inst ) ;
u2_phy_props_set ( xsphy , inst ) ;
break ;
case PHY_TYPE_USB3 :
u3_phy_props_set ( xsphy , inst ) ;
break ;
default :
dev_err ( xsphy - > dev , " incompatible phy type \n " ) ;
clk_disable_unprepare ( inst - > ref_clk ) ;
return - EINVAL ;
}
return 0 ;
}
static int mtk_phy_power_on ( struct phy * phy )
{
struct xsphy_instance * inst = phy_get_drvdata ( phy ) ;
struct mtk_xsphy * xsphy = dev_get_drvdata ( phy - > dev . parent ) ;
if ( inst - > type = = PHY_TYPE_USB2 ) {
u2_phy_instance_power_on ( xsphy , inst ) ;
u2_phy_slew_rate_calibrate ( xsphy , inst ) ;
}
return 0 ;
}
static int mtk_phy_power_off ( struct phy * phy )
{
struct xsphy_instance * inst = phy_get_drvdata ( phy ) ;
struct mtk_xsphy * xsphy = dev_get_drvdata ( phy - > dev . parent ) ;
if ( inst - > type = = PHY_TYPE_USB2 )
u2_phy_instance_power_off ( xsphy , inst ) ;
return 0 ;
}
static int mtk_phy_exit ( struct phy * phy )
{
struct xsphy_instance * inst = phy_get_drvdata ( phy ) ;
clk_disable_unprepare ( inst - > ref_clk ) ;
return 0 ;
}
2018-11-19 19:24:20 -06:00
static int mtk_phy_set_mode ( struct phy * phy , enum phy_mode mode , int submode )
2018-05-10 14:10:29 +08:00
{
struct xsphy_instance * inst = phy_get_drvdata ( phy ) ;
struct mtk_xsphy * xsphy = dev_get_drvdata ( phy - > dev . parent ) ;
if ( inst - > type = = PHY_TYPE_USB2 )
u2_phy_instance_set_mode ( xsphy , inst , mode ) ;
return 0 ;
}
static struct phy * mtk_phy_xlate ( struct device * dev ,
struct of_phandle_args * args )
{
struct mtk_xsphy * xsphy = dev_get_drvdata ( dev ) ;
struct xsphy_instance * inst = NULL ;
struct device_node * phy_np = args - > np ;
int index ;
if ( args - > args_count ! = 1 ) {
dev_err ( dev , " invalid number of cells in 'phy' property \n " ) ;
return ERR_PTR ( - EINVAL ) ;
}
for ( index = 0 ; index < xsphy - > nphys ; index + + )
if ( phy_np = = xsphy - > phys [ index ] - > phy - > dev . of_node ) {
inst = xsphy - > phys [ index ] ;
break ;
}
if ( ! inst ) {
dev_err ( dev , " failed to find appropriate phy \n " ) ;
return ERR_PTR ( - EINVAL ) ;
}
inst - > type = args - > args [ 0 ] ;
if ( ! ( inst - > type = = PHY_TYPE_USB2 | |
inst - > type = = PHY_TYPE_USB3 ) ) {
dev_err ( dev , " unsupported phy type: %d \n " , inst - > type ) ;
return ERR_PTR ( - EINVAL ) ;
}
phy_parse_property ( xsphy , inst ) ;
return inst - > phy ;
}
static const struct phy_ops mtk_xsphy_ops = {
. init = mtk_phy_init ,
. exit = mtk_phy_exit ,
. power_on = mtk_phy_power_on ,
. power_off = mtk_phy_power_off ,
. set_mode = mtk_phy_set_mode ,
. owner = THIS_MODULE ,
} ;
static const struct of_device_id mtk_xsphy_id_table [ ] = {
{ . compatible = " mediatek,xsphy " , } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , mtk_xsphy_id_table ) ;
static int mtk_xsphy_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct device_node * np = dev - > of_node ;
struct device_node * child_np ;
struct phy_provider * provider ;
struct resource * glb_res ;
struct mtk_xsphy * xsphy ;
struct resource res ;
int port , retval ;
xsphy = devm_kzalloc ( dev , sizeof ( * xsphy ) , GFP_KERNEL ) ;
if ( ! xsphy )
return - ENOMEM ;
xsphy - > nphys = of_get_child_count ( np ) ;
xsphy - > phys = devm_kcalloc ( dev , xsphy - > nphys ,
sizeof ( * xsphy - > phys ) , GFP_KERNEL ) ;
if ( ! xsphy - > phys )
return - ENOMEM ;
xsphy - > dev = dev ;
platform_set_drvdata ( pdev , xsphy ) ;
glb_res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
/* optional, may not exist if no u3 phys */
if ( glb_res ) {
/* get banks shared by multiple u3 phys */
xsphy - > glb_base = devm_ioremap_resource ( dev , glb_res ) ;
if ( IS_ERR ( xsphy - > glb_base ) ) {
dev_err ( dev , " failed to remap glb regs \n " ) ;
return PTR_ERR ( xsphy - > glb_base ) ;
}
}
xsphy - > src_ref_clk = XSP_REF_CLK ;
xsphy - > src_coef = XSP_SLEW_RATE_COEF ;
/* update parameters of slew rate calibrate if exist */
device_property_read_u32 ( dev , " mediatek,src-ref-clk-mhz " ,
& xsphy - > src_ref_clk ) ;
device_property_read_u32 ( dev , " mediatek,src-coef " , & xsphy - > src_coef ) ;
port = 0 ;
for_each_child_of_node ( np , child_np ) {
struct xsphy_instance * inst ;
struct phy * phy ;
inst = devm_kzalloc ( dev , sizeof ( * inst ) , GFP_KERNEL ) ;
if ( ! inst ) {
retval = - ENOMEM ;
goto put_child ;
}
xsphy - > phys [ port ] = inst ;
phy = devm_phy_create ( dev , child_np , & mtk_xsphy_ops ) ;
if ( IS_ERR ( phy ) ) {
dev_err ( dev , " failed to create phy \n " ) ;
retval = PTR_ERR ( phy ) ;
goto put_child ;
}
retval = of_address_to_resource ( child_np , 0 , & res ) ;
if ( retval ) {
dev_err ( dev , " failed to get address resource(id-%d) \n " ,
port ) ;
goto put_child ;
}
inst - > port_base = devm_ioremap_resource ( & phy - > dev , & res ) ;
if ( IS_ERR ( inst - > port_base ) ) {
dev_err ( dev , " failed to remap phy regs \n " ) ;
retval = PTR_ERR ( inst - > port_base ) ;
goto put_child ;
}
inst - > phy = phy ;
inst - > index = port ;
phy_set_drvdata ( phy , inst ) ;
port + + ;
inst - > ref_clk = devm_clk_get ( & phy - > dev , " ref " ) ;
if ( IS_ERR ( inst - > ref_clk ) ) {
dev_err ( dev , " failed to get ref_clk(id-%d) \n " , port ) ;
retval = PTR_ERR ( inst - > ref_clk ) ;
goto put_child ;
}
}
provider = devm_of_phy_provider_register ( dev , mtk_phy_xlate ) ;
return PTR_ERR_OR_ZERO ( provider ) ;
put_child :
of_node_put ( child_np ) ;
return retval ;
}
static struct platform_driver mtk_xsphy_driver = {
. probe = mtk_xsphy_probe ,
. driver = {
. name = " mtk-xsphy " ,
. of_match_table = mtk_xsphy_id_table ,
} ,
} ;
module_platform_driver ( mtk_xsphy_driver ) ;
MODULE_AUTHOR ( " Chunfeng Yun <chunfeng.yun@mediatek.com> " ) ;
MODULE_DESCRIPTION ( " MediaTek USB XS-PHY driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;