linux/drivers/usb/typec/ucsi/displayport.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * UCSI DisplayPort Alternate Mode Support
   4 *
   5 * Copyright (C) 2018, Intel Corporation
   6 * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
   7 */
   8
   9#include <linux/usb/typec_dp.h>
  10#include <linux/usb/pd_vdo.h>
  11
  12#include "ucsi.h"
  13
  14#define UCSI_CMD_SET_NEW_CAM(_con_num_, _enter_, _cam_, _am_)           \
  15         (UCSI_SET_NEW_CAM | ((_con_num_) << 16) | ((_enter_) << 23) |  \
  16          ((_cam_) << 24) | ((u64)(_am_) << 32))
  17
  18struct ucsi_dp {
  19        struct typec_displayport_data data;
  20        struct ucsi_connector *con;
  21        struct typec_altmode *alt;
  22        struct work_struct work;
  23        int offset;
  24
  25        bool override;
  26        bool initialized;
  27
  28        u32 header;
  29        u32 *vdo_data;
  30        u8 vdo_size;
  31};
  32
  33/*
  34 * Note. Alternate mode control is optional feature in UCSI. It means that even
  35 * if the system supports alternate modes, the OS may not be aware of them.
  36 *
  37 * In most cases however, the OS will be able to see the supported alternate
  38 * modes, but it may still not be able to configure them, not even enter or exit
  39 * them. That is because UCSI defines alt mode details and alt mode "overriding"
  40 * as separate options.
  41 *
  42 * In case alt mode details are supported, but overriding is not, the driver
  43 * will still display the supported pin assignments and configuration, but any
  44 * changes the user attempts to do will lead into failure with return value of
  45 * -EOPNOTSUPP.
  46 */
  47
  48static int ucsi_displayport_enter(struct typec_altmode *alt, u32 *vdo)
  49{
  50        struct ucsi_dp *dp = typec_altmode_get_drvdata(alt);
  51        struct ucsi *ucsi = dp->con->ucsi;
  52        u64 command;
  53        u8 cur = 0;
  54        int ret;
  55
  56        mutex_lock(&dp->con->lock);
  57
  58        if (!dp->override && dp->initialized) {
  59                const struct typec_altmode *p = typec_altmode_get_partner(alt);
  60
  61                dev_warn(&p->dev,
  62                         "firmware doesn't support alternate mode overriding\n");
  63                ret = -EOPNOTSUPP;
  64                goto err_unlock;
  65        }
  66
  67        command = UCSI_GET_CURRENT_CAM | UCSI_CONNECTOR_NUMBER(dp->con->num);
  68        ret = ucsi_send_command(ucsi, command, &cur, sizeof(cur));
  69        if (ret < 0) {
  70                if (ucsi->version > 0x0100)
  71                        goto err_unlock;
  72                cur = 0xff;
  73        }
  74
  75        if (cur != 0xff) {
  76                ret = dp->con->port_altmode[cur] == alt ? 0 : -EBUSY;
  77                goto err_unlock;
  78        }
  79
  80        /*
  81         * We can't send the New CAM command yet to the PPM as it needs the
  82         * configuration value as well. Pretending that we have now entered the
  83         * mode, and letting the alt mode driver continue.
  84         */
  85
  86        dp->header = VDO(USB_TYPEC_DP_SID, 1, CMD_ENTER_MODE);
  87        dp->header |= VDO_OPOS(USB_TYPEC_DP_MODE);
  88        dp->header |= VDO_CMDT(CMDT_RSP_ACK);
  89
  90        dp->vdo_data = NULL;
  91        dp->vdo_size = 1;
  92
  93        schedule_work(&dp->work);
  94        ret = 0;
  95err_unlock:
  96        mutex_unlock(&dp->con->lock);
  97
  98        return ret;
  99}
 100
 101static int ucsi_displayport_exit(struct typec_altmode *alt)
 102{
 103        struct ucsi_dp *dp = typec_altmode_get_drvdata(alt);
 104        u64 command;
 105        int ret = 0;
 106
 107        mutex_lock(&dp->con->lock);
 108
 109        if (!dp->override) {
 110                const struct typec_altmode *p = typec_altmode_get_partner(alt);
 111
 112                dev_warn(&p->dev,
 113                         "firmware doesn't support alternate mode overriding\n");
 114                ret = -EOPNOTSUPP;
 115                goto out_unlock;
 116        }
 117
 118        command = UCSI_CMD_SET_NEW_CAM(dp->con->num, 0, dp->offset, 0);
 119        ret = ucsi_send_command(dp->con->ucsi, command, NULL, 0);
 120        if (ret < 0)
 121                goto out_unlock;
 122
 123        dp->header = VDO(USB_TYPEC_DP_SID, 1, CMD_EXIT_MODE);
 124        dp->header |= VDO_OPOS(USB_TYPEC_DP_MODE);
 125        dp->header |= VDO_CMDT(CMDT_RSP_ACK);
 126
 127        dp->vdo_data = NULL;
 128        dp->vdo_size = 1;
 129
 130        schedule_work(&dp->work);
 131
 132out_unlock:
 133        mutex_unlock(&dp->con->lock);
 134
 135        return ret;
 136}
 137
 138/*
 139 * We do not actually have access to the Status Update VDO, so we have to guess
 140 * things.
 141 */
 142static int ucsi_displayport_status_update(struct ucsi_dp *dp)
 143{
 144        u32 cap = dp->alt->vdo;
 145
 146        dp->data.status = DP_STATUS_ENABLED;
 147
 148        /*
 149         * If pin assignement D is supported, claiming always
 150         * that Multi-function is preferred.
 151         */
 152        if (DP_CAP_CAPABILITY(cap) & DP_CAP_UFP_D) {
 153                dp->data.status |= DP_STATUS_CON_UFP_D;
 154
 155                if (DP_CAP_UFP_D_PIN_ASSIGN(cap) & BIT(DP_PIN_ASSIGN_D))
 156                        dp->data.status |= DP_STATUS_PREFER_MULTI_FUNC;
 157        } else {
 158                dp->data.status |= DP_STATUS_CON_DFP_D;
 159
 160                if (DP_CAP_DFP_D_PIN_ASSIGN(cap) & BIT(DP_PIN_ASSIGN_D))
 161                        dp->data.status |= DP_STATUS_PREFER_MULTI_FUNC;
 162        }
 163
 164        dp->vdo_data = &dp->data.status;
 165        dp->vdo_size = 2;
 166
 167        return 0;
 168}
 169
 170static int ucsi_displayport_configure(struct ucsi_dp *dp)
 171{
 172        u32 pins = DP_CONF_GET_PIN_ASSIGN(dp->data.conf);
 173        u64 command;
 174
 175        if (!dp->override)
 176                return 0;
 177
 178        command = UCSI_CMD_SET_NEW_CAM(dp->con->num, 1, dp->offset, pins);
 179
 180        return ucsi_send_command(dp->con->ucsi, command, NULL, 0);
 181}
 182
 183static int ucsi_displayport_vdm(struct typec_altmode *alt,
 184                                u32 header, const u32 *data, int count)
 185{
 186        struct ucsi_dp *dp = typec_altmode_get_drvdata(alt);
 187        int cmd_type = PD_VDO_CMDT(header);
 188        int cmd = PD_VDO_CMD(header);
 189
 190        mutex_lock(&dp->con->lock);
 191
 192        if (!dp->override && dp->initialized) {
 193                const struct typec_altmode *p = typec_altmode_get_partner(alt);
 194
 195                dev_warn(&p->dev,
 196                         "firmware doesn't support alternate mode overriding\n");
 197                mutex_unlock(&dp->con->lock);
 198                return -EOPNOTSUPP;
 199        }
 200
 201        switch (cmd_type) {
 202        case CMDT_INIT:
 203                dp->header = VDO(USB_TYPEC_DP_SID, 1, cmd);
 204                dp->header |= VDO_OPOS(USB_TYPEC_DP_MODE);
 205
 206                switch (cmd) {
 207                case DP_CMD_STATUS_UPDATE:
 208                        if (ucsi_displayport_status_update(dp))
 209                                dp->header |= VDO_CMDT(CMDT_RSP_NAK);
 210                        else
 211                                dp->header |= VDO_CMDT(CMDT_RSP_ACK);
 212                        break;
 213                case DP_CMD_CONFIGURE:
 214                        dp->data.conf = *data;
 215                        if (ucsi_displayport_configure(dp)) {
 216                                dp->header |= VDO_CMDT(CMDT_RSP_NAK);
 217                        } else {
 218                                dp->header |= VDO_CMDT(CMDT_RSP_ACK);
 219                                if (dp->initialized)
 220                                        ucsi_altmode_update_active(dp->con);
 221                                else
 222                                        dp->initialized = true;
 223                        }
 224                        break;
 225                default:
 226                        dp->header |= VDO_CMDT(CMDT_RSP_ACK);
 227                        break;
 228                }
 229
 230                schedule_work(&dp->work);
 231                break;
 232        default:
 233                break;
 234        }
 235
 236        mutex_unlock(&dp->con->lock);
 237
 238        return 0;
 239}
 240
 241static const struct typec_altmode_ops ucsi_displayport_ops = {
 242        .enter = ucsi_displayport_enter,
 243        .exit = ucsi_displayport_exit,
 244        .vdm = ucsi_displayport_vdm,
 245};
 246
 247static void ucsi_displayport_work(struct work_struct *work)
 248{
 249        struct ucsi_dp *dp = container_of(work, struct ucsi_dp, work);
 250        int ret;
 251
 252        mutex_lock(&dp->con->lock);
 253
 254        ret = typec_altmode_vdm(dp->alt, dp->header,
 255                                dp->vdo_data, dp->vdo_size);
 256        if (ret)
 257                dev_err(&dp->alt->dev, "VDM 0x%x failed\n", dp->header);
 258
 259        dp->vdo_data = NULL;
 260        dp->vdo_size = 0;
 261        dp->header = 0;
 262
 263        mutex_unlock(&dp->con->lock);
 264}
 265
 266void ucsi_displayport_remove_partner(struct typec_altmode *alt)
 267{
 268        struct ucsi_dp *dp;
 269
 270        if (!alt)
 271                return;
 272
 273        dp = typec_altmode_get_drvdata(alt);
 274        if (!dp)
 275                return;
 276
 277        dp->data.conf = 0;
 278        dp->data.status = 0;
 279        dp->initialized = false;
 280}
 281
 282struct typec_altmode *ucsi_register_displayport(struct ucsi_connector *con,
 283                                                bool override, int offset,
 284                                                struct typec_altmode_desc *desc)
 285{
 286        u8 all_assignments = BIT(DP_PIN_ASSIGN_C) | BIT(DP_PIN_ASSIGN_D) |
 287                             BIT(DP_PIN_ASSIGN_E);
 288        struct typec_altmode *alt;
 289        struct ucsi_dp *dp;
 290
 291        /* We can't rely on the firmware with the capabilities. */
 292        desc->vdo |= DP_CAP_DP_SIGNALING | DP_CAP_RECEPTACLE;
 293
 294        /* Claiming that we support all pin assignments */
 295        desc->vdo |= all_assignments << 8;
 296        desc->vdo |= all_assignments << 16;
 297
 298        alt = typec_port_register_altmode(con->port, desc);
 299        if (IS_ERR(alt))
 300                return alt;
 301
 302        dp = devm_kzalloc(&alt->dev, sizeof(*dp), GFP_KERNEL);
 303        if (!dp) {
 304                typec_unregister_altmode(alt);
 305                return ERR_PTR(-ENOMEM);
 306        }
 307
 308        INIT_WORK(&dp->work, ucsi_displayport_work);
 309        dp->override = override;
 310        dp->offset = offset;
 311        dp->con = con;
 312        dp->alt = alt;
 313
 314        alt->ops = &ucsi_displayport_ops;
 315        typec_altmode_set_drvdata(alt, dp);
 316
 317        return alt;
 318}
 319