162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * USB4 port device
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2021, Intel Corporation
662306a36Sopenharmony_ci * Author: Mika Westerberg <mika.westerberg@linux.intel.com>
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/pm_runtime.h>
1062306a36Sopenharmony_ci#include <linux/component.h>
1162306a36Sopenharmony_ci#include <linux/property.h>
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#include "tb.h"
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_cistatic int connector_bind(struct device *dev, struct device *connector, void *data)
1662306a36Sopenharmony_ci{
1762306a36Sopenharmony_ci	int ret;
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci	ret = sysfs_create_link(&dev->kobj, &connector->kobj, "connector");
2062306a36Sopenharmony_ci	if (ret)
2162306a36Sopenharmony_ci		return ret;
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci	ret = sysfs_create_link(&connector->kobj, &dev->kobj, dev_name(dev));
2462306a36Sopenharmony_ci	if (ret)
2562306a36Sopenharmony_ci		sysfs_remove_link(&dev->kobj, "connector");
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci	return ret;
2862306a36Sopenharmony_ci}
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_cistatic void connector_unbind(struct device *dev, struct device *connector, void *data)
3162306a36Sopenharmony_ci{
3262306a36Sopenharmony_ci	sysfs_remove_link(&connector->kobj, dev_name(dev));
3362306a36Sopenharmony_ci	sysfs_remove_link(&dev->kobj, "connector");
3462306a36Sopenharmony_ci}
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_cistatic const struct component_ops connector_ops = {
3762306a36Sopenharmony_ci	.bind = connector_bind,
3862306a36Sopenharmony_ci	.unbind = connector_unbind,
3962306a36Sopenharmony_ci};
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_cistatic ssize_t link_show(struct device *dev, struct device_attribute *attr,
4262306a36Sopenharmony_ci			 char *buf)
4362306a36Sopenharmony_ci{
4462306a36Sopenharmony_ci	struct usb4_port *usb4 = tb_to_usb4_port_device(dev);
4562306a36Sopenharmony_ci	struct tb_port *port = usb4->port;
4662306a36Sopenharmony_ci	struct tb *tb = port->sw->tb;
4762306a36Sopenharmony_ci	const char *link;
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci	if (mutex_lock_interruptible(&tb->lock))
5062306a36Sopenharmony_ci		return -ERESTARTSYS;
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	if (tb_is_upstream_port(port))
5362306a36Sopenharmony_ci		link = port->sw->link_usb4 ? "usb4" : "tbt";
5462306a36Sopenharmony_ci	else if (tb_port_has_remote(port))
5562306a36Sopenharmony_ci		link = port->remote->sw->link_usb4 ? "usb4" : "tbt";
5662306a36Sopenharmony_ci	else if (port->xdomain)
5762306a36Sopenharmony_ci		link = port->xdomain->link_usb4 ? "usb4" : "tbt";
5862306a36Sopenharmony_ci	else
5962306a36Sopenharmony_ci		link = "none";
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	mutex_unlock(&tb->lock);
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	return sysfs_emit(buf, "%s\n", link);
6462306a36Sopenharmony_ci}
6562306a36Sopenharmony_cistatic DEVICE_ATTR_RO(link);
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_cistatic struct attribute *common_attrs[] = {
6862306a36Sopenharmony_ci	&dev_attr_link.attr,
6962306a36Sopenharmony_ci	NULL
7062306a36Sopenharmony_ci};
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_cistatic const struct attribute_group common_group = {
7362306a36Sopenharmony_ci	.attrs = common_attrs,
7462306a36Sopenharmony_ci};
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_cistatic int usb4_port_offline(struct usb4_port *usb4)
7762306a36Sopenharmony_ci{
7862306a36Sopenharmony_ci	struct tb_port *port = usb4->port;
7962306a36Sopenharmony_ci	int ret;
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	ret = tb_acpi_power_on_retimers(port);
8262306a36Sopenharmony_ci	if (ret)
8362306a36Sopenharmony_ci		return ret;
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	ret = usb4_port_router_offline(port);
8662306a36Sopenharmony_ci	if (ret) {
8762306a36Sopenharmony_ci		tb_acpi_power_off_retimers(port);
8862306a36Sopenharmony_ci		return ret;
8962306a36Sopenharmony_ci	}
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	ret = tb_retimer_scan(port, false);
9262306a36Sopenharmony_ci	if (ret) {
9362306a36Sopenharmony_ci		usb4_port_router_online(port);
9462306a36Sopenharmony_ci		tb_acpi_power_off_retimers(port);
9562306a36Sopenharmony_ci	}
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci	return ret;
9862306a36Sopenharmony_ci}
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_cistatic void usb4_port_online(struct usb4_port *usb4)
10162306a36Sopenharmony_ci{
10262306a36Sopenharmony_ci	struct tb_port *port = usb4->port;
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	usb4_port_router_online(port);
10562306a36Sopenharmony_ci	tb_acpi_power_off_retimers(port);
10662306a36Sopenharmony_ci}
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_cistatic ssize_t offline_show(struct device *dev,
10962306a36Sopenharmony_ci	struct device_attribute *attr, char *buf)
11062306a36Sopenharmony_ci{
11162306a36Sopenharmony_ci	struct usb4_port *usb4 = tb_to_usb4_port_device(dev);
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	return sysfs_emit(buf, "%d\n", usb4->offline);
11462306a36Sopenharmony_ci}
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_cistatic ssize_t offline_store(struct device *dev,
11762306a36Sopenharmony_ci	struct device_attribute *attr, const char *buf, size_t count)
11862306a36Sopenharmony_ci{
11962306a36Sopenharmony_ci	struct usb4_port *usb4 = tb_to_usb4_port_device(dev);
12062306a36Sopenharmony_ci	struct tb_port *port = usb4->port;
12162306a36Sopenharmony_ci	struct tb *tb = port->sw->tb;
12262306a36Sopenharmony_ci	bool val;
12362306a36Sopenharmony_ci	int ret;
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	ret = kstrtobool(buf, &val);
12662306a36Sopenharmony_ci	if (ret)
12762306a36Sopenharmony_ci		return ret;
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	pm_runtime_get_sync(&usb4->dev);
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	if (mutex_lock_interruptible(&tb->lock)) {
13262306a36Sopenharmony_ci		ret = -ERESTARTSYS;
13362306a36Sopenharmony_ci		goto out_rpm;
13462306a36Sopenharmony_ci	}
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	if (val == usb4->offline)
13762306a36Sopenharmony_ci		goto out_unlock;
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	/* Offline mode works only for ports that are not connected */
14062306a36Sopenharmony_ci	if (tb_port_has_remote(port)) {
14162306a36Sopenharmony_ci		ret = -EBUSY;
14262306a36Sopenharmony_ci		goto out_unlock;
14362306a36Sopenharmony_ci	}
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	if (val) {
14662306a36Sopenharmony_ci		ret = usb4_port_offline(usb4);
14762306a36Sopenharmony_ci		if (ret)
14862306a36Sopenharmony_ci			goto out_unlock;
14962306a36Sopenharmony_ci	} else {
15062306a36Sopenharmony_ci		usb4_port_online(usb4);
15162306a36Sopenharmony_ci		tb_retimer_remove_all(port);
15262306a36Sopenharmony_ci	}
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	usb4->offline = val;
15562306a36Sopenharmony_ci	tb_port_dbg(port, "%s offline mode\n", val ? "enter" : "exit");
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ciout_unlock:
15862306a36Sopenharmony_ci	mutex_unlock(&tb->lock);
15962306a36Sopenharmony_ciout_rpm:
16062306a36Sopenharmony_ci	pm_runtime_mark_last_busy(&usb4->dev);
16162306a36Sopenharmony_ci	pm_runtime_put_autosuspend(&usb4->dev);
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci	return ret ? ret : count;
16462306a36Sopenharmony_ci}
16562306a36Sopenharmony_cistatic DEVICE_ATTR_RW(offline);
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_cistatic ssize_t rescan_store(struct device *dev,
16862306a36Sopenharmony_ci	struct device_attribute *attr, const char *buf, size_t count)
16962306a36Sopenharmony_ci{
17062306a36Sopenharmony_ci	struct usb4_port *usb4 = tb_to_usb4_port_device(dev);
17162306a36Sopenharmony_ci	struct tb_port *port = usb4->port;
17262306a36Sopenharmony_ci	struct tb *tb = port->sw->tb;
17362306a36Sopenharmony_ci	bool val;
17462306a36Sopenharmony_ci	int ret;
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	ret = kstrtobool(buf, &val);
17762306a36Sopenharmony_ci	if (ret)
17862306a36Sopenharmony_ci		return ret;
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	if (!val)
18162306a36Sopenharmony_ci		return count;
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci	pm_runtime_get_sync(&usb4->dev);
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci	if (mutex_lock_interruptible(&tb->lock)) {
18662306a36Sopenharmony_ci		ret = -ERESTARTSYS;
18762306a36Sopenharmony_ci		goto out_rpm;
18862306a36Sopenharmony_ci	}
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci	/* Must be in offline mode already */
19162306a36Sopenharmony_ci	if (!usb4->offline) {
19262306a36Sopenharmony_ci		ret = -EINVAL;
19362306a36Sopenharmony_ci		goto out_unlock;
19462306a36Sopenharmony_ci	}
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci	tb_retimer_remove_all(port);
19762306a36Sopenharmony_ci	ret = tb_retimer_scan(port, true);
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ciout_unlock:
20062306a36Sopenharmony_ci	mutex_unlock(&tb->lock);
20162306a36Sopenharmony_ciout_rpm:
20262306a36Sopenharmony_ci	pm_runtime_mark_last_busy(&usb4->dev);
20362306a36Sopenharmony_ci	pm_runtime_put_autosuspend(&usb4->dev);
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci	return ret ? ret : count;
20662306a36Sopenharmony_ci}
20762306a36Sopenharmony_cistatic DEVICE_ATTR_WO(rescan);
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_cistatic struct attribute *service_attrs[] = {
21062306a36Sopenharmony_ci	&dev_attr_offline.attr,
21162306a36Sopenharmony_ci	&dev_attr_rescan.attr,
21262306a36Sopenharmony_ci	NULL
21362306a36Sopenharmony_ci};
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_cistatic umode_t service_attr_is_visible(struct kobject *kobj,
21662306a36Sopenharmony_ci				       struct attribute *attr, int n)
21762306a36Sopenharmony_ci{
21862306a36Sopenharmony_ci	struct device *dev = kobj_to_dev(kobj);
21962306a36Sopenharmony_ci	struct usb4_port *usb4 = tb_to_usb4_port_device(dev);
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	/*
22262306a36Sopenharmony_ci	 * Always need some platform help to cycle the modes so that
22362306a36Sopenharmony_ci	 * retimers can be accessed through the sideband.
22462306a36Sopenharmony_ci	 */
22562306a36Sopenharmony_ci	return usb4->can_offline ? attr->mode : 0;
22662306a36Sopenharmony_ci}
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_cistatic const struct attribute_group service_group = {
22962306a36Sopenharmony_ci	.attrs = service_attrs,
23062306a36Sopenharmony_ci	.is_visible = service_attr_is_visible,
23162306a36Sopenharmony_ci};
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_cistatic const struct attribute_group *usb4_port_device_groups[] = {
23462306a36Sopenharmony_ci	&common_group,
23562306a36Sopenharmony_ci	&service_group,
23662306a36Sopenharmony_ci	NULL
23762306a36Sopenharmony_ci};
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_cistatic void usb4_port_device_release(struct device *dev)
24062306a36Sopenharmony_ci{
24162306a36Sopenharmony_ci	struct usb4_port *usb4 = container_of(dev, struct usb4_port, dev);
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_ci	kfree(usb4);
24462306a36Sopenharmony_ci}
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_cistruct device_type usb4_port_device_type = {
24762306a36Sopenharmony_ci	.name = "usb4_port",
24862306a36Sopenharmony_ci	.groups = usb4_port_device_groups,
24962306a36Sopenharmony_ci	.release = usb4_port_device_release,
25062306a36Sopenharmony_ci};
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci/**
25362306a36Sopenharmony_ci * usb4_port_device_add() - Add USB4 port device
25462306a36Sopenharmony_ci * @port: Lane 0 adapter port to add the USB4 port
25562306a36Sopenharmony_ci *
25662306a36Sopenharmony_ci * Creates and registers a USB4 port device for @port. Returns the new
25762306a36Sopenharmony_ci * USB4 port device pointer or ERR_PTR() in case of error.
25862306a36Sopenharmony_ci */
25962306a36Sopenharmony_cistruct usb4_port *usb4_port_device_add(struct tb_port *port)
26062306a36Sopenharmony_ci{
26162306a36Sopenharmony_ci	struct usb4_port *usb4;
26262306a36Sopenharmony_ci	int ret;
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_ci	usb4 = kzalloc(sizeof(*usb4), GFP_KERNEL);
26562306a36Sopenharmony_ci	if (!usb4)
26662306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci	usb4->port = port;
26962306a36Sopenharmony_ci	usb4->dev.type = &usb4_port_device_type;
27062306a36Sopenharmony_ci	usb4->dev.parent = &port->sw->dev;
27162306a36Sopenharmony_ci	dev_set_name(&usb4->dev, "usb4_port%d", port->port);
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci	ret = device_register(&usb4->dev);
27462306a36Sopenharmony_ci	if (ret) {
27562306a36Sopenharmony_ci		put_device(&usb4->dev);
27662306a36Sopenharmony_ci		return ERR_PTR(ret);
27762306a36Sopenharmony_ci	}
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_ci	if (dev_fwnode(&usb4->dev)) {
28062306a36Sopenharmony_ci		ret = component_add(&usb4->dev, &connector_ops);
28162306a36Sopenharmony_ci		if (ret) {
28262306a36Sopenharmony_ci			dev_err(&usb4->dev, "failed to add component\n");
28362306a36Sopenharmony_ci			device_unregister(&usb4->dev);
28462306a36Sopenharmony_ci		}
28562306a36Sopenharmony_ci	}
28662306a36Sopenharmony_ci
28762306a36Sopenharmony_ci	if (!tb_is_upstream_port(port))
28862306a36Sopenharmony_ci		device_set_wakeup_capable(&usb4->dev, true);
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_ci	pm_runtime_no_callbacks(&usb4->dev);
29162306a36Sopenharmony_ci	pm_runtime_set_active(&usb4->dev);
29262306a36Sopenharmony_ci	pm_runtime_enable(&usb4->dev);
29362306a36Sopenharmony_ci	pm_runtime_set_autosuspend_delay(&usb4->dev, TB_AUTOSUSPEND_DELAY);
29462306a36Sopenharmony_ci	pm_runtime_mark_last_busy(&usb4->dev);
29562306a36Sopenharmony_ci	pm_runtime_use_autosuspend(&usb4->dev);
29662306a36Sopenharmony_ci
29762306a36Sopenharmony_ci	return usb4;
29862306a36Sopenharmony_ci}
29962306a36Sopenharmony_ci
30062306a36Sopenharmony_ci/**
30162306a36Sopenharmony_ci * usb4_port_device_remove() - Removes USB4 port device
30262306a36Sopenharmony_ci * @usb4: USB4 port device
30362306a36Sopenharmony_ci *
30462306a36Sopenharmony_ci * Unregisters the USB4 port device from the system. The device will be
30562306a36Sopenharmony_ci * released when the last reference is dropped.
30662306a36Sopenharmony_ci */
30762306a36Sopenharmony_civoid usb4_port_device_remove(struct usb4_port *usb4)
30862306a36Sopenharmony_ci{
30962306a36Sopenharmony_ci	if (dev_fwnode(&usb4->dev))
31062306a36Sopenharmony_ci		component_del(&usb4->dev, &connector_ops);
31162306a36Sopenharmony_ci	device_unregister(&usb4->dev);
31262306a36Sopenharmony_ci}
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_ci/**
31562306a36Sopenharmony_ci * usb4_port_device_resume() - Resumes USB4 port device
31662306a36Sopenharmony_ci * @usb4: USB4 port device
31762306a36Sopenharmony_ci *
31862306a36Sopenharmony_ci * Used to resume USB4 port device after sleep state.
31962306a36Sopenharmony_ci */
32062306a36Sopenharmony_ciint usb4_port_device_resume(struct usb4_port *usb4)
32162306a36Sopenharmony_ci{
32262306a36Sopenharmony_ci	return usb4->offline ? usb4_port_offline(usb4) : 0;
32362306a36Sopenharmony_ci}
324