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 *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; 95 err_unlock: 96 mutex_unlock(&dp->con->lock); 97 98 return ret; 99 } 100 101 static 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 132 out_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 */ 142 static 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 170 static 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 183 static 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 241 static 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 247 static 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 266 void 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 dp->data.conf = 0; 275 dp->data.status = 0; 276 dp->initialized = false; 277 } 278 279 struct typec_altmode *ucsi_register_displayport(struct ucsi_connector *con, 280 bool override, int offset, 281 struct typec_altmode_desc *desc) 282 { 283 u8 all_assignments = BIT(DP_PIN_ASSIGN_C) | BIT(DP_PIN_ASSIGN_D) | 284 BIT(DP_PIN_ASSIGN_E); 285 struct typec_altmode *alt; 286 struct ucsi_dp *dp; 287 288 /* We can't rely on the firmware with the capabilities. */ 289 desc->vdo |= DP_CAP_DP_SIGNALING | DP_CAP_RECEPTACLE; 290 291 /* Claiming that we support all pin assignments */ 292 desc->vdo |= all_assignments << 8; 293 desc->vdo |= all_assignments << 16; 294 295 alt = typec_port_register_altmode(con->port, desc); 296 if (IS_ERR(alt)) 297 return alt; 298 299 dp = devm_kzalloc(&alt->dev, sizeof(*dp), GFP_KERNEL); 300 if (!dp) { 301 typec_unregister_altmode(alt); 302 return ERR_PTR(-ENOMEM); 303 } 304 305 INIT_WORK(&dp->work, ucsi_displayport_work); 306 dp->override = override; 307 dp->offset = offset; 308 dp->con = con; 309 dp->alt = alt; 310 311 alt->ops = &ucsi_displayport_ops; 312 typec_altmode_set_drvdata(alt, dp); 313 314 return alt; 315 } 316