aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb/typec/tcpm
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/typec/tcpm')
-rw-r--r--drivers/usb/typec/tcpm/fusb302.c16
-rw-r--r--drivers/usb/typec/tcpm/tcpci.c123
-rw-r--r--drivers/usb/typec/tcpm/tcpci.h25
-rw-r--r--drivers/usb/typec/tcpm/tcpci_maxim.c51
-rw-r--r--drivers/usb/typec/tcpm/tcpm.c227
-rw-r--r--drivers/usb/typec/tcpm/wcove.c3
6 files changed, 369 insertions, 76 deletions
diff --git a/drivers/usb/typec/tcpm/fusb302.c b/drivers/usb/typec/tcpm/fusb302.c
index 99562cc65ca6..ebc46b9f776c 100644
--- a/drivers/usb/typec/tcpm/fusb302.c
+++ b/drivers/usb/typec/tcpm/fusb302.c
@@ -343,12 +343,11 @@ static int fusb302_sw_reset(struct fusb302_chip *chip)
return ret;
}
-static int fusb302_enable_tx_auto_retries(struct fusb302_chip *chip)
+static int fusb302_enable_tx_auto_retries(struct fusb302_chip *chip, u8 retry_count)
{
int ret = 0;
- ret = fusb302_i2c_set_bits(chip, FUSB_REG_CONTROL3,
- FUSB_REG_CONTROL3_N_RETRIES_3 |
+ ret = fusb302_i2c_set_bits(chip, FUSB_REG_CONTROL3, retry_count |
FUSB_REG_CONTROL3_AUTO_RETRY);
return ret;
@@ -399,7 +398,7 @@ static int tcpm_init(struct tcpc_dev *dev)
ret = fusb302_sw_reset(chip);
if (ret < 0)
return ret;
- ret = fusb302_enable_tx_auto_retries(chip);
+ ret = fusb302_enable_tx_auto_retries(chip, FUSB_REG_CONTROL3_N_RETRIES_3);
if (ret < 0)
return ret;
ret = fusb302_init_interrupt(chip);
@@ -1017,7 +1016,7 @@ static const char * const transmit_type_name[] = {
};
static int tcpm_pd_transmit(struct tcpc_dev *dev, enum tcpm_transmit_type type,
- const struct pd_message *msg)
+ const struct pd_message *msg, unsigned int negotiated_rev)
{
struct fusb302_chip *chip = container_of(dev, struct fusb302_chip,
tcpc_dev);
@@ -1026,6 +1025,13 @@ static int tcpm_pd_transmit(struct tcpc_dev *dev, enum tcpm_transmit_type type,
mutex_lock(&chip->lock);
switch (type) {
case TCPC_TX_SOP:
+ /* nRetryCount 3 in P2.0 spec, whereas 2 in PD3.0 spec */
+ ret = fusb302_enable_tx_auto_retries(chip, negotiated_rev > PD_REV20 ?
+ FUSB_REG_CONTROL3_N_RETRIES_2 :
+ FUSB_REG_CONTROL3_N_RETRIES_3);
+ if (ret < 0)
+ fusb302_log(chip, "Cannot update retry count ret=%d", ret);
+
ret = fusb302_pd_send_message(chip, msg);
if (ret < 0)
fusb302_log(chip,
diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c
index f9f0af64da5f..f676abab044b 100644
--- a/drivers/usb/typec/tcpm/tcpci.c
+++ b/drivers/usb/typec/tcpm/tcpci.c
@@ -18,7 +18,11 @@
#include "tcpci.h"
-#define PD_RETRY_COUNT 3
+#define PD_RETRY_COUNT_DEFAULT 3
+#define PD_RETRY_COUNT_3_0_OR_HIGHER 2
+#define AUTO_DISCHARGE_DEFAULT_THRESHOLD_MV 3500
+#define AUTO_DISCHARGE_PD_HEADROOM_MV 850
+#define AUTO_DISCHARGE_PPS_HEADROOM_MV 1250
struct tcpci {
struct device *dev;
@@ -268,6 +272,58 @@ static int tcpci_set_vconn(struct tcpc_dev *tcpc, bool enable)
enable ? TCPC_POWER_CTRL_VCONN_ENABLE : 0);
}
+static int tcpci_enable_auto_vbus_discharge(struct tcpc_dev *dev, bool enable)
+{
+ struct tcpci *tcpci = tcpc_to_tcpci(dev);
+ int ret;
+
+ ret = regmap_update_bits(tcpci->regmap, TCPC_POWER_CTRL, TCPC_POWER_CTRL_AUTO_DISCHARGE,
+ enable ? TCPC_POWER_CTRL_AUTO_DISCHARGE : 0);
+ return ret;
+}
+
+static int tcpci_set_auto_vbus_discharge_threshold(struct tcpc_dev *dev, enum typec_pwr_opmode mode,
+ bool pps_active, u32 requested_vbus_voltage_mv)
+{
+ struct tcpci *tcpci = tcpc_to_tcpci(dev);
+ unsigned int pwr_ctrl, threshold = 0;
+ int ret;
+
+ /*
+ * Indicates that vbus is going to go away due PR_SWAP, hard reset etc.
+ * Do not discharge vbus here.
+ */
+ if (requested_vbus_voltage_mv == 0)
+ goto write_thresh;
+
+ ret = regmap_read(tcpci->regmap, TCPC_POWER_CTRL, &pwr_ctrl);
+ if (ret < 0)
+ return ret;
+
+ if (pwr_ctrl & TCPC_FAST_ROLE_SWAP_EN) {
+ /* To prevent disconnect when the source is fast role swap is capable. */
+ threshold = AUTO_DISCHARGE_DEFAULT_THRESHOLD_MV;
+ } else if (mode == TYPEC_PWR_MODE_PD) {
+ if (pps_active)
+ threshold = (95 * requested_vbus_voltage_mv / 100) -
+ AUTO_DISCHARGE_PD_HEADROOM_MV;
+ else
+ threshold = (95 * requested_vbus_voltage_mv / 100) -
+ AUTO_DISCHARGE_PPS_HEADROOM_MV;
+ } else {
+ /* 3.5V for non-pd sink */
+ threshold = AUTO_DISCHARGE_DEFAULT_THRESHOLD_MV;
+ }
+
+ threshold = threshold / TCPC_VBUS_SINK_DISCONNECT_THRESH_LSB_MV;
+
+ if (threshold > TCPC_VBUS_SINK_DISCONNECT_THRESH_MAX)
+ return -EINVAL;
+
+write_thresh:
+ return tcpci_write16(tcpci, TCPC_VBUS_SINK_DISCONNECT_THRESH, threshold);
+}
+
static int tcpci_enable_frs(struct tcpc_dev *dev, bool enable)
{
struct tcpci *tcpci = tcpc_to_tcpci(dev);
@@ -284,6 +340,14 @@ static int tcpci_enable_frs(struct tcpc_dev *dev, bool enable)
return ret;
}
+static void tcpci_frs_sourcing_vbus(struct tcpc_dev *dev)
+{
+ struct tcpci *tcpci = tcpc_to_tcpci(dev);
+
+ if (tcpci->data->frs_sourcing_vbus)
+ tcpci->data->frs_sourcing_vbus(tcpci, tcpci->data);
+}
+
static int tcpci_set_bist_data(struct tcpc_dev *tcpc, bool enable)
{
struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
@@ -339,6 +403,19 @@ static int tcpci_get_vbus(struct tcpc_dev *tcpc)
return !!(reg & TCPC_POWER_STATUS_VBUS_PRES);
}
+static bool tcpci_is_vbus_vsafe0v(struct tcpc_dev *tcpc)
+{
+ struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
+ unsigned int reg;
+ int ret;
+
+ ret = regmap_read(tcpci->regmap, TCPC_EXTENDED_STATUS, &reg);
+ if (ret < 0)
+ return false;
+
+ return !!(reg & TCPC_EXTENDED_STATUS_VSAFE0V);
+}
+
static int tcpci_set_vbus(struct tcpc_dev *tcpc, bool source, bool sink)
{
struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
@@ -384,9 +461,8 @@ static int tcpci_set_vbus(struct tcpc_dev *tcpc, bool source, bool sink)
return 0;
}
-static int tcpci_pd_transmit(struct tcpc_dev *tcpc,
- enum tcpm_transmit_type type,
- const struct pd_message *msg)
+static int tcpci_pd_transmit(struct tcpc_dev *tcpc, enum tcpm_transmit_type type,
+ const struct pd_message *msg, unsigned int negotiated_rev)
{
struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
u16 header = msg ? le16_to_cpu(msg->header) : 0;
@@ -434,7 +510,9 @@ static int tcpci_pd_transmit(struct tcpc_dev *tcpc,
}
}
- reg = (PD_RETRY_COUNT << TCPC_TRANSMIT_RETRY_SHIFT) | (type << TCPC_TRANSMIT_TYPE_SHIFT);
+ /* nRetryCount is 3 in PD2.0 spec where 2 in PD3.0 spec */
+ reg = ((negotiated_rev > PD_REV20 ? PD_RETRY_COUNT_3_0_OR_HIGHER : PD_RETRY_COUNT_DEFAULT)
+ << TCPC_TRANSMIT_RETRY_SHIFT) | (type << TCPC_TRANSMIT_TYPE_SHIFT);
ret = regmap_write(tcpci->regmap, TCPC_TRANSMIT, reg);
if (ret < 0)
return ret;
@@ -491,12 +569,22 @@ static int tcpci_init(struct tcpc_dev *tcpc)
TCPC_ALERT_RX_HARD_RST | TCPC_ALERT_CC_STATUS;
if (tcpci->controls_vbus)
reg |= TCPC_ALERT_POWER_STATUS;
+ /* Enable VSAFE0V status interrupt when detecting VSAFE0V is supported */
+ if (tcpci->data->vbus_vsafe0v) {
+ reg |= TCPC_ALERT_EXTENDED_STATUS;
+ ret = regmap_write(tcpci->regmap, TCPC_EXTENDED_STATUS_MASK,
+ TCPC_EXTENDED_STATUS_VSAFE0V);
+ if (ret < 0)
+ return ret;
+ }
return tcpci_write16(tcpci, TCPC_ALERT_MASK, reg);
}
irqreturn_t tcpci_irq(struct tcpci *tcpci)
{
u16 status;
+ int ret;
+ unsigned int raw;
tcpci_read16(tcpci, TCPC_ALERT, &status);
@@ -512,15 +600,12 @@ irqreturn_t tcpci_irq(struct tcpci *tcpci)
tcpm_cc_change(tcpci->port);
if (status & TCPC_ALERT_POWER_STATUS) {
- unsigned int reg;
-
- regmap_read(tcpci->regmap, TCPC_POWER_STATUS_MASK, &reg);
-
+ regmap_read(tcpci->regmap, TCPC_POWER_STATUS_MASK, &raw);
/*
* If power status mask has been reset, then the TCPC
* has reset.
*/
- if (reg == 0xff)
+ if (raw == 0xff)
tcpm_tcpc_reset(tcpci->port);
else
tcpm_vbus_change(tcpci->port);
@@ -559,6 +644,12 @@ irqreturn_t tcpci_irq(struct tcpci *tcpci)
tcpm_pd_receive(tcpci->port, &msg);
}
+ if (status & TCPC_ALERT_EXTENDED_STATUS) {
+ ret = regmap_read(tcpci->regmap, TCPC_EXTENDED_STATUS, &raw);
+ if (!ret && (raw & TCPC_EXTENDED_STATUS_VSAFE0V))
+ tcpm_vbus_change(tcpci->port);
+ }
+
if (status & TCPC_ALERT_RX_HARD_RST)
tcpm_pd_hard_reset(tcpci->port);
@@ -628,6 +719,18 @@ struct tcpci *tcpci_register_port(struct device *dev, struct tcpci_data *data)
tcpci->tcpc.pd_transmit = tcpci_pd_transmit;
tcpci->tcpc.set_bist_data = tcpci_set_bist_data;
tcpci->tcpc.enable_frs = tcpci_enable_frs;
+ tcpci->tcpc.frs_sourcing_vbus = tcpci_frs_sourcing_vbus;
+
+ if (tcpci->data->auto_discharge_disconnect) {
+ tcpci->tcpc.enable_auto_vbus_discharge = tcpci_enable_auto_vbus_discharge;
+ tcpci->tcpc.set_auto_vbus_discharge_threshold =
+ tcpci_set_auto_vbus_discharge_threshold;
+ regmap_update_bits(tcpci->regmap, TCPC_POWER_CTRL, TCPC_POWER_CTRL_BLEED_DISCHARGE,
+ TCPC_POWER_CTRL_BLEED_DISCHARGE);
+ }
+
+ if (tcpci->data->vbus_vsafe0v)
+ tcpci->tcpc.is_vbus_vsafe0v = tcpci_is_vbus_vsafe0v;
err = tcpci_parse_config(tcpci);
if (err < 0)
diff --git a/drivers/usb/typec/tcpm/tcpci.h b/drivers/usb/typec/tcpm/tcpci.h
index 5ef07a56d67a..c3c7d07d9b4e 100644
--- a/drivers/usb/typec/tcpm/tcpci.h
+++ b/drivers/usb/typec/tcpm/tcpci.h
@@ -8,6 +8,8 @@
#ifndef __LINUX_USB_TCPCI_H
#define __LINUX_USB_TCPCI_H
+#include <linux/usb/typec.h>
+
#define TCPC_VENDOR_ID 0x0
#define TCPC_PRODUCT_ID 0x2
#define TCPC_BCD_DEV 0x4
@@ -47,6 +49,9 @@
#define TCPC_TCPC_CTRL_ORIENTATION BIT(0)
#define TCPC_TCPC_CTRL_BIST_TM BIT(1)
+#define TCPC_EXTENDED_STATUS 0x20
+#define TCPC_EXTENDED_STATUS_VSAFE0V BIT(0)
+
#define TCPC_ROLE_CTRL 0x1a
#define TCPC_ROLE_CTRL_DRP BIT(6)
#define TCPC_ROLE_CTRL_RP_VAL_SHIFT 4
@@ -67,6 +72,8 @@
#define TCPC_POWER_CTRL 0x1c
#define TCPC_POWER_CTRL_VCONN_ENABLE BIT(0)
+#define TCPC_POWER_CTRL_BLEED_DISCHARGE BIT(3)
+#define TCPC_POWER_CTRL_AUTO_DISCHARGE BIT(4)
#define TCPC_FAST_ROLE_SWAP_EN BIT(7)
#define TCPC_CC_STATUS 0x1d
@@ -133,6 +140,8 @@
#define TCPC_VBUS_VOLTAGE 0x70
#define TCPC_VBUS_SINK_DISCONNECT_THRESH 0x72
+#define TCPC_VBUS_SINK_DISCONNECT_THRESH_LSB_MV 25
+#define TCPC_VBUS_SINK_DISCONNECT_THRESH_MAX 0x3ff
#define TCPC_VBUS_STOP_DISCHARGE_THRESH 0x74
#define TCPC_VBUS_VOLTAGE_ALARM_HI_CFG 0x76
#define TCPC_VBUS_VOLTAGE_ALARM_LO_CFG 0x78
@@ -140,20 +149,32 @@
/* I2C_WRITE_BYTE_COUNT + 1 when TX_BUF_BYTE_x is only accessible I2C_WRITE_BYTE_COUNT */
#define TCPC_TRANSMIT_BUFFER_MAX_LEN 31
+struct tcpci;
+
/*
- * @TX_BUF_BYTE_x_hidden
+ * @TX_BUF_BYTE_x_hidden:
* optional; Set when TX_BUF_BYTE_x can only be accessed through I2C_WRITE_BYTE_COUNT.
+ * @frs_sourcing_vbus:
+ * Optional; Callback to perform chip specific operations when FRS
+ * is sourcing vbus.
+ * @auto_discharge_disconnect:
+ * Optional; Enables TCPC to autonously discharge vbus on disconnect.
+ * @vbus_vsafe0v:
+ * optional; Set when TCPC can detect whether vbus is at VSAFE0V.
*/
-struct tcpci;
struct tcpci_data {
struct regmap *regmap;
unsigned char TX_BUF_BYTE_x_hidden:1;
+ unsigned char auto_discharge_disconnect:1;
+ unsigned char vbus_vsafe0v:1;
+
int (*init)(struct tcpci *tcpci, struct tcpci_data *data);
int (*set_vconn)(struct tcpci *tcpci, struct tcpci_data *data,
bool enable);
int (*start_drp_toggling)(struct tcpci *tcpci, struct tcpci_data *data,
enum typec_cc_status cc);
int (*set_vbus)(struct tcpci *tcpci, struct tcpci_data *data, bool source, bool sink);
+ void (*frs_sourcing_vbus)(struct tcpci *tcpci, struct tcpci_data *data);
};
struct tcpci *tcpci_register_port(struct device *dev, struct tcpci_data *data);
diff --git a/drivers/usb/typec/tcpm/tcpci_maxim.c b/drivers/usb/typec/tcpm/tcpci_maxim.c
index 723d7dd38f75..319266329b42 100644
--- a/drivers/usb/typec/tcpm/tcpci_maxim.c
+++ b/drivers/usb/typec/tcpm/tcpci_maxim.c
@@ -112,11 +112,18 @@ static void max_tcpci_init_regs(struct max_tcpci_chip *chip)
return;
}
+ /* Enable VSAFE0V detection */
+ ret = max_tcpci_write8(chip, TCPC_EXTENDED_STATUS_MASK, TCPC_EXTENDED_STATUS_VSAFE0V);
+ if (ret < 0) {
+ dev_err(chip->dev, "Unable to unmask TCPC_EXTENDED_STATUS_VSAFE0V ret:%d\n", ret);
+ return;
+ }
+
alert_mask = TCPC_ALERT_TX_SUCCESS | TCPC_ALERT_TX_DISCARDED | TCPC_ALERT_TX_FAILED |
TCPC_ALERT_RX_HARD_RST | TCPC_ALERT_RX_STATUS | TCPC_ALERT_CC_STATUS |
TCPC_ALERT_VBUS_DISCNCT | TCPC_ALERT_RX_BUF_OVF | TCPC_ALERT_POWER_STATUS |
/* Enable Extended alert for detecting Fast Role Swap Signal */
- TCPC_ALERT_EXTND;
+ TCPC_ALERT_EXTND | TCPC_ALERT_EXTENDED_STATUS;
ret = max_tcpci_write16(chip, TCPC_ALERT_MASK, alert_mask);
if (ret < 0) {
@@ -238,23 +245,22 @@ static void process_power_status(struct max_tcpci_chip *chip)
if (ret < 0)
return;
- if (pwr_status == 0xff) {
+ if (pwr_status == 0xff)
max_tcpci_init_regs(chip);
- } else if (pwr_status & TCPC_POWER_STATUS_SOURCING_VBUS) {
+ else if (pwr_status & TCPC_POWER_STATUS_SOURCING_VBUS)
tcpm_sourcing_vbus(chip->port);
- /*
- * Alawys re-enable boost here.
- * In normal case, when say an headset is attached, TCPM would
- * have instructed to TCPC to enable boost, so the call is a
- * no-op.
- * But for Fast Role Swap case, Boost turns on autonomously without
- * AP intervention, but, needs AP to enable source mode explicitly
- * for AP to regain control.
- */
- max_tcpci_set_vbus(chip->tcpci, &chip->data, true, false);
- } else {
+ else
tcpm_vbus_change(chip->port);
- }
+}
+
+static void max_tcpci_frs_sourcing_vbus(struct tcpci *tcpci, struct tcpci_data *tdata)
+{
+ /*
+ * For Fast Role Swap case, Boost turns on autonomously without
+ * AP intervention, but, needs AP to enable source mode explicitly
+ * for AP to regain control.
+ */
+ max_tcpci_set_vbus(tcpci, tdata, true, false);
}
static void process_tx(struct max_tcpci_chip *chip, u16 status)
@@ -316,6 +322,12 @@ static irqreturn_t _max_tcpci_irq(struct max_tcpci_chip *chip, u16 status)
}
}
+ if (status & TCPC_ALERT_EXTENDED_STATUS) {
+ ret = max_tcpci_read8(chip, TCPC_EXTENDED_STATUS, (u8 *)&reg_status);
+ if (ret >= 0 && (reg_status & TCPC_EXTENDED_STATUS_VSAFE0V))
+ tcpm_vbus_change(chip->port);
+ }
+
if (status & TCPC_ALERT_RX_STATUS)
process_rx(chip, status);
@@ -344,7 +356,7 @@ static irqreturn_t max_tcpci_irq(int irq, void *dev_id)
{
struct max_tcpci_chip *chip = dev_id;
u16 status;
- irqreturn_t irq_return;
+ irqreturn_t irq_return = IRQ_HANDLED;
int ret;
if (!chip->port)
@@ -441,10 +453,13 @@ static int max_tcpci_probe(struct i2c_client *client, const struct i2c_device_id
chip->data.start_drp_toggling = max_tcpci_start_toggling;
chip->data.TX_BUF_BYTE_x_hidden = true;
chip->data.init = tcpci_init;
+ chip->data.frs_sourcing_vbus = max_tcpci_frs_sourcing_vbus;
+ chip->data.auto_discharge_disconnect = true;
+ chip->data.vbus_vsafe0v = true;
max_tcpci_init_regs(chip);
chip->tcpci = tcpci_register_port(chip->dev, &chip->data);
- if (IS_ERR_OR_NULL(chip->tcpci)) {
+ if (IS_ERR(chip->tcpci)) {
dev_err(&client->dev, "TCPCI port registration failed");
ret = PTR_ERR(chip->tcpci);
return PTR_ERR(chip->tcpci);
@@ -481,7 +496,7 @@ MODULE_DEVICE_TABLE(i2c, max_tcpci_id);
#ifdef CONFIG_OF
static const struct of_device_id max_tcpci_of_match[] = {
- { .compatible = "maxim,tcpc", },
+ { .compatible = "maxim,max33359", },
{},
};
MODULE_DEVICE_TABLE(of, max_tcpci_of_match);
diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c
index a6fae1f86505..22a85b396f69 100644
--- a/drivers/usb/typec/tcpm/tcpm.c
+++ b/drivers/usb/typec/tcpm/tcpm.c
@@ -258,7 +258,19 @@ struct tcpm_port {
bool attached;
bool connected;
enum typec_port_type port_type;
+
+ /*
+ * Set to true when vbus is greater than VSAFE5V min.
+ * Set to false when vbus falls below vSinkDisconnect max threshold.
+ */
bool vbus_present;
+
+ /*
+ * Set to true when vbus is less than VSAFE0V max.
+ * Set to false when vbus is greater than VSAFE0V max.
+ */
+ bool vbus_vsafe0v;
+
bool vbus_never_low;
bool vbus_source;
bool vbus_charge;
@@ -363,8 +375,8 @@ struct tcpm_port {
/* port belongs to a self powered device */
bool self_powered;
- /* FRS */
- enum frs_typec_current frs_current;
+ /* Sink FRS */
+ enum frs_typec_current new_source_frs_current;
/* Sink caps have been queried */
bool sink_cap_done;
@@ -667,7 +679,7 @@ static int tcpm_pd_transmit(struct tcpm_port *port,
tcpm_log(port, "PD TX, type: %#x", type);
reinit_completion(&port->tx_complete);
- ret = port->tcpc->pd_transmit(port->tcpc, type, msg);
+ ret = port->tcpc->pd_transmit(port->tcpc, type, msg, port->negotiated_rev);
if (ret < 0)
return ret;
@@ -1706,6 +1718,24 @@ static void tcpm_handle_alert(struct tcpm_port *port, const __le32 *payload,
}
}
+static int tcpm_set_auto_vbus_discharge_threshold(struct tcpm_port *port,
+ enum typec_pwr_opmode mode, bool pps_active,
+ u32 requested_vbus_voltage)
+{
+ int ret;
+
+ if (!port->tcpc->set_auto_vbus_discharge_threshold)
+ return 0;
+
+ ret = port->tcpc->set_auto_vbus_discharge_threshold(port->tcpc, mode, pps_active,
+ requested_vbus_voltage);
+ tcpm_log_force(port,
+ "set_auto_vbus_discharge_threshold mode:%d pps_active:%c vbus:%u ret:%d",
+ mode, pps_active ? 'y' : 'n', requested_vbus_voltage, ret);
+
+ return ret;
+}
+
static void tcpm_pd_data_request(struct tcpm_port *port,
const struct pd_message *msg)
{
@@ -1713,7 +1743,7 @@ static void tcpm_pd_data_request(struct tcpm_port *port,
unsigned int cnt = pd_header_cnt_le(msg->header);
unsigned int rev = pd_header_rev_le(msg->header);
unsigned int i;
- enum frs_typec_current frs_current;
+ enum frs_typec_current partner_frs_current;
bool frs_enable;
int ret;
@@ -1786,12 +1816,13 @@ static void tcpm_pd_data_request(struct tcpm_port *port,
for (i = 0; i < cnt; i++)
port->sink_caps[i] = le32_to_cpu(msg->payload[i]);
- frs_current = (port->sink_caps[0] & PDO_FIXED_FRS_CURR_MASK) >>
+ partner_frs_current = (port->sink_caps[0] & PDO_FIXED_FRS_CURR_MASK) >>
PDO_FIXED_FRS_CURR_SHIFT;
- frs_enable = frs_current && (frs_current <= port->frs_current);
+ frs_enable = partner_frs_current && (partner_frs_current <=
+ port->new_source_frs_current);
tcpm_log(port,
"Port partner FRS capable partner_frs_current:%u port_frs_current:%u enable:%c",
- frs_current, port->frs_current, frs_enable ? 'y' : 'n');
+ partner_frs_current, port->new_source_frs_current, frs_enable ? 'y' : 'n');
if (frs_enable) {
ret = port->tcpc->enable_frs(port->tcpc, true);
tcpm_log(port, "Enable FRS %s, ret:%d\n", ret ? "fail" : "success", ret);
@@ -1875,6 +1906,10 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port,
port->current_limit,
port->supply_voltage);
port->explicit_contract = true;
+ tcpm_set_auto_vbus_discharge_threshold(port,
+ TYPEC_PWR_MODE_PD,
+ port->pps_data.active,
+ port->supply_voltage);
tcpm_set_state(port, SNK_READY, 0);
} else {
/*
@@ -2193,6 +2228,7 @@ static int tcpm_pd_send_control(struct tcpm_port *port,
static bool tcpm_send_queued_message(struct tcpm_port *port)
{
enum pd_msg_request queued_message;
+ int ret;
do {
queued_message = port->queued_message;
@@ -2212,7 +2248,16 @@ static bool tcpm_send_queued_message(struct tcpm_port *port)
tcpm_pd_send_sink_caps(port);
break;
case PD_MSG_DATA_SOURCE_CAP:
- tcpm_pd_send_source_caps(port);
+ ret = tcpm_pd_send_source_caps(port);
+ if (ret < 0) {
+ tcpm_log(port,
+ "Unable to send src caps, ret=%d",
+ ret);
+ tcpm_set_state(port, SOFT_RESET_SEND, 0);
+ } else if (port->pwr_role == TYPEC_SOURCE) {
+ tcpm_set_state(port, HARD_RESET_SEND,
+ PD_T_SENDER_RESPONSE);
+ }
break;
default:
break;
@@ -2789,8 +2834,12 @@ static int tcpm_src_attach(struct tcpm_port *port)
if (ret < 0)
return ret;
- ret = tcpm_set_roles(port, true, TYPEC_SOURCE,
- tcpm_data_role_for_source(port));
+ if (port->tcpc->enable_auto_vbus_discharge) {
+ ret = port->tcpc->enable_auto_vbus_discharge(port->tcpc, true);
+ tcpm_log_force(port, "enable vbus discharge ret:%d", ret);
+ }
+
+ ret = tcpm_set_roles(port, true, TYPEC_SOURCE, tcpm_data_role_for_source(port));
if (ret < 0)
return ret;
@@ -2857,6 +2906,12 @@ static void tcpm_unregister_altmodes(struct tcpm_port *port)
static void tcpm_reset_port(struct tcpm_port *port)
{
+ int ret;
+
+ if (port->tcpc->enable_auto_vbus_discharge) {
+ ret = port->tcpc->enable_auto_vbus_discharge(port->tcpc, false);
+ tcpm_log_force(port, "Disable vbus discharge ret:%d", ret);
+ }
tcpm_unregister_altmodes(port);
tcpm_typec_disconnect(port);
port->attached = false;
@@ -2921,8 +2976,13 @@ static int tcpm_snk_attach(struct tcpm_port *port)
if (ret < 0)
return ret;
- ret = tcpm_set_roles(port, true, TYPEC_SINK,
- tcpm_data_role_for_sink(port));
+ if (port->tcpc->enable_auto_vbus_discharge) {
+ tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_PWR_MODE_USB, false, VSAFE5V);
+ ret = port->tcpc->enable_auto_vbus_discharge(port->tcpc, true);
+ tcpm_log_force(port, "enable vbus discharge ret:%d", ret);
+ }
+
+ ret = tcpm_set_roles(port, true, TYPEC_SINK, tcpm_data_role_for_sink(port));
if (ret < 0)
return ret;
@@ -2997,10 +3057,9 @@ static inline enum tcpm_state unattached_state(struct tcpm_port *port)
static void tcpm_check_send_discover(struct tcpm_port *port)
{
if (port->data_role == TYPEC_HOST && port->send_discover &&
- port->pd_capable) {
+ port->pd_capable)
tcpm_send_vdm(port, USB_SID_PD, CMD_DISCOVER_IDENT, NULL, 0);
- port->send_discover = false;
- }
+ port->send_discover = false;
}
static void tcpm_swap_complete(struct tcpm_port *port, int result)
@@ -3056,7 +3115,7 @@ static void run_state_machine(struct tcpm_port *port)
else if (tcpm_port_is_audio(port))
tcpm_set_state(port, AUDIO_ACC_ATTACHED,
PD_T_CC_DEBOUNCE);
- else if (tcpm_port_is_source(port))
+ else if (tcpm_port_is_source(port) && port->vbus_vsafe0v)
tcpm_set_state(port,
tcpm_try_snk(port) ? SNK_TRY
: SRC_ATTACHED,
@@ -3086,15 +3145,13 @@ static void run_state_machine(struct tcpm_port *port)
break;
case SNK_TRY_WAIT_DEBOUNCE:
tcpm_set_state(port, SNK_TRY_WAIT_DEBOUNCE_CHECK_VBUS,
- PD_T_PD_DEBOUNCE);
+ PD_T_TRY_CC_DEBOUNCE);
break;
case SNK_TRY_WAIT_DEBOUNCE_CHECK_VBUS:
- if (port->vbus_present && tcpm_port_is_sink(port)) {
+ if (port->vbus_present && tcpm_port_is_sink(port))
tcpm_set_state(port, SNK_ATTACHED, 0);
- } else {
- tcpm_set_state(port, SRC_TRYWAIT, 0);
+ else
port->max_wait = 0;
- }
break;
case SRC_TRYWAIT:
tcpm_set_cc(port, tcpm_rp_cc(port));
@@ -3506,6 +3563,8 @@ 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:
+ /* Do not discharge/disconnect during hard reseet */
+ tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_PWR_MODE_USB, false, 0);
memset(&port->pps_data, 0, sizeof(port->pps_data));
tcpm_set_vconn(port, false);
if (port->pd_capable)
@@ -3548,6 +3607,7 @@ static void run_state_machine(struct tcpm_port *port)
tcpm_set_charge(port, true);
}
tcpm_set_attached_state(port, true);
+ tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_PWR_MODE_USB, false, VSAFE5V);
tcpm_set_state(port, SNK_STARTUP, 0);
break;
@@ -3649,6 +3709,10 @@ static void run_state_machine(struct tcpm_port *port)
tcpm_set_state(port, PR_SWAP_SNK_SRC_SINK_OFF, 0);
break;
case PR_SWAP_SRC_SNK_TRANSITION_OFF:
+ /*
+ * Prevent vbus discharge circuit from turning on during PR_SWAP
+ * as this is not a disconnect.
+ */
tcpm_set_vbus(port, false);
port->explicit_contract = false;
/* allow time for Vbus discharge, must be < tSrcSwapStdby */
@@ -3674,12 +3738,20 @@ static void run_state_machine(struct tcpm_port *port)
tcpm_set_state(port, ERROR_RECOVERY, 0);
break;
}
- tcpm_set_state_cond(port, SNK_UNATTACHED, PD_T_PS_SOURCE_ON);
+ tcpm_set_state(port, ERROR_RECOVERY, PD_T_PS_SOURCE_ON_PRS);
break;
case PR_SWAP_SRC_SNK_SINK_ON:
+ /* Set the vbus disconnect threshold for implicit contract */
+ tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_PWR_MODE_USB, false, VSAFE5V);
tcpm_set_state(port, SNK_STARTUP, 0);
break;
case PR_SWAP_SNK_SRC_SINK_OFF:
+ /*
+ * Prevent vbus discharge circuit from turning on during PR_SWAP
+ * as this is not a disconnect.
+ */
+ tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_PWR_MODE_USB,
+ port->pps_data.active, 0);
tcpm_set_charge(port, false);
tcpm_set_state(port, hard_reset_state(port),
PD_T_PS_SOURCE_OFF);
@@ -4000,6 +4072,12 @@ static void _tcpm_cc_change(struct tcpm_port *port, enum typec_cc_status cc1,
if (!tcpm_port_is_sink(port))
tcpm_set_state(port, SNK_TRYWAIT_DEBOUNCE, 0);
break;
+ case SNK_TRY_WAIT_DEBOUNCE_CHECK_VBUS:
+ if (!tcpm_port_is_sink(port))
+ tcpm_set_state(port, SRC_TRYWAIT, PD_T_TRY_CC_DEBOUNCE);
+ else
+ tcpm_set_state(port, SNK_TRY_WAIT_DEBOUNCE_CHECK_VBUS, 0);
+ break;
case SNK_TRYWAIT:
/* Do nothing, waiting for tCCDebounce */
break;
@@ -4040,6 +4118,12 @@ static void _tcpm_pd_vbus_on(struct tcpm_port *port)
{
tcpm_log_force(port, "VBUS on");
port->vbus_present = true;
+ /*
+ * When vbus_present is true i.e. Voltage at VBUS is greater than VSAFE5V implicitly
+ * states that vbus is not at VSAFE0V, hence clear the vbus_vsafe0v flag here.
+ */
+ port->vbus_vsafe0v = false;
+
switch (port->state) {
case SNK_TRANSITION_SINK_VBUS:
port->explicit_contract = true;
@@ -4086,11 +4170,24 @@ static void _tcpm_pd_vbus_on(struct tcpm_port *port)
case SNK_TRYWAIT_DEBOUNCE:
/* Do nothing, waiting for Rp */
break;
+ case SNK_TRY_WAIT_DEBOUNCE_CHECK_VBUS:
+ if (port->vbus_present && tcpm_port_is_sink(port))
+ tcpm_set_state(port, SNK_ATTACHED, 0);
+ break;
case SRC_TRY_WAIT:
case SRC_TRY_DEBOUNCE:
/* Do nothing, waiting for sink detection */
break;
+ case FR_SWAP_SEND:
+ case FR_SWAP_SEND_TIMEOUT:
+ case FR_SWAP_SNK_SRC_TRANSITION_TO_OFF:
+ case FR_SWAP_SNK_SRC_SOURCE_VBUS_APPLIED:
+ if (port->tcpc->frs_sourcing_vbus)
+ port->tcpc->frs_sourcing_vbus(port->tcpc);
+ break;
case FR_SWAP_SNK_SRC_NEW_SINK_READY:
+ if (port->tcpc->frs_sourcing_vbus)
+ port->tcpc->frs_sourcing_vbus(port->tcpc);
tcpm_set_state(port, FR_SWAP_SNK_SRC_SOURCE_VBUS_APPLIED, 0);
break;
@@ -4116,16 +4213,8 @@ static void _tcpm_pd_vbus_off(struct tcpm_port *port)
case SNK_HARD_RESET_SINK_OFF:
tcpm_set_state(port, SNK_HARD_RESET_WAIT_VBUS, 0);
break;
- case SRC_HARD_RESET_VBUS_OFF:
- /*
- * After establishing the vSafe0V voltage condition on VBUS, the Source Shall wait
- * tSrcRecover before re-applying VCONN and restoring VBUS to vSafe5V.
- */
- tcpm_set_state(port, SRC_HARD_RESET_VBUS_ON, PD_T_SRC_RECOVER);
- break;
case HARD_RESET_SEND:
break;
-
case SNK_TRY:
/* Do nothing, waiting for timeout */
break;
@@ -4156,6 +4245,14 @@ static void _tcpm_pd_vbus_off(struct tcpm_port *port)
/* Do nothing, expected */
break;
+ case PR_SWAP_SNK_SRC_SOURCE_ON:
+ /*
+ * Do nothing when vbus off notification is received.
+ * TCPM can wait for PD_T_NEWSRC in PR_SWAP_SNK_SRC_SOURCE_ON
+ * for the vbus source to ramp up.
+ */
+ break;
+
case PORT_RESET_WAIT_OFF:
tcpm_set_state(port, tcpm_default_state(port), 0);
break;
@@ -4188,6 +4285,28 @@ static void _tcpm_pd_vbus_off(struct tcpm_port *port)
}
}
+static void _tcpm_pd_vbus_vsafe0v(struct tcpm_port *port)
+{
+ tcpm_log_force(port, "VBUS VSAFE0V");
+ port->vbus_vsafe0v = true;
+ switch (port->state) {
+ case SRC_HARD_RESET_VBUS_OFF:
+ /*
+ * After establishing the vSafe0V voltage condition on VBUS, the Source Shall wait
+ * tSrcRecover before re-applying VCONN and restoring VBUS to vSafe5V.
+ */
+ tcpm_set_state(port, SRC_HARD_RESET_VBUS_ON, PD_T_SRC_RECOVER);
+ break;
+ case SRC_ATTACH_WAIT:
+ if (tcpm_port_is_source(port))
+ tcpm_set_state(port, tcpm_try_snk(port) ? SNK_TRY : SRC_ATTACHED,
+ PD_T_CC_DEBOUNCE);
+ break;
+ default:
+ break;
+ }
+}
+
static void _tcpm_pd_hard_reset(struct tcpm_port *port)
{
tcpm_log_force(port, "Received hard reset");
@@ -4223,10 +4342,19 @@ static void tcpm_pd_event_handler(struct kthread_work *work)
bool vbus;
vbus = port->tcpc->get_vbus(port->tcpc);
- if (vbus)
+ if (vbus) {
_tcpm_pd_vbus_on(port);
- else
+ } else {
_tcpm_pd_vbus_off(port);
+ /*
+ * When TCPC does not support detecting vsafe0v voltage level,
+ * treat vbus absent as vsafe0v. Else invoke is_vbus_vsafe0v
+ * to see if vbus has discharge to VSAFE0V.
+ */
+ if (!port->tcpc->is_vbus_vsafe0v ||
+ port->tcpc->is_vbus_vsafe0v(port->tcpc))
+ _tcpm_pd_vbus_vsafe0v(port);
+ }
}
if (events & TCPM_CC_EVENT) {
enum typec_cc_status cc1, cc2;
@@ -4676,6 +4804,24 @@ static void tcpm_init(struct tcpm_port *port)
if (port->vbus_present)
port->vbus_never_low = true;
+ /*
+ * 1. When vbus_present is true, voltage on VBUS is already at VSAFE5V.
+ * So implicitly vbus_vsafe0v = false.
+ *
+ * 2. When vbus_present is false and TCPC does NOT support querying
+ * vsafe0v status, then, it's best to assume vbus is at VSAFE0V i.e.
+ * vbus_vsafe0v is true.
+ *
+ * 3. When vbus_present is false and TCPC does support querying vsafe0v,
+ * then, query tcpc for vsafe0v status.
+ */
+ if (port->vbus_present)
+ port->vbus_vsafe0v = false;
+ else if (!port->tcpc->is_vbus_vsafe0v)
+ port->vbus_vsafe0v = true;
+ else
+ port->vbus_vsafe0v = port->tcpc->is_vbus_vsafe0v(port->tcpc);
+
tcpm_set_state(port, tcpm_default_state(port), 0);
if (port->tcpc->get_cc(port->tcpc, &cc1, &cc2) == 0)
@@ -4808,9 +4954,10 @@ sink:
/* FRS can only be supported byb DRP ports */
if (port->port_type == TYPEC_PORT_DRP) {
- ret = fwnode_property_read_u32(fwnode, "frs-typec-current", &frs_current);
+ ret = fwnode_property_read_u32(fwnode, "new-source-frs-typec-current",
+ &frs_current);
if (ret >= 0 && frs_current <= FRS_5V_3A)
- port->frs_current = frs_current;
+ port->new_source_frs_current = frs_current;
}
return 0;
@@ -5024,14 +5171,14 @@ static int devm_tcpm_psy_register(struct tcpm_port *port)
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.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->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;
diff --git a/drivers/usb/typec/tcpm/wcove.c b/drivers/usb/typec/tcpm/wcove.c
index 9b745f432c91..79ae63950050 100644
--- a/drivers/usb/typec/tcpm/wcove.c
+++ b/drivers/usb/typec/tcpm/wcove.c
@@ -356,7 +356,8 @@ static int wcove_set_pd_rx(struct tcpc_dev *tcpc, bool on)
static int wcove_pd_transmit(struct tcpc_dev *tcpc,
enum tcpm_transmit_type type,
- const struct pd_message *msg)
+ const struct pd_message *msg,
+ unsigned int negotiated_rev)
{
struct wcove_typec *wcove = tcpc_to_wcove(tcpc);
unsigned int info = 0;