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