diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index f253310a92b4..516d00affa82 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -1061,6 +1061,15 @@ static int ucsi_register_port(struct ucsi *ucsi, int index) con->num = index + 1; con->ucsi = ucsi; + cap->fwnode = ucsi_find_fwnode(con); + con->usb_role_sw = fwnode_usb_role_switch_get(cap->fwnode); + if (IS_ERR(con->usb_role_sw)) { + dev_err(ucsi->dev, "con%d: failed to get usb role switch\n", + con->num); + return PTR_ERR(con->usb_role_sw); + } + + /* Delay other interactions with the con until registration is complete */ mutex_lock(&con->lock); @@ -1096,7 +1105,6 @@ static int ucsi_register_port(struct ucsi *ucsi, int index) if (con->cap.op_mode & UCSI_CONCAP_OPMODE_DEBUG_ACCESSORY) *accessory = TYPEC_ACCESSORY_DEBUG; - cap->fwnode = ucsi_find_fwnode(con); cap->driver_data = con; cap->ops = &ucsi_ops; @@ -1154,13 +1162,6 @@ static int ucsi_register_port(struct ucsi *ucsi, int index) ucsi_port_psy_changed(con); } - con->usb_role_sw = fwnode_usb_role_switch_get(cap->fwnode); - if (IS_ERR(con->usb_role_sw)) { - dev_err(ucsi->dev, "con%d: failed to get usb role switch\n", - con->num); - con->usb_role_sw = NULL; - } - /* Only notify USB controller if partner supports USB data */ if (!(UCSI_CONSTAT_PARTNER_FLAGS(con->status.flags) & UCSI_CONSTAT_PARTNER_FLAG_USB)) u_role = USB_ROLE_NONE; @@ -1273,12 +1274,21 @@ err: static void ucsi_init_work(struct work_struct *work) { - struct ucsi *ucsi = container_of(work, struct ucsi, work); + struct ucsi_android *aucsi = container_of(work, + struct ucsi_android, work.work); int ret; - ret = ucsi_init(ucsi); + ret = ucsi_init(&aucsi->ucsi); if (ret) - dev_err(ucsi->dev, "PPM init failed (%d)\n", ret); + dev_err(aucsi->ucsi.dev, "PPM init failed (%d)\n", ret); + + if (ret == -EPROBE_DEFER) { + if (aucsi->work_count++ > UCSI_ROLE_SWITCH_WAIT_COUNT) + return; + + queue_delayed_work(system_long_wq, &aucsi->work, + UCSI_ROLE_SWITCH_INTERVAL); + } } /** @@ -1310,15 +1320,17 @@ EXPORT_SYMBOL_GPL(ucsi_set_drvdata); struct ucsi *ucsi_create(struct device *dev, const struct ucsi_operations *ops) { struct ucsi *ucsi; + struct ucsi_android *aucsi; if (!ops || !ops->read || !ops->sync_write || !ops->async_write) return ERR_PTR(-EINVAL); - ucsi = kzalloc(sizeof(*ucsi), GFP_KERNEL); - if (!ucsi) + aucsi = kzalloc(sizeof(*aucsi), GFP_KERNEL); + if (!aucsi) return ERR_PTR(-ENOMEM); - INIT_WORK(&ucsi->work, ucsi_init_work); + ucsi = &aucsi->ucsi; + INIT_DELAYED_WORK(&aucsi->work, ucsi_init_work); mutex_init(&ucsi->ppm_lock); ucsi->dev = dev; ucsi->ops = ops; @@ -1333,7 +1345,9 @@ EXPORT_SYMBOL_GPL(ucsi_create); */ void ucsi_destroy(struct ucsi *ucsi) { - kfree(ucsi); + struct ucsi_android *aucsi = container_of(ucsi, + struct ucsi_android, ucsi); + kfree(aucsi); } EXPORT_SYMBOL_GPL(ucsi_destroy); @@ -1343,6 +1357,8 @@ EXPORT_SYMBOL_GPL(ucsi_destroy); */ int ucsi_register(struct ucsi *ucsi) { + struct ucsi_android *aucsi = container_of(ucsi, + struct ucsi_android, ucsi); int ret; ret = ucsi->ops->read(ucsi, UCSI_VERSION, &ucsi->version, @@ -1353,7 +1369,7 @@ int ucsi_register(struct ucsi *ucsi) if (!ucsi->version) return -ENODEV; - queue_work(system_long_wq, &ucsi->work); + queue_delayed_work(system_long_wq, &aucsi->work, 0); return 0; } @@ -1367,11 +1383,13 @@ EXPORT_SYMBOL_GPL(ucsi_register); */ void ucsi_unregister(struct ucsi *ucsi) { + struct ucsi_android *aucsi = container_of(ucsi, + struct ucsi_android, ucsi); u64 cmd = UCSI_SET_NOTIFICATION_ENABLE; int i; /* Make sure that we are not in the middle of driver initialization */ - cancel_work_sync(&ucsi->work); + cancel_delayed_work_sync(&aucsi->work); /* Disable notifications */ ucsi->ops->async_write(ucsi, UCSI_CONTROL, &cmd, sizeof(cmd)); diff --git a/drivers/usb/typec/ucsi/ucsi.h b/drivers/usb/typec/ucsi/ucsi.h index b3450d87f324..d366ab2add37 100644 --- a/drivers/usb/typec/ucsi/ucsi.h +++ b/drivers/usb/typec/ucsi/ucsi.h @@ -289,6 +289,9 @@ struct ucsi { struct ucsi_connector *connector; struct work_struct work; +#define UCSI_ROLE_SWITCH_RETRY_PER_HZ 10 +#define UCSI_ROLE_SWITCH_INTERVAL (HZ / UCSI_ROLE_SWITCH_RETRY_PER_HZ) +#define UCSI_ROLE_SWITCH_WAIT_COUNT (10 * UCSI_ROLE_SWITCH_RETRY_PER_HZ) /* PPM Communication lock */ struct mutex ppm_lock; @@ -304,6 +307,25 @@ struct ucsi { #define EVENT_PROCESSING 3 }; +/** + * struct ucsi_android - contains parameters without modifying the format + * of ucsi struct. + * @ucsi: contains the ucsi reference. + * @work: work structure for queuing ucsi_init_work. + * @work_count: to track the wait count(MAX= UCSI_ROLE_SWITCH_WAIT_COUNT). + * + * Required to address Bug: 260537721 + * If the role switch module probes late the + * fwnode_usb_role_switch_get() will fail with -EPROBE_DEFER. + * To recover from this, restart the ucsi_init_work + * to find the fwnode again using a delayed workqueue. + */ +struct ucsi_android { + struct ucsi ucsi; + struct delayed_work work; + int work_count; +}; + #define UCSI_MAX_SVID 5 #define UCSI_MAX_ALTMODES (UCSI_MAX_SVID * 6)