| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * USB4 port device |
| * |
| * Copyright (C) 2021, Intel Corporation |
| * Author: Mika Westerberg <mika.westerberg@linux.intel.com> |
| */ |
| |
| #include <linux/pm_runtime.h> |
| #include <linux/component.h> |
| #include <linux/property.h> |
| |
| #include "tb.h" |
| |
| static int connector_bind(struct device *dev, struct device *connector, void *data) |
| { |
| int ret; |
| |
| ret = sysfs_create_link(&dev->kobj, &connector->kobj, "connector"); |
| if (ret) |
| return ret; |
| |
| ret = sysfs_create_link(&connector->kobj, &dev->kobj, dev_name(dev)); |
| if (ret) |
| sysfs_remove_link(&dev->kobj, "connector"); |
| |
| return ret; |
| } |
| |
| static void connector_unbind(struct device *dev, struct device *connector, void *data) |
| { |
| sysfs_remove_link(&connector->kobj, dev_name(dev)); |
| sysfs_remove_link(&dev->kobj, "connector"); |
| } |
| |
| static const struct component_ops connector_ops = { |
| .bind = connector_bind, |
| .unbind = connector_unbind, |
| }; |
| |
| static ssize_t link_show(struct device *dev, struct device_attribute *attr, |
| char *buf) |
| { |
| struct usb4_port *usb4 = tb_to_usb4_port_device(dev); |
| struct tb_port *port = usb4->port; |
| struct tb *tb = port->sw->tb; |
| const char *link; |
| |
| if (mutex_lock_interruptible(&tb->lock)) |
| return -ERESTARTSYS; |
| |
| if (tb_is_upstream_port(port)) |
| link = port->sw->link_usb4 ? "usb4" : "tbt"; |
| else if (tb_port_has_remote(port)) |
| link = port->remote->sw->link_usb4 ? "usb4" : "tbt"; |
| else if (port->xdomain) |
| link = port->xdomain->link_usb4 ? "usb4" : "tbt"; |
| else |
| link = "none"; |
| |
| mutex_unlock(&tb->lock); |
| |
| return sysfs_emit(buf, "%s\n", link); |
| } |
| static DEVICE_ATTR_RO(link); |
| |
| static struct attribute *common_attrs[] = { |
| &dev_attr_link.attr, |
| NULL |
| }; |
| |
| static const struct attribute_group common_group = { |
| .attrs = common_attrs, |
| }; |
| |
| static int usb4_port_offline(struct usb4_port *usb4) |
| { |
| struct tb_port *port = usb4->port; |
| int ret; |
| |
| ret = tb_acpi_power_on_retimers(port); |
| if (ret) |
| return ret; |
| |
| ret = usb4_port_router_offline(port); |
| if (ret) { |
| tb_acpi_power_off_retimers(port); |
| return ret; |
| } |
| |
| ret = tb_retimer_scan(port, false); |
| if (ret) { |
| usb4_port_router_online(port); |
| tb_acpi_power_off_retimers(port); |
| } |
| |
| return ret; |
| } |
| |
| static void usb4_port_online(struct usb4_port *usb4) |
| { |
| struct tb_port *port = usb4->port; |
| |
| usb4_port_router_online(port); |
| tb_acpi_power_off_retimers(port); |
| } |
| |
| static ssize_t offline_show(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct usb4_port *usb4 = tb_to_usb4_port_device(dev); |
| |
| return sysfs_emit(buf, "%d\n", usb4->offline); |
| } |
| |
| static ssize_t offline_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct usb4_port *usb4 = tb_to_usb4_port_device(dev); |
| struct tb_port *port = usb4->port; |
| struct tb *tb = port->sw->tb; |
| bool val; |
| int ret; |
| |
| ret = kstrtobool(buf, &val); |
| if (ret) |
| return ret; |
| |
| pm_runtime_get_sync(&usb4->dev); |
| |
| if (mutex_lock_interruptible(&tb->lock)) { |
| ret = -ERESTARTSYS; |
| goto out_rpm; |
| } |
| |
| if (val == usb4->offline) |
| goto out_unlock; |
| |
| /* Offline mode works only for ports that are not connected */ |
| if (tb_port_has_remote(port)) { |
| ret = -EBUSY; |
| goto out_unlock; |
| } |
| |
| if (val) { |
| ret = usb4_port_offline(usb4); |
| if (ret) |
| goto out_unlock; |
| } else { |
| usb4_port_online(usb4); |
| tb_retimer_remove_all(port); |
| } |
| |
| usb4->offline = val; |
| tb_port_dbg(port, "%s offline mode\n", val ? "enter" : "exit"); |
| |
| out_unlock: |
| mutex_unlock(&tb->lock); |
| out_rpm: |
| pm_runtime_mark_last_busy(&usb4->dev); |
| pm_runtime_put_autosuspend(&usb4->dev); |
| |
| return ret ? ret : count; |
| } |
| static DEVICE_ATTR_RW(offline); |
| |
| static ssize_t rescan_store(struct device *dev, |
| struct device_attribute *attr, const char *buf, size_t count) |
| { |
| struct usb4_port *usb4 = tb_to_usb4_port_device(dev); |
| struct tb_port *port = usb4->port; |
| struct tb *tb = port->sw->tb; |
| bool val; |
| int ret; |
| |
| ret = kstrtobool(buf, &val); |
| if (ret) |
| return ret; |
| |
| if (!val) |
| return count; |
| |
| pm_runtime_get_sync(&usb4->dev); |
| |
| if (mutex_lock_interruptible(&tb->lock)) { |
| ret = -ERESTARTSYS; |
| goto out_rpm; |
| } |
| |
| /* Must be in offline mode already */ |
| if (!usb4->offline) { |
| ret = -EINVAL; |
| goto out_unlock; |
| } |
| |
| tb_retimer_remove_all(port); |
| ret = tb_retimer_scan(port, true); |
| |
| out_unlock: |
| mutex_unlock(&tb->lock); |
| out_rpm: |
| pm_runtime_mark_last_busy(&usb4->dev); |
| pm_runtime_put_autosuspend(&usb4->dev); |
| |
| return ret ? ret : count; |
| } |
| static DEVICE_ATTR_WO(rescan); |
| |
| static struct attribute *service_attrs[] = { |
| &dev_attr_offline.attr, |
| &dev_attr_rescan.attr, |
| NULL |
| }; |
| |
| static umode_t service_attr_is_visible(struct kobject *kobj, |
| struct attribute *attr, int n) |
| { |
| struct device *dev = kobj_to_dev(kobj); |
| struct usb4_port *usb4 = tb_to_usb4_port_device(dev); |
| |
| /* |
| * Always need some platform help to cycle the modes so that |
| * retimers can be accessed through the sideband. |
| */ |
| return usb4->can_offline ? attr->mode : 0; |
| } |
| |
| static const struct attribute_group service_group = { |
| .attrs = service_attrs, |
| .is_visible = service_attr_is_visible, |
| }; |
| |
| static const struct attribute_group *usb4_port_device_groups[] = { |
| &common_group, |
| &service_group, |
| NULL |
| }; |
| |
| static void usb4_port_device_release(struct device *dev) |
| { |
| struct usb4_port *usb4 = container_of(dev, struct usb4_port, dev); |
| |
| kfree(usb4); |
| } |
| |
| struct device_type usb4_port_device_type = { |
| .name = "usb4_port", |
| .groups = usb4_port_device_groups, |
| .release = usb4_port_device_release, |
| }; |
| |
| /** |
| * usb4_port_device_add() - Add USB4 port device |
| * @port: Lane 0 adapter port to add the USB4 port |
| * |
| * Creates and registers a USB4 port device for @port. Returns the new |
| * USB4 port device pointer or ERR_PTR() in case of error. |
| */ |
| struct usb4_port *usb4_port_device_add(struct tb_port *port) |
| { |
| struct usb4_port *usb4; |
| int ret; |
| |
| usb4 = kzalloc(sizeof(*usb4), GFP_KERNEL); |
| if (!usb4) |
| return ERR_PTR(-ENOMEM); |
| |
| usb4->port = port; |
| usb4->dev.type = &usb4_port_device_type; |
| usb4->dev.parent = &port->sw->dev; |
| dev_set_name(&usb4->dev, "usb4_port%d", port->port); |
| |
| ret = device_register(&usb4->dev); |
| if (ret) { |
| put_device(&usb4->dev); |
| return ERR_PTR(ret); |
| } |
| |
| if (dev_fwnode(&usb4->dev)) { |
| ret = component_add(&usb4->dev, &connector_ops); |
| if (ret) { |
| dev_err(&usb4->dev, "failed to add component\n"); |
| device_unregister(&usb4->dev); |
| } |
| } |
| |
| if (!tb_is_upstream_port(port)) |
| device_set_wakeup_capable(&usb4->dev, true); |
| |
| pm_runtime_no_callbacks(&usb4->dev); |
| pm_runtime_set_active(&usb4->dev); |
| pm_runtime_enable(&usb4->dev); |
| pm_runtime_set_autosuspend_delay(&usb4->dev, TB_AUTOSUSPEND_DELAY); |
| pm_runtime_mark_last_busy(&usb4->dev); |
| pm_runtime_use_autosuspend(&usb4->dev); |
| |
| return usb4; |
| } |
| |
| /** |
| * usb4_port_device_remove() - Removes USB4 port device |
| * @usb4: USB4 port device |
| * |
| * Unregisters the USB4 port device from the system. The device will be |
| * released when the last reference is dropped. |
| */ |
| void usb4_port_device_remove(struct usb4_port *usb4) |
| { |
| if (dev_fwnode(&usb4->dev)) |
| component_del(&usb4->dev, &connector_ops); |
| device_unregister(&usb4->dev); |
| } |
| |
| /** |
| * usb4_port_device_resume() - Resumes USB4 port device |
| * @usb4: USB4 port device |
| * |
| * Used to resume USB4 port device after sleep state. |
| */ |
| int usb4_port_device_resume(struct usb4_port *usb4) |
| { |
| return usb4->offline ? usb4_port_offline(usb4) : 0; |
| } |