Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

usb: typec: Implement mode selection

The mode selection process is controlled by the following API functions,
which allow to initiate and complete mode entry based on the priority of
each mode:

`typec_mode_selection_start` function compiles a priority list of supported
Alternate Modes.
`typec_altmode_state_update` function is invoked by the port driver to
communicate the current mode of the Type-C connector.
`typec_mode_selection_delete` function stops the currently running mode
selection process and releases all associated system resources.

`mode_selection_work_fn` task attempts to activate modes. The process stops
on success; otherwise, it proceeds to the next mode after a timeout or
error.

Signed-off-by: Andrei Kuchynski <akuchynski@chromium.org>
Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Link: https://patch.msgid.link/20260119131824.2529334-5-akuchynski@chromium.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Andrei Kuchynski and committed by
Greg Kroah-Hartman
fb2abc75 027b304c

+326 -1
+1 -1
drivers/usb/typec/Makefile
··· 1 1 # SPDX-License-Identifier: GPL-2.0 2 2 obj-$(CONFIG_TYPEC) += typec.o 3 - typec-y := class.o mux.o bus.o pd.o retimer.o 3 + typec-y := class.o mux.o bus.o pd.o retimer.o mode_selection.o 4 4 typec-$(CONFIG_ACPI) += port-mapper.o 5 5 obj-$(CONFIG_TYPEC) += altmodes/ 6 6 obj-$(CONFIG_TYPEC_TCPM) += tcpm/
+2
drivers/usb/typec/class.h
··· 9 9 struct typec_mux; 10 10 struct typec_switch; 11 11 struct usb_device; 12 + struct mode_selection; 12 13 13 14 struct typec_plug { 14 15 struct device dev; ··· 40 39 u8 usb_capability; 41 40 42 41 struct usb_power_delivery *pd; 42 + struct mode_selection *sel; 43 43 44 44 void (*attach)(struct typec_partner *partner, struct device *dev); 45 45 void (*deattach)(struct typec_partner *partner, struct device *dev);
+283
drivers/usb/typec/mode_selection.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + /* 3 + * Copyright 2025 Google LLC. 4 + */ 5 + 6 + #include <linux/types.h> 7 + #include <linux/list_sort.h> 8 + #include <linux/slab.h> 9 + #include <linux/mutex.h> 10 + #include <linux/workqueue.h> 11 + #include <linux/usb/typec_altmode.h> 12 + 13 + #include "class.h" 14 + 15 + /** 16 + * struct mode_state - State tracking for a specific Type-C alternate mode 17 + * @svid: Standard or Vendor ID of the Alternate Mode 18 + * @priority: Mode priority 19 + * @error: Outcome of the last attempt to enter the mode 20 + * @list: List head to link this mode state into a prioritized list 21 + */ 22 + struct mode_state { 23 + u16 svid; 24 + u8 priority; 25 + int error; 26 + struct list_head list; 27 + }; 28 + 29 + /** 30 + * struct mode_selection - Manages the selection and state of Alternate Modes 31 + * @mode_list: Prioritized list of available Alternate Modes 32 + * @lock: Mutex to protect mode_list 33 + * @work: Work structure 34 + * @partner: Handle to the Type-C partner device 35 + * @active_svid: svid of currently active mode 36 + * @timeout: Timeout for a mode entry attempt, ms 37 + * @delay: Delay between mode entry/exit attempts, ms 38 + */ 39 + struct mode_selection { 40 + struct list_head mode_list; 41 + /* Protects the mode_list*/ 42 + struct mutex lock; 43 + struct delayed_work work; 44 + struct typec_partner *partner; 45 + u16 active_svid; 46 + unsigned int timeout; 47 + unsigned int delay; 48 + }; 49 + 50 + /** 51 + * struct mode_order - Mode activation tracking 52 + * @svid: Standard or Vendor ID of the Alternate Mode 53 + * @enter: Flag indicating if the driver is currently attempting to enter or 54 + * exit the mode 55 + * @result: Outcome of the attempt to activate the mode 56 + */ 57 + struct mode_order { 58 + u16 svid; 59 + int enter; 60 + int result; 61 + }; 62 + 63 + static int activate_altmode(struct device *dev, void *data) 64 + { 65 + if (is_typec_partner_altmode(dev)) { 66 + struct typec_altmode *alt = to_typec_altmode(dev); 67 + struct mode_order *order = (struct mode_order *)data; 68 + 69 + if (order->svid == alt->svid) { 70 + if (alt->ops && alt->ops->activate) 71 + order->result = alt->ops->activate(alt, order->enter); 72 + else 73 + order->result = -EOPNOTSUPP; 74 + return 1; 75 + } 76 + } 77 + return 0; 78 + } 79 + 80 + static int mode_selection_activate(struct mode_selection *sel, 81 + const u16 svid, const int enter) 82 + 83 + __must_hold(&sel->lock) 84 + { 85 + struct mode_order order = {.svid = svid, .enter = enter, .result = -ENODEV}; 86 + 87 + /* 88 + * The port driver may acquire its internal mutex during alternate mode 89 + * activation. Since this is the same mutex that may be held during the 90 + * execution of typec_altmode_state_update(), it is crucial to release 91 + * sel->mutex before activation to avoid potential deadlock. 92 + * Note that sel->mode_list must remain invariant throughout this unlocked 93 + * interval. 94 + */ 95 + mutex_unlock(&sel->lock); 96 + device_for_each_child(&sel->partner->dev, &order, activate_altmode); 97 + mutex_lock(&sel->lock); 98 + 99 + return order.result; 100 + } 101 + 102 + static void mode_list_clean(struct mode_selection *sel) 103 + { 104 + struct mode_state *ms, *tmp; 105 + 106 + list_for_each_entry_safe(ms, tmp, &sel->mode_list, list) { 107 + list_del(&ms->list); 108 + kfree(ms); 109 + } 110 + } 111 + 112 + /** 113 + * mode_selection_work_fn() - Alternate mode activation task 114 + * @work: work structure 115 + * 116 + * - If the Alternate Mode currently prioritized at the top of the list is already 117 + * active, the entire selection process is considered finished. 118 + * - If a different Alternate Mode is currently active, the system must exit that 119 + * active mode first before attempting any new entry. 120 + * 121 + * The function then checks the result of the attempt to entre the current mode, 122 + * stored in the `ms->error` field: 123 + * - if the attempt FAILED, the mode is deactivated and removed from the list. 124 + * - `ms->error` value of 0 signifies that the mode has not yet been activated. 125 + * 126 + * Once successfully activated, the task is scheduled for subsequent entry after 127 + * a timeout period. The alternate mode driver is expected to call back with the 128 + * actual mode entry result via `typec_altmode_state_update()`. 129 + */ 130 + static void mode_selection_work_fn(struct work_struct *work) 131 + { 132 + struct mode_selection *sel = container_of(work, 133 + struct mode_selection, work.work); 134 + struct mode_state *ms; 135 + unsigned int delay = sel->delay; 136 + int result; 137 + 138 + guard(mutex)(&sel->lock); 139 + 140 + ms = list_first_entry_or_null(&sel->mode_list, struct mode_state, list); 141 + if (!ms) 142 + return; 143 + 144 + if (sel->active_svid == ms->svid) { 145 + dev_dbg(&sel->partner->dev, "%x altmode is active\n", ms->svid); 146 + mode_list_clean(sel); 147 + } else if (sel->active_svid != 0) { 148 + result = mode_selection_activate(sel, sel->active_svid, 0); 149 + if (result) 150 + mode_list_clean(sel); 151 + else 152 + sel->active_svid = 0; 153 + } else if (ms->error) { 154 + dev_err(&sel->partner->dev, "%x: entry error %pe\n", 155 + ms->svid, ERR_PTR(ms->error)); 156 + mode_selection_activate(sel, ms->svid, 0); 157 + list_del(&ms->list); 158 + kfree(ms); 159 + } else { 160 + result = mode_selection_activate(sel, ms->svid, 1); 161 + if (result) { 162 + dev_err(&sel->partner->dev, "%x: activation error %pe\n", 163 + ms->svid, ERR_PTR(result)); 164 + list_del(&ms->list); 165 + kfree(ms); 166 + } else { 167 + delay = sel->timeout; 168 + ms->error = -ETIMEDOUT; 169 + } 170 + } 171 + 172 + if (!list_empty(&sel->mode_list)) 173 + schedule_delayed_work(&sel->work, msecs_to_jiffies(delay)); 174 + } 175 + 176 + void typec_altmode_state_update(struct typec_partner *partner, const u16 svid, 177 + const int error) 178 + { 179 + struct mode_selection *sel = partner->sel; 180 + struct mode_state *ms; 181 + 182 + if (sel) { 183 + mutex_lock(&sel->lock); 184 + ms = list_first_entry_or_null(&sel->mode_list, struct mode_state, list); 185 + if (ms && ms->svid == svid) { 186 + ms->error = error; 187 + if (cancel_delayed_work(&sel->work)) 188 + schedule_delayed_work(&sel->work, 0); 189 + } 190 + if (!error) 191 + sel->active_svid = svid; 192 + else 193 + sel->active_svid = 0; 194 + mutex_unlock(&sel->lock); 195 + } 196 + } 197 + EXPORT_SYMBOL_GPL(typec_altmode_state_update); 198 + 199 + static int compare_priorities(void *priv, 200 + const struct list_head *a, const struct list_head *b) 201 + { 202 + const struct mode_state *msa = container_of(a, struct mode_state, list); 203 + const struct mode_state *msb = container_of(b, struct mode_state, list); 204 + 205 + if (msa->priority < msb->priority) 206 + return -1; 207 + return 1; 208 + } 209 + 210 + static int altmode_add_to_list(struct device *dev, void *data) 211 + { 212 + if (is_typec_partner_altmode(dev)) { 213 + struct list_head *list = (struct list_head *)data; 214 + struct typec_altmode *altmode = to_typec_altmode(dev); 215 + const struct typec_altmode *pdev = typec_altmode_get_partner(altmode); 216 + struct mode_state *ms; 217 + 218 + if (pdev && altmode->ops && altmode->ops->activate) { 219 + ms = kzalloc(sizeof(*ms), GFP_KERNEL); 220 + if (!ms) 221 + return -ENOMEM; 222 + ms->svid = pdev->svid; 223 + ms->priority = pdev->priority; 224 + INIT_LIST_HEAD(&ms->list); 225 + list_add_tail(&ms->list, list); 226 + } 227 + } 228 + return 0; 229 + } 230 + 231 + int typec_mode_selection_start(struct typec_partner *partner, 232 + const unsigned int delay, const unsigned int timeout) 233 + { 234 + struct mode_selection *sel; 235 + int ret; 236 + 237 + if (partner->usb_mode == USB_MODE_USB4) 238 + return -EBUSY; 239 + 240 + if (partner->sel) 241 + return -EALREADY; 242 + 243 + sel = kzalloc(sizeof(*sel), GFP_KERNEL); 244 + if (!sel) 245 + return -ENOMEM; 246 + 247 + INIT_LIST_HEAD(&sel->mode_list); 248 + 249 + ret = device_for_each_child(&partner->dev, &sel->mode_list, 250 + altmode_add_to_list); 251 + 252 + if (ret || list_empty(&sel->mode_list)) { 253 + mode_list_clean(sel); 254 + kfree(sel); 255 + return ret; 256 + } 257 + 258 + list_sort(NULL, &sel->mode_list, compare_priorities); 259 + sel->partner = partner; 260 + sel->delay = delay; 261 + sel->timeout = timeout; 262 + mutex_init(&sel->lock); 263 + INIT_DELAYED_WORK(&sel->work, mode_selection_work_fn); 264 + schedule_delayed_work(&sel->work, msecs_to_jiffies(delay)); 265 + partner->sel = sel; 266 + 267 + return 0; 268 + } 269 + EXPORT_SYMBOL_GPL(typec_mode_selection_start); 270 + 271 + void typec_mode_selection_delete(struct typec_partner *partner) 272 + { 273 + struct mode_selection *sel = partner->sel; 274 + 275 + if (sel) { 276 + partner->sel = NULL; 277 + cancel_delayed_work_sync(&sel->work); 278 + mode_list_clean(sel); 279 + mutex_destroy(&sel->lock); 280 + kfree(sel); 281 + } 282 + } 283 + EXPORT_SYMBOL_GPL(typec_mode_selection_delete);
+40
include/linux/usb/typec_altmode.h
··· 240 240 module_driver(__typec_altmode_driver, typec_altmode_register_driver, \ 241 241 typec_altmode_unregister_driver) 242 242 243 + /** 244 + * typec_mode_selection_start - Start an alternate mode selection process 245 + * @partner: Handle to the Type-C partner device 246 + * @delay: Delay between mode entry/exit attempts, ms 247 + * @timeout: Timeout for a mode entry attempt, ms 248 + * 249 + * This function initiates the process of attempting to enter an Alternate Mode 250 + * supported by the connected Type-C partner. 251 + * Returns 0 on success, or a negative error code on failure. 252 + */ 253 + int typec_mode_selection_start(struct typec_partner *partner, 254 + const unsigned int delay, const unsigned int timeout); 255 + 256 + /** 257 + * typec_altmode_state_update - Report the current status of an Alternate Mode 258 + * negotiation 259 + * @partner: Handle to the Type-C partner device 260 + * @svid: Standard or Vendor ID of the Alternate Mode. A value of 0 should be 261 + * passed if no mode is currently active 262 + * @result: Result of the entry operation. This should be 0 on success, or a 263 + * negative error code if the negotiation failed 264 + * 265 + * This function should be called by an Alternate Mode driver to report the 266 + * result of an asynchronous alternate mode entry request. It signals what the 267 + * current active SVID is (or 0 if none) and the success or failure status of 268 + * the last attempt. 269 + */ 270 + void typec_altmode_state_update(struct typec_partner *partner, const u16 svid, 271 + const int result); 272 + 273 + /** 274 + * typec_mode_selection_delete - Delete an alternate mode selection instance 275 + * @partner: Handle to the Type-C partner device. 276 + * 277 + * This function cancels a pending alternate mode selection request that was 278 + * previously started with typec_mode_selection_start(). 279 + * This is typically called when the partner disconnects. 280 + */ 281 + void typec_mode_selection_delete(struct typec_partner *partner); 282 + 243 283 #endif /* __USB_TYPEC_ALTMODE_H */