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

PM: domains: Add helper functions to attach/detach multiple PM domains

Attaching/detaching of a device to multiple PM domains has started to
become a common operation for many drivers, typically during ->probe() and
->remove(). In most cases, this has lead to lots of boilerplate code in the
drivers.

To fixup up the situation, let's introduce a pair of helper functions,
dev_pm_domain_attach|detach_list(), that driver can use instead of the
open-coding. Note that, it seems reasonable to limit the support for these
helpers to DT based platforms, at it's the only valid use case for now.

Suggested-by: Daniel Baluta <daniel.baluta@nxp.com>
Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org>
Tested-by: Iuliana Prodan <iuliana.prodan@nxp.com>
Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
Link: https://lore.kernel.org/r/20240130123951.236243-2-ulf.hansson@linaro.org

+172
+134
drivers/base/power/common.c
··· 168 168 EXPORT_SYMBOL_GPL(dev_pm_domain_attach_by_name); 169 169 170 170 /** 171 + * dev_pm_domain_attach_list - Associate a device with its PM domains. 172 + * @dev: The device used to lookup the PM domains for. 173 + * @data: The data used for attaching to the PM domains. 174 + * @list: An out-parameter with an allocated list of attached PM domains. 175 + * 176 + * This function helps to attach a device to its multiple PM domains. The 177 + * caller, which is typically a driver's probe function, may provide a list of 178 + * names for the PM domains that we should try to attach the device to, but it 179 + * may also provide an empty list, in case the attach should be done for all of 180 + * the available PM domains. 181 + * 182 + * Callers must ensure proper synchronization of this function with power 183 + * management callbacks. 184 + * 185 + * Returns the number of attached PM domains or a negative error code in case of 186 + * a failure. Note that, to detach the list of PM domains, the driver shall call 187 + * dev_pm_domain_detach_list(), typically during the remove phase. 188 + */ 189 + int dev_pm_domain_attach_list(struct device *dev, 190 + const struct dev_pm_domain_attach_data *data, 191 + struct dev_pm_domain_list **list) 192 + { 193 + struct device_node *np = dev->of_node; 194 + struct dev_pm_domain_list *pds; 195 + struct device *pd_dev = NULL; 196 + int ret, i, num_pds = 0; 197 + bool by_id = true; 198 + u32 pd_flags = data ? data->pd_flags : 0; 199 + u32 link_flags = pd_flags & PD_FLAG_NO_DEV_LINK ? 0 : 200 + DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME; 201 + 202 + if (dev->pm_domain) 203 + return -EEXIST; 204 + 205 + /* For now this is limited to OF based platforms. */ 206 + if (!np) 207 + return 0; 208 + 209 + if (data && data->pd_names) { 210 + num_pds = data->num_pd_names; 211 + by_id = false; 212 + } else { 213 + num_pds = of_count_phandle_with_args(np, "power-domains", 214 + "#power-domain-cells"); 215 + } 216 + 217 + if (num_pds <= 0) 218 + return 0; 219 + 220 + pds = devm_kzalloc(dev, sizeof(*pds), GFP_KERNEL); 221 + if (!pds) 222 + return -ENOMEM; 223 + 224 + pds->pd_devs = devm_kcalloc(dev, num_pds, sizeof(*pds->pd_devs), 225 + GFP_KERNEL); 226 + if (!pds->pd_devs) 227 + return -ENOMEM; 228 + 229 + pds->pd_links = devm_kcalloc(dev, num_pds, sizeof(*pds->pd_links), 230 + GFP_KERNEL); 231 + if (!pds->pd_links) 232 + return -ENOMEM; 233 + 234 + if (link_flags && pd_flags & PD_FLAG_DEV_LINK_ON) 235 + link_flags |= DL_FLAG_RPM_ACTIVE; 236 + 237 + for (i = 0; i < num_pds; i++) { 238 + if (by_id) 239 + pd_dev = dev_pm_domain_attach_by_id(dev, i); 240 + else 241 + pd_dev = dev_pm_domain_attach_by_name(dev, 242 + data->pd_names[i]); 243 + if (IS_ERR_OR_NULL(pd_dev)) { 244 + ret = pd_dev ? PTR_ERR(pd_dev) : -ENODEV; 245 + goto err_attach; 246 + } 247 + 248 + if (link_flags) { 249 + struct device_link *link; 250 + 251 + link = device_link_add(dev, pd_dev, link_flags); 252 + if (!link) { 253 + ret = -ENODEV; 254 + goto err_link; 255 + } 256 + 257 + pds->pd_links[i] = link; 258 + } 259 + 260 + pds->pd_devs[i] = pd_dev; 261 + } 262 + 263 + pds->num_pds = num_pds; 264 + *list = pds; 265 + return num_pds; 266 + 267 + err_link: 268 + dev_pm_domain_detach(pd_dev, true); 269 + err_attach: 270 + while (--i >= 0) { 271 + if (pds->pd_links[i]) 272 + device_link_del(pds->pd_links[i]); 273 + dev_pm_domain_detach(pds->pd_devs[i], true); 274 + } 275 + return ret; 276 + } 277 + EXPORT_SYMBOL_GPL(dev_pm_domain_attach_list); 278 + 279 + /** 171 280 * dev_pm_domain_detach - Detach a device from its PM domain. 172 281 * @dev: Device to detach. 173 282 * @power_off: Used to indicate whether we should power off the device. ··· 295 186 dev->pm_domain->detach(dev, power_off); 296 187 } 297 188 EXPORT_SYMBOL_GPL(dev_pm_domain_detach); 189 + 190 + /** 191 + * dev_pm_domain_detach_list - Detach a list of PM domains. 192 + * @list: The list of PM domains to detach. 193 + * 194 + * This function reverse the actions from dev_pm_domain_attach_list(). 195 + * Typically it should be invoked during the remove phase from drivers. 196 + * 197 + * Callers must ensure proper synchronization of this function with power 198 + * management callbacks. 199 + */ 200 + void dev_pm_domain_detach_list(struct dev_pm_domain_list *list) 201 + { 202 + int i; 203 + 204 + if (!list) 205 + return; 206 + 207 + for (i = 0; i < list->num_pds; i++) { 208 + if (list->pd_links[i]) 209 + device_link_del(list->pd_links[i]); 210 + dev_pm_domain_detach(list->pd_devs[i], true); 211 + } 212 + } 213 + EXPORT_SYMBOL_GPL(dev_pm_domain_detach_list); 298 214 299 215 /** 300 216 * dev_pm_domain_start - Start the device through its PM domain.
+38
include/linux/pm_domain.h
··· 20 20 #include <linux/time64.h> 21 21 22 22 /* 23 + * Flags to control the behaviour when attaching a device to its PM domains. 24 + * 25 + * PD_FLAG_NO_DEV_LINK: As the default behaviour creates a device-link 26 + * for every PM domain that gets attached, this 27 + * flag can be used to skip that. 28 + * 29 + * PD_FLAG_DEV_LINK_ON: Add the DL_FLAG_RPM_ACTIVE to power-on the 30 + * supplier and its PM domain when creating the 31 + * device-links. 32 + * 33 + */ 34 + #define PD_FLAG_NO_DEV_LINK BIT(0) 35 + #define PD_FLAG_DEV_LINK_ON BIT(1) 36 + 37 + struct dev_pm_domain_attach_data { 38 + const char * const *pd_names; 39 + const u32 num_pd_names; 40 + const u32 pd_flags; 41 + }; 42 + 43 + struct dev_pm_domain_list { 44 + struct device **pd_devs; 45 + struct device_link **pd_links; 46 + u32 num_pds; 47 + }; 48 + 49 + /* 23 50 * Flags to control the behaviour of a genpd. 24 51 * 25 52 * These flags may be set in the struct generic_pm_domain's flags field by a ··· 447 420 unsigned int index); 448 421 struct device *dev_pm_domain_attach_by_name(struct device *dev, 449 422 const char *name); 423 + int dev_pm_domain_attach_list(struct device *dev, 424 + const struct dev_pm_domain_attach_data *data, 425 + struct dev_pm_domain_list **list); 450 426 void dev_pm_domain_detach(struct device *dev, bool power_off); 427 + void dev_pm_domain_detach_list(struct dev_pm_domain_list *list); 451 428 int dev_pm_domain_start(struct device *dev); 452 429 void dev_pm_domain_set(struct device *dev, struct dev_pm_domain *pd); 453 430 int dev_pm_domain_set_performance_state(struct device *dev, unsigned int state); ··· 470 439 { 471 440 return NULL; 472 441 } 442 + static inline int dev_pm_domain_attach_list(struct device *dev, 443 + const struct dev_pm_domain_attach_data *data, 444 + struct dev_pm_domain_list **list) 445 + { 446 + return 0; 447 + } 473 448 static inline void dev_pm_domain_detach(struct device *dev, bool power_off) {} 449 + static inline void dev_pm_domain_detach_list(struct dev_pm_domain_list *list) {} 474 450 static inline int dev_pm_domain_start(struct device *dev) 475 451 { 476 452 return 0;