1669 lines · 41.6 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0/* * USB Type-C Connector Class * * Copyright (C) 2017, Intel Corporation * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com> */ #include <linux/device.h>#include <linux/module.h>#include <linux/mutex.h>#include <linux/property.h>#include <linux/slab.h> #include "bus.h" struct typec_plug {	struct device			dev;	enum typec_plug_index		index;	struct ida			mode_ids;}; struct typec_cable {	struct device			dev;	enum typec_plug_type		type;	struct usb_pd_identity		*identity;	unsigned int			active:1;}; struct typec_partner {	struct device			dev;	unsigned int			usb_pd:1;	struct usb_pd_identity		*identity;	enum typec_accessory		accessory;	struct ida			mode_ids;}; struct typec_port {	unsigned int			id;	struct device			dev;	struct ida			mode_ids; 	int				prefer_role;	enum typec_data_role		data_role;	enum typec_role			pwr_role;	enum typec_role			vconn_role;	enum typec_pwr_opmode		pwr_opmode;	enum typec_port_type		port_type;	struct mutex			port_type_lock; 	enum typec_orientation		orientation;	struct typec_switch		*sw;	struct typec_mux		*mux; 	const struct typec_capability	*cap;}; #define to_typec_port(_dev_) container_of(_dev_, struct typec_port, dev)#define to_typec_plug(_dev_) container_of(_dev_, struct typec_plug, dev)#define to_typec_cable(_dev_) container_of(_dev_, struct typec_cable, dev)#define to_typec_partner(_dev_) container_of(_dev_, struct typec_partner, dev) static const struct device_type typec_partner_dev_type;static const struct device_type typec_cable_dev_type;static const struct device_type typec_plug_dev_type; #define is_typec_partner(_dev_) (_dev_->type == &typec_partner_dev_type)#define is_typec_cable(_dev_) (_dev_->type == &typec_cable_dev_type)#define is_typec_plug(_dev_) (_dev_->type == &typec_plug_dev_type) static DEFINE_IDA(typec_index_ida);static struct class *typec_class; /* ------------------------------------------------------------------------- *//* Common attributes */ static const char * const typec_accessory_modes[] = {	[TYPEC_ACCESSORY_NONE]		= "none",	[TYPEC_ACCESSORY_AUDIO]		= "analog_audio",	[TYPEC_ACCESSORY_DEBUG]		= "debug",}; static struct usb_pd_identity *get_pd_identity(struct device *dev){	if (is_typec_partner(dev)) {		struct typec_partner *partner = to_typec_partner(dev); 		return partner->identity;	} else if (is_typec_cable(dev)) {		struct typec_cable *cable = to_typec_cable(dev); 		return cable->identity;	}	return NULL;} static ssize_t id_header_show(struct device *dev, struct device_attribute *attr,			      char *buf){	struct usb_pd_identity *id = get_pd_identity(dev); 	return sprintf(buf, "0x%08x\n", id->id_header);}static DEVICE_ATTR_RO(id_header); static ssize_t cert_stat_show(struct device *dev, struct device_attribute *attr,			      char *buf){	struct usb_pd_identity *id = get_pd_identity(dev); 	return sprintf(buf, "0x%08x\n", id->cert_stat);}static DEVICE_ATTR_RO(cert_stat); static ssize_t product_show(struct device *dev, struct device_attribute *attr,			    char *buf){	struct usb_pd_identity *id = get_pd_identity(dev); 	return sprintf(buf, "0x%08x\n", id->product);}static DEVICE_ATTR_RO(product); static struct attribute *usb_pd_id_attrs[] = {	&dev_attr_id_header.attr,	&dev_attr_cert_stat.attr,	&dev_attr_product.attr,	NULL}; static const struct attribute_group usb_pd_id_group = {	.name = "identity",	.attrs = usb_pd_id_attrs,}; static const struct attribute_group *usb_pd_id_groups[] = {	&usb_pd_id_group,	NULL,}; static void typec_report_identity(struct device *dev){	sysfs_notify(&dev->kobj, "identity", "id_header");	sysfs_notify(&dev->kobj, "identity", "cert_stat");	sysfs_notify(&dev->kobj, "identity", "product");} /* ------------------------------------------------------------------------- *//* Alternate Modes */ static int altmode_match(struct device *dev, void *data){	struct typec_altmode *adev = to_typec_altmode(dev);	struct typec_device_id *id = data; 	if (!is_typec_altmode(dev))		return 0; 	return ((adev->svid == id->svid) && (adev->mode == id->mode));} static void typec_altmode_set_partner(struct altmode *altmode){	struct typec_altmode *adev = &altmode->adev;	struct typec_device_id id = { adev->svid, adev->mode, };	struct typec_port *port = typec_altmode2port(adev);	struct altmode *partner;	struct device *dev; 	dev = device_find_child(&port->dev, &id, altmode_match);	if (!dev)		return; 	/* Bind the port alt mode to the partner/plug alt mode. */	partner = to_altmode(to_typec_altmode(dev));	altmode->partner = partner; 	/* Bind the partner/plug alt mode to the port alt mode. */	if (is_typec_plug(adev->dev.parent)) {		struct typec_plug *plug = to_typec_plug(adev->dev.parent); 		partner->plug[plug->index] = altmode;	} else {		partner->partner = altmode;	}} static void typec_altmode_put_partner(struct altmode *altmode){	struct altmode *partner = altmode->partner;	struct typec_altmode *adev; 	if (!partner)		return; 	adev = &partner->adev; 	if (is_typec_plug(adev->dev.parent)) {		struct typec_plug *plug = to_typec_plug(adev->dev.parent); 		partner->plug[plug->index] = NULL;	} else {		partner->partner = NULL;	}	put_device(&adev->dev);} static int typec_port_fwnode_match(struct device *dev, const void *fwnode){	return dev_fwnode(dev) == fwnode;} static int typec_port_name_match(struct device *dev, const void *name){	return !strcmp((const char *)name, dev_name(dev));} static void *typec_port_match(struct device_connection *con, int ep, void *data){	struct device *dev; 	/*	 * FIXME: Check does the fwnode supports the requested SVID. If it does	 * we need to return ERR_PTR(-PROBE_DEFER) when there is no device.	 */	if (con->fwnode)		return class_find_device(typec_class, NULL, con->fwnode,					 typec_port_fwnode_match); 	dev = class_find_device(typec_class, NULL, con->endpoint[ep],				typec_port_name_match); 	return dev ? dev : ERR_PTR(-EPROBE_DEFER);} struct typec_altmode *typec_altmode_register_notifier(struct device *dev, u16 svid, u8 mode,				struct notifier_block *nb){	struct typec_device_id id = { svid, mode, };	struct device *altmode_dev;	struct device *port_dev;	struct altmode *altmode;	int ret; 	/* Find the port linked to the caller */	port_dev = device_connection_find_match(dev, NULL, NULL,						typec_port_match);	if (IS_ERR_OR_NULL(port_dev))		return port_dev ? ERR_CAST(port_dev) : ERR_PTR(-ENODEV); 	/* Find the altmode with matching svid */	altmode_dev = device_find_child(port_dev, &id, altmode_match); 	put_device(port_dev); 	if (!altmode_dev)		return ERR_PTR(-ENODEV); 	altmode = to_altmode(to_typec_altmode(altmode_dev)); 	/* Register notifier */	ret = blocking_notifier_chain_register(&altmode->nh, nb);	if (ret) {		put_device(altmode_dev);		return ERR_PTR(ret);	} 	return &altmode->adev;}EXPORT_SYMBOL_GPL(typec_altmode_register_notifier); void typec_altmode_unregister_notifier(struct typec_altmode *adev,				       struct notifier_block *nb){	struct altmode *altmode = to_altmode(adev); 	blocking_notifier_chain_unregister(&altmode->nh, nb);	put_device(&adev->dev);}EXPORT_SYMBOL_GPL(typec_altmode_unregister_notifier); /** * typec_altmode_update_active - Report Enter/Exit mode * @adev: Handle to the alternate mode * @active: True when the mode has been entered * * If a partner or cable plug executes Enter/Exit Mode command successfully, the * drivers use this routine to report the updated state of the mode. */void typec_altmode_update_active(struct typec_altmode *adev, bool active){	char dir[6]; 	if (adev->active == active)		return; 	if (!is_typec_port(adev->dev.parent) && adev->dev.driver) {		if (!active)			module_put(adev->dev.driver->owner);		else			WARN_ON(!try_module_get(adev->dev.driver->owner));	} 	adev->active = active;	snprintf(dir, sizeof(dir), "mode%d", adev->mode);	sysfs_notify(&adev->dev.kobj, dir, "active");	sysfs_notify(&adev->dev.kobj, NULL, "active");	kobject_uevent(&adev->dev.kobj, KOBJ_CHANGE);}EXPORT_SYMBOL_GPL(typec_altmode_update_active); /** * typec_altmode2port - Alternate Mode to USB Type-C port * @alt: The Alternate Mode * * Returns handle to the port that a cable plug or partner with @alt is * connected to. */struct typec_port *typec_altmode2port(struct typec_altmode *alt){	if (is_typec_plug(alt->dev.parent))		return to_typec_port(alt->dev.parent->parent->parent);	if (is_typec_partner(alt->dev.parent))		return to_typec_port(alt->dev.parent->parent);	if (is_typec_port(alt->dev.parent))		return to_typec_port(alt->dev.parent); 	return NULL;}EXPORT_SYMBOL_GPL(typec_altmode2port); static ssize_tvdo_show(struct device *dev, struct device_attribute *attr, char *buf){	struct typec_altmode *alt = to_typec_altmode(dev); 	return sprintf(buf, "0x%08x\n", alt->vdo);}static DEVICE_ATTR_RO(vdo); static ssize_tdescription_show(struct device *dev, struct device_attribute *attr, char *buf){	struct typec_altmode *alt = to_typec_altmode(dev); 	return sprintf(buf, "%s\n", alt->desc ? alt->desc : "");}static DEVICE_ATTR_RO(description); static ssize_tactive_show(struct device *dev, struct device_attribute *attr, char *buf){	struct typec_altmode *alt = to_typec_altmode(dev); 	return sprintf(buf, "%s\n", alt->active ? "yes" : "no");} static ssize_t active_store(struct device *dev, struct device_attribute *attr,			    const char *buf, size_t size){	struct typec_altmode *adev = to_typec_altmode(dev);	struct altmode *altmode = to_altmode(adev);	bool enter;	int ret; 	ret = kstrtobool(buf, &enter);	if (ret)		return ret; 	if (adev->active == enter)		return size; 	if (is_typec_port(adev->dev.parent)) {		typec_altmode_update_active(adev, enter); 		/* Make sure that the partner exits the mode before disabling */		if (altmode->partner && !enter && altmode->partner->adev.active)			typec_altmode_exit(&altmode->partner->adev);	} else if (altmode->partner) {		if (enter && !altmode->partner->adev.active) {			dev_warn(dev, "port has the mode disabled\n");			return -EPERM;		}	} 	/* Note: If there is no driver, the mode will not be entered */	if (adev->ops && adev->ops->activate) {		ret = adev->ops->activate(adev, enter);		if (ret)			return ret;	} 	return size;}static DEVICE_ATTR_RW(active); static ssize_tsupported_roles_show(struct device *dev, struct device_attribute *attr,		     char *buf){	struct altmode *alt = to_altmode(to_typec_altmode(dev));	ssize_t ret; 	switch (alt->roles) {	case TYPEC_PORT_SRC:		ret = sprintf(buf, "source\n");		break;	case TYPEC_PORT_SNK:		ret = sprintf(buf, "sink\n");		break;	case TYPEC_PORT_DRP:	default:		ret = sprintf(buf, "source sink\n");		break;	}	return ret;}static DEVICE_ATTR_RO(supported_roles); static ssize_tmode_show(struct device *dev, struct device_attribute *attr, char *buf){	struct typec_altmode *adev = to_typec_altmode(dev); 	return sprintf(buf, "%u\n", adev->mode);}static DEVICE_ATTR_RO(mode); static ssize_tsvid_show(struct device *dev, struct device_attribute *attr, char *buf){	struct typec_altmode *adev = to_typec_altmode(dev); 	return sprintf(buf, "%04x\n", adev->svid);}static DEVICE_ATTR_RO(svid); static struct attribute *typec_altmode_attrs[] = {	&dev_attr_active.attr,	&dev_attr_mode.attr,	&dev_attr_svid.attr,	&dev_attr_vdo.attr,	NULL};ATTRIBUTE_GROUPS(typec_altmode); static int altmode_id_get(struct device *dev){	struct ida *ids; 	if (is_typec_partner(dev))		ids = &to_typec_partner(dev)->mode_ids;	else if (is_typec_plug(dev))		ids = &to_typec_plug(dev)->mode_ids;	else		ids = &to_typec_port(dev)->mode_ids; 	return ida_simple_get(ids, 0, 0, GFP_KERNEL);} static void altmode_id_remove(struct device *dev, int id){	struct ida *ids; 	if (is_typec_partner(dev))		ids = &to_typec_partner(dev)->mode_ids;	else if (is_typec_plug(dev))		ids = &to_typec_plug(dev)->mode_ids;	else		ids = &to_typec_port(dev)->mode_ids; 	ida_simple_remove(ids, id);} static void typec_altmode_release(struct device *dev){	struct altmode *alt = to_altmode(to_typec_altmode(dev)); 	typec_altmode_put_partner(alt); 	altmode_id_remove(alt->adev.dev.parent, alt->id);	kfree(alt);} const struct device_type typec_altmode_dev_type = {	.name = "typec_alternate_mode",	.groups = typec_altmode_groups,	.release = typec_altmode_release,}; static struct typec_altmode *typec_register_altmode(struct device *parent,		       const struct typec_altmode_desc *desc){	unsigned int id = altmode_id_get(parent);	bool is_port = is_typec_port(parent);	struct altmode *alt;	int ret; 

Lines 1–500 of 1669.