Re: [PATCH v4 1/2] usb: typec: Add support for UCSI interface

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Fri, Jun 16, 2017 at 11:21:24AM +0300, Heikki Krogerus wrote:
> UCSI - USB Type-C Connector System Software Interface - is a
> specification that defines set of registers and data
> structures for controlling the USB Type-C ports. It's
> designed for systems where an embedded controller (EC) is in
> charge of the USB Type-C PHY or USB Power Delivery
> controller. It is designed for systems with EC, but it is
> not limited to them, and for example some USB Power Delivery
> controllers will use it as their direct control interface.
> 
> With UCSI the EC (or USB PD controller) acts as the port
> manager, implementing all USB Type-C and Power Delivery state
> machines. The OS can use the interfaces for reading the
> status of the ports and controlling basic operations like
> role swapping.
> 
> The UCSI specification highlights the fact that it does not
> define the interface method (PCI/I2C/ACPI/etc.).
> Therefore the driver is implemented as library and every
> supported interface method needs its own driver. Driver for
> ACPI is provided in separate patch following this one.
> 
> The initial driver includes support for all required
> features from UCSI specification version 1.0 (getting
> connector capabilities and status, and support for power and
> data role swapping), but none of the optional UCSI features
> (alternate modes, power source capabilities, and cable
> capabilities).
> 
> Signed-off-by: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx>

I looked at this way too often, so I am sure I am missing something
really obvious (except for the u64 data:48 field, but I just assume
you know what you are doing there).

Reviewed-by: Guenter Roeck <linux@xxxxxxxxxxxx>

> ---
>  drivers/usb/typec/Kconfig       |   2 +
>  drivers/usb/typec/Makefile      |   1 +
>  drivers/usb/typec/ucsi/Kconfig  |  23 ++
>  drivers/usb/typec/ucsi/Makefile |   7 +
>  drivers/usb/typec/ucsi/debug.h  |  64 ++++
>  drivers/usb/typec/ucsi/trace.c  |   2 +
>  drivers/usb/typec/ucsi/trace.h  | 143 ++++++++
>  drivers/usb/typec/ucsi/ucsi.c   | 790 ++++++++++++++++++++++++++++++++++++++++
>  drivers/usb/typec/ucsi/ucsi.h   | 335 +++++++++++++++++
>  9 files changed, 1367 insertions(+)
>  create mode 100644 drivers/usb/typec/ucsi/Kconfig
>  create mode 100644 drivers/usb/typec/ucsi/Makefile
>  create mode 100644 drivers/usb/typec/ucsi/debug.h
>  create mode 100644 drivers/usb/typec/ucsi/trace.c
>  create mode 100644 drivers/usb/typec/ucsi/trace.h
>  create mode 100644 drivers/usb/typec/ucsi/ucsi.c
>  create mode 100644 drivers/usb/typec/ucsi/ucsi.h
> 
> diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig
> index dfcfe459b7cf..bc1b7745f1d4 100644
> --- a/drivers/usb/typec/Kconfig
> +++ b/drivers/usb/typec/Kconfig
> @@ -19,4 +19,6 @@ config TYPEC_WCOVE
>  	  To compile this driver as module, choose M here: the module will be
>  	  called typec_wcove
>  
> +source "drivers/usb/typec/ucsi/Kconfig"
> +
>  endmenu
> diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile
> index b9cb862221af..bc214f15f1b5 100644
> --- a/drivers/usb/typec/Makefile
> +++ b/drivers/usb/typec/Makefile
> @@ -1,2 +1,3 @@
>  obj-$(CONFIG_TYPEC)		+= typec.o
>  obj-$(CONFIG_TYPEC_WCOVE)	+= typec_wcove.o
> +obj-$(CONFIG_TYPEC_UCSI)	+= ucsi/
> diff --git a/drivers/usb/typec/ucsi/Kconfig b/drivers/usb/typec/ucsi/Kconfig
> new file mode 100644
> index 000000000000..da4c5c3d8870
> --- /dev/null
> +++ b/drivers/usb/typec/ucsi/Kconfig
> @@ -0,0 +1,23 @@
> +config TYPEC_UCSI
> +	tristate "USB Type-C Connector System Software Interface driver"
> +	depends on !CPU_BIG_ENDIAN
> +	select TYPEC
> +	help
> +	  USB Type-C Connector System Software Interface (UCSI) is a
> +	  specification for an interface that allows the operating system to
> +	  control the USB Type-C ports. On UCSI system the USB Type-C ports
> +	  function autonomously by default, but in order to get the status of
> +	  the ports and support basic operations like role swapping, the driver
> +	  is required. UCSI is available on most of the new Intel based systems
> +	  that are equipped with Embedded Controller and USB Type-C ports.
> +
> +	  UCSI specification does not define the interface method, so depending
> +	  on the platform, ACPI, PCI, I2C, etc. may be used. Therefore this
> +	  driver only provides the core part, and separate drivers are needed
> +	  for every supported interface method.
> +
> +	  The UCSI specification can be downloaded from:
> +	  http://www.intel.com/content/www/us/en/io/universal-serial-bus/usb-type-c-ucsi-spec.html
> +
> +	  To compile the driver as a module, choose M here: the module will be
> +	  called typec_ucsi.
> diff --git a/drivers/usb/typec/ucsi/Makefile b/drivers/usb/typec/ucsi/Makefile
> new file mode 100644
> index 000000000000..87dd6ee6c9f3
> --- /dev/null
> +++ b/drivers/usb/typec/ucsi/Makefile
> @@ -0,0 +1,7 @@
> +CFLAGS_trace.o			:= -I$(src)
> +
> +obj-$(CONFIG_TYPEC_UCSI)	+= typec_ucsi.o
> +
> +typec_ucsi-y			:= ucsi.o
> +
> +typec_ucsi-$(CONFIG_FTRACE)	+= trace.o
> diff --git a/drivers/usb/typec/ucsi/debug.h b/drivers/usb/typec/ucsi/debug.h
> new file mode 100644
> index 000000000000..e4d8fc763e6c
> --- /dev/null
> +++ b/drivers/usb/typec/ucsi/debug.h
> @@ -0,0 +1,64 @@
> +#ifndef __UCSI_DEBUG_H
> +#define __UCSI_DEBUG_H
> +
> +#include "ucsi.h"
> +
> +static const char * const ucsi_cmd_strs[] = {
> +	[0]				= "Unknown command",
> +	[UCSI_PPM_RESET]		= "PPM_RESET",
> +	[UCSI_CANCEL]			= "CANCEL",
> +	[UCSI_CONNECTOR_RESET]		= "CONNECTOR_RESET",
> +	[UCSI_ACK_CC_CI]		= "ACK_CC_CI",
> +	[UCSI_SET_NOTIFICATION_ENABLE]	= "SET_NOTIFICATION_ENABLE",
> +	[UCSI_GET_CAPABILITY]		= "GET_CAPABILITY",
> +	[UCSI_GET_CONNECTOR_CAPABILITY]	= "GET_CONNECTOR_CAPABILITY",
> +	[UCSI_SET_UOM]			= "SET_UOM",
> +	[UCSI_SET_UOR]			= "SET_UOR",
> +	[UCSI_SET_PDM]			= "SET_PDM",
> +	[UCSI_SET_PDR]			= "SET_PDR",
> +	[UCSI_GET_ALTERNATE_MODES]	= "GET_ALTERNATE_MODES",
> +	[UCSI_GET_CAM_SUPPORTED]	= "GET_CAM_SUPPORTED",
> +	[UCSI_GET_CURRENT_CAM]		= "GET_CURRENT_CAM",
> +	[UCSI_SET_NEW_CAM]		= "SET_NEW_CAM",
> +	[UCSI_GET_PDOS]			= "GET_PDOS",
> +	[UCSI_GET_CABLE_PROPERTY]	= "GET_CABLE_PROPERTY",
> +	[UCSI_GET_CONNECTOR_STATUS]	= "GET_CONNECTOR_STATUS",
> +	[UCSI_GET_ERROR_STATUS]		= "GET_ERROR_STATUS",
> +};
> +
> +static inline const char *ucsi_cmd_str(u64 raw_cmd)
> +{
> +	u8 cmd = raw_cmd & GENMASK(7, 0);
> +
> +	return ucsi_cmd_strs[(cmd >= ARRAY_SIZE(ucsi_cmd_strs)) ? 0 : cmd];
> +}
> +
> +static const char * const ucsi_ack_strs[] = {
> +	[0]				= "",
> +	[UCSI_ACK_EVENT]		= "event",
> +	[UCSI_ACK_CMD]			= "command",
> +};
> +
> +static inline const char *ucsi_ack_str(u8 ack)
> +{
> +	return ucsi_ack_strs[(ack >= ARRAY_SIZE(ucsi_ack_strs)) ? 0 : ack];
> +}
> +
> +static inline const char *ucsi_cci_str(u32 cci)
> +{
> +	if (cci & GENMASK(7, 0)) {
> +		if (cci & BIT(29))
> +			return "Event pending (ACK completed)";
> +		if (cci & BIT(31))
> +			return "Event pending (command completed)";
> +		return "Connector Change";
> +	}
> +	if (cci & BIT(29))
> +		return "ACK completed";
> +	if (cci & BIT(31))
> +		return "Command completed";
> +
> +	return "";
> +}
> +
> +#endif /* __UCSI_DEBUG_H */
> diff --git a/drivers/usb/typec/ucsi/trace.c b/drivers/usb/typec/ucsi/trace.c
> new file mode 100644
> index 000000000000..006f65c72a34
> --- /dev/null
> +++ b/drivers/usb/typec/ucsi/trace.c
> @@ -0,0 +1,2 @@
> +#define CREATE_TRACE_POINTS
> +#include "trace.h"
> diff --git a/drivers/usb/typec/ucsi/trace.h b/drivers/usb/typec/ucsi/trace.h
> new file mode 100644
> index 000000000000..98b404404834
> --- /dev/null
> +++ b/drivers/usb/typec/ucsi/trace.h
> @@ -0,0 +1,143 @@
> +
> +#undef TRACE_SYSTEM
> +#define TRACE_SYSTEM ucsi
> +
> +#if !defined(__UCSI_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
> +#define __UCSI_TRACE_H
> +
> +#include <linux/tracepoint.h>
> +#include "ucsi.h"
> +#include "debug.h"
> +
> +DECLARE_EVENT_CLASS(ucsi_log_ack,
> +	TP_PROTO(u8 ack),
> +	TP_ARGS(ack),
> +	TP_STRUCT__entry(
> +		__field(u8, ack)
> +	),
> +	TP_fast_assign(
> +		__entry->ack = ack;
> +	),
> +	TP_printk("ACK %s", ucsi_ack_str(__entry->ack))
> +);
> +
> +DEFINE_EVENT(ucsi_log_ack, ucsi_ack,
> +	TP_PROTO(u8 ack),
> +	TP_ARGS(ack)
> +);
> +
> +DECLARE_EVENT_CLASS(ucsi_log_control,
> +	TP_PROTO(struct ucsi_control *ctrl),
> +	TP_ARGS(ctrl),
> +	TP_STRUCT__entry(
> +		__field(u64, ctrl)
> +	),
> +	TP_fast_assign(
> +		__entry->ctrl = ctrl->raw_cmd;
> +	),
> +	TP_printk("control=%08llx (%s)", __entry->ctrl,
> +		ucsi_cmd_str(__entry->ctrl))
> +);
> +
> +DEFINE_EVENT(ucsi_log_control, ucsi_command,
> +	TP_PROTO(struct ucsi_control *ctrl),
> +	TP_ARGS(ctrl)
> +);
> +
> +DECLARE_EVENT_CLASS(ucsi_log_command,
> +	TP_PROTO(struct ucsi_control *ctrl, int ret),
> +	TP_ARGS(ctrl, ret),
> +	TP_STRUCT__entry(
> +		__field(u64, ctrl)
> +		__field(int, ret)
> +	),
> +	TP_fast_assign(
> +		__entry->ctrl = ctrl->raw_cmd;
> +		__entry->ret = ret;
> +	),
> +	TP_printk("%s -> %s (err=%d)", ucsi_cmd_str(__entry->ctrl),
> +		__entry->ret < 0 ? "FAIL" : "OK",
> +		__entry->ret < 0 ? __entry->ret : 0)
> +);
> +
> +DEFINE_EVENT(ucsi_log_command, ucsi_run_command,
> +	TP_PROTO(struct ucsi_control *ctrl, int ret),
> +	TP_ARGS(ctrl, ret)
> +);
> +
> +DEFINE_EVENT(ucsi_log_command, ucsi_reset_ppm,
> +	TP_PROTO(struct ucsi_control *ctrl, int ret),
> +	TP_ARGS(ctrl, ret)
> +);
> +
> +DECLARE_EVENT_CLASS(ucsi_log_cci,
> +	TP_PROTO(u32 cci),
> +	TP_ARGS(cci),
> +	TP_STRUCT__entry(
> +		__field(u32, cci)
> +	),
> +	TP_fast_assign(
> +		__entry->cci = cci;
> +	),
> +	TP_printk("CCI=%08x %s", __entry->cci, ucsi_cci_str(__entry->cci))
> +);
> +
> +DEFINE_EVENT(ucsi_log_cci, ucsi_notify,
> +	TP_PROTO(u32 cci),
> +	TP_ARGS(cci)
> +);
> +
> +DECLARE_EVENT_CLASS(ucsi_log_connector_status,
> +	TP_PROTO(int port, struct ucsi_connector_status *status),
> +	TP_ARGS(port, status),
> +	TP_STRUCT__entry(
> +		__field(int, port)
> +		__field(u16, change)
> +		__field(u8, opmode)
> +		__field(u8, connected)
> +		__field(u8, pwr_dir)
> +		__field(u8, partner_flags)
> +		__field(u8, partner_type)
> +		__field(u32, request_data_obj)
> +		__field(u8, bc_status)
> +	),
> +	TP_fast_assign(
> +		__entry->port = port - 1;
> +		__entry->change = status->change;
> +		__entry->opmode = status->pwr_op_mode;
> +		__entry->connected = status->connected;
> +		__entry->pwr_dir = status->pwr_dir;
> +		__entry->partner_flags = status->partner_flags;
> +		__entry->partner_type = status->partner_type;
> +		__entry->request_data_obj = status->request_data_obj;
> +		__entry->bc_status = status->bc_status;
> +	),
> +	TP_printk("port%d status: change=%04x, opmode=%x, connected=%d, "
> +		"sourcing=%d, partner_flags=%x, partner_type=%x, "
> +		"request_data_obj=%08x, BC status=%x", __entry->port,
> +		__entry->change, __entry->opmode, __entry->connected,
> +		__entry->pwr_dir, __entry->partner_flags, __entry->partner_type,
> +		__entry->request_data_obj, __entry->bc_status)
> +);
> +
> +DEFINE_EVENT(ucsi_log_connector_status, ucsi_connector_change,
> +	TP_PROTO(int port, struct ucsi_connector_status *status),
> +	TP_ARGS(port, status)
> +);
> +
> +DEFINE_EVENT(ucsi_log_connector_status, ucsi_register_port,
> +	TP_PROTO(int port, struct ucsi_connector_status *status),
> +	TP_ARGS(port, status)
> +);
> +
> +#endif /* __UCSI_TRACE_H */
> +
> +/* This part must be outside protection */
> +
> +#undef TRACE_INCLUDE_PATH
> +#define TRACE_INCLUDE_PATH .
> +
> +#undef TRACE_INCLUDE_FILE
> +#define TRACE_INCLUDE_FILE trace
> +
> +#include <trace/define_trace.h>
> diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c
> new file mode 100644
> index 000000000000..714c5bcedf2b
> --- /dev/null
> +++ b/drivers/usb/typec/ucsi/ucsi.c
> @@ -0,0 +1,790 @@
> +/*
> + * USB Type-C Connector System Software Interface driver
> + *
> + * Copyright (C) 2017, Intel Corporation
> + * Author: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx>
> + *
> + * 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/completion.h>
> +#include <linux/property.h>
> +#include <linux/device.h>
> +#include <linux/module.h>
> +#include <linux/delay.h>
> +#include <linux/slab.h>
> +#include <linux/usb/typec.h>
> +
> +#include "ucsi.h"
> +#include "trace.h"
> +
> +#define to_ucsi_connector(_cap_) container_of(_cap_, struct ucsi_connector, \
> +					      typec_cap)
> +
> +/*
> + * UCSI_TIMEOUT_MS - PPM communication timeout
> + *
> + * Ideally we could use MIN_TIME_TO_RESPOND_WITH_BUSY (which is defined in UCSI
> + * specification) here as reference, but unfortunately we can't. It is very
> + * difficult to estimate the time it takes for the system to process the command
> + * before it is actually passed to the PPM.
> + */
> +#define UCSI_TIMEOUT_MS		1000
> +
> +/*
> + * UCSI_SWAP_TIMEOUT_MS - Timeout for role swap requests
> + *
> + * 5 seconds is close to the time it takes for CapsCounter to reach 0, so even
> + * if the PPM does not generate Connector Change events before that with
> + * partners that do not support USB Power Delivery, this should still work.
> + */
> +#define UCSI_SWAP_TIMEOUT_MS	5000
> +
> +enum ucsi_status {
> +	UCSI_IDLE = 0,
> +	UCSI_BUSY,
> +	UCSI_ERROR,
> +};
> +
> +struct ucsi_connector {
> +	int num;
> +
> +	struct ucsi *ucsi;
> +	struct work_struct work;
> +	struct completion complete;
> +
> +	struct typec_port *port;
> +	struct typec_partner *partner;
> +
> +	struct typec_capability typec_cap;
> +
> +	struct ucsi_connector_status status;
> +	struct ucsi_connector_capability cap;
> +};
> +
> +struct ucsi {
> +	struct device *dev;
> +	struct ucsi_ppm *ppm;
> +
> +	enum ucsi_status status;
> +	struct completion complete;
> +	struct ucsi_capability cap;
> +	struct ucsi_connector *connector;
> +
> +	struct work_struct work;
> +
> +	/* PPM Communication lock */
> +	struct mutex ppm_lock;
> +
> +	/* PPM communication flags */
> +	unsigned long flags;
> +#define EVENT_PENDING	0
> +#define COMMAND_PENDING	1
> +#define ACK_PENDING	2
> +};
> +
> +static inline int ucsi_sync(struct ucsi *ucsi)
> +{
> +	if (ucsi->ppm && ucsi->ppm->sync)
> +		return ucsi->ppm->sync(ucsi->ppm);
> +	return 0;
> +}
> +
> +static int ucsi_command(struct ucsi *ucsi, struct ucsi_control *ctrl)
> +{
> +	int ret;
> +
> +	trace_ucsi_command(ctrl);
> +
> +	set_bit(COMMAND_PENDING, &ucsi->flags);
> +
> +	ret = ucsi->ppm->cmd(ucsi->ppm, ctrl);
> +	if (ret)
> +		goto err_clear_flag;
> +
> +	if (!wait_for_completion_timeout(&ucsi->complete,
> +					 msecs_to_jiffies(UCSI_TIMEOUT_MS))) {
> +		dev_warn(ucsi->dev, "PPM NOT RESPONDING\n");
> +		ret = -ETIMEDOUT;
> +	}
> +
> +err_clear_flag:
> +	clear_bit(COMMAND_PENDING, &ucsi->flags);
> +
> +	return ret;
> +}
> +
> +static int ucsi_ack(struct ucsi *ucsi, u8 ack)
> +{
> +	struct ucsi_control ctrl;
> +	int ret;
> +
> +	trace_ucsi_ack(ack);
> +
> +	set_bit(ACK_PENDING, &ucsi->flags);
> +
> +	UCSI_CMD_ACK(ctrl, ack);
> +	ret = ucsi->ppm->cmd(ucsi->ppm, &ctrl);
> +	if (ret)
> +		goto out_clear_bit;
> +
> +	/* Waiting for ACK with ACK CMD, but not with EVENT for now */
> +	if (ack == UCSI_ACK_EVENT)
> +		goto out_clear_bit;
> +
> +	if (!wait_for_completion_timeout(&ucsi->complete,
> +					 msecs_to_jiffies(UCSI_TIMEOUT_MS)))
> +		ret = -ETIMEDOUT;
> +
> +out_clear_bit:
> +	clear_bit(ACK_PENDING, &ucsi->flags);
> +
> +	if (ret)
> +		dev_err(ucsi->dev, "%s: failed\n", __func__);
> +
> +	return ret;
> +}
> +
> +static int ucsi_run_command(struct ucsi *ucsi, struct ucsi_control *ctrl,
> +			    void *data, size_t size)
> +{
> +	struct ucsi_control _ctrl;
> +	u8 data_length;
> +	u16 error;
> +	int ret;
> +
> +	ret = ucsi_command(ucsi, ctrl);
> +	if (ret)
> +		goto err;
> +
> +	switch (ucsi->status) {
> +	case UCSI_IDLE:
> +		ret = ucsi_sync(ucsi);
> +		if (ret)
> +			dev_warn(ucsi->dev, "%s: sync failed\n", __func__);
> +
> +		if (data)
> +			memcpy(data, ucsi->ppm->data->message_in, size);
> +
> +		data_length = ucsi->ppm->data->cci.data_length;
> +
> +		ret = ucsi_ack(ucsi, UCSI_ACK_CMD);
> +		if (!ret)
> +			ret = data_length;
> +		break;
> +	case UCSI_BUSY:
> +		/* The caller decides whether to cancel or not */
> +		ret = -EBUSY;
> +		break;
> +	case UCSI_ERROR:
> +		ret = ucsi_ack(ucsi, UCSI_ACK_CMD);
> +		if (ret)
> +			break;
> +
> +		_ctrl.raw_cmd = 0;
> +		_ctrl.cmd.cmd = UCSI_GET_ERROR_STATUS;
> +		ret = ucsi_command(ucsi, &_ctrl);
> +		if (ret) {
> +			dev_err(ucsi->dev, "reading error failed!\n");
> +			break;
> +		}
> +
> +		memcpy(&error, ucsi->ppm->data->message_in, sizeof(error));
> +
> +		/* Something has really gone wrong */
> +		if (WARN_ON(ucsi->status == UCSI_ERROR)) {
> +			ret = -ENODEV;
> +			break;
> +		}
> +
> +		ret = ucsi_ack(ucsi, UCSI_ACK_CMD);
> +		if (ret)
> +			break;
> +
> +		switch (error) {
> +		case UCSI_ERROR_INCOMPATIBLE_PARTNER:
> +			ret = -EOPNOTSUPP;
> +			break;
> +		case UCSI_ERROR_CC_COMMUNICATION_ERR:
> +			ret = -ECOMM;
> +			break;
> +		case UCSI_ERROR_CONTRACT_NEGOTIATION_FAIL:
> +			ret = -EPROTO;
> +			break;
> +		case UCSI_ERROR_DEAD_BATTERY:
> +			dev_warn(ucsi->dev, "Dead battery condition!\n");
> +			ret = -EPERM;
> +			break;
> +		/* The following mean a bug in this driver */
> +		case UCSI_ERROR_INVALID_CON_NUM:
> +		case UCSI_ERROR_UNREGONIZED_CMD:
> +		case UCSI_ERROR_INVALID_CMD_ARGUMENT:
> +			dev_warn(ucsi->dev,
> +				 "%s: possible UCSI driver bug - error 0x%x\n",
> +				 __func__, error);
> +			ret = -EINVAL;
> +			break;
> +		default:
> +			dev_warn(ucsi->dev,
> +				 "%s: error without status\n", __func__);
> +			ret = -EIO;
> +			break;
> +		}
> +		break;
> +	}
> +
> +err:
> +	trace_ucsi_run_command(ctrl, ret);
> +
> +	return ret;
> +}
> +
> +/* -------------------------------------------------------------------------- */
> +
> +static void ucsi_pwr_opmode_change(struct ucsi_connector *con)
> +{
> +	switch (con->status.pwr_op_mode) {
> +	case UCSI_CONSTAT_PWR_OPMODE_PD:
> +		typec_set_pwr_opmode(con->port, TYPEC_PWR_MODE_PD);
> +		break;
> +	case UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5:
> +		typec_set_pwr_opmode(con->port, TYPEC_PWR_MODE_1_5A);
> +		break;
> +	case UCSI_CONSTAT_PWR_OPMODE_TYPEC3_0:
> +		typec_set_pwr_opmode(con->port, TYPEC_PWR_MODE_3_0A);
> +		break;
> +	default:
> +		typec_set_pwr_opmode(con->port, TYPEC_PWR_MODE_USB);
> +		break;
> +	}
> +}
> +
> +static int ucsi_register_partner(struct ucsi_connector *con)
> +{
> +	struct typec_partner_desc partner;
> +
> +	if (con->partner)
> +		return 0;
> +
> +	memset(&partner, 0, sizeof(partner));
> +
> +	switch (con->status.partner_type) {
> +	case UCSI_CONSTAT_PARTNER_TYPE_DEBUG:
> +		partner.accessory = TYPEC_ACCESSORY_DEBUG;
> +		break;
> +	case UCSI_CONSTAT_PARTNER_TYPE_AUDIO:
> +		partner.accessory = TYPEC_ACCESSORY_AUDIO;
> +		break;
> +	default:
> +		break;
> +	}
> +
> +	partner.usb_pd = con->status.pwr_op_mode == UCSI_CONSTAT_PWR_OPMODE_PD;
> +
> +	con->partner = typec_register_partner(con->port, &partner);
> +	if (!con->partner) {
> +		dev_err(con->ucsi->dev, "con%d: failed to register partner\n",
> +			con->num);
> +		return -ENODEV;
> +	}
> +
> +	return 0;
> +}
> +
> +static void ucsi_unregister_partner(struct ucsi_connector *con)
> +{
> +	typec_unregister_partner(con->partner);
> +	con->partner = NULL;
> +}
> +
> +static void ucsi_connector_change(struct work_struct *work)
> +{
> +	struct ucsi_connector *con = container_of(work, struct ucsi_connector,
> +						  work);
> +	struct ucsi *ucsi = con->ucsi;
> +	struct ucsi_control ctrl;
> +	int ret;
> +
> +	mutex_lock(&ucsi->ppm_lock);
> +
> +	UCSI_CMD_GET_CONNECTOR_STATUS(ctrl, con->num);
> +	ret = ucsi_run_command(ucsi, &ctrl, &con->status, sizeof(con->status));
> +	if (ret < 0) {
> +		dev_err(ucsi->dev, "%s: GET_CONNECTOR_STATUS failed (%d)\n",
> +			__func__, ret);
> +		goto out_unlock;
> +	}
> +
> +	if (con->status.change & UCSI_CONSTAT_POWER_OPMODE_CHANGE)
> +		ucsi_pwr_opmode_change(con);
> +
> +	if (con->status.change & UCSI_CONSTAT_POWER_DIR_CHANGE) {
> +		typec_set_pwr_role(con->port, con->status.pwr_dir);
> +
> +		/* Complete pending power role swap */
> +		if (!completion_done(&con->complete))
> +			complete(&con->complete);
> +	}
> +
> +	if (con->status.change & UCSI_CONSTAT_PARTNER_CHANGE) {
> +		switch (con->status.partner_type) {
> +		case UCSI_CONSTAT_PARTNER_TYPE_UFP:
> +			typec_set_data_role(con->port, TYPEC_HOST);
> +			break;
> +		case UCSI_CONSTAT_PARTNER_TYPE_DFP:
> +			typec_set_data_role(con->port, TYPEC_DEVICE);
> +			break;
> +		default:
> +			break;
> +		}
> +
> +		/* Complete pending data role swap */
> +		if (!completion_done(&con->complete))
> +			complete(&con->complete);
> +	}
> +
> +	if (con->status.change & UCSI_CONSTAT_CONNECT_CHANGE) {
> +		if (con->status.connected)
> +			ucsi_register_partner(con);
> +		else
> +			ucsi_unregister_partner(con);
> +	}
> +
> +	ret = ucsi_ack(ucsi, UCSI_ACK_EVENT);
> +	if (ret)
> +		dev_err(ucsi->dev, "%s: ACK failed (%d)", __func__, ret);
> +
> +	trace_ucsi_connector_change(con->num, &con->status);
> +
> +out_unlock:
> +	clear_bit(EVENT_PENDING, &ucsi->flags);
> +	mutex_unlock(&ucsi->ppm_lock);
> +}
> +
> +/**
> + * ucsi_notify - PPM notification handler
> + * @ucsi: Source UCSI Interface for the notifications
> + *
> + * Handle notifications from PPM of @ucsi.
> + */
> +void ucsi_notify(struct ucsi *ucsi)
> +{
> +	struct ucsi_cci *cci;
> +
> +	/* There is no requirement to sync here, but no harm either. */
> +	ucsi_sync(ucsi);
> +
> +	cci = &ucsi->ppm->data->cci;
> +
> +	if (cci->error)
> +		ucsi->status = UCSI_ERROR;
> +	else if (cci->busy)
> +		ucsi->status = UCSI_BUSY;
> +	else
> +		ucsi->status = UCSI_IDLE;
> +
> +	if (cci->cmd_complete && test_bit(COMMAND_PENDING, &ucsi->flags)) {
> +		complete(&ucsi->complete);
> +	} else if (cci->ack_complete && test_bit(ACK_PENDING, &ucsi->flags)) {
> +		complete(&ucsi->complete);
> +	} else if (cci->connector_change) {
> +		struct ucsi_connector *con;
> +
> +		con = &ucsi->connector[cci->connector_change - 1];
> +
> +		if (!test_and_set_bit(EVENT_PENDING, &ucsi->flags))
> +			schedule_work(&con->work);
> +	}
> +
> +	trace_ucsi_notify(ucsi->ppm->data->raw_cci);
> +}
> +EXPORT_SYMBOL_GPL(ucsi_notify);
> +
> +/* -------------------------------------------------------------------------- */
> +
> +static int ucsi_reset_connector(struct ucsi_connector *con, bool hard)
> +{
> +	struct ucsi_control ctrl;
> +
> +	UCSI_CMD_CONNECTOR_RESET(ctrl, con, hard);
> +
> +	return ucsi_run_command(con->ucsi, &ctrl, NULL, 0);
> +}
> +
> +static int ucsi_reset_ppm(struct ucsi *ucsi)
> +{
> +	struct ucsi_control ctrl;
> +	unsigned long tmo;
> +	int ret;
> +
> +	ctrl.raw_cmd = 0;
> +	ctrl.cmd.cmd = UCSI_PPM_RESET;
> +	trace_ucsi_command(&ctrl);
> +	ret = ucsi->ppm->cmd(ucsi->ppm, &ctrl);
> +	if (ret)
> +		goto err;
> +
> +	tmo = jiffies + msecs_to_jiffies(UCSI_TIMEOUT_MS);
> +
> +	do {
> +		/* Here sync is critical. */
> +		ret = ucsi_sync(ucsi);
> +		if (ret)
> +			goto err;
> +
> +		if (ucsi->ppm->data->cci.reset_complete)
> +			break;
> +
> +		/* If the PPM is still doing something else, reset it again. */
> +		if (ucsi->ppm->data->raw_cci) {
> +			dev_warn_ratelimited(ucsi->dev,
> +				"Failed to reset PPM! Trying again..\n");
> +
> +			trace_ucsi_command(&ctrl);
> +			ret = ucsi->ppm->cmd(ucsi->ppm, &ctrl);
> +			if (ret)
> +				goto err;
> +		}
> +
> +		/* Letting the PPM settle down. */
> +		msleep(20);
> +
> +		ret = -ETIMEDOUT;
> +	} while (time_is_after_jiffies(tmo));
> +
> +err:
> +	trace_ucsi_reset_ppm(&ctrl, ret);
> +
> +	return ret;
> +}
> +
> +static int ucsi_role_cmd(struct ucsi_connector *con, struct ucsi_control *ctrl)
> +{
> +	int ret;
> +
> +	ret = ucsi_run_command(con->ucsi, ctrl, NULL, 0);
> +	if (ret == -ETIMEDOUT) {
> +		struct ucsi_control c;
> +
> +		/* PPM most likely stopped responding. Resetting everything. */
> +		ucsi_reset_ppm(con->ucsi);
> +
> +		UCSI_CMD_SET_NTFY_ENABLE(c, UCSI_ENABLE_NTFY_ALL);
> +		ucsi_run_command(con->ucsi, &c, NULL, 0);
> +
> +		ucsi_reset_connector(con, true);
> +	}
> +
> +	return ret;
> +}
> +
> +static int
> +ucsi_dr_swap(const struct typec_capability *cap, enum typec_data_role role)
> +{
> +	struct ucsi_connector *con = to_ucsi_connector(cap);
> +	struct ucsi_control ctrl;
> +	int ret = 0;
> +
> +	if (!con->partner)
> +		return -ENOTCONN;
> +
> +	mutex_lock(&con->ucsi->ppm_lock);
> +
> +	if ((con->status.partner_type == UCSI_CONSTAT_PARTNER_TYPE_DFP &&
> +	     role == TYPEC_DEVICE) ||
> +	    (con->status.partner_type == UCSI_CONSTAT_PARTNER_TYPE_UFP &&
> +	     role == TYPEC_HOST))
> +		goto out_unlock;
> +
> +	UCSI_CMD_SET_UOR(ctrl, con, role);
> +	ret = ucsi_role_cmd(con, &ctrl);
> +	if (ret < 0)
> +		goto out_unlock;
> +
> +	mutex_unlock(&con->ucsi->ppm_lock);
> +
> +	if (!wait_for_completion_timeout(&con->complete,
> +					msecs_to_jiffies(UCSI_SWAP_TIMEOUT_MS)))
> +		return -ETIMEDOUT;
> +
> +	return 0;
> +
> +out_unlock:
> +	mutex_unlock(&con->ucsi->ppm_lock);
> +
> +	return ret;
> +}
> +
> +static int
> +ucsi_pr_swap(const struct typec_capability *cap, enum typec_role role)
> +{
> +	struct ucsi_connector *con = to_ucsi_connector(cap);
> +	struct ucsi_control ctrl;
> +	int ret = 0;
> +
> +	if (!con->partner)
> +		return -ENOTCONN;
> +
> +	mutex_lock(&con->ucsi->ppm_lock);
> +
> +	if (con->status.pwr_dir == role)
> +		goto out_unlock;
> +
> +	UCSI_CMD_SET_PDR(ctrl, con, role);
> +	ret = ucsi_role_cmd(con, &ctrl);
> +	if (ret < 0)
> +		goto out_unlock;
> +
> +	mutex_unlock(&con->ucsi->ppm_lock);
> +
> +	if (!wait_for_completion_timeout(&con->complete,
> +					msecs_to_jiffies(UCSI_SWAP_TIMEOUT_MS)))
> +		return -ETIMEDOUT;
> +
> +	mutex_lock(&con->ucsi->ppm_lock);
> +
> +	/* Something has gone wrong while swapping the role */
> +	if (con->status.pwr_op_mode != UCSI_CONSTAT_PWR_OPMODE_PD) {
> +		ucsi_reset_connector(con, true);
> +		ret = -EPROTO;
> +	}
> +
> +out_unlock:
> +	mutex_unlock(&con->ucsi->ppm_lock);
> +
> +	return ret;
> +}
> +
> +static struct fwnode_handle *ucsi_find_fwnode(struct ucsi_connector *con)
> +{
> +	struct fwnode_handle *fwnode;
> +	int i = 1;
> +
> +	device_for_each_child_node(con->ucsi->dev, fwnode)
> +		if (i++ == con->num)
> +			return fwnode;
> +	return NULL;
> +}
> +
> +static int ucsi_register_port(struct ucsi *ucsi, int index)
> +{
> +	struct ucsi_connector *con = &ucsi->connector[index];
> +	struct typec_capability *cap = &con->typec_cap;
> +	enum typec_accessory *accessory = cap->accessory;
> +	struct ucsi_control ctrl;
> +	int ret;
> +
> +	INIT_WORK(&con->work, ucsi_connector_change);
> +	init_completion(&con->complete);
> +	con->num = index + 1;
> +	con->ucsi = ucsi;
> +
> +	/* Get connector capability */
> +	UCSI_CMD_GET_CONNECTOR_CAPABILITY(ctrl, con->num);
> +	ret = ucsi_run_command(ucsi, &ctrl, &con->cap, sizeof(con->cap));
> +	if (ret < 0)
> +		return ret;
> +
> +	if (con->cap.op_mode & UCSI_CONCAP_OPMODE_DRP)
> +		cap->type = TYPEC_PORT_DRP;
> +	else if (con->cap.op_mode & UCSI_CONCAP_OPMODE_DFP)
> +		cap->type = TYPEC_PORT_DFP;
> +	else if (con->cap.op_mode & UCSI_CONCAP_OPMODE_UFP)
> +		cap->type = TYPEC_PORT_UFP;
> +
> +	cap->revision = ucsi->cap.typec_version;
> +	cap->pd_revision = ucsi->cap.pd_version;
> +	cap->prefer_role = TYPEC_NO_PREFERRED_ROLE;
> +
> +	if (con->cap.op_mode & UCSI_CONCAP_OPMODE_AUDIO_ACCESSORY)
> +		*accessory++ = TYPEC_ACCESSORY_AUDIO;
> +	if (con->cap.op_mode & UCSI_CONCAP_OPMODE_DEBUG_ACCESSORY)
> +		*accessory = TYPEC_ACCESSORY_DEBUG;
> +
> +	cap->fwnode = ucsi_find_fwnode(con);
> +	cap->dr_set = ucsi_dr_swap;
> +	cap->pr_set = ucsi_pr_swap;
> +
> +	/* Register the connector */
> +	con->port = typec_register_port(ucsi->dev, cap);
> +	if (!con->port)
> +		return -ENODEV;
> +
> +	/* Get the status */
> +	UCSI_CMD_GET_CONNECTOR_STATUS(ctrl, con->num);
> +	ret = ucsi_run_command(ucsi, &ctrl, &con->status, sizeof(con->status));
> +	if (ret < 0) {
> +		dev_err(ucsi->dev, "con%d: failed to get status\n", con->num);
> +		return 0;
> +	}
> +
> +	ucsi_pwr_opmode_change(con);
> +	typec_set_pwr_role(con->port, con->status.pwr_dir);
> +
> +	switch (con->status.partner_type) {
> +	case UCSI_CONSTAT_PARTNER_TYPE_UFP:
> +		typec_set_data_role(con->port, TYPEC_HOST);
> +		break;
> +	case UCSI_CONSTAT_PARTNER_TYPE_DFP:
> +		typec_set_data_role(con->port, TYPEC_DEVICE);
> +		break;
> +	default:
> +		break;
> +	}
> +
> +	/* Check if there is already something connected */
> +	if (con->status.connected)
> +		ucsi_register_partner(con);
> +
> +	trace_ucsi_register_port(con->num, &con->status);
> +
> +	return 0;
> +}
> +
> +static void ucsi_init(struct work_struct *work)
> +{
> +	struct ucsi *ucsi = container_of(work, struct ucsi, work);
> +	struct ucsi_connector *con;
> +	struct ucsi_control ctrl;
> +	int ret;
> +	int i;
> +
> +	mutex_lock(&ucsi->ppm_lock);
> +
> +	/* Reset the PPM */
> +	ret = ucsi_reset_ppm(ucsi);
> +	if (ret) {
> +		dev_err(ucsi->dev, "failed to reset PPM!\n");
> +		goto err;
> +	}
> +
> +	/* Enable basic notifications */
> +	UCSI_CMD_SET_NTFY_ENABLE(ctrl, UCSI_ENABLE_NTFY_CMD_COMPLETE |
> +					UCSI_ENABLE_NTFY_ERROR);
> +	ret = ucsi_run_command(ucsi, &ctrl, NULL, 0);
> +	if (ret < 0)
> +		goto err_reset;
> +
> +	/* Get PPM capabilities */
> +	UCSI_CMD_GET_CAPABILITY(ctrl);
> +	ret = ucsi_run_command(ucsi, &ctrl, &ucsi->cap, sizeof(ucsi->cap));
> +	if (ret < 0)
> +		goto err_reset;
> +
> +	if (!ucsi->cap.num_connectors) {
> +		ret = -ENODEV;
> +		goto err_reset;
> +	}
> +
> +	/* Allocate the connectors. Released in ucsi_unregister_ppm() */
> +	ucsi->connector = kcalloc(ucsi->cap.num_connectors + 1,
> +				  sizeof(*ucsi->connector), GFP_KERNEL);
> +	if (!ucsi->connector) {
> +		ret = -ENOMEM;
> +		goto err_reset;
> +	}
> +
> +	/* Register all connectors */
> +	for (i = 0; i < ucsi->cap.num_connectors; i++) {
> +		ret = ucsi_register_port(ucsi, i);
> +		if (ret)
> +			goto err_unregister;
> +	}
> +
> +	/* Enable all notifications */
> +	UCSI_CMD_SET_NTFY_ENABLE(ctrl, UCSI_ENABLE_NTFY_ALL);
> +	ret = ucsi_run_command(ucsi, &ctrl, NULL, 0);
> +	if (ret < 0)
> +		goto err_unregister;
> +
> +	mutex_unlock(&ucsi->ppm_lock);
> +
> +	return;
> +
> +err_unregister:
> +	for (con = ucsi->connector; con->port; con++) {
> +		ucsi_unregister_partner(con);
> +		typec_unregister_port(con->port);
> +		con->port = NULL;
> +	}
> +
> +err_reset:
> +	ucsi_reset_ppm(ucsi);
> +err:
> +	mutex_unlock(&ucsi->ppm_lock);
> +	dev_err(ucsi->dev, "PPM init failed (%d)\n", ret);
> +}
> +
> +/**
> + * ucsi_register_ppm - Register UCSI PPM Interface
> + * @dev: Device interface to the PPM
> + * @ppm: The PPM interface
> + *
> + * Allocates UCSI instance, associates it with @ppm and returns it to the
> + * caller, and schedules initialization of the interface.
> + */
> +struct ucsi *ucsi_register_ppm(struct device *dev, struct ucsi_ppm *ppm)
> +{
> +	struct ucsi *ucsi;
> +
> +	ucsi = kzalloc(sizeof(*ucsi), GFP_KERNEL);
> +	if (!ucsi)
> +		return ERR_PTR(-ENOMEM);
> +
> +	INIT_WORK(&ucsi->work, ucsi_init);
> +	init_completion(&ucsi->complete);
> +	mutex_init(&ucsi->ppm_lock);
> +
> +	ucsi->dev = dev;
> +	ucsi->ppm = ppm;
> +
> +	/*
> +	 * Communication with the PPM takes a lot of time. It is not reasonable
> +	 * to initialize the driver here. Using a work for now.
> +	 */
> +	queue_work(system_long_wq, &ucsi->work);
> +
> +	return ucsi;
> +}
> +EXPORT_SYMBOL_GPL(ucsi_register_ppm);
> +
> +/**
> + * ucsi_unregister_ppm - Unregister UCSI PPM Interface
> + * @ucsi: struct ucsi associated with the PPM
> + *
> + * Unregister UCSI PPM that was created with ucsi_register().
> + */
> +void ucsi_unregister_ppm(struct ucsi *ucsi)
> +{
> +	struct ucsi_control ctrl;
> +	int i;
> +
> +	/* Make sure that we are not in the middle of driver initialization */
> +	cancel_work_sync(&ucsi->work);
> +
> +	mutex_lock(&ucsi->ppm_lock);
> +
> +	/* Disable everything except command complete notification */
> +	UCSI_CMD_SET_NTFY_ENABLE(ctrl, UCSI_ENABLE_NTFY_CMD_COMPLETE)
> +	ucsi_run_command(ucsi, &ctrl, NULL, 0);
> +
> +	mutex_unlock(&ucsi->ppm_lock);
> +
> +	for (i = 0; i < ucsi->cap.num_connectors; i++) {
> +		cancel_work_sync(&ucsi->connector[i].work);
> +		ucsi_unregister_partner(&ucsi->connector[i]);
> +		typec_unregister_port(ucsi->connector[i].port);
> +	}
> +
> +	ucsi_reset_ppm(ucsi);
> +
> +	kfree(ucsi->connector);
> +	kfree(ucsi);
> +}
> +EXPORT_SYMBOL_GPL(ucsi_unregister_ppm);
> +
> +MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx>");
> +MODULE_LICENSE("GPL v2");
> +MODULE_DESCRIPTION("USB Type-C Connector System Software Interface driver");
> diff --git a/drivers/usb/typec/ucsi/ucsi.h b/drivers/usb/typec/ucsi/ucsi.h
> new file mode 100644
> index 000000000000..6b0d2f0918c6
> --- /dev/null
> +++ b/drivers/usb/typec/ucsi/ucsi.h
> @@ -0,0 +1,335 @@
> +
> +#ifndef __DRIVER_USB_TYPEC_UCSI_H
> +#define __DRIVER_USB_TYPEC_UCSI_H
> +
> +#include <linux/bitops.h>
> +#include <linux/types.h>
> +
> +/* -------------------------------------------------------------------------- */
> +
> +/* Command Status and Connector Change Indication (CCI) data structure */
> +struct ucsi_cci {
> +	u8:1; /* reserved */
> +	u8 connector_change:7;
> +	u8 data_length;
> +	u16:9; /* reserved */
> +	u16 not_supported:1;
> +	u16 cancel_complete:1;
> +	u16 reset_complete:1;
> +	u16 busy:1;
> +	u16 ack_complete:1;
> +	u16 error:1;
> +	u16 cmd_complete:1;
> +} __packed;
> +
> +/* Default fields in CONTROL data structure */
> +struct ucsi_command {
> +	u8 cmd;
> +	u8 length;
> +	u64 data:48;
> +} __packed;
> +
> +/* ACK Command structure */
> +struct ucsi_ack_cmd {
> +	u8 cmd;
> +	u8 length;
> +	u8 cci_ack:1;
> +	u8 cmd_ack:1;
> +	u8:6; /* reserved */
> +} __packed;
> +
> +/* Connector Reset Command structure */
> +struct ucsi_con_rst {
> +	u8 cmd;
> +	u8 length;
> +	u8 con_num:7;
> +	u8 hard_reset:1;
> +} __packed;
> +
> +/* Set USB Operation Mode Command structure */
> +struct ucsi_uor_cmd {
> +	u8 cmd;
> +	u8 length;
> +	u16 con_num:7;
> +	u16 role:3;
> +#define UCSI_UOR_ROLE_DFP			BIT(0)
> +#define UCSI_UOR_ROLE_UFP			BIT(1)
> +#define UCSI_UOR_ROLE_DRP			BIT(2)
> +	u16:6; /* reserved */
> +} __packed;
> +
> +struct ucsi_control {
> +	union {
> +		u64 raw_cmd;
> +		struct ucsi_command cmd;
> +		struct ucsi_uor_cmd uor;
> +		struct ucsi_ack_cmd ack;
> +		struct ucsi_con_rst con_rst;
> +	};
> +};
> +
> +#define __UCSI_CMD(_ctrl_, _cmd_)					\
> +{									\
> +	(_ctrl_).raw_cmd = 0;						\
> +	(_ctrl_).cmd.cmd = _cmd_;					\
> +}
> +
> +/* Helper for preparing ucsi_control for CONNECTOR_RESET command. */
> +#define UCSI_CMD_CONNECTOR_RESET(_ctrl_, _con_, _hard_)			\
> +{									\
> +	__UCSI_CMD(_ctrl_, UCSI_CONNECTOR_RESET)			\
> +	(_ctrl_).con_rst.con_num = (_con_)->num;			\
> +	(_ctrl_).con_rst.hard_reset = _hard_;				\
> +}
> +
> +/* Helper for preparing ucsi_control for ACK_CC_CI command. */
> +#define UCSI_CMD_ACK(_ctrl_, _ack_)					\
> +{									\
> +	__UCSI_CMD(_ctrl_, UCSI_ACK_CC_CI)				\
> +	(_ctrl_).ack.cci_ack = ((_ack_) == UCSI_ACK_EVENT);		\
> +	(_ctrl_).ack.cmd_ack = ((_ack_) == UCSI_ACK_CMD);		\
> +}
> +
> +/* Helper for preparing ucsi_control for SET_NOTIFY_ENABLE command. */
> +#define UCSI_CMD_SET_NTFY_ENABLE(_ctrl_, _ntfys_)			\
> +{									\
> +	__UCSI_CMD(_ctrl_, UCSI_SET_NOTIFICATION_ENABLE)		\
> +	(_ctrl_).cmd.data = _ntfys_;					\
> +}
> +
> +/* Helper for preparing ucsi_control for GET_CAPABILITY command. */
> +#define UCSI_CMD_GET_CAPABILITY(_ctrl_)					\
> +{									\
> +	__UCSI_CMD(_ctrl_, UCSI_GET_CAPABILITY)				\
> +}
> +
> +/* Helper for preparing ucsi_control for GET_CONNECTOR_CAPABILITY command. */
> +#define UCSI_CMD_GET_CONNECTOR_CAPABILITY(_ctrl_, _con_)		\
> +{									\
> +	__UCSI_CMD(_ctrl_, UCSI_GET_CONNECTOR_CAPABILITY)		\
> +	(_ctrl_).cmd.data = _con_;					\
> +}
> +
> +/* Helper for preparing ucsi_control for GET_CONNECTOR_STATUS command. */
> +#define UCSI_CMD_GET_CONNECTOR_STATUS(_ctrl_, _con_)			\
> +{									\
> +	__UCSI_CMD(_ctrl_, UCSI_GET_CONNECTOR_STATUS)			\
> +	(_ctrl_).cmd.data = _con_;					\
> +}
> +
> +#define __UCSI_ROLE(_ctrl_, _cmd_, _con_num_)				\
> +{									\
> +	__UCSI_CMD(_ctrl_, _cmd_)					\
> +	(_ctrl_).uor.con_num = _con_num_;				\
> +	(_ctrl_).uor.role = UCSI_UOR_ROLE_DRP;				\
> +}
> +
> +/* Helper for preparing ucsi_control for SET_UOR command. */
> +#define UCSI_CMD_SET_UOR(_ctrl_, _con_, _role_)				\
> +{									\
> +	__UCSI_ROLE(_ctrl_, UCSI_SET_UOR, (_con_)->num)		\
> +	(_ctrl_).uor.role |= (_role_) == TYPEC_HOST ? UCSI_UOR_ROLE_DFP : \
> +			  UCSI_UOR_ROLE_UFP;				\
> +}
> +
> +/* Helper for preparing ucsi_control for SET_PDR command. */
> +#define UCSI_CMD_SET_PDR(_ctrl_, _con_, _role_)			\
> +{									\
> +	__UCSI_ROLE(_ctrl_, UCSI_SET_PDR, (_con_)->num)		\
> +	(_ctrl_).uor.role |= (_role_) == TYPEC_SOURCE ? UCSI_UOR_ROLE_DFP : \
> +			UCSI_UOR_ROLE_UFP;				\
> +}
> +
> +/* Commands */
> +#define UCSI_PPM_RESET			0x01
> +#define UCSI_CANCEL			0x02
> +#define UCSI_CONNECTOR_RESET		0x03
> +#define UCSI_ACK_CC_CI			0x04
> +#define UCSI_SET_NOTIFICATION_ENABLE	0x05
> +#define UCSI_GET_CAPABILITY		0x06
> +#define UCSI_GET_CONNECTOR_CAPABILITY	0x07
> +#define UCSI_SET_UOM			0x08
> +#define UCSI_SET_UOR			0x09
> +#define UCSI_SET_PDM			0x0a
> +#define UCSI_SET_PDR			0x0b
> +#define UCSI_GET_ALTERNATE_MODES	0x0c
> +#define UCSI_GET_CAM_SUPPORTED		0x0d
> +#define UCSI_GET_CURRENT_CAM		0x0e
> +#define UCSI_SET_NEW_CAM		0x0f
> +#define UCSI_GET_PDOS			0x10
> +#define UCSI_GET_CABLE_PROPERTY		0x11
> +#define UCSI_GET_CONNECTOR_STATUS	0x12
> +#define UCSI_GET_ERROR_STATUS		0x13
> +
> +/* ACK_CC_CI commands */
> +#define UCSI_ACK_EVENT			1
> +#define UCSI_ACK_CMD			2
> +
> +/* Bits for SET_NOTIFICATION_ENABLE command */
> +#define UCSI_ENABLE_NTFY_CMD_COMPLETE		BIT(0)
> +#define UCSI_ENABLE_NTFY_EXT_PWR_SRC_CHANGE	BIT(1)
> +#define UCSI_ENABLE_NTFY_PWR_OPMODE_CHANGE	BIT(2)
> +#define UCSI_ENABLE_NTFY_CAP_CHANGE		BIT(5)
> +#define UCSI_ENABLE_NTFY_PWR_LEVEL_CHANGE	BIT(6)
> +#define UCSI_ENABLE_NTFY_PD_RESET_COMPLETE	BIT(7)
> +#define UCSI_ENABLE_NTFY_CAM_CHANGE		BIT(8)
> +#define UCSI_ENABLE_NTFY_BAT_STATUS_CHANGE	BIT(9)
> +#define UCSI_ENABLE_NTFY_PARTNER_CHANGE		BIT(11)
> +#define UCSI_ENABLE_NTFY_PWR_DIR_CHANGE		BIT(12)
> +#define UCSI_ENABLE_NTFY_CONNECTOR_CHANGE	BIT(14)
> +#define UCSI_ENABLE_NTFY_ERROR			BIT(15)
> +#define UCSI_ENABLE_NTFY_ALL			0xdbe7
> +
> +/* Error information returned by PPM in response to GET_ERROR_STATUS command. */
> +#define UCSI_ERROR_UNREGONIZED_CMD		BIT(0)
> +#define UCSI_ERROR_INVALID_CON_NUM		BIT(1)
> +#define UCSI_ERROR_INVALID_CMD_ARGUMENT		BIT(2)
> +#define UCSI_ERROR_INCOMPATIBLE_PARTNER		BIT(3)
> +#define UCSI_ERROR_CC_COMMUNICATION_ERR		BIT(4)
> +#define UCSI_ERROR_DEAD_BATTERY			BIT(5)
> +#define UCSI_ERROR_CONTRACT_NEGOTIATION_FAIL	BIT(6)
> +
> +/* Data structure filled by PPM in response to GET_CAPABILITY command. */
> +struct ucsi_capability {
> +	u32 attributes;
> +#define UCSI_CAP_ATTR_DISABLE_STATE		BIT(0)
> +#define UCSI_CAP_ATTR_BATTERY_CHARGING		BIT(1)
> +#define UCSI_CAP_ATTR_USB_PD			BIT(2)
> +#define UCSI_CAP_ATTR_TYPEC_CURRENT		BIT(6)
> +#define UCSI_CAP_ATTR_POWER_AC_SUPPLY		BIT(8)
> +#define UCSI_CAP_ATTR_POWER_OTHER		BIT(10)
> +#define UCSI_CAP_ATTR_POWER_VBUS		BIT(14)
> +	u32 num_connectors:8;
> +	u32 features:24;
> +#define UCSI_CAP_SET_UOM			BIT(0)
> +#define UCSI_CAP_SET_PDM			BIT(1)
> +#define UCSI_CAP_ALT_MODE_DETAILS		BIT(2)
> +#define UCSI_CAP_ALT_MODE_OVERRIDE		BIT(3)
> +#define UCSI_CAP_PDO_DETAILS			BIT(4)
> +#define UCSI_CAP_CABLE_DETAILS			BIT(5)
> +#define UCSI_CAP_EXT_SUPPLY_NOTIFICATIONS	BIT(6)
> +#define UCSI_CAP_PD_RESET			BIT(7)
> +	u8 num_alt_modes;
> +	u8 reserved;
> +	u16 bc_version;
> +	u16 pd_version;
> +	u16 typec_version;
> +} __packed;
> +
> +/* Data structure filled by PPM in response to GET_CONNECTOR_CAPABILITY cmd. */
> +struct ucsi_connector_capability {
> +	u8 op_mode;
> +#define UCSI_CONCAP_OPMODE_DFP			BIT(0)
> +#define UCSI_CONCAP_OPMODE_UFP			BIT(1)
> +#define UCSI_CONCAP_OPMODE_DRP			BIT(2)
> +#define UCSI_CONCAP_OPMODE_AUDIO_ACCESSORY	BIT(3)
> +#define UCSI_CONCAP_OPMODE_DEBUG_ACCESSORY	BIT(4)
> +#define UCSI_CONCAP_OPMODE_USB2			BIT(5)
> +#define UCSI_CONCAP_OPMODE_USB3			BIT(6)
> +#define UCSI_CONCAP_OPMODE_ALT_MODE		BIT(7)
> +	u8 provider:1;
> +	u8 consumer:1;
> +	u8:6; /* reserved */
> +} __packed;
> +
> +struct ucsi_altmode {
> +	u16 svid;
> +	u32 mid;
> +} __packed;
> +
> +/* Data structure filled by PPM in response to GET_CABLE_PROPERTY command. */
> +struct ucsi_cable_property {
> +	u16 speed_supported;
> +	u8 current_capability;
> +	u8 vbus_in_cable:1;
> +	u8 active_cable:1;
> +	u8 directionality:1;
> +	u8 plug_type:2;
> +#define UCSI_CABLE_PROPERTY_PLUG_TYPE_A		0
> +#define UCSI_CABLE_PROPERTY_PLUG_TYPE_B		1
> +#define UCSI_CABLE_PROPERTY_PLUG_TYPE_C		2
> +#define UCSI_CABLE_PROPERTY_PLUG_OTHER		3
> +	u8 mode_support:1;
> +	u8:2; /* reserved */
> +	u8 latency:4;
> +	u8:4; /* reserved */
> +} __packed;
> +
> +/* Data structure filled by PPM in response to GET_CONNECTOR_STATUS command. */
> +struct ucsi_connector_status {
> +	u16 change;
> +#define UCSI_CONSTAT_EXT_SUPPLY_CHANGE		BIT(1)
> +#define UCSI_CONSTAT_POWER_OPMODE_CHANGE	BIT(2)
> +#define UCSI_CONSTAT_PDOS_CHANGE		BIT(5)
> +#define UCSI_CONSTAT_POWER_LEVEL_CHANGE		BIT(6)
> +#define UCSI_CONSTAT_PD_RESET_COMPLETE		BIT(7)
> +#define UCSI_CONSTAT_CAM_CHANGE			BIT(8)
> +#define UCSI_CONSTAT_BC_CHANGE			BIT(9)
> +#define UCSI_CONSTAT_PARTNER_CHANGE		BIT(11)
> +#define UCSI_CONSTAT_POWER_DIR_CHANGE		BIT(12)
> +#define UCSI_CONSTAT_CONNECT_CHANGE		BIT(14)
> +#define UCSI_CONSTAT_ERROR			BIT(15)
> +	u16 pwr_op_mode:3;
> +#define UCSI_CONSTAT_PWR_OPMODE_NONE		0
> +#define UCSI_CONSTAT_PWR_OPMODE_DEFAULT		1
> +#define UCSI_CONSTAT_PWR_OPMODE_BC		2
> +#define UCSI_CONSTAT_PWR_OPMODE_PD		3
> +#define UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5	4
> +#define UCSI_CONSTAT_PWR_OPMODE_TYPEC3_0	5
> +	u16 connected:1;
> +	u16 pwr_dir:1;
> +	u16 partner_flags:8;
> +#define UCSI_CONSTAT_PARTNER_FLAG_USB		BIT(0)
> +#define UCSI_CONSTAT_PARTNER_FLAG_ALT_MODE	BIT(1)
> +	u16 partner_type:3;
> +#define UCSI_CONSTAT_PARTNER_TYPE_DFP		1
> +#define UCSI_CONSTAT_PARTNER_TYPE_UFP		2
> +#define UCSI_CONSTAT_PARTNER_TYPE_CABLE		3 /* Powered Cable */
> +#define UCSI_CONSTAT_PARTNER_TYPE_CABLE_AND_UFP	4 /* Powered Cable */
> +#define UCSI_CONSTAT_PARTNER_TYPE_DEBUG		5
> +#define UCSI_CONSTAT_PARTNER_TYPE_AUDIO		6
> +	u32 request_data_obj;
> +	u8 bc_status:2;
> +#define UCSI_CONSTAT_BC_NOT_CHARGING		0
> +#define UCSI_CONSTAT_BC_NOMINAL_CHARGING	1
> +#define UCSI_CONSTAT_BC_SLOW_CHARGING		2
> +#define UCSI_CONSTAT_BC_TRICKLE_CHARGING	3
> +	u8 provider_cap_limit_reason:4;
> +#define UCSI_CONSTAT_CAP_PWR_LOWERED		0
> +#define UCSI_CONSTAT_CAP_PWR_BUDGET_LIMIT	1
> +	u8:2; /* reserved */
> +} __packed;
> +
> +/* -------------------------------------------------------------------------- */
> +
> +struct ucsi;
> +
> +struct ucsi_data {
> +	u16 version;
> +	u16 reserved;
> +	union {
> +		u32 raw_cci;
> +		struct ucsi_cci cci;
> +	};
> +	struct ucsi_control ctrl;
> +	u32 message_in[4];
> +	u32 message_out[4];
> +} __packed;
> +
> +/*
> + * struct ucsi_ppm - Interface to UCSI Platform Policy Manager
> + * @data: memory location to the UCSI data structures
> + * @cmd: UCSI command execution routine
> + * @sync: Refresh UCSI mailbox (the data structures)
> + */
> +struct ucsi_ppm {
> +	struct ucsi_data *data;
> +	int (*cmd)(struct ucsi_ppm *, struct ucsi_control *);
> +	int (*sync)(struct ucsi_ppm *);
> +};
> +
> +struct ucsi *ucsi_register_ppm(struct device *dev, struct ucsi_ppm *ppm);
> +void ucsi_unregister_ppm(struct ucsi *ucsi);
> +void ucsi_notify(struct ucsi *ucsi);
> +
> +#endif /* __DRIVER_USB_TYPEC_UCSI_H */
> -- 
> 2.11.0
> 
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux