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