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        int svdm_version;
  53        u64 command;
  54        u8 cur = 0;
  55        int ret;
  56
  57        mutex_lock(&dp->con->lock);
  58
  59        if (!dp->override && dp->initialized) {
  60                const struct typec_altmode *p = typec_altmode_get_partner(alt);
  61
  62                dev_warn(&p->dev,
  63                         "firmware doesn't support alternate mode overriding\n");
  64                ret = -EOPNOTSUPP;
  65                goto err_unlock;
  66        }
  67
  68        command = UCSI_GET_CURRENT_CAM | UCSI_CONNECTOR_NUMBER(dp->con->num);
  69        ret = ucsi_send_command(ucsi, command, &cur, sizeof(cur));
  70        if (ret < 0) {
  71                if (ucsi->version > 0x0100)
  72                        goto err_unlock;
  73                cur = 0xff;
  74        }
  75
  76        if (cur != 0xff) {
  77                ret = dp->con->port_altmode[cur] == alt ? 0 : -EBUSY;
  78                goto err_unlock;
  79        }
  80
  81        /*
  82         * We can't send the New CAM command yet to the PPM as it needs the
  83         * configuration value as well. Pretending that we have now entered the
  84         * mode, and letting the alt mode driver continue.
  85         */
  86
  87        svdm_version = typec_altmode_get_svdm_version(alt);
  88        if (svdm_version < 0) {
  89                ret = svdm_version;
  90                goto err_unlock;
  91        }
  92
  93        dp->header = VDO(USB_TYPEC_DP_SID, 1, svdm_version, CMD_ENTER_MODE);
  94        dp->header |= VDO_OPOS(USB_TYPEC_DP_MODE);
  95        dp->header |= VDO_CMDT(CMDT_RSP_ACK);
  96
  97        dp->vdo_data = NULL;
  98        dp->vdo_size = 1;
  99
 100        schedule_work(&dp->work);
 101        ret = 0;
 102err_unlock:
 103        mutex_unlock(&dp->con->lock);
 104
 105        return ret;
 106}
 107
 108static int ucsi_displayport_exit(struct typec_altmode *alt)
 109{
 110        struct ucsi_dp *dp = typec_altmode_get_drvdata(alt);
 111        int svdm_version;
 112        u64 command;
 113        int ret = 0;
 114
 115        mutex_lock(&dp->con->lock);
 116
 117        if (!dp->override) {
 118                const struct typec_altmode *p = typec_altmode_get_partner(alt);
 119
 120                dev_warn(&p->dev,
 121                         "firmware doesn't support alternate mode overriding\n");
 122                ret = -EOPNOTSUPP;
 123                goto out_unlock;
 124        }
 125
 126        command = UCSI_CMD_SET_NEW_CAM(dp->con->num, 0, dp->offset, 0);
 127        ret = ucsi_send_command(dp->con->ucsi, command, NULL, 0);
 128        if (ret < 0)
 129                goto out_unlock;
 130
 131        svdm_version = typec_altmode_get_svdm_version(alt);
 132        if (svdm_version < 0) {
 133                ret = svdm_version;
 134                goto out_unlock;
 135        }
 136
 137        dp->header = VDO(USB_TYPEC_DP_SID, 1, svdm_version, CMD_EXIT_MODE);
 138        dp->header |= VDO_OPOS(USB_TYPEC_DP_MODE);
 139        dp->header |= VDO_CMDT(CMDT_RSP_ACK);
 140
 141        dp->vdo_data = NULL;
 142        dp->vdo_size = 1;
 143
 144        schedule_work(&dp->work);
 145
 146out_unlock:
 147        mutex_unlock(&dp->con->lock);
 148
 149        return ret;
 150}
 151
 152/*
 153 * We do not actually have access to the Status Update VDO, so we have to guess
 154 * things.
 155 */
 156static int ucsi_displayport_status_update(struct ucsi_dp *dp)
 157{
 158        u32 cap = dp->alt->vdo;
 159
 160        dp->data.status = DP_STATUS_ENABLED;
 161
 162        /*
 163         * If pin assignement D is supported, claiming always
 164         * that Multi-function is preferred.
 165         */
 166        if (DP_CAP_CAPABILITY(cap) & DP_CAP_UFP_D) {
 167                dp->data.status |= DP_STATUS_CON_UFP_D;
 168
 169                if (DP_CAP_UFP_D_PIN_ASSIGN(cap) & BIT(DP_PIN_ASSIGN_D))
 170                        dp->data.status |= DP_STATUS_PREFER_MULTI_FUNC;
 171        } else {
 172                dp->data.status |= DP_STATUS_CON_DFP_D;
 173
 174                if (DP_CAP_DFP_D_PIN_ASSIGN(cap) & BIT(DP_PIN_ASSIGN_D))
 175                        dp->data.status |= DP_STATUS_PREFER_MULTI_FUNC;
 176        }
 177
 178        dp->vdo_data = &dp->data.status;
 179        dp->vdo_size = 2;
 180
 181        return 0;
 182}
 183
 184static int ucsi_displayport_configure(struct ucsi_dp *dp)
 185{
 186        u32 pins = DP_CONF_GET_PIN_ASSIGN(dp->data.conf);
 187        u64 command;
 188
 189        if (!dp->override)
 190                return 0;
 191
 192        command = UCSI_CMD_SET_NEW_CAM(dp->con->num, 1, dp->offset, pins);
 193
 194        return ucsi_send_command(dp->con->ucsi, command, NULL, 0);
 195}
 196
 197static int ucsi_displayport_vdm(struct typec_altmode *alt,
 198                                u32 header, const u32 *data, int count)
 199{
 200        struct ucsi_dp *dp = typec_altmode_get_drvdata(alt);
 201        int cmd_type = PD_VDO_CMDT(header);
 202        int cmd = PD_VDO_CMD(header);
 203        int svdm_version;
 204
 205        mutex_lock(&dp->con->lock);
 206
 207        if (!dp->override && dp->initialized) {
 208                const struct typec_altmode *p = typec_altmode_get_partner(alt);
 209
 210                dev_warn(&p->dev,
 211                         "firmware doesn't support alternate mode overriding\n");
 212                mutex_unlock(&dp->con->lock);
 213                return -EOPNOTSUPP;
 214        }
 215
 216        svdm_version = typec_altmode_get_svdm_version(alt);
 217        if (svdm_version < 0) {
 218                mutex_unlock(&dp->con->lock);
 219                return svdm_version;
 220        }
 221
 222        switch (cmd_type) {
 223        case CMDT_INIT:
 224                if (PD_VDO_SVDM_VER(header) < svdm_version) {
 225                        typec_partner_set_svdm_version(dp->con->partner, PD_VDO_SVDM_VER(header));
 226                        svdm_version = PD_VDO_SVDM_VER(header);
 227                }
 228
 229                dp->header = VDO(USB_TYPEC_DP_SID, 1, svdm_version, cmd);
 230                dp->header |= VDO_OPOS(USB_TYPEC_DP_MODE);
 231
 232                switch (cmd) {
 233                case DP_CMD_STATUS_UPDATE:
 234                        if (ucsi_displayport_status_update(dp))
 235                                dp->header |= VDO_CMDT(CMDT_RSP_NAK);
 236                        else
 237                                dp->header |= VDO_CMDT(CMDT_RSP_ACK);
 238                        break;
 239                case DP_CMD_CONFIGURE:
 240                        dp->data.conf = *data;
 241                        if (ucsi_displayport_configure(dp)) {
 242                                dp->header |= VDO_CMDT(CMDT_RSP_NAK);
 243                        } else {
 244                                dp->header |= VDO_CMDT(CMDT_RSP_ACK);
 245                                if (dp->initialized)
 246                                        ucsi_altmode_update_active(dp->con);
 247                                else
 248                                        dp->initialized = true;
 249                        }
 250                        break;
 251                default:
 252                        dp->header |= VDO_CMDT(CMDT_RSP_ACK);
 253                        break;
 254                }
 255
 256                schedule_work(&dp->work);
 257                break;
 258        default:
 259                break;
 260        }
 261
 262        mutex_unlock(&dp->con->lock);
 263
 264        return 0;
 265}
 266
 267static const struct typec_altmode_ops ucsi_displayport_ops = {
 268        .enter = ucsi_displayport_enter,
 269        .exit = ucsi_displayport_exit,
 270        .vdm = ucsi_displayport_vdm,
 271};
 272
 273static void ucsi_displayport_work(struct work_struct *work)
 274{
 275        struct ucsi_dp *dp = container_of(work, struct ucsi_dp, work);
 276        int ret;
 277
 278        mutex_lock(&dp->con->lock);
 279
 280        ret = typec_altmode_vdm(dp->alt, dp->header,
 281                                dp->vdo_data, dp->vdo_size);
 282        if (ret)
 283                dev_err(&dp->alt->dev, "VDM 0x%x failed\n", dp->header);
 284
 285        dp->vdo_data = NULL;
 286        dp->vdo_size = 0;
 287        dp->header = 0;
 288
 289        mutex_unlock(&dp->con->lock);
 290}
 291
 292void ucsi_displayport_remove_partner(struct typec_altmode *alt)
 293{
 294        struct ucsi_dp *dp;
 295
 296        if (!alt)
 297                return;
 298
 299        dp = typec_altmode_get_drvdata(alt);
 300        if (!dp)
 301                return;
 302
 303        dp->data.conf = 0;
 304        dp->data.status = 0;
 305        dp->initialized = false;
 306}
 307
 308struct typec_altmode *ucsi_register_displayport(struct ucsi_connector *con,
 309                                                bool override, int offset,
 310                                                struct typec_altmode_desc *desc)
 311{
 312        u8 all_assignments = BIT(DP_PIN_ASSIGN_C) | BIT(DP_PIN_ASSIGN_D) |
 313                             BIT(DP_PIN_ASSIGN_E);
 314        struct typec_altmode *alt;
 315        struct ucsi_dp *dp;
 316
 317        /* We can't rely on the firmware with the capabilities. */
 318        desc->vdo |= DP_CAP_DP_SIGNALING | DP_CAP_RECEPTACLE;
 319
 320        /* Claiming that we support all pin assignments */
 321        desc->vdo |= all_assignments << 8;
 322        desc->vdo |= all_assignments << 16;
 323
 324        alt = typec_port_register_altmode(con->port, desc);
 325        if (IS_ERR(alt))
 326                return alt;
 327
 328        dp = devm_kzalloc(&alt->dev, sizeof(*dp), GFP_KERNEL);
 329        if (!dp) {
 330                typec_unregister_altmode(alt);
 331                return ERR_PTR(-ENOMEM);
 332        }
 333
 334        INIT_WORK(&dp->work, ucsi_displayport_work);
 335        dp->override = override;
 336        dp->offset = offset;
 337        dp->con = con;
 338        dp->alt = alt;
 339
 340        alt->ops = &ucsi_displayport_ops;
 341        typec_altmode_set_drvdata(alt, dp);
 342
 343        return alt;
 344}
 345