// 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/slab.h>
#include <linux/usb/typec.h>
#include <linux/usb/typec_mux.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;
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)
#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_t
typec_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_t
typec_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_t
typec_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_t
typec_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_t
typec_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_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 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(&
|