162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * USB-ACPI glue code
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright 2012 Red Hat <mjg@redhat.com>
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci#include <linux/module.h>
862306a36Sopenharmony_ci#include <linux/usb.h>
962306a36Sopenharmony_ci#include <linux/device.h>
1062306a36Sopenharmony_ci#include <linux/errno.h>
1162306a36Sopenharmony_ci#include <linux/kernel.h>
1262306a36Sopenharmony_ci#include <linux/acpi.h>
1362306a36Sopenharmony_ci#include <linux/pci.h>
1462306a36Sopenharmony_ci#include <linux/usb/hcd.h>
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#include "hub.h"
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci/**
1962306a36Sopenharmony_ci * usb_acpi_power_manageable - check whether usb port has
2062306a36Sopenharmony_ci * acpi power resource.
2162306a36Sopenharmony_ci * @hdev: USB device belonging to the usb hub
2262306a36Sopenharmony_ci * @index: port index based zero
2362306a36Sopenharmony_ci *
2462306a36Sopenharmony_ci * Return true if the port has acpi power resource and false if no.
2562306a36Sopenharmony_ci */
2662306a36Sopenharmony_cibool usb_acpi_power_manageable(struct usb_device *hdev, int index)
2762306a36Sopenharmony_ci{
2862306a36Sopenharmony_ci	acpi_handle port_handle;
2962306a36Sopenharmony_ci	int port1 = index + 1;
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci	port_handle = usb_get_hub_port_acpi_handle(hdev,
3262306a36Sopenharmony_ci		port1);
3362306a36Sopenharmony_ci	if (port_handle)
3462306a36Sopenharmony_ci		return acpi_bus_power_manageable(port_handle);
3562306a36Sopenharmony_ci	else
3662306a36Sopenharmony_ci		return false;
3762306a36Sopenharmony_ci}
3862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(usb_acpi_power_manageable);
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci#define UUID_USB_CONTROLLER_DSM "ce2ee385-00e6-48cb-9f05-2edb927c4899"
4162306a36Sopenharmony_ci#define USB_DSM_DISABLE_U1_U2_FOR_PORT	5
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci/**
4462306a36Sopenharmony_ci * usb_acpi_port_lpm_incapable - check if lpm should be disabled for a port.
4562306a36Sopenharmony_ci * @hdev: USB device belonging to the usb hub
4662306a36Sopenharmony_ci * @index: zero based port index
4762306a36Sopenharmony_ci *
4862306a36Sopenharmony_ci * Some USB3 ports may not support USB3 link power management U1/U2 states
4962306a36Sopenharmony_ci * due to different retimer setup. ACPI provides _DSM method which returns 0x01
5062306a36Sopenharmony_ci * if U1 and U2 states should be disabled. Evaluate _DSM with:
5162306a36Sopenharmony_ci * Arg0: UUID = ce2ee385-00e6-48cb-9f05-2edb927c4899
5262306a36Sopenharmony_ci * Arg1: Revision ID = 0
5362306a36Sopenharmony_ci * Arg2: Function Index = 5
5462306a36Sopenharmony_ci * Arg3: (empty)
5562306a36Sopenharmony_ci *
5662306a36Sopenharmony_ci * Return 1 if USB3 port is LPM incapable, negative on error, otherwise 0
5762306a36Sopenharmony_ci */
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ciint usb_acpi_port_lpm_incapable(struct usb_device *hdev, int index)
6062306a36Sopenharmony_ci{
6162306a36Sopenharmony_ci	union acpi_object *obj;
6262306a36Sopenharmony_ci	acpi_handle port_handle;
6362306a36Sopenharmony_ci	int port1 = index + 1;
6462306a36Sopenharmony_ci	guid_t guid;
6562306a36Sopenharmony_ci	int ret;
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	ret = guid_parse(UUID_USB_CONTROLLER_DSM, &guid);
6862306a36Sopenharmony_ci	if (ret)
6962306a36Sopenharmony_ci		return ret;
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	port_handle = usb_get_hub_port_acpi_handle(hdev, port1);
7262306a36Sopenharmony_ci	if (!port_handle) {
7362306a36Sopenharmony_ci		dev_dbg(&hdev->dev, "port-%d no acpi handle\n", port1);
7462306a36Sopenharmony_ci		return -ENODEV;
7562306a36Sopenharmony_ci	}
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	if (!acpi_check_dsm(port_handle, &guid, 0,
7862306a36Sopenharmony_ci			    BIT(USB_DSM_DISABLE_U1_U2_FOR_PORT))) {
7962306a36Sopenharmony_ci		dev_dbg(&hdev->dev, "port-%d no _DSM function %d\n",
8062306a36Sopenharmony_ci			port1, USB_DSM_DISABLE_U1_U2_FOR_PORT);
8162306a36Sopenharmony_ci		return -ENODEV;
8262306a36Sopenharmony_ci	}
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	obj = acpi_evaluate_dsm_typed(port_handle, &guid, 0,
8562306a36Sopenharmony_ci				      USB_DSM_DISABLE_U1_U2_FOR_PORT, NULL,
8662306a36Sopenharmony_ci				      ACPI_TYPE_INTEGER);
8762306a36Sopenharmony_ci	if (!obj) {
8862306a36Sopenharmony_ci		dev_dbg(&hdev->dev, "evaluate port-%d _DSM failed\n", port1);
8962306a36Sopenharmony_ci		return -EINVAL;
9062306a36Sopenharmony_ci	}
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	if (obj->integer.value == 0x01)
9362306a36Sopenharmony_ci		ret = 1;
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	ACPI_FREE(obj);
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci	return ret;
9862306a36Sopenharmony_ci}
9962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(usb_acpi_port_lpm_incapable);
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci/**
10262306a36Sopenharmony_ci * usb_acpi_set_power_state - control usb port's power via acpi power
10362306a36Sopenharmony_ci * resource
10462306a36Sopenharmony_ci * @hdev: USB device belonging to the usb hub
10562306a36Sopenharmony_ci * @index: port index based zero
10662306a36Sopenharmony_ci * @enable: power state expected to be set
10762306a36Sopenharmony_ci *
10862306a36Sopenharmony_ci * Notice to use usb_acpi_power_manageable() to check whether the usb port
10962306a36Sopenharmony_ci * has acpi power resource before invoking this function.
11062306a36Sopenharmony_ci *
11162306a36Sopenharmony_ci * Returns 0 on success, else negative errno.
11262306a36Sopenharmony_ci */
11362306a36Sopenharmony_ciint usb_acpi_set_power_state(struct usb_device *hdev, int index, bool enable)
11462306a36Sopenharmony_ci{
11562306a36Sopenharmony_ci	struct usb_hub *hub = usb_hub_to_struct_hub(hdev);
11662306a36Sopenharmony_ci	struct usb_port *port_dev;
11762306a36Sopenharmony_ci	acpi_handle port_handle;
11862306a36Sopenharmony_ci	unsigned char state;
11962306a36Sopenharmony_ci	int port1 = index + 1;
12062306a36Sopenharmony_ci	int error = -EINVAL;
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	if (!hub)
12362306a36Sopenharmony_ci		return -ENODEV;
12462306a36Sopenharmony_ci	port_dev = hub->ports[port1 - 1];
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	port_handle = (acpi_handle) usb_get_hub_port_acpi_handle(hdev, port1);
12762306a36Sopenharmony_ci	if (!port_handle)
12862306a36Sopenharmony_ci		return error;
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	if (enable)
13162306a36Sopenharmony_ci		state = ACPI_STATE_D0;
13262306a36Sopenharmony_ci	else
13362306a36Sopenharmony_ci		state = ACPI_STATE_D3_COLD;
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	error = acpi_bus_set_power(port_handle, state);
13662306a36Sopenharmony_ci	if (!error)
13762306a36Sopenharmony_ci		dev_dbg(&port_dev->dev, "acpi: power was set to %d\n", enable);
13862306a36Sopenharmony_ci	else
13962306a36Sopenharmony_ci		dev_dbg(&port_dev->dev, "acpi: power failed to be set\n");
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci	return error;
14262306a36Sopenharmony_ci}
14362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(usb_acpi_set_power_state);
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_cistatic enum usb_port_connect_type usb_acpi_get_connect_type(acpi_handle handle,
14662306a36Sopenharmony_ci		struct acpi_pld_info *pld)
14762306a36Sopenharmony_ci{
14862306a36Sopenharmony_ci	enum usb_port_connect_type connect_type = USB_PORT_CONNECT_TYPE_UNKNOWN;
14962306a36Sopenharmony_ci	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
15062306a36Sopenharmony_ci	union acpi_object *upc = NULL;
15162306a36Sopenharmony_ci	acpi_status status;
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci	/*
15462306a36Sopenharmony_ci	 * According to 9.14 in ACPI Spec 6.2. _PLD indicates whether usb port
15562306a36Sopenharmony_ci	 * is user visible and _UPC indicates whether it is connectable. If
15662306a36Sopenharmony_ci	 * the port was visible and connectable, it could be freely connected
15762306a36Sopenharmony_ci	 * and disconnected with USB devices. If no visible and connectable,
15862306a36Sopenharmony_ci	 * a usb device is directly hard-wired to the port. If no visible and
15962306a36Sopenharmony_ci	 * no connectable, the port would be not used.
16062306a36Sopenharmony_ci	 */
16162306a36Sopenharmony_ci	status = acpi_evaluate_object(handle, "_UPC", NULL, &buffer);
16262306a36Sopenharmony_ci	if (ACPI_FAILURE(status))
16362306a36Sopenharmony_ci		goto out;
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	upc = buffer.pointer;
16662306a36Sopenharmony_ci	if (!upc || (upc->type != ACPI_TYPE_PACKAGE) || upc->package.count != 4)
16762306a36Sopenharmony_ci		goto out;
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci	if (upc->package.elements[0].integer.value)
17062306a36Sopenharmony_ci		if (pld->user_visible)
17162306a36Sopenharmony_ci			connect_type = USB_PORT_CONNECT_TYPE_HOT_PLUG;
17262306a36Sopenharmony_ci		else
17362306a36Sopenharmony_ci			connect_type = USB_PORT_CONNECT_TYPE_HARD_WIRED;
17462306a36Sopenharmony_ci	else if (!pld->user_visible)
17562306a36Sopenharmony_ci		connect_type = USB_PORT_NOT_USED;
17662306a36Sopenharmony_ciout:
17762306a36Sopenharmony_ci	kfree(upc);
17862306a36Sopenharmony_ci	return connect_type;
17962306a36Sopenharmony_ci}
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci/*
18362306a36Sopenharmony_ci * Private to usb-acpi, all the core needs to know is that
18462306a36Sopenharmony_ci * port_dev->location is non-zero when it has been set by the firmware.
18562306a36Sopenharmony_ci */
18662306a36Sopenharmony_ci#define USB_ACPI_LOCATION_VALID (1 << 31)
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_cistatic struct acpi_device *
18962306a36Sopenharmony_ciusb_acpi_get_companion_for_port(struct usb_port *port_dev)
19062306a36Sopenharmony_ci{
19162306a36Sopenharmony_ci	struct usb_device *udev;
19262306a36Sopenharmony_ci	struct acpi_device *adev;
19362306a36Sopenharmony_ci	acpi_handle *parent_handle;
19462306a36Sopenharmony_ci	int port1;
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci	/* Get the struct usb_device point of port's hub */
19762306a36Sopenharmony_ci	udev = to_usb_device(port_dev->dev.parent->parent);
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci	/*
20062306a36Sopenharmony_ci	 * The root hub ports' parent is the root hub. The non-root-hub
20162306a36Sopenharmony_ci	 * ports' parent is the parent hub port which the hub is
20262306a36Sopenharmony_ci	 * connected to.
20362306a36Sopenharmony_ci	 */
20462306a36Sopenharmony_ci	if (!udev->parent) {
20562306a36Sopenharmony_ci		adev = ACPI_COMPANION(&udev->dev);
20662306a36Sopenharmony_ci		port1 = usb_hcd_find_raw_port_number(bus_to_hcd(udev->bus),
20762306a36Sopenharmony_ci						     port_dev->portnum);
20862306a36Sopenharmony_ci	} else {
20962306a36Sopenharmony_ci		parent_handle = usb_get_hub_port_acpi_handle(udev->parent,
21062306a36Sopenharmony_ci							     udev->portnum);
21162306a36Sopenharmony_ci		if (!parent_handle)
21262306a36Sopenharmony_ci			return NULL;
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci		adev = acpi_fetch_acpi_dev(parent_handle);
21562306a36Sopenharmony_ci		port1 = port_dev->portnum;
21662306a36Sopenharmony_ci	}
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	return acpi_find_child_by_adr(adev, port1);
21962306a36Sopenharmony_ci}
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_cistatic struct acpi_device *
22262306a36Sopenharmony_ciusb_acpi_find_companion_for_port(struct usb_port *port_dev)
22362306a36Sopenharmony_ci{
22462306a36Sopenharmony_ci	struct acpi_device *adev;
22562306a36Sopenharmony_ci	struct acpi_pld_info *pld;
22662306a36Sopenharmony_ci	acpi_handle *handle;
22762306a36Sopenharmony_ci	acpi_status status;
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_ci	adev = usb_acpi_get_companion_for_port(port_dev);
23062306a36Sopenharmony_ci	if (!adev)
23162306a36Sopenharmony_ci		return NULL;
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci	handle = adev->handle;
23462306a36Sopenharmony_ci	status = acpi_get_physical_device_location(handle, &pld);
23562306a36Sopenharmony_ci	if (ACPI_SUCCESS(status) && pld) {
23662306a36Sopenharmony_ci		port_dev->location = USB_ACPI_LOCATION_VALID
23762306a36Sopenharmony_ci			| pld->group_token << 8 | pld->group_position;
23862306a36Sopenharmony_ci		port_dev->connect_type = usb_acpi_get_connect_type(handle, pld);
23962306a36Sopenharmony_ci		ACPI_FREE(pld);
24062306a36Sopenharmony_ci	}
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	return adev;
24362306a36Sopenharmony_ci}
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_cistatic struct acpi_device *
24662306a36Sopenharmony_ciusb_acpi_find_companion_for_device(struct usb_device *udev)
24762306a36Sopenharmony_ci{
24862306a36Sopenharmony_ci	struct acpi_device *adev;
24962306a36Sopenharmony_ci	struct usb_port *port_dev;
25062306a36Sopenharmony_ci	struct usb_hub *hub;
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci	if (!udev->parent) {
25362306a36Sopenharmony_ci		/*
25462306a36Sopenharmony_ci		 * root hub is only child (_ADR=0) under its parent, the HC.
25562306a36Sopenharmony_ci		 * sysdev pointer is the HC as seen from firmware.
25662306a36Sopenharmony_ci		 */
25762306a36Sopenharmony_ci		adev = ACPI_COMPANION(udev->bus->sysdev);
25862306a36Sopenharmony_ci		return acpi_find_child_device(adev, 0, false);
25962306a36Sopenharmony_ci	}
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_ci	hub = usb_hub_to_struct_hub(udev->parent);
26262306a36Sopenharmony_ci	if (!hub)
26362306a36Sopenharmony_ci		return NULL;
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_ci	/*
26662306a36Sopenharmony_ci	 * This is an embedded USB device connected to a port and such
26762306a36Sopenharmony_ci	 * devices share port's ACPI companion.
26862306a36Sopenharmony_ci	 */
26962306a36Sopenharmony_ci	port_dev = hub->ports[udev->portnum - 1];
27062306a36Sopenharmony_ci	return usb_acpi_get_companion_for_port(port_dev);
27162306a36Sopenharmony_ci}
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_cistatic struct acpi_device *usb_acpi_find_companion(struct device *dev)
27462306a36Sopenharmony_ci{
27562306a36Sopenharmony_ci	/*
27662306a36Sopenharmony_ci	 * The USB hierarchy like following:
27762306a36Sopenharmony_ci	 *
27862306a36Sopenharmony_ci	 * Device (EHC1)
27962306a36Sopenharmony_ci	 *	Device (HUBN)
28062306a36Sopenharmony_ci	 *		Device (PR01)
28162306a36Sopenharmony_ci	 *			Device (PR11)
28262306a36Sopenharmony_ci	 *			Device (PR12)
28362306a36Sopenharmony_ci	 *				Device (FN12)
28462306a36Sopenharmony_ci	 *				Device (FN13)
28562306a36Sopenharmony_ci	 *			Device (PR13)
28662306a36Sopenharmony_ci	 *			...
28762306a36Sopenharmony_ci	 * where HUBN is root hub, and PRNN are USB ports and devices
28862306a36Sopenharmony_ci	 * connected to them, and FNNN are individualk functions for
28962306a36Sopenharmony_ci	 * connected composite USB devices. PRNN and FNNN may contain
29062306a36Sopenharmony_ci	 * _CRS and other methods describing sideband resources for
29162306a36Sopenharmony_ci	 * the connected device.
29262306a36Sopenharmony_ci	 *
29362306a36Sopenharmony_ci	 * On the kernel side both root hub and embedded USB devices are
29462306a36Sopenharmony_ci	 * represented as instances of usb_device structure, and ports
29562306a36Sopenharmony_ci	 * are represented as usb_port structures, so the whole process
29662306a36Sopenharmony_ci	 * is split into 2 parts: finding companions for devices and
29762306a36Sopenharmony_ci	 * finding companions for ports.
29862306a36Sopenharmony_ci	 *
29962306a36Sopenharmony_ci	 * Note that we do not handle individual functions of composite
30062306a36Sopenharmony_ci	 * devices yet, for that we would need to assign companions to
30162306a36Sopenharmony_ci	 * devices corresponding to USB interfaces.
30262306a36Sopenharmony_ci	 */
30362306a36Sopenharmony_ci	if (is_usb_device(dev))
30462306a36Sopenharmony_ci		return usb_acpi_find_companion_for_device(to_usb_device(dev));
30562306a36Sopenharmony_ci	else if (is_usb_port(dev))
30662306a36Sopenharmony_ci		return usb_acpi_find_companion_for_port(to_usb_port(dev));
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_ci	return NULL;
30962306a36Sopenharmony_ci}
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_cistatic bool usb_acpi_bus_match(struct device *dev)
31262306a36Sopenharmony_ci{
31362306a36Sopenharmony_ci	return is_usb_device(dev) || is_usb_port(dev);
31462306a36Sopenharmony_ci}
31562306a36Sopenharmony_ci
31662306a36Sopenharmony_cistatic struct acpi_bus_type usb_acpi_bus = {
31762306a36Sopenharmony_ci	.name = "USB",
31862306a36Sopenharmony_ci	.match = usb_acpi_bus_match,
31962306a36Sopenharmony_ci	.find_companion = usb_acpi_find_companion,
32062306a36Sopenharmony_ci};
32162306a36Sopenharmony_ci
32262306a36Sopenharmony_ciint usb_acpi_register(void)
32362306a36Sopenharmony_ci{
32462306a36Sopenharmony_ci	return register_acpi_bus_type(&usb_acpi_bus);
32562306a36Sopenharmony_ci}
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_civoid usb_acpi_unregister(void)
32862306a36Sopenharmony_ci{
32962306a36Sopenharmony_ci	unregister_acpi_bus_type(&usb_acpi_bus);
33062306a36Sopenharmony_ci}
331