diff options
author | Nicolas Royer <nroyer@baylibre.com> | 2020-09-27 17:41:19 +0200 |
---|---|---|
committer | nicola-mazzucato-arm <42373140+nicola-mazzucato-arm@users.noreply.github.com> | 2020-10-15 17:45:38 +0100 |
commit | e88b03fd71f44a24bdf0cc72838f276404de7b0b (patch) | |
tree | 2e61a057816b264221bb4e50e8b2d55fb65879bc | |
parent | 9407c9b53bc33339c6d28a68fc1d4a1fd9951f73 (diff) |
rcar/module: add rcar power_domain module and config data
Change-Id: I44796fe0ea4c66e90d77c6cd93843914a7978f3b
Signed-off-by: Tsutomu Muroya <tsutomu.muroya.jy@bp.renesas.com>
Signed-off-by: Nicolas Royer <nroyer@baylibre.com>
6 files changed, 3204 insertions, 0 deletions
diff --git a/product/rcar/module/rcar_power_domain/include/mod_power_domain.h b/product/rcar/module/rcar_power_domain/include/mod_power_domain.h new file mode 100644 index 00000000..1ee6e9a7 --- /dev/null +++ b/product/rcar/module/rcar_power_domain/include/mod_power_domain.h @@ -0,0 +1,16 @@ +/* + * Renesas SCP/MCP Software + * Copyright (c) 2020, Renesas Electronics Corporation. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef MOD_POWER_DOMAIN_H +#define MOD_POWER_DOMAIN_H + +#define FWK_MODULE_IDX_POWER_DOMAIN FWK_MODULE_IDX_RCAR_POWER_DOMAIN +#define fwk_module_id_power_domain fwk_module_id_rcar_power_domain + +#include "mod_rcar_power_domain.h" + +#endif /* MOD_POWER_DOMAIN_H */ diff --git a/product/rcar/module/rcar_power_domain/include/mod_rcar_power_domain.h b/product/rcar/module/rcar_power_domain/include/mod_rcar_power_domain.h new file mode 100644 index 00000000..03b8e7e3 --- /dev/null +++ b/product/rcar/module/rcar_power_domain/include/mod_rcar_power_domain.h @@ -0,0 +1,1149 @@ +/* + * Renesas SCP/MCP Software + * Copyright (c) 2015-2020, Arm Limited and Contributors. All rights reserved. + * Copyright (c) 2020, Renesas Electronics Corporation. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef MOD_RCAR_POWER_DOMAIN_H +#define MOD_RCAR_POWER_DOMAIN_H + +#include <fwk_element.h> +#include <fwk_id.h> +#include <fwk_module_idx.h> + +#include <limits.h> +#include <stdbool.h> +#include <stdint.h> + +/*! + * \addtogroup GroupRCARModule RCAR Product Modules + * @{ + */ + +/*! + * \defgroup GroupRCARPowerDomain Power Domains + * + * \details Support for querying and setting the power state of power domains + * such as CPU, cluster and GPU power domains. + * + * @{ + */ + +/*! + * Maximum number of states for a power domain. + */ +#define MOD_PD_STATE_COUNT_MAX 16 + +/*! + * \brief Types of power domain. + */ +enum mod_pd_type { + /*! Processor. */ + MOD_PD_TYPE_CORE, + /*! Processor cluster. */ + MOD_PD_TYPE_CLUSTER, + /*! Generic device. */ + MOD_PD_TYPE_DEVICE, + /*! Debug device. */ + MOD_PD_TYPE_DEVICE_DEBUG, + /*! System. */ + MOD_PD_TYPE_SYSTEM, + /*! Number of power domain types. */ + MOD_PD_TYPE_COUNT +}; + +/*! + * \brief Bit width of power domain type within attributes. + */ +#define MOD_PD_TYPE_BITS_WIDTH \ + ((sizeof(unsigned int) * CHAR_BIT) - __builtin_clz(MOD_PD_TYPE_COUNT - 1)) + +/*! + * \brief Attributes of a power domain. + */ +struct mod_pd_attributes { + /*! Type of the power domain. */ + enum mod_pd_type pd_type : MOD_PD_TYPE_BITS_WIDTH; + + /*! \internal */ + unsigned int not_used + : ((sizeof(unsigned int) * CHAR_BIT) - MOD_PD_TYPE_BITS_WIDTH); +}; + +/*! + * \brief Identifiers for the power levels. + */ +enum mod_pd_level { + /*! Level 0. */ + MOD_PD_LEVEL_0, + /*! Level 1. */ + MOD_PD_LEVEL_1, + /*! Level 2. */ + MOD_PD_LEVEL_2, + /*! Level 3. */ + MOD_PD_LEVEL_3, + /*! Number of power domain levels. */ + MOD_PD_LEVEL_COUNT +}; + +/*! + * \brief Number of bits used for each level within a power domain tree + * position. + */ +#define MOD_PD_TREE_POS_BITS_PER_LEVEL 8 + +/*! + * \brief Shifts for the power level fields within a power domain tree position. + */ +enum { + /*! Number of bits to shift for the power level 0 field. */ + MOD_PD_TREE_POS_LEVEL_0_SHIFT = + MOD_PD_LEVEL_0 * MOD_PD_TREE_POS_BITS_PER_LEVEL, + /*! Number of bits to shift for the power level 1 field. */ + MOD_PD_TREE_POS_LEVEL_1_SHIFT = + MOD_PD_LEVEL_1 * MOD_PD_TREE_POS_BITS_PER_LEVEL, + /*! Number of bits to shift for the power level 2 field. */ + MOD_PD_TREE_POS_LEVEL_2_SHIFT = + MOD_PD_LEVEL_2 * MOD_PD_TREE_POS_BITS_PER_LEVEL, + /*! Number of bits to shift for the power level 3 field. */ + MOD_PD_TREE_POS_LEVEL_3_SHIFT = + MOD_PD_LEVEL_3 * MOD_PD_TREE_POS_BITS_PER_LEVEL, + MOD_PD_TREE_POS_LEVEL_SHIFT = + MOD_PD_LEVEL_COUNT * MOD_PD_TREE_POS_BITS_PER_LEVEL, +}; + +/*! + * \brief Mask for the power level fields within a power domain tree position. + */ +#define MOD_PD_TREE_POS_LEVEL_MASK UINT64_C(0xFF) + +/*! + * \brief Build the power domain tree position of a power domain. + */ +#define MOD_PD_TREE_POS(LEVEL, LEVEL_3, LEVEL_2, LEVEL_1, LEVEL_0) \ + ((((uint64_t)((LEVEL)&MOD_PD_TREE_POS_LEVEL_MASK)) \ + << MOD_PD_TREE_POS_LEVEL_SHIFT) | \ + (((uint64_t)((LEVEL_3)&MOD_PD_TREE_POS_LEVEL_MASK)) \ + << MOD_PD_TREE_POS_LEVEL_3_SHIFT) | \ + (((uint64_t)((LEVEL_2)&MOD_PD_TREE_POS_LEVEL_MASK)) \ + << MOD_PD_TREE_POS_LEVEL_2_SHIFT) | \ + (((uint64_t)((LEVEL_1)&MOD_PD_TREE_POS_LEVEL_MASK)) \ + << MOD_PD_TREE_POS_LEVEL_1_SHIFT) | \ + (((uint64_t)((LEVEL_0)&MOD_PD_TREE_POS_LEVEL_MASK)) \ + << MOD_PD_TREE_POS_LEVEL_0_SHIFT)) + +/*! + * \brief Representation of the invalid tree position. Used when checking that + * power domains are declared in increasing order of their tree position. + */ +#define MOD_PD_INVALID_TREE_POS MOD_PD_TREE_POS(MOD_PD_LEVEL_COUNT, 0, 0, 0, 0) + +/*! + * \brief Power domain module configuration. + */ +struct mod_power_domain_config { + /*! + * Identifiers of the modules and elements which are granted access to the + * restricted interface of the module. + */ + fwk_id_t *authorized_id_table; + + /*! Number of identifiers in the "authorized_id_table" table. */ + size_t authorized_id_table_size; +}; + +/*! + * \brief Configuration data for a power domain. + * + * \details The configuration data of a power domain describes the power + * domain and its relationship with other power domains in the system. + * Assumptions about the organisation of the power domains, underlying the + * way in which power domains are described here, are: + * - Each power domain can be represented as a node in a power domain + * topology tree. + * - Sibling power domains are mutually exclusive. + * - Parent power domains are shared by the children. + * + * For more information, refer to the "Arm Power State Coordination + * Interface Platform Design Document", available here: + * http://infocenter.arm.com/help/topic/com.arm.doc.den0022d/Power_State_ + * Coordination_Interface_PDD_v1_1_DEN0022D.pdf + */ +struct mod_power_domain_element_config { + /*! + * \brief Defines the position of the power domain within the power domain + * tree. + * + * \details Each child of a power domain is assigned a number ranging from 0 + * to 255. Compute the position of a power domain from the position of + * its parent 'parent_pos' (the number assigned to the power domain as + * a child of its parent is 'child_pos') as follows: + * + * tree_pos = (parent_pos - (1 << MOD_PD_TREE_POS_LEVEL_SHIFT)) + + * (child_pos << (8*pd_level)) + * + * The position of the top-level domain is defined as: + * (level of the top-level domain) << MOD_PD_TREE_POS_LEVEL_SHIFT + * + * If the power domain hierarchy maps to the core hierarchy (based on + * MPIDR levels of affinity), derive the position of the core power + * domains from the Aff0, Aff1, Aff2 and Aff3 fields of the MPIDR + * registers of the cores as follows: + * + * core power domain position = (Aff3 << MOD_PD_TREE_LEVEL_3_SHIFT) + + * (Aff2 << MOD_PD_TREE_LEVEL_2_SHIFT) + + * (Aff1 << MOD_PD_TREE_LEVEL_1_SHIFT) + + * (Aff0 << MOD_PD_TREE_LEVEL_0_SHIFT) + * + * In the module configuration data, the power domains have to be in + * increasing order of their power domain position. Thus, the power + * domains with the lowest power level have to be first and the system + * power domain has to be last. This table must contain at least one + * element, the system power domain. + */ + uint64_t tree_pos; + + /*! + * Identifier of the module or element providing the driver for the power + * domain. + */ + fwk_id_t driver_id; + + /*! Identifier of the driver API. */ + fwk_id_t api_id; + + /*! Attributes of the power domain. */ + struct mod_pd_attributes attributes; + + /*! + * Mask of the allowed power states for the power domain, for each possible + * power state of its parent. Table of allowed state masks. The bit 'i' of + * the entry 'j' is equal to one if and only if the state 'i' for the power + * domain is allowed when its parent is in state 'j'. The number of bits of + * each entry of this table has to be greater or equal than + * MOD_PD_STATE_COUNT_MAX. + */ + const uint32_t *allowed_state_mask_table; + + /*! + * Power state names. The power state names are used only for debug + * purposes and this table is optional. If not provided then the default + * power state names (OFF, ON, SLEEP, 3, 4, ...) are used for the power + * domain states. + */ + const char **state_name_table; + + /*! Size of the table of allowed state masks */ + size_t allowed_state_mask_table_size; + + /*! Size of the table of allowed state masks */ + size_t state_name_table_size; +}; + +/*! + * \brief Types of system shutdown + */ +enum mod_pd_system_shutdown { + /*! System shutdown */ + MOD_PD_SYSTEM_SHUTDOWN, + + /*! System cold reset */ + MOD_PD_SYSTEM_COLD_RESET, + + /*! System warm reset */ + MOD_PD_SYSTEM_WARM_RESET, + + /*! Sub-system reset */ + MOD_PD_SYSTEM_SUB_SYSTEM_RESET, + + /*! Forced system shutdown */ + MOD_PD_SYSTEM_FORCED_SHUTDOWN, + + /*! Number of shutdown types */ + MOD_PD_SYSTEM_COUNT, +}; + +/*! + * \brief Identifiers of the power domain states. The other states are defined + * by the platform code for more flexibility. The power states defined by + * the platform must be ordered from the shallowest to the deepest state. + */ +enum mod_pd_state { + /*! \c OFF power state */ + MOD_PD_STATE_OFF, + + /*! \c ON power state */ + MOD_PD_STATE_ON, + + /*! \c SLEEP power state */ + MOD_PD_STATE_SLEEP, + + /*! Number of power states */ + MOD_PD_STATE_COUNT +}; + +/*! + * \brief Masks for the power domain states. + */ +enum mod_pd_state_mask { + MOD_PD_STATE_OFF_MASK = 1 << MOD_PD_STATE_OFF, + MOD_PD_STATE_ON_MASK = 1 << MOD_PD_STATE_ON, + MOD_PD_STATE_SLEEP_MASK = 1 << MOD_PD_STATE_SLEEP, +}; + +/*! + * \brief Number of bits for each level state in a composite power state. + */ +#define MOD_PD_CS_STATE_BITS_PER_LEVEL 4 + +/*! + * \brief Mask for a composite power state. + */ +#define MOD_PD_CS_STATE_MASK ((1 << MOD_PD_CS_STATE_BITS_PER_LEVEL) - 1) + +/*! + * \brief Shifts for the states and child policies fields in a composite + * power state. + */ +enum { + MOD_PD_CS_LEVEL_0_STATE_SHIFT = + MOD_PD_LEVEL_0 * MOD_PD_CS_STATE_BITS_PER_LEVEL, + MOD_PD_CS_LEVEL_1_STATE_SHIFT = + MOD_PD_LEVEL_1 * MOD_PD_CS_STATE_BITS_PER_LEVEL, + MOD_PD_CS_LEVEL_2_STATE_SHIFT = + MOD_PD_LEVEL_2 * MOD_PD_CS_STATE_BITS_PER_LEVEL, + MOD_PD_CS_LEVEL_3_STATE_SHIFT = + MOD_PD_LEVEL_3 * MOD_PD_CS_STATE_BITS_PER_LEVEL, + MOD_PD_CS_LEVEL_SHIFT = MOD_PD_LEVEL_COUNT * MOD_PD_CS_STATE_BITS_PER_LEVEL, + + MOD_PD_CS_VALID_BITS = + (1 << (MOD_PD_CS_LEVEL_SHIFT + MOD_PD_CS_STATE_BITS_PER_LEVEL)) - 1 +}; + +/*! + * \brief Compute a composite power domain state. + */ +#define MOD_PD_COMPOSITE_STATE( \ + HIGHEST_LEVEL, LEVEL_3_STATE, LEVEL_2_STATE, LEVEL_1_STATE, LEVEL_0_STATE) \ + (((HIGHEST_LEVEL) << MOD_PD_CS_LEVEL_SHIFT) | \ + ((LEVEL_3_STATE) << MOD_PD_CS_LEVEL_3_STATE_SHIFT) | \ + ((LEVEL_2_STATE) << MOD_PD_CS_LEVEL_2_STATE_SHIFT) | \ + ((LEVEL_1_STATE) << MOD_PD_CS_LEVEL_1_STATE_SHIFT) | \ + ((LEVEL_0_STATE) << MOD_PD_CS_LEVEL_0_STATE_SHIFT)) + +/*! + * \brief Power domain driver interface. + * + * \details The interface the power domain module relies on to perform + * actions on a power domain. Examples include powering a domain on or off, + * resetting the domain and preparing for/reacting to a system shutdown or + * reset. + */ +struct mod_pd_driver_api { + /*! + * \brief Set the power domain identified by \p dev_id to the \p state power + * state. + * + * \details The power domain module calls this function when processing a + * <tt>set state</tt> request. The function is called once the module + * has checked that the power domain's children and parent are in a + * proper state for the power domain to be transitioned to the \p state + * power state. This function must not return until the transition has + * completed. + * + * \warning In the case of a <tt>shutdown</tt> request, if provided, \ref + * shutdown will be called instead. + * + * \param dev_id Driver identifier of the power domain. + * \param state Power state the power domain has to be put into. + * + * \retval FWK_SUCCESS The power state has been successfully set. + * \retval FWK_E_ACCESS Invalid access, the framework has rejected the + * call to the API. + * \return One of the other specific error codes described by the driver + * module. + */ + int (*set_state)(fwk_id_t dev_id, unsigned int state); + + /*! + * \brief Get the current power state of the power domain identified by + * \p dev_id. + * + * \details The power domain module calls this function when processing a + * <tt>get state</tt> or <tt>get composite state</tt> request. + * + * \param dev_id Driver identifier of the power domain. + * \param state Pointer to storage for the power state of the power domain. + * + * \retval FWK_SUCCESS The power state was successfully returned. + * \retval FWK_E_ACCESS Invalid access, the framework has rejected the + * call to the API. + */ + int (*get_state)(fwk_id_t dev_id, unsigned int *state); + + /*! + * \brief Reset the power domain identified by \p dev_id. + * + * \details The power domain module calls this function when processing a + * <tt>reset</tt> request. The module will ensure that any children of + * the power domain are powered off before attempting a reset. + * + * \param dev_id Driver identifier of the power domain. + * + * \retval FWK_SUCCESS The power domain was successfully reset. + * \retval FWK_E_ACCESS Invalid access, the framework has rejected the + * call to the API. + * \return One of the other specific error codes described by the module. + */ + int (*reset)(fwk_id_t dev_id); + + /*! + * \brief Check whether a state transition request should be denied. + * + * \note This function is optional (may be \c NULL) and it is expected that + * most drivers will not implement it. + * + * \details The power domain module calls this function when it wishes to + * know whether it should permit or deny a state transition request. + * It is intended to provide a device a denial mechanism that can make + * judgements based on information unavailable to the power domain + * module, such as dependencies between power domains beyond the power + * domain tree model. + * + * \param dev_id Driver identifier of the power domain. + * \param state Power state the power domain is intended to be put into. + * + * \retval true The transition should be denied. + * \retval false The power domain can transition to the power state \p + * state. + */ + bool (*deny)(fwk_id_t dev_id, unsigned int state); + + /*! + * \brief Prepare the last standing core for a system suspend. + * + * \details The function prepares the last standing core for entering the + * \ref MOD_PD_STATE_OFF state (powered off, no wake-up interrupt) when + * it will execute WFI. The function should also ensure that when the + * core is powered off a state transition report is sent by means of + * the \ref mod_pd_driver_input_api::report_power_state_transition + * driver input interface function indicating that the core power + * domain state should be updated. This function is mandatory for core + * power domains, but is otherwise unused. + * + * \param dev_id Driver identifier of the power domain. + * + * \retval FWK_SUCCESS The core has been successfully prepared. + * \retval FWK_E_ACCESS Invalid access, the framework has rejected the + * call to the API. + */ + int (*prepare_core_for_system_suspend)(fwk_id_t dev_id); + + /*! + * \brief Shutdown a power domain as part of a system shutdown. + * + * \note This function is optional (may be \c NULL). + * + * \details The power domain module calls this function when processing a + * <tt>shutdown</tt> request. The power domain must be in the + * \ref MOD_PD_STATE_OFF state when it returns. + * + * \param dev_id Driver identifier of the power domain. + * \param state State the system will be suspended to. The definition + * of those states is platform specific. + * + * \retval FWK_SUCCESS The core has been successfully shutdown. + * \retval FWK_E_ACCESS Invalid access, the framework has rejected the + * call to the API. + */ + int (*shutdown)( + fwk_id_t dev_id, + enum mod_pd_system_shutdown system_shutdown); +}; + +/*! + * \brief Power domain module public interface. + * + * \details The interface the power domain module exposes to the other modules + * and their elements without any restriction. + */ + +struct mod_pd_public_api { + /*! + * \brief Get the type of a power domain. + * + * \param pd_id Identifier of the power domain that the type is being + * queried for. + * \param[out] type Type of the power domain. + * + * \retval FWK_SUCCESS The type of the power domain was returned. + * \retval FWK_E_INIT The module has not been initialized. + * \retval FWK_E_STATE The power domain module is in an invalid state. + * \retval FWK_E_PARAM The power domain identifier is unknown. + * \retval FWK_E_PARAM The pointer 'type' is equal to NULL. + */ + int (*get_domain_type)(fwk_id_t pd_id, enum mod_pd_type *type); + + /*! + * \brief Get the identifier of the parent of a power domain. + * + * \note The function returns \ref FWK_ID_NONE in the case of the root power + * domain which does not have any parent. + * + * \param pd_id Identifier of the power domain. + * \param[out] parent_pd_id Identifier of the parent power domain. + * + * \retval FWK_SUCCESS The identifier of the parent power domain was + * returned. + * \retval FWK_E_INIT The module has not been initialized. + * \retval FWK_E_STATE The power domain module is in an invalid state. + * \retval FWK_E_PARAM The power domain identifier is unknown. + * \retval FWK_E_PARAM The pointer 'parent_pd_id' is equal to NULL. + */ + int (*get_domain_parent_id)(fwk_id_t pd_id, fwk_id_t *parent_pd_id); +}; + +/*! + * \brief Power domain module restricted interface. + * + * \details The interface the power domain module exposes to a restricted set + * of modules and/or elements. The set of modules and/or elements that are + * allowed to access this interface is defined by the module configuration + * data. + */ +struct mod_pd_restricted_api { + /*! + * \brief Get the type of a power domain. + * + * \param pd_id Identifier of the power domain that the type is being + * queried for. + * \param[out] type Type of the power domain. + * + * \retval FWK_SUCCESS The type of the power domain was returned. + * \retval FWK_E_INIT The module has not been initialized. + * \retval FWK_E_STATE The power domain module is in an invalid state. + * \retval FWK_E_PARAM The power domain identifier is unknown. + * \retval FWK_E_PARAM The pointer 'type' is equal to NULL. + */ + int (*get_domain_type)(fwk_id_t pd_id, enum mod_pd_type *type); + + /*! + * \brief Get the identifier of the parent of a power domain. + * + * \note The function returns \ref FWK_ID_NONE in the case of the root power + * domain which does not have any parent. + * + * \param pd_id Identifier of the power domain. + * \param[out] parent_pd_id The identifier of the parent power domain. + * + * \retval FWK_SUCCESS The identifier of the parent power domain was + * returned. + * \retval FWK_E_INIT The module has not been initialized. + * \retval FWK_E_STATE The power domain module is in an invalid state. + * \retval FWK_E_PARAM The power domain identifier is unknown. + * \retval FWK_E_PARAM The pointer 'parent_pd_id' is equal to NULL. + */ + int (*get_domain_parent_id)(fwk_id_t pd_id, fwk_id_t *parent_pd_id); + + /*! + * \brief Set the state of a power domain and possibly of one or several of + * its ancestors. + * + * \note The function sets the state of the power domain identified by + * 'pd_id' and possibly of one or several of its ancestors. When the + * function returns the state transition is completed. + * + * \param pd_id Identifier of the power domain whose state has to be set. + * + * \param state State the power domain has to be put into and + * possibly the state(s) its ancestor(s) has(have) to be put into. The + * module will ensure that, for each power state transition, the parent + * and the children of the power domain involved are in a state where + * the transition can be completed. + * + * \retval FWK_SUCCESS The power state transition was completed. + * \retval FWK_E_ACCESS Invalid access, the framework has rejected the + * call to the API. + * \retval FWK_E_HANDLER The function is not called from a thread. + * \retval FWK_E_PARAM One or more parameters were invalid. + */ + int (*set_state)(fwk_id_t pd_id, uint32_t state); + + /*! + * \brief Request an asynchronous power state transition. + * + * \warning Successful completion of this function does not indicate + * completion of a transition, but instead that a request has been + * submitted. + * + * \param pd_id Identifier of the power domain whose state has to be set. + * + * \param resp_requested True if the caller wants to be notified with an + * event response at the end of the request processing. + * + * \param composite State the power domain has to be put into and + * possibly the state(s) its ancestor(s) has(have) to be put into. The + * module will ensure that, for each power state transition, the parent + * and the children of the power domain involved are in a state where + * the transition can be completed. + * + * \retval FWK_SUCCESS The power state transition was submitted. + * \retval FWK_E_ACCESS Invalid access, the framework has rejected the + * call to the API. + * \retval FWK_E_PARAM One or more parameters were invalid. + */ + int (*set_state_async)(fwk_id_t pd_id, bool resp_requested, uint32_t state); + + /*! + * \brief Get the state of a given power domain. + * + * \param pd_id Identifier of the power domain whose state has to be + * retrieved. + * \param[out] state The power domain state. + * + * \retval FWK_SUCCESS The power state was returned. + * \retval FWK_E_ACCESS Invalid access, the framework has rejected the + * call to the API. + * \retval FWK_E_HANDLER The function is not called from a thread. + * \retval FWK_E_PARAM The power domain identifier is unknown. + * \retval FWK_E_PARAM The pointer 'state' is equal to NULL. + */ + int (*get_state)(fwk_id_t pd_id, unsigned int *state); + + /*! + * \brief Reset of a power domain. + * + * \note The function resets the power domain identified by 'pd_id'. When + * the function returns the power domain reset is completed. + * + * \param pd_id Identifier of the power domain to reset. + * + * \retval FWK_SUCCESS Power state retrieving request transmitted. + * \retval FWK_E_ACCESS Invalid access, the framework has rejected the + * call to the API. + * \retval FWK_E_HANDLER The function is not called from a thread. + * \retval FWK_E_NOMEM Failed to allocate a request descriptor. + * \retval FWK_E_PARAM The power domain identifier is unknown. + */ + int (*reset)(fwk_id_t pd_id); + + /*! + * \brief Suspend the system. + * + * \note The function initiates the suspension of the system. On call, all + * but one core power domain must be in the MOD_PD_STATE_OFF state. + * When the function returns the power down of the last standing core + * is programmed to occur as soon as the core executes WFI. + * + * \param state State the system has to be suspended to. The definition + * of those states is platform specific. + * + * \retval FWK_SUCCESS The system suspension has been initiated + * successfully. + * \retval FWK_E_ACCESS Invalid access, the framework has rejected the + * call to the API. + * \retval FWK_E_HANDLER The function is not called from a thread. + * \retval FWK_E_NOMEM Failed to allocate a request descriptor. + * \retval FWK_E_PARAM Invalid state. + * \retval FWK_E_STATE The system is not in the proper state to be + * suspended. + */ + int (*system_suspend)(unsigned int state); + + /*! + * \brief Shutdown the system. + * + * \note The function shutdowns the system whatever its current state. If + * the shutdown is successful, the function does not return. + * + * \retval FWK_E_ACCESS Invalid access, the framework has rejected the + * call to the API. + * \retval FWK_PENDING Request aknowledged. A response event will not be + * sent to the caller. + * \retval FWK_E_HANDLER The function is not called from a thread. + * \retval FWK_E_NOMEM Failed to allocate a request descriptor. + */ + int (*system_shutdown)(enum mod_pd_system_shutdown system_shutdown); +}; + +/*! + * \brief Power domain module driver input API. + * + * \details The interface the power domain module exposes to its module drivers + * to be able to ask for power state transitions or report power state + * transitions following the occurrence of interrupts. + */ +struct mod_pd_driver_input_api { + /*! + * \brief Request an asynchronous composite power state transition. + * + * \warning Successful completion of this function does not indicate + * completion of a transition, but instead that a request has been + * submitted. + * + * \param pd_id Identifier of the power domain whose state has to be set. + * + * \param resp_requested True if the caller wants to be notified with an + * event response at the end of the request processing. + * + * \param composite_state State the power domain has to be put into and + * possibly the state(s) its ancestor(s) has(have) to be put into. The + * module will ensure that, for each power state transition, the parent + * and the children of the power domain involved are in a state where + * the transition can be completed. + * + * \retval FWK_SUCCESS The composite power state transition was submitted. + * \retval FWK_E_ACCESS Invalid access, the framework has rejected the + * call to the API. + * \retval FWK_E_PARAM One or more parameters were invalid. + */ + int (*set_state_async)( + fwk_id_t pd_id, + bool resp_requested, + uint32_t composite_state); + + /*! + * \brief Request for a power domain to be reset. + * + * \note The function queues a reset request. When the function returns the + * power domain has not been reset, the reset has just been requested + * to the power domain module. + * + * \param pd_id Identifier of the power domain to reset. + * + * \param resp_requested True if the caller wants to be notified with an + * event response at the end of the request processing. + * + * \retval FWK_SUCCESS Reset request transmitted. + * \retval FWK_E_ACCESS Invalid access, the framework has rejected the + * call to the API. + * \retval FWK_E_HANDLER The function is not called from a thread. + * \retval FWK_E_NOMEM Failed to allocate a request descriptor. + * \retval FWK_E_PARAM The power domain identifier is unknown. + */ + int (*reset_async)(fwk_id_t pd_id, bool resp_requested); + + /*! + * \brief Report a power domain state transition. + * + * \warning Failure to call this function on a power state transition may + * leave the power domain module with an outdated power state for the + * power domain. + * + * \param pd_id Identifier of the power domain, a power state transition + * report is is sent to. + * + * \retval FWK_SUCCESS Report transmitted. + * \retval FWK_E_ACCESS Invalid access, the framework has rejected the call + * to the API. + * \retval FWK_E_NOMEM Failed to allocate a report event. + * \retval FWK_E_PARAM The power domain identifier is unknown. + */ + int (*report_power_state_transition)(fwk_id_t pd_id, unsigned int state); + + /*! + * \brief Get the power domain identifier of the last core online before or + * during system suspend. + * + * \details When an agent calls for system suspend, the power domain module + * ensures that all cores are turned off except for one, which is the + * calling entity. Because any core can call system suspend, it may be + * necessary for a driver to know which core called system suspend. + * With this information the driver can, for example, safely resume the + * system from the same core which was turned off lastly. + * + * \note The power domain module does not perform any checks to ensure that + * this function is called during a system suspend sequence. + * + * \param[out] last_core_pd_id Identifier of the last core. + * + * \retval FWK_E_PARAM The pointer to the identifier is not valid. + * \retval FWK_SUCCESS The request was successful. + * \return One of the standard framework error codes. + */ + int (*get_last_core_pd_id)(fwk_id_t *last_core_pd_id); +}; + +/*! + * \brief Parameters of a power state pre-transition notification. + */ +struct mod_pd_power_state_pre_transition_notification_params { + /*! Current power state */ + unsigned int current_state; + + /*! Target power state */ + unsigned int target_state; +}; + +/*! + * \brief Parameters of the response to a power state pre-transition + * notification. + */ +struct mod_pd_power_state_pre_transition_notification_resp_params { + /*! + * Status of the transition for the entity being notified. + */ + int status; +}; + +/*! + * \brief Parameters of a power domain transition notification. + */ +struct mod_pd_power_state_transition_notification_params { + /*! Power state the power domain has transitioned to */ + unsigned int state; +}; + +/*! + * \defgroup GroupPowerDomainIds Identifiers + * \{ + */ + +/*! + * \brief API indices + */ +enum mod_pd_api_idx { + MOD_PD_API_IDX_PUBLIC, + MOD_PD_API_IDX_RESTRICTED, + MOD_PD_API_IDX_DRIVER_INPUT, + MOD_PD_API_IDX_COUNT, +}; + +/*! Public API identifier */ +static const fwk_id_t mod_pd_api_id_public = + FWK_ID_API_INIT(FWK_MODULE_IDX_RCAR_POWER_DOMAIN, MOD_PD_API_IDX_PUBLIC); + +/*! Restricted API identifier */ +static const fwk_id_t mod_pd_api_id_restricted = FWK_ID_API_INIT( + FWK_MODULE_IDX_RCAR_POWER_DOMAIN, + MOD_PD_API_IDX_RESTRICTED); + +/*! Driver input API identifier */ +static const fwk_id_t mod_pd_api_id_driver_input = FWK_ID_API_INIT( + FWK_MODULE_IDX_RCAR_POWER_DOMAIN, + MOD_PD_API_IDX_DRIVER_INPUT); + +/*! + * \brief Notification indices. + */ +enum mod_pd_notification_idx { + /*! Power state transition */ + MOD_PD_NOTIFICATION_IDX_POWER_STATE_TRANSITION, + + /*! Power state pre-transition */ + MOD_PD_NOTIFICATION_IDX_POWER_STATE_PRE_TRANSITION, + + /*! Number of notifications defined by the power domain module */ + MOD_PD_NOTIFICATION_COUNT, +}; + +/*! + * \brief Notification identifiers. + */ +/*! Identifier of the power state transition notification */ +static const fwk_id_t mod_pd_notification_id_power_state_transition = + FWK_ID_NOTIFICATION_INIT( + FWK_MODULE_IDX_RCAR_POWER_DOMAIN, + MOD_PD_NOTIFICATION_IDX_POWER_STATE_TRANSITION); +/*! Identifier of the power state pre-transition notification */ +static const fwk_id_t mod_pd_notification_id_power_state_pre_transition = + FWK_ID_NOTIFICATION_INIT( + FWK_MODULE_IDX_RCAR_POWER_DOMAIN, + MOD_PD_NOTIFICATION_IDX_POWER_STATE_PRE_TRANSITION); + +/*! + * @cond + */ + +#define CPU_PWR_OFF U(0x00000003) +#define MODE_L2_DOWN (0x00000002U) + +/* + * Module and power domain contexts + */ + +struct response_ctx { + /* Pending response flag. */ + bool pending; + + /* Cookie of the event to respond to. */ + uint32_t cookie; +}; + +/* Context for the power state transition notification */ +struct power_state_transition_notification_ctx { + /* Number of pending notification responses */ + unsigned int pending_responses; + + /* + * Power state the power domain has transitioned to. + */ + unsigned int state; +}; + +/* Context for the power state pre-transition notification */ +struct power_state_pre_transition_notification_ctx { + /* Number of pending notification responses */ + unsigned int pending_responses; + + /* Target power state */ + unsigned int state; + + /* + * Status of the responses received so far. Either FWK_SUCCESS if all the + * responses received so far have indicated success, or FWK_E_DEVICE + * otherwise. + */ + int response_status; + + /* + * Validity flag. Set to true when a notification is sent and reset to + * false when the requested state for the power domain is changed. + */ + bool valid; +}; + +struct pd_ctx { + /* Identifier of the power domain */ + fwk_id_t id; + + /* Driver interface */ + struct mod_pd_driver_api *driver_api; + + /* Driver's identifier of the power domain */ + fwk_id_t driver_id; + + /* Power domain configuration data */ + const struct mod_power_domain_element_config *config; + + /* + * Mask of the valid states. Bit \c n in \ref valid_states_mask is equal + * to one if and only if the state \c n is a valid state for the power + * domain. The number of bits of this field has to be greater or equal than + * MOD_PD_STATE_COUNT_MAX. + */ + uint32_t valid_state_mask; + + /* + * Table of allowed state masks. Bit \c n of the entry \c m is equal to + * one if and only if the state \c n for the power domain is allowed when + * its parent is in state \c m. The number of bits of each entry of this + * table has to be greater or equal than MOD_PD_STATE_COUNT_MAX. + */ + const uint32_t *allowed_state_mask_table; + + /* Size of the table of allowed state masks */ + size_t allowed_state_mask_table_size; + + /* Pointer to the power domain's parent context */ + struct pd_ctx *parent; + + /* + * Pointer to the context of the power domain's first child. This + * field is equal to NULL if the power domain does not have any children. + */ + struct pd_ctx *first_child; + + /* + * Pointer to the context of the power domain sibling in the chain of the + * children power domains of their parent. + */ + struct pd_ctx *sibling; + + /* Requested power state for the power domain */ + unsigned int requested_state; + + /* Last power state requested to the driver for the power domain */ + unsigned int state_requested_to_driver; + + /* + * Current state of the power domain. When a transition is in progress, the + * current state is different from the last requested state. + */ + unsigned int current_state; + + /* Pending response context */ + struct response_ctx response; + + /* Context for the power state transition notification */ + struct power_state_transition_notification_ctx + power_state_transition_notification_ctx; + + /* Context for the power state pre-transition notification */ + struct power_state_pre_transition_notification_ctx + power_state_pre_transition_notification_ctx; +}; + +struct system_suspend_ctx { + /* Flag indicating if a system suspend is ongoing (true) or not (false) */ + bool ongoing; + + /* Last standing core context */ + struct pd_ctx *last_core_pd; + + /* Suspend state for the system power domain */ + unsigned int state; +}; + +struct mod_pd_ctx { + /* Module configuration data */ + struct mod_power_domain_config *config; + + /* Table of power domain contexts */ + struct pd_ctx *pd_ctx_table; + + /* Number of power domains */ + unsigned int pd_count; + + /* Log module API */ + const struct mod_log_api *log_api; + + /* Context of the system power domain */ + struct pd_ctx *system_pd_ctx; + + /* System suspend context */ + struct system_suspend_ctx system_suspend; +}; + +/* + * Power domain module events + */ + +/* Power module event indexes */ +enum pd_event_idx { + PD_EVENT_IDX_SET_STATE, + PD_EVENT_IDX_GET_STATE, + PD_EVENT_IDX_RESET, + PD_EVENT_IDX_REPORT_POWER_STATE_TRANSITION, + PD_EVENT_IDX_SYSTEM_SUSPEND, + PD_EVENT_IDX_SYSTEM_SHUTDOWN, + PD_EVENT_COUNT +}; + +/* Standard parameter of a response event */ +struct pd_response { + /* Status of the event processing */ + int status; +}; + +/* + * PD_EVENT_IDX_SET_STATE + * Parameters of the set state request event + */ +struct pd_set_state_request { + /* + * The composite state that defines the power state that the power domain, + * target of the request, has to be put into and possibly the power states + * the ancestors of the power domain have to be put into. + */ + uint32_t composite_state; + uint32_t domain_id; +}; + +/* Parameters of the set state response event */ +struct pd_set_state_response { + /* Status of the set state request event processing */ + int status; + + /* + * The composite state that defines the power state that the power domain, + * target of the request, had to be put into and possibly the power states + * the ancestors of the power domain had to be put into. + */ + uint32_t composite_state; +}; + +/* + * PD_EVENT_IDX_GET_STATE + * Parameters of the get state request event + */ +struct pd_get_state_request { + /* + * Flag indicating if the composite state of the power domain and its + * ancestors has to be returned (composite=true) or just the power domain + * state (composite=false). + */ + bool composite; +}; + +/* Parameters of the get state response event */ +struct pd_get_state_response { + /* Status of the get state request event processing */ + int status; + + /* Copy of the "composite" request parameter */ + bool composite; + + /* + * The power state of the power domain target of the request or the + * composite state of the power domain and its ancestors depending on the + * value of the "composite" request parameter. + */ + uint32_t state; +}; + +/* + * PD_EVENT_IDX_REPORT_POWER_STATE_TRANSITION + * Parameters of the power state transition report event + */ +struct pd_power_state_transition_report { + /* The new power state of the power domain */ + uint32_t state; +}; + +/* + * PD_EVENT_IDX_SYSTEM_SUSPEND + * Parameters of the system suspend request event + */ +struct pd_system_suspend_request { + /* System suspend state, platform specific */ + unsigned int state; +}; + +/* + * PD_EVENT_IDX_SYSTEM_SHUTDOWN + * Parameters of the system shutdown request event + */ +struct pd_system_shutdown_request { + /* System shutdown type */ + enum mod_pd_system_shutdown system_shutdown; +}; + +/* + * For each power level, shift in a composite state of the state for the power + * level. + */ +static const unsigned int mod_pd_cs_level_state_shift[MOD_PD_LEVEL_COUNT] = { + MOD_PD_CS_LEVEL_0_STATE_SHIFT, + MOD_PD_CS_LEVEL_1_STATE_SHIFT, + MOD_PD_CS_LEVEL_2_STATE_SHIFT, + MOD_PD_CS_LEVEL_3_STATE_SHIFT +}; + +/* + * Internal variables + */ +static const char *const default_state_name_table[] = { + "OFF", "ON", "SLEEP", "3", "4", "5", "6", "7", + "8", "9", "10", "11", "12", "13", "14", "15" +}; + +/*! + * @endcond + */ + +/*! + * \} + */ + +/*! + * @} + */ + +/*! + * @} + */ + +#endif /* MOD_RCAR_POWER_DOMAIN_H */ diff --git a/product/rcar/module/rcar_power_domain/src/Makefile b/product/rcar/module/rcar_power_domain/src/Makefile new file mode 100644 index 00000000..18ec7212 --- /dev/null +++ b/product/rcar/module/rcar_power_domain/src/Makefile @@ -0,0 +1,11 @@ +# +# Renesas SCP/MCP Software +# Copyright (c) 2020, Renesas Electronics Corporation. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +BS_LIB_NAME := Power domain +BS_LIB_SOURCES := mod_rcar_power_domain.c + +include $(BS_DIR)/lib.mk diff --git a/product/rcar/module/rcar_power_domain/src/mod_rcar_power_domain.c b/product/rcar/module/rcar_power_domain/src/mod_rcar_power_domain.c new file mode 100644 index 00000000..ea355263 --- /dev/null +++ b/product/rcar/module/rcar_power_domain/src/mod_rcar_power_domain.c @@ -0,0 +1,1567 @@ +/* + * Renesas SCP/MCP Software + * Copyright (c) 2015-2020, Arm Limited and Contributors. All rights reserved. + * Copyright (c) 2020, Renesas Electronics Corporation. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <config_rcar_power_domain.h> +#include <mmio.h> +#include <rcar_core.h> +#include <rcar_scmi_id.h> + +#include <mod_rcar_power_domain.h> + +#include <fwk_assert.h> +#include <fwk_element.h> +#include <fwk_id.h> +#include <fwk_log.h> +#include <fwk_macros.h> +#include <fwk_mm.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <fwk_multi_thread.h> +#include <fwk_notification.h> +#include <fwk_status.h> +#include <fwk_thread.h> + +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> + +/* + * Internal variables + */ +static struct mod_pd_ctx mod_pd_ctx; + +/* + * Utility functions + */ + +/* Functions related to power domain positions in the power domain tree */ +static bool is_valid_tree_pos(uint64_t tree_pos) +{ + return (tree_pos < MOD_PD_TREE_POS(MOD_PD_LEVEL_COUNT, 0, 0, 0, 0)); +} + +static enum mod_pd_level get_level_from_tree_pos(uint64_t tree_pos) +{ + return (enum mod_pd_level)( + (tree_pos >> MOD_PD_TREE_POS_LEVEL_SHIFT) & MOD_PD_TREE_POS_LEVEL_MASK); +} + +/* State related utility functions */ +static bool is_valid_state(const struct pd_ctx *pd, unsigned int state) +{ + return (state < MOD_PD_STATE_COUNT_MAX) && + ((pd->valid_state_mask & (1 << state)) != 0); +} + +static unsigned int normalize_state(unsigned int state) +{ + switch (state) { + case MOD_PD_STATE_OFF: + return (MOD_PD_STATE_COUNT_MAX + 1); + + case MOD_PD_STATE_SLEEP: + return MOD_PD_STATE_COUNT_MAX; + + default: + return state; + } +} + +static bool is_deeper_state( + unsigned int state, + unsigned int state_to_compare_to) +{ + return normalize_state(state) > normalize_state(state_to_compare_to); +} + +static bool is_shallower_state( + unsigned int state, + unsigned int state_to_compare_to) +{ + return normalize_state(state) < normalize_state(state_to_compare_to); +} + +static bool is_allowed_by_child( + const struct pd_ctx *child, + unsigned int parent_state, + unsigned int child_state) +{ + if (parent_state >= child->allowed_state_mask_table_size) + return false; + + return ( + (child->allowed_state_mask_table[parent_state] & (1 << child_state)) != + 0); +} + +static const char *get_state_name(const struct pd_ctx *pd, unsigned int state) +{ + static char const unknown_name[] = "Unknown"; + + if (state < pd->config->state_name_table_size) + return pd->config->state_name_table[state]; + else if (state < FWK_ARRAY_SIZE(default_state_name_table)) + return default_state_name_table[state]; + else + return unknown_name; +} + +/* Functions related to a composite state */ +static unsigned int get_level_state_from_composite_state( + uint32_t composite_state, + enum mod_pd_level level) +{ + return (composite_state >> mod_pd_cs_level_state_shift[level]) & + MOD_PD_CS_STATE_MASK; +} + +static enum mod_pd_level get_highest_level_from_composite_state( + uint32_t composite_state) +{ + return (enum mod_pd_level)( + (composite_state >> MOD_PD_CS_LEVEL_SHIFT) & MOD_PD_CS_STATE_MASK); +} + +static bool is_valid_composite_state( + struct pd_ctx *target_pd, + uint32_t composite_state) +{ + enum mod_pd_level level; + enum mod_pd_level highest_level; + unsigned int state, child_state = MOD_PD_STATE_OFF; + struct pd_ctx *pd = target_pd; + struct pd_ctx *child = NULL; + + assert(target_pd != NULL); + + if (composite_state & (~MOD_PD_CS_VALID_BITS)) + goto error; + + level = get_level_from_tree_pos(pd->config->tree_pos); + highest_level = get_highest_level_from_composite_state(composite_state); + + if ((highest_level < level) || (highest_level >= MOD_PD_LEVEL_COUNT)) + goto error; + + for (; level <= highest_level; level++) { + if (pd == NULL) + goto error; + + state = get_level_state_from_composite_state(composite_state, level); + if (!is_valid_state(pd, state)) + goto error; + + if ((child != NULL) && !is_allowed_by_child(child, state, child_state)) + goto error; + + child = pd; + child_state = state; + pd = pd->parent; + } + + return true; + +error: + FWK_LOG_ERR( + "[PD] ERROR: Invalid composite state for %s: 0x%08x.", + fwk_module_get_name(target_pd->id), + composite_state); + + return false; +} + +/* + * Check whether a transition to a given power state for a power domain is + * possible given the current state of its parent and children (if any). + * + * \param pd Description of the power domain to check the power state transition + * for. + * \param state Power state. + */ +static bool is_allowed_by_parent_and_children( + struct pd_ctx *pd, + unsigned int state) +{ + struct pd_ctx *parent, *child; + + parent = pd->parent; + if (parent != NULL) { + if (!is_allowed_by_child(pd, parent->current_state, state)) + return false; + } + + child = pd->first_child; + while (child != NULL) { + if (!is_allowed_by_child(child, state, child->current_state)) + return false; + child = child->sibling; + } + + return true; +} + +/* + * Check whether a power state pre-transition notification must be sent. + * + * \param pd Description of the power domain + * \param state Power state the power domain has to transit to + * + * \retval true A power state pre-transition notification must be sent. + * \retval false A power state pre-transition notification doesn't have to be + * sent. + */ +static bool check_power_state_pre_transition_notification( + struct pd_ctx *pd, + unsigned int state) +{ + if (!is_deeper_state(state, pd->state_requested_to_driver)) + return false; + + if ((state == pd->power_state_pre_transition_notification_ctx.state) && + pd->power_state_pre_transition_notification_ctx.valid) { + return ( + pd->power_state_pre_transition_notification_ctx.response_status != + FWK_SUCCESS); + } + + return true; +} + +/* + * Initiate a power state pre-transition notification if necessary. + * + * \param pd Description of the power domain to initiate the notification + * for. + * + * \retval true Waiting for notification responses. + * \retval false Not waiting for any notification response. + */ +static bool initiate_power_state_pre_transition_notification(struct pd_ctx *pd) +{ + unsigned int state; + struct fwk_event notification_event = { + .id = mod_pd_notification_id_power_state_pre_transition, + .response_requested = true + }; + struct mod_pd_power_state_pre_transition_notification_params *params; + + state = pd->requested_state; + if (!check_power_state_pre_transition_notification(pd, state)) + return false; + + /* + * If still waiting for some responses on the previous power state + * pre-transition notification, wait for them before to issue the next one. + */ + if (pd->power_state_pre_transition_notification_ctx.pending_responses != 0) + return true; + + params = (struct mod_pd_power_state_pre_transition_notification_params *) + notification_event.params; + params->current_state = pd->current_state; + params->target_state = state; + fwk_notification_notify( + ¬ification_event, + &pd->power_state_pre_transition_notification_ctx.pending_responses); + + pd->power_state_pre_transition_notification_ctx.state = state; + pd->power_state_pre_transition_notification_ctx.response_status = + FWK_SUCCESS; + pd->power_state_pre_transition_notification_ctx.valid = true; + + return ( + pd->power_state_pre_transition_notification_ctx.pending_responses != 0); +} + +/* + * Initiate the transition to a power state for a power domain. + * + * \param pd Description of the power domain to initiate the state transition + * for. + * + * \retval FWK_SUCCESS The power state transition was initiated. + * \retval FWK_E_DEVICE The power state transition was denied by the driver. + * \return One of the other driver-defined error codes. + */ +static int initiate_power_state_transition(struct pd_ctx *pd) +{ + int status; + unsigned int state = pd->requested_state; + + status = pd->driver_api->set_state(pd->driver_id, state); + pd->current_state = state; + + FWK_LOG_TRACE( + "[PD] %s: %s->%s, %d.", + fwk_module_get_name(pd->id), + get_state_name(pd, pd->state_requested_to_driver), + get_state_name(pd, state), + status); + + pd->state_requested_to_driver = state; + + return status; +} + +/* + * Respond to a request. + * + * \param pd Description of the power domain in charge of the response + * \param status Response status + */ +static void respond(struct pd_ctx *pd, int status) +{ + struct fwk_event resp_event; + const struct pd_set_state_request *req_params = + (struct pd_set_state_request *)(&resp_event.params); + struct pd_set_state_response *resp_params = + (struct pd_set_state_response *)(&resp_event.params); + + if (!pd->response.pending) + return; + + status = fwk_thread_get_delayed_response( + pd->id, pd->response.cookie, &resp_event); + pd->response.pending = false; + + if (status != FWK_SUCCESS) + return; + + resp_params->composite_state = req_params->composite_state; + resp_params->status = status; + + fwk_thread_put_event(&resp_event); +} + +/* + * Determine whether a composite state requires that the transition begins + * with the highest or lowest level. + * + * \param lowest_pd Target of the composite state transition request. + * \param uint32_t composite_state Target composite state. + * \retval true The power state transition must propagate upwards. + * \retval false The power state transition must propagate downwards. + */ +static bool is_upwards_transition_propagation( + const struct pd_ctx *lowest_pd, + uint32_t composite_state) +{ + enum mod_pd_level lowest_level, highest_level, level; + const struct pd_ctx *pd; + unsigned int state; + + lowest_level = get_level_from_tree_pos(lowest_pd->config->tree_pos); + highest_level = get_highest_level_from_composite_state(composite_state); + + for (level = lowest_level, pd = lowest_pd; level <= highest_level; + level++, pd = pd->parent) { + state = get_level_state_from_composite_state(composite_state, level); + if (state == pd->requested_state) + continue; + + return is_deeper_state(state, pd->requested_state); + } + + return false; +} +static bool is_allowed_by_children(const struct pd_ctx *pd, unsigned int state) +{ + const struct pd_ctx *child; + + for (child = pd->first_child; child != NULL; child = child->sibling) { + if (!is_allowed_by_child(child, state, child->requested_state)) + return false; + } + + return true; +} + +static void process_set_state_request( + struct pd_ctx *lowest_pd, + struct pd_set_state_request *req_params, + struct fwk_event *resp_event) +{ + int status; + struct pd_set_state_response *resp_params = + (struct pd_set_state_response *)resp_event->params; + uint32_t composite_state; + bool up; + enum mod_pd_level lowest_level, highest_level, level; + unsigned int nb_pds; + struct pd_ctx *pd; + const struct pd_ctx *parent; + unsigned int pd_index; + unsigned int state; + bool first_power_state_transition_initiated = false; + + /* A set state request cancels any pending system suspend. */ + mod_pd_ctx.system_suspend.ongoing = false; + + composite_state = req_params->composite_state; + up = is_upwards_transition_propagation(lowest_pd, composite_state); + + /* + * It has already been tested as part of the composite state validation that + * 'highest_level >= lowest_level' and 'highest_level' is lower + * than the highest power level. + */ + lowest_level = get_level_from_tree_pos(lowest_pd->config->tree_pos); + highest_level = get_highest_level_from_composite_state(composite_state); + nb_pds = highest_level - lowest_level + 1; + + status = FWK_SUCCESS; + pd = lowest_pd; + for (pd_index = 0; pd_index < nb_pds; pd_index++, pd = pd->parent) { + if (up) + level = lowest_level + pd_index; + else { + /* + * When walking down the power domain tree, get the context of the + * next power domain to process as well as its level. + */ + pd = lowest_pd; + for (level = lowest_level; level < (highest_level - pd_index); + level++) + pd = pd->parent; + } + + state = get_level_state_from_composite_state(composite_state, level); + if (state == pd->requested_state) { + continue; + } + + /* + * Check that the requested power state is compatible with the states + * currently requested for the parent and children of the power domain. + */ + parent = pd->parent; + if ((parent != NULL) && + (!is_allowed_by_child(pd, parent->requested_state, state))) { + status = FWK_E_PWRSTATE; + break; + } + + if (!is_allowed_by_children(pd, state)) + continue; + + /* + * A new valid power state is requested for the power domain. Send any + * pending response concerning the previous requested power state. + */ + pd->requested_state = state; + pd->power_state_pre_transition_notification_ctx.valid = false; + respond(pd, FWK_E_OVERWRITTEN); + + if (pd->state_requested_to_driver == state) + continue; + + /* + * If a power state transition has already been initiated for an + * ancestor or descendant, we don't initiate the power state transition + * now. It will be initiated on completion of the transition of one + * of its ancestor or descendant. + */ + if (first_power_state_transition_initiated) + continue; + + /* + * If the parent or a child is not currently in a power state + * compatible with the new requested state for the power domain, do not + * initiate the transition now as well. It will be initiated when the + * parent and the children are in a proper state. + */ + if (!is_allowed_by_parent_and_children(pd, state)) + continue; + + /* + * Defer the power state transition if power state pre-transition + * notification responses need to be waited for. + */ + if (initiate_power_state_pre_transition_notification(pd)) + continue; + + status = initiate_power_state_transition(pd); + first_power_state_transition_initiated = (status == FWK_SUCCESS); + } + + if (!resp_event->response_requested) + return; + + resp_params->status = status; + resp_params->composite_state = composite_state; +} + +/* + * Complete a system suspend + * + * Following the shutdown of the last standing core put all of its ancestors + * in the MOD_PD_STATE_OFF state but the system power domain which is put + * into the state that has been asked for. + * + * target_pd Description of the power domain target of the 'set composite state' + * request to suspend the system in the desired state. + */ +static int complete_system_suspend(struct pd_ctx *target_pd) +{ + enum mod_pd_level level; + unsigned int composite_state = 0; + struct pd_ctx *pd = target_pd; + struct fwk_event resp_event; + struct pd_set_state_response *resp_params = + (struct pd_set_state_response *)(&resp_event.params); + + /* + * Traverse the PD tree bottom-up from current power domain to the top + * to build the composite state with MOD_PD_STATE_OFF power state for all + * levels but the last one. + */ + level = get_level_from_tree_pos(target_pd->config->tree_pos); + do { + composite_state |= + ((pd->parent != NULL) ? MOD_PD_STATE_OFF : + mod_pd_ctx.system_suspend.state) + << mod_pd_cs_level_state_shift[level++]; + pd = pd->parent; + } while (pd != NULL); + + /* + * Finally, we need to update the highest valid level in the composite + * state. + */ + composite_state |= (--level) << MOD_PD_CS_LEVEL_SHIFT; + + resp_event = (struct fwk_event){ 0 }; + + process_set_state_request( + target_pd, + &((struct pd_set_state_request){ + .composite_state = composite_state, + }), + &resp_event); + + return resp_params->status; +} + +/* + * Process a 'get composite state' request. + * + * pd Description of the target of the 'get state' request + * req_params Parameters of the 'get state' request + * resp_params Parameters of the 'get state' request response to be filled in + */ +static void process_get_state_request( + struct pd_ctx *pd, + const struct pd_get_state_request *req_params, + struct pd_get_state_response *resp_params) +{ + enum mod_pd_level level = get_level_from_tree_pos(pd->config->tree_pos); + unsigned int composite_state = 0; + if (!req_params->composite) { + resp_params->state = pd->current_state; + } else { + /* + * Traverse the PD tree bottom-up from current power domain to the top, + * collecting node's states and placing them in the correct position in + * the composite state. + */ + do { + composite_state |= pd->current_state + << mod_pd_cs_level_state_shift[level++]; + pd = pd->parent; + } while (pd != NULL); + + /* + * Finally, we need to update the highest valid level in + * the composite state. + */ + composite_state |= (--level) << MOD_PD_CS_LEVEL_SHIFT; + + resp_params->state = composite_state; + } + + resp_params->status = FWK_SUCCESS; +} + +/* + * Process a 'reset' request. + * + * pd Description of the target of the 'reset' request + * resp_params Parameters of the 'reset' request response to be filled in + */ +static void process_reset_request( + struct pd_ctx *pd, + struct pd_response *resp_params) +{ + struct pd_ctx *child; + + if (pd->requested_state == MOD_PD_STATE_OFF) { + resp_params->status = FWK_E_PWRSTATE; + return; + } + + child = pd->first_child; + while (child != NULL) { + if ((child->requested_state != MOD_PD_STATE_OFF) || + (child->current_state != MOD_PD_STATE_OFF)) { + resp_params->status = FWK_E_PWRSTATE; + return; + } + child = child->sibling; + } + + resp_params->status = pd->driver_api->reset(pd->driver_id); +} + +/* + * Process a power state transition report describing a transition to a deeper + * state. + * + * \param pd Target power domain context + */ +static void process_power_state_transition_report_deeper_state( + struct pd_ctx *pd) +{ + struct pd_ctx *parent = pd->parent; + unsigned int requested_state = parent->requested_state; + + if (parent == NULL) + return; + + if (parent->state_requested_to_driver == requested_state) + return; + + if (!is_allowed_by_parent_and_children(parent, requested_state)) + return; + + if (!initiate_power_state_pre_transition_notification(parent)) + initiate_power_state_transition(parent); +} + +/* + * Process a power state transition report describing a transition to a + * shallower state. + * + * \param pd Target power domain context + */ +static void process_power_state_transition_report_shallower_state( + struct pd_ctx *pd) +{ + struct pd_ctx *child; + unsigned int requested_state; + + for (child = pd->first_child; child != NULL; child = child->sibling) { + requested_state = child->requested_state; + if (child->state_requested_to_driver == requested_state) + continue; + + if (!is_allowed_by_parent_and_children(child, requested_state)) + return; + + if (!initiate_power_state_pre_transition_notification(child)) + initiate_power_state_transition(child); + } +} + +/* + * Process a power state transition report + * + * \param pd Description of the target of the power state transition report + * \param report_params Parameters of the power state transition report + */ +static void process_power_state_transition_report( + struct pd_ctx *pd, + const struct pd_power_state_transition_report *report_params) +{ + unsigned int new_state = report_params->state; + unsigned int previous_state; + struct fwk_event notification_event = { + .id = mod_pd_notification_id_power_state_transition, + .response_requested = true + }; + struct mod_pd_power_state_transition_notification_params *params; + + if (new_state == pd->requested_state) + respond(pd, FWK_SUCCESS); + + previous_state = pd->current_state; + pd->current_state = new_state; + + if (pd->power_state_transition_notification_ctx.pending_responses == 0) { + params = (struct mod_pd_power_state_transition_notification_params *) + notification_event.params; + params->state = new_state; + pd->power_state_transition_notification_ctx.state = new_state; + fwk_notification_notify( + ¬ification_event, + &pd->power_state_transition_notification_ctx.pending_responses); + } + + if ((mod_pd_ctx.system_suspend.ongoing) && + (pd == mod_pd_ctx.system_suspend.last_core_pd)) { + mod_pd_ctx.system_suspend.ongoing = false; + complete_system_suspend(pd); + } + + if (is_deeper_state(new_state, previous_state)) + process_power_state_transition_report_deeper_state(pd); + else if (is_shallower_state(new_state, previous_state)) + process_power_state_transition_report_shallower_state(pd); +} + +/* + * Process a 'system suspend' request + * + * req_params Parameters of the 'system suspend' request + * resp_params Parameters of the 'system suspend' request response to be filled + * in + */ +static void process_system_suspend_request( + const struct pd_system_suspend_request *req_params, + struct pd_response *resp_params) +{ + int status; + unsigned int pd_idx; + struct pd_ctx *pd; + struct pd_ctx *last_core_pd = NULL; + struct pd_ctx *last_cluster_pd = NULL; + + /* + * All core related power domains have to be in the MOD_PD_STATE_OFF state + * but one core and its ancestors. + */ + for (pd_idx = 0; pd_idx < mod_pd_ctx.pd_count; pd_idx++) { + pd = &mod_pd_ctx.pd_ctx_table[pd_idx]; + if ((pd->requested_state == MOD_PD_STATE_OFF) && + (pd->current_state == MOD_PD_STATE_OFF)) + continue; + + if (pd->config->attributes.pd_type == MOD_PD_TYPE_CORE) { + if (last_core_pd != NULL) { + resp_params->status = FWK_E_STATE; + return; + } + last_core_pd = pd; + } else if (pd->config->attributes.pd_type == MOD_PD_TYPE_CLUSTER) { + if (last_cluster_pd != NULL) { + if (PD_RCAR_CLUSTER1 != pd_idx) { + resp_params->status = FWK_E_STATE; + return; + } + } + last_cluster_pd = pd; + } + } + + if (last_core_pd == NULL) { + status = complete_system_suspend( + (last_cluster_pd != NULL) ? last_cluster_pd : + mod_pd_ctx.system_pd_ctx); + } else { + status = last_core_pd->driver_api->prepare_core_for_system_suspend( + last_core_pd->driver_id); + if (status == FWK_SUCCESS) { + mod_pd_ctx.system_suspend.ongoing = true; + mod_pd_ctx.system_suspend.last_core_pd = last_core_pd; + mod_pd_ctx.system_suspend.state = req_params->state; + last_core_pd->requested_state = + last_core_pd->state_requested_to_driver = MOD_PD_STATE_OFF; + } + } + + resp_params->status = status; +} + +/* + * Process a 'system shutdown' request + * + * req_params Parameters of the 'system shutdown' request + * resp_params Parameters of the 'system shutdown' request response to be filled + * in + */ +static void process_system_shutdown_request( + const struct pd_system_shutdown_request *req_params, + struct pd_response *resp_params) +{ + int status; + unsigned int pd_idx; + struct pd_ctx *pd; + fwk_id_t pd_id; + + for (pd_idx = 0; pd_idx < mod_pd_ctx.pd_count; pd_idx++) { + pd = &mod_pd_ctx.pd_ctx_table[pd_idx]; + pd_id = FWK_ID_ELEMENT(FWK_MODULE_IDX_RCAR_POWER_DOMAIN, pd_idx); + + FWK_LOG_TRACE("[PD] Shutting down %s.", fwk_module_get_name(pd_id)); + + if (pd->driver_api->shutdown != NULL) { + status = pd->driver_api->shutdown( + pd->driver_id, req_params->system_shutdown); + } else + status = pd->driver_api->set_state(pd->driver_id, MOD_PD_STATE_OFF); + + if (status != FWK_SUCCESS) + FWK_LOG_ERR( + "[PD] ERROR: Shutdown of %s returned %d.", + fwk_module_get_name(pd_id), + status); + else + FWK_LOG_TRACE("[PD] %s shutdown.", fwk_module_get_name(pd_id)); + + pd->requested_state = pd->state_requested_to_driver = + pd->current_state = MOD_PD_STATE_OFF; + } + + resp_params->status = FWK_E_PANIC; +} + +/* + * API functions + */ + +/* Functions common to the public and restricted API */ +static int pd_get_domain_type(fwk_id_t pd_id, enum mod_pd_type *type) +{ + struct pd_ctx *pd; + + if (type == NULL) + return FWK_E_PARAM; + + if (!fwk_module_is_valid_element_id(pd_id)) + return FWK_E_PARAM; + + pd = &mod_pd_ctx.pd_ctx_table[fwk_id_get_element_idx(pd_id)]; + + *type = pd->config->attributes.pd_type; + + return FWK_SUCCESS; +} + +static int pd_get_domain_parent_id(fwk_id_t pd_id, fwk_id_t *parent_pd_id) +{ + const struct pd_ctx *pd; + + if (parent_pd_id == NULL) + return FWK_E_PARAM; + + if (!fwk_module_is_valid_element_id(pd_id)) + return FWK_E_PARAM; + + pd = &mod_pd_ctx.pd_ctx_table[fwk_id_get_element_idx(pd_id)]; + + *parent_pd_id = (pd->parent != NULL) ? pd->parent->id : FWK_ID_NONE; + + return FWK_SUCCESS; +} + +/* Functions specific to the restricted API */ + +static int pd_set_state(fwk_id_t pd_id, unsigned int state) +{ + int status; + struct pd_ctx *pd; + enum mod_pd_level level; + struct fwk_event req; + struct fwk_event resp; + struct pd_set_state_request *req_params = + (struct pd_set_state_request *)(&req.params); + struct pd_set_state_response *resp_params = + (struct pd_set_state_response *)(&resp.params); + + pd = &mod_pd_ctx.pd_ctx_table[fwk_id_get_element_idx(pd_id)]; + + if (!is_valid_state(pd, state)) + return FWK_E_PARAM; + + level = get_level_from_tree_pos(pd->config->tree_pos); + + req = (struct fwk_event){ + .id = FWK_ID_EVENT( + FWK_MODULE_IDX_RCAR_POWER_DOMAIN, PD_EVENT_IDX_SET_STATE), + .target_id = pd_id, + }; + + req_params->composite_state = (level << MOD_PD_CS_LEVEL_SHIFT) | + (state << mod_pd_cs_level_state_shift[level]); + + status = fwk_thread_put_event_and_wait(&req, &resp); + if (status != FWK_SUCCESS) { + return status; + } + + return resp_params->status; +} + +#if 0 +static int pd_set_state_async(fwk_id_t pd_id, + bool response_requested, unsigned int state) +{ + return FWK_SUCCESS; +} +#endif + +static int pd_set_composite_state(fwk_id_t pd_id, uint32_t composite_state) +{ + int status; + struct pd_ctx *pd; + struct fwk_event req; + struct fwk_event resp; + struct pd_set_state_request *req_params = + (struct pd_set_state_request *)(&req.params); + struct pd_set_state_response *resp_params = + (struct pd_set_state_response *)(&resp.params); + + pd = &mod_pd_ctx.pd_ctx_table[fwk_id_get_element_idx(pd_id)]; + + if (!is_valid_composite_state(pd, composite_state)) + return FWK_E_PARAM; + + req = (struct fwk_event){ + .id = FWK_ID_EVENT( + FWK_MODULE_IDX_RCAR_POWER_DOMAIN, PD_EVENT_IDX_SET_STATE), + .source_id = pd->driver_id, + .target_id = pd_id, + }; + + req_params->composite_state = composite_state; + + status = fwk_thread_put_event_and_wait(&req, &resp); + if (status != FWK_SUCCESS) + return status; + + return resp_params->status; +} + +static int pd_set_composite_state_async( + fwk_id_t pd_id, + bool response_requested, + uint32_t composite_state) +{ + return pd_set_composite_state(pd_id, composite_state); +} + +static int pd_get_state(fwk_id_t pd_id, unsigned int *state) +{ + int status; + struct fwk_event req; + struct fwk_event resp; + struct pd_get_state_request *req_params = + (struct pd_get_state_request *)(&req.params); + struct pd_get_state_response *resp_params = + (struct pd_get_state_response *)(&resp.params); + + if (state == NULL) + return FWK_E_PARAM; + + req = (struct fwk_event){ + .id = FWK_ID_EVENT( + FWK_MODULE_IDX_RCAR_POWER_DOMAIN, PD_EVENT_IDX_GET_STATE), + .target_id = pd_id, + }; + + req_params->composite = false; + + status = fwk_thread_put_event_and_wait(&req, &resp); + if (status != FWK_SUCCESS) + return status; + + if (resp_params->status != FWK_SUCCESS) + return resp_params->status; + + *state = resp_params->state; + + return FWK_SUCCESS; +} + +#if 0 +static int pd_get_composite_state(fwk_id_t pd_id, unsigned int *composite_state) +{ + int status; + struct fwk_event req; + struct fwk_event resp; + struct pd_get_state_request *req_params = + (struct pd_get_state_request *)(&req.params); + struct pd_get_state_response *resp_params = + (struct pd_get_state_response *)(&resp.params); + + if (composite_state == NULL) + return FWK_E_PARAM; + + req = (struct fwk_event) { + .id = FWK_ID_EVENT(FWK_MODULE_IDX_RCAR_POWER_DOMAIN, + PD_EVENT_IDX_GET_STATE), + .target_id = pd_id, + }; + + req_params->composite = true; + + status = fwk_thread_put_event_and_wait(&req, &resp); + if (status != FWK_SUCCESS) + return status; + + if (resp_params->status != FWK_SUCCESS) + return resp_params->status; + + *composite_state = resp_params->state; + + return FWK_SUCCESS; +} +#endif + +static int pd_reset(fwk_id_t pd_id) +{ + int status; + struct fwk_event req; + struct fwk_event resp; + struct pd_response *resp_params = (struct pd_response *)(&resp.params); + + req = (struct fwk_event){ + .id = + FWK_ID_EVENT(FWK_MODULE_IDX_RCAR_POWER_DOMAIN, PD_EVENT_IDX_RESET), + .target_id = pd_id, + }; + + status = fwk_thread_put_event_and_wait(&req, &resp); + if (status != FWK_SUCCESS) + return status; + + return resp_params->status; +} + +static int pd_system_suspend(unsigned int state) +{ + int status; + struct fwk_event req; + struct fwk_event resp; + struct pd_system_suspend_request *req_params = + (struct pd_system_suspend_request *)(&req.params); + struct pd_response *resp_params = (struct pd_response *)(&resp.params); + + req = (struct fwk_event){ + .id = FWK_ID_EVENT( + FWK_MODULE_IDX_RCAR_POWER_DOMAIN, PD_EVENT_IDX_SYSTEM_SUSPEND), + .target_id = fwk_module_id_rcar_power_domain, + }; + + req_params->state = state; + + status = fwk_thread_put_event_and_wait(&req, &resp); + if (status != FWK_SUCCESS) + return status; + + return resp_params->status; +} + +static int pd_system_shutdown(enum mod_pd_system_shutdown system_shutdown) +{ + int status; + struct fwk_event req; + struct fwk_event resp; + struct pd_system_shutdown_request *req_params = + (struct pd_system_shutdown_request *)(&req.params); + struct pd_response *resp_params = (struct pd_response *)(&resp.params); + + req = (struct fwk_event){ + .id = FWK_ID_EVENT( + FWK_MODULE_IDX_RCAR_POWER_DOMAIN, PD_EVENT_IDX_SYSTEM_SHUTDOWN), + .target_id = fwk_module_id_rcar_power_domain, + }; + + req_params->system_shutdown = system_shutdown; + + status = fwk_thread_put_event_and_wait(&req, &resp); + if (status != FWK_SUCCESS) + return status; + + return resp_params->status; +} + +/* Functions specific to the driver input API */ + +static int pd_reset_async(fwk_id_t pd_id, bool response_requested) +{ + struct fwk_event req; + struct pd_ctx *pd; + + pd = &mod_pd_ctx.pd_ctx_table[fwk_id_get_element_idx(pd_id)]; + + req = (struct fwk_event){ + .id = + FWK_ID_EVENT(FWK_MODULE_IDX_RCAR_POWER_DOMAIN, PD_EVENT_IDX_RESET), + .source_id = pd->driver_id, + .target_id = pd_id, + .response_requested = response_requested, + }; + + return fwk_thread_put_event(&req); +} + +static int report_power_state_transition( + const struct pd_ctx *pd, + unsigned int state) +{ + struct fwk_event report; + struct pd_power_state_transition_report *report_params = + (struct pd_power_state_transition_report *)(&report.params); + + report = + (struct fwk_event){ .source_id = pd->driver_id, + .target_id = pd->id, + .id = FWK_ID_EVENT( + FWK_MODULE_IDX_RCAR_POWER_DOMAIN, + PD_EVENT_IDX_REPORT_POWER_STATE_TRANSITION) }; + report_params->state = state; + + return fwk_thread_put_event(&report); +} + +static int pd_report_power_state_transition(fwk_id_t pd_id, unsigned int state) +{ + const struct pd_ctx *pd; + + pd = &mod_pd_ctx.pd_ctx_table[fwk_id_get_element_idx(pd_id)]; + + return report_power_state_transition(pd, state); +} + +static int pd_get_last_core_pd_id(fwk_id_t *last_core_pd_id) +{ + if (last_core_pd_id == NULL) + return FWK_E_PARAM; + + *last_core_pd_id = mod_pd_ctx.system_suspend.last_core_pd->id; + + return FWK_SUCCESS; +} + +/* Module APIs */ + +static const struct mod_pd_public_api pd_public_api = { + .get_domain_type = pd_get_domain_type, + .get_domain_parent_id = pd_get_domain_parent_id, +}; + +static const struct mod_pd_restricted_api pd_restricted_api = { + .get_domain_type = pd_get_domain_type, + .get_domain_parent_id = pd_get_domain_parent_id, + + .set_state = pd_set_state, + .set_state_async = pd_set_composite_state_async, + .get_state = pd_get_state, + .reset = pd_reset, + .system_suspend = pd_system_suspend, + .system_shutdown = pd_system_shutdown +}; + +static const struct mod_pd_driver_input_api pd_driver_input_api = { + .set_state_async = pd_set_composite_state_async, + .reset_async = pd_reset_async, + .report_power_state_transition = pd_report_power_state_transition, + .get_last_core_pd_id = pd_get_last_core_pd_id, +}; + +/* + * Framework handlers + */ +static int pd_init(fwk_id_t module_id, unsigned int dev_count, const void *data) +{ + if ((data == NULL) || (dev_count == 0)) + return FWK_E_PARAM; + + mod_pd_ctx.config = (struct mod_power_domain_config *)data; + + if ((mod_pd_ctx.config->authorized_id_table == NULL) && + (mod_pd_ctx.config->authorized_id_table_size != 0)) + return FWK_E_PARAM; + + mod_pd_ctx.pd_ctx_table = fwk_mm_calloc(dev_count, sizeof(struct pd_ctx)); + if (mod_pd_ctx.pd_ctx_table == NULL) + return FWK_E_NOMEM; + + mod_pd_ctx.pd_count = dev_count; + mod_pd_ctx.system_pd_ctx = &mod_pd_ctx.pd_ctx_table[dev_count - 1]; + + return FWK_SUCCESS; +} + +static int pd_power_domain_init( + fwk_id_t pd_id, + unsigned int unused, + const void *config) +{ + static uint64_t max_tree_pos = MOD_PD_INVALID_TREE_POS; + + const struct mod_power_domain_element_config *pd_config = + (const struct mod_power_domain_element_config *)config; + struct pd_ctx *pd; + unsigned int state; + + pd = &mod_pd_ctx.pd_ctx_table[fwk_id_get_element_idx(pd_id)]; + + if (!is_valid_tree_pos(pd_config->tree_pos)) + return FWK_E_PARAM; + + if (pd_config->attributes.pd_type >= MOD_PD_TYPE_COUNT) + return FWK_E_PARAM; + + /* + * Check that the power domains are declared by increasing order of their + * tree position. + */ + if ((max_tree_pos != MOD_PD_INVALID_TREE_POS) && + (pd_config->tree_pos <= max_tree_pos)) + return FWK_E_PARAM; + max_tree_pos = pd_config->tree_pos; + + if ((pd_config->allowed_state_mask_table == NULL) || + (pd_config->allowed_state_mask_table_size == 0)) + return FWK_E_PARAM; + + pd->allowed_state_mask_table = pd_config->allowed_state_mask_table; + pd->allowed_state_mask_table_size = + pd_config->allowed_state_mask_table_size; + + for (state = 0; state < pd->allowed_state_mask_table_size; state++) + pd->valid_state_mask |= pd->allowed_state_mask_table[state]; + + pd->id = pd_id; + pd->config = pd_config; + + return FWK_SUCCESS; +} + +static int pd_post_init(fwk_id_t module_id) +{ + return FWK_SUCCESS; +} + +static int pd_bind(fwk_id_t id, unsigned int round) +{ + int status; + struct pd_ctx *pd; + const struct mod_power_domain_element_config *config; + struct mod_pd_driver_api *driver_api = NULL; + + /* Nothing to do but during the first round of calls */ + if (round != 0) { + return FWK_SUCCESS; + } + +#if 0 + if (fwk_id_is_type(id, FWK_ID_TYPE_MODULE)) { + return fwk_module_bind(FWK_ID_MODULE(FWK_MODULE_IDX_LOG), + FWK_ID_API(FWK_MODULE_IDX_LOG, 0), &mod_pd_ctx.log_api); + } +#endif + + pd = &mod_pd_ctx.pd_ctx_table[fwk_id_get_element_idx(id)]; + config = pd->config; + + status = fwk_module_bind(config->driver_id, config->api_id, &driver_api); + if (status != FWK_SUCCESS) { + return status; + } + + pd->driver_id = config->driver_id; + if ((driver_api->set_state == NULL) || (driver_api->get_state == NULL) || + (driver_api->reset == NULL) || + ((config->attributes.pd_type == MOD_PD_TYPE_CORE) && + (driver_api->prepare_core_for_system_suspend == NULL))) { + return FWK_E_PARAM; + } + + pd->driver_api = driver_api; + + return FWK_SUCCESS; +} + +static int pd_start(fwk_id_t id) +{ + struct pd_ctx *pd; + int index; + struct fwk_event notification_event = { + .source_id = FWK_ID_ELEMENT( + FWK_MODULE_IDX_RCAR_POWER_DOMAIN, + CONFIG_POWER_DOMAIN_CHILD_COUNT + rcar_core_get_count()), + .id = mod_pd_notification_id_power_state_transition, + .response_requested = true, + }; + struct mod_pd_power_state_transition_notification_params *params; + unsigned int notification_count; + + /* Nothing to do for elements */ + if (fwk_module_is_valid_element_id(id)) + return FWK_SUCCESS; + + for (index = mod_pd_ctx.pd_count - 1; index >= 0; index--) { + pd = &mod_pd_ctx.pd_ctx_table[index]; + pd->requested_state = MOD_PD_STATE_OFF; + pd->state_requested_to_driver = MOD_PD_STATE_OFF; + pd->current_state = MOD_PD_STATE_OFF; + } + + /* CPU0 is always on. */ + pd = &mod_pd_ctx.pd_ctx_table[PD_RCAR_CLUS0CORE0]; + pd->current_state = MOD_PD_STATE_ON; + /* CLUSTER0 is always on. */ + pd = &mod_pd_ctx.pd_ctx_table[PD_RCAR_CLUSTER0]; + pd->current_state = MOD_PD_STATE_ON; + /* CLUSTER1 is always on. */ + pd = &mod_pd_ctx.pd_ctx_table[PD_RCAR_CLUSTER1]; + pd->current_state = MOD_PD_STATE_ON; + + params = (struct mod_pd_power_state_transition_notification_params *) + notification_event.params; + params->state = MOD_PD_STATE_ON; + fwk_notification_notify(¬ification_event, ¬ification_count); + + return FWK_SUCCESS; +} + +static int pd_process_bind_request( + fwk_id_t source_id, + fwk_id_t target_id, + fwk_id_t api_id, + const void **api) +{ + struct pd_ctx *pd; + unsigned int id_idx; + + switch (fwk_id_get_api_idx(api_id)) { + case MOD_PD_API_IDX_PUBLIC: + if (!fwk_id_is_type(target_id, FWK_ID_TYPE_MODULE)) + return FWK_E_ACCESS; + *api = &pd_public_api; + break; + + case MOD_PD_API_IDX_RESTRICTED: + if (!fwk_id_is_type(target_id, FWK_ID_TYPE_MODULE)) + return FWK_E_ACCESS; + if (mod_pd_ctx.config->authorized_id_table_size == 0) { + *api = &pd_restricted_api; + return FWK_SUCCESS; + } + for (id_idx = 0; id_idx < mod_pd_ctx.config->authorized_id_table_size; + id_idx++) { + if (fwk_id_is_equal( + source_id, + mod_pd_ctx.config->authorized_id_table[id_idx])) { + *api = &pd_restricted_api; + return FWK_SUCCESS; + } + } + return FWK_E_ACCESS; + + case MOD_PD_API_IDX_DRIVER_INPUT: + if (!fwk_id_is_type(target_id, FWK_ID_TYPE_ELEMENT)) + return FWK_E_ACCESS; + pd = &mod_pd_ctx.pd_ctx_table[fwk_id_get_element_idx(target_id)]; + if (!fwk_id_is_equal(source_id, pd->driver_id)) + return FWK_E_ACCESS; + *api = &pd_driver_input_api; + break; + + default: + return FWK_E_PARAM; + } + + return FWK_SUCCESS; +} + +static int pd_process_event( + const struct fwk_event *event, + struct fwk_event *resp) +{ + struct pd_ctx *pd = NULL; + + if (fwk_id_is_type(event->target_id, FWK_ID_TYPE_ELEMENT)) + pd = &mod_pd_ctx.pd_ctx_table[fwk_id_get_element_idx(event->target_id)]; + + switch (fwk_id_get_event_idx(event->id)) { + case PD_EVENT_IDX_SET_STATE: + assert(pd != NULL); + + process_set_state_request( + pd, (struct pd_set_state_request *)event->params, resp); + + return FWK_SUCCESS; + + case PD_EVENT_IDX_GET_STATE: + assert(pd != NULL); + + process_get_state_request( + pd, + (struct pd_get_state_request *)event->params, + (struct pd_get_state_response *)resp->params); + + return FWK_SUCCESS; + + case PD_EVENT_IDX_RESET: + assert(pd != NULL); + + process_reset_request(pd, (struct pd_response *)resp->params); + + return FWK_SUCCESS; + + case PD_EVENT_IDX_REPORT_POWER_STATE_TRANSITION: + assert(pd != NULL); + + process_power_state_transition_report( + pd, (struct pd_power_state_transition_report *)event->params); + + return FWK_SUCCESS; + + case PD_EVENT_IDX_SYSTEM_SUSPEND: + process_system_suspend_request( + (struct pd_system_suspend_request *)event->params, + (struct pd_response *)resp->params); + + return FWK_SUCCESS; + + case PD_EVENT_IDX_SYSTEM_SHUTDOWN: + process_system_shutdown_request( + (struct pd_system_shutdown_request *)event->params, + (struct pd_response *)resp->params); + + return FWK_SUCCESS; + + default: + FWK_LOG_ERR( + "[PD] ERROR: Invalid power state request: <%d>.", event->id.value); + + return FWK_E_PARAM; + } +} + +static int process_power_state_pre_transition_notification_response( + struct pd_ctx *pd, + struct mod_pd_power_state_pre_transition_notification_resp_params *params) +{ + if (pd->power_state_pre_transition_notification_ctx.pending_responses == + 0) { + assert(false); + return FWK_E_PANIC; + } + + if (params->status != FWK_SUCCESS) { + pd->power_state_pre_transition_notification_ctx.response_status = + FWK_E_DEVICE; + } + + pd->power_state_pre_transition_notification_ctx.pending_responses--; + if (pd->power_state_pre_transition_notification_ctx.pending_responses != 0) + return FWK_SUCCESS; + + if (pd->power_state_pre_transition_notification_ctx.valid == true) { + /* + * All the notification responses have been received, the requested + * state for the power domain has not changed in the + * meantime and all the notified entities agreed on the power state + * transition, proceed with it. + */ + if (pd->power_state_pre_transition_notification_ctx.response_status == + FWK_SUCCESS) + initiate_power_state_transition(pd); + } else { + /* + * All the notification responses have been received but the + * requested state for the power domain has changed, start the + * processings for the new requested state. + */ + if ((pd->requested_state == pd->state_requested_to_driver) || + (!is_allowed_by_parent_and_children(pd, pd->requested_state))) + return FWK_SUCCESS; + + if (!initiate_power_state_pre_transition_notification(pd)) + initiate_power_state_transition(pd); + } + + return FWK_SUCCESS; +} + +static int process_power_state_transition_notification_response( + struct pd_ctx *pd) +{ + struct fwk_event notification_event; + struct mod_pd_power_state_transition_notification_params *params; + + if (pd->power_state_transition_notification_ctx.pending_responses == 0) { + assert(false); + return FWK_E_PANIC; + } + + pd->power_state_transition_notification_ctx.pending_responses--; + if (pd->power_state_transition_notification_ctx.pending_responses != 0) + return FWK_SUCCESS; + + if (pd->power_state_transition_notification_ctx.state == pd->current_state) + return FWK_SUCCESS; + + /* + * While receiving the responses, the power state of the power domain + * has changed. Send a notification for the current power state. + */ + notification_event.id = mod_pd_notification_id_power_state_transition; + notification_event.response_requested = true; + params = (struct mod_pd_power_state_transition_notification_params *) + notification_event.params; + params->state = pd->current_state; + + pd->power_state_transition_notification_ctx.state = pd->current_state; + fwk_notification_notify( + ¬ification_event, + &pd->power_state_transition_notification_ctx.pending_responses); + + return FWK_SUCCESS; +} + +static int pd_process_notification( + const struct fwk_event *event, + struct fwk_event *resp) +{ + struct pd_ctx *pd; + + /* Only responses are expected. */ + if (!event->is_response) { + assert(false); + return FWK_E_SUPPORT; + } + + if (!fwk_module_is_valid_element_id(event->target_id)) { + assert(false); + return FWK_E_PARAM; + } + + pd = &mod_pd_ctx.pd_ctx_table[fwk_id_get_element_idx(event->target_id)]; + + if (fwk_id_is_equal( + event->id, mod_pd_notification_id_power_state_transition)) + return process_power_state_transition_notification_response(pd); + + return process_power_state_pre_transition_notification_response( + pd, + (struct mod_pd_power_state_pre_transition_notification_resp_params *) + event->params); +} + +/* Module definition */ +const struct fwk_module module_rcar_power_domain = { + .name = "POWER DOMAIN", + .type = FWK_MODULE_TYPE_HAL, + .api_count = MOD_PD_API_IDX_COUNT, + .event_count = PD_EVENT_COUNT, + .notification_count = MOD_PD_NOTIFICATION_COUNT, + .init = pd_init, + .element_init = pd_power_domain_init, + .post_init = pd_post_init, + .bind = pd_bind, + .start = pd_start, + .process_bind_request = pd_process_bind_request, + .process_event = pd_process_event, + .process_notification = pd_process_notification +}; diff --git a/product/rcar/scp_ramfw/config_rcar_power_domain.c b/product/rcar/scp_ramfw/config_rcar_power_domain.c new file mode 100644 index 00000000..75558351 --- /dev/null +++ b/product/rcar/scp_ramfw/config_rcar_power_domain.c @@ -0,0 +1,423 @@ +/* + * Renesas SCP/MCP Software + * Copyright (c) 2020, Renesas Electronics Corporation. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <config_rcar_pd_core.h> +#include <config_rcar_pd_sysc.h> +#include <config_rcar_power_domain.h> +#include <rcar_core.h> + +#include <mod_rcar_pd_core.h> +#include <mod_rcar_power_domain.h> +#include <mod_system_power.h> + +#include <fwk_element.h> +#include <fwk_macros.h> +#include <fwk_mm.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> + +#include <stdint.h> +#include <string.h> + +static const char *core_pd_name_table[RCAR_CORE_PER_CLUSTER_MAX] = { + "CLUS0CORE0", "CLUS0CORE1", "CLUS0CORE2", "CLUS0CORE3", + "CLUS1CORE0", "CLUS1CORE1", "CLUS1CORE2", "CLUS1CORE3", +}; + +/* Mask of the allowed states for the systop power domain */ +static const uint32_t systop_allowed_state_mask_table[] = { + [0] = MOD_PD_STATE_OFF_MASK | MOD_PD_STATE_ON_MASK | + (1 << MOD_SYSTEM_POWER_POWER_STATE_SLEEP0) | + (1 << MOD_SYSTEM_POWER_POWER_STATE_SLEEP1) +}; +/* + * Mask of the allowed states for the top level power domains + * (but the cluster power domains) depending on the system states. + */ +static const uint32_t toplevel_allowed_state_mask_table[] = { + [MOD_PD_STATE_OFF] = MOD_PD_STATE_OFF_MASK, + [MOD_PD_STATE_ON] = MOD_PD_STATE_OFF_MASK | MOD_PD_STATE_ON_MASK, + [MOD_SYSTEM_POWER_POWER_STATE_SLEEP0] = MOD_PD_STATE_OFF_MASK, + [MOD_SYSTEM_POWER_POWER_STATE_SLEEP1] = MOD_PD_STATE_OFF_MASK +}; +/* + * Mask of the allowed states for the cluster power domain depending on the + * system states. + */ +static const uint32_t cluster_pd_allowed_state_mask_table[] = { + [MOD_PD_STATE_OFF] = MOD_PD_STATE_OFF_MASK, + [MOD_PD_STATE_ON] = + MOD_PD_STATE_OFF_MASK | MOD_PD_STATE_ON_MASK | MOD_PD_STATE_SLEEP_MASK, + [MOD_SYSTEM_POWER_POWER_STATE_SLEEP0] = MOD_PD_STATE_OFF_MASK, + [MOD_SYSTEM_POWER_POWER_STATE_SLEEP1] = MOD_PD_STATE_OFF_MASK +}; + +/* Mask of the allowed states for a core depending on the cluster states. */ +static const uint32_t core_pd_allowed_state_mask_table[] = { + [MOD_PD_STATE_OFF] = MOD_PD_STATE_OFF_MASK, + [MOD_PD_STATE_ON] = + MOD_PD_STATE_OFF_MASK | MOD_PD_STATE_ON_MASK | MOD_PD_STATE_SLEEP_MASK, + [MOD_PD_STATE_SLEEP] = MOD_PD_STATE_OFF_MASK | MOD_PD_STATE_SLEEP_MASK, +}; + +/* Power module specific configuration data (none) */ +static const struct mod_power_domain_config rcar_power_domain_config = {}; + +static struct + fwk_element + rcar_power_domain_static_element_table + [] = + { + [CONFIG_POWER_DOMAIN_CHILD_CLUSTER0] = + { + .name = "CLUS0", + .data = + &( + (struct mod_power_domain_element_config){ + .attributes.pd_type = + MOD_PD_TYPE_CLUSTER, + .tree_pos = MOD_PD_TREE_POS( + MOD_PD_LEVEL_2, + 0, + 0, + CONFIG_POWER_DOMAIN_CHILD_CLUSTER0, + 0), + .driver_id = FWK_ID_ELEMENT_INIT( + FWK_MODULE_IDX_RCAR_PD_CORE, + RCAR_PD_CORE_ELEMENT_IDX_CLU0), + .api_id = + FWK_ID_API_INIT( + FWK_MODULE_IDX_RCAR_PD_CORE, + MOD_RCAR_PD_SYSC_API_IDX_POWER_DOMAIN_DRIVER), + .allowed_state_mask_table = cluster_pd_allowed_state_mask_table, + .allowed_state_mask_table_size = + FWK_ARRAY_SIZE( + cluster_pd_allowed_state_mask_table) }), + }, + [CONFIG_POWER_DOMAIN_CHILD_CLUSTER1] = + { + .name = "CLUS1", + .data = &((struct mod_power_domain_element_config){ .attributes + .pd_type = MOD_PD_TYPE_CLUSTER, + .tree_pos = MOD_PD_TREE_POS( + MOD_PD_LEVEL_2, + 0, + 0, + CONFIG_POWER_DOMAIN_CHILD_CLUSTER1, + 0), + .driver_id = FWK_ID_ELEMENT_INIT( + FWK_MODULE_IDX_RCAR_PD_CORE, + RCAR_PD_CORE_ELEMENT_IDX_CLU1), + .api_id = FWK_ID_API_INIT( + FWK_MODULE_IDX_RCAR_PD_CORE, + MOD_RCAR_PD_SYSC_API_IDX_POWER_DOMAIN_DRIVER), + .allowed_state_mask_table = cluster_pd_allowed_state_mask_table, + .allowed_state_mask_table_size = FWK_ARRAY_SIZE( + cluster_pd_allowed_state_mask_table) }), + }, + [CONFIG_POWER_DOMAIN_CHILD_A3IR] = + { + .name = "a3ir", + .data = &((struct mod_power_domain_element_config){ .attributes + .pd_type = MOD_PD_TYPE_DEVICE, + .tree_pos = MOD_PD_TREE_POS( + MOD_PD_LEVEL_2, + 0, + 0, + CONFIG_POWER_DOMAIN_CHILD_A3IR, + 0), + .driver_id = FWK_ID_ELEMENT_INIT( + FWK_MODULE_IDX_RCAR_PD_SYSC, + RCAR_PD_SYSC_ELEMENT_IDX_A3IR), + .api_id = FWK_ID_API_INIT( + FWK_MODULE_IDX_RCAR_PD_SYSC, + 0), + .allowed_state_mask_table = toplevel_allowed_state_mask_table, + .allowed_state_mask_table_size = FWK_ARRAY_SIZE( + toplevel_allowed_state_mask_table) }), + }, + [CONFIG_POWER_DOMAIN_CHILD_3DGE] = + { + .name = "3dg-e", + .data = &((struct mod_power_domain_element_config){ .attributes + .pd_type = MOD_PD_TYPE_DEVICE, + .tree_pos = MOD_PD_TREE_POS( + MOD_PD_LEVEL_2, + 0, + 0, + CONFIG_POWER_DOMAIN_CHILD_3DGE, + 0), + .driver_id = FWK_ID_ELEMENT_INIT( + FWK_MODULE_IDX_RCAR_PD_SYSC, + RCAR_PD_SYSC_ELEMENT_IDX_3DGE), + .api_id = FWK_ID_API_INIT( + FWK_MODULE_IDX_RCAR_PD_SYSC, + 0), + .allowed_state_mask_table = toplevel_allowed_state_mask_table, + .allowed_state_mask_table_size = FWK_ARRAY_SIZE( + toplevel_allowed_state_mask_table) }), + }, + [CONFIG_POWER_DOMAIN_CHILD_3DGD] = + { + .name = "3dg-d", + .data = &( + (struct + mod_power_domain_element_config){ .attributes.pd_type = MOD_PD_TYPE_DEVICE, .tree_pos = MOD_PD_TREE_POS(MOD_PD_LEVEL_2, 0, 0, CONFIG_POWER_DOMAIN_CHILD_3DGD, 0), .driver_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_RCAR_PD_SYSC, RCAR_PD_SYSC_ELEMENT_IDX_3DGD), .api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_RCAR_PD_SYSC, 0), .allowed_state_mask_table = toplevel_allowed_state_mask_table, .allowed_state_mask_table_size = FWK_ARRAY_SIZE(toplevel_allowed_state_mask_table) }), + }, + [CONFIG_POWER_DOMAIN_CHILD_3DGC] = + { + .name = "3dg-c", + .data = &( + (struct + mod_power_domain_element_config){ .attributes + .pd_type = MOD_PD_TYPE_DEVICE, + .tree_pos = MOD_PD_TREE_POS( + MOD_PD_LEVEL_2, + 0, + 0, + CONFIG_POWER_DOMAIN_CHILD_3DGC, + 0), + .driver_id = FWK_ID_ELEMENT_INIT( + FWK_MODULE_IDX_RCAR_PD_SYSC, + RCAR_PD_SYSC_ELEMENT_IDX_3DGC), + .api_id = FWK_ID_API_INIT( + FWK_MODULE_IDX_RCAR_PD_SYSC, + 0), + .allowed_state_mask_table = + toplevel_allowed_state_mask_table, + .allowed_state_mask_table_size = + FWK_ARRAY_SIZE( + toplevel_allowed_state_mask_table) }), + }, + [CONFIG_POWER_DOMAIN_CHILD_3DGB] = + { + .name = "3dg-b", + .data = &( + (struct mod_power_domain_element_config){ + .attributes.pd_type = + MOD_PD_TYPE_DEVICE, + .tree_pos = MOD_PD_TREE_POS( + MOD_PD_LEVEL_2, + 0, + 0, + CONFIG_POWER_DOMAIN_CHILD_3DGB, + 0), + .driver_id = + FWK_ID_ELEMENT_INIT( + FWK_MODULE_IDX_RCAR_PD_SYSC, + RCAR_PD_SYSC_ELEMENT_IDX_3DGB), + .api_id = + FWK_ID_API_INIT( + FWK_MODULE_IDX_RCAR_PD_SYSC, + 0), + .allowed_state_mask_table = + toplevel_allowed_state_mask_table, + .allowed_state_mask_table_size = + FWK_ARRAY_SIZE( + toplevel_allowed_state_mask_table) }), + }, + [CONFIG_POWER_DOMAIN_CHILD_3DGA] = + { + .name = "3dg-a", + .data = &( + (struct mod_power_domain_element_config){ + .attributes.pd_type = + MOD_PD_TYPE_DEVICE, + .tree_pos = MOD_PD_TREE_POS( + MOD_PD_LEVEL_2, + 0, + 0, + CONFIG_POWER_DOMAIN_CHILD_3DGA, + 0), + .driver_id = + FWK_ID_ELEMENT_INIT( + FWK_MODULE_IDX_RCAR_PD_SYSC, + RCAR_PD_SYSC_ELEMENT_IDX_3DGA), + .api_id = + FWK_ID_API_INIT( + FWK_MODULE_IDX_RCAR_PD_SYSC, + 0), + .allowed_state_mask_table = + toplevel_allowed_state_mask_table, + .allowed_state_mask_table_size = + FWK_ARRAY_SIZE( + toplevel_allowed_state_mask_table) }), + }, + [CONFIG_POWER_DOMAIN_CHILD_A2VC1] = + { + .name = "a2vc1", + .data = &((struct mod_power_domain_element_config){ .attributes + .pd_type = MOD_PD_TYPE_DEVICE, + .tree_pos = MOD_PD_TREE_POS( + MOD_PD_LEVEL_2, + 0, + 0, + CONFIG_POWER_DOMAIN_CHILD_A2VC1, + 0), + .driver_id = FWK_ID_ELEMENT_INIT( + FWK_MODULE_IDX_RCAR_PD_SYSC, + RCAR_PD_SYSC_ELEMENT_IDX_A2VC1), + .api_id = FWK_ID_API_INIT( + FWK_MODULE_IDX_RCAR_PD_SYSC, + 0), + .allowed_state_mask_table = + toplevel_allowed_state_mask_table, + .allowed_state_mask_table_size = + FWK_ARRAY_SIZE( + toplevel_allowed_state_mask_table) }), + }, + [CONFIG_POWER_DOMAIN_CHILD_A3VC] = + { + .name = "a3vc", + .data = &((struct mod_power_domain_element_config){ .attributes + .pd_type = MOD_PD_TYPE_DEVICE, + .tree_pos = MOD_PD_TREE_POS( + MOD_PD_LEVEL_2, + 0, + 0, + CONFIG_POWER_DOMAIN_CHILD_A3VC, + 0), + .driver_id = + FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_RCAR_PD_SYSC, RCAR_PD_SYSC_ELEMENT_IDX_A3VC), + .api_id = FWK_ID_API_INIT( + FWK_MODULE_IDX_RCAR_PD_SYSC, + 0), + .allowed_state_mask_table = + toplevel_allowed_state_mask_table, + .allowed_state_mask_table_size = + FWK_ARRAY_SIZE( + toplevel_allowed_state_mask_table) }), + }, + [CONFIG_POWER_DOMAIN_CHILD_CR7] = + { + .name = "cr7", + .data = &((struct mod_power_domain_element_config){ .attributes + .pd_type = MOD_PD_TYPE_DEVICE, + .tree_pos = MOD_PD_TREE_POS( + MOD_PD_LEVEL_2, + 0, + 0, + CONFIG_POWER_DOMAIN_CHILD_CR7, + 0), + .driver_id = FWK_ID_ELEMENT_INIT( + FWK_MODULE_IDX_RCAR_PD_SYSC, + RCAR_PD_SYSC_ELEMENT_IDX_CR7), + .api_id = FWK_ID_API_INIT( + FWK_MODULE_IDX_RCAR_PD_SYSC, + 0), + .allowed_state_mask_table = + toplevel_allowed_state_mask_table, + .allowed_state_mask_table_size = + FWK_ARRAY_SIZE( + toplevel_allowed_state_mask_table) }), + }, + [CONFIG_POWER_DOMAIN_CHILD_A3VP] = + { + .name = "a3vp", + .data = &((struct mod_power_domain_element_config){ .attributes + .pd_type = MOD_PD_TYPE_DEVICE, + .tree_pos = MOD_PD_TREE_POS( + MOD_PD_LEVEL_2, + 0, + 0, + CONFIG_POWER_DOMAIN_CHILD_A3VP, + 0), + .driver_id = FWK_ID_ELEMENT_INIT( + FWK_MODULE_IDX_RCAR_PD_SYSC, + RCAR_PD_SYSC_ELEMENT_IDX_A3VP), + .api_id = FWK_ID_API_INIT( + FWK_MODULE_IDX_RCAR_PD_SYSC, + 0), + .allowed_state_mask_table = + toplevel_allowed_state_mask_table, + .allowed_state_mask_table_size = + FWK_ARRAY_SIZE( + toplevel_allowed_state_mask_table) }), + }, + [CONFIG_POWER_DOMAIN_CHILD_COUNT] = + { + .name = "SYSTOP", + .data = &((struct mod_power_domain_element_config){ .attributes + .pd_type = MOD_PD_TYPE_SYSTEM, + .tree_pos = MOD_PD_TREE_POS( + MOD_PD_LEVEL_2, + 0, + 0, + 0, + 0), + .driver_id = + FWK_ID_MODULE_INIT( + FWK_MODULE_IDX_SYSTEM_POWER), + .api_id = FWK_ID_API_INIT( + FWK_MODULE_IDX_SYSTEM_POWER, + MOD_SYSTEM_POWER_API_IDX_PD_DRIVER), + .allowed_state_mask_table = + systop_allowed_state_mask_table, + .allowed_state_mask_table_size = FWK_ARRAY_SIZE( + systop_allowed_state_mask_table) }), + }, + }; + +/* + * Function definitions with internal linkage + */ +static const struct fwk_element *rcar_power_domain_get_element_table( + fwk_id_t module_id) +{ + struct fwk_element *element_table, *element; + struct mod_power_domain_element_config *pd_config_table, *pd_config; + unsigned int core_idx; + + element_table = fwk_mm_calloc( + rcar_core_get_count() + + FWK_ARRAY_SIZE(rcar_power_domain_static_element_table) + + 1, /* Terminator */ + sizeof(struct fwk_element)); + if (element_table == NULL) + return NULL; + + pd_config_table = fwk_mm_calloc( + rcar_core_get_count(), sizeof(struct mod_power_domain_element_config)); + if (pd_config_table == NULL) + return NULL; + + for (core_idx = 0; core_idx < rcar_core_get_count(); core_idx++) { + element = &element_table[core_idx]; + pd_config = &pd_config_table[core_idx]; + + element->name = core_pd_name_table[core_idx]; + element->data = pd_config; + + pd_config->attributes.pd_type = MOD_PD_TYPE_CORE, + pd_config->tree_pos = MOD_PD_TREE_POS( + MOD_PD_LEVEL_2, 0, 0, CONFIG_POWER_DOMAIN_CHILD_CLUSTER0, core_idx), + pd_config->driver_id = + FWK_ID_ELEMENT(FWK_MODULE_IDX_RCAR_PD_CORE, core_idx), + pd_config->api_id = FWK_ID_API( + FWK_MODULE_IDX_RCAR_PD_CORE, + MOD_RCAR_PD_SYSC_API_IDX_POWER_DOMAIN_DRIVER), + pd_config->allowed_state_mask_table = core_pd_allowed_state_mask_table, + pd_config->allowed_state_mask_table_size = + FWK_ARRAY_SIZE(core_pd_allowed_state_mask_table); + } + + memcpy( + element_table + rcar_core_get_count(), + rcar_power_domain_static_element_table, + sizeof(rcar_power_domain_static_element_table)); + + return element_table; +} + +/* + * Power module configuration data + */ +struct fwk_module_config config_rcar_power_domain = { + .elements = + FWK_MODULE_DYNAMIC_ELEMENTS(rcar_power_domain_get_element_table), + .data = &rcar_power_domain_config, +}; diff --git a/product/rcar/scp_ramfw/config_rcar_power_domain.h b/product/rcar/scp_ramfw/config_rcar_power_domain.h new file mode 100644 index 00000000..39987ca8 --- /dev/null +++ b/product/rcar/scp_ramfw/config_rcar_power_domain.h @@ -0,0 +1,38 @@ +/* + * Renesas SCP/MCP Software + * Copyright (c) 2020, Renesas Electronics Corporation. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef CONFIG_RCAR_POWER_DOMAIN_H +#define CONFIG_RCAR_POWER_DOMAIN_H + +#include <rcar_scmi_id.h> + +#define CONFIG_POWER_DOMAIN_CORE_CLUS0CORE0 PD_RCAR_CLUS0CORE0 +#define CONFIG_POWER_DOMAIN_CORE_CLUS0CORE1 PD_RCAR_CLUS0CORE1 +#define CONFIG_POWER_DOMAIN_CORE_CLUS0CORE2 PD_RCAR_CLUS0CORE2 +#define CONFIG_POWER_DOMAIN_CORE_CLUS0CORE3 PD_RCAR_CLUS0CORE3 +#define CONFIG_POWER_DOMAIN_CORE_CLUS1CORE0 PD_RCAR_CLUS1CORE0 +#define CONFIG_POWER_DOMAIN_CORE_CLUS1CORE1 PD_RCAR_CLUS1CORE1 +#define CONFIG_POWER_DOMAIN_CORE_CLUS1CORE2 PD_RCAR_CLUS1CORE2 +#define CONFIG_POWER_DOMAIN_CORE_CLUS1CORE3 PD_RCAR_CLUS1CORE3 + +enum rcar_powerdomain_child_index { + CONFIG_POWER_DOMAIN_CHILD_CLUSTER0, + CONFIG_POWER_DOMAIN_CHILD_CLUSTER1, + CONFIG_POWER_DOMAIN_CHILD_A3IR, + CONFIG_POWER_DOMAIN_CHILD_3DGE, + CONFIG_POWER_DOMAIN_CHILD_3DGD, + CONFIG_POWER_DOMAIN_CHILD_3DGC, + CONFIG_POWER_DOMAIN_CHILD_3DGB, + CONFIG_POWER_DOMAIN_CHILD_3DGA, + CONFIG_POWER_DOMAIN_CHILD_A2VC1, + CONFIG_POWER_DOMAIN_CHILD_A3VC, + CONFIG_POWER_DOMAIN_CHILD_CR7, + CONFIG_POWER_DOMAIN_CHILD_A3VP, + CONFIG_POWER_DOMAIN_CHILD_COUNT +}; + +#endif /* CONFIG_RCAR_POWER_DOMAIN_H */ |