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
  17#define DP_HEADER(_dp, cmd)             (VDO((_dp)->alt->svid, 1, cmd) | \
  18                                         VDO_OPOS(USB_TYPEC_DP_MODE))
  19
  20enum {
  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
  47enum dp_state {
  48        DP_STATE_IDLE,
  49        DP_STATE_ENTER,
  50        DP_STATE_UPDATE,
  51        DP_STATE_CONFIGURE,
  52        DP_STATE_EXIT,
  53};
  54
  55struct 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
  66static 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
  74static 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
 117static 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
 137static 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
 156static 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
 180static 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
 223static 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
 249static 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
 302err_unlock:
 303        mutex_unlock(&dp->lock);
 304        return ret;
 305}
 306
 307static int dp_altmode_activate(struct typec_altmode *alt, int activate)
 308{
 309        return activate ? typec_altmode_enter(alt) : typec_altmode_exit(alt);
 310}
 311
 312static 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
 318static const char * const configurations[] = {
 319        [DP_CONF_USB]   = "USB",
 320        [DP_CONF_DFP_D] = "source",
 321        [DP_CONF_UFP_D] = "sink",
 322};
 323
 324static ssize_t
 325configuration_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
 364err_unlock:
 365        mutex_unlock(&dp->lock);
 366
 367        return ret ? ret : size;
 368}
 369
 370static 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}
 399static DEVICE_ATTR_RW(configuration);
 400
 401static 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
 410static ssize_t
 411pin_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
 457out_unlock:
 458        mutex_unlock(&dp->lock);
 459
 460        return ret ? ret : size;
 461}
 462
 463static 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}
 497static DEVICE_ATTR_RW(pin_assignment);
 498
 499static struct attribute *dp_altmode_attrs[] = {
 500        &dev_attr_configuration.attr,
 501        &dev_attr_pin_assignment.attr,
 502        NULL
 503};
 504
 505static const struct attribute_group dp_altmode_group = {
 506        .name = "displayport",
 507        .attrs = dp_altmode_attrs,
 508};
 509
 510int 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}
 548EXPORT_SYMBOL_GPL(dp_altmode_probe);
 549
 550void 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}
 557EXPORT_SYMBOL_GPL(dp_altmode_remove);
 558
 559static const struct typec_device_id dp_typec_id[] = {
 560        { USB_TYPEC_DP_SID, USB_TYPEC_DP_MODE },
 561        { },
 562};
 563MODULE_DEVICE_TABLE(typec, dp_typec_id);
 564
 565static 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};
 574module_typec_altmode_driver(dp_altmode_driver);
 575
 576MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@linux.intel.com>");
 577MODULE_LICENSE("GPL v2");
 578MODULE_DESCRIPTION("DisplayPort Alternate Mode");
 579