18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Intel XHCI (Cherry Trail, Broxton and others) USB OTG role switch driver 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (c) 2016-2017 Hans de Goede <hdegoede@redhat.com> 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * Loosely based on android x86 kernel code which is: 88c2ecf20Sopenharmony_ci * 98c2ecf20Sopenharmony_ci * Copyright (C) 2014 Intel Corp. 108c2ecf20Sopenharmony_ci * 118c2ecf20Sopenharmony_ci * Author: Wu, Hao 128c2ecf20Sopenharmony_ci */ 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_ci#include <linux/acpi.h> 158c2ecf20Sopenharmony_ci#include <linux/delay.h> 168c2ecf20Sopenharmony_ci#include <linux/err.h> 178c2ecf20Sopenharmony_ci#include <linux/io.h> 188c2ecf20Sopenharmony_ci#include <linux/kernel.h> 198c2ecf20Sopenharmony_ci#include <linux/module.h> 208c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 218c2ecf20Sopenharmony_ci#include <linux/pm_runtime.h> 228c2ecf20Sopenharmony_ci#include <linux/property.h> 238c2ecf20Sopenharmony_ci#include <linux/usb/role.h> 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci/* register definition */ 268c2ecf20Sopenharmony_ci#define DUAL_ROLE_CFG0 0x68 278c2ecf20Sopenharmony_ci#define SW_VBUS_VALID BIT(24) 288c2ecf20Sopenharmony_ci#define SW_IDPIN_EN BIT(21) 298c2ecf20Sopenharmony_ci#define SW_IDPIN BIT(20) 308c2ecf20Sopenharmony_ci#define SW_SWITCH_EN BIT(16) 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci#define DRD_CONFIG_DYNAMIC 0 338c2ecf20Sopenharmony_ci#define DRD_CONFIG_STATIC_HOST 1 348c2ecf20Sopenharmony_ci#define DRD_CONFIG_STATIC_DEVICE 2 358c2ecf20Sopenharmony_ci#define DRD_CONFIG_MASK 3 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci#define DUAL_ROLE_CFG1 0x6c 388c2ecf20Sopenharmony_ci#define HOST_MODE BIT(29) 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_ci#define DUAL_ROLE_CFG1_POLL_TIMEOUT 1000 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci#define DRV_NAME "intel_xhci_usb_sw" 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_cistruct intel_xhci_usb_data { 458c2ecf20Sopenharmony_ci struct device *dev; 468c2ecf20Sopenharmony_ci struct usb_role_switch *role_sw; 478c2ecf20Sopenharmony_ci void __iomem *base; 488c2ecf20Sopenharmony_ci bool enable_sw_switch; 498c2ecf20Sopenharmony_ci}; 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_cistatic const struct software_node intel_xhci_usb_node = { 528c2ecf20Sopenharmony_ci "intel-xhci-usb-sw", 538c2ecf20Sopenharmony_ci}; 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_cistatic int intel_xhci_usb_set_role(struct usb_role_switch *sw, 568c2ecf20Sopenharmony_ci enum usb_role role) 578c2ecf20Sopenharmony_ci{ 588c2ecf20Sopenharmony_ci struct intel_xhci_usb_data *data = usb_role_switch_get_drvdata(sw); 598c2ecf20Sopenharmony_ci unsigned long timeout; 608c2ecf20Sopenharmony_ci acpi_status status; 618c2ecf20Sopenharmony_ci u32 glk, val; 628c2ecf20Sopenharmony_ci u32 drd_config = DRD_CONFIG_DYNAMIC; 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci /* 658c2ecf20Sopenharmony_ci * On many CHT devices ACPI event (_AEI) handlers read / modify / 668c2ecf20Sopenharmony_ci * write the cfg0 register, just like we do. Take the ACPI lock 678c2ecf20Sopenharmony_ci * to avoid us racing with the AML code. 688c2ecf20Sopenharmony_ci */ 698c2ecf20Sopenharmony_ci status = acpi_acquire_global_lock(ACPI_WAIT_FOREVER, &glk); 708c2ecf20Sopenharmony_ci if (ACPI_FAILURE(status) && status != AE_NOT_CONFIGURED) { 718c2ecf20Sopenharmony_ci dev_err(data->dev, "Error could not acquire lock\n"); 728c2ecf20Sopenharmony_ci return -EIO; 738c2ecf20Sopenharmony_ci } 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci pm_runtime_get_sync(data->dev); 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci /* 788c2ecf20Sopenharmony_ci * Set idpin value as requested. 798c2ecf20Sopenharmony_ci * Since some devices rely on firmware setting DRD_CONFIG and 808c2ecf20Sopenharmony_ci * SW_SWITCH_EN bits to be zero for role switch, 818c2ecf20Sopenharmony_ci * do not set these bits for those devices. 828c2ecf20Sopenharmony_ci */ 838c2ecf20Sopenharmony_ci val = readl(data->base + DUAL_ROLE_CFG0); 848c2ecf20Sopenharmony_ci switch (role) { 858c2ecf20Sopenharmony_ci case USB_ROLE_NONE: 868c2ecf20Sopenharmony_ci val |= SW_IDPIN; 878c2ecf20Sopenharmony_ci val &= ~SW_VBUS_VALID; 888c2ecf20Sopenharmony_ci drd_config = DRD_CONFIG_DYNAMIC; 898c2ecf20Sopenharmony_ci break; 908c2ecf20Sopenharmony_ci case USB_ROLE_HOST: 918c2ecf20Sopenharmony_ci val &= ~SW_IDPIN; 928c2ecf20Sopenharmony_ci val &= ~SW_VBUS_VALID; 938c2ecf20Sopenharmony_ci drd_config = DRD_CONFIG_STATIC_HOST; 948c2ecf20Sopenharmony_ci break; 958c2ecf20Sopenharmony_ci case USB_ROLE_DEVICE: 968c2ecf20Sopenharmony_ci val |= SW_IDPIN; 978c2ecf20Sopenharmony_ci val |= SW_VBUS_VALID; 988c2ecf20Sopenharmony_ci drd_config = DRD_CONFIG_STATIC_DEVICE; 998c2ecf20Sopenharmony_ci break; 1008c2ecf20Sopenharmony_ci } 1018c2ecf20Sopenharmony_ci val |= SW_IDPIN_EN; 1028c2ecf20Sopenharmony_ci if (data->enable_sw_switch) { 1038c2ecf20Sopenharmony_ci val &= ~DRD_CONFIG_MASK; 1048c2ecf20Sopenharmony_ci val |= SW_SWITCH_EN | drd_config; 1058c2ecf20Sopenharmony_ci } 1068c2ecf20Sopenharmony_ci writel(val, data->base + DUAL_ROLE_CFG0); 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci acpi_release_global_lock(glk); 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci /* In most case it takes about 600ms to finish mode switching */ 1118c2ecf20Sopenharmony_ci timeout = jiffies + msecs_to_jiffies(DUAL_ROLE_CFG1_POLL_TIMEOUT); 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ci /* Polling on CFG1 register to confirm mode switch.*/ 1148c2ecf20Sopenharmony_ci do { 1158c2ecf20Sopenharmony_ci val = readl(data->base + DUAL_ROLE_CFG1); 1168c2ecf20Sopenharmony_ci if (!!(val & HOST_MODE) == (role == USB_ROLE_HOST)) { 1178c2ecf20Sopenharmony_ci pm_runtime_put(data->dev); 1188c2ecf20Sopenharmony_ci return 0; 1198c2ecf20Sopenharmony_ci } 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci /* Interval for polling is set to about 5 - 10 ms */ 1228c2ecf20Sopenharmony_ci usleep_range(5000, 10000); 1238c2ecf20Sopenharmony_ci } while (time_before(jiffies, timeout)); 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci pm_runtime_put(data->dev); 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci dev_warn(data->dev, "Timeout waiting for role-switch\n"); 1288c2ecf20Sopenharmony_ci return -ETIMEDOUT; 1298c2ecf20Sopenharmony_ci} 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_cistatic enum usb_role intel_xhci_usb_get_role(struct usb_role_switch *sw) 1328c2ecf20Sopenharmony_ci{ 1338c2ecf20Sopenharmony_ci struct intel_xhci_usb_data *data = usb_role_switch_get_drvdata(sw); 1348c2ecf20Sopenharmony_ci enum usb_role role; 1358c2ecf20Sopenharmony_ci u32 val; 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_ci pm_runtime_get_sync(data->dev); 1388c2ecf20Sopenharmony_ci val = readl(data->base + DUAL_ROLE_CFG0); 1398c2ecf20Sopenharmony_ci pm_runtime_put(data->dev); 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci if (!(val & SW_IDPIN)) 1428c2ecf20Sopenharmony_ci role = USB_ROLE_HOST; 1438c2ecf20Sopenharmony_ci else if (val & SW_VBUS_VALID) 1448c2ecf20Sopenharmony_ci role = USB_ROLE_DEVICE; 1458c2ecf20Sopenharmony_ci else 1468c2ecf20Sopenharmony_ci role = USB_ROLE_NONE; 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci return role; 1498c2ecf20Sopenharmony_ci} 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_cistatic int intel_xhci_usb_probe(struct platform_device *pdev) 1528c2ecf20Sopenharmony_ci{ 1538c2ecf20Sopenharmony_ci struct usb_role_switch_desc sw_desc = { }; 1548c2ecf20Sopenharmony_ci struct device *dev = &pdev->dev; 1558c2ecf20Sopenharmony_ci struct intel_xhci_usb_data *data; 1568c2ecf20Sopenharmony_ci struct resource *res; 1578c2ecf20Sopenharmony_ci int ret; 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); 1608c2ecf20Sopenharmony_ci if (!data) 1618c2ecf20Sopenharmony_ci return -ENOMEM; 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_ci res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 1648c2ecf20Sopenharmony_ci if (!res) 1658c2ecf20Sopenharmony_ci return -EINVAL; 1668c2ecf20Sopenharmony_ci data->base = devm_ioremap(dev, res->start, resource_size(res)); 1678c2ecf20Sopenharmony_ci if (!data->base) 1688c2ecf20Sopenharmony_ci return -ENOMEM; 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, data); 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_ci ret = software_node_register(&intel_xhci_usb_node); 1738c2ecf20Sopenharmony_ci if (ret) 1748c2ecf20Sopenharmony_ci return ret; 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci sw_desc.set = intel_xhci_usb_set_role, 1778c2ecf20Sopenharmony_ci sw_desc.get = intel_xhci_usb_get_role, 1788c2ecf20Sopenharmony_ci sw_desc.allow_userspace_control = true, 1798c2ecf20Sopenharmony_ci sw_desc.fwnode = software_node_fwnode(&intel_xhci_usb_node); 1808c2ecf20Sopenharmony_ci sw_desc.driver_data = data; 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci data->dev = dev; 1838c2ecf20Sopenharmony_ci data->enable_sw_switch = !device_property_read_bool(dev, 1848c2ecf20Sopenharmony_ci "sw_switch_disable"); 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci data->role_sw = usb_role_switch_register(dev, &sw_desc); 1878c2ecf20Sopenharmony_ci if (IS_ERR(data->role_sw)) { 1888c2ecf20Sopenharmony_ci fwnode_handle_put(sw_desc.fwnode); 1898c2ecf20Sopenharmony_ci return PTR_ERR(data->role_sw); 1908c2ecf20Sopenharmony_ci } 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci pm_runtime_set_active(dev); 1938c2ecf20Sopenharmony_ci pm_runtime_enable(dev); 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_ci return 0; 1968c2ecf20Sopenharmony_ci} 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_cistatic int intel_xhci_usb_remove(struct platform_device *pdev) 1998c2ecf20Sopenharmony_ci{ 2008c2ecf20Sopenharmony_ci struct intel_xhci_usb_data *data = platform_get_drvdata(pdev); 2018c2ecf20Sopenharmony_ci 2028c2ecf20Sopenharmony_ci pm_runtime_disable(&pdev->dev); 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci usb_role_switch_unregister(data->role_sw); 2058c2ecf20Sopenharmony_ci fwnode_handle_put(software_node_fwnode(&intel_xhci_usb_node)); 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci return 0; 2088c2ecf20Sopenharmony_ci} 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_cistatic const struct platform_device_id intel_xhci_usb_table[] = { 2118c2ecf20Sopenharmony_ci { .name = DRV_NAME }, 2128c2ecf20Sopenharmony_ci {} 2138c2ecf20Sopenharmony_ci}; 2148c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(platform, intel_xhci_usb_table); 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_cistatic struct platform_driver intel_xhci_usb_driver = { 2178c2ecf20Sopenharmony_ci .driver = { 2188c2ecf20Sopenharmony_ci .name = DRV_NAME, 2198c2ecf20Sopenharmony_ci }, 2208c2ecf20Sopenharmony_ci .id_table = intel_xhci_usb_table, 2218c2ecf20Sopenharmony_ci .probe = intel_xhci_usb_probe, 2228c2ecf20Sopenharmony_ci .remove = intel_xhci_usb_remove, 2238c2ecf20Sopenharmony_ci}; 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_cimodule_platform_driver(intel_xhci_usb_driver); 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_ciMODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); 2288c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Intel XHCI USB role switch driver"); 2298c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 230