Implement active cable VDM support for SOP' according to the DisplayPort
Alt Mode 2.0 specification.
When probing the DisplayPort driver, the state machine will transition to
Enter Mode on SOP' if an active cable altmode is detected. The SVDM flow
is as followed:
(1) Enter Mode SOP'
(2) Enter Mode SOP
(3) Status Update SOP
(4) Configure SOP'
(5) Configure SOP
Status Update on SOP' after Enter Mode is optional and not implemented for
now. When exiting the alt mode, send Exit Mode over SOP' after SOP.
Should an altmode vdm fail on SOP', the DisplayPort driver will drop its
reference to the plug and attempt to continue in SOP operation.
Add new dp_state enums DP_STATE_ENTER_PRIME, DP_STATE_CONFIGURE_PRIME, and
DP_STATE_EXIT_PRIME. dp_altmode adds typec_displayport_data for the cable
plug to store the plug configuration and adds a typec_altmode reference
for the cable plug.
dp_altmode_configure takes the cable pin assignment capabilities into
account when deciding on pin configuration. dp_altmode_configure_vdm_cable
sends the configure message on SOP'.
dp_altmode_activate now attempts to enter on SOP' if applicable, and will
attempt to enter on SOP on failure.
dp_cable_altmode_vdm handles VDMs passed to the DisplayPort driver from
the tcpm.
Signed-off-by: RD Babiera <rdbabiera@google.com>
---
Changes since v1:
* dp_altmode_configure_vdm no longer handles sop', now handled by
dp_altmode_configure_vdm_cable
* dp_exit_mode_handler deleted
* dp_altmode_vdm no longer handles sop', now handled by
dp_cable_altmode_vdm as typec_cable_ops callback assigned to plug_prime
if it exists.
* driver data registered to plug_prime if applicable.
---
drivers/usb/typec/altmodes/displayport.c | 161 ++++++++++++++++++++++-
1 file changed, 157 insertions(+), 4 deletions(-)
diff --git a/drivers/usb/typec/altmodes/displayport.c b/drivers/usb/typec/altmodes/displayport.c
index f81bec0c7b86..06ea63bc536e 100644
--- a/drivers/usb/typec/altmodes/displayport.c
+++ b/drivers/usb/typec/altmodes/displayport.c
@@ -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,7 +156,9 @@ 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))) {
@@ -209,6 +224,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 +253,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 +284,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 +309,12 @@ 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);
}
static void dp_altmode_attention(struct typec_altmode *alt, const u32 vdo)
@@ -314,6 +375,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 +411,84 @@ static int dp_altmode_vdm(struct typec_altmode *alt,
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 +497,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 +642,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;
@@ -574,6 +716,7 @@ static const struct attribute_group dp_altmode_group = {
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;
int ret;
@@ -603,6 +746,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 +762,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;
@@ -626,6 +778,7 @@ void dp_altmode_remove(struct typec_altmode *alt)
sysfs_remove_group(&alt->dev.kobj, &dp_altmode_group);
cancel_work_sync(&dp->work);
+ typec_altmode_put_plug(dp->plug_prime);
if (dp->connector_fwnode) {
drm_connector_oob_hotplug_event(dp->connector_fwnode,
--
2.43.0.472.g3155946c3a-goog
Hi RD,
kernel test robot noticed the following build warnings:
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/RD-Babiera/usb-typec-altmodes-add-typec_cable_ops-to-typec_altmode/20231215-071339
base: https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb.git usb-testing
patch link: https://lore.kernel.org/r/20231214230850.379863-26-rdbabiera%40google.com
patch subject: [PATCH v2 12/12] usb: typec: altmodes/displayport: add SOP' support
config: riscv-randconfig-r081-20231216 (https://download.01.org/0day-ci/archive/20231216/202312161205.sNH5M6Pz-lkp@intel.com/config)
compiler: clang version 17.0.0 (https://github.com/llvm/llvm-project.git 4a5ac14ee968ff0ad5d2cc1ffa0299048db4c88a)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Reported-by: Dan Carpenter <dan.carpenter@linaro.org>
| Closes: https://lore.kernel.org/r/202312161205.sNH5M6Pz-lkp@intel.com/
smatch warnings:
drivers/usb/typec/altmodes/displayport.c:317 dp_altmode_work() warn: inconsistent returns '&dp->lock'.
vim +317 drivers/usb/typec/altmodes/displayport.c
0e3bb7d6894d9b Heikki Krogerus 2018-06-27 240 static void dp_altmode_work(struct work_struct *work)
0e3bb7d6894d9b Heikki Krogerus 2018-06-27 241 {
0e3bb7d6894d9b Heikki Krogerus 2018-06-27 242 struct dp_altmode *dp = container_of(work, struct dp_altmode, work);
4c93cad8cc78bd Kyle Tso 2021-02-05 243 int svdm_version;
0e3bb7d6894d9b Heikki Krogerus 2018-06-27 244 u32 header;
0e3bb7d6894d9b Heikki Krogerus 2018-06-27 245 u32 vdo;
0e3bb7d6894d9b Heikki Krogerus 2018-06-27 246 int ret;
0e3bb7d6894d9b Heikki Krogerus 2018-06-27 247
0e3bb7d6894d9b Heikki Krogerus 2018-06-27 248 mutex_lock(&dp->lock);
0e3bb7d6894d9b Heikki Krogerus 2018-06-27 249
0e3bb7d6894d9b Heikki Krogerus 2018-06-27 250 switch (dp->state) {
0e3bb7d6894d9b Heikki Krogerus 2018-06-27 251 case DP_STATE_ENTER:
8face9aa57c833 Heikki Krogerus 2019-12-30 252 ret = typec_altmode_enter(dp->alt, NULL);
5789051fc57bb6 Heikki Krogerus 2020-09-28 253 if (ret && ret != -EBUSY)
0e3bb7d6894d9b Heikki Krogerus 2018-06-27 254 dev_err(&dp->alt->dev, "failed to enter mode\n");
0e3bb7d6894d9b Heikki Krogerus 2018-06-27 255 break;
92483f2f3092f0 RD Babiera 2023-12-14 256 case DP_STATE_ENTER_PRIME:
92483f2f3092f0 RD Babiera 2023-12-14 257 ret = typec_cable_altmode_enter(dp->alt, TYPEC_PLUG_SOP_P, NULL);
92483f2f3092f0 RD Babiera 2023-12-14 258 /*
92483f2f3092f0 RD Babiera 2023-12-14 259 * If we fail to enter Alt Mode on SOP', then we should drop the
92483f2f3092f0 RD Babiera 2023-12-14 260 * plug from the driver and attempt to run the driver without
92483f2f3092f0 RD Babiera 2023-12-14 261 * it.
92483f2f3092f0 RD Babiera 2023-12-14 262 */
92483f2f3092f0 RD Babiera 2023-12-14 263 if (ret && ret != -EBUSY) {
92483f2f3092f0 RD Babiera 2023-12-14 264 dev_err(&dp->alt->dev, "plug failed to enter mode\n");
92483f2f3092f0 RD Babiera 2023-12-14 265 dp->state = DP_STATE_ENTER;
92483f2f3092f0 RD Babiera 2023-12-14 266 goto disable_prime;
92483f2f3092f0 RD Babiera 2023-12-14 267 }
92483f2f3092f0 RD Babiera 2023-12-14 268 break;
0e3bb7d6894d9b Heikki Krogerus 2018-06-27 269 case DP_STATE_UPDATE:
4c93cad8cc78bd Kyle Tso 2021-02-05 270 svdm_version = typec_altmode_get_svdm_version(dp->alt);
4c93cad8cc78bd Kyle Tso 2021-02-05 271 if (svdm_version < 0)
4c93cad8cc78bd Kyle Tso 2021-02-05 272 break;
4c93cad8cc78bd Kyle Tso 2021-02-05 273 header = DP_HEADER(dp, svdm_version, DP_CMD_STATUS_UPDATE);
0e3bb7d6894d9b Heikki Krogerus 2018-06-27 274 vdo = 1;
0e3bb7d6894d9b Heikki Krogerus 2018-06-27 275 ret = typec_altmode_vdm(dp->alt, header, &vdo, 2);
0e3bb7d6894d9b Heikki Krogerus 2018-06-27 276 if (ret)
0e3bb7d6894d9b Heikki Krogerus 2018-06-27 277 dev_err(&dp->alt->dev,
0e3bb7d6894d9b Heikki Krogerus 2018-06-27 278 "unable to send Status Update command (%d)\n",
0e3bb7d6894d9b Heikki Krogerus 2018-06-27 279 ret);
0e3bb7d6894d9b Heikki Krogerus 2018-06-27 280 break;
0e3bb7d6894d9b Heikki Krogerus 2018-06-27 281 case DP_STATE_CONFIGURE:
0e3bb7d6894d9b Heikki Krogerus 2018-06-27 282 ret = dp_altmode_configure_vdm(dp, dp->data.conf);
0e3bb7d6894d9b Heikki Krogerus 2018-06-27 283 if (ret)
0e3bb7d6894d9b Heikki Krogerus 2018-06-27 284 dev_err(&dp->alt->dev,
0e3bb7d6894d9b Heikki Krogerus 2018-06-27 285 "unable to send Configure command (%d)\n", ret);
0e3bb7d6894d9b Heikki Krogerus 2018-06-27 286 break;
92483f2f3092f0 RD Babiera 2023-12-14 287 case DP_STATE_CONFIGURE_PRIME:
92483f2f3092f0 RD Babiera 2023-12-14 288 ret = dp_altmode_configure_vdm_cable(dp, dp->data_prime.conf);
92483f2f3092f0 RD Babiera 2023-12-14 289 if (ret) {
92483f2f3092f0 RD Babiera 2023-12-14 290 dev_err(&dp->plug_prime->dev,
92483f2f3092f0 RD Babiera 2023-12-14 291 "unable to send Configure command (%d)\n",
92483f2f3092f0 RD Babiera 2023-12-14 292 ret);
92483f2f3092f0 RD Babiera 2023-12-14 293 dp->state = DP_STATE_CONFIGURE;
92483f2f3092f0 RD Babiera 2023-12-14 294 goto disable_prime;
92483f2f3092f0 RD Babiera 2023-12-14 295 }
92483f2f3092f0 RD Babiera 2023-12-14 296 break;
0e3bb7d6894d9b Heikki Krogerus 2018-06-27 297 case DP_STATE_EXIT:
0e3bb7d6894d9b Heikki Krogerus 2018-06-27 298 if (typec_altmode_exit(dp->alt))
0e3bb7d6894d9b Heikki Krogerus 2018-06-27 299 dev_err(&dp->alt->dev, "Exit Mode Failed!\n");
0e3bb7d6894d9b Heikki Krogerus 2018-06-27 300 break;
92483f2f3092f0 RD Babiera 2023-12-14 301 case DP_STATE_EXIT_PRIME:
92483f2f3092f0 RD Babiera 2023-12-14 302 if (typec_cable_altmode_exit(dp->plug_prime, TYPEC_PLUG_SOP_P))
92483f2f3092f0 RD Babiera 2023-12-14 303 dev_err(&dp->plug_prime->dev, "Exit Mode Failed!\n");
92483f2f3092f0 RD Babiera 2023-12-14 304 break;
0e3bb7d6894d9b Heikki Krogerus 2018-06-27 305 default:
0e3bb7d6894d9b Heikki Krogerus 2018-06-27 306 break;
0e3bb7d6894d9b Heikki Krogerus 2018-06-27 307 }
0e3bb7d6894d9b Heikki Krogerus 2018-06-27 308
0e3bb7d6894d9b Heikki Krogerus 2018-06-27 309 dp->state = DP_STATE_IDLE;
0e3bb7d6894d9b Heikki Krogerus 2018-06-27 310
0e3bb7d6894d9b Heikki Krogerus 2018-06-27 311 mutex_unlock(&dp->lock);
92483f2f3092f0 RD Babiera 2023-12-14 312 return;
92483f2f3092f0 RD Babiera 2023-12-14 313
92483f2f3092f0 RD Babiera 2023-12-14 314 disable_prime:
92483f2f3092f0 RD Babiera 2023-12-14 315 typec_altmode_put_plug(dp->plug_prime);
92483f2f3092f0 RD Babiera 2023-12-14 316 dp->plug_prime = NULL;
We need a mutex_unlock(&dp->lock); somewhere here.
92483f2f3092f0 RD Babiera 2023-12-14 @317 schedule_work(&dp->work);
0e3bb7d6894d9b Heikki Krogerus 2018-06-27 318 }
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
© 2016 - 2025 Red Hat, Inc.