Skip to content

Commit b9aa02c

Browse files
Heikki Krogerusgregkh
authored andcommitted
usb: typec: ucsi: Add polling mechanism for partner tasks like alt mode checking
The "poll worker" that is introduced here is first used for checking partner alternate modes, but it can later be used for any partner task that requires a separate job to be scheduled to the connector specific workqueues. The mechanism allows the partner device specific tasks to be polling tasks and also delayed tasks if necessary. By polling the partner alternate modes with this mechanism the long command completion timeout value can be reduced back to normal. The long command completion timeout was only used to work around a problem on some platforms where the EC firmware (PPM) didn't return BUSY even when it should with the alt mode commands. Signed-off-by: Heikki Krogerus <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Greg Kroah-Hartman <[email protected]>
1 parent 47eb8de commit b9aa02c

File tree

2 files changed

+101
-19
lines changed

2 files changed

+101
-19
lines changed

drivers/usb/typec/ucsi/ucsi.c

Lines changed: 100 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,64 @@ int ucsi_resume(struct ucsi *ucsi)
191191
EXPORT_SYMBOL_GPL(ucsi_resume);
192192
/* -------------------------------------------------------------------------- */
193193

194+
struct ucsi_work {
195+
struct delayed_work work;
196+
unsigned long delay;
197+
unsigned int count;
198+
struct ucsi_connector *con;
199+
int (*cb)(struct ucsi_connector *);
200+
};
201+
202+
static void ucsi_poll_worker(struct work_struct *work)
203+
{
204+
struct ucsi_work *uwork = container_of(work, struct ucsi_work, work.work);
205+
struct ucsi_connector *con = uwork->con;
206+
int ret;
207+
208+
mutex_lock(&con->lock);
209+
210+
if (!con->partner) {
211+
mutex_unlock(&con->lock);
212+
kfree(uwork);
213+
return;
214+
}
215+
216+
ret = uwork->cb(con);
217+
218+
if (uwork->count-- && (ret == -EBUSY || ret == -ETIMEDOUT))
219+
queue_delayed_work(con->wq, &uwork->work, uwork->delay);
220+
else
221+
kfree(uwork);
222+
223+
mutex_unlock(&con->lock);
224+
}
225+
226+
static int ucsi_partner_task(struct ucsi_connector *con,
227+
int (*cb)(struct ucsi_connector *),
228+
int retries, unsigned long delay)
229+
{
230+
struct ucsi_work *uwork;
231+
232+
if (!con->partner)
233+
return 0;
234+
235+
uwork = kzalloc(sizeof(*uwork), GFP_KERNEL);
236+
if (!uwork)
237+
return -ENOMEM;
238+
239+
INIT_DELAYED_WORK(&uwork->work, ucsi_poll_worker);
240+
uwork->count = retries;
241+
uwork->delay = delay;
242+
uwork->con = con;
243+
uwork->cb = cb;
244+
245+
queue_delayed_work(con->wq, &uwork->work, delay);
246+
247+
return 0;
248+
}
249+
250+
/* -------------------------------------------------------------------------- */
251+
194252
void ucsi_altmode_update_active(struct ucsi_connector *con)
195253
{
196254
const struct typec_altmode *altmode = NULL;
@@ -543,6 +601,25 @@ static void ucsi_get_src_pdos(struct ucsi_connector *con, int is_partner)
543601
con->num_pdos += ret / sizeof(u32);
544602
}
545603

604+
static int ucsi_check_altmodes(struct ucsi_connector *con)
605+
{
606+
int ret;
607+
608+
ret = ucsi_register_altmodes(con, UCSI_RECIPIENT_SOP);
609+
if (ret && ret != -ETIMEDOUT)
610+
dev_err(con->ucsi->dev,
611+
"con%d: failed to register partner alt modes (%d)\n",
612+
con->num, ret);
613+
614+
/* Ignoring the errors in this case. */
615+
if (con->partner_altmode[0]) {
616+
ucsi_altmode_update_active(con);
617+
return 0;
618+
}
619+
620+
return ret;
621+
}
622+
546623
static void ucsi_pwr_opmode_change(struct ucsi_connector *con)
547624
{
548625
switch (UCSI_CONSTAT_PWR_OPMODE(con->status.flags)) {
@@ -650,14 +727,7 @@ static void ucsi_partner_change(struct ucsi_connector *con)
650727
dev_err(con->ucsi->dev, "con:%d: failed to set usb role:%d\n",
651728
con->num, u_role);
652729

653-
/* Can't rely on Partner Flags field. Always checking the alt modes. */
654-
ret = ucsi_register_altmodes(con, UCSI_RECIPIENT_SOP);
655-
if (ret)
656-
dev_err(con->ucsi->dev,
657-
"con%d: failed to register partner alternate modes\n",
658-
con->num);
659-
else
660-
ucsi_altmode_update_active(con);
730+
ucsi_partner_task(con, ucsi_check_altmodes, 30, 0);
661731
}
662732

663733
static void ucsi_handle_connector_change(struct work_struct *work)
@@ -1045,8 +1115,18 @@ static int ucsi_register_port(struct ucsi *ucsi, int index)
10451115
enum typec_accessory *accessory = cap->accessory;
10461116
enum usb_role u_role = USB_ROLE_NONE;
10471117
u64 command;
1118+
char *name;
10481119
int ret;
10491120

1121+
name = kasprintf(GFP_KERNEL, "%s-con%d", dev_name(ucsi->dev), con->num);
1122+
if (!name)
1123+
return -ENOMEM;
1124+
1125+
con->wq = create_singlethread_workqueue(name);
1126+
kfree(name);
1127+
if (!con->wq)
1128+
return -ENOMEM;
1129+
10501130
INIT_WORK(&con->work, ucsi_handle_connector_change);
10511131
init_completion(&con->complete);
10521132
mutex_init(&con->lock);
@@ -1164,24 +1244,21 @@ static int ucsi_register_port(struct ucsi *ucsi, int index)
11641244
ret = 0;
11651245
}
11661246

1167-
if (con->partner) {
1168-
ret = ucsi_register_altmodes(con, UCSI_RECIPIENT_SOP);
1169-
if (ret) {
1170-
dev_err(ucsi->dev,
1171-
"con%d: failed to register alternate modes\n",
1172-
con->num);
1173-
ret = 0;
1174-
} else {
1175-
ucsi_altmode_update_active(con);
1176-
}
1177-
}
1247+
if (con->partner)
1248+
ucsi_check_altmodes(con);
11781249

11791250
trace_ucsi_register_port(con->num, &con->status);
11801251

11811252
out:
11821253
fwnode_handle_put(cap->fwnode);
11831254
out_unlock:
11841255
mutex_unlock(&con->lock);
1256+
1257+
if (ret && con->wq) {
1258+
destroy_workqueue(con->wq);
1259+
con->wq = NULL;
1260+
}
1261+
11851262
return ret;
11861263
}
11871264

@@ -1252,6 +1329,8 @@ static int ucsi_init(struct ucsi *ucsi)
12521329
ucsi_unregister_partner(con);
12531330
ucsi_unregister_altmodes(con, UCSI_RECIPIENT_CON);
12541331
ucsi_unregister_port_psy(con);
1332+
if (con->wq)
1333+
destroy_workqueue(con->wq);
12551334
typec_unregister_port(con->port);
12561335
con->port = NULL;
12571336
}
@@ -1374,6 +1453,8 @@ void ucsi_unregister(struct ucsi *ucsi)
13741453
ucsi_unregister_altmodes(&ucsi->connector[i],
13751454
UCSI_RECIPIENT_CON);
13761455
ucsi_unregister_port_psy(&ucsi->connector[i]);
1456+
if (ucsi->connector[i].wq)
1457+
destroy_workqueue(ucsi->connector[i].wq);
13771458
typec_unregister_port(ucsi->connector[i].port);
13781459
}
13791460

drivers/usb/typec/ucsi/ucsi.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,7 @@ struct ucsi_connector {
317317
struct mutex lock; /* port lock */
318318
struct work_struct work;
319319
struct completion complete;
320+
struct workqueue_struct *wq;
320321

321322
struct typec_port *port;
322323
struct typec_partner *partner;

0 commit comments

Comments
 (0)