linux/drivers/usb/roles/intel-xhci-usb-role-switch.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * Intel XHCI (Cherry Trail, Broxton and others) USB OTG role switch driver
   4 *
   5 * Copyright (c) 2016-2017 Hans de Goede <hdegoede@redhat.com>
   6 *
   7 * Loosely based on android x86 kernel code which is:
   8 *
   9 * Copyright (C) 2014 Intel Corp.
  10 *
  11 * Author: Wu, Hao
  12 */
  13
  14#include <linux/acpi.h>
  15#include <linux/delay.h>
  16#include <linux/err.h>
  17#include <linux/io.h>
  18#include <linux/kernel.h>
  19#include <linux/module.h>
  20#include <linux/platform_device.h>
  21#include <linux/pm_runtime.h>
  22#include <linux/property.h>
  23#include <linux/usb/role.h>
  24
  25/* register definition */
  26#define DUAL_ROLE_CFG0                  0x68
  27#define SW_VBUS_VALID                   BIT(24)
  28#define SW_IDPIN_EN                     BIT(21)
  29#define SW_IDPIN                        BIT(20)
  30#define SW_SWITCH_EN                    BIT(16)
  31
  32#define DRD_CONFIG_DYNAMIC              0
  33#define DRD_CONFIG_STATIC_HOST          1
  34#define DRD_CONFIG_STATIC_DEVICE        2
  35#define DRD_CONFIG_MASK                 3
  36
  37#define DUAL_ROLE_CFG1                  0x6c
  38#define HOST_MODE                       BIT(29)
  39
  40#define DUAL_ROLE_CFG1_POLL_TIMEOUT     1000
  41
  42#define DRV_NAME                        "intel_xhci_usb_sw"
  43
  44struct intel_xhci_usb_data {
  45        struct device *dev;
  46        struct usb_role_switch *role_sw;
  47        void __iomem *base;
  48        bool enable_sw_switch;
  49};
  50
  51static const struct software_node intel_xhci_usb_node = {
  52        "intel-xhci-usb-sw",
  53};
  54
  55static int intel_xhci_usb_set_role(struct usb_role_switch *sw,
  56                                   enum usb_role role)
  57{
  58        struct intel_xhci_usb_data *data = usb_role_switch_get_drvdata(sw);
  59        unsigned long timeout;
  60        acpi_status status;
  61        u32 glk, val;
  62        u32 drd_config = DRD_CONFIG_DYNAMIC;
  63
  64        /*
  65         * On many CHT devices ACPI event (_AEI) handlers read / modify /
  66         * write the cfg0 register, just like we do. Take the ACPI lock
  67         * to avoid us racing with the AML code.
  68         */
  69        status = acpi_acquire_global_lock(ACPI_WAIT_FOREVER, &glk);
  70        if (ACPI_FAILURE(status) && status != AE_NOT_CONFIGURED) {
  71                dev_err(data->dev, "Error could not acquire lock\n");
  72                return -EIO;
  73        }
  74
  75        pm_runtime_get_sync(data->dev);
  76
  77        /*
  78         * Set idpin value as requested.
  79         * Since some devices rely on firmware setting DRD_CONFIG and
  80         * SW_SWITCH_EN bits to be zero for role switch,
  81         * do not set these bits for those devices.
  82         */
  83        val = readl(data->base + DUAL_ROLE_CFG0);
  84        switch (role) {
  85        case USB_ROLE_NONE:
  86                val |= SW_IDPIN;
  87                val &= ~SW_VBUS_VALID;
  88                drd_config = DRD_CONFIG_DYNAMIC;
  89                break;
  90        case USB_ROLE_HOST:
  91                val &= ~SW_IDPIN;
  92                val &= ~SW_VBUS_VALID;
  93                drd_config = DRD_CONFIG_STATIC_HOST;
  94                break;
  95        case USB_ROLE_DEVICE:
  96                val |= SW_IDPIN;
  97                val |= SW_VBUS_VALID;
  98                drd_config = DRD_CONFIG_STATIC_DEVICE;
  99                break;
 100        }
 101        val |= SW_IDPIN_EN;
 102        if (data->enable_sw_switch) {
 103                val &= ~DRD_CONFIG_MASK;
 104                val |= SW_SWITCH_EN | drd_config;
 105        }
 106        writel(val, data->base + DUAL_ROLE_CFG0);
 107
 108        acpi_release_global_lock(glk);
 109
 110        /* In most case it takes about 600ms to finish mode switching */
 111        timeout = jiffies + msecs_to_jiffies(DUAL_ROLE_CFG1_POLL_TIMEOUT);
 112
 113        /* Polling on CFG1 register to confirm mode switch.*/
 114        do {
 115                val = readl(data->base + DUAL_ROLE_CFG1);
 116                if (!!(val & HOST_MODE) == (role == USB_ROLE_HOST)) {
 117                        pm_runtime_put(data->dev);
 118                        return 0;
 119                }
 120
 121                /* Interval for polling is set to about 5 - 10 ms */
 122                usleep_range(5000, 10000);
 123        } while (time_before(jiffies, timeout));
 124
 125        pm_runtime_put(data->dev);
 126
 127        dev_warn(data->dev, "Timeout waiting for role-switch\n");
 128        return -ETIMEDOUT;
 129}
 130
 131static enum usb_role intel_xhci_usb_get_role(struct usb_role_switch *sw)
 132{
 133        struct intel_xhci_usb_data *data = usb_role_switch_get_drvdata(sw);
 134        enum usb_role role;
 135        u32 val;
 136
 137        pm_runtime_get_sync(data->dev);
 138        val = readl(data->base + DUAL_ROLE_CFG0);
 139        pm_runtime_put(data->dev);
 140
 141        if (!(val & SW_IDPIN))
 142                role = USB_ROLE_HOST;
 143        else if (val & SW_VBUS_VALID)
 144                role = USB_ROLE_DEVICE;
 145        else
 146                role = USB_ROLE_NONE;
 147
 148        return role;
 149}
 150
 151static int intel_xhci_usb_probe(struct platform_device *pdev)
 152{
 153        struct usb_role_switch_desc sw_desc = { };
 154        struct device *dev = &pdev->dev;
 155        struct intel_xhci_usb_data *data;
 156        struct resource *res;
 157        int ret;
 158
 159        data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
 160        if (!data)
 161                return -ENOMEM;
 162
 163        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 164        if (!res)
 165                return -EINVAL;
 166        data->base = devm_ioremap(dev, res->start, resource_size(res));
 167        if (!data->base)
 168                return -ENOMEM;
 169
 170        platform_set_drvdata(pdev, data);
 171
 172        ret = software_node_register(&intel_xhci_usb_node);
 173        if (ret)
 174                return ret;
 175
 176        sw_desc.set = intel_xhci_usb_set_role,
 177        sw_desc.get = intel_xhci_usb_get_role,
 178        sw_desc.allow_userspace_control = true,
 179        sw_desc.fwnode = software_node_fwnode(&intel_xhci_usb_node);
 180        sw_desc.driver_data = data;
 181
 182        data->dev = dev;
 183        data->enable_sw_switch = !device_property_read_bool(dev,
 184                                                "sw_switch_disable");
 185
 186        data->role_sw = usb_role_switch_register(dev, &sw_desc);
 187        if (IS_ERR(data->role_sw)) {
 188                fwnode_handle_put(sw_desc.fwnode);
 189                return PTR_ERR(data->role_sw);
 190        }
 191
 192        pm_runtime_set_active(dev);
 193        pm_runtime_enable(dev);
 194
 195        return 0;
 196}
 197
 198static int intel_xhci_usb_remove(struct platform_device *pdev)
 199{
 200        struct intel_xhci_usb_data *data = platform_get_drvdata(pdev);
 201
 202        pm_runtime_disable(&pdev->dev);
 203
 204        usb_role_switch_unregister(data->role_sw);
 205        fwnode_handle_put(software_node_fwnode(&intel_xhci_usb_node));
 206
 207        return 0;
 208}
 209
 210static const struct platform_device_id intel_xhci_usb_table[] = {
 211        { .name = DRV_NAME },
 212        {}
 213};
 214MODULE_DEVICE_TABLE(platform, intel_xhci_usb_table);
 215
 216static struct platform_driver intel_xhci_usb_driver = {
 217        .driver = {
 218                .name = DRV_NAME,
 219        },
 220        .id_table = intel_xhci_usb_table,
 221        .probe = intel_xhci_usb_probe,
 222        .remove = intel_xhci_usb_remove,
 223};
 224
 225module_platform_driver(intel_xhci_usb_driver);
 226
 227MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
 228MODULE_DESCRIPTION("Intel XHCI USB role switch driver");
 229MODULE_LICENSE("GPL");
 230
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.