All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC PATCH v2 0/7] typec: tcpm: Add sink side support for PPS
@ 2017-11-14 11:44 ` Adam Thomson
  0 siblings, 0 replies; 28+ messages in thread
From: Adam Thomson @ 2017-11-14 11:44 UTC (permalink / raw)
  To: Heikki Krogerus, Guenter Roeck, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Yueyao Zhu, Rui Miguel Silva
  Cc: linux-usb, linux-pm, linux-kernel, support.opensource

This patch set adds sink side support for the PPS feature introduced in the
USB PD 3.0 specification.

The source PPS supply is represented using the Power Supply framework to provide
access and control APIs for dealing with it's operating voltage and current,
and switching between a standard PDO and PPS APDO operation. During standard PDO
operation the voltage and current is read-only, but for APDO PPS these are
writable as well to allow for control.

It should be noted that the keepalive for PPS is not handled within TCPM. The
expectation is that the external user will be required to ensure re-requests
occur regularly to ensure PPS remains and the source does not hard reset.

Changes in v2:
 - Use USB_PD and usb_pd prefixes for macros and inline functions in headers.
 - Negotiate spec revision of PD headers during initial contract agreement.
 - New headers now use SPDX tags for referencing correct license.

NOTE: Code changes are based on linux-next tag 'next-20171114' to pick up
the move out of staging of TCPM related code.

Adam Thomson (7):
  typec: tcpm: Add PD Rev 3.0 definitions to PD header
  typec: tcpm: Add ADO header for Alert message handling
  typec: tcpm: Add SDB header for Status message handling
  typec: tcpm: Add core support for sink side PPS
  power: supply: Add type for USB PD PPS chargers
  typec: tcpm: Represent source supply through power_supply class
  typec: tcpm: Add support for sink PPS related messages

 drivers/power/supply/power_supply_sysfs.c |   2 +-
 drivers/usb/typec/Kconfig                 |   1 +
 drivers/usb/typec/fusb302/Kconfig         |   2 +-
 drivers/usb/typec/fusb302/fusb302.c       |  63 +--
 drivers/usb/typec/tcpm.c                  | 851 +++++++++++++++++++++++++++++-
 include/linux/power_supply.h              |   1 +
 include/linux/usb/pd.h                    | 174 +++++-
 include/linux/usb/pd_ado.h                |  42 ++
 include/linux/usb/pd_ext_sdb.h            |  31 ++
 include/linux/usb/tcpm.h                  |   2 +-
 10 files changed, 1071 insertions(+), 98 deletions(-)
 create mode 100644 include/linux/usb/pd_ado.h
 create mode 100644 include/linux/usb/pd_ext_sdb.h

-- 
1.9.1

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

* [RFC PATCH v2 0/7] typec: tcpm: Add sink side support for PPS
@ 2017-11-14 11:44 ` Adam Thomson
  0 siblings, 0 replies; 28+ messages in thread
From: Adam Thomson @ 2017-11-14 11:44 UTC (permalink / raw)
  To: Heikki Krogerus, Guenter Roeck, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Yueyao Zhu, Rui Miguel Silva
  Cc: linux-usb, linux-pm, linux-kernel, support.opensource

This patch set adds sink side support for the PPS feature introduced in the
USB PD 3.0 specification.

The source PPS supply is represented using the Power Supply framework to provide
access and control APIs for dealing with it's operating voltage and current,
and switching between a standard PDO and PPS APDO operation. During standard PDO
operation the voltage and current is read-only, but for APDO PPS these are
writable as well to allow for control.

It should be noted that the keepalive for PPS is not handled within TCPM. The
expectation is that the external user will be required to ensure re-requests
occur regularly to ensure PPS remains and the source does not hard reset.

Changes in v2:
 - Use USB_PD and usb_pd prefixes for macros and inline functions in headers.
 - Negotiate spec revision of PD headers during initial contract agreement.
 - New headers now use SPDX tags for referencing correct license.

NOTE: Code changes are based on linux-next tag 'next-20171114' to pick up
the move out of staging of TCPM related code.

Adam Thomson (7):
  typec: tcpm: Add PD Rev 3.0 definitions to PD header
  typec: tcpm: Add ADO header for Alert message handling
  typec: tcpm: Add SDB header for Status message handling
  typec: tcpm: Add core support for sink side PPS
  power: supply: Add type for USB PD PPS chargers
  typec: tcpm: Represent source supply through power_supply class
  typec: tcpm: Add support for sink PPS related messages

 drivers/power/supply/power_supply_sysfs.c |   2 +-
 drivers/usb/typec/Kconfig                 |   1 +
 drivers/usb/typec/fusb302/Kconfig         |   2 +-
 drivers/usb/typec/fusb302/fusb302.c       |  63 +--
 drivers/usb/typec/tcpm.c                  | 851 +++++++++++++++++++++++++++++-
 include/linux/power_supply.h              |   1 +
 include/linux/usb/pd.h                    | 174 +++++-
 include/linux/usb/pd_ado.h                |  42 ++
 include/linux/usb/pd_ext_sdb.h            |  31 ++
 include/linux/usb/tcpm.h                  |   2 +-
 10 files changed, 1071 insertions(+), 98 deletions(-)
 create mode 100644 include/linux/usb/pd_ado.h
 create mode 100644 include/linux/usb/pd_ext_sdb.h

-- 
1.9.1

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

* [RFC PATCH v2 1/7] typec: tcpm: Add PD Rev 3.0 definitions to PD header
  2017-11-14 11:44 ` Adam Thomson
@ 2017-11-14 11:44   ` Adam Thomson
  -1 siblings, 0 replies; 28+ messages in thread
From: Adam Thomson @ 2017-11-14 11:44 UTC (permalink / raw)
  To: Heikki Krogerus, Guenter Roeck, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Yueyao Zhu, Rui Miguel Silva
  Cc: linux-usb, linux-pm, linux-kernel, support.opensource

This commit adds definitions for PD Rev 3.0 messages, including
APDO PPS and extended message support for TCPM.

Signed-off-by: Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
---
 include/linux/usb/pd.h | 174 +++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 162 insertions(+), 12 deletions(-)

diff --git a/include/linux/usb/pd.h b/include/linux/usb/pd.h
index e00051c..8c9ac80 100644
--- a/include/linux/usb/pd.h
+++ b/include/linux/usb/pd.h
@@ -35,6 +35,13 @@ enum pd_ctrl_msg_type {
 	PD_CTRL_WAIT = 12,
 	PD_CTRL_SOFT_RESET = 13,
 	/* 14-15 Reserved */
+	PD_CTRL_NOT_SUPP = 16,
+	PD_CTRL_GET_SOURCE_CAP_EXT = 17,
+	PD_CTRL_GET_STATUS = 18,
+	PD_CTRL_FR_SWAP = 19,
+	PD_CTRL_GET_PPS_STATUS = 20,
+	PD_CTRL_GET_COUNTRY_CODES = 21,
+	/* 22-31 Reserved */
 };
 
 enum pd_data_msg_type {
@@ -43,13 +50,39 @@ enum pd_data_msg_type {
 	PD_DATA_REQUEST = 2,
 	PD_DATA_BIST = 3,
 	PD_DATA_SINK_CAP = 4,
-	/* 5-14 Reserved */
+	PD_DATA_BATT_STATUS = 5,
+	PD_DATA_ALERT = 6,
+	PD_DATA_GET_COUNTRY_INFO = 7,
+	/* 8-14 Reserved */
 	PD_DATA_VENDOR_DEF = 15,
+	/* 16-31 Reserved */
+};
+
+enum pd_ext_msg_type {
+	/* 0 Reserved */
+	PD_EXT_SOURCE_CAP_EXT = 1,
+	PD_EXT_STATUS = 2,
+	PD_EXT_GET_BATT_CAP = 3,
+	PD_EXT_GET_BATT_STATUS = 4,
+	PD_EXT_BATT_CAP = 5,
+	PD_EXT_GET_MANUFACTURER_INFO = 6,
+	PD_EXT_MANUFACTURER_INFO = 7,
+	PD_EXT_SECURITY_REQUEST = 8,
+	PD_EXT_SECURITY_RESPONSE = 9,
+	PD_EXT_FW_UPDATE_REQUEST = 10,
+	PD_EXT_FW_UPDATE_RESPONSE = 11,
+	PD_EXT_PPS_STATUS = 12,
+	PD_EXT_COUNTRY_INFO = 13,
+	PD_EXT_COUNTRY_CODES = 14,
+	/* 15-31 Reserved */
 };
 
 #define PD_REV10	0x0
 #define PD_REV20	0x1
+#define PD_REV30	0x2
+#define PD_MAX_REV	PD_REV30
 
+#define PD_HEADER_EXT_HDR	BIT(15)
 #define PD_HEADER_CNT_SHIFT	12
 #define PD_HEADER_CNT_MASK	0x7
 #define PD_HEADER_ID_SHIFT	9
@@ -59,18 +92,19 @@ enum pd_data_msg_type {
 #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_MASK	0x1f
 
-#define PD_HEADER(type, pwr, data, id, cnt)				\
+#define PD_HEADER(type, pwr, data, rev, id, cnt, ext_hdr)		\
 	((((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) |				\
+	 (rev << PD_HEADER_REV_SHIFT) |					\
 	 (((id) & PD_HEADER_ID_MASK) << PD_HEADER_ID_SHIFT) |		\
-	 (((cnt) & PD_HEADER_CNT_MASK) << PD_HEADER_CNT_SHIFT))
+	 (((cnt) & PD_HEADER_CNT_MASK) << PD_HEADER_CNT_SHIFT) |	\
+	 ((ext_hdr) ? PD_HEADER_EXT_HDR : 0))
 
-#define PD_HEADER_LE(type, pwr, data, id, cnt) \
-	cpu_to_le16(PD_HEADER((type), (pwr), (data), (id), (cnt)))
+#define PD_HEADER_LE(type, pwr, data, rev, id, cnt) \
+	cpu_to_le16(PD_HEADER((type), (pwr), (data), (rev), (id), (cnt), (0)))
 
 static inline unsigned int pd_header_cnt(u16 header)
 {
@@ -102,16 +136,75 @@ static inline unsigned int pd_header_msgid_le(__le16 header)
 	return pd_header_msgid(le16_to_cpu(header));
 }
 
+static inline unsigned int pd_header_rev(u16 header)
+{
+	return (header >> PD_HEADER_REV_SHIFT) & PD_HEADER_REV_MASK;
+}
+
+static inline unsigned int pd_header_rev_le(__le16 header)
+{
+	return pd_header_rev(le16_to_cpu(header));
+}
+
+#define PD_EXT_HDR_CHUNKED		BIT(15)
+#define PD_EXT_HDR_CHUNK_NUM_SHIFT	11
+#define PD_EXT_HDR_CHUNK_NUM_MASK	0xf
+#define PD_EXT_HDR_REQ_CHUNK		BIT(10)
+#define PD_EXT_HDR_DATA_SIZE_SHIFT	0
+#define PD_EXT_HDR_DATA_SIZE_MASK	0x1ff
+
+#define PD_EXT_HDR(data_size, req_chunk, chunk_num, chunked)				\
+	((((data_size) & PD_EXT_HDR_DATA_SIZE_MASK) << PD_EXT_HDR_DATA_SIZE_SHIFT) |	\
+	 ((req_chunk) ? PD_EXT_HDR_REQ_CHUNK : 0) |					\
+	 (((chunk_num) & PD_EXT_HDR_CHUNK_NUM_MASK) << PD_EXT_HDR_CHUNK_NUM_SHIFT) |	\
+	 ((chunked) ? PD_EXT_HDR_CHUNKED : 0))
+
+#define PD_EXT_HDR_LE(data_size, req_chunk, chunk_num, chunked) \
+	cpu_to_le16(PD_EXT_HDR((data_size), (req_chunk), (chunk_num), (chunked)))
+
+static inline unsigned int pd_ext_header_chunk_num(u16 ext_header)
+{
+	return (ext_header >> PD_EXT_HDR_CHUNK_NUM_SHIFT) &
+		PD_EXT_HDR_CHUNK_NUM_MASK;
+}
+
+static inline unsigned int pd_ext_header_data_size(u16 ext_header)
+{
+	return (ext_header >> PD_EXT_HDR_DATA_SIZE_SHIFT) &
+		PD_EXT_HDR_DATA_SIZE_MASK;
+}
+
+static inline unsigned int pd_ext_header_data_size_le(__le16 ext_header)
+{
+	return pd_ext_header_data_size(le16_to_cpu(ext_header));
+}
+
 #define PD_MAX_PAYLOAD		7
+#define PD_EXT_MAX_CHUNK_DATA	26
 
 /**
- * struct pd_message - PD message as seen on wire
- * @header:	PD message header
- * @payload:	PD message payload
- */
+  * struct pd_chunked_ext_message_data - PD chunked extended message data as
+  *					 seen on wire
+  * @header:    PD extended message header
+  * @data:      PD extended message data
+  */
+struct pd_chunked_ext_message_data {
+	__le16 header;
+	u8 data[PD_EXT_MAX_CHUNK_DATA];
+} __packed;
+
+/**
+  * struct pd_message - PD message as seen on wire
+  * @header:    PD message header
+  * @payload:   PD message payload
+  * @ext_msg:   PD message chunked extended message data
+  */
 struct pd_message {
 	__le16 header;
-	__le32 payload[PD_MAX_PAYLOAD];
+	union {
+		__le32 payload[PD_MAX_PAYLOAD];
+		struct pd_chunked_ext_message_data ext_msg;
+	};
 } __packed;
 
 /* PDO: Power Data Object */
@@ -121,6 +214,7 @@ enum pd_pdo_type {
 	PDO_TYPE_FIXED = 0,
 	PDO_TYPE_BATT = 1,
 	PDO_TYPE_VAR = 2,
+	PDO_TYPE_APDO = 3,
 };
 
 #define PDO_TYPE_SHIFT		30
@@ -172,6 +266,29 @@ enum pd_pdo_type {
 	(PDO_TYPE(PDO_TYPE_VAR) | PDO_VAR_MIN_VOLT(min_mv) |	\
 	 PDO_VAR_MAX_VOLT(max_mv) | PDO_VAR_MAX_CURR(max_ma))
 
+enum pd_apdo_type {
+	APDO_TYPE_PPS = 0,
+};
+
+#define PDO_APDO_TYPE_SHIFT	28	/* Only valid value currently is 0x0 - PPS */
+#define PDO_APDO_MAX_VOLT_SHIFT	17	/* 100mV units */
+#define PDO_APDO_MIN_VOLT_SHIFT	8	/* 100mV units */
+#define PDO_APDO_MAX_CURR_SHIFT	0	/* 50mA units */
+
+#define PDO_APDO_TYPE_MASK	0x3
+#define PDO_APDO_VOLT_MASK	0xff
+#define PDO_APDO_CURR_MASK	0x7f
+
+#define PDO_APDO_TYPE(t)	((t) << PDO_APDO_TYPE_SHIFT)
+#define PDO_APDO_MIN_VOLT(mv)	((((mv) / 100) & PDO_APDO_VOLT_MASK) << PDO_APDO_MIN_VOLT_SHIFT)
+#define PDO_APDO_MAX_VOLT(mv)	((((mv) / 100) & PDO_APDO_VOLT_MASK) << PDO_APDO_MAX_VOLT_SHIFT)
+#define PDO_APDO_MAX_CURR(ma)	((((ma) / 50) & PDO_APDO_CURR_MASK) << PDO_APDO_MAX_CURR_SHIFT)
+
+#define PDO_APDO(min_mv, max_mv, max_ma)				\
+	(PDO_TYPE(PDO_TYPE_APDO) | PDO_APDO_TYPE(ADPO_TYPE_PPS) |	\
+	PDO_APDO_MIN_VOLT(min_mv) | PDO_APDO_MAX_VOLT(max_mv) |		\
+	PDO_APDO_MAX_CURR(max_ma))
+
 static inline enum pd_pdo_type pdo_type(u32 pdo)
 {
 	return (pdo >> PDO_TYPE_SHIFT) & PDO_TYPE_MASK;
@@ -202,6 +319,26 @@ static inline unsigned int pdo_max_power(u32 pdo)
 	return ((pdo >> PDO_BATT_MAX_PWR_SHIFT) & PDO_PWR_MASK) * 250;
 }
 
+static inline enum pd_apdo_type pdo_apdo_type(u32 pdo)
+{
+	return (pdo >> PDO_APDO_TYPE_SHIFT) & PDO_APDO_TYPE_MASK;
+}
+
+static inline unsigned int pdo_apdo_min_voltage(u32 pdo)
+{
+	return ((pdo >> PDO_APDO_MIN_VOLT_SHIFT) & PDO_APDO_VOLT_MASK) * 100;
+}
+
+static inline unsigned int pdo_apdo_max_voltage(u32 pdo)
+{
+	return ((pdo >> PDO_APDO_MAX_VOLT_SHIFT) & PDO_APDO_VOLT_MASK) * 100;
+}
+
+static inline unsigned int pdo_apdo_max_current(u32 pdo)
+{
+	return ((pdo >> PDO_APDO_MAX_CURR_SHIFT) & PDO_APDO_CURR_MASK) * 50;
+}
+
 /* RDO: Request Data Object */
 #define RDO_OBJ_POS_SHIFT	28
 #define RDO_OBJ_POS_MASK	0x7
@@ -235,6 +372,19 @@ static inline unsigned int pdo_max_power(u32 pdo)
 	(RDO_OBJ(idx) | (flags) |				\
 	 RDO_BATT_OP_PWR(op_mw) | RDO_BATT_MAX_PWR(max_mw))
 
+#define RDO_PROG_VOLT_MASK	0x7ff
+#define RDO_PROG_CURR_MASK	0x7f
+
+#define RDO_PROG_VOLT_SHIFT	9
+#define RDO_PROG_CURR_SHIFT	0
+
+#define PDO_PROG_OUT_VOLT(mv) ((((mv) / 20) & RDO_PROG_VOLT_MASK) << RDO_PROG_VOLT_SHIFT)
+#define PDO_PROG_OP_CURR(mv) ((((ma) / 50) & RDO_PROG_CURR_MASK) << RDO_PROG_CURR_SHIFT)
+
+#define RDO_PROG(idx, out_mv, op_ma, flags)			\
+	(RDO_OBJ(idx) | (flags) |				\
+	 PDO_PROG_OUT_VOLT(out_mv) | PDO_PROG_OP_CURR(op_ma))
+
 static inline unsigned int rdo_index(u32 rdo)
 {
 	return (rdo >> RDO_OBJ_POS_SHIFT) & RDO_OBJ_POS_MASK;
-- 
1.9.1

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

* [RFC PATCH v2 1/7] typec: tcpm: Add PD Rev 3.0 definitions to PD header
@ 2017-11-14 11:44   ` Adam Thomson
  0 siblings, 0 replies; 28+ messages in thread
From: Adam Thomson @ 2017-11-14 11:44 UTC (permalink / raw)
  To: Heikki Krogerus, Guenter Roeck, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Yueyao Zhu, Rui Miguel Silva
  Cc: linux-usb, linux-pm, linux-kernel, support.opensource

This commit adds definitions for PD Rev 3.0 messages, including
APDO PPS and extended message support for TCPM.

Signed-off-by: Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
---
 include/linux/usb/pd.h | 174 +++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 162 insertions(+), 12 deletions(-)

diff --git a/include/linux/usb/pd.h b/include/linux/usb/pd.h
index e00051c..8c9ac80 100644
--- a/include/linux/usb/pd.h
+++ b/include/linux/usb/pd.h
@@ -35,6 +35,13 @@ enum pd_ctrl_msg_type {
 	PD_CTRL_WAIT = 12,
 	PD_CTRL_SOFT_RESET = 13,
 	/* 14-15 Reserved */
+	PD_CTRL_NOT_SUPP = 16,
+	PD_CTRL_GET_SOURCE_CAP_EXT = 17,
+	PD_CTRL_GET_STATUS = 18,
+	PD_CTRL_FR_SWAP = 19,
+	PD_CTRL_GET_PPS_STATUS = 20,
+	PD_CTRL_GET_COUNTRY_CODES = 21,
+	/* 22-31 Reserved */
 };
 
 enum pd_data_msg_type {
@@ -43,13 +50,39 @@ enum pd_data_msg_type {
 	PD_DATA_REQUEST = 2,
 	PD_DATA_BIST = 3,
 	PD_DATA_SINK_CAP = 4,
-	/* 5-14 Reserved */
+	PD_DATA_BATT_STATUS = 5,
+	PD_DATA_ALERT = 6,
+	PD_DATA_GET_COUNTRY_INFO = 7,
+	/* 8-14 Reserved */
 	PD_DATA_VENDOR_DEF = 15,
+	/* 16-31 Reserved */
+};
+
+enum pd_ext_msg_type {
+	/* 0 Reserved */
+	PD_EXT_SOURCE_CAP_EXT = 1,
+	PD_EXT_STATUS = 2,
+	PD_EXT_GET_BATT_CAP = 3,
+	PD_EXT_GET_BATT_STATUS = 4,
+	PD_EXT_BATT_CAP = 5,
+	PD_EXT_GET_MANUFACTURER_INFO = 6,
+	PD_EXT_MANUFACTURER_INFO = 7,
+	PD_EXT_SECURITY_REQUEST = 8,
+	PD_EXT_SECURITY_RESPONSE = 9,
+	PD_EXT_FW_UPDATE_REQUEST = 10,
+	PD_EXT_FW_UPDATE_RESPONSE = 11,
+	PD_EXT_PPS_STATUS = 12,
+	PD_EXT_COUNTRY_INFO = 13,
+	PD_EXT_COUNTRY_CODES = 14,
+	/* 15-31 Reserved */
 };
 
 #define PD_REV10	0x0
 #define PD_REV20	0x1
+#define PD_REV30	0x2
+#define PD_MAX_REV	PD_REV30
 
+#define PD_HEADER_EXT_HDR	BIT(15)
 #define PD_HEADER_CNT_SHIFT	12
 #define PD_HEADER_CNT_MASK	0x7
 #define PD_HEADER_ID_SHIFT	9
@@ -59,18 +92,19 @@ enum pd_data_msg_type {
 #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_MASK	0x1f
 
-#define PD_HEADER(type, pwr, data, id, cnt)				\
+#define PD_HEADER(type, pwr, data, rev, id, cnt, ext_hdr)		\
 	((((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) |				\
+	 (rev << PD_HEADER_REV_SHIFT) |					\
 	 (((id) & PD_HEADER_ID_MASK) << PD_HEADER_ID_SHIFT) |		\
-	 (((cnt) & PD_HEADER_CNT_MASK) << PD_HEADER_CNT_SHIFT))
+	 (((cnt) & PD_HEADER_CNT_MASK) << PD_HEADER_CNT_SHIFT) |	\
+	 ((ext_hdr) ? PD_HEADER_EXT_HDR : 0))
 
-#define PD_HEADER_LE(type, pwr, data, id, cnt) \
-	cpu_to_le16(PD_HEADER((type), (pwr), (data), (id), (cnt)))
+#define PD_HEADER_LE(type, pwr, data, rev, id, cnt) \
+	cpu_to_le16(PD_HEADER((type), (pwr), (data), (rev), (id), (cnt), (0)))
 
 static inline unsigned int pd_header_cnt(u16 header)
 {
@@ -102,16 +136,75 @@ static inline unsigned int pd_header_msgid_le(__le16 header)
 	return pd_header_msgid(le16_to_cpu(header));
 }
 
+static inline unsigned int pd_header_rev(u16 header)
+{
+	return (header >> PD_HEADER_REV_SHIFT) & PD_HEADER_REV_MASK;
+}
+
+static inline unsigned int pd_header_rev_le(__le16 header)
+{
+	return pd_header_rev(le16_to_cpu(header));
+}
+
+#define PD_EXT_HDR_CHUNKED		BIT(15)
+#define PD_EXT_HDR_CHUNK_NUM_SHIFT	11
+#define PD_EXT_HDR_CHUNK_NUM_MASK	0xf
+#define PD_EXT_HDR_REQ_CHUNK		BIT(10)
+#define PD_EXT_HDR_DATA_SIZE_SHIFT	0
+#define PD_EXT_HDR_DATA_SIZE_MASK	0x1ff
+
+#define PD_EXT_HDR(data_size, req_chunk, chunk_num, chunked)				\
+	((((data_size) & PD_EXT_HDR_DATA_SIZE_MASK) << PD_EXT_HDR_DATA_SIZE_SHIFT) |	\
+	 ((req_chunk) ? PD_EXT_HDR_REQ_CHUNK : 0) |					\
+	 (((chunk_num) & PD_EXT_HDR_CHUNK_NUM_MASK) << PD_EXT_HDR_CHUNK_NUM_SHIFT) |	\
+	 ((chunked) ? PD_EXT_HDR_CHUNKED : 0))
+
+#define PD_EXT_HDR_LE(data_size, req_chunk, chunk_num, chunked) \
+	cpu_to_le16(PD_EXT_HDR((data_size), (req_chunk), (chunk_num), (chunked)))
+
+static inline unsigned int pd_ext_header_chunk_num(u16 ext_header)
+{
+	return (ext_header >> PD_EXT_HDR_CHUNK_NUM_SHIFT) &
+		PD_EXT_HDR_CHUNK_NUM_MASK;
+}
+
+static inline unsigned int pd_ext_header_data_size(u16 ext_header)
+{
+	return (ext_header >> PD_EXT_HDR_DATA_SIZE_SHIFT) &
+		PD_EXT_HDR_DATA_SIZE_MASK;
+}
+
+static inline unsigned int pd_ext_header_data_size_le(__le16 ext_header)
+{
+	return pd_ext_header_data_size(le16_to_cpu(ext_header));
+}
+
 #define PD_MAX_PAYLOAD		7
+#define PD_EXT_MAX_CHUNK_DATA	26
 
 /**
- * struct pd_message - PD message as seen on wire
- * @header:	PD message header
- * @payload:	PD message payload
- */
+  * struct pd_chunked_ext_message_data - PD chunked extended message data as
+  *					 seen on wire
+  * @header:    PD extended message header
+  * @data:      PD extended message data
+  */
+struct pd_chunked_ext_message_data {
+	__le16 header;
+	u8 data[PD_EXT_MAX_CHUNK_DATA];
+} __packed;
+
+/**
+  * struct pd_message - PD message as seen on wire
+  * @header:    PD message header
+  * @payload:   PD message payload
+  * @ext_msg:   PD message chunked extended message data
+  */
 struct pd_message {
 	__le16 header;
-	__le32 payload[PD_MAX_PAYLOAD];
+	union {
+		__le32 payload[PD_MAX_PAYLOAD];
+		struct pd_chunked_ext_message_data ext_msg;
+	};
 } __packed;
 
 /* PDO: Power Data Object */
@@ -121,6 +214,7 @@ enum pd_pdo_type {
 	PDO_TYPE_FIXED = 0,
 	PDO_TYPE_BATT = 1,
 	PDO_TYPE_VAR = 2,
+	PDO_TYPE_APDO = 3,
 };
 
 #define PDO_TYPE_SHIFT		30
@@ -172,6 +266,29 @@ enum pd_pdo_type {
 	(PDO_TYPE(PDO_TYPE_VAR) | PDO_VAR_MIN_VOLT(min_mv) |	\
 	 PDO_VAR_MAX_VOLT(max_mv) | PDO_VAR_MAX_CURR(max_ma))
 
+enum pd_apdo_type {
+	APDO_TYPE_PPS = 0,
+};
+
+#define PDO_APDO_TYPE_SHIFT	28	/* Only valid value currently is 0x0 - PPS */
+#define PDO_APDO_MAX_VOLT_SHIFT	17	/* 100mV units */
+#define PDO_APDO_MIN_VOLT_SHIFT	8	/* 100mV units */
+#define PDO_APDO_MAX_CURR_SHIFT	0	/* 50mA units */
+
+#define PDO_APDO_TYPE_MASK	0x3
+#define PDO_APDO_VOLT_MASK	0xff
+#define PDO_APDO_CURR_MASK	0x7f
+
+#define PDO_APDO_TYPE(t)	((t) << PDO_APDO_TYPE_SHIFT)
+#define PDO_APDO_MIN_VOLT(mv)	((((mv) / 100) & PDO_APDO_VOLT_MASK) << PDO_APDO_MIN_VOLT_SHIFT)
+#define PDO_APDO_MAX_VOLT(mv)	((((mv) / 100) & PDO_APDO_VOLT_MASK) << PDO_APDO_MAX_VOLT_SHIFT)
+#define PDO_APDO_MAX_CURR(ma)	((((ma) / 50) & PDO_APDO_CURR_MASK) << PDO_APDO_MAX_CURR_SHIFT)
+
+#define PDO_APDO(min_mv, max_mv, max_ma)				\
+	(PDO_TYPE(PDO_TYPE_APDO) | PDO_APDO_TYPE(ADPO_TYPE_PPS) |	\
+	PDO_APDO_MIN_VOLT(min_mv) | PDO_APDO_MAX_VOLT(max_mv) |		\
+	PDO_APDO_MAX_CURR(max_ma))
+
 static inline enum pd_pdo_type pdo_type(u32 pdo)
 {
 	return (pdo >> PDO_TYPE_SHIFT) & PDO_TYPE_MASK;
@@ -202,6 +319,26 @@ static inline unsigned int pdo_max_power(u32 pdo)
 	return ((pdo >> PDO_BATT_MAX_PWR_SHIFT) & PDO_PWR_MASK) * 250;
 }
 
+static inline enum pd_apdo_type pdo_apdo_type(u32 pdo)
+{
+	return (pdo >> PDO_APDO_TYPE_SHIFT) & PDO_APDO_TYPE_MASK;
+}
+
+static inline unsigned int pdo_apdo_min_voltage(u32 pdo)
+{
+	return ((pdo >> PDO_APDO_MIN_VOLT_SHIFT) & PDO_APDO_VOLT_MASK) * 100;
+}
+
+static inline unsigned int pdo_apdo_max_voltage(u32 pdo)
+{
+	return ((pdo >> PDO_APDO_MAX_VOLT_SHIFT) & PDO_APDO_VOLT_MASK) * 100;
+}
+
+static inline unsigned int pdo_apdo_max_current(u32 pdo)
+{
+	return ((pdo >> PDO_APDO_MAX_CURR_SHIFT) & PDO_APDO_CURR_MASK) * 50;
+}
+
 /* RDO: Request Data Object */
 #define RDO_OBJ_POS_SHIFT	28
 #define RDO_OBJ_POS_MASK	0x7
@@ -235,6 +372,19 @@ static inline unsigned int pdo_max_power(u32 pdo)
 	(RDO_OBJ(idx) | (flags) |				\
 	 RDO_BATT_OP_PWR(op_mw) | RDO_BATT_MAX_PWR(max_mw))
 
+#define RDO_PROG_VOLT_MASK	0x7ff
+#define RDO_PROG_CURR_MASK	0x7f
+
+#define RDO_PROG_VOLT_SHIFT	9
+#define RDO_PROG_CURR_SHIFT	0
+
+#define PDO_PROG_OUT_VOLT(mv) ((((mv) / 20) & RDO_PROG_VOLT_MASK) << RDO_PROG_VOLT_SHIFT)
+#define PDO_PROG_OP_CURR(mv) ((((ma) / 50) & RDO_PROG_CURR_MASK) << RDO_PROG_CURR_SHIFT)
+
+#define RDO_PROG(idx, out_mv, op_ma, flags)			\
+	(RDO_OBJ(idx) | (flags) |				\
+	 PDO_PROG_OUT_VOLT(out_mv) | PDO_PROG_OP_CURR(op_ma))
+
 static inline unsigned int rdo_index(u32 rdo)
 {
 	return (rdo >> RDO_OBJ_POS_SHIFT) & RDO_OBJ_POS_MASK;
-- 
1.9.1

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

* [RFC PATCH v2 2/7] typec: tcpm: Add ADO header for Alert message handling
@ 2017-11-14 11:44   ` Adam Thomson
  0 siblings, 0 replies; 28+ messages in thread
From: Adam Thomson @ 2017-11-14 11:44 UTC (permalink / raw)
  To: Heikki Krogerus, Guenter Roeck, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Yueyao Zhu, Rui Miguel Silva
  Cc: linux-usb, linux-pm, linux-kernel, support.opensource

This commit adds a header providing definitions for handling Alert
messages. Currently the header only focuses on handling incoming
alerts.

Signed-off-by: Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
---
 include/linux/usb/pd_ado.h | 42 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 42 insertions(+)
 create mode 100644 include/linux/usb/pd_ado.h

diff --git a/include/linux/usb/pd_ado.h b/include/linux/usb/pd_ado.h
new file mode 100644
index 0000000..9aa1cf3
--- /dev/null
+++ b/include/linux/usb/pd_ado.h
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2017 Dialog Semiconductor
+ *
+ * Author: Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
+ */
+
+#ifndef __LINUX_USB_PD_ADO_H
+#define __LINUX_USB_PD_ADO_H
+
+/* ADO : Alert Data Object */
+#define USB_PD_ADO_TYPE_SHIFT			24
+#define USB_PD_ADO_TYPE_MASK			0xff
+#define USB_PD_ADO_FIXED_BATT_SHIFT		20
+#define USB_PD_ADO_FIXED_BATT_MASK		0xf
+#define USB_PD_ADO_HOT_SWAP_BATT_SHIFT		16
+#define USB_PD_ADO_HOT_SWAP_BATT_MASK		0xf
+
+#define USB_PD_ADO_TYPE_BATT_STATUS_CHANGE	BIT(1)
+#define USB_PD_ADO_TYPE_OCP			BIT(2)
+#define USB_PD_ADO_TYPE_OTP			BIT(3)
+#define USB_PD_ADO_TYPE_OP_COND_CHANGE		BIT(4)
+#define USB_PD_ADO_TYPE_SRC_INPUT_CHANGE	BIT(5)
+#define USB_PD_ADO_TYPE_OVP			BIT(6)
+
+static inline unsigned int usb_pd_ado_type(u32 ado)
+{
+	return (ado >> USB_PD_ADO_TYPE_SHIFT) & USB_PD_ADO_TYPE_MASK;
+}
+
+static inline unsigned int usb_pd_ado_fixed_batt(u32 ado)
+{
+	return (ado >> USB_PD_ADO_FIXED_BATT_SHIFT) &
+	       USB_PD_ADO_FIXED_BATT_MASK;
+}
+
+static inline unsigned int usb_pd_ado_hot_swap_batt(u32 ado)
+{
+	return (ado >> USB_PD_ADO_HOT_SWAP_BATT_SHIFT) &
+	       USB_PD_ADO_HOT_SWAP_BATT_MASK;
+}
+#endif /* __LINUX_USB_PD_ADO_H */
-- 
1.9.1

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

* [RFC PATCH v2 2/7] typec: tcpm: Add ADO header for Alert message handling
@ 2017-11-14 11:44   ` Adam Thomson
  0 siblings, 0 replies; 28+ messages in thread
From: Adam Thomson @ 2017-11-14 11:44 UTC (permalink / raw)
  To: Heikki Krogerus, Guenter Roeck, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Yueyao Zhu, Rui Miguel Silva
  Cc: linux-usb-u79uwXL29TY76Z2rM5mHXA,
	linux-pm-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	support.opensource-WBD+wuPFNBhBDgjK7y7TUQ

This commit adds a header providing definitions for handling Alert
messages. Currently the header only focuses on handling incoming
alerts.

Signed-off-by: Adam Thomson <Adam.Thomson.Opensource-WBD+wuPFNBhBDgjK7y7TUQ@public.gmane.org>
---
 include/linux/usb/pd_ado.h | 42 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 42 insertions(+)
 create mode 100644 include/linux/usb/pd_ado.h

diff --git a/include/linux/usb/pd_ado.h b/include/linux/usb/pd_ado.h
new file mode 100644
index 0000000..9aa1cf3
--- /dev/null
+++ b/include/linux/usb/pd_ado.h
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2017 Dialog Semiconductor
+ *
+ * Author: Adam Thomson <Adam.Thomson.Opensource-WBD+wuPFNBhBDgjK7y7TUQ@public.gmane.org>
+ */
+
+#ifndef __LINUX_USB_PD_ADO_H
+#define __LINUX_USB_PD_ADO_H
+
+/* ADO : Alert Data Object */
+#define USB_PD_ADO_TYPE_SHIFT			24
+#define USB_PD_ADO_TYPE_MASK			0xff
+#define USB_PD_ADO_FIXED_BATT_SHIFT		20
+#define USB_PD_ADO_FIXED_BATT_MASK		0xf
+#define USB_PD_ADO_HOT_SWAP_BATT_SHIFT		16
+#define USB_PD_ADO_HOT_SWAP_BATT_MASK		0xf
+
+#define USB_PD_ADO_TYPE_BATT_STATUS_CHANGE	BIT(1)
+#define USB_PD_ADO_TYPE_OCP			BIT(2)
+#define USB_PD_ADO_TYPE_OTP			BIT(3)
+#define USB_PD_ADO_TYPE_OP_COND_CHANGE		BIT(4)
+#define USB_PD_ADO_TYPE_SRC_INPUT_CHANGE	BIT(5)
+#define USB_PD_ADO_TYPE_OVP			BIT(6)
+
+static inline unsigned int usb_pd_ado_type(u32 ado)
+{
+	return (ado >> USB_PD_ADO_TYPE_SHIFT) & USB_PD_ADO_TYPE_MASK;
+}
+
+static inline unsigned int usb_pd_ado_fixed_batt(u32 ado)
+{
+	return (ado >> USB_PD_ADO_FIXED_BATT_SHIFT) &
+	       USB_PD_ADO_FIXED_BATT_MASK;
+}
+
+static inline unsigned int usb_pd_ado_hot_swap_batt(u32 ado)
+{
+	return (ado >> USB_PD_ADO_HOT_SWAP_BATT_SHIFT) &
+	       USB_PD_ADO_HOT_SWAP_BATT_MASK;
+}
+#endif /* __LINUX_USB_PD_ADO_H */
-- 
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* [RFC PATCH v2 3/7] typec: tcpm: Add SDB header for Status message handling
@ 2017-11-14 11:44   ` Adam Thomson
  0 siblings, 0 replies; 28+ messages in thread
From: Adam Thomson @ 2017-11-14 11:44 UTC (permalink / raw)
  To: Heikki Krogerus, Guenter Roeck, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Yueyao Zhu, Rui Miguel Silva
  Cc: linux-usb, linux-pm, linux-kernel, support.opensource

This commit adds a header providing definitions for handling
Status messages. Currently the header only focuses on handling
incoming Status messages.

Signed-off-by: Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
---
 include/linux/usb/pd_ext_sdb.h | 31 +++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)
 create mode 100644 include/linux/usb/pd_ext_sdb.h

diff --git a/include/linux/usb/pd_ext_sdb.h b/include/linux/usb/pd_ext_sdb.h
new file mode 100644
index 0000000..0eb83ce
--- /dev/null
+++ b/include/linux/usb/pd_ext_sdb.h
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2017 Dialog Semiconductor
+ *
+ * Author: Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
+ */
+
+#ifndef __LINUX_USB_PD_EXT_SDB_H
+#define __LINUX_USB_PD_EXT_SDB_H
+
+/* SDB : Status Data Block */
+enum usb_pd_ext_sdb_fields {
+	USB_PD_EXT_SDB_INTERNAL_TEMP = 0,
+	USB_PD_EXT_SDB_PRESENT_INPUT,
+	USB_PD_EXT_SDB_PRESENT_BATT_INPUT,
+	USB_PD_EXT_SDB_EVENT_FLAGS,
+	USB_PD_EXT_SDB_TEMP_STATUS,
+	USB_PD_EXT_SDB_DATA_SIZE,
+};
+
+/* Event Flags */
+#define USB_PD_EXT_SDB_EVENT_OCP		BIT(1)
+#define USB_PD_EXT_SDB_EVENT_OTP		BIT(2)
+#define USB_PD_EXT_SDB_EVENT_OVP		BIT(3)
+#define USB_PD_EXT_SDB_EVENT_CF_CV_MODE		BIT(4)
+
+#define USB_PD_EXT_SDB_PPS_EVENTS	(USB_PD_EXT_SDB_EVENT_OCP |	\
+					 USB_PD_EXT_SDB_EVENT_OTP |	\
+					 USB_PD_EXT_SDB_EVENT_OVP)
+
+#endif /* __LINUX_USB_PD_EXT_SDB_H */
-- 
1.9.1

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

* [RFC PATCH v2 3/7] typec: tcpm: Add SDB header for Status message handling
@ 2017-11-14 11:44   ` Adam Thomson
  0 siblings, 0 replies; 28+ messages in thread
From: Adam Thomson @ 2017-11-14 11:44 UTC (permalink / raw)
  To: Heikki Krogerus, Guenter Roeck, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Yueyao Zhu, Rui Miguel Silva
  Cc: linux-usb-u79uwXL29TY76Z2rM5mHXA,
	linux-pm-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	support.opensource-WBD+wuPFNBhBDgjK7y7TUQ

This commit adds a header providing definitions for handling
Status messages. Currently the header only focuses on handling
incoming Status messages.

Signed-off-by: Adam Thomson <Adam.Thomson.Opensource-WBD+wuPFNBhBDgjK7y7TUQ@public.gmane.org>
---
 include/linux/usb/pd_ext_sdb.h | 31 +++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)
 create mode 100644 include/linux/usb/pd_ext_sdb.h

diff --git a/include/linux/usb/pd_ext_sdb.h b/include/linux/usb/pd_ext_sdb.h
new file mode 100644
index 0000000..0eb83ce
--- /dev/null
+++ b/include/linux/usb/pd_ext_sdb.h
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2017 Dialog Semiconductor
+ *
+ * Author: Adam Thomson <Adam.Thomson.Opensource-WBD+wuPFNBhBDgjK7y7TUQ@public.gmane.org>
+ */
+
+#ifndef __LINUX_USB_PD_EXT_SDB_H
+#define __LINUX_USB_PD_EXT_SDB_H
+
+/* SDB : Status Data Block */
+enum usb_pd_ext_sdb_fields {
+	USB_PD_EXT_SDB_INTERNAL_TEMP = 0,
+	USB_PD_EXT_SDB_PRESENT_INPUT,
+	USB_PD_EXT_SDB_PRESENT_BATT_INPUT,
+	USB_PD_EXT_SDB_EVENT_FLAGS,
+	USB_PD_EXT_SDB_TEMP_STATUS,
+	USB_PD_EXT_SDB_DATA_SIZE,
+};
+
+/* Event Flags */
+#define USB_PD_EXT_SDB_EVENT_OCP		BIT(1)
+#define USB_PD_EXT_SDB_EVENT_OTP		BIT(2)
+#define USB_PD_EXT_SDB_EVENT_OVP		BIT(3)
+#define USB_PD_EXT_SDB_EVENT_CF_CV_MODE		BIT(4)
+
+#define USB_PD_EXT_SDB_PPS_EVENTS	(USB_PD_EXT_SDB_EVENT_OCP |	\
+					 USB_PD_EXT_SDB_EVENT_OTP |	\
+					 USB_PD_EXT_SDB_EVENT_OVP)
+
+#endif /* __LINUX_USB_PD_EXT_SDB_H */
-- 
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* [RFC PATCH v2 4/7] typec: tcpm: Add core support for sink side PPS
  2017-11-14 11:44 ` Adam Thomson
@ 2017-11-14 11:44   ` Adam Thomson
  -1 siblings, 0 replies; 28+ messages in thread
From: Adam Thomson @ 2017-11-14 11:44 UTC (permalink / raw)
  To: Heikki Krogerus, Guenter Roeck, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Yueyao Zhu, Rui Miguel Silva
  Cc: linux-usb, linux-pm, linux-kernel, support.opensource

This commit adds code to handle requesting of PPS APDOs. Switching
between standard PDOs and APDOs, and re-requesting an APDO to
modify operating voltage/current will be triggered by an
external call into TCPM.

Signed-off-by: Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
---
 drivers/usb/typec/tcpm.c | 476 +++++++++++++++++++++++++++++++++++++++++++++--
 include/linux/usb/tcpm.h |   2 +-
 2 files changed, 464 insertions(+), 14 deletions(-)

diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c
index c166fc7..78983e1 100644
--- a/drivers/usb/typec/tcpm.c
+++ b/drivers/usb/typec/tcpm.c
@@ -47,6 +47,7 @@
 	S(SNK_DISCOVERY_DEBOUNCE_DONE),		\
 	S(SNK_WAIT_CAPABILITIES),		\
 	S(SNK_NEGOTIATE_CAPABILITIES),		\
+	S(SNK_NEGOTIATE_PPS_CAPABILITIES),	\
 	S(SNK_TRANSITION_SINK),			\
 	S(SNK_TRANSITION_SINK_VBUS),		\
 	S(SNK_READY),				\
@@ -166,6 +167,16 @@ struct pd_mode_data {
 	struct typec_altmode_desc altmode_desc[SVID_DISCOVERY_MAX];
 };
 
+struct pd_pps_data {
+	u16 min_volt;
+	u16 max_volt;
+	u16 max_curr;
+	u16 out_volt;
+	u16 op_curr;
+	bool supported;
+	bool active;
+};
+
 struct tcpm_port {
 	struct device *dev;
 
@@ -233,6 +244,7 @@ struct tcpm_port {
 	struct completion swap_complete;
 	int swap_status;
 
+	unsigned int negotiated_rev;
 	unsigned int message_id;
 	unsigned int caps_count;
 	unsigned int hard_reset_count;
@@ -259,6 +271,7 @@ struct tcpm_port {
 	unsigned int max_snk_ma;
 	unsigned int max_snk_mw;
 	unsigned int operating_snk_mw;
+	bool update_sink_caps;
 
 	/* Requested current / voltage */
 	u32 current_limit;
@@ -275,8 +288,13 @@ struct tcpm_port {
 	/* VDO to retry if UFP responder replied busy */
 	u32 vdo_retry;
 
-	/* Alternate mode data */
+	/* PPS */
+	struct pd_pps_data pps_data;
+	struct completion pps_complete;
+	bool pps_pending;
+	int pps_status;
 
+	/* Alternate mode data */
 	struct pd_mode_data mode_data;
 	struct typec_altmode *partner_altmode[SVID_DISCOVERY_MAX];
 	struct typec_altmode *port_altmode[SVID_DISCOVERY_MAX];
@@ -494,6 +512,13 @@ static void tcpm_log_source_caps(struct tcpm_port *port)
 				  pdo_max_voltage(pdo),
 				  pdo_max_power(pdo));
 			break;
+		case PDO_TYPE_APDO:
+			scnprintf(msg, sizeof(msg),
+				  "%u-%u mV, %u mA",
+				  pdo_apdo_min_voltage(pdo),
+				  pdo_apdo_max_voltage(pdo),
+				  pdo_apdo_max_current(pdo));
+			break;
 		default:
 			strcpy(msg, "undefined");
 			break;
@@ -788,11 +813,13 @@ static int tcpm_pd_send_source_caps(struct tcpm_port *port)
 		msg.header = PD_HEADER_LE(PD_CTRL_REJECT,
 					  port->pwr_role,
 					  port->data_role,
+					  port->negotiated_rev,
 					  port->message_id, 0);
 	} else {
 		msg.header = PD_HEADER_LE(PD_DATA_SOURCE_CAP,
 					  port->pwr_role,
 					  port->data_role,
+					  port->negotiated_rev,
 					  port->message_id,
 					  port->nr_src_pdo);
 	}
@@ -813,11 +840,13 @@ static int tcpm_pd_send_sink_caps(struct tcpm_port *port)
 		msg.header = PD_HEADER_LE(PD_CTRL_REJECT,
 					  port->pwr_role,
 					  port->data_role,
+					  port->negotiated_rev,
 					  port->message_id, 0);
 	} else {
 		msg.header = PD_HEADER_LE(PD_DATA_SINK_CAP,
 					  port->pwr_role,
 					  port->data_role,
+					  port->negotiated_rev,
 					  port->message_id,
 					  port->nr_snk_pdo);
 	}
@@ -1184,6 +1213,7 @@ static void vdm_run_state_machine(struct tcpm_port *port)
 		msg.header = PD_HEADER_LE(PD_DATA_VENDOR_DEF,
 					  port->pwr_role,
 					  port->data_role,
+					  port->negotiated_rev,
 					  port->message_id, port->vdo_count);
 		for (i = 0; i < port->vdo_count; i++)
 			msg.payload[i] = cpu_to_le32(port->vdo_data[i]);
@@ -1250,11 +1280,16 @@ static void vdm_state_machine_work(struct work_struct *work)
 /*
  * PD (data, control) command handling functions
  */
+
+static int tcpm_pd_send_control(struct tcpm_port *port,
+				enum pd_ctrl_msg_type type);
+
 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 rev = pd_header_rev_le(msg->header);
 	unsigned int i;
 
 	switch (type) {
@@ -1270,6 +1305,16 @@ static void tcpm_pd_data_request(struct tcpm_port *port,
 		tcpm_log_source_caps(port);
 
 		/*
+		 * Adjust revision in subsequent message headers, as required,
+		 * to comply with 6.2.1.1.5 of the USB PD 3.0 spec. We don't
+		 * support Rev 1.0 so just do nothing in that scenario.
+		 */
+		if (rev == PD_REV10)
+			break;
+		else if (rev < PD_MAX_REV)
+			port->negotiated_rev = rev;
+
+		/*
 		 * 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.
@@ -1290,6 +1335,19 @@ static void tcpm_pd_data_request(struct tcpm_port *port,
 			tcpm_queue_message(port, PD_MSG_CTRL_REJECT);
 			break;
 		}
+
+		/*
+		 * Adjust revision in subsequent message headers, as required,
+		 * to comply with 6.2.1.1.5 of the USB PD 3.0 spec. We don't
+		 * support Rev 1.0 so just reject in that scenario.
+		 */
+		if (rev == PD_REV10) {
+			tcpm_queue_message(port, PD_MSG_CTRL_REJECT);
+			break;
+		} else if (rev < PD_MAX_REV) {
+			port->negotiated_rev = rev;
+		}
+
 		port->sink_request = le32_to_cpu(msg->payload[0]);
 		tcpm_set_state(port, SRC_NEGOTIATE_CAPABILITIES, 0);
 		break;
@@ -1314,6 +1372,15 @@ static void tcpm_pd_data_request(struct tcpm_port *port,
 	}
 }
 
+static void tcpm_pps_complete(struct tcpm_port *port, int result)
+{
+	if (port->pps_pending) {
+		port->pps_status = result;
+		port->pps_pending = false;
+		complete(&port->pps_complete);
+	}
+}
+
 static void tcpm_pd_ctrl_request(struct tcpm_port *port,
 				 const struct pd_message *msg)
 {
@@ -1390,6 +1457,11 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port,
 				next_state = SNK_WAIT_CAPABILITIES;
 			tcpm_set_state(port, next_state, 0);
 			break;
+		case SNK_NEGOTIATE_PPS_CAPABILITIES:
+			port->pps_status = (type == PD_CTRL_WAIT ?
+					    -EAGAIN : -EOPNOTSUPP);
+			tcpm_set_state(port, SNK_READY, 0);
+			break;
 		case DR_SWAP_SEND:
 			port->swap_status = (type == PD_CTRL_WAIT ?
 					     -EAGAIN : -EOPNOTSUPP);
@@ -1412,6 +1484,13 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port,
 	case PD_CTRL_ACCEPT:
 		switch (port->state) {
 		case SNK_NEGOTIATE_CAPABILITIES:
+			port->pps_data.active = false;
+			tcpm_set_state(port, SNK_TRANSITION_SINK, 0);
+			break;
+		case SNK_NEGOTIATE_PPS_CAPABILITIES:
+			port->pps_data.active = true;
+			port->pps_data.out_volt = port->supply_voltage;
+			port->pps_data.op_curr = port->current_limit;
 			tcpm_set_state(port, SNK_TRANSITION_SINK, 0);
 			break;
 		case SOFT_RESET_SEND:
@@ -1566,6 +1645,7 @@ static int tcpm_pd_send_control(struct tcpm_port *port,
 	memset(&msg, 0, sizeof(msg));
 	msg.header = PD_HEADER_LE(type, port->pwr_role,
 				  port->data_role,
+				  port->negotiated_rev,
 				  port->message_id, 0);
 
 	return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg);
@@ -1675,6 +1755,8 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
 	unsigned int i, max_mw = 0, max_mv = 0;
 	int ret = -EINVAL;
 
+	port->pps_data.supported = false;
+
 	/*
 	 * Select the source PDO providing the most power while staying within
 	 * the board's voltage limits. Prefer PDO providing exp
@@ -1684,17 +1766,38 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
 		enum pd_pdo_type type = pdo_type(pdo);
 		unsigned int mv, ma, mw;
 
-		if (type == PDO_TYPE_FIXED)
+		switch (type) {
+		case PDO_TYPE_FIXED:
 			mv = pdo_fixed_voltage(pdo);
-		else
+			break;
+		case PDO_TYPE_BATT:
+		case PDO_TYPE_VAR:
 			mv = pdo_min_voltage(pdo);
+			break;
+		case PDO_TYPE_APDO:
+			if (pdo_apdo_type(pdo) == APDO_TYPE_PPS)
+				port->pps_data.supported = true;
+			continue;
+		default:
+			tcpm_log(port, "Invalid PDO type, ignoring");
+			continue;
+		}
 
-		if (type == PDO_TYPE_BATT) {
-			mw = pdo_max_power(pdo);
-		} else {
+		switch (type) {
+		case PDO_TYPE_FIXED:
+		case PDO_TYPE_VAR:
 			ma = min(pdo_max_current(pdo),
 				 port->max_snk_ma);
 			mw = ma * mv / 1000;
+			break;
+		case PDO_TYPE_BATT:
+			mw = pdo_max_power(pdo);
+			break;
+		case PDO_TYPE_APDO:
+			continue;
+		default:
+			tcpm_log(port, "Invalid PDO type, ignoring");
+			continue;
 		}
 
 		/* Perfer higher voltages if available */
@@ -1709,6 +1812,64 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
 	return ret;
 }
 
+static unsigned int tcpm_pd_select_pps_apdo(struct tcpm_port *port)
+{
+	unsigned int i, max_mw = 0, max_mv = 0;
+	unsigned int pps_min_mv, pps_max_mv, ma, mw;
+	enum pd_pdo_type type;
+	u32 pdo;
+	unsigned int index = 0;
+
+	/*
+	 * Select the source PPS APDO providing the most power while staying
+	 * within the board's limits. We skip the first PDO as this is always
+	 * 5V 3A.
+	 */
+	for (i = 1; i < port->nr_source_caps; ++i) {
+		pdo = port->source_caps[i];
+		type = pdo_type(pdo);
+
+		switch (type) {
+		case PDO_TYPE_APDO:
+			if (pdo_apdo_type(pdo) != APDO_TYPE_PPS) {
+				tcpm_log(port, "Not PPS APDO, ignoring");
+				continue;
+			}
+
+			pps_min_mv = pdo_apdo_min_voltage(pdo);
+			pps_max_mv = pdo_apdo_max_voltage(pdo);
+			ma = min(pdo_apdo_max_current(pdo), port->max_snk_ma);
+			mw = (ma * pps_max_mv) / 1000;
+			break;
+		default:
+			tcpm_log(port, "Not APDO type, ignoring");
+			continue;
+		}
+
+		/* Perfer higher voltages if available */
+		if ((mw > max_mw || (mw == max_mw && pps_max_mv > max_mv)) &&
+		    pps_max_mv <= port->max_snk_mv) {
+			index = i;
+			max_mw = mw;
+			max_mv = pps_max_mv;
+		}
+	}
+
+	if (index) {
+		pdo = port->source_caps[index];
+
+		port->pps_data.min_volt = pdo_apdo_min_voltage(pdo);
+		port->pps_data.max_volt = pdo_apdo_max_voltage(pdo);
+		port->pps_data.max_curr = pdo_apdo_max_current(pdo);
+		port->pps_data.out_volt =
+			min(port->supply_voltage, pdo_apdo_max_voltage(pdo));
+		port->pps_data.op_curr =
+			min(port->current_limit, pdo_apdo_max_current(pdo));
+	}
+
+	return index;
+}
+
 static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo)
 {
 	unsigned int mv, ma, mw, flags;
@@ -1720,13 +1881,22 @@ static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo)
 	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)
+	switch (type) {
+	case PDO_TYPE_FIXED:
 		mv = pdo_fixed_voltage(pdo);
-	else
+		break;
+	case PDO_TYPE_BATT:
+	case PDO_TYPE_VAR:
 		mv = pdo_min_voltage(pdo);
+		break;
+	default:
+		tcpm_log(port, "Invalid PDO selected!");
+		return -EINVAL;
+	}
 
 	/* Select maximum available current within the board's power limit */
 	if (type == PDO_TYPE_BATT) {
@@ -1789,6 +1959,92 @@ static int tcpm_pd_send_request(struct tcpm_port *port)
 	msg.header = PD_HEADER_LE(PD_DATA_REQUEST,
 				  port->pwr_role,
 				  port->data_role,
+				  port->negotiated_rev,
+				  port->message_id, 1);
+	msg.payload[0] = cpu_to_le32(rdo);
+
+	return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg);
+}
+
+static int tcpm_pd_build_pps_request(struct tcpm_port *port, u32 *rdo)
+{
+	unsigned int min_mv, max_mv, max_ma, max_mw, ma, mw, flags;
+	unsigned int out_mv, op_ma;
+	enum pd_pdo_type type;
+	unsigned int index;
+	u32 pdo;
+
+	index = tcpm_pd_select_pps_apdo(port);
+	if (!index)
+		return -EOPNOTSUPP;
+
+	pdo = port->source_caps[index];
+	type = pdo_type(pdo);
+
+	switch (type) {
+	case PDO_TYPE_APDO:
+		min_mv = pdo_apdo_min_voltage(pdo);
+		max_mv = pdo_apdo_max_voltage(pdo);
+		max_ma = pdo_apdo_max_current(pdo);
+		out_mv = min(port->supply_voltage, max_mv);
+		op_ma = min(port->current_limit, max_ma);
+		break;
+	default:
+		tcpm_log(port, "Invalid PDO selected!");
+		return -EINVAL;
+	}
+
+	if ((out_mv < min_mv) || (out_mv > max_mv) || (op_ma > max_ma))
+		return -EINVAL;
+
+	/* Select maximum available current within the board's power limit */
+	ma = min(op_ma, (1000 * port->max_snk_mw) / out_mv);
+	ma = min(ma, port->max_snk_ma);
+
+	flags = RDO_USB_COMM | RDO_NO_SUSPEND;
+
+	/* Set mismatch bit if offered power is less than operating power */
+	mw = (ma * out_mv) / 1000;
+	max_ma = ma;
+	max_mw = mw;
+	if (mw < port->operating_snk_mw) {
+		flags |= RDO_CAP_MISMATCH;
+		max_mw = port->operating_snk_mw;
+		max_ma = (max_mw * 1000) / out_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->vconn_role == TYPEC_SOURCE ? "source" : "sink",
+		 port->polarity);
+
+	*rdo = RDO_PROG(index + 1, out_mv, max_ma, flags);
+
+	tcpm_log(port, "Requesting APDO %d: %u mV, %u mA%s",
+		 index, out_mv, max_ma,
+		 flags & RDO_CAP_MISMATCH ? " [mismatch]" : "");
+
+	port->current_limit = max_ma;
+	port->supply_voltage = out_mv;
+
+	return 0;
+}
+
+static int tcpm_pd_send_pps_request(struct tcpm_port *port)
+{
+	struct pd_message msg;
+	int ret;
+	u32 rdo;
+
+	ret = tcpm_pd_build_pps_request(port, &rdo);
+	if (ret < 0)
+		return ret;
+
+	memset(&msg, 0, sizeof(msg));
+	msg.header = PD_HEADER_LE(PD_DATA_REQUEST,
+				  port->pwr_role,
+				  port->data_role,
+				  port->negotiated_rev,
 				  port->message_id, 1);
 	msg.payload[0] = cpu_to_le32(rdo);
 
@@ -1974,6 +2230,7 @@ static void tcpm_reset_port(struct tcpm_port *port)
 	tcpm_typec_disconnect(port);
 	port->attached = false;
 	port->pd_capable = false;
+	port->pps_data.supported = false;
 
 	/*
 	 * First Rx ID should be 0; set this to a sentinel of -1 so that
@@ -1989,6 +2246,8 @@ static void tcpm_reset_port(struct tcpm_port *port)
 	tcpm_set_attached_state(port, false);
 	port->try_src_count = 0;
 	port->try_snk_count = 0;
+	port->supply_voltage = 0;
+	port->current_limit = 0;
 }
 
 static void tcpm_detach(struct tcpm_port *port)
@@ -2235,6 +2494,7 @@ static void run_state_machine(struct tcpm_port *port)
 		typec_set_pwr_opmode(port->typec_port, opmode);
 		port->pwr_opmode = TYPEC_PWR_MODE_USB;
 		port->caps_count = 0;
+		port->negotiated_rev = PD_MAX_REV;
 		port->message_id = 0;
 		port->rx_msgid = -1;
 		port->explicit_contract = false;
@@ -2295,6 +2555,7 @@ static void run_state_machine(struct tcpm_port *port)
 
 		tcpm_swap_complete(port, 0);
 		tcpm_typec_connect(port);
+
 		tcpm_check_send_discover(port);
 		/*
 		 * 6.3.5
@@ -2318,6 +2579,7 @@ static void run_state_machine(struct tcpm_port *port)
 	case SNK_UNATTACHED:
 		if (!port->non_pd_role_swap)
 			tcpm_swap_complete(port, -ENOTCONN);
+		tcpm_pps_complete(port, -ENOTCONN);
 		tcpm_snk_detach(port);
 		if (tcpm_start_drp_toggling(port)) {
 			tcpm_set_state(port, DRP_TOGGLING, 0);
@@ -2326,6 +2588,7 @@ static void run_state_machine(struct tcpm_port *port)
 		tcpm_set_cc(port, TYPEC_CC_RD);
 		if (port->port_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 &&
@@ -2407,6 +2670,7 @@ static void run_state_machine(struct tcpm_port *port)
 					      port->cc2 : port->cc1);
 		typec_set_pwr_opmode(port->typec_port, opmode);
 		port->pwr_opmode = TYPEC_PWR_MODE_USB;
+		port->negotiated_rev = PD_MAX_REV;
 		port->message_id = 0;
 		port->rx_msgid = -1;
 		port->explicit_contract = false;
@@ -2477,6 +2741,24 @@ static void run_state_machine(struct tcpm_port *port)
 					    PD_T_SENDER_RESPONSE);
 		}
 		break;
+	case SNK_NEGOTIATE_PPS_CAPABILITIES:
+		ret = tcpm_pd_send_pps_request(port);
+		if (ret < 0) {
+			port->pps_status = ret;
+			/*
+			 * If this was called due to updates to sink
+			 * capabilities, and pps is no longer valid, we should
+			 * safely fall back to a standard PDO.
+			 */
+			if (port->update_sink_caps)
+				tcpm_set_state(port, SNK_NEGOTIATE_CAPABILITIES, 0);
+			else
+				tcpm_set_state(port, SNK_READY, 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),
@@ -2484,6 +2766,7 @@ static void run_state_machine(struct tcpm_port *port)
 		break;
 	case SNK_READY:
 		port->try_snk_count = 0;
+		port->update_sink_caps = false;
 		if (port->explicit_contract) {
 			typec_set_pwr_opmode(port->typec_port,
 					     TYPEC_PWR_MODE_PD);
@@ -2492,7 +2775,11 @@ static void run_state_machine(struct tcpm_port *port)
 
 		tcpm_swap_complete(port, 0);
 		tcpm_typec_connect(port);
+
 		tcpm_check_send_discover(port);
+
+		tcpm_pps_complete(port, port->pps_status);
+
 		break;
 
 	/* Accessory states */
@@ -2539,6 +2826,7 @@ static void run_state_machine(struct tcpm_port *port)
 		tcpm_set_state(port, SRC_UNATTACHED, PD_T_PS_SOURCE_ON);
 		break;
 	case SNK_HARD_RESET_SINK_OFF:
+		memset(&port->pps_data, 0, sizeof(port->pps_data));
 		tcpm_set_vconn(port, false);
 		tcpm_set_charge(port, false);
 		tcpm_set_roles(port, false, TYPEC_SINK, TYPEC_DEVICE);
@@ -2759,6 +3047,7 @@ static void run_state_machine(struct tcpm_port *port)
 		break;
 	case ERROR_RECOVERY:
 		tcpm_swap_complete(port, -EPROTO);
+		tcpm_pps_complete(port, -EPROTO);
 		tcpm_set_state(port, PORT_RESET, 0);
 		break;
 	case PORT_RESET:
@@ -3224,7 +3513,7 @@ static int tcpm_dr_set(const struct typec_capability *cap,
 	mutex_unlock(&port->lock);
 
 	if (!wait_for_completion_timeout(&port->swap_complete,
-				msecs_to_jiffies(PD_ROLE_SWAP_TIMEOUT)))
+				msecs_to_jiffies(PD_STATE_MACHINE_TIMEOUT)))
 		ret = -ETIMEDOUT;
 	else
 		ret = port->swap_status;
@@ -3269,7 +3558,7 @@ static int tcpm_pr_set(const struct typec_capability *cap,
 	mutex_unlock(&port->lock);
 
 	if (!wait_for_completion_timeout(&port->swap_complete,
-				msecs_to_jiffies(PD_ROLE_SWAP_TIMEOUT)))
+				msecs_to_jiffies(PD_STATE_MACHINE_TIMEOUT)))
 		ret = -ETIMEDOUT;
 	else
 		ret = port->swap_status;
@@ -3309,7 +3598,7 @@ static int tcpm_vconn_set(const struct typec_capability *cap,
 	mutex_unlock(&port->lock);
 
 	if (!wait_for_completion_timeout(&port->swap_complete,
-				msecs_to_jiffies(PD_ROLE_SWAP_TIMEOUT)))
+				msecs_to_jiffies(PD_STATE_MACHINE_TIMEOUT)))
 		ret = -ETIMEDOUT;
 	else
 		ret = port->swap_status;
@@ -3341,6 +3630,161 @@ static int tcpm_try_role(const struct typec_capability *cap, int role)
 	return ret;
 }
 
+static int tcpm_pps_set_op_curr(struct tcpm_port *port, u16 op_curr)
+{
+	unsigned int target_mw;
+	int ret = 0;
+
+	mutex_lock(&port->swap_lock);
+	mutex_lock(&port->lock);
+
+	if (!port->pps_data.active) {
+		ret = -EOPNOTSUPP;
+		goto port_unlock;
+	}
+
+	if (port->state != SNK_READY) {
+		ret = -EAGAIN;
+		goto port_unlock;
+	}
+
+	if (op_curr > port->pps_data.max_curr) {
+		ret = -EINVAL;
+		goto port_unlock;
+	}
+
+	target_mw = (op_curr * port->pps_data.out_volt) / 1000;
+	if ((target_mw < port->operating_snk_mw) ||
+	    (target_mw > port->max_snk_mw)) {
+		ret = -EINVAL;
+		goto port_unlock;
+	}
+
+	reinit_completion(&port->pps_complete);
+	port->current_limit = op_curr;
+	port->pps_status = 0;
+	port->pps_pending = true;
+	tcpm_set_state(port, SNK_NEGOTIATE_PPS_CAPABILITIES, 0);
+	mutex_unlock(&port->lock);
+
+	if (!wait_for_completion_timeout(&port->pps_complete,
+				msecs_to_jiffies(PD_STATE_MACHINE_TIMEOUT)))
+		ret = -ETIMEDOUT;
+	else
+		ret = port->pps_status;
+
+	goto swap_unlock;
+
+port_unlock:
+	mutex_unlock(&port->lock);
+swap_unlock:
+	mutex_unlock(&port->swap_lock);
+
+	return ret;
+}
+
+static int tcpm_pps_set_out_volt(struct tcpm_port *port, u16 out_volt)
+{
+	unsigned int target_mw;
+	int ret = 0;
+
+	mutex_lock(&port->swap_lock);
+	mutex_lock(&port->lock);
+
+	if (!port->pps_data.active) {
+		ret = -EOPNOTSUPP;
+		goto port_unlock;
+	}
+
+	if (port->state != SNK_READY) {
+		ret = -EAGAIN;
+		goto port_unlock;
+	}
+
+	if ((out_volt < port->pps_data.min_volt) ||
+	    (out_volt > port->pps_data.max_volt)) {
+		ret = -EINVAL;
+		goto port_unlock;
+	}
+
+	target_mw = (port->pps_data.op_curr * out_volt) / 1000;
+	if ((target_mw < port->operating_snk_mw) ||
+	    (target_mw > port->max_snk_mw)) {
+		ret = -EINVAL;
+		goto port_unlock;
+	}
+
+	reinit_completion(&port->pps_complete);
+	port->supply_voltage = out_volt;
+	port->pps_status = 0;
+	port->pps_pending = true;
+	tcpm_set_state(port, SNK_NEGOTIATE_PPS_CAPABILITIES, 0);
+	mutex_unlock(&port->lock);
+
+	if (!wait_for_completion_timeout(&port->pps_complete,
+				msecs_to_jiffies(PD_STATE_MACHINE_TIMEOUT)))
+		ret = -ETIMEDOUT;
+	else
+		ret = port->pps_status;
+
+	goto swap_unlock;
+
+port_unlock:
+	mutex_unlock(&port->lock);
+swap_unlock:
+	mutex_unlock(&port->swap_lock);
+
+	return ret;
+}
+
+static int tcpm_pps_activate(struct tcpm_port *port, bool activate)
+{
+	int ret = 0;
+
+	mutex_lock(&port->swap_lock);
+	mutex_lock(&port->lock);
+
+	if (!port->pps_data.supported) {
+		ret = -EOPNOTSUPP;
+		goto port_unlock;
+	}
+
+	/* Trying to deactivate PPS when already deactivated so just bail */
+	if ((!port->pps_data.active) && (!activate))
+		goto port_unlock;
+
+	if (port->state != SNK_READY) {
+		ret = -EAGAIN;
+		goto port_unlock;
+	}
+
+	reinit_completion(&port->pps_complete);
+	port->pps_status = 0;
+	port->pps_pending = true;
+
+	/* Trigger PPS request or move back to standard PDO contract */
+	if (activate)
+		tcpm_set_state(port, SNK_NEGOTIATE_PPS_CAPABILITIES, 0);
+	else
+		tcpm_set_state(port, SNK_NEGOTIATE_CAPABILITIES, 0);
+	mutex_unlock(&port->lock);
+
+	if (!wait_for_completion_timeout(&port->pps_complete,
+				msecs_to_jiffies(PD_STATE_MACHINE_TIMEOUT)))
+		ret = -ETIMEDOUT;
+	else
+		ret = port->pps_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;
@@ -3473,13 +3917,18 @@ void tcpm_update_sink_capabilities(struct tcpm_port *port, const u32 *pdo,
 	port->max_snk_ma = max_snk_ma;
 	port->max_snk_mw = max_snk_mw;
 	port->operating_snk_mw = operating_snk_mw;
+	port->update_sink_caps = true;
 
 	switch (port->state) {
 	case SNK_NEGOTIATE_CAPABILITIES:
+	case SNK_NEGOTIATE_PPS_CAPABILITIES:
 	case SNK_READY:
 	case SNK_TRANSITION_SINK:
 	case SNK_TRANSITION_SINK_VBUS:
-		tcpm_set_state(port, SNK_NEGOTIATE_CAPABILITIES, 0);
+		if (port->pps_data.active)
+			tcpm_set_state(port, SNK_NEGOTIATE_PPS_CAPABILITIES, 0);
+		else
+			tcpm_set_state(port, SNK_NEGOTIATE_CAPABILITIES, 0);
 		break;
 	default:
 		break;
@@ -3520,6 +3969,7 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
 
 	init_completion(&port->tx_complete);
 	init_completion(&port->swap_complete);
+	init_completion(&port->pps_complete);
 
 	port->nr_src_pdo = tcpm_copy_pdos(port->src_pdo, tcpc->config->src_pdo,
 					  tcpc->config->nr_src_pdo);
@@ -3540,7 +3990,7 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
 	port->typec_caps.prefer_role = tcpc->config->default_role;
 	port->typec_caps.type = tcpc->config->type;
 	port->typec_caps.revision = 0x0120;	/* Type-C spec release 1.2 */
-	port->typec_caps.pd_revision = 0x0200;	/* USB-PD spec release 2.0 */
+	port->typec_caps.pd_revision = 0x0300;	/* USB-PD spec release 3.0 */
 	port->typec_caps.dr_set = tcpm_dr_set;
 	port->typec_caps.pr_set = tcpm_pr_set;
 	port->typec_caps.vconn_set = tcpm_vconn_set;
diff --git a/include/linux/usb/tcpm.h b/include/linux/usb/tcpm.h
index 073197f..dde3c2a 100644
--- a/include/linux/usb/tcpm.h
+++ b/include/linux/usb/tcpm.h
@@ -35,7 +35,7 @@ enum typec_cc_polarity {
 
 /* Time to wait for TCPC to complete transmit */
 #define PD_T_TCPC_TX_TIMEOUT	100		/* in ms	*/
-#define PD_ROLE_SWAP_TIMEOUT	(MSEC_PER_SEC * 10)
+#define PD_STATE_MACHINE_TIMEOUT	(MSEC_PER_SEC * 10)
 
 enum tcpm_transmit_status {
 	TCPC_TX_SUCCESS = 0,
-- 
1.9.1

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

* [RFC PATCH v2 4/7] typec: tcpm: Add core support for sink side PPS
@ 2017-11-14 11:44   ` Adam Thomson
  0 siblings, 0 replies; 28+ messages in thread
From: Adam Thomson @ 2017-11-14 11:44 UTC (permalink / raw)
  To: Heikki Krogerus, Guenter Roeck, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Yueyao Zhu, Rui Miguel Silva
  Cc: linux-usb, linux-pm, linux-kernel, support.opensource

This commit adds code to handle requesting of PPS APDOs. Switching
between standard PDOs and APDOs, and re-requesting an APDO to
modify operating voltage/current will be triggered by an
external call into TCPM.

Signed-off-by: Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
---
 drivers/usb/typec/tcpm.c | 476 +++++++++++++++++++++++++++++++++++++++++++++--
 include/linux/usb/tcpm.h |   2 +-
 2 files changed, 464 insertions(+), 14 deletions(-)

diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c
index c166fc7..78983e1 100644
--- a/drivers/usb/typec/tcpm.c
+++ b/drivers/usb/typec/tcpm.c
@@ -47,6 +47,7 @@
 	S(SNK_DISCOVERY_DEBOUNCE_DONE),		\
 	S(SNK_WAIT_CAPABILITIES),		\
 	S(SNK_NEGOTIATE_CAPABILITIES),		\
+	S(SNK_NEGOTIATE_PPS_CAPABILITIES),	\
 	S(SNK_TRANSITION_SINK),			\
 	S(SNK_TRANSITION_SINK_VBUS),		\
 	S(SNK_READY),				\
@@ -166,6 +167,16 @@ struct pd_mode_data {
 	struct typec_altmode_desc altmode_desc[SVID_DISCOVERY_MAX];
 };
 
+struct pd_pps_data {
+	u16 min_volt;
+	u16 max_volt;
+	u16 max_curr;
+	u16 out_volt;
+	u16 op_curr;
+	bool supported;
+	bool active;
+};
+
 struct tcpm_port {
 	struct device *dev;
 
@@ -233,6 +244,7 @@ struct tcpm_port {
 	struct completion swap_complete;
 	int swap_status;
 
+	unsigned int negotiated_rev;
 	unsigned int message_id;
 	unsigned int caps_count;
 	unsigned int hard_reset_count;
@@ -259,6 +271,7 @@ struct tcpm_port {
 	unsigned int max_snk_ma;
 	unsigned int max_snk_mw;
 	unsigned int operating_snk_mw;
+	bool update_sink_caps;
 
 	/* Requested current / voltage */
 	u32 current_limit;
@@ -275,8 +288,13 @@ struct tcpm_port {
 	/* VDO to retry if UFP responder replied busy */
 	u32 vdo_retry;
 
-	/* Alternate mode data */
+	/* PPS */
+	struct pd_pps_data pps_data;
+	struct completion pps_complete;
+	bool pps_pending;
+	int pps_status;
 
+	/* Alternate mode data */
 	struct pd_mode_data mode_data;
 	struct typec_altmode *partner_altmode[SVID_DISCOVERY_MAX];
 	struct typec_altmode *port_altmode[SVID_DISCOVERY_MAX];
@@ -494,6 +512,13 @@ static void tcpm_log_source_caps(struct tcpm_port *port)
 				  pdo_max_voltage(pdo),
 				  pdo_max_power(pdo));
 			break;
+		case PDO_TYPE_APDO:
+			scnprintf(msg, sizeof(msg),
+				  "%u-%u mV, %u mA",
+				  pdo_apdo_min_voltage(pdo),
+				  pdo_apdo_max_voltage(pdo),
+				  pdo_apdo_max_current(pdo));
+			break;
 		default:
 			strcpy(msg, "undefined");
 			break;
@@ -788,11 +813,13 @@ static int tcpm_pd_send_source_caps(struct tcpm_port *port)
 		msg.header = PD_HEADER_LE(PD_CTRL_REJECT,
 					  port->pwr_role,
 					  port->data_role,
+					  port->negotiated_rev,
 					  port->message_id, 0);
 	} else {
 		msg.header = PD_HEADER_LE(PD_DATA_SOURCE_CAP,
 					  port->pwr_role,
 					  port->data_role,
+					  port->negotiated_rev,
 					  port->message_id,
 					  port->nr_src_pdo);
 	}
@@ -813,11 +840,13 @@ static int tcpm_pd_send_sink_caps(struct tcpm_port *port)
 		msg.header = PD_HEADER_LE(PD_CTRL_REJECT,
 					  port->pwr_role,
 					  port->data_role,
+					  port->negotiated_rev,
 					  port->message_id, 0);
 	} else {
 		msg.header = PD_HEADER_LE(PD_DATA_SINK_CAP,
 					  port->pwr_role,
 					  port->data_role,
+					  port->negotiated_rev,
 					  port->message_id,
 					  port->nr_snk_pdo);
 	}
@@ -1184,6 +1213,7 @@ static void vdm_run_state_machine(struct tcpm_port *port)
 		msg.header = PD_HEADER_LE(PD_DATA_VENDOR_DEF,
 					  port->pwr_role,
 					  port->data_role,
+					  port->negotiated_rev,
 					  port->message_id, port->vdo_count);
 		for (i = 0; i < port->vdo_count; i++)
 			msg.payload[i] = cpu_to_le32(port->vdo_data[i]);
@@ -1250,11 +1280,16 @@ static void vdm_state_machine_work(struct work_struct *work)
 /*
  * PD (data, control) command handling functions
  */
+
+static int tcpm_pd_send_control(struct tcpm_port *port,
+				enum pd_ctrl_msg_type type);
+
 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 rev = pd_header_rev_le(msg->header);
 	unsigned int i;
 
 	switch (type) {
@@ -1270,6 +1305,16 @@ static void tcpm_pd_data_request(struct tcpm_port *port,
 		tcpm_log_source_caps(port);
 
 		/*
+		 * Adjust revision in subsequent message headers, as required,
+		 * to comply with 6.2.1.1.5 of the USB PD 3.0 spec. We don't
+		 * support Rev 1.0 so just do nothing in that scenario.
+		 */
+		if (rev == PD_REV10)
+			break;
+		else if (rev < PD_MAX_REV)
+			port->negotiated_rev = rev;
+
+		/*
 		 * 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.
@@ -1290,6 +1335,19 @@ static void tcpm_pd_data_request(struct tcpm_port *port,
 			tcpm_queue_message(port, PD_MSG_CTRL_REJECT);
 			break;
 		}
+
+		/*
+		 * Adjust revision in subsequent message headers, as required,
+		 * to comply with 6.2.1.1.5 of the USB PD 3.0 spec. We don't
+		 * support Rev 1.0 so just reject in that scenario.
+		 */
+		if (rev == PD_REV10) {
+			tcpm_queue_message(port, PD_MSG_CTRL_REJECT);
+			break;
+		} else if (rev < PD_MAX_REV) {
+			port->negotiated_rev = rev;
+		}
+
 		port->sink_request = le32_to_cpu(msg->payload[0]);
 		tcpm_set_state(port, SRC_NEGOTIATE_CAPABILITIES, 0);
 		break;
@@ -1314,6 +1372,15 @@ static void tcpm_pd_data_request(struct tcpm_port *port,
 	}
 }
 
+static void tcpm_pps_complete(struct tcpm_port *port, int result)
+{
+	if (port->pps_pending) {
+		port->pps_status = result;
+		port->pps_pending = false;
+		complete(&port->pps_complete);
+	}
+}
+
 static void tcpm_pd_ctrl_request(struct tcpm_port *port,
 				 const struct pd_message *msg)
 {
@@ -1390,6 +1457,11 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port,
 				next_state = SNK_WAIT_CAPABILITIES;
 			tcpm_set_state(port, next_state, 0);
 			break;
+		case SNK_NEGOTIATE_PPS_CAPABILITIES:
+			port->pps_status = (type == PD_CTRL_WAIT ?
+					    -EAGAIN : -EOPNOTSUPP);
+			tcpm_set_state(port, SNK_READY, 0);
+			break;
 		case DR_SWAP_SEND:
 			port->swap_status = (type == PD_CTRL_WAIT ?
 					     -EAGAIN : -EOPNOTSUPP);
@@ -1412,6 +1484,13 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port,
 	case PD_CTRL_ACCEPT:
 		switch (port->state) {
 		case SNK_NEGOTIATE_CAPABILITIES:
+			port->pps_data.active = false;
+			tcpm_set_state(port, SNK_TRANSITION_SINK, 0);
+			break;
+		case SNK_NEGOTIATE_PPS_CAPABILITIES:
+			port->pps_data.active = true;
+			port->pps_data.out_volt = port->supply_voltage;
+			port->pps_data.op_curr = port->current_limit;
 			tcpm_set_state(port, SNK_TRANSITION_SINK, 0);
 			break;
 		case SOFT_RESET_SEND:
@@ -1566,6 +1645,7 @@ static int tcpm_pd_send_control(struct tcpm_port *port,
 	memset(&msg, 0, sizeof(msg));
 	msg.header = PD_HEADER_LE(type, port->pwr_role,
 				  port->data_role,
+				  port->negotiated_rev,
 				  port->message_id, 0);
 
 	return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg);
@@ -1675,6 +1755,8 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
 	unsigned int i, max_mw = 0, max_mv = 0;
 	int ret = -EINVAL;
 
+	port->pps_data.supported = false;
+
 	/*
 	 * Select the source PDO providing the most power while staying within
 	 * the board's voltage limits. Prefer PDO providing exp
@@ -1684,17 +1766,38 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
 		enum pd_pdo_type type = pdo_type(pdo);
 		unsigned int mv, ma, mw;
 
-		if (type == PDO_TYPE_FIXED)
+		switch (type) {
+		case PDO_TYPE_FIXED:
 			mv = pdo_fixed_voltage(pdo);
-		else
+			break;
+		case PDO_TYPE_BATT:
+		case PDO_TYPE_VAR:
 			mv = pdo_min_voltage(pdo);
+			break;
+		case PDO_TYPE_APDO:
+			if (pdo_apdo_type(pdo) == APDO_TYPE_PPS)
+				port->pps_data.supported = true;
+			continue;
+		default:
+			tcpm_log(port, "Invalid PDO type, ignoring");
+			continue;
+		}
 
-		if (type == PDO_TYPE_BATT) {
-			mw = pdo_max_power(pdo);
-		} else {
+		switch (type) {
+		case PDO_TYPE_FIXED:
+		case PDO_TYPE_VAR:
 			ma = min(pdo_max_current(pdo),
 				 port->max_snk_ma);
 			mw = ma * mv / 1000;
+			break;
+		case PDO_TYPE_BATT:
+			mw = pdo_max_power(pdo);
+			break;
+		case PDO_TYPE_APDO:
+			continue;
+		default:
+			tcpm_log(port, "Invalid PDO type, ignoring");
+			continue;
 		}
 
 		/* Perfer higher voltages if available */
@@ -1709,6 +1812,64 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
 	return ret;
 }
 
+static unsigned int tcpm_pd_select_pps_apdo(struct tcpm_port *port)
+{
+	unsigned int i, max_mw = 0, max_mv = 0;
+	unsigned int pps_min_mv, pps_max_mv, ma, mw;
+	enum pd_pdo_type type;
+	u32 pdo;
+	unsigned int index = 0;
+
+	/*
+	 * Select the source PPS APDO providing the most power while staying
+	 * within the board's limits. We skip the first PDO as this is always
+	 * 5V 3A.
+	 */
+	for (i = 1; i < port->nr_source_caps; ++i) {
+		pdo = port->source_caps[i];
+		type = pdo_type(pdo);
+
+		switch (type) {
+		case PDO_TYPE_APDO:
+			if (pdo_apdo_type(pdo) != APDO_TYPE_PPS) {
+				tcpm_log(port, "Not PPS APDO, ignoring");
+				continue;
+			}
+
+			pps_min_mv = pdo_apdo_min_voltage(pdo);
+			pps_max_mv = pdo_apdo_max_voltage(pdo);
+			ma = min(pdo_apdo_max_current(pdo), port->max_snk_ma);
+			mw = (ma * pps_max_mv) / 1000;
+			break;
+		default:
+			tcpm_log(port, "Not APDO type, ignoring");
+			continue;
+		}
+
+		/* Perfer higher voltages if available */
+		if ((mw > max_mw || (mw == max_mw && pps_max_mv > max_mv)) &&
+		    pps_max_mv <= port->max_snk_mv) {
+			index = i;
+			max_mw = mw;
+			max_mv = pps_max_mv;
+		}
+	}
+
+	if (index) {
+		pdo = port->source_caps[index];
+
+		port->pps_data.min_volt = pdo_apdo_min_voltage(pdo);
+		port->pps_data.max_volt = pdo_apdo_max_voltage(pdo);
+		port->pps_data.max_curr = pdo_apdo_max_current(pdo);
+		port->pps_data.out_volt =
+			min(port->supply_voltage, pdo_apdo_max_voltage(pdo));
+		port->pps_data.op_curr =
+			min(port->current_limit, pdo_apdo_max_current(pdo));
+	}
+
+	return index;
+}
+
 static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo)
 {
 	unsigned int mv, ma, mw, flags;
@@ -1720,13 +1881,22 @@ static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo)
 	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)
+	switch (type) {
+	case PDO_TYPE_FIXED:
 		mv = pdo_fixed_voltage(pdo);
-	else
+		break;
+	case PDO_TYPE_BATT:
+	case PDO_TYPE_VAR:
 		mv = pdo_min_voltage(pdo);
+		break;
+	default:
+		tcpm_log(port, "Invalid PDO selected!");
+		return -EINVAL;
+	}
 
 	/* Select maximum available current within the board's power limit */
 	if (type == PDO_TYPE_BATT) {
@@ -1789,6 +1959,92 @@ static int tcpm_pd_send_request(struct tcpm_port *port)
 	msg.header = PD_HEADER_LE(PD_DATA_REQUEST,
 				  port->pwr_role,
 				  port->data_role,
+				  port->negotiated_rev,
+				  port->message_id, 1);
+	msg.payload[0] = cpu_to_le32(rdo);
+
+	return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg);
+}
+
+static int tcpm_pd_build_pps_request(struct tcpm_port *port, u32 *rdo)
+{
+	unsigned int min_mv, max_mv, max_ma, max_mw, ma, mw, flags;
+	unsigned int out_mv, op_ma;
+	enum pd_pdo_type type;
+	unsigned int index;
+	u32 pdo;
+
+	index = tcpm_pd_select_pps_apdo(port);
+	if (!index)
+		return -EOPNOTSUPP;
+
+	pdo = port->source_caps[index];
+	type = pdo_type(pdo);
+
+	switch (type) {
+	case PDO_TYPE_APDO:
+		min_mv = pdo_apdo_min_voltage(pdo);
+		max_mv = pdo_apdo_max_voltage(pdo);
+		max_ma = pdo_apdo_max_current(pdo);
+		out_mv = min(port->supply_voltage, max_mv);
+		op_ma = min(port->current_limit, max_ma);
+		break;
+	default:
+		tcpm_log(port, "Invalid PDO selected!");
+		return -EINVAL;
+	}
+
+	if ((out_mv < min_mv) || (out_mv > max_mv) || (op_ma > max_ma))
+		return -EINVAL;
+
+	/* Select maximum available current within the board's power limit */
+	ma = min(op_ma, (1000 * port->max_snk_mw) / out_mv);
+	ma = min(ma, port->max_snk_ma);
+
+	flags = RDO_USB_COMM | RDO_NO_SUSPEND;
+
+	/* Set mismatch bit if offered power is less than operating power */
+	mw = (ma * out_mv) / 1000;
+	max_ma = ma;
+	max_mw = mw;
+	if (mw < port->operating_snk_mw) {
+		flags |= RDO_CAP_MISMATCH;
+		max_mw = port->operating_snk_mw;
+		max_ma = (max_mw * 1000) / out_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->vconn_role == TYPEC_SOURCE ? "source" : "sink",
+		 port->polarity);
+
+	*rdo = RDO_PROG(index + 1, out_mv, max_ma, flags);
+
+	tcpm_log(port, "Requesting APDO %d: %u mV, %u mA%s",
+		 index, out_mv, max_ma,
+		 flags & RDO_CAP_MISMATCH ? " [mismatch]" : "");
+
+	port->current_limit = max_ma;
+	port->supply_voltage = out_mv;
+
+	return 0;
+}
+
+static int tcpm_pd_send_pps_request(struct tcpm_port *port)
+{
+	struct pd_message msg;
+	int ret;
+	u32 rdo;
+
+	ret = tcpm_pd_build_pps_request(port, &rdo);
+	if (ret < 0)
+		return ret;
+
+	memset(&msg, 0, sizeof(msg));
+	msg.header = PD_HEADER_LE(PD_DATA_REQUEST,
+				  port->pwr_role,
+				  port->data_role,
+				  port->negotiated_rev,
 				  port->message_id, 1);
 	msg.payload[0] = cpu_to_le32(rdo);
 
@@ -1974,6 +2230,7 @@ static void tcpm_reset_port(struct tcpm_port *port)
 	tcpm_typec_disconnect(port);
 	port->attached = false;
 	port->pd_capable = false;
+	port->pps_data.supported = false;
 
 	/*
 	 * First Rx ID should be 0; set this to a sentinel of -1 so that
@@ -1989,6 +2246,8 @@ static void tcpm_reset_port(struct tcpm_port *port)
 	tcpm_set_attached_state(port, false);
 	port->try_src_count = 0;
 	port->try_snk_count = 0;
+	port->supply_voltage = 0;
+	port->current_limit = 0;
 }
 
 static void tcpm_detach(struct tcpm_port *port)
@@ -2235,6 +2494,7 @@ static void run_state_machine(struct tcpm_port *port)
 		typec_set_pwr_opmode(port->typec_port, opmode);
 		port->pwr_opmode = TYPEC_PWR_MODE_USB;
 		port->caps_count = 0;
+		port->negotiated_rev = PD_MAX_REV;
 		port->message_id = 0;
 		port->rx_msgid = -1;
 		port->explicit_contract = false;
@@ -2295,6 +2555,7 @@ static void run_state_machine(struct tcpm_port *port)
 
 		tcpm_swap_complete(port, 0);
 		tcpm_typec_connect(port);
+
 		tcpm_check_send_discover(port);
 		/*
 		 * 6.3.5
@@ -2318,6 +2579,7 @@ static void run_state_machine(struct tcpm_port *port)
 	case SNK_UNATTACHED:
 		if (!port->non_pd_role_swap)
 			tcpm_swap_complete(port, -ENOTCONN);
+		tcpm_pps_complete(port, -ENOTCONN);
 		tcpm_snk_detach(port);
 		if (tcpm_start_drp_toggling(port)) {
 			tcpm_set_state(port, DRP_TOGGLING, 0);
@@ -2326,6 +2588,7 @@ static void run_state_machine(struct tcpm_port *port)
 		tcpm_set_cc(port, TYPEC_CC_RD);
 		if (port->port_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 &&
@@ -2407,6 +2670,7 @@ static void run_state_machine(struct tcpm_port *port)
 					      port->cc2 : port->cc1);
 		typec_set_pwr_opmode(port->typec_port, opmode);
 		port->pwr_opmode = TYPEC_PWR_MODE_USB;
+		port->negotiated_rev = PD_MAX_REV;
 		port->message_id = 0;
 		port->rx_msgid = -1;
 		port->explicit_contract = false;
@@ -2477,6 +2741,24 @@ static void run_state_machine(struct tcpm_port *port)
 					    PD_T_SENDER_RESPONSE);
 		}
 		break;
+	case SNK_NEGOTIATE_PPS_CAPABILITIES:
+		ret = tcpm_pd_send_pps_request(port);
+		if (ret < 0) {
+			port->pps_status = ret;
+			/*
+			 * If this was called due to updates to sink
+			 * capabilities, and pps is no longer valid, we should
+			 * safely fall back to a standard PDO.
+			 */
+			if (port->update_sink_caps)
+				tcpm_set_state(port, SNK_NEGOTIATE_CAPABILITIES, 0);
+			else
+				tcpm_set_state(port, SNK_READY, 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),
@@ -2484,6 +2766,7 @@ static void run_state_machine(struct tcpm_port *port)
 		break;
 	case SNK_READY:
 		port->try_snk_count = 0;
+		port->update_sink_caps = false;
 		if (port->explicit_contract) {
 			typec_set_pwr_opmode(port->typec_port,
 					     TYPEC_PWR_MODE_PD);
@@ -2492,7 +2775,11 @@ static void run_state_machine(struct tcpm_port *port)
 
 		tcpm_swap_complete(port, 0);
 		tcpm_typec_connect(port);
+
 		tcpm_check_send_discover(port);
+
+		tcpm_pps_complete(port, port->pps_status);
+
 		break;
 
 	/* Accessory states */
@@ -2539,6 +2826,7 @@ static void run_state_machine(struct tcpm_port *port)
 		tcpm_set_state(port, SRC_UNATTACHED, PD_T_PS_SOURCE_ON);
 		break;
 	case SNK_HARD_RESET_SINK_OFF:
+		memset(&port->pps_data, 0, sizeof(port->pps_data));
 		tcpm_set_vconn(port, false);
 		tcpm_set_charge(port, false);
 		tcpm_set_roles(port, false, TYPEC_SINK, TYPEC_DEVICE);
@@ -2759,6 +3047,7 @@ static void run_state_machine(struct tcpm_port *port)
 		break;
 	case ERROR_RECOVERY:
 		tcpm_swap_complete(port, -EPROTO);
+		tcpm_pps_complete(port, -EPROTO);
 		tcpm_set_state(port, PORT_RESET, 0);
 		break;
 	case PORT_RESET:
@@ -3224,7 +3513,7 @@ static int tcpm_dr_set(const struct typec_capability *cap,
 	mutex_unlock(&port->lock);
 
 	if (!wait_for_completion_timeout(&port->swap_complete,
-				msecs_to_jiffies(PD_ROLE_SWAP_TIMEOUT)))
+				msecs_to_jiffies(PD_STATE_MACHINE_TIMEOUT)))
 		ret = -ETIMEDOUT;
 	else
 		ret = port->swap_status;
@@ -3269,7 +3558,7 @@ static int tcpm_pr_set(const struct typec_capability *cap,
 	mutex_unlock(&port->lock);
 
 	if (!wait_for_completion_timeout(&port->swap_complete,
-				msecs_to_jiffies(PD_ROLE_SWAP_TIMEOUT)))
+				msecs_to_jiffies(PD_STATE_MACHINE_TIMEOUT)))
 		ret = -ETIMEDOUT;
 	else
 		ret = port->swap_status;
@@ -3309,7 +3598,7 @@ static int tcpm_vconn_set(const struct typec_capability *cap,
 	mutex_unlock(&port->lock);
 
 	if (!wait_for_completion_timeout(&port->swap_complete,
-				msecs_to_jiffies(PD_ROLE_SWAP_TIMEOUT)))
+				msecs_to_jiffies(PD_STATE_MACHINE_TIMEOUT)))
 		ret = -ETIMEDOUT;
 	else
 		ret = port->swap_status;
@@ -3341,6 +3630,161 @@ static int tcpm_try_role(const struct typec_capability *cap, int role)
 	return ret;
 }
 
+static int tcpm_pps_set_op_curr(struct tcpm_port *port, u16 op_curr)
+{
+	unsigned int target_mw;
+	int ret = 0;
+
+	mutex_lock(&port->swap_lock);
+	mutex_lock(&port->lock);
+
+	if (!port->pps_data.active) {
+		ret = -EOPNOTSUPP;
+		goto port_unlock;
+	}
+
+	if (port->state != SNK_READY) {
+		ret = -EAGAIN;
+		goto port_unlock;
+	}
+
+	if (op_curr > port->pps_data.max_curr) {
+		ret = -EINVAL;
+		goto port_unlock;
+	}
+
+	target_mw = (op_curr * port->pps_data.out_volt) / 1000;
+	if ((target_mw < port->operating_snk_mw) ||
+	    (target_mw > port->max_snk_mw)) {
+		ret = -EINVAL;
+		goto port_unlock;
+	}
+
+	reinit_completion(&port->pps_complete);
+	port->current_limit = op_curr;
+	port->pps_status = 0;
+	port->pps_pending = true;
+	tcpm_set_state(port, SNK_NEGOTIATE_PPS_CAPABILITIES, 0);
+	mutex_unlock(&port->lock);
+
+	if (!wait_for_completion_timeout(&port->pps_complete,
+				msecs_to_jiffies(PD_STATE_MACHINE_TIMEOUT)))
+		ret = -ETIMEDOUT;
+	else
+		ret = port->pps_status;
+
+	goto swap_unlock;
+
+port_unlock:
+	mutex_unlock(&port->lock);
+swap_unlock:
+	mutex_unlock(&port->swap_lock);
+
+	return ret;
+}
+
+static int tcpm_pps_set_out_volt(struct tcpm_port *port, u16 out_volt)
+{
+	unsigned int target_mw;
+	int ret = 0;
+
+	mutex_lock(&port->swap_lock);
+	mutex_lock(&port->lock);
+
+	if (!port->pps_data.active) {
+		ret = -EOPNOTSUPP;
+		goto port_unlock;
+	}
+
+	if (port->state != SNK_READY) {
+		ret = -EAGAIN;
+		goto port_unlock;
+	}
+
+	if ((out_volt < port->pps_data.min_volt) ||
+	    (out_volt > port->pps_data.max_volt)) {
+		ret = -EINVAL;
+		goto port_unlock;
+	}
+
+	target_mw = (port->pps_data.op_curr * out_volt) / 1000;
+	if ((target_mw < port->operating_snk_mw) ||
+	    (target_mw > port->max_snk_mw)) {
+		ret = -EINVAL;
+		goto port_unlock;
+	}
+
+	reinit_completion(&port->pps_complete);
+	port->supply_voltage = out_volt;
+	port->pps_status = 0;
+	port->pps_pending = true;
+	tcpm_set_state(port, SNK_NEGOTIATE_PPS_CAPABILITIES, 0);
+	mutex_unlock(&port->lock);
+
+	if (!wait_for_completion_timeout(&port->pps_complete,
+				msecs_to_jiffies(PD_STATE_MACHINE_TIMEOUT)))
+		ret = -ETIMEDOUT;
+	else
+		ret = port->pps_status;
+
+	goto swap_unlock;
+
+port_unlock:
+	mutex_unlock(&port->lock);
+swap_unlock:
+	mutex_unlock(&port->swap_lock);
+
+	return ret;
+}
+
+static int tcpm_pps_activate(struct tcpm_port *port, bool activate)
+{
+	int ret = 0;
+
+	mutex_lock(&port->swap_lock);
+	mutex_lock(&port->lock);
+
+	if (!port->pps_data.supported) {
+		ret = -EOPNOTSUPP;
+		goto port_unlock;
+	}
+
+	/* Trying to deactivate PPS when already deactivated so just bail */
+	if ((!port->pps_data.active) && (!activate))
+		goto port_unlock;
+
+	if (port->state != SNK_READY) {
+		ret = -EAGAIN;
+		goto port_unlock;
+	}
+
+	reinit_completion(&port->pps_complete);
+	port->pps_status = 0;
+	port->pps_pending = true;
+
+	/* Trigger PPS request or move back to standard PDO contract */
+	if (activate)
+		tcpm_set_state(port, SNK_NEGOTIATE_PPS_CAPABILITIES, 0);
+	else
+		tcpm_set_state(port, SNK_NEGOTIATE_CAPABILITIES, 0);
+	mutex_unlock(&port->lock);
+
+	if (!wait_for_completion_timeout(&port->pps_complete,
+				msecs_to_jiffies(PD_STATE_MACHINE_TIMEOUT)))
+		ret = -ETIMEDOUT;
+	else
+		ret = port->pps_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;
@@ -3473,13 +3917,18 @@ void tcpm_update_sink_capabilities(struct tcpm_port *port, const u32 *pdo,
 	port->max_snk_ma = max_snk_ma;
 	port->max_snk_mw = max_snk_mw;
 	port->operating_snk_mw = operating_snk_mw;
+	port->update_sink_caps = true;
 
 	switch (port->state) {
 	case SNK_NEGOTIATE_CAPABILITIES:
+	case SNK_NEGOTIATE_PPS_CAPABILITIES:
 	case SNK_READY:
 	case SNK_TRANSITION_SINK:
 	case SNK_TRANSITION_SINK_VBUS:
-		tcpm_set_state(port, SNK_NEGOTIATE_CAPABILITIES, 0);
+		if (port->pps_data.active)
+			tcpm_set_state(port, SNK_NEGOTIATE_PPS_CAPABILITIES, 0);
+		else
+			tcpm_set_state(port, SNK_NEGOTIATE_CAPABILITIES, 0);
 		break;
 	default:
 		break;
@@ -3520,6 +3969,7 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
 
 	init_completion(&port->tx_complete);
 	init_completion(&port->swap_complete);
+	init_completion(&port->pps_complete);
 
 	port->nr_src_pdo = tcpm_copy_pdos(port->src_pdo, tcpc->config->src_pdo,
 					  tcpc->config->nr_src_pdo);
@@ -3540,7 +3990,7 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
 	port->typec_caps.prefer_role = tcpc->config->default_role;
 	port->typec_caps.type = tcpc->config->type;
 	port->typec_caps.revision = 0x0120;	/* Type-C spec release 1.2 */
-	port->typec_caps.pd_revision = 0x0200;	/* USB-PD spec release 2.0 */
+	port->typec_caps.pd_revision = 0x0300;	/* USB-PD spec release 3.0 */
 	port->typec_caps.dr_set = tcpm_dr_set;
 	port->typec_caps.pr_set = tcpm_pr_set;
 	port->typec_caps.vconn_set = tcpm_vconn_set;
diff --git a/include/linux/usb/tcpm.h b/include/linux/usb/tcpm.h
index 073197f..dde3c2a 100644
--- a/include/linux/usb/tcpm.h
+++ b/include/linux/usb/tcpm.h
@@ -35,7 +35,7 @@ enum typec_cc_polarity {
 
 /* Time to wait for TCPC to complete transmit */
 #define PD_T_TCPC_TX_TIMEOUT	100		/* in ms	*/
-#define PD_ROLE_SWAP_TIMEOUT	(MSEC_PER_SEC * 10)
+#define PD_STATE_MACHINE_TIMEOUT	(MSEC_PER_SEC * 10)
 
 enum tcpm_transmit_status {
 	TCPC_TX_SUCCESS = 0,
-- 
1.9.1

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

* [RFC PATCH v2 5/7] power: supply: Add type for USB PD PPS chargers
  2017-11-14 11:44 ` Adam Thomson
@ 2017-11-14 11:44   ` Adam Thomson
  -1 siblings, 0 replies; 28+ messages in thread
From: Adam Thomson @ 2017-11-14 11:44 UTC (permalink / raw)
  To: Heikki Krogerus, Guenter Roeck, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Yueyao Zhu, Rui Miguel Silva
  Cc: linux-usb, linux-pm, linux-kernel, support.opensource

This adds a type to represent USB PPS chargers as defined in the
USB Power Delivery Specification Revision 3.0 V1.1

Signed-off-by: Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.co.uk>
---
 drivers/power/supply/power_supply_sysfs.c | 2 +-
 include/linux/power_supply.h              | 1 +
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c
index 5204f11..efd2469 100644
--- a/drivers/power/supply/power_supply_sysfs.c
+++ b/drivers/power/supply/power_supply_sysfs.c
@@ -43,7 +43,7 @@
 static const char * const power_supply_type_text[] = {
 	"Unknown", "Battery", "UPS", "Mains", "USB",
 	"USB_DCP", "USB_CDP", "USB_ACA", "USB_C",
-	"USB_PD", "USB_PD_DRP", "BrickID"
+	"USB_PD", "USB_PD_DRP", "USB_PD_PPS", "BrickID"
 };
 
 static const char * const power_supply_status_text[] = {
diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h
index 79e90b3..3a79c75 100644
--- a/include/linux/power_supply.h
+++ b/include/linux/power_supply.h
@@ -167,6 +167,7 @@ enum power_supply_type {
 	POWER_SUPPLY_TYPE_USB_TYPE_C,		/* Type C Port */
 	POWER_SUPPLY_TYPE_USB_PD,		/* Power Delivery Port */
 	POWER_SUPPLY_TYPE_USB_PD_DRP,		/* PD Dual Role Port */
+	POWER_SUPPLY_TYPE_USB_PD_PPS,		/* PD Programmable Power Supply */
 	POWER_SUPPLY_TYPE_APPLE_BRICK_ID,	/* Apple Charging Method */
 };
 
-- 
1.9.1

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

* [RFC PATCH v2 5/7] power: supply: Add type for USB PD PPS chargers
@ 2017-11-14 11:44   ` Adam Thomson
  0 siblings, 0 replies; 28+ messages in thread
From: Adam Thomson @ 2017-11-14 11:44 UTC (permalink / raw)
  To: Heikki Krogerus, Guenter Roeck, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Yueyao Zhu, Rui Miguel Silva
  Cc: linux-usb, linux-pm, linux-kernel, support.opensource

This adds a type to represent USB PPS chargers as defined in the
USB Power Delivery Specification Revision 3.0 V1.1

Signed-off-by: Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.co.uk>
---
 drivers/power/supply/power_supply_sysfs.c | 2 +-
 include/linux/power_supply.h              | 1 +
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c
index 5204f11..efd2469 100644
--- a/drivers/power/supply/power_supply_sysfs.c
+++ b/drivers/power/supply/power_supply_sysfs.c
@@ -43,7 +43,7 @@
 static const char * const power_supply_type_text[] = {
 	"Unknown", "Battery", "UPS", "Mains", "USB",
 	"USB_DCP", "USB_CDP", "USB_ACA", "USB_C",
-	"USB_PD", "USB_PD_DRP", "BrickID"
+	"USB_PD", "USB_PD_DRP", "USB_PD_PPS", "BrickID"
 };
 
 static const char * const power_supply_status_text[] = {
diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h
index 79e90b3..3a79c75 100644
--- a/include/linux/power_supply.h
+++ b/include/linux/power_supply.h
@@ -167,6 +167,7 @@ enum power_supply_type {
 	POWER_SUPPLY_TYPE_USB_TYPE_C,		/* Type C Port */
 	POWER_SUPPLY_TYPE_USB_PD,		/* Power Delivery Port */
 	POWER_SUPPLY_TYPE_USB_PD_DRP,		/* PD Dual Role Port */
+	POWER_SUPPLY_TYPE_USB_PD_PPS,		/* PD Programmable Power Supply */
 	POWER_SUPPLY_TYPE_APPLE_BRICK_ID,	/* Apple Charging Method */
 };
 
-- 
1.9.1

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

* [RFC PATCH v2 6/7] typec: tcpm: Represent source supply through power_supply class
@ 2017-11-14 11:44   ` Adam Thomson
  0 siblings, 0 replies; 28+ messages in thread
From: Adam Thomson @ 2017-11-14 11:44 UTC (permalink / raw)
  To: Heikki Krogerus, Guenter Roeck, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Yueyao Zhu, Rui Miguel Silva
  Cc: linux-usb, linux-pm, linux-kernel, support.opensource

This commit adds a power_supply class instance to represent a
PD source's voltage and current properties. This provides an
interface for reading these properties from user-space or other
drivers.

For PPS enabled Sources, this also provides write access to set
the current and voltage and allows for swapping between standard
PDO and PPS APDO.

As this represents a superset of the information provided in the
fusb302 driver, the power_supply instance in that code is removed
as part of this change, so reverting the commit titled
'typec: tcpm: Represent source supply through power_supply class'

Signed-off-by: Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
---
 drivers/usb/typec/Kconfig           |   1 +
 drivers/usb/typec/fusb302/Kconfig   |   2 +-
 drivers/usb/typec/fusb302/fusb302.c |  63 +---------
 drivers/usb/typec/tcpm.c            | 225 +++++++++++++++++++++++++++++++++++-
 4 files changed, 228 insertions(+), 63 deletions(-)

diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig
index 465d7da..b9cd806 100644
--- a/drivers/usb/typec/Kconfig
+++ b/drivers/usb/typec/Kconfig
@@ -8,6 +8,7 @@ config TYPEC_TCPM
 	tristate "USB Type-C Port Controller Manager"
 	depends on USB
 	select TYPEC
+	select POWER_SUPPLY
 	help
 	  The Type-C Port Controller Manager provides a USB PD and USB Type-C
 	  state machine for use with Type-C Port Controllers.
diff --git a/drivers/usb/typec/fusb302/Kconfig b/drivers/usb/typec/fusb302/Kconfig
index 48a4f2f..fce099f 100644
--- a/drivers/usb/typec/fusb302/Kconfig
+++ b/drivers/usb/typec/fusb302/Kconfig
@@ -1,6 +1,6 @@
 config TYPEC_FUSB302
 	tristate "Fairchild FUSB302 Type-C chip driver"
-	depends on I2C && POWER_SUPPLY
+	depends on I2C
 	help
 	  The Fairchild FUSB302 Type-C chip driver that works with
 	  Type-C Port Controller Manager to provide USB PD and USB
diff --git a/drivers/usb/typec/fusb302/fusb302.c b/drivers/usb/typec/fusb302/fusb302.c
index 72cb060..21bd075 100644
--- a/drivers/usb/typec/fusb302/fusb302.c
+++ b/drivers/usb/typec/fusb302/fusb302.c
@@ -19,7 +19,6 @@
 #include <linux/of_device.h>
 #include <linux/of_gpio.h>
 #include <linux/pinctrl/consumer.h>
-#include <linux/power_supply.h>
 #include <linux/proc_fs.h>
 #include <linux/regulator/consumer.h>
 #include <linux/sched/clock.h>
@@ -100,11 +99,6 @@ struct fusb302_chip {
 	/* lock for sharing chip states */
 	struct mutex lock;
 
-	/* psy + psy status */
-	struct power_supply *psy;
-	u32 current_limit;
-	u32 supply_voltage;
-
 	/* chip status */
 	enum toggling_mode toggling_mode;
 	enum src_current_status src_current_status;
@@ -873,13 +867,11 @@ static int tcpm_set_vbus(struct tcpc_dev *dev, bool on, bool charge)
 		chip->vbus_on = on;
 		fusb302_log(chip, "vbus := %s", on ? "On" : "Off");
 	}
-	if (chip->charge_on == charge) {
+	if (chip->charge_on == charge)
 		fusb302_log(chip, "charge is already %s",
 			    charge ? "On" : "Off");
-	} else {
+	else
 		chip->charge_on = charge;
-		power_supply_changed(chip->psy);
-	}
 
 done:
 	mutex_unlock(&chip->lock);
@@ -895,11 +887,6 @@ static int tcpm_set_current_limit(struct tcpc_dev *dev, u32 max_ma, u32 mv)
 	fusb302_log(chip, "current limit: %d ma, %d mv (not implemented)",
 		    max_ma, mv);
 
-	chip->supply_voltage = mv;
-	chip->current_limit = max_ma;
-
-	power_supply_changed(chip->psy);
-
 	return 0;
 }
 
@@ -1685,43 +1672,6 @@ static irqreturn_t fusb302_irq_intn(int irq, void *dev_id)
 	return IRQ_HANDLED;
 }
 
-static int fusb302_psy_get_property(struct power_supply *psy,
-				    enum power_supply_property psp,
-				    union power_supply_propval *val)
-{
-	struct fusb302_chip *chip = power_supply_get_drvdata(psy);
-
-	switch (psp) {
-	case POWER_SUPPLY_PROP_ONLINE:
-		val->intval = chip->charge_on;
-		break;
-	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
-		val->intval = chip->supply_voltage * 1000; /* mV -> µV */
-		break;
-	case POWER_SUPPLY_PROP_CURRENT_MAX:
-		val->intval = chip->current_limit * 1000; /* mA -> µA */
-		break;
-	default:
-		return -ENODATA;
-	}
-
-	return 0;
-}
-
-static enum power_supply_property fusb302_psy_properties[] = {
-	POWER_SUPPLY_PROP_ONLINE,
-	POWER_SUPPLY_PROP_VOLTAGE_NOW,
-	POWER_SUPPLY_PROP_CURRENT_MAX,
-};
-
-static const struct power_supply_desc fusb302_psy_desc = {
-	.name		= "fusb302-typec-source",
-	.type		= POWER_SUPPLY_TYPE_USB_TYPE_C,
-	.properties	= fusb302_psy_properties,
-	.num_properties	= ARRAY_SIZE(fusb302_psy_properties),
-	.get_property	= fusb302_psy_get_property,
-};
-
 static int init_gpio(struct fusb302_chip *chip)
 {
 	struct device_node *node;
@@ -1761,7 +1711,6 @@ static int fusb302_probe(struct i2c_client *client,
 	struct fusb302_chip *chip;
 	struct i2c_adapter *adapter;
 	struct device *dev = &client->dev;
-	struct power_supply_config cfg = {};
 	const char *name;
 	int ret = 0;
 	u32 v;
@@ -1808,14 +1757,6 @@ static int fusb302_probe(struct i2c_client *client,
 			return -EPROBE_DEFER;
 	}
 
-	cfg.drv_data = chip;
-	chip->psy = devm_power_supply_register(dev, &fusb302_psy_desc, &cfg);
-	if (IS_ERR(chip->psy)) {
-		ret = PTR_ERR(chip->psy);
-		dev_err(chip->dev, "Error registering power-supply: %d\n", ret);
-		return ret;
-	}
-
 	ret = fusb302_debugfs_init(chip);
 	if (ret < 0)
 		return ret;
diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c
index 78983e1..7c26c3d 100644
--- a/drivers/usb/typec/tcpm.c
+++ b/drivers/usb/typec/tcpm.c
@@ -12,6 +12,7 @@
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/mutex.h>
+#include <linux/power_supply.h>
 #include <linux/proc_fs.h>
 #include <linux/sched/clock.h>
 #include <linux/seq_file.h>
@@ -277,6 +278,10 @@ struct tcpm_port {
 	u32 current_limit;
 	u32 supply_voltage;
 
+	/* Used to export TA voltage and current */
+	struct power_supply *psy;
+	struct power_supply_desc psy_desc;
+
 	u32 bist_request;
 
 	/* PD state for Vendor Defined Messages */
@@ -1756,6 +1761,7 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
 	int ret = -EINVAL;
 
 	port->pps_data.supported = false;
+	port->psy_desc.type = POWER_SUPPLY_TYPE_USB_PD;
 
 	/*
 	 * Select the source PDO providing the most power while staying within
@@ -1775,8 +1781,10 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
 			mv = pdo_min_voltage(pdo);
 			break;
 		case PDO_TYPE_APDO:
-			if (pdo_apdo_type(pdo) == APDO_TYPE_PPS)
+			if (pdo_apdo_type(pdo) == APDO_TYPE_PPS) {
 				port->pps_data.supported = true;
+				port->psy_desc.type = POWER_SUPPLY_TYPE_USB_PD_PPS;
+			}
 			continue;
 		default:
 			tcpm_log(port, "Invalid PDO type, ignoring");
@@ -2248,6 +2256,9 @@ static void tcpm_reset_port(struct tcpm_port *port)
 	port->try_snk_count = 0;
 	port->supply_voltage = 0;
 	port->current_limit = 0;
+	port->psy_desc.type = POWER_SUPPLY_TYPE_USB_TYPE_C;
+
+	power_supply_changed(port->psy);
 }
 
 static void tcpm_detach(struct tcpm_port *port)
@@ -2780,6 +2791,8 @@ static void run_state_machine(struct tcpm_port *port)
 
 		tcpm_pps_complete(port, port->pps_status);
 
+		power_supply_changed(port->psy);
+
 		break;
 
 	/* Accessory states */
@@ -3937,6 +3950,212 @@ void tcpm_update_sink_capabilities(struct tcpm_port *port, const u32 *pdo,
 }
 EXPORT_SYMBOL_GPL(tcpm_update_sink_capabilities);
 
+/* Power Supply access to expose source power information */
+enum tcpm_psy_online_states {
+	TCPM_PSY_OFFLINE = 0,
+	TCPM_PSY_FIXED_ONLINE,
+	TCPM_PSY_PROG_ONLINE,
+};
+
+static enum power_supply_property tcpm_psy_props[] = {
+	POWER_SUPPLY_PROP_ONLINE,
+	POWER_SUPPLY_PROP_VOLTAGE_MIN,
+	POWER_SUPPLY_PROP_VOLTAGE_MAX,
+	POWER_SUPPLY_PROP_VOLTAGE_NOW,
+	POWER_SUPPLY_PROP_CURRENT_MAX,
+	POWER_SUPPLY_PROP_CURRENT_NOW,
+};
+
+static int tcpm_psy_get_online(struct tcpm_port *port,
+			       union power_supply_propval *val)
+{
+	if (port->vbus_charge) {
+		if (port->pps_data.active)
+			val->intval = TCPM_PSY_PROG_ONLINE;
+		else
+			val->intval = TCPM_PSY_FIXED_ONLINE;
+	} else {
+		val->intval = TCPM_PSY_OFFLINE;
+	}
+
+	return 0;
+}
+
+static int tcpm_psy_get_voltage_min(struct tcpm_port *port,
+				    union power_supply_propval *val)
+{
+	if (port->pps_data.active)
+		val->intval = port->pps_data.min_volt * 1000;
+	else
+		val->intval = port->supply_voltage * 1000;
+
+	return 0;
+}
+
+static int tcpm_psy_get_voltage_max(struct tcpm_port *port,
+				    union power_supply_propval *val)
+{
+	if (port->pps_data.active)
+		val->intval = port->pps_data.max_volt * 1000;
+	else
+		val->intval = port->supply_voltage * 1000;
+
+	return 0;
+}
+
+static int tcpm_psy_get_voltage_now(struct tcpm_port *port,
+				    union power_supply_propval *val)
+{
+	if (port->pps_data.active)
+		val->intval = port->pps_data.out_volt * 1000;
+	else
+		val->intval = port->supply_voltage * 1000;
+
+	return 0;
+}
+
+static int tcpm_psy_get_current_max(struct tcpm_port *port,
+				    union power_supply_propval *val)
+{
+	if (port->pps_data.active)
+		val->intval = port->pps_data.max_curr * 1000;
+	else
+		val->intval = port->current_limit * 1000;
+
+	return 0;
+}
+
+static int tcpm_psy_get_current_now(struct tcpm_port *port,
+				    union power_supply_propval *val)
+{
+	if (port->pps_data.active)
+		val->intval = port->pps_data.op_curr * 1000;
+	else
+		val->intval = port->current_limit * 1000;
+
+	return 0;
+}
+
+static int tcpm_psy_get_prop(struct power_supply *psy,
+			     enum power_supply_property psp,
+			     union power_supply_propval *val)
+{
+	struct tcpm_port *port = power_supply_get_drvdata(psy);
+	int ret;
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_ONLINE:
+		ret = tcpm_psy_get_online(port, val);
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+		ret = tcpm_psy_get_voltage_min(port, val);
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+		ret = tcpm_psy_get_voltage_max(port, val);
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+		ret = tcpm_psy_get_voltage_now(port, val);
+		break;
+	case POWER_SUPPLY_PROP_CURRENT_MAX:
+		ret = tcpm_psy_get_current_max(port, val);
+		break;
+	case POWER_SUPPLY_PROP_CURRENT_NOW:
+		ret = tcpm_psy_get_current_now(port, val);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static int tcpm_psy_set_online(struct tcpm_port *port,
+			       const union power_supply_propval *val)
+{
+	int ret;
+
+	switch (val->intval) {
+	case TCPM_PSY_FIXED_ONLINE:
+		ret = tcpm_pps_activate(port, false);
+		break;
+	case TCPM_PSY_PROG_ONLINE:
+		ret = tcpm_pps_activate(port, true);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static int tcpm_psy_set_prop(struct power_supply *psy,
+			     enum power_supply_property psp,
+			     const union power_supply_propval *val)
+{
+	struct tcpm_port *port = power_supply_get_drvdata(psy);
+	int ret = 0;
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_ONLINE:
+		ret = tcpm_psy_set_online(port, val);
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+		if ((val->intval < (port->pps_data.min_volt * 1000)) ||
+		    (val->intval > (port->pps_data.max_volt * 1000)))
+			ret = -EINVAL;
+		else
+			ret = tcpm_pps_set_out_volt(port, (val->intval / 1000));
+		break;
+	case POWER_SUPPLY_PROP_CURRENT_NOW:
+		if (val->intval > (port->pps_data.max_curr * 1000))
+			ret = -EINVAL;
+		else
+			ret = tcpm_pps_set_op_curr(port, (val->intval / 1000));
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static int tcpm_psy_prop_writeable(struct power_supply *psy,
+				   enum power_supply_property psp)
+{
+	switch (psp) {
+	case POWER_SUPPLY_PROP_ONLINE:
+	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+	case POWER_SUPPLY_PROP_CURRENT_NOW:
+		return 1;
+	default:
+		return 0;
+	}
+}
+
+int devm_tcpm_psy_register(struct tcpm_port *port)
+{
+	struct power_supply_config psy_cfg = {};
+
+	psy_cfg.drv_data = port;
+	port->psy_desc.name = "tcpm-source-psy",
+	port->psy_desc.type = POWER_SUPPLY_TYPE_USB_TYPE_C,
+	port->psy_desc.properties = tcpm_psy_props,
+	port->psy_desc.num_properties = ARRAY_SIZE(tcpm_psy_props),
+	port->psy_desc.get_property = tcpm_psy_get_prop,
+	port->psy_desc.set_property = tcpm_psy_set_prop,
+	port->psy_desc.property_is_writeable = tcpm_psy_prop_writeable,
+
+	port->psy = devm_power_supply_register(port->dev, &port->psy_desc,
+					       &psy_cfg);
+	if (IS_ERR(port->psy))
+		return PTR_ERR(port->psy);
+
+	return 0;
+}
+
 struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
 {
 	struct tcpm_port *port;
@@ -4000,6 +4219,10 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
 	port->partner_desc.identity = &port->partner_ident;
 	port->port_type = tcpc->config->type;
 
+	err = devm_tcpm_psy_register(port);
+	if (err)
+		goto out_destroy_wq;
+
 	port->typec_port = typec_register_port(port->dev, &port->typec_caps);
 	if (!port->typec_port) {
 		err = -ENOMEM;
-- 
1.9.1

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

* [RFC PATCH v2 6/7] typec: tcpm: Represent source supply through power_supply class
@ 2017-11-14 11:44   ` Adam Thomson
  0 siblings, 0 replies; 28+ messages in thread
From: Adam Thomson @ 2017-11-14 11:44 UTC (permalink / raw)
  To: Heikki Krogerus, Guenter Roeck, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Yueyao Zhu, Rui Miguel Silva
  Cc: linux-usb-u79uwXL29TY76Z2rM5mHXA,
	linux-pm-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	support.opensource-WBD+wuPFNBhBDgjK7y7TUQ

This commit adds a power_supply class instance to represent a
PD source's voltage and current properties. This provides an
interface for reading these properties from user-space or other
drivers.

For PPS enabled Sources, this also provides write access to set
the current and voltage and allows for swapping between standard
PDO and PPS APDO.

As this represents a superset of the information provided in the
fusb302 driver, the power_supply instance in that code is removed
as part of this change, so reverting the commit titled
'typec: tcpm: Represent source supply through power_supply class'

Signed-off-by: Adam Thomson <Adam.Thomson.Opensource-WBD+wuPFNBhBDgjK7y7TUQ@public.gmane.org>
---
 drivers/usb/typec/Kconfig           |   1 +
 drivers/usb/typec/fusb302/Kconfig   |   2 +-
 drivers/usb/typec/fusb302/fusb302.c |  63 +---------
 drivers/usb/typec/tcpm.c            | 225 +++++++++++++++++++++++++++++++++++-
 4 files changed, 228 insertions(+), 63 deletions(-)

diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig
index 465d7da..b9cd806 100644
--- a/drivers/usb/typec/Kconfig
+++ b/drivers/usb/typec/Kconfig
@@ -8,6 +8,7 @@ config TYPEC_TCPM
 	tristate "USB Type-C Port Controller Manager"
 	depends on USB
 	select TYPEC
+	select POWER_SUPPLY
 	help
 	  The Type-C Port Controller Manager provides a USB PD and USB Type-C
 	  state machine for use with Type-C Port Controllers.
diff --git a/drivers/usb/typec/fusb302/Kconfig b/drivers/usb/typec/fusb302/Kconfig
index 48a4f2f..fce099f 100644
--- a/drivers/usb/typec/fusb302/Kconfig
+++ b/drivers/usb/typec/fusb302/Kconfig
@@ -1,6 +1,6 @@
 config TYPEC_FUSB302
 	tristate "Fairchild FUSB302 Type-C chip driver"
-	depends on I2C && POWER_SUPPLY
+	depends on I2C
 	help
 	  The Fairchild FUSB302 Type-C chip driver that works with
 	  Type-C Port Controller Manager to provide USB PD and USB
diff --git a/drivers/usb/typec/fusb302/fusb302.c b/drivers/usb/typec/fusb302/fusb302.c
index 72cb060..21bd075 100644
--- a/drivers/usb/typec/fusb302/fusb302.c
+++ b/drivers/usb/typec/fusb302/fusb302.c
@@ -19,7 +19,6 @@
 #include <linux/of_device.h>
 #include <linux/of_gpio.h>
 #include <linux/pinctrl/consumer.h>
-#include <linux/power_supply.h>
 #include <linux/proc_fs.h>
 #include <linux/regulator/consumer.h>
 #include <linux/sched/clock.h>
@@ -100,11 +99,6 @@ struct fusb302_chip {
 	/* lock for sharing chip states */
 	struct mutex lock;
 
-	/* psy + psy status */
-	struct power_supply *psy;
-	u32 current_limit;
-	u32 supply_voltage;
-
 	/* chip status */
 	enum toggling_mode toggling_mode;
 	enum src_current_status src_current_status;
@@ -873,13 +867,11 @@ static int tcpm_set_vbus(struct tcpc_dev *dev, bool on, bool charge)
 		chip->vbus_on = on;
 		fusb302_log(chip, "vbus := %s", on ? "On" : "Off");
 	}
-	if (chip->charge_on == charge) {
+	if (chip->charge_on == charge)
 		fusb302_log(chip, "charge is already %s",
 			    charge ? "On" : "Off");
-	} else {
+	else
 		chip->charge_on = charge;
-		power_supply_changed(chip->psy);
-	}
 
 done:
 	mutex_unlock(&chip->lock);
@@ -895,11 +887,6 @@ static int tcpm_set_current_limit(struct tcpc_dev *dev, u32 max_ma, u32 mv)
 	fusb302_log(chip, "current limit: %d ma, %d mv (not implemented)",
 		    max_ma, mv);
 
-	chip->supply_voltage = mv;
-	chip->current_limit = max_ma;
-
-	power_supply_changed(chip->psy);
-
 	return 0;
 }
 
@@ -1685,43 +1672,6 @@ static irqreturn_t fusb302_irq_intn(int irq, void *dev_id)
 	return IRQ_HANDLED;
 }
 
-static int fusb302_psy_get_property(struct power_supply *psy,
-				    enum power_supply_property psp,
-				    union power_supply_propval *val)
-{
-	struct fusb302_chip *chip = power_supply_get_drvdata(psy);
-
-	switch (psp) {
-	case POWER_SUPPLY_PROP_ONLINE:
-		val->intval = chip->charge_on;
-		break;
-	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
-		val->intval = chip->supply_voltage * 1000; /* mV -> µV */
-		break;
-	case POWER_SUPPLY_PROP_CURRENT_MAX:
-		val->intval = chip->current_limit * 1000; /* mA -> µA */
-		break;
-	default:
-		return -ENODATA;
-	}
-
-	return 0;
-}
-
-static enum power_supply_property fusb302_psy_properties[] = {
-	POWER_SUPPLY_PROP_ONLINE,
-	POWER_SUPPLY_PROP_VOLTAGE_NOW,
-	POWER_SUPPLY_PROP_CURRENT_MAX,
-};
-
-static const struct power_supply_desc fusb302_psy_desc = {
-	.name		= "fusb302-typec-source",
-	.type		= POWER_SUPPLY_TYPE_USB_TYPE_C,
-	.properties	= fusb302_psy_properties,
-	.num_properties	= ARRAY_SIZE(fusb302_psy_properties),
-	.get_property	= fusb302_psy_get_property,
-};
-
 static int init_gpio(struct fusb302_chip *chip)
 {
 	struct device_node *node;
@@ -1761,7 +1711,6 @@ static int fusb302_probe(struct i2c_client *client,
 	struct fusb302_chip *chip;
 	struct i2c_adapter *adapter;
 	struct device *dev = &client->dev;
-	struct power_supply_config cfg = {};
 	const char *name;
 	int ret = 0;
 	u32 v;
@@ -1808,14 +1757,6 @@ static int fusb302_probe(struct i2c_client *client,
 			return -EPROBE_DEFER;
 	}
 
-	cfg.drv_data = chip;
-	chip->psy = devm_power_supply_register(dev, &fusb302_psy_desc, &cfg);
-	if (IS_ERR(chip->psy)) {
-		ret = PTR_ERR(chip->psy);
-		dev_err(chip->dev, "Error registering power-supply: %d\n", ret);
-		return ret;
-	}
-
 	ret = fusb302_debugfs_init(chip);
 	if (ret < 0)
 		return ret;
diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c
index 78983e1..7c26c3d 100644
--- a/drivers/usb/typec/tcpm.c
+++ b/drivers/usb/typec/tcpm.c
@@ -12,6 +12,7 @@
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/mutex.h>
+#include <linux/power_supply.h>
 #include <linux/proc_fs.h>
 #include <linux/sched/clock.h>
 #include <linux/seq_file.h>
@@ -277,6 +278,10 @@ struct tcpm_port {
 	u32 current_limit;
 	u32 supply_voltage;
 
+	/* Used to export TA voltage and current */
+	struct power_supply *psy;
+	struct power_supply_desc psy_desc;
+
 	u32 bist_request;
 
 	/* PD state for Vendor Defined Messages */
@@ -1756,6 +1761,7 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
 	int ret = -EINVAL;
 
 	port->pps_data.supported = false;
+	port->psy_desc.type = POWER_SUPPLY_TYPE_USB_PD;
 
 	/*
 	 * Select the source PDO providing the most power while staying within
@@ -1775,8 +1781,10 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
 			mv = pdo_min_voltage(pdo);
 			break;
 		case PDO_TYPE_APDO:
-			if (pdo_apdo_type(pdo) == APDO_TYPE_PPS)
+			if (pdo_apdo_type(pdo) == APDO_TYPE_PPS) {
 				port->pps_data.supported = true;
+				port->psy_desc.type = POWER_SUPPLY_TYPE_USB_PD_PPS;
+			}
 			continue;
 		default:
 			tcpm_log(port, "Invalid PDO type, ignoring");
@@ -2248,6 +2256,9 @@ static void tcpm_reset_port(struct tcpm_port *port)
 	port->try_snk_count = 0;
 	port->supply_voltage = 0;
 	port->current_limit = 0;
+	port->psy_desc.type = POWER_SUPPLY_TYPE_USB_TYPE_C;
+
+	power_supply_changed(port->psy);
 }
 
 static void tcpm_detach(struct tcpm_port *port)
@@ -2780,6 +2791,8 @@ static void run_state_machine(struct tcpm_port *port)
 
 		tcpm_pps_complete(port, port->pps_status);
 
+		power_supply_changed(port->psy);
+
 		break;
 
 	/* Accessory states */
@@ -3937,6 +3950,212 @@ void tcpm_update_sink_capabilities(struct tcpm_port *port, const u32 *pdo,
 }
 EXPORT_SYMBOL_GPL(tcpm_update_sink_capabilities);
 
+/* Power Supply access to expose source power information */
+enum tcpm_psy_online_states {
+	TCPM_PSY_OFFLINE = 0,
+	TCPM_PSY_FIXED_ONLINE,
+	TCPM_PSY_PROG_ONLINE,
+};
+
+static enum power_supply_property tcpm_psy_props[] = {
+	POWER_SUPPLY_PROP_ONLINE,
+	POWER_SUPPLY_PROP_VOLTAGE_MIN,
+	POWER_SUPPLY_PROP_VOLTAGE_MAX,
+	POWER_SUPPLY_PROP_VOLTAGE_NOW,
+	POWER_SUPPLY_PROP_CURRENT_MAX,
+	POWER_SUPPLY_PROP_CURRENT_NOW,
+};
+
+static int tcpm_psy_get_online(struct tcpm_port *port,
+			       union power_supply_propval *val)
+{
+	if (port->vbus_charge) {
+		if (port->pps_data.active)
+			val->intval = TCPM_PSY_PROG_ONLINE;
+		else
+			val->intval = TCPM_PSY_FIXED_ONLINE;
+	} else {
+		val->intval = TCPM_PSY_OFFLINE;
+	}
+
+	return 0;
+}
+
+static int tcpm_psy_get_voltage_min(struct tcpm_port *port,
+				    union power_supply_propval *val)
+{
+	if (port->pps_data.active)
+		val->intval = port->pps_data.min_volt * 1000;
+	else
+		val->intval = port->supply_voltage * 1000;
+
+	return 0;
+}
+
+static int tcpm_psy_get_voltage_max(struct tcpm_port *port,
+				    union power_supply_propval *val)
+{
+	if (port->pps_data.active)
+		val->intval = port->pps_data.max_volt * 1000;
+	else
+		val->intval = port->supply_voltage * 1000;
+
+	return 0;
+}
+
+static int tcpm_psy_get_voltage_now(struct tcpm_port *port,
+				    union power_supply_propval *val)
+{
+	if (port->pps_data.active)
+		val->intval = port->pps_data.out_volt * 1000;
+	else
+		val->intval = port->supply_voltage * 1000;
+
+	return 0;
+}
+
+static int tcpm_psy_get_current_max(struct tcpm_port *port,
+				    union power_supply_propval *val)
+{
+	if (port->pps_data.active)
+		val->intval = port->pps_data.max_curr * 1000;
+	else
+		val->intval = port->current_limit * 1000;
+
+	return 0;
+}
+
+static int tcpm_psy_get_current_now(struct tcpm_port *port,
+				    union power_supply_propval *val)
+{
+	if (port->pps_data.active)
+		val->intval = port->pps_data.op_curr * 1000;
+	else
+		val->intval = port->current_limit * 1000;
+
+	return 0;
+}
+
+static int tcpm_psy_get_prop(struct power_supply *psy,
+			     enum power_supply_property psp,
+			     union power_supply_propval *val)
+{
+	struct tcpm_port *port = power_supply_get_drvdata(psy);
+	int ret;
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_ONLINE:
+		ret = tcpm_psy_get_online(port, val);
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+		ret = tcpm_psy_get_voltage_min(port, val);
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+		ret = tcpm_psy_get_voltage_max(port, val);
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+		ret = tcpm_psy_get_voltage_now(port, val);
+		break;
+	case POWER_SUPPLY_PROP_CURRENT_MAX:
+		ret = tcpm_psy_get_current_max(port, val);
+		break;
+	case POWER_SUPPLY_PROP_CURRENT_NOW:
+		ret = tcpm_psy_get_current_now(port, val);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static int tcpm_psy_set_online(struct tcpm_port *port,
+			       const union power_supply_propval *val)
+{
+	int ret;
+
+	switch (val->intval) {
+	case TCPM_PSY_FIXED_ONLINE:
+		ret = tcpm_pps_activate(port, false);
+		break;
+	case TCPM_PSY_PROG_ONLINE:
+		ret = tcpm_pps_activate(port, true);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static int tcpm_psy_set_prop(struct power_supply *psy,
+			     enum power_supply_property psp,
+			     const union power_supply_propval *val)
+{
+	struct tcpm_port *port = power_supply_get_drvdata(psy);
+	int ret = 0;
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_ONLINE:
+		ret = tcpm_psy_set_online(port, val);
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+		if ((val->intval < (port->pps_data.min_volt * 1000)) ||
+		    (val->intval > (port->pps_data.max_volt * 1000)))
+			ret = -EINVAL;
+		else
+			ret = tcpm_pps_set_out_volt(port, (val->intval / 1000));
+		break;
+	case POWER_SUPPLY_PROP_CURRENT_NOW:
+		if (val->intval > (port->pps_data.max_curr * 1000))
+			ret = -EINVAL;
+		else
+			ret = tcpm_pps_set_op_curr(port, (val->intval / 1000));
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static int tcpm_psy_prop_writeable(struct power_supply *psy,
+				   enum power_supply_property psp)
+{
+	switch (psp) {
+	case POWER_SUPPLY_PROP_ONLINE:
+	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+	case POWER_SUPPLY_PROP_CURRENT_NOW:
+		return 1;
+	default:
+		return 0;
+	}
+}
+
+int devm_tcpm_psy_register(struct tcpm_port *port)
+{
+	struct power_supply_config psy_cfg = {};
+
+	psy_cfg.drv_data = port;
+	port->psy_desc.name = "tcpm-source-psy",
+	port->psy_desc.type = POWER_SUPPLY_TYPE_USB_TYPE_C,
+	port->psy_desc.properties = tcpm_psy_props,
+	port->psy_desc.num_properties = ARRAY_SIZE(tcpm_psy_props),
+	port->psy_desc.get_property = tcpm_psy_get_prop,
+	port->psy_desc.set_property = tcpm_psy_set_prop,
+	port->psy_desc.property_is_writeable = tcpm_psy_prop_writeable,
+
+	port->psy = devm_power_supply_register(port->dev, &port->psy_desc,
+					       &psy_cfg);
+	if (IS_ERR(port->psy))
+		return PTR_ERR(port->psy);
+
+	return 0;
+}
+
 struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
 {
 	struct tcpm_port *port;
@@ -4000,6 +4219,10 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
 	port->partner_desc.identity = &port->partner_ident;
 	port->port_type = tcpc->config->type;
 
+	err = devm_tcpm_psy_register(port);
+	if (err)
+		goto out_destroy_wq;
+
 	port->typec_port = typec_register_port(port->dev, &port->typec_caps);
 	if (!port->typec_port) {
 		err = -ENOMEM;
-- 
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* [RFC PATCH v2 7/7] typec: tcpm: Add support for sink PPS related messages
@ 2017-11-14 11:44   ` Adam Thomson
  0 siblings, 0 replies; 28+ messages in thread
From: Adam Thomson @ 2017-11-14 11:44 UTC (permalink / raw)
  To: Heikki Krogerus, Guenter Roeck, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Yueyao Zhu, Rui Miguel Silva
  Cc: linux-usb, linux-pm, linux-kernel, support.opensource

This commit adds sink side support for Get_Status, Status,
Get_PPS_Status and PPS_Status handling. As there's the
potential for a partner to respond with Not_Supported
handling of this message is also added. Sending of
Not_Supported is added is added to handle messages
received but not yet handled.

Signed-off-by: Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
---
 drivers/usb/typec/tcpm.c | 152 ++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 143 insertions(+), 9 deletions(-)

diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c
index 7c26c3d..8db49ab 100644
--- a/drivers/usb/typec/tcpm.c
+++ b/drivers/usb/typec/tcpm.c
@@ -19,7 +19,9 @@
 #include <linux/slab.h>
 #include <linux/spinlock.h>
 #include <linux/usb/pd.h>
+#include <linux/usb/pd_ado.h>
 #include <linux/usb/pd_bdo.h>
+#include <linux/usb/pd_ext_sdb.h>
 #include <linux/usb/pd_vdo.h>
 #include <linux/usb/tcpm.h>
 #include <linux/usb/typec.h>
@@ -113,6 +115,11 @@
 	S(SNK_TRYWAIT_VBUS),			\
 	S(BIST_RX),				\
 						\
+	S(GET_STATUS_SEND),			\
+	S(GET_STATUS_SEND_TIMEOUT),		\
+	S(GET_PPS_STATUS_SEND),			\
+	S(GET_PPS_STATUS_SEND_TIMEOUT),		\
+						\
 	S(ERROR_RECOVERY),			\
 	S(PORT_RESET),				\
 	S(PORT_RESET_WAIT_OFF)
@@ -143,6 +150,7 @@ enum pd_msg_request {
 	PD_MSG_NONE = 0,
 	PD_MSG_CTRL_REJECT,
 	PD_MSG_CTRL_WAIT,
+	PD_MSG_CTRL_NOT_SUPP,
 	PD_MSG_DATA_SINK_CAP,
 	PD_MSG_DATA_SOURCE_CAP,
 };
@@ -1285,10 +1293,42 @@ static void vdm_state_machine_work(struct work_struct *work)
 /*
  * PD (data, control) command handling functions
  */
+static inline enum tcpm_state ready_state(struct tcpm_port *port)
+{
+	if (port->pwr_role == TYPEC_SOURCE)
+		return SRC_READY;
+	else
+		return SNK_READY;
+}
 
 static int tcpm_pd_send_control(struct tcpm_port *port,
 				enum pd_ctrl_msg_type type);
 
+static void tcpm_handle_alert(struct tcpm_port *port, const __le32 *payload,
+			      int cnt)
+{
+	u32 p0 = le32_to_cpu(payload[0]);
+	unsigned int type = usb_pd_ado_type(p0);
+
+	if (!type) {
+		tcpm_log(port, "Alert message received with no type");
+		return;
+	}
+
+	/* Just handling non-battery alerts for now */
+	if (!(type & USB_PD_ADO_TYPE_BATT_STATUS_CHANGE)) {
+		switch (port->state) {
+		case SRC_READY:
+		case SNK_READY:
+			tcpm_set_state(port, GET_STATUS_SEND, 0);
+			break;
+		default:
+			tcpm_queue_message(port, PD_MSG_CTRL_WAIT);
+			break;
+		}
+	}
+}
+
 static void tcpm_pd_data_request(struct tcpm_port *port,
 				 const struct pd_message *msg)
 {
@@ -1371,6 +1411,14 @@ static void tcpm_pd_data_request(struct tcpm_port *port,
 			tcpm_set_state(port, BIST_RX, 0);
 		}
 		break;
+	case PD_DATA_ALERT:
+		tcpm_handle_alert(port, msg->payload, cnt);
+		break;
+	case PD_DATA_BATT_STATUS:
+	case PD_DATA_GET_COUNTRY_INFO:
+		/* Currently unsupported */
+		tcpm_queue_message(port, PD_MSG_CTRL_NOT_SUPP);
+		break;
 	default:
 		tcpm_log(port, "Unhandled data message type %#x", type);
 		break;
@@ -1453,6 +1501,7 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port,
 		break;
 	case PD_CTRL_REJECT:
 	case PD_CTRL_WAIT:
+	case PD_CTRL_NOT_SUPP:
 		switch (port->state) {
 		case SNK_NEGOTIATE_CAPABILITIES:
 			/* USB PD specification, Figure 8-43 */
@@ -1569,12 +1618,84 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port,
 			break;
 		}
 		break;
+	case PD_CTRL_GET_SOURCE_CAP_EXT:
+	case PD_CTRL_GET_STATUS:
+	case PD_CTRL_FR_SWAP:
+	case PD_CTRL_GET_PPS_STATUS:
+	case PD_CTRL_GET_COUNTRY_CODES:
+		/* Currently not supported */
+		tcpm_queue_message(port, PD_MSG_CTRL_NOT_SUPP);
+		break;
 	default:
 		tcpm_log(port, "Unhandled ctrl message type %#x", type);
 		break;
 	}
 }
 
+static void tcpm_pd_ext_msg_request(struct tcpm_port *port,
+				    const struct pd_message *msg)
+{
+	enum pd_ext_msg_type type = pd_header_type_le(msg->header);
+	unsigned int data_size = pd_ext_header_data_size_le(msg->ext_msg.header);
+	u8 *data;
+
+	if (!(msg->ext_msg.header && PD_EXT_HDR_CHUNKED)) {
+		tcpm_log(port, "Unchunked extended messages unsupported");
+		return;
+	}
+
+	if (data_size > (PD_EXT_MAX_CHUNK_DATA)) {
+		tcpm_log(port, "Chunk handling not yet supported");
+		return;
+	}
+
+	data = kzalloc(data_size, GFP_KERNEL);
+	if (!data) {
+		tcpm_log(port, "Failed to allocate memory for ext msg data");
+		return;
+	}
+	memcpy(data, msg->ext_msg.data, data_size);
+
+	switch (type) {
+	case PD_EXT_STATUS:
+		/*
+		 * If PPS related events raised then get PPS status to clear
+		 * (see USB PD 3.0 Spec, 6.5.2.4)
+		 */
+		if (data[USB_PD_EXT_SDB_EVENT_FLAGS] & USB_PD_EXT_SDB_PPS_EVENTS)
+			tcpm_set_state(port, GET_PPS_STATUS_SEND, 0);
+		else
+			tcpm_set_state(port, ready_state(port), 0);
+		break;
+	case PD_EXT_PPS_STATUS:
+		/*
+		 * For now the PPS status message is used to clear events
+		 * and nothing more.
+		 */
+		tcpm_set_state(port, ready_state(port), 0);
+		break;
+	case PD_EXT_SOURCE_CAP_EXT:
+	case PD_EXT_GET_BATT_CAP:
+	case PD_EXT_GET_BATT_STATUS:
+	case PD_EXT_BATT_CAP:
+	case PD_EXT_GET_MANUFACTURER_INFO:
+	case PD_EXT_MANUFACTURER_INFO:
+	case PD_EXT_SECURITY_REQUEST:
+	case PD_EXT_SECURITY_RESPONSE:
+	case PD_EXT_FW_UPDATE_REQUEST:
+	case PD_EXT_FW_UPDATE_RESPONSE:
+	case PD_EXT_COUNTRY_INFO:
+	case PD_EXT_COUNTRY_CODES:
+		tcpm_queue_message(port, PD_MSG_CTRL_NOT_SUPP);
+		break;
+	default:
+		tcpm_log(port, "Unhandle extended message type %#x", type);
+		break;
+	}
+
+	kfree(data);
+}
+
 static void tcpm_pd_rx_handler(struct work_struct *work)
 {
 	struct pd_rx_event *event = container_of(work,
@@ -1615,7 +1736,9 @@ static void tcpm_pd_rx_handler(struct work_struct *work)
 				 "Data role mismatch, initiating error recovery");
 			tcpm_set_state(port, ERROR_RECOVERY, 0);
 		} else {
-			if (cnt)
+			if (msg->header & PD_HEADER_EXT_HDR)
+				tcpm_pd_ext_msg_request(port, msg);
+			else if (cnt)
 				tcpm_pd_data_request(port, msg);
 			else
 				tcpm_pd_ctrl_request(port, msg);
@@ -1676,6 +1799,9 @@ static bool tcpm_send_queued_message(struct tcpm_port *port)
 		case PD_MSG_CTRL_REJECT:
 			tcpm_pd_send_control(port, PD_CTRL_REJECT);
 			break;
+		case PD_MSG_CTRL_NOT_SUPP:
+			tcpm_pd_send_control(port, PD_CTRL_NOT_SUPP);
+			break;
 		case PD_MSG_DATA_SINK_CAP:
 			tcpm_pd_send_sink_caps(port);
 			break;
@@ -2348,14 +2474,6 @@ static inline enum tcpm_state hard_reset_state(struct tcpm_port *port)
 	return SNK_UNATTACHED;
 }
 
-static inline enum tcpm_state ready_state(struct tcpm_port *port)
-{
-	if (port->pwr_role == TYPEC_SOURCE)
-		return SRC_READY;
-	else
-		return SNK_READY;
-}
-
 static inline enum tcpm_state unattached_state(struct tcpm_port *port)
 {
 	if (port->port_type == TYPEC_PORT_DRP) {
@@ -3058,6 +3176,22 @@ static void run_state_machine(struct tcpm_port *port)
 		/* Always switch to unattached state */
 		tcpm_set_state(port, unattached_state(port), 0);
 		break;
+	case GET_STATUS_SEND:
+		tcpm_pd_send_control(port, PD_CTRL_GET_STATUS);
+		tcpm_set_state(port, GET_STATUS_SEND_TIMEOUT,
+			       PD_T_SENDER_RESPONSE);
+		break;
+	case GET_STATUS_SEND_TIMEOUT:
+		tcpm_set_state(port, ready_state(port), 0);
+		break;
+	case GET_PPS_STATUS_SEND:
+		tcpm_pd_send_control(port, PD_CTRL_GET_PPS_STATUS);
+		tcpm_set_state(port, GET_PPS_STATUS_SEND_TIMEOUT,
+			       PD_T_SENDER_RESPONSE);
+		break;
+	case GET_PPS_STATUS_SEND_TIMEOUT:
+		tcpm_set_state(port, ready_state(port), 0);
+		break;
 	case ERROR_RECOVERY:
 		tcpm_swap_complete(port, -EPROTO);
 		tcpm_pps_complete(port, -EPROTO);
-- 
1.9.1

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

* [RFC PATCH v2 7/7] typec: tcpm: Add support for sink PPS related messages
@ 2017-11-14 11:44   ` Adam Thomson
  0 siblings, 0 replies; 28+ messages in thread
From: Adam Thomson @ 2017-11-14 11:44 UTC (permalink / raw)
  To: Heikki Krogerus, Guenter Roeck, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Yueyao Zhu, Rui Miguel Silva
  Cc: linux-usb-u79uwXL29TY76Z2rM5mHXA,
	linux-pm-u79uwXL29TY76Z2rM5mHXA,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA,
	support.opensource-WBD+wuPFNBhBDgjK7y7TUQ

This commit adds sink side support for Get_Status, Status,
Get_PPS_Status and PPS_Status handling. As there's the
potential for a partner to respond with Not_Supported
handling of this message is also added. Sending of
Not_Supported is added is added to handle messages
received but not yet handled.

Signed-off-by: Adam Thomson <Adam.Thomson.Opensource-WBD+wuPFNBhBDgjK7y7TUQ@public.gmane.org>
---
 drivers/usb/typec/tcpm.c | 152 ++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 143 insertions(+), 9 deletions(-)

diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c
index 7c26c3d..8db49ab 100644
--- a/drivers/usb/typec/tcpm.c
+++ b/drivers/usb/typec/tcpm.c
@@ -19,7 +19,9 @@
 #include <linux/slab.h>
 #include <linux/spinlock.h>
 #include <linux/usb/pd.h>
+#include <linux/usb/pd_ado.h>
 #include <linux/usb/pd_bdo.h>
+#include <linux/usb/pd_ext_sdb.h>
 #include <linux/usb/pd_vdo.h>
 #include <linux/usb/tcpm.h>
 #include <linux/usb/typec.h>
@@ -113,6 +115,11 @@
 	S(SNK_TRYWAIT_VBUS),			\
 	S(BIST_RX),				\
 						\
+	S(GET_STATUS_SEND),			\
+	S(GET_STATUS_SEND_TIMEOUT),		\
+	S(GET_PPS_STATUS_SEND),			\
+	S(GET_PPS_STATUS_SEND_TIMEOUT),		\
+						\
 	S(ERROR_RECOVERY),			\
 	S(PORT_RESET),				\
 	S(PORT_RESET_WAIT_OFF)
@@ -143,6 +150,7 @@ enum pd_msg_request {
 	PD_MSG_NONE = 0,
 	PD_MSG_CTRL_REJECT,
 	PD_MSG_CTRL_WAIT,
+	PD_MSG_CTRL_NOT_SUPP,
 	PD_MSG_DATA_SINK_CAP,
 	PD_MSG_DATA_SOURCE_CAP,
 };
@@ -1285,10 +1293,42 @@ static void vdm_state_machine_work(struct work_struct *work)
 /*
  * PD (data, control) command handling functions
  */
+static inline enum tcpm_state ready_state(struct tcpm_port *port)
+{
+	if (port->pwr_role == TYPEC_SOURCE)
+		return SRC_READY;
+	else
+		return SNK_READY;
+}
 
 static int tcpm_pd_send_control(struct tcpm_port *port,
 				enum pd_ctrl_msg_type type);
 
+static void tcpm_handle_alert(struct tcpm_port *port, const __le32 *payload,
+			      int cnt)
+{
+	u32 p0 = le32_to_cpu(payload[0]);
+	unsigned int type = usb_pd_ado_type(p0);
+
+	if (!type) {
+		tcpm_log(port, "Alert message received with no type");
+		return;
+	}
+
+	/* Just handling non-battery alerts for now */
+	if (!(type & USB_PD_ADO_TYPE_BATT_STATUS_CHANGE)) {
+		switch (port->state) {
+		case SRC_READY:
+		case SNK_READY:
+			tcpm_set_state(port, GET_STATUS_SEND, 0);
+			break;
+		default:
+			tcpm_queue_message(port, PD_MSG_CTRL_WAIT);
+			break;
+		}
+	}
+}
+
 static void tcpm_pd_data_request(struct tcpm_port *port,
 				 const struct pd_message *msg)
 {
@@ -1371,6 +1411,14 @@ static void tcpm_pd_data_request(struct tcpm_port *port,
 			tcpm_set_state(port, BIST_RX, 0);
 		}
 		break;
+	case PD_DATA_ALERT:
+		tcpm_handle_alert(port, msg->payload, cnt);
+		break;
+	case PD_DATA_BATT_STATUS:
+	case PD_DATA_GET_COUNTRY_INFO:
+		/* Currently unsupported */
+		tcpm_queue_message(port, PD_MSG_CTRL_NOT_SUPP);
+		break;
 	default:
 		tcpm_log(port, "Unhandled data message type %#x", type);
 		break;
@@ -1453,6 +1501,7 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port,
 		break;
 	case PD_CTRL_REJECT:
 	case PD_CTRL_WAIT:
+	case PD_CTRL_NOT_SUPP:
 		switch (port->state) {
 		case SNK_NEGOTIATE_CAPABILITIES:
 			/* USB PD specification, Figure 8-43 */
@@ -1569,12 +1618,84 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port,
 			break;
 		}
 		break;
+	case PD_CTRL_GET_SOURCE_CAP_EXT:
+	case PD_CTRL_GET_STATUS:
+	case PD_CTRL_FR_SWAP:
+	case PD_CTRL_GET_PPS_STATUS:
+	case PD_CTRL_GET_COUNTRY_CODES:
+		/* Currently not supported */
+		tcpm_queue_message(port, PD_MSG_CTRL_NOT_SUPP);
+		break;
 	default:
 		tcpm_log(port, "Unhandled ctrl message type %#x", type);
 		break;
 	}
 }
 
+static void tcpm_pd_ext_msg_request(struct tcpm_port *port,
+				    const struct pd_message *msg)
+{
+	enum pd_ext_msg_type type = pd_header_type_le(msg->header);
+	unsigned int data_size = pd_ext_header_data_size_le(msg->ext_msg.header);
+	u8 *data;
+
+	if (!(msg->ext_msg.header && PD_EXT_HDR_CHUNKED)) {
+		tcpm_log(port, "Unchunked extended messages unsupported");
+		return;
+	}
+
+	if (data_size > (PD_EXT_MAX_CHUNK_DATA)) {
+		tcpm_log(port, "Chunk handling not yet supported");
+		return;
+	}
+
+	data = kzalloc(data_size, GFP_KERNEL);
+	if (!data) {
+		tcpm_log(port, "Failed to allocate memory for ext msg data");
+		return;
+	}
+	memcpy(data, msg->ext_msg.data, data_size);
+
+	switch (type) {
+	case PD_EXT_STATUS:
+		/*
+		 * If PPS related events raised then get PPS status to clear
+		 * (see USB PD 3.0 Spec, 6.5.2.4)
+		 */
+		if (data[USB_PD_EXT_SDB_EVENT_FLAGS] & USB_PD_EXT_SDB_PPS_EVENTS)
+			tcpm_set_state(port, GET_PPS_STATUS_SEND, 0);
+		else
+			tcpm_set_state(port, ready_state(port), 0);
+		break;
+	case PD_EXT_PPS_STATUS:
+		/*
+		 * For now the PPS status message is used to clear events
+		 * and nothing more.
+		 */
+		tcpm_set_state(port, ready_state(port), 0);
+		break;
+	case PD_EXT_SOURCE_CAP_EXT:
+	case PD_EXT_GET_BATT_CAP:
+	case PD_EXT_GET_BATT_STATUS:
+	case PD_EXT_BATT_CAP:
+	case PD_EXT_GET_MANUFACTURER_INFO:
+	case PD_EXT_MANUFACTURER_INFO:
+	case PD_EXT_SECURITY_REQUEST:
+	case PD_EXT_SECURITY_RESPONSE:
+	case PD_EXT_FW_UPDATE_REQUEST:
+	case PD_EXT_FW_UPDATE_RESPONSE:
+	case PD_EXT_COUNTRY_INFO:
+	case PD_EXT_COUNTRY_CODES:
+		tcpm_queue_message(port, PD_MSG_CTRL_NOT_SUPP);
+		break;
+	default:
+		tcpm_log(port, "Unhandle extended message type %#x", type);
+		break;
+	}
+
+	kfree(data);
+}
+
 static void tcpm_pd_rx_handler(struct work_struct *work)
 {
 	struct pd_rx_event *event = container_of(work,
@@ -1615,7 +1736,9 @@ static void tcpm_pd_rx_handler(struct work_struct *work)
 				 "Data role mismatch, initiating error recovery");
 			tcpm_set_state(port, ERROR_RECOVERY, 0);
 		} else {
-			if (cnt)
+			if (msg->header & PD_HEADER_EXT_HDR)
+				tcpm_pd_ext_msg_request(port, msg);
+			else if (cnt)
 				tcpm_pd_data_request(port, msg);
 			else
 				tcpm_pd_ctrl_request(port, msg);
@@ -1676,6 +1799,9 @@ static bool tcpm_send_queued_message(struct tcpm_port *port)
 		case PD_MSG_CTRL_REJECT:
 			tcpm_pd_send_control(port, PD_CTRL_REJECT);
 			break;
+		case PD_MSG_CTRL_NOT_SUPP:
+			tcpm_pd_send_control(port, PD_CTRL_NOT_SUPP);
+			break;
 		case PD_MSG_DATA_SINK_CAP:
 			tcpm_pd_send_sink_caps(port);
 			break;
@@ -2348,14 +2474,6 @@ static inline enum tcpm_state hard_reset_state(struct tcpm_port *port)
 	return SNK_UNATTACHED;
 }
 
-static inline enum tcpm_state ready_state(struct tcpm_port *port)
-{
-	if (port->pwr_role == TYPEC_SOURCE)
-		return SRC_READY;
-	else
-		return SNK_READY;
-}
-
 static inline enum tcpm_state unattached_state(struct tcpm_port *port)
 {
 	if (port->port_type == TYPEC_PORT_DRP) {
@@ -3058,6 +3176,22 @@ static void run_state_machine(struct tcpm_port *port)
 		/* Always switch to unattached state */
 		tcpm_set_state(port, unattached_state(port), 0);
 		break;
+	case GET_STATUS_SEND:
+		tcpm_pd_send_control(port, PD_CTRL_GET_STATUS);
+		tcpm_set_state(port, GET_STATUS_SEND_TIMEOUT,
+			       PD_T_SENDER_RESPONSE);
+		break;
+	case GET_STATUS_SEND_TIMEOUT:
+		tcpm_set_state(port, ready_state(port), 0);
+		break;
+	case GET_PPS_STATUS_SEND:
+		tcpm_pd_send_control(port, PD_CTRL_GET_PPS_STATUS);
+		tcpm_set_state(port, GET_PPS_STATUS_SEND_TIMEOUT,
+			       PD_T_SENDER_RESPONSE);
+		break;
+	case GET_PPS_STATUS_SEND_TIMEOUT:
+		tcpm_set_state(port, ready_state(port), 0);
+		break;
 	case ERROR_RECOVERY:
 		tcpm_swap_complete(port, -EPROTO);
 		tcpm_pps_complete(port, -EPROTO);
-- 
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* Re: [RFC PATCH v2 0/7] typec: tcpm: Add sink side support for PPS
  2017-11-14 11:44 ` Adam Thomson
                   ` (7 preceding siblings ...)
  (?)
@ 2017-11-21 13:35 ` Heikki Krogerus
  2017-11-21 13:51   ` Adam Thomson
  -1 siblings, 1 reply; 28+ messages in thread
From: Heikki Krogerus @ 2017-11-21 13:35 UTC (permalink / raw)
  To: Adam Thomson
  Cc: Guenter Roeck, Greg Kroah-Hartman, Sebastian Reichel,
	Hans de Goede, Yueyao Zhu, Rui Miguel Silva, linux-usb, linux-pm,
	linux-kernel, support.opensource

Hi Adam,

On Tue, Nov 14, 2017 at 11:44:41AM +0000, Adam Thomson wrote:
> This patch set adds sink side support for the PPS feature introduced in the
> USB PD 3.0 specification.
> 
> The source PPS supply is represented using the Power Supply framework to provide
> access and control APIs for dealing with it's operating voltage and current,
> and switching between a standard PDO and PPS APDO operation. During standard PDO
> operation the voltage and current is read-only, but for APDO PPS these are
> writable as well to allow for control.
> 
> It should be noted that the keepalive for PPS is not handled within TCPM. The
> expectation is that the external user will be required to ensure re-requests
> occur regularly to ensure PPS remains and the source does not hard reset.
> 
> Changes in v2:
>  - Use USB_PD and usb_pd prefixes for macros and inline functions in headers.
>  - Negotiate spec revision of PD headers during initial contract agreement.
>  - New headers now use SPDX tags for referencing correct license.
> 
> NOTE: Code changes are based on linux-next tag 'next-20171114' to pick up
> the move out of staging of TCPM related code.
> 
> Adam Thomson (7):
>   typec: tcpm: Add PD Rev 3.0 definitions to PD header
>   typec: tcpm: Add ADO header for Alert message handling
>   typec: tcpm: Add SDB header for Status message handling
>   typec: tcpm: Add core support for sink side PPS
>   power: supply: Add type for USB PD PPS chargers
>   typec: tcpm: Represent source supply through power_supply class
>   typec: tcpm: Add support for sink PPS related messages
> 
>  drivers/power/supply/power_supply_sysfs.c |   2 +-
>  drivers/usb/typec/Kconfig                 |   1 +
>  drivers/usb/typec/fusb302/Kconfig         |   2 +-
>  drivers/usb/typec/fusb302/fusb302.c       |  63 +--
>  drivers/usb/typec/tcpm.c                  | 851 +++++++++++++++++++++++++++++-
>  include/linux/power_supply.h              |   1 +
>  include/linux/usb/pd.h                    | 174 +++++-
>  include/linux/usb/pd_ado.h                |  42 ++
>  include/linux/usb/pd_ext_sdb.h            |  31 ++
>  include/linux/usb/tcpm.h                  |   2 +-
>  10 files changed, 1071 insertions(+), 98 deletions(-)
>  create mode 100644 include/linux/usb/pd_ado.h
>  create mode 100644 include/linux/usb/pd_ext_sdb.h

These don't apply on top of Badhri's series:
http://www.spinics.net/lists/kernel/msg2649921.html


Thanks,

-- 
heikki

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

* RE: [RFC PATCH v2 0/7] typec: tcpm: Add sink side support for PPS
  2017-11-21 13:35 ` [RFC PATCH v2 0/7] typec: tcpm: Add sink side support for PPS Heikki Krogerus
@ 2017-11-21 13:51   ` Adam Thomson
  2017-11-21 14:18     ` Heikki Krogerus
  0 siblings, 1 reply; 28+ messages in thread
From: Adam Thomson @ 2017-11-21 13:51 UTC (permalink / raw)
  To: Heikki Krogerus, Adam Thomson
  Cc: Guenter Roeck, Greg Kroah-Hartman, Sebastian Reichel,
	Hans de Goede, Yueyao Zhu, Rui Miguel Silva, linux-usb, linux-pm,
	linux-kernel, Support Opensource

On 21 November 2017 13:36, Heikki Krogerus write:

> Hi Adam,
> 
> On Tue, Nov 14, 2017 at 11:44:41AM +0000, Adam Thomson wrote:
> > This patch set adds sink side support for the PPS feature introduced in the
> > USB PD 3.0 specification.
> >
> > The source PPS supply is represented using the Power Supply framework to provide
> > access and control APIs for dealing with it's operating voltage and current,
> > and switching between a standard PDO and PPS APDO operation. During standard
> PDO
> > operation the voltage and current is read-only, but for APDO PPS these are
> > writable as well to allow for control.
> >
> > It should be noted that the keepalive for PPS is not handled within TCPM. The
> > expectation is that the external user will be required to ensure re-requests
> > occur regularly to ensure PPS remains and the source does not hard reset.
> >
> > Changes in v2:
> >  - Use USB_PD and usb_pd prefixes for macros and inline functions in headers.
> >  - Negotiate spec revision of PD headers during initial contract agreement.
> >  - New headers now use SPDX tags for referencing correct license.
> >
> > NOTE: Code changes are based on linux-next tag 'next-20171114' to pick up
> > the move out of staging of TCPM related code.
> >
> > Adam Thomson (7):
> >   typec: tcpm: Add PD Rev 3.0 definitions to PD header
> >   typec: tcpm: Add ADO header for Alert message handling
> >   typec: tcpm: Add SDB header for Status message handling
> >   typec: tcpm: Add core support for sink side PPS
> >   power: supply: Add type for USB PD PPS chargers
> >   typec: tcpm: Represent source supply through power_supply class
> >   typec: tcpm: Add support for sink PPS related messages
> >
> >  drivers/power/supply/power_supply_sysfs.c |   2 +-
> >  drivers/usb/typec/Kconfig                 |   1 +
> >  drivers/usb/typec/fusb302/Kconfig         |   2 +-
> >  drivers/usb/typec/fusb302/fusb302.c       |  63 +--
> >  drivers/usb/typec/tcpm.c                  | 851 +++++++++++++++++++++++++++++-
> >  include/linux/power_supply.h              |   1 +
> >  include/linux/usb/pd.h                    | 174 +++++-
> >  include/linux/usb/pd_ado.h                |  42 ++
> >  include/linux/usb/pd_ext_sdb.h            |  31 ++
> >  include/linux/usb/tcpm.h                  |   2 +-
> >  10 files changed, 1071 insertions(+), 98 deletions(-)
> >  create mode 100644 include/linux/usb/pd_ado.h
> >  create mode 100644 include/linux/usb/pd_ext_sdb.h
> 
> These don't apply on top of Badhri's series:
> http://www.spinics.net/lists/kernel/msg2649921.html

Hi Heikki,

When I submitted these I was aware of Badhri's patch set but at the time they
hadn't been approved or pulled, at least not into the version of Linux next
that I had based on. If these are now merged in to Linux Next then I'll re-spin
based on those changes.

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

* Re: [RFC PATCH v2 0/7] typec: tcpm: Add sink side support for PPS
  2017-11-21 13:51   ` Adam Thomson
@ 2017-11-21 14:18     ` Heikki Krogerus
  2017-11-21 14:21       ` Adam Thomson
  0 siblings, 1 reply; 28+ messages in thread
From: Heikki Krogerus @ 2017-11-21 14:18 UTC (permalink / raw)
  To: Adam Thomson
  Cc: Guenter Roeck, Greg Kroah-Hartman, Sebastian Reichel,
	Hans de Goede, Yueyao Zhu, Rui Miguel Silva, linux-usb, linux-pm,
	linux-kernel, Support Opensource

On Tue, Nov 21, 2017 at 01:51:41PM +0000, Adam Thomson wrote:
> > These don't apply on top of Badhri's series:
> > http://www.spinics.net/lists/kernel/msg2649921.html
> 
> Hi Heikki,
> 
> When I submitted these I was aware of Badhri's patch set but at the time they
> hadn't been approved or pulled, at least not into the version of Linux next
> that I had based on. If these are now merged in to Linux Next then I'll re-spin
> based on those changes.

Sure, just wanted to give you heads up in case you had not noticed.

Sorry for the noise.

Cheers,

-- 
heikki

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

* RE: [RFC PATCH v2 0/7] typec: tcpm: Add sink side support for PPS
  2017-11-21 14:18     ` Heikki Krogerus
@ 2017-11-21 14:21       ` Adam Thomson
  0 siblings, 0 replies; 28+ messages in thread
From: Adam Thomson @ 2017-11-21 14:21 UTC (permalink / raw)
  To: Heikki Krogerus, Adam Thomson
  Cc: Guenter Roeck, Greg Kroah-Hartman, Sebastian Reichel,
	Hans de Goede, Yueyao Zhu, Rui Miguel Silva, linux-usb, linux-pm,
	linux-kernel, Support Opensource

On 21 November 2017 14:18, Heikki Krogerus wrote:

> On Tue, Nov 21, 2017 at 01:51:41PM +0000, Adam Thomson wrote:
> > > These don't apply on top of Badhri's series:
> > > http://www.spinics.net/lists/kernel/msg2649921.html
> >
> > Hi Heikki,
> >
> > When I submitted these I was aware of Badhri's patch set but at the time they
> > hadn't been approved or pulled, at least not into the version of Linux next
> > that I had based on. If these are now merged in to Linux Next then I'll re-spin
> > based on those changes.
> 
> Sure, just wanted to give you heads up in case you had not noticed.
> 
> Sorry for the noise.

No noise, and thanks for the heads up. I actually hadn't seen that go in :)

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

* Re: [RFC PATCH v2 6/7] typec: tcpm: Represent source supply through power_supply class
  2017-11-14 11:44   ` Adam Thomson
  (?)
@ 2017-11-24 12:19   ` Heikki Krogerus
  2017-11-24 14:05     ` Adam Thomson
  2017-11-25 14:03     ` Hans de Goede
  -1 siblings, 2 replies; 28+ messages in thread
From: Heikki Krogerus @ 2017-11-24 12:19 UTC (permalink / raw)
  To: Adam Thomson, Hans de Goede
  Cc: Guenter Roeck, Greg Kroah-Hartman, Sebastian Reichel, Yueyao Zhu,
	Rui Miguel Silva, linux-usb, linux-pm, linux-kernel,
	support.opensource

Hi,

On Tue, Nov 14, 2017 at 11:44:47AM +0000, Adam Thomson wrote:
> diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c
> index 78983e1..7c26c3d 100644
> --- a/drivers/usb/typec/tcpm.c
> +++ b/drivers/usb/typec/tcpm.c
> @@ -12,6 +12,7 @@
>  #include <linux/kernel.h>
>  #include <linux/module.h>
>  #include <linux/mutex.h>
> +#include <linux/power_supply.h>
>  #include <linux/proc_fs.h>
>  #include <linux/sched/clock.h>
>  #include <linux/seq_file.h>
> @@ -277,6 +278,10 @@ struct tcpm_port {
>  	u32 current_limit;
>  	u32 supply_voltage;
>  
> +	/* Used to export TA voltage and current */
> +	struct power_supply *psy;
> +	struct power_supply_desc psy_desc;
> +
>  	u32 bist_request;
>  
>  	/* PD state for Vendor Defined Messages */
> @@ -1756,6 +1761,7 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
>  	int ret = -EINVAL;
>  
>  	port->pps_data.supported = false;
> +	port->psy_desc.type = POWER_SUPPLY_TYPE_USB_PD;
>  
>  	/*
>  	 * Select the source PDO providing the most power while staying within
> @@ -1775,8 +1781,10 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
>  			mv = pdo_min_voltage(pdo);
>  			break;
>  		case PDO_TYPE_APDO:
> -			if (pdo_apdo_type(pdo) == APDO_TYPE_PPS)
> +			if (pdo_apdo_type(pdo) == APDO_TYPE_PPS) {
>  				port->pps_data.supported = true;
> +				port->psy_desc.type = POWER_SUPPLY_TYPE_USB_PD_PPS;
> +			}
>  			continue;
>  		default:
>  			tcpm_log(port, "Invalid PDO type, ignoring");
> @@ -2248,6 +2256,9 @@ static void tcpm_reset_port(struct tcpm_port *port)
>  	port->try_snk_count = 0;
>  	port->supply_voltage = 0;
>  	port->current_limit = 0;
> +	port->psy_desc.type = POWER_SUPPLY_TYPE_USB_TYPE_C;

Is it OK to everybody that the type of the psy is changed like that?
Hans?!

We do have drivers that already change the type, for example
drivers/power/supply/isp1704_charger.c, but what does the user space
expect? The ABI for the power supply class was never documented I
guess.

I'm not against changing the type, but I think that we should have an
attribute file listing all supported types a psy can have if we go
forward with this. Ideally the type file would just list them as space
separated values, and show the current one with asterisk in front of
it. The output would be similar we have with some of the other files
under /sys/power, at least /sys/power/state, but that would break the
ABI.


Thanks,

-- 
heikki

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

* RE: [RFC PATCH v2 6/7] typec: tcpm: Represent source supply through power_supply class
  2017-11-24 12:19   ` Heikki Krogerus
@ 2017-11-24 14:05     ` Adam Thomson
  2017-11-27 14:11       ` Heikki Krogerus
  2017-11-25 14:03     ` Hans de Goede
  1 sibling, 1 reply; 28+ messages in thread
From: Adam Thomson @ 2017-11-24 14:05 UTC (permalink / raw)
  To: Heikki Krogerus, Adam Thomson, Hans de Goede, Sebastian Reichel
  Cc: Guenter Roeck, Greg Kroah-Hartman, Yueyao Zhu, Rui Miguel Silva,
	linux-usb, linux-pm, linux-kernel, Support Opensource

On 24 November 2017 12:19, Heikki Krogerus wrote:

> Hi,
> 
> On Tue, Nov 14, 2017 at 11:44:47AM +0000, Adam Thomson wrote:
> > diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c
> > index 78983e1..7c26c3d 100644
> > --- a/drivers/usb/typec/tcpm.c
> > +++ b/drivers/usb/typec/tcpm.c
> > @@ -12,6 +12,7 @@
> >  #include <linux/kernel.h>
> >  #include <linux/module.h>
> >  #include <linux/mutex.h>
> > +#include <linux/power_supply.h>
> >  #include <linux/proc_fs.h>
> >  #include <linux/sched/clock.h>
> >  #include <linux/seq_file.h>
> > @@ -277,6 +278,10 @@ struct tcpm_port {
> >  	u32 current_limit;
> >  	u32 supply_voltage;
> >
> > +	/* Used to export TA voltage and current */
> > +	struct power_supply *psy;
> > +	struct power_supply_desc psy_desc;
> > +
> >  	u32 bist_request;
> >
> >  	/* PD state for Vendor Defined Messages */
> > @@ -1756,6 +1761,7 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
> >  	int ret = -EINVAL;
> >
> >  	port->pps_data.supported = false;
> > +	port->psy_desc.type = POWER_SUPPLY_TYPE_USB_PD;
> >
> >  	/*
> >  	 * Select the source PDO providing the most power while staying within
> > @@ -1775,8 +1781,10 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
> >  			mv = pdo_min_voltage(pdo);
> >  			break;
> >  		case PDO_TYPE_APDO:
> > -			if (pdo_apdo_type(pdo) == APDO_TYPE_PPS)
> > +			if (pdo_apdo_type(pdo) == APDO_TYPE_PPS) {
> >  				port->pps_data.supported = true;
> > +				port->psy_desc.type =
> POWER_SUPPLY_TYPE_USB_PD_PPS;
> > +			}
> >  			continue;
> >  		default:
> >  			tcpm_log(port, "Invalid PDO type, ignoring");
> > @@ -2248,6 +2256,9 @@ static void tcpm_reset_port(struct tcpm_port *port)
> >  	port->try_snk_count = 0;
> >  	port->supply_voltage = 0;
> >  	port->current_limit = 0;
> > +	port->psy_desc.type = POWER_SUPPLY_TYPE_USB_TYPE_C;
> 
> Is it OK to everybody that the type of the psy is changed like that?
> Hans?!
> 
> We do have drivers that already change the type, for example
> drivers/power/supply/isp1704_charger.c, but what does the user space
> expect? The ABI for the power supply class was never documented I
> guess.
> 

Hi Heikki,

Appreciate your time in reviewing this.

Yes, I actually saw that as an example when I considered this approach. I didn't
see anything obvious for this in the ABI documentation. Any ideas Sebastian?
What is user-space expectation for the 'type' property of a power_supply? I
assume having this dynamic is ok given existing drivers can already do something
like this, but would be good to have clarification.

> I'm not against changing the type, but I think that we should have an
> attribute file listing all supported types a psy can have if we go
> forward with this. Ideally the type file would just list them as space
> separated values, and show the current one with asterisk in front of
> it. The output would be similar we have with some of the other files
> under /sys/power, at least /sys/power/state, but that would break the
> ABI.
> 

I added this as I wanted the user to know what was connected rather than
blindly trying to set the 'online' property to enable PPS, even if the attached
source partner didn't support this. As you say, am not sure we could change the
'TYPE' property as that to my knowledge has always been a single string.

Maybe the addition of a 'SUPPORTED_TYPES' property or something similar could
close this gap (as you eluded to), at least by providing a RO list of all
supported types? Another option would be to add a type which indicates the
supply supports multiple types, and then based on this we can read another
property which does as you suggest with multiple strings and one being
highlighted? Am certainly open to discussion on this.

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

* Re: [RFC PATCH v2 6/7] typec: tcpm: Represent source supply through power_supply class
  2017-11-24 12:19   ` Heikki Krogerus
  2017-11-24 14:05     ` Adam Thomson
@ 2017-11-25 14:03     ` Hans de Goede
  2017-11-27 13:38       ` Heikki Krogerus
  2017-11-27 13:43       ` Adam Thomson
  1 sibling, 2 replies; 28+ messages in thread
From: Hans de Goede @ 2017-11-25 14:03 UTC (permalink / raw)
  To: Heikki Krogerus, Adam Thomson
  Cc: Guenter Roeck, Greg Kroah-Hartman, Sebastian Reichel, Yueyao Zhu,
	Rui Miguel Silva, linux-usb, linux-pm, linux-kernel,
	support.opensource

Hi,

On 11/24/2017 01:19 PM, Heikki Krogerus wrote:
> Hi,
> 
> On Tue, Nov 14, 2017 at 11:44:47AM +0000, Adam Thomson wrote:
>> diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c
>> index 78983e1..7c26c3d 100644
>> --- a/drivers/usb/typec/tcpm.c
>> +++ b/drivers/usb/typec/tcpm.c
>> @@ -12,6 +12,7 @@
>>   #include <linux/kernel.h>
>>   #include <linux/module.h>
>>   #include <linux/mutex.h>
>> +#include <linux/power_supply.h>
>>   #include <linux/proc_fs.h>
>>   #include <linux/sched/clock.h>
>>   #include <linux/seq_file.h>
>> @@ -277,6 +278,10 @@ struct tcpm_port {
>>   	u32 current_limit;
>>   	u32 supply_voltage;
>>   
>> +	/* Used to export TA voltage and current */
>> +	struct power_supply *psy;
>> +	struct power_supply_desc psy_desc;
>> +
>>   	u32 bist_request;
>>   
>>   	/* PD state for Vendor Defined Messages */
>> @@ -1756,6 +1761,7 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
>>   	int ret = -EINVAL;
>>   
>>   	port->pps_data.supported = false;
>> +	port->psy_desc.type = POWER_SUPPLY_TYPE_USB_PD;
>>   
>>   	/*
>>   	 * Select the source PDO providing the most power while staying within
>> @@ -1775,8 +1781,10 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
>>   			mv = pdo_min_voltage(pdo);
>>   			break;
>>   		case PDO_TYPE_APDO:
>> -			if (pdo_apdo_type(pdo) == APDO_TYPE_PPS)
>> +			if (pdo_apdo_type(pdo) == APDO_TYPE_PPS) {
>>   				port->pps_data.supported = true;
>> +				port->psy_desc.type = POWER_SUPPLY_TYPE_USB_PD_PPS;
>> +			}
>>   			continue;
>>   		default:
>>   			tcpm_log(port, "Invalid PDO type, ignoring");
>> @@ -2248,6 +2256,9 @@ static void tcpm_reset_port(struct tcpm_port *port)
>>   	port->try_snk_count = 0;
>>   	port->supply_voltage = 0;
>>   	port->current_limit = 0;
>> +	port->psy_desc.type = POWER_SUPPLY_TYPE_USB_TYPE_C;
> 
> Is it OK to everybody that the type of the psy is changed like that?
> Hans?!
> 
> We do have drivers that already change the type, for example
> drivers/power/supply/isp1704_charger.c, but what does the user space
> expect? The ABI for the power supply class was never documented I
> guess.

The main userspace consumer of the power_supply sysfs class is upower,
upower knows about 3 types: "Mains", "Battery" and "USB", anything
else it gives a type of UP_DEVICE_KIND_UNKNOWN. So POWER_SUPPLY_TYPE_USB_TYPE_C
vs POWER_SUPPLY_TYPE_USB_PD_PPS does matter to it.

Regards,

Hans

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

* Re: [RFC PATCH v2 6/7] typec: tcpm: Represent source supply through power_supply class
  2017-11-25 14:03     ` Hans de Goede
@ 2017-11-27 13:38       ` Heikki Krogerus
  2017-11-27 13:43       ` Adam Thomson
  1 sibling, 0 replies; 28+ messages in thread
From: Heikki Krogerus @ 2017-11-27 13:38 UTC (permalink / raw)
  To: Hans de Goede
  Cc: Adam Thomson, Guenter Roeck, Greg Kroah-Hartman,
	Sebastian Reichel, Yueyao Zhu, Rui Miguel Silva, linux-usb,
	linux-pm, linux-kernel, support.opensource

On Sat, Nov 25, 2017 at 03:03:02PM +0100, Hans de Goede wrote:
> > Is it OK to everybody that the type of the psy is changed like that?
> > Hans?!
> > 
> > We do have drivers that already change the type, for example
> > drivers/power/supply/isp1704_charger.c, but what does the user space
> > expect? The ABI for the power supply class was never documented I
> > guess.
> 
> The main userspace consumer of the power_supply sysfs class is upower,
> upower knows about 3 types: "Mains", "Battery" and "USB", anything
> else it gives a type of UP_DEVICE_KIND_UNKNOWN. So POWER_SUPPLY_TYPE_USB_TYPE_C
> vs POWER_SUPPLY_TYPE_USB_PD_PPS does matter to it.

OK.

Thanks Hans,

-- 
heikki

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

* RE: [RFC PATCH v2 6/7] typec: tcpm: Represent source supply through power_supply class
  2017-11-25 14:03     ` Hans de Goede
  2017-11-27 13:38       ` Heikki Krogerus
@ 2017-11-27 13:43       ` Adam Thomson
  1 sibling, 0 replies; 28+ messages in thread
From: Adam Thomson @ 2017-11-27 13:43 UTC (permalink / raw)
  To: Hans de Goede, Heikki Krogerus, Adam Thomson
  Cc: Guenter Roeck, Greg Kroah-Hartman, Sebastian Reichel, Yueyao Zhu,
	Rui Miguel Silva, linux-usb, linux-pm, linux-kernel,
	Support Opensource

On 25 November 2017 14:03, Hans de Goede wrote:

> Hi,
> 
> On 11/24/2017 01:19 PM, Heikki Krogerus wrote:
> > Hi,
> >
> > On Tue, Nov 14, 2017 at 11:44:47AM +0000, Adam Thomson wrote:
> >> diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c
> >> index 78983e1..7c26c3d 100644
> >> --- a/drivers/usb/typec/tcpm.c
> >> +++ b/drivers/usb/typec/tcpm.c
> >> @@ -12,6 +12,7 @@
> >>   #include <linux/kernel.h>
> >>   #include <linux/module.h>
> >>   #include <linux/mutex.h>
> >> +#include <linux/power_supply.h>
> >>   #include <linux/proc_fs.h>
> >>   #include <linux/sched/clock.h>
> >>   #include <linux/seq_file.h>
> >> @@ -277,6 +278,10 @@ struct tcpm_port {
> >>   	u32 current_limit;
> >>   	u32 supply_voltage;
> >>
> >> +	/* Used to export TA voltage and current */
> >> +	struct power_supply *psy;
> >> +	struct power_supply_desc psy_desc;
> >> +
> >>   	u32 bist_request;
> >>
> >>   	/* PD state for Vendor Defined Messages */
> >> @@ -1756,6 +1761,7 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
> >>   	int ret = -EINVAL;
> >>
> >>   	port->pps_data.supported = false;
> >> +	port->psy_desc.type = POWER_SUPPLY_TYPE_USB_PD;
> >>
> >>   	/*
> >>   	 * Select the source PDO providing the most power while staying within
> >> @@ -1775,8 +1781,10 @@ static int tcpm_pd_select_pdo(struct tcpm_port
> *port)
> >>   			mv = pdo_min_voltage(pdo);
> >>   			break;
> >>   		case PDO_TYPE_APDO:
> >> -			if (pdo_apdo_type(pdo) == APDO_TYPE_PPS)
> >> +			if (pdo_apdo_type(pdo) == APDO_TYPE_PPS) {
> >>   				port->pps_data.supported = true;
> >> +				port->psy_desc.type =
> POWER_SUPPLY_TYPE_USB_PD_PPS;
> >> +			}
> >>   			continue;
> >>   		default:
> >>   			tcpm_log(port, "Invalid PDO type, ignoring");
> >> @@ -2248,6 +2256,9 @@ static void tcpm_reset_port(struct tcpm_port *port)
> >>   	port->try_snk_count = 0;
> >>   	port->supply_voltage = 0;
> >>   	port->current_limit = 0;
> >> +	port->psy_desc.type = POWER_SUPPLY_TYPE_USB_TYPE_C;
> >
> > Is it OK to everybody that the type of the psy is changed like that?
> > Hans?!
> >
> > We do have drivers that already change the type, for example
> > drivers/power/supply/isp1704_charger.c, but what does the user space
> > expect? The ABI for the power supply class was never documented I
> > guess.
> 
> The main userspace consumer of the power_supply sysfs class is upower,
> upower knows about 3 types: "Mains", "Battery" and "USB", anything
> else it gives a type of UP_DEVICE_KIND_UNKNOWN. So
> POWER_SUPPLY_TYPE_USB_TYPE_C
> vs POWER_SUPPLY_TYPE_USB_PD_PPS does matter to it.

Does or doesn't matter? I had a quick look at the code for upower and if I
understand correctly right now both USB_TYPE_C and USB_PD_PPS would be
represented as 'UP_DEVICE_KIND_UNKNOWN' based on g_ascii_strcasecmp result. Is
that correct?

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

* Re: [RFC PATCH v2 6/7] typec: tcpm: Represent source supply through power_supply class
  2017-11-24 14:05     ` Adam Thomson
@ 2017-11-27 14:11       ` Heikki Krogerus
  2017-11-27 16:54         ` Adam Thomson
  0 siblings, 1 reply; 28+ messages in thread
From: Heikki Krogerus @ 2017-11-27 14:11 UTC (permalink / raw)
  To: Adam Thomson
  Cc: Hans de Goede, Sebastian Reichel, Guenter Roeck,
	Greg Kroah-Hartman, Yueyao Zhu, Rui Miguel Silva, linux-usb,
	linux-pm, linux-kernel, Support Opensource

Hi Adam,

On Fri, Nov 24, 2017 at 02:05:27PM +0000, Adam Thomson wrote:
> On 24 November 2017 12:19, Heikki Krogerus wrote:
> > Is it OK to everybody that the type of the psy is changed like that?
> > Hans?!
> > 
> > We do have drivers that already change the type, for example
> > drivers/power/supply/isp1704_charger.c, but what does the user space
> > expect? The ABI for the power supply class was never documented I
> > guess.
> > 
> 
> Hi Heikki,
> 
> Appreciate your time in reviewing this.
> 
> Yes, I actually saw that as an example when I considered this approach. I didn't
> see anything obvious for this in the ABI documentation. Any ideas Sebastian?
> What is user-space expectation for the 'type' property of a power_supply? I
> assume having this dynamic is ok given existing drivers can already do something
> like this, but would be good to have clarification.
> 
> > I'm not against changing the type, but I think that we should have an
> > attribute file listing all supported types a psy can have if we go
> > forward with this. Ideally the type file would just list them as space
> > separated values, and show the current one with asterisk in front of
> > it. The output would be similar we have with some of the other files
> > under /sys/power, at least /sys/power/state, but that would break the
> > ABI.
> > 
> 
> I added this as I wanted the user to know what was connected rather than
> blindly trying to set the 'online' property to enable PPS, even if the attached
> source partner didn't support this. As you say, am not sure we could change the
> 'TYPE' property as that to my knowledge has always been a single string.
> 
> Maybe the addition of a 'SUPPORTED_TYPES' property or something similar could
> close this gap (as you eluded to), at least by providing a RO list of all
> supported types? Another option would be to add a type which indicates the
> supply supports multiple types, and then based on this we can read another
> property which does as you suggest with multiple strings and one being
> highlighted? Am certainly open to discussion on this.

It looks like this is USB specific problem. I think the type should
actually be just USB, and there should be an other USB only attribute
file which should list the current and supported connection types.
Well, maybe it does not even need to be USB only.


Thanks,

-- 
heikki

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

* RE: [RFC PATCH v2 6/7] typec: tcpm: Represent source supply through power_supply class
  2017-11-27 14:11       ` Heikki Krogerus
@ 2017-11-27 16:54         ` Adam Thomson
  2017-11-28 11:45           ` Heikki Krogerus
  0 siblings, 1 reply; 28+ messages in thread
From: Adam Thomson @ 2017-11-27 16:54 UTC (permalink / raw)
  To: Heikki Krogerus, Adam Thomson, Sebastian Reichel (sre@kernel.org)
  Cc: Hans de Goede, Guenter Roeck, Greg Kroah-Hartman, Yueyao Zhu,
	Rui Miguel Silva, linux-usb, linux-pm, linux-kernel,
	Support Opensource

On 27 November 2017 14:12, Heikki Krogerus wrote:

> Hi Adam,
> 
> On Fri, Nov 24, 2017 at 02:05:27PM +0000, Adam Thomson wrote:
> > On 24 November 2017 12:19, Heikki Krogerus wrote:
> > > Is it OK to everybody that the type of the psy is changed like that?
> > > Hans?!
> > >
> > > We do have drivers that already change the type, for example
> > > drivers/power/supply/isp1704_charger.c, but what does the user space
> > > expect? The ABI for the power supply class was never documented I
> > > guess.
> > >
> >
> > Hi Heikki,
> >
> > Appreciate your time in reviewing this.
> >
> > Yes, I actually saw that as an example when I considered this approach. I didn't
> > see anything obvious for this in the ABI documentation. Any ideas Sebastian?
> > What is user-space expectation for the 'type' property of a power_supply? I
> > assume having this dynamic is ok given existing drivers can already do something
> > like this, but would be good to have clarification.
> >
> > > I'm not against changing the type, but I think that we should have an
> > > attribute file listing all supported types a psy can have if we go
> > > forward with this. Ideally the type file would just list them as space
> > > separated values, and show the current one with asterisk in front of
> > > it. The output would be similar we have with some of the other files
> > > under /sys/power, at least /sys/power/state, but that would break the
> > > ABI.
> > >
> >
> > I added this as I wanted the user to know what was connected rather than
> > blindly trying to set the 'online' property to enable PPS, even if the attached
> > source partner didn't support this. As you say, am not sure we could change the
> > 'TYPE' property as that to my knowledge has always been a single string.
> >
> > Maybe the addition of a 'SUPPORTED_TYPES' property or something similar could
> > close this gap (as you eluded to), at least by providing a RO list of all
> > supported types? Another option would be to add a type which indicates the
> > supply supports multiple types, and then based on this we can read another
> > property which does as you suggest with multiple strings and one being
> > highlighted? Am certainly open to discussion on this.
> 
> It looks like this is USB specific problem. I think the type should
> actually be just USB, and there should be an other USB only attribute
> file which should list the current and supported connection types.
> Well, maybe it does not even need to be USB only.

I believe in Android (BatteryMonitor) right now it maps the various USB
types that can be reported by a power_supply driver to an internal Android 'AC'
type (or 'USB' for 'PD_DRP') for use in upper level software, so they handle the
various USB reported types but simplify reporting. That code seems like it would
also handle changes in type as well because the type is re-read when dealing
with updates. So there is a wide spread user which already 'copes' with current
driver implementations. That's not saying it's necessarily the correct way to go
in the future but wanted to highlight current usage.

Personally what you're suggesting seems reasonable to me. I would however like
to get a general consensus on this as I guess this could potentially change
future power_supply driver implementations.

Sebastian, do you have any thoughts on this topic?

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

* Re: [RFC PATCH v2 6/7] typec: tcpm: Represent source supply through power_supply class
  2017-11-27 16:54         ` Adam Thomson
@ 2017-11-28 11:45           ` Heikki Krogerus
  0 siblings, 0 replies; 28+ messages in thread
From: Heikki Krogerus @ 2017-11-28 11:45 UTC (permalink / raw)
  To: Adam Thomson
  Cc: Sebastian Reichel (sre@kernel.org),
	Hans de Goede, Guenter Roeck, Greg Kroah-Hartman, Yueyao Zhu,
	Rui Miguel Silva, linux-usb, linux-pm, linux-kernel,
	Support Opensource

On Mon, Nov 27, 2017 at 04:54:08PM +0000, Adam Thomson wrote:
> On 27 November 2017 14:12, Heikki Krogerus wrote:
> 
> > Hi Adam,
> > 
> > On Fri, Nov 24, 2017 at 02:05:27PM +0000, Adam Thomson wrote:
> > > On 24 November 2017 12:19, Heikki Krogerus wrote:
> > > > Is it OK to everybody that the type of the psy is changed like that?
> > > > Hans?!
> > > >
> > > > We do have drivers that already change the type, for example
> > > > drivers/power/supply/isp1704_charger.c, but what does the user space
> > > > expect? The ABI for the power supply class was never documented I
> > > > guess.
> > > >
> > >
> > > Hi Heikki,
> > >
> > > Appreciate your time in reviewing this.
> > >
> > > Yes, I actually saw that as an example when I considered this approach. I didn't
> > > see anything obvious for this in the ABI documentation. Any ideas Sebastian?
> > > What is user-space expectation for the 'type' property of a power_supply? I
> > > assume having this dynamic is ok given existing drivers can already do something
> > > like this, but would be good to have clarification.
> > >
> > > > I'm not against changing the type, but I think that we should have an
> > > > attribute file listing all supported types a psy can have if we go
> > > > forward with this. Ideally the type file would just list them as space
> > > > separated values, and show the current one with asterisk in front of
> > > > it. The output would be similar we have with some of the other files
> > > > under /sys/power, at least /sys/power/state, but that would break the
> > > > ABI.
> > > >
> > >
> > > I added this as I wanted the user to know what was connected rather than
> > > blindly trying to set the 'online' property to enable PPS, even if the attached
> > > source partner didn't support this. As you say, am not sure we could change the
> > > 'TYPE' property as that to my knowledge has always been a single string.
> > >
> > > Maybe the addition of a 'SUPPORTED_TYPES' property or something similar could
> > > close this gap (as you eluded to), at least by providing a RO list of all
> > > supported types? Another option would be to add a type which indicates the
> > > supply supports multiple types, and then based on this we can read another
> > > property which does as you suggest with multiple strings and one being
> > > highlighted? Am certainly open to discussion on this.
> > 
> > It looks like this is USB specific problem. I think the type should
> > actually be just USB, and there should be an other USB only attribute
> > file which should list the current and supported connection types.
> > Well, maybe it does not even need to be USB only.
> 
> I believe in Android (BatteryMonitor) right now it maps the various USB
> types that can be reported by a power_supply driver to an internal Android 'AC'
> type (or 'USB' for 'PD_DRP') for use in upper level software, so they handle the
> various USB reported types but simplify reporting. That code seems like it would
> also handle changes in type as well because the type is re-read when dealing
> with updates. So there is a wide spread user which already 'copes' with current
> driver implementations. That's not saying it's necessarily the correct way to go
> in the future but wanted to highlight current usage.
> 
> Personally what you're suggesting seems reasonable to me. I would however like
> to get a general consensus on this as I guess this could potentially change
> future power_supply driver implementations.

Agreed. What ever the final solution will be, the power supply ABI
should be documented at the same time.

> Sebastian, do you have any thoughts on this topic?


Cheers,

-- 
heikki

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

end of thread, other threads:[~2017-11-28 11:45 UTC | newest]

Thread overview: 28+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-11-14 11:44 [RFC PATCH v2 0/7] typec: tcpm: Add sink side support for PPS Adam Thomson
2017-11-14 11:44 ` Adam Thomson
2017-11-14 11:44 ` [RFC PATCH v2 1/7] typec: tcpm: Add PD Rev 3.0 definitions to PD header Adam Thomson
2017-11-14 11:44   ` Adam Thomson
2017-11-14 11:44 ` [RFC PATCH v2 2/7] typec: tcpm: Add ADO header for Alert message handling Adam Thomson
2017-11-14 11:44   ` Adam Thomson
2017-11-14 11:44 ` [RFC PATCH v2 3/7] typec: tcpm: Add SDB header for Status " Adam Thomson
2017-11-14 11:44   ` Adam Thomson
2017-11-14 11:44 ` [RFC PATCH v2 4/7] typec: tcpm: Add core support for sink side PPS Adam Thomson
2017-11-14 11:44   ` Adam Thomson
2017-11-14 11:44 ` [RFC PATCH v2 5/7] power: supply: Add type for USB PD PPS chargers Adam Thomson
2017-11-14 11:44   ` Adam Thomson
2017-11-14 11:44 ` [RFC PATCH v2 6/7] typec: tcpm: Represent source supply through power_supply class Adam Thomson
2017-11-14 11:44   ` Adam Thomson
2017-11-24 12:19   ` Heikki Krogerus
2017-11-24 14:05     ` Adam Thomson
2017-11-27 14:11       ` Heikki Krogerus
2017-11-27 16:54         ` Adam Thomson
2017-11-28 11:45           ` Heikki Krogerus
2017-11-25 14:03     ` Hans de Goede
2017-11-27 13:38       ` Heikki Krogerus
2017-11-27 13:43       ` Adam Thomson
2017-11-14 11:44 ` [RFC PATCH v2 7/7] typec: tcpm: Add support for sink PPS related messages Adam Thomson
2017-11-14 11:44   ` Adam Thomson
2017-11-21 13:35 ` [RFC PATCH v2 0/7] typec: tcpm: Add sink side support for PPS Heikki Krogerus
2017-11-21 13:51   ` Adam Thomson
2017-11-21 14:18     ` Heikki Krogerus
2017-11-21 14:21       ` Adam Thomson

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.