FILE
cros_typec_altmode.c
File preview with syntax highlighting.
linux-6.19.14/drivers/platform/chrome/cros_typec_altmode.c
1// SPDX-License-Identifier: GPL-2.0-only 2/* 3 * Alt-mode implementation on ChromeOS EC. 4 * 5 * Copyright 2024 Google LLC 6 * Author: Abhishek Pandit-Subedi <abhishekpandit@chromium.org> 7 */ 8#include "cros_ec_typec.h" 9 10#include <linux/mutex.h> 11#include <linux/workqueue.h> 12#include <linux/usb/typec_dp.h> 13#include <linux/usb/typec_tbt.h> 14#include <linux/usb/pd_vdo.h> 15 16#include "cros_typec_altmode.h" 17 18struct cros_typec_altmode_data { 19 struct work_struct work; 20 struct cros_typec_port *port; 21 struct typec_altmode *alt; 22 bool ap_mode_entry; 23 24 struct mutex lock; 25 u32 header; 26 u32 *vdo_data; 27 u8 vdo_size; 28 29 u16 sid; 30 u8 mode; 31}; 32 33struct cros_typec_dp_data { 34 struct cros_typec_altmode_data adata; 35 struct typec_displayport_data data; 36 bool configured; 37 bool pending_status_update; 38}; 39 40static void cros_typec_altmode_work(struct work_struct *work) 41{ 42 struct cros_typec_altmode_data *data = 43 container_of(work, struct cros_typec_altmode_data, work); 44 45 mutex_lock(&data->lock); 46 47 if (typec_altmode_vdm(data->alt, data->header, data->vdo_data, 48 data->vdo_size)) 49 dev_err(&data->alt->dev, "VDM 0x%x failed\n", data->header); 50 51 data->header = 0; 52 data->vdo_data = NULL; 53 data->vdo_size = 0; 54 55 mutex_unlock(&data->lock); 56} 57 58static int cros_typec_altmode_enter(struct typec_altmode *alt, u32 *vdo) 59{ 60 struct cros_typec_altmode_data *adata = typec_altmode_get_drvdata(alt); 61 struct ec_params_typec_control req = { 62 .port = adata->port->port_num, 63 .command = TYPEC_CONTROL_COMMAND_ENTER_MODE, 64 }; 65 int svdm_version; 66 int ret; 67 68 if (!adata->ap_mode_entry) { 69 dev_warn(&alt->dev, 70 "EC does not support AP driven mode entry\n"); 71 return -EOPNOTSUPP; 72 } 73 74 if (adata->sid == USB_TYPEC_DP_SID) 75 req.mode_to_enter = CROS_EC_ALTMODE_DP; 76 else if (adata->sid == USB_TYPEC_TBT_SID) 77 req.mode_to_enter = CROS_EC_ALTMODE_TBT; 78 else 79 return -EOPNOTSUPP; 80 81 ret = cros_ec_cmd(adata->port->typec_data->ec, 0, EC_CMD_TYPEC_CONTROL, 82 &req, sizeof(req), NULL, 0); 83 if (ret < 0) 84 return ret; 85 86 svdm_version = typec_altmode_get_svdm_version(alt); 87 if (svdm_version < 0) 88 return svdm_version; 89 90 mutex_lock(&adata->lock); 91 92 adata->header = VDO(adata->sid, 1, svdm_version, CMD_ENTER_MODE); 93 adata->header |= VDO_OPOS(adata->mode); 94 adata->header |= VDO_CMDT(CMDT_RSP_ACK); 95 adata->vdo_data = NULL; 96 adata->vdo_size = 1; 97 schedule_work(&adata->work); 98 99 mutex_unlock(&adata->lock); 100 return ret; 101} 102 103static int cros_typec_altmode_exit(struct typec_altmode *alt) 104{ 105 struct cros_typec_altmode_data *adata = typec_altmode_get_drvdata(alt); 106 struct ec_params_typec_control req = { 107 .port = adata->port->port_num, 108 .command = TYPEC_CONTROL_COMMAND_EXIT_MODES, 109 }; 110 int svdm_version; 111 int ret; 112 113 if (!adata->ap_mode_entry) { 114 dev_warn(&alt->dev, 115 "EC does not support AP driven mode exit\n"); 116 return -EOPNOTSUPP; 117 } 118 119 ret = cros_ec_cmd(adata->port->typec_data->ec, 0, EC_CMD_TYPEC_CONTROL, 120 &req, sizeof(req), NULL, 0); 121 122 if (ret < 0) 123 return ret; 124 125 svdm_version = typec_altmode_get_svdm_version(alt); 126 if (svdm_version < 0) 127 return svdm_version; 128 129 mutex_lock(&adata->lock); 130 131 adata->header = VDO(adata->sid, 1, svdm_version, CMD_EXIT_MODE); 132 adata->header |= VDO_OPOS(adata->mode); 133 adata->header |= VDO_CMDT(CMDT_RSP_ACK); 134 adata->vdo_data = NULL; 135 adata->vdo_size = 1; 136 schedule_work(&adata->work); 137 138 mutex_unlock(&adata->lock); 139 return ret; 140} 141 142static int cros_typec_displayport_vdm(struct typec_altmode *alt, u32 header, 143 const u32 *data, int count) 144{ 145 struct cros_typec_dp_data *dp_data = typec_altmode_get_drvdata(alt); 146 struct cros_typec_altmode_data *adata = &dp_data->adata; 147 148 149 int cmd_type = PD_VDO_CMDT(header); 150 int cmd = PD_VDO_CMD(header); 151 int svdm_version; 152 153 svdm_version = typec_altmode_get_svdm_version(alt); 154 if (svdm_version < 0) 155 return svdm_version; 156 157 mutex_lock(&adata->lock); 158 159 switch (cmd_type) { 160 case CMDT_INIT: 161 if (PD_VDO_SVDM_VER(header) < svdm_version) { 162 typec_partner_set_svdm_version(adata->port->partner, 163 PD_VDO_SVDM_VER(header)); 164 svdm_version = PD_VDO_SVDM_VER(header); 165 } 166 167 adata->header = VDO(adata->sid, 1, svdm_version, cmd); 168 adata->header |= VDO_OPOS(adata->mode); 169 170 /* 171 * DP_CMD_CONFIGURE: We can't actually do anything with the 172 * provided VDO yet so just send back an ACK. 173 * 174 * DP_CMD_STATUS_UPDATE: We wait for Mux changes to send 175 * DPStatus Acks. 176 */ 177 switch (cmd) { 178 case DP_CMD_CONFIGURE: 179 dp_data->data.conf = *data; 180 adata->header |= VDO_CMDT(CMDT_RSP_ACK); 181 dp_data->configured = true; 182 schedule_work(&adata->work); 183 break; 184 case DP_CMD_STATUS_UPDATE: 185 dp_data->pending_status_update = true; 186 break; 187 default: 188 adata->header |= VDO_CMDT(CMDT_RSP_ACK); 189 schedule_work(&adata->work); 190 break; 191 } 192 193 break; 194 default: 195 break; 196 } 197 198 mutex_unlock(&adata->lock); 199 return 0; 200} 201 202static int cros_typec_thunderbolt_vdm(struct typec_altmode *alt, u32 header, 203 const u32 *data, int count) 204{ 205 struct cros_typec_altmode_data *adata = typec_altmode_get_drvdata(alt); 206 207 int cmd_type = PD_VDO_CMDT(header); 208 int cmd = PD_VDO_CMD(header); 209 int svdm_version; 210 211 svdm_version = typec_altmode_get_svdm_version(alt); 212 if (svdm_version < 0) 213 return svdm_version; 214 215 mutex_lock(&adata->lock); 216 217 switch (cmd_type) { 218 case CMDT_INIT: 219 if (PD_VDO_SVDM_VER(header) < svdm_version) { 220 typec_partner_set_svdm_version(adata->port->partner, 221 PD_VDO_SVDM_VER(header)); 222 svdm_version = PD_VDO_SVDM_VER(header); 223 } 224 225 adata->header = VDO(adata->sid, 1, svdm_version, cmd); 226 adata->header |= VDO_OPOS(adata->mode); 227 228 switch (cmd) { 229 case CMD_ENTER_MODE: 230 /* Don't respond to the enter mode vdm because it 231 * triggers mux configuration. This is handled directly 232 * by the cros_ec_typec driver so the Thunderbolt driver 233 * doesn't need to be involved. 234 */ 235 break; 236 default: 237 adata->header |= VDO_CMDT(CMDT_RSP_ACK); 238 schedule_work(&adata->work); 239 break; 240 } 241 242 break; 243 default: 244 break; 245 } 246 247 mutex_unlock(&adata->lock); 248 return 0; 249} 250 251 252static int cros_typec_altmode_vdm(struct typec_altmode *alt, u32 header, 253 const u32 *data, int count) 254{ 255 struct cros_typec_altmode_data *adata = typec_altmode_get_drvdata(alt); 256 257 if (!adata->ap_mode_entry) 258 return -EOPNOTSUPP; 259 260 if (adata->sid == USB_TYPEC_DP_SID) 261 return cros_typec_displayport_vdm(alt, header, data, count); 262 263 if (adata->sid == USB_TYPEC_TBT_SID) 264 return cros_typec_thunderbolt_vdm(alt, header, data, count); 265 266 return -EINVAL; 267} 268 269static const struct typec_altmode_ops cros_typec_altmode_ops = { 270 .enter = cros_typec_altmode_enter, 271 .exit = cros_typec_altmode_exit, 272 .vdm = cros_typec_altmode_vdm, 273}; 274 275#if IS_ENABLED(CONFIG_TYPEC_DP_ALTMODE) 276int cros_typec_displayport_status_update(struct typec_altmode *altmode, 277 struct typec_displayport_data *data) 278{ 279 struct cros_typec_dp_data *dp_data = 280 typec_altmode_get_drvdata(altmode); 281 struct cros_typec_altmode_data *adata = &dp_data->adata; 282 283 if (!dp_data->pending_status_update) { 284 dev_dbg(&altmode->dev, 285 "Got DPStatus without a pending request\n"); 286 return 0; 287 } 288 289 if (dp_data->configured && dp_data->data.conf != data->conf) 290 dev_dbg(&altmode->dev, 291 "DP Conf doesn't match. Requested 0x%04x, Actual 0x%04x\n", 292 dp_data->data.conf, data->conf); 293 294 mutex_lock(&adata->lock); 295 296 dp_data->data = *data; 297 dp_data->pending_status_update = false; 298 adata->header |= VDO_CMDT(CMDT_RSP_ACK); 299 adata->vdo_data = &dp_data->data.status; 300 adata->vdo_size = 2; 301 schedule_work(&adata->work); 302 303 mutex_unlock(&adata->lock); 304 305 return 0; 306} 307 308struct typec_altmode * 309cros_typec_register_displayport(struct cros_typec_port *port, 310 struct typec_altmode_desc *desc, 311 bool ap_mode_entry) 312{ 313 struct typec_altmode *alt; 314 struct cros_typec_dp_data *dp_data; 315 struct cros_typec_altmode_data *adata; 316 317 alt = typec_port_register_altmode(port->port, desc); 318 if (IS_ERR(alt)) 319 return alt; 320 321 dp_data = devm_kzalloc(&alt->dev, sizeof(*dp_data), GFP_KERNEL); 322 if (!dp_data) { 323 typec_unregister_altmode(alt); 324 return ERR_PTR(-ENOMEM); 325 } 326 327 adata = &dp_data->adata; 328 INIT_WORK(&adata->work, cros_typec_altmode_work); 329 mutex_init(&adata->lock); 330 adata->alt = alt; 331 adata->port = port; 332 adata->ap_mode_entry = ap_mode_entry; 333 adata->sid = desc->svid; 334 adata->mode = desc->mode; 335 336 typec_altmode_set_ops(alt, &cros_typec_altmode_ops); 337 typec_altmode_set_drvdata(alt, adata); 338 339 return alt; 340} 341#endif 342 343#if IS_ENABLED(CONFIG_TYPEC_TBT_ALTMODE) 344struct typec_altmode * 345cros_typec_register_thunderbolt(struct cros_typec_port *port, 346 struct typec_altmode_desc *desc) 347{ 348 struct typec_altmode *alt; 349 struct cros_typec_altmode_data *adata; 350 351 alt = typec_port_register_altmode(port->port, desc); 352 if (IS_ERR(alt)) 353 return alt; 354 355 adata = devm_kzalloc(&alt->dev, sizeof(*adata), GFP_KERNEL); 356 if (!adata) { 357 typec_unregister_altmode(alt); 358 return ERR_PTR(-ENOMEM); 359 } 360 361 INIT_WORK(&adata->work, cros_typec_altmode_work); 362 adata->alt = alt; 363 adata->port = port; 364 adata->ap_mode_entry = true; 365 adata->sid = desc->svid; 366 adata->mode = desc->mode; 367 368 typec_altmode_set_ops(alt, &cros_typec_altmode_ops); 369 typec_altmode_set_drvdata(alt, adata); 370 371 return alt; 372} 373#endif
~/
Read-only file preview.