aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb/typec/tcpm/tcpm.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/typec/tcpm/tcpm.c')
-rw-r--r--drivers/usb/typec/tcpm/tcpm.c430
1 files changed, 332 insertions, 98 deletions
diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c
index bfb6f9481e87..096597231027 100644
--- a/drivers/usb/typec/tcpm/tcpm.c
+++ b/drivers/usb/typec/tcpm/tcpm.c
@@ -251,6 +251,7 @@ enum frs_typec_current {
#define TCPM_FRS_EVENT BIT(3)
#define TCPM_SOURCING_VBUS BIT(4)
#define TCPM_PORT_CLEAN BIT(5)
+#define TCPM_PORT_ERROR BIT(6)
#define LOG_BUFFER_ENTRIES 1024
#define LOG_BUFFER_ENTRY_SIZE 128
@@ -296,6 +297,15 @@ struct pd_pps_data {
bool active;
};
+struct pd_data {
+ struct usb_power_delivery *pd;
+ struct usb_power_delivery_capabilities *source_cap;
+ struct usb_power_delivery_capabilities_desc source_desc;
+ struct usb_power_delivery_capabilities *sink_cap;
+ struct usb_power_delivery_capabilities_desc sink_desc;
+ unsigned int operating_snk_mw;
+};
+
struct tcpm_port {
struct device *dev;
@@ -397,12 +407,14 @@ struct tcpm_port {
unsigned int rx_msgid;
/* USB PD objects */
- struct usb_power_delivery *pd;
+ struct usb_power_delivery **pds;
+ struct pd_data **pd_list;
struct usb_power_delivery_capabilities *port_source_caps;
struct usb_power_delivery_capabilities *port_sink_caps;
struct usb_power_delivery *partner_pd;
struct usb_power_delivery_capabilities *partner_source_caps;
struct usb_power_delivery_capabilities *partner_sink_caps;
+ struct usb_power_delivery *selected_pd;
/* Partner capabilities/requests */
u32 sink_request;
@@ -412,6 +424,7 @@ struct tcpm_port {
unsigned int nr_sink_caps;
/* Local capabilities */
+ unsigned int pd_count;
u32 src_pdo[PDO_MAX_OBJECTS];
unsigned int nr_src_pdo;
u32 snk_pdo[PDO_MAX_OBJECTS];
@@ -2847,7 +2860,7 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port,
PD_MSG_CTRL_NOT_SUPP,
NONE_AMS);
} else {
- if (port->send_discover) {
+ if (port->send_discover && port->negotiated_rev < PD_REV30) {
tcpm_queue_message(port, PD_MSG_CTRL_WAIT);
break;
}
@@ -2863,7 +2876,7 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port,
PD_MSG_CTRL_NOT_SUPP,
NONE_AMS);
} else {
- if (port->send_discover) {
+ if (port->send_discover && port->negotiated_rev < PD_REV30) {
tcpm_queue_message(port, PD_MSG_CTRL_WAIT);
break;
}
@@ -2872,7 +2885,7 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port,
}
break;
case PD_CTRL_VCONN_SWAP:
- if (port->send_discover) {
+ if (port->send_discover && port->negotiated_rev < PD_REV30) {
tcpm_queue_message(port, PD_MSG_CTRL_WAIT);
break;
}
@@ -3730,9 +3743,6 @@ static void tcpm_detach(struct tcpm_port *port)
if (tcpm_port_is_disconnected(port))
port->hard_reset_count = 0;
- port->try_src_count = 0;
- port->try_snk_count = 0;
-
if (!port->attached)
return;
@@ -4401,7 +4411,8 @@ static void run_state_machine(struct tcpm_port *port)
tcpm_set_current_limit(port, tcpm_get_current_limit(port), 5000);
tcpm_swap_complete(port, 0);
tcpm_typec_connect(port);
- mod_enable_frs_delayed_work(port, 0);
+ if (port->pd_capable && port->source_caps[0] & PDO_FIXED_DUAL_ROLE)
+ mod_enable_frs_delayed_work(port, 0);
tcpm_pps_complete(port, port->pps_status);
if (port->ams != NONE_AMS)
@@ -4862,8 +4873,11 @@ static void run_state_machine(struct tcpm_port *port)
break;
case PORT_RESET:
tcpm_reset_port(port);
- tcpm_set_cc(port, tcpm_default_state(port) == SNK_UNATTACHED ?
- TYPEC_CC_RD : tcpm_rp_cc(port));
+ if (port->self_powered)
+ tcpm_set_cc(port, TYPEC_CC_OPEN);
+ else
+ tcpm_set_cc(port, tcpm_default_state(port) == SNK_UNATTACHED ?
+ TYPEC_CC_RD : tcpm_rp_cc(port));
tcpm_set_state(port, PORT_RESET_WAIT_OFF,
PD_T_ERROR_RECOVERY);
break;
@@ -5487,6 +5501,10 @@ static void tcpm_pd_event_handler(struct kthread_work *work)
tcpm_set_state(port, tcpm_default_state(port), 0);
}
}
+ if (events & TCPM_PORT_ERROR) {
+ tcpm_log(port, "port triggering error recovery");
+ tcpm_set_state(port, ERROR_RECOVERY, 0);
+ }
spin_lock(&port->pd_event_lock);
}
@@ -5554,6 +5572,15 @@ bool tcpm_port_is_toggling(struct tcpm_port *port)
}
EXPORT_SYMBOL_GPL(tcpm_port_is_toggling);
+void tcpm_port_error_recovery(struct tcpm_port *port)
+{
+ spin_lock(&port->pd_event_lock);
+ port->pd_events |= TCPM_PORT_ERROR;
+ spin_unlock(&port->pd_event_lock);
+ kthread_queue_work(port->wq, &port->event_work);
+}
+EXPORT_SYMBOL_GPL(tcpm_port_error_recovery);
+
static void tcpm_enable_frs_work(struct kthread_work *work)
{
struct tcpm_port *port = container_of(work, struct tcpm_port, enable_frs);
@@ -6045,12 +6072,114 @@ port_unlock:
return 0;
}
+static struct pd_data *tcpm_find_pd_data(struct tcpm_port *port, struct usb_power_delivery *pd)
+{
+ int i;
+
+ for (i = 0; port->pd_list[i]; i++) {
+ if (port->pd_list[i]->pd == pd)
+ return port->pd_list[i];
+ }
+
+ return ERR_PTR(-ENODATA);
+}
+
+static struct usb_power_delivery **tcpm_pd_get(struct typec_port *p)
+{
+ struct tcpm_port *port = typec_get_drvdata(p);
+
+ return port->pds;
+}
+
+static int tcpm_pd_set(struct typec_port *p, struct usb_power_delivery *pd)
+{
+ struct tcpm_port *port = typec_get_drvdata(p);
+ struct pd_data *data;
+ int i, ret = 0;
+
+ mutex_lock(&port->lock);
+
+ if (port->selected_pd == pd)
+ goto unlock;
+
+ data = tcpm_find_pd_data(port, pd);
+ if (IS_ERR(data)) {
+ ret = PTR_ERR(data);
+ goto unlock;
+ }
+
+ if (data->sink_desc.pdo[0]) {
+ for (i = 0; i < PDO_MAX_OBJECTS && data->sink_desc.pdo[i]; i++)
+ port->snk_pdo[i] = data->sink_desc.pdo[i];
+ port->nr_snk_pdo = i + 1;
+ port->operating_snk_mw = data->operating_snk_mw;
+ }
+
+ if (data->source_desc.pdo[0]) {
+ for (i = 0; i < PDO_MAX_OBJECTS && data->source_desc.pdo[i]; i++)
+ port->snk_pdo[i] = data->source_desc.pdo[i];
+ port->nr_src_pdo = i + 1;
+ }
+
+ switch (port->state) {
+ case SRC_UNATTACHED:
+ case SRC_ATTACH_WAIT:
+ case SRC_TRYWAIT:
+ tcpm_set_cc(port, tcpm_rp_cc(port));
+ break;
+ case SRC_SEND_CAPABILITIES:
+ case SRC_SEND_CAPABILITIES_TIMEOUT:
+ case SRC_NEGOTIATE_CAPABILITIES:
+ case SRC_READY:
+ case SRC_WAIT_NEW_CAPABILITIES:
+ port->caps_count = 0;
+ port->upcoming_state = SRC_SEND_CAPABILITIES;
+ ret = tcpm_ams_start(port, POWER_NEGOTIATION);
+ if (ret == -EAGAIN) {
+ port->upcoming_state = INVALID_STATE;
+ goto unlock;
+ }
+ break;
+ case SNK_NEGOTIATE_CAPABILITIES:
+ case SNK_NEGOTIATE_PPS_CAPABILITIES:
+ case SNK_READY:
+ case SNK_TRANSITION_SINK:
+ case SNK_TRANSITION_SINK_VBUS:
+ if (port->pps_data.active)
+ port->upcoming_state = SNK_NEGOTIATE_PPS_CAPABILITIES;
+ else if (port->pd_capable)
+ port->upcoming_state = SNK_NEGOTIATE_CAPABILITIES;
+ else
+ break;
+
+ port->update_sink_caps = true;
+
+ ret = tcpm_ams_start(port, POWER_NEGOTIATION);
+ if (ret == -EAGAIN) {
+ port->upcoming_state = INVALID_STATE;
+ goto unlock;
+ }
+ break;
+ default:
+ break;
+ }
+
+ port->port_source_caps = data->source_cap;
+ port->port_sink_caps = data->sink_cap;
+ port->selected_pd = pd;
+unlock:
+ mutex_unlock(&port->lock);
+ return ret;
+}
+
static const struct typec_operations tcpm_ops = {
.try_role = tcpm_try_role,
.dr_set = tcpm_dr_set,
.pr_set = tcpm_pr_set,
.vconn_set = tcpm_vconn_set,
- .port_type_set = tcpm_port_type_set
+ .port_type_set = tcpm_port_type_set,
+ .pd_get = tcpm_pd_get,
+ .pd_set = tcpm_pd_set
};
void tcpm_tcpc_reset(struct tcpm_port *port)
@@ -6064,58 +6193,63 @@ EXPORT_SYMBOL_GPL(tcpm_tcpc_reset);
static void tcpm_port_unregister_pd(struct tcpm_port *port)
{
- usb_power_delivery_unregister_capabilities(port->port_sink_caps);
+ int i;
+
port->port_sink_caps = NULL;
- usb_power_delivery_unregister_capabilities(port->port_source_caps);
port->port_source_caps = NULL;
- usb_power_delivery_unregister(port->pd);
- port->pd = NULL;
+ for (i = 0; i < port->pd_count; i++) {
+ usb_power_delivery_unregister_capabilities(port->pd_list[i]->sink_cap);
+ kfree(port->pd_list[i]->sink_cap);
+ usb_power_delivery_unregister_capabilities(port->pd_list[i]->source_cap);
+ kfree(port->pd_list[i]->source_cap);
+ devm_kfree(port->dev, port->pd_list[i]);
+ port->pd_list[i] = NULL;
+ usb_power_delivery_unregister(port->pds[i]);
+ port->pds[i] = NULL;
+ }
}
static int tcpm_port_register_pd(struct tcpm_port *port)
{
struct usb_power_delivery_desc desc = { port->typec_caps.pd_revision };
- struct usb_power_delivery_capabilities_desc caps = { };
struct usb_power_delivery_capabilities *cap;
- int ret;
+ int ret, i;
if (!port->nr_src_pdo && !port->nr_snk_pdo)
return 0;
- port->pd = usb_power_delivery_register(port->dev, &desc);
- if (IS_ERR(port->pd)) {
- ret = PTR_ERR(port->pd);
- goto err_unregister;
- }
-
- if (port->nr_src_pdo) {
- memcpy_and_pad(caps.pdo, sizeof(caps.pdo), port->src_pdo,
- port->nr_src_pdo * sizeof(u32), 0);
- caps.role = TYPEC_SOURCE;
-
- cap = usb_power_delivery_register_capabilities(port->pd, &caps);
- if (IS_ERR(cap)) {
- ret = PTR_ERR(cap);
+ for (i = 0; i < port->pd_count; i++) {
+ port->pds[i] = usb_power_delivery_register(port->dev, &desc);
+ if (IS_ERR(port->pds[i])) {
+ ret = PTR_ERR(port->pds[i]);
goto err_unregister;
}
-
- port->port_source_caps = cap;
- }
-
- if (port->nr_snk_pdo) {
- memcpy_and_pad(caps.pdo, sizeof(caps.pdo), port->snk_pdo,
- port->nr_snk_pdo * sizeof(u32), 0);
- caps.role = TYPEC_SINK;
-
- cap = usb_power_delivery_register_capabilities(port->pd, &caps);
- if (IS_ERR(cap)) {
- ret = PTR_ERR(cap);
- goto err_unregister;
+ port->pd_list[i]->pd = port->pds[i];
+
+ if (port->pd_list[i]->source_desc.pdo[0]) {
+ cap = usb_power_delivery_register_capabilities(port->pds[i],
+ &port->pd_list[i]->source_desc);
+ if (IS_ERR(cap)) {
+ ret = PTR_ERR(cap);
+ goto err_unregister;
+ }
+ port->pd_list[i]->source_cap = cap;
}
- port->port_sink_caps = cap;
+ if (port->pd_list[i]->sink_desc.pdo[0]) {
+ cap = usb_power_delivery_register_capabilities(port->pds[i],
+ &port->pd_list[i]->sink_desc);
+ if (IS_ERR(cap)) {
+ ret = PTR_ERR(cap);
+ goto err_unregister;
+ }
+ port->pd_list[i]->sink_cap = cap;
+ }
}
+ port->port_source_caps = port->pd_list[0]->source_cap;
+ port->port_sink_caps = port->pd_list[0]->sink_cap;
+ port->selected_pd = port->pds[0];
return 0;
err_unregister:
@@ -6124,12 +6258,15 @@ err_unregister:
return ret;
}
-static int tcpm_fw_get_caps(struct tcpm_port *port,
- struct fwnode_handle *fwnode)
+static int tcpm_fw_get_caps(struct tcpm_port *port, struct fwnode_handle *fwnode)
{
+ struct fwnode_handle *capabilities, *child, *caps = NULL;
+ unsigned int nr_src_pdo, nr_snk_pdo;
const char *opmode_str;
- int ret;
- u32 mw, frs_current;
+ u32 *src_pdo, *snk_pdo;
+ u32 uw, frs_current;
+ int ret = 0, i;
+ int mode;
if (!fwnode)
return -EINVAL;
@@ -6147,30 +6284,20 @@ static int tcpm_fw_get_caps(struct tcpm_port *port,
if (ret < 0)
return ret;
+ mode = 0;
+
+ if (fwnode_property_read_bool(fwnode, "accessory-mode-audio"))
+ port->typec_caps.accessory[mode++] = TYPEC_ACCESSORY_AUDIO;
+
+ if (fwnode_property_read_bool(fwnode, "accessory-mode-debug"))
+ port->typec_caps.accessory[mode++] = TYPEC_ACCESSORY_DEBUG;
+
port->port_type = port->typec_caps.type;
port->pd_supported = !fwnode_property_read_bool(fwnode, "pd-disable");
-
port->slow_charger_loop = fwnode_property_read_bool(fwnode, "slow-charger-loop");
- if (port->port_type == TYPEC_PORT_SNK)
- goto sink;
-
- /* Get Source PDOs for the PD port or Source Rp value for the non-PD port */
- if (port->pd_supported) {
- ret = fwnode_property_count_u32(fwnode, "source-pdos");
- if (ret == 0)
- return -EINVAL;
- else if (ret < 0)
- return ret;
+ port->self_powered = fwnode_property_read_bool(fwnode, "self-powered");
- port->nr_src_pdo = min(ret, PDO_MAX_OBJECTS);
- ret = fwnode_property_read_u32_array(fwnode, "source-pdos",
- port->src_pdo, port->nr_src_pdo);
- if (ret)
- return ret;
- ret = tcpm_validate_caps(port, port->src_pdo, port->nr_src_pdo);
- if (ret)
- return ret;
- } else {
+ if (!port->pd_supported) {
ret = fwnode_property_read_string(fwnode, "typec-power-opmode", &opmode_str);
if (ret)
return ret;
@@ -6178,45 +6305,150 @@ static int tcpm_fw_get_caps(struct tcpm_port *port,
if (ret < 0)
return ret;
port->src_rp = tcpm_pwr_opmode_to_rp(ret);
- }
-
- if (port->port_type == TYPEC_PORT_SRC)
return 0;
+ }
-sink:
- port->self_powered = fwnode_property_read_bool(fwnode, "self-powered");
-
- if (!port->pd_supported)
- return 0;
-
- /* Get sink pdos */
- ret = fwnode_property_count_u32(fwnode, "sink-pdos");
- if (ret <= 0)
- return -EINVAL;
-
- port->nr_snk_pdo = min(ret, PDO_MAX_OBJECTS);
- ret = fwnode_property_read_u32_array(fwnode, "sink-pdos",
- port->snk_pdo, port->nr_snk_pdo);
- if ((ret < 0) || tcpm_validate_caps(port, port->snk_pdo,
- port->nr_snk_pdo))
- return -EINVAL;
-
- if (fwnode_property_read_u32(fwnode, "op-sink-microwatt", &mw) < 0)
- return -EINVAL;
- port->operating_snk_mw = mw / 1000;
+ /* The following code are applicable to pd-capable ports, i.e. pd_supported is true. */
/* FRS can only be supported by DRP ports */
if (port->port_type == TYPEC_PORT_DRP) {
ret = fwnode_property_read_u32(fwnode, "new-source-frs-typec-current",
&frs_current);
- if (ret >= 0 && frs_current <= FRS_5V_3A)
+ if (!ret && frs_current <= FRS_5V_3A)
port->new_source_frs_current = frs_current;
+
+ if (ret)
+ ret = 0;
+ }
+
+ /* For the backward compatibility, "capabilities" node is optional. */
+ capabilities = fwnode_get_named_child_node(fwnode, "capabilities");
+ if (!capabilities) {
+ port->pd_count = 1;
+ } else {
+ fwnode_for_each_child_node(capabilities, child)
+ port->pd_count++;
+
+ if (!port->pd_count) {
+ ret = -ENODATA;
+ goto put_capabilities;
+ }
+ }
+
+ port->pds = devm_kcalloc(port->dev, port->pd_count, sizeof(struct usb_power_delivery *),
+ GFP_KERNEL);
+ if (!port->pds) {
+ ret = -ENOMEM;
+ goto put_capabilities;
+ }
+
+ port->pd_list = devm_kcalloc(port->dev, port->pd_count, sizeof(struct pd_data *),
+ GFP_KERNEL);
+ if (!port->pd_list) {
+ ret = -ENOMEM;
+ goto put_capabilities;
+ }
+
+ for (i = 0; i < port->pd_count; i++) {
+ port->pd_list[i] = devm_kzalloc(port->dev, sizeof(struct pd_data), GFP_KERNEL);
+ if (!port->pd_list[i]) {
+ ret = -ENOMEM;
+ goto put_capabilities;
+ }
+
+ src_pdo = port->pd_list[i]->source_desc.pdo;
+ port->pd_list[i]->source_desc.role = TYPEC_SOURCE;
+ snk_pdo = port->pd_list[i]->sink_desc.pdo;
+ port->pd_list[i]->sink_desc.role = TYPEC_SINK;
+
+ /* If "capabilities" is NULL, fall back to single pd cap population. */
+ if (!capabilities)
+ caps = fwnode;
+ else
+ caps = fwnode_get_next_child_node(capabilities, caps);
+
+ if (port->port_type != TYPEC_PORT_SNK) {
+ ret = fwnode_property_count_u32(caps, "source-pdos");
+ if (ret == 0) {
+ ret = -EINVAL;
+ goto put_caps;
+ }
+ if (ret < 0)
+ goto put_caps;
+
+ nr_src_pdo = min(ret, PDO_MAX_OBJECTS);
+ ret = fwnode_property_read_u32_array(caps, "source-pdos", src_pdo,
+ nr_src_pdo);
+ if (ret)
+ goto put_caps;
+
+ ret = tcpm_validate_caps(port, src_pdo, nr_src_pdo);
+ if (ret)
+ goto put_caps;
+
+ if (i == 0) {
+ port->nr_src_pdo = nr_src_pdo;
+ memcpy_and_pad(port->src_pdo, sizeof(u32) * PDO_MAX_OBJECTS,
+ port->pd_list[0]->source_desc.pdo,
+ sizeof(u32) * nr_src_pdo,
+ 0);
+ }
+ }
+
+ if (port->port_type != TYPEC_PORT_SRC) {
+ ret = fwnode_property_count_u32(caps, "sink-pdos");
+ if (ret == 0) {
+ ret = -EINVAL;
+ goto put_caps;
+ }
+
+ if (ret < 0)
+ goto put_caps;
+
+ nr_snk_pdo = min(ret, PDO_MAX_OBJECTS);
+ ret = fwnode_property_read_u32_array(caps, "sink-pdos", snk_pdo,
+ nr_snk_pdo);
+ if (ret)
+ goto put_caps;
+
+ ret = tcpm_validate_caps(port, snk_pdo, nr_snk_pdo);
+ if (ret)
+ goto put_caps;
+
+ if (fwnode_property_read_u32(caps, "op-sink-microwatt", &uw) < 0) {
+ ret = -EINVAL;
+ goto put_caps;
+ }
+
+ port->pd_list[i]->operating_snk_mw = uw / 1000;
+
+ if (i == 0) {
+ port->nr_snk_pdo = nr_snk_pdo;
+ memcpy_and_pad(port->snk_pdo, sizeof(u32) * PDO_MAX_OBJECTS,
+ port->pd_list[0]->sink_desc.pdo,
+ sizeof(u32) * nr_snk_pdo,
+ 0);
+ port->operating_snk_mw = port->pd_list[0]->operating_snk_mw;
+ }
+ }
}
+put_caps:
+ if (caps != fwnode)
+ fwnode_handle_put(caps);
+put_capabilities:
+ fwnode_handle_put(capabilities);
+ return ret;
+}
+
+static int tcpm_fw_get_snk_vdos(struct tcpm_port *port, struct fwnode_handle *fwnode)
+{
+ int ret;
+
/* sink-vdos is optional */
ret = fwnode_property_count_u32(fwnode, "sink-vdos");
if (ret < 0)
- ret = 0;
+ return 0;
port->nr_snk_vdo = min(ret, VDO_MAX_OBJECTS);
if (port->nr_snk_vdo) {
@@ -6584,10 +6816,12 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
err = tcpm_fw_get_caps(port, tcpc->fwnode);
if (err < 0)
goto out_destroy_wq;
+ err = tcpm_fw_get_snk_vdos(port, tcpc->fwnode);
+ if (err < 0)
+ goto out_destroy_wq;
port->try_role = port->typec_caps.prefer_role;
- port->typec_caps.fwnode = tcpc->fwnode;
port->typec_caps.revision = 0x0120; /* Type-C spec release 1.2 */
port->typec_caps.pd_revision = 0x0300; /* USB-PD spec release 3.0 */
port->typec_caps.svdm_version = SVDM_VER_2_0;
@@ -6596,7 +6830,6 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
port->typec_caps.orientation_aware = 1;
port->partner_desc.identity = &port->partner_ident;
- port->port_type = port->typec_caps.type;
port->role_sw = usb_role_switch_get(port->dev);
if (!port->role_sw)
@@ -6615,7 +6848,8 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
if (err)
goto out_role_sw_put;
- port->typec_caps.pd = port->pd;
+ if (port->pds)
+ port->typec_caps.pd = port->pds[0];
port->typec_port = typec_register_port(port->dev, &port->typec_caps);
if (IS_ERR(port->typec_port)) {