2022-07-25 10:44:11 +08:00
// SPDX-License-Identifier: GPL-2.0
/*
* Sunplus SP7021 USB 2.0 phy driver
*
* Copyright ( C ) 2022 Sunplus Technology Inc . , All rights reserved .
*
* Note 1 : non - posted write command for the registers accesses of
* Sunplus SP7021 .
*
*/
# include <linux/bitfield.h>
# include <linux/clk.h>
# include <linux/delay.h>
# include <linux/io.h>
# include <linux/module.h>
# include <linux/nvmem-consumer.h>
# include <linux/of_platform.h>
# include <linux/phy/phy.h>
# include <linux/platform_device.h>
# include <linux/reset.h>
# define HIGH_MASK_BITS GENMASK(31, 16)
# define LOW_MASK_BITS GENMASK(15, 0)
# define OTP_DISC_LEVEL_DEFAULT 0xd
/* GROUP UPHY */
# define CONFIG1 0x4
# define J_HS_TX_PWRSAV BIT(5)
# define CONFIG3 0xc
# define J_FORCE_DISC_ON BIT(5)
# define J_DEBUG_CTRL_ADDR_MACRO BIT(0)
# define CONFIG7 0x1c
# define J_DISC 0X1f
# define CONFIG9 0x24
# define J_ECO_PATH BIT(6)
# define CONFIG16 0x40
# define J_TBCWAIT_MASK GENMASK(6, 5)
# define J_TBCWAIT_1P1_MS FIELD_PREP(J_TBCWAIT_MASK, 0)
# define J_TVDM_SRC_DIS_MASK GENMASK(4, 3)
# define J_TVDM_SRC_DIS_8P2_MS FIELD_PREP(J_TVDM_SRC_DIS_MASK, 3)
# define J_TVDM_SRC_EN_MASK GENMASK(2, 1)
# define J_TVDM_SRC_EN_1P6_MS FIELD_PREP(J_TVDM_SRC_EN_MASK, 0)
# define J_BC_EN BIT(0)
# define CONFIG17 0x44
# define IBG_TRIM0_MASK GENMASK(7, 5)
# define IBG_TRIM0_SSLVHT FIELD_PREP(IBG_TRIM0_MASK, 4)
# define J_VDATREE_TRIM_MASK GENMASK(4, 1)
# define J_VDATREE_TRIM_DEFAULT FIELD_PREP(J_VDATREE_TRIM_MASK, 9)
# define CONFIG23 0x5c
# define PROB_MASK GENMASK(5, 3)
# define PROB FIELD_PREP(PROB_MASK, 7)
/* GROUP MOON4 */
# define UPHY_CONTROL0 0x0
# define UPHY_CONTROL1 0x4
# define UPHY_CONTROL2 0x8
# define MO1_UPHY_RX_CLK_SEL BIT(6)
# define MASK_MO1_UPHY_RX_CLK_SEL BIT(6 + 16)
# define UPHY_CONTROL3 0xc
# define MO1_UPHY_PLL_POWER_OFF_SEL BIT(7)
# define MASK_MO1_UPHY_PLL_POWER_OFF_SEL BIT(7 + 16)
# define MO1_UPHY_PLL_POWER_OFF BIT(3)
# define MASK_UPHY_PLL_POWER_OFF BIT(3 + 16)
struct sp_usbphy {
struct device * dev ;
struct resource * phy_res_mem ;
struct resource * moon4_res_mem ;
struct reset_control * rstc ;
struct clk * phy_clk ;
void __iomem * phy_regs ;
void __iomem * moon4_regs ;
u32 disc_vol_addr_off ;
} ;
static int update_disc_vol ( struct sp_usbphy * usbphy )
{
struct nvmem_cell * cell ;
char * disc_name = " disc_vol " ;
ssize_t otp_l = 0 ;
char * otp_v ;
u32 val , set ;
cell = nvmem_cell_get ( usbphy - > dev , disc_name ) ;
if ( IS_ERR_OR_NULL ( cell ) ) {
if ( PTR_ERR ( cell ) = = - EPROBE_DEFER )
return - EPROBE_DEFER ;
}
otp_v = nvmem_cell_read ( cell , & otp_l ) ;
nvmem_cell_put ( cell ) ;
2022-09-09 09:47:09 +00:00
if ( ! IS_ERR ( otp_v ) ) {
2022-07-25 10:44:11 +08:00
set = * ( otp_v + 1 ) ;
set = ( set < < ( sizeof ( char ) * 8 ) ) | * otp_v ;
set = ( set > > usbphy - > disc_vol_addr_off ) & J_DISC ;
}
2022-09-09 09:47:09 +00:00
if ( IS_ERR ( otp_v ) | | set = = 0 )
2022-07-25 10:44:11 +08:00
set = OTP_DISC_LEVEL_DEFAULT ;
val = readl ( usbphy - > phy_regs + CONFIG7 ) ;
val = ( val & ~ J_DISC ) | set ;
writel ( val , usbphy - > phy_regs + CONFIG7 ) ;
return 0 ;
}
static int sp_uphy_init ( struct phy * phy )
{
struct sp_usbphy * usbphy = phy_get_drvdata ( phy ) ;
u32 val ;
int ret ;
ret = clk_prepare_enable ( usbphy - > phy_clk ) ;
if ( ret )
goto err_clk ;
ret = reset_control_deassert ( usbphy - > rstc ) ;
if ( ret )
goto err_reset ;
/* Default value modification */
writel ( HIGH_MASK_BITS | 0x4002 , usbphy - > moon4_regs + UPHY_CONTROL0 ) ;
writel ( HIGH_MASK_BITS | 0x8747 , usbphy - > moon4_regs + UPHY_CONTROL1 ) ;
/* disconnect voltage */
ret = update_disc_vol ( usbphy ) ;
if ( ret < 0 )
return ret ;
/* board uphy 0 internal register modification for tid certification */
val = readl ( usbphy - > phy_regs + CONFIG9 ) ;
val & = ~ ( J_ECO_PATH ) ;
writel ( val , usbphy - > phy_regs + CONFIG9 ) ;
val = readl ( usbphy - > phy_regs + CONFIG1 ) ;
val & = ~ ( J_HS_TX_PWRSAV ) ;
writel ( val , usbphy - > phy_regs + CONFIG1 ) ;
val = readl ( usbphy - > phy_regs + CONFIG23 ) ;
val = ( val & ~ PROB ) | PROB ;
writel ( val , usbphy - > phy_regs + CONFIG23 ) ;
/* port 0 uphy clk fix */
writel ( MASK_MO1_UPHY_RX_CLK_SEL | MO1_UPHY_RX_CLK_SEL ,
usbphy - > moon4_regs + UPHY_CONTROL2 ) ;
/* battery charger */
writel ( J_TBCWAIT_1P1_MS | J_TVDM_SRC_DIS_8P2_MS | J_TVDM_SRC_EN_1P6_MS | J_BC_EN ,
usbphy - > phy_regs + CONFIG16 ) ;
writel ( IBG_TRIM0_SSLVHT | J_VDATREE_TRIM_DEFAULT , usbphy - > phy_regs + CONFIG17 ) ;
/* chirp mode */
writel ( J_FORCE_DISC_ON | J_DEBUG_CTRL_ADDR_MACRO , usbphy - > phy_regs + CONFIG3 ) ;
return 0 ;
err_reset :
reset_control_assert ( usbphy - > rstc ) ;
err_clk :
clk_disable_unprepare ( usbphy - > phy_clk ) ;
return ret ;
}
static int sp_uphy_power_on ( struct phy * phy )
{
struct sp_usbphy * usbphy = phy_get_drvdata ( phy ) ;
u32 pll_pwr_on , pll_pwr_off ;
/* PLL power off/on twice */
pll_pwr_off = ( readl ( usbphy - > moon4_regs + UPHY_CONTROL3 ) & ~ LOW_MASK_BITS )
| MO1_UPHY_PLL_POWER_OFF_SEL | MO1_UPHY_PLL_POWER_OFF ;
pll_pwr_on = ( readl ( usbphy - > moon4_regs + UPHY_CONTROL3 ) & ~ LOW_MASK_BITS )
| MO1_UPHY_PLL_POWER_OFF_SEL ;
writel ( MASK_MO1_UPHY_PLL_POWER_OFF_SEL | MASK_UPHY_PLL_POWER_OFF | pll_pwr_off ,
usbphy - > moon4_regs + UPHY_CONTROL3 ) ;
mdelay ( 1 ) ;
writel ( MASK_MO1_UPHY_PLL_POWER_OFF_SEL | MASK_UPHY_PLL_POWER_OFF | pll_pwr_on ,
usbphy - > moon4_regs + UPHY_CONTROL3 ) ;
mdelay ( 1 ) ;
writel ( MASK_MO1_UPHY_PLL_POWER_OFF_SEL | MASK_UPHY_PLL_POWER_OFF | pll_pwr_off ,
usbphy - > moon4_regs + UPHY_CONTROL3 ) ;
mdelay ( 1 ) ;
writel ( MASK_MO1_UPHY_PLL_POWER_OFF_SEL | MASK_UPHY_PLL_POWER_OFF | pll_pwr_on ,
usbphy - > moon4_regs + UPHY_CONTROL3 ) ;
mdelay ( 1 ) ;
writel ( MASK_MO1_UPHY_PLL_POWER_OFF_SEL | MASK_UPHY_PLL_POWER_OFF | 0x0 ,
usbphy - > moon4_regs + UPHY_CONTROL3 ) ;
return 0 ;
}
static int sp_uphy_power_off ( struct phy * phy )
{
struct sp_usbphy * usbphy = phy_get_drvdata ( phy ) ;
u32 pll_pwr_off ;
pll_pwr_off = ( readl ( usbphy - > moon4_regs + UPHY_CONTROL3 ) & ~ LOW_MASK_BITS )
| MO1_UPHY_PLL_POWER_OFF_SEL | MO1_UPHY_PLL_POWER_OFF ;
writel ( MASK_MO1_UPHY_PLL_POWER_OFF_SEL | MASK_UPHY_PLL_POWER_OFF | pll_pwr_off ,
usbphy - > moon4_regs + UPHY_CONTROL3 ) ;
mdelay ( 1 ) ;
writel ( MASK_MO1_UPHY_PLL_POWER_OFF_SEL | MASK_UPHY_PLL_POWER_OFF | 0x0 ,
usbphy - > moon4_regs + UPHY_CONTROL3 ) ;
return 0 ;
}
static int sp_uphy_exit ( struct phy * phy )
{
struct sp_usbphy * usbphy = phy_get_drvdata ( phy ) ;
reset_control_assert ( usbphy - > rstc ) ;
clk_disable_unprepare ( usbphy - > phy_clk ) ;
return 0 ;
}
static const struct phy_ops sp_uphy_ops = {
. init = sp_uphy_init ,
. power_on = sp_uphy_power_on ,
. power_off = sp_uphy_power_off ,
. exit = sp_uphy_exit ,
} ;
static const struct of_device_id sp_uphy_dt_ids [ ] = {
{ . compatible = " sunplus,sp7021-usb2-phy " , } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , sp_uphy_dt_ids ) ;
static int sp_usb_phy_probe ( struct platform_device * pdev )
{
struct sp_usbphy * usbphy ;
struct phy_provider * phy_provider ;
struct phy * phy ;
int ret ;
usbphy = devm_kzalloc ( & pdev - > dev , sizeof ( * usbphy ) , GFP_KERNEL ) ;
if ( ! usbphy )
return - ENOMEM ;
usbphy - > dev = & pdev - > dev ;
usbphy - > phy_res_mem = platform_get_resource_byname ( pdev , IORESOURCE_MEM , " phy " ) ;
usbphy - > phy_regs = devm_ioremap_resource ( & pdev - > dev , usbphy - > phy_res_mem ) ;
if ( IS_ERR ( usbphy - > phy_regs ) )
return PTR_ERR ( usbphy - > phy_regs ) ;
usbphy - > moon4_res_mem = platform_get_resource_byname ( pdev , IORESOURCE_MEM , " moon4 " ) ;
usbphy - > moon4_regs = devm_ioremap ( & pdev - > dev , usbphy - > moon4_res_mem - > start ,
resource_size ( usbphy - > moon4_res_mem ) ) ;
2022-09-11 06:00:53 +00:00
if ( ! usbphy - > moon4_regs )
return - ENOMEM ;
2022-07-25 10:44:11 +08:00
usbphy - > phy_clk = devm_clk_get ( & pdev - > dev , NULL ) ;
if ( IS_ERR ( usbphy - > phy_clk ) )
return PTR_ERR ( usbphy - > phy_clk ) ;
usbphy - > rstc = devm_reset_control_get_exclusive ( & pdev - > dev , NULL ) ;
if ( IS_ERR ( usbphy - > rstc ) )
return PTR_ERR ( usbphy - > rstc ) ;
of_property_read_u32 ( pdev - > dev . of_node , " sunplus,disc-vol-addr-off " ,
& usbphy - > disc_vol_addr_off ) ;
phy = devm_phy_create ( & pdev - > dev , NULL , & sp_uphy_ops ) ;
if ( IS_ERR ( phy ) ) {
ret = - PTR_ERR ( phy ) ;
return ret ;
}
phy_set_drvdata ( phy , usbphy ) ;
phy_provider = devm_of_phy_provider_register ( & pdev - > dev , of_phy_simple_xlate ) ;
return PTR_ERR_OR_ZERO ( phy_provider ) ;
}
static struct platform_driver sunplus_usb_phy_driver = {
. probe = sp_usb_phy_probe ,
. driver = {
. name = " sunplus-usb2-phy " ,
. of_match_table = sp_uphy_dt_ids ,
} ,
} ;
module_platform_driver ( sunplus_usb_phy_driver ) ;
MODULE_AUTHOR ( " Vincent Shih <vincent.shih@sunplus.com> " ) ;
MODULE_DESCRIPTION ( " Sunplus USB 2.0 phy driver " ) ;
MODULE_LICENSE ( " GPL " ) ;