blob: 7afede46f914db2a98ec811fe71fe7d76f03ae16 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#include "sw/device/lib/dif/dif_pwrmgr.h"
#include <assert.h>
#include "sw/device/lib/base/bitfield.h"
#include "sw/device/lib/base/mmio.h"
#include "sw/device/lib/dif/dif_base.h"
#include "pwrmgr_regs.h" // Generated.
/**
* Following static assertions make sure that generated values match the
* definitions in the header, which we rely on for a simpler implementation.
* These constants and their usages must be revisited if there is a change in
* hardware.
*/
/**
* Relevant bits of the control register must start at
* `PWRMGR_CONTROL_CORE_CLK_EN_BIT` and be in the same order as
* `dif_pwrmgr_domain_option_t` constants.
*/
static_assert(kDifPwrmgrDomainOptionCoreClockInLowPower ==
(1u << (PWRMGR_CONTROL_CORE_CLK_EN_BIT -
PWRMGR_CONTROL_CORE_CLK_EN_BIT)),
"Layout of control register changed.");
static_assert(kDifPwrmgrDomainOptionIoClockInLowPower ==
(1u << (PWRMGR_CONTROL_IO_CLK_EN_BIT -
PWRMGR_CONTROL_CORE_CLK_EN_BIT)),
"Layout of control register changed.");
static_assert(kDifPwrmgrDomainOptionUsbClockInLowPower ==
(1u << (PWRMGR_CONTROL_USB_CLK_EN_LP_BIT -
PWRMGR_CONTROL_CORE_CLK_EN_BIT)),
"Layout of control register changed.");
static_assert(kDifPwrmgrDomainOptionUsbClockInActivePower ==
(1u << (PWRMGR_CONTROL_USB_CLK_EN_ACTIVE_BIT -
PWRMGR_CONTROL_CORE_CLK_EN_BIT)),
"Layout of control register changed.");
static_assert(kDifPwrmgrDomainOptionMainPowerInLowPower ==
(1u << (PWRMGR_CONTROL_MAIN_PD_N_BIT -
PWRMGR_CONTROL_CORE_CLK_EN_BIT)),
"Layout of control register changed.");
/**
* Bitfield for interpreting the configuration options in the control
* register.
*/
static const bitfield_field32_t kDomainConfigBitfield = {
.mask = kDifPwrmgrDomainOptionCoreClockInLowPower |
kDifPwrmgrDomainOptionIoClockInLowPower |
kDifPwrmgrDomainOptionUsbClockInLowPower |
kDifPwrmgrDomainOptionUsbClockInActivePower |
kDifPwrmgrDomainOptionMainPowerInLowPower,
.index = PWRMGR_CONTROL_CORE_CLK_EN_BIT,
};
/**
* Relevant bits of the WAKEUP_EN and WAKE_INFO registers must start at `0` and
* be in the same order as `dif_pwrmgr_wakeup_request_source_t` constants.
*/
static_assert(kDifPwrmgrWakeupRequestSourceOne ==
(1u << PWRMGR_WAKEUP_EN_EN_0_BIT),
"Layout of WAKEUP_EN register changed.");
static_assert(kDifPwrmgrWakeupRequestSourceOne ==
(1u << PWRMGR_PARAM_SYSRST_CTRL_AON_WKUP_REQ_IDX),
"Layout of WAKE_INFO register changed.");
static_assert(kDifPwrmgrWakeupRequestSourceTwo ==
(1u << PWRMGR_PARAM_ADC_CTRL_AON_WKUP_REQ_IDX),
"Layout of WAKE_INFO register changed.");
static_assert(kDifPwrmgrWakeupRequestSourceThree ==
(1u << PWRMGR_PARAM_PINMUX_AON_PIN_WKUP_REQ_IDX),
"Layout of WAKE_INFO register changed.");
static_assert(kDifPwrmgrWakeupRequestSourceFour ==
(1u << PWRMGR_PARAM_PINMUX_AON_USB_WKUP_REQ_IDX),
"Layout of WAKE_INFO register changed.");
static_assert(kDifPwrmgrWakeupRequestSourceFive ==
(1u << PWRMGR_PARAM_AON_TIMER_AON_WKUP_REQ_IDX),
"Layout of WAKE_INFO register changed.");
static_assert(kDifPwrmgrWakeupRequestSourceSix ==
(1u << PWRMGR_PARAM_SENSOR_CTRL_WKUP_REQ_IDX),
"Layout of WAKE_INFO register changed.");
/**
* Relevant bits of the RESET_EN register must start at `0` and be in the same
* order as `dif_pwrmgr_reset_request_source_t` constants.
*/
static_assert(kDifPwrmgrResetRequestSourceOne ==
(1u << PWRMGR_RESET_EN_EN_0_BIT),
"Layout of RESET_EN register changed.");
static_assert(kDifPwrmgrResetRequestSourceTwo ==
(1u << PWRMGR_RESET_EN_EN_1_BIT),
"Layout of RESET_EN register changed.");
/**
* `dif_pwrmgr_irq_t` constants must match the corresponding generated values.
*/
static_assert(kDifPwrmgrIrqWakeup == PWRMGR_INTR_COMMON_WAKEUP_BIT,
"Layout of interrupt registers changed.");
/**
* Register information for a request type.
*/
typedef struct request_reg_info {
ptrdiff_t write_enable_reg_offset;
bitfield_bit32_index_t write_enable_bit_index;
ptrdiff_t sources_enable_reg_offset;
ptrdiff_t cur_req_sources_reg_offset;
bitfield_field32_t bitfield;
} request_reg_info_t;
/**
* Register information for wakeup and reset requests.
*
* Wakeup and reset requests follow the same logic for configuration and
* monitoring but use different registers. Defining these constants here
* allows us to use the same code for both types of requests.
*/
static const request_reg_info_t request_reg_infos[2] = {
[kDifPwrmgrReqTypeWakeup] =
{
.write_enable_reg_offset = PWRMGR_WAKEUP_EN_REGWEN_REG_OFFSET,
.write_enable_bit_index = PWRMGR_WAKEUP_EN_REGWEN_EN_BIT,
.sources_enable_reg_offset = PWRMGR_WAKEUP_EN_REG_OFFSET,
.cur_req_sources_reg_offset = PWRMGR_WAKE_STATUS_REG_OFFSET,
.bitfield =
{
.mask = kDifPwrmgrWakeupRequestSourceOne |
kDifPwrmgrWakeupRequestSourceTwo |
kDifPwrmgrWakeupRequestSourceThree |
kDifPwrmgrWakeupRequestSourceFour |
kDifPwrmgrWakeupRequestSourceFive |
kDifPwrmgrWakeupRequestSourceSix,
.index = 0,
},
},
[kDifPwrmgrReqTypeReset] =
{
.write_enable_reg_offset = PWRMGR_RESET_EN_REGWEN_REG_OFFSET,
.write_enable_bit_index = PWRMGR_RESET_EN_REGWEN_EN_BIT,
.sources_enable_reg_offset = PWRMGR_RESET_EN_REG_OFFSET,
.cur_req_sources_reg_offset = PWRMGR_RESET_STATUS_REG_OFFSET,
.bitfield =
{
.mask = kDifPwrmgrResetRequestSourceOne |
kDifPwrmgrResetRequestSourceTwo,
.index = 0,
},
},
};
/**
* Checks if a value is a valid `dif_pwrmgr_req_type_t`.
*/
OT_WARN_UNUSED_RESULT
static bool is_valid_req_type(dif_pwrmgr_req_type_t val) {
return val == kDifPwrmgrReqTypeWakeup || val == kDifPwrmgrReqTypeReset;
}
/**
* Checks if a value is valid for, i.e. fits in the mask of, a
* `bitfield_field32_t`.
*/
OT_WARN_UNUSED_RESULT
static bool is_valid_for_bitfield(uint32_t val, bitfield_field32_t bitfield) {
return (val & bitfield.mask) == val;
}
/**
* Checks if the control register is locked.
*
* Control register is locked when low power is enabled and WFI occurs. It is
* unlocked when the chip transitions back to active power state.
*/
OT_WARN_UNUSED_RESULT
static bool control_register_is_locked(const dif_pwrmgr_t *pwrmgr) {
// Control register is locked when `PWRMGR_CTRL_CFG_REGWEN_EN_BIT` bit is 0.
return !bitfield_bit32_read(
mmio_region_read32(pwrmgr->base_addr, PWRMGR_CTRL_CFG_REGWEN_REG_OFFSET),
PWRMGR_CTRL_CFG_REGWEN_EN_BIT);
}
/**
* The configuration registers CONTROL, WAKEUP_EN, and RESET_EN are all written
* in the fast clock domain but used in the slow clock domain. Values of these
* registers are not propagated across the clock boundary until this function is
* called.
*/
static void sync_slow_clock_domain_polled(const dif_pwrmgr_t *pwrmgr) {
// Start sync and wait for it to finish.
mmio_region_write32(
pwrmgr->base_addr, PWRMGR_CFG_CDC_SYNC_REG_OFFSET,
bitfield_bit32_write(0, PWRMGR_CFG_CDC_SYNC_SYNC_BIT, true));
while (bitfield_bit32_read(
mmio_region_read32(pwrmgr->base_addr, PWRMGR_CFG_CDC_SYNC_REG_OFFSET),
PWRMGR_CFG_CDC_SYNC_SYNC_BIT)) {
}
}
/**
* Checks if sources of a request type are locked.
*/
OT_WARN_UNUSED_RESULT
static bool request_sources_is_locked(const dif_pwrmgr_t *pwrmgr,
dif_pwrmgr_req_type_t req_type) {
request_reg_info_t reg_info = request_reg_infos[req_type];
uint32_t reg_val =
mmio_region_read32(pwrmgr->base_addr, reg_info.write_enable_reg_offset);
// Locked if write enable bit is set to 0.
return !bitfield_bit32_read(reg_val, reg_info.write_enable_bit_index);
}
dif_result_t dif_pwrmgr_low_power_set_enabled(const dif_pwrmgr_t *pwrmgr,
dif_toggle_t new_state,
dif_toggle_t sync_state) {
if (pwrmgr == NULL || !dif_is_valid_toggle(new_state) ||
!dif_is_valid_toggle(sync_state)) {
return kDifBadArg;
}
if (control_register_is_locked(pwrmgr)) {
return kDifLocked;
}
uint32_t reg_val =
mmio_region_read32(pwrmgr->base_addr, PWRMGR_CONTROL_REG_OFFSET);
reg_val = bitfield_bit32_write(reg_val, PWRMGR_CONTROL_LOW_POWER_HINT_BIT,
dif_toggle_to_bool(new_state));
mmio_region_write32(pwrmgr->base_addr, PWRMGR_CONTROL_REG_OFFSET, reg_val);
// Slow clock domain may be synced for changes to take effect.
if (sync_state == kDifToggleEnabled)
sync_slow_clock_domain_polled(pwrmgr);
return kDifOk;
}
dif_result_t dif_pwrmgr_low_power_get_enabled(const dif_pwrmgr_t *pwrmgr,
dif_toggle_t *cur_state) {
if (pwrmgr == NULL || cur_state == NULL) {
return kDifBadArg;
}
uint32_t reg_val =
mmio_region_read32(pwrmgr->base_addr, PWRMGR_CONTROL_REG_OFFSET);
*cur_state = dif_bool_to_toggle(
bitfield_bit32_read(reg_val, PWRMGR_CONTROL_LOW_POWER_HINT_BIT));
return kDifOk;
}
dif_result_t dif_pwrmgr_set_domain_config(const dif_pwrmgr_t *pwrmgr,
dif_pwrmgr_domain_config_t config,
dif_toggle_t sync_state) {
if (pwrmgr == NULL || !is_valid_for_bitfield(config, kDomainConfigBitfield) ||
!dif_is_valid_toggle(sync_state)) {
return kDifBadArg;
}
if (control_register_is_locked(pwrmgr)) {
return kDifLocked;
}
uint32_t reg_val =
mmio_region_read32(pwrmgr->base_addr, PWRMGR_CONTROL_REG_OFFSET);
reg_val = bitfield_field32_write(reg_val, kDomainConfigBitfield, config);
mmio_region_write32(pwrmgr->base_addr, PWRMGR_CONTROL_REG_OFFSET, reg_val);
// Slow clock domain may be synced for changes to take effect.
if (sync_state == kDifToggleEnabled)
sync_slow_clock_domain_polled(pwrmgr);
return kDifOk;
}
dif_result_t dif_pwrmgr_get_domain_config(const dif_pwrmgr_t *pwrmgr,
dif_pwrmgr_domain_config_t *config) {
if (pwrmgr == NULL || config == NULL) {
return kDifBadArg;
}
uint32_t reg_val =
mmio_region_read32(pwrmgr->base_addr, PWRMGR_CONTROL_REG_OFFSET);
*config = bitfield_field32_read(reg_val, kDomainConfigBitfield);
return kDifOk;
}
dif_result_t dif_pwrmgr_set_request_sources(
const dif_pwrmgr_t *pwrmgr, dif_pwrmgr_req_type_t req_type,
dif_pwrmgr_request_sources_t sources, dif_toggle_t sync_state) {
if (pwrmgr == NULL || !is_valid_req_type(req_type) ||
!dif_is_valid_toggle(sync_state)) {
return kDifBadArg;
}
request_reg_info_t reg_info = request_reg_infos[req_type];
if (!is_valid_for_bitfield(sources, reg_info.bitfield)) {
return kDifBadArg;
}
// Return early if locked.
if (request_sources_is_locked(pwrmgr, req_type)) {
return kDifLocked;
}
// Write new value
uint32_t reg_val = bitfield_field32_write(0, reg_info.bitfield, sources);
mmio_region_write32(pwrmgr->base_addr, reg_info.sources_enable_reg_offset,
reg_val);
// Slow clock domain may be synced for changes to take effect.
if (sync_state == kDifToggleEnabled)
sync_slow_clock_domain_polled(pwrmgr);
return kDifOk;
}
dif_result_t dif_pwrmgr_get_request_sources(
const dif_pwrmgr_t *pwrmgr, dif_pwrmgr_req_type_t req_type,
dif_pwrmgr_request_sources_t *sources) {
if (pwrmgr == NULL || !is_valid_req_type(req_type) || sources == NULL) {
return kDifBadArg;
}
request_reg_info_t reg_info = request_reg_infos[req_type];
uint32_t reg_val =
mmio_region_read32(pwrmgr->base_addr, reg_info.sources_enable_reg_offset);
*sources = bitfield_field32_read(reg_val, reg_info.bitfield);
return kDifOk;
}
dif_result_t dif_pwrmgr_get_current_request_sources(
const dif_pwrmgr_t *pwrmgr, dif_pwrmgr_req_type_t req_type,
dif_pwrmgr_request_sources_t *sources) {
if (pwrmgr == NULL || !is_valid_req_type(req_type) || sources == NULL) {
return kDifBadArg;
}
request_reg_info_t reg_info = request_reg_infos[req_type];
uint32_t reg_val = mmio_region_read32(pwrmgr->base_addr,
reg_info.cur_req_sources_reg_offset);
*sources = bitfield_field32_read(reg_val, reg_info.bitfield);
return kDifOk;
}
dif_result_t dif_pwrmgr_request_sources_lock(const dif_pwrmgr_t *pwrmgr,
dif_pwrmgr_req_type_t req_type) {
if (pwrmgr == NULL || !is_valid_req_type(req_type)) {
return kDifBadArg;
}
// Only a single bit of this register is significant, thus we don't perform a
// read-modify-write. Setting this bit to 0 locks sources.
mmio_region_write32(pwrmgr->base_addr,
request_reg_infos[req_type].write_enable_reg_offset, 0);
return kDifOk;
}
dif_result_t dif_pwrmgr_request_sources_is_locked(
const dif_pwrmgr_t *pwrmgr, dif_pwrmgr_req_type_t req_type,
bool *is_locked) {
if (pwrmgr == NULL || !is_valid_req_type(req_type) || is_locked == NULL) {
return kDifBadArg;
}
*is_locked = request_sources_is_locked(pwrmgr, req_type);
return kDifOk;
}
dif_result_t dif_pwrmgr_wakeup_request_recording_set_enabled(
const dif_pwrmgr_t *pwrmgr, dif_toggle_t new_state) {
if (pwrmgr == NULL || !dif_is_valid_toggle(new_state)) {
return kDifBadArg;
}
// Only a single bit of this register is significant, thus we don't perform a
// read-modify-write. Setting this bit to 1 disables recording.
uint32_t reg_val = bitfield_bit32_write(
0, PWRMGR_WAKE_INFO_CAPTURE_DIS_VAL_BIT, !dif_toggle_to_bool(new_state));
mmio_region_write32(pwrmgr->base_addr,
PWRMGR_WAKE_INFO_CAPTURE_DIS_REG_OFFSET, reg_val);
return kDifOk;
}
dif_result_t dif_pwrmgr_wakeup_request_recording_get_enabled(
const dif_pwrmgr_t *pwrmgr, dif_toggle_t *cur_state) {
if (pwrmgr == NULL || cur_state == NULL) {
return kDifBadArg;
}
uint32_t reg_val = mmio_region_read32(
pwrmgr->base_addr, PWRMGR_WAKE_INFO_CAPTURE_DIS_REG_OFFSET);
// Recording is disabled if this bit is set to 1.
*cur_state = dif_bool_to_toggle(
!bitfield_bit32_read(reg_val, PWRMGR_WAKE_INFO_CAPTURE_DIS_VAL_BIT));
return kDifOk;
}
dif_result_t dif_pwrmgr_wakeup_reason_get(const dif_pwrmgr_t *pwrmgr,
dif_pwrmgr_wakeup_reason_t *reason) {
if (pwrmgr == NULL || reason == NULL) {
return kDifBadArg;
}
uint32_t reg_val =
mmio_region_read32(pwrmgr->base_addr, PWRMGR_WAKE_INFO_REG_OFFSET);
dif_pwrmgr_wakeup_types_t types = 0;
if (bitfield_bit32_read(reg_val, PWRMGR_WAKE_INFO_FALL_THROUGH_BIT)) {
types |= kDifPwrmgrWakeupTypeFallThrough;
}
if (bitfield_bit32_read(reg_val, PWRMGR_WAKE_INFO_ABORT_BIT)) {
types |= kDifPwrmgrWakeupTypeAbort;
}
uint32_t request_sources = bitfield_field32_read(
reg_val, request_reg_infos[kDifPwrmgrReqTypeWakeup].bitfield);
if (request_sources != 0) {
types |= kDifPwrmgrWakeupTypeRequest;
}
*reason = (dif_pwrmgr_wakeup_reason_t){
.types = types,
.request_sources = request_sources,
};
return kDifOk;
}
dif_result_t dif_pwrmgr_wakeup_reason_clear(const dif_pwrmgr_t *pwrmgr) {
if (pwrmgr == NULL) {
return kDifBadArg;
}
mmio_region_write32(pwrmgr->base_addr, PWRMGR_WAKE_INFO_REG_OFFSET,
UINT32_MAX);
return kDifOk;
}
dif_result_t dif_pwrmgr_fatal_err_code_get_codes(
const dif_pwrmgr_t *pwrmgr, dif_pwrmgr_fatal_err_codes_t *codes) {
if (pwrmgr == NULL || codes == NULL) {
return kDifBadArg;
}
*codes =
mmio_region_read32(pwrmgr->base_addr, PWRMGR_FAULT_STATUS_REG_OFFSET);
return kDifOk;
}