Skip to content

Commit

Permalink
usb: roles: Add Intel xHCI USB role switch driver
Browse files Browse the repository at this point in the history
Various Intel SoCs (Cherry Trail, Broxton and others) have an internal USB
role switch for swiching the OTG USB data lines between the xHCI host
controller and the dwc3 gadget controller.

Note on some Cherry Trail systems there is ACPI/AML code listening to
edge interrupts on the id-pin (through an _AIE ACPI method) and switching
the role between ROLE_HOST and ROLE_NONE based on the id-pin. Note it does
not set the role to ROLE_DEVICE, because device-mode is usually not used
under Windows.

The presence of AML code which modifies the cfg0 reg (on some systems)
means that our read/write/modify of cfg0 may race with the AML code
doing the same to avoid this we take the global ACPI lock while doing
the read/write/modify.

Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
  • Loading branch information
Hans de Goede authored and Greg Kroah-Hartman committed Mar 22, 2018
1 parent fa31b3c commit f6fb9ec
Show file tree
Hide file tree
Showing 6 changed files with 217 additions and 0 deletions.
6 changes: 6 additions & 0 deletions MAINTAINERS
Original file line number Diff line number Diff line change
Expand Up @@ -14408,6 +14408,12 @@ S: Maintained
F: Documentation/hid/hiddev.txt
F: drivers/hid/usbhid/

USB INTEL XHCI ROLE MUX DRIVER
M: Hans de Goede <hdegoede@redhat.com>
L: linux-usb@vger.kernel.org
S: Maintained
F: drivers/usb/roles/intel-xhci-usb-role-switch.c

USB ISP116X DRIVER
M: Olav Kongas <ok@artecdesign.ee>
L: linux-usb@vger.kernel.org
Expand Down
2 changes: 2 additions & 0 deletions drivers/usb/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@ source "drivers/usb/gadget/Kconfig"

source "drivers/usb/typec/Kconfig"

source "drivers/usb/roles/Kconfig"

config USB_LED_TRIG
bool "USB LED Triggers"
depends on LEDS_CLASS && LEDS_TRIGGERS
Expand Down
2 changes: 2 additions & 0 deletions drivers/usb/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,5 @@ obj-$(CONFIG_USB_COMMON) += common/
obj-$(CONFIG_USBIP_CORE) += usbip/

obj-$(CONFIG_TYPEC) += typec/

obj-$(CONFIG_USB_ROLE_SWITCH) += roles/
14 changes: 14 additions & 0 deletions drivers/usb/roles/Kconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
if USB_ROLE_SWITCH

config USB_ROLES_INTEL_XHCI
tristate "Intel XHCI USB Role Switch"
depends on ACPI && X86
help
Driver for the internal USB role switch for switching the USB data
lines between the xHCI host controller and the dwc3 gadget controller
found on various Intel SoCs.

To compile the driver as a module, choose M here: the module will
be called intel-xhci-usb-role-switch.

endif # USB_ROLE_SWITCH
1 change: 1 addition & 0 deletions drivers/usb/roles/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
obj-$(CONFIG_USB_ROLES_INTEL_XHCI) += intel-xhci-usb-role-switch.o
192 changes: 192 additions & 0 deletions drivers/usb/roles/intel-xhci-usb-role-switch.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
// 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));
if (IS_ERR(data->base))
return PTR_ERR(data->base);

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");

0 comments on commit f6fb9ec

Please sign in to comment.