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
28ad4b4e
Commit
28ad4b4e
authored
Jun 30, 2019
by
Rafael J. Wysocki
Browse files
Options
Browse Files
Download
Plain Diff
Merge back PCI power management material for v5.3.
parents
471a739a
b51033e0
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
333 additions
and
41 deletions
+333
-41
drivers/acpi/power.c
drivers/acpi/power.c
+135
-0
drivers/pci/pci-acpi.c
drivers/pci/pci-acpi.c
+13
-1
drivers/pci/pci-driver.c
drivers/pci/pci-driver.c
+26
-5
drivers/pci/pci.c
drivers/pci/pci.c
+82
-34
drivers/pci/pci.h
drivers/pci/pci.h
+7
-1
drivers/pci/pcie/portdrv_core.c
drivers/pci/pcie/portdrv_core.c
+66
-0
include/acpi/acpi_bus.h
include/acpi/acpi_bus.h
+4
-0
No files found.
drivers/acpi/power.c
View file @
28ad4b4e
...
...
@@ -42,6 +42,11 @@ ACPI_MODULE_NAME("power");
#define ACPI_POWER_RESOURCE_STATE_ON 0x01
#define ACPI_POWER_RESOURCE_STATE_UNKNOWN 0xFF
struct
acpi_power_dependent_device
{
struct
device
*
dev
;
struct
list_head
node
;
};
struct
acpi_power_resource
{
struct
acpi_device
device
;
struct
list_head
list_node
;
...
...
@@ -51,6 +56,7 @@ struct acpi_power_resource {
unsigned
int
ref_count
;
bool
wakeup_enabled
;
struct
mutex
resource_lock
;
struct
list_head
dependents
;
};
struct
acpi_power_resource_entry
{
...
...
@@ -232,8 +238,121 @@ static int acpi_power_get_list_state(struct list_head *list, int *state)
return
0
;
}
static
int
acpi_power_resource_add_dependent
(
struct
acpi_power_resource
*
resource
,
struct
device
*
dev
)
{
struct
acpi_power_dependent_device
*
dep
;
int
ret
=
0
;
mutex_lock
(
&
resource
->
resource_lock
);
list_for_each_entry
(
dep
,
&
resource
->
dependents
,
node
)
{
/* Only add it once */
if
(
dep
->
dev
==
dev
)
goto
unlock
;
}
dep
=
kzalloc
(
sizeof
(
*
dep
),
GFP_KERNEL
);
if
(
!
dep
)
{
ret
=
-
ENOMEM
;
goto
unlock
;
}
dep
->
dev
=
dev
;
list_add_tail
(
&
dep
->
node
,
&
resource
->
dependents
);
dev_dbg
(
dev
,
"added power dependency to [%s]
\n
"
,
resource
->
name
);
unlock:
mutex_unlock
(
&
resource
->
resource_lock
);
return
ret
;
}
static
void
acpi_power_resource_remove_dependent
(
struct
acpi_power_resource
*
resource
,
struct
device
*
dev
)
{
struct
acpi_power_dependent_device
*
dep
;
mutex_lock
(
&
resource
->
resource_lock
);
list_for_each_entry
(
dep
,
&
resource
->
dependents
,
node
)
{
if
(
dep
->
dev
==
dev
)
{
list_del
(
&
dep
->
node
);
kfree
(
dep
);
dev_dbg
(
dev
,
"removed power dependency to [%s]
\n
"
,
resource
->
name
);
break
;
}
}
mutex_unlock
(
&
resource
->
resource_lock
);
}
/**
* acpi_device_power_add_dependent - Add dependent device of this ACPI device
* @adev: ACPI device pointer
* @dev: Dependent device
*
* If @adev has non-empty _PR0 the @dev is added as dependent device to all
* power resources returned by it. This means that whenever these power
* resources are turned _ON the dependent devices get runtime resumed. This
* is needed for devices such as PCI to allow its driver to re-initialize
* it after it went to D0uninitialized.
*
* If @adev does not have _PR0 this does nothing.
*
* Returns %0 in case of success and negative errno otherwise.
*/
int
acpi_device_power_add_dependent
(
struct
acpi_device
*
adev
,
struct
device
*
dev
)
{
struct
acpi_power_resource_entry
*
entry
;
struct
list_head
*
resources
;
int
ret
;
if
(
!
adev
->
flags
.
power_manageable
)
return
0
;
resources
=
&
adev
->
power
.
states
[
ACPI_STATE_D0
].
resources
;
list_for_each_entry
(
entry
,
resources
,
node
)
{
ret
=
acpi_power_resource_add_dependent
(
entry
->
resource
,
dev
);
if
(
ret
)
goto
err
;
}
return
0
;
err:
list_for_each_entry
(
entry
,
resources
,
node
)
acpi_power_resource_remove_dependent
(
entry
->
resource
,
dev
);
return
ret
;
}
/**
* acpi_device_power_remove_dependent - Remove dependent device
* @adev: ACPI device pointer
* @dev: Dependent device
*
* Does the opposite of acpi_device_power_add_dependent() and removes the
* dependent device if it is found. Can be called to @adev that does not
* have _PR0 as well.
*/
void
acpi_device_power_remove_dependent
(
struct
acpi_device
*
adev
,
struct
device
*
dev
)
{
struct
acpi_power_resource_entry
*
entry
;
struct
list_head
*
resources
;
if
(
!
adev
->
flags
.
power_manageable
)
return
;
resources
=
&
adev
->
power
.
states
[
ACPI_STATE_D0
].
resources
;
list_for_each_entry_reverse
(
entry
,
resources
,
node
)
acpi_power_resource_remove_dependent
(
entry
->
resource
,
dev
);
}
static
int
__acpi_power_on
(
struct
acpi_power_resource
*
resource
)
{
struct
acpi_power_dependent_device
*
dep
;
acpi_status
status
=
AE_OK
;
status
=
acpi_evaluate_object
(
resource
->
device
.
handle
,
"_ON"
,
NULL
,
NULL
);
...
...
@@ -243,6 +362,21 @@ static int __acpi_power_on(struct acpi_power_resource *resource)
ACPI_DEBUG_PRINT
((
ACPI_DB_INFO
,
"Power resource [%s] turned on
\n
"
,
resource
->
name
));
/*
* If there are other dependents on this power resource we need to
* resume them now so that their drivers can re-initialize the
* hardware properly after it went back to D0.
*/
if
(
list_empty
(
&
resource
->
dependents
)
||
list_is_singular
(
&
resource
->
dependents
))
return
0
;
list_for_each_entry
(
dep
,
&
resource
->
dependents
,
node
)
{
dev_dbg
(
dep
->
dev
,
"runtime resuming because [%s] turned on
\n
"
,
resource
->
name
);
pm_request_resume
(
dep
->
dev
);
}
return
0
;
}
...
...
@@ -810,6 +944,7 @@ int acpi_add_power_resource(acpi_handle handle)
ACPI_STA_DEFAULT
);
mutex_init
(
&
resource
->
resource_lock
);
INIT_LIST_HEAD
(
&
resource
->
list_node
);
INIT_LIST_HEAD
(
&
resource
->
dependents
);
resource
->
name
=
device
->
pnp
.
bus_id
;
strcpy
(
acpi_device_name
(
device
),
ACPI_POWER_DEVICE_NAME
);
strcpy
(
acpi_device_class
(
device
),
ACPI_POWER_CLASS
);
...
...
drivers/pci/pci-acpi.c
View file @
28ad4b4e
...
...
@@ -685,12 +685,21 @@ static pci_power_t acpi_pci_get_power_state(struct pci_dev *dev)
if
(
!
adev
||
!
acpi_device_power_manageable
(
adev
))
return
PCI_UNKNOWN
;
if
(
acpi_device_get_power
(
adev
,
&
state
)
||
state
==
ACPI_STATE_UNKNOWN
)
state
=
adev
->
power
.
state
;
if
(
state
==
ACPI_STATE_UNKNOWN
)
return
PCI_UNKNOWN
;
return
state_conv
[
state
];
}
static
void
acpi_pci_refresh_power_state
(
struct
pci_dev
*
dev
)
{
struct
acpi_device
*
adev
=
ACPI_COMPANION
(
&
dev
->
dev
);
if
(
adev
&&
acpi_device_power_manageable
(
adev
))
acpi_device_update_power
(
adev
,
NULL
);
}
static
int
acpi_pci_propagate_wakeup
(
struct
pci_bus
*
bus
,
bool
enable
)
{
while
(
bus
->
parent
)
{
...
...
@@ -748,6 +757,7 @@ static const struct pci_platform_pm_ops acpi_pci_platform_pm = {
.
is_manageable
=
acpi_pci_power_manageable
,
.
set_state
=
acpi_pci_set_power_state
,
.
get_state
=
acpi_pci_get_power_state
,
.
refresh_state
=
acpi_pci_refresh_power_state
,
.
choose_state
=
acpi_pci_choose_state
,
.
set_wakeup
=
acpi_pci_wakeup
,
.
need_resume
=
acpi_pci_need_resume
,
...
...
@@ -901,6 +911,7 @@ static void pci_acpi_setup(struct device *dev)
device_wakeup_enable
(
dev
);
acpi_pci_wakeup
(
pci_dev
,
false
);
acpi_device_power_add_dependent
(
adev
,
dev
);
}
static
void
pci_acpi_cleanup
(
struct
device
*
dev
)
...
...
@@ -913,6 +924,7 @@ static void pci_acpi_cleanup(struct device *dev)
pci_acpi_remove_pm_notifier
(
adev
);
if
(
adev
->
wakeup
.
flags
.
valid
)
{
acpi_device_power_remove_dependent
(
adev
,
dev
);
if
(
pci_dev
->
bridge_d3
)
device_wakeup_disable
(
dev
);
...
...
drivers/pci/pci-driver.c
View file @
28ad4b4e
...
...
@@ -678,6 +678,7 @@ static bool pci_has_legacy_pm_support(struct pci_dev *pci_dev)
static
int
pci_pm_prepare
(
struct
device
*
dev
)
{
struct
device_driver
*
drv
=
dev
->
driver
;
struct
pci_dev
*
pci_dev
=
to_pci_dev
(
dev
);
if
(
drv
&&
drv
->
pm
&&
drv
->
pm
->
prepare
)
{
int
error
=
drv
->
pm
->
prepare
(
dev
);
...
...
@@ -687,7 +688,15 @@ static int pci_pm_prepare(struct device *dev)
if
(
!
error
&&
dev_pm_test_driver_flags
(
dev
,
DPM_FLAG_SMART_PREPARE
))
return
0
;
}
return
pci_dev_keep_suspended
(
to_pci_dev
(
dev
));
if
(
pci_dev_need_resume
(
pci_dev
))
return
0
;
/*
* The PME setting needs to be adjusted here in case the direct-complete
* optimization is used with respect to this device.
*/
pci_dev_adjust_pme
(
pci_dev
);
return
1
;
}
static
void
pci_pm_complete
(
struct
device
*
dev
)
...
...
@@ -701,7 +710,14 @@ static void pci_pm_complete(struct device *dev)
if
(
pm_runtime_suspended
(
dev
)
&&
pm_resume_via_firmware
())
{
pci_power_t
pre_sleep_state
=
pci_dev
->
current_state
;
pci_update_current_state
(
pci_dev
,
pci_dev
->
current_state
);
pci_refresh_power_state
(
pci_dev
);
/*
* On platforms with ACPI this check may also trigger for
* devices sharing power resources if one of those power
* resources has been activated as a result of a change of the
* power state of another device sharing it. However, in that
* case it is also better to resume the device, in general.
*/
if
(
pci_dev
->
current_state
<
pre_sleep_state
)
pm_request_resume
(
dev
);
}
...
...
@@ -757,9 +773,11 @@ static int pci_pm_suspend(struct device *dev)
* better to resume the device from runtime suspend here.
*/
if
(
!
dev_pm_test_driver_flags
(
dev
,
DPM_FLAG_SMART_SUSPEND
)
||
!
pci_dev_keep_suspended
(
pci_dev
))
{
pci_dev_need_resume
(
pci_dev
))
{
pm_runtime_resume
(
dev
);
pci_dev
->
state_saved
=
false
;
}
else
{
pci_dev_adjust_pme
(
pci_dev
);
}
if
(
pm
->
suspend
)
{
...
...
@@ -1130,10 +1148,13 @@ static int pci_pm_poweroff(struct device *dev)
/* The reason to do that is the same as in pci_pm_suspend(). */
if
(
!
dev_pm_test_driver_flags
(
dev
,
DPM_FLAG_SMART_SUSPEND
)
||
!
pci_dev_keep_suspended
(
pci_dev
))
pci_dev_need_resume
(
pci_dev
))
{
pm_runtime_resume
(
dev
);
pci_dev
->
state_saved
=
false
;
}
else
{
pci_dev_adjust_pme
(
pci_dev
);
}
pci_dev
->
state_saved
=
false
;
if
(
pm
->
poweroff
)
{
int
error
;
...
...
drivers/pci/pci.c
View file @
28ad4b4e
...
...
@@ -777,6 +777,12 @@ static inline pci_power_t platform_pci_get_power_state(struct pci_dev *dev)
return
pci_platform_pm
?
pci_platform_pm
->
get_state
(
dev
)
:
PCI_UNKNOWN
;
}
static
inline
void
platform_pci_refresh_power_state
(
struct
pci_dev
*
dev
)
{
if
(
pci_platform_pm
&&
pci_platform_pm
->
refresh_state
)
pci_platform_pm
->
refresh_state
(
dev
);
}
static
inline
pci_power_t
platform_pci_choose_state
(
struct
pci_dev
*
dev
)
{
return
pci_platform_pm
?
...
...
@@ -937,6 +943,21 @@ void pci_update_current_state(struct pci_dev *dev, pci_power_t state)
}
}
/**
* pci_refresh_power_state - Refresh the given device's power state data
* @dev: Target PCI device.
*
* Ask the platform to refresh the devices power state information and invoke
* pci_update_current_state() to update its current PCI power state.
*/
void
pci_refresh_power_state
(
struct
pci_dev
*
dev
)
{
if
(
platform_pci_power_manageable
(
dev
))
platform_pci_refresh_power_state
(
dev
);
pci_update_current_state
(
dev
,
dev
->
current_state
);
}
/**
* pci_power_up - Put the given device into D0 forcibly
* @dev: PCI device to power up
...
...
@@ -1004,15 +1025,10 @@ static void __pci_start_power_transition(struct pci_dev *dev, pci_power_t state)
if
(
state
==
PCI_D0
)
{
pci_platform_power_transition
(
dev
,
PCI_D0
);
/*
* Mandatory power management transition delays, see
* PCI Express Base Specification Revision 2.0 Section
* 6.6.1: Conventional Reset. Do not delay for
* devices powered on/off by corresponding bridge,
* because have already delayed for the bridge.
* Mandatory power management transition delays are
* handled in the PCIe portdrv resume hooks.
*/
if
(
dev
->
runtime_d3cold
)
{
if
(
dev
->
d3cold_delay
&&
!
dev
->
imm_ready
)
msleep
(
dev
->
d3cold_delay
);
/*
* When powering on a bridge from D3cold, the
* whole hierarchy may be powered on into
...
...
@@ -2065,6 +2081,13 @@ static void pci_pme_list_scan(struct work_struct *work)
*/
if
(
bridge
&&
bridge
->
current_state
!=
PCI_D0
)
continue
;
/*
* If the device is in D3cold it should not be
* polled either.
*/
if
(
pme_dev
->
dev
->
current_state
==
PCI_D3cold
)
continue
;
pci_pme_wakeup
(
pme_dev
->
dev
,
NULL
);
}
else
{
list_del
(
&
pme_dev
->
list
);
...
...
@@ -2459,45 +2482,56 @@ bool pci_dev_run_wake(struct pci_dev *dev)
EXPORT_SYMBOL_GPL
(
pci_dev_run_wake
);
/**
* pci_dev_
keep_suspended - Check if the device can stay in the suspended stat
e.
* pci_dev_
need_resume - Check if it is necessary to resume the devic
e.
* @pci_dev: Device to check.
*
* Return 'true' if the device is
runtime-suspended, it doesn't have
to be
* Return 'true' if the device is
not runtime-suspended or it has
to be
* reconfigured due to wakeup settings difference between system and runtime
* suspend and the current power state of it is suitable for the upcoming
* (system) transition.
*
* If the device is not configured for system wakeup, disable PME for it before
* returning 'true' to prevent it from waking up the system unnecessarily.
* suspend, or the current power state of it is not suitable for the upcoming
* (system-wide) transition.
*/
bool
pci_dev_
keep_suspended
(
struct
pci_dev
*
pci_dev
)
bool
pci_dev_
need_resume
(
struct
pci_dev
*
pci_dev
)
{
struct
device
*
dev
=
&
pci_dev
->
dev
;
bool
wakeup
=
device_may_wakeup
(
dev
)
;
pci_power_t
target_state
;
if
(
!
pm_runtime_suspended
(
dev
)
||
pci_target_state
(
pci_dev
,
wakeup
)
!=
pci_dev
->
current_state
||
platform_pci_need_resume
(
pci_dev
))
return
false
;
if
(
!
pm_runtime_suspended
(
dev
)
||
platform_pci_need_resume
(
pci_dev
))
return
true
;
target_state
=
pci_target_state
(
pci_dev
,
device_may_wakeup
(
dev
))
;
/*
* At this point the device is good to go unless it's been configured
* to generate PME at the runtime suspend time, but it is not supposed
* to wake up the system. In that case, simply disable PME for it
* (it will have to be re-enabled on exit from system resume).
*
* If the device's power state is D3cold and the platform check above
* hasn't triggered, the device's configuration is suitable and we don't
* need to manipulate it at all.
* If the earlier platform check has not triggered, D3cold is just power
* removal on top of D3hot, so no need to resume the device in that
* case.
*/
return
target_state
!=
pci_dev
->
current_state
&&
target_state
!=
PCI_D3cold
&&
pci_dev
->
current_state
!=
PCI_D3hot
;
}
/**
* pci_dev_adjust_pme - Adjust PME setting for a suspended device.
* @pci_dev: Device to check.
*
* If the device is suspended and it is not configured for system wakeup,
* disable PME for it to prevent it from waking up the system unnecessarily.
*
* Note that if the device's power state is D3cold and the platform check in
* pci_dev_need_resume() has not triggered, the device's configuration need not
* be changed.
*/
void
pci_dev_adjust_pme
(
struct
pci_dev
*
pci_dev
)
{
struct
device
*
dev
=
&
pci_dev
->
dev
;
spin_lock_irq
(
&
dev
->
power
.
lock
);
if
(
pm_runtime_suspended
(
dev
)
&&
pci_dev
->
current_state
<
PCI_D3cold
&&
!
wakeup
)
if
(
pm_runtime_suspended
(
dev
)
&&
!
device_may_wakeup
(
dev
)
&&
pci_dev
->
current_state
<
PCI_D3cold
)
__pci_pme_active
(
pci_dev
,
false
);
spin_unlock_irq
(
&
dev
->
power
.
lock
);
return
true
;
}
/**
...
...
@@ -4568,14 +4602,16 @@ static int pci_pm_reset(struct pci_dev *dev, int probe)
return
pci_dev_wait
(
dev
,
"PM D3->D0"
,
PCIE_RESET_READY_POLL_MS
);
}
/**
* pcie_wait_for_link - Wait until link is active or inactive
* pcie_wait_for_link
_delay
- Wait until link is active or inactive
* @pdev: Bridge device
* @active: waiting for active or inactive?
* @delay: Delay to wait after link has become active (in ms)
*
* Use this to wait till link becomes active or inactive.
*/
bool
pcie_wait_for_link
(
struct
pci_dev
*
pdev
,
bool
active
)
bool
pcie_wait_for_link
_delay
(
struct
pci_dev
*
pdev
,
bool
active
,
int
delay
)
{
int
timeout
=
1000
;
bool
ret
;
...
...
@@ -4612,13 +4648,25 @@ bool pcie_wait_for_link(struct pci_dev *pdev, bool active)
timeout
-=
10
;
}
if
(
active
&&
ret
)
msleep
(
100
);
msleep
(
delay
);
else
if
(
ret
!=
active
)
pci_info
(
pdev
,
"Data Link Layer Link Active not %s in 1000 msec
\n
"
,
active
?
"set"
:
"cleared"
);
return
ret
==
active
;
}
/**
* pcie_wait_for_link - Wait until link is active or inactive
* @pdev: Bridge device
* @active: waiting for active or inactive?
*
* Use this to wait till link becomes active or inactive.
*/
bool
pcie_wait_for_link
(
struct
pci_dev
*
pdev
,
bool
active
)
{
return
pcie_wait_for_link_delay
(
pdev
,
active
,
100
);
}
void
pci_reset_secondary_bus
(
struct
pci_dev
*
dev
)
{
u16
ctrl
;
...
...
drivers/pci/pci.h
View file @
28ad4b4e
...
...
@@ -51,6 +51,8 @@ int pci_bus_error_reset(struct pci_dev *dev);
*
* @get_state: queries the platform firmware for a device's current power state
*
* @refresh_state: asks the platform to refresh the device's power state data
*
* @choose_state: returns PCI power state of given device preferred by the
* platform; to be used during system-wide transitions from a
* sleeping state to the working state and vice versa
...
...
@@ -69,6 +71,7 @@ struct pci_platform_pm_ops {
bool
(
*
is_manageable
)(
struct
pci_dev
*
dev
);
int
(
*
set_state
)(
struct
pci_dev
*
dev
,
pci_power_t
state
);
pci_power_t
(
*
get_state
)(
struct
pci_dev
*
dev
);
void
(
*
refresh_state
)(
struct
pci_dev
*
dev
);
pci_power_t
(
*
choose_state
)(
struct
pci_dev
*
dev
);
int
(
*
set_wakeup
)(
struct
pci_dev
*
dev
,
bool
enable
);
bool
(
*
need_resume
)(
struct
pci_dev
*
dev
);
...
...
@@ -76,13 +79,15 @@ struct pci_platform_pm_ops {
int
pci_set_platform_pm
(
const
struct
pci_platform_pm_ops
*
ops
);
void
pci_update_current_state
(
struct
pci_dev
*
dev
,
pci_power_t
state
);
void
pci_refresh_power_state
(
struct
pci_dev
*
dev
);
void
pci_power_up
(
struct
pci_dev
*
dev
);
void
pci_disable_enabled_device
(
struct
pci_dev
*
dev
);
int
pci_finish_runtime_suspend
(
struct
pci_dev
*
dev
);
void
pcie_clear_root_pme_status
(
struct
pci_dev
*
dev
);
int
__pci_pme_wakeup
(
struct
pci_dev
*
dev
,
void
*
ign
);
void
pci_pme_restore
(
struct
pci_dev
*
dev
);
bool
pci_dev_keep_suspended
(
struct
pci_dev
*
dev
);
bool
pci_dev_need_resume
(
struct
pci_dev
*
dev
);
void
pci_dev_adjust_pme
(
struct
pci_dev
*
dev
);
void
pci_dev_complete_resume
(
struct
pci_dev
*
pci_dev
);
void
pci_config_pm_runtime_get
(
struct
pci_dev
*
dev
);
void
pci_config_pm_runtime_put
(
struct
pci_dev
*
dev
);
...
...
@@ -493,6 +498,7 @@ static inline int pci_dev_specific_disable_acs_redir(struct pci_dev *dev)
void
pcie_do_recovery
(
struct
pci_dev
*
dev
,
enum
pci_channel_state
state
,
u32
service
);
bool
pcie_wait_for_link_delay
(
struct
pci_dev
*
pdev
,
bool
active
,
int
delay
);
bool
pcie_wait_for_link
(
struct
pci_dev
*
pdev
,
bool
active
);
#ifdef CONFIG_PCIEASPM
void
pcie_aspm_init_link_state
(
struct
pci_dev
*
pdev
);
...
...
drivers/pci/pcie/portdrv_core.c
View file @
28ad4b4e
...
...
@@ -9,6 +9,7 @@
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/pm.h>
#include <linux/pm_runtime.h>
...
...
@@ -378,6 +379,67 @@ static int pm_iter(struct device *dev, void *data)
return
0
;
}
static
int
get_downstream_delay
(
struct
pci_bus
*
bus
)
{
struct
pci_dev
*
pdev
;
int
min_delay
=
100
;
int
max_delay
=
0
;
list_for_each_entry
(
pdev
,
&
bus
->
devices
,
bus_list
)
{
if
(
!
pdev
->
imm_ready
)
min_delay
=
0
;
else
if
(
pdev
->
d3cold_delay
<
min_delay
)
min_delay
=
pdev
->
d3cold_delay
;
if
(
pdev
->
d3cold_delay
>
max_delay
)
max_delay
=
pdev
->
d3cold_delay
;
}
return
max
(
min_delay
,
max_delay
);
}
/*
* wait_for_downstream_link - Wait for downstream link to establish
* @pdev: PCIe port whose downstream link is waited
*
* Handle delays according to PCIe 4.0 section 6.6.1 before configuration
* access to the downstream component is permitted.
*
* This blocks PCI core resume of the hierarchy below this port until the
* link is trained. Should be called before resuming port services to
* prevent pciehp from starting to tear-down the hierarchy too soon.
*/
static
void
wait_for_downstream_link
(
struct
pci_dev
*
pdev
)
{
int
delay
;
if
(
pci_pcie_type
(
pdev
)
!=
PCI_EXP_TYPE_ROOT_PORT
&&
pci_pcie_type
(
pdev
)
!=
PCI_EXP_TYPE_DOWNSTREAM
)
return
;
if
(
pci_dev_is_disconnected
(
pdev
))
return
;
if
(
!
pdev
->
subordinate
||
list_empty
(
&
pdev
->
subordinate
->
devices
)
||
!
pdev
->
bridge_d3
)
return
;
delay
=
get_downstream_delay
(
pdev
->
subordinate
);
if
(
!
delay
)
return
;
dev_dbg
(
&
pdev
->
dev
,
"waiting downstream link for %d ms
\n
"
,
delay
);
/*
* If downstream port does not support speeds greater than 5 GT/s
* need to wait 100ms. For higher speeds (gen3) we need to wait
* first for the data link layer to become active.
*/
if
(
pcie_get_speed_cap
(
pdev
)
<=
PCIE_SPEED_5_0GT
)
msleep
(
delay
);
else
pcie_wait_for_link_delay
(
pdev
,
true
,
delay
);
}
/**
* pcie_port_device_suspend - suspend port services associated with a PCIe port
* @dev: PCI Express port to handle
...
...
@@ -391,6 +453,8 @@ int pcie_port_device_suspend(struct device *dev)
int
pcie_port_device_resume_noirq
(
struct
device
*
dev
)
{
size_t
off
=
offsetof
(
struct
pcie_port_service_driver
,
resume_noirq
);
wait_for_downstream_link
(
to_pci_dev
(
dev
));
return
device_for_each_child
(
dev
,
&
off
,
pm_iter
);
}
...
...
@@ -421,6 +485,8 @@ int pcie_port_device_runtime_suspend(struct device *dev)
int
pcie_port_device_runtime_resume
(
struct
device
*
dev
)
{
size_t
off
=
offsetof
(
struct
pcie_port_service_driver
,
runtime_resume
);
wait_for_downstream_link
(
to_pci_dev
(
dev
));
return
device_for_each_child
(
dev
,
&
off
,
pm_iter
);
}
#endif
/* PM */
...
...
include/acpi/acpi_bus.h
View file @
28ad4b4e
...
...
@@ -513,6 +513,10 @@ int acpi_device_fix_up_power(struct acpi_device *device);
int
acpi_bus_update_power
(
acpi_handle
handle
,
int
*
state_p
);
int
acpi_device_update_power
(
struct
acpi_device
*
device
,
int
*
state_p
);
bool
acpi_bus_power_manageable
(
acpi_handle
handle
);
int
acpi_device_power_add_dependent
(
struct
acpi_device
*
adev
,
struct
device
*
dev
);
void
acpi_device_power_remove_dependent
(
struct
acpi_device
*
adev
,
struct
device
*
dev
);
#ifdef CONFIG_PM
bool
acpi_bus_can_wakeup
(
acpi_handle
handle
);
...
...
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