Skip to content

Commit 161e16a

Browse files
committed
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
1 parent 697624e commit 161e16a

File tree

2 files changed

+172
-0
lines changed

2 files changed

+172
-0
lines changed

drivers/base/power/common.c

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,115 @@ struct device *dev_pm_domain_attach_by_name(struct device *dev,
167167
}
168168
EXPORT_SYMBOL_GPL(dev_pm_domain_attach_by_name);
169169

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+
170279
/**
171280
* dev_pm_domain_detach - Detach a device from its PM domain.
172281
* @dev: Device to detach.
@@ -187,6 +296,31 @@ void dev_pm_domain_detach(struct device *dev, bool power_off)
187296
}
188297
EXPORT_SYMBOL_GPL(dev_pm_domain_detach);
189298

299+
/**
300+
* dev_pm_domain_detach_list - Detach a list of PM domains.
301+
* @list: The list of PM domains to detach.
302+
*
303+
* This function reverse the actions from dev_pm_domain_attach_list().
304+
* Typically it should be invoked during the remove phase from drivers.
305+
*
306+
* Callers must ensure proper synchronization of this function with power
307+
* management callbacks.
308+
*/
309+
void dev_pm_domain_detach_list(struct dev_pm_domain_list *list)
310+
{
311+
int i;
312+
313+
if (!list)
314+
return;
315+
316+
for (i = 0; i < list->num_pds; i++) {
317+
if (list->pd_links[i])
318+
device_link_del(list->pd_links[i]);
319+
dev_pm_domain_detach(list->pd_devs[i], true);
320+
}
321+
}
322+
EXPORT_SYMBOL_GPL(dev_pm_domain_detach_list);
323+
190324
/**
191325
* dev_pm_domain_start - Start the device through its PM domain.
192326
* @dev: Device to start.

include/linux/pm_domain.h

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,33 @@
1919
#include <linux/cpumask.h>
2020
#include <linux/time64.h>
2121

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+
2249
/*
2350
* Flags to control the behaviour of a genpd.
2451
*
@@ -420,7 +447,11 @@ struct device *dev_pm_domain_attach_by_id(struct device *dev,
420447
unsigned int index);
421448
struct device *dev_pm_domain_attach_by_name(struct device *dev,
422449
const char *name);
450+
int dev_pm_domain_attach_list(struct device *dev,
451+
const struct dev_pm_domain_attach_data *data,
452+
struct dev_pm_domain_list **list);
423453
void dev_pm_domain_detach(struct device *dev, bool power_off);
454+
void dev_pm_domain_detach_list(struct dev_pm_domain_list *list);
424455
int dev_pm_domain_start(struct device *dev);
425456
void dev_pm_domain_set(struct device *dev, struct dev_pm_domain *pd);
426457
int dev_pm_domain_set_performance_state(struct device *dev, unsigned int state);
@@ -439,7 +470,14 @@ static inline struct device *dev_pm_domain_attach_by_name(struct device *dev,
439470
{
440471
return NULL;
441472
}
473+
static inline int dev_pm_domain_attach_list(struct device *dev,
474+
const struct dev_pm_domain_attach_data *data,
475+
struct dev_pm_domain_list **list)
476+
{
477+
return 0;
478+
}
442479
static inline void dev_pm_domain_detach(struct device *dev, bool power_off) {}
480+
static inline void dev_pm_domain_detach_list(struct dev_pm_domain_list *list) {}
443481
static inline int dev_pm_domain_start(struct device *dev)
444482
{
445483
return 0;

0 commit comments

Comments
 (0)