* [PATCH v6 1/2] usb: typec: ucsi: add a common function ucsi_unregister_connectors()
2022-04-25 9:00 [PATCH v6 0/2] usb: typec: ucsi: allow retry to find role switch Linyu Yuan
@ 2022-04-25 9:00 ` Linyu Yuan
2022-04-25 9:00 ` [PATCH v6 2/2] usb: typec: ucsi: Wait for the USB role switches Linyu Yuan
1 sibling, 0 replies; 3+ messages in thread
From: Linyu Yuan @ 2022-04-25 9:00 UTC (permalink / raw)
To: Heikki Krogerus, Greg Kroah-Hartman; +Cc: linux-usb, Jack Pham, Linyu Yuan
In error path of ucsi_init(), it will unregister all valid ucsi connectors,
and similar operation also happen in ucsi_unregister(),
add a common function ucsi_unregister_connectors() for two places.
Also in ucsi_init(), it allocate number of (ucsi->cap.num_connectors + 1)
connectors, there is one extra as the ending,
ucsi_unregister_connectors() is safe to unregister all ucsi connectors
according ucsi->cap.num_connectors,
remove the extra one connector to save memory.
Signed-off-by: Linyu Yuan <quic_linyyuan@quicinc.com>
---
v2: improve ucsi_connector_clean(), check total number of connector.
v3: rename to ucsi_unregister_connectors(), suggest by maintainer
v4: merge patch#1 in V3, fix a typo samiliar -> similar in commit description
v5: no change
v6: merge patch#2 in v5 to this one,
remove con->port = NULL; and change break condition
in ucsi_unregister_connectors().
drivers/usb/typec/ucsi/ucsi.c | 53 +++++++++++++++++++++++--------------------
1 file changed, 29 insertions(+), 24 deletions(-)
diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c
index f0c2fa1..981b561 100644
--- a/drivers/usb/typec/ucsi/ucsi.c
+++ b/drivers/usb/typec/ucsi/ucsi.c
@@ -1186,6 +1186,32 @@ static int ucsi_register_port(struct ucsi *ucsi, int index)
return ret;
}
+static void ucsi_unregister_connectors(struct ucsi *ucsi)
+{
+ struct ucsi_connector *con;
+ int i;
+
+ if (!ucsi->connector)
+ return;
+
+ for (i = 0; i < ucsi->cap.num_connectors; i++) {
+ con = &ucsi->connector[i];
+ if (IS_ERR_OR_NULL(con->port))
+ break;
+
+ cancel_work_sync(&con->work);
+ ucsi_unregister_partner(con);
+ ucsi_unregister_altmodes(con, UCSI_RECIPIENT_CON);
+ ucsi_unregister_port_psy(con);
+ if (con->wq)
+ destroy_workqueue(con->wq);
+ typec_unregister_port(con->port);
+ }
+
+ kfree(ucsi->connector);
+ ucsi->connector = NULL;
+}
+
/**
* ucsi_init - Initialize UCSI interface
* @ucsi: UCSI to be initialized
@@ -1194,7 +1220,6 @@ static int ucsi_register_port(struct ucsi *ucsi, int index)
*/
static int ucsi_init(struct ucsi *ucsi)
{
- struct ucsi_connector *con;
u64 command;
int ret;
int i;
@@ -1225,7 +1250,7 @@ static int ucsi_init(struct ucsi *ucsi)
}
/* Allocate the connectors. Released in ucsi_unregister() */
- ucsi->connector = kcalloc(ucsi->cap.num_connectors + 1,
+ ucsi->connector = kcalloc(ucsi->cap.num_connectors,
sizeof(*ucsi->connector), GFP_KERNEL);
if (!ucsi->connector) {
ret = -ENOMEM;
@@ -1249,15 +1274,7 @@ static int ucsi_init(struct ucsi *ucsi)
return 0;
err_unregister:
- for (con = ucsi->connector; con->port; con++) {
- ucsi_unregister_partner(con);
- ucsi_unregister_altmodes(con, UCSI_RECIPIENT_CON);
- ucsi_unregister_port_psy(con);
- if (con->wq)
- destroy_workqueue(con->wq);
- typec_unregister_port(con->port);
- con->port = NULL;
- }
+ ucsi_unregister_connectors(ucsi);
err_reset:
memset(&ucsi->cap, 0, sizeof(ucsi->cap));
@@ -1363,7 +1380,6 @@ EXPORT_SYMBOL_GPL(ucsi_register);
void ucsi_unregister(struct ucsi *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);
@@ -1371,18 +1387,7 @@ void ucsi_unregister(struct ucsi *ucsi)
/* Disable notifications */
ucsi->ops->async_write(ucsi, UCSI_CONTROL, &cmd, sizeof(cmd));
- for (i = 0; i < ucsi->cap.num_connectors; i++) {
- cancel_work_sync(&ucsi->connector[i].work);
- ucsi_unregister_partner(&ucsi->connector[i]);
- ucsi_unregister_altmodes(&ucsi->connector[i],
- UCSI_RECIPIENT_CON);
- ucsi_unregister_port_psy(&ucsi->connector[i]);
- if (ucsi->connector[i].wq)
- destroy_workqueue(ucsi->connector[i].wq);
- typec_unregister_port(ucsi->connector[i].port);
- }
-
- kfree(ucsi->connector);
+ ucsi_unregister_connectors(ucsi);
}
EXPORT_SYMBOL_GPL(ucsi_unregister);
--
2.7.4
^ permalink raw reply related [flat|nested] 3+ messages in thread
* [PATCH v6 2/2] usb: typec: ucsi: Wait for the USB role switches
2022-04-25 9:00 [PATCH v6 0/2] usb: typec: ucsi: allow retry to find role switch Linyu Yuan
2022-04-25 9:00 ` [PATCH v6 1/2] usb: typec: ucsi: add a common function ucsi_unregister_connectors() Linyu Yuan
@ 2022-04-25 9:00 ` Linyu Yuan
1 sibling, 0 replies; 3+ messages in thread
From: Linyu Yuan @ 2022-04-25 9:00 UTC (permalink / raw)
To: Heikki Krogerus, Greg Kroah-Hartman; +Cc: linux-usb, Jack Pham, Linyu Yuan
When role switch module probe late than ucsi module,
fwnode_usb_role_switch_get() will return -EPROBE_DEFER,
it is better to restart ucsi init work to find
it again every 100ms, total wait time is 10 second.
It also means change ucsi init work to delayed_work.
Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Signed-off-by: Linyu Yuan <quic_linyyuan@quicinc.com>
---
v2: keep original con->num in debug log
v3: change return value from -EAGAIN to PTR_ERR()
v4: change subject line,
add counter for retry limit,
correct commit descripton to match change in V3
v5: small update of commit description
v6: add Reviewed-by tag of ucsi maintainer.
drivers/usb/typec/ucsi/ucsi.c | 32 ++++++++++++++++++++------------
drivers/usb/typec/ucsi/ucsi.h | 6 +++++-
2 files changed, 25 insertions(+), 13 deletions(-)
diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c
index 981b561..20d368a 100644
--- a/drivers/usb/typec/ucsi/ucsi.c
+++ b/drivers/usb/typec/ucsi/ucsi.c
@@ -1053,6 +1053,14 @@ 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);
@@ -1088,7 +1096,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;
@@ -1146,13 +1153,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;
@@ -1285,12 +1285,20 @@ static int ucsi_init(struct ucsi *ucsi)
static void ucsi_init_work(struct work_struct *work)
{
- struct ucsi *ucsi = container_of(work, struct ucsi, work);
+ struct ucsi *ucsi = container_of(work, struct ucsi, work.work);
int ret;
ret = ucsi_init(ucsi);
if (ret)
dev_err(ucsi->dev, "PPM init failed (%d)\n", ret);
+
+ if (ret == -EPROBE_DEFER) {
+ if (ucsi->work_count++ > UCSI_ROLE_SWITCH_WAIT_COUNT)
+ return;
+
+ queue_delayed_work(system_long_wq, &ucsi->work,
+ UCSI_ROLE_SWITCH_INTERVAL);
+ }
}
/**
@@ -1330,7 +1338,7 @@ struct ucsi *ucsi_create(struct device *dev, const struct ucsi_operations *ops)
if (!ucsi)
return ERR_PTR(-ENOMEM);
- INIT_WORK(&ucsi->work, ucsi_init_work);
+ INIT_DELAYED_WORK(&ucsi->work, ucsi_init_work);
mutex_init(&ucsi->ppm_lock);
ucsi->dev = dev;
ucsi->ops = ops;
@@ -1365,7 +1373,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, &ucsi->work, 0);
return 0;
}
@@ -1382,7 +1390,7 @@ void ucsi_unregister(struct ucsi *ucsi)
u64 cmd = UCSI_SET_NOTIFICATION_ENABLE;
/* Make sure that we are not in the middle of driver initialization */
- cancel_work_sync(&ucsi->work);
+ cancel_delayed_work_sync(&ucsi->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 280f1e1..8eb391e 100644
--- a/drivers/usb/typec/ucsi/ucsi.h
+++ b/drivers/usb/typec/ucsi/ucsi.h
@@ -287,7 +287,11 @@ struct ucsi {
struct ucsi_capability cap;
struct ucsi_connector *connector;
- struct work_struct work;
+ struct delayed_work work;
+ int work_count;
+#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;
--
2.7.4
^ permalink raw reply related [flat|nested] 3+ messages in thread