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>
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)
# 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 {
struct usb_role_switch * role_sw ;
void __iomem * base ;
} ;
static int intel_xhci_usb_set_role ( struct device * dev , enum usb_role role )
{
struct intel_xhci_usb_data * data = dev_get_drvdata ( dev ) ;
unsigned long timeout ;
acpi_status status ;
u32 glk , val ;
/*
* 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 ) {
dev_err ( dev , " Error could not acquire lock \n " ) ;
return - EIO ;
}
2018-05-24 11:18:27 +03:00
pm_runtime_get_sync ( dev ) ;
2018-03-20 15:57:10 +03:00
/* Set idpin value as requested */
val = readl ( data - > base + DUAL_ROLE_CFG0 ) ;
switch ( role ) {
case USB_ROLE_NONE :
val | = SW_IDPIN ;
val & = ~ SW_VBUS_VALID ;
break ;
case USB_ROLE_HOST :
val & = ~ SW_IDPIN ;
val & = ~ SW_VBUS_VALID ;
break ;
case USB_ROLE_DEVICE :
val | = SW_IDPIN ;
val | = SW_VBUS_VALID ;
break ;
}
val | = SW_IDPIN_EN ;
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 ) ) {
pm_runtime_put ( 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 ) ) ;
2018-05-24 11:18:27 +03:00
pm_runtime_put ( dev ) ;
2018-03-20 15:57:10 +03:00
dev_warn ( dev , " Timeout waiting for role-switch \n " ) ;
return - ETIMEDOUT ;
}
static enum usb_role intel_xhci_usb_get_role ( struct device * dev )
{
struct intel_xhci_usb_data * data = dev_get_drvdata ( dev ) ;
enum usb_role role ;
u32 val ;
2018-05-24 11:18:27 +03:00
pm_runtime_get_sync ( dev ) ;
2018-03-20 15:57:10 +03:00
val = readl ( data - > base + DUAL_ROLE_CFG0 ) ;
2018-05-24 11:18:27 +03:00
pm_runtime_put ( 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 ;
}
2018-05-24 11:18:24 +03:00
static const struct usb_role_switch_desc sw_desc = {
2018-03-20 15:57:10 +03:00
. set = intel_xhci_usb_set_role ,
. get = intel_xhci_usb_get_role ,
2018-05-24 11:18:24 +03:00
. allow_userspace_control = true ,
2018-03-20 15:57:10 +03:00
} ;
static int intel_xhci_usb_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct intel_xhci_usb_data * data ;
struct resource * res ;
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 05:14:11 +03:00
if ( ! res )
return - EINVAL ;
2018-03-20 15:57:10 +03:00
data - > base = devm_ioremap_nocache ( dev , res - > start , resource_size ( res ) ) ;
2018-03-26 09:43:57 +03:00
if ( ! data - > base )
return - ENOMEM ;
2018-03-20 15:57:10 +03:00
platform_set_drvdata ( pdev , data ) ;
data - > role_sw = usb_role_switch_register ( dev , & sw_desc ) ;
if ( IS_ERR ( data - > role_sw ) )
return PTR_ERR ( data - > role_sw ) ;
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 07:52:47 +03:00
pm_runtime_disable ( & pdev - > dev ) ;
2018-03-20 15:57:10 +03:00
usb_role_switch_unregister ( data - > role_sw ) ;
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 " ) ;