usb: typec: Port mapping utility
Adding functions that can be used to link/unlink ports - USB ports, TBT3/USB4 ports, DisplayPorts and so on - to the USB Type-C connectors they are attached to inside a system. The symlink that is created for the port device is named "connector". Initially only ACPI is supported. ACPI port object shares the _PLD (Physical Location of Device) with the USB Type-C connector that it's attached to. Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com> Link: https://lore.kernel.org/r/20210407065555.88110-2-heikki.krogerus@linux.intel.com Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
4050f2683f
commit
ae196ddb0d
|
@ -1,6 +1,6 @@
|
||||||
# SPDX-License-Identifier: GPL-2.0
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
obj-$(CONFIG_TYPEC) += typec.o
|
obj-$(CONFIG_TYPEC) += typec.o
|
||||||
typec-y := class.o mux.o bus.o
|
typec-y := class.o mux.o bus.o port-mapper.o
|
||||||
obj-$(CONFIG_TYPEC) += altmodes/
|
obj-$(CONFIG_TYPEC) += altmodes/
|
||||||
obj-$(CONFIG_TYPEC_TCPM) += tcpm/
|
obj-$(CONFIG_TYPEC_TCPM) += tcpm/
|
||||||
obj-$(CONFIG_TYPEC_UCSI) += ucsi/
|
obj-$(CONFIG_TYPEC_UCSI) += ucsi/
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
static DEFINE_IDA(typec_index_ida);
|
static DEFINE_IDA(typec_index_ida);
|
||||||
|
|
||||||
static struct class typec_class = {
|
struct class typec_class = {
|
||||||
.name = "typec",
|
.name = "typec",
|
||||||
.owner = THIS_MODULE,
|
.owner = THIS_MODULE,
|
||||||
};
|
};
|
||||||
|
@ -1601,6 +1601,7 @@ static void typec_release(struct device *dev)
|
||||||
ida_destroy(&port->mode_ids);
|
ida_destroy(&port->mode_ids);
|
||||||
typec_switch_put(port->sw);
|
typec_switch_put(port->sw);
|
||||||
typec_mux_put(port->mux);
|
typec_mux_put(port->mux);
|
||||||
|
free_pld(port->pld);
|
||||||
kfree(port->cap);
|
kfree(port->cap);
|
||||||
kfree(port);
|
kfree(port);
|
||||||
}
|
}
|
||||||
|
@ -1983,6 +1984,8 @@ struct typec_port *typec_register_port(struct device *parent,
|
||||||
|
|
||||||
ida_init(&port->mode_ids);
|
ida_init(&port->mode_ids);
|
||||||
mutex_init(&port->port_type_lock);
|
mutex_init(&port->port_type_lock);
|
||||||
|
mutex_init(&port->port_list_lock);
|
||||||
|
INIT_LIST_HEAD(&port->port_list);
|
||||||
|
|
||||||
port->id = id;
|
port->id = id;
|
||||||
port->ops = cap->ops;
|
port->ops = cap->ops;
|
||||||
|
@ -2024,6 +2027,8 @@ struct typec_port *typec_register_port(struct device *parent,
|
||||||
return ERR_PTR(ret);
|
return ERR_PTR(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
port->pld = get_pld(&port->dev);
|
||||||
|
|
||||||
return port;
|
return port;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(typec_register_port);
|
EXPORT_SYMBOL_GPL(typec_register_port);
|
||||||
|
|
|
@ -54,6 +54,11 @@ struct typec_port {
|
||||||
|
|
||||||
const struct typec_capability *cap;
|
const struct typec_capability *cap;
|
||||||
const struct typec_operations *ops;
|
const struct typec_operations *ops;
|
||||||
|
|
||||||
|
struct list_head port_list;
|
||||||
|
struct mutex port_list_lock; /* Port list lock */
|
||||||
|
|
||||||
|
void *pld;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define to_typec_port(_dev_) container_of(_dev_, struct typec_port, dev)
|
#define to_typec_port(_dev_) container_of(_dev_, struct typec_port, dev)
|
||||||
|
@ -72,5 +77,9 @@ extern const struct device_type typec_port_dev_type;
|
||||||
#define is_typec_port(dev) ((dev)->type == &typec_port_dev_type)
|
#define is_typec_port(dev) ((dev)->type == &typec_port_dev_type)
|
||||||
|
|
||||||
extern struct class typec_mux_class;
|
extern struct class typec_mux_class;
|
||||||
|
extern struct class typec_class;
|
||||||
|
|
||||||
|
void *get_pld(struct device *dev);
|
||||||
|
void free_pld(void *pld);
|
||||||
|
|
||||||
#endif /* __USB_TYPEC_CLASS__ */
|
#endif /* __USB_TYPEC_CLASS__ */
|
||||||
|
|
|
@ -0,0 +1,219 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/*
|
||||||
|
* USB Type-C Connector Class Port Mapping Utility
|
||||||
|
*
|
||||||
|
* Copyright (C) 2021, Intel Corporation
|
||||||
|
* Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/acpi.h>
|
||||||
|
#include <linux/usb.h>
|
||||||
|
#include <linux/usb/typec.h>
|
||||||
|
|
||||||
|
#include "class.h"
|
||||||
|
|
||||||
|
struct port_node {
|
||||||
|
struct list_head list;
|
||||||
|
struct device *dev;
|
||||||
|
void *pld;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int acpi_pld_match(const struct acpi_pld_info *pld1,
|
||||||
|
const struct acpi_pld_info *pld2)
|
||||||
|
{
|
||||||
|
if (!pld1 || !pld2)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* To speed things up, first checking only the group_position. It seems
|
||||||
|
* to often have the first unique value in the _PLD.
|
||||||
|
*/
|
||||||
|
if (pld1->group_position == pld2->group_position)
|
||||||
|
return !memcmp(pld1, pld2, sizeof(struct acpi_pld_info));
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *get_pld(struct device *dev)
|
||||||
|
{
|
||||||
|
#ifdef CONFIG_ACPI
|
||||||
|
struct acpi_pld_info *pld;
|
||||||
|
acpi_status status;
|
||||||
|
|
||||||
|
if (!has_acpi_companion(dev))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
status = acpi_get_physical_device_location(ACPI_HANDLE(dev), &pld);
|
||||||
|
if (ACPI_FAILURE(status))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return pld;
|
||||||
|
#else
|
||||||
|
return NULL;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void free_pld(void *pld)
|
||||||
|
{
|
||||||
|
#ifdef CONFIG_ACPI
|
||||||
|
ACPI_FREE(pld);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static int __link_port(struct typec_port *con, struct port_node *node)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = sysfs_create_link(&node->dev->kobj, &con->dev.kobj, "connector");
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = sysfs_create_link(&con->dev.kobj, &node->dev->kobj,
|
||||||
|
dev_name(node->dev));
|
||||||
|
if (ret) {
|
||||||
|
sysfs_remove_link(&node->dev->kobj, "connector");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
list_add_tail(&node->list, &con->port_list);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int link_port(struct typec_port *con, struct port_node *node)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
mutex_lock(&con->port_list_lock);
|
||||||
|
ret = __link_port(con, node);
|
||||||
|
mutex_unlock(&con->port_list_lock);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __unlink_port(struct typec_port *con, struct port_node *node)
|
||||||
|
{
|
||||||
|
sysfs_remove_link(&con->dev.kobj, dev_name(node->dev));
|
||||||
|
sysfs_remove_link(&node->dev->kobj, "connector");
|
||||||
|
list_del(&node->list);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void unlink_port(struct typec_port *con, struct port_node *node)
|
||||||
|
{
|
||||||
|
mutex_lock(&con->port_list_lock);
|
||||||
|
__unlink_port(con, node);
|
||||||
|
mutex_unlock(&con->port_list_lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct port_node *create_port_node(struct device *port)
|
||||||
|
{
|
||||||
|
struct port_node *node;
|
||||||
|
|
||||||
|
node = kzalloc(sizeof(*node), GFP_KERNEL);
|
||||||
|
if (!node)
|
||||||
|
return ERR_PTR(-ENOMEM);
|
||||||
|
|
||||||
|
node->dev = get_device(port);
|
||||||
|
node->pld = get_pld(port);
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void remove_port_node(struct port_node *node)
|
||||||
|
{
|
||||||
|
put_device(node->dev);
|
||||||
|
free_pld(node->pld);
|
||||||
|
kfree(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int connector_match(struct device *dev, const void *data)
|
||||||
|
{
|
||||||
|
const struct port_node *node = data;
|
||||||
|
|
||||||
|
if (!is_typec_port(dev))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return acpi_pld_match(to_typec_port(dev)->pld, node->pld);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct device *find_connector(struct port_node *node)
|
||||||
|
{
|
||||||
|
if (!node->pld)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return class_find_device(&typec_class, NULL, node, connector_match);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* typec_link_port - Link a port to its connector
|
||||||
|
* @port: The port device
|
||||||
|
*
|
||||||
|
* Find the connector of @port and create symlink named "connector" for it.
|
||||||
|
* Returns 0 on success, or errno in case of a failure.
|
||||||
|
*
|
||||||
|
* NOTE. The function increments the reference count of @port on success.
|
||||||
|
*/
|
||||||
|
int typec_link_port(struct device *port)
|
||||||
|
{
|
||||||
|
struct device *connector;
|
||||||
|
struct port_node *node;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
node = create_port_node(port);
|
||||||
|
if (IS_ERR(node))
|
||||||
|
return PTR_ERR(node);
|
||||||
|
|
||||||
|
connector = find_connector(node);
|
||||||
|
if (!connector)
|
||||||
|
goto remove_node;
|
||||||
|
|
||||||
|
ret = link_port(to_typec_port(connector), node);
|
||||||
|
if (ret)
|
||||||
|
goto put_connector;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
put_connector:
|
||||||
|
put_device(connector);
|
||||||
|
remove_node:
|
||||||
|
remove_port_node(node);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(typec_link_port);
|
||||||
|
|
||||||
|
static int port_match_and_unlink(struct device *connector, void *port)
|
||||||
|
{
|
||||||
|
struct port_node *node;
|
||||||
|
struct port_node *tmp;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
if (!is_typec_port(connector))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
mutex_lock(&to_typec_port(connector)->port_list_lock);
|
||||||
|
list_for_each_entry_safe(node, tmp, &to_typec_port(connector)->port_list, list) {
|
||||||
|
ret = node->dev == port;
|
||||||
|
if (ret) {
|
||||||
|
unlink_port(to_typec_port(connector), node);
|
||||||
|
remove_port_node(node);
|
||||||
|
put_device(connector);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mutex_unlock(&to_typec_port(connector)->port_list_lock);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* typec_unlink_port - Unlink port from its connector
|
||||||
|
* @port: The port device
|
||||||
|
*
|
||||||
|
* Removes the symlink "connector" and decrements the reference count of @port.
|
||||||
|
*/
|
||||||
|
void typec_unlink_port(struct device *port)
|
||||||
|
{
|
||||||
|
class_for_each_device(&typec_class, NULL, port, port_match_and_unlink);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(typec_unlink_port);
|
|
@ -298,4 +298,17 @@ int typec_find_port_data_role(const char *name);
|
||||||
void typec_partner_set_svdm_version(struct typec_partner *partner,
|
void typec_partner_set_svdm_version(struct typec_partner *partner,
|
||||||
enum usb_pd_svdm_ver svdm_version);
|
enum usb_pd_svdm_ver svdm_version);
|
||||||
int typec_get_negotiated_svdm_version(struct typec_port *port);
|
int typec_get_negotiated_svdm_version(struct typec_port *port);
|
||||||
|
|
||||||
|
#if IS_REACHABLE(CONFIG_TYPEC)
|
||||||
|
int typec_link_port(struct device *port);
|
||||||
|
void typec_unlink_port(struct device *port);
|
||||||
|
#else
|
||||||
|
static inline int typec_link_port(struct device *port)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void typec_unlink_port(struct device *port) { }
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif /* __LINUX_USB_TYPEC_H */
|
#endif /* __LINUX_USB_TYPEC_H */
|
||||||
|
|
Loading…
Reference in New Issue