2145 lines · 54.1 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/module.h>#include <linux/mutex.h>#include <linux/property.h>#include <linux/slab.h>#include <linux/usb/pd_vdo.h>#include <linux/usb/typec_mux.h> #include "bus.h"#include "class.h" static DEFINE_IDA(typec_index_ida); struct class typec_class = {	.name = "typec",	.owner = THIS_MODULE,}; /* ------------------------------------------------------------------------- *//* Common attributes */ static const char * const typec_accessory_modes[] = {	[TYPEC_ACCESSORY_NONE]		= "none",	[TYPEC_ACCESSORY_AUDIO]		= "analog_audio",	[TYPEC_ACCESSORY_DEBUG]		= "debug",}; /* Product types defined in USB PD Specification R3.0 V2.0 */static const char * const product_type_ufp[8] = {	[IDH_PTYPE_NOT_UFP]		= "not_ufp",	[IDH_PTYPE_HUB]			= "hub",	[IDH_PTYPE_PERIPH]		= "peripheral",	[IDH_PTYPE_PSD]			= "psd",	[IDH_PTYPE_AMA]			= "ama",}; static const char * const product_type_dfp[8] = {	[IDH_PTYPE_NOT_DFP]		= "not_dfp",	[IDH_PTYPE_DFP_HUB]		= "hub",	[IDH_PTYPE_DFP_HOST]		= "host",	[IDH_PTYPE_DFP_PB]		= "power_brick",}; static const char * const product_type_cable[8] = {	[IDH_PTYPE_NOT_CABLE]		= "not_cable",	[IDH_PTYPE_PCABLE]		= "passive",	[IDH_PTYPE_ACABLE]		= "active",	[IDH_PTYPE_VPD]			= "vpd",}; 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 const char *get_pd_product_type(struct device *dev){	struct typec_port *port = to_typec_port(dev->parent);	struct usb_pd_identity *id = get_pd_identity(dev);	const char *ptype = NULL; 	if (is_typec_partner(dev)) {		if (!id)			return NULL; 		if (port->data_role == TYPEC_HOST)			ptype = product_type_ufp[PD_IDH_PTYPE(id->id_header)];		else			ptype = product_type_dfp[PD_IDH_DFP_PTYPE(id->id_header)];	} else if (is_typec_cable(dev)) {		if (id)			ptype = product_type_cable[PD_IDH_PTYPE(id->id_header)];		else			ptype = to_typec_cable(dev)->active ?				product_type_cable[IDH_PTYPE_ACABLE] :				product_type_cable[IDH_PTYPE_PCABLE];	} 	return ptype;} 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 ssize_t product_type_vdo1_show(struct device *dev, struct device_attribute *attr,				      char *buf){	struct usb_pd_identity *id = get_pd_identity(dev); 	return sysfs_emit(buf, "0x%08x\n", id->vdo[0]);}static DEVICE_ATTR_RO(product_type_vdo1); static ssize_t product_type_vdo2_show(struct device *dev, struct device_attribute *attr,				      char *buf){	struct usb_pd_identity *id = get_pd_identity(dev); 	return sysfs_emit(buf, "0x%08x\n", id->vdo[1]);}static DEVICE_ATTR_RO(product_type_vdo2); static ssize_t product_type_vdo3_show(struct device *dev, struct device_attribute *attr,				      char *buf){	struct usb_pd_identity *id = get_pd_identity(dev); 	return sysfs_emit(buf, "0x%08x\n", id->vdo[2]);}static DEVICE_ATTR_RO(product_type_vdo3); static struct attribute *usb_pd_id_attrs[] = {	&dev_attr_id_header.attr,	&dev_attr_cert_stat.attr,	&dev_attr_product.attr,	&dev_attr_product_type_vdo1.attr,	&dev_attr_product_type_vdo2.attr,	&dev_attr_product_type_vdo3.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_product_type_notify(struct device *dev){	char *envp[2] = { };	const char *ptype; 	ptype = get_pd_product_type(dev);	if (!ptype)		return; 	sysfs_notify(&dev->kobj, NULL, "type"); 	envp[0] = kasprintf(GFP_KERNEL, "PRODUCT_TYPE=%s", ptype);	if (!envp[0])		return; 	kobject_uevent_env(&dev->kobj, KOBJ_CHANGE, envp);	kfree(envp[0]);} 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");	sysfs_notify(&dev->kobj, "identity", "product_type_vdo1");	sysfs_notify(&dev->kobj, "identity", "product_type_vdo2");	sysfs_notify(&dev->kobj, "identity", "product_type_vdo3");	typec_product_type_notify(dev);} static ssize_ttype_show(struct device *dev, struct device_attribute *attr, char *buf){	const char *ptype; 	ptype = get_pd_product_type(dev);	if (!ptype)		return 0; 	return sysfs_emit(buf, "%s\n", ptype);}static DEVICE_ATTR_RO(type); static ssize_t usb_power_delivery_revision_show(struct device *dev,						struct device_attribute *attr,						char *buf);static DEVICE_ATTR_RO(usb_power_delivery_revision); /* ------------------------------------------------------------------------- *//* 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);} /** * 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}; static umode_t typec_altmode_attr_is_visible(struct kobject *kobj,					     struct attribute *attr, int n){	struct typec_altmode *adev = to_typec_altmode(kobj_to_dev(kobj)); 	if (attr == &dev_attr_active.attr)		if (!adev->ops || !adev->ops->activate)			return 0444; 	return attr->mode;} static const struct attribute_group typec_altmode_group = {	.is_visible = typec_altmode_attr_is_visible,	.attrs = typec_altmode_attrs,}; static const struct attribute_group *typec_altmode_groups[] = {	&typec_altmode_group,	NULL}; 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);

Lines 1–500 of 2145.