Skip to contentSkip to conversation

usb: typec: Add USB Power Delivery sink port support

PATCH In review Bin Gao 3 participants 5 messages Jul 26, 2016 – Jul 27, 2016

Subsystems (2)
lore
Share & tools
lore

Bin Gao

author
#0 lore raw

PATCH v2 1/2 usb: typec: Add USB Power Delivery sink port support

This patch implements a simple USB Power Delivery sink port state machine. It assumes the hardware only handles PD packet transmitting and receiving over the CC line of the USB Type-C connector. The state transition is completely controlled by software. This patch only implement the sink port function and it doesn't support source port and port swap yet.

This patch depends on these two patches:
https://lkml.org/lkml/2016/6/29/349
https://lkml.org/lkml/2016/6/29/350

Changes in v2:
- Removed work queue so messages are directly handled in phy driver's interrupt context
- used pr_debug instead of pr_info for message dump
- Converted PD driver to tristate and typec driver is independent of it

---
 drivers/usb/typec/Kconfig      |  11 +
 drivers/usb/typec/Makefile     |   1 +
 drivers/usb/typec/pd_sink.c    | 908 +++++++++++++++++++++++++++++++++++++++++
 include/linux/usb/pd_message.h | 369 +++++++++++++++++
 include/linux/usb/pd_sink.h    | 299 ++++++++++++++
 5 files changed, 1588 insertions(+)
 create mode 100644 drivers/usb/typec/pd_sink.c
 create mode 100644 include/linux/usb/pd_message.h
 create mode 100644 include/linux/usb/pd_sink.h
drivers/usb/typec/Kconfig +11 -0
index 7a345a4..662aa54 100644
@@ -4,6 +4,17 @@ menu "USB PD and Type-C drivers"
 config TYPEC
 	tristate
 
+config USB_PD_SINK
+	tristate "USB Power Delivery Sink Port State Machine Driver"
+	help
+	  Enable this to support USB PD(Power Delivery) Sink port.
+	  This driver implements a simple USB PD sink state machine.
+	  The underlying TypeC phy driver is responsible for cable
+	  plug/unplug event, port orientation detection, transmitting
+	  and receiving PD messages. This driver only process messages
+	  received by the TypeC phy driver and maintain the sink port's
+	  state machine.
+
 config TYPEC_WCOVE
 	tristate "Intel WhiskeyCove PMIC USB Type-C PHY driver"
 	depends on ACPI
drivers/usb/typec/Makefile +1 -0
index b9cb862..74aad6c 100644
@@ -1,2 +1,3 @@
 obj-$(CONFIG_TYPEC)		+= typec.o
+obj-$(CONFIG_USB_PD_SINK)	+= pd_sink.o
 obj-$(CONFIG_TYPEC_WCOVE)	+= typec_wcove.o
drivers/usb/typec/pd_sink.c new file +908 -0

908 lines not on this page · raw message

include/linux/usb/pd_message.h new file +369 -0

369 lines not on this page · raw message

include/linux/usb/pd_sink.h new file +299 -0

299 lines not on this page · raw message

Signature · 1 line
1.9.1
#1 lore raw

Hi,

Quoted 10 lines · Bin Gao

This patch implements a simple USB Power Delivery sink port state machine. It assumes the hardware only handles PD packet transmitting and receiving over the CC line of the USB Type-C connector. The state transition is completely controlled by software. This patch only implement the sink port function and it doesn't support source port and port swap yet.

This patch depends on these two patches:
https://lkml.org/lkml/2016/6/29/349
https://lkml.org/lkml/2016/6/29/350

Quoted 5 lines

Changes in v2:
- Removed work queue so messages are directly handled in phy driver's interrupt context
- used pr_debug instead of pr_info for message dump
- Converted PD driver to tristate and typec driver is independent of it

this should be after the tearline (---) below. We don't want this in changelog ;-)

Quoted 41 lines
drivers/usb/typec/pd_sink.c new file +41 -0
+static void handle_source_cap(struct pd_sink_port *port, u8 msg_revision,
+						u8 nr_objs, u8 *buf)
+{
+	int i;
+	u32 *obj;
+	u8 type;
+	struct pd_source_cap *cap = port->source_caps;
+
+	/*
+	 * The PD spec revision included in SOURCE_CAPABILITY message is the
+	 * highest revision that the Source supports.
+	 */
+	port->pd_rev = msg_revision;
+
+	/*
+	 * First we need to save all PDOs - they may be used in the future.
+	 * USB PD spec says we must use PDOs in the most recent
+	 * SOURCE_CAPABILITY message. Here we replace old PDOs with new ones.
+	 */
+	port->nr_source_caps = 0;
+	for (i = 0; i < nr_objs; i++) {
+		obj = (u32 *)(buf + i * PD_OBJ_SIZE);
+		type = (*obj >> SOURCE_CAP_TYPE_BIT) & SOURCE_CAP_TYPE_MASK;
+		switch (type) {
+		case PS_TYPE_FIXED:
+			cap->ps_type = PS_TYPE_FIXED;
+			cap->fixed = *(struct pd_pdo_src_fixed *)obj;
+			break;
+		case PS_TYPE_VARIABLE:
+			cap->ps_type = PS_TYPE_VARIABLE;
+			cap->variable = *(struct pd_pdo_variable *)obj;
+			break;
+		case PS_TYPE_BATTERY:
+			cap->ps_type = PS_TYPE_BATTERY;
+			cap->battery = *(struct pd_pdo_battery *)obj;
+			break;
+		default: /* shouldn't come here */
+			pr_err("Invalid Source Capability type: %u.\n", type);
+			continue;
+		}
+		port->nr_source_caps++;

drivers/usb/typec/pd_sink.c line 575

Quoted 10 lines
drivers/usb/typec/pd_sink.c new file +10 -0
+		cap++;
+	}
+
+	if (port->nr_source_caps == 0) {
+		pr_err("There is no valid PDOs in SOURCE_CAPABILITY message\n");
+		return;
+	}
+
+	/* If a contract is not established, we need send a REQUEST message */
+	if (port->state == PD_SINK_STATE_WAIT_FOR_SRC_CAP) {

this is wrong. Read the fluxchart in figure 8-42. Source can decide to send another Source Capability before receiving our Good CRC and we need to work with that. This state check is, at a minimum, wrong. I'd actually just go ahead and remove it.

Quoted 4 lines
drivers/usb/typec/pd_sink.c new file +4 -0
+		if (!send_request(port))
+			port->state = PD_SINK_STATE_REQUEST_SENT;
+	}
+}
Signature · 1 line
balbi
author
#2 lore raw
Quoted 80 lines · Felipe Balbi

Hi,

Quoted 10 lines · Bin Gao

This patch implements a simple USB Power Delivery sink port state machine. It assumes the hardware only handles PD packet transmitting and receiving over the CC line of the USB Type-C connector. The state transition is completely controlled by software. This patch only implement the sink port function and it doesn't support source port and port swap yet.

This patch depends on these two patches:
https://lkml.org/lkml/2016/6/29/349
https://lkml.org/lkml/2016/6/29/350

Quoted 5 lines

Changes in v2:
- Removed work queue so messages are directly handled in phy driver's interrupt context
- used pr_debug instead of pr_info for message dump
- Converted PD driver to tristate and typec driver is independent of it

this should be after the tearline (---) below. We don't want this in changelog ;-)

Quoted 41 lines
drivers/usb/typec/pd_sink.c new file +41 -0
+static void handle_source_cap(struct pd_sink_port *port, u8 msg_revision,
+						u8 nr_objs, u8 *buf)
+{
+	int i;
+	u32 *obj;
+	u8 type;
+	struct pd_source_cap *cap = port->source_caps;
+
+	/*
+	 * The PD spec revision included in SOURCE_CAPABILITY message is the
+	 * highest revision that the Source supports.
+	 */
+	port->pd_rev = msg_revision;
+
+	/*
+	 * First we need to save all PDOs - they may be used in the future.
+	 * USB PD spec says we must use PDOs in the most recent
+	 * SOURCE_CAPABILITY message. Here we replace old PDOs with new ones.
+	 */
+	port->nr_source_caps = 0;
+	for (i = 0; i < nr_objs; i++) {
+		obj = (u32 *)(buf + i * PD_OBJ_SIZE);
+		type = (*obj >> SOURCE_CAP_TYPE_BIT) & SOURCE_CAP_TYPE_MASK;
+		switch (type) {
+		case PS_TYPE_FIXED:
+			cap->ps_type = PS_TYPE_FIXED;
+			cap->fixed = *(struct pd_pdo_src_fixed *)obj;
+			break;
+		case PS_TYPE_VARIABLE:
+			cap->ps_type = PS_TYPE_VARIABLE;
+			cap->variable = *(struct pd_pdo_variable *)obj;
+			break;
+		case PS_TYPE_BATTERY:
+			cap->ps_type = PS_TYPE_BATTERY;
+			cap->battery = *(struct pd_pdo_battery *)obj;
+			break;
+		default: /* shouldn't come here */
+			pr_err("Invalid Source Capability type: %u.\n", type);
+			continue;
+		}
+		port->nr_source_caps++;

drivers/usb/typec/pd_sink.c line 575

Quoted 10 lines
drivers/usb/typec/pd_sink.c new file +10 -0
+		cap++;
+	}
+
+	if (port->nr_source_caps == 0) {
+		pr_err("There is no valid PDOs in SOURCE_CAPABILITY message\n");
+		return;
+	}
+
+	/* If a contract is not established, we need send a REQUEST message */
+	if (port->state == PD_SINK_STATE_WAIT_FOR_SRC_CAP) {

this is wrong. Read the fluxchart in figure 8-42. Source can decide to send another Source Capability before receiving our Good CRC and we need to work with that. This state check is, at a minimum, wrong. I'd actually just go ahead and remove it.

Quoted 7 lines
Quoted 4 lines
drivers/usb/typec/pd_sink.c new file +4 -0
+		if (!send_request(port))
+			port->state = PD_SINK_STATE_REQUEST_SENT;
+	}
+}
Signature · 1 line
balbi

I'll fix these in next revision. Thanks for your review.

-Bin

#3 lore raw
In reply to Bin Gao
Quoted 109 lines · Bin Gao
drivers/usb/typec/pd_sink.c new file +78 -0
+#define MAKE_HEADER(port, header, msg, objs) \
+do { \
+       header->type = msg; \
+       header->data_role = PD_DATA_ROLE_UFP; \
+       header->revision = port->pd_rev; \
+       header->power_role = PD_POWER_ROLE_SINK; \
+       header->id = roll_msg_id(port); \
+       header->nr_objs = objs; \
+       header->extended = PD_MSG_NOT_EXTENDED; \
+} while (0)
+
+static struct pd_sink_port *sink_ports[MAX_NR_SINK_PORTS];
+static int nr_ports;
+
+BLOCKING_NOTIFIER_HEAD(pd_sink_notifier_list);
+
+static char *state_strings[] = {
+       "WAIT_FOR_SOURCE_CAPABILITY",
+       "REQUEST_SENT",
+       "ACCEPT_RECEIVED",
+       "POWER_SUPPLY_READY",
+};
+
+/* Control messages */
+static char *cmsg_strings[] = {
+       "GOODCRC",                      /* 1 */
+       "GOTOMIN",                      /* 2 */
+       "ACCEPT",                       /* 3 */
+       "REJECT",                       /* 4 */
+       "PING",                         /* 5 */
+       "PS_RDY",                       /* 6 */
+       "GET_SRC_CAP",                  /* 7 */
+       "GET_SINK_CAP",                 /* 8 */
+       "DR_SWAP",                      /* 9 */
+       "PR_SWAP",                      /* 10 */
+       "VCONN_SWAP",                   /* 11 */
+       "WAIT",                         /* 12 */
+       "SOFT_RESET",                   /* 13 */
+       "RESERVED",                     /* 14 */
+       "RESERVED",                     /* 15 */
+       "NOT_SUPPORTED",                /* 16 */
+       "GET_SOURCE_CAP_EXTENDED",      /* 17 */
+       "GET_STATUS",                   /* 18 */
+       "FR_SWAP",                      /* 19 */
+       /* RESERVED                     20 - 31 */
+};
+
+/* Data messages */
+static char *dmsg_strings[] = {
+       "SOURCE_CAP",           /* 1 */
+       "REQUEST",              /* 2 */
+       "BIST",                 /* 3 */
+       "SINK_CAP",             /* 4 */
+       "BATTERY_STATUS",       /* 5 */
+       "ALERT",                /* 6 */
+       "RESERVED",             /* 7 */
+       "RESERVED",             /* 8 */
+       "RESERVED",             /* 9 */
+       "RESERVED",             /* 10 */
+       "RESERVED",             /* 11 */
+       "RESERVED",             /* 12 */
+       "RESERVED",             /* 13 */
+       "RESERVED",             /* 14 */
+       "VENDOR_DEFINED",       /* 15 */
+       /* RESERVED             16 - 31 */
+};
+
+static char *state_to_string(enum pd_sink_state state)
+{
+       if (state < ARRAY_SIZE(state_strings))
+               return state_strings[state];
+       else
+               return NULL;
+}
+
+static char *msg_to_string(bool is_cmsg, u8 msg)
+{
+       int nr = is_cmsg ? ARRAY_SIZE(cmsg_strings) :

ARRAY_SIZE(dmsg_strings);

drivers/usb/typec/pd_sink.c new file +3 -0
+
+       if (msg <= nr)
+               return is_cmsg ? cmsg_strings[msg - 1] :

dmsg_strings[msg - 1];

drivers/usb/typec/pd_sink.c new file +17 -0
+       else
+               return "RESERVED";
+}
+
+static void print_message(int port, bool is_cmsg, u8 msg, bool recv)
+{
+       pr_debug("sink port %d: %s message %s %s\n", port,
+                               is_cmsg ? "Control" : "Data",
+                               msg_to_string(is_cmsg, msg),
+                                recv ? "received" : "sent)");
+}
+
+static inline bool fixed_ps_equal(struct sink_ps *p1,
+                               struct sink_ps *p2)
+{
+       return p1->ps_type == p2->ps_type &&
+               p1->ps_fixed.voltage_fixed ==

p2->ps_fixed.voltage_fixed &&
+ p1->ps_fixed.current_default ==
p2->ps_fixed.current_default;

drivers/usb/typec/pd_sink.c new file +6 -0
+}
+
+/* The message ID increments each time we send out a new message */
+static u8 roll_msg_id(struct pd_sink_port *port)
+{
+       u8 msg_id = port->msg_id;

drivers/usb/typec/pd_sink.c line 141

Quoted 10 lines
drivers/usb/typec/pd_sink.c new file +10 -0
+
+       if (msg_id == PD_MSG_ID_MAX)
+               msg_id = PD_MSG_ID_MIN;
+       else
+               msg_id++;
+
+       port->msg_id = msg_id;
+       return msg_id;
+}
+

These pieces of code are completely generic. They should be shared among drivers. Please move them into the include files.

Regards
Oliver

author
#4 lore raw
Quoted 124 lines · Oliver Neukum
Quoted 109 lines · Bin Gao
drivers/usb/typec/pd_sink.c new file +78 -0
+#define MAKE_HEADER(port, header, msg, objs) \
+do { \
+       header->type = msg; \
+       header->data_role = PD_DATA_ROLE_UFP; \
+       header->revision = port->pd_rev; \
+       header->power_role = PD_POWER_ROLE_SINK; \
+       header->id = roll_msg_id(port); \
+       header->nr_objs = objs; \
+       header->extended = PD_MSG_NOT_EXTENDED; \
+} while (0)
+
+static struct pd_sink_port *sink_ports[MAX_NR_SINK_PORTS];
+static int nr_ports;
+
+BLOCKING_NOTIFIER_HEAD(pd_sink_notifier_list);
+
+static char *state_strings[] = {
+       "WAIT_FOR_SOURCE_CAPABILITY",
+       "REQUEST_SENT",
+       "ACCEPT_RECEIVED",
+       "POWER_SUPPLY_READY",
+};
+
+/* Control messages */
+static char *cmsg_strings[] = {
+       "GOODCRC",                      /* 1 */
+       "GOTOMIN",                      /* 2 */
+       "ACCEPT",                       /* 3 */
+       "REJECT",                       /* 4 */
+       "PING",                         /* 5 */
+       "PS_RDY",                       /* 6 */
+       "GET_SRC_CAP",                  /* 7 */
+       "GET_SINK_CAP",                 /* 8 */
+       "DR_SWAP",                      /* 9 */
+       "PR_SWAP",                      /* 10 */
+       "VCONN_SWAP",                   /* 11 */
+       "WAIT",                         /* 12 */
+       "SOFT_RESET",                   /* 13 */
+       "RESERVED",                     /* 14 */
+       "RESERVED",                     /* 15 */
+       "NOT_SUPPORTED",                /* 16 */
+       "GET_SOURCE_CAP_EXTENDED",      /* 17 */
+       "GET_STATUS",                   /* 18 */
+       "FR_SWAP",                      /* 19 */
+       /* RESERVED                     20 - 31 */
+};
+
+/* Data messages */
+static char *dmsg_strings[] = {
+       "SOURCE_CAP",           /* 1 */
+       "REQUEST",              /* 2 */
+       "BIST",                 /* 3 */
+       "SINK_CAP",             /* 4 */
+       "BATTERY_STATUS",       /* 5 */
+       "ALERT",                /* 6 */
+       "RESERVED",             /* 7 */
+       "RESERVED",             /* 8 */
+       "RESERVED",             /* 9 */
+       "RESERVED",             /* 10 */
+       "RESERVED",             /* 11 */
+       "RESERVED",             /* 12 */
+       "RESERVED",             /* 13 */
+       "RESERVED",             /* 14 */
+       "VENDOR_DEFINED",       /* 15 */
+       /* RESERVED             16 - 31 */
+};
+
+static char *state_to_string(enum pd_sink_state state)
+{
+       if (state < ARRAY_SIZE(state_strings))
+               return state_strings[state];
+       else
+               return NULL;
+}
+
+static char *msg_to_string(bool is_cmsg, u8 msg)
+{
+       int nr = is_cmsg ? ARRAY_SIZE(cmsg_strings) :

ARRAY_SIZE(dmsg_strings);

drivers/usb/typec/pd_sink.c new file +3 -0
+
+       if (msg <= nr)
+               return is_cmsg ? cmsg_strings[msg - 1] :

dmsg_strings[msg - 1];

drivers/usb/typec/pd_sink.c new file +17 -0
+       else
+               return "RESERVED";
+}
+
+static void print_message(int port, bool is_cmsg, u8 msg, bool recv)
+{
+       pr_debug("sink port %d: %s message %s %s\n", port,
+                               is_cmsg ? "Control" : "Data",
+                               msg_to_string(is_cmsg, msg),
+                                recv ? "received" : "sent)");
+}
+
+static inline bool fixed_ps_equal(struct sink_ps *p1,
+                               struct sink_ps *p2)
+{
+       return p1->ps_type == p2->ps_type &&
+               p1->ps_fixed.voltage_fixed ==

p2->ps_fixed.voltage_fixed &&
+ p1->ps_fixed.current_default ==
p2->ps_fixed.current_default;

drivers/usb/typec/pd_sink.c new file +6 -0
+}
+
+/* The message ID increments each time we send out a new message */
+static u8 roll_msg_id(struct pd_sink_port *port)
+{
+       u8 msg_id = port->msg_id;

drivers/usb/typec/pd_sink.c line 141

Quoted 10 lines
drivers/usb/typec/pd_sink.c new file +10 -0
+
+       if (msg_id == PD_MSG_ID_MAX)
+               msg_id = PD_MSG_ID_MIN;
+       else
+               msg_id++;
+
+       port->msg_id = msg_id;
+       return msg_id;
+}
+

These pieces of code are completely generic. They should be shared among drivers. Please move them into the include files.

Quoted 4 lines

Regards
Oliver

They are only internally used by this driver (USB PD state machine driver). And they are not used by drivers (typicall USB Type-C phy drivers) which use APIs exposed by this driver. So I think it's not appropriate to move to include files.