Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
L
linux
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
linux
Commits
2af3411f
Commit
2af3411f
authored
Dec 01, 2014
by
Rafael J. Wysocki
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'pm-opp' into pm-cpufreq
parents
8a497cfd
b4037aaa
Changes
2
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
166 additions
and
42 deletions
+166
-42
drivers/base/power/opp.c
drivers/base/power/opp.c
+155
-41
include/linux/pm_opp.h
include/linux/pm_opp.h
+11
-1
No files found.
drivers/base/power/opp.c
View file @
2af3411f
...
...
@@ -49,11 +49,12 @@
* are protected by the dev_opp_list_lock for integrity.
* IMPORTANT: the opp nodes should be maintained in increasing
* order.
* @dynamic: not-created from static DT entries.
* @available: true/false - marks if this OPP as available or not
* @rate: Frequency in hertz
* @u_volt: Nominal voltage in microvolts corresponding to this OPP
* @dev_opp: points back to the device_opp struct this opp belongs to
* @head: RCU callback head used for deferred freeing
* @
rcu_
head: RCU callback head used for deferred freeing
*
* This structure stores the OPP information for a given device.
*/
...
...
@@ -61,11 +62,12 @@ struct dev_pm_opp {
struct
list_head
node
;
bool
available
;
bool
dynamic
;
unsigned
long
rate
;
unsigned
long
u_volt
;
struct
device_opp
*
dev_opp
;
struct
rcu_head
head
;
struct
rcu_head
rcu_
head
;
};
/**
...
...
@@ -76,7 +78,8 @@ struct dev_pm_opp {
* RCU usage: nodes are not modified in the list of device_opp,
* however addition is possible and is secured by dev_opp_list_lock
* @dev: device pointer
* @head: notifier head to notify the OPP availability changes.
* @srcu_head: notifier head to notify the OPP availability changes.
* @rcu_head: RCU callback head used for deferred freeing
* @opp_list: list of opps
*
* This is an internal data structure maintaining the link to opps attached to
...
...
@@ -87,7 +90,8 @@ struct device_opp {
struct
list_head
node
;
struct
device
*
dev
;
struct
srcu_notifier_head
head
;
struct
srcu_notifier_head
srcu_head
;
struct
rcu_head
rcu_head
;
struct
list_head
opp_list
;
};
...
...
@@ -378,30 +382,8 @@ struct dev_pm_opp *dev_pm_opp_find_freq_floor(struct device *dev,
}
EXPORT_SYMBOL_GPL
(
dev_pm_opp_find_freq_floor
);
/**
* dev_pm_opp_add() - Add an OPP table from a table definitions
* @dev: device for which we do this operation
* @freq: Frequency in Hz for this OPP
* @u_volt: Voltage in uVolts for this OPP
*
* This function adds an opp definition to the opp list and returns status.
* The opp is made available by default and it can be controlled using
* dev_pm_opp_enable/disable functions.
*
* Locking: The internal device_opp and opp structures are RCU protected.
* Hence this function internally uses RCU updater strategy with mutex locks
* to keep the integrity of the internal data structures. Callers should ensure
* that this function is *NOT* called under RCU protection or in contexts where
* mutex cannot be locked.
*
* Return:
* 0: On success OR
* Duplicate OPPs (both freq and volt are same) and opp->available
* -EEXIST: Freq are same and volt are different OR
* Duplicate OPPs (both freq and volt are same) and !opp->available
* -ENOMEM: Memory allocation failure
*/
int
dev_pm_opp_add
(
struct
device
*
dev
,
unsigned
long
freq
,
unsigned
long
u_volt
)
static
int
dev_pm_opp_add_dynamic
(
struct
device
*
dev
,
unsigned
long
freq
,
unsigned
long
u_volt
,
bool
dynamic
)
{
struct
device_opp
*
dev_opp
=
NULL
;
struct
dev_pm_opp
*
opp
,
*
new_opp
;
...
...
@@ -417,6 +399,13 @@ int dev_pm_opp_add(struct device *dev, unsigned long freq, unsigned long u_volt)
/* Hold our list modification lock here */
mutex_lock
(
&
dev_opp_list_lock
);
/* populate the opp table */
new_opp
->
dev_opp
=
dev_opp
;
new_opp
->
rate
=
freq
;
new_opp
->
u_volt
=
u_volt
;
new_opp
->
available
=
true
;
new_opp
->
dynamic
=
dynamic
;
/* Check for existing list for 'dev' */
dev_opp
=
find_device_opp
(
dev
);
if
(
IS_ERR
(
dev_opp
))
{
...
...
@@ -436,19 +425,15 @@ int dev_pm_opp_add(struct device *dev, unsigned long freq, unsigned long u_volt)
}
dev_opp
->
dev
=
dev
;
srcu_init_notifier_head
(
&
dev_opp
->
head
);
srcu_init_notifier_head
(
&
dev_opp
->
srcu_
head
);
INIT_LIST_HEAD
(
&
dev_opp
->
opp_list
);
/* Secure the device list modification */
list_add_rcu
(
&
dev_opp
->
node
,
&
dev_opp_list
);
head
=
&
dev_opp
->
opp_list
;
goto
list_add
;
}
/* populate the opp table */
new_opp
->
dev_opp
=
dev_opp
;
new_opp
->
rate
=
freq
;
new_opp
->
u_volt
=
u_volt
;
new_opp
->
available
=
true
;
/*
* Insert new OPP in order of increasing frequency
* and discard if already present
...
...
@@ -474,6 +459,7 @@ int dev_pm_opp_add(struct device *dev, unsigned long freq, unsigned long u_volt)
return
ret
;
}
list_add:
list_add_rcu
(
&
new_opp
->
node
,
head
);
mutex_unlock
(
&
dev_opp_list_lock
);
...
...
@@ -481,11 +467,109 @@ int dev_pm_opp_add(struct device *dev, unsigned long freq, unsigned long u_volt)
* Notify the changes in the availability of the operable
* frequency/voltage list.
*/
srcu_notifier_call_chain
(
&
dev_opp
->
head
,
OPP_EVENT_ADD
,
new_opp
);
srcu_notifier_call_chain
(
&
dev_opp
->
srcu_
head
,
OPP_EVENT_ADD
,
new_opp
);
return
0
;
}
/**
* dev_pm_opp_add() - Add an OPP table from a table definitions
* @dev: device for which we do this operation
* @freq: Frequency in Hz for this OPP
* @u_volt: Voltage in uVolts for this OPP
*
* This function adds an opp definition to the opp list and returns status.
* The opp is made available by default and it can be controlled using
* dev_pm_opp_enable/disable functions.
*
* Locking: The internal device_opp and opp structures are RCU protected.
* Hence this function internally uses RCU updater strategy with mutex locks
* to keep the integrity of the internal data structures. Callers should ensure
* that this function is *NOT* called under RCU protection or in contexts where
* mutex cannot be locked.
*
* Return:
* 0: On success OR
* Duplicate OPPs (both freq and volt are same) and opp->available
* -EEXIST: Freq are same and volt are different OR
* Duplicate OPPs (both freq and volt are same) and !opp->available
* -ENOMEM: Memory allocation failure
*/
int
dev_pm_opp_add
(
struct
device
*
dev
,
unsigned
long
freq
,
unsigned
long
u_volt
)
{
return
dev_pm_opp_add_dynamic
(
dev
,
freq
,
u_volt
,
true
);
}
EXPORT_SYMBOL_GPL
(
dev_pm_opp_add
);
static
void
kfree_opp_rcu
(
struct
rcu_head
*
head
)
{
struct
dev_pm_opp
*
opp
=
container_of
(
head
,
struct
dev_pm_opp
,
rcu_head
);
kfree_rcu
(
opp
,
rcu_head
);
}
static
void
kfree_device_rcu
(
struct
rcu_head
*
head
)
{
struct
device_opp
*
device_opp
=
container_of
(
head
,
struct
device_opp
,
rcu_head
);
kfree
(
device_opp
);
}
void
__dev_pm_opp_remove
(
struct
device_opp
*
dev_opp
,
struct
dev_pm_opp
*
opp
)
{
/*
* Notify the changes in the availability of the operable
* frequency/voltage list.
*/
srcu_notifier_call_chain
(
&
dev_opp
->
srcu_head
,
OPP_EVENT_REMOVE
,
opp
);
list_del_rcu
(
&
opp
->
node
);
call_srcu
(
&
dev_opp
->
srcu_head
.
srcu
,
&
opp
->
rcu_head
,
kfree_opp_rcu
);
if
(
list_empty
(
&
dev_opp
->
opp_list
))
{
list_del_rcu
(
&
dev_opp
->
node
);
call_srcu
(
&
dev_opp
->
srcu_head
.
srcu
,
&
dev_opp
->
rcu_head
,
kfree_device_rcu
);
}
}
/**
* dev_pm_opp_remove() - Remove an OPP from OPP list
* @dev: device for which we do this operation
* @freq: OPP to remove with matching 'freq'
*
* This function removes an opp from the opp list.
*/
void
dev_pm_opp_remove
(
struct
device
*
dev
,
unsigned
long
freq
)
{
struct
dev_pm_opp
*
opp
;
struct
device_opp
*
dev_opp
;
bool
found
=
false
;
/* Hold our list modification lock here */
mutex_lock
(
&
dev_opp_list_lock
);
dev_opp
=
find_device_opp
(
dev
);
if
(
IS_ERR
(
dev_opp
))
goto
unlock
;
list_for_each_entry
(
opp
,
&
dev_opp
->
opp_list
,
node
)
{
if
(
opp
->
rate
==
freq
)
{
found
=
true
;
break
;
}
}
if
(
!
found
)
{
dev_warn
(
dev
,
"%s: Couldn't find OPP with freq: %lu
\n
"
,
__func__
,
freq
);
goto
unlock
;
}
__dev_pm_opp_remove
(
dev_opp
,
opp
);
unlock:
mutex_unlock
(
&
dev_opp_list_lock
);
}
EXPORT_SYMBOL_GPL
(
dev_pm_opp_remove
);
/**
* opp_set_availability() - helper to set the availability of an opp
* @dev: device for which we do this operation
...
...
@@ -557,14 +641,14 @@ static int opp_set_availability(struct device *dev, unsigned long freq,
list_replace_rcu
(
&
opp
->
node
,
&
new_opp
->
node
);
mutex_unlock
(
&
dev_opp_list_lock
);
kfree_rcu
(
opp
,
head
);
call_srcu
(
&
dev_opp
->
srcu_head
.
srcu
,
&
opp
->
rcu_head
,
kfree_opp_rcu
);
/* Notify the change of the OPP availability */
if
(
availability_req
)
srcu_notifier_call_chain
(
&
dev_opp
->
head
,
OPP_EVENT_ENABLE
,
srcu_notifier_call_chain
(
&
dev_opp
->
srcu_
head
,
OPP_EVENT_ENABLE
,
new_opp
);
else
srcu_notifier_call_chain
(
&
dev_opp
->
head
,
OPP_EVENT_DISABLE
,
srcu_notifier_call_chain
(
&
dev_opp
->
srcu_
head
,
OPP_EVENT_DISABLE
,
new_opp
);
return
0
;
...
...
@@ -629,7 +713,7 @@ struct srcu_notifier_head *dev_pm_opp_get_notifier(struct device *dev)
if
(
IS_ERR
(
dev_opp
))
return
ERR_CAST
(
dev_opp
);
/* matching type */
return
&
dev_opp
->
head
;
return
&
dev_opp
->
srcu_
head
;
}
#ifdef CONFIG_OF
...
...
@@ -666,7 +750,7 @@ int of_init_opp_table(struct device *dev)
unsigned
long
freq
=
be32_to_cpup
(
val
++
)
*
1000
;
unsigned
long
volt
=
be32_to_cpup
(
val
++
);
if
(
dev_pm_opp_add
(
dev
,
freq
,
volt
))
if
(
dev_pm_opp_add
_dynamic
(
dev
,
freq
,
volt
,
false
))
dev_warn
(
dev
,
"%s: Failed to add OPP %ld
\n
"
,
__func__
,
freq
);
nr
-=
2
;
...
...
@@ -675,4 +759,34 @@ int of_init_opp_table(struct device *dev)
return
0
;
}
EXPORT_SYMBOL_GPL
(
of_init_opp_table
);
/**
* of_free_opp_table() - Free OPP table entries created from static DT entries
* @dev: device pointer used to lookup device OPPs.
*
* Free OPPs created using static entries present in DT.
*/
void
of_free_opp_table
(
struct
device
*
dev
)
{
struct
device_opp
*
dev_opp
=
find_device_opp
(
dev
);
struct
dev_pm_opp
*
opp
,
*
tmp
;
/* Check for existing list for 'dev' */
dev_opp
=
find_device_opp
(
dev
);
if
(
WARN
(
IS_ERR
(
dev_opp
),
"%s: dev_opp: %ld
\n
"
,
dev_name
(
dev
),
PTR_ERR
(
dev_opp
)))
return
;
/* Hold our list modification lock here */
mutex_lock
(
&
dev_opp_list_lock
);
/* Free static OPPs */
list_for_each_entry_safe
(
opp
,
tmp
,
&
dev_opp
->
opp_list
,
node
)
{
if
(
!
opp
->
dynamic
)
__dev_pm_opp_remove
(
dev_opp
,
opp
);
}
mutex_unlock
(
&
dev_opp_list_lock
);
}
EXPORT_SYMBOL_GPL
(
of_free_opp_table
);
#endif
include/linux/pm_opp.h
View file @
2af3411f
...
...
@@ -21,7 +21,7 @@ struct dev_pm_opp;
struct
device
;
enum
dev_pm_opp_event
{
OPP_EVENT_ADD
,
OPP_EVENT_ENABLE
,
OPP_EVENT_DISABLE
,
OPP_EVENT_ADD
,
OPP_EVENT_
REMOVE
,
OPP_EVENT_
ENABLE
,
OPP_EVENT_DISABLE
,
};
#if defined(CONFIG_PM_OPP)
...
...
@@ -44,6 +44,7 @@ struct dev_pm_opp *dev_pm_opp_find_freq_ceil(struct device *dev,
int
dev_pm_opp_add
(
struct
device
*
dev
,
unsigned
long
freq
,
unsigned
long
u_volt
);
void
dev_pm_opp_remove
(
struct
device
*
dev
,
unsigned
long
freq
);
int
dev_pm_opp_enable
(
struct
device
*
dev
,
unsigned
long
freq
);
...
...
@@ -90,6 +91,10 @@ static inline int dev_pm_opp_add(struct device *dev, unsigned long freq,
return
-
EINVAL
;
}
static
inline
void
dev_pm_opp_remove
(
struct
device
*
dev
,
unsigned
long
freq
)
{
}
static
inline
int
dev_pm_opp_enable
(
struct
device
*
dev
,
unsigned
long
freq
)
{
return
0
;
...
...
@@ -109,11 +114,16 @@ static inline struct srcu_notifier_head *dev_pm_opp_get_notifier(
#if defined(CONFIG_PM_OPP) && defined(CONFIG_OF)
int
of_init_opp_table
(
struct
device
*
dev
);
void
of_free_opp_table
(
struct
device
*
dev
);
#else
static
inline
int
of_init_opp_table
(
struct
device
*
dev
)
{
return
-
EINVAL
;
}
static
inline
void
of_free_opp_table
(
struct
device
*
dev
)
{
}
#endif
#endif
/* __LINUX_OPP_H__ */
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment