All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v5 0/5] typec: tcpm: Add sink side support for PPS
@ 2018-03-20 14:33 ` Adam Thomson
  0 siblings, 0 replies; 35+ messages in thread
From: Adam Thomson @ 2018-03-20 14:33 UTC (permalink / raw)
  To: Heikki Krogerus, Guenter Roeck, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Jun Li
  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 v5:
 - Rebase on branch with 'Revert "typec: tcpm: Only request matching pdos"' and
   header changes already included.
 - Update power_supply registration to make power_supply names unique per port,
   to avoid errors creating duplicate psy instances. New name uses port
   dev name as a suffix.
 - Renamed 'connected_type' psy property to 'usb_type', as requested by
   maintainer.
 - Added initial attempt at generic ABI documentation for common psy class
   properties for Battery and USB type supplies.
 - Small update to PPS APDO selection code to limit maximum current requested
   based on sink maximum allowed current. Have left Heikki's 'Acked-by' tag as
   it's a minor change, but can remove if that's not deemed appropriate.

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 based on usb-next (027bd6cafd9a1e3a109b5e5682c85ac84e804a8d)

Adam Thomson (5):
  typec: tcpm: Add core support for sink side PPS
  Documentation: power: Initial effort to document power_supply ABI
  power: supply: Add 'usb_type' property and supporting code
  typec: tcpm: Represent source supply through power_supply
  typec: tcpm: Add support for sink PPS related messages

 Documentation/ABI/testing/sysfs-class-power | 455 ++++++++++++++
 MAINTAINERS                                 |   1 +
 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                |  16 +
 include/linux/usb/pd.h                      |   4 +-
 include/linux/usb/tcpm.h                    |   2 +-
 10 files changed, 1422 insertions(+), 88 deletions(-)

-- 
1.9.1

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

* [PATCH v5 0/5] typec: tcpm: Add sink side support for PPS
@ 2018-03-20 14:33 ` Adam Thomson
  0 siblings, 0 replies; 35+ messages in thread
From: Adam Thomson @ 2018-03-20 14:33 UTC (permalink / raw)
  To: Heikki Krogerus, Guenter Roeck, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Jun Li
  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 v5:
 - Rebase on branch with 'Revert "typec: tcpm: Only request matching pdos"' and
   header changes already included.
 - Update power_supply registration to make power_supply names unique per port,
   to avoid errors creating duplicate psy instances. New name uses port
   dev name as a suffix.
 - Renamed 'connected_type' psy property to 'usb_type', as requested by
   maintainer.
 - Added initial attempt at generic ABI documentation for common psy class
   properties for Battery and USB type supplies.
 - Small update to PPS APDO selection code to limit maximum current requested
   based on sink maximum allowed current. Have left Heikki's 'Acked-by' tag as
   it's a minor change, but can remove if that's not deemed appropriate.

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 based on usb-next (027bd6cafd9a1e3a109b5e5682c85ac84e804a8d)

Adam Thomson (5):
  typec: tcpm: Add core support for sink side PPS
  Documentation: power: Initial effort to document power_supply ABI
  power: supply: Add 'usb_type' property and supporting code
  typec: tcpm: Represent source supply through power_supply
  typec: tcpm: Add support for sink PPS related messages

 Documentation/ABI/testing/sysfs-class-power | 455 ++++++++++++++
 MAINTAINERS                                 |   1 +
 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                |  16 +
 include/linux/usb/pd.h                      |   4 +-
 include/linux/usb/tcpm.h                    |   2 +-
 10 files changed, 1422 insertions(+), 88 deletions(-)

-- 
1.9.1

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

* [PATCH v5 1/5] typec: tcpm: Add core support for sink side PPS
@ 2018-03-20 14:33   ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 35+ messages in thread
From: Adam Thomson @ 2018-03-20 14:33 UTC (permalink / raw)
  To: Heikki Krogerus, Guenter Roeck, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Jun Li
  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>
Acked-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
---
 drivers/usb/typec/tcpm.c | 524 +++++++++++++++++++++++++++++++++++++++++++++--
 include/linux/usb/pd.h   |   4 +-
 include/linux/usb/tcpm.h |   2 +-
 3 files changed, 513 insertions(+), 17 deletions(-)

diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c
index 4c0fc54..b4cf1ca 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;
@@ -259,6 +271,7 @@ struct tcpm_port {
 	unsigned int max_snk_ma;
 	unsigned int max_snk_mw;
 	unsigned int operating_snk_mw;
+	bool update_sink_caps;
 
 	/* Requested current / voltage */
 	u32 current_limit;
@@ -275,8 +288,13 @@ struct tcpm_port {
 	/* VDO to retry if UFP responder replied busy */
 	u32 vdo_retry;
 
-	/* Alternate mode data */
+	/* PPS */
+	struct pd_pps_data pps_data;
+	struct completion pps_complete;
+	bool pps_pending;
+	int pps_status;
 
+	/* Alternate mode data */
 	struct pd_mode_data mode_data;
 	struct typec_altmode *partner_altmode[SVID_DISCOVERY_MAX];
 	struct typec_altmode *port_altmode[SVID_DISCOVERY_MAX];
@@ -494,6 +512,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;
@@ -777,11 +805,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);
 	}
@@ -802,11 +832,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);
 	}
@@ -1173,6 +1205,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]);
@@ -1244,6 +1277,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[] = {
@@ -1259,6 +1294,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,
@@ -1308,6 +1347,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");
 			}
@@ -1333,11 +1392,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) {
@@ -1356,6 +1420,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.
@@ -1376,6 +1450,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;
@@ -1400,6 +1487,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)
 {
@@ -1476,6 +1572,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);
@@ -1498,6 +1602,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:
@@ -1652,6 +1763,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);
@@ -1761,6 +1873,8 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
 	unsigned int i, max_mw = 0, max_mv = 0;
 	int ret = -EINVAL;
 
+	port->pps_data.supported = false;
+
 	/*
 	 * Select the source PDO providing the most power while staying within
 	 * the board's voltage limits. Prefer PDO providing exp
@@ -1770,20 +1884,41 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
 		enum pd_pdo_type type = pdo_type(pdo);
 		unsigned int mv, ma, mw;
 
-		if (type == PDO_TYPE_FIXED)
+		switch (type) {
+		case PDO_TYPE_FIXED:
 			mv = pdo_fixed_voltage(pdo);
-		else
+			break;
+		case PDO_TYPE_BATT:
+		case PDO_TYPE_VAR:
 			mv = pdo_min_voltage(pdo);
+			break;
+		case PDO_TYPE_APDO:
+			if (pdo_apdo_type(pdo) == APDO_TYPE_PPS)
+				port->pps_data.supported = true;
+			continue;
+		default:
+			tcpm_log(port, "Invalid PDO type, ignoring");
+			continue;
+		}
 
-		if (type == PDO_TYPE_BATT) {
-			mw = pdo_max_power(pdo);
-		} else {
+		switch (type) {
+		case PDO_TYPE_FIXED:
+		case PDO_TYPE_VAR:
 			ma = min(pdo_max_current(pdo),
 				 port->max_snk_ma);
 			mw = ma * mv / 1000;
+			break;
+		case PDO_TYPE_BATT:
+			mw = pdo_max_power(pdo);
+			break;
+		case PDO_TYPE_APDO:
+			continue;
+		default:
+			tcpm_log(port, "Invalid PDO type, ignoring");
+			continue;
 		}
 
-		/* Perfer higher voltages if available */
+		/* Prefer higher voltages if available */
 		if ((mw > max_mw || (mw == max_mw && mv > max_mv)) &&
 		    mv <= port->max_snk_mv) {
 			ret = i;
@@ -1795,6 +1930,65 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
 	return ret;
 }
 
+static unsigned int tcpm_pd_select_pps_apdo(struct tcpm_port *port)
+{
+	unsigned int i, max_mw = 0, max_mv = 0;
+	unsigned int pps_min_mv, pps_max_mv, ma, mw;
+	enum pd_pdo_type type;
+	u32 pdo;
+	unsigned int index = 0;
+
+	/*
+	 * Select the source PPS APDO providing the most power while staying
+	 * within the board's limits. We skip the first PDO as this is always
+	 * 5V 3A.
+	 */
+	for (i = 1; i < port->nr_source_caps; ++i) {
+		pdo = port->source_caps[i];
+		type = pdo_type(pdo);
+
+		switch (type) {
+		case PDO_TYPE_APDO:
+			if (pdo_apdo_type(pdo) != APDO_TYPE_PPS) {
+				tcpm_log(port, "Not PPS APDO, ignoring");
+				continue;
+			}
+
+			pps_min_mv = pdo_pps_apdo_min_voltage(pdo);
+			pps_max_mv = pdo_pps_apdo_max_voltage(pdo);
+			ma = min(pdo_pps_apdo_max_current(pdo), port->max_snk_ma);
+			mw = (ma * pps_max_mv) / 1000;
+			break;
+		default:
+			tcpm_log(port, "Not APDO type, ignoring");
+			continue;
+		}
+
+		/* Prefer higher voltages if available */
+		if ((mw > max_mw || (mw == max_mw && pps_max_mv > max_mv)) &&
+		    pps_max_mv <= port->max_snk_mv) {
+			index = i;
+			max_mw = mw;
+			max_mv = pps_max_mv;
+		}
+	}
+
+	if (index) {
+		pdo = port->source_caps[index];
+
+		port->pps_data.min_volt = pdo_pps_apdo_min_voltage(pdo);
+		port->pps_data.max_volt = pdo_pps_apdo_max_voltage(pdo);
+		port->pps_data.max_curr =
+			min(pdo_pps_apdo_max_current(pdo), port->max_snk_ma);
+		port->pps_data.out_volt =
+			min(pdo_pps_apdo_max_voltage(pdo), port->pps_data.out_volt);
+		port->pps_data.op_curr =
+			min(pdo_pps_apdo_max_current(pdo), port->pps_data.op_curr);
+	}
+
+	return index;
+}
+
 static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo)
 {
 	unsigned int mv, ma, mw, flags;
@@ -1809,10 +2003,18 @@ static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo)
 	pdo = port->source_caps[index];
 	type = pdo_type(pdo);
 
-	if (type == PDO_TYPE_FIXED)
+	switch (type) {
+	case PDO_TYPE_FIXED:
 		mv = pdo_fixed_voltage(pdo);
-	else
+		break;
+	case PDO_TYPE_BATT:
+	case PDO_TYPE_VAR:
 		mv = pdo_min_voltage(pdo);
+		break;
+	default:
+		tcpm_log(port, "Invalid PDO selected!");
+		return -EINVAL;
+	}
 
 	/* Select maximum available current within the board's power limit */
 	if (type == PDO_TYPE_BATT) {
@@ -1875,6 +2077,105 @@ 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 index;
+	u32 pdo;
+
+	index = tcpm_pd_select_pps_apdo(port);
+	if (!index)
+		return -EOPNOTSUPP;
+
+	pdo = port->source_caps[index];
+	type = pdo_type(pdo);
+
+	switch (type) {
+	case PDO_TYPE_APDO:
+		if (pdo_apdo_type(pdo) != APDO_TYPE_PPS) {
+			tcpm_log(port, "Invalid APDO selected!");
+			return -EINVAL;
+		}
+		min_mv = port->pps_data.min_volt;
+		max_mv = port->pps_data.max_volt;
+		max_ma = port->pps_data.max_curr;
+		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 enough
+		 * 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(index + 1, out_mv, op_ma, flags);
+
+	tcpm_log(port, "Requesting APDO %d: %u mV, %u mA",
+		 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);
 
@@ -2060,6 +2361,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
@@ -2075,6 +2377,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)
@@ -2321,6 +2625,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;
@@ -2381,6 +2686,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
@@ -2404,6 +2710,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);
@@ -2412,6 +2719,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 &&
@@ -2493,6 +2801,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;
@@ -2563,6 +2872,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),
@@ -2570,6 +2897,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);
@@ -2578,7 +2906,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 */
@@ -2625,6 +2957,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);
@@ -2845,6 +3178,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:
@@ -3310,7 +3644,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;
@@ -3355,7 +3689,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;
@@ -3395,7 +3729,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;
@@ -3427,6 +3761,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;
@@ -3566,13 +4056,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;
@@ -3614,6 +4109,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,
@@ -3642,7 +4138,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] 35+ messages in thread

* [v5,1/5] typec: tcpm: Add core support for sink side PPS
@ 2018-03-20 14:33   ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 35+ messages in thread
From: Opensource [Adam Thomson] @ 2018-03-20 14:33 UTC (permalink / raw)
  To: Heikki Krogerus, Guenter Roeck, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Jun Li
  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>
Acked-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
---
 drivers/usb/typec/tcpm.c | 524 +++++++++++++++++++++++++++++++++++++++++++++--
 include/linux/usb/pd.h   |   4 +-
 include/linux/usb/tcpm.h |   2 +-
 3 files changed, 513 insertions(+), 17 deletions(-)

diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c
index 4c0fc54..b4cf1ca 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;
@@ -259,6 +271,7 @@ struct tcpm_port {
 	unsigned int max_snk_ma;
 	unsigned int max_snk_mw;
 	unsigned int operating_snk_mw;
+	bool update_sink_caps;
 
 	/* Requested current / voltage */
 	u32 current_limit;
@@ -275,8 +288,13 @@ struct tcpm_port {
 	/* VDO to retry if UFP responder replied busy */
 	u32 vdo_retry;
 
-	/* Alternate mode data */
+	/* PPS */
+	struct pd_pps_data pps_data;
+	struct completion pps_complete;
+	bool pps_pending;
+	int pps_status;
 
+	/* Alternate mode data */
 	struct pd_mode_data mode_data;
 	struct typec_altmode *partner_altmode[SVID_DISCOVERY_MAX];
 	struct typec_altmode *port_altmode[SVID_DISCOVERY_MAX];
@@ -494,6 +512,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;
@@ -777,11 +805,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);
 	}
@@ -802,11 +832,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);
 	}
@@ -1173,6 +1205,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]);
@@ -1244,6 +1277,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[] = {
@@ -1259,6 +1294,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,
@@ -1308,6 +1347,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");
 			}
@@ -1333,11 +1392,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) {
@@ -1356,6 +1420,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.
@@ -1376,6 +1450,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;
@@ -1400,6 +1487,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)
 {
@@ -1476,6 +1572,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);
@@ -1498,6 +1602,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:
@@ -1652,6 +1763,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);
@@ -1761,6 +1873,8 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
 	unsigned int i, max_mw = 0, max_mv = 0;
 	int ret = -EINVAL;
 
+	port->pps_data.supported = false;
+
 	/*
 	 * Select the source PDO providing the most power while staying within
 	 * the board's voltage limits. Prefer PDO providing exp
@@ -1770,20 +1884,41 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
 		enum pd_pdo_type type = pdo_type(pdo);
 		unsigned int mv, ma, mw;
 
-		if (type == PDO_TYPE_FIXED)
+		switch (type) {
+		case PDO_TYPE_FIXED:
 			mv = pdo_fixed_voltage(pdo);
-		else
+			break;
+		case PDO_TYPE_BATT:
+		case PDO_TYPE_VAR:
 			mv = pdo_min_voltage(pdo);
+			break;
+		case PDO_TYPE_APDO:
+			if (pdo_apdo_type(pdo) == APDO_TYPE_PPS)
+				port->pps_data.supported = true;
+			continue;
+		default:
+			tcpm_log(port, "Invalid PDO type, ignoring");
+			continue;
+		}
 
-		if (type == PDO_TYPE_BATT) {
-			mw = pdo_max_power(pdo);
-		} else {
+		switch (type) {
+		case PDO_TYPE_FIXED:
+		case PDO_TYPE_VAR:
 			ma = min(pdo_max_current(pdo),
 				 port->max_snk_ma);
 			mw = ma * mv / 1000;
+			break;
+		case PDO_TYPE_BATT:
+			mw = pdo_max_power(pdo);
+			break;
+		case PDO_TYPE_APDO:
+			continue;
+		default:
+			tcpm_log(port, "Invalid PDO type, ignoring");
+			continue;
 		}
 
-		/* Perfer higher voltages if available */
+		/* Prefer higher voltages if available */
 		if ((mw > max_mw || (mw == max_mw && mv > max_mv)) &&
 		    mv <= port->max_snk_mv) {
 			ret = i;
@@ -1795,6 +1930,65 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
 	return ret;
 }
 
+static unsigned int tcpm_pd_select_pps_apdo(struct tcpm_port *port)
+{
+	unsigned int i, max_mw = 0, max_mv = 0;
+	unsigned int pps_min_mv, pps_max_mv, ma, mw;
+	enum pd_pdo_type type;
+	u32 pdo;
+	unsigned int index = 0;
+
+	/*
+	 * Select the source PPS APDO providing the most power while staying
+	 * within the board's limits. We skip the first PDO as this is always
+	 * 5V 3A.
+	 */
+	for (i = 1; i < port->nr_source_caps; ++i) {
+		pdo = port->source_caps[i];
+		type = pdo_type(pdo);
+
+		switch (type) {
+		case PDO_TYPE_APDO:
+			if (pdo_apdo_type(pdo) != APDO_TYPE_PPS) {
+				tcpm_log(port, "Not PPS APDO, ignoring");
+				continue;
+			}
+
+			pps_min_mv = pdo_pps_apdo_min_voltage(pdo);
+			pps_max_mv = pdo_pps_apdo_max_voltage(pdo);
+			ma = min(pdo_pps_apdo_max_current(pdo), port->max_snk_ma);
+			mw = (ma * pps_max_mv) / 1000;
+			break;
+		default:
+			tcpm_log(port, "Not APDO type, ignoring");
+			continue;
+		}
+
+		/* Prefer higher voltages if available */
+		if ((mw > max_mw || (mw == max_mw && pps_max_mv > max_mv)) &&
+		    pps_max_mv <= port->max_snk_mv) {
+			index = i;
+			max_mw = mw;
+			max_mv = pps_max_mv;
+		}
+	}
+
+	if (index) {
+		pdo = port->source_caps[index];
+
+		port->pps_data.min_volt = pdo_pps_apdo_min_voltage(pdo);
+		port->pps_data.max_volt = pdo_pps_apdo_max_voltage(pdo);
+		port->pps_data.max_curr =
+			min(pdo_pps_apdo_max_current(pdo), port->max_snk_ma);
+		port->pps_data.out_volt =
+			min(pdo_pps_apdo_max_voltage(pdo), port->pps_data.out_volt);
+		port->pps_data.op_curr =
+			min(pdo_pps_apdo_max_current(pdo), port->pps_data.op_curr);
+	}
+
+	return index;
+}
+
 static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo)
 {
 	unsigned int mv, ma, mw, flags;
@@ -1809,10 +2003,18 @@ static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo)
 	pdo = port->source_caps[index];
 	type = pdo_type(pdo);
 
-	if (type == PDO_TYPE_FIXED)
+	switch (type) {
+	case PDO_TYPE_FIXED:
 		mv = pdo_fixed_voltage(pdo);
-	else
+		break;
+	case PDO_TYPE_BATT:
+	case PDO_TYPE_VAR:
 		mv = pdo_min_voltage(pdo);
+		break;
+	default:
+		tcpm_log(port, "Invalid PDO selected!");
+		return -EINVAL;
+	}
 
 	/* Select maximum available current within the board's power limit */
 	if (type == PDO_TYPE_BATT) {
@@ -1875,6 +2077,105 @@ 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 index;
+	u32 pdo;
+
+	index = tcpm_pd_select_pps_apdo(port);
+	if (!index)
+		return -EOPNOTSUPP;
+
+	pdo = port->source_caps[index];
+	type = pdo_type(pdo);
+
+	switch (type) {
+	case PDO_TYPE_APDO:
+		if (pdo_apdo_type(pdo) != APDO_TYPE_PPS) {
+			tcpm_log(port, "Invalid APDO selected!");
+			return -EINVAL;
+		}
+		min_mv = port->pps_data.min_volt;
+		max_mv = port->pps_data.max_volt;
+		max_ma = port->pps_data.max_curr;
+		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 enough
+		 * 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(index + 1, out_mv, op_ma, flags);
+
+	tcpm_log(port, "Requesting APDO %d: %u mV, %u mA",
+		 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);
 
@@ -2060,6 +2361,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
@@ -2075,6 +2377,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)
@@ -2321,6 +2625,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;
@@ -2381,6 +2686,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
@@ -2404,6 +2710,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);
@@ -2412,6 +2719,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 &&
@@ -2493,6 +2801,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;
@@ -2563,6 +2872,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),
@@ -2570,6 +2897,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);
@@ -2578,7 +2906,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 */
@@ -2625,6 +2957,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);
@@ -2845,6 +3178,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:
@@ -3310,7 +3644,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;
@@ -3355,7 +3689,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;
@@ -3395,7 +3729,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;
@@ -3427,6 +3761,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;
@@ -3566,13 +4056,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;
@@ -3614,6 +4109,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,
@@ -3642,7 +4138,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] 35+ messages in thread

* [PATCH v5 1/5] typec: tcpm: Add core support for sink side PPS
@ 2018-03-20 14:33   ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 35+ messages in thread
From: Adam Thomson @ 2018-03-20 14:33 UTC (permalink / raw)
  To: Heikki Krogerus, Guenter Roeck, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Jun Li
  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>
Acked-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
---
 drivers/usb/typec/tcpm.c | 524 +++++++++++++++++++++++++++++++++++++++++++++--
 include/linux/usb/pd.h   |   4 +-
 include/linux/usb/tcpm.h |   2 +-
 3 files changed, 513 insertions(+), 17 deletions(-)

diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c
index 4c0fc54..b4cf1ca 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;
@@ -259,6 +271,7 @@ struct tcpm_port {
 	unsigned int max_snk_ma;
 	unsigned int max_snk_mw;
 	unsigned int operating_snk_mw;
+	bool update_sink_caps;
 
 	/* Requested current / voltage */
 	u32 current_limit;
@@ -275,8 +288,13 @@ struct tcpm_port {
 	/* VDO to retry if UFP responder replied busy */
 	u32 vdo_retry;
 
-	/* Alternate mode data */
+	/* PPS */
+	struct pd_pps_data pps_data;
+	struct completion pps_complete;
+	bool pps_pending;
+	int pps_status;
 
+	/* Alternate mode data */
 	struct pd_mode_data mode_data;
 	struct typec_altmode *partner_altmode[SVID_DISCOVERY_MAX];
 	struct typec_altmode *port_altmode[SVID_DISCOVERY_MAX];
@@ -494,6 +512,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;
@@ -777,11 +805,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);
 	}
@@ -802,11 +832,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);
 	}
@@ -1173,6 +1205,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]);
@@ -1244,6 +1277,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[] = {
@@ -1259,6 +1294,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,
@@ -1308,6 +1347,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");
 			}
@@ -1333,11 +1392,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) {
@@ -1356,6 +1420,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.
@@ -1376,6 +1450,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;
@@ -1400,6 +1487,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)
 {
@@ -1476,6 +1572,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);
@@ -1498,6 +1602,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:
@@ -1652,6 +1763,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);
@@ -1761,6 +1873,8 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
 	unsigned int i, max_mw = 0, max_mv = 0;
 	int ret = -EINVAL;
 
+	port->pps_data.supported = false;
+
 	/*
 	 * Select the source PDO providing the most power while staying within
 	 * the board's voltage limits. Prefer PDO providing exp
@@ -1770,20 +1884,41 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
 		enum pd_pdo_type type = pdo_type(pdo);
 		unsigned int mv, ma, mw;
 
-		if (type == PDO_TYPE_FIXED)
+		switch (type) {
+		case PDO_TYPE_FIXED:
 			mv = pdo_fixed_voltage(pdo);
-		else
+			break;
+		case PDO_TYPE_BATT:
+		case PDO_TYPE_VAR:
 			mv = pdo_min_voltage(pdo);
+			break;
+		case PDO_TYPE_APDO:
+			if (pdo_apdo_type(pdo) == APDO_TYPE_PPS)
+				port->pps_data.supported = true;
+			continue;
+		default:
+			tcpm_log(port, "Invalid PDO type, ignoring");
+			continue;
+		}
 
-		if (type == PDO_TYPE_BATT) {
-			mw = pdo_max_power(pdo);
-		} else {
+		switch (type) {
+		case PDO_TYPE_FIXED:
+		case PDO_TYPE_VAR:
 			ma = min(pdo_max_current(pdo),
 				 port->max_snk_ma);
 			mw = ma * mv / 1000;
+			break;
+		case PDO_TYPE_BATT:
+			mw = pdo_max_power(pdo);
+			break;
+		case PDO_TYPE_APDO:
+			continue;
+		default:
+			tcpm_log(port, "Invalid PDO type, ignoring");
+			continue;
 		}
 
-		/* Perfer higher voltages if available */
+		/* Prefer higher voltages if available */
 		if ((mw > max_mw || (mw == max_mw && mv > max_mv)) &&
 		    mv <= port->max_snk_mv) {
 			ret = i;
@@ -1795,6 +1930,65 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
 	return ret;
 }
 
+static unsigned int tcpm_pd_select_pps_apdo(struct tcpm_port *port)
+{
+	unsigned int i, max_mw = 0, max_mv = 0;
+	unsigned int pps_min_mv, pps_max_mv, ma, mw;
+	enum pd_pdo_type type;
+	u32 pdo;
+	unsigned int index = 0;
+
+	/*
+	 * Select the source PPS APDO providing the most power while staying
+	 * within the board's limits. We skip the first PDO as this is always
+	 * 5V 3A.
+	 */
+	for (i = 1; i < port->nr_source_caps; ++i) {
+		pdo = port->source_caps[i];
+		type = pdo_type(pdo);
+
+		switch (type) {
+		case PDO_TYPE_APDO:
+			if (pdo_apdo_type(pdo) != APDO_TYPE_PPS) {
+				tcpm_log(port, "Not PPS APDO, ignoring");
+				continue;
+			}
+
+			pps_min_mv = pdo_pps_apdo_min_voltage(pdo);
+			pps_max_mv = pdo_pps_apdo_max_voltage(pdo);
+			ma = min(pdo_pps_apdo_max_current(pdo), port->max_snk_ma);
+			mw = (ma * pps_max_mv) / 1000;
+			break;
+		default:
+			tcpm_log(port, "Not APDO type, ignoring");
+			continue;
+		}
+
+		/* Prefer higher voltages if available */
+		if ((mw > max_mw || (mw == max_mw && pps_max_mv > max_mv)) &&
+		    pps_max_mv <= port->max_snk_mv) {
+			index = i;
+			max_mw = mw;
+			max_mv = pps_max_mv;
+		}
+	}
+
+	if (index) {
+		pdo = port->source_caps[index];
+
+		port->pps_data.min_volt = pdo_pps_apdo_min_voltage(pdo);
+		port->pps_data.max_volt = pdo_pps_apdo_max_voltage(pdo);
+		port->pps_data.max_curr =
+			min(pdo_pps_apdo_max_current(pdo), port->max_snk_ma);
+		port->pps_data.out_volt =
+			min(pdo_pps_apdo_max_voltage(pdo), port->pps_data.out_volt);
+		port->pps_data.op_curr =
+			min(pdo_pps_apdo_max_current(pdo), port->pps_data.op_curr);
+	}
+
+	return index;
+}
+
 static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo)
 {
 	unsigned int mv, ma, mw, flags;
@@ -1809,10 +2003,18 @@ static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo)
 	pdo = port->source_caps[index];
 	type = pdo_type(pdo);
 
-	if (type == PDO_TYPE_FIXED)
+	switch (type) {
+	case PDO_TYPE_FIXED:
 		mv = pdo_fixed_voltage(pdo);
-	else
+		break;
+	case PDO_TYPE_BATT:
+	case PDO_TYPE_VAR:
 		mv = pdo_min_voltage(pdo);
+		break;
+	default:
+		tcpm_log(port, "Invalid PDO selected!");
+		return -EINVAL;
+	}
 
 	/* Select maximum available current within the board's power limit */
 	if (type == PDO_TYPE_BATT) {
@@ -1875,6 +2077,105 @@ 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 index;
+	u32 pdo;
+
+	index = tcpm_pd_select_pps_apdo(port);
+	if (!index)
+		return -EOPNOTSUPP;
+
+	pdo = port->source_caps[index];
+	type = pdo_type(pdo);
+
+	switch (type) {
+	case PDO_TYPE_APDO:
+		if (pdo_apdo_type(pdo) != APDO_TYPE_PPS) {
+			tcpm_log(port, "Invalid APDO selected!");
+			return -EINVAL;
+		}
+		min_mv = port->pps_data.min_volt;
+		max_mv = port->pps_data.max_volt;
+		max_ma = port->pps_data.max_curr;
+		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 enough
+		 * 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(index + 1, out_mv, op_ma, flags);
+
+	tcpm_log(port, "Requesting APDO %d: %u mV, %u mA",
+		 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);
 
@@ -2060,6 +2361,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
@@ -2075,6 +2377,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)
@@ -2321,6 +2625,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;
@@ -2381,6 +2686,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
@@ -2404,6 +2710,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);
@@ -2412,6 +2719,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 &&
@@ -2493,6 +2801,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;
@@ -2563,6 +2872,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),
@@ -2570,6 +2897,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);
@@ -2578,7 +2906,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 */
@@ -2625,6 +2957,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);
@@ -2845,6 +3178,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:
@@ -3310,7 +3644,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;
@@ -3355,7 +3689,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;
@@ -3395,7 +3729,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;
@@ -3427,6 +3761,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;
@@ -3566,13 +4056,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;
@@ -3614,6 +4109,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,
@@ -3642,7 +4138,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] 35+ messages in thread

* [PATCH v5 2/5] Documentation: power: Initial effort to document power_supply ABI
@ 2018-03-20 14:33   ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 35+ messages in thread
From: Adam Thomson @ 2018-03-20 14:33 UTC (permalink / raw)
  To: Heikki Krogerus, Guenter Roeck, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Jun Li
  Cc: linux-usb, linux-pm, linux-kernel, support.opensource

This commit adds generic ABI information regarding power_supply
properties. This is an initial attempt to try and align the usage
of these properties between drivers. As part of this commit,
common Battery and USB related properties have been listed.

Signed-off-by: Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
---
 Documentation/ABI/testing/sysfs-class-power | 443 ++++++++++++++++++++++++++++
 MAINTAINERS                                 |   1 +
 2 files changed, 444 insertions(+)

diff --git a/Documentation/ABI/testing/sysfs-class-power b/Documentation/ABI/testing/sysfs-class-power
index f85ce9e..e046566 100644
--- a/Documentation/ABI/testing/sysfs-class-power
+++ b/Documentation/ABI/testing/sysfs-class-power
@@ -1,3 +1,446 @@
+===== General Properties =====
+
+What:		/sys/class/power_supply/<supply_name>/manufacturer
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the name of the device manufacturer.
+
+		Access: Read
+		Valid values: Represented as string
+
+What:		/sys/class/power_supply/<supply_name>/model_name
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the name of the device model.
+
+		Access: Read
+		Valid values: Represented as string
+
+What:		/sys/class/power_supply/<supply_name>/serial_number
+Date:		January 2008
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the serial number of the device.
+
+		Access: Read
+		Valid values: Represented as string
+
+What:		/sys/class/power_supply/<supply_name>/type
+Date:		May 2010
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Describes the main type of the supply.
+
+		Access: Read
+		Valid values: "Battery", "UPS", "Mains", "USB"
+
+===== Battery Properties =====
+
+What:		/sys/class/power_supply/<supply_name>/capacity
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Fine grain representation of battery capacity.
+		Access: Read
+		Valid values: 0 - 100 (percent)
+
+What:		/sys/class/power_supply/<supply_name>/capacity_alert_max
+Date:		July 2012
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Maximum battery capacity trip-wire value where the supply will
+		notify user-space of the event. This is normally used for the
+		battery discharging scenario where user-space needs to know the
+		battery has dropped to an upper level so it can take
+		appropriate action (e.g. warning user that battery level is
+		low).
+
+		Access: Read, Write
+		Valid values: 0 - 100 (percent)
+
+What:		/sys/class/power_supply/<supply_name>/capacity_alert_min
+Date:		July 2012
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Minimum battery capacity trip-wire value where the supply will
+		notify user-space of the event. This is normally used for the
+		battery discharging scenario where user-space needs to know the
+		battery has dropped to a lower level so it can take
+		appropriate action (e.g. warning user that battery level is
+		critically low).
+
+		Access: Read, Write
+		Valid values: 0 - 100 (percent)
+
+What:		/sys/class/power_supply/<supply_name>/capacity_level
+Date:		June 2009
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Coarse representation of battery capacity.
+
+		Access: Read
+		Valid values: "Unknown", "Critical", "Low", "Normal", "High",
+			      "Full"
+
+What:		/sys/class/power_supply/<supply_name>/current_avg
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports an average IBAT current reading for the battery, over a
+		fixed period. Normally devices will provide a fixed interval in
+		which they average readings to smooth out the reported value.
+
+		Access: Read
+		Valid values: Represented in microamps
+
+What:		/sys/class/power_supply/<supply_name>/current_max
+Date:		October 2010
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the maximum IBAT current allowed into the battery.
+
+		Access: Read
+		Valid values: Represented in microamps
+
+What:		/sys/class/power_supply/<supply_name>/current_now
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports an instant, single IBAT current reading for the battery.
+		This value is not averaged/smoothed.
+
+		Access: Read
+		Valid values: Represented in microamps
+
+What:		/sys/class/power_supply/<supply_name>/charge_type
+Date:		July 2009
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Represents the type of charging currently being applied to the
+		battery.
+
+		Access: Read
+		Valid values: "Unknown", "N/A", "Trickle", "Fast"
+
+What:		/sys/class/power_supply/<supply_name>/charge_term_current
+Date:		July 2014
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the charging current value which is used to determine
+		when the battery is considered full and charging should end.
+
+		Access: Read
+		Valid values: Represented in microamps
+
+What:		/sys/class/power_supply/<supply_name>/health
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the health of the battery or battery side of charger
+		functionality.
+
+		Access: Read
+		Valid values: "Unknown", "Good", "Overheat", "Dead",
+			      "Over voltage", "Unspecified failure", "Cold",
+			      "Watchdog timer expire", "Safety timer expire"
+
+What:		/sys/class/power_supply/<supply_name>/precharge_current
+Date:		June 2017
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the charging current applied during pre-charging phase
+		for a battery charge cycle.
+
+		Access: Read
+		Valid values: Represented in microamps
+
+What:		/sys/class/power_supply/<supply_name>/present
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports whether a battery is present or not in the system.
+
+		Access: Read
+		Valid values:
+			0: Absent
+			1: Present
+
+What:		/sys/class/power_supply/<supply_name>/status
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Represents the charging status of the battery. Normally this
+		is read-only reporting although for some supplies this can be
+		used to enable/disable charging to the battery.
+
+		Access: Read, Write
+		Valid values: "Unknown", "Charging", "Discharging",
+			      "Not charging", "Full"
+
+What:		/sys/class/power_supply/<supply_name>/technology
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Describes the battery technology supported by the supply.
+
+		Access: Read
+		Valid values: "Unknown", "NiMH", "Li-ion", "Li-poly", "LiFe",
+			      "NiCd", "LiMn"
+
+What:		/sys/class/power_supply/<supply_name>/temp
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the current TBAT battery temperature reading.
+
+		Access: Read
+		Valid values: Represented in 1/10 Degrees Celsius
+
+What:		/sys/class/power_supply/<supply_name>/temp_alert_max
+Date:		July 2012
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Maximum TBAT temperature trip-wire value where the supply will
+		notify user-space of the event. This is normally used for the
+		battery charging scenario where user-space needs to know the
+		battery temperature has crossed an upper threshold so it can
+		take appropriate action (e.g. warning user that battery level is
+		critically high, and charging has stopped).
+
+		Access: Read
+		Valid values: Represented in 1/10 Degrees Celsius
+
+What:		/sys/class/power_supply/<supply_name>/temp_alert_min
+Date:		July 2012
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Minimum TBAT temperature trip-wire value where the supply will
+		notify user-space of the event. This is normally used for the
+		battery charging scenario where user-space needs to know the
+		battery temperature has crossed a lower threshold so it can take
+		appropriate action (e.g. warning user that battery level is
+		high, and charging current has been reduced accordingly to
+		remedy the situation).
+
+		Access: Read
+		Valid values: Represented in 1/10 Degrees Celsius
+
+What:		/sys/class/power_supply/<supply_name>/temp_max
+Date:		July 2014
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the maximum allowed TBAT battery temperature for
+		charging.
+
+		Access: Read
+		Valid values: Represented in 1/10 Degrees Celsius
+
+What:		/sys/class/power_supply/<supply_name>/temp_min
+Date:		July 2014
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the minimum allowed TBAT battery temperature for
+		charging.
+
+		Access: Read
+		Valid values: Represented in 1/10 Degrees Celsius
+
+What:		/sys/class/power_supply/<supply_name>/voltage_avg,
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports an average VBAT voltage reading for the battery, over a
+		fixed period. Normally devices will provide a fixed interval in
+		which they average readings to smooth out the reported value.
+
+		Access: Read
+		Valid values: Represented in microvolts
+
+What:		/sys/class/power_supply/<supply_name>/voltage_max,
+Date:		January 2008
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the maximum safe VBAT voltage permitted for the battery,
+		during charging.
+
+		Access: Read
+		Valid values: Represented in microvolts
+
+What:		/sys/class/power_supply/<supply_name>/voltage_min,
+Date:		January 2008
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the minimum safe VBAT voltage permitted for the battery,
+		during discharging.
+
+		Access: Read
+		Valid values: Represented in microvolts
+
+What:		/sys/class/power_supply/<supply_name>/voltage_now,
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports an instant, single VBAT voltage reading for the battery.
+		This value is not averaged/smoothed.
+
+		Access: Read
+		Valid values: Represented in microvolts
+
+===== USB Properties =====
+
+What: 		/sys/class/power_supply/<supply_name>/current_avg
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports an average IBUS current reading over a fixed period.
+		Normally devices will provide a fixed interval in which they
+		average readings to smooth out the reported value.
+
+		Access: Read
+		Valid values: Represented in microamps
+
+
+What: 		/sys/class/power_supply/<supply_name>/current_max
+Date:		October 2010
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the maximum IBUS current the supply can support.
+
+		Access: Read
+		Valid values: Represented in microamps
+
+What: 		/sys/class/power_supply/<supply_name>/current_now
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the IBUS current supplied now. This value is generally
+		read-only reporting, unless the 'online' state of the supply
+		is set to be programmable, in which case this value can be set
+		within the reported min/max range.
+
+		Access: Read, Write
+		Valid values: Represented in microamps
+
+What:		/sys/class/power_supply/<supply_name>/input_current_limit
+Date:		July 2014
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Details the incoming IBUS current limit currently set in the
+		supply. Normally this is configured based on the type of
+		connection made (e.g. A configured SDP should output a maximum
+		of 500mA so the input current limit is set to the same value).
+
+		Access: Read, Write
+		Valid values: Represented in microamps
+
+What:		/sys/class/power_supply/<supply_name>/online,
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Indicates if VBUS is present for the supply. When the supply is
+		online, and the supply allows it, then it's possible to switch
+		between online states (e.g. Fixed -> Programmable for a PD_PPS
+		USB supply so voltage and current can be controlled).
+
+		Access: Read, Write
+		Valid values:
+			0: Offline
+			1: Online Fixed - Fixed Voltage Supply
+			2: Online Programmable - Programmable Voltage Supply
+
+What:		/sys/class/power_supply/<supply_name>/temp
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the current supply temperature reading. This would
+		normally be the internal temperature of the device itself (e.g
+		TJUNC temperature of an IC)
+
+		Access: Read
+		Valid values: Represented in 1/10 Degrees Celsius
+
+What:		/sys/class/power_supply/<supply_name>/temp_alert_max
+Date:		July 2012
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Maximum supply temperature trip-wire value where the supply will
+		notify user-space of the event. This is normally used for the
+		charging scenario where user-space needs to know the supply
+		temperature has crossed an upper threshold so it can take
+		appropriate action (e.g. warning user that the supply
+		temperature is critically high, and charging has stopped to
+		remedy the situation).
+
+		Access: Read
+		Valid values: Represented in 1/10 Degrees Celsius
+
+What:		/sys/class/power_supply/<supply_name>/temp_alert_min
+Date:		July 2012
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Minimum supply temperature trip-wire value where the supply will
+		notify user-space of the event. This is normally used for the
+		charging scenario where user-space needs to know the supply
+		temperature has crossed a lower threshold so it can take
+		appropriate action (e.g. warning user that the supply
+		temperature is high, and charging current has been reduced
+		accordingly to remedy the situation).
+
+		Access: Read
+		Valid values: Represented in 1/10 Degrees Celsius
+
+What:		/sys/class/power_supply/<supply_name>/temp_max
+Date:		July 2014
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the maximum allowed supply temperature for operation.
+
+		Access: Read
+		Valid values: Represented in 1/10 Degrees Celsius
+
+What:		/sys/class/power_supply/<supply_name>/temp_min
+Date:		July 2014
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the mainimum allowed supply temperature for operation.
+
+		Access: Read
+		Valid values: Represented in 1/10 Degrees Celsius
+
+What: 		/sys/class/power_supply/<supply_name>/voltage_max
+Date:		January 2008
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the maximum VBUS voltage the supply can support.
+
+		Access: Read
+		Valid values: Represented in microvolts
+
+What: 		/sys/class/power_supply/<supply_name>/voltage_min
+Date:		January 2008
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the minimum VBUS voltage the supply can support.
+
+		Access: Read
+		Valid values: Represented in microvolts
+
+What: 		/sys/class/power_supply/<supply_name>/voltage_now
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the VBUS voltage supplied now. This value is generally
+		read-only reporting, unless the 'online' state of the supply
+		is set to be programmable, in which case this value can be set
+		within the reported min/max range.
+
+		Access: Read, Write
+		Valid values: Represented in microvolts
+
+===== Device Specific Properties =====
+
 What:		/sys/class/power/ds2760-battery.*/charge_now
 Date:		May 2010
 KernelVersion:	2.6.35
diff --git a/MAINTAINERS b/MAINTAINERS
index 2bee7ac..5bb0a10 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11098,6 +11098,7 @@ M:	Sebastian Reichel <sre@kernel.org>
 L:	linux-pm@vger.kernel.org
 T:	git git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply.git
 S:	Maintained
+F:	Documentation/ABI/testing/sysfs-class-power
 F:	Documentation/devicetree/bindings/power/supply/
 F:	include/linux/power_supply.h
 F:	drivers/power/supply/
-- 
1.9.1


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

* [v5,2/5] Documentation: power: Initial effort to document power_supply ABI
@ 2018-03-20 14:33   ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 35+ messages in thread
From: Opensource [Adam Thomson] @ 2018-03-20 14:33 UTC (permalink / raw)
  To: Heikki Krogerus, Guenter Roeck, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Jun Li
  Cc: linux-usb, linux-pm, linux-kernel, support.opensource

This commit adds generic ABI information regarding power_supply
properties. This is an initial attempt to try and align the usage
of these properties between drivers. As part of this commit,
common Battery and USB related properties have been listed.

Signed-off-by: Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
---
 Documentation/ABI/testing/sysfs-class-power | 443 ++++++++++++++++++++++++++++
 MAINTAINERS                                 |   1 +
 2 files changed, 444 insertions(+)

diff --git a/Documentation/ABI/testing/sysfs-class-power b/Documentation/ABI/testing/sysfs-class-power
index f85ce9e..e046566 100644
--- a/Documentation/ABI/testing/sysfs-class-power
+++ b/Documentation/ABI/testing/sysfs-class-power
@@ -1,3 +1,446 @@
+===== General Properties =====
+
+What:		/sys/class/power_supply/<supply_name>/manufacturer
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the name of the device manufacturer.
+
+		Access: Read
+		Valid values: Represented as string
+
+What:		/sys/class/power_supply/<supply_name>/model_name
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the name of the device model.
+
+		Access: Read
+		Valid values: Represented as string
+
+What:		/sys/class/power_supply/<supply_name>/serial_number
+Date:		January 2008
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the serial number of the device.
+
+		Access: Read
+		Valid values: Represented as string
+
+What:		/sys/class/power_supply/<supply_name>/type
+Date:		May 2010
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Describes the main type of the supply.
+
+		Access: Read
+		Valid values: "Battery", "UPS", "Mains", "USB"
+
+===== Battery Properties =====
+
+What:		/sys/class/power_supply/<supply_name>/capacity
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Fine grain representation of battery capacity.
+		Access: Read
+		Valid values: 0 - 100 (percent)
+
+What:		/sys/class/power_supply/<supply_name>/capacity_alert_max
+Date:		July 2012
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Maximum battery capacity trip-wire value where the supply will
+		notify user-space of the event. This is normally used for the
+		battery discharging scenario where user-space needs to know the
+		battery has dropped to an upper level so it can take
+		appropriate action (e.g. warning user that battery level is
+		low).
+
+		Access: Read, Write
+		Valid values: 0 - 100 (percent)
+
+What:		/sys/class/power_supply/<supply_name>/capacity_alert_min
+Date:		July 2012
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Minimum battery capacity trip-wire value where the supply will
+		notify user-space of the event. This is normally used for the
+		battery discharging scenario where user-space needs to know the
+		battery has dropped to a lower level so it can take
+		appropriate action (e.g. warning user that battery level is
+		critically low).
+
+		Access: Read, Write
+		Valid values: 0 - 100 (percent)
+
+What:		/sys/class/power_supply/<supply_name>/capacity_level
+Date:		June 2009
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Coarse representation of battery capacity.
+
+		Access: Read
+		Valid values: "Unknown", "Critical", "Low", "Normal", "High",
+			      "Full"
+
+What:		/sys/class/power_supply/<supply_name>/current_avg
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports an average IBAT current reading for the battery, over a
+		fixed period. Normally devices will provide a fixed interval in
+		which they average readings to smooth out the reported value.
+
+		Access: Read
+		Valid values: Represented in microamps
+
+What:		/sys/class/power_supply/<supply_name>/current_max
+Date:		October 2010
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the maximum IBAT current allowed into the battery.
+
+		Access: Read
+		Valid values: Represented in microamps
+
+What:		/sys/class/power_supply/<supply_name>/current_now
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports an instant, single IBAT current reading for the battery.
+		This value is not averaged/smoothed.
+
+		Access: Read
+		Valid values: Represented in microamps
+
+What:		/sys/class/power_supply/<supply_name>/charge_type
+Date:		July 2009
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Represents the type of charging currently being applied to the
+		battery.
+
+		Access: Read
+		Valid values: "Unknown", "N/A", "Trickle", "Fast"
+
+What:		/sys/class/power_supply/<supply_name>/charge_term_current
+Date:		July 2014
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the charging current value which is used to determine
+		when the battery is considered full and charging should end.
+
+		Access: Read
+		Valid values: Represented in microamps
+
+What:		/sys/class/power_supply/<supply_name>/health
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the health of the battery or battery side of charger
+		functionality.
+
+		Access: Read
+		Valid values: "Unknown", "Good", "Overheat", "Dead",
+			      "Over voltage", "Unspecified failure", "Cold",
+			      "Watchdog timer expire", "Safety timer expire"
+
+What:		/sys/class/power_supply/<supply_name>/precharge_current
+Date:		June 2017
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the charging current applied during pre-charging phase
+		for a battery charge cycle.
+
+		Access: Read
+		Valid values: Represented in microamps
+
+What:		/sys/class/power_supply/<supply_name>/present
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports whether a battery is present or not in the system.
+
+		Access: Read
+		Valid values:
+			0: Absent
+			1: Present
+
+What:		/sys/class/power_supply/<supply_name>/status
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Represents the charging status of the battery. Normally this
+		is read-only reporting although for some supplies this can be
+		used to enable/disable charging to the battery.
+
+		Access: Read, Write
+		Valid values: "Unknown", "Charging", "Discharging",
+			      "Not charging", "Full"
+
+What:		/sys/class/power_supply/<supply_name>/technology
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Describes the battery technology supported by the supply.
+
+		Access: Read
+		Valid values: "Unknown", "NiMH", "Li-ion", "Li-poly", "LiFe",
+			      "NiCd", "LiMn"
+
+What:		/sys/class/power_supply/<supply_name>/temp
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the current TBAT battery temperature reading.
+
+		Access: Read
+		Valid values: Represented in 1/10 Degrees Celsius
+
+What:		/sys/class/power_supply/<supply_name>/temp_alert_max
+Date:		July 2012
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Maximum TBAT temperature trip-wire value where the supply will
+		notify user-space of the event. This is normally used for the
+		battery charging scenario where user-space needs to know the
+		battery temperature has crossed an upper threshold so it can
+		take appropriate action (e.g. warning user that battery level is
+		critically high, and charging has stopped).
+
+		Access: Read
+		Valid values: Represented in 1/10 Degrees Celsius
+
+What:		/sys/class/power_supply/<supply_name>/temp_alert_min
+Date:		July 2012
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Minimum TBAT temperature trip-wire value where the supply will
+		notify user-space of the event. This is normally used for the
+		battery charging scenario where user-space needs to know the
+		battery temperature has crossed a lower threshold so it can take
+		appropriate action (e.g. warning user that battery level is
+		high, and charging current has been reduced accordingly to
+		remedy the situation).
+
+		Access: Read
+		Valid values: Represented in 1/10 Degrees Celsius
+
+What:		/sys/class/power_supply/<supply_name>/temp_max
+Date:		July 2014
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the maximum allowed TBAT battery temperature for
+		charging.
+
+		Access: Read
+		Valid values: Represented in 1/10 Degrees Celsius
+
+What:		/sys/class/power_supply/<supply_name>/temp_min
+Date:		July 2014
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the minimum allowed TBAT battery temperature for
+		charging.
+
+		Access: Read
+		Valid values: Represented in 1/10 Degrees Celsius
+
+What:		/sys/class/power_supply/<supply_name>/voltage_avg,
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports an average VBAT voltage reading for the battery, over a
+		fixed period. Normally devices will provide a fixed interval in
+		which they average readings to smooth out the reported value.
+
+		Access: Read
+		Valid values: Represented in microvolts
+
+What:		/sys/class/power_supply/<supply_name>/voltage_max,
+Date:		January 2008
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the maximum safe VBAT voltage permitted for the battery,
+		during charging.
+
+		Access: Read
+		Valid values: Represented in microvolts
+
+What:		/sys/class/power_supply/<supply_name>/voltage_min,
+Date:		January 2008
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the minimum safe VBAT voltage permitted for the battery,
+		during discharging.
+
+		Access: Read
+		Valid values: Represented in microvolts
+
+What:		/sys/class/power_supply/<supply_name>/voltage_now,
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports an instant, single VBAT voltage reading for the battery.
+		This value is not averaged/smoothed.
+
+		Access: Read
+		Valid values: Represented in microvolts
+
+===== USB Properties =====
+
+What: 		/sys/class/power_supply/<supply_name>/current_avg
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports an average IBUS current reading over a fixed period.
+		Normally devices will provide a fixed interval in which they
+		average readings to smooth out the reported value.
+
+		Access: Read
+		Valid values: Represented in microamps
+
+
+What: 		/sys/class/power_supply/<supply_name>/current_max
+Date:		October 2010
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the maximum IBUS current the supply can support.
+
+		Access: Read
+		Valid values: Represented in microamps
+
+What: 		/sys/class/power_supply/<supply_name>/current_now
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the IBUS current supplied now. This value is generally
+		read-only reporting, unless the 'online' state of the supply
+		is set to be programmable, in which case this value can be set
+		within the reported min/max range.
+
+		Access: Read, Write
+		Valid values: Represented in microamps
+
+What:		/sys/class/power_supply/<supply_name>/input_current_limit
+Date:		July 2014
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Details the incoming IBUS current limit currently set in the
+		supply. Normally this is configured based on the type of
+		connection made (e.g. A configured SDP should output a maximum
+		of 500mA so the input current limit is set to the same value).
+
+		Access: Read, Write
+		Valid values: Represented in microamps
+
+What:		/sys/class/power_supply/<supply_name>/online,
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Indicates if VBUS is present for the supply. When the supply is
+		online, and the supply allows it, then it's possible to switch
+		between online states (e.g. Fixed -> Programmable for a PD_PPS
+		USB supply so voltage and current can be controlled).
+
+		Access: Read, Write
+		Valid values:
+			0: Offline
+			1: Online Fixed - Fixed Voltage Supply
+			2: Online Programmable - Programmable Voltage Supply
+
+What:		/sys/class/power_supply/<supply_name>/temp
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the current supply temperature reading. This would
+		normally be the internal temperature of the device itself (e.g
+		TJUNC temperature of an IC)
+
+		Access: Read
+		Valid values: Represented in 1/10 Degrees Celsius
+
+What:		/sys/class/power_supply/<supply_name>/temp_alert_max
+Date:		July 2012
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Maximum supply temperature trip-wire value where the supply will
+		notify user-space of the event. This is normally used for the
+		charging scenario where user-space needs to know the supply
+		temperature has crossed an upper threshold so it can take
+		appropriate action (e.g. warning user that the supply
+		temperature is critically high, and charging has stopped to
+		remedy the situation).
+
+		Access: Read
+		Valid values: Represented in 1/10 Degrees Celsius
+
+What:		/sys/class/power_supply/<supply_name>/temp_alert_min
+Date:		July 2012
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Minimum supply temperature trip-wire value where the supply will
+		notify user-space of the event. This is normally used for the
+		charging scenario where user-space needs to know the supply
+		temperature has crossed a lower threshold so it can take
+		appropriate action (e.g. warning user that the supply
+		temperature is high, and charging current has been reduced
+		accordingly to remedy the situation).
+
+		Access: Read
+		Valid values: Represented in 1/10 Degrees Celsius
+
+What:		/sys/class/power_supply/<supply_name>/temp_max
+Date:		July 2014
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the maximum allowed supply temperature for operation.
+
+		Access: Read
+		Valid values: Represented in 1/10 Degrees Celsius
+
+What:		/sys/class/power_supply/<supply_name>/temp_min
+Date:		July 2014
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the mainimum allowed supply temperature for operation.
+
+		Access: Read
+		Valid values: Represented in 1/10 Degrees Celsius
+
+What: 		/sys/class/power_supply/<supply_name>/voltage_max
+Date:		January 2008
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the maximum VBUS voltage the supply can support.
+
+		Access: Read
+		Valid values: Represented in microvolts
+
+What: 		/sys/class/power_supply/<supply_name>/voltage_min
+Date:		January 2008
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the minimum VBUS voltage the supply can support.
+
+		Access: Read
+		Valid values: Represented in microvolts
+
+What: 		/sys/class/power_supply/<supply_name>/voltage_now
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the VBUS voltage supplied now. This value is generally
+		read-only reporting, unless the 'online' state of the supply
+		is set to be programmable, in which case this value can be set
+		within the reported min/max range.
+
+		Access: Read, Write
+		Valid values: Represented in microvolts
+
+===== Device Specific Properties =====
+
 What:		/sys/class/power/ds2760-battery.*/charge_now
 Date:		May 2010
 KernelVersion:	2.6.35
diff --git a/MAINTAINERS b/MAINTAINERS
index 2bee7ac..5bb0a10 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11098,6 +11098,7 @@ M:	Sebastian Reichel <sre@kernel.org>
 L:	linux-pm@vger.kernel.org
 T:	git git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply.git
 S:	Maintained
+F:	Documentation/ABI/testing/sysfs-class-power
 F:	Documentation/devicetree/bindings/power/supply/
 F:	include/linux/power_supply.h
 F:	drivers/power/supply/

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

* [PATCH v5 2/5] Documentation: power: Initial effort to document power_supply ABI
@ 2018-03-20 14:33   ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 35+ messages in thread
From: Adam Thomson @ 2018-03-20 14:33 UTC (permalink / raw)
  To: Heikki Krogerus, Guenter Roeck, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Jun Li
  Cc: linux-usb, linux-pm, linux-kernel, support.opensource

This commit adds generic ABI information regarding power_supply
properties. This is an initial attempt to try and align the usage
of these properties between drivers. As part of this commit,
common Battery and USB related properties have been listed.

Signed-off-by: Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
---
 Documentation/ABI/testing/sysfs-class-power | 443 ++++++++++++++++++++++++++++
 MAINTAINERS                                 |   1 +
 2 files changed, 444 insertions(+)

diff --git a/Documentation/ABI/testing/sysfs-class-power b/Documentation/ABI/testing/sysfs-class-power
index f85ce9e..e046566 100644
--- a/Documentation/ABI/testing/sysfs-class-power
+++ b/Documentation/ABI/testing/sysfs-class-power
@@ -1,3 +1,446 @@
+===== General Properties =====
+
+What:		/sys/class/power_supply/<supply_name>/manufacturer
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the name of the device manufacturer.
+
+		Access: Read
+		Valid values: Represented as string
+
+What:		/sys/class/power_supply/<supply_name>/model_name
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the name of the device model.
+
+		Access: Read
+		Valid values: Represented as string
+
+What:		/sys/class/power_supply/<supply_name>/serial_number
+Date:		January 2008
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the serial number of the device.
+
+		Access: Read
+		Valid values: Represented as string
+
+What:		/sys/class/power_supply/<supply_name>/type
+Date:		May 2010
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Describes the main type of the supply.
+
+		Access: Read
+		Valid values: "Battery", "UPS", "Mains", "USB"
+
+===== Battery Properties =====
+
+What:		/sys/class/power_supply/<supply_name>/capacity
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Fine grain representation of battery capacity.
+		Access: Read
+		Valid values: 0 - 100 (percent)
+
+What:		/sys/class/power_supply/<supply_name>/capacity_alert_max
+Date:		July 2012
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Maximum battery capacity trip-wire value where the supply will
+		notify user-space of the event. This is normally used for the
+		battery discharging scenario where user-space needs to know the
+		battery has dropped to an upper level so it can take
+		appropriate action (e.g. warning user that battery level is
+		low).
+
+		Access: Read, Write
+		Valid values: 0 - 100 (percent)
+
+What:		/sys/class/power_supply/<supply_name>/capacity_alert_min
+Date:		July 2012
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Minimum battery capacity trip-wire value where the supply will
+		notify user-space of the event. This is normally used for the
+		battery discharging scenario where user-space needs to know the
+		battery has dropped to a lower level so it can take
+		appropriate action (e.g. warning user that battery level is
+		critically low).
+
+		Access: Read, Write
+		Valid values: 0 - 100 (percent)
+
+What:		/sys/class/power_supply/<supply_name>/capacity_level
+Date:		June 2009
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Coarse representation of battery capacity.
+
+		Access: Read
+		Valid values: "Unknown", "Critical", "Low", "Normal", "High",
+			      "Full"
+
+What:		/sys/class/power_supply/<supply_name>/current_avg
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports an average IBAT current reading for the battery, over a
+		fixed period. Normally devices will provide a fixed interval in
+		which they average readings to smooth out the reported value.
+
+		Access: Read
+		Valid values: Represented in microamps
+
+What:		/sys/class/power_supply/<supply_name>/current_max
+Date:		October 2010
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the maximum IBAT current allowed into the battery.
+
+		Access: Read
+		Valid values: Represented in microamps
+
+What:		/sys/class/power_supply/<supply_name>/current_now
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports an instant, single IBAT current reading for the battery.
+		This value is not averaged/smoothed.
+
+		Access: Read
+		Valid values: Represented in microamps
+
+What:		/sys/class/power_supply/<supply_name>/charge_type
+Date:		July 2009
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Represents the type of charging currently being applied to the
+		battery.
+
+		Access: Read
+		Valid values: "Unknown", "N/A", "Trickle", "Fast"
+
+What:		/sys/class/power_supply/<supply_name>/charge_term_current
+Date:		July 2014
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the charging current value which is used to determine
+		when the battery is considered full and charging should end.
+
+		Access: Read
+		Valid values: Represented in microamps
+
+What:		/sys/class/power_supply/<supply_name>/health
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the health of the battery or battery side of charger
+		functionality.
+
+		Access: Read
+		Valid values: "Unknown", "Good", "Overheat", "Dead",
+			      "Over voltage", "Unspecified failure", "Cold",
+			      "Watchdog timer expire", "Safety timer expire"
+
+What:		/sys/class/power_supply/<supply_name>/precharge_current
+Date:		June 2017
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the charging current applied during pre-charging phase
+		for a battery charge cycle.
+
+		Access: Read
+		Valid values: Represented in microamps
+
+What:		/sys/class/power_supply/<supply_name>/present
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports whether a battery is present or not in the system.
+
+		Access: Read
+		Valid values:
+			0: Absent
+			1: Present
+
+What:		/sys/class/power_supply/<supply_name>/status
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Represents the charging status of the battery. Normally this
+		is read-only reporting although for some supplies this can be
+		used to enable/disable charging to the battery.
+
+		Access: Read, Write
+		Valid values: "Unknown", "Charging", "Discharging",
+			      "Not charging", "Full"
+
+What:		/sys/class/power_supply/<supply_name>/technology
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Describes the battery technology supported by the supply.
+
+		Access: Read
+		Valid values: "Unknown", "NiMH", "Li-ion", "Li-poly", "LiFe",
+			      "NiCd", "LiMn"
+
+What:		/sys/class/power_supply/<supply_name>/temp
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the current TBAT battery temperature reading.
+
+		Access: Read
+		Valid values: Represented in 1/10 Degrees Celsius
+
+What:		/sys/class/power_supply/<supply_name>/temp_alert_max
+Date:		July 2012
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Maximum TBAT temperature trip-wire value where the supply will
+		notify user-space of the event. This is normally used for the
+		battery charging scenario where user-space needs to know the
+		battery temperature has crossed an upper threshold so it can
+		take appropriate action (e.g. warning user that battery level is
+		critically high, and charging has stopped).
+
+		Access: Read
+		Valid values: Represented in 1/10 Degrees Celsius
+
+What:		/sys/class/power_supply/<supply_name>/temp_alert_min
+Date:		July 2012
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Minimum TBAT temperature trip-wire value where the supply will
+		notify user-space of the event. This is normally used for the
+		battery charging scenario where user-space needs to know the
+		battery temperature has crossed a lower threshold so it can take
+		appropriate action (e.g. warning user that battery level is
+		high, and charging current has been reduced accordingly to
+		remedy the situation).
+
+		Access: Read
+		Valid values: Represented in 1/10 Degrees Celsius
+
+What:		/sys/class/power_supply/<supply_name>/temp_max
+Date:		July 2014
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the maximum allowed TBAT battery temperature for
+		charging.
+
+		Access: Read
+		Valid values: Represented in 1/10 Degrees Celsius
+
+What:		/sys/class/power_supply/<supply_name>/temp_min
+Date:		July 2014
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the minimum allowed TBAT battery temperature for
+		charging.
+
+		Access: Read
+		Valid values: Represented in 1/10 Degrees Celsius
+
+What:		/sys/class/power_supply/<supply_name>/voltage_avg,
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports an average VBAT voltage reading for the battery, over a
+		fixed period. Normally devices will provide a fixed interval in
+		which they average readings to smooth out the reported value.
+
+		Access: Read
+		Valid values: Represented in microvolts
+
+What:		/sys/class/power_supply/<supply_name>/voltage_max,
+Date:		January 2008
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the maximum safe VBAT voltage permitted for the battery,
+		during charging.
+
+		Access: Read
+		Valid values: Represented in microvolts
+
+What:		/sys/class/power_supply/<supply_name>/voltage_min,
+Date:		January 2008
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the minimum safe VBAT voltage permitted for the battery,
+		during discharging.
+
+		Access: Read
+		Valid values: Represented in microvolts
+
+What:		/sys/class/power_supply/<supply_name>/voltage_now,
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports an instant, single VBAT voltage reading for the battery.
+		This value is not averaged/smoothed.
+
+		Access: Read
+		Valid values: Represented in microvolts
+
+===== USB Properties =====
+
+What: 		/sys/class/power_supply/<supply_name>/current_avg
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports an average IBUS current reading over a fixed period.
+		Normally devices will provide a fixed interval in which they
+		average readings to smooth out the reported value.
+
+		Access: Read
+		Valid values: Represented in microamps
+
+
+What: 		/sys/class/power_supply/<supply_name>/current_max
+Date:		October 2010
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the maximum IBUS current the supply can support.
+
+		Access: Read
+		Valid values: Represented in microamps
+
+What: 		/sys/class/power_supply/<supply_name>/current_now
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the IBUS current supplied now. This value is generally
+		read-only reporting, unless the 'online' state of the supply
+		is set to be programmable, in which case this value can be set
+		within the reported min/max range.
+
+		Access: Read, Write
+		Valid values: Represented in microamps
+
+What:		/sys/class/power_supply/<supply_name>/input_current_limit
+Date:		July 2014
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Details the incoming IBUS current limit currently set in the
+		supply. Normally this is configured based on the type of
+		connection made (e.g. A configured SDP should output a maximum
+		of 500mA so the input current limit is set to the same value).
+
+		Access: Read, Write
+		Valid values: Represented in microamps
+
+What:		/sys/class/power_supply/<supply_name>/online,
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Indicates if VBUS is present for the supply. When the supply is
+		online, and the supply allows it, then it's possible to switch
+		between online states (e.g. Fixed -> Programmable for a PD_PPS
+		USB supply so voltage and current can be controlled).
+
+		Access: Read, Write
+		Valid values:
+			0: Offline
+			1: Online Fixed - Fixed Voltage Supply
+			2: Online Programmable - Programmable Voltage Supply
+
+What:		/sys/class/power_supply/<supply_name>/temp
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the current supply temperature reading. This would
+		normally be the internal temperature of the device itself (e.g
+		TJUNC temperature of an IC)
+
+		Access: Read
+		Valid values: Represented in 1/10 Degrees Celsius
+
+What:		/sys/class/power_supply/<supply_name>/temp_alert_max
+Date:		July 2012
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Maximum supply temperature trip-wire value where the supply will
+		notify user-space of the event. This is normally used for the
+		charging scenario where user-space needs to know the supply
+		temperature has crossed an upper threshold so it can take
+		appropriate action (e.g. warning user that the supply
+		temperature is critically high, and charging has stopped to
+		remedy the situation).
+
+		Access: Read
+		Valid values: Represented in 1/10 Degrees Celsius
+
+What:		/sys/class/power_supply/<supply_name>/temp_alert_min
+Date:		July 2012
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Minimum supply temperature trip-wire value where the supply will
+		notify user-space of the event. This is normally used for the
+		charging scenario where user-space needs to know the supply
+		temperature has crossed a lower threshold so it can take
+		appropriate action (e.g. warning user that the supply
+		temperature is high, and charging current has been reduced
+		accordingly to remedy the situation).
+
+		Access: Read
+		Valid values: Represented in 1/10 Degrees Celsius
+
+What:		/sys/class/power_supply/<supply_name>/temp_max
+Date:		July 2014
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the maximum allowed supply temperature for operation.
+
+		Access: Read
+		Valid values: Represented in 1/10 Degrees Celsius
+
+What:		/sys/class/power_supply/<supply_name>/temp_min
+Date:		July 2014
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the mainimum allowed supply temperature for operation.
+
+		Access: Read
+		Valid values: Represented in 1/10 Degrees Celsius
+
+What: 		/sys/class/power_supply/<supply_name>/voltage_max
+Date:		January 2008
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the maximum VBUS voltage the supply can support.
+
+		Access: Read
+		Valid values: Represented in microvolts
+
+What: 		/sys/class/power_supply/<supply_name>/voltage_min
+Date:		January 2008
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the minimum VBUS voltage the supply can support.
+
+		Access: Read
+		Valid values: Represented in microvolts
+
+What: 		/sys/class/power_supply/<supply_name>/voltage_now
+Date:		May 2007
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports the VBUS voltage supplied now. This value is generally
+		read-only reporting, unless the 'online' state of the supply
+		is set to be programmable, in which case this value can be set
+		within the reported min/max range.
+
+		Access: Read, Write
+		Valid values: Represented in microvolts
+
+===== Device Specific Properties =====
+
 What:		/sys/class/power/ds2760-battery.*/charge_now
 Date:		May 2010
 KernelVersion:	2.6.35
diff --git a/MAINTAINERS b/MAINTAINERS
index 2bee7ac..5bb0a10 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11098,6 +11098,7 @@ M:	Sebastian Reichel <sre@kernel.org>
 L:	linux-pm@vger.kernel.org
 T:	git git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply.git
 S:	Maintained
+F:	Documentation/ABI/testing/sysfs-class-power
 F:	Documentation/devicetree/bindings/power/supply/
 F:	include/linux/power_supply.h
 F:	drivers/power/supply/
-- 
1.9.1

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

* [PATCH v5 3/5] power: supply: Add 'usb_type' property and supporting code
@ 2018-03-20 14:33   ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 35+ messages in thread
From: Adam Thomson @ 2018-03-20 14:33 UTC (permalink / raw)
  To: Heikki Krogerus, Guenter Roeck, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Jun Li
  Cc: linux-usb, linux-pm, linux-kernel, support.opensource

This commit adds the 'usb_type' property to represent USB supplies
which can report a number of different types 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, 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 'usb_type' property is intended to fill this void and show users
all possible USB 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 'USB', and this would be
static.

Currently the 'usb_type' enum contains all of the USB variant types
that exist for the 'type' enum at this time, and in addition has
SDP and PPS types. 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>
Acked-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.co.uk>
---
 Documentation/ABI/testing/sysfs-class-power | 12 +++++++
 drivers/power/supply/power_supply_sysfs.c   | 50 +++++++++++++++++++++++++++++
 include/linux/power_supply.h                | 16 +++++++++
 3 files changed, 78 insertions(+)

diff --git a/Documentation/ABI/testing/sysfs-class-power b/Documentation/ABI/testing/sysfs-class-power
index e046566..5e23e22 100644
--- a/Documentation/ABI/testing/sysfs-class-power
+++ b/Documentation/ABI/testing/sysfs-class-power
@@ -409,6 +409,18 @@ Description:
 		Access: Read
 		Valid values: Represented in 1/10 Degrees Celsius
 
+What: 		/sys/class/power_supply/<supply_name>/usb_type
+Date:		March 2018
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports what type of USB connection is currently active for
+		the supply, for example it can show if USB-PD capable source
+		is attached.
+
+		Access: Read-Only
+		Valid values: "Unknown", "SDP", "DCP", "CDP", "ACA", "C", "PD",
+			      "PD_DRP", "PD_PPS", "BrickID"
+
 What: 		/sys/class/power_supply/<supply_name>/voltage_max
 Date:		January 2008
 Contact:	linux-pm@vger.kernel.org
diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c
index 5204f11..b68def4 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_usb_type_text[] = {
+	"Unknown", "SDP", "DCP", "CDP", "ACA", "C",
+	"PD", "PD_DRP", "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_usb_type(struct device *dev,
+					  enum power_supply_usb_type *usb_types,
+					  ssize_t num_usb_types,
+					  union power_supply_propval *value,
+					  char *buf)
+{
+	enum power_supply_usb_type usb_type;
+	ssize_t count = 0;
+	bool match = false;
+	int i;
+
+	if ((!usb_types) || (num_usb_types <= 0)) {
+		dev_warn(dev, "driver has no valid connected types\n");
+		return -ENODATA;
+	}
+
+	for (i = 0; i < num_usb_types; ++i) {
+		usb_type = usb_types[i];
+
+		if (value->intval == usb_type) {
+			count += sprintf(buf + count, "[%s] ",
+					 power_supply_usb_type_text[usb_type]);
+			match = true;
+		} else {
+			count += sprintf(buf + count, "%s ",
+					 power_supply_usb_type_text[usb_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_USB_TYPE)
+		return power_supply_show_usb_type(dev, psy->desc->usb_types,
+						  psy->desc->num_usb_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(usb_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..4ca8876 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_USB_TYPE,
 	POWER_SUPPLY_PROP_SCOPE,
 	POWER_SUPPLY_PROP_PRECHARGE_CURRENT,
 	POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
@@ -170,6 +171,19 @@ enum power_supply_type {
 	POWER_SUPPLY_TYPE_APPLE_BRICK_ID,	/* Apple Charging Method */
 };
 
+enum power_supply_usb_type {
+	POWER_SUPPLY_USB_TYPE_UNKNOWN = 0,
+	POWER_SUPPLY_USB_TYPE_SDP,		/* Standard Downstream Port */
+	POWER_SUPPLY_USB_TYPE_DCP,		/* Dedicated Charging Port */
+	POWER_SUPPLY_USB_TYPE_CDP,		/* Charging Downstream Port */
+	POWER_SUPPLY_USB_TYPE_ACA,		/* Accessory Charger Adapters */
+	POWER_SUPPLY_USB_TYPE_C,		/* Type C Port */
+	POWER_SUPPLY_USB_TYPE_PD,		/* Power Delivery Port */
+	POWER_SUPPLY_USB_TYPE_PD_DRP,		/* PD Dual Role Port */
+	POWER_SUPPLY_USB_TYPE_PD_PPS,		/* PD Programmable Power Supply */
+	POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID,	/* Apple Charging Method */
+};
+
 enum power_supply_notifier_events {
 	PSY_EVENT_PROP_CHANGED,
 };
@@ -196,6 +210,8 @@ struct power_supply_config {
 struct power_supply_desc {
 	const char *name;
 	enum power_supply_type type;
+	enum power_supply_usb_type *usb_types;
+	size_t num_usb_types;
 	enum power_supply_property *properties;
 	size_t num_properties;
 
-- 
1.9.1

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

* [v5,3/5] power: supply: Add 'usb_type' property and supporting code
@ 2018-03-20 14:33   ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 35+ messages in thread
From: Opensource [Adam Thomson] @ 2018-03-20 14:33 UTC (permalink / raw)
  To: Heikki Krogerus, Guenter Roeck, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Jun Li
  Cc: linux-usb, linux-pm, linux-kernel, support.opensource

This commit adds the 'usb_type' property to represent USB supplies
which can report a number of different types 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, 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 'usb_type' property is intended to fill this void and show users
all possible USB 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 'USB', and this would be
static.

Currently the 'usb_type' enum contains all of the USB variant types
that exist for the 'type' enum at this time, and in addition has
SDP and PPS types. 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>
Acked-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.co.uk>
---
 Documentation/ABI/testing/sysfs-class-power | 12 +++++++
 drivers/power/supply/power_supply_sysfs.c   | 50 +++++++++++++++++++++++++++++
 include/linux/power_supply.h                | 16 +++++++++
 3 files changed, 78 insertions(+)

diff --git a/Documentation/ABI/testing/sysfs-class-power b/Documentation/ABI/testing/sysfs-class-power
index e046566..5e23e22 100644
--- a/Documentation/ABI/testing/sysfs-class-power
+++ b/Documentation/ABI/testing/sysfs-class-power
@@ -409,6 +409,18 @@ Description:
 		Access: Read
 		Valid values: Represented in 1/10 Degrees Celsius
 
+What: 		/sys/class/power_supply/<supply_name>/usb_type
+Date:		March 2018
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports what type of USB connection is currently active for
+		the supply, for example it can show if USB-PD capable source
+		is attached.
+
+		Access: Read-Only
+		Valid values: "Unknown", "SDP", "DCP", "CDP", "ACA", "C", "PD",
+			      "PD_DRP", "PD_PPS", "BrickID"
+
 What: 		/sys/class/power_supply/<supply_name>/voltage_max
 Date:		January 2008
 Contact:	linux-pm@vger.kernel.org
diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c
index 5204f11..b68def4 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_usb_type_text[] = {
+	"Unknown", "SDP", "DCP", "CDP", "ACA", "C",
+	"PD", "PD_DRP", "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_usb_type(struct device *dev,
+					  enum power_supply_usb_type *usb_types,
+					  ssize_t num_usb_types,
+					  union power_supply_propval *value,
+					  char *buf)
+{
+	enum power_supply_usb_type usb_type;
+	ssize_t count = 0;
+	bool match = false;
+	int i;
+
+	if ((!usb_types) || (num_usb_types <= 0)) {
+		dev_warn(dev, "driver has no valid connected types\n");
+		return -ENODATA;
+	}
+
+	for (i = 0; i < num_usb_types; ++i) {
+		usb_type = usb_types[i];
+
+		if (value->intval == usb_type) {
+			count += sprintf(buf + count, "[%s] ",
+					 power_supply_usb_type_text[usb_type]);
+			match = true;
+		} else {
+			count += sprintf(buf + count, "%s ",
+					 power_supply_usb_type_text[usb_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_USB_TYPE)
+		return power_supply_show_usb_type(dev, psy->desc->usb_types,
+						  psy->desc->num_usb_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(usb_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..4ca8876 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_USB_TYPE,
 	POWER_SUPPLY_PROP_SCOPE,
 	POWER_SUPPLY_PROP_PRECHARGE_CURRENT,
 	POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
@@ -170,6 +171,19 @@ enum power_supply_type {
 	POWER_SUPPLY_TYPE_APPLE_BRICK_ID,	/* Apple Charging Method */
 };
 
+enum power_supply_usb_type {
+	POWER_SUPPLY_USB_TYPE_UNKNOWN = 0,
+	POWER_SUPPLY_USB_TYPE_SDP,		/* Standard Downstream Port */
+	POWER_SUPPLY_USB_TYPE_DCP,		/* Dedicated Charging Port */
+	POWER_SUPPLY_USB_TYPE_CDP,		/* Charging Downstream Port */
+	POWER_SUPPLY_USB_TYPE_ACA,		/* Accessory Charger Adapters */
+	POWER_SUPPLY_USB_TYPE_C,		/* Type C Port */
+	POWER_SUPPLY_USB_TYPE_PD,		/* Power Delivery Port */
+	POWER_SUPPLY_USB_TYPE_PD_DRP,		/* PD Dual Role Port */
+	POWER_SUPPLY_USB_TYPE_PD_PPS,		/* PD Programmable Power Supply */
+	POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID,	/* Apple Charging Method */
+};
+
 enum power_supply_notifier_events {
 	PSY_EVENT_PROP_CHANGED,
 };
@@ -196,6 +210,8 @@ struct power_supply_config {
 struct power_supply_desc {
 	const char *name;
 	enum power_supply_type type;
+	enum power_supply_usb_type *usb_types;
+	size_t num_usb_types;
 	enum power_supply_property *properties;
 	size_t num_properties;
 

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

* [PATCH v5 3/5] power: supply: Add 'usb_type' property and supporting code
@ 2018-03-20 14:33   ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 35+ messages in thread
From: Adam Thomson @ 2018-03-20 14:33 UTC (permalink / raw)
  To: Heikki Krogerus, Guenter Roeck, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Jun Li
  Cc: linux-usb, linux-pm, linux-kernel, support.opensource

This commit adds the 'usb_type' property to represent USB supplies
which can report a number of different types 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, 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 'usb_type' property is intended to fill this void and show users
all possible USB 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 'USB', and this would be
static.

Currently the 'usb_type' enum contains all of the USB variant types
that exist for the 'type' enum at this time, and in addition has
SDP and PPS types. 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>
Acked-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.co.uk>
---
 Documentation/ABI/testing/sysfs-class-power | 12 +++++++
 drivers/power/supply/power_supply_sysfs.c   | 50 +++++++++++++++++++++++++++++
 include/linux/power_supply.h                | 16 +++++++++
 3 files changed, 78 insertions(+)

diff --git a/Documentation/ABI/testing/sysfs-class-power b/Documentation/ABI/testing/sysfs-class-power
index e046566..5e23e22 100644
--- a/Documentation/ABI/testing/sysfs-class-power
+++ b/Documentation/ABI/testing/sysfs-class-power
@@ -409,6 +409,18 @@ Description:
 		Access: Read
 		Valid values: Represented in 1/10 Degrees Celsius
 
+What: 		/sys/class/power_supply/<supply_name>/usb_type
+Date:		March 2018
+Contact:	linux-pm@vger.kernel.org
+Description:
+		Reports what type of USB connection is currently active for
+		the supply, for example it can show if USB-PD capable source
+		is attached.
+
+		Access: Read-Only
+		Valid values: "Unknown", "SDP", "DCP", "CDP", "ACA", "C", "PD",
+			      "PD_DRP", "PD_PPS", "BrickID"
+
 What: 		/sys/class/power_supply/<supply_name>/voltage_max
 Date:		January 2008
 Contact:	linux-pm@vger.kernel.org
diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c
index 5204f11..b68def4 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_usb_type_text[] = {
+	"Unknown", "SDP", "DCP", "CDP", "ACA", "C",
+	"PD", "PD_DRP", "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_usb_type(struct device *dev,
+					  enum power_supply_usb_type *usb_types,
+					  ssize_t num_usb_types,
+					  union power_supply_propval *value,
+					  char *buf)
+{
+	enum power_supply_usb_type usb_type;
+	ssize_t count = 0;
+	bool match = false;
+	int i;
+
+	if ((!usb_types) || (num_usb_types <= 0)) {
+		dev_warn(dev, "driver has no valid connected types\n");
+		return -ENODATA;
+	}
+
+	for (i = 0; i < num_usb_types; ++i) {
+		usb_type = usb_types[i];
+
+		if (value->intval == usb_type) {
+			count += sprintf(buf + count, "[%s] ",
+					 power_supply_usb_type_text[usb_type]);
+			match = true;
+		} else {
+			count += sprintf(buf + count, "%s ",
+					 power_supply_usb_type_text[usb_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_USB_TYPE)
+		return power_supply_show_usb_type(dev, psy->desc->usb_types,
+						  psy->desc->num_usb_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(usb_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..4ca8876 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_USB_TYPE,
 	POWER_SUPPLY_PROP_SCOPE,
 	POWER_SUPPLY_PROP_PRECHARGE_CURRENT,
 	POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
@@ -170,6 +171,19 @@ enum power_supply_type {
 	POWER_SUPPLY_TYPE_APPLE_BRICK_ID,	/* Apple Charging Method */
 };
 
+enum power_supply_usb_type {
+	POWER_SUPPLY_USB_TYPE_UNKNOWN = 0,
+	POWER_SUPPLY_USB_TYPE_SDP,		/* Standard Downstream Port */
+	POWER_SUPPLY_USB_TYPE_DCP,		/* Dedicated Charging Port */
+	POWER_SUPPLY_USB_TYPE_CDP,		/* Charging Downstream Port */
+	POWER_SUPPLY_USB_TYPE_ACA,		/* Accessory Charger Adapters */
+	POWER_SUPPLY_USB_TYPE_C,		/* Type C Port */
+	POWER_SUPPLY_USB_TYPE_PD,		/* Power Delivery Port */
+	POWER_SUPPLY_USB_TYPE_PD_DRP,		/* PD Dual Role Port */
+	POWER_SUPPLY_USB_TYPE_PD_PPS,		/* PD Programmable Power Supply */
+	POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID,	/* Apple Charging Method */
+};
+
 enum power_supply_notifier_events {
 	PSY_EVENT_PROP_CHANGED,
 };
@@ -196,6 +210,8 @@ struct power_supply_config {
 struct power_supply_desc {
 	const char *name;
 	enum power_supply_type type;
+	enum power_supply_usb_type *usb_types;
+	size_t num_usb_types;
 	enum power_supply_property *properties;
 	size_t num_properties;
 
-- 
1.9.1

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

* [PATCH v5 4/5] typec: tcpm: Represent source supply through power_supply
@ 2018-03-20 14:33   ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 35+ messages in thread
From: Adam Thomson @ 2018-03-20 14:33 UTC (permalink / raw)
  To: Heikki Krogerus, Guenter Roeck, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Jun Li
  Cc: linux-usb, linux-pm, linux-kernel, support.opensource

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

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

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

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

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 06794c0..6a8f279 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;
@@ -861,13 +855,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);
@@ -883,11 +875,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;
 }
 
@@ -1686,43 +1673,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;
@@ -1762,7 +1712,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;
@@ -1809,14 +1758,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 b4cf1ca..18ab36f 100644
--- a/drivers/usb/typec/tcpm.c
+++ b/drivers/usb/typec/tcpm.c
@@ -12,6 +12,7 @@
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/mutex.h>
+#include <linux/power_supply.h>
 #include <linux/proc_fs.h>
 #include <linux/sched/clock.h>
 #include <linux/seq_file.h>
@@ -277,6 +278,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_usb_type usb_type;
+
 	u32 bist_request;
 
 	/* PD state for Vendor Defined Messages */
@@ -1874,6 +1880,7 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
 	int ret = -EINVAL;
 
 	port->pps_data.supported = false;
+	port->usb_type = POWER_SUPPLY_USB_TYPE_PD;
 
 	/*
 	 * Select the source PDO providing the most power while staying within
@@ -1893,8 +1900,11 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
 			mv = pdo_min_voltage(pdo);
 			break;
 		case PDO_TYPE_APDO:
-			if (pdo_apdo_type(pdo) == APDO_TYPE_PPS)
+			if (pdo_apdo_type(pdo) == APDO_TYPE_PPS) {
 				port->pps_data.supported = true;
+				port->usb_type =
+					POWER_SUPPLY_USB_TYPE_PD_PPS;
+			}
 			continue;
 		default:
 			tcpm_log(port, "Invalid PDO type, ignoring");
@@ -2379,6 +2389,9 @@ static void tcpm_reset_port(struct tcpm_port *port)
 	port->try_snk_count = 0;
 	port->supply_voltage = 0;
 	port->current_limit = 0;
+	port->usb_type = POWER_SUPPLY_USB_TYPE_C;
+
+	power_supply_changed(port->psy);
 }
 
 static void tcpm_detach(struct tcpm_port *port)
@@ -2911,6 +2924,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 */
@@ -4077,6 +4092,227 @@ int tcpm_update_sink_capabilities(struct tcpm_port *port, const u32 *pdo,
 }
 EXPORT_SYMBOL_GPL(tcpm_update_sink_capabilities);
 
+/* Power Supply access to expose source power information */
+enum tcpm_psy_online_states {
+	TCPM_PSY_OFFLINE = 0,
+	TCPM_PSY_FIXED_ONLINE,
+	TCPM_PSY_PROG_ONLINE,
+};
+
+static enum power_supply_property tcpm_psy_props[] = {
+	POWER_SUPPLY_PROP_USB_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_USB_TYPE:
+		val->intval = port->usb_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_usb_type tcpm_psy_usb_types[] = {
+	POWER_SUPPLY_USB_TYPE_C,
+	POWER_SUPPLY_USB_TYPE_PD,
+	POWER_SUPPLY_USB_TYPE_PD_PPS,
+};
+
+static const char *tcpm_psy_name_prefix = "tcpm-source-psy-";
+
+static int devm_tcpm_psy_register(struct tcpm_port *port)
+{
+	struct power_supply_config psy_cfg = {};
+	const char *port_dev_name = dev_name(port->dev);
+	size_t psy_name_len = strlen(tcpm_psy_name_prefix) +
+				     strlen(port_dev_name) + 1;
+	char *psy_name;
+
+	psy_cfg.drv_data = port;
+	psy_name = devm_kzalloc(port->dev, psy_name_len, GFP_KERNEL);
+	snprintf(psy_name, psy_name_len, "%s%s", tcpm_psy_name_prefix,
+		 port_dev_name);
+	port->psy_desc.name = psy_name;
+	port->psy_desc.type = POWER_SUPPLY_TYPE_USB,
+	port->psy_desc.usb_types = tcpm_psy_usb_types;
+	port->psy_desc.num_usb_types = ARRAY_SIZE(tcpm_psy_usb_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->usb_type = POWER_SUPPLY_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;
@@ -4148,6 +4384,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 (IS_ERR(port->typec_port)) {
 		err = PTR_ERR(port->typec_port);
-- 
1.9.1


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

* [v5,4/5] typec: tcpm: Represent source supply through power_supply
@ 2018-03-20 14:33   ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 35+ messages in thread
From: Opensource [Adam Thomson] @ 2018-03-20 14:33 UTC (permalink / raw)
  To: Heikki Krogerus, Guenter Roeck, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Jun Li
  Cc: linux-usb, linux-pm, linux-kernel, support.opensource

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

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

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

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

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 06794c0..6a8f279 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;
@@ -861,13 +855,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);
@@ -883,11 +875,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;
 }
 
@@ -1686,43 +1673,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;
@@ -1762,7 +1712,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;
@@ -1809,14 +1758,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 b4cf1ca..18ab36f 100644
--- a/drivers/usb/typec/tcpm.c
+++ b/drivers/usb/typec/tcpm.c
@@ -12,6 +12,7 @@
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/mutex.h>
+#include <linux/power_supply.h>
 #include <linux/proc_fs.h>
 #include <linux/sched/clock.h>
 #include <linux/seq_file.h>
@@ -277,6 +278,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_usb_type usb_type;
+
 	u32 bist_request;
 
 	/* PD state for Vendor Defined Messages */
@@ -1874,6 +1880,7 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
 	int ret = -EINVAL;
 
 	port->pps_data.supported = false;
+	port->usb_type = POWER_SUPPLY_USB_TYPE_PD;
 
 	/*
 	 * Select the source PDO providing the most power while staying within
@@ -1893,8 +1900,11 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
 			mv = pdo_min_voltage(pdo);
 			break;
 		case PDO_TYPE_APDO:
-			if (pdo_apdo_type(pdo) == APDO_TYPE_PPS)
+			if (pdo_apdo_type(pdo) == APDO_TYPE_PPS) {
 				port->pps_data.supported = true;
+				port->usb_type =
+					POWER_SUPPLY_USB_TYPE_PD_PPS;
+			}
 			continue;
 		default:
 			tcpm_log(port, "Invalid PDO type, ignoring");
@@ -2379,6 +2389,9 @@ static void tcpm_reset_port(struct tcpm_port *port)
 	port->try_snk_count = 0;
 	port->supply_voltage = 0;
 	port->current_limit = 0;
+	port->usb_type = POWER_SUPPLY_USB_TYPE_C;
+
+	power_supply_changed(port->psy);
 }
 
 static void tcpm_detach(struct tcpm_port *port)
@@ -2911,6 +2924,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 */
@@ -4077,6 +4092,227 @@ int tcpm_update_sink_capabilities(struct tcpm_port *port, const u32 *pdo,
 }
 EXPORT_SYMBOL_GPL(tcpm_update_sink_capabilities);
 
+/* Power Supply access to expose source power information */
+enum tcpm_psy_online_states {
+	TCPM_PSY_OFFLINE = 0,
+	TCPM_PSY_FIXED_ONLINE,
+	TCPM_PSY_PROG_ONLINE,
+};
+
+static enum power_supply_property tcpm_psy_props[] = {
+	POWER_SUPPLY_PROP_USB_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_USB_TYPE:
+		val->intval = port->usb_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_usb_type tcpm_psy_usb_types[] = {
+	POWER_SUPPLY_USB_TYPE_C,
+	POWER_SUPPLY_USB_TYPE_PD,
+	POWER_SUPPLY_USB_TYPE_PD_PPS,
+};
+
+static const char *tcpm_psy_name_prefix = "tcpm-source-psy-";
+
+static int devm_tcpm_psy_register(struct tcpm_port *port)
+{
+	struct power_supply_config psy_cfg = {};
+	const char *port_dev_name = dev_name(port->dev);
+	size_t psy_name_len = strlen(tcpm_psy_name_prefix) +
+				     strlen(port_dev_name) + 1;
+	char *psy_name;
+
+	psy_cfg.drv_data = port;
+	psy_name = devm_kzalloc(port->dev, psy_name_len, GFP_KERNEL);
+	snprintf(psy_name, psy_name_len, "%s%s", tcpm_psy_name_prefix,
+		 port_dev_name);
+	port->psy_desc.name = psy_name;
+	port->psy_desc.type = POWER_SUPPLY_TYPE_USB,
+	port->psy_desc.usb_types = tcpm_psy_usb_types;
+	port->psy_desc.num_usb_types = ARRAY_SIZE(tcpm_psy_usb_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->usb_type = POWER_SUPPLY_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;
@@ -4148,6 +4384,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 (IS_ERR(port->typec_port)) {
 		err = PTR_ERR(port->typec_port);

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

* [PATCH v5 4/5] typec: tcpm: Represent source supply through power_supply
@ 2018-03-20 14:33   ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 35+ messages in thread
From: Adam Thomson @ 2018-03-20 14:33 UTC (permalink / raw)
  To: Heikki Krogerus, Guenter Roeck, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Jun Li
  Cc: linux-usb, linux-pm, linux-kernel, support.opensource

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

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

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

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

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 06794c0..6a8f279 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;
@@ -861,13 +855,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);
@@ -883,11 +875,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;
 }
 
@@ -1686,43 +1673,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;
@@ -1762,7 +1712,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;
@@ -1809,14 +1758,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 b4cf1ca..18ab36f 100644
--- a/drivers/usb/typec/tcpm.c
+++ b/drivers/usb/typec/tcpm.c
@@ -12,6 +12,7 @@
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/mutex.h>
+#include <linux/power_supply.h>
 #include <linux/proc_fs.h>
 #include <linux/sched/clock.h>
 #include <linux/seq_file.h>
@@ -277,6 +278,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_usb_type usb_type;
+
 	u32 bist_request;
 
 	/* PD state for Vendor Defined Messages */
@@ -1874,6 +1880,7 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
 	int ret = -EINVAL;
 
 	port->pps_data.supported = false;
+	port->usb_type = POWER_SUPPLY_USB_TYPE_PD;
 
 	/*
 	 * Select the source PDO providing the most power while staying within
@@ -1893,8 +1900,11 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
 			mv = pdo_min_voltage(pdo);
 			break;
 		case PDO_TYPE_APDO:
-			if (pdo_apdo_type(pdo) == APDO_TYPE_PPS)
+			if (pdo_apdo_type(pdo) == APDO_TYPE_PPS) {
 				port->pps_data.supported = true;
+				port->usb_type =
+					POWER_SUPPLY_USB_TYPE_PD_PPS;
+			}
 			continue;
 		default:
 			tcpm_log(port, "Invalid PDO type, ignoring");
@@ -2379,6 +2389,9 @@ static void tcpm_reset_port(struct tcpm_port *port)
 	port->try_snk_count = 0;
 	port->supply_voltage = 0;
 	port->current_limit = 0;
+	port->usb_type = POWER_SUPPLY_USB_TYPE_C;
+
+	power_supply_changed(port->psy);
 }
 
 static void tcpm_detach(struct tcpm_port *port)
@@ -2911,6 +2924,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 */
@@ -4077,6 +4092,227 @@ int tcpm_update_sink_capabilities(struct tcpm_port *port, const u32 *pdo,
 }
 EXPORT_SYMBOL_GPL(tcpm_update_sink_capabilities);
 
+/* Power Supply access to expose source power information */
+enum tcpm_psy_online_states {
+	TCPM_PSY_OFFLINE = 0,
+	TCPM_PSY_FIXED_ONLINE,
+	TCPM_PSY_PROG_ONLINE,
+};
+
+static enum power_supply_property tcpm_psy_props[] = {
+	POWER_SUPPLY_PROP_USB_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_USB_TYPE:
+		val->intval = port->usb_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_usb_type tcpm_psy_usb_types[] = {
+	POWER_SUPPLY_USB_TYPE_C,
+	POWER_SUPPLY_USB_TYPE_PD,
+	POWER_SUPPLY_USB_TYPE_PD_PPS,
+};
+
+static const char *tcpm_psy_name_prefix = "tcpm-source-psy-";
+
+static int devm_tcpm_psy_register(struct tcpm_port *port)
+{
+	struct power_supply_config psy_cfg = {};
+	const char *port_dev_name = dev_name(port->dev);
+	size_t psy_name_len = strlen(tcpm_psy_name_prefix) +
+				     strlen(port_dev_name) + 1;
+	char *psy_name;
+
+	psy_cfg.drv_data = port;
+	psy_name = devm_kzalloc(port->dev, psy_name_len, GFP_KERNEL);
+	snprintf(psy_name, psy_name_len, "%s%s", tcpm_psy_name_prefix,
+		 port_dev_name);
+	port->psy_desc.name = psy_name;
+	port->psy_desc.type = POWER_SUPPLY_TYPE_USB,
+	port->psy_desc.usb_types = tcpm_psy_usb_types;
+	port->psy_desc.num_usb_types = ARRAY_SIZE(tcpm_psy_usb_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->usb_type = POWER_SUPPLY_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;
@@ -4148,6 +4384,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 (IS_ERR(port->typec_port)) {
 		err = PTR_ERR(port->typec_port);
-- 
1.9.1

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

* [PATCH v5 5/5] typec: tcpm: Add support for sink PPS related messages
@ 2018-03-20 14:33   ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 35+ messages in thread
From: Adam Thomson @ 2018-03-20 14:33 UTC (permalink / raw)
  To: Heikki Krogerus, Guenter Roeck, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Jun Li
  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 to handle messagescreceived but not
yet handled.

Signed-off-by: Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
Acked-by: Heikki Krogerus <heikki.krogerus@linux.intel.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 18ab36f..148db99 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,
 };
@@ -1398,10 +1406,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)
 {
@@ -1487,6 +1527,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;
@@ -1569,6 +1617,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 */
@@ -1688,12 +1737,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,
@@ -1734,7 +1855,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);
@@ -1795,6 +1918,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;
@@ -2481,14 +2607,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) {
@@ -3191,6 +3309,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] 35+ messages in thread

* [v5,5/5] typec: tcpm: Add support for sink PPS related messages
@ 2018-03-20 14:33   ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 35+ messages in thread
From: Opensource [Adam Thomson] @ 2018-03-20 14:33 UTC (permalink / raw)
  To: Heikki Krogerus, Guenter Roeck, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Jun Li
  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 to handle messagescreceived but not
yet handled.

Signed-off-by: Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
Acked-by: Heikki Krogerus <heikki.krogerus@linux.intel.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 18ab36f..148db99 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,
 };
@@ -1398,10 +1406,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)
 {
@@ -1487,6 +1527,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;
@@ -1569,6 +1617,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 */
@@ -1688,12 +1737,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,
@@ -1734,7 +1855,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);
@@ -1795,6 +1918,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;
@@ -2481,14 +2607,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) {
@@ -3191,6 +3309,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] 35+ messages in thread

* [PATCH v5 5/5] typec: tcpm: Add support for sink PPS related messages
@ 2018-03-20 14:33   ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 35+ messages in thread
From: Adam Thomson @ 2018-03-20 14:33 UTC (permalink / raw)
  To: Heikki Krogerus, Guenter Roeck, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Jun Li
  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 to handle messagescreceived but not
yet handled.

Signed-off-by: Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
Acked-by: Heikki Krogerus <heikki.krogerus@linux.intel.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 18ab36f..148db99 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,
 };
@@ -1398,10 +1406,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)
 {
@@ -1487,6 +1527,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;
@@ -1569,6 +1617,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 */
@@ -1688,12 +1737,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,
@@ -1734,7 +1855,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);
@@ -1795,6 +1918,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;
@@ -2481,14 +2607,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) {
@@ -3191,6 +3309,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] 35+ messages in thread

* Re: [PATCH v5 5/5] typec: tcpm: Add support for sink PPS related messages
@ 2018-03-22  3:52     ` Guenter Roeck
  0 siblings, 0 replies; 35+ messages in thread
From: Guenter Roeck @ 2018-03-22  3:52 UTC (permalink / raw)
  To: Adam Thomson, Heikki Krogerus, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Jun Li
  Cc: linux-usb, linux-pm, linux-kernel, support.opensource

On 03/20/2018 07:33 AM, 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 to handle messagescreceived but not
> yet handled.
> 
> Signed-off-by: Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
> Acked-by: Heikki Krogerus <heikki.krogerus@linux.intel.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 18ab36f..148db99 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,
>   };
> @@ -1398,10 +1406,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)
>   {
> @@ -1487,6 +1527,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;
> @@ -1569,6 +1617,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 */
> @@ -1688,12 +1737,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)

This seems to be the only use of 'data'. Can you explain why it is needed
in the first place ? Am I missing something ?

> +			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,
> @@ -1734,7 +1855,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);
> @@ -1795,6 +1918,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;
> @@ -2481,14 +2607,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) {
> @@ -3191,6 +3309,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	[flat|nested] 35+ messages in thread

* [v5,5/5] typec: tcpm: Add support for sink PPS related messages
@ 2018-03-22  3:52     ` Guenter Roeck
  0 siblings, 0 replies; 35+ messages in thread
From: Guenter Roeck @ 2018-03-22  3:52 UTC (permalink / raw)
  To: Adam Thomson, Heikki Krogerus, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Jun Li
  Cc: linux-usb, linux-pm, linux-kernel, support.opensource

On 03/20/2018 07:33 AM, 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 to handle messagescreceived but not
> yet handled.
> 
> Signed-off-by: Adam Thomson <Adam.Thomson.Opensource@diasemi.com>
> Acked-by: Heikki Krogerus <heikki.krogerus@linux.intel.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 18ab36f..148db99 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,
>   };
> @@ -1398,10 +1406,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)
>   {
> @@ -1487,6 +1527,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;
> @@ -1569,6 +1617,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 */
> @@ -1688,12 +1737,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)

This seems to be the only use of 'data'. Can you explain why it is needed
in the first place ? Am I missing something ?

> +			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,
> @@ -1734,7 +1855,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);
> @@ -1795,6 +1918,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;
> @@ -2481,14 +2607,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) {
> @@ -3191,6 +3309,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);
>
---
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] 35+ messages in thread

* Re: [PATCH v5 1/5] typec: tcpm: Add core support for sink side PPS
@ 2018-03-22  4:03     ` Guenter Roeck
  0 siblings, 0 replies; 35+ messages in thread
From: Guenter Roeck @ 2018-03-22  4:03 UTC (permalink / raw)
  To: Adam Thomson, Heikki Krogerus, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Jun Li
  Cc: linux-usb, linux-pm, linux-kernel, support.opensource

On 03/20/2018 07:33 AM, 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>
> Acked-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> ---
>   drivers/usb/typec/tcpm.c | 524 +++++++++++++++++++++++++++++++++++++++++++++--
>   include/linux/usb/pd.h   |   4 +-
>   include/linux/usb/tcpm.h |   2 +-
>   3 files changed, 513 insertions(+), 17 deletions(-)
> 
> diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c
> index 4c0fc54..b4cf1ca 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;
> @@ -259,6 +271,7 @@ struct tcpm_port {
>   	unsigned int max_snk_ma;
>   	unsigned int max_snk_mw;
>   	unsigned int operating_snk_mw;
> +	bool update_sink_caps;
>   
>   	/* Requested current / voltage */
>   	u32 current_limit;
> @@ -275,8 +288,13 @@ struct tcpm_port {
>   	/* VDO to retry if UFP responder replied busy */
>   	u32 vdo_retry;
>   
> -	/* Alternate mode data */
> +	/* PPS */
> +	struct pd_pps_data pps_data;
> +	struct completion pps_complete;
> +	bool pps_pending;
> +	int pps_status;
>   
> +	/* Alternate mode data */
>   	struct pd_mode_data mode_data;
>   	struct typec_altmode *partner_altmode[SVID_DISCOVERY_MAX];
>   	struct typec_altmode *port_altmode[SVID_DISCOVERY_MAX];
> @@ -494,6 +512,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;
> @@ -777,11 +805,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);
>   	}
> @@ -802,11 +832,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);
>   	}
> @@ -1173,6 +1205,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]);
> @@ -1244,6 +1277,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[] = {
> @@ -1259,6 +1294,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,
> @@ -1308,6 +1347,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])))

Unnecessary ( )

> +					return PDO_ERR_DUPE_PPS_APDO;
> +				break;
>   			default:
>   				tcpm_log_force(port, " Unknown pdo type");
>   			}
> @@ -1333,11 +1392,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) {
> @@ -1356,6 +1420,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.
> @@ -1376,6 +1450,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;
> @@ -1400,6 +1487,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)
>   {
> @@ -1476,6 +1572,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);
> @@ -1498,6 +1602,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:
> @@ -1652,6 +1763,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);
> @@ -1761,6 +1873,8 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
>   	unsigned int i, max_mw = 0, max_mv = 0;
>   	int ret = -EINVAL;
>   
> +	port->pps_data.supported = false;
> +
>   	/*
>   	 * Select the source PDO providing the most power while staying within
>   	 * the board's voltage limits. Prefer PDO providing exp
> @@ -1770,20 +1884,41 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
>   		enum pd_pdo_type type = pdo_type(pdo);
>   		unsigned int mv, ma, mw;
>   
> -		if (type == PDO_TYPE_FIXED)
> +		switch (type) {
> +		case PDO_TYPE_FIXED:
>   			mv = pdo_fixed_voltage(pdo);
> -		else
> +			break;
> +		case PDO_TYPE_BATT:
> +		case PDO_TYPE_VAR:
>   			mv = pdo_min_voltage(pdo);
> +			break;
> +		case PDO_TYPE_APDO:
> +			if (pdo_apdo_type(pdo) == APDO_TYPE_PPS)
> +				port->pps_data.supported = true;
> +			continue;
> +		default:
> +			tcpm_log(port, "Invalid PDO type, ignoring");
> +			continue;
> +		}
>   
> -		if (type == PDO_TYPE_BATT) {
> -			mw = pdo_max_power(pdo);
> -		} else {
> +		switch (type) {
> +		case PDO_TYPE_FIXED:
> +		case PDO_TYPE_VAR:
>   			ma = min(pdo_max_current(pdo),
>   				 port->max_snk_ma);
>   			mw = ma * mv / 1000;
> +			break;
> +		case PDO_TYPE_BATT:
> +			mw = pdo_max_power(pdo);
> +			break;
> +		case PDO_TYPE_APDO:
> +			continue;
> +		default:
> +			tcpm_log(port, "Invalid PDO type, ignoring");
> +			continue;
>   		}
>   
> -		/* Perfer higher voltages if available */
> +		/* Prefer higher voltages if available */
>   		if ((mw > max_mw || (mw == max_mw && mv > max_mv)) &&
>   		    mv <= port->max_snk_mv) {
>   			ret = i;
> @@ -1795,6 +1930,65 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
>   	return ret;
>   }
>   
> +static unsigned int tcpm_pd_select_pps_apdo(struct tcpm_port *port)
> +{
> +	unsigned int i, max_mw = 0, max_mv = 0;
> +	unsigned int pps_min_mv, pps_max_mv, ma, mw;
> +	enum pd_pdo_type type;
> +	u32 pdo;
> +	unsigned int index = 0;
> +
> +	/*
> +	 * Select the source PPS APDO providing the most power while staying
> +	 * within the board's limits. We skip the first PDO as this is always
> +	 * 5V 3A.
> +	 */
> +	for (i = 1; i < port->nr_source_caps; ++i) {
> +		pdo = port->source_caps[i];
> +		type = pdo_type(pdo);
> +
> +		switch (type) {
> +		case PDO_TYPE_APDO:
> +			if (pdo_apdo_type(pdo) != APDO_TYPE_PPS) {
> +				tcpm_log(port, "Not PPS APDO, ignoring");
> +				continue;
> +			}
> +
> +			pps_min_mv = pdo_pps_apdo_min_voltage(pdo);
> +			pps_max_mv = pdo_pps_apdo_max_voltage(pdo);
> +			ma = min(pdo_pps_apdo_max_current(pdo), port->max_snk_ma);
> +			mw = (ma * pps_max_mv) / 1000;
> +			break;
> +		default:
> +			tcpm_log(port, "Not APDO type, ignoring");
> +			continue;
> +		}
> +
> +		/* Prefer higher voltages if available */
> +		if ((mw > max_mw || (mw == max_mw && pps_max_mv > max_mv)) &&
> +		    pps_max_mv <= port->max_snk_mv) {
> +			index = i;
> +			max_mw = mw;
> +			max_mv = pps_max_mv;
> +		}
> +	}
> +
> +	if (index) {
> +		pdo = port->source_caps[index];
> +
> +		port->pps_data.min_volt = pdo_pps_apdo_min_voltage(pdo);
> +		port->pps_data.max_volt = pdo_pps_apdo_max_voltage(pdo);
> +		port->pps_data.max_curr =
> +			min(pdo_pps_apdo_max_current(pdo), port->max_snk_ma);
> +		port->pps_data.out_volt =
> +			min(pdo_pps_apdo_max_voltage(pdo), port->pps_data.out_volt);
> +		port->pps_data.op_curr =
> +			min(pdo_pps_apdo_max_current(pdo), port->pps_data.op_curr);
> +	}
> +
> +	return index;
> +}
> +
>   static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo)
>   {
>   	unsigned int mv, ma, mw, flags;
> @@ -1809,10 +2003,18 @@ static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo)
>   	pdo = port->source_caps[index];
>   	type = pdo_type(pdo);
>   
> -	if (type == PDO_TYPE_FIXED)
> +	switch (type) {
> +	case PDO_TYPE_FIXED:
>   		mv = pdo_fixed_voltage(pdo);
> -	else
> +		break;
> +	case PDO_TYPE_BATT:
> +	case PDO_TYPE_VAR:
>   		mv = pdo_min_voltage(pdo);
> +		break;
> +	default:
> +		tcpm_log(port, "Invalid PDO selected!");
> +		return -EINVAL;
> +	}
>   
>   	/* Select maximum available current within the board's power limit */
>   	if (type == PDO_TYPE_BATT) {
> @@ -1875,6 +2077,105 @@ 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 index;
> +	u32 pdo;
> +
> +	index = tcpm_pd_select_pps_apdo(port);
> +	if (!index)
> +		return -EOPNOTSUPP;
> +
> +	pdo = port->source_caps[index];
> +	type = pdo_type(pdo);
> +
> +	switch (type) {
> +	case PDO_TYPE_APDO:
> +		if (pdo_apdo_type(pdo) != APDO_TYPE_PPS) {
> +			tcpm_log(port, "Invalid APDO selected!");
> +			return -EINVAL;
> +		}
> +		min_mv = port->pps_data.min_volt;
> +		max_mv = port->pps_data.max_volt;
> +		max_ma = port->pps_data.max_curr;
> +		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 enough
> +		 * 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(index + 1, out_mv, op_ma, flags);
> +
> +	tcpm_log(port, "Requesting APDO %d: %u mV, %u mA",
> +		 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);
>   
> @@ -2060,6 +2361,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
> @@ -2075,6 +2377,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)
> @@ -2321,6 +2625,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;
> @@ -2381,6 +2686,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
> @@ -2404,6 +2710,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);
> @@ -2412,6 +2719,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 &&
> @@ -2493,6 +2801,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;
> @@ -2563,6 +2872,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),
> @@ -2570,6 +2897,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);
> @@ -2578,7 +2906,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 */
> @@ -2625,6 +2957,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);
> @@ -2845,6 +3178,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:
> @@ -3310,7 +3644,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;
> @@ -3355,7 +3689,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;
> @@ -3395,7 +3729,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;
> @@ -3427,6 +3761,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;
> +

Unnecessary initialization.

> +	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;
> +
Unnecessary initialization.

> +	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)) {

Unnecessary ( )

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

Unnecessary ( )

> +		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;
> @@ -3566,13 +4056,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;
> @@ -3614,6 +4109,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,
> @@ -3642,7 +4138,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	[flat|nested] 35+ messages in thread

* [v5,1/5] typec: tcpm: Add core support for sink side PPS
@ 2018-03-22  4:03     ` Guenter Roeck
  0 siblings, 0 replies; 35+ messages in thread
From: Guenter Roeck @ 2018-03-22  4:03 UTC (permalink / raw)
  To: Adam Thomson, Heikki Krogerus, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Jun Li
  Cc: linux-usb, linux-pm, linux-kernel, support.opensource

On 03/20/2018 07:33 AM, 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>
> Acked-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> ---
>   drivers/usb/typec/tcpm.c | 524 +++++++++++++++++++++++++++++++++++++++++++++--
>   include/linux/usb/pd.h   |   4 +-
>   include/linux/usb/tcpm.h |   2 +-
>   3 files changed, 513 insertions(+), 17 deletions(-)
> 
> diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c
> index 4c0fc54..b4cf1ca 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;
> @@ -259,6 +271,7 @@ struct tcpm_port {
>   	unsigned int max_snk_ma;
>   	unsigned int max_snk_mw;
>   	unsigned int operating_snk_mw;
> +	bool update_sink_caps;
>   
>   	/* Requested current / voltage */
>   	u32 current_limit;
> @@ -275,8 +288,13 @@ struct tcpm_port {
>   	/* VDO to retry if UFP responder replied busy */
>   	u32 vdo_retry;
>   
> -	/* Alternate mode data */
> +	/* PPS */
> +	struct pd_pps_data pps_data;
> +	struct completion pps_complete;
> +	bool pps_pending;
> +	int pps_status;
>   
> +	/* Alternate mode data */
>   	struct pd_mode_data mode_data;
>   	struct typec_altmode *partner_altmode[SVID_DISCOVERY_MAX];
>   	struct typec_altmode *port_altmode[SVID_DISCOVERY_MAX];
> @@ -494,6 +512,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;
> @@ -777,11 +805,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);
>   	}
> @@ -802,11 +832,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);
>   	}
> @@ -1173,6 +1205,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]);
> @@ -1244,6 +1277,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[] = {
> @@ -1259,6 +1294,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,
> @@ -1308,6 +1347,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])))

Unnecessary ( )

> +					return PDO_ERR_DUPE_PPS_APDO;
> +				break;
>   			default:
>   				tcpm_log_force(port, " Unknown pdo type");
>   			}
> @@ -1333,11 +1392,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) {
> @@ -1356,6 +1420,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.
> @@ -1376,6 +1450,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;
> @@ -1400,6 +1487,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)
>   {
> @@ -1476,6 +1572,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);
> @@ -1498,6 +1602,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:
> @@ -1652,6 +1763,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);
> @@ -1761,6 +1873,8 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
>   	unsigned int i, max_mw = 0, max_mv = 0;
>   	int ret = -EINVAL;
>   
> +	port->pps_data.supported = false;
> +
>   	/*
>   	 * Select the source PDO providing the most power while staying within
>   	 * the board's voltage limits. Prefer PDO providing exp
> @@ -1770,20 +1884,41 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
>   		enum pd_pdo_type type = pdo_type(pdo);
>   		unsigned int mv, ma, mw;
>   
> -		if (type == PDO_TYPE_FIXED)
> +		switch (type) {
> +		case PDO_TYPE_FIXED:
>   			mv = pdo_fixed_voltage(pdo);
> -		else
> +			break;
> +		case PDO_TYPE_BATT:
> +		case PDO_TYPE_VAR:
>   			mv = pdo_min_voltage(pdo);
> +			break;
> +		case PDO_TYPE_APDO:
> +			if (pdo_apdo_type(pdo) == APDO_TYPE_PPS)
> +				port->pps_data.supported = true;
> +			continue;
> +		default:
> +			tcpm_log(port, "Invalid PDO type, ignoring");
> +			continue;
> +		}
>   
> -		if (type == PDO_TYPE_BATT) {
> -			mw = pdo_max_power(pdo);
> -		} else {
> +		switch (type) {
> +		case PDO_TYPE_FIXED:
> +		case PDO_TYPE_VAR:
>   			ma = min(pdo_max_current(pdo),
>   				 port->max_snk_ma);
>   			mw = ma * mv / 1000;
> +			break;
> +		case PDO_TYPE_BATT:
> +			mw = pdo_max_power(pdo);
> +			break;
> +		case PDO_TYPE_APDO:
> +			continue;
> +		default:
> +			tcpm_log(port, "Invalid PDO type, ignoring");
> +			continue;
>   		}
>   
> -		/* Perfer higher voltages if available */
> +		/* Prefer higher voltages if available */
>   		if ((mw > max_mw || (mw == max_mw && mv > max_mv)) &&
>   		    mv <= port->max_snk_mv) {
>   			ret = i;
> @@ -1795,6 +1930,65 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
>   	return ret;
>   }
>   
> +static unsigned int tcpm_pd_select_pps_apdo(struct tcpm_port *port)
> +{
> +	unsigned int i, max_mw = 0, max_mv = 0;
> +	unsigned int pps_min_mv, pps_max_mv, ma, mw;
> +	enum pd_pdo_type type;
> +	u32 pdo;
> +	unsigned int index = 0;
> +
> +	/*
> +	 * Select the source PPS APDO providing the most power while staying
> +	 * within the board's limits. We skip the first PDO as this is always
> +	 * 5V 3A.
> +	 */
> +	for (i = 1; i < port->nr_source_caps; ++i) {
> +		pdo = port->source_caps[i];
> +		type = pdo_type(pdo);
> +
> +		switch (type) {
> +		case PDO_TYPE_APDO:
> +			if (pdo_apdo_type(pdo) != APDO_TYPE_PPS) {
> +				tcpm_log(port, "Not PPS APDO, ignoring");
> +				continue;
> +			}
> +
> +			pps_min_mv = pdo_pps_apdo_min_voltage(pdo);
> +			pps_max_mv = pdo_pps_apdo_max_voltage(pdo);
> +			ma = min(pdo_pps_apdo_max_current(pdo), port->max_snk_ma);
> +			mw = (ma * pps_max_mv) / 1000;
> +			break;
> +		default:
> +			tcpm_log(port, "Not APDO type, ignoring");
> +			continue;
> +		}
> +
> +		/* Prefer higher voltages if available */
> +		if ((mw > max_mw || (mw == max_mw && pps_max_mv > max_mv)) &&
> +		    pps_max_mv <= port->max_snk_mv) {
> +			index = i;
> +			max_mw = mw;
> +			max_mv = pps_max_mv;
> +		}
> +	}
> +
> +	if (index) {
> +		pdo = port->source_caps[index];
> +
> +		port->pps_data.min_volt = pdo_pps_apdo_min_voltage(pdo);
> +		port->pps_data.max_volt = pdo_pps_apdo_max_voltage(pdo);
> +		port->pps_data.max_curr =
> +			min(pdo_pps_apdo_max_current(pdo), port->max_snk_ma);
> +		port->pps_data.out_volt =
> +			min(pdo_pps_apdo_max_voltage(pdo), port->pps_data.out_volt);
> +		port->pps_data.op_curr =
> +			min(pdo_pps_apdo_max_current(pdo), port->pps_data.op_curr);
> +	}
> +
> +	return index;
> +}
> +
>   static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo)
>   {
>   	unsigned int mv, ma, mw, flags;
> @@ -1809,10 +2003,18 @@ static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo)
>   	pdo = port->source_caps[index];
>   	type = pdo_type(pdo);
>   
> -	if (type == PDO_TYPE_FIXED)
> +	switch (type) {
> +	case PDO_TYPE_FIXED:
>   		mv = pdo_fixed_voltage(pdo);
> -	else
> +		break;
> +	case PDO_TYPE_BATT:
> +	case PDO_TYPE_VAR:
>   		mv = pdo_min_voltage(pdo);
> +		break;
> +	default:
> +		tcpm_log(port, "Invalid PDO selected!");
> +		return -EINVAL;
> +	}
>   
>   	/* Select maximum available current within the board's power limit */
>   	if (type == PDO_TYPE_BATT) {
> @@ -1875,6 +2077,105 @@ 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 index;
> +	u32 pdo;
> +
> +	index = tcpm_pd_select_pps_apdo(port);
> +	if (!index)
> +		return -EOPNOTSUPP;
> +
> +	pdo = port->source_caps[index];
> +	type = pdo_type(pdo);
> +
> +	switch (type) {
> +	case PDO_TYPE_APDO:
> +		if (pdo_apdo_type(pdo) != APDO_TYPE_PPS) {
> +			tcpm_log(port, "Invalid APDO selected!");
> +			return -EINVAL;
> +		}
> +		min_mv = port->pps_data.min_volt;
> +		max_mv = port->pps_data.max_volt;
> +		max_ma = port->pps_data.max_curr;
> +		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 enough
> +		 * 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(index + 1, out_mv, op_ma, flags);
> +
> +	tcpm_log(port, "Requesting APDO %d: %u mV, %u mA",
> +		 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);
>   
> @@ -2060,6 +2361,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
> @@ -2075,6 +2377,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)
> @@ -2321,6 +2625,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;
> @@ -2381,6 +2686,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
> @@ -2404,6 +2710,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);
> @@ -2412,6 +2719,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 &&
> @@ -2493,6 +2801,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;
> @@ -2563,6 +2872,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),
> @@ -2570,6 +2897,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);
> @@ -2578,7 +2906,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 */
> @@ -2625,6 +2957,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);
> @@ -2845,6 +3178,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:
> @@ -3310,7 +3644,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;
> @@ -3355,7 +3689,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;
> @@ -3395,7 +3729,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;
> @@ -3427,6 +3761,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;
> +

Unnecessary initialization.

> +	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;
> +
Unnecessary initialization.

> +	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)) {

Unnecessary ( )

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

Unnecessary ( )

> +		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;
> @@ -3566,13 +4056,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;
> @@ -3614,6 +4109,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,
> @@ -3642,7 +4138,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,
>
---
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] 35+ messages in thread

* Re: [PATCH v5 3/5] power: supply: Add 'usb_type' property and supporting code
@ 2018-03-22  4:07     ` Guenter Roeck
  0 siblings, 0 replies; 35+ messages in thread
From: Guenter Roeck @ 2018-03-22  4:07 UTC (permalink / raw)
  To: Adam Thomson, Heikki Krogerus, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Jun Li
  Cc: linux-usb, linux-pm, linux-kernel, support.opensource

On 03/20/2018 07:33 AM, Adam Thomson wrote:
> This commit adds the 'usb_type' property to represent USB supplies
> which can report a number of different types 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, 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 'usb_type' property is intended to fill this void and show users
> all possible USB 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 'USB', and this would be
> static.
> 
> Currently the 'usb_type' enum contains all of the USB variant types
> that exist for the 'type' enum at this time, and in addition has
> SDP and PPS types. 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>
> Acked-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.co.uk>
> ---
>   Documentation/ABI/testing/sysfs-class-power | 12 +++++++
>   drivers/power/supply/power_supply_sysfs.c   | 50 +++++++++++++++++++++++++++++
>   include/linux/power_supply.h                | 16 +++++++++
>   3 files changed, 78 insertions(+)
> 
> diff --git a/Documentation/ABI/testing/sysfs-class-power b/Documentation/ABI/testing/sysfs-class-power
> index e046566..5e23e22 100644
> --- a/Documentation/ABI/testing/sysfs-class-power
> +++ b/Documentation/ABI/testing/sysfs-class-power
> @@ -409,6 +409,18 @@ Description:
>   		Access: Read
>   		Valid values: Represented in 1/10 Degrees Celsius
>   
> +What: 		/sys/class/power_supply/<supply_name>/usb_type
> +Date:		March 2018
> +Contact:	linux-pm@vger.kernel.org
> +Description:
> +		Reports what type of USB connection is currently active for
> +		the supply, for example it can show if USB-PD capable source
> +		is attached.
> +
> +		Access: Read-Only
> +		Valid values: "Unknown", "SDP", "DCP", "CDP", "ACA", "C", "PD",
> +			      "PD_DRP", "PD_PPS", "BrickID"
> +
>   What: 		/sys/class/power_supply/<supply_name>/voltage_max
>   Date:		January 2008
>   Contact:	linux-pm@vger.kernel.org
> diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c
> index 5204f11..b68def4 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_usb_type_text[] = {
> +	"Unknown", "SDP", "DCP", "CDP", "ACA", "C",
> +	"PD", "PD_DRP", "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_usb_type(struct device *dev,
> +					  enum power_supply_usb_type *usb_types,
> +					  ssize_t num_usb_types,
> +					  union power_supply_propval *value,
> +					  char *buf)
> +{
> +	enum power_supply_usb_type usb_type;
> +	ssize_t count = 0;
> +	bool match = false;
> +	int i;
> +
> +	if ((!usb_types) || (num_usb_types <= 0)) {

Unnecessary ( )

> +		dev_warn(dev, "driver has no valid connected types\n");

Are those warnings useful or do they just clog the log ? Either case, if that happens,
wouldn't it be better to detect the situation during registration and abort ?

> +		return -ENODATA;
> +	}
> +
> +	for (i = 0; i < num_usb_types; ++i) {
> +		usb_type = usb_types[i];
> +
> +		if (value->intval == usb_type) {
> +			count += sprintf(buf + count, "[%s] ",
> +					 power_supply_usb_type_text[usb_type]);
> +			match = true;
> +		} else {
> +			count += sprintf(buf + count, "%s ",
> +					 power_supply_usb_type_text[usb_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_USB_TYPE)
> +		return power_supply_show_usb_type(dev, psy->desc->usb_types,
> +						  psy->desc->num_usb_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(usb_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..4ca8876 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_USB_TYPE,
>   	POWER_SUPPLY_PROP_SCOPE,
>   	POWER_SUPPLY_PROP_PRECHARGE_CURRENT,
>   	POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
> @@ -170,6 +171,19 @@ enum power_supply_type {
>   	POWER_SUPPLY_TYPE_APPLE_BRICK_ID,	/* Apple Charging Method */
>   };
>   
> +enum power_supply_usb_type {
> +	POWER_SUPPLY_USB_TYPE_UNKNOWN = 0,
> +	POWER_SUPPLY_USB_TYPE_SDP,		/* Standard Downstream Port */
> +	POWER_SUPPLY_USB_TYPE_DCP,		/* Dedicated Charging Port */
> +	POWER_SUPPLY_USB_TYPE_CDP,		/* Charging Downstream Port */
> +	POWER_SUPPLY_USB_TYPE_ACA,		/* Accessory Charger Adapters */
> +	POWER_SUPPLY_USB_TYPE_C,		/* Type C Port */
> +	POWER_SUPPLY_USB_TYPE_PD,		/* Power Delivery Port */
> +	POWER_SUPPLY_USB_TYPE_PD_DRP,		/* PD Dual Role Port */
> +	POWER_SUPPLY_USB_TYPE_PD_PPS,		/* PD Programmable Power Supply */
> +	POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID,	/* Apple Charging Method */
> +};
> +
>   enum power_supply_notifier_events {
>   	PSY_EVENT_PROP_CHANGED,
>   };
> @@ -196,6 +210,8 @@ struct power_supply_config {
>   struct power_supply_desc {
>   	const char *name;
>   	enum power_supply_type type;
> +	enum power_supply_usb_type *usb_types;
> +	size_t num_usb_types;
>   	enum power_supply_property *properties;
>   	size_t num_properties;
>   
> 

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

* [v5,3/5] power: supply: Add 'usb_type' property and supporting code
@ 2018-03-22  4:07     ` Guenter Roeck
  0 siblings, 0 replies; 35+ messages in thread
From: Guenter Roeck @ 2018-03-22  4:07 UTC (permalink / raw)
  To: Adam Thomson, Heikki Krogerus, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Jun Li
  Cc: linux-usb, linux-pm, linux-kernel, support.opensource

On 03/20/2018 07:33 AM, Adam Thomson wrote:
> This commit adds the 'usb_type' property to represent USB supplies
> which can report a number of different types 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, 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 'usb_type' property is intended to fill this void and show users
> all possible USB 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 'USB', and this would be
> static.
> 
> Currently the 'usb_type' enum contains all of the USB variant types
> that exist for the 'type' enum at this time, and in addition has
> SDP and PPS types. 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>
> Acked-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.co.uk>
> ---
>   Documentation/ABI/testing/sysfs-class-power | 12 +++++++
>   drivers/power/supply/power_supply_sysfs.c   | 50 +++++++++++++++++++++++++++++
>   include/linux/power_supply.h                | 16 +++++++++
>   3 files changed, 78 insertions(+)
> 
> diff --git a/Documentation/ABI/testing/sysfs-class-power b/Documentation/ABI/testing/sysfs-class-power
> index e046566..5e23e22 100644
> --- a/Documentation/ABI/testing/sysfs-class-power
> +++ b/Documentation/ABI/testing/sysfs-class-power
> @@ -409,6 +409,18 @@ Description:
>   		Access: Read
>   		Valid values: Represented in 1/10 Degrees Celsius
>   
> +What: 		/sys/class/power_supply/<supply_name>/usb_type
> +Date:		March 2018
> +Contact:	linux-pm@vger.kernel.org
> +Description:
> +		Reports what type of USB connection is currently active for
> +		the supply, for example it can show if USB-PD capable source
> +		is attached.
> +
> +		Access: Read-Only
> +		Valid values: "Unknown", "SDP", "DCP", "CDP", "ACA", "C", "PD",
> +			      "PD_DRP", "PD_PPS", "BrickID"
> +
>   What: 		/sys/class/power_supply/<supply_name>/voltage_max
>   Date:		January 2008
>   Contact:	linux-pm@vger.kernel.org
> diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c
> index 5204f11..b68def4 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_usb_type_text[] = {
> +	"Unknown", "SDP", "DCP", "CDP", "ACA", "C",
> +	"PD", "PD_DRP", "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_usb_type(struct device *dev,
> +					  enum power_supply_usb_type *usb_types,
> +					  ssize_t num_usb_types,
> +					  union power_supply_propval *value,
> +					  char *buf)
> +{
> +	enum power_supply_usb_type usb_type;
> +	ssize_t count = 0;
> +	bool match = false;
> +	int i;
> +
> +	if ((!usb_types) || (num_usb_types <= 0)) {

Unnecessary ( )

> +		dev_warn(dev, "driver has no valid connected types\n");

Are those warnings useful or do they just clog the log ? Either case, if that happens,
wouldn't it be better to detect the situation during registration and abort ?

> +		return -ENODATA;
> +	}
> +
> +	for (i = 0; i < num_usb_types; ++i) {
> +		usb_type = usb_types[i];
> +
> +		if (value->intval == usb_type) {
> +			count += sprintf(buf + count, "[%s] ",
> +					 power_supply_usb_type_text[usb_type]);
> +			match = true;
> +		} else {
> +			count += sprintf(buf + count, "%s ",
> +					 power_supply_usb_type_text[usb_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_USB_TYPE)
> +		return power_supply_show_usb_type(dev, psy->desc->usb_types,
> +						  psy->desc->num_usb_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(usb_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..4ca8876 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_USB_TYPE,
>   	POWER_SUPPLY_PROP_SCOPE,
>   	POWER_SUPPLY_PROP_PRECHARGE_CURRENT,
>   	POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
> @@ -170,6 +171,19 @@ enum power_supply_type {
>   	POWER_SUPPLY_TYPE_APPLE_BRICK_ID,	/* Apple Charging Method */
>   };
>   
> +enum power_supply_usb_type {
> +	POWER_SUPPLY_USB_TYPE_UNKNOWN = 0,
> +	POWER_SUPPLY_USB_TYPE_SDP,		/* Standard Downstream Port */
> +	POWER_SUPPLY_USB_TYPE_DCP,		/* Dedicated Charging Port */
> +	POWER_SUPPLY_USB_TYPE_CDP,		/* Charging Downstream Port */
> +	POWER_SUPPLY_USB_TYPE_ACA,		/* Accessory Charger Adapters */
> +	POWER_SUPPLY_USB_TYPE_C,		/* Type C Port */
> +	POWER_SUPPLY_USB_TYPE_PD,		/* Power Delivery Port */
> +	POWER_SUPPLY_USB_TYPE_PD_DRP,		/* PD Dual Role Port */
> +	POWER_SUPPLY_USB_TYPE_PD_PPS,		/* PD Programmable Power Supply */
> +	POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID,	/* Apple Charging Method */
> +};
> +
>   enum power_supply_notifier_events {
>   	PSY_EVENT_PROP_CHANGED,
>   };
> @@ -196,6 +210,8 @@ struct power_supply_config {
>   struct power_supply_desc {
>   	const char *name;
>   	enum power_supply_type type;
> +	enum power_supply_usb_type *usb_types;
> +	size_t num_usb_types;
>   	enum power_supply_property *properties;
>   	size_t num_properties;
>   
>
---
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] 35+ messages in thread

* Re: [PATCH v5 4/5] typec: tcpm: Represent source supply through power_supply
@ 2018-03-22  4:09     ` Guenter Roeck
  0 siblings, 0 replies; 35+ messages in thread
From: Guenter Roeck @ 2018-03-22  4:09 UTC (permalink / raw)
  To: Adam Thomson, Heikki Krogerus, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Jun Li
  Cc: linux-usb, linux-pm, linux-kernel, support.opensource

On 03/20/2018 07:33 AM, 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>
> ---
>   drivers/usb/typec/Kconfig           |   1 +
>   drivers/usb/typec/fusb302/Kconfig   |   2 +-
>   drivers/usb/typec/fusb302/fusb302.c |  63 +---------
>   drivers/usb/typec/tcpm.c            | 242 +++++++++++++++++++++++++++++++++++-
>   4 files changed, 245 insertions(+), 63 deletions(-)
> 
> 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 06794c0..6a8f279 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;
> @@ -861,13 +855,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);
> @@ -883,11 +875,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;
>   }
>   
> @@ -1686,43 +1673,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;
> @@ -1762,7 +1712,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;
> @@ -1809,14 +1758,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 b4cf1ca..18ab36f 100644
> --- a/drivers/usb/typec/tcpm.c
> +++ b/drivers/usb/typec/tcpm.c
> @@ -12,6 +12,7 @@
>   #include <linux/kernel.h>
>   #include <linux/module.h>
>   #include <linux/mutex.h>
> +#include <linux/power_supply.h>
>   #include <linux/proc_fs.h>
>   #include <linux/sched/clock.h>
>   #include <linux/seq_file.h>
> @@ -277,6 +278,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_usb_type usb_type;
> +
>   	u32 bist_request;
>   
>   	/* PD state for Vendor Defined Messages */
> @@ -1874,6 +1880,7 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
>   	int ret = -EINVAL;
>   
>   	port->pps_data.supported = false;
> +	port->usb_type = POWER_SUPPLY_USB_TYPE_PD;
>   
>   	/*
>   	 * Select the source PDO providing the most power while staying within
> @@ -1893,8 +1900,11 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
>   			mv = pdo_min_voltage(pdo);
>   			break;
>   		case PDO_TYPE_APDO:
> -			if (pdo_apdo_type(pdo) == APDO_TYPE_PPS)
> +			if (pdo_apdo_type(pdo) == APDO_TYPE_PPS) {
>   				port->pps_data.supported = true;
> +				port->usb_type =
> +					POWER_SUPPLY_USB_TYPE_PD_PPS;
> +			}
>   			continue;
>   		default:
>   			tcpm_log(port, "Invalid PDO type, ignoring");
> @@ -2379,6 +2389,9 @@ static void tcpm_reset_port(struct tcpm_port *port)
>   	port->try_snk_count = 0;
>   	port->supply_voltage = 0;
>   	port->current_limit = 0;
> +	port->usb_type = POWER_SUPPLY_USB_TYPE_C;
> +
> +	power_supply_changed(port->psy);
>   }
>   
>   static void tcpm_detach(struct tcpm_port *port)
> @@ -2911,6 +2924,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 */
> @@ -4077,6 +4092,227 @@ int tcpm_update_sink_capabilities(struct tcpm_port *port, const u32 *pdo,
>   }
>   EXPORT_SYMBOL_GPL(tcpm_update_sink_capabilities);
>   
> +/* Power Supply access to expose source power information */
> +enum tcpm_psy_online_states {
> +	TCPM_PSY_OFFLINE = 0,
> +	TCPM_PSY_FIXED_ONLINE,
> +	TCPM_PSY_PROG_ONLINE,
> +};
> +
> +static enum power_supply_property tcpm_psy_props[] = {
> +	POWER_SUPPLY_PROP_USB_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_USB_TYPE:
> +		val->intval = port->usb_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));

I am really not a friend of excessive ( ).

> +		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_usb_type tcpm_psy_usb_types[] = {
> +	POWER_SUPPLY_USB_TYPE_C,
> +	POWER_SUPPLY_USB_TYPE_PD,
> +	POWER_SUPPLY_USB_TYPE_PD_PPS,
> +};
> +
> +static const char *tcpm_psy_name_prefix = "tcpm-source-psy-";
> +
> +static int devm_tcpm_psy_register(struct tcpm_port *port)
> +{
> +	struct power_supply_config psy_cfg = {};
> +	const char *port_dev_name = dev_name(port->dev);
> +	size_t psy_name_len = strlen(tcpm_psy_name_prefix) +
> +				     strlen(port_dev_name) + 1;
> +	char *psy_name;
> +
> +	psy_cfg.drv_data = port;
> +	psy_name = devm_kzalloc(port->dev, psy_name_len, GFP_KERNEL);
> +	snprintf(psy_name, psy_name_len, "%s%s", tcpm_psy_name_prefix,
> +		 port_dev_name);
> +	port->psy_desc.name = psy_name;
> +	port->psy_desc.type = POWER_SUPPLY_TYPE_USB,
> +	port->psy_desc.usb_types = tcpm_psy_usb_types;
> +	port->psy_desc.num_usb_types = ARRAY_SIZE(tcpm_psy_usb_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->usb_type = POWER_SUPPLY_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;
> @@ -4148,6 +4384,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 (IS_ERR(port->typec_port)) {
>   		err = PTR_ERR(port->typec_port);
> 


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

* [v5,4/5] typec: tcpm: Represent source supply through power_supply
@ 2018-03-22  4:09     ` Guenter Roeck
  0 siblings, 0 replies; 35+ messages in thread
From: Guenter Roeck @ 2018-03-22  4:09 UTC (permalink / raw)
  To: Adam Thomson, Heikki Krogerus, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Jun Li
  Cc: linux-usb, linux-pm, linux-kernel, support.opensource

On 03/20/2018 07:33 AM, 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>
> ---
>   drivers/usb/typec/Kconfig           |   1 +
>   drivers/usb/typec/fusb302/Kconfig   |   2 +-
>   drivers/usb/typec/fusb302/fusb302.c |  63 +---------
>   drivers/usb/typec/tcpm.c            | 242 +++++++++++++++++++++++++++++++++++-
>   4 files changed, 245 insertions(+), 63 deletions(-)
> 
> 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 06794c0..6a8f279 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;
> @@ -861,13 +855,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);
> @@ -883,11 +875,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;
>   }
>   
> @@ -1686,43 +1673,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;
> @@ -1762,7 +1712,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;
> @@ -1809,14 +1758,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 b4cf1ca..18ab36f 100644
> --- a/drivers/usb/typec/tcpm.c
> +++ b/drivers/usb/typec/tcpm.c
> @@ -12,6 +12,7 @@
>   #include <linux/kernel.h>
>   #include <linux/module.h>
>   #include <linux/mutex.h>
> +#include <linux/power_supply.h>
>   #include <linux/proc_fs.h>
>   #include <linux/sched/clock.h>
>   #include <linux/seq_file.h>
> @@ -277,6 +278,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_usb_type usb_type;
> +
>   	u32 bist_request;
>   
>   	/* PD state for Vendor Defined Messages */
> @@ -1874,6 +1880,7 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
>   	int ret = -EINVAL;
>   
>   	port->pps_data.supported = false;
> +	port->usb_type = POWER_SUPPLY_USB_TYPE_PD;
>   
>   	/*
>   	 * Select the source PDO providing the most power while staying within
> @@ -1893,8 +1900,11 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
>   			mv = pdo_min_voltage(pdo);
>   			break;
>   		case PDO_TYPE_APDO:
> -			if (pdo_apdo_type(pdo) == APDO_TYPE_PPS)
> +			if (pdo_apdo_type(pdo) == APDO_TYPE_PPS) {
>   				port->pps_data.supported = true;
> +				port->usb_type =
> +					POWER_SUPPLY_USB_TYPE_PD_PPS;
> +			}
>   			continue;
>   		default:
>   			tcpm_log(port, "Invalid PDO type, ignoring");
> @@ -2379,6 +2389,9 @@ static void tcpm_reset_port(struct tcpm_port *port)
>   	port->try_snk_count = 0;
>   	port->supply_voltage = 0;
>   	port->current_limit = 0;
> +	port->usb_type = POWER_SUPPLY_USB_TYPE_C;
> +
> +	power_supply_changed(port->psy);
>   }
>   
>   static void tcpm_detach(struct tcpm_port *port)
> @@ -2911,6 +2924,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 */
> @@ -4077,6 +4092,227 @@ int tcpm_update_sink_capabilities(struct tcpm_port *port, const u32 *pdo,
>   }
>   EXPORT_SYMBOL_GPL(tcpm_update_sink_capabilities);
>   
> +/* Power Supply access to expose source power information */
> +enum tcpm_psy_online_states {
> +	TCPM_PSY_OFFLINE = 0,
> +	TCPM_PSY_FIXED_ONLINE,
> +	TCPM_PSY_PROG_ONLINE,
> +};
> +
> +static enum power_supply_property tcpm_psy_props[] = {
> +	POWER_SUPPLY_PROP_USB_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_USB_TYPE:
> +		val->intval = port->usb_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));

I am really not a friend of excessive ( ).

> +		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_usb_type tcpm_psy_usb_types[] = {
> +	POWER_SUPPLY_USB_TYPE_C,
> +	POWER_SUPPLY_USB_TYPE_PD,
> +	POWER_SUPPLY_USB_TYPE_PD_PPS,
> +};
> +
> +static const char *tcpm_psy_name_prefix = "tcpm-source-psy-";
> +
> +static int devm_tcpm_psy_register(struct tcpm_port *port)
> +{
> +	struct power_supply_config psy_cfg = {};
> +	const char *port_dev_name = dev_name(port->dev);
> +	size_t psy_name_len = strlen(tcpm_psy_name_prefix) +
> +				     strlen(port_dev_name) + 1;
> +	char *psy_name;
> +
> +	psy_cfg.drv_data = port;
> +	psy_name = devm_kzalloc(port->dev, psy_name_len, GFP_KERNEL);
> +	snprintf(psy_name, psy_name_len, "%s%s", tcpm_psy_name_prefix,
> +		 port_dev_name);
> +	port->psy_desc.name = psy_name;
> +	port->psy_desc.type = POWER_SUPPLY_TYPE_USB,
> +	port->psy_desc.usb_types = tcpm_psy_usb_types;
> +	port->psy_desc.num_usb_types = ARRAY_SIZE(tcpm_psy_usb_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->usb_type = POWER_SUPPLY_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;
> @@ -4148,6 +4384,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 (IS_ERR(port->typec_port)) {
>   		err = PTR_ERR(port->typec_port);
>
---
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] 35+ messages in thread

* RE: [PATCH v5 5/5] typec: tcpm: Add support for sink PPS related messages
@ 2018-03-22  9:37       ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 35+ messages in thread
From: Adam Thomson @ 2018-03-22  9:37 UTC (permalink / raw)
  To: Guenter Roeck, Adam Thomson, Heikki Krogerus, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Jun Li
  Cc: linux-usb, linux-pm, linux-kernel, Support Opensource

On 22 March 2018 03:53, Guenter Roeck wrote:

> > +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)
>
> This seems to be the only use of 'data'. Can you explain why it is needed
> in the first place ? Am I missing something ?

Actually it's a fair point. Right now it's not needed so will remove it and
directly reference the ext_msg.data array.

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

* [v5,5/5] typec: tcpm: Add support for sink PPS related messages
@ 2018-03-22  9:37       ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 35+ messages in thread
From: Opensource [Adam Thomson] @ 2018-03-22  9:37 UTC (permalink / raw)
  To: Guenter Roeck, Adam Thomson, Heikki Krogerus, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Jun Li
  Cc: linux-usb, linux-pm, linux-kernel, Support Opensource

T24gMjIgTWFyY2ggMjAxOCAwMzo1MywgR3VlbnRlciBSb2VjayB3cm90ZToNCg0KPiA+ICtzdGF0
aWMgdm9pZCB0Y3BtX3BkX2V4dF9tc2dfcmVxdWVzdChzdHJ1Y3QgdGNwbV9wb3J0ICpwb3J0LA0K
PiA+ICsJCQkJICAgIGNvbnN0IHN0cnVjdCBwZF9tZXNzYWdlICptc2cpDQo+ID4gK3sNCj4gPiAr
CWVudW0gcGRfZXh0X21zZ190eXBlIHR5cGUgPSBwZF9oZWFkZXJfdHlwZV9sZShtc2ctPmhlYWRl
cik7DQo+ID4gKwl1bnNpZ25lZCBpbnQgZGF0YV9zaXplID0gcGRfZXh0X2hlYWRlcl9kYXRhX3Np
emVfbGUobXNnLQ0KPiA+ZXh0X21zZy5oZWFkZXIpOw0KPiA+ICsJdTggKmRhdGE7DQo+ID4gKw0K
PiA+ICsJaWYgKCEobXNnLT5leHRfbXNnLmhlYWRlciAmJiBQRF9FWFRfSERSX0NIVU5LRUQpKSB7
DQo+ID4gKwkJdGNwbV9sb2cocG9ydCwgIlVuY2h1bmtlZCBleHRlbmRlZCBtZXNzYWdlcyB1bnN1
cHBvcnRlZCIpOw0KPiA+ICsJCXJldHVybjsNCj4gPiArCX0NCj4gPiArDQo+ID4gKwlpZiAoZGF0
YV9zaXplID4gKFBEX0VYVF9NQVhfQ0hVTktfREFUQSkpIHsNCj4gPiArCQl0Y3BtX2xvZyhwb3J0
LCAiQ2h1bmsgaGFuZGxpbmcgbm90IHlldCBzdXBwb3J0ZWQiKTsNCj4gPiArCQlyZXR1cm47DQo+
ID4gKwl9DQo+ID4gKw0KPiA+ICsJZGF0YSA9IGt6YWxsb2MoZGF0YV9zaXplLCBHRlBfS0VSTkVM
KTsNCj4gPiArCWlmICghZGF0YSkgew0KPiA+ICsJCXRjcG1fbG9nKHBvcnQsICJGYWlsZWQgdG8g
YWxsb2NhdGUgbWVtb3J5IGZvciBleHQgbXNnIGRhdGEiKTsNCj4gPiArCQlyZXR1cm47DQo+ID4g
Kwl9DQo+ID4gKwltZW1jcHkoZGF0YSwgbXNnLT5leHRfbXNnLmRhdGEsIGRhdGFfc2l6ZSk7DQo+
ID4gKw0KPiA+ICsJc3dpdGNoICh0eXBlKSB7DQo+ID4gKwljYXNlIFBEX0VYVF9TVEFUVVM6DQo+
ID4gKwkJLyoNCj4gPiArCQkgKiBJZiBQUFMgcmVsYXRlZCBldmVudHMgcmFpc2VkIHRoZW4gZ2V0
IFBQUyBzdGF0dXMgdG8gY2xlYXINCj4gPiArCQkgKiAoc2VlIFVTQiBQRCAzLjAgU3BlYywgNi41
LjIuNCkNCj4gPiArCQkgKi8NCj4gPiArCQlpZiAoZGF0YVtVU0JfUERfRVhUX1NEQl9FVkVOVF9G
TEFHU10gJg0KPiBVU0JfUERfRVhUX1NEQl9QUFNfRVZFTlRTKQ0KPg0KPiBUaGlzIHNlZW1zIHRv
IGJlIHRoZSBvbmx5IHVzZSBvZiAnZGF0YScuIENhbiB5b3UgZXhwbGFpbiB3aHkgaXQgaXMgbmVl
ZGVkDQo+IGluIHRoZSBmaXJzdCBwbGFjZSA/IEFtIEkgbWlzc2luZyBzb21ldGhpbmcgPw0KDQpB
Y3R1YWxseSBpdCdzIGEgZmFpciBwb2ludC4gUmlnaHQgbm93IGl0J3Mgbm90IG5lZWRlZCBzbyB3
aWxsIHJlbW92ZSBpdCBhbmQNCmRpcmVjdGx5IHJlZmVyZW5jZSB0aGUgZXh0X21zZy5kYXRhIGFy
cmF5Lg0K
---
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] 35+ messages in thread

* RE: [PATCH v5 1/5] typec: tcpm: Add core support for sink side PPS
@ 2018-03-22  9:44       ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 35+ messages in thread
From: Adam Thomson @ 2018-03-22  9:44 UTC (permalink / raw)
  To: Guenter Roeck, Adam Thomson, Heikki Krogerus, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Jun Li
  Cc: linux-usb, linux-pm, linux-kernel, Support Opensource

On 22 March 2018 04:03, Guenter Roeck wrote:

> >   static enum pdo_err tcpm_caps_err(struct tcpm_port *port, const u32 *pdo,
> > @@ -1308,6 +1347,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])))
>
> Unnecessary ( )

I have to say I think it's neater/clearer with than without but if that's
something you really don't like then I'll remove them.


> > +static int tcpm_pps_set_op_curr(struct tcpm_port *port, u16 op_curr)
> > +{
> > +	unsigned int target_mw;
> > +	int ret = 0;
> > +
>
> Unnecessary initialization.

Ok, will remove.


> > +static int tcpm_pps_set_out_volt(struct tcpm_port *port, u16 out_volt)
> > +{
> > +	unsigned int target_mw;
> > +	int ret = 0;
> > +
> Unnecessary initialization.

Ditto

> > +	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)) {
>
> Unnecessary ( )

Ok.

> > +		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))
>
> Unnecessary ( )

Actually agree on this one :)

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

* [v5,1/5] typec: tcpm: Add core support for sink side PPS
@ 2018-03-22  9:44       ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 35+ messages in thread
From: Opensource [Adam Thomson] @ 2018-03-22  9:44 UTC (permalink / raw)
  To: Guenter Roeck, Adam Thomson, Heikki Krogerus, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Jun Li
  Cc: linux-usb, linux-pm, linux-kernel, Support Opensource

T24gMjIgTWFyY2ggMjAxOCAwNDowMywgR3VlbnRlciBSb2VjayB3cm90ZToNCg0KPiA+ICAgc3Rh
dGljIGVudW0gcGRvX2VyciB0Y3BtX2NhcHNfZXJyKHN0cnVjdCB0Y3BtX3BvcnQgKnBvcnQsIGNv
bnN0IHUzMiAqcGRvLA0KPiA+IEBAIC0xMzA4LDYgKzEzNDcsMjYgQEAgc3RhdGljIGVudW0gcGRv
X2VyciB0Y3BtX2NhcHNfZXJyKHN0cnVjdCB0Y3BtX3BvcnQNCj4gKnBvcnQsIGNvbnN0IHUzMiAq
cGRvLA0KPiA+ICAgCQkJCQkgIHBkb19taW5fdm9sdGFnZShwZG9baSAtIDFdKSkpDQo+ID4gICAJ
CQkJCXJldHVybiBQRE9fRVJSX0RVUEVfUERPOw0KPiA+ICAgCQkJCWJyZWFrOw0KPiA+ICsJCQkv
Kg0KPiA+ICsJCQkgKiBUaGUgUHJvZ3JhbW1hYmxlIFBvd2VyIFN1cHBseSBBUERPcywgaWYgcHJl
c2VudCwNCj4gPiArCQkJICogc2hhbGwgYmUgc2VudCBpbiBNYXhpbXVtIFZvbHRhZ2Ugb3JkZXI7
DQo+ID4gKwkJCSAqIGxvd2VzdCB0byBoaWdoZXN0Lg0KPiA+ICsJCQkgKi8NCj4gPiArCQkJY2Fz
ZSBQRE9fVFlQRV9BUERPOg0KPiA+ICsJCQkJaWYgKHBkb19hcGRvX3R5cGUocGRvW2ldKSAhPSBB
UERPX1RZUEVfUFBTKQ0KPiA+ICsJCQkJCWJyZWFrOw0KPiA+ICsNCj4gPiArCQkJCWlmIChwZG9f
cHBzX2FwZG9fbWF4X2N1cnJlbnQocGRvW2ldKSA8DQo+ID4gKwkJCQkgICAgcGRvX3Bwc19hcGRv
X21heF9jdXJyZW50KHBkb1tpIC0gMV0pKQ0KPiA+ICsJCQkJCXJldHVybg0KPiBQRE9fRVJSX1BQ
U19BUERPX05PVF9TT1JURUQ7DQo+ID4gKwkJCQllbHNlIGlmICgocGRvX3Bwc19hcGRvX21pbl92
b2x0YWdlKHBkb1tpXSkgPT0NCj4gPiArCQkJCQkgIHBkb19wcHNfYXBkb19taW5fdm9sdGFnZShw
ZG9baSAtIDFdKSkNCj4gJiYNCj4gPiArCQkJCQkgKHBkb19wcHNfYXBkb19tYXhfdm9sdGFnZShw
ZG9baV0pID09DQo+ID4gKwkJCQkJICBwZG9fcHBzX2FwZG9fbWF4X3ZvbHRhZ2UocGRvW2kgLSAx
XSkpDQo+ICYmDQo+ID4gKwkJCQkJIChwZG9fcHBzX2FwZG9fbWF4X2N1cnJlbnQocGRvW2ldKSA9
PQ0KPiA+ICsJCQkJCSAgcGRvX3Bwc19hcGRvX21heF9jdXJyZW50KHBkb1tpIC0gMV0pKSkNCj4N
Cj4gVW5uZWNlc3NhcnkgKCApDQoNCkkgaGF2ZSB0byBzYXkgSSB0aGluayBpdCdzIG5lYXRlci9j
bGVhcmVyIHdpdGggdGhhbiB3aXRob3V0IGJ1dCBpZiB0aGF0J3MNCnNvbWV0aGluZyB5b3UgcmVh
bGx5IGRvbid0IGxpa2UgdGhlbiBJJ2xsIHJlbW92ZSB0aGVtLg0KDQoNCj4gPiArc3RhdGljIGlu
dCB0Y3BtX3Bwc19zZXRfb3BfY3VycihzdHJ1Y3QgdGNwbV9wb3J0ICpwb3J0LCB1MTYgb3BfY3Vy
cikNCj4gPiArew0KPiA+ICsJdW5zaWduZWQgaW50IHRhcmdldF9tdzsNCj4gPiArCWludCByZXQg
PSAwOw0KPiA+ICsNCj4NCj4gVW5uZWNlc3NhcnkgaW5pdGlhbGl6YXRpb24uDQoNCk9rLCB3aWxs
IHJlbW92ZS4NCg0KDQo+ID4gK3N0YXRpYyBpbnQgdGNwbV9wcHNfc2V0X291dF92b2x0KHN0cnVj
dCB0Y3BtX3BvcnQgKnBvcnQsIHUxNiBvdXRfdm9sdCkNCj4gPiArew0KPiA+ICsJdW5zaWduZWQg
aW50IHRhcmdldF9tdzsNCj4gPiArCWludCByZXQgPSAwOw0KPiA+ICsNCj4gVW5uZWNlc3Nhcnkg
aW5pdGlhbGl6YXRpb24uDQoNCkRpdHRvDQoNCj4gPiArCW11dGV4X2xvY2soJnBvcnQtPnN3YXBf
bG9jayk7DQo+ID4gKwltdXRleF9sb2NrKCZwb3J0LT5sb2NrKTsNCj4gPiArDQo+ID4gKwlpZiAo
IXBvcnQtPnBwc19kYXRhLmFjdGl2ZSkgew0KPiA+ICsJCXJldCA9IC1FT1BOT1RTVVBQOw0KPiA+
ICsJCWdvdG8gcG9ydF91bmxvY2s7DQo+ID4gKwl9DQo+ID4gKw0KPiA+ICsJaWYgKHBvcnQtPnN0
YXRlICE9IFNOS19SRUFEWSkgew0KPiA+ICsJCXJldCA9IC1FQUdBSU47DQo+ID4gKwkJZ290byBw
b3J0X3VubG9jazsNCj4gPiArCX0NCj4gPiArDQo+ID4gKwlpZiAoKG91dF92b2x0IDwgcG9ydC0+
cHBzX2RhdGEubWluX3ZvbHQpIHx8DQo+ID4gKwkgICAgKG91dF92b2x0ID4gcG9ydC0+cHBzX2Rh
dGEubWF4X3ZvbHQpKSB7DQo+DQo+IFVubmVjZXNzYXJ5ICggKQ0KDQpPay4NCg0KPiA+ICsJCXJl
dCA9IC1FSU5WQUw7DQo+ID4gKwkJZ290byBwb3J0X3VubG9jazsNCj4gPiArCX0NCj4gPiArDQo+
ID4gKwl0YXJnZXRfbXcgPSAocG9ydC0+cHBzX2RhdGEub3BfY3VyciAqIG91dF92b2x0KSAvIDEw
MDA7DQo+ID4gKwlpZiAodGFyZ2V0X213IDwgcG9ydC0+b3BlcmF0aW5nX3Nua19tdykgew0KPiA+
ICsJCXJldCA9IC1FSU5WQUw7DQo+ID4gKwkJZ290byBwb3J0X3VubG9jazsNCj4gPiArCX0NCj4g
PiArDQo+ID4gKwlyZWluaXRfY29tcGxldGlvbigmcG9ydC0+cHBzX2NvbXBsZXRlKTsNCj4gPiAr
CXBvcnQtPnBwc19kYXRhLm91dF92b2x0ID0gb3V0X3ZvbHQ7DQo+ID4gKwlwb3J0LT5wcHNfc3Rh
dHVzID0gMDsNCj4gPiArCXBvcnQtPnBwc19wZW5kaW5nID0gdHJ1ZTsNCj4gPiArCXRjcG1fc2V0
X3N0YXRlKHBvcnQsIFNOS19ORUdPVElBVEVfUFBTX0NBUEFCSUxJVElFUywgMCk7DQo+ID4gKwlt
dXRleF91bmxvY2soJnBvcnQtPmxvY2spOw0KPiA+ICsNCj4gPiArCWlmICghd2FpdF9mb3JfY29t
cGxldGlvbl90aW1lb3V0KCZwb3J0LT5wcHNfY29tcGxldGUsDQo+ID4gKwkJCQltc2Vjc190b19q
aWZmaWVzKFBEX1NUQVRFX01BQ0hJTkVfVElNRU9VVCkpKQ0KPiA+ICsJCXJldCA9IC1FVElNRURP
VVQ7DQo+ID4gKwllbHNlDQo+ID4gKwkJcmV0ID0gcG9ydC0+cHBzX3N0YXR1czsNCj4gPiArDQo+
ID4gKwlnb3RvIHN3YXBfdW5sb2NrOw0KPiA+ICsNCj4gPiArcG9ydF91bmxvY2s6DQo+ID4gKwlt
dXRleF91bmxvY2soJnBvcnQtPmxvY2spOw0KPiA+ICtzd2FwX3VubG9jazoNCj4gPiArCW11dGV4
X3VubG9jaygmcG9ydC0+c3dhcF9sb2NrKTsNCj4gPiArDQo+ID4gKwlyZXR1cm4gcmV0Ow0KPiA+
ICt9DQo+ID4gKw0KPiA+ICtzdGF0aWMgaW50IHRjcG1fcHBzX2FjdGl2YXRlKHN0cnVjdCB0Y3Bt
X3BvcnQgKnBvcnQsIGJvb2wgYWN0aXZhdGUpDQo+ID4gK3sNCj4gPiArCWludCByZXQgPSAwOw0K
PiA+ICsNCj4gPiArCW11dGV4X2xvY2soJnBvcnQtPnN3YXBfbG9jayk7DQo+ID4gKwltdXRleF9s
b2NrKCZwb3J0LT5sb2NrKTsNCj4gPiArDQo+ID4gKwlpZiAoIXBvcnQtPnBwc19kYXRhLnN1cHBv
cnRlZCkgew0KPiA+ICsJCXJldCA9IC1FT1BOT1RTVVBQOw0KPiA+ICsJCWdvdG8gcG9ydF91bmxv
Y2s7DQo+ID4gKwl9DQo+ID4gKw0KPiA+ICsJLyogVHJ5aW5nIHRvIGRlYWN0aXZhdGUgUFBTIHdo
ZW4gYWxyZWFkeSBkZWFjdGl2YXRlZCBzbyBqdXN0IGJhaWwgKi8NCj4gPiArCWlmICgoIXBvcnQt
PnBwc19kYXRhLmFjdGl2ZSkgJiYgKCFhY3RpdmF0ZSkpDQo+DQo+IFVubmVjZXNzYXJ5ICggKQ0K
DQpBY3R1YWxseSBhZ3JlZSBvbiB0aGlzIG9uZSA6KQ0K
---
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] 35+ messages in thread

* RE: [PATCH v5 3/5] power: supply: Add 'usb_type' property and supporting code
@ 2018-03-22 10:27       ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 35+ messages in thread
From: Adam Thomson @ 2018-03-22 10:27 UTC (permalink / raw)
  To: Guenter Roeck, Adam Thomson, Heikki Krogerus, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Jun Li
  Cc: linux-usb, linux-pm, linux-kernel, Support Opensource

On 22 March 2018 04:08, Guenter Roeck wrote:

> > +static ssize_t power_supply_show_usb_type(struct device *dev,
> > +					  enum power_supply_usb_type
> *usb_types,
> > +					  ssize_t num_usb_types,
> > +					  union power_supply_propval *value,
> > +					  char *buf)
> > +{
> > +	enum power_supply_usb_type usb_type;
> > +	ssize_t count = 0;
> > +	bool match = false;
> > +	int i;
> > +
> > +	if ((!usb_types) || (num_usb_types <= 0)) {
>
> Unnecessary ( )

Fine, can remove.

>
> > +		dev_warn(dev, "driver has no valid connected types\n");
>
> Are those warnings useful or do they just clog the log ? Either case, if that happens,
> wouldn't it be better to detect the situation during registration and abort ?

This is added as an optional property for supply drivers. This will only show
up if a supply driver adds the USB_TYPE property to its list, and then someone
attempts to access it. Actually there's no current checking for the psy_desc
information provided to the register function, so for example if it's NULL then
the kernel will dump when it tries to dereference. I'll take a look at this and
see if I can add something, including checks on the usb_type property.

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

* [v5,3/5] power: supply: Add 'usb_type' property and supporting code
@ 2018-03-22 10:27       ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 35+ messages in thread
From: Opensource [Adam Thomson] @ 2018-03-22 10:27 UTC (permalink / raw)
  To: Guenter Roeck, Adam Thomson, Heikki Krogerus, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Jun Li
  Cc: linux-usb, linux-pm, linux-kernel, Support Opensource

T24gMjIgTWFyY2ggMjAxOCAwNDowOCwgR3VlbnRlciBSb2VjayB3cm90ZToNCg0KPiA+ICtzdGF0
aWMgc3NpemVfdCBwb3dlcl9zdXBwbHlfc2hvd191c2JfdHlwZShzdHJ1Y3QgZGV2aWNlICpkZXYs
DQo+ID4gKwkJCQkJICBlbnVtIHBvd2VyX3N1cHBseV91c2JfdHlwZQ0KPiAqdXNiX3R5cGVzLA0K
PiA+ICsJCQkJCSAgc3NpemVfdCBudW1fdXNiX3R5cGVzLA0KPiA+ICsJCQkJCSAgdW5pb24gcG93
ZXJfc3VwcGx5X3Byb3B2YWwgKnZhbHVlLA0KPiA+ICsJCQkJCSAgY2hhciAqYnVmKQ0KPiA+ICt7
DQo+ID4gKwllbnVtIHBvd2VyX3N1cHBseV91c2JfdHlwZSB1c2JfdHlwZTsNCj4gPiArCXNzaXpl
X3QgY291bnQgPSAwOw0KPiA+ICsJYm9vbCBtYXRjaCA9IGZhbHNlOw0KPiA+ICsJaW50IGk7DQo+
ID4gKw0KPiA+ICsJaWYgKCghdXNiX3R5cGVzKSB8fCAobnVtX3VzYl90eXBlcyA8PSAwKSkgew0K
Pg0KPiBVbm5lY2Vzc2FyeSAoICkNCg0KRmluZSwgY2FuIHJlbW92ZS4NCg0KPg0KPiA+ICsJCWRl
dl93YXJuKGRldiwgImRyaXZlciBoYXMgbm8gdmFsaWQgY29ubmVjdGVkIHR5cGVzXG4iKTsNCj4N
Cj4gQXJlIHRob3NlIHdhcm5pbmdzIHVzZWZ1bCBvciBkbyB0aGV5IGp1c3QgY2xvZyB0aGUgbG9n
ID8gRWl0aGVyIGNhc2UsIGlmIHRoYXQgaGFwcGVucywNCj4gd291bGRuJ3QgaXQgYmUgYmV0dGVy
IHRvIGRldGVjdCB0aGUgc2l0dWF0aW9uIGR1cmluZyByZWdpc3RyYXRpb24gYW5kIGFib3J0ID8N
Cg0KVGhpcyBpcyBhZGRlZCBhcyBhbiBvcHRpb25hbCBwcm9wZXJ0eSBmb3Igc3VwcGx5IGRyaXZl
cnMuIFRoaXMgd2lsbCBvbmx5IHNob3cNCnVwIGlmIGEgc3VwcGx5IGRyaXZlciBhZGRzIHRoZSBV
U0JfVFlQRSBwcm9wZXJ0eSB0byBpdHMgbGlzdCwgYW5kIHRoZW4gc29tZW9uZQ0KYXR0ZW1wdHMg
dG8gYWNjZXNzIGl0LiBBY3R1YWxseSB0aGVyZSdzIG5vIGN1cnJlbnQgY2hlY2tpbmcgZm9yIHRo
ZSBwc3lfZGVzYw0KaW5mb3JtYXRpb24gcHJvdmlkZWQgdG8gdGhlIHJlZ2lzdGVyIGZ1bmN0aW9u
LCBzbyBmb3IgZXhhbXBsZSBpZiBpdCdzIE5VTEwgdGhlbg0KdGhlIGtlcm5lbCB3aWxsIGR1bXAg
d2hlbiBpdCB0cmllcyB0byBkZXJlZmVyZW5jZS4gSSdsbCB0YWtlIGEgbG9vayBhdCB0aGlzIGFu
ZA0Kc2VlIGlmIEkgY2FuIGFkZCBzb21ldGhpbmcsIGluY2x1ZGluZyBjaGVja3Mgb24gdGhlIHVz
Yl90eXBlIHByb3BlcnR5Lg0K
---
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] 35+ messages in thread

* RE: [PATCH v5 4/5] typec: tcpm: Represent source supply through power_supply
@ 2018-03-22 10:40       ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 35+ messages in thread
From: Adam Thomson @ 2018-03-22 10:40 UTC (permalink / raw)
  To: Guenter Roeck, Adam Thomson, Heikki Krogerus, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Jun Li
  Cc: linux-usb, linux-pm, linux-kernel, Support Opensource

On 22 March 2018 04:09, Guenter Roeck wrote:

> > +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));
> 
> I am really not a friend of excessive ( ).

Yes, I got that. :) I am of the opinion that they should be used to enforce
precedence. This to me is good coding practice and makes it unambiguous for the
reader. That's why I use them as above. Do you think the above uses make it
harder to understand or more difficult to maintain?

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

* [v5,4/5] typec: tcpm: Represent source supply through power_supply
@ 2018-03-22 10:40       ` Opensource [Adam Thomson]
  0 siblings, 0 replies; 35+ messages in thread
From: Opensource [Adam Thomson] @ 2018-03-22 10:40 UTC (permalink / raw)
  To: Guenter Roeck, Adam Thomson, Heikki Krogerus, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Jun Li
  Cc: linux-usb, linux-pm, linux-kernel, Support Opensource

On 22 March 2018 04:09, Guenter Roeck wrote:

> > +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));
> 
> I am really not a friend of excessive ( ).

Yes, I got that. :) I am of the opinion that they should be used to enforce
precedence. This to me is good coding practice and makes it unambiguous for the
reader. That's why I use them as above. Do you think the above uses make it
harder to understand or more difficult to maintain?

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

* Re: [PATCH v5 4/5] typec: tcpm: Represent source supply through power_supply
@ 2018-03-22 13:20         ` Guenter Roeck
  0 siblings, 0 replies; 35+ messages in thread
From: Guenter Roeck @ 2018-03-22 13:20 UTC (permalink / raw)
  To: Adam Thomson, Heikki Krogerus, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Jun Li
  Cc: linux-usb, linux-pm, linux-kernel, Support Opensource

On 03/22/2018 03:40 AM, Adam Thomson wrote:
> On 22 March 2018 04:09, Guenter Roeck wrote:
> 
>>> +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));
>>
>> I am really not a friend of excessive ( ).
> 
> Yes, I got that. :) I am of the opinion that they should be used to enforce
> precedence. This to me is good coding practice and makes it unambiguous for the
> reader. That's why I use them as above. Do you think the above uses make it
> harder to understand or more difficult to maintain?
> 
It confuses me and makes me think I am missing something, and causes me to miss
the _real_ problems. If the compiler is not able to enforce precedence, even more so
in situations like the above, I think it is about time to dump it.

Either case, your call to make. I wont give patches with excessive ( ) a Reviewed-by:,
but then others can review the code.

Thanks,
Guenter

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

* [v5,4/5] typec: tcpm: Represent source supply through power_supply
@ 2018-03-22 13:20         ` Guenter Roeck
  0 siblings, 0 replies; 35+ messages in thread
From: Guenter Roeck @ 2018-03-22 13:20 UTC (permalink / raw)
  To: Adam Thomson, Heikki Krogerus, Greg Kroah-Hartman,
	Sebastian Reichel, Hans de Goede, Jun Li
  Cc: linux-usb, linux-pm, linux-kernel, Support Opensource

On 03/22/2018 03:40 AM, Adam Thomson wrote:
> On 22 March 2018 04:09, Guenter Roeck wrote:
> 
>>> +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));
>>
>> I am really not a friend of excessive ( ).
> 
> Yes, I got that. :) I am of the opinion that they should be used to enforce
> precedence. This to me is good coding practice and makes it unambiguous for the
> reader. That's why I use them as above. Do you think the above uses make it
> harder to understand or more difficult to maintain?
> 
It confuses me and makes me think I am missing something, and causes me to miss
the _real_ problems. If the compiler is not able to enforce precedence, even more so
in situations like the above, I think it is about time to dump it.

Either case, your call to make. I wont give patches with excessive ( ) a Reviewed-by:,
but then others can review the code.

Thanks,
Guenter
---
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] 35+ messages in thread

end of thread, other threads:[~2018-03-22 13:20 UTC | newest]

Thread overview: 35+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-03-20 14:33 [PATCH v5 0/5] typec: tcpm: Add sink side support for PPS Adam Thomson
2018-03-20 14:33 ` Adam Thomson
2018-03-20 14:33 ` [PATCH v5 1/5] typec: tcpm: Add core support for sink side PPS Adam Thomson
2018-03-20 14:33   ` Adam Thomson
2018-03-20 14:33   ` [v5,1/5] " Opensource [Adam Thomson]
2018-03-22  4:03   ` [PATCH v5 1/5] " Guenter Roeck
2018-03-22  4:03     ` [v5,1/5] " Guenter Roeck
2018-03-22  9:44     ` [PATCH v5 1/5] " Adam Thomson
2018-03-22  9:44       ` [v5,1/5] " Opensource [Adam Thomson]
2018-03-20 14:33 ` [PATCH v5 2/5] Documentation: power: Initial effort to document power_supply ABI Adam Thomson
2018-03-20 14:33   ` Adam Thomson
2018-03-20 14:33   ` [v5,2/5] " Opensource [Adam Thomson]
2018-03-20 14:33 ` [PATCH v5 3/5] power: supply: Add 'usb_type' property and supporting code Adam Thomson
2018-03-20 14:33   ` Adam Thomson
2018-03-20 14:33   ` [v5,3/5] " Opensource [Adam Thomson]
2018-03-22  4:07   ` [PATCH v5 3/5] " Guenter Roeck
2018-03-22  4:07     ` [v5,3/5] " Guenter Roeck
2018-03-22 10:27     ` [PATCH v5 3/5] " Adam Thomson
2018-03-22 10:27       ` [v5,3/5] " Opensource [Adam Thomson]
2018-03-20 14:33 ` [PATCH v5 4/5] typec: tcpm: Represent source supply through power_supply Adam Thomson
2018-03-20 14:33   ` Adam Thomson
2018-03-20 14:33   ` [v5,4/5] " Opensource [Adam Thomson]
2018-03-22  4:09   ` [PATCH v5 4/5] " Guenter Roeck
2018-03-22  4:09     ` [v5,4/5] " Guenter Roeck
2018-03-22 10:40     ` [PATCH v5 4/5] " Adam Thomson
2018-03-22 10:40       ` [v5,4/5] " Opensource [Adam Thomson]
2018-03-22 13:20       ` [PATCH v5 4/5] " Guenter Roeck
2018-03-22 13:20         ` [v5,4/5] " Guenter Roeck
2018-03-20 14:33 ` [PATCH v5 5/5] typec: tcpm: Add support for sink PPS related messages Adam Thomson
2018-03-20 14:33   ` Adam Thomson
2018-03-20 14:33   ` [v5,5/5] " Opensource [Adam Thomson]
2018-03-22  3:52   ` [PATCH v5 5/5] " Guenter Roeck
2018-03-22  3:52     ` [v5,5/5] " Guenter Roeck
2018-03-22  9:37     ` [PATCH v5 5/5] " Adam Thomson
2018-03-22  9:37       ` [v5,5/5] " Opensource [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.