All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v4 0/7] typec: tcpm: Add sink side support for PPS
@ 2018-01-02 15:50 ` Adam Thomson
  0 siblings, 0 replies; 67+ messages in thread
From: Adam Thomson @ 2018-01-02 15:50 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 v4:
 - For PD 3.0 definitions patch, make it benign with regards to existing TCPM
   code so build isn't broken if this one patch is applied, as suggested by
   kbuild robot. Update for dynamic revision is moved to be part of sink side
   PPS support patch.
 - Use PTR_ERR_OR_ZERO macro to simplify return of devm_tcpm_psy_register()
   function, as suggested by kbuild robot.
 - Make devm_tcpm_psy_register() static as not used outside this file.

Changes in v3:
 - Drop 'RFC' from patch series titles
 - Rename PPS related defines to be PPS specific rather than generic APDO titles
 - Update source caps logging to only print PPS APDOs, and for others report as
   undefined.
 - Add ABI documentation for tcpm-source-psy sysfs properties
 - Rebase PDO selection on top of 'typec: tcpm: Only request matching pdos'
   patch.
 - Update capabilities validation introduced in
   'typec: tcpm: Validate source and sink caps' to support PPS APDOs.
 - Dropped power_supply 'type' property update for PPS addition
 - Added 'connected_type' property to power_supply framework, to support
   supplies which can report multiple connected types (e.g. USB), as discussed
   with Heikki.

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-20171212' to pick up
capabilities validation and selection updates.

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 'connected_type' property and supporting code
  typec: tcpm: Represent source supply through power_supply class
  typec: tcpm: Add support for sink PPS related messages

 .../ABI/testing/sysfs-class-power-tcpm-source-psy  |  92 +++
 drivers/power/supply/power_supply_sysfs.c          |  50 ++
 drivers/usb/typec/Kconfig                          |   1 +
 drivers/usb/typec/fusb302/Kconfig                  |   2 +-
 drivers/usb/typec/fusb302/fusb302.c                |  63 +-
 drivers/usb/typec/tcpm.c                           | 916 ++++++++++++++++++++-
 include/linux/power_supply.h                       |  15 +
 include/linux/usb/pd.h                             | 187 ++++-
 include/linux/usb/pd_ado.h                         |  42 +
 include/linux/usb/pd_ext_sdb.h                     |  31 +
 include/linux/usb/tcpm.h                           |   2 +-
 11 files changed, 1306 insertions(+), 95 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-class-power-tcpm-source-psy
 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] 67+ messages in thread

* [PATCH v4 0/7] typec: tcpm: Add sink side support for PPS
@ 2018-01-02 15:50 ` Adam Thomson
  0 siblings, 0 replies; 67+ messages in thread
From: Adam Thomson @ 2018-01-02 15:50 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 v4:
 - For PD 3.0 definitions patch, make it benign with regards to existing TCPM
   code so build isn't broken if this one patch is applied, as suggested by
   kbuild robot. Update for dynamic revision is moved to be part of sink side
   PPS support patch.
 - Use PTR_ERR_OR_ZERO macro to simplify return of devm_tcpm_psy_register()
   function, as suggested by kbuild robot.
 - Make devm_tcpm_psy_register() static as not used outside this file.

Changes in v3:
 - Drop 'RFC' from patch series titles
 - Rename PPS related defines to be PPS specific rather than generic APDO titles
 - Update source caps logging to only print PPS APDOs, and for others report as
   undefined.
 - Add ABI documentation for tcpm-source-psy sysfs properties
 - Rebase PDO selection on top of 'typec: tcpm: Only request matching pdos'
   patch.
 - Update capabilities validation introduced in
   'typec: tcpm: Validate source and sink caps' to support PPS APDOs.
 - Dropped power_supply 'type' property update for PPS addition
 - Added 'connected_type' property to power_supply framework, to support
   supplies which can report multiple connected types (e.g. USB), as discussed
   with Heikki.

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-20171212' to pick up
capabilities validation and selection updates.

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 'connected_type' property and supporting code
  typec: tcpm: Represent source supply through power_supply class
  typec: tcpm: Add support for sink PPS related messages

 .../ABI/testing/sysfs-class-power-tcpm-source-psy  |  92 +++
 drivers/power/supply/power_supply_sysfs.c          |  50 ++
 drivers/usb/typec/Kconfig                          |   1 +
 drivers/usb/typec/fusb302/Kconfig                  |   2 +-
 drivers/usb/typec/fusb302/fusb302.c                |  63 +-
 drivers/usb/typec/tcpm.c                           | 916 ++++++++++++++++++++-
 include/linux/power_supply.h                       |  15 +
 include/linux/usb/pd.h                             | 187 ++++-
 include/linux/usb/pd_ado.h                         |  42 +
 include/linux/usb/pd_ext_sdb.h                     |  31 +
 include/linux/usb/tcpm.h                           |   2 +-
 11 files changed, 1306 insertions(+), 95 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-class-power-tcpm-source-psy
 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] 67+ messages in thread

* [PATCH v4 1/7] typec: tcpm: Add PD Rev 3.0 definitions to PD header
@ 2018-01-02 15:50   ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 67+ messages in thread
From: Adam Thomson @ 2018-01-02 15:50 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 | 185 ++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 174 insertions(+), 11 deletions(-)

diff --git a/include/linux/usb/pd.h b/include/linux/usb/pd.h
index b3d41d7..ff359bdf 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)))
+	cpu_to_le16(PD_HEADER((type), (pwr), (data), PD_REV20, (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
@@ -174,6 +268,34 @@ 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_TYPE_MASK	0x3
+
+#define PDO_APDO_TYPE(t)	((t) << PDO_APDO_TYPE_SHIFT)
+
+#define PDO_PPS_APDO_MAX_VOLT_SHIFT	17	/* 100mV units */
+#define PDO_PPS_APDO_MIN_VOLT_SHIFT	8	/* 100mV units */
+#define PDO_PPS_APDO_MAX_CURR_SHIFT	0	/* 50mA units */
+
+#define PDO_PPS_APDO_VOLT_MASK	0xff
+#define PDO_PPS_APDO_CURR_MASK	0x7f
+
+#define PDO_PPS_APDO_MIN_VOLT(mv)	\
+	((((mv) / 100) & PDO_PPS_APDO_VOLT_MASK) << PDO_PPS_APDO_MIN_VOLT_SHIFT)
+#define PDO_PPS_APDO_MAX_VOLT(mv)	\
+	((((mv) / 100) & PDO_PPS_APDO_VOLT_MASK) << PDO_PPS_APDO_MAX_VOLT_SHIFT)
+#define PDO_PPS_APDO_MAX_CURR(ma)	\
+	((((ma) / 50) & PDO_PPS_APDO_CURR_MASK) << PDO_PPS_APDO_MAX_CURR_SHIFT)
+
+#define PDO_PPS_APDO(min_mv, max_mv, max_ma)				\
+	(PDO_TYPE(PDO_TYPE_APDO) | PDO_APDO_TYPE(APDO_TYPE_PPS) |	\
+	PDO_PPS_APDO_MIN_VOLT(min_mv) | PDO_PPS_APDO_MAX_VOLT(max_mv) |	\
+	PDO_PPS_APDO_MAX_CURR(max_ma))
+
 static inline enum pd_pdo_type pdo_type(u32 pdo)
 {
 	return (pdo >> PDO_TYPE_SHIFT) & PDO_TYPE_MASK;
@@ -204,6 +326,29 @@ 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_pps_apdo_min_voltage(u32 pdo)
+{
+	return ((pdo >> PDO_PPS_APDO_MIN_VOLT_SHIFT) &
+		PDO_PPS_APDO_VOLT_MASK) * 100;
+}
+
+static inline unsigned int pdo_pps_apdo_max_voltage(u32 pdo)
+{
+	return ((pdo >> PDO_PPS_APDO_MAX_VOLT_SHIFT) &
+		PDO_PPS_APDO_VOLT_MASK) * 100;
+}
+
+static inline unsigned int pdo_pps_apdo_max_current(u32 pdo)
+{
+	return ((pdo >> PDO_PPS_APDO_MAX_CURR_SHIFT) &
+		PDO_PPS_APDO_CURR_MASK) * 50;
+}
+
 /* RDO: Request Data Object */
 #define RDO_OBJ_POS_SHIFT	28
 #define RDO_OBJ_POS_MASK	0x7
@@ -237,6 +382,24 @@ 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 RDO_PROG_VOLT_MV_STEP	20
+#define RDO_PROG_CURR_MA_STEP	50
+
+#define PDO_PROG_OUT_VOLT(mv)	\
+	((((mv) / RDO_PROG_VOLT_MV_STEP) & RDO_PROG_VOLT_MASK) << RDO_PROG_VOLT_SHIFT)
+#define PDO_PROG_OP_CURR(ma)	\
+	((((ma) / RDO_PROG_CURR_MA_STEP) & 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] 67+ messages in thread

* [v4,1/7] typec: tcpm: Add PD Rev 3.0 definitions to PD header
@ 2018-01-02 15:50   ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 67+ messages in thread
From: Opensource [Adam Thomson] @ 2018-01-02 15:50 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 | 185 ++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 174 insertions(+), 11 deletions(-)

diff --git a/include/linux/usb/pd.h b/include/linux/usb/pd.h
index b3d41d7..ff359bdf 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)))
+	cpu_to_le16(PD_HEADER((type), (pwr), (data), PD_REV20, (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
@@ -174,6 +268,34 @@ 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_TYPE_MASK	0x3
+
+#define PDO_APDO_TYPE(t)	((t) << PDO_APDO_TYPE_SHIFT)
+
+#define PDO_PPS_APDO_MAX_VOLT_SHIFT	17	/* 100mV units */
+#define PDO_PPS_APDO_MIN_VOLT_SHIFT	8	/* 100mV units */
+#define PDO_PPS_APDO_MAX_CURR_SHIFT	0	/* 50mA units */
+
+#define PDO_PPS_APDO_VOLT_MASK	0xff
+#define PDO_PPS_APDO_CURR_MASK	0x7f
+
+#define PDO_PPS_APDO_MIN_VOLT(mv)	\
+	((((mv) / 100) & PDO_PPS_APDO_VOLT_MASK) << PDO_PPS_APDO_MIN_VOLT_SHIFT)
+#define PDO_PPS_APDO_MAX_VOLT(mv)	\
+	((((mv) / 100) & PDO_PPS_APDO_VOLT_MASK) << PDO_PPS_APDO_MAX_VOLT_SHIFT)
+#define PDO_PPS_APDO_MAX_CURR(ma)	\
+	((((ma) / 50) & PDO_PPS_APDO_CURR_MASK) << PDO_PPS_APDO_MAX_CURR_SHIFT)
+
+#define PDO_PPS_APDO(min_mv, max_mv, max_ma)				\
+	(PDO_TYPE(PDO_TYPE_APDO) | PDO_APDO_TYPE(APDO_TYPE_PPS) |	\
+	PDO_PPS_APDO_MIN_VOLT(min_mv) | PDO_PPS_APDO_MAX_VOLT(max_mv) |	\
+	PDO_PPS_APDO_MAX_CURR(max_ma))
+
 static inline enum pd_pdo_type pdo_type(u32 pdo)
 {
 	return (pdo >> PDO_TYPE_SHIFT) & PDO_TYPE_MASK;
@@ -204,6 +326,29 @@ 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_pps_apdo_min_voltage(u32 pdo)
+{
+	return ((pdo >> PDO_PPS_APDO_MIN_VOLT_SHIFT) &
+		PDO_PPS_APDO_VOLT_MASK) * 100;
+}
+
+static inline unsigned int pdo_pps_apdo_max_voltage(u32 pdo)
+{
+	return ((pdo >> PDO_PPS_APDO_MAX_VOLT_SHIFT) &
+		PDO_PPS_APDO_VOLT_MASK) * 100;
+}
+
+static inline unsigned int pdo_pps_apdo_max_current(u32 pdo)
+{
+	return ((pdo >> PDO_PPS_APDO_MAX_CURR_SHIFT) &
+		PDO_PPS_APDO_CURR_MASK) * 50;
+}
+
 /* RDO: Request Data Object */
 #define RDO_OBJ_POS_SHIFT	28
 #define RDO_OBJ_POS_MASK	0x7
@@ -237,6 +382,24 @@ 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 RDO_PROG_VOLT_MV_STEP	20
+#define RDO_PROG_CURR_MA_STEP	50
+
+#define PDO_PROG_OUT_VOLT(mv)	\
+	((((mv) / RDO_PROG_VOLT_MV_STEP) & RDO_PROG_VOLT_MASK) << RDO_PROG_VOLT_SHIFT)
+#define PDO_PROG_OP_CURR(ma)	\
+	((((ma) / RDO_PROG_CURR_MA_STEP) & 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;

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

* [PATCH v4 1/7] typec: tcpm: Add PD Rev 3.0 definitions to PD header
@ 2018-01-02 15:50   ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 67+ messages in thread
From: Adam Thomson @ 2018-01-02 15:50 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 | 185 ++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 174 insertions(+), 11 deletions(-)

diff --git a/include/linux/usb/pd.h b/include/linux/usb/pd.h
index b3d41d7..ff359bdf 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)))
+	cpu_to_le16(PD_HEADER((type), (pwr), (data), PD_REV20, (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
@@ -174,6 +268,34 @@ 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_TYPE_MASK	0x3
+
+#define PDO_APDO_TYPE(t)	((t) << PDO_APDO_TYPE_SHIFT)
+
+#define PDO_PPS_APDO_MAX_VOLT_SHIFT	17	/* 100mV units */
+#define PDO_PPS_APDO_MIN_VOLT_SHIFT	8	/* 100mV units */
+#define PDO_PPS_APDO_MAX_CURR_SHIFT	0	/* 50mA units */
+
+#define PDO_PPS_APDO_VOLT_MASK	0xff
+#define PDO_PPS_APDO_CURR_MASK	0x7f
+
+#define PDO_PPS_APDO_MIN_VOLT(mv)	\
+	((((mv) / 100) & PDO_PPS_APDO_VOLT_MASK) << PDO_PPS_APDO_MIN_VOLT_SHIFT)
+#define PDO_PPS_APDO_MAX_VOLT(mv)	\
+	((((mv) / 100) & PDO_PPS_APDO_VOLT_MASK) << PDO_PPS_APDO_MAX_VOLT_SHIFT)
+#define PDO_PPS_APDO_MAX_CURR(ma)	\
+	((((ma) / 50) & PDO_PPS_APDO_CURR_MASK) << PDO_PPS_APDO_MAX_CURR_SHIFT)
+
+#define PDO_PPS_APDO(min_mv, max_mv, max_ma)				\
+	(PDO_TYPE(PDO_TYPE_APDO) | PDO_APDO_TYPE(APDO_TYPE_PPS) |	\
+	PDO_PPS_APDO_MIN_VOLT(min_mv) | PDO_PPS_APDO_MAX_VOLT(max_mv) |	\
+	PDO_PPS_APDO_MAX_CURR(max_ma))
+
 static inline enum pd_pdo_type pdo_type(u32 pdo)
 {
 	return (pdo >> PDO_TYPE_SHIFT) & PDO_TYPE_MASK;
@@ -204,6 +326,29 @@ 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_pps_apdo_min_voltage(u32 pdo)
+{
+	return ((pdo >> PDO_PPS_APDO_MIN_VOLT_SHIFT) &
+		PDO_PPS_APDO_VOLT_MASK) * 100;
+}
+
+static inline unsigned int pdo_pps_apdo_max_voltage(u32 pdo)
+{
+	return ((pdo >> PDO_PPS_APDO_MAX_VOLT_SHIFT) &
+		PDO_PPS_APDO_VOLT_MASK) * 100;
+}
+
+static inline unsigned int pdo_pps_apdo_max_current(u32 pdo)
+{
+	return ((pdo >> PDO_PPS_APDO_MAX_CURR_SHIFT) &
+		PDO_PPS_APDO_CURR_MASK) * 50;
+}
+
 /* RDO: Request Data Object */
 #define RDO_OBJ_POS_SHIFT	28
 #define RDO_OBJ_POS_MASK	0x7
@@ -237,6 +382,24 @@ 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 RDO_PROG_VOLT_MV_STEP	20
+#define RDO_PROG_CURR_MA_STEP	50
+
+#define PDO_PROG_OUT_VOLT(mv)	\
+	((((mv) / RDO_PROG_VOLT_MV_STEP) & RDO_PROG_VOLT_MASK) << RDO_PROG_VOLT_SHIFT)
+#define PDO_PROG_OP_CURR(ma)	\
+	((((ma) / RDO_PROG_CURR_MA_STEP) & 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] 67+ messages in thread

* [PATCH v4 2/7] typec: tcpm: Add ADO header for Alert message handling
@ 2018-01-02 15:50   ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 67+ messages in thread
From: Adam Thomson @ 2018-01-02 15:50 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] 67+ messages in thread

* [v4,2/7] typec: tcpm: Add ADO header for Alert message handling
@ 2018-01-02 15:50   ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 67+ messages in thread
From: Opensource [Adam Thomson] @ 2018-01-02 15:50 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 */

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

* [PATCH v4 2/7] typec: tcpm: Add ADO header for Alert message handling
@ 2018-01-02 15:50   ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 67+ messages in thread
From: Adam Thomson @ 2018-01-02 15:50 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] 67+ messages in thread

* [PATCH v4 3/7] typec: tcpm: Add SDB header for Status message handling
@ 2018-01-02 15:50   ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 67+ messages in thread
From: Adam Thomson @ 2018-01-02 15:50 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] 67+ messages in thread

* [v4,3/7] typec: tcpm: Add SDB header for Status message handling
@ 2018-01-02 15:50   ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 67+ messages in thread
From: Opensource [Adam Thomson] @ 2018-01-02 15:50 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 */

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

* [PATCH v4 3/7] typec: tcpm: Add SDB header for Status message handling
@ 2018-01-02 15:50   ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 67+ messages in thread
From: Adam Thomson @ 2018-01-02 15:50 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] 67+ messages in thread

* [PATCH v4 4/7] typec: tcpm: Add core support for sink side PPS
@ 2018-01-02 15:50   ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 67+ messages in thread
From: Adam Thomson @ 2018-01-02 15:50 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 | 533 ++++++++++++++++++++++++++++++++++++++++++++++-
 include/linux/usb/pd.h   |   4 +-
 include/linux/usb/tcpm.h |   2 +-
 3 files changed, 525 insertions(+), 14 deletions(-)

diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c
index f4d563e..b66d26c 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 {
+	u32 min_volt;
+	u32 max_volt;
+	u32 max_curr;
+	u32 out_volt;
+	u32 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;
@@ -255,6 +267,7 @@ struct tcpm_port {
 	unsigned int nr_fixed; /* number of fixed sink PDOs */
 	unsigned int nr_var; /* number of variable sink PDOs */
 	unsigned int nr_batt; /* number of battery sink PDOs */
+	unsigned int nr_apdo; /* number of APDO type PDOs */
 	u32 snk_vdo[VDO_MAX_OBJECTS];
 	unsigned int nr_snk_vdo;
 
@@ -262,6 +275,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;
@@ -278,8 +292,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];
@@ -497,6 +516,16 @@ static void tcpm_log_source_caps(struct tcpm_port *port)
 				  pdo_max_voltage(pdo),
 				  pdo_max_power(pdo));
 			break;
+		case PDO_TYPE_APDO:
+			if (pdo_apdo_type(pdo) == APDO_TYPE_PPS)
+				scnprintf(msg, sizeof(msg),
+					  "%u-%u mV, %u mA",
+					  pdo_pps_apdo_min_voltage(pdo),
+					  pdo_pps_apdo_max_voltage(pdo),
+					  pdo_pps_apdo_max_current(pdo));
+			else
+				strcpy(msg, "undefined APDO");
+			break;
 		default:
 			strcpy(msg, "undefined");
 			break;
@@ -791,11 +820,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);
 	}
@@ -816,11 +847,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);
 	}
@@ -1187,6 +1220,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]);
@@ -1258,6 +1292,8 @@ enum pdo_err {
 	PDO_ERR_FIXED_NOT_SORTED,
 	PDO_ERR_VARIABLE_BATT_NOT_SORTED,
 	PDO_ERR_DUPE_PDO,
+	PDO_ERR_PPS_APDO_NOT_SORTED,
+	PDO_ERR_DUPE_PPS_APDO,
 };
 
 static const char * const pdo_err_msg[] = {
@@ -1273,6 +1309,10 @@ enum pdo_err {
 	" err: Variable/Battery supply pdos should be in increasing order of their minimum voltage",
 	[PDO_ERR_DUPE_PDO] =
 	" err: Variable/Batt supply pdos cannot have same min/max voltage",
+	[PDO_ERR_PPS_APDO_NOT_SORTED] =
+	" err: Programmable power supply apdos should be in increasing order of their maximum voltage",
+	[PDO_ERR_DUPE_PPS_APDO] =
+	" err: Programmable power supply apdos cannot have same min/max voltage and max current",
 };
 
 static enum pdo_err tcpm_caps_err(struct tcpm_port *port, const u32 *pdo,
@@ -1322,6 +1362,26 @@ static enum pdo_err tcpm_caps_err(struct tcpm_port *port, const u32 *pdo,
 					  pdo_min_voltage(pdo[i - 1])))
 					return PDO_ERR_DUPE_PDO;
 				break;
+			/*
+			 * The Programmable Power Supply APDOs, if present,
+			 * shall be sent in Maximum Voltage order;
+			 * lowest to highest.
+			 */
+			case PDO_TYPE_APDO:
+				if (pdo_apdo_type(pdo[i]) != APDO_TYPE_PPS)
+					break;
+
+				if (pdo_pps_apdo_max_current(pdo[i]) <
+				    pdo_pps_apdo_max_current(pdo[i - 1]))
+					return PDO_ERR_PPS_APDO_NOT_SORTED;
+				else if ((pdo_pps_apdo_min_voltage(pdo[i]) ==
+					  pdo_pps_apdo_min_voltage(pdo[i - 1])) &&
+					 (pdo_pps_apdo_max_voltage(pdo[i]) ==
+					  pdo_pps_apdo_max_voltage(pdo[i - 1])) &&
+					 (pdo_pps_apdo_max_current(pdo[i]) ==
+					  pdo_pps_apdo_max_current(pdo[i - 1])))
+					return PDO_ERR_DUPE_PPS_APDO;
+				break;
 			default:
 				tcpm_log_force(port, " Unknown pdo type");
 			}
@@ -1347,11 +1407,16 @@ static int tcpm_validate_caps(struct tcpm_port *port, const u32 *pdo,
 /*
  * 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) {
@@ -1370,6 +1435,16 @@ static void tcpm_pd_data_request(struct tcpm_port *port,
 				   port->nr_source_caps);
 
 		/*
+		 * 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.
@@ -1390,6 +1465,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;
@@ -1414,6 +1502,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)
 {
@@ -1490,6 +1587,14 @@ 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:
+			/* Revert data back from any requested PPS updates */
+			port->pps_data.out_volt = port->supply_voltage;
+			port->pps_data.op_curr = port->current_limit;
+			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);
@@ -1512,6 +1617,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->supply_voltage = port->pps_data.out_volt;
+			port->current_limit = port->pps_data.op_curr;
 			tcpm_set_state(port, SNK_TRANSITION_SINK, 0);
 			break;
 		case SOFT_RESET_SEND:
@@ -1666,6 +1778,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);
@@ -1779,6 +1892,8 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo,
 	unsigned int i, j, max_mw = 0, max_mv = 0, mw = 0, mv = 0, ma = 0;
 	int ret = -EINVAL;
 
+	port->pps_data.supported = false;
+
 	/*
 	 * Select the source PDO providing the most power which has a
 	 * matchig sink cap.
@@ -1787,7 +1902,8 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo,
 		u32 pdo = port->source_caps[i];
 		enum pd_pdo_type type = pdo_type(pdo);
 
-		if (type == PDO_TYPE_FIXED) {
+		switch (type) {
+		case PDO_TYPE_FIXED:
 			for (j = 0; j < port->nr_fixed; j++) {
 				if (pdo_fixed_voltage(pdo) ==
 				    pdo_fixed_voltage(port->snk_pdo[j])) {
@@ -1809,7 +1925,8 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo,
 					break;
 				}
 			}
-		} else if (type == PDO_TYPE_BATT) {
+			break;
+		case PDO_TYPE_BATT:
 			for (j = port->nr_fixed;
 			     j < port->nr_fixed +
 				 port->nr_batt;
@@ -1830,7 +1947,8 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo,
 					}
 				}
 			}
-		} else if (type == PDO_TYPE_VAR) {
+			break;
+		case PDO_TYPE_VAR:
 			for (j = port->nr_fixed +
 				 port->nr_batt;
 			     j < port->nr_fixed +
@@ -1854,12 +1972,98 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_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;
 		}
 	}
 
 	return ret;
 }
 
+#define min_pps_apdo_current(x, y)	\
+	min(pdo_pps_apdo_max_current(x), pdo_pps_apdo_max_current(y))
+
+static unsigned int tcpm_pd_select_pps_apdo(struct tcpm_port *port,
+					    int *snk_pdo, int *src_pdo)
+{
+	unsigned int i, j, max_mw = 0, max_mv = 0, mw = 0, mv = 0, ma = 0;
+	enum pd_pdo_type type;
+	u32 pdo;
+	int ret = -EOPNOTSUPP;
+
+	/*
+	 * 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.
+	 */
+	*src_pdo = 0;
+	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;
+			}
+
+			for (j = port->nr_fixed +
+				 port->nr_batt +
+				 port->nr_var;
+			     j < port->nr_fixed +
+				 port->nr_batt +
+				 port->nr_var +
+				 port->nr_apdo;
+			     ++j) {
+				if ((pdo_pps_apdo_min_voltage(pdo) >=
+				     pdo_pps_apdo_min_voltage(port->snk_pdo[j])) &&
+				    (pdo_pps_apdo_max_voltage(pdo) <=
+				     pdo_pps_apdo_max_voltage(port->snk_pdo[j]))) {
+					ma = min_pps_apdo_current(pdo,
+								  port->snk_pdo[j]);
+					mv = pdo_pps_apdo_max_voltage(pdo);
+					mw = (ma * mv) / 1000;
+					if ((mw > max_mw) ||
+					    ((mw == max_mw) && (mv > max_mv))) {
+						ret = 0;
+						*src_pdo = i;
+						*snk_pdo = j;
+						max_mw = mw;
+						max_mv = mv;
+					}
+				}
+			}
+
+			break;
+		default:
+			tcpm_log(port, "Not APDO type, ignoring");
+			continue;
+		}
+	}
+
+	if (*src_pdo > 0) {
+		pdo = port->source_caps[*src_pdo];
+
+		port->pps_data.min_volt = pdo_pps_apdo_min_voltage(pdo);
+		port->pps_data.max_volt = pdo_pps_apdo_max_voltage(pdo);
+		port->pps_data.max_curr =
+			min_pps_apdo_current(pdo, port->snk_pdo[*snk_pdo]);
+		port->pps_data.out_volt =
+			min(port->pps_data.out_volt, pdo_pps_apdo_max_voltage(pdo));
+		port->pps_data.op_curr =
+			min(port->pps_data.op_curr, pdo_pps_apdo_max_current(pdo));
+	}
+
+	return ret;
+}
+
 static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo)
 {
 	unsigned int mv, ma, mw, flags;
@@ -1875,10 +2079,18 @@ static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo)
 	matching_snk_pdo = port->snk_pdo[snk_pdo_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 sink pdo's limit */
 	if (type == PDO_TYPE_BATT) {
@@ -1943,6 +2155,107 @@ 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 out_mv, op_ma, op_mw, min_mv, max_mv, max_ma, flags;
+	enum pd_pdo_type type;
+	int src_pdo_index, snk_pdo_index;
+	u32 pdo, matching_snk_pdo;
+	int ret;
+
+	ret = tcpm_pd_select_pps_apdo(port, &snk_pdo_index, &src_pdo_index);
+	if (ret)
+		return ret;
+
+	pdo = port->source_caps[src_pdo_index];
+	matching_snk_pdo = port->snk_pdo[snk_pdo_index];
+	type = pdo_type(pdo);
+
+	switch (type) {
+	case PDO_TYPE_APDO:
+		if (pdo_apdo_type(pdo) != APDO_TYPE_PPS) {
+			tcpm_log(port, "Invalid APDO selected!");
+			return -EINVAL;
+		}
+		min_mv = pdo_pps_apdo_min_voltage(pdo);
+		max_mv = pdo_pps_apdo_max_voltage(pdo);
+		max_ma = pdo_pps_apdo_max_current(pdo);
+		out_mv = port->pps_data.out_volt;
+		op_ma = port->pps_data.op_curr;
+		break;
+	default:
+		tcpm_log(port, "Invalid PDO selected!");
+		return -EINVAL;
+	}
+
+	flags = RDO_USB_COMM | RDO_NO_SUSPEND;
+
+	op_mw = (op_ma * out_mv) / 1000;
+	if (op_mw < port->operating_snk_mw) {
+		/*
+		 * Try raising current to meet power needs. If that's not enough
+		 * then try upping the voltage. If that's still not enoguh
+		 * then we've obviously chosen a PPS APDO which really isn't
+		 * suitable so abandon ship.
+		 */
+		op_ma = ((port->operating_snk_mw * 1000) / out_mv);
+		if ((port->operating_snk_mw * 1000) % out_mv)
+			++op_ma;
+		op_ma += RDO_PROG_CURR_MA_STEP - (op_ma % RDO_PROG_CURR_MA_STEP);
+
+		if (op_ma > max_ma) {
+			op_ma = max_ma;
+			out_mv = ((port->operating_snk_mw * 1000) / op_ma);
+			if ((port->operating_snk_mw * 1000) % op_ma)
+				++out_mv;
+			out_mv += RDO_PROG_VOLT_MV_STEP -
+				  (out_mv % RDO_PROG_VOLT_MV_STEP);
+
+			if (out_mv > max_mv) {
+				tcpm_log(port, "Invalid PPS APDO selected!");
+				return -EINVAL;
+			}
+		}
+	}
+
+	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(src_pdo_index + 1, out_mv, op_ma, flags);
+
+	tcpm_log(port, "Requesting APDO %d: %u mV, %u mA",
+		 src_pdo_index, out_mv, op_ma);
+
+	port->pps_data.op_curr = op_ma;
+	port->pps_data.out_volt = 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);
 
@@ -2128,6 +2441,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
@@ -2143,6 +2457,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)
@@ -2389,6 +2705,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;
@@ -2449,6 +2766,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
@@ -2472,6 +2790,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);
@@ -2480,6 +2799,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 &&
@@ -2561,6 +2881,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;
@@ -2631,6 +2952,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),
@@ -2638,6 +2977,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);
@@ -2646,7 +2986,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 */
@@ -2693,6 +3037,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);
@@ -2913,6 +3258,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:
@@ -3378,7 +3724,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;
@@ -3423,7 +3769,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;
@@ -3463,7 +3809,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;
@@ -3495,6 +3841,162 @@ 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) {
+		ret = -EINVAL;
+		goto port_unlock;
+	}
+
+	reinit_completion(&port->pps_complete);
+	port->pps_data.op_curr = 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) {
+		ret = -EINVAL;
+		goto port_unlock;
+	}
+
+	reinit_completion(&port->pps_complete);
+	port->pps_data.out_volt = 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) {
+		port->pps_data.out_volt = port->supply_voltage;
+		port->pps_data.op_curr = port->current_limit;
+		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;
@@ -3634,13 +4136,18 @@ int 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;
@@ -3695,6 +4202,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);
 	tcpm_debugfs_init(port);
 
 	if (tcpm_validate_caps(port, tcpc->config->src_pdo,
@@ -3717,6 +4225,9 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
 	port->nr_batt = nr_type_pdos(port->snk_pdo,
 				     port->nr_snk_pdo,
 				     PDO_TYPE_BATT);
+	port->nr_apdo = nr_type_pdos(port->snk_pdo,
+				     port->nr_snk_pdo,
+				     PDO_TYPE_APDO);
 	port->nr_snk_vdo = tcpm_copy_vdos(port->snk_vdo, tcpc->config->snk_vdo,
 					  tcpc->config->nr_snk_vdo);
 
@@ -3732,7 +4243,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/pd.h b/include/linux/usb/pd.h
index ff359bdf..09b570f 100644
--- a/include/linux/usb/pd.h
+++ b/include/linux/usb/pd.h
@@ -103,8 +103,8 @@ enum pd_ext_msg_type {
 	 (((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), PD_REV20, (id), (cnt), (0)))
+#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)
 {
diff --git a/include/linux/usb/tcpm.h b/include/linux/usb/tcpm.h
index ca1c0b5..d6673f7 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] 67+ messages in thread

* [v4,4/7] typec: tcpm: Add core support for sink side PPS
@ 2018-01-02 15:50   ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 67+ messages in thread
From: Opensource [Adam Thomson] @ 2018-01-02 15:50 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 | 533 ++++++++++++++++++++++++++++++++++++++++++++++-
 include/linux/usb/pd.h   |   4 +-
 include/linux/usb/tcpm.h |   2 +-
 3 files changed, 525 insertions(+), 14 deletions(-)

diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c
index f4d563e..b66d26c 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 {
+	u32 min_volt;
+	u32 max_volt;
+	u32 max_curr;
+	u32 out_volt;
+	u32 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;
@@ -255,6 +267,7 @@ struct tcpm_port {
 	unsigned int nr_fixed; /* number of fixed sink PDOs */
 	unsigned int nr_var; /* number of variable sink PDOs */
 	unsigned int nr_batt; /* number of battery sink PDOs */
+	unsigned int nr_apdo; /* number of APDO type PDOs */
 	u32 snk_vdo[VDO_MAX_OBJECTS];
 	unsigned int nr_snk_vdo;
 
@@ -262,6 +275,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;
@@ -278,8 +292,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];
@@ -497,6 +516,16 @@ static void tcpm_log_source_caps(struct tcpm_port *port)
 				  pdo_max_voltage(pdo),
 				  pdo_max_power(pdo));
 			break;
+		case PDO_TYPE_APDO:
+			if (pdo_apdo_type(pdo) == APDO_TYPE_PPS)
+				scnprintf(msg, sizeof(msg),
+					  "%u-%u mV, %u mA",
+					  pdo_pps_apdo_min_voltage(pdo),
+					  pdo_pps_apdo_max_voltage(pdo),
+					  pdo_pps_apdo_max_current(pdo));
+			else
+				strcpy(msg, "undefined APDO");
+			break;
 		default:
 			strcpy(msg, "undefined");
 			break;
@@ -791,11 +820,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);
 	}
@@ -816,11 +847,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);
 	}
@@ -1187,6 +1220,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]);
@@ -1258,6 +1292,8 @@ enum pdo_err {
 	PDO_ERR_FIXED_NOT_SORTED,
 	PDO_ERR_VARIABLE_BATT_NOT_SORTED,
 	PDO_ERR_DUPE_PDO,
+	PDO_ERR_PPS_APDO_NOT_SORTED,
+	PDO_ERR_DUPE_PPS_APDO,
 };
 
 static const char * const pdo_err_msg[] = {
@@ -1273,6 +1309,10 @@ enum pdo_err {
 	" err: Variable/Battery supply pdos should be in increasing order of their minimum voltage",
 	[PDO_ERR_DUPE_PDO] =
 	" err: Variable/Batt supply pdos cannot have same min/max voltage",
+	[PDO_ERR_PPS_APDO_NOT_SORTED] =
+	" err: Programmable power supply apdos should be in increasing order of their maximum voltage",
+	[PDO_ERR_DUPE_PPS_APDO] =
+	" err: Programmable power supply apdos cannot have same min/max voltage and max current",
 };
 
 static enum pdo_err tcpm_caps_err(struct tcpm_port *port, const u32 *pdo,
@@ -1322,6 +1362,26 @@ static enum pdo_err tcpm_caps_err(struct tcpm_port *port, const u32 *pdo,
 					  pdo_min_voltage(pdo[i - 1])))
 					return PDO_ERR_DUPE_PDO;
 				break;
+			/*
+			 * The Programmable Power Supply APDOs, if present,
+			 * shall be sent in Maximum Voltage order;
+			 * lowest to highest.
+			 */
+			case PDO_TYPE_APDO:
+				if (pdo_apdo_type(pdo[i]) != APDO_TYPE_PPS)
+					break;
+
+				if (pdo_pps_apdo_max_current(pdo[i]) <
+				    pdo_pps_apdo_max_current(pdo[i - 1]))
+					return PDO_ERR_PPS_APDO_NOT_SORTED;
+				else if ((pdo_pps_apdo_min_voltage(pdo[i]) ==
+					  pdo_pps_apdo_min_voltage(pdo[i - 1])) &&
+					 (pdo_pps_apdo_max_voltage(pdo[i]) ==
+					  pdo_pps_apdo_max_voltage(pdo[i - 1])) &&
+					 (pdo_pps_apdo_max_current(pdo[i]) ==
+					  pdo_pps_apdo_max_current(pdo[i - 1])))
+					return PDO_ERR_DUPE_PPS_APDO;
+				break;
 			default:
 				tcpm_log_force(port, " Unknown pdo type");
 			}
@@ -1347,11 +1407,16 @@ static int tcpm_validate_caps(struct tcpm_port *port, const u32 *pdo,
 /*
  * 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) {
@@ -1370,6 +1435,16 @@ static void tcpm_pd_data_request(struct tcpm_port *port,
 				   port->nr_source_caps);
 
 		/*
+		 * 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.
@@ -1390,6 +1465,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;
@@ -1414,6 +1502,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)
 {
@@ -1490,6 +1587,14 @@ 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:
+			/* Revert data back from any requested PPS updates */
+			port->pps_data.out_volt = port->supply_voltage;
+			port->pps_data.op_curr = port->current_limit;
+			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);
@@ -1512,6 +1617,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->supply_voltage = port->pps_data.out_volt;
+			port->current_limit = port->pps_data.op_curr;
 			tcpm_set_state(port, SNK_TRANSITION_SINK, 0);
 			break;
 		case SOFT_RESET_SEND:
@@ -1666,6 +1778,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);
@@ -1779,6 +1892,8 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo,
 	unsigned int i, j, max_mw = 0, max_mv = 0, mw = 0, mv = 0, ma = 0;
 	int ret = -EINVAL;
 
+	port->pps_data.supported = false;
+
 	/*
 	 * Select the source PDO providing the most power which has a
 	 * matchig sink cap.
@@ -1787,7 +1902,8 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo,
 		u32 pdo = port->source_caps[i];
 		enum pd_pdo_type type = pdo_type(pdo);
 
-		if (type == PDO_TYPE_FIXED) {
+		switch (type) {
+		case PDO_TYPE_FIXED:
 			for (j = 0; j < port->nr_fixed; j++) {
 				if (pdo_fixed_voltage(pdo) ==
 				    pdo_fixed_voltage(port->snk_pdo[j])) {
@@ -1809,7 +1925,8 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo,
 					break;
 				}
 			}
-		} else if (type == PDO_TYPE_BATT) {
+			break;
+		case PDO_TYPE_BATT:
 			for (j = port->nr_fixed;
 			     j < port->nr_fixed +
 				 port->nr_batt;
@@ -1830,7 +1947,8 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo,
 					}
 				}
 			}
-		} else if (type == PDO_TYPE_VAR) {
+			break;
+		case PDO_TYPE_VAR:
 			for (j = port->nr_fixed +
 				 port->nr_batt;
 			     j < port->nr_fixed +
@@ -1854,12 +1972,98 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_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;
 		}
 	}
 
 	return ret;
 }
 
+#define min_pps_apdo_current(x, y)	\
+	min(pdo_pps_apdo_max_current(x), pdo_pps_apdo_max_current(y))
+
+static unsigned int tcpm_pd_select_pps_apdo(struct tcpm_port *port,
+					    int *snk_pdo, int *src_pdo)
+{
+	unsigned int i, j, max_mw = 0, max_mv = 0, mw = 0, mv = 0, ma = 0;
+	enum pd_pdo_type type;
+	u32 pdo;
+	int ret = -EOPNOTSUPP;
+
+	/*
+	 * 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.
+	 */
+	*src_pdo = 0;
+	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;
+			}
+
+			for (j = port->nr_fixed +
+				 port->nr_batt +
+				 port->nr_var;
+			     j < port->nr_fixed +
+				 port->nr_batt +
+				 port->nr_var +
+				 port->nr_apdo;
+			     ++j) {
+				if ((pdo_pps_apdo_min_voltage(pdo) >=
+				     pdo_pps_apdo_min_voltage(port->snk_pdo[j])) &&
+				    (pdo_pps_apdo_max_voltage(pdo) <=
+				     pdo_pps_apdo_max_voltage(port->snk_pdo[j]))) {
+					ma = min_pps_apdo_current(pdo,
+								  port->snk_pdo[j]);
+					mv = pdo_pps_apdo_max_voltage(pdo);
+					mw = (ma * mv) / 1000;
+					if ((mw > max_mw) ||
+					    ((mw == max_mw) && (mv > max_mv))) {
+						ret = 0;
+						*src_pdo = i;
+						*snk_pdo = j;
+						max_mw = mw;
+						max_mv = mv;
+					}
+				}
+			}
+
+			break;
+		default:
+			tcpm_log(port, "Not APDO type, ignoring");
+			continue;
+		}
+	}
+
+	if (*src_pdo > 0) {
+		pdo = port->source_caps[*src_pdo];
+
+		port->pps_data.min_volt = pdo_pps_apdo_min_voltage(pdo);
+		port->pps_data.max_volt = pdo_pps_apdo_max_voltage(pdo);
+		port->pps_data.max_curr =
+			min_pps_apdo_current(pdo, port->snk_pdo[*snk_pdo]);
+		port->pps_data.out_volt =
+			min(port->pps_data.out_volt, pdo_pps_apdo_max_voltage(pdo));
+		port->pps_data.op_curr =
+			min(port->pps_data.op_curr, pdo_pps_apdo_max_current(pdo));
+	}
+
+	return ret;
+}
+
 static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo)
 {
 	unsigned int mv, ma, mw, flags;
@@ -1875,10 +2079,18 @@ static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo)
 	matching_snk_pdo = port->snk_pdo[snk_pdo_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 sink pdo's limit */
 	if (type == PDO_TYPE_BATT) {
@@ -1943,6 +2155,107 @@ 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 out_mv, op_ma, op_mw, min_mv, max_mv, max_ma, flags;
+	enum pd_pdo_type type;
+	int src_pdo_index, snk_pdo_index;
+	u32 pdo, matching_snk_pdo;
+	int ret;
+
+	ret = tcpm_pd_select_pps_apdo(port, &snk_pdo_index, &src_pdo_index);
+	if (ret)
+		return ret;
+
+	pdo = port->source_caps[src_pdo_index];
+	matching_snk_pdo = port->snk_pdo[snk_pdo_index];
+	type = pdo_type(pdo);
+
+	switch (type) {
+	case PDO_TYPE_APDO:
+		if (pdo_apdo_type(pdo) != APDO_TYPE_PPS) {
+			tcpm_log(port, "Invalid APDO selected!");
+			return -EINVAL;
+		}
+		min_mv = pdo_pps_apdo_min_voltage(pdo);
+		max_mv = pdo_pps_apdo_max_voltage(pdo);
+		max_ma = pdo_pps_apdo_max_current(pdo);
+		out_mv = port->pps_data.out_volt;
+		op_ma = port->pps_data.op_curr;
+		break;
+	default:
+		tcpm_log(port, "Invalid PDO selected!");
+		return -EINVAL;
+	}
+
+	flags = RDO_USB_COMM | RDO_NO_SUSPEND;
+
+	op_mw = (op_ma * out_mv) / 1000;
+	if (op_mw < port->operating_snk_mw) {
+		/*
+		 * Try raising current to meet power needs. If that's not enough
+		 * then try upping the voltage. If that's still not enoguh
+		 * then we've obviously chosen a PPS APDO which really isn't
+		 * suitable so abandon ship.
+		 */
+		op_ma = ((port->operating_snk_mw * 1000) / out_mv);
+		if ((port->operating_snk_mw * 1000) % out_mv)
+			++op_ma;
+		op_ma += RDO_PROG_CURR_MA_STEP - (op_ma % RDO_PROG_CURR_MA_STEP);
+
+		if (op_ma > max_ma) {
+			op_ma = max_ma;
+			out_mv = ((port->operating_snk_mw * 1000) / op_ma);
+			if ((port->operating_snk_mw * 1000) % op_ma)
+				++out_mv;
+			out_mv += RDO_PROG_VOLT_MV_STEP -
+				  (out_mv % RDO_PROG_VOLT_MV_STEP);
+
+			if (out_mv > max_mv) {
+				tcpm_log(port, "Invalid PPS APDO selected!");
+				return -EINVAL;
+			}
+		}
+	}
+
+	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(src_pdo_index + 1, out_mv, op_ma, flags);
+
+	tcpm_log(port, "Requesting APDO %d: %u mV, %u mA",
+		 src_pdo_index, out_mv, op_ma);
+
+	port->pps_data.op_curr = op_ma;
+	port->pps_data.out_volt = 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);
 
@@ -2128,6 +2441,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
@@ -2143,6 +2457,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)
@@ -2389,6 +2705,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;
@@ -2449,6 +2766,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
@@ -2472,6 +2790,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);
@@ -2480,6 +2799,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 &&
@@ -2561,6 +2881,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;
@@ -2631,6 +2952,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),
@@ -2638,6 +2977,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);
@@ -2646,7 +2986,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 */
@@ -2693,6 +3037,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);
@@ -2913,6 +3258,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:
@@ -3378,7 +3724,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;
@@ -3423,7 +3769,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;
@@ -3463,7 +3809,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;
@@ -3495,6 +3841,162 @@ 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) {
+		ret = -EINVAL;
+		goto port_unlock;
+	}
+
+	reinit_completion(&port->pps_complete);
+	port->pps_data.op_curr = 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) {
+		ret = -EINVAL;
+		goto port_unlock;
+	}
+
+	reinit_completion(&port->pps_complete);
+	port->pps_data.out_volt = 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) {
+		port->pps_data.out_volt = port->supply_voltage;
+		port->pps_data.op_curr = port->current_limit;
+		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;
@@ -3634,13 +4136,18 @@ int 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;
@@ -3695,6 +4202,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);
 	tcpm_debugfs_init(port);
 
 	if (tcpm_validate_caps(port, tcpc->config->src_pdo,
@@ -3717,6 +4225,9 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
 	port->nr_batt = nr_type_pdos(port->snk_pdo,
 				     port->nr_snk_pdo,
 				     PDO_TYPE_BATT);
+	port->nr_apdo = nr_type_pdos(port->snk_pdo,
+				     port->nr_snk_pdo,
+				     PDO_TYPE_APDO);
 	port->nr_snk_vdo = tcpm_copy_vdos(port->snk_vdo, tcpc->config->snk_vdo,
 					  tcpc->config->nr_snk_vdo);
 
@@ -3732,7 +4243,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/pd.h b/include/linux/usb/pd.h
index ff359bdf..09b570f 100644
--- a/include/linux/usb/pd.h
+++ b/include/linux/usb/pd.h
@@ -103,8 +103,8 @@ enum pd_ext_msg_type {
 	 (((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), PD_REV20, (id), (cnt), (0)))
+#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)
 {
diff --git a/include/linux/usb/tcpm.h b/include/linux/usb/tcpm.h
index ca1c0b5..d6673f7 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,

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

* [PATCH v4 4/7] typec: tcpm: Add core support for sink side PPS
@ 2018-01-02 15:50   ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 67+ messages in thread
From: Adam Thomson @ 2018-01-02 15:50 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 | 533 ++++++++++++++++++++++++++++++++++++++++++++++-
 include/linux/usb/pd.h   |   4 +-
 include/linux/usb/tcpm.h |   2 +-
 3 files changed, 525 insertions(+), 14 deletions(-)

diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c
index f4d563e..b66d26c 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 {
+	u32 min_volt;
+	u32 max_volt;
+	u32 max_curr;
+	u32 out_volt;
+	u32 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;
@@ -255,6 +267,7 @@ struct tcpm_port {
 	unsigned int nr_fixed; /* number of fixed sink PDOs */
 	unsigned int nr_var; /* number of variable sink PDOs */
 	unsigned int nr_batt; /* number of battery sink PDOs */
+	unsigned int nr_apdo; /* number of APDO type PDOs */
 	u32 snk_vdo[VDO_MAX_OBJECTS];
 	unsigned int nr_snk_vdo;
 
@@ -262,6 +275,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;
@@ -278,8 +292,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];
@@ -497,6 +516,16 @@ static void tcpm_log_source_caps(struct tcpm_port *port)
 				  pdo_max_voltage(pdo),
 				  pdo_max_power(pdo));
 			break;
+		case PDO_TYPE_APDO:
+			if (pdo_apdo_type(pdo) == APDO_TYPE_PPS)
+				scnprintf(msg, sizeof(msg),
+					  "%u-%u mV, %u mA",
+					  pdo_pps_apdo_min_voltage(pdo),
+					  pdo_pps_apdo_max_voltage(pdo),
+					  pdo_pps_apdo_max_current(pdo));
+			else
+				strcpy(msg, "undefined APDO");
+			break;
 		default:
 			strcpy(msg, "undefined");
 			break;
@@ -791,11 +820,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);
 	}
@@ -816,11 +847,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);
 	}
@@ -1187,6 +1220,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]);
@@ -1258,6 +1292,8 @@ enum pdo_err {
 	PDO_ERR_FIXED_NOT_SORTED,
 	PDO_ERR_VARIABLE_BATT_NOT_SORTED,
 	PDO_ERR_DUPE_PDO,
+	PDO_ERR_PPS_APDO_NOT_SORTED,
+	PDO_ERR_DUPE_PPS_APDO,
 };
 
 static const char * const pdo_err_msg[] = {
@@ -1273,6 +1309,10 @@ enum pdo_err {
 	" err: Variable/Battery supply pdos should be in increasing order of their minimum voltage",
 	[PDO_ERR_DUPE_PDO] =
 	" err: Variable/Batt supply pdos cannot have same min/max voltage",
+	[PDO_ERR_PPS_APDO_NOT_SORTED] =
+	" err: Programmable power supply apdos should be in increasing order of their maximum voltage",
+	[PDO_ERR_DUPE_PPS_APDO] =
+	" err: Programmable power supply apdos cannot have same min/max voltage and max current",
 };
 
 static enum pdo_err tcpm_caps_err(struct tcpm_port *port, const u32 *pdo,
@@ -1322,6 +1362,26 @@ static enum pdo_err tcpm_caps_err(struct tcpm_port *port, const u32 *pdo,
 					  pdo_min_voltage(pdo[i - 1])))
 					return PDO_ERR_DUPE_PDO;
 				break;
+			/*
+			 * The Programmable Power Supply APDOs, if present,
+			 * shall be sent in Maximum Voltage order;
+			 * lowest to highest.
+			 */
+			case PDO_TYPE_APDO:
+				if (pdo_apdo_type(pdo[i]) != APDO_TYPE_PPS)
+					break;
+
+				if (pdo_pps_apdo_max_current(pdo[i]) <
+				    pdo_pps_apdo_max_current(pdo[i - 1]))
+					return PDO_ERR_PPS_APDO_NOT_SORTED;
+				else if ((pdo_pps_apdo_min_voltage(pdo[i]) ==
+					  pdo_pps_apdo_min_voltage(pdo[i - 1])) &&
+					 (pdo_pps_apdo_max_voltage(pdo[i]) ==
+					  pdo_pps_apdo_max_voltage(pdo[i - 1])) &&
+					 (pdo_pps_apdo_max_current(pdo[i]) ==
+					  pdo_pps_apdo_max_current(pdo[i - 1])))
+					return PDO_ERR_DUPE_PPS_APDO;
+				break;
 			default:
 				tcpm_log_force(port, " Unknown pdo type");
 			}
@@ -1347,11 +1407,16 @@ static int tcpm_validate_caps(struct tcpm_port *port, const u32 *pdo,
 /*
  * 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) {
@@ -1370,6 +1435,16 @@ static void tcpm_pd_data_request(struct tcpm_port *port,
 				   port->nr_source_caps);
 
 		/*
+		 * 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.
@@ -1390,6 +1465,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;
@@ -1414,6 +1502,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)
 {
@@ -1490,6 +1587,14 @@ 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:
+			/* Revert data back from any requested PPS updates */
+			port->pps_data.out_volt = port->supply_voltage;
+			port->pps_data.op_curr = port->current_limit;
+			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);
@@ -1512,6 +1617,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->supply_voltage = port->pps_data.out_volt;
+			port->current_limit = port->pps_data.op_curr;
 			tcpm_set_state(port, SNK_TRANSITION_SINK, 0);
 			break;
 		case SOFT_RESET_SEND:
@@ -1666,6 +1778,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);
@@ -1779,6 +1892,8 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo,
 	unsigned int i, j, max_mw = 0, max_mv = 0, mw = 0, mv = 0, ma = 0;
 	int ret = -EINVAL;
 
+	port->pps_data.supported = false;
+
 	/*
 	 * Select the source PDO providing the most power which has a
 	 * matchig sink cap.
@@ -1787,7 +1902,8 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo,
 		u32 pdo = port->source_caps[i];
 		enum pd_pdo_type type = pdo_type(pdo);
 
-		if (type == PDO_TYPE_FIXED) {
+		switch (type) {
+		case PDO_TYPE_FIXED:
 			for (j = 0; j < port->nr_fixed; j++) {
 				if (pdo_fixed_voltage(pdo) ==
 				    pdo_fixed_voltage(port->snk_pdo[j])) {
@@ -1809,7 +1925,8 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo,
 					break;
 				}
 			}
-		} else if (type == PDO_TYPE_BATT) {
+			break;
+		case PDO_TYPE_BATT:
 			for (j = port->nr_fixed;
 			     j < port->nr_fixed +
 				 port->nr_batt;
@@ -1830,7 +1947,8 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo,
 					}
 				}
 			}
-		} else if (type == PDO_TYPE_VAR) {
+			break;
+		case PDO_TYPE_VAR:
 			for (j = port->nr_fixed +
 				 port->nr_batt;
 			     j < port->nr_fixed +
@@ -1854,12 +1972,98 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_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;
 		}
 	}
 
 	return ret;
 }
 
+#define min_pps_apdo_current(x, y)	\
+	min(pdo_pps_apdo_max_current(x), pdo_pps_apdo_max_current(y))
+
+static unsigned int tcpm_pd_select_pps_apdo(struct tcpm_port *port,
+					    int *snk_pdo, int *src_pdo)
+{
+	unsigned int i, j, max_mw = 0, max_mv = 0, mw = 0, mv = 0, ma = 0;
+	enum pd_pdo_type type;
+	u32 pdo;
+	int ret = -EOPNOTSUPP;
+
+	/*
+	 * 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.
+	 */
+	*src_pdo = 0;
+	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;
+			}
+
+			for (j = port->nr_fixed +
+				 port->nr_batt +
+				 port->nr_var;
+			     j < port->nr_fixed +
+				 port->nr_batt +
+				 port->nr_var +
+				 port->nr_apdo;
+			     ++j) {
+				if ((pdo_pps_apdo_min_voltage(pdo) >=
+				     pdo_pps_apdo_min_voltage(port->snk_pdo[j])) &&
+				    (pdo_pps_apdo_max_voltage(pdo) <=
+				     pdo_pps_apdo_max_voltage(port->snk_pdo[j]))) {
+					ma = min_pps_apdo_current(pdo,
+								  port->snk_pdo[j]);
+					mv = pdo_pps_apdo_max_voltage(pdo);
+					mw = (ma * mv) / 1000;
+					if ((mw > max_mw) ||
+					    ((mw == max_mw) && (mv > max_mv))) {
+						ret = 0;
+						*src_pdo = i;
+						*snk_pdo = j;
+						max_mw = mw;
+						max_mv = mv;
+					}
+				}
+			}
+
+			break;
+		default:
+			tcpm_log(port, "Not APDO type, ignoring");
+			continue;
+		}
+	}
+
+	if (*src_pdo > 0) {
+		pdo = port->source_caps[*src_pdo];
+
+		port->pps_data.min_volt = pdo_pps_apdo_min_voltage(pdo);
+		port->pps_data.max_volt = pdo_pps_apdo_max_voltage(pdo);
+		port->pps_data.max_curr =
+			min_pps_apdo_current(pdo, port->snk_pdo[*snk_pdo]);
+		port->pps_data.out_volt =
+			min(port->pps_data.out_volt, pdo_pps_apdo_max_voltage(pdo));
+		port->pps_data.op_curr =
+			min(port->pps_data.op_curr, pdo_pps_apdo_max_current(pdo));
+	}
+
+	return ret;
+}
+
 static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo)
 {
 	unsigned int mv, ma, mw, flags;
@@ -1875,10 +2079,18 @@ static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo)
 	matching_snk_pdo = port->snk_pdo[snk_pdo_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 sink pdo's limit */
 	if (type == PDO_TYPE_BATT) {
@@ -1943,6 +2155,107 @@ 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 out_mv, op_ma, op_mw, min_mv, max_mv, max_ma, flags;
+	enum pd_pdo_type type;
+	int src_pdo_index, snk_pdo_index;
+	u32 pdo, matching_snk_pdo;
+	int ret;
+
+	ret = tcpm_pd_select_pps_apdo(port, &snk_pdo_index, &src_pdo_index);
+	if (ret)
+		return ret;
+
+	pdo = port->source_caps[src_pdo_index];
+	matching_snk_pdo = port->snk_pdo[snk_pdo_index];
+	type = pdo_type(pdo);
+
+	switch (type) {
+	case PDO_TYPE_APDO:
+		if (pdo_apdo_type(pdo) != APDO_TYPE_PPS) {
+			tcpm_log(port, "Invalid APDO selected!");
+			return -EINVAL;
+		}
+		min_mv = pdo_pps_apdo_min_voltage(pdo);
+		max_mv = pdo_pps_apdo_max_voltage(pdo);
+		max_ma = pdo_pps_apdo_max_current(pdo);
+		out_mv = port->pps_data.out_volt;
+		op_ma = port->pps_data.op_curr;
+		break;
+	default:
+		tcpm_log(port, "Invalid PDO selected!");
+		return -EINVAL;
+	}
+
+	flags = RDO_USB_COMM | RDO_NO_SUSPEND;
+
+	op_mw = (op_ma * out_mv) / 1000;
+	if (op_mw < port->operating_snk_mw) {
+		/*
+		 * Try raising current to meet power needs. If that's not enough
+		 * then try upping the voltage. If that's still not enoguh
+		 * then we've obviously chosen a PPS APDO which really isn't
+		 * suitable so abandon ship.
+		 */
+		op_ma = ((port->operating_snk_mw * 1000) / out_mv);
+		if ((port->operating_snk_mw * 1000) % out_mv)
+			++op_ma;
+		op_ma += RDO_PROG_CURR_MA_STEP - (op_ma % RDO_PROG_CURR_MA_STEP);
+
+		if (op_ma > max_ma) {
+			op_ma = max_ma;
+			out_mv = ((port->operating_snk_mw * 1000) / op_ma);
+			if ((port->operating_snk_mw * 1000) % op_ma)
+				++out_mv;
+			out_mv += RDO_PROG_VOLT_MV_STEP -
+				  (out_mv % RDO_PROG_VOLT_MV_STEP);
+
+			if (out_mv > max_mv) {
+				tcpm_log(port, "Invalid PPS APDO selected!");
+				return -EINVAL;
+			}
+		}
+	}
+
+	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(src_pdo_index + 1, out_mv, op_ma, flags);
+
+	tcpm_log(port, "Requesting APDO %d: %u mV, %u mA",
+		 src_pdo_index, out_mv, op_ma);
+
+	port->pps_data.op_curr = op_ma;
+	port->pps_data.out_volt = 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);
 
@@ -2128,6 +2441,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
@@ -2143,6 +2457,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)
@@ -2389,6 +2705,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;
@@ -2449,6 +2766,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
@@ -2472,6 +2790,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);
@@ -2480,6 +2799,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 &&
@@ -2561,6 +2881,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;
@@ -2631,6 +2952,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),
@@ -2638,6 +2977,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);
@@ -2646,7 +2986,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 */
@@ -2693,6 +3037,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);
@@ -2913,6 +3258,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:
@@ -3378,7 +3724,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;
@@ -3423,7 +3769,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;
@@ -3463,7 +3809,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;
@@ -3495,6 +3841,162 @@ 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) {
+		ret = -EINVAL;
+		goto port_unlock;
+	}
+
+	reinit_completion(&port->pps_complete);
+	port->pps_data.op_curr = 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) {
+		ret = -EINVAL;
+		goto port_unlock;
+	}
+
+	reinit_completion(&port->pps_complete);
+	port->pps_data.out_volt = 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) {
+		port->pps_data.out_volt = port->supply_voltage;
+		port->pps_data.op_curr = port->current_limit;
+		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;
@@ -3634,13 +4136,18 @@ int 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;
@@ -3695,6 +4202,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);
 	tcpm_debugfs_init(port);
 
 	if (tcpm_validate_caps(port, tcpc->config->src_pdo,
@@ -3717,6 +4225,9 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
 	port->nr_batt = nr_type_pdos(port->snk_pdo,
 				     port->nr_snk_pdo,
 				     PDO_TYPE_BATT);
+	port->nr_apdo = nr_type_pdos(port->snk_pdo,
+				     port->nr_snk_pdo,
+				     PDO_TYPE_APDO);
 	port->nr_snk_vdo = tcpm_copy_vdos(port->snk_vdo, tcpc->config->snk_vdo,
 					  tcpc->config->nr_snk_vdo);
 
@@ -3732,7 +4243,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/pd.h b/include/linux/usb/pd.h
index ff359bdf..09b570f 100644
--- a/include/linux/usb/pd.h
+++ b/include/linux/usb/pd.h
@@ -103,8 +103,8 @@ enum pd_ext_msg_type {
 	 (((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), PD_REV20, (id), (cnt), (0)))
+#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)
 {
diff --git a/include/linux/usb/tcpm.h b/include/linux/usb/tcpm.h
index ca1c0b5..d6673f7 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] 67+ messages in thread

* [PATCH v4 5/7] power: supply: Add 'connected_type' property and supporting code
@ 2018-01-02 15:50   ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 67+ messages in thread
From: Adam Thomson @ 2018-01-02 15:50 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 the 'connected_type' property to represent supplies
which can report a number of different types of supply based on a
connection event.

Examples of this already exist in drivers whereby the existing 'type'
property is updated, based on an event, to represent what was
connected (e.g. USB_DCP, USB_ACA, ...). Current implementations
however don't show all supported connectable types, so this knowledge
has to be exlicitly known for each driver that supports this.

The 'connected_type' property is intended to fill this void and show
users all possible types supported by a driver. The property, when
read, shows all available types for the driver, and the one currently
chosen is highlighted/bracketed. It is expected that the 'type'
property would then just show the top-level type, such as 'USB', and
this would be static.

Currently the 'conn_type' enum contains all of the USB variant types
that exist for the 'type' enum at this time, and in addition has
the PPS type. In the future this can be extended further for other
types which have multiple connected types supported. The mirroring
is intentional so as to not impact existing usage of the 'type'
property.

Signed-off-by: Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
---
 drivers/power/supply/power_supply_sysfs.c | 50 +++++++++++++++++++++++++++++++
 include/linux/power_supply.h              | 15 ++++++++++
 2 files changed, 65 insertions(+)

diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c
index 5204f11..1b3b202 100644
--- a/drivers/power/supply/power_supply_sysfs.c
+++ b/drivers/power/supply/power_supply_sysfs.c
@@ -46,6 +46,11 @@
 	"USB_PD", "USB_PD_DRP", "BrickID"
 };
 
+static const char * const power_supply_conn_type_text[] = {
+	"Unknown", "USB_DCP", "USB_CDP", "USB_ACA", "USB_C",
+	"USB_PD", "USB_PD_DRP", "USB_PD_PPS", "BrickID"
+};
+
 static const char * const power_supply_status_text[] = {
 	"Unknown", "Charging", "Discharging", "Not charging", "Full"
 };
@@ -73,6 +78,46 @@
 	"Unknown", "System", "Device"
 };
 
+static ssize_t power_supply_show_conn_type(struct device *dev,
+					   enum power_supply_conn_type *conn_types,
+					   ssize_t num_conn_types,
+					   union power_supply_propval *value,
+					   char *buf)
+{
+	enum power_supply_conn_type conn_type;
+	ssize_t count = 0;
+	bool match = false;
+	int i;
+
+	if ((!conn_types) || (num_conn_types <= 0)) {
+		dev_warn(dev, "driver has no valid connected types\n");
+		return -ENODATA;
+	}
+
+	for (i = 0; i < num_conn_types; ++i) {
+		conn_type = conn_types[i];
+
+		if (value->intval == conn_type) {
+			count += sprintf(buf + count, "[%s] ",
+					 power_supply_conn_type_text[conn_type]);
+			match = true;
+		} else {
+			count += sprintf(buf + count, "%s ",
+					 power_supply_conn_type_text[conn_type]);
+		}
+	}
+
+	if (!match) {
+		dev_warn(dev, "driver reporting unsupported connected type\n");
+		return -EINVAL;
+	}
+
+	if (count)
+		buf[count - 1] = '\n';
+
+	return count;
+}
+
 static ssize_t power_supply_show_property(struct device *dev,
 					  struct device_attribute *attr,
 					  char *buf) {
@@ -115,6 +160,10 @@ static ssize_t power_supply_show_property(struct device *dev,
 	else if (off == POWER_SUPPLY_PROP_TYPE)
 		return sprintf(buf, "%s\n",
 			       power_supply_type_text[value.intval]);
+	else if (off == POWER_SUPPLY_PROP_CONNECTED_TYPE)
+		return power_supply_show_conn_type(dev, psy->desc->conn_types,
+						   psy->desc->num_conn_types,
+						   &value, buf);
 	else if (off == POWER_SUPPLY_PROP_SCOPE)
 		return sprintf(buf, "%s\n",
 			       power_supply_scope_text[value.intval]);
@@ -241,6 +290,7 @@ static ssize_t power_supply_store_property(struct device *dev,
 	POWER_SUPPLY_ATTR(time_to_full_now),
 	POWER_SUPPLY_ATTR(time_to_full_avg),
 	POWER_SUPPLY_ATTR(type),
+	POWER_SUPPLY_ATTR(connected_type),
 	POWER_SUPPLY_ATTR(scope),
 	POWER_SUPPLY_ATTR(precharge_current),
 	POWER_SUPPLY_ATTR(charge_term_current),
diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h
index 79e90b3..e15a629 100644
--- a/include/linux/power_supply.h
+++ b/include/linux/power_supply.h
@@ -145,6 +145,7 @@ enum power_supply_property {
 	POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
 	POWER_SUPPLY_PROP_TIME_TO_FULL_AVG,
 	POWER_SUPPLY_PROP_TYPE, /* use power_supply.type instead */
+	POWER_SUPPLY_PROP_CONNECTED_TYPE,
 	POWER_SUPPLY_PROP_SCOPE,
 	POWER_SUPPLY_PROP_PRECHARGE_CURRENT,
 	POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
@@ -170,6 +171,18 @@ enum power_supply_type {
 	POWER_SUPPLY_TYPE_APPLE_BRICK_ID,	/* Apple Charging Method */
 };
 
+enum power_supply_conn_type {
+	POWER_SUPPLY_CONN_TYPE_UNKNOWN = 0,
+	POWER_SUPPLY_CONN_TYPE_USB_DCP,		/* Dedicated Charging Port */
+	POWER_SUPPLY_CONN_TYPE_USB_CDP,		/* Charging Downstream Port */
+	POWER_SUPPLY_CONN_TYPE_USB_ACA,		/* Accessory Charger Adapters */
+	POWER_SUPPLY_CONN_TYPE_USB_TYPE_C,	/* Type C Port */
+	POWER_SUPPLY_CONN_TYPE_USB_PD,		/* Power Delivery Port */
+	POWER_SUPPLY_CONN_TYPE_USB_PD_DRP,	/* PD Dual Role Port */
+	POWER_SUPPLY_CONN_TYPE_USB_PD_PPS,	/* PD Programmable Power Supply */
+	POWER_SUPPLY_CONN_TYPE_APPLE_BRICK_ID,	/* Apple Charging Method */
+};
+
 enum power_supply_notifier_events {
 	PSY_EVENT_PROP_CHANGED,
 };
@@ -196,6 +209,8 @@ struct power_supply_config {
 struct power_supply_desc {
 	const char *name;
 	enum power_supply_type type;
+	enum power_supply_conn_type *conn_types;
+	size_t num_conn_types;
 	enum power_supply_property *properties;
 	size_t num_properties;
 
-- 
1.9.1

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

* [v4,5/7] power: supply: Add 'connected_type' property and supporting code
@ 2018-01-02 15:50   ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 67+ messages in thread
From: Opensource [Adam Thomson] @ 2018-01-02 15:50 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 the 'connected_type' property to represent supplies
which can report a number of different types of supply based on a
connection event.

Examples of this already exist in drivers whereby the existing 'type'
property is updated, based on an event, to represent what was
connected (e.g. USB_DCP, USB_ACA, ...). Current implementations
however don't show all supported connectable types, so this knowledge
has to be exlicitly known for each driver that supports this.

The 'connected_type' property is intended to fill this void and show
users all possible types supported by a driver. The property, when
read, shows all available types for the driver, and the one currently
chosen is highlighted/bracketed. It is expected that the 'type'
property would then just show the top-level type, such as 'USB', and
this would be static.

Currently the 'conn_type' enum contains all of the USB variant types
that exist for the 'type' enum at this time, and in addition has
the PPS type. In the future this can be extended further for other
types which have multiple connected types supported. The mirroring
is intentional so as to not impact existing usage of the 'type'
property.

Signed-off-by: Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
---
 drivers/power/supply/power_supply_sysfs.c | 50 +++++++++++++++++++++++++++++++
 include/linux/power_supply.h              | 15 ++++++++++
 2 files changed, 65 insertions(+)

diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c
index 5204f11..1b3b202 100644
--- a/drivers/power/supply/power_supply_sysfs.c
+++ b/drivers/power/supply/power_supply_sysfs.c
@@ -46,6 +46,11 @@
 	"USB_PD", "USB_PD_DRP", "BrickID"
 };
 
+static const char * const power_supply_conn_type_text[] = {
+	"Unknown", "USB_DCP", "USB_CDP", "USB_ACA", "USB_C",
+	"USB_PD", "USB_PD_DRP", "USB_PD_PPS", "BrickID"
+};
+
 static const char * const power_supply_status_text[] = {
 	"Unknown", "Charging", "Discharging", "Not charging", "Full"
 };
@@ -73,6 +78,46 @@
 	"Unknown", "System", "Device"
 };
 
+static ssize_t power_supply_show_conn_type(struct device *dev,
+					   enum power_supply_conn_type *conn_types,
+					   ssize_t num_conn_types,
+					   union power_supply_propval *value,
+					   char *buf)
+{
+	enum power_supply_conn_type conn_type;
+	ssize_t count = 0;
+	bool match = false;
+	int i;
+
+	if ((!conn_types) || (num_conn_types <= 0)) {
+		dev_warn(dev, "driver has no valid connected types\n");
+		return -ENODATA;
+	}
+
+	for (i = 0; i < num_conn_types; ++i) {
+		conn_type = conn_types[i];
+
+		if (value->intval == conn_type) {
+			count += sprintf(buf + count, "[%s] ",
+					 power_supply_conn_type_text[conn_type]);
+			match = true;
+		} else {
+			count += sprintf(buf + count, "%s ",
+					 power_supply_conn_type_text[conn_type]);
+		}
+	}
+
+	if (!match) {
+		dev_warn(dev, "driver reporting unsupported connected type\n");
+		return -EINVAL;
+	}
+
+	if (count)
+		buf[count - 1] = '\n';
+
+	return count;
+}
+
 static ssize_t power_supply_show_property(struct device *dev,
 					  struct device_attribute *attr,
 					  char *buf) {
@@ -115,6 +160,10 @@ static ssize_t power_supply_show_property(struct device *dev,
 	else if (off == POWER_SUPPLY_PROP_TYPE)
 		return sprintf(buf, "%s\n",
 			       power_supply_type_text[value.intval]);
+	else if (off == POWER_SUPPLY_PROP_CONNECTED_TYPE)
+		return power_supply_show_conn_type(dev, psy->desc->conn_types,
+						   psy->desc->num_conn_types,
+						   &value, buf);
 	else if (off == POWER_SUPPLY_PROP_SCOPE)
 		return sprintf(buf, "%s\n",
 			       power_supply_scope_text[value.intval]);
@@ -241,6 +290,7 @@ static ssize_t power_supply_store_property(struct device *dev,
 	POWER_SUPPLY_ATTR(time_to_full_now),
 	POWER_SUPPLY_ATTR(time_to_full_avg),
 	POWER_SUPPLY_ATTR(type),
+	POWER_SUPPLY_ATTR(connected_type),
 	POWER_SUPPLY_ATTR(scope),
 	POWER_SUPPLY_ATTR(precharge_current),
 	POWER_SUPPLY_ATTR(charge_term_current),
diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h
index 79e90b3..e15a629 100644
--- a/include/linux/power_supply.h
+++ b/include/linux/power_supply.h
@@ -145,6 +145,7 @@ enum power_supply_property {
 	POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
 	POWER_SUPPLY_PROP_TIME_TO_FULL_AVG,
 	POWER_SUPPLY_PROP_TYPE, /* use power_supply.type instead */
+	POWER_SUPPLY_PROP_CONNECTED_TYPE,
 	POWER_SUPPLY_PROP_SCOPE,
 	POWER_SUPPLY_PROP_PRECHARGE_CURRENT,
 	POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
@@ -170,6 +171,18 @@ enum power_supply_type {
 	POWER_SUPPLY_TYPE_APPLE_BRICK_ID,	/* Apple Charging Method */
 };
 
+enum power_supply_conn_type {
+	POWER_SUPPLY_CONN_TYPE_UNKNOWN = 0,
+	POWER_SUPPLY_CONN_TYPE_USB_DCP,		/* Dedicated Charging Port */
+	POWER_SUPPLY_CONN_TYPE_USB_CDP,		/* Charging Downstream Port */
+	POWER_SUPPLY_CONN_TYPE_USB_ACA,		/* Accessory Charger Adapters */
+	POWER_SUPPLY_CONN_TYPE_USB_TYPE_C,	/* Type C Port */
+	POWER_SUPPLY_CONN_TYPE_USB_PD,		/* Power Delivery Port */
+	POWER_SUPPLY_CONN_TYPE_USB_PD_DRP,	/* PD Dual Role Port */
+	POWER_SUPPLY_CONN_TYPE_USB_PD_PPS,	/* PD Programmable Power Supply */
+	POWER_SUPPLY_CONN_TYPE_APPLE_BRICK_ID,	/* Apple Charging Method */
+};
+
 enum power_supply_notifier_events {
 	PSY_EVENT_PROP_CHANGED,
 };
@@ -196,6 +209,8 @@ struct power_supply_config {
 struct power_supply_desc {
 	const char *name;
 	enum power_supply_type type;
+	enum power_supply_conn_type *conn_types;
+	size_t num_conn_types;
 	enum power_supply_property *properties;
 	size_t num_properties;
 

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

* [PATCH v4 5/7] power: supply: Add 'connected_type' property and supporting code
@ 2018-01-02 15:50   ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 67+ messages in thread
From: Adam Thomson @ 2018-01-02 15:50 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 the 'connected_type' property to represent supplies
which can report a number of different types of supply based on a
connection event.

Examples of this already exist in drivers whereby the existing 'type'
property is updated, based on an event, to represent what was
connected (e.g. USB_DCP, USB_ACA, ...). Current implementations
however don't show all supported connectable types, so this knowledge
has to be exlicitly known for each driver that supports this.

The 'connected_type' property is intended to fill this void and show
users all possible types supported by a driver. The property, when
read, shows all available types for the driver, and the one currently
chosen is highlighted/bracketed. It is expected that the 'type'
property would then just show the top-level type, such as 'USB', and
this would be static.

Currently the 'conn_type' enum contains all of the USB variant types
that exist for the 'type' enum at this time, and in addition has
the PPS type. In the future this can be extended further for other
types which have multiple connected types supported. The mirroring
is intentional so as to not impact existing usage of the 'type'
property.

Signed-off-by: Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
---
 drivers/power/supply/power_supply_sysfs.c | 50 +++++++++++++++++++++++++++++++
 include/linux/power_supply.h              | 15 ++++++++++
 2 files changed, 65 insertions(+)

diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c
index 5204f11..1b3b202 100644
--- a/drivers/power/supply/power_supply_sysfs.c
+++ b/drivers/power/supply/power_supply_sysfs.c
@@ -46,6 +46,11 @@
 	"USB_PD", "USB_PD_DRP", "BrickID"
 };
 
+static const char * const power_supply_conn_type_text[] = {
+	"Unknown", "USB_DCP", "USB_CDP", "USB_ACA", "USB_C",
+	"USB_PD", "USB_PD_DRP", "USB_PD_PPS", "BrickID"
+};
+
 static const char * const power_supply_status_text[] = {
 	"Unknown", "Charging", "Discharging", "Not charging", "Full"
 };
@@ -73,6 +78,46 @@
 	"Unknown", "System", "Device"
 };
 
+static ssize_t power_supply_show_conn_type(struct device *dev,
+					   enum power_supply_conn_type *conn_types,
+					   ssize_t num_conn_types,
+					   union power_supply_propval *value,
+					   char *buf)
+{
+	enum power_supply_conn_type conn_type;
+	ssize_t count = 0;
+	bool match = false;
+	int i;
+
+	if ((!conn_types) || (num_conn_types <= 0)) {
+		dev_warn(dev, "driver has no valid connected types\n");
+		return -ENODATA;
+	}
+
+	for (i = 0; i < num_conn_types; ++i) {
+		conn_type = conn_types[i];
+
+		if (value->intval == conn_type) {
+			count += sprintf(buf + count, "[%s] ",
+					 power_supply_conn_type_text[conn_type]);
+			match = true;
+		} else {
+			count += sprintf(buf + count, "%s ",
+					 power_supply_conn_type_text[conn_type]);
+		}
+	}
+
+	if (!match) {
+		dev_warn(dev, "driver reporting unsupported connected type\n");
+		return -EINVAL;
+	}
+
+	if (count)
+		buf[count - 1] = '\n';
+
+	return count;
+}
+
 static ssize_t power_supply_show_property(struct device *dev,
 					  struct device_attribute *attr,
 					  char *buf) {
@@ -115,6 +160,10 @@ static ssize_t power_supply_show_property(struct device *dev,
 	else if (off == POWER_SUPPLY_PROP_TYPE)
 		return sprintf(buf, "%s\n",
 			       power_supply_type_text[value.intval]);
+	else if (off == POWER_SUPPLY_PROP_CONNECTED_TYPE)
+		return power_supply_show_conn_type(dev, psy->desc->conn_types,
+						   psy->desc->num_conn_types,
+						   &value, buf);
 	else if (off == POWER_SUPPLY_PROP_SCOPE)
 		return sprintf(buf, "%s\n",
 			       power_supply_scope_text[value.intval]);
@@ -241,6 +290,7 @@ static ssize_t power_supply_store_property(struct device *dev,
 	POWER_SUPPLY_ATTR(time_to_full_now),
 	POWER_SUPPLY_ATTR(time_to_full_avg),
 	POWER_SUPPLY_ATTR(type),
+	POWER_SUPPLY_ATTR(connected_type),
 	POWER_SUPPLY_ATTR(scope),
 	POWER_SUPPLY_ATTR(precharge_current),
 	POWER_SUPPLY_ATTR(charge_term_current),
diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h
index 79e90b3..e15a629 100644
--- a/include/linux/power_supply.h
+++ b/include/linux/power_supply.h
@@ -145,6 +145,7 @@ enum power_supply_property {
 	POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
 	POWER_SUPPLY_PROP_TIME_TO_FULL_AVG,
 	POWER_SUPPLY_PROP_TYPE, /* use power_supply.type instead */
+	POWER_SUPPLY_PROP_CONNECTED_TYPE,
 	POWER_SUPPLY_PROP_SCOPE,
 	POWER_SUPPLY_PROP_PRECHARGE_CURRENT,
 	POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
@@ -170,6 +171,18 @@ enum power_supply_type {
 	POWER_SUPPLY_TYPE_APPLE_BRICK_ID,	/* Apple Charging Method */
 };
 
+enum power_supply_conn_type {
+	POWER_SUPPLY_CONN_TYPE_UNKNOWN = 0,
+	POWER_SUPPLY_CONN_TYPE_USB_DCP,		/* Dedicated Charging Port */
+	POWER_SUPPLY_CONN_TYPE_USB_CDP,		/* Charging Downstream Port */
+	POWER_SUPPLY_CONN_TYPE_USB_ACA,		/* Accessory Charger Adapters */
+	POWER_SUPPLY_CONN_TYPE_USB_TYPE_C,	/* Type C Port */
+	POWER_SUPPLY_CONN_TYPE_USB_PD,		/* Power Delivery Port */
+	POWER_SUPPLY_CONN_TYPE_USB_PD_DRP,	/* PD Dual Role Port */
+	POWER_SUPPLY_CONN_TYPE_USB_PD_PPS,	/* PD Programmable Power Supply */
+	POWER_SUPPLY_CONN_TYPE_APPLE_BRICK_ID,	/* Apple Charging Method */
+};
+
 enum power_supply_notifier_events {
 	PSY_EVENT_PROP_CHANGED,
 };
@@ -196,6 +209,8 @@ struct power_supply_config {
 struct power_supply_desc {
 	const char *name;
 	enum power_supply_type type;
+	enum power_supply_conn_type *conn_types;
+	size_t num_conn_types;
 	enum power_supply_property *properties;
 	size_t num_properties;
 
-- 
1.9.1

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

* [PATCH v4 6/7] typec: tcpm: Represent source supply through power_supply class
@ 2018-01-02 15:50   ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 67+ messages in thread
From: Adam Thomson @ 2018-01-02 15:50 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>
---
 .../ABI/testing/sysfs-class-power-tcpm-source-psy  |  92 ++++++++
 drivers/usb/typec/Kconfig                          |   1 +
 drivers/usb/typec/fusb302/Kconfig                  |   2 +-
 drivers/usb/typec/fusb302/fusb302.c                |  63 +-----
 drivers/usb/typec/tcpm.c                           | 233 ++++++++++++++++++++-
 5 files changed, 328 insertions(+), 63 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-class-power-tcpm-source-psy

diff --git a/Documentation/ABI/testing/sysfs-class-power-tcpm-source-psy b/Documentation/ABI/testing/sysfs-class-power-tcpm-source-psy
new file mode 100644
index 0000000..4986cba
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-class-power-tcpm-source-psy
@@ -0,0 +1,92 @@
+What: 		/sys/class/power_supply/tcpm-source-psy/type
+Date:		December 2017
+Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
+Description:
+	This read-only property describes the main type of source supply.
+	Type-C is a USB standard so this property always returns "USB".
+
+What: 		/sys/class/power_supply/tcpm-source-psy/connected_type
+Date:		December 2017
+Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
+Description:
+	This read-only property describes the type of source supply that is
+	connected, if the supply is online. The value is always Type C
+	unless a source has been attached which is identified as USB-PD capable.
+
+	Valid values:
+		- "USB_TYPE_C"	: Type C connected supply, not UBS-PD capable
+				  (default value)
+		- "USB_PD"	: USB-PD capable source supply connected
+		- "USB_PD_PPS"	: USB-PD PPS capable source supply connected
+
+What: 		/sys/class/power_supply/tcpm-source-psy/online
+Date:		December 2017
+Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
+Description:
+	This read-write property describes the online state of the source
+	supply. When the value of this property is not 0, and the supply allows
+	it, then it's possible to switch between online states (i.e. 1 -> 2,
+	2 -> 1)
+
+	Valid values:
+		- 0	: Offline, no source supply attached
+		- 1	: Fixed Online, Type-C or USB-PD capable supply
+			  attached, non-configurable current and voltage
+			  properties in this state.
+		- 2	: PPS Online, USB-PD PPS feature enabled, 'current_now'
+			  and 'voltage_now' properties can be modified in this
+			  state. Re-writing of this value again, once already
+			  set, will re-request the same configured voltage and
+			  current values. This can be used as a keep-alive for
+			  the PPS connection.
+			  [NOTE: This is value only selectable if
+			   'connected_type' reports a value of "USB_PD_PPS"]
+
+What: 		/sys/class/power_supply/tcpm-source-psy/voltage_min
+Date:		December 2017
+Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
+Description:
+	This read-only property describes the minimum voltage the source supply
+	can provide.
+
+	Value in microvolts.
+
+What: 		/sys/class/power_supply/tcpm-source-psy/voltage_max
+Date:		December 2017
+Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
+Description:
+	This read-only property describes the maximum voltage the source supply
+	can provide.
+
+	Value in microvolts.
+
+What: 		/sys/class/power_supply/tcpm-source-psy/voltage_now
+Date:		December 2017
+Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
+Description:
+	This read-write property describes the voltage the source supply is
+	providing now. This property can only be written to if the source supply
+	is in online state '2' (PPS enabled), otherwise it's read-only
+	information.
+
+	Value in microvolts.
+
+What: 		/sys/class/power_supply/tcpm-source-psy/current_max
+Date:		December 2017
+Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
+Description:
+	This read-only property describes the maximum current the source supply
+	can provide.
+
+	Value in microamps.
+
+What: 		/sys/class/power_supply/tcpm-source-psy/current_now
+Date:		December 2017
+Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
+Description:
+	This read-write property describes the current the source supply can
+	provide now. This property can only be written to if the source supply
+	is in online state '2' (PPS enabled), otherwise it's read-only
+	information.
+
+	Value in microamps.
diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig
index bcb2744..1ef606d 100644
--- a/drivers/usb/typec/Kconfig
+++ b/drivers/usb/typec/Kconfig
@@ -48,6 +48,7 @@ if TYPEC
 config TYPEC_TCPM
 	tristate "USB Type-C Port Controller Manager"
 	depends on USB
+	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 9ce4756..82fddc7 100644
--- a/drivers/usb/typec/fusb302/fusb302.c
+++ b/drivers/usb/typec/fusb302/fusb302.c
@@ -18,7 +18,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>
@@ -99,11 +98,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;
@@ -872,13 +866,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);
@@ -894,11 +886,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;
 }
 
@@ -1697,43 +1684,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;
@@ -1773,7 +1723,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;
@@ -1820,14 +1769,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 b66d26c..b86a51c 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>
@@ -281,6 +282,11 @@ 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;
+	enum power_supply_conn_type connected_type;
+
 	u32 bist_request;
 
 	/* PD state for Vendor Defined Messages */
@@ -1893,6 +1899,7 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo,
 	int ret = -EINVAL;
 
 	port->pps_data.supported = false;
+	port->connected_type = POWER_SUPPLY_CONN_TYPE_USB_PD;
 
 	/*
 	 * Select the source PDO providing the most power which has a
@@ -1974,8 +1981,11 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_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->connected_type =
+					POWER_SUPPLY_CONN_TYPE_USB_PD_PPS;
+			}
 			continue;
 		default:
 			tcpm_log(port, "Invalid PDO type, ignoring");
@@ -2459,6 +2469,9 @@ static void tcpm_reset_port(struct tcpm_port *port)
 	port->try_snk_count = 0;
 	port->supply_voltage = 0;
 	port->current_limit = 0;
+	port->connected_type = POWER_SUPPLY_CONN_TYPE_USB_TYPE_C;
+
+	power_supply_changed(port->psy);
 }
 
 static void tcpm_detach(struct tcpm_port *port)
@@ -2991,6 +3004,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 */
@@ -4170,6 +4185,218 @@ static int nr_type_pdos(const u32 *pdo, unsigned int nr_pdo,
 	return count;
 }
 
+/* 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_CONNECTED_TYPE,
+	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)
+{
+	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)
+{
+	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 = 0;
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_CONNECTED_TYPE:
+		val->intval = port->connected_type;
+		break;
+	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;
+	}
+}
+
+static enum power_supply_conn_type tcpm_psy_conn_types[] = {
+	POWER_SUPPLY_CONN_TYPE_USB_TYPE_C,
+	POWER_SUPPLY_CONN_TYPE_USB_PD,
+	POWER_SUPPLY_CONN_TYPE_USB_PD_PPS,
+};
+
+static 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,
+	port->psy_desc.conn_types = tcpm_psy_conn_types;
+	port->psy_desc.num_conn_types = ARRAY_SIZE(tcpm_psy_conn_types);
+	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->connected_type = POWER_SUPPLY_CONN_TYPE_USB_TYPE_C;
+
+	port->psy = devm_power_supply_register(port->dev, &port->psy_desc,
+					       &psy_cfg);
+
+	return PTR_ERR_OR_ZERO(port->psy);
+}
+
 struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
 {
 	struct tcpm_port *port;
@@ -4253,6 +4480,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] 67+ messages in thread

* [v4,6/7] typec: tcpm: Represent source supply through power_supply class
@ 2018-01-02 15:50   ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 67+ messages in thread
From: Opensource [Adam Thomson] @ 2018-01-02 15:50 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>
---
 .../ABI/testing/sysfs-class-power-tcpm-source-psy  |  92 ++++++++
 drivers/usb/typec/Kconfig                          |   1 +
 drivers/usb/typec/fusb302/Kconfig                  |   2 +-
 drivers/usb/typec/fusb302/fusb302.c                |  63 +-----
 drivers/usb/typec/tcpm.c                           | 233 ++++++++++++++++++++-
 5 files changed, 328 insertions(+), 63 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-class-power-tcpm-source-psy

diff --git a/Documentation/ABI/testing/sysfs-class-power-tcpm-source-psy b/Documentation/ABI/testing/sysfs-class-power-tcpm-source-psy
new file mode 100644
index 0000000..4986cba
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-class-power-tcpm-source-psy
@@ -0,0 +1,92 @@
+What: 		/sys/class/power_supply/tcpm-source-psy/type
+Date:		December 2017
+Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
+Description:
+	This read-only property describes the main type of source supply.
+	Type-C is a USB standard so this property always returns "USB".
+
+What: 		/sys/class/power_supply/tcpm-source-psy/connected_type
+Date:		December 2017
+Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
+Description:
+	This read-only property describes the type of source supply that is
+	connected, if the supply is online. The value is always Type C
+	unless a source has been attached which is identified as USB-PD capable.
+
+	Valid values:
+		- "USB_TYPE_C"	: Type C connected supply, not UBS-PD capable
+				  (default value)
+		- "USB_PD"	: USB-PD capable source supply connected
+		- "USB_PD_PPS"	: USB-PD PPS capable source supply connected
+
+What: 		/sys/class/power_supply/tcpm-source-psy/online
+Date:		December 2017
+Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
+Description:
+	This read-write property describes the online state of the source
+	supply. When the value of this property is not 0, and the supply allows
+	it, then it's possible to switch between online states (i.e. 1 -> 2,
+	2 -> 1)
+
+	Valid values:
+		- 0	: Offline, no source supply attached
+		- 1	: Fixed Online, Type-C or USB-PD capable supply
+			  attached, non-configurable current and voltage
+			  properties in this state.
+		- 2	: PPS Online, USB-PD PPS feature enabled, 'current_now'
+			  and 'voltage_now' properties can be modified in this
+			  state. Re-writing of this value again, once already
+			  set, will re-request the same configured voltage and
+			  current values. This can be used as a keep-alive for
+			  the PPS connection.
+			  [NOTE: This is value only selectable if
+			   'connected_type' reports a value of "USB_PD_PPS"]
+
+What: 		/sys/class/power_supply/tcpm-source-psy/voltage_min
+Date:		December 2017
+Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
+Description:
+	This read-only property describes the minimum voltage the source supply
+	can provide.
+
+	Value in microvolts.
+
+What: 		/sys/class/power_supply/tcpm-source-psy/voltage_max
+Date:		December 2017
+Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
+Description:
+	This read-only property describes the maximum voltage the source supply
+	can provide.
+
+	Value in microvolts.
+
+What: 		/sys/class/power_supply/tcpm-source-psy/voltage_now
+Date:		December 2017
+Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
+Description:
+	This read-write property describes the voltage the source supply is
+	providing now. This property can only be written to if the source supply
+	is in online state '2' (PPS enabled), otherwise it's read-only
+	information.
+
+	Value in microvolts.
+
+What: 		/sys/class/power_supply/tcpm-source-psy/current_max
+Date:		December 2017
+Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
+Description:
+	This read-only property describes the maximum current the source supply
+	can provide.
+
+	Value in microamps.
+
+What: 		/sys/class/power_supply/tcpm-source-psy/current_now
+Date:		December 2017
+Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
+Description:
+	This read-write property describes the current the source supply can
+	provide now. This property can only be written to if the source supply
+	is in online state '2' (PPS enabled), otherwise it's read-only
+	information.
+
+	Value in microamps.
diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig
index bcb2744..1ef606d 100644
--- a/drivers/usb/typec/Kconfig
+++ b/drivers/usb/typec/Kconfig
@@ -48,6 +48,7 @@ if TYPEC
 config TYPEC_TCPM
 	tristate "USB Type-C Port Controller Manager"
 	depends on USB
+	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 9ce4756..82fddc7 100644
--- a/drivers/usb/typec/fusb302/fusb302.c
+++ b/drivers/usb/typec/fusb302/fusb302.c
@@ -18,7 +18,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>
@@ -99,11 +98,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;
@@ -872,13 +866,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);
@@ -894,11 +886,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;
 }
 
@@ -1697,43 +1684,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;
@@ -1773,7 +1723,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;
@@ -1820,14 +1769,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 b66d26c..b86a51c 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>
@@ -281,6 +282,11 @@ 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;
+	enum power_supply_conn_type connected_type;
+
 	u32 bist_request;
 
 	/* PD state for Vendor Defined Messages */
@@ -1893,6 +1899,7 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo,
 	int ret = -EINVAL;
 
 	port->pps_data.supported = false;
+	port->connected_type = POWER_SUPPLY_CONN_TYPE_USB_PD;
 
 	/*
 	 * Select the source PDO providing the most power which has a
@@ -1974,8 +1981,11 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_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->connected_type =
+					POWER_SUPPLY_CONN_TYPE_USB_PD_PPS;
+			}
 			continue;
 		default:
 			tcpm_log(port, "Invalid PDO type, ignoring");
@@ -2459,6 +2469,9 @@ static void tcpm_reset_port(struct tcpm_port *port)
 	port->try_snk_count = 0;
 	port->supply_voltage = 0;
 	port->current_limit = 0;
+	port->connected_type = POWER_SUPPLY_CONN_TYPE_USB_TYPE_C;
+
+	power_supply_changed(port->psy);
 }
 
 static void tcpm_detach(struct tcpm_port *port)
@@ -2991,6 +3004,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 */
@@ -4170,6 +4185,218 @@ static int nr_type_pdos(const u32 *pdo, unsigned int nr_pdo,
 	return count;
 }
 
+/* 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_CONNECTED_TYPE,
+	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)
+{
+	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)
+{
+	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 = 0;
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_CONNECTED_TYPE:
+		val->intval = port->connected_type;
+		break;
+	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;
+	}
+}
+
+static enum power_supply_conn_type tcpm_psy_conn_types[] = {
+	POWER_SUPPLY_CONN_TYPE_USB_TYPE_C,
+	POWER_SUPPLY_CONN_TYPE_USB_PD,
+	POWER_SUPPLY_CONN_TYPE_USB_PD_PPS,
+};
+
+static 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,
+	port->psy_desc.conn_types = tcpm_psy_conn_types;
+	port->psy_desc.num_conn_types = ARRAY_SIZE(tcpm_psy_conn_types);
+	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->connected_type = POWER_SUPPLY_CONN_TYPE_USB_TYPE_C;
+
+	port->psy = devm_power_supply_register(port->dev, &port->psy_desc,
+					       &psy_cfg);
+
+	return PTR_ERR_OR_ZERO(port->psy);
+}
+
 struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
 {
 	struct tcpm_port *port;
@@ -4253,6 +4480,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;

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

* [PATCH v4 6/7] typec: tcpm: Represent source supply through power_supply class
@ 2018-01-02 15:50   ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 67+ messages in thread
From: Adam Thomson @ 2018-01-02 15:50 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>
---
 .../ABI/testing/sysfs-class-power-tcpm-source-psy  |  92 ++++++++
 drivers/usb/typec/Kconfig                          |   1 +
 drivers/usb/typec/fusb302/Kconfig                  |   2 +-
 drivers/usb/typec/fusb302/fusb302.c                |  63 +-----
 drivers/usb/typec/tcpm.c                           | 233 ++++++++++++++++++++-
 5 files changed, 328 insertions(+), 63 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-class-power-tcpm-source-psy

diff --git a/Documentation/ABI/testing/sysfs-class-power-tcpm-source-psy b/Documentation/ABI/testing/sysfs-class-power-tcpm-source-psy
new file mode 100644
index 0000000..4986cba
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-class-power-tcpm-source-psy
@@ -0,0 +1,92 @@
+What: 		/sys/class/power_supply/tcpm-source-psy/type
+Date:		December 2017
+Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
+Description:
+	This read-only property describes the main type of source supply.
+	Type-C is a USB standard so this property always returns "USB".
+
+What: 		/sys/class/power_supply/tcpm-source-psy/connected_type
+Date:		December 2017
+Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
+Description:
+	This read-only property describes the type of source supply that is
+	connected, if the supply is online. The value is always Type C
+	unless a source has been attached which is identified as USB-PD capable.
+
+	Valid values:
+		- "USB_TYPE_C"	: Type C connected supply, not UBS-PD capable
+				  (default value)
+		- "USB_PD"	: USB-PD capable source supply connected
+		- "USB_PD_PPS"	: USB-PD PPS capable source supply connected
+
+What: 		/sys/class/power_supply/tcpm-source-psy/online
+Date:		December 2017
+Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
+Description:
+	This read-write property describes the online state of the source
+	supply. When the value of this property is not 0, and the supply allows
+	it, then it's possible to switch between online states (i.e. 1 -> 2,
+	2 -> 1)
+
+	Valid values:
+		- 0	: Offline, no source supply attached
+		- 1	: Fixed Online, Type-C or USB-PD capable supply
+			  attached, non-configurable current and voltage
+			  properties in this state.
+		- 2	: PPS Online, USB-PD PPS feature enabled, 'current_now'
+			  and 'voltage_now' properties can be modified in this
+			  state. Re-writing of this value again, once already
+			  set, will re-request the same configured voltage and
+			  current values. This can be used as a keep-alive for
+			  the PPS connection.
+			  [NOTE: This is value only selectable if
+			   'connected_type' reports a value of "USB_PD_PPS"]
+
+What: 		/sys/class/power_supply/tcpm-source-psy/voltage_min
+Date:		December 2017
+Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
+Description:
+	This read-only property describes the minimum voltage the source supply
+	can provide.
+
+	Value in microvolts.
+
+What: 		/sys/class/power_supply/tcpm-source-psy/voltage_max
+Date:		December 2017
+Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
+Description:
+	This read-only property describes the maximum voltage the source supply
+	can provide.
+
+	Value in microvolts.
+
+What: 		/sys/class/power_supply/tcpm-source-psy/voltage_now
+Date:		December 2017
+Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
+Description:
+	This read-write property describes the voltage the source supply is
+	providing now. This property can only be written to if the source supply
+	is in online state '2' (PPS enabled), otherwise it's read-only
+	information.
+
+	Value in microvolts.
+
+What: 		/sys/class/power_supply/tcpm-source-psy/current_max
+Date:		December 2017
+Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
+Description:
+	This read-only property describes the maximum current the source supply
+	can provide.
+
+	Value in microamps.
+
+What: 		/sys/class/power_supply/tcpm-source-psy/current_now
+Date:		December 2017
+Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
+Description:
+	This read-write property describes the current the source supply can
+	provide now. This property can only be written to if the source supply
+	is in online state '2' (PPS enabled), otherwise it's read-only
+	information.
+
+	Value in microamps.
diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig
index bcb2744..1ef606d 100644
--- a/drivers/usb/typec/Kconfig
+++ b/drivers/usb/typec/Kconfig
@@ -48,6 +48,7 @@ if TYPEC
 config TYPEC_TCPM
 	tristate "USB Type-C Port Controller Manager"
 	depends on USB
+	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 9ce4756..82fddc7 100644
--- a/drivers/usb/typec/fusb302/fusb302.c
+++ b/drivers/usb/typec/fusb302/fusb302.c
@@ -18,7 +18,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>
@@ -99,11 +98,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;
@@ -872,13 +866,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);
@@ -894,11 +886,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;
 }
 
@@ -1697,43 +1684,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;
@@ -1773,7 +1723,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;
@@ -1820,14 +1769,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 b66d26c..b86a51c 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>
@@ -281,6 +282,11 @@ 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;
+	enum power_supply_conn_type connected_type;
+
 	u32 bist_request;
 
 	/* PD state for Vendor Defined Messages */
@@ -1893,6 +1899,7 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo,
 	int ret = -EINVAL;
 
 	port->pps_data.supported = false;
+	port->connected_type = POWER_SUPPLY_CONN_TYPE_USB_PD;
 
 	/*
 	 * Select the source PDO providing the most power which has a
@@ -1974,8 +1981,11 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_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->connected_type =
+					POWER_SUPPLY_CONN_TYPE_USB_PD_PPS;
+			}
 			continue;
 		default:
 			tcpm_log(port, "Invalid PDO type, ignoring");
@@ -2459,6 +2469,9 @@ static void tcpm_reset_port(struct tcpm_port *port)
 	port->try_snk_count = 0;
 	port->supply_voltage = 0;
 	port->current_limit = 0;
+	port->connected_type = POWER_SUPPLY_CONN_TYPE_USB_TYPE_C;
+
+	power_supply_changed(port->psy);
 }
 
 static void tcpm_detach(struct tcpm_port *port)
@@ -2991,6 +3004,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 */
@@ -4170,6 +4185,218 @@ static int nr_type_pdos(const u32 *pdo, unsigned int nr_pdo,
 	return count;
 }
 
+/* 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_CONNECTED_TYPE,
+	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)
+{
+	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)
+{
+	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 = 0;
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_CONNECTED_TYPE:
+		val->intval = port->connected_type;
+		break;
+	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;
+	}
+}
+
+static enum power_supply_conn_type tcpm_psy_conn_types[] = {
+	POWER_SUPPLY_CONN_TYPE_USB_TYPE_C,
+	POWER_SUPPLY_CONN_TYPE_USB_PD,
+	POWER_SUPPLY_CONN_TYPE_USB_PD_PPS,
+};
+
+static 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,
+	port->psy_desc.conn_types = tcpm_psy_conn_types;
+	port->psy_desc.num_conn_types = ARRAY_SIZE(tcpm_psy_conn_types);
+	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->connected_type = POWER_SUPPLY_CONN_TYPE_USB_TYPE_C;
+
+	port->psy = devm_power_supply_register(port->dev, &port->psy_desc,
+					       &psy_cfg);
+
+	return PTR_ERR_OR_ZERO(port->psy);
+}
+
 struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
 {
 	struct tcpm_port *port;
@@ -4253,6 +4480,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] 67+ messages in thread

* [PATCH v4 7/7] typec: tcpm: Add support for sink PPS related messages
@ 2018-01-02 15:50   ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 67+ messages in thread
From: Adam Thomson @ 2018-01-02 15:50 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 b86a51c..54d17d7 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,
 };
@@ -1413,10 +1421,42 @@ static int tcpm_validate_caps(struct tcpm_port *port, const u32 *pdo,
 /*
  * 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)
 {
@@ -1502,6 +1542,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;
@@ -1584,6 +1632,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 */
@@ -1703,12 +1752,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,
@@ -1749,7 +1870,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);
@@ -1810,6 +1933,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;
@@ -2561,14 +2687,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) {
@@ -3271,6 +3389,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] 67+ messages in thread

* [v4,7/7] typec: tcpm: Add support for sink PPS related messages
@ 2018-01-02 15:50   ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 67+ messages in thread
From: Opensource [Adam Thomson] @ 2018-01-02 15:50 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 b86a51c..54d17d7 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,
 };
@@ -1413,10 +1421,42 @@ static int tcpm_validate_caps(struct tcpm_port *port, const u32 *pdo,
 /*
  * 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)
 {
@@ -1502,6 +1542,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;
@@ -1584,6 +1632,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 */
@@ -1703,12 +1752,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,
@@ -1749,7 +1870,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);
@@ -1810,6 +1933,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;
@@ -2561,14 +2687,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) {
@@ -3271,6 +3389,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);

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

* [PATCH v4 7/7] typec: tcpm: Add support for sink PPS related messages
@ 2018-01-02 15:50   ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 67+ messages in thread
From: Adam Thomson @ 2018-01-02 15:50 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 b86a51c..54d17d7 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,
 };
@@ -1413,10 +1421,42 @@ static int tcpm_validate_caps(struct tcpm_port *port, const u32 *pdo,
 /*
  * 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)
 {
@@ -1502,6 +1542,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;
@@ -1584,6 +1632,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 */
@@ -1703,12 +1752,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,
@@ -1749,7 +1870,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);
@@ -1810,6 +1933,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;
@@ -2561,14 +2687,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) {
@@ -3271,6 +3389,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] 67+ messages in thread

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

On Tue, Jan 02, 2018 at 03:50:48PM +0000, Adam Thomson wrote:
> This patch set adds sink side support for the PPS feature introduced in the
> USB PD 3.0 specification.

Heikki and Guenter, any thoughts on this patch series?

thanks,

greg k-h

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

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

On Tue, Jan 02, 2018 at 03:50:48PM +0000, Adam Thomson wrote:
> This patch set adds sink side support for the PPS feature introduced in the
> USB PD 3.0 specification.

Heikki and Guenter, any thoughts on this patch series?

thanks,

greg k-h
--
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	[flat|nested] 67+ messages in thread

* Re: [PATCH v4 0/7] typec: tcpm: Add sink side support for PPS
  2018-01-22 14:31   ` Greg Kroah-Hartman
  (?)
@ 2018-01-22 15:57   ` Guenter Roeck
  -1 siblings, 0 replies; 67+ messages in thread
From: Guenter Roeck @ 2018-01-22 15:57 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: Adam Thomson, Heikki Krogerus, Sebastian Reichel, Hans de Goede,
	Yueyao Zhu, Rui Miguel Silva, linux-usb, linux-pm, linux-kernel,
	support.opensource

On Mon, Jan 22, 2018 at 03:31:29PM +0100, Greg Kroah-Hartman wrote:
> On Tue, Jan 02, 2018 at 03:50:48PM +0000, Adam Thomson wrote:
> > This patch set adds sink side support for the PPS feature introduced in the
> > USB PD 3.0 specification.
> 
> Heikki and Guenter, any thoughts on this patch series?
> 
On my plate, but I am still buried in the fallout from Spectre and Meltdown.
It might take a while for me to dig out from it.

Guenter

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

* Re: [PATCH v4 1/7] typec: tcpm: Add PD Rev 3.0 definitions to PD header
@ 2018-01-30 12:21     ` Heikki Krogerus
  0 siblings, 0 replies; 67+ messages in thread
From: Heikki Krogerus @ 2018-01-30 12:21 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, Jan 02, 2018 at 03:50:49PM +0000, Adam Thomson wrote:
> 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>

Just one nitpick. I noticed that you are exceeding the 80 character
limit per line in several places in this series, and I many cases it
does not look like splitting the line would make the code any less
readable.

But I don't think that is critical, so if there are no other comments:


Acked-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>


> ---
>  include/linux/usb/pd.h | 185 ++++++++++++++++++++++++++++++++++++++++++++++---
>  1 file changed, 174 insertions(+), 11 deletions(-)
> 
> diff --git a/include/linux/usb/pd.h b/include/linux/usb/pd.h
> index b3d41d7..ff359bdf 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)))
> +	cpu_to_le16(PD_HEADER((type), (pwr), (data), PD_REV20, (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
> @@ -174,6 +268,34 @@ 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_TYPE_MASK	0x3
> +
> +#define PDO_APDO_TYPE(t)	((t) << PDO_APDO_TYPE_SHIFT)
> +
> +#define PDO_PPS_APDO_MAX_VOLT_SHIFT	17	/* 100mV units */
> +#define PDO_PPS_APDO_MIN_VOLT_SHIFT	8	/* 100mV units */
> +#define PDO_PPS_APDO_MAX_CURR_SHIFT	0	/* 50mA units */
> +
> +#define PDO_PPS_APDO_VOLT_MASK	0xff
> +#define PDO_PPS_APDO_CURR_MASK	0x7f
> +
> +#define PDO_PPS_APDO_MIN_VOLT(mv)	\
> +	((((mv) / 100) & PDO_PPS_APDO_VOLT_MASK) << PDO_PPS_APDO_MIN_VOLT_SHIFT)
> +#define PDO_PPS_APDO_MAX_VOLT(mv)	\
> +	((((mv) / 100) & PDO_PPS_APDO_VOLT_MASK) << PDO_PPS_APDO_MAX_VOLT_SHIFT)
> +#define PDO_PPS_APDO_MAX_CURR(ma)	\
> +	((((ma) / 50) & PDO_PPS_APDO_CURR_MASK) << PDO_PPS_APDO_MAX_CURR_SHIFT)
> +
> +#define PDO_PPS_APDO(min_mv, max_mv, max_ma)				\
> +	(PDO_TYPE(PDO_TYPE_APDO) | PDO_APDO_TYPE(APDO_TYPE_PPS) |	\
> +	PDO_PPS_APDO_MIN_VOLT(min_mv) | PDO_PPS_APDO_MAX_VOLT(max_mv) |	\
> +	PDO_PPS_APDO_MAX_CURR(max_ma))
> +
>  static inline enum pd_pdo_type pdo_type(u32 pdo)
>  {
>  	return (pdo >> PDO_TYPE_SHIFT) & PDO_TYPE_MASK;
> @@ -204,6 +326,29 @@ 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_pps_apdo_min_voltage(u32 pdo)
> +{
> +	return ((pdo >> PDO_PPS_APDO_MIN_VOLT_SHIFT) &
> +		PDO_PPS_APDO_VOLT_MASK) * 100;
> +}
> +
> +static inline unsigned int pdo_pps_apdo_max_voltage(u32 pdo)
> +{
> +	return ((pdo >> PDO_PPS_APDO_MAX_VOLT_SHIFT) &
> +		PDO_PPS_APDO_VOLT_MASK) * 100;
> +}
> +
> +static inline unsigned int pdo_pps_apdo_max_current(u32 pdo)
> +{
> +	return ((pdo >> PDO_PPS_APDO_MAX_CURR_SHIFT) &
> +		PDO_PPS_APDO_CURR_MASK) * 50;
> +}
> +
>  /* RDO: Request Data Object */
>  #define RDO_OBJ_POS_SHIFT	28
>  #define RDO_OBJ_POS_MASK	0x7
> @@ -237,6 +382,24 @@ 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 RDO_PROG_VOLT_MV_STEP	20
> +#define RDO_PROG_CURR_MA_STEP	50
> +
> +#define PDO_PROG_OUT_VOLT(mv)	\
> +	((((mv) / RDO_PROG_VOLT_MV_STEP) & RDO_PROG_VOLT_MASK) << RDO_PROG_VOLT_SHIFT)
> +#define PDO_PROG_OP_CURR(ma)	\
> +	((((ma) / RDO_PROG_CURR_MA_STEP) & 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

-- 
heikki

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

* [v4,1/7] typec: tcpm: Add PD Rev 3.0 definitions to PD header
@ 2018-01-30 12:21     ` Heikki Krogerus
  0 siblings, 0 replies; 67+ messages in thread
From: Heikki Krogerus @ 2018-01-30 12:21 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, Jan 02, 2018 at 03:50:49PM +0000, Adam Thomson wrote:
> 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>

Just one nitpick. I noticed that you are exceeding the 80 character
limit per line in several places in this series, and I many cases it
does not look like splitting the line would make the code any less
readable.

But I don't think that is critical, so if there are no other comments:


Acked-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>


> ---
>  include/linux/usb/pd.h | 185 ++++++++++++++++++++++++++++++++++++++++++++++---
>  1 file changed, 174 insertions(+), 11 deletions(-)
> 
> diff --git a/include/linux/usb/pd.h b/include/linux/usb/pd.h
> index b3d41d7..ff359bdf 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)))
> +	cpu_to_le16(PD_HEADER((type), (pwr), (data), PD_REV20, (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
> @@ -174,6 +268,34 @@ 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_TYPE_MASK	0x3
> +
> +#define PDO_APDO_TYPE(t)	((t) << PDO_APDO_TYPE_SHIFT)
> +
> +#define PDO_PPS_APDO_MAX_VOLT_SHIFT	17	/* 100mV units */
> +#define PDO_PPS_APDO_MIN_VOLT_SHIFT	8	/* 100mV units */
> +#define PDO_PPS_APDO_MAX_CURR_SHIFT	0	/* 50mA units */
> +
> +#define PDO_PPS_APDO_VOLT_MASK	0xff
> +#define PDO_PPS_APDO_CURR_MASK	0x7f
> +
> +#define PDO_PPS_APDO_MIN_VOLT(mv)	\
> +	((((mv) / 100) & PDO_PPS_APDO_VOLT_MASK) << PDO_PPS_APDO_MIN_VOLT_SHIFT)
> +#define PDO_PPS_APDO_MAX_VOLT(mv)	\
> +	((((mv) / 100) & PDO_PPS_APDO_VOLT_MASK) << PDO_PPS_APDO_MAX_VOLT_SHIFT)
> +#define PDO_PPS_APDO_MAX_CURR(ma)	\
> +	((((ma) / 50) & PDO_PPS_APDO_CURR_MASK) << PDO_PPS_APDO_MAX_CURR_SHIFT)
> +
> +#define PDO_PPS_APDO(min_mv, max_mv, max_ma)				\
> +	(PDO_TYPE(PDO_TYPE_APDO) | PDO_APDO_TYPE(APDO_TYPE_PPS) |	\
> +	PDO_PPS_APDO_MIN_VOLT(min_mv) | PDO_PPS_APDO_MAX_VOLT(max_mv) |	\
> +	PDO_PPS_APDO_MAX_CURR(max_ma))
> +
>  static inline enum pd_pdo_type pdo_type(u32 pdo)
>  {
>  	return (pdo >> PDO_TYPE_SHIFT) & PDO_TYPE_MASK;
> @@ -204,6 +326,29 @@ 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_pps_apdo_min_voltage(u32 pdo)
> +{
> +	return ((pdo >> PDO_PPS_APDO_MIN_VOLT_SHIFT) &
> +		PDO_PPS_APDO_VOLT_MASK) * 100;
> +}
> +
> +static inline unsigned int pdo_pps_apdo_max_voltage(u32 pdo)
> +{
> +	return ((pdo >> PDO_PPS_APDO_MAX_VOLT_SHIFT) &
> +		PDO_PPS_APDO_VOLT_MASK) * 100;
> +}
> +
> +static inline unsigned int pdo_pps_apdo_max_current(u32 pdo)
> +{
> +	return ((pdo >> PDO_PPS_APDO_MAX_CURR_SHIFT) &
> +		PDO_PPS_APDO_CURR_MASK) * 50;
> +}
> +
>  /* RDO: Request Data Object */
>  #define RDO_OBJ_POS_SHIFT	28
>  #define RDO_OBJ_POS_MASK	0x7
> @@ -237,6 +382,24 @@ 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 RDO_PROG_VOLT_MV_STEP	20
> +#define RDO_PROG_CURR_MA_STEP	50
> +
> +#define PDO_PROG_OUT_VOLT(mv)	\
> +	((((mv) / RDO_PROG_VOLT_MV_STEP) & RDO_PROG_VOLT_MASK) << RDO_PROG_VOLT_SHIFT)
> +#define PDO_PROG_OP_CURR(ma)	\
> +	((((ma) / RDO_PROG_CURR_MA_STEP) & 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	[flat|nested] 67+ messages in thread

* Re: [PATCH v4 2/7] typec: tcpm: Add ADO header for Alert message handling
@ 2018-01-30 12:22     ` Heikki Krogerus
  0 siblings, 0 replies; 67+ messages in thread
From: Heikki Krogerus @ 2018-01-30 12:22 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, Jan 02, 2018 at 03:50:50PM +0000, Adam Thomson wrote:
> 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>

Acked-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>


Thanks,

-- 
heikki

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

* [v4,2/7] typec: tcpm: Add ADO header for Alert message handling
@ 2018-01-30 12:22     ` Heikki Krogerus
  0 siblings, 0 replies; 67+ messages in thread
From: Heikki Krogerus @ 2018-01-30 12:22 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, Jan 02, 2018 at 03:50:50PM +0000, Adam Thomson wrote:
> 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>

Acked-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>


Thanks,

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

* Re: [PATCH v4 3/7] typec: tcpm: Add SDB header for Status message handling
@ 2018-01-30 12:22     ` Heikki Krogerus
  0 siblings, 0 replies; 67+ messages in thread
From: Heikki Krogerus @ 2018-01-30 12:22 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, Jan 02, 2018 at 03:50:51PM +0000, Adam Thomson wrote:
> 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>

Acked-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>


Thanks,

-- 
heikki

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

* [v4,3/7] typec: tcpm: Add SDB header for Status message handling
@ 2018-01-30 12:22     ` Heikki Krogerus
  0 siblings, 0 replies; 67+ messages in thread
From: Heikki Krogerus @ 2018-01-30 12:22 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, Jan 02, 2018 at 03:50:51PM +0000, Adam Thomson wrote:
> 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>

Acked-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>


Thanks,

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

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

On Tue, Jan 02, 2018 at 03:50:51PM +0000, Adam Thomson wrote:
> 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>

Acked-by: Heikki Krogerus <heikki.krogerus-VuQAYsv1563Yd54FQh9/CA@public.gmane.org>


Thanks,

-- 
heikki
--
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	[flat|nested] 67+ messages in thread

* Re: [PATCH v4 4/7] typec: tcpm: Add core support for sink side PPS
@ 2018-01-30 12:46     ` Heikki Krogerus
  0 siblings, 0 replies; 67+ messages in thread
From: Heikki Krogerus @ 2018-01-30 12:46 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, Jan 02, 2018 at 03:50:52PM +0000, Adam Thomson wrote:
> 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 | 533 ++++++++++++++++++++++++++++++++++++++++++++++-
>  include/linux/usb/pd.h   |   4 +-
>  include/linux/usb/tcpm.h |   2 +-
>  3 files changed, 525 insertions(+), 14 deletions(-)
> 
> diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c
> index f4d563e..b66d26c 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 {
> +	u32 min_volt;
> +	u32 max_volt;
> +	u32 max_curr;
> +	u32 out_volt;
> +	u32 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;
> @@ -255,6 +267,7 @@ struct tcpm_port {
>  	unsigned int nr_fixed; /* number of fixed sink PDOs */
>  	unsigned int nr_var; /* number of variable sink PDOs */
>  	unsigned int nr_batt; /* number of battery sink PDOs */
> +	unsigned int nr_apdo; /* number of APDO type PDOs */
>  	u32 snk_vdo[VDO_MAX_OBJECTS];
>  	unsigned int nr_snk_vdo;
>  
> @@ -262,6 +275,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;
> @@ -278,8 +292,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];
> @@ -497,6 +516,16 @@ static void tcpm_log_source_caps(struct tcpm_port *port)
>  				  pdo_max_voltage(pdo),
>  				  pdo_max_power(pdo));
>  			break;
> +		case PDO_TYPE_APDO:
> +			if (pdo_apdo_type(pdo) == APDO_TYPE_PPS)
> +				scnprintf(msg, sizeof(msg),
> +					  "%u-%u mV, %u mA",
> +					  pdo_pps_apdo_min_voltage(pdo),
> +					  pdo_pps_apdo_max_voltage(pdo),
> +					  pdo_pps_apdo_max_current(pdo));
> +			else
> +				strcpy(msg, "undefined APDO");
> +			break;
>  		default:
>  			strcpy(msg, "undefined");
>  			break;
> @@ -791,11 +820,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);
>  	}
> @@ -816,11 +847,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);
>  	}
> @@ -1187,6 +1220,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]);
> @@ -1258,6 +1292,8 @@ enum pdo_err {
>  	PDO_ERR_FIXED_NOT_SORTED,
>  	PDO_ERR_VARIABLE_BATT_NOT_SORTED,
>  	PDO_ERR_DUPE_PDO,
> +	PDO_ERR_PPS_APDO_NOT_SORTED,
> +	PDO_ERR_DUPE_PPS_APDO,
>  };
>  
>  static const char * const pdo_err_msg[] = {
> @@ -1273,6 +1309,10 @@ enum pdo_err {
>  	" err: Variable/Battery supply pdos should be in increasing order of their minimum voltage",
>  	[PDO_ERR_DUPE_PDO] =
>  	" err: Variable/Batt supply pdos cannot have same min/max voltage",
> +	[PDO_ERR_PPS_APDO_NOT_SORTED] =
> +	" err: Programmable power supply apdos should be in increasing order of their maximum voltage",
> +	[PDO_ERR_DUPE_PPS_APDO] =
> +	" err: Programmable power supply apdos cannot have same min/max voltage and max current",
>  };
>  
>  static enum pdo_err tcpm_caps_err(struct tcpm_port *port, const u32 *pdo,
> @@ -1322,6 +1362,26 @@ static enum pdo_err tcpm_caps_err(struct tcpm_port *port, const u32 *pdo,
>  					  pdo_min_voltage(pdo[i - 1])))
>  					return PDO_ERR_DUPE_PDO;
>  				break;
> +			/*
> +			 * The Programmable Power Supply APDOs, if present,
> +			 * shall be sent in Maximum Voltage order;
> +			 * lowest to highest.
> +			 */
> +			case PDO_TYPE_APDO:
> +				if (pdo_apdo_type(pdo[i]) != APDO_TYPE_PPS)
> +					break;
> +
> +				if (pdo_pps_apdo_max_current(pdo[i]) <
> +				    pdo_pps_apdo_max_current(pdo[i - 1]))
> +					return PDO_ERR_PPS_APDO_NOT_SORTED;
> +				else if ((pdo_pps_apdo_min_voltage(pdo[i]) ==
> +					  pdo_pps_apdo_min_voltage(pdo[i - 1])) &&
> +					 (pdo_pps_apdo_max_voltage(pdo[i]) ==
> +					  pdo_pps_apdo_max_voltage(pdo[i - 1])) &&
> +					 (pdo_pps_apdo_max_current(pdo[i]) ==
> +					  pdo_pps_apdo_max_current(pdo[i - 1])))
> +					return PDO_ERR_DUPE_PPS_APDO;
> +				break;
>  			default:
>  				tcpm_log_force(port, " Unknown pdo type");
>  			}
> @@ -1347,11 +1407,16 @@ static int tcpm_validate_caps(struct tcpm_port *port, const u32 *pdo,
>  /*
>   * 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) {
> @@ -1370,6 +1435,16 @@ static void tcpm_pd_data_request(struct tcpm_port *port,
>  				   port->nr_source_caps);
>  
>  		/*
> +		 * 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.
> @@ -1390,6 +1465,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;
> @@ -1414,6 +1502,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)
>  {
> @@ -1490,6 +1587,14 @@ 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:
> +			/* Revert data back from any requested PPS updates */
> +			port->pps_data.out_volt = port->supply_voltage;
> +			port->pps_data.op_curr = port->current_limit;
> +			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);
> @@ -1512,6 +1617,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->supply_voltage = port->pps_data.out_volt;
> +			port->current_limit = port->pps_data.op_curr;
>  			tcpm_set_state(port, SNK_TRANSITION_SINK, 0);
>  			break;
>  		case SOFT_RESET_SEND:
> @@ -1666,6 +1778,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);
> @@ -1779,6 +1892,8 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo,
>  	unsigned int i, j, max_mw = 0, max_mv = 0, mw = 0, mv = 0, ma = 0;
>  	int ret = -EINVAL;
>  
> +	port->pps_data.supported = false;
> +
>  	/*
>  	 * Select the source PDO providing the most power which has a
>  	 * matchig sink cap.
> @@ -1787,7 +1902,8 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo,
>  		u32 pdo = port->source_caps[i];
>  		enum pd_pdo_type type = pdo_type(pdo);
>  
> -		if (type == PDO_TYPE_FIXED) {
> +		switch (type) {
> +		case PDO_TYPE_FIXED:
>  			for (j = 0; j < port->nr_fixed; j++) {
>  				if (pdo_fixed_voltage(pdo) ==
>  				    pdo_fixed_voltage(port->snk_pdo[j])) {
> @@ -1809,7 +1925,8 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo,
>  					break;
>  				}
>  			}
> -		} else if (type == PDO_TYPE_BATT) {
> +			break;
> +		case PDO_TYPE_BATT:
>  			for (j = port->nr_fixed;
>  			     j < port->nr_fixed +
>  				 port->nr_batt;
> @@ -1830,7 +1947,8 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo,
>  					}
>  				}
>  			}
> -		} else if (type == PDO_TYPE_VAR) {
> +			break;
> +		case PDO_TYPE_VAR:
>  			for (j = port->nr_fixed +
>  				 port->nr_batt;
>  			     j < port->nr_fixed +
> @@ -1854,12 +1972,98 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_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;
>  		}
>  	}
>  
>  	return ret;
>  }
>  
> +#define min_pps_apdo_current(x, y)	\
> +	min(pdo_pps_apdo_max_current(x), pdo_pps_apdo_max_current(y))
> +
> +static unsigned int tcpm_pd_select_pps_apdo(struct tcpm_port *port,
> +					    int *snk_pdo, int *src_pdo)
> +{
> +	unsigned int i, j, max_mw = 0, max_mv = 0, mw = 0, mv = 0, ma = 0;
> +	enum pd_pdo_type type;
> +	u32 pdo;
> +	int ret = -EOPNOTSUPP;
> +
> +	/*
> +	 * 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.
> +	 */
> +	*src_pdo = 0;
> +	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;
> +			}
> +
> +			for (j = port->nr_fixed +
> +				 port->nr_batt +
> +				 port->nr_var;
> +			     j < port->nr_fixed +
> +				 port->nr_batt +
> +				 port->nr_var +
> +				 port->nr_apdo;
> +			     ++j) {
> +				if ((pdo_pps_apdo_min_voltage(pdo) >=
> +				     pdo_pps_apdo_min_voltage(port->snk_pdo[j])) &&
> +				    (pdo_pps_apdo_max_voltage(pdo) <=
> +				     pdo_pps_apdo_max_voltage(port->snk_pdo[j]))) {
> +					ma = min_pps_apdo_current(pdo,
> +								  port->snk_pdo[j]);
> +					mv = pdo_pps_apdo_max_voltage(pdo);
> +					mw = (ma * mv) / 1000;
> +					if ((mw > max_mw) ||
> +					    ((mw == max_mw) && (mv > max_mv))) {
> +						ret = 0;
> +						*src_pdo = i;
> +						*snk_pdo = j;
> +						max_mw = mw;
> +						max_mv = mv;
> +					}
> +				}
> +			}
> +
> +			break;
> +		default:
> +			tcpm_log(port, "Not APDO type, ignoring");
> +			continue;
> +		}
> +	}
> +
> +	if (*src_pdo > 0) {
> +		pdo = port->source_caps[*src_pdo];
> +
> +		port->pps_data.min_volt = pdo_pps_apdo_min_voltage(pdo);
> +		port->pps_data.max_volt = pdo_pps_apdo_max_voltage(pdo);
> +		port->pps_data.max_curr =
> +			min_pps_apdo_current(pdo, port->snk_pdo[*snk_pdo]);
> +		port->pps_data.out_volt =
> +			min(port->pps_data.out_volt, pdo_pps_apdo_max_voltage(pdo));
> +		port->pps_data.op_curr =
> +			min(port->pps_data.op_curr, pdo_pps_apdo_max_current(pdo));
> +	}
> +
> +	return ret;
> +}
> +
>  static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo)
>  {
>  	unsigned int mv, ma, mw, flags;
> @@ -1875,10 +2079,18 @@ static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo)
>  	matching_snk_pdo = port->snk_pdo[snk_pdo_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 sink pdo's limit */
>  	if (type == PDO_TYPE_BATT) {
> @@ -1943,6 +2155,107 @@ 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 out_mv, op_ma, op_mw, min_mv, max_mv, max_ma, flags;
> +	enum pd_pdo_type type;
> +	int src_pdo_index, snk_pdo_index;
> +	u32 pdo, matching_snk_pdo;
> +	int ret;
> +
> +	ret = tcpm_pd_select_pps_apdo(port, &snk_pdo_index, &src_pdo_index);
> +	if (ret)
> +		return ret;
> +
> +	pdo = port->source_caps[src_pdo_index];
> +	matching_snk_pdo = port->snk_pdo[snk_pdo_index];
> +	type = pdo_type(pdo);
> +
> +	switch (type) {
> +	case PDO_TYPE_APDO:
> +		if (pdo_apdo_type(pdo) != APDO_TYPE_PPS) {
> +			tcpm_log(port, "Invalid APDO selected!");
> +			return -EINVAL;
> +		}
> +		min_mv = pdo_pps_apdo_min_voltage(pdo);
> +		max_mv = pdo_pps_apdo_max_voltage(pdo);
> +		max_ma = pdo_pps_apdo_max_current(pdo);
> +		out_mv = port->pps_data.out_volt;
> +		op_ma = port->pps_data.op_curr;
> +		break;
> +	default:
> +		tcpm_log(port, "Invalid PDO selected!");
> +		return -EINVAL;
> +	}
> +
> +	flags = RDO_USB_COMM | RDO_NO_SUSPEND;
> +
> +	op_mw = (op_ma * out_mv) / 1000;
> +	if (op_mw < port->operating_snk_mw) {
> +		/*
> +		 * Try raising current to meet power needs. If that's not enough
> +		 * then try upping the voltage. If that's still not enoguh
> +		 * then we've obviously chosen a PPS APDO which really isn't
> +		 * suitable so abandon ship.
> +		 */
> +		op_ma = ((port->operating_snk_mw * 1000) / out_mv);
> +		if ((port->operating_snk_mw * 1000) % out_mv)
> +			++op_ma;
> +		op_ma += RDO_PROG_CURR_MA_STEP - (op_ma % RDO_PROG_CURR_MA_STEP);
> +
> +		if (op_ma > max_ma) {
> +			op_ma = max_ma;
> +			out_mv = ((port->operating_snk_mw * 1000) / op_ma);
> +			if ((port->operating_snk_mw * 1000) % op_ma)
> +				++out_mv;
> +			out_mv += RDO_PROG_VOLT_MV_STEP -
> +				  (out_mv % RDO_PROG_VOLT_MV_STEP);
> +
> +			if (out_mv > max_mv) {
> +				tcpm_log(port, "Invalid PPS APDO selected!");
> +				return -EINVAL;
> +			}
> +		}
> +	}
> +
> +	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(src_pdo_index + 1, out_mv, op_ma, flags);
> +
> +	tcpm_log(port, "Requesting APDO %d: %u mV, %u mA",
> +		 src_pdo_index, out_mv, op_ma);
> +
> +	port->pps_data.op_curr = op_ma;
> +	port->pps_data.out_volt = 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);
>  
> @@ -2128,6 +2441,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
> @@ -2143,6 +2457,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)
> @@ -2389,6 +2705,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;
> @@ -2449,6 +2766,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
> @@ -2472,6 +2790,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);
> @@ -2480,6 +2799,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 &&
> @@ -2561,6 +2881,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;
> @@ -2631,6 +2952,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),
> @@ -2638,6 +2977,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);
> @@ -2646,7 +2986,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 */
> @@ -2693,6 +3037,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);
> @@ -2913,6 +3258,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:
> @@ -3378,7 +3724,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;
> @@ -3423,7 +3769,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;
> @@ -3463,7 +3809,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;
> @@ -3495,6 +3841,162 @@ 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) {
> +		ret = -EINVAL;
> +		goto port_unlock;
> +	}
> +
> +	reinit_completion(&port->pps_complete);
> +	port->pps_data.op_curr = op_curr;
> +	port->pps_status = 0;
> +	port->pps_pending = true;
> +	tcpm_set_state(port, SNK_NEGOTIATE_PPS_CAPABILITIES, 0);

Why not just take the swap_lock here..

> +	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;

and you don't need that goto..

> +port_unlock:
> +	mutex_unlock(&port->lock);
> +swap_unlock:
> +	mutex_unlock(&port->swap_lock);

and this becomes..

	mutex_unlock(&port->swap_lock);
        return ret;

port_unlock:
	mutex_unlock(&port->lock);
        return ret;

> +	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;

Or, on top of what I said above, you could actually consider releasing
the port lock here and just returning. Then you would not need those
port_unlock and swap_unlock labels at all..

                mutex_unlock(&port->lock);
                return -EOPNOTSUPP;

> +	}
> +
> +	if (port->state != SNK_READY) {
> +		ret = -EAGAIN;
> +		goto port_unlock;

                mutex_unlock(&port->lock);
                return -EAGAIN;
> +	}
> +
> +	if ((out_volt < port->pps_data.min_volt) ||
> +	    (out_volt > port->pps_data.max_volt)) {
> +		ret = -EINVAL;
> +		goto port_unlock;

                mutex_unlock(&port->lock);
                return -EINVAL;

> +	}
> +
> +	target_mw = (port->pps_data.op_curr * out_volt) / 1000;
> +	if (target_mw < port->operating_snk_mw) {
> +		ret = -EINVAL;
> +		goto port_unlock;

                mutex_unlock(&port->lock);
                return -EINVAL;
> +	}
> +
> +	reinit_completion(&port->pps_complete);
> +	port->pps_data.out_volt = out_volt;
> +	port->pps_status = 0;
> +	port->pps_pending = true;
> +	tcpm_set_state(port, SNK_NEGOTIATE_PPS_CAPABILITIES, 0);

        mutex_lock(&port->swap_lock);

> +	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;

        mutex_unlock(&port->swap_lock);

        return ret;

> +	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) {
> +		port->pps_data.out_volt = port->supply_voltage;
> +		port->pps_data.op_curr = port->current_limit;
> +		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);

You can do the same as above here as well.

> +	return ret;
> +}
> +
>  static void tcpm_init(struct tcpm_port *port)
>  {
>  	enum typec_cc_status cc1, cc2;
> @@ -3634,13 +4136,18 @@ int 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;
> @@ -3695,6 +4202,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);
>  	tcpm_debugfs_init(port);
>  
>  	if (tcpm_validate_caps(port, tcpc->config->src_pdo,
> @@ -3717,6 +4225,9 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
>  	port->nr_batt = nr_type_pdos(port->snk_pdo,
>  				     port->nr_snk_pdo,
>  				     PDO_TYPE_BATT);
> +	port->nr_apdo = nr_type_pdos(port->snk_pdo,
> +				     port->nr_snk_pdo,
> +				     PDO_TYPE_APDO);
>  	port->nr_snk_vdo = tcpm_copy_vdos(port->snk_vdo, tcpc->config->snk_vdo,
>  					  tcpc->config->nr_snk_vdo);
>  
> @@ -3732,7 +4243,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/pd.h b/include/linux/usb/pd.h
> index ff359bdf..09b570f 100644
> --- a/include/linux/usb/pd.h
> +++ b/include/linux/usb/pd.h
> @@ -103,8 +103,8 @@ enum pd_ext_msg_type {
>  	 (((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), PD_REV20, (id), (cnt), (0)))
> +#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)
>  {
> diff --git a/include/linux/usb/tcpm.h b/include/linux/usb/tcpm.h
> index ca1c0b5..d6673f7 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

-- 
heikki

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

* [v4,4/7] typec: tcpm: Add core support for sink side PPS
@ 2018-01-30 12:46     ` Heikki Krogerus
  0 siblings, 0 replies; 67+ messages in thread
From: Heikki Krogerus @ 2018-01-30 12:46 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, Jan 02, 2018 at 03:50:52PM +0000, Adam Thomson wrote:
> 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 | 533 ++++++++++++++++++++++++++++++++++++++++++++++-
>  include/linux/usb/pd.h   |   4 +-
>  include/linux/usb/tcpm.h |   2 +-
>  3 files changed, 525 insertions(+), 14 deletions(-)
> 
> diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c
> index f4d563e..b66d26c 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 {
> +	u32 min_volt;
> +	u32 max_volt;
> +	u32 max_curr;
> +	u32 out_volt;
> +	u32 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;
> @@ -255,6 +267,7 @@ struct tcpm_port {
>  	unsigned int nr_fixed; /* number of fixed sink PDOs */
>  	unsigned int nr_var; /* number of variable sink PDOs */
>  	unsigned int nr_batt; /* number of battery sink PDOs */
> +	unsigned int nr_apdo; /* number of APDO type PDOs */
>  	u32 snk_vdo[VDO_MAX_OBJECTS];
>  	unsigned int nr_snk_vdo;
>  
> @@ -262,6 +275,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;
> @@ -278,8 +292,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];
> @@ -497,6 +516,16 @@ static void tcpm_log_source_caps(struct tcpm_port *port)
>  				  pdo_max_voltage(pdo),
>  				  pdo_max_power(pdo));
>  			break;
> +		case PDO_TYPE_APDO:
> +			if (pdo_apdo_type(pdo) == APDO_TYPE_PPS)
> +				scnprintf(msg, sizeof(msg),
> +					  "%u-%u mV, %u mA",
> +					  pdo_pps_apdo_min_voltage(pdo),
> +					  pdo_pps_apdo_max_voltage(pdo),
> +					  pdo_pps_apdo_max_current(pdo));
> +			else
> +				strcpy(msg, "undefined APDO");
> +			break;
>  		default:
>  			strcpy(msg, "undefined");
>  			break;
> @@ -791,11 +820,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);
>  	}
> @@ -816,11 +847,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);
>  	}
> @@ -1187,6 +1220,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]);
> @@ -1258,6 +1292,8 @@ enum pdo_err {
>  	PDO_ERR_FIXED_NOT_SORTED,
>  	PDO_ERR_VARIABLE_BATT_NOT_SORTED,
>  	PDO_ERR_DUPE_PDO,
> +	PDO_ERR_PPS_APDO_NOT_SORTED,
> +	PDO_ERR_DUPE_PPS_APDO,
>  };
>  
>  static const char * const pdo_err_msg[] = {
> @@ -1273,6 +1309,10 @@ enum pdo_err {
>  	" err: Variable/Battery supply pdos should be in increasing order of their minimum voltage",
>  	[PDO_ERR_DUPE_PDO] =
>  	" err: Variable/Batt supply pdos cannot have same min/max voltage",
> +	[PDO_ERR_PPS_APDO_NOT_SORTED] =
> +	" err: Programmable power supply apdos should be in increasing order of their maximum voltage",
> +	[PDO_ERR_DUPE_PPS_APDO] =
> +	" err: Programmable power supply apdos cannot have same min/max voltage and max current",
>  };
>  
>  static enum pdo_err tcpm_caps_err(struct tcpm_port *port, const u32 *pdo,
> @@ -1322,6 +1362,26 @@ static enum pdo_err tcpm_caps_err(struct tcpm_port *port, const u32 *pdo,
>  					  pdo_min_voltage(pdo[i - 1])))
>  					return PDO_ERR_DUPE_PDO;
>  				break;
> +			/*
> +			 * The Programmable Power Supply APDOs, if present,
> +			 * shall be sent in Maximum Voltage order;
> +			 * lowest to highest.
> +			 */
> +			case PDO_TYPE_APDO:
> +				if (pdo_apdo_type(pdo[i]) != APDO_TYPE_PPS)
> +					break;
> +
> +				if (pdo_pps_apdo_max_current(pdo[i]) <
> +				    pdo_pps_apdo_max_current(pdo[i - 1]))
> +					return PDO_ERR_PPS_APDO_NOT_SORTED;
> +				else if ((pdo_pps_apdo_min_voltage(pdo[i]) ==
> +					  pdo_pps_apdo_min_voltage(pdo[i - 1])) &&
> +					 (pdo_pps_apdo_max_voltage(pdo[i]) ==
> +					  pdo_pps_apdo_max_voltage(pdo[i - 1])) &&
> +					 (pdo_pps_apdo_max_current(pdo[i]) ==
> +					  pdo_pps_apdo_max_current(pdo[i - 1])))
> +					return PDO_ERR_DUPE_PPS_APDO;
> +				break;
>  			default:
>  				tcpm_log_force(port, " Unknown pdo type");
>  			}
> @@ -1347,11 +1407,16 @@ static int tcpm_validate_caps(struct tcpm_port *port, const u32 *pdo,
>  /*
>   * 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) {
> @@ -1370,6 +1435,16 @@ static void tcpm_pd_data_request(struct tcpm_port *port,
>  				   port->nr_source_caps);
>  
>  		/*
> +		 * 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.
> @@ -1390,6 +1465,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;
> @@ -1414,6 +1502,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)
>  {
> @@ -1490,6 +1587,14 @@ 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:
> +			/* Revert data back from any requested PPS updates */
> +			port->pps_data.out_volt = port->supply_voltage;
> +			port->pps_data.op_curr = port->current_limit;
> +			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);
> @@ -1512,6 +1617,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->supply_voltage = port->pps_data.out_volt;
> +			port->current_limit = port->pps_data.op_curr;
>  			tcpm_set_state(port, SNK_TRANSITION_SINK, 0);
>  			break;
>  		case SOFT_RESET_SEND:
> @@ -1666,6 +1778,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);
> @@ -1779,6 +1892,8 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo,
>  	unsigned int i, j, max_mw = 0, max_mv = 0, mw = 0, mv = 0, ma = 0;
>  	int ret = -EINVAL;
>  
> +	port->pps_data.supported = false;
> +
>  	/*
>  	 * Select the source PDO providing the most power which has a
>  	 * matchig sink cap.
> @@ -1787,7 +1902,8 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo,
>  		u32 pdo = port->source_caps[i];
>  		enum pd_pdo_type type = pdo_type(pdo);
>  
> -		if (type == PDO_TYPE_FIXED) {
> +		switch (type) {
> +		case PDO_TYPE_FIXED:
>  			for (j = 0; j < port->nr_fixed; j++) {
>  				if (pdo_fixed_voltage(pdo) ==
>  				    pdo_fixed_voltage(port->snk_pdo[j])) {
> @@ -1809,7 +1925,8 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo,
>  					break;
>  				}
>  			}
> -		} else if (type == PDO_TYPE_BATT) {
> +			break;
> +		case PDO_TYPE_BATT:
>  			for (j = port->nr_fixed;
>  			     j < port->nr_fixed +
>  				 port->nr_batt;
> @@ -1830,7 +1947,8 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo,
>  					}
>  				}
>  			}
> -		} else if (type == PDO_TYPE_VAR) {
> +			break;
> +		case PDO_TYPE_VAR:
>  			for (j = port->nr_fixed +
>  				 port->nr_batt;
>  			     j < port->nr_fixed +
> @@ -1854,12 +1972,98 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_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;
>  		}
>  	}
>  
>  	return ret;
>  }
>  
> +#define min_pps_apdo_current(x, y)	\
> +	min(pdo_pps_apdo_max_current(x), pdo_pps_apdo_max_current(y))
> +
> +static unsigned int tcpm_pd_select_pps_apdo(struct tcpm_port *port,
> +					    int *snk_pdo, int *src_pdo)
> +{
> +	unsigned int i, j, max_mw = 0, max_mv = 0, mw = 0, mv = 0, ma = 0;
> +	enum pd_pdo_type type;
> +	u32 pdo;
> +	int ret = -EOPNOTSUPP;
> +
> +	/*
> +	 * 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.
> +	 */
> +	*src_pdo = 0;
> +	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;
> +			}
> +
> +			for (j = port->nr_fixed +
> +				 port->nr_batt +
> +				 port->nr_var;
> +			     j < port->nr_fixed +
> +				 port->nr_batt +
> +				 port->nr_var +
> +				 port->nr_apdo;
> +			     ++j) {
> +				if ((pdo_pps_apdo_min_voltage(pdo) >=
> +				     pdo_pps_apdo_min_voltage(port->snk_pdo[j])) &&
> +				    (pdo_pps_apdo_max_voltage(pdo) <=
> +				     pdo_pps_apdo_max_voltage(port->snk_pdo[j]))) {
> +					ma = min_pps_apdo_current(pdo,
> +								  port->snk_pdo[j]);
> +					mv = pdo_pps_apdo_max_voltage(pdo);
> +					mw = (ma * mv) / 1000;
> +					if ((mw > max_mw) ||
> +					    ((mw == max_mw) && (mv > max_mv))) {
> +						ret = 0;
> +						*src_pdo = i;
> +						*snk_pdo = j;
> +						max_mw = mw;
> +						max_mv = mv;
> +					}
> +				}
> +			}
> +
> +			break;
> +		default:
> +			tcpm_log(port, "Not APDO type, ignoring");
> +			continue;
> +		}
> +	}
> +
> +	if (*src_pdo > 0) {
> +		pdo = port->source_caps[*src_pdo];
> +
> +		port->pps_data.min_volt = pdo_pps_apdo_min_voltage(pdo);
> +		port->pps_data.max_volt = pdo_pps_apdo_max_voltage(pdo);
> +		port->pps_data.max_curr =
> +			min_pps_apdo_current(pdo, port->snk_pdo[*snk_pdo]);
> +		port->pps_data.out_volt =
> +			min(port->pps_data.out_volt, pdo_pps_apdo_max_voltage(pdo));
> +		port->pps_data.op_curr =
> +			min(port->pps_data.op_curr, pdo_pps_apdo_max_current(pdo));
> +	}
> +
> +	return ret;
> +}
> +
>  static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo)
>  {
>  	unsigned int mv, ma, mw, flags;
> @@ -1875,10 +2079,18 @@ static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo)
>  	matching_snk_pdo = port->snk_pdo[snk_pdo_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 sink pdo's limit */
>  	if (type == PDO_TYPE_BATT) {
> @@ -1943,6 +2155,107 @@ 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 out_mv, op_ma, op_mw, min_mv, max_mv, max_ma, flags;
> +	enum pd_pdo_type type;
> +	int src_pdo_index, snk_pdo_index;
> +	u32 pdo, matching_snk_pdo;
> +	int ret;
> +
> +	ret = tcpm_pd_select_pps_apdo(port, &snk_pdo_index, &src_pdo_index);
> +	if (ret)
> +		return ret;
> +
> +	pdo = port->source_caps[src_pdo_index];
> +	matching_snk_pdo = port->snk_pdo[snk_pdo_index];
> +	type = pdo_type(pdo);
> +
> +	switch (type) {
> +	case PDO_TYPE_APDO:
> +		if (pdo_apdo_type(pdo) != APDO_TYPE_PPS) {
> +			tcpm_log(port, "Invalid APDO selected!");
> +			return -EINVAL;
> +		}
> +		min_mv = pdo_pps_apdo_min_voltage(pdo);
> +		max_mv = pdo_pps_apdo_max_voltage(pdo);
> +		max_ma = pdo_pps_apdo_max_current(pdo);
> +		out_mv = port->pps_data.out_volt;
> +		op_ma = port->pps_data.op_curr;
> +		break;
> +	default:
> +		tcpm_log(port, "Invalid PDO selected!");
> +		return -EINVAL;
> +	}
> +
> +	flags = RDO_USB_COMM | RDO_NO_SUSPEND;
> +
> +	op_mw = (op_ma * out_mv) / 1000;
> +	if (op_mw < port->operating_snk_mw) {
> +		/*
> +		 * Try raising current to meet power needs. If that's not enough
> +		 * then try upping the voltage. If that's still not enoguh
> +		 * then we've obviously chosen a PPS APDO which really isn't
> +		 * suitable so abandon ship.
> +		 */
> +		op_ma = ((port->operating_snk_mw * 1000) / out_mv);
> +		if ((port->operating_snk_mw * 1000) % out_mv)
> +			++op_ma;
> +		op_ma += RDO_PROG_CURR_MA_STEP - (op_ma % RDO_PROG_CURR_MA_STEP);
> +
> +		if (op_ma > max_ma) {
> +			op_ma = max_ma;
> +			out_mv = ((port->operating_snk_mw * 1000) / op_ma);
> +			if ((port->operating_snk_mw * 1000) % op_ma)
> +				++out_mv;
> +			out_mv += RDO_PROG_VOLT_MV_STEP -
> +				  (out_mv % RDO_PROG_VOLT_MV_STEP);
> +
> +			if (out_mv > max_mv) {
> +				tcpm_log(port, "Invalid PPS APDO selected!");
> +				return -EINVAL;
> +			}
> +		}
> +	}
> +
> +	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(src_pdo_index + 1, out_mv, op_ma, flags);
> +
> +	tcpm_log(port, "Requesting APDO %d: %u mV, %u mA",
> +		 src_pdo_index, out_mv, op_ma);
> +
> +	port->pps_data.op_curr = op_ma;
> +	port->pps_data.out_volt = 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);
>  
> @@ -2128,6 +2441,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
> @@ -2143,6 +2457,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)
> @@ -2389,6 +2705,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;
> @@ -2449,6 +2766,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
> @@ -2472,6 +2790,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);
> @@ -2480,6 +2799,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 &&
> @@ -2561,6 +2881,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;
> @@ -2631,6 +2952,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),
> @@ -2638,6 +2977,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);
> @@ -2646,7 +2986,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 */
> @@ -2693,6 +3037,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);
> @@ -2913,6 +3258,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:
> @@ -3378,7 +3724,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;
> @@ -3423,7 +3769,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;
> @@ -3463,7 +3809,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;
> @@ -3495,6 +3841,162 @@ 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) {
> +		ret = -EINVAL;
> +		goto port_unlock;
> +	}
> +
> +	reinit_completion(&port->pps_complete);
> +	port->pps_data.op_curr = op_curr;
> +	port->pps_status = 0;
> +	port->pps_pending = true;
> +	tcpm_set_state(port, SNK_NEGOTIATE_PPS_CAPABILITIES, 0);

Why not just take the swap_lock here..

> +	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;

and you don't need that goto..

> +port_unlock:
> +	mutex_unlock(&port->lock);
> +swap_unlock:
> +	mutex_unlock(&port->swap_lock);

and this becomes..

	mutex_unlock(&port->swap_lock);
        return ret;

port_unlock:
	mutex_unlock(&port->lock);
        return ret;

> +	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;

Or, on top of what I said above, you could actually consider releasing
the port lock here and just returning. Then you would not need those
port_unlock and swap_unlock labels at all..

                mutex_unlock(&port->lock);
                return -EOPNOTSUPP;

> +	}
> +
> +	if (port->state != SNK_READY) {
> +		ret = -EAGAIN;
> +		goto port_unlock;

                mutex_unlock(&port->lock);
                return -EAGAIN;
> +	}
> +
> +	if ((out_volt < port->pps_data.min_volt) ||
> +	    (out_volt > port->pps_data.max_volt)) {
> +		ret = -EINVAL;
> +		goto port_unlock;

                mutex_unlock(&port->lock);
                return -EINVAL;

> +	}
> +
> +	target_mw = (port->pps_data.op_curr * out_volt) / 1000;
> +	if (target_mw < port->operating_snk_mw) {
> +		ret = -EINVAL;
> +		goto port_unlock;

                mutex_unlock(&port->lock);
                return -EINVAL;
> +	}
> +
> +	reinit_completion(&port->pps_complete);
> +	port->pps_data.out_volt = out_volt;
> +	port->pps_status = 0;
> +	port->pps_pending = true;
> +	tcpm_set_state(port, SNK_NEGOTIATE_PPS_CAPABILITIES, 0);

        mutex_lock(&port->swap_lock);

> +	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;

        mutex_unlock(&port->swap_lock);

        return ret;

> +	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) {
> +		port->pps_data.out_volt = port->supply_voltage;
> +		port->pps_data.op_curr = port->current_limit;
> +		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);

You can do the same as above here as well.

> +	return ret;
> +}
> +
>  static void tcpm_init(struct tcpm_port *port)
>  {
>  	enum typec_cc_status cc1, cc2;
> @@ -3634,13 +4136,18 @@ int 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;
> @@ -3695,6 +4202,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);
>  	tcpm_debugfs_init(port);
>  
>  	if (tcpm_validate_caps(port, tcpc->config->src_pdo,
> @@ -3717,6 +4225,9 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
>  	port->nr_batt = nr_type_pdos(port->snk_pdo,
>  				     port->nr_snk_pdo,
>  				     PDO_TYPE_BATT);
> +	port->nr_apdo = nr_type_pdos(port->snk_pdo,
> +				     port->nr_snk_pdo,
> +				     PDO_TYPE_APDO);
>  	port->nr_snk_vdo = tcpm_copy_vdos(port->snk_vdo, tcpc->config->snk_vdo,
>  					  tcpc->config->nr_snk_vdo);
>  
> @@ -3732,7 +4243,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/pd.h b/include/linux/usb/pd.h
> index ff359bdf..09b570f 100644
> --- a/include/linux/usb/pd.h
> +++ b/include/linux/usb/pd.h
> @@ -103,8 +103,8 @@ enum pd_ext_msg_type {
>  	 (((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), PD_REV20, (id), (cnt), (0)))
> +#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)
>  {
> diff --git a/include/linux/usb/tcpm.h b/include/linux/usb/tcpm.h
> index ca1c0b5..d6673f7 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	[flat|nested] 67+ messages in thread

* Re: [PATCH v4 5/7] power: supply: Add 'connected_type' property and supporting code
@ 2018-01-30 12:54     ` Heikki Krogerus
  0 siblings, 0 replies; 67+ messages in thread
From: Heikki Krogerus @ 2018-01-30 12:54 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, Jan 02, 2018 at 03:50:53PM +0000, Adam Thomson wrote:
> This commit adds the 'connected_type' property to represent supplies
> which can report a number of different types of supply based on a
> connection event.
> 
> Examples of this already exist in drivers whereby the existing 'type'
> property is updated, based on an event, to represent what was
> connected (e.g. USB_DCP, USB_ACA, ...). Current implementations
> however don't show all supported connectable types, so this knowledge
> has to be exlicitly known for each driver that supports this.
> 
> The 'connected_type' property is intended to fill this void and show
> users all possible types supported by a driver. The property, when
> read, shows all available types for the driver, and the one currently
> chosen is highlighted/bracketed. It is expected that the 'type'
> property would then just show the top-level type, such as 'USB', and
> this would be static.
> 
> Currently the 'conn_type' enum contains all of the USB variant types
> that exist for the 'type' enum at this time, and in addition has
> the PPS type. In the future this can be extended further for other
> types which have multiple connected types supported. The mirroring
> is intentional so as to not impact existing usage of the 'type'
> property.
> 
> Signed-off-by: Adam Thomson <Adam.Thomson.Opensource@diasemi.com>

Looks good to me:

Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>


> ---
>  drivers/power/supply/power_supply_sysfs.c | 50 +++++++++++++++++++++++++++++++
>  include/linux/power_supply.h              | 15 ++++++++++
>  2 files changed, 65 insertions(+)
> 
> diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c
> index 5204f11..1b3b202 100644
> --- a/drivers/power/supply/power_supply_sysfs.c
> +++ b/drivers/power/supply/power_supply_sysfs.c
> @@ -46,6 +46,11 @@
>  	"USB_PD", "USB_PD_DRP", "BrickID"
>  };
>  
> +static const char * const power_supply_conn_type_text[] = {
> +	"Unknown", "USB_DCP", "USB_CDP", "USB_ACA", "USB_C",
> +	"USB_PD", "USB_PD_DRP", "USB_PD_PPS", "BrickID"
> +};
> +
>  static const char * const power_supply_status_text[] = {
>  	"Unknown", "Charging", "Discharging", "Not charging", "Full"
>  };
> @@ -73,6 +78,46 @@
>  	"Unknown", "System", "Device"
>  };
>  
> +static ssize_t power_supply_show_conn_type(struct device *dev,
> +					   enum power_supply_conn_type *conn_types,
> +					   ssize_t num_conn_types,
> +					   union power_supply_propval *value,
> +					   char *buf)
> +{
> +	enum power_supply_conn_type conn_type;
> +	ssize_t count = 0;
> +	bool match = false;
> +	int i;
> +
> +	if ((!conn_types) || (num_conn_types <= 0)) {
> +		dev_warn(dev, "driver has no valid connected types\n");
> +		return -ENODATA;
> +	}
> +
> +	for (i = 0; i < num_conn_types; ++i) {
> +		conn_type = conn_types[i];
> +
> +		if (value->intval == conn_type) {
> +			count += sprintf(buf + count, "[%s] ",
> +					 power_supply_conn_type_text[conn_type]);
> +			match = true;
> +		} else {
> +			count += sprintf(buf + count, "%s ",
> +					 power_supply_conn_type_text[conn_type]);
> +		}
> +	}
> +
> +	if (!match) {
> +		dev_warn(dev, "driver reporting unsupported connected type\n");
> +		return -EINVAL;
> +	}
> +
> +	if (count)
> +		buf[count - 1] = '\n';
> +
> +	return count;
> +}
> +
>  static ssize_t power_supply_show_property(struct device *dev,
>  					  struct device_attribute *attr,
>  					  char *buf) {
> @@ -115,6 +160,10 @@ static ssize_t power_supply_show_property(struct device *dev,
>  	else if (off == POWER_SUPPLY_PROP_TYPE)
>  		return sprintf(buf, "%s\n",
>  			       power_supply_type_text[value.intval]);
> +	else if (off == POWER_SUPPLY_PROP_CONNECTED_TYPE)
> +		return power_supply_show_conn_type(dev, psy->desc->conn_types,
> +						   psy->desc->num_conn_types,
> +						   &value, buf);
>  	else if (off == POWER_SUPPLY_PROP_SCOPE)
>  		return sprintf(buf, "%s\n",
>  			       power_supply_scope_text[value.intval]);
> @@ -241,6 +290,7 @@ static ssize_t power_supply_store_property(struct device *dev,
>  	POWER_SUPPLY_ATTR(time_to_full_now),
>  	POWER_SUPPLY_ATTR(time_to_full_avg),
>  	POWER_SUPPLY_ATTR(type),
> +	POWER_SUPPLY_ATTR(connected_type),
>  	POWER_SUPPLY_ATTR(scope),
>  	POWER_SUPPLY_ATTR(precharge_current),
>  	POWER_SUPPLY_ATTR(charge_term_current),
> diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h
> index 79e90b3..e15a629 100644
> --- a/include/linux/power_supply.h
> +++ b/include/linux/power_supply.h
> @@ -145,6 +145,7 @@ enum power_supply_property {
>  	POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
>  	POWER_SUPPLY_PROP_TIME_TO_FULL_AVG,
>  	POWER_SUPPLY_PROP_TYPE, /* use power_supply.type instead */
> +	POWER_SUPPLY_PROP_CONNECTED_TYPE,
>  	POWER_SUPPLY_PROP_SCOPE,
>  	POWER_SUPPLY_PROP_PRECHARGE_CURRENT,
>  	POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
> @@ -170,6 +171,18 @@ enum power_supply_type {
>  	POWER_SUPPLY_TYPE_APPLE_BRICK_ID,	/* Apple Charging Method */
>  };
>  
> +enum power_supply_conn_type {
> +	POWER_SUPPLY_CONN_TYPE_UNKNOWN = 0,
> +	POWER_SUPPLY_CONN_TYPE_USB_DCP,		/* Dedicated Charging Port */
> +	POWER_SUPPLY_CONN_TYPE_USB_CDP,		/* Charging Downstream Port */
> +	POWER_SUPPLY_CONN_TYPE_USB_ACA,		/* Accessory Charger Adapters */
> +	POWER_SUPPLY_CONN_TYPE_USB_TYPE_C,	/* Type C Port */
> +	POWER_SUPPLY_CONN_TYPE_USB_PD,		/* Power Delivery Port */
> +	POWER_SUPPLY_CONN_TYPE_USB_PD_DRP,	/* PD Dual Role Port */
> +	POWER_SUPPLY_CONN_TYPE_USB_PD_PPS,	/* PD Programmable Power Supply */
> +	POWER_SUPPLY_CONN_TYPE_APPLE_BRICK_ID,	/* Apple Charging Method */
> +};
> +
>  enum power_supply_notifier_events {
>  	PSY_EVENT_PROP_CHANGED,
>  };
> @@ -196,6 +209,8 @@ struct power_supply_config {
>  struct power_supply_desc {
>  	const char *name;
>  	enum power_supply_type type;
> +	enum power_supply_conn_type *conn_types;
> +	size_t num_conn_types;
>  	enum power_supply_property *properties;
>  	size_t num_properties;
>  
> -- 
> 1.9.1

-- 
heikki

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

* [v4,5/7] power: supply: Add 'connected_type' property and supporting code
@ 2018-01-30 12:54     ` Heikki Krogerus
  0 siblings, 0 replies; 67+ messages in thread
From: Heikki Krogerus @ 2018-01-30 12:54 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, Jan 02, 2018 at 03:50:53PM +0000, Adam Thomson wrote:
> This commit adds the 'connected_type' property to represent supplies
> which can report a number of different types of supply based on a
> connection event.
> 
> Examples of this already exist in drivers whereby the existing 'type'
> property is updated, based on an event, to represent what was
> connected (e.g. USB_DCP, USB_ACA, ...). Current implementations
> however don't show all supported connectable types, so this knowledge
> has to be exlicitly known for each driver that supports this.
> 
> The 'connected_type' property is intended to fill this void and show
> users all possible types supported by a driver. The property, when
> read, shows all available types for the driver, and the one currently
> chosen is highlighted/bracketed. It is expected that the 'type'
> property would then just show the top-level type, such as 'USB', and
> this would be static.
> 
> Currently the 'conn_type' enum contains all of the USB variant types
> that exist for the 'type' enum at this time, and in addition has
> the PPS type. In the future this can be extended further for other
> types which have multiple connected types supported. The mirroring
> is intentional so as to not impact existing usage of the 'type'
> property.
> 
> Signed-off-by: Adam Thomson <Adam.Thomson.Opensource@diasemi.com>

Looks good to me:

Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>


> ---
>  drivers/power/supply/power_supply_sysfs.c | 50 +++++++++++++++++++++++++++++++
>  include/linux/power_supply.h              | 15 ++++++++++
>  2 files changed, 65 insertions(+)
> 
> diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c
> index 5204f11..1b3b202 100644
> --- a/drivers/power/supply/power_supply_sysfs.c
> +++ b/drivers/power/supply/power_supply_sysfs.c
> @@ -46,6 +46,11 @@
>  	"USB_PD", "USB_PD_DRP", "BrickID"
>  };
>  
> +static const char * const power_supply_conn_type_text[] = {
> +	"Unknown", "USB_DCP", "USB_CDP", "USB_ACA", "USB_C",
> +	"USB_PD", "USB_PD_DRP", "USB_PD_PPS", "BrickID"
> +};
> +
>  static const char * const power_supply_status_text[] = {
>  	"Unknown", "Charging", "Discharging", "Not charging", "Full"
>  };
> @@ -73,6 +78,46 @@
>  	"Unknown", "System", "Device"
>  };
>  
> +static ssize_t power_supply_show_conn_type(struct device *dev,
> +					   enum power_supply_conn_type *conn_types,
> +					   ssize_t num_conn_types,
> +					   union power_supply_propval *value,
> +					   char *buf)
> +{
> +	enum power_supply_conn_type conn_type;
> +	ssize_t count = 0;
> +	bool match = false;
> +	int i;
> +
> +	if ((!conn_types) || (num_conn_types <= 0)) {
> +		dev_warn(dev, "driver has no valid connected types\n");
> +		return -ENODATA;
> +	}
> +
> +	for (i = 0; i < num_conn_types; ++i) {
> +		conn_type = conn_types[i];
> +
> +		if (value->intval == conn_type) {
> +			count += sprintf(buf + count, "[%s] ",
> +					 power_supply_conn_type_text[conn_type]);
> +			match = true;
> +		} else {
> +			count += sprintf(buf + count, "%s ",
> +					 power_supply_conn_type_text[conn_type]);
> +		}
> +	}
> +
> +	if (!match) {
> +		dev_warn(dev, "driver reporting unsupported connected type\n");
> +		return -EINVAL;
> +	}
> +
> +	if (count)
> +		buf[count - 1] = '\n';
> +
> +	return count;
> +}
> +
>  static ssize_t power_supply_show_property(struct device *dev,
>  					  struct device_attribute *attr,
>  					  char *buf) {
> @@ -115,6 +160,10 @@ static ssize_t power_supply_show_property(struct device *dev,
>  	else if (off == POWER_SUPPLY_PROP_TYPE)
>  		return sprintf(buf, "%s\n",
>  			       power_supply_type_text[value.intval]);
> +	else if (off == POWER_SUPPLY_PROP_CONNECTED_TYPE)
> +		return power_supply_show_conn_type(dev, psy->desc->conn_types,
> +						   psy->desc->num_conn_types,
> +						   &value, buf);
>  	else if (off == POWER_SUPPLY_PROP_SCOPE)
>  		return sprintf(buf, "%s\n",
>  			       power_supply_scope_text[value.intval]);
> @@ -241,6 +290,7 @@ static ssize_t power_supply_store_property(struct device *dev,
>  	POWER_SUPPLY_ATTR(time_to_full_now),
>  	POWER_SUPPLY_ATTR(time_to_full_avg),
>  	POWER_SUPPLY_ATTR(type),
> +	POWER_SUPPLY_ATTR(connected_type),
>  	POWER_SUPPLY_ATTR(scope),
>  	POWER_SUPPLY_ATTR(precharge_current),
>  	POWER_SUPPLY_ATTR(charge_term_current),
> diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h
> index 79e90b3..e15a629 100644
> --- a/include/linux/power_supply.h
> +++ b/include/linux/power_supply.h
> @@ -145,6 +145,7 @@ enum power_supply_property {
>  	POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
>  	POWER_SUPPLY_PROP_TIME_TO_FULL_AVG,
>  	POWER_SUPPLY_PROP_TYPE, /* use power_supply.type instead */
> +	POWER_SUPPLY_PROP_CONNECTED_TYPE,
>  	POWER_SUPPLY_PROP_SCOPE,
>  	POWER_SUPPLY_PROP_PRECHARGE_CURRENT,
>  	POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
> @@ -170,6 +171,18 @@ enum power_supply_type {
>  	POWER_SUPPLY_TYPE_APPLE_BRICK_ID,	/* Apple Charging Method */
>  };
>  
> +enum power_supply_conn_type {
> +	POWER_SUPPLY_CONN_TYPE_UNKNOWN = 0,
> +	POWER_SUPPLY_CONN_TYPE_USB_DCP,		/* Dedicated Charging Port */
> +	POWER_SUPPLY_CONN_TYPE_USB_CDP,		/* Charging Downstream Port */
> +	POWER_SUPPLY_CONN_TYPE_USB_ACA,		/* Accessory Charger Adapters */
> +	POWER_SUPPLY_CONN_TYPE_USB_TYPE_C,	/* Type C Port */
> +	POWER_SUPPLY_CONN_TYPE_USB_PD,		/* Power Delivery Port */
> +	POWER_SUPPLY_CONN_TYPE_USB_PD_DRP,	/* PD Dual Role Port */
> +	POWER_SUPPLY_CONN_TYPE_USB_PD_PPS,	/* PD Programmable Power Supply */
> +	POWER_SUPPLY_CONN_TYPE_APPLE_BRICK_ID,	/* Apple Charging Method */
> +};
> +
>  enum power_supply_notifier_events {
>  	PSY_EVENT_PROP_CHANGED,
>  };
> @@ -196,6 +209,8 @@ struct power_supply_config {
>  struct power_supply_desc {
>  	const char *name;
>  	enum power_supply_type type;
> +	enum power_supply_conn_type *conn_types;
> +	size_t num_conn_types;
>  	enum power_supply_property *properties;
>  	size_t num_properties;
>  
> -- 
> 1.9.1

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

* Re: [PATCH v4 6/7] typec: tcpm: Represent source supply through power_supply class
@ 2018-01-30 13:11     ` Heikki Krogerus
  0 siblings, 0 replies; 67+ messages in thread
From: Heikki Krogerus @ 2018-01-30 13:11 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, Jan 02, 2018 at 03:50:54PM +0000, Adam Thomson wrote:
> 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>
> ---
>  .../ABI/testing/sysfs-class-power-tcpm-source-psy  |  92 ++++++++
>  drivers/usb/typec/Kconfig                          |   1 +
>  drivers/usb/typec/fusb302/Kconfig                  |   2 +-
>  drivers/usb/typec/fusb302/fusb302.c                |  63 +-----
>  drivers/usb/typec/tcpm.c                           | 233 ++++++++++++++++++++-
>  5 files changed, 328 insertions(+), 63 deletions(-)
>  create mode 100644 Documentation/ABI/testing/sysfs-class-power-tcpm-source-psy
> 
> diff --git a/Documentation/ABI/testing/sysfs-class-power-tcpm-source-psy b/Documentation/ABI/testing/sysfs-class-power-tcpm-source-psy
> new file mode 100644
> index 0000000..4986cba
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-class-power-tcpm-source-psy
> @@ -0,0 +1,92 @@
> +What: 		/sys/class/power_supply/tcpm-source-psy/type
> +Date:		December 2017
> +Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
> +Description:
> +	This read-only property describes the main type of source supply.
> +	Type-C is a USB standard so this property always returns "USB".
> +
> +What: 		/sys/class/power_supply/tcpm-source-psy/connected_type
> +Date:		December 2017
> +Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
> +Description:
> +	This read-only property describes the type of source supply that is
> +	connected, if the supply is online. The value is always Type C
> +	unless a source has been attached which is identified as USB-PD capable.
> +
> +	Valid values:
> +		- "USB_TYPE_C"	: Type C connected supply, not UBS-PD capable
> +				  (default value)
> +		- "USB_PD"	: USB-PD capable source supply connected
> +		- "USB_PD_PPS"	: USB-PD PPS capable source supply connected
> +
> +What: 		/sys/class/power_supply/tcpm-source-psy/online
> +Date:		December 2017
> +Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
> +Description:
> +	This read-write property describes the online state of the source
> +	supply. When the value of this property is not 0, and the supply allows
> +	it, then it's possible to switch between online states (i.e. 1 -> 2,
> +	2 -> 1)
> +
> +	Valid values:
> +		- 0	: Offline, no source supply attached
> +		- 1	: Fixed Online, Type-C or USB-PD capable supply
> +			  attached, non-configurable current and voltage
> +			  properties in this state.
> +		- 2	: PPS Online, USB-PD PPS feature enabled, 'current_now'
> +			  and 'voltage_now' properties can be modified in this
> +			  state. Re-writing of this value again, once already
> +			  set, will re-request the same configured voltage and
> +			  current values. This can be used as a keep-alive for
> +			  the PPS connection.
> +			  [NOTE: This is value only selectable if
> +			   'connected_type' reports a value of "USB_PD_PPS"]
> +
> +What: 		/sys/class/power_supply/tcpm-source-psy/voltage_min
> +Date:		December 2017
> +Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
> +Description:
> +	This read-only property describes the minimum voltage the source supply
> +	can provide.
> +
> +	Value in microvolts.
> +
> +What: 		/sys/class/power_supply/tcpm-source-psy/voltage_max
> +Date:		December 2017
> +Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
> +Description:
> +	This read-only property describes the maximum voltage the source supply
> +	can provide.
> +
> +	Value in microvolts.
> +
> +What: 		/sys/class/power_supply/tcpm-source-psy/voltage_now
> +Date:		December 2017
> +Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
> +Description:
> +	This read-write property describes the voltage the source supply is
> +	providing now. This property can only be written to if the source supply
> +	is in online state '2' (PPS enabled), otherwise it's read-only
> +	information.
> +
> +	Value in microvolts.
> +
> +What: 		/sys/class/power_supply/tcpm-source-psy/current_max
> +Date:		December 2017
> +Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
> +Description:
> +	This read-only property describes the maximum current the source supply
> +	can provide.
> +
> +	Value in microamps.
> +
> +What: 		/sys/class/power_supply/tcpm-source-psy/current_now
> +Date:		December 2017
> +Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
> +Description:
> +	This read-write property describes the current the source supply can
> +	provide now. This property can only be written to if the source supply
> +	is in online state '2' (PPS enabled), otherwise it's read-only
> +	information.
> +
> +	Value in microamps.

I think those should be documented for the entire psy class, not just
for this driver.

> diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig
> index bcb2744..1ef606d 100644
> --- a/drivers/usb/typec/Kconfig
> +++ b/drivers/usb/typec/Kconfig
> @@ -48,6 +48,7 @@ if TYPEC
>  config TYPEC_TCPM
>  	tristate "USB Type-C Port Controller Manager"
>  	depends on USB
> +	select POWER_SUPPLY

I'm a little bit uncomfortable with such a strong dependency on an
other subsystem that we may not always need, but let's see what
Guenter says.


Thanks,

-- 
heikki

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

* [v4,6/7] typec: tcpm: Represent source supply through power_supply class
@ 2018-01-30 13:11     ` Heikki Krogerus
  0 siblings, 0 replies; 67+ messages in thread
From: Heikki Krogerus @ 2018-01-30 13:11 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, Jan 02, 2018 at 03:50:54PM +0000, Adam Thomson wrote:
> 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>
> ---
>  .../ABI/testing/sysfs-class-power-tcpm-source-psy  |  92 ++++++++
>  drivers/usb/typec/Kconfig                          |   1 +
>  drivers/usb/typec/fusb302/Kconfig                  |   2 +-
>  drivers/usb/typec/fusb302/fusb302.c                |  63 +-----
>  drivers/usb/typec/tcpm.c                           | 233 ++++++++++++++++++++-
>  5 files changed, 328 insertions(+), 63 deletions(-)
>  create mode 100644 Documentation/ABI/testing/sysfs-class-power-tcpm-source-psy
> 
> diff --git a/Documentation/ABI/testing/sysfs-class-power-tcpm-source-psy b/Documentation/ABI/testing/sysfs-class-power-tcpm-source-psy
> new file mode 100644
> index 0000000..4986cba
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-class-power-tcpm-source-psy
> @@ -0,0 +1,92 @@
> +What: 		/sys/class/power_supply/tcpm-source-psy/type
> +Date:		December 2017
> +Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
> +Description:
> +	This read-only property describes the main type of source supply.
> +	Type-C is a USB standard so this property always returns "USB".
> +
> +What: 		/sys/class/power_supply/tcpm-source-psy/connected_type
> +Date:		December 2017
> +Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
> +Description:
> +	This read-only property describes the type of source supply that is
> +	connected, if the supply is online. The value is always Type C
> +	unless a source has been attached which is identified as USB-PD capable.
> +
> +	Valid values:
> +		- "USB_TYPE_C"	: Type C connected supply, not UBS-PD capable
> +				  (default value)
> +		- "USB_PD"	: USB-PD capable source supply connected
> +		- "USB_PD_PPS"	: USB-PD PPS capable source supply connected
> +
> +What: 		/sys/class/power_supply/tcpm-source-psy/online
> +Date:		December 2017
> +Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
> +Description:
> +	This read-write property describes the online state of the source
> +	supply. When the value of this property is not 0, and the supply allows
> +	it, then it's possible to switch between online states (i.e. 1 -> 2,
> +	2 -> 1)
> +
> +	Valid values:
> +		- 0	: Offline, no source supply attached
> +		- 1	: Fixed Online, Type-C or USB-PD capable supply
> +			  attached, non-configurable current and voltage
> +			  properties in this state.
> +		- 2	: PPS Online, USB-PD PPS feature enabled, 'current_now'
> +			  and 'voltage_now' properties can be modified in this
> +			  state. Re-writing of this value again, once already
> +			  set, will re-request the same configured voltage and
> +			  current values. This can be used as a keep-alive for
> +			  the PPS connection.
> +			  [NOTE: This is value only selectable if
> +			   'connected_type' reports a value of "USB_PD_PPS"]
> +
> +What: 		/sys/class/power_supply/tcpm-source-psy/voltage_min
> +Date:		December 2017
> +Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
> +Description:
> +	This read-only property describes the minimum voltage the source supply
> +	can provide.
> +
> +	Value in microvolts.
> +
> +What: 		/sys/class/power_supply/tcpm-source-psy/voltage_max
> +Date:		December 2017
> +Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
> +Description:
> +	This read-only property describes the maximum voltage the source supply
> +	can provide.
> +
> +	Value in microvolts.
> +
> +What: 		/sys/class/power_supply/tcpm-source-psy/voltage_now
> +Date:		December 2017
> +Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
> +Description:
> +	This read-write property describes the voltage the source supply is
> +	providing now. This property can only be written to if the source supply
> +	is in online state '2' (PPS enabled), otherwise it's read-only
> +	information.
> +
> +	Value in microvolts.
> +
> +What: 		/sys/class/power_supply/tcpm-source-psy/current_max
> +Date:		December 2017
> +Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
> +Description:
> +	This read-only property describes the maximum current the source supply
> +	can provide.
> +
> +	Value in microamps.
> +
> +What: 		/sys/class/power_supply/tcpm-source-psy/current_now
> +Date:		December 2017
> +Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
> +Description:
> +	This read-write property describes the current the source supply can
> +	provide now. This property can only be written to if the source supply
> +	is in online state '2' (PPS enabled), otherwise it's read-only
> +	information.
> +
> +	Value in microamps.

I think those should be documented for the entire psy class, not just
for this driver.

> diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig
> index bcb2744..1ef606d 100644
> --- a/drivers/usb/typec/Kconfig
> +++ b/drivers/usb/typec/Kconfig
> @@ -48,6 +48,7 @@ if TYPEC
>  config TYPEC_TCPM
>  	tristate "USB Type-C Port Controller Manager"
>  	depends on USB
> +	select POWER_SUPPLY

I'm a little bit uncomfortable with such a strong dependency on an
other subsystem that we may not always need, but let's see what
Guenter says.


Thanks,

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

* Re: [PATCH v4 7/7] typec: tcpm: Add support for sink PPS related messages
@ 2018-01-30 13:18     ` Heikki Krogerus
  0 siblings, 0 replies; 67+ messages in thread
From: Heikki Krogerus @ 2018-01-30 13: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, Jan 02, 2018 at 03:50:55PM +0000, Adam Thomson wrote:
> 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>

FWIW:

Acked-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>

-- 
heikki

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

* [v4,7/7] typec: tcpm: Add support for sink PPS related messages
@ 2018-01-30 13:18     ` Heikki Krogerus
  0 siblings, 0 replies; 67+ messages in thread
From: Heikki Krogerus @ 2018-01-30 13: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, Jan 02, 2018 at 03:50:55PM +0000, Adam Thomson wrote:
> 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>

FWIW:

Acked-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>

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

* Re: [PATCH v4 0/7] typec: tcpm: Add sink side support for PPS
  2018-01-02 15:50 ` Adam Thomson
                   ` (8 preceding siblings ...)
  (?)
@ 2018-01-30 13:25 ` Heikki Krogerus
  2018-01-30 14:27   ` Guenter Roeck
  2018-02-05 10:30   ` Adam Thomson
  -1 siblings, 2 replies; 67+ messages in thread
From: Heikki Krogerus @ 2018-01-30 13:25 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, Jan 02, 2018 at 03:50:48PM +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.

Sorry for the late reply. I don't have any major problems with these
other than with 6/7. The documentation should be for the psy class,
not tcpm. I'm also not comfortable with the "select POWER_SUPPLY", but
if Guenter does not think it's a problem, I'm fine with it. I guess we
can always, for example, introduce stubs for the power_supply*
functions, and drop the dependency later.

But as usual with tcpm.c, Guenter needs to give his ACK.

Oh yes, and Sebastian needs to ACK the power_supply changes or course.


Thanks,

-- 
heikki

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

* Re: [PATCH v4 0/7] typec: tcpm: Add sink side support for PPS
  2018-01-30 13:25 ` Heikki Krogerus
@ 2018-01-30 14:27   ` Guenter Roeck
  2018-02-05 10:30   ` Adam Thomson
  1 sibling, 0 replies; 67+ messages in thread
From: Guenter Roeck @ 2018-01-30 14:27 UTC (permalink / raw)
  To: Heikki Krogerus, Adam Thomson
  Cc: Greg Kroah-Hartman, Sebastian Reichel, Hans de Goede, Yueyao Zhu,
	Rui Miguel Silva, linux-usb, linux-pm, linux-kernel,
	support.opensource

On 01/30/2018 05:25 AM, Heikki Krogerus wrote:
> On Tue, Jan 02, 2018 at 03:50:48PM +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.
> 
> Sorry for the late reply. I don't have any major problems with these
> other than with 6/7. The documentation should be for the psy class,
> not tcpm. I'm also not comfortable with the "select POWER_SUPPLY", but
> if Guenter does not think it's a problem, I'm fine with it. I guess we

"select POWER_SUPPLY" seems to be quite widely used, so I don't have problems
with it as long as it is ok for PS maintainers.

> can always, for example, introduce stubs for the power_supply*
> functions, and drop the dependency later.
> 
That doesn't sound like a good idea unless generic stubs are already available.

> But as usual with tcpm.c, Guenter needs to give his ACK.
> 
I'll try to get to it this week.

Guenter

> Oh yes, and Sebastian needs to ACK the power_supply changes or course.
> 
> 
> Thanks,
> 


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

* RE: [PATCH v4 0/7] typec: tcpm: Add sink side support for PPS
  2018-01-30 13:25 ` Heikki Krogerus
  2018-01-30 14:27   ` Guenter Roeck
@ 2018-02-05 10:30   ` Adam Thomson
  1 sibling, 0 replies; 67+ messages in thread
From: Adam Thomson @ 2018-02-05 10:30 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 30 January 2018 13:25, Heikki Krogerus wrote:

> On Tue, Jan 02, 2018 at 03:50:48PM +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.
> 
> Sorry for the late reply. I don't have any major problems with these
> other than with 6/7. The documentation should be for the psy class,
> not tcpm. I'm also not comfortable with the "select POWER_SUPPLY", but
> if Guenter does not think it's a problem, I'm fine with it. I guess we
> can always, for example, introduce stubs for the power_supply*
> functions, and drop the dependency later.
> 
> But as usual with tcpm.c, Guenter needs to give his ACK.
> 
> Oh yes, and Sebastian needs to ACK the power_supply changes or course.

No problem. Thanks for your time reviewing this. Not the smallest patch set so
appreciate the effort spent. Was travelling last week, but should now have a bit
of free time to take a proper look at your comments and get back to you with
some meaningful responses.

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

* RE: [PATCH v4 1/7] typec: tcpm: Add PD Rev 3.0 definitions to PD header
@ 2018-02-06 14:14       ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 67+ messages in thread
From: Adam Thomson @ 2018-02-06 14:14 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 30 January 2018 12:22, Heikki Krogerus wrote:

> On Tue, Jan 02, 2018 at 03:50:49PM +0000, Adam Thomson wrote:
> > 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>
>
> Just one nitpick. I noticed that you are exceeding the 80 character
> limit per line in several places in this series, and I many cases it
> does not look like splitting the line would make the code any less
> readable.
>
> But I don't think that is critical, so if there are no other comments:
>
>
> Acked-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>

Thanks for the Ack. The file, before I updated it, already had lines like this
for similar definitions. With that in mind I'd erred on the side of style (and
readability, at least in my opinion :)) rather than dropping onto new lines as
they weren't going over by a great deal. However if there's a desire for this to
change then I can update accordingly.

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

* [v4,1/7] typec: tcpm: Add PD Rev 3.0 definitions to PD header
@ 2018-02-06 14:14       ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 67+ messages in thread
From: Opensource [Adam Thomson] @ 2018-02-06 14:14 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 30 January 2018 12:22, Heikki Krogerus wrote:

> On Tue, Jan 02, 2018 at 03:50:49PM +0000, Adam Thomson wrote:
> > 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>
>
> Just one nitpick. I noticed that you are exceeding the 80 character
> limit per line in several places in this series, and I many cases it
> does not look like splitting the line would make the code any less
> readable.
>
> But I don't think that is critical, so if there are no other comments:
>
>
> Acked-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>

Thanks for the Ack. The file, before I updated it, already had lines like this
for similar definitions. With that in mind I'd erred on the side of style (and
readability, at least in my opinion :)) rather than dropping onto new lines as
they weren't going over by a great deal. However if there's a desire for this to
change then I can update accordingly.
---
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* RE: [PATCH v4 4/7] typec: tcpm: Add core support for sink side PPS
@ 2018-02-06 14:33       ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 67+ messages in thread
From: Adam Thomson @ 2018-02-06 14:33 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 30 January 2018 12:47, Heikki Krogerus wrote:

> > +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) {
> > +		ret = -EINVAL;
> > +		goto port_unlock;
> > +	}
> > +
> > +	reinit_completion(&port->pps_complete);
> > +	port->pps_data.op_curr = op_curr;
> > +	port->pps_status = 0;
> > +	port->pps_pending = true;
> > +	tcpm_set_state(port, SNK_NEGOTIATE_PPS_CAPABILITIES, 0);
>
> Why not just take the swap_lock here..

I believe this would result in deadlock. All of the existing uses of swap_lock
acquire it first before the port->lock is then acquired (and vice-versa for
unlock). We don't want the power role to change during this procedure, so we
hold the swap_lock for the whole process. Have a look at tcpm_dr_set() and
tcpm_pr_set() as examples of existing usage.

> > +	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;
>
> Or, on top of what I said above, you could actually consider releasing
> the port lock here and just returning. Then you would not need those
> port_unlock and swap_unlock labels at all..
>
>                 mutex_unlock(&port->lock);
>                 return -EOPNOTSUPP;

Based on the comment above, I don't think this makes sense as you'd still need
to release the swap_lock as well, so there would be quite a lot of duplicated
code. Would prefer to stick with the present implementation.

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

* [v4,4/7] typec: tcpm: Add core support for sink side PPS
@ 2018-02-06 14:33       ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 67+ messages in thread
From: Opensource [Adam Thomson] @ 2018-02-06 14:33 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 30 January 2018 12:47, Heikki Krogerus wrote:

> > +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) {
> > +		ret = -EINVAL;
> > +		goto port_unlock;
> > +	}
> > +
> > +	reinit_completion(&port->pps_complete);
> > +	port->pps_data.op_curr = op_curr;
> > +	port->pps_status = 0;
> > +	port->pps_pending = true;
> > +	tcpm_set_state(port, SNK_NEGOTIATE_PPS_CAPABILITIES, 0);
>
> Why not just take the swap_lock here..

I believe this would result in deadlock. All of the existing uses of swap_lock
acquire it first before the port->lock is then acquired (and vice-versa for
unlock). We don't want the power role to change during this procedure, so we
hold the swap_lock for the whole process. Have a look at tcpm_dr_set() and
tcpm_pr_set() as examples of existing usage.

> > +	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;
>
> Or, on top of what I said above, you could actually consider releasing
> the port lock here and just returning. Then you would not need those
> port_unlock and swap_unlock labels at all..
>
>                 mutex_unlock(&port->lock);
>                 return -EOPNOTSUPP;

Based on the comment above, I don't think this makes sense as you'd still need
to release the swap_lock as well, so there would be quite a lot of duplicated
code. Would prefer to stick with the present implementation.
---
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* Re: [PATCH v4 4/7] typec: tcpm: Add core support for sink side PPS
@ 2018-02-06 14:53         ` Heikki Krogerus
  0 siblings, 0 replies; 67+ messages in thread
From: Heikki Krogerus @ 2018-02-06 14:53 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, Feb 06, 2018 at 02:33:08PM +0000, Adam Thomson wrote:
> On 30 January 2018 12:47, Heikki Krogerus wrote:
> 
> > > +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) {
> > > +		ret = -EINVAL;
> > > +		goto port_unlock;
> > > +	}
> > > +
> > > +	reinit_completion(&port->pps_complete);
> > > +	port->pps_data.op_curr = op_curr;
> > > +	port->pps_status = 0;
> > > +	port->pps_pending = true;
> > > +	tcpm_set_state(port, SNK_NEGOTIATE_PPS_CAPABILITIES, 0);
> >
> > Why not just take the swap_lock here..
> 
> I believe this would result in deadlock. All of the existing uses of swap_lock
> acquire it first before the port->lock is then acquired (and vice-versa for
> unlock). We don't want the power role to change during this procedure, so we
> hold the swap_lock for the whole process. Have a look at tcpm_dr_set() and
> tcpm_pr_set() as examples of existing usage.

OK. Then I'm fine with this patch as well. FWIW:

Acked-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>

-- 
heikki

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

* [v4,4/7] typec: tcpm: Add core support for sink side PPS
@ 2018-02-06 14:53         ` Heikki Krogerus
  0 siblings, 0 replies; 67+ messages in thread
From: Heikki Krogerus @ 2018-02-06 14:53 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, Feb 06, 2018 at 02:33:08PM +0000, Adam Thomson wrote:
> On 30 January 2018 12:47, Heikki Krogerus wrote:
> 
> > > +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) {
> > > +		ret = -EINVAL;
> > > +		goto port_unlock;
> > > +	}
> > > +
> > > +	reinit_completion(&port->pps_complete);
> > > +	port->pps_data.op_curr = op_curr;
> > > +	port->pps_status = 0;
> > > +	port->pps_pending = true;
> > > +	tcpm_set_state(port, SNK_NEGOTIATE_PPS_CAPABILITIES, 0);
> >
> > Why not just take the swap_lock here..
> 
> I believe this would result in deadlock. All of the existing uses of swap_lock
> acquire it first before the port->lock is then acquired (and vice-versa for
> unlock). We don't want the power role to change during this procedure, so we
> hold the swap_lock for the whole process. Have a look at tcpm_dr_set() and
> tcpm_pr_set() as examples of existing usage.

OK. Then I'm fine with this patch as well. FWIW:

Acked-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>

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

* RE: [PATCH v4 6/7] typec: tcpm: Represent source supply through power_supply class
@ 2018-02-06 15:51       ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 67+ messages in thread
From: Adam Thomson @ 2018-02-06 15:51 UTC (permalink / raw)
  To: Heikki Krogerus, Sebastian Reichel, Adam Thomson
  Cc: Guenter Roeck, Greg Kroah-Hartman, Hans de Goede, Yueyao Zhu,
	Rui Miguel Silva, linux-usb, linux-pm, linux-kernel,
	Support Opensource

On 30 January 2018 13:12, Heikki Krogerus wrote:

> Hi Adam,
>
> On Tue, Jan 02, 2018 at 03:50:54PM +0000, Adam Thomson wrote:
> > 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>
> > ---
> >  .../ABI/testing/sysfs-class-power-tcpm-source-psy  |  92 ++++++++
> >  drivers/usb/typec/Kconfig                          |   1 +
> >  drivers/usb/typec/fusb302/Kconfig                  |   2 +-
> >  drivers/usb/typec/fusb302/fusb302.c                |  63 +-----
> >  drivers/usb/typec/tcpm.c                           | 233 ++++++++++++++++++++-
> >  5 files changed, 328 insertions(+), 63 deletions(-)
> >  create mode 100644 Documentation/ABI/testing/sysfs-class-power-tcpm-source-
> psy
> >
> > diff --git a/Documentation/ABI/testing/sysfs-class-power-tcpm-source-psy
> b/Documentation/ABI/testing/sysfs-class-power-tcpm-source-psy
> > new file mode 100644
> > index 0000000..4986cba
> > --- /dev/null
> > +++ b/Documentation/ABI/testing/sysfs-class-power-tcpm-source-psy
> > @@ -0,0 +1,92 @@
> > +What: 		/sys/class/power_supply/tcpm-source-psy/type
> > +Date:		December 2017
> > +Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
> > +Description:
> > +	This read-only property describes the main type of source supply.
> > +	Type-C is a USB standard so this property always returns "USB".
> > +
> > +What: 		/sys/class/power_supply/tcpm-source-psy/connected_type
> > +Date:		December 2017
> > +Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
> > +Description:
> > +	This read-only property describes the type of source supply that is
> > +	connected, if the supply is online. The value is always Type C
> > +	unless a source has been attached which is identified as USB-PD capable.
> > +
> > +	Valid values:
> > +		- "USB_TYPE_C"	: Type C connected supply, not UBS-PD capable
> > +				  (default value)
> > +		- "USB_PD"	: USB-PD capable source supply connected
> > +		- "USB_PD_PPS"	: USB-PD PPS capable source supply connected
> > +
> > +What: 		/sys/class/power_supply/tcpm-source-psy/online
> > +Date:		December 2017
> > +Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
> > +Description:
> > +	This read-write property describes the online state of the source
> > +	supply. When the value of this property is not 0, and the supply allows
> > +	it, then it's possible to switch between online states (i.e. 1 -> 2,
> > +	2 -> 1)
> > +
> > +	Valid values:
> > +		- 0	: Offline, no source supply attached
> > +		- 1	: Fixed Online, Type-C or USB-PD capable supply
> > +			  attached, non-configurable current and voltage
> > +			  properties in this state.
> > +		- 2	: PPS Online, USB-PD PPS feature enabled, 'current_now'
> > +			  and 'voltage_now' properties can be modified in this
> > +			  state. Re-writing of this value again, once already
> > +			  set, will re-request the same configured voltage and
> > +			  current values. This can be used as a keep-alive for
> > +			  the PPS connection.
> > +			  [NOTE: This is value only selectable if
> > +			   'connected_type' reports a value of "USB_PD_PPS"]
> > +
> > +What: 		/sys/class/power_supply/tcpm-source-psy/voltage_min
> > +Date:		December 2017
> > +Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
> > +Description:
> > +	This read-only property describes the minimum voltage the source supply
> > +	can provide.
> > +
> > +	Value in microvolts.
> > +
> > +What: 		/sys/class/power_supply/tcpm-source-psy/voltage_max
> > +Date:		December 2017
> > +Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
> > +Description:
> > +	This read-only property describes the maximum voltage the source supply
> > +	can provide.
> > +
> > +	Value in microvolts.
> > +
> > +What: 		/sys/class/power_supply/tcpm-source-psy/voltage_now
> > +Date:		December 2017
> > +Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
> > +Description:
> > +	This read-write property describes the voltage the source supply is
> > +	providing now. This property can only be written to if the source supply
> > +	is in online state '2' (PPS enabled), otherwise it's read-only
> > +	information.
> > +
> > +	Value in microvolts.
> > +
> > +What: 		/sys/class/power_supply/tcpm-source-psy/current_max
> > +Date:		December 2017
> > +Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
> > +Description:
> > +	This read-only property describes the maximum current the source supply
> > +	can provide.
> > +
> > +	Value in microamps.
> > +
> > +What: 		/sys/class/power_supply/tcpm-source-psy/current_now
> > +Date:		December 2017
> > +Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
> > +Description:
> > +	This read-write property describes the current the source supply can
> > +	provide now. This property can only be written to if the source supply
> > +	is in online state '2' (PPS enabled), otherwise it's read-only
> > +	information.
> > +
> > +	Value in microamps.
>
> I think those should be documented for the entire psy class, not just
> for this driver.

Right now there is no documentation for the generic psy class. The stuff in
sysfs-class-power is device specific property information, and the same goes for
sysfs-class-power-twl4030. The property usage can vary depending on driver
implementation, an example being the 'online' property which can differ between
drivers, so the usage I have here is very much tcpm related. Also, the ability
to write to certain properties varies depending on the driver and HW, so here
where we configure 'voltage_now' and 'current_now', the likelihood is that most
other psy driver implementations won't allow for this.

Sebastian, do you have any thoughts on this discussion? Would be useful to get
your input here.

>
> > diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig
> > index bcb2744..1ef606d 100644
> > --- a/drivers/usb/typec/Kconfig
> > +++ b/drivers/usb/typec/Kconfig
> > @@ -48,6 +48,7 @@ if TYPEC
> >  config TYPEC_TCPM
> >  	tristate "USB Type-C Port Controller Manager"
> >  	depends on USB
> > +	select POWER_SUPPLY
>
> I'm a little bit uncomfortable with such a strong dependency on an
> other subsystem that we may not always need, but let's see what
> Guenter says.

Right now I've used the psy class to represent power information from the
external PD source supply, so for the scenarios where we're the source or a
non-PD device has been attached then this is not going to provide any
information to user-space. I guess in the future we could update to represent
information when we're the PD source as well, although that'll likely be all
just read-only info. Right now, not sure if this is something that would be
useful.

Sebastian, do you seen any problems on the dependency front?

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

* [v4,6/7] typec: tcpm: Represent source supply through power_supply class
@ 2018-02-06 15:51       ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 67+ messages in thread
From: Opensource [Adam Thomson] @ 2018-02-06 15:51 UTC (permalink / raw)
  To: Heikki Krogerus, Sebastian Reichel, Adam Thomson
  Cc: Guenter Roeck, Greg Kroah-Hartman, Hans de Goede, Yueyao Zhu,
	Rui Miguel Silva, linux-usb, linux-pm, linux-kernel,
	Support Opensource

On 30 January 2018 13:12, Heikki Krogerus wrote:

> Hi Adam,
>
> On Tue, Jan 02, 2018 at 03:50:54PM +0000, Adam Thomson wrote:
> > 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>
> > ---
> >  .../ABI/testing/sysfs-class-power-tcpm-source-psy  |  92 ++++++++
> >  drivers/usb/typec/Kconfig                          |   1 +
> >  drivers/usb/typec/fusb302/Kconfig                  |   2 +-
> >  drivers/usb/typec/fusb302/fusb302.c                |  63 +-----
> >  drivers/usb/typec/tcpm.c                           | 233 ++++++++++++++++++++-
> >  5 files changed, 328 insertions(+), 63 deletions(-)
> >  create mode 100644 Documentation/ABI/testing/sysfs-class-power-tcpm-source-
> psy
> >
> > diff --git a/Documentation/ABI/testing/sysfs-class-power-tcpm-source-psy
> b/Documentation/ABI/testing/sysfs-class-power-tcpm-source-psy
> > new file mode 100644
> > index 0000000..4986cba
> > --- /dev/null
> > +++ b/Documentation/ABI/testing/sysfs-class-power-tcpm-source-psy
> > @@ -0,0 +1,92 @@
> > +What: 		/sys/class/power_supply/tcpm-source-psy/type
> > +Date:		December 2017
> > +Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
> > +Description:
> > +	This read-only property describes the main type of source supply.
> > +	Type-C is a USB standard so this property always returns "USB".
> > +
> > +What: 		/sys/class/power_supply/tcpm-source-psy/connected_type
> > +Date:		December 2017
> > +Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
> > +Description:
> > +	This read-only property describes the type of source supply that is
> > +	connected, if the supply is online. The value is always Type C
> > +	unless a source has been attached which is identified as USB-PD capable.
> > +
> > +	Valid values:
> > +		- "USB_TYPE_C"	: Type C connected supply, not UBS-PD capable
> > +				  (default value)
> > +		- "USB_PD"	: USB-PD capable source supply connected
> > +		- "USB_PD_PPS"	: USB-PD PPS capable source supply connected
> > +
> > +What: 		/sys/class/power_supply/tcpm-source-psy/online
> > +Date:		December 2017
> > +Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
> > +Description:
> > +	This read-write property describes the online state of the source
> > +	supply. When the value of this property is not 0, and the supply allows
> > +	it, then it's possible to switch between online states (i.e. 1 -> 2,
> > +	2 -> 1)
> > +
> > +	Valid values:
> > +		- 0	: Offline, no source supply attached
> > +		- 1	: Fixed Online, Type-C or USB-PD capable supply
> > +			  attached, non-configurable current and voltage
> > +			  properties in this state.
> > +		- 2	: PPS Online, USB-PD PPS feature enabled, 'current_now'
> > +			  and 'voltage_now' properties can be modified in this
> > +			  state. Re-writing of this value again, once already
> > +			  set, will re-request the same configured voltage and
> > +			  current values. This can be used as a keep-alive for
> > +			  the PPS connection.
> > +			  [NOTE: This is value only selectable if
> > +			   'connected_type' reports a value of "USB_PD_PPS"]
> > +
> > +What: 		/sys/class/power_supply/tcpm-source-psy/voltage_min
> > +Date:		December 2017
> > +Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
> > +Description:
> > +	This read-only property describes the minimum voltage the source supply
> > +	can provide.
> > +
> > +	Value in microvolts.
> > +
> > +What: 		/sys/class/power_supply/tcpm-source-psy/voltage_max
> > +Date:		December 2017
> > +Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
> > +Description:
> > +	This read-only property describes the maximum voltage the source supply
> > +	can provide.
> > +
> > +	Value in microvolts.
> > +
> > +What: 		/sys/class/power_supply/tcpm-source-psy/voltage_now
> > +Date:		December 2017
> > +Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
> > +Description:
> > +	This read-write property describes the voltage the source supply is
> > +	providing now. This property can only be written to if the source supply
> > +	is in online state '2' (PPS enabled), otherwise it's read-only
> > +	information.
> > +
> > +	Value in microvolts.
> > +
> > +What: 		/sys/class/power_supply/tcpm-source-psy/current_max
> > +Date:		December 2017
> > +Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
> > +Description:
> > +	This read-only property describes the maximum current the source supply
> > +	can provide.
> > +
> > +	Value in microamps.
> > +
> > +What: 		/sys/class/power_supply/tcpm-source-psy/current_now
> > +Date:		December 2017
> > +Contact:	Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
> > +Description:
> > +	This read-write property describes the current the source supply can
> > +	provide now. This property can only be written to if the source supply
> > +	is in online state '2' (PPS enabled), otherwise it's read-only
> > +	information.
> > +
> > +	Value in microamps.
>
> I think those should be documented for the entire psy class, not just
> for this driver.

Right now there is no documentation for the generic psy class. The stuff in
sysfs-class-power is device specific property information, and the same goes for
sysfs-class-power-twl4030. The property usage can vary depending on driver
implementation, an example being the 'online' property which can differ between
drivers, so the usage I have here is very much tcpm related. Also, the ability
to write to certain properties varies depending on the driver and HW, so here
where we configure 'voltage_now' and 'current_now', the likelihood is that most
other psy driver implementations won't allow for this.

Sebastian, do you have any thoughts on this discussion? Would be useful to get
your input here.

>
> > diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig
> > index bcb2744..1ef606d 100644
> > --- a/drivers/usb/typec/Kconfig
> > +++ b/drivers/usb/typec/Kconfig
> > @@ -48,6 +48,7 @@ if TYPEC
> >  config TYPEC_TCPM
> >  	tristate "USB Type-C Port Controller Manager"
> >  	depends on USB
> > +	select POWER_SUPPLY
>
> I'm a little bit uncomfortable with such a strong dependency on an
> other subsystem that we may not always need, but let's see what
> Guenter says.

Right now I've used the psy class to represent power information from the
external PD source supply, so for the scenarios where we're the source or a
non-PD device has been attached then this is not going to provide any
information to user-space. I guess in the future we could update to represent
information when we're the PD source as well, although that'll likely be all
just read-only info. Right now, not sure if this is something that would be
useful.

Sebastian, do you seen any problems on the dependency front?
---
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* Re: [PATCH v4 6/7] typec: tcpm: Represent source supply through power_supply class
@ 2018-02-08 10:45         ` Heikki Krogerus
  0 siblings, 0 replies; 67+ messages in thread
From: Heikki Krogerus @ 2018-02-08 10:45 UTC (permalink / raw)
  To: Adam Thomson
  Cc: Sebastian Reichel, Guenter Roeck, Greg Kroah-Hartman,
	Hans de Goede, Yueyao Zhu, Rui Miguel Silva, linux-usb, linux-pm,
	linux-kernel, Support Opensource

Hi Adam,

On Tue, Feb 06, 2018 at 03:51:26PM +0000, Adam Thomson wrote:
> Right now there is no documentation for the generic psy class. The stuff in
> sysfs-class-power is device specific property information, and the same goes for
> sysfs-class-power-twl4030. The property usage can vary depending on driver
> implementation, an example being the 'online' property which can differ between
> drivers, so the usage I have here is very much tcpm related. Also, the ability
> to write to certain properties varies depending on the driver and HW, so here
> where we configure 'voltage_now' and 'current_now', the likelihood is that most
> other psy driver implementations won't allow for this.

The power supply class is missing documentation, YES! That is what I
have been saying! The fact that even an attribute like "online" can
mean different things depending on the driver is absolutely horrible.
The ABI documentation FOR THE POWER SUPPLY CLASS needs to provide
clear meaning for the attributes. It needs to also point out which
attributes can be hidden, and it should also give some hints for
things like which attributes can be expected to be visible for example
in case of USB type of psy and so on.

We are talking about user space ABI for power supplies here. The user
space does not know that its dealing with tcpm in this case, or some
other driver in an other case, AND, the user space _must_ not be
expected to know that kind of details. The behaviour and meaning of an
individual attribute file quite simply has to be the same, always,
regardless of the platform, HW, driver or whatever. Otherwise this
whole ABI is completely useless.

Working around the issue of missing guidelines and documentation for
subsystem ABI by providing it for the device drivers instead is not
acceptable. If you don't want to propose documentation for the class,
don't propose any documentation at all is better answer then that. And
using arguments like "well, twl4030 did it" is really starting to
annoy me. We are not lemmings here. We can make this right instead of
following others blindly over the cliff edge.

To summarize: We can't just accept chaos. Instead we should organize
the places without structure, in this case the user space ABI for
power supplies.

On top of ABI documentation, we will need driver API documentation as
well. I'm not expecting that you would also propose something for the
API too, but I just wanted to bring that up here. I would like to have
some guidelines on how the power supplies should be used also in
kernel.

Right now it is possible for one driver to create the power supply and
an other to take over the control of it. It is super easy to gain
access to a power supply. You can request it with just the name
without any control, and after gaining access, you have full control
over it. That makes it really easy to have race condition where both
the psy device driver and some other driver try to control the same
things of the same psy.

I guess the whole design of the psy class could use a little bit of
re-designing. So IMO, access to the psy should be more strict then it
is now, and also, even after gaining access to a psy handle, drivers
that are not the actual psy device drivers should have more controlled
access to it. So possibly separate API for them... OK, this is
definitely a separate topic. Sorry, I'll stop here.


Thanks,

-- 
heikki

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

* [v4,6/7] typec: tcpm: Represent source supply through power_supply class
@ 2018-02-08 10:45         ` Heikki Krogerus
  0 siblings, 0 replies; 67+ messages in thread
From: Heikki Krogerus @ 2018-02-08 10:45 UTC (permalink / raw)
  To: Adam Thomson
  Cc: Sebastian Reichel, Guenter Roeck, Greg Kroah-Hartman,
	Hans de Goede, Yueyao Zhu, Rui Miguel Silva, linux-usb, linux-pm,
	linux-kernel, Support Opensource

Hi Adam,

On Tue, Feb 06, 2018 at 03:51:26PM +0000, Adam Thomson wrote:
> Right now there is no documentation for the generic psy class. The stuff in
> sysfs-class-power is device specific property information, and the same goes for
> sysfs-class-power-twl4030. The property usage can vary depending on driver
> implementation, an example being the 'online' property which can differ between
> drivers, so the usage I have here is very much tcpm related. Also, the ability
> to write to certain properties varies depending on the driver and HW, so here
> where we configure 'voltage_now' and 'current_now', the likelihood is that most
> other psy driver implementations won't allow for this.

The power supply class is missing documentation, YES! That is what I
have been saying! The fact that even an attribute like "online" can
mean different things depending on the driver is absolutely horrible.
The ABI documentation FOR THE POWER SUPPLY CLASS needs to provide
clear meaning for the attributes. It needs to also point out which
attributes can be hidden, and it should also give some hints for
things like which attributes can be expected to be visible for example
in case of USB type of psy and so on.

We are talking about user space ABI for power supplies here. The user
space does not know that its dealing with tcpm in this case, or some
other driver in an other case, AND, the user space _must_ not be
expected to know that kind of details. The behaviour and meaning of an
individual attribute file quite simply has to be the same, always,
regardless of the platform, HW, driver or whatever. Otherwise this
whole ABI is completely useless.

Working around the issue of missing guidelines and documentation for
subsystem ABI by providing it for the device drivers instead is not
acceptable. If you don't want to propose documentation for the class,
don't propose any documentation at all is better answer then that. And
using arguments like "well, twl4030 did it" is really starting to
annoy me. We are not lemmings here. We can make this right instead of
following others blindly over the cliff edge.

To summarize: We can't just accept chaos. Instead we should organize
the places without structure, in this case the user space ABI for
power supplies.

On top of ABI documentation, we will need driver API documentation as
well. I'm not expecting that you would also propose something for the
API too, but I just wanted to bring that up here. I would like to have
some guidelines on how the power supplies should be used also in
kernel.

Right now it is possible for one driver to create the power supply and
an other to take over the control of it. It is super easy to gain
access to a power supply. You can request it with just the name
without any control, and after gaining access, you have full control
over it. That makes it really easy to have race condition where both
the psy device driver and some other driver try to control the same
things of the same psy.

I guess the whole design of the psy class could use a little bit of
re-designing. So IMO, access to the psy should be more strict then it
is now, and also, even after gaining access to a psy handle, drivers
that are not the actual psy device drivers should have more controlled
access to it. So possibly separate API for them... OK, this is
definitely a separate topic. Sorry, I'll stop here.


Thanks,

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

* Re: [PATCH v4 5/7] power: supply: Add 'connected_type' property and supporting code
@ 2018-02-08 21:30     ` Sebastian Reichel
  0 siblings, 0 replies; 67+ messages in thread
From: Sebastian Reichel @ 2018-02-08 21:30 UTC (permalink / raw)
  To: Adam Thomson
  Cc: Heikki Krogerus, Guenter Roeck, Greg Kroah-Hartman,
	Hans de Goede, Yueyao Zhu, Rui Miguel Silva, linux-usb, linux-pm,
	linux-kernel, support.opensource

[-- Attachment #1: Type: text/plain, Size: 6352 bytes --]

Hi,

On Tue, Jan 02, 2018 at 03:50:53PM +0000, Adam Thomson wrote:
> This commit adds the 'connected_type' property to represent supplies
> which can report a number of different types of supply based on a
> connection event.
> 
> Examples of this already exist in drivers whereby the existing 'type'
> property is updated, based on an event, to represent what was
> connected (e.g. USB_DCP, USB_ACA, ...). Current implementations
> however don't show all supported connectable types, so this knowledge
> has to be exlicitly known for each driver that supports this.
> 
> The 'connected_type' property is intended to fill this void and show
> users all possible types supported by a driver. The property, when
> read, shows all available types for the driver, and the one currently
> chosen is highlighted/bracketed. It is expected that the 'type'
> property would then just show the top-level type, such as 'USB', and
> this would be static.
> 
> Currently the 'conn_type' enum contains all of the USB variant types
> that exist for the 'type' enum at this time, and in addition has
> the PPS type. In the future this can be extended further for other
> types which have multiple connected types supported. The mirroring
> is intentional so as to not impact existing usage of the 'type'
> property.
> 
> Signed-off-by: Adam Thomson <Adam.Thomson.Opensource@diasemi.com>

Thanks for the patch. I think, that it's a good idea to provide the
subtype in its own property and just set the type property to "USB".
I would prefer to name this "usb_type". Otherwise

Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.co.uk>

-- Sebastian

> ---
>  drivers/power/supply/power_supply_sysfs.c | 50 +++++++++++++++++++++++++++++++
>  include/linux/power_supply.h              | 15 ++++++++++
>  2 files changed, 65 insertions(+)
> 
> diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c
> index 5204f11..1b3b202 100644
> --- a/drivers/power/supply/power_supply_sysfs.c
> +++ b/drivers/power/supply/power_supply_sysfs.c
> @@ -46,6 +46,11 @@
>  	"USB_PD", "USB_PD_DRP", "BrickID"
>  };
>  
> +static const char * const power_supply_conn_type_text[] = {
> +	"Unknown", "USB_DCP", "USB_CDP", "USB_ACA", "USB_C",
> +	"USB_PD", "USB_PD_DRP", "USB_PD_PPS", "BrickID"
> +};
> +
>  static const char * const power_supply_status_text[] = {
>  	"Unknown", "Charging", "Discharging", "Not charging", "Full"
>  };
> @@ -73,6 +78,46 @@
>  	"Unknown", "System", "Device"
>  };
>  
> +static ssize_t power_supply_show_conn_type(struct device *dev,
> +					   enum power_supply_conn_type *conn_types,
> +					   ssize_t num_conn_types,
> +					   union power_supply_propval *value,
> +					   char *buf)
> +{
> +	enum power_supply_conn_type conn_type;
> +	ssize_t count = 0;
> +	bool match = false;
> +	int i;
> +
> +	if ((!conn_types) || (num_conn_types <= 0)) {
> +		dev_warn(dev, "driver has no valid connected types\n");
> +		return -ENODATA;
> +	}
> +
> +	for (i = 0; i < num_conn_types; ++i) {
> +		conn_type = conn_types[i];
> +
> +		if (value->intval == conn_type) {
> +			count += sprintf(buf + count, "[%s] ",
> +					 power_supply_conn_type_text[conn_type]);
> +			match = true;
> +		} else {
> +			count += sprintf(buf + count, "%s ",
> +					 power_supply_conn_type_text[conn_type]);
> +		}
> +	}
> +
> +	if (!match) {
> +		dev_warn(dev, "driver reporting unsupported connected type\n");
> +		return -EINVAL;
> +	}
> +
> +	if (count)
> +		buf[count - 1] = '\n';
> +
> +	return count;
> +}
> +
>  static ssize_t power_supply_show_property(struct device *dev,
>  					  struct device_attribute *attr,
>  					  char *buf) {
> @@ -115,6 +160,10 @@ static ssize_t power_supply_show_property(struct device *dev,
>  	else if (off == POWER_SUPPLY_PROP_TYPE)
>  		return sprintf(buf, "%s\n",
>  			       power_supply_type_text[value.intval]);
> +	else if (off == POWER_SUPPLY_PROP_CONNECTED_TYPE)
> +		return power_supply_show_conn_type(dev, psy->desc->conn_types,
> +						   psy->desc->num_conn_types,
> +						   &value, buf);
>  	else if (off == POWER_SUPPLY_PROP_SCOPE)
>  		return sprintf(buf, "%s\n",
>  			       power_supply_scope_text[value.intval]);
> @@ -241,6 +290,7 @@ static ssize_t power_supply_store_property(struct device *dev,
>  	POWER_SUPPLY_ATTR(time_to_full_now),
>  	POWER_SUPPLY_ATTR(time_to_full_avg),
>  	POWER_SUPPLY_ATTR(type),
> +	POWER_SUPPLY_ATTR(connected_type),
>  	POWER_SUPPLY_ATTR(scope),
>  	POWER_SUPPLY_ATTR(precharge_current),
>  	POWER_SUPPLY_ATTR(charge_term_current),
> diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h
> index 79e90b3..e15a629 100644
> --- a/include/linux/power_supply.h
> +++ b/include/linux/power_supply.h
> @@ -145,6 +145,7 @@ enum power_supply_property {
>  	POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
>  	POWER_SUPPLY_PROP_TIME_TO_FULL_AVG,
>  	POWER_SUPPLY_PROP_TYPE, /* use power_supply.type instead */
> +	POWER_SUPPLY_PROP_CONNECTED_TYPE,
>  	POWER_SUPPLY_PROP_SCOPE,
>  	POWER_SUPPLY_PROP_PRECHARGE_CURRENT,
>  	POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
> @@ -170,6 +171,18 @@ enum power_supply_type {
>  	POWER_SUPPLY_TYPE_APPLE_BRICK_ID,	/* Apple Charging Method */
>  };
>  
> +enum power_supply_conn_type {
> +	POWER_SUPPLY_CONN_TYPE_UNKNOWN = 0,
> +	POWER_SUPPLY_CONN_TYPE_USB_DCP,		/* Dedicated Charging Port */
> +	POWER_SUPPLY_CONN_TYPE_USB_CDP,		/* Charging Downstream Port */
> +	POWER_SUPPLY_CONN_TYPE_USB_ACA,		/* Accessory Charger Adapters */
> +	POWER_SUPPLY_CONN_TYPE_USB_TYPE_C,	/* Type C Port */
> +	POWER_SUPPLY_CONN_TYPE_USB_PD,		/* Power Delivery Port */
> +	POWER_SUPPLY_CONN_TYPE_USB_PD_DRP,	/* PD Dual Role Port */
> +	POWER_SUPPLY_CONN_TYPE_USB_PD_PPS,	/* PD Programmable Power Supply */
> +	POWER_SUPPLY_CONN_TYPE_APPLE_BRICK_ID,	/* Apple Charging Method */
> +};
> +
>  enum power_supply_notifier_events {
>  	PSY_EVENT_PROP_CHANGED,
>  };
> @@ -196,6 +209,8 @@ struct power_supply_config {
>  struct power_supply_desc {
>  	const char *name;
>  	enum power_supply_type type;
> +	enum power_supply_conn_type *conn_types;
> +	size_t num_conn_types;
>  	enum power_supply_property *properties;
>  	size_t num_properties;
>  
> -- 
> 1.9.1
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* [v4,5/7] power: supply: Add 'connected_type' property and supporting code
@ 2018-02-08 21:30     ` Sebastian Reichel
  0 siblings, 0 replies; 67+ messages in thread
From: Sebastian Reichel @ 2018-02-08 21:30 UTC (permalink / raw)
  To: Adam Thomson
  Cc: Heikki Krogerus, Guenter Roeck, Greg Kroah-Hartman,
	Hans de Goede, Yueyao Zhu, Rui Miguel Silva, linux-usb, linux-pm,
	linux-kernel, support.opensource

Hi,

On Tue, Jan 02, 2018 at 03:50:53PM +0000, Adam Thomson wrote:
> This commit adds the 'connected_type' property to represent supplies
> which can report a number of different types of supply based on a
> connection event.
> 
> Examples of this already exist in drivers whereby the existing 'type'
> property is updated, based on an event, to represent what was
> connected (e.g. USB_DCP, USB_ACA, ...). Current implementations
> however don't show all supported connectable types, so this knowledge
> has to be exlicitly known for each driver that supports this.
> 
> The 'connected_type' property is intended to fill this void and show
> users all possible types supported by a driver. The property, when
> read, shows all available types for the driver, and the one currently
> chosen is highlighted/bracketed. It is expected that the 'type'
> property would then just show the top-level type, such as 'USB', and
> this would be static.
> 
> Currently the 'conn_type' enum contains all of the USB variant types
> that exist for the 'type' enum at this time, and in addition has
> the PPS type. In the future this can be extended further for other
> types which have multiple connected types supported. The mirroring
> is intentional so as to not impact existing usage of the 'type'
> property.
> 
> Signed-off-by: Adam Thomson <Adam.Thomson.Opensource@diasemi.com>

Thanks for the patch. I think, that it's a good idea to provide the
subtype in its own property and just set the type property to "USB".
I would prefer to name this "usb_type". Otherwise

Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.co.uk>

-- Sebastian

> ---
>  drivers/power/supply/power_supply_sysfs.c | 50 +++++++++++++++++++++++++++++++
>  include/linux/power_supply.h              | 15 ++++++++++
>  2 files changed, 65 insertions(+)
> 
> diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c
> index 5204f11..1b3b202 100644
> --- a/drivers/power/supply/power_supply_sysfs.c
> +++ b/drivers/power/supply/power_supply_sysfs.c
> @@ -46,6 +46,11 @@
>  	"USB_PD", "USB_PD_DRP", "BrickID"
>  };
>  
> +static const char * const power_supply_conn_type_text[] = {
> +	"Unknown", "USB_DCP", "USB_CDP", "USB_ACA", "USB_C",
> +	"USB_PD", "USB_PD_DRP", "USB_PD_PPS", "BrickID"
> +};
> +
>  static const char * const power_supply_status_text[] = {
>  	"Unknown", "Charging", "Discharging", "Not charging", "Full"
>  };
> @@ -73,6 +78,46 @@
>  	"Unknown", "System", "Device"
>  };
>  
> +static ssize_t power_supply_show_conn_type(struct device *dev,
> +					   enum power_supply_conn_type *conn_types,
> +					   ssize_t num_conn_types,
> +					   union power_supply_propval *value,
> +					   char *buf)
> +{
> +	enum power_supply_conn_type conn_type;
> +	ssize_t count = 0;
> +	bool match = false;
> +	int i;
> +
> +	if ((!conn_types) || (num_conn_types <= 0)) {
> +		dev_warn(dev, "driver has no valid connected types\n");
> +		return -ENODATA;
> +	}
> +
> +	for (i = 0; i < num_conn_types; ++i) {
> +		conn_type = conn_types[i];
> +
> +		if (value->intval == conn_type) {
> +			count += sprintf(buf + count, "[%s] ",
> +					 power_supply_conn_type_text[conn_type]);
> +			match = true;
> +		} else {
> +			count += sprintf(buf + count, "%s ",
> +					 power_supply_conn_type_text[conn_type]);
> +		}
> +	}
> +
> +	if (!match) {
> +		dev_warn(dev, "driver reporting unsupported connected type\n");
> +		return -EINVAL;
> +	}
> +
> +	if (count)
> +		buf[count - 1] = '\n';
> +
> +	return count;
> +}
> +
>  static ssize_t power_supply_show_property(struct device *dev,
>  					  struct device_attribute *attr,
>  					  char *buf) {
> @@ -115,6 +160,10 @@ static ssize_t power_supply_show_property(struct device *dev,
>  	else if (off == POWER_SUPPLY_PROP_TYPE)
>  		return sprintf(buf, "%s\n",
>  			       power_supply_type_text[value.intval]);
> +	else if (off == POWER_SUPPLY_PROP_CONNECTED_TYPE)
> +		return power_supply_show_conn_type(dev, psy->desc->conn_types,
> +						   psy->desc->num_conn_types,
> +						   &value, buf);
>  	else if (off == POWER_SUPPLY_PROP_SCOPE)
>  		return sprintf(buf, "%s\n",
>  			       power_supply_scope_text[value.intval]);
> @@ -241,6 +290,7 @@ static ssize_t power_supply_store_property(struct device *dev,
>  	POWER_SUPPLY_ATTR(time_to_full_now),
>  	POWER_SUPPLY_ATTR(time_to_full_avg),
>  	POWER_SUPPLY_ATTR(type),
> +	POWER_SUPPLY_ATTR(connected_type),
>  	POWER_SUPPLY_ATTR(scope),
>  	POWER_SUPPLY_ATTR(precharge_current),
>  	POWER_SUPPLY_ATTR(charge_term_current),
> diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h
> index 79e90b3..e15a629 100644
> --- a/include/linux/power_supply.h
> +++ b/include/linux/power_supply.h
> @@ -145,6 +145,7 @@ enum power_supply_property {
>  	POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
>  	POWER_SUPPLY_PROP_TIME_TO_FULL_AVG,
>  	POWER_SUPPLY_PROP_TYPE, /* use power_supply.type instead */
> +	POWER_SUPPLY_PROP_CONNECTED_TYPE,
>  	POWER_SUPPLY_PROP_SCOPE,
>  	POWER_SUPPLY_PROP_PRECHARGE_CURRENT,
>  	POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
> @@ -170,6 +171,18 @@ enum power_supply_type {
>  	POWER_SUPPLY_TYPE_APPLE_BRICK_ID,	/* Apple Charging Method */
>  };
>  
> +enum power_supply_conn_type {
> +	POWER_SUPPLY_CONN_TYPE_UNKNOWN = 0,
> +	POWER_SUPPLY_CONN_TYPE_USB_DCP,		/* Dedicated Charging Port */
> +	POWER_SUPPLY_CONN_TYPE_USB_CDP,		/* Charging Downstream Port */
> +	POWER_SUPPLY_CONN_TYPE_USB_ACA,		/* Accessory Charger Adapters */
> +	POWER_SUPPLY_CONN_TYPE_USB_TYPE_C,	/* Type C Port */
> +	POWER_SUPPLY_CONN_TYPE_USB_PD,		/* Power Delivery Port */
> +	POWER_SUPPLY_CONN_TYPE_USB_PD_DRP,	/* PD Dual Role Port */
> +	POWER_SUPPLY_CONN_TYPE_USB_PD_PPS,	/* PD Programmable Power Supply */
> +	POWER_SUPPLY_CONN_TYPE_APPLE_BRICK_ID,	/* Apple Charging Method */
> +};
> +
>  enum power_supply_notifier_events {
>  	PSY_EVENT_PROP_CHANGED,
>  };
> @@ -196,6 +209,8 @@ struct power_supply_config {
>  struct power_supply_desc {
>  	const char *name;
>  	enum power_supply_type type;
> +	enum power_supply_conn_type *conn_types;
> +	size_t num_conn_types;
>  	enum power_supply_property *properties;
>  	size_t num_properties;
>  
> -- 
> 1.9.1
>

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

* RE: [PATCH v4 5/7] power: supply: Add 'connected_type' property and supporting code
@ 2018-02-09 11:28       ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 67+ messages in thread
From: Adam Thomson @ 2018-02-09 11:28 UTC (permalink / raw)
  To: Sebastian Reichel, Adam Thomson
  Cc: Heikki Krogerus, Guenter Roeck, Greg Kroah-Hartman,
	Hans de Goede, Yueyao Zhu, Rui Miguel Silva, linux-usb, linux-pm,
	linux-kernel, Support Opensource

On 08 February 2018 21:31, Sebastian Reichel wrote:

> Hi,
>
> On Tue, Jan 02, 2018 at 03:50:53PM +0000, Adam Thomson wrote:
> > This commit adds the 'connected_type' property to represent supplies
> > which can report a number of different types of supply based on a
> > connection event.
> >
> > Examples of this already exist in drivers whereby the existing 'type'
> > property is updated, based on an event, to represent what was
> > connected (e.g. USB_DCP, USB_ACA, ...). Current implementations
> > however don't show all supported connectable types, so this knowledge
> > has to be exlicitly known for each driver that supports this.
> >
> > The 'connected_type' property is intended to fill this void and show
> > users all possible types supported by a driver. The property, when
> > read, shows all available types for the driver, and the one currently
> > chosen is highlighted/bracketed. It is expected that the 'type'
> > property would then just show the top-level type, such as 'USB', and
> > this would be static.
> >
> > Currently the 'conn_type' enum contains all of the USB variant types
> > that exist for the 'type' enum at this time, and in addition has
> > the PPS type. In the future this can be extended further for other
> > types which have multiple connected types supported. The mirroring
> > is intentional so as to not impact existing usage of the 'type'
> > property.
> >
> > Signed-off-by: Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
>
> Thanks for the patch. I think, that it's a good idea to provide the
> subtype in its own property and just set the type property to "USB".
> I would prefer to name this "usb_type". Otherwise
>
> Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.co.uk>
>
> -- Sebastian

Thanks for your review. I chose 'connected_type' as I thought this would be more
generic in case other main types would have some dynamic connection element to
them and could also benefit from this. If we don't see that as being something
we want though then I can make this just for USB.

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

* [v4,5/7] power: supply: Add 'connected_type' property and supporting code
@ 2018-02-09 11:28       ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 67+ messages in thread
From: Opensource [Adam Thomson] @ 2018-02-09 11:28 UTC (permalink / raw)
  To: Sebastian Reichel, Adam Thomson
  Cc: Heikki Krogerus, Guenter Roeck, Greg Kroah-Hartman,
	Hans de Goede, Yueyao Zhu, Rui Miguel Silva, linux-usb, linux-pm,
	linux-kernel, Support Opensource

On 08 February 2018 21:31, Sebastian Reichel wrote:

> Hi,
>
> On Tue, Jan 02, 2018 at 03:50:53PM +0000, Adam Thomson wrote:
> > This commit adds the 'connected_type' property to represent supplies
> > which can report a number of different types of supply based on a
> > connection event.
> >
> > Examples of this already exist in drivers whereby the existing 'type'
> > property is updated, based on an event, to represent what was
> > connected (e.g. USB_DCP, USB_ACA, ...). Current implementations
> > however don't show all supported connectable types, so this knowledge
> > has to be exlicitly known for each driver that supports this.
> >
> > The 'connected_type' property is intended to fill this void and show
> > users all possible types supported by a driver. The property, when
> > read, shows all available types for the driver, and the one currently
> > chosen is highlighted/bracketed. It is expected that the 'type'
> > property would then just show the top-level type, such as 'USB', and
> > this would be static.
> >
> > Currently the 'conn_type' enum contains all of the USB variant types
> > that exist for the 'type' enum at this time, and in addition has
> > the PPS type. In the future this can be extended further for other
> > types which have multiple connected types supported. The mirroring
> > is intentional so as to not impact existing usage of the 'type'
> > property.
> >
> > Signed-off-by: Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
>
> Thanks for the patch. I think, that it's a good idea to provide the
> subtype in its own property and just set the type property to "USB".
> I would prefer to name this "usb_type". Otherwise
>
> Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.co.uk>
>
> -- Sebastian

Thanks for your review. I chose 'connected_type' as I thought this would be more
generic in case other main types would have some dynamic connection element to
them and could also benefit from this. If we don't see that as being something
we want though then I can make this just for USB.
---
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* Re: [PATCH v4 5/7] power: supply: Add 'connected_type' property and supporting code
@ 2018-02-09 15:01         ` Sebastian Reichel
  0 siblings, 0 replies; 67+ messages in thread
From: Sebastian Reichel @ 2018-02-09 15:01 UTC (permalink / raw)
  To: Adam Thomson
  Cc: Heikki Krogerus, Guenter Roeck, Greg Kroah-Hartman,
	Hans de Goede, Yueyao Zhu, Rui Miguel Silva, linux-usb, linux-pm,
	linux-kernel, Support Opensource

[-- Attachment #1: Type: text/plain, Size: 2422 bytes --]

Hi,

On Fri, Feb 09, 2018 at 11:28:42AM +0000, Adam Thomson wrote:
> On 08 February 2018 21:31, Sebastian Reichel wrote:
> > On Tue, Jan 02, 2018 at 03:50:53PM +0000, Adam Thomson wrote:
> > > This commit adds the 'connected_type' property to represent supplies
> > > which can report a number of different types of supply based on a
> > > connection event.
> > >
> > > Examples of this already exist in drivers whereby the existing 'type'
> > > property is updated, based on an event, to represent what was
> > > connected (e.g. USB_DCP, USB_ACA, ...). Current implementations
> > > however don't show all supported connectable types, so this knowledge
> > > has to be exlicitly known for each driver that supports this.
> > >
> > > The 'connected_type' property is intended to fill this void and show
> > > users all possible types supported by a driver. The property, when
> > > read, shows all available types for the driver, and the one currently
> > > chosen is highlighted/bracketed. It is expected that the 'type'
> > > property would then just show the top-level type, such as 'USB', and
> > > this would be static.
> > >
> > > Currently the 'conn_type' enum contains all of the USB variant types
> > > that exist for the 'type' enum at this time, and in addition has
> > > the PPS type. In the future this can be extended further for other
> > > types which have multiple connected types supported. The mirroring
> > > is intentional so as to not impact existing usage of the 'type'
> > > property.
> > >
> > > Signed-off-by: Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
> >
> > Thanks for the patch. I think, that it's a good idea to provide the
> > subtype in its own property and just set the type property to "USB".
> > I would prefer to name this "usb_type". Otherwise
> >
> > Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.co.uk>
> >
> > -- Sebastian
> 
> Thanks for your review. I chose 'connected_type' as I thought this would be more
> generic in case other main types would have some dynamic connection element to
> them and could also benefit from this. If we don't see that as being something
> we want though then I can make this just for USB.

If such a thing appears at some point we can add another property.
I think usb_type is easier to understand from a user point of view
and it helps to keep our enums clean.

-- Sebastian

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

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

* [v4,5/7] power: supply: Add 'connected_type' property and supporting code
@ 2018-02-09 15:01         ` Sebastian Reichel
  0 siblings, 0 replies; 67+ messages in thread
From: Sebastian Reichel @ 2018-02-09 15:01 UTC (permalink / raw)
  To: Adam Thomson
  Cc: Heikki Krogerus, Guenter Roeck, Greg Kroah-Hartman,
	Hans de Goede, Yueyao Zhu, Rui Miguel Silva, linux-usb, linux-pm,
	linux-kernel, Support Opensource

Hi,

On Fri, Feb 09, 2018 at 11:28:42AM +0000, Adam Thomson wrote:
> On 08 February 2018 21:31, Sebastian Reichel wrote:
> > On Tue, Jan 02, 2018 at 03:50:53PM +0000, Adam Thomson wrote:
> > > This commit adds the 'connected_type' property to represent supplies
> > > which can report a number of different types of supply based on a
> > > connection event.
> > >
> > > Examples of this already exist in drivers whereby the existing 'type'
> > > property is updated, based on an event, to represent what was
> > > connected (e.g. USB_DCP, USB_ACA, ...). Current implementations
> > > however don't show all supported connectable types, so this knowledge
> > > has to be exlicitly known for each driver that supports this.
> > >
> > > The 'connected_type' property is intended to fill this void and show
> > > users all possible types supported by a driver. The property, when
> > > read, shows all available types for the driver, and the one currently
> > > chosen is highlighted/bracketed. It is expected that the 'type'
> > > property would then just show the top-level type, such as 'USB', and
> > > this would be static.
> > >
> > > Currently the 'conn_type' enum contains all of the USB variant types
> > > that exist for the 'type' enum at this time, and in addition has
> > > the PPS type. In the future this can be extended further for other
> > > types which have multiple connected types supported. The mirroring
> > > is intentional so as to not impact existing usage of the 'type'
> > > property.
> > >
> > > Signed-off-by: Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
> >
> > Thanks for the patch. I think, that it's a good idea to provide the
> > subtype in its own property and just set the type property to "USB".
> > I would prefer to name this "usb_type". Otherwise
> >
> > Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.co.uk>
> >
> > -- Sebastian
> 
> Thanks for your review. I chose 'connected_type' as I thought this would be more
> generic in case other main types would have some dynamic connection element to
> them and could also benefit from this. If we don't see that as being something
> we want though then I can make this just for USB.

If such a thing appears at some point we can add another property.
I think usb_type is easier to understand from a user point of view
and it helps to keep our enums clean.

-- Sebastian

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

* RE: [PATCH v4 5/7] power: supply: Add 'connected_type' property and supporting code
@ 2018-02-09 15:43           ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 67+ messages in thread
From: Adam Thomson @ 2018-02-09 15:43 UTC (permalink / raw)
  To: Sebastian Reichel, Adam Thomson
  Cc: Heikki Krogerus, Guenter Roeck, Greg Kroah-Hartman,
	Hans de Goede, Yueyao Zhu, Rui Miguel Silva, linux-usb, linux-pm,
	linux-kernel, Support Opensource

On 09 February 2018 15:02, Sebastian Reichel wrote:

> Hi,
> 
> On Fri, Feb 09, 2018 at 11:28:42AM +0000, Adam Thomson wrote:
> > On 08 February 2018 21:31, Sebastian Reichel wrote:
> > > On Tue, Jan 02, 2018 at 03:50:53PM +0000, Adam Thomson wrote:
> > > > This commit adds the 'connected_type' property to represent supplies
> > > > which can report a number of different types of supply based on a
> > > > connection event.
> > > >
> > > > Examples of this already exist in drivers whereby the existing 'type'
> > > > property is updated, based on an event, to represent what was
> > > > connected (e.g. USB_DCP, USB_ACA, ...). Current implementations
> > > > however don't show all supported connectable types, so this knowledge
> > > > has to be exlicitly known for each driver that supports this.
> > > >
> > > > The 'connected_type' property is intended to fill this void and show
> > > > users all possible types supported by a driver. The property, when
> > > > read, shows all available types for the driver, and the one currently
> > > > chosen is highlighted/bracketed. It is expected that the 'type'
> > > > property would then just show the top-level type, such as 'USB', and
> > > > this would be static.
> > > >
> > > > Currently the 'conn_type' enum contains all of the USB variant types
> > > > that exist for the 'type' enum at this time, and in addition has
> > > > the PPS type. In the future this can be extended further for other
> > > > types which have multiple connected types supported. The mirroring
> > > > is intentional so as to not impact existing usage of the 'type'
> > > > property.
> > > >
> > > > Signed-off-by: Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
> > >
> > > Thanks for the patch. I think, that it's a good idea to provide the
> > > subtype in its own property and just set the type property to "USB".
> > > I would prefer to name this "usb_type". Otherwise
> > >
> > > Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.co.uk>
> > >
> > > -- Sebastian
> >
> > Thanks for your review. I chose 'connected_type' as I thought this would be more
> > generic in case other main types would have some dynamic connection element to
> > them and could also benefit from this. If we don't see that as being something
> > we want though then I can make this just for USB.
> 
> If such a thing appears at some point we can add another property.
> I think usb_type is easier to understand from a user point of view
> and it helps to keep our enums clean.

Ok, fair enough. Will update accordingly.

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

* [v4,5/7] power: supply: Add 'connected_type' property and supporting code
@ 2018-02-09 15:43           ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 67+ messages in thread
From: Opensource [Adam Thomson] @ 2018-02-09 15:43 UTC (permalink / raw)
  To: Sebastian Reichel, Adam Thomson
  Cc: Heikki Krogerus, Guenter Roeck, Greg Kroah-Hartman,
	Hans de Goede, Yueyao Zhu, Rui Miguel Silva, linux-usb, linux-pm,
	linux-kernel, Support Opensource

On 09 February 2018 15:02, Sebastian Reichel wrote:

> Hi,
> 
> On Fri, Feb 09, 2018 at 11:28:42AM +0000, Adam Thomson wrote:
> > On 08 February 2018 21:31, Sebastian Reichel wrote:
> > > On Tue, Jan 02, 2018 at 03:50:53PM +0000, Adam Thomson wrote:
> > > > This commit adds the 'connected_type' property to represent supplies
> > > > which can report a number of different types of supply based on a
> > > > connection event.
> > > >
> > > > Examples of this already exist in drivers whereby the existing 'type'
> > > > property is updated, based on an event, to represent what was
> > > > connected (e.g. USB_DCP, USB_ACA, ...). Current implementations
> > > > however don't show all supported connectable types, so this knowledge
> > > > has to be exlicitly known for each driver that supports this.
> > > >
> > > > The 'connected_type' property is intended to fill this void and show
> > > > users all possible types supported by a driver. The property, when
> > > > read, shows all available types for the driver, and the one currently
> > > > chosen is highlighted/bracketed. It is expected that the 'type'
> > > > property would then just show the top-level type, such as 'USB', and
> > > > this would be static.
> > > >
> > > > Currently the 'conn_type' enum contains all of the USB variant types
> > > > that exist for the 'type' enum at this time, and in addition has
> > > > the PPS type. In the future this can be extended further for other
> > > > types which have multiple connected types supported. The mirroring
> > > > is intentional so as to not impact existing usage of the 'type'
> > > > property.
> > > >
> > > > Signed-off-by: Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
> > >
> > > Thanks for the patch. I think, that it's a good idea to provide the
> > > subtype in its own property and just set the type property to "USB".
> > > I would prefer to name this "usb_type". Otherwise
> > >
> > > Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.co.uk>
> > >
> > > -- Sebastian
> >
> > Thanks for your review. I chose 'connected_type' as I thought this would be more
> > generic in case other main types would have some dynamic connection element to
> > them and could also benefit from this. If we don't see that as being something
> > we want though then I can make this just for USB.
> 
> If such a thing appears at some point we can add another property.
> I think usb_type is easier to understand from a user point of view
> and it helps to keep our enums clean.

Ok, fair enough. Will update accordingly.
---
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* RE: [PATCH v4 6/7] typec: tcpm: Represent source supply through power_supply class
@ 2018-02-09 16:06           ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 67+ messages in thread
From: Adam Thomson @ 2018-02-09 16:06 UTC (permalink / raw)
  To: Heikki Krogerus, Adam Thomson
  Cc: Sebastian Reichel, Guenter Roeck, Greg Kroah-Hartman,
	Hans de Goede, Yueyao Zhu, Rui Miguel Silva, linux-usb, linux-pm,
	linux-kernel, Support Opensource

On 08 February 2018 10:45, Heikki Krogerus wrote:

Hi Heikki,

Comments in-line as usual. Bit verbose, and may have stated the obvious in
places, but trying to build a picture and aim for something sensible.

> Hi Adam,
>
> On Tue, Feb 06, 2018 at 03:51:26PM +0000, Adam Thomson wrote:
> > Right now there is no documentation for the generic psy class. The stuff in
> > sysfs-class-power is device specific property information, and the same goes for
> > sysfs-class-power-twl4030. The property usage can vary depending on driver
> > implementation, an example being the 'online' property which can differ between
> > drivers, so the usage I have here is very much tcpm related. Also, the ability
> > to write to certain properties varies depending on the driver and HW, so here
> > where we configure 'voltage_now' and 'current_now', the likelihood is that most
> > other psy driver implementations won't allow for this.
>
> The power supply class is missing documentation, YES! That is what I
> have been saying! The fact that even an attribute like "online" can
> mean different things depending on the driver is absolutely horrible.
> The ABI documentation FOR THE POWER SUPPLY CLASS needs to provide
> clear meaning for the attributes. It needs to also point out which
> attributes can be hidden, and it should also give some hints for
> things like which attributes can be expected to be visible for example
> in case of USB type of psy and so on.

There is some coverage under Documenation/power/power_supply_class.txt but it's
not necessarily extensive, at least not to the level you're suggesting. With
regards to the psy class though, I believe this is meant to cover a range of
supply types so I guess the original intention was to make it flexible to cover
those. Maybe along the way that's helped muddy the waters somewhat though.

As an example, a 'Battery' type supply would represent say voltage_now
and current_now, and those values would relate to VBAT and IBAT. However a
Charger IC reporting the USB side information (probably of type 'USB', but could
be AC, Mains) would report voltage_now and current_now, and those values would
represent VIN/VBUS and IIN/IBUS. This example assumes that both sets of HW would
be able to provide instantaneous readings. Some only give averaged readings.
Just from this you can already start to see why that framework has evolved in
the way it has.

Covering the 'online' property, for Charger ICs this would indicate whether
they were externally powered or not (i.e. external charger attached/detached),
so a positive value would indicate external charger presence and 0 would mean
not present. In drivers/power/supply/abx500_chargalg.c it goes further with this
and the positive online value can differ based on what kind of charger was
attached (i.e. Mains, USB). Not sure if anything in user-space would behave
differently based on different values though and suspect a positive or 0 value
will be the only differentiator. For the Battery type scenario 'online', if
provided, is usually a repeat of battery presence information. Examples of this
exist although it's really just redundant information, so a guideline would help
to avoid this.

So right now the only sensible method seems to be to describe property usage
based on the main type, which brings me round to what you're suggesting I think.
I can start something, at least for common USB property usage scenarios, and
maybe Battery as well, but I do think this needs additional attention as there
are so far 66 (including my new addition) properties to document, some of which
may require multiple definitions based on type specific usage. With regards to
presence of attributes, that's going to be solely related to HW and what is
implemented for that device, I think. We can give preferred, sunny day examples,
but I think you'll be hard pressed to make one size fit all. Or do you disagree
with that sentiment?

>
> We are talking about user space ABI for power supplies here. The user
> space does not know that its dealing with tcpm in this case, or some
> other driver in an other case, AND, the user space _must_ not be
> expected to know that kind of details. The behaviour and meaning of an
> individual attribute file quite simply has to be the same, always,
> regardless of the platform, HW, driver or whatever. Otherwise this
> whole ABI is completely useless.

Not technically true as decisions could be based on psy naming. Generic TCPM
usage of the power_supply framework would mean the naming of the psy sysfs entry
(or entries) are well known, just like using /dev. I believe 'type' though is
specifically used in Android to determine which properties it uses for specific
purposes. Personally I go along with this and think 'type' should be the major
determining factor on expectation/usage. User-space needs to know the type of
supply it's dealing with to be able to act accordingly.

> To summarize: We can't just accept chaos. Instead we should organize
> the places without structure, in this case the user space ABI for
> power supplies.
>
> On top of ABI documentation, we will need driver API documentation as
> well. I'm not expecting that you would also propose something for the
> API too, but I just wanted to bring that up here. I would like to have
> some guidelines on how the power supplies should be used also in
> kernel.
>
> Right now it is possible for one driver to create the power supply and
> an other to take over the control of it. It is super easy to gain
> access to a power supply. You can request it with just the name
> without any control, and after gaining access, you have full control
> over it. That makes it really easy to have race condition where both
> the psy device driver and some other driver try to control the same
> things of the same psy.
>
> I guess the whole design of the psy class could use a little bit of
> re-designing. So IMO, access to the psy should be more strict then it
> is now, and also, even after gaining access to a psy handle, drivers
> that are not the actual psy device drivers should have more controlled
> access to it. So possibly separate API for them... OK, this is
> definitely a separate topic. Sorry, I'll stop here.

Actually being able to use psy's in other drivers is an approach that's very
useful, especially for monitoring & controlling charging using PPS. You are
right in that it's very easy to gain access and have control, but have not
personally experienced race issues. I guess that's down to the individual
driver's own locking implementation right now?

Anyway, based on all of this, and assuming I get no major disagreement, I'll
try and come up with something as a decent starting point to better document
and fix property usage, based on supply type. It won't be every property, but
hopefully will be a decent first stab at this.

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

* [v4,6/7] typec: tcpm: Represent source supply through power_supply class
@ 2018-02-09 16:06           ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 67+ messages in thread
From: Opensource [Adam Thomson] @ 2018-02-09 16:06 UTC (permalink / raw)
  To: Heikki Krogerus, Adam Thomson
  Cc: Sebastian Reichel, Guenter Roeck, Greg Kroah-Hartman,
	Hans de Goede, Yueyao Zhu, Rui Miguel Silva, linux-usb, linux-pm,
	linux-kernel, Support Opensource

On 08 February 2018 10:45, Heikki Krogerus wrote:

Hi Heikki,

Comments in-line as usual. Bit verbose, and may have stated the obvious in
places, but trying to build a picture and aim for something sensible.

> Hi Adam,
>
> On Tue, Feb 06, 2018 at 03:51:26PM +0000, Adam Thomson wrote:
> > Right now there is no documentation for the generic psy class. The stuff in
> > sysfs-class-power is device specific property information, and the same goes for
> > sysfs-class-power-twl4030. The property usage can vary depending on driver
> > implementation, an example being the 'online' property which can differ between
> > drivers, so the usage I have here is very much tcpm related. Also, the ability
> > to write to certain properties varies depending on the driver and HW, so here
> > where we configure 'voltage_now' and 'current_now', the likelihood is that most
> > other psy driver implementations won't allow for this.
>
> The power supply class is missing documentation, YES! That is what I
> have been saying! The fact that even an attribute like "online" can
> mean different things depending on the driver is absolutely horrible.
> The ABI documentation FOR THE POWER SUPPLY CLASS needs to provide
> clear meaning for the attributes. It needs to also point out which
> attributes can be hidden, and it should also give some hints for
> things like which attributes can be expected to be visible for example
> in case of USB type of psy and so on.

There is some coverage under Documenation/power/power_supply_class.txt but it's
not necessarily extensive, at least not to the level you're suggesting. With
regards to the psy class though, I believe this is meant to cover a range of
supply types so I guess the original intention was to make it flexible to cover
those. Maybe along the way that's helped muddy the waters somewhat though.

As an example, a 'Battery' type supply would represent say voltage_now
and current_now, and those values would relate to VBAT and IBAT. However a
Charger IC reporting the USB side information (probably of type 'USB', but could
be AC, Mains) would report voltage_now and current_now, and those values would
represent VIN/VBUS and IIN/IBUS. This example assumes that both sets of HW would
be able to provide instantaneous readings. Some only give averaged readings.
Just from this you can already start to see why that framework has evolved in
the way it has.

Covering the 'online' property, for Charger ICs this would indicate whether
they were externally powered or not (i.e. external charger attached/detached),
so a positive value would indicate external charger presence and 0 would mean
not present. In drivers/power/supply/abx500_chargalg.c it goes further with this
and the positive online value can differ based on what kind of charger was
attached (i.e. Mains, USB). Not sure if anything in user-space would behave
differently based on different values though and suspect a positive or 0 value
will be the only differentiator. For the Battery type scenario 'online', if
provided, is usually a repeat of battery presence information. Examples of this
exist although it's really just redundant information, so a guideline would help
to avoid this.

So right now the only sensible method seems to be to describe property usage
based on the main type, which brings me round to what you're suggesting I think.
I can start something, at least for common USB property usage scenarios, and
maybe Battery as well, but I do think this needs additional attention as there
are so far 66 (including my new addition) properties to document, some of which
may require multiple definitions based on type specific usage. With regards to
presence of attributes, that's going to be solely related to HW and what is
implemented for that device, I think. We can give preferred, sunny day examples,
but I think you'll be hard pressed to make one size fit all. Or do you disagree
with that sentiment?

>
> We are talking about user space ABI for power supplies here. The user
> space does not know that its dealing with tcpm in this case, or some
> other driver in an other case, AND, the user space _must_ not be
> expected to know that kind of details. The behaviour and meaning of an
> individual attribute file quite simply has to be the same, always,
> regardless of the platform, HW, driver or whatever. Otherwise this
> whole ABI is completely useless.

Not technically true as decisions could be based on psy naming. Generic TCPM
usage of the power_supply framework would mean the naming of the psy sysfs entry
(or entries) are well known, just like using /dev. I believe 'type' though is
specifically used in Android to determine which properties it uses for specific
purposes. Personally I go along with this and think 'type' should be the major
determining factor on expectation/usage. User-space needs to know the type of
supply it's dealing with to be able to act accordingly.

> To summarize: We can't just accept chaos. Instead we should organize
> the places without structure, in this case the user space ABI for
> power supplies.
>
> On top of ABI documentation, we will need driver API documentation as
> well. I'm not expecting that you would also propose something for the
> API too, but I just wanted to bring that up here. I would like to have
> some guidelines on how the power supplies should be used also in
> kernel.
>
> Right now it is possible for one driver to create the power supply and
> an other to take over the control of it. It is super easy to gain
> access to a power supply. You can request it with just the name
> without any control, and after gaining access, you have full control
> over it. That makes it really easy to have race condition where both
> the psy device driver and some other driver try to control the same
> things of the same psy.
>
> I guess the whole design of the psy class could use a little bit of
> re-designing. So IMO, access to the psy should be more strict then it
> is now, and also, even after gaining access to a psy handle, drivers
> that are not the actual psy device drivers should have more controlled
> access to it. So possibly separate API for them... OK, this is
> definitely a separate topic. Sorry, I'll stop here.

Actually being able to use psy's in other drivers is an approach that's very
useful, especially for monitoring & controlling charging using PPS. You are
right in that it's very easy to gain access and have control, but have not
personally experienced race issues. I guess that's down to the individual
driver's own locking implementation right now?

Anyway, based on all of this, and assuming I get no major disagreement, I'll
try and come up with something as a decent starting point to better document
and fix property usage, based on supply type. It won't be every property, but
hopefully will be a decent first stab at this.
---
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* Re: [PATCH v4 0/7] typec: tcpm: Add sink side support for PPS
  2018-01-02 15:50 ` Adam Thomson
                   ` (9 preceding siblings ...)
  (?)
@ 2018-03-09 17:33 ` Greg Kroah-Hartman
  2018-03-12  8:32   ` Adam Thomson
  -1 siblings, 1 reply; 67+ messages in thread
From: Greg Kroah-Hartman @ 2018-03-09 17:33 UTC (permalink / raw)
  To: Adam Thomson
  Cc: Heikki Krogerus, Guenter Roeck, Sebastian Reichel, Hans de Goede,
	Yueyao Zhu, Rui Miguel Silva, linux-usb, linux-pm, linux-kernel,
	support.opensource

On Tue, Jan 02, 2018 at 03:50:48PM +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.

I've applied the first 3 patches now.  Can you rebase and resend based
on the feedback so far?

thanks,

greg k-h

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

* RE: [PATCH v4 0/7] typec: tcpm: Add sink side support for PPS
  2018-03-09 17:33 ` Greg Kroah-Hartman
@ 2018-03-12  8:32   ` Adam Thomson
  2018-03-19 10:39     ` Adam Thomson
  0 siblings, 1 reply; 67+ messages in thread
From: Adam Thomson @ 2018-03-12  8:32 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Adam Thomson
  Cc: Heikki Krogerus, Guenter Roeck, Sebastian Reichel, Hans de Goede,
	Yueyao Zhu, Rui Miguel Silva, linux-usb, linux-pm, linux-kernel,
	Support Opensource

On 09 March 2018 17:34, Greg Kroah-Hartman wrote:

> On Tue, Jan 02, 2018 at 03:50:48PM +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.
> 
> I've applied the first 3 patches now.  Can you rebase and resend based
> on the feedback so far?

That's great. Thanks. Need to also rebase based on the patch
'Revert "typec: tcpm: Only request matching pdos"' as this impacts the APDO
selection logic. This is on my TODO list and will try and deal with it ASAP,
hopefully this week.

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

* RE: [PATCH v4 0/7] typec: tcpm: Add sink side support for PPS
  2018-03-12  8:32   ` Adam Thomson
@ 2018-03-19 10:39     ` Adam Thomson
  0 siblings, 0 replies; 67+ messages in thread
From: Adam Thomson @ 2018-03-19 10:39 UTC (permalink / raw)
  To: Adam Thomson, Greg Kroah-Hartman
  Cc: Heikki Krogerus, Guenter Roeck, Sebastian Reichel, Hans de Goede,
	Yueyao Zhu, Rui Miguel Silva, linux-usb, linux-pm, linux-kernel,
	Support Opensource

On 12 March 2018 08:33, Adam Thomson wrote:

> On 09 March 2018 17:34, Greg Kroah-Hartman wrote:
> 
> > On Tue, Jan 02, 2018 at 03:50:48PM +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.
> >
> > I've applied the first 3 patches now.  Can you rebase and resend based
> > on the feedback so far?
> 
> That's great. Thanks. Need to also rebase based on the patch
> 'Revert "typec: tcpm: Only request matching pdos"' as this impacts the APDO
> selection logic. This is on my TODO list and will try and deal with it ASAP,
> hopefully this week.

One quick question. The patch 'Revert "typec: tcpm: Only request matching pdos"'
from Hans isn't on your next branch. Is that something that will get pulled in
soon? Need this as well as my updates you already pulled into your next branch
before I can resubmit. For now I cherry-picked that patch locally for testing,
but that's no good for submission.

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

end of thread, other threads:[~2018-03-19 10:39 UTC | newest]

Thread overview: 67+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-01-02 15:50 [PATCH v4 0/7] typec: tcpm: Add sink side support for PPS Adam Thomson
2018-01-02 15:50 ` Adam Thomson
2018-01-02 15:50 ` [PATCH v4 1/7] typec: tcpm: Add PD Rev 3.0 definitions to PD header Adam Thomson
2018-01-02 15:50   ` Adam Thomson
2018-01-02 15:50   ` [v4,1/7] " Opensource [Adam Thomson]
2018-01-30 12:21   ` [PATCH v4 1/7] " Heikki Krogerus
2018-01-30 12:21     ` [v4,1/7] " Heikki Krogerus
2018-02-06 14:14     ` [PATCH v4 1/7] " Adam Thomson
2018-02-06 14:14       ` [v4,1/7] " Opensource [Adam Thomson]
2018-01-02 15:50 ` [PATCH v4 2/7] typec: tcpm: Add ADO header for Alert message handling Adam Thomson
2018-01-02 15:50   ` Adam Thomson
2018-01-02 15:50   ` [v4,2/7] " Opensource [Adam Thomson]
2018-01-30 12:22   ` [PATCH v4 2/7] " Heikki Krogerus
2018-01-30 12:22     ` [v4,2/7] " Heikki Krogerus
2018-01-02 15:50 ` [PATCH v4 3/7] typec: tcpm: Add SDB header for Status " Adam Thomson
2018-01-02 15:50   ` Adam Thomson
2018-01-02 15:50   ` [v4,3/7] " Opensource [Adam Thomson]
2018-01-30 12:22   ` [PATCH v4 3/7] " Heikki Krogerus
2018-01-30 12:22     ` Heikki Krogerus
2018-01-30 12:22     ` [v4,3/7] " Heikki Krogerus
2018-01-02 15:50 ` [PATCH v4 4/7] typec: tcpm: Add core support for sink side PPS Adam Thomson
2018-01-02 15:50   ` Adam Thomson
2018-01-02 15:50   ` [v4,4/7] " Opensource [Adam Thomson]
2018-01-30 12:46   ` [PATCH v4 4/7] " Heikki Krogerus
2018-01-30 12:46     ` [v4,4/7] " Heikki Krogerus
2018-02-06 14:33     ` [PATCH v4 4/7] " Adam Thomson
2018-02-06 14:33       ` [v4,4/7] " Opensource [Adam Thomson]
2018-02-06 14:53       ` [PATCH v4 4/7] " Heikki Krogerus
2018-02-06 14:53         ` [v4,4/7] " Heikki Krogerus
2018-01-02 15:50 ` [PATCH v4 5/7] power: supply: Add 'connected_type' property and supporting code Adam Thomson
2018-01-02 15:50   ` Adam Thomson
2018-01-02 15:50   ` [v4,5/7] " Opensource [Adam Thomson]
2018-01-30 12:54   ` [PATCH v4 5/7] " Heikki Krogerus
2018-01-30 12:54     ` [v4,5/7] " Heikki Krogerus
2018-02-08 21:30   ` [PATCH v4 5/7] " Sebastian Reichel
2018-02-08 21:30     ` [v4,5/7] " Sebastian Reichel
2018-02-09 11:28     ` [PATCH v4 5/7] " Adam Thomson
2018-02-09 11:28       ` [v4,5/7] " Opensource [Adam Thomson]
2018-02-09 15:01       ` [PATCH v4 5/7] " Sebastian Reichel
2018-02-09 15:01         ` [v4,5/7] " Sebastian Reichel
2018-02-09 15:43         ` [PATCH v4 5/7] " Adam Thomson
2018-02-09 15:43           ` [v4,5/7] " Opensource [Adam Thomson]
2018-01-02 15:50 ` [PATCH v4 6/7] typec: tcpm: Represent source supply through power_supply class Adam Thomson
2018-01-02 15:50   ` Adam Thomson
2018-01-02 15:50   ` [v4,6/7] " Opensource [Adam Thomson]
2018-01-30 13:11   ` [PATCH v4 6/7] " Heikki Krogerus
2018-01-30 13:11     ` [v4,6/7] " Heikki Krogerus
2018-02-06 15:51     ` [PATCH v4 6/7] " Adam Thomson
2018-02-06 15:51       ` [v4,6/7] " Opensource [Adam Thomson]
2018-02-08 10:45       ` [PATCH v4 6/7] " Heikki Krogerus
2018-02-08 10:45         ` [v4,6/7] " Heikki Krogerus
2018-02-09 16:06         ` [PATCH v4 6/7] " Adam Thomson
2018-02-09 16:06           ` [v4,6/7] " Opensource [Adam Thomson]
2018-01-02 15:50 ` [PATCH v4 7/7] typec: tcpm: Add support for sink PPS related messages Adam Thomson
2018-01-02 15:50   ` Adam Thomson
2018-01-02 15:50   ` [v4,7/7] " Opensource [Adam Thomson]
2018-01-30 13:18   ` [PATCH v4 7/7] " Heikki Krogerus
2018-01-30 13:18     ` [v4,7/7] " Heikki Krogerus
2018-01-22 14:31 ` [PATCH v4 0/7] typec: tcpm: Add sink side support for PPS Greg Kroah-Hartman
2018-01-22 14:31   ` Greg Kroah-Hartman
2018-01-22 15:57   ` Guenter Roeck
2018-01-30 13:25 ` Heikki Krogerus
2018-01-30 14:27   ` Guenter Roeck
2018-02-05 10:30   ` Adam Thomson
2018-03-09 17:33 ` Greg Kroah-Hartman
2018-03-12  8:32   ` Adam Thomson
2018-03-19 10:39     ` 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.