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>
# 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 ;
} ;
struct intel_xhci_acpi_match {
const char * hid ;
int hrv ;
} ;
/*
* ACPI IDs for PMICs which do not support separate data and power role
* detection ( USB ACA detection for micro USB OTG ) , we allow userspace to
* change the role manually on these .
*/
static const struct intel_xhci_acpi_match allow_userspace_ctrl_ids [ ] = {
{ " INT33F4 " , 3 } , /* X-Powers AXP288 PMIC */
} ;
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 ;
}
/* 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 ) ;
if ( ! ! ( val & HOST_MODE ) = = ( role = = USB_ROLE_HOST ) )
return 0 ;
/* Interval for polling is set to about 5 - 10 ms */
usleep_range ( 5000 , 10000 ) ;
} while ( time_before ( jiffies , timeout ) ) ;
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 ;
val = readl ( data - > base + DUAL_ROLE_CFG0 ) ;
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 struct usb_role_switch_desc sw_desc = {
. set = intel_xhci_usb_set_role ,
. get = intel_xhci_usb_get_role ,
} ;
static int intel_xhci_usb_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
struct intel_xhci_usb_data * data ;
struct resource * res ;
int i ;
data = devm_kzalloc ( dev , sizeof ( * data ) , GFP_KERNEL ) ;
if ( ! data )
return - ENOMEM ;
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
data - > base = devm_ioremap_nocache ( 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
for ( i = 0 ; i < ARRAY_SIZE ( allow_userspace_ctrl_ids ) ; i + + )
if ( acpi_dev_present ( allow_userspace_ctrl_ids [ i ] . hid , " 1 " ,
allow_userspace_ctrl_ids [ i ] . hrv ) )
sw_desc . allow_userspace_control = true ;
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 ) ;
return 0 ;
}
static int intel_xhci_usb_remove ( struct platform_device * pdev )
{
struct intel_xhci_usb_data * data = platform_get_drvdata ( pdev ) ;
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 " ) ;