1368 lines · 34.4 KB · C
RawDownload
/* * USB Type-C Connector Class * * Copyright (C) 2017, Intel Corporation * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include <linux/device.h>#include <linux/module.h>#include <linux/mutex.h>#include <linux/slab.h>#include <linux/usb/typec.h> struct typec_mode {	int				index;	u32				vdo;	char				*desc;	enum typec_port_type		roles; 	struct typec_altmode		*alt_mode; 	unsigned int			active:1; 	char				group_name[6];	struct attribute_group		group;	struct attribute		*attrs[5];	struct device_attribute		vdo_attr;	struct device_attribute		desc_attr;	struct device_attribute		active_attr;	struct device_attribute		roles_attr;}; struct typec_altmode {	struct device			dev;	u16				svid;	int				n_modes;	struct typec_mode		modes[ALTMODE_MAX_MODES];	const struct attribute_group	*mode_groups[ALTMODE_MAX_MODES];}; struct typec_plug {	struct device			dev;	enum typec_plug_index		index;}; 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 typec_port {	unsigned int			id;	struct device			dev; 	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; 	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)#define to_altmode(_dev_) container_of(_dev_, struct typec_altmode, 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;static const struct device_type typec_port_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)#define is_typec_port(_dev_) (_dev_->type == &typec_port_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 */ /** * typec_altmode_update_active - Report Enter/Exit mode * @alt: Handle to the alternate mode * @mode: Mode index * @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 *alt, int mode,				 bool active){	struct typec_mode *m = &alt->modes[mode];	char dir[6]; 	if (m->active == active)		return; 	m->active = active;	snprintf(dir, sizeof(dir), "mode%d", mode);	sysfs_notify(&alt->dev.kobj, dir, "active");	kobject_uevent(&alt->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_ttypec_altmode_vdo_show(struct device *dev, struct device_attribute *attr,		       char *buf){	struct typec_mode *mode = container_of(attr, struct typec_mode,					       vdo_attr); 	return sprintf(buf, "0x%08x\n", mode->vdo);} static ssize_ttypec_altmode_desc_show(struct device *dev, struct device_attribute *attr,			char *buf){	struct typec_mode *mode = container_of(attr, struct typec_mode,					       desc_attr); 	return sprintf(buf, "%s\n", mode->desc ? mode->desc : "");} static ssize_ttypec_altmode_active_show(struct device *dev, struct device_attribute *attr,			  char *buf){	struct typec_mode *mode = container_of(attr, struct typec_mode,					       active_attr); 	return sprintf(buf, "%s\n", mode->active ? "yes" : "no");} static ssize_ttypec_altmode_active_store(struct device *dev, struct device_attribute *attr,			   const char *buf, size_t size){	struct typec_mode *mode = container_of(attr, struct typec_mode,					       active_attr);	struct typec_port *port = typec_altmode2port(mode->alt_mode);	bool activate;	int ret; 	if (!port->cap->activate_mode)		return -EOPNOTSUPP; 	ret = kstrtobool(buf, &activate);	if (ret)		return ret; 	ret = port->cap->activate_mode(port->cap, mode->index, activate);	if (ret)		return ret; 	return size;} static ssize_ttypec_altmode_roles_show(struct device *dev, struct device_attribute *attr,			 char *buf){	struct typec_mode *mode = container_of(attr, struct typec_mode,					       roles_attr);	ssize_t ret; 	switch (mode->roles) {	case TYPEC_PORT_DFP:		ret = sprintf(buf, "source\n");		break;	case TYPEC_PORT_UFP:		ret = sprintf(buf, "sink\n");		break;	case TYPEC_PORT_DRP:	default:		ret = sprintf(buf, "source sink\n");		break;	}	return ret;} static void typec_init_modes(struct typec_altmode *alt,			     const struct typec_mode_desc *desc, bool is_port){	int i; 	for (i = 0; i < alt->n_modes; i++, desc++) {		struct typec_mode *mode = &alt->modes[i]; 		/* Not considering the human readable description critical */		mode->desc = kstrdup(desc->desc, GFP_KERNEL);		if (desc->desc && !mode->desc)			dev_err(&alt->dev, "failed to copy mode%d desc\n", i); 		mode->alt_mode = alt;		mode->vdo = desc->vdo;		mode->roles = desc->roles;		mode->index = desc->index;		sprintf(mode->group_name, "mode%d", desc->index); 		sysfs_attr_init(&mode->vdo_attr.attr);		mode->vdo_attr.attr.name = "vdo";		mode->vdo_attr.attr.mode = 0444;		mode->vdo_attr.show = typec_altmode_vdo_show; 		sysfs_attr_init(&mode->desc_attr.attr);		mode->desc_attr.attr.name = "description";		mode->desc_attr.attr.mode = 0444;		mode->desc_attr.show = typec_altmode_desc_show; 		sysfs_attr_init(&mode->active_attr.attr);		mode->active_attr.attr.name = "active";		mode->active_attr.attr.mode = 0644;		mode->active_attr.show = typec_altmode_active_show;		mode->active_attr.store = typec_altmode_active_store; 		mode->attrs[0] = &mode->vdo_attr.attr;		mode->attrs[1] = &mode->desc_attr.attr;		mode->attrs[2] = &mode->active_attr.attr; 		/* With ports, list the roles that the mode is supported with */		if (is_port) {			sysfs_attr_init(&mode->roles_attr.attr);			mode->roles_attr.attr.name = "supported_roles";			mode->roles_attr.attr.mode = 0444;			mode->roles_attr.show = typec_altmode_roles_show; 			mode->attrs[3] = &mode->roles_attr.attr;		} 		mode->group.attrs = mode->attrs;		mode->group.name = mode->group_name; 		alt->mode_groups[i] = &mode->group;	}} static ssize_t svid_show(struct device *dev, struct device_attribute *attr,			 char *buf){	struct typec_altmode *alt = to_altmode(dev); 	return sprintf(buf, "%04x\n", alt->svid);}static DEVICE_ATTR_RO(svid); static struct attribute *typec_altmode_attrs[] = {	&dev_attr_svid.attr,	NULL};ATTRIBUTE_GROUPS(typec_altmode); static void typec_altmode_release(struct device *dev){	struct typec_altmode *alt = to_altmode(dev);	int i; 	for (i = 0; i < alt->n_modes; i++)		kfree(alt->modes[i].desc);	kfree(alt);} static 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){	struct typec_altmode *alt;	int ret; 	alt = kzalloc(sizeof(*alt), GFP_KERNEL);	if (!alt)		return NULL; 	alt->svid = desc->svid;	alt->n_modes = desc->n_modes;	typec_init_modes(alt, desc->modes, is_typec_port(parent)); 	alt->dev.parent = parent;	alt->dev.groups = alt->mode_groups;	alt->dev.type = &typec_altmode_dev_type;	dev_set_name(&alt->dev, "svid-%04x", alt->svid); 	ret = device_register(&alt->dev);	if (ret) {		dev_err(parent, "failed to register alternate mode (%d)\n",			ret);		put_device(&alt->dev);		return NULL;	} 	return alt;} /** * typec_unregister_altmode - Unregister Alternate Mode * @alt: The alternate mode to be unregistered * * Unregister device created with typec_partner_register_altmode(), * typec_plug_register_altmode() or typec_port_register_altmode(). */void typec_unregister_altmode(struct typec_altmode *alt){	if (alt)		device_unregister(&alt->dev);}EXPORT_SYMBOL_GPL(typec_unregister_altmode); /* ------------------------------------------------------------------------- *//* Type-C Partners */ static ssize_t accessory_mode_show(struct device *dev,				   struct device_attribute *attr,				   char *buf){	struct typec_partner *p = to_typec_partner(dev); 	return sprintf(buf, "%s\n", typec_accessory_modes[p->accessory]);}static DEVICE_ATTR_RO(accessory_mode); static ssize_t supports_usb_power_delivery_show(struct device *dev,						struct device_attribute *attr,						char *buf){	struct typec_partner *p = to_typec_partner(dev); 	return sprintf(buf, "%s\n", p->usb_pd ? "yes" : "no");}static DEVICE_ATTR_RO(supports_usb_power_delivery); static struct attribute *typec_partner_attrs[] = {	&dev_attr_accessory_mode.attr,	&dev_attr_supports_usb_power_delivery.attr,	NULL};ATTRIBUTE_GROUPS(typec_partner); static void typec_partner_release(struct device *dev){	struct typec_partner *partner = to_typec_partner(dev); 	kfree(partner);} static const struct device_type typec_partner_dev_type = {	.name = "typec_partner",	.groups = typec_partner_groups,	.release = typec_partner_release,}; /** * typec_partner_set_identity - Report result from Discover Identity command * @partner: The partner updated identity values * * This routine is used to report that the result of Discover Identity USB power * delivery command has become available. */int typec_partner_set_identity(struct typec_partner *partner){	if (!partner->identity)		return -EINVAL; 	typec_report_identity(&partner->dev);	return 0;}EXPORT_SYMBOL_GPL(typec_partner_set_identity); /** * typec_partner_register_altmode - Register USB Type-C Partner Alternate Mode * @partner: USB Type-C Partner that supports the alternate mode * @desc: Description of the alternate mode * * This routine is used to register each alternate mode individually that * @partner has listed in response to Discover SVIDs command. The modes for a * SVID listed in response to Discover Modes command need to be listed in an * array in @desc. * * Returns handle to the alternate mode on success or NULL on failure. */struct typec_altmode *

Lines 1–500 of 1368.