summaryrefslogtreecommitdiff
path: root/drivers/usb
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb')
-rw-r--r--drivers/usb/typec/Kconfig2
-rw-r--r--drivers/usb/typec/Makefile1
-rw-r--r--drivers/usb/typec/ucsi/Kconfig23
-rw-r--r--drivers/usb/typec/ucsi/Makefile7
-rw-r--r--drivers/usb/typec/ucsi/debug.h64
-rw-r--r--drivers/usb/typec/ucsi/trace.c2
-rw-r--r--drivers/usb/typec/ucsi/trace.h143
-rw-r--r--drivers/usb/typec/ucsi/ucsi.c790
-rw-r--r--drivers/usb/typec/ucsi/ucsi.h335
9 files changed, 1367 insertions, 0 deletions
diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig
index dfcfe459b7cf..bc1b7745f1d4 100644
--- a/drivers/usb/typec/Kconfig
+++ b/drivers/usb/typec/Kconfig
@@ -19,4 +19,6 @@ config TYPEC_WCOVE
To compile this driver as module, choose M here: the module will be
called typec_wcove
+source "drivers/usb/typec/ucsi/Kconfig"
+
endmenu
diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile
index b9cb862221af..bc214f15f1b5 100644
--- a/drivers/usb/typec/Makefile
+++ b/drivers/usb/typec/Makefile
@@ -1,2 +1,3 @@
obj-$(CONFIG_TYPEC) += typec.o
obj-$(CONFIG_TYPEC_WCOVE) += typec_wcove.o
+obj-$(CONFIG_TYPEC_UCSI) += ucsi/
diff --git a/drivers/usb/typec/ucsi/Kconfig b/drivers/usb/typec/ucsi/Kconfig
new file mode 100644
index 000000000000..da4c5c3d8870
--- /dev/null
+++ b/drivers/usb/typec/ucsi/Kconfig
@@ -0,0 +1,23 @@
+config TYPEC_UCSI
+ tristate "USB Type-C Connector System Software Interface driver"
+ depends on !CPU_BIG_ENDIAN
+ select TYPEC
+ help
+ USB Type-C Connector System Software Interface (UCSI) is a
+ specification for an interface that allows the operating system to
+ control the USB Type-C ports. On UCSI system the USB Type-C ports
+ function autonomously by default, but in order to get the status of
+ the ports and support basic operations like role swapping, the driver
+ is required. UCSI is available on most of the new Intel based systems
+ that are equipped with Embedded Controller and USB Type-C ports.
+
+ UCSI specification does not define the interface method, so depending
+ on the platform, ACPI, PCI, I2C, etc. may be used. Therefore this
+ driver only provides the core part, and separate drivers are needed
+ for every supported interface method.
+
+ The UCSI specification can be downloaded from:
+ http://www.intel.com/content/www/us/en/io/universal-serial-bus/usb-type-c-ucsi-spec.html
+
+ To compile the driver as a module, choose M here: the module will be
+ called typec_ucsi.
diff --git a/drivers/usb/typec/ucsi/Makefile b/drivers/usb/typec/ucsi/Makefile
new file mode 100644
index 000000000000..87dd6ee6c9f3
--- /dev/null
+++ b/drivers/usb/typec/ucsi/Makefile
@@ -0,0 +1,7 @@
+CFLAGS_trace.o := -I$(src)
+
+obj-$(CONFIG_TYPEC_UCSI) += typec_ucsi.o
+
+typec_ucsi-y := ucsi.o
+
+typec_ucsi-$(CONFIG_FTRACE) += trace.o
diff --git a/drivers/usb/typec/ucsi/debug.h b/drivers/usb/typec/ucsi/debug.h
new file mode 100644
index 000000000000..e4d8fc763e6c
--- /dev/null
+++ b/drivers/usb/typec/ucsi/debug.h
@@ -0,0 +1,64 @@
+#ifndef __UCSI_DEBUG_H
+#define __UCSI_DEBUG_H
+
+#include "ucsi.h"
+
+static const char * const ucsi_cmd_strs[] = {
+ [0] = "Unknown command",
+ [UCSI_PPM_RESET] = "PPM_RESET",
+ [UCSI_CANCEL] = "CANCEL",
+ [UCSI_CONNECTOR_RESET] = "CONNECTOR_RESET",
+ [UCSI_ACK_CC_CI] = "ACK_CC_CI",
+ [UCSI_SET_NOTIFICATION_ENABLE] = "SET_NOTIFICATION_ENABLE",
+ [UCSI_GET_CAPABILITY] = "GET_CAPABILITY",
+ [UCSI_GET_CONNECTOR_CAPABILITY] = "GET_CONNECTOR_CAPABILITY",
+ [UCSI_SET_UOM] = "SET_UOM",
+ [UCSI_SET_UOR] = "SET_UOR",
+ [UCSI_SET_PDM] = "SET_PDM",
+ [UCSI_SET_PDR] = "SET_PDR",
+ [UCSI_GET_ALTERNATE_MODES] = "GET_ALTERNATE_MODES",
+ [UCSI_GET_CAM_SUPPORTED] = "GET_CAM_SUPPORTED",
+ [UCSI_GET_CURRENT_CAM] = "GET_CURRENT_CAM",
+ [UCSI_SET_NEW_CAM] = "SET_NEW_CAM",
+ [UCSI_GET_PDOS] = "GET_PDOS",
+ [UCSI_GET_CABLE_PROPERTY] = "GET_CABLE_PROPERTY",
+ [UCSI_GET_CONNECTOR_STATUS] = "GET_CONNECTOR_STATUS",
+ [UCSI_GET_ERROR_STATUS] = "GET_ERROR_STATUS",
+};
+
+static inline const char *ucsi_cmd_str(u64 raw_cmd)
+{
+ u8 cmd = raw_cmd & GENMASK(7, 0);
+
+ return ucsi_cmd_strs[(cmd >= ARRAY_SIZE(ucsi_cmd_strs)) ? 0 : cmd];
+}
+
+static const char * const ucsi_ack_strs[] = {
+ [0] = "",
+ [UCSI_ACK_EVENT] = "event",
+ [UCSI_ACK_CMD] = "command",
+};
+
+static inline const char *ucsi_ack_str(u8 ack)
+{
+ return ucsi_ack_strs[(ack >= ARRAY_SIZE(ucsi_ack_strs)) ? 0 : ack];
+}
+
+static inline const char *ucsi_cci_str(u32 cci)
+{
+ if (cci & GENMASK(7, 0)) {
+ if (cci & BIT(29))
+ return "Event pending (ACK completed)";
+ if (cci & BIT(31))
+ return "Event pending (command completed)";
+ return "Connector Change";
+ }
+ if (cci & BIT(29))
+ return "ACK completed";
+ if (cci & BIT(31))
+ return "Command completed";
+
+ return "";
+}
+
+#endif /* __UCSI_DEBUG_H */
diff --git a/drivers/usb/typec/ucsi/trace.c b/drivers/usb/typec/ucsi/trace.c
new file mode 100644
index 000000000000..006f65c72a34
--- /dev/null
+++ b/drivers/usb/typec/ucsi/trace.c
@@ -0,0 +1,2 @@
+#define CREATE_TRACE_POINTS
+#include "trace.h"
diff --git a/drivers/usb/typec/ucsi/trace.h b/drivers/usb/typec/ucsi/trace.h
new file mode 100644
index 000000000000..98b404404834
--- /dev/null
+++ b/drivers/usb/typec/ucsi/trace.h
@@ -0,0 +1,143 @@
+
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM ucsi
+
+#if !defined(__UCSI_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
+#define __UCSI_TRACE_H
+
+#include <linux/tracepoint.h>
+#include "ucsi.h"
+#include "debug.h"
+
+DECLARE_EVENT_CLASS(ucsi_log_ack,
+ TP_PROTO(u8 ack),
+ TP_ARGS(ack),
+ TP_STRUCT__entry(
+ __field(u8, ack)
+ ),
+ TP_fast_assign(
+ __entry->ack = ack;
+ ),
+ TP_printk("ACK %s", ucsi_ack_str(__entry->ack))
+);
+
+DEFINE_EVENT(ucsi_log_ack, ucsi_ack,
+ TP_PROTO(u8 ack),
+ TP_ARGS(ack)
+);
+
+DECLARE_EVENT_CLASS(ucsi_log_control,
+ TP_PROTO(struct ucsi_control *ctrl),
+ TP_ARGS(ctrl),
+ TP_STRUCT__entry(
+ __field(u64, ctrl)
+ ),
+ TP_fast_assign(
+ __entry->ctrl = ctrl->raw_cmd;
+ ),
+ TP_printk("control=%08llx (%s)", __entry->ctrl,
+ ucsi_cmd_str(__entry->ctrl))
+);
+
+DEFINE_EVENT(ucsi_log_control, ucsi_command,
+ TP_PROTO(struct ucsi_control *ctrl),
+ TP_ARGS(ctrl)
+);
+
+DECLARE_EVENT_CLASS(ucsi_log_command,
+ TP_PROTO(struct ucsi_control *ctrl, int ret),
+ TP_ARGS(ctrl, ret),
+ TP_STRUCT__entry(
+ __field(u64, ctrl)
+ __field(int, ret)
+ ),
+ TP_fast_assign(
+ __entry->ctrl = ctrl->raw_cmd;
+ __entry->ret = ret;
+ ),
+ TP_printk("%s -> %s (err=%d)", ucsi_cmd_str(__entry->ctrl),
+ __entry->ret < 0 ? "FAIL" : "OK",
+ __entry->ret < 0 ? __entry->ret : 0)
+);
+
+DEFINE_EVENT(ucsi_log_command, ucsi_run_command,
+ TP_PROTO(struct ucsi_control *ctrl, int ret),
+ TP_ARGS(ctrl, ret)
+);
+
+DEFINE_EVENT(ucsi_log_command, ucsi_reset_ppm,
+ TP_PROTO(struct ucsi_control *ctrl, int ret),
+ TP_ARGS(ctrl, ret)
+);
+
+DECLARE_EVENT_CLASS(ucsi_log_cci,
+ TP_PROTO(u32 cci),
+ TP_ARGS(cci),
+ TP_STRUCT__entry(
+ __field(u32, cci)
+ ),
+ TP_fast_assign(
+ __entry->cci = cci;
+ ),
+ TP_printk("CCI=%08x %s", __entry->cci, ucsi_cci_str(__entry->cci))
+);
+
+DEFINE_EVENT(ucsi_log_cci, ucsi_notify,
+ TP_PROTO(u32 cci),
+ TP_ARGS(cci)
+);
+
+DECLARE_EVENT_CLASS(ucsi_log_connector_status,
+ TP_PROTO(int port, struct ucsi_connector_status *status),
+ TP_ARGS(port, status),
+ TP_STRUCT__entry(
+ __field(int, port)
+ __field(u16, change)
+ __field(u8, opmode)
+ __field(u8, connected)
+ __field(u8, pwr_dir)
+ __field(u8, partner_flags)
+ __field(u8, partner_type)
+ __field(u32, request_data_obj)
+ __field(u8, bc_status)
+ ),
+ TP_fast_assign(
+ __entry->port = port - 1;
+ __entry->change = status->change;
+ __entry->opmode = status->pwr_op_mode;
+ __entry->connected = status->connected;
+ __entry->pwr_dir = status->pwr_dir;
+ __entry->partner_flags = status->partner_flags;
+ __entry->partner_type = status->partner_type;
+ __entry->request_data_obj = status->request_data_obj;
+ __entry->bc_status = status->bc_status;
+ ),
+ TP_printk("port%d status: change=%04x, opmode=%x, connected=%d, "
+ "sourcing=%d, partner_flags=%x, partner_type=%x, "
+ "request_data_obj=%08x, BC status=%x", __entry->port,
+ __entry->change, __entry->opmode, __entry->connected,
+ __entry->pwr_dir, __entry->partner_flags, __entry->partner_type,
+ __entry->request_data_obj, __entry->bc_status)
+);
+
+DEFINE_EVENT(ucsi_log_connector_status, ucsi_connector_change,
+ TP_PROTO(int port, struct ucsi_connector_status *status),
+ TP_ARGS(port, status)
+);
+
+DEFINE_EVENT(ucsi_log_connector_status, ucsi_register_port,
+ TP_PROTO(int port, struct ucsi_connector_status *status),
+ TP_ARGS(port, status)
+);
+
+#endif /* __UCSI_TRACE_H */
+
+/* This part must be outside protection */
+
+#undef TRACE_INCLUDE_PATH
+#define TRACE_INCLUDE_PATH .
+
+#undef TRACE_INCLUDE_FILE
+#define TRACE_INCLUDE_FILE trace
+
+#include <trace/define_trace.h>
diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c
new file mode 100644
index 000000000000..714c5bcedf2b
--- /dev/null
+++ b/drivers/usb/typec/ucsi/ucsi.c
@@ -0,0 +1,790 @@
+/*
+ * USB Type-C Connector System Software Interface driver
+ *
+ * Copyright (C) 2017, Intel Corporation
+ * Author: Heikki Krogerus <heikki.krogerus@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
index 000000000000..6b0d2f0918c6
--- /dev/null
+++ b/drivers/usb/typec/ucsi/ucsi.h
@@ -0,0 +1,335 @@
+
+#ifndef __DRIVER_USB_TYPEC_UCSI_H
+#define __DRIVER_USB_TYPEC_UCSI_H
+
+#include <linux/bitops.h>
+#include <linux/types.h>
+
+/* -------------------------------------------------------------------------- */
+
+/* Command Status and Connector Change Indication (CCI) data structure */
+struct ucsi_cci {
+ u8:1; /* reserved */
+ u8 connector_change:7;
+ u8 data_length;
+ u16:9; /* reserved */
+ u16 not_supported:1;
+ u16 cancel_complete:1;
+ u16 reset_complete:1;
+ u16 busy:1;
+ u16 ack_complete:1;
+ u16 error:1;
+ u16 cmd_complete:1;
+} __packed;
+
+/* Default fields in CONTROL data structure */
+struct ucsi_command {
+ u8 cmd;
+ u8 length;
+ u64 data:48;
+} __packed;
+
+/* ACK Command structure */
+struct ucsi_ack_cmd {
+ u8 cmd;
+ u8 length;
+ u8 cci_ack:1;
+ u8 cmd_ack:1;
+ u8:6; /* reserved */
+} __packed;
+
+/* Connector Reset Command structure */
+struct ucsi_con_rst {
+ u8 cmd;
+ u8 length;
+ u8 con_num:7;
+ u8 hard_reset:1;
+} __packed;
+
+/* Set USB Operation Mode Command structure */
+struct ucsi_uor_cmd {
+ u8 cmd;
+ u8 length;
+ u16 con_num:7;
+ u16 role:3;
+#define UCSI_UOR_ROLE_DFP BIT(0)
+#define UCSI_UOR_ROLE_UFP BIT(1)
+#define UCSI_UOR_ROLE_DRP BIT(2)
+ u16:6; /* reserved */
+} __packed;
+
+struct ucsi_control {
+ union {
+ u64 raw_cmd;
+ struct ucsi_command cmd;
+ struct ucsi_uor_cmd uor;
+ struct ucsi_ack_cmd ack;
+ struct ucsi_con_rst con_rst;
+ };
+};
+
+#define __UCSI_CMD(_ctrl_, _cmd_) \
+{ \
+ (_ctrl_).raw_cmd = 0; \
+ (_ctrl_).cmd.cmd = _cmd_; \
+}
+
+/* Helper for preparing ucsi_control for CONNECTOR_RESET command. */
+#define UCSI_CMD_CONNECTOR_RESET(_ctrl_, _con_, _hard_) \
+{ \
+ __UCSI_CMD(_ctrl_, UCSI_CONNECTOR_RESET) \
+ (_ctrl_).con_rst.con_num = (_con_)->num; \
+ (_ctrl_).con_rst.hard_reset = _hard_; \
+}
+
+/* Helper for preparing ucsi_control for ACK_CC_CI command. */
+#define UCSI_CMD_ACK(_ctrl_, _ack_) \
+{ \
+ __UCSI_CMD(_ctrl_, UCSI_ACK_CC_CI) \
+ (_ctrl_).ack.cci_ack = ((_ack_) == UCSI_ACK_EVENT); \
+ (_ctrl_).ack.cmd_ack = ((_ack_) == UCSI_ACK_CMD); \
+}
+
+/* Helper for preparing ucsi_control for SET_NOTIFY_ENABLE command. */
+#define UCSI_CMD_SET_NTFY_ENABLE(_ctrl_, _ntfys_) \
+{ \
+ __UCSI_CMD(_ctrl_, UCSI_SET_NOTIFICATION_ENABLE) \
+ (_ctrl_).cmd.data = _ntfys_; \
+}
+
+/* Helper for preparing ucsi_control for GET_CAPABILITY command. */
+#define UCSI_CMD_GET_CAPABILITY(_ctrl_) \
+{ \
+ __UCSI_CMD(_ctrl_, UCSI_GET_CAPABILITY) \
+}
+
+/* Helper for preparing ucsi_control for GET_CONNECTOR_CAPABILITY command. */
+#define UCSI_CMD_GET_CONNECTOR_CAPABILITY(_ctrl_, _con_) \
+{ \
+ __UCSI_CMD(_ctrl_, UCSI_GET_CONNECTOR_CAPABILITY) \
+ (_ctrl_).cmd.data = _con_; \
+}
+
+/* Helper for preparing ucsi_control for GET_CONNECTOR_STATUS command. */
+#define UCSI_CMD_GET_CONNECTOR_STATUS(_ctrl_, _con_) \
+{ \
+ __UCSI_CMD(_ctrl_, UCSI_GET_CONNECTOR_STATUS) \
+ (_ctrl_).cmd.data = _con_; \
+}
+
+#define __UCSI_ROLE(_ctrl_, _cmd_, _con_num_) \
+{ \
+ __UCSI_CMD(_ctrl_, _cmd_) \
+ (_ctrl_).uor.con_num = _con_num_; \
+ (_ctrl_).uor.role = UCSI_UOR_ROLE_DRP; \
+}
+
+/* Helper for preparing ucsi_control for SET_UOR command. */
+#define UCSI_CMD_SET_UOR(_ctrl_, _con_, _role_) \
+{ \
+ __UCSI_ROLE(_ctrl_, UCSI_SET_UOR, (_con_)->num) \
+ (_ctrl_).uor.role |= (_role_) == TYPEC_HOST ? UCSI_UOR_ROLE_DFP : \
+ UCSI_UOR_ROLE_UFP; \
+}
+
+/* Helper for preparing ucsi_control for SET_PDR command. */
+#define UCSI_CMD_SET_PDR(_ctrl_, _con_, _role_) \
+{ \
+ __UCSI_ROLE(_ctrl_, UCSI_SET_PDR, (_con_)->num) \
+ (_ctrl_).uor.role |= (_role_) == TYPEC_SOURCE ? UCSI_UOR_ROLE_DFP : \
+ UCSI_UOR_ROLE_UFP; \
+}
+
+/* Commands */
+#define UCSI_PPM_RESET 0x01
+#define UCSI_CANCEL 0x02
+#define UCSI_CONNECTOR_RESET 0x03
+#define UCSI_ACK_CC_CI 0x04
+#define UCSI_SET_NOTIFICATION_ENABLE 0x05
+#define UCSI_GET_CAPABILITY 0x06
+#define UCSI_GET_CONNECTOR_CAPABILITY 0x07
+#define UCSI_SET_UOM 0x08
+#define UCSI_SET_UOR 0x09
+#define UCSI_SET_PDM 0x0a
+#define UCSI_SET_PDR 0x0b
+#define UCSI_GET_ALTERNATE_MODES 0x0c
+#define UCSI_GET_CAM_SUPPORTED 0x0d
+#define UCSI_GET_CURRENT_CAM 0x0e
+#define UCSI_SET_NEW_CAM 0x0f
+#define UCSI_GET_PDOS 0x10
+#define UCSI_GET_CABLE_PROPERTY 0x11
+#define UCSI_GET_CONNECTOR_STATUS 0x12
+#define UCSI_GET_ERROR_STATUS 0x13
+
+/* ACK_CC_CI commands */
+#define UCSI_ACK_EVENT 1
+#define UCSI_ACK_CMD 2
+
+/* Bits for SET_NOTIFICATION_ENABLE command */
+#define UCSI_ENABLE_NTFY_CMD_COMPLETE BIT(0)
+#define UCSI_ENABLE_NTFY_EXT_PWR_SRC_CHANGE BIT(1)
+#define UCSI_ENABLE_NTFY_PWR_OPMODE_CHANGE BIT(2)
+#define UCSI_ENABLE_NTFY_CAP_CHANGE BIT(5)
+#define UCSI_ENABLE_NTFY_PWR_LEVEL_CHANGE BIT(6)
+#define UCSI_ENABLE_NTFY_PD_RESET_COMPLETE BIT(7)
+#define UCSI_ENABLE_NTFY_CAM_CHANGE BIT(8)
+#define UCSI_ENABLE_NTFY_BAT_STATUS_CHANGE BIT(9)
+#define UCSI_ENABLE_NTFY_PARTNER_CHANGE BIT(11)
+#define UCSI_ENABLE_NTFY_PWR_DIR_CHANGE BIT(12)
+#define UCSI_ENABLE_NTFY_CONNECTOR_CHANGE BIT(14)
+#define UCSI_ENABLE_NTFY_ERROR BIT(15)
+#define UCSI_ENABLE_NTFY_ALL 0xdbe7
+
+/* Error information returned by PPM in response to GET_ERROR_STATUS command. */
+#define UCSI_ERROR_UNREGONIZED_CMD BIT(0)
+#define UCSI_ERROR_INVALID_CON_NUM BIT(1)
+#define UCSI_ERROR_INVALID_CMD_ARGUMENT BIT(2)
+#define UCSI_ERROR_INCOMPATIBLE_PARTNER BIT(3)
+#define UCSI_ERROR_CC_COMMUNICATION_ERR BIT(4)
+#define UCSI_ERROR_DEAD_BATTERY BIT(5)
+#define UCSI_ERROR_CONTRACT_NEGOTIATION_FAIL BIT(6)
+
+/* Data structure filled by PPM in response to GET_CAPABILITY command. */
+struct ucsi_capability {
+ u32 attributes;
+#define UCSI_CAP_ATTR_DISABLE_STATE BIT(0)
+#define UCSI_CAP_ATTR_BATTERY_CHARGING BIT(1)
+#define UCSI_CAP_ATTR_USB_PD BIT(2)
+#define UCSI_CAP_ATTR_TYPEC_CURRENT BIT(6)
+#define UCSI_CAP_ATTR_POWER_AC_SUPPLY BIT(8)
+#define UCSI_CAP_ATTR_POWER_OTHER BIT(10)
+#define UCSI_CAP_ATTR_POWER_VBUS BIT(14)
+ u32 num_connectors:8;
+ u32 features:24;
+#define UCSI_CAP_SET_UOM BIT(0)
+#define UCSI_CAP_SET_PDM BIT(1)
+#define UCSI_CAP_ALT_MODE_DETAILS BIT(2)
+#define UCSI_CAP_ALT_MODE_OVERRIDE BIT(3)
+#define UCSI_CAP_PDO_DETAILS BIT(4)
+#define UCSI_CAP_CABLE_DETAILS BIT(5)
+#define UCSI_CAP_EXT_SUPPLY_NOTIFICATIONS BIT(6)
+#define UCSI_CAP_PD_RESET BIT(7)
+ u8 num_alt_modes;
+ u8 reserved;
+ u16 bc_version;
+ u16 pd_version;
+ u16 typec_version;
+} __packed;
+
+/* Data structure filled by PPM in response to GET_CONNECTOR_CAPABILITY cmd. */
+struct ucsi_connector_capability {
+ u8 op_mode;
+#define UCSI_CONCAP_OPMODE_DFP BIT(0)
+#define UCSI_CONCAP_OPMODE_UFP BIT(1)
+#define UCSI_CONCAP_OPMODE_DRP BIT(2)
+#define UCSI_CONCAP_OPMODE_AUDIO_ACCESSORY BIT(3)
+#define UCSI_CONCAP_OPMODE_DEBUG_ACCESSORY BIT(4)
+#define UCSI_CONCAP_OPMODE_USB2 BIT(5)
+#define UCSI_CONCAP_OPMODE_USB3 BIT(6)
+#define UCSI_CONCAP_OPMODE_ALT_MODE BIT(7)
+ u8 provider:1;
+ u8 consumer:1;
+ u8:6; /* reserved */
+} __packed;
+
+struct ucsi_altmode {
+ u16 svid;
+ u32 mid;
+} __packed;
+
+/* Data structure filled by PPM in response to GET_CABLE_PROPERTY command. */
+struct ucsi_cable_property {
+ u16 speed_supported;
+ u8 current_capability;
+ u8 vbus_in_cable:1;
+ u8 active_cable:1;
+ u8 directionality:1;
+ u8 plug_type:2;
+#define UCSI_CABLE_PROPERTY_PLUG_TYPE_A 0
+#define UCSI_CABLE_PROPERTY_PLUG_TYPE_B 1
+#define UCSI_CABLE_PROPERTY_PLUG_TYPE_C 2
+#define UCSI_CABLE_PROPERTY_PLUG_OTHER 3
+ u8 mode_support:1;
+ u8:2; /* reserved */
+ u8 latency:4;
+ u8:4; /* reserved */
+} __packed;
+
+/* Data structure filled by PPM in response to GET_CONNECTOR_STATUS command. */
+struct ucsi_connector_status {
+ u16 change;
+#define UCSI_CONSTAT_EXT_SUPPLY_CHANGE BIT(1)
+#define UCSI_CONSTAT_POWER_OPMODE_CHANGE BIT(2)
+#define UCSI_CONSTAT_PDOS_CHANGE BIT(5)
+#define UCSI_CONSTAT_POWER_LEVEL_CHANGE BIT(6)
+#define UCSI_CONSTAT_PD_RESET_COMPLETE BIT(7)
+#define UCSI_CONSTAT_CAM_CHANGE BIT(8)
+#define UCSI_CONSTAT_BC_CHANGE BIT(9)
+#define UCSI_CONSTAT_PARTNER_CHANGE BIT(11)
+#define UCSI_CONSTAT_POWER_DIR_CHANGE BIT(12)
+#define UCSI_CONSTAT_CONNECT_CHANGE BIT(14)
+#define UCSI_CONSTAT_ERROR BIT(15)
+ u16 pwr_op_mode:3;
+#define UCSI_CONSTAT_PWR_OPMODE_NONE 0
+#define UCSI_CONSTAT_PWR_OPMODE_DEFAULT 1
+#define UCSI_CONSTAT_PWR_OPMODE_BC 2
+#define UCSI_CONSTAT_PWR_OPMODE_PD 3
+#define UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5 4
+#define UCSI_CONSTAT_PWR_OPMODE_TYPEC3_0 5
+ u16 connected:1;
+ u16 pwr_dir:1;
+ u16 partner_flags:8;
+#define UCSI_CONSTAT_PARTNER_FLAG_USB BIT(0)
+#define UCSI_CONSTAT_PARTNER_FLAG_ALT_MODE BIT(1)
+ u16 partner_type:3;
+#define UCSI_CONSTAT_PARTNER_TYPE_DFP 1
+#define UCSI_CONSTAT_PARTNER_TYPE_UFP 2
+#define UCSI_CONSTAT_PARTNER_TYPE_CABLE 3 /* Powered Cable */
+#define UCSI_CONSTAT_PARTNER_TYPE_CABLE_AND_UFP 4 /* Powered Cable */
+#define UCSI_CONSTAT_PARTNER_TYPE_DEBUG 5
+#define UCSI_CONSTAT_PARTNER_TYPE_AUDIO 6
+ u32 request_data_obj;
+ u8 bc_status:2;
+#define UCSI_CONSTAT_BC_NOT_CHARGING 0
+#define UCSI_CONSTAT_BC_NOMINAL_CHARGING 1
+#define UCSI_CONSTAT_BC_SLOW_CHARGING 2
+#define UCSI_CONSTAT_BC_TRICKLE_CHARGING 3
+ u8 provider_cap_limit_reason:4;
+#define UCSI_CONSTAT_CAP_PWR_LOWERED 0
+#define UCSI_CONSTAT_CAP_PWR_BUDGET_LIMIT 1
+ u8:2; /* reserved */
+} __packed;
+
+/* -------------------------------------------------------------------------- */
+
+struct ucsi;
+
+struct ucsi_data {
+ u16 version;
+ u16 reserved;
+ union {
+ u32 raw_cci;
+ struct ucsi_cci cci;
+ };
+ struct ucsi_control ctrl;
+ u32 message_in[4];
+ u32 message_out[4];
+} __packed;
+
+/*
+ * struct ucsi_ppm - Interface to UCSI Platform Policy Manager
+ * @data: memory location to the UCSI data structures
+ * @cmd: UCSI command execution routine
+ * @sync: Refresh UCSI mailbox (the data structures)
+ */
+struct ucsi_ppm {
+ struct ucsi_data *data;
+ int (*cmd)(struct ucsi_ppm *, struct ucsi_control *);
+ int (*sync)(struct ucsi_ppm *);
+};
+
+struct ucsi *ucsi_register_ppm(struct device *dev, struct ucsi_ppm *ppm);
+void ucsi_unregister_ppm(struct ucsi *ucsi);
+void ucsi_notify(struct ucsi *ucsi);
+
+#endif /* __DRIVER_USB_TYPEC_UCSI_H */