root/drivers/usb/typec/ucsi/displayport.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. ucsi_displayport_enter
  2. ucsi_displayport_exit
  3. ucsi_displayport_status_update
  4. ucsi_displayport_configure
  5. ucsi_displayport_vdm
  6. ucsi_displayport_work
  7. ucsi_displayport_remove_partner
  8. ucsi_register_displayport

   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 
  18 struct 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 
  48 static 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 
 103 static 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 
 134 out_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  */
 144 static 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 
 172 static 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 
 185 static 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 
 243 static 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 
 249 static 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 
 268 void 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         if (!dp)
 277                 return;
 278 
 279         dp->data.conf = 0;
 280         dp->data.status = 0;
 281         dp->initialized = false;
 282 }
 283 
 284 struct typec_altmode *ucsi_register_displayport(struct ucsi_connector *con,
 285                                                 bool override, int offset,
 286                                                 struct typec_altmode_desc *desc)
 287 {
 288         u8 all_assignments = BIT(DP_PIN_ASSIGN_C) | BIT(DP_PIN_ASSIGN_D) |
 289                              BIT(DP_PIN_ASSIGN_E);
 290         struct typec_altmode *alt;
 291         struct ucsi_dp *dp;
 292 
 293         mutex_lock(&con->lock);
 294 
 295         /* We can't rely on the firmware with the capabilities. */
 296         desc->vdo |= DP_CAP_DP_SIGNALING | DP_CAP_RECEPTACLE;
 297 
 298         /* Claiming that we support all pin assignments */
 299         desc->vdo |= all_assignments << 8;
 300         desc->vdo |= all_assignments << 16;
 301 
 302         alt = typec_port_register_altmode(con->port, desc);
 303         if (IS_ERR(alt)) {
 304                 mutex_unlock(&con->lock);
 305                 return alt;
 306         }
 307 
 308         dp = devm_kzalloc(&alt->dev, sizeof(*dp), GFP_KERNEL);
 309         if (!dp) {
 310                 typec_unregister_altmode(alt);
 311                 mutex_unlock(&con->lock);
 312                 return ERR_PTR(-ENOMEM);
 313         }
 314 
 315         INIT_WORK(&dp->work, ucsi_displayport_work);
 316         dp->override = override;
 317         dp->offset = offset;
 318         dp->con = con;
 319         dp->alt = alt;
 320 
 321         alt->ops = &ucsi_displayport_ops;
 322         typec_altmode_set_drvdata(alt, dp);
 323 
 324         mutex_unlock(&con->lock);
 325 
 326         return alt;
 327 }

/* [<][>][^][v][top][bottom][index][help] */