On Mon, Jun 12, 2017 at 05:40:08PM +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.kroge...@linux.intel.com>

Couple of nitpicks, but I am also still concerned about the use
of bit fields, epecially when the bit field size does not align
with register sizes. Maybe my concerns are overblown; if so, I
would kindly ask for someone from Intel (or any other listener)
to provide a Reviewed-by: tag.

Thanks,
Guenter

> ---
>  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   | 330 +++++++++++++++++
>  9 files changed, 1362 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..30a24e8fadc7
> --- /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, "
> +             "reguest_data_obj=%08x, BC status=%x", __entry->port,

s/reguest_data_obj/request_data_obj/

> +             __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.kroge...@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/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.kroge...@linux.intel.com>");
> +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..f8c9b4d60b05
> --- /dev/null
> +++ b/drivers/usb/typec/ucsi/ucsi.h
> @@ -0,0 +1,330 @@
> +
> +#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;
> +} __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)
> +} __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_;                                         \

(_ctrl_) ?

> +}
> +
> +/* 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_) ?

> +     _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);                   \

... and so on

> +}
> +
> +/* 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)
> +     u8 num_connectors;
> +     u32 features:24;

Still wonder what this kind of construct is doing.
Is it guaranteed that it only allocates 24 bit, overlapping
with num_conectors ? If so, why not "u32 num_connectors:8;" ?
Or does it allocate 32 bit, with 8 bit unused ?
In the latter case, it might be useful to fill the field with
"u32 unused:8;" or similar for clarification.

> +#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;
> +} __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;

And another 4 bit reserved ?

Overall the mixed use of bit fields makes me a bit uneasy.
The bit fields often don't align with the real world (it is
unlikely that struct ucsi_cable_property, as defined by the
protocol, has a length of 36 bit). Since, presumably, the
structures are supposed to reflect the protocol, I would find
it useful to have the structures filled with "reserved" fields.

That is of course just my personal opinion, but I think it would
be _really_ helpful in cases where an incomplete bit field is followed
by more variables, like further above. It would also help understanding
the complete protocol (is the above a 5-byte field per UCSI ? or 6 ? 8 ?).

> +} __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
> +} __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 majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to