2018-03-20 15:57:10 +03:00
// SPDX-License-Identifier: GPL-2.0+
/*
* Intel XHCI ( Cherry Trail , Broxton and others ) USB OTG role switch driver
*
* Copyright ( c ) 2016 - 2017 Hans de Goede < hdegoede @ redhat . com >
*
* Loosely based on android x86 kernel code which is :
*
* Copyright ( C ) 2014 Intel Corp .
*
* Author : Wu , Hao
*/
# include <linux/acpi.h>
# include <linux/delay.h>
# include <linux/err.h>
# include <linux/io.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/platform_device.h>
2018-05-24 11:18:27 +03:00
# include <linux/pm_runtime.h>
2019-08-29 17:26:00 +05:30
# include <linux/property.h>
2018-03-20 15:57:10 +03:00
# include <linux/usb/role.h>
/* register definition */
# define DUAL_ROLE_CFG0 0x68
# define SW_VBUS_VALID BIT(24)
# define SW_IDPIN_EN BIT(21)
# define SW_IDPIN BIT(20)
2019-08-29 17:26:00 +05:30
# define SW_SWITCH_EN BIT(16)
# define DRD_CONFIG_DYNAMIC 0
# define DRD_CONFIG_STATIC_HOST 1
# define DRD_CONFIG_STATIC_DEVICE 2
# define DRD_CONFIG_MASK 3
2018-03-20 15:57:10 +03:00
# define DUAL_ROLE_CFG1 0x6c
# define HOST_MODE BIT(29)
# define DUAL_ROLE_CFG1_POLL_TIMEOUT 1000
# define DRV_NAME "intel_xhci_usb_sw"
struct intel_xhci_usb_data {
2020-03-02 16:53:49 +03:00
struct device * dev ;
2018-03-20 15:57:10 +03:00
struct usb_role_switch * role_sw ;
void __iomem * base ;
2019-08-29 17:26:00 +05:30
bool enable_sw_switch ;
2018-03-20 15:57:10 +03:00
} ;
2019-08-19 13:07:23 +03:00
static const struct software_node intel_xhci_usb_node = {
" intel-xhci-usb-sw " ,
} ;
2020-03-02 16:53:49 +03:00
static int intel_xhci_usb_set_role ( struct usb_role_switch * sw ,
enum usb_role role )
2018-03-20 15:57:10 +03:00
{
2020-03-02 16:53:49 +03:00
struct intel_xhci_usb_data * data = usb_role_switch_get_drvdata ( sw ) ;
2018-03-20 15:57:10 +03:00
unsigned long timeout ;
acpi_status status ;
u32 glk , val ;
2019-08-29 17:26:00 +05:30
u32 drd_config = DRD_CONFIG_DYNAMIC ;
2018-03-20 15:57:10 +03:00
/*
* On many CHT devices ACPI event ( _AEI ) handlers read / modify /
* write the cfg0 register , just like we do . Take the ACPI lock
* to avoid us racing with the AML code .
*/
status = acpi_acquire_global_lock ( ACPI_WAIT_FOREVER , & glk ) ;
if ( ACPI_FAILURE ( status ) & & status ! = AE_NOT_CONFIGURED ) {
2020-03-02 16:53:49 +03:00
dev_err ( data - > dev , " Error could not acquire lock \n " ) ;
2018-03-20 15:57:10 +03:00
return - EIO ;
}
2020-03-02 16:53:49 +03:00
pm_runtime_get_sync ( data - > dev ) ;
2018-05-24 11:18:27 +03:00
2019-08-29 17:26:00 +05:30
/*
* Set idpin value as requested .
* Since some devices rely on firmware setting DRD_CONFIG and
* SW_SWITCH_EN bits to be zero for role switch ,
* do not set these bits for those devices .
*/
2018-03-20 15:57:10 +03:00
val = readl ( data - > base + DUAL_ROLE_CFG0 ) ;
switch ( role ) {
case USB_ROLE_NONE :
val | = SW_IDPIN ;
val & = ~ SW_VBUS_VALID ;
2019-08-29 17:26:00 +05:30
drd_config = DRD_CONFIG_DYNAMIC ;
2018-03-20 15:57:10 +03:00
break ;
case USB_ROLE_HOST :
val & = ~ SW_IDPIN ;
val & = ~ SW_VBUS_VALID ;
2019-08-29 17:26:00 +05:30
drd_config = DRD_CONFIG_STATIC_HOST ;
2018-03-20 15:57:10 +03:00
break ;
case USB_ROLE_DEVICE :
val | = SW_IDPIN ;
val | = SW_VBUS_VALID ;
2019-08-29 17:26:00 +05:30
drd_config = DRD_CONFIG_STATIC_DEVICE ;
2018-03-20 15:57:10 +03:00
break ;
}
val | = SW_IDPIN_EN ;
2019-08-29 17:26:00 +05:30
if ( data - > enable_sw_switch ) {
val & = ~ DRD_CONFIG_MASK ;
val | = SW_SWITCH_EN | drd_config ;
}
2018-03-20 15:57:10 +03:00
writel ( val , data - > base + DUAL_ROLE_CFG0 ) ;
acpi_release_global_lock ( glk ) ;
/* In most case it takes about 600ms to finish mode switching */
timeout = jiffies + msecs_to_jiffies ( DUAL_ROLE_CFG1_POLL_TIMEOUT ) ;
/* Polling on CFG1 register to confirm mode switch.*/
do {
val = readl ( data - > base + DUAL_ROLE_CFG1 ) ;
2018-05-24 11:18:27 +03:00
if ( ! ! ( val & HOST_MODE ) = = ( role = = USB_ROLE_HOST ) ) {
2020-03-02 16:53:49 +03:00
pm_runtime_put ( data - > dev ) ;
2018-03-20 15:57:10 +03:00
return 0 ;
2018-05-24 11:18:27 +03:00
}
2018-03-20 15:57:10 +03:00
/* Interval for polling is set to about 5 - 10 ms */
usleep_range ( 5000 , 10000 ) ;
} while ( time_before ( jiffies , timeout ) ) ;
2020-03-02 16:53:49 +03:00
pm_runtime_put ( data - > dev ) ;
2018-05-24 11:18:27 +03:00
2020-03-02 16:53:49 +03:00
dev_warn ( data - > dev , " Timeout waiting for role-switch \n " ) ;
2018-03-20 15:57:10 +03:00
return - ETIMEDOUT ;
}
2020-03-02 16:53:49 +03:00
static enum usb_role intel_xhci_usb_get_role ( struct usb_role_switch * sw )
2018-03-20 15:57:10 +03:00
{
2020-03-02 16:53:49 +03:00
struct intel_xhci_usb_data * data = usb_role_switch_get_drvdata ( sw ) ;
2018-03-20 15:57:10 +03:00
enum usb_role role ;
u32 val ;
2020-03-02 16:53:49 +03:00
pm_runtime_get_sync ( data - > dev ) ;
2018-03-20 15:57:10 +03:00
val = readl ( data - > base + DUAL_ROLE_CFG0 ) ;
2020-03-02 16:53:49 +03:00
pm_runtime_put ( data - > dev ) ;
2018-03-20 15:57:10 +03:00
if ( ! ( val & SW_IDPIN ) )
role = USB_ROLE_HOST ;
else if ( val & SW_VBUS_VALID )
role = USB_ROLE_DEVICE ;
else
role = USB_ROLE_NONE ;
return role ;
}
static int intel_xhci_usb_probe ( struct platform_device * pdev )
{
2019-08-19 13:07:23 +03:00
struct usb_role_switch_desc sw_desc = { } ;
2018-03-20 15:57:10 +03:00
struct device * dev = & pdev - > dev ;
struct intel_xhci_usb_data * data ;
struct resource * res ;
2019-08-19 13:07:23 +03:00
int ret ;
2018-03-20 15:57:10 +03:00
data = devm_kzalloc ( dev , sizeof ( * data ) , GFP_KERNEL ) ;
if ( ! data )
return - ENOMEM ;
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
usb: roles: Fix potential NULL dereference in intel_xhci_usb_probe()
platform_get_resource() may fail and return NULL, so we should
better check it's return value to avoid a NULL pointer dereference
a bit later in the code.
This is detected by Coccinelle semantic patch.
@@
expression pdev, res, n, t, e, e1, e2;
@@
res = platform_get_resource(pdev, t, n);
+ if (!res)
+ return -EINVAL;
... when != res == NULL
e = devm_ioremap_nocache(e1, res->start, e2);
Signed-off-by: Wei Yongjun <weiyongjun1@huawei.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2018-03-29 02:14:11 +00:00
if ( ! res )
return - EINVAL ;
2020-01-06 09:43:50 +01:00
data - > base = devm_ioremap ( dev , res - > start , resource_size ( res ) ) ;
2018-03-26 06:43:57 +00:00
if ( ! data - > base )
return - ENOMEM ;
2018-03-20 15:57:10 +03:00
platform_set_drvdata ( pdev , data ) ;
2019-08-19 13:07:23 +03:00
ret = software_node_register ( & intel_xhci_usb_node ) ;
if ( ret )
return ret ;
sw_desc . set = intel_xhci_usb_set_role ,
sw_desc . get = intel_xhci_usb_get_role ,
sw_desc . allow_userspace_control = true ,
sw_desc . fwnode = software_node_fwnode ( & intel_xhci_usb_node ) ;
2020-03-02 16:53:49 +03:00
sw_desc . driver_data = data ;
2019-08-19 13:07:23 +03:00
2020-03-02 16:53:49 +03:00
data - > dev = dev ;
2019-08-29 17:26:00 +05:30
data - > enable_sw_switch = ! device_property_read_bool ( dev ,
" sw_switch_disable " ) ;
2018-03-20 15:57:10 +03:00
data - > role_sw = usb_role_switch_register ( dev , & sw_desc ) ;
2019-08-19 13:07:23 +03:00
if ( IS_ERR ( data - > role_sw ) ) {
fwnode_handle_put ( sw_desc . fwnode ) ;
2018-03-20 15:57:10 +03:00
return PTR_ERR ( data - > role_sw ) ;
2019-08-19 13:07:23 +03:00
}
2018-03-20 15:57:10 +03:00
2018-05-24 11:18:27 +03:00
pm_runtime_set_active ( dev ) ;
pm_runtime_enable ( dev ) ;
2018-03-20 15:57:10 +03:00
return 0 ;
}
static int intel_xhci_usb_remove ( struct platform_device * pdev )
{
struct intel_xhci_usb_data * data = platform_get_drvdata ( pdev ) ;
2018-10-09 12:52:47 +08:00
pm_runtime_disable ( & pdev - > dev ) ;
2018-03-20 15:57:10 +03:00
usb_role_switch_unregister ( data - > role_sw ) ;
2019-08-19 13:07:23 +03:00
fwnode_handle_put ( software_node_fwnode ( & intel_xhci_usb_node ) ) ;
2018-03-20 15:57:10 +03:00
return 0 ;
}
static const struct platform_device_id intel_xhci_usb_table [ ] = {
{ . name = DRV_NAME } ,
{ }
} ;
MODULE_DEVICE_TABLE ( platform , intel_xhci_usb_table ) ;
static struct platform_driver intel_xhci_usb_driver = {
. driver = {
. name = DRV_NAME ,
} ,
. id_table = intel_xhci_usb_table ,
. probe = intel_xhci_usb_probe ,
. remove = intel_xhci_usb_remove ,
} ;
module_platform_driver ( intel_xhci_usb_driver ) ;
MODULE_AUTHOR ( " Hans de Goede <hdegoede@redhat.com> " ) ;
MODULE_DESCRIPTION ( " Intel XHCI USB role switch driver " ) ;
MODULE_LICENSE ( " GPL " ) ;