Power Management

The Power Manager is responsible for system and device power management. The power management behavior can be customized by power policy configuration and direct power API requests which allows you to adjust system power savings to the current firmware activity.

node sw_driver [
	<b>SW Driver</b>
	* DSP power control over IPC protocol
]

node fw [
	<b>SOF Zephyr Library</b>

	* Exposes interface of <i>SOF with XTOS</i> for Host Power IPC handling
	* Executes requested sequence of Zephyr power operations
	* Waits and verifies power request completion

	---
	<b>Zephyr Power Management</b>
	<i>Generic RTOS Power Management service</i>

	* Manages System Power States
	* Manages Power Policies
	* Manages Device Runtime Power

	---
	<b>SoC HAL</b>
	<i>Hardware specific power control</i>

	* Moves SoC and its resources to power state requested by Zephyr
	* Interacts directly with hardware and power registers
	* Suppors different power states depending on the target SoC
]

sw_driver -down-> fw : <<IPC>> <b>Set Dx</b> - power state transition D0/D3\n<<IPC>> <b>Set D0ix</b> - power gating override (on/off)

Figure 65 Participants of the Firmware power management.

Zephyr Power Management documentation

DSP Cores

Each DSP core can be separately powered up and down.

The assumption is that DSP #0 is a primary core and is responsible for powering up all secondary cores. The primary core powers up the secondary cores on Set Dx IPC request.

The secondary core shall be powered up prior to any task allocated to it by the SW driver.

Power Transitions

There are three DSP core power states:

DSP Power State

Zephyr Power State

Notes

D0

PM_STATE_ACTIVE

built-in state, no extra mapping required

D0i3

PM_STATE_RUNTIME_IDLE

custom mapping in the Device Tree d0i3: idle { power-state-name = “runtime-idle” }

D3

PM_STATE_SOFT_OFF

custom mapping in the Device Tree d3: off { power-state-name = “soft-off” }

A major consumer of power related to the main part of the DSP subsystem is a source of the clock that is wired to the DSP core and the DSP core itself. Transitions to lower power states focus on this part. Another power consumer, a bit less significant, is the L2 SRAM memory embedded in the DSP subsystem.

The clock source and clock gating is managed by the Power Manager according to Power Policy configuration settings.

Memory power is controlled by the Memory Management Driver that is responsible for memory setup on power state transitions and memory banks power gating on map/unmap requests (if it is supported by the SoC).

Other power-related settings are clock gating and power gating of I/Os (I2C, I3C, GPIO, SPI, UART, DMIC, etc.) and external DSP accelerators (if supported by the hardware).

The low power state transition can be triggered either by Zephyr (on CPU idle) or on the Host IPC request through the Zephyr force power state set request. The entrance to D0i3 power state can be dynamically locked on SetD0ix IPC request that configures the Zephyr Power Policy to prevent a selected power state transition.

More details are in the Zephyr Power Management documentation

@startuml
hide empty description

state "D0 / D0ix" as D0 {
	[*] --> PM_STATE_ACTIVE: Initialization
	PM_STATE_ACTIVE -> PM_STATE_RUNTIME_IDLE
	PM_STATE_RUNTIME_IDLE -> PM_STATE_ACTIVE
	PM_STATE_ACTIVE --> [*]
}

[*] --> D0: Go to D0 / Power DSP on
D0 --> [*]: Go to D3 / Power DSP Off

@enduml

Figure 66 DSP and FW Power States

@startuml

[*] --> D3
D3 --> D0
D0 --> D0ix: SET_D0ix(wake = 0)
D0ix --> D0: SET_D0ix(wake = 1)
D0ix --> D3: ENTER_DX_STATE
D0 --> D3: ENTER_DX_STATE

@enduml

Figure 67 D3, D0 and D0ix state transitions

Power Up of Secondary Core (D3 to D0 transition)

The below diagram shows secondary core boot flow:

@startuml

box "Host" #LightSkyBlue
	participant "driver" as driver
end box

box "SOF" #LightBlue
	participant "Core #0: Zephyr lib" as sof_zephyr_lib_0
	participant "Core #1: FW Init" as fw_init_1
	participant "Zephyr Power Manager" as zephyr_power_manager
	participant "Zephyr SoC HAL" as soc_hal
end box

box "Hardware" #LightGreen
	participant "Core #1: Control" as core_control_1
end box

opt If D0ix is enabled
	driver -> sof_zephyr_lib_0: SET_D0ix(prevent D0/D0ix) IPC request
	activate sof_zephyr_lib_0
		sof_zephyr_lib_0 -> zephyr_power_manager: pm_policy_state_lock_get\n(PM_STATE_RUNTIME_IDLE)
		activate zephyr_power_manager
		return
	return
end

== DSP FW in PM_STATE_ACTIVE ==

driver -> sof_zephyr_lib_0: SET_DX(PID: Core #1, D0) IPC request
activate sof_zephyr_lib_0
		sof_zephyr_lib_0 -> soc_hal: arch_start_cpu(PID: Core #1)
		activate soc_hal
			soc_hal -> core_control_1: Set alternate boot vector to FW Init
			note right: Cores share the \nsame FW binary\nand the firmware must be\npresent in SRAM.
			soc_hal -> core_control_1: Set SPA bit

			core_control_1 -> fw_init_1: Start and jump to alternate boot vector
				activate fw_init_1
				fw_init_1 -> fw_init_1: Restore context\nif any saved

			loop Until Core #1 is enabled
				soc_hal -> core_control_1: read power register
			end
			deactivate fw_init_1
		return
return

@enduml

Figure 68 DSP Secondary Core Boot flow

Power down of DSP core (D0 to D3 transition)

The below diagram shows a primary core power down flow:

@startuml

scale max 1280 width

box "Host" #LightSkyBlue
	participant "driver" as driver
end box

box "SOF" #LightBlue
	participant "Core #0: Zephyr lib" as sof_zephyr_lib_0
	participant "Zephyr Power Manager" as zephyr_power_manager
	participant "Zephyr SoC HAL" as soc_hal
end box

box "Hardware" #LightGreen
	participant "Core #0: Control" as core_hw_control
end box

opt If D0ix is enabled
	driver -> sof_zephyr_lib_0: SET_D0ix(prevent D0ix) IPC request
	activate sof_zephyr_lib_0
		sof_zephyr_lib_0 -> zephyr_power_manager: pm_policy_state_lock_get\n(PM_STATE_RUNTIME_IDLE)
		activate zephyr_power_manager
		return
	return
end

== DSP FW in PM_STATE_ACTIVE ==

driver -> sof_zephyr_lib_0: SET_DX(PID: Core #0, D3) IPC request
activate sof_zephyr_lib_0
	sof_zephyr_lib_0 -> zephyr_power_manager: Read status of secondary cores
	activate zephyr_power_manager
		return

	alt If any secondary core is powered up
		sof_zephyr_lib_0 --> driver: SET_DX(ERROR) IPC response
	else else
		sof_zephyr_lib_0 -> zephyr_power_manager: pm_power_state_force\n(PM_STATE_SOFT_OFF, PID: Core #0)
		activate zephyr_power_manager
			zephyr_power_manager -> soc_hal: pm_power_state_set\n(PM_STATE_SOFT_OFF, PID: Core #0)
			activate soc_hal
				soc_hal -> soc_hal: Save context
				soc_hal -> soc_hal: Prepare restore vector
			return
		return

return SET_DX(SUCCESS) IPC response
end

driver -> core_hw_control: clear power register
loop Until Core #0 power is down
	driver -> core_hw_control: Read Core #0 power bit
end

@enduml

Figure 69 DSP Primary Core Power Down flow

Power down of Secondary Core (D0 to D3 transition)

The below diagram shows a secondary core power down flow:

@startuml

scale max 1280 width

box "Host" #LightSkyBlue
	participant "driver" as driver
end box

box "SOF" #LightBlue
	participant "Core #0: Zephyr lib" as sof_zephyr_lib_0
	participant "Zephyr Power Manager" as zephyr_power_manager
	participant "Zephyr SoC HAL" as soc_hal
end box

box "Hardware" #LightGreen
	participant "Core #1: Control" as core_1_control
end box

opt If D0ix is enabled
	driver -> sof_zephyr_lib_0: SET_D0ix(prevent D0ix) IPC request
	activate sof_zephyr_lib_0
		sof_zephyr_lib_0 -> zephyr_power_manager: pm_policy_state_lock_get\n(PM_STATE_RUNTIME_IDLE)
		activate zephyr_power_manager
		return
	return
end

== DSP FW in PM_STATE_ACTIVE ==

driver -> sof_zephyr_lib_0: SET_DX(PID: Core #1, D3) IPC request
activate sof_zephyr_lib_0
	sof_zephyr_lib_0 -> zephyr_power_manager: pm_power_state_force\n(PM_STATE_SOFT_OFF, PID: Core #1)
	activate zephyr_power_manager
		zephyr_power_manager -> soc_hal: pm_power_state_set\n(PM_STATE_SOFT_OFF, PID: Core #1)
		activate soc_hal
			soc_hal -> soc_hal: Save context to IMR
		return
	deactivate zephyr_power_manager

	loop Until Core #1 transition to PM_STATE_SOFT_OFF
		sof_zephyr_lib_0 -> zephyr_power_manager: pm_power_state_get(PID: Core #1)
		activate zephyr_power_manager
		return
	end

	sof_zephyr_lib_0 -> soc_hal: arch_stop_cpu(PID: Core #1)
	activate soc_hal
		soc_hal -> core_1_control: clear power bit
		loop Until Core #1 is disabled
			soc_hal -> core_1_control: read power register
		end
	return

return SET_DX(SUCCESS) IPC response

@enduml

Figure 70 DSP Secondary Core Power Down flow

Enable D0ix (D0 to D0ix)

D0ix is enabled on explicit SET_D0ix IPC message with prevent_power_gating bit set to 0.

@startuml

box "Host" #LightSkyBlue
	participant "driver" as driver
end box

box "SOF" #LightBlue
	participant "Core #0: Zephyr lib" as sof_zephyr_lib
	participant "Zephyr Power Manager" as zephyr_power_manager
end box

driver -> sof_zephyr_lib: SET_D0ix(prevent_power_gating = 0) IPC request
activate sof_zephyr_lib
	sof_zephyr_lib -> zephyr_power_manager: pm_policy_state_lock_put\n(PM_STATE_RUNTIME_IDLE)
		activate zephyr_power_manager
		return
	sof_zephyr_lib -> zephyr_power_manager: pm_policy_state_lock_is_active\n(PM_STATE_RUNTIME_IDLE)
		activate zephyr_power_manager
		return

	alt if D0ix is still locked
		sof_zephyr_lib --> driver: return ERROR
		note right: Zephyr PM Policy can be used concurrently\nand there can be more then one lock\non D0ix state
	else
		sof_zephyr_lib --> driver: return SUCCESS
	end

	deactivate sof_zephyr_lib
@enduml

Figure 71 Enable D0i3 flow

Disable D0ix (D0ix to D0)

D0ix is disabled on explicit SET_D0ix IPC message with prevent_power_gating bit set to 1.

@startuml

box "Host" #LightSkyBlue
	participant "Driver" as DRIVER
end box

box "SOF" #LightBlue
	participant "Core #0: Zephyr lib" as sof_zephyr_lib
	participant "Zephyr Power Manager" as zephyr_power_manager
end box

DRIVER -> sof_zephyr_lib: SET_D0ix(prevent_power_gating = 1) IPC request
activate sof_zephyr_lib
	sof_zephyr_lib -> zephyr_power_manager: pm_policy_state_lock_get\n(PM_STATE_RUNTIME_IDLE)
	activate zephyr_power_manager
	return
return SET_D0ix IPC response

@enduml

Figure 72 Disable D0i3 flow

DSP idle state

@startuml

box "SOF" #LightBlue
	participant "Core #N: Zephyr lib" as sof_zephyr_lib
	participant "Zephyr Power Manager" as zephyr_power_manager
	participant "Zephyr SoC HAL" as soc_hal
end box

box "Hardware" #LightGreen
	participant "Core #N: Control" as core_hw_control
end box

opt When Core is Idle

	zephyr_power_manager -> sof_zephyr_lib: pm_policy_next_state (PID: Core #N, ticks)
	activate sof_zephyr_lib
		activate zephyr_power_manager
		sof_zephyr_lib -> zephyr_power_manager: pm_policy_state_lock_get\n(PM_STATE_RUNTIME_IDLE)
		activate zephyr_power_manager
		return
		alt if no lock on D0ix state
			return PM_STATE_RUNTIME_IDLE
		else if there is lock on D0ix state
			return PM_STATE_ACTIVE
		end

	zephyr_power_manager -> soc_hal: pm_power_state_set\n(power_state, PID: Core #N)
	activate soc_hal
	alt If power_state is PM_STATE_IDLE
		soc_hal -> soc_hal: arch_clear_power_gating_prevent (Core #N)
		soc_hal -> core_hw_control: Clear power gating prevent
	else if PM_STATE_RUNTIME_ACTIVE
		soc_hal -> soc_hal: arch_set_power_gating_prevent (Core #N)
		soc_hal -> core_hw_control: Set power gating prevent
	end
	return

	deactivate zephyr_power_manager
end

@enduml

Figure 73 DSP idle state flow

DSP Cores Clock Gating

DSP clocks, similar to DSP cores, can be separately gated as well. Clock gating shall be enabled by default for all DSP cores unless there is request to prevent it.

NOTE: Power and clock gating is controlled via Set D0ix IPC message.

I/O Power and Clock Gating Management

Zephyr is responsible for I/O devices power and clock management.

The I/O device power is controlled based on usage count. More details can be found in Zephyr Device Runtime Power Management documentation

The I/O clock gating is configurable in driver power policy. Each driver shall request the desired clock and clock power gating if it is necessary for I/O, accelerator, etc. to work correctly.

For instance, audio I/Os such as I2S associated with audio domain require a high accuracy XTAL clock and may request it. This clock shall be used for as long as audio I/Os are active.