]> git.kernelconcepts.de Git - karo-tx-linux.git/commitdiff
usb: typec: Add support for UCSI interface
authorHeikki Krogerus <heikki.krogerus@linux.intel.com>
Fri, 16 Jun 2017 08:21:24 +0000 (11:21 +0300)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Tue, 27 Jun 2017 15:55:45 +0000 (17:55 +0200)
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@linux.intel.com>
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/typec/Kconfig
drivers/usb/typec/Makefile
drivers/usb/typec/ucsi/Kconfig [new file with mode: 0644]
drivers/usb/typec/ucsi/Makefile [new file with mode: 0644]
drivers/usb/typec/ucsi/debug.h [new file with mode: 0644]
drivers/usb/typec/ucsi/trace.c [new file with mode: 0644]
drivers/usb/typec/ucsi/trace.h [new file with mode: 0644]
drivers/usb/typec/ucsi/ucsi.c [new file with mode: 0644]
drivers/usb/typec/ucsi/ucsi.h [new file with mode: 0644]

index dfcfe459b7cf5605d4a00abc78e402c6e2468445..bc1b7745f1d4f3e73fc74b80a475d2bf02bcded5 100644 (file)
@@ -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
index b9cb862221af39f36b28701576cec2011fccb845..bc214f15f1b5580111a93bbfb253143c6f2a15a8 100644 (file)
@@ -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 (file)
index 0000000..da4c5c3
--- /dev/null
@@ -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 (file)
index 0000000..87dd6ee
--- /dev/null
@@ -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 (file)
index 0000000..e4d8fc7
--- /dev/null
@@ -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 (file)
index 0000000..006f65c
--- /dev/null
@@ -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 (file)
index 0000000..98b4044
--- /dev/null
@@ -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 (file)
index 0000000..714c5bc
--- /dev/null
@@ -0,0 +1,790 @@
+/*
+ * USB Type-C Connector System Software Interface driver
+ *
+ * Copyright (C) 2017, Intel Corporation
+ * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/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@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 (file)
index 0000000..6b0d2f0
--- /dev/null
@@ -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 */