summaryrefslogtreecommitdiff
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.c126
1 files changed, 101 insertions, 25 deletions
diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c
index 89391939630b..b77e08b7e355 100644
--- a/drivers/usb/typec/tcpm/tcpm.c
+++ b/drivers/usb/typec/tcpm/tcpm.c
@@ -29,6 +29,8 @@
#include <linux/usb/tcpm.h>
#include <linux/usb/typec_altmode.h>
#include <linux/workqueue.h>
+#include <linux/extcon.h>
+#include <linux/extcon-provider.h>
#define FOREACH_STATE(S) \
S(INVALID_STATE), \
@@ -139,6 +141,12 @@ static const char * const tcpm_states[] = {
FOREACH_STATE(GENERATE_STRING)
};
+static const unsigned int tcpm_extcon_cable[] = {
+ EXTCON_USB_HOST,
+ EXTCON_USB,
+ EXTCON_NONE,
+};
+
enum vdm_states {
VDM_STATE_ERR_BUSY = -3,
VDM_STATE_ERR_SEND = -2,
@@ -208,6 +216,7 @@ struct pd_pps_data {
struct tcpm_port {
struct device *dev;
+ struct extcon_dev *edev;
struct mutex lock; /* tcpm state machine lock */
struct workqueue_struct *wq;
@@ -391,6 +400,14 @@ struct pd_rx_event {
((port)->try_src_count == 0 && (port)->try_role == TYPEC_SOURCE && \
(port)->port_type == TYPEC_PORT_DRP)
+#define tcpm_data_role_for_source(port) \
+ ((port)->typec_caps.data == TYPEC_PORT_UFP ? \
+ TYPEC_DEVICE : TYPEC_HOST)
+
+#define tcpm_data_role_for_sink(port) \
+ ((port)->typec_caps.data == TYPEC_PORT_DFP ? \
+ TYPEC_HOST : TYPEC_DEVICE)
+
static enum tcpm_state tcpm_default_state(struct tcpm_port *port)
{
if (port->port_type == TYPEC_PORT_DRP) {
@@ -688,6 +705,20 @@ static int tcpm_mux_set(struct tcpm_port *port, int state,
ret = usb_role_switch_set_role(port->role_sw, usb_role);
if (ret)
return ret;
+ } else if (port->edev) {
+ if (usb_role == USB_ROLE_NONE) {
+ extcon_set_state_sync(port->edev, EXTCON_USB_HOST,
+ false);
+ extcon_set_state_sync(port->edev, EXTCON_USB, false);
+ } else if (usb_role == USB_ROLE_DEVICE) {
+ extcon_set_state_sync(port->edev, EXTCON_USB_HOST,
+ false);
+ extcon_set_state_sync(port->edev, EXTCON_USB, true);
+ } else {
+ extcon_set_state_sync(port->edev, EXTCON_USB, false);
+ extcon_set_state_sync(port->edev, EXTCON_USB_HOST,
+ true);
+ }
}
return typec_set_mode(port->typec_port, state);
@@ -816,10 +847,30 @@ static int tcpm_set_roles(struct tcpm_port *port, bool attached,
else
orientation = TYPEC_ORIENTATION_REVERSE;
- if (data == TYPEC_HOST)
- usb_role = USB_ROLE_HOST;
- else
- usb_role = USB_ROLE_DEVICE;
+ if (port->typec_caps.data == TYPEC_PORT_DRD) {
+ if (data == TYPEC_HOST)
+ usb_role = USB_ROLE_HOST;
+ else
+ usb_role = USB_ROLE_DEVICE;
+ } else if (port->typec_caps.data == TYPEC_PORT_DFP) {
+ if (data == TYPEC_HOST) {
+ if (role == TYPEC_SOURCE)
+ usb_role = USB_ROLE_HOST;
+ else
+ usb_role = USB_ROLE_NONE;
+ } else {
+ return -ENOTSUPP;
+ }
+ } else {
+ if (data == TYPEC_DEVICE) {
+ if (role == TYPEC_SINK)
+ usb_role = USB_ROLE_DEVICE;
+ else
+ usb_role = USB_ROLE_NONE;
+ } else {
+ return -ENOTSUPP;
+ }
+ }
ret = tcpm_mux_set(port, TYPEC_STATE_USB, usb_role, orientation);
if (ret < 0)
@@ -1849,7 +1900,7 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port,
tcpm_set_state(port, SOFT_RESET, 0);
break;
case PD_CTRL_DR_SWAP:
- if (port->port_type != TYPEC_PORT_DRP) {
+ if (port->typec_caps.data != TYPEC_PORT_DRD) {
tcpm_queue_message(port, PD_MSG_CTRL_REJECT);
break;
}
@@ -2194,20 +2245,11 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo,
continue;
}
- switch (type) {
- case PDO_TYPE_FIXED:
- case PDO_TYPE_VAR:
+ if (type == PDO_TYPE_FIXED || type == PDO_TYPE_VAR) {
src_ma = pdo_max_current(pdo);
src_mw = src_ma * min_src_mv / 1000;
- break;
- case PDO_TYPE_BATT:
+ } else if (type == PDO_TYPE_BATT) {
src_mw = pdo_max_power(pdo);
- break;
- case PDO_TYPE_APDO:
- continue;
- default:
- tcpm_log(port, "Invalid source PDO type, ignoring");
- continue;
}
for (j = 0; j < port->nr_snk_pdo; j++) {
@@ -2649,11 +2691,14 @@ static int tcpm_src_attach(struct tcpm_port *port)
if (port->attached)
return 0;
+ tcpm_set_cc(port, tcpm_rp_cc(port));
+
ret = tcpm_set_polarity(port, polarity);
if (ret < 0)
return ret;
- ret = tcpm_set_roles(port, true, TYPEC_SOURCE, TYPEC_HOST);
+ ret = tcpm_set_roles(port, true, TYPEC_SOURCE,
+ tcpm_data_role_for_source(port));
if (ret < 0)
return ret;
@@ -2768,12 +2813,15 @@ static int tcpm_snk_attach(struct tcpm_port *port)
if (port->attached)
return 0;
+ tcpm_set_cc(port, TYPEC_CC_RD);
+
ret = tcpm_set_polarity(port, port->cc2 != TYPEC_CC_OPEN ?
TYPEC_POLARITY_CC2 : TYPEC_POLARITY_CC1);
if (ret < 0)
return ret;
- ret = tcpm_set_roles(port, true, TYPEC_SINK, TYPEC_DEVICE);
+ ret = tcpm_set_roles(port, true, TYPEC_SINK,
+ tcpm_data_role_for_sink(port));
if (ret < 0)
return ret;
@@ -2799,7 +2847,8 @@ static int tcpm_acc_attach(struct tcpm_port *port)
if (port->attached)
return 0;
- ret = tcpm_set_roles(port, true, TYPEC_SOURCE, TYPEC_HOST);
+ ret = tcpm_set_roles(port, true, TYPEC_SOURCE,
+ tcpm_data_role_for_source(port));
if (ret < 0)
return ret;
@@ -3170,7 +3219,11 @@ static void run_state_machine(struct tcpm_port *port)
ret = tcpm_snk_attach(port);
if (ret < 0)
tcpm_set_state(port, SNK_UNATTACHED, 0);
- else
+ else if (port->port_type == TYPEC_PORT_SRC &&
+ port->typec_caps.data == TYPEC_PORT_DRD) {
+ tcpm_typec_connect(port);
+ tcpm_log(port, "Keep at SNK_ATTACHED for USB data.");
+ } else
tcpm_set_state(port, SNK_STARTUP, 0);
break;
case SNK_STARTUP:
@@ -3326,7 +3379,7 @@ static void run_state_machine(struct tcpm_port *port)
tcpm_set_vconn(port, true);
tcpm_set_vbus(port, false);
tcpm_set_roles(port, port->self_powered, TYPEC_SOURCE,
- TYPEC_HOST);
+ tcpm_data_role_for_source(port));
tcpm_set_state(port, SRC_HARD_RESET_VBUS_ON, PD_T_SRC_RECOVER);
break;
case SRC_HARD_RESET_VBUS_ON:
@@ -3341,7 +3394,7 @@ static void run_state_machine(struct tcpm_port *port)
if (port->pd_capable)
tcpm_set_charge(port, false);
tcpm_set_roles(port, port->self_powered, TYPEC_SINK,
- TYPEC_DEVICE);
+ tcpm_data_role_for_sink(port));
/*
* VBUS may or may not toggle, depending on the adapter.
* If it doesn't toggle, transition to SNK_HARD_RESET_SINK_ON
@@ -4030,7 +4083,7 @@ static int tcpm_dr_set(const struct typec_capability *cap,
mutex_lock(&port->swap_lock);
mutex_lock(&port->lock);
- if (port->port_type != TYPEC_PORT_DRP) {
+ if (port->typec_caps.data != TYPEC_PORT_DRD) {
ret = -EINVAL;
goto port_unlock;
}
@@ -4544,7 +4597,7 @@ static enum power_supply_property tcpm_psy_props[] = {
static int tcpm_psy_get_online(struct tcpm_port *port,
union power_supply_propval *val)
{
- if (port->vbus_charge) {
+ if (port->vbus_present && tcpm_port_is_sink(port)) {
if (port->pps_data.active)
val->intval = TCPM_PSY_PROG_ONLINE;
else
@@ -4686,6 +4739,9 @@ static int tcpm_psy_set_prop(struct power_supply *psy,
else
ret = tcpm_pps_set_op_curr(port, val->intval / 1000);
break;
+ case POWER_SUPPLY_PROP_USB_TYPE:
+ port->usb_type = val->intval;
+ break;
default:
ret = -EINVAL;
break;
@@ -4708,6 +4764,10 @@ static int tcpm_psy_prop_writeable(struct power_supply *psy,
}
static enum power_supply_usb_type tcpm_psy_usb_types[] = {
+ POWER_SUPPLY_USB_TYPE_SDP,
+ POWER_SUPPLY_USB_TYPE_DCP,
+ POWER_SUPPLY_USB_TYPE_CDP,
+ POWER_SUPPLY_USB_TYPE_ACA,
POWER_SUPPLY_USB_TYPE_C,
POWER_SUPPLY_USB_TYPE_PD,
POWER_SUPPLY_USB_TYPE_PD_PPS,
@@ -4795,7 +4855,7 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
mutex_init(&port->lock);
mutex_init(&port->swap_lock);
- port->wq = create_singlethread_workqueue(dev_name(dev));
+ port->wq = create_freezable_workqueue(dev_name(dev));
if (!port->wq)
return ERR_PTR(-ENOMEM);
INIT_DELAYED_WORK(&port->state_machine, tcpm_state_machine_work);
@@ -4872,6 +4932,19 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
}
}
+ port->edev = devm_extcon_dev_allocate(port->dev, tcpm_extcon_cable);
+ if (IS_ERR(port->edev)) {
+ dev_err(port->dev, "failed to allocate extcon dev.\n");
+ err = -ENOMEM;
+ goto out_role_sw_put;
+ }
+
+ err = devm_extcon_dev_register(port->dev, port->edev);
+ if (err) {
+ dev_err(port->dev, "failed to register extcon dev.\n");
+ goto out_role_sw_put;
+ }
+
mutex_lock(&port->lock);
tcpm_init(port);
mutex_unlock(&port->lock);
@@ -4892,6 +4965,9 @@ void tcpm_unregister_port(struct tcpm_port *port)
{
int i;
+ cancel_delayed_work_sync(&port->state_machine);
+ cancel_delayed_work_sync(&port->vdm_state_machine);
+
tcpm_reset_port(port);
for (i = 0; i < ARRAY_SIZE(port->port_altmode); i++)
typec_unregister_altmode(port->port_altmode[i]);