root/drivers/usb/roles/intel-xhci-usb-role-switch.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. intel_xhci_usb_set_role
  2. intel_xhci_usb_get_role
  3. intel_xhci_usb_probe
  4. intel_xhci_usb_remove

   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 
  44 struct intel_xhci_usb_data {
  45         struct usb_role_switch *role_sw;
  46         void __iomem *base;
  47         bool enable_sw_switch;
  48 };
  49 
  50 static const struct software_node intel_xhci_usb_node = {
  51         "intel-xhci-usb-sw",
  52 };
  53 
  54 static int intel_xhci_usb_set_role(struct device *dev, enum usb_role role)
  55 {
  56         struct intel_xhci_usb_data *data = dev_get_drvdata(dev);
  57         unsigned long timeout;
  58         acpi_status status;
  59         u32 glk, val;
  60         u32 drd_config = DRD_CONFIG_DYNAMIC;
  61 
  62         /*
  63          * On many CHT devices ACPI event (_AEI) handlers read / modify /
  64          * write the cfg0 register, just like we do. Take the ACPI lock
  65          * to avoid us racing with the AML code.
  66          */
  67         status = acpi_acquire_global_lock(ACPI_WAIT_FOREVER, &glk);
  68         if (ACPI_FAILURE(status) && status != AE_NOT_CONFIGURED) {
  69                 dev_err(dev, "Error could not acquire lock\n");
  70                 return -EIO;
  71         }
  72 
  73         pm_runtime_get_sync(dev);
  74 
  75         /*
  76          * Set idpin value as requested.
  77          * Since some devices rely on firmware setting DRD_CONFIG and
  78          * SW_SWITCH_EN bits to be zero for role switch,
  79          * do not set these bits for those devices.
  80          */
  81         val = readl(data->base + DUAL_ROLE_CFG0);
  82         switch (role) {
  83         case USB_ROLE_NONE:
  84                 val |= SW_IDPIN;
  85                 val &= ~SW_VBUS_VALID;
  86                 drd_config = DRD_CONFIG_DYNAMIC;
  87                 break;
  88         case USB_ROLE_HOST:
  89                 val &= ~SW_IDPIN;
  90                 val &= ~SW_VBUS_VALID;
  91                 drd_config = DRD_CONFIG_STATIC_HOST;
  92                 break;
  93         case USB_ROLE_DEVICE:
  94                 val |= SW_IDPIN;
  95                 val |= SW_VBUS_VALID;
  96                 drd_config = DRD_CONFIG_STATIC_DEVICE;
  97                 break;
  98         }
  99         val |= SW_IDPIN_EN;
 100         if (data->enable_sw_switch) {
 101                 val &= ~DRD_CONFIG_MASK;
 102                 val |= SW_SWITCH_EN | drd_config;
 103         }
 104         writel(val, data->base + DUAL_ROLE_CFG0);
 105 
 106         acpi_release_global_lock(glk);
 107 
 108         /* In most case it takes about 600ms to finish mode switching */
 109         timeout = jiffies + msecs_to_jiffies(DUAL_ROLE_CFG1_POLL_TIMEOUT);
 110 
 111         /* Polling on CFG1 register to confirm mode switch.*/
 112         do {
 113                 val = readl(data->base + DUAL_ROLE_CFG1);
 114                 if (!!(val & HOST_MODE) == (role == USB_ROLE_HOST)) {
 115                         pm_runtime_put(dev);
 116                         return 0;
 117                 }
 118 
 119                 /* Interval for polling is set to about 5 - 10 ms */
 120                 usleep_range(5000, 10000);
 121         } while (time_before(jiffies, timeout));
 122 
 123         pm_runtime_put(dev);
 124 
 125         dev_warn(dev, "Timeout waiting for role-switch\n");
 126         return -ETIMEDOUT;
 127 }
 128 
 129 static enum usb_role intel_xhci_usb_get_role(struct device *dev)
 130 {
 131         struct intel_xhci_usb_data *data = dev_get_drvdata(dev);
 132         enum usb_role role;
 133         u32 val;
 134 
 135         pm_runtime_get_sync(dev);
 136         val = readl(data->base + DUAL_ROLE_CFG0);
 137         pm_runtime_put(dev);
 138 
 139         if (!(val & SW_IDPIN))
 140                 role = USB_ROLE_HOST;
 141         else if (val & SW_VBUS_VALID)
 142                 role = USB_ROLE_DEVICE;
 143         else
 144                 role = USB_ROLE_NONE;
 145 
 146         return role;
 147 }
 148 
 149 static int intel_xhci_usb_probe(struct platform_device *pdev)
 150 {
 151         struct usb_role_switch_desc sw_desc = { };
 152         struct device *dev = &pdev->dev;
 153         struct intel_xhci_usb_data *data;
 154         struct resource *res;
 155         int ret;
 156 
 157         data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
 158         if (!data)
 159                 return -ENOMEM;
 160 
 161         res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 162         if (!res)
 163                 return -EINVAL;
 164         data->base = devm_ioremap_nocache(dev, res->start, resource_size(res));
 165         if (!data->base)
 166                 return -ENOMEM;
 167 
 168         platform_set_drvdata(pdev, data);
 169 
 170         ret = software_node_register(&intel_xhci_usb_node);
 171         if (ret)
 172                 return ret;
 173 
 174         sw_desc.set = intel_xhci_usb_set_role,
 175         sw_desc.get = intel_xhci_usb_get_role,
 176         sw_desc.allow_userspace_control = true,
 177         sw_desc.fwnode = software_node_fwnode(&intel_xhci_usb_node);
 178 
 179         data->enable_sw_switch = !device_property_read_bool(dev,
 180                                                 "sw_switch_disable");
 181 
 182         data->role_sw = usb_role_switch_register(dev, &sw_desc);
 183         if (IS_ERR(data->role_sw)) {
 184                 fwnode_handle_put(sw_desc.fwnode);
 185                 return PTR_ERR(data->role_sw);
 186         }
 187 
 188         pm_runtime_set_active(dev);
 189         pm_runtime_enable(dev);
 190 
 191         return 0;
 192 }
 193 
 194 static int intel_xhci_usb_remove(struct platform_device *pdev)
 195 {
 196         struct intel_xhci_usb_data *data = platform_get_drvdata(pdev);
 197 
 198         pm_runtime_disable(&pdev->dev);
 199 
 200         usb_role_switch_unregister(data->role_sw);
 201         fwnode_handle_put(software_node_fwnode(&intel_xhci_usb_node));
 202 
 203         return 0;
 204 }
 205 
 206 static const struct platform_device_id intel_xhci_usb_table[] = {
 207         { .name = DRV_NAME },
 208         {}
 209 };
 210 MODULE_DEVICE_TABLE(platform, intel_xhci_usb_table);
 211 
 212 static struct platform_driver intel_xhci_usb_driver = {
 213         .driver = {
 214                 .name = DRV_NAME,
 215         },
 216         .id_table = intel_xhci_usb_table,
 217         .probe = intel_xhci_usb_probe,
 218         .remove = intel_xhci_usb_remove,
 219 };
 220 
 221 module_platform_driver(intel_xhci_usb_driver);
 222 
 223 MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
 224 MODULE_DESCRIPTION("Intel XHCI USB role switch driver");
 225 MODULE_LICENSE("GPL");

/* [<][>][^][v][top][bottom][index][help] */