All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC PATCH v3 0/2] Type-C Port Manager
@ 2016-08-23 21:10 Guenter Roeck
  2016-08-23 21:10 ` [RFC PATCH v3 1/2] usb: typec: USB Type-C Port Manager (tcpm) Guenter Roeck
                   ` (2 more replies)
  0 siblings, 3 replies; 24+ messages in thread
From: Guenter Roeck @ 2016-08-23 21:10 UTC (permalink / raw)
  To: Felipe Balbi
  Cc: Chandra Sekhar Anagani, Bruce Ashfield, Bin Gao, Pranav Tipnis,
	Heikki Krogerus, linux-kernel, linux-usb, Guenter Roeck

The following series of patches implements a USB Type-C Port Manager
using the pending USB Type-C class code as basis. The code is still WIP,
but I think it is important to get feedback from the community at this point.

There are two patches in the series. The first patch implements the Type-C
Port Manager state machine. The forth patch is an interface between the
Type-C Port Manager and a TCPCI (Type-C Port Controller Interface) compliant
USB Type-C Port Controller.

Patch 2/2 (the interface to a TCPCI compliant chip) is currently untested
since I don't have the necessary hardware available. The port manager code
was tested connecting to an Embedded Controller on a Chromebook, bypassing
the Port Manager implementation in the EC.

Both Source and Sink operation was tested with various Type-C chargers, docks,
and connectors. Alternate mode support is partially implemented (Alternate mode
support is requested from the partner), but alternate modes are not actually
selected. Implementing this will require more thought, since the actual
alternate mode support has to be implemented elsewhere, such as in a dedicated
Phy driver. It should be possible to implement the interface between phy driver
and Type-C Port Controller driver using extcon, but I have not further explored
the possibilities, and other options might be possible and/or better.

v3:
- Improve TCPM state machine resiliency if there are spurious CC line changes
  while the state machine is in a transient change (waiting for a timeout)
- Update current limit after CC voltage level changes on a port which is not
  PD capable.
- Applies to v6 of "USB Type-C Connector class" patch series.

v2:
- Class code no longer uses locking, so the patch to remove it is no longer
  necessary.
- tcpm: Only update polarity if setting it was successful
  If setting the CC line polarity in the driver was not successful,
  don't update the internal polarity state.
- tcpm: All PD messages are little endian; convert to and from CPU endianness.
- tcpm: Avoid comparisons against NULL.
- tcpm: Use u8/u16/u32 instead of uint8_t/uint16_t/uint32_t consistently.
- tcpm/tcpc: Callbacks into tcpm need to be lockless to avoid timing problems
  in low level drivers.
- tcpm/tcpc: Simplify callbacks; tcpm can request the current state of cc/vbus
  when it is ready to use it.
- Applies to v5 of "USB Type-C Connector class" patch series.

^ permalink raw reply	[flat|nested] 24+ messages in thread

* [RFC PATCH v3 1/2] usb: typec: USB Type-C Port Manager (tcpm)
  2016-08-23 21:10 [RFC PATCH v3 0/2] Type-C Port Manager Guenter Roeck
@ 2016-08-23 21:10 ` Guenter Roeck
  2016-09-10  0:26   ` Jun Li
                     ` (3 more replies)
  2016-08-23 21:10 ` [RFC PATCH v3 2/2] usb: typec: Type-C Port Controller Interface driver (tcpci) Guenter Roeck
  2016-08-24 12:52 ` [RFC PATCH v3 0/2] Type-C Port Manager Heikki Krogerus
  2 siblings, 4 replies; 24+ messages in thread
From: Guenter Roeck @ 2016-08-23 21:10 UTC (permalink / raw)
  To: Felipe Balbi
  Cc: Chandra Sekhar Anagani, Bruce Ashfield, Bin Gao, Pranav Tipnis,
	Heikki Krogerus, linux-kernel, linux-usb, Guenter Roeck

This driver implements the USB Type-C Power Delivery state machine
for both source and sink ports. Alternate mode support is not
fully implemented.

The driver attaches to the USB Type-C class code implemented in
the following patches.

	usb: typec: add driver for Intel Whiskey Cove PMIC USB Type-C PHY
	usb: USB Type-C connector class

This driver only implements the state machine. Lower level drivers are
responsible for
- Reporting VBUS status and activating VBUS
- Setting CC lines and providing CC line status
- Setting line polarity
- Activating and deactivating VCONN
- Setting the current limit
- Activating and deactivating PD message transfers
- Sending and receiving PD messages

The driver provides both a functional API as well as callbacks for
lower level drivers.

Signed-off-by: Guenter Roeck <groeck@chromium.org>
---
v3:
- Improve TCPM state machine resiliency if there are spurious CC line changes
  while the state machine is in a transient change (waiting for a timeout)
- Update current limit after CC voltage level changes on a port which is not
  PD capable.

v2:
- Only update polarity if setting it was successful
  If setting the CC line polarity in the driver was not successful,
  don't update the internal polarity state.
- All PD messages are little endian; convert to and from CPU endianness.
- Avoid comparisons against NULL.
- Use u8/u16/u32 instead of uint8_t/uint16_t/uint32_t consistently.
- Callbacks into tcpm need to be lockless to avoid timing problems
  in low level drivers.
- Simplify callbacks; tcpm can request the current state of cc/vbus
  when it is ready to use it.

 drivers/usb/typec/Kconfig  |    7 +
 drivers/usb/typec/Makefile |    1 +
 drivers/usb/typec/tcpm.c   | 3163 ++++++++++++++++++++++++++++++++++++++++++++
 drivers/usb/typec/tcpm.h   |  137 ++
 include/linux/usb/pd.h     |  282 ++++
 include/linux/usb/pd_bdo.h |   31 +
 include/linux/usb/pd_vdo.h |  412 ++++++
 7 files changed, 4033 insertions(+)
 create mode 100644 drivers/usb/typec/tcpm.c
 create mode 100644 drivers/usb/typec/tcpm.h
 create mode 100644 include/linux/usb/pd.h
 create mode 100644 include/linux/usb/pd_bdo.h
 create mode 100644 include/linux/usb/pd_vdo.h

diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig
index 7a345a4b1e7a..113bb1b3589c 100644
--- a/drivers/usb/typec/Kconfig
+++ b/drivers/usb/typec/Kconfig
@@ -18,4 +18,11 @@ config TYPEC_WCOVE
 	  To compile this driver as module, choose M here: the module will be
 	  called typec_wcove
 
+config TYPEC_TCPM
+	tristate "USB Type-C Port Controller Manager"
+	select TYPEC
+	help
+	  The Type-C Port Controller Manager provides a USB PD and USB Type-C
+	  state machine for use with Type-C Port Controllers.
+
 endmenu
diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile
index b9cb862221af..bbe45721cf52 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_TCPM)	+= tcpm.o
diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c
new file mode 100644
index 000000000000..bd98a2dda36f
--- /dev/null
+++ b/drivers/usb/typec/tcpm.c
@@ -0,0 +1,3163 @@
+/*
+ * Copyright 2015-2016 Google, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * USB Power Delivery protocol stack.
+ */
+
+#include <linux/completion.h>
+#include <linux/debugfs.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/proc_fs.h>
+#include <linux/sched.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/usb/pd.h>
+#include <linux/usb/pd_vdo.h>
+#include <linux/usb/pd_bdo.h>
+#include <linux/usb/typec.h>
+#include <linux/workqueue.h>
+
+#include "tcpm.h"
+
+#define FOREACH_STATE(S)			\
+	S(INVALID_STATE),			\
+	S(SRC_UNATTACHED),			\
+	S(SRC_ATTACH_WAIT),			\
+	S(SRC_ATTACHED),			\
+	S(SRC_STARTUP),				\
+	S(SRC_SEND_CAPABILITIES),		\
+	S(SRC_NEGOTIATE_CAPABILITIES),		\
+	S(SRC_TRANSITION_SUPPLY),		\
+	S(SRC_READY),				\
+	S(SRC_WAIT_NEW_CAPABILITIES),		\
+						\
+	S(SNK_UNATTACHED),			\
+	S(SNK_ATTACH_WAIT),			\
+	S(SNK_DEBOUNCED),			\
+	S(SNK_ATTACHED),			\
+	S(SNK_STARTUP),				\
+	S(SNK_DISCOVERY),			\
+	S(SNK_DISCOVERY_DEBOUNCE),		\
+	S(SNK_DISCOVERY_DEBOUNCE_DONE),		\
+	S(SNK_WAIT_CAPABILITIES),		\
+	S(SNK_NEGOTIATE_CAPABILITIES),		\
+	S(SNK_TRANSITION_SINK),			\
+	S(SNK_TRANSITION_SINK_VBUS),		\
+	S(SNK_READY),				\
+						\
+	S(ACC_UNATTACHED),			\
+	S(DEBUG_ACC_ATTACHED),			\
+	S(AUDIO_ACC_ATTACHED),			\
+	S(AUDIO_ACC_DEBOUNCE),			\
+						\
+	S(HARD_RESET_SEND),			\
+	S(HARD_RESET_START),			\
+	S(SRC_HARD_RESET_VBUS_OFF),		\
+	S(SRC_HARD_RESET_VBUS_ON),		\
+	S(SNK_HARD_RESET_SINK_OFF),		\
+	S(SNK_HARD_RESET_WAIT_VBUS),		\
+	S(SNK_HARD_RESET_SINK_ON),		\
+						\
+	S(SOFT_RESET),				\
+	S(SOFT_RESET_SEND),			\
+						\
+	S(DR_SWAP_ACCEPT),			\
+	S(DR_SWAP_SEND),			\
+	S(DR_SWAP_SEND_TIMEOUT),		\
+	S(DR_SWAP_CANCEL),			\
+	S(DR_SWAP_CHANGE_DR),			\
+						\
+	S(PR_SWAP_ACCEPT),			\
+	S(PR_SWAP_SEND),			\
+	S(PR_SWAP_SEND_TIMEOUT),		\
+	S(PR_SWAP_CANCEL),			\
+	S(PR_SWAP_START),			\
+	S(PR_SWAP_SRC_SNK_TRANSITION_OFF),	\
+	S(PR_SWAP_SRC_SNK_SOURCE_OFF),		\
+	S(PR_SWAP_SRC_SNK_SINK_ON),		\
+	S(PR_SWAP_SNK_SRC_SINK_OFF),		\
+	S(PR_SWAP_SNK_SRC_SOURCE_ON),		\
+						\
+	S(VCONN_SWAP_ACCEPT),			\
+	S(VCONN_SWAP_SEND),			\
+	S(VCONN_SWAP_SEND_TIMEOUT),		\
+	S(VCONN_SWAP_CANCEL),			\
+	S(VCONN_SWAP_START),			\
+	S(VCONN_SWAP_WAIT_FOR_VCONN),		\
+	S(VCONN_SWAP_TURN_ON_VCONN),		\
+	S(VCONN_SWAP_TURN_OFF_VCONN),		\
+						\
+	S(SNK_TRY),				\
+	S(SNK_TRY_WAIT),			\
+	S(SRC_TRYWAIT),				\
+	S(SRC_TRYWAIT_UNATTACHED),		\
+						\
+	S(SRC_TRY),				\
+	S(SRC_TRY_DEBOUNCE),			\
+	S(SNK_TRYWAIT),				\
+	S(SNK_TRYWAIT_DEBOUNCE),		\
+	S(SNK_TRYWAIT_VBUS),			\
+	S(BIST_RX),				\
+						\
+	S(ERROR_RECOVERY),			\
+	S(ERROR_RECOVERY_WAIT_OFF)
+
+#define GENERATE_ENUM(e)	e
+#define GENERATE_STRING(s)	#s
+
+enum tcpm_state {
+	FOREACH_STATE(GENERATE_ENUM)
+};
+
+static const char * const tcpm_states[] = {
+	FOREACH_STATE(GENERATE_STRING)
+};
+
+enum vdm_states {
+	VDM_STATE_ERR_BUSY = -3,
+	VDM_STATE_ERR_SEND = -2,
+	VDM_STATE_ERR_TMOUT = -1,
+	VDM_STATE_DONE = 0,
+	/* Anything >0 represents an active state */
+	VDM_STATE_READY = 1,
+	VDM_STATE_BUSY = 2,
+	VDM_STATE_WAIT_RSP_BUSY = 3,
+};
+
+enum pd_msg_request {
+	PD_MSG_NONE = 0,
+	PD_MSG_CTRL_REJECT,
+	PD_MSG_CTRL_WAIT,
+	PD_MSG_DATA_SINK_CAP,
+	PD_MSG_DATA_SOURCE_CAP,
+};
+
+/* Events from low level driver */
+
+#define TCPM_CC_EVENT		BIT(0)
+#define TCPM_VBUS_EVENT		BIT(1)
+#define TCPM_RESET_EVENT	BIT(2)
+
+#define LOG_BUFFER_ENTRIES	1024
+#define LOG_BUFFER_ENTRY_SIZE	128
+
+struct tcpm_port {
+	struct device *dev;
+
+	struct mutex lock;		/* tcpm state machine lock */
+	struct workqueue_struct *wq;
+
+	struct typec_capability typec_caps;
+	struct typec_port *typec_port;
+
+	struct tcpc_dev	*tcpc;
+
+	struct typec_connection con;
+	struct typec_partner partner;
+
+	enum typec_cc_status cc_req;
+
+	enum typec_cc_status cc1;
+	enum typec_cc_status cc2;
+	enum typec_cc_polarity polarity;
+
+	bool attached;
+	bool connected;
+	bool vbus_present;
+	bool vbus_never_low;
+	bool vbus_source;
+	bool vbus_charge;
+
+	bool send_discover;
+	bool op_vsafe5v;
+
+	int try_snk_count;
+	int try_src_count;
+
+	enum pd_msg_request queued_message;
+
+	enum tcpm_state enter_state;
+	enum tcpm_state prev_state;
+	enum tcpm_state state;
+	enum tcpm_state delayed_state;
+	unsigned long delayed_runtime;
+	unsigned long delay_ms;
+
+	spinlock_t pd_event_lock;
+	u32 pd_events;
+
+	struct work_struct event_work;
+	struct delayed_work state_machine;
+	struct delayed_work vdm_state_machine;
+	bool state_machine_running;
+
+	struct completion tx_complete;
+	enum tcpm_transmit_status tx_status;
+
+	struct mutex swap_lock;		/* swap command lock */
+	bool swap_pending;
+	struct completion swap_complete;
+	int swap_status;
+
+	unsigned int message_id;
+	unsigned int caps_count;
+	unsigned int hard_reset_count;
+	bool pd_capable;
+	bool explicit_contract;
+
+	/* Partner capabilities/requests */
+	u32 sink_request;
+	u32 source_caps[PDO_MAX_OBJECTS];
+	unsigned int nr_source_caps;
+	u32 sink_caps[PDO_MAX_OBJECTS];
+	unsigned int nr_sink_caps;
+	/* Requested current / voltage */
+	u32 current_limit;
+	u32 supply_voltage;
+
+	u32 bist_request;
+
+	/* PD state for Vendor Defined Messages */
+	enum vdm_states vdm_state;
+	u32 vdm_retries;
+	/* next Vendor Defined Message to send */
+	u32 vdo_data[VDO_MAX_SIZE];
+	u8 vdo_count;
+	/* VDO to retry if UFP responder replied busy */
+	u32 vdo_retry;
+
+	/* Alternate mode data */
+
+	struct pd_mode_data mode_data;
+	bool altmodes_registered;
+
+#ifdef CONFIG_DEBUG_FS
+	struct dentry *dentry;
+	struct mutex logbuffer_lock;	/* log buffer access lock */
+	int logbuffer_head;
+	int logbuffer_tail;
+	u8 *logbuffer[LOG_BUFFER_ENTRIES];
+#endif
+};
+
+struct pd_rx_event {
+	struct work_struct work;
+	struct tcpm_port *port;
+	struct pd_message msg;
+};
+
+#define tcpm_cc_is_sink(cc) \
+	((cc) == TYPEC_CC_RP_DEF || (cc) == TYPEC_CC_RP_1_5 || \
+	 (cc) == TYPEC_CC_RP_3_0)
+
+#define tcpm_port_is_sink(port) \
+	((tcpm_cc_is_sink((port)->cc1) && !tcpm_cc_is_sink((port)->cc2)) || \
+	 (tcpm_cc_is_sink((port)->cc2) && !tcpm_cc_is_sink((port)->cc1)))
+
+#define tcpm_cc_is_source(cc) ((cc) == TYPEC_CC_RD)
+
+#define tcpm_port_is_source(port) \
+	((tcpm_cc_is_source((port)->cc1) && \
+	 !tcpm_cc_is_source((port)->cc2)) || \
+	 (tcpm_cc_is_source((port)->cc2) && \
+	  !tcpm_cc_is_source((port)->cc1)))
+
+#define tcpm_try_snk(port) \
+	((port)->try_snk_count == 0 && \
+	(port)->tcpc->config->default_role == TYPEC_SINK)
+
+#define tcpm_try_src(port) \
+	((port)->try_src_count == 0 && \
+	(port)->tcpc->config->default_role == TYPEC_SOURCE)
+
+#define tcpm_default_state(port) \
+	((port)->tcpc->config->default_role == TYPEC_SOURCE ? \
+					SRC_UNATTACHED : SNK_UNATTACHED)
+
+static inline
+struct tcpm_port *typec_cap_to_tcpm(const struct typec_capability *cap)
+{
+	return container_of(cap, struct tcpm_port, typec_caps);
+}
+
+static bool tcpm_port_is_disconnected(struct tcpm_port *port)
+{
+	return (!port->attached && port->cc1 == TYPEC_CC_OPEN &&
+		port->cc2 == TYPEC_CC_OPEN) ||
+	       (port->attached && ((port->polarity == TYPEC_POLARITY_CC1 &&
+				    port->cc1 == TYPEC_CC_OPEN) ||
+				   (port->polarity == TYPEC_POLARITY_CC2 &&
+				    port->cc2 == TYPEC_CC_OPEN)));
+}
+
+/*
+ * Logging
+ */
+
+#ifdef CONFIG_DEBUG_FS
+
+static bool tcpm_log_full(struct tcpm_port *port)
+{
+	return port->logbuffer_tail ==
+		(port->logbuffer_head + 1) % LOG_BUFFER_ENTRIES;
+}
+
+static void _tcpm_log(struct tcpm_port *port, const char *fmt, va_list args)
+{
+	char tmpbuffer[LOG_BUFFER_ENTRY_SIZE];
+	u64 ts_nsec = local_clock();
+	unsigned long rem_nsec;
+
+	if (!port->logbuffer[port->logbuffer_head]) {
+		port->logbuffer[port->logbuffer_head] =
+				kzalloc(LOG_BUFFER_ENTRY_SIZE, GFP_KERNEL);
+		if (!port->logbuffer[port->logbuffer_head])
+			return;
+	}
+
+	vsnprintf(tmpbuffer, sizeof(tmpbuffer), fmt, args);
+
+	mutex_lock(&port->logbuffer_lock);
+
+	if (tcpm_log_full(port)) {
+		port->logbuffer_head = max(port->logbuffer_head - 1, 0);
+		strcpy(tmpbuffer, "overflow");
+	}
+
+	if (port->logbuffer_head < 0 ||
+	    port->logbuffer_head >= LOG_BUFFER_ENTRIES) {
+		dev_warn(port->dev,
+			 "Bad log buffer index %d\n", port->logbuffer_head);
+		goto abort;
+	}
+
+	if (!port->logbuffer[port->logbuffer_head]) {
+		dev_warn(port->dev,
+			 "Log buffer index %d is NULL\n", port->logbuffer_head);
+		goto abort;
+	}
+
+	rem_nsec = do_div(ts_nsec, 1000000000);
+	scnprintf(port->logbuffer[port->logbuffer_head],
+		  LOG_BUFFER_ENTRY_SIZE, "[%5lu.%06lu] %s",
+		  (unsigned long)ts_nsec, rem_nsec / 1000,
+		  tmpbuffer);
+	port->logbuffer_head = (port->logbuffer_head + 1) % LOG_BUFFER_ENTRIES;
+
+abort:
+	mutex_unlock(&port->logbuffer_lock);
+}
+
+static void tcpm_log(struct tcpm_port *port, const char *fmt, ...)
+{
+	va_list args;
+
+	/* Do not log while disconnected and unattached */
+	if (tcpm_port_is_disconnected(port) &&
+	    (port->state == SRC_UNATTACHED || port->state == SNK_UNATTACHED))
+		return;
+
+	va_start(args, fmt);
+	_tcpm_log(port, fmt, args);
+	va_end(args);
+}
+
+static void tcpm_log_force(struct tcpm_port *port, const char *fmt, ...)
+{
+	va_list args;
+
+	va_start(args, fmt);
+	_tcpm_log(port, fmt, args);
+	va_end(args);
+}
+
+static int tcpm_seq_show(struct seq_file *s, void *v)
+{
+	struct tcpm_port *port = (struct tcpm_port *)s->private;
+	int tail;
+
+	mutex_lock(&port->logbuffer_lock);
+	tail = port->logbuffer_tail;
+	while (tail != port->logbuffer_head) {
+		seq_printf(s, "%s\n", port->logbuffer[tail]);
+		tail = (tail + 1) % LOG_BUFFER_ENTRIES;
+	}
+	if (!seq_has_overflowed(s))
+		port->logbuffer_tail = tail;
+	mutex_unlock(&port->logbuffer_lock);
+
+	return 0;
+}
+
+static int tcpm_debug_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, tcpm_seq_show, inode->i_private);
+}
+
+static const struct file_operations tcpm_debug_operations = {
+	.open		= tcpm_debug_open,
+	.llseek		= seq_lseek,
+	.read		= seq_read,
+	.release	= single_release,
+};
+
+static struct dentry *rootdir;
+
+static int tcpm_debugfs_init(struct tcpm_port *port)
+{
+	mutex_init(&port->logbuffer_lock);
+	/* /sys/kernel/debug/tcpm/usbcX */
+	if (!rootdir) {
+		rootdir = debugfs_create_dir("tcpm", NULL);
+		if (!rootdir)
+			return -ENOMEM;
+	}
+
+	port->dentry = debugfs_create_file(dev_name(port->dev),
+					   S_IFREG | S_IRUGO, rootdir,
+					   port, &tcpm_debug_operations);
+
+	return 0;
+}
+
+static void tcpm_debugfs_exit(struct tcpm_port *port)
+{
+	debugfs_remove(port->dentry);
+}
+
+#else
+
+static void tcpm_log(const struct tcpm_port *port, const char *fmt, ...) { }
+static int tcpm_debugfs_init(const struct tcpm_port *port) { return 0; }
+static void tcpm_debugfs_exit(const struct tcpm_port *port) { }
+
+#endif
+
+static int tcpm_pd_transmit(struct tcpm_port *port,
+			    enum tcpm_transmit_type type,
+			    const struct pd_message *msg)
+{
+	unsigned long timeout;
+	int ret;
+
+	if (msg)
+		tcpm_log(port, "PD TX, header: %#x", le16_to_cpu(msg->header));
+	else
+		tcpm_log(port, "PD TX, type: %#x", type);
+
+	reinit_completion(&port->tx_complete);
+	ret = port->tcpc->pd_transmit(port->tcpc, type, msg);
+	if (ret < 0)
+		return ret;
+
+	mutex_unlock(&port->lock);
+	timeout = wait_for_completion_timeout(&port->tx_complete,
+				msecs_to_jiffies(PD_T_TCPC_TX_TIMEOUT));
+	mutex_lock(&port->lock);
+	if (!timeout)
+		return -ETIMEDOUT;
+
+	switch (port->tx_status) {
+	case TCPC_TX_SUCCESS:
+		port->message_id = (port->message_id + 1) & PD_HEADER_ID_MASK;
+		return 0;
+	case TCPC_TX_DISCARDED:
+		return -EAGAIN;
+	case TCPC_TX_FAILED:
+	default:
+		return -EIO;
+	}
+}
+
+void tcpm_pd_transmit_complete(struct tcpm_port *port,
+			       enum tcpm_transmit_status status)
+{
+	tcpm_log(port, "PD TX complete, status: %u", status);
+	port->tx_status = status;
+	complete(&port->tx_complete);
+}
+EXPORT_SYMBOL_GPL(tcpm_pd_transmit_complete);
+
+static int tcpm_register_altmodes(struct tcpm_port *port)
+{
+	int ret = -ENOENT;
+	struct pd_mode_data *modep = &port->mode_data;
+
+	if (modep->nmodes) {
+		ret = typec_register_altmodes(&port->partner.dev,
+					      modep->altmodes);
+		if (!ret)
+			port->altmodes_registered = true;
+	}
+	return ret;
+}
+
+static int tcpm_mux_set(struct tcpm_port *port, enum tcpc_mux_mode mode,
+			enum tcpc_usb_switch config)
+{
+	int ret = 0;
+
+	tcpm_log(port, "Requesting mux mode %d, config %d, polarity %d",
+		 mode, config, port->polarity);
+
+	if (port->tcpc->mux)
+		ret = port->tcpc->mux->set(port->tcpc->mux, mode, config,
+					   port->polarity);
+
+	return ret;
+}
+
+static int tcpm_set_polarity(struct tcpm_port *port,
+			     enum typec_cc_polarity polarity)
+{
+	int ret;
+
+	tcpm_log(port, "polarity %d", polarity);
+
+	ret = port->tcpc->set_polarity(port->tcpc, polarity);
+	if (ret < 0)
+		return ret;
+
+	port->polarity = polarity;
+
+	return 0;
+}
+
+static int tcpm_set_vconn(struct tcpm_port *port, bool enable)
+{
+	int ret;
+
+	tcpm_log(port, "vconn:=%d", enable);
+
+	ret = port->tcpc->set_vconn(port->tcpc, enable);
+	if (!ret) {
+		port->con.vconn_role = enable ? TYPEC_SOURCE
+					      : TYPEC_SINK;
+		typec_set_vconn_role(port->typec_port, port->con.vconn_role);
+	}
+
+	return ret;
+}
+
+static u32 tcpm_get_current_limit(struct tcpm_port *port)
+{
+	enum typec_cc_status cc;
+	u32 limit;
+
+	cc = port->polarity ? port->cc2 : port->cc1;
+	switch (cc) {
+	case TYPEC_CC_RP_1_5:
+		limit = 1500;
+		break;
+	case TYPEC_CC_RP_3_0:
+		limit = 3000;
+		break;
+	case TYPEC_CC_RP_DEF:
+	default:
+		limit = 0;
+		break;
+	}
+
+	return limit;
+}
+
+static int tcpm_set_current_limit(struct tcpm_port *port, u32 max_ma, u32 mv)
+{
+	int ret = -EOPNOTSUPP;
+
+	tcpm_log(port, "Setting voltage/current limit %u mV %u mA", mv, max_ma);
+
+	if (port->tcpc->set_current_limit)
+		ret = port->tcpc->set_current_limit(port->tcpc, max_ma, mv);
+
+	return ret;
+}
+
+static int tcpm_set_roles(struct tcpm_port *port, enum typec_role role,
+			  enum typec_data_role data)
+{
+	int ret;
+
+	if (data == TYPEC_HOST)
+		ret = tcpm_mux_set(port, TYPEC_MUX_USB,
+				   TCPC_USB_SWITCH_CONNECT);
+	else
+		ret = tcpm_mux_set(port, TYPEC_MUX_NONE,
+				   TCPC_USB_SWITCH_DISCONNECT);
+	if (ret < 0)
+		return ret;
+
+	ret = port->tcpc->set_pd_header(port->tcpc, role, data);
+	if (ret < 0)
+		return ret;
+
+	port->con.pwr_role = role;
+	port->con.data_role = data;
+	typec_set_data_role(port->typec_port, data);
+	typec_set_pwr_role(port->typec_port, role);
+
+	return 0;
+}
+
+static int tcpm_set_pwr_role(struct tcpm_port *port, enum typec_role role)
+{
+	int ret;
+
+	ret = port->tcpc->set_pd_header(port->tcpc, role, port->con.data_role);
+	if (ret < 0)
+		return ret;
+
+	port->con.pwr_role = role;
+	typec_set_pwr_role(port->typec_port, role);
+
+	return 0;
+}
+
+static int tcpm_pd_send_source_caps(struct tcpm_port *port)
+{
+	struct pd_message msg;
+	int i;
+
+	memset(&msg, 0, sizeof(msg));
+	if (!port->tcpc->config->nr_src_pdo) {
+		/* No source capabilities defined, sink only */
+		msg.header = PD_HEADER_LE(PD_CTRL_REJECT,
+					  port->con.pwr_role,
+					  port->con.data_role,
+					  port->message_id, 0);
+	} else {
+		msg.header = PD_HEADER_LE(PD_DATA_SOURCE_CAP,
+					  port->con.pwr_role,
+					  port->con.data_role,
+					  port->message_id,
+					  port->tcpc->config->nr_src_pdo);
+	}
+	for (i = 0; i < port->tcpc->config->nr_src_pdo; i++)
+		msg.payload[i] = cpu_to_le32(port->tcpc->config->src_pdo[i]);
+
+	return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg);
+}
+
+static int tcpm_pd_send_sink_caps(struct tcpm_port *port)
+{
+	struct pd_message msg;
+	int i;
+
+	memset(&msg, 0, sizeof(msg));
+	if (!port->tcpc->config->nr_snk_pdo) {
+		/* No sink capabilities defined, source only */
+		msg.header = PD_HEADER_LE(PD_CTRL_REJECT,
+					  port->con.pwr_role,
+					  port->con.data_role,
+					  port->message_id, 0);
+	} else {
+		msg.header = PD_HEADER_LE(PD_DATA_SINK_CAP,
+					  port->con.pwr_role,
+					  port->con.data_role,
+					  port->message_id,
+					  port->tcpc->config->nr_snk_pdo);
+	}
+	for (i = 0; i < port->tcpc->config->nr_snk_pdo; i++)
+		msg.payload[i] = cpu_to_le32(port->tcpc->config->snk_pdo[i]);
+
+	return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg);
+}
+
+static void tcpm_set_state(struct tcpm_port *port, enum tcpm_state state,
+			   unsigned int delay_ms)
+{
+	if (delay_ms) {
+		tcpm_log(port, "pending state change %s -> %s @ %u ms",
+			 tcpm_states[port->state], tcpm_states[state],
+			 delay_ms);
+		port->delayed_state = state;
+		mod_delayed_work(port->wq, &port->state_machine,
+				 msecs_to_jiffies(delay_ms));
+		port->delayed_runtime = jiffies + msecs_to_jiffies(delay_ms);
+		port->delay_ms = delay_ms;
+	} else {
+		tcpm_log(port, "state change %s -> %s",
+			 tcpm_states[port->state], tcpm_states[state]);
+		port->delayed_state = INVALID_STATE;
+		port->prev_state = port->state;
+		port->state = state;
+		/*
+		 * Don't re-queue the state machine work item if we're currently
+		 * in the state machine and we're immediately changing states.
+		 * tcpm_state_machine_work() will continue running the state
+		 * machine.
+		 */
+		if (!port->state_machine_running)
+			mod_delayed_work(port->wq, &port->state_machine, 0);
+	}
+}
+
+static void tcpm_set_state_cond(struct tcpm_port *port, enum tcpm_state state,
+				unsigned int delay_ms)
+{
+	if (port->enter_state == port->state)
+		tcpm_set_state(port, state, delay_ms);
+	else
+		tcpm_log(port,
+			 "skipped %sstate change %s -> %s [%u ms], context state %s",
+			 delay_ms ? "delayed " : "",
+			 tcpm_states[port->state], tcpm_states[state],
+			 delay_ms, tcpm_states[port->enter_state]);
+}
+
+static void tcpm_queue_message(struct tcpm_port *port,
+			       enum pd_msg_request message)
+{
+	port->queued_message = message;
+	mod_delayed_work(port->wq, &port->state_machine, 0);
+}
+
+/*
+ * VDM/VDO handling functions
+ */
+static void tcpm_queue_vdm(struct tcpm_port *port, const u32 header,
+			   const u32 *data, int cnt)
+{
+	port->vdo_count = cnt + 1;
+	port->vdo_data[0] = header;
+	memcpy(&port->vdo_data[1], data, sizeof(u32) * cnt);
+	/* Set ready, vdm state machine will actually send */
+	port->vdm_retries = 0;
+	port->vdm_state = VDM_STATE_READY;
+}
+
+static void svdm_consume_identity(struct tcpm_port *port, const __le32 *payload,
+				  int cnt)
+{
+	int i, size;
+	u32 vdo = le32_to_cpu(payload[VDO_INDEX_IDH]);
+	u32 product = le32_to_cpu(payload[VDO_INDEX_PRODUCT]);
+
+	memset(&port->mode_data, 0, sizeof(port->mode_data));
+	size = min_t(int, cnt, ARRAY_SIZE(port->mode_data.identity));
+	for (i = 0; i < size; i++)
+		port->mode_data.identity[i] = le32_to_cpu(payload[i]);
+
+#if 0 /* Not really a match */
+	switch (PD_IDH_PTYPE(vdo)) {
+	case IDH_PTYPE_UNDEF:
+		port->partner.type = TYPEC_PARTNER_NONE; /* no longer exists */
+		break;
+	case IDH_PTYPE_HUB:
+		break;
+	case IDH_PTYPE_PERIPH:
+		break;
+	case IDH_PTYPE_PCABLE:
+		break;
+	case IDH_PTYPE_ACABLE:
+		break;
+	case IDH_PTYPE_AMA:
+		port->partner.type = TYPEC_PARTNER_ALTMODE;
+		break;
+	default:
+		break;
+	}
+#endif
+
+	port->partner.vdo = vdo;
+
+	tcpm_log(port, "Identity: %04x:%04x.%04x",
+		 PD_IDH_VID(vdo),
+		 PD_PRODUCT_PID(product), product & 0xffff);
+}
+
+static bool svdm_consume_svids(struct tcpm_port *port, const __le32 *payload,
+			       int cnt)
+{
+	struct pd_mode_data *pmdata = &port->mode_data;
+	int i;
+
+	for (i = 1; i < cnt; i++) {
+		u32 p = le32_to_cpu(payload[i]);
+		u16 svid;
+
+		svid = (p >> 16) & 0xffff;
+		if (!svid)
+			return false;
+
+		if (pmdata->nsvids >= SVID_DISCOVERY_MAX)
+			goto abort;
+
+		pmdata->svids[pmdata->nsvids++] = svid;
+		tcpm_log(port, "SVID %d: 0x%x", pmdata->nsvids, svid);
+
+		svid = p & 0xffff;
+		if (!svid)
+			return false;
+
+		if (pmdata->nsvids >= SVID_DISCOVERY_MAX)
+			goto abort;
+
+		pmdata->svids[pmdata->nsvids++] = svid;
+		tcpm_log(port, "SVID %d: 0x%x", pmdata->nsvids, svid);
+	}
+	return true;
+abort:
+	tcpm_log(port, "SVID_DISCOVERY_MAX(%d) too low!", SVID_DISCOVERY_MAX);
+	return false;
+}
+
+static void svdm_consume_modes(struct tcpm_port *port, const __le32 *payload,
+			       int cnt)
+{
+	struct pd_mode_data *pmdata = &port->mode_data;
+	struct typec_altmode *paltmode;
+	struct typec_mode *pmode;
+	int i;
+
+	if (pmdata->naltmodes < ALT_MODE_MAX && pmdata->nmodes < MODE_MAX) {
+		paltmode = &pmdata->altmodes[pmdata->naltmodes++];
+
+		memset(paltmode, 0, sizeof(*paltmode));
+
+		paltmode->svid = pmdata->svids[pmdata->svid_index];
+		paltmode->modes = &pmdata->modes[pmdata->nmodes];
+
+		tcpm_log(port, " Alternate mode %d: SVID 0x%04x",
+			 pmdata->naltmodes, paltmode->svid);
+
+		for (i = 1; i < cnt && pmdata->nmodes < MODE_MAX; i++) {
+			pmode = &pmdata->modes[pmdata->nmodes];
+			memset(pmode, 0, sizeof(*pmode));
+			pmode->vdo = le32_to_cpu(payload[i]);
+			pmode->index = i - 1;
+			pmdata->nmodes++;
+			paltmode->n_modes++;
+			tcpm_log(port, "  VDO %d: 0x%04x",
+				 pmode->index, pmode->vdo);
+		}
+	}
+	pmdata->nmodes++;
+}
+
+#define supports_modal(m)	PD_IDH_MODAL_SUPP((m)->identity[VDO_INDEX_IDH])
+
+static int tcpm_pd_svdm(struct tcpm_port *port, const __le32 *payload, int cnt,
+			u32 *response)
+{
+	u32 p0 = le32_to_cpu(payload[0]);
+	int cmd_type = PD_VDO_CMDT(p0);
+	int cmd = PD_VDO_CMD(p0);
+	struct pd_mode_data *modep;
+	int rlen = 0;
+	u16 svid;
+
+	tcpm_log(port, "Rx VDM cmd 0x%x type %d cmd %d len %d",
+		 p0, cmd_type, cmd, cnt);
+
+	modep = &port->mode_data;
+
+	switch (cmd_type) {
+	case CMDT_INIT:
+		switch (cmd) {
+		case CMD_DISCOVER_IDENT:
+			break;
+		case CMD_DISCOVER_SVID:
+			break;
+		case CMD_DISCOVER_MODES:
+			break;
+		case CMD_ENTER_MODE:
+			break;
+		case CMD_DP_STATUS:
+			break;
+		case CMD_DP_CONFIG:
+			break;
+		case CMD_EXIT_MODE:
+			break;
+		case CMD_ATTENTION:
+			break;
+		}
+		if (rlen >= 1) {
+			response[0] = p0 | VDO_CMDT(CMDT_RSP_ACK);
+		} else if (rlen == 0) {
+			response[0] = p0 | VDO_CMDT(CMDT_RSP_NAK);
+			rlen = 1;
+		} else {
+			response[0] = p0 | VDO_CMDT(CMDT_RSP_BUSY);
+			rlen = 1;
+		}
+		break;
+	case CMDT_RSP_ACK:
+		switch (cmd) {
+		case CMD_DISCOVER_IDENT:
+			/* 6.4.4.3.1 */
+			svdm_consume_identity(port, payload, cnt);
+			response[0] = VDO(USB_SID_PD, 1, CMD_DISCOVER_SVID);
+			rlen = 1;
+			break;
+		case CMD_DISCOVER_SVID:
+			/* 6.4.4.3.2 */
+			if (svdm_consume_svids(port, payload, cnt)) {
+				response[0] = VDO(USB_SID_PD, 1,
+						  CMD_DISCOVER_SVID);
+				rlen = 1;
+			} else if (modep->nsvids && supports_modal(modep)) {
+				response[0] = VDO(modep->svids[0], 1,
+						  CMD_DISCOVER_MODES);
+				rlen = 1;
+			}
+			break;
+		case CMD_DISCOVER_MODES:
+			/* 6.4.4.3.3 */
+			svdm_consume_modes(port, payload, cnt);
+			modep->svid_index++;
+			if (modep->svid_index < modep->nsvids) {
+				svid = modep->svids[modep->svid_index];
+				response[0] = VDO(svid, 1, CMD_DISCOVER_MODES);
+				rlen = 1;
+			} else {
+				if (!tcpm_register_altmodes(port)) {
+#if 0
+					response[0] =
+						pd_dfp_enter_mode(port, 0, 0);
+					if (response[0])
+						rlen = 1;
+#endif
+				}
+			}
+			break;
+		case CMD_ENTER_MODE:
+			break;
+		default:
+			break;
+		}
+		break;
+	default:
+		break;
+	}
+
+	return rlen;
+}
+
+static void tcpm_handle_vdm_request(struct tcpm_port *port,
+				    const __le32 *payload, int cnt)
+{
+	int rlen = 0;
+	u32 response[8] = { };
+	u32 p0 = le32_to_cpu(payload[0]);
+
+	if (port->vdm_state == VDM_STATE_BUSY) {
+		/* If UFP responded busy retry after timeout */
+		if (PD_VDO_CMDT(p0) == CMDT_RSP_BUSY) {
+			port->vdm_state = VDM_STATE_WAIT_RSP_BUSY;
+			port->vdo_retry = (p0 & ~VDO_CMDT_MASK) |
+				CMDT_INIT;
+			mod_delayed_work(port->wq, &port->vdm_state_machine,
+					 msecs_to_jiffies(PD_T_VDM_BUSY));
+			return;
+		}
+		port->vdm_state = VDM_STATE_DONE;
+	}
+
+	if (PD_VDO_SVDM(p0))
+		rlen = tcpm_pd_svdm(port, payload, cnt, response);
+#if 0
+	else
+		rlen = tcpm_pd_custom_vdm(port, cnt, payload, response);
+#endif
+
+	if (rlen > 0) {
+		tcpm_queue_vdm(port, response[0], &response[1], rlen - 1);
+		mod_delayed_work(port->wq, &port->vdm_state_machine, 0);
+	}
+}
+
+static void tcpm_send_vdm(struct tcpm_port *port, u32 vid, int cmd,
+			  const u32 *data, int count)
+{
+	u32 header;
+
+	if (WARN_ON(count > VDO_MAX_SIZE - 1))
+		count = VDO_MAX_SIZE - 1;
+
+	/* set VDM header with VID & CMD */
+	header = VDO(vid, ((vid & USB_SID_PD) == USB_SID_PD) ?
+			1 : (PD_VDO_CMD(cmd) <= CMD_ATTENTION), cmd);
+	tcpm_queue_vdm(port, header, data, count);
+
+	mod_delayed_work(port->wq, &port->vdm_state_machine, 0);
+}
+
+static unsigned vdm_ready_timeout(u32 vdm_hdr)
+{
+	unsigned timeout;
+	int cmd = PD_VDO_CMD(vdm_hdr);
+
+	/* its not a structured VDM command */
+	if (!PD_VDO_SVDM(vdm_hdr))
+		return PD_T_VDM_UNSTRUCTURED;
+
+	switch (PD_VDO_CMDT(vdm_hdr)) {
+	case CMDT_INIT:
+		if (cmd == CMD_ENTER_MODE || cmd == CMD_EXIT_MODE)
+			timeout = PD_T_VDM_WAIT_MODE_E;
+		else
+			timeout = PD_T_VDM_SNDR_RSP;
+		break;
+	default:
+		if (cmd == CMD_ENTER_MODE || cmd == CMD_EXIT_MODE)
+			timeout = PD_T_VDM_E_MODE;
+		else
+			timeout = PD_T_VDM_RCVR_RSP;
+		break;
+	}
+	return timeout;
+}
+
+static void vdm_run_state_machine(struct tcpm_port *port)
+{
+	struct pd_message msg;
+	int i, res;
+
+	switch (port->vdm_state) {
+	case VDM_STATE_READY:
+		/* Only transmit VDM if attached */
+		if (!port->attached) {
+			port->vdm_state = VDM_STATE_ERR_BUSY;
+			break;
+		}
+
+		/*
+		 * if there's traffic or we're not in PDO ready state don't send
+		 * a VDM.
+		 */
+		if (port->state != SRC_READY && port->state != SNK_READY)
+			break;
+
+		/* Prepare and send VDM */
+		memset(&msg, 0, sizeof(msg));
+		msg.header = PD_HEADER_LE(PD_DATA_VENDOR_DEF,
+					  port->con.pwr_role,
+					  port->con.data_role,
+					  port->message_id, port->vdo_count);
+		for (i = 0; i < port->vdo_count; i++)
+			msg.payload[i] = cpu_to_le32(port->vdo_data[i]);
+		res = tcpm_pd_transmit(port, TCPC_TX_SOP, &msg);
+		if (res < 0) {
+			port->vdm_state = VDM_STATE_ERR_SEND;
+		} else {
+			unsigned long timeout;
+
+			port->vdm_retries = 0;
+			port->vdm_state = VDM_STATE_BUSY;
+			timeout = vdm_ready_timeout(port->vdo_data[0]);
+			mod_delayed_work(port->wq, &port->vdm_state_machine,
+					 timeout);
+		}
+		break;
+	case VDM_STATE_WAIT_RSP_BUSY:
+		port->vdo_data[0] = port->vdo_retry;
+		port->vdo_count = 1;
+		port->vdm_state = VDM_STATE_READY;
+		break;
+	case VDM_STATE_BUSY:
+		port->vdm_state = VDM_STATE_ERR_TMOUT;
+		break;
+	case VDM_STATE_ERR_SEND:
+		/*
+		 * A partner which does not support USB PD will not reply,
+		 * so this is not a fatal error. At the same time, some
+		 * devices may not return GoodCRC under some circumstances,
+		 * so we need to retry.
+		 */
+		if (port->vdm_retries < 3) {
+			tcpm_log(port, "VDM Tx error, retry");
+			port->vdm_retries++;
+			port->vdm_state = VDM_STATE_READY;
+		}
+		break;
+	default:
+		break;
+	}
+}
+
+static void vdm_state_machine_work(struct work_struct *work)
+{
+	struct tcpm_port *port = container_of(work, struct tcpm_port,
+					      vdm_state_machine.work);
+	enum vdm_states prev_state;
+
+	mutex_lock(&port->lock);
+
+	/*
+	 * Continue running as long as the port is not busy and there was
+	 * a state change.
+	 */
+	do {
+		prev_state = port->vdm_state;
+		vdm_run_state_machine(port);
+	} while (port->vdm_state != prev_state &&
+		 port->vdm_state != VDM_STATE_BUSY);
+
+	mutex_unlock(&port->lock);
+}
+
+/*
+ * PD (data, control) command handling functions
+ */
+static void tcpm_pd_data_request(struct tcpm_port *port,
+				 const struct pd_message *msg)
+{
+	enum pd_data_msg_type type = pd_header_type_le(msg->header);
+	unsigned int cnt = pd_header_cnt_le(msg->header);
+	unsigned int i;
+
+	switch (type) {
+	case PD_DATA_SOURCE_CAP:
+		if (port->con.pwr_role != TYPEC_SINK)
+			break;
+
+		memcpy(&port->source_caps, msg->payload, cnt * sizeof(u32));
+		port->nr_source_caps = cnt;
+
+		for (i = 0; i < cnt; i++) {
+			u32 pdo = port->source_caps[i];
+			enum pd_pdo_type type = pdo_type(pdo);
+			char msg[64];
+
+			switch (type) {
+			case PDO_TYPE_FIXED:
+				scnprintf(msg, sizeof(msg),
+					  "%u mV, %u mA [%s%s%s%s%s%s]",
+					  pdo_fixed_voltage(pdo),
+					  pdo_max_current(pdo),
+					  (pdo & PDO_FIXED_DUAL_ROLE) ?
+								"R" : "",
+					  (pdo & PDO_FIXED_SUSPEND) ?
+								"S" : "",
+					  (pdo & PDO_FIXED_HIGHER_CAP) ?
+								"H" : "",
+					  (pdo & PDO_FIXED_USB_COMM) ?
+								"U" : "",
+					  (pdo & PDO_FIXED_DATA_SWAP) ?
+								"D" : "",
+					  (pdo & PDO_FIXED_EXTPOWER) ?
+								"E" : "");
+				break;
+			case PDO_TYPE_VAR:
+				scnprintf(msg, sizeof(msg),
+					  "%u-%u mV, %u mA",
+					  pdo_min_voltage(pdo),
+					  pdo_max_voltage(pdo),
+					  pdo_max_current(pdo));
+				break;
+			case PDO_TYPE_BATT:
+				scnprintf(msg, sizeof(msg),
+					  "%u-%u mV, %u mW",
+					  pdo_min_voltage(pdo),
+					  pdo_max_voltage(pdo),
+					  pdo_max_power(pdo));
+				break;
+			default:
+				strcpy(msg, "undefined");
+				break;
+			}
+			tcpm_log(port, " PDO %d: type %d, %s",
+				 i, type, msg);
+		}
+		/*
+		 * This message may be received even if VBUS is not
+		 * present. This is quite unexpected; see USB PD
+		 * specification, sections 8.3.3.6.3.1 and 8.3.3.6.3.2.
+		 * However, at the same time, we must be ready to
+		 * receive this message and respond to it 15ms after
+		 * receiving PS_RDY during power swap operations, no matter
+		 * if VBUS is available or not (USB PD specification,
+		 * section 6.5.9.2).
+		 * So we need to accept the message either way,
+		 * but be prepared to keep waiting for VBUS after it was
+		 * handled.
+		 */
+		tcpm_set_state(port, SNK_NEGOTIATE_CAPABILITIES, 0);
+		break;
+	case PD_DATA_REQUEST:
+		if (port->con.pwr_role != TYPEC_SOURCE ||
+		    cnt != 1) {
+			tcpm_queue_message(port, PD_MSG_CTRL_REJECT);
+			break;
+		}
+		port->sink_request = msg->payload[0];
+		tcpm_set_state(port, SRC_NEGOTIATE_CAPABILITIES, 0);
+		break;
+	case PD_DATA_SINK_CAP:
+		/* We don't do anything with this at the moment... */
+		for (i = 0; i < cnt; i++)
+			port->sink_caps[i] = le32_to_cpu(msg->payload[i]);
+		port->nr_sink_caps = cnt;
+		break;
+	case PD_DATA_VENDOR_DEF:
+		tcpm_handle_vdm_request(port, msg->payload, cnt);
+		break;
+	case PD_DATA_BIST:
+		if (port->state == SRC_READY || port->state == SNK_READY) {
+			port->bist_request = msg->payload[0];
+			tcpm_set_state(port, BIST_RX, 0);
+		}
+		break;
+	default:
+		tcpm_log(port, "Unhandled data message type %#x", type);
+		break;
+	}
+}
+
+static void tcpm_pd_ctrl_request(struct tcpm_port *port,
+				 const struct pd_message *msg)
+{
+	enum pd_ctrl_msg_type type = pd_header_type_le(msg->header);
+	enum tcpm_state next_state;
+
+	switch (type) {
+	case PD_CTRL_GOOD_CRC:
+	case PD_CTRL_PING:
+		break;
+	case PD_CTRL_GET_SOURCE_CAP:
+		switch (port->state) {
+		case SRC_READY:
+		case SNK_READY:
+			tcpm_queue_message(port, PD_MSG_DATA_SOURCE_CAP);
+			break;
+		default:
+			tcpm_queue_message(port, PD_MSG_CTRL_REJECT);
+			break;
+		}
+		break;
+	case PD_CTRL_GET_SINK_CAP:
+		switch (port->state) {
+		case SRC_READY:
+		case SNK_READY:
+			tcpm_queue_message(port, PD_MSG_DATA_SINK_CAP);
+			break;
+		default:
+			tcpm_queue_message(port, PD_MSG_CTRL_REJECT);
+			break;
+		}
+		break;
+	case PD_CTRL_GOTO_MIN:
+		break;
+	case PD_CTRL_PS_RDY:
+		switch (port->state) {
+		case SNK_TRANSITION_SINK:
+			if (port->vbus_present) {
+				tcpm_set_current_limit(port,
+						       port->current_limit,
+						       port->supply_voltage);
+				tcpm_set_state(port, SNK_READY, 0);
+			} else {
+				/*
+				 * Seen after power swap. Keep waiting for VBUS
+				 * in a transitional state.
+				 */
+				tcpm_set_state(port,
+					       SNK_TRANSITION_SINK_VBUS, 0);
+			}
+			break;
+		case PR_SWAP_SRC_SNK_SOURCE_OFF:
+			tcpm_set_state(port, PR_SWAP_SRC_SNK_SINK_ON, 0);
+			break;
+		case PR_SWAP_SNK_SRC_SINK_OFF:
+			tcpm_set_state(port, PR_SWAP_SNK_SRC_SOURCE_ON, 0);
+			break;
+		case VCONN_SWAP_WAIT_FOR_VCONN:
+			tcpm_set_state(port, VCONN_SWAP_TURN_OFF_VCONN, 0);
+			break;
+		default:
+			break;
+		}
+		break;
+	case PD_CTRL_REJECT:
+	case PD_CTRL_WAIT:
+		switch (port->state) {
+		case SNK_NEGOTIATE_CAPABILITIES:
+			/* USB PD specification, Figure 8-43 */
+			if (port->explicit_contract)
+				next_state = SNK_READY;
+			else
+				next_state = SNK_WAIT_CAPABILITIES;
+			tcpm_set_state(port, next_state, 0);
+			break;
+		case DR_SWAP_SEND:
+			port->swap_status = (type == PD_CTRL_WAIT ?
+					     -EAGAIN : -EOPNOTSUPP);
+			tcpm_set_state(port, DR_SWAP_CANCEL, 0);
+			break;
+		case PR_SWAP_SEND:
+			port->swap_status = (type == PD_CTRL_WAIT ?
+					     -EAGAIN : -EOPNOTSUPP);
+			tcpm_set_state(port, PR_SWAP_CANCEL, 0);
+			break;
+		case VCONN_SWAP_SEND:
+			port->swap_status = (type == PD_CTRL_WAIT ?
+					     -EAGAIN : -EOPNOTSUPP);
+			tcpm_set_state(port, VCONN_SWAP_CANCEL, 0);
+			break;
+		default:
+			break;
+		}
+		break;
+	case PD_CTRL_ACCEPT:
+		switch (port->state) {
+		case SNK_NEGOTIATE_CAPABILITIES:
+			tcpm_set_state(port, SNK_TRANSITION_SINK, 0);
+			break;
+		case SOFT_RESET_SEND:
+			port->message_id = 0;
+			if (port->con.pwr_role == TYPEC_SOURCE)
+				next_state = SRC_SEND_CAPABILITIES;
+			else
+				next_state = SNK_WAIT_CAPABILITIES;
+			tcpm_set_state(port, next_state, 0);
+			break;
+		case DR_SWAP_SEND:
+			tcpm_set_state(port, DR_SWAP_CHANGE_DR, 0);
+			break;
+		case PR_SWAP_SEND:
+			tcpm_set_state(port, PR_SWAP_START, 0);
+			break;
+		case VCONN_SWAP_SEND:
+			tcpm_set_state(port, VCONN_SWAP_START, 0);
+			break;
+		default:
+			break;
+		}
+		break;
+	case PD_CTRL_SOFT_RESET:
+		tcpm_set_state(port, SOFT_RESET, 0);
+		break;
+	case PD_CTRL_DR_SWAP:
+		if (port->typec_caps.type != TYPEC_PORT_DRP) {
+			tcpm_queue_message(port, PD_MSG_CTRL_REJECT);
+			break;
+		}
+		/*
+		 * XXX
+		 * 6.3.9: If an alternate mode is active, a request to swap
+		 * alternate modes shall trigger a port reset.
+		 */
+		switch (port->state) {
+		case SRC_READY:
+		case SNK_READY:
+			tcpm_set_state(port, DR_SWAP_ACCEPT, 0);
+			break;
+		default:
+			tcpm_queue_message(port, PD_MSG_CTRL_WAIT);
+			break;
+		}
+		break;
+	case PD_CTRL_PR_SWAP:
+		if (port->typec_caps.type != TYPEC_PORT_DRP) {
+			tcpm_queue_message(port, PD_MSG_CTRL_REJECT);
+			break;
+		}
+		switch (port->state) {
+		case SRC_READY:
+		case SNK_READY:
+			tcpm_set_state(port, PR_SWAP_ACCEPT, 0);
+			break;
+		default:
+			tcpm_queue_message(port, PD_MSG_CTRL_WAIT);
+			break;
+		}
+		break;
+	case PD_CTRL_VCONN_SWAP:
+		switch (port->state) {
+		case SRC_READY:
+		case SNK_READY:
+			tcpm_set_state(port, VCONN_SWAP_ACCEPT, 0);
+			break;
+		default:
+			tcpm_queue_message(port, PD_MSG_CTRL_WAIT);
+			break;
+		}
+		break;
+	default:
+		tcpm_log(port, "Unhandled ctrl message type %#x", type);
+		break;
+	}
+}
+
+static void tcpm_pd_rx_handler(struct work_struct *work)
+{
+	struct pd_rx_event *event = container_of(work,
+						 struct pd_rx_event, work);
+	const struct pd_message *msg = &event->msg;
+	unsigned int cnt = pd_header_cnt_le(msg->header);
+	struct tcpm_port *port = event->port;
+
+	mutex_lock(&port->lock);
+
+	tcpm_log(port, "PD RX, header: %#x [%d]", le16_to_cpu(msg->header),
+		 port->attached);
+
+	if (port->attached) {
+		/*
+		 * If both ends believe to be DFP/host, we have a data role
+		 * mismatch.
+		 */
+		if (!!(le16_to_cpu(msg->header) & PD_HEADER_DATA_ROLE) ==
+		    (port->con.data_role == TYPEC_HOST)) {
+			tcpm_log(port,
+				 "Data role mismatch, initiating error recovery");
+			tcpm_set_state(port, ERROR_RECOVERY, 0);
+		} else {
+			if (cnt)
+				tcpm_pd_data_request(port, msg);
+			else
+				tcpm_pd_ctrl_request(port, msg);
+		}
+	}
+
+	mutex_unlock(&port->lock);
+	kfree(event);
+}
+
+void tcpm_pd_receive(struct tcpm_port *port, const struct pd_message *msg)
+{
+	struct pd_rx_event *event;
+
+	event = kzalloc(sizeof(*event), GFP_ATOMIC);
+	if (!event)
+		return;
+
+	INIT_WORK(&event->work, tcpm_pd_rx_handler);
+	event->port = port;
+	memcpy(&event->msg, msg, sizeof(*msg));
+	queue_work(port->wq, &event->work);
+}
+EXPORT_SYMBOL_GPL(tcpm_pd_receive);
+
+static int tcpm_pd_send_control(struct tcpm_port *port,
+				enum pd_ctrl_msg_type type)
+{
+	struct pd_message msg;
+
+	memset(&msg, 0, sizeof(msg));
+	msg.header = PD_HEADER_LE(type, port->con.pwr_role,
+				  port->con.data_role,
+				  port->message_id, 0);
+
+	return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg);
+}
+
+/*
+ * Send queued message without affecting state.
+ * Return true if state machine should go back to sleep,
+ * false otherwise.
+ */
+static bool tcpm_send_queued_message(struct tcpm_port *port)
+{
+	enum pd_msg_request queued_message;
+
+	do {
+		queued_message = port->queued_message;
+		port->queued_message = PD_MSG_NONE;
+
+		switch (queued_message) {
+		case PD_MSG_CTRL_WAIT:
+			tcpm_pd_send_control(port, PD_CTRL_WAIT);
+			break;
+		case PD_MSG_CTRL_REJECT:
+			tcpm_pd_send_control(port, PD_MSG_CTRL_REJECT);
+			break;
+		case PD_MSG_DATA_SINK_CAP:
+			tcpm_pd_send_sink_caps(port);
+			break;
+		case PD_MSG_DATA_SOURCE_CAP:
+			tcpm_pd_send_source_caps(port);
+			break;
+		default:
+			break;
+		}
+	} while (port->queued_message != PD_MSG_NONE);
+
+	if (port->delayed_state != INVALID_STATE) {
+		if (time_is_after_jiffies(port->delayed_runtime)) {
+			mod_delayed_work(port->wq, &port->state_machine,
+					 port->delayed_runtime - jiffies);
+			return true;
+		}
+		port->delayed_state = INVALID_STATE;
+	}
+	return false;
+}
+
+static int tcpm_pd_check_request(struct tcpm_port *port)
+{
+	u32 pdo, rdo = port->sink_request;
+	unsigned int max, op, pdo_max, index;
+	enum pd_pdo_type type;
+
+	index = rdo_index(rdo);
+	if (!index || index > port->tcpc->config->nr_src_pdo)
+		return -EINVAL;
+
+	pdo = port->tcpc->config->src_pdo[index - 1];
+	type = pdo_type(pdo);
+	switch (type) {
+	case PDO_TYPE_FIXED:
+	case PDO_TYPE_VAR:
+		max = rdo_max_current(rdo);
+		op = rdo_op_current(rdo);
+		pdo_max = pdo_max_current(pdo);
+
+		if (op > pdo_max)
+			return -EINVAL;
+		if (max > pdo_max && !(rdo & RDO_CAP_MISMATCH))
+			return -EINVAL;
+
+		if (type == PDO_TYPE_FIXED)
+			tcpm_log(port,
+				 "Requested %u mV, %u mA for %u / %u mA",
+				 pdo_fixed_voltage(pdo), pdo_max, op, max);
+		else
+			tcpm_log(port,
+				 "Requested %u -> %u mV, %u mA for %u / %u mA",
+				 pdo_min_voltage(pdo), pdo_max_voltage(pdo),
+				 pdo_max, op, max);
+		break;
+	case PDO_TYPE_BATT:
+		max = rdo_max_power(rdo);
+		op = rdo_op_power(rdo);
+		pdo_max = pdo_max_power(pdo);
+
+		if (op > pdo_max)
+			return -EINVAL;
+		if (max > pdo_max && !(rdo & RDO_CAP_MISMATCH))
+			return -EINVAL;
+		tcpm_log(port,
+			 "Requested %u -> %u mV, %u mW for %u / %u mW",
+			 pdo_min_voltage(pdo), pdo_max_voltage(pdo),
+			 pdo_max, op, max);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	port->op_vsafe5v = index == 1;
+
+	return 0;
+}
+
+static int tcpm_pd_select_pdo(struct tcpm_port *port)
+{
+	unsigned int i, max_mw = 0, max_mv = 0;
+	int ret = -EINVAL;
+
+	/*
+	 * Select the source PDO providing the most power while staying within
+	 * the board's voltage limits. Prefer PDO providing exp
+	 */
+	for (i = 0; i < port->nr_source_caps; i++) {
+		u32 pdo = port->source_caps[i];
+		enum pd_pdo_type type = pdo_type(pdo);
+		unsigned int mv, ma, mw;
+
+		if (type == PDO_TYPE_FIXED)
+			mv = pdo_fixed_voltage(pdo);
+		else
+			mv = pdo_min_voltage(pdo);
+
+		if (type == PDO_TYPE_BATT) {
+			mw = pdo_max_power(pdo);
+		} else {
+			ma = min(pdo_max_current(pdo),
+				 port->tcpc->config->max_snk_ma);
+			mw = ma * mv / 1000;
+		}
+
+		/* Perfer higher voltages if available */
+		if ((mw > max_mw || (mw == max_mw && mv > max_mv)) &&
+		    mv <= port->tcpc->config->max_snk_mv) {
+			ret = i;
+			max_mw = mw;
+			max_mv = mv;
+		}
+	}
+
+	return ret;
+}
+
+static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo)
+{
+	unsigned int mv, ma, mw, flags;
+	unsigned int max_ma, max_mw;
+	enum pd_pdo_type type;
+	int index;
+	u32 pdo;
+
+	index = tcpm_pd_select_pdo(port);
+	if (index < 0)
+		return -EINVAL;
+	pdo = port->source_caps[index];
+	type = pdo_type(pdo);
+
+	if (type == PDO_TYPE_FIXED)
+		mv = pdo_fixed_voltage(pdo);
+	else
+		mv = pdo_min_voltage(pdo);
+
+	/* Select maximum available current within the board's power limit */
+	if (type == PDO_TYPE_BATT) {
+		mw = pdo_max_power(pdo);
+		ma = 1000 * min(mw, port->tcpc->config->max_snk_mw) / mv;
+	} else {
+		ma = min(pdo_max_current(pdo),
+			 1000 * port->tcpc->config->max_snk_mw / mv);
+	}
+	ma = min(ma, port->tcpc->config->max_snk_ma);
+
+	/* XXX: Any other flags need to be set? */
+	flags = 0;
+
+	/* Set mismatch bit if offered power is less than operating power */
+	mw = ma * mv / 1000;
+	max_ma = ma;
+	max_mw = mw;
+	if (mw < port->tcpc->config->operating_snk_mw) {
+		flags |= RDO_CAP_MISMATCH;
+		max_mw = port->tcpc->config->operating_snk_mw;
+		max_ma = max_mw * 1000 / mv;
+	}
+
+	tcpm_log(port, "cc=%d cc1=%d cc2=%d vbus=%d vconn=%s polarity=%d",
+		 port->cc_req, port->cc1, port->cc2, port->vbus_source,
+		 port->con.vconn_role == TYPEC_SOURCE ? "source" : "sink",
+		 port->polarity);
+
+	if (type == PDO_TYPE_BATT) {
+		*rdo = RDO_BATT(index + 1, mw, max_mw, flags);
+
+		tcpm_log(port, "Requesting PDO %d: %u mV, %u mW%s",
+			 index, mv, mw,
+			 flags & RDO_CAP_MISMATCH ? " [mismatch]" : "");
+	} else {
+		*rdo = RDO_FIXED(index + 1, ma, max_ma, flags);
+
+		tcpm_log(port, "Requesting PDO %d: %u mV, %u mA%s",
+			 index, mv, ma,
+			 flags & RDO_CAP_MISMATCH ? " [mismatch]" : "");
+	}
+
+	port->current_limit = ma;
+	port->supply_voltage = mv;
+
+	return 0;
+}
+
+static int tcpm_pd_send_request(struct tcpm_port *port)
+{
+	struct pd_message msg;
+	int ret;
+	u32 rdo;
+
+	ret = tcpm_pd_build_request(port, &rdo);
+	if (ret < 0)
+		return ret;
+
+	memset(&msg, 0, sizeof(msg));
+	msg.header = PD_HEADER_LE(PD_DATA_REQUEST,
+				  port->con.pwr_role,
+				  port->con.data_role,
+				  port->message_id, 1);
+	msg.payload[0] = cpu_to_le32(rdo);
+
+	return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg);
+}
+
+static int tcpm_set_vbus(struct tcpm_port *port, bool enable)
+{
+	int ret;
+
+	if (enable && port->vbus_charge)
+		return -EINVAL;
+
+	tcpm_log(port, "vbus:=%d charge=%d", enable, port->vbus_charge);
+
+	ret = port->tcpc->set_vbus(port->tcpc, enable, port->vbus_charge);
+	if (ret < 0)
+		return ret;
+
+	port->vbus_source = enable;
+	return 0;
+}
+
+static int tcpm_set_charge(struct tcpm_port *port, bool charge)
+{
+	int ret;
+
+	if (charge && port->vbus_source)
+		return -EINVAL;
+
+	if (charge != port->vbus_charge) {
+		tcpm_log(port, "vbus=%d charge:=%d", port->vbus_source, charge);
+		ret = port->tcpc->set_vbus(port->tcpc, port->vbus_source,
+					   charge);
+		if (ret < 0)
+			return ret;
+	}
+	port->vbus_charge = charge;
+	return 0;
+}
+
+static void tcpm_set_cc(struct tcpm_port *port, enum typec_cc_status cc)
+{
+	tcpm_log(port, "cc:=%d", cc);
+	port->cc_req = cc;
+	port->tcpc->set_cc(port->tcpc, cc);
+}
+
+static int tcpm_init_vbus(struct tcpm_port *port)
+{
+	int ret;
+
+	ret = port->tcpc->set_vbus(port->tcpc, false, false);
+	port->vbus_source = false;
+	port->vbus_charge = false;
+	return ret;
+}
+
+static int tcpm_init_vconn(struct tcpm_port *port)
+{
+	int ret;
+
+	ret = port->tcpc->set_vconn(port->tcpc, false);
+	port->con.vconn_role = TYPEC_SINK;
+	return ret;
+}
+
+static void tcpm_typec_connect(struct tcpm_port *port)
+{
+	if (!port->connected) {
+		port->partner.usb_pd = port->pd_capable;
+		typec_connect(port->typec_port, &port->con);
+		port->connected = true;
+	}
+}
+
+static int tcpm_src_attach(struct tcpm_port *port)
+{
+	int ret;
+
+	if (port->attached)
+		return 0;
+
+	ret = tcpm_set_polarity(port, port->cc2 == TYPEC_CC_RD ?
+				TYPEC_POLARITY_CC2 : TYPEC_POLARITY_CC1);
+	if (ret < 0)
+		return ret;
+
+	ret = tcpm_set_roles(port, TYPEC_SOURCE, TYPEC_HOST);
+	if (ret < 0)
+		return ret;
+
+	ret = port->tcpc->set_pd_rx(port->tcpc, true);
+	if (ret < 0)
+		goto out_disable_mux;
+
+	ret = tcpm_set_vconn(port, true);
+	if (ret < 0)
+		goto out_disable_pd;
+
+	ret = tcpm_set_vbus(port, true);
+	if (ret < 0)
+		goto out_disable_vconn;
+
+	port->pd_capable = false;
+
+	memset(&port->partner, 0, sizeof(port->partner));
+
+	port->attached = true;
+	port->send_discover = true;
+
+	return 0;
+
+out_disable_vconn:
+	tcpm_set_vconn(port, false);
+out_disable_pd:
+	port->tcpc->set_pd_rx(port->tcpc, false);
+out_disable_mux:
+	tcpm_mux_set(port, TYPEC_MUX_NONE, TCPC_USB_SWITCH_DISCONNECT);
+	return ret;
+}
+
+static void tcpm_typec_disconnect(struct tcpm_port *port)
+{
+	if (port->connected) {
+		typec_disconnect(port->typec_port);
+		port->connected = false;
+	}
+}
+
+static void tcpm_unregister_altmodes(struct tcpm_port *port)
+{
+	if (port->altmodes_registered) {
+		typec_unregister_altmodes(&port->partner.dev);
+		port->altmodes_registered = false;
+		memset(&port->mode_data, 0, sizeof(port->mode_data));
+	}
+}
+
+static void tcpm_reset_port(struct tcpm_port *port)
+{
+	tcpm_unregister_altmodes(port);
+	tcpm_typec_disconnect(port);
+	port->attached = false;
+	port->pd_capable = false;
+
+	port->tcpc->set_pd_rx(port->tcpc, false);
+	tcpm_init_vbus(port);	/* also disables charging */
+	tcpm_init_vconn(port);
+	tcpm_set_current_limit(port, 0, 0);
+	tcpm_set_polarity(port, TYPEC_POLARITY_CC1);
+	tcpm_mux_set(port, TYPEC_MUX_NONE, TCPC_USB_SWITCH_DISCONNECT);
+}
+
+static void tcpm_detach(struct tcpm_port *port)
+{
+	if (!port->attached)
+		return;
+
+	if (tcpm_port_is_disconnected(port))
+		port->hard_reset_count = 0;
+
+	tcpm_reset_port(port);
+}
+
+static void tcpm_src_detach(struct tcpm_port *port)
+{
+	tcpm_detach(port);
+}
+
+static int tcpm_snk_attach(struct tcpm_port *port)
+{
+	int ret;
+
+	if (port->attached)
+		return 0;
+
+	ret = tcpm_set_polarity(port, port->cc2 != TYPEC_CC_OPEN ?
+				TYPEC_POLARITY_CC2 : TYPEC_POLARITY_CC1);
+	if (ret < 0)
+		return ret;
+
+	ret = tcpm_set_roles(port, TYPEC_SINK, TYPEC_DEVICE);
+	if (ret < 0)
+		return ret;
+
+	port->pd_capable = false;
+
+	memset(&port->partner, 0, sizeof(port->partner));
+
+	port->attached = true;
+	port->send_discover = true;
+
+	return 0;
+}
+
+static void tcpm_snk_detach(struct tcpm_port *port)
+{
+	tcpm_detach(port);
+
+	/* XXX: (Dis)connect SuperSpeed mux? */
+}
+
+static int tcpm_acc_attach(struct tcpm_port *port)
+{
+	int ret;
+
+	if (!port->attached)
+		return 0;
+
+	ret = tcpm_set_roles(port, TYPEC_SOURCE, TYPEC_HOST);
+	if (ret < 0)
+		return ret;
+
+	memset(&port->partner, 0, sizeof(port->partner));
+
+	port->attached = true;
+
+	return 0;
+}
+
+static void tcpm_acc_detach(struct tcpm_port *port)
+{
+	tcpm_detach(port);
+}
+
+static inline enum tcpm_state hard_reset_state(struct tcpm_port *port)
+{
+	if (port->hard_reset_count < PD_N_HARD_RESET_COUNT)
+		return HARD_RESET_SEND;
+	if (port->pd_capable)
+		return ERROR_RECOVERY;
+	if (port->con.pwr_role == TYPEC_SOURCE)
+		return SRC_UNATTACHED;
+	if (port->state == SNK_WAIT_CAPABILITIES)
+		return SNK_READY;
+	return SNK_UNATTACHED;
+}
+
+static inline enum tcpm_state ready_state(struct tcpm_port *port)
+{
+	if (port->con.pwr_role == TYPEC_SOURCE)
+		return SRC_READY;
+	else
+		return SNK_READY;
+}
+
+static inline enum tcpm_state unattached_state(struct tcpm_port *port)
+{
+	if (port->con.pwr_role == TYPEC_SOURCE)
+		return SRC_UNATTACHED;
+	else
+		return SNK_UNATTACHED;
+}
+
+static void tcpm_check_send_discover(struct tcpm_port *port)
+{
+	if (port->con.data_role == TYPEC_HOST && port->send_discover &&
+	    port->pd_capable) {
+		tcpm_send_vdm(port, USB_SID_PD, CMD_DISCOVER_IDENT, NULL, 0);
+		port->send_discover = false;
+	}
+}
+
+static void tcpm_swap_complete(struct tcpm_port *port, int result)
+{
+	if (port->swap_pending) {
+		port->swap_status = result;
+		port->swap_pending = false;
+		complete(&port->swap_complete);
+	}
+}
+
+static void run_state_machine(struct tcpm_port *port)
+{
+	int ret;
+
+	port->enter_state = port->state;
+	switch (port->state) {
+	/* SRC states */
+	case SRC_UNATTACHED:
+		tcpm_swap_complete(port, -ENOTCONN);
+		tcpm_src_detach(port);
+		tcpm_set_cc(port, TYPEC_CC_RP_DEF);
+		if (port->typec_caps.type == TYPEC_PORT_DRP)
+			tcpm_set_state(port, SNK_UNATTACHED, PD_T_DRP_SNK);
+		break;
+	case SRC_ATTACH_WAIT:
+		if (port->cc1 == TYPEC_CC_RD &&
+		    port->cc2 == TYPEC_CC_RD)
+			tcpm_set_state(port, DEBUG_ACC_ATTACHED,
+				       PD_T_CC_DEBOUNCE);
+		else if (port->cc1 == TYPEC_CC_RA &&
+			 port->cc2 == TYPEC_CC_RA)
+			tcpm_set_state(port, AUDIO_ACC_ATTACHED,
+				       PD_T_CC_DEBOUNCE);
+		else if (tcpm_port_is_source(port))
+			tcpm_set_state(port,
+				       tcpm_try_snk(port) ? SNK_TRY
+							  : SRC_ATTACHED,
+				       PD_T_CC_DEBOUNCE);
+		break;
+
+	case SNK_TRY:
+		port->try_snk_count++;
+		/*
+		 * Requirements:
+		 * - Do not drive vconn or vbus
+		 * - Terminate CC pins (both) to Rd
+		 * Action:
+		 * - Wait for tDRPTry (PD_T_DRP_TRY).
+		 *   Until then, ignore any state changes.
+		 */
+		tcpm_set_cc(port, TYPEC_CC_RD);
+		tcpm_set_state(port, SNK_TRY_WAIT, PD_T_DRP_TRY);
+		break;
+	case SNK_TRY_WAIT:
+		if (port->vbus_present && tcpm_port_is_sink(port)) {
+			tcpm_set_state(port, SNK_ATTACHED, 0);
+			break;
+		}
+		if (!tcpm_port_is_sink(port)) {
+			tcpm_set_state(port, SRC_TRYWAIT,
+				       PD_T_PD_DEBOUNCE);
+			break;
+		}
+		/* No vbus, cc state is sink or open */
+		tcpm_set_state(port, SRC_TRYWAIT_UNATTACHED, PD_T_DRP_TRYWAIT);
+		break;
+	case SRC_TRYWAIT:
+		tcpm_set_cc(port, TYPEC_CC_RP_DEF);
+		if (!port->vbus_present && tcpm_port_is_source(port))
+			tcpm_set_state(port, SRC_ATTACHED, PD_T_CC_DEBOUNCE);
+		else
+			tcpm_set_state(port, SRC_TRYWAIT_UNATTACHED,
+				       PD_T_DRP_TRY);
+		break;
+	case SRC_TRYWAIT_UNATTACHED:
+		tcpm_set_state(port, SNK_UNATTACHED, 0);
+		break;
+
+	case SRC_ATTACHED:
+		ret = tcpm_src_attach(port);
+		tcpm_set_state(port, SRC_UNATTACHED,
+			       ret < 0 ? 0 : PD_T_PS_SOURCE_ON);
+		break;
+	case SRC_STARTUP:
+		typec_set_pwr_opmode(port->typec_port, TYPEC_PWR_MODE_USB);
+		port->con.pwr_opmode = TYPEC_PWR_MODE_USB;
+		port->caps_count = 0;
+		port->message_id = 0;
+		port->explicit_contract = false;
+		tcpm_set_state(port, SRC_SEND_CAPABILITIES, 0);
+		break;
+	case SRC_SEND_CAPABILITIES:
+		port->caps_count++;
+		if (port->caps_count > PD_N_CAPS_COUNT)
+			break;
+		ret = tcpm_pd_send_source_caps(port);
+		if (ret < 0) {
+			tcpm_set_state(port, SRC_SEND_CAPABILITIES,
+				       PD_T_SEND_SOURCE_CAP);
+		} else {
+			/*
+			 * Per standard, we should clear the reset counter here.
+			 * However, that can result in state machine hang-ups.
+			 * Reset it only in READY state to improve stability.
+			 */
+			/* port->hard_reset_count = 0; */
+			port->caps_count = 0;
+			port->pd_capable = true;
+			tcpm_set_state_cond(port, hard_reset_state(port),
+					    PD_T_SEND_SOURCE_CAP);
+		}
+		break;
+	case SRC_NEGOTIATE_CAPABILITIES:
+		ret = tcpm_pd_check_request(port);
+		if (ret < 0) {
+			tcpm_pd_send_control(port, PD_CTRL_REJECT);
+			if (!port->explicit_contract) {
+				tcpm_set_state(port,
+					       SRC_WAIT_NEW_CAPABILITIES, 0);
+			} else {
+				tcpm_set_state(port, SRC_READY, 0);
+			}
+		} else {
+			tcpm_pd_send_control(port, PD_CTRL_ACCEPT);
+			tcpm_set_state(port, SRC_TRANSITION_SUPPLY,
+				       PD_T_SRC_TRANSITION);
+		}
+		break;
+	case SRC_TRANSITION_SUPPLY:
+		/* XXX: regulator_set_voltage(vbus, ...) */
+		tcpm_pd_send_control(port, PD_CTRL_PS_RDY);
+		port->explicit_contract = true;
+		typec_set_pwr_opmode(port->typec_port, TYPEC_PWR_MODE_PD);
+		port->con.pwr_opmode = TYPEC_PWR_MODE_PD;
+		tcpm_set_state_cond(port, SRC_READY, 0);
+		break;
+	case SRC_READY:
+#if 1
+		port->hard_reset_count = 0;
+#endif
+		port->try_src_count = 0;
+
+		tcpm_typec_connect(port);
+
+		tcpm_check_send_discover(port);
+		/*
+		 * 6.3.5
+		 * Sending ping messages is not necessary if
+		 * - the source operates at vSafe5V
+		 * or
+		 * - The system is not operating in PD mode
+		 * or
+		 * - Both partners are connected using a Type-C connector
+		 *   XXX How do we know that ?
+		 */
+		if (port->con.pwr_opmode == TYPEC_PWR_MODE_PD &&
+		    !port->op_vsafe5v) {
+			tcpm_pd_send_control(port, PD_CTRL_PING);
+			tcpm_set_state_cond(port, SRC_READY,
+					    PD_T_SOURCE_ACTIVITY);
+		}
+		break;
+	case SRC_WAIT_NEW_CAPABILITIES:
+		/* Nothing to do... */
+		break;
+
+	/* SNK states */
+	case SNK_UNATTACHED:
+		tcpm_swap_complete(port, -ENOTCONN);
+		tcpm_snk_detach(port);
+		tcpm_set_cc(port, TYPEC_CC_RD);
+		if (port->typec_caps.type == TYPEC_PORT_DRP)
+			tcpm_set_state(port, SRC_UNATTACHED, PD_T_DRP_SRC);
+		break;
+	case SNK_ATTACH_WAIT:
+		if ((port->cc1 == TYPEC_CC_OPEN &&
+		     port->cc2 != TYPEC_CC_OPEN) ||
+		    (port->cc1 != TYPEC_CC_OPEN &&
+		     port->cc2 == TYPEC_CC_OPEN))
+			tcpm_set_state(port, SNK_DEBOUNCED,
+				       PD_T_CC_DEBOUNCE);
+		else if (tcpm_port_is_disconnected(port))
+			tcpm_set_state(port, SNK_UNATTACHED,
+				       PD_T_PD_DEBOUNCE);
+		break;
+	case SNK_DEBOUNCED:
+		if (tcpm_port_is_disconnected(port))
+			tcpm_set_state(port, SNK_UNATTACHED,
+				       PD_T_PD_DEBOUNCE);
+		else if (port->vbus_present)
+			tcpm_set_state(port,
+				       tcpm_try_src(port) ? SRC_TRY
+							  : SNK_ATTACHED,
+				       0);
+		else
+			/* Wait for VBUS, but not forever */
+			tcpm_set_state(port, SNK_UNATTACHED, PD_T_PS_SOURCE_ON);
+		break;
+
+	case SRC_TRY:
+		port->try_src_count++;
+		tcpm_set_cc(port, TYPEC_CC_RP_DEF);
+		tcpm_set_state(port, SNK_TRYWAIT, PD_T_DRP_TRY);
+		break;
+	case SRC_TRY_DEBOUNCE:
+		tcpm_set_state(port, SRC_ATTACHED, PD_T_PD_DEBOUNCE);
+		break;
+	case SNK_TRYWAIT:
+		tcpm_set_cc(port, TYPEC_CC_RD);
+		tcpm_set_state(port, SNK_TRYWAIT_DEBOUNCE, PD_T_CC_DEBOUNCE);
+		break;
+	case SNK_TRYWAIT_DEBOUNCE:
+		if (port->vbus_present) {
+			tcpm_set_state(port, SNK_ATTACHED, 0);
+			break;
+		}
+		if (tcpm_port_is_disconnected(port)) {
+			tcpm_set_state(port, SNK_UNATTACHED,
+				       PD_T_PD_DEBOUNCE);
+			break;
+		}
+		if (tcpm_port_is_source(port))
+			tcpm_set_state(port, SRC_ATTACHED, 0);
+		/* XXX Are we supposed to stay in this state ? */
+		break;
+	case SNK_TRYWAIT_VBUS:
+		tcpm_set_state(port, SNK_ATTACHED, PD_T_CC_DEBOUNCE);
+		break;
+
+	case SNK_ATTACHED:
+		ret = tcpm_snk_attach(port);
+		if (ret < 0)
+			tcpm_set_state(port, SNK_UNATTACHED, 0);
+		else
+			tcpm_set_state(port, SNK_STARTUP, 0);
+		break;
+	case SNK_STARTUP:
+		/* XXX: callback into infrastructure */
+		typec_set_pwr_opmode(port->typec_port, TYPEC_PWR_MODE_USB);
+		port->con.pwr_opmode = TYPEC_PWR_MODE_USB;
+		port->message_id = 0;
+		port->explicit_contract = false;
+		tcpm_set_state(port, SNK_DISCOVERY, 0);
+		break;
+	case SNK_DISCOVERY:
+		if (port->vbus_present) {
+			tcpm_set_current_limit(port,
+					       tcpm_get_current_limit(port),
+					       5000);
+			tcpm_set_charge(port, true);
+			tcpm_set_state(port, SNK_WAIT_CAPABILITIES, 0);
+			break;
+		}
+		/*
+		 * For DRP, timeouts differ. Also, handling is supposed to be
+		 * different and much more complex (dead battery detection;
+		 * see USB power delivery specification, section 8.3.3.6.1.5.1).
+		 */
+		tcpm_set_state(port, hard_reset_state(port),
+			       port->typec_caps.type == TYPEC_PORT_DRP ?
+					PD_T_DB_DETECT : PD_T_NO_RESPONSE);
+		break;
+	case SNK_DISCOVERY_DEBOUNCE:
+		tcpm_set_state(port, SNK_DISCOVERY_DEBOUNCE_DONE,
+			       PD_T_CC_DEBOUNCE);
+		break;
+	case SNK_DISCOVERY_DEBOUNCE_DONE:
+		if (!tcpm_port_is_disconnected(port) &&
+		    tcpm_port_is_sink(port) &&
+		    time_is_after_jiffies(port->delayed_runtime)) {
+			tcpm_set_state(port, SNK_DISCOVERY,
+				       port->delayed_runtime - jiffies);
+			break;
+		}
+		tcpm_set_state(port, unattached_state(port), 0);
+		break;
+	case SNK_WAIT_CAPABILITIES:
+		ret = port->tcpc->set_pd_rx(port->tcpc, true);
+		if (ret < 0) {
+			tcpm_set_state(port, SNK_READY, 0);
+			break;
+		}
+		/*
+		 * If VBUS has never been low, and we time out waiting
+		 * for source cap, try a soft reset first, in case we
+		 * were already in a stable contract before this boot.
+		 * Do this only once.
+		 */
+		if (port->vbus_never_low) {
+			port->vbus_never_low = false;
+			tcpm_set_state(port, SOFT_RESET_SEND,
+				       PD_T_SINK_WAIT_CAP);
+		} else {
+			tcpm_set_state(port, hard_reset_state(port),
+				       PD_T_SINK_WAIT_CAP);
+		}
+		break;
+	case SNK_NEGOTIATE_CAPABILITIES:
+		port->pd_capable = true;
+		port->hard_reset_count = 0;
+		ret = tcpm_pd_send_request(port);
+		if (ret < 0) {
+			/* Let the Source send capabilities again. */
+			tcpm_set_state(port, SNK_WAIT_CAPABILITIES, 0);
+		} else {
+			tcpm_set_state_cond(port, hard_reset_state(port),
+					    PD_T_SENDER_RESPONSE);
+		}
+		break;
+	case SNK_TRANSITION_SINK:
+	case SNK_TRANSITION_SINK_VBUS:
+		tcpm_set_state(port, hard_reset_state(port),
+			       PD_T_PS_TRANSITION);
+		break;
+	case SNK_READY:
+		port->try_snk_count = 0;
+		port->explicit_contract = true;
+		typec_set_pwr_opmode(port->typec_port, TYPEC_PWR_MODE_PD);
+		port->con.pwr_opmode = TYPEC_PWR_MODE_PD;
+
+		tcpm_typec_connect(port);
+
+		tcpm_check_send_discover(port);
+		break;
+
+	/* Accessory states */
+	case ACC_UNATTACHED:
+		tcpm_acc_detach(port);
+		tcpm_set_state(port, SRC_UNATTACHED, 0);
+		break;
+	case DEBUG_ACC_ATTACHED:
+	case AUDIO_ACC_ATTACHED:
+		ret = tcpm_acc_attach(port);
+		if (ret < 0)
+			tcpm_set_state(port, ACC_UNATTACHED, 0);
+		break;
+	case AUDIO_ACC_DEBOUNCE:
+		tcpm_set_state(port, ACC_UNATTACHED, PD_T_CC_DEBOUNCE);
+		break;
+
+	/* Hard_Reset states */
+	case HARD_RESET_SEND:
+		tcpm_pd_transmit(port, TCPC_TX_HARD_RESET, NULL);
+		tcpm_set_state(port, HARD_RESET_START, 0);
+		break;
+	case HARD_RESET_START:
+		port->hard_reset_count++;
+		port->tcpc->set_pd_rx(port->tcpc, false);
+		tcpm_unregister_altmodes(port);
+		port->send_discover = true;
+		if (port->con.pwr_role == TYPEC_SOURCE)
+			tcpm_set_state(port, SRC_HARD_RESET_VBUS_OFF,
+				       PD_T_PS_HARD_RESET);
+		else
+			tcpm_set_state(port, SNK_HARD_RESET_SINK_OFF, 0);
+		break;
+	case SRC_HARD_RESET_VBUS_OFF:
+		tcpm_set_vconn(port, true);
+		tcpm_set_vbus(port, false);
+		tcpm_set_roles(port, TYPEC_SOURCE, TYPEC_HOST);
+		tcpm_set_state(port, SRC_HARD_RESET_VBUS_ON, PD_T_SRC_RECOVER);
+		break;
+	case SRC_HARD_RESET_VBUS_ON:
+		tcpm_set_vbus(port, true);
+		tcpm_set_state(port, SRC_UNATTACHED, PD_T_PS_SOURCE_ON);
+		break;
+	case SNK_HARD_RESET_SINK_OFF:
+		tcpm_set_vconn(port, false);
+		tcpm_set_charge(port, false);
+		tcpm_set_roles(port, TYPEC_SINK, TYPEC_DEVICE);
+		/*
+		 * VBUS may or may not toggle, depending on the adapter.
+		 * If it doesn't toggle, transition to SNK_HARD_RESET_SINK_ON
+		 * directly after timeout.
+		 */
+		tcpm_set_state(port, SNK_HARD_RESET_SINK_ON, PD_T_SAFE_0V);
+		break;
+	case SNK_HARD_RESET_WAIT_VBUS:
+		/* Assume we're disconnected if VBUS doesn't come back. */
+		tcpm_set_state(port, SNK_UNATTACHED,
+			       PD_T_SRC_RECOVER_MAX + PD_T_SRC_TURN_ON);
+		break;
+	case SNK_HARD_RESET_SINK_ON:
+		/* Note: There is no guarantee that VBUS is on in this state */
+		/*
+		 * XXX:
+		 * The specification suggests that dual mode ports in sink
+		 * mode should transition to state PE_SRC_Transition_to_default.
+		 * See USB power delivery specification chapter 8.3.3.6.1.3.
+		 * This would mean to to
+		 * - turn off VCONN, reset power supply
+		 * - request hardware reset
+		 * - turn on VCONN
+		 * - Transition to state PE_Src_Startup
+		 * SNK only ports shall transition to state Snk_Startup
+		 * (see chapter 8.3.3.3.8).
+		 * Similar, dual-mode ports in source mode should transition
+		 * to PE_SNK_Transition_to_default.
+		 */
+		tcpm_set_state(port, SNK_STARTUP, 0);
+		break;
+
+	/* Soft_Reset states */
+	case SOFT_RESET:
+		port->message_id = 0;
+		tcpm_pd_send_control(port, PD_CTRL_ACCEPT);
+		if (port->con.pwr_role == TYPEC_SOURCE)
+			tcpm_set_state(port, SRC_SEND_CAPABILITIES, 0);
+		else
+			tcpm_set_state(port, SNK_WAIT_CAPABILITIES, 0);
+		break;
+	case SOFT_RESET_SEND:
+		port->message_id = 0;
+		if (tcpm_pd_send_control(port, PD_CTRL_SOFT_RESET))
+			tcpm_set_state_cond(port, hard_reset_state(port), 0);
+		else
+			tcpm_set_state_cond(port, hard_reset_state(port),
+					    PD_T_SENDER_RESPONSE);
+		break;
+
+	/* DR_Swap states */
+	case DR_SWAP_SEND:
+		tcpm_pd_send_control(port, PD_CTRL_DR_SWAP);
+		tcpm_set_state_cond(port, DR_SWAP_SEND_TIMEOUT,
+				    PD_T_SENDER_RESPONSE);
+		break;
+	case DR_SWAP_ACCEPT:
+		tcpm_pd_send_control(port, PD_CTRL_ACCEPT);
+		tcpm_set_state_cond(port, DR_SWAP_CHANGE_DR, 0);
+		break;
+	case DR_SWAP_SEND_TIMEOUT:
+		tcpm_swap_complete(port, -ETIMEDOUT);
+		tcpm_set_state(port, ready_state(port), 0);
+		break;
+	case DR_SWAP_CHANGE_DR:
+		if (port->con.data_role == TYPEC_HOST) {
+			tcpm_unregister_altmodes(port);
+			tcpm_set_roles(port, port->con.pwr_role,
+				       TYPEC_DEVICE);
+		} else {
+			tcpm_set_roles(port, port->con.pwr_role,
+				       TYPEC_HOST);
+			port->send_discover = true;
+		}
+		tcpm_swap_complete(port, 0);
+		tcpm_set_state(port, ready_state(port), 0);
+		break;
+
+	/* PR_Swap states */
+	case PR_SWAP_ACCEPT:
+		tcpm_pd_send_control(port, PD_CTRL_ACCEPT);
+		tcpm_set_state(port, PR_SWAP_START, 0);
+		break;
+	case PR_SWAP_SEND:
+		tcpm_pd_send_control(port, PD_CTRL_PR_SWAP);
+		tcpm_set_state_cond(port, PR_SWAP_SEND_TIMEOUT,
+				    PD_T_SENDER_RESPONSE);
+		break;
+	case PR_SWAP_SEND_TIMEOUT:
+		tcpm_swap_complete(port, -ETIMEDOUT);
+		tcpm_set_state(port, ready_state(port), 0);
+		break;
+	case PR_SWAP_START:
+		if (port->con.pwr_role == TYPEC_SOURCE)
+			tcpm_set_state(port, PR_SWAP_SRC_SNK_TRANSITION_OFF,
+				       PD_T_SRC_TRANSITION);
+		else
+			tcpm_set_state(port, PR_SWAP_SNK_SRC_SINK_OFF, 0);
+		break;
+	case PR_SWAP_SRC_SNK_TRANSITION_OFF:
+		tcpm_set_vbus(port, false);
+		port->explicit_contract = false;
+		tcpm_set_state(port, PR_SWAP_SRC_SNK_SOURCE_OFF,
+			       PD_T_PS_SOURCE_OFF);
+		break;
+	case PR_SWAP_SRC_SNK_SOURCE_OFF:
+		tcpm_set_cc(port, TYPEC_CC_RD);
+		if (tcpm_pd_send_control(port, PD_CTRL_PS_RDY)) {
+			tcpm_set_state(port, ERROR_RECOVERY, 0);
+			break;
+		}
+		tcpm_set_state_cond(port, SNK_UNATTACHED, PD_T_PS_SOURCE_ON);
+		break;
+	case PR_SWAP_SRC_SNK_SINK_ON:
+		tcpm_set_pwr_role(port, TYPEC_SINK);
+		tcpm_swap_complete(port, 0);
+		tcpm_set_state(port, SNK_STARTUP, 0);
+		break;
+	case PR_SWAP_SNK_SRC_SINK_OFF:
+		tcpm_set_charge(port, false);
+		tcpm_set_state(port, hard_reset_state(port),
+			       PD_T_PS_SOURCE_OFF);
+		break;
+	case PR_SWAP_SNK_SRC_SOURCE_ON:
+		tcpm_set_cc(port, TYPEC_CC_RP_DEF);
+		tcpm_set_vbus(port, true);
+		tcpm_pd_send_control(port, PD_CTRL_PS_RDY);
+		tcpm_set_pwr_role(port, TYPEC_SOURCE);
+		tcpm_swap_complete(port, 0);
+		tcpm_set_state(port, SRC_STARTUP, 0);
+		break;
+
+	case VCONN_SWAP_ACCEPT:
+		tcpm_pd_send_control(port, PD_CTRL_ACCEPT);
+		tcpm_set_state(port, VCONN_SWAP_START, 0);
+		break;
+	case VCONN_SWAP_SEND:
+		tcpm_pd_send_control(port, PD_CTRL_VCONN_SWAP);
+		tcpm_set_state(port, VCONN_SWAP_SEND_TIMEOUT,
+			       PD_T_SENDER_RESPONSE);
+		break;
+	case VCONN_SWAP_SEND_TIMEOUT:
+		tcpm_swap_complete(port, -ETIMEDOUT);
+		tcpm_set_state(port, ready_state(port), 0);
+		break;
+	case VCONN_SWAP_START:
+		if (port->con.vconn_role == TYPEC_SOURCE)
+			tcpm_set_state(port, VCONN_SWAP_WAIT_FOR_VCONN, 0);
+		else
+			tcpm_set_state(port, VCONN_SWAP_TURN_ON_VCONN, 0);
+		break;
+	case VCONN_SWAP_WAIT_FOR_VCONN:
+		tcpm_set_state(port, hard_reset_state(port),
+			       PD_T_VCONN_SOURCE_ON);
+		break;
+	case VCONN_SWAP_TURN_ON_VCONN:
+		tcpm_set_vconn(port, true);
+		tcpm_pd_send_control(port, PD_CTRL_PS_RDY);
+		tcpm_swap_complete(port, 0);
+		tcpm_set_state(port, ready_state(port), 0);
+		break;
+	case VCONN_SWAP_TURN_OFF_VCONN:
+		tcpm_set_vconn(port, false);
+		tcpm_swap_complete(port, 0);
+		tcpm_set_state(port, ready_state(port), 0);
+		break;
+
+	case DR_SWAP_CANCEL:
+	case PR_SWAP_CANCEL:
+	case VCONN_SWAP_CANCEL:
+		tcpm_swap_complete(port, port->swap_status);
+		if (port->con.pwr_role == TYPEC_SOURCE)
+			tcpm_set_state(port, SRC_READY, 0);
+		else
+			tcpm_set_state(port, SNK_READY, 0);
+		break;
+
+	case BIST_RX:
+		switch (BDO_MODE_MASK(port->bist_request)) {
+		case BDO_MODE_CARRIER2:
+			tcpm_pd_transmit(port, TCPC_TX_BIST_MODE_2, NULL);
+			break;
+		default:
+			break;
+		}
+		/* Always switch to unattached state */
+		tcpm_set_state(port, unattached_state(port), 0);
+		break;
+	case ERROR_RECOVERY:
+		tcpm_swap_complete(port, -EPROTO);
+		tcpm_reset_port(port);
+
+		tcpm_set_cc(port, TYPEC_CC_OPEN);
+		tcpm_set_state(port, ERROR_RECOVERY_WAIT_OFF,
+			       PD_T_ERROR_RECOVERY);
+		break;
+	case ERROR_RECOVERY_WAIT_OFF:
+		tcpm_set_state(port,
+			       tcpm_default_state(port),
+			       port->vbus_present ? PD_T_PS_SOURCE_OFF : 0);
+		break;
+	default:
+		WARN(1, "Unexpected port state %d\n", port->state);
+		break;
+	}
+}
+
+static void tcpm_state_machine_work(struct work_struct *work)
+{
+	struct tcpm_port *port = container_of(work, struct tcpm_port,
+					      state_machine.work);
+	enum tcpm_state prev_state;
+
+	mutex_lock(&port->lock);
+	port->state_machine_running = true;
+
+	if (port->queued_message && tcpm_send_queued_message(port))
+		goto done;
+
+	/* If we were queued due to a delayed state change, update it now */
+	if (port->delayed_state) {
+		tcpm_log(port, "state change %s -> %s [delayed %ld ms]",
+			 tcpm_states[port->state],
+			 tcpm_states[port->delayed_state], port->delay_ms);
+		port->prev_state = port->state;
+		port->state = port->delayed_state;
+		port->delayed_state = INVALID_STATE;
+	}
+
+	/*
+	 * Continue running as long as we have (non-delayed) state changes
+	 * to make.
+	 */
+	do {
+		prev_state = port->state;
+		run_state_machine(port);
+		if (port->queued_message)
+			tcpm_send_queued_message(port);
+	} while (port->state != prev_state && !port->delayed_state);
+
+done:
+	port->state_machine_running = false;
+	mutex_unlock(&port->lock);
+}
+
+static void _tcpm_cc_change(struct tcpm_port *port, enum typec_cc_status cc1,
+			    enum typec_cc_status cc2)
+{
+	enum typec_cc_status old_cc1, old_cc2;
+	enum tcpm_state new_state;
+
+	old_cc1 = port->cc1;
+	old_cc2 = port->cc2;
+	port->cc1 = cc1;
+	port->cc2 = cc2;
+
+	tcpm_log_force(port,
+		       "CC1: %u -> %u, CC2: %u -> %u [state %s, polarity %d, %s]",
+		       old_cc1, cc1, old_cc2, cc2, tcpm_states[port->state],
+		       port->polarity,
+		       tcpm_port_is_disconnected(port) ? "disconnected"
+						       : "connected");
+
+	/*
+	 * TODO:
+	 *  - DRP toggling
+	 *  - Try.SRC and TryWait.SNK states
+	 */
+
+	switch (port->state) {
+	case SRC_UNATTACHED:
+	case ACC_UNATTACHED:
+		if ((cc1 == TYPEC_CC_RD || cc2 == TYPEC_CC_RD) ||
+		    (cc1 == TYPEC_CC_RA && cc2 == TYPEC_CC_RA))
+			tcpm_set_state(port, SRC_ATTACH_WAIT, 0);
+		break;
+	case SRC_ATTACH_WAIT:
+		if (tcpm_port_is_disconnected(port) ||
+		    (cc1 == TYPEC_CC_OPEN && cc2 == TYPEC_CC_RA) ||
+		    (cc1 == TYPEC_CC_RA && cc2 == TYPEC_CC_OPEN))
+			tcpm_set_state(port, SRC_UNATTACHED, 0);
+		else if (cc1 != old_cc1 || cc2 != old_cc2)
+			tcpm_set_state(port, SRC_ATTACH_WAIT, 0);
+		break;
+	case SRC_ATTACHED:
+		if (tcpm_port_is_disconnected(port))
+			tcpm_set_state(port, SRC_UNATTACHED, 0);
+		break;
+
+	case SNK_UNATTACHED:
+		if (tcpm_port_is_sink(port))
+			tcpm_set_state(port, SNK_ATTACH_WAIT, 0);
+		break;
+	case SNK_ATTACH_WAIT:
+		if ((port->cc1 == TYPEC_CC_OPEN &&
+		     port->cc2 != TYPEC_CC_OPEN) ||
+		    (port->cc1 != TYPEC_CC_OPEN &&
+		     port->cc2 == TYPEC_CC_OPEN))
+			new_state = SNK_DEBOUNCED;
+		else if (tcpm_port_is_disconnected(port))
+			new_state = SNK_UNATTACHED;
+		else
+			break;
+		if (new_state != port->delayed_state)
+			tcpm_set_state(port, SNK_ATTACH_WAIT, 0);
+		break;
+	case SNK_DEBOUNCED:
+		if (tcpm_port_is_disconnected(port))
+			new_state = SNK_UNATTACHED;
+		else if (port->vbus_present)
+			new_state = tcpm_try_src(port) ? SRC_TRY : SNK_ATTACHED;
+		else
+			new_state = SNK_UNATTACHED;
+		if (new_state != port->delayed_state)
+			tcpm_set_state(port, SNK_DEBOUNCED, 0);
+		break;
+	case SNK_READY:
+		if (tcpm_port_is_disconnected(port))
+			tcpm_set_state(port, unattached_state(port), 0);
+		else if (!port->pd_capable &&
+			 (cc1 != old_cc1 || cc2 != old_cc2))
+			tcpm_set_current_limit(port,
+					       tcpm_get_current_limit(port),
+					       5000);
+		break;
+
+	case AUDIO_ACC_ATTACHED:
+		if (cc1 == TYPEC_CC_OPEN || cc2 == TYPEC_CC_OPEN)
+			tcpm_set_state(port, AUDIO_ACC_DEBOUNCE, 0);
+		break;
+	case AUDIO_ACC_DEBOUNCE:
+		if (cc1 == TYPEC_CC_RA && cc2 == TYPEC_CC_RA)
+			tcpm_set_state(port, AUDIO_ACC_ATTACHED, 0);
+		break;
+
+	case DEBUG_ACC_ATTACHED:
+		if (cc1 == TYPEC_CC_OPEN || cc2 == TYPEC_CC_OPEN)
+			tcpm_set_state(port, ACC_UNATTACHED, 0);
+		break;
+
+	case SNK_TRY:
+		/* Do nothing, waiting for timeout */
+		break;
+
+	case SNK_DISCOVERY:
+		/* CC line is unstable, wait for debounce */
+		if (tcpm_port_is_disconnected(port))
+			tcpm_set_state(port, SNK_DISCOVERY_DEBOUNCE, 0);
+		break;
+	case SNK_DISCOVERY_DEBOUNCE:
+		break;
+
+	case SRC_TRYWAIT:
+		/* Hand over to state machine if needed */
+		if (!port->vbus_present && tcpm_port_is_source(port))
+			new_state = SRC_ATTACHED;
+		else
+			new_state = SRC_TRYWAIT_UNATTACHED;
+
+		if (new_state != port->delayed_state)
+			tcpm_set_state(port, SRC_TRYWAIT, 0);
+		break;
+	case SNK_TRY_WAIT:
+		if (port->vbus_present && tcpm_port_is_sink(port)) {
+			tcpm_set_state(port, SNK_ATTACHED, 0);
+			break;
+		}
+		if (!tcpm_port_is_sink(port))
+			new_state = SRC_TRYWAIT;
+		else
+			new_state = SRC_TRYWAIT_UNATTACHED;
+
+		if (new_state != port->delayed_state)
+			tcpm_set_state(port, SNK_TRY_WAIT, 0);
+		break;
+
+	case SRC_TRY:
+		tcpm_set_state(port, SRC_TRY_DEBOUNCE, 0);
+		break;
+	case SRC_TRY_DEBOUNCE:
+		tcpm_set_state(port, SRC_TRY, 0);
+		break;
+	case SNK_TRYWAIT_DEBOUNCE:
+		if (port->vbus_present) {
+			tcpm_set_state(port, SNK_ATTACHED, 0);
+			break;
+		}
+		if (tcpm_port_is_source(port)) {
+			tcpm_set_state(port, SRC_ATTACHED, 0);
+			break;
+		}
+		if (tcpm_port_is_disconnected(port) &&
+		    port->delayed_state != SNK_UNATTACHED)
+			tcpm_set_state(port, SNK_TRYWAIT_DEBOUNCE, 0);
+		break;
+
+	case PR_SWAP_SNK_SRC_SINK_OFF:
+	case PR_SWAP_SRC_SNK_TRANSITION_OFF:
+	case PR_SWAP_SRC_SNK_SOURCE_OFF:
+		/*
+		 * CC state change is expected here; we just turned off power.
+		 * Ignore it.
+		 */
+		break;
+
+	default:
+		if (tcpm_port_is_disconnected(port))
+			tcpm_set_state(port, unattached_state(port), 0);
+		break;
+	}
+}
+
+static void _tcpm_pd_vbus_on(struct tcpm_port *port)
+{
+	enum tcpm_state new_state;
+
+	tcpm_log_force(port, "VBUS on");
+	port->vbus_present = true;
+	switch (port->state) {
+	case SNK_TRANSITION_SINK_VBUS:
+		tcpm_set_state(port, SNK_READY, 0);
+		break;
+	case SNK_DISCOVERY:
+		tcpm_set_state(port, SNK_DISCOVERY, 0);
+		break;
+
+	case SNK_DEBOUNCED:
+		tcpm_set_state(port, tcpm_try_src(port) ? SRC_TRY
+							: SNK_ATTACHED,
+				       0);
+		break;
+	case SNK_HARD_RESET_WAIT_VBUS:
+		tcpm_set_state(port, SNK_HARD_RESET_SINK_ON, 0);
+		break;
+	case SRC_ATTACHED:
+		tcpm_set_state(port, SRC_STARTUP, 0);
+		break;
+	case SRC_HARD_RESET_VBUS_ON:
+		tcpm_set_state(port, SRC_STARTUP, 0);
+		break;
+
+	case SNK_TRY:
+		/* Do nothing, waiting for timeout */
+		break;
+	case SRC_TRYWAIT:
+		/* Hand over to state machine if needed */
+		if (port->delayed_state != SRC_TRYWAIT_UNATTACHED)
+			tcpm_set_state(port, SRC_TRYWAIT, 0);
+		break;
+	case SNK_TRY_WAIT:
+		if (tcpm_port_is_sink(port)) {
+			tcpm_set_state(port, SNK_ATTACHED, 0);
+			break;
+		}
+		if (!tcpm_port_is_sink(port))
+			new_state = SRC_TRYWAIT;
+		else
+			new_state = SRC_TRYWAIT_UNATTACHED;
+
+		if (new_state != port->delayed_state)
+			tcpm_set_state(port, SNK_TRY_WAIT, 0);
+		break;
+	case SNK_TRYWAIT:
+		tcpm_set_state(port, SNK_TRYWAIT_VBUS, 0);
+		break;
+
+	default:
+		break;
+	}
+}
+
+static void _tcpm_pd_vbus_off(struct tcpm_port *port)
+{
+	enum tcpm_state new_state;
+
+	tcpm_log_force(port, "VBUS off");
+	port->vbus_present = false;
+	port->vbus_never_low = false;
+	switch (port->state) {
+	case SNK_HARD_RESET_SINK_OFF:
+		tcpm_set_state(port, SNK_HARD_RESET_WAIT_VBUS, 0);
+		break;
+	case SRC_HARD_RESET_VBUS_OFF:
+		tcpm_set_state(port, SRC_HARD_RESET_VBUS_ON, 0);
+		break;
+	case HARD_RESET_SEND:
+		break;
+
+	case SNK_TRY:
+		/* Do nothing, waiting for timeout */
+		break;
+	case SRC_TRYWAIT:
+		/* Hand over to state machine if needed */
+		if (tcpm_port_is_source(port))
+			new_state = SRC_ATTACHED;
+		else
+			new_state = SRC_TRYWAIT_UNATTACHED;
+		if (new_state != port->delayed_state)
+			tcpm_set_state(port, SRC_TRYWAIT, 0);
+		break;
+	case SNK_TRY_WAIT:
+		if (!tcpm_port_is_sink(port))
+			new_state = SRC_TRYWAIT;
+		else
+			new_state = SRC_TRYWAIT_UNATTACHED;
+
+		if (new_state != port->delayed_state)
+			tcpm_set_state(port, SNK_TRY_WAIT, 0);
+		break;
+	case SNK_TRYWAIT_VBUS:
+		tcpm_set_state(port, SNK_TRYWAIT, 0);
+		break;
+
+	case SNK_ATTACH_WAIT:
+		tcpm_set_state(port, SNK_UNATTACHED, 0);
+		break;
+
+	case SNK_NEGOTIATE_CAPABILITIES:
+		break;
+
+	case PR_SWAP_SRC_SNK_TRANSITION_OFF:
+		tcpm_set_state(port, PR_SWAP_SRC_SNK_SOURCE_OFF, 0);
+		break;
+
+	case PR_SWAP_SNK_SRC_SINK_OFF:
+		/* Do nothing, expected */
+		break;
+
+	case ERROR_RECOVERY_WAIT_OFF:
+		tcpm_set_state(port,
+			       port->con.pwr_role == TYPEC_SOURCE ?
+					SRC_UNATTACHED : SNK_UNATTACHED,
+			       0);
+		break;
+
+	default:
+		if (port->con.pwr_role == TYPEC_SINK &&
+		    port->attached)
+			tcpm_set_state(port, SNK_UNATTACHED, 0);
+		break;
+	}
+}
+
+static void _tcpm_pd_hard_reset(struct tcpm_port *port)
+{
+	tcpm_log_force(port, "Received hard reset");
+	/*
+	 * If we keep receiving hard reset requests, executing the hard reset
+	 * must have failed. Revert to error recovery if that happens.
+	 */
+	tcpm_set_state(port,
+		       port->hard_reset_count < PD_N_HARD_RESET_COUNT ?
+				HARD_RESET_START : ERROR_RECOVERY,
+		       0);
+}
+
+static void tcpm_pd_event_handler(struct work_struct *work)
+{
+	struct tcpm_port *port = container_of(work, struct tcpm_port,
+					      event_work);
+	u32 events;
+
+	mutex_lock(&port->lock);
+
+	spin_lock(&port->pd_event_lock);
+	while (port->pd_events) {
+		events = port->pd_events;
+		port->pd_events = 0;
+		spin_unlock(&port->pd_event_lock);
+		if (events & TCPM_RESET_EVENT)
+			_tcpm_pd_hard_reset(port);
+		if (events & TCPM_VBUS_EVENT) {
+			bool vbus;
+
+			vbus = port->tcpc->get_vbus(port->tcpc);
+			if (vbus)
+				_tcpm_pd_vbus_on(port);
+			else
+				_tcpm_pd_vbus_off(port);
+		}
+		if (events & TCPM_CC_EVENT) {
+			enum typec_cc_status cc1, cc2;
+
+			if (port->tcpc->get_cc(port->tcpc, &cc1, &cc2) == 0)
+				_tcpm_cc_change(port, cc1, cc2);
+		}
+		spin_lock(&port->pd_event_lock);
+	}
+	spin_unlock(&port->pd_event_lock);
+	mutex_unlock(&port->lock);
+}
+
+void tcpm_cc_change(struct tcpm_port *port)
+{
+	spin_lock(&port->pd_event_lock);
+	port->pd_events |= TCPM_CC_EVENT;
+	spin_unlock(&port->pd_event_lock);
+	queue_work(port->wq, &port->event_work);
+}
+EXPORT_SYMBOL_GPL(tcpm_cc_change);
+
+void tcpm_vbus_change(struct tcpm_port *port)
+{
+	spin_lock(&port->pd_event_lock);
+	port->pd_events |= TCPM_VBUS_EVENT;
+	spin_unlock(&port->pd_event_lock);
+	queue_work(port->wq, &port->event_work);
+}
+EXPORT_SYMBOL_GPL(tcpm_vbus_change);
+
+void tcpm_pd_hard_reset(struct tcpm_port *port)
+{
+	spin_lock(&port->pd_event_lock);
+	port->pd_events = TCPM_RESET_EVENT;
+	spin_unlock(&port->pd_event_lock);
+	queue_work(port->wq, &port->event_work);
+}
+EXPORT_SYMBOL_GPL(tcpm_pd_hard_reset);
+
+static int tcpm_dr_set(const struct typec_capability *cap,
+		       enum typec_data_role data)
+{
+	struct tcpm_port *port = typec_cap_to_tcpm(cap);
+	int ret;
+
+	mutex_lock(&port->swap_lock);
+	mutex_lock(&port->lock);
+
+	if (port->typec_caps.type != TYPEC_PORT_DRP) {
+		ret = -EINVAL;
+		goto port_unlock;
+	}
+	if (port->state != SRC_READY && port->state != SNK_READY) {
+		ret = -EAGAIN;
+		goto port_unlock;
+	}
+
+	if (port->con.data_role == data) {
+		ret = 0;
+		goto port_unlock;
+	}
+
+	/*
+	 * XXX
+	 * 6.3.9: If an alternate mode is active, a request to swap
+	 * alternate modes shall trigger a port reset.
+	 * Reject data role swap request in this case.
+	 */
+
+	port->swap_status = 0;
+	port->swap_pending = true;
+	reinit_completion(&port->swap_complete);
+	tcpm_set_state(port, DR_SWAP_SEND, 0);
+	mutex_unlock(&port->lock);
+
+	wait_for_completion(&port->swap_complete);
+
+	ret = port->swap_status;
+	goto swap_unlock;
+
+port_unlock:
+	mutex_unlock(&port->lock);
+swap_unlock:
+	mutex_unlock(&port->swap_lock);
+	return ret;
+}
+
+static int tcpm_pr_set(const struct typec_capability *cap,
+		       enum typec_role role)
+{
+	struct tcpm_port *port = typec_cap_to_tcpm(cap);
+	int ret;
+
+	mutex_lock(&port->swap_lock);
+	mutex_lock(&port->lock);
+
+	if (port->typec_caps.type != TYPEC_PORT_DRP) {
+		ret = -EINVAL;
+		goto port_unlock;
+	}
+	if (port->state != SRC_READY && port->state != SNK_READY) {
+		ret = -EAGAIN;
+		goto port_unlock;
+	}
+
+	if (role == port->con.pwr_role) {
+		ret = 0;
+		goto port_unlock;
+	}
+
+	port->swap_status = 0;
+	port->swap_pending = true;
+	reinit_completion(&port->swap_complete);
+	tcpm_set_state(port, PR_SWAP_SEND, 0);
+	mutex_unlock(&port->lock);
+
+	wait_for_completion(&port->swap_complete);
+
+	ret = port->swap_status;
+	goto swap_unlock;
+
+port_unlock:
+	mutex_unlock(&port->lock);
+swap_unlock:
+	mutex_unlock(&port->swap_lock);
+	return ret;
+}
+
+static int tcpm_vconn_set(const struct typec_capability *cap,
+			  enum typec_role role)
+{
+	struct tcpm_port *port = typec_cap_to_tcpm(cap);
+	int ret;
+
+	mutex_lock(&port->swap_lock);
+	mutex_lock(&port->lock);
+
+	if (port->state != SRC_READY && port->state != SNK_READY) {
+		ret = -EAGAIN;
+		goto port_unlock;
+	}
+
+	if (role == port->con.vconn_role) {
+		ret = 0;
+		goto port_unlock;
+	}
+
+	port->swap_status = 0;
+	port->swap_pending = true;
+	reinit_completion(&port->swap_complete);
+	tcpm_set_state(port, VCONN_SWAP_SEND, 0);
+	mutex_unlock(&port->lock);
+
+	wait_for_completion(&port->swap_complete);
+
+	ret = port->swap_status;
+	goto swap_unlock;
+
+port_unlock:
+	mutex_unlock(&port->lock);
+swap_unlock:
+	mutex_unlock(&port->swap_lock);
+	return ret;
+}
+
+static void tcpm_init(struct tcpm_port *port)
+{
+	enum typec_cc_status cc1, cc2;
+
+	port->tcpc->init(port->tcpc);
+
+	tcpm_reset_port(port);
+
+	/*
+	 * XXX
+	 * Should possibly wait for VBUS to settle if it was enabled locally
+	 * since tcpm_reset_port() will disable VBUS.
+	 */
+	port->vbus_present = port->tcpc->get_vbus(port->tcpc);
+	if (port->vbus_present)
+		port->vbus_never_low = true;
+
+	tcpm_set_state(port, tcpm_default_state(port), 0);
+
+	if (port->tcpc->get_cc(port->tcpc, &cc1, &cc2) == 0)
+		_tcpm_cc_change(port, cc1, cc2);
+
+	/*
+	 * Some adapters need a clean slate at startup, and won't recover
+	 * otherwise. So do not try to be fancy and force a clean disconnect.
+	 */
+	tcpm_set_state(port, ERROR_RECOVERY, 0);
+}
+
+void tcpm_tcpc_reset(struct tcpm_port *port)
+{
+	mutex_lock(&port->lock);
+	/* XXX: Maintain PD connection if possible? */
+	tcpm_init(port);
+	mutex_unlock(&port->lock);
+}
+EXPORT_SYMBOL_GPL(tcpm_tcpc_reset);
+
+struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
+{
+	struct tcpm_port *port;
+	int err;
+
+	port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL);
+	if (!port)
+		return ERR_PTR(-ENOMEM);
+
+	port->dev = dev;
+	port->tcpc = tcpc;
+
+	mutex_init(&port->lock);
+	mutex_init(&port->swap_lock);
+
+	port->wq = create_singlethread_workqueue(dev_name(dev));
+	if (!port->wq)
+		return ERR_PTR(-ENOMEM);
+	INIT_DELAYED_WORK(&port->state_machine, tcpm_state_machine_work);
+	INIT_DELAYED_WORK(&port->vdm_state_machine, vdm_state_machine_work);
+	INIT_WORK(&port->event_work, tcpm_pd_event_handler);
+
+	spin_lock_init(&port->pd_event_lock);
+
+	init_completion(&port->tx_complete);
+	init_completion(&port->swap_complete);
+
+	port->typec_caps.type = port->tcpc->config->type;
+	port->typec_caps.usb_pd = 1;
+	port->typec_caps.dr_set = tcpm_dr_set;
+	port->typec_caps.pr_set = tcpm_pr_set;
+	port->typec_caps.vconn_set = tcpm_vconn_set;
+	port->typec_caps.alt_modes = tcpc->config->alt_modes;
+
+	port->con.partner = &port->partner;
+
+	/*
+	 * TODO:
+	 *  - alt_modes, set_alt_mode
+	 *  - {debug,audio}_accessory
+	 */
+
+	port->typec_port = typec_register_port(port->dev, &port->typec_caps);
+	if (IS_ERR(port->typec_port)) {
+		err = PTR_ERR(port->typec_port);
+		goto out_destroy_wq;
+	}
+
+	tcpm_debugfs_init(port);
+	mutex_lock(&port->lock);
+	tcpm_init(port);
+	mutex_unlock(&port->lock);
+
+	tcpm_log(port, "%s: registered", dev_name(dev));
+	return port;
+
+out_destroy_wq:
+	destroy_workqueue(port->wq);
+	return ERR_PTR(err);
+}
+EXPORT_SYMBOL_GPL(tcpm_register_port);
+
+void tcpm_unregister_port(struct tcpm_port *port)
+{
+	typec_unregister_port(port->typec_port);
+	tcpm_debugfs_exit(port);
+	destroy_workqueue(port->wq);
+}
+EXPORT_SYMBOL_GPL(tcpm_unregister_port);
diff --git a/drivers/usb/typec/tcpm.h b/drivers/usb/typec/tcpm.h
new file mode 100644
index 000000000000..de1fc6add9f6
--- /dev/null
+++ b/drivers/usb/typec/tcpm.h
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2015-2016 Google, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __LINUX_USB_TCPM_H
+#define __LINUX_USB_TCPM_H
+
+#include <linux/bitops.h>
+#include <linux/usb/typec.h>
+#include <linux/usb/pd.h>
+
+enum typec_cc_status {
+	TYPEC_CC_OPEN,
+	TYPEC_CC_RA,
+	TYPEC_CC_RD,
+	TYPEC_CC_RP_DEF,
+	TYPEC_CC_RP_1_5,
+	TYPEC_CC_RP_3_0,
+};
+
+enum typec_cc_polarity {
+	TYPEC_POLARITY_CC1,
+	TYPEC_POLARITY_CC2,
+};
+
+/* Time to wait for TCPC to complete transmit */
+#define PD_T_TCPC_TX_TIMEOUT  100
+
+enum tcpm_transmit_status {
+	TCPC_TX_SUCCESS = 0,
+	TCPC_TX_DISCARDED = 1,
+	TCPC_TX_FAILED = 2,
+};
+
+enum tcpm_transmit_type {
+	TCPC_TX_SOP = 0,
+	TCPC_TX_SOP_PRIME = 1,
+	TCPC_TX_SOP_PRIME_PRIME = 2,
+	TCPC_TX_SOP_DEBUG_PRIME = 3,
+	TCPC_TX_SOP_DEBUG_PRIME_PRIME = 4,
+	TCPC_TX_HARD_RESET = 5,
+	TCPC_TX_CABLE_RESET = 6,
+	TCPC_TX_BIST_MODE_2 = 7
+};
+
+struct tcpc_config {
+	const u32 *src_pdo;
+	unsigned int nr_src_pdo;
+
+	const u32 *snk_pdo;
+	unsigned int nr_snk_pdo;
+
+	unsigned int max_snk_mv;
+	unsigned int max_snk_ma;
+	unsigned int max_snk_mw;
+	unsigned int operating_snk_mw;
+
+	enum typec_port_type type;
+	enum typec_role default_role;
+
+	struct typec_altmode *alt_modes;
+};
+
+enum tcpc_usb_switch {
+	TCPC_USB_SWITCH_CONNECT,
+	TCPC_USB_SWITCH_DISCONNECT,
+	TCPC_USB_SWITCH_RESTORE,	/* TODO FIXME */
+};
+
+/* Mux state attributes */
+#define TCPC_MUX_USB_ENABLED		BIT(0)	/* USB enabled */
+#define TCPC_MUX_DP_ENABLED		BIT(1)	/* DP enabled */
+#define TCPC_MUX_POLARITY_INVERTED	BIT(2)	/* Polarity inverted */
+
+/* Mux modes, decoded to attributes */
+enum tcpc_mux_mode {
+	TYPEC_MUX_NONE	= 0,				/* Open switch */
+	TYPEC_MUX_USB	= TCPC_MUX_USB_ENABLED,		/* USB only */
+	TYPEC_MUX_DP	= TCPC_MUX_DP_ENABLED,		/* DP only */
+	TYPEC_MUX_DOCK	= TCPC_MUX_USB_ENABLED |	/* Both USB and DP */
+			  TCPC_MUX_DP_ENABLED,
+};
+
+struct tcpc_mux_dev {
+	int (*set)(struct tcpc_mux_dev *dev, enum tcpc_mux_mode mux_mode,
+		   enum tcpc_usb_switch usb_config,
+		   enum typec_cc_polarity polarity);
+	bool dfp_only;
+	void *priv_data;
+};
+
+struct tcpc_dev {
+	const struct tcpc_config *config;
+
+	int (*init)(struct tcpc_dev *dev);
+	int (*get_vbus)(struct tcpc_dev *dev);
+	int (*set_cc)(struct tcpc_dev *dev, enum typec_cc_status cc);
+	int (*get_cc)(struct tcpc_dev *dev, enum typec_cc_status *cc1,
+		      enum typec_cc_status *cc2);
+	int (*set_polarity)(struct tcpc_dev *dev,
+			    enum typec_cc_polarity polarity);
+	int (*set_vconn)(struct tcpc_dev *dev, bool on);
+	int (*set_vbus)(struct tcpc_dev *dev, bool on, bool charge);
+	int (*set_current_limit)(struct tcpc_dev *dev, u32 max_ma, u32 mv);
+	int (*set_pd_rx)(struct tcpc_dev *dev, bool on);
+	int (*set_pd_header)(struct tcpc_dev *dev, enum typec_role role,
+			     enum typec_data_role data);
+	int (*pd_transmit)(struct tcpc_dev *dev, enum tcpm_transmit_type type,
+			   const struct pd_message *msg);
+	struct tcpc_mux_dev *mux;
+};
+
+struct tcpm_port;
+
+struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc);
+void tcpm_unregister_port(struct tcpm_port *port);
+
+void tcpm_vbus_change(struct tcpm_port *port);
+void tcpm_cc_change(struct tcpm_port *port);
+void tcpm_pd_receive(struct tcpm_port *port,
+		     const struct pd_message *msg);
+void tcpm_pd_transmit_complete(struct tcpm_port *port,
+			       enum tcpm_transmit_status status);
+void tcpm_pd_hard_reset(struct tcpm_port *port);
+void tcpm_tcpc_reset(struct tcpm_port *port);
+
+#endif /* __LINUX_USB_TCPM_H */
diff --git a/include/linux/usb/pd.h b/include/linux/usb/pd.h
new file mode 100644
index 000000000000..6b1679af7a25
--- /dev/null
+++ b/include/linux/usb/pd.h
@@ -0,0 +1,282 @@
+/*
+ * Copyright 2015-2016 Google, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __LINUX_USB_PD_H
+#define __LINUX_USB_PD_H
+
+#include <linux/types.h>
+#include <linux/usb/typec.h>
+
+/* USB PD Messages */
+enum pd_ctrl_msg_type {
+	/* 0 Reserved */
+	PD_CTRL_GOOD_CRC = 1,
+	PD_CTRL_GOTO_MIN = 2,
+	PD_CTRL_ACCEPT = 3,
+	PD_CTRL_REJECT = 4,
+	PD_CTRL_PING = 5,
+	PD_CTRL_PS_RDY = 6,
+	PD_CTRL_GET_SOURCE_CAP = 7,
+	PD_CTRL_GET_SINK_CAP = 8,
+	PD_CTRL_DR_SWAP = 9,
+	PD_CTRL_PR_SWAP = 10,
+	PD_CTRL_VCONN_SWAP = 11,
+	PD_CTRL_WAIT = 12,
+	PD_CTRL_SOFT_RESET = 13,
+	/* 14-15 Reserved */
+};
+
+enum pd_data_msg_type {
+	/* 0 Reserved */
+	PD_DATA_SOURCE_CAP = 1,
+	PD_DATA_REQUEST = 2,
+	PD_DATA_BIST = 3,
+	PD_DATA_SINK_CAP = 4,
+	/* 5-14 Reserved */
+	PD_DATA_VENDOR_DEF = 15,
+};
+
+#define PD_REV10	0x0
+#define PD_REV20	0x1
+
+#define PD_HEADER_CNT_SHIFT	12
+#define PD_HEADER_CNT_MASK	0x7
+#define PD_HEADER_ID_SHIFT	9
+#define PD_HEADER_ID_MASK	0x7
+#define PD_HEADER_PWR_ROLE	BIT(8)
+#define PD_HEADER_REV_SHIFT	6
+#define PD_HEADER_REV_MASK	0x3
+#define PD_HEADER_DATA_ROLE	BIT(5)
+#define PD_HEADER_TYPE_SHIFT	0
+#define PD_HEADER_TYPE_MASK	0xf
+
+#define PD_HEADER(type, pwr, data, id, cnt)				\
+	((((type) & PD_HEADER_TYPE_MASK) << PD_HEADER_TYPE_SHIFT) |	\
+	 ((pwr) == TYPEC_SOURCE ? PD_HEADER_PWR_ROLE : 0) |		\
+	 ((data) == TYPEC_HOST ? PD_HEADER_DATA_ROLE : 0) |		\
+	 (PD_REV20 << PD_HEADER_REV_SHIFT) |				\
+	 (((id) & PD_HEADER_ID_MASK) << PD_HEADER_ID_SHIFT) |		\
+	 (((cnt) & PD_HEADER_CNT_MASK) << PD_HEADER_CNT_SHIFT))
+
+#define PD_HEADER_LE(type, pwr, data, id, cnt) \
+	cpu_to_le16(PD_HEADER((type), (pwr), (data), (id), (cnt)))
+
+static inline unsigned int pd_header_cnt(u16 header)
+{
+	return (header >> PD_HEADER_CNT_SHIFT) & PD_HEADER_CNT_MASK;
+}
+
+static inline unsigned int pd_header_cnt_le(__le16 header)
+{
+	return pd_header_cnt(le16_to_cpu(header));
+}
+
+static inline unsigned int pd_header_type(u16 header)
+{
+	return (header >> PD_HEADER_TYPE_SHIFT) & PD_HEADER_TYPE_MASK;
+}
+
+static inline unsigned int pd_header_type_le(__le16 header)
+{
+	return pd_header_type(le16_to_cpu(header));
+}
+
+#define PD_MAX_PAYLOAD		7
+
+struct pd_message {
+	__le16 header;
+	__le32 payload[PD_MAX_PAYLOAD];
+};
+
+/* PDO: Power Data Object */
+#define PDO_MAX_OBJECTS		7
+
+enum pd_pdo_type {
+	PDO_TYPE_FIXED = 0,
+	PDO_TYPE_BATT = 1,
+	PDO_TYPE_VAR = 2,
+};
+
+#define PDO_TYPE_SHIFT		30
+#define PDO_TYPE_MASK		0x3
+
+#define PDO_FIXED_DUAL_ROLE	BIT(29)	/* Power role swap supported */
+#define PDO_FIXED_SUSPEND	BIT(28) /* USB Suspend supported (Source) */
+#define PDO_FIXED_HIGHER_CAP	BIT(28) /* Requires more than vSafe5V (Sink) */
+#define PDO_FIXED_EXTPOWER	BIT(27) /* Externally powered */
+#define PDO_FIXED_USB_COMM	BIT(26) /* USB communications capable */
+#define PDO_FIXED_DATA_SWAP	BIT(25) /* Data role swap supported */
+#define PDO_FIXED_VOLT_SHIFT	10	/* 50mV units */
+#define PDO_FIXED_VOLT_MASK	0x3ff
+#define PDO_FIXED_MAX_CURR_SHIFT	0	/* 10mA units */
+#define PDO_FIXED_MAX_CURR_MASK		0x3ff
+
+#define PDO_FIXED(mv, ma, flags)					\
+	((PDO_TYPE_FIXED << PDO_TYPE_SHIFT) | (flags) |			\
+	 ((((mv) / 50) & PDO_FIXED_VOLT_MASK) << PDO_FIXED_VOLT_SHIFT) | \
+	 ((((ma) / 10) & PDO_FIXED_MAX_CURR_MASK) << PDO_FIXED_MAX_CURR_SHIFT))
+
+#define PDO_BATT_MAX_VOLT_SHIFT	20	/* 50mV units */
+#define PDO_BATT_MAX_VOLT_MASK	0x3ff
+#define PDO_BATT_MIN_VOLT_SHIFT	10	/* 50mV units */
+#define PDO_BATT_MIN_VOLT_MASK	0x3ff
+#define PDO_BATT_MAX_PWR_SHIFT	0	/* 250mW units */
+#define PDO_BATT_MAX_PWR_MASK	0x3ff
+
+#define PDO_BATT(min_mv, max_mv, max_mw)				\
+	((PDO_TYPE_BATT << PDO_TYPE_SHIFT) |				\
+	 ((((min_mv) / 50) & PDO_BATT_MIN_VOLT_MASK) <<			\
+	  PDO_BATT_MIN_VOLT_SHIFT) |					\
+	 ((((max_mv) / 50) & PDO_BATT_MAX_VOLT_MASK) <<			\
+	  PDO_BATT_MAX_VOLT_SHIFT) |					\
+	 ((((max_mw) / 50) & PDO_BATT_MAX_PWR_MASK) <<			\
+	  PDO_BATT_MAX_PWR_SHIFT))
+
+#define PDO_VAR_MAX_VOLT_SHIFT	20	/* 50mV units */
+#define PDO_VAR_MAX_VOLT_MASK	0x3ff
+#define PDO_VAR_MIN_VOLT_SHIFT	10	/* 50mV units */
+#define PDO_VAR_MIN_VOLT_MASK	0x3ff
+#define PDO_VAR_MAX_CURR_SHIFT	0	/* 10mA units */
+#define PDO_VAR_MAX_CURR_MASK	0x3ff
+
+#define PDO_VAR(min_mv, max_mv, max_ma)					\
+	((PDO_TYPE_VAR << PDO_TYPE_SHIFT) |				\
+	 ((((min_mv) / 50) & PDO_VAR_MIN_VOLT_MASK) <<			\
+	  PDO_VAR_MIN_VOLT_SHIFT) |					\
+	 ((((max_mv) / 50) & PDO_VAR_MAX_VOLT_MASK) <<			\
+	  PDO_VAR_MAX_VOLT_SHIFT) |					\
+	 ((((max_ma) / 50) & PDO_VAR_MAX_CURR_MASK) <<			\
+	  PDO_VAR_MAX_CURR_SHIFT))
+
+static inline enum pd_pdo_type pdo_type(u32 pdo)
+{
+	return (pdo >> PDO_TYPE_SHIFT) & PDO_TYPE_MASK;
+}
+
+static inline unsigned int pdo_fixed_voltage(u32 pdo)
+{
+	return ((pdo >> PDO_FIXED_VOLT_SHIFT) & PDO_FIXED_VOLT_MASK) * 50;
+}
+
+static inline unsigned int pdo_min_voltage(u32 pdo)
+{
+	return ((pdo >> PDO_VAR_MIN_VOLT_SHIFT) & PDO_VAR_MIN_VOLT_MASK) * 50;
+}
+
+static inline unsigned int pdo_max_voltage(u32 pdo)
+{
+	return ((pdo >> PDO_VAR_MAX_VOLT_SHIFT) & PDO_VAR_MAX_VOLT_MASK) * 50;
+}
+
+static inline unsigned int pdo_max_current(u32 pdo)
+{
+	return ((pdo >> PDO_VAR_MAX_CURR_SHIFT) & PDO_VAR_MAX_CURR_MASK) * 10;
+}
+
+static inline unsigned int pdo_max_power(u32 pdo)
+{
+	return ((pdo >> PDO_BATT_MAX_PWR_SHIFT) & PDO_BATT_MAX_PWR_MASK) * 250;
+}
+
+/* RDO: Request Data Object */
+#define RDO_OBJ_POS_SHIFT	28
+#define RDO_OBJ_POS_MASK	0x7
+#define RDO_GIVE_BACK		BIT(27)	/* Supports reduced operating current */
+#define RDO_CAP_MISMATCH	BIT(26) /* Not satisfied by source caps */
+#define RDO_USB_COMM		BIT(25) /* USB communications capable */
+#define RDO_NO_SUSPEND		BIT(24) /* USB Suspend not supported */
+
+#define RDO_FIXED_OP_CURR_SHIFT		10
+#define RDO_FIXED_OP_CURR_MASK		0x3ff
+#define RDO_FIXED_MAX_CURR_SHIFT	0
+#define RDO_FIXED_MAX_CURR_MASK		0x3ff
+
+#define RDO_FIXED(idx, op_ma, max_ma, flags)				\
+	((((idx) & RDO_OBJ_POS_MASK) << RDO_OBJ_POS_SHIFT) | (flags) |	\
+	 ((((op_ma) / 10) & RDO_FIXED_OP_CURR_MASK) <<			\
+	  RDO_FIXED_OP_CURR_SHIFT) |					\
+	 ((((max_ma) / 10) & RDO_FIXED_MAX_CURR_MASK) <<		\
+	  RDO_FIXED_MAX_CURR_SHIFT))
+
+#define RDO_BATT_OP_PWR_SHIFT		10	/* 250mW units */
+#define RDO_BATT_OP_PWR_MASK		0x3ff
+#define RDO_BATT_MAX_PWR_SHIFT		0	/* 250mW units */
+#define RDO_BATT_MAX_PWR_MASK		0x3ff
+
+#define RDO_BATT(idx, op_mw, max_mw, flags)				\
+	((((idx) & RDO_OBJ_POS_MASK) << RDO_OBJ_POS_SHIFT) | (flags) |	\
+	 ((((op_mw) / 250) & RDO_BATT_OP_PWR_MASK) <<			\
+	  RDO_BATT_OP_PWR_SHIFT) |					\
+	 ((((max_mw) / 250) & RDO_BATT_MAX_PWR_MASK) <<			\
+	  RDO_BATT_MAX_PWR_SHIFT))
+
+static inline unsigned int rdo_index(u32 rdo)
+{
+	return (rdo >> RDO_OBJ_POS_SHIFT) & RDO_OBJ_POS_MASK;
+}
+
+static inline unsigned int rdo_op_current(u32 rdo)
+{
+	return ((rdo >> RDO_FIXED_OP_CURR_SHIFT) & RDO_FIXED_OP_CURR_MASK) * 10;
+}
+
+static inline unsigned int rdo_max_current(u32 rdo)
+{
+	return ((rdo >> RDO_FIXED_MAX_CURR_SHIFT) &
+		RDO_FIXED_MAX_CURR_MASK) * 10;
+}
+
+static inline unsigned int rdo_op_power(u32 rdo)
+{
+	return ((rdo >> RDO_BATT_OP_PWR_SHIFT) & RDO_BATT_OP_PWR_MASK) * 250;
+}
+
+static inline unsigned int rdo_max_power(u32 rdo)
+{
+	return ((rdo >> RDO_BATT_MAX_PWR_SHIFT) & RDO_BATT_MAX_PWR_MASK) * 250;
+}
+
+/* USB PD timers and counters */
+#define PD_T_NO_RESPONSE	5000	/* 4.5 - 5.5 seconds */
+#define PD_T_DB_DETECT		10000	/* 10 - 15 seconds */
+#define PD_T_SEND_SOURCE_CAP	100
+#define PD_T_SENDER_RESPONSE	60	/* 24 - 30 ms, relaxed */
+#define PD_T_SOURCE_ACTIVITY	45
+#define PD_T_SINK_ACTIVITY	135
+#define PD_T_SINK_WAIT_CAP	240
+#define PD_T_PS_TRANSITION	500
+#define PD_T_SRC_TRANSITION	35
+#define PD_T_DRP_SNK		40
+#define PD_T_DRP_SRC		30
+#define PD_T_PS_SOURCE_OFF	920
+#define PD_T_PS_SOURCE_ON	480
+#define PD_T_PS_HARD_RESET	30
+#define PD_T_SRC_RECOVER	760
+#define PD_T_SRC_RECOVER_MAX	1000
+#define PD_T_SRC_TURN_ON	275
+#define PD_T_SAFE_0V		650
+#define PD_T_VCONN_SOURCE_ON	100
+#define PD_T_SINK_REQUEST	100	/* 100 ms minimum */
+#define PD_T_ERROR_RECOVERY	100	/* minimum 25 is insufficient */
+
+#define PD_T_DRP_TRY		100	/* 75 - 150 ms */
+#define PD_T_DRP_TRYWAIT	600	/* 400 - 800 ms */
+
+#define PD_T_CC_DEBOUNCE	200	/* 100 - 200 ms */
+#define PD_T_PD_DEBOUNCE	20	/* 10 - 20 ms */
+
+#define PD_N_CAPS_COUNT		50
+#define PD_N_HARD_RESET_COUNT	2
+
+#endif /* __LINUX_USB_PD_H */
diff --git a/include/linux/usb/pd_bdo.h b/include/linux/usb/pd_bdo.h
new file mode 100644
index 000000000000..17e9388cf955
--- /dev/null
+++ b/include/linux/usb/pd_bdo.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2015-2016 Google, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __LINUX_USB_PD_BDO_H
+#define __LINUX_USB_PD_BDO_H
+
+/* BDO : BIST Data Object */
+#define BDO_MODE_RECV		(0 << 28)
+#define BDO_MODE_TRANSMIT	(1 << 28)
+#define BDO_MODE_COUNTERS	(2 << 28)
+#define BDO_MODE_CARRIER0	(3 << 28)
+#define BDO_MODE_CARRIER1	(4 << 28)
+#define BDO_MODE_CARRIER2	(5 << 28)
+#define BDO_MODE_CARRIER3	(6 << 28)
+#define BDO_MODE_EYE		(7 << 28)
+#define BDO_MODE_TESTDATA	(8 << 28)
+
+#define BDO_MODE_MASK(mode)	((mode) & 0xf0000000)
+
+#endif
diff --git a/include/linux/usb/pd_vdo.h b/include/linux/usb/pd_vdo.h
new file mode 100644
index 000000000000..7f9533316b92
--- /dev/null
+++ b/include/linux/usb/pd_vdo.h
@@ -0,0 +1,412 @@
+/*
+ * Copyright 2015-2016 Google, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __LINUX_USB_PD_VDO_H
+#define __LINUX_USB_PD_VDO_H
+
+#include <linux/usb/pd.h>
+
+/*
+ * VDO : Vendor Defined Message Object
+ * VDM object is minimum of VDM header + 6 additional data objects.
+ */
+
+/*
+ * VDM header
+ * ----------
+ * <31:16>  :: SVID
+ * <15>     :: VDM type ( 1b == structured, 0b == unstructured )
+ * <14:13>  :: Structured VDM version (can only be 00 == 1.0 currently)
+ * <12:11>  :: reserved
+ * <10:8>   :: object position (1-7 valid ... used for enter/exit mode only)
+ * <7:6>    :: command type (SVDM only?)
+ * <5>      :: reserved (SVDM), command type (UVDM)
+ * <4:0>    :: command
+ */
+#define VDO_MAX_SIZE 7
+#define VDO(vid, type, custom)				\
+	(((vid) << 16) |				\
+	 ((type) << 15) |				\
+	 ((custom) & 0x7FFF))
+
+#define VDO_SVDM_TYPE		(1 << 15)
+#define VDO_SVDM_VERS(x)	((x) << 13)
+#define VDO_OPOS(x)		((x) << 8)
+#define VDO_CMDT(x)		((x) << 6)
+#define VDO_OPOS_MASK		VDO_OPOS(0x7)
+#define VDO_CMDT_MASK		VDO_CMDT(0x3)
+
+#define CMDT_INIT		0
+#define CMDT_RSP_ACK		1
+#define CMDT_RSP_NAK		2
+#define CMDT_RSP_BUSY		3
+
+/* reserved for SVDM ... for Google UVDM */
+#define VDO_SRC_INITIATOR	(0 << 5)
+#define VDO_SRC_RESPONDER	(1 << 5)
+
+#define CMD_DISCOVER_IDENT	1
+#define CMD_DISCOVER_SVID	2
+#define CMD_DISCOVER_MODES	3
+#define CMD_ENTER_MODE		4
+#define CMD_EXIT_MODE		5
+#define CMD_ATTENTION		6
+#define CMD_DP_STATUS		16
+#define CMD_DP_CONFIG		17
+
+#define VDO_CMD_VENDOR(x)    (((10 + (x)) & 0x1f))
+
+/* ChromeOS specific commands */
+#define VDO_CMD_VERSION		VDO_CMD_VENDOR(0)
+#define VDO_CMD_SEND_INFO	VDO_CMD_VENDOR(1)
+#define VDO_CMD_READ_INFO	VDO_CMD_VENDOR(2)
+#define VDO_CMD_REBOOT		VDO_CMD_VENDOR(5)
+#define VDO_CMD_FLASH_ERASE	VDO_CMD_VENDOR(6)
+#define VDO_CMD_FLASH_WRITE	VDO_CMD_VENDOR(7)
+#define VDO_CMD_ERASE_SIG	VDO_CMD_VENDOR(8)
+#define VDO_CMD_PING_ENABLE	VDO_CMD_VENDOR(10)
+#define VDO_CMD_CURRENT		VDO_CMD_VENDOR(11)
+#define VDO_CMD_FLIP		VDO_CMD_VENDOR(12)
+#define VDO_CMD_GET_LOG		VDO_CMD_VENDOR(13)
+#define VDO_CMD_CCD_EN		VDO_CMD_VENDOR(14)
+
+#define PD_VDO_VID(vdo)		((vdo) >> 16)
+#define PD_VDO_SVDM(vdo)	(((vdo) >> 15) & 1)
+#define PD_VDO_OPOS(vdo)	(((vdo) >> 8) & 0x7)
+#define PD_VDO_CMD(vdo)		((vdo) & 0x1f)
+#define PD_VDO_CMDT(vdo)	(((vdo) >> 6) & 0x3)
+
+/*
+ * SVDM Identity request -> response
+ *
+ * Request is simply properly formatted SVDM header
+ *
+ * Response is 4 data objects:
+ * [0] :: SVDM header
+ * [1] :: Identitiy header
+ * [2] :: Cert Stat VDO
+ * [3] :: (Product | Cable) VDO
+ * [4] :: AMA VDO
+ *
+ */
+#define VDO_INDEX_HDR		0
+#define VDO_INDEX_IDH		1
+#define VDO_INDEX_CSTAT		2
+#define VDO_INDEX_CABLE		3
+#define VDO_INDEX_PRODUCT	3
+#define VDO_INDEX_AMA		4
+
+/*
+ * SVDM Identity Header
+ * --------------------
+ * <31>     :: data capable as a USB host
+ * <30>     :: data capable as a USB device
+ * <29:27>  :: product type
+ * <26>     :: modal operation supported (1b == yes)
+ * <25:16>  :: SBZ
+ * <15:0>   :: USB-IF assigned VID for this cable vendor
+ */
+#define IDH_PTYPE_UNDEF		0
+#define IDH_PTYPE_HUB		1
+#define IDH_PTYPE_PERIPH	2
+#define IDH_PTYPE_PCABLE	3
+#define IDH_PTYPE_ACABLE	4
+#define IDH_PTYPE_AMA		5
+
+#define VDO_IDH(usbh, usbd, ptype, is_modal, vid)		\
+	((usbh) << 31 | (usbd) << 30 | ((ptype) & 0x7) << 27	\
+	 | (is_modal) << 26 | ((vid) & 0xffff))
+
+#define PD_IDH_PTYPE(vdo)	(((vdo) >> 27) & 0x7)
+#define PD_IDH_VID(vdo)		((vdo) & 0xffff)
+#define PD_IDH_MODAL_SUPP(vdo)	((vdo) & (1 << 26))
+
+/*
+ * Cert Stat VDO
+ * -------------
+ * <31:20> : SBZ
+ * <19:0>  : USB-IF assigned TID for this cable
+ */
+#define VDO_CSTAT(tid)		((tid) & 0xfffff)
+#define PD_CSTAT_TID(vdo)	((vdo) & 0xfffff)
+
+/*
+ * Product VDO
+ * -----------
+ * <31:16> : USB Product ID
+ * <15:0>  : USB bcdDevice
+ */
+#define VDO_PRODUCT(pid, bcd)	(((pid) & 0xffff) << 16 | ((bcd) & 0xffff))
+#define PD_PRODUCT_PID(vdo)	(((vdo) >> 16) & 0xffff)
+
+/*
+ * Cable VDO
+ * ---------
+ * <31:28> :: Cable HW version
+ * <27:24> :: Cable FW version
+ * <23:20> :: SBZ
+ * <19:18> :: type-C to Type-A/B/C (00b == A, 01 == B, 10 == C)
+ * <17>    :: Type-C to Plug/Receptacle (0b == plug, 1b == receptacle)
+ * <16:13> :: cable latency (0001 == <10ns(~1m length))
+ * <12:11> :: cable termination type (11b == both ends active VCONN req)
+ * <10>    :: SSTX1 Directionality support (0b == fixed, 1b == cfgable)
+ * <9>     :: SSTX2 Directionality support
+ * <8>     :: SSRX1 Directionality support
+ * <7>     :: SSRX2 Directionality support
+ * <6:5>   :: Vbus current handling capability
+ * <4>     :: Vbus through cable (0b == no, 1b == yes)
+ * <3>     :: SOP" controller present? (0b == no, 1b == yes)
+ * <2:0>   :: USB SS Signaling support
+ */
+#define CABLE_ATYPE		0
+#define CABLE_BTYPE		1
+#define CABLE_CTYPE		2
+#define CABLE_PLUG		0
+#define CABLE_RECEPTACLE	1
+#define CABLE_CURR_1A5		0
+#define CABLE_CURR_3A		1
+#define CABLE_CURR_5A		2
+#define CABLE_USBSS_U2_ONLY	0
+#define CABLE_USBSS_U31_GEN1	1
+#define CABLE_USBSS_U31_GEN2	2
+#define VDO_CABLE(hw, fw, cbl, gdr, lat, term, tx1d, tx2d, rx1d, rx2d, cur,\
+		  vps, sopp, usbss) \
+	(((hw) & 0x7) << 28 | ((fw) & 0x7) << 24 | ((cbl) & 0x3) << 18	\
+	 | (gdr) << 17 | ((lat) & 0x7) << 13 | ((term) & 0x3) << 11	\
+	 | (tx1d) << 10 | (tx2d) << 9 | (rx1d) << 8 | (rx2d) << 7	\
+	 | ((cur) & 0x3) << 5 | (vps) << 4 | (sopp) << 3		\
+	 | ((usbss) & 0x7))
+
+/*
+ * AMA VDO
+ * ---------
+ * <31:28> :: Cable HW version
+ * <27:24> :: Cable FW version
+ * <23:12> :: SBZ
+ * <11>    :: SSTX1 Directionality support (0b == fixed, 1b == cfgable)
+ * <10>    :: SSTX2 Directionality support
+ * <9>     :: SSRX1 Directionality support
+ * <8>     :: SSRX2 Directionality support
+ * <7:5>   :: Vconn power
+ * <4>     :: Vconn power required
+ * <3>     :: Vbus power required
+ * <2:0>   :: USB SS Signaling support
+ */
+#define VDO_AMA(hw, fw, tx1d, tx2d, rx1d, rx2d, vcpwr, vcr, vbr, usbss) \
+	(((hw) & 0x7) << 28 | ((fw) & 0x7) << 24			\
+	 | (tx1d) << 11 | (tx2d) << 10 | (rx1d) << 9 | (rx2d) << 8	\
+	 | ((vcpwr) & 0x3) << 5 | (vcr) << 4 | (vbr) << 3		\
+	 | ((usbss) & 0x7))
+
+#define PD_VDO_AMA_VCONN_REQ(vdo)	(((vdo) >> 4) & 1)
+#define PD_VDO_AMA_VBUS_REQ(vdo)	(((vdo) >> 3) & 1)
+
+#define AMA_VCONN_PWR_1W	0
+#define AMA_VCONN_PWR_1W5	1
+#define AMA_VCONN_PWR_2W	2
+#define AMA_VCONN_PWR_3W	3
+#define AMA_VCONN_PWR_4W	4
+#define AMA_VCONN_PWR_5W	5
+#define AMA_VCONN_PWR_6W	6
+#define AMA_USBSS_U2_ONLY	0
+#define AMA_USBSS_U31_GEN1	1
+#define AMA_USBSS_U31_GEN2	2
+#define AMA_USBSS_BBONLY	3
+
+/*
+ * SVDM Discover SVIDs request -> response
+ *
+ * Request is properly formatted VDM Header with discover SVIDs command.
+ * Response is a set of SVIDs of all all supported SVIDs with all zero's to
+ * mark the end of SVIDs.  If more than 12 SVIDs are supported command SHOULD be
+ * repeated.
+ */
+#define VDO_SVID(svid0, svid1)	(((svid0) & 0xffff) << 16 | ((svid1) & 0xffff))
+#define PD_VDO_SVID_SVID0(vdo)	((vdo) >> 16)
+#define PD_VDO_SVID_SVID1(vdo)	((vdo) & 0xffff)
+
+/*
+ * Google modes capabilities
+ * <31:8> : reserved
+ * <7:0>  : mode
+ */
+#define VDO_MODE_GOOGLE(mode) ((mode) & 0xff)
+
+#define MODE_GOOGLE_FU 1 /* Firmware Update mode */
+
+/*
+ * Mode Capabilities
+ *
+ * Number of VDOs supplied is SID dependent (but <= 6 VDOS?)
+ */
+#define VDO_MODE_CNT_DISPLAYPORT 1
+
+/*
+ * DisplayPort modes capabilities
+ * -------------------------------
+ * <31:24> : SBZ
+ * <23:16> : UFP_D pin assignment supported
+ * <15:8>  : DFP_D pin assignment supported
+ * <7>     : USB 2.0 signaling (0b=yes, 1b=no)
+ * <6>     : Plug | Receptacle (0b == plug, 1b == receptacle)
+ * <5:2>   : xxx1: Supports DPv1.3, xx1x Supports USB Gen 2 signaling
+ *           Other bits are reserved.
+ * <1:0>   : signal direction ( 00b=rsv, 01b=sink, 10b=src 11b=both )
+ */
+#define VDO_MODE_DP(snkp, srcp, usb, gdr, sign, sdir)			\
+	(((snkp) & 0xff) << 16 | ((srcp) & 0xff) << 8			\
+	 | ((usb) & 1) << 7 | ((gdr) & 1) << 6 | ((sign) & 0xF) << 2	\
+	 | ((sdir) & 0x3))
+#define PD_DP_PIN_CAPS(x) ((((x) >> 6) & 0x1) ? (((x) >> 16) & 0x3f)	\
+			   : (((x) >> 8) & 0x3f))
+
+#define MODE_DP_PIN_A		0x01
+#define MODE_DP_PIN_B		0x02
+#define MODE_DP_PIN_C		0x04
+#define MODE_DP_PIN_D		0x08
+#define MODE_DP_PIN_E		0x10
+#define MODE_DP_PIN_F		0x20
+
+/* Pin configs B/D/F support multi-function */
+#define MODE_DP_PIN_MF_MASK	0x2a
+/* Pin configs A/B support BR2 signaling levels */
+#define MODE_DP_PIN_BR2_MASK	0x3
+/* Pin configs C/D/E/F support DP signaling levels */
+#define MODE_DP_PIN_DP_MASK	0x3c
+
+#define MODE_DP_V13		0x1
+#define MODE_DP_GEN2		0x2
+
+#define MODE_DP_SNK		0x1
+#define MODE_DP_SRC		0x2
+#define MODE_DP_BOTH		0x3
+
+/*
+ * DisplayPort Status VDO
+ * ----------------------
+ * <31:9> : SBZ
+ * <8>    : IRQ_HPD : 1 == irq arrived since last message otherwise 0.
+ * <7>    : HPD state : 0 = HPD_LOW, 1 == HPD_HIGH
+ * <6>    : Exit DP Alt mode: 0 == maintain, 1 == exit
+ * <5>    : USB config : 0 == maintain current, 1 == switch to USB from DP
+ * <4>    : Multi-function preference : 0 == no pref, 1 == MF preferred.
+ * <3>    : enabled : is DPout on/off.
+ * <2>    : power low : 0 == normal or LPM disabled, 1 == DP disabled for LPM
+ * <1:0>  : connect status : 00b ==  no (DFP|UFP)_D is connected or disabled.
+ *          01b == DFP_D connected, 10b == UFP_D connected, 11b == both.
+ */
+#define VDO_DP_STATUS(irq, lvl, amode, usbc, mf, en, lp, conn)		\
+	(((irq) & 1) << 8 | ((lvl) & 1) << 7 | ((amode) & 1) << 6	\
+	 | ((usbc) & 1) << 5 | ((mf) & 1) << 4 | ((en) & 1) << 3	\
+	 | ((lp) & 1) << 2 | ((conn & 0x3) << 0))
+
+#define PD_VDO_DPSTS_HPD_IRQ(x)	(((x) >> 8) & 1)
+#define PD_VDO_DPSTS_HPD_LVL(x)	(((x) >> 7) & 1)
+#define PD_VDO_DPSTS_MF_PREF(x)	(((x) >> 4) & 1)
+
+/* Per DisplayPort Spec v1.3 Section 3.3, in uS */
+#define HPD_USTREAM_DEBOUNCE_LVL	2000
+#define HPD_USTREAM_DEBOUNCE_IRQ	250
+#define HPD_DSTREAM_DEBOUNCE_IRQ	750	/* between 500-1000us */
+
+/*
+ * DisplayPort Configure VDO
+ * -------------------------
+ * <31:24> : SBZ
+ * <23:16> : SBZ
+ * <15:8>  : Pin assignment requested.  Choose one from mode caps.
+ * <7:6>   : SBZ
+ * <5:2>   : signalling : 1h == DP v1.3, 2h == Gen 2
+ *           Oh is only for USB, remaining values are reserved
+ * <1:0>   : cfg : 00 == USB, 01 == DFP_D, 10 == UFP_D, 11 == reserved
+ */
+#define VDO_DP_CFG(pin, sig, cfg) \
+	(((pin) & 0xff) << 8 | ((sig) & 0xf) << 2 | ((cfg) & 0x3))
+
+#define PD_DP_CFG_DPON(x)	((((x) & 0x3) == 1) || (((x) & 0x3) == 2))
+/*
+ * Get the pin assignment mask
+ * for backward compatibility, if it is null,
+ * get the former sink pin assignment we used to be in <23:16>.
+ */
+#define PD_DP_CFG_PIN(x) ((((x) >> 8) & 0xff) ? (((x) >> 8) & 0xff) \
+					      : (((x) >> 16) & 0xff))
+/*
+ * ChromeOS specific PD device Hardware IDs. Used to identify unique
+ * products and used in VDO_INFO. Note this field is 10 bits.
+ */
+#define USB_PD_HW_DEV_ID_RESERVED	0
+#define USB_PD_HW_DEV_ID_ZINGER		1
+#define USB_PD_HW_DEV_ID_MINIMUFFIN	2
+#define USB_PD_HW_DEV_ID_DINGDONG	3
+#define USB_PD_HW_DEV_ID_HOHO		4
+#define USB_PD_HW_DEV_ID_HONEYBUNS	5
+
+/*
+ * ChromeOS specific VDO_CMD_READ_INFO responds with device info including:
+ * RW Hash: First 20 bytes of SHA-256 of RW (20 bytes)
+ * HW Device ID: unique descriptor for each ChromeOS model (2 bytes)
+ *               top 6 bits are minor revision, bottom 10 bits are major
+ * SW Debug Version: Software version useful for debugging (15 bits)
+ * IS RW: True if currently in RW, False otherwise (1 bit)
+ */
+#define VDO_INFO(id, id_minor, ver, is_rw) ((id_minor) << 26 \
+				| ((id) & 0x3ff) << 16 \
+				| ((ver) & 0x7fff) << 1 \
+				| ((is_rw) & 1))
+#define VDO_INFO_HW_DEV_ID(x)	((x) >> 16)
+#define VDO_INFO_SW_DBG_VER(x)	(((x) >> 1) & 0x7fff)
+#define VDO_INFO_IS_RW(x)	((x) & 1)
+
+#define HW_DEV_ID_MAJ(x) ((x) & 0x3ff)
+#define HW_DEV_ID_MIN(x) ((x) >> 10)
+
+/* USB-IF SIDs */
+#define USB_SID_PD		0xff00 /* power delivery */
+#define USB_SID_DISPLAYPORT	0xff01
+#define USB_SID_MHL		0xff02	/* Mobile High-Definition Link */
+
+#define USB_DP_TYPEC_URL	"http://help.vesa.org/dp-usb-type-c/"
+
+/* USB Vendor IDs */
+#define USB_VID_APPLE		0x05ac
+#define USB_VID_GOOGLE		0x18d1
+
+/* VDM command timeouts (in ms) */
+
+#define PD_T_VDM_UNSTRUCTURED	500
+#define PD_T_VDM_BUSY		100
+#define PD_T_VDM_WAIT_MODE_E	100
+#define PD_T_VDM_SNDR_RSP	30
+#define PD_T_VDM_E_MODE		25
+#define PD_T_VDM_RCVR_RSP	15
+
+/* Alternate mode support */
+
+#define SVID_DISCOVERY_MAX	16
+#define ALT_MODE_MAX		16
+#define MODE_MAX		32
+
+struct pd_mode_data {
+	int svid_index;		/* current SVID index		*/
+	int nsvids;		/* number of SVIDs		*/
+	int naltmodes;		/* number of alternate modes	*/
+	int nmodes;		/* total number of modes	*/
+	u32 identity[PDO_MAX_OBJECTS];
+	u16 svids[SVID_DISCOVERY_MAX];
+	struct typec_altmode altmodes[ALT_MODE_MAX + 1];
+	struct typec_mode modes[MODE_MAX + 1];
+};
+
+#endif /* __LINUX_USB_PD_VDO_H */
-- 
2.8.0.rc3.226.g39d4020

^ permalink raw reply related	[flat|nested] 24+ messages in thread

* [RFC PATCH v3 2/2] usb: typec: Type-C Port Controller Interface driver (tcpci)
  2016-08-23 21:10 [RFC PATCH v3 0/2] Type-C Port Manager Guenter Roeck
  2016-08-23 21:10 ` [RFC PATCH v3 1/2] usb: typec: USB Type-C Port Manager (tcpm) Guenter Roeck
@ 2016-08-23 21:10 ` Guenter Roeck
  2016-09-30  6:24   ` Jun Li
  2016-08-24 12:52 ` [RFC PATCH v3 0/2] Type-C Port Manager Heikki Krogerus
  2 siblings, 1 reply; 24+ messages in thread
From: Guenter Roeck @ 2016-08-23 21:10 UTC (permalink / raw)
  To: Felipe Balbi
  Cc: Chandra Sekhar Anagani, Bruce Ashfield, Bin Gao, Pranav Tipnis,
	Heikki Krogerus, linux-kernel, linux-usb, Guenter Roeck

The port controller interface driver interconnects the Type-C Port
Manager with a Type-C Port Controller Interface (TCPCI) compliant
port controller.

Signed-off-by: Guenter Roeck <groeck@chromium.org>
---
v3:
- No change

v2:
- Adjust to modified callbacks into tcpm code
 
 drivers/usb/typec/Kconfig  |   9 +
 drivers/usb/typec/Makefile |   1 +
 drivers/usb/typec/tcpci.c  | 487 +++++++++++++++++++++++++++++++++++++++++++++
 drivers/usb/typec/tcpci.h  | 133 +++++++++++++
 4 files changed, 630 insertions(+)
 create mode 100644 drivers/usb/typec/tcpci.c
 create mode 100644 drivers/usb/typec/tcpci.h

diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig
index 113bb1b3589c..a92c9d1a3e00 100644
--- a/drivers/usb/typec/Kconfig
+++ b/drivers/usb/typec/Kconfig
@@ -25,4 +25,13 @@ config TYPEC_TCPM
 	  The Type-C Port Controller Manager provides a USB PD and USB Type-C
 	  state machine for use with Type-C Port Controllers.
 
+if TYPEC_TCPM
+
+config TYPEC_TCPCI
+	tristate "Type-C Port Controller Interface driver"
+	help
+	  Type-C Port Controller driver for TCPCI-compliant controller.
+
+endif
+
 endmenu
diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile
index bbe45721cf52..7dbaf8c3911d 100644
--- a/drivers/usb/typec/Makefile
+++ b/drivers/usb/typec/Makefile
@@ -1,3 +1,4 @@
 obj-$(CONFIG_TYPEC)		+= typec.o
 obj-$(CONFIG_TYPEC_WCOVE)	+= typec_wcove.o
 obj-$(CONFIG_TYPEC_TCPM)	+= tcpm.o
+obj-$(CONFIG_TYPEC_TCPCI)	+= tcpci.o
diff --git a/drivers/usb/typec/tcpci.c b/drivers/usb/typec/tcpci.c
new file mode 100644
index 000000000000..af338218a1f3
--- /dev/null
+++ b/drivers/usb/typec/tcpci.c
@@ -0,0 +1,487 @@
+/*
+ * Copyright 2015-2016 Google, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * USB Type-C Port Controller Interface.
+ */
+
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/regmap.h>
+#include <linux/usb/pd.h>
+#include <linux/usb/typec.h>
+
+#include "tcpci.h"
+#include "tcpm.h"
+
+#define PD_RETRY_COUNT 3
+
+struct tcpci {
+	struct device *dev;
+	struct i2c_client *client;
+
+	struct tcpm_port *port;
+
+	struct regmap *regmap;
+
+	bool controls_vbus;
+
+	struct tcpc_dev tcpc;
+};
+
+static inline struct tcpci *tcpc_to_tcpci(struct tcpc_dev *tcpc)
+{
+	return container_of(tcpc, struct tcpci, tcpc);
+}
+
+static int tcpci_read16(struct tcpci *tcpci, unsigned int reg,
+			unsigned int *val)
+{
+	return regmap_raw_read(tcpci->regmap, reg, val, sizeof(u16));
+}
+
+static int tcpci_write16(struct tcpci *tcpci, unsigned int reg, u16 val)
+{
+	return regmap_raw_write(tcpci->regmap, reg, &val, sizeof(u16));
+}
+
+static int tcpci_set_cc(struct tcpc_dev *tcpc, enum typec_cc_status cc)
+{
+	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
+	unsigned int reg;
+	int ret;
+
+	switch (cc) {
+	case TYPEC_CC_RA:
+		reg = (TCPC_ROLE_CTRL_CC_RA << TCPC_ROLE_CTRL_CC1_SHIFT) |
+			(TCPC_ROLE_CTRL_CC_RA << TCPC_ROLE_CTRL_CC2_SHIFT);
+		break;
+	case TYPEC_CC_RD:
+		reg = (TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC1_SHIFT) |
+			(TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC2_SHIFT);
+		break;
+	case TYPEC_CC_RP_DEF:
+		reg = (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC1_SHIFT) |
+			(TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC2_SHIFT) |
+			(TCPC_ROLE_CTRL_RP_VAL_DEF <<
+			 TCPC_ROLE_CTRL_RP_VAL_SHIFT);
+		break;
+	case TYPEC_CC_RP_1_5:
+		reg = (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC1_SHIFT) |
+			(TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC2_SHIFT) |
+			(TCPC_ROLE_CTRL_RP_VAL_1_5 <<
+			 TCPC_ROLE_CTRL_RP_VAL_SHIFT);
+		break;
+	case TYPEC_CC_RP_3_0:
+		reg = (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC1_SHIFT) |
+			(TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC2_SHIFT) |
+			(TCPC_ROLE_CTRL_RP_VAL_1_5 <<
+			 TCPC_ROLE_CTRL_RP_VAL_SHIFT);
+		break;
+	case TYPEC_CC_OPEN:
+	default:
+		reg = (TCPC_ROLE_CTRL_CC_OPEN << TCPC_ROLE_CTRL_CC1_SHIFT) |
+			(TCPC_ROLE_CTRL_CC_OPEN << TCPC_ROLE_CTRL_CC2_SHIFT);
+		break;
+	}
+
+	ret = regmap_write(tcpci->regmap, TCPC_ROLE_CTRL, reg);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static enum typec_cc_status tcpci_to_typec_cc(unsigned int cc, bool sink)
+{
+	switch (cc) {
+	case 0x1:
+		return sink ? TYPEC_CC_RP_DEF : TYPEC_CC_RA;
+	case 0x2:
+		return sink ? TYPEC_CC_RP_1_5 : TYPEC_CC_RD;
+	case 0x3:
+		if (sink)
+			return TYPEC_CC_RP_3_0;
+	case 0x0:
+	default:
+		return TYPEC_CC_OPEN;
+	}
+}
+
+static int tcpci_get_cc(struct tcpc_dev *tcpc,
+			enum typec_cc_status *cc1, enum typec_cc_status *cc2)
+{
+	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
+	unsigned int reg;
+	int ret;
+
+	ret = regmap_read(tcpci->regmap, TCPC_CC_STATUS, &reg);
+	if (ret < 0)
+		return ret;
+
+	*cc1 = tcpci_to_typec_cc((reg >> TCPC_CC_STATUS_CC1_SHIFT) &
+				 TCPC_CC_STATUS_CC1_MASK,
+				 reg & TCPC_CC_STATUS_TERM);
+	*cc2 = tcpci_to_typec_cc((reg >> TCPC_CC_STATUS_CC2_SHIFT) &
+				 TCPC_CC_STATUS_CC2_MASK,
+				 reg & TCPC_CC_STATUS_TERM);
+
+	return 0;
+}
+
+static int tcpci_set_polarity(struct tcpc_dev *tcpc,
+			      enum typec_cc_polarity polarity)
+{
+	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
+	int ret;
+
+	ret = regmap_write(tcpci->regmap, TCPC_TCPC_CTRL,
+			   (polarity == TYPEC_POLARITY_CC2) ?
+			   TCPC_TCPC_CTRL_ORIENTATION : 0);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int tcpci_set_vconn(struct tcpc_dev *tcpc, bool enable)
+{
+	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
+	int ret;
+
+	ret = regmap_write(tcpci->regmap, TCPC_POWER_CTRL,
+			   enable ? TCPC_POWER_CTRL_VCONN_ENABLE : 0);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int tcpci_set_pd_header(struct tcpc_dev *tcpc, enum typec_role role,
+			       enum typec_data_role data)
+{
+	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
+	unsigned int reg;
+	int ret;
+
+	reg = PD_REV20 << TCPC_MSG_HDR_INFO_REV_SHIFT;
+	if (role == TYPEC_SOURCE)
+		reg |= TCPC_MSG_HDR_INFO_PWR_ROLE;
+	if (data == TYPEC_HOST)
+		reg |= TCPC_MSG_HDR_INFO_DATA_ROLE;
+	ret = regmap_write(tcpci->regmap, TCPC_MSG_HDR_INFO, reg);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int tcpci_set_pd_rx(struct tcpc_dev *tcpc, bool enable)
+{
+	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
+	unsigned int reg = 0;
+	int ret;
+
+	if (enable)
+		reg = TCPC_RX_DETECT_SOP | TCPC_RX_DETECT_HARD_RESET;
+	ret = regmap_write(tcpci->regmap, TCPC_RX_DETECT, reg);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int tcpci_get_vbus(struct tcpc_dev *tcpc)
+{
+	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
+	unsigned int reg;
+	int ret;
+
+	ret = regmap_read(tcpci->regmap, TCPC_POWER_STATUS, &reg);
+	if (ret < 0)
+		return ret;
+
+	return !!(reg & TCPC_POWER_STATUS_VBUS_PRES);
+}
+
+static int tcpci_set_vbus(struct tcpc_dev *tcpc, bool source, bool sink)
+{
+	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
+	int ret;
+
+	if (source) {
+		ret = regmap_write(tcpci->regmap, TCPC_COMMAND,
+				   TCPC_CMD_SRC_VBUS_DEFAULT);
+		if (ret < 0)
+			return ret;
+	} else {
+		ret = regmap_write(tcpci->regmap, TCPC_COMMAND,
+				   TCPC_CMD_DISABLE_SRC_VBUS);
+		if (ret < 0)
+			return ret;
+	}
+
+	if (sink) {
+		ret = regmap_write(tcpci->regmap, TCPC_COMMAND,
+				   TCPC_CMD_SINK_VBUS);
+		if (ret < 0)
+			return ret;
+	} else {
+		ret = regmap_write(tcpci->regmap, TCPC_COMMAND,
+				   TCPC_CMD_DISABLE_SINK_VBUS);
+		if (ret < 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int tcpci_pd_transmit(struct tcpc_dev *tcpc,
+			     enum tcpm_transmit_type type,
+			     const struct pd_message *msg)
+{
+	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
+	unsigned int reg, cnt, header;
+	int ret;
+
+	cnt = msg ? pd_header_cnt(msg->header) * 4 : 0;
+	ret = regmap_write(tcpci->regmap, TCPC_TX_BYTE_CNT, cnt);
+	if (ret < 0)
+		return ret;
+
+	header = msg ? msg->header : 0;
+	ret = tcpci_write16(tcpci, TCPC_TX_HDR, header);
+	if (ret < 0)
+		return ret;
+
+	if (cnt > 0) {
+		ret = regmap_raw_write(tcpci->regmap, TCPC_TX_DATA,
+				       &msg->payload, cnt);
+		if (ret < 0)
+			return ret;
+	}
+
+	reg = (PD_RETRY_COUNT << TCPC_TRANSMIT_RETRY_SHIFT) |
+		(type << TCPC_TRANSMIT_TYPE_SHIFT);
+	ret = regmap_write(tcpci->regmap, TCPC_TRANSMIT, reg);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int tcpci_init(struct tcpc_dev *tcpc)
+{
+	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
+	unsigned long timeout = jiffies + msecs_to_jiffies(2000); /* XXX */
+	unsigned int reg;
+	int ret;
+
+	while (time_before_eq(jiffies, timeout)) {
+		ret = regmap_read(tcpci->regmap, TCPC_POWER_STATUS, &reg);
+		if (ret < 0)
+			return ret;
+		if (!(reg & TCPC_POWER_STATUS_UNINIT))
+			break;
+		usleep_range(10000, 20000);
+	}
+	if (time_after(jiffies, timeout))
+		return -ETIMEDOUT;
+
+	/* Clear all events */
+	ret = tcpci_write16(tcpci, TCPC_ALERT, 0xffff);
+	if (ret < 0)
+		return ret;
+
+	if (tcpci->controls_vbus)
+		reg = TCPC_POWER_STATUS_VBUS_PRES;
+	else
+		reg = 0;
+	ret = regmap_write(tcpci->regmap, TCPC_POWER_STATUS_MASK, reg);
+	if (ret < 0)
+		return ret;
+
+	reg = TCPC_ALERT_TX_SUCCESS | TCPC_ALERT_TX_FAILED |
+		TCPC_ALERT_TX_DISCARDED | TCPC_ALERT_RX_STATUS |
+		TCPC_ALERT_RX_HARD_RST | TCPC_ALERT_CC_STATUS;
+	if (tcpci->controls_vbus)
+		reg |= TCPC_ALERT_POWER_STATUS;
+	return tcpci_write16(tcpci, TCPC_ALERT_MASK, reg);
+}
+
+static irqreturn_t tcpci_irq(int irq, void *dev_id)
+{
+	struct tcpci *tcpci = dev_id;
+	unsigned int status, reg;
+
+	tcpci_read16(tcpci, TCPC_ALERT, &status);
+
+	/*
+	 * Clear alert status for everything except RX_STATUS, which shouldn't
+	 * be cleared until we have successfully retrieved message.
+	 */
+	if (status & ~TCPC_ALERT_RX_STATUS)
+		tcpci_write16(tcpci, TCPC_ALERT,
+			      status & ~TCPC_ALERT_RX_STATUS);
+
+	if (status & TCPC_ALERT_CC_STATUS)
+		tcpm_cc_change(tcpci->port);
+
+	if (status & TCPC_ALERT_POWER_STATUS) {
+		regmap_read(tcpci->regmap, TCPC_POWER_STATUS_MASK, &reg);
+
+		/*
+		 * If power status mask has been reset, then the TCPC
+		 * has reset.
+		 */
+		if (reg == 0xff)
+			tcpm_tcpc_reset(tcpci->port);
+		else
+			tcpm_vbus_change(tcpci->port);
+	}
+
+	if (status & TCPC_ALERT_RX_STATUS) {
+		struct pd_message msg;
+		unsigned int cnt;
+
+		regmap_read(tcpci->regmap, TCPC_RX_BYTE_CNT, &cnt);
+
+		tcpci_read16(tcpci, TCPC_RX_HDR, &reg);
+		msg.header = reg;
+
+		if (WARN_ON(cnt > sizeof(msg.payload)))
+			cnt = sizeof(msg.payload);
+
+		if (cnt > 0)
+			regmap_raw_read(tcpci->regmap, TCPC_RX_DATA,
+					&msg.payload, cnt);
+
+		/* Read complete, clear RX status alert bit */
+		tcpci_write16(tcpci, TCPC_ALERT, TCPC_ALERT_RX_STATUS);
+
+		tcpm_pd_receive(tcpci->port, &msg);
+	}
+
+	if (status & TCPC_ALERT_RX_HARD_RST)
+		tcpm_pd_hard_reset(tcpci->port);
+
+	if (status & TCPC_ALERT_TX_SUCCESS)
+		tcpm_pd_transmit_complete(tcpci->port, TCPC_TX_SUCCESS);
+	else if (status & TCPC_ALERT_TX_DISCARDED)
+		tcpm_pd_transmit_complete(tcpci->port, TCPC_TX_DISCARDED);
+	else if (status & TCPC_ALERT_TX_FAILED)
+		tcpm_pd_transmit_complete(tcpci->port, TCPC_TX_FAILED);
+
+	return IRQ_HANDLED;
+}
+
+static const struct regmap_config tcpci_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+
+	.max_register = 0x7F, /* 0x80 .. 0xFF are vendor defined */
+};
+
+static int tcpci_parse_config(struct tcpci *tcpci)
+{
+	tcpci->controls_vbus = true; /* XXX */
+
+	/* TODO: Populate struct tcpc_config from ACPI/device-tree */
+
+	return 0;
+}
+
+static int tcpci_probe(struct i2c_client *client,
+		       const struct i2c_device_id *i2c_id)
+{
+	struct tcpci *tcpci;
+	int err;
+
+	tcpci = devm_kzalloc(&client->dev, sizeof(*tcpci), GFP_KERNEL);
+	if (!tcpci)
+		return -ENOMEM;
+
+	tcpci->client = client;
+	tcpci->dev = &client->dev;
+	i2c_set_clientdata(client, tcpci);
+	tcpci->regmap = devm_regmap_init_i2c(client, &tcpci_regmap_config);
+	if (IS_ERR(tcpci->regmap))
+		return PTR_ERR(tcpci->regmap);
+
+	tcpci->tcpc.init = tcpci_init;
+	tcpci->tcpc.get_vbus = tcpci_get_vbus;
+	tcpci->tcpc.set_vbus = tcpci_set_vbus;
+	tcpci->tcpc.set_cc = tcpci_set_cc;
+	tcpci->tcpc.get_cc = tcpci_get_cc;
+	tcpci->tcpc.set_polarity = tcpci_set_polarity;
+	tcpci->tcpc.set_vconn = tcpci_set_vconn;
+
+	tcpci->tcpc.set_pd_rx = tcpci_set_pd_rx;
+	tcpci->tcpc.set_pd_header = tcpci_set_pd_header;
+	tcpci->tcpc.pd_transmit = tcpci_pd_transmit;
+
+	err = tcpci_parse_config(tcpci);
+	if (err < 0)
+		return err;
+
+	err = devm_request_threaded_irq(tcpci->dev, client->irq, NULL,
+					tcpci_irq, IRQF_TRIGGER_FALLING,
+					dev_name(tcpci->dev), tcpci);
+	if (err < 0)
+		return err;
+
+	tcpci->port = tcpm_register_port(tcpci->dev, &tcpci->tcpc);
+	if (IS_ERR(tcpci->port))
+		return PTR_ERR(tcpci->port);
+
+	return 0;
+}
+
+static int tcpci_remove(struct i2c_client *client)
+{
+	struct tcpci *tcpci = i2c_get_clientdata(client);
+
+	tcpm_unregister_port(tcpci->port);
+
+	return 0;
+}
+
+static const struct i2c_device_id tcpci_id[] = {
+	{ "tcpci", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, tcpci_id);
+
+#ifdef CONFIG_OF
+static const struct of_device_id tcpci_of_match[] = {
+	{ .compatible = "usb,tcpci", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, tcpci_of_match);
+#endif
+
+static struct i2c_driver tcpci_i2c_driver = {
+	.driver = {
+		.name = "tcpci",
+		.of_match_table = of_match_ptr(tcpci_of_match),
+	},
+	.probe = tcpci_probe,
+	.remove = tcpci_remove,
+	.id_table = tcpci_id,
+};
+module_i2c_driver(tcpci_i2c_driver);
+
+MODULE_DESCRIPTION("USB Type-C Port Controller Interface driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/typec/tcpci.h b/drivers/usb/typec/tcpci.h
new file mode 100644
index 000000000000..0c996a97cef1
--- /dev/null
+++ b/drivers/usb/typec/tcpci.h
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2015-2016 Google, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * USB Type-C Port Controller Interface.
+ */
+
+#ifndef __LINUX_USB_TCPCI_H
+#define __LINUX_USB_TCPCI_H
+
+#define TCPC_VENDOR_ID			0x0
+#define TCPC_PRODUCT_ID			0x2
+#define TCPC_BCD_DEV			0x4
+#define TCPC_TC_REV			0x6
+#define TCPC_PD_REV			0x8
+#define TCPC_PD_INT_REV			0xa
+
+#define TCPC_ALERT			0x10
+#define TCPC_ALERT_VBUS_DISCNCT		BIT(11)
+#define TCPC_ALERT_RX_BUF_OVF		BIT(10)
+#define TCPC_ALERT_FAULT		BIT(9)
+#define TCPC_ALERT_V_ALARM_LO		BIT(8)
+#define TCPC_ALERT_V_ALARM_HI		BIT(7)
+#define TCPC_ALERT_TX_SUCCESS		BIT(6)
+#define TCPC_ALERT_TX_DISCARDED		BIT(5)
+#define TCPC_ALERT_TX_FAILED		BIT(4)
+#define TCPC_ALERT_RX_HARD_RST		BIT(3)
+#define TCPC_ALERT_RX_STATUS		BIT(2)
+#define TCPC_ALERT_POWER_STATUS		BIT(1)
+#define TCPC_ALERT_CC_STATUS		BIT(0)
+
+#define TCPC_ALERT_MASK			0x12
+#define TCPC_POWER_STATUS_MASK		0x14
+#define TCPC_FAULT_STATUS_MASK		0x15
+#define TCPC_CONFIG_STD_OUTPUT		0x18
+
+#define TCPC_TCPC_CTRL			0x19
+#define TCPC_TCPC_CTRL_ORIENTATION	BIT(0)
+
+#define TCPC_ROLE_CTRL			0x1a
+#define TCPC_ROLE_CTRL_DRP		BIT(6)
+#define TCPC_ROLE_CTRL_RP_VAL_SHIFT	4
+#define TCPC_ROLE_CTRL_RP_VAL_MASK	0x3
+#define TCPC_ROLE_CTRL_RP_VAL_DEF	0x0
+#define TCPC_ROLE_CTRL_RP_VAL_1_5	0x1
+#define TCPC_ROLE_CTRL_RP_VAL_3_0	0x2
+#define TCPC_ROLE_CTRL_CC2_SHIFT	2
+#define TCPC_ROLE_CTRL_CC2_MASK		0x3
+#define TCPC_ROLE_CTRL_CC1_SHIFT	0
+#define TCPC_ROLE_CTRL_CC1_MASK		0x3
+#define TCPC_ROLE_CTRL_CC_RA		0x0
+#define TCPC_ROLE_CTRL_CC_RP		0x1
+#define TCPC_ROLE_CTRL_CC_RD		0x2
+#define TCPC_ROLE_CTRL_CC_OPEN		0x3
+
+#define TCPC_FAULT_CTRL			0x1b
+
+#define TCPC_POWER_CTRL			0x1c
+#define TCPC_POWER_CTRL_VCONN_ENABLE	BIT(0)
+
+#define TCPC_CC_STATUS			0x1d
+#define TCPC_CC_STATUS_TERM		BIT(4)
+#define TCPC_CC_STATUS_CC2_SHIFT	2
+#define TCPC_CC_STATUS_CC2_MASK		0x3
+#define TCPC_CC_STATUS_CC1_SHIFT	0
+#define TCPC_CC_STATUS_CC1_MASK		0x3
+
+#define TCPC_POWER_STATUS		0x1e
+#define TCPC_POWER_STATUS_UNINIT	BIT(6)
+#define TCPC_POWER_STATUS_VBUS_DET	BIT(3)
+#define TCPC_POWER_STATUS_VBUS_PRES	BIT(2)
+
+#define TCPC_FAULT_STATUS		0x1f
+
+#define TCPC_COMMAND			0x23
+#define TCPC_CMD_WAKE_I2C		0x11
+#define TCPC_CMD_DISABLE_VBUS_DETECT	0x22
+#define TCPC_CMD_ENABLE_VBUS_DETECT	0x33
+#define TCPC_CMD_DISABLE_SINK_VBUS	0x44
+#define TCPC_CMD_SINK_VBUS		0x55
+#define TCPC_CMD_DISABLE_SRC_VBUS	0x66
+#define TCPC_CMD_SRC_VBUS_DEFAULT	0x77
+#define TCPC_CMD_SRC_VBUS_HIGH		0x88
+#define TCPC_CMD_LOOK4CONNECTION	0x99
+#define TCPC_CMD_RXONEMORE		0xAA
+#define TCPC_CMD_I2C_IDLE		0xFF
+
+#define TCPC_DEV_CAP_1			0x24
+#define TCPC_DEV_CAP_2			0x26
+#define TCPC_STD_INPUT_CAP		0x28
+#define TCPC_STD_OUTPUT_CAP		0x29
+
+#define TCPC_MSG_HDR_INFO		0x2e
+#define TCPC_MSG_HDR_INFO_DATA_ROLE	BIT(3)
+#define TCPC_MSG_HDR_INFO_PWR_ROLE	BIT(0)
+#define TCPC_MSG_HDR_INFO_REV_SHIFT	1
+#define TCPC_MSG_HDR_INFO_REV_MASK	0x3
+
+#define TCPC_RX_DETECT			0x2f
+#define TCPC_RX_DETECT_HARD_RESET	BIT(5)
+#define TCPC_RX_DETECT_SOP		BIT(0)
+
+#define TCPC_RX_BYTE_CNT		0x30
+#define TCPC_RX_BUF_FRAME_TYPE		0x31
+#define TCPC_RX_HDR			0x32
+#define TCPC_RX_DATA			0x34 /* through 0x4f */
+
+#define TCPC_TRANSMIT			0x50
+#define TCPC_TRANSMIT_RETRY_SHIFT	4
+#define TCPC_TRANSMIT_RETRY_MASK	0x3
+#define TCPC_TRANSMIT_TYPE_SHIFT	0
+#define TCPC_TRANSMIT_TYPE_MASK		0x7
+
+#define TCPC_TX_BYTE_CNT		0x51
+#define TCPC_TX_HDR			0x52
+#define TCPC_TX_DATA			0x54 /* through 0x6f */
+
+#define TCPC_VBUS_VOLTAGE			0x70
+#define TCPC_VBUS_SINK_DISCONNECT_THRESH	0x72
+#define TCPC_VBUS_STOP_DISCHARGE_THRESH		0x74
+#define TCPC_VBUS_VOLTAGE_ALARM_HI_CFG		0x76
+#define TCPC_VBUS_VOLTAGE_ALARM_LO_CFG		0x78
+
+#endif /* __LINUX_USB_TCPCI_H */
-- 
2.8.0.rc3.226.g39d4020

^ permalink raw reply related	[flat|nested] 24+ messages in thread

* Re: [RFC PATCH v3 0/2] Type-C Port Manager
  2016-08-23 21:10 [RFC PATCH v3 0/2] Type-C Port Manager Guenter Roeck
  2016-08-23 21:10 ` [RFC PATCH v3 1/2] usb: typec: USB Type-C Port Manager (tcpm) Guenter Roeck
  2016-08-23 21:10 ` [RFC PATCH v3 2/2] usb: typec: Type-C Port Controller Interface driver (tcpci) Guenter Roeck
@ 2016-08-24 12:52 ` Heikki Krogerus
  2016-08-24 17:56   ` Guenter Roeck
  2 siblings, 1 reply; 24+ messages in thread
From: Heikki Krogerus @ 2016-08-24 12:52 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Felipe Balbi, Chandra Sekhar Anagani, Bruce Ashfield, Bin Gao,
	Pranav Tipnis, linux-kernel, linux-usb

Hi Guenter,

On Tue, Aug 23, 2016 at 02:10:49PM -0700, Guenter Roeck wrote:
> The following series of patches implements a USB Type-C Port Manager
> using the pending USB Type-C class code as basis. The code is still WIP,
> but I think it is important to get feedback from the community at this point.
> 
> There are two patches in the series. The first patch implements the Type-C
> Port Manager state machine. The forth patch is an interface between the
> Type-C Port Manager and a TCPCI (Type-C Port Controller Interface) compliant
> USB Type-C Port Controller.
> 
> Patch 2/2 (the interface to a TCPCI compliant chip) is currently untested
> since I don't have the necessary hardware available. The port manager code
> was tested connecting to an Embedded Controller on a Chromebook, bypassing
> the Port Manager implementation in the EC.
> 
> Both Source and Sink operation was tested with various Type-C chargers, docks,
> and connectors. Alternate mode support is partially implemented (Alternate mode
> support is requested from the partner), but alternate modes are not actually
> selected. Implementing this will require more thought, since the actual
> alternate mode support has to be implemented elsewhere, such as in a dedicated
> Phy driver. It should be possible to implement the interface between phy driver
> and Type-C Port Controller driver using extcon, but I have not further explored
> the possibilities, and other options might be possible and/or better.
> 
> v3:
> - Improve TCPM state machine resiliency if there are spurious CC line changes
>   while the state machine is in a transient change (waiting for a timeout)
> - Update current limit after CC voltage level changes on a port which is not
>   PD capable.
> - Applies to v6 of "USB Type-C Connector class" patch series.
> 
> v2:
> - Class code no longer uses locking, so the patch to remove it is no longer
>   necessary.
> - tcpm: Only update polarity if setting it was successful
>   If setting the CC line polarity in the driver was not successful,
>   don't update the internal polarity state.
> - tcpm: All PD messages are little endian; convert to and from CPU endianness.
> - tcpm: Avoid comparisons against NULL.
> - tcpm: Use u8/u16/u32 instead of uint8_t/uint16_t/uint32_t consistently.
> - tcpm/tcpc: Callbacks into tcpm need to be lockless to avoid timing problems
>   in low level drivers.
> - tcpm/tcpc: Simplify callbacks; tcpm can request the current state of cc/vbus
>   when it is ready to use it.
> - Applies to v5 of "USB Type-C Connector class" patch series.

I have not reviewed these yet completely, but so far the series looks
really good. Nice job!

There are a lot of things that look generic and not tied to TCPCs, for
example the USB PD message handling. Couldn't those already be
separated from TCPM code or made otherwise available for non-TCPC
PHYs? The struct tcpc_dev looks to me like it would be usable for also
non-TCPC PHYs as is and enough for most cases.


Thanks,

-- 
heikki

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [RFC PATCH v3 0/2] Type-C Port Manager
  2016-08-24 12:52 ` [RFC PATCH v3 0/2] Type-C Port Manager Heikki Krogerus
@ 2016-08-24 17:56   ` Guenter Roeck
  0 siblings, 0 replies; 24+ messages in thread
From: Guenter Roeck @ 2016-08-24 17:56 UTC (permalink / raw)
  To: Heikki Krogerus
  Cc: Guenter Roeck, Felipe Balbi, Chandra Sekhar Anagani,
	Bruce Ashfield, Bin Gao, Pranav Tipnis, linux-kernel, linux-usb

Hello Heikki,

On Wed, Aug 24, 2016 at 5:52 AM, Heikki Krogerus
<heikki.krogerus@linux.intel.com> wrote:
> Hi Guenter,
>
> On Tue, Aug 23, 2016 at 02:10:49PM -0700, Guenter Roeck wrote:
>> The following series of patches implements a USB Type-C Port Manager
>> using the pending USB Type-C class code as basis. The code is still WIP,
>> but I think it is important to get feedback from the community at this point.
>>
>> There are two patches in the series. The first patch implements the Type-C
>> Port Manager state machine. The forth patch is an interface between the
>> Type-C Port Manager and a TCPCI (Type-C Port Controller Interface) compliant
>> USB Type-C Port Controller.
>>
>> Patch 2/2 (the interface to a TCPCI compliant chip) is currently untested
>> since I don't have the necessary hardware available. The port manager code
>> was tested connecting to an Embedded Controller on a Chromebook, bypassing
>> the Port Manager implementation in the EC.
>>
>> Both Source and Sink operation was tested with various Type-C chargers, docks,
>> and connectors. Alternate mode support is partially implemented (Alternate mode
>> support is requested from the partner), but alternate modes are not actually
>> selected. Implementing this will require more thought, since the actual
>> alternate mode support has to be implemented elsewhere, such as in a dedicated
>> Phy driver. It should be possible to implement the interface between phy driver
>> and Type-C Port Controller driver using extcon, but I have not further explored
>> the possibilities, and other options might be possible and/or better.
>>
>> v3:
>> - Improve TCPM state machine resiliency if there are spurious CC line changes
>>   while the state machine is in a transient change (waiting for a timeout)
>> - Update current limit after CC voltage level changes on a port which is not
>>   PD capable.
>> - Applies to v6 of "USB Type-C Connector class" patch series.
>>
>> v2:
>> - Class code no longer uses locking, so the patch to remove it is no longer
>>   necessary.
>> - tcpm: Only update polarity if setting it was successful
>>   If setting the CC line polarity in the driver was not successful,
>>   don't update the internal polarity state.
>> - tcpm: All PD messages are little endian; convert to and from CPU endianness.
>> - tcpm: Avoid comparisons against NULL.
>> - tcpm: Use u8/u16/u32 instead of uint8_t/uint16_t/uint32_t consistently.
>> - tcpm/tcpc: Callbacks into tcpm need to be lockless to avoid timing problems
>>   in low level drivers.
>> - tcpm/tcpc: Simplify callbacks; tcpm can request the current state of cc/vbus
>>   when it is ready to use it.
>> - Applies to v5 of "USB Type-C Connector class" patch series.
>
> I have not reviewed these yet completely, but so far the series looks
> really good. Nice job!
>
> There are a lot of things that look generic and not tied to TCPCs, for
> example the USB PD message handling. Couldn't those already be
> separated from TCPM code or made otherwise available for non-TCPC
> PHYs? The struct tcpc_dev looks to me like it would be usable for also
> non-TCPC PHYs as is and enough for most cases.
>
Makes sense, if not for anything else to reduce file size. I'll see if
I can separate it.

Thanks,
Guenter

^ permalink raw reply	[flat|nested] 24+ messages in thread

* RE: [RFC PATCH v3 1/2] usb: typec: USB Type-C Port Manager (tcpm)
  2016-08-23 21:10 ` [RFC PATCH v3 1/2] usb: typec: USB Type-C Port Manager (tcpm) Guenter Roeck
@ 2016-09-10  0:26   ` Jun Li
  2016-09-10  2:23     ` Guenter Roeck
  2016-09-29 14:35   ` Jun Li
                     ` (2 subsequent siblings)
  3 siblings, 1 reply; 24+ messages in thread
From: Jun Li @ 2016-09-10  0:26 UTC (permalink / raw)
  To: Guenter Roeck, Felipe Balbi
  Cc: Chandra Sekhar Anagani, Bruce Ashfield, Bin Gao, Pranav Tipnis,
	Heikki Krogerus, linux-kernel, linux-usb

Hi Guenter,

> -----Original Message-----
> From: linux-usb-owner@vger.kernel.org [mailto:linux-usb-
> owner@vger.kernel.org] On Behalf Of Guenter Roeck
> Sent: Wednesday, August 24, 2016 5:11 AM
> To: Felipe Balbi <felipe.balbi@linux.intel.com>
> Cc: Chandra Sekhar Anagani <chandra.sekhar.anagani@intel.com>; Bruce
> Ashfield <bruce.ashfield@windriver.com>; Bin Gao <bin.gao@intel.com>;
> Pranav Tipnis <pranav.tipnis@intel.com>; Heikki Krogerus
> <heikki.krogerus@linux.intel.com>; linux-kernel@vger.kernel.org; linux-
> usb@vger.kernel.org; Guenter Roeck <groeck@chromium.org>
> Subject: [RFC PATCH v3 1/2] usb: typec: USB Type-C Port Manager (tcpm)
> 
> This driver implements the USB Type-C Power Delivery state machine
> for both source and sink ports. Alternate mode support is not
> fully implemented.
> 
> The driver attaches to the USB Type-C class code implemented in
> the following patches.
> 
> 	usb: typec: add driver for Intel Whiskey Cove PMIC USB Type-C PHY
> 	usb: USB Type-C connector class
> 
> This driver only implements the state machine. Lower level drivers are
> responsible for
> - Reporting VBUS status and activating VBUS
> - Setting CC lines and providing CC line status
> - Setting line polarity
> - Activating and deactivating VCONN
> - Setting the current limit
> - Activating and deactivating PD message transfers
> - Sending and receiving PD messages
> 
> The driver provides both a functional API as well as callbacks for
> lower level drivers.
> 
> Signed-off-by: Guenter Roeck <groeck@chromium.org>
> ---

A specific question, if power sink wants to request a new power level
after SNK_READY, how to handle it with this tcpm?

Thanks
Li Jun

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [RFC PATCH v3 1/2] usb: typec: USB Type-C Port Manager (tcpm)
  2016-09-10  0:26   ` Jun Li
@ 2016-09-10  2:23     ` Guenter Roeck
  2016-09-12  2:16       ` Jun Li
  0 siblings, 1 reply; 24+ messages in thread
From: Guenter Roeck @ 2016-09-10  2:23 UTC (permalink / raw)
  To: Jun Li
  Cc: Guenter Roeck, Felipe Balbi, Chandra Sekhar Anagani,
	Bruce Ashfield, Bin Gao, Pranav Tipnis, Heikki Krogerus,
	linux-kernel, linux-usb

On Fri, Sep 9, 2016 at 5:26 PM, Jun Li <jun.li@nxp.com> wrote:
> Hi Guenter,
>
>> -----Original Message-----
>> From: linux-usb-owner@vger.kernel.org [mailto:linux-usb-
>> owner@vger.kernel.org] On Behalf Of Guenter Roeck
>> Sent: Wednesday, August 24, 2016 5:11 AM
>> To: Felipe Balbi <felipe.balbi@linux.intel.com>
>> Cc: Chandra Sekhar Anagani <chandra.sekhar.anagani@intel.com>; Bruce
>> Ashfield <bruce.ashfield@windriver.com>; Bin Gao <bin.gao@intel.com>;
>> Pranav Tipnis <pranav.tipnis@intel.com>; Heikki Krogerus
>> <heikki.krogerus@linux.intel.com>; linux-kernel@vger.kernel.org; linux-
>> usb@vger.kernel.org; Guenter Roeck <groeck@chromium.org>
>> Subject: [RFC PATCH v3 1/2] usb: typec: USB Type-C Port Manager (tcpm)
>>
>> This driver implements the USB Type-C Power Delivery state machine
>> for both source and sink ports. Alternate mode support is not
>> fully implemented.
>>
>> The driver attaches to the USB Type-C class code implemented in
>> the following patches.
>>
>>       usb: typec: add driver for Intel Whiskey Cove PMIC USB Type-C PHY
>>       usb: USB Type-C connector class
>>
>> This driver only implements the state machine. Lower level drivers are
>> responsible for
>> - Reporting VBUS status and activating VBUS
>> - Setting CC lines and providing CC line status
>> - Setting line polarity
>> - Activating and deactivating VCONN
>> - Setting the current limit
>> - Activating and deactivating PD message transfers
>> - Sending and receiving PD messages
>>
>> The driver provides both a functional API as well as callbacks for
>> lower level drivers.
>>
>> Signed-off-by: Guenter Roeck <groeck@chromium.org>
>> ---
>
> A specific question, if power sink wants to request a new power level
> after SNK_READY, how to handle it with this tcpm?
>

So far I have considered the required power level to be static, based
on our curent implementations. That should be easy to change, though,
with an additional API function, to be called from a low level driver.
Do you have that requirement, and would such a function meet your
needs ?

Thanks,
Guenter

^ permalink raw reply	[flat|nested] 24+ messages in thread

* RE: [RFC PATCH v3 1/2] usb: typec: USB Type-C Port Manager (tcpm)
  2016-09-10  2:23     ` Guenter Roeck
@ 2016-09-12  2:16       ` Jun Li
  2016-09-12  2:23         ` Guenter Roeck
  0 siblings, 1 reply; 24+ messages in thread
From: Jun Li @ 2016-09-12  2:16 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Guenter Roeck, Felipe Balbi, Chandra Sekhar Anagani,
	Bruce Ashfield, Bin Gao, Pranav Tipnis, Heikki Krogerus,
	linux-kernel, linux-usb

Hi Guenter

> -----Original Message-----
> From: Guenter Roeck [mailto:groeck@google.com]
> Sent: Saturday, September 10, 2016 10:23 AM
> To: Jun Li <jun.li@nxp.com>
> Cc: Guenter Roeck <groeck@chromium.org>; Felipe Balbi
> <felipe.balbi@linux.intel.com>; Chandra Sekhar Anagani
> <chandra.sekhar.anagani@intel.com>; Bruce Ashfield
> <bruce.ashfield@windriver.com>; Bin Gao <bin.gao@intel.com>; Pranav Tipnis
> <pranav.tipnis@intel.com>; Heikki Krogerus
> <heikki.krogerus@linux.intel.com>; linux-kernel@vger.kernel.org; linux-
> usb@vger.kernel.org
> Subject: Re: [RFC PATCH v3 1/2] usb: typec: USB Type-C Port Manager (tcpm)
> 
> On Fri, Sep 9, 2016 at 5:26 PM, Jun Li <jun.li@nxp.com> wrote:
> > Hi Guenter,
> >
> >> -----Original Message-----
> >> From: linux-usb-owner@vger.kernel.org [mailto:linux-usb-
> >> owner@vger.kernel.org] On Behalf Of Guenter Roeck
> >> Sent: Wednesday, August 24, 2016 5:11 AM
> >> To: Felipe Balbi <felipe.balbi@linux.intel.com>
> >> Cc: Chandra Sekhar Anagani <chandra.sekhar.anagani@intel.com>; Bruce
> >> Ashfield <bruce.ashfield@windriver.com>; Bin Gao <bin.gao@intel.com>;
> >> Pranav Tipnis <pranav.tipnis@intel.com>; Heikki Krogerus
> >> <heikki.krogerus@linux.intel.com>; linux-kernel@vger.kernel.org;
> >> linux- usb@vger.kernel.org; Guenter Roeck <groeck@chromium.org>
> >> Subject: [RFC PATCH v3 1/2] usb: typec: USB Type-C Port Manager
> >> (tcpm)
> >>
> >> This driver implements the USB Type-C Power Delivery state machine
> >> for both source and sink ports. Alternate mode support is not fully
> >> implemented.
> >>
> >> The driver attaches to the USB Type-C class code implemented in the
> >> following patches.
> >>
> >>       usb: typec: add driver for Intel Whiskey Cove PMIC USB Type-C PHY
> >>       usb: USB Type-C connector class
> >>
> >> This driver only implements the state machine. Lower level drivers
> >> are responsible for
> >> - Reporting VBUS status and activating VBUS
> >> - Setting CC lines and providing CC line status
> >> - Setting line polarity
> >> - Activating and deactivating VCONN
> >> - Setting the current limit
> >> - Activating and deactivating PD message transfers
> >> - Sending and receiving PD messages
> >>
> >> The driver provides both a functional API as well as callbacks for
> >> lower level drivers.
> >>
> >> Signed-off-by: Guenter Roeck <groeck@chromium.org>
> >> ---
> >
> > A specific question, if power sink wants to request a new power level
> > after SNK_READY, how to handle it with this tcpm?
> >
> 
> So far I have considered the required power level to be static, based on
> our curent implementations. That should be easy to change, though, with an
> additional API function, to be called from a low level driver.
> Do you have that requirement, and would such a function meet your needs ?
> 

So you are going to make port->tcpc->config to be dynamic to meet my need?

Li Jun
 
> Thanks,
> Guenter

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [RFC PATCH v3 1/2] usb: typec: USB Type-C Port Manager (tcpm)
  2016-09-12  2:16       ` Jun Li
@ 2016-09-12  2:23         ` Guenter Roeck
  2016-09-12  2:41           ` Jun Li
  0 siblings, 1 reply; 24+ messages in thread
From: Guenter Roeck @ 2016-09-12  2:23 UTC (permalink / raw)
  To: Jun Li
  Cc: Guenter Roeck, Felipe Balbi, Chandra Sekhar Anagani,
	Bruce Ashfield, Bin Gao, Pranav Tipnis, Heikki Krogerus,
	linux-kernel, linux-usb

On Sun, Sep 11, 2016 at 7:16 PM, Jun Li <jun.li@nxp.com> wrote:
> Hi Guenter
>
>> -----Original Message-----
>> From: Guenter Roeck [mailto:groeck@google.com]
>> Sent: Saturday, September 10, 2016 10:23 AM
>> To: Jun Li <jun.li@nxp.com>
>> Cc: Guenter Roeck <groeck@chromium.org>; Felipe Balbi
>> <felipe.balbi@linux.intel.com>; Chandra Sekhar Anagani
>> <chandra.sekhar.anagani@intel.com>; Bruce Ashfield
>> <bruce.ashfield@windriver.com>; Bin Gao <bin.gao@intel.com>; Pranav Tipnis
>> <pranav.tipnis@intel.com>; Heikki Krogerus
>> <heikki.krogerus@linux.intel.com>; linux-kernel@vger.kernel.org; linux-
>> usb@vger.kernel.org
>> Subject: Re: [RFC PATCH v3 1/2] usb: typec: USB Type-C Port Manager (tcpm)
>>
>> On Fri, Sep 9, 2016 at 5:26 PM, Jun Li <jun.li@nxp.com> wrote:
>> > Hi Guenter,
>> >
>> >> -----Original Message-----
>> >> From: linux-usb-owner@vger.kernel.org [mailto:linux-usb-
>> >> owner@vger.kernel.org] On Behalf Of Guenter Roeck
>> >> Sent: Wednesday, August 24, 2016 5:11 AM
>> >> To: Felipe Balbi <felipe.balbi@linux.intel.com>
>> >> Cc: Chandra Sekhar Anagani <chandra.sekhar.anagani@intel.com>; Bruce
>> >> Ashfield <bruce.ashfield@windriver.com>; Bin Gao <bin.gao@intel.com>;
>> >> Pranav Tipnis <pranav.tipnis@intel.com>; Heikki Krogerus
>> >> <heikki.krogerus@linux.intel.com>; linux-kernel@vger.kernel.org;
>> >> linux- usb@vger.kernel.org; Guenter Roeck <groeck@chromium.org>
>> >> Subject: [RFC PATCH v3 1/2] usb: typec: USB Type-C Port Manager
>> >> (tcpm)
>> >>
>> >> This driver implements the USB Type-C Power Delivery state machine
>> >> for both source and sink ports. Alternate mode support is not fully
>> >> implemented.
>> >>
>> >> The driver attaches to the USB Type-C class code implemented in the
>> >> following patches.
>> >>
>> >>       usb: typec: add driver for Intel Whiskey Cove PMIC USB Type-C PHY
>> >>       usb: USB Type-C connector class
>> >>
>> >> This driver only implements the state machine. Lower level drivers
>> >> are responsible for
>> >> - Reporting VBUS status and activating VBUS
>> >> - Setting CC lines and providing CC line status
>> >> - Setting line polarity
>> >> - Activating and deactivating VCONN
>> >> - Setting the current limit
>> >> - Activating and deactivating PD message transfers
>> >> - Sending and receiving PD messages
>> >>
>> >> The driver provides both a functional API as well as callbacks for
>> >> lower level drivers.
>> >>
>> >> Signed-off-by: Guenter Roeck <groeck@chromium.org>
>> >> ---
>> >
>> > A specific question, if power sink wants to request a new power level
>> > after SNK_READY, how to handle it with this tcpm?
>> >
>>
>> So far I have considered the required power level to be static, based on
>> our curent implementations. That should be easy to change, though, with an
>> additional API function, to be called from a low level driver.
>> Do you have that requirement, and would such a function meet your needs ?
>>
>
> So you are going to make port->tcpc->config to be dynamic to meet my need?
>
What would that help ? How would tcpm get informed that the power
requirements changed without an API function telling it that power
requirements changed ?

Guenter

^ permalink raw reply	[flat|nested] 24+ messages in thread

* RE: [RFC PATCH v3 1/2] usb: typec: USB Type-C Port Manager (tcpm)
  2016-09-12  2:23         ` Guenter Roeck
@ 2016-09-12  2:41           ` Jun Li
  0 siblings, 0 replies; 24+ messages in thread
From: Jun Li @ 2016-09-12  2:41 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Guenter Roeck, Felipe Balbi, Chandra Sekhar Anagani,
	Bruce Ashfield, Bin Gao, Pranav Tipnis, Heikki Krogerus,
	linux-kernel, linux-usb



> -----Original Message-----
> From: Guenter Roeck [mailto:groeck@google.com]
> Sent: Monday, September 12, 2016 10:24 AM
> To: Jun Li <jun.li@nxp.com>
> Cc: Guenter Roeck <groeck@chromium.org>; Felipe Balbi
> <felipe.balbi@linux.intel.com>; Chandra Sekhar Anagani
> <chandra.sekhar.anagani@intel.com>; Bruce Ashfield
> <bruce.ashfield@windriver.com>; Bin Gao <bin.gao@intel.com>; Pranav Tipnis
> <pranav.tipnis@intel.com>; Heikki Krogerus
> <heikki.krogerus@linux.intel.com>; linux-kernel@vger.kernel.org; linux-
> usb@vger.kernel.org
> Subject: Re: [RFC PATCH v3 1/2] usb: typec: USB Type-C Port Manager (tcpm)
> 
> On Sun, Sep 11, 2016 at 7:16 PM, Jun Li <jun.li@nxp.com> wrote:
> > Hi Guenter
> >
> >> -----Original Message-----
> >> From: Guenter Roeck [mailto:groeck@google.com]
> >> Sent: Saturday, September 10, 2016 10:23 AM
> >> To: Jun Li <jun.li@nxp.com>
> >> Cc: Guenter Roeck <groeck@chromium.org>; Felipe Balbi
> >> <felipe.balbi@linux.intel.com>; Chandra Sekhar Anagani
> >> <chandra.sekhar.anagani@intel.com>; Bruce Ashfield
> >> <bruce.ashfield@windriver.com>; Bin Gao <bin.gao@intel.com>; Pranav
> >> Tipnis <pranav.tipnis@intel.com>; Heikki Krogerus
> >> <heikki.krogerus@linux.intel.com>; linux-kernel@vger.kernel.org;
> >> linux- usb@vger.kernel.org
> >> Subject: Re: [RFC PATCH v3 1/2] usb: typec: USB Type-C Port Manager
> >> (tcpm)
> >>
> >> On Fri, Sep 9, 2016 at 5:26 PM, Jun Li <jun.li@nxp.com> wrote:
> >> > Hi Guenter,
> >> >
> >> >> -----Original Message-----
> >> >> From: linux-usb-owner@vger.kernel.org [mailto:linux-usb-
> >> >> owner@vger.kernel.org] On Behalf Of Guenter Roeck
> >> >> Sent: Wednesday, August 24, 2016 5:11 AM
> >> >> To: Felipe Balbi <felipe.balbi@linux.intel.com>
> >> >> Cc: Chandra Sekhar Anagani <chandra.sekhar.anagani@intel.com>;
> >> >> Bruce Ashfield <bruce.ashfield@windriver.com>; Bin Gao
> >> >> <bin.gao@intel.com>; Pranav Tipnis <pranav.tipnis@intel.com>;
> >> >> Heikki Krogerus <heikki.krogerus@linux.intel.com>;
> >> >> linux-kernel@vger.kernel.org;
> >> >> linux- usb@vger.kernel.org; Guenter Roeck <groeck@chromium.org>
> >> >> Subject: [RFC PATCH v3 1/2] usb: typec: USB Type-C Port Manager
> >> >> (tcpm)
> >> >>
> >> >> This driver implements the USB Type-C Power Delivery state machine
> >> >> for both source and sink ports. Alternate mode support is not
> >> >> fully implemented.
> >> >>
> >> >> The driver attaches to the USB Type-C class code implemented in
> >> >> the following patches.
> >> >>
> >> >>       usb: typec: add driver for Intel Whiskey Cove PMIC USB Type-C
> PHY
> >> >>       usb: USB Type-C connector class
> >> >>
> >> >> This driver only implements the state machine. Lower level drivers
> >> >> are responsible for
> >> >> - Reporting VBUS status and activating VBUS
> >> >> - Setting CC lines and providing CC line status
> >> >> - Setting line polarity
> >> >> - Activating and deactivating VCONN
> >> >> - Setting the current limit
> >> >> - Activating and deactivating PD message transfers
> >> >> - Sending and receiving PD messages
> >> >>
> >> >> The driver provides both a functional API as well as callbacks for
> >> >> lower level drivers.
> >> >>
> >> >> Signed-off-by: Guenter Roeck <groeck@chromium.org>
> >> >> ---
> >> >
> >> > A specific question, if power sink wants to request a new power
> >> > level after SNK_READY, how to handle it with this tcpm?
> >> >
> >>
> >> So far I have considered the required power level to be static, based
> >> on our curent implementations. That should be easy to change, though,
> >> with an additional API function, to be called from a low level driver.
> >> Do you have that requirement, and would such a function meet your
> needs ?
> >>
> >
> > So you are going to make port->tcpc->config to be dynamic to meet my
> need?
> >
> What would that help ? How would tcpm get informed that the power
> requirements changed without an API function telling it that power
> requirements changed ?

Of cos I agree an additional API is required, I am just wondering how
that API will be look like, as current request build is according to
port->tcpc->config.

Li Jun	
> 
> Guenter

^ permalink raw reply	[flat|nested] 24+ messages in thread

* RE: [RFC PATCH v3 1/2] usb: typec: USB Type-C Port Manager (tcpm)
  2016-08-23 21:10 ` [RFC PATCH v3 1/2] usb: typec: USB Type-C Port Manager (tcpm) Guenter Roeck
  2016-09-10  0:26   ` Jun Li
@ 2016-09-29 14:35   ` Jun Li
  2016-09-29 16:36     ` Guenter Roeck
  2016-09-30  6:37   ` Jun Li
  2016-10-03 14:04   ` Heikki Krogerus
  3 siblings, 1 reply; 24+ messages in thread
From: Jun Li @ 2016-09-29 14:35 UTC (permalink / raw)
  To: Guenter Roeck, Felipe Balbi
  Cc: Chandra Sekhar Anagani, Bruce Ashfield, Bin Gao, Pranav Tipnis,
	Heikki Krogerus, linux-kernel, linux-usb

Hi Guenter,

> -----Original Message-----
> From: linux-usb-owner@vger.kernel.org [mailto:linux-usb-
> owner@vger.kernel.org] On Behalf Of Guenter Roeck
> Sent: Wednesday, August 24, 2016 5:11 AM
> To: Felipe Balbi <felipe.balbi@linux.intel.com>
> Cc: Chandra Sekhar Anagani <chandra.sekhar.anagani@intel.com>; Bruce
> Ashfield <bruce.ashfield@windriver.com>; Bin Gao <bin.gao@intel.com>;
> Pranav Tipnis <pranav.tipnis@intel.com>; Heikki Krogerus
> <heikki.krogerus@linux.intel.com>; linux-kernel@vger.kernel.org; linux-
> usb@vger.kernel.org; Guenter Roeck <groeck@chromium.org>
> Subject: [RFC PATCH v3 1/2] usb: typec: USB Type-C Port Manager (tcpm)
> 
> This driver implements the USB Type-C Power Delivery state machine
> for both source and sink ports. Alternate mode support is not
> fully implemented.
> 
> The driver attaches to the USB Type-C class code implemented in
> the following patches.
> 
> 	usb: typec: add driver for Intel Whiskey Cove PMIC USB Type-C PHY
> 	usb: USB Type-C connector class
> 
> This driver only implements the state machine. Lower level drivers are
> responsible for
> - Reporting VBUS status and activating VBUS
> - Setting CC lines and providing CC line status
> - Setting line polarity
> - Activating and deactivating VCONN
> - Setting the current limit
> - Activating and deactivating PD message transfers
> - Sending and receiving PD messages
> 
> The driver provides both a functional API as well as callbacks for
> lower level drivers.
> 
> Signed-off-by: Guenter Roeck <groeck@chromium.org>
> ---
> v3:
> - Improve TCPM state machine resiliency if there are spurious CC line
> changes
>   while the state machine is in a transient change (waiting for a timeout)
> - Update current limit after CC voltage level changes on a port which is
> not
>   PD capable.
> 
> v2:
> - Only update polarity if setting it was successful
>   If setting the CC line polarity in the driver was not successful,
>   don't update the internal polarity state.
> - All PD messages are little endian; convert to and from CPU endianness.
> - Avoid comparisons against NULL.
> - Use u8/u16/u32 instead of uint8_t/uint16_t/uint32_t consistently.
> - Callbacks into tcpm need to be lockless to avoid timing problems
>   in low level drivers.
> - Simplify callbacks; tcpm can request the current state of cc/vbus
>   when it is ready to use it.
> 
>  drivers/usb/typec/Kconfig  |    7 +
>  drivers/usb/typec/Makefile |    1 +
>  drivers/usb/typec/tcpm.c   | 3163
> ++++++++++++++++++++++++++++++++++++++++++++
>  drivers/usb/typec/tcpm.h   |  137 ++
>  include/linux/usb/pd.h     |  282 ++++
>  include/linux/usb/pd_bdo.h |   31 +
>  include/linux/usb/pd_vdo.h |  412 ++++++
>  7 files changed, 4033 insertions(+)
>  create mode 100644 drivers/usb/typec/tcpm.c
>  create mode 100644 drivers/usb/typec/tcpm.h
>  create mode 100644 include/linux/usb/pd.h
>  create mode 100644 include/linux/usb/pd_bdo.h
>  create mode 100644 include/linux/usb/pd_vdo.h
> 

...

> +
> +static void run_state_machine(struct tcpm_port *port)
> +{
> +	int ret;
> +
> +	port->enter_state = port->state;
> +	switch (port->state) {
> +	/* SRC states */
> +	case SRC_UNATTACHED:
> +		tcpm_swap_complete(port, -ENOTCONN);
> +		tcpm_src_detach(port);
> +		tcpm_set_cc(port, TYPEC_CC_RP_DEF);
> +		if (port->typec_caps.type == TYPEC_PORT_DRP)
> +			tcpm_set_state(port, SNK_UNATTACHED, PD_T_DRP_SNK);

With this and below, after disconnect, the DRP port state machine will be
in infinite loop of state transition between SRC_UNATTACHED <--> SNK_UNATTACHED,
correct?

Li Jun
...

> +	/* SNK states */
> +	case SNK_UNATTACHED:
> +		tcpm_swap_complete(port, -ENOTCONN);
> +		tcpm_snk_detach(port);
> +		tcpm_set_cc(port, TYPEC_CC_RD);
> +		if (port->typec_caps.type == TYPEC_PORT_DRP)
> +			tcpm_set_state(port, SRC_UNATTACHED, PD_T_DRP_SRC);
> +		break;
> 

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [RFC PATCH v3 1/2] usb: typec: USB Type-C Port Manager (tcpm)
  2016-09-29 14:35   ` Jun Li
@ 2016-09-29 16:36     ` Guenter Roeck
  2016-09-30  6:41       ` Jun Li
  0 siblings, 1 reply; 24+ messages in thread
From: Guenter Roeck @ 2016-09-29 16:36 UTC (permalink / raw)
  To: Jun Li
  Cc: Guenter Roeck, Felipe Balbi, Chandra Sekhar Anagani,
	Bruce Ashfield, Bin Gao, Pranav Tipnis, Heikki Krogerus,
	linux-kernel, linux-usb

On Thu, Sep 29, 2016 at 7:35 AM, Jun Li <jun.li@nxp.com> wrote:
> Hi Guenter,
>
>> -----Original Message-----
>> From: linux-usb-owner@vger.kernel.org [mailto:linux-usb-
>> owner@vger.kernel.org] On Behalf Of Guenter Roeck
>> Sent: Wednesday, August 24, 2016 5:11 AM
>> To: Felipe Balbi <felipe.balbi@linux.intel.com>
>> Cc: Chandra Sekhar Anagani <chandra.sekhar.anagani@intel.com>; Bruce
>> Ashfield <bruce.ashfield@windriver.com>; Bin Gao <bin.gao@intel.com>;
>> Pranav Tipnis <pranav.tipnis@intel.com>; Heikki Krogerus
>> <heikki.krogerus@linux.intel.com>; linux-kernel@vger.kernel.org; linux-
>> usb@vger.kernel.org; Guenter Roeck <groeck@chromium.org>
>> Subject: [RFC PATCH v3 1/2] usb: typec: USB Type-C Port Manager (tcpm)
>>
>> This driver implements the USB Type-C Power Delivery state machine
>> for both source and sink ports. Alternate mode support is not
>> fully implemented.
>>
>> The driver attaches to the USB Type-C class code implemented in
>> the following patches.
>>
>>       usb: typec: add driver for Intel Whiskey Cove PMIC USB Type-C PHY
>>       usb: USB Type-C connector class
>>
>> This driver only implements the state machine. Lower level drivers are
>> responsible for
>> - Reporting VBUS status and activating VBUS
>> - Setting CC lines and providing CC line status
>> - Setting line polarity
>> - Activating and deactivating VCONN
>> - Setting the current limit
>> - Activating and deactivating PD message transfers
>> - Sending and receiving PD messages
>>
>> The driver provides both a functional API as well as callbacks for
>> lower level drivers.
>>
>> Signed-off-by: Guenter Roeck <groeck@chromium.org>
>> ---
>> v3:
>> - Improve TCPM state machine resiliency if there are spurious CC line
>> changes
>>   while the state machine is in a transient change (waiting for a timeout)
>> - Update current limit after CC voltage level changes on a port which is
>> not
>>   PD capable.
>>
>> v2:
>> - Only update polarity if setting it was successful
>>   If setting the CC line polarity in the driver was not successful,
>>   don't update the internal polarity state.
>> - All PD messages are little endian; convert to and from CPU endianness.
>> - Avoid comparisons against NULL.
>> - Use u8/u16/u32 instead of uint8_t/uint16_t/uint32_t consistently.
>> - Callbacks into tcpm need to be lockless to avoid timing problems
>>   in low level drivers.
>> - Simplify callbacks; tcpm can request the current state of cc/vbus
>>   when it is ready to use it.
>>
>>  drivers/usb/typec/Kconfig  |    7 +
>>  drivers/usb/typec/Makefile |    1 +
>>  drivers/usb/typec/tcpm.c   | 3163
>> ++++++++++++++++++++++++++++++++++++++++++++
>>  drivers/usb/typec/tcpm.h   |  137 ++
>>  include/linux/usb/pd.h     |  282 ++++
>>  include/linux/usb/pd_bdo.h |   31 +
>>  include/linux/usb/pd_vdo.h |  412 ++++++
>>  7 files changed, 4033 insertions(+)
>>  create mode 100644 drivers/usb/typec/tcpm.c
>>  create mode 100644 drivers/usb/typec/tcpm.h
>>  create mode 100644 include/linux/usb/pd.h
>>  create mode 100644 include/linux/usb/pd_bdo.h
>>  create mode 100644 include/linux/usb/pd_vdo.h
>>
>
> ...
>
>> +
>> +static void run_state_machine(struct tcpm_port *port)
>> +{
>> +     int ret;
>> +
>> +     port->enter_state = port->state;
>> +     switch (port->state) {
>> +     /* SRC states */
>> +     case SRC_UNATTACHED:
>> +             tcpm_swap_complete(port, -ENOTCONN);
>> +             tcpm_src_detach(port);
>> +             tcpm_set_cc(port, TYPEC_CC_RP_DEF);
>> +             if (port->typec_caps.type == TYPEC_PORT_DRP)
>> +                     tcpm_set_state(port, SNK_UNATTACHED, PD_T_DRP_SNK);
>
> With this and below, after disconnect, the DRP port state machine will be
> in infinite loop of state transition between SRC_UNATTACHED <--> SNK_UNATTACHED,
> correct?
>

Only while disconnected. It tries to alternatively connect as source
and as sink (being configured as DRP). Once a CC line state change is
reported it will transition out. I have a newer version of the patch
(not yet published) which supports DRP toggling by the TCPC. With that
enabled, TCPM does not change states until a CC state change is
reported.

Guenter

> Li Jun
> ...
>
>> +     /* SNK states */
>> +     case SNK_UNATTACHED:
>> +             tcpm_swap_complete(port, -ENOTCONN);
>> +             tcpm_snk_detach(port);
>> +             tcpm_set_cc(port, TYPEC_CC_RD);
>> +             if (port->typec_caps.type == TYPEC_PORT_DRP)
>> +                     tcpm_set_state(port, SRC_UNATTACHED, PD_T_DRP_SRC);
>> +             break;
>>

^ permalink raw reply	[flat|nested] 24+ messages in thread

* RE: [RFC PATCH v3 2/2] usb: typec: Type-C Port Controller Interface driver (tcpci)
  2016-08-23 21:10 ` [RFC PATCH v3 2/2] usb: typec: Type-C Port Controller Interface driver (tcpci) Guenter Roeck
@ 2016-09-30  6:24   ` Jun Li
  2016-09-30 18:45     ` Guenter Roeck
  0 siblings, 1 reply; 24+ messages in thread
From: Jun Li @ 2016-09-30  6:24 UTC (permalink / raw)
  To: Guenter Roeck, Felipe Balbi
  Cc: Chandra Sekhar Anagani, Bruce Ashfield, Bin Gao, Pranav Tipnis,
	Heikki Krogerus, linux-kernel, linux-usb

Hi Guenter,

> -----Original Message-----
> From: linux-usb-owner@vger.kernel.org [mailto:linux-usb-
> owner@vger.kernel.org] On Behalf Of Guenter Roeck
> Sent: Wednesday, August 24, 2016 5:11 AM
> To: Felipe Balbi <felipe.balbi@linux.intel.com>
> Cc: Chandra Sekhar Anagani <chandra.sekhar.anagani@intel.com>; Bruce
> Ashfield <bruce.ashfield@windriver.com>; Bin Gao <bin.gao@intel.com>;
> Pranav Tipnis <pranav.tipnis@intel.com>; Heikki Krogerus
> <heikki.krogerus@linux.intel.com>; linux-kernel@vger.kernel.org; linux-
> usb@vger.kernel.org; Guenter Roeck <groeck@chromium.org>
> Subject: [RFC PATCH v3 2/2] usb: typec: Type-C Port Controller Interface
> driver (tcpci)
> 
> The port controller interface driver interconnects the Type-C Port Manager
> with a Type-C Port Controller Interface (TCPCI) compliant port controller.
> 
> Signed-off-by: Guenter Roeck <groeck@chromium.org>
> ---
> v3:
> - No change
> 
> v2:
> - Adjust to modified callbacks into tcpm code
> 
>  drivers/usb/typec/Kconfig  |   9 +
>  drivers/usb/typec/Makefile |   1 +
>  drivers/usb/typec/tcpci.c  | 487
> +++++++++++++++++++++++++++++++++++++++++++++
>  drivers/usb/typec/tcpci.h  | 133 +++++++++++++
>  4 files changed, 630 insertions(+)
>  create mode 100644 drivers/usb/typec/tcpci.c  create mode 100644
> drivers/usb/typec/tcpci.h
> 
> diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig index
> 113bb1b3589c..a92c9d1a3e00 100644
> --- a/drivers/usb/typec/Kconfig
> +++ b/drivers/usb/typec/Kconfig
> @@ -25,4 +25,13 @@ config TYPEC_TCPM
>  	  The Type-C Port Controller Manager provides a USB PD and USB Type-C
>  	  state machine for use with Type-C Port Controllers.
> 
> +if TYPEC_TCPM
> +
> +config TYPEC_TCPCI
> +	tristate "Type-C Port Controller Interface driver"
> +	help
> +	  Type-C Port Controller driver for TCPCI-compliant controller.
> +
> +endif
> +
>  endmenu
> diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile index
> bbe45721cf52..7dbaf8c3911d 100644
> --- a/drivers/usb/typec/Makefile
> +++ b/drivers/usb/typec/Makefile
> @@ -1,3 +1,4 @@
>  obj-$(CONFIG_TYPEC)		+= typec.o
>  obj-$(CONFIG_TYPEC_WCOVE)	+= typec_wcove.o
>  obj-$(CONFIG_TYPEC_TCPM)	+= tcpm.o
> +obj-$(CONFIG_TYPEC_TCPCI)	+= tcpci.o
> diff --git a/drivers/usb/typec/tcpci.c b/drivers/usb/typec/tcpci.c new
> file mode 100644 index 000000000000..af338218a1f3
> --- /dev/null
> +++ b/drivers/usb/typec/tcpci.c
> @@ -0,0 +1,487 @@
> +/*
> + * Copyright 2015-2016 Google, Inc
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * USB Type-C Port Controller Interface.
> + */
> +
> +#include <linux/delay.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/i2c.h>
> +#include <linux/interrupt.h>
> +#include <linux/regmap.h>
> +#include <linux/usb/pd.h>
> +#include <linux/usb/typec.h>
> +
> +#include "tcpci.h"
> +#include "tcpm.h"
> +
> +#define PD_RETRY_COUNT 3
> +
> +struct tcpci {
> +	struct device *dev;
> +	struct i2c_client *client;
> +
> +	struct tcpm_port *port;
> +
> +	struct regmap *regmap;
> +
> +	bool controls_vbus;
> +
> +	struct tcpc_dev tcpc;
> +};
> +
> +static inline struct tcpci *tcpc_to_tcpci(struct tcpc_dev *tcpc) {
> +	return container_of(tcpc, struct tcpci, tcpc); }
> +
> +static int tcpci_read16(struct tcpci *tcpci, unsigned int reg,
> +			unsigned int *val)
> +{
> +	return regmap_raw_read(tcpci->regmap, reg, val, sizeof(u16)); }
> +
> +static int tcpci_write16(struct tcpci *tcpci, unsigned int reg, u16
> +val) {
> +	return regmap_raw_write(tcpci->regmap, reg, &val, sizeof(u16)); }
> +
> +static int tcpci_set_cc(struct tcpc_dev *tcpc, enum typec_cc_status cc)
> +{
> +	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
> +	unsigned int reg;
> +	int ret;
> +
> +	switch (cc) {
> +	case TYPEC_CC_RA:
> +		reg = (TCPC_ROLE_CTRL_CC_RA << TCPC_ROLE_CTRL_CC1_SHIFT) |
> +			(TCPC_ROLE_CTRL_CC_RA << TCPC_ROLE_CTRL_CC2_SHIFT);
> +		break;
> +	case TYPEC_CC_RD:
> +		reg = (TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC1_SHIFT) |
> +			(TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC2_SHIFT);
> +		break;
> +	case TYPEC_CC_RP_DEF:
> +		reg = (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC1_SHIFT) |
> +			(TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC2_SHIFT) |
> +			(TCPC_ROLE_CTRL_RP_VAL_DEF <<
> +			 TCPC_ROLE_CTRL_RP_VAL_SHIFT);
> +		break;
> +	case TYPEC_CC_RP_1_5:
> +		reg = (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC1_SHIFT) |
> +			(TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC2_SHIFT) |
> +			(TCPC_ROLE_CTRL_RP_VAL_1_5 <<
> +			 TCPC_ROLE_CTRL_RP_VAL_SHIFT);
> +		break;
> +	case TYPEC_CC_RP_3_0:
> +		reg = (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC1_SHIFT) |
> +			(TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC2_SHIFT) |
> +			(TCPC_ROLE_CTRL_RP_VAL_1_5 <<
> +			 TCPC_ROLE_CTRL_RP_VAL_SHIFT);
> +		break;
> +	case TYPEC_CC_OPEN:
> +	default:
> +		reg = (TCPC_ROLE_CTRL_CC_OPEN << TCPC_ROLE_CTRL_CC1_SHIFT) |
> +			(TCPC_ROLE_CTRL_CC_OPEN << TCPC_ROLE_CTRL_CC2_SHIFT);
> +		break;
> +	}
> +
> +	ret = regmap_write(tcpci->regmap, TCPC_ROLE_CTRL, reg);
> +	if (ret < 0)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static enum typec_cc_status tcpci_to_typec_cc(unsigned int cc, bool
> +sink) {
> +	switch (cc) {
> +	case 0x1:
> +		return sink ? TYPEC_CC_RP_DEF : TYPEC_CC_RA;
> +	case 0x2:
> +		return sink ? TYPEC_CC_RP_1_5 : TYPEC_CC_RD;
> +	case 0x3:
> +		if (sink)
> +			return TYPEC_CC_RP_3_0;
> +	case 0x0:
> +	default:
> +		return TYPEC_CC_OPEN;
> +	}
> +}
> +
> +static int tcpci_get_cc(struct tcpc_dev *tcpc,
> +			enum typec_cc_status *cc1, enum typec_cc_status *cc2) {
> +	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
> +	unsigned int reg;
> +	int ret;
> +
> +	ret = regmap_read(tcpci->regmap, TCPC_CC_STATUS, &reg);
> +	if (ret < 0)
> +		return ret;
> +
> +	*cc1 = tcpci_to_typec_cc((reg >> TCPC_CC_STATUS_CC1_SHIFT) &
> +				 TCPC_CC_STATUS_CC1_MASK,
> +				 reg & TCPC_CC_STATUS_TERM);
> +	*cc2 = tcpci_to_typec_cc((reg >> TCPC_CC_STATUS_CC2_SHIFT) &
> +				 TCPC_CC_STATUS_CC2_MASK,
> +				 reg & TCPC_CC_STATUS_TERM);
> +
> +	return 0;
> +}
> +
> +static int tcpci_set_polarity(struct tcpc_dev *tcpc,
> +			      enum typec_cc_polarity polarity) {
> +	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
> +	int ret;
> +
> +	ret = regmap_write(tcpci->regmap, TCPC_TCPC_CTRL,
> +			   (polarity == TYPEC_POLARITY_CC2) ?
> +			   TCPC_TCPC_CTRL_ORIENTATION : 0);
> +	if (ret < 0)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int tcpci_set_vconn(struct tcpc_dev *tcpc, bool enable) {
> +	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
> +	int ret;
> +
> +	ret = regmap_write(tcpci->regmap, TCPC_POWER_CTRL,
> +			   enable ? TCPC_POWER_CTRL_VCONN_ENABLE : 0);
> +	if (ret < 0)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int tcpci_set_pd_header(struct tcpc_dev *tcpc, enum typec_role
> role,
> +			       enum typec_data_role data)
> +{
> +	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
> +	unsigned int reg;
> +	int ret;
> +
> +	reg = PD_REV20 << TCPC_MSG_HDR_INFO_REV_SHIFT;
> +	if (role == TYPEC_SOURCE)
> +		reg |= TCPC_MSG_HDR_INFO_PWR_ROLE;
> +	if (data == TYPEC_HOST)
> +		reg |= TCPC_MSG_HDR_INFO_DATA_ROLE;
> +	ret = regmap_write(tcpci->regmap, TCPC_MSG_HDR_INFO, reg);
> +	if (ret < 0)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int tcpci_set_pd_rx(struct tcpc_dev *tcpc, bool enable) {
> +	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
> +	unsigned int reg = 0;
> +	int ret;
> +
> +	if (enable)
> +		reg = TCPC_RX_DETECT_SOP | TCPC_RX_DETECT_HARD_RESET;
> +	ret = regmap_write(tcpci->regmap, TCPC_RX_DETECT, reg);
> +	if (ret < 0)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int tcpci_get_vbus(struct tcpc_dev *tcpc) {
> +	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
> +	unsigned int reg;
> +	int ret;
> +
> +	ret = regmap_read(tcpci->regmap, TCPC_POWER_STATUS, &reg);
> +	if (ret < 0)
> +		return ret;
> +
> +	return !!(reg & TCPC_POWER_STATUS_VBUS_PRES); }
> +
> +static int tcpci_set_vbus(struct tcpc_dev *tcpc, bool source, bool
> +sink) {
> +	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
> +	int ret;
> +
> +	if (source) {
> +		ret = regmap_write(tcpci->regmap, TCPC_COMMAND,
> +				   TCPC_CMD_SRC_VBUS_DEFAULT);
> +		if (ret < 0)
> +			return ret;
> +	} else {
> +		ret = regmap_write(tcpci->regmap, TCPC_COMMAND,
> +				   TCPC_CMD_DISABLE_SRC_VBUS);
> +		if (ret < 0)
> +			return ret;
> +	}
> +
> +	if (sink) {
> +		ret = regmap_write(tcpci->regmap, TCPC_COMMAND,
> +				   TCPC_CMD_SINK_VBUS);
> +		if (ret < 0)
> +			return ret;
> +	} else {
> +		ret = regmap_write(tcpci->regmap, TCPC_COMMAND,
> +				   TCPC_CMD_DISABLE_SINK_VBUS);
> +		if (ret < 0)
> +			return ret;
> +	}
> +

Per tcpc spec, disable should be firstly done before enable,
so should it be done like this?
if(!source) {};
if(!sink) {};
if(source) {};
if(sink) {};

> +	return 0;
> +}
> +
> +static int tcpci_pd_transmit(struct tcpc_dev *tcpc,
> +			     enum tcpm_transmit_type type,
> +			     const struct pd_message *msg)
> +{
> +	struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
> +	unsigned int reg, cnt, header;
> +	int ret;
> +
> +	cnt = msg ? pd_header_cnt(msg->header) * 4 : 0;
> +	ret = regmap_write(tcpci->regmap, TCPC_TX_BYTE_CNT, cnt);

Here miss the 2 bytes' header, so cnt should be cnt += 2;

Tcpc 4.4.13
TRANSMIT_BYTE_COUNT:
The number of bytes the TCPM will write
This is the number of bytes in the TX_BUFFER_DATA_OBJECTS plus two
(for the TX_BUF_HEADER)
 
> +	if (ret < 0)
> +		return ret;
> +
> +	header = msg ? msg->header : 0;
> +	ret = tcpci_write16(tcpci, TCPC_TX_HDR, header);
> +	if (ret < 0)
> +		return ret;
> +
> +	if (cnt > 0) {
> +		ret = regmap_raw_write(tcpci->regmap, TCPC_TX_DATA,
> +				       &msg->payload, cnt);
> +		if (ret < 0)
> +			return ret;
> +	}
> +
> +	reg = (PD_RETRY_COUNT << TCPC_TRANSMIT_RETRY_SHIFT) |
> +		(type << TCPC_TRANSMIT_TYPE_SHIFT);
> +	ret = regmap_write(tcpci->regmap, TCPC_TRANSMIT, reg);
> +	if (ret < 0)
> +		return ret;
> +
> +	return 0;
> +}
> +

...

> +
> +static int tcpci_probe(struct i2c_client *client,
> +		       const struct i2c_device_id *i2c_id) {
> +	struct tcpci *tcpci;
> +	int err;
> +
> +	tcpci = devm_kzalloc(&client->dev, sizeof(*tcpci), GFP_KERNEL);
> +	if (!tcpci)
> +		return -ENOMEM;
> +
> +	tcpci->client = client;
> +	tcpci->dev = &client->dev;
> +	i2c_set_clientdata(client, tcpci);
> +	tcpci->regmap = devm_regmap_init_i2c(client, &tcpci_regmap_config);
> +	if (IS_ERR(tcpci->regmap))
> +		return PTR_ERR(tcpci->regmap);
> +
> +	tcpci->tcpc.init = tcpci_init;
> +	tcpci->tcpc.get_vbus = tcpci_get_vbus;
> +	tcpci->tcpc.set_vbus = tcpci_set_vbus;
> +	tcpci->tcpc.set_cc = tcpci_set_cc;
> +	tcpci->tcpc.get_cc = tcpci_get_cc;
> +	tcpci->tcpc.set_polarity = tcpci_set_polarity;
> +	tcpci->tcpc.set_vconn = tcpci_set_vconn;
> +
> +	tcpci->tcpc.set_pd_rx = tcpci_set_pd_rx;
> +	tcpci->tcpc.set_pd_header = tcpci_set_pd_header;
> +	tcpci->tcpc.pd_transmit = tcpci_pd_transmit;
> +
> +	err = tcpci_parse_config(tcpci);
> +	if (err < 0)
> +		return err;
> +

As the alert mask reg is defaultly to be unmasked after power on,
It's better clear and mask all before request irq.

> +	err = devm_request_threaded_irq(tcpci->dev, client->irq, NULL,
> +					tcpci_irq, IRQF_TRIGGER_FALLING,

IRQF_ONESHOT is required, and do we need the irq trigger to be
IRQF_TRIGGER_LOW?

Li Jun

^ permalink raw reply	[flat|nested] 24+ messages in thread

* RE: [RFC PATCH v3 1/2] usb: typec: USB Type-C Port Manager (tcpm)
  2016-08-23 21:10 ` [RFC PATCH v3 1/2] usb: typec: USB Type-C Port Manager (tcpm) Guenter Roeck
  2016-09-10  0:26   ` Jun Li
  2016-09-29 14:35   ` Jun Li
@ 2016-09-30  6:37   ` Jun Li
  2016-09-30 19:06     ` Guenter Roeck
  2016-10-03 14:04   ` Heikki Krogerus
  3 siblings, 1 reply; 24+ messages in thread
From: Jun Li @ 2016-09-30  6:37 UTC (permalink / raw)
  To: Guenter Roeck, Felipe Balbi
  Cc: Chandra Sekhar Anagani, Bruce Ashfield, Bin Gao, Pranav Tipnis,
	Heikki Krogerus, linux-kernel, linux-usb

Hi Guenter,
> -----Original Message-----
> From: linux-usb-owner@vger.kernel.org [mailto:linux-usb-
> owner@vger.kernel.org] On Behalf Of Guenter Roeck
> Sent: Wednesday, August 24, 2016 5:11 AM
> To: Felipe Balbi <felipe.balbi@linux.intel.com>
> Cc: Chandra Sekhar Anagani <chandra.sekhar.anagani@intel.com>; Bruce
> Ashfield <bruce.ashfield@windriver.com>; Bin Gao <bin.gao@intel.com>;
> Pranav Tipnis <pranav.tipnis@intel.com>; Heikki Krogerus
> <heikki.krogerus@linux.intel.com>; linux-kernel@vger.kernel.org; linux-
> usb@vger.kernel.org; Guenter Roeck <groeck@chromium.org>
> Subject: [RFC PATCH v3 1/2] usb: typec: USB Type-C Port Manager (tcpm)
> 
...
> diff --git a/include/linux/usb/pd.h b/include/linux/usb/pd.h
> new file mode 100644
> index 000000000000..6b1679af7a25
> --- /dev/null
> +++ b/include/linux/usb/pd.h

...

> +#define PDO_VAR(min_mv, max_mv, max_ma)					\
> +	((PDO_TYPE_VAR << PDO_TYPE_SHIFT) |				\
> +	 ((((min_mv) / 50) & PDO_VAR_MIN_VOLT_MASK) <<			\
> +	  PDO_VAR_MIN_VOLT_SHIFT) |					\
> +	 ((((max_mv) / 50) & PDO_VAR_MAX_VOLT_MASK) <<			\
> +	  PDO_VAR_MAX_VOLT_SHIFT) |					\
> +	 ((((max_ma) / 50) & PDO_VAR_MAX_CURR_MASK) <<			\

((((max_ma) / 10) & PDO_VAR_MAX_CURR_MASK) <<                  \

Li Jun

^ permalink raw reply	[flat|nested] 24+ messages in thread

* RE: [RFC PATCH v3 1/2] usb: typec: USB Type-C Port Manager (tcpm)
  2016-09-29 16:36     ` Guenter Roeck
@ 2016-09-30  6:41       ` Jun Li
  2016-09-30 15:46         ` Guenter Roeck
  0 siblings, 1 reply; 24+ messages in thread
From: Jun Li @ 2016-09-30  6:41 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Guenter Roeck, Felipe Balbi, Chandra Sekhar Anagani,
	Bruce Ashfield, Bin Gao, Pranav Tipnis, Heikki Krogerus,
	linux-kernel, linux-usb

Hi,

> -----Original Message-----
> From: Guenter Roeck [mailto:groeck@google.com]
> Sent: Friday, September 30, 2016 12:37 AM
> To: Jun Li <jun.li@nxp.com>
> Cc: Guenter Roeck <groeck@chromium.org>; Felipe Balbi
> <felipe.balbi@linux.intel.com>; Chandra Sekhar Anagani
> <chandra.sekhar.anagani@intel.com>; Bruce Ashfield
> <bruce.ashfield@windriver.com>; Bin Gao <bin.gao@intel.com>; Pranav Tipnis
> <pranav.tipnis@intel.com>; Heikki Krogerus
> <heikki.krogerus@linux.intel.com>; linux-kernel@vger.kernel.org; linux-
> usb@vger.kernel.org
> Subject: Re: [RFC PATCH v3 1/2] usb: typec: USB Type-C Port Manager (tcpm)
> 
> On Thu, Sep 29, 2016 at 7:35 AM, Jun Li <jun.li@nxp.com> wrote:
> > Hi Guenter,
> >
> >> -----Original Message-----
> >> From: linux-usb-owner@vger.kernel.org [mailto:linux-usb-
> >> owner@vger.kernel.org] On Behalf Of Guenter Roeck
> >> Sent: Wednesday, August 24, 2016 5:11 AM
> >> To: Felipe Balbi <felipe.balbi@linux.intel.com>
> >> Cc: Chandra Sekhar Anagani <chandra.sekhar.anagani@intel.com>; Bruce
> >> Ashfield <bruce.ashfield@windriver.com>; Bin Gao <bin.gao@intel.com>;
> >> Pranav Tipnis <pranav.tipnis@intel.com>; Heikki Krogerus
> >> <heikki.krogerus@linux.intel.com>; linux-kernel@vger.kernel.org;
> >> linux- usb@vger.kernel.org; Guenter Roeck <groeck@chromium.org>
> >> Subject: [RFC PATCH v3 1/2] usb: typec: USB Type-C Port Manager
> >> (tcpm)
> >>
> >> This driver implements the USB Type-C Power Delivery state machine
> >> for both source and sink ports. Alternate mode support is not fully
> >> implemented.
> >>
> >> The driver attaches to the USB Type-C class code implemented in the
> >> following patches.
> >>
> >>       usb: typec: add driver for Intel Whiskey Cove PMIC USB Type-C PHY
> >>       usb: USB Type-C connector class
> >>
> >> This driver only implements the state machine. Lower level drivers
> >> are responsible for
> >> - Reporting VBUS status and activating VBUS
> >> - Setting CC lines and providing CC line status
> >> - Setting line polarity
> >> - Activating and deactivating VCONN
> >> - Setting the current limit
> >> - Activating and deactivating PD message transfers
> >> - Sending and receiving PD messages
> >>
> >> The driver provides both a functional API as well as callbacks for
> >> lower level drivers.
> >>
> >> Signed-off-by: Guenter Roeck <groeck@chromium.org>
> >> ---
> >> v3:
> >> - Improve TCPM state machine resiliency if there are spurious CC line
> >> changes
> >>   while the state machine is in a transient change (waiting for a
> >> timeout)
> >> - Update current limit after CC voltage level changes on a port which
> >> is not
> >>   PD capable.
> >>
> >> v2:
> >> - Only update polarity if setting it was successful
> >>   If setting the CC line polarity in the driver was not successful,
> >>   don't update the internal polarity state.
> >> - All PD messages are little endian; convert to and from CPU endianness.
> >> - Avoid comparisons against NULL.
> >> - Use u8/u16/u32 instead of uint8_t/uint16_t/uint32_t consistently.
> >> - Callbacks into tcpm need to be lockless to avoid timing problems
> >>   in low level drivers.
> >> - Simplify callbacks; tcpm can request the current state of cc/vbus
> >>   when it is ready to use it.
> >>
> >>  drivers/usb/typec/Kconfig  |    7 +
> >>  drivers/usb/typec/Makefile |    1 +
> >>  drivers/usb/typec/tcpm.c   | 3163
> >> ++++++++++++++++++++++++++++++++++++++++++++
> >>  drivers/usb/typec/tcpm.h   |  137 ++
> >>  include/linux/usb/pd.h     |  282 ++++
> >>  include/linux/usb/pd_bdo.h |   31 +
> >>  include/linux/usb/pd_vdo.h |  412 ++++++
> >>  7 files changed, 4033 insertions(+)
> >>  create mode 100644 drivers/usb/typec/tcpm.c  create mode 100644
> >> drivers/usb/typec/tcpm.h  create mode 100644 include/linux/usb/pd.h
> >> create mode 100644 include/linux/usb/pd_bdo.h  create mode 100644
> >> include/linux/usb/pd_vdo.h
> >>
> >
> > ...
> >
> >> +
> >> +static void run_state_machine(struct tcpm_port *port) {
> >> +     int ret;
> >> +
> >> +     port->enter_state = port->state;
> >> +     switch (port->state) {
> >> +     /* SRC states */
> >> +     case SRC_UNATTACHED:
> >> +             tcpm_swap_complete(port, -ENOTCONN);
> >> +             tcpm_src_detach(port);
> >> +             tcpm_set_cc(port, TYPEC_CC_RP_DEF);
> >> +             if (port->typec_caps.type == TYPEC_PORT_DRP)
> >> +                     tcpm_set_state(port, SNK_UNATTACHED,
> >> + PD_T_DRP_SNK);
> >
> > With this and below, after disconnect, the DRP port state machine will
> > be in infinite loop of state transition between SRC_UNATTACHED <-->
> > SNK_UNATTACHED, correct?
> >
> 
> Only while disconnected. It tries to alternatively connect as source and
> as sink (being configured as DRP). Once a CC line state change is reported
> it will transition out. I have a newer version of the patch (not yet
> published) which supports DRP toggling by the TCPC. With that enabled,
> TCPM does not change states until a CC state change is reported.
> 
Great, that way will be better, do you plan post your newer patch recently? 

Thanks
Li Jun

> Guenter
> 
> > Li Jun
> > ...
> >
> >> +     /* SNK states */
> >> +     case SNK_UNATTACHED:
> >> +             tcpm_swap_complete(port, -ENOTCONN);
> >> +             tcpm_snk_detach(port);
> >> +             tcpm_set_cc(port, TYPEC_CC_RD);
> >> +             if (port->typec_caps.type == TYPEC_PORT_DRP)
> >> +                     tcpm_set_state(port, SRC_UNATTACHED,
> PD_T_DRP_SRC);
> >> +             break;
> >>

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [RFC PATCH v3 1/2] usb: typec: USB Type-C Port Manager (tcpm)
  2016-09-30  6:41       ` Jun Li
@ 2016-09-30 15:46         ` Guenter Roeck
  0 siblings, 0 replies; 24+ messages in thread
From: Guenter Roeck @ 2016-09-30 15:46 UTC (permalink / raw)
  To: Jun Li
  Cc: Guenter Roeck, Felipe Balbi, Chandra Sekhar Anagani,
	Bruce Ashfield, Bin Gao, Pranav Tipnis, Heikki Krogerus,
	linux-kernel, linux-usb

On Thu, Sep 29, 2016 at 11:41 PM, Jun Li <jun.li@nxp.com> wrote:
> Hi,
>
>> -----Original Message-----
>> From: Guenter Roeck [mailto:groeck@google.com]
>> Sent: Friday, September 30, 2016 12:37 AM
>> To: Jun Li <jun.li@nxp.com>
>> Cc: Guenter Roeck <groeck@chromium.org>; Felipe Balbi
>> <felipe.balbi@linux.intel.com>; Chandra Sekhar Anagani
>> <chandra.sekhar.anagani@intel.com>; Bruce Ashfield
>> <bruce.ashfield@windriver.com>; Bin Gao <bin.gao@intel.com>; Pranav Tipnis
>> <pranav.tipnis@intel.com>; Heikki Krogerus
>> <heikki.krogerus@linux.intel.com>; linux-kernel@vger.kernel.org; linux-
>> usb@vger.kernel.org
>> Subject: Re: [RFC PATCH v3 1/2] usb: typec: USB Type-C Port Manager (tcpm)
>>
>> On Thu, Sep 29, 2016 at 7:35 AM, Jun Li <jun.li@nxp.com> wrote:
>> > Hi Guenter,
>> >
>> >> -----Original Message-----
>> >> From: linux-usb-owner@vger.kernel.org [mailto:linux-usb-
>> >> owner@vger.kernel.org] On Behalf Of Guenter Roeck
>> >> Sent: Wednesday, August 24, 2016 5:11 AM
>> >> To: Felipe Balbi <felipe.balbi@linux.intel.com>
>> >> Cc: Chandra Sekhar Anagani <chandra.sekhar.anagani@intel.com>; Bruce
>> >> Ashfield <bruce.ashfield@windriver.com>; Bin Gao <bin.gao@intel.com>;
>> >> Pranav Tipnis <pranav.tipnis@intel.com>; Heikki Krogerus
>> >> <heikki.krogerus@linux.intel.com>; linux-kernel@vger.kernel.org;
>> >> linux- usb@vger.kernel.org; Guenter Roeck <groeck@chromium.org>
>> >> Subject: [RFC PATCH v3 1/2] usb: typec: USB Type-C Port Manager
>> >> (tcpm)
>> >>
>> >> This driver implements the USB Type-C Power Delivery state machine
>> >> for both source and sink ports. Alternate mode support is not fully
>> >> implemented.
>> >>
>> >> The driver attaches to the USB Type-C class code implemented in the
>> >> following patches.
>> >>
>> >>       usb: typec: add driver for Intel Whiskey Cove PMIC USB Type-C PHY
>> >>       usb: USB Type-C connector class
>> >>
>> >> This driver only implements the state machine. Lower level drivers
>> >> are responsible for
>> >> - Reporting VBUS status and activating VBUS
>> >> - Setting CC lines and providing CC line status
>> >> - Setting line polarity
>> >> - Activating and deactivating VCONN
>> >> - Setting the current limit
>> >> - Activating and deactivating PD message transfers
>> >> - Sending and receiving PD messages
>> >>
>> >> The driver provides both a functional API as well as callbacks for
>> >> lower level drivers.
>> >>
>> >> Signed-off-by: Guenter Roeck <groeck@chromium.org>
>> >> ---
>> >> v3:
>> >> - Improve TCPM state machine resiliency if there are spurious CC line
>> >> changes
>> >>   while the state machine is in a transient change (waiting for a
>> >> timeout)
>> >> - Update current limit after CC voltage level changes on a port which
>> >> is not
>> >>   PD capable.
>> >>
>> >> v2:
>> >> - Only update polarity if setting it was successful
>> >>   If setting the CC line polarity in the driver was not successful,
>> >>   don't update the internal polarity state.
>> >> - All PD messages are little endian; convert to and from CPU endianness.
>> >> - Avoid comparisons against NULL.
>> >> - Use u8/u16/u32 instead of uint8_t/uint16_t/uint32_t consistently.
>> >> - Callbacks into tcpm need to be lockless to avoid timing problems
>> >>   in low level drivers.
>> >> - Simplify callbacks; tcpm can request the current state of cc/vbus
>> >>   when it is ready to use it.
>> >>
>> >>  drivers/usb/typec/Kconfig  |    7 +
>> >>  drivers/usb/typec/Makefile |    1 +
>> >>  drivers/usb/typec/tcpm.c   | 3163
>> >> ++++++++++++++++++++++++++++++++++++++++++++
>> >>  drivers/usb/typec/tcpm.h   |  137 ++
>> >>  include/linux/usb/pd.h     |  282 ++++
>> >>  include/linux/usb/pd_bdo.h |   31 +
>> >>  include/linux/usb/pd_vdo.h |  412 ++++++
>> >>  7 files changed, 4033 insertions(+)
>> >>  create mode 100644 drivers/usb/typec/tcpm.c  create mode 100644
>> >> drivers/usb/typec/tcpm.h  create mode 100644 include/linux/usb/pd.h
>> >> create mode 100644 include/linux/usb/pd_bdo.h  create mode 100644
>> >> include/linux/usb/pd_vdo.h
>> >>
>> >
>> > ...
>> >
>> >> +
>> >> +static void run_state_machine(struct tcpm_port *port) {
>> >> +     int ret;
>> >> +
>> >> +     port->enter_state = port->state;
>> >> +     switch (port->state) {
>> >> +     /* SRC states */
>> >> +     case SRC_UNATTACHED:
>> >> +             tcpm_swap_complete(port, -ENOTCONN);
>> >> +             tcpm_src_detach(port);
>> >> +             tcpm_set_cc(port, TYPEC_CC_RP_DEF);
>> >> +             if (port->typec_caps.type == TYPEC_PORT_DRP)
>> >> +                     tcpm_set_state(port, SNK_UNATTACHED,
>> >> + PD_T_DRP_SNK);
>> >
>> > With this and below, after disconnect, the DRP port state machine will
>> > be in infinite loop of state transition between SRC_UNATTACHED <-->
>> > SNK_UNATTACHED, correct?
>> >
>>
>> Only while disconnected. It tries to alternatively connect as source and
>> as sink (being configured as DRP). Once a CC line state change is reported
>> it will transition out. I have a newer version of the patch (not yet
>> published) which supports DRP toggling by the TCPC. With that enabled,
>> TCPM does not change states until a CC state change is reported.
>>
> Great, that way will be better, do you plan post your newer patch recently?
>

I should be able to send out a new version early next week, after
applying and testing your suggested changes. I'll also try to make a
test branch available in my repository at kernel.org.

Guenter

> Thanks
> Li Jun
>
>> Guenter
>>
>> > Li Jun
>> > ...
>> >
>> >> +     /* SNK states */
>> >> +     case SNK_UNATTACHED:
>> >> +             tcpm_swap_complete(port, -ENOTCONN);
>> >> +             tcpm_snk_detach(port);
>> >> +             tcpm_set_cc(port, TYPEC_CC_RD);
>> >> +             if (port->typec_caps.type == TYPEC_PORT_DRP)
>> >> +                     tcpm_set_state(port, SRC_UNATTACHED,
>> PD_T_DRP_SRC);
>> >> +             break;
>> >>

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [RFC PATCH v3 2/2] usb: typec: Type-C Port Controller Interface driver (tcpci)
  2016-09-30  6:24   ` Jun Li
@ 2016-09-30 18:45     ` Guenter Roeck
  2016-10-01  0:12       ` Jun Li
  0 siblings, 1 reply; 24+ messages in thread
From: Guenter Roeck @ 2016-09-30 18:45 UTC (permalink / raw)
  To: Jun Li
  Cc: Guenter Roeck, Felipe Balbi, Chandra Sekhar Anagani,
	Bruce Ashfield, Bin Gao, Pranav Tipnis, Heikki Krogerus,
	linux-kernel, linux-usb

On Thu, Sep 29, 2016 at 11:24 PM, Jun Li <jun.li@nxp.com> wrote:
> Hi Guenter,
>
>> -----Original Message-----
>> From: linux-usb-owner@vger.kernel.org [mailto:linux-usb-
>> owner@vger.kernel.org] On Behalf Of Guenter Roeck
>> Sent: Wednesday, August 24, 2016 5:11 AM
>> To: Felipe Balbi <felipe.balbi@linux.intel.com>
>> Cc: Chandra Sekhar Anagani <chandra.sekhar.anagani@intel.com>; Bruce
>> Ashfield <bruce.ashfield@windriver.com>; Bin Gao <bin.gao@intel.com>;
>> Pranav Tipnis <pranav.tipnis@intel.com>; Heikki Krogerus
>> <heikki.krogerus@linux.intel.com>; linux-kernel@vger.kernel.org; linux-
>> usb@vger.kernel.org; Guenter Roeck <groeck@chromium.org>
>> Subject: [RFC PATCH v3 2/2] usb: typec: Type-C Port Controller Interface
>> driver (tcpci)
>>
>> The port controller interface driver interconnects the Type-C Port Manager
>> with a Type-C Port Controller Interface (TCPCI) compliant port controller.
>>
>> Signed-off-by: Guenter Roeck <groeck@chromium.org>
>> ---
>> v3:
>> - No change
>>
>> v2:
>> - Adjust to modified callbacks into tcpm code
>>
>>  drivers/usb/typec/Kconfig  |   9 +
>>  drivers/usb/typec/Makefile |   1 +
>>  drivers/usb/typec/tcpci.c  | 487
>> +++++++++++++++++++++++++++++++++++++++++++++
>>  drivers/usb/typec/tcpci.h  | 133 +++++++++++++
>>  4 files changed, 630 insertions(+)
>>  create mode 100644 drivers/usb/typec/tcpci.c  create mode 100644
>> drivers/usb/typec/tcpci.h
>>
>> diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig index
>> 113bb1b3589c..a92c9d1a3e00 100644
>> --- a/drivers/usb/typec/Kconfig
>> +++ b/drivers/usb/typec/Kconfig
>> @@ -25,4 +25,13 @@ config TYPEC_TCPM
>>         The Type-C Port Controller Manager provides a USB PD and USB Type-C
>>         state machine for use with Type-C Port Controllers.
>>
>> +if TYPEC_TCPM
>> +
>> +config TYPEC_TCPCI
>> +     tristate "Type-C Port Controller Interface driver"
>> +     help
>> +       Type-C Port Controller driver for TCPCI-compliant controller.
>> +
>> +endif
>> +
>>  endmenu
>> diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile index
>> bbe45721cf52..7dbaf8c3911d 100644
>> --- a/drivers/usb/typec/Makefile
>> +++ b/drivers/usb/typec/Makefile
>> @@ -1,3 +1,4 @@
>>  obj-$(CONFIG_TYPEC)          += typec.o
>>  obj-$(CONFIG_TYPEC_WCOVE)    += typec_wcove.o
>>  obj-$(CONFIG_TYPEC_TCPM)     += tcpm.o
>> +obj-$(CONFIG_TYPEC_TCPCI)    += tcpci.o
>> diff --git a/drivers/usb/typec/tcpci.c b/drivers/usb/typec/tcpci.c new
>> file mode 100644 index 000000000000..af338218a1f3
>> --- /dev/null
>> +++ b/drivers/usb/typec/tcpci.c
>> @@ -0,0 +1,487 @@
>> +/*
>> + * Copyright 2015-2016 Google, Inc
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License as published by
>> + * the Free Software Foundation; either version 2 of the License, or
>> + * (at your option) any later version.
>> + *
>> + * This program is distributed in the hope that it will be useful,
>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
>> + * GNU General Public License for more details.
>> + *
>> + * USB Type-C Port Controller Interface.
>> + */
>> +
>> +#include <linux/delay.h>
>> +#include <linux/kernel.h>
>> +#include <linux/module.h>
>> +#include <linux/i2c.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/regmap.h>
>> +#include <linux/usb/pd.h>
>> +#include <linux/usb/typec.h>
>> +
>> +#include "tcpci.h"
>> +#include "tcpm.h"
>> +
>> +#define PD_RETRY_COUNT 3
>> +
>> +struct tcpci {
>> +     struct device *dev;
>> +     struct i2c_client *client;
>> +
>> +     struct tcpm_port *port;
>> +
>> +     struct regmap *regmap;
>> +
>> +     bool controls_vbus;
>> +
>> +     struct tcpc_dev tcpc;
>> +};
>> +
>> +static inline struct tcpci *tcpc_to_tcpci(struct tcpc_dev *tcpc) {
>> +     return container_of(tcpc, struct tcpci, tcpc); }
>> +
>> +static int tcpci_read16(struct tcpci *tcpci, unsigned int reg,
>> +                     unsigned int *val)
>> +{
>> +     return regmap_raw_read(tcpci->regmap, reg, val, sizeof(u16)); }
>> +
>> +static int tcpci_write16(struct tcpci *tcpci, unsigned int reg, u16
>> +val) {
>> +     return regmap_raw_write(tcpci->regmap, reg, &val, sizeof(u16)); }
>> +
>> +static int tcpci_set_cc(struct tcpc_dev *tcpc, enum typec_cc_status cc)
>> +{
>> +     struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
>> +     unsigned int reg;
>> +     int ret;
>> +
>> +     switch (cc) {
>> +     case TYPEC_CC_RA:
>> +             reg = (TCPC_ROLE_CTRL_CC_RA << TCPC_ROLE_CTRL_CC1_SHIFT) |
>> +                     (TCPC_ROLE_CTRL_CC_RA << TCPC_ROLE_CTRL_CC2_SHIFT);
>> +             break;
>> +     case TYPEC_CC_RD:
>> +             reg = (TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC1_SHIFT) |
>> +                     (TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC2_SHIFT);
>> +             break;
>> +     case TYPEC_CC_RP_DEF:
>> +             reg = (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC1_SHIFT) |
>> +                     (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC2_SHIFT) |
>> +                     (TCPC_ROLE_CTRL_RP_VAL_DEF <<
>> +                      TCPC_ROLE_CTRL_RP_VAL_SHIFT);
>> +             break;
>> +     case TYPEC_CC_RP_1_5:
>> +             reg = (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC1_SHIFT) |
>> +                     (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC2_SHIFT) |
>> +                     (TCPC_ROLE_CTRL_RP_VAL_1_5 <<
>> +                      TCPC_ROLE_CTRL_RP_VAL_SHIFT);
>> +             break;
>> +     case TYPEC_CC_RP_3_0:
>> +             reg = (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC1_SHIFT) |
>> +                     (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC2_SHIFT) |
>> +                     (TCPC_ROLE_CTRL_RP_VAL_1_5 <<
>> +                      TCPC_ROLE_CTRL_RP_VAL_SHIFT);
>> +             break;
>> +     case TYPEC_CC_OPEN:
>> +     default:
>> +             reg = (TCPC_ROLE_CTRL_CC_OPEN << TCPC_ROLE_CTRL_CC1_SHIFT) |
>> +                     (TCPC_ROLE_CTRL_CC_OPEN << TCPC_ROLE_CTRL_CC2_SHIFT);
>> +             break;
>> +     }
>> +
>> +     ret = regmap_write(tcpci->regmap, TCPC_ROLE_CTRL, reg);
>> +     if (ret < 0)
>> +             return ret;
>> +
>> +     return 0;
>> +}
>> +
>> +static enum typec_cc_status tcpci_to_typec_cc(unsigned int cc, bool
>> +sink) {
>> +     switch (cc) {
>> +     case 0x1:
>> +             return sink ? TYPEC_CC_RP_DEF : TYPEC_CC_RA;
>> +     case 0x2:
>> +             return sink ? TYPEC_CC_RP_1_5 : TYPEC_CC_RD;
>> +     case 0x3:
>> +             if (sink)
>> +                     return TYPEC_CC_RP_3_0;
>> +     case 0x0:
>> +     default:
>> +             return TYPEC_CC_OPEN;
>> +     }
>> +}
>> +
>> +static int tcpci_get_cc(struct tcpc_dev *tcpc,
>> +                     enum typec_cc_status *cc1, enum typec_cc_status *cc2) {
>> +     struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
>> +     unsigned int reg;
>> +     int ret;
>> +
>> +     ret = regmap_read(tcpci->regmap, TCPC_CC_STATUS, &reg);
>> +     if (ret < 0)
>> +             return ret;
>> +
>> +     *cc1 = tcpci_to_typec_cc((reg >> TCPC_CC_STATUS_CC1_SHIFT) &
>> +                              TCPC_CC_STATUS_CC1_MASK,
>> +                              reg & TCPC_CC_STATUS_TERM);
>> +     *cc2 = tcpci_to_typec_cc((reg >> TCPC_CC_STATUS_CC2_SHIFT) &
>> +                              TCPC_CC_STATUS_CC2_MASK,
>> +                              reg & TCPC_CC_STATUS_TERM);
>> +
>> +     return 0;
>> +}
>> +
>> +static int tcpci_set_polarity(struct tcpc_dev *tcpc,
>> +                           enum typec_cc_polarity polarity) {
>> +     struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
>> +     int ret;
>> +
>> +     ret = regmap_write(tcpci->regmap, TCPC_TCPC_CTRL,
>> +                        (polarity == TYPEC_POLARITY_CC2) ?
>> +                        TCPC_TCPC_CTRL_ORIENTATION : 0);
>> +     if (ret < 0)
>> +             return ret;
>> +
>> +     return 0;
>> +}
>> +
>> +static int tcpci_set_vconn(struct tcpc_dev *tcpc, bool enable) {
>> +     struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
>> +     int ret;
>> +
>> +     ret = regmap_write(tcpci->regmap, TCPC_POWER_CTRL,
>> +                        enable ? TCPC_POWER_CTRL_VCONN_ENABLE : 0);
>> +     if (ret < 0)
>> +             return ret;
>> +
>> +     return 0;
>> +}
>> +
>> +static int tcpci_set_pd_header(struct tcpc_dev *tcpc, enum typec_role
>> role,
>> +                            enum typec_data_role data)
>> +{
>> +     struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
>> +     unsigned int reg;
>> +     int ret;
>> +
>> +     reg = PD_REV20 << TCPC_MSG_HDR_INFO_REV_SHIFT;
>> +     if (role == TYPEC_SOURCE)
>> +             reg |= TCPC_MSG_HDR_INFO_PWR_ROLE;
>> +     if (data == TYPEC_HOST)
>> +             reg |= TCPC_MSG_HDR_INFO_DATA_ROLE;
>> +     ret = regmap_write(tcpci->regmap, TCPC_MSG_HDR_INFO, reg);
>> +     if (ret < 0)
>> +             return ret;
>> +
>> +     return 0;
>> +}
>> +
>> +static int tcpci_set_pd_rx(struct tcpc_dev *tcpc, bool enable) {
>> +     struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
>> +     unsigned int reg = 0;
>> +     int ret;
>> +
>> +     if (enable)
>> +             reg = TCPC_RX_DETECT_SOP | TCPC_RX_DETECT_HARD_RESET;
>> +     ret = regmap_write(tcpci->regmap, TCPC_RX_DETECT, reg);
>> +     if (ret < 0)
>> +             return ret;
>> +
>> +     return 0;
>> +}
>> +
>> +static int tcpci_get_vbus(struct tcpc_dev *tcpc) {
>> +     struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
>> +     unsigned int reg;
>> +     int ret;
>> +
>> +     ret = regmap_read(tcpci->regmap, TCPC_POWER_STATUS, &reg);
>> +     if (ret < 0)
>> +             return ret;
>> +
>> +     return !!(reg & TCPC_POWER_STATUS_VBUS_PRES); }
>> +
>> +static int tcpci_set_vbus(struct tcpc_dev *tcpc, bool source, bool
>> +sink) {
>> +     struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
>> +     int ret;
>> +
>> +     if (source) {
>> +             ret = regmap_write(tcpci->regmap, TCPC_COMMAND,
>> +                                TCPC_CMD_SRC_VBUS_DEFAULT);
>> +             if (ret < 0)
>> +                     return ret;
>> +     } else {
>> +             ret = regmap_write(tcpci->regmap, TCPC_COMMAND,
>> +                                TCPC_CMD_DISABLE_SRC_VBUS);
>> +             if (ret < 0)
>> +                     return ret;
>> +     }
>> +
>> +     if (sink) {
>> +             ret = regmap_write(tcpci->regmap, TCPC_COMMAND,
>> +                                TCPC_CMD_SINK_VBUS);
>> +             if (ret < 0)
>> +                     return ret;
>> +     } else {
>> +             ret = regmap_write(tcpci->regmap, TCPC_COMMAND,
>> +                                TCPC_CMD_DISABLE_SINK_VBUS);
>> +             if (ret < 0)
>> +                     return ret;
>> +     }
>> +
>
> Per tcpc spec, disable should be firstly done before enable,
> so should it be done like this?
> if(!source) {};
> if(!sink) {};
> if(source) {};
> if(sink) {};
>
Makes sense. Changed accordingly.

>> +     return 0;
>> +}
>> +
>> +static int tcpci_pd_transmit(struct tcpc_dev *tcpc,
>> +                          enum tcpm_transmit_type type,
>> +                          const struct pd_message *msg)
>> +{
>> +     struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
>> +     unsigned int reg, cnt, header;
>> +     int ret;
>> +
>> +     cnt = msg ? pd_header_cnt(msg->header) * 4 : 0;
>> +     ret = regmap_write(tcpci->regmap, TCPC_TX_BYTE_CNT, cnt);
>
> Here miss the 2 bytes' header, so cnt should be cnt += 2;
>
> Tcpc 4.4.13
> TRANSMIT_BYTE_COUNT:
> The number of bytes the TCPM will write
> This is the number of bytes in the TX_BUFFER_DATA_OBJECTS plus two
> (for the TX_BUF_HEADER)
>
Yes, you are correct. Fixed.

>> +     if (ret < 0)
>> +             return ret;
>> +
>> +     header = msg ? msg->header : 0;
>> +     ret = tcpci_write16(tcpci, TCPC_TX_HDR, header);
>> +     if (ret < 0)
>> +             return ret;
>> +
>> +     if (cnt > 0) {
>> +             ret = regmap_raw_write(tcpci->regmap, TCPC_TX_DATA,
>> +                                    &msg->payload, cnt);
>> +             if (ret < 0)
>> +                     return ret;
>> +     }
>> +
>> +     reg = (PD_RETRY_COUNT << TCPC_TRANSMIT_RETRY_SHIFT) |
>> +             (type << TCPC_TRANSMIT_TYPE_SHIFT);
>> +     ret = regmap_write(tcpci->regmap, TCPC_TRANSMIT, reg);
>> +     if (ret < 0)
>> +             return ret;
>> +
>> +     return 0;
>> +}
>> +
>
> ...
>
>> +
>> +static int tcpci_probe(struct i2c_client *client,
>> +                    const struct i2c_device_id *i2c_id) {
>> +     struct tcpci *tcpci;
>> +     int err;
>> +
>> +     tcpci = devm_kzalloc(&client->dev, sizeof(*tcpci), GFP_KERNEL);
>> +     if (!tcpci)
>> +             return -ENOMEM;
>> +
>> +     tcpci->client = client;
>> +     tcpci->dev = &client->dev;
>> +     i2c_set_clientdata(client, tcpci);
>> +     tcpci->regmap = devm_regmap_init_i2c(client, &tcpci_regmap_config);
>> +     if (IS_ERR(tcpci->regmap))
>> +             return PTR_ERR(tcpci->regmap);
>> +
>> +     tcpci->tcpc.init = tcpci_init;
>> +     tcpci->tcpc.get_vbus = tcpci_get_vbus;
>> +     tcpci->tcpc.set_vbus = tcpci_set_vbus;
>> +     tcpci->tcpc.set_cc = tcpci_set_cc;
>> +     tcpci->tcpc.get_cc = tcpci_get_cc;
>> +     tcpci->tcpc.set_polarity = tcpci_set_polarity;
>> +     tcpci->tcpc.set_vconn = tcpci_set_vconn;
>> +
>> +     tcpci->tcpc.set_pd_rx = tcpci_set_pd_rx;
>> +     tcpci->tcpc.set_pd_header = tcpci_set_pd_header;
>> +     tcpci->tcpc.pd_transmit = tcpci_pd_transmit;
>> +
>> +     err = tcpci_parse_config(tcpci);
>> +     if (err < 0)
>> +             return err;
>> +
>
> As the alert mask reg is defaultly to be unmasked after power on,
> It's better clear and mask all before request irq.
>
Agreed and done.

>> +     err = devm_request_threaded_irq(tcpci->dev, client->irq, NULL,
>> +                                     tcpci_irq, IRQF_TRIGGER_FALLING,
>
> IRQF_ONESHOT is required, and do we need the irq trigger to be
> IRQF_TRIGGER_LOW?
>

Good question; I don't really know. IRQF_TRIGGER_FALLING definitely
seems to be wrong (and I don't recall where I got it from). Not sure
about  IRQF_TRIGGER_LOW. Can you test on real hardware, by any chance
?

Thanks,
Guenter


> Li Jun

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [RFC PATCH v3 1/2] usb: typec: USB Type-C Port Manager (tcpm)
  2016-09-30  6:37   ` Jun Li
@ 2016-09-30 19:06     ` Guenter Roeck
  2016-09-30 19:41       ` Joe Perches
  0 siblings, 1 reply; 24+ messages in thread
From: Guenter Roeck @ 2016-09-30 19:06 UTC (permalink / raw)
  To: Jun Li
  Cc: Guenter Roeck, Felipe Balbi, Chandra Sekhar Anagani,
	Bruce Ashfield, Bin Gao, Pranav Tipnis, Heikki Krogerus,
	linux-kernel, linux-usb

On Thu, Sep 29, 2016 at 11:37 PM, Jun Li <jun.li@nxp.com> wrote:
> Hi Guenter,
>> -----Original Message-----
>> From: linux-usb-owner@vger.kernel.org [mailto:linux-usb-
>> owner@vger.kernel.org] On Behalf Of Guenter Roeck
>> Sent: Wednesday, August 24, 2016 5:11 AM
>> To: Felipe Balbi <felipe.balbi@linux.intel.com>
>> Cc: Chandra Sekhar Anagani <chandra.sekhar.anagani@intel.com>; Bruce
>> Ashfield <bruce.ashfield@windriver.com>; Bin Gao <bin.gao@intel.com>;
>> Pranav Tipnis <pranav.tipnis@intel.com>; Heikki Krogerus
>> <heikki.krogerus@linux.intel.com>; linux-kernel@vger.kernel.org; linux-
>> usb@vger.kernel.org; Guenter Roeck <groeck@chromium.org>
>> Subject: [RFC PATCH v3 1/2] usb: typec: USB Type-C Port Manager (tcpm)
>>
> ...
>> diff --git a/include/linux/usb/pd.h b/include/linux/usb/pd.h
>> new file mode 100644
>> index 000000000000..6b1679af7a25
>> --- /dev/null
>> +++ b/include/linux/usb/pd.h
>
> ...
>
>> +#define PDO_VAR(min_mv, max_mv, max_ma)                                      \
>> +     ((PDO_TYPE_VAR << PDO_TYPE_SHIFT) |                             \
>> +      ((((min_mv) / 50) & PDO_VAR_MIN_VOLT_MASK) <<                  \
>> +       PDO_VAR_MIN_VOLT_SHIFT) |                                     \
>> +      ((((max_mv) / 50) & PDO_VAR_MAX_VOLT_MASK) <<                  \
>> +       PDO_VAR_MAX_VOLT_SHIFT) |                                     \
>> +      ((((max_ma) / 50) & PDO_VAR_MAX_CURR_MASK) <<                  \
>
> ((((max_ma) / 10) & PDO_VAR_MAX_CURR_MASK) <<                  \
>

Thanks, fixed. PDO_BATT has a similar problem, which I noticed while
fixing the above.

Guenter

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [RFC PATCH v3 1/2] usb: typec: USB Type-C Port Manager (tcpm)
  2016-09-30 19:06     ` Guenter Roeck
@ 2016-09-30 19:41       ` Joe Perches
  2016-09-30 20:57         ` Guenter Roeck
  0 siblings, 1 reply; 24+ messages in thread
From: Joe Perches @ 2016-09-30 19:41 UTC (permalink / raw)
  To: Guenter Roeck, Jun Li
  Cc: Guenter Roeck, Felipe Balbi, Chandra Sekhar Anagani,
	Bruce Ashfield, Bin Gao, Pranav Tipnis, Heikki Krogerus,
	linux-kernel, linux-usb

On Fri, 2016-09-30 at 12:06 -0700, Guenter Roeck wrote:
> On Thu, Sep 29, 2016 at 11:37 PM, Jun Li <jun.li@nxp.com> wrote:
[]
> > diff --git a/include/linux/usb/pd.h b/include/linux/usb/pd.h
[]
> > +#define PDO_VAR(min_mv, max_mv, max_ma)                                      \
> > +     ((PDO_TYPE_VAR << PDO_TYPE_SHIFT) |                             \
> > +      ((((min_mv) / 50) & PDO_VAR_MIN_VOLT_MASK) <<                  \
> > +       PDO_VAR_MIN_VOLT_SHIFT) |                                     \
> > +      ((((max_mv) / 50) & PDO_VAR_MAX_VOLT_MASK) <<                  \
> > +       PDO_VAR_MAX_VOLT_SHIFT) |                                     \
> > +      ((((max_ma) / 50) & PDO_VAR_MAX_CURR_MASK) <<                  \
> 
> 
> ((((max_ma) / 10) & PDO_VAR_MAX_CURR_MASK) <<                  \

This would be easier to read if laid out differently.

#define PDO_VAR(min_mv, max_mv, max_ma)							\
	((PDO_TYPE_VAR << PDO_TYPE_SHIFT) |						\
	 ((((min_mv) / 50) & PDO_VAR_MIN_VOLT_MASK) << PDO_VAR_MIN_VOLT_SHIFT) |	\
	 ((((max_mv) / 50) & PDO_VAR_MAX_VOLT_MASK) << PDO_VAR_MAX_VOLT_SHIFT) |	\
	 ((((max_ma) / 10) & PDO_VAR_MAX_CURR_MASK) << PDO_VAR_MAX_CURR_SHIFT))

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [RFC PATCH v3 1/2] usb: typec: USB Type-C Port Manager (tcpm)
  2016-09-30 19:41       ` Joe Perches
@ 2016-09-30 20:57         ` Guenter Roeck
  2016-09-30 21:02           ` Joe Perches
  0 siblings, 1 reply; 24+ messages in thread
From: Guenter Roeck @ 2016-09-30 20:57 UTC (permalink / raw)
  To: Joe Perches
  Cc: Jun Li, Guenter Roeck, Felipe Balbi, Chandra Sekhar Anagani,
	Bruce Ashfield, Bin Gao, Pranav Tipnis, Heikki Krogerus,
	linux-kernel, linux-usb

On Fri, Sep 30, 2016 at 12:41 PM, Joe Perches <joe@perches.com> wrote:
> On Fri, 2016-09-30 at 12:06 -0700, Guenter Roeck wrote:
>> On Thu, Sep 29, 2016 at 11:37 PM, Jun Li <jun.li@nxp.com> wrote:
> []
>> > diff --git a/include/linux/usb/pd.h b/include/linux/usb/pd.h
> []
>> > +#define PDO_VAR(min_mv, max_mv, max_ma)                                      \
>> > +     ((PDO_TYPE_VAR << PDO_TYPE_SHIFT) |                             \
>> > +      ((((min_mv) / 50) & PDO_VAR_MIN_VOLT_MASK) <<                  \
>> > +       PDO_VAR_MIN_VOLT_SHIFT) |                                     \
>> > +      ((((max_mv) / 50) & PDO_VAR_MAX_VOLT_MASK) <<                  \
>> > +       PDO_VAR_MAX_VOLT_SHIFT) |                                     \
>> > +      ((((max_ma) / 50) & PDO_VAR_MAX_CURR_MASK) <<                  \
>>
>>
>> ((((max_ma) / 10) & PDO_VAR_MAX_CURR_MASK) <<                  \
>
> This would be easier to read if laid out differently.
>
> #define PDO_VAR(min_mv, max_mv, max_ma)                                                 \
>         ((PDO_TYPE_VAR << PDO_TYPE_SHIFT) |                                             \
>          ((((min_mv) / 50) & PDO_VAR_MIN_VOLT_MASK) << PDO_VAR_MIN_VOLT_SHIFT) |        \
>          ((((max_mv) / 50) & PDO_VAR_MAX_VOLT_MASK) << PDO_VAR_MAX_VOLT_SHIFT) |        \
>          ((((max_ma) / 10) & PDO_VAR_MAX_CURR_MASK) << PDO_VAR_MAX_CURR_SHIFT))
>

Code now looks as follows.

#define PDO_VAR_MIN_VOLT(mv) ((((mv) / 50) & PDO_VAR_MIN_VOLT_MASK) << \
                              PDO_VAR_MIN_VOLT_SHIFT)
#define PDO_VAR_MAX_VOLT(mv) ((((mv) / 50) & PDO_VAR_MAX_VOLT_MASK) << \
                              PDO_VAR_MAX_VOLT_SHIFT)
#define PDO_VAR_MAX_CURR(ma) ((((ma) / 10) & PDO_VAR_MAX_CURR_MASK) << \
                              PDO_VAR_MAX_CURR_SHIFT)

#define PDO_VAR(min_mv, max_mv, max_ma)                         \
        (PDO_TYPE(PDO_TYPE_VAR) | PDO_VAR_MIN_VOLT(min_mv) |    \
         PDO_VAR_MAX_VOLT(max_mv) | PDO_VAR_MAX_CURR(max_ma))

Though maybe I should just ignore line length limits or use shorter defines.

Guenter

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [RFC PATCH v3 1/2] usb: typec: USB Type-C Port Manager (tcpm)
  2016-09-30 20:57         ` Guenter Roeck
@ 2016-09-30 21:02           ` Joe Perches
  0 siblings, 0 replies; 24+ messages in thread
From: Joe Perches @ 2016-09-30 21:02 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Jun Li, Guenter Roeck, Felipe Balbi, Chandra Sekhar Anagani,
	Bruce Ashfield, Bin Gao, Pranav Tipnis, Heikki Krogerus,
	linux-kernel, linux-usb

On Fri, 2016-09-30 at 13:57 -0700, Guenter Roeck wrote:
> Code now looks as follows.
> 
> #define PDO_VAR_MIN_VOLT(mv) ((((mv) / 50) & PDO_VAR_MIN_VOLT_MASK) << \
>                               PDO_VAR_MIN_VOLT_SHIFT)
> #define PDO_VAR_MAX_VOLT(mv) ((((mv) / 50) & PDO_VAR_MAX_VOLT_MASK) << \
>                               PDO_VAR_MAX_VOLT_SHIFT)
> #define PDO_VAR_MAX_CURR(ma) ((((ma) / 10) & PDO_VAR_MAX_CURR_MASK) << \
>                               PDO_VAR_MAX_CURR_SHIFT)

When #defines are continued, I generally find it nicer to have the
entire definition on a separate line

#define PDO_VAR_MIN_VOLT(mv)						\
	((((mv) / 50) & PDO_VAR_MIN_VOLT_MASK) << PDO_VAR_MIN_VOLT_SHIFT)

> Though maybe I should just ignore line length limits or use shorter defines.

When it makes sense, yes please.

^ permalink raw reply	[flat|nested] 24+ messages in thread

* RE: [RFC PATCH v3 2/2] usb: typec: Type-C Port Controller Interface driver (tcpci)
  2016-09-30 18:45     ` Guenter Roeck
@ 2016-10-01  0:12       ` Jun Li
  2016-10-01 16:27         ` Guenter Roeck
  0 siblings, 1 reply; 24+ messages in thread
From: Jun Li @ 2016-10-01  0:12 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Guenter Roeck, Felipe Balbi, Chandra Sekhar Anagani,
	Bruce Ashfield, Bin Gao, Pranav Tipnis, Heikki Krogerus,
	linux-kernel, linux-usb

Hi,
> -----Original Message-----
> From: Guenter Roeck [mailto:groeck@google.com]
> Sent: Saturday, October 01, 2016 2:45 AM
> To: Jun Li <jun.li@nxp.com>
> Cc: Guenter Roeck <groeck@chromium.org>; Felipe Balbi
> <felipe.balbi@linux.intel.com>; Chandra Sekhar Anagani
> <chandra.sekhar.anagani@intel.com>; Bruce Ashfield
> <bruce.ashfield@windriver.com>; Bin Gao <bin.gao@intel.com>; Pranav Tipnis
> <pranav.tipnis@intel.com>; Heikki Krogerus
> <heikki.krogerus@linux.intel.com>; linux-kernel@vger.kernel.org; linux-
> usb@vger.kernel.org
> Subject: Re: [RFC PATCH v3 2/2] usb: typec: Type-C Port Controller
> Interface driver (tcpci)
> 
> On Thu, Sep 29, 2016 at 11:24 PM, Jun Li <jun.li@nxp.com> wrote:
> > Hi Guenter,
> >
> >> -----Original Message-----
> >> From: linux-usb-owner@vger.kernel.org [mailto:linux-usb-
> >> owner@vger.kernel.org] On Behalf Of Guenter Roeck
> >> Sent: Wednesday, August 24, 2016 5:11 AM
> >> To: Felipe Balbi <felipe.balbi@linux.intel.com>
> >> Cc: Chandra Sekhar Anagani <chandra.sekhar.anagani@intel.com>; Bruce
> >> Ashfield <bruce.ashfield@windriver.com>; Bin Gao <bin.gao@intel.com>;
> >> Pranav Tipnis <pranav.tipnis@intel.com>; Heikki Krogerus
> >> <heikki.krogerus@linux.intel.com>; linux-kernel@vger.kernel.org;
> >> linux- usb@vger.kernel.org; Guenter Roeck <groeck@chromium.org>
> >> Subject: [RFC PATCH v3 2/2] usb: typec: Type-C Port Controller
> >> Interface driver (tcpci)
> >>
> >> The port controller interface driver interconnects the Type-C Port
> >> Manager with a Type-C Port Controller Interface (TCPCI) compliant port
> controller.
> >>
> >> Signed-off-by: Guenter Roeck <groeck@chromium.org>
> >> ---
> >> v3:
> >> - No change
> >>
> >> v2:
> >> - Adjust to modified callbacks into tcpm code
> >>
> >>  drivers/usb/typec/Kconfig  |   9 +
> >>  drivers/usb/typec/Makefile |   1 +
> >>  drivers/usb/typec/tcpci.c  | 487
> >> +++++++++++++++++++++++++++++++++++++++++++++
> >>  drivers/usb/typec/tcpci.h  | 133 +++++++++++++
> >>  4 files changed, 630 insertions(+)
> >>  create mode 100644 drivers/usb/typec/tcpci.c  create mode 100644
> >> drivers/usb/typec/tcpci.h
> >>
> >> diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig
> >> index
> >> 113bb1b3589c..a92c9d1a3e00 100644
> >> --- a/drivers/usb/typec/Kconfig
> >> +++ b/drivers/usb/typec/Kconfig
> >> @@ -25,4 +25,13 @@ config TYPEC_TCPM
> >>         The Type-C Port Controller Manager provides a USB PD and USB
> Type-C
> >>         state machine for use with Type-C Port Controllers.
> >>
> >> +if TYPEC_TCPM
> >> +
> >> +config TYPEC_TCPCI
> >> +     tristate "Type-C Port Controller Interface driver"
> >> +     help
> >> +       Type-C Port Controller driver for TCPCI-compliant controller.
> >> +
> >> +endif
> >> +
> >>  endmenu
> >> diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile
> >> index bbe45721cf52..7dbaf8c3911d 100644
> >> --- a/drivers/usb/typec/Makefile
> >> +++ b/drivers/usb/typec/Makefile
> >> @@ -1,3 +1,4 @@
> >>  obj-$(CONFIG_TYPEC)          += typec.o
> >>  obj-$(CONFIG_TYPEC_WCOVE)    += typec_wcove.o
> >>  obj-$(CONFIG_TYPEC_TCPM)     += tcpm.o
> >> +obj-$(CONFIG_TYPEC_TCPCI)    += tcpci.o
> >> diff --git a/drivers/usb/typec/tcpci.c b/drivers/usb/typec/tcpci.c
> >> new file mode 100644 index 000000000000..af338218a1f3
> >> --- /dev/null
> >> +++ b/drivers/usb/typec/tcpci.c
> >> @@ -0,0 +1,487 @@
> >> +/*
> >> + * Copyright 2015-2016 Google, Inc
> >> + *
> >> + * This program is free software; you can redistribute it and/or
> >> +modify
> >> + * it under the terms of the GNU General Public License as published
> >> +by
> >> + * the Free Software Foundation; either version 2 of the License, or
> >> + * (at your option) any later version.
> >> + *
> >> + * This program is distributed in the hope that it will be useful,
> >> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> >> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> >> + * GNU General Public License for more details.
> >> + *
> >> + * USB Type-C Port Controller Interface.
> >> + */
> >> +
> >> +#include <linux/delay.h>
> >> +#include <linux/kernel.h>
> >> +#include <linux/module.h>
> >> +#include <linux/i2c.h>
> >> +#include <linux/interrupt.h>
> >> +#include <linux/regmap.h>
> >> +#include <linux/usb/pd.h>
> >> +#include <linux/usb/typec.h>
> >> +
> >> +#include "tcpci.h"
> >> +#include "tcpm.h"
> >> +
> >> +#define PD_RETRY_COUNT 3
> >> +
> >> +struct tcpci {
> >> +     struct device *dev;
> >> +     struct i2c_client *client;
> >> +
> >> +     struct tcpm_port *port;
> >> +
> >> +     struct regmap *regmap;
> >> +
> >> +     bool controls_vbus;
> >> +
> >> +     struct tcpc_dev tcpc;
> >> +};
> >> +
> >> +static inline struct tcpci *tcpc_to_tcpci(struct tcpc_dev *tcpc) {
> >> +     return container_of(tcpc, struct tcpci, tcpc); }
> >> +
> >> +static int tcpci_read16(struct tcpci *tcpci, unsigned int reg,
> >> +                     unsigned int *val) {
> >> +     return regmap_raw_read(tcpci->regmap, reg, val, sizeof(u16)); }
> >> +
> >> +static int tcpci_write16(struct tcpci *tcpci, unsigned int reg, u16
> >> +val) {
> >> +     return regmap_raw_write(tcpci->regmap, reg, &val, sizeof(u16));
> >> +}
> >> +
> >> +static int tcpci_set_cc(struct tcpc_dev *tcpc, enum typec_cc_status
> >> +cc) {
> >> +     struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
> >> +     unsigned int reg;
> >> +     int ret;
> >> +
> >> +     switch (cc) {
> >> +     case TYPEC_CC_RA:
> >> +             reg = (TCPC_ROLE_CTRL_CC_RA << TCPC_ROLE_CTRL_CC1_SHIFT)
> |
> >> +                     (TCPC_ROLE_CTRL_CC_RA <<
> TCPC_ROLE_CTRL_CC2_SHIFT);
> >> +             break;
> >> +     case TYPEC_CC_RD:
> >> +             reg = (TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC1_SHIFT)
> |
> >> +                     (TCPC_ROLE_CTRL_CC_RD <<
> TCPC_ROLE_CTRL_CC2_SHIFT);
> >> +             break;
> >> +     case TYPEC_CC_RP_DEF:
> >> +             reg = (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC1_SHIFT)
> |
> >> +                     (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC2_SHIFT)
> |
> >> +                     (TCPC_ROLE_CTRL_RP_VAL_DEF <<
> >> +                      TCPC_ROLE_CTRL_RP_VAL_SHIFT);
> >> +             break;
> >> +     case TYPEC_CC_RP_1_5:
> >> +             reg = (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC1_SHIFT)
> |
> >> +                     (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC2_SHIFT)
> |
> >> +                     (TCPC_ROLE_CTRL_RP_VAL_1_5 <<
> >> +                      TCPC_ROLE_CTRL_RP_VAL_SHIFT);
> >> +             break;
> >> +     case TYPEC_CC_RP_3_0:
> >> +             reg = (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC1_SHIFT)
> |
> >> +                     (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC2_SHIFT)
> |
> >> +                     (TCPC_ROLE_CTRL_RP_VAL_1_5 <<
> >> +                      TCPC_ROLE_CTRL_RP_VAL_SHIFT);
> >> +             break;
> >> +     case TYPEC_CC_OPEN:
> >> +     default:
> >> +             reg = (TCPC_ROLE_CTRL_CC_OPEN << TCPC_ROLE_CTRL_CC1_SHIFT)
> |
> >> +                     (TCPC_ROLE_CTRL_CC_OPEN <<
> TCPC_ROLE_CTRL_CC2_SHIFT);
> >> +             break;
> >> +     }
> >> +
> >> +     ret = regmap_write(tcpci->regmap, TCPC_ROLE_CTRL, reg);
> >> +     if (ret < 0)
> >> +             return ret;
> >> +
> >> +     return 0;
> >> +}
> >> +
> >> +static enum typec_cc_status tcpci_to_typec_cc(unsigned int cc, bool
> >> +sink) {
> >> +     switch (cc) {
> >> +     case 0x1:
> >> +             return sink ? TYPEC_CC_RP_DEF : TYPEC_CC_RA;
> >> +     case 0x2:
> >> +             return sink ? TYPEC_CC_RP_1_5 : TYPEC_CC_RD;
> >> +     case 0x3:
> >> +             if (sink)
> >> +                     return TYPEC_CC_RP_3_0;
> >> +     case 0x0:
> >> +     default:
> >> +             return TYPEC_CC_OPEN;
> >> +     }
> >> +}
> >> +
> >> +static int tcpci_get_cc(struct tcpc_dev *tcpc,
> >> +                     enum typec_cc_status *cc1, enum typec_cc_status
> *cc2) {
> >> +     struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
> >> +     unsigned int reg;
> >> +     int ret;
> >> +
> >> +     ret = regmap_read(tcpci->regmap, TCPC_CC_STATUS, &reg);
> >> +     if (ret < 0)
> >> +             return ret;
> >> +
> >> +     *cc1 = tcpci_to_typec_cc((reg >> TCPC_CC_STATUS_CC1_SHIFT) &
> >> +                              TCPC_CC_STATUS_CC1_MASK,
> >> +                              reg & TCPC_CC_STATUS_TERM);
> >> +     *cc2 = tcpci_to_typec_cc((reg >> TCPC_CC_STATUS_CC2_SHIFT) &
> >> +                              TCPC_CC_STATUS_CC2_MASK,
> >> +                              reg & TCPC_CC_STATUS_TERM);
> >> +
> >> +     return 0;
> >> +}
> >> +
> >> +static int tcpci_set_polarity(struct tcpc_dev *tcpc,
> >> +                           enum typec_cc_polarity polarity) {
> >> +     struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
> >> +     int ret;
> >> +
> >> +     ret = regmap_write(tcpci->regmap, TCPC_TCPC_CTRL,
> >> +                        (polarity == TYPEC_POLARITY_CC2) ?
> >> +                        TCPC_TCPC_CTRL_ORIENTATION : 0);
> >> +     if (ret < 0)
> >> +             return ret;
> >> +
> >> +     return 0;
> >> +}
> >> +
> >> +static int tcpci_set_vconn(struct tcpc_dev *tcpc, bool enable) {
> >> +     struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
> >> +     int ret;
> >> +
> >> +     ret = regmap_write(tcpci->regmap, TCPC_POWER_CTRL,
> >> +                        enable ? TCPC_POWER_CTRL_VCONN_ENABLE : 0);
> >> +     if (ret < 0)
> >> +             return ret;
> >> +
> >> +     return 0;
> >> +}
> >> +
> >> +static int tcpci_set_pd_header(struct tcpc_dev *tcpc, enum
> >> +typec_role
> >> role,
> >> +                            enum typec_data_role data) {
> >> +     struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
> >> +     unsigned int reg;
> >> +     int ret;
> >> +
> >> +     reg = PD_REV20 << TCPC_MSG_HDR_INFO_REV_SHIFT;
> >> +     if (role == TYPEC_SOURCE)
> >> +             reg |= TCPC_MSG_HDR_INFO_PWR_ROLE;
> >> +     if (data == TYPEC_HOST)
> >> +             reg |= TCPC_MSG_HDR_INFO_DATA_ROLE;
> >> +     ret = regmap_write(tcpci->regmap, TCPC_MSG_HDR_INFO, reg);
> >> +     if (ret < 0)
> >> +             return ret;
> >> +
> >> +     return 0;
> >> +}
> >> +
> >> +static int tcpci_set_pd_rx(struct tcpc_dev *tcpc, bool enable) {
> >> +     struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
> >> +     unsigned int reg = 0;
> >> +     int ret;
> >> +
> >> +     if (enable)
> >> +             reg = TCPC_RX_DETECT_SOP | TCPC_RX_DETECT_HARD_RESET;
> >> +     ret = regmap_write(tcpci->regmap, TCPC_RX_DETECT, reg);
> >> +     if (ret < 0)
> >> +             return ret;
> >> +
> >> +     return 0;
> >> +}
> >> +
> >> +static int tcpci_get_vbus(struct tcpc_dev *tcpc) {
> >> +     struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
> >> +     unsigned int reg;
> >> +     int ret;
> >> +
> >> +     ret = regmap_read(tcpci->regmap, TCPC_POWER_STATUS, &reg);
> >> +     if (ret < 0)
> >> +             return ret;
> >> +
> >> +     return !!(reg & TCPC_POWER_STATUS_VBUS_PRES); }
> >> +
> >> +static int tcpci_set_vbus(struct tcpc_dev *tcpc, bool source, bool
> >> +sink) {
> >> +     struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
> >> +     int ret;
> >> +
> >> +     if (source) {
> >> +             ret = regmap_write(tcpci->regmap, TCPC_COMMAND,
> >> +                                TCPC_CMD_SRC_VBUS_DEFAULT);
> >> +             if (ret < 0)
> >> +                     return ret;
> >> +     } else {
> >> +             ret = regmap_write(tcpci->regmap, TCPC_COMMAND,
> >> +                                TCPC_CMD_DISABLE_SRC_VBUS);
> >> +             if (ret < 0)
> >> +                     return ret;
> >> +     }
> >> +
> >> +     if (sink) {
> >> +             ret = regmap_write(tcpci->regmap, TCPC_COMMAND,
> >> +                                TCPC_CMD_SINK_VBUS);
> >> +             if (ret < 0)
> >> +                     return ret;
> >> +     } else {
> >> +             ret = regmap_write(tcpci->regmap, TCPC_COMMAND,
> >> +                                TCPC_CMD_DISABLE_SINK_VBUS);
> >> +             if (ret < 0)
> >> +                     return ret;
> >> +     }
> >> +
> >
> > Per tcpc spec, disable should be firstly done before enable, so should
> > it be done like this?
> > if(!source) {};
> > if(!sink) {};
> > if(source) {};
> > if(sink) {};
> >
> Makes sense. Changed accordingly.
> 
> >> +     return 0;
> >> +}
> >> +
> >> +static int tcpci_pd_transmit(struct tcpc_dev *tcpc,
> >> +                          enum tcpm_transmit_type type,
> >> +                          const struct pd_message *msg) {
> >> +     struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
> >> +     unsigned int reg, cnt, header;
> >> +     int ret;
> >> +
> >> +     cnt = msg ? pd_header_cnt(msg->header) * 4 : 0;
> >> +     ret = regmap_write(tcpci->regmap, TCPC_TX_BYTE_CNT, cnt);
> >
> > Here miss the 2 bytes' header, so cnt should be cnt += 2;
> >
> > Tcpc 4.4.13
> > TRANSMIT_BYTE_COUNT:
> > The number of bytes the TCPM will write This is the number of bytes in
> > the TX_BUFFER_DATA_OBJECTS plus two (for the TX_BUF_HEADER)
> >
> Yes, you are correct. Fixed.
> 
> >> +     if (ret < 0)
> >> +             return ret;
> >> +
> >> +     header = msg ? msg->header : 0;
> >> +     ret = tcpci_write16(tcpci, TCPC_TX_HDR, header);
> >> +     if (ret < 0)
> >> +             return ret;
> >> +
> >> +     if (cnt > 0) {
> >> +             ret = regmap_raw_write(tcpci->regmap, TCPC_TX_DATA,
> >> +                                    &msg->payload, cnt);
> >> +             if (ret < 0)
> >> +                     return ret;
> >> +     }
> >> +
> >> +     reg = (PD_RETRY_COUNT << TCPC_TRANSMIT_RETRY_SHIFT) |
> >> +             (type << TCPC_TRANSMIT_TYPE_SHIFT);
> >> +     ret = regmap_write(tcpci->regmap, TCPC_TRANSMIT, reg);
> >> +     if (ret < 0)
> >> +             return ret;
> >> +
> >> +     return 0;
> >> +}
> >> +
> >
> > ...
> >
> >> +
> >> +static int tcpci_probe(struct i2c_client *client,
> >> +                    const struct i2c_device_id *i2c_id) {
> >> +     struct tcpci *tcpci;
> >> +     int err;
> >> +
> >> +     tcpci = devm_kzalloc(&client->dev, sizeof(*tcpci), GFP_KERNEL);
> >> +     if (!tcpci)
> >> +             return -ENOMEM;
> >> +
> >> +     tcpci->client = client;
> >> +     tcpci->dev = &client->dev;
> >> +     i2c_set_clientdata(client, tcpci);
> >> +     tcpci->regmap = devm_regmap_init_i2c(client,
> &tcpci_regmap_config);
> >> +     if (IS_ERR(tcpci->regmap))
> >> +             return PTR_ERR(tcpci->regmap);
> >> +
> >> +     tcpci->tcpc.init = tcpci_init;
> >> +     tcpci->tcpc.get_vbus = tcpci_get_vbus;
> >> +     tcpci->tcpc.set_vbus = tcpci_set_vbus;
> >> +     tcpci->tcpc.set_cc = tcpci_set_cc;
> >> +     tcpci->tcpc.get_cc = tcpci_get_cc;
> >> +     tcpci->tcpc.set_polarity = tcpci_set_polarity;
> >> +     tcpci->tcpc.set_vconn = tcpci_set_vconn;
> >> +
> >> +     tcpci->tcpc.set_pd_rx = tcpci_set_pd_rx;
> >> +     tcpci->tcpc.set_pd_header = tcpci_set_pd_header;
> >> +     tcpci->tcpc.pd_transmit = tcpci_pd_transmit;
> >> +
> >> +     err = tcpci_parse_config(tcpci);
> >> +     if (err < 0)
> >> +             return err;
> >> +
> >
> > As the alert mask reg is defaultly to be unmasked after power on, It's
> > better clear and mask all before request irq.
> >
> Agreed and done.
> 
> >> +     err = devm_request_threaded_irq(tcpci->dev, client->irq, NULL,
> >> +                                     tcpci_irq,
> >> + IRQF_TRIGGER_FALLING,
> >
> > IRQF_ONESHOT is required, and do we need the irq trigger to be
> > IRQF_TRIGGER_LOW?
> >
> 
> Good question; I don't really know. IRQF_TRIGGER_FALLING definitely seems
> to be wrong (and I don't recall where I got it from). Not sure about
> IRQF_TRIGGER_LOW. Can you test on real hardware, by any chance ?
> 

I tested on real hardware, IRQF_TRIGGER_LOW can work fine, so I think
the right flag should be IRQF_TRIGGER_LOW | IRQF_ONESHOT.

Li Jun

> Thanks,
> Guenter
> 
> 
> > Li Jun

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [RFC PATCH v3 2/2] usb: typec: Type-C Port Controller Interface driver (tcpci)
  2016-10-01  0:12       ` Jun Li
@ 2016-10-01 16:27         ` Guenter Roeck
  0 siblings, 0 replies; 24+ messages in thread
From: Guenter Roeck @ 2016-10-01 16:27 UTC (permalink / raw)
  To: Jun Li
  Cc: Guenter Roeck, Felipe Balbi, Chandra Sekhar Anagani,
	Bruce Ashfield, Bin Gao, Pranav Tipnis, Heikki Krogerus,
	linux-kernel, linux-usb

On Fri, Sep 30, 2016 at 5:12 PM, Jun Li <jun.li@nxp.com> wrote:
[ ... ]
>>
>> >> +     err = devm_request_threaded_irq(tcpci->dev, client->irq, NULL,
>> >> +                                     tcpci_irq,
>> >> + IRQF_TRIGGER_FALLING,
>> >
>> > IRQF_ONESHOT is required, and do we need the irq trigger to be
>> > IRQF_TRIGGER_LOW?
>> >
>>
>> Good question; I don't really know. IRQF_TRIGGER_FALLING definitely seems
>> to be wrong (and I don't recall where I got it from). Not sure about
>> IRQF_TRIGGER_LOW. Can you test on real hardware, by any chance ?
>>
>
> I tested on real hardware, IRQF_TRIGGER_LOW can work fine, so I think
> the right flag should be IRQF_TRIGGER_LOW | IRQF_ONESHOT.
>
I had wondered if the trigger should be part of the devicetree file.
However, TCPCI specifies (chapter 4, bullet point 6), that "The TCPC
shall have an open drain output, active low Alert# Pin. This pin is
used to indicate a change of state, where Alert# pin is asserted when
a ny Alert Bits are set."

Given that, I agree, and I'll add IRQF_TRIGGER_LOW.

Thanks,
Guenter

^ permalink raw reply	[flat|nested] 24+ messages in thread

* Re: [RFC PATCH v3 1/2] usb: typec: USB Type-C Port Manager (tcpm)
  2016-08-23 21:10 ` [RFC PATCH v3 1/2] usb: typec: USB Type-C Port Manager (tcpm) Guenter Roeck
                     ` (2 preceding siblings ...)
  2016-09-30  6:37   ` Jun Li
@ 2016-10-03 14:04   ` Heikki Krogerus
  3 siblings, 0 replies; 24+ messages in thread
From: Heikki Krogerus @ 2016-10-03 14:04 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Felipe Balbi, Chandra Sekhar Anagani, Bruce Ashfield, Bin Gao,
	Pranav Tipnis, linux-kernel, linux-usb

Hi Guenter,

On Tue, Aug 23, 2016 at 02:10:50PM -0700, Guenter Roeck wrote:
> +config TYPEC_TCPM
> +	tristate "USB Type-C Port Controller Manager"
> +	select TYPEC
> +	help
> +	  The Type-C Port Controller Manager provides a USB PD and USB Type-C
> +	  state machine for use with Type-C Port Controllers.

Since this is "tristate", don't forget to add MODULE_*() stuff in the
next version.


Thanks,

-- 
heikki

^ permalink raw reply	[flat|nested] 24+ messages in thread

end of thread, other threads:[~2016-10-03 14:09 UTC | newest]

Thread overview: 24+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-08-23 21:10 [RFC PATCH v3 0/2] Type-C Port Manager Guenter Roeck
2016-08-23 21:10 ` [RFC PATCH v3 1/2] usb: typec: USB Type-C Port Manager (tcpm) Guenter Roeck
2016-09-10  0:26   ` Jun Li
2016-09-10  2:23     ` Guenter Roeck
2016-09-12  2:16       ` Jun Li
2016-09-12  2:23         ` Guenter Roeck
2016-09-12  2:41           ` Jun Li
2016-09-29 14:35   ` Jun Li
2016-09-29 16:36     ` Guenter Roeck
2016-09-30  6:41       ` Jun Li
2016-09-30 15:46         ` Guenter Roeck
2016-09-30  6:37   ` Jun Li
2016-09-30 19:06     ` Guenter Roeck
2016-09-30 19:41       ` Joe Perches
2016-09-30 20:57         ` Guenter Roeck
2016-09-30 21:02           ` Joe Perches
2016-10-03 14:04   ` Heikki Krogerus
2016-08-23 21:10 ` [RFC PATCH v3 2/2] usb: typec: Type-C Port Controller Interface driver (tcpci) Guenter Roeck
2016-09-30  6:24   ` Jun Li
2016-09-30 18:45     ` Guenter Roeck
2016-10-01  0:12       ` Jun Li
2016-10-01 16:27         ` Guenter Roeck
2016-08-24 12:52 ` [RFC PATCH v3 0/2] Type-C Port Manager Heikki Krogerus
2016-08-24 17:56   ` Guenter Roeck

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.