2020-09-09 11:35:10 +02:00
// SPDX-License-Identifier: GPL-2.0
/*
* drd . c - DesignWare USB2 DRD Controller Dual - role support
*
* Copyright ( C ) 2020 STMicroelectronics
*
* Author ( s ) : Amelie Delaunay < amelie . delaunay @ st . com >
*/
2021-10-05 11:53:04 +02:00
# include <linux/clk.h>
2020-09-09 11:35:10 +02:00
# include <linux/iopoll.h>
# include <linux/platform_device.h>
# include <linux/usb/role.h>
# include "core.h"
2021-12-06 16:56:43 +01:00
# define dwc2_ovr_gotgctl(gotgctl) \
( ( gotgctl ) | = GOTGCTL_BVALOEN | GOTGCTL_AVALOEN | GOTGCTL_VBVALOEN | \
GOTGCTL_DBNCE_FLTR_BYPASS )
2020-09-09 11:35:10 +02:00
static void dwc2_ovr_init ( struct dwc2_hsotg * hsotg )
{
unsigned long flags ;
u32 gotgctl ;
spin_lock_irqsave ( & hsotg - > lock , flags ) ;
gotgctl = dwc2_readl ( hsotg , GOTGCTL ) ;
2021-12-06 16:56:43 +01:00
dwc2_ovr_gotgctl ( gotgctl ) ;
2020-09-09 11:35:10 +02:00
gotgctl & = ~ ( GOTGCTL_BVALOVAL | GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL ) ;
2021-12-06 16:56:42 +01:00
if ( hsotg - > role_sw_default_mode = = USB_DR_MODE_HOST )
gotgctl | = GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL ;
else if ( hsotg - > role_sw_default_mode = = USB_DR_MODE_PERIPHERAL )
gotgctl | = GOTGCTL_BVALOVAL | GOTGCTL_VBVALOVAL ;
2020-09-09 11:35:10 +02:00
dwc2_writel ( hsotg , gotgctl , GOTGCTL ) ;
spin_unlock_irqrestore ( & hsotg - > lock , flags ) ;
2021-10-05 11:53:03 +02:00
dwc2_force_mode ( hsotg , ( hsotg - > dr_mode = = USB_DR_MODE_HOST ) ) ;
2020-09-09 11:35:10 +02:00
}
static int dwc2_ovr_avalid ( struct dwc2_hsotg * hsotg , bool valid )
{
u32 gotgctl = dwc2_readl ( hsotg , GOTGCTL ) ;
/* Check if A-Session is already in the right state */
if ( ( valid & & ( gotgctl & GOTGCTL_ASESVLD ) ) | |
( ! valid & & ! ( gotgctl & GOTGCTL_ASESVLD ) ) )
return - EALREADY ;
2021-12-06 16:56:43 +01:00
/* Always enable overrides to handle the resume case */
dwc2_ovr_gotgctl ( gotgctl ) ;
2021-10-05 11:53:05 +02:00
gotgctl & = ~ GOTGCTL_BVALOVAL ;
2020-09-09 11:35:10 +02:00
if ( valid )
gotgctl | = GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL ;
else
gotgctl & = ~ ( GOTGCTL_AVALOVAL | GOTGCTL_VBVALOVAL ) ;
dwc2_writel ( hsotg , gotgctl , GOTGCTL ) ;
return 0 ;
}
static int dwc2_ovr_bvalid ( struct dwc2_hsotg * hsotg , bool valid )
{
u32 gotgctl = dwc2_readl ( hsotg , GOTGCTL ) ;
/* Check if B-Session is already in the right state */
if ( ( valid & & ( gotgctl & GOTGCTL_BSESVLD ) ) | |
( ! valid & & ! ( gotgctl & GOTGCTL_BSESVLD ) ) )
return - EALREADY ;
2021-12-06 16:56:43 +01:00
/* Always enable overrides to handle the resume case */
dwc2_ovr_gotgctl ( gotgctl ) ;
2021-10-05 11:53:05 +02:00
gotgctl & = ~ GOTGCTL_AVALOVAL ;
2020-09-09 11:35:10 +02:00
if ( valid )
gotgctl | = GOTGCTL_BVALOVAL | GOTGCTL_VBVALOVAL ;
else
gotgctl & = ~ ( GOTGCTL_BVALOVAL | GOTGCTL_VBVALOVAL ) ;
dwc2_writel ( hsotg , gotgctl , GOTGCTL ) ;
return 0 ;
}
static int dwc2_drd_role_sw_set ( struct usb_role_switch * sw , enum usb_role role )
{
struct dwc2_hsotg * hsotg = usb_role_switch_get_drvdata ( sw ) ;
unsigned long flags ;
int already = 0 ;
/* Skip session not in line with dr_mode */
if ( ( role = = USB_ROLE_DEVICE & & hsotg - > dr_mode = = USB_DR_MODE_HOST ) | |
( role = = USB_ROLE_HOST & & hsotg - > dr_mode = = USB_DR_MODE_PERIPHERAL ) )
return - EINVAL ;
# if IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) || \
IS_ENABLED ( CONFIG_USB_DWC2_DUAL_ROLE )
/* Skip session if core is in test mode */
if ( role = = USB_ROLE_NONE & & hsotg - > test_mode ) {
dev_dbg ( hsotg - > dev , " Core is in test mode \n " ) ;
return - EBUSY ;
}
# endif
2021-10-05 11:53:04 +02:00
/*
* In case of USB_DR_MODE_PERIPHERAL , clock is disabled at the end of
* the probe and enabled on udc_start .
* If role - switch set is called before the udc_start , we need to enable
* the clock to read / write GOTGCTL and GUSBCFG registers to override
* mode and sessions . It is the case if cable is plugged at boot .
*/
if ( ! hsotg - > ll_hw_enabled & & hsotg - > clk ) {
int ret = clk_prepare_enable ( hsotg - > clk ) ;
if ( ret )
return ret ;
}
2020-09-09 11:35:10 +02:00
spin_lock_irqsave ( & hsotg - > lock , flags ) ;
2021-12-06 16:56:42 +01:00
if ( role = = USB_ROLE_NONE ) {
/* default operation mode when usb role is USB_ROLE_NONE */
if ( hsotg - > role_sw_default_mode = = USB_DR_MODE_HOST )
role = USB_ROLE_HOST ;
else if ( hsotg - > role_sw_default_mode = = USB_DR_MODE_PERIPHERAL )
role = USB_ROLE_DEVICE ;
}
2020-09-09 11:35:10 +02:00
if ( role = = USB_ROLE_HOST ) {
already = dwc2_ovr_avalid ( hsotg , true ) ;
} else if ( role = = USB_ROLE_DEVICE ) {
already = dwc2_ovr_bvalid ( hsotg , true ) ;
/* This clear DCTL.SFTDISCON bit */
dwc2_hsotg_core_connect ( hsotg ) ;
} else {
if ( dwc2_is_device_mode ( hsotg ) ) {
if ( ! dwc2_ovr_bvalid ( hsotg , false ) )
/* This set DCTL.SFTDISCON bit */
dwc2_hsotg_core_disconnect ( hsotg ) ;
} else {
dwc2_ovr_avalid ( hsotg , false ) ;
}
}
spin_unlock_irqrestore ( & hsotg - > lock , flags ) ;
if ( ! already & & hsotg - > dr_mode = = USB_DR_MODE_OTG )
/* This will raise a Connector ID Status Change Interrupt */
dwc2_force_mode ( hsotg , role = = USB_ROLE_HOST ) ;
2021-10-05 11:53:04 +02:00
if ( ! hsotg - > ll_hw_enabled & & hsotg - > clk )
clk_disable_unprepare ( hsotg - > clk ) ;
2020-09-09 11:35:10 +02:00
dev_dbg ( hsotg - > dev , " %s-session valid \n " ,
role = = USB_ROLE_NONE ? " No " :
role = = USB_ROLE_HOST ? " A " : " B " ) ;
return 0 ;
}
int dwc2_drd_init ( struct dwc2_hsotg * hsotg )
{
struct usb_role_switch_desc role_sw_desc = { 0 } ;
struct usb_role_switch * role_sw ;
int ret ;
if ( ! device_property_read_bool ( hsotg - > dev , " usb-role-switch " ) )
return 0 ;
2021-12-06 16:56:42 +01:00
hsotg - > role_sw_default_mode = usb_get_role_switch_default_mode ( hsotg - > dev ) ;
2020-09-09 11:35:10 +02:00
role_sw_desc . driver_data = hsotg ;
role_sw_desc . fwnode = dev_fwnode ( hsotg - > dev ) ;
role_sw_desc . set = dwc2_drd_role_sw_set ;
role_sw_desc . allow_userspace_control = true ;
role_sw = usb_role_switch_register ( hsotg - > dev , & role_sw_desc ) ;
if ( IS_ERR ( role_sw ) ) {
ret = PTR_ERR ( role_sw ) ;
dev_err ( hsotg - > dev ,
" failed to register role switch: %d \n " , ret ) ;
return ret ;
}
hsotg - > role_sw = role_sw ;
/* Enable override and initialize values */
dwc2_ovr_init ( hsotg ) ;
return 0 ;
}
void dwc2_drd_suspend ( struct dwc2_hsotg * hsotg )
{
u32 gintsts , gintmsk ;
if ( hsotg - > role_sw & & ! hsotg - > params . external_id_pin_ctl ) {
gintmsk = dwc2_readl ( hsotg , GINTMSK ) ;
gintmsk & = ~ GINTSTS_CONIDSTSCHNG ;
dwc2_writel ( hsotg , gintmsk , GINTMSK ) ;
gintsts = dwc2_readl ( hsotg , GINTSTS ) ;
dwc2_writel ( hsotg , gintsts | GINTSTS_CONIDSTSCHNG , GINTSTS ) ;
}
}
void dwc2_drd_resume ( struct dwc2_hsotg * hsotg )
{
u32 gintsts , gintmsk ;
2021-12-06 16:56:43 +01:00
enum usb_role role ;
if ( hsotg - > role_sw ) {
/* get last known role (as the get ops isn't implemented by this driver) */
role = usb_role_switch_get_role ( hsotg - > role_sw ) ;
if ( role = = USB_ROLE_NONE ) {
if ( hsotg - > role_sw_default_mode = = USB_DR_MODE_HOST )
role = USB_ROLE_HOST ;
else if ( hsotg - > role_sw_default_mode = = USB_DR_MODE_PERIPHERAL )
role = USB_ROLE_DEVICE ;
}
/* restore last role that may have been lost */
if ( role = = USB_ROLE_HOST )
dwc2_ovr_avalid ( hsotg , true ) ;
else if ( role = = USB_ROLE_DEVICE )
dwc2_ovr_bvalid ( hsotg , true ) ;
dwc2_force_mode ( hsotg , role = = USB_ROLE_HOST ) ;
dev_dbg ( hsotg - > dev , " resuming %s-session valid \n " ,
role = = USB_ROLE_NONE ? " No " :
role = = USB_ROLE_HOST ? " A " : " B " ) ;
}
2020-09-09 11:35:10 +02:00
if ( hsotg - > role_sw & & ! hsotg - > params . external_id_pin_ctl ) {
gintsts = dwc2_readl ( hsotg , GINTSTS ) ;
dwc2_writel ( hsotg , gintsts | GINTSTS_CONIDSTSCHNG , GINTSTS ) ;
gintmsk = dwc2_readl ( hsotg , GINTMSK ) ;
gintmsk | = GINTSTS_CONIDSTSCHNG ;
dwc2_writel ( hsotg , gintmsk , GINTMSK ) ;
}
}
void dwc2_drd_exit ( struct dwc2_hsotg * hsotg )
{
if ( hsotg - > role_sw )
usb_role_switch_unregister ( hsotg - > role_sw ) ;
}