FILE
ucsi_huawei_gaokun.c
File preview with syntax highlighting.
linux-6.19.14/drivers/usb/typec/ucsi/ucsi_huawei_gaokun.c
1// SPDX-License-Identifier: GPL-2.0-only 2/* 3 * ucsi-huawei-gaokun - A UCSI driver for HUAWEI Matebook E Go 4 * 5 * Copyright (C) 2024-2025 Pengyu Luo <mitltlatltl@gmail.com> 6 */ 7 8#include <drm/bridge/aux-bridge.h> 9#include <linux/auxiliary_bus.h> 10#include <linux/bitops.h> 11#include <linux/completion.h> 12#include <linux/container_of.h> 13#include <linux/module.h> 14#include <linux/notifier.h> 15#include <linux/of.h> 16#include <linux/platform_data/huawei-gaokun-ec.h> 17#include <linux/string.h> 18#include <linux/usb/pd_vdo.h> 19#include <linux/usb/typec_altmode.h> 20#include <linux/usb/typec_dp.h> 21#include <linux/workqueue_types.h> 22 23#include "ucsi.h" 24 25#define EC_EVENT_UCSI 0x21 26#define EC_EVENT_USB 0x22 27 28#define GAOKUN_CCX_MASK GENMASK(1, 0) 29#define GAOKUN_MUX_MASK GENMASK(3, 2) 30 31#define GAOKUN_DPAM_MASK GENMASK(3, 0) 32#define GAOKUN_HPD_STATE_MASK BIT(4) 33#define GAOKUN_HPD_IRQ_MASK BIT(5) 34 35#define GET_IDX(updt) (ffs(updt) - 1) 36 37#define CCX_TO_ORI(ccx) (++(ccx) % 3) /* convert ccx to enum typec_orientation */ 38 39/* Configuration Channel Extension */ 40enum gaokun_ucsi_ccx { 41 USBC_CCX_NORMAL, 42 USBC_CCX_REVERSE, 43 USBC_CCX_NONE, 44}; 45 46enum gaokun_ucsi_mux { 47 USBC_MUX_NONE, 48 USBC_MUX_USB_2L, 49 USBC_MUX_DP_4L, 50 USBC_MUX_USB_DP, 51}; 52 53/* based on pmic_glink_altmode_pin_assignment */ 54enum gaokun_ucsi_dpam_pan { /* DP Alt Mode Pin Assignments */ 55 USBC_DPAM_PAN_NONE, 56 USBC_DPAM_PAN_A, /* Not supported after USB Type-C Standard v1.0b */ 57 USBC_DPAM_PAN_B, /* Not supported after USB Type-C Standard v1.0b */ 58 USBC_DPAM_PAN_C, /* USBC_DPAM_PAN_C_REVERSE - 6 */ 59 USBC_DPAM_PAN_D, 60 USBC_DPAM_PAN_E, 61 USBC_DPAM_PAN_F, /* Not supported after USB Type-C Standard v1.0b */ 62 USBC_DPAM_PAN_A_REVERSE,/* Not supported after USB Type-C Standard v1.0b */ 63 USBC_DPAM_PAN_B_REVERSE,/* Not supported after USB Type-C Standard v1.0b */ 64 USBC_DPAM_PAN_C_REVERSE, 65 USBC_DPAM_PAN_D_REVERSE, 66 USBC_DPAM_PAN_E_REVERSE, 67 USBC_DPAM_PAN_F_REVERSE,/* Not supported after USB Type-C Standard v1.0b */ 68}; 69 70struct gaokun_ucsi_reg { 71 u8 num_ports; 72 u8 port_updt; 73 u8 port_data[4]; 74 u8 checksum; 75 u8 reserved; 76} __packed; 77 78struct gaokun_ucsi_port { 79 struct completion usb_ack; 80 spinlock_t lock; /* serializing port resource access */ 81 82 struct gaokun_ucsi *ucsi; 83 struct auxiliary_device *bridge; 84 85 int idx; 86 enum gaokun_ucsi_ccx ccx; 87 enum gaokun_ucsi_mux mux; 88 u8 mode; 89 u16 svid; 90 u8 hpd_state; 91 u8 hpd_irq; 92}; 93 94struct gaokun_ucsi { 95 struct gaokun_ec *ec; 96 struct ucsi *ucsi; 97 struct gaokun_ucsi_port *ports; 98 struct device *dev; 99 struct delayed_work work; 100 struct notifier_block nb; 101 u16 version; 102 u8 num_ports; 103}; 104 105/* -------------------------------------------------------------------------- */ 106/* For UCSI */ 107 108static int gaokun_ucsi_read_version(struct ucsi *ucsi, u16 *version) 109{ 110 struct gaokun_ucsi *uec = ucsi_get_drvdata(ucsi); 111 112 *version = uec->version; 113 114 return 0; 115} 116 117static int gaokun_ucsi_read_cci(struct ucsi *ucsi, u32 *cci) 118{ 119 struct gaokun_ucsi *uec = ucsi_get_drvdata(ucsi); 120 u8 buf[GAOKUN_UCSI_READ_SIZE]; 121 int ret; 122 123 ret = gaokun_ec_ucsi_read(uec->ec, buf); 124 if (ret) 125 return ret; 126 127 memcpy(cci, buf, sizeof(*cci)); 128 129 return 0; 130} 131 132static int gaokun_ucsi_read_message_in(struct ucsi *ucsi, 133 void *val, size_t val_len) 134{ 135 struct gaokun_ucsi *uec = ucsi_get_drvdata(ucsi); 136 u8 buf[GAOKUN_UCSI_READ_SIZE]; 137 int ret; 138 139 ret = gaokun_ec_ucsi_read(uec->ec, buf); 140 if (ret) 141 return ret; 142 143 memcpy(val, buf + GAOKUN_UCSI_CCI_SIZE, 144 min(val_len, GAOKUN_UCSI_MSGI_SIZE)); 145 146 return 0; 147} 148 149static int gaokun_ucsi_async_control(struct ucsi *ucsi, u64 command) 150{ 151 struct gaokun_ucsi *uec = ucsi_get_drvdata(ucsi); 152 u8 buf[GAOKUN_UCSI_WRITE_SIZE] = {}; 153 154 memcpy(buf, &command, sizeof(command)); 155 156 return gaokun_ec_ucsi_write(uec->ec, buf); 157} 158 159static void gaokun_ucsi_update_connector(struct ucsi_connector *con) 160{ 161 struct gaokun_ucsi *uec = ucsi_get_drvdata(con->ucsi); 162 163 if (con->num > uec->num_ports) 164 return; 165 166 con->typec_cap.orientation_aware = true; 167} 168 169static void gaokun_set_orientation(struct ucsi_connector *con, 170 struct gaokun_ucsi_port *port) 171{ 172 enum gaokun_ucsi_ccx ccx; 173 unsigned long flags; 174 175 spin_lock_irqsave(&port->lock, flags); 176 ccx = port->ccx; 177 spin_unlock_irqrestore(&port->lock, flags); 178 179 typec_set_orientation(con->port, CCX_TO_ORI(ccx)); 180} 181 182static void gaokun_ucsi_connector_status(struct ucsi_connector *con) 183{ 184 struct gaokun_ucsi *uec = ucsi_get_drvdata(con->ucsi); 185 int idx; 186 187 idx = con->num - 1; 188 if (con->num > uec->num_ports) { 189 dev_warn(uec->dev, "set orientation out of range: con%d\n", idx); 190 return; 191 } 192 193 gaokun_set_orientation(con, &uec->ports[idx]); 194} 195 196const struct ucsi_operations gaokun_ucsi_ops = { 197 .read_version = gaokun_ucsi_read_version, 198 .read_cci = gaokun_ucsi_read_cci, 199 .poll_cci = gaokun_ucsi_read_cci, 200 .read_message_in = gaokun_ucsi_read_message_in, 201 .sync_control = ucsi_sync_control_common, 202 .async_control = gaokun_ucsi_async_control, 203 .update_connector = gaokun_ucsi_update_connector, 204 .connector_status = gaokun_ucsi_connector_status, 205}; 206 207/* -------------------------------------------------------------------------- */ 208/* For Altmode */ 209 210static void gaokun_ucsi_port_update(struct gaokun_ucsi_port *port, 211 const u8 *port_data) 212{ 213 struct gaokun_ucsi *uec = port->ucsi; 214 int offset = port->idx * 2; /* every port has 2 Bytes data */ 215 unsigned long flags; 216 u8 dcc, ddi; 217 218 dcc = port_data[offset]; 219 ddi = port_data[offset + 1]; 220 221 spin_lock_irqsave(&port->lock, flags); 222 223 port->ccx = FIELD_GET(GAOKUN_CCX_MASK, dcc); 224 port->mux = FIELD_GET(GAOKUN_MUX_MASK, dcc); 225 port->mode = FIELD_GET(GAOKUN_DPAM_MASK, ddi); 226 port->hpd_state = FIELD_GET(GAOKUN_HPD_STATE_MASK, ddi); 227 port->hpd_irq = FIELD_GET(GAOKUN_HPD_IRQ_MASK, ddi); 228 229 /* Mode and SVID are unused; keeping them to make things clearer */ 230 switch (port->mode) { 231 case USBC_DPAM_PAN_C: 232 case USBC_DPAM_PAN_C_REVERSE: 233 port->mode = DP_PIN_ASSIGN_C; /* correct it for usb later */ 234 break; 235 case USBC_DPAM_PAN_D: 236 case USBC_DPAM_PAN_D_REVERSE: 237 port->mode = DP_PIN_ASSIGN_D; 238 break; 239 case USBC_DPAM_PAN_E: 240 case USBC_DPAM_PAN_E_REVERSE: 241 port->mode = DP_PIN_ASSIGN_E; 242 break; 243 case USBC_DPAM_PAN_NONE: 244 port->mode = TYPEC_STATE_SAFE; 245 break; 246 default: 247 dev_warn(uec->dev, "unknown mode %d\n", port->mode); 248 break; 249 } 250 251 switch (port->mux) { 252 case USBC_MUX_NONE: 253 port->svid = 0; 254 break; 255 case USBC_MUX_USB_2L: 256 port->svid = USB_SID_PD; 257 port->mode = TYPEC_STATE_USB; /* same as PAN_C, correct it */ 258 break; 259 case USBC_MUX_DP_4L: 260 case USBC_MUX_USB_DP: 261 port->svid = USB_SID_DISPLAYPORT; 262 break; 263 default: 264 dev_warn(uec->dev, "unknown mux state %d\n", port->mux); 265 break; 266 } 267 268 spin_unlock_irqrestore(&port->lock, flags); 269} 270 271static int gaokun_ucsi_refresh(struct gaokun_ucsi *uec) 272{ 273 struct gaokun_ucsi_reg ureg; 274 int ret, idx; 275 276 ret = gaokun_ec_ucsi_get_reg(uec->ec, &ureg); 277 if (ret) 278 return GAOKUN_UCSI_NO_PORT_UPDATE; 279 280 uec->num_ports = ureg.num_ports; 281 idx = GET_IDX(ureg.port_updt); 282 283 if (idx < 0 || idx >= ureg.num_ports) 284 return GAOKUN_UCSI_NO_PORT_UPDATE; 285 286 gaokun_ucsi_port_update(&uec->ports[idx], ureg.port_data); 287 return idx; 288} 289 290static void gaokun_ucsi_handle_altmode(struct gaokun_ucsi_port *port) 291{ 292 struct gaokun_ucsi *uec = port->ucsi; 293 int idx = port->idx; 294 295 if (idx >= uec->ucsi->cap.num_connectors) { 296 dev_warn(uec->dev, "altmode port out of range: %d\n", idx); 297 return; 298 } 299 300 /* UCSI callback .connector_status() have set orientation */ 301 if (port->bridge) 302 drm_aux_hpd_bridge_notify(&port->bridge->dev, 303 port->hpd_state ? 304 connector_status_connected : 305 connector_status_disconnected); 306 307 gaokun_ec_ucsi_pan_ack(uec->ec, port->idx); 308} 309 310static void gaokun_ucsi_altmode_notify_ind(struct gaokun_ucsi *uec) 311{ 312 int idx; 313 314 if (!uec->ucsi->connector) { /* slow to register */ 315 dev_err_ratelimited(uec->dev, "ucsi connector is not initialized yet\n"); 316 return; 317 } 318 319 idx = gaokun_ucsi_refresh(uec); 320 if (idx == GAOKUN_UCSI_NO_PORT_UPDATE) 321 gaokun_ec_ucsi_pan_ack(uec->ec, idx); /* ack directly if no update */ 322 else 323 gaokun_ucsi_handle_altmode(&uec->ports[idx]); 324} 325 326/* 327 * When inserting, 2 UCSI events(connector change) are followed by USB events. 328 * If we received one USB event, that means USB events are not blocked, so we 329 * can complelte for all ports, and we should signal all events. 330 */ 331static void gaokun_ucsi_complete_usb_ack(struct gaokun_ucsi *uec) 332{ 333 struct gaokun_ucsi_port *port; 334 int idx = 0; 335 336 while (idx < uec->num_ports) { 337 port = &uec->ports[idx++]; 338 if (!completion_done(&port->usb_ack)) 339 complete_all(&port->usb_ack); 340 } 341} 342 343/* 344 * USB event is necessary for enabling altmode, the event should follow 345 * UCSI event, if not after timeout(this notify may be disabled somehow), 346 * then force to enable altmode. 347 */ 348static void gaokun_ucsi_handle_no_usb_event(struct gaokun_ucsi *uec, int idx) 349{ 350 struct gaokun_ucsi_port *port; 351 352 port = &uec->ports[idx]; 353 if (!wait_for_completion_timeout(&port->usb_ack, 2 * HZ)) { 354 dev_warn(uec->dev, "No USB EVENT, triggered by UCSI EVENT"); 355 gaokun_ucsi_altmode_notify_ind(uec); 356 } 357} 358 359static int gaokun_ucsi_notify(struct notifier_block *nb, 360 unsigned long action, void *data) 361{ 362 u32 cci; 363 int ret; 364 struct gaokun_ucsi *uec = container_of(nb, struct gaokun_ucsi, nb); 365 366 switch (action) { 367 case EC_EVENT_USB: 368 gaokun_ucsi_complete_usb_ack(uec); 369 gaokun_ucsi_altmode_notify_ind(uec); 370 return NOTIFY_OK; 371 372 case EC_EVENT_UCSI: 373 ret = gaokun_ucsi_read_cci(uec->ucsi, &cci); 374 if (ret) 375 return NOTIFY_DONE; 376 377 ucsi_notify_common(uec->ucsi, cci); 378 if (UCSI_CCI_CONNECTOR(cci)) 379 gaokun_ucsi_handle_no_usb_event(uec, UCSI_CCI_CONNECTOR(cci) - 1); 380 381 return NOTIFY_OK; 382 383 default: 384 return NOTIFY_DONE; 385 } 386} 387 388static int gaokun_ucsi_ports_init(struct gaokun_ucsi *uec) 389{ 390 struct gaokun_ucsi_port *ucsi_port; 391 struct gaokun_ucsi_reg ureg = {}; 392 struct device *dev = uec->dev; 393 struct fwnode_handle *fwnode; 394 int i, ret, num_ports; 395 u32 port; 396 397 gaokun_ec_ucsi_get_reg(uec->ec, &ureg); 398 num_ports = ureg.num_ports; 399 uec->ports = devm_kcalloc(dev, num_ports, sizeof(*uec->ports), 400 GFP_KERNEL); 401 if (!uec->ports) 402 return -ENOMEM; 403 404 for (i = 0; i < num_ports; ++i) { 405 ucsi_port = &uec->ports[i]; 406 ucsi_port->ccx = USBC_CCX_NONE; 407 ucsi_port->idx = i; 408 ucsi_port->ucsi = uec; 409 init_completion(&ucsi_port->usb_ack); 410 spin_lock_init(&ucsi_port->lock); 411 } 412 413 device_for_each_child_node(dev, fwnode) { 414 ret = fwnode_property_read_u32(fwnode, "reg", &port); 415 if (ret < 0) { 416 dev_err(dev, "missing reg property of %pOFn\n", fwnode); 417 fwnode_handle_put(fwnode); 418 return ret; 419 } 420 421 if (port >= num_ports) { 422 dev_warn(dev, "invalid connector number %d, ignoring\n", port); 423 continue; 424 } 425 426 ucsi_port = &uec->ports[port]; 427 ucsi_port->bridge = devm_drm_dp_hpd_bridge_alloc(dev, to_of_node(fwnode)); 428 if (IS_ERR(ucsi_port->bridge)) { 429 fwnode_handle_put(fwnode); 430 return PTR_ERR(ucsi_port->bridge); 431 } 432 } 433 434 for (i = 0; i < num_ports; i++) { 435 if (!uec->ports[i].bridge) 436 continue; 437 438 ret = devm_drm_dp_hpd_bridge_add(dev, uec->ports[i].bridge); 439 if (ret) 440 return ret; 441 } 442 443 return 0; 444} 445 446static void gaokun_ucsi_register_worker(struct work_struct *work) 447{ 448 struct gaokun_ucsi *uec; 449 struct ucsi *ucsi; 450 int ret; 451 452 uec = container_of(work, struct gaokun_ucsi, work.work); 453 ucsi = uec->ucsi; 454 455 ret = gaokun_ec_register_notify(uec->ec, &uec->nb); 456 if (ret) { 457 dev_err_probe(ucsi->dev, ret, "notifier register failed\n"); 458 return; 459 } 460 461 ret = ucsi_register(ucsi); 462 if (ret) 463 dev_err_probe(ucsi->dev, ret, "ucsi register failed\n"); 464} 465 466static int gaokun_ucsi_probe(struct auxiliary_device *adev, 467 const struct auxiliary_device_id *id) 468{ 469 struct gaokun_ec *ec = adev->dev.platform_data; 470 struct device *dev = &adev->dev; 471 struct gaokun_ucsi *uec; 472 int ret; 473 474 uec = devm_kzalloc(dev, sizeof(*uec), GFP_KERNEL); 475 if (!uec) 476 return -ENOMEM; 477 478 uec->ec = ec; 479 uec->dev = dev; 480 uec->version = UCSI_VERSION_1_0; 481 uec->nb.notifier_call = gaokun_ucsi_notify; 482 483 INIT_DELAYED_WORK(&uec->work, gaokun_ucsi_register_worker); 484 485 ret = gaokun_ucsi_ports_init(uec); 486 if (ret) 487 return ret; 488 489 uec->ucsi = ucsi_create(dev, &gaokun_ucsi_ops); 490 if (IS_ERR(uec->ucsi)) 491 return PTR_ERR(uec->ucsi); 492 493 ucsi_set_drvdata(uec->ucsi, uec); 494 auxiliary_set_drvdata(adev, uec); 495 496 /* EC can't handle UCSI properly in the early stage */ 497 schedule_delayed_work(&uec->work, 3 * HZ); 498 499 return 0; 500} 501 502static void gaokun_ucsi_remove(struct auxiliary_device *adev) 503{ 504 struct gaokun_ucsi *uec = auxiliary_get_drvdata(adev); 505 506 disable_delayed_work_sync(&uec->work); 507 gaokun_ec_unregister_notify(uec->ec, &uec->nb); 508 ucsi_unregister(uec->ucsi); 509 ucsi_destroy(uec->ucsi); 510} 511 512static const struct auxiliary_device_id gaokun_ucsi_id_table[] = { 513 { .name = GAOKUN_MOD_NAME "." GAOKUN_DEV_UCSI, }, 514 {} 515}; 516MODULE_DEVICE_TABLE(auxiliary, gaokun_ucsi_id_table); 517 518static struct auxiliary_driver gaokun_ucsi_driver = { 519 .name = GAOKUN_DEV_UCSI, 520 .id_table = gaokun_ucsi_id_table, 521 .probe = gaokun_ucsi_probe, 522 .remove = gaokun_ucsi_remove, 523}; 524 525module_auxiliary_driver(gaokun_ucsi_driver); 526 527MODULE_DESCRIPTION("HUAWEI Matebook E Go UCSI driver"); 528MODULE_LICENSE("GPL");
~/
Read-only file preview.