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