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