root/drivers/usb/typec/altmodes/displayport.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. dp_altmode_notify
  2. dp_altmode_configure
  3. dp_altmode_status_update
  4. dp_altmode_configured
  5. dp_altmode_configure_vdm
  6. dp_altmode_work
  7. dp_altmode_attention
  8. dp_altmode_vdm
  9. dp_altmode_activate
  10. configuration_store
  11. configuration_show
  12. pin_assignment_store
  13. pin_assignment_show
  14. dp_altmode_probe
  15. dp_altmode_remove

   1 // SPDX-License-Identifier: GPL-2.0
   2 /**
   3  * USB Typec-C DisplayPort Alternate Mode driver
   4  *
   5  * Copyright (C) 2018 Intel Corporation
   6  * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
   7  *
   8  * DisplayPort is trademark of VESA (www.vesa.org)
   9  */
  10 
  11 #include <linux/delay.h>
  12 #include <linux/mutex.h>
  13 #include <linux/module.h>
  14 #include <linux/usb/pd_vdo.h>
  15 #include <linux/usb/typec_dp.h>
  16 
  17 #define DP_HEADER(_dp, cmd)             (VDO((_dp)->alt->svid, 1, cmd) | \
  18                                          VDO_OPOS(USB_TYPEC_DP_MODE))
  19 
  20 enum {
  21         DP_CONF_USB,
  22         DP_CONF_DFP_D,
  23         DP_CONF_UFP_D,
  24         DP_CONF_DUAL_D,
  25 };
  26 
  27 /* Pin assignments that use USB3.1 Gen2 signaling to carry DP protocol */
  28 #define DP_PIN_ASSIGN_GEN2_BR_MASK      (BIT(DP_PIN_ASSIGN_A) | \
  29                                          BIT(DP_PIN_ASSIGN_B))
  30 
  31 /* Pin assignments that use DP v1.3 signaling to carry DP protocol */
  32 #define DP_PIN_ASSIGN_DP_BR_MASK        (BIT(DP_PIN_ASSIGN_C) | \
  33                                          BIT(DP_PIN_ASSIGN_D) | \
  34                                          BIT(DP_PIN_ASSIGN_E) | \
  35                                          BIT(DP_PIN_ASSIGN_F))
  36 
  37 /* DP only pin assignments */
  38 #define DP_PIN_ASSIGN_DP_ONLY_MASK      (BIT(DP_PIN_ASSIGN_A) | \
  39                                          BIT(DP_PIN_ASSIGN_C) | \
  40                                          BIT(DP_PIN_ASSIGN_E))
  41 
  42 /* Pin assignments where one channel is for USB */
  43 #define DP_PIN_ASSIGN_MULTI_FUNC_MASK   (BIT(DP_PIN_ASSIGN_B) | \
  44                                          BIT(DP_PIN_ASSIGN_D) | \
  45                                          BIT(DP_PIN_ASSIGN_F))
  46 
  47 enum dp_state {
  48         DP_STATE_IDLE,
  49         DP_STATE_ENTER,
  50         DP_STATE_UPDATE,
  51         DP_STATE_CONFIGURE,
  52         DP_STATE_EXIT,
  53 };
  54 
  55 struct dp_altmode {
  56         struct typec_displayport_data data;
  57 
  58         enum dp_state state;
  59 
  60         struct mutex lock; /* device lock */
  61         struct work_struct work;
  62         struct typec_altmode *alt;
  63         const struct typec_altmode *port;
  64 };
  65 
  66 static int dp_altmode_notify(struct dp_altmode *dp)
  67 {
  68         u8 state = get_count_order(DP_CONF_GET_PIN_ASSIGN(dp->data.conf));
  69 
  70         return typec_altmode_notify(dp->alt, TYPEC_MODAL_STATE(state),
  71                                    &dp->data);
  72 }
  73 
  74 static int dp_altmode_configure(struct dp_altmode *dp, u8 con)
  75 {
  76         u32 conf = DP_CONF_SIGNALING_DP; /* Only DP signaling supported */
  77         u8 pin_assign = 0;
  78 
  79         switch (con) {
  80         case DP_STATUS_CON_DISABLED:
  81                 return 0;
  82         case DP_STATUS_CON_DFP_D:
  83                 conf |= DP_CONF_UFP_U_AS_DFP_D;
  84                 pin_assign = DP_CAP_UFP_D_PIN_ASSIGN(dp->alt->vdo) &
  85                              DP_CAP_DFP_D_PIN_ASSIGN(dp->port->vdo);
  86                 break;
  87         case DP_STATUS_CON_UFP_D:
  88         case DP_STATUS_CON_BOTH: /* NOTE: First acting as DP source */
  89                 conf |= DP_CONF_UFP_U_AS_UFP_D;
  90                 pin_assign = DP_CAP_DFP_D_PIN_ASSIGN(dp->alt->vdo) &
  91                              DP_CAP_UFP_D_PIN_ASSIGN(dp->port->vdo);
  92                 break;
  93         default:
  94                 break;
  95         }
  96 
  97         /* Determining the initial pin assignment. */
  98         if (!DP_CONF_GET_PIN_ASSIGN(dp->data.conf)) {
  99                 /* Is USB together with DP preferred */
 100                 if (dp->data.status & DP_STATUS_PREFER_MULTI_FUNC &&
 101                     pin_assign & DP_PIN_ASSIGN_MULTI_FUNC_MASK)
 102                         pin_assign &= DP_PIN_ASSIGN_MULTI_FUNC_MASK;
 103                 else if (pin_assign & DP_PIN_ASSIGN_DP_ONLY_MASK)
 104                         pin_assign &= DP_PIN_ASSIGN_DP_ONLY_MASK;
 105 
 106                 if (!pin_assign)
 107                         return -EINVAL;
 108 
 109                 conf |= DP_CONF_SET_PIN_ASSIGN(pin_assign);
 110         }
 111 
 112         dp->data.conf = conf;
 113 
 114         return 0;
 115 }
 116 
 117 static int dp_altmode_status_update(struct dp_altmode *dp)
 118 {
 119         bool configured = !!DP_CONF_GET_PIN_ASSIGN(dp->data.conf);
 120         u8 con = DP_STATUS_CONNECTION(dp->data.status);
 121         int ret = 0;
 122 
 123         if (configured && (dp->data.status & DP_STATUS_SWITCH_TO_USB)) {
 124                 dp->data.conf = 0;
 125                 dp->state = DP_STATE_CONFIGURE;
 126         } else if (dp->data.status & DP_STATUS_EXIT_DP_MODE) {
 127                 dp->state = DP_STATE_EXIT;
 128         } else if (!(con & DP_CONF_CURRENTLY(dp->data.conf))) {
 129                 ret = dp_altmode_configure(dp, con);
 130                 if (!ret)
 131                         dp->state = DP_STATE_CONFIGURE;
 132         }
 133 
 134         return ret;
 135 }
 136 
 137 static int dp_altmode_configured(struct dp_altmode *dp)
 138 {
 139         int ret;
 140 
 141         sysfs_notify(&dp->alt->dev.kobj, "displayport", "configuration");
 142 
 143         if (!dp->data.conf)
 144                 return typec_altmode_notify(dp->alt, TYPEC_STATE_USB,
 145                                             &dp->data);
 146 
 147         ret = dp_altmode_notify(dp);
 148         if (ret)
 149                 return ret;
 150 
 151         sysfs_notify(&dp->alt->dev.kobj, "displayport", "pin_assignment");
 152 
 153         return 0;
 154 }
 155 
 156 static int dp_altmode_configure_vdm(struct dp_altmode *dp, u32 conf)
 157 {
 158         u32 header = DP_HEADER(dp, DP_CMD_CONFIGURE);
 159         int ret;
 160 
 161         ret = typec_altmode_notify(dp->alt, TYPEC_STATE_SAFE, &dp->data);
 162         if (ret) {
 163                 dev_err(&dp->alt->dev,
 164                         "unable to put to connector to safe mode\n");
 165                 return ret;
 166         }
 167 
 168         ret = typec_altmode_vdm(dp->alt, header, &conf, 2);
 169         if (ret) {
 170                 if (DP_CONF_GET_PIN_ASSIGN(dp->data.conf))
 171                         dp_altmode_notify(dp);
 172                 else
 173                         typec_altmode_notify(dp->alt, TYPEC_STATE_USB,
 174                                              &dp->data);
 175         }
 176 
 177         return ret;
 178 }
 179 
 180 static void dp_altmode_work(struct work_struct *work)
 181 {
 182         struct dp_altmode *dp = container_of(work, struct dp_altmode, work);
 183         u32 header;
 184         u32 vdo;
 185         int ret;
 186 
 187         mutex_lock(&dp->lock);
 188 
 189         switch (dp->state) {
 190         case DP_STATE_ENTER:
 191                 ret = typec_altmode_enter(dp->alt);
 192                 if (ret)
 193                         dev_err(&dp->alt->dev, "failed to enter mode\n");
 194                 break;
 195         case DP_STATE_UPDATE:
 196                 header = DP_HEADER(dp, DP_CMD_STATUS_UPDATE);
 197                 vdo = 1;
 198                 ret = typec_altmode_vdm(dp->alt, header, &vdo, 2);
 199                 if (ret)
 200                         dev_err(&dp->alt->dev,
 201                                 "unable to send Status Update command (%d)\n",
 202                                 ret);
 203                 break;
 204         case DP_STATE_CONFIGURE:
 205                 ret = dp_altmode_configure_vdm(dp, dp->data.conf);
 206                 if (ret)
 207                         dev_err(&dp->alt->dev,
 208                                 "unable to send Configure command (%d)\n", ret);
 209                 break;
 210         case DP_STATE_EXIT:
 211                 if (typec_altmode_exit(dp->alt))
 212                         dev_err(&dp->alt->dev, "Exit Mode Failed!\n");
 213                 break;
 214         default:
 215                 break;
 216         }
 217 
 218         dp->state = DP_STATE_IDLE;
 219 
 220         mutex_unlock(&dp->lock);
 221 }
 222 
 223 static void dp_altmode_attention(struct typec_altmode *alt, const u32 vdo)
 224 {
 225         struct dp_altmode *dp = typec_altmode_get_drvdata(alt);
 226         u8 old_state;
 227 
 228         mutex_lock(&dp->lock);
 229 
 230         old_state = dp->state;
 231         dp->data.status = vdo;
 232 
 233         if (old_state != DP_STATE_IDLE)
 234                 dev_warn(&alt->dev, "ATTENTION while processing state %d\n",
 235                          old_state);
 236 
 237         if (dp_altmode_status_update(dp))
 238                 dev_warn(&alt->dev, "%s: status update failed\n", __func__);
 239 
 240         if (dp_altmode_notify(dp))
 241                 dev_err(&alt->dev, "%s: notification failed\n", __func__);
 242 
 243         if (old_state == DP_STATE_IDLE && dp->state != DP_STATE_IDLE)
 244                 schedule_work(&dp->work);
 245 
 246         mutex_unlock(&dp->lock);
 247 }
 248 
 249 static int dp_altmode_vdm(struct typec_altmode *alt,
 250                           const u32 hdr, const u32 *vdo, int count)
 251 {
 252         struct dp_altmode *dp = typec_altmode_get_drvdata(alt);
 253         int cmd_type = PD_VDO_CMDT(hdr);
 254         int cmd = PD_VDO_CMD(hdr);
 255         int ret = 0;
 256 
 257         mutex_lock(&dp->lock);
 258 
 259         if (dp->state != DP_STATE_IDLE) {
 260                 ret = -EBUSY;
 261                 goto err_unlock;
 262         }
 263 
 264         switch (cmd_type) {
 265         case CMDT_RSP_ACK:
 266                 switch (cmd) {
 267                 case CMD_ENTER_MODE:
 268                         dp->state = DP_STATE_UPDATE;
 269                         break;
 270                 case CMD_EXIT_MODE:
 271                         dp->data.status = 0;
 272                         dp->data.conf = 0;
 273                         break;
 274                 case DP_CMD_STATUS_UPDATE:
 275                         dp->data.status = *vdo;
 276                         ret = dp_altmode_status_update(dp);
 277                         break;
 278                 case DP_CMD_CONFIGURE:
 279                         ret = dp_altmode_configured(dp);
 280                         break;
 281                 default:
 282                         break;
 283                 }
 284                 break;
 285         case CMDT_RSP_NAK:
 286                 switch (cmd) {
 287                 case DP_CMD_CONFIGURE:
 288                         dp->data.conf = 0;
 289                         ret = dp_altmode_configured(dp);
 290                         break;
 291                 default:
 292                         break;
 293                 }
 294                 break;
 295         default:
 296                 break;
 297         }
 298 
 299         if (dp->state != DP_STATE_IDLE)
 300                 schedule_work(&dp->work);
 301 
 302 err_unlock:
 303         mutex_unlock(&dp->lock);
 304         return ret;
 305 }
 306 
 307 static int dp_altmode_activate(struct typec_altmode *alt, int activate)
 308 {
 309         return activate ? typec_altmode_enter(alt) : typec_altmode_exit(alt);
 310 }
 311 
 312 static const struct typec_altmode_ops dp_altmode_ops = {
 313         .attention = dp_altmode_attention,
 314         .vdm = dp_altmode_vdm,
 315         .activate = dp_altmode_activate,
 316 };
 317 
 318 static const char * const configurations[] = {
 319         [DP_CONF_USB]   = "USB",
 320         [DP_CONF_DFP_D] = "source",
 321         [DP_CONF_UFP_D] = "sink",
 322 };
 323 
 324 static ssize_t
 325 configuration_store(struct device *dev, struct device_attribute *attr,
 326                     const char *buf, size_t size)
 327 {
 328         struct dp_altmode *dp = dev_get_drvdata(dev);
 329         u32 conf;
 330         u32 cap;
 331         int con;
 332         int ret = 0;
 333 
 334         con = sysfs_match_string(configurations, buf);
 335         if (con < 0)
 336                 return con;
 337 
 338         mutex_lock(&dp->lock);
 339 
 340         if (dp->state != DP_STATE_IDLE) {
 341                 ret = -EBUSY;
 342                 goto err_unlock;
 343         }
 344 
 345         cap = DP_CAP_CAPABILITY(dp->alt->vdo);
 346 
 347         if ((con == DP_CONF_DFP_D && !(cap & DP_CAP_DFP_D)) ||
 348             (con == DP_CONF_UFP_D && !(cap & DP_CAP_UFP_D))) {
 349                 ret = -EINVAL;
 350                 goto err_unlock;
 351         }
 352 
 353         conf = dp->data.conf & ~DP_CONF_DUAL_D;
 354         conf |= con;
 355 
 356         if (dp->alt->active) {
 357                 ret = dp_altmode_configure_vdm(dp, conf);
 358                 if (ret)
 359                         goto err_unlock;
 360         }
 361 
 362         dp->data.conf = conf;
 363 
 364 err_unlock:
 365         mutex_unlock(&dp->lock);
 366 
 367         return ret ? ret : size;
 368 }
 369 
 370 static ssize_t configuration_show(struct device *dev,
 371                                   struct device_attribute *attr, char *buf)
 372 {
 373         struct dp_altmode *dp = dev_get_drvdata(dev);
 374         int len;
 375         u8 cap;
 376         u8 cur;
 377         int i;
 378 
 379         mutex_lock(&dp->lock);
 380 
 381         cap = DP_CAP_CAPABILITY(dp->alt->vdo);
 382         cur = DP_CONF_CURRENTLY(dp->data.conf);
 383 
 384         len = sprintf(buf, "%s ", cur ? "USB" : "[USB]");
 385 
 386         for (i = 1; i < ARRAY_SIZE(configurations); i++) {
 387                 if (i == cur)
 388                         len += sprintf(buf + len, "[%s] ", configurations[i]);
 389                 else if ((i == DP_CONF_DFP_D && cap & DP_CAP_DFP_D) ||
 390                          (i == DP_CONF_UFP_D && cap & DP_CAP_UFP_D))
 391                         len += sprintf(buf + len, "%s ", configurations[i]);
 392         }
 393 
 394         mutex_unlock(&dp->lock);
 395 
 396         buf[len - 1] = '\n';
 397         return len;
 398 }
 399 static DEVICE_ATTR_RW(configuration);
 400 
 401 static const char * const pin_assignments[] = {
 402         [DP_PIN_ASSIGN_A] = "A",
 403         [DP_PIN_ASSIGN_B] = "B",
 404         [DP_PIN_ASSIGN_C] = "C",
 405         [DP_PIN_ASSIGN_D] = "D",
 406         [DP_PIN_ASSIGN_E] = "E",
 407         [DP_PIN_ASSIGN_F] = "F",
 408 };
 409 
 410 static ssize_t
 411 pin_assignment_store(struct device *dev, struct device_attribute *attr,
 412                      const char *buf, size_t size)
 413 {
 414         struct dp_altmode *dp = dev_get_drvdata(dev);
 415         u8 assignments;
 416         u32 conf;
 417         int ret;
 418 
 419         ret = sysfs_match_string(pin_assignments, buf);
 420         if (ret < 0)
 421                 return ret;
 422 
 423         conf = DP_CONF_SET_PIN_ASSIGN(BIT(ret));
 424         ret = 0;
 425 
 426         mutex_lock(&dp->lock);
 427 
 428         if (conf & dp->data.conf)
 429                 goto out_unlock;
 430 
 431         if (dp->state != DP_STATE_IDLE) {
 432                 ret = -EBUSY;
 433                 goto out_unlock;
 434         }
 435 
 436         if (DP_CONF_CURRENTLY(dp->data.conf) == DP_CONF_DFP_D)
 437                 assignments = DP_CAP_UFP_D_PIN_ASSIGN(dp->alt->vdo);
 438         else
 439                 assignments = DP_CAP_DFP_D_PIN_ASSIGN(dp->alt->vdo);
 440 
 441         if (!(DP_CONF_GET_PIN_ASSIGN(conf) & assignments)) {
 442                 ret = -EINVAL;
 443                 goto out_unlock;
 444         }
 445 
 446         conf |= dp->data.conf & ~DP_CONF_PIN_ASSIGNEMENT_MASK;
 447 
 448         /* Only send Configure command if a configuration has been set */
 449         if (dp->alt->active && DP_CONF_CURRENTLY(dp->data.conf)) {
 450                 ret = dp_altmode_configure_vdm(dp, conf);
 451                 if (ret)
 452                         goto out_unlock;
 453         }
 454 
 455         dp->data.conf = conf;
 456 
 457 out_unlock:
 458         mutex_unlock(&dp->lock);
 459 
 460         return ret ? ret : size;
 461 }
 462 
 463 static ssize_t pin_assignment_show(struct device *dev,
 464                                    struct device_attribute *attr, char *buf)
 465 {
 466         struct dp_altmode *dp = dev_get_drvdata(dev);
 467         u8 assignments;
 468         int len = 0;
 469         u8 cur;
 470         int i;
 471 
 472         mutex_lock(&dp->lock);
 473 
 474         cur = get_count_order(DP_CONF_GET_PIN_ASSIGN(dp->data.conf));
 475 
 476         if (DP_CONF_CURRENTLY(dp->data.conf) == DP_CONF_DFP_D)
 477                 assignments = DP_CAP_UFP_D_PIN_ASSIGN(dp->alt->vdo);
 478         else
 479                 assignments = DP_CAP_DFP_D_PIN_ASSIGN(dp->alt->vdo);
 480 
 481         for (i = 0; assignments; assignments >>= 1, i++) {
 482                 if (assignments & 1) {
 483                         if (i == cur)
 484                                 len += sprintf(buf + len, "[%s] ",
 485                                                pin_assignments[i]);
 486                         else
 487                                 len += sprintf(buf + len, "%s ",
 488                                                pin_assignments[i]);
 489                 }
 490         }
 491 
 492         mutex_unlock(&dp->lock);
 493 
 494         buf[len - 1] = '\n';
 495         return len;
 496 }
 497 static DEVICE_ATTR_RW(pin_assignment);
 498 
 499 static struct attribute *dp_altmode_attrs[] = {
 500         &dev_attr_configuration.attr,
 501         &dev_attr_pin_assignment.attr,
 502         NULL
 503 };
 504 
 505 static const struct attribute_group dp_altmode_group = {
 506         .name = "displayport",
 507         .attrs = dp_altmode_attrs,
 508 };
 509 
 510 int dp_altmode_probe(struct typec_altmode *alt)
 511 {
 512         const struct typec_altmode *port = typec_altmode_get_partner(alt);
 513         struct dp_altmode *dp;
 514         int ret;
 515 
 516         /* FIXME: Port can only be DFP_U. */
 517 
 518         /* Make sure we have compatiple pin configurations */
 519         if (!(DP_CAP_DFP_D_PIN_ASSIGN(port->vdo) &
 520               DP_CAP_UFP_D_PIN_ASSIGN(alt->vdo)) &&
 521             !(DP_CAP_UFP_D_PIN_ASSIGN(port->vdo) &
 522               DP_CAP_DFP_D_PIN_ASSIGN(alt->vdo)))
 523                 return -ENODEV;
 524 
 525         ret = sysfs_create_group(&alt->dev.kobj, &dp_altmode_group);
 526         if (ret)
 527                 return ret;
 528 
 529         dp = devm_kzalloc(&alt->dev, sizeof(*dp), GFP_KERNEL);
 530         if (!dp)
 531                 return -ENOMEM;
 532 
 533         INIT_WORK(&dp->work, dp_altmode_work);
 534         mutex_init(&dp->lock);
 535         dp->port = port;
 536         dp->alt = alt;
 537 
 538         alt->desc = "DisplayPort";
 539         alt->ops = &dp_altmode_ops;
 540 
 541         typec_altmode_set_drvdata(alt, dp);
 542 
 543         dp->state = DP_STATE_ENTER;
 544         schedule_work(&dp->work);
 545 
 546         return 0;
 547 }
 548 EXPORT_SYMBOL_GPL(dp_altmode_probe);
 549 
 550 void dp_altmode_remove(struct typec_altmode *alt)
 551 {
 552         struct dp_altmode *dp = typec_altmode_get_drvdata(alt);
 553 
 554         sysfs_remove_group(&alt->dev.kobj, &dp_altmode_group);
 555         cancel_work_sync(&dp->work);
 556 }
 557 EXPORT_SYMBOL_GPL(dp_altmode_remove);
 558 
 559 static const struct typec_device_id dp_typec_id[] = {
 560         { USB_TYPEC_DP_SID, USB_TYPEC_DP_MODE },
 561         { },
 562 };
 563 MODULE_DEVICE_TABLE(typec, dp_typec_id);
 564 
 565 static struct typec_altmode_driver dp_altmode_driver = {
 566         .id_table = dp_typec_id,
 567         .probe = dp_altmode_probe,
 568         .remove = dp_altmode_remove,
 569         .driver = {
 570                 .name = "typec_displayport",
 571                 .owner = THIS_MODULE,
 572         },
 573 };
 574 module_typec_altmode_driver(dp_altmode_driver);
 575 
 576 MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@linux.intel.com>");
 577 MODULE_LICENSE("GPL v2");
 578 MODULE_DESCRIPTION("DisplayPort Alternate Mode");

/* [<][>][^][v][top][bottom][index][help] */