2017-11-03 11:28:30 +01:00
// SPDX-License-Identifier: GPL-2.0
2016-10-19 10:28:25 +08:00
/*
* mtu3_dr . c - dual role switch and host glue layer
*
* Copyright ( C ) 2016 MediaTek Inc .
*
* Author : Chunfeng Yun < chunfeng . yun @ mediatek . com >
*/
# include <linux/clk.h>
# include <linux/iopoll.h>
# include <linux/irq.h>
# include <linux/kernel.h>
# include <linux/mfd/syscon.h>
# include <linux/of_device.h>
# include <linux/regmap.h>
# include "mtu3.h"
# include "mtu3_dr.h"
2018-01-03 16:53:18 +08:00
/* mt8173 etc */
# define PERI_WK_CTRL1 0x4
# define WC1_IS_C(x) (((x) & 0xf) << 26) /* cycle debounce */
# define WC1_IS_EN BIT(25)
# define WC1_IS_P BIT(6) /* polarity for ip sleep */
/* mt2712 etc */
# define PERI_SSUSB_SPM_CTRL 0x0
# define SSC_IP_SLEEP_EN BIT(4)
# define SSC_SPM_INT_EN BIT(1)
enum ssusb_uwk_vers {
SSUSB_UWK_V1 = 1 ,
SSUSB_UWK_V2 ,
} ;
2016-10-19 10:28:25 +08:00
/*
* ip - sleep wakeup mode :
* all clocks can be turn off , but power domain should be kept on
*/
2018-01-03 16:53:18 +08:00
static void ssusb_wakeup_ip_sleep_set ( struct ssusb_mtk * ssusb , bool enable )
2016-10-19 10:28:25 +08:00
{
2018-01-03 16:53:18 +08:00
u32 reg , msk , val ;
switch ( ssusb - > uwk_vers ) {
case SSUSB_UWK_V1 :
reg = ssusb - > uwk_reg_base + PERI_WK_CTRL1 ;
msk = WC1_IS_EN | WC1_IS_C ( 0xf ) | WC1_IS_P ;
val = enable ? ( WC1_IS_EN | WC1_IS_C ( 0x8 ) ) : 0 ;
break ;
case SSUSB_UWK_V2 :
reg = ssusb - > uwk_reg_base + PERI_SSUSB_SPM_CTRL ;
msk = SSC_IP_SLEEP_EN | SSC_SPM_INT_EN ;
val = enable ? msk : 0 ;
break ;
default :
return ;
2018-01-13 01:18:53 +08:00
}
2018-01-03 16:53:18 +08:00
regmap_update_bits ( ssusb - > uwk , reg , msk , val ) ;
2016-10-19 10:28:25 +08:00
}
int ssusb_wakeup_of_property_parse ( struct ssusb_mtk * ssusb ,
struct device_node * dn )
{
2018-01-03 16:53:18 +08:00
struct of_phandle_args args ;
int ret ;
2016-10-19 10:28:25 +08:00
2018-01-03 16:53:18 +08:00
/* wakeup function is optional */
ssusb - > uwk_en = of_property_read_bool ( dn , " wakeup-source " ) ;
if ( ! ssusb - > uwk_en )
2016-10-19 10:28:25 +08:00
return 0 ;
2018-01-03 16:53:18 +08:00
ret = of_parse_phandle_with_fixed_args ( dn ,
" mediatek,syscon-wakeup " , 2 , 0 , & args ) ;
if ( ret )
return ret ;
2016-10-19 10:28:25 +08:00
2018-01-03 16:53:18 +08:00
ssusb - > uwk_reg_base = args . args [ 0 ] ;
ssusb - > uwk_vers = args . args [ 1 ] ;
ssusb - > uwk = syscon_node_to_regmap ( args . np ) ;
of_node_put ( args . np ) ;
dev_info ( ssusb - > dev , " uwk - reg:0x%x, version:%d \n " ,
ssusb - > uwk_reg_base , ssusb - > uwk_vers ) ;
return PTR_ERR_OR_ZERO ( ssusb - > uwk ) ;
}
void ssusb_wakeup_set ( struct ssusb_mtk * ssusb , bool enable )
{
if ( ssusb - > uwk_en )
ssusb_wakeup_ip_sleep_set ( ssusb , enable ) ;
2016-10-19 10:28:25 +08:00
}
static void host_ports_num_get ( struct ssusb_mtk * ssusb )
{
u32 xhci_cap ;
xhci_cap = mtu3_readl ( ssusb - > ippc_base , U3D_SSUSB_IP_XHCI_CAP ) ;
ssusb - > u2_ports = SSUSB_IP_XHCI_U2_PORT_NUM ( xhci_cap ) ;
ssusb - > u3_ports = SSUSB_IP_XHCI_U3_PORT_NUM ( xhci_cap ) ;
dev_dbg ( ssusb - > dev , " host - u2_ports:%d, u3_ports:%d \n " ,
ssusb - > u2_ports , ssusb - > u3_ports ) ;
}
/* only configure ports will be used later */
int ssusb_host_enable ( struct ssusb_mtk * ssusb )
{
void __iomem * ibase = ssusb - > ippc_base ;
int num_u3p = ssusb - > u3_ports ;
int num_u2p = ssusb - > u2_ports ;
2017-10-13 17:10:38 +08:00
int u3_ports_disabed ;
2016-10-19 10:28:25 +08:00
u32 check_clk ;
u32 value ;
int i ;
/* power on host ip */
mtu3_clrbits ( ibase , U3D_SSUSB_IP_PW_CTRL1 , SSUSB_IP_HOST_PDN ) ;
2017-10-13 17:10:38 +08:00
/* power on and enable u3 ports except skipped ones */
u3_ports_disabed = 0 ;
2016-10-19 10:28:25 +08:00
for ( i = 0 ; i < num_u3p ; i + + ) {
2017-10-13 17:10:38 +08:00
if ( ( 0x1 < < i ) & ssusb - > u3p_dis_msk ) {
u3_ports_disabed + + ;
continue ;
}
2016-10-19 10:28:25 +08:00
value = mtu3_readl ( ibase , SSUSB_U3_CTRL ( i ) ) ;
value & = ~ ( SSUSB_U3_PORT_PDN | SSUSB_U3_PORT_DIS ) ;
value | = SSUSB_U3_PORT_HOST_SEL ;
mtu3_writel ( ibase , SSUSB_U3_CTRL ( i ) , value ) ;
}
/* power on and enable all u2 ports */
for ( i = 0 ; i < num_u2p ; i + + ) {
value = mtu3_readl ( ibase , SSUSB_U2_CTRL ( i ) ) ;
value & = ~ ( SSUSB_U2_PORT_PDN | SSUSB_U2_PORT_DIS ) ;
value | = SSUSB_U2_PORT_HOST_SEL ;
mtu3_writel ( ibase , SSUSB_U2_CTRL ( i ) , value ) ;
}
check_clk = SSUSB_XHCI_RST_B_STS ;
2017-10-13 17:10:38 +08:00
if ( num_u3p > u3_ports_disabed )
2016-10-19 10:28:25 +08:00
check_clk = SSUSB_U3_MAC_RST_B_STS ;
return ssusb_check_clocks ( ssusb , check_clk ) ;
}
int ssusb_host_disable ( struct ssusb_mtk * ssusb , bool suspend )
{
void __iomem * ibase = ssusb - > ippc_base ;
int num_u3p = ssusb - > u3_ports ;
int num_u2p = ssusb - > u2_ports ;
u32 value ;
int ret ;
int i ;
2017-10-13 17:10:38 +08:00
/* power down and disable u3 ports except skipped ones */
2016-10-19 10:28:25 +08:00
for ( i = 0 ; i < num_u3p ; i + + ) {
2017-10-13 17:10:38 +08:00
if ( ( 0x1 < < i ) & ssusb - > u3p_dis_msk )
continue ;
2016-10-19 10:28:25 +08:00
value = mtu3_readl ( ibase , SSUSB_U3_CTRL ( i ) ) ;
value | = SSUSB_U3_PORT_PDN ;
value | = suspend ? 0 : SSUSB_U3_PORT_DIS ;
mtu3_writel ( ibase , SSUSB_U3_CTRL ( i ) , value ) ;
}
/* power down and disable all u2 ports */
for ( i = 0 ; i < num_u2p ; i + + ) {
value = mtu3_readl ( ibase , SSUSB_U2_CTRL ( i ) ) ;
value | = SSUSB_U2_PORT_PDN ;
value | = suspend ? 0 : SSUSB_U2_PORT_DIS ;
mtu3_writel ( ibase , SSUSB_U2_CTRL ( i ) , value ) ;
}
/* power down host ip */
mtu3_setbits ( ibase , U3D_SSUSB_IP_PW_CTRL1 , SSUSB_IP_HOST_PDN ) ;
if ( ! suspend )
return 0 ;
/* wait for host ip to sleep */
ret = readl_poll_timeout ( ibase + U3D_SSUSB_IP_PW_STS1 , value ,
( value & SSUSB_IP_SLEEP_STS ) , 100 , 100000 ) ;
if ( ret )
dev_err ( ssusb - > dev , " ip sleep failed!!! \n " ) ;
return ret ;
}
static void ssusb_host_setup ( struct ssusb_mtk * ssusb )
{
2017-10-13 17:10:42 +08:00
struct otg_switch_mtk * otg_sx = & ssusb - > otg_switch ;
2016-10-19 10:28:25 +08:00
host_ports_num_get ( ssusb ) ;
/*
* power on host and power on / enable all ports
* if support OTG , gadget driver will switch port0 to device mode
*/
ssusb_host_enable ( ssusb ) ;
2016-10-19 10:28:26 +08:00
2017-10-13 17:10:42 +08:00
if ( otg_sx - > manual_drd_enabled )
ssusb_set_force_mode ( ssusb , MTU3_DR_FORCE_HOST ) ;
2016-10-19 10:28:26 +08:00
/* if port0 supports dual-role, works as host mode by default */
ssusb_set_vbus ( & ssusb - > otg_switch , 1 ) ;
2016-10-19 10:28:25 +08:00
}
static void ssusb_host_cleanup ( struct ssusb_mtk * ssusb )
{
2016-10-19 10:28:26 +08:00
if ( ssusb - > is_host )
ssusb_set_vbus ( & ssusb - > otg_switch , 0 ) ;
2016-10-19 10:28:25 +08:00
ssusb_host_disable ( ssusb , false ) ;
}
/*
* If host supports multiple ports , the VBUSes ( 5 V ) of ports except port0
* which supports OTG are better to be enabled by default in DTS .
* Because the host driver will keep link with devices attached when system
* enters suspend mode , so no need to control VBUSes after initialization .
*/
int ssusb_host_init ( struct ssusb_mtk * ssusb , struct device_node * parent_dn )
{
struct device * parent_dev = ssusb - > dev ;
int ret ;
ssusb_host_setup ( ssusb ) ;
ret = of_platform_populate ( parent_dn , NULL , NULL , parent_dev ) ;
if ( ret ) {
2017-07-18 16:43:35 -05:00
dev_dbg ( parent_dev , " failed to create child devices at %pOF \n " ,
parent_dn ) ;
2016-10-19 10:28:25 +08:00
return ret ;
}
dev_info ( parent_dev , " xHCI platform device register success... \n " ) ;
return 0 ;
}
void ssusb_host_exit ( struct ssusb_mtk * ssusb )
{
of_platform_depopulate ( ssusb - > dev ) ;
ssusb_host_cleanup ( ssusb ) ;
}