Merge tag 'usb-6.9-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb
[sfrench/cifs-2.6.git] / drivers / usb / typec / altmodes / displayport.c
index f8ea3054be54245c4233b48facaabe91f52868ed..038dc51f429dda5146cb0d92d8146880c39d2b61 100644 (file)
@@ -50,13 +50,17 @@ enum {
 enum dp_state {
        DP_STATE_IDLE,
        DP_STATE_ENTER,
+       DP_STATE_ENTER_PRIME,
        DP_STATE_UPDATE,
        DP_STATE_CONFIGURE,
+       DP_STATE_CONFIGURE_PRIME,
        DP_STATE_EXIT,
+       DP_STATE_EXIT_PRIME,
 };
 
 struct dp_altmode {
        struct typec_displayport_data data;
+       struct typec_displayport_data data_prime;
 
        enum dp_state state;
        bool hpd;
@@ -67,6 +71,7 @@ struct dp_altmode {
        struct typec_altmode *alt;
        const struct typec_altmode *port;
        struct fwnode_handle *connector_fwnode;
+       struct typec_altmode *plug_prime;
 };
 
 static int dp_altmode_notify(struct dp_altmode *dp)
@@ -99,12 +104,18 @@ static int dp_altmode_configure(struct dp_altmode *dp, u8 con)
                conf |= DP_CONF_UFP_U_AS_DFP_D;
                pin_assign = DP_CAP_UFP_D_PIN_ASSIGN(dp->alt->vdo) &
                             DP_CAP_DFP_D_PIN_ASSIGN(dp->port->vdo);
+               /* Account for active cable capabilities */
+               if (dp->plug_prime)
+                       pin_assign &= DP_CAP_DFP_D_PIN_ASSIGN(dp->plug_prime->vdo);
                break;
        case DP_STATUS_CON_UFP_D:
        case DP_STATUS_CON_BOTH: /* NOTE: First acting as DP source */
                conf |= DP_CONF_UFP_U_AS_UFP_D;
                pin_assign = DP_CAP_PIN_ASSIGN_UFP_D(dp->alt->vdo) &
                                 DP_CAP_PIN_ASSIGN_DFP_D(dp->port->vdo);
+               /* Account for active cable capabilities */
+               if (dp->plug_prime)
+                       pin_assign &= DP_CAP_UFP_D_PIN_ASSIGN(dp->plug_prime->vdo);
                break;
        default:
                break;
@@ -130,6 +141,8 @@ static int dp_altmode_configure(struct dp_altmode *dp, u8 con)
        }
 
        dp->data.conf = conf;
+       if (dp->plug_prime)
+               dp->data_prime.conf = conf;
 
        return 0;
 }
@@ -143,13 +156,16 @@ static int dp_altmode_status_update(struct dp_altmode *dp)
 
        if (configured && (dp->data.status & DP_STATUS_SWITCH_TO_USB)) {
                dp->data.conf = 0;
-               dp->state = DP_STATE_CONFIGURE;
+               dp->data_prime.conf = 0;
+               dp->state = dp->plug_prime ? DP_STATE_CONFIGURE_PRIME :
+                                            DP_STATE_CONFIGURE;
        } else if (dp->data.status & DP_STATUS_EXIT_DP_MODE) {
                dp->state = DP_STATE_EXIT;
        } else if (!(con & DP_CONF_CURRENTLY(dp->data.conf))) {
                ret = dp_altmode_configure(dp, con);
                if (!ret) {
-                       dp->state = DP_STATE_CONFIGURE;
+                       dp->state = dp->plug_prime ? DP_STATE_CONFIGURE_PRIME :
+                                                    DP_STATE_CONFIGURE;
                        if (dp->hpd != hpd) {
                                dp->hpd = hpd;
                                dp->pending_hpd = true;
@@ -209,6 +225,19 @@ static int dp_altmode_configure_vdm(struct dp_altmode *dp, u32 conf)
        return ret;
 }
 
+static int dp_altmode_configure_vdm_cable(struct dp_altmode *dp, u32 conf)
+{
+       int svdm_version = typec_altmode_get_cable_svdm_version(dp->plug_prime);
+       u32 header;
+
+       if (svdm_version < 0)
+               return svdm_version;
+
+       header = DP_HEADER(dp, svdm_version, DP_CMD_CONFIGURE);
+
+       return typec_cable_altmode_vdm(dp->plug_prime, TYPEC_PLUG_SOP_P, header, &conf, 2);
+}
+
 static void dp_altmode_work(struct work_struct *work)
 {
        struct dp_altmode *dp = container_of(work, struct dp_altmode, work);
@@ -225,6 +254,19 @@ static void dp_altmode_work(struct work_struct *work)
                if (ret && ret != -EBUSY)
                        dev_err(&dp->alt->dev, "failed to enter mode\n");
                break;
+       case DP_STATE_ENTER_PRIME:
+               ret = typec_cable_altmode_enter(dp->alt, TYPEC_PLUG_SOP_P, NULL);
+               /*
+                * If we fail to enter Alt Mode on SOP', then we should drop the
+                * plug from the driver and attempt to run the driver without
+                * it.
+                */
+               if (ret && ret != -EBUSY) {
+                       dev_err(&dp->alt->dev, "plug failed to enter mode\n");
+                       dp->state = DP_STATE_ENTER;
+                       goto disable_prime;
+               }
+               break;
        case DP_STATE_UPDATE:
                svdm_version = typec_altmode_get_svdm_version(dp->alt);
                if (svdm_version < 0)
@@ -243,10 +285,24 @@ static void dp_altmode_work(struct work_struct *work)
                        dev_err(&dp->alt->dev,
                                "unable to send Configure command (%d)\n", ret);
                break;
+       case DP_STATE_CONFIGURE_PRIME:
+               ret = dp_altmode_configure_vdm_cable(dp, dp->data_prime.conf);
+               if (ret) {
+                       dev_err(&dp->plug_prime->dev,
+                               "unable to send Configure command (%d)\n",
+                               ret);
+                       dp->state = DP_STATE_CONFIGURE;
+                       goto disable_prime;
+               }
+               break;
        case DP_STATE_EXIT:
                if (typec_altmode_exit(dp->alt))
                        dev_err(&dp->alt->dev, "Exit Mode Failed!\n");
                break;
+       case DP_STATE_EXIT_PRIME:
+               if (typec_cable_altmode_exit(dp->plug_prime, TYPEC_PLUG_SOP_P))
+                       dev_err(&dp->plug_prime->dev, "Exit Mode Failed!\n");
+               break;
        default:
                break;
        }
@@ -254,6 +310,13 @@ static void dp_altmode_work(struct work_struct *work)
        dp->state = DP_STATE_IDLE;
 
        mutex_unlock(&dp->lock);
+       return;
+
+disable_prime:
+       typec_altmode_put_plug(dp->plug_prime);
+       dp->plug_prime = NULL;
+       schedule_work(&dp->work);
+       mutex_unlock(&dp->lock);
 }
 
 static void dp_altmode_attention(struct typec_altmode *alt, const u32 vdo)
@@ -314,6 +377,8 @@ static int dp_altmode_vdm(struct typec_altmode *alt,
                                dp->hpd = false;
                                sysfs_notify(&dp->alt->dev.kobj, "displayport", "hpd");
                        }
+                       if (dp->plug_prime)
+                               dp->state = DP_STATE_EXIT_PRIME;
                        break;
                case DP_CMD_STATUS_UPDATE:
                        dp->data.status = *vdo;
@@ -348,10 +413,84 @@ err_unlock:
        return ret;
 }
 
+static int dp_cable_altmode_vdm(struct typec_altmode *alt, enum typec_plug_index sop,
+                               const u32 hdr, const u32 *vdo, int count)
+{
+       struct dp_altmode *dp = typec_altmode_get_drvdata(alt);
+       int cmd_type = PD_VDO_CMDT(hdr);
+       int cmd = PD_VDO_CMD(hdr);
+       int ret = 0;
+
+       mutex_lock(&dp->lock);
+
+       if (dp->state != DP_STATE_IDLE) {
+               ret = -EBUSY;
+               goto err_unlock;
+       }
+
+       switch (cmd_type) {
+       case CMDT_RSP_ACK:
+               switch (cmd) {
+               case CMD_ENTER_MODE:
+                       typec_altmode_update_active(dp->plug_prime, true);
+                       dp->state = DP_STATE_ENTER;
+                       break;
+               case CMD_EXIT_MODE:
+                       dp->data_prime.status = 0;
+                       dp->data_prime.conf = 0;
+                       typec_altmode_update_active(dp->plug_prime, false);
+                       break;
+               case DP_CMD_CONFIGURE:
+                       dp->state = DP_STATE_CONFIGURE;
+                       break;
+               default:
+                       break;
+               }
+               break;
+       case CMDT_RSP_NAK:
+               switch (cmd) {
+               case DP_CMD_CONFIGURE:
+                       dp->data_prime.conf = 0;
+                       /* Attempt to configure on SOP, drop plug */
+                       typec_altmode_put_plug(dp->plug_prime);
+                       dp->plug_prime = NULL;
+                       dp->state = DP_STATE_CONFIGURE;
+                       break;
+               default:
+                       break;
+               }
+               break;
+       default:
+               break;
+       }
+
+       if (dp->state != DP_STATE_IDLE)
+               schedule_work(&dp->work);
+
+err_unlock:
+       mutex_unlock(&dp->lock);
+       return ret;
+}
+
 static int dp_altmode_activate(struct typec_altmode *alt, int activate)
 {
-       return activate ? typec_altmode_enter(alt, NULL) :
-                         typec_altmode_exit(alt);
+       struct dp_altmode *dp = typec_altmode_get_drvdata(alt);
+       int ret;
+
+       if (activate) {
+               if (dp->plug_prime) {
+                       ret = typec_cable_altmode_enter(alt, TYPEC_PLUG_SOP_P, NULL);
+                       if (ret < 0) {
+                               typec_altmode_put_plug(dp->plug_prime);
+                               dp->plug_prime = NULL;
+                       } else {
+                               return ret;
+                       }
+               }
+               return typec_altmode_enter(alt, NULL);
+       } else {
+               return typec_altmode_exit(alt);
+       }
 }
 
 static const struct typec_altmode_ops dp_altmode_ops = {
@@ -360,6 +499,10 @@ static const struct typec_altmode_ops dp_altmode_ops = {
        .activate = dp_altmode_activate,
 };
 
+static const struct typec_cable_ops dp_cable_ops = {
+       .vdm = dp_cable_altmode_vdm,
+};
+
 static const char * const configurations[] = {
        [DP_CONF_USB]   = "USB",
        [DP_CONF_DFP_D] = "source",
@@ -501,6 +644,7 @@ pin_assignment_store(struct device *dev, struct device_attribute *attr,
 
        /* Only send Configure command if a configuration has been set */
        if (dp->alt->active && DP_CONF_CURRENTLY(dp->data.conf)) {
+               /* todo: send manual configure over SOP'*/
                ret = dp_altmode_configure_vdm(dp, conf);
                if (ret)
                        goto out_unlock;
@@ -579,6 +723,7 @@ static const struct attribute_group *displayport_groups[] = {
 int dp_altmode_probe(struct typec_altmode *alt)
 {
        const struct typec_altmode *port = typec_altmode_get_partner(alt);
+       struct typec_altmode *plug = typec_altmode_get_plug(alt, TYPEC_PLUG_SOP_P);
        struct fwnode_handle *fwnode;
        struct dp_altmode *dp;
 
@@ -603,6 +748,13 @@ int dp_altmode_probe(struct typec_altmode *alt)
        alt->desc = "DisplayPort";
        alt->ops = &dp_altmode_ops;
 
+       if (plug) {
+               plug->desc = "Displayport";
+               plug->cable_ops = &dp_cable_ops;
+       }
+
+       dp->plug_prime = plug;
+
        fwnode = dev_fwnode(alt->dev.parent->parent); /* typec_port fwnode */
        if (fwnode_property_present(fwnode, "displayport"))
                dp->connector_fwnode = fwnode_find_reference(fwnode, "displayport", 0);
@@ -612,8 +764,10 @@ int dp_altmode_probe(struct typec_altmode *alt)
                dp->connector_fwnode = NULL;
 
        typec_altmode_set_drvdata(alt, dp);
+       if (plug)
+               typec_altmode_set_drvdata(plug, dp);
 
-       dp->state = DP_STATE_ENTER;
+       dp->state = plug ? DP_STATE_ENTER_PRIME : DP_STATE_ENTER;
        schedule_work(&dp->work);
 
        return 0;
@@ -625,6 +779,7 @@ void dp_altmode_remove(struct typec_altmode *alt)
        struct dp_altmode *dp = typec_altmode_get_drvdata(alt);
 
        cancel_work_sync(&dp->work);
+       typec_altmode_put_plug(dp->plug_prime);
 
        if (dp->connector_fwnode) {
                drm_connector_oob_hotplug_event(dp->connector_fwnode,