root/drivers/usb/roles/intel-xhci-usb-role-switch.c
// 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/pm_runtime.h>
#include <linux/property.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 SW_SWITCH_EN                    BIT(16)

#define DRD_CONFIG_DYNAMIC              0
#define DRD_CONFIG_STATIC_HOST          1
#define DRD_CONFIG_STATIC_DEVICE        2
#define DRD_CONFIG_MASK                 3

#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 device *dev;
        struct usb_role_switch *role_sw;
        void __iomem *base;
        bool enable_sw_switch;
};

static const struct software_node intel_xhci_usb_node = {
        "intel-xhci-usb-sw",
};

static int intel_xhci_usb_set_role(struct usb_role_switch *sw,
                                   enum usb_role role)
{
        struct intel_xhci_usb_data *data = usb_role_switch_get_drvdata(sw);
        unsigned long timeout;
        acpi_status status;
        u32 glk, val;
        u32 drd_config = DRD_CONFIG_DYNAMIC;

        /*
         * 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(data->dev, "Error could not acquire lock\n");
                return -EIO;
        }

        pm_runtime_get_sync(data->dev);

        /*
         * Set idpin value as requested.
         * Since some devices rely on firmware setting DRD_CONFIG and
         * SW_SWITCH_EN bits to be zero for role switch,
         * do not set these bits for those devices.
         */
        val = readl(data->base + DUAL_ROLE_CFG0);
        switch (role) {
        case USB_ROLE_NONE:
                val |= SW_IDPIN;
                val &= ~SW_VBUS_VALID;
                drd_config = DRD_CONFIG_DYNAMIC;
                break;
        case USB_ROLE_HOST:
                val &= ~SW_IDPIN;
                val &= ~SW_VBUS_VALID;
                drd_config = DRD_CONFIG_STATIC_HOST;
                break;
        case USB_ROLE_DEVICE:
                val |= SW_IDPIN;
                val |= SW_VBUS_VALID;
                drd_config = DRD_CONFIG_STATIC_DEVICE;
                break;
        }
        val |= SW_IDPIN_EN;
        if (data->enable_sw_switch) {
                val &= ~DRD_CONFIG_MASK;
                val |= SW_SWITCH_EN | drd_config;
        }
        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)) {
                        pm_runtime_put(data->dev);
                        return 0;
                }

                /* Interval for polling is set to about 5 - 10 ms */
                usleep_range(5000, 10000);
        } while (time_before(jiffies, timeout));

        pm_runtime_put(data->dev);

        dev_warn(data->dev, "Timeout waiting for role-switch\n");
        return -ETIMEDOUT;
}

static enum usb_role intel_xhci_usb_get_role(struct usb_role_switch *sw)
{
        struct intel_xhci_usb_data *data = usb_role_switch_get_drvdata(sw);
        enum usb_role role;
        u32 val;

        pm_runtime_get_sync(data->dev);
        val = readl(data->base + DUAL_ROLE_CFG0);
        pm_runtime_put(data->dev);

        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 int intel_xhci_usb_probe(struct platform_device *pdev)
{
        struct usb_role_switch_desc sw_desc = { };
        struct device *dev = &pdev->dev;
        struct intel_xhci_usb_data *data;
        struct resource *res;
        int ret;

        data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
        if (!data)
                return -ENOMEM;

        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        if (!res)
                return -EINVAL;
        data->base = devm_ioremap(dev, res->start, resource_size(res));
        if (!data->base)
                return -ENOMEM;

        platform_set_drvdata(pdev, data);

        ret = software_node_register(&intel_xhci_usb_node);
        if (ret)
                return ret;

        sw_desc.set = intel_xhci_usb_set_role,
        sw_desc.get = intel_xhci_usb_get_role,
        sw_desc.allow_userspace_control = true,
        sw_desc.fwnode = software_node_fwnode(&intel_xhci_usb_node);
        sw_desc.driver_data = data;

        data->dev = dev;
        data->enable_sw_switch = !device_property_read_bool(dev,
                                                "sw_switch_disable");

        data->role_sw = usb_role_switch_register(dev, &sw_desc);
        if (IS_ERR(data->role_sw)) {
                fwnode_handle_put(sw_desc.fwnode);
                return PTR_ERR(data->role_sw);
        }

        pm_runtime_set_active(dev);
        pm_runtime_enable(dev);

        return 0;
}

static void intel_xhci_usb_remove(struct platform_device *pdev)
{
        struct intel_xhci_usb_data *data = platform_get_drvdata(pdev);

        pm_runtime_disable(&pdev->dev);

        usb_role_switch_unregister(data->role_sw);
        fwnode_handle_put(software_node_fwnode(&intel_xhci_usb_node));
}

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