aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb/typec
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/typec')
-rw-r--r--drivers/usb/typec/fusb302/fusb302.c39
-rw-r--r--drivers/usb/typec/tcpm.c293
-rw-r--r--drivers/usb/typec/typec_wcove.c4
3 files changed, 268 insertions, 68 deletions
diff --git a/drivers/usb/typec/fusb302/fusb302.c b/drivers/usb/typec/fusb302/fusb302.c
index 72cb060b3fca..9ce4756adad6 100644
--- a/drivers/usb/typec/fusb302/fusb302.c
+++ b/drivers/usb/typec/fusb302/fusb302.c
@@ -16,7 +16,6 @@
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of_device.h>
-#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/pinctrl/consumer.h>
#include <linux/power_supply.h>
@@ -1543,6 +1542,21 @@ static int fusb302_pd_read_message(struct fusb302_chip *chip,
fusb302_log(chip, "PD message header: %x", msg->header);
fusb302_log(chip, "PD message len: %d", len);
+ /*
+ * Check if we've read off a GoodCRC message. If so then indicate to
+ * TCPM that the previous transmission has completed. Otherwise we pass
+ * the received message over to TCPM for processing.
+ *
+ * We make this check here instead of basing the reporting decision on
+ * the IRQ event type, as it's possible for the chip to report the
+ * TX_SUCCESS and GCRCSENT events out of order on occasion, so we need
+ * to check the message type to ensure correct reporting to TCPM.
+ */
+ if ((!len) && (pd_header_type_le(msg->header) == PD_CTRL_GOOD_CRC))
+ tcpm_pd_transmit_complete(chip->tcpm_port, TCPC_TX_SUCCESS);
+ else
+ tcpm_pd_receive(chip->tcpm_port, msg);
+
return ret;
}
@@ -1650,13 +1664,12 @@ static irqreturn_t fusb302_irq_intn(int irq, void *dev_id)
if (interrupta & FUSB_REG_INTERRUPTA_TX_SUCCESS) {
fusb302_log(chip, "IRQ: PD tx success");
- /* read out the received good CRC */
ret = fusb302_pd_read_message(chip, &pd_msg);
if (ret < 0) {
- fusb302_log(chip, "cannot read in GCRC, ret=%d", ret);
+ fusb302_log(chip,
+ "cannot read in PD message, ret=%d", ret);
goto done;
}
- tcpm_pd_transmit_complete(chip->tcpm_port, TCPC_TX_SUCCESS);
}
if (interrupta & FUSB_REG_INTERRUPTA_HARDRESET) {
@@ -1677,7 +1690,6 @@ static irqreturn_t fusb302_irq_intn(int irq, void *dev_id)
"cannot read in PD message, ret=%d", ret);
goto done;
}
- tcpm_pd_receive(chip->tcpm_port, &pd_msg);
}
done:
mutex_unlock(&chip->lock);
@@ -1731,24 +1743,24 @@ static int init_gpio(struct fusb302_chip *chip)
chip->gpio_int_n = of_get_named_gpio(node, "fcs,int_n", 0);
if (!gpio_is_valid(chip->gpio_int_n)) {
ret = chip->gpio_int_n;
- fusb302_log(chip, "cannot get named GPIO Int_N, ret=%d", ret);
+ dev_err(chip->dev, "cannot get named GPIO Int_N, ret=%d", ret);
return ret;
}
ret = devm_gpio_request(chip->dev, chip->gpio_int_n, "fcs,int_n");
if (ret < 0) {
- fusb302_log(chip, "cannot request GPIO Int_N, ret=%d", ret);
+ dev_err(chip->dev, "cannot request GPIO Int_N, ret=%d", ret);
return ret;
}
ret = gpio_direction_input(chip->gpio_int_n);
if (ret < 0) {
- fusb302_log(chip,
- "cannot set GPIO Int_N to input, ret=%d", ret);
+ dev_err(chip->dev,
+ "cannot set GPIO Int_N to input, ret=%d", ret);
return ret;
}
ret = gpio_to_irq(chip->gpio_int_n);
if (ret < 0) {
- fusb302_log(chip,
- "cannot request IRQ for GPIO Int_N, ret=%d", ret);
+ dev_err(chip->dev,
+ "cannot request IRQ for GPIO Int_N, ret=%d", ret);
return ret;
}
chip->gpio_int_n_irq = ret;
@@ -1845,7 +1857,7 @@ static int fusb302_probe(struct i2c_client *client,
chip->tcpm_port = tcpm_register_port(&client->dev, &chip->tcpc_dev);
if (IS_ERR(chip->tcpm_port)) {
ret = PTR_ERR(chip->tcpm_port);
- fusb302_log(chip, "cannot register tcpm port, ret=%d", ret);
+ dev_err(dev, "cannot register tcpm port, ret=%d", ret);
goto destroy_workqueue;
}
@@ -1854,8 +1866,7 @@ static int fusb302_probe(struct i2c_client *client,
IRQF_ONESHOT | IRQF_TRIGGER_LOW,
"fsc_interrupt_int_n", chip);
if (ret < 0) {
- fusb302_log(chip,
- "cannot request IRQ for GPIO Int_N, ret=%d", ret);
+ dev_err(dev, "cannot request IRQ for GPIO Int_N, ret=%d", ret);
goto tcpm_unregister_port;
}
enable_irq_wake(chip->gpio_int_n_irq);
diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c
index c166fc77dfb8..f4d563ee7690 100644
--- a/drivers/usb/typec/tcpm.c
+++ b/drivers/usb/typec/tcpm.c
@@ -252,6 +252,9 @@ struct tcpm_port {
unsigned int nr_src_pdo;
u32 snk_pdo[PDO_MAX_OBJECTS];
unsigned int nr_snk_pdo;
+ unsigned int nr_fixed; /* number of fixed sink PDOs */
+ unsigned int nr_var; /* number of variable sink PDOs */
+ unsigned int nr_batt; /* number of battery sink PDOs */
u32 snk_vdo[VDO_MAX_OBJECTS];
unsigned int nr_snk_vdo;
@@ -1247,6 +1250,100 @@ static void vdm_state_machine_work(struct work_struct *work)
mutex_unlock(&port->lock);
}
+enum pdo_err {
+ PDO_NO_ERR,
+ PDO_ERR_NO_VSAFE5V,
+ PDO_ERR_VSAFE5V_NOT_FIRST,
+ PDO_ERR_PDO_TYPE_NOT_IN_ORDER,
+ PDO_ERR_FIXED_NOT_SORTED,
+ PDO_ERR_VARIABLE_BATT_NOT_SORTED,
+ PDO_ERR_DUPE_PDO,
+};
+
+static const char * const pdo_err_msg[] = {
+ [PDO_ERR_NO_VSAFE5V] =
+ " err: source/sink caps should atleast have vSafe5V",
+ [PDO_ERR_VSAFE5V_NOT_FIRST] =
+ " err: vSafe5V Fixed Supply Object Shall always be the first object",
+ [PDO_ERR_PDO_TYPE_NOT_IN_ORDER] =
+ " err: PDOs should be in the following order: Fixed; Battery; Variable",
+ [PDO_ERR_FIXED_NOT_SORTED] =
+ " err: Fixed supply pdos should be in increasing order of their fixed voltage",
+ [PDO_ERR_VARIABLE_BATT_NOT_SORTED] =
+ " 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",
+};
+
+static enum pdo_err tcpm_caps_err(struct tcpm_port *port, const u32 *pdo,
+ unsigned int nr_pdo)
+{
+ unsigned int i;
+
+ /* Should at least contain vSafe5v */
+ if (nr_pdo < 1)
+ return PDO_ERR_NO_VSAFE5V;
+
+ /* The vSafe5V Fixed Supply Object Shall always be the first object */
+ if (pdo_type(pdo[0]) != PDO_TYPE_FIXED ||
+ pdo_fixed_voltage(pdo[0]) != VSAFE5V)
+ return PDO_ERR_VSAFE5V_NOT_FIRST;
+
+ for (i = 1; i < nr_pdo; i++) {
+ if (pdo_type(pdo[i]) < pdo_type(pdo[i - 1])) {
+ return PDO_ERR_PDO_TYPE_NOT_IN_ORDER;
+ } else if (pdo_type(pdo[i]) == pdo_type(pdo[i - 1])) {
+ enum pd_pdo_type type = pdo_type(pdo[i]);
+
+ switch (type) {
+ /*
+ * The remaining Fixed Supply Objects, if
+ * present, shall be sent in voltage order;
+ * lowest to highest.
+ */
+ case PDO_TYPE_FIXED:
+ if (pdo_fixed_voltage(pdo[i]) <=
+ pdo_fixed_voltage(pdo[i - 1]))
+ return PDO_ERR_FIXED_NOT_SORTED;
+ break;
+ /*
+ * The Battery Supply Objects and Variable
+ * supply, if present shall be sent in Minimum
+ * Voltage order; lowest to highest.
+ */
+ case PDO_TYPE_VAR:
+ case PDO_TYPE_BATT:
+ if (pdo_min_voltage(pdo[i]) <
+ pdo_min_voltage(pdo[i - 1]))
+ return PDO_ERR_VARIABLE_BATT_NOT_SORTED;
+ else if ((pdo_min_voltage(pdo[i]) ==
+ pdo_min_voltage(pdo[i - 1])) &&
+ (pdo_max_voltage(pdo[i]) ==
+ pdo_min_voltage(pdo[i - 1])))
+ return PDO_ERR_DUPE_PDO;
+ break;
+ default:
+ tcpm_log_force(port, " Unknown pdo type");
+ }
+ }
+ }
+
+ return PDO_NO_ERR;
+}
+
+static int tcpm_validate_caps(struct tcpm_port *port, const u32 *pdo,
+ unsigned int nr_pdo)
+{
+ enum pdo_err err_index = tcpm_caps_err(port, pdo, nr_pdo);
+
+ if (err_index != PDO_NO_ERR) {
+ tcpm_log_force(port, " %s", pdo_err_msg[err_index]);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
/*
* PD (data, control) command handling functions
*/
@@ -1269,6 +1366,9 @@ static void tcpm_pd_data_request(struct tcpm_port *port,
tcpm_log_source_caps(port);
+ tcpm_validate_caps(port, port->source_caps,
+ port->nr_source_caps);
+
/*
* This message may be received even if VBUS is not
* present. This is quite unexpected; see USB PD
@@ -1670,39 +1770,90 @@ static int tcpm_pd_check_request(struct tcpm_port *port)
return 0;
}
-static int tcpm_pd_select_pdo(struct tcpm_port *port)
+#define min_power(x, y) min(pdo_max_power(x), pdo_max_power(y))
+#define min_current(x, y) min(pdo_max_current(x), pdo_max_current(y))
+
+static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo,
+ int *src_pdo)
{
- unsigned int i, max_mw = 0, max_mv = 0;
+ unsigned int i, j, max_mw = 0, max_mv = 0, mw = 0, mv = 0, ma = 0;
int ret = -EINVAL;
/*
- * Select the source PDO providing the most power while staying within
- * the board's voltage limits. Prefer PDO providing exp
+ * Select the source PDO providing the most power which has a
+ * matchig sink cap.
*/
for (i = 0; i < port->nr_source_caps; i++) {
u32 pdo = port->source_caps[i];
enum pd_pdo_type type = pdo_type(pdo);
- unsigned int mv, ma, mw;
-
- if (type == PDO_TYPE_FIXED)
- mv = pdo_fixed_voltage(pdo);
- else
- mv = pdo_min_voltage(pdo);
-
- if (type == PDO_TYPE_BATT) {
- mw = pdo_max_power(pdo);
- } else {
- ma = min(pdo_max_current(pdo),
- port->max_snk_ma);
- mw = ma * mv / 1000;
- }
- /* Perfer higher voltages if available */
- if ((mw > max_mw || (mw == max_mw && mv > max_mv)) &&
- mv <= port->max_snk_mv) {
- ret = i;
- max_mw = mw;
- max_mv = mv;
+ if (type == PDO_TYPE_FIXED) {
+ for (j = 0; j < port->nr_fixed; j++) {
+ if (pdo_fixed_voltage(pdo) ==
+ pdo_fixed_voltage(port->snk_pdo[j])) {
+ ma = min_current(pdo, port->snk_pdo[j]);
+ mv = pdo_fixed_voltage(pdo);
+ mw = ma * mv / 1000;
+ if (mw > max_mw ||
+ (mw == max_mw && mv > max_mv)) {
+ ret = 0;
+ *src_pdo = i;
+ *sink_pdo = j;
+ max_mw = mw;
+ max_mv = mv;
+ }
+ /* There could only be one fixed pdo
+ * at a specific voltage level.
+ * So breaking here.
+ */
+ break;
+ }
+ }
+ } else if (type == PDO_TYPE_BATT) {
+ for (j = port->nr_fixed;
+ j < port->nr_fixed +
+ port->nr_batt;
+ j++) {
+ if (pdo_min_voltage(pdo) >=
+ pdo_min_voltage(port->snk_pdo[j]) &&
+ pdo_max_voltage(pdo) <=
+ pdo_max_voltage(port->snk_pdo[j])) {
+ mw = min_power(pdo, port->snk_pdo[j]);
+ mv = pdo_min_voltage(pdo);
+ if (mw > max_mw ||
+ (mw == max_mw && mv > max_mv)) {
+ ret = 0;
+ *src_pdo = i;
+ *sink_pdo = j;
+ max_mw = mw;
+ max_mv = mv;
+ }
+ }
+ }
+ } else if (type == PDO_TYPE_VAR) {
+ for (j = port->nr_fixed +
+ port->nr_batt;
+ j < port->nr_fixed +
+ port->nr_batt +
+ port->nr_var;
+ j++) {
+ if (pdo_min_voltage(pdo) >=
+ pdo_min_voltage(port->snk_pdo[j]) &&
+ pdo_max_voltage(pdo) <=
+ pdo_max_voltage(port->snk_pdo[j])) {
+ ma = min_current(pdo, port->snk_pdo[j]);
+ mv = pdo_min_voltage(pdo);
+ mw = ma * mv / 1000;
+ if (mw > max_mw ||
+ (mw == max_mw && mv > max_mv)) {
+ ret = 0;
+ *src_pdo = i;
+ *sink_pdo = j;
+ max_mw = mw;
+ max_mv = mv;
+ }
+ }
+ }
}
}
@@ -1714,13 +1865,14 @@ static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo)
unsigned int mv, ma, mw, flags;
unsigned int max_ma, max_mw;
enum pd_pdo_type type;
- int index;
- u32 pdo;
+ int src_pdo_index, snk_pdo_index;
+ u32 pdo, matching_snk_pdo;
- index = tcpm_pd_select_pdo(port);
- if (index < 0)
+ if (tcpm_pd_select_pdo(port, &snk_pdo_index, &src_pdo_index) < 0)
return -EINVAL;
- pdo = port->source_caps[index];
+
+ pdo = port->source_caps[src_pdo_index];
+ matching_snk_pdo = port->snk_pdo[snk_pdo_index];
type = pdo_type(pdo);
if (type == PDO_TYPE_FIXED)
@@ -1728,26 +1880,28 @@ static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo)
else
mv = pdo_min_voltage(pdo);
- /* Select maximum available current within the board's power limit */
+ /* Select maximum available current within the sink pdo's limit */
if (type == PDO_TYPE_BATT) {
- mw = pdo_max_power(pdo);
- ma = 1000 * min(mw, port->max_snk_mw) / mv;
+ mw = min_power(pdo, matching_snk_pdo);
+ ma = 1000 * mw / mv;
} else {
- ma = min(pdo_max_current(pdo),
- 1000 * port->max_snk_mw / mv);
+ ma = min_current(pdo, matching_snk_pdo);
+ mw = ma * mv / 1000;
}
- ma = min(ma, port->max_snk_ma);
flags = RDO_USB_COMM | RDO_NO_SUSPEND;
/* Set mismatch bit if offered power is less than operating power */
- mw = ma * mv / 1000;
max_ma = ma;
max_mw = mw;
if (mw < port->operating_snk_mw) {
flags |= RDO_CAP_MISMATCH;
- max_mw = port->operating_snk_mw;
- max_ma = max_mw * 1000 / mv;
+ if (type == PDO_TYPE_BATT &&
+ (pdo_max_power(matching_snk_pdo) > pdo_max_power(pdo)))
+ max_mw = pdo_max_power(matching_snk_pdo);
+ else if (pdo_max_current(matching_snk_pdo) >
+ pdo_max_current(pdo))
+ max_ma = pdo_max_current(matching_snk_pdo);
}
tcpm_log(port, "cc=%d cc1=%d cc2=%d vbus=%d vconn=%s polarity=%d",
@@ -1756,16 +1910,16 @@ static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo)
port->polarity);
if (type == PDO_TYPE_BATT) {
- *rdo = RDO_BATT(index + 1, mw, max_mw, flags);
+ *rdo = RDO_BATT(src_pdo_index + 1, mw, max_mw, flags);
tcpm_log(port, "Requesting PDO %d: %u mV, %u mW%s",
- index, mv, mw,
+ src_pdo_index, mv, mw,
flags & RDO_CAP_MISMATCH ? " [mismatch]" : "");
} else {
- *rdo = RDO_FIXED(index + 1, ma, max_ma, flags);
+ *rdo = RDO_FIXED(src_pdo_index + 1, ma, max_ma, flags);
tcpm_log(port, "Requesting PDO %d: %u mV, %u mA%s",
- index, mv, ma,
+ src_pdo_index, mv, ma,
flags & RDO_CAP_MISMATCH ? " [mismatch]" : "");
}
@@ -3435,9 +3589,12 @@ static int tcpm_copy_vdos(u32 *dest_vdo, const u32 *src_vdo,
return nr_vdo;
}
-void tcpm_update_source_capabilities(struct tcpm_port *port, const u32 *pdo,
- unsigned int nr_pdo)
+int tcpm_update_source_capabilities(struct tcpm_port *port, const u32 *pdo,
+ unsigned int nr_pdo)
{
+ if (tcpm_validate_caps(port, pdo, nr_pdo))
+ return -EINVAL;
+
mutex_lock(&port->lock);
port->nr_src_pdo = tcpm_copy_pdos(port->src_pdo, pdo, nr_pdo);
switch (port->state) {
@@ -3457,16 +3614,20 @@ void tcpm_update_source_capabilities(struct tcpm_port *port, const u32 *pdo,
break;
}
mutex_unlock(&port->lock);
+ return 0;
}
EXPORT_SYMBOL_GPL(tcpm_update_source_capabilities);
-void tcpm_update_sink_capabilities(struct tcpm_port *port, const u32 *pdo,
- unsigned int nr_pdo,
- unsigned int max_snk_mv,
- unsigned int max_snk_ma,
- unsigned int max_snk_mw,
- unsigned int operating_snk_mw)
+int tcpm_update_sink_capabilities(struct tcpm_port *port, const u32 *pdo,
+ unsigned int nr_pdo,
+ unsigned int max_snk_mv,
+ unsigned int max_snk_ma,
+ unsigned int max_snk_mw,
+ unsigned int operating_snk_mw)
{
+ if (tcpm_validate_caps(port, pdo, nr_pdo))
+ return -EINVAL;
+
mutex_lock(&port->lock);
port->nr_snk_pdo = tcpm_copy_pdos(port->snk_pdo, pdo, nr_pdo);
port->max_snk_mv = max_snk_mv;
@@ -3485,9 +3646,23 @@ void tcpm_update_sink_capabilities(struct tcpm_port *port, const u32 *pdo,
break;
}
mutex_unlock(&port->lock);
+ return 0;
}
EXPORT_SYMBOL_GPL(tcpm_update_sink_capabilities);
+static int nr_type_pdos(const u32 *pdo, unsigned int nr_pdo,
+ enum pd_pdo_type type)
+{
+ int count = 0;
+ int i;
+
+ for (i = 0; i < nr_pdo; i++) {
+ if (pdo_type(pdo[i]) == type)
+ count++;
+ }
+ return count;
+}
+
struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
{
struct tcpm_port *port;
@@ -3520,11 +3695,28 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
init_completion(&port->tx_complete);
init_completion(&port->swap_complete);
+ tcpm_debugfs_init(port);
+ if (tcpm_validate_caps(port, tcpc->config->src_pdo,
+ tcpc->config->nr_src_pdo) ||
+ tcpm_validate_caps(port, tcpc->config->snk_pdo,
+ tcpc->config->nr_snk_pdo)) {
+ err = -EINVAL;
+ goto out_destroy_wq;
+ }
port->nr_src_pdo = tcpm_copy_pdos(port->src_pdo, tcpc->config->src_pdo,
tcpc->config->nr_src_pdo);
port->nr_snk_pdo = tcpm_copy_pdos(port->snk_pdo, tcpc->config->snk_pdo,
tcpc->config->nr_snk_pdo);
+ port->nr_fixed = nr_type_pdos(port->snk_pdo,
+ port->nr_snk_pdo,
+ PDO_TYPE_FIXED);
+ port->nr_var = nr_type_pdos(port->snk_pdo,
+ port->nr_snk_pdo,
+ PDO_TYPE_VAR);
+ port->nr_batt = nr_type_pdos(port->snk_pdo,
+ port->nr_snk_pdo,
+ PDO_TYPE_BATT);
port->nr_snk_vdo = tcpm_copy_vdos(port->snk_vdo, tcpc->config->snk_vdo,
tcpc->config->nr_snk_vdo);
@@ -3575,7 +3767,6 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
}
}
- tcpm_debugfs_init(port);
mutex_lock(&port->lock);
tcpm_init(port);
mutex_unlock(&port->lock);
diff --git a/drivers/usb/typec/typec_wcove.c b/drivers/usb/typec/typec_wcove.c
index a8d479eb221a..2e990e0d917d 100644
--- a/drivers/usb/typec/typec_wcove.c
+++ b/drivers/usb/typec/typec_wcove.c
@@ -556,10 +556,8 @@ static const u32 src_pdo[] = {
};
static const u32 snk_pdo[] = {
- PDO_FIXED(12000, 3000, PDO_FIXED_DUAL_ROLE | PDO_FIXED_DATA_SWAP |
+ PDO_FIXED(5000, 500, PDO_FIXED_DUAL_ROLE | PDO_FIXED_DATA_SWAP |
PDO_FIXED_USB_COMM),
- PDO_BATT(4750, 12000, 15000),
- PDO_VAR(4750, 12000, 3000),
};
static struct tcpc_config wcove_typec_config = {