blob: 6bb8a53ad8ea49fe2324453215f07a518612d8f9 [file] [log] [blame]
// 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 <dm/device.h>
#include <dm/device_compat.h>
#include <linux/compat.h>
#include <linux/usb/typec.h>
#include <linux/string.h>
#include <linux/device_type.h>
struct typec_partner {
struct udevice dev;
unsigned int usb_pd:1;
struct usb_pd_identity *identity;
enum typec_accessory accessory;
};
struct typec_port {
unsigned int id;
struct udevice 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;
const struct typec_capability *cap;
const struct typec_operations *ops;
};
#define to_typec_port(_dev_) container_of(_dev_, struct typec_port, dev)
#define to_typec_partner(_dev_) container_of(_dev_, struct typec_partner, dev)
struct typec_platdata {
const struct device_type *type;
};
/*
* copy from mm/util.c
*/
void *kmemdup(const void *src, size_t len, gfp_t gfp)
{
void *p;
p = kmalloc(len, gfp);
if (p)
memcpy(p, src, len);
return p;
}
/*
* replace ida_simple_get() in kernel as uboot is a single thread scenario.
*/
static unsigned int get_port_id(void) {
static unsigned int port_id_counter = 0;
return port_id_counter++;
}
const struct device_type typec_port_dev_type = {
.name = "typec_port",
};
static int match_string(const char * const *array, size_t n, const char *string)
{
int index;
const char *item;
for (index = 0; index < n; index++) {
item = array[index];
if (!item)
break;
if (!strcmp(item, string))
return index;
}
return -EINVAL;
}
/* ------------------------------------------------------------------------- */
/* Type-C Partners */
static const struct device_type typec_partner_dev_type = {
.name = "typec_partner",
};
/**
* 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;
return 0;
}
EXPORT_SYMBOL_GPL(typec_partner_set_identity);
/**
* typec_register_partner - Register a USB Type-C Partner
* @port: The USB Type-C Port the partner is connected to
* @desc: Description of the partner
*
* Registers a device for USB Type-C Partner described in @desc.
*
* Returns handle to the partner on success or ERR_PTR on failure.
*/
struct typec_partner *typec_register_partner(struct typec_port *port,
struct typec_partner_desc *desc)
{
struct typec_partner *partner;
struct typec_platdata *plat;
char *name;
partner = kzalloc(sizeof(*partner), GFP_KERNEL);
if (!partner)
return ERR_PTR(-ENOMEM);
partner->usb_pd = desc->usb_pd;
partner->accessory = desc->accessory;
if (desc->identity) {
/*
* Creating directory for the identity only if the driver is
* able to provide data to it.
*/
partner->identity = desc->identity;
}
partner->dev.parent = &port->dev;
plat = kzalloc(sizeof(*plat), GFP_KERNEL);
if (!plat)
return ERR_PTR(-ENOMEM);
plat->type = &typec_partner_dev_type;
partner->dev.platdata = plat;
name = kzalloc(32, GFP_KERNEL);
snprintf(name, 32, "%s-partner", port->dev.name);
partner->dev.name = name;
INIT_LIST_HEAD(&partner->dev.uclass_node);
INIT_LIST_HEAD(&partner->dev.child_head);
INIT_LIST_HEAD(&partner->dev.sibling_node);
/* put partner dev into port's successor list */
list_add_tail(&partner->dev.sibling_node, &port->dev.child_head);
return partner;
}
EXPORT_SYMBOL_GPL(typec_register_partner);
/**
* typec_unregister_partner - Unregister a USB Type-C Partner
* @partner: The partner to be unregistered
*
* Unregister device created with typec_register_partner().
*/
void typec_unregister_partner(struct typec_partner *partner)
{
if (!IS_ERR_OR_NULL(partner))
device_unregister(&partner->dev);
}
EXPORT_SYMBOL_GPL(typec_unregister_partner);
/* ------------------------------------------------------------------------- */
/* USB Type-C ports */
static const char * const typec_roles[] = {
[TYPEC_SINK] = "sink",
[TYPEC_SOURCE] = "source",
};
static const char * const typec_port_power_roles[] = {
[TYPEC_PORT_SRC] = "source",
[TYPEC_PORT_SNK] = "sink",
[TYPEC_PORT_DRP] = "dual",
};
static const char * const typec_port_data_roles[] = {
[TYPEC_PORT_DFP] = "host",
[TYPEC_PORT_UFP] = "device",
[TYPEC_PORT_DRD] = "dual",
};
/* --------------------------------------- */
/* Driver callbacks to report role updates */
/**
* typec_set_data_role - Report data role change
* @port: The USB Type-C Port where the role was changed
* @role: The new data role
*
* This routine is used by the port drivers to report data role changes.
*/
void typec_set_data_role(struct typec_port *port, enum typec_data_role role)
{
if (port->data_role == role)
return;
port->data_role = role;
}
EXPORT_SYMBOL_GPL(typec_set_data_role);
/**
* typec_set_pwr_role - Report power role change
* @port: The USB Type-C Port where the role was changed
* @role: The new data role
*
* This routine is used by the port drivers to report power role changes.
*/
void typec_set_pwr_role(struct typec_port *port, enum typec_role role)
{
if (port->pwr_role == role)
return;
port->pwr_role = role;
}
EXPORT_SYMBOL_GPL(typec_set_pwr_role);
/**
* typec_set_vconn_role - Report VCONN source change
* @port: The USB Type-C Port which VCONN role changed
* @role: Source when @port is sourcing VCONN, or Sink when it's not
*
* This routine is used by the port drivers to report if the VCONN source is
* changes.
*/
void typec_set_vconn_role(struct typec_port *port, enum typec_role role)
{
if (port->vconn_role == role)
return;
port->vconn_role = role;
}
EXPORT_SYMBOL_GPL(typec_set_vconn_role);
/**
* typec_set_pwr_opmode - Report changed power operation mode
* @port: The USB Type-C Port where the mode was changed
* @opmode: New power operation mode
*
* This routine is used by the port drivers to report changed power operation
* mode in @port. The modes are USB (default), 1.5A, 3.0A as defined in USB
* Type-C specification, and "USB Power Delivery" when the power levels are
* negotiated with methods defined in USB Power Delivery specification.
*/
void typec_set_pwr_opmode(struct typec_port *port,
enum typec_pwr_opmode opmode)
{
struct udevice *partner_dev;
if (port->pwr_opmode == opmode)
return;
port->pwr_opmode = opmode;
device_find_first_child(&port->dev, &partner_dev);
if (partner_dev) {
struct typec_partner *partner = to_typec_partner(partner_dev);
if (opmode == TYPEC_PWR_MODE_PD && !partner->usb_pd) {
partner->usb_pd = 1;
}
}
}
EXPORT_SYMBOL_GPL(typec_set_pwr_opmode);
/**
* typec_find_port_power_role - Get the typec port power capability
* @name: port power capability string
*
* This routine is used to find the typec_port_type by its string name.
*
* Returns typec_port_type if success, otherwise negative error code.
*/
int typec_find_port_power_role(const char *name)
{
return match_string(typec_port_power_roles,
ARRAY_SIZE(typec_port_power_roles), name);
}
EXPORT_SYMBOL_GPL(typec_find_port_power_role);
/**
* typec_find_power_role - Find the typec one specific power role
* @name: power role string
*
* This routine is used to find the typec_role by its string name.
*
* Returns typec_role if success, otherwise negative error code.
*/
int typec_find_power_role(const char *name)
{
return match_string(typec_roles, ARRAY_SIZE(typec_roles), name);
}
EXPORT_SYMBOL_GPL(typec_find_power_role);
/**
* typec_find_port_data_role - Get the typec port data capability
* @name: port data capability string
*
* This routine is used to find the typec_port_data by its string name.
*
* Returns typec_port_data if success, otherwise negative error code.
*/
int typec_find_port_data_role(const char *name)
{
return match_string(typec_port_data_roles,
ARRAY_SIZE(typec_port_data_roles), name);
}
EXPORT_SYMBOL_GPL(typec_find_port_data_role);
/* ------------------------------------------ */
/* API for Multiplexer/DeMultiplexer Switches */
/**
* typec_set_orientation - Set USB Type-C cable plug orientation
* @port: USB Type-C Port
* @orientation: USB Type-C cable plug orientation
*
* Set cable plug orientation for @port.
*/
int typec_set_orientation(struct typec_port *port,
enum typec_orientation orientation)
{
port->orientation = orientation;
return 0;
}
EXPORT_SYMBOL_GPL(typec_set_orientation);
/**
* typec_get_orientation - Get USB Type-C cable plug orientation
* @port: USB Type-C Port
*
* Get current cable plug orientation for @port.
*/
enum typec_orientation typec_get_orientation(struct typec_port *port)
{
return port->orientation;
}
EXPORT_SYMBOL_GPL(typec_get_orientation);
/**
* typec_set_mode - Set mode of operation for USB Type-C connector
* @port: USB Type-C connector
* @mode: Accessory Mode, USB Operation or Safe State
*
* Configure @port for Accessory Mode @mode. This function will configure the
* muxes needed for @mode.
*/
int typec_set_mode(struct typec_port *port, int mode)
{
return 0;
}
EXPORT_SYMBOL_GPL(typec_set_mode);
/* --------------------------------------- */
/**
* typec_get_drvdata - Return private driver data pointer
* @port: USB Type-C port
*/
void *typec_get_drvdata(struct typec_port *port)
{
return dev_get_priv(&port->dev);
}
EXPORT_SYMBOL_GPL(typec_get_drvdata);
/**
* typec_register_port - Register a USB Type-C Port
* @parent: Parent device
* @cap: Description of the port
*
* Registers a device for USB Type-C Port described in @cap.
*
* Returns handle to the port on success or ERR_PTR on failure.
*/
struct typec_port *typec_register_port(struct udevice *parent,
const struct typec_capability *cap)
{
struct typec_port *port;
struct typec_platdata *plat;
int id;
char *name;
port = kzalloc(sizeof(*port), GFP_KERNEL);
if (!port)
return ERR_PTR(-ENOMEM);
id = get_port_id();
if (id < 0) {
kfree(port);
return ERR_PTR(id);
}
switch (cap->type) {
case TYPEC_PORT_SRC:
port->pwr_role = TYPEC_SOURCE;
port->vconn_role = TYPEC_SOURCE;
break;
case TYPEC_PORT_SNK:
port->pwr_role = TYPEC_SINK;
port->vconn_role = TYPEC_SINK;
break;
case TYPEC_PORT_DRP:
if (cap->prefer_role != TYPEC_NO_PREFERRED_ROLE)
port->pwr_role = cap->prefer_role;
else
port->pwr_role = TYPEC_SINK;
break;
}
switch (cap->data) {
case TYPEC_PORT_DFP:
port->data_role = TYPEC_HOST;
break;
case TYPEC_PORT_UFP:
port->data_role = TYPEC_DEVICE;
break;
case TYPEC_PORT_DRD:
if (cap->prefer_role == TYPEC_SOURCE)
port->data_role = TYPEC_HOST;
else
port->data_role = TYPEC_DEVICE;
break;
}
mutex_init(&port->port_type_lock);
port->id = id;
port->ops = cap->ops;
port->port_type = cap->type;
port->prefer_role = cap->prefer_role;
port->dev.parent = parent;
name = kzalloc(16, GFP_KERNEL);
snprintf(name, 16, "port%d", id);
port->dev.name = name;
plat = kzalloc(sizeof(*plat), GFP_KERNEL);
if (!plat)
return ERR_PTR(-ENOMEM);
plat->type = &typec_port_dev_type;
port->dev.platdata = plat;
port->dev.priv = cap->driver_data;
INIT_LIST_HEAD(&port->dev.uclass_node);
INIT_LIST_HEAD(&port->dev.child_head);
INIT_LIST_HEAD(&port->dev.sibling_node);
port->cap = kmemdup(cap, sizeof(*cap), GFP_KERNEL);
if (!port->cap) {
return ERR_PTR(-ENOMEM);
}
return port;
}
EXPORT_SYMBOL_GPL(typec_register_port);
/**
* typec_unregister_port - Unregister a USB Type-C Port
* @port: The port to be unregistered
*
* Unregister device created with typec_register_port().
*/
void typec_unregister_port(struct typec_port *port)
{
if (!IS_ERR_OR_NULL(port))
device_unregister(&port->dev);
}
EXPORT_SYMBOL_GPL(typec_unregister_port);