diff options
author | Ronald Cron <ronald.cron@arm.com> | 2018-06-05 09:31:39 +0200 |
---|---|---|
committer | Ronald Cron <ronald.cron@arm.com> | 2018-06-08 11:46:47 +0200 |
commit | b151958dbb2f37383f4d9a1f7802c36008d9fef2 (patch) | |
tree | fe20ebfb8c10facbfd028edefe601462ae3ee64c | |
parent | fd3027b6fd17a4a33a685adb73f2acfcae9a2ced (diff) |
Add support for SGM-775
Co-authored-by: Filipe Rinaldi <filipe.rinaldi@arm.com>
Co-authored-by: Paul Beesley <paul.beesley@arm.com>
Co-authored-by: Chris Kay <chris.kay@arm.com>
Co-authored-by: Elieva Pignat <elieva.pignat@arm.com>
Co-authored-by: Pedro Custodio <pedro.krewinkelcustodio@arm.com>
Change-Id: Ic7524ad58a7c15d5b055e88a9719b2feee437f1d
Signed-off-by: Ronald Cron <ronald.cron@arm.com>
197 files changed, 29185 insertions, 0 deletions
diff --git a/module/bootloader/include/mod_bootloader.h b/module/bootloader/include/mod_bootloader.h new file mode 100644 index 00000000..47d22575 --- /dev/null +++ b/module/bootloader/include/mod_bootloader.h @@ -0,0 +1,100 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef MOD_BOOTLOADER_H +#define MOD_BOOTLOADER_H + +#include <stdint.h> +#include <fwk_element.h> + +/*! + * \addtogroup GroupModules Modules + * @{ + */ + +/*! + * \defgroup GroupBoot Bootloader + * + * \details A service module providing support for loading firmware images. + * + * @{ + */ + +/*! + * \brief Module configuration. + */ +struct mod_bootloader_config { + /*! + * Base address of the memory region that the image will be copied from. + * Note that this is not the base address of the image to be loaded. This + * base address must be combined with an offset value, provided by + * application processor firmware via a Shared Data Storage region. + */ + uintptr_t source_base; + + /*! + * The number of bytes of storage available at the source location. + * This value implicitly limits the maximum size of the image that can be + * copied. + */ + size_t source_size; + + /*! Base address of the location that the image will be copied to. */ + uintptr_t destination_base; + + /*! + * The number of bytes of storage available at the destination location. + * This value implicitly limits the maximum size of the image that can be + * copied. + */ + size_t destination_size; + + /*! + * Identifier of the SDS structure containing image metadata, such as the + * size of the image and its offset from source_base. + */ + uint32_t sds_struct_id; +}; + +/*! + * \brief Bootloader interface. + */ +struct mod_bootloader_api { + /*! + * \brief Copy a RAM Firmware image from a source location to a destination + * (which is expected to be the SCP SRAM). + * + * \param config Pointer to an scp_bootloader_config structure containing + * settings that control where the image is copied from and to. + * + * \retval FWK_SUCCESS The RAM Firmware image was copied successfully. + * \retval FWK_E_ALIGN The given image offset is not properly aligned. + * \retval FWK_E_DATA The image did not pass checksum validation. + * \retval FWK_E_PARAM The config structure pointer is invalid. + * \retval FWK_E_PARAM One or more config structure fields are invalid. + * \retval FWK_E_SIZE The given image size is below the minimum possible + * size. + * \retval FWK_E_SIZE The image is too large for the destination memory + * area. + * \retval FWK_E_SIZE The given image offset is beyond the source memory + * area. + * \retval FWK_E_SIZE The header's size field does not match the given size. + * \retval FWK_E_SIZE Part of the image lies outside of the source memory + * area. + */ + int (*load_image)(void); +}; + +/*! + * @} + */ + +/*! + * @} + */ + +#endif /* MOD_BOOTLOADER_H */ diff --git a/module/bootloader/src/Makefile b/module/bootloader/src/Makefile new file mode 100644 index 00000000..effaca48 --- /dev/null +++ b/module/bootloader/src/Makefile @@ -0,0 +1,11 @@ +# +# Arm SCP/MCP Software +# Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +BS_LIB_NAME := Bootloader +BS_LIB_SOURCES := mod_bootloader.c + +include $(BS_DIR)/lib.mk diff --git a/module/bootloader/src/mod_bootloader.c b/module/bootloader/src/mod_bootloader.c new file mode 100644 index 00000000..f2d34113 --- /dev/null +++ b/module/bootloader/src/mod_bootloader.c @@ -0,0 +1,162 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <stdint.h> +#include <string.h> +#include <fwk_element.h> +#include <fwk_errno.h> +#include <fwk_id.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <mod_bootloader.h> +#include <mod_sds.h> + +/* Offset within the SDS structure where the valid flag is located. */ +#define BOOTLOADER_STRUCT_VALID_POS 0 +/* Offset within the SDS structure where the image offset is located. */ +#define BOOTLOADER_STRUCT_IMAGE_OFFSET_POS 4 +/* Offset within the SDS structure where the image size is located. */ +#define BOOTLOADER_STRUCT_IMAGE_SIZE_POS 8 + +#define IMAGE_FLAGS_VALID_MASK 0x1 + +/* Module context */ +struct bootloader_ctx { + const struct mod_bootloader_config *module_config; + const struct mod_sds_api *sds_api; +}; + +static struct bootloader_ctx module_ctx; + +/* + * Module API + */ + +static int load_image(void) +{ + int status; + void *image_base; + uint32_t image_flags; + uint32_t image_offset; + uint32_t image_size; + + if (module_ctx.module_config->source_base == 0) + return FWK_E_PARAM; + if (module_ctx.module_config->destination_base == 0) + return FWK_E_PARAM; + if (module_ctx.module_config->source_size == 0) + return FWK_E_PARAM; + if (module_ctx.module_config->destination_size == 0) + return FWK_E_PARAM; + if (module_ctx.module_config->sds_struct_id == 0) + return FWK_E_PARAM; + + /* + * Wait until Trusted Firmware writes the image metadata and sets the + * data valid flag. + */ + while (true) { + status = module_ctx.sds_api->struct_read( + module_ctx.module_config->sds_struct_id, + BOOTLOADER_STRUCT_VALID_POS, &image_flags, sizeof(image_flags)); + + if (status != FWK_SUCCESS) + return status; + if (image_flags & IMAGE_FLAGS_VALID_MASK) + break; + } + + /* The image metadata from Trusted Firmware can now be read and validated */ + status = module_ctx.sds_api->struct_read( + module_ctx.module_config->sds_struct_id, + BOOTLOADER_STRUCT_IMAGE_OFFSET_POS, &image_offset, + sizeof(image_offset)); + + if (status != FWK_SUCCESS) + return status; + status = module_ctx.sds_api->struct_read( + module_ctx.module_config->sds_struct_id, + BOOTLOADER_STRUCT_IMAGE_SIZE_POS, &image_size, sizeof(image_size)); + + if (status != FWK_SUCCESS) + return status; + + if (image_size == 0) + return FWK_E_SIZE; + if ((image_offset % 4) != 0) + return FWK_E_ALIGN; + if (image_offset > module_ctx.module_config->source_size) + return FWK_E_SIZE; + + /* Read the image header now that its base address is known */ + image_base = (void *)((uint8_t *)module_ctx.module_config->source_base + + image_offset); + + memcpy((void *)module_ctx.module_config->destination_base, image_base, + image_size); + + return FWK_SUCCESS; +} + +static const struct mod_bootloader_api bootloader_api = { + .load_image = load_image, +}; + +/* + * Framework handlers + */ + +static int bootloader_init(fwk_id_t module_id, unsigned int element_count, + const void *data) +{ + /* Store a pointer to the module config within the module context */ + module_ctx.module_config = data; + + return FWK_SUCCESS; +} + +static int bootloader_bind(fwk_id_t id, unsigned int call_number) +{ + int status; + + /* Only the first round of binding is used (round number is zero-indexed) */ + if (call_number == 1) + return FWK_SUCCESS; + + if (fwk_module_is_valid_element_id(id)) + /* No element-level binding required */ + return FWK_SUCCESS; + + status = fwk_module_bind(FWK_ID_MODULE(FWK_MODULE_IDX_SDS), + FWK_ID_API(FWK_MODULE_IDX_SDS, 0), + &module_ctx.sds_api); + + return status; +} + +static int bootloader_process_bind_request(fwk_id_t requester_id, fwk_id_t id, + fwk_id_t api_id, const void **api) +{ + if (api == NULL) + return FWK_E_PARAM; + + if (!fwk_module_is_valid_module_id(id)) + return FWK_E_PARAM; + + *api = &bootloader_api; + return FWK_SUCCESS; +} + +const struct fwk_module module_bootloader = { + .name = "Bootloader", + .type = FWK_MODULE_TYPE_SERVICE, + .api_count = 1, + .event_count = 0, + .init = bootloader_init, + .bind = bootloader_bind, + .process_bind_request = bootloader_process_bind_request, +}; diff --git a/module/clock/include/mod_clock.h b/module/clock/include/mod_clock.h new file mode 100644 index 00000000..1e3e5dc9 --- /dev/null +++ b/module/clock/include/mod_clock.h @@ -0,0 +1,477 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef MOD_CLOCK_H +#define MOD_CLOCK_H + +#include <stdint.h> +#include <fwk_element.h> +#include <fwk_module_idx.h> + +/*! + * \addtogroup GroupModules Modules + * @{ + */ + +/*! + * \defgroup GroupClock Clock HAL + * + * \details A Hardware Abstraction Library for configuring clock devices. + * + * @{ + */ + +/*! + * \brief Clock states. + */ +enum mod_clock_state { + /*! The clock is stopped */ + MOD_CLOCK_STATE_STOPPED, + + /*! The clock is running */ + MOD_CLOCK_STATE_RUNNING, + + /*! Number of defined clock states */ + MOD_CLOCK_STATE_COUNT +}; + +/*! + * \brief Clock notification indices. + */ +enum mod_clock_notification_idx { + /*! The running state of a clock changed */ + MOD_CLOCK_NOTIFICATION_IDX_STATE_CHANGED, + + /*! The running state of a clock is about to change */ + MOD_CLOCK_NOTIFICATION_IDX_STATE_CHANGE_PENDING, + + /*! Number of defined notifications */ + MOD_CLOCK_NOTIFICATION_IDX_COUNT +}; + +#if BUILD_HAS_MOD_CLOCK +/*! + * \brief Identifier for the \ref MOD_CLOCK_NOTIFICATION_IDX_STATE_CHANGED + * notification. + */ +static const fwk_id_t mod_clock_notification_id_state_changed = + FWK_ID_NOTIFICATION_INIT( + FWK_MODULE_IDX_CLOCK, + MOD_CLOCK_NOTIFICATION_IDX_STATE_CHANGED); + +/*! + * \brief Identifier for the \ref + * MOD_CLOCK_NOTIFICATION_IDX_STATE_CHANGE_PENDING notification + */ +static const fwk_id_t mod_clock_notification_id_state_change_pending = + FWK_ID_NOTIFICATION_INIT( + FWK_MODULE_IDX_CLOCK, + MOD_CLOCK_NOTIFICATION_IDX_STATE_CHANGE_PENDING); +#endif + +/*! + * \brief Event parameters shared by the + * \ref MOD_CLOCK_NOTIFICATION_IDX_STATE_CHANGED and + * \ref MOD_CLOCK_NOTIFICATION_IDX_STATE_CHANGE_PENDING notifications. + */ +struct clock_notification_params { + /*! + * The state that the clock has transitioned to, or is about + * to transition to. + */ + enum mod_clock_state new_state; +}; + +/*! + * \brief Response parameters for the + * \ref MOD_CLOCK_NOTIFICATION_IDX_STATE_CHANGE_PENDING notification. + */ +struct clock_state_change_pending_resp_params { + /*! + * The status returned by the notified subscriber on processing the + * \ref MOD_CLOCK_NOTIFICATION_IDX_STATE_CHANGE_PENDING notification. + */ + int status; +}; + +/*! + * \brief APIs that the module makes available to entities requesting binding. + */ +enum mod_clock_api_type { + /*! Clock HAL */ + MOD_CLOCK_API_TYPE_HAL, + MOD_CLOCK_API_COUNT, +}; + +/*! + * \brief Clock rate types. + */ +enum mod_clock_rate_type { + /*! The clock has a discrete set of rates that it can attain */ + MOD_CLOCK_RATE_TYPE_DISCRETE, + + /*! The clock has a continuous range of rates with a constant step */ + MOD_CLOCK_RATE_TYPE_CONTINUOUS, +}; + +/*! + * \brief Clock rounding modes. + */ +enum mod_clock_round_mode { + /*! + * Do not perform any rounding. Any rate that is not precise and attainable + * will be rejected. + */ + MOD_CLOCK_ROUND_MODE_NONE, + + /*! Round to the closest attainable rate, whether higher or lower */ + MOD_CLOCK_ROUND_MODE_NEAREST, + + /*! Round to the closest attainable higher rate */ + MOD_CLOCK_ROUND_MODE_UP, + + /*! Round to the closest attainable lower rate */ + MOD_CLOCK_ROUND_MODE_DOWN, +}; + +/*! + * \brief Clock module configuration data. + */ +struct mod_clock_config { + /*! + * \brief Identifier of a notification to subscribe clock devices to in + * order to receive notifications of power domain transitions that have + * already occurred. + * + * \note May be \ref FWK_ID_NONE to disable this functionality for all + * elements. + */ + const fwk_id_t pd_transition_notification_id; + + /*! + * \brief Identifier of a notification to subscribe clock devices to in + * order to receive notifications of power domain transitions that are + * about to occur. + * + * \note May be \ref FWK_ID_NONE to disable this functionality for all + * elements. + */ + const fwk_id_t pd_pre_transition_notification_id; +}; + +/*! + * \brief Clock element configuration data. + */ +struct mod_clock_dev_config { + /*! Reference to the device element within the associated driver module */ + const fwk_id_t driver_id; + + /*! Reference to the API provided by the device driver module */ + const fwk_id_t api_id; + + /*! + * \brief Reference to the element or module that is the source of the + * power domain notification. + * + * \details If the clock belongs to the always-on (AON) power domain (i.e. + * it is always running), or if there are no actions to be performed + * when the clock's power domain changes state, then this identifier + * must be FWK_ID_NONE. In this case the clock will not be registered + * to receive notifications from the power domain module. + */ + fwk_id_t pd_source_id; +}; + +/*! + * \brief Range of supported rates for a clock. + */ +struct mod_clock_range { + /*! The type of rates the clock provides (discrete or continuous) */ + enum mod_clock_rate_type rate_type; + + /*! Minimum rate (in Hz) */ + uint64_t min; + + /*! Maximum rate (in Hz) */ + uint64_t max; + + /*! + * The number of Hertz by which the rate can be incremented at each step + * throughout the clock's range. Valid only when rate_type is equal to + * \ref mod_clock_rate_type.MOD_CLOCK_RATE_TYPE_CONTINUOUS, as clocks that + * use \ref mod_clock_rate_type.MOD_CLOCK_RATE_TYPE_DISCRETE may not have a + * regular step between their rates. + */ + uint64_t step; + + /*! The number of unique rates that the clock can attain */ + uint64_t rate_count; +}; + +/*! + * \brief Clock properties exposed via the get_info() API function. + * + * \details This structure is intended to store clock information that is static + * and which does not change during runtime. Dynamic information, such as + * the current clock state, are exposed through functions in the clock and + * clock driver APIs. + */ +struct mod_clock_info { + /*! Human-friendly clock name */ + const char *name; + + /*! Range of supported clock rates */ + struct mod_clock_range range; + + /*! Number of discrete rates supported */ + uint64_t rate_count; +}; + +/*! + * \brief Clock driver interface. + */ +struct mod_clock_drv_api { + /*! Name of the driver */ + const char *name; + + /*! + * \brief Set a new clock rate by providing a frequency in Hertz (Hz). + * + * \param clock_id Clock device identifier. + * + * \param rate The desired frequency in Hertz. + * + * \param round_mode The type of rounding to perform, if required, to + * achieve the given rate. + * + * \retval FWK_SUCCESS The operation succeeded. + * \return One of the standard framework error codes. + */ + int (*set_rate)(fwk_id_t clock_id, uint64_t rate, + enum mod_clock_round_mode round_mode); + + /*! + * \brief Get the current rate of a clock in Hertz (Hz). + * + * \param clock_id Clock device identifier. + * + * \param rate Pointer to storage for the current clock rate in Hertz. + * + * \retval FWK_SUCCESS The operation succeeded. + * \return One of the standard framework error codes. + */ + int (*get_rate)(fwk_id_t clock_id, uint64_t *rate); + + /*! + * \brief Get a clock rate in Hertz from an index into the clock's range. + * + * \param clock_id Clock device identifier. + * + * \param rate_index The index into the clock's range to get the rate of. + * + * \param rate Pointer to storage for the rate, in Hertz, + * corresponding to the index. + * + * \retval FWK_SUCCESS The operation succeeded. + * \return One of the standard framework error codes. + */ + int (*get_rate_from_index)(fwk_id_t clock_id, unsigned int rate_index, + uint64_t *rate); + + /*! + * \brief Set the running state of a clock. + * + * \param clock_id Clock device identifier. + * + * \param state One of the valid clock states. + * + * \retval FWK_SUCCESS The operation succeeded. + * \return One of the standard framework error codes. + */ + int (*set_state)(fwk_id_t clock_id, enum mod_clock_state state); + + /*! + * \brief Get the running state of a clock. + * + * \param clock_id Clock device identifier. + * + * \param state Pointer to storage for the current clock state. + * + * \retval FWK_SUCCESS The operation succeeded. + * \return One of the standard framework error codes. + */ + int (*get_state)(fwk_id_t clock_id, enum mod_clock_state *state); + + /*! + * \brief Get the range of rates that the clock supports. + * + * \param clock_id Clock device identifier. + * + * \param state Pointer to storage for the clock range structure. + * + * \retval FWK_SUCCESS The operation succeeded. + * \return One of the standard framework error codes. + */ + int (*get_range)(fwk_id_t clock_id, struct mod_clock_range *range); + + /*! + * \brief Handle the condition where the state of a clock's power domain is + * about to change. + * + * \details This function will be called prior to the change in power + * state occurring so that the clock driver implementing this API is + * able to perform any required preparatory work beforehand. + * + * \note This function is optional. If the driver does not control any + * clocks that require power state awareness then the pointer may be set + * to NULL. + * + * \param clock_id Clock device identifier. + * + * \param current_state The current power state that the clock's power + * domain will transition away from. + * + * \param new_state The power state that the clock's power domain will + * transition to. + * + * \retval FWK_SUCCESS The operation succeeded. + * \return One of the standard framework error codes. + */ + int (*process_pending_power_transition)( + fwk_id_t clock_id, + unsigned int current_state, + unsigned int new_state); + + /*! + * \brief Handle the condition where the state of a clock's power domain + * has changed. + * + * \details This function will be called after the change in power state + * has occurred. The driver can take any appropriate actions that are + * required to accommodate the new state. The transition can be to a + * deeper power state (e.g. ON->OFF) or to a shallower power state + * (e.g. OFF->ON). + * + * \note This function is optional. If the driver does not control any + * clocks that require power state awareness then the pointer may be set + * to NULL. + * + * \param clock_id Clock device identifier. + * + * \param state The power state that the clock's power domain transitioned + * to. + * + * \retval FWK_SUCCESS The operation succeeded. + * \return One of the standard framework error codes. + */ + int (*process_power_transition)(fwk_id_t clock_id, unsigned int state); +}; + +/*! + * \brief Clock interface. + */ +struct mod_clock_api { + /*! + * \brief Set a new clock rate by providing a frequency in Hertz (Hz). + * + * \param clock_id Clock device identifier. + * + * \param rate The desired frequency in Hertz. + * + * \param round_mode The type of rounding to perform, if required, to + * achieve the given rate. + * + * \retval FWK_SUCCESS The operation succeeded. + * \retval FWK_E_PARAM The clock identifier was invalid. + * \return One of the standard framework error codes. + */ + int (*set_rate)(fwk_id_t clock_id, uint64_t rate, + enum mod_clock_round_mode round_mode); + + /*! + * \brief Get the current rate of a clock in Hertz (Hz). + * + * \param clock_id Clock device identifier. + * + * \param rate Pointer to storage for the current clock rate in Hertz. + * + * \retval FWK_SUCCESS The operation succeeded. + * \retval FWK_E_PARAM The clock identifier was invalid. + * \retval FWK_E_PARAM The rate pointer was NULL. + * \return One of the standard framework error codes. + */ + int (*get_rate)(fwk_id_t clock_id, uint64_t *rate); + + /*! + * \brief Get a clock rate in Hertz from an index into the clock's range. + * + * \param clock_id Clock device identifier. + * + * \param rate_index The index into the clock's range to get the rate of. + * + * \param rate Pointer to storage for the rate, in Hertz, + * corresponding to the index. + * + * \retval FWK_SUCCESS The operation succeeded. + * \retval FWK_E_PARAM The clock identifier was invalid. + * \retval FWK_E_PARAM The rate pointer was NULL. + * \return One of the standard framework error codes. + */ + int (*get_rate_from_index)(fwk_id_t clock_id, unsigned int rate_index, + uint64_t *rate); + + /*! + * \brief Set the running state of a clock. + * + * \param clock_id Clock device identifier. + * + * \param state One of the valid clock states. + * + * \retval FWK_SUCCESS The operation succeeded. + * \retval FWK_E_PARAM The clock identifier was invalid. + * \return One of the standard framework error codes. + */ + int (*set_state)(fwk_id_t clock_id, enum mod_clock_state state); + + /*! + * \brief Get the running state of a clock. + * + * \param clock_id Clock device identifier. + * + * \param state Pointer to storage for the current clock state. + * + * \retval FWK_SUCCESS The operation succeeded. + * \retval FWK_E_PARAM The clock identifier was invalid. + * \retval FWK_E_PARAM The state pointer was NULL. + * \return One of the standard framework error codes. + */ + int (*get_state)(fwk_id_t clock_id, enum mod_clock_state *state); + + /*! + * \brief Get information about a clock's fixed properties. + * + * \param clock_id Clock device identifier. + * + * \param info Pointer to storage for the clock device properties. + * + * \retval FWK_SUCCESS The operation succeeded. + * \retval FWK_E_PARAM The clock identifier was invalid. + * \retval FWK_E_PARAM The info pointer was NULL. + * \return One of the standard framework error codes. + */ + int (*get_info)(fwk_id_t clock_id, struct mod_clock_info *info); +}; + +/*! + * @} + */ + +/*! + * @} + */ + +#endif /* MOD_CLOCK_H */ diff --git a/module/clock/src/Makefile b/module/clock/src/Makefile new file mode 100644 index 00000000..fd66eff2 --- /dev/null +++ b/module/clock/src/Makefile @@ -0,0 +1,11 @@ +# +# Arm SCP/MCP Software +# Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +BS_LIB_NAME := Clock HAL +BS_LIB_SOURCES := mod_clock.c + +include $(BS_DIR)/lib.mk diff --git a/module/clock/src/mod_clock.c b/module/clock/src/mod_clock.c new file mode 100644 index 00000000..1c05b03b --- /dev/null +++ b/module/clock/src/mod_clock.c @@ -0,0 +1,460 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <assert.h> +#include <stdint.h> +#include <fwk_element.h> +#include <fwk_errno.h> +#include <fwk_id.h> +#include <fwk_mm.h> +#include <fwk_module.h> +#include <fwk_thread.h> +#include <fwk_notification.h> +#include <mod_clock.h> +#include <mod_power_domain.h> + +/* Device context */ +struct clock_dev_ctx { + const struct mod_clock_dev_config *config; + struct mod_clock_drv_api *api; + unsigned int pd_pre_power_transition_notification_cookie; + unsigned int transition_pending_notifications_sent; + unsigned int transition_pending_response_status; +}; + +/* Module context */ +struct clock_ctx { + const struct mod_clock_config *config; + struct clock_dev_ctx *dev_ctx_table; + unsigned int dev_count; +}; + +struct clock_ctx module_ctx; + +/* + * Module API functions + */ + +static int clock_set_rate(fwk_id_t clock_id, uint64_t rate, + enum mod_clock_round_mode round_mode) +{ + int status; + struct clock_dev_ctx *ctx; + + status = fwk_module_check_call(clock_id); + if (status != FWK_SUCCESS) + return status; + + ctx = module_ctx.dev_ctx_table + fwk_id_get_element_idx(clock_id); + + return ctx->api->set_rate(ctx->config->driver_id, rate, round_mode); +} + +static int clock_get_rate(fwk_id_t clock_id, uint64_t *rate) +{ + int status; + struct clock_dev_ctx *ctx; + + status = fwk_module_check_call(clock_id); + if (status != FWK_SUCCESS) + return status; + + if (rate == NULL) + return FWK_E_PARAM; + + ctx = module_ctx.dev_ctx_table + fwk_id_get_element_idx(clock_id); + + return ctx->api->get_rate(ctx->config->driver_id, rate); +} + +static int clock_get_rate_from_index(fwk_id_t clock_id, unsigned int rate_index, + uint64_t *rate) +{ + int status; + struct clock_dev_ctx *ctx; + + status = fwk_module_check_call(clock_id); + if (status != FWK_SUCCESS) + return status; + + if (rate == NULL) + return FWK_E_PARAM; + + ctx = module_ctx.dev_ctx_table + fwk_id_get_element_idx(clock_id); + + return ctx->api->get_rate_from_index(ctx->config->driver_id, rate_index, + rate); +} + +static int clock_set_state(fwk_id_t clock_id, enum mod_clock_state state) +{ + int status; + struct clock_dev_ctx *ctx; + + status = fwk_module_check_call(clock_id); + if (status != FWK_SUCCESS) + return status; + + ctx = module_ctx.dev_ctx_table + fwk_id_get_element_idx(clock_id); + + return ctx->api->set_state(ctx->config->driver_id, state); +} + +static int clock_get_state(fwk_id_t clock_id, enum mod_clock_state *state) +{ + int status; + struct clock_dev_ctx *ctx; + + status = fwk_module_check_call(clock_id); + if (status != FWK_SUCCESS) + return status; + + if (state == NULL) + return FWK_E_PARAM; + + ctx = module_ctx.dev_ctx_table + fwk_id_get_element_idx(clock_id); + + return ctx->api->get_state(ctx->config->driver_id, state); +} + +static int clock_get_info(fwk_id_t clock_id, struct mod_clock_info *info) +{ + int status; + struct clock_dev_ctx *ctx; + + status = fwk_module_check_call(clock_id); + if (status != FWK_SUCCESS) + return status; + + if (info == NULL) + return FWK_E_PARAM; + + ctx = module_ctx.dev_ctx_table + fwk_id_get_element_idx(clock_id); + + status = ctx->api->get_range(ctx->config->driver_id, &info->range); + if (status != FWK_SUCCESS) + return status; + + info->name = fwk_module_get_name(clock_id); + + return FWK_SUCCESS; +} + +static const struct mod_clock_api clock_api = { + .set_rate = clock_set_rate, + .get_rate = clock_get_rate, + .get_rate_from_index = clock_get_rate_from_index, + .set_state = clock_set_state, + .get_state = clock_get_state, + .get_info = clock_get_info, +}; + +/* + * Framework handler functions + */ + +static int clock_init(fwk_id_t module_id, unsigned int element_count, + const void *data) +{ + const struct mod_clock_config *config = data; + + module_ctx.dev_count = element_count; + + if (element_count == 0) + return FWK_SUCCESS; + + if (config == NULL) + return FWK_E_PARAM; + + module_ctx.config = config; + module_ctx.dev_ctx_table = fwk_mm_calloc(element_count, + sizeof(struct clock_dev_ctx)); + if (module_ctx.dev_ctx_table == NULL) + return FWK_E_NOMEM; + + return FWK_SUCCESS; +} + +static int clock_dev_init(fwk_id_t element_id, unsigned int sub_element_count, + const void *data) +{ + struct clock_dev_ctx *ctx; + const struct mod_clock_dev_config *dev_config = data; + + ctx = module_ctx.dev_ctx_table + fwk_id_get_element_idx(element_id); + ctx->config = dev_config; + + return FWK_SUCCESS; +} + +static int clock_bind(fwk_id_t id, unsigned int round) +{ + struct clock_dev_ctx *ctx; + + if (round == 1) + return FWK_SUCCESS; + + if (!fwk_id_is_type(id, FWK_ID_TYPE_ELEMENT)) + /* Only element binding is supported */ + return FWK_SUCCESS; + + ctx = module_ctx.dev_ctx_table + fwk_id_get_element_idx(id); + + return fwk_module_bind(ctx->config->driver_id, + ctx->config->api_id, + &ctx->api); + +} + +static int clock_start(fwk_id_t id) +{ + int status; + struct clock_dev_ctx *ctx; + + /* Nothing to be done at the module level */ + if (!fwk_id_is_type(id, FWK_ID_TYPE_ELEMENT)) + return FWK_SUCCESS; + + ctx = module_ctx.dev_ctx_table + fwk_id_get_element_idx(id); + + if (fwk_id_is_type(ctx->config->pd_source_id, FWK_ID_TYPE_NONE)) + return FWK_SUCCESS; + + if ((ctx->api->process_power_transition != NULL) && + (fwk_id_is_type( + module_ctx.config->pd_transition_notification_id, + FWK_ID_TYPE_NOTIFICATION))) { + status = fwk_notification_subscribe( + module_ctx.config->pd_transition_notification_id, + ctx->config->pd_source_id, + id); + if (status != FWK_SUCCESS) + return status; + } + + if ((ctx->api->process_pending_power_transition != NULL) && + (fwk_id_is_type( + module_ctx.config->pd_pre_transition_notification_id, + FWK_ID_TYPE_NOTIFICATION))) { + status = fwk_notification_subscribe( + module_ctx.config->pd_pre_transition_notification_id, + ctx->config->pd_source_id, + id); + if (status != FWK_SUCCESS) + return status; + } + + return FWK_SUCCESS; +} + +static int clock_process_bind_request(fwk_id_t source_id, fwk_id_t target_id, + fwk_id_t api_id, const void **api) +{ + if (fwk_id_get_api_idx(api_id) != MOD_CLOCK_API_TYPE_HAL) + /* The requested API is not supported. */ + return FWK_E_ACCESS; + + *api = &clock_api; + return FWK_SUCCESS; +} + +static int clock_process_pd_pre_transition_notification( + struct clock_dev_ctx *ctx, + const struct fwk_event *event, + struct fwk_event *resp_event) +{ + int status; + struct mod_pd_power_state_pre_transition_notification_params *pd_params; + struct mod_pd_power_state_pre_transition_notification_resp_params + *pd_resp_params; + struct clock_notification_params *out_params; + struct fwk_event outbound_event = { + .response_requested = true, + .id = mod_clock_notification_id_state_change_pending, + }; + + pd_params = (struct mod_pd_power_state_pre_transition_notification_params *) + event->params; + pd_resp_params = + (struct mod_pd_power_state_pre_transition_notification_resp_params *) + resp_event->params; + + assert(ctx->api->process_pending_power_transition != NULL); + status = ctx->api->process_pending_power_transition( + ctx->config->driver_id, + pd_params->current_state, + pd_params->target_state); + + /* + * The response to the notification should initially be the overall result + * of the downwards propagation of the state change through the driver(s). + */ + pd_resp_params->status = status; + + if (status != FWK_SUCCESS) { + ctx->transition_pending_notifications_sent = 0; + return status; + } + + ctx->transition_pending_response_status = FWK_SUCCESS; + out_params = + (struct clock_notification_params *)outbound_event.params; + + /* + * For now it is sufficient to assume that a PD ON state implies that + * the clock is running and any other state implies that the clock has + * stopped. This will likely need to be revisited so that the clock + * driver can influence the resulting state that is propagated via the + * outgoing notification. + */ + out_params->new_state = (pd_params->target_state == MOD_PD_STATE_ON) + ? MOD_CLOCK_STATE_RUNNING + : MOD_CLOCK_STATE_STOPPED; + + /* Notify subscribers of the pending clock state change */ + status = fwk_notification_notify( + &outbound_event, + &(ctx->transition_pending_notifications_sent)); + + if (ctx->transition_pending_notifications_sent > 0) { + /* There are one or more subscribers that must respond */ + resp_event->is_delayed_response = true; + ctx->pd_pre_power_transition_notification_cookie = event->cookie; + } + + return FWK_SUCCESS; +} + +static int clock_process_pd_transition_notification( + struct clock_dev_ctx *ctx, + const struct fwk_event *event) +{ + int status; + unsigned int transition_notifications_sent; + struct mod_pd_power_state_transition_notification_params *pd_params; + struct clock_notification_params* out_params; + struct fwk_event outbound_event = { + .response_requested = false, + .id = mod_clock_notification_id_state_changed, + }; + + pd_params = + (struct mod_pd_power_state_transition_notification_params *)event + ->params; + + assert(ctx->api->process_power_transition != NULL); + status = ctx->api->process_power_transition( + ctx->config->driver_id, pd_params->state); + + if (status != FWK_SUCCESS) + return status; + + /* Notify subscribers of the clock state change */ + out_params = (struct clock_notification_params *)outbound_event.params; + if (pd_params->state == MOD_PD_STATE_ON) + out_params->new_state = MOD_CLOCK_STATE_RUNNING; + else + out_params->new_state = MOD_CLOCK_STATE_STOPPED; + + status = fwk_notification_notify( + &outbound_event, &(transition_notifications_sent)); + + return FWK_SUCCESS; +} + +static int clock_process_notification_response( + struct clock_dev_ctx *ctx, + const struct fwk_event *event) +{ + struct clock_state_change_pending_resp_params *resp_params; + struct mod_pd_power_state_pre_transition_notification_resp_params + *pd_resp_params; + struct fwk_event pd_response_event = { + .id = module_ctx.config->pd_pre_transition_notification_id, + .target_id = ctx->config->pd_source_id, + .cookie = ctx->pd_pre_power_transition_notification_cookie, + .is_notification = true, + .is_response = true, + .is_delayed_response = true, + }; + + assert(fwk_id_is_equal(event->id, + mod_clock_notification_id_state_change_pending)); + + /* At least one notification response must be outstanding */ + if (ctx->transition_pending_notifications_sent == 0) { + assert(false); + return FWK_E_PANIC; + } + + resp_params = + (struct clock_state_change_pending_resp_params *)event->params; + + /* + * Change the status used in the response to the power domain notification + * if this response has a non-success status code, otherwise leave the + * existing value alone. By default the status is FWK_SUCCESS and changing + * it to an error status code will veto the power domain state transition + * that is pending. + */ + if (resp_params->status != FWK_SUCCESS) + ctx->transition_pending_response_status = resp_params->status; + + if ((--(ctx->transition_pending_notifications_sent)) == 0) { + /* + * If this is the final response then the response to the power domain + * notification can be sent. + */ + pd_resp_params = + (struct mod_pd_power_state_pre_transition_notification_resp_params + *)pd_response_event.params; + pd_resp_params->status = ctx->transition_pending_response_status; + fwk_thread_put_event(&pd_response_event); + } + + return FWK_SUCCESS; +} + +static int clock_process_notification( + const struct fwk_event *event, + struct fwk_event *resp_event) +{ + struct clock_dev_ctx *ctx; + + if (!fwk_id_is_type(event->target_id, FWK_ID_TYPE_ELEMENT)) + /* Only elements should be registered for notifications */ + return FWK_E_PARAM; + + ctx = module_ctx.dev_ctx_table + fwk_id_get_element_idx(event->target_id); + + if (event->is_response) + return clock_process_notification_response(ctx, event); + + if (fwk_id_is_equal( + event->id, module_ctx.config->pd_transition_notification_id)) + return clock_process_pd_transition_notification(ctx, event); + else if (fwk_id_is_equal(event->id, + module_ctx.config->pd_pre_transition_notification_id)) + return clock_process_pd_pre_transition_notification( + ctx, event, resp_event); + else + return FWK_E_HANDLER; +} + +const struct fwk_module module_clock = { + .name = "Clock HAL", + .type = FWK_MODULE_TYPE_HAL, + .api_count = MOD_CLOCK_API_COUNT, + .event_count = 0, + .notification_count = MOD_CLOCK_NOTIFICATION_IDX_COUNT, + .init = clock_init, + .element_init = clock_dev_init, + .bind = clock_bind, + .start = clock_start, + .process_bind_request = clock_process_bind_request, + .process_notification = clock_process_notification, +}; diff --git a/module/css_clock/include/mod_css_clock.h b/module/css_clock/include/mod_css_clock.h new file mode 100644 index 00000000..057362c5 --- /dev/null +++ b/module/css_clock/include/mod_css_clock.h @@ -0,0 +1,192 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef MOD_CSS_CLOCK_H +#define MOD_CSS_CLOCK_H + +#include <stdbool.h> +#include <stdint.h> +#include <fwk_element.h> + +/*! + * \addtogroup GroupModules Modules + * @{ + */ + +/*! + * \defgroup GroupCSSClock CSS Clock Driver + * + * \details A driver for Arm Compute Sub-System clock devices. + * + * @{ + */ + +/*! + * \brief Type of the clock (indexed vs non-indexed). + */ +enum mod_css_clock_type { + /*! A lookup table is used to find the settings for a given rate. */ + MOD_CSS_CLOCK_TYPE_INDEXED, + /*! The clock rate is set without looking up settings in a table. */ + MOD_CSS_CLOCK_TYPE_NON_INDEXED, +}; + +/*! + * \brief APIs that the module makes available to entities requesting binding. + */ +enum mod_css_clock_api_types { + /*! Clock HAL */ + MOD_CSS_CLOCK_API_TYPE_CLOCK, + MOD_CSS_CLOCK_API_COUNT, +}; + +/*! + * \brief Rate lookup table entry. + */ +struct mod_css_clock_rate { + /*! Rate of the clock in Hertz. */ + uint64_t rate; + + /*! Rate of the source PLL in Hertz. */ + uint64_t pll_rate; + + /*! The clock source used to attain the rate. */ + uint8_t clock_source; + + /*! The clock divider to program. */ + uint8_t clock_div_type; + + /*! The clock divider used to attain the rate. */ + uint32_t clock_div; + + /*! The clock modulator numerator setting, if implemented. */ + uint32_t clock_mod_numerator; + + /*! The clock modulator denominator setting, if implemented. */ + uint32_t clock_mod_denominator; +}; + +/*! + * \brief Subsystem clock device configuration. + */ +struct mod_css_clock_dev_config { + /*! The type of the clock (indexed vs non-indexed). */ + enum mod_css_clock_type clock_type; + + /*! Clock source used for non-indexed clocks. */ + uint8_t clock_default_source; + + /*! Clock source used when changing the PLL rate. */ + uint8_t clock_switching_source; + + /*! Element identifier for the associated PLL. */ + fwk_id_t pll_id; + + /*! Reference to the API provided by the PLL driver. */ + fwk_id_t pll_api_id; + + /*! Pointer to the table of clocks that are members of the group. */ + fwk_id_t const *member_table; + + /*! The number of clocks in the member table. */ + uint32_t member_count; + + /*! Reference to the API for the clocks that are members of the group. */ + fwk_id_t member_api_id; + + /*! + * The rate, in Hz, that the member clocks run at before any configuration. + */ + uint64_t initial_rate; + + /*! The clock's support for modulation. */ + bool modulation_supported; + + /*! Pointer to the clock's rate lookup table. */ + struct mod_css_clock_rate const *rate_table; + + /*! The number of rates in the rate lookup table. */ + uint32_t rate_count; +}; + +/*! + * \brief CSS clock control interface. + */ +struct mod_css_clock_direct_api { + /*! Set the clock device's divider */ + int (*set_div)(fwk_id_t device_id, uint32_t divider_type, + uint32_t divider); + + /*! Set the clock device's source (multi-source clocks only) */ + int (*set_source)(fwk_id_t device_id, uint8_t source); + + /*! Set the clock device's modulator (multi-source clocks only) */ + int (*set_mod)(fwk_id_t device_id, uint32_t numerator, + uint32_t denominator); + + /*! + * \brief Handle the condition where the state of a clock's power domain is + * about to change. + * + * \details This function will be called prior to the change in power + * state occurring so that the clock driver implementing this API is + * able to perform any required preparatory work beforehand. + * + * \note This function is optional. If the driver does not control any + * clocks that require power state awareness then the pointer may be set + * to NULL. + * + * \param clock_id Clock device identifier. + * + * \param current_state The current power state that the clock's power + * domain will transition away from. + * + * \param new_state The power state that the clock's power domain will + * transition to. + * + * \retval FWK_SUCCESS The operation succeeded. + * \return One of the standard framework error codes. + */ + int (*process_pending_power_transition)( + fwk_id_t clock_id, + unsigned int current_state, + unsigned int new_state); + + /*! + * \brief Handle the condition where the state of a clock's power domain + * has changed. + * + * \details This function will be called after the change in power state + * has occurred. The driver can take any appropriate actions that are + * required to accommodate the new state. The transition can be to a + * deeper power state (e.g. ON->OFF) or to a shallower power state + * (e.g. OFF->ON). + * + * \note This function is optional. If the driver does not control any + * clocks that require power state awareness then the pointer may be set + * to NULL. + * + * \param clock_id Clock device identifier. + * + * \param state The power state that the clock's power domain transitioned + * to. + * + * \retval FWK_SUCCESS The operation succeeded. + * \return One of the standard framework error codes. + */ + int (*process_power_transition)(fwk_id_t clock_id, unsigned int state); +}; + +/*! + * @} + */ + +/*! + * @} + */ + +#endif /* MOD_CSS_CLOCK_H */ diff --git a/module/css_clock/src/Makefile b/module/css_clock/src/Makefile new file mode 100644 index 00000000..2cc84f99 --- /dev/null +++ b/module/css_clock/src/Makefile @@ -0,0 +1,11 @@ +# +# Arm SCP/MCP Software +# Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +BS_LIB_NAME := CSS Clock Driver +BS_LIB_SOURCES := mod_css_clock.c + +include $(BS_DIR)/lib.mk diff --git a/module/css_clock/src/mod_css_clock.c b/module/css_clock/src/mod_css_clock.c new file mode 100644 index 00000000..c3300628 --- /dev/null +++ b/module/css_clock/src/mod_css_clock.c @@ -0,0 +1,479 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <assert.h> +#include <stdint.h> +#include <stdlib.h> +#include <fwk_element.h> +#include <fwk_errno.h> +#include <fwk_mm.h> +#include <fwk_module.h> +#include <mod_clock.h> +#include <mod_css_clock.h> +#include <mod_power_domain.h> + +/* Device context */ +struct css_clock_dev_ctx { + bool initialized; + uint64_t current_rate; + struct mod_clock_drv_api *pll_api; + struct mod_css_clock_direct_api *clock_api; + const struct mod_css_clock_dev_config *config; +}; + +/* Module context */ +struct css_clock_ctx { + struct css_clock_dev_ctx *dev_ctx_table; + unsigned int dev_count; +}; + +static struct css_clock_ctx module_ctx; + +/* + * Static helper functions + */ + +static int compare_rate_entry(const void *a, const void *b) +{ + struct mod_css_clock_rate *key = (struct mod_css_clock_rate *)a; + struct mod_css_clock_rate *element = (struct mod_css_clock_rate *)b; + + return (key->rate - element->rate); +} + +static int get_rate_entry(struct css_clock_dev_ctx *ctx, uint64_t target_rate, + struct mod_css_clock_rate **entry) +{ + struct mod_css_clock_rate *current_rate_entry; + + if (ctx == NULL) + return FWK_E_PARAM; + if (entry == NULL) + return FWK_E_PARAM; + + /* Perform a binary search to find the entry matching the requested rate */ + current_rate_entry = (struct mod_css_clock_rate *) bsearch(&target_rate, + ctx->config->rate_table, ctx->config->rate_count, + sizeof(struct mod_css_clock_rate), compare_rate_entry); + + if (current_rate_entry == NULL) + return FWK_E_PARAM; + + *entry = current_rate_entry; + return FWK_SUCCESS; +} + +static int set_rate_indexed(struct css_clock_dev_ctx *ctx, uint64_t rate, + enum mod_clock_round_mode round_mode) +{ + int status; + unsigned int i; + struct mod_css_clock_rate *rate_entry; + + if (ctx == NULL) + return FWK_E_PARAM; + + /* Look up the divider and source settings */ + status = get_rate_entry(ctx, rate, &rate_entry); + if (status != FWK_SUCCESS) + goto exit; + + /* Switch each member clock away from the PLL source */ + for (i = 0; i < ctx->config->member_count; i++) { + status = ctx->clock_api->set_source(ctx->config->member_table[i], + ctx->config->clock_switching_source); + if (status != FWK_SUCCESS) + goto exit; + + status = ctx->clock_api->set_div(ctx->config->member_table[i], + rate_entry->clock_div_type, + rate_entry->clock_div); + if (status != FWK_SUCCESS) + goto exit; + + if (ctx->config->modulation_supported) { + status = ctx->clock_api->set_mod(ctx->config->member_table[i], + rate_entry->clock_mod_numerator, + rate_entry->clock_mod_denominator); + if (status != FWK_SUCCESS) + goto exit; + } + } + + /* Change the PLL to the desired rate */ + status = ctx->pll_api->set_rate(ctx->config->pll_id, rate_entry->pll_rate, + MOD_CLOCK_ROUND_MODE_NONE); + if (status != FWK_SUCCESS) + goto exit; + + /* Return each member clock back to the PLL source */ + for (i = 0; i < ctx->config->member_count; i++) { + status = ctx->clock_api->set_source(ctx->config->member_table[i], + rate_entry->clock_source); + if (status != FWK_SUCCESS) + goto exit; + } + +exit: + if (status == FWK_SUCCESS) + ctx->current_rate = rate; + return status; +} + +static int set_rate_non_indexed(struct css_clock_dev_ctx *ctx, uint64_t rate, + enum mod_clock_round_mode round_mode) +{ + int status; + unsigned int i; + + if (ctx == NULL) + return FWK_E_PARAM; + + /* Switch each member clock away from the PLL source */ + for (i = 0; i < ctx->config->member_count; i++) { + status = ctx->clock_api->set_source(ctx->config->member_table[i], + ctx->config->clock_switching_source); + if (status != FWK_SUCCESS) + goto exit; + } + + /* Change the PLL to the desired rate */ + status = ctx->pll_api->set_rate(ctx->config->pll_id, rate, round_mode); + if (status != FWK_SUCCESS) + goto exit; + + /* Return each member clock back to the PLL source */ + for (i = 0; i < ctx->config->member_count; i++) { + status = ctx->clock_api->set_source(ctx->config->member_table[i], + ctx->config->clock_default_source); + if (status != FWK_SUCCESS) + goto exit; + } + +exit: + if (status == FWK_SUCCESS) + ctx->current_rate = rate; + return status; +} + +/* + * Module API functions + */ + +static int css_clock_set_rate(fwk_id_t dev_id, uint64_t rate, + enum mod_clock_round_mode round_mode) +{ + int status; + struct css_clock_dev_ctx *ctx; + + status = fwk_module_check_call(dev_id); + if (status != FWK_SUCCESS) + return status; + + ctx = module_ctx.dev_ctx_table + fwk_id_get_element_idx(dev_id); + + if (ctx->config->clock_type == MOD_CSS_CLOCK_TYPE_INDEXED) + return set_rate_indexed(ctx, rate, round_mode); + else + return set_rate_non_indexed(ctx, rate, round_mode); +} + +static int css_clock_get_rate(fwk_id_t dev_id, uint64_t *rate) +{ + int status; + struct css_clock_dev_ctx *ctx; + + status = fwk_module_check_call(dev_id); + if (status != FWK_SUCCESS) + return status; + + ctx = module_ctx.dev_ctx_table + fwk_id_get_element_idx(dev_id); + *rate = ctx->current_rate; + + return FWK_SUCCESS; +} + +static int css_clock_get_rate_from_index(fwk_id_t dev_id, + unsigned int rate_index, + uint64_t *rate) +{ + int status; + struct css_clock_dev_ctx *ctx; + + status = fwk_module_check_call(dev_id); + if (status != FWK_SUCCESS) + return status; + + if (rate == NULL) + return FWK_E_PARAM; + + ctx = module_ctx.dev_ctx_table + fwk_id_get_element_idx(dev_id); + + if (rate_index >= ctx->config->rate_count) + return FWK_E_PARAM; + + if (ctx->config->clock_type == MOD_CSS_CLOCK_TYPE_INDEXED) { + *rate = ctx->config->rate_table[rate_index].rate; + return FWK_SUCCESS; + } else + return FWK_E_SUPPORT; +} + +static int css_clock_set_state(fwk_id_t dev_id, enum mod_clock_state state) +{ + int status; + + status = fwk_module_check_call(dev_id); + if (status != FWK_SUCCESS) + return status; + + if (state == MOD_CLOCK_STATE_RUNNING) + return FWK_SUCCESS; /* CSS clocks are always running */ + + /* CSS clocks cannot be turned off */ + return FWK_E_SUPPORT; +} + +static int css_clock_get_state(fwk_id_t dev_id, enum mod_clock_state *state) +{ + int status; + + status = fwk_module_check_call(dev_id); + if (status != FWK_SUCCESS) + return status; + + *state = MOD_CLOCK_STATE_RUNNING; + + return FWK_SUCCESS; +} + +static int css_clock_get_range(fwk_id_t dev_id, struct mod_clock_range *range) +{ + int status; + struct css_clock_dev_ctx *ctx; + + status = fwk_module_check_call(dev_id); + if (status != FWK_SUCCESS) + return status; + + if (range == NULL) + return FWK_E_PARAM; + + ctx = module_ctx.dev_ctx_table + fwk_id_get_element_idx(dev_id); + + if (ctx->config->clock_type == MOD_CSS_CLOCK_TYPE_INDEXED) { + range->rate_type = MOD_CLOCK_RATE_TYPE_DISCRETE; + range->min = ctx->config->rate_table[0].rate; + range->max = ctx->config->rate_table[ctx->config->rate_count - 1].rate; + range->rate_count = ctx->config->rate_count; + return FWK_SUCCESS; + } else + return ctx->pll_api->get_range(ctx->config->pll_id, range); +} + +static int css_clock_power_state_change( + fwk_id_t dev_id, + unsigned int next_state) +{ + int status; + unsigned int clock_idx; + struct css_clock_dev_ctx *ctx; + const struct mod_css_clock_dev_config *dev_config; + + status = fwk_module_check_call(dev_id); + if (status != FWK_SUCCESS) + return status; + + ctx = module_ctx.dev_ctx_table + fwk_id_get_element_idx(dev_id); + dev_config = ctx->config; + + /* The group's clock driver is not required to handle this transition */ + if (ctx->clock_api->process_power_transition != NULL) { + for (clock_idx = 0; clock_idx < dev_config->member_count; clock_idx++) { + /* Allow the member clock's driver to perform any required + * processing */ + status = ctx->clock_api->process_power_transition( + dev_config->member_table[clock_idx], next_state); + + if (status != FWK_SUCCESS) + return status; + } + } + + if (next_state == MOD_PD_STATE_ON) { + if (ctx->initialized) { + /* Restore all clocks in the group to the last frequency */ + return css_clock_set_rate(dev_id, ctx->current_rate, + MOD_CLOCK_ROUND_MODE_NONE); + } else { + ctx->initialized = true; + /* Set all clocks in the group to the initial frequency */ + return css_clock_set_rate(dev_id, dev_config->initial_rate, + MOD_CLOCK_ROUND_MODE_NONE); + } + } + + return FWK_SUCCESS; +} + +static int css_clock_pending_power_state_change( + fwk_id_t dev_id, + unsigned int current_state, + unsigned int next_state) +{ + int status; + unsigned int clock_idx; + struct css_clock_dev_ctx *ctx; + const struct mod_css_clock_dev_config *dev_config; + + status = fwk_module_check_call(dev_id); + if (status != FWK_SUCCESS) + return status; + + ctx = module_ctx.dev_ctx_table + fwk_id_get_element_idx(dev_id); + dev_config = ctx->config; + + /* The group's clock driver is not required to handle this transition */ + if (ctx->clock_api->process_pending_power_transition != NULL) { + for (clock_idx = 0; clock_idx < dev_config->member_count; clock_idx++) { + /* Allow the member clock's driver to perform any required + * processing */ + status = ctx->clock_api->process_pending_power_transition( + dev_config->member_table[clock_idx], current_state, next_state); + + if (status != FWK_SUCCESS) + return status; + } + } + + /* Nothing specific to be done in this driver */ + return FWK_SUCCESS; +} + +static const struct mod_clock_drv_api api_clock = { + .set_rate = css_clock_set_rate, + .get_rate = css_clock_get_rate, + .get_rate_from_index = css_clock_get_rate_from_index, + .set_state = css_clock_set_state, + .get_state = css_clock_get_state, + .get_range = css_clock_get_range, + .process_power_transition = css_clock_power_state_change, + .process_pending_power_transition = css_clock_pending_power_state_change, +}; + +/* + * Framework handler functions + */ + +static int css_clock_init(fwk_id_t module_id, unsigned int element_count, + const void *data) +{ + module_ctx.dev_count = element_count; + + if (element_count == 0) + return FWK_SUCCESS; + + module_ctx.dev_ctx_table = fwk_mm_calloc(element_count, + sizeof(struct css_clock_dev_ctx)); + if (module_ctx.dev_ctx_table == NULL) + return FWK_E_NOMEM; + + return FWK_SUCCESS; +} + +static int css_clock_element_init(fwk_id_t element_id, + unsigned int sub_element_count, + const void *data) +{ + unsigned int i = 0; + uint64_t current_rate; + uint64_t last_rate = 0; + struct css_clock_dev_ctx *ctx; + const struct mod_css_clock_dev_config *dev_config = data; + + if (!fwk_module_is_valid_element_id(element_id)) + return FWK_E_PARAM; + + ctx = module_ctx.dev_ctx_table + fwk_id_get_element_idx(element_id); + + if (dev_config->clock_type == MOD_CSS_CLOCK_TYPE_INDEXED) { + /* Verify that the rate entries in the lookup table are ordered */ + while (i < dev_config->rate_count) { + current_rate = dev_config->rate_table[i].rate; + + /* The rate entries must be in ascending order */ + if (current_rate < last_rate) + return FWK_E_DATA; + + last_rate = current_rate; + i++; + } + } + + ctx->config = dev_config; + ctx->current_rate = ctx->config->initial_rate; + + return FWK_SUCCESS; +} + +static int css_clock_bind(fwk_id_t id, unsigned int round) +{ + int status; + struct css_clock_dev_ctx *ctx; + const struct mod_css_clock_dev_config *config; + + if (round == 1) + return FWK_SUCCESS; + + if (fwk_module_is_valid_module_id(id)) + /* No module-level binding required */ + return FWK_SUCCESS; + + ctx = module_ctx.dev_ctx_table + fwk_id_get_element_idx(id); + config = ctx->config; + + /* Ensure that the group has at least one member */ + if (config->member_count == 0) + return FWK_E_DATA; + + /* Bind to the group's common PLL driver */ + status = fwk_module_bind(config->pll_id, config->pll_api_id, + &ctx->pll_api); + if (status != FWK_SUCCESS) + return status; + + /* Bind to the API used to control the clocks in the group */ + status = fwk_module_bind(config->member_table[0], + config->member_api_id, &ctx->clock_api); + if (status != FWK_SUCCESS) + return status; + + return FWK_SUCCESS; +} + +static int css_clock_process_bind_request(fwk_id_t source_id, + fwk_id_t target_id, fwk_id_t api_id, + const void **api) +{ + if (fwk_id_get_api_idx(api_id) != MOD_CSS_CLOCK_API_TYPE_CLOCK) + /* The requested API is not supported. */ + return FWK_E_ACCESS; + + *api = &api_clock; + return FWK_SUCCESS; +} + +const struct fwk_module module_css_clock = { + .name = "Subsystem Clock Driver", + .type = FWK_MODULE_TYPE_DRIVER, + .api_count = MOD_CSS_CLOCK_API_COUNT, + .event_count = 0, + .init = css_clock_init, + .element_init = css_clock_element_init, + .bind = css_clock_bind, + .process_bind_request = css_clock_process_bind_request, +}; diff --git a/module/ddr_phy500/include/mod_ddr_phy500.h b/module/ddr_phy500/include/mod_ddr_phy500.h new file mode 100644 index 00000000..1bf5e545 --- /dev/null +++ b/module/ddr_phy500/include/mod_ddr_phy500.h @@ -0,0 +1,95 @@ + /* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * DDR-PHY500 driver + */ +#ifndef MOD_DDR_PHY500_H +#define MOD_DDR_PHY500_H + +#include <stdint.h> +#include <fwk_id.h> +#include <fwk_macros.h> +#include <fwk_module.h> +#include <mod_log.h> + +/*! + * \addtogroup GroupModules Modules + * @{ + */ + +/*! + * \addtogroup GroupDDR DDR PHY500 Driver + * @{ + */ + +/*! + * \brief DDR PHY500 register definitions. + */ +struct mod_ddr_phy500_reg { + /*! + * \cond + * @{ + */ + FWK_RW uint32_t INIT_COMPLETE; + FWK_RW uint32_t MEMORY_WIDTH; + FWK_RW uint32_t READ_DELAY; + FWK_RW uint32_t CAPTURE_MASK; + FWK_RW uint32_t CAS_LATENCY; + FWK_RW uint32_t T_CTRL_DELAY; + FWK_RW uint32_t T_WRLAT; + FWK_RW uint32_t T_RDDATA_EN; + FWK_RW uint32_t T_RDLAT; + FWK_RW uint32_t RESERVED1; + FWK_R uint32_t RESERVED2; + FWK_R uint32_t LP_REQ; + FWK_RW uint32_t LP_ACK; + FWK_RW uint32_t RDLVL_REQ; + FWK_R uint32_t RDLVL_EN; + FWK_RW uint32_t WRLVL_REQ; + FWK_RW uint32_t WRLVL_EN; + FWK_RW uint32_t MSTR_REQ; + FWK_RW uint32_t MSTR_ACK; + FWK_RW uint32_t DFI_WR_PREMBL; + FWK_R uint8_t RESERVED3[0x820 - 0x50]; + FWK_RW uint32_t DELAY_SEL; + FWK_R uint32_t RESERVED4; + FWK_RW uint32_t T_CTRL_UPD_MIN; + /*! + * \endcond + * @} + */ +}; + +/*! + * \brief Element configuration. + */ +struct mod_ddr_phy500_element_config { + /*! Base address of a device configuration register. */ + uintptr_t ddr; +}; + +/*! + * \brief DDR PHY500 module configuration. + */ +struct mod_ddr_phy500_module_config { + /*! + * Pointer to a structure containing default values for a subset of the PHY's + * configuration registers. These values are common to all PHYs that are + * represented by elements in the module's element table. + */ + const struct mod_ddr_phy500_reg *ddr_reg_val; +}; + +/*! + * @} + */ + +/*! + * @} + */ + +#endif /* MOD_DDR_PHY500_H */ diff --git a/module/ddr_phy500/src/Makefile b/module/ddr_phy500/src/Makefile new file mode 100644 index 00000000..d5f606a7 --- /dev/null +++ b/module/ddr_phy500/src/Makefile @@ -0,0 +1,11 @@ +# +# Arm SCP/MCP Software +# Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +BS_LIB_NAME := DDR PHY500 +BS_LIB_SOURCES += mod_ddr_phy500.c + +include $(BS_DIR)/lib.mk diff --git a/module/ddr_phy500/src/mod_ddr_phy500.c b/module/ddr_phy500/src/mod_ddr_phy500.c new file mode 100644 index 00000000..b3e87fbb --- /dev/null +++ b/module/ddr_phy500/src/mod_ddr_phy500.c @@ -0,0 +1,128 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * DDR-PHY500 driver + */ + +#include <assert.h> +#include <fwk_errno.h> +#include <fwk_id.h> +#include <fwk_mm.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <mod_ddr_phy500.h> +#include <mod_dmc500.h> + +static struct mod_log_api *log_api; + +/* + * Functions fulfilling this module's interface + */ +static int ddr_phy500_config(fwk_id_t element_id) +{ + int status; + struct mod_ddr_phy500_reg *ddr; + const struct mod_ddr_phy500_module_config *module_config; + const struct mod_ddr_phy500_element_config *element_config; + + status = fwk_module_check_call(element_id); + if (status != FWK_SUCCESS) + return status; + + module_config = fwk_module_get_data(fwk_module_id_ddr_phy500); + + element_config = fwk_module_get_data(element_id); + + ddr = (struct mod_ddr_phy500_reg *)element_config->ddr; + + status = log_api->log(MOD_LOG_GROUP_INFO, + "[DDR] Initializing PHY at 0x%x\n", (uintptr_t) ddr); + if (status != FWK_SUCCESS) + return status; + + ddr->T_CTRL_DELAY = module_config->ddr_reg_val->T_CTRL_DELAY; + ddr->READ_DELAY = module_config->ddr_reg_val->READ_DELAY; + ddr->T_CTRL_UPD_MIN = module_config->ddr_reg_val->T_CTRL_UPD_MIN; + ddr->DELAY_SEL = module_config->ddr_reg_val->DELAY_SEL; + + ddr->CAPTURE_MASK = module_config->ddr_reg_val->CAPTURE_MASK; + ddr->T_RDDATA_EN = module_config->ddr_reg_val->T_RDDATA_EN; + ddr->T_RDLAT = module_config->ddr_reg_val->T_RDLAT; + ddr->T_WRLAT = module_config->ddr_reg_val->T_WRLAT; + ddr->DFI_WR_PREMBL = module_config->ddr_reg_val->DFI_WR_PREMBL; + + ddr->LP_ACK = module_config->ddr_reg_val->LP_ACK; + + return FWK_SUCCESS; +} + +static struct mod_dmc500_ddr_phy_api ddr_phy500_api = { + .configure = ddr_phy500_config, +}; + +/* + * Functions fulfilling the framework's module interface + */ + +static int ddr_phy500_init(fwk_id_t module_id, unsigned int element_count, + const void *config) +{ + assert(config != NULL); + + return FWK_SUCCESS; +} + +static int ddr_phy500_element_init(fwk_id_t element_id, unsigned int unused, + const void *data) +{ + assert(data != NULL); + + return FWK_SUCCESS; +} + +static int ddr_phy500_bind(fwk_id_t id, unsigned int round) +{ + int status; + + /* Skip the second round (rounds are zero-indexed) */ + if (round == 1) + return FWK_SUCCESS; + + /* Nothing to be done for element-level binding */ + if (fwk_module_is_valid_element_id(id)) + return FWK_SUCCESS; + + /* Bind to the log module and get a pointer to its API */ + status = fwk_module_bind(FWK_ID_MODULE(FWK_MODULE_IDX_LOG), MOD_LOG_API_ID, + &log_api); + if (status != FWK_SUCCESS) + return FWK_E_HANDLER; + + return FWK_SUCCESS; +} + +static int ddr_phy500_process_bind_request(fwk_id_t requester_id, fwk_id_t id, + fwk_id_t api_type, const void **api) +{ + /* Binding to elements is not permitted. */ + if (fwk_module_is_valid_element_id(id)) + return FWK_E_ACCESS; + + *((const void**)api) = &ddr_phy500_api; + + return FWK_SUCCESS; +} + +const struct fwk_module module_ddr_phy500 = { + .name = "DDR-PHY500", + .type = FWK_MODULE_TYPE_DRIVER, + .init = ddr_phy500_init, + .element_init = ddr_phy500_element_init, + .bind = ddr_phy500_bind, + .process_bind_request = ddr_phy500_process_bind_request, + .api_count = 1, +}; diff --git a/module/dmc500/include/mod_dmc500.h b/module/dmc500/include/mod_dmc500.h new file mode 100644 index 00000000..7f424363 --- /dev/null +++ b/module/dmc500/include/mod_dmc500.h @@ -0,0 +1,571 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * DMC-500 module. + */ + +#ifndef MOD_DMC500_H +#define MOD_DMC500_H + +#include <stdint.h> +#include <fwk_macros.h> +#include <fwk_module.h> +#include <mod_log.h> +#include <mod_timer.h> + +/*! + * \addtogroup GroupModules Modules + * @{ + */ + +/*! + * \addtogroup GroupDMC DMC-500 Driver + * + * \details Please consult the Arm CoreLink DMC-500 Dynamic Memory Controller + * Technical Reference Manual for details on the specific registers that + * are programmed here. + * + * \sa https://developer.arm.com/docs/100132_0000/latest/programmers-model/ + register-summary + * @{ + */ + +/*! + * \brief DMC-500 register definitions + */ +struct mod_dmc500_reg { + /*! + * \cond + * @{ + */ + FWK_R uint32_t SI0_SI_STATUS; + FWK_R uint32_t SI0_SI_INTERRUPT_STATUS; + FWK_R uint32_t SI0_TZ_FAIL_ADDRESS_LOW; + FWK_R uint32_t SI0_TZ_FAIL_ADDRESS_HIGH; + FWK_R uint32_t SI0_TZ_FAIL_CONTROL; + FWK_R uint32_t SI0_TZ_FAIL_ID; + FWK_R uint32_t SI0_PMU_REQ_INT_INFO; + FWK_RW uint32_t SI0_PMU_REQ_COUNT0; + FWK_RW uint32_t SI0_PMU_REQ_COUNT1; + FWK_RW uint32_t SI0_PMU_REQ_COUNT2; + FWK_RW uint32_t SI0_PMU_REQ_COUNT3; + FWK_RW uint32_t SI0_PMU_SCLK_COUNT_COUNT; + FWK_RW uint32_t SI0_SI_STATE_CONTROL; + FWK_W uint32_t SI0_SI_FLUSH_CONTROL; + FWK_RW uint32_t ADDRESS_CONTROL; + FWK_RW uint32_t DECODE_CONTROL; + FWK_RW uint32_t ADDRESS_MAP; + FWK_RW uint32_t RANK_REMAP_CONTROL; + FWK_RW uint32_t SI0_SI_INTERRUPT_CONTROL; + FWK_W uint32_t SI0_SI_INTERRUPT_CLR; + FWK_RW uint32_t TZ_ACTION; + FWK_R uint32_t SI0_TZ_REGION_BASE_LOW_0; + FWK_R uint32_t SI0_TZ_REGION_BASE_HIGH_0; + FWK_RW uint32_t SI0_TZ_REGION_TOP_LOW_0; + FWK_RW uint32_t SI0_TZ_REGION_TOP_HIGH_0; + FWK_RW uint32_t SI0_TZ_REGION_ATTRIBUTES_0; + FWK_RW uint32_t SI0_TZ_REGION_ID_ACCESS_0; + FWK_RW uint32_t SI0_TZ_REGION_BASE_LOW_1; + FWK_RW uint32_t SI0_TZ_REGION_BASE_HIGH_1; + FWK_RW uint32_t SI0_TZ_REGION_TOP_LOW_1; + FWK_RW uint32_t SI0_TZ_REGION_TOP_HIGH_1; + FWK_RW uint32_t SI0_TZ_REGION_ATTRIBUTES_1; + FWK_RW uint32_t SI0_TZ_REGION_ID_ACCESS_1; + FWK_RW uint32_t SI0_TZ_REGION_BASE_LOW_2; + FWK_RW uint32_t SI0_TZ_REGION_BASE_HIGH_2; + FWK_RW uint32_t SI0_TZ_REGION_TOP_LOW_2; + FWK_RW uint32_t SI0_TZ_REGION_TOP_HIGH_2; + FWK_RW uint32_t SI0_TZ_REGION_ATTRIBUTES_2; + FWK_RW uint32_t SI0_TZ_REGION_ID_ACCESS_2; + FWK_RW uint32_t SI0_TZ_REGION_BASE_LOW_3; + FWK_RW uint32_t SI0_TZ_REGION_BASE_HIGH_3; + FWK_RW uint32_t SI0_TZ_REGION_TOP_LOW_3; + FWK_RW uint32_t SI0_TZ_REGION_TOP_HIGH_3; + FWK_RW uint32_t SI0_TZ_REGION_ATTRIBUTES_3; + FWK_RW uint32_t SI0_TZ_REGION_ID_ACCESS_3; + FWK_RW uint32_t SI0_TZ_REGION_BASE_LOW_4; + FWK_RW uint32_t SI0_TZ_REGION_BASE_HIGH_4; + FWK_RW uint32_t SI0_TZ_REGION_TOP_LOW_4; + FWK_RW uint32_t SI0_TZ_REGION_TOP_HIGH_4; + FWK_RW uint32_t SI0_TZ_REGION_ATTRIBUTES_4; + FWK_RW uint32_t SI0_TZ_REGION_ID_ACCESS_4; + FWK_RW uint32_t SI0_TZ_REGION_BASE_LOW_5; + FWK_RW uint32_t SI0_TZ_REGION_BASE_HIGH_5; + FWK_RW uint32_t SI0_TZ_REGION_TOP_LOW_5; + FWK_RW uint32_t SI0_TZ_REGION_TOP_HIGH_5; + FWK_RW uint32_t SI0_TZ_REGION_ATTRIBUTES_5; + FWK_RW uint32_t SI0_TZ_REGION_ID_ACCESS_5; + FWK_RW uint32_t SI0_TZ_REGION_BASE_LOW_6; + FWK_RW uint32_t SI0_TZ_REGION_BASE_HIGH_6; + FWK_RW uint32_t SI0_TZ_REGION_TOP_LOW_6; + FWK_RW uint32_t SI0_TZ_REGION_TOP_HIGH_6; + FWK_RW uint32_t SI0_TZ_REGION_ATTRIBUTES_6; + FWK_RW uint32_t SI0_TZ_REGION_ID_ACCESS_6; + FWK_RW uint32_t SI0_TZ_REGION_BASE_LOW_7; + FWK_RW uint32_t SI0_TZ_REGION_BASE_HIGH_7; + FWK_RW uint32_t SI0_TZ_REGION_TOP_LOW_7; + FWK_RW uint32_t SI0_TZ_REGION_TOP_HIGH_7; + FWK_RW uint32_t SI0_TZ_REGION_ATTRIBUTES_7; + FWK_RW uint32_t SI0_TZ_REGION_ID_ACCESS_7; + FWK_RW uint32_t SI0_TZ_REGION_BASE_LOW_8; + FWK_RW uint32_t SI0_TZ_REGION_BASE_HIGH_8; + FWK_RW uint32_t SI0_TZ_REGION_TOP_LOW_8; + FWK_RW uint32_t SI0_TZ_REGION_TOP_HIGH_8; + FWK_RW uint32_t SI0_TZ_REGION_ATTRIBUTES_8; + FWK_RW uint32_t SI0_TZ_REGION_ID_ACCESS_8; + FWK_RW uint32_t SI0_PMU_REQ_CONTROL; + FWK_RW uint32_t SI0_PMU_REQ_ATTRIBUTE_MASK_0; + FWK_RW uint32_t SI0_PMU_REQ_ATTRIBUTE_MATCH_0; + FWK_RW uint32_t SI0_PMU_REQ_ATTRIBUTE_MASK_1; + FWK_RW uint32_t SI0_PMU_REQ_ATTRIBUTE_MATCH_1; + FWK_RW uint32_t SI0_PMU_REQ_ATTRIBUTE_MASK_2; + FWK_RW uint32_t SI0_PMU_REQ_ATTRIBUTE_MATCH_2; + FWK_RW uint32_t SI0_PMU_REQ_ATTRIBUTE_MASK_3; + FWK_RW uint32_t SI0_PMU_REQ_ATTRIBUTE_MATCH_3; + FWK_RW uint32_t SI0_THRESHOLD_CONTROL; + uint8_t RESERVED0[0x200 - 0x154]; + FWK_R uint32_t SI1_SI_STATUS; + FWK_R uint32_t SI1_SI_INTERRUPT_STATUS; + FWK_R uint32_t SI1_TZ_FAIL_ADDRESS_LOW; + FWK_R uint32_t SI1_TZ_FAIL_ADDRESS_HIGH; + FWK_R uint32_t SI1_TZ_FAIL_CONTROL; + FWK_R uint32_t SI1_TZ_FAIL_ID; + FWK_R uint32_t SI1_PMU_REQ_INT_INFO; + FWK_RW uint32_t SI1_PMU_REQ_COUNT0; + FWK_RW uint32_t SI1_PMU_REQ_COUNT1; + FWK_RW uint32_t SI1_PMU_REQ_COUNT2; + FWK_RW uint32_t SI1_PMU_REQ_COUNT3; + FWK_RW uint32_t SI1_PMU_SCLK_COUNT_COUNT; + FWK_RW uint32_t SI1_SI_STATE_CONTROL; + FWK_W uint32_t SI1_SI_FLUSH_CONTROL; + uint8_t RESERVED1[0x248 - 0x238]; + FWK_RW uint32_t SI1_SI_INTERRUPT_CONTROL; + FWK_W uint32_t SI1_SI_INTERRUPT_CLR; + uint32_t RESERVED2; + FWK_R uint32_t SI1_TZ_REGION_BASE_LOW_0; + FWK_R uint32_t SI1_TZ_REGION_BASE_HIGH_0; + FWK_RW uint32_t SI1_TZ_REGION_TOP_LOW_0; + FWK_RW uint32_t SI1_TZ_REGION_TOP_HIGH_0; + FWK_RW uint32_t SI1_TZ_REGION_ATTRIBUTES_0; + FWK_RW uint32_t SI1_TZ_REGION_ID_ACCESS_0; + FWK_RW uint32_t SI1_TZ_REGION_BASE_LOW_1; + FWK_RW uint32_t SI1_TZ_REGION_BASE_HIGH_1; + FWK_RW uint32_t SI1_TZ_REGION_TOP_LOW_1; + FWK_RW uint32_t SI1_TZ_REGION_TOP_HIGH_1; + FWK_RW uint32_t SI1_TZ_REGION_ATTRIBUTES_1; + FWK_RW uint32_t SI1_TZ_REGION_ID_ACCESS_1; + FWK_RW uint32_t SI1_TZ_REGION_BASE_LOW_2; + FWK_RW uint32_t SI1_TZ_REGION_BASE_HIGH_2; + FWK_RW uint32_t SI1_TZ_REGION_TOP_LOW_2; + FWK_RW uint32_t SI1_TZ_REGION_TOP_HIGH_2; + FWK_RW uint32_t SI1_TZ_REGION_ATTRIBUTES_2; + FWK_RW uint32_t SI1_TZ_REGION_ID_ACCESS_2; + FWK_RW uint32_t SI1_TZ_REGION_BASE_LOW_3; + FWK_RW uint32_t SI1_TZ_REGION_BASE_HIGH_3; + FWK_RW uint32_t SI1_TZ_REGION_TOP_LOW_3; + FWK_RW uint32_t SI1_TZ_REGION_TOP_HIGH_3; + FWK_RW uint32_t SI1_TZ_REGION_ATTRIBUTES_3; + FWK_RW uint32_t SI1_TZ_REGION_ID_ACCESS_3; + FWK_RW uint32_t SI1_TZ_REGION_BASE_LOW_4; + FWK_RW uint32_t SI1_TZ_REGION_BASE_HIGH_4; + FWK_RW uint32_t SI1_TZ_REGION_TOP_LOW_4; + FWK_RW uint32_t SI1_TZ_REGION_TOP_HIGH_4; + FWK_RW uint32_t SI1_TZ_REGION_ATTRIBUTES_4; + FWK_RW uint32_t SI1_TZ_REGION_ID_ACCESS_4; + FWK_RW uint32_t SI1_TZ_REGION_BASE_LOW_5; + FWK_RW uint32_t SI1_TZ_REGION_BASE_HIGH_5; + FWK_RW uint32_t SI1_TZ_REGION_TOP_LOW_5; + FWK_RW uint32_t SI1_TZ_REGION_TOP_HIGH_5; + FWK_RW uint32_t SI1_TZ_REGION_ATTRIBUTES_5; + FWK_RW uint32_t SI1_TZ_REGION_ID_ACCESS_5; + FWK_RW uint32_t SI1_TZ_REGION_BASE_LOW_6; + FWK_RW uint32_t SI1_TZ_REGION_BASE_HIGH_6; + FWK_RW uint32_t SI1_TZ_REGION_TOP_LOW_6; + FWK_RW uint32_t SI1_TZ_REGION_TOP_HIGH_6; + FWK_RW uint32_t SI1_TZ_REGION_ATTRIBUTES_6; + FWK_RW uint32_t SI1_TZ_REGION_ID_ACCESS_6; + FWK_RW uint32_t SI1_TZ_REGION_BASE_LOW_7; + FWK_RW uint32_t SI1_TZ_REGION_BASE_HIGH_7; + FWK_RW uint32_t SI1_TZ_REGION_TOP_LOW_7; + FWK_RW uint32_t SI1_TZ_REGION_TOP_HIGH_7; + FWK_RW uint32_t SI1_TZ_REGION_ATTRIBUTES_7; + FWK_RW uint32_t SI1_TZ_REGION_ID_ACCESS_7; + FWK_RW uint32_t SI1_TZ_REGION_BASE_LOW_8; + FWK_RW uint32_t SI1_TZ_REGION_BASE_HIGH_8; + FWK_RW uint32_t SI1_TZ_REGION_TOP_LOW_8; + FWK_RW uint32_t SI1_TZ_REGION_TOP_HIGH_8; + FWK_RW uint32_t SI1_TZ_REGION_ATTRIBUTES_8; + FWK_RW uint32_t SI1_TZ_REGION_ID_ACCESS_8; + FWK_RW uint32_t SI1_PMU_REQ_CONTROL; + FWK_RW uint32_t SI1_PMU_REQ_ATTRIBUTE_MASK_0; + FWK_RW uint32_t SI1_PMU_REQ_ATTRIBUTE_MATCH_0; + FWK_RW uint32_t SI1_PMU_REQ_ATTRIBUTE_MASK_1; + FWK_RW uint32_t SI1_PMU_REQ_ATTRIBUTE_MATCH_1; + FWK_RW uint32_t SI1_PMU_REQ_ATTRIBUTE_MASK_2; + FWK_RW uint32_t SI1_PMU_REQ_ATTRIBUTE_MATCH_2; + FWK_RW uint32_t SI1_PMU_REQ_ATTRIBUTE_MASK_3; + FWK_RW uint32_t SI1_PMU_REQ_ATTRIBUTE_MATCH_3; + FWK_RW uint32_t SI1_THRESHOLD_CONTROL; + uint8_t RESERVED3[0x400 - 0x354]; + FWK_R uint32_t DCB_STATUS; + FWK_R uint32_t M_INTERRUPT_STATUS; + FWK_R uint32_t PMU_DCB_INT_INFO; + FWK_W uint32_t DCB_STATE_CONTROL; + uint32_t RESERVED4; + FWK_RW uint32_t QUEUE_THRESHOLD_CONTROL_31_00; + FWK_RW uint32_t QUEUE_THRESHOLD_CONTROL_63_32; + uint8_t RESERVED5[0x42C - 0x41C]; + FWK_RW uint32_t DCB_INTERRUPT_CONTROL; + FWK_W uint32_t DCB_INTERRUPT_CLR; + FWK_RW uint32_t PMU_DCB_CONTROL; + FWK_RW uint32_t PMU_DATA_CONTROL_BLOCK_ATTRIBUTE_MASK_0; + FWK_RW uint32_t PMU_DATA_CONTROL_BLOCK_ATTRIBUTE_MATCH_0; + FWK_RW uint32_t PMU_DATA_CONTROL_BLOCK_COUNT_0; + FWK_RW uint32_t PMU_DATA_CONTROL_BLOCK_ATTRIBUTE_MASK_1; + FWK_RW uint32_t PMU_DATA_CONTROL_BLOCK_ATTRIBUTE_MATCH_1; + FWK_RW uint32_t PMU_DATA_CONTROL_BLOCK_COUNT_1; + FWK_RW uint32_t PMU_DATA_CONTROL_BLOCK_ATTRIBUTE_MASK_2; + FWK_RW uint32_t PMU_DATA_CONTROL_BLOCK_ATTRIBUTE_MATCH_2; + FWK_RW uint32_t PMU_DATA_CONTROL_BLOCK_COUNT_2; + FWK_RW uint32_t PMU_TAG_ENTRIES_ATTRIBUTE_MASK; + FWK_RW uint32_t PMU_TAG_ENTRIES_ATTRIBUTE_MATCH; + FWK_RW uint32_t PMU_TAG_ENTRIES_COUNT; + FWK_RW uint32_t PMU_MCLK_COUNT_COUNT; + uint32_t RESERVED6; + FWK_R uint32_t ERR_RAMECC_FR; + uint32_t RESERVED7; + FWK_RW uint32_t ERR_RAMECC_CTLR; + uint32_t RESERVED8; + FWK_RW uint32_t ERR_RAMECC_STATUS; + uint32_t RESERVED9; + FWK_RW uint32_t ERR_RAMECC_ADDR; + FWK_RW uint32_t ERR_RAMECC_ADDR2; + FWK_RW uint32_t ERR_RAMECC_MISC0; + uint32_t RESERVED10[3]; + FWK_W uint32_t ERR_RAMECC_INJECT; + uint8_t RESERVED11[0x500 - 0x4A4]; + FWK_R uint32_t QUEUE_STATUS; + uint32_t RESERVED12; + FWK_R uint32_t PMU_QE_INT_INFO; + FWK_RW uint32_t QUEUE_STATE_CONTROL; + FWK_RW uint32_t QE_INTERRUPT_CONTROL; + FWK_W uint32_t QE_INTERRUPT_CLR; + FWK_RW uint32_t RANK_TURNAROUND_CONTROL; + FWK_RW uint32_t HIT_TURNAROUND_CONTROL; + FWK_RW uint32_t QOS_CLASS_CONTROL; + FWK_RW uint32_t ESCALATION_CONTROL; + FWK_RW uint32_t QV_CONTROL_31_00; + FWK_RW uint32_t QV_CONTROL_63_32; + FWK_RW uint32_t RT_CONTROL_31_00; + FWK_RW uint32_t RT_CONTROL_63_32; + FWK_RW uint32_t TIMEOUT_CONTROL; + FWK_RW uint32_t WRITE_PRIORITY_CONTROL_31_00; + FWK_RW uint32_t WRITE_PRIORITY_CONTROL_63_32; + uint32_t RESERVED13; + FWK_RW uint32_t DIR_TURNAROUND_CONTROL; + FWK_RW uint32_t HIT_PREDICTION_CONTROL; + FWK_RW uint32_t REFRESH_ENABLE; + FWK_R uint32_t REFRESH_STATUS; + FWK_R uint32_t REFRESH_STATUS_FG; + FWK_RW uint32_t REFRESH_PRIORITY; + FWK_RW uint32_t MC_UPDATE_CONTROL; + FWK_RW uint32_t PHY_UPDATE_CONTROL; + FWK_RW uint32_t PHY_MASTER_CONTROL; + FWK_RW uint32_t LOW_POWER_CONTROL; + FWK_RW uint32_t PMU_QE_CONTROL; + FWK_RW uint32_t PMU_QE_MUX; + FWK_RW uint32_t PMU_QOS_ENGINE_ATTRIBUTE_MASK_0; + FWK_RW uint32_t PMU_QOS_ENGINE_ATTRIBUTE_MATCH_0; + FWK_RW uint32_t PMU_QOS_ENGINE_COUNT_0; + FWK_RW uint32_t PMU_QOS_ENGINE_ATTRIBUTE_MASK_1; + FWK_RW uint32_t PMU_QOS_ENGINE_ATTRIBUTE_MATCH_1; + FWK_RW uint32_t PMU_QOS_ENGINE_COUNT_1; + FWK_RW uint32_t PMU_QOS_ENGINE_ATTRIBUTE_MASK_2; + FWK_RW uint32_t PMU_QOS_ENGINE_ATTRIBUTE_MATCH_2; + FWK_RW uint32_t PMU_QOS_ENGINE_COUNT_2; + FWK_RW uint32_t PMU_QUEUED_ENTRIES_ATTRIBUTE_MASK; + FWK_RW uint32_t PMU_QUEUED_ENTRIES_ATTRIBUTE_MATCH; + FWK_RW uint32_t PMU_QUEUED_ENTRIES_COUNT; + uint8_t RESERVED14[0x600 - 0x5A8]; + FWK_R uint32_t MI_STATUS; + FWK_R uint32_t RANKS_READY; + FWK_R uint32_t RANKS_RESET; + FWK_R uint32_t RANKS_DEEP_POWER_DOWN; + FWK_R uint32_t RANKS_SELF_REFRESH; + FWK_R uint32_t RANKS_POWERED_DOWN; + FWK_R uint32_t RANKS_CLOCK_DISABLED; + FWK_R uint32_t PHY_STATUS0; + FWK_R uint32_t PHY_STATUS1; + uint32_t RESERVED15; + FWK_R uint32_t PMU_MI_INT_INFO; + uint32_t RESERVED16; + FWK_RW uint32_t MI_STATE_CONTROL; + FWK_RW uint32_t PHY_CONFIG; + FWK_RW uint32_t DIRECT_CMD_SETTINGS; + FWK_RW uint32_t DIRECT_CMD; + FWK_RW uint32_t DIRECT_CLK_DISABLE; + FWK_RW uint32_t DIRECT_ODT; + FWK_RW uint32_t DCI_STRB; + FWK_RW uint32_t DCI_DATA; + FWK_W uint32_t DCI_DATA_CLR; + FWK_W uint32_t RANK_STATUS_OVERRIDE; + FWK_W uint32_t CLK_STATUS_OVERRIDE; + FWK_W uint32_t BANK_STATUS_OVERRIDE; + FWK_RW uint32_t MI_INTERRUPT_CONTROL; + FWK_W uint32_t MI_INTERRUPT_CLR; + FWK_RW uint32_t MEMORY_TYPE; + FWK_RW uint32_t FORMAT_CONTROL; + FWK_RW uint32_t FEATURE_CONTROL; + FWK_RW uint32_t POWER_DOWN_CONTROL; + FWK_RW uint32_t REFRESH_CONTROL; + FWK_RW uint32_t ODT_WR_CONTROL_31_00; + uint32_t RESERVED17; + FWK_RW uint32_t ODT_RD_CONTROL_31_00; + uint32_t RESERVED18; + FWK_RW uint32_t PHY_WRDATA_CS_CONTROL_31_00; + uint32_t RESERVED19; + FWK_RW uint32_t PHY_RDDATA_CS_CONTROL_31_00; + uint32_t RESERVED20; + FWK_RW uint32_t PHYUPD_INIT; + FWK_RW uint32_t PHY_POWER_CONTROL; + uint32_t RESERVED21; + FWK_RW uint32_t ODT_TIMING; + FWK_RW uint32_t T_REFI; + FWK_RW uint32_t T_RFC; + FWK_RW uint32_t T_RCD; + FWK_RW uint32_t T_RAS; + FWK_RW uint32_t T_RP; + FWK_RW uint32_t T_RRD; + FWK_RW uint32_t T_ACT_WINDOW; + FWK_RW uint32_t T_RTR; + FWK_RW uint32_t T_RTW; + FWK_RW uint32_t T_RTP; + FWK_RW uint32_t T_RDPDEN; + FWK_RW uint32_t T_WR; + FWK_RW uint32_t T_WTR; + FWK_RW uint32_t T_WTW; + FWK_RW uint32_t T_XTMW; + FWK_RW uint32_t T_WRPDEN; + FWK_RW uint32_t T_CLOCK_CONTROL; + FWK_RW uint32_t T_EP; + FWK_RW uint32_t T_XP; + FWK_RW uint32_t T_ESR; + FWK_RW uint32_t T_XSR; + uint32_t RESERVED22; + FWK_RW uint32_t T_COMPLETION_CHECKS; + FWK_RW uint32_t T_RDDATA_EN; + FWK_RW uint32_t T_PHYRDLAT; + FWK_RW uint32_t T_PHYWRLAT; + FWK_RW uint32_t T_PHY_TRAIN; + FWK_R uint32_t ERR_PHY_FR; + uint32_t RESERVED23; + FWK_RW uint32_t ERR_PHY_CTLR; + uint32_t RESERVED24; + FWK_RW uint32_t ERR_PHY_STATUS; + uint32_t RESERVED25; + FWK_RW uint32_t ERR_PHY_ADDR; + FWK_RW uint32_t ERR_PHY_ADDR2; + FWK_RW uint32_t ERR_PHY_MISC0; + uint8_t RESERVED26[0x74C - 0x73C]; + FWK_W uint32_t ERR_PHY_INJECT; + FWK_RW uint32_t PMU_MI_CONTROL; + FWK_RW uint32_t PMU_MEMORY_IF_ATTRIBUTE_MASK_0; + FWK_RW uint32_t PMU_MEMORY_IF_ATTRIBUTE_MATCH_0; + FWK_RW uint32_t PMU_MEMORY_IF_COUNT_0; + FWK_RW uint32_t PMU_MEMORY_IF_ATTRIBUTE_MASK_1; + FWK_RW uint32_t PMU_MEMORY_IF_ATTRIBUTE_MATCH_1; + FWK_RW uint32_t PMU_MEMORY_IF_COUNT_1; + FWK_RW uint32_t PMU_BANK_STATES_ATTRIBUTE_MASK; + FWK_RW uint32_t PMU_BANK_STATES_ATTRIBUTE_MATCH; + FWK_RW uint32_t PMU_BANK_STATES_COUNT; + FWK_RW uint32_t PMU_RANK_STATES_ATTRIBUTE_MASK; + FWK_RW uint32_t PMU_RANK_STATES_ATTRIBUTE_MATCH; + FWK_RW uint32_t PMU_RANK_STATES_COUNT; + uint8_t RESERVED27[0xF00 - 0x784]; + FWK_R uint32_t MEMC_CONFIG; + uint32_t RESERVED28[3]; + FWK_R uint32_t CFG_INTERRUPT_STATUS; + FWK_R uint32_t CFG_FAILED_ACCESS_INT_INFO; + uint8_t RESERVED29[0xF30 - 0xF18]; + FWK_RW uint32_t CFG_INTERRUPT_CONTROL; + uint32_t RESERVED30; + FWK_W uint32_t CFG_INTERRUPT_CLR; + uint8_t RESERVED31[0xFC0 - 0xF3C]; + FWK_RW uint32_t INTEGRATION_TEST_CONTROL; + FWK_RW uint32_t INTEGRATION_TEST_OUTPUT; + uint32_t RESERVED32[2]; + FWK_R uint32_t PERIPH_ID_4; + uint32_t RESERVED33[3]; + FWK_R uint32_t PERIPH_ID_0; + FWK_R uint32_t PERIPH_ID_1; + FWK_R uint32_t PERIPH_ID_2; + FWK_R uint32_t PERIPH_ID_3; + FWK_R uint32_t COMPONENT_ID_0; + FWK_R uint32_t COMPONENT_ID_1; + FWK_R uint32_t COMPONENT_ID_2; + FWK_R uint32_t COMPONENT_ID_3; + /*! + * \endcond + * @} + */ +}; + +/*! + * \brief SI_STATE_CONTROL mask used to prevent request stalling. + */ +#define MOD_DMC500_SI_STATE_CONTROL_GO 0 + +/*! + * \brief SI_STATE_CONTROL mask used to enable request stalling. + */ +#define MOD_DMC500_SI_STATE_CONTROL_STALL_REQ (1 << 0) + +/*! + * \brief SI_STATUS mask used to confirm that request stalling is active. + */ +#define MOD_DMC500_SI_STATUS_STALL_ACK (1 << 0) + +/*! + * \brief SI_STATUS mask used to read the empty bit. + */ +#define MOD_DMC500_SI_STATUS_EMPTY (1 << 1) + +/*! + * \brief QUEUE_STATE_CONTROL mask used to prevent request stalling. + */ +#define MOD_DMC500_QUEUE_STATE_CONTROL_GO 0 + +/*! + * \brief QUEUE_STATE_CONTROL mask used to enable request stalling. + */ +#define MOD_DMC500_QUEUE_STATE_CONTROL_STALL_REQ (1 << 0) + +/*! + * \brief QUEUE_STATUS mask used to confirm that request stalling is active. + */ +#define MOD_DMC500_QUEUE_STATUS_STALL_ACK (1 << 0) + +/*! + * \brief QUEUE_STATUS mask used to read the empty bit. + */ +#define MOD_DMC500_QUEUE_STATUS_EMPTY (1 << 1) + +/*! + * \brief MI_STATUS mask used to read the idle bit. + */ +#define MOD_DMC500_MI_STATUS_IDLE (1 << 0) + +/*! + * \brief MI_STATUS mask used to read the empty bit. + */ +#define MOD_DMC500_MI_STATUS_EMPTY (1 << 1) + +/*! + * \brief Create the ADDRESS_MAP value. + * + * \param SHUTTER The address shutter. + * + * \return The ADDRESS_MAP value. + */ +#define ADDRESS_MAP_VAL(SHUTTER) ((1 << 8) | (SHUTTER)) + +/*! + * \brief Create the ADDRESS_CONTROL value. + * + * \param RANK Number of bits for the rank. + * \param BANK Number of bits for the bank. + * \param ROW Number of bits for the row. + * \param COL Number of bits for the column. + * + * \return The ADDRESS_CONTROL value. + */ +#define ADDRESS_CONTROL_VAL(RANK, BANK, ROW, COL) (((RANK) << 24) | \ + ((BANK) << 16) | \ + ((ROW) << 8) | \ + (COL)) + +/*! + * \brief Create the MEMORY_TYPE value + * + * \param BANK_GROUP Bank group. + * \param WIDTH Memory device width. + * \param TYPE Memory type. + * + * \return The MEMORY_TYPE value. + */ +#define MEMORY_TYPE_VAL(BANK_GROUP, WIDTH, TYPE) (((BANK_GROUP) << 16) | \ + ((WIDTH) << 8) | \ + (TYPE)) + +/*! + * \brief Create the FORMAT_CONTROL value. + * + * \param BURST Memory burst. + * + * \return The FORMAT_CONTROL value. + */ +#define FORMAT_CONTROL_VAL(BURST) ((BURST) << 8) + +/*! + * \brief Element configuration. + */ +struct mod_dmc500_element_config { + /*! Base address of the DMC-500 device's registers */ + uintptr_t dmc; + /*! Element identifier of the associated DDR PHY-500 device */ + fwk_id_t ddr_phy_id; +}; + +/*! + * \brief API of the DDR PHY associate to the DMC + */ +struct mod_dmc500_ddr_phy_api { + /*! + * \brief Configure a DDR PHY500 device + * + * \param element_id Element identifier corresponding to the device to + * configure. + * + * \retval FWK_SUCCESS if the operation succeed. + * \return one of the error code otherwise. + */ + int (*configure)(fwk_id_t element_id); +}; + +/*! + * \brief DMC-500 module configuration. + */ +struct mod_dmc500_module_config { + /*! + * Element identifier of the timer used for delays when programming the + * DMC-500 + */ + fwk_id_t timer_id; + /*! DDR PHY module ID */ + fwk_id_t ddr_phy_module_id; + /*! DDR PHY API ID */ + fwk_id_t ddr_phy_api_id; + /*! Initial value for the dmc registers */ + const struct mod_dmc500_reg *reg_val; + /*! Pointer to a product-specific function that issues direct commands */ + void (*direct_ddr_cmd)(struct mod_dmc500_reg *dmc); +}; + +/*! + * \brief DMC-500 module description. + */ +extern const struct fwk_module module_dmc500; + +/*! + * @} + */ + +/*! + * @} + */ + +#endif /* MOD_DMC500_H */ diff --git a/module/dmc500/src/Makefile b/module/dmc500/src/Makefile new file mode 100644 index 00000000..fd08ee04 --- /dev/null +++ b/module/dmc500/src/Makefile @@ -0,0 +1,11 @@ +# +# Arm SCP/MCP Software +# Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +BS_LIB_NAME := mod_dmc500 +BS_LIB_SOURCES += mod_dmc500.c + +include $(BS_DIR)/lib.mk diff --git a/module/dmc500/src/mod_dmc500.c b/module/dmc500/src/mod_dmc500.c new file mode 100644 index 00000000..0b68374c --- /dev/null +++ b/module/dmc500/src/mod_dmc500.c @@ -0,0 +1,390 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * DMC-500 driver + */ + +#include <assert.h> +#include <fwk_errno.h> +#include <fwk_mm.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <mod_dmc500.h> + +static struct mod_log_api *log_api; +static struct mod_dmc500_ddr_phy_api *ddr_phy_api; +static struct mod_timer_api *timer_api; + +static int dmc500_config(struct mod_dmc500_reg *dmc, fwk_id_t ddr_phy_id); + +/* Framework API */ +static int mod_dmc500_init(fwk_id_t module_id, unsigned int element_count, + const void *data) +{ + return FWK_SUCCESS; +} + +static int mod_dmc500_element_init(fwk_id_t element_id, unsigned int unused, + const void *data) +{ + assert(data != NULL); + + return FWK_SUCCESS; +} + +static int mod_dmc500_bind(fwk_id_t id, unsigned int round) +{ + int status; + const struct mod_dmc500_module_config *module_config; + + /* Nothing to do in the second round of calls. */ + if (round == 1) + return FWK_SUCCESS; + + /* Nothing to do in case of elements. */ + if (fwk_module_is_valid_element_id(id)) + return FWK_SUCCESS; + + module_config = fwk_module_get_data(fwk_module_id_dmc500); + assert(module_config != NULL); + + status = fwk_module_bind(FWK_ID_MODULE(FWK_MODULE_IDX_LOG), + MOD_LOG_API_ID, &log_api); + if (status != FWK_SUCCESS) + return status; + + status = fwk_module_bind(module_config->ddr_phy_module_id, + module_config->ddr_phy_api_id, &ddr_phy_api); + if (status != FWK_SUCCESS) + return status; + + status = fwk_module_bind(module_config->timer_id, + FWK_ID_API(FWK_MODULE_IDX_TIMER, 0), &timer_api); + if (status != FWK_SUCCESS) + return status; + + return FWK_SUCCESS; +} + +static int mod_dmc500_start(fwk_id_t id) +{ + const struct mod_dmc500_element_config *element_config; + struct mod_dmc500_reg *dmc; + + /* Nothing to start for the module */ + if (fwk_module_is_valid_module_id(id)) + return FWK_SUCCESS; + + element_config = fwk_module_get_data(id); + dmc = (struct mod_dmc500_reg *)element_config->dmc; + + return dmc500_config(dmc, element_config->ddr_phy_id); +} + +const struct fwk_module module_dmc500 = { + .name = "DMC500", + .type = FWK_MODULE_TYPE_DRIVER, + .init = mod_dmc500_init, + .element_init = mod_dmc500_element_init, + .bind = mod_dmc500_bind, + .start = mod_dmc500_start, + .api_count = 0, + .event_count = 0, +}; + + +static int dmc500_config(struct mod_dmc500_reg *dmc, fwk_id_t ddr_phy_id) +{ + int status; + uint64_t timeout; + uint64_t remaining_ticks; + uint64_t counter; + const struct mod_dmc500_reg *reg_val; + const struct mod_dmc500_module_config *module_config; + + module_config = fwk_module_get_data(fwk_module_id_dmc500); + reg_val = module_config->reg_val; + + status = log_api->log(MOD_LOG_GROUP_DEBUG, + "[DDR] Initialising DMC500 at 0x%x\n", (uintptr_t)dmc); + if (status != FWK_SUCCESS) + return status; + + dmc->ADDRESS_CONTROL = reg_val->ADDRESS_CONTROL; + dmc->RANK_REMAP_CONTROL = reg_val->RANK_REMAP_CONTROL; + dmc->MEMORY_TYPE = reg_val->MEMORY_TYPE; + dmc->FORMAT_CONTROL = reg_val->FORMAT_CONTROL; + dmc->DECODE_CONTROL = reg_val->DECODE_CONTROL; + dmc->FEATURE_CONTROL = reg_val->FEATURE_CONTROL; + dmc->ODT_WR_CONTROL_31_00 = reg_val->ODT_WR_CONTROL_31_00; + dmc->ODT_RD_CONTROL_31_00 = reg_val->ODT_RD_CONTROL_31_00; + dmc->ODT_TIMING = reg_val->ODT_TIMING; + + status = log_api->log(MOD_LOG_GROUP_DEBUG, + "[DDR] Setting timing settings\n"); + if (status != FWK_SUCCESS) + return status; + + dmc->T_REFI = reg_val->T_REFI; + dmc->T_RFC = reg_val->T_RFC; + dmc->T_RDPDEN = reg_val->T_RDPDEN; + dmc->T_RCD = reg_val->T_RCD; + dmc->T_RAS = reg_val->T_RAS; + dmc->T_RP = reg_val->T_RP; + dmc->T_RRD = reg_val->T_RRD; + dmc->T_ACT_WINDOW = reg_val->T_ACT_WINDOW; + dmc->T_RTR = reg_val->T_RTR; + dmc->T_RTW = reg_val->T_RTW; + dmc->T_RTP = reg_val->T_RTP; + dmc->T_WR = reg_val->T_WR; + dmc->T_WTR = reg_val->T_WTR; + dmc->T_WTW = reg_val->T_WTW; + dmc->T_XTMW = reg_val->T_XTMW; + dmc->T_CLOCK_CONTROL = reg_val->T_CLOCK_CONTROL; + dmc->T_EP = reg_val->T_EP; + dmc->T_XP = reg_val->T_XP; + dmc->T_ESR = reg_val->T_ESR; + dmc->T_XSR = reg_val->T_XSR; + + status = log_api->log(MOD_LOG_GROUP_DEBUG, + "[DDR] Setting address map\n"); + if (status != FWK_SUCCESS) + return status; + + dmc->ADDRESS_MAP = reg_val->ADDRESS_MAP; + + status = log_api->log(MOD_LOG_GROUP_DEBUG, + "[DDR] Setting PMU settings\n"); + if (status != FWK_SUCCESS) + return status; + + dmc->SI0_SI_INTERRUPT_CONTROL = reg_val->SI0_SI_INTERRUPT_CONTROL; + dmc->SI0_PMU_REQ_CONTROL = reg_val->SI0_PMU_REQ_CONTROL; + dmc->SI0_PMU_REQ_ATTRIBUTE_MASK_0 = reg_val->SI0_PMU_REQ_ATTRIBUTE_MASK_0; + dmc->SI0_PMU_REQ_ATTRIBUTE_MATCH_0 = reg_val->SI0_PMU_REQ_ATTRIBUTE_MATCH_0; + dmc->SI0_PMU_REQ_ATTRIBUTE_MASK_1 = reg_val->SI0_PMU_REQ_ATTRIBUTE_MASK_1; + dmc->SI0_PMU_REQ_ATTRIBUTE_MATCH_1 = reg_val->SI0_PMU_REQ_ATTRIBUTE_MATCH_1; + dmc->SI0_PMU_REQ_ATTRIBUTE_MASK_2 = reg_val->SI0_PMU_REQ_ATTRIBUTE_MASK_2; + dmc->SI0_PMU_REQ_ATTRIBUTE_MATCH_2 = reg_val->SI0_PMU_REQ_ATTRIBUTE_MATCH_2; + dmc->SI0_PMU_REQ_ATTRIBUTE_MASK_3 = reg_val->SI0_PMU_REQ_ATTRIBUTE_MASK_3; + dmc->SI0_PMU_REQ_ATTRIBUTE_MATCH_3 = reg_val->SI0_PMU_REQ_ATTRIBUTE_MATCH_3; + dmc->SI0_THRESHOLD_CONTROL = reg_val->SI0_THRESHOLD_CONTROL; + dmc->SI1_SI_INTERRUPT_CONTROL = reg_val->SI1_SI_INTERRUPT_CONTROL; + dmc->SI1_PMU_REQ_CONTROL = reg_val->SI1_PMU_REQ_CONTROL; + dmc->SI1_PMU_REQ_ATTRIBUTE_MASK_0 = reg_val->SI1_PMU_REQ_ATTRIBUTE_MASK_0; + dmc->SI1_PMU_REQ_ATTRIBUTE_MATCH_0 = reg_val->SI1_PMU_REQ_ATTRIBUTE_MATCH_0; + dmc->SI1_PMU_REQ_ATTRIBUTE_MASK_1 = reg_val->SI1_PMU_REQ_ATTRIBUTE_MASK_1; + dmc->SI1_PMU_REQ_ATTRIBUTE_MATCH_1 = reg_val->SI1_PMU_REQ_ATTRIBUTE_MATCH_1; + dmc->SI1_PMU_REQ_ATTRIBUTE_MASK_2 = reg_val->SI1_PMU_REQ_ATTRIBUTE_MASK_2; + dmc->SI1_PMU_REQ_ATTRIBUTE_MATCH_2 = reg_val->SI1_PMU_REQ_ATTRIBUTE_MATCH_2; + dmc->SI1_PMU_REQ_ATTRIBUTE_MASK_3 = reg_val->SI1_PMU_REQ_ATTRIBUTE_MASK_3; + dmc->SI1_PMU_REQ_ATTRIBUTE_MATCH_3 = reg_val->SI1_PMU_REQ_ATTRIBUTE_MATCH_3; + dmc->SI1_THRESHOLD_CONTROL = reg_val->SI1_THRESHOLD_CONTROL; + dmc->QUEUE_THRESHOLD_CONTROL_31_00 = reg_val->QUEUE_THRESHOLD_CONTROL_31_00; + dmc->QUEUE_THRESHOLD_CONTROL_63_32 = reg_val->QUEUE_THRESHOLD_CONTROL_63_32; + dmc->DCB_INTERRUPT_CONTROL = reg_val->DCB_INTERRUPT_CONTROL; + dmc->PMU_DCB_CONTROL = reg_val->PMU_DCB_CONTROL; + dmc->PMU_DATA_CONTROL_BLOCK_ATTRIBUTE_MASK_0 = + reg_val->PMU_DATA_CONTROL_BLOCK_ATTRIBUTE_MASK_0; + dmc->PMU_DATA_CONTROL_BLOCK_ATTRIBUTE_MATCH_0 = + reg_val->PMU_DATA_CONTROL_BLOCK_ATTRIBUTE_MATCH_0; + dmc->PMU_DATA_CONTROL_BLOCK_ATTRIBUTE_MASK_1 = + reg_val->PMU_DATA_CONTROL_BLOCK_ATTRIBUTE_MASK_1; + dmc->PMU_DATA_CONTROL_BLOCK_ATTRIBUTE_MATCH_1 = + reg_val->PMU_DATA_CONTROL_BLOCK_ATTRIBUTE_MATCH_1; + dmc->PMU_DATA_CONTROL_BLOCK_ATTRIBUTE_MASK_2 = + reg_val->PMU_DATA_CONTROL_BLOCK_ATTRIBUTE_MASK_2; + dmc->PMU_DATA_CONTROL_BLOCK_ATTRIBUTE_MATCH_2 = + reg_val->PMU_DATA_CONTROL_BLOCK_ATTRIBUTE_MATCH_2; + dmc->PMU_TAG_ENTRIES_ATTRIBUTE_MASK = + reg_val->PMU_TAG_ENTRIES_ATTRIBUTE_MASK; + dmc->PMU_TAG_ENTRIES_ATTRIBUTE_MATCH = + reg_val->PMU_TAG_ENTRIES_ATTRIBUTE_MATCH; + dmc->QE_INTERRUPT_CONTROL = reg_val->QE_INTERRUPT_CONTROL; + dmc->RANK_TURNAROUND_CONTROL = reg_val->RANK_TURNAROUND_CONTROL; + dmc->HIT_TURNAROUND_CONTROL = reg_val->HIT_TURNAROUND_CONTROL; + dmc->QOS_CLASS_CONTROL = reg_val->QOS_CLASS_CONTROL; + dmc->ESCALATION_CONTROL = reg_val->ESCALATION_CONTROL; + dmc->QV_CONTROL_31_00 = reg_val->QV_CONTROL_31_00; + dmc->QV_CONTROL_63_32 = reg_val->QV_CONTROL_63_32; + dmc->RT_CONTROL_31_00 = reg_val->RT_CONTROL_31_00; + dmc->RT_CONTROL_63_32 = reg_val->RT_CONTROL_63_32; + dmc->TIMEOUT_CONTROL = reg_val->TIMEOUT_CONTROL; + dmc->WRITE_PRIORITY_CONTROL_31_00 = reg_val->WRITE_PRIORITY_CONTROL_31_00; + dmc->WRITE_PRIORITY_CONTROL_63_32 = reg_val->WRITE_PRIORITY_CONTROL_63_32; + dmc->DIR_TURNAROUND_CONTROL = reg_val->DIR_TURNAROUND_CONTROL; + dmc->HIT_PREDICTION_CONTROL = reg_val->HIT_PREDICTION_CONTROL; + dmc->REFRESH_PRIORITY = reg_val->REFRESH_PRIORITY; + dmc->MC_UPDATE_CONTROL = reg_val->MC_UPDATE_CONTROL; + dmc->PHY_UPDATE_CONTROL = reg_val->PHY_UPDATE_CONTROL; + dmc->PHY_MASTER_CONTROL = reg_val->PHY_MASTER_CONTROL; + dmc->LOW_POWER_CONTROL = reg_val->LOW_POWER_CONTROL; + dmc->PMU_QE_CONTROL = reg_val->PMU_QE_CONTROL; + dmc->PMU_QE_MUX = reg_val->PMU_QE_MUX; + dmc->PMU_QOS_ENGINE_ATTRIBUTE_MASK_0 = + reg_val->PMU_QOS_ENGINE_ATTRIBUTE_MASK_0; + dmc->PMU_QOS_ENGINE_ATTRIBUTE_MATCH_0 = + reg_val->PMU_QOS_ENGINE_ATTRIBUTE_MATCH_0; + dmc->PMU_QOS_ENGINE_ATTRIBUTE_MASK_1 = + reg_val->PMU_QOS_ENGINE_ATTRIBUTE_MASK_1; + dmc->PMU_QOS_ENGINE_ATTRIBUTE_MATCH_1 = + reg_val->PMU_QOS_ENGINE_ATTRIBUTE_MATCH_1; + dmc->PMU_QOS_ENGINE_ATTRIBUTE_MASK_2 = + reg_val->PMU_QOS_ENGINE_ATTRIBUTE_MASK_2; + dmc->PMU_QOS_ENGINE_ATTRIBUTE_MATCH_2 = + reg_val->PMU_QOS_ENGINE_ATTRIBUTE_MATCH_2; + dmc->PMU_QUEUED_ENTRIES_ATTRIBUTE_MASK = + reg_val->PMU_QUEUED_ENTRIES_ATTRIBUTE_MASK; + dmc->PMU_QUEUED_ENTRIES_ATTRIBUTE_MATCH = + reg_val->PMU_QUEUED_ENTRIES_ATTRIBUTE_MATCH; + dmc->MI_INTERRUPT_CONTROL = reg_val->MI_INTERRUPT_CONTROL; + dmc->POWER_DOWN_CONTROL = reg_val->POWER_DOWN_CONTROL; + dmc->REFRESH_CONTROL = reg_val->REFRESH_CONTROL; + dmc->PMU_MI_CONTROL = reg_val->PMU_MI_CONTROL; + dmc->PMU_MEMORY_IF_ATTRIBUTE_MASK_0 = + reg_val->PMU_MEMORY_IF_ATTRIBUTE_MASK_0; + dmc->PMU_MEMORY_IF_ATTRIBUTE_MATCH_0 = + reg_val->PMU_MEMORY_IF_ATTRIBUTE_MATCH_0; + dmc->PMU_MEMORY_IF_ATTRIBUTE_MASK_1 = + reg_val->PMU_MEMORY_IF_ATTRIBUTE_MASK_1; + dmc->PMU_MEMORY_IF_ATTRIBUTE_MATCH_1 = + reg_val->PMU_MEMORY_IF_ATTRIBUTE_MATCH_1; + dmc->PMU_BANK_STATES_ATTRIBUTE_MASK = + reg_val->PMU_BANK_STATES_ATTRIBUTE_MASK; + dmc->PMU_BANK_STATES_ATTRIBUTE_MATCH = + reg_val->PMU_BANK_STATES_ATTRIBUTE_MATCH; + dmc->PMU_RANK_STATES_ATTRIBUTE_MASK = + reg_val->PMU_RANK_STATES_ATTRIBUTE_MASK; + dmc->PMU_RANK_STATES_ATTRIBUTE_MATCH = + reg_val->PMU_RANK_STATES_ATTRIBUTE_MATCH; + dmc->CFG_INTERRUPT_CONTROL = reg_val->CFG_INTERRUPT_CONTROL; + dmc->T_RDDATA_EN = reg_val->T_RDDATA_EN; + dmc->T_PHYRDLAT = reg_val->T_PHYRDLAT; + dmc->T_PHYWRLAT = reg_val->T_PHYWRLAT; + + dmc->ERR_RAMECC_CTLR = reg_val->ERR_RAMECC_CTLR; + + status = log_api->log(MOD_LOG_GROUP_DEBUG, + "[DDR] Setting PHY-related settings\n"); + if (status != FWK_SUCCESS) + return status; + + dmc->PHY_POWER_CONTROL = reg_val->PHY_POWER_CONTROL; + dmc->T_PHY_TRAIN = reg_val->T_PHY_TRAIN; + dmc->PHYUPD_INIT = reg_val->PHYUPD_INIT; + + dmc->PHY_CONFIG = 0x03000000; + dmc->PHY_CONFIG = 0x0600000A; + dmc->PHY_CONFIG = 0x01000001; + + status = ddr_phy_api->configure(ddr_phy_id); + if (status != FWK_SUCCESS) + return status; + + dmc->PHY_CONFIG = 0x01000001; + dmc->PHY_CONFIG = 0x01000000; + dmc->PHY_CONFIG = 0x00000003; + + status = log_api->log(MOD_LOG_GROUP_DEBUG, + "[DDR] Doing direct DDR commands\n"); + if (status != FWK_SUCCESS) + return status; + + module_config->direct_ddr_cmd(dmc); + + dmc->REFRESH_ENABLE = reg_val->REFRESH_ENABLE; + + status = log_api->log(MOD_LOG_GROUP_DEBUG, + "[DDR] Setting dmc in READY mode\n"); + if (status != FWK_SUCCESS) + return status; + + status = timer_api->time_to_timestamp(module_config->timer_id, + 1000 * 1000, &timeout); + if (status != FWK_SUCCESS) + return status; + + status = timer_api->get_counter(module_config->timer_id, &counter); + if (status != FWK_SUCCESS) + return status; + + timeout += counter; + + while ((dmc->MI_STATUS & MOD_DMC500_MI_STATUS_IDLE) != + MOD_DMC500_MI_STATUS_IDLE) { + status = timer_api->remaining(module_config->timer_id, timeout, + &remaining_ticks); + if (status != FWK_SUCCESS) + return status; + + if (remaining_ticks == 0) + goto timeout; + } + + dmc->MI_STATE_CONTROL = reg_val->MI_STATE_CONTROL; + dmc->QUEUE_STATE_CONTROL = reg_val->QUEUE_STATE_CONTROL; + dmc->SI0_SI_STATE_CONTROL = reg_val->SI0_SI_STATE_CONTROL; + dmc->SI1_SI_STATE_CONTROL = reg_val->SI1_SI_STATE_CONTROL; + + status = log_api->log(MOD_LOG_GROUP_DEBUG, + "[DDR] Waiting for Queue stall = 0...\n"); + if (status != FWK_SUCCESS) + return status; + + while ((dmc->QUEUE_STATUS & MOD_DMC500_QUEUE_STATUS_STALL_ACK) != 0) { + status = timer_api->remaining(module_config->timer_id, timeout, + &remaining_ticks); + if (status != FWK_SUCCESS) + return status; + + if (remaining_ticks == 0) + goto timeout; + } + + status = log_api->log(MOD_LOG_GROUP_DEBUG, + "[DDR] Waiting for SI0 stall = 0...\n"); + if (status != FWK_SUCCESS) + return FWK_SUCCESS; + + while ((dmc->SI0_SI_STATUS & MOD_DMC500_SI_STATUS_STALL_ACK) != 0) { + status = timer_api->remaining(module_config->timer_id, timeout, + &remaining_ticks); + if (status != FWK_SUCCESS) + return status; + + if (remaining_ticks == 0) + goto timeout; + } + + status = log_api->log(MOD_LOG_GROUP_DEBUG, + "[DDR] Waiting for SI1 stall = 0...\n"); + if (status != FWK_SUCCESS) + return status; + + while ((dmc->SI1_SI_STATUS & MOD_DMC500_SI_STATUS_STALL_ACK) != 0) { + status = timer_api->remaining(module_config->timer_id, timeout, + &remaining_ticks); + if (status != FWK_SUCCESS) + return status; + + if (remaining_ticks == 0) + goto timeout; + } + + status = log_api->log(MOD_LOG_GROUP_DEBUG, "[DDR] DMC init done.\n"); + if (status != FWK_SUCCESS) + return status; + + return FWK_SUCCESS; + +timeout: + status = log_api->log(MOD_LOG_GROUP_ERROR, + "[DDR] Timed out in DMC500 init.\n"); + if (status != FWK_SUCCESS) + return status; + return FWK_E_TIMEOUT; +} diff --git a/module/dvfs/include/mod_dvfs.h b/module/dvfs/include/mod_dvfs.h new file mode 100644 index 00000000..9b54b087 --- /dev/null +++ b/module/dvfs/include/mod_dvfs.h @@ -0,0 +1,271 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef MOD_DVFS_H +#define MOD_DVFS_H + +#include <stddef.h> +#include <stdint.h> +#include <fwk_id.h> +#include <fwk_module_idx.h> + +/*! + * \ingroup GroupModules + * \defgroup GroupDvfs Dynamic Voltage and Frequency Scaling (DVFS) + * \{ + */ + +/*! + * \defgroup GroupDvfsTypes Types + * \{ + */ + +/*! + * \brief Frequency limits. + */ +struct mod_dvfs_frequency_limits { + uint64_t minimum; /*!< Minimum permitted rate */ + uint64_t maximum; /*!< Maximum permitted rate */ +}; + +/*! + * \brief Operating Performance Point (OPP). + */ +struct mod_dvfs_opp { + uint64_t voltage; /*!< Power supply voltage in millivolts (mV) */ + uint64_t frequency; /*!< Clock rate in Hertz (Hz) */ +}; + +/*! + * \} + */ + +/*! + * \defgroup GroupDvfsConfig Configuration + * \{ + */ + +/*! + * \brief Domain configuration. + */ +struct mod_dvfs_domain_config { + /*! + * \brief Power supply identifier. + * + * \warning This identifier must refer to an element of the \c psu module. + */ + fwk_id_t psu_id; + + /*! + * \brief Clock identifier. + * + * \warning This identifier must refer to an element of the \c clock module. + */ + fwk_id_t clock_id; + + /*! Worst-case transition latency in microseconds */ + uint16_t latency; + + /*! Sustained operating point index */ + size_t sustained_idx; + + /*! + * \brief Operating points. + * + * \note The frequencies of these operating points must be in ascending + * order. + */ + struct mod_dvfs_opp *opps; +}; + +/*! + * \} + */ + +/*! + * \defgroup GroupDvfsApis APIs + * \{ + */ + +/*! + * \brief Domain API. + */ +struct mod_dvfs_domain_api { + /*! + * \brief Get the current operating point of a domain. + * + * \param domain_id Element identifier of the domain. + * \param [out] opp Current operating point. + */ + int (*get_current_opp)(fwk_id_t domain_id, struct mod_dvfs_opp *opp); + + /*! + * \brief Get the sustained operating point of a domain. + * + * \param domain_id Element identifier of the domain. + * \param [out] opp Sustained operating point. + */ + int (*get_sustained_opp)( + fwk_id_t domain_id, + struct mod_dvfs_opp *opp); + + /*! + * \brief Get an operating point from its index. + * + * \param domain_id Element identifier of the domain. + * \param n Index of the operating point to retrieve. + * \param [out] opp Requested operating point. + */ + int (*get_nth_opp)( + fwk_id_t domain_id, + size_t n, + struct mod_dvfs_opp *opp); + + /*! + * \brief Get the number of operating points of a domain. + * + * \param domain_id Element identifier of the domain. + * \param [out] opp_count Number of operating points. + */ + int (*get_opp_count)(fwk_id_t domain_id, size_t *opp_count); + + /*! + * \brief Get the worst-case transition latency of a domain. + * + * \param domain_id Element identifier of the domain. + * \param [out] latency Worst-case transition latency. + */ + int (*get_latency)(fwk_id_t domain_id, uint16_t *latency); + + /*! + * \brief Set the frequency of a domain. + * + * \param domain_id Element identifier of the domain. + * \param idx Index of the operating point to transition to. + */ + int (*set_frequency)(fwk_id_t domain_id, uint64_t frequency); + + /*! + * \brief Set the frequency of a domain. + * + * \note This function is asynchronous. + * + * \param domain_id Element identifier of the domain. + * \param idx Index of the operating point to transition to. + */ + int (*set_frequency_async)(fwk_id_t domain_id, uint64_t frequency); + + /*! + * \brief Get the frequency of a domain. + * + * \param domain_id Element identifier of the domain. + * \param [out] limits Current frequency limits. + */ + int (*get_frequency_limits)( + fwk_id_t domain_id, + struct mod_dvfs_frequency_limits *limits); + + /*! + * \brief Set the frequency of a domain. + * + * \param domain_id Element identifier of the domain. + * \param limits Pointer to the new limits. + */ + int (*set_frequency_limits)( + fwk_id_t domain_id, + const struct mod_dvfs_frequency_limits *limits); + + /*! + * \brief Set the frequency of a domain. + * + * \note This function is asynchronous. + * + * \param domain_id Element identifier of the domain. + * \param limits Pointer to the new limits. + */ + int (*set_frequency_limits_async)( + fwk_id_t domain_id, + const struct mod_dvfs_frequency_limits *limits); +}; + +/*! + * \} + */ + +/*! + * \defgroup GroupDvfsEvents Events + * \{ + */ + +/*! + * \brief <tt>Set operating point</tt> event response parameters. + */ +struct mod_dvfs_event_params_set_frequency_response { + int status; /*!< Status of the request */ +}; + +/*! + * \brief <tt>Set limits</tt> event response parameters. + */ +struct mod_dvfs_event_params_set_frequency_limits_response { + int status; /*!< Status of the request */ +}; + +/*! + * \} + */ + +/*! + * \defgroup GroupDvfsIds Identifiers + * \{ + */ + +/*! + * \brief API indices. + */ +enum mod_dvfs_api_idx { + MOD_DVFS_API_IDX_DVFS, /*!< API index for mod_dvfs_api_id_dvfs() */ + MOD_DVFS_API_IDX_COUNT /*!< Number of defined APIs */ +}; + +/*! Module API identifier */ +static const fwk_id_t mod_dvfs_api_id_dvfs = + FWK_ID_API_INIT(FWK_MODULE_IDX_DVFS, MOD_DVFS_API_IDX_DVFS); + +/*! + * \brief Event indices. + */ +enum mod_dvfs_event_idx { + /*! Event index for mod_dvfs_event_id_set_frequency() */ + MOD_DVFS_EVENT_IDX_SET_FREQUENCY, + + /*! Event index for mod_dvfs_event_id_set_frequency_limits() */ + MOD_DVFS_EVENT_IDX_SET_FREQUENCY_LIMITS, + + /*! Number of defined events */ + MOD_DVFS_EVENT_IDX_COUNT +}; + +/*! <tt>Set operating point</tt> event identifier */ +static const fwk_id_t mod_dvfs_event_id_set_frequency = + FWK_ID_EVENT_INIT(FWK_MODULE_IDX_DVFS, MOD_DVFS_EVENT_IDX_SET_FREQUENCY); + +/*! <tt>Set frequency limits</tt> event identifier */ +static const fwk_id_t mod_dvfs_event_id_set_frequency_limits = + FWK_ID_EVENT_INIT( + FWK_MODULE_IDX_DVFS, + MOD_DVFS_EVENT_IDX_SET_FREQUENCY_LIMITS); + +/*! + * \} + */ + +/*! + * \} + */ + +#endif /* MOD_DVFS_H */ diff --git a/module/dvfs/src/Makefile b/module/dvfs/src/Makefile new file mode 100644 index 00000000..2f62d737 --- /dev/null +++ b/module/dvfs/src/Makefile @@ -0,0 +1,15 @@ +# +# Arm SCP/MCP Software +# Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +BS_LIB_NAME := dvfs +BS_LIB_SOURCES := \ + mod_dvfs_domain_api.c \ + mod_dvfs_event.c \ + mod_dvfs_module.c \ + mod_dvfs_util.c \ + +include $(BS_DIR)/lib.mk diff --git a/module/dvfs/src/mod_dvfs_domain_api.c b/module/dvfs/src/mod_dvfs_domain_api.c new file mode 100644 index 00000000..4b7d32d5 --- /dev/null +++ b/module/dvfs/src/mod_dvfs_domain_api.c @@ -0,0 +1,265 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <assert.h> +#include <stdbool.h> +#include <fwk_macros.h> +#include <fwk_mm.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <mod_clock.h> +#include <mod_dvfs_private.h> +#include <mod_psu.h> + +static const struct mod_dvfs_opp *get_opp_for_values( + const struct mod_dvfs_domain_ctx *ctx, + uint64_t frequency, + uint64_t voltage) +{ + size_t opp_idx; + const struct mod_dvfs_opp *opp; + + /* A value of zero indicates the parameter should be ignored */ + assert((frequency != 0) || (voltage != 0)); + + for (opp_idx = 0; opp_idx < ctx->opp_count; opp_idx++) { + opp = &ctx->config->opps[opp_idx]; + + /* Only check the frequency if requested */ + if ((frequency != 0) && (opp->frequency != frequency)) + continue; + + /* Only check the voltage if requested */ + if ((voltage != 0) && (opp->voltage != voltage)) + continue; + + return opp; + } + + return NULL; +} + +static bool is_opp_within_limits( + const struct mod_dvfs_opp *opp, + const struct mod_dvfs_frequency_limits *limits) +{ + return (opp->frequency >= limits->minimum) && + (opp->frequency <= limits->maximum); +} + +static bool are_limits_valid( + const struct mod_dvfs_domain_ctx *ctx, + const struct mod_dvfs_frequency_limits *limits) +{ + if (limits->minimum > limits->maximum) + return false; + + if (get_opp_for_values(ctx, limits->minimum, 0) == NULL) + return false; + + if (get_opp_for_values(ctx, limits->maximum, 0) == NULL) + return false; + + return true; +} + +static const struct mod_dvfs_opp *adjust_opp_for_new_limits( + const struct mod_dvfs_domain_ctx *ctx, + const struct mod_dvfs_opp *opp, + const struct mod_dvfs_frequency_limits *limits) +{ + uint64_t needle; + + if (opp->frequency < limits->minimum) + needle = limits->minimum; + else if (opp->frequency > limits->maximum) + needle = limits->maximum; + else { + /* No transition necessary */ + return opp; + } + + return get_opp_for_values(ctx, needle, 0); +} + +static int api_get_current_opp(fwk_id_t domain_id, struct mod_dvfs_opp *opp) +{ + int status; + const struct mod_dvfs_domain_ctx *ctx; + + assert(opp != NULL); + + ctx = __mod_dvfs_get_valid_domain_ctx(domain_id); + if (ctx == NULL) + return FWK_E_PARAM; + + status = __mod_dvfs_get_current_opp(ctx, opp); + if (status != FWK_SUCCESS) + return status; + + return FWK_SUCCESS; +} + +int api_get_sustained_opp(fwk_id_t domain_id, struct mod_dvfs_opp *opp) +{ + const struct mod_dvfs_domain_ctx *ctx; + + assert(opp != NULL); + + ctx = __mod_dvfs_get_valid_domain_ctx(domain_id); + if (ctx == NULL) + return FWK_E_PARAM; + + *opp = ctx->config->opps[ctx->config->sustained_idx]; + + return FWK_SUCCESS; +} + +int api_get_nth_opp(fwk_id_t domain_id, + size_t n, + struct mod_dvfs_opp *opp) +{ + const struct mod_dvfs_domain_ctx *ctx; + + assert(opp != NULL); + + ctx = __mod_dvfs_get_valid_domain_ctx(domain_id); + if (ctx == NULL) + return FWK_E_PARAM; + + if (n >= ctx->opp_count) + return FWK_E_PARAM; + + *opp = ctx->config->opps[n]; + + return FWK_SUCCESS; +} + +static int api_get_opp_count(fwk_id_t domain_id, size_t *opp_count) +{ + const struct mod_dvfs_domain_ctx *ctx; + + assert(opp_count != NULL); + + ctx = __mod_dvfs_get_valid_domain_ctx(domain_id); + if (ctx == NULL) + return FWK_E_PARAM; + + *opp_count = ctx->opp_count; + + return FWK_SUCCESS; +} + +int api_get_latency(fwk_id_t domain_id, uint16_t *latency) +{ + const struct mod_dvfs_domain_ctx *ctx; + + assert(latency != NULL); + + ctx = __mod_dvfs_get_valid_domain_ctx(domain_id); + if (ctx == NULL) + return FWK_E_PARAM; + + *latency = ctx->config->latency; + + return FWK_SUCCESS; +} + +static int api_set_frequency(fwk_id_t domain_id, uint64_t frequency) +{ + int status; + const struct mod_dvfs_domain_ctx *ctx; + const struct mod_dvfs_opp *new_opp; + + ctx = __mod_dvfs_get_valid_domain_ctx(domain_id); + if (ctx == NULL) + return FWK_E_PARAM; + + /* Only accept frequencies that exist in the operating point table */ + new_opp = get_opp_for_values(ctx, frequency, 0); + if (new_opp == NULL) + return FWK_E_RANGE; + + if (!is_opp_within_limits(new_opp, &ctx->frequency_limits)) + return FWK_E_RANGE; + + status = __mod_dvfs_set_opp(ctx, new_opp); + if (status != FWK_SUCCESS) + return status; + + return FWK_SUCCESS; +} + +static int api_set_frequency_async(fwk_id_t domain_id, uint64_t frequency) +{ + return FWK_E_SUPPORT; +} + +int api_get_frequency_limits( + fwk_id_t domain_id, + struct mod_dvfs_frequency_limits *limits) +{ + const struct mod_dvfs_domain_ctx *ctx; + + ctx = __mod_dvfs_get_valid_domain_ctx(domain_id); + if (ctx == NULL) + return FWK_E_PARAM; + + *limits = ctx->frequency_limits; + + return FWK_SUCCESS; +} + +static int api_set_frequency_limits( + fwk_id_t domain_id, + const struct mod_dvfs_frequency_limits *limits) +{ + int status; + struct mod_dvfs_domain_ctx *ctx; + struct mod_dvfs_opp current_opp; + const struct mod_dvfs_opp *new_opp; + + ctx = __mod_dvfs_get_valid_domain_ctx(domain_id); + if (ctx == NULL) + return FWK_E_PARAM; + + if (!are_limits_valid(ctx, limits)) + return FWK_E_PARAM; + + status = __mod_dvfs_get_current_opp(ctx, ¤t_opp); + if (status != FWK_SUCCESS) + return status; + + new_opp = adjust_opp_for_new_limits(ctx, ¤t_opp, limits); + status = __mod_dvfs_set_opp(ctx, new_opp); + if (status != FWK_SUCCESS) + return status; + + ctx->frequency_limits = *limits; + + return FWK_SUCCESS; +} + +static int api_set_frequency_limits_async( + fwk_id_t domain_id, + const struct mod_dvfs_frequency_limits *limits) +{ + return FWK_E_SUPPORT; +} + +const struct mod_dvfs_domain_api __mod_dvfs_domain_api = { + .get_current_opp = api_get_current_opp, + .get_sustained_opp = api_get_sustained_opp, + .get_nth_opp = api_get_nth_opp, + .get_opp_count = api_get_opp_count, + .get_latency = api_get_latency, + .set_frequency = api_set_frequency, + .set_frequency_async = api_set_frequency_async, + .get_frequency_limits = api_get_frequency_limits, + .set_frequency_limits = api_set_frequency_limits, + .set_frequency_limits_async = api_set_frequency_limits_async, +}; diff --git a/module/dvfs/src/mod_dvfs_domain_api_private.h b/module/dvfs/src/mod_dvfs_domain_api_private.h new file mode 100644 index 00000000..5efd8867 --- /dev/null +++ b/module/dvfs/src/mod_dvfs_domain_api_private.h @@ -0,0 +1,16 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef MOD_DVFS_DOMAIN_API_PRIVATE_H +#define MOD_DVFS_DOMAIN_API_PRIVATE_H + +#include <mod_dvfs.h> + +/* Module API implementation */ +extern const struct mod_dvfs_domain_api __mod_dvfs_domain_api; + +#endif /* MOD_DVFS_DOMAIN_API_PRIVATE_H */ diff --git a/module/dvfs/src/mod_dvfs_event.c b/module/dvfs/src/mod_dvfs_event.c new file mode 100644 index 00000000..ff702ef1 --- /dev/null +++ b/module/dvfs/src/mod_dvfs_event.c @@ -0,0 +1,47 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <fwk_macros.h> +#include <mod_dvfs_private.h> + +static int event_set_opp( + const struct fwk_event *event, + struct fwk_event *response) +{ + return FWK_E_SUPPORT; +} + +static int event_set_frequency_limits( + const struct fwk_event *event, + struct fwk_event *response) +{ + return FWK_E_SUPPORT; +} + +int __mod_dvfs_process_event( + const struct fwk_event *event, + struct fwk_event *response) +{ + typedef int (*handler_t)( + const struct fwk_event *event, + struct fwk_event *response); + + static const handler_t handlers[] = { + [MOD_DVFS_EVENT_IDX_SET_FREQUENCY] = event_set_opp, + [MOD_DVFS_EVENT_IDX_SET_FREQUENCY_LIMITS] = event_set_frequency_limits, + }; + + handler_t handler; + + /* Ensure we have a handler implemented for this event */ + handler = handlers[fwk_id_get_event_idx(event->id)]; + if (handler == NULL) + return FWK_E_PARAM; + + /* Delegate event handling to the relevant handler */ + return handler(event, response); +} diff --git a/module/dvfs/src/mod_dvfs_event_private.h b/module/dvfs/src/mod_dvfs_event_private.h new file mode 100644 index 00000000..b2ff90b0 --- /dev/null +++ b/module/dvfs/src/mod_dvfs_event_private.h @@ -0,0 +1,18 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef MOD_DVFS_EVENT_PRIVATE_H +#define MOD_DVFS_EVENT_PRIVATE_H + +#include <fwk_event.h> + +/* Event handler */ +int __mod_dvfs_process_event( + const struct fwk_event *event, + struct fwk_event *response); + +#endif /* MOD_DVFS_EVENT_PRIVATE_H */ diff --git a/module/dvfs/src/mod_dvfs_module.c b/module/dvfs/src/mod_dvfs_module.c new file mode 100644 index 00000000..2a1eaae5 --- /dev/null +++ b/module/dvfs/src/mod_dvfs_module.c @@ -0,0 +1,240 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <assert.h> +#include <fwk_macros.h> +#include <fwk_mm.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <fwk_notification.h> +#include <mod_clock.h> +#include <mod_dvfs_private.h> +#include <mod_power_domain.h> +#include <mod_psu.h> + +static struct mod_dvfs_domain_ctx (*domain_ctx)[]; + +static int count_opps(const struct mod_dvfs_opp *opps) +{ + const struct mod_dvfs_opp *opp = &opps[0]; + + while ((opp->voltage != 0) && (opp->frequency != 0)) + opp++; + + return opp - &opps[0]; +} + +static struct mod_dvfs_domain_ctx *get_domain_ctx(fwk_id_t domain_id) +{ + unsigned int element_idx = fwk_id_get_element_idx(domain_id); + + return &(*domain_ctx)[element_idx]; +} + +static int dvfs_init( + fwk_id_t module_id, + unsigned int element_count, + const void *data) +{ + domain_ctx = fwk_mm_calloc( + element_count, + sizeof((*domain_ctx)[0])); + if (domain_ctx == NULL) + return FWK_E_NOMEM; + + return FWK_SUCCESS; +} + +static int dvfs_element_init( + fwk_id_t domain_id, + unsigned int sub_element_count, + const void *data) +{ + struct mod_dvfs_domain_ctx *ctx = get_domain_ctx(domain_id); + + assert(sub_element_count == 0); + + /* Initialize the configuration */ + ctx->config = data; + assert(ctx->config->opps != NULL); + + /* Initialize the context */ + ctx->opp_count = count_opps(ctx->config->opps); + assert(ctx->opp_count > 0); + + /* Frequency limits default to the minimum and maximum available */ + ctx->frequency_limits = (struct mod_dvfs_frequency_limits) { + .minimum = ctx->config->opps[0].frequency, + .maximum = ctx->config->opps[ctx->opp_count - 1].frequency, + }; + + ctx->suspended_opp = ctx->config->opps[ctx->config->sustained_idx]; + + return FWK_SUCCESS; +} + +static int dvfs_bind_element(fwk_id_t domain_id, unsigned int round) +{ + int status; + const struct mod_dvfs_domain_ctx *ctx = + get_domain_ctx(domain_id); + + /* Only handle the first round */ + if (round > 0) + return FWK_SUCCESS; + + /* Bind to the power supply module */ + status = fwk_module_bind( + ctx->config->psu_id, + mod_psu_api_id_psu_device, + &ctx->apis.psu); + if (status != FWK_SUCCESS) + return FWK_E_PANIC; + + /* Bind to the clock module */ + status = fwk_module_bind( + ctx->config->clock_id, + FWK_ID_API(FWK_MODULE_IDX_CLOCK, 0), + &ctx->apis.clock); + if (status != FWK_SUCCESS) + return FWK_E_PANIC; + + return FWK_SUCCESS; +} + +static int dvfs_bind(fwk_id_t id, unsigned int round) +{ + /* We only need to handle binding our elements */ + if (fwk_id_is_type(id, FWK_ID_TYPE_ELEMENT)) + return dvfs_bind_element(id, round); + + return FWK_SUCCESS; +} + +static int dvfs_process_bind_request_module( + fwk_id_t source_id, + fwk_id_t api_id, + const void **api) +{ + /* Only expose the module API */ + if (!fwk_id_is_equal(api_id, mod_dvfs_api_id_dvfs)) + return FWK_E_PARAM; + + /* We don't do any permissions management */ + *api = &__mod_dvfs_domain_api; + + return FWK_SUCCESS; +} + +static int dvfs_process_bind_request( + fwk_id_t source_id, + fwk_id_t target_id, + fwk_id_t api_id, + const void **api) +{ + /* Only allow binding to the module */ + if (!fwk_id_is_equal(target_id, fwk_module_id_dvfs)) + return FWK_E_PARAM; + + return dvfs_process_bind_request_module(source_id, api_id, api); +} + +static int dvfs_start(fwk_id_t id) +{ + int status; + const struct mod_dvfs_domain_ctx *ctx; + + if (!fwk_id_is_type(id, FWK_ID_TYPE_ELEMENT)) + return FWK_SUCCESS; + + ctx = get_domain_ctx(id); + + /* Register for clock state notifications */ + status = fwk_notification_subscribe( + mod_clock_notification_id_state_changed, + ctx->config->clock_id, + id); + if (status != FWK_SUCCESS) + return status; + + return fwk_notification_subscribe( + mod_clock_notification_id_state_change_pending, + ctx->config->clock_id, + id); +} + +static int dvfs_notify_system_state_transition_suspend(fwk_id_t domain_id) +{ + struct mod_dvfs_domain_ctx *ctx = + get_domain_ctx(domain_id); + + return __mod_dvfs_get_current_opp(ctx, &ctx->suspended_opp); +} + +static int dvfs_notify_system_state_transition_resume(fwk_id_t domain_id) +{ + const struct mod_dvfs_domain_ctx *ctx = + get_domain_ctx(domain_id); + + return __mod_dvfs_set_opp(ctx, &ctx->suspended_opp); +} + +static int dvfs_process_notification( + const struct fwk_event *event, + struct fwk_event *resp_event) +{ + struct clock_notification_params *params; + struct clock_state_change_pending_resp_params *resp_params; + + assert( + fwk_id_is_equal( + event->id, + mod_clock_notification_id_state_changed) || + fwk_id_is_equal( + event->id, + mod_clock_notification_id_state_change_pending)); + assert(fwk_id_is_type(event->target_id, FWK_ID_TYPE_ELEMENT)); + + params = (struct clock_notification_params *)event->params; + + if (fwk_id_is_equal(event->id, mod_clock_notification_id_state_changed)) { + if (params->new_state == MOD_CLOCK_STATE_RUNNING) + return dvfs_notify_system_state_transition_resume(event->target_id); + } else if (params->new_state == MOD_CLOCK_STATE_STOPPED) { + /* DVFS has received the pending change notification */ + resp_params = + (struct clock_state_change_pending_resp_params *)resp_event->params; + resp_params->status = FWK_SUCCESS; + + return dvfs_notify_system_state_transition_suspend(event->target_id); + } + + return FWK_SUCCESS; +} + +struct mod_dvfs_domain_ctx *__mod_dvfs_get_valid_domain_ctx(fwk_id_t domain_id) +{ + if (fwk_module_check_call(domain_id) != FWK_SUCCESS) + return NULL; + + return get_domain_ctx(domain_id); +} + +/* Module description */ +const struct fwk_module module_dvfs = { + .name = "DVFS", + .type = FWK_MODULE_TYPE_HAL, + .init = dvfs_init, + .element_init = dvfs_element_init, + .bind = dvfs_bind, + .process_bind_request = dvfs_process_bind_request, + .process_event = __mod_dvfs_process_event, + .start = dvfs_start, + .process_notification = dvfs_process_notification, + .api_count = MOD_DVFS_API_IDX_COUNT, + .event_count = MOD_DVFS_EVENT_IDX_COUNT, +}; diff --git a/module/dvfs/src/mod_dvfs_module_private.h b/module/dvfs/src/mod_dvfs_module_private.h new file mode 100644 index 00000000..5c441c0c --- /dev/null +++ b/module/dvfs/src/mod_dvfs_module_private.h @@ -0,0 +1,40 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef MOD_DVFS_MODULE_PRIVATE_H +#define MOD_DVFS_MODULE_PRIVATE_H + +#include <fwk_id.h> +#include <mod_clock.h> +#include <mod_psu.h> + +/* Domain context */ +struct mod_dvfs_domain_ctx { + /* Domain configuration */ + const struct mod_dvfs_domain_config *config; + + struct { + /* Power supply API */ + const struct mod_psu_device_api *psu; + + /* Clock API */ + const struct mod_clock_api *clock; + } apis; + + /* Number of operating points */ + size_t opp_count; + + /* Operating point prior to domain suspension */ + struct mod_dvfs_opp suspended_opp; + + /* Current operating point limits */ + struct mod_dvfs_frequency_limits frequency_limits; +}; + +struct mod_dvfs_domain_ctx *__mod_dvfs_get_valid_domain_ctx(fwk_id_t domain_id); + +#endif /* MOD_DVFS_MODULE_PRIVATE_H */ diff --git a/module/dvfs/src/mod_dvfs_private.h b/module/dvfs/src/mod_dvfs_private.h new file mode 100644 index 00000000..1db8b2c8 --- /dev/null +++ b/module/dvfs/src/mod_dvfs_private.h @@ -0,0 +1,16 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef MOD_DVFS_PRIVATE_H +#define MOD_DVFS_PRIVATE_H + +#include <mod_dvfs_event_private.h> +#include <mod_dvfs_domain_api_private.h> +#include <mod_dvfs_module_private.h> +#include <mod_dvfs_util_private.h> + +#endif /* MOD_DVFS_PRIVATE_H */ diff --git a/module/dvfs/src/mod_dvfs_util.c b/module/dvfs/src/mod_dvfs_util.c new file mode 100644 index 00000000..e073a766 --- /dev/null +++ b/module/dvfs/src/mod_dvfs_util.c @@ -0,0 +1,71 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <fwk_module.h> +#include <mod_dvfs_private.h> + +int __mod_dvfs_set_opp( + const struct mod_dvfs_domain_ctx *ctx, + const struct mod_dvfs_opp *new_opp) +{ + int status; + struct mod_dvfs_opp current_opp; + + status = __mod_dvfs_get_current_opp(ctx, ¤t_opp); + if (status != FWK_SUCCESS) + return status; + + if (new_opp->voltage > current_opp.voltage) { + /* Raise the voltage before raising the frequency */ + status = ctx->apis.psu->set_voltage( + ctx->config->psu_id, + new_opp->voltage); + if (status != FWK_SUCCESS) + return FWK_E_DEVICE; + } + + if (new_opp->frequency != current_opp.frequency) { + status = ctx->apis.clock->set_rate( + ctx->config->clock_id, + new_opp->frequency, + MOD_CLOCK_ROUND_MODE_NONE); + if (status != FWK_SUCCESS) + return FWK_E_DEVICE; + } + + if (new_opp->voltage < current_opp.voltage) { + /* Lower the voltage after lowering the frequency */ + status = ctx->apis.psu->set_voltage( + ctx->config->psu_id, + new_opp->voltage); + if (status != FWK_SUCCESS) + return FWK_E_DEVICE; + } + + return FWK_SUCCESS; +} + +int __mod_dvfs_get_current_opp( + const struct mod_dvfs_domain_ctx *ctx, + struct mod_dvfs_opp *opp) +{ + int status; + + status = ctx->apis.clock->get_rate( + ctx->config->clock_id, + &opp->frequency); + if (status != FWK_SUCCESS) + return FWK_E_DEVICE; + + status = ctx->apis.psu->get_voltage( + ctx->config->psu_id, + &opp->voltage); + if (status != FWK_SUCCESS) + return FWK_E_DEVICE; + + return FWK_SUCCESS; +} diff --git a/module/dvfs/src/mod_dvfs_util_private.h b/module/dvfs/src/mod_dvfs_util_private.h new file mode 100644 index 00000000..9b530a20 --- /dev/null +++ b/module/dvfs/src/mod_dvfs_util_private.h @@ -0,0 +1,22 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef MOD_DVFS_UTIL_PRIVATE_H +#define MOD_DVFS_UTIL_PRIVATE_H + +#include <mod_dvfs.h> +#include <mod_dvfs_domain_api_private.h> + +int __mod_dvfs_set_opp( + const struct mod_dvfs_domain_ctx *ctx, + const struct mod_dvfs_opp *new_opp); + +int __mod_dvfs_get_current_opp( + const struct mod_dvfs_domain_ctx *ctx, + struct mod_dvfs_opp *opp); + +#endif /* MOD_DVFS_PRIVATE_H */ diff --git a/module/gtimer/include/mod_gtimer.h b/module/gtimer/include/mod_gtimer.h new file mode 100644 index 00000000..69215e2e --- /dev/null +++ b/module/gtimer/include/mod_gtimer.h @@ -0,0 +1,58 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * Generic Timer device driver module and definitions. + */ + +#ifndef MOD_GTIMER_H +#define MOD_GTIMER_H + +#include <stdint.h> +#include <fwk_id.h> + +/*! + * \addtogroup GroupModules Modules + * @{ + */ + +/*! + * \defgroup GroupModuleGtimer Generic Timer Driver + * + * \details Driver module for the generic timer. + * + * @{ + */ + +/*! + * \brief Generic timer device descriptor + */ +struct mod_gtimer_dev_config { + /*! Address of the device's timer register */ + uintptr_t hw_timer; + + /*! Address of the device's counter register */ + uintptr_t hw_counter; + + /*! Address of the device's control register */ + uintptr_t control; + + /*! The frequency in Hertz that the timer ticks at */ + const uint32_t frequency; + + /*! Identifier of the clock that this device depends on */ + fwk_id_t clock_id; +}; + +/*! + * @} + */ + +/*! + * @} + */ + +#endif /* MOD_GTIMER_H */ diff --git a/module/gtimer/src/Makefile b/module/gtimer/src/Makefile new file mode 100644 index 00000000..39c8d591 --- /dev/null +++ b/module/gtimer/src/Makefile @@ -0,0 +1,11 @@ +# +# Arm SCP/MCP Software +# Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +BS_LIB_NAME := Gtimer +BS_LIB_SOURCES = mod_gtimer.c + +include $(BS_DIR)/lib.mk diff --git a/module/gtimer/src/gtimer_reg.h b/module/gtimer/src/gtimer_reg.h new file mode 100644 index 00000000..29427565 --- /dev/null +++ b/module/gtimer/src/gtimer_reg.h @@ -0,0 +1,90 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef GTIMER_REG_H +#define GTIMER_REG_H + +#include <stdint.h> +#include <fwk_macros.h> + +/*! + * \brief Counter registers (CNTCONTROL) + */ +struct cntcontrol_reg { + FWK_RW uint32_t CR; + FWK_R uint32_t SR; + FWK_RW uint32_t CVL; + FWK_RW uint32_t CVH; + uint32_t RESERVED0[4]; + FWK_RW uint32_t FID0; + uint8_t RESERVED1[0xC0 - 0x24]; + FWK_RW uint32_t SCR; /* CSS: Synchronization Control Register */ + FWK_R uint32_t SVL; /* CSS: Synchronized Counter Lower Value Register */ + FWK_R uint32_t SVU; /* CSS: Synchronized Counter Upper Value Register */ + uint8_t RESERVED2[0xFD0 - 0xCC]; + FWK_R uint32_t PID[11]; +}; + +#define CNTCONTROL_CR_EN UINT32_C(0x00000001) +#define CNTCONTROL_CR_HDBG UINT32_C(0x00000002) +#define CNTCONTROL_CR_FCREQ UINT32_C(0x00000100) + +#define CNTCONTROL_SCR_ENSYNC UINT32_C(0x00000001) +#define CNTCONTROL_SCR_ENSYNC_DIRECT UINT32_C(0x00000000) +#define CNTCONTROL_SCR_ENSYNC_DELAY UINT32_C(0x00000001) + +/*! + * \brief Counter registers (CNTCTL) + */ +struct cntctl_reg { + FWK_RW uint32_t FRQ; + FWK_RW uint32_t NSAR; + FWK_R uint32_t TTIDR; + uint8_t RESERVED0[0x40 - 0x0C]; + FWK_RW uint32_t ACR; + uint8_t RESERVED1[0xFD0 - 0x44]; + FWK_R uint32_t PID[11]; +}; + +#define CNTCTL_ACR UINT32_C(0x0000003f) +#define CNTCTL_ACR_RPCT UINT32_C(0x00000001) +#define CNTCTL_ACR_RVCT UINT32_C(0x00000002) +#define CNTCTL_ACR_RFRQ UINT32_C(0x00000004) +#define CNTCTL_ACR_RVOFF UINT32_C(0x00000008) +#define CNTCTL_ACR_RWVT UINT32_C(0x00000010) +#define CNTCTL_ACR_RWPT UINT32_C(0x00000020) + +/*! + * \brief Counter registers (CNTBASE) + */ +struct cntbase_reg { + FWK_R uint32_t PCTL; + FWK_R uint32_t PCTH; + FWK_R uint32_t VCTL; + FWK_R uint32_t VCTH; + FWK_R uint32_t FRQ; + FWK_RW uint32_t PL0ACR; + FWK_R uint32_t OFFL; + FWK_R uint32_t OFFH; + FWK_RW uint32_t P_CVALL; + FWK_RW uint32_t P_CVALH; + FWK_RW uint32_t P_TVAL; + FWK_RW uint32_t P_CTL; + FWK_RW uint32_t V_CVALL; + FWK_RW uint32_t V_CVALH; + FWK_RW uint32_t V_TVAL; + FWK_RW uint32_t V_CTL; + uint8_t RESERVED0[0xFD0 - 0x40]; + FWK_R uint32_t PID[11]; +}; + +#define CNTBASE_P_CTL UINT32_C(0x00000007) +#define CNTBASE_P_CTL_ENABLE UINT32_C(0x00000001) +#define CNTBASE_P_CTL_IMASK UINT32_C(0x00000002) +#define CNTBASE_P_CTL_ISTATUS UINT32_C(0x00000004) + +#endif /* GTIMER_REG_H */ diff --git a/module/gtimer/src/mod_gtimer.c b/module/gtimer/src/mod_gtimer.c new file mode 100644 index 00000000..74845169 --- /dev/null +++ b/module/gtimer/src/mod_gtimer.c @@ -0,0 +1,295 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <assert.h> +#include <stdint.h> +#include <fwk_errno.h> +#include <fwk_id.h> +#include <fwk_mm.h> +#include <fwk_module.h> +#include <fwk_notification.h> +#include <gtimer_reg.h> +#include <mod_clock.h> +#include <mod_gtimer.h> +#include <mod_power_domain.h> +#include <mod_timer.h> + +#define GTIMER_FREQUENCY_MIN_HZ UINT32_C(1) +#define GTIMER_FREQUENCY_MAX_HZ UINT32_C(1000000000) + +/* Device content */ +struct dev_ctx { + struct cntbase_reg *hw_timer; + struct cntctl_reg *hw_counter; + struct cntcontrol_reg *control; + const struct mod_gtimer_dev_config *config; +}; + +/* Table of device context structures */ +static struct dev_ctx *ctx_table; + +/* + * Functions fulfilling the Timer module's driver interface + */ + +static int enable(fwk_id_t dev_id) +{ + struct dev_ctx *ctx; + int status; + + status = fwk_module_check_call(dev_id); + if (status != FWK_SUCCESS) + return status; + + ctx = ctx_table + fwk_id_get_element_idx(dev_id); + + ctx->hw_timer->P_CTL &= ~CNTBASE_P_CTL_IMASK; + ctx->hw_timer->P_CTL |= CNTBASE_P_CTL_ENABLE; + + return FWK_SUCCESS; +} + +static int disable(fwk_id_t dev_id) +{ + struct dev_ctx *ctx; + int status; + + status = fwk_module_check_call(dev_id); + if (status != FWK_SUCCESS) + return status; + + ctx = ctx_table + fwk_id_get_element_idx(dev_id); + + ctx->hw_timer->P_CTL |= CNTBASE_P_CTL_IMASK; + ctx->hw_timer->P_CTL &= ~CNTBASE_P_CTL_ENABLE; + + return FWK_SUCCESS; +} + +static int set_timer(fwk_id_t dev_id, uint64_t timestamp) +{ + struct dev_ctx *ctx; + int status; + + status = fwk_module_check_call(dev_id); + if (status != FWK_SUCCESS) + return status; + + ctx = ctx_table + fwk_id_get_element_idx(dev_id); + + ctx->hw_timer->P_CVALL = timestamp & 0xFFFFFFFF; + ctx->hw_timer->P_CVALH = timestamp >> 32; + + return FWK_SUCCESS; +} + +static int get_timer(fwk_id_t dev_id, uint64_t *timestamp) +{ + struct dev_ctx *ctx; + int status; + + status = fwk_module_check_call(dev_id); + if (status != FWK_SUCCESS) + return status; + + ctx = ctx_table + fwk_id_get_element_idx(dev_id); + + struct { + uint32_t low; + uint32_t high; + } value; + + /* Read 64-bit timer value */ + value.low = ctx->hw_timer->P_CVALL; + value.high = ctx->hw_timer->P_CVALH; + + *timestamp = *(uint64_t *)&value; + return FWK_SUCCESS; +} + +static int get_counter(fwk_id_t dev_id, uint64_t *value) +{ + struct dev_ctx *ctx; + int status; + + status = fwk_module_check_call(dev_id); + if (status != FWK_SUCCESS) + return status; + + ctx = ctx_table + fwk_id_get_element_idx(dev_id); + + struct { + uint32_t low; + uint32_t high; + } counter; + + /* Read 64-bit counter value */ + do { + counter.high = ctx->hw_timer->PCTH; + counter.low = ctx->hw_timer->PCTL; + } while (counter.high != ctx->hw_timer->PCTH); + + *value = *(uint64_t *)&counter; + return FWK_SUCCESS; +} + +static int get_frequency(fwk_id_t dev_id, uint32_t *frequency) +{ + int status; + struct dev_ctx *ctx; + + if (frequency == NULL) + return FWK_E_PARAM; + + status = fwk_module_check_call(dev_id); + if (status != FWK_SUCCESS) + return status; + + ctx = ctx_table + fwk_id_get_element_idx(dev_id); + + *frequency = ctx->config->frequency; + + return FWK_SUCCESS; +} + +static const struct mod_timer_driver_api module_api = { + .name = "Generic Timer Driver", + .enable = enable, + .disable = disable, + .set_timer = set_timer, + .get_timer = get_timer, + .get_counter = get_counter, + .get_frequency = get_frequency, +}; + +/* + * Functions fulfilling the framework's module interface + */ + +static int gtimer_init(fwk_id_t module_id, + unsigned int element_count, + const void *data) +{ + ctx_table = fwk_mm_alloc(element_count, sizeof(struct dev_ctx)); + + if (ctx_table == NULL) + return FWK_E_NOMEM; + + return FWK_SUCCESS; +} + +static int gtimer_device_init(fwk_id_t element_id, unsigned int unused, + const void *data) +{ + struct dev_ctx *ctx; + + ctx = ctx_table + fwk_id_get_element_idx(element_id); + + ctx->config = data; + if (ctx->config->hw_timer == 0 || + ctx->config->hw_counter == 0 || + ctx->config->control == 0 || + ctx->config->frequency < GTIMER_FREQUENCY_MIN_HZ || + ctx->config->frequency > GTIMER_FREQUENCY_MAX_HZ) { + + return FWK_E_DEVICE; + } + + ctx->hw_timer = (struct cntbase_reg *)ctx->config->hw_timer; + ctx->hw_counter = (struct cntctl_reg *)ctx->config->hw_counter; + ctx->control = (struct cntcontrol_reg *)ctx->config->control; + + disable(element_id); + + ctx->hw_counter->ACR = CNTCTL_ACR_RPCT | + CNTCTL_ACR_RVCT | + CNTCTL_ACR_RFRQ | + CNTCTL_ACR_RVOFF| + CNTCTL_ACR_RWPT; + ctx->hw_counter->FRQ = ctx->config->frequency; + + return FWK_SUCCESS; +} + +static int gtimer_process_bind_request(fwk_id_t requester_id, + fwk_id_t id, + fwk_id_t api_type, + const void **api) +{ + /* No binding to the module */ + if (fwk_module_is_valid_module_id(id)) + return FWK_E_ACCESS; + + *api = &module_api; + + return FWK_SUCCESS; +} + +static void gtimer_control_init(struct dev_ctx *ctx) +{ + /* Set primary counter update frequency and enable counter. */ + ctx->control->CR &= ~CNTCONTROL_CR_EN; + ctx->control->FID0 = ctx->config->frequency; + ctx->control->CR |= CNTCONTROL_CR_FCREQ | CNTCONTROL_CR_EN; +} + +static int gtimer_start(fwk_id_t id) +{ + struct dev_ctx *ctx; + + if (!fwk_id_is_type(id, FWK_ID_TYPE_ELEMENT)) + return FWK_SUCCESS; + + ctx = ctx_table + fwk_id_get_element_idx(id); + + if (!fwk_id_is_type(ctx->config->clock_id, FWK_ID_TYPE_NONE)) { + /* Register for clock state notifications */ + return fwk_notification_subscribe( + mod_clock_notification_id_state_changed, + ctx->config->clock_id, + id); + } else + gtimer_control_init(ctx); + + return FWK_SUCCESS; +} + +static int gtimer_process_notification( + const struct fwk_event *event, + struct fwk_event *resp_event) +{ + struct clock_notification_params *params; + struct dev_ctx *ctx; + + assert(fwk_id_is_equal(event->id, mod_clock_notification_id_state_changed)); + assert(fwk_id_is_type(event->target_id, FWK_ID_TYPE_ELEMENT)); + + params = (struct clock_notification_params *)event->params; + + if (params->new_state == MOD_CLOCK_STATE_RUNNING) { + ctx = ctx_table + fwk_id_get_element_idx(event->target_id); + gtimer_control_init(ctx); + } + + return FWK_SUCCESS; +} + + +/* + * Module descriptor + */ +const struct fwk_module module_gtimer = { + .name = "Generic Timer Driver", + .api_count = 1, + .event_count = 0, + .type = FWK_MODULE_TYPE_DRIVER, + .init = gtimer_init, + .element_init = gtimer_device_init, + .start = gtimer_start, + .process_bind_request = gtimer_process_bind_request, + .process_notification = gtimer_process_notification, +}; diff --git a/module/mhu/include/internal/mhu.h b/module/mhu/include/internal/mhu.h new file mode 100644 index 00000000..b77a027b --- /dev/null +++ b/module/mhu/include/internal/mhu.h @@ -0,0 +1,29 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef MHU_H +#define MHU_H + +#include <stdint.h> +#include <fwk_macros.h> +#include <mod_mhu.h> + +/*! + * \brief MHU Register Definitions + */ +struct mhu_reg { + /*! Status register */ + FWK_R uint32_t STAT; + uint32_t RESERVED0; + /*! Set register (sets the value of STAT) */ + FWK_W uint32_t SET; + uint32_t RESERVED1; + /*! Clear register (clears STAT) */ + FWK_W uint32_t CLEAR; +}; + +#endif /* MHU_H */ diff --git a/module/mhu/include/mod_mhu.h b/module/mhu/include/mod_mhu.h new file mode 100644 index 00000000..feec6596 --- /dev/null +++ b/module/mhu/include/mod_mhu.h @@ -0,0 +1,53 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * Message Handling Unit (MHU) Device Driver. + */ + +#ifndef MOD_MHU_H +#define MOD_MHU_H + +#include <stdint.h> +#include <fwk_macros.h> + +/*! + * \addtogroup GroupModules Modules + * @{ + */ + +/*! + * \defgroup GroupMHU Message Handling Unit (MHU) Driver + * @{ + */ + +/*! + * \brief MHU device + * + * \details Abstract representation of a bidirectional MHU device that consists + * of a single receive interrupt line and a pair of register sets, one for + * each direction of communication. + */ +struct mod_mhu_device_config { + /*! IRQ number of the receive interrupt line */ + unsigned int irq; + + /*! Base address of the registers of the incoming MHU */ + uintptr_t in; + + /*! Base address of the registers of the outgoing MHU */ + uintptr_t out; +}; + +/*! + * @} + */ + +/*! + * @} + */ + +#endif /* MOD_MHU_H */ diff --git a/module/mhu/src/Makefile b/module/mhu/src/Makefile new file mode 100644 index 00000000..959a1cf6 --- /dev/null +++ b/module/mhu/src/Makefile @@ -0,0 +1,11 @@ +# +# Arm SCP/MCP Software +# Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +BS_LIB_NAME := MHU +BS_LIB_SOURCES := mod_mhu.c + +include $(BS_DIR)/lib.mk diff --git a/module/mhu/src/mod_mhu.c b/module/mhu/src/mod_mhu.c new file mode 100644 index 00000000..761d719a --- /dev/null +++ b/module/mhu/src/mod_mhu.c @@ -0,0 +1,256 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * Message Handling Unit (MHU) Device Driver. + */ + +#include <stddef.h> +#include <stdint.h> +#include <fwk_errno.h> +#include <fwk_id.h> +#include <fwk_interrupt.h> +#include <fwk_mm.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <internal/mhu.h> +#include <mod_smt.h> + +/* + * Maximum number of slots per MHU device. The maximum number of slots is 31 and + * not 32 because bit [31] in the MHU STAT register is reserved in secure MHUs + * for indicating that a non-secure access attempt occurred. This reservation + * also applies to non-secure MHUs for consistency, though the bit is unused. + */ +#define MHU_SLOT_COUNT_MAX 31 + +struct mhu_smt_channel { + fwk_id_t id; + struct mod_smt_driver_input_api *api; +}; + +/* MHU device context */ +struct mhu_device_ctx { + /* Pointer to the device configuration */ + const struct mod_mhu_device_config *config; + + /* Number of slots (represented by sub-elements) */ + unsigned int slot_count; + + /* Mask of slots that are bound to an SMT channel */ + uint32_t bound_slots; + + /* Table of SMT channels bound to the device */ + struct mhu_smt_channel *smt_channel_table; +}; + +/* MHU context */ +struct mhu_ctx { + /* Table of device contexts */ + struct mhu_device_ctx *device_ctx_table; + + /* Number of devices in the device context table*/ + unsigned int device_count; +}; + +static struct mhu_ctx mhu_ctx; + +static void mhu_isr(void) +{ + int status; + unsigned int interrupt; + unsigned int device_idx; + struct mhu_device_ctx *device_ctx; + struct mhu_reg *reg; + unsigned int slot; + struct mhu_smt_channel *smt_channel; + + status = fwk_interrupt_get_current(&interrupt); + if (status != FWK_SUCCESS) + return; + + for (device_idx = 0; device_idx < mhu_ctx.device_count; device_idx++) { + device_ctx = &mhu_ctx.device_ctx_table[device_idx]; + if (device_ctx->config->irq == interrupt) + break; + } + + if (device_idx >= mhu_ctx.device_count) + return; + + reg = (struct mhu_reg *)device_ctx->config->in; + + /* Loop over all the slots */ + while (reg->STAT != 0) { + slot = __builtin_ctz(reg->STAT); + + /* + * If the slot is bound to an SMT channel, signal the message to the + * SMT channel. + */ + if (device_ctx->bound_slots & (1 << slot)) { + smt_channel = &device_ctx->smt_channel_table[slot]; + smt_channel->api->signal_message(smt_channel->id); + } + + /* Acknowledge the interrupt */ + reg->CLEAR = 1 << slot; + } +} + +/* + * SMT module driver API + */ + +static int raise_interrupt(fwk_id_t slot_id) +{ + int status; + struct mhu_device_ctx *device_ctx; + unsigned int slot; + struct mhu_reg *reg; + + status = fwk_module_check_call(slot_id); + if (status != FWK_SUCCESS) + return status; + + device_ctx = &mhu_ctx.device_ctx_table[fwk_id_get_element_idx(slot_id)]; + slot = fwk_id_get_sub_element_idx(slot_id); + reg = (struct mhu_reg *)device_ctx->config->out; + + reg->SET |= (1 << slot); + + return FWK_SUCCESS; +} + +const struct mod_smt_driver_api mhu_mod_smt_driver_api = { + .raise_interrupt = raise_interrupt, +}; + +/* + * Framework handlers + */ + +static int mhu_init(fwk_id_t module_id, unsigned int device_count, + const void *unused) +{ + if (device_count == 0) + return FWK_E_PARAM; + + mhu_ctx.device_ctx_table = fwk_mm_calloc(device_count, + sizeof(mhu_ctx.device_ctx_table[0])); + if (mhu_ctx.device_ctx_table == NULL) + return FWK_E_NOMEM; + + mhu_ctx.device_count = device_count; + + return FWK_SUCCESS; +} + +static int mhu_device_init(fwk_id_t device_id, unsigned int slot_count, + const void *data) +{ + struct mod_mhu_device_config *config = (struct mod_mhu_device_config *)data; + struct mhu_device_ctx *device_ctx; + + if ((config->in == 0) || (config->out == 0)) + return FWK_E_PARAM; + + device_ctx = &mhu_ctx.device_ctx_table[fwk_id_get_element_idx(device_id)]; + + device_ctx->smt_channel_table = fwk_mm_calloc(slot_count, + sizeof(device_ctx->smt_channel_table[0])); + if (device_ctx->smt_channel_table == NULL) + return FWK_E_NOMEM; + + device_ctx->config = config; + device_ctx->slot_count = slot_count; + + return FWK_SUCCESS; +} + +static int mhu_bind(fwk_id_t id, unsigned int round) +{ + int status; + struct mhu_device_ctx *device_ctx; + unsigned int slot; + struct mhu_smt_channel *smt_channel; + + if ((round == 1) && fwk_id_is_type(id, FWK_ID_TYPE_ELEMENT)) { + device_ctx = &mhu_ctx.device_ctx_table[fwk_id_get_element_idx(id)]; + + for (slot = 0; slot < MHU_SLOT_COUNT_MAX; slot++) { + if (!(device_ctx->bound_slots & (1 << slot))) + continue; + + smt_channel = &device_ctx->smt_channel_table[slot]; + + status = fwk_module_bind(smt_channel->id, + FWK_ID_API(FWK_MODULE_IDX_SMT, MOD_SMT_API_IDX_DRIVER_INPUT), + &smt_channel->api); + if (status != FWK_SUCCESS) + return status; + } + } + + return FWK_SUCCESS; +} + +static int mhu_process_bind_request(fwk_id_t source_id, fwk_id_t target_id, + fwk_id_t api_id, const void **api) +{ + struct mhu_device_ctx *device_ctx; + unsigned int slot; + + if (!fwk_id_is_type(target_id, FWK_ID_TYPE_SUB_ELEMENT)) + return FWK_E_ACCESS; + + device_ctx = &mhu_ctx.device_ctx_table[fwk_id_get_element_idx(target_id)]; + slot = fwk_id_get_sub_element_idx(target_id); + + if (device_ctx->bound_slots & (1 << slot)) + return FWK_E_ACCESS; + + device_ctx->smt_channel_table[slot].id = source_id; + device_ctx->bound_slots |= 1 << slot; + + *api = &mhu_mod_smt_driver_api; + + return FWK_SUCCESS; +} + +static int mhu_start(fwk_id_t id) +{ + int status; + struct mhu_device_ctx *device_ctx; + + if (fwk_id_get_type(id) == FWK_ID_TYPE_MODULE) + return FWK_SUCCESS; + + device_ctx = &mhu_ctx.device_ctx_table[fwk_id_get_element_idx(id)]; + + if (device_ctx->bound_slots != 0) { + status = fwk_interrupt_set_isr(device_ctx->config->irq, &mhu_isr); + if (status != FWK_SUCCESS) + return status; + status = fwk_interrupt_enable(device_ctx->config->irq); + if (status != FWK_SUCCESS) + return status; + } + + return FWK_SUCCESS; +} + +/* MHU module definition */ +const struct fwk_module module_mhu = { + .name = "MHU", + .type = FWK_MODULE_TYPE_DRIVER, + .api_count = 1, + .init = mhu_init, + .element_init = mhu_device_init, + .bind = mhu_bind, + .start = mhu_start, + .process_bind_request = mhu_process_bind_request, +}; diff --git a/module/mock_psu/include/mod_mock_psu.h b/module/mock_psu/include/mod_mock_psu.h new file mode 100644 index 00000000..f1d6ae4a --- /dev/null +++ b/module/mock_psu/include/mod_mock_psu.h @@ -0,0 +1,69 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef MOD_MOCK_PSU_H +#define MOD_MOCK_PSU_H + +#include <stdbool.h> +#include <stdint.h> +#include <fwk_module_idx.h> + +/*! + * \ingroup GroupModules + * \defgroup GroupMockPsu Mock Power Supply Driver + * \{ + */ + +/*! + * \defgroup GroupMockPsuConfig Configuration + * \{ + */ + +/*! + * \brief Element configuration. + */ +struct mod_mock_psu_device_config { + /*! Default state of the mock device's supply (enabled or disabled) */ + bool default_enabled; + + /*! Default voltage, in millivolts (mV), of the device's supply */ + uint64_t default_voltage; +}; + +/*! + * \} + */ + +/*! + * \defgroup GroupMockPsuIds Identifiers + * \{ + */ + +/*! + * \brief API indices. + */ +enum mod_mock_psu_api_idx { + /*! API index for PSU driver API */ + MOD_MOCK_PSU_API_IDX_PSU_DRIVER, + + /*! Number of defined APIs */ + MOD_MOCK_PSU_API_COUNT +}; + +/*! Driver API identifier */ +static const fwk_id_t mod_mock_psu_api_id_psu_driver = + FWK_ID_API_INIT(FWK_MODULE_IDX_MOCK_PSU, MOD_MOCK_PSU_API_IDX_PSU_DRIVER); + +/*! + * \} + */ + +/*! + * \} + */ + +#endif /* MOD_MOCK_PSU_H */ diff --git a/module/mock_psu/src/Makefile b/module/mock_psu/src/Makefile new file mode 100644 index 00000000..5ab1db02 --- /dev/null +++ b/module/mock_psu/src/Makefile @@ -0,0 +1,13 @@ +# +# Arm SCP/MCP Software +# Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +BS_LIB_NAME := mock_psu +BS_LIB_SOURCES := \ + mod_mock_psu_module.c \ + mod_mock_psu_psu_driver_api.c + +include $(BS_DIR)/lib.mk diff --git a/module/mock_psu/src/mod_mock_psu_module.c b/module/mock_psu/src/mod_mock_psu_module.c new file mode 100644 index 00000000..c39ef7bd --- /dev/null +++ b/module/mock_psu/src/mod_mock_psu_module.c @@ -0,0 +1,87 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <assert.h> +#include <fwk_macros.h> +#include <fwk_mm.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <mod_mock_psu.h> +#include <mod_mock_psu_private.h> + +static struct mod_mock_psu_device_ctx (*device_ctx)[]; + +static struct mod_mock_psu_device_ctx *get_device_ctx( + fwk_id_t device_id) +{ + return &(*device_ctx)[fwk_id_get_element_idx(device_id)]; +} + +static int mock_psu_init( + fwk_id_t module_id, + unsigned int element_count, + const void *data) +{ + device_ctx = fwk_mm_calloc( + element_count, + sizeof((*device_ctx)[0])); + if (device_ctx == NULL) + return FWK_E_NOMEM; + + return FWK_SUCCESS; +} + +static int mock_psu_element_init( + fwk_id_t device_id, + unsigned int sub_element_count, + const void *data) +{ + struct mod_mock_psu_device_ctx *ctx; + const struct mod_mock_psu_device_config *config = data; + + assert(sub_element_count == 0); + + ctx = get_device_ctx(device_id); + ctx->enabled = config->default_enabled; + ctx->voltage = config->default_voltage; + + return FWK_SUCCESS; +} + +static int mock_psu_process_bind_request( + fwk_id_t source_id, + fwk_id_t target_id, + fwk_id_t api_id, + const void **api) +{ + /* Only accept binds to the elements */ + if (!fwk_id_is_type(target_id, FWK_ID_TYPE_ELEMENT)) + return FWK_E_PARAM; + + *api = &__mod_mock_psu_psu_driver_api; + + return FWK_SUCCESS; +} + +struct mod_mock_psu_device_ctx *__mod_mock_psu_get_valid_device_ctx( + fwk_id_t device_id) +{ + if (fwk_module_check_call(device_id) != FWK_SUCCESS) + return NULL; + + return get_device_ctx(device_id); +} + +/* Module description */ +const struct fwk_module module_mock_psu = { + .name = "MOCK_PSU", + .type = FWK_MODULE_TYPE_DRIVER, + .api_count = MOD_MOCK_PSU_API_COUNT, + .init = mock_psu_init, + .element_init = mock_psu_element_init, + .process_bind_request = mock_psu_process_bind_request, +}; diff --git a/module/mock_psu/src/mod_mock_psu_module_private.h b/module/mock_psu/src/mod_mock_psu_module_private.h new file mode 100644 index 00000000..25f73fc7 --- /dev/null +++ b/module/mock_psu/src/mod_mock_psu_module_private.h @@ -0,0 +1,22 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef MOD_MOCK_PSU_MODULE_PRIVATE_H +#define MOD_MOCK_PSU_MODULE_PRIVATE_H + +#include <stdbool.h> +#include <fwk_id.h> + +struct mod_mock_psu_device_ctx { + bool enabled; /* Current enabled state */ + uint64_t voltage; /* Current voltage (in mV) */ +}; + +struct mod_mock_psu_device_ctx *__mod_mock_psu_get_valid_device_ctx( + fwk_id_t device_id); + +#endif /* MOD_MOCK_PSU_MODULE_PRIVATE_H */ diff --git a/module/mock_psu/src/mod_mock_psu_private.h b/module/mock_psu/src/mod_mock_psu_private.h new file mode 100644 index 00000000..a80a54a8 --- /dev/null +++ b/module/mock_psu/src/mod_mock_psu_private.h @@ -0,0 +1,17 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef MOD_MOCK_PSU_PRIVATE_H +#define MOD_MOCK_PSU_PRIVATE_H + +#include <stdbool.h> +#include <stdint.h> +#include <mod_mock_psu_module_private.h> +#include <mod_mock_psu_psu_driver_api_private.h> +#include <mod_mock_psu.h> + +#endif /* MOD_MOCK_PSU_PRIVATE_H */ diff --git a/module/mock_psu/src/mod_mock_psu_psu_driver_api.c b/module/mock_psu/src/mod_mock_psu_psu_driver_api.c new file mode 100644 index 00000000..fa510536 --- /dev/null +++ b/module/mock_psu/src/mod_mock_psu_psu_driver_api.c @@ -0,0 +1,71 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <fwk_id.h> +#include <mod_mock_psu_private.h> + +static int api_set_enabled(fwk_id_t device_id, bool enable) +{ + struct mod_mock_psu_device_ctx *ctx; + + ctx = __mod_mock_psu_get_valid_device_ctx(device_id); + if (ctx == NULL) + return FWK_E_PARAM; + + ctx->enabled = enable; + + return FWK_SUCCESS; +} + +static int api_get_enabled(fwk_id_t device_id, bool *enabled) +{ + struct mod_mock_psu_device_ctx *ctx; + + ctx = __mod_mock_psu_get_valid_device_ctx(device_id); + if (ctx == NULL) + return FWK_E_PARAM; + + *enabled = ctx->enabled; + + return FWK_SUCCESS; +} + +static int api_set_voltage(fwk_id_t device_id, uintmax_t voltage) +{ + struct mod_mock_psu_device_ctx *ctx; + + ctx = __mod_mock_psu_get_valid_device_ctx(device_id); + if (ctx == NULL) + return FWK_E_PARAM; + + ctx->voltage = voltage; + + return FWK_SUCCESS; +} + +static int api_get_voltage(fwk_id_t device_id, uintmax_t *voltage) +{ + struct mod_mock_psu_device_ctx *ctx; + + ctx = __mod_mock_psu_get_valid_device_ctx(device_id); + if (ctx == NULL) + return FWK_E_PARAM; + + *voltage = ctx->voltage; + + return FWK_SUCCESS; +} + +const struct mod_psu_driver_api __mod_mock_psu_psu_driver_api = { + .set_enabled = api_set_enabled, + .get_enabled = api_get_enabled, + .set_voltage = api_set_voltage, + .get_voltage = api_get_voltage, +}; diff --git a/module/mock_psu/src/mod_mock_psu_psu_driver_api_private.h b/module/mock_psu/src/mod_mock_psu_psu_driver_api_private.h new file mode 100644 index 00000000..397b7052 --- /dev/null +++ b/module/mock_psu/src/mod_mock_psu_psu_driver_api_private.h @@ -0,0 +1,15 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef MOD_MOCK_PSU_PSU_DRIVER_API_PRIVATE_H +#define MOD_MOCK_PSU_PSU_DRIVER_API_PRIVATE_H + +#include <mod_psu.h> + +extern const struct mod_psu_driver_api __mod_mock_psu_psu_driver_api; + +#endif /* MOD_MOCK_PSU_PSU_DRIVER_API_PRIVATE_H */ diff --git a/module/msys_rom/include/mod_msys_rom.h b/module/msys_rom/include/mod_msys_rom.h new file mode 100644 index 00000000..82ca90f9 --- /dev/null +++ b/module/msys_rom/include/mod_msys_rom.h @@ -0,0 +1,69 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: Module dedicated to the ROM firmware of the mobile platforms to + * initiate the boot of the primary AP core and then to jump to the SCP RAM + * firmware. + */ + +#ifndef MOD_MSYS_ROM_H +#define MOD_MSYS_ROM_H + +#include <stddef.h> +#include <stdint.h> +#include <fwk_id.h> + +/*! + * \ingroup GroupMSYSModule + * \defgroup GroupMSYSROM ROM Support + * @{ + */ + +/*! + * \brief Module configuration data. + */ +struct msys_rom_config { + /*! Base address of the Application Processor (AP) context area */ + const uintptr_t ap_context_base; + + /*! Size of the AP context area */ + const size_t ap_context_size; + + /*! Base address of the RAM firmware image */ + const uintptr_t ramfw_base; + + /*! Element ID of the primary cluster PPU */ + const fwk_id_t id_primary_cluster; + + /*! Element ID of the primary core PPU */ + const fwk_id_t id_primary_core; +}; + +/*! + * \brief Notification indices. + */ +enum mod_msys_rom_notification_idx { + /*! <tt>SYSTOP powered on</tt> notification */ + MOD_MSYS_ROM_NOTIFICATION_IDX_POWER_SYSTOP, + + /*! Number of notifications defined by the module */ + MOD_MSYS_ROM_NOTIFICATION_COUNT, +}; + +/*! <tt>SYSTOP powered on</tt> notification identifier */ +static const fwk_id_t mod_msys_rom_notification_id_systop = + FWK_ID_NOTIFICATION_INIT(FWK_MODULE_IDX_MSYS_ROM, + MOD_MSYS_ROM_NOTIFICATION_IDX_POWER_SYSTOP); + +/*! + * @} + */ + +/*! + * @} + */ + +#endif /* MOD_MSYS_ROM_H */ diff --git a/module/msys_rom/src/Makefile b/module/msys_rom/src/Makefile new file mode 100644 index 00000000..416e8c41 --- /dev/null +++ b/module/msys_rom/src/Makefile @@ -0,0 +1,11 @@ +# +# Arm SCP/MCP Software +# Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +BS_LIB_NAME := msys ROM +BS_LIB_SOURCES := mod_msys_rom.c + +include $(BS_DIR)/lib.mk diff --git a/module/msys_rom/src/mod_msys_rom.c b/module/msys_rom/src/mod_msys_rom.c new file mode 100644 index 00000000..354f112f --- /dev/null +++ b/module/msys_rom/src/mod_msys_rom.c @@ -0,0 +1,217 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: Module dedicated to the ROM firmware of the mobile platforms to + * initiate the boot of the primary AP core and then to jump to the SCP RAM + * firmware. + */ + +#include <assert.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdnoreturn.h> +#include <string.h> +#include <fwk_errno.h> +#include <fwk_interrupt.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <fwk_notification.h> +#include <fwk_thread.h> +#include <mod_bootloader.h> +#include <mod_log.h> +#include <mod_msys_rom.h> +#include <mod_power_domain.h> +#include <mod_ppu_v1.h> + +struct msys_rom_ctx { + const struct msys_rom_config *rom_config; + struct mod_log_api *log_api; + struct ppu_v1_boot_api *ppu_boot_api; + struct mod_bootloader_api *bootloader_api; + unsigned int notification_count; /* Notifications awaiting a response */ +} ctx; + +enum rom_event { + ROM_EVENT_RUN, + ROM_EVENT_COUNT +}; + +/* + * This function assumes that the RAM firmware image is located at the beginning + * of the SCP SRAM. The reset handler will be at offset 0x4. + */ +static noreturn void msys_jump_to_ramfw(void) +{ + uintptr_t const *reset_base = + (uintptr_t *)(ctx.rom_config->ramfw_base + 0x4); + void (*ramfw_reset_handler)(void); + + /* + * Disable interrupts for the duration of the ROM firmware to RAM firmware + * transition. + */ + fwk_interrupt_global_disable(); + + ramfw_reset_handler = (void (*)(void))*reset_base; + + /* + * Execute the RAM firmware's reset handler to pass control from ROM + * firmware to the RAM firmware. + */ + ramfw_reset_handler(); + + while (true) + continue; +} + +static int msys_deferred_setup(void) +{ + int status; + + /* Initialize the AP context area by zeroing it */ + memset((void *)ctx.rom_config->ap_context_base, + 0, + ctx.rom_config->ap_context_size); + + /* Power on the primary cluster and cpu */ + ctx.ppu_boot_api->power_mode_on(ctx.rom_config->id_primary_cluster); + ctx.ppu_boot_api->power_mode_on(ctx.rom_config->id_primary_core); + + ctx.log_api->log(MOD_LOG_GROUP_INFO, "[SYSTEM] Primary CPU powered\n"); + + status = ctx.bootloader_api->load_image(); + if (status != FWK_SUCCESS) { + ctx.log_api->log(MOD_LOG_GROUP_ERROR, + "[SYSTEM] Failed to load RAM firmware image\n"); + return FWK_E_DATA; + } + + msys_jump_to_ramfw(); + + return FWK_SUCCESS; +} + +/* + * Functions fulfilling the framework's module interface + */ + +static int msys_rom_init(fwk_id_t module_id, unsigned int element_count, + const void *data) +{ + ctx.rom_config = data; + + if ((ctx.rom_config->ap_context_base == 0) || + (ctx.rom_config->ap_context_size == 0)) + return FWK_E_RANGE; + + return FWK_SUCCESS; +} + +static int msys_rom_bind(fwk_id_t id, unsigned int round) +{ + int status; + + /* Use second round only (round numbering is zero-indexed) */ + if (round == 1) { + + /* Bind to the log component */ + status = fwk_module_bind(FWK_ID_MODULE(FWK_MODULE_IDX_LOG), + FWK_ID_API(FWK_MODULE_IDX_LOG, 0), + &ctx.log_api); + + if (status != FWK_SUCCESS) + return FWK_E_PANIC; + + /* Bind to the PPU module */ + status = fwk_module_bind(FWK_ID_MODULE(FWK_MODULE_IDX_PPU_V1), + FWK_ID_API(FWK_MODULE_IDX_PPU_V1, + MOD_PPU_V1_API_IDX_BOOT), + &ctx.ppu_boot_api); + if (status != FWK_SUCCESS) + return FWK_E_PANIC; + + /* Bind to the bootloader module */ + status = fwk_module_bind(FWK_ID_MODULE(FWK_MODULE_IDX_BOOTLOADER), + FWK_ID_API(FWK_MODULE_IDX_BOOTLOADER, 0), + &ctx.bootloader_api); + if (status != FWK_SUCCESS) + return FWK_E_PANIC; + } + + return FWK_SUCCESS; +} + +static int msys_rom_start(fwk_id_t id) +{ + struct fwk_event event = { + .source_id = FWK_ID_MODULE(FWK_MODULE_IDX_MSYS_ROM), + .target_id = FWK_ID_MODULE(FWK_MODULE_IDX_MSYS_ROM), + .id = FWK_ID_EVENT(FWK_MODULE_IDX_MSYS_ROM, ROM_EVENT_RUN), + }; + + return fwk_thread_put_event(&event); +} + +static int msys_rom_process_event(const struct fwk_event *event, + struct fwk_event *resp) +{ + int status; + struct mod_pd_power_state_transition_notification_params + *notification_params; + struct fwk_event systop_on_event = { + .response_requested = true, + .id = mod_msys_rom_notification_id_systop, + }; + + /* Notify any subscribers of the SYSTOP power domain state change */ + notification_params = + (struct mod_pd_power_state_transition_notification_params *) + systop_on_event.params; + notification_params->state = MOD_PD_STATE_ON; + + status = fwk_notification_notify(&systop_on_event, &ctx.notification_count); + if (status != FWK_SUCCESS) + return status; + + /* Complete remaining setup now if there are no subscribers to respond */ + if (ctx.notification_count == 0) + return msys_deferred_setup(); + + return FWK_SUCCESS; +} + +static int msys_rom_process_notification( + const struct fwk_event *event, + struct fwk_event *resp_event) +{ + assert(fwk_id_is_equal(event->id, mod_msys_rom_notification_id_systop)); + assert(event->is_response == true); + + /* At least one notification response must be outstanding */ + if (ctx.notification_count == 0) { + assert(false); + return FWK_E_PANIC; + } + + /* Complete remaining setup now that all subscribers have responded */ + if ((--ctx.notification_count) == 0) + return msys_deferred_setup(); + + return FWK_SUCCESS; +} + +/* Module descriptor */ +const struct fwk_module module_msys_rom = { + .name = "MSYS_ROM", + .type = FWK_MODULE_TYPE_SERVICE, + .event_count = ROM_EVENT_COUNT, + .notification_count = MOD_MSYS_ROM_NOTIFICATION_COUNT, + .init = msys_rom_init, + .bind = msys_rom_bind, + .start = msys_rom_start, + .process_event = msys_rom_process_event, + .process_notification = msys_rom_process_notification, +}; diff --git a/module/pik_clock/include/mod_pik_clock.h b/module/pik_clock/include/mod_pik_clock.h new file mode 100644 index 00000000..d03e7b36 --- /dev/null +++ b/module/pik_clock/include/mod_pik_clock.h @@ -0,0 +1,269 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef MOD_PIK_CLOCK_H +#define MOD_PIK_CLOCK_H + +#include <stdint.h> +#include <fwk_element.h> + +/*! + * \addtogroup GroupModules Modules + * @{ + */ + +/*! + * \defgroup GroupPIKClock PIK Clock Driver + * + * \details A driver for clock devices that are part of a PIK. + * + * @{ + */ + +/*! + * \brief APIs provided by the driver. + */ +enum mod_pik_clock_api_type { + /*! An implementation of the Clock HAL module's clock driver API */ + MOD_PIK_CLOCK_API_TYPE_CLOCK, + /*! A low-level API for direct control of CSS clocks */ + MOD_PIK_CLOCK_API_TYPE_CSS, + MOD_PIK_CLOCK_API_COUNT, +}; + +/*! + * \brief Sub-types of PIK clock. + */ +enum mod_pik_clock_type { + /*! A clock with a fixed source. Only its divider can be changed. */ + MOD_PIK_CLOCK_TYPE_SINGLE_SOURCE, + /*! A clock with multiple, selectable sources and at least one divider. */ + MOD_PIK_CLOCK_TYPE_MULTI_SOURCE, + /*! + * A clock with multiple, selectable sources, at least one divider, and + * support for modulation. + */ + MOD_PIK_CLOCK_TYPE_CLUSTER, +}; + +/*! + * \brief Divider register types. + */ +enum mod_pik_clock_msclock_divider { + /*! Divider affecting the system PLL clock source. */ + MOD_PIK_CLOCK_MSCLOCK_DIVIDER_DIV_SYS, + /*! Divider affecting the private or external PLL clock sources. */ + MOD_PIK_CLOCK_MSCLOCK_DIVIDER_DIV_EXT, +}; + +/*! + * \brief Selectable clock sources for multi-source clocks. + */ +enum mod_pik_clock_msclock_source { + /*! The clock is gated */ + MOD_PIK_CLOCK_MSCLOCK_SOURCE_GATED = 0, + /*! The clock source is set to the system reference clock */ + MOD_PIK_CLOCK_MSCLOCK_SOURCE_SYSREFCLK = 1, + /*! The clock source is set to the system PLL clock */ + MOD_PIK_CLOCK_MSCLOCK_SOURCE_SYSPLLCLK = 2, + /*! The clock source is set to a private PLL */ + MOD_PIK_CLOCK_MSCLOCK_SOURCE_PRIVPLLCLK = 4, + /*! Number of valid clock sources */ + MOD_PIK_CLOCK_MSCLOCK_SOURCE_MAX +}; + +/*! + * \brief Selectable clock sources for V8.2 cluster clocks. + */ +enum mod_clusclock_source { + /*! The clock is gated */ + MOD_PIK_CLOCK_CLUSCLK_SOURCE_GATED = 0, + /*! The clock source is set to the system reference clock */ + MOD_PIK_CLOCK_CLUSCLK_SOURCE_SYSREFCLK = 1, + /*! The clock source is set to a private cluster PLL */ + MOD_PIK_CLOCK_CLUSCLK_SOURCE_PLL0 = 2, + /*! The clock source is set to a private cluster PLL */ + MOD_PIK_CLOCK_CLUSCLK_SOURCE_PLL1 = 3, + /*! The clock source is set to a private cluster PLL */ + MOD_PIK_CLOCK_CLUSCLK_SOURCE_PLL2 = 4, + /*! The clock source is set to a private cluster PLL */ + MOD_PIK_CLOCK_CLUSCLK_SOURCE_PLL3 = 5, + /*! The clock source is set to a private cluster PLL */ + MOD_PIK_CLOCK_CLUSCLK_SOURCE_PLL4 = 6, + /*! The clock source is set to a private cluster PLL */ + MOD_PIK_CLOCK_CLUSCLK_SOURCE_PLL5 = 7, + /*! The clock source is set to a private cluster PLL */ + MOD_PIK_CLOCK_CLUSCLK_SOURCE_PLL6 = 8, + /*! The clock source is set to a private cluster PLL */ + MOD_PIK_CLOCK_CLUSCLK_SOURCE_PLL7 = 9, + /*! The clock source is set to the system PLL clock */ + MOD_PIK_CLOCK_CLUSCLK_SOURCE_SYSPLLCLK = 10, + /*! Number of valid clock sources */ + MOD_PIK_CLOCK_CLUSCLK_SOURCE_MAX +}; + +/*! + * \brief Selectable clock sources for GPU clocks. + */ +enum mod_pik_clock_gpuclock_source { + /*! The clock is gated */ + MOD_PIK_CLOCK_GPUCLK_SOURCE_GATED = 0, + /*! The clock source is set to the system reference clock */ + MOD_PIK_CLOCK_GPUCLK_SOURCE_SYSREFCLK = 1, + /*! The clock source is set to the system PLL clock */ + MOD_PIK_CLOCK_GPUCLK_SOURCE_SYSPLLCLK = 2, + /*! The clock source is set to the dedicated GPU PLL */ + MOD_PIK_CLOCK_GPUCLK_SOURCE_GPUPLLCLK = 4, + /*! Number of valid clock sources */ + MOD_PIK_CLOCK_GPUCLK_SOURCE_MAX +}; + +/*! + * \brief Selectable clock sources for DPU scaler clocks. + */ +enum mod_pik_clock_dpuclock_source { + /*! The clock is gated */ + MOD_PIK_CLOCK_DPUCLK_SOURCE_GATED = 0, + /*! The clock source is set to the system reference clock */ + MOD_PIK_CLOCK_DPUCLK_SOURCE_SYSREFCLK = 1, + /*! The clock source is set to the dedicated display PLL */ + MOD_PIK_CLOCK_DPUCLK_SOURCE_DISPLAYPLLCLK = 2, + /*! The clock source is set to a pixel clock PLL */ + MOD_PIK_CLOCK_DPUCLK_SOURCE_PIXELCLK = 4, + /*! Number of valid clock sources */ + MOD_PIK_CLOCK_DPUCLK_SOURCE_MAX +}; + +/*! + * \brief Selectable clock sources for the DPU AXI clock. + */ +enum mod_pik_clock_aclkdpu_source { + /*! The clock is gated */ + MOD_PIK_CLOCK_ACLKDPU_SOURCE_GATED = 0, + /*! The clock source is set to the system reference clock */ + MOD_PIK_CLOCK_ACLKDPU_SOURCE_SYSREFCLK = 1, + /*! The clock source is set to the dedicated display PLL */ + MOD_PIK_CLOCK_ACLKDPU_SOURCE_DISPLAYPLLCLK = 2, + /*! The clock source is set to the system PLL clock */ + MOD_PIK_CLOCK_ACLKDPU_SOURCE_SYSPLLCLK = 4, + /*! Number of valid clock sources */ + MOD_PIK_CLOCK_ACLKDPU_SOURCE_MAX +}; + +/*! + * \brief Selectable clock sources for the video processor clock. + */ +enum mod_pik_clock_vpuclk_source { + /*! The clock is gated */ + MOD_PIK_CLOCK_VPUCLK_SOURCE_GATED = 0, + /*! The clock source is set to the system reference clock */ + MOD_PIK_CLOCK_VPUCLK_SOURCE_SYSREFCLK = 1, + /*! The clock source is set to the system PLL clock */ + MOD_PIK_CLOCK_VPUCLK_SOURCE_SYSPLLCLK = 2, + /*! The clock source is set to the dedicated video PLL */ + MOD_PIK_CLOCK_VPUCLK_SOURCE_VIDEOPLLCLK = 4, + /*! Number of valid clock sources */ + MOD_PIK_CLOCK_VPUCLK_SOURCE_MAX +}; + +/*! + * \brief Selectable clock sources for interconnect clock. + */ +enum mod_pik_clock_intclk_source { + /*! The clock is gated */ + MOD_PIK_CLOCK_INTCLK_SOURCE_GATED = 0, + /*! The clock source is set to the system reference clock */ + MOD_PIK_CLOCK_INTCLK_SOURCE_SYSREFCLK = 1, + /*! The clock source is set to a private PLL */ + MOD_PIK_CLOCK_INTCLK_SOURCE_INTPLL = 2, +}; + +/*! + * \brief Selectable clock sources for DMC clock. + */ +enum mod_pik_clock_dmcclk_source { + /*! The clock is gated */ + MOD_PIK_CLOCK_DMCCLK_SOURCE_GATED = 0, + /*! The clock source is set to the system reference clock */ + MOD_PIK_CLOCK_DMCCLK_SOURCE_REFCLK = 1, + /*! The clock source is set to a private PLL */ + MOD_PIK_CLOCK_DMCCLK_SOURCE_DDRPLL = 2, +}; + +/*! + * \brief Rate lookup entry. + */ +struct mod_pik_clock_rate { + /*! Rate in Hertz. */ + uint64_t rate; + /*! Clock source used to obtain the rate (multi-source clocks only). */ + uint8_t source; + /*! The divider register to use (multi-source clocks only). */ + enum mod_pik_clock_msclock_divider divider_reg; + /*! Divider used to obtain the rate. */ + uint8_t divider; +}; + +/*! + * \brief Subsystem clock device configuration. + */ +struct mod_pik_clock_dev_config { + /*! The type of the clock device. */ + enum mod_pik_clock_type type; + + /*! + * \brief Indicates whether the clock is part of a CSS clock group (\c true) + * or operating as an independent clock (\c false). + * + * \details The value determines the API that the clock exposes during + * binding. If the clock is part of a group then the \ref + * mod_pik_clock_api_type.MOD_PIK_CLOCK_API_TYPE_CSS API is exposed for + * direct control via the CSS Clock module, otherwise the \ref + * mod_pik_clock_api_type.MOD_PIK_CLOCK_API_TYPE_CLOCK API, defined by + * the Clock HAL, is exposed. + */ + bool is_group_member; + + /*! Pointer to the clock's control register. */ + volatile uint32_t * const control_reg; + + /*! Pointer to the clock's modulator register, if any. */ + volatile uint32_t * const modulator_reg; + + /*! Pointer to the clock's DIV_SYS divider register, if any. */ + volatile uint32_t * const divsys_reg; + + /*! Pointer to the clock's DIV_EXT divider register, if any. */ + volatile uint32_t * const divext_reg; + + /*! Pointer to the clock's rate lookup table. */ + const struct mod_pik_clock_rate *rate_table; + + /*! The number of rates in the rate lookup table. */ + uint32_t rate_count; + + /*! The rate, in Hz, to set during module initialization. */ + uint64_t initial_rate; + + /*! + * If \c true, the driver will not attempt to set a default frequency, or to + * otherwise configure the PLL during the pre-runtime phase. The PLL is + * expected to be initialized later in response to a notification or other + * event. + */ + const bool defer_initialization; +}; + +/*! + * @} + */ + +/*! + * @} + */ + +#endif /* MOD_PIK_CLOCK_H */ diff --git a/module/pik_clock/src/Makefile b/module/pik_clock/src/Makefile new file mode 100644 index 00000000..0437f00d --- /dev/null +++ b/module/pik_clock/src/Makefile @@ -0,0 +1,11 @@ +# +# Arm SCP/MCP Software +# Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +BS_LIB_NAME := PIK Clock Driver +BS_LIB_SOURCES := mod_pik_clock.c + +include $(BS_DIR)/lib.mk diff --git a/module/pik_clock/src/mod_pik_clock.c b/module/pik_clock/src/mod_pik_clock.c new file mode 100644 index 00000000..e03788f7 --- /dev/null +++ b/module/pik_clock/src/mod_pik_clock.c @@ -0,0 +1,767 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <assert.h> +#include <stdint.h> +#include <stdlib.h> +#include <fwk_element.h> +#include <fwk_errno.h> +#include <fwk_mm.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <mod_clock.h> +#include <mod_css_clock.h> +#include <mod_pik_clock.h> +#include <mod_power_domain.h> + +/* + * Masks for single-source clock divider control. + */ +#define SSCLK_CONTROL_CLKDIV UINT32_C(0x000000F0) +#define SSCLK_CONTROL_CRNTCLKDIV UINT32_C(0x0000F000) +#define SSCLK_CONTROL_ENTRY_DLY UINT32_C(0xFF000000) + +/* + * Offsets for single-source clock divider control. + */ +#define SSCLK_CONTROL_CLKDIV_POS 4 +#define SSCLK_CONTROL_CRNTCLKDIV_POS 12 +#define SSCLK_CONTROL_ENTRY_DLY_POS 24 + +/* + * Masks for multi-source clock divider control (both DIV1 and DIV2). + */ +#define MSCLK_DIV_CLKDIV UINT32_C(0x0000001F) +#define MSCLK_DIV_CRNTCLKDIV UINT32_C(0x001F0000) +#define MSCLK_DIV_ENTRY_DLY UINT32_C(0xFF000000) + +/* + * Offsets for multi-source clock divider control (both DIV1 and DIV2). + */ +#define MSCLK_DIV_CLKDIV_POS 0 +#define MSCLK_DIV_CRNTCLKDIV_POS 16 +#define MSCLK_DIV_ENTRY_DLY_POS 24 + +/* + * Masks for multi-source clock source selection. + */ +#define MSCLK_CONTROL_CLKSEL UINT32_C(0x000000FF) +#define MSCLK_CONTROL_CLKSEL_GATED UINT32_C(0x00000000) +#define MSCLK_CONTROL_CLKSEL_SYSREFCLK UINT32_C(0x00000001) +#define MSCLK_CONTROL_CLKSEL_SYSPLLCLK UINT32_C(0x00000002) +#define MSCLK_CONTROL_CLKSEL_PRIVPLLCLK UINT32_C(0x00000004) +#define MSCLK_CONTROL_CRNTCLK UINT32_C(0x0000FF00) + +/* + * Offsets for multi-source clock source selection. + */ +#define MSCLK_CONTROL_CLKSEL_POS 0 +#define MSCLK_CONTROL_CRNTCLK_POS 8 + +/* + * Masks for cluster clock source selection. + * + * Note: The CLKSEL field mask is shared with the multi-source clock subtype, as + * are the masks for the GATED and REFCLK sources. + */ +#define CLUSCLK_CONTROL_CLKSEL_PLL0 UINT32_C(0x00000002) +#define CLUSCLK_CONTROL_CLKSEL_PLL1 UINT32_C(0x00000003) +#define CLUSCLK_CONTROL_CLKSEL_PLL2 UINT32_C(0x00000004) +#define CLUSCLK_CONTROL_CLKSEL_PLL3 UINT32_C(0x00000005) +#define CLUSCLK_CONTROL_CLKSEL_PLL4 UINT32_C(0x00000006) +#define CLUSCLK_CONTROL_CLKSEL_PLL5 UINT32_C(0x00000007) +#define CLUSCLK_CONTROL_CLKSEL_PLL6 UINT32_C(0x00000008) +#define CLUSCLK_CONTROL_CLKSEL_PLL7 UINT32_C(0x00000009) +#define CLUSCLK_CONTROL_CLKSEL_SYSPLLCLK UINT32_C(0x0000000A) + +/* + * Masks for cluster clock modulator control. + */ +#define CLUSCLK_MOD_DENOMINATOR UINT32_C(0x000000FF) +#define CLUSCLK_MOD_NUMERATOR UINT32_C(0x0000FF00) +#define CLUSCLK_MOD_CRNTDENOMINATOR UINT32_C(0x00FF0000) +#define CLUSCLK_MOD_CRNTNUMERATOR UINT32_C(0xFF000000) + +/* + * Offsets for cluster clock modulator control. + */ +#define CLUSCLK_MOD_DENOMINATOR_POS 0 +#define CLUSCLK_MOD_NUMERATOR_POS 8 +#define CLUSCLK_MOD_CRNTDENOMINATOR_POS 16 +#define CLUSCLK_MOD_CRNTNUMERATOR_POS 24 + +/* Device context */ +struct pik_clock_dev_ctx { + bool initialized; + uint64_t current_rate; + uint8_t current_source; + enum mod_clock_state current_state; + const struct mod_pik_clock_dev_config *config; +}; + +/* Module context */ +struct pik_clock_ctx { + struct pik_clock_dev_ctx *dev_ctx_table; + unsigned int dev_count; +}; + +static struct pik_clock_ctx module_ctx; + +/* + * Static helper functions + */ + +static int compare_rate_entry(const void *a, const void *b) +{ + struct mod_pik_clock_rate *key = (struct mod_pik_clock_rate *)a; + struct mod_pik_clock_rate *element = (struct mod_pik_clock_rate *)b; + + return (key->rate - element->rate); +} + +static int get_rate_entry(struct pik_clock_dev_ctx *ctx, uint64_t target_rate, + struct mod_pik_clock_rate **entry) +{ + struct mod_pik_clock_rate *current_rate_entry; + + if (ctx == NULL) + return FWK_E_PARAM; + if (entry == NULL) + return FWK_E_PARAM; + + /* Perform a binary search to find the entry matching the requested rate */ + current_rate_entry = (struct mod_pik_clock_rate *) bsearch(&target_rate, + ctx->config->rate_table, ctx->config->rate_count, + sizeof(struct mod_pik_clock_rate), compare_rate_entry); + + if (current_rate_entry == NULL) + return FWK_E_PARAM; + + *entry = current_rate_entry; + return FWK_SUCCESS; +} + +static int ssclock_set_div(struct pik_clock_dev_ctx *ctx, uint32_t divider, + bool wait_after_set) +{ + uint32_t clkdiv; + + if (divider == 0) + return FWK_E_PARAM; + if (divider > 16) + return FWK_E_PARAM; + if (ctx == NULL) + return FWK_E_PARAM; + if (ctx->config->type != MOD_PIK_CLOCK_TYPE_SINGLE_SOURCE) + return FWK_E_PARAM; + + /* The resulting divider is the programmed value plus one */ + clkdiv = divider - 1; + + /* Set */ + *ctx->config->control_reg = + (*ctx->config->control_reg & ~SSCLK_CONTROL_CLKDIV) | + (clkdiv << SSCLK_CONTROL_CLKDIV_POS); + + if (wait_after_set) { + while ((*ctx->config->control_reg & SSCLK_CONTROL_CRNTCLKDIV) != + (clkdiv << SSCLK_CONTROL_CRNTCLKDIV_POS)) + continue; + } + + return FWK_SUCCESS; +} + +static int msclock_set_div(struct pik_clock_dev_ctx *ctx, + enum mod_pik_clock_msclock_divider divider_type, + uint32_t divider, + bool wait_after_set) +{ + volatile uint32_t * divider_reg; + uint32_t clkdiv; + + if (ctx == NULL) + return FWK_E_PARAM; + if (divider == 0) + return FWK_E_PARAM; + if (divider > 16) + return FWK_E_PARAM; + if (ctx->config->type == MOD_PIK_CLOCK_TYPE_SINGLE_SOURCE) + return FWK_E_PARAM; + + /* The resulting divider is the programmed value plus one */ + clkdiv = divider - 1; + + if (divider_type == MOD_PIK_CLOCK_MSCLOCK_DIVIDER_DIV_SYS) + divider_reg = ctx->config->divsys_reg; + else if (divider_type == MOD_PIK_CLOCK_MSCLOCK_DIVIDER_DIV_EXT) + divider_reg = ctx->config->divext_reg; + else + return FWK_E_PARAM; + + /* Set */ + *divider_reg = (*divider_reg & ~MSCLK_DIV_CLKDIV) | + (clkdiv << MSCLK_DIV_CLKDIV_POS); + + if (wait_after_set) { + while ((*divider_reg & MSCLK_DIV_CRNTCLKDIV) != + (clkdiv << MSCLK_DIV_CRNTCLKDIV_POS)) + continue; + } + + return FWK_SUCCESS; +} + +static int msclock_set_source(struct pik_clock_dev_ctx *ctx, + enum mod_pik_clock_msclock_source source, + bool wait_after_set) +{ + if (ctx == NULL) + return FWK_E_PARAM; + if (ctx->config->type == MOD_PIK_CLOCK_TYPE_SINGLE_SOURCE) + return FWK_E_PARAM; + + /* Set */ + *ctx->config->control_reg = + (*ctx->config->control_reg & ~MSCLK_CONTROL_CLKSEL) | + (source << MSCLK_CONTROL_CLKSEL_POS); + + if (wait_after_set) { + while ((*ctx->config->control_reg & MSCLK_CONTROL_CRNTCLK) != + ((uint32_t)(source << MSCLK_CONTROL_CRNTCLK_POS))) + continue; + } + + return FWK_SUCCESS; +} + +static int cluster_set_modulator(struct pik_clock_dev_ctx *ctx, + uint32_t numerator, uint32_t denominator, + bool wait_after_set) +{ + uint32_t modulator_setting; + + if (ctx == NULL) + return FWK_E_PARAM; + if (ctx->config->type != MOD_PIK_CLOCK_TYPE_CLUSTER) + return FWK_E_PARAM; + if (ctx->config->modulator_reg == NULL) + return FWK_E_PARAM; + if (denominator > 255) + return FWK_E_PARAM; + if (denominator == 0) + return FWK_E_PARAM; + if (numerator > 255) + return FWK_E_PARAM; + + modulator_setting = (denominator << CLUSCLK_MOD_DENOMINATOR_POS) | + (numerator << CLUSCLK_MOD_NUMERATOR_POS); + + + *ctx->config->modulator_reg = ((*ctx->config->modulator_reg & + ~(CLUSCLK_MOD_DENOMINATOR | CLUSCLK_MOD_NUMERATOR)) | + modulator_setting); + + if (wait_after_set) { + while ((*ctx->config->modulator_reg & CLUSCLK_MOD_CRNTNUMERATOR) != + (numerator << CLUSCLK_MOD_CRNTNUMERATOR_POS)) + continue; + + while ((*ctx->config->modulator_reg & CLUSCLK_MOD_CRNTDENOMINATOR) != + (denominator << CLUSCLK_MOD_CRNTDENOMINATOR_POS)) + continue; + } + + return FWK_SUCCESS; +} + +static int do_pik_clock_set_rate(fwk_id_t dev_id, uint64_t rate, + enum mod_clock_round_mode round_mode) +{ + int status; + struct pik_clock_dev_ctx *ctx; + struct mod_pik_clock_rate *rate_entry; + + ctx = module_ctx.dev_ctx_table + fwk_id_get_element_idx(dev_id); + + /* Look up the divider and source settings */ + status = get_rate_entry(ctx, rate, &rate_entry); + if (status != FWK_SUCCESS) + return status; + + switch (ctx->config->type) { + case MOD_PIK_CLOCK_TYPE_SINGLE_SOURCE: + status = ssclock_set_div(ctx, rate_entry->divider, false); + if (status != FWK_SUCCESS) + goto exit; + break; + case MOD_PIK_CLOCK_TYPE_CLUSTER: + /* Modulator feature not currently used */ + cluster_set_modulator(ctx, 1, 1, false); + /* Intentional fall-through */ + case MOD_PIK_CLOCK_TYPE_MULTI_SOURCE: + if (ctx->current_source == MOD_PIK_CLOCK_MSCLOCK_SOURCE_GATED) + /* Leave the new rate to be applied when the clock is (re)started */ + goto exit; + + status = msclock_set_div(ctx, rate_entry->divider_reg, + rate_entry->divider, false); + if (status != FWK_SUCCESS) + goto exit; + + status = msclock_set_source(ctx, rate_entry->source, false); + if (status != FWK_SUCCESS) + goto exit; + + ctx->current_source = rate_entry->source; + break; + default: + return FWK_E_SUPPORT; + } + +exit: + if (status == FWK_SUCCESS) + ctx->current_rate = rate; + return status; +} + +/* + * Clock driver API functions + */ + +static int pik_clock_set_rate(fwk_id_t dev_id, uint64_t rate, + enum mod_clock_round_mode round_mode) +{ + int status; + struct pik_clock_dev_ctx *ctx; + + status = fwk_module_check_call(dev_id); + if (status != FWK_SUCCESS) + return status; + + ctx = module_ctx.dev_ctx_table + fwk_id_get_element_idx(dev_id); + + if (!ctx->initialized) + return FWK_E_INIT; + + if (ctx->current_state == MOD_CLOCK_STATE_STOPPED) + return FWK_E_PWRSTATE; + + return do_pik_clock_set_rate(dev_id, rate, round_mode); +} + +static int pik_clock_get_rate(fwk_id_t dev_id, uint64_t *rate) +{ + int status; + struct pik_clock_dev_ctx *ctx; + + status = fwk_module_check_call(dev_id); + if (status != FWK_SUCCESS) + return status; + + if (rate == NULL) + return FWK_E_PARAM; + + ctx = module_ctx.dev_ctx_table + fwk_id_get_element_idx(dev_id); + + if (ctx->config->type == MOD_PIK_CLOCK_TYPE_MULTI_SOURCE && + ctx->current_source == MOD_PIK_CLOCK_MSCLOCK_SOURCE_GATED) + /* Indicate that the clock is not running */ + *rate = 0; + else + *rate = ctx->current_rate; + + return FWK_SUCCESS; +} + +static int pik_clock_get_rate_from_index(fwk_id_t dev_id, + unsigned int rate_index, + uint64_t *rate) +{ + int status; + struct pik_clock_dev_ctx *ctx; + + status = fwk_module_check_call(dev_id); + if (status != FWK_SUCCESS) + return status; + + if (rate == NULL) + return FWK_E_PARAM; + + ctx = module_ctx.dev_ctx_table + fwk_id_get_element_idx(dev_id); + + if (rate_index >= ctx->config->rate_count) + return FWK_E_PARAM; + + *rate = ctx->config->rate_table[rate_index].rate; + return FWK_SUCCESS; +} + +static int pik_clock_set_state( + fwk_id_t dev_id, + enum mod_clock_state target_state) +{ + int status; + struct pik_clock_dev_ctx *ctx; + struct mod_pik_clock_rate *rate_entry; + + status = fwk_module_check_call(dev_id); + if (status != FWK_SUCCESS) + return status; + + ctx = module_ctx.dev_ctx_table + fwk_id_get_element_idx(dev_id); + + if (ctx->config->type == MOD_PIK_CLOCK_TYPE_SINGLE_SOURCE) + /* Cannot gate single-source clocks */ + return FWK_E_SUPPORT; + + if (!ctx->initialized) + return FWK_E_INIT; + + if (ctx->current_state == MOD_CLOCK_STATE_STOPPED) + /* + * This state from the device context relates only to the clock state + * that is derived from its parent power domain. + */ + return FWK_E_PWRSTATE; + + if (target_state == MOD_CLOCK_STATE_STOPPED) { + /* The clock is powered and will be gated. */ + status = msclock_set_source(ctx, MOD_PIK_CLOCK_MSCLOCK_SOURCE_GATED, + false); + if (status == FWK_SUCCESS) + ctx->current_source = MOD_PIK_CLOCK_MSCLOCK_SOURCE_GATED; + + return status; + } else { + /* Look up the divider and source settings */ + status = get_rate_entry(ctx, ctx->current_rate, &rate_entry); + if (status != FWK_SUCCESS) + return status; + + status = msclock_set_source(ctx, rate_entry->source, false); + if (status == FWK_SUCCESS) + ctx->current_source = rate_entry->source; + + return status; + } +} + +static int pik_clock_get_state(fwk_id_t dev_id, enum mod_clock_state *state) +{ + int status; + struct pik_clock_dev_ctx *ctx; + + status = fwk_module_check_call(dev_id); + if (status != FWK_SUCCESS) + return status; + + ctx = module_ctx.dev_ctx_table + fwk_id_get_element_idx(dev_id); + + if (ctx->config->type == MOD_PIK_CLOCK_TYPE_SINGLE_SOURCE) { + /* + * Single-source clocks cannot be gated so their running state will be + * derived purely from the state of their parent power domain, if any. + */ + *state = ctx->current_state; + return FWK_SUCCESS; + } + + /* + * Multi-source clocks may be stopped due to gating as well as the state of + * their parent power domain. + */ + if (ctx->current_source == MOD_PIK_CLOCK_MSCLOCK_SOURCE_GATED) + *state = MOD_CLOCK_STATE_STOPPED; + else + *state = ctx->current_state; + + return FWK_SUCCESS; +} + +static int pik_clock_get_range(fwk_id_t dev_id, struct mod_clock_range *range) +{ + int status; + struct pik_clock_dev_ctx *ctx; + + status = fwk_module_check_call(dev_id); + if (status != FWK_SUCCESS) + return status; + + if (range == NULL) + return FWK_E_PARAM; + + ctx = module_ctx.dev_ctx_table + fwk_id_get_element_idx(dev_id); + + range->rate_type = MOD_CLOCK_RATE_TYPE_DISCRETE; + range->min = ctx->config->rate_table[0].rate; + range->max = ctx->config->rate_table[ctx->config->rate_count - 1].rate; + range->rate_count = ctx->config->rate_count; + + return FWK_SUCCESS; +} + +static int pik_clock_power_state_change( + fwk_id_t dev_id, + unsigned int state) +{ + int status; + struct pik_clock_dev_ctx *ctx; + + status = fwk_module_check_call(dev_id); + if (status != FWK_SUCCESS) + return status; + + ctx = module_ctx.dev_ctx_table + fwk_id_get_element_idx(dev_id); + + if (ctx->config->is_group_member) + return FWK_E_ACCESS; + + if (state == MOD_PD_STATE_ON) { + if (ctx->initialized) + /* Restore the previous rate */ + return do_pik_clock_set_rate( + dev_id, ctx->current_rate, MOD_CLOCK_ROUND_MODE_NONE); + else { + /* Perform deferred initialization and set the initial rate */ + ctx->current_state = MOD_CLOCK_STATE_RUNNING; + ctx->initialized = true; + return do_pik_clock_set_rate( + dev_id, ctx->config->initial_rate, MOD_CLOCK_ROUND_MODE_NONE); + } + } else + ctx->current_state = MOD_CLOCK_STATE_STOPPED; + + return FWK_SUCCESS; +} + +static const struct mod_clock_drv_api api_clock = { + .set_rate = pik_clock_set_rate, + .get_rate = pik_clock_get_rate, + .get_rate_from_index = pik_clock_get_rate_from_index, + .set_state = pik_clock_set_state, + .get_state = pik_clock_get_state, + .get_range = pik_clock_get_range, + .process_power_transition = pik_clock_power_state_change, +}; + +/* + * Direct driver API functions + */ + +static int pik_clock_direct_set_div(fwk_id_t clock_id, uint32_t divider_type, + uint32_t divider) +{ + int status; + struct pik_clock_dev_ctx *ctx; + + status = fwk_module_check_call(clock_id); + if (status != FWK_SUCCESS) + return status; + + ctx = module_ctx.dev_ctx_table + fwk_id_get_element_idx(clock_id); + assert(ctx->config->is_group_member); + + if (ctx->current_state == MOD_CLOCK_STATE_STOPPED) + return FWK_E_PWRSTATE; + + switch (ctx->config->type) { + case MOD_PIK_CLOCK_TYPE_SINGLE_SOURCE: + status = ssclock_set_div(ctx, divider, false); + break; + case MOD_PIK_CLOCK_TYPE_CLUSTER: + case MOD_PIK_CLOCK_TYPE_MULTI_SOURCE: + status = msclock_set_div(ctx, + (enum mod_pik_clock_msclock_divider)divider_type, divider, false); + break; + default: + return FWK_E_SUPPORT; + } + + return status; +} + +static int pik_clock_direct_set_source(fwk_id_t clock_id, uint8_t source) +{ + int status; + struct pik_clock_dev_ctx *ctx; + + status = fwk_module_check_call(clock_id); + if (status != FWK_SUCCESS) + return status; + + ctx = module_ctx.dev_ctx_table + fwk_id_get_element_idx(clock_id); + assert(ctx->config->is_group_member); + + if (ctx->current_state == MOD_CLOCK_STATE_STOPPED) + return FWK_E_PWRSTATE; + + return msclock_set_source(ctx, source, false); +} + +static int pik_clock_direct_set_mod(fwk_id_t clock_id, uint32_t numerator, + uint32_t denominator) +{ + int status; + struct pik_clock_dev_ctx *ctx; + + status = fwk_module_check_call(clock_id); + if (status != FWK_SUCCESS) + return status; + + ctx = module_ctx.dev_ctx_table + fwk_id_get_element_idx(clock_id); + assert(ctx->config->is_group_member); + + if (ctx->current_state == MOD_CLOCK_STATE_STOPPED) + return FWK_E_PWRSTATE; + + return cluster_set_modulator(ctx, numerator, denominator, false); +} + +static int pik_clock_direct_power_state_change( + fwk_id_t dev_id, + unsigned int state) +{ + int status; + struct pik_clock_dev_ctx *ctx; + + status = fwk_module_check_call(dev_id); + if (status != FWK_SUCCESS) + return status; + + ctx = module_ctx.dev_ctx_table + fwk_id_get_element_idx(dev_id); + + if (!ctx->config->is_group_member) + return FWK_E_ACCESS; + + if (state == MOD_PD_STATE_ON) { + if (!ctx->initialized) + /* Perform delayed intialization */ + ctx->initialized = true; + ctx->current_state = MOD_CLOCK_STATE_RUNNING; + } else + ctx->current_state = MOD_CLOCK_STATE_STOPPED; + + return FWK_SUCCESS; +} + +static const struct mod_css_clock_direct_api api_direct = { + .set_div = pik_clock_direct_set_div, + .set_source = pik_clock_direct_set_source, + .set_mod = pik_clock_direct_set_mod, + .process_power_transition = pik_clock_direct_power_state_change, +}; + +/* + * Framework handler functions + */ + +static int pik_clock_init(fwk_id_t module_id, unsigned int element_count, + const void *data) +{ + module_ctx.dev_count = element_count; + + if (element_count == 0) + return FWK_SUCCESS; + + module_ctx.dev_ctx_table = fwk_mm_calloc(element_count, + sizeof(struct pik_clock_dev_ctx)); + if (module_ctx.dev_ctx_table == NULL) + return FWK_E_NOMEM; + + return FWK_SUCCESS; +} + +static int pik_clock_element_init(fwk_id_t element_id, + unsigned int sub_element_count, + const void *data) +{ + unsigned int i = 0; + uint64_t current_rate; + uint64_t last_rate = 0; + struct pik_clock_dev_ctx *ctx; + const struct mod_pik_clock_dev_config *dev_config = data; + + if (!fwk_module_is_valid_element_id(element_id)) + return FWK_E_PARAM; + + ctx = module_ctx.dev_ctx_table + fwk_id_get_element_idx(element_id); + + /* Verify that the rate entries in the device's lookup table are ordered */ + while (i < dev_config->rate_count) { + current_rate = dev_config->rate_table[i].rate; + + /* The rate entries must be in ascending order */ + if (current_rate < last_rate) + return FWK_E_DATA; + + last_rate = current_rate; + i++; + } + + ctx->config = dev_config; + + /* Begin with an invalid source */ + ctx->current_source = MOD_PIK_CLOCK_MSCLOCK_SOURCE_MAX; + + if (ctx->config->defer_initialization) + return FWK_SUCCESS; + + ctx->current_state = MOD_CLOCK_STATE_RUNNING; + ctx->initialized = true; + + /* + * Clock devices that are members of a clock group must skip initialization + * at this time since they will be set to a specific rate by the CSS Clock + * driver during the start stage or in response to a notification. + */ + if (ctx->config->is_group_member) + return FWK_SUCCESS; + + return do_pik_clock_set_rate( + element_id, dev_config->initial_rate, MOD_CLOCK_ROUND_MODE_NONE); +} + +static int pik_clock_process_bind_request(fwk_id_t source_id, + fwk_id_t target_id, fwk_id_t api_id, + const void **api) +{ + struct pik_clock_dev_ctx *ctx; + + /* Only elements can be bound to as the API depends on the element type */ + if (!fwk_id_is_type(target_id, FWK_ID_TYPE_ELEMENT)) + return FWK_E_ACCESS; + + ctx = module_ctx.dev_ctx_table + fwk_id_get_element_idx(target_id); + + if (ctx->config->is_group_member) { + #if BUILD_HAS_MOD_CSS_CLOCK + /* Only the CSS Clock module can bind to group members. */ + if (fwk_id_get_module_idx(source_id) == FWK_MODULE_IDX_CSS_CLOCK) { + *api = &api_direct; + return FWK_SUCCESS; + } else + return FWK_E_ACCESS; + #else + /* The CSS Clock module is required to support group members. */ + return FWK_E_SUPPORT; + #endif + } else + *api = &api_clock; + + return FWK_SUCCESS; +} + +const struct fwk_module module_pik_clock = { + .name = "PIK Clock Driver", + .type = FWK_MODULE_TYPE_DRIVER, + .api_count = MOD_PIK_CLOCK_API_COUNT, + .event_count = 0, + .init = pik_clock_init, + .element_init = pik_clock_element_init, + .process_bind_request = pik_clock_process_bind_request, +}; diff --git a/module/pl011/include/mod_pl011.h b/module/pl011/include/mod_pl011.h new file mode 100644 index 00000000..6c96bac5 --- /dev/null +++ b/module/pl011/include/mod_pl011.h @@ -0,0 +1,55 @@ + +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef MOD_PL011_H +#define MOD_PL011_H + +#include <stdint.h> +#include <fwk_id.h> + +/*! + * \addtogroup GroupModules Modules + * @{ + */ + +/*! + * \defgroup GroupModulePl011 PL011 Driver + * + * \brief Arm PL011 device driver, fulfilling the Log module's driver API. + * + * \details This module implements a device driver for the Primecell® PL011 + * UART. + * @{ + */ + +/*! + * \brief PL011 device configuration data. + */ +struct mod_pl011_device_config { + /*! Base address of the device registers */ + uintptr_t reg_base; + + /*! Baud rate (bits per second) */ + unsigned int baud_rate_bps; + + /*! Reference clock (Hertz) */ + uint64_t clock_rate_hz; + + /*! Identifier of the clock that this device depends on */ + fwk_id_t clock_id; +}; + +/*! + * @} + */ + +/*! + * @} + */ + +#endif /* MOD_PL011_H */ diff --git a/module/pl011/src/Makefile b/module/pl011/src/Makefile new file mode 100644 index 00000000..d4bec75c --- /dev/null +++ b/module/pl011/src/Makefile @@ -0,0 +1,11 @@ +# +# Arm SCP/MCP Software +# Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +BS_LIB_NAME := "PL011" +BS_LIB_SOURCES = mod_pl011.c + +include $(BS_DIR)/lib.mk diff --git a/module/pl011/src/mod_pl011.c b/module/pl011/src/mod_pl011.c new file mode 100644 index 00000000..da0bf939 --- /dev/null +++ b/module/pl011/src/mod_pl011.c @@ -0,0 +1,244 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <assert.h> +#include <fwk_errno.h> +#include <fwk_mm.h> +#include <fwk_module.h> +#include <fwk_notification.h> +#include <mod_clock.h> +#include <mod_log.h> +#include <mod_pl011.h> +#include <mod_power_domain.h> +#include <pl011.h> + +static const struct mod_pl011_device_config **device_config_table; + +static struct pl011_reg *get_device_reg(fwk_id_t device_id) +{ + unsigned int device_idx; + + device_idx = fwk_id_get_element_idx(device_id); + return (struct pl011_reg *)device_config_table[device_idx]->reg_base; +} + +/* + * For details on the constants and equations used to calculate the baud rate + * settings, please consult the PL011 TRM. + */ +static int set_baud_rate(unsigned int baud_rate_bps, uint64_t clock_rate_hz, + struct pl011_reg *reg) +{ + uint32_t divisor_integer; + uint32_t divisor_fractional; + uint32_t divisor; + uint32_t clock_rate_x4; + + assert(reg); + + if (baud_rate_bps == 0) + return FWK_E_PARAM; + + if ((clock_rate_hz < PL011_UARTCLK_MIN) || + (clock_rate_hz > PL011_UARTCLK_MAX)) + return FWK_E_PARAM; + + /* The highest clock x4 should still fit in 32 bits */ + assert((PL011_UARTCLK_MAX * UINT64_C(4)) < UINT32_MAX); + + /* Ensure baud rate is not higher than the clock can support */ + clock_rate_x4 = clock_rate_hz * 4; + if (baud_rate_bps > clock_rate_x4) + return FWK_E_RANGE; + + /* Calculate integer and fractional divisors */ + divisor = clock_rate_x4 / baud_rate_bps; + divisor_integer = divisor / 64; + divisor_fractional = divisor % 64; + + /* The integer divisor must fit in 16 bits */ + if (divisor_integer > 0xffff) + return FWK_E_RANGE; + + /* The fractional divisor must fit in 6 bits */ + if (divisor_fractional > 0x3f) + return FWK_E_RANGE; + + /* + * When the integer divisor is equals to 0xffff, the fractional divisor can + * only be 0. + */ + if ((divisor_integer == 0xffff) && (divisor_fractional != 0)) + return FWK_E_RANGE; + + /* Set registers */ + reg->IBRD = divisor_integer; + reg->FBRD = divisor_fractional; + + return FWK_SUCCESS; +} + +/* + * Module log driver API + */ + +static int do_putchar(fwk_id_t device_id, char c) +{ + int status; + struct pl011_reg *reg; + + status = fwk_module_check_call(device_id); + if (status != FWK_SUCCESS) + return status; + + reg = get_device_reg(device_id); + + while (reg->FR & PL011_FR_TXFF) + continue; + + reg->DR = c; + + return FWK_SUCCESS; +} + +static int do_flush(fwk_id_t device_id) +{ + int status; + struct pl011_reg *reg; + + status = fwk_module_check_call(device_id); + if (status != FWK_SUCCESS) + return status; + + reg = get_device_reg(device_id); + + while (reg->FR & PL011_FR_BUSY) + continue; + + return FWK_SUCCESS; +} + +static const struct mod_log_driver_api driver_api = { + .flush = do_flush, + .putchar = do_putchar, +}; + +/* + * Framework handlers + */ + +static int pl011_init(fwk_id_t module_id, unsigned int element_count, + const void *data) +{ + if (element_count == 0) + return FWK_E_DATA; + + /* + * Create an array of pointers used to store the configuration data pointer + * of each element. + */ + device_config_table = fwk_mm_calloc(element_count, + sizeof(*device_config_table)); + if (device_config_table == NULL) + return FWK_E_NOMEM; + + return FWK_SUCCESS; +} + +static int pl011_element_init(fwk_id_t element_id, unsigned int unused, + const void *data) +{ + struct pl011_reg *reg; + const struct mod_pl011_device_config *config = data; + int status; + + reg = (struct pl011_reg *)config->reg_base; + if (reg == NULL) + return FWK_E_DATA; + + status = set_baud_rate(config->baud_rate_bps, + config->clock_rate_hz, + reg); + if (status != FWK_SUCCESS) + return status; + + /* + * Initialize PL011 device + */ + reg->ECR = PL011_ECR_CLR; + reg->LCR_H = PL011_LCR_H_WLEN_8BITS | + PL011_LCR_H_FEN; + reg->CR = PL011_CR_UARTEN | + PL011_CR_RXE | + PL011_CR_TXE; + + device_config_table[fwk_id_get_element_idx(element_id)] = config; + + return FWK_SUCCESS; +} + +static int pl011_process_bind_request(fwk_id_t requester_id, fwk_id_t target_id, + fwk_id_t api_id, const void **api) +{ + *api = &driver_api; + + return FWK_SUCCESS; +} + +static int pl011_start(fwk_id_t id) +{ + const struct mod_pl011_device_config *config; + + if (!fwk_id_is_type(id, FWK_ID_TYPE_ELEMENT)) + return FWK_SUCCESS; + + config = device_config_table[fwk_id_get_element_idx(id)]; + + if (fwk_id_is_type(config->clock_id, FWK_ID_TYPE_NONE)) + return FWK_SUCCESS; + + return fwk_notification_subscribe( + mod_clock_notification_id_state_change_pending, + config->clock_id, + id); +} + +int pl011_process_notification( + const struct fwk_event *event, + struct fwk_event *resp_event) +{ + struct clock_notification_params *params; + struct clock_state_change_pending_resp_params *resp_params; + + assert( + fwk_id_is_equal( + event->id, + mod_clock_notification_id_state_change_pending)); + assert(fwk_id_is_type(event->target_id, FWK_ID_TYPE_ELEMENT)); + + resp_params = + (struct clock_state_change_pending_resp_params *)resp_event->params; + resp_params->status = FWK_SUCCESS; + + params = (struct clock_notification_params *)event->params; + + if (params->new_state == MOD_CLOCK_STATE_STOPPED) + return do_flush(event->target_id); + + return FWK_SUCCESS; +} + +const struct fwk_module module_pl011 = { + .name = "PL011", + .type = FWK_MODULE_TYPE_DRIVER, + .api_count = 1, + .init = pl011_init, + .element_init = pl011_element_init, + .start = pl011_start, + .process_bind_request = pl011_process_bind_request, + .process_notification = pl011_process_notification +}; diff --git a/module/pl011/src/pl011.h b/module/pl011/src/pl011.h new file mode 100644 index 00000000..00f6f8e5 --- /dev/null +++ b/module/pl011/src/pl011.h @@ -0,0 +1,162 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * PL011 (UART) register definitions + */ + +#ifndef PL011_H +#define PL011_H + +#include <fwk_macros.h> + +struct pl011_reg { + FWK_RW uint16_t DR; + uint16_t RESERVED0; + union { + FWK_RW uint8_t RSR; + FWK_RW uint8_t ECR; + }; + uint8_t RESERVED1[0x18 - 0x05]; + FWK_R uint16_t FR; + uint16_t RESERVED2[3]; + FWK_RW uint8_t ILPR; + uint8_t RESERVED3[3]; + FWK_RW uint16_t IBRD; + uint16_t RESERVED4; + FWK_RW uint32_t FBRD; + FWK_RW uint16_t LCR_H; + uint16_t RESERVED5; + FWK_RW uint16_t CR; + uint16_t RESERVED6; + FWK_RW uint16_t IFLS; + uint16_t RESERVED7; + FWK_RW uint16_t IMSC; + uint16_t RESERVED8; + FWK_R uint16_t RIS; + uint16_t RESERVED9; + FWK_R uint16_t MIS; + uint16_t RESERVED10; + FWK_W uint16_t ICR; + uint16_t RESERVED11; + FWK_RW uint16_t DMACR; + uint8_t RESERVED12[0xFE0 - 0x4C]; + FWK_R uint32_t PID0; + FWK_R uint32_t PID1; + FWK_R uint32_t PID2; + FWK_R uint32_t PID3; + FWK_R uint32_t CID0; + FWK_R uint32_t CID1; + FWK_R uint32_t CID2; + FWK_R uint32_t CID3; +}; + +#define PL011_DR_DATA UINT16_C(0x00FF) +#define PL011_DR_FE UINT16_C(0x0100) +#define PL011_DR_PE UINT16_C(0x0200) +#define PL011_DR_BE UINT16_C(0x0400) +#define PL011_DR_OE UINT16_C(0x0800) + +#define PL011_RSR_FE UINT8_C(0x01) +#define PL011_RSR_PE UINT8_C(0x02) +#define PL011_RSR_BE UINT8_C(0x04) +#define PL011_RSR_OE UINT8_C(0x08) +#define PL011_ECR_CLR UINT8_C(0xFF) + +#define PL011_FR_CTS UINT16_C(0x0001) +#define PL011_FR_DSR UINT16_C(0x0002) +#define PL011_FR_DCD UINT16_C(0x0004) +#define PL011_FR_BUSY UINT16_C(0x0008) +#define PL011_FR_RXFE UINT16_C(0x0010) +#define PL011_FR_TXFF UINT16_C(0x0020) +#define PL011_FR_RXFF UINT16_C(0x0040) +#define PL011_FR_TXFE UINT16_C(0x0080) +#define PL011_FR_RI UINT16_C(0x0100) + +#define PL011_LCR_H_BRK UINT16_C(0x0001) +#define PL011_LCR_H_PEN UINT16_C(0x0002) +#define PL011_LCR_H_EPS UINT16_C(0x0004) +#define PL011_LCR_H_STP2 UINT16_C(0x0008) +#define PL011_LCR_H_FEN UINT16_C(0x0010) +#define PL011_LCR_H_WLEN UINT16_C(0x0060) +#define PL011_LCR_H_WLEN_5BITS UINT16_C(0x0000) +#define PL011_LCR_H_WLEN_6BITS UINT16_C(0x0020) +#define PL011_LCR_H_WLEN_7BITS UINT16_C(0x0040) +#define PL011_LCR_H_WLEN_8BITS UINT16_C(0x0060) +#define PL011_LCR_H_SPS UINT16_C(0x0080) + +#define PL011_CR_UARTEN UINT16_C(0x0001) +#define PL011_CR_SIREN UINT16_C(0x0002) +#define PL011_CR_SIRLP UINT16_C(0x0004) +#define PL011_CR_LBE UINT16_C(0x0080) +#define PL011_CR_TXE UINT16_C(0x0100) +#define PL011_CR_RXE UINT16_C(0x0200) +#define PL011_CR_DTR UINT16_C(0x0400) +#define PL011_CR_RTS UINT16_C(0x0800) +#define PL011_CR_OUT1 UINT16_C(0x1000) +#define PL011_CR_OUT2 UINT16_C(0x2000) +#define PL011_CR_RTSEN UINT16_C(0x4000) +#define PL011_CR_CTSEN UINT16_C(0x8000) + +#define PL011_IFLS_TXIFLSEL UINT16_C(0x0007) +#define PL011_IFLS_RXIFLSEL UINT16_C(0x0038) + +#define PL011_IMSC_RIMIM UINT16_C(0x0001) +#define PL011_IMSC_CTSMIM UINT16_C(0x0002) +#define PL011_IMSC_DCDMIM UINT16_C(0x0004) +#define PL011_IMSC_DSRMIM UINT16_C(0x0008) +#define PL011_IMSC_RXIM UINT16_C(0x0010) +#define PL011_IMSC_TXIM UINT16_C(0x0020) +#define PL011_IMSC_RTIM UINT16_C(0x0040) +#define PL011_IMSC_FEIM UINT16_C(0x0080) +#define PL011_IMSC_PEIM UINT16_C(0x0100) +#define PL011_IMSC_BEIM UINT16_C(0x0200) +#define PL011_IMSC_OEIM UINT16_C(0x0400) + +#define PL011_RIS_RIRMIS UINT16_C(0x0001) +#define PL011_RIS_CTSRMIS UINT16_C(0x0002) +#define PL011_RIS_DCDRMIS UINT16_C(0x0004) +#define PL011_RIS_DSRRMIS UINT16_C(0x0008) +#define PL011_RIS_RXRIS UINT16_C(0x0010) +#define PL011_RIS_TXRIS UINT16_C(0x0020) +#define PL011_RIS_RTRIS UINT16_C(0x0040) +#define PL011_RIS_FERIS UINT16_C(0x0080) +#define PL011_RIS_PERIS UINT16_C(0x0100) +#define PL011_RIS_BERIS UINT16_C(0x0200) +#define PL011_RIS_OERIS UINT16_C(0x0400) + +#define PL011_MIS_RIMMIS UINT16_C(0x0001) +#define PL011_MIS_CTSMMIS UINT16_C(0x0002) +#define PL011_MIS_DCDMMIS UINT16_C(0x0004) +#define PL011_MIS_DSRMMIS UINT16_C(0x0008) +#define PL011_MIS_RXMIS UINT16_C(0x0010) +#define PL011_MIS_TXMIS UINT16_C(0x0020) +#define PL011_MIS_RTMIS UINT16_C(0x0040) +#define PL011_MIS_FEMIS UINT16_C(0x0080) +#define PL011_MIS_PEMIS UINT16_C(0x0100) +#define PL011_MIS_BEMIS UINT16_C(0x0200) +#define PL011_MIS_OEMIS UINT16_C(0x0400) + +#define PL011_ICR_RIMIC UINT16_C(0x0001) +#define PL011_ICR_CTSMIC UINT16_C(0x0002) +#define PL011_ICR_DCDMIC UINT16_C(0x0004) +#define PL011_ICR_DSRMIC UINT16_C(0x0008) +#define PL011_ICR_RXIC UINT16_C(0x0010) +#define PL011_ICR_TXIC UINT16_C(0x0020) +#define PL011_ICR_RTIC UINT16_C(0x0040) +#define PL011_ICR_FEIC UINT16_C(0x0080) +#define PL011_ICR_PEIC UINT16_C(0x0100) +#define PL011_ICR_BEIC UINT16_C(0x0200) +#define PL011_ICR_OEIC UINT16_C(0x0400) + +#define PL011_DMACR_RXDMAE UINT16_C(0x0001) +#define PL011_DMACR_TXDMAE UINT16_C(0x0002) +#define PL011_DMACR_DMAAONERR UINT16_C(0x0004) + +#define PL011_UARTCLK_MIN (1420 * FWK_KHZ) +#define PL011_UARTCLK_MAX (542720 * FWK_KHZ) + +#endif /* PL011 */ diff --git a/module/power_domain/include/mod_power_domain.h b/module/power_domain/include/mod_power_domain.h new file mode 100644 index 00000000..ed204f83 --- /dev/null +++ b/module/power_domain/include/mod_power_domain.h @@ -0,0 +1,922 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * Power domain management support. + */ + +#ifndef MOD_POWER_DOMAIN_H +#define MOD_POWER_DOMAIN_H + +#include <limits.h> +#include <stdbool.h> +#include <stdint.h> +#include <fwk_element.h> +#include <fwk_id.h> +#include <fwk_module_idx.h> + +/*! + * \addtogroup GroupModules Modules + * @{ + */ + +/*! + * \defgroup GroupPowerDomain 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 type Pointer to storage for the type of the power domain. + * + * \retval FWK_SUCCESS The type of the power domain was returned. + * \retval FWK_E_STATE The module has not been initialized. + * \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 parent_pd_id Pointer to storage for the identifier of the parent + * power domain. + * + * \retval FWK_SUCCESS The identifier of the parent power domain was + * returned. + * \retval FWK_E_STATE The component has not been initialized. + * \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 type Pointer to storage for the type of the power domain. + * + * \retval FWK_SUCCESS The type of the power domain was returned. + * \retval FWK_E_STATE The module has not been initialized. + * \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 parent_pd_id Pointer to storage for the identifier of the parent + * power domain. + * + * \retval FWK_SUCCESS The identifier of the parent power domain was + * returned. + * \retval FWK_E_STATE The component has not been initialized. + * \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. + * + * \note The function sets the state of the power domain identified by + * 'pd_id' synchronously from the point of view of the caller. + * + * \param pd_id Identifier of the power domain whose state has to be set. + * \param state State of the power domain. + * + * \retval FWK_SUCCESS The power state was set. + * \retval FWK_E_ACCESS Invalid access, the framework has rejected the + * call to the API. + * \retval FWK_E_PARAM The power domain identifier is unknown. + * \retval FWK_E_PARAM The 'state' is not valid. + */ + int (*set_state)(fwk_id_t pd_id, unsigned int 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 state State of the power domain. + * + * \retval FWK_SUCCESS The power state transition request 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, + unsigned int state); + + /*! + * \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 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 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_composite_state)(fwk_id_t pd_id, uint32_t composite_state); + + /*! + * \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_composite_state_async)(fwk_id_t pd_id, bool resp_requested, + uint32_t composite_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 state Pointer to storage for 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 Get the composite state of a power domain and its ancestors (if + * any) in the power domain tree. + * + * \note The function gets the composite state of the power domain + * identified by 'pd_id' and its ancestors (if any) synchronously from + * the point of view of the calling thread. + * + * \param pd_id Identifier of the power domain whose composite state has to + * be retrieved. + * \param composite_state Pointer to storage for the power domain composite + * state. + * + * \retval FWK_SUCCESS The composite 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 'composite state' is equal to NULL. + */ + int (*get_composite_state)(fwk_id_t pd_id, unsigned int *composite_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_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 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 state State of the power domain. + * + * \retval FWK_SUCCESS The power state transition request 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, + unsigned int state); + + /*! + * \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_composite_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 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, +}; + +#if BUILD_HAS_MOD_POWER_DOMAIN +/*! Public API identifier */ +static const fwk_id_t mod_pd_api_id_public = + FWK_ID_API_INIT(FWK_MODULE_IDX_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_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_POWER_DOMAIN, MOD_PD_API_IDX_DRIVER_INPUT); +#endif + +/*! + * \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. + */ +#if BUILD_HAS_MOD_POWER_DOMAIN +/*! 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_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_POWER_DOMAIN, + MOD_PD_NOTIFICATION_IDX_POWER_STATE_PRE_TRANSITION); +#endif + +/*! + * \} + */ + +/*! + * @} + */ + +/*! + * @} + */ + +#endif /* MOD_POWER_DOMAIN_H */ diff --git a/module/power_domain/src/Makefile b/module/power_domain/src/Makefile new file mode 100644 index 00000000..1c4c5fe8 --- /dev/null +++ b/module/power_domain/src/Makefile @@ -0,0 +1,11 @@ +# +# Arm SCP/MCP Software +# Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +BS_LIB_NAME := Power domain +BS_LIB_SOURCES := mod_power_domain.c + +include $(BS_DIR)/lib.mk diff --git a/module/power_domain/src/mod_power_domain.c b/module/power_domain/src/mod_power_domain.c new file mode 100644 index 00000000..cba05ed4 --- /dev/null +++ b/module/power_domain/src/mod_power_domain.c @@ -0,0 +1,1999 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * Power domain management support. + */ + +#include <assert.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> +#include <fwk_element.h> +#include <fwk_errno.h> +#include <fwk_id.h> +#include <fwk_macros.h> +#include <fwk_mm.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <fwk_thread.h> +#include <fwk_multi_thread.h> +#include <fwk_notification.h> +#include <mod_log.h> +#include <mod_power_domain.h> + +/* + * 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; +}; + +/* 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 struct mod_pd_ctx mod_pd_ctx; +static const char driver_error_msg[] = "[PD] Driver error %e in %s @%d\n"; + +static const unsigned int tree_pos_level_shift[MOD_PD_LEVEL_COUNT] = { + MOD_PD_TREE_POS_LEVEL_0_SHIFT, + MOD_PD_TREE_POS_LEVEL_1_SHIFT, + MOD_PD_TREE_POS_LEVEL_2_SHIFT, + MOD_PD_TREE_POS_LEVEL_3_SHIFT +}; + +static const char * const default_state_name_table[] = { + "OFF", "ON", "SLEEP", "3", "4", "5", "6", "7", + "8", "9", "10", "11", "12", "13", "14", "15" +}; + +/* + * 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); +} + +static uint64_t compute_parent_tree_pos_from_tree_pos(uint64_t tree_pos) +{ + uint64_t parent_tree_pos; + unsigned int level; + + level = get_level_from_tree_pos(tree_pos); + + parent_tree_pos = (tree_pos & + (~((((uint64_t)1) << tree_pos_level_shift[level+1])-1))) + + (((uint64_t)1) << MOD_PD_TREE_POS_LEVEL_SHIFT); + + return parent_tree_pos; +} + +/* + * Get a pointer to the descriptor of a power domain given its position in the + * power domain tree. + * + * \param tree_pos The power domain position in the power domain tree. + * + * \retval NULL The tree position of the power domain is invalid. + * \return Pointer to the descriptor of the power domain. + */ +static struct pd_ctx *get_ctx_from_tree_pos(uint64_t tree_pos) +{ + unsigned int min_idx = 0; + unsigned int max_idx_plus_one = mod_pd_ctx.pd_count; + unsigned int middle_idx; + struct pd_ctx *pd; + + while (min_idx < max_idx_plus_one) { + middle_idx = (min_idx + max_idx_plus_one) / 2; + pd = &mod_pd_ctx.pd_ctx_table[middle_idx]; + if (pd->config->tree_pos == tree_pos) + return pd; + else { + if (pd->config->tree_pos > tree_pos) + max_idx_plus_one = middle_idx; + else + min_idx = middle_idx + 1; + } + } + + return NULL; +} + +/* 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 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 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: + mod_pd_ctx.log_api->log(MOD_LOG_GROUP_ERROR, + "[PD] Invalid composite state for %s: 0x%08x\n", + fwk_module_get_name(target_pd->id), composite_state); + return false; +} + +/* + * 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; +} + +/* Sub-routine of 'pd_post_init()', to build the power domain tree */ +static int build_pd_tree(void) +{ + unsigned int index; + struct pd_ctx *pd; + uint64_t tree_pos; + uint64_t parent_tree_pos; + uint64_t last_parent_tree_pos; + struct pd_ctx *parent = NULL; + struct pd_ctx *child; + struct pd_ctx *prev_sibling; + + last_parent_tree_pos = 0; /* Impossible value for a parent position */ + for (index = 0; index < mod_pd_ctx.pd_count; index++) { + pd = &mod_pd_ctx.pd_ctx_table[index]; + tree_pos = pd->config->tree_pos; + parent_tree_pos = compute_parent_tree_pos_from_tree_pos(tree_pos); + if (parent_tree_pos != last_parent_tree_pos) { + parent = get_ctx_from_tree_pos(parent_tree_pos); + last_parent_tree_pos = parent_tree_pos; + } + pd->parent = parent; + + if (parent == NULL) { + if (index == (mod_pd_ctx.pd_count - 1)) + break; + else + return FWK_E_PARAM; + } + + /* + * Update the list of children of the power domain parent. The children + * are in increasing order of their identifier in the chain of children. + */ + child = parent->first_child; + prev_sibling = NULL; + + while ((child != NULL) && (child->config->tree_pos < tree_pos)) { + prev_sibling = child; + child = child->sibling; + } + + if (prev_sibling == NULL) { + pd->sibling = parent->first_child; + parent->first_child = pd; + } else { + pd->sibling = prev_sibling->sibling; + prev_sibling->sibling = pd; + } + } + + return FWK_SUCCESS; +} + +/* + * 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; + + if ((pd->driver_api->deny != NULL) && + pd->driver_api->deny(pd->driver_id, state)) { + mod_pd_ctx.log_api->log(MOD_LOG_GROUP_ERROR, + "[PD] Transition of %s to state <%s>,\n", + fwk_module_get_name(pd->id), get_state_name(pd, state)); + mod_pd_ctx.log_api->log(MOD_LOG_GROUP_ERROR, + "\tdenied by driver.\n"); + return FWK_E_DEVICE; + } + + status = pd->driver_api->set_state(pd->driver_id, state); + + mod_pd_ctx.log_api->log(MOD_LOG_GROUP_DEBUG, + "[PD] %s: %s->%s, %e\n", 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); +} + +/* + * Process a 'set state' request + * + * \param lowest_pd Description of the target of the 'set state' request + * \param req_params Parameters of the 'set state' request + * \param [out] Response event + */ +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; + struct pd_ctx *pd_in_charge_of_response = NULL; + 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; + + /* + * The driver must be called thus the processing of the set state + * request is going to be asynchronous. Assign the responsability of + * the response to the request to the power domain. If there is no + * need for a driver call for the ancestors or descendants of the power + * domain as part of the processing of the requested composite state, + * the response to the request will be sent when the transition to the + * new requested power state is completed. + */ + pd_in_charge_of_response = pd; + + /* + * 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; + + if (pd_in_charge_of_response != NULL) { + resp_event->is_delayed_response = true; + resp_event->source_id = pd_in_charge_of_response->id; + pd_in_charge_of_response->response.pending = true; + pd_in_charge_of_response->response.cookie = resp_event->cookie; + } else { + 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; + + 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) +{ + int status; + struct pd_ctx *child; + + status = FWK_E_PWRSTATE; + if (pd->requested_state == MOD_PD_STATE_OFF) + goto exit; + + child = pd->first_child; + while (child != NULL) { + if ((child->requested_state != MOD_PD_STATE_OFF) || + (child->current_state != MOD_PD_STATE_OFF)) + goto exit; + child = child->sibling; + } + + status = pd->driver_api->reset(pd->driver_id); + +exit: + resp_params->status = status; +} + +/* + * 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) { + 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_POWER_DOMAIN, pd_idx); + + mod_pd_ctx.log_api->log(MOD_LOG_GROUP_DEBUG, + "[PD] Shutting down %s\n", 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) + mod_pd_ctx.log_api->log(MOD_LOG_GROUP_ERROR, + "[PD] Shutdown of %s returned %e\n", + fwk_module_get_name(pd_id), status); + else + mod_pd_ctx.log_api->log(MOD_LOG_GROUP_DEBUG, + "[PD] %s shutdown\n", 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) +{ + int status; + struct pd_ctx *pd; + + status = fwk_module_check_call(pd_id); + if (status != FWK_SUCCESS) + return status; + + 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) +{ + int status; + const struct pd_ctx *pd; + + status = fwk_module_check_call(pd_id); + if (status != FWK_SUCCESS) + return status; + + 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); + + status = fwk_module_check_call(pd_id); + if (status != FWK_SUCCESS) + return status; + + 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.target_id = pd_id; + req.id = FWK_ID_EVENT(FWK_MODULE_IDX_POWER_DOMAIN, PD_EVENT_IDX_SET_STATE); + 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; +} + +static int pd_set_state_async(fwk_id_t pd_id, + bool response_requested, unsigned int state) +{ + int status; + struct pd_ctx *pd; + enum mod_pd_level level; + struct fwk_event req = { }; + struct pd_set_state_request *req_params = + (struct pd_set_state_request *)(&req.params); + + status = fwk_module_check_call(pd_id); + if (status != FWK_SUCCESS) + return status; + + 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.source_id = pd->driver_id; + req.target_id = pd_id; + req.id = FWK_ID_EVENT(FWK_MODULE_IDX_POWER_DOMAIN, PD_EVENT_IDX_SET_STATE); + req.response_requested = response_requested; + req_params->composite_state = (level << MOD_PD_CS_LEVEL_SHIFT) | + (state << mod_pd_cs_level_state_shift[level]); + + return fwk_thread_put_event(&req); +} + +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); + + status = fwk_module_check_call(pd_id); + if (status != FWK_SUCCESS) + return status; + + 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.source_id = pd->driver_id; + req.target_id = pd_id; + req.id = FWK_ID_EVENT(FWK_MODULE_IDX_POWER_DOMAIN, PD_EVENT_IDX_SET_STATE); + 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) +{ + int status; + struct pd_ctx *pd; + struct fwk_event req = { }; + struct pd_set_state_request *req_params = + (struct pd_set_state_request *)(&req.params); + + status = fwk_module_check_call(pd_id); + if (status != FWK_SUCCESS) + return status; + + 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.source_id = pd->driver_id; + req.target_id = pd_id; + req.id = FWK_ID_EVENT(FWK_MODULE_IDX_POWER_DOMAIN, PD_EVENT_IDX_SET_STATE); + req.response_requested = response_requested; + req_params->composite_state = composite_state; + + return fwk_thread_put_event(&req); +} + +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); + + status = fwk_module_check_call(pd_id); + if (status != FWK_SUCCESS) + return status; + + if (state == NULL) + return FWK_E_PARAM; + + req.target_id = pd_id; + req.id = FWK_ID_EVENT(FWK_MODULE_IDX_POWER_DOMAIN, PD_EVENT_IDX_GET_STATE); + 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; +} + +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); + + status = fwk_module_check_call(pd_id); + if (status != FWK_SUCCESS) + return status; + + if (composite_state == NULL) + return FWK_E_PARAM; + + req.target_id = pd_id; + req.id = FWK_ID_EVENT(FWK_MODULE_IDX_POWER_DOMAIN, PD_EVENT_IDX_GET_STATE); + 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; +} + +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); + + status = fwk_module_check_call(pd_id); + if (status != FWK_SUCCESS) + return status; + + req.target_id = pd_id; + req.id = FWK_ID_EVENT(FWK_MODULE_IDX_POWER_DOMAIN, PD_EVENT_IDX_RESET); + + 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); + + status = fwk_module_check_call(fwk_module_id_power_domain); + if (status != FWK_SUCCESS) + return status; + + req.target_id = fwk_module_id_power_domain; + req.id = FWK_ID_EVENT(FWK_MODULE_IDX_POWER_DOMAIN, + PD_EVENT_IDX_SYSTEM_SUSPEND); + 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); + + status = fwk_module_check_call(fwk_module_id_power_domain); + if (status != FWK_SUCCESS) + return status; + + req.target_id = fwk_module_id_power_domain; + req.id = FWK_ID_EVENT(FWK_MODULE_IDX_POWER_DOMAIN, + PD_EVENT_IDX_SYSTEM_SHUTDOWN); + 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) +{ + int status; + struct fwk_event req = { }; + struct pd_ctx *pd; + + status = fwk_module_check_call(pd_id); + if (status != FWK_SUCCESS) + return status; + + pd = &mod_pd_ctx.pd_ctx_table[fwk_id_get_element_idx(pd_id)]; + + req.source_id = pd->driver_id; + req.target_id = pd_id; + req.id = FWK_ID_EVENT(FWK_MODULE_IDX_POWER_DOMAIN, PD_EVENT_IDX_RESET); + req.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_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) +{ + int status; + const struct pd_ctx *pd; + + status = fwk_module_check_call(pd_id); + if (status != FWK_SUCCESS) + return status; + + pd = &mod_pd_ctx.pd_ctx_table[fwk_id_get_element_idx(pd_id)]; + + return report_power_state_transition(pd, state); +} + +/* 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_state_async, + .set_composite_state = pd_set_composite_state, + .set_composite_state_async = pd_set_composite_state_async, + .get_state = pd_get_state, + .get_composite_state = pd_get_composite_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_state_async, + .set_composite_state_async = pd_set_composite_state_async, + .reset_async = pd_reset_async, + .report_power_state_transition = pd_report_power_state_transition, +}; + +/* + * 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) +{ + int status; + + status = build_pd_tree(); + if (status != FWK_SUCCESS) + return status; + + 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; + + /* Nothing to do but during the first round of calls */ + if (round != 0) + return FWK_SUCCESS; + + 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); + } + + 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) +{ + int status; + int index; + struct pd_ctx *pd; + unsigned int state; + + /* 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; + + /* + * If the power domain parent is powered down, don't call the driver + * to get the power domain state as the power domain registers may + * not be accessible. That way, the drivers don't have to care about + * this case. + */ + if ((pd->parent != NULL) && + (pd->parent->requested_state == MOD_PD_STATE_OFF)) + continue; + + /* Get the current power state of the power domain from its driver. */ + status = pd->driver_api->get_state(pd->driver_id, &state); + if (status != FWK_SUCCESS) { + mod_pd_ctx.log_api->log(MOD_LOG_GROUP_ERROR, driver_error_msg, + status, __func__, __LINE__); + } else { + pd->requested_state = pd->state_requested_to_driver = state; + + if (state == MOD_PD_STATE_OFF) + continue; + + report_power_state_transition(pd, state); + } + } + + 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: + mod_pd_ctx.log_api->log( + MOD_LOG_GROUP_ERROR, + "[PD] Invalid power state request: <%d>.\n", + event->id); + + 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_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/module/ppu_v0/include/mod_ppu_v0.h b/module/ppu_v0/include/mod_ppu_v0.h new file mode 100644 index 00000000..d18aa443 --- /dev/null +++ b/module/ppu_v0/include/mod_ppu_v0.h @@ -0,0 +1,63 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * PPU v0 driver module + */ + +#ifndef MOD_PPU_V0_H +#define MOD_PPU_V0_H + +#include <stdbool.h> +#include <stdint.h> +#include <mod_power_domain.h> + +/*! + * \addtogroup GroupModules Modules + * @{ + */ + +/*! + * \defgroup GroupModulePPUv0 PPUv0 Driver + * @{ + */ + +/*! + * \brief Power domain PPU descriptor. + */ +struct mod_ppu_v0 { + /*! Base address of the PPU registers */ + uintptr_t reg_base; + + /*! PPU's IRQ number */ + unsigned int irq; +}; + +/*! + * \brief Configuration data of a power domain of the PPU_V0 driver module. + */ +struct mod_ppu_v0_pd_config { + /*! Power domain type */ + enum mod_pd_type pd_type; + + /*! PPU descriptor */ + struct mod_ppu_v0 ppu; + + /*! + * Flag indicating if this domain should be powered on during element init. + */ + bool default_power_on; +}; + +/*! + * @} + */ + +/*! + * @} + */ + +#endif /* MOD_PPU_V0_H */ diff --git a/module/ppu_v0/src/Makefile b/module/ppu_v0/src/Makefile new file mode 100644 index 00000000..c958e926 --- /dev/null +++ b/module/ppu_v0/src/Makefile @@ -0,0 +1,11 @@ +# +# Arm SCP/MCP Software +# Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +BS_LIB_NAME := PPU_V0 +BS_LIB_SOURCES = ppu_v0.c mod_ppu_v0.c + +include $(BS_DIR)/lib.mk diff --git a/module/ppu_v0/src/mod_ppu_v0.c b/module/ppu_v0/src/mod_ppu_v0.c new file mode 100644 index 00000000..1d207d62 --- /dev/null +++ b/module/ppu_v0/src/mod_ppu_v0.c @@ -0,0 +1,316 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * Power State Management PPU v0 driver. + */ + +#include <assert.h> +#include <stdint.h> +#include <fwk_id.h> +#include <fwk_macros.h> +#include <fwk_mm.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <mod_log.h> +#include <mod_power_domain.h> +#include <mod_ppu_v0.h> +#include <ppu_v0.h> +#if BUILD_HAS_MOD_SYSTEM_POWER +#include <mod_system_power.h> +#endif + +/* Power domain context */ +struct ppu_v0_pd_ctx { + /* Power domain configuration data */ + const struct mod_ppu_v0_pd_config *config; + + /* PPU registers */ + struct ppu_v0_reg *ppu; + + /* Identifier of the entity bound to the power domain driver API */ + fwk_id_t bound_id; + + /* Power module driver input API */ + struct mod_pd_driver_input_api *pd_driver_input_api; +}; + +/* Module context */ +struct ppu_v0_ctx { + /* Table of the power domain contexts */ + struct ppu_v0_pd_ctx *pd_ctx_table; + + /* Log API */ + struct mod_log_api *log_api; +}; + +/* + * Internal variables + */ + +static struct ppu_v0_ctx ppu_v0_ctx; + +#define MODE_UNSUPPORTED ~0U +static const uint8_t ppu_mode_to_power_state[] = { + [PPU_V0_MODE_ON] = (uint8_t)MOD_PD_STATE_ON, + [PPU_V0_MODE_FUNC_RET] = (uint8_t)MOD_PD_STATE_ON, + [PPU_V0_MODE_MEM_OFF] = (uint8_t)MODE_UNSUPPORTED, + [PPU_V0_MODE_FULL_RET] = (uint8_t)MOD_PD_STATE_ON, + [PPU_V0_MODE_LOGIC_RET] = (uint8_t)MODE_UNSUPPORTED, + [PPU_V0_MODE_MEM_RET] = (uint8_t)MOD_PD_STATE_ON, + [PPU_V0_MODE_OFF] = (uint8_t)MOD_PD_STATE_OFF, + [PPU_V0_MODE_WARM_RESET] = (uint8_t)MODE_UNSUPPORTED, +}; + +/* + * Power domain driver interface + */ + +/* Driver functions not specific to any type of power domain. */ +static int get_state(struct ppu_v0_reg *ppu, unsigned int *state) +{ + enum ppu_v0_mode ppu_mode; + + /* Ensure ppu_mode_to_power_state has an entry for each PPU state */ + static_assert(FWK_ARRAY_SIZE(ppu_mode_to_power_state) == PPU_V0_MODE_COUNT, + "[MOD_PPU_V0] ppu_mode_to_power_state size error"); + + ppu_v0_get_power_mode(ppu, &ppu_mode); + assert(ppu_mode < PPU_V0_MODE_COUNT); + + *state = ppu_mode_to_power_state[ppu_mode]; + if (*state == MODE_UNSUPPORTED) { + ppu_v0_ctx.log_api->log(MOD_LOG_GROUP_ERROR, + "[PD] Unexpected PPU mode (%i).\n", ppu_mode); + return FWK_E_DEVICE; + } + + return FWK_SUCCESS; +} + +static int pd_init(struct ppu_v0_pd_ctx *pd_ctx) +{ + ppu_v0_init(pd_ctx->ppu); + + return FWK_SUCCESS; +} + +static int pd_set_state(fwk_id_t pd_id, unsigned int state) +{ + int status; + struct ppu_v0_pd_ctx *pd_ctx; + + status = fwk_module_check_call(pd_id); + if (status != FWK_SUCCESS) + return status; + + pd_ctx = ppu_v0_ctx.pd_ctx_table + fwk_id_get_element_idx(pd_id); + + switch (state) { + case MOD_PD_STATE_ON: + ppu_v0_set_power_mode(pd_ctx->ppu, PPU_V0_MODE_ON); + status = pd_ctx->pd_driver_input_api->report_power_state_transition( + pd_ctx->bound_id, MOD_PD_STATE_ON); + assert(status == FWK_SUCCESS); + break; + + case MOD_PD_STATE_OFF: + ppu_v0_set_power_mode(pd_ctx->ppu, PPU_V0_MODE_OFF); + status = pd_ctx->pd_driver_input_api->report_power_state_transition( + pd_ctx->bound_id, MOD_PD_STATE_OFF); + assert(status == FWK_SUCCESS); + break; + + default: + ppu_v0_ctx.log_api->log(MOD_LOG_GROUP_ERROR, + "[PD] Requested power state (%i) is not supported.\n", state); + return FWK_E_PARAM; + } + + return FWK_SUCCESS; +} + +static int pd_get_state(fwk_id_t pd_id, unsigned int *state) +{ + int status; + struct ppu_v0_pd_ctx *pd_ctx; + + status = fwk_module_check_call(pd_id); + if (status != FWK_SUCCESS) + return status; + + pd_ctx = ppu_v0_ctx.pd_ctx_table + fwk_id_get_element_idx(pd_id); + + return get_state(pd_ctx->ppu, state); +} + +static int pd_reset(fwk_id_t pd_id) +{ + int status; + struct ppu_v0_pd_ctx *pd_ctx; + + status = fwk_module_check_call(pd_id); + if (status != FWK_SUCCESS) + return status; + + pd_ctx = ppu_v0_ctx.pd_ctx_table + fwk_id_get_element_idx(pd_id); + + /* Model does not support warm reset at the moment. Using OFF instead. */ + status = ppu_v0_set_power_mode(pd_ctx->ppu, PPU_V0_MODE_OFF); + if (status == FWK_SUCCESS) + status = ppu_v0_set_power_mode(pd_ctx->ppu, PPU_V0_MODE_ON); + + return status; +} + +static const struct mod_pd_driver_api pd_driver = { + .set_state = pd_set_state, + .get_state = pd_get_state, + .reset = pd_reset, +}; + +/* + * Framework handlers + */ + +static int ppu_v0_mod_init(fwk_id_t module_id, unsigned int pd_count, + const void *unused) +{ + ppu_v0_ctx.pd_ctx_table = fwk_mm_calloc(pd_count, + sizeof(struct ppu_v0_pd_ctx)); + if (ppu_v0_ctx.pd_ctx_table == NULL) + return FWK_E_NOMEM; + + return FWK_SUCCESS; +} + +static int ppu_v0_pd_init(fwk_id_t pd_id, unsigned int unused, const void *data) +{ + const struct mod_ppu_v0_pd_config *config = data; + struct ppu_v0_pd_ctx *pd_ctx; + int status; + + if (config->pd_type >= MOD_PD_TYPE_COUNT) + return FWK_E_DATA; + + pd_ctx = ppu_v0_ctx.pd_ctx_table + fwk_id_get_element_idx(pd_id); + pd_ctx->config = config; + pd_ctx->ppu = (struct ppu_v0_reg *)(config->ppu.reg_base); + pd_ctx->bound_id = FWK_ID_NONE; + + switch (config->pd_type) { + case MOD_PD_TYPE_DEVICE: + case MOD_PD_TYPE_DEVICE_DEBUG: + case MOD_PD_TYPE_SYSTEM: + status = pd_init(pd_ctx); + if (status != FWK_SUCCESS) + return status; + + if (config->default_power_on) + return ppu_v0_set_power_mode(pd_ctx->ppu, PPU_V0_MODE_ON); + + return FWK_SUCCESS; + + default: + return FWK_E_SUPPORT; + } +} + +static int ppu_v0_bind(fwk_id_t id, unsigned int round) +{ + struct ppu_v0_pd_ctx *pd_ctx; + + /* Nothing to do during the first round of calls where the power module + will bind to the power domains of this module. */ + if (round == 0) + return FWK_SUCCESS; + + /* In the case of the module, bind to the log component */ + if (fwk_module_is_valid_module_id(id)) { + return fwk_module_bind(FWK_ID_MODULE(FWK_MODULE_IDX_LOG), + FWK_ID_API(FWK_MODULE_IDX_LOG, 0), + &ppu_v0_ctx.log_api); + } + + pd_ctx = ppu_v0_ctx.pd_ctx_table + fwk_id_get_element_idx(id); + + if (fwk_id_is_equal(pd_ctx->bound_id, FWK_ID_NONE)) + return FWK_SUCCESS; + + switch (fwk_id_get_module_idx(pd_ctx->bound_id)) { + #if BUILD_HAS_MOD_POWER_DOMAIN + case FWK_MODULE_IDX_POWER_DOMAIN: + return fwk_module_bind(pd_ctx->bound_id, + mod_pd_api_id_driver_input, + &pd_ctx->pd_driver_input_api); + break; + #endif + + #if BUILD_HAS_MOD_SYSTEM_POWER + case FWK_MODULE_IDX_SYSTEM_POWER: + return fwk_module_bind(pd_ctx->bound_id, + mod_system_power_api_id_pd_driver_input, + &pd_ctx->pd_driver_input_api); + break; + #endif + + default: + assert(false); + return FWK_E_SUPPORT; + } +} + +static int ppu_v0_process_bind_request(fwk_id_t source_id, + fwk_id_t target_id, fwk_id_t not_used, + const void **api) +{ + struct ppu_v0_pd_ctx *pd_ctx; + + pd_ctx = ppu_v0_ctx.pd_ctx_table + fwk_id_get_element_idx(target_id); + + switch (pd_ctx->config->pd_type) { + case MOD_PD_TYPE_SYSTEM: + if (!fwk_id_is_equal(pd_ctx->bound_id, FWK_ID_NONE)) { + assert(false); + return FWK_E_ACCESS; + } + /* Fallthrough */ + + case MOD_PD_TYPE_DEVICE: + case MOD_PD_TYPE_DEVICE_DEBUG: + #if BUILD_HAS_MOD_POWER_DOMAIN + if (fwk_id_get_module_idx(source_id) == FWK_MODULE_IDX_POWER_DOMAIN) { + pd_ctx->bound_id = source_id; + *api = &pd_driver; + break; + } + #endif + #if BUILD_HAS_MOD_SYSTEM_POWER + if (fwk_id_get_module_idx(source_id) == FWK_MODULE_IDX_SYSTEM_POWER) { + *api = &pd_driver; + break; + } + #endif + assert(false); + return FWK_E_ACCESS; + + default: + (void)pd_driver; + return FWK_E_SUPPORT; + } + + return FWK_SUCCESS; +} + +const struct fwk_module module_ppu_v0 = { + .name = "PPU_V0", + .type = FWK_MODULE_TYPE_DRIVER, + .api_count = 1, + .init = ppu_v0_mod_init, + .element_init = ppu_v0_pd_init, + .bind = ppu_v0_bind, + .process_bind_request = ppu_v0_process_bind_request, +}; diff --git a/module/ppu_v0/src/ppu_v0.c b/module/ppu_v0/src/ppu_v0.c new file mode 100644 index 00000000..cedaf24e --- /dev/null +++ b/module/ppu_v0/src/ppu_v0.c @@ -0,0 +1,64 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <assert.h> +#include <stddef.h> +#include <ppu_v0.h> +#include <fwk_errno.h> + +void ppu_v0_init(struct ppu_v0_reg *ppu) +{ + assert(ppu != NULL); + + /* Set mode as masked to all input edge interrupts */ + ppu->IESR = 0; + + /* Mask all interrupts */ + ppu->IMR = PPU_V0_IMR_MASK; + + /* Acknowledge any interrupt left pending */ + ppu->ISR = PPU_V0_ISR_MASK; +} + +int ppu_v0_request_power_mode(struct ppu_v0_reg *ppu, enum ppu_v0_mode mode) +{ + uint32_t power_policy; + assert(ppu != NULL); + assert(mode < PPU_V0_MODE_COUNT); + + power_policy = ppu->POWER_POLICY & + ~(PPU_V0_PPR_POLICY | PPU_V0_PPR_DYNAMIC_EN); + ppu->POWER_POLICY = power_policy | mode; + + return FWK_SUCCESS; +} + +int ppu_v0_set_power_mode(struct ppu_v0_reg *ppu, enum ppu_v0_mode mode) +{ + int status; + assert(ppu != NULL); + + status = ppu_v0_request_power_mode(ppu, mode); + if (status != FWK_SUCCESS) + return status; + + while ((ppu->POWER_STATUS & (PPU_V0_PSR_POWSTAT | PPU_V0_PSR_DYNAMIC)) + != mode) + continue; + + return FWK_SUCCESS; +} + +int ppu_v0_get_power_mode(struct ppu_v0_reg *ppu, enum ppu_v0_mode *mode) +{ + assert(ppu != NULL); + assert(mode != NULL); + + *mode = (enum ppu_v0_mode)(ppu->POWER_STATUS & PPU_V0_PSR_POWSTAT); + + return FWK_SUCCESS; +} diff --git a/module/ppu_v0/src/ppu_v0.h b/module/ppu_v0/src/ppu_v0.h new file mode 100644 index 00000000..b9e034cb --- /dev/null +++ b/module/ppu_v0/src/ppu_v0.h @@ -0,0 +1,144 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef PPU_V0_H +#define PPU_V0_H + +/*! + * \cond + * @{ + */ + +#include <stdbool.h> +#include <stdint.h> +#include <fwk_macros.h> + +struct ppu_v0_reg { + FWK_RW uint32_t POWER_POLICY; + FWK_R uint32_t POWER_STATUS; + FWK_R uint32_t STATIC_CFG_STATUS; + FWK_R uint32_t DEV_IF_IP_CUR_STATUS; + FWK_R uint32_t MISC_IP_CUR_STATUS; + FWK_R uint32_t STORED_STATUS; + FWK_W uint32_t OFF_MEM_RET_UNLOCK; + uint32_t RESERVED0; + FWK_RW uint32_t POWER_CFG; + uint32_t RESERVED1[3]; + FWK_RW uint32_t IMR; + FWK_RW uint32_t ISR; + FWK_RW uint32_t IESR; + uint32_t RESERVED2[5]; + FWK_RW uint32_t FUNC_RET_RAM_CFG; + FWK_RW uint32_t FULL_RET_RAM_CFG; + FWK_RW uint32_t MEM_RET_RAM_CFG; + uint32_t RESERVED3; + FWK_RW uint32_t MODE_ENTRY_DELAY_TIME_0; + FWK_RW uint32_t MODE_ENTRY_DELAY_TIME_1; + uint32_t RESERVED4[2]; + FWK_RW uint32_t DEV_CONTROL_DELAY_CFG_0; + FWK_RW uint32_t DEV_CONTROL_DELAY_CFG_1; + uint8_t RESERVED5[0xFC8 - 0x78]; + FWK_R uint32_t IMPLEMENTATION_ID; + FWK_R uint32_t ARCHITECTURE_ID; + FWK_R uint32_t PID4; + FWK_R uint32_t PID5; + FWK_R uint32_t PID6; + FWK_R uint32_t PID7; + FWK_R uint32_t PID0; + FWK_R uint32_t PID1; + FWK_R uint32_t PID2; + FWK_R uint32_t PID3; + FWK_R uint32_t CID0; + FWK_R uint32_t CID1; + FWK_R uint32_t CID2; + FWK_R uint32_t CID3; +}; + +enum ppu_v0_mode { + PPU_V0_MODE_OFF = 0, + PPU_V0_MODE_MEM_RET = 1, + PPU_V0_MODE_LOGIC_RET = 2, + PPU_V0_MODE_FULL_RET = 3, + PPU_V0_MODE_MEM_OFF = 4, + PPU_V0_MODE_FUNC_RET = 5, + PPU_V0_MODE_ON = 6, + PPU_V0_MODE_WARM_RESET = 7, + PPU_V0_MODE_COUNT, +}; + +/* + * Bit definitions for PPR + */ +#define PPU_V0_PPR_POLICY UINT32_C(0x00000007) +#define PPU_V0_PPR_DYNAMIC_EN UINT32_C(0x00000100) +#define PPU_V0_PPR_EMULATED_EN UINT32_C(0x00000200) +#define PPU_V0_PPR_OFF_LOCK_EN UINT32_C(0x00001000) + +/* + * Bit definitions for PSR + */ +#define PPU_V0_PSR_EMULATED UINT32_C(0x00000200) +#define PPU_V0_PSR_DYNAMIC UINT32_C(0x00000100) +#define PPU_V0_PSR_POWSTAT UINT32_C(0x00000007) + +/* + * Bit definitions for IMR + */ +#define PPU_V0_IMR_MASK UINT32_C(0x010000FF) +#define PPU_V0_IMR_STA_POLICY_TRN UINT32_C(0x00000001) +#define PPU_V0_IMR_STA_ACCEPT UINT32_C(0x00000002) +#define PPU_V0_IMR_STA_DENY UINT32_C(0x00000004) +#define PPU_V0_IMR_DYN_ACCEPT UINT32_C(0x00000008) +#define PPU_V0_IMR_DYN_DENY UINT32_C(0x00000010) +#define PPU_V0_IMR_EMU_ACCEPT UINT32_C(0x00000020) +#define PPU_V0_IMR_EMU_DENY UINT32_C(0x00000040) +#define PPU_V0_IMR_UNSPT_POLICY UINT32_C(0x00000080) +#define PPU_V0_IMR_DYN_POLICY_MIN UINT32_C(0x01000000) + +/* + * Bit definitions for ISR + */ +#define PPU_V0_ISR_MASK UINT32_C(0x01FF01FF) +#define PPU_V0_ISR_STA_POLICY_TRN UINT32_C(0x00000001) +#define PPU_V0_ISR_STA_ACCEPT UINT32_C(0x00000002) +#define PPU_V0_ISR_STA_DENY UINT32_C(0x00000004) +#define PPU_V0_ISR_DYN_ACCEPT UINT32_C(0x00000008) +#define PPU_V0_ISR_DYN_DENY UINT32_C(0x00000010) +#define PPU_V0_ISR_EMU_ACCEPT UINT32_C(0x00000020) +#define PPU_V0_ISR_EMU_DENY UINT32_C(0x00000040) +#define PPU_V0_ISR_UNSPT_POLICY UINT32_C(0x00000080) +#define PPU_V0_ISR_DBGEMUPWRDWN_EDGE UINT32_C(0x00000100) +#define PPU_V0_ISR_ACTIVE_EDGE UINT32_C(0x00FF0000) +#define PPU_V0_ISR_ACTIVE_EDGE_ACTIVE0 UINT32_C(0x00010000) +#define PPU_V0_ISR_ACTIVE_EDGE_ACTIVE1 UINT32_C(0x00020000) +#define PPU_V0_ISR_ACTIVE_EDGE_ACTIVE2 UINT32_C(0x00040000) +#define PPU_V0_ISR_ACTIVE_EDGE_ACTIVE3 UINT32_C(0x00080000) +#define PPU_V0_ISR_ACTIVE_EDGE_ACTIVE4 UINT32_C(0x00100000) +#define PPU_V0_ISR_ACTIVE_EDGE_ACTIVE5 UINT32_C(0x00200000) +#define PPU_V0_ISR_ACTIVE_EDGE_ACTIVE6 UINT32_C(0x00400000) +#define PPU_V0_ISR_ACTIVE_EDGE_ACTIVE7 UINT32_C(0x00800000) +#define PPU_V0_ISR_DYN_POLICY_MIN UINT32_C(0x01000000) + +/* + * Bit definitions for ARCHITECTURE_ID + */ +#define PPU_V0_ARCHITECTURE_ID UINT32_C(0x00000000) + +/* + * Interface + */ +void ppu_v0_init(struct ppu_v0_reg *ppu); +int ppu_v0_request_power_mode(struct ppu_v0_reg *ppu, enum ppu_v0_mode mode); +int ppu_v0_set_power_mode(struct ppu_v0_reg *ppu, enum ppu_v0_mode mode); +int ppu_v0_get_power_mode(struct ppu_v0_reg *ppu, enum ppu_v0_mode *mode); + +/*! + * \endcond + * @} + */ + +#endif /* PPU_V0_H */ diff --git a/module/ppu_v1/include/mod_ppu_v1.h b/module/ppu_v1/include/mod_ppu_v1.h new file mode 100644 index 00000000..6966101e --- /dev/null +++ b/module/ppu_v1/include/mod_ppu_v1.h @@ -0,0 +1,166 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * PPU v1 Driver + */ + +#ifndef MOD_PPU_V1_H +#define MOD_PPU_V1_H + +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <fwk_id.h> +#include <mod_power_domain.h> + + +/*! + * \addtogroup GroupModules Modules + * @{ + */ + +/*! + * \defgroup GroupModulePPUv1 PPUv1 Driver + * @{ + */ + +/*! + * \brief Indexes of the interfaces exposed by the module. + */ +enum mod_ppu_v1_api_idx { + /*! Power domain driver API */ + MOD_PPU_V1_API_IDX_POWER_DOMAIN_DRIVER, + /*! interrupt Service Routine driver API */ + MOD_PPU_V1_API_IDX_ISR, + /*! System boot API */ + MOD_PPU_V1_API_IDX_BOOT, + /*! Number of exposed interfaces */ + MOD_PPU_V1_API_IDX_COUNT, +}; + +/*! + * \brief Power domain PPU descriptor. + */ +struct mod_ppu_v1 { + /*! Base address of the PPU registers */ + uintptr_t reg_base; + + /*! PPU's IRQ number */ + unsigned int irq; +}; + +/*! + * \brief PPU_V1 module configuration + */ +struct mod_ppu_v1_config { + /*! Identifier of the power domain notification to register elements for */ + const fwk_id_t pd_notification_id; + + /*! + * Identifier of the source module or element that is expected to send power + * domain notifications. + */ + fwk_id_t pd_source_id; +}; + +/*! + * \brief Configuration data of a power domain of the PPU_V1 driver module. + */ +struct mod_ppu_v1_pd_config { + /*! Power domain type */ + enum mod_pd_type pd_type; + + /*! PPU descriptor */ + struct mod_ppu_v1 ppu; + + /*! + * In the case of a core power domain, identifier of the cluster power + * domain it belongs to. If the power domain is not a core power domain, + * the value of this field is undefined. + */ + fwk_id_t cluster_id; + + /*! + * Flag indicating if this domain should be powered on during element + * init. This flag is only supported for device and system PPUs and should + * not be set for any other type. + */ + bool default_power_on; + + /*! + * \brief Identifier of an entity wishing to be notified when the PPU + * transitions out of the OFF state. + * + * \note This field may be set to \ref FWK_ID_NONE, in which case no + * observer will be set. + */ + fwk_id_t observer_id; + + /*! + * \brief Identifier of the power state observer API implemented by + * \ref observer_id. + */ + fwk_id_t observer_api; + + /*! + * \brief Parameter passed to + * \ref mod_ppu_v1_power_state_observer_api::post_ppu_on(). + */ + void *post_ppu_on_param; +}; + +/*! + * \brief PPU_V1 Power State Observer API. + * + * \details This API should be implemented by any modules that should be + * notified when a PPU changes state. + */ +struct mod_ppu_v1_power_state_observer_api { + /*! + * \brief Called after a PPU has turned on. + * + * \param param Generic configurable parameter. + */ + void (*post_ppu_on)(void *param); +}; + +/*! + * \brief PPU_V1 module ISR API + */ +struct ppu_v1_isr_api { + /*! + * \brief Handle a power domain PPU interrupt + * + * \param pd_id Identifier of the power domain + */ + void (*ppu_interrupt_handler)(fwk_id_t pd_id); +}; + +/*! + * \brief PPU_V1 module boot API + */ +struct ppu_v1_boot_api { + /*! + * \brief Power on a specified power domain + * + * \param pd_id Identifier of the power domain + * + * \retval FWK_SUCCESS Operation successful. + * \return One of the standard framework error codes. + */ + int (*power_mode_on)(fwk_id_t pd_id); +}; + +/*! + * @} + */ + +/*! + * @} + */ + +#endif /* MOD_PPU_V1_H */ diff --git a/module/ppu_v1/src/Makefile b/module/ppu_v1/src/Makefile new file mode 100644 index 00000000..8d3a5227 --- /dev/null +++ b/module/ppu_v1/src/Makefile @@ -0,0 +1,11 @@ +# +# Arm SCP/MCP Software +# Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +BS_LIB_NAME := PPU_V1 +BS_LIB_SOURCES = ppu_v1.c mod_ppu_v1.c + +include $(BS_DIR)/lib.mk diff --git a/module/ppu_v1/src/mod_ppu_v1.c b/module/ppu_v1/src/mod_ppu_v1.c new file mode 100644 index 00000000..2f13d72a --- /dev/null +++ b/module/ppu_v1/src/mod_ppu_v1.c @@ -0,0 +1,991 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * Power State Management PPU v1 driver. + */ + +#include <assert.h> +#include <stdbool.h> +#include <fwk_id.h> +#include <fwk_interrupt.h> +#include <fwk_macros.h> +#include <fwk_mm.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <fwk_notification.h> +#include <mod_log.h> +#include <mod_power_domain.h> +#include <mod_ppu_v1.h> +#include <ppu_v1.h> +#if BUILD_HAS_MOD_SYSTEM_POWER +#include <mod_system_power.h> +#endif + +#define CORE_PER_CLUSTER_COUNT_MAX 8 + +/* Power domain context */ +struct ppu_v1_pd_ctx { + /* Power domain configuration data */ + const struct mod_ppu_v1_pd_config *config; + + /* PPU registers */ + struct ppu_v1_reg *ppu; + + /* Identifier of the entity bound to the power domain driver API */ + fwk_id_t bound_id; + + /* Power module driver input API */ + struct mod_pd_driver_input_api *pd_driver_input_api; + + /* Context of the parent power domain (used only for core power domains) */ + struct ppu_v1_pd_ctx *parent_pd_ctx; + + /* Pointer to the power state observer API */ + const struct mod_ppu_v1_power_state_observer_api *observer_api; + + /* Context data specific to the type of power domain */ + void *data; +}; + +/* Cluster power domain specific context */ +struct ppu_v1_cluster_pd_ctx { + /* + * Table of pointers to the contexts of the cores being part of the + * cluster. + */ + struct ppu_v1_pd_ctx *core_pd_ctx_table[CORE_PER_CLUSTER_COUNT_MAX]; + + /* Number of cores */ + unsigned int core_count; +}; + +/* Module context */ +struct ppu_v1_ctx { + /* Table of the power domain contexts */ + struct ppu_v1_pd_ctx *pd_ctx_table; + + /* Number of power domains */ + size_t pd_ctx_table_size; + + /* Log API */ + struct mod_log_api *log_api; +}; + +/* + * Internal variables + */ + +static struct ppu_v1_ctx ppu_v1_ctx; + +#define MODE_UNSUPPORTED ~0U +static const uint8_t ppu_mode_to_power_state[] = { + [PPU_V1_MODE_OFF] = (uint8_t)MOD_PD_STATE_OFF, + [PPU_V1_MODE_OFF_EMU] = (uint8_t)MOD_PD_STATE_OFF, + [PPU_V1_MODE_MEM_RET] = (uint8_t)MOD_PD_STATE_OFF, + [PPU_V1_MODE_MEM_RET_EMU] = (uint8_t)MOD_PD_STATE_OFF, + [PPU_V1_MODE_LOGIC_RET] = (uint8_t)MOD_PD_STATE_ON, + [PPU_V1_MODE_FULL_RET] = (uint8_t)MOD_PD_STATE_ON, + [PPU_V1_MODE_MEM_OFF] = (uint8_t)MOD_PD_STATE_ON, + [PPU_V1_MODE_FUNC_RET] = (uint8_t)MOD_PD_STATE_ON, + [PPU_V1_MODE_ON] = (uint8_t)MOD_PD_STATE_ON, + [PPU_V1_MODE_WARM_RST] = (uint8_t)MODE_UNSUPPORTED, + [PPU_V1_MODE_DBG_RECOV] = (uint8_t)MODE_UNSUPPORTED +}; + +/* + * Functions not specific to any type of power domain + */ + +static int get_state(struct ppu_v1_reg *ppu, unsigned int *state) +{ + enum ppu_v1_mode mode; + + /* Ensure ppu_to_pd_state_v1 has an entry for each PPU state */ + static_assert((FWK_ARRAY_SIZE(ppu_mode_to_power_state) == + PPU_V1_MODE_COUNT), "[PPU_V1] ppu_mode_to_power_state size error"); + + mode = ppu_v1_get_power_mode(ppu); + assert(mode < PPU_V1_MODE_COUNT); + + *state = ppu_mode_to_power_state[mode]; + + if ((*state == MOD_PD_STATE_OFF) && (ppu_v1_is_dynamic_enabled(ppu))) + *state = MOD_PD_STATE_SLEEP; + + if (*state == MODE_UNSUPPORTED) { + ppu_v1_ctx.log_api->log(MOD_LOG_GROUP_ERROR, + "[PPU_V1] Unexpected PPU mode (%i).\n", mode); + return FWK_E_DEVICE; + } + + return FWK_SUCCESS; +} + +static int ppu_v1_pd_set_state(fwk_id_t pd_id, unsigned int state) +{ + int status; + struct ppu_v1_pd_ctx *pd_ctx; + + status = fwk_module_check_call(pd_id); + if (status != FWK_SUCCESS) + return status; + + pd_ctx = ppu_v1_ctx.pd_ctx_table + fwk_id_get_element_idx(pd_id); + + switch (state) { + case MOD_PD_STATE_ON: + ppu_v1_set_power_mode(pd_ctx->ppu, PPU_V1_MODE_ON); + status = pd_ctx->pd_driver_input_api->report_power_state_transition( + pd_ctx->bound_id, MOD_PD_STATE_ON); + assert(status == FWK_SUCCESS); + break; + + case MOD_PD_STATE_OFF: + ppu_v1_set_power_mode(pd_ctx->ppu, PPU_V1_MODE_OFF); + status = pd_ctx->pd_driver_input_api->report_power_state_transition( + pd_ctx->bound_id, MOD_PD_STATE_OFF); + assert(status == FWK_SUCCESS); + break; + + default: + ppu_v1_ctx.log_api->log(MOD_LOG_GROUP_ERROR, + "[PD] Requested power state (%i) is not supported.\n", state); + return FWK_E_PARAM; + } + + return FWK_SUCCESS; +} + +static int ppu_v1_pd_get_state(fwk_id_t pd_id, unsigned int *state) +{ + int status; + struct ppu_v1_pd_ctx *pd_ctx; + + status = fwk_module_check_call(pd_id); + if (status != FWK_SUCCESS) + return status; + + pd_ctx = ppu_v1_ctx.pd_ctx_table + fwk_id_get_element_idx(pd_id); + + return get_state(pd_ctx->ppu, state); +} + +static int ppu_v1_pd_reset(fwk_id_t pd_id) +{ + int status; + struct ppu_v1_pd_ctx *pd_ctx; + + status = fwk_module_check_call(pd_id); + if (status != FWK_SUCCESS) + return status; + + pd_ctx = ppu_v1_ctx.pd_ctx_table + fwk_id_get_element_idx(pd_id); + + /* Model does not support warm reset at the moment. Using OFF instead. */ + status = ppu_v1_set_power_mode(pd_ctx->ppu, PPU_V1_MODE_OFF); + if (status == FWK_SUCCESS) + status = ppu_v1_set_power_mode(pd_ctx->ppu, PPU_V1_MODE_ON); + + return status; +} + +static const struct mod_pd_driver_api pd_driver = { + .set_state = ppu_v1_pd_set_state, + .get_state = ppu_v1_pd_get_state, + .reset = ppu_v1_pd_reset, +}; + +/* + * Functions specific to core power domains + */ +static int ppu_v1_core_pd_init(struct ppu_v1_pd_ctx *pd_ctx) +{ + int status; + struct ppu_v1_reg *ppu = pd_ctx->ppu; + unsigned int state; + + ppu_v1_init(ppu); + + status = get_state(ppu, &state); + if (status != FWK_SUCCESS) + return status; + + if (state == MOD_PD_STATE_ON) { + ppu_v1_interrupt_unmask(ppu, PPU_V1_IMR_DYN_POLICY_MIN_IRQ_MASK); + ppu_v1_dynamic_enable(ppu, PPU_V1_MODE_OFF); + } + + return FWK_SUCCESS; +} + +static int ppu_v1_core_pd_set_state(fwk_id_t core_pd_id, unsigned int state) +{ + int status; + struct ppu_v1_pd_ctx *pd_ctx; + struct ppu_v1_reg *ppu; + + status = fwk_module_check_call(core_pd_id); + if (status != FWK_SUCCESS) + return status; + + pd_ctx = ppu_v1_ctx.pd_ctx_table + fwk_id_get_element_idx(core_pd_id); + ppu = pd_ctx->ppu; + + switch (state) { + case MOD_PD_STATE_OFF: + ppu_v1_set_input_edge_sensitivity(ppu, + PPU_V1_MODE_ON, + PPU_V1_EDGE_SENSITIVITY_MASKED); + ppu_v1_interrupt_mask(ppu, PPU_V1_IMR_DYN_POLICY_MIN_IRQ_MASK); + ppu_v1_set_power_mode(ppu, PPU_V1_MODE_OFF); + ppu_v1_lock_off_disable(ppu); + ppu_v1_off_unlock(ppu); + status = pd_ctx->pd_driver_input_api->report_power_state_transition( + pd_ctx->bound_id, MOD_PD_STATE_OFF); + assert(status == FWK_SUCCESS); + break; + + case MOD_PD_STATE_ON: + ppu_v1_interrupt_unmask(ppu, PPU_V1_IMR_DYN_POLICY_MIN_IRQ_MASK); + ppu_v1_set_input_edge_sensitivity(ppu, + PPU_V1_MODE_ON, + PPU_V1_EDGE_SENSITIVITY_MASKED); + ppu_v1_set_power_mode(ppu, PPU_V1_MODE_ON); + ppu_v1_dynamic_enable(ppu, PPU_V1_MODE_OFF); + status = pd_ctx->pd_driver_input_api->report_power_state_transition( + pd_ctx->bound_id, MOD_PD_STATE_ON); + assert(status == FWK_SUCCESS); + break; + + case MOD_PD_STATE_SLEEP: + /* + * If the dynamic transitions have been enabled then the core is + * already in the SLEEP power state or will transit to the SLEEP power + * state if the appropriate processing is done on AP side. Thus nothing + * to do in that case. If the dynamic transitions are not enabled then + * this is an OFF to SLEEP transition. + */ + if (!ppu_v1_is_dynamic_enabled(ppu)) { + ppu_v1_dynamic_enable(ppu, PPU_V1_MODE_OFF); + ppu_v1_set_input_edge_sensitivity(ppu, + PPU_V1_MODE_ON, + PPU_V1_EDGE_SENSITIVITY_MASKED); + } + status = pd_ctx->pd_driver_input_api->report_power_state_transition( + pd_ctx->bound_id, MOD_PD_STATE_SLEEP); + assert(status == FWK_SUCCESS); + break; + + default: + ppu_v1_ctx.log_api->log(MOD_LOG_GROUP_ERROR, + "[PPU_V1] Requested CPU power state (%i) is not supported!\n", + state); + return FWK_E_PARAM; + } + + return FWK_SUCCESS; +} + +static int ppu_v1_core_pd_reset(fwk_id_t core_pd_id) +{ + int status; + + status = ppu_v1_core_pd_set_state(core_pd_id, MOD_PD_STATE_OFF); + if (status == FWK_SUCCESS) + status = ppu_v1_core_pd_set_state(core_pd_id, MOD_PD_STATE_ON); + + return status; +} + +static int ppu_v1_core_pd_prepare_for_system_suspend(fwk_id_t core_pd_id) +{ + int status; + struct ppu_v1_pd_ctx *pd_ctx; + struct ppu_v1_reg *ppu; + + status = fwk_module_check_call(core_pd_id); + if (status != FWK_SUCCESS) + return status; + + pd_ctx = ppu_v1_ctx.pd_ctx_table + fwk_id_get_element_idx(core_pd_id); + ppu = pd_ctx->ppu; + + ppu_v1_set_input_edge_sensitivity(ppu, + PPU_V1_MODE_ON, + PPU_V1_EDGE_SENSITIVITY_MASKED); + ppu_v1_request_power_mode(ppu, PPU_V1_MODE_OFF); + + return FWK_SUCCESS; +} + +static void core_pd_ppu_interrupt_handler(struct ppu_v1_pd_ctx *pd_ctx) +{ + int status; + struct ppu_v1_reg *ppu; + + ppu = pd_ctx->ppu; + + /* ON request interrupt */ + if (ppu_v1_is_power_active_edge_interrupt(ppu, PPU_V1_MODE_ON)) { + ppu_v1_ack_power_active_edge_interrupt(ppu, PPU_V1_MODE_ON); + ppu_v1_set_input_edge_sensitivity(ppu, + PPU_V1_MODE_ON, + PPU_V1_EDGE_SENSITIVITY_MASKED); + ppu_v1_interrupt_unmask(ppu, PPU_V1_IMR_DYN_POLICY_MIN_IRQ_MASK); + + status = pd_ctx->pd_driver_input_api->report_power_state_transition( + pd_ctx->bound_id, MOD_PD_STATE_ON); + assert(status == FWK_SUCCESS); + (void)status; + /* Minimum policy reached interrupt */ + } else if (ppu_v1_is_dyn_policy_min_interrupt(ppu)) { + ppu_v1_ack_interrupt(ppu, PPU_V1_ISR_DYN_POLICY_MIN_IRQ); + ppu_v1_interrupt_mask(ppu, PPU_V1_IMR_DYN_POLICY_MIN_IRQ_MASK); + + status = pd_ctx->pd_driver_input_api->report_power_state_transition( + pd_ctx->bound_id, MOD_PD_STATE_SLEEP); + assert(status == FWK_SUCCESS); + (void)status; + + /* + * Enable the core PACTIVE ON signal rising edge interrupt then check if + * the PACTIVE ON signal is high. If it is high, we may have missed the + * transition from low to high. In that case, just disable the interrupt + * and acknowledge it in case it is pending. There is no need to send an + * update request as one has already been queued. + */ + ppu_v1_set_input_edge_sensitivity(ppu, + PPU_V1_MODE_ON, + PPU_V1_EDGE_SENSITIVITY_RISING_EDGE); + if (ppu_v1_is_power_devactive_high(ppu, PPU_V1_MODE_ON)) { + ppu_v1_set_input_edge_sensitivity(ppu, + PPU_V1_MODE_ON, + PPU_V1_EDGE_SENSITIVITY_MASKED); + ppu_v1_ack_power_active_edge_interrupt(ppu, PPU_V1_MODE_ON); + ppu_v1_interrupt_unmask(ppu, PPU_V1_IMR_DYN_POLICY_MIN_IRQ_MASK); + } + } +} + +static const struct mod_pd_driver_api core_pd_driver = { + .set_state = ppu_v1_core_pd_set_state, + .get_state = ppu_v1_pd_get_state, + .reset = ppu_v1_core_pd_reset, + .prepare_core_for_system_suspend = ppu_v1_core_pd_prepare_for_system_suspend +}; + +/* + * Functions specific to cluster power domains + */ + +static void unlock_all_cores(struct ppu_v1_pd_ctx *pd_ctx) +{ + struct ppu_v1_cluster_pd_ctx *cluster_pd_ctx; + struct ppu_v1_reg *cpu_ppu; + unsigned int core_idx; + + assert(pd_ctx != NULL); + + cluster_pd_ctx = pd_ctx->data; + + for (core_idx = 0; core_idx < cluster_pd_ctx->core_count; ++core_idx) { + cpu_ppu = cluster_pd_ctx->core_pd_ctx_table[core_idx]->ppu; + ppu_v1_lock_off_disable(cpu_ppu); + ppu_v1_off_unlock(cpu_ppu); + } +} + +static bool lock_all_dynamic_cores(struct ppu_v1_pd_ctx *pd_ctx) +{ + struct ppu_v1_cluster_pd_ctx *cluster_pd_ctx; + struct ppu_v1_reg *cpu_ppu; + unsigned int core_idx; + + assert(pd_ctx != NULL); + + cluster_pd_ctx = pd_ctx->data; + + for (core_idx = 0; core_idx < cluster_pd_ctx->core_count; ++core_idx) { + cpu_ppu = cluster_pd_ctx->core_pd_ctx_table[core_idx]->ppu; + + if (!ppu_v1_is_dynamic_enabled(cpu_ppu)) + continue; + + ppu_v1_lock_off_enable(cpu_ppu); + while ((!ppu_v1_is_locked(cpu_ppu)) && + (!ppu_v1_is_power_devactive_high(cpu_ppu, PPU_V1_MODE_ON))) + continue; + + if (ppu_v1_is_power_devactive_high(cpu_ppu, PPU_V1_MODE_ON)) + return false; + } + + return true; +} + +static bool cluster_off(struct ppu_v1_pd_ctx *pd_ctx) +{ + struct ppu_v1_reg *ppu; + bool lock_successful; + + assert(pd_ctx != NULL); + + ppu = pd_ctx->ppu; + + ppu_v1_set_input_edge_sensitivity(ppu, + PPU_V1_MODE_ON, + PPU_V1_EDGE_SENSITIVITY_MASKED); + + lock_successful = lock_all_dynamic_cores(pd_ctx); + if (!lock_successful) { + unlock_all_cores(pd_ctx); + return false; + } + + ppu_v1_set_power_mode(ppu, PPU_V1_MODE_OFF); + return true; +} + +static void cluster_on(struct ppu_v1_pd_ctx *pd_ctx) +{ + int status; + struct ppu_v1_reg *ppu; + + assert(pd_ctx != NULL); + + ppu = pd_ctx->ppu; + + ppu_v1_set_input_edge_sensitivity(ppu, + PPU_V1_MODE_ON, + PPU_V1_EDGE_SENSITIVITY_MASKED); + + ppu_v1_set_power_mode(ppu, PPU_V1_MODE_ON); + status = pd_ctx->pd_driver_input_api->report_power_state_transition( + pd_ctx->bound_id, MOD_PD_STATE_ON); + assert(status == FWK_SUCCESS); + (void)status; + + if (pd_ctx->observer_api != NULL) + pd_ctx->observer_api->post_ppu_on(pd_ctx->config->post_ppu_on_param); + + unlock_all_cores(pd_ctx); +} + +static int ppu_v1_cluster_pd_init(struct ppu_v1_pd_ctx *pd_ctx) +{ + int status; + struct ppu_v1_reg *ppu = pd_ctx->ppu; + unsigned int state; + + ppu_v1_init(ppu); + + status = get_state(ppu, &state); + if (status != FWK_SUCCESS) + return status; + + /* For clusters with operating mode support, enable the dynamic support */ + if (ppu_v1_get_num_opmode(ppu) > 1) + ppu_v1_opmode_dynamic_enable(ppu, PPU_V1_OPMODE_00); + + if (state == MOD_PD_STATE_ON) { + ppu_v1_set_input_edge_sensitivity(ppu, + PPU_V1_MODE_ON, + PPU_V1_EDGE_SENSITIVITY_FALLING_EDGE); + } + + return FWK_SUCCESS; +} + +static int ppu_v1_cluster_pd_set_state(fwk_id_t cluster_pd_id, + unsigned int state) +{ + int status; + struct ppu_v1_pd_ctx *pd_ctx; + struct ppu_v1_reg *ppu; + (void)ppu; + + status = fwk_module_check_call(cluster_pd_id); + if (status != FWK_SUCCESS) + return status; + + pd_ctx = ppu_v1_ctx.pd_ctx_table + fwk_id_get_element_idx(cluster_pd_id); + ppu = pd_ctx->ppu; + + switch (state) { + case MOD_PD_STATE_ON: + cluster_on(pd_ctx); + #ifdef BUILD_HAS_MULTITHREADING + ppu_v1_set_input_edge_sensitivity(ppu, + PPU_V1_MODE_ON, + PPU_V1_EDGE_SENSITIVITY_FALLING_EDGE); + #endif + return FWK_SUCCESS; + + case MOD_PD_STATE_OFF: + if (!cluster_off(pd_ctx)) { + /* Cluster failed to transition to off */ + #ifdef BUILD_HAS_MULTITHREADING + ppu_v1_set_input_edge_sensitivity(ppu, + PPU_V1_MODE_ON, PPU_V1_EDGE_SENSITIVITY_FALLING_EDGE); + #endif + return FWK_E_STATE; + } + status = pd_ctx->pd_driver_input_api->report_power_state_transition( + pd_ctx->bound_id, MOD_PD_STATE_OFF); + assert(status == FWK_SUCCESS); + return FWK_SUCCESS; + + default: + ppu_v1_ctx.log_api->log(MOD_LOG_GROUP_ERROR, + "[PPU_V1] Requested CPU power state (%i) is not supported!\n", + state); + return FWK_E_PARAM; + } +} + +static void cluster_pd_ppu_interrupt_handler(struct ppu_v1_pd_ctx *pd_ctx) +{ + int status; + struct ppu_v1_reg *ppu; + enum ppu_v1_mode current_mode; + + assert(pd_ctx != NULL); + + ppu = pd_ctx->ppu; + + if (!ppu_v1_is_power_active_edge_interrupt(ppu, PPU_V1_MODE_ON)) + return; /* Spurious interrupt */ + + ppu_v1_ack_power_active_edge_interrupt(ppu, PPU_V1_MODE_ON); + current_mode = ppu_v1_get_power_mode(ppu); + + switch (current_mode) { + case PPU_V1_MODE_OFF: + /* Cluster has to be powered on */ + cluster_on(pd_ctx); + ppu_v1_set_input_edge_sensitivity(ppu, + PPU_V1_MODE_ON, + PPU_V1_EDGE_SENSITIVITY_FALLING_EDGE); + return; + + case PPU_V1_MODE_ON: + /* + * It may be possible to turn off the cluster, check all PACTIVE lines + * to make sure it is not just requesting a low power mode. + */ + while (current_mode > 0) { + if (ppu_v1_is_power_devactive_high(ppu, current_mode--)) + return; + } + + /* All PACTIVE lines are low, so the cluster can be turned off */ + if (cluster_off(pd_ctx)) { + /* Cluster successfuly transitioned to off */ + ppu_v1_set_input_edge_sensitivity(ppu, + PPU_V1_MODE_ON, PPU_V1_EDGE_SENSITIVITY_RISING_EDGE); + status = pd_ctx->pd_driver_input_api->report_power_state_transition( + pd_ctx->bound_id, MOD_PD_STATE_SLEEP); + assert(status == FWK_SUCCESS); + (void)status; + } else { + /* Cluster did not transition to off */ + ppu_v1_set_input_edge_sensitivity(ppu, + PPU_V1_MODE_ON, PPU_V1_EDGE_SENSITIVITY_FALLING_EDGE); + } + return; + + default: + /* Cluster is in an invalid power mode */ + assert(false); + return; + } +} + +static const struct mod_pd_driver_api cluster_pd_driver = { + .set_state = ppu_v1_cluster_pd_set_state, + .get_state = ppu_v1_pd_get_state, + .reset = ppu_v1_pd_reset, +}; + +static void ppu_interrupt_handler(uintptr_t pd_ctx_param) +{ + struct ppu_v1_pd_ctx *pd_ctx = (struct ppu_v1_pd_ctx *)pd_ctx_param; + + assert(pd_ctx != NULL); + + if (pd_ctx->config->pd_type == MOD_PD_TYPE_CORE) + core_pd_ppu_interrupt_handler(pd_ctx); + else + cluster_pd_ppu_interrupt_handler(pd_ctx); +}; + +static void ppu_isr_api_interrupt_handler(fwk_id_t pd_id) +{ + int status; + struct ppu_v1_pd_ctx *pd_ctx; + + status = fwk_module_check_call(pd_id); + if (status != FWK_SUCCESS) + return; + + if (!fwk_id_is_type(pd_id, FWK_ID_TYPE_ELEMENT)) + return; + + pd_ctx = ppu_v1_ctx.pd_ctx_table + fwk_id_get_element_idx(pd_id); + ppu_interrupt_handler((uintptr_t)pd_ctx); +} + +static const struct ppu_v1_isr_api isr_api = { + .ppu_interrupt_handler = ppu_isr_api_interrupt_handler, +}; + +static int ppu_power_mode_on(fwk_id_t pd_id) +{ + int status; + struct ppu_v1_pd_ctx *pd_ctx; + + status = fwk_module_check_call(pd_id); + if (status != FWK_SUCCESS) + return status; + + if (!fwk_id_is_type(pd_id, FWK_ID_TYPE_ELEMENT)) + return FWK_E_PARAM; + + pd_ctx = ppu_v1_ctx.pd_ctx_table + fwk_id_get_element_idx(pd_id); + + return ppu_v1_set_power_mode(pd_ctx->ppu, PPU_V1_MODE_ON); +} + +static const struct ppu_v1_boot_api boot_api = { + .power_mode_on = ppu_power_mode_on, +}; + +/* + * Framework handlers + */ + +static int ppu_v1_mod_init(fwk_id_t module_id, unsigned int pd_count, + const void *unused) +{ + ppu_v1_ctx.pd_ctx_table = fwk_mm_calloc(pd_count, + sizeof(struct ppu_v1_pd_ctx)); + if (ppu_v1_ctx.pd_ctx_table == NULL) + return FWK_E_NOMEM; + + ppu_v1_ctx.pd_ctx_table_size = pd_count; + + return FWK_SUCCESS; +} + +static int ppu_v1_pd_init(fwk_id_t pd_id, unsigned int unused, const void *data) +{ + const struct mod_ppu_v1_pd_config *config = data; + struct ppu_v1_pd_ctx *pd_ctx; + + if (config->pd_type >= MOD_PD_TYPE_COUNT) + return FWK_E_DATA; + + pd_ctx = ppu_v1_ctx.pd_ctx_table + fwk_id_get_element_idx(pd_id); + pd_ctx->config = config; + pd_ctx->ppu = (struct ppu_v1_reg *)(config->ppu.reg_base); + pd_ctx->bound_id = FWK_ID_NONE; + + if (config->ppu.irq != FWK_INTERRUPT_NONE) { + fwk_interrupt_set_isr_param(config->ppu.irq, + ppu_interrupt_handler, + (uintptr_t)pd_ctx); + } + + if (config->pd_type == MOD_PD_TYPE_CLUSTER) { + pd_ctx->data = fwk_mm_calloc(1, sizeof(struct ppu_v1_cluster_pd_ctx)); + if (pd_ctx->data == NULL) + return FWK_E_NOMEM; + } + + if (config->default_power_on) { + switch (config->pd_type) { + case MOD_PD_TYPE_DEVICE: + /* Fall through */ + case MOD_PD_TYPE_DEVICE_DEBUG: + /* Fall through */ + case MOD_PD_TYPE_SYSTEM: + ppu_v1_init(pd_ctx->ppu); + return ppu_v1_set_power_mode(pd_ctx->ppu, PPU_V1_MODE_ON); + + default: + assert(false); + return FWK_E_SUPPORT; + } + } + + return FWK_SUCCESS; +} + +static int ppu_v1_post_init(fwk_id_t module_id) +{ + unsigned int pd_idx; + struct ppu_v1_pd_ctx *pd_ctx, *cluster_pd_ctx; + const struct mod_ppu_v1_pd_config *config; + fwk_id_t cluster_id; + struct ppu_v1_cluster_pd_ctx *cluster_pd_specific_ctx; + + for (pd_idx = 0; pd_idx < ppu_v1_ctx.pd_ctx_table_size; pd_idx++) { + pd_ctx = &ppu_v1_ctx.pd_ctx_table[pd_idx]; + config = pd_ctx->config; + if (config->pd_type != MOD_PD_TYPE_CORE) + continue; + + cluster_id = config->cluster_id; + + if ((!fwk_module_is_valid_element_id(cluster_id)) || + (fwk_id_get_module_idx(cluster_id) != FWK_MODULE_IDX_PPU_V1)) + return FWK_E_PARAM; + + cluster_pd_ctx = &ppu_v1_ctx.pd_ctx_table[ + fwk_id_get_element_idx(cluster_id)]; + cluster_pd_specific_ctx = cluster_pd_ctx->data; + + if (cluster_pd_specific_ctx->core_count >= CORE_PER_CLUSTER_COUNT_MAX) + return FWK_E_NOMEM; + + cluster_pd_specific_ctx->core_pd_ctx_table[ + cluster_pd_specific_ctx->core_count++] = pd_ctx; + pd_ctx->parent_pd_ctx = cluster_pd_ctx; + } + + return FWK_SUCCESS; +} + +static int ppu_v1_bind(fwk_id_t id, unsigned int round) +{ + int status = FWK_SUCCESS; + struct ppu_v1_pd_ctx *pd_ctx; + + /* Nothing to do during the first round of calls where the power module + will bind to the power domains of this module. */ + if (round == 0) + return FWK_SUCCESS; + + /* In the case of the module, bind to the log component */ + if (fwk_id_is_type(id, FWK_ID_TYPE_MODULE)) { + status = fwk_module_bind(FWK_ID_MODULE(FWK_MODULE_IDX_LOG), + FWK_ID_API(FWK_MODULE_IDX_LOG, 0), + &ppu_v1_ctx.log_api); + return status; + } + + pd_ctx = ppu_v1_ctx.pd_ctx_table + fwk_id_get_element_idx(id); + + if (!fwk_id_is_equal(pd_ctx->config->observer_id, FWK_ID_NONE)) { + if (pd_ctx->config->pd_type != MOD_PD_TYPE_CLUSTER) { + /* State observation only supported for clusters */ + assert(false); + return FWK_E_SUPPORT; + } + + status = fwk_module_bind(pd_ctx->config->observer_id, + pd_ctx->config->observer_api, + &pd_ctx->observer_api); + if (status != FWK_SUCCESS) + return status; + } + + if (fwk_id_is_equal(pd_ctx->bound_id, FWK_ID_NONE)) + return FWK_SUCCESS; + + switch (fwk_id_get_module_idx(pd_ctx->bound_id)) { + #if BUILD_HAS_MOD_POWER_DOMAIN + case FWK_MODULE_IDX_POWER_DOMAIN: + return fwk_module_bind(pd_ctx->bound_id, + mod_pd_api_id_driver_input, + &pd_ctx->pd_driver_input_api); + break; + #endif + + #if BUILD_HAS_MOD_SYSTEM_POWER + case FWK_MODULE_IDX_SYSTEM_POWER: + return fwk_module_bind(pd_ctx->bound_id, + mod_system_power_api_id_pd_driver_input, + &pd_ctx->pd_driver_input_api); + break; + #endif + + default: + assert(false); + return FWK_E_SUPPORT; + } +} + +static int ppu_v1_process_bind_request(fwk_id_t source_id, + fwk_id_t target_id, fwk_id_t api_id, + const void **api) +{ + struct ppu_v1_pd_ctx *pd_ctx; + unsigned int api_idx; + bool is_power_domain_module = false; + bool is_system_power_module = false; + + api_idx = fwk_id_get_api_idx(api_id); + + if (api_idx == MOD_PPU_V1_API_IDX_ISR) { + if (!fwk_id_is_type(target_id, FWK_ID_TYPE_MODULE)) + return FWK_E_SUPPORT; + + *api = &isr_api; + return FWK_SUCCESS; + } + + if (api_idx == MOD_PPU_V1_API_IDX_BOOT) { + *api = &boot_api; + return FWK_SUCCESS; + } + + if (api_idx != MOD_PPU_V1_API_IDX_POWER_DOMAIN_DRIVER) + return FWK_E_SUPPORT; + + if (!fwk_module_is_valid_element_id(target_id)) + return FWK_E_PARAM; + + pd_ctx = ppu_v1_ctx.pd_ctx_table + fwk_id_get_element_idx(target_id); + + /* Allow multiple binding only for device power domain for now */ + if ((pd_ctx->config->pd_type != MOD_PD_TYPE_DEVICE) && + (!fwk_id_is_equal(pd_ctx->bound_id, FWK_ID_NONE))) { + assert(false); + return FWK_E_ACCESS; + } + + #if BUILD_HAS_MOD_POWER_DOMAIN + is_power_domain_module = (fwk_id_get_module_idx(source_id) == + FWK_MODULE_IDX_POWER_DOMAIN); + #endif + #if BUILD_HAS_MOD_SYSTEM_POWER + is_system_power_module = (fwk_id_get_module_idx(source_id) == + FWK_MODULE_IDX_SYSTEM_POWER); + #endif + + switch (pd_ctx->config->pd_type) { + case MOD_PD_TYPE_CORE: + if (is_power_domain_module) { + *api = &core_pd_driver; + pd_ctx->bound_id = source_id; + return FWK_SUCCESS; + } + break; + + case MOD_PD_TYPE_CLUSTER: + if (is_power_domain_module) { + *api = &cluster_pd_driver; + pd_ctx->bound_id = source_id; + return FWK_SUCCESS; + } + break; + + case MOD_PD_TYPE_SYSTEM: + if (is_power_domain_module || is_system_power_module) { + *api = &pd_driver; + pd_ctx->bound_id = source_id; + return FWK_SUCCESS; + } + break; + + default: + if (is_power_domain_module) + pd_ctx->bound_id = source_id; + *api = &pd_driver; + return FWK_SUCCESS; + } + + pd_ctx->bound_id = FWK_ID_NONE; + return FWK_E_ACCESS; +} + +static int ppu_v1_start(fwk_id_t id) +{ + int status; + struct ppu_v1_pd_ctx *pd_ctx; + const struct mod_ppu_v1_config *module_config; + + if (!fwk_id_is_type(id, FWK_ID_TYPE_ELEMENT)) + return FWK_SUCCESS; + + pd_ctx = ppu_v1_ctx.pd_ctx_table + fwk_id_get_element_idx(id); + module_config = fwk_module_get_data(fwk_id_build_module_id(id)); + assert(module_config != NULL); + + /* Register for power domain transition notifications */ + status = fwk_notification_subscribe( + module_config->pd_notification_id, + module_config->pd_source_id, + id); + if (status != FWK_SUCCESS) + return status; + + switch (pd_ctx->config->pd_type) { + case MOD_PD_TYPE_CORE: + case MOD_PD_TYPE_CLUSTER: + fwk_interrupt_clear_pending(pd_ctx->config->ppu.irq); + fwk_interrupt_enable(pd_ctx->config->ppu.irq); + break; + default: + /* Nothing to be done for other types */ + break; + } + + return FWK_SUCCESS; +} + +static int ppu_v1_process_notification( + const struct fwk_event *event, + struct fwk_event *resp_event) +{ + const struct mod_ppu_v1_config *module_config; + struct ppu_v1_pd_ctx *pd_ctx; + struct mod_pd_power_state_transition_notification_params *params; + + assert(fwk_id_is_type(event->target_id, FWK_ID_TYPE_ELEMENT)); + module_config = + fwk_module_get_data(fwk_id_build_module_id(event->target_id)); + assert( + fwk_id_is_equal( + event->id, + module_config->pd_notification_id)); + (void)module_config; + + params = (struct mod_pd_power_state_transition_notification_params *) + event->params; + + if (params->state != MOD_PD_STATE_ON) + return FWK_SUCCESS; + + pd_ctx = ppu_v1_ctx.pd_ctx_table + fwk_id_get_element_idx(event->target_id); + + switch (pd_ctx->config->pd_type) { + case MOD_PD_TYPE_CORE: + return ppu_v1_core_pd_init(pd_ctx); + + case MOD_PD_TYPE_CLUSTER: + return ppu_v1_cluster_pd_init(pd_ctx); + + default: + ppu_v1_init(pd_ctx->ppu); + return FWK_SUCCESS; + } +} + +const struct fwk_module module_ppu_v1 = { + .name = "PPU_V1", + .type = FWK_MODULE_TYPE_DRIVER, + .api_count = MOD_PPU_V1_API_IDX_COUNT, + .init = ppu_v1_mod_init, + .element_init = ppu_v1_pd_init, + .post_init = ppu_v1_post_init, + .bind = ppu_v1_bind, + .start = ppu_v1_start, + .process_bind_request = ppu_v1_process_bind_request, + .process_notification = ppu_v1_process_notification, +}; diff --git a/module/ppu_v1/src/ppu_v1.c b/module/ppu_v1/src/ppu_v1.c new file mode 100644 index 00000000..14311245 --- /dev/null +++ b/module/ppu_v1/src/ppu_v1.c @@ -0,0 +1,360 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <assert.h> +#include <stddef.h> +#include <ppu_v1.h> +#include <fwk_errno.h> + +void ppu_v1_init(struct ppu_v1_reg *ppu) +{ + assert(ppu != NULL); + + /* Set edge sensitivity to masked for all input edges */ + ppu->IESR = 0; + + /* Mask all interrupts */ + ppu->IMR = PPU_V1_IMR_MASK; + + /* Acknowledge any interrupt left pending */ + ppu->ISR = PPU_V1_ISR_MASK; +} + +/* + * PWPR and PWSR registers + */ +int ppu_v1_request_power_mode(struct ppu_v1_reg *ppu, enum ppu_v1_mode ppu_mode) +{ + uint32_t power_policy; + assert(ppu != NULL); + assert(ppu_mode < PPU_V1_MODE_COUNT); + + power_policy = ppu->PWPR & ~(PPU_V1_PWPR_POLICY | PPU_V1_PWPR_DYNAMIC_EN); + ppu->PWPR = power_policy | ppu_mode; + + return FWK_SUCCESS; +} + +int ppu_v1_set_power_mode(struct ppu_v1_reg *ppu, enum ppu_v1_mode ppu_mode) +{ + int status; + + status = ppu_v1_request_power_mode(ppu, ppu_mode); + if (status != FWK_SUCCESS) + return status; + + while ((ppu->PWSR & (PPU_V1_PWSR_PWR_STATUS | PPU_V1_PWSR_PWR_DYN_STATUS)) + != ppu_mode) + continue; + + return FWK_SUCCESS; +} + +int ppu_v1_request_operating_mode(struct ppu_v1_reg *ppu, + enum ppu_v1_opmode op_mode) +{ + uint32_t power_policy; + assert(ppu != NULL); + assert(op_mode < PPU_V1_OPMODE_COUNT); + + power_policy = ppu->PWPR & ~(PPU_V1_PWPR_OP_POLICY | PPU_V1_PWPR_OP_DYN_EN); + ppu->PWPR = power_policy | (op_mode << PPU_V1_PWPR_OP_POLICY_POS); + + return FWK_SUCCESS; +} + +void ppu_v1_opmode_dynamic_enable(struct ppu_v1_reg *ppu, + enum ppu_v1_opmode min_dyn_mode) +{ + uint32_t power_policy; + + assert(ppu != NULL); + assert(min_dyn_mode < PPU_V1_OPMODE_COUNT); + + power_policy = ppu->PWPR & ~PPU_V1_PWPR_OP_POLICY; + ppu->PWPR = power_policy | + PPU_V1_PWPR_OP_DYN_EN | + (min_dyn_mode << PPU_V1_PWPR_OP_POLICY_POS); + while ((ppu->PWSR & PPU_V1_PWSR_OP_DYN_STATUS) == 0) + continue; +} + +void ppu_v1_dynamic_enable(struct ppu_v1_reg *ppu, + enum ppu_v1_mode min_dyn_state) +{ + uint32_t power_policy; + + assert(ppu != NULL); + assert(min_dyn_state < PPU_V1_MODE_COUNT); + + power_policy = ppu->PWPR & ~PPU_V1_PWPR_POLICY; + ppu->PWPR = power_policy | PPU_V1_PWPR_DYNAMIC_EN | min_dyn_state; + while ((ppu->PWSR & PPU_V1_PWSR_PWR_DYN_STATUS) == 0) + continue; +} + +void ppu_v1_lock_off_enable(struct ppu_v1_reg *ppu) +{ + assert(ppu != NULL); + + ppu->PWPR |= PPU_V1_PWPR_OFF_LOCK_EN; +} + +void ppu_v1_lock_off_disable(struct ppu_v1_reg *ppu) +{ + assert(ppu != NULL); + + ppu->PWPR &= ~PPU_V1_PWPR_OFF_LOCK_EN; +} + +enum ppu_v1_mode ppu_v1_get_power_mode(struct ppu_v1_reg *ppu) +{ + assert(ppu != NULL); + + return (enum ppu_v1_mode)(ppu->PWSR & PPU_V1_PWSR_PWR_STATUS); +} + +enum ppu_v1_mode ppu_v1_get_programmed_power_mode(struct ppu_v1_reg *ppu) +{ + assert(ppu != NULL); + + return (enum ppu_v1_mode)(ppu->PWPR & PPU_V1_PWPR_POLICY); +} + +enum ppu_v1_opmode ppu_v1_get_operating_mode(struct ppu_v1_reg *ppu) +{ + assert(ppu != NULL); + + return (enum ppu_v1_opmode) + (ppu->PWSR & PPU_V1_PWSR_OP_STATUS) >> PPU_V1_PWSR_OP_STATUS_POS; +} + +enum ppu_v1_opmode ppu_v1_get_programmed_operating_mode(struct ppu_v1_reg *ppu) +{ + assert(ppu != NULL); + + return (enum ppu_v1_opmode) + (ppu->PWPR & PPU_V1_PWPR_OP_POLICY) >> PPU_V1_PWPR_OP_POLICY_POS; +} + +bool ppu_v1_is_dynamic_enabled(struct ppu_v1_reg *ppu) +{ + assert(ppu != NULL); + + return ((ppu->PWSR & PPU_V1_PWSR_PWR_DYN_STATUS) != 0); +} + +bool ppu_v1_is_locked(struct ppu_v1_reg *ppu) +{ + assert(ppu != NULL); + + return ((ppu->PWSR & PPU_V1_PWSR_OFF_LOCK_STATUS) != 0); +} + +/* + * DISR register + */ +bool ppu_v1_is_power_devactive_high(struct ppu_v1_reg *ppu, + enum ppu_v1_mode ppu_mode) +{ + assert(ppu != NULL); + + return (ppu->DISR & + (1 << (ppu_mode + PPU_V1_DISR_PWR_DEVACTIVE_STATUS_POS))) != 0; +} + +bool ppu_v1_is_op_devactive_high(struct ppu_v1_reg *ppu, + enum ppu_v1_op_devactive op_devactive) +{ + assert(ppu != NULL); + + return (ppu->DISR & + (1 << (op_devactive + PPU_V1_DISR_OP_DEVACTIVE_STATUS_POS))) != 0; +} + +/* + * UNLK register + */ +void ppu_v1_off_unlock(struct ppu_v1_reg *ppu) +{ + assert(ppu != NULL); + + ppu->UNLK = PPU_V1_UNLK_OFF_UNLOCK; +} + +/* + * PWCR register + */ +void ppu_v1_disable_devactive(struct ppu_v1_reg *ppu) +{ + assert(ppu != NULL); + + ppu->PWCR &= ~PPU_V1_PWCR_DEV_ACTIVE_EN; +} + +void ppu_v1_disable_handshake(struct ppu_v1_reg *ppu) +{ + assert(ppu != NULL); + + ppu->PWCR &= ~PPU_V1_PWCR_DEV_REQ_EN; +} + +/* + * Interrupt registers: IMR, AIMR, ISR, AISR, IESR, OPSR + */ +void ppu_v1_interrupt_mask(struct ppu_v1_reg *ppu, unsigned int mask) +{ + assert(ppu != NULL); + + ppu->IMR |= mask & PPU_V1_IMR_MASK; +} + +void ppu_v1_additional_interrupt_mask(struct ppu_v1_reg *ppu, unsigned int mask) +{ + assert(ppu != NULL); + + ppu->AIMR |= mask & PPU_V1_AIMR_MASK; +} + +void ppu_v1_interrupt_unmask(struct ppu_v1_reg *ppu, unsigned int mask) +{ + assert(ppu != NULL); + + ppu->IMR &= ~(mask & PPU_V1_IMR_MASK); +} + +void ppu_v1_additional_interrupt_unmask(struct ppu_v1_reg *ppu, + unsigned int mask) +{ + assert(ppu != NULL); + + ppu->AIMR &= ~(mask & PPU_V1_AIMR_MASK); +} + +bool ppu_v1_is_additional_interrupt_pending(struct ppu_v1_reg *ppu, + unsigned int mask) +{ + return (ppu->AISR & (mask & PPU_V1_AISR_MASK)) != 0; +} + +void ppu_v1_ack_interrupt(struct ppu_v1_reg *ppu, unsigned int mask) +{ + assert(ppu != NULL); + + ppu->ISR &= mask & PPU_V1_IMR_MASK; +} + +void ppu_v1_ack_additional_interrupt(struct ppu_v1_reg *ppu, unsigned int mask) +{ + assert(ppu != NULL); + + ppu->AISR &= mask & PPU_V1_AIMR_MASK; +} + +void ppu_v1_set_input_edge_sensitivity(struct ppu_v1_reg *ppu, + enum ppu_v1_mode ppu_mode, enum ppu_v1_edge_sensitivity edge_sensitivity) +{ + assert(ppu != NULL); + assert(ppu_mode < PPU_V1_MODE_COUNT); + assert((edge_sensitivity & ~PPU_V1_EDGE_SENSITIVITY_MASK) == 0); + + /* Clear current settings */ + ppu->IESR &= ~(PPU_V1_EDGE_SENSITIVITY_MASK << + (ppu_mode * PPU_V1_BITS_PER_EDGE_SENSITIVITY)); + + /* Update settings */ + ppu->IESR |= edge_sensitivity << + (ppu_mode * PPU_V1_BITS_PER_EDGE_SENSITIVITY); +} + +enum ppu_v1_edge_sensitivity ppu_v1_get_input_edge_sensitivity( + struct ppu_v1_reg *ppu, enum ppu_v1_mode ppu_mode) +{ + assert(ppu != NULL); + assert(ppu_mode < PPU_V1_MODE_COUNT); + + return (enum ppu_v1_edge_sensitivity)( + (ppu->IESR >> (ppu_mode * PPU_V1_BITS_PER_EDGE_SENSITIVITY)) & + PPU_V1_EDGE_SENSITIVITY_MASK); +} + +void ppu_v1_ack_power_active_edge_interrupt(struct ppu_v1_reg *ppu, + enum ppu_v1_mode ppu_mode) +{ + ppu->ISR = 1 << (ppu_mode + PPU_V1_ISR_ACTIVE_EDGE_POS); +} + +bool ppu_v1_is_power_active_edge_interrupt(struct ppu_v1_reg *ppu, + enum ppu_v1_mode ppu_mode) +{ + return ppu->ISR & (1 << (ppu_mode + PPU_V1_ISR_ACTIVE_EDGE_POS)); +} + +void ppu_v1_set_op_active_edge_sensitivity(struct ppu_v1_reg *ppu, + enum ppu_v1_op_devactive op_devactive, + enum ppu_v1_edge_sensitivity edge_sensitivity) +{ + assert(ppu != NULL); + assert(op_devactive < PPU_V1_OP_DEVACTIVE_COUNT); + assert((edge_sensitivity & ~PPU_V1_EDGE_SENSITIVITY_MASK) == 0); + + /* Clear current settings */ + ppu->OPSR &= ~(PPU_V1_EDGE_SENSITIVITY_MASK << + (op_devactive * PPU_V1_BITS_PER_EDGE_SENSITIVITY)); + + /* Update settings */ + ppu->OPSR |= edge_sensitivity << + (op_devactive * PPU_V1_BITS_PER_EDGE_SENSITIVITY); +} + +enum ppu_v1_edge_sensitivity ppu_v1_get_op_active_edge_sensitivity( + struct ppu_v1_reg *ppu, enum ppu_v1_op_devactive op_devactive) +{ + assert(ppu != NULL); + assert(op_devactive < PPU_V1_OP_DEVACTIVE_COUNT); + + return (enum ppu_v1_edge_sensitivity)( + (ppu->OPSR >> (op_devactive * PPU_V1_BITS_PER_EDGE_SENSITIVITY)) & + PPU_V1_EDGE_SENSITIVITY_MASK); +} + +void ppu_v1_ack_op_active_edge_interrupt(struct ppu_v1_reg *ppu, + enum ppu_v1_op_devactive op_devactive) +{ + ppu->ISR = 1 << (op_devactive + PPU_V1_ISR_OP_ACTIVE_EDGE_POS); +} + +bool ppu_v1_is_op_active_edge_interrupt(struct ppu_v1_reg *ppu, + enum ppu_v1_op_devactive op_devactive) +{ + return ppu->ISR & (1 << (op_devactive + PPU_V1_ISR_OP_ACTIVE_EDGE_POS)); +} + +bool ppu_v1_is_dyn_policy_min_interrupt(struct ppu_v1_reg *ppu) +{ + return ppu->ISR & PPU_V1_ISR_DYN_POLICY_MIN_IRQ; +} + +/* + * IDR0 register + */ +unsigned int ppu_v1_get_num_opmode(struct ppu_v1_reg *ppu) +{ + return ((ppu->IDR0 & PPU_V1_IDR0_NUM_OPMODE) + >> PPU_V1_IDR0_NUM_OPMODE_POS) + 1; +} + +/* + * AIDR register + */ +unsigned int ppu_v1_get_arch_id(struct ppu_v1_reg *ppu) +{ + assert(ppu != NULL); + + return (ppu->AIDR & (PPU_V1_AIDR_ARCH_REV_MINOR | + PPU_V1_AIDR_ARCH_REV_MAJOR)); +} diff --git a/module/ppu_v1/src/ppu_v1.h b/module/ppu_v1/src/ppu_v1.h new file mode 100644 index 00000000..a4e37647 --- /dev/null +++ b/module/ppu_v1/src/ppu_v1.h @@ -0,0 +1,467 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef PPU_V1_H +#define PPU_V1_H + +/*! + * \cond + * @{ + */ + +#include <stdbool.h> +#include <stdint.h> +#include <fwk_macros.h> + +/* + * PPU 1.1 register definitions + */ +struct ppu_v1_reg { + FWK_RW uint32_t PWPR; + FWK_RW uint32_t PMER; + FWK_R uint32_t PWSR; + uint32_t RESERVED0; + FWK_R uint32_t DISR; + FWK_R uint32_t MISR; + FWK_R uint32_t STSR; + FWK_RW uint32_t UNLK; + FWK_RW uint32_t PWCR; + FWK_RW uint32_t PTCR; + uint32_t RESERVED1[2]; + FWK_RW uint32_t IMR; + FWK_RW uint32_t AIMR; + FWK_RW uint32_t ISR; + FWK_RW uint32_t AISR; + FWK_RW uint32_t IESR; + FWK_RW uint32_t OPSR; + uint32_t RESERVED2[2]; + FWK_RW uint32_t FUNRR; + FWK_RW uint32_t FULRR; + FWK_RW uint32_t MEMRR; + uint8_t RESERVED3[0x160 - 0x5C]; + FWK_RW uint32_t EDTR0; + FWK_RW uint32_t EDTR1; + uint32_t RESERVED4[2]; + FWK_RW uint32_t DCCR0; + FWK_RW uint32_t DCCR1; + uint8_t RESERVED5[0xFB0 - 0x178]; + FWK_R uint32_t IDR0; + FWK_R uint32_t IDR1; + uint8_t RESERVED6[0xFC8 - 0xFB8]; + FWK_R uint32_t IIDR; + FWK_R uint32_t AIDR; + uint8_t RESERVED7[0x1000 - 0xFD0]; +}; + +enum ppu_v1_mode { + PPU_V1_MODE_OFF = 0, + PPU_V1_MODE_OFF_EMU = 1, + PPU_V1_MODE_MEM_RET = 2, + PPU_V1_MODE_MEM_RET_EMU = 3, + PPU_V1_MODE_LOGIC_RET = 4, + PPU_V1_MODE_FULL_RET = 5, + PPU_V1_MODE_MEM_OFF = 6, + PPU_V1_MODE_FUNC_RET = 7, + PPU_V1_MODE_ON = 8, + PPU_V1_MODE_WARM_RST = 9, + PPU_V1_MODE_DBG_RECOV = 10, + /* No valid modes after this line */ + PPU_V1_MODE_COUNT +}; + +enum ppu_v1_opmode { + PPU_V1_OPMODE_00, + PPU_V1_OPMODE_01, + PPU_V1_OPMODE_02, + PPU_V1_OPMODE_03, + PPU_V1_OPMODE_04, + PPU_V1_OPMODE_05, + PPU_V1_OPMODE_06, + PPU_V1_OPMODE_07, + PPU_V1_OPMODE_08, + PPU_V1_OPMODE_09, + PPU_V1_OPMODE_10, + PPU_V1_OPMODE_11, + PPU_V1_OPMODE_12, + PPU_V1_OPMODE_13, + PPU_V1_OPMODE_14, + PPU_V1_OPMODE_15, + /* No valid operating modes after this line */ + PPU_V1_OPMODE_COUNT +}; + +enum ppu_v1_op_devactive { + PPU_V1_OP_DEVACTIVE_0, + PPU_V1_OP_DEVACTIVE_1, + PPU_V1_OP_DEVACTIVE_2, + PPU_V1_OP_DEVACTIVE_3, + PPU_V1_OP_DEVACTIVE_INDEPENDENT_COUNT, + PPU_V1_OP_DEVACTIVE_4 = 4, + PPU_V1_OP_DEVACTIVE_5, + PPU_V1_OP_DEVACTIVE_6, + PPU_V1_OP_DEVACTIVE_7, + /* No valid operating mode devactive signal number after this line */ + PPU_V1_OP_DEVACTIVE_COUNT +}; + +/* + * Bit definitions for PWPR + */ +#define PPU_V1_PWPR_PWR_POLICY_POS 0 +#define PPU_V1_PWPR_OP_POLICY_POS 16 + +#define PPU_V1_PWPR_POLICY UINT32_C(0x0000000F) +#define PPU_V1_PWPR_DYNAMIC_EN UINT32_C(0x00000100) +#define PPU_V1_PWPR_OFF_LOCK_EN UINT32_C(0x00001000) +#define PPU_V1_PWPR_OP_POLICY UINT32_C(0x000F0000) +#define PPU_V1_PWPR_OP_DYN_EN UINT32_C(0x01000000) + +/* + * Bit definitions for PWSR + */ +#define PPU_V1_PWSR_PWR_STATUS_POS 0 +#define PPU_V1_PWSR_OP_STATUS_POS 16 + +#define PPU_V1_PWSR_PWR_STATUS UINT32_C(0x0000000F) +#define PPU_V1_PWSR_PWR_DYN_STATUS UINT32_C(0x00000100) +#define PPU_V1_PWSR_OFF_LOCK_STATUS UINT32_C(0x00001000) +#define PPU_V1_PWSR_OP_STATUS UINT32_C(0x000F0000) +#define PPU_V1_PWSR_OP_DYN_STATUS UINT32_C(0x01000000) + +/* + * Bit definitions for DISR + */ +#define PPU_V1_DISR_PWR_DEVACTIVE_STATUS_POS 0 +#define PPU_V1_DISR_OP_DEVACTIVE_STATUS_POS 24 + +/* + * Bit definitions for UNLK + */ +#define PPU_V1_UNLK_OFF_UNLOCK UINT32_C(0x00000001) + +/* + * Bit definitions for PWCR + */ +#define PPU_V1_PWCR_DEV_REQ_EN UINT32_C(0x000000FF) +#define PPU_V1_PWCR_DEV_ACTIVE_EN UINT32_C(0x0007FF00) +#define PPU_V1_PWCR_OP_DEV_ACTIVE_EN UINT32_C(0xFF000000) + +/* + * Definitions for IESR and OPSR + */ +enum ppu_v1_edge_sensitivity { + PPU_V1_EDGE_SENSITIVITY_MASKED, + PPU_V1_EDGE_SENSITIVITY_RISING_EDGE, + PPU_V1_EDGE_SENSITIVITY_FALLING_EDGE, + PPU_V1_EDGE_SENSITIVITY_BOTH_EDGES, + /* No valid edge sensitivity after this line */ + PPU_V1_EDGE_SENSITIVITY_COUNT +}; + +#define PPU_V1_BITS_PER_EDGE_SENSITIVITY 2 +#define PPU_V1_EDGE_SENSITIVITY_MASK 0x3 + +/* + * Bit definitions for IMR + */ +#define PPU_V1_IMR_MASK UINT32_C(0x0000003F) +#define PPU_V1_IMR_STA_POLICY_TRN_IRQ_MASK UINT32_C(0x00000001) +#define PPU_V1_IMR_STA_ACCEPT_IRQ_MASK UINT32_C(0x00000002) +#define PPU_V1_IMR_STA_DENY_IRQ_MASK UINT32_C(0x00000004) +#define PPU_V1_IMR_EMU_ACCEPT_IRQ_MASK UINT32_C(0x00000008) +#define PPU_V1_IMR_EMU_DENY_IRQ_MASK UINT32_C(0x00000010) +#define PPU_V1_IMR_DYN_POLICY_MIN_IRQ_MASK UINT32_C(0x00000020) + +/* + * Bit definitions for AIMR + */ +#define PPU_V1_AIMR_MASK UINT32_C(0x0000001F) +#define PPU_V1_AIMR_UNSPT_POLICY_IRQ_MASK UINT32_C(0x00000001) +#define PPU_V1_AIMR_DYN_ACCEPT_IRQ_MASK UINT32_C(0x00000002) +#define PPU_V1_AIMR_DYN_DENY_IRQ_MASK UINT32_C(0x00000004) +#define PPU_V1_AIMR_STA_POLICY_PWR_IRQ_MASK UINT32_C(0x00000008) +#define PPU_V1_AIMR_STA_POLICY_OP_IRQ_MASK UINT32_C(0x00000010) + +/* + * Bit definitions for ISR + */ +#define PPU_V1_ISR_MASK UINT32_C(0xFF07FFBF) +#define PPU_V1_ISR_STA_POLICY_TRN_IRQ UINT32_C(0x00000001) +#define PPU_V1_ISR_STA_ACCEPT_IRQ UINT32_C(0x00000002) +#define PPU_V1_ISR_STA_DENY_IRQ UINT32_C(0x00000004) +#define PPU_V1_ISR_EMU_ACCEPT_IRQ UINT32_C(0x00000008) +#define PPU_V1_ISR_EMU_DENY_IRQ UINT32_C(0x00000010) +#define PPU_V1_ISR_DYN_POLICY_MIN_IRQ UINT32_C(0x00000020) +#define PPU_V1_ISR_OTHER_IRQ UINT32_C(0x00000080) +#define PPU_V1_ISR_ACTIVE_EDGE_POS 8 +#define PPU_V1_ISR_ACTIVE_EDGE_IRQ_MASK UINT32_C(0x0007FF00) +#define PPU_V1_ISR_ACTIVE_EDGE_ACTIVE0_IRQ UINT32_C(0x00000100) +#define PPU_V1_ISR_ACTIVE_EDGE_ACTIVE1_IRQ UINT32_C(0x00000200) +#define PPU_V1_ISR_ACTIVE_EDGE_ACTIVE2_IRQ UINT32_C(0x00000400) +#define PPU_V1_ISR_ACTIVE_EDGE_ACTIVE3_IRQ UINT32_C(0x00000800) +#define PPU_V1_ISR_ACTIVE_EDGE_ACTIVE4_IRQ UINT32_C(0x00001000) +#define PPU_V1_ISR_ACTIVE_EDGE_ACTIVE5_IRQ UINT32_C(0x00002000) +#define PPU_V1_ISR_ACTIVE_EDGE_ACTIVE6_IRQ UINT32_C(0x00004000) +#define PPU_V1_ISR_ACTIVE_EDGE_ACTIVE7_IRQ UINT32_C(0x00008000) +#define PPU_V1_ISR_ACTIVE_EDGE_ACTIVE8_IRQ UINT32_C(0x00010000) +#define PPU_V1_ISR_ACTIVE_EDGE_ACTIVE9_IRQ UINT32_C(0x00020000) +#define PPU_V1_ISR_ACTIVE_EDGE_ACTIVE10_IRQ UINT32_C(0x00040000) +#define PPU_V1_ISR_OP_ACTIVE_EDGE_POS 24 +#define PPU_V1_ISR_OP_ACTIVE_EDGE_IRQ_MASK UINT32_C(0xFF000000) +#define PPU_V1_ISR_OP_ACTIVE_EDGE_ACTIVE0_IRQ UINT32_C(0x01000000) +#define PPU_V1_ISR_OP_ACTIVE_EDGE_ACTIVE1_IRQ UINT32_C(0x02000000) +#define PPU_V1_ISR_OP_ACTIVE_EDGE_ACTIVE2_IRQ UINT32_C(0x04000000) +#define PPU_V1_ISR_OP_ACTIVE_EDGE_ACTIVE3_IRQ UINT32_C(0x08000000) +#define PPU_V1_ISR_OP_ACTIVE_EDGE_ACTIVE4_IRQ UINT32_C(0x10000000) +#define PPU_V1_ISR_OP_ACTIVE_EDGE_ACTIVE5_IRQ UINT32_C(0x20000000) +#define PPU_V1_ISR_OP_ACTIVE_EDGE_ACTIVE6_IRQ UINT32_C(0x40000000) +#define PPU_V1_ISR_OP_ACTIVE_EDGE_ACTIVE7_IRQ UINT32_C(0x80000000) + +/* + * Bit definitions for AISR + */ +#define PPU_V1_AISR_MASK UINT32_C(0x0000001F) +#define PPU_V1_AISR_UNSPT_POLICY_IRQ UINT32_C(0x00000001) +#define PPU_V1_AISR_DYN_ACCEPT_IRQ UINT32_C(0x00000002) +#define PPU_V1_AISR_DYN_DENY_IRQ UINT32_C(0x00000004) +#define PPU_V1_AISR_STA_POLICY_PWR_IRQ UINT32_C(0x00000008) +#define PPU_V1_AISR_STA_POLICY_OP_IRQ UINT32_C(0x00000010) + +/* + * Bit definitions for AIDR + */ +#define PPU_V1_AIDR_ARCH_REV_MINOR UINT32_C(0x0000000F) +#define PPU_V1_AIDR_ARCH_REV_MAJOR UINT32_C(0x000000F0) + +/* + * Definitions for PPU Arch version ID + */ +#define PPU_V1_ARCH_ID 0x11 + +/* + * Bit definitions for IDR0 + */ +#define PPU_V1_IDR0_NUM_OPMODE_POS 4 +#define PPU_V1_IDR0_NUM_OPMODE UINT32_C(0x000000F0) + +/* + * Initializes the PPU by masking all interrupts and acknowledging any + * previously pending interrupt. + */ +void ppu_v1_init(struct ppu_v1_reg *ppu); + +/* + * Set PPU's power mode and wait for the transition. + * Note: This function currently supports only synchronous transitions with + * limited error detection. + */ +int ppu_v1_set_power_mode(struct ppu_v1_reg *ppu, enum ppu_v1_mode ppu_mode); + +/* + * Request PPU's power mode and don't wait for the transition. + */ +int ppu_v1_request_power_mode(struct ppu_v1_reg *ppu, + enum ppu_v1_mode ppu_mode); + +/* + * Request a change to the PPU's operating mode. + */ +int ppu_v1_request_operating_mode(struct ppu_v1_reg *ppu, + enum ppu_v1_opmode op_mode); + +/* + * Enable PPU's dynamic operating mode transitions + */ +void ppu_v1_opmode_dynamic_enable(struct ppu_v1_reg *ppu, + enum ppu_v1_opmode min_dyn_mode); + +/* + * Enable PPU's dynamic power mode transitions + */ +void ppu_v1_dynamic_enable(struct ppu_v1_reg *ppu, + enum ppu_v1_mode min_dyn_state); + +/* + * Enable the lock in the OFF state + */ +void ppu_v1_lock_off_enable(struct ppu_v1_reg *ppu); + +/* + * Disable the lock in the OFF state + */ +void ppu_v1_lock_off_disable(struct ppu_v1_reg *ppu); + +/* + * Get the current power mode. + */ +enum ppu_v1_mode ppu_v1_get_power_mode(struct ppu_v1_reg *ppu); + +/* + * Get the current programmed power policy mode. + */ +enum ppu_v1_mode ppu_v1_get_programmed_power_mode(struct ppu_v1_reg *ppu); + +/* + * Get the current operating mode. + */ +enum ppu_v1_opmode ppu_v1_get_operating_mode(struct ppu_v1_reg *ppu); + +/* + * Get the current programmed operating mode policy. + */ +enum ppu_v1_opmode ppu_v1_get_programmed_operating_mode(struct ppu_v1_reg *ppu); + +/* + * Check whether the dynamic transitions are enabled or not. + */ +bool ppu_v1_is_dynamic_enabled(struct ppu_v1_reg *ppu); + +/* + * Check whether the locked in the MEM_RET or OFF state. + */ +bool ppu_v1_is_locked(struct ppu_v1_reg *ppu); + +/* + * Check if the DEVACTIVE signal associated to a power mode is high. + */ +bool ppu_v1_is_power_devactive_high(struct ppu_v1_reg *ppu, + enum ppu_v1_mode ppu_mode); + +/* + * Check if the DEVACTIVE signal associated to an operating mode is high. + */ +bool ppu_v1_is_op_devactive_high(struct ppu_v1_reg *ppu, + enum ppu_v1_op_devactive op_devactive); + +/* + * Unlock the power domain from the OFF power mode. + */ +void ppu_v1_off_unlock(struct ppu_v1_reg *ppu); + +/* + * Disable the check of the DEVACTIVE signals by the PPU logic for state + * transition. + */ +void ppu_v1_disable_devactive(struct ppu_v1_reg *ppu); + +/* + * Disable the handshake with the P-channel or the Q-channels + */ +void ppu_v1_disable_handshake(struct ppu_v1_reg *ppu); + +/* + * Set one or more bits of the interrupt mask register. + */ +void ppu_v1_interrupt_mask(struct ppu_v1_reg *ppu, unsigned int mask); + +/* + * Set one or more bits of the additional interrupt mask register. + */ +void ppu_v1_additional_interrupt_mask(struct ppu_v1_reg *ppu, + unsigned int mask); + +/* + * Clear one or more bits of the interrupt mask register. + */ +void ppu_v1_interrupt_unmask(struct ppu_v1_reg *ppu, unsigned int mask); + +/* + * Clear one or more bits of the additional interrupt mask register. + */ +void ppu_v1_additional_interrupt_unmask(struct ppu_v1_reg *ppu, + unsigned int mask); + +/* + * Check if some additional interrupts are pending. + */ +bool ppu_v1_is_additional_interrupt_pending(struct ppu_v1_reg *ppu, + unsigned int mask); + +/* + * Acknowledge one or more interrupts. + */ +void ppu_v1_ack_interrupt(struct ppu_v1_reg *ppu, unsigned int mask); + +/* + * Acknowledge one or more additional interrupts. + */ +void ppu_v1_ack_additional_interrupt(struct ppu_v1_reg *ppu, unsigned int mask); + +/* + * Set input edge sensitivity. See 'enum ppu_v1_edge_sensitivity' for the + * available sensitivities. + */ +void ppu_v1_set_input_edge_sensitivity(struct ppu_v1_reg *ppu, + enum ppu_v1_mode ppu_mode, enum ppu_v1_edge_sensitivity edge_sensitivity); + +/* + * Get input edge sensitivity. See 'enum ppu_v1_edge_sensitivity' for the + * available sensitivities. + */ +enum ppu_v1_edge_sensitivity ppu_v1_get_input_edge_sensitivity( + struct ppu_v1_reg *ppu, enum ppu_v1_mode ppu_mode); + +/* + * Acknowledge a power active edge interrupt. + */ +void ppu_v1_ack_power_active_edge_interrupt(struct ppu_v1_reg *ppu, + enum ppu_v1_mode ppu_mode); + +/* + * Check if a power active edge interrupt is pending. + */ +bool ppu_v1_is_power_active_edge_interrupt(struct ppu_v1_reg *ppu, + enum ppu_v1_mode ppu_mode); + +/* + * Set operating mode active edge sensitivity. See + * 'enum ppu_v1_edge_sensitivity' for the available sensitivities. + */ +void ppu_v1_set_op_active_edge_sensitivity(struct ppu_v1_reg *ppu, + enum ppu_v1_op_devactive op_devactive, + enum ppu_v1_edge_sensitivity edge_sensitivity); + +/* + * Get operating mode active edge sensitivity. + * See 'enum ppu_v1_edge_sensitivity for the available sensitivities. + */ +enum ppu_v1_edge_sensitivity ppu_v1_get_op_active_edge_sensitivity( + struct ppu_v1_reg *ppu, enum ppu_v1_op_devactive op_devactive); + +/* + * Acknowledge operating mode active edge interrupt. + */ +void ppu_v1_ack_op_active_edge_interrupt(struct ppu_v1_reg *ppu, + enum ppu_v1_op_devactive op_devactive); + +/* + * Check if an operating mode active edge interrupt is pending. + */ +bool ppu_v1_is_op_active_edge_interrupt(struct ppu_v1_reg *ppu, + enum ppu_v1_op_devactive op_devactive); + +/* + * Check if the DYN input edge interrupt is pending. + */ +bool ppu_v1_is_dyn_policy_min_interrupt(struct ppu_v1_reg *ppu); + +/* + * Get the number of operating modes. + */ +unsigned int ppu_v1_get_num_opmode(struct ppu_v1_reg *ppu); + +/* + * Get the PPU architecture ID. + */ +unsigned int ppu_v1_get_arch_id(struct ppu_v1_reg *ppu); + +/*! + * \endcond + * @} + */ + +#endif /* PPU_V1_H */ diff --git a/module/psu/include/mod_psu.h b/module/psu/include/mod_psu.h new file mode 100644 index 00000000..026a2221 --- /dev/null +++ b/module/psu/include/mod_psu.h @@ -0,0 +1,257 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef MOD_PSU_H +#define MOD_PSU_H + +#include <stdbool.h> +#include <stdint.h> +#include <fwk_id.h> +#include <fwk_module_idx.h> + +/*! + * \ingroup GroupModules + * \defgroup GroupPsu PSU HAL + * \{ + */ + +/*! + * \defgroup GroupPsuApis APIs + * \{ + */ + +/*! + * \brief Device API. + */ +struct mod_psu_device_api { + /*! + * \brief Get the enabled state of a device. + * + * \param device_id Identifier of the device to get the state of. + * \param [out] enabled \c true if the device is enabled, or \c if it is + * disabled. + * + * \retval FWK_SUCCESS The operation succeeded. + * \retval FWK_E_PARAM One or more parameters were invalid. + * \retval FWK_E_STATE The element cannot accept the request. + * \retval FWK_E_HANDLER An error occurred in the device driver. + */ + int (*get_enabled)(fwk_id_t device_id, bool *enabled); + + /*! + * \brief Set the enabled state of a device. + * + * \param device_id Identifier of the device to set the state of. + * \param enable \c true to enable the device, or \c false to disable it. + * + * \retval FWK_SUCCESS The operation succeeded. + * \retval FWK_E_PARAM One or more parameters were invalid. + * \retval FWK_E_STATE The element cannot accept the request. + * \retval FWK_E_HANDLER An error occurred in the device driver. + */ + int (*set_enabled)(fwk_id_t device_id, bool enable); + + /*! + * \brief Set the enabled state of a device. + * + * \param device_id Identifier of the device to set the state of. + * \param enable \c true to enable the device, or \c false to disable it. + * + * \retval FWK_SUCCESS The operation succeeded. + * \retval FWK_E_PARAM One or more parameters were invalid. + * \retval FWK_E_STATE The element cannot accept the request. + * \retval FWK_E_NOMEM The event queue is full. + * \retval FWK_E_PANIC An error in the framework occurred. + */ + int (*set_enabled_async)(fwk_id_t device_id, bool enable); + + /*! + * \brief Get the voltage of a device. + * + * \param device_id Identifier of the device to get the voltage of. + * \param [out] voltage Voltage in mV. + * + * \retval FWK_SUCCESS The operation succeeded. + * \retval FWK_E_PARAM One or more parameters were invalid. + * \retval FWK_E_STATE The element cannot accept the request. + * \retval FWK_E_HANDLER An error occurred in the device driver. + */ + int (*get_voltage)(fwk_id_t device_id, uintmax_t *voltage); + + /*! + * \brief Set the voltage of a device. + * + * \param device_id Identifier of the device to set the voltage of. + * \param voltage New voltage in mV. + * + * \retval FWK_SUCCESS The operation succeeded. + * \retval FWK_E_PARAM One or more parameters were invalid. + * \retval FWK_E_STATE The element cannot accept the request. + * \retval FWK_E_HANDLER An error occurred in the device driver. + */ + int (*set_voltage)(fwk_id_t device_id, uintmax_t voltage); + + /*! + * \brief Set the voltage of a device. + * + * \param device_id Identifier of the device to set the voltage of. + * \param voltage New voltage in mV. + * + * \retval FWK_SUCCESS The operation succeeded. + * \retval FWK_E_PARAM One or more parameters were invalid. + * \retval FWK_E_STATE The element cannot accept the request. + * \retval FWK_E_NOMEM The event queue is full. + * \retval FWK_E_PANIC An error in the framework occurred. + */ + int (*set_voltage_async)(fwk_id_t device_id, uintmax_t voltage); +}; + +/*! + * \brief Driver API. + */ +struct mod_psu_driver_api { + /*! + * \brief Set the enabled state of a device. + * + * \param id Identifier of the device to set the state of. + * \param enable \c true to enable the device, or \c false to disable it. + * + * \retval FWK_SUCCESS The operation succeeded. + * \return One of the other driver-defined error codes. + */ + int (*set_enabled)(fwk_id_t id, bool enable); + + /*! + * \brief Get the enabled state of a device. + * + * \param id Identifier of the device to get the state of. + * \param [out] enabled \c true if the device is enabled, or \c if it is + * disabled. + * + * \retval FWK_SUCCESS The operation succeeded. + * \return One of the other driver-defined error codes. + */ + int (*get_enabled)(fwk_id_t id, bool *enabled); + + /*! + * \brief Set the voltage of a device. + * + * \param id Identifier of the device to set the voltage of. + * \param voltage New voltage in millivolts (mV). + * + * \retval FWK_SUCCESS The operation succeeded. + * \return One of the other driver-defined error codes. + */ + int (*set_voltage)(fwk_id_t id, uintmax_t voltage); + + /*! + * \brief Get the voltage of a device. + * + * \param id Identifier of the device to get the voltage of. + * \param [out] voltage Voltage in millivolts (mV). + * + * \retval FWK_SUCCESS The operation succeeded. + * \return One of the other driver-defined error codes. + */ + int (*get_voltage)(fwk_id_t id, uintmax_t *voltage); +}; + +/*! + * \} + */ + +/*! + * \defgroup GroupPsuConfig Configuration + * \{ + */ + +/*! + * \brief Device configuration. + */ +struct mod_psu_device_config { + fwk_id_t driver_id; /*!< Driver identifier */ + fwk_id_t driver_api_id; /*!< Driver API identifier */ +}; + +/*! + * \} + */ + +/*! + * \defgroup GroupPsuEvents Events + * \{ + */ + +/*! + * \brief <tt>Set enabled</tt> event response parameters. + */ +struct mod_psu_event_params_set_enabled_response { + int status; /*!< Status of the request */ +}; + +/*! + * \brief <tt>Set voltage</tt> event response parameters. + */ +struct mod_psu_event_params_set_voltage_response { + int status; /*!< Status of the request */ +}; + +/*! + * \} + */ + +/*! + * \defgroup GroupPsuIds Identifiers + * \{ + */ + +/*! + * \brief API indices. + */ +enum mod_psu_api_idx { + /*! API index for mod_psu_api_id_psu_device */ + MOD_PSU_API_IDX_PSU_DEVICE, + + /*! Number of defined APIs */ + MOD_PSU_API_IDX_COUNT +}; + +/*! Device API identifier */ +static const fwk_id_t mod_psu_api_id_psu_device = + FWK_ID_API_INIT(FWK_MODULE_IDX_PSU, MOD_PSU_API_IDX_PSU_DEVICE); + +/*! + * \brief Event indices. + */ +enum mod_psu_event_idx { + /*! Event index for mod_psu_event_id_set_enabled */ + MOD_PSU_EVENT_IDX_SET_ENABLED, + + /*! Event index for mod_psu_event_id_set_voltage */ + MOD_PSU_EVENT_IDX_SET_VOLTAGE, + + /*! Number of defined events */ + MOD_PSU_EVENT_IDX_COUNT +}; + +/*! <tt>Set enabled</tt> event identifier */ +static const fwk_id_t mod_psu_event_id_set_enabled = + FWK_ID_EVENT_INIT(FWK_MODULE_IDX_PSU, MOD_PSU_EVENT_IDX_SET_ENABLED); + +/*! <tt>Set voltage</tt> event identifier */ +static const fwk_id_t mod_psu_event_id_set_voltage = + FWK_ID_EVENT_INIT(FWK_MODULE_IDX_PSU, MOD_PSU_EVENT_IDX_SET_VOLTAGE); + +/*! + * \} + */ + +/*! + * \} + */ + +#endif /* MOD_PSU_H */ diff --git a/module/psu/src/Makefile b/module/psu/src/Makefile new file mode 100644 index 00000000..da070113 --- /dev/null +++ b/module/psu/src/Makefile @@ -0,0 +1,14 @@ +# +# Arm SCP/MCP Software +# Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +BS_LIB_NAME := psu +BS_LIB_SOURCES := \ + mod_psu_device_api.c \ + mod_psu_event.c \ + mod_psu_module.c + +include $(BS_DIR)/lib.mk diff --git a/module/psu/src/mod_psu_device_api.c b/module/psu/src/mod_psu_device_api.c new file mode 100644 index 00000000..f3500d7a --- /dev/null +++ b/module/psu/src/mod_psu_device_api.c @@ -0,0 +1,224 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <assert.h> +#include <fwk_mm.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <fwk_thread.h> +#include <mod_psu_private.h> + +static int api_get_enabled(fwk_id_t device_id, bool *enabled) +{ + int status; + const struct mod_psu_device_ctx *ctx; + + /* This API call cannot target another module */ + if (fwk_id_get_module_idx(device_id) != FWK_MODULE_IDX_PSU) + return FWK_E_PARAM; + + /* Ensure the identifier refers to a valid element */ + if (!fwk_module_is_valid_element_id(device_id)) + return FWK_E_PARAM; + + /* Validate the API call */ + status = fwk_module_check_call(device_id); + if (status != FWK_SUCCESS) + return FWK_E_STATE; + + ctx = __mod_psu_get_valid_device_ctx(device_id); + if (ctx == NULL) + return FWK_E_PARAM; + + /* Get the enabled state from the driver */ + status = ctx->apis.driver->get_enabled(ctx->config->driver_id, enabled); + if (status != FWK_SUCCESS) + return FWK_E_HANDLER; + + return FWK_SUCCESS; +} + +static int api_set_enabled(fwk_id_t device_id, bool enable) +{ + int status; + const struct mod_psu_device_ctx *ctx; + + /* This API call cannot target another module */ + if (fwk_id_get_module_idx(device_id) != FWK_MODULE_IDX_PSU) + return FWK_E_PARAM; + + /* Ensure the identifier refers to a valid element */ + if (!fwk_module_is_valid_element_id(device_id)) + return FWK_E_PARAM; + + /* Validate the API call */ + status = fwk_module_check_call(device_id); + if (status != FWK_SUCCESS) + return FWK_E_STATE; + + ctx = __mod_psu_get_valid_device_ctx(device_id); + if (ctx == NULL) + return FWK_E_PARAM; + + /* Set the enabled state through the driver */ + status = ctx->apis.driver->set_enabled(ctx->config->driver_id, enable); + if (status != FWK_SUCCESS) + return FWK_E_HANDLER; + + return FWK_SUCCESS; +} + +static int api_set_enabled_async(fwk_id_t device_id, bool enable) +{ + int status; + struct fwk_event event; + struct mod_psu_event_params_set_enabled *params; + + /* This API call cannot target another module */ + if (fwk_id_get_module_idx(device_id) != FWK_MODULE_IDX_PSU) + return FWK_E_PARAM; + + /* Ensure the identifier refers to an existing element */ + if (fwk_module_is_valid_element_id(device_id)) + return FWK_E_PARAM; + + /* Validate the API call */ + status = fwk_module_check_call(device_id); + if (status != FWK_SUCCESS) + return FWK_E_STATE; + + /* Build and submit the event */ + event = (struct fwk_event) { + .id = mod_psu_event_id_set_enabled, + .target_id = device_id, + .response_requested = true, + }; + + params = (void *)&event.params; + *params = (struct mod_psu_event_params_set_enabled) { + .enable = enable, + }; + + /* Submit the event for processing */ + status = fwk_thread_put_event(&event); + if (status == FWK_E_NOMEM) + return FWK_E_NOMEM; + else if (status != FWK_SUCCESS) + return FWK_E_PANIC; + + return FWK_SUCCESS; +} + +static int api_get_voltage(fwk_id_t device_id, uintmax_t *voltage) +{ + int status; + const struct mod_psu_device_ctx *ctx; + + /* This API call cannot target another module */ + if (fwk_id_get_module_idx(device_id) != FWK_MODULE_IDX_PSU) + return FWK_E_PARAM; + + /* Ensure the identifier refers to a valid element */ + if (!fwk_module_is_valid_element_id(device_id)) + return FWK_E_PARAM; + + /* Validate the API call */ + status = fwk_module_check_call(device_id); + if (status != FWK_SUCCESS) + return FWK_E_STATE; + + ctx = __mod_psu_get_valid_device_ctx(device_id); + if (ctx == NULL) + return FWK_E_PARAM; + + /* Get the voltage from the driver */ + status = ctx->apis.driver->get_voltage(ctx->config->driver_id, voltage); + if (status != FWK_SUCCESS) + return FWK_E_HANDLER; + + return FWK_SUCCESS; +} + +static int api_set_voltage(fwk_id_t device_id, uintmax_t voltage) +{ + int status; + const struct mod_psu_device_ctx *ctx; + + /* This API call cannot target another module */ + if (fwk_id_get_module_idx(device_id) != FWK_MODULE_IDX_PSU) + return FWK_E_PARAM; + + /* Ensure the identifier refers to a valid element */ + if (!fwk_module_is_valid_element_id(device_id)) + return FWK_E_PARAM; + + /* Validate the API call */ + status = fwk_module_check_call(device_id); + if (status != FWK_SUCCESS) + return FWK_E_STATE; + + ctx = __mod_psu_get_valid_device_ctx(device_id); + if (ctx == NULL) + return FWK_E_PARAM; + + /* Set the voltage state through the driver */ + status = ctx->apis.driver->set_voltage(ctx->config->driver_id, voltage); + if (status != FWK_SUCCESS) + return FWK_E_HANDLER; + + return FWK_SUCCESS; +} + +static int api_set_voltage_async(fwk_id_t device_id, uintmax_t voltage) +{ + int status; + struct fwk_event event; + struct mod_psu_event_params_set_voltage *params; + + /* This API call cannot target another module */ + if (fwk_id_get_module_idx(device_id) != FWK_MODULE_IDX_PSU) + return FWK_E_PARAM; + + /* Ensure the identifier refers to an existing element */ + if (fwk_module_is_valid_element_id(device_id)) + return FWK_E_PARAM; + + /* Validate the API call */ + status = fwk_module_check_call(device_id); + if (status != FWK_SUCCESS) + return FWK_E_STATE; + + /* Build and submit the event */ + event = (struct fwk_event) { + .id = mod_psu_event_id_set_enabled, + .target_id = device_id, + .response_requested = true, + }; + + params = (void *)&event.params; + *params = (struct mod_psu_event_params_set_voltage) { + .voltage = voltage, + }; + + status = fwk_thread_put_event(&event); + if (status == FWK_E_NOMEM) + return FWK_E_NOMEM; + else if (status != FWK_SUCCESS) + return FWK_E_PANIC; + + return FWK_SUCCESS; +} + +/* Module API implementation */ +const struct mod_psu_device_api __mod_psu_device_api = { + .get_enabled = api_get_enabled, + .set_enabled = api_set_enabled, + .set_enabled_async = api_set_enabled_async, + .get_voltage = api_get_voltage, + .set_voltage = api_set_voltage, + .set_voltage_async = api_set_voltage_async, +}; diff --git a/module/psu/src/mod_psu_device_api_private.h b/module/psu/src/mod_psu_device_api_private.h new file mode 100644 index 00000000..6959f594 --- /dev/null +++ b/module/psu/src/mod_psu_device_api_private.h @@ -0,0 +1,16 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef MOD_PSU_DEVICE_API_PRIVATE_H +#define MOD_PSU_DEVICE_API_PRIVATE_H + +#include <mod_psu.h> + +/* Module API implementation */ +extern const struct mod_psu_device_api __mod_psu_device_api; + +#endif /* MOD_PSU_DEVICE_API_PRIVATE_H */ diff --git a/module/psu/src/mod_psu_event.c b/module/psu/src/mod_psu_event.c new file mode 100644 index 00000000..104045a6 --- /dev/null +++ b/module/psu/src/mod_psu_event.c @@ -0,0 +1,98 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <assert.h> +#include <fwk_macros.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <mod_psu_private.h> + +int mod_psu_event_set_enabled( + const struct fwk_event *event, + struct fwk_event *response) +{ + const struct mod_psu_device_ctx *ctx; + const struct mod_psu_event_params_set_enabled *params; + struct mod_psu_event_params_set_enabled_response *response_params; + + /* These conditions were checked when we submitted the event */ + assert(fwk_id_get_module_idx(event->target_id) == FWK_MODULE_IDX_PSU); + assert(fwk_module_is_valid_element_id(event->target_id)); + + /* Explicitly cast to our parameter types */ + params = (void *)&event->params; + response_params = (void *)&response->params; + + ctx = __mod_psu_get_device_ctx(event->target_id); + + /* Set the enabled state through the driver */ + response_params->status = ctx->apis.driver->set_enabled( + ctx->config->driver_id, + params->enable); + + return FWK_SUCCESS; +} + +int mod_psu_event_set_voltage( + const struct fwk_event *event, + struct fwk_event *response) +{ + const struct mod_psu_device_ctx *ctx; + const struct mod_psu_event_params_set_voltage *params; + struct mod_psu_event_params_set_voltage_response *response_params; + + /* These conditions were checked when we submitted the event */ + assert(fwk_id_get_module_idx(event->target_id) == FWK_MODULE_IDX_PSU); + assert(fwk_module_is_valid_element_id(event->target_id)); + + /* Explicitly cast to our parameter types */ + params = (void *)&event->params; + response_params = (void *)&response->params; + + ctx = __mod_psu_get_device_ctx(event->target_id); + + /* Set the voltage through the driver */ + response_params->status = ctx->apis.driver->set_voltage( + ctx->config->driver_id, + params->voltage); + + return FWK_SUCCESS; +} + +int __mod_psu_process_event( + const struct fwk_event *event, + struct fwk_event *response) +{ + typedef int (*handler_t)( + const struct fwk_event *event, + struct fwk_event *response); + + static const handler_t handlers[] = { + [MOD_PSU_EVENT_IDX_SET_ENABLED] = mod_psu_event_set_enabled, + [MOD_PSU_EVENT_IDX_SET_VOLTAGE] = mod_psu_event_set_voltage, + }; + + unsigned int event_idx; + handler_t handler; + + /* We only handle the events defined by us */ + if (fwk_id_get_module_idx(event->id) != FWK_MODULE_IDX_PSU) + return FWK_E_PARAM; + + /* Ensure the event index is within bounds we can handle */ + event_idx = fwk_id_get_event_idx(event->id); + if (event_idx >= FWK_ARRAY_SIZE(handlers)) + return FWK_E_PARAM; + + /* Ensure we have an implemented handler for this event */ + handler = handlers[event_idx]; + if (handler == NULL) + return FWK_E_PARAM; + + /* Delegate event handling to the relevant handler */ + return handler(event, response); +} diff --git a/module/psu/src/mod_psu_event_private.h b/module/psu/src/mod_psu_event_private.h new file mode 100644 index 00000000..e451c8ac --- /dev/null +++ b/module/psu/src/mod_psu_event_private.h @@ -0,0 +1,29 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef MOD_PSU_EVENT_PRIVATE_H +#define MOD_PSU_EVENT_PRIVATE_H + +#include <fwk_event.h> +#include <fwk_id.h> + +/* "Set enabled" event */ +struct mod_psu_event_params_set_enabled { + bool enable; +}; + +/* "Set voltage" event */ +struct mod_psu_event_params_set_voltage { + uintmax_t voltage; +}; + +/* Event handler */ +int __mod_psu_process_event( + const struct fwk_event *event, + struct fwk_event *response); + +#endif /* MOD_PSU_EVENT_PRIVATE_H */ diff --git a/module/psu/src/mod_psu_module.c b/module/psu/src/mod_psu_module.c new file mode 100644 index 00000000..9eab2f1d --- /dev/null +++ b/module/psu/src/mod_psu_module.c @@ -0,0 +1,128 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <assert.h> +#include <fwk_macros.h> +#include <fwk_mm.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <mod_psu_private.h> + +/* Device context table */ +static struct mod_psu_device_ctx (*device_ctx)[]; + +static int psu_init( + fwk_id_t module_id, + unsigned int element_count, + const void *data) +{ + device_ctx = fwk_mm_calloc( + element_count, + sizeof((*device_ctx)[0])); + if (device_ctx == NULL) + return FWK_E_NOMEM; + + return FWK_SUCCESS; +} + +static int psu_element_init( + fwk_id_t device_id, + unsigned int sub_element_count, + const void *data) +{ + assert(sub_element_count == 0); + + __mod_psu_get_device_ctx(device_id)->config = data; + + return FWK_SUCCESS; +} + +static int psu_bind_element(fwk_id_t device_id, unsigned int round) +{ + int status; + const struct mod_psu_device_ctx *ctx; + + /* Only handle the first round */ + if (round > 0) + return FWK_SUCCESS; + + ctx = __mod_psu_get_device_ctx(device_id); + + /* Bind to the driver */ + status = fwk_module_bind( + ctx->config->driver_id, + ctx->config->driver_api_id, + &ctx->apis.driver); + if (status != FWK_SUCCESS) { + assert(false); + + return FWK_E_PANIC; + } + + assert(ctx->apis.driver->set_enabled != NULL); + assert(ctx->apis.driver->get_enabled != NULL); + assert(ctx->apis.driver->set_voltage != NULL); + assert(ctx->apis.driver->get_voltage != NULL); + + return FWK_SUCCESS; +} + +static int psu_bind(fwk_id_t id, unsigned int round) +{ + /* We only need to handle element binding */ + if (fwk_id_is_type(id, FWK_ID_TYPE_ELEMENT)) + return psu_bind_element(id, round); + + return FWK_SUCCESS; +} + +static int psu_process_bind_request( + fwk_id_t source_id, + fwk_id_t target_id, + fwk_id_t api_id, + const void **api) +{ + /* Only accept binds to the elements */ + if (!fwk_id_is_type(target_id, FWK_ID_TYPE_ELEMENT)) + return FWK_E_PARAM; + + /* Only expose the device API */ + if (!fwk_id_is_equal(api_id, mod_psu_api_id_psu_device)) + return FWK_E_PARAM; + + *api = &__mod_psu_device_api; + + return FWK_SUCCESS; +} + +struct mod_psu_device_ctx *__mod_psu_get_device_ctx(fwk_id_t device_id) +{ + unsigned int element_idx = fwk_id_get_element_idx(device_id); + + return &(*device_ctx)[element_idx]; +} + +struct mod_psu_device_ctx *__mod_psu_get_valid_device_ctx(fwk_id_t device_id) +{ + if (fwk_module_check_call(device_id) != FWK_SUCCESS) + return NULL; + + return __mod_psu_get_device_ctx(device_id); +} + +/* Module description */ +const struct fwk_module module_psu = { + .name = "PSU", + .type = FWK_MODULE_TYPE_HAL, + .init = psu_init, + .element_init = psu_element_init, + .bind = psu_bind, + .process_bind_request = psu_process_bind_request, + .process_event = __mod_psu_process_event, + .api_count = MOD_PSU_API_IDX_COUNT, + .event_count = MOD_PSU_EVENT_IDX_COUNT, +}; diff --git a/module/psu/src/mod_psu_module_private.h b/module/psu/src/mod_psu_module_private.h new file mode 100644 index 00000000..adf01784 --- /dev/null +++ b/module/psu/src/mod_psu_module_private.h @@ -0,0 +1,28 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef MOD_PSU_DEVICE_CTX_PRIVATE_H +#define MOD_PSU_DEVICE_CTX_PRIVATE_H + +#include <fwk_id.h> +#include <mod_psu.h> + +/* Device context */ +struct mod_psu_device_ctx { + /* Device configuration */ + const struct mod_psu_device_config *config; + + struct { + /* Driver API */ + const struct mod_psu_driver_api *driver; + } apis; +}; + +struct mod_psu_device_ctx *__mod_psu_get_device_ctx(fwk_id_t device_id); +struct mod_psu_device_ctx *__mod_psu_get_valid_device_ctx(fwk_id_t device_id); + +#endif /* MOD_PSU_DEVICE_CTX_PRIVATE_H */ diff --git a/module/psu/src/mod_psu_private.h b/module/psu/src/mod_psu_private.h new file mode 100644 index 00000000..446e9f63 --- /dev/null +++ b/module/psu/src/mod_psu_private.h @@ -0,0 +1,16 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef MOD_PSU_PRIVATE_H +#define MOD_PSU_PRIVATE_H + +#include <fwk_id.h> +#include <mod_psu_device_api_private.h> +#include <mod_psu_event_private.h> +#include <mod_psu_module_private.h> + +#endif /* MOD_PSU_PRIVATE_H */ diff --git a/module/reg_sensor/include/mod_reg_sensor.h b/module/reg_sensor/include/mod_reg_sensor.h new file mode 100644 index 00000000..73ba9156 --- /dev/null +++ b/module/reg_sensor/include/mod_reg_sensor.h @@ -0,0 +1,38 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef MOD_REG_SENSOR_H +#define MOD_REG_SENSOR_H + +#include <stdint.h> + +/*! + * \addtogroup GroupModules Modules + * @{ + */ + +/*! + * \defgroup GroupModuleRegSensor Register Sensor Driver + * + * \brief Driver for simple, register-based sensors. + * @{ + */ + +/*! \brief Element configuration */ +struct mod_reg_sensor_dev_config { + uintptr_t reg; /*!< Address of the sensor register */ +}; + +/*! + * @} + */ + +/*! + * @} + */ + +#endif /* MOD_REG_SENSOR_H */ diff --git a/module/reg_sensor/src/Makefile b/module/reg_sensor/src/Makefile new file mode 100644 index 00000000..817a1efe --- /dev/null +++ b/module/reg_sensor/src/Makefile @@ -0,0 +1,11 @@ +# +# Arm SCP/MCP Software +# Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +BS_LIB_NAME := reg_sensor +BS_LIB_SOURCES := mod_reg_sensor.c + +include $(BS_DIR)/lib.mk diff --git a/module/reg_sensor/src/mod_reg_sensor.c b/module/reg_sensor/src/mod_reg_sensor.c new file mode 100644 index 00000000..65157075 --- /dev/null +++ b/module/reg_sensor/src/mod_reg_sensor.c @@ -0,0 +1,101 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <assert.h> +#include <stdint.h> +#include <fwk_errno.h> +#include <fwk_id.h> +#include <fwk_mm.h> +#include <fwk_module.h> +#include <mod_reg_sensor.h> +#include <mod_sensor.h> + +static struct mod_reg_sensor_dev_config **config_table; + +/* + * Module API + */ +static int get_value(fwk_id_t id, uint64_t *value) +{ + int status; + struct mod_reg_sensor_dev_config *config; + + status = fwk_module_check_call(id); + if (status != FWK_SUCCESS) { + assert(false); + return status; + } + + config = config_table[fwk_id_get_element_idx(id)]; + + if (value == NULL) { + assert(false); + return FWK_E_PARAM; + } + + *value = *(uint64_t*)config->reg; + + return FWK_SUCCESS; +} + +static const struct mod_sensor_driver_api reg_sensor_api = { + .get_value = get_value, +}; + +/* + * Framework handlers + */ +static int reg_sensor_init(fwk_id_t module_id, + unsigned int element_count, + const void *data) +{ + config_table = fwk_mm_alloc(element_count, sizeof(*config_table)); + + if (config_table == NULL) { + /* Unable to allocate device context memory */ + assert(false); + return FWK_E_NOMEM; + } + + return FWK_SUCCESS; +} + +static int reg_sensor_element_init(fwk_id_t element_id, + unsigned int sub_element_count, + const void *data) +{ + struct mod_reg_sensor_dev_config *config = + (struct mod_reg_sensor_dev_config *)data; + + if (config->reg == 0) { + /* Invalid element configuration */ + assert(false); + return FWK_E_PARAM; + } + + config_table[fwk_id_get_element_idx(element_id)] = config; + + return FWK_SUCCESS; +} + +static int reg_sensor_process_bind_request(fwk_id_t source_id, + fwk_id_t target_id, + fwk_id_t api_type, + const void **api) +{ + *api = ®_sensor_api; + return FWK_SUCCESS; +} + +const struct fwk_module module_reg_sensor = { + .name = "Register Sensor", + .api_count = 1, + .type = FWK_MODULE_TYPE_DRIVER, + .init = reg_sensor_init, + .element_init = reg_sensor_element_init, + .process_bind_request = reg_sensor_process_bind_request, +}; diff --git a/module/scmi/include/internal/mod_scmi.h b/module/scmi/include/internal/mod_scmi.h new file mode 100644 index 00000000..822e196b --- /dev/null +++ b/module/scmi/include/internal/mod_scmi.h @@ -0,0 +1,63 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * System Control and Management Interface (SCMI) support. + */ + +#ifndef MOD_INTERNAL_SCMI_H +#define MOD_INTERNAL_SCMI_H + +#include <stddef.h> +#include <stdint.h> +#include <mod_scmi.h> + +#define SCMI_VERSION 0x10000 + +#define SCMI_MESSAGE_HEADER_MESSAGE_ID_POS 0 +#define SCMI_MESSAGE_HEADER_PROTOCOL_ID_POS 10 +#define SCMI_MESSAGE_HEADER_TOKEN_POS 18 + +#define SCMI_MESSAGE_HEADER_MESSAGE_ID_MASK \ + (UINT32_C(0x3FF) << SCMI_MESSAGE_HEADER_MESSAGE_ID_POS) +#define SCMI_MESSAGE_HEADER_PROTOCOL_ID_MASK \ + (UINT32_C(0xFF) << SCMI_MESSAGE_HEADER_PROTOCOL_ID_POS) +#define SCMI_MESSAGE_HEADER_TOKEN_MASK \ + (UINT32_C(0x3FF) << SCMI_MESSAGE_HEADER_TOKEN_POS) + +#define SCMI_MESSAGE_HEADER(MESSAGE_ID, PROTOCOL_ID, TOKEN) \ + ((((MESSAGE_ID) << SCMI_MESSAGE_HEADER_MESSAGE_ID_POS) & \ + SCMI_MESSAGE_HEADER_MESSAGE_ID_MASK) | \ + (((PROTOCOL_ID) << SCMI_MESSAGE_HEADER_PROTOCOL_ID_POS) & \ + SCMI_MESSAGE_HEADER_PROTOCOL_ID_MASK) | \ + (((TOKEN) << SCMI_MESSAGE_HEADER_TOKEN_POS) & \ + SCMI_MESSAGE_HEADER_TOKEN_POS)) + +/* SCMI service context */ +struct scmi_service_ctx { + /* Pointer to SCMI service configuration data */ + const struct mod_scmi_service_config *config; + + /* + * Identifier of the transport entity used by the service to read/respond + * to SCMI messages. + */ + fwk_id_t transport_id; + + /* Pointer to the transport API used to read and respond to messages */ + const struct mod_scmi_to_transport_api *transport_api; + + /* Copy of the pointer to the 'respond' function within the transport API */ + int (*respond)(fwk_id_t transport_id, const void *payload, size_t size); + + /* SCMI identifier of the protocol processing the current message */ + unsigned int scmi_protocol_id; + + /* SCMI identifier of the message currently being processed */ + unsigned int scmi_message_id; +}; + +#endif /* MOD_INTERNAL_SCMI_H */ diff --git a/module/scmi/include/internal/scmi.h b/module/scmi/include/internal/scmi.h new file mode 100644 index 00000000..ae910a7c --- /dev/null +++ b/module/scmi/include/internal/scmi.h @@ -0,0 +1,171 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * System Control and Management Interface (SCMI) protocol independent + * definitions. + */ + +#ifndef SCMI_H +#define SCMI_H + +#include <stdint.h> + +/*! + * \defgroup GroupSCMI System Control & Management Interface (SCMI) + * @{ + */ + +/*! + * \brief Maximum value for an agent identifier. The limit is derived from the + * the base protocol's "PROTOCOL_ATTRIBUTES" command. This command returns + * a 32-bits "attributes" value which, in turn, contains an 8-bit field + * giving the number of agents in the system. + */ +#define SCMI_AGENT_ID_MAX 0xFF + +/*! + * \brief SCMI identifier of the platform. + */ +#define SCMI_PLATFORM_ID 0 + +/*! Maximum value of an SCMI protocol identifier */ +#define SCMI_PROTOCOL_ID_MAX 0xFF + +/*! + * \brief Return a packed 32-bit message header comprised of a 10-bit message + * identifier, an 8-bit protocol identifier, and a 10-bit token. + * + * \param MESSAGE_ID Message identifier. + * \param PROTOCOL_ID Protocol identifier. + * \param TOKEN Token. + */ +#define SCMI_MESSAGE_HEADER(MESSAGE_ID, PROTOCOL_ID, TOKEN) \ + ((((MESSAGE_ID) << SCMI_MESSAGE_HEADER_MESSAGE_ID_POS) & \ + SCMI_MESSAGE_HEADER_MESSAGE_ID_MASK) | \ + (((PROTOCOL_ID) << SCMI_MESSAGE_HEADER_PROTOCOL_ID_POS) & \ + SCMI_MESSAGE_HEADER_PROTOCOL_ID_MASK) | \ + (((TOKEN) << SCMI_MESSAGE_HEADER_TOKEN_POS) & \ + SCMI_MESSAGE_HEADER_TOKEN_POS)) + +/*! + * \brief Entity role. + */ +enum scmi_role { + /*! Agent entity */ + SCMI_ROLE_AGENT, + + /*! Platform entity */ + SCMI_ROLE_PLATFORM +}; + +/*! + * \brief Agent type + * + * \details The SCMI specification defines three specific agent types: + * - a PSCI implementation on AP processors. + * - a management agent. + * - an OSPM. + * The POWER_STATE_SET command targeting a power domain, including AP cores, is + * processed only if issued by a PSCI agent. The processing of the + * SYSTEM_POWER_STATE_SET command depends on the type of the agent that issued + * it. The OTHER type is added here to cover the other type of agents. + */ +enum scmi_agent_type { + /*! PSCI agent */ + SCMI_AGENT_TYPE_PSCI, + + /*! Management agent */ + SCMI_AGENT_TYPE_MANAGEMENT, + + /*! OSPM agent */ + SCMI_AGENT_TYPE_OSPM, + + /*! Other agent */ + SCMI_AGENT_TYPE_OTHER, + + /*! Number of agent types */ + SCMI_AGENT_TYPE_COUNT, +}; + +/*! + * \brief Channel type. + * + * \details Defines the channel direction in terms of the master to the slave. + * + * \note The integer values of this enumeration are based on the master of + * communications in that configuration. + */ +enum scmi_channel_type { + /*!< Agent-to-platform */ + SCMI_CHANNEL_TYPE_A2P = SCMI_ROLE_AGENT, + + /*!< Platform-to-agent */ + SCMI_CHANNEL_TYPE_P2A = SCMI_ROLE_PLATFORM +}; + +/*! + * \brief SCMI error codes. + */ +enum scmi_error { + SCMI_SUCCESS = 0, + SCMI_NOT_SUPPORTED = -1, + SCMI_INVALID_PARAMETERS = -2, + SCMI_DENIED = -3, + SCMI_NOT_FOUND = -4, + SCMI_OUT_OF_RANGE = -5, + SCMI_BUSY = -6, + SCMI_COMMS_ERROR = -7, + SCMI_GENERIC_ERROR = -8, + SCMI_HARDWARE_ERROR = -9, + SCMI_PROTOCOL_ERROR = -10, +}; + +/*! + * \brief Common command identifiers. + */ +enum scmi_command_id { + SCMI_PROTOCOL_VERSION = 0x000, + SCMI_PROTOCOL_ATTRIBUTES = 0x001, + SCMI_PROTOCOL_MESSAGE_ATTRIBUTES = 0x002 +}; + +/*! + * \brief Generic platform-to-agent PROTOCOL_VERSION structure. + */ +struct __attribute((packed)) scmi_protocol_version_p2a { + int32_t status; + uint32_t version; +}; + +/*! + * \brief Generic platform-to-agent PROTOCOL_ATTRIBUTES structure. + */ +struct __attribute((packed)) scmi_protocol_attributes_p2a { + int32_t status; + uint32_t attributes; +}; + +/*! + * \brief Generic agent-to-platform PROTOCOL_MESSAGE_ATTRIBUTES structure. + */ +struct __attribute((packed)) scmi_protocol_message_attributes_a2p { + uint32_t message_id; +}; + +/*! + * \brief Generic platform-to-agent PROTOCOL_MESSAGE_ATTRIBUTES structure. + */ +struct __attribute((packed)) scmi_protocol_message_attributes_p2a { + int32_t status; + uint32_t attributes; +}; + +/*! + * @} + */ + +#endif /* SCMI_H */ diff --git a/module/scmi/include/internal/scmi_base.h b/module/scmi/include/internal/scmi_base.h new file mode 100644 index 00000000..4e5c255b --- /dev/null +++ b/module/scmi/include/internal/scmi_base.h @@ -0,0 +1,90 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * SCMI base protocol definitions. + */ + +#ifndef SCMI_BASE_H +#define SCMI_BASE_H + +#define SCMI_PROTOCOL_ID_BASE UINT32_C(0x10) +#define SCMI_PROTOCOL_VERSION_BASE UINT32_C(0x10000) + +enum scmi_base_command_id { + SCMI_BASE_DISCOVER_VENDOR = 0x003, + SCMI_BASE_DISCOVER_SUB_VENDOR = 0x004, + SCMI_BASE_DISCOVER_IMPLEMENTATION_VERSION = 0x005, + SCMI_BASE_DISCOVER_LIST_PROTOCOLS = 0x006, + SCMI_BASE_DISCOVER_AGENT = 0x007, + SCMI_BASE_NOTIFY_ERRORS = 0x008, +}; + +/* + * PROTOCOL_ATTRIBUTES + */ + +#define SCMI_BASE_PROTOCOL_ATTRIBUTES_NUM_PROTOCOLS_POS 0 +#define SCMI_BASE_PROTOCOL_ATTRIBUTES_NUM_AGENTS_POS 8 + +#define SCMI_BASE_PROTOCOL_ATTRIBUTES_NUM_PROTOCOLS_MASK 0xFF +#define SCMI_BASE_PROTOCOL_ATTRIBUTES_NUM_AGENTS_MASK 0xFF00 + +#define SCMI_BASE_PROTOCOL_ATTRIBUTES(NUM_PROTOCOLS, NUM_AGENTS) \ + ((((NUM_PROTOCOLS) << SCMI_BASE_PROTOCOL_ATTRIBUTES_NUM_PROTOCOLS_POS) \ + & SCMI_BASE_PROTOCOL_ATTRIBUTES_NUM_PROTOCOLS_MASK) | \ + (((NUM_AGENTS) << SCMI_BASE_PROTOCOL_ATTRIBUTES_NUM_AGENTS_POS) \ + & SCMI_BASE_PROTOCOL_ATTRIBUTES_NUM_AGENTS_MASK)) + +/* + * BASE_DISCOVER_VENDOR + */ +struct __attribute((packed)) scmi_base_discover_vendor_p2a { + int32_t status; + char vendor_identifier[16]; +}; + +/* + * BASE_DISCOVER_SUB_VENDOR + */ +struct __attribute((packed)) scmi_base_discover_sub_vendor_p2a { + int32_t status; + char sub_vendor_identifier[16]; +}; + +/* + * BASE_DISCOVER_IMPLEMENTATION_VERSION + * No special structure right now, see protocol_version. + */ + +/* + * BASE_DISCOVER_LIST_PROTOCOLS + */ +struct __attribute((packed)) scmi_base_discover_list_protocols_a2p { + uint32_t skip; +}; + +struct __attribute((packed)) scmi_base_discover_list_protocols_p2a { + int32_t status; + uint32_t num_protocols; + uint32_t protocols[]; +}; + +/* + * BASE_DISCOVER_AGENT + */ +struct __attribute((packed)) scmi_base_discover_agent_a2p { + uint32_t agent_id; +}; + +struct __attribute((packed)) scmi_base_discover_agent_p2a { + int32_t status; + char name[16]; +}; + +extern struct scp_scmi_protocol scmi_base_protocol; + +#endif /* SCMI_BASE_H */ diff --git a/module/scmi/include/mod_scmi.h b/module/scmi/include/mod_scmi.h new file mode 100644 index 00000000..b846064b --- /dev/null +++ b/module/scmi/include/mod_scmi.h @@ -0,0 +1,425 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * System Control and Management Interface (SCMI) support. + */ + +#ifndef MOD_SCMI_H +#define MOD_SCMI_H + +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <internal/scmi.h> + +/*! + * \addtogroup GroupModules Modules + * @{ + */ + +/*! + * \defgroup GroupSCMI System Control & Management Interface (SCMI) + * @{ + */ + +/*! + * \brief Index of the interfaces exposed by the SCMI module. + */ +enum mod_scmi_api_idx { + MOD_SCMI_API_IDX_PROTOCOL, + MOD_SCMI_API_IDX_TRANSPORT, + MOD_SCMI_API_IDX_COUNT, +}; + +/*! + * \brief Agent descriptor + */ +struct mod_scmi_agent { + /*! \brief Type of the agent. */ + enum scmi_agent_type type; + + /*! + * \brief Pointer to the agent's name. This pointer may be equal to NULL. + * In that case, the agent will be assigned a default name based on + * its type: "PSCI", "MANAGEMENT", "OSPM", or "OTHER". + * + * \details The agent name of the BASE_DISCOVER_AGENT command can be at + * most 16 bytes in length (null terminator included). If the value + * assigned to this variable is longer than the limit then the agents + * in the system will be provided with a truncated version of it. + */ + const char *name; +}; + +/*! + * \brief SCMI module configuration data. + */ +struct mod_scmi_config { + /*! + * \brief Maximum number of SCMI protocol modules that can bind to the + * SCMI module. + */ + unsigned int protocol_count_max; + + /*! + * \brief Number of agents in the system. Must be smaller than or equal to + * SCMI_AGENT_ID_MAX. + */ + unsigned int agent_count; + + /*! + * \brief Table of descriptors of the agents in the system. + * + * \details Entry 'i' in the table contains the descriptor of the agent + * with identifier 'i'. The agent identifiers are sequential and start + * from one, with the identifier 0 being reserved to identify the + * platform. The table must therefore have space for 'agent_count + 1' + * entries. The first entry is ignored. + */ + const struct mod_scmi_agent *agent_table; + + /*! + * \brief Pointer to the vendor identifier. + * + * \note The vendor identifier in the BASE_DISCOVER_VENDOR command is + * up to 16 bytes in length (null terminator included). The vendor + * identifier provided to agents will be truncated if it exceeds + * this limit. + */ + const char *vendor_identifier; + + /*! + * \brief Pointer to the sub-vendor identifier. + * + * \note The sub-vendor identifier in the BASE_DISCOVER_SUB_VENDOR + * command is up to 16 bytes in length (null terminator included). + * The sub-vendor identifier provided to agents will be truncated + * if it exceeds this limit. + */ + const char *sub_vendor_identifier; +}; + +/*! + * \brief Service configuration data. + */ +struct mod_scmi_service_config { + /*! + * \brief Identifier of the transport entity. + */ + fwk_id_t transport_id; + + /*! + * \brief Identifier of the API of the transport entity. + */ + fwk_id_t transport_api_id; + + /*! + * \brief Identifier of the agent. + * + * \details An SCMI channel is the communication channel between an agent + * and the platform. This is the identifier assigned in the system + * to the agent using the transport channel identified by + * 'scmi_channel_id'. The agent identifier must be greater than or + * equal to one (the identifier 0 is assigned to the platform) and + * lower than or equal to the number of agents declared in SCMI + * module configuration data. + */ + unsigned int scmi_agent_id; +}; + +/*! + * \brief SCMI module to transport entity API. + */ +struct mod_scmi_to_transport_api { + /*! + * \brief Check whether a channel is secure or non-secure. + * + * \param channel_id Channel identifier. + * \param secure Pointer to storage for the channel security state. True + * if the channel is secure, or false if it is non-secure. + * + * \retval FWK_SUCCESS The operation succeeded. + * \retval FWK_E_PARAM The channel_id parameter is invalid. + * \retval FWK_E_PARAM The secure parameter is NULL. + * \return One of the standard error codes for implementation-defined + * errors. + */ + int (*get_secure)(fwk_id_t channel_id, bool *secure); + + /*! + * \brief Get the maximum permitted payload size of a channel. + * + * \param channel_id Channel identifier. + * \param size Pointer to storage for the maximum payload size in bytes. + * + * \retval FWK_SUCCESS The operation succeeded. + * \retval FWK_E_PARAM The channel_id parameter is invalid. + * \retval FWK_E_PARAM The size parameter is NULL. + * \return One of the standard error codes for implementation-defined + * errors. + */ + int (*get_max_payload_size)(fwk_id_t channel_id, size_t *size); + + /*! + * \brief Get the SCMI message header from a channel. + * + * \param channel_id Channel identifier. + * \param message_header Pointer to storage for the SCMI message header. + * + * \retval FWK_SUCCESS The operation succeeded. + * \retval FWK_E_PARAM The channel_id parameter is invalid. + * \retval FWK_E_PARAM The message_header parameter is NULL. + * \retval FWK_E_ACCESS No message is available to read. + * \return One of the standard error codes for implementation-defined + * errors. + */ + int (*get_message_header)(fwk_id_t channel_id, uint32_t *message_header); + + /*! + * \brief Get the SCMI payload from a channel. + * + * \param channel_id Channel identifier. + * \param payload Pointer to storage for the pointer to the payload. + * \param size Pointer to storage for the payload size. May be NULL, in + * which case the parameter should be ignored. + * + * \retval FWK_SUCCESS The operation succeeded. + * \retval FWK_E_PARAM The channel_id parameter is invalid. + * \retval FWK_E_PARAM The payload parameter is NULL. + * \retval FWK_E_ACCESS No message is available to read. + * \return One of the standard error codes for implementation-defined + * errors. + */ + int (*get_payload)(fwk_id_t channel_id, const void **payload, + size_t *size); + + /*! + * \brief Write part of a payload to a channel. + * + * \param channel_id Channel identifier. + * \param offset Offset to begin writing at. + * \param payload Payload data to write. + * \param size Size of the payload data. + * + * \retval FWK_SUCCESS The operation succeeded. + * \retval FWK_E_PARAM The payload parameter is NULL. + * \retval FWK_E_PARAM The offset and size provided are not within the + * bounds of the payload area. + * \return One of the standard error codes for implementation-defined + * errors. + */ + int (*write_payload)(fwk_id_t channel_id, size_t offset, + const void *payload, size_t size); + + /*! + * \brief Respond to an SCMI message on a channel. + * + * \param channel_id Channel identifier. + * \param payload Payload data to write, or NULL if a payload has already + * been written. + * \param size Size of the payload source. + * + * \retval FWK_SUCCESS The operation succeeded. + * \retval FWK_E_PARAM The channel_id parameter is invalid. + * \retval FWK_E_PARAM The size parameter is less than the size of one + * payload entry. + * \retval FWK_E_ACCESS No message is available to respond to. + * \return One of the standard error codes for implementation-defined + * errors. + */ + int (*respond)(fwk_id_t channel_id, const void *payload, size_t size); +}; + +/*! + * \brief Transport entity API to SCMI module API. + */ +struct mod_scmi_from_transport_api { + /*! + * \brief Signal to a service that a message is incoming. + * + * \param service_id SCMI service identifier. + * + * \retval FWK_SUCCESS The operation succeeded. + * \retval FWK_E_PARAM The channel_id parameter is invalid. + * \retval FWK_E_PARAM The secure parameter is NULL. + * \return One of the standard error codes for implementation-defined + * errors. + */ + int (*signal_message)(fwk_id_t service_id); +}; + +/*! + * \brief SCMI protocol message handler prototype. + * + * \details Prototype of a message handler called by the SCMI module when it + * receives a message for a SCMI protocol module. + * + * \note A return value of FWK_SUCCESS indicates only that no internal error + * was encountered, not that the SCMI command has returned a successful + * result to the SCMI agent. In the case where the return value indicates + * an internal failure, the SCMI command is expected to return the status + * code SCMI_GENERIC_ERROR per the specification. + * + * \param protocol_id Identifier of the protocol module. + * \param service_id Identifer of the SCMI service which received the message. + * \param payload Pointer to the message payload. + * \param payload_size Size in number of bytes of the message payload. + * \param message_id Identifier of the message to be handled by the protocol + * handler. + * + * \retval FWK_SUCCESS The operation succeeded. + * \return One of the standard error codes for implementation-defined errors. + * + */ +typedef int mod_scmi_message_handler_t(fwk_id_t protocol_id, + fwk_id_t service_id, const uint32_t *payload, size_t payload_size, + unsigned int message_id); + +/*! + * \brief SCMI module to SCMI protocol module API. + */ +struct mod_scmi_to_protocol_api { + /*! + * \brief Get the SCMI protocol identifier of the SCMI protocol implemented + * by the SCMI protocol module implementing this API. + * + * \param protocol_id Identifier of the protocol module. + * \param scmi_protocol_id SCMI protocol identifier. + * + * \retval FWK_SUCCESS The operation succeeded. + * \return One of the standard error codes for implementation-defined + * errors. + * + */ + int (*get_scmi_protocol_id)(fwk_id_t protocol_id, + uint8_t *scmi_protocol_id); + + /*! Protocol message handler. */ + mod_scmi_message_handler_t *message_handler; +}; + +/*! + * \brief SCMI protocol module to SCMI module API. + */ +struct mod_scmi_from_protocol_api { + /*! + * \brief Get the identifier of the agent associated with a service + * + * \param service_id Identifier of the service. + * \param agent_id Pointer to storage for the agent identifier. + * + * \retval FWK_SUCCESS The agent identifier was returned. + * \retval FWK_E_PARAM The service identifier is not valid. + * \retval FWK_E_PARAM The parameter 'agent_id' is equal to NULL. + * \retval FWK_E_STATE The service is not initialized. + */ + int (*get_agent_id)(fwk_id_t service_id, unsigned int *agent_id); + + /*! + * \brief Get the type of the agent given its identifier. + * + * \details This API can be used by SCMI protocols to check the validity + * of an agent identifier. + * + * \param agent_id Identifier of the agent. + * \param agent_type Pointer to storage for the agent type. + * + * \retval FWK_SUCCESS The agent identifier was returned. + * \retval FWK_E_PARAM The agent identifier is not valid. + * \retval FWK_E_PARAM The parameter 'agent_type' is equal to NULL. + * \retval FWK_E_STATE The component is not initialized. + */ + int (*get_agent_type)(uint32_t agent_id, + enum scmi_agent_type *agent_type); + + /*! + * \brief Get the maximum permitted payload size of a channel associated + * with a service. + * + * \param service_id Service identifier. + * \param size Pointer to storage for the maximum payload size in bytes. + * + * \retval FWK_SUCCESS The operation succeeded. + * \retval FWK_E_PARAM The service_id parameter is invalid. + * \retval FWK_E_PARAM The size parameter is NULL. + * \retval FWK_E_STATE The service is not initialized. + * \return One of the standard error codes for implementation-defined + * errors. + */ + int (*get_max_payload_size)(fwk_id_t service_id, size_t *size); + + /*! + * \brief Write part of a payload through a service. + * + * \param service_id Service identifier. + * \param offset Offset to begin writing at. + * \param payload Payload data to write. + * \param size Size of the payload data. + * + * \retval FWK_SUCCESS The operation succeeded. + * \retval FWK_E_PARAM The payload parameter is NULL. + * \retval FWK_E_PARAM The offset and size provided are not within the + * bounds of the payload area. + * \return One of the standard error codes for implementation-defined + * errors. + */ + int (*write_payload)(fwk_id_t service_id, size_t offset, + const void *payload, size_t size); + + /*! + * \brief Respond to an SCMI message on a service. + * + * \param service_id Service identifier. + * \param payload Payload data to write, or NULL if a payload has already + * been written. + * \param size Size of the payload. + */ + void (*respond)(fwk_id_t service_id, const void *payload, size_t size); +}; + + +/*! + * \brief Identify if an SCMI entity is the communications master for a given + * channel type. + * + * \param type Channel type. + * \param role Entity role. + * + * \retval true The entity is the communications master of the channel. + * \retval false The entity is the communications slave of the channel. + */ +static inline bool mod_scmi_is_master(enum scmi_channel_type type, + enum scmi_role role) +{ + return (int)type == (int)role; +} + +/*! + * \brief Identify if an SCMI entity is the communications slave for a given + * channel type. + * + * \param type Channel type. + * \param role Entity role. + * + * \retval true The entity is the communications slave of the channel. + * \retval false The entity is the communications master of the channel. + */ +static inline bool mod_scmi_is_slave(enum scmi_channel_type type, + enum scmi_role role) +{ + return (int)type != (int)role; +} + +/*! + * @} + */ + +/*! + * @} + */ + +#endif /* MOD_SCMI_H */ diff --git a/module/scmi/src/Makefile b/module/scmi/src/Makefile new file mode 100644 index 00000000..ae55ccc9 --- /dev/null +++ b/module/scmi/src/Makefile @@ -0,0 +1,11 @@ +# +# Arm SCP/MCP Software +# Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +BS_LIB_NAME := SCMI +BS_LIB_SOURCES := mod_scmi.c + +include $(BS_DIR)/lib.mk diff --git a/module/scmi/src/mod_scmi.c b/module/scmi/src/mod_scmi.c new file mode 100644 index 00000000..7a86d2e3 --- /dev/null +++ b/module/scmi/src/mod_scmi.c @@ -0,0 +1,794 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * System Control and Management Interface (SCMI) support. + */ + +#include <assert.h> +#include <stdbool.h> +#include <string.h> +#include <fwk_errno.h> +#include <fwk_id.h> +#include <fwk_macros.h> +#include <fwk_mm.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <fwk_multi_thread.h> +#include <internal/mod_scmi.h> +#include <internal/scmi.h> +#include <internal/scmi_base.h> +#include <mod_log.h> + +struct scmi_protocol { + /* SCMI protocol message handler */ + mod_scmi_message_handler_t *message_handler; + + /* SCMI protocol framework identifier */ + fwk_id_t id; +}; + +struct scmi_ctx { + /* SCMI module configuration data */ + struct mod_scmi_config *config; + + /* Table of bound protocols */ + struct scmi_protocol *protocol_table; + + /* Number of bound protocols */ + unsigned int protocol_count; + + /* + * SCMI protocol identifier to the index of the entry in protocol_table[] + * dedicated to the protocol. + */ + uint8_t scmi_protocol_id_to_idx[SCMI_PROTOCOL_ID_MAX + 1]; + + /* Table of service contexts */ + struct scmi_service_ctx *service_ctx_table; + + /* Log module API */ + struct mod_log_api *log_api; +}; + +/* + * Entry zero (0) of the protocol table 'protocol_table' is not used, as index + * 0 is the index of the unused entries of the 'scmi_protocol_id_to_idx[]' + * table. Entry one (1) is reserved for the base protocol implemented in this + * file. + */ +#define PROTOCOL_TABLE_BASE_PROTOCOL_IDX 1 +#define PROTOCOL_TABLE_RESERVED_ENTRIES_COUNT 2 + +static int scmi_base_protocol_version_handler( + fwk_id_t service_id, const uint32_t *payload); +static int scmi_base_protocol_attributes_handler( + fwk_id_t service_id, const uint32_t *payload); +static int scmi_base_protocol_message_attributes_handler( + fwk_id_t service_id, const uint32_t *payload); +static int scmi_base_discover_vendor_handler( + fwk_id_t service_id, const uint32_t *payload); +static int scmi_base_discover_sub_vendor_handler( + fwk_id_t service_id, const uint32_t *payload); +static int scmi_base_discover_implementation_version_handler( + fwk_id_t service_id, const uint32_t *payload); +static int scmi_base_discover_list_protocols_handler( + fwk_id_t service_id, const uint32_t *payload); +static int scmi_base_discover_agent_handler( + fwk_id_t service_id, const uint32_t *payload); + +static int (* const base_handler_table[])(fwk_id_t, const uint32_t *) = { + [SCMI_PROTOCOL_VERSION] = + scmi_base_protocol_version_handler, + [SCMI_PROTOCOL_ATTRIBUTES] = + scmi_base_protocol_attributes_handler, + [SCMI_PROTOCOL_MESSAGE_ATTRIBUTES] = + scmi_base_protocol_message_attributes_handler, + [SCMI_BASE_DISCOVER_VENDOR] = + scmi_base_discover_vendor_handler, + [SCMI_BASE_DISCOVER_SUB_VENDOR] = + scmi_base_discover_sub_vendor_handler, + [SCMI_BASE_DISCOVER_IMPLEMENTATION_VERSION] = + scmi_base_discover_implementation_version_handler, + [SCMI_BASE_DISCOVER_LIST_PROTOCOLS] = + scmi_base_discover_list_protocols_handler, + [SCMI_BASE_DISCOVER_AGENT] = + scmi_base_discover_agent_handler, +}; + +static const unsigned int base_payload_size_table[] = { + [SCMI_PROTOCOL_VERSION] = 0, + [SCMI_PROTOCOL_ATTRIBUTES] = 0, + [SCMI_PROTOCOL_MESSAGE_ATTRIBUTES] = + sizeof(struct scmi_protocol_message_attributes_a2p), + [SCMI_BASE_DISCOVER_VENDOR] = 0, + [SCMI_BASE_DISCOVER_SUB_VENDOR] = 0, + [SCMI_BASE_DISCOVER_IMPLEMENTATION_VERSION] = 0, + [SCMI_BASE_DISCOVER_LIST_PROTOCOLS] = + sizeof(struct scmi_base_discover_list_protocols_a2p), + [SCMI_BASE_DISCOVER_AGENT] = + sizeof(struct scmi_base_discover_agent_a2p), +}; + +static const char * const default_agent_names[] = { + [SCMI_AGENT_TYPE_PSCI] = "PSCI", + [SCMI_AGENT_TYPE_MANAGEMENT] = "MANAGEMENT", + [SCMI_AGENT_TYPE_OSPM] = "OSPM", + [SCMI_AGENT_TYPE_OTHER] = "OTHER", +}; + +static struct scmi_ctx scmi_ctx; + +/* + * Utility functions + */ +static uint16_t read_message_id(uint32_t message_header) +{ + return (message_header & SCMI_MESSAGE_HEADER_MESSAGE_ID_MASK) >> + SCMI_MESSAGE_HEADER_MESSAGE_ID_POS; +} + +static uint8_t read_protocol_id(uint32_t message_header) +{ + return (message_header & SCMI_MESSAGE_HEADER_PROTOCOL_ID_MASK) >> + SCMI_MESSAGE_HEADER_PROTOCOL_ID_POS; +} + +/* + * Transport entity -> SCMI module + */ + +static int signal_message(fwk_id_t service_id) +{ + int32_t status; + struct fwk_event event = { }; + + status = fwk_module_check_call(service_id); + if (status != FWK_SUCCESS) + return status; + + event.source_id = FWK_ID_MODULE(FWK_MODULE_IDX_SCMI); + event.target_id = service_id; + event.id = FWK_ID_EVENT(FWK_MODULE_IDX_SCMI, 0); + + return fwk_thread_put_event(&event); +} + +static const struct mod_scmi_from_transport_api mod_scmi_from_transport_api = { + .signal_message = signal_message +}; + +/* + * SCMI protocol module -> SCMI module interface + */ + +static int get_agent_id(fwk_id_t service_id, unsigned int *agent_id) +{ + int status; + struct scmi_service_ctx *ctx; + + status = fwk_module_check_call(service_id); + if (status != FWK_SUCCESS) + return status; + + if (agent_id == NULL) + return FWK_E_PARAM; + + ctx = &scmi_ctx.service_ctx_table[fwk_id_get_element_idx(service_id)]; + + *agent_id = ctx->config->scmi_agent_id; + + return FWK_SUCCESS; +} + +static int get_agent_type(uint32_t scmi_agent_id, + enum scmi_agent_type *agent_type) +{ + if ((agent_type == NULL) || + (scmi_agent_id > scmi_ctx.config->agent_count) || + (scmi_agent_id == SCMI_PLATFORM_ID)) + return FWK_E_PARAM; + + *agent_type = scmi_ctx.config->agent_table[scmi_agent_id].type; + + return FWK_SUCCESS; +} + +static int get_max_payload_size(fwk_id_t service_id, size_t *size) +{ + int status; + struct scmi_service_ctx *ctx; + + status = fwk_module_check_call(service_id); + if (status != FWK_SUCCESS) + return status; + + if (size == NULL) + return FWK_E_PARAM; + + ctx = &scmi_ctx.service_ctx_table[fwk_id_get_element_idx(service_id)]; + + return ctx->transport_api->get_max_payload_size(ctx->transport_id, size); +} + +static int write_payload(fwk_id_t service_id, size_t offset, + const void *payload, size_t size) +{ + int status; + const struct scmi_service_ctx *ctx; + + status = fwk_module_check_call(service_id); + if (status != FWK_SUCCESS) + return status; + + ctx = &scmi_ctx.service_ctx_table[fwk_id_get_element_idx(service_id)]; + + return ctx->transport_api->write_payload(ctx->transport_id, + offset, payload, size); +} + +static void respond(fwk_id_t service_id, const void *payload, size_t size) +{ + int status; + const struct scmi_service_ctx *ctx; + + status = fwk_module_check_call(service_id); + if (status != FWK_SUCCESS) + return; + + ctx = &scmi_ctx.service_ctx_table[fwk_id_get_element_idx(service_id)]; + + /* + * Print to the error log if the message was not successfully processed. + * We assume here that the first payload entry of the command response + * holds an SCMI status code. This is the case for all the SCMI commands + * specified so far, but it is not explicitly stated (yet) in the + * specification it should be like that for all commands. + */ + if ((payload != NULL) && (*((int32_t *)payload) < SCMI_SUCCESS)) { + scmi_ctx.log_api->log(MOD_LOG_GROUP_ERROR, + "[SCMI] Protocol 0x%x, message_id 0x%x returned with error %d\n", + ctx->scmi_protocol_id, ctx->scmi_message_id, *((int *)payload)); + } + + status = ctx->respond(ctx->transport_id, payload, size); + if (status != FWK_SUCCESS) + scmi_ctx.log_api->log(MOD_LOG_GROUP_ERROR, + "[SCMI] Failed to send response (%e)\n", status); +} + +static const struct mod_scmi_from_protocol_api mod_scmi_from_protocol_api = { + .get_agent_id = get_agent_id, + .get_agent_type = get_agent_type, + .get_max_payload_size = get_max_payload_size, + .write_payload = write_payload, + .respond = respond, +}; + +/* + * Base protocol implementation + */ +/* + * Base Protocol - PROTOCOL_VERSION + */ +static int scmi_base_protocol_version_handler(fwk_id_t service_id, + const uint32_t *payload) +{ + struct scmi_protocol_version_p2a return_values = { + .status = SCMI_SUCCESS, + .version = SCMI_PROTOCOL_VERSION_BASE, + }; + + respond(service_id, &return_values, sizeof(return_values)); + + return FWK_SUCCESS; +} + +/* + * Base Protocol - PROTOCOL_ATTRIBUTES + */ +static int scmi_base_protocol_attributes_handler(fwk_id_t service_id, + const uint32_t *payload) +{ + struct scmi_protocol_attributes_p2a return_values = { + .status = SCMI_SUCCESS, + }; + + return_values.attributes = + SCMI_BASE_PROTOCOL_ATTRIBUTES(scmi_ctx.protocol_count, + scmi_ctx.config->agent_count); + + respond(service_id, &return_values, sizeof(return_values)); + + return FWK_SUCCESS; +} + +/* + * Base Protocol - PROTOCOL_MESSAGE_ATTRIBUTES + */ +static int scmi_base_protocol_message_attributes_handler(fwk_id_t service_id, + const uint32_t *payload) +{ + struct scmi_protocol_message_attributes_a2p *parameters; + unsigned int message_id; + struct scmi_protocol_message_attributes_p2a return_values = { + .status = SCMI_NOT_FOUND, + }; + + parameters = (struct scmi_protocol_message_attributes_a2p *)payload; + message_id = parameters->message_id; + + if ((message_id < FWK_ARRAY_SIZE(base_handler_table)) && + (base_handler_table[message_id] != NULL)) + return_values.status = SCMI_SUCCESS; + + /* For this protocol, all commands have an attributes value of 0, which + * has already been set by the initialization of "return_values". + */ + + respond(service_id, &return_values, + (return_values.status == SCMI_SUCCESS) ? + sizeof(return_values) : sizeof(return_values.status)); + + return FWK_SUCCESS; +} + +/* + * Base Protocol - BASE_DISCOVER_VENDOR + */ +static int scmi_base_discover_vendor_handler(fwk_id_t service_id, + const uint32_t *payload) +{ + struct scmi_base_discover_vendor_p2a return_values = { + .status = SCMI_SUCCESS, + }; + + if (scmi_ctx.config->vendor_identifier != NULL) + strncpy(return_values.vendor_identifier, + scmi_ctx.config->vendor_identifier, + sizeof(return_values.vendor_identifier) - 1); + + respond(service_id, &return_values, sizeof(return_values)); + + return FWK_SUCCESS; +} + +/* + * BASE_DISCOVER_SUB_VENDOR + */ +static int scmi_base_discover_sub_vendor_handler(fwk_id_t service_id, + const uint32_t *payload) +{ + struct scmi_base_discover_sub_vendor_p2a return_values = { + .status = SCMI_SUCCESS, + }; + + if (scmi_ctx.config->sub_vendor_identifier != NULL) + strncpy(return_values.sub_vendor_identifier, + scmi_ctx.config->sub_vendor_identifier, + sizeof(return_values.sub_vendor_identifier) - 1); + + respond(service_id, &return_values, sizeof(return_values)); + + return FWK_SUCCESS; +} + +/* + * BASE_DISCOVER_IMPLEMENTATION_VERSION + */ +static int scmi_base_discover_implementation_version_handler( + fwk_id_t service_id, const uint32_t *payload) +{ + struct scmi_protocol_version_p2a return_values = { + .status = SCMI_SUCCESS, + .version = FWK_BUILD_VERSION + }; + + respond(service_id, &return_values, sizeof(return_values)); + + return FWK_SUCCESS; +} + +/* + * BASE_DISCOVER_LIST_PROTOCOLS + */ +static int scmi_base_discover_list_protocols_handler(fwk_id_t service_id, + const uint32_t *payload) +{ + int status; + const struct scmi_base_discover_list_protocols_a2p *parameters; + struct scmi_base_discover_list_protocols_p2a return_values = { + .status = SCMI_GENERIC_ERROR, + .num_protocols = 0, + }; + unsigned int skip; + size_t max_payload_size; + size_t payload_size; + size_t entry_count; + size_t protocol_count, protocol_count_max; + unsigned int index; + uint8_t protocol_id; + + status = get_max_payload_size(service_id, &max_payload_size); + if (status != FWK_SUCCESS) + goto error; + + if (max_payload_size < + (sizeof(struct scmi_base_discover_list_protocols_p2a) + + sizeof(return_values.protocols[0]))) { + status = FWK_E_SIZE; + goto error; + } + + entry_count = max_payload_size - + sizeof(struct scmi_base_discover_list_protocols_p2a); + + parameters = (const struct scmi_base_discover_list_protocols_a2p *)payload; + skip = parameters->skip; + + if (skip > scmi_ctx.protocol_count) { + return_values.status = SCMI_INVALID_PARAMETERS; + goto error; + } + + protocol_count_max = (scmi_ctx.protocol_count < (skip + entry_count)) ? + scmi_ctx.protocol_count : (skip + entry_count); + + for (index = 0, protocol_count = 0, + payload_size = sizeof(struct scmi_base_discover_list_protocols_p2a); + (index < FWK_ARRAY_SIZE(scmi_ctx.scmi_protocol_id_to_idx)) && + (protocol_count < protocol_count_max); + index++) { + if ((scmi_ctx.scmi_protocol_id_to_idx[index] == 0) || + (index == SCMI_PROTOCOL_ID_BASE)) + continue; + + protocol_count++; + if (protocol_count <= skip) + continue; + + protocol_id = index; + status = write_payload(service_id, payload_size, &protocol_id, + sizeof(protocol_id)); + if (status != FWK_SUCCESS) + goto error; + payload_size += sizeof(protocol_id); + } + + return_values.status = SCMI_SUCCESS; + return_values.num_protocols = protocol_count_max - skip; + + status = write_payload(service_id, 0, + &return_values, sizeof(return_values)); + if (status != FWK_SUCCESS) + goto error; + + payload_size = FWK_ALIGN_NEXT(payload_size, sizeof(uint32_t)); + + respond(service_id, NULL, payload_size); + + return status; + +error: + respond(service_id, &return_values, + (return_values.status == SCMI_SUCCESS) ? + sizeof(return_values) : sizeof(return_values.status)); + + return status; +} + +/* + * BASE_DISCOVER_AGENT + */ +static int scmi_base_discover_agent_handler(fwk_id_t service_id, + const uint32_t *payload) +{ + const struct scmi_base_discover_agent_a2p *parameters; + struct scmi_base_discover_agent_p2a return_values = { + .status = SCMI_NOT_FOUND, + }; + const struct mod_scmi_agent *agent; + + parameters = (const struct scmi_base_discover_agent_a2p *)payload; + + if (parameters->agent_id > scmi_ctx.config->agent_count) + goto exit; + + return_values.status = SCMI_SUCCESS; + + if (parameters->agent_id == SCMI_PLATFORM_ID) { + strcpy(return_values.name, "platform"); + goto exit; + } + + agent = &scmi_ctx.config->agent_table[parameters->agent_id]; + + strncpy(return_values.name, + (agent->name != NULL) ? agent->name : + default_agent_names[agent->type], + sizeof(return_values.name) - 1); + +exit: + respond(service_id, &return_values, + (return_values.status == SCMI_SUCCESS) ? + sizeof(return_values) : sizeof(return_values.status)); + + return FWK_SUCCESS; +} + +static int scmi_base_message_handler(fwk_id_t protocol_id, fwk_id_t service_id, + const uint32_t *payload, size_t payload_size, unsigned int message_id) +{ + int32_t return_value; + + static_assert(FWK_ARRAY_SIZE(base_handler_table) == + FWK_ARRAY_SIZE(base_payload_size_table), + "[SCMI] Base protocol table sizes not consistent"); + assert(payload != NULL); + + if (message_id >= FWK_ARRAY_SIZE(base_handler_table)) { + return_value = SCMI_NOT_SUPPORTED; + goto error; + } + + if (payload_size != base_payload_size_table[message_id]) { + return_value = SCMI_PROTOCOL_ERROR; + goto error; + } + + return base_handler_table[message_id](service_id, payload); + +error: + respond(service_id, &return_value, sizeof(return_value)); + + return FWK_SUCCESS; +} + +/* + * Framework handlers + */ + +static int scmi_init(fwk_id_t module_id, unsigned int service_count, + const void *data) +{ + struct mod_scmi_config *config = (struct mod_scmi_config *)data; + unsigned int agent_idx; + const struct mod_scmi_agent *agent; + + if (config == NULL) + return FWK_E_PARAM; + + if ((config->agent_count == 0) || + (config->agent_count > SCMI_AGENT_ID_MAX)) + return FWK_E_PARAM; + + /* + * Loop over the agent descriptors. The SCMI_PLATFORM_ID(0) entry of + * the table - that would refer to the platform - is ignored. + */ + for (agent_idx = SCMI_PLATFORM_ID + 1; + agent_idx <= config->agent_count; agent_idx++) { + agent = &config->agent_table[agent_idx]; + if (agent->type >= SCMI_AGENT_TYPE_COUNT) + return FWK_E_PARAM; + } + + scmi_ctx.protocol_table = fwk_mm_calloc( + config->protocol_count_max + PROTOCOL_TABLE_RESERVED_ENTRIES_COUNT, + sizeof(scmi_ctx.protocol_table[0])); + if (scmi_ctx.protocol_table == NULL) + return FWK_E_NOMEM; + + scmi_ctx.service_ctx_table = fwk_mm_calloc( + service_count, sizeof(scmi_ctx.service_ctx_table[0])); + if (scmi_ctx.service_ctx_table == NULL) + return FWK_E_NOMEM; + + scmi_ctx.protocol_table[PROTOCOL_TABLE_BASE_PROTOCOL_IDX].message_handler = + scmi_base_message_handler; + scmi_ctx.scmi_protocol_id_to_idx[SCMI_PROTOCOL_ID_BASE] = + PROTOCOL_TABLE_BASE_PROTOCOL_IDX; + + scmi_ctx.config = config; + + return FWK_SUCCESS; +} + +static int scmi_service_init(fwk_id_t service_id, unsigned int unused, + const void *data) +{ + const struct mod_scmi_service_config *config = + (struct mod_scmi_service_config *)data; + struct scmi_service_ctx *ctx; + + if ((config->scmi_agent_id == SCMI_PLATFORM_ID) || + (config->scmi_agent_id > scmi_ctx.config->agent_count)) + return FWK_E_PARAM; + + ctx = &scmi_ctx.service_ctx_table[fwk_id_get_element_idx(service_id)]; + ctx->config = config; + + return fwk_thread_create(service_id); +} + +static int scmi_bind(fwk_id_t id, unsigned int round) +{ + int status; + struct scmi_service_ctx *ctx; + const struct mod_scmi_to_transport_api *transport_api; + unsigned int protocol_idx; + struct scmi_protocol *protocol; + struct mod_scmi_to_protocol_api *protocol_api; + uint8_t scmi_protocol_id; + + if (round == 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), + &scmi_ctx.log_api); + } + + ctx = &scmi_ctx.service_ctx_table[fwk_id_get_element_idx(id)]; + status = fwk_module_bind(ctx->config->transport_id, + ctx->config->transport_api_id, &transport_api); + if (status != FWK_SUCCESS) + return status; + + if ((transport_api->get_secure == NULL) || + (transport_api->get_max_payload_size == NULL) || + (transport_api->get_message_header == NULL) || + (transport_api->get_payload == NULL) || + (transport_api->write_payload == NULL) || + (transport_api->respond == NULL)) + return FWK_E_DATA; + + ctx->transport_api = transport_api; + ctx->transport_id = ctx->config->transport_id; + ctx->respond = transport_api->respond; + + return FWK_SUCCESS; + } + + if (fwk_id_is_type(id, FWK_ID_TYPE_ELEMENT)) + return FWK_SUCCESS; + + for (protocol_idx = 0; + protocol_idx < scmi_ctx.protocol_count; protocol_idx++) { + protocol = &scmi_ctx.protocol_table[ + PROTOCOL_TABLE_RESERVED_ENTRIES_COUNT + protocol_idx]; + + status = fwk_module_bind(protocol->id, + FWK_ID_API(fwk_id_get_module_idx(protocol->id), 0), &protocol_api); + if (status != FWK_SUCCESS) + return status; + + if ((protocol_api->get_scmi_protocol_id == NULL) || + (protocol_api->message_handler == NULL)) + return FWK_E_DATA; + status = protocol_api->get_scmi_protocol_id(protocol->id, + &scmi_protocol_id); + if (status != FWK_SUCCESS) + return status; + + if (scmi_ctx.scmi_protocol_id_to_idx[scmi_protocol_id] != 0) + return FWK_E_STATE; + + scmi_ctx.scmi_protocol_id_to_idx[scmi_protocol_id] = + protocol_idx + PROTOCOL_TABLE_RESERVED_ENTRIES_COUNT; + protocol->message_handler = protocol_api->message_handler; + } + + return FWK_SUCCESS; +} + +static int scmi_process_bind_request(fwk_id_t source_id, fwk_id_t target_id, + fwk_id_t api_id, const void **api) +{ + unsigned int api_idx; + struct scmi_service_ctx *ctx; + + api_idx = fwk_id_get_api_idx(api_id); + + switch (api_idx) { + case MOD_SCMI_API_IDX_PROTOCOL: + if (!fwk_id_is_type(target_id, FWK_ID_TYPE_MODULE)) + return FWK_E_SUPPORT; + + if (scmi_ctx.protocol_count >= scmi_ctx.config->protocol_count_max) + return FWK_E_NOMEM; + + scmi_ctx.protocol_table[PROTOCOL_TABLE_RESERVED_ENTRIES_COUNT + + scmi_ctx.protocol_count++].id = source_id; + *api = &mod_scmi_from_protocol_api; + break; + + case MOD_SCMI_API_IDX_TRANSPORT: + if (!fwk_id_is_type(target_id, FWK_ID_TYPE_ELEMENT)) + return FWK_E_SUPPORT; + + ctx = &scmi_ctx.service_ctx_table[fwk_id_get_element_idx(target_id)]; + if (!fwk_id_is_equal(source_id, ctx->transport_id)) + return FWK_E_ACCESS; + + *api = &mod_scmi_from_transport_api; + break; + + default: + return FWK_E_SUPPORT; + }; + + return FWK_SUCCESS; +} + +static int scmi_process_event(const struct fwk_event *event, + struct fwk_event *resp) +{ + int status; + struct scmi_service_ctx *ctx; + const struct mod_scmi_to_transport_api *transport_api; + fwk_id_t transport_id; + uint32_t message_header; + const void *payload; + size_t payload_size; + unsigned int protocol_idx; + struct scmi_protocol *protocol; + + ctx = &scmi_ctx.service_ctx_table[fwk_id_get_element_idx(event->target_id)]; + transport_api = ctx->transport_api; + transport_id = ctx->transport_id; + + status = transport_api->get_message_header(transport_id, &message_header); + if (status != FWK_SUCCESS) { + scmi_ctx.log_api->log(MOD_LOG_GROUP_ERROR, + "[SCMI] Unable to read message header\n"); + return status; + } + + status = transport_api->get_payload(transport_id, &payload, &payload_size); + if (status != FWK_SUCCESS) { + scmi_ctx.log_api->log(MOD_LOG_GROUP_ERROR, + "[SCMI] Unable to read message payload\n"); + return status; + } + + ctx->scmi_protocol_id = read_protocol_id(message_header); + ctx->scmi_message_id = read_message_id(message_header); + + protocol_idx = scmi_ctx.scmi_protocol_id_to_idx[ctx->scmi_protocol_id]; + + if (protocol_idx == 0) { + scmi_ctx.log_api->log(MOD_LOG_GROUP_ERROR, + "[SCMI] Protocol 0x%x not supported\n", ctx->scmi_protocol_id); + ctx->respond(transport_id, &(int32_t) { SCMI_NOT_SUPPORTED }, + sizeof(int32_t)); + return FWK_SUCCESS; + } + + protocol = &scmi_ctx.protocol_table[protocol_idx]; + status = protocol->message_handler(protocol->id, event->target_id, + payload, payload_size, ctx->scmi_message_id); + + if (status != FWK_SUCCESS) { + scmi_ctx.log_api->log(MOD_LOG_GROUP_ERROR, + "[SCMI] Protocol 0x%x handler error (%e), message_id = 0x%x\n", + ctx->scmi_protocol_id, status, ctx->scmi_message_id); + } + + return FWK_SUCCESS; +} + +/* SCMI module definition */ +const struct fwk_module module_scmi = { + .name = "SCMI", + .api_count = MOD_SCMI_API_IDX_COUNT, + .event_count = 1, + .type = FWK_MODULE_TYPE_SERVICE, + .init = scmi_init, + .element_init = scmi_service_init, + .bind = scmi_bind, + .process_bind_request = scmi_process_bind_request, + .process_event = scmi_process_event +}; diff --git a/module/scmi_apcore/doc/scmi_apcore.md b/module/scmi_apcore/doc/scmi_apcore.md new file mode 100644 index 00000000..9af53a93 --- /dev/null +++ b/module/scmi_apcore/doc/scmi_apcore.md @@ -0,0 +1,153 @@ +SCMI AP Core Configuration Protocol v1.0 +======================================== + +Protocol Overview {#scmi_apcore_protocol_overview} +================= + +This protocol is an extension of the [Arm System Control and Management +Interface (SCMI)] +(http://infocenter.arm.com/help/topic/com.arm.doc.den0056a/index.html). + +The goal of this protocol is for the SCP to provide an interface to the AP +firmware that supports changing the configuration of one or more AP cores. +For example, one of the supported commands allows programming of the AP core +address and is used when AP firmware is unable to program the reset address +directly. + +The protocol identifier used for this protocol (0x90) is within the range that +the SCMI specification provides for platform-specific extensions (0x80 - 0xFF). +For further information on protocol identifiers refer to section 4.1.2 of the +SCMI specification. + +Protocol Commands {#scmi_apcore_protocol} +================= + +Protocol Version {#scmi_apcore_protocol_version} +---------------- + +On success, this command returns the version of the protocol. For this version +of the specification the return value must be 0x10000, which corresponds to 1.0. + +message_id: 0x0<br> +protocol_id: 0x90 + +This command is mandatory. + +Return values: +* int32 status + * See section 4.1.4 of the SCMI specification for status code + definitions +* uint32 version + * For this version of the specification the return value must be 0x10000 + +Protocol Attributes {#scmi_apcore_protocol_attributes} +------------------- + +This command returns the implementation details associated with this protocol. + +message_id: 0x1<br> +protocol_id: 0x90 + +This command is mandatory. + +Return values: +* int32 status + * See section 4.1.4 of the SCMI specification for status code + definitions +* uint32 attributes + * Bits [31:1] Reserved, must be zero. + * Bit [0] If set to 1, the platform supports 64-bit reset addresses. If set + to 0, the platform supports 32-bit reset addresses. + +Protocol Message Attributes {#scmi_apcore_protocol_message__attributes} +--------------------------- + +On success, this command returns the implementation details associated with a +specific message in this protocol. In addition to the standard status codes +described in section 4.1.4 of the SCMI specification, the command can return the +error NOT_FOUND if the message identified by message_id is not provided by +the implementation. + +message_id: 0x2<br> +protocol_id: 0x90 + +This command is mandatory. + +Parameters: +* uint32 message_id + * message_id of the message. + +Return values: +* int32 status + * See section 4.1.4 of the SCMI specification for status code + definitions. +* uint32 attributes + * Flags associated with a specific command in the protocol. For all commands + in this protocol this parameter has a value of 0. + +Core Reset Address Set {#scmi_apcore_protocol_set_address} +---------------------- + +Set the application core reset address. The address applies to all cores. + +In some platforms only the SCP is capable of programming the application core +reset address. This command allows the SCP to carry out the programming on +behalf of AP firmware. Such a feature is supported in the [Arm Trusted Firmware] +(https://github.com/ARM-software/arm-trusted-firmware/blob/master/docs/reset-design.rst) + +message_id: 0x3<br> +protocol_id: 0x90 + +This command is optional. + +Parameters: +* uint32 Reset address (lower word) + * Bit[31:0] AP core reset address (low) +* uint32 Reset address (higher word) + * Bit[31:0] AP core reset address (high) + * On platforms that support only 32-bit addresses, only the lower word is + used - this higher word must be zero and the address must be 4-byte + aligned. For platforms supporting 64-bit addresses both words may be used + and the address must be 8-byte aligned. +* uint32 attributes + * Bit[31:1] Reserved, must be zero. + * Bit[0] Lock. When set to 1, the platform will deny any further attempts to + change the reset address. + +Return values: +* int32 status + * SUCCESS if the reset address was set successfully. + * INVALID_PARAMETERS: + * Reset address alignment is invalid. + * Platform supports only 32-bit addresses and the reset address received + is larger than 32-bits. + * DENIED: The reset address is locked and changes are not permitted. + * DENIED: The calling agent is not permitted to modify the reset address. + * See section 4.1.4 of the SCMI specification for status code + definitions. + +Core Reset Address Get {#scmi_apcore_protocol_get_address} +---------------------- + +Get the application core reset address. The address applies to all cores. + +message_id: 0x4<br> +protocol_id: 0x90 + +This command is optional. + +Return values: +* int32 status + * SUCCESS if the reset address was retrieved successfully. + * DENIED: The calling agent is not permitted to retrieve the reset address. + * See section 4.1.4 of the SCMI specification for status code + definitions. +* uint32 Reset address (lower word) + * Bit[31:0] AP core reset address (low) +* uint32 Reset address (higher word) + * Bit[31:0] AP core reset address (high) + * On platforms that support only 32-bit addresses, only the lower word is + used and this higher word must be zero. +* uint32 attributes + * Bit[31:1] Reserved, must be zero. + * Bit[0] Lock. When set to 1, changing the reset address is not permitted. diff --git a/module/scmi_apcore/include/internal/scmi_apcore.h b/module/scmi_apcore/include/internal/scmi_apcore.h new file mode 100644 index 00000000..d55e676b --- /dev/null +++ b/module/scmi_apcore/include/internal/scmi_apcore.h @@ -0,0 +1,71 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * SCMI Core Configuration Protocol Support + */ + +#ifndef SCMI_APCORE_H +#define SCMI_APCORE_H + +#include <stdint.h> + +#define SCMI_PROTOCOL_ID_APCORE UINT32_C(0x90) +#define SCMI_PROTOCOL_VERSION_APCORE UINT32_C(0x10000) + +/* + * Identifiers of the SCMI Core Configuration Protocol commands + */ +enum scmi_apcore_command_id { + SCMI_APCORE_RESET_ADDRESS_SET = 0x3, + SCMI_APCORE_RESET_ADDRESS_GET = 0x4, +}; + +/* + * Protocol Attributes + */ + +#define SCMI_APCORE_PROTOCOL_ATTRIBUTES_64BIT_POS 0 + +#define SCMI_APCORE_PROTOCOL_ATTRIBUTES_64BIT_MASK \ + (UINT32_C(0x1) << SCMI_APCORE_PROTOCOL_ATTRIBUTES_64BIT_POS) + +/* + * Reset Address Set + */ + +#define SCMI_APCORE_RESET_ADDRESS_SET_LOCK_POS 0 + +#define SCMI_APCORE_RESET_ADDRESS_SET_LOCK_MASK \ + (UINT32_C(0x1) << SCMI_APCORE_RESET_ADDRESS_SET_LOCK_POS) + +struct __attribute((packed)) scmi_apcore_reset_address_set_a2p { + uint32_t reset_address_low; + uint32_t reset_address_high; + uint32_t attributes; +}; + +struct __attribute((packed)) scmi_apcore_reset_address_set_p2a { + int32_t status; +}; + +/* + * Reset Address Get + */ + +#define SCMI_APCORE_RESET_ADDRESS_GET_LOCK_POS 0 + +#define SCMI_APCORE_RESET_ADDRESS_GET_LOCK_MASK \ + (UINT32_C(0x1) << SCMI_APCORE_RESET_ADDRESS_GET_LOCK_POS) + +struct __attribute((packed)) scmi_apcore_reset_address_get_p2a { + int32_t status; + uint32_t reset_address_low; + uint32_t reset_address_high; + uint32_t attributes; +}; + +#endif /* SCMI_APCORE_H */ diff --git a/module/scmi_apcore/include/mod_scmi_apcore.h b/module/scmi_apcore/include/mod_scmi_apcore.h new file mode 100644 index 00000000..82fe61d6 --- /dev/null +++ b/module/scmi_apcore/include/mod_scmi_apcore.h @@ -0,0 +1,75 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * SCMI Core Configuration Protocol Support. + */ + +#ifndef MOD_SCMI_APCORE_H +#define MOD_SCMI_APCORE_H + +#include <stdint.h> +#include <stddef.h> + +/*! + * \ingroup GroupModules Modules + * \defgroup GroupSCMI_APCORE SCMI Core Configuration Protocol + * \{ + */ + +/*! + * \brief Platform reset register widths. + */ +enum mod_scmi_apcore_register_width { + /*! Single-word, 32-bit reset address registers supported */ + MOD_SCMI_APCORE_REG_WIDTH_32, + + /*! Double-word, 64-bit reset address registers supported */ + MOD_SCMI_APCORE_REG_WIDTH_64, + + /*! Number of valid register widths */ + MOD_SCMI_APCORE_REG_WIDTH_COUNT, +}; + +/*! + * \brief Reset register group. + * + * \details Describes a set of reset registers that are contiguous in memory. + */ +struct mod_scmi_apcore_reset_register_group { + /*! Address of the first register in the group */ + uintptr_t base_register; + + /*! The number of registers in the group */ + size_t register_count; +}; + +/*! + * \brief Module configuration. + */ +struct mod_scmi_apcore_config { + /*! + * \brief Pointer to the table of \ref mod_scmi_apcore_reset_register_group + * structures that define the reset registers within the platform. + */ + const struct mod_scmi_apcore_reset_register_group + *reset_register_group_table; + + /*! + * \brief Number of \ref mod_scmi_apcore_reset_register_group structures in + * \ref reset_register_group_table. + */ + size_t reset_register_group_count; + + /*! Width of the reset address supported by the platform */ + enum mod_scmi_apcore_register_width reset_register_width; +}; + +/*! + * \} + */ + +#endif /* MOD_SCMI_APCORE_H */ diff --git a/module/scmi_apcore/src/Makefile b/module/scmi_apcore/src/Makefile new file mode 100644 index 00000000..c5cf7079 --- /dev/null +++ b/module/scmi_apcore/src/Makefile @@ -0,0 +1,11 @@ +# +# Arm SCP/MCP Software +# Copyright (c) 2018, Arm Limited and Contributors. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +BS_LIB_NAME := SCMI Core Configuration Protocol +BS_LIB_SOURCES := mod_scmi_apcore.c + +include $(BS_DIR)/lib.mk diff --git a/module/scmi_apcore/src/mod_scmi_apcore.c b/module/scmi_apcore/src/mod_scmi_apcore.c new file mode 100644 index 00000000..afedf8fc --- /dev/null +++ b/module/scmi_apcore/src/mod_scmi_apcore.c @@ -0,0 +1,432 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * SCMI Core Configuration Protocol Support. + */ + +#include <assert.h> +#include <stdint.h> +#include <fwk_errno.h> +#include <fwk_id.h> +#include <fwk_macros.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <internal/scmi.h> +#include <internal/scmi_apcore.h> +#include <mod_scmi.h> +#include <mod_scmi_apcore.h> + +struct scmi_apcore_ctx { + /* Module Configuration */ + const struct mod_scmi_apcore_config *config; + + /* SCMI module API */ + const struct mod_scmi_from_protocol_api *scmi_api; + + /* + * Tracks whether an agent has requested that the configuration be locked. + * \c true if the configuration is locked and the reset address of the CPUs + * can no longer be altered, \c false otherwise. + */ + bool locked; +}; + +static int scmi_apcore_protocol_version_handler(fwk_id_t service_id, + const uint32_t *payload); +static int scmi_apcore_protocol_attributes_handler(fwk_id_t service_id, + const uint32_t *payload); +static int scmi_apcore_protocol_message_attributes_handler( + fwk_id_t service_id, const uint32_t *payload); +static int scmi_apcore_reset_address_set_handler(fwk_id_t service_id, + const uint32_t *payload); +static int scmi_apcore_reset_address_get_handler(fwk_id_t service_id, + const uint32_t *payload); + +/* + * Internal variables. + */ +static struct scmi_apcore_ctx scmi_apcore_ctx; + +static int (* const handler_table[])(fwk_id_t, const uint32_t *) = { + [SCMI_PROTOCOL_VERSION] = scmi_apcore_protocol_version_handler, + [SCMI_PROTOCOL_ATTRIBUTES] = scmi_apcore_protocol_attributes_handler, + [SCMI_PROTOCOL_MESSAGE_ATTRIBUTES] = + scmi_apcore_protocol_message_attributes_handler, + [SCMI_APCORE_RESET_ADDRESS_SET] = scmi_apcore_reset_address_set_handler, + [SCMI_APCORE_RESET_ADDRESS_GET] = scmi_apcore_reset_address_get_handler, +}; + +static const unsigned int payload_size_table[] = { + [SCMI_PROTOCOL_VERSION] = 0, + [SCMI_PROTOCOL_ATTRIBUTES] = 0, + [SCMI_PROTOCOL_MESSAGE_ATTRIBUTES] = + sizeof(struct scmi_protocol_message_attributes_a2p), + [SCMI_APCORE_RESET_ADDRESS_SET] = + sizeof(struct scmi_apcore_reset_address_set_a2p), + [SCMI_APCORE_RESET_ADDRESS_GET] = 0, +}; + +/* + * Static, Helper Functions + */ +static int set_reset_address(uint32_t address_low, uint32_t address_high) +{ + uint64_t address_composite; + unsigned int grp_idx; + unsigned int reg_idx; + const struct mod_scmi_apcore_reset_register_group *reg_group; + uintptr_t reset_reg; + + address_composite = ((uint64_t)address_high << 32) | address_low; + + /* Iterate over the reset register group structures */ + for (grp_idx = 0; + grp_idx < scmi_apcore_ctx.config->reset_register_group_count; + grp_idx++) { + + reg_group = + &scmi_apcore_ctx.config->reset_register_group_table[grp_idx]; + assert(reg_group->base_register != 0); + + /* Begin with the first register in the group */ + reset_reg = reg_group->base_register; + + /* Program each reset vector register within the group */ + for (reg_idx = 0; reg_idx < reg_group->register_count; reg_idx++) { + if (scmi_apcore_ctx.config->reset_register_width == + MOD_SCMI_APCORE_REG_WIDTH_32) { + /* Treat the register as 32-bit */ + *(uint32_t *)reset_reg = address_low; + reset_reg += sizeof(uint32_t); + } else { + /* Treat the register as 64-bit */ + *(uint64_t *)reset_reg = address_composite; + reset_reg += sizeof(uint64_t); + } + } + } + + return FWK_SUCCESS; +} + +/* + * Protocol Version + */ +static int scmi_apcore_protocol_version_handler(fwk_id_t service_id, + const uint32_t *payload) +{ + struct scmi_protocol_version_p2a return_values = { + .status = SCMI_SUCCESS, + .version = SCMI_PROTOCOL_VERSION_APCORE, + }; + + scmi_apcore_ctx.scmi_api->respond( + service_id, &return_values, sizeof(return_values)); + return FWK_SUCCESS; +} + +/* + * Protocol Attributes + */ +static int scmi_apcore_protocol_attributes_handler(fwk_id_t service_id, + const uint32_t *payload) +{ + struct scmi_protocol_attributes_p2a return_values = { + .status = SCMI_SUCCESS, + .attributes = 0, + }; + + if (scmi_apcore_ctx.config->reset_register_width == + MOD_SCMI_APCORE_REG_WIDTH_64) + return_values.attributes |= SCMI_APCORE_PROTOCOL_ATTRIBUTES_64BIT_MASK; + + scmi_apcore_ctx.scmi_api->respond( + service_id, + &return_values, + sizeof(return_values)); + + return FWK_SUCCESS; +} + +/* + * Protocol Message Attributes + */ +static int scmi_apcore_protocol_message_attributes_handler(fwk_id_t service_id, + const uint32_t *payload) +{ + size_t response_size; + const struct scmi_protocol_message_attributes_a2p *parameters; + unsigned int message_id; + struct scmi_protocol_message_attributes_p2a return_values = { + .status = SCMI_SUCCESS, + .attributes = 0, + }; + + parameters = (const struct scmi_protocol_message_attributes_a2p *) + payload; + message_id = parameters->message_id; + + if ((message_id >= FWK_ARRAY_SIZE(handler_table)) || + (handler_table[message_id] == NULL)) + return_values.status = SCMI_NOT_FOUND; + + response_size = (return_values.status == SCMI_SUCCESS) ? + sizeof(return_values) : sizeof(return_values.status); + + scmi_apcore_ctx.scmi_api->respond( + service_id, &return_values, response_size); + + return FWK_SUCCESS; +} + +/* + * Reset Address Set + */ +static int scmi_apcore_reset_address_set_handler(fwk_id_t service_id, + const uint32_t *payload) +{ + int status; + unsigned int agent_id; + enum scmi_agent_type agent_type; + const struct scmi_apcore_reset_address_set_a2p *parameters; + struct scmi_apcore_reset_address_set_p2a return_values = { + .status = SCMI_GENERIC_ERROR + }; + + parameters = (const struct scmi_apcore_reset_address_set_a2p *)payload; + + status = scmi_apcore_ctx.scmi_api->get_agent_id(service_id, &agent_id); + if (status != FWK_SUCCESS) + goto exit; + + status = scmi_apcore_ctx.scmi_api->get_agent_type(agent_id, &agent_type); + if (status != FWK_SUCCESS) + goto exit; + + /* Only the PSCI agent may set the reset address */ + if (agent_type != SCMI_AGENT_TYPE_PSCI) { + return_values.status = SCMI_DENIED; + goto exit; + } + + /* An agent previously requested that the configuration be locked */ + if (scmi_apcore_ctx.locked) { + return_values.status = SCMI_DENIED; + goto exit; + } + + /* + * Ensure that the platform has 64-bit reset vector registers if a reset + * address utilizing more that 32 bits has been provided. + */ + if ((parameters->reset_address_high != 0) && + (scmi_apcore_ctx.config->reset_register_width == + MOD_SCMI_APCORE_REG_WIDTH_32)) { + return_values.status = SCMI_INVALID_PARAMETERS; + goto exit; + } + + /* Check for alignment */ + if (scmi_apcore_ctx.config->reset_register_width == + MOD_SCMI_APCORE_REG_WIDTH_32) { + if ((parameters->reset_address_low % 4) != 0) { + return_values.status = SCMI_INVALID_PARAMETERS; + goto exit; + } + } else if ((parameters->reset_address_low % 8) != 0) { + return_values.status = SCMI_INVALID_PARAMETERS; + goto exit; + } + + status = set_reset_address( + parameters->reset_address_low, parameters->reset_address_high); + if (status != FWK_SUCCESS) + goto exit; + + return_values.status = SCMI_SUCCESS; + + /* Lock the configuration if requested */ + if (parameters->attributes & SCMI_APCORE_RESET_ADDRESS_SET_LOCK_MASK) + scmi_apcore_ctx.locked = true; + +exit: + scmi_apcore_ctx.scmi_api->respond( + service_id, &return_values, sizeof(return_values)); + return status; +} + +/* + * Reset Address Get + */ +static int scmi_apcore_reset_address_get_handler(fwk_id_t service_id, + const uint32_t *payload) +{ + int status; + unsigned int agent_id; + const struct mod_scmi_apcore_reset_register_group *reg_group; + uint64_t reset_address; + enum scmi_agent_type agent_type; + struct scmi_apcore_reset_address_get_p2a return_values = { + .status = SCMI_GENERIC_ERROR + }; + + status = scmi_apcore_ctx.scmi_api->get_agent_id(service_id, &agent_id); + if (status != FWK_SUCCESS) + goto exit; + + status = scmi_apcore_ctx.scmi_api->get_agent_type(agent_id, &agent_type); + if (status != FWK_SUCCESS) + goto exit; + + /* Only the PSCI agent may get the current reset address */ + if (agent_type != SCMI_AGENT_TYPE_PSCI) { + return_values.status = SCMI_DENIED; + goto exit; + } + + /* The reset address is common across all reset address registers */ + reg_group = &scmi_apcore_ctx.config->reset_register_group_table[0]; + + if (scmi_apcore_ctx.config->reset_register_width == + MOD_SCMI_APCORE_REG_WIDTH_32) { + reset_address = *(uint32_t *)reg_group->base_register; + return_values.reset_address_high = 0; + } else { + reset_address = *(uint64_t *)reg_group->base_register; + return_values.reset_address_high = (reset_address >> 32) & UINT32_MAX; + } + + return_values.reset_address_low = (uint32_t)reset_address; + + return_values.attributes |= + (scmi_apcore_ctx.locked << SCMI_APCORE_RESET_ADDRESS_GET_LOCK_POS); + return_values.status = SCMI_SUCCESS; + +exit: + scmi_apcore_ctx.scmi_api->respond( + service_id, &return_values, sizeof(return_values)); + return status; +} + +/* + * SCMI module -> SCMI AP Core Configuration module interface + */ +static int scmi_apcore_get_scmi_protocol_id(fwk_id_t protocol_id, + uint8_t *scmi_protocol_id) +{ + int status; + + status = fwk_module_check_call(protocol_id); + if (status != FWK_SUCCESS) + return status; + + *scmi_protocol_id = SCMI_PROTOCOL_ID_APCORE; + + return FWK_SUCCESS; +} + +static int scmi_apcore_message_handler( + fwk_id_t protocol_id, + fwk_id_t service_id, + const uint32_t *payload, + size_t payload_size, + unsigned int message_id) +{ + int status; + int32_t return_value; + + static_assert(FWK_ARRAY_SIZE(handler_table) == + FWK_ARRAY_SIZE(payload_size_table), + "[SCMI] Core configuration protocol table sizes not consistent"); + assert(payload != NULL); + + status = fwk_module_check_call(protocol_id); + if (status != FWK_SUCCESS) + return status; + + if (message_id >= FWK_ARRAY_SIZE(handler_table)) { + return_value = SCMI_NOT_SUPPORTED; + goto error; + } + + if (payload_size != payload_size_table[message_id]) { + return_value = SCMI_PROTOCOL_ERROR; + goto error; + } + + return handler_table[message_id](service_id, payload); + +error: + scmi_apcore_ctx.scmi_api->respond( + service_id, + &return_value, + sizeof(return_value)); + + return FWK_SUCCESS; +} + +static struct mod_scmi_to_protocol_api scmi_apcore_mod_scmi_to_protocol_api = { + .get_scmi_protocol_id = scmi_apcore_get_scmi_protocol_id, + .message_handler = scmi_apcore_message_handler +}; + +/* + * Framework handlers + */ + +static int scmi_apcore_init(fwk_id_t module_id, unsigned int element_count, + const void *data) +{ + const struct mod_scmi_apcore_config *config = + (const struct mod_scmi_apcore_config *)data; + + if (config == NULL) + return FWK_E_PARAM; + if (config->reset_register_group_table == NULL) + return FWK_E_PARAM; + if (config->reset_register_group_count == 0) + return FWK_E_PARAM; + if (config->reset_register_width >= MOD_SCMI_APCORE_REG_WIDTH_COUNT) + return FWK_E_PARAM; + + scmi_apcore_ctx.config = config; + + return FWK_SUCCESS; +} + +static int scmi_apcore_bind(fwk_id_t id, unsigned int round) +{ + if (round == 1) + return FWK_SUCCESS; + + /* Bind to the SCMI module, storing an API pointer for later use. */ + return fwk_module_bind(FWK_ID_MODULE(FWK_MODULE_IDX_SCMI), + FWK_ID_API(FWK_MODULE_IDX_SCMI, MOD_SCMI_API_IDX_PROTOCOL), + &scmi_apcore_ctx.scmi_api); +} + +static int scmi_apcore_process_bind_request(fwk_id_t source_id, + fwk_id_t target_id, fwk_id_t api_id, const void **api) +{ + /* Only accept binding requests from the SCMI module. */ + if (!fwk_id_is_equal(source_id, FWK_ID_MODULE(FWK_MODULE_IDX_SCMI))) + return FWK_E_ACCESS; + + *api = &scmi_apcore_mod_scmi_to_protocol_api; + + return FWK_SUCCESS; +} + +/* SCMI Clock Management Protocol Definition */ +const struct fwk_module module_scmi_apcore = { + .name = "SCMI Core Configuration Protocol", + .api_count = 1, + .type = FWK_MODULE_TYPE_PROTOCOL, + .init = scmi_apcore_init, + .bind = scmi_apcore_bind, + .process_bind_request = scmi_apcore_process_bind_request, +}; diff --git a/module/scmi_clock/include/internal/scmi_clock.h b/module/scmi_clock/include/internal/scmi_clock.h new file mode 100644 index 00000000..50d65d99 --- /dev/null +++ b/module/scmi_clock/include/internal/scmi_clock.h @@ -0,0 +1,191 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * SCMI Clock Management Protocol Support + */ + +#ifndef SCMI_CLOCK_H +#define SCMI_CLOCK_H + +#define SCMI_PROTOCOL_ID_CLOCK UINT32_C(0x14) +#define SCMI_PROTOCOL_VERSION_CLOCK UINT32_C(0x10000) + +/* + * Identifiers of the SCMI Clock Management Protocol commands + */ +enum scmi_clock_command_id { + SCMI_CLOCK_ATTRIBUTES = 0x003, + SCMI_CLOCK_DESCRIBE_RATES = 0x004, + SCMI_CLOCK_RATE_SET = 0x005, + SCMI_CLOCK_RATE_GET = 0x006, + SCMI_CLOCK_CONFIG_SET = 0x007, +}; + +/* + * Protocol Attributes + */ + +#define SCMI_CLOCK_PROTOCOL_ATTRIBUTES_MAX_PENDING_TRANSITIONS_POS 16 +#define SCMI_CLOCK_PROTOCOL_ATTRIBUTES_CLOCK_COUNT_POS 0 + +#define SCMI_CLOCK_PROTOCOL_ATTRIBUTES_MAX_PENDING_TRANSITIONS_MASK \ + (UINT32_C(0xFF) << \ + SCMI_CLOCK_PROTOCOL_ATTRIBUTES_MAX_PENDING_TRANSITIONS_POS) +#define SCMI_CLOCK_PROTOCOL_ATTRIBUTES_CLOCK_COUNT_MASK \ + (UINT32_C(0xFFFF) << SCMI_CLOCK_PROTOCOL_ATTRIBUTES_CLOCK_COUNT_POS) + +#define SCMI_CLOCK_PROTOCOL_ATTRIBUTES(MAX_PENDING_TRANSACTIONS, CLOCK_COUNT) \ + ( \ + ((MAX_PENDING_TRANSACTIONS << \ + SCMI_CLOCK_PROTOCOL_ATTRIBUTES_MAX_PENDING_TRANSITIONS_POS) & \ + SCMI_CLOCK_PROTOCOL_ATTRIBUTES_MAX_PENDING_TRANSITIONS_MASK) | \ + (((CLOCK_COUNT) << SCMI_CLOCK_PROTOCOL_ATTRIBUTES_CLOCK_COUNT_POS) & \ + SCMI_CLOCK_PROTOCOL_ATTRIBUTES_CLOCK_COUNT_MASK) \ + ) + +/* + * Clock Attributes + */ + +#define SCMI_CLOCK_ATTRIBUTES_ENABLED_POS 0 + +#define SCMI_CLOCK_ATTRIBUTES_ENABLED_MASK \ + (UINT32_C(0x1) << SCMI_CLOCK_ATTRIBUTES_ENABLED_POS) + +#define SCMI_CLOCK_ATTRIBUTES(ENABLED) \ + ( \ + (((ENABLED) << SCMI_CLOCK_ATTRIBUTES_ENABLED_POS) & \ + SCMI_CLOCK_ATTRIBUTES_ENABLED_MASK) \ + ) + +struct __attribute((packed)) scmi_clock_attributes_a2p { + uint32_t clock_id; +}; + +#define SCMI_CLOCK_NAME_LENGTH_MAX 16 + +struct __attribute((packed)) scmi_clock_attributes_p2a { + int32_t status; + uint32_t attributes; + char clock_name[SCMI_CLOCK_NAME_LENGTH_MAX]; +}; + +/* + * Clock Rate Get + */ + +struct __attribute((packed)) scmi_clock_rate_get_a2p { + uint32_t clock_id; +}; + +struct __attribute((packed)) scmi_clock_rate_get_p2a { + int32_t status; + uint32_t rate[2]; +}; + +/* + * Clock Rate Set + */ + +/* If set, set the new clock rate asynchronously */ +#define SCMI_CLOCK_RATE_SET_ASYNC_POS 0 +/* If set, do not send a delayed asynchronous response */ +#define SCMI_CLOCK_RATE_SET_NO_DELAYED_RESPONSE_POS 1 +/* Round up, if set, otherwise round down */ +#define SCMI_CLOCK_RATE_SET_ROUND_UP_POS 2 +/* If set, the platform chooses the appropriate rounding mode */ +#define SCMI_CLOCK_RATE_SET_ROUND_AUTO_POS 3 + +#define SCMI_CLOCK_RATE_SET_ASYNC_MASK \ + (UINT32_C(0x1) << SCMI_CLOCK_RATE_SET_ASYNC_POS) +#define SCMI_CLOCK_RATE_SET_NO_DELAYED_RESPONSE_MASK \ + (UINT32_C(0x1) << SCMI_CLOCK_RATE_SET_NO_DELAYED_RESPONSE_POS) +#define SCMI_CLOCK_RATE_SET_ROUND_UP_MASK \ + (UINT32_C(0x1) << SCMI_CLOCK_RATE_SET_ROUND_UP_POS) +#define SCMI_CLOCK_RATE_SET_ROUND_AUTO_MASK \ + (UINT32_C(0x1) << SCMI_CLOCK_RATE_SET_ROUND_AUTO_POS) + +struct __attribute((packed)) scmi_clock_rate_set_a2p { + uint32_t flags; + uint32_t clock_id; + uint32_t rate[2]; +}; + +struct __attribute((packed)) scmi_clock_rate_set_p2a { + int32_t status; +}; + +/* + * Clock Config Set + */ + +#define SCMI_CLOCK_CONFIG_SET_ENABLE_POS 0 + +#define SCMI_CLOCK_CONFIG_SET_ENABLE_MASK \ + (UINT32_C(0x1) << SCMI_CLOCK_CONFIG_SET_ENABLE_POS) + +struct __attribute((packed)) scmi_clock_config_set_a2p { + uint32_t clock_id; + uint32_t attributes; +}; + +struct __attribute((packed)) scmi_clock_config_set_p2a { + int32_t status; +}; + +/* + * Clock Describe Rates + */ + +#define SCMI_CLOCK_RATE_FORMAT_RANGE 1 +#define SCMI_CLOCK_RATE_FORMAT_LIST 0 + +#define SCMI_CLOCK_DESCRIBE_RATES_REMAINING_POS 16 +#define SCMI_CLOCK_DESCRIBE_RATES_FORMAT_POS 12 +#define SCMI_CLOCK_DESCRIBE_RATES_COUNT_POS 0 + +#define SCMI_CLOCK_DESCRIBE_RATES_NUM_RATES_FLAGS( \ + RATE_COUNT, RETURN_FORMAT, REMAINING_RATES) \ + ( \ + ((RATE_COUNT << \ + SCMI_CLOCK_DESCRIBE_RATES_COUNT_POS) & \ + SCMI_CLOCK_DESCRIBE_RATES_COUNT_MASK) | \ + ((REMAINING_RATES << SCMI_CLOCK_DESCRIBE_RATES_REMAINING_POS) & \ + SCMI_CLOCK_DESCRIBE_RATES_REMAINING_MASK) | \ + ((RETURN_FORMAT << SCMI_CLOCK_DESCRIBE_RATES_FORMAT_POS) & \ + SCMI_CLOCK_DESCRIBE_RATES_FORMAT_MASK) \ + ) + +#define SCMI_CLOCK_DESCRIBE_RATES_REMAINING_MASK \ + (UINT32_C(0xFFFF) << SCMI_CLOCK_DESCRIBE_RATES_REMAINING_POS) +#define SCMI_CLOCK_DESCRIBE_RATES_FORMAT_MASK \ + (UINT32_C(0x1) << SCMI_CLOCK_DESCRIBE_RATES_FORMAT_POS) +#define SCMI_CLOCK_DESCRIBE_RATES_COUNT_MASK \ + (UINT32_C(0xFFF) << SCMI_CLOCK_DESCRIBE_RATES_COUNT_POS) + +#define SCMI_CLOCK_RATES_MAX(MAILBOX_SIZE) \ + ((sizeof(struct scmi_clock_describe_rates_p2a) < (MAILBOX_SIZE)) ? \ + (((MAILBOX_SIZE) - sizeof(struct scmi_clock_describe_rates_p2a)) \ + / sizeof(struct scmi_clock_rate)) : 0) + +struct __attribute((packed)) scmi_clock_rate { + uint32_t low; + uint32_t high; +}; + +struct __attribute((packed)) scmi_clock_describe_rates_a2p { + uint32_t clock_id; + uint32_t rate_index; +}; + +struct __attribute((packed)) scmi_clock_describe_rates_p2a { + int32_t status; + uint32_t num_rates_flags; + struct scmi_clock_rate rates[]; +}; + +#endif /* SCMI_CLOCK_H */ diff --git a/module/scmi_clock/include/mod_scmi_clock.h b/module/scmi_clock/include/mod_scmi_clock.h new file mode 100644 index 00000000..82874510 --- /dev/null +++ b/module/scmi_clock/include/mod_scmi_clock.h @@ -0,0 +1,111 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * SCMI Clock Management Protocol Support. + */ + +#ifndef MOD_SCMI_CLOCK_H +#define MOD_SCMI_CLOCK_H + +#include <stdint.h> + +/*! + * \ingroup GroupModules Modules + * \defgroup GroupSCMI_CLOCK SCMI Clock Management Protocol + * \{ + */ + +/*! + * \brief Permission flags governing the ability to use certain SCMI commands to + * interact with a clock. + * + * \details Setting a permission flag for a clock enables the corresponding + * functionality for any agent that has visibilty of the clock + * through its clock device table. + */ +enum mod_scmi_clock_permissions { + /*! No permissions (at least one must be granted) */ + MOD_SCMI_CLOCK_PERM_INVALID = 0, + + /*! The clock's attributes can be queried */ + MOD_SCMI_CLOCK_PERM_ATTRIBUTES = (1 << 0), + + /*! The clock's supported rates can be queried */ + MOD_SCMI_CLOCK_PERM_DESCRIBE_RATES = (1 << 1), + + /*! The clock's current rate can be queried */ + MOD_SCMI_CLOCK_PERM_GET_RATE = (1 << 2), + + /*! The clock can be set to a new rate */ + MOD_SCMI_CLOCK_PERM_SET_RATE = (1 << 3), + + /*! The clock can be enabled and disabled */ + MOD_SCMI_CLOCK_PERM_SET_CONFIG = (1 << 4), +}; + +/*! + * \brief Clock device. + * + * \details Clock device structures are used in per-agent clock device tables. + * Each contains an identifier of an element that will be bound to in order + * to use the clock device. The permission flags for the clock are applied + * to any agent that uses the device configuration in its clock device + * table. + */ +struct mod_scmi_clock_device { + /*! + * \brief Clock element identifier. + * + * \details The module that owns the element must implement the Clock API + * that is defined by the \c clock module. + */ + fwk_id_t element_id; + + /*! Mask of permission flags defined by \ref mod_scmi_clock_permissions */ + uint8_t permissions; +}; + +/*! + * \brief Agent descriptor. + * + * \details Describes an agent that uses the SCMI Clock Management protocol. + * Provides a pointer to the agent's clock device table and the number of + * devices within the table. + */ +struct mod_scmi_clock_agent { + /*! Pointer to a table of clock devices that are visible to the agent */ + const struct mod_scmi_clock_device *device_table; + + /*! + * \brief The number of \c mod_scmi_clock_device structures in the table + * pointed to by \c device_table. + */ + uint8_t device_count; +}; + +/*! + * \brief Module configuration. + */ +struct mod_scmi_clock_config { + /*! Maximum supported number of pending, asynchronous clock rate changes */ + uint8_t max_pending_transactions; + + /*! + * \brief Pointer to the table of agent descriptors, used to provide + * per-agent views of clocks in the system. + */ + const struct mod_scmi_clock_agent *agent_table; + + /*! Number of agents in \ref agent_table */ + size_t agent_count; +}; + +/*! + * \} + */ + +#endif /* MOD_SCMI_CLOCK_H */ diff --git a/module/scmi_clock/src/Makefile b/module/scmi_clock/src/Makefile new file mode 100644 index 00000000..3b814d07 --- /dev/null +++ b/module/scmi_clock/src/Makefile @@ -0,0 +1,11 @@ +# +# Arm SCP/MCP Software +# Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +BS_LIB_NAME := SCMI Clock Protocol +BS_LIB_SOURCES := mod_scmi_clock.c + +include $(BS_DIR)/lib.mk diff --git a/module/scmi_clock/src/mod_scmi_clock.c b/module/scmi_clock/src/mod_scmi_clock.c new file mode 100644 index 00000000..0e95ea69 --- /dev/null +++ b/module/scmi_clock/src/mod_scmi_clock.c @@ -0,0 +1,763 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * SCMI Clock Management Protocol Support. + */ + +#include <assert.h> +#include <string.h> +#include <fwk_errno.h> +#include <fwk_id.h> +#include <fwk_macros.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <internal/scmi.h> +#include <internal/scmi_clock.h> +#include <mod_clock.h> +#include <mod_scmi.h> +#include <mod_scmi_clock.h> + +struct scmi_clock_ctx { + /*! SCMI Clock Module Configuration */ + const struct mod_scmi_clock_config *config; + + /*! Maximum supported number of pending, asynchronous clock rate changes */ + uint8_t max_pending_transactions; + + /*! + * Pointer to the table of agent descriptors, used to provide per-agent + * views of clocks in the system. + */ + const struct mod_scmi_clock_agent *agent_table; + + /* SCMI module API */ + const struct mod_scmi_from_protocol_api *scmi_api; + + /* Clock module API */ + const struct mod_clock_api *clock_api; +}; + +static int scmi_clock_protocol_version_handler(fwk_id_t service_id, + const uint32_t *payload); +static int scmi_clock_protocol_attributes_handler(fwk_id_t service_id, + const uint32_t *payload); +static int scmi_clock_protocol_message_attributes_handler( + fwk_id_t service_id, const uint32_t *payload); +static int scmi_clock_attributes_handler(fwk_id_t service_id, + const uint32_t *payload); +static int scmi_clock_rate_get_handler(fwk_id_t service_id, + const uint32_t *payload); +static int scmi_clock_rate_set_handler(fwk_id_t service_id, + const uint32_t *payload); +static int scmi_clock_config_set_handler(fwk_id_t service_id, + const uint32_t *payload); +static int scmi_clock_describe_rates_handler(fwk_id_t service_id, + const uint32_t *payload); + +/* + * Internal variables. + */ +static struct scmi_clock_ctx scmi_clock_ctx; + +static int (* const handler_table[])(fwk_id_t, const uint32_t *) = { + [SCMI_PROTOCOL_VERSION] = scmi_clock_protocol_version_handler, + [SCMI_PROTOCOL_ATTRIBUTES] = scmi_clock_protocol_attributes_handler, + [SCMI_PROTOCOL_MESSAGE_ATTRIBUTES] = + scmi_clock_protocol_message_attributes_handler, + [SCMI_CLOCK_ATTRIBUTES] = scmi_clock_attributes_handler, + [SCMI_CLOCK_RATE_GET] = scmi_clock_rate_get_handler, + [SCMI_CLOCK_RATE_SET] = scmi_clock_rate_set_handler, + [SCMI_CLOCK_CONFIG_SET] = scmi_clock_config_set_handler, + [SCMI_CLOCK_DESCRIBE_RATES] = scmi_clock_describe_rates_handler, +}; + +static const unsigned int payload_size_table[] = { + [SCMI_PROTOCOL_VERSION] = 0, + [SCMI_PROTOCOL_ATTRIBUTES] = 0, + [SCMI_PROTOCOL_MESSAGE_ATTRIBUTES] = + sizeof(struct scmi_protocol_message_attributes_a2p), + [SCMI_CLOCK_ATTRIBUTES] = sizeof(struct scmi_clock_attributes_a2p), + [SCMI_CLOCK_RATE_GET] = sizeof(struct scmi_clock_rate_get_a2p), + [SCMI_CLOCK_RATE_SET] = sizeof(struct scmi_clock_rate_set_a2p), + [SCMI_CLOCK_CONFIG_SET] = sizeof(struct scmi_clock_config_set_a2p), + [SCMI_CLOCK_DESCRIBE_RATES] = sizeof(struct scmi_clock_describe_rates_a2p), +}; + +/* + * Given a service identifier, retrieve a pointer to its agent's + * \c mod_scmi_clock_agent structure within the agent table. + */ +static int get_agent_entry( + fwk_id_t service_id, + const struct mod_scmi_clock_agent **agent) +{ + int status; + unsigned int agent_id; + + if (agent == NULL) + return FWK_E_PARAM; + + status = scmi_clock_ctx.scmi_api->get_agent_id(service_id, &agent_id); + if (status != FWK_SUCCESS) + return status; + + if (agent_id >= scmi_clock_ctx.config->agent_count) + return FWK_E_PARAM; + + *agent = + (struct mod_scmi_clock_agent *)&scmi_clock_ctx.agent_table[agent_id]; + + return FWK_SUCCESS; +} + +/* + * Given a service identifier and a clock index, retrieve a pointer to the + * clock's \c mod_scmi_clock_device structure within the agent's device table. + * Optionally, a pointer to the agent may be retrieved as well. + */ +static int get_clock_device_entry( + fwk_id_t service_id, + unsigned int clock_idx, + const struct mod_scmi_clock_device **clock_device, + const struct mod_scmi_clock_agent **agent) +{ + int status; + const struct mod_scmi_clock_agent *agent_entry; + + if (clock_device == NULL) + return FWK_E_PARAM; + + status = get_agent_entry(service_id, &agent_entry); + if (status != FWK_SUCCESS) + return status; + + if (clock_idx >= agent_entry->device_count) + return FWK_E_RANGE; + + *clock_device = &agent_entry->device_table[clock_idx]; + + assert((*clock_device)->permissions != MOD_SCMI_CLOCK_PERM_INVALID); + assert(fwk_module_is_valid_element_id((*clock_device)->element_id)); + + if (agent != NULL) + *agent = agent_entry; + + return FWK_SUCCESS; +} + +/* + * Query the permissions for a service making a request to determine if + * the operation is permitted on the given clock device. + */ +static int check_service_permission( + const struct mod_scmi_clock_device *device, + enum mod_scmi_clock_permissions requested_permission, + bool *granted) +{ + if (granted == NULL) + return FWK_E_PARAM; + + *granted = device->permissions & requested_permission; + + return FWK_SUCCESS; +} + +/* + * Protocol Version + */ +static int scmi_clock_protocol_version_handler(fwk_id_t service_id, + const uint32_t *payload) +{ + struct scmi_protocol_version_p2a return_values = { + .status = SCMI_SUCCESS, + .version = SCMI_PROTOCOL_VERSION_CLOCK, + }; + + scmi_clock_ctx.scmi_api->respond(service_id, &return_values, + sizeof(return_values)); + return FWK_SUCCESS; +} + +/* + * Protocol Attributes + */ +static int scmi_clock_protocol_attributes_handler(fwk_id_t service_id, + const uint32_t *payload) +{ + int status; + const struct mod_scmi_clock_agent *agent; + struct scmi_protocol_attributes_p2a return_values = { + .status = SCMI_SUCCESS, + }; + + status = get_agent_entry(service_id, &agent); + if (status != FWK_SUCCESS) { + return_values.status = SCMI_GENERIC_ERROR; + goto exit; + } + + return_values.attributes = SCMI_CLOCK_PROTOCOL_ATTRIBUTES( + scmi_clock_ctx.max_pending_transactions, + agent->device_count + ); + +exit: + scmi_clock_ctx.scmi_api->respond(service_id, &return_values, + sizeof(return_values)); + return status; +} + +/* + * Protocol Message Attributes + */ +static int scmi_clock_protocol_message_attributes_handler(fwk_id_t service_id, + const uint32_t *payload) +{ + size_t response_size; + const struct scmi_protocol_message_attributes_a2p *parameters; + unsigned int message_id; + struct scmi_protocol_message_attributes_p2a return_values = { + .status = SCMI_SUCCESS, + .attributes = 0, + }; + + parameters = (const struct scmi_protocol_message_attributes_a2p*) + payload; + message_id = parameters->message_id; + + if ((message_id >= FWK_ARRAY_SIZE(handler_table)) || + (handler_table[message_id] == NULL)) { + return_values.status = SCMI_NOT_FOUND; + goto exit; + } + +exit: + response_size = (return_values.status == SCMI_SUCCESS) ? + sizeof(return_values) : sizeof(return_values.status); + scmi_clock_ctx.scmi_api->respond(service_id, &return_values, response_size); + return FWK_SUCCESS; +} + +/* + * Clock Attributes + */ +static int scmi_clock_attributes_handler(fwk_id_t service_id, + const uint32_t *payload) +{ + int status; + const struct mod_scmi_clock_agent *agent; + const struct mod_scmi_clock_device *clock_device; + bool service_permission_granted; + size_t response_size; + enum mod_clock_state clock_state; + const struct scmi_clock_attributes_a2p *parameters; + struct scmi_clock_attributes_p2a return_values = { + .status = SCMI_GENERIC_ERROR + }; + + parameters = (const struct scmi_clock_attributes_a2p*)payload; + + status = get_clock_device_entry(service_id, + parameters->clock_id, + &clock_device, + &agent); + if (status != FWK_SUCCESS) { + return_values.status = SCMI_NOT_FOUND; + goto exit; + } + + status = check_service_permission(clock_device, + MOD_SCMI_CLOCK_PERM_ATTRIBUTES, &service_permission_granted); + if (status != FWK_SUCCESS) + goto exit; + + if (!service_permission_granted) { + return_values.status = SCMI_DENIED; + goto exit; + } + + status = scmi_clock_ctx.clock_api->get_state(clock_device->element_id, + &clock_state); + if (status != FWK_SUCCESS) + goto exit; + + return_values = (struct scmi_clock_attributes_p2a) { + .status = SCMI_SUCCESS, + .attributes = SCMI_CLOCK_ATTRIBUTES( + clock_state == MOD_CLOCK_STATE_RUNNING), + }; + + strncpy(return_values.clock_name, + fwk_module_get_name(clock_device->element_id), + sizeof(return_values.clock_name) - 1); + +exit: + response_size = (return_values.status == SCMI_SUCCESS) ? + sizeof(return_values) : sizeof(return_values.status); + scmi_clock_ctx.scmi_api->respond(service_id, &return_values, response_size); + return status; +} + +/* + * Clock Rate Get + */ +static int scmi_clock_rate_get_handler(fwk_id_t service_id, + const uint32_t *payload) +{ + int status; + const struct mod_scmi_clock_agent *agent; + const struct mod_scmi_clock_device *clock_device; + bool service_permission_granted; + size_t response_size; + uint64_t rate; + const struct scmi_clock_rate_get_a2p *parameters; + struct scmi_clock_rate_get_p2a return_values = { + .status = SCMI_GENERIC_ERROR + }; + + parameters = (const struct scmi_clock_rate_get_a2p*)payload; + + status = get_clock_device_entry(service_id, + parameters->clock_id, + &clock_device, + &agent); + if (status != FWK_SUCCESS) { + return_values.status = SCMI_NOT_FOUND; + goto exit; + } + + status = check_service_permission(clock_device, + MOD_SCMI_CLOCK_PERM_ATTRIBUTES, &service_permission_granted); + if (status != FWK_SUCCESS) + goto exit; + + if (!service_permission_granted) { + return_values.status = SCMI_NOT_SUPPORTED; + goto exit; + } + + status = scmi_clock_ctx.clock_api->get_rate( + clock_device->element_id, &rate); + return_values.rate[0] = (uint32_t)rate; + return_values.rate[1] = (uint32_t)(rate >> 32); + + if (status == FWK_SUCCESS) + return_values.status = SCMI_SUCCESS; + +exit: + response_size = (return_values.status == SCMI_SUCCESS) ? + sizeof(return_values) : sizeof(return_values.status); + scmi_clock_ctx.scmi_api->respond(service_id, &return_values, response_size); + return status; +} + +/* + * Clock Rate Set (Synchronous Only) + */ +static int scmi_clock_rate_set_handler(fwk_id_t service_id, + const uint32_t *payload) +{ + int status; + const struct mod_scmi_clock_agent *agent; + const struct mod_scmi_clock_device *clock_device; + bool service_permission_granted; + size_t response_size; + uint64_t rate; + bool round_auto; + bool round_up; + bool asynchronous; + const struct scmi_clock_rate_set_a2p *parameters; + struct scmi_clock_rate_set_p2a return_values = { + .status = SCMI_GENERIC_ERROR + }; + + parameters = (const struct scmi_clock_rate_set_a2p*)payload; + round_up = parameters->flags & SCMI_CLOCK_RATE_SET_ROUND_UP_MASK; + round_auto = parameters->flags & SCMI_CLOCK_RATE_SET_ROUND_AUTO_MASK; + asynchronous = parameters->flags & SCMI_CLOCK_RATE_SET_ASYNC_MASK; + rate = (uint64_t)parameters->rate[0] + + (((uint64_t)parameters->rate[1]) << 32); + + status = get_clock_device_entry(service_id, + parameters->clock_id, + &clock_device, + &agent); + if (status != FWK_SUCCESS) { + return_values.status = SCMI_NOT_FOUND; + goto exit; + } + + status = check_service_permission(clock_device, + MOD_SCMI_CLOCK_PERM_ATTRIBUTES, &service_permission_granted); + if (status != FWK_SUCCESS) + goto exit; + + if (!service_permission_granted) { + return_values.status = SCMI_NOT_SUPPORTED; + goto exit; + } + + if (asynchronous) { + /* Support for async clock set commands not yet implemented */ + return_values.status = SCMI_NOT_SUPPORTED; + goto exit; + } + + status = scmi_clock_ctx.clock_api->set_rate(clock_device->element_id, rate, + round_auto ? MOD_CLOCK_ROUND_MODE_NEAREST : + (round_up ? MOD_CLOCK_ROUND_MODE_UP : MOD_CLOCK_ROUND_MODE_DOWN)); + if (status == FWK_E_RANGE) { + return_values.status = SCMI_INVALID_PARAMETERS; + goto exit; + } + if (status != FWK_SUCCESS) + goto exit; + + return_values.status = SCMI_SUCCESS; + +exit: + response_size = (return_values.status == SCMI_SUCCESS) ? + sizeof(return_values) : sizeof(return_values.status); + scmi_clock_ctx.scmi_api->respond(service_id, &return_values, response_size); + return FWK_SUCCESS; +} + +/* + * Clock Config Set + */ +static int scmi_clock_config_set_handler(fwk_id_t service_id, + const uint32_t *payload) +{ + int status; + bool enable; + bool service_permission_granted; + size_t response_size; + const struct scmi_clock_config_set_a2p *parameters; + const struct mod_scmi_clock_agent *agent; + const struct mod_scmi_clock_device *clock_device; + struct scmi_clock_rate_set_p2a return_values = { + .status = SCMI_GENERIC_ERROR + }; + + parameters = (const struct scmi_clock_config_set_a2p*)payload; + enable = parameters->attributes & SCMI_CLOCK_CONFIG_SET_ENABLE_MASK; + + status = get_clock_device_entry(service_id, + parameters->clock_id, + &clock_device, + &agent); + if (status != FWK_SUCCESS) { + return_values.status = SCMI_NOT_FOUND; + goto exit; + } + + status = check_service_permission(clock_device, + MOD_SCMI_CLOCK_PERM_ATTRIBUTES, &service_permission_granted); + if (status != FWK_SUCCESS) + goto exit; + + if (!service_permission_granted) { + return_values.status = SCMI_NOT_SUPPORTED; + goto exit; + } + + status = scmi_clock_ctx.clock_api->set_state( + clock_device->element_id, + enable ? MOD_CLOCK_STATE_RUNNING : MOD_CLOCK_STATE_STOPPED); + if (status == FWK_E_SUPPORT) { + return_values.status = SCMI_NOT_SUPPORTED; + goto exit; + } + if (status != FWK_SUCCESS) + goto exit; + + return_values.status = SCMI_SUCCESS; + +exit: + response_size = (return_values.status == SCMI_SUCCESS) ? + sizeof(return_values) : sizeof(return_values.status); + scmi_clock_ctx.scmi_api->respond(service_id, &return_values, response_size); + return FWK_SUCCESS; +} + +/* + * Clock Describe Rates + */ +static int scmi_clock_describe_rates_handler(fwk_id_t service_id, + const uint32_t *payload) +{ + int status; + const struct mod_scmi_clock_agent *agent; + const struct mod_scmi_clock_device *clock_device; + bool service_permission_granted; + unsigned int i; + size_t max_payload_size; + uint32_t payload_size; + uint32_t index; + unsigned int rate_count; + unsigned int remaining_rates; + uint64_t rate; + struct scmi_clock_rate scmi_rate; + struct scmi_clock_rate clock_range[3]; + struct mod_clock_info info; + const struct scmi_clock_describe_rates_a2p *parameters; + struct scmi_clock_describe_rates_p2a return_values = { + .status = SCMI_GENERIC_ERROR + }; + + parameters = (const struct scmi_clock_describe_rates_a2p*)payload; + index = parameters->rate_index; + payload_size = sizeof(return_values); + + status = get_clock_device_entry(service_id, + parameters->clock_id, + &clock_device, + &agent); + if (status != FWK_SUCCESS) { + return_values.status = SCMI_NOT_FOUND; + goto exit; + } + + status = check_service_permission(clock_device, + MOD_SCMI_CLOCK_PERM_ATTRIBUTES, &service_permission_granted); + if (status != FWK_SUCCESS) + goto exit; + + if (!service_permission_granted) { + return_values.status = SCMI_NOT_SUPPORTED; + goto exit; + } + + /* + * Get the maximum payload size to determine how many clock rate entries can + * be returned in one response. + */ + status = scmi_clock_ctx.scmi_api->get_max_payload_size( + service_id, &max_payload_size); + if (status != FWK_SUCCESS) + goto exit; + + status = scmi_clock_ctx.clock_api->get_info(clock_device->element_id, + &info); + if (status != FWK_SUCCESS) + goto exit; + + if (info.range.rate_type == MOD_CLOCK_RATE_TYPE_DISCRETE) { + /* The clock has a discrete list of frequencies */ + + if (index >= info.range.rate_count) { + return_values.status = SCMI_INVALID_PARAMETERS; + goto exit; + } + + /* Can at least one entry be returned? */ + if (SCMI_CLOCK_RATES_MAX(max_payload_size) == 0) { + status = FWK_E_SIZE; + goto exit; + } + + /* The number of rates being returned in this payload is defined as the + * smaller of: + * - The clock rates that are available between the index and the + clock's maximum rate. + - The number of rates that can be returned in each payload. + */ + rate_count = FWK_MIN(SCMI_CLOCK_RATES_MAX(max_payload_size), + info.range.rate_count - index); + + /* + * Because the agent gives a starting index into the clock's rate list + * the number of rates remaining is calculated as the number of rates + * the clock supports minus the index, with the number of rates being + * returned in this payload subtracted. + */ + remaining_rates = (info.range.rate_count - index) - rate_count; + + /* Give the number of rates sent in the message payload */ + return_values.num_rates_flags = + SCMI_CLOCK_DESCRIBE_RATES_NUM_RATES_FLAGS( + rate_count, + SCMI_CLOCK_RATE_FORMAT_LIST, + remaining_rates + ); + + /* Set each rate entry in the payload to the associated frequency */ + for (i = 0; i < rate_count; i++, + payload_size += sizeof(struct scmi_clock_rate)) { + status = scmi_clock_ctx.clock_api->get_rate_from_index( + clock_device->element_id, + index + i, + &rate); + if (status != FWK_SUCCESS) + goto exit; + + scmi_rate.low = (uint32_t)rate; + scmi_rate.high = (uint32_t)(rate >> 32); + + status = scmi_clock_ctx.scmi_api->write_payload(service_id, + payload_size, &scmi_rate, sizeof(scmi_rate)); + if (status != FWK_SUCCESS) + goto exit; + } + } else { + /* The clock has a linear stepping */ + + /* Is the payload area large enough to return the complete triplet? */ + if (SCMI_CLOCK_RATES_MAX(max_payload_size) < 3) { + status = FWK_E_SIZE; + goto exit; + } + + return_values.num_rates_flags = + SCMI_CLOCK_DESCRIBE_RATES_NUM_RATES_FLAGS( + /* Only a single rate is returned */ + 1, + SCMI_CLOCK_RATE_FORMAT_RANGE, + /* No further rates are available */ + 0 + ); + + /* Store the range data in the range entry in the payload */ + clock_range[0].low = (uint32_t)info.range.min; + clock_range[0].high = (uint32_t)(info.range.min >> 32); + clock_range[1].low = (uint32_t)info.range.max; + clock_range[1].high = (uint32_t)(info.range.max >> 32); + clock_range[2].low = (uint32_t)info.range.step; + clock_range[2].high = (uint32_t)(info.range.step >> 32); + + status = scmi_clock_ctx.scmi_api->write_payload(service_id, + payload_size, &clock_range, sizeof(clock_range)); + if (status != FWK_SUCCESS) + goto exit; + payload_size += sizeof(clock_range); + } + + return_values.status = SCMI_SUCCESS; + status = scmi_clock_ctx.scmi_api->write_payload(service_id, 0, + &return_values, sizeof(return_values)); + +exit: + scmi_clock_ctx.scmi_api->respond(service_id, + (return_values.status == SCMI_SUCCESS) ? + NULL : &return_values.status, + (return_values.status == SCMI_SUCCESS) ? + payload_size : sizeof(return_values.status)); + return status; +} + +/* + * SCMI module -> SCMI clock module interface + */ +static int scmi_clock_get_scmi_protocol_id(fwk_id_t protocol_id, + uint8_t *scmi_protocol_id) +{ + int status; + + status = fwk_module_check_call(protocol_id); + if (status != FWK_SUCCESS) + return status; + + *scmi_protocol_id = SCMI_PROTOCOL_ID_CLOCK; + + return FWK_SUCCESS; +} + +static int scmi_clock_message_handler(fwk_id_t protocol_id, fwk_id_t service_id, + const uint32_t *payload, size_t payload_size, unsigned int message_id) +{ + int status; + int32_t return_value; + + static_assert(FWK_ARRAY_SIZE(handler_table) == + FWK_ARRAY_SIZE(payload_size_table), + "[SCMI] Clock management protocol table sizes not consistent"); + assert(payload != NULL); + + status = fwk_module_check_call(protocol_id); + if (status != FWK_SUCCESS) + return status; + + if (message_id >= FWK_ARRAY_SIZE(handler_table)) { + return_value = SCMI_NOT_SUPPORTED; + goto error; + } + + if (payload_size != payload_size_table[message_id]) { + return_value = SCMI_PROTOCOL_ERROR; + goto error; + } + + return handler_table[message_id](service_id, payload); + +error: + scmi_clock_ctx.scmi_api->respond(service_id, + &return_value, sizeof(return_value)); + return FWK_SUCCESS; +} + +static struct mod_scmi_to_protocol_api scmi_clock_mod_scmi_to_protocol_api = { + .get_scmi_protocol_id = scmi_clock_get_scmi_protocol_id, + .message_handler = scmi_clock_message_handler +}; + +/* + * Framework handlers + */ + +static int scmi_clock_init(fwk_id_t module_id, unsigned int element_count, + const void *data) +{ + const struct mod_scmi_clock_config *config = + (const struct mod_scmi_clock_config *)data; + + if ((config == NULL) || (config->agent_table == NULL)) + return FWK_E_PARAM; + + scmi_clock_ctx.config = config; + scmi_clock_ctx.max_pending_transactions = config->max_pending_transactions; + scmi_clock_ctx.agent_table = config->agent_table; + + return FWK_SUCCESS; +} + +static int scmi_clock_bind(fwk_id_t id, unsigned int round) +{ + int status; + + if (round == 1) + return FWK_SUCCESS; + + status = fwk_module_bind(FWK_ID_MODULE(FWK_MODULE_IDX_SCMI), + FWK_ID_API(FWK_MODULE_IDX_SCMI, MOD_SCMI_API_IDX_PROTOCOL), + &scmi_clock_ctx.scmi_api); + if (status != FWK_SUCCESS) + return status; + + return fwk_module_bind(FWK_ID_MODULE(FWK_MODULE_IDX_CLOCK), + FWK_ID_API(FWK_MODULE_IDX_CLOCK, 0), &scmi_clock_ctx.clock_api); +} + +static int scmi_clock_process_bind_request(fwk_id_t source_id, + fwk_id_t target_id, fwk_id_t api_id, const void **api) +{ + if (!fwk_id_is_equal(source_id, FWK_ID_MODULE(FWK_MODULE_IDX_SCMI))) + return FWK_E_ACCESS; + + *api = &scmi_clock_mod_scmi_to_protocol_api; + + return FWK_SUCCESS; +} + +/* SCMI Clock Management Protocol Definition */ +const struct fwk_module module_scmi_clock = { + .name = "SCMI Clock Management Protocol", + .api_count = 1, + .type = FWK_MODULE_TYPE_PROTOCOL, + .init = scmi_clock_init, + .bind = scmi_clock_bind, + .process_bind_request = scmi_clock_process_bind_request, +}; diff --git a/module/scmi_perf/include/internal/scmi_perf.h b/module/scmi_perf/include/internal/scmi_perf.h new file mode 100644 index 00000000..b3690997 --- /dev/null +++ b/module/scmi_perf/include/internal/scmi_perf.h @@ -0,0 +1,248 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * SCMI performance domain management protocol support. + */ + +#ifndef SCMI_PERF_H +#define SCMI_PERF_H + +#define SCMI_PROTOCOL_ID_PERF UINT32_C(0x13) +#define SCMI_PROTOCOL_VERSION_PERF UINT32_C(0x10000) + +#define SCMI_PERF_SUPPORTS_STATS_SHARED_MEM_REGION 0 +#define SCMI_PERF_STATS_SHARED_MEM_REGION_ADDR_LOW 0 +#define SCMI_PERF_STATS_SHARED_MEM_REGION_ADDR_HIGH 0 +#define SCMI_PERF_STATS_SHARED_MEM_REGION_LENGTH 0 + +/* + * Identifier of the SCMI Performance Domain Management Protocol commands + */ + +enum scmi_perf_command_id { + SCMI_PERF_DOMAIN_ATTRIBUTES = 0x003, + SCMI_PERF_DESCRIBE_LEVELS = 0x004, + SCMI_PERF_LIMITS_SET = 0x005, + SCMI_PERF_LIMITS_GET = 0x006, + SCMI_PERF_LEVEL_SET = 0x007, + SCMI_PERF_LEVEL_GET = 0x008, + SCMI_PERF_NOTIFY_LIMITS = 0x009, + SCMI_PERF_NOTIFY_LEVEL = 0x00A +}; + +/* + * PROTOCOL_ATTRIBUTES + */ + +#define SCMI_PERF_PROTOCOL_ATTRIBUTES_POWER_MW_POS 16 +#define SCMI_PERF_PROTOCOL_ATTRIBUTES_NUM_DOMAINS_POS 0 + +#define SCMI_PERF_PROTOCOL_ATTRIBUTES_POWER_MW_MASK \ + (UINT32_C(0x1) << SCMI_PERF_PROTOCOL_ATTRIBUTES_POWER_MW_POS) +#define SCMI_PERF_PROTOCOL_ATTRIBUTES_NUM_DOMAINS_MASK \ + (UINT32_C(0xFFFF) << SCMI_PERF_PROTOCOL_ATTRIBUTES_NUM_DOMAINS_POS) + +#define SCMI_PERF_PROTOCOL_ATTRIBUTES(POWER_MW, NUM_DOMAINS) \ + ( \ + (((POWER_MW) << SCMI_PERF_PROTOCOL_ATTRIBUTES_POWER_MW_POS) & \ + SCMI_PERF_PROTOCOL_ATTRIBUTES_POWER_MW_MASK) | \ + (((NUM_DOMAINS) << SCMI_PERF_PROTOCOL_ATTRIBUTES_NUM_DOMAINS_POS) & \ + SCMI_PERF_PROTOCOL_ATTRIBUTES_NUM_DOMAINS_MASK) \ + ) + +struct __attribute((packed)) scmi_perf_protocol_attributes_p2a { + int32_t status; + uint32_t attributes; + uint32_t statistics_address_low; + uint32_t statistics_address_high; + uint32_t statistics_len; +}; + +/* + * PERFORMANCE_DOMAIN_ATTRIBUTES + */ + +#define SCMI_PERF_DOMAIN_ATTRIBUTES_CAN_SET_LIMITS_POS 31 +#define SCMI_PERF_DOMAIN_ATTRIBUTES_CAN_SET_LEVEL_POS 30 +#define SCMI_PERF_DOMAIN_ATTRIBUTES_LIMITS_NOTIFY_POS 29 +#define SCMI_PERF_DOMAIN_ATTRIBUTES_LEVEL_NOTIFY_POS 28 + +#define SCMI_PERF_DOMAIN_ATTRIBUTES_CAN_SET_LIMITS_MASK \ + (UINT32_C(0x1) << SCMI_PERF_DOMAIN_ATTRIBUTES_CAN_SET_LIMITS_POS) +#define SCMI_PERF_DOMAIN_ATTRIBUTES_CAN_SET_LEVEL_MASK \ + (UINT32_C(0x1) << SCMI_PERF_DOMAIN_ATTRIBUTES_CAN_SET_LEVEL_POS) +#define SCMI_PERF_DOMAIN_ATTRIBUTES_LIMITS_NOTIFY_MASK \ + (UINT32_C(0x1) << SCMI_PERF_DOMAIN_ATTRIBUTES_LIMITS_NOTIFY_POS) +#define SCMI_PERF_DOMAIN_ATTRIBUTES_LEVEL_NOTIFY_MASK \ + (UINT32_C(0x1) << SCMI_PERF_DOMAIN_ATTRIBUTES_LEVEL_NOTIFY_POS) + +#define SCMI_PERF_DOMAIN_ATTRIBUTES(LEVEL_NOTIFY, LIMITS_NOTIFY, \ + CAN_SET_LEVEL, CAN_SET_LIMITS) \ + ( \ + (((LEVEL_NOTIFY) << \ + SCMI_PERF_DOMAIN_ATTRIBUTES_LEVEL_NOTIFY_POS) & \ + SCMI_PERF_DOMAIN_ATTRIBUTES_LEVEL_NOTIFY_MASK) | \ + (((LIMITS_NOTIFY) << \ + SCMI_PERF_DOMAIN_ATTRIBUTES_LIMITS_NOTIFY_POS) & \ + SCMI_PERF_DOMAIN_ATTRIBUTES_LIMITS_NOTIFY_MASK) | \ + (((CAN_SET_LEVEL) << \ + SCMI_PERF_DOMAIN_ATTRIBUTES_CAN_SET_LEVEL_POS) & \ + SCMI_PERF_DOMAIN_ATTRIBUTES_CAN_SET_LEVEL_MASK) | \ + (((CAN_SET_LIMITS) << \ + SCMI_PERF_DOMAIN_ATTRIBUTES_CAN_SET_LIMITS_POS) & \ + SCMI_PERF_DOMAIN_ATTRIBUTES_CAN_SET_LIMITS_MASK) \ + ) + +struct __attribute((packed)) scmi_perf_domain_attributes_a2p { + uint32_t domain_id; +}; + +#define SCMI_PERF_DOMAIN_RATE_LIMIT_POS 0 +#define SCMI_PERF_DOMAIN_RATE_LIMIT_MASK \ + (UINT32_C(0xFFFFF) << SCMI_PERF_DOMAIN_RATE_LIMIT_POS) + +struct __attribute((packed)) scmi_perf_domain_attributes_p2a { + int32_t status; + uint32_t attributes; + uint32_t rate_limit; + uint32_t sustained_freq; + uint32_t sustained_perf_level; + uint8_t name[16]; +}; + +/* + * PERFORMANCE_DESCRIBE_LEVELS + */ + +#define SCMI_PERF_LEVELS_MAX(MAILBOX_SIZE) \ + ((sizeof(struct scmi_perf_describe_levels_p2a) < MAILBOX_SIZE) ? \ + ((MAILBOX_SIZE - sizeof(struct scmi_perf_describe_levels_p2a)) \ + / sizeof(struct scmi_perf_level)) : 0) + +#define SCMI_PERF_LEVEL_ATTRIBUTES_POS 0 +#define SCMI_PERF_LEVEL_ATTRIBUTES_MASK \ + (UINT32_C(0xFFFF) << SCMI_PERF_LEVEL_ATTRIBUTES_POS) + +#define SCMI_PERF_LEVEL_ATTRIBUTES(LATENCY) \ + (((LATENCY) << SCMI_PERF_LEVEL_ATTRIBUTES_POS) & \ + SCMI_PERF_LEVEL_ATTRIBUTES_MASK) + +struct __attribute((packed)) scmi_perf_level { + uint32_t performance_level; + uint32_t power_cost; + uint32_t attributes; +}; + +struct __attribute((packed)) scmi_perf_describe_levels_a2p { + uint32_t domain_id; + uint32_t level_index; +}; + +#define SCMI_PERF_NUM_LEVELS_REMAINING_LEVELS_POS 16 +#define SCMI_PERF_NUM_LEVELS_NUM_LEVELS_POS 0 + +#define SCMI_PERF_NUM_LEVELS_REMAINING_LEVELS_MASK \ + (UINT32_C(0xFFFF) << SCMI_PERF_NUM_LEVELS_REMAINING_LEVELS_POS) +#define SCMI_PERF_NUM_LEVELS_NUM_LEVELS_MASK \ + (UINT32_C(0xFFF) << SCMI_PERF_NUM_LEVELS_NUM_LEVELS_POS) + +#define SCMI_PERF_NUM_LEVELS(NUM_LEVELS, REMAINING_LEVELS) \ + ((((NUM_LEVELS) << SCMI_PERF_NUM_LEVELS_NUM_LEVELS_POS) & \ + SCMI_PERF_NUM_LEVELS_NUM_LEVELS_MASK) | \ + (((REMAINING_LEVELS) << SCMI_PERF_NUM_LEVELS_REMAINING_LEVELS_POS) & \ + SCMI_PERF_NUM_LEVELS_REMAINING_LEVELS_MASK)) + +struct __attribute((packed)) scmi_perf_describe_levels_p2a { + int32_t status; + uint32_t num_levels; + + struct scmi_perf_level perf_levels[]; +}; + +/* + * PERFORMANCE_LIMITS_SET + */ + +struct __attribute((packed)) scmi_perf_limits_set_a2p { + uint32_t domain_id; + uint32_t range_max; + uint32_t range_min; +}; + +struct __attribute((packed)) scmi_perf_limits_set_p2a { + int32_t status; +}; + +/* + * PERFORMANCE_LIMITS_GET + */ + +struct __attribute((packed)) scmi_perf_limits_get_a2p { + uint32_t domain_id; +}; + +struct __attribute((packed)) scmi_perf_limits_get_p2a { + int32_t status; + uint32_t range_max; + uint32_t range_min; +}; + +/* + * PERFORMANCE_LEVEL_SET + */ + +struct __attribute((packed)) scmi_perf_level_set_a2p { + uint32_t domain_id; + uint32_t performance_level; +}; + +struct __attribute((packed)) scmi_perf_level_set_p2a { + int32_t status; +}; + +/* + * PERFORMANCE_LEVEL_GET + */ + +struct __attribute((packed)) scmi_perf_level_get_a2p { + uint32_t domain_id; +}; + +struct __attribute((packed)) scmi_perf_level_get_p2a { + int32_t status; + uint32_t performance_level; +}; + +/* + * PERFORMANCE_NOTIFY_LIMITS + */ + +struct __attribute((packed)) scmi_perf_notify_limits_a2p { + uint32_t domain_id; + uint32_t notify_enable; +}; + +struct __attribute((packed)) scmi_perf_notify_limits_p2a { + int32_t status; +}; + +/* + * PERFORMANCE_NOTIFY_LEVEL + */ + +struct __attribute((packed)) scmi_perf_notify_level_a2p { + uint32_t domain_id; + uint32_t notify_enable; +}; + +struct __attribute((packed)) scmi_perf_notify_level_p2a { + int32_t status; +}; + +extern struct scp_scmi_protocol scmi_perf_protocol; + +#endif /* SCMI_PERF_H */ diff --git a/module/scmi_perf/include/mod_scmi_perf.h b/module/scmi_perf/include/mod_scmi_perf.h new file mode 100644 index 00000000..b9510106 --- /dev/null +++ b/module/scmi_perf/include/mod_scmi_perf.h @@ -0,0 +1,63 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * SCMI performance domain management protocol support. + */ + +#ifndef MOD_SCMI_PERF_H +#define MOD_SCMI_PERF_H + +#include <stddef.h> + +/*! + * \addtogroup GroupModules Modules + * @{ + */ + +/*! + * \defgroup GroupSCMI_PERF SCMI Performance Domain Management Protocol + * @{ + */ + +/*! + * \brief Agent permissions. + */ +enum mod_scmi_perf_permissions { + /*! No permissions */ + MOD_SCMI_PERF_PERMS_NONE = 0, + + /*! Permission to set performance level */ + MOD_SCMI_PERF_PERMS_SET_LEVEL = (1 << 0), + + /*! Permission to set performance limits */ + MOD_SCMI_PERF_PERMS_SET_LIMITS = (1 << 1), +}; + +/*! + * \brief Performance domain configuration data. + */ +struct mod_scmi_perf_domain_config { + const uint32_t (*permissions)[]; /*!< Per-agent permission flags */ +}; + +/*! + * \brief SCMI Performance Domain Management Protocol configuration data. + */ +struct mod_scmi_perf_config { + /*! Per-domain configuration data */ + const struct mod_scmi_perf_domain_config (*domains)[]; +}; + +/*! + * @} + */ + +/*! + * @} + */ + +#endif /* SCP_SCMI_PERF_H */ diff --git a/module/scmi_perf/src/Makefile b/module/scmi_perf/src/Makefile new file mode 100644 index 00000000..4c57efdd --- /dev/null +++ b/module/scmi_perf/src/Makefile @@ -0,0 +1,11 @@ +# +# Arm SCP/MCP Software +# Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +BS_LIB_NAME := SCMI Performance Protocol +BS_LIB_SOURCES := mod_scmi_perf.c + +include $(BS_DIR)/lib.mk diff --git a/module/scmi_perf/src/mod_scmi_perf.c b/module/scmi_perf/src/mod_scmi_perf.c new file mode 100644 index 00000000..5bcb0a99 --- /dev/null +++ b/module/scmi_perf/src/mod_scmi_perf.c @@ -0,0 +1,634 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * SCMI performance domain management protocol support. + */ + +#include <assert.h> +#include <string.h> +#include <fwk_errno.h> +#include <fwk_macros.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <internal/scmi.h> +#include <internal/scmi_perf.h> +#include <mod_dvfs.h> +#include <mod_scmi.h> +#include <mod_scmi_perf.h> + +struct scmi_perf_ctx { + /* SCMI Performance Module Configuration */ + const struct mod_scmi_perf_config *config; + + /* Number of power domains */ + unsigned int domain_count; + + /* SCMI module API */ + const struct mod_scmi_from_protocol_api *scmi_api; + + /* DVFS module API */ + const struct mod_dvfs_domain_api *dvfs_api; +}; + +static int scmi_perf_protocol_version_handler( + fwk_id_t service_id, const uint32_t *payload); +static int scmi_perf_protocol_attributes_handler( + fwk_id_t service_id, const uint32_t *payload); +static int scmi_perf_protocol_message_attributes_handler( + fwk_id_t service_id, const uint32_t *payload); +static int scmi_perf_domain_attributes_handler( + fwk_id_t service_id, const uint32_t *payload); +static int scmi_perf_describe_levels_handler( + fwk_id_t service_id, const uint32_t *payload); +static int scmi_perf_level_set_handler( + fwk_id_t service_id, const uint32_t *payload); +static int scmi_perf_level_get_handler( + fwk_id_t service_id, const uint32_t *payload); +static int scmi_perf_limits_set_handler( + fwk_id_t service_id, const uint32_t *payload); +static int scmi_perf_limits_get_handler( + fwk_id_t service_id, const uint32_t *payload); + +static struct scmi_perf_ctx scmi_perf_ctx; + +static int (*handler_table[])(fwk_id_t, const uint32_t *) = { + [SCMI_PROTOCOL_VERSION] = + scmi_perf_protocol_version_handler, + [SCMI_PROTOCOL_ATTRIBUTES] = + scmi_perf_protocol_attributes_handler, + [SCMI_PROTOCOL_MESSAGE_ATTRIBUTES] = + scmi_perf_protocol_message_attributes_handler, + [SCMI_PERF_DOMAIN_ATTRIBUTES] = + scmi_perf_domain_attributes_handler, + [SCMI_PERF_DESCRIBE_LEVELS] = + scmi_perf_describe_levels_handler, + [SCMI_PERF_LIMITS_SET] = + scmi_perf_limits_set_handler, + [SCMI_PERF_LIMITS_GET] = + scmi_perf_limits_get_handler, + [SCMI_PERF_LEVEL_SET] = + scmi_perf_level_set_handler, + [SCMI_PERF_LEVEL_GET] = + scmi_perf_level_get_handler +}; + +static unsigned int payload_size_table[] = { + [SCMI_PROTOCOL_VERSION] = 0, + [SCMI_PROTOCOL_ATTRIBUTES] = 0, + [SCMI_PROTOCOL_MESSAGE_ATTRIBUTES] = + sizeof(struct scmi_protocol_message_attributes_a2p), + [SCMI_PERF_DOMAIN_ATTRIBUTES] = + sizeof(struct scmi_perf_domain_attributes_a2p), + [SCMI_PERF_DESCRIBE_LEVELS] = + sizeof(struct scmi_perf_describe_levels_a2p), + [SCMI_PERF_LEVEL_SET] = + sizeof(struct scmi_perf_level_set_a2p), + [SCMI_PERF_LEVEL_GET] = + sizeof(struct scmi_perf_level_get_a2p), + [SCMI_PERF_LIMITS_SET] = + sizeof(struct scmi_perf_limits_set_a2p), + [SCMI_PERF_LIMITS_GET] = + sizeof(struct scmi_perf_limits_get_a2p), +}; + +/* + * Protocol command handlers + */ + +static int scmi_perf_protocol_version_handler(fwk_id_t service_id, + const uint32_t *payload) +{ + struct scmi_protocol_version_p2a return_values = { + .status = SCMI_SUCCESS, + .version = SCMI_PROTOCOL_VERSION_PERF, + }; + + scmi_perf_ctx.scmi_api->respond(service_id, &return_values, + sizeof(return_values)); + + return FWK_SUCCESS; +} + +static int scmi_perf_protocol_attributes_handler(fwk_id_t service_id, + const uint32_t *payload) +{ + struct scmi_perf_protocol_attributes_p2a return_values = { + .status = SCMI_SUCCESS, + .attributes = + SCMI_PERF_PROTOCOL_ATTRIBUTES(true, scmi_perf_ctx.domain_count), + .statistics_len = 0, /* Unsupported */ + .statistics_address_low = 0, /* Unsupported */ + .statistics_address_high = 0, /* Unsupported */ + }; + + scmi_perf_ctx.scmi_api->respond(service_id, &return_values, + sizeof(return_values)); + + return FWK_SUCCESS; +} + +static int scmi_perf_protocol_message_attributes_handler(fwk_id_t service_id, + const uint32_t *payload) +{ + const struct scmi_protocol_message_attributes_a2p *parameters; + unsigned int message_id; + struct scmi_protocol_message_attributes_p2a return_values; + + parameters = + (const struct scmi_protocol_message_attributes_a2p *)payload; + message_id = parameters->message_id; + + if ((message_id < FWK_ARRAY_SIZE(handler_table)) && + (handler_table[message_id] != NULL)) { + return_values = (struct scmi_protocol_message_attributes_p2a) { + .status = SCMI_SUCCESS, + .attributes = 0, /* All commands have an attributes value of 0 */ + }; + } else + return_values.status = SCMI_NOT_FOUND; + + scmi_perf_ctx.scmi_api->respond(service_id, &return_values, + (return_values.status == SCMI_SUCCESS) ? + sizeof(return_values) : sizeof(return_values.status)); + + return FWK_SUCCESS; +} + +static int scmi_perf_domain_attributes_handler(fwk_id_t service_id, + const uint32_t *payload) +{ + int status; + unsigned int agent_id; + const struct mod_scmi_perf_domain_config *domain; + const struct scmi_perf_domain_attributes_a2p *parameters; + struct scmi_perf_domain_attributes_p2a return_values; + uint32_t permissions; + fwk_id_t domain_id; + struct mod_dvfs_opp opp; + + return_values.status = SCMI_GENERIC_ERROR; + + /* Validate the domain identifier */ + parameters = (const struct scmi_perf_domain_attributes_a2p *)payload; + if (parameters->domain_id >= scmi_perf_ctx.domain_count) { + status = FWK_SUCCESS; + return_values.status = SCMI_NOT_FOUND; + + goto exit; + } + + status = scmi_perf_ctx.scmi_api->get_agent_id(service_id, &agent_id); + if (status != FWK_SUCCESS) + goto exit; + + domain = &(*scmi_perf_ctx.config->domains)[parameters->domain_id]; + permissions = (*domain->permissions)[agent_id]; + + domain_id = FWK_ID_ELEMENT(FWK_MODULE_IDX_DVFS, parameters->domain_id), + status = scmi_perf_ctx.dvfs_api->get_sustained_opp(domain_id, &opp); + if (status != FWK_SUCCESS) + goto exit; + + return_values = (struct scmi_perf_domain_attributes_p2a) { + .status = SCMI_SUCCESS, + .attributes = SCMI_PERF_DOMAIN_ATTRIBUTES( + false, false, + !!(permissions & MOD_SCMI_PERF_PERMS_SET_LEVEL), + !!(permissions & MOD_SCMI_PERF_PERMS_SET_LIMITS) + ), + .rate_limit = 0, /* Unsupported */ + .sustained_freq = opp.frequency / FWK_KHZ, + .sustained_perf_level = opp.frequency, + }; + + /* Copy the domain name into the mailbox */ + strncpy((char *)return_values.name, fwk_module_get_name(domain_id), + sizeof(return_values.name) - 1); + +exit: + scmi_perf_ctx.scmi_api->respond(service_id, &return_values, + (return_values.status == SCMI_SUCCESS) ? + sizeof(return_values) : sizeof(return_values.status)); + + return status; +} + +static int scmi_perf_describe_levels_handler(fwk_id_t service_id, + const uint32_t *payload) +{ + int status; + size_t max_payload_size; + const struct scmi_perf_describe_levels_a2p *parameters; + struct scmi_perf_describe_levels_p2a return_values; + fwk_id_t domain_id; + struct scmi_perf_level perf_level; + unsigned int num_levels, level_index, level_index_max; + size_t payload_size; + size_t opp_count; + struct mod_dvfs_opp opp; + uint16_t latency; + + return_values.status = SCMI_GENERIC_ERROR; + payload_size = sizeof(return_values); + + status = scmi_perf_ctx.scmi_api->get_max_payload_size(service_id, + &max_payload_size); + if (status != FWK_SUCCESS) + goto exit; + + status = (SCMI_PERF_LEVELS_MAX(max_payload_size) > 0) ? + FWK_SUCCESS : FWK_E_SIZE; + assert(status == FWK_SUCCESS); + if (status != FWK_SUCCESS) + goto exit; + + + /* Validate the domain identifier */ + parameters = (const struct scmi_perf_describe_levels_a2p *)payload; + if (parameters->domain_id >= scmi_perf_ctx.domain_count) { + return_values.status = SCMI_NOT_FOUND; + + goto exit; + } + + /* Get the number of operating points for the domain */ + domain_id = FWK_ID_ELEMENT(FWK_MODULE_IDX_DVFS, parameters->domain_id); + status = scmi_perf_ctx.dvfs_api->get_opp_count(domain_id, &opp_count); + if (status != FWK_SUCCESS) + goto exit; + + /* Validate level index */ + level_index = parameters->level_index; + if (level_index >= opp_count) { + return_values.status = SCMI_INVALID_PARAMETERS; + + goto exit; + } + + /* Identify the maximum number of performance levels we can send at once */ + num_levels = + (SCMI_PERF_LEVELS_MAX(max_payload_size) < + (opp_count - level_index)) ? + SCMI_PERF_LEVELS_MAX(max_payload_size) : + (opp_count - level_index); + level_index_max = (level_index + num_levels - 1); + + status = scmi_perf_ctx.dvfs_api->get_latency(domain_id, &latency); + if (status != FWK_SUCCESS) + goto exit; + + /* Copy DVFS data into returned data structure */ + for (; level_index <= level_index_max; level_index++, + payload_size += sizeof(perf_level)) { + + status = scmi_perf_ctx.dvfs_api->get_nth_opp( + domain_id, level_index, &opp); + if (status != FWK_SUCCESS) + goto exit; + + perf_level.power_cost = opp.voltage; + perf_level.performance_level = opp.frequency; + perf_level.attributes = latency; + + status = scmi_perf_ctx.scmi_api->write_payload(service_id, payload_size, + &perf_level, sizeof(perf_level)); + if (status != FWK_SUCCESS) + goto exit; + } + + return_values = (struct scmi_perf_describe_levels_p2a) { + .status = SCMI_SUCCESS, + .num_levels = SCMI_PERF_NUM_LEVELS(num_levels, + (opp_count - level_index_max - 1)) + }; + + status = scmi_perf_ctx.scmi_api->write_payload(service_id, 0, + &return_values, sizeof(return_values)); + +exit: + scmi_perf_ctx.scmi_api->respond(service_id, + (return_values.status == SCMI_SUCCESS) ? + NULL : &return_values.status, + (return_values.status == SCMI_SUCCESS) ? + payload_size : sizeof(return_values.status)); + + return status; +} + +static int scmi_perf_limits_set_handler(fwk_id_t service_id, + const uint32_t *payload) +{ + int status; + unsigned int agent_id; + const struct mod_scmi_perf_domain_config *domain; + const struct scmi_perf_limits_set_a2p *parameters; + struct scmi_perf_limits_set_p2a return_values; + uint32_t permissions; + + return_values.status = SCMI_GENERIC_ERROR; + + parameters = (const struct scmi_perf_limits_set_a2p *)payload; + if (parameters->domain_id >= scmi_perf_ctx.domain_count) { + status = FWK_SUCCESS; + return_values.status = SCMI_NOT_FOUND; + + goto exit; + } + + status = scmi_perf_ctx.scmi_api->get_agent_id(service_id, &agent_id); + if (status != FWK_SUCCESS) + goto exit; + + /* Ensure the agent has permission to do this */ + domain = &(*scmi_perf_ctx.config->domains)[parameters->domain_id]; + permissions = (*domain->permissions)[agent_id]; + if (!(permissions & MOD_SCMI_PERF_PERMS_SET_LIMITS)) { + return_values.status = SCMI_DENIED; + + goto exit; + } + + if (parameters->range_min > parameters->range_max) { + return_values.status = SCMI_INVALID_PARAMETERS; + + goto exit; + } + + /* Execute the transition asynchronously */ + status = scmi_perf_ctx.dvfs_api->set_frequency_limits( + FWK_ID_ELEMENT(FWK_MODULE_IDX_DVFS, parameters->domain_id), + &((struct mod_dvfs_frequency_limits) { + .minimum = parameters->range_min, + .maximum = parameters->range_max + })); + if (status != FWK_SUCCESS) { + status = FWK_SUCCESS; + return_values.status = SCMI_OUT_OF_RANGE; + + goto exit; + } + + return_values = (struct scmi_perf_limits_set_p2a) { + .status = SCMI_SUCCESS, + }; + +exit: + scmi_perf_ctx.scmi_api->respond(service_id, &return_values, + (return_values.status == SCMI_SUCCESS) ? + sizeof(return_values) : sizeof(return_values.status)); + + return status; +} + +static int scmi_perf_limits_get_handler(fwk_id_t service_id, + const uint32_t *payload) +{ + int status; + const struct scmi_perf_limits_get_a2p *parameters; + struct scmi_perf_limits_get_p2a return_values; + struct mod_dvfs_frequency_limits limits; + + return_values.status = SCMI_GENERIC_ERROR; + + parameters = (const struct scmi_perf_limits_get_a2p *)payload; + if (parameters->domain_id >= scmi_perf_ctx.domain_count) { + status = FWK_SUCCESS; + return_values.status = SCMI_NOT_FOUND; + + goto exit; + } + + status = scmi_perf_ctx.dvfs_api->get_frequency_limits( + FWK_ID_ELEMENT(FWK_MODULE_IDX_DVFS, parameters->domain_id), &limits); + if (status != FWK_SUCCESS) + goto exit; + + return_values = (struct scmi_perf_limits_get_p2a) { + .status = SCMI_SUCCESS, + .range_min = (uint32_t)limits.minimum, + .range_max = (uint32_t)limits.maximum + }; + +exit: + scmi_perf_ctx.scmi_api->respond(service_id, &return_values, + (return_values.status == SCMI_SUCCESS) ? + sizeof(return_values) : sizeof(return_values.status)); + + return status; +} + +static int scmi_perf_level_set_handler(fwk_id_t service_id, + const uint32_t *payload) +{ + int status; + unsigned int agent_id; + const struct mod_scmi_perf_domain_config *domain; + const struct scmi_perf_level_set_a2p *parameters; + struct scmi_perf_level_set_p2a return_values; + uint32_t permissions; + + return_values.status = SCMI_GENERIC_ERROR; + + parameters = (const struct scmi_perf_level_set_a2p *)payload; + if (parameters->domain_id >= scmi_perf_ctx.domain_count) { + status = FWK_SUCCESS; + return_values.status = SCMI_NOT_FOUND; + + goto exit; + } + + status = scmi_perf_ctx.scmi_api->get_agent_id(service_id, &agent_id); + if (status != FWK_SUCCESS) + goto exit; + + /* Ensure the agent has permission to do this */ + domain = &(*scmi_perf_ctx.config->domains)[parameters->domain_id]; + permissions = (*domain->permissions)[agent_id]; + if (!(permissions & MOD_SCMI_PERF_PERMS_SET_LEVEL)) { + return_values.status = SCMI_DENIED; + + goto exit; + } + + /* Execute the transition synchronously */ + status = scmi_perf_ctx.dvfs_api->set_frequency( + FWK_ID_ELEMENT(FWK_MODULE_IDX_DVFS, parameters->domain_id), + parameters->performance_level); + + if (status == FWK_E_RANGE) { + status = FWK_SUCCESS; + return_values.status = SCMI_OUT_OF_RANGE; + } else if (status != FWK_SUCCESS) { + return_values.status = SCMI_GENERIC_ERROR; + } else { + return_values = (struct scmi_perf_level_set_p2a) { + .status = SCMI_SUCCESS, + }; + } + +exit: + scmi_perf_ctx.scmi_api->respond(service_id, &return_values, + (return_values.status == SCMI_SUCCESS) ? + sizeof(return_values) : sizeof(return_values.status)); + + return status; +} + +static int scmi_perf_level_get_handler(fwk_id_t service_id, + const uint32_t *payload) +{ + int status; + const struct scmi_perf_level_get_a2p *parameters; + struct scmi_perf_level_get_p2a return_values; + struct mod_dvfs_opp opp; + + return_values.status = SCMI_GENERIC_ERROR; + + parameters = (const struct scmi_perf_level_get_a2p *)payload; + if (parameters->domain_id >= scmi_perf_ctx.domain_count) { + status = FWK_SUCCESS; + return_values.status = SCMI_NOT_FOUND; + + goto exit; + } + + status = scmi_perf_ctx.dvfs_api->get_current_opp( + FWK_ID_ELEMENT(FWK_MODULE_IDX_DVFS, parameters->domain_id), &opp); + if (status != FWK_SUCCESS) + goto exit; + + /* Return the frequency as the performance level */ + return_values = (struct scmi_perf_level_get_p2a) { + .status = SCMI_SUCCESS, + .performance_level = (uint32_t)opp.frequency, + }; + +exit: + scmi_perf_ctx.scmi_api->respond(service_id, &return_values, + (return_values.status == SCMI_SUCCESS) ? + sizeof(return_values) : sizeof(return_values.status)); + + return status; +} + +/* + * SCMI module -> SCMI performance module interface + */ + +static int scmi_perf_get_scmi_protocol_id(fwk_id_t protocol_id, + uint8_t *scmi_protocol_id) +{ + int status; + + status = fwk_module_check_call(protocol_id); + if (status != FWK_SUCCESS) + return status; + + *scmi_protocol_id = SCMI_PROTOCOL_ID_PERF; + + return FWK_SUCCESS; +} + +static int scmi_perf_message_handler(fwk_id_t protocol_id, fwk_id_t service_id, + const uint32_t *payload, size_t payload_size, unsigned int message_id) +{ + int status; + int32_t return_value; + + static_assert(FWK_ARRAY_SIZE(handler_table) == + FWK_ARRAY_SIZE(payload_size_table), + "[SCMI] Performance management protocol table sizes not consistent"); + assert(payload != NULL); + + status = fwk_module_check_call(protocol_id); + if (status != FWK_SUCCESS) + return status; + + if (message_id >= FWK_ARRAY_SIZE(handler_table)) { + return_value = SCMI_NOT_SUPPORTED; + goto error; + } + + if (payload_size != payload_size_table[message_id]) { + return_value = SCMI_PROTOCOL_ERROR; + goto error; + } + + return handler_table[message_id](service_id, payload); + +error: + scmi_perf_ctx.scmi_api->respond(service_id, &return_value, + sizeof(return_value)); + + return FWK_SUCCESS; +} + +static struct mod_scmi_to_protocol_api scmi_perf_mod_scmi_to_protocol_api = { + .get_scmi_protocol_id = scmi_perf_get_scmi_protocol_id, + .message_handler = scmi_perf_message_handler +}; + +/* + * Framework handlers + */ +static int scmi_perf_init(fwk_id_t module_id, unsigned int element_count, + const void *data) +{ + int return_val; + const struct mod_scmi_perf_config *config = + (const struct mod_scmi_perf_config *)data; + + if ((config == NULL) || (config->domains == NULL)) + return FWK_E_PARAM; + + return_val = fwk_module_get_element_count( + FWK_ID_MODULE(FWK_MODULE_IDX_DVFS)); + if (return_val <= 0) + return FWK_E_SUPPORT; + + scmi_perf_ctx.config = config; + scmi_perf_ctx.domain_count = return_val; + + return FWK_SUCCESS; +} + +static int scmi_perf_bind(fwk_id_t id, unsigned int round) +{ + int status; + + if (round == 1) + return FWK_SUCCESS; + + status = fwk_module_bind(FWK_ID_MODULE(FWK_MODULE_IDX_SCMI), + FWK_ID_API(FWK_MODULE_IDX_SCMI, MOD_SCMI_API_IDX_PROTOCOL), + &scmi_perf_ctx.scmi_api); + if (status != FWK_SUCCESS) + return status; + + return fwk_module_bind(FWK_ID_MODULE(FWK_MODULE_IDX_DVFS), + FWK_ID_API(FWK_MODULE_IDX_DVFS, 0), &scmi_perf_ctx.dvfs_api); +} + +static int scmi_perf_process_bind_request(fwk_id_t source_id, + fwk_id_t target_id, fwk_id_t api_id, const void **api) +{ + if (!fwk_id_is_equal(source_id, FWK_ID_MODULE(FWK_MODULE_IDX_SCMI))) + return FWK_E_ACCESS; + + *api = &scmi_perf_mod_scmi_to_protocol_api; + + return FWK_SUCCESS; +} +/* SCMI Performance Management Protocol Definition */ +const struct fwk_module module_scmi_perf = { + .name = "SCMI Performance Management Protocol", + .api_count = 1, + .type = FWK_MODULE_TYPE_PROTOCOL, + .init = scmi_perf_init, + .bind = scmi_perf_bind, + .process_bind_request = scmi_perf_process_bind_request, +}; diff --git a/module/scmi_power_domain/include/internal/scmi_power_domain.h b/module/scmi_power_domain/include/internal/scmi_power_domain.h new file mode 100644 index 00000000..61595f73 --- /dev/null +++ b/module/scmi_power_domain/include/internal/scmi_power_domain.h @@ -0,0 +1,121 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * System Control and Management Interface (SCMI) support. + */ + +#ifndef SCMI_POWER_H +#define SCMI_POWER_H + +/*! + * \addtogroup GroupModules Modules + * @{ + */ + +/*! + * \defgroup GroupSCMI_PERF SCMI Power Domain Management Protocol + * @{ + */ + +#define SCMI_PROTOCOL_ID_POWER_DOMAIN UINT32_C(0x11) +#define SCMI_PROTOCOL_VERSION_POWER_DOMAIN UINT32_C(0x10000) + +#define SCMI_PD_DEVICE_STATE_ID_OFF 0 +#define SCMI_PD_DEVICE_STATE_ID_ON 0 +#define SCMI_PD_DEVICE_STATE_ID_MASK 0xFFFFFFF +#define SCMI_PD_DEVICE_STATE_TYPE (1 << 30) + +/* + * Identifier of the SCMI Power Domain Management Protocol commands + */ +enum scmi_pd_command_id { + SCMI_PD_POWER_DOMAIN_ATTRIBUTES = 0x03, + SCMI_PD_POWER_STATE_SET = 0x04, + SCMI_PD_POWER_STATE_GET = 0x05, + SCMI_PD_POWER_STATE_NOTIFY = 0x06, +}; + +/* + * PROTOCOL_ATTRIBUTES + */ + +struct __attribute((packed)) scmi_pd_protocol_attributes_p2a { + int32_t status; + uint32_t attributes; + uint32_t statistics_address_low; + uint32_t statistics_address_high; + uint32_t statistics_len; +}; + +/* + * POWER_DOMAIN_ATTRIBUTES + */ + +struct __attribute((packed)) scmi_pd_power_domain_attributes_a2p { + uint32_t domain_id; +}; + +#define SCMI_PD_POWER_STATE_SET_ASYNC (1 << 30) +#define SCMI_PD_POWER_STATE_SET_SYNC (1 << 29) + +struct __attribute((packed)) scmi_pd_power_domain_attributes_p2a { + int32_t status; + uint32_t attributes; + uint8_t name[16]; +}; + +/* + * POWER_STATE_SET + */ + +#define SCMI_PD_POWER_STATE_SET_ASYNC_FLAG_MASK (1 << 0) + +struct __attribute((packed)) scmi_pd_power_state_set_a2p { + uint32_t flags; + uint32_t domain_id; + uint32_t power_state; +}; + +struct __attribute((packed)) scmi_pd_power_state_set_p2a { + int32_t status; +}; + +/* + * POWER_STATE_GET + */ + +struct __attribute((packed)) scmi_pd_power_state_get_a2p { + uint32_t domain_id; +}; + +struct __attribute((packed)) scmi_pd_power_state_get_p2a { + int32_t status; + uint32_t power_state; +}; + +/* + * POWER_STATE_NOTIFY + */ + +struct __attribute((packed)) scmi_pd_power_state_notify_a2p { + uint32_t domain_id; + uint32_t notify_enable; +}; + +struct __attribute((packed)) scmi_pd_power_state_notify_p2a { + int32_t status; +}; + +/*! + * @} + */ + +/*! + * @} + */ + +#endif /* SCMI_POWER_DOMAIN_H */ diff --git a/module/scmi_power_domain/src/Makefile b/module/scmi_power_domain/src/Makefile new file mode 100644 index 00000000..727fa169 --- /dev/null +++ b/module/scmi_power_domain/src/Makefile @@ -0,0 +1,11 @@ +# +# Arm SCP/MCP Software +# Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +BS_LIB_NAME := SCMI Power Domain Protocol +BS_LIB_SOURCES := mod_scmi_power_domain.c + +include $(BS_DIR)/lib.mk diff --git a/module/scmi_power_domain/src/mod_scmi_power_domain.c b/module/scmi_power_domain/src/mod_scmi_power_domain.c new file mode 100644 index 00000000..3f9703fc --- /dev/null +++ b/module/scmi_power_domain/src/mod_scmi_power_domain.c @@ -0,0 +1,605 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * SCMI power domain management protocol support. + */ + +#include <assert.h> +#include <string.h> +#include <fwk_element.h> +#include <fwk_errno.h> +#include <fwk_id.h> +#include <fwk_macros.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <internal/scmi.h> +#include <internal/scmi_power_domain.h> +#include <mod_log.h> +#include <mod_power_domain.h> +#include <mod_scmi.h> + +struct scmi_pd_ctx { + /* Number of power domains */ + unsigned int domain_count; + + /* Log module API */ + const struct mod_log_api *log_api; + + /* SCMI module API */ + const struct mod_scmi_from_protocol_api *scmi_api; + + /* Power domain module API */ + const struct mod_pd_restricted_api *pd_api; +}; + +static int scmi_pd_protocol_version_handler(fwk_id_t service_id, + const uint32_t *payload); +static int scmi_pd_protocol_attributes_handler(fwk_id_t service_id, + const uint32_t *payload); +static int scmi_pd_protocol_message_attributes_handler(fwk_id_t service_id, + const uint32_t *payload); +static int scmi_pd_power_domain_attributes_handler(fwk_id_t service_id, + const uint32_t *payload); +static int scmi_pd_power_state_set_handler(fwk_id_t service_id, + const uint32_t *payload); +static int scmi_pd_power_state_get_handler(fwk_id_t service_id, + const uint32_t *payload); + +/* + * Internal variables + */ + +static struct scmi_pd_ctx scmi_pd_ctx; + +static int (*handler_table[])(fwk_id_t, const uint32_t *) = { + [SCMI_PROTOCOL_VERSION] = + scmi_pd_protocol_version_handler, + [SCMI_PROTOCOL_ATTRIBUTES] = + scmi_pd_protocol_attributes_handler, + [SCMI_PROTOCOL_MESSAGE_ATTRIBUTES] = + scmi_pd_protocol_message_attributes_handler, + [SCMI_PD_POWER_DOMAIN_ATTRIBUTES] = + scmi_pd_power_domain_attributes_handler, + [SCMI_PD_POWER_STATE_SET] = + scmi_pd_power_state_set_handler, + [SCMI_PD_POWER_STATE_GET] = + scmi_pd_power_state_get_handler, +}; + +static unsigned int payload_size_table[] = { + [SCMI_PROTOCOL_VERSION] = 0, + [SCMI_PROTOCOL_ATTRIBUTES] = 0, + [SCMI_PROTOCOL_MESSAGE_ATTRIBUTES] = + sizeof(struct scmi_protocol_message_attributes_a2p), + [SCMI_PD_POWER_DOMAIN_ATTRIBUTES] = + sizeof(struct scmi_pd_power_domain_attributes_a2p), + [SCMI_PD_POWER_STATE_SET] = + sizeof(struct scmi_pd_power_state_set_a2p), + [SCMI_PD_POWER_STATE_GET] = + sizeof(struct scmi_pd_power_state_get_a2p), +}; + +static unsigned int scmi_dev_state_id_lost_ctx_to_pd_state[] = { + [SCMI_PD_DEVICE_STATE_ID_OFF] = MOD_PD_STATE_OFF, +}; + +static unsigned int scmi_dev_state_id_preserved_ctx_to_pd_state[] = { + [SCMI_PD_DEVICE_STATE_ID_ON] = MOD_PD_STATE_ON, +}; + +static uint32_t pd_state_to_scmi_dev_state[] = { + [MOD_PD_STATE_OFF] = SCMI_PD_DEVICE_STATE_ID_OFF | + SCMI_PD_DEVICE_STATE_TYPE, + [MOD_PD_STATE_ON] = SCMI_PD_DEVICE_STATE_ID_ON, + /* In case of more supported device states review the map functions */ +}; + +/* + * Power domain management protocol implementation + */ + +static int scmi_device_state_to_pd_state(uint32_t scmi_state, + unsigned int *pd_state) +{ + uint32_t scmi_state_id; + bool ctx_lost; + + ctx_lost = !!(scmi_state & SCMI_PD_DEVICE_STATE_TYPE); + scmi_state_id = scmi_state & SCMI_PD_DEVICE_STATE_ID_MASK; + + if (ctx_lost) { + if (scmi_state_id >= + FWK_ARRAY_SIZE(scmi_dev_state_id_lost_ctx_to_pd_state)) + return FWK_E_PWRSTATE; + + *pd_state = scmi_dev_state_id_lost_ctx_to_pd_state[scmi_state_id]; + } else { + if (scmi_state_id >= + FWK_ARRAY_SIZE(scmi_dev_state_id_preserved_ctx_to_pd_state)) + return FWK_E_PWRSTATE; + + *pd_state = scmi_dev_state_id_preserved_ctx_to_pd_state[scmi_state_id]; + } + + return FWK_SUCCESS; +} + +static int pd_state_to_scmi_device_state(unsigned int pd_state, + uint32_t *scmi_state) +{ + if (pd_state >= FWK_ARRAY_SIZE(pd_state_to_scmi_dev_state)) + return FWK_E_PWRSTATE; + + *scmi_state = pd_state_to_scmi_dev_state[pd_state]; + return FWK_SUCCESS; +} + +static int scmi_pd_protocol_version_handler(fwk_id_t service_id, + const uint32_t *payload) +{ + struct scmi_protocol_version_p2a return_values = { + .status = SCMI_SUCCESS, + .version = SCMI_PROTOCOL_VERSION_POWER_DOMAIN, + }; + + scmi_pd_ctx.scmi_api->respond(service_id, + &return_values, sizeof(return_values)); + + return FWK_SUCCESS; +} + +static int scmi_pd_protocol_attributes_handler(fwk_id_t service_id, + const uint32_t *payload) +{ + struct scmi_pd_protocol_attributes_p2a return_values = { + .status = SCMI_SUCCESS, + }; + + return_values.attributes = scmi_pd_ctx.domain_count; + + scmi_pd_ctx.scmi_api->respond(service_id, + &return_values, sizeof(return_values)); + + return FWK_SUCCESS; +} + +static int scmi_pd_power_domain_attributes_handler(fwk_id_t service_id, + const uint32_t *payload) +{ + int status = FWK_SUCCESS; + const struct scmi_pd_power_domain_attributes_a2p *parameters; + enum mod_pd_type pd_type; + unsigned int domain_idx; + fwk_id_t pd_id; + unsigned int agent_id; + enum scmi_agent_type agent_type; + struct scmi_pd_power_domain_attributes_p2a return_values = { + .status = SCMI_GENERIC_ERROR, + }; + + parameters = (const struct scmi_pd_power_domain_attributes_a2p *)payload; + + domain_idx = parameters->domain_id; + if (domain_idx > UINT16_MAX) { + return_values.status = SCMI_NOT_FOUND; + goto exit; + } + + pd_id = FWK_ID_ELEMENT(FWK_MODULE_IDX_POWER_DOMAIN, domain_idx); + if (!fwk_module_is_valid_element_id(pd_id)) { + return_values.status = SCMI_NOT_FOUND; + goto exit; + } + + status = scmi_pd_ctx.scmi_api->get_agent_id(service_id, &agent_id); + if (status != FWK_SUCCESS) + goto exit; + + status = scmi_pd_ctx.scmi_api->get_agent_type(agent_id, &agent_type); + if (status != FWK_SUCCESS) + goto exit; + + status = scmi_pd_ctx.pd_api->get_domain_type(pd_id, &pd_type); + if (status != FWK_SUCCESS) + goto exit; + + switch (pd_type) { + case MOD_PD_TYPE_CORE: + /* + * For core power domains, the POWER_STATE_SET command is supported + * only asynchronously for the PSCI agent. In all other cases, reply + * that the command is not supported either synchronously nor + * asynchronously. + */ + if (agent_type == SCMI_AGENT_TYPE_PSCI) + return_values.attributes = SCMI_PD_POWER_STATE_SET_ASYNC; + break; + + case MOD_PD_TYPE_CLUSTER: + /* + * For cluster power domains, the POWER_STATE_SET command is supported + * only synchronously for the PSCI agent. In all other cases, reply + * that the command is not supported either synchronously nor + * asynchronously. + */ + if (agent_type == SCMI_AGENT_TYPE_PSCI) + return_values.attributes = SCMI_PD_POWER_STATE_SET_SYNC; + break; + + case MOD_PD_TYPE_DEVICE: + case MOD_PD_TYPE_DEVICE_DEBUG: + /* + * Support only synchronous POWER_STATE_SET for devices for any agent. + */ + return_values.attributes = SCMI_PD_POWER_STATE_SET_SYNC; + break; + + case MOD_PD_TYPE_SYSTEM: + return_values.status = SCMI_NOT_FOUND; + /* Fallthrough. */ + + default: + goto exit; + } + + strncpy((char *)return_values.name, fwk_module_get_name(pd_id), + sizeof(return_values.name) - 1); + + return_values.status = SCMI_SUCCESS; + +exit: + scmi_pd_ctx.scmi_api->respond(service_id, &return_values, + (return_values.status == SCMI_SUCCESS) ? + sizeof(return_values) : sizeof(return_values.status)); + + return status; +} + +static int scmi_pd_protocol_message_attributes_handler( + fwk_id_t service_id, const uint32_t *payload) +{ + const struct scmi_protocol_message_attributes_a2p *parameters; + struct scmi_protocol_message_attributes_p2a return_values = { + .status = SCMI_NOT_FOUND, + }; + + parameters = (const struct scmi_protocol_message_attributes_a2p *) + payload; + + if ((parameters->message_id < FWK_ARRAY_SIZE(handler_table)) && + (handler_table[parameters->message_id] != NULL)) + return_values.status = SCMI_SUCCESS; + + scmi_pd_ctx.scmi_api->respond(service_id, &return_values, + (return_values.status == SCMI_SUCCESS) ? + sizeof(return_values) : sizeof(return_values.status)); + + return FWK_SUCCESS; +} + +static int scmi_power_scp_set_core_state(fwk_id_t pd_id, + uint32_t composite_state) +{ + int status; + + status = scmi_pd_ctx.pd_api->set_composite_state_async(pd_id, false, + composite_state); + if (status != FWK_SUCCESS) { + scmi_pd_ctx.log_api->log(MOD_LOG_GROUP_ERROR, + "[SCMI:power] Failed to send core set request (error %e)\n", + status); + } + + return status; +} + +static int scmi_pd_power_state_set_handler(fwk_id_t service_id, + const uint32_t *payload) +{ + int status; + const struct scmi_pd_power_state_set_a2p *parameters; + bool is_sync; + unsigned int agent_id; + enum scmi_agent_type agent_type; + unsigned int domain_idx; + fwk_id_t pd_id; + unsigned int pd_power_state; + struct scmi_pd_power_state_set_p2a return_values = { + .status = SCMI_GENERIC_ERROR + }; + enum mod_pd_type pd_type; + + parameters = (const struct scmi_pd_power_state_set_a2p *)payload; + + is_sync = !(parameters->flags & SCMI_PD_POWER_STATE_SET_ASYNC_FLAG_MASK); + + status = scmi_pd_ctx.scmi_api->get_agent_id(service_id, &agent_id); + if (status != FWK_SUCCESS) + goto exit; + + status = scmi_pd_ctx.scmi_api->get_agent_type(agent_id, &agent_type); + if (status != FWK_SUCCESS) + goto exit; + + domain_idx = parameters->domain_id; + if (domain_idx > UINT16_MAX) { + return_values.status = SCMI_NOT_FOUND; + goto exit; + } + + pd_id = FWK_ID_ELEMENT(FWK_MODULE_IDX_POWER_DOMAIN, domain_idx); + if (!fwk_module_is_valid_element_id(pd_id)) { + return_values.status = SCMI_NOT_FOUND; + goto exit; + } + + status = scmi_pd_ctx.pd_api->get_domain_type(pd_id, &pd_type); + if (status != FWK_SUCCESS) + goto exit; + + if (((pd_type == MOD_PD_TYPE_CORE) || + (pd_type == MOD_PD_TYPE_CLUSTER)) && + (agent_type != SCMI_AGENT_TYPE_PSCI)) { + + return_values.status = SCMI_NOT_SUPPORTED; + goto exit; + } + + switch (pd_type) { + case MOD_PD_TYPE_CORE: + /* + * Async/sync flag is ignored for core power domains as stated + * by the specification. + */ + status = scmi_power_scp_set_core_state(pd_id, parameters->power_state); + if (status == FWK_E_PARAM) + return_values.status = SCMI_INVALID_PARAMETERS; + break; + + case MOD_PD_TYPE_CLUSTER: + if (!is_sync) { + return_values.status = SCMI_NOT_SUPPORTED; + goto exit; + } + + status = scmi_pd_ctx.pd_api->set_state(pd_id, parameters->power_state); + break; + + case MOD_PD_TYPE_DEVICE: + case MOD_PD_TYPE_DEVICE_DEBUG: + if (!is_sync) { + return_values.status = SCMI_NOT_SUPPORTED; + goto exit; + } + + status = scmi_device_state_to_pd_state(parameters->power_state, + &pd_power_state); + if (status != FWK_SUCCESS) { + status = FWK_SUCCESS; + return_values.status = SCMI_INVALID_PARAMETERS; + goto exit; + } + + status = scmi_pd_ctx.pd_api->set_state(pd_id, pd_power_state); + break; + + case MOD_PD_TYPE_SYSTEM: + return_values.status = SCMI_NOT_FOUND; + /* Fallthrough. */ + + default: + goto exit; + } + + if (status == FWK_SUCCESS) + return_values.status = SCMI_SUCCESS; + +exit: + scmi_pd_ctx.scmi_api->respond(service_id, &return_values, + (return_values.status == SCMI_SUCCESS) ? + sizeof(return_values) : sizeof(return_values.status)); + + return status; +} + +static int scmi_pd_power_state_get_handler(fwk_id_t service_id, + const uint32_t *payload) +{ + int status = FWK_SUCCESS; + const struct scmi_pd_power_state_get_a2p *parameters; + unsigned int domain_idx; + fwk_id_t pd_id; + struct scmi_pd_power_state_get_p2a return_values = { + .status = SCMI_GENERIC_ERROR + }; + enum mod_pd_type pd_type; + unsigned int pd_power_state; + unsigned int power_state; + + parameters = (const struct scmi_pd_power_state_get_a2p *)payload; + + domain_idx = parameters->domain_id; + if (domain_idx > UINT16_MAX) { + return_values.status = SCMI_NOT_FOUND; + goto exit; + } + + pd_id = FWK_ID_ELEMENT(FWK_MODULE_IDX_POWER_DOMAIN, domain_idx); + if (!fwk_module_is_valid_element_id(pd_id)) { + return_values.status = SCMI_NOT_FOUND; + goto exit; + } + + status = scmi_pd_ctx.pd_api->get_domain_type(pd_id, &pd_type); + if (status != FWK_SUCCESS) + goto exit; + + switch (pd_type) { + case MOD_PD_TYPE_CORE: + status = scmi_pd_ctx.pd_api->get_composite_state(pd_id, &power_state); + break; + + case MOD_PD_TYPE_CLUSTER: + status = scmi_pd_ctx.pd_api->get_state(pd_id, &power_state); + break; + + case MOD_PD_TYPE_DEVICE: + case MOD_PD_TYPE_DEVICE_DEBUG: + + status = scmi_pd_ctx.pd_api->get_state(pd_id, &pd_power_state); + if (status != FWK_SUCCESS) + goto exit; + + status = pd_state_to_scmi_device_state(pd_power_state, + (uint32_t *) &power_state); + break; + + case MOD_PD_TYPE_SYSTEM: + return_values.status = SCMI_NOT_FOUND; + /* Fallthrough. */ + + default: + goto exit; + } + + if (status == FWK_SUCCESS) { + return_values.status = SCMI_SUCCESS; + return_values.power_state = power_state; + } + +exit: + scmi_pd_ctx.scmi_api->respond(service_id, &return_values, + (return_values.status == SCMI_SUCCESS) ? + sizeof(return_values) : sizeof(return_values.status)); + + return status; +} + +/* + * SCMI module -> SCMI power module interface + */ +static int scmi_pd_get_scmi_protocol_id(fwk_id_t protocol_id, + uint8_t *scmi_protocol_id) +{ + int status; + + status = fwk_module_check_call(protocol_id); + if (status != FWK_SUCCESS) + return status; + + *scmi_protocol_id = SCMI_PROTOCOL_ID_POWER_DOMAIN; + + return FWK_SUCCESS; +} + +static int scmi_pd_message_handler(fwk_id_t protocol_id, fwk_id_t service_id, + const uint32_t *payload, size_t payload_size, unsigned int message_id) +{ + int status; + int32_t return_value; + + status = fwk_module_check_call(protocol_id); + if (status != FWK_SUCCESS) + return status; + + static_assert(FWK_ARRAY_SIZE(handler_table) == + FWK_ARRAY_SIZE(payload_size_table), + "[SCMI] Power domain management protocol table sizes not consistent"); + assert(payload != NULL); + + if (message_id >= FWK_ARRAY_SIZE(handler_table)) { + return_value = SCMI_NOT_SUPPORTED; + goto error; + } + + if (payload_size != payload_size_table[message_id]) { + return_value = SCMI_PROTOCOL_ERROR; + goto error; + } + + return handler_table[message_id](service_id, payload); + +error: + scmi_pd_ctx.scmi_api->respond(service_id, + &return_value, sizeof(return_value)); + return FWK_SUCCESS; +} + +static struct mod_scmi_to_protocol_api scmi_pd_mod_scmi_to_protocol_api = { + .get_scmi_protocol_id = scmi_pd_get_scmi_protocol_id, + .message_handler = scmi_pd_message_handler +}; + +/* + * Framework handlers + */ + +static int scmi_pd_init(fwk_id_t module_id, unsigned int element_count, + const void *unused) +{ + if (element_count != 0) + return FWK_E_SUPPORT; + + scmi_pd_ctx.domain_count = fwk_module_get_element_count( + fwk_module_id_power_domain); + if (scmi_pd_ctx.domain_count <= 1) + return FWK_E_SUPPORT; + + /* Do not expose SYSTEM domain (always the last one) to agents and ... */ + scmi_pd_ctx.domain_count--; + /* ... and expose no more than 0xFFFF number of domains. */ + if (scmi_pd_ctx.domain_count > UINT16_MAX) + scmi_pd_ctx.domain_count = UINT16_MAX; + + return FWK_SUCCESS; +} + +static int scmi_pd_bind(fwk_id_t id, unsigned int round) +{ + int status; + + if (round == 1) + return FWK_SUCCESS; + + status = fwk_module_bind(FWK_ID_MODULE(FWK_MODULE_IDX_LOG), + FWK_ID_API(FWK_MODULE_IDX_LOG, 0), &scmi_pd_ctx.log_api); + if (status != FWK_SUCCESS) + return status; + + status = fwk_module_bind(FWK_ID_MODULE(FWK_MODULE_IDX_SCMI), + FWK_ID_API(FWK_MODULE_IDX_SCMI, MOD_SCMI_API_IDX_PROTOCOL), + &scmi_pd_ctx.scmi_api); + if (status != FWK_SUCCESS) + return status; + + return fwk_module_bind(fwk_module_id_power_domain, mod_pd_api_id_restricted, + &scmi_pd_ctx.pd_api); +} + +static int scmi_pd_process_bind_request(fwk_id_t source_id, fwk_id_t target_id, + fwk_id_t api_id, const void **api) +{ + if (!fwk_id_is_equal(source_id, FWK_ID_MODULE(FWK_MODULE_IDX_SCMI))) + return FWK_E_ACCESS; + + *api = &scmi_pd_mod_scmi_to_protocol_api; + + return FWK_SUCCESS; +} + +/* SCMI Power Domain Management Protocol Definition */ +const struct fwk_module module_scmi_power_domain = { + .name = "SCMI Power Domain Management Protocol", + .api_count = 1, + .type = FWK_MODULE_TYPE_PROTOCOL, + .init = scmi_pd_init, + .bind = scmi_pd_bind, + .process_bind_request = scmi_pd_process_bind_request, +}; + +/* No elements, no module configuration data */ +struct fwk_module_config config_scmi_power_domain = {}; diff --git a/module/scmi_sensor/include/internal/scmi_sensor.h b/module/scmi_sensor/include/internal/scmi_sensor.h new file mode 100644 index 00000000..d89e3ff0 --- /dev/null +++ b/module/scmi_sensor/include/internal/scmi_sensor.h @@ -0,0 +1,164 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * System Control and Management Interface (SCMI) support. + */ + +#ifndef SCMI_SENSOR_H +#define SCMI_SENSOR_H + +/*! + * \addtogroup GroupModules Modules + * @{ + */ + +/*! + * \defgroup GroupSCMI_PERF SCMI Sensor Management Protocol + * @{ + */ + +#define SCMI_PROTOCOL_ID_SENSOR UINT32_C(0x15) +#define SCMI_PROTOCOL_VERSION_SENSOR UINT32_C(0x10000) + +/* + * Identifiers of the SCMI Sensor Management Protocol commands + */ +enum scmi_sensor_command_id { + SCMI_SENSOR_DESCRIPTION_GET = 0x003, + SCMI_SENSOR_CONFIG_SET = 0x004, + SCMI_SENSOR_TRIP_POINT_SET = 0x005, + SCMI_SENSOR_READING_GET = 0x006, +}; + +/* + * PROTOCOL_ATTRIBUTES + */ + +struct __attribute((packed)) scmi_sensor_protocol_attributes_p2a { + int32_t status; + uint32_t attributes; + uint32_t sensor_reg_address_low; + uint32_t sensor_reg_address_high; + uint32_t sensor_reg_len; +}; + +/* + * SENSOR_READING_GET + */ + +#define SCMI_SENSOR_PROTOCOL_READING_GET_ASYNC_FLAG_MASK (1 << 0) + +struct __attribute((packed)) scmi_sensor_protocol_reading_get_a2p { + uint32_t sensor_id; + uint32_t flags; +}; + +struct __attribute((packed)) scmi_sensor_protocol_reading_get_p2a { + int32_t status; + uint32_t sensor_value_low; + uint32_t sensor_value_high; +}; + +/* + * SENSOR_DESCRIPTION_GET + */ + + #define SCMI_SENSOR_DESCS_MAX(MAILBOX_SIZE) \ + ((sizeof(struct scmi_sensor_protocol_description_get_p2a) < MAILBOX_SIZE) \ + ? ((MAILBOX_SIZE - \ + sizeof(struct scmi_sensor_protocol_description_get_p2a)) \ + / sizeof(struct scmi_sensor_desc)) \ + : 0) + +#define SCMI_SENSOR_DESC_ATTRS_HIGH_SENSOR_TYPE_POS 0 +#define SCMI_SENSOR_DESC_ATTRS_HIGH_SENSOR_UNIT_MULTIPLIER_POS 11 +#define SCMI_SENSOR_DESC_ATTRS_HIGH_SENSOR_UPDATE_MULTIPLIER_POS 22 +#define SCMI_SENSOR_DESC_ATTRS_HIGH_SENSOR_UPDATE_INTERVAL_POS 27 + +#define SCMI_SENSOR_DESC_ATTRS_HIGH_SENSOR_TYPE_MASK \ + (UINT32_C(0xFF) << SCMI_SENSOR_DESC_ATTRS_HIGH_SENSOR_TYPE_POS) +#define SCMI_SENSOR_DESC_ATTRS_HIGH_SENSOR_UNIT_MULTIPLIER_MASK \ + (UINT32_C(0x1F) << SCMI_SENSOR_DESC_ATTRS_HIGH_SENSOR_UNIT_MULTIPLIER_POS) +#define SCMI_SENSOR_DESC_ATTRS_HIGH_SENSOR_UPDATE_MULTIPLIER_MASK \ + (UINT32_C(0x1F) \ + << SCMI_SENSOR_DESC_ATTRS_HIGH_SENSOR_UPDATE_MULTIPLIER_POS) +#define SCMI_SENSOR_DESC_ATTRS_HIGH_SENSOR_UPDATE_INTERVAL_MASK \ + (UINT32_C(0x1F) << SCMI_SENSOR_DESC_ATTRS_HIGH_SENSOR_UPDATE_INTERVAL_POS) + +#define SCMI_SENSOR_DESC_ATTRS_HIGH_SENSOR_UNIT_MULTIPLIER_MAX \ + (int32_t)(SCMI_SENSOR_DESC_ATTRS_HIGH_SENSOR_UNIT_MULTIPLIER_MASK >> 1) +#define SCMI_SENSOR_DESC_ATTRS_HIGH_SENSOR_UNIT_MULTIPLIER_MIN \ + (-(SCMI_SENSOR_DESC_ATTRS_HIGH_SENSOR_UNIT_MULTIPLIER_MAX + 1)) + +#define SCMI_SENSOR_DESC_ATTRS_HIGH_SENSOR_UPDATE_MULTIPLIER_MAX \ + (int32_t)(SCMI_SENSOR_DESC_ATTRS_HIGH_SENSOR_UPDATE_INTERVAL_MASK >> 1) +#define SCMI_SENSOR_DESC_ATTRS_HIGH_SENSOR_UPDATE_MULTIPLIER_MIN \ + (-(SCMI_SENSOR_DESC_ATTRS_HIGH_SENSOR_UPDATE_MULTIPLIER_MAX + 1)) + +#define SCMI_SENSOR_DESC_ATTRIBUTES_HIGH(SENSOR_TYPE, UNIT_MULTIPLIER, \ + UPDATE_MULTIPLIER, UPDATE_INTERVAL) \ + ( \ + (((SENSOR_TYPE) << \ + SCMI_SENSOR_DESC_ATTRS_HIGH_SENSOR_TYPE_POS) & \ + SCMI_SENSOR_DESC_ATTRS_HIGH_SENSOR_TYPE_MASK) | \ + (((UNIT_MULTIPLIER) << \ + SCMI_SENSOR_DESC_ATTRS_HIGH_SENSOR_UNIT_MULTIPLIER_POS) & \ + SCMI_SENSOR_DESC_ATTRS_HIGH_SENSOR_UNIT_MULTIPLIER_MASK) | \ + (((UPDATE_MULTIPLIER) << \ + SCMI_SENSOR_DESC_ATTRS_HIGH_SENSOR_UPDATE_MULTIPLIER_POS) & \ + SCMI_SENSOR_DESC_ATTRS_HIGH_SENSOR_UPDATE_MULTIPLIER_MASK) | \ + (((UPDATE_INTERVAL) << \ + SCMI_SENSOR_DESC_ATTRS_HIGH_SENSOR_UPDATE_INTERVAL_POS) & \ + SCMI_SENSOR_DESC_ATTRS_HIGH_SENSOR_UPDATE_INTERVAL_MASK) \ + ) + +#define SCMI_SENSOR_NUM_SENSOR_FLAGS_NUM_DESCS_POS 0 +#define SCMI_SENSOR_NUM_SENSOR_FLAGS_NUM_REMAINING_DESCS_POS 16 + +#define SCMI_SENSOR_NUM_SENSOR_FLAGS_NUM_DESCS_MASK \ + (UINT32_C(0xFFF) << SCMI_SENSOR_NUM_SENSOR_FLAGS_NUM_DESCS_POS) +#define SCMI_SENSOR_NUM_SENSOR_FLAGS_NUM_REMAINING_DESCS_MASK \ + (UINT32_C(0xFFFF) << SCMI_SENSOR_NUM_SENSOR_FLAGS_NUM_REMAINING_DESCS_POS) + +#define SCMI_SENSOR_NUM_SENSOR_FLAGS(NUM_DESCS, NUM_REMAINING_DESCS) \ + ( \ + (((NUM_DESCS) << \ + SCMI_SENSOR_NUM_SENSOR_FLAGS_NUM_DESCS_POS) & \ + SCMI_SENSOR_NUM_SENSOR_FLAGS_NUM_DESCS_MASK) | \ + (((NUM_REMAINING_DESCS) << \ + SCMI_SENSOR_NUM_SENSOR_FLAGS_NUM_REMAINING_DESCS_POS) & \ + SCMI_SENSOR_NUM_SENSOR_FLAGS_NUM_REMAINING_DESCS_MASK) \ + ) + +#define SCMI_SENSOR_NAME_LEN 16 + +struct __attribute((packed)) scmi_sensor_desc { + uint32_t sensor_id; + uint32_t sensor_attributes_low; + uint32_t sensor_attributes_high; + char sensor_name[SCMI_SENSOR_NAME_LEN]; +}; + +struct __attribute((packed)) scmi_sensor_protocol_description_get_a2p { + uint32_t desc_index; +}; + +struct __attribute((packed)) scmi_sensor_protocol_description_get_p2a { + int32_t status; + uint32_t num_sensor_flags; + struct scmi_sensor_desc sensor_desc[]; +}; + +/*! + * @} + */ + +/*! + * @} + */ + +#endif /* SCMI_SENSOR_H */ diff --git a/module/scmi_sensor/src/Makefile b/module/scmi_sensor/src/Makefile new file mode 100644 index 00000000..a177d28e --- /dev/null +++ b/module/scmi_sensor/src/Makefile @@ -0,0 +1,11 @@ +# +# Arm SCP/MCP Software +# Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +BS_LIB_NAME := SCMI Sensor Protocol +BS_LIB_SOURCES := mod_scmi_sensor.c + +include $(BS_DIR)/lib.mk diff --git a/module/scmi_sensor/src/mod_scmi_sensor.c b/module/scmi_sensor/src/mod_scmi_sensor.c new file mode 100644 index 00000000..0822563d --- /dev/null +++ b/module/scmi_sensor/src/mod_scmi_sensor.c @@ -0,0 +1,462 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * SCMI sensor management protocol support. + */ + +#include <assert.h> +#include <string.h> +#include <fwk_element.h> +#include <fwk_errno.h> +#include <fwk_id.h> +#include <fwk_macros.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <internal/scmi.h> +#include <internal/scmi_sensor.h> +#include <mod_sensor.h> +#include <mod_scmi.h> + +struct scmi_sensor_ctx { + unsigned int sensor_count; + const struct mod_scmi_from_protocol_api *scmi_api; + const struct mod_sensor_api *sensor_api; +}; + +static int scmi_sensor_protocol_version_handler(fwk_id_t service_id, + const uint32_t *payload); +static int scmi_sensor_protocol_attributes_handler(fwk_id_t service_id, + const uint32_t *payload); +static int scmi_sensor_protocol_msg_attributes_handler(fwk_id_t service_id, + const uint32_t *payload); +static int scmi_sensor_protocol_desc_get_handler(fwk_id_t service_id, + const uint32_t *payload); +static int scmi_sensor_reading_get_handler(fwk_id_t service_id, + const uint32_t *payload); + +/* + * Internal variables. + */ +static struct scmi_sensor_ctx scmi_sensor_ctx; + +static int (*handler_table[])(fwk_id_t, const uint32_t *) = { + [SCMI_PROTOCOL_VERSION] = + scmi_sensor_protocol_version_handler, + [SCMI_PROTOCOL_ATTRIBUTES] = + scmi_sensor_protocol_attributes_handler, + [SCMI_PROTOCOL_MESSAGE_ATTRIBUTES] = + scmi_sensor_protocol_msg_attributes_handler, + [SCMI_SENSOR_DESCRIPTION_GET] = + scmi_sensor_protocol_desc_get_handler, + [SCMI_SENSOR_READING_GET] = scmi_sensor_reading_get_handler +}; + +static unsigned int payload_size_table[] = { + [SCMI_PROTOCOL_VERSION] = 0, + [SCMI_PROTOCOL_ATTRIBUTES] = 0, + [SCMI_PROTOCOL_MESSAGE_ATTRIBUTES] = + sizeof(struct scmi_protocol_message_attributes_a2p), + [SCMI_SENSOR_DESCRIPTION_GET] = + sizeof(struct scmi_sensor_protocol_description_get_a2p), + [SCMI_SENSOR_READING_GET] = + sizeof(struct scmi_sensor_protocol_reading_get_a2p), +}; + +/* + * Sensor management protocol implementation + */ +static int scmi_sensor_protocol_version_handler(fwk_id_t service_id, + const uint32_t *payload) +{ + struct scmi_protocol_version_p2a return_values = { + .status = SCMI_SUCCESS, + .version = SCMI_PROTOCOL_VERSION_SENSOR, + }; + + scmi_sensor_ctx.scmi_api->respond(service_id, &return_values, + sizeof(return_values)); + + return FWK_SUCCESS; +} + +static int scmi_sensor_protocol_attributes_handler(fwk_id_t service_id, + const uint32_t *payload) +{ + struct scmi_sensor_protocol_attributes_p2a return_values = { + .status = SCMI_SUCCESS, + .attributes = scmi_sensor_ctx.sensor_count, + .sensor_reg_len = 0, /* Unsupported */ + }; + + scmi_sensor_ctx.scmi_api->respond(service_id, &return_values, + sizeof(return_values)); + + return FWK_SUCCESS; +} + +static int scmi_sensor_protocol_msg_attributes_handler(fwk_id_t service_id, + const uint32_t *payload) +{ + const struct scmi_protocol_message_attributes_a2p *parameters; + struct scmi_protocol_message_attributes_p2a return_values; + + parameters = (const struct scmi_protocol_message_attributes_a2p *) + payload; + + if ((parameters->message_id < FWK_ARRAY_SIZE(handler_table)) && + (handler_table[parameters->message_id] != NULL)) { + return_values = (struct scmi_protocol_message_attributes_p2a) { + .status = SCMI_SUCCESS, + /* All commands have an attributes value of 0 */ + .attributes = 0, + }; + } else + return_values.status = SCMI_NOT_FOUND; + + scmi_sensor_ctx.scmi_api->respond(service_id, &return_values, + (return_values.status == SCMI_SUCCESS) ? + sizeof(return_values) : sizeof(return_values.status)); + + return FWK_SUCCESS; +} + +static int scmi_sensor_protocol_desc_get_handler(fwk_id_t service_id, + const uint32_t *payload) +{ + int status; + size_t payload_size; + size_t max_payload_size; + const struct scmi_sensor_protocol_description_get_a2p *parameters = + (const struct scmi_sensor_protocol_description_get_a2p *)payload; + struct scmi_sensor_desc desc = {}; + unsigned int num_descs, desc_index, desc_index_max; + struct mod_sensor_info sensor_info; + struct scmi_sensor_protocol_description_get_p2a return_values = { + .status = SCMI_GENERIC_ERROR, + }; + fwk_id_t sensor_id; + + payload_size = sizeof(return_values); + + status = scmi_sensor_ctx.scmi_api->get_max_payload_size(service_id, + &max_payload_size); + if (status != FWK_SUCCESS) + goto exit; + + if (SCMI_SENSOR_DESCS_MAX(max_payload_size) == 0) { + /* Can't even fit one sensor description in the payload */ + assert(false); + status = FWK_E_SIZE; + goto exit; + } + + parameters = + (const struct scmi_sensor_protocol_description_get_a2p *)payload; + desc_index = parameters->desc_index; + + if (desc_index >= scmi_sensor_ctx.sensor_count) { + return_values.status = SCMI_INVALID_PARAMETERS; + goto exit; + } + + num_descs = FWK_MIN(SCMI_SENSOR_DESCS_MAX(max_payload_size), + (scmi_sensor_ctx.sensor_count - desc_index)); + desc_index_max = (desc_index + num_descs - 1); + + for (; desc_index <= desc_index_max; ++desc_index, + payload_size += sizeof(desc)) { + + desc = (struct scmi_sensor_desc) { + .sensor_id = desc_index, + .sensor_attributes_low = 0, /* None supported */ + }; + + sensor_id = FWK_ID_ELEMENT(FWK_MODULE_IDX_SENSOR, desc_index); + if (!fwk_module_is_valid_element_id(sensor_id)) { + /* domain_idx did not map to a sensor device */ + assert(false); + return_values.status = SCMI_NOT_FOUND; + goto exit; + } + + status = scmi_sensor_ctx.sensor_api->get_info(sensor_id, &sensor_info); + if (status != FWK_SUCCESS) { + /* Unable to get sensor info */ + assert(false); + goto exit; + } + + if (sensor_info.type >= MOD_SENSOR_TYPE_COUNT) { + /* Invalid sensor type */ + assert(false); + goto exit; + } + + if ((sensor_info.unit_multiplier < + SCMI_SENSOR_DESC_ATTRS_HIGH_SENSOR_UNIT_MULTIPLIER_MIN) || + (sensor_info.unit_multiplier > + SCMI_SENSOR_DESC_ATTRS_HIGH_SENSOR_UNIT_MULTIPLIER_MAX)) { + + /* Sensor unit multiplier out of range */ + assert(false); + goto exit; + } + + if ((sensor_info.update_interval_multiplier < + SCMI_SENSOR_DESC_ATTRS_HIGH_SENSOR_UPDATE_MULTIPLIER_MIN) || + (sensor_info.update_interval_multiplier > + SCMI_SENSOR_DESC_ATTRS_HIGH_SENSOR_UPDATE_MULTIPLIER_MAX)) { + + /* Sensor update interval multiplier is out of range */ + assert(false); + goto exit; + } + + if (sensor_info.update_interval >= + SCMI_SENSOR_DESC_ATTRS_HIGH_SENSOR_UPDATE_INTERVAL_MASK) { + + /* Update interval is too big to fit in it's mask */ + assert(false); + goto exit; + } + + desc.sensor_attributes_high = + SCMI_SENSOR_DESC_ATTRIBUTES_HIGH(sensor_info.type, + sensor_info.unit_multiplier, + (uint32_t)sensor_info.update_interval_multiplier, + (uint32_t)sensor_info.update_interval); + + /* + * Copy sensor name into description struct. Copy n-1 chars to ensure a + * NULL terminator at the end. (struct has been zeroed out) + */ + strncpy(desc.sensor_name, sensor_info.name, + sizeof(desc.sensor_name) - 1); + + status = scmi_sensor_ctx.scmi_api->write_payload(service_id, + payload_size, &desc, sizeof(struct scmi_sensor_desc)); + if (status != FWK_SUCCESS) { + /* Failed to write sensor description into message payload */ + assert(false); + goto exit; + } + } + + return_values = (struct scmi_sensor_protocol_description_get_p2a) { + .status = SCMI_SUCCESS, + .num_sensor_flags = SCMI_SENSOR_NUM_SENSOR_FLAGS(num_descs, + (scmi_sensor_ctx.sensor_count - desc_index_max - 1)) + }; + + status = scmi_sensor_ctx.scmi_api->write_payload(service_id, 0, + &return_values, sizeof(return_values)); + if (status != FWK_SUCCESS) + return_values.status = SCMI_GENERIC_ERROR; + +exit: + scmi_sensor_ctx.scmi_api->respond(service_id, + (return_values.status == SCMI_SUCCESS) ? + NULL : &return_values.status, + (return_values.status == SCMI_SUCCESS) ? + payload_size : sizeof(return_values.status)); + + return status; +} + +static int scmi_sensor_reading_get_handler(fwk_id_t service_id, + const uint32_t *payload) +{ + const struct scmi_sensor_protocol_reading_get_a2p *parameters; + struct scmi_sensor_protocol_reading_get_p2a return_values; + uint64_t sensor_value; + uint32_t flags; + fwk_id_t sensor_id; + int status; + + parameters = (const struct scmi_sensor_protocol_reading_get_a2p *)payload; + return_values.status = SCMI_GENERIC_ERROR; + + if (parameters->sensor_id >= scmi_sensor_ctx.sensor_count) { + /* Sensor does not exist */ + status = FWK_SUCCESS; + return_values.status = SCMI_NOT_FOUND; + goto exit; + } + + /* Reject asynchronous read requests for now */ + flags = parameters->flags; + if (flags & SCMI_SENSOR_PROTOCOL_READING_GET_ASYNC_FLAG_MASK) { + return_values.status = SCMI_NOT_SUPPORTED; + status = FWK_SUCCESS; + goto exit; + } + + sensor_id = FWK_ID_ELEMENT(FWK_MODULE_IDX_SENSOR, + parameters->sensor_id); + + status = scmi_sensor_ctx.sensor_api->get_value(sensor_id, &sensor_value); + if (status == FWK_SUCCESS) { + return_values = (struct scmi_sensor_protocol_reading_get_p2a) { + .status = SCMI_SUCCESS, + .sensor_value_low = (uint32_t)sensor_value, + .sensor_value_high = (uint32_t)(sensor_value >> 32), + }; + } else if (status == FWK_E_PWRSTATE) { + /* The sensor is currently unpowered */ + status = FWK_SUCCESS; + return_values.status = SCMI_HARDWARE_ERROR; + } else { + /* Unable to read sensor */ + assert(false); + } + +exit: + scmi_sensor_ctx.scmi_api->respond(service_id, &return_values, + (return_values.status == SCMI_SUCCESS) ? + sizeof(return_values) : sizeof(return_values.status)); + + return status; +} + +/* + * SCMI module -> SCMI sensor module interface + */ +static int scmi_sensor_get_scmi_protocol_id(fwk_id_t protocol_id, + uint8_t *scmi_protocol_id) +{ + int status; + + status = fwk_module_check_call(protocol_id); + if (status != FWK_SUCCESS) + return status; + + *scmi_protocol_id = SCMI_PROTOCOL_ID_SENSOR; + + return FWK_SUCCESS; +} + +static int scmi_sensor_message_handler(fwk_id_t protocol_id, + fwk_id_t service_id, + const uint32_t *payload, + size_t payload_size, + unsigned int message_id) +{ + int status; + int32_t return_value; + + status = fwk_module_check_call(protocol_id); + if (status != FWK_SUCCESS) + return status; + + static_assert(FWK_ARRAY_SIZE(handler_table) == + FWK_ARRAY_SIZE(payload_size_table), + "[SCMI] Sensor management protocol table sizes not consistent"); + assert(payload != NULL); + + if (message_id >= FWK_ARRAY_SIZE(handler_table)) { + return_value = SCMI_NOT_SUPPORTED; + goto error; + } + + if (payload_size != payload_size_table[message_id]) { + /* Incorrect payload size or message is not supported */ + return_value = SCMI_PROTOCOL_ERROR; + goto error; + } + + return handler_table[message_id](service_id, payload); + +error: + scmi_sensor_ctx.scmi_api->respond(service_id, &return_value, + sizeof(return_value)); + return FWK_SUCCESS; +} + +static struct mod_scmi_to_protocol_api scmi_sensor_mod_scmi_to_protocol_api = { + .get_scmi_protocol_id = scmi_sensor_get_scmi_protocol_id, + .message_handler = scmi_sensor_message_handler +}; + +/* + * Framework interface + */ +static int scmi_sensor_init(fwk_id_t module_id, + unsigned int element_count, + const void *unused) +{ + if (element_count != 0) { + /* This module should not have any elements */ + assert(false); + return FWK_E_SUPPORT; + } + + scmi_sensor_ctx.sensor_count = fwk_module_get_element_count( + FWK_ID_MODULE(FWK_MODULE_IDX_SENSOR)); + if (scmi_sensor_ctx.sensor_count == 0) + return FWK_E_SUPPORT; + + /* SCMI protocol uses a 16 bit number to store the number of sensors. + * So expose no more than 0xFFFF number of sensors. */ + if (scmi_sensor_ctx.sensor_count > UINT16_MAX) + scmi_sensor_ctx.sensor_count = UINT16_MAX; + + return FWK_SUCCESS; +} + +static int scmi_sensor_bind(fwk_id_t id, unsigned int round) +{ + int status; + + if (round == 1) + return FWK_SUCCESS; + + status = fwk_module_bind(FWK_ID_MODULE(FWK_MODULE_IDX_SCMI), + FWK_ID_API(FWK_MODULE_IDX_SCMI, + MOD_SCMI_API_IDX_PROTOCOL), + &scmi_sensor_ctx.scmi_api); + if (status != FWK_SUCCESS) { + /* Failed to bind to SCMI module */ + assert(false); + return status; + } + + status = fwk_module_bind(FWK_ID_MODULE(FWK_MODULE_IDX_SENSOR), + FWK_ID_API(FWK_MODULE_IDX_SENSOR, 0), + &scmi_sensor_ctx.sensor_api); + if (status != FWK_SUCCESS) { + /* Failed to bind to sensor module */ + assert(false); + return status; + } + + return FWK_SUCCESS; +} + +static int scmi_sensor_process_bind_request(fwk_id_t source_id, + fwk_id_t target_id, + fwk_id_t api_id, + const void **api) +{ + if (!fwk_id_is_equal(source_id, FWK_ID_MODULE(FWK_MODULE_IDX_SCMI))) + return FWK_E_ACCESS; + + *api = &scmi_sensor_mod_scmi_to_protocol_api; + + return FWK_SUCCESS; +} + +const struct fwk_module module_scmi_sensor = { + .name = "SCMI sensor management", + .api_count = 1, + .type = FWK_MODULE_TYPE_PROTOCOL, + .init = scmi_sensor_init, + .bind = scmi_sensor_bind, + .process_bind_request = scmi_sensor_process_bind_request, +}; + +/* No elements, no module configuration data */ +struct fwk_module_config config_scmi_sensor = {}; diff --git a/module/scmi_system_power/include/internal/scmi_system_power.h b/module/scmi_system_power/include/internal/scmi_system_power.h new file mode 100644 index 00000000..bcb5b278 --- /dev/null +++ b/module/scmi_system_power/include/internal/scmi_system_power.h @@ -0,0 +1,86 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef SCMI_SYSTEM_POWER_H +#define SCMI_SYSTEM_POWER_H + +#define SCMI_PROTOCOL_ID_SYS_POWER UINT32_C(0x12) +#define SCMI_PROTOCOL_VERSION_SYS_POWER UINT32_C(0x10000) + +/* + * Identifiers of the SCMI System Power Management Protocol commands + */ +enum scmi_sys_power_command_id { + SCMI_SYS_POWER_STATE_SET = 0x003, + SCMI_SYS_POWER_STATE_GET = 0x004, + SCMI_SYS_POWER_STATE_NOTIFY = 0x005, +}; + +/* + * PROTOCOL_MESSAGE_ATTRIBUTES + */ +#define SYS_POWER_STATE_SET_ATTRIBUTES_WARM_RESET UINT32_C(0x80000000) +#define SYS_POWER_STATE_SET_ATTRIBUTES_SUSPEND UINT32_C(0x40000000) + +/* + * SYSTEM_POWER_STATE_SET + */ + +#define STATE_SET_FLAGS_MASK 0x1 +#define STATE_SET_FLAGS_GRACEFUL_REQUEST 0x1 + +enum scmi_system_state { + SCMI_SYSTEM_STATE_SHUTDOWN, + SCMI_SYSTEM_STATE_COLD_RESET, + SCMI_SYSTEM_STATE_WARM_RESET, + SCMI_SYSTEM_STATE_POWER_UP, + SCMI_SYSTEM_STATE_SUSPEND, + SCMI_SYSTEM_STATE_MAX +}; + +struct __attribute((packed)) scmi_sys_power_state_set_a2p { + uint32_t flags; + uint32_t system_state; +}; + +struct __attribute((packed)) scmi_sys_power_state_set_p2a { + int32_t status; +}; + +/* + * SYSTEM_POWER_STATE_GET + */ + +struct __attribute((packed)) scmi_sys_power_state_get_p2a { + int32_t status; + uint32_t system_state; +}; + +/* + * SYSTEM_POWER_STATE_NOTIFY + */ + +#define STATE_NOTIFY_FLAGS_MASK 0x1 + +struct __attribute((packed)) scmi_sys_power_state_notify_a2p { + uint32_t flags; +}; + +struct __attribute((packed)) scmi_sys_power_state_notify_p2a { + int32_t status; +}; + +/* + * SYSTEM_POWER_STATE_NOTIFIER + */ + +struct __attribute((packed)) scmi_sys_power_state_notifier_p2a { + uint32_t agent_id; + uint32_t system_state; +}; + +#endif diff --git a/module/scmi_system_power/include/mod_scmi_system_power.h b/module/scmi_system_power/include/mod_scmi_system_power.h new file mode 100644 index 00000000..ff8157d9 --- /dev/null +++ b/module/scmi_system_power/include/mod_scmi_system_power.h @@ -0,0 +1,69 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef MOD_SCMI_SYSTEM_POWER_H +#define MOD_SCMI_SYSTEM_POWER_H + +/*! + * \addtogroup GroupModules Modules + * @{ + */ + +/*! + * \defgroup GroupSCMI_SYS SCMI System Power Management Protocol + * @{ + */ + +/*! + * \brief SCMI system views. + */ +enum mod_scmi_system_view { + /*! OSPM view of the system */ + MOD_SCMI_SYSTEM_VIEW_OSPM, + + /*! Full view of the system */ + MOD_SCMI_SYSTEM_VIEW_FULL, + + MOD_SCMI_SYSTEM_VIEW_COUNT, +}; + +/*! + * \brief SCMI System Power Management Protocol configuration data. + */ +struct mod_scmi_system_power_config { + /*! System view */ + enum mod_scmi_system_view system_view; + + /*! + * \brief Identifier of the power domain to target for a system wake-up. + * + * \note This is only used with the OSPM view. + */ + fwk_id_t wakeup_power_domain_id; + + /*! + * \brief Composite state for a system wake-up. + * + * \note This is only used with the OSPM view. + */ + uint32_t wakeup_composite_state; + + /*! + * \brief System suspend state. + */ + unsigned int system_suspend_state; +}; + +/*! + * @} + */ + +/*! + * @} + */ + +#endif /* MOD_SCMI_SYSTEM_POWER_H */ diff --git a/module/scmi_system_power/src/Makefile b/module/scmi_system_power/src/Makefile new file mode 100644 index 00000000..de13c175 --- /dev/null +++ b/module/scmi_system_power/src/Makefile @@ -0,0 +1,11 @@ +# +# Arm SCP/MCP Software +# Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +BS_LIB_NAME := SCMI System Power Protocol +BS_LIB_SOURCES := mod_scmi_system_power.c + +include $(BS_DIR)/lib.mk diff --git a/module/scmi_system_power/src/mod_scmi_system_power.c b/module/scmi_system_power/src/mod_scmi_system_power.c new file mode 100644 index 00000000..752ef3d7 --- /dev/null +++ b/module/scmi_system_power/src/mod_scmi_system_power.c @@ -0,0 +1,443 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <assert.h> +#include <fwk_element.h> +#include <fwk_errno.h> +#include <fwk_id.h> +#include <fwk_macros.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <internal/scmi.h> +#include <internal/scmi_system_power.h> +#include <mod_power_domain.h> +#include <mod_scmi.h> +#include <mod_scmi_system_power.h> + +struct scmi_sys_power_ctx { + const struct mod_scmi_system_power_config *config; + const struct mod_scmi_from_protocol_api *scmi_api; + const struct mod_pd_restricted_api *pd_api; + fwk_id_t system_power_domain_id; +}; + +static int scmi_sys_power_version_handler(fwk_id_t service_id, + const uint32_t *payload); +static int scmi_sys_power_attributes_handler(fwk_id_t service_id, + const uint32_t *payload); +static int scmi_sys_power_msg_attributes_handler(fwk_id_t service_id, + const uint32_t *payload); +static int scmi_sys_power_state_set_handler(fwk_id_t service_id, + const uint32_t *payload); +static int scmi_sys_power_state_get_handler(fwk_id_t service_id, + const uint32_t *payload); + +/* + * Internal variables + */ +static struct scmi_sys_power_ctx scmi_sys_power_ctx; + +static int (* const handler_table[])(fwk_id_t, const uint32_t *) = { + [SCMI_PROTOCOL_VERSION] = scmi_sys_power_version_handler, + [SCMI_PROTOCOL_ATTRIBUTES] = scmi_sys_power_attributes_handler, + [SCMI_PROTOCOL_MESSAGE_ATTRIBUTES] = scmi_sys_power_msg_attributes_handler, + [SCMI_SYS_POWER_STATE_SET] = scmi_sys_power_state_set_handler, + [SCMI_SYS_POWER_STATE_GET] = scmi_sys_power_state_get_handler, +}; + +static const unsigned int payload_size_table[] = { + [SCMI_PROTOCOL_VERSION] = 0, + [SCMI_PROTOCOL_ATTRIBUTES] = 0, + [SCMI_PROTOCOL_MESSAGE_ATTRIBUTES] = + sizeof(struct scmi_protocol_message_attributes_a2p), + [SCMI_SYS_POWER_STATE_SET] = + sizeof(struct scmi_sys_power_state_set_a2p), + [SCMI_SYS_POWER_STATE_GET] = 0, +}; + +static enum mod_pd_system_shutdown system_state2system_shutdown[] = { + [SCMI_SYSTEM_STATE_SHUTDOWN] = MOD_PD_SYSTEM_SHUTDOWN, + [SCMI_SYSTEM_STATE_COLD_RESET] = MOD_PD_SYSTEM_COLD_RESET, + [SCMI_SYSTEM_STATE_WARM_RESET] = MOD_PD_SYSTEM_WARM_RESET, +}; + +static int system_state_get(enum scmi_system_state *system_state) +{ + int status; + unsigned int state; + + status = scmi_sys_power_ctx.pd_api->get_state( + scmi_sys_power_ctx.system_power_domain_id, &state); + if (status != FWK_SUCCESS) + return status; + + switch (state) { + case MOD_PD_STATE_OFF: + *system_state = SCMI_SYSTEM_STATE_SHUTDOWN; + break; + + case MOD_PD_STATE_ON: + *system_state = SCMI_SYSTEM_STATE_POWER_UP; + break; + + default: + *system_state = SCMI_SYSTEM_STATE_SUSPEND; + } + + return FWK_SUCCESS; +}; + +/* + * PROTOCOL_VERSION + */ +static int scmi_sys_power_version_handler(fwk_id_t service_id, + const uint32_t *payload) +{ + struct scmi_protocol_version_p2a return_values = { + .status = SCMI_SUCCESS, + .version = SCMI_PROTOCOL_VERSION_SYS_POWER, + }; + + scmi_sys_power_ctx.scmi_api->respond(service_id, &return_values, + sizeof(return_values)); + return FWK_SUCCESS; +} + +/* + * PROTOCOL_ATTRIBUTES + */ +static int scmi_sys_power_attributes_handler(fwk_id_t service_id, + const uint32_t *payload) +{ + struct scmi_protocol_attributes_p2a return_values = { + .status = SCMI_SUCCESS, + .attributes = 0, + }; + + scmi_sys_power_ctx.scmi_api->respond(service_id, &return_values, + sizeof(return_values)); + return FWK_SUCCESS; +} + +/* + * PROTOCOL_MESSAGE_ATTRIBUTES + */ +static int scmi_sys_power_msg_attributes_handler(fwk_id_t service_id, + const uint32_t *payload) +{ + const struct scmi_protocol_message_attributes_a2p *parameters; + unsigned int message_id; + struct scmi_protocol_message_attributes_p2a return_values; + + parameters = (const struct scmi_protocol_message_attributes_a2p*)payload; + message_id = parameters->message_id; + + if ((message_id >= FWK_ARRAY_SIZE(handler_table)) || + (handler_table[message_id] == NULL)) { + + return_values.status = SCMI_NOT_FOUND; + goto exit; + } + + return_values = (struct scmi_protocol_message_attributes_p2a) { + .status = SCMI_SUCCESS, + .attributes = 0, + }; + + if (message_id == SCMI_SYS_POWER_STATE_SET) { + return_values.attributes |= SYS_POWER_STATE_SET_ATTRIBUTES_SUSPEND | + SYS_POWER_STATE_SET_ATTRIBUTES_WARM_RESET; + } + +exit: + scmi_sys_power_ctx.scmi_api->respond(service_id, &return_values, + (return_values.status == SCMI_SUCCESS) ? sizeof(return_values) : + sizeof(return_values.status)); + + return FWK_SUCCESS; +} + +/* + * SYSTEM_POWER_STATE_SET + */ +static int scmi_sys_power_state_set_handler(fwk_id_t service_id, + const uint32_t *payload) +{ + int status = FWK_SUCCESS; + const struct scmi_sys_power_state_set_a2p *parameters; + struct scmi_sys_power_state_set_p2a return_values = { + .status = SCMI_GENERIC_ERROR, + }; + unsigned int agent_id; + enum scmi_agent_type agent_type; + enum mod_pd_system_shutdown system_shutdown; + enum scmi_system_state system_state; + + parameters = (const struct scmi_sys_power_state_set_a2p *)payload; + + if (parameters->flags & (~STATE_SET_FLAGS_MASK)) { + return_values.status = SCMI_INVALID_PARAMETERS; + goto exit; + } + + status = scmi_sys_power_ctx.scmi_api->get_agent_id(service_id, &agent_id); + if (status != FWK_SUCCESS) + goto exit; + + status = scmi_sys_power_ctx.scmi_api->get_agent_type(agent_id, &agent_type); + if (status != FWK_SUCCESS) + goto exit; + + if ((agent_type != SCMI_AGENT_TYPE_PSCI) && + (agent_type != SCMI_AGENT_TYPE_MANAGEMENT)) { + + return_values.status = SCMI_NOT_SUPPORTED; + goto exit; + } + + /* + * Graceful request from MCP not supported yet as we need the + * notifications to do that. + */ + if (parameters->flags & STATE_SET_FLAGS_GRACEFUL_REQUEST) { + return_values.status = SCMI_NOT_SUPPORTED; + goto exit; + } + + switch (parameters->system_state) { + case SCMI_SYSTEM_STATE_SHUTDOWN: + case SCMI_SYSTEM_STATE_COLD_RESET: + case SCMI_SYSTEM_STATE_WARM_RESET: + system_shutdown = + system_state2system_shutdown[parameters->system_state]; + status = scmi_sys_power_ctx.pd_api->system_shutdown(system_shutdown); + if (status != FWK_SUCCESS) + goto exit; + break; + + case SCMI_SYSTEM_STATE_SUSPEND: + status = scmi_sys_power_ctx.pd_api->system_suspend( + scmi_sys_power_ctx.config->system_suspend_state); + if (status != FWK_SUCCESS) { + if (status == FWK_E_STATE) { + status = FWK_SUCCESS; + return_values.status = SCMI_DENIED; + } + goto exit; + } + break; + + case SCMI_SYSTEM_STATE_POWER_UP: + if ((agent_type != SCMI_AGENT_TYPE_MANAGEMENT) || + (scmi_sys_power_ctx.config->system_view != + MOD_SCMI_SYSTEM_VIEW_OSPM)) { + + return_values.status = SCMI_NOT_SUPPORTED; + goto exit; + } + + status = system_state_get(&system_state); + if (status != FWK_SUCCESS) + goto exit; + + if ((system_state != SCMI_SYSTEM_STATE_SHUTDOWN) && + (system_state != SCMI_SYSTEM_STATE_SUSPEND)) { + + return_values.status = SCMI_DENIED; + goto exit; + } + + status = scmi_sys_power_ctx.pd_api->set_composite_state( + scmi_sys_power_ctx.config->wakeup_power_domain_id, + scmi_sys_power_ctx.config->wakeup_composite_state); + if (status != FWK_SUCCESS) + goto exit; + break; + + default: + return_values.status = SCMI_INVALID_PARAMETERS; + goto exit; + }; + + return_values.status = SCMI_SUCCESS; + +exit: + scmi_sys_power_ctx.scmi_api->respond(service_id, &return_values, + (return_values.status == SCMI_SUCCESS) ? sizeof(return_values) : + sizeof(return_values.status)); + + return status; +} + +/* + * SYSTEM_POWER_STATE_GET + */ +static int scmi_sys_power_state_get_handler(fwk_id_t service_id, + const uint32_t *payload) +{ + int status = FWK_SUCCESS; + struct scmi_sys_power_state_get_p2a return_values = { + .status = SCMI_GENERIC_ERROR, + }; + enum scmi_system_state system_state; + unsigned int agent_id; + enum scmi_agent_type agent_type; + + status = scmi_sys_power_ctx.scmi_api->get_agent_id(service_id, &agent_id); + if (status != FWK_SUCCESS) + goto exit; + + status = scmi_sys_power_ctx.scmi_api->get_agent_type(agent_id, &agent_type); + if (status != FWK_SUCCESS) + goto exit; + + return_values.status = SCMI_NOT_SUPPORTED; + if (scmi_sys_power_ctx.config->system_view == MOD_SCMI_SYSTEM_VIEW_FULL) + goto exit; + else { + if (agent_type == SCMI_AGENT_TYPE_PSCI) + goto exit; + + status = system_state_get(&system_state); + if (status != FWK_SUCCESS) + goto exit; + + return_values = (struct scmi_sys_power_state_get_p2a) { + .status = SCMI_SUCCESS, + .system_state = system_state, + }; + } + +exit: + scmi_sys_power_ctx.scmi_api->respond(service_id, &return_values, + (return_values.status == SCMI_SUCCESS) ? sizeof(return_values) : + sizeof(return_values.status)); + + return status; +} + +/* + * SCMI module -> SCMI system power module interface + */ +static int scmi_sys_power_get_scmi_protocol_id(fwk_id_t protocol_id, + uint8_t *scmi_protocol_id) +{ + int status; + + status = fwk_module_check_call(protocol_id); + if (status != FWK_SUCCESS) + return status; + + *scmi_protocol_id = SCMI_PROTOCOL_ID_SYS_POWER; + + return FWK_SUCCESS; +} + +static int scmi_sys_power_handler(fwk_id_t protocol_id, + fwk_id_t service_id, + const uint32_t *payload, + size_t payload_size, + unsigned int message_id) +{ + int status; + int32_t return_value; + + static_assert(FWK_ARRAY_SIZE(handler_table) == + FWK_ARRAY_SIZE(payload_size_table), + "[SCMI] System power protocol table sizes not consistent"); + + status = fwk_module_check_call(protocol_id); + if (status != FWK_SUCCESS) + return status; + assert(payload != NULL); + + if (message_id >= FWK_ARRAY_SIZE(handler_table)) { + return_value = SCMI_NOT_SUPPORTED; + goto error; + } + + if (payload_size != payload_size_table[message_id]) { + /* Incorrect payload size or message is not supported */ + return_value = SCMI_PROTOCOL_ERROR; + goto error; + } + + return handler_table[message_id](service_id, payload); + +error: + scmi_sys_power_ctx.scmi_api->respond(service_id, &return_value, + sizeof(return_value)); + return FWK_SUCCESS; +} + +static struct mod_scmi_to_protocol_api scmi_sys_power_mod_scmi_to_protocol = { + .get_scmi_protocol_id = scmi_sys_power_get_scmi_protocol_id, + .message_handler = scmi_sys_power_handler, +}; + +/* + * Framework handlers + */ +static int scmi_sys_power_init(fwk_id_t module_id, unsigned int element_count, + const void *data) +{ + scmi_sys_power_ctx.config = data; + + return FWK_SUCCESS; +} + +static int scmi_sys_power_bind(fwk_id_t id, unsigned int round) +{ + int status; + int pd_count; + + if (round != 0) + return FWK_SUCCESS; + + /* Bind to SCMI module */ + status = fwk_module_bind(FWK_ID_MODULE(FWK_MODULE_IDX_SCMI), + FWK_ID_API(FWK_MODULE_IDX_SCMI, + MOD_SCMI_API_IDX_PROTOCOL), + &scmi_sys_power_ctx.scmi_api); + if (status != FWK_SUCCESS) + return status; + + /* Bind to POWER DOMAIN module */ + status = fwk_module_bind(fwk_module_id_power_domain, + mod_pd_api_id_restricted, &scmi_sys_power_ctx.pd_api); + if (status != FWK_SUCCESS) + return status; + + pd_count = fwk_module_get_element_count(fwk_module_id_power_domain); + if (pd_count <= 0) + return FWK_E_SUPPORT; + + scmi_sys_power_ctx.system_power_domain_id = + FWK_ID_ELEMENT(FWK_MODULE_IDX_POWER_DOMAIN, pd_count - 1); + + return FWK_SUCCESS; +} + +static int scmi_sys_power_process_bind_request(fwk_id_t source_id, + fwk_id_t _target_id, fwk_id_t api_id, const void **api) +{ + if (!fwk_id_is_equal(source_id, FWK_ID_MODULE(FWK_MODULE_IDX_SCMI))) + return FWK_E_ACCESS; + + *api = &scmi_sys_power_mod_scmi_to_protocol; + + return FWK_SUCCESS; +} + +const struct fwk_module module_scmi_system_power = { + .name = "SCMI System Power Management Protocol", + .api_count = 1, + .type = FWK_MODULE_TYPE_PROTOCOL, + .init = scmi_sys_power_init, + .bind = scmi_sys_power_bind, + .process_bind_request = scmi_sys_power_process_bind_request, +}; diff --git a/module/sds/include/mod_sds.h b/module/sds/include/mod_sds.h new file mode 100644 index 00000000..cd75a396 --- /dev/null +++ b/module/sds/include/mod_sds.h @@ -0,0 +1,187 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef MOD_SDS_H +#define MOD_SDS_H + +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <fwk_element.h> +#include <fwk_id.h> + +/*! + * \addtogroup GroupModules Modules + * @{ + */ + +/*! + * \defgroup GroupSDS Shared Data Storage + * + * \details Provides a framework for the structured storage of data that is + * shared between the SCP Firmware and application processor firmware. + * + * @{ + */ + +/* + * Bit positions for the fields that make up a structure's unique, + * 32-bit identifier. This value consists of the 16-bit ID, an 8-bit major + * version number and an 8-bit minor version number. + */ + +/*! Position of the identifier field. */ +#define MOD_SDS_ID_IDENTIFIER_POS 0 +/*! Position of the minor version field. */ +#define MOD_SDS_ID_VERSION_MINOR_POS 16 +/*! Position of the major version field. */ +#define MOD_SDS_ID_VERSION_MAJOR_POS 24 + +/* + * Masks for the structure identifier fields. + */ + +/*! Mask for the identifier field. */ +#define MOD_SDS_ID_IDENTIFIER_MASK 0x0000FFFF +/*! Mask for the minor version field. */ +#define MOD_SDS_ID_VERSION_MINOR_MASK 0x00FF0000 +/*! Mask for the major version field. */ +#define MOD_SDS_ID_VERSION_MAJOR_MASK 0xFF000000 + +/*! + * \brief Element descriptor that describes an SDS structure that will be + * automatically created during element initialization. + */ +struct mod_sds_structure_desc { + /*! Identifier of the structure to be created. */ + uint32_t id; + /*! Size, in bytes, of the structure. */ + size_t size; + /*! + * Payload of the structure. If not equal to NULL, as part of the + * initialization of the module's elements, the payload of the structure + * identified by 'id' is initalized/updated to the value pointed to by + * 'payload'. + */ + const void *payload; + /*! Set the valid flag in the structure if true. */ + bool finalize; +}; + +/*! + * \brief Module configuration. + */ +struct mod_sds_config { + /*! Base address of the region used for shared data storage */ + uintptr_t region_base_address; + + /*! + * Size, in bytes, of the SDS Memory Region. If the SDS module is + * initialized and a region already exists then the region will be + * resized to match the size given here. The existing region may be + * grown or shrunk, as required. In the latter case the new size must + * be sufficient to hold the existing contents and if it is not then + * the initialization process will fail. + */ + size_t region_size; + + /*! Identifier of the clock that this module depends on */ + fwk_id_t clock_id; +}; + +/*! + * \brief Module interface. + */ +struct mod_sds_api { + /*! + * \brief Write data within a Shared Data Structure. + * + * \details Write data to a given offset within a Shared Data Structure. The + * SDS module ensures that the data being written is properly + * contained within the structure but it is the caller's responsibility + * to ensure that the correct field or fields are being updated. + * + * \note A single write may update a portion of a field, a complete + * field, or multiple fields, depending on the definition of the + * structure and the size of the data being written. + * + * \param structure_id The identifier of the Shared Data Structure into + * which data will be written. + * + * \param offset The offset, in bytes, of the field within the Shared Data + * Structure. + * + * \param data Pointer to the data that will be written into the Shared Data + * Structure's field. + * + * \param size Size, in bytes, of the data to be written. + * + * \retval FWK_SUCCESS Data was successfully written to the structure. + * \retval FWK_E_PARAM The data pointer parameter was NULL. + * \retval FWK_E_PARAM An invalid structure identifier was provided. + * \retval FWK_E_RANGE The field extends outside of the structure bounds. + */ + int (*struct_write)(uint32_t structure_id, unsigned int offset, + const void *data, size_t size); + + /*! + * \brief Read data from within a Shared Data Structure. + * + * \details Read data from a given offset within a Shared Data Structure. + * The SDS module ensures that the data being read is contained + * completely within the structure but it is the caller's + * responsibility to ensure that the correct field(s) are being read. + * + * A single read may return only a portion of a field, a complete + * field, or multiple fields, depending on the definition of the + * structure and the value of the size parameter. + * + * \note Reading from a structure that has not been finalized is permitted + * but discouraged. + * + * \param structure_id The identifier of the Shared Data Structure from + * which data will be read. + * + * \param offset The offset, in bytes, of the field within the Shared Data + * Structure. + * + * \param data Pointer to storage for the field data that will be read. + * + * \param size Size, in bytes, of the storage pointed to by the data + * parameter. + * + * \retval FWK_SUCCESS Data was successfully read from the structure. + * \retval FWK_E_PARAM The data pointer parameter was NULL. + * \retval FWK_E_PARAM An invalid structure identifier was provided. + * \retval FWK_E_RANGE The field extends outside of the structure bounds. + */ + int (*struct_read)(uint32_t structure_id, unsigned int offset, void *data, + size_t size); + + /*! + * \brief Mark a Shared Data Structure as valid and ready for use by + * application processor firmware. + * + * \param structure_id The identifier of the Shared Data Structure to + * finalize. + * + * \retval FWK_SUCCESS The structure was successfully finalized. + * \retval FWK_E_PARAM An invalid structure identifier was provided. + * \retval FWK_E_STATE The structure has already been finalized. + */ + int (*struct_finalize)(uint32_t structure_id); +}; + +/*! + * @} + */ + +/*! + * @} + */ + +#endif /* MOD_SDS_H */ diff --git a/module/sds/src/Makefile b/module/sds/src/Makefile new file mode 100644 index 00000000..e813dc5b --- /dev/null +++ b/module/sds/src/Makefile @@ -0,0 +1,11 @@ +# +# Arm SCP/MCP Software +# Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +BS_LIB_NAME := "Shared Data Storage" +BS_LIB_SOURCES = mod_sds.c + +include $(BS_DIR)/lib.mk diff --git a/module/sds/src/mod_sds.c b/module/sds/src/mod_sds.c new file mode 100644 index 00000000..98144557 --- /dev/null +++ b/module/sds/src/mod_sds.c @@ -0,0 +1,620 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <assert.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> +#include <fwk_element.h> +#include <fwk_errno.h> +#include <fwk_macros.h> +#include <fwk_module.h> +#include <fwk_notification.h> +#include <mod_clock.h> +#include <mod_sds.h> + +/* Arbitrary, 16 bit value that indicates a valid SDS Memory Region */ +#define REGION_SIGNATURE 0xAA7A +/* The minor version of the SDS schema supported by this implementation */ +#define SUPPORTED_VERSION_MINOR 0x0 +/* The major version of the SDS schema supported by this implementation */ +#define SUPPORTED_VERSION_MAJOR 0x1 + +/* Minimum required byte alignment for fields within structures */ +#define MIN_FIELD_ALIGNMENT 4 +/* Minimum required byte alignment for structures within the region */ +#define MIN_STRUCT_ALIGNMENT 8 +/* Minimum structure size in bytes */ +#define MIN_STRUCT_SIZE 4 + +/* Header containing Shared Data Structure metadata */ +struct structure_header { + /* + * Compound identifier value consisting of a linear structure ID, a major + * version, and a minor version. Together these uniquely identify the + * structure. + */ + uint32_t id; + + /* + * Flag indicating whether the structure's content is valid. Always false + * initially, the flag is set to true when the structure is finalized. + */ + bool valid : 1; + + /* + * Size, in bytes, of the structure. The size of the header is not included, + * only the structure content. If padding was used to maintain alignment + * during the structure's creation then the additional space used for the + * padding will be reflected in this value. + */ + uint32_t size : 23; + + /* Reserved */ + uint32_t reserved : 8; +}; + +/* Region Descriptor describing the SDS Memory Region */ +struct region_descriptor { + /* + * Field used to determine the presence, or absence, of an SDS Memory Region + * and Region Descriptor. When the region is initialized the signature field + * is given a fixed value according to the REGION_SIGNATURE definition. + */ + uint16_t signature; + + /* + * The total number of structures, finalized or otherwise, within the SDS + * Memory Region. + */ + uint16_t structure_count : 8; + + /* The minor version component of the implemented SDS specification */ + uint16_t version_minor : 4; + + /* The major version component of the implemented SDS specification */ + uint16_t version_major : 4; + + /* + * The total size of the SDS Memory Region, in bytes. The value includes the + * size of the Region Descriptor because the descriptor is located within + * the region that it describes. This is in contrast to structure headers, + * which are considered to be prepended to the memory allocated for the + * structure content. + */ + uint32_t region_size; +}; + +/* Module context structure*/ +struct sds_ctx { + /* Pointer to the module configuration. */ + const struct mod_sds_config *module_config; + + /* Pointer to the base of the SDS Memory Region. */ + uint8_t *mem_base; + + /* Size of the SDS Memory Region in bytes. */ + unsigned int mem_size; + + /* Remaining, unused memory in the SDS Memory Region, in bytes. */ + unsigned int mem_free; + + /* Pointer to the next free memory address in the SDS Memory Region. */ + uint8_t *mem_next_free; + + /* Pointer to the Region Descriptor structure at the memory region base. */ + struct region_descriptor *region_desc; +}; + +/* Module context */ +static struct sds_ctx ctx; + +/* + * Static functions + */ + +/* + * Perform some tests to determine whether any of the fields within a Structure + * Header contain obviously invalid data. + */ +static bool header_is_valid(struct structure_header *header) +{ + if (header->id == 0) + return false; /* Zero is not a valid identifier */ + + if ((header->id >> MOD_SDS_ID_VERSION_MAJOR_POS) == 0) + return false; /* 0 is not a valid major version */ + + if (header->size < FWK_ALIGN_NEXT(MIN_STRUCT_SIZE, MIN_STRUCT_ALIGNMENT)) + return false; /* Padded structure size is less than the minimum */ + + if (header->size > (ctx.mem_size - sizeof(struct structure_header))) + /* Structure exceeds the capacity of the SDS Memory Region */ + return false; + + if ((header->size % MIN_STRUCT_ALIGNMENT) != 0) + return false; /* Structure does not meet alignment requirements */ + + return true; +} + +static bool validate_structure_access(uint32_t structure_size, uint32_t offset, + size_t access_size) +{ + if ((offset >= structure_size) || (access_size > structure_size)) + return FWK_E_PARAM; + + if ((structure_size - offset) < access_size) + return FWK_E_PARAM; + + return FWK_SUCCESS; +} + +/* + * Search the SDS Memory Region for a given structure ID and return a + * copy of the Structure Header that holds its information. Optionally, a + * uint8_t pointer pointer may be provided to retrieve a pointer to the base + * address of the structure content. + * + * The validity of the header is checked before its contents are returned. These + * tests check that the structure size is sane, and that the entire contents of + * the structure are contained within the SDS Memory Region. + * + * If the address of the structure header in the SDS Memory Region is needed + * (as opposed to a copy of the header contents) then this can be calculated by + * subtracting the size of the header from the structure base address obtained + * from this function. + * + * If a structure with the given ID is not present then FWK_E_PARAM is returned. + */ +static int get_structure_info(uint32_t structure_id, + struct structure_header *header, + uint8_t **structure_base) +{ + unsigned int struct_idx; + struct structure_header current_header; + uint32_t offset; + + offset = sizeof(struct region_descriptor); + + /* Iterate over structure headers to find one with a matching ID */ + for (struct_idx = 0; struct_idx < ctx.region_desc->structure_count; + struct_idx++) { + current_header = *(struct structure_header *)(ctx.mem_base + offset); + if (!header_is_valid(¤t_header)) + return FWK_E_DATA; + + if (current_header.id == structure_id) { + if (structure_base != NULL) + *structure_base = ((uint8_t *)(ctx.mem_base + offset)) + + sizeof(struct structure_header); + + *header = current_header; + return FWK_SUCCESS; + } + + offset += current_header.size; + offset += sizeof(struct structure_header); + if (offset >= ctx.mem_size) + return FWK_E_RANGE; + } + + return FWK_E_PARAM; +} + +/* + * Search the SDS Memory Region to determine if a structure with the given ID + * is present. + */ +static bool structure_exists(uint32_t structure_id) +{ + int status; + struct structure_header header; + + status = get_structure_info(structure_id, &header, NULL); + return status == FWK_SUCCESS; +} + +static int struct_alloc(uint32_t structure_id, size_t size) +{ + struct structure_header *header = NULL; + unsigned int padded_size; + unsigned int status; + + if (size < MIN_STRUCT_SIZE) + return FWK_E_PARAM; + + padded_size = FWK_ALIGN_NEXT(size, MIN_STRUCT_ALIGNMENT); + + if (padded_size + sizeof(*header) > ctx.mem_free) { + status = FWK_E_NOMEM; + goto exit; + } + + if (structure_exists(structure_id)) { + status = FWK_E_RANGE; + goto exit; + } + + /* Create the Structure Header */ + header = (struct structure_header *)ctx.mem_next_free; + header->id = structure_id; + header->size = padded_size; + header->valid = false; + ctx.mem_next_free += sizeof(*header); + ctx.mem_free -= sizeof(*header); + + /* Zero the memory reserved for the structure, avoiding the header */ + memset(ctx.mem_next_free, 0, padded_size); + ctx.mem_next_free += padded_size; + ctx.mem_free -= padded_size; + + /* Increment the structure count within the region descriptor */ + ctx.region_desc->structure_count++; + + status = FWK_SUCCESS; + +exit: + return status; +} + +/* + * Read an SDS Memory Region to determine its properties (through the Region + * Descriptor) such as its size limit, its schema version and the current number + * of structures stored within the region. Any Structure Headers that are + * found within the region will be checked for validity. + * + * Once the existing contents have been evaluated the memory region will be + * resized if the RAM Firmware has defined a different region size than the ROM + * Firmware. The resizing process only takes place if the new region size is + * not the same as the existing size. When resizing, the Region Descriptor's + * 'region size' field is modified but the contents of the region are not + * modified in any way. + * + * Finally, the total size of the structures and headers that are present + * is subtracted from the size of the memory region to determine the amount + * of free memory that remains. + */ +static int reinitialize_memory_region(void) +{ + unsigned int struct_idx; + uint32_t mem_used; + struct structure_header header; + + if (ctx.region_desc->signature != REGION_SIGNATURE) + return FWK_E_DATA; + + if (ctx.region_desc->version_major != SUPPORTED_VERSION_MAJOR) + return FWK_E_DATA; + + mem_used = sizeof(struct region_descriptor); + + for (struct_idx = 0; struct_idx < ctx.region_desc->structure_count; + struct_idx++) { + header = *(struct structure_header *)(ctx.mem_base + mem_used); + + if (!header_is_valid(&header)) + return FWK_E_DATA; /* Unexpected invalid header */ + + mem_used += header.size; + mem_used += sizeof(struct structure_header); + if (mem_used > ctx.region_desc->region_size) + return FWK_E_SIZE; + } + + if (ctx.mem_size < mem_used) + return FWK_E_SIZE; + + /* + * Update the SDS Memory Region size (if different between ROM and RAM) and + * the amount of free memory left in the SDS Memory Region. + */ + if (ctx.region_desc->region_size != ctx.mem_size) + ctx.region_desc->region_size = ctx.mem_size; + + ctx.mem_free = ctx.mem_size - mem_used; + + /* + * Initialize the pointer that holds the next free memory location for a + * Shared Data Structure. This follows the last Shared Data Structure, or + * the Region Descriptor if there are no existing structures. + */ + ctx.mem_next_free = ctx.mem_base + mem_used; + + return FWK_SUCCESS; +} + +/* + * Initialize an empty SDS Memory Region so that it is ready for use. + */ +static int create_memory_region(void) +{ + unsigned int min_storage; + + /* + * Check that the SDS Memory Region is large enough to support both the + * Region Descriptor and a single Shared Data Structure of the minimum + * permitted size. + */ + min_storage = sizeof(struct region_descriptor) + + FWK_ALIGN_NEXT(MIN_STRUCT_SIZE, MIN_STRUCT_ALIGNMENT); + if (ctx.mem_size < min_storage) + return FWK_E_NOMEM; + + ctx.mem_free = ctx.mem_size - sizeof(struct region_descriptor); + + /* + * Update the Region Descriptor + */ + ctx.region_desc->signature = REGION_SIGNATURE; + ctx.region_desc->structure_count = 0; + ctx.region_desc->version_major = SUPPORTED_VERSION_MAJOR; + ctx.region_desc->version_minor = SUPPORTED_VERSION_MINOR; + ctx.region_desc->region_size = ctx.mem_size; + + /* + * Initialize the pointer that holds the next free memory location for + * placing a Structure Header. Initially this follows the Region Descriptor + * directly. + */ + ctx.mem_next_free = ctx.mem_base + sizeof(struct region_descriptor); + assert(((uintptr_t)ctx.mem_next_free % + MIN_STRUCT_ALIGNMENT) == 0); + + return FWK_SUCCESS; +} + +static int struct_write(uint32_t structure_id, unsigned int offset, + const void *data, size_t size) +{ + int status; + uint8_t *structure_base; + struct structure_header header; + + assert(data != NULL); + assert(size != 0); + + /* Look up the Structure Header by its identifier */ + status = get_structure_info(structure_id, &header, &structure_base); + if (status != FWK_SUCCESS) + return status; + + /* + * Perform sanity checks on the field location and data size before invoking + * memcpy. + */ + status = validate_structure_access(header.size, offset, size); + if (status != FWK_SUCCESS) + return status; + + memcpy(structure_base + offset, data, size); + return FWK_SUCCESS; +} + +static int struct_finalize(uint32_t structure_id) +{ + int status; + uint8_t *structure_base; + struct structure_header header; + struct structure_header *header_mem; + + /* Check that the structure being finalized exists */ + status = get_structure_info(structure_id, &header, &structure_base); + if (status != FWK_SUCCESS) + return status; + + /* Update the valid flag of the header within the SDS Memory Region */ + header_mem = (struct structure_header *)(structure_base - sizeof(header)); + header_mem->valid = true; + + return FWK_SUCCESS; +} + +static int struct_init(const struct mod_sds_structure_desc *struct_desc) +{ + int status = FWK_SUCCESS; + + /* If the structure does not already exist, allocate it. */ + if (!structure_exists(struct_desc->id)) { + status = struct_alloc(struct_desc->id, struct_desc->size); + if (status != FWK_SUCCESS) + return status; + } + + if (struct_desc->payload != NULL) { + status = struct_write(struct_desc->id, 0, struct_desc->payload, + struct_desc->size); + if (status != FWK_SUCCESS) + return status; + } + + /* Finalize the structure immediately if required */ + if (struct_desc->finalize) + status = struct_finalize(struct_desc->id); + + return status; +} + +/* + * Module API + */ + +static int sds_struct_write(uint32_t structure_id, unsigned int offset, + const void *data, size_t size) +{ + int status; + + status = fwk_module_check_call(fwk_module_id_sds); + if (status != FWK_SUCCESS) + return status; + + if (data == NULL) + return FWK_E_PARAM; + + if (size == 0) + return FWK_E_PARAM; + + return struct_write(structure_id, offset, data, size); +} + +static int sds_struct_read(uint32_t structure_id, unsigned int offset, + void *data, size_t size) +{ + int status; + uint8_t *structure_base; + struct structure_header header; + + status = fwk_module_check_call(fwk_module_id_sds); + if (status != FWK_SUCCESS) + return status; + + if (data == NULL) + return FWK_E_PARAM; + + if (size == 0) + return FWK_E_PARAM; + + /* Check if a structure with this ID exists */ + status = get_structure_info(structure_id, &header, &structure_base); + if (status != FWK_SUCCESS) + return status; + + /* + * Perform sanity checks on the field location and data size before invoking + * memcpy. + */ + status = validate_structure_access(header.size, offset, size); + if (status != FWK_SUCCESS) + return status; + + memcpy(data, structure_base + offset, size); + return FWK_SUCCESS; +} + +static int sds_struct_finalize(uint32_t structure_id) +{ + int status; + + status = fwk_module_check_call(fwk_module_id_sds); + if (status != FWK_SUCCESS) + return status; + + return struct_finalize(structure_id); +} + +static const struct mod_sds_api module_api = { + .struct_write = sds_struct_write, + .struct_read = sds_struct_read, + .struct_finalize = sds_struct_finalize, +}; + +/* + * Framework handlers + */ + +static int sds_init(fwk_id_t module_id, unsigned int element_count, + const void *data) +{ + if (data == NULL) + return FWK_E_PARAM; + + ctx.module_config = data; + + assert((MIN_STRUCT_ALIGNMENT % MIN_FIELD_ALIGNMENT) == 0); + + if (ctx.module_config->region_base_address == 0) + return FWK_E_PARAM; + if (((uintptr_t)ctx.module_config->region_base_address % + MIN_STRUCT_ALIGNMENT) > 0) + return FWK_E_PARAM; + + ctx.mem_base = (uint8_t *)ctx.module_config->region_base_address; + ctx.mem_size = ctx.module_config->region_size; + ctx.region_desc = (struct region_descriptor *)ctx.mem_base; + + return FWK_SUCCESS; +} + +static int sds_element_init(fwk_id_t element_id, unsigned int unused, + const void *data) +{ + return FWK_SUCCESS; +} + +static int sds_process_bind_request(fwk_id_t requester_id, fwk_id_t id, + fwk_id_t api_id, const void **api) +{ + if (!fwk_module_is_valid_module_id(requester_id)) + return FWK_E_ACCESS; + + *api = &module_api; + return FWK_SUCCESS; +} + +static int sds_start(fwk_id_t id) +{ + if (!fwk_id_is_type(id, FWK_ID_TYPE_MODULE)) + return FWK_SUCCESS; + + /* Register the module for clock state notifications */ + return fwk_notification_subscribe( + mod_clock_notification_id_state_changed, + ctx.module_config->clock_id, + id); +} + +static int sds_process_notification( + const struct fwk_event *event, + struct fwk_event *resp_event) +{ + int status; + struct clock_notification_params *params; + int element_idx; + int element_count; + const struct mod_sds_structure_desc *struct_desc; + + assert(fwk_id_is_equal(event->id, mod_clock_notification_id_state_changed)); + assert(fwk_id_is_type(event->target_id, FWK_ID_TYPE_MODULE)); + + params = (struct clock_notification_params *)event->params; + if (params->new_state != MOD_CLOCK_STATE_RUNNING) + return FWK_SUCCESS; + + /* Either reinitialize the memory region, or create it for the first time */ + status = reinitialize_memory_region(); + if (status != FWK_SUCCESS) { + status = create_memory_region(); + if (status != FWK_SUCCESS) + return status; + } + + element_count = fwk_module_get_element_count(event->target_id); + for (element_idx = 0; element_idx < element_count; ++element_idx) { + struct_desc = fwk_module_get_data( + fwk_id_build_element_id(event->target_id, element_idx)); + + status = struct_init(struct_desc); + if (status != FWK_SUCCESS) + return status; + } + + return FWK_SUCCESS; +} + +/* Module descriptor */ +const struct fwk_module module_sds = { + .name = "Shared Data Storage", + .type = FWK_MODULE_TYPE_SERVICE, + .api_count = 1, + .event_count = 0, + .init = sds_init, + .element_init = sds_element_init, + .process_bind_request = sds_process_bind_request, + .start = sds_start, + .process_notification = sds_process_notification +}; diff --git a/module/sensor/include/mod_sensor.h b/module/sensor/include/mod_sensor.h new file mode 100644 index 00000000..4420fed8 --- /dev/null +++ b/module/sensor/include/mod_sensor.h @@ -0,0 +1,234 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef MOD_SENSOR_H +#define MOD_SENSOR_H + +#include <fwk_id.h> +#include <stdint.h> +#include <stdbool.h> + +/*! + * \addtogroup GroupModules Modules + * @{ + */ + +/*! + * \defgroup GroupModuleSensor Sensor + * + * \brief Module for reading hardware sensors. + * + * \details Module for interfacing with and reading various hardware sensors. + * + * @{ + */ + +/*! + * \brief Sensor types as defined by SCMI + */ +enum mod_sensor_type { + MOD_SENSOR_TYPE_NONE = 0, + MOD_SENSOR_TYPE_UNSPECIFIED, + MOD_SENSOR_TYPE_DEGREES_C, + MOD_SENSOR_TYPE_DEGREES_F, + MOD_SENSOR_TYPE_DEGREES_K, + MOD_SENSOR_TYPE_VOLTS, + MOD_SENSOR_TYPE_AMPS, + MOD_SENSOR_TYPE_WATTS, + MOD_SENSOR_TYPE_JOULES, + MOD_SENSOR_TYPE_COULOMBS, + MOD_SENSOR_TYPE_VA, + MOD_SENSOR_TYPE_NITS, + MOD_SENSOR_TYPE_LUMENS, + MOD_SENSOR_TYPE_LUX, + MOD_SENSOR_TYPE_CANDELAS, + MOD_SENSOR_TYPE_KPA, + MOD_SENSOR_TYPE_PSI, + MOD_SENSOR_TYPE_NEWTONS, + MOD_SENSOR_TYPE_CFM, + MOD_SENSOR_TYPE_RPM, + MOD_SENSOR_TYPE_HERTZ, + MOD_SENSOR_TYPE_SECONDS, + MOD_SENSOR_TYPE_MINUTES, + MOD_SENSOR_TYPE_HOURS, + MOD_SENSOR_TYPE_DAYS, + MOD_SENSOR_TYPE_WEEKS, + MOD_SENSOR_TYPE_MILS, + MOD_SENSOR_TYPE_INCHES, + MOD_SENSOR_TYPE_FEET, + MOD_SENSOR_TYPE_CUBIC_INCHES, + MOD_SENSOR_TYPE_CUBIC_FEET, + MOD_SENSOR_TYPE_METERS, + MOD_SENSOR_TYPE_CUBIC_CENTIMETERS, + MOD_SENSOR_TYPE_CUBIC_METERS, + MOD_SENSOR_TYPE_LITRES, + MOD_SENSOR_TYPE_FLUID_OUNCES, + MOD_SENSOR_TYPE_RADIANS, + MOD_SENSOR_TYPE_STERADIANS, + MOD_SENSOR_TYPE_REVOLUTIONS, + MOD_SENSOR_TYPE_CYCLES, + MOD_SENSOR_TYPE_GRAVITIES, + MOD_SENSOR_TYPE_OUNCES, + MOD_SENSOR_TYPE_POUNDS, + MOD_SENSOR_TYPE_FOOT_POUNDS, + MOD_SENSOR_TYPE_OUNCE_INCHES, + MOD_SENSOR_TYPE_GAUSS, + MOD_SENSOR_TYPE_GILBERTS, + MOD_SENSOR_TYPE_HENRIES, + MOD_SENSOR_TYPE_FARADS, + MOD_SENSOR_TYPE_OHMS, + MOD_SENSOR_TYPE_SIEMENS, + MOD_SENSOR_TYPE_MOLES, + MOD_SENSOR_TYPE_BECQUERELS, + MOD_SENSOR_TYPE_PPM, + MOD_SENSOR_TYPE_DECIBELS, + MOD_SENSOR_TYPE_DBA, + MOD_SENSOR_TYPE_DBC, + MOD_SENSOR_TYPE_GRAYS, + MOD_SENSOR_TYPE_SIEVERTS, + MOD_SENSOR_TYPE_COLOR_TEMP_DEGREES_K, + MOD_SENSOR_TYPE_BITS, + MOD_SENSOR_TYPE_BYTES, + MOD_SENSOR_TYPE_WORDS, + MOD_SENSOR_TYPE_DWORDS, + MOD_SENSOR_TYPE_QWORDS, + MOD_SENSOR_TYPE_PERCENTAGE, + MOD_SENSOR_TYPE_PASCALS, + MOD_SENSOR_TYPE_COUNTS, + MOD_SENSOR_TYPE_GRAMS, + MOD_SENSOR_TYPE_NEWTON_METERS, + MOD_SENSOR_TYPE_HITS, + MOD_SENSOR_TYPE_MISSES, + MOD_SENSOR_TYPE_RETRIES, + MOD_SENSOR_TYPE_OVERRUNS, + MOD_SENSOR_TYPE_UNDERRUNS, + MOD_SENSOR_TYPE_COLLISIONS, + MOD_SENSOR_TYPE_PACKETS, + MOD_SENSOR_TYPE_MESSAGES, + MOD_SENSOR_TYPE_CHARACTERS, + MOD_SENSOR_TYPE_ERRORS, + MOD_SENSOR_TYPE_CORRECTED_ERRORS, + MOD_SENSOR_TYPE_UNCORRECTABLE_ERRORS, + MOD_SENSOR_TYPE_SQUARE_MILS, + MOD_SENSOR_TYPE_SQUARE_INCHES, + MOD_SENSOR_TYPE_SQUARE_FEET, + MOD_SENSOR_TYPE_SQUARE_CENTIMETERS, + MOD_SENSOR_TYPE_SQUARE_METERS, + MOD_SENSOR_TYPE_OEM_UNIT = 0xFF, + MOD_SENSOR_TYPE_COUNT +}; + +/*! + * \brief Structure containing all sensor information + * + * \details Sensor information structure used to configure the sensor and serve + * SCMI requests. + */ +struct mod_sensor_info { + /*! SCMI sensor type */ + enum mod_sensor_type type; + + /*! Pointer to NULL-terminated name string */ + const char *name; + + /*! Time (in seconds) between sensor updates. Set this field to 0 to + * indicate that the sensor does not have a minimum update interval. This + * field is used with \ref update_interval_multiplier to calculate the + * actual update_interval. + */ + unsigned int update_interval; + + /*! + * Power-of-10 multiplier for \ref update_interval \n\n + * This is used to calculate the actual interval time:\n + * actual = \ref update_interval x10^(\ref update_interval_multiplier)\n + */ + int update_interval_multiplier; + + /*! + * Power-of-10 multiplier applied to the unit (specified by \ref type)\n\n + * Used like this: unit x10^(\ref unit_multiplier) + */ + int unit_multiplier; +}; + +/*! + * \brief Sensor device configuration + * + * \details Configuration structure for individual sensors + */ +struct mod_sensor_dev_config { + /*! Module or element id of the driver */ + fwk_id_t driver_id; + + /*! Sensor information */ + struct mod_sensor_info *info; +}; + +/*! + * \brief Sensor driver API + * + * \details Api used by this module to interface with the driver + */ +struct mod_sensor_driver_api { + /*! + * \brief Read sensor value + * + * \details Synchronously read current sensor value + * + * \param id Specific sensor device id + * \param value Pointer to storage for the sensor value + * + * \retval FWK_SUCCESS Value was read successfully + * + * One of the other specific error codes described by the module. + */ + int (*get_value)(fwk_id_t id, uint64_t *value); +}; + +/*! + * \brief Sensor API + */ +struct mod_sensor_api { + /*! + * \brief Read sensor value + * + * \details Synchronously read current sensor value + * + * \param id Specific sensor device id + * \param value Pointer to storage for the sensor value + * + * \retval FWK_SUCCESS Operation succeeded + * + * One of the other specific error codes described by the module. + */ + int (*get_value)(fwk_id_t id, uint64_t *value); + + /*! + * \brief Get sensor information + * + * \details Get a pointer to the sensor_info structure of a specific sensor + * + * \param id Specific sensor device id + * \param value Pointer to storage for the info struct + * + * \retval FWK_SUCCESS Operation succeeded + * + * One of the other specific error codes described by the module. + */ + int (*get_info)(fwk_id_t id, struct mod_sensor_info *info); +}; + +/*! + * @} + */ + +/*! + * @} + */ + +#endif /* MOD_SENSOR_H */ diff --git a/module/sensor/src/Makefile b/module/sensor/src/Makefile new file mode 100644 index 00000000..c6eeef7f --- /dev/null +++ b/module/sensor/src/Makefile @@ -0,0 +1,11 @@ +# +# Arm SCP/MCP Software +# Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +BS_LIB_NAME := Sensor +BS_LIB_SOURCES := mod_sensor.c + +include $(BS_DIR)/lib.mk diff --git a/module/sensor/src/mod_sensor.c b/module/sensor/src/mod_sensor.c new file mode 100644 index 00000000..26697438 --- /dev/null +++ b/module/sensor/src/mod_sensor.c @@ -0,0 +1,182 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <assert.h> +#include <stdbool.h> +#include <fwk_errno.h> +#include <fwk_id.h> +#include <fwk_mm.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <mod_sensor.h> + +struct sensor_dev_ctx { + struct mod_sensor_dev_config *config; + struct mod_sensor_driver_api *driver_api; +}; + +static struct sensor_dev_ctx *ctx_table; + +static int get_ctx_if_valid_call(fwk_id_t id, + void *data, + struct sensor_dev_ctx **ctx) +{ + int status; + + if (data == NULL) { + /* Invalid pointer */ + assert(false); + return FWK_E_PARAM; + } + + status = fwk_module_check_call(id); + if (status != FWK_SUCCESS) { + /* Module is in invalid state or ID is invalid */ + assert(false); + return status; + } + + *ctx = ctx_table + fwk_id_get_element_idx(id); + + return FWK_SUCCESS; +} + +/* + * Module API + */ +static int get_value(fwk_id_t id, uint64_t *value) +{ + int status; + struct sensor_dev_ctx *ctx; + + status = get_ctx_if_valid_call(id, value, &ctx); + if (status != FWK_SUCCESS) + return status; + + return ctx->driver_api->get_value(ctx->config->driver_id, value); +} + +static int get_info(fwk_id_t id, struct mod_sensor_info *info) +{ + int status; + struct sensor_dev_ctx *ctx; + + status = get_ctx_if_valid_call(id, info, &ctx); + if (status != FWK_SUCCESS) + return status; + + *info = *ctx->config->info; + + return FWK_SUCCESS; +} + +static struct mod_sensor_api sensor_api = { + .get_value = get_value, + .get_info = get_info, +}; + +/* + * Framework handlers + */ +static int sensor_init(fwk_id_t module_id, + unsigned int element_count, + const void *unused) +{ + ctx_table = fwk_mm_alloc(element_count, sizeof(ctx_table[0])); + + if (ctx_table == NULL) { + /* Unable to allocate device context memory */ + assert(false); + return FWK_E_NOMEM; + } + + return FWK_SUCCESS; +} + +static int sensor_dev_init(fwk_id_t element_id, + unsigned int unused, + const void *data) +{ + struct sensor_dev_ctx *ctx; + struct mod_sensor_dev_config *config; + + ctx = ctx_table + fwk_id_get_element_idx(element_id); + config = (struct mod_sensor_dev_config*)data; + + /* Validate config */ + if ((config->info == NULL) || + (config->info->type >= MOD_SENSOR_TYPE_COUNT)) { + + /* Invalid config */ + assert(false); + return FWK_E_DATA; + } + + /* Link info->name to framework element name */ + config->info->name = fwk_module_get_name(element_id); + + ctx->config = config; + + return FWK_SUCCESS; +} + +static int sensor_bind(fwk_id_t id, unsigned int round) +{ + struct sensor_dev_ctx *ctx; + int status; + struct mod_sensor_driver_api *driver; + + if ((round > 0) || fwk_id_is_type(id, FWK_ID_TYPE_MODULE)) { + /* + * Only bind in first round of calls + * Nothing to do for module + */ + return FWK_SUCCESS; + } + + ctx = ctx_table + fwk_id_get_element_idx(id); + + /* Bind to driver */ + status = fwk_module_bind(ctx->config->driver_id, + FWK_ID_API(fwk_id_get_module_idx(ctx->config->driver_id), 0), + &driver); + if (status != FWK_SUCCESS) { + /* Unable to bind to driver */ + assert(false); + return status; + } + + if (driver->get_value == NULL) { + /* Incomplete driver API */ + assert(false); + return FWK_E_DATA; + } + + ctx->driver_api = driver; + + return FWK_SUCCESS; +} + +static int sensor_process_bind_request(fwk_id_t source_id, + fwk_id_t target_id, + fwk_id_t api_type, + const void **api) +{ + *api = &sensor_api; + return FWK_SUCCESS; +} + +const struct fwk_module module_sensor = { + .name = "SENSOR", + .api_count = 1, + .event_count = 0, + .type = FWK_MODULE_TYPE_HAL, + .init = sensor_init, + .element_init = sensor_dev_init, + .bind = sensor_bind, + .process_bind_request = sensor_process_bind_request, +}; diff --git a/module/smt/include/internal/smt.h b/module/smt/include/internal/smt.h new file mode 100644 index 00000000..2582d26a --- /dev/null +++ b/module/smt/include/internal/smt.h @@ -0,0 +1,41 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef SMT_H +#define SMT_H + +struct __attribute((packed)) mod_smt_memory { + uint32_t reserved0; + uint32_t status; + uint64_t reserved1; + uint32_t flags; + uint32_t length; /* message_header + payload */ + uint32_t message_header; + uint32_t payload[]; +}; + +#define MOD_SMT_MAX_CHANNELS 8 + +#define MOD_SMT_MAILBOX_STATUS_FREE_POS 0 +#define MOD_SMT_MAILBOX_STATUS_FREE_MASK \ + (UINT32_C(0x1) << MOD_SMT_MAILBOX_STATUS_FREE_POS) + +#define MOD_SMT_MAILBOX_STATUS_ERROR_POS 1 +#define MOD_SMT_MAILBOX_STATUS_ERROR_MASK \ + (UINT32_C(0x1) << MOD_SMT_MAILBOX_STATUS_ERROR_POS) + +#define MOD_SMT_MAILBOX_FLAGS_IENABLED_POS 0 +#define MOD_SMT_MAILBOX_FLAGS_IENABLED_MASK \ + (UINT32_C(0x1) << MOD_SMT_MAILBOX_FLAGS_IENABLED_POS) + +#define MOD_SMT_MIN_PAYLOAD_SIZE \ + sizeof(((struct mod_smt_memory *)NULL)->payload[0]) + +#define MOD_SMT_MIN_MAILBOX_SIZE \ + (sizeof(struct mod_smt_memory) + MOD_SMT_MIN_PAYLOAD_SIZE) + +#endif /* SMT_H */ diff --git a/module/smt/include/mod_smt.h b/module/smt/include/mod_smt.h new file mode 100644 index 00000000..700aa5d5 --- /dev/null +++ b/module/smt/include/mod_smt.h @@ -0,0 +1,123 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef MOD_SMT_H +#define MOD_SMT_H + +#include <stddef.h> +#include <stdint.h> +#include <fwk_id.h> + +/*! + * \name Channel policies + * + * \details These policies define attributes that affect how the channel is + * treated by the SMT component. + * + * @{ + */ + +/*! No policies */ +#define MOD_SMT_POLICY_NONE ((uint32_t)0) + +/*! This channel is secure */ +#define MOD_SMT_POLICY_SECURE ((uint32_t)(1 << 0)) + +/*! The mailbox for this channel requires initialization */ +#define MOD_SMT_POLICY_INIT_MAILBOX ((uint32_t)(1 << 1)) + +/*! + * @} + */ + +/*! + * \brief Channel type + * + * \details Defines the role of an entity in a channel + */ +enum mod_smt_channel_type { + /*! Master channel */ + MOD_SMT_CHANNEL_TYPE_MASTER, + + /*! Slave channel */ + MOD_SMT_CHANNEL_TYPE_SLAVE, + + /*! Channel type count */ + MOD_SMT_CHANNEL_TYPE_COUNT, +}; + +/*! + * \brief Channel config. + */ +struct mod_smt_channel_config { + /*! Channel role (slave or master) */ + enum mod_smt_channel_type type; + + /*! Channel policies */ + uint32_t policies; + + /*! Shared mailbox address */ + uintptr_t mailbox_address; + + /*! Shared mailbox size in bytes */ + size_t mailbox_size; + + /*! Identifier of the driver */ + fwk_id_t driver_id; + + /*! Identifier of the driver API to bind to */ + fwk_id_t driver_api_id; + + /*! Identifier of the power domain that this channel depends on */ + fwk_id_t pd_source_id; +}; + +/*! + * \brief Driver API + */ +struct mod_smt_driver_api { + /*! + * \brief Raise an interrupt on the receiver + * + * \param device_id Device identifier + * + * \retval FWK_SUCCESS The operation succeeded + * \retval FWK_E_PARAM The device_id parameter is invalid + * \return One of the standard error codes for implementation-defined + * errors + */ + int (*raise_interrupt)(fwk_id_t device_id); +}; + +/*! + * \brief Driver input API (Implemented by SMT) + * + * \details Interface used for driver -> SMT communication. + */ +struct mod_smt_driver_input_api { + /*! + * \brief Signal an incoming message in the mailbox + * + * \param device_id Channel identifier + * + * \retval FWK_SUCCESS The operation succeeded. + * \return One of the standard error codes for implementation-defined + * errors. + */ + int (*signal_message)(fwk_id_t channel_id); +}; + +/*! + * \brief Type of the interfaces exposed by the power domain module. + */ +enum mod_smt_api_idx { + MOD_SMT_API_IDX_DRIVER_INPUT, + MOD_SMT_API_IDX_SCMI_TRANSPORT, + MOD_SMT_API_IDX_COUNT, +}; + +#endif /* MOD_SMT_H */ diff --git a/module/smt/src/Makefile b/module/smt/src/Makefile new file mode 100644 index 00000000..4d9d6b95 --- /dev/null +++ b/module/smt/src/Makefile @@ -0,0 +1,11 @@ +# +# Arm SCP/MCP Software +# Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +BS_LIB_NAME := SMT +BS_LIB_SOURCES = mod_smt.c + +include $(BS_DIR)/lib.mk diff --git a/module/smt/src/mod_smt.c b/module/smt/src/mod_smt.c new file mode 100644 index 00000000..e57c830c --- /dev/null +++ b/module/smt/src/mod_smt.c @@ -0,0 +1,563 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <assert.h> +#include <stdbool.h> +#include <string.h> +#include <fwk_errno.h> +#include <fwk_interrupt.h> +#include <fwk_mm.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <fwk_notification.h> +#include <mod_log.h> +#include <mod_power_domain.h> +#include <mod_scmi.h> +#include <mod_smt.h> +#include <internal/smt.h> + +struct smt_channel_ctx { + /* Channel identifier */ + fwk_id_t id; + + /* Channel configuration data */ + struct mod_smt_channel_config *config; + + /* Channel read and write cache memory areas */ + struct mod_smt_memory *in, *out; + + /* Message processing in progrees flag */ + volatile bool locked; + + /* Maximum payload size of the channel */ + size_t max_payload_size; + + /* Driver entity identifier */ + fwk_id_t driver_id; + + /* SCMI module service bound to the channel */ + fwk_id_t scmi_service_id; + + /* Driver API */ + struct mod_smt_driver_api *driver_api; + + /* SCMI service API */ + struct mod_scmi_from_transport_api *scmi_api; +}; + +struct smt_ctx { + /* Log module API */ + struct mod_log_api *log_api; + + /* Table of channel contexts */ + struct smt_channel_ctx *channel_ctx_table; + + /* Number of channels */ + unsigned int channel_count; +}; + +static struct smt_ctx smt_ctx; + +/* + * SCMI Transport API + */ +static int smt_get_secure(fwk_id_t channel_id, bool *secure) +{ + int status; + struct smt_channel_ctx *channel_ctx; + + status = fwk_module_check_call(channel_id); + if (status != FWK_SUCCESS) { + assert(false); + return status; + } + + if (secure == NULL) { + assert(false); + return FWK_E_PARAM; + } + + channel_ctx = + &smt_ctx.channel_ctx_table[fwk_id_get_element_idx(channel_id)]; + + *secure = channel_ctx->config->policies & MOD_SMT_POLICY_SECURE; + + return FWK_SUCCESS; +} + +static int smt_get_max_payload_size(fwk_id_t channel_id, size_t *size) +{ + int status; + struct smt_channel_ctx *channel_ctx; + + status = fwk_module_check_call(channel_id); + if (status != FWK_SUCCESS) { + assert(false); + return status; + } + + if (size == NULL) { + assert(false); + return FWK_E_PARAM; + } + + channel_ctx = + &smt_ctx.channel_ctx_table[fwk_id_get_element_idx(channel_id)]; + + *size = channel_ctx->max_payload_size; + + return FWK_SUCCESS; +} + +static int smt_get_message_header(fwk_id_t channel_id, uint32_t *header) +{ + int status; + struct smt_channel_ctx *channel_ctx; + + status = fwk_module_check_call(channel_id); + if (status != FWK_SUCCESS) { + assert(false); + return status; + } + + if (header == NULL) { + assert(false); + return FWK_E_PARAM; + } + + channel_ctx = + &smt_ctx.channel_ctx_table[fwk_id_get_element_idx(channel_id)]; + + if (!channel_ctx->locked) + return FWK_E_ACCESS; + + *header = channel_ctx->in->message_header; + + return FWK_SUCCESS; +} + +static int smt_get_payload(fwk_id_t channel_id, + const void **payload, + size_t *size) +{ + int status; + struct smt_channel_ctx *channel_ctx; + + status = fwk_module_check_call(channel_id); + if (status != FWK_SUCCESS) { + assert(false); + return status; + } + + if (payload == NULL) { + assert(false); + return FWK_E_PARAM; + } + + channel_ctx = + &smt_ctx.channel_ctx_table[fwk_id_get_element_idx(channel_id)]; + + if (!channel_ctx->locked) + return FWK_E_ACCESS; + + *payload = channel_ctx->in->payload; + + if (size != NULL) { + *size = channel_ctx->in->length - + sizeof(channel_ctx->in->message_header); + } + + return FWK_SUCCESS; +} + +static int smt_write_payload(fwk_id_t channel_id, + size_t offset, + const void *payload, + size_t size) +{ + int status; + struct smt_channel_ctx *channel_ctx; + + status = fwk_module_check_call(channel_id); + if (status != FWK_SUCCESS) { + assert(false); + return status; + } + + channel_ctx = + &smt_ctx.channel_ctx_table[fwk_id_get_element_idx(channel_id)]; + + if ((payload == NULL) || + (offset > channel_ctx->max_payload_size) || + (size > channel_ctx->max_payload_size) || + ((offset + size) > channel_ctx->max_payload_size)) { + + assert(false); + return FWK_E_PARAM; + } + + if (!channel_ctx->locked) + return FWK_E_ACCESS; + + memcpy(((uint8_t*)channel_ctx->out->payload) + offset, payload, size); + + return FWK_SUCCESS; +} + +static int smt_respond(fwk_id_t channel_id, const void *payload, size_t size) +{ + struct smt_channel_ctx *channel_ctx; + struct mod_smt_memory *memory; + int status; + (void)status; + + status = fwk_module_check_call(channel_id); + if (status != FWK_SUCCESS) { + assert(false); + return status; + } + + channel_ctx = + &smt_ctx.channel_ctx_table[fwk_id_get_element_idx(channel_id)]; + memory = ((struct mod_smt_memory*)channel_ctx->config->mailbox_address); + + /* Copy the header from the write buffer */ + *memory = *channel_ctx->out; + + /* Copy the payload from either the write buffer or the payload parameter */ + memcpy(memory->payload, + (payload == NULL ? channel_ctx->out->payload : payload), + size); + + /* + * NOTE: Disable interrupts for a brief period to ensure interrupts are not + * erroneously accepted in between unlocking the context, and setting + * the mailbox free bit. The agent should not interrupt during this + * period anyway, but this guard is included to protect against a + * misbehaving agent. + */ + fwk_interrupt_global_disable(); + + channel_ctx->locked = false; + + memory->length = sizeof(memory->message_header) + size; + memory->status |= MOD_SMT_MAILBOX_STATUS_FREE_MASK; + + fwk_interrupt_global_enable(); + + if (memory->flags & MOD_SMT_MAILBOX_FLAGS_IENABLED_MASK) + channel_ctx->driver_api->raise_interrupt(channel_ctx->driver_id); + + return FWK_SUCCESS; +} + +static const struct mod_scmi_to_transport_api smt_mod_scmi_to_transport_api = { + .get_secure = smt_get_secure, + .get_max_payload_size = smt_get_max_payload_size, + .get_message_header = smt_get_message_header, + .get_payload = smt_get_payload, + .write_payload = smt_write_payload, + .respond = smt_respond, +}; + +/* + * Driver handler API + */ +static int smt_slave_handler(struct smt_channel_ctx *channel_ctx) +{ + struct mod_smt_memory *memory, *in, *out; + size_t payload_size; + int status; + + /* Check if we are already processing */ + if (channel_ctx->locked) + return FWK_E_STATE; + + memory = ((struct mod_smt_memory*)channel_ctx->config->mailbox_address); + in = channel_ctx->in; + out = channel_ctx->out; + + /* Check we have ownership of the mailbox */ + if (memory->status & MOD_SMT_MAILBOX_STATUS_FREE_MASK) + return FWK_E_STATE; + + /* Commit to sending a response */ + channel_ctx->locked = true; + + /* Mirror mailbox contents in read and write buffers (Payload not copied) */ + *in = *memory; + *out = *memory; + + /* Ensure error bit is not set */ + out->status &= ~MOD_SMT_MAILBOX_STATUS_ERROR_MASK; + + /* + * Verify: + * 1. The length is at least as large as the message header + * 2. The length, minus the size of the message header, is less than or + * equal to the maximum payload size + * + * Note: the payload size is permitted to be of size zero. + */ + if ((in->length < sizeof(in->message_header)) || + ((in->length - sizeof(in->message_header)) + > channel_ctx->max_payload_size)) { + + out->status |= MOD_SMT_MAILBOX_STATUS_ERROR_MASK; + return smt_respond(channel_ctx->id, &(int32_t){SCMI_PROTOCOL_ERROR}, + sizeof(int32_t)); + } + + /* Copy payload from shared memory to read buffer */ + payload_size = in->length - sizeof(in->message_header); + memcpy(in->payload, memory->payload, payload_size); + + /* Let SCMI handle the message */ + status = + channel_ctx->scmi_api->signal_message(channel_ctx->scmi_service_id); + if (status != FWK_SUCCESS) + return FWK_E_HANDLER; + + return FWK_SUCCESS; +} + +static int smt_signal_message(fwk_id_t channel_id) +{ + int status; + struct smt_channel_ctx *channel_ctx; + + status = fwk_module_check_call(channel_id); + if (status != FWK_SUCCESS) + return status; + + channel_ctx = + &smt_ctx.channel_ctx_table[fwk_id_get_element_idx(channel_id)]; + + switch (channel_ctx->config->type) { + case MOD_SMT_CHANNEL_TYPE_MASTER: + /* Not supported yet */ + assert(false); + break; + case MOD_SMT_CHANNEL_TYPE_SLAVE: + return smt_slave_handler(channel_ctx); + break; + default: + /* Invalid config */ + assert(false); + break; + } + + return FWK_SUCCESS; +} + +static const struct mod_smt_driver_input_api driver_input_api = { + .signal_message = smt_signal_message, +}; + +/* + * Framework API + */ +static int smt_init(fwk_id_t module_id, unsigned int element_count, + const void *data) +{ + smt_ctx.channel_ctx_table = fwk_mm_calloc(element_count, + sizeof(smt_ctx.channel_ctx_table[0])); + if (smt_ctx.channel_ctx_table == NULL) { + assert(false); + return FWK_E_NOMEM; + } + smt_ctx.channel_count = element_count; + + return FWK_SUCCESS; +} + +static int smt_channel_init(fwk_id_t channel_id, unsigned int unused, + const void *data) +{ + struct smt_channel_ctx *channel_ctx; + + channel_ctx = + &smt_ctx.channel_ctx_table[fwk_id_get_element_idx(channel_id)]; + channel_ctx->config = (struct mod_smt_channel_config*)data; + + /* Validate channel config */ + if ((channel_ctx->config->type >= MOD_SMT_CHANNEL_TYPE_COUNT) || + (channel_ctx->config->mailbox_address == 0) || + (channel_ctx->config->mailbox_size == 0)) { + assert(false); + return FWK_E_DATA; + } + + channel_ctx->id = channel_id; + channel_ctx->in = fwk_mm_alloc(1, channel_ctx->config->mailbox_size); + channel_ctx->out = fwk_mm_alloc(1, channel_ctx->config->mailbox_size); + + if ((channel_ctx->in == NULL) || (channel_ctx->out == NULL)) + return FWK_E_NOMEM; + + channel_ctx->max_payload_size = channel_ctx->config->mailbox_size - + sizeof(struct mod_smt_memory); + + /* Check memory allocations */ + if ((channel_ctx->in == NULL) || (channel_ctx->out == NULL)) { + assert(false); + return FWK_E_NOMEM; + } + + return FWK_SUCCESS; +} + +static int smt_bind(fwk_id_t id, unsigned int round) +{ + int status; + struct smt_channel_ctx *channel_ctx; + + if (round == 0) { + if (fwk_id_is_type(id, FWK_ID_TYPE_MODULE)) { + return fwk_module_bind(fwk_module_id_log, + FWK_ID_API(FWK_MODULE_IDX_LOG, 0), + &smt_ctx.log_api); + } + + channel_ctx = &smt_ctx.channel_ctx_table[fwk_id_get_element_idx(id)]; + status = fwk_module_bind(channel_ctx->config->driver_id, + channel_ctx->config->driver_api_id, + &channel_ctx->driver_api); + if (status != FWK_SUCCESS) + return status; + channel_ctx->driver_id = channel_ctx->config->driver_id; + } + + if ((round == 1) && fwk_id_is_type(id, FWK_ID_TYPE_ELEMENT)) { + channel_ctx = &smt_ctx.channel_ctx_table[fwk_id_get_element_idx(id)]; + status = fwk_module_bind(channel_ctx->scmi_service_id, + FWK_ID_API(FWK_MODULE_IDX_SCMI, MOD_SCMI_API_IDX_TRANSPORT), + &channel_ctx->scmi_api); + if (status != FWK_SUCCESS) + return status; + } + + return FWK_SUCCESS; +} + +static int smt_process_bind_request(fwk_id_t source_id, + fwk_id_t target_id, + fwk_id_t api_id, + const void **api) +{ + struct smt_channel_ctx *channel_ctx; + + /* Only bind to a channel (not the whole module) */ + if (!fwk_id_is_type(target_id, FWK_ID_TYPE_ELEMENT)) { + /* Tried to bind to something other than a specific channel */ + assert(false); + return FWK_E_PARAM; + } + + channel_ctx = + &smt_ctx.channel_ctx_table[fwk_id_get_element_idx(target_id)]; + + switch (fwk_id_get_api_idx(api_id)) { + case MOD_SMT_API_IDX_DRIVER_INPUT: + /* Driver input API */ + + /* + * Make sure that the element that is trying to bind to us is the + * same element that we previously bound to. + * + * NOTE: We bound to an element but a sub-element should be binding + * back to us. This means we cannot use fwk_id_is_equal() because + * the ids have different types. For now we compare the indicies + * manually. + */ + if (fwk_id_get_module_idx(channel_ctx->driver_id) == + fwk_id_get_module_idx(source_id) && + fwk_id_get_element_idx(channel_ctx->driver_id) == + fwk_id_get_element_idx(source_id)) { + + /* Ids are equal */ + *api = &driver_input_api; + } else { + /* A module that we did not bind to is trying to bind to us */ + assert(false); + return FWK_E_ACCESS; + } + break; + + case MOD_SMT_API_IDX_SCMI_TRANSPORT: + /* SCMI transport API */ + *api = &smt_mod_scmi_to_transport_api; + channel_ctx->scmi_service_id = source_id; + break; + + default: + /* Invalid API */ + assert(false); + return FWK_E_PARAM; + } + + return FWK_SUCCESS; +} + +static int smt_start(fwk_id_t id) +{ + struct smt_channel_ctx *ctx; + + if (!fwk_id_is_type(id, FWK_ID_TYPE_ELEMENT)) + return FWK_SUCCESS; + + ctx = &smt_ctx.channel_ctx_table[fwk_id_get_element_idx(id)]; + + /* Register for power domain state transition notifications */ + return fwk_notification_subscribe( + mod_pd_notification_id_power_state_transition, + ctx->config->pd_source_id, + id); +} + +static int smt_process_notification( + const struct fwk_event *event, + struct fwk_event *resp_event) +{ + struct mod_pd_power_state_transition_notification_params *params; + struct smt_channel_ctx *channel_ctx; + + assert(fwk_id_is_equal(event->id, + mod_pd_notification_id_power_state_transition)); + assert(fwk_id_is_type(event->target_id, FWK_ID_TYPE_ELEMENT)); + + params = (struct mod_pd_power_state_transition_notification_params *) + event->params; + + if (params->state != MOD_PD_STATE_ON) + return FWK_SUCCESS; + + channel_ctx = + &smt_ctx.channel_ctx_table[fwk_id_get_element_idx(event->target_id)]; + + if (channel_ctx->config->policies & MOD_SMT_POLICY_INIT_MAILBOX) { + /* Initialize mailbox */ + *((struct mod_smt_memory *)channel_ctx->config->mailbox_address) = + (struct mod_smt_memory) { + .status = (1 << MOD_SMT_MAILBOX_STATUS_FREE_POS) + }; + } + + return FWK_SUCCESS; +} + +const struct fwk_module module_smt = { + .name = "smt", + .type = FWK_MODULE_TYPE_SERVICE, + .api_count = MOD_SMT_API_IDX_COUNT, + .init = smt_init, + .element_init = smt_channel_init, + .bind = smt_bind, + .start = smt_start, + .process_bind_request = smt_process_bind_request, + .process_notification = smt_process_notification, +}; diff --git a/module/system_pll/include/mod_system_pll.h b/module/system_pll/include/mod_system_pll.h new file mode 100644 index 00000000..0806e385 --- /dev/null +++ b/module/system_pll/include/mod_system_pll.h @@ -0,0 +1,93 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef MOD_SYSTEM_PLL_H +#define MOD_SYSTEM_PLL_H + +#include <stdint.h> +#include <fwk_element.h> +#include <fwk_macros.h> + +/*! + * \ingroup GroupModules Modules + * \defgroup GroupSystemPll System PLL Driver + * + * \details A driver for system PLL devices. + * + * @{ + */ + +/*! The slowest rate at which the PLL hardware can operate. */ +#define MOD_SYSTEM_PLL_MIN_RATE (50UL * FWK_MHZ) + +/*! The fastest rate at which the PLL hardware can operate. */ +#define MOD_SYSTEM_PLL_MAX_RATE (4UL * FWK_GHZ) + +/*! The maximum precision that can be used when setting the PLL rate. */ +#define MOD_SYSTEM_PLL_MIN_INTERVAL (1UL * FWK_KHZ) + +/*! Indexes of APIs that the module offers for binding. */ +enum mod_system_pll_api_types { + MOD_SYSTEM_PLL_API_TYPE_DEFAULT, + MOD_SYSTEM_PLL_API_COUNT, +}; + +/*! + * \brief PLL device configuration. + */ +struct mod_system_pll_dev_config { + /*! Pointer to the PLL's control register. */ + volatile uint32_t * const control_reg; + + /*! Pointer to the PLL's status register, if any. */ + volatile uint32_t * const status_reg; + + /*! + * Mask for the bit within the status register that indicates whether the + * PLL has locked at the programmed rate. + */ + const uint8_t lock_flag_mask; + + /*! The initial rate the PLL is set to during initialization. */ + const uint64_t initial_rate; + + /*! + * The slowest rate the PLL can be set to. This may be different from the + * hardware-imposed limit). + */ + const uint64_t min_rate; + + /*! + * The fastest rate the PLL can be set to. This may be different from the + * hardware-imposed limit). + */ + const uint64_t max_rate; + + /*! + * The maximum precision that can be used when setting the PLL rate. This + * may be different from the hardware-imposed limit). + */ + const uint64_t min_step; + + /*! + * If \c true, the driver will not attempt to set a default frequency, or to + * otherwise configure the PLL during the pre-runtime phase. The PLL is + * expected to be initialized later in response to a notification or other + * event. + */ + const bool defer_initialization; +}; + +/*! + * @} + */ + +/*! + * @} + */ + +#endif /* MOD_SYSTEM_PLL_H */ diff --git a/module/system_pll/src/Makefile b/module/system_pll/src/Makefile new file mode 100644 index 00000000..5f5e4f07 --- /dev/null +++ b/module/system_pll/src/Makefile @@ -0,0 +1,11 @@ +# +# Arm SCP/MCP Software +# Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +BS_LIB_NAME := SYSTEM PLL +BS_LIB_SOURCES := mod_system_pll.c + +include $(BS_DIR)/lib.mk diff --git a/module/system_pll/src/mod_system_pll.c b/module/system_pll/src/mod_system_pll.c new file mode 100644 index 00000000..edc30c2f --- /dev/null +++ b/module/system_pll/src/mod_system_pll.c @@ -0,0 +1,336 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <assert.h> +#include <stdint.h> +#include <stdlib.h> +#include <fwk_element.h> +#include <fwk_errno.h> +#include <fwk_macros.h> +#include <fwk_mm.h> +#include <fwk_module.h> +#include <mod_clock.h> +#include <mod_system_pll.h> +#include <mod_power_domain.h> + +/* Device context */ +struct system_pll_dev_ctx { + bool initialized; + uint64_t current_rate; + enum mod_clock_state current_state; + const struct mod_system_pll_dev_config *config; +}; + +/* Module context */ +struct system_pll_ctx { + struct system_pll_dev_ctx *dev_ctx_table; + unsigned int dev_count; +}; + +static struct system_pll_ctx module_ctx; + +/* + * Static helper functions + */ + +/* + * Given a frequency (Hz), return the period (picoseconds) of a half cycle. + * + * Note: For performance reasons, 32-bit math is used for the conversion. This + * may cause a loss of precision if the given frequency is not a multiple + * of 1 KHz. + */ +static unsigned int freq_to_half_cycle_ps(unsigned int freq_hz) +{ + unsigned int freq_khz; + + /* Check if the given frequency is a multiple of 1 KHz */ + if (freq_hz % MOD_SYSTEM_PLL_MIN_INTERVAL != 0) + return FWK_E_PARAM; + + freq_khz = freq_hz / FWK_KHZ; + if (freq_khz == 0) + return 0; + + /* + * Legend: + * s = seconds + * P = Half cycle period in picoseconds + * Fh = Frequency in hertz + * Fk = Frequency in kilohertz + * + * Starting from "Period = Time / Frequency" + * General equation for half cycle in picoseconds: + * P = ((1s / Fh ) / 2) * 10^12 + * To avoid decimal calculations, re-arrange and simplify equation: + * P = 10^12 / 2 * Fh + * To avoid dividend overflowing a 32-bit storage, dividend and divisor can + * be divided by 10^3: + * P = (10^12 / 10^3) / (2 * Fh / 10^3) + * Given Fk = Fh / 10^3, the equation can be further simplified as: + * P = 10^9 / 2 * Fk + * P = 5*10^8 / Fk + */ + return 500000000UL / freq_khz; +} + +/* + * Clock driver API functions + */ + +static int system_pll_set_rate(fwk_id_t dev_id, uint64_t rate, + enum mod_clock_round_mode round_mode) +{ + uint64_t rounded_rate; + uint64_t rounded_rate_alt; + unsigned int picoseconds; + struct system_pll_dev_ctx *ctx; + + if (!fwk_module_is_valid_element_id(dev_id)) + return FWK_E_PARAM; + + ctx = module_ctx.dev_ctx_table + fwk_id_get_element_idx(dev_id); + + if (ctx->current_state == MOD_CLOCK_STATE_STOPPED) + return FWK_E_PWRSTATE; + + /* If the given rate is not attainable as-is then round as requested */ + if ((rate % ctx->config->min_step) > 0) { + switch (round_mode) { + case MOD_CLOCK_ROUND_MODE_NONE: + return FWK_E_RANGE; + case MOD_CLOCK_ROUND_MODE_NEAREST: + rounded_rate = FWK_ALIGN_PREVIOUS(rate, ctx->config->min_step); + rounded_rate_alt = FWK_ALIGN_NEXT(rate, ctx->config->min_step); + + /* Select the rounded rate that is closest to the given rate */ + if ((rate - rounded_rate) > (rounded_rate_alt - rate)) + rounded_rate = rounded_rate_alt; + break; + case MOD_CLOCK_ROUND_MODE_DOWN: + rounded_rate = FWK_ALIGN_PREVIOUS(rate, ctx->config->min_step); + break; + case MOD_CLOCK_ROUND_MODE_UP: + rounded_rate = FWK_ALIGN_NEXT(rate, ctx->config->min_step); + break; + default: + return FWK_E_SUPPORT; + } + } else + rounded_rate = rate; + + if (rounded_rate < ctx->config->min_rate) + return FWK_E_RANGE; + if (rounded_rate > ctx->config->max_rate) + return FWK_E_RANGE; + + picoseconds = freq_to_half_cycle_ps(rounded_rate); + + if (picoseconds == 0) + return FWK_E_RANGE; + + *ctx->config->control_reg = picoseconds; + + if (ctx->config->status_reg != NULL) { + /* Wait until the PLL has locked */ + while ((*ctx->config->status_reg & ctx->config->lock_flag_mask) == 0) + continue; + } + + ctx->current_rate = rounded_rate; + + return FWK_SUCCESS; +} + +static int system_pll_get_rate(fwk_id_t dev_id, uint64_t *rate) +{ + struct system_pll_dev_ctx *ctx; + + if (!fwk_module_is_valid_element_id(dev_id)) + return FWK_E_PARAM; + if (rate == NULL) + return FWK_E_PARAM; + + ctx = module_ctx.dev_ctx_table + fwk_id_get_element_idx(dev_id); + *rate = ctx->current_rate; + + return FWK_SUCCESS; +} + +static int system_pll_get_rate_from_index(fwk_id_t dev_id, + unsigned int rate_index, + uint64_t *rate) +{ + /* PLLs have a continuous range of rates and are not indexed */ + return FWK_E_SUPPORT; +} + +static int system_pll_set_state(fwk_id_t dev_id, enum mod_clock_state state) +{ + if (state == MOD_CLOCK_STATE_RUNNING) + return FWK_SUCCESS; + + /* PLLs can only be stopped by a parent power domain state change. */ + return FWK_E_SUPPORT; +} + +static int system_pll_get_state(fwk_id_t dev_id, enum mod_clock_state *state) +{ + struct system_pll_dev_ctx *ctx; + + if (!fwk_module_is_valid_element_id(dev_id)) + return FWK_E_PARAM; + if (state == NULL) + return FWK_E_PARAM; + + ctx = module_ctx.dev_ctx_table + fwk_id_get_element_idx(dev_id); + *state = ctx->current_state; + + return FWK_SUCCESS; +} + +static int system_pll_get_range(fwk_id_t dev_id, struct mod_clock_range *range) +{ + struct system_pll_dev_ctx *ctx; + + if (!fwk_module_is_valid_element_id(dev_id)) + return FWK_E_PARAM; + if (range == NULL) + return FWK_E_PARAM; + + ctx = module_ctx.dev_ctx_table + fwk_id_get_element_idx(dev_id); + + range->rate_type = MOD_CLOCK_RATE_TYPE_CONTINUOUS; + range->min = ctx->config->min_rate; + range->max = ctx->config->max_rate; + range->step = ctx->config->min_step; + + return FWK_SUCCESS; +} + +static int system_pll_power_state_change( + fwk_id_t dev_id, + unsigned int state) +{ + int status; + uint64_t rate; + struct system_pll_dev_ctx *ctx; + + status = fwk_module_check_call(dev_id); + if (status != FWK_SUCCESS) + return status; + + ctx = module_ctx.dev_ctx_table + fwk_id_get_element_idx(dev_id); + + if (state != MOD_PD_STATE_ON) + return FWK_SUCCESS; + + ctx->current_state = MOD_CLOCK_STATE_RUNNING; + + if (ctx->initialized) { + /* Restore the previous rate */ + rate = ctx->current_rate; + } else { + /* Initialize the PLL to its default rate */ + ctx->initialized = true; + rate = ctx->config->initial_rate; + } + + return system_pll_set_rate(dev_id, rate, MOD_CLOCK_ROUND_MODE_NONE); +} + +static int system_pll_power_state_pending_change( + fwk_id_t dev_id, + unsigned int current_state, + unsigned int next_state) +{ + int status; + struct system_pll_dev_ctx *ctx; + + status = fwk_module_check_call(dev_id); + if (status != FWK_SUCCESS) + return status; + + ctx = module_ctx.dev_ctx_table + fwk_id_get_element_idx(dev_id); + + if (next_state == MOD_PD_STATE_OFF) { + /* Just mark the PLL as stopped */ + ctx->current_state = MOD_CLOCK_STATE_STOPPED; + } + + return FWK_SUCCESS; +} + +static const struct mod_clock_drv_api api_system_pll = { + .set_rate = system_pll_set_rate, + .get_rate = system_pll_get_rate, + .get_rate_from_index = system_pll_get_rate_from_index, + .set_state = system_pll_set_state, + .get_state = system_pll_get_state, + .get_range = system_pll_get_range, + .process_power_transition = system_pll_power_state_change, + .process_pending_power_transition = system_pll_power_state_pending_change, +}; + +/* + * Framework handler functions + */ + +static int system_pll_init(fwk_id_t module_id, unsigned int element_count, + const void *data) +{ + module_ctx.dev_count = element_count; + + if (element_count == 0) + return FWK_SUCCESS; + + module_ctx.dev_ctx_table = fwk_mm_calloc(element_count, + sizeof(struct system_pll_dev_ctx)); + if (module_ctx.dev_ctx_table == NULL) + return FWK_E_NOMEM; + + return FWK_SUCCESS; +} + +static int system_pll_element_init(fwk_id_t element_id, unsigned int unused, + const void *data) +{ + struct system_pll_dev_ctx *ctx; + const struct mod_system_pll_dev_config *dev_config = data; + + if (!fwk_module_is_valid_element_id(element_id)) + return FWK_E_PARAM; + + ctx = module_ctx.dev_ctx_table + fwk_id_get_element_idx(element_id); + + ctx->config = dev_config; + + if (ctx->config->defer_initialization) + return FWK_SUCCESS; + + ctx->initialized = true; + ctx->current_state = MOD_CLOCK_STATE_RUNNING; + return system_pll_set_rate(element_id, ctx->config->initial_rate, + MOD_CLOCK_ROUND_MODE_NONE); +} + +static int system_pll_process_bind_request(fwk_id_t requester_id, fwk_id_t id, + fwk_id_t api_type, const void **api) +{ + *api = &api_system_pll; + return FWK_SUCCESS; +} + +const struct fwk_module module_system_pll = { + .name = "SYSTEM PLL Driver", + .type = FWK_MODULE_TYPE_DRIVER, + .api_count = MOD_SYSTEM_PLL_API_COUNT, + .event_count = 0, + .init = system_pll_init, + .element_init = system_pll_element_init, + .process_bind_request = system_pll_process_bind_request, +}; diff --git a/module/system_power/include/mod_system_power.h b/module/system_power/include/mod_system_power.h new file mode 100644 index 00000000..c2251e33 --- /dev/null +++ b/module/system_power/include/mod_system_power.h @@ -0,0 +1,126 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * System Power Support + */ + +#ifndef MOD_SYSTEM_POWER_H +#define MOD_SYSTEM_POWER_H + +#include <fwk_id.h> +#include <mod_power_domain.h> + +/*! + * \ingroup GroupModules Modules + * \defgroup GroupSystemPower System Power Support + * + * @{ + */ + +/*! Additional system_power power states */ +enum mod_system_power_power_states { + MOD_SYSTEM_POWER_POWER_STATE_SLEEP0 = MOD_PD_STATE_COUNT, + MOD_SYSTEM_POWER_POWER_STATE_SLEEP1, + MOD_SYSTEM_POWER_POWER_STATE_COUNT +}; + +/*! Extended PPU configuration */ +struct mod_system_power_ext_ppu_config { + /*! PPU identifier */ + fwk_id_t ppu_id; + + /*! API identifier */ + fwk_id_t api_id; +}; + +/*! Module configuration */ +struct mod_system_power_config { + /*! SoC wakeup IRQ number */ + unsigned int soc_wakeup_irq; + + /*! System 0 PPU element ID */ + fwk_id_t ppu_sys0_id; + + /*! System 1 PPU element ID */ + fwk_id_t ppu_sys1_id; + + /*! System PPUs API ID */ + fwk_id_t ppu_sys_api_id; + + /*! Number of extended PPUs */ + size_t ext_ppus_count; + + /*! + * \brief Pointer to array of extended PPU configurations. + * + * \details These PPUs will be powered on automatically with the rest of the + * system. + */ + const struct mod_system_power_ext_ppu_config *ext_ppus; + + /*! System shutdown driver identifier */ + fwk_id_t driver_id; + + /*! System shutdown driver API identifier */ + fwk_id_t driver_api_id; +}; + +/*! + * \brief Driver interface. + */ +struct mod_system_power_driver_api { + /*! + * \brief Pointer to the system shutdown function. + * + * \note This function is \b mandatory. In case of a successful call the + * function does not return. + * + * \param system_shutdown Type of system shutdown. + * + * \retval One of the driver-defined error code. + */ + int (*system_shutdown)(enum mod_pd_system_shutdown system_shutdown); +}; + +/*! + * \defgroup GroupSystemPowerIds Identifiers + * \{ + */ + +/*! + * \brief API indices. + */ +enum mod_system_power_api_idx { + /*! API index for the power domain driver API */ + MOD_SYSTEM_POWER_API_IDX_PD_DRIVER, + + /*! API index for the power domain driver input API */ + MOD_SYSTEM_POWER_API_IDX_PD_DRIVER_INPUT, + + /*! Number of exposed APIs */ + MOD_SYSTEM_POWER_API_COUNT +}; + +/*! Identifier of the power domain driver API */ +static const fwk_id_t mod_system_power_api_id_pd_driver = + FWK_ID_API_INIT(FWK_MODULE_IDX_SYSTEM_POWER, + MOD_SYSTEM_POWER_API_IDX_PD_DRIVER); + +/*! Identifier of the power domain driver input API */ +static const fwk_id_t mod_system_power_api_id_pd_driver_input = + FWK_ID_API_INIT(FWK_MODULE_IDX_SYSTEM_POWER, + MOD_SYSTEM_POWER_API_IDX_PD_DRIVER_INPUT); + +/*! + * \} + */ + +/*! + * @} + */ + +#endif /* MOD_SYSTEM_POWER_H */ diff --git a/module/system_power/src/Makefile b/module/system_power/src/Makefile new file mode 100644 index 00000000..45a6acb6 --- /dev/null +++ b/module/system_power/src/Makefile @@ -0,0 +1,11 @@ +# +# Arm SCP/MCP Software +# Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +BS_LIB_NAME := SYSTEM POWER +BS_LIB_SOURCES = mod_system_power.c + +include $(BS_DIR)/lib.mk diff --git a/module/system_power/src/mod_system_power.c b/module/system_power/src/mod_system_power.c new file mode 100644 index 00000000..acd49f42 --- /dev/null +++ b/module/system_power/src/mod_system_power.c @@ -0,0 +1,365 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * System Power Support. + */ + +#include <assert.h> +#include <stdint.h> +#include <fwk_id.h> +#include <fwk_interrupt.h> +#include <fwk_macros.h> +#include <fwk_mm.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <mod_log.h> +#include <mod_system_power.h> +#include <mod_power_domain.h> + +/* Module context */ +struct system_power_ctx { + /* Log API pointer */ + const struct mod_log_api *log_api; + + /* SYS0 power domain driver API pointer */ + const struct mod_pd_driver_api *sys0_api; + + /* SYS1 power domain driver API pointer*/ + const struct mod_pd_driver_api *sys1_api; + + /* Pointer to array of extended PPU power domain driver APIs */ + const struct mod_pd_driver_api *ext_ppu_apis; + + /* Power domain module restricted API pointer */ + const struct mod_pd_restricted_api *mod_pd_restricted_api; + + /* Power domain module driver input API pointer */ + const struct mod_pd_driver_input_api *mod_pd_driver_input_api; + + /* Driver API pointer */ + const struct mod_system_power_driver_api *driver_api; + + /* Power domain module identifier of the system power domain */ + fwk_id_t mod_pd_system_id; + + /* Current system-level power state */ + unsigned int state; + + /* Pointer to module config */ + const struct mod_system_power_config *config; +}; + +struct system_power_isr { + unsigned int interrupt; + void (*handler)(void); +}; + +static struct system_power_ctx system_power_ctx; + +static void ext_ppus_set_state(enum mod_pd_state state) +{ + unsigned int i; + + for (i = 0; i < system_power_ctx.config->ext_ppus_count; i++) { + system_power_ctx.ext_ppu_apis[i].set_state( + system_power_ctx.config->ext_ppus[i].ppu_id, + state); + } +} + +/* + * Functions fulfilling the Power Domain module's driver API + */ + +static int system_power_set_state(fwk_id_t pd_id, unsigned int state) +{ + int status; + + status = fwk_module_check_call(pd_id); + if (status != FWK_SUCCESS) + return status; + + switch (state) { + case MOD_PD_STATE_ON: + fwk_interrupt_disable(system_power_ctx.config->soc_wakeup_irq); + + system_power_ctx.sys0_api->set_state( + system_power_ctx.config->ppu_sys0_id, MOD_PD_STATE_ON); + system_power_ctx.sys0_api->set_state( + system_power_ctx.config->ppu_sys1_id, MOD_PD_STATE_ON); + + ext_ppus_set_state(MOD_PD_STATE_ON); + + break; + + case MOD_SYSTEM_POWER_POWER_STATE_SLEEP0: + ext_ppus_set_state(MOD_PD_STATE_OFF); + + system_power_ctx.sys0_api->set_state( + system_power_ctx.config->ppu_sys0_id, MOD_PD_STATE_OFF); + system_power_ctx.sys0_api->set_state( + system_power_ctx.config->ppu_sys1_id, MOD_PD_STATE_ON); + + fwk_interrupt_enable(system_power_ctx.config->soc_wakeup_irq); + + break; + + case MOD_PD_STATE_OFF: + fwk_interrupt_disable(system_power_ctx.config->soc_wakeup_irq); + ext_ppus_set_state(MOD_PD_STATE_OFF); + + system_power_ctx.sys0_api->set_state( + system_power_ctx.config->ppu_sys0_id, MOD_PD_STATE_OFF); + system_power_ctx.sys0_api->set_state( + system_power_ctx.config->ppu_sys1_id, MOD_PD_STATE_OFF); + + break; + + default: + return FWK_E_SUPPORT; + } + + return FWK_SUCCESS; +} + +static int system_power_get_state(fwk_id_t pd_id, unsigned int *state) +{ + int status; + + status = fwk_module_check_call(pd_id); + if (status != FWK_SUCCESS) + return status; + + *state = system_power_ctx.state; + + return FWK_SUCCESS; +} + +static int system_power_reset(fwk_id_t pd_id) +{ + return FWK_E_SUPPORT; +} + +static int system_power_shutdown(fwk_id_t pd_id, + enum mod_pd_system_shutdown system_shutdown) +{ + int status; + + status = system_power_set_state(pd_id, MOD_PD_STATE_OFF); + if (status != FWK_SUCCESS) + return status; + + return system_power_ctx.driver_api->system_shutdown(system_shutdown); +} + +static void soc_wakeup_handler(void) +{ + int status; + fwk_id_t pd_id = FWK_ID_ELEMENT(FWK_MODULE_IDX_POWER_DOMAIN, 0); + uint32_t state = MOD_PD_COMPOSITE_STATE(MOD_PD_LEVEL_2, + 0, + MOD_PD_STATE_ON, + MOD_PD_STATE_ON, + MOD_PD_STATE_ON); + + status = + system_power_ctx.mod_pd_restricted_api->set_composite_state_async( + pd_id, false, state); + assert(status == FWK_SUCCESS); + (void)status; +} + +static const struct mod_pd_driver_api system_power_power_domain_driver_api = { + .set_state = system_power_set_state, + .get_state = system_power_get_state, + .reset = system_power_reset, + .shutdown = system_power_shutdown +}; + +/* + * Functions fulfilling the Power Domain module's driver input API + */ + +static int system_power_report_power_state_transition(fwk_id_t module_id, + unsigned int state) +{ + int status; + unsigned int sys0_state, sys1_state; + + system_power_ctx.sys0_api->get_state(system_power_ctx.config->ppu_sys0_id, + &sys0_state); + system_power_ctx.sys1_api->get_state(system_power_ctx.config->ppu_sys1_id, + &sys1_state); + + if ((sys0_state == MOD_PD_STATE_ON) && (sys1_state == MOD_PD_STATE_ON)) + system_power_ctx.state = MOD_PD_STATE_ON; + else if ((sys0_state == MOD_PD_STATE_OFF) && + (sys1_state == MOD_PD_STATE_ON)) + system_power_ctx.state = MOD_SYSTEM_POWER_POWER_STATE_SLEEP0; + else + system_power_ctx.state = MOD_PD_STATE_OFF; + + status = + system_power_ctx.mod_pd_driver_input_api->report_power_state_transition( + system_power_ctx.mod_pd_system_id, system_power_ctx.state); + assert(status == FWK_SUCCESS); + (void)status; + + return FWK_SUCCESS; +} + +static const struct mod_pd_driver_input_api + system_power_power_domain_driver_input_api = { + .report_power_state_transition = system_power_report_power_state_transition +}; + +/* + * Functions fulfilling the framework's module interface + */ + +static int system_power_mod_init(fwk_id_t module_id, + unsigned int unused, + const void *data) +{ + assert(data != NULL); + + system_power_ctx.config = data; + system_power_ctx.mod_pd_system_id = FWK_ID_NONE; + + if (system_power_ctx.config->ext_ppus_count > 0) { + system_power_ctx.ext_ppu_apis = fwk_mm_calloc( + system_power_ctx.config->ext_ppus_count, + sizeof(system_power_ctx.ext_ppu_apis[0])); + if (system_power_ctx.ext_ppu_apis == NULL) + return FWK_E_NOMEM; + } + + if (system_power_ctx.config->soc_wakeup_irq != FWK_INTERRUPT_NONE) { + return fwk_interrupt_set_isr(system_power_ctx.config->soc_wakeup_irq, + soc_wakeup_handler); + } else + return FWK_SUCCESS; +} + +static int system_power_bind(fwk_id_t id, unsigned int round) +{ + int status; + unsigned int i; + + if (round == 1) { + /* + * During the first round of binding, the power domain module should + * have bound to the power domain driver API provided by the present + * module. Bind back to the power domain driver input API provided by + * the system_power_ctx.mod_pd_system_id power domain module element to + * report power state transitions of the system power domains. + */ + return fwk_module_bind(system_power_ctx.mod_pd_system_id, + mod_pd_api_id_driver_input, + &system_power_ctx.mod_pd_driver_input_api); + } + + status = fwk_module_bind(FWK_ID_MODULE(FWK_MODULE_IDX_LOG), + FWK_ID_API(FWK_MODULE_IDX_LOG, 0), &system_power_ctx.log_api); + if (status != FWK_SUCCESS) + return status; + + status = fwk_module_bind(system_power_ctx.config->ppu_sys0_id, + system_power_ctx.config->ppu_sys_api_id, + &system_power_ctx.sys0_api); + if (status != FWK_SUCCESS) + return status; + + status = fwk_module_bind(system_power_ctx.config->ppu_sys1_id, + system_power_ctx.config->ppu_sys_api_id, + &system_power_ctx.sys1_api); + if (status != FWK_SUCCESS) + return status; + + for (i = 0; i < system_power_ctx.config->ext_ppus_count; i++) { + status = fwk_module_bind( + system_power_ctx.config->ext_ppus[i].ppu_id, + system_power_ctx.config->ext_ppus[i].api_id, + &system_power_ctx.ext_ppu_apis[i]); + if (status != FWK_SUCCESS) + return status; + } + + status = fwk_module_bind(system_power_ctx.config->driver_id, + system_power_ctx.config->driver_api_id, &system_power_ctx.driver_api); + if (status != FWK_SUCCESS) + return status; + + status = fwk_module_bind(fwk_module_id_power_domain, + mod_pd_api_id_restricted, + &system_power_ctx.mod_pd_restricted_api); + if (status != FWK_SUCCESS) + return status; + + return FWK_SUCCESS; +} + +static int system_power_process_bind_request(fwk_id_t requester_id, + fwk_id_t pd_id, fwk_id_t api_id, + const void **api) +{ + if (fwk_id_is_equal(api_id, mod_system_power_api_id_pd_driver)) { + + if (!fwk_id_is_equal(fwk_id_build_module_id(requester_id), + fwk_module_id_power_domain)) + return FWK_E_ACCESS; + + *api = &system_power_power_domain_driver_api; + system_power_ctx.mod_pd_system_id = requester_id; + } else { + if (!fwk_id_is_equal(requester_id, + system_power_ctx.config->ppu_sys0_id) && + !fwk_id_is_equal(requester_id, + system_power_ctx.config->ppu_sys1_id)) + return FWK_E_ACCESS; + *api = &system_power_power_domain_driver_input_api; + } + + return FWK_SUCCESS; +} + +static int system_power_start(fwk_id_t id) +{ + int status; + unsigned int state; + + status = system_power_ctx.sys1_api->get_state + (system_power_ctx.config->ppu_sys1_id, &state); + if (status != FWK_SUCCESS) + return status; + + if (state == MOD_PD_STATE_OFF) { + system_power_ctx.state = MOD_PD_STATE_OFF; + return FWK_SUCCESS; + } + + status = system_power_ctx.sys0_api->get_state + (system_power_ctx.config->ppu_sys0_id, &state); + if (status != FWK_SUCCESS) + return status; + + system_power_ctx.state = (state == MOD_PD_STATE_ON) ? + MOD_PD_STATE_ON : + MOD_SYSTEM_POWER_POWER_STATE_SLEEP0; + + return FWK_SUCCESS; +} + +const struct fwk_module module_system_power = { + .name = "SYSTEM_POWER", + .type = FWK_MODULE_TYPE_DRIVER, + .api_count = MOD_SYSTEM_POWER_API_COUNT, + .init = system_power_mod_init, + .bind = system_power_bind, + .start = system_power_start, + .process_bind_request = system_power_process_bind_request, +}; diff --git a/module/timer/include/mod_timer.h b/module/timer/include/mod_timer.h new file mode 100644 index 00000000..7bf3567a --- /dev/null +++ b/module/timer/include/mod_timer.h @@ -0,0 +1,294 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * Timer HAL + */ + +#ifndef MOD_TIMER_H +#define MOD_TIMER_H + +#include <stdbool.h> +#include <stdint.h> +#include <fwk_id.h> +#include <fwk_module_idx.h> + +/*! + * \addtogroup GroupModules Modules + * @{ + */ + +/*! + * \defgroup GroupModuleTimer Timer HAL + * + * \brief Hardware Abstraction Layer for Timers. + * + * \details Provides functionality for setting timer events, tracking elapsed + * time, and synchronously delaying execution. + * + * @{ + */ + +/*! + * \brief Timer module API indicies + */ +enum mod_timer_api_idx { + /*! Timer API index */ + MOD_TIMER_API_IDX_TIMER, + + /*! Alarm API index */ + MOD_TIMER_API_IDX_ALARM, + + /*! Number of APIs */ + MOD_TIMER_API_COUNT, +}; + +/*! + * \brief Timer API ID + */ +#define MOD_TIMER_API_ID_TIMER FWK_ID_API(FWK_MODULE_IDX_TIMER, \ + MOD_TIMER_API_IDX_TIMER) + +/*! + * \brief Alarm API ID + */ +#define MOD_TIMER_API_ID_ALARM FWK_ID_API(FWK_MODULE_IDX_TIMER, \ + MOD_TIMER_API_IDX_ALARM) + +/*! + * \brief Alarm type. + */ +enum mod_timer_alarm_type { + /*! Alarm that will trigger once */ + MOD_TIMER_ALARM_TYPE_ONCE, + + /*! Alarm that will trigger at regular intervals */ + MOD_TIMER_ALARM_TYPE_PERIODIC, + + /*! Number of alarm types */ + MOD_TIMER_ALARM_TYPE_COUNT, +}; + +/*! + * \brief Timer device descriptor + */ +struct mod_timer_dev_config { + /*! Element identifier for the device's associated driver */ + fwk_id_t id; + + /*! Timer device IRQ number */ + unsigned int timer_irq; +}; + +/*! + * \brief Timer driver interface. + */ +struct mod_timer_driver_api { + /*! Name of the driver. */ + const char *name; + + /*! Enable timer events */ + int (*enable)(fwk_id_t dev_id); + + /*! Disable timer events */ + int (*disable)(fwk_id_t dev_id); + + /*! Set timer event for a specified timestamp */ + int (*set_timer)(fwk_id_t dev_id, uint64_t timestamp); + + /*! Get remaining time until the next pending timer event is due to fire */ + int (*get_timer)(fwk_id_t dev_id, uint64_t *timestamp); + + /*! Get current counter value */ + int (*get_counter)(fwk_id_t dev_id, uint64_t *value); + + /*! Get counter frequency */ + int (*get_frequency)(fwk_id_t dev_id, uint32_t *value); +}; + +/*! + * \brief Timer HAL interface + */ +struct mod_timer_api { + /*! + * \brief Get the frequency of a given timer. + * + * \details Get the frequency in Hertz (Hz) that a timer is running at. + * + * \param dev_id Element identifier that identifies the timer device. + * \param frequency Pointer to storage for the timer frequency. + * + * \retval FWK_SUCCESS Operation succeeded. + * \retval FWK_E_PARAM The frequency pointer was invalid. + * \retval One of the other specific error codes described by the framework. + */ + int (*get_frequency)(fwk_id_t dev_id, uint32_t *frequency); + + /*! + * \brief Get a counter timestamp that represents a given time period in + * microseconds (µS). + * + * \note The value of the resulting timestamp is only valid for the given + * device, since other timer devices may operate at different rates. + * + * \param dev_id Element identifier that identifies the timer device. + * \param microseconds Period, in microseconds. + * \param timestamp Pointer to storage for the resulting counter timestamp. + * + * \retval FWK_SUCCESS Operation succeeded. + * \retval FWK_E_PARAM The timestamp pointer was invalid. + * \retval One of the other specific error codes described by the framework. + */ + int (*time_to_timestamp)(fwk_id_t dev_id, + uint32_t microseconds, + uint64_t *timestamp); + + /*! + * \brief Get the current counter value of a given timer. + * + * \details Directly returns the counter value of the timer at the present + * moment. + * + * \param dev_id Element identifier that identifies the timer device. + * \param counter Pointer to storage for the counter value. + * + * \retval FWK_SUCCESS Operation succeeded. + * \retval FWK_E_PARAM The counter pointer was invalid. + * \retval One of the other specific error codes described by the framework. + */ + int (*get_counter)(fwk_id_t dev_id, uint64_t *counter); + + /*! + * \brief Delay execution by synchronously waiting for a specified amount + * of time. + * + * \details Blocks the calling thread for the specified amount of time. + * + * \param dev_id Element identifier that identifies the timer device. + * \param microseconds The amount of time, given in microseconds, to delay. + * + * \retval FWK_SUCCESS Operation succeeded. + * \retval One of the other specific error codes described by the module. + */ + int (*delay)(fwk_id_t dev_id, uint32_t microseconds); + + /*! + * \brief Delay execution, waiting until a given condition is true or until + * a given timeout period has been exceeded, whichever occurs first. + * + * \note The calling thread is blocked until either condition has been met. + * + * \param dev_id Element identifier that identifies the timer device. + * \param microseconds Maximum amount of time, in microseconds, to wait for + * the given condition to be met. + * \param cond Pointer to the function that evaluates the condition and + * which returns a boolean value indicating if it has been met or not. + * The condition function is called repeatedly until it returns true, + * or until the timeout period has elapsed. + * \param data Pointer passed to the condition function when it is called. + * + * \retval FWK_SUCCESS The condition was met before the timeout period + * elapsed. + * \retval FWK_E_TIMEOUT The timeout period elapsed before the condition was + * met. + * \retval One of the other specific error codes described by the module. + */ + int (*wait)(fwk_id_t dev_id, + uint32_t microseconds, + bool (*cond)(void*), + void *data); + + /*! + * \brief Get the time difference, expressed in timer ticks, between the + * current timer counter value and the given timestamp. This represents + * the remaining number of ticks until the given timestamp is reached. + * + * \note If the given timestamp is in the past then the remaining_ticks is + * set to zero. + * + * \param dev_id Element identifier that identifies the timer device. + * \param timestamp Timestamp to compare to the current timer value. + * \param remaining_ticks Pointer to storage for the remaining number of + * ticks before the timer value reaches the given timestamp. + * + * \retval FWK_SUCCESS Operation succeeded. + * \retval FWK_E_PARAM The remaining_ticks pointer was invalid. + * \retval One of the other specific error codes described by the module. + * + * \note remaining_ticks is also a timestamp. + */ + int (*remaining)(fwk_id_t dev_id, + uint64_t timestamp, + uint64_t *remaining_ticks); +}; + +/*! + * \brief Alarm interface + */ +struct mod_timer_alarm_api { + /*! + * \brief Start an alarm so it will trigger after a specified time. + * + * \details When an alarm is triggered, an event with identifier + * \p event_id will be sent to the entity that is bound to the alarm. + * The first word of the event's parameter will be set to \p param. + * + * If the alarm is periodic, it will automatically be started again + * with the same time delay after it triggers. + * + * An alarm can be started multiple times without being stopped. In this + * case, internally, the alarm will be stopped then started again with + * the new configuration. + * + * \param alarm_id Sub-element identifier of the alarm. + * \param event_id Identifier of the event the caller is expecting. + * \param milliseconds The time delay, given in milliseconds, until the + * alarm should trigger. + * \param type \ref MOD_TIMER_ALARM_TYPE_ONCE or + * \ref MOD_TIMER_ALARM_TYPE_PERIODIC. + * \param param Word-size parameter for the event. + * + * \pre \p alarm_id must be a valid sub-element alarm identifier that has + * previously been bound to. + * + * \retval FWK_SUCCESS The alarm was started. + */ + int (*start)(fwk_id_t alarm_id, + unsigned int milliseconds, + enum mod_timer_alarm_type type, + fwk_id_t event_id, + uintptr_t param); + + /*! + * \brief Stop a previously started alarm. + * + * \details Stop an alarm that was previously started. This will prevent the + * alarm from triggering. This does not undo the binding of the alarm + * and it can be started again afterwards. + * + * Any pending alarm events associated with the alarm are not cancelled + * or removed when the alarm is stopped. + * + * \param alarm_id Sub-element identifier of the alarm item. + * + * \pre \p alarm_id must be a valid sub-element alarm identifier that has + * previously been bound to. + * + * \retval FWK_SUCCESS The alarm was stopped. + * \retval FWK_E_STATE The alarm was already stopped. + */ + int (*stop)(fwk_id_t alarm_id); +}; + +/*! + * @} + */ + +/*! + * @} + */ + +#endif /* MOD_TIMER_H */ diff --git a/module/timer/src/Makefile b/module/timer/src/Makefile new file mode 100644 index 00000000..a89d8a96 --- /dev/null +++ b/module/timer/src/Makefile @@ -0,0 +1,11 @@ +# +# Arm SCP/MCP Software +# Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +BS_LIB_NAME := Timer +BS_LIB_SOURCES = mod_timer.c + +include $(BS_DIR)/lib.mk diff --git a/module/timer/src/mod_timer.c b/module/timer/src/mod_timer.c new file mode 100644 index 00000000..2b61cd26 --- /dev/null +++ b/module/timer/src/mod_timer.c @@ -0,0 +1,653 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * Implementation of Timer module + */ + +#include <assert.h> +#include <stdbool.h> +#include <stdint.h> +#include <fwk_element.h> +#include <fwk_errno.h> +#include <fwk_event.h> +#include <fwk_id.h> +#include <fwk_interrupt.h> +#include <fwk_list.h> +#include <fwk_macros.h> +#include <fwk_mm.h> +#include <fwk_module.h> +#include <fwk_thread.h> +#include <mod_log.h> +#include <mod_timer.h> +#include <fwk_module_idx.h> + +/* Timer device context (element) */ +struct dev_ctx { + /* Pointer to the device's configuration */ + const struct mod_timer_dev_config *config; + /* Pointer to an API provided by the driver that controls the device */ + struct mod_timer_driver_api *driver; + /* Identifier of the driver that controls the device */ + fwk_id_t driver_dev_id; + /* Storage for all alarms */ + struct alarm_ctx *alarm_pool; + /* Queue of active alarms */ + struct fwk_dlist alarms_active; +}; + +/* Alarm item context (sub-element) */ +struct alarm_ctx { + /* List node */ + struct fwk_dlist_node node; + /* Time between starting this alarm and it triggering */ + uint32_t microseconds; + /* Timestamp of the time this alarm will trigger */ + uint64_t timestamp; + /* Identifier of the entity to send the alarm event to */ + fwk_id_t listener; + /* Identifier of the event the listener is expecting to receive */ + fwk_id_t event_id; + /* Parameter of the event */ + uintptr_t param; + /* Flag indicating if this alarm if periodic */ + bool periodic; + /* Flag indicating if this alarm is in the active queue */ + bool started; + /* Flag indicating if this alarm has been bound to */ + bool bound; +}; + +/* Table of timer device context structures */ +static struct dev_ctx *ctx_table; + +/* Log API */ +static const struct mod_log_api *log_api; + +/* + * Forward declarations + */ + +static void timer_isr(uintptr_t ctx_ptr); + +/* + * Internal functions + */ + +static int _time_to_timestamp(struct dev_ctx *ctx, + uint32_t microseconds, + uint64_t *timestamp) +{ + int status; + uint32_t frequency; + + assert(ctx != NULL); + assert(timestamp != NULL); + + status = ctx->driver->get_frequency(ctx->driver_dev_id, &frequency); + if (status != FWK_SUCCESS) + return status; + + *timestamp = ((uint64_t)frequency * microseconds) / 1000000; + + return FWK_SUCCESS; +} + +static int _timestamp_from_now(struct dev_ctx *ctx, + uint32_t microseconds, + uint64_t *timestamp) +{ + int status; + uint64_t counter; + + assert(ctx != NULL); + assert(timestamp != NULL); + + status = _time_to_timestamp(ctx, microseconds, timestamp); + if (status != FWK_SUCCESS) + return status; + + status = ctx->driver->get_counter(ctx->driver_dev_id, &counter); + if (status != FWK_SUCCESS) + return status; + + *timestamp += counter; + + return FWK_SUCCESS; +} + +static void _configure_timer_with_next_alarm(struct dev_ctx *ctx) +{ + struct alarm_ctx *alarm_head; + uint64_t counter = 0; + int status; + + assert(ctx != NULL); + + alarm_head = (struct alarm_ctx *)fwk_list_head(&ctx->alarms_active); + if (alarm_head != NULL) { + /* + * If an alarm's period is very small, the timer device could be + * configured to interrupt on a timestamp that is "in the past" by the + * time interrupts are enabled. In this case, the interrupt will not be + * generated due to a model bug. This code can be deleted once this bug + * has been fixed. + * + * If this alarm occurs very soon, process it immediately to avoid + * potentially missing the interrupt and waiting forever. + */ + status = ctx->driver->get_counter(ctx->driver_dev_id, &counter); + if ((status == FWK_SUCCESS) && + (counter + 2000 >= alarm_head->timestamp)) + timer_isr((uintptr_t)ctx); + + /* Configure timer device */ + ctx->driver->set_timer(ctx->driver_dev_id, alarm_head->timestamp); + ctx->driver->enable(ctx->driver_dev_id); + } +} + +static void _insert_alarm_ctx_into_active_queue(struct dev_ctx *ctx, + struct alarm_ctx *alarm_new) +{ + struct fwk_dlist_node *alarm_node; + struct alarm_ctx *alarm; + + assert(ctx != NULL); + assert(alarm_new != NULL); + + /* + * Search though the active queue to find the correct place to insert the + * new alarm item + */ + alarm_node = fwk_list_head(&ctx->alarms_active); + alarm = FWK_LIST_GET(alarm_node, struct alarm_ctx, node); + + while ((alarm_node != NULL) && (alarm_new->timestamp > alarm->timestamp)) { + alarm_node = fwk_list_next(&ctx->alarms_active, alarm_node); + alarm = FWK_LIST_GET(alarm_node, struct alarm_ctx, node); + } + + /* Insert alarm_new just BEFORE the alarm that was found */ + fwk_list_insert(&ctx->alarms_active, + &(alarm_new->node), + alarm_node); + + alarm_new->started = true; +} + + +/* + * Functions fulfilling the timer API + */ + +static int get_frequency(fwk_id_t dev_id, uint32_t *frequency) +{ + struct dev_ctx *ctx; + int status; + + status = fwk_module_check_call(dev_id); + if (status != FWK_SUCCESS) + return status; + + ctx = &ctx_table[fwk_id_get_element_idx(dev_id)]; + + if (frequency == NULL) + return FWK_E_PARAM; + + return ctx->driver->get_frequency(ctx->driver_dev_id, frequency); +} + +static int time_to_timestamp(fwk_id_t dev_id, + uint32_t microseconds, + uint64_t *timestamp) +{ + int status; + struct dev_ctx *ctx; + + status = fwk_module_check_call(dev_id); + if (status != FWK_SUCCESS) + return status; + + if (timestamp == NULL) + return FWK_E_PARAM; + + ctx = &ctx_table[fwk_id_get_element_idx(dev_id)]; + + return _time_to_timestamp(ctx, microseconds, timestamp); +} + +static int get_counter(fwk_id_t dev_id, uint64_t *counter) +{ + struct dev_ctx *ctx; + int status; + + status = fwk_module_check_call(dev_id); + if (status != FWK_SUCCESS) + return status; + + ctx = &ctx_table[fwk_id_get_element_idx(dev_id)]; + + if (counter == NULL) + return FWK_E_PARAM; + + /* Read counter */ + return ctx->driver->get_counter(ctx->driver_dev_id, counter); +} + +static int delay(fwk_id_t dev_id, uint32_t microseconds) +{ + int status; + struct dev_ctx *ctx; + uint64_t counter, counter_limit; + + status = fwk_module_check_call(dev_id); + if (status != FWK_SUCCESS) + return status; + + ctx = &ctx_table[fwk_id_get_element_idx(dev_id)]; + + status = _timestamp_from_now(ctx, microseconds, &counter_limit); + if (status != FWK_SUCCESS) + return status; + + do { + status = ctx->driver->get_counter(ctx->driver_dev_id, &counter); + if (status != FWK_SUCCESS) + return status; + } while (counter < counter_limit); + + return FWK_SUCCESS; +} + +static int wait(fwk_id_t dev_id, + uint32_t microseconds, + bool (*cond)(void*), + void *data) +{ + struct dev_ctx *ctx; + int status; + uint64_t counter, counter_limit; + + status = fwk_module_check_call(dev_id); + if (status != FWK_SUCCESS) + return status; + + ctx = &ctx_table[fwk_id_get_element_idx(dev_id)]; + + status = _timestamp_from_now(ctx, microseconds, &counter_limit); + if (status != FWK_SUCCESS) + return status; + + while (true) { + + if (cond(data)) + return FWK_SUCCESS; + + status = ctx->driver->get_counter(ctx->driver_dev_id, &counter); + if (status != FWK_SUCCESS) + return FWK_E_DEVICE; + + /* + * If the time to wait is over, check condition one last time. + */ + if (counter > counter_limit) { + if (cond(data)) + return FWK_SUCCESS; + else + return FWK_E_TIMEOUT; + } + } +} + +static int remaining(fwk_id_t dev_id, + uint64_t timestamp, + uint64_t *remaining_ticks) +{ + struct dev_ctx *ctx; + int status; + uint64_t counter; + + status = fwk_module_check_call(dev_id); + if (status != FWK_SUCCESS) + return status; + + ctx = &ctx_table[fwk_id_get_element_idx(dev_id)]; + + if (remaining_ticks == NULL) + return FWK_E_PARAM; + + status = ctx->driver->get_counter(ctx->driver_dev_id, &counter); + if (status != FWK_SUCCESS) + return status; + + if (timestamp <= counter) + *remaining_ticks = 0; + else + *remaining_ticks = timestamp - counter; + + return FWK_SUCCESS; +} + +static const struct mod_timer_api timer_api = { + .get_frequency = get_frequency, + .time_to_timestamp = time_to_timestamp, + .get_counter = get_counter, + .delay = delay, + .wait = wait, + .remaining = remaining, +}; + +/* + * Functions fulfilling the alarm API + */ + +static int alarm_stop(fwk_id_t alarm_id) +{ + int status; + struct dev_ctx *ctx; + struct alarm_ctx *alarm; + + assert(fwk_module_is_valid_sub_element_id(alarm_id)); + + status = fwk_module_check_call(alarm_id); + if (status != FWK_SUCCESS) + return status; + + ctx = &ctx_table[fwk_id_get_element_idx(alarm_id)]; + alarm = &ctx->alarm_pool[fwk_id_get_sub_element_idx(alarm_id)]; + + if (!alarm->started) + return FWK_E_STATE; + + /* Disable timer interrupts to work with the active queue */ + ctx->driver->disable(ctx->driver_dev_id); + + fwk_list_remove(&ctx->alarms_active, (struct fwk_dlist_node *)alarm); + alarm->started = false; + + _configure_timer_with_next_alarm(ctx); + + return FWK_SUCCESS; +} + +static int alarm_start(fwk_id_t alarm_id, + unsigned int milliseconds, + enum mod_timer_alarm_type type, + fwk_id_t event_id, + uintptr_t param) +{ + int status; + struct dev_ctx *ctx; + struct alarm_ctx *alarm; + + assert(fwk_module_is_valid_sub_element_id(alarm_id)); + + status = fwk_module_check_call(alarm_id); + if (status != FWK_SUCCESS) + return status; + + ctx = ctx_table + fwk_id_get_element_idx(alarm_id); + alarm = &ctx->alarm_pool[fwk_id_get_sub_element_idx(alarm_id)]; + + if (alarm->started) + alarm_stop(alarm_id); + + /* Cap to ensure value will not overflow when stored as microseconds */ + milliseconds = FWK_MIN(milliseconds, UINT32_MAX / 1000); + + /* Populate alarm item */ + alarm->event_id = event_id; + alarm->param = param; + alarm->periodic = + (type == MOD_TIMER_ALARM_TYPE_PERIODIC ? true : false); + alarm->microseconds = milliseconds * 1000; + status = _timestamp_from_now(ctx, + alarm->microseconds, + &alarm->timestamp); + if (status != FWK_SUCCESS) + return status; + + /* Disable timer interrupts to work with the active queue */ + ctx->driver->disable(ctx->driver_dev_id); + + _insert_alarm_ctx_into_active_queue(ctx, alarm); + + _configure_timer_with_next_alarm(ctx); + + return FWK_SUCCESS; +} + +static const struct mod_timer_alarm_api alarm_api = { + .start = alarm_start, + .stop = alarm_stop, +}; + +static void timer_isr(uintptr_t ctx_ptr) +{ + int status; + struct alarm_ctx *alarm; + struct alarm_ctx *alarm_head; + struct dev_ctx *ctx = (struct dev_ctx *)ctx_ptr; + uint64_t timestamp = 0; + uint64_t counter = 0; + struct fwk_event event; + + assert(ctx != NULL); + + /* Disable timer interrupts to work with the active queue */ + ctx->driver->disable(ctx->driver_dev_id); + fwk_interrupt_clear_pending(ctx->config->timer_irq); + +process_alarm: + alarm = (struct alarm_ctx *)fwk_list_pop_head(&ctx->alarms_active); + + if (alarm == NULL) { + /* Timer interrupt triggered without any alarm in the active queue */ + assert(false); + return; + } + + alarm->started = false; + + event = (struct fwk_event) { + .source_id = fwk_module_id_timer, + .target_id = alarm->listener, + .id = alarm->event_id, + }; + *(uintptr_t *)event.params = alarm->param; /* Word-size parameter */ + + status = fwk_thread_put_event(&event); + if (status != FWK_SUCCESS) + log_api->log(MOD_LOG_GROUP_WARNING, + "[Timer] Warning: Alarm was triggered but event could not " + "be pushed. Error code: %i\n", status); + + if (alarm->periodic) { + /* Put this alarm back into the active queue */ + status = _time_to_timestamp(ctx, alarm->microseconds, ×tamp); + + if (status == FWK_SUCCESS) { + alarm->timestamp += timestamp; + _insert_alarm_ctx_into_active_queue(ctx, alarm); + } else + log_api->log(MOD_LOG_GROUP_ERROR, + "[Timer] Error: Periodic alarm could not be added " + "back into queue.\n"); + } + + alarm_head = FWK_LIST_GET(fwk_list_head(&ctx->alarms_active), + struct alarm_ctx, + node); + if (alarm_head != NULL) { + /* + * If successive alarm item timestamps are very close together, the + * timer device could be configured to interrupt on a timestamp that is + * "in the past". In this case, the interrupt will not be generated due + * to a model bug. This code can be deleted once this bug has been + * fixed. + * + * If the next alarm occurs very soon, process it immidiately to avoid + * potentially missing the interrupt and waiting forever. + */ + status = ctx->driver->get_counter(ctx->driver_dev_id, &counter); + if ((status == FWK_SUCCESS) && + (counter + 2000 >= alarm_head->timestamp)) + goto process_alarm; + + ctx->driver->set_timer(ctx->driver_dev_id, alarm_head->timestamp); + ctx->driver->enable(ctx->driver_dev_id); + } +} + +/* + * Functions fulfilling the framework's module interface + */ + +static int timer_init(fwk_id_t module_id, + unsigned int element_count, + const void *data) +{ + ctx_table = fwk_mm_calloc(element_count, sizeof(struct dev_ctx)); + + if (ctx_table == NULL) + return FWK_E_NOMEM; + + return FWK_SUCCESS; +} + +static int timer_device_init(fwk_id_t element_id, unsigned int alarm_count, + const void *data) +{ + struct dev_ctx *ctx; + + assert(data != NULL); + + ctx = ctx_table + fwk_id_get_element_idx(element_id); + ctx->config = data; + + if (alarm_count > 0) { + ctx->alarm_pool = fwk_mm_calloc(alarm_count, sizeof(struct alarm_ctx)); + if (ctx->alarm_pool == NULL) { + assert(false); + return FWK_E_NOMEM; + } + } + + return FWK_SUCCESS; +} + +static int timer_bind(fwk_id_t id, unsigned int round) +{ + int status; + struct dev_ctx *ctx; + struct mod_timer_driver_api *driver; + unsigned int driver_module_idx; + + /* Nothing to do after the initial round. */ + if (round > 0) + return FWK_SUCCESS; + + /* Bind to log module */ + if (fwk_module_is_valid_module_id(id)) { + return fwk_module_bind(fwk_module_id_log, + FWK_ID_API(FWK_MODULE_IDX_LOG, 0), + &log_api); + } + + ctx = ctx_table + fwk_id_get_element_idx(id); + ctx->driver_dev_id = ctx->config->id; + + /* Bind to the driver API for the current device */ + driver_module_idx = fwk_id_get_module_idx(ctx->driver_dev_id); + status = fwk_module_bind(ctx->driver_dev_id, + FWK_ID_API(driver_module_idx, 0), + &driver); + if (status != FWK_SUCCESS) + return status; + + /* Check that the driver API is completely fulfilled */ + if (driver->enable == NULL || + driver->disable == NULL || + driver->get_counter == NULL || + driver->get_frequency == NULL) + return FWK_E_DEVICE; + + ctx->driver = driver; + + return FWK_SUCCESS; +} + +static int timer_process_bind_request(fwk_id_t requester_id, + fwk_id_t id, + fwk_id_t api_id, + const void **api) +{ + struct dev_ctx *ctx; + struct alarm_ctx *alarm_ctx; + + if (fwk_id_is_equal(api_id, MOD_TIMER_API_ID_TIMER)) { + if (!fwk_module_is_valid_element_id(id)) { + assert(false); + return FWK_E_PARAM; + } + + *api = &timer_api; + return FWK_SUCCESS; + } + + /* Alarm API requested */ + + if (!fwk_module_is_valid_sub_element_id(id)) { + assert(false); + return FWK_E_PARAM; + } + + ctx = ctx_table + fwk_id_get_element_idx(id); + alarm_ctx = &ctx->alarm_pool[fwk_id_get_sub_element_idx(id)]; + + if (alarm_ctx->bound) { + assert(false); + return FWK_E_STATE; + } + + alarm_ctx->bound = true; + alarm_ctx->listener = requester_id; + + *api = &alarm_api; + return FWK_SUCCESS; +} + +static int timer_start(fwk_id_t id) +{ + struct dev_ctx *ctx; + + if (!fwk_module_is_valid_element_id(id)) + return FWK_SUCCESS; + + ctx = ctx_table + fwk_id_get_element_idx(id); + + fwk_list_init(&ctx->alarms_active); + + fwk_interrupt_set_isr_param(ctx->config->timer_irq, + timer_isr, + (uintptr_t)ctx); + fwk_interrupt_enable(ctx->config->timer_irq); + + return FWK_SUCCESS; +} + +/* Module descriptor */ +const struct fwk_module module_timer = { + .name = "Timer HAL", + .api_count = MOD_TIMER_API_COUNT, + .type = FWK_MODULE_TYPE_HAL, + .init = timer_init, + .element_init = timer_device_init, + .bind = timer_bind, + .process_bind_request = timer_process_bind_request, + .start = timer_start, +}; diff --git a/product/sgm775/include/fmw_cmsis.h b/product/sgm775/include/fmw_cmsis.h new file mode 100644 index 00000000..8cf22ddd --- /dev/null +++ b/product/sgm775/include/fmw_cmsis.h @@ -0,0 +1,30 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef FMW_CMSIS_H +#define FMW_CMSIS_H + +#define __CHECK_DEVICE_DEFINES +#define __CM3_REV 0x0201 +#define __MPU_PRESENT 1 +#define __NVIC_PRIO_BITS 3 +#define __Vendor_SysTickConfig 0 + +typedef enum IRQn { + NonMaskableInt_IRQn = -14, + MemoryManagement_IRQn = -12, + BusFault_IRQn = -11, + UsageFault_IRQn = -10, + SVCall_IRQn = -5, + DebugMonitor_IRQn = -4, + PendSV_IRQn = -2, + SysTick_IRQn = -1, +} IRQn_Type; + +#include <core_cm3.h> + +#endif /* FMW_CMSIS_H */ diff --git a/product/sgm775/include/sgm775_core.h b/product/sgm775/include/sgm775_core.h new file mode 100644 index 00000000..afa59509 --- /dev/null +++ b/product/sgm775/include/sgm775_core.h @@ -0,0 +1,15 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef SGM775_CORE_H +#define SGM775_CORE_H + +#define SGM775_CORE_PER_CLUSTER_MAX 8 + +unsigned int sgm775_core_get_count(void); + +#endif /* SGM775_CORE_H */ diff --git a/product/sgm775/include/sgm775_irq.h b/product/sgm775/include/sgm775_irq.h new file mode 100644 index 00000000..fbbe92e7 --- /dev/null +++ b/product/sgm775/include/sgm775_irq.h @@ -0,0 +1,178 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef SGM775_IRQ_H +#define SGM775_IRQ_H + +#include <fwk_interrupt.h> + +#define WDOG_IRQ FWK_INTERRUPT_NMI /* SCP Watchdog (SP805) */ + +enum sgm775_irq { + TIM32KHZ_IRQ = 0, /* 32KHz Physical Timer */ + CDBG_PWR_UP_REQ_IRQ = 1, /* Coresight Debug Power Request */ + CSYS_PWR_UP_REQ_IRQ = 2, /* Coresight System Power Request */ + CDBG_RST_REQ_IRQ = 3, /* Coresight Debug Reset Request */ + GIC_EXT_WAKEUP_IRQ = 4, /* External GIC Wakeup Request */ + RESERVED5_IRQ = 5, /* Reserved */ + RESERVED6_IRQ = 6, /* Reserved */ + RESERVED7_IRQ = 7, /* Reserved */ + RESERVED8_IRQ = 8, /* Reserved */ + RESERVED9_IRQ = 9, /* Reserved */ + RESERVED10_IRQ = 10, /* Reserved */ + RESERVED11_IRQ = 11, /* Reserved */ + RESERVED12_IRQ = 12, /* Reserved */ + RESERVED13_IRQ = 13, /* Reserved */ + RESERVED14_IRQ = 14, /* Reserved */ + RESERVED15_IRQ = 15, /* Reserved */ + SOC_WAKEUP0_IRQ = 16, /* SoC Expansion Wakeup */ + SOC_WAKEUP1_IRQ = 17, /* SoC Expansion Wakeup */ + SOC_WAKEUP2_IRQ = 18, /* SoC Expansion Wakeup */ + SOC_WAKEUP3_IRQ = 19, /* SoC Expansion Wakeup */ + SOC_WAKEUP4_IRQ = 20, /* SoC Expansion Wakeup */ + SOC_WAKEUP5_IRQ = 21, /* SoC Expansion Wakeup */ + SOC_WAKEUP6_IRQ = 22, /* SoC Expansion Wakeup */ + SOC_WAKEUP7_IRQ = 23, /* SoC Expansion Wakeup */ + SOC_WAKEUP8_IRQ = 24, /* SoC Expansion Wakeup */ + SOC_WAKEUP9_IRQ = 25, /* SoC Expansion Wakeup */ + SOC_WAKEUP10_IRQ = 26, /* SoC Expansion Wakeup */ + SOC_WAKEUP11_IRQ = 27, /* SoC Expansion Wakeup */ + SOC_WAKEUP12_IRQ = 28, /* SoC Expansion Wakeup */ + SOC_WAKEUP13_IRQ = 29, /* SoC Expansion Wakeup */ + SOC_WAKEUP14_IRQ = 30, /* SoC Expansion Wakeup */ + SOC_WAKEUP15_IRQ = 31, /* SoC Expansion Wakeup */ + PPU_SCP_IRQ = 32, /* SCP Power Policy Unit */ + TIMREFCLK_IRQ = 33, /* REFCLK Physical Timer */ + MHU_HIGH_PRIO_IRQ = 34, /* MHU High Priority */ + MHU_LOW_PRIO_IRQ = 35, /* MHU Low Priority */ + MHU_SECURE_IRQ = 36, /* MHU Secure */ + CTI_TRIGGER0_IRQ = 37, /* SCP CTI Trigger */ + CTI_TRIGGER1_IRQ = 38, /* SCP CTI Trigger */ + GIC_ERROR_ECC_IRQ = 39, /* GIC Error (ECC Fatal) */ + GIC_ERROR_AXIM_IRQ = 40, /* GIC Error (AXIM) */ + DMC_RESERVED0_IRQ = 41, /* DMC, Reserved */ + DMC_0_ERROR_ECC_IRQ = 42, /* DMC0 Combined ECC Error */ + DMC_0_ERROR_ACCESS_IRQ = 43, /* DMC0 Combined Misc Access Error */ + DMC_RESERVED1_IRQ = 44, /* DMC, Reserved */ + DMC_RESERVED2_IRQ = 45, /* DMC, Reserved */ + DMC_1_ERROR_ECC_IRQ = 46, /* DMC1 Combined ECC Error */ + DMC_1_ERROR_ACCESS_IRQ = 47, /* DMC1 Combined Misc Access Error */ + DMC_RESERVED3_IRQ = 48, /* DMC, Reserved */ + DMC_RESERVED4_IRQ = 49, /* DMC, Reserved */ + DMC_2_ERROR_ECC_IRQ = 50, /* DMC2 Combined ECC Error */ + DMC_2_ERROR_ACCESS_IRQ = 51, /* DMC2 Combined Misc Access Error */ + DMC_RESERVED5_IRQ = 52, /* DMC, Reserved */ + DMC_RESERVED6_IRQ = 53, /* DMC, Reserved */ + DMC_3_ERROR_ECC_IRQ = 54, /* DMC3 Combined ECC Error */ + DMC_3_ERROR_ACCESS_IRQ = 55, /* DMC3 Combined Misc Access Error */ + DMC_RESERVED7_IRQ = 56, /* DMC, Reserved */ + RESERVED57_IRQ = 57, /* Reserved */ + RESERVED58_IRQ = 58, /* Reserved */ + RESERVED59_IRQ = 59, /* Reserved */ + RESERVED60_IRQ = 60, /* Reserved */ + RESERVED61_IRQ = 61, /* Reserved */ + RESERVED62_IRQ = 62, /* Reserved */ + RESERVED63_IRQ = 63, /* Reserved */ + PPU_CLUS0CORE0_IRQ = 64, /* Cluster 0 Core 0 Power Policy Unit */ + PPU_CLUS0CORE1_IRQ = 65, /* Cluster 0 Core 1 Power Policy Unit */ + PPU_CLUS0CORE2_IRQ = 66, /* Cluster 0 Core 2 Power Policy Unit */ + PPU_CLUS0CORE3_IRQ = 67, /* Cluster 0 Core 3 Power Policy Unit */ + PPU_CLUS0_IRQ = 68, /* Cluster 0 Power Policy Unit */ + PPU_CLUS1CORE0_IRQ = 69, /* Cluster 1 Core 0 Power Policy Unit */ + PPU_CLUS1CORE1_IRQ = 70, /* Cluster 1 Core 1 Power Policy Unit */ + PPU_CLUS1CORE2_IRQ = 71, /* Cluster 1 Core 2 Power Policy Unit */ + PPU_CLUS1CORE3_IRQ = 72, /* Cluster 1 Core 3 Power Policy Unit */ + PPU_CLUS1_IRQ = 73, /* Cluster 1 Power Policy Unit */ + PPU_SYS0_IRQ = 74, /* System Power Policy Unit 0 */ + PPU_SYS1_IRQ = 75, /* System Power Policy Unit 1 */ + PPU_GPU_IRQ = 76, /* GPU Power Policy Unit */ + PPU_VPU_IRQ = 77, /* Video Power Policy Unit */ + RESERVED78_IRQ = 78, /* Reserved */ + PPU_DPU0_IRQ = 79, /* Display Power Policy Unit 0 */ + PPU_DPU1_IRQ = 80, /* Display Power Policy Unit 1 */ + PPU_DEBUG_IRQ = 81, /* DBGSYS Power Policy Unit */ + RESERVED82_IRQ = 82, /* Reserved */ + RESERVED83_IRQ = 83, /* Reserved */ + RESERVED84_IRQ = 84, /* Reserved */ + RESERVED85_IRQ = 85, /* Reserved */ + RESERVED86_IRQ = 86, /* Reserved */ + RESERVED87_IRQ = 87, /* Reserved */ + RESERVED88_IRQ = 88, /* Reserved */ + RESERVED89_IRQ = 89, /* Reserved */ + PPU_CLUS0CORE4_IRQ = 90, /* Cluster 0 Core 4 Power Policy Unit */ + PPU_CLUS0CORE5_IRQ = 91, /* Cluster 0 Core 5 Power Policy Unit */ + PPU_CLUS0CORE6_IRQ = 92, /* Cluster 0 Core 6 Power Policy Unit */ + PPU_CLUS0CORE7_IRQ = 93, /* Cluster 0 Core 7 Power Policy Unit */ + PPU_CLUS1CORE4_IRQ = 94, /* Cluster 1 Core 4 Power Policy Unit */ + PPU_CLUS1CORE5_IRQ = 95, /* Cluster 1 Core 5 Power Policy Unit */ + PPU_CLUS1CORE6_IRQ = 96, /* Cluster 1 Core 6 Power Policy Unit */ + PPU_CLUS1CORE7_IRQ = 97, /* Cluster 1 Core 7 Power Policy Unit */ + PLL_CLUS0_LOCK_IRQ = 98, /* Cluster 0 CPU PLL Lock */ + PLL_CLUS1_LOCK_IRQ = 99, /* Cluster 1 CPU PLL Lock */ + PLL_GPU_LOCK_IRQ = 100, /* GPU PLL Lock */ + PLL_VPU_LOCK_IRQ = 101, /* Video PLL Lock */ + PLL_SYS_LOCK_IRQ = 102, /* System PLL Lock */ + PLL_DPU_LOCK_IRQ = 103, /* Display PLL Lock */ + PLL_CLUS0CORE0_IRQ = 104, /* Cluster 0 PLL0 Lock */ + PLL_CLUS0CORE1_IRQ = 105, /* Cluster 0 PLL1 Lock */ + PLL_CLUS0CORE2_IRQ = 106, /* Cluster 0 PLL2 Lock */ + PLL_CLUS0CORE3_IRQ = 107, /* Cluster 0 PLL3 Lock */ + PLL_CLUS0CORE4_IRQ = 108, /* Cluster 0 PLL4 Lock */ + PLL_CLUS0CORE5_IRQ = 109, /* Cluster 0 PLL5 Lock */ + PLL_CLUS0CORE6_IRQ = 110, /* Cluster 0 PLL6 Lock */ + PLL_CLUS0CORE7_IRQ = 111, /* Cluster 0 PLL7 Lock */ + PLL_CLUS1CORE0_IRQ = 112, /* Cluster 1 PLL0 Lock */ + PLL_CLUS1CORE1_IRQ = 113, /* Cluster 1 PLL1 Lock */ + PLL_CLUS1CORE2_IRQ = 114, /* Cluster 1 PLL2 Lock */ + PLL_CLUS1CORE3_IRQ = 115, /* Cluster 1 PLL3 Lock */ + PLL_CLUS1CORE4_IRQ = 116, /* Cluster 1 PLL4 Lock */ + PLL_CLUS1CORE5_IRQ = 117, /* Cluster 1 PLL5 Lock */ + PLL_CLUS1CORE6_IRQ = 118, /* Cluster 1 PLL6 Lock */ + PLL_CLUS1CORE7_IRQ = 119, /* Cluster 1 PLL7 Lock */ + RESERVED120_IRQ = 120, /* Reserved */ + RESERVED121_IRQ = 121, /* Reserved */ + RESERVED122_IRQ = 122, /* Reserved */ + RESERVED123_IRQ = 123, /* Reserved */ + RESERVED124_IRQ = 124, /* Reserved */ + RESERVED125_IRQ = 125, /* Reserved */ + RESERVED126_IRQ = 126, /* Reserved */ + RESERVED127_IRQ = 127, /* Reserved */ + SCP_EXT_INTR0_IRQ = 128, /* SCP Customer Extension */ + SCP_EXT_INTR1_IRQ = 129, /* SCP Customer Extension */ + SCP_EXT_INTR2_IRQ = 130, /* SCP Customer Extension */ + SCP_EXT_INTR3_IRQ = 131, /* SCP Customer Extension */ + SCP_EXT_INTR4_IRQ = 132, /* SCP Customer Extension */ + SCP_EXT_INTR5_IRQ = 133, /* SCP Customer Extension */ + SCP_EXT_INTR6_IRQ = 134, /* SCP Customer Extension */ + SCP_EXT_INTR7_IRQ = 135, /* SCP Customer Extension */ + SCP_EXT_INTR8_IRQ = 136, /* SCP Customer Extension */ + SCP_EXT_INTR9_IRQ = 137, /* SCP Customer Extension */ + SCP_EXT_INTR10_IRQ = 138, /* SCP Customer Extension */ + SCP_EXT_INTR11_IRQ = 139, /* SCP Customer Extension */ + SCP_EXT_INTR12_IRQ = 140, /* SCP Customer Extension */ + SCP_EXT_INTR13_IRQ = 141, /* SCP Customer Extension */ + SCP_EXT_INTR14_IRQ = 142, /* SCP Customer Extension */ + SCP_EXT_INTR15_IRQ = 143, /* SCP Customer Extension */ + SCP_EXT_INTR16_IRQ = 144, /* SCP Customer Extension */ + SCP_EXT_INTR17_IRQ = 145, /* SCP Customer Extension */ + SCP_EXT_INTR18_IRQ = 146, /* SCP Customer Extension */ + SCP_EXT_INTR19_IRQ = 147, /* SCP Customer Extension */ + SCP_EXT_INTR20_IRQ = 148, /* SCP Customer Extension */ + SCP_EXT_INTR21_IRQ = 149, /* SCP Customer Extension */ + SCP_EXT_INTR22_IRQ = 150, /* SCP Customer Extension */ + SCP_EXT_INTR23_IRQ = 151, /* SCP Customer Extension */ + SCP_EXT_INTR24_IRQ = 152, /* SCP Customer Extension */ + SCP_EXT_INTR25_IRQ = 153, /* SCP Customer Extension */ + SCP_EXT_INTR26_IRQ = 154, /* SCP Customer Extension */ + SCP_EXT_INTR27_IRQ = 155, /* SCP Customer Extension */ + SCP_EXT_INTR28_IRQ = 156, /* SCP Customer Extension */ + SCP_EXT_INTR29_IRQ = 157, /* SCP Customer Extension */ + SCP_EXT_INTR30_IRQ = 158, /* SCP Customer Extension */ + SCP_EXT_INTR31_IRQ = 159, /* SCP Customer Extension */ +}; + +#endif /* SGM775_IRQ_H */ diff --git a/product/sgm775/include/sgm775_mhu.h b/product/sgm775/include/sgm775_mhu.h new file mode 100644 index 00000000..8cdc4dd0 --- /dev/null +++ b/product/sgm775/include/sgm775_mhu.h @@ -0,0 +1,21 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * MHU module device indexes. + */ + +#ifndef SGM775_MHU_H +#define SGM775_MHU_H + +enum sgm775_mhu_device_idx { + SGM775_MHU_DEVICE_IDX_S, + SGM775_MHU_DEVICE_IDX_NS_H, + SGM775_MHU_DEVICE_IDX_NS_L, + SGM775_MHU_DEVICE_IDX_COUNT +}; + +#endif /* SGM775_MHU_H */ diff --git a/product/sgm775/include/sgm775_mmap.h b/product/sgm775/include/sgm775_mmap.h new file mode 100644 index 00000000..16626760 --- /dev/null +++ b/product/sgm775/include/sgm775_mmap.h @@ -0,0 +1,101 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef SGM775_MMAP_H +#define SGM775_MMAP_H + +#include <stdint.h> + +/* + * Top-level base addresses + */ +#define EXPANSION0_BASE UINT32_C(0x40000000) +#define PERIPHERAL_BASE UINT32_C(0x44000000) +#define POWER_PERIPHERAL_BASE UINT32_C(0x50000000) +#define SYS0_BASE UINT32_C(0x60000000) +#define SYS1_BASE UINT32_C(0xA0000000) +#define PPB_INTERNAL_BASE UINT32_C(0xE0000000) +#define PPB_EXTERNAL_BASE UINT32_C(0xE0040000) +#define EXPANSION1_BASE UINT32_C(0xE0100000) + +/* + * Peripherals + */ +#define REFCLK_CNTCTL_BASE (PERIPHERAL_BASE + 0x0000) +#define REFCLK_CNTBASE0_BASE (PERIPHERAL_BASE + 0x1000) +#define WDOG_BASE (PERIPHERAL_BASE + 0x6000) +#define S32K_CNTCONTROL_BASE (PERIPHERAL_BASE + 0x7000) +#define S32K_CNTCTL_BASE (PERIPHERAL_BASE + 0x8000) +#define S32K_CNTBASE0_BASE (PERIPHERAL_BASE + 0x9000) +#define CS_CNTCONTROL_BASE (PERIPHERAL_BASE + 0xA000) + +/* + * Power control peripherals + */ +#define PIK_SCP_BASE (POWER_PERIPHERAL_BASE + 0x00000) +#define PIK_DEBUG_BASE (POWER_PERIPHERAL_BASE + 0x20000) +#define SENSOR_DEBUG_BASE (POWER_PERIPHERAL_BASE + 0x30000) +#define PIK_SYSTEM_BASE (POWER_PERIPHERAL_BASE + 0x40000) +#define SENSOR_SYSTEM_BASE (POWER_PERIPHERAL_BASE + 0x50000) +#define PIK_CLUS0_BASE (POWER_PERIPHERAL_BASE + 0x60000) +#define SENSOR_CLUS0_BASE (POWER_PERIPHERAL_BASE + 0x70000) +#define PIK_CLUS1_BASE (POWER_PERIPHERAL_BASE + 0x80000) +#define SENSOR_CLUS1_BASE (POWER_PERIPHERAL_BASE + 0x90000) +#define PIK_GPU_BASE (POWER_PERIPHERAL_BASE + 0xA0000) +#define SENSOR_GPU_BASE (POWER_PERIPHERAL_BASE + 0xB0000) +#define PIK_VPU_BASE (POWER_PERIPHERAL_BASE + 0xC0000) +#define SENSOR_VPU_BASE (POWER_PERIPHERAL_BASE + 0xD0000) +#define PIK_DPU_BASE (POWER_PERIPHERAL_BASE + 0xE0000) +#define SENSOR_DPU_BASE (POWER_PERIPHERAL_BASE + 0xF0000) + +/* + * PPU base address + */ +#define PPU_SCP_BASE (PIK_SCP_BASE + 0x1000) +#define PPU_SYS0_BASE (PIK_SYSTEM_BASE + 0x1000) +#define PPU_SYS1_BASE (PIK_SYSTEM_BASE + 0x2000) +#define PPU_DEBUG_BASE (PIK_DEBUG_BASE + 0x1000) +#define PPU_CLUS0CORE0_BASE (PIK_CLUS0_BASE + 0x2000) +#define PPU_CLUS0CORE1_BASE (PIK_CLUS0_BASE + 0x3000) +#define PPU_CLUS0CORE2_BASE (PIK_CLUS0_BASE + 0x4000) +#define PPU_CLUS0CORE3_BASE (PIK_CLUS0_BASE + 0x5000) +#define PPU_CLUS0CORE4_BASE (PIK_CLUS0_BASE + 0x6000) +#define PPU_CLUS0CORE5_BASE (PIK_CLUS0_BASE + 0x7000) +#define PPU_CLUS0CORE6_BASE (PIK_CLUS0_BASE + 0x8000) +#define PPU_CLUS0CORE7_BASE (PIK_CLUS0_BASE + 0x9000) +#define PPU_CLUS0_BASE (PIK_CLUS0_BASE + 0x1000) +#define PPU_CLUS1CORE0_BASE (PIK_CLUS1_BASE + 0x2000) +#define PPU_CLUS1CORE1_BASE (PIK_CLUS1_BASE + 0x3000) +#define PPU_CLUS1CORE2_BASE (PIK_CLUS1_BASE + 0x4000) +#define PPU_CLUS1CORE3_BASE (PIK_CLUS1_BASE + 0x5000) +#define PPU_CLUS1_BASE (PIK_CLUS1_BASE + 0x1000) +#define PPU_GPU_BASE (PIK_GPU_BASE + 0x1000) +#define PPU_VPU_BASE (PIK_VPU_BASE + 0x1000) +#define PPU_DPU0_BASE (PIK_DPU_BASE + 0x2000) +#define PPU_DPU1_BASE (PIK_DPU_BASE + 0x3000) + +/* + * System access port 1 + */ +#define TRUSTED_RAM_BASE (SYS1_BASE + 0x04000000) +#define NONTRUSTED_RAM_BASE (SYS1_BASE + 0x06000000) +#define SSC_BASE (SYS1_BASE + 0x2A420000) +#define REFCLK_CNTCONTROL_BASE (SYS1_BASE + 0x2A430000) +#define MHU_BASE (SYS1_BASE + 0x2B1F0000) + +/* + * Base addresses of MHU devices + */ + +#define MHU_SCP_INTR_L_BASE (MHU_BASE) +#define MHU_SCP_INTR_H_BASE (MHU_BASE + 0x0020) +#define MHU_CPU_INTR_L_BASE (MHU_BASE + 0x0100) +#define MHU_CPU_INTR_H_BASE (MHU_BASE + 0x0120) +#define MHU_SCP_INTR_S_BASE (MHU_BASE + 0x0200) +#define MHU_CPU_INTR_S_BASE (MHU_BASE + 0x0300) + +#endif /* SGM775_MMAP_H */ diff --git a/product/sgm775/include/sgm775_mmap_scp.h b/product/sgm775/include/sgm775_mmap_scp.h new file mode 100644 index 00000000..a4ceb5b9 --- /dev/null +++ b/product/sgm775/include/sgm775_mmap_scp.h @@ -0,0 +1,19 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * SCP ROM and RAM memory bases. These definitions are kept isolated without + * the UINT32_C() or UL decorators allowing them to be used in the linker + * script. + */ + +#ifndef SGM775_MMAP_SCP_H +#define SGM775_MMAP_SCP_H + +#define SCP_ROM_BASE 0x00000000 +#define SCP_RAM_BASE 0x10000000 + +#endif /* SGM775_MMAP_SCP_H */ diff --git a/product/sgm775/include/sgm775_pik.h b/product/sgm775/include/sgm775_pik.h new file mode 100644 index 00000000..7d0ac12b --- /dev/null +++ b/product/sgm775/include/sgm775_pik.h @@ -0,0 +1,29 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef SGM775_PIK_H +#define SGM775_PIK_H + +#include <sgm775_mmap.h> +#include <sgm775_pik_cpu.h> +#include <sgm775_pik_debug.h> +#include <sgm775_pik_dpu.h> +#include <sgm775_pik_gpu.h> +#include <sgm775_pik_scp.h> +#include <sgm775_pik_system.h> +#include <sgm775_pik_vpu.h> + +#define PIK_CLUS0 ((struct pik_cpu_reg_v8_2 *) PIK_CLUS0_BASE) +#define PIK_CLUS1 ((struct pik_cpu_reg_v8_2 *) PIK_CLUS1_BASE) +#define PIK_DEBUG ((struct pik_debug_reg *) PIK_DEBUG_BASE) +#define PIK_DPU ((struct pik_dpu_reg *) PIK_DPU_BASE) +#define PIK_GPU ((struct pik_gpu_reg *) PIK_GPU_BASE) +#define PIK_SCP ((struct pik_scp_reg *) PIK_SCP_BASE) +#define PIK_SYSTEM ((struct pik_system_reg *) PIK_SYSTEM_BASE) +#define PIK_VPU ((struct pik_vpu_reg *) PIK_VPU_BASE) + +#endif /* SGM775_PIK_H */ diff --git a/product/sgm775/include/sgm775_pik_cpu.h b/product/sgm775/include/sgm775_pik_cpu.h new file mode 100644 index 00000000..5c7cc646 --- /dev/null +++ b/product/sgm775/include/sgm775_pik_cpu.h @@ -0,0 +1,145 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef SGM775_PIK_CPU_H +#define SGM775_PIK_CPU_H + +#include <stdint.h> +#include <fwk_macros.h> + +#define PE_COUNT_MAX 16 + +/*! + * \brief CPU PIK V8 register definitions + */ +struct pik_cpu_reg_v8 { + FWK_RW uint32_t STATIC_CONFIG; + uint8_t RESERVED0[0x10 - 0x4]; + FWK_RW uint32_t RVBARADDR0_LW; + FWK_RW uint32_t RVBARADDR0_UP; + FWK_RW uint32_t RVBARADDR1_LW; + FWK_RW uint32_t RVBARADDR1_UP; + FWK_RW uint32_t RVBARADDR2_LW; + FWK_RW uint32_t RVBARADDR2_UP; + FWK_RW uint32_t RVBARADDR3_LW; + FWK_RW uint32_t RVBARADDR3_UP; + FWK_RW uint32_t CLUSTER_CONFIG; + uint8_t RESERVED1[0x200 - 0x34]; + FWK_R uint32_t DBG_RST_STATU; + FWK_RW uint32_t DBG_RST_SET; + FWK_RW uint32_t DBG_RST_CLR; + uint8_t RESERVED2[0x400 - 0x20C]; + FWK_RW uint32_t CPUACTIVE_CTRL; + uint8_t RESERVED3[0x800 - 0x404]; + FWK_RW uint32_t PPUCLK_CTRL; + FWK_RW uint32_t PPUCLK_DIV1; + FWK_RW uint32_t PPUCLK_DIV2; + FWK_R uint32_t RESERVED4; + FWK_RW uint32_t CPUCLK_CTRL; + FWK_RW uint32_t CPUCLK_DIV1; + FWK_RW uint32_t CPUCLK_DIV2; + FWK_R uint32_t RESERVED5; + FWK_RW uint32_t PCLKDBG_CTRL; + uint8_t RESERVED6[0x830 - 0x824]; + FWK_RW uint32_t ATCLKDBG_CTRL; + uint8_t RESERVED7[0x840 - 0x834]; + FWK_RW uint32_t ACLKCPU_CTRL; + uint8_t RESERVED8[0xA00 - 0x844]; + FWK_R uint32_t CLKFORCE_STATUS; + FWK_RW uint32_t CLKFORCE_SET; + FWK_RW uint32_t CLKFORCE_CLR; + uint8_t RESERVED9[0xFC0 - 0xA0C]; + FWK_R uint32_t PIK_CONFIG; + uint8_t RESERVED10[0xFD0 - 0xFC4]; + FWK_R uint32_t PID4; + FWK_R uint32_t PID5; + FWK_R uint32_t PID6; + FWK_R uint32_t PID7; + FWK_R uint32_t PID0; + FWK_R uint32_t PID1; + FWK_R uint32_t PID2; + FWK_R uint32_t PID3; + FWK_R uint32_t ID0; + FWK_R uint32_t ID1; + FWK_R uint32_t ID2; + FWK_R uint32_t ID3; +}; + +/*! + * \brief PE Static Configuration register definitions + */ +struct static_config { + FWK_RW uint32_t STATIC_CONFIG; + FWK_RW uint32_t RVBARADDR_LW; + FWK_RW uint32_t RVBARADDR_UP; + uint32_t RESERVED; +}; + +/*! + * \brief AP cores clock control register definitions + */ +struct ap_clk_ctrl { + FWK_RW uint32_t CORECLK_CTRL; + FWK_RW uint32_t CORECLK_DIV; + uint32_t RESERVED; + FWK_RW uint32_t CORECLK_MOD; +}; + +/*! + * \brief CPU PIK V8.2 register definitions + */ +struct pik_cpu_reg_v8_2 { + FWK_RW uint32_t CLUSTER_CONFIG; + uint8_t RESERVED0[0x10 - 0x4]; + struct static_config STATIC_CONFIG[PE_COUNT_MAX]; + uint8_t RESERVED1[0x800 - 0x110]; + FWK_RW uint32_t PPUCLK_CTRL; + FWK_RW uint32_t PPUCLK_DIV1; + uint8_t RESERVED2[0x810 - 0x808]; + FWK_RW uint32_t PCLK_CTRL; + uint8_t RESERVED3[0x820 - 0x814]; + FWK_RW uint32_t ATCLK_CTRL; + uint8_t RESERVED4[0x830 - 0x824]; + FWK_RW uint32_t GICCLK_CTRL; + uint8_t RESERVED5[0x840 - 0x834]; + FWK_RW uint32_t AMBACLK_CTRL; + uint8_t RESERVED6[0x850 - 0x844]; + FWK_RW uint32_t CLUSCLK_CTRL; + FWK_RW uint32_t CLUSCLK_DIV1; + uint8_t RESERVED7[0x860 - 0x858]; + struct ap_clk_ctrl AP_CLK_CTRL[8]; + uint8_t RESERVED8[0xA00 - 0x8E0]; + FWK_R uint32_t CLKFORCE_STATUS; + FWK_RW uint32_t CLKFORCE_SET; + FWK_RW uint32_t CLKFORCE_CLR; + uint8_t RESERVED9[0xFB8 - 0xA0C]; + FWK_R uint32_t CAP2; + FWK_R uint32_t CAP; + FWK_R uint32_t PIK_CONFIG; + uint8_t RESERVED10[0xFD0 - 0xFC4]; + FWK_R uint32_t PID4; + FWK_R uint32_t PID5; + FWK_R uint32_t PID6; + FWK_R uint32_t PID7; + FWK_R uint32_t PID0; + FWK_R uint32_t PID1; + FWK_R uint32_t PID2; + FWK_R uint32_t PID3; + FWK_R uint32_t ID0; + FWK_R uint32_t ID1; + FWK_R uint32_t ID2; + FWK_R uint32_t ID3; +}; + +#define PIK_CPU_V8_2_CAP_CLUSSYNC UINT32_C(0x00000001) +#define PIK_CPU_V8_2_CAP_CORESYNC(CORE) ((uint32_t)(1 << ((CORE) + 1))) +#define PIK_CPU_V8_2_CAP_PE_MASK UINT32_C(0xF0000000) +#define PIK_CPU_V8_2_CAP_PE_POS 28 + +#define PIK_CPU_V8_2_PIK_CONFIG_NO_OF_PPU UINT32_C(0x0000000F) + +#endif /* SGM775_PIK_CPU_H */ diff --git a/product/sgm775/include/sgm775_pik_debug.h b/product/sgm775/include/sgm775_pik_debug.h new file mode 100644 index 00000000..3a7f5fee --- /dev/null +++ b/product/sgm775/include/sgm775_pik_debug.h @@ -0,0 +1,49 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef SGM775_PIK_DEBUG_H +#define SGM775_PIK_DEBUG_H + +#include <stdint.h> +#include <fwk_macros.h> + +/*! + * \brief Debug register definitions + */ +struct pik_debug_reg { + FWK_RW uint32_t DEBUG_CONTROL; + FWK_R uint32_t DEBUG_STATUS; + uint32_t RESERVED0[2]; + FWK_R uint32_t APP_DAP_TARGET_ID; + FWK_R uint32_t SCP_DAP_TARGET_ID; + FWK_R uint32_t DAP_INSTANCE_ID; + uint8_t RESERVED1[0x810 - 0x1C]; + FWK_RW uint32_t TRACECLK_CTRL; + FWK_RW uint32_t TRACECLK_DIV1; + uint32_t RESERVED2[2]; + FWK_RW uint32_t PCLKDBG_CTRL; + uint32_t RESERVED3[3]; + FWK_RW uint32_t ATCLK_CTRL; + FWK_RW uint32_t ATCLK_DIV1; + FWK_R uint8_t RESERVED4[0xFC0 - 0x838]; + FWK_R uint32_t PIK_CONFIG; + uint32_t RESERVED5[3]; + FWK_R uint32_t PID4; + FWK_R uint32_t PID5; + FWK_R uint32_t PID6; + FWK_R uint32_t PID7; + FWK_R uint32_t PID0; + FWK_R uint32_t PID1; + FWK_R uint32_t PID2; + FWK_R uint32_t PID3; + FWK_R uint32_t ID0; + FWK_R uint32_t ID1; + FWK_R uint32_t ID2; + FWK_R uint32_t ID3; +}; + +#endif /* SGM775_PIK_DEBUG_H */ diff --git a/product/sgm775/include/sgm775_pik_dpu.h b/product/sgm775/include/sgm775_pik_dpu.h new file mode 100644 index 00000000..e9c5292f --- /dev/null +++ b/product/sgm775/include/sgm775_pik_dpu.h @@ -0,0 +1,51 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef SGM775_PIK_DPU_H +#define SGM775_PIK_DPU_H + +#include <stdint.h> +#include <fwk_macros.h> + +/*! + * \brief DPU PIK register definitions + */ +struct pik_dpu_reg { + FWK_R uint8_t RESERVED0[0x810]; + FWK_RW uint32_t M0CLK_CTRL; + FWK_RW uint32_t M0CLK_DIV1; + FWK_RW uint32_t M0CLK_DIV2; + uint32_t RESERVED1; + FWK_RW uint32_t M1CLK_CTRL; + FWK_RW uint32_t M1CLK_DIV1; + FWK_RW uint32_t M1CLK_DIV2; + uint32_t RESERVED2; + FWK_RW uint32_t ACLKDP_CTRL; + FWK_RW uint32_t ACLKDP_DIV1; + FWK_RW uint32_t ACLKDP_DIV2; + uint8_t RESERVED3[0xA00 - 0x83C]; + FWK_R uint32_t CLKFORCE_STATUS; + FWK_W uint32_t CLKFORCE_SET; + FWK_W uint32_t CLKFORCE_CLR; + uint8_t RESERVED4[0xFC0 - 0xA0C]; + FWK_RW uint32_t PWR_CTRL_CONFIG; + uint8_t RESERVED5[0xFD0 - 0xFC4]; + FWK_R uint32_t PID4; + FWK_R uint32_t PID5; + FWK_R uint32_t PID6; + FWK_R uint32_t PID7; + FWK_R uint32_t PID0; + FWK_R uint32_t PID1; + FWK_R uint32_t PID2; + FWK_R uint32_t PID3; + FWK_R uint32_t ID0; + FWK_R uint32_t ID1; + FWK_R uint32_t ID2; + FWK_R uint32_t ID3; +}; + +#endif /* SGM775_PIK_DPU_H */ diff --git a/product/sgm775/include/sgm775_pik_gpu.h b/product/sgm775/include/sgm775_pik_gpu.h new file mode 100644 index 00000000..c92a3e94 --- /dev/null +++ b/product/sgm775/include/sgm775_pik_gpu.h @@ -0,0 +1,45 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef SGM775_PIK_GPU_H +#define SGM775_PIK_GPU_H + +#include <stdint.h> +#include <fwk_macros.h> + +/*! + * \brief GPU PIK register definitions + */ +struct pik_gpu_reg { + uint8_t RESERVED0[0x810]; + FWK_RW uint32_t GPUCLK_CTRL; + FWK_RW uint32_t GPUCLK_DIV1; + FWK_RW uint32_t GPUCLK_DIV2; + FWK_R uint32_t RESERVED1; + FWK_RW uint32_t ACLKGPU_CTRL; + uint8_t RESERVED2[0xA00 - 0x824]; + FWK_R uint32_t CLKFORCE_STATUS; + FWK_W uint32_t CLKFORCE_SET; + FWK_W uint32_t CLKFORCE_CLR; + uint8_t RESERVED3[0xFC0 - 0xA0C]; + FWK_RW uint32_t PWR_CTRL_CONFIG; + uint8_t RESERVED4[0xFD0 - 0xFC4]; + FWK_R uint32_t PID4; + FWK_R uint32_t PID5; + FWK_R uint32_t PID6; + FWK_R uint32_t PID7; + FWK_R uint32_t PID0; + FWK_R uint32_t PID1; + FWK_R uint32_t PID2; + FWK_R uint32_t PID3; + FWK_R uint32_t ID0; + FWK_R uint32_t ID1; + FWK_R uint32_t ID2; + FWK_R uint32_t ID3; +}; + +#endif /* SGM775_PIK_GPU_H */ diff --git a/product/sgm775/include/sgm775_pik_scp.h b/product/sgm775/include/sgm775_pik_scp.h new file mode 100644 index 00000000..dce3a371 --- /dev/null +++ b/product/sgm775/include/sgm775_pik_scp.h @@ -0,0 +1,63 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef SGM775_PIK_SCP_H +#define SGM775_PIK_SCP_H + +#include <stdint.h> +#include <fwk_macros.h> + +/*! + * \brief SCP PIK register definitions + */ +struct pik_scp_reg { + uint32_t RESERVED0[4]; + FWK_RW uint32_t RESET_SYNDROME; + FWK_RW uint32_t WIC_CTRL; + FWK_R uint32_t WIC_STATUS; + uint8_t RESERVED1[0xA00 - 0x1C]; + FWK_R uint32_t CLKFORCE_STATUS; + FWK_RW uint32_t CLKFORCE_SET; + FWK_RW uint32_t CLKFORCE_CLR; + uint32_t RESERVED2; + FWK_R uint32_t PLL_STATUS0; + FWK_R uint32_t PLL_STATUS1; + uint8_t RESERVED3[0xFC0 - 0xA18]; + FWK_R uint32_t PIK_CONFIG; + uint32_t RESERVED4[3]; + FWK_R uint32_t PID4; + FWK_R uint32_t PID5; + FWK_R uint32_t PID6; + FWK_R uint32_t PID7; + FWK_R uint32_t PID0; + FWK_R uint32_t PID1; + FWK_R uint32_t PID2; + FWK_R uint32_t PID3; + FWK_R uint32_t ID0; + FWK_R uint32_t ID1; + FWK_R uint32_t ID2; + FWK_R uint32_t ID3; +}; + +#define PLL_STATUS0_REFCLK UINT32_C(0x00000001) +#define PLL_STATUS0_BCPUPLLLOCK UINT32_C(0x00000002) +#define PLL_STATUS0_LCPUPLLLOCK UINT32_C(0x00000004) +#define PLL_STATUS0_GPUPLLLOCK UINT32_C(0x00000008) +#define PLL_STATUS0_VIDEOPLLLOCK UINT32_C(0x00000010) +#define PLL_STATUS0_SYSPLLLOCK UINT32_C(0x00000020) +#define PLL_STATUS0_DISPLAYPLLLOCK UINT32_C(0x00000040) + +#define PLL_STATUS1_CPUPLLLOCK(CPU, PLL) \ + ((uint32_t)((1 << (PLL)) << ((CPU) * 8))) + +#define RESET_SYNDROME_PORESET UINT32_C(0x01) +#define RESET_SYNDROME_WDOGRESET_SCP UINT32_C(0x02) +#define RESET_SYNDROME_WDOGRESET_SYS UINT32_C(0x04) +#define RESET_SYNDROME_SYSRESETREQ UINT32_C(0x08) +#define RESET_SYNDROME_SCPM3LOCKUP UINT32_C(0x10) + +#endif /* SGM775_PIK_SCP_H */ diff --git a/product/sgm775/include/sgm775_pik_system.h b/product/sgm775/include/sgm775_pik_system.h new file mode 100644 index 00000000..3e3dccdb --- /dev/null +++ b/product/sgm775/include/sgm775_pik_system.h @@ -0,0 +1,69 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef SGM775_PIK_SYSTEM_H +#define SGM775_PIK_SYSTEM_H + +#include <stdint.h> +#include <fwk_macros.h> + +/*! + * \brief System PIK register definitions + */ +struct pik_system_reg { + uint8_t RESERVED0[0x800]; + FWK_RW uint32_t PPUCLK_CTRL; + FWK_RW uint32_t PPUCLK_DIV1; + uint32_t RESERVED1[2]; + FWK_RW uint32_t ACLKNCI_CTRL; + FWK_RW uint32_t ACLKNCI_DIV1; + uint32_t RESERVED2[2]; + FWK_RW uint32_t ACLKCCI_CTRL; + FWK_RW uint32_t ACLKCCI_DIV1; + uint32_t RESERVED3[6]; + FWK_RW uint32_t TCUCLK_CTRL; + FWK_RW uint32_t TCUCLK_DIV1; + uint32_t RESERVED4[2]; + FWK_RW uint32_t GICCLK_CTRL; + FWK_RW uint32_t GICCLK_DIV1; + uint32_t RESERVED5[2]; + FWK_RW uint32_t PCLKSCP_CTRL; + FWK_RW uint32_t PCLKSCP_DIV1; + uint32_t RESERVED6[2]; + FWK_RW uint32_t SYSPERCLK_CTRL; + FWK_RW uint32_t SYSPERCLK_DIV1; + uint32_t RESERVED7[6]; + FWK_RW uint32_t FCMCLK_CTRL; + FWK_RW uint32_t FCMCLK_DIV1; + uint8_t RESERVED8[0xA00 - 0x898]; + FWK_R uint32_t CLKFORCE_STATUS; + FWK_RW uint32_t CLKFORCE_SET; + FWK_RW uint32_t CLKFORCE_CLR; + uint8_t RESERVED9[0xFBC - 0xA0C]; + FWK_R uint32_t CAP; + FWK_R uint32_t PIK_CONFIG; + uint32_t RESERVED10[3]; + FWK_R uint32_t PID4; + FWK_R uint32_t PID5; + FWK_R uint32_t PID6; + FWK_R uint32_t PID7; + FWK_R uint32_t PID0; + FWK_R uint32_t PID1; + FWK_R uint32_t PID2; + FWK_R uint32_t PID3; + FWK_R uint32_t ID0; + FWK_R uint32_t ID1; + FWK_R uint32_t ID2; + FWK_R uint32_t ID3; +}; + +#define CAP_GICCLK_GATING_SUPPORT UINT32_C(0x00000001) +#define CAP_TCUCLK_SUPPORT UINT32_C(0x00000002) +#define CAP_ELA_SUPPORT UINT32_C(0x00000004) +#define CAP_FCMCLK_SUPPORT UINT32_C(0x00000008) + +#endif /* SGM775_PIK_SYSTEM_H */ diff --git a/product/sgm775/include/sgm775_pik_vpu.h b/product/sgm775/include/sgm775_pik_vpu.h new file mode 100644 index 00000000..a2742bb9 --- /dev/null +++ b/product/sgm775/include/sgm775_pik_vpu.h @@ -0,0 +1,43 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef SGM775_PIK_VPU_H +#define SGM775_PIK_VPU_H + +#include <stdint.h> +#include <fwk_macros.h> + +/*! + * \brief VPU PIK register definitions + */ +struct pik_vpu_reg { + uint8_t RESERVED0[0x810]; + FWK_RW uint32_t VIDEOCLK_CTRL; + FWK_RW uint32_t VIDEOCLK_DIV1; + FWK_RW uint32_t VIDEOCLK_DIV2; + uint8_t RESERVED1[0xA00 - 0x81C]; + FWK_R uint32_t CLKFORCE_STATUS; + FWK_W uint32_t CLKFORCE_SET; + FWK_W uint32_t CLKFORCE_CLR; + uint8_t RESERVED2[0xFC0 - 0xA0C]; + FWK_RW uint32_t PWR_CTRL_CONFIG; + uint8_t RESERVED3[0xFD0 - 0xFC4]; + FWK_R uint32_t PID4; + FWK_R uint32_t PID5; + FWK_R uint32_t PID6; + FWK_R uint32_t PID7; + FWK_R uint32_t PID0; + FWK_R uint32_t PID1; + FWK_R uint32_t PID2; + FWK_R uint32_t PID3; + FWK_R uint32_t ID0; + FWK_R uint32_t ID1; + FWK_R uint32_t ID2; + FWK_R uint32_t ID3; +}; + +#endif /* SGM775_PIK_VPU_H */ diff --git a/product/sgm775/include/sgm775_scmi.h b/product/sgm775/include/sgm775_scmi.h new file mode 100644 index 00000000..a920c09f --- /dev/null +++ b/product/sgm775/include/sgm775_scmi.h @@ -0,0 +1,29 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * Definitions for SCMI and SMT module configurations. + */ + +#ifndef SGM775_SCMI_H +#define SGM775_SCMI_H + +/* SCMI agent identifiers */ +enum sgm775_scmi_agent_id { + /* 0 is reserved for the platform */ + SCMI_AGENT_ID_OSPM = 1, + SCMI_AGENT_ID_PSCI, + SCMI_AGENT_ID_COUNT, +}; + +/* SCMI service indexes */ +enum sgm775_scmi_service_idx { + SGM775_SCMI_SERVICE_IDX_PSCI, + SGM775_SCMI_SERVICE_IDX_OSPM_0, + SGM775_SCMI_SERVICE_IDX_OSPM_1, + SGM775_SCMI_SERVICE_IDX_COUNT, +}; +#endif /* SGM775_SCMI_H */ diff --git a/product/sgm775/include/sgm775_sds.h b/product/sgm775/include/sgm775_sds.h new file mode 100644 index 00000000..943cfa7f --- /dev/null +++ b/product/sgm775/include/sgm775_sds.h @@ -0,0 +1,149 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef SGM775_SDS_H +#define SGM775_SDS_H + +#include <mod_sds.h> + +/* + * Structure identifiers. + */ +enum sgm775_sds_struct_id { + SGM775_SDS_CPU_INFO = 1 | (1 << MOD_SDS_ID_VERSION_MAJOR_POS), + SGM775_SDS_ROM_VERSION = 2 | (1 << MOD_SDS_ID_VERSION_MAJOR_POS), + SGM775_SDS_RAM_VERSION = 3 | (1 << MOD_SDS_ID_VERSION_MAJOR_POS), + SGM775_SDS_PLATFORM_ID = 4 | (1 << MOD_SDS_ID_VERSION_MAJOR_POS), + SGM775_SDS_RESET_SYNDROME = 5 | (1 << MOD_SDS_ID_VERSION_MAJOR_POS), + SGM775_SDS_FEATURE_AVAILABILITY = 6 | (1 << MOD_SDS_ID_VERSION_MAJOR_POS), + SGM775_SDS_CPU_BOOTCTR = 7 | (1 << MOD_SDS_ID_VERSION_MAJOR_POS), + SGM775_SDS_CPU_FLAGS = 8 | (1 << MOD_SDS_ID_VERSION_MAJOR_POS), + SGM775_SDS_BOOTLOADER = 9 | (1 << MOD_SDS_ID_VERSION_MAJOR_POS), +}; + +/* + * Structure sizes. + */ +#define SGM775_SDS_CPU_INFO_SIZE 4 +#define SGM775_SDS_ROM_VERSION_SIZE 4 +#define SGM775_SDS_RAM_VERSION_SIZE 4 +#define SGM775_SDS_PLATFORM_ID_SIZE 8 +#define SGM775_SDS_RESET_SYNDROME_SIZE 4 +#define SGM775_SDS_FEATURE_AVAILABILITY_SIZE 4 +#define SGM775_SDS_CPU_BOOTCTR_SIZE 8 +#define SGM775_SDS_CPU_FLAGS_SIZE 8 +#define SGM775_SDS_BOOTLOADER_SIZE 12 + +/* + * Field masks and offsets for the SGM775_SDS_AP_CPU_INFO structure. + */ +#define SGM775_SDS_CPU_INFO_PRIMARY_MASK 0xFFFFFFFF +#define SGM775_SDS_CPU_INFO_PRIMARY_POS 0 + +/* + * Structure, field masks and offsets for the SGM775_SDS_PLATFORM_ID structure. + */ + +struct sgm775_sds_platid { + uint32_t platform_identifier; + uint32_t platform_type_identifier; +}; + +#define SGM775_SDS_PLATID_PARTNO_MASK 0xFFF +#define SGM775_SDS_PLATID_DESIGNER_MASK 0xFF000 +#define SGM775_SDS_PLATID_REV_MINOR_MASK 0xF00000 +#define SGM775_SDS_PLATID_REV_MAJOR_MASK 0xF000000 +#define SGM775_SDS_PLATID_CONFIG_MASK 0xF0000000 +#define SGM775_SDS_PLATID_TYPE_MASK 0xF + +#define SGM775_SDS_PLATID_PARTNO_POS 0 +#define SGM775_SDS_PLATID_DESIGNER_POS 12 +#define SGM775_SDS_PLATID_REV_MINOR_POS 20 +#define SGM775_SDS_PLATID_REV_MAJOR_POS 24 +#define SGM775_SDS_PLATID_CONFIG_POS 28 + +#define SGM775_SDS_PLATID_TYPE_POS 0 +/* + * Field masks and offsets for the SGM775_SDS_RESET_SYNDROME structure. + */ +#define SGM775_SDS_RESET_SYNDROME_POR_MASK 0x1 +#define SGM775_SDS_RESET_SYNDROME_WDOGSCP_MASK 0x2 +#define SGM775_SDS_RESET_SYNDROME_WDOGAP_MASK 0x4 +#define SGM775_SDS_RESET_SYNDROME_SYSRESET_MASK 0x8 +#define SGM775_SDS_RESET_SYNDROME_M3LOCKUP_MASK 0x10 + +#define SGM775_SDS_RESET_SYNDROME_POR_POS 0 +#define SGM775_SDS_RESET_SYNDROME_WDOGSCP_POS 1 +#define SGM775_SDS_RESET_SYNDROME_WDOGAP_POS 2 +#define SGM775_SDS_RESET_SYNDROME_SYSRESET_POS 3 +#define SGM775_SDS_RESET_SYNDROME_M3LOCKUP_POS 4 + +/* + * Field masks and offsets for the SGM775_SDS_FEATURE_AVAILABILITY structure. + */ +#define SGM775_SDS_FEATURE_FIRMWARE_MASK 0x1 +#define SGM775_SDS_FEATURE_DMC_MASK 0x2 +#define SGM775_SDS_FEATURE_MESSAGING_MASK 0x4 + +#define SGM775_SDS_FEATURE_FIRMWARE_POS 0 +#define SGM775_SDS_FEATURE_DMC_POS 1 +#define SGM775_SDS_FEATURE_MESSAGING_POS 2 + +/* + * Field masks and offsets for the SGM775_SDS_CPU_BOOTCTR structure. + */ +#define SGM775_SDS_CPU_BOOTCTR_CPU0_MASK 0xFF +#define SGM775_SDS_CPU_BOOTCTR_CPU1_MASK 0xFF00 +#define SGM775_SDS_CPU_BOOTCTR_CPU2_MASK 0xFF0000 +#define SGM775_SDS_CPU_BOOTCTR_CPU3_MASK 0xFF000000 +#define SGM775_SDS_CPU_BOOTCTR_CPU4_MASK 0xFF +#define SGM775_SDS_CPU_BOOTCTR_CPU5_MASK 0xFF00 +#define SGM775_SDS_CPU_BOOTCTR_CPU6_MASK 0xFF0000 +#define SGM775_SDS_CPU_BOOTCTR_CPU7_MASK 0xFF000000 + + +#define SGM775_SDS_CPU_BOOTCTR_CPU0_POS 0 +#define SGM775_SDS_CPU_BOOTCTR_CPU1_POS 8 +#define SGM775_SDS_CPU_BOOTCTR_CPU2_POS 16 +#define SGM775_SDS_CPU_BOOTCTR_CPU3_POS 24 +#define SGM775_SDS_CPU_BOOTCTR_CPU4_POS 0 +#define SGM775_SDS_CPU_BOOTCTR_CPU5_POS 8 +#define SGM775_SDS_CPU_BOOTCTR_CPU6_POS 16 +#define SGM775_SDS_CPU_BOOTCTR_CPU7_POS 24 + +/* + * Field masks and offsets for the SGM775_SDS_CPU_FLAGS structure. + */ +#define SGM775_SDS_CPU_FLAGS_CPU0_WFI_MASK 0x1 +#define SGM775_SDS_CPU_FLAGS_CPU1_WFI_MASK 0x100 +#define SGM775_SDS_CPU_FLAGS_CPU2_WFI_MASK 0x10000 +#define SGM775_SDS_CPU_FLAGS_CPU3_WFI_MASK 0x1000000 +#define SGM775_SDS_CPU_FLAGS_CPU4_WFI_MASK 0x1 +#define SGM775_SDS_CPU_FLAGS_CPU5_WFI_MASK 0x100 +#define SGM775_SDS_CPU_FLAGS_CPU6_WFI_MASK 0x10000 +#define SGM775_SDS_CPU_FLAGS_CPU7_WFI_MASK 0x1000000 + +#define SGM775_SDS_CPU_FLAGS_CPU0_WFI_POS 0 +#define SGM775_SDS_CPU_FLAGS_CPU1_WFI_POS 8 +#define SGM775_SDS_CPU_FLAGS_CPU2_WFI_POS 16 +#define SGM775_SDS_CPU_FLAGS_CPU3_WFI_POS 24 +#define SGM775_SDS_CPU_FLAGS_CPU4_WFI_POS 0 +#define SGM775_SDS_CPU_FLAGS_CPU5_WFI_POS 8 +#define SGM775_SDS_CPU_FLAGS_CPU6_WFI_POS 16 +#define SGM775_SDS_CPU_FLAGS_CPU7_WFI_POS 24 +/* + * Field masks and offsets for the SGM775_SDS_BOOTLOADER structure. + */ +#define SGM775_SDS_BOOTLOADER_VALID_MASK 0x1 +#define SGM775_SDS_BOOTLOADER_OFFSET_MASK 0xFFFFFFFF +#define SGM775_SDS_BOOTLOADER_SIZE_MASK 0xFFFFFFFF + +#define SGM775_SDS_BOOTLOADER_VALID_POS 0 +#define SGM775_SDS_BOOTLOADER_OFFSET_POS 0 +#define SGM775_SDS_BOOTLOADER_SIZE_POS 0 + +#endif /* SGM775_SDS_H */ diff --git a/product/sgm775/include/sgm775_ssc.h b/product/sgm775/include/sgm775_ssc.h new file mode 100644 index 00000000..9b514a11 --- /dev/null +++ b/product/sgm775/include/sgm775_ssc.h @@ -0,0 +1,42 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef SGM775_SSC_H +#define SGM775_SSC_H + +#include <stdint.h> +#include <fwk_macros.h> + +/*! + * \brief System Security Control (SSC) register definitions + */ +struct ssc_reg { + FWK_R uint32_t RESERVED1[4]; + FWK_R uint32_t SSC_DBGCFG_STAT; + FWK_W uint32_t SSC_DBGCFG_SET; + FWK_W uint32_t SSC_DBGCFG_CLR; + FWK_R uint32_t RESERVED2[2]; + FWK_RW uint32_t SSC_SWDHOD; + FWK_RW uint32_t SSC_AUXDBGCFG; + FWK_R uint32_t RESERVED3; + FWK_RW uint32_t SSC_GPRETN; + FWK_R uint32_t RESERVED4[3]; + FWK_R uint32_t SSC_VERSION; + FWK_R uint32_t RESERVED5[995]; + FWK_R uint32_t PID4; + FWK_R uint32_t RESERVED6[3]; + FWK_R uint32_t PID0; + FWK_R uint32_t PID1; + FWK_R uint32_t PID2; + FWK_R uint32_t PID3; + FWK_R uint32_t COMPID0; + FWK_R uint32_t COMPID1; + FWK_R uint32_t COMPID2; + FWK_R uint32_t COMPID3; +}; + +#endif /* SGM775_SSC_H */ diff --git a/product/sgm775/include/software_mmap.h b/product/sgm775/include/software_mmap.h new file mode 100644 index 00000000..e0ce772f --- /dev/null +++ b/product/sgm775/include/software_mmap.h @@ -0,0 +1,128 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * Software defined memory map shared between SCP and AP cores. + */ + +#ifndef SOFTWARE_MMAP_H +#define SOFTWARE_MMAP_H + +#include <fwk_macros.h> +#include <system_mmap.h> + +/* + * The 4KiB AP/SCP Shared memory at the base of Trusted SRAM is used for several + * purposes. These are: the Shared Data Storage (SDS) Memory Region, the SCMI + * secure payload areas, and the context area for Application Processor + * firmware. + * + * Shared Data Storage (SDS) Memory Region: Used for structured storage of data + * that is shared between SCP Firmware and Application Processor firmware. The + * SDS Memory Region occupies the area between the context region base and + * the SCMI Secure Payload base. + * + * SCMI Secure Payload Areas: Storage for SCMI message contents in both the + * Agent->Platform and Platform->Agent directions. + * + * Application Processor Context Area: The usage of this area is defined by the + * firmware running on the Application Processors. The SCP Firmware must zero + * this memory before releasing any Application Processors. This area must + * always be located in the top 64 bytes of the 4KiB reserved region. + * + * +-----------------------+ 4096 + * | | + * 64B | AP Context Area | + * | | + * +-----------------------+ + * | | + * 256B | Unused | + * | | + * +-----------------------+ + * | | + * | SCMI Sec. Payload | + * 128B | Platform to Agent | + * | | + * +-----------------------+ + * | | + * 128B | SCMI Sec. Payload | + * | Agent to Platform | + * | | + * +-----------------------+ + * | | + * 3520B | SDS Memory Region | + * | | + * +-----------------------+ 0 + */ + +/* Secure shared memory at the base of Trusted SRAM */ +#define SHARED_SECURE_BASE (TRUSTED_RAM_BASE) +#define SHARED_SECURE_SIZE (4 * FWK_KIB) + +/* SDS Memory Region */ +#define SDS_MEM_BASE (SHARED_SECURE_BASE) +#define SDS_MEM_SIZE (3520) + +/* AP Context Area */ +#define AP_CONTEXT_BASE (SHARED_SECURE_BASE + SHARED_SECURE_SIZE - \ + AP_CONTEXT_SIZE) +#define AP_CONTEXT_SIZE (64) + +/* SCMI Secure Payload Areas */ +#define SCMI_PAYLOAD_SIZE (128) +#define SCMI_PAYLOAD_S_A2P_BASE (SDS_MEM_BASE + SDS_MEM_SIZE) +#define SCMI_PAYLOAD_S_P2A_BASE (SCMI_PAYLOAD_S_A2P_BASE + SCMI_PAYLOAD_SIZE) + +/* + * The 4KiB AP/SCP Shared memory at the base of Non-trusted SRAM is used for the + * SCMI non-secure payload areas. + * + * Two SCMI non-Secure Payload Areas: Storage for SCMI message contents in both + * the Agent->Platform and Platform->Agent directions. + * + * +-----------------------+ 4096 + * 3584B | Unused | + * +-----------------------+ + * | | + * | Non-Sec. Channel 1 | + * | SCMI non-Sec. Payload | + * 128B | Platform to Agent | + * | | + * +-----------------------+ + * | | + * | Non-Sec. Channel 1 | + * 128B | SCMI non-Sec. Payload | + * | Agent to Platform | + * | | + * +-----------------------+ + * | | + * | Non-Sec. Channel 0 | + * | SCMI non-Sec. Payload | + * 128B | Platform to Agent | + * | | + * +-----------------------+ + * | | + * | Non-Sec. Channel 0 | + * 128B | SCMI non-Sec. Payload | + * | Agent to Platform | + * | | + * +-----------------------+ 0 + */ + +/* Non-secure shared memory at the base of Non-trusted SRAM */ +#define SHARED_NONSECURE_BASE (NONTRUSTED_RAM_BASE) +#define SHARED_NONSECURE_SIZE (4 * FWK_KIB) + +/* SCMI Non-Secure Payload Areas */ +#define SCMI_PAYLOAD0_NS_A2P_BASE (SHARED_NONSECURE_BASE) +#define SCMI_PAYLOAD0_NS_P2A_BASE (SCMI_PAYLOAD0_NS_A2P_BASE + \ + SCMI_PAYLOAD_SIZE) +#define SCMI_PAYLOAD1_NS_A2P_BASE (SCMI_PAYLOAD0_NS_P2A_BASE + \ + SCMI_PAYLOAD_SIZE) +#define SCMI_PAYLOAD1_NS_P2A_BASE (SCMI_PAYLOAD1_NS_A2P_BASE + \ + SCMI_PAYLOAD_SIZE) + +#endif /* SOFTWARE_MMAP_H */ diff --git a/product/sgm775/include/system_clock.h b/product/sgm775/include/system_clock.h new file mode 100644 index 00000000..c3f5a76c --- /dev/null +++ b/product/sgm775/include/system_clock.h @@ -0,0 +1,28 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef SYSTEM_CLOCK_H +#define SYSTEM_CLOCK_H + +#include <fwk_macros.h> + +/*! + * \brief Calculates the necessary divider for obtaining a target frequency + * from a given clock. + * + * \param CLOCK_RATE The tick rate of the clock to be divided. + * + * \param TARGET_FREQ The target frequency to be obtained by the division. + * + * \return The divider needed to obtain TARGET_FREQ from CLOCK_RATE. + */ +#define DIV_FROM_CLOCK(CLOCK_RATE, TARGET_FREQ) ((CLOCK_RATE) / (TARGET_FREQ)) + +#define CLOCK_RATE_REFCLK (50UL * FWK_MHZ) +#define CLOCK_RATE_SYSPLLCLK (2000UL * FWK_MHZ) + +#endif /* SYSTEM_CLOCK_H */ diff --git a/product/sgm775/include/system_mmap.h b/product/sgm775/include/system_mmap.h new file mode 100644 index 00000000..697f7811 --- /dev/null +++ b/product/sgm775/include/system_mmap.h @@ -0,0 +1,61 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef SYSTEM_MMAP_H +#define SYSTEM_MMAP_H + +#include <sgm775_mmap.h> + +#define DMC_EXTERNAL0 (SYS0_BASE + 0x3FBE0000) +#define DMC_EXTERNAL1 (SYS0_BASE + 0x3FBF0000) +#define DMC_EXTERNAL2 (SYS0_BASE + 0x3FC00000) +#define DMC_EXTERNAL3 (SYS0_BASE + 0x3FC10000) + +#define BOARD_UART1_BASE (SYS0_BASE + 0x3FF70000) +#define PLAT_BASE (SYS0_BASE + 0x3FFE0000) + +#define PLL_GPU (PLAT_BASE + 0x00000008) +#define PLL_SYSTEM (PLAT_BASE + 0x0000000C) +#define PLL_VIDEO (PLAT_BASE + 0x00000010) +#define PLL_DISPLAY (PLAT_BASE + 0x00000014) + +#define PIX0_CONTROL (PLAT_BASE + 0x00000018) +#define PIX1_CONTROL (PLAT_BASE + 0x0000001C) + +#define SWCLKTCK_CONTROL (PLAT_BASE + 0x00000020) +#define SENSOR_SOC_TEMP (PLAT_BASE + 0x00000080) +#define PLATFORM_ID (PLAT_BASE + 0x000000E0) + +#define PLL_CLUS0_0 (PLAT_BASE + 0x00000100) +#define PLL_CLUS0_1 (PLAT_BASE + 0x00000104) +#define PLL_CLUS0_2 (PLAT_BASE + 0x00000108) +#define PLL_CLUS0_3 (PLAT_BASE + 0x0000010C) +#define PLL_CLUS0_4 (PLAT_BASE + 0x00000110) +#define PLL_CLUS0_5 (PLAT_BASE + 0x00000114) +#define PLL_CLUS0_6 (PLAT_BASE + 0x00000118) +#define PLL_CLUS0_7 (PLAT_BASE + 0x0000011C) + +#define DDR_PHY0 (SYS0_BASE + 0x3FB60000) +#define DDR_PHY1 (SYS0_BASE + 0x3FB70000) +#define DDR_PHY2 (SYS0_BASE + 0x3FB80000) +#define DDR_PHY3 (SYS0_BASE + 0x3FB90000) + +#define GPV_CCI_GPU1 (SYS1_BASE + 0x2A004000) +#define GPV_CCI_GPU0 (SYS1_BASE + 0x2A005000) +#define GPV_CCI_LITTLE (SYS1_BASE + 0x2A006000) +#define GPV_CCI_BIG (SYS1_BASE + 0x2A007000) +#define GPV_VPU (SYS1_BASE + 0x2A243000) +#define GPV_DPU0 (SYS1_BASE + 0x2A244000) +#define GPV_DPU1 (SYS1_BASE + 0x2A245000) + +#define DMC_INTERNAL0 (SYS1_BASE + 0x2A500000) +#define DMC_INTERNAL1 (SYS1_BASE + 0x2A540000) +#define DMC_INTERNAL2 (SYS1_BASE + 0x2A580000) +#define DMC_INTERNAL3 (SYS1_BASE + 0x2A5C0000) + + +#endif /* SYSTEM_MMAP_H */ diff --git a/product/sgm775/include/system_mmap_scp.h b/product/sgm775/include/system_mmap_scp.h new file mode 100644 index 00000000..097e4316 --- /dev/null +++ b/product/sgm775/include/system_mmap_scp.h @@ -0,0 +1,21 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * SCP ROM and RAM memory sizes. These definitions are kept isolated without + * the UINT32_C() or UL decorators allowing them to be used in the linker + * script. + */ + +#ifndef SYSTEM_MMAP_SCP_H +#define SYSTEM_MMAP_SCP_H + +#include <sgm775_mmap_scp.h> + +#define SCP_ROM_SIZE (64 * 1024) +#define SCP_RAM_SIZE (128 * 1024) + +#endif /* SYSTEM_MMAP_SCP_H */ diff --git a/product/sgm775/module/sgm775_system/include/mod_sgm775_system.h b/product/sgm775/module/sgm775_system/include/mod_sgm775_system.h new file mode 100644 index 00000000..c3cfb498 --- /dev/null +++ b/product/sgm775/module/sgm775_system/include/mod_sgm775_system.h @@ -0,0 +1,44 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * SGM775 System Support + */ + +#ifndef MOD_SGM775_SYSTEM_H +#define MOD_SGM775_SYSTEM_H + +/*! + * \addtogroup GroupSGM775Module SGM775 Product Modules + * @{ + */ + +/*! + * \defgroup GroupSGM775System SGM775 System Support + * + * @{ + */ + +/*! + * \brief API indices. + */ +enum mod_sgm775_system_api_idx { + /*! API index for the driver interface of the SYSTEM POWER module */ + MOD_SGM775_SYSTEM_API_IDX_SYSTEM_POWER_DRIVER, + + /*! Number of defined APIs */ + MOD_SGM775_SYSTEM_API_COUNT +}; + +/*! + * @} + */ + +/*! + * @} + */ + +#endif /* MOD_SGM775_SYSTEM_H */ diff --git a/product/sgm775/module/sgm775_system/src/Makefile b/product/sgm775/module/sgm775_system/src/Makefile new file mode 100644 index 00000000..77ad18c1 --- /dev/null +++ b/product/sgm775/module/sgm775_system/src/Makefile @@ -0,0 +1,11 @@ +# +# Arm SCP/MCP Software +# Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +BS_LIB_NAME := SGM775 SYSTEM +BS_LIB_SOURCES = mod_sgm775_system.c + +include $(BS_DIR)/lib.mk diff --git a/product/sgm775/module/sgm775_system/src/mod_sgm775_system.c b/product/sgm775/module/sgm775_system/src/mod_sgm775_system.c new file mode 100644 index 00000000..dc1459e4 --- /dev/null +++ b/product/sgm775/module/sgm775_system/src/mod_sgm775_system.c @@ -0,0 +1,59 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * SGM775 System Support. + */ + +#include <fmw_cmsis.h> +#include <fwk_module.h> +#include <mod_system_power.h> +#include <mod_sgm775_system.h> + +/* + * Functions fulfilling the framework's module interface + */ + +static int sgm775_system_shutdown(enum mod_pd_system_shutdown system_shutdown) +{ + NVIC_SystemReset(); + + return FWK_E_DEVICE; +} + +static const struct mod_system_power_driver_api + sgm775_system_system_power_driver_api = { + .system_shutdown = sgm775_system_shutdown +}; + +/* + * Functions fulfilling the framework's module interface + */ + +static int sgm775_system_init(fwk_id_t module_id, unsigned int unused, + const void *unused2) +{ + return FWK_SUCCESS; +} + +static int sgm775_system_process_bind_request(fwk_id_t source_id, + fwk_id_t target_id, fwk_id_t api_id, const void **api) +{ + *api = &sgm775_system_system_power_driver_api; + + return FWK_SUCCESS; +} + +const struct fwk_module module_sgm775_system = { + .name = "SGM775_SYSTEM", + .api_count = MOD_SGM775_SYSTEM_API_COUNT, + .type = FWK_MODULE_TYPE_DRIVER, + .init = sgm775_system_init, + .process_bind_request = sgm775_system_process_bind_request, +}; + +/* No elements, no module configuration data */ +struct fwk_module_config config_sgm775_system = {}; diff --git a/product/sgm775/product.mk b/product/sgm775/product.mk new file mode 100644 index 00000000..0572bdc3 --- /dev/null +++ b/product/sgm775/product.mk @@ -0,0 +1,10 @@ +# +# Arm SCP/MCP Software +# Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +BS_PRODUCT_NAME := sgm775 +BS_FIRMWARE_LIST := scp_romfw \ + scp_ramfw diff --git a/product/sgm775/scp_ramfw/RTX_Config.h b/product/sgm775/scp_ramfw/RTX_Config.h new file mode 100644 index 00000000..25621f84 --- /dev/null +++ b/product/sgm775/scp_ramfw/RTX_Config.h @@ -0,0 +1,56 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * RTX2 v5 configuration file. + * The file must be called RTX_Config.h as it is included by the an RTX + * file in order to create a object file containing the configuration. + */ + +#ifndef RTX_CONFIG_H_ +#define RTX_CONFIG_H_ + +/* System */ +#define OS_DYNAMIC_MEM_SIZE 0 +#define OS_TICK_FREQ 1000 /* Hz */ +#define OS_ROBIN_ENABLE 0 +#define OS_ROBIN_TIMEOUT 0 +#define OS_ISR_FIFO_QUEUE 16 + +/* Thread */ +#define OS_THREAD_OBJ_MEM 0 +#define OS_THREAD_NUM 1 +#define OS_THREAD_DEF_STACK_NUM 0 +#define OS_THREAD_USER_STACK_SIZE 0 +#define OS_STACK_SIZE 200 +#define OS_IDLE_THREAD_STACK_SIZE 200 +#define OS_STACK_CHECK 1 +#define OS_STACK_WATERMARK 0 +#define OS_PRIVILEGE_MODE 1 + +/* Timer */ +#define OS_TIMER_OBJ_MEM 0 +#define OS_TIMER_NUM 1 +#define OS_TIMER_THREAD_PRIO 40 +#define OS_TIMER_THREAD_STACK_SIZE 200 +#define OS_TIMER_CB_QUEUE 4 + +/* Event flags */ +#define OS_EVFLAGS_OBJ_MEM 0 +#define OS_EVFLAGS_NUM 1 + +#define OS_MUTEX_OBJ_MEM 0 +#define OS_MUTEX_NUM 1 +#define OS_SEMAPHORE_OBJ_MEM 0 +#define OS_SEMAPHORE_NUM 1 +#define OS_MEMPOOL_OBJ_MEM 0 +#define OS_MEMPOOL_NUM 1 +#define OS_MEMPOOL_DATA_SIZE 0 +#define OS_MSGQUEUE_OBJ_MEM 0 +#define OS_MSGQUEUE_NUM 1 +#define OS_MSGQUEUE_DATA_SIZE 0 + +#endif /* RTX_CONFIG_H_ */ diff --git a/product/sgm775/scp_ramfw/clock_devices.h b/product/sgm775/scp_ramfw/clock_devices.h new file mode 100644 index 00000000..c1eed2ef --- /dev/null +++ b/product/sgm775/scp_ramfw/clock_devices.h @@ -0,0 +1,26 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef CLOCK_DEVICES_H +#define CLOCK_DEVICES_H + +/*! + * \brief Clock device indexes. + */ +enum clock_dev_idx { + CLOCK_DEV_IDX_BIG, + CLOCK_DEV_IDX_LITTLE, + CLOCK_DEV_IDX_GPU, + CLOCK_DEV_IDX_VPU, + CLOCK_DEV_IDX_DPU, + CLOCK_DEV_IDX_PIXEL_0, + CLOCK_DEV_IDX_PIXEL_1, + CLOCK_DEV_IDX_FCMCLK, + CLOCK_DEV_IDX_COUNT +}; + +#endif /* CLOCK_DEVICES_H */ diff --git a/product/sgm775/scp_ramfw/config_clock.c b/product/sgm775/scp_ramfw/config_clock.c new file mode 100644 index 00000000..3728fb79 --- /dev/null +++ b/product/sgm775/scp_ramfw/config_clock.c @@ -0,0 +1,119 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <stddef.h> +#include <fwk_element.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <mod_clock.h> +#include <mod_css_clock.h> +#include <mod_system_pll.h> +#include <mod_pik_clock.h> +#include <mod_power_domain.h> +#include <config_power_domain.h> +#include <clock_devices.h> +#include <sgm775_core.h> + +static struct fwk_element clock_dev_desc_table[] = { + [CLOCK_DEV_IDX_BIG] = { + .name = "CPU_GROUP_BIG", + .data = &((struct mod_clock_dev_config) { + .driver_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_CSS_CLOCK, 0), + .api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_CSS_CLOCK, + MOD_CSS_CLOCK_API_TYPE_CLOCK), + }), + }, + [CLOCK_DEV_IDX_LITTLE] = { + .name = "CPU_GROUP_LITTLE", + .data = &((struct mod_clock_dev_config) { + .driver_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_CSS_CLOCK, 1), + .api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_CSS_CLOCK, + MOD_CSS_CLOCK_API_TYPE_CLOCK), + }), + }, + [CLOCK_DEV_IDX_GPU] = { + .name = "GPU", + .data = &((struct mod_clock_dev_config) { + .driver_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_CSS_CLOCK, 2), + .api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_CSS_CLOCK, + MOD_CSS_CLOCK_API_TYPE_CLOCK), + }), + }, + [CLOCK_DEV_IDX_VPU] = { + .name = "VPU", + .data = &((struct mod_clock_dev_config) { + .driver_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_CSS_CLOCK, 3), + .api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_CSS_CLOCK, + MOD_CSS_CLOCK_API_TYPE_CLOCK), + }), + }, + [CLOCK_DEV_IDX_DPU] = { + .name = "DPU", + .data = &((struct mod_clock_dev_config) { + .driver_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_CSS_CLOCK, 4), + .api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_CSS_CLOCK, + MOD_CSS_CLOCK_API_TYPE_CLOCK), + }), + }, + [CLOCK_DEV_IDX_PIXEL_0] = { + .name = "PIXEL_0", + .data = &((struct mod_clock_dev_config) { + .driver_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_SYSTEM_PLL, 5), + .api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_SYSTEM_PLL, + MOD_SYSTEM_PLL_API_TYPE_DEFAULT), + }), + }, + [CLOCK_DEV_IDX_PIXEL_1] = { + .name = "PIXEL_1", + .data = &((struct mod_clock_dev_config) { + .driver_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_SYSTEM_PLL, 6), + .api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_SYSTEM_PLL, + MOD_SYSTEM_PLL_API_TYPE_DEFAULT), + }), + }, + [CLOCK_DEV_IDX_FCMCLK] = { + .name = "FCMCLK", + .data = &((struct mod_clock_dev_config) { + .driver_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_PIK_CLOCK, 12), + .api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_PIK_CLOCK, + MOD_PIK_CLOCK_API_TYPE_CLOCK), + }), + }, + [CLOCK_DEV_IDX_COUNT] = { }, /* Termination description. */ +}; + +static const struct fwk_element *clock_get_dev_desc_table(fwk_id_t module_id) +{ + unsigned int i; + unsigned int core_count; + struct mod_clock_dev_config *dev_config; + + core_count = sgm775_core_get_count(); + + /* Configure all clocks to respond to changes in SYSTOP power state */ + for (i = 0; i < CLOCK_DEV_IDX_COUNT; i++) { + dev_config = + (struct mod_clock_dev_config *)clock_dev_desc_table[i].data; + dev_config->pd_source_id = FWK_ID_ELEMENT( + FWK_MODULE_IDX_POWER_DOMAIN, + CONFIG_POWER_DOMAIN_SYSTOP_CHILD_COUNT + core_count); + } + + return clock_dev_desc_table; +} + +struct fwk_module_config config_clock = { + .get_element_table = clock_get_dev_desc_table, + .data = &((struct mod_clock_config) { + .pd_transition_notification_id = FWK_ID_NOTIFICATION_INIT( + FWK_MODULE_IDX_POWER_DOMAIN, + MOD_PD_NOTIFICATION_IDX_POWER_STATE_TRANSITION), + .pd_pre_transition_notification_id = FWK_ID_NOTIFICATION_INIT( + FWK_MODULE_IDX_POWER_DOMAIN, + MOD_PD_NOTIFICATION_IDX_POWER_STATE_PRE_TRANSITION), + }), +}; diff --git a/product/sgm775/scp_ramfw/config_css_clock.c b/product/sgm775/scp_ramfw/config_css_clock.c new file mode 100644 index 00000000..aa83f2c3 --- /dev/null +++ b/product/sgm775/scp_ramfw/config_css_clock.c @@ -0,0 +1,302 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <fwk_element.h> +#include <fwk_id.h> +#include <fwk_macros.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <mod_css_clock.h> +#include <mod_system_pll.h> +#include <mod_pik_clock.h> +#include <sgm775_pik.h> + +static const struct mod_css_clock_rate rate_table_cpu_group_big[] = { + { + /* Super Underdrive */ + .rate = 1313 * FWK_MHZ, + .pll_rate = 1313 * FWK_MHZ, + .clock_source = MOD_PIK_CLOCK_CLUSCLK_SOURCE_PLL1, + .clock_div_type = MOD_PIK_CLOCK_MSCLOCK_DIVIDER_DIV_EXT, + .clock_div = 1, + .clock_mod_numerator = 1, + .clock_mod_denominator = 1, + }, + { + /* Underdrive */ + .rate = 1531 * FWK_MHZ, + .pll_rate = 1531 * FWK_MHZ, + .clock_source = MOD_PIK_CLOCK_CLUSCLK_SOURCE_PLL1, + .clock_div_type = MOD_PIK_CLOCK_MSCLOCK_DIVIDER_DIV_EXT, + .clock_div = 1, + .clock_mod_numerator = 1, + .clock_mod_denominator = 1, + }, + { + /* Nominal */ + .rate = 1750 * FWK_MHZ, + .pll_rate = 1750 * FWK_MHZ, + .clock_source = MOD_PIK_CLOCK_CLUSCLK_SOURCE_PLL1, + .clock_div_type = MOD_PIK_CLOCK_MSCLOCK_DIVIDER_DIV_EXT, + .clock_div = 1, + .clock_mod_numerator = 1, + .clock_mod_denominator = 1, + }, + { + /* Overdrive */ + .rate = 2100 * FWK_MHZ, + .pll_rate = 2100 * FWK_MHZ, + .clock_source = MOD_PIK_CLOCK_CLUSCLK_SOURCE_PLL1, + .clock_div_type = MOD_PIK_CLOCK_MSCLOCK_DIVIDER_DIV_EXT, + .clock_div = 1, + .clock_mod_numerator = 1, + .clock_mod_denominator = 1, + }, + { + /* Super Overdrive */ + .rate = 2450 * FWK_MHZ, + .pll_rate = 2450 * FWK_MHZ, + .clock_source = MOD_PIK_CLOCK_CLUSCLK_SOURCE_PLL1, + .clock_div_type = MOD_PIK_CLOCK_MSCLOCK_DIVIDER_DIV_EXT, + .clock_div = 1, + .clock_mod_numerator = 1, + .clock_mod_denominator = 1, + }, +}; + +static const struct mod_css_clock_rate rate_table_cpu_group_little[] = { + { + /* Super Underdrive */ + .rate = 665 * FWK_MHZ, + .pll_rate = 665 * FWK_MHZ, + .clock_source = MOD_PIK_CLOCK_CLUSCLK_SOURCE_PLL0, + .clock_div_type = MOD_PIK_CLOCK_MSCLOCK_DIVIDER_DIV_EXT, + .clock_div = 1, + .clock_mod_numerator = 1, + .clock_mod_denominator = 1, + }, + { + /* Underdrive */ + .rate = 998 * FWK_MHZ, + .pll_rate = 998 * FWK_MHZ, + .clock_source = MOD_PIK_CLOCK_CLUSCLK_SOURCE_PLL0, + .clock_div_type = MOD_PIK_CLOCK_MSCLOCK_DIVIDER_DIV_EXT, + .clock_div = 1, + .clock_mod_numerator = 1, + .clock_mod_denominator = 1, + }, + { + /* Nominal */ + .rate = 1330 * FWK_MHZ, + .pll_rate = 1330 * FWK_MHZ, + .clock_source = MOD_PIK_CLOCK_CLUSCLK_SOURCE_PLL0, + .clock_div_type = MOD_PIK_CLOCK_MSCLOCK_DIVIDER_DIV_EXT, + .clock_div = 1, + .clock_mod_numerator = 1, + .clock_mod_denominator = 1, + }, + { + /* Overdrive */ + .rate = 1463 * FWK_MHZ, + .pll_rate = 1463 * FWK_MHZ, + .clock_source = MOD_PIK_CLOCK_CLUSCLK_SOURCE_PLL0, + .clock_div_type = MOD_PIK_CLOCK_MSCLOCK_DIVIDER_DIV_EXT, + .clock_div = 1, + .clock_mod_numerator = 1, + .clock_mod_denominator = 1, + }, + { + /* Super Overdrive */ + .rate = 1596 * FWK_MHZ, + .pll_rate = 1596 * FWK_MHZ, + .clock_source = MOD_PIK_CLOCK_CLUSCLK_SOURCE_PLL0, + .clock_div_type = MOD_PIK_CLOCK_MSCLOCK_DIVIDER_DIV_EXT, + .clock_div = 1, + .clock_mod_numerator = 1, + .clock_mod_denominator = 1, + }, +}; + +static const struct mod_css_clock_rate rate_table_gpu[] = { + { + .rate = 450 * FWK_MHZ, + .pll_rate = 450 * FWK_MHZ, + .clock_source = MOD_PIK_CLOCK_MSCLOCK_SOURCE_PRIVPLLCLK, + .clock_div_type = MOD_PIK_CLOCK_MSCLOCK_DIVIDER_DIV_EXT, + .clock_div = 1, + }, + { + .rate = 487500 * FWK_KHZ, + .pll_rate = 487500 * FWK_KHZ, + .clock_source = MOD_PIK_CLOCK_MSCLOCK_SOURCE_PRIVPLLCLK, + .clock_div_type = MOD_PIK_CLOCK_MSCLOCK_DIVIDER_DIV_EXT, + .clock_div = 1, + }, + { + .rate = 525 * FWK_MHZ, + .pll_rate = 525 * FWK_MHZ, + .clock_source = MOD_PIK_CLOCK_MSCLOCK_SOURCE_PRIVPLLCLK, + .clock_div_type = MOD_PIK_CLOCK_MSCLOCK_DIVIDER_DIV_EXT, + .clock_div = 1, + }, + { + .rate = 562500 * FWK_KHZ, + .pll_rate = 562500 * FWK_KHZ, + .clock_source = MOD_PIK_CLOCK_MSCLOCK_SOURCE_PRIVPLLCLK, + .clock_div_type = MOD_PIK_CLOCK_MSCLOCK_DIVIDER_DIV_EXT, + .clock_div = 1, + }, + { + /* Nominal */ + .rate = 600 * FWK_MHZ, + .pll_rate = 600 * FWK_MHZ, + .clock_source = MOD_PIK_CLOCK_MSCLOCK_SOURCE_PRIVPLLCLK, + .clock_div_type = MOD_PIK_CLOCK_MSCLOCK_DIVIDER_DIV_EXT, + .clock_div = 1, + }, +}; + +static const struct mod_css_clock_rate rate_table_vpu[] = { + { + /* Nominal */ + .rate = 600 * FWK_MHZ, + .pll_rate = 600 * FWK_MHZ, + .clock_source = MOD_PIK_CLOCK_MSCLOCK_SOURCE_PRIVPLLCLK, + .clock_div_type = MOD_PIK_CLOCK_MSCLOCK_DIVIDER_DIV_EXT, + .clock_div = 1, + }, +}; + +static const fwk_id_t member_table_cpu_group_big[] = { + FWK_ID_ELEMENT(FWK_MODULE_IDX_PIK_CLOCK, 4), + FWK_ID_ELEMENT(FWK_MODULE_IDX_PIK_CLOCK, 5), + FWK_ID_ELEMENT(FWK_MODULE_IDX_PIK_CLOCK, 6), + FWK_ID_ELEMENT(FWK_MODULE_IDX_PIK_CLOCK, 7), +}; + +static const fwk_id_t member_table_cpu_group_little[] = { + FWK_ID_ELEMENT(FWK_MODULE_IDX_PIK_CLOCK, 0), + FWK_ID_ELEMENT(FWK_MODULE_IDX_PIK_CLOCK, 1), + FWK_ID_ELEMENT(FWK_MODULE_IDX_PIK_CLOCK, 2), + FWK_ID_ELEMENT(FWK_MODULE_IDX_PIK_CLOCK, 3), +}; + +static const fwk_id_t member_table_gpu[] = { + FWK_ID_ELEMENT(FWK_MODULE_IDX_PIK_CLOCK, 8), +}; + +static const fwk_id_t member_table_vpu[] = { + FWK_ID_ELEMENT(FWK_MODULE_IDX_PIK_CLOCK, 9), +}; + +static const fwk_id_t member_table_dpu[] = { + FWK_ID_ELEMENT(FWK_MODULE_IDX_PIK_CLOCK, 11), +}; + +static const struct fwk_element css_clock_element_table[] = { + { + .name = "CPU_GROUP_BIG", + .data = &((struct mod_css_clock_dev_config) { + .clock_type = MOD_CSS_CLOCK_TYPE_INDEXED, + .rate_table = rate_table_cpu_group_big, + .rate_count = FWK_ARRAY_SIZE(rate_table_cpu_group_big), + .clock_switching_source = MOD_PIK_CLOCK_CLUSCLK_SOURCE_SYSREFCLK, + .pll_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_SYSTEM_PLL, 1), + .pll_api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_SYSTEM_PLL, + MOD_SYSTEM_PLL_API_TYPE_DEFAULT), + .member_table = member_table_cpu_group_big, + .member_count = FWK_ARRAY_SIZE(member_table_cpu_group_big), + .member_api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_PIK_CLOCK, + MOD_PIK_CLOCK_API_TYPE_CSS), + .initial_rate = 1750 * FWK_MHZ, + .modulation_supported = true, + }), + }, + { + .name = "CPU_GROUP_LITTLE", + .data = &((struct mod_css_clock_dev_config) { + .clock_type = MOD_CSS_CLOCK_TYPE_INDEXED, + .rate_table = rate_table_cpu_group_little, + .rate_count = FWK_ARRAY_SIZE(rate_table_cpu_group_little), + .clock_switching_source = MOD_PIK_CLOCK_CLUSCLK_SOURCE_SYSREFCLK, + .pll_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_SYSTEM_PLL, 0), + .pll_api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_SYSTEM_PLL, + MOD_SYSTEM_PLL_API_TYPE_DEFAULT), + .member_table = member_table_cpu_group_little, + .member_count = FWK_ARRAY_SIZE(member_table_cpu_group_little), + .member_api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_PIK_CLOCK, + MOD_PIK_CLOCK_API_TYPE_CSS), + .initial_rate = 1330 * FWK_MHZ, + .modulation_supported = true, + }), + }, + { + .name = "GPU", + .data = &((struct mod_css_clock_dev_config) { + .clock_type = MOD_CSS_CLOCK_TYPE_INDEXED, + .rate_table = rate_table_gpu, + .rate_count = FWK_ARRAY_SIZE(rate_table_gpu), + .clock_switching_source = MOD_PIK_CLOCK_GPUCLK_SOURCE_SYSREFCLK, + .pll_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_SYSTEM_PLL, 2), + .pll_api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_SYSTEM_PLL, + MOD_SYSTEM_PLL_API_TYPE_DEFAULT), + .member_table = member_table_gpu, + .member_count = FWK_ARRAY_SIZE(member_table_gpu), + .member_api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_PIK_CLOCK, + MOD_PIK_CLOCK_API_TYPE_CSS), + .initial_rate = 600 * FWK_MHZ, + .modulation_supported = false, + }), + }, + { + .name = "VPU", + .data = &((struct mod_css_clock_dev_config) { + .clock_type = MOD_CSS_CLOCK_TYPE_INDEXED, + .rate_table = rate_table_vpu, + .rate_count = FWK_ARRAY_SIZE(rate_table_vpu), + .clock_switching_source = MOD_PIK_CLOCK_VPUCLK_SOURCE_SYSREFCLK, + .pll_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_SYSTEM_PLL, 4), + .pll_api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_SYSTEM_PLL, + MOD_SYSTEM_PLL_API_TYPE_DEFAULT), + .member_table = member_table_vpu, + .member_count = FWK_ARRAY_SIZE(member_table_vpu), + .member_api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_PIK_CLOCK, + MOD_PIK_CLOCK_API_TYPE_CSS), + .initial_rate = 600 * FWK_MHZ, + .modulation_supported = false, + }), + }, + { + .name = "DPU", + .data = &((struct mod_css_clock_dev_config) { + .clock_type = MOD_CSS_CLOCK_TYPE_NON_INDEXED, + .clock_default_source = MOD_PIK_CLOCK_DPUCLK_SOURCE_DISPLAYPLLCLK, + .clock_switching_source = MOD_PIK_CLOCK_DPUCLK_SOURCE_PIXELCLK, + .pll_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_SYSTEM_PLL, 3), + .pll_api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_SYSTEM_PLL, + MOD_SYSTEM_PLL_API_TYPE_DEFAULT), + .member_table = member_table_dpu, + .member_count = FWK_ARRAY_SIZE(member_table_dpu), + .member_api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_PIK_CLOCK, + MOD_PIK_CLOCK_API_TYPE_CSS), + .initial_rate = 260 * FWK_MHZ, + .modulation_supported = false, + }), + }, + { }, /* Termination description. */ +}; + +static const struct fwk_element *css_clock_get_element_table + (fwk_id_t module_id) +{ + return css_clock_element_table; +} + +struct fwk_module_config config_css_clock = { + .get_element_table = css_clock_get_element_table, + .data = NULL, +}; diff --git a/product/sgm775/scp_ramfw/config_ddr_phy500.c b/product/sgm775/scp_ramfw/config_ddr_phy500.c new file mode 100644 index 00000000..ad66c744 --- /dev/null +++ b/product/sgm775/scp_ramfw/config_ddr_phy500.c @@ -0,0 +1,63 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +#include <fwk_element.h> +#include <fwk_module.h> +#include <mod_ddr_phy500.h> +#include <system_mmap.h> + +/* Default configuration values for DDR PHY500 devices. */ +static const struct mod_ddr_phy500_reg ddr_reg_val = { + .T_CTRL_DELAY = 0x00000000, + .READ_DELAY = 0x00000003, + .T_CTRL_UPD_MIN = 0x00000000, + .DELAY_SEL = 0x0000000A, + .CAPTURE_MASK = 0x0000001f, + .T_RDDATA_EN = 0x00001C00, + .T_RDLAT = 0x00000016, + .T_WRLAT = 0x01000000, + .DFI_WR_PREMBL = 0x00000002, + .LP_ACK = 0x00641300, +}; + +/* Table of DDR PHY500 element descriptions. */ +static struct fwk_element ddr_phy500_element_table[] = { + [0] = { .name = "DDR_PHY500-0", + .data = &((struct mod_ddr_phy500_element_config) { + .ddr = DDR_PHY0, + }), + }, + [1] = { .name = "DDR_PHY500-1", + .data = &((struct mod_ddr_phy500_element_config) { + .ddr = DDR_PHY1, + }), + }, + [2] = { .name = "DDR_PHY500-2", + .data = &((struct mod_ddr_phy500_element_config) { + .ddr = DDR_PHY2, + }), + }, + [3] = { .name = "DDR_PHY500-3", + .data = &((struct mod_ddr_phy500_element_config) { + .ddr = DDR_PHY3, + }), + }, + [4] = { }, /* Termination description. */ +}; + +static const struct fwk_element *ddr_phy500_get_element_table + (fwk_id_t module_id) +{ + return ddr_phy500_element_table; +} + +/* Configuration of the DDR PHY500 module. */ +struct fwk_module_config config_ddr_phy500 = { + .get_element_table = ddr_phy500_get_element_table, + .data = &((struct mod_ddr_phy500_module_config) { + .ddr_reg_val = &ddr_reg_val, + }), +}; diff --git a/product/sgm775/scp_ramfw/config_dmc500.c b/product/sgm775/scp_ramfw/config_dmc500.c new file mode 100644 index 00000000..567c787b --- /dev/null +++ b/product/sgm775/scp_ramfw/config_dmc500.c @@ -0,0 +1,221 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <fwk_element.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <mod_dmc500.h> +#include <system_mmap.h> + +#define COL_BITS 1 +#define BANK_BITS 0 +#define RANK_BITS 1 +#define BANK_GROUP 0 +#define ROW_BITS 4 +#define MEM_TYPE 3 +#define MEM_BURST 2 +#define DEVICE_WIDTH 2 +#define ADDR_SHUTTER 2 + +static const struct mod_dmc500_reg reg_val = { + .ADDRESS_CONTROL = ADDRESS_CONTROL_VAL(RANK_BITS, + BANK_BITS, + ROW_BITS, + COL_BITS), + .RANK_REMAP_CONTROL = 0x00000000, + .MEMORY_TYPE = MEMORY_TYPE_VAL(BANK_GROUP, + DEVICE_WIDTH, + MEM_TYPE), + .FORMAT_CONTROL = FORMAT_CONTROL_VAL(MEM_BURST), + .DECODE_CONTROL = 0x00000011, + .FEATURE_CONTROL = 0x00000000, + .ODT_WR_CONTROL_31_00 = 0x00000000, + .ODT_RD_CONTROL_31_00 = 0x00000000, + .ODT_TIMING = 0x10001000, + .T_REFI = 0x0000030b, + .T_RFC = 0x000340d0, + .T_RDPDEN = 0x0000002e, + .T_RCD = 0x0000001d, + .T_RAS = 0x80000044, + .T_RP = 0x0000221d, + .T_RRD = 0x00001010, + .T_ACT_WINDOW = 0x00000040, + .T_RTR = 0x000c0808, + .T_RTW = 0x001f1f1f, + .T_RTP = 0x0000000C, + .T_WR = 0x00000035, + .T_WTR = 0x00082929, + .T_WTW = 0x000b0808, + .T_XTMW = 0x00000020, + .T_CLOCK_CONTROL = 0x1119030d, + .T_EP = 0x0000000C, + .T_XP = 0x000c000c, + .T_ESR = 0x00000019, + .T_XSR = 0x00e100e1, + .ADDRESS_MAP = ADDRESS_MAP_VAL(ADDR_SHUTTER), + .SI0_SI_INTERRUPT_CONTROL = 0x00000000, + .SI0_PMU_REQ_CONTROL = 0x00000B1A, + .SI0_PMU_REQ_ATTRIBUTE_MASK_0 = 0xB0562AA1, + .SI0_PMU_REQ_ATTRIBUTE_MATCH_0 = 0xD0FB6716, + .SI0_PMU_REQ_ATTRIBUTE_MASK_1 = 0x7FC24C15, + .SI0_PMU_REQ_ATTRIBUTE_MATCH_1 = 0xF7A9B2AC, + .SI0_PMU_REQ_ATTRIBUTE_MASK_2 = 0xDD35FA69, + .SI0_PMU_REQ_ATTRIBUTE_MATCH_2 = 0x3555A8F5, + .SI0_PMU_REQ_ATTRIBUTE_MASK_3 = 0xDE382B10, + .SI0_PMU_REQ_ATTRIBUTE_MATCH_3 = 0x3484B32C, + .SI0_THRESHOLD_CONTROL = 0x80000801, + .SI1_SI_INTERRUPT_CONTROL = 0x00000000, + .SI1_PMU_REQ_CONTROL = 0x00000B1A, + .SI1_PMU_REQ_ATTRIBUTE_MASK_0 = 0xB0562AA1, + .SI1_PMU_REQ_ATTRIBUTE_MATCH_0 = 0xD0FB6716, + .SI1_PMU_REQ_ATTRIBUTE_MASK_1 = 0x7FC24C15, + .SI1_PMU_REQ_ATTRIBUTE_MATCH_1 = 0xF7A9B2AC, + .SI1_PMU_REQ_ATTRIBUTE_MASK_2 = 0xDD35FA69, + .SI1_PMU_REQ_ATTRIBUTE_MATCH_2 = 0x3555A8F5, + .SI1_PMU_REQ_ATTRIBUTE_MASK_3 = 0xDE382B10, + .SI1_PMU_REQ_ATTRIBUTE_MATCH_3 = 0x3484B32C, + .SI1_THRESHOLD_CONTROL = 0x80000801, + .QUEUE_THRESHOLD_CONTROL_31_00 = 0xDEF8D550, + .QUEUE_THRESHOLD_CONTROL_63_32 = 0xB038362F, + .DCB_INTERRUPT_CONTROL = 0x00000000, + .PMU_DCB_CONTROL = 0x00000800, + .PMU_DATA_CONTROL_BLOCK_ATTRIBUTE_MASK_0 = 0xFD98CF7D, + .PMU_DATA_CONTROL_BLOCK_ATTRIBUTE_MATCH_0 = 0x9F276EB5, + .PMU_DATA_CONTROL_BLOCK_ATTRIBUTE_MASK_1 = 0x40B1FC24, + .PMU_DATA_CONTROL_BLOCK_ATTRIBUTE_MATCH_1 = 0x04BBF4FA, + .PMU_DATA_CONTROL_BLOCK_ATTRIBUTE_MASK_2 = 0x8089B0AF, + .PMU_DATA_CONTROL_BLOCK_ATTRIBUTE_MATCH_2 = 0x7D26E0BE, + .PMU_TAG_ENTRIES_ATTRIBUTE_MASK = 0x000000CE, + .PMU_TAG_ENTRIES_ATTRIBUTE_MATCH = 0x00000056, + .QE_INTERRUPT_CONTROL = 0x00000000, + .RANK_TURNAROUND_CONTROL = 0x8909020F, + .HIT_TURNAROUND_CONTROL = 0x37B8222C, + .QOS_CLASS_CONTROL = 0x00000D50, + .ESCALATION_CONTROL = 0x000D0C00, + .QV_CONTROL_31_00 = 0xED2626B0, + .QV_CONTROL_63_32 = 0x4159BE97, + .RT_CONTROL_31_00 = 0xE8DC790A, + .RT_CONTROL_63_32 = 0x9441A291, + .TIMEOUT_CONTROL = 0x00000003, + .WRITE_PRIORITY_CONTROL_31_00 = 0x81268C40, + .WRITE_PRIORITY_CONTROL_63_32 = 0x15F20D15, + .DIR_TURNAROUND_CONTROL = 0x06060403, + .HIT_PREDICTION_CONTROL = 0x00020705, + .REFRESH_PRIORITY = 0x00000204, + .MC_UPDATE_CONTROL = 0x0000ff00, + .PHY_UPDATE_CONTROL = 0x15A3925F, + .PHY_MASTER_CONTROL = 0x6875AF9A, + .LOW_POWER_CONTROL = 0x000E0801, + .PMU_QE_CONTROL = 0x00000C0D, + .PMU_QE_MUX = 0x05670023, + .PMU_QOS_ENGINE_ATTRIBUTE_MASK_0 = 0x000000F1, + .PMU_QOS_ENGINE_ATTRIBUTE_MATCH_0 = 0x00000662, + .PMU_QOS_ENGINE_ATTRIBUTE_MASK_1 = 0x000000DD, + .PMU_QOS_ENGINE_ATTRIBUTE_MATCH_1 = 0x00000097, + .PMU_QOS_ENGINE_ATTRIBUTE_MASK_2 = 0x0000001A, + .PMU_QOS_ENGINE_ATTRIBUTE_MATCH_2 = 0x00000755, + .PMU_QUEUED_ENTRIES_ATTRIBUTE_MASK = 0xAD625ED5, + .PMU_QUEUED_ENTRIES_ATTRIBUTE_MATCH = 0x853C65BB, + .MI_INTERRUPT_CONTROL = 0x00000000, + .POWER_DOWN_CONTROL = 0x00000005, + .REFRESH_CONTROL = 0x00000000, + .PMU_MI_CONTROL = 0x00000100, + .PMU_MEMORY_IF_ATTRIBUTE_MASK_0 = 0x0032BB0E, + .PMU_MEMORY_IF_ATTRIBUTE_MATCH_0 = 0x0033F5AB, + .PMU_MEMORY_IF_ATTRIBUTE_MASK_1 = 0x00296B28, + .PMU_MEMORY_IF_ATTRIBUTE_MATCH_1 = 0x002C67BF, + .PMU_BANK_STATES_ATTRIBUTE_MASK = 0x00000005, + .PMU_BANK_STATES_ATTRIBUTE_MATCH = 0x00000019, + .PMU_RANK_STATES_ATTRIBUTE_MASK = 0x0000001B, + .PMU_RANK_STATES_ATTRIBUTE_MATCH = 0x00000020, + .CFG_INTERRUPT_CONTROL = 0x00000000, + .T_RDDATA_EN = 0x00000001, + .T_PHYRDLAT = 0x0000003f, + .T_PHYWRLAT = 0x010f170e, + .ERR_RAMECC_CTLR = 0x00000000, + .PHY_POWER_CONTROL = 0x0000012a, + .T_PHY_TRAIN = 0x00f8000a, + .PHYUPD_INIT = 0x00000000, + .REFRESH_ENABLE = 0x00000001, + .MI_STATE_CONTROL = 0, + .QUEUE_STATE_CONTROL = 0, + .SI0_SI_STATE_CONTROL = 0, + .SI1_SI_STATE_CONTROL = 0, +}; + +/* Table of DMC500 elements descriptions. */ +static struct fwk_element dmc500_element_table[] = { + [0] = { .name = "DMC500-0", + .data = &((struct mod_dmc500_element_config) { + .dmc = DMC_INTERNAL0, + .ddr_phy_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_DDR_PHY500, 0), + }), + }, + [1] = { .name = "DMC500-1", + .data = &((struct mod_dmc500_element_config) { + .dmc = DMC_INTERNAL1, + .ddr_phy_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_DDR_PHY500, 1), + }), + }, + [2] = { .name = "DMC500-2", + .data = &((struct mod_dmc500_element_config) { + .dmc = DMC_INTERNAL2, + .ddr_phy_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_DDR_PHY500, 2), + }), + }, + [3] = { .name = "DMC500-3", + .data = &((struct mod_dmc500_element_config) { + .dmc = DMC_INTERNAL3, + .ddr_phy_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_DDR_PHY500, 3), + }), + }, + [4] = { }, /* Termination description. */ +}; + +static const struct fwk_element *dmc500_get_element_table(fwk_id_t module_id) +{ + return dmc500_element_table; +} + +static void direct_ddr_cmd(struct mod_dmc500_reg *dmc) +{ + dmc->DIRECT_CMD_SETTINGS = 0x00C80000; + dmc->DIRECT_CMD = 0x00000000; + dmc->DIRECT_CLK_DISABLE = 0x00280003; + dmc->CLK_STATUS_OVERRIDE = 0x00000003; + dmc->DIRECT_CMD_SETTINGS = 0x01F40003; + dmc->DIRECT_CMD = 0x00800080; + dmc->RANK_STATUS_OVERRIDE = 0x30000003; + dmc->DIRECT_CMD_SETTINGS = 0x04b00003; + dmc->DIRECT_CMD = 0x00800FE0; + dmc->DIRECT_CMD_SETTINGS = 0x00500003; + dmc->DIRECT_CMD = 0x008011E0; + dmc->DIRECT_CMD_SETTINGS = 0x00140003; + dmc->DIRECT_CMD = 0x06d601c6; + dmc->DIRECT_CMD_SETTINGS = 0x01000003; + dmc->DIRECT_CMD = 0x00f60dc6; + dmc->DIRECT_CMD_SETTINGS = 0x00140003; + dmc->DIRECT_CMD = 0x31d603c6; + dmc->DIRECT_CMD_SETTINGS = 0x00140003; + dmc->DIRECT_CMD = 0x16f601c6; + dmc->DIRECT_CMD_SETTINGS = 0x00140003; + dmc->DIRECT_CMD = 0x2dd602c6; + dmc->DIRECT_CMD_SETTINGS = 0x02000003; + dmc->DIRECT_CMD = 0x00d60de6; +} + +/* Configuration of the DMC500 module. */ +struct fwk_module_config config_dmc500 = { + .get_element_table = dmc500_get_element_table, + .data = &((struct mod_dmc500_module_config) { + .timer_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_TIMER, 0), + .ddr_phy_module_id = FWK_ID_MODULE_INIT(FWK_MODULE_IDX_DDR_PHY500), + .ddr_phy_api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_DDR_PHY500, 0), + .reg_val = ®_val, + .direct_ddr_cmd = direct_ddr_cmd, + }), +}; diff --git a/product/sgm775/scp_ramfw/config_dvfs.c b/product/sgm775/scp_ramfw/config_dvfs.c new file mode 100644 index 00000000..43c7ab97 --- /dev/null +++ b/product/sgm775/scp_ramfw/config_dvfs.c @@ -0,0 +1,129 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <fwk_element.h> +#include <fwk_macros.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <config_dvfs.h> +#include <mod_dvfs.h> + +static const struct mod_dvfs_domain_config cpu_group_little = { + .psu_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_PSU, 0), + .clock_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_CLOCK, 1), + .latency = 1200, + .sustained_idx = 2, + .opps = (struct mod_dvfs_opp[]) { + { + .frequency = 665 * FWK_MHZ, + .voltage = 100, + }, + { + .frequency = 998 * FWK_MHZ, + .voltage = 200, + }, + { + .frequency = 1330 * FWK_MHZ, + .voltage = 300, + }, + { + .frequency = 1463 * FWK_MHZ, + .voltage = 400, + }, + { + .frequency = 1596 * FWK_MHZ, + .voltage = 500, + }, + { } + } +}; + +static const struct mod_dvfs_domain_config cpu_group_big = { + .psu_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_PSU, 1), + .clock_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_CLOCK, 0), + .latency = 1200, + .sustained_idx = 2, + .opps = (struct mod_dvfs_opp[]) { + { + .frequency = 1313 * FWK_MHZ, + .voltage = 100, + }, + { + .frequency = 1531 * FWK_MHZ, + .voltage = 200, + }, + { + .frequency = 1750 * FWK_MHZ, + .voltage = 300, + }, + { + .frequency = 2100 * FWK_MHZ, + .voltage = 400, + }, + { + .frequency = 2450 * FWK_MHZ, + .voltage = 500, + }, + { } + } +}; + +static const struct mod_dvfs_domain_config gpu = { + .psu_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_PSU, 2), + .clock_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_CLOCK, 2), + .latency = 1200, + .sustained_idx = 4, + .opps = (struct mod_dvfs_opp[]) { + { + .frequency = 450 * FWK_MHZ, + .voltage = 100, + }, + { + .frequency = 487500 * FWK_KHZ, + .voltage = 200, + }, + { + .frequency = 525 * FWK_MHZ, + .voltage = 300, + }, + { + .frequency = 562500 * FWK_KHZ, + .voltage = 400, + }, + { + .frequency = 600 * FWK_MHZ, + .voltage = 500, + }, + { } + } +}; + +static const struct fwk_element element_table[] = { + [DVFS_ELEMENT_IDX_LITTLE] = { + .name = "CPU_GROUP_LITTLE", + .data = &cpu_group_little, + }, + [DVFS_ELEMENT_IDX_BIG] = { + .name = "CPU_GROUP_BIG", + .data = &cpu_group_big, + }, + [DVFS_ELEMENT_IDX_GPU] = { + .name = "GPU", + .data = &gpu, + }, + { } +}; + +static const struct fwk_element *dvfs_get_element_table(fwk_id_t module_id) +{ + return element_table; +} + +struct fwk_module_config config_dvfs = { + .get_element_table = dvfs_get_element_table, + .data = NULL, +}; diff --git a/product/sgm775/scp_ramfw/config_dvfs.h b/product/sgm775/scp_ramfw/config_dvfs.h new file mode 100644 index 00000000..365cc1cd --- /dev/null +++ b/product/sgm775/scp_ramfw/config_dvfs.h @@ -0,0 +1,18 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef CONFIG_DVFS_H +#define CONFIG_DVFS_H + +enum dvfs_element_idx { + DVFS_ELEMENT_IDX_LITTLE, + DVFS_ELEMENT_IDX_BIG, + DVFS_ELEMENT_IDX_GPU, + DVFS_ELEMENT_IDX_COUNT +}; + +#endif /* CONFIG_DVFS_H */ diff --git a/product/sgm775/scp_ramfw/config_log.c b/product/sgm775/scp_ramfw/config_log.c new file mode 100644 index 00000000..938ca39b --- /dev/null +++ b/product/sgm775/scp_ramfw/config_log.c @@ -0,0 +1,63 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <fwk_banner.h> +#include <fwk_macros.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <mod_clock.h> +#include <mod_log.h> +#include <mod_pl011.h> +#include <system_mmap.h> +#include <clock_devices.h> + +/* + * PL011 module + */ +static const struct fwk_element pl011_element_table[] = { + [0] = { + .name = "board-uart1", + .data = &((struct mod_pl011_device_config) { + .reg_base = BOARD_UART1_BASE, + .baud_rate_bps = 115200, + .clock_rate_hz = 24 * FWK_MHZ, + .clock_id = FWK_ID_ELEMENT_INIT( + FWK_MODULE_IDX_CLOCK, + CLOCK_DEV_IDX_FCMCLK), + }), + }, + [1] = {}, +}; + +static const struct fwk_element *get_pl011_table(fwk_id_t module_id) +{ + return pl011_element_table; +} + +struct fwk_module_config config_pl011 = { + .get_element_table = get_pl011_table, +}; + +/* + * Log module + */ +static const struct mod_log_config log_data = { + .device_id = FWK_ID_ELEMENT(FWK_MODULE_IDX_PL011, 0), + .api_id = FWK_ID_API(FWK_MODULE_IDX_PL011, 0), + .log_groups = MOD_LOG_GROUP_ERROR | + MOD_LOG_GROUP_INFO | + MOD_LOG_GROUP_WARNING | + MOD_LOG_GROUP_DEBUG, + .banner = FWK_BANNER_SCP + FWK_BANNER_RAM_FIRMWARE + BUILD_VERSION_DESCRIBE_STRING "\n", +}; + +struct fwk_module_config config_log = { + .get_element_table = NULL, + .data = &log_data, +}; diff --git a/product/sgm775/scp_ramfw/config_mhu.c b/product/sgm775/scp_ramfw/config_mhu.c new file mode 100644 index 00000000..b6f89940 --- /dev/null +++ b/product/sgm775/scp_ramfw/config_mhu.c @@ -0,0 +1,54 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <fwk_element.h> +#include <fwk_id.h> +#include <fwk_module.h> +#include <mod_mhu.h> +#include <sgm775_irq.h> +#include <sgm775_mhu.h> +#include <sgm775_mmap.h> + +static const struct fwk_element mhu_element_table[] = { + [SGM775_MHU_DEVICE_IDX_S] = { + .name = "MHU_S", + .sub_element_count = 1, + .data = &((struct mod_mhu_device_config) { + .irq = MHU_SECURE_IRQ, + .in = MHU_CPU_INTR_S_BASE, + .out = MHU_SCP_INTR_S_BASE, + }) + }, + [SGM775_MHU_DEVICE_IDX_NS_H] = { + .name = "MHU_NS_H", + .sub_element_count = 1, + .data = &((struct mod_mhu_device_config) { + .irq = MHU_HIGH_PRIO_IRQ, + .in = MHU_CPU_INTR_H_BASE, + .out = MHU_SCP_INTR_H_BASE, + }) + }, + [SGM775_MHU_DEVICE_IDX_NS_L] = { + .name = "MHU_NS_L", + .sub_element_count = 1, + .data = &((struct mod_mhu_device_config) { + .irq = MHU_LOW_PRIO_IRQ, + .in = MHU_CPU_INTR_L_BASE, + .out = MHU_SCP_INTR_L_BASE, + }) + }, + [SGM775_MHU_DEVICE_IDX_COUNT] = {}, +}; + +static const struct fwk_element *mhu_get_element_table(fwk_id_t module_id) +{ + return mhu_element_table; +} + +struct fwk_module_config config_mhu = { + .get_element_table = mhu_get_element_table, +}; diff --git a/product/sgm775/scp_ramfw/config_mock_psu.c b/product/sgm775/scp_ramfw/config_mock_psu.c new file mode 100644 index 00000000..366f2023 --- /dev/null +++ b/product/sgm775/scp_ramfw/config_mock_psu.c @@ -0,0 +1,52 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <fwk_element.h> +#include <fwk_module.h> +#include <mod_mock_psu.h> + +static const struct fwk_element element_table[] = { + { + .name = "CPU_GROUP_LITTLE", + .data = &(const struct mod_mock_psu_device_config) { + .default_enabled = true, + .default_voltage = 100, + }, + }, + { + .name = "CPU_GROUP_BIG", + .data = &(const struct mod_mock_psu_device_config) { + .default_enabled = true, + .default_voltage = 100, + }, + }, + { + .name = "GPU", + .data = &(const struct mod_mock_psu_device_config) { + .default_enabled = true, + .default_voltage = 100, + }, + }, + { + .name = "VPU", + .data = &(const struct mod_mock_psu_device_config) { + .default_enabled = true, + .default_voltage = 100, + }, + }, + { } +}; + +static const struct fwk_element *get_element_table(fwk_id_t module_id) +{ + return element_table; +} + +struct fwk_module_config config_mock_psu = { + .get_element_table = get_element_table, + .data = NULL, +}; diff --git a/product/sgm775/scp_ramfw/config_pik_clock.c b/product/sgm775/scp_ramfw/config_pik_clock.c new file mode 100644 index 00000000..06c37de7 --- /dev/null +++ b/product/sgm775/scp_ramfw/config_pik_clock.c @@ -0,0 +1,295 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <fwk_element.h> +#include <fwk_id.h> +#include <fwk_macros.h> +#include <fwk_module.h> +#include <mod_pik_clock.h> +#include <sgm775_pik.h> +#include <system_clock.h> + +/* + * Rate lookup tables + */ + +static struct mod_pik_clock_rate rate_table_cpu_a55[] = { + { + .rate = 1330 * FWK_MHZ, + .source = MOD_PIK_CLOCK_CLUSCLK_SOURCE_PLL0, + .divider_reg = MOD_PIK_CLOCK_MSCLOCK_DIVIDER_DIV_EXT, + .divider = 1, /* Rate adjusted via CPU PLL */ + }, +}; + +static struct mod_pik_clock_rate rate_table_cpu_a75[] = { + { + .rate = 1750 * FWK_MHZ, + .source = MOD_PIK_CLOCK_CLUSCLK_SOURCE_PLL1, + .divider_reg = MOD_PIK_CLOCK_MSCLOCK_DIVIDER_DIV_EXT, + .divider = 1, /* Rate adjusted via CPU PLL */ + }, +}; + +static struct mod_pik_clock_rate rate_table_gpu[] = { + { + .rate = 600 * FWK_MHZ, + .source = MOD_PIK_CLOCK_MSCLOCK_SOURCE_PRIVPLLCLK, + .divider_reg = MOD_PIK_CLOCK_MSCLOCK_DIVIDER_DIV_EXT, + .divider = 1, /* Rate adjusted via GPU PLL */ + }, +}; + +static struct mod_pik_clock_rate rate_table_vpu[] = { + { + .rate = 600 * FWK_MHZ, + .source = MOD_PIK_CLOCK_VPUCLK_SOURCE_VIDEOPLLCLK, + .divider_reg = MOD_PIK_CLOCK_MSCLOCK_DIVIDER_DIV_EXT, + .divider = 1, /* Rate adjusted via VPU PLL */ + }, +}; + +static struct mod_pik_clock_rate rate_table_dpu[] = { + { + .rate = 260 * FWK_MHZ, + .source = MOD_PIK_CLOCK_DPUCLK_SOURCE_DISPLAYPLLCLK, + .divider_reg = MOD_PIK_CLOCK_MSCLOCK_DIVIDER_DIV_EXT, + .divider = 1, /* Rate adjusted via display PLL */ + }, +}; + +static struct mod_pik_clock_rate rate_table_aclkdp[] = { + { + .rate = (CLOCK_RATE_SYSPLLCLK / 3), + .source = MOD_PIK_CLOCK_ACLKDPU_SOURCE_SYSPLLCLK, + .divider_reg = MOD_PIK_CLOCK_MSCLOCK_DIVIDER_DIV_SYS, + .divider = 3, + }, +}; + +static const struct mod_pik_clock_rate rate_table_sys_fcmclk[] = { + { + .rate = CLOCK_RATE_SYSPLLCLK, + .source = MOD_PIK_CLOCK_MSCLOCK_SOURCE_SYSPLLCLK, + .divider_reg = MOD_PIK_CLOCK_MSCLOCK_DIVIDER_DIV_SYS, + .divider = 1, + }, +}; + +static const struct fwk_element pik_clock_element_table[] = { + /* + * A55 CPUS + */ + { + .name = "CLUS0_CPU0", + .data = &((struct mod_pik_clock_dev_config) { + .type = MOD_PIK_CLOCK_TYPE_CLUSTER, + .is_group_member = true, + .control_reg = &PIK_CLUS0->AP_CLK_CTRL[0].CORECLK_CTRL, + .divext_reg = &PIK_CLUS0->AP_CLK_CTRL[0].CORECLK_DIV, + .modulator_reg = &PIK_CLUS0->AP_CLK_CTRL[0].CORECLK_MOD, + .rate_table = rate_table_cpu_a55, + .rate_count = FWK_ARRAY_SIZE(rate_table_cpu_a55), + .initial_rate = 1330 * FWK_MHZ, + .defer_initialization = false, + }), + }, + { + .name = "CLUS0_CPU1", + .data = &((struct mod_pik_clock_dev_config) { + .type = MOD_PIK_CLOCK_TYPE_CLUSTER, + .is_group_member = true, + .control_reg = &PIK_CLUS0->AP_CLK_CTRL[1].CORECLK_CTRL, + .divext_reg = &PIK_CLUS0->AP_CLK_CTRL[1].CORECLK_DIV, + .modulator_reg = &PIK_CLUS0->AP_CLK_CTRL[1].CORECLK_MOD, + .rate_table = rate_table_cpu_a55, + .rate_count = FWK_ARRAY_SIZE(rate_table_cpu_a55), + .initial_rate = 1330 * FWK_MHZ, + .defer_initialization = false, + }), + }, + { + .name = "CLUS0_CPU2", + .data = &((struct mod_pik_clock_dev_config) { + .type = MOD_PIK_CLOCK_TYPE_CLUSTER, + .is_group_member = true, + .control_reg = &PIK_CLUS0->AP_CLK_CTRL[2].CORECLK_CTRL, + .divext_reg = &PIK_CLUS0->AP_CLK_CTRL[2].CORECLK_DIV, + .modulator_reg = &PIK_CLUS0->AP_CLK_CTRL[2].CORECLK_MOD, + .rate_table = rate_table_cpu_a55, + .rate_count = FWK_ARRAY_SIZE(rate_table_cpu_a55), + .initial_rate = 1330 * FWK_MHZ, + .defer_initialization = false, + }), + }, + { + .name = "CLUS0_CPU3", + .data = &((struct mod_pik_clock_dev_config) { + .type = MOD_PIK_CLOCK_TYPE_CLUSTER, + .is_group_member = true, + .control_reg = &PIK_CLUS0->AP_CLK_CTRL[3].CORECLK_CTRL, + .divext_reg = &PIK_CLUS0->AP_CLK_CTRL[3].CORECLK_DIV, + .modulator_reg = &PIK_CLUS0->AP_CLK_CTRL[3].CORECLK_MOD, + .rate_table = rate_table_cpu_a55, + .rate_count = FWK_ARRAY_SIZE(rate_table_cpu_a55), + .initial_rate = 1330 * FWK_MHZ, + .defer_initialization = false, + }), + }, + /* + * A75 CPUS + */ + { + .name = "CLUS0_CPU4", + .data = &((struct mod_pik_clock_dev_config) { + .type = MOD_PIK_CLOCK_TYPE_CLUSTER, + .is_group_member = true, + .control_reg = &PIK_CLUS0->AP_CLK_CTRL[4].CORECLK_CTRL, + .divext_reg = &PIK_CLUS0->AP_CLK_CTRL[4].CORECLK_DIV, + .modulator_reg = &PIK_CLUS0->AP_CLK_CTRL[4].CORECLK_MOD, + .rate_table = rate_table_cpu_a75, + .rate_count = FWK_ARRAY_SIZE(rate_table_cpu_a75), + .initial_rate = 1750 * FWK_MHZ, + .defer_initialization = false, + }), + }, + { + .name = "CLUS0_CPU5", + .data = &((struct mod_pik_clock_dev_config) { + .type = MOD_PIK_CLOCK_TYPE_CLUSTER, + .is_group_member = true, + .control_reg = &PIK_CLUS0->AP_CLK_CTRL[5].CORECLK_CTRL, + .divext_reg = &PIK_CLUS0->AP_CLK_CTRL[5].CORECLK_DIV, + .modulator_reg = &PIK_CLUS0->AP_CLK_CTRL[5].CORECLK_MOD, + .rate_table = rate_table_cpu_a75, + .rate_count = FWK_ARRAY_SIZE(rate_table_cpu_a75), + .initial_rate = 1750 * FWK_MHZ, + .defer_initialization = false, + }), + }, + { + .name = "CLUS0_CPU6", + .data = &((struct mod_pik_clock_dev_config) { + .type = MOD_PIK_CLOCK_TYPE_CLUSTER, + .is_group_member = true, + .control_reg = &PIK_CLUS0->AP_CLK_CTRL[6].CORECLK_CTRL, + .divext_reg = &PIK_CLUS0->AP_CLK_CTRL[6].CORECLK_DIV, + .modulator_reg = &PIK_CLUS0->AP_CLK_CTRL[6].CORECLK_MOD, + .rate_table = rate_table_cpu_a75, + .rate_count = FWK_ARRAY_SIZE(rate_table_cpu_a75), + .initial_rate = 1750 * FWK_MHZ, + .defer_initialization = false, + }), + }, + { + .name = "CLUS0_CPU7", + .data = &((struct mod_pik_clock_dev_config) { + .type = MOD_PIK_CLOCK_TYPE_CLUSTER, + .is_group_member = true, + .control_reg = &PIK_CLUS0->AP_CLK_CTRL[7].CORECLK_CTRL, + .divext_reg = &PIK_CLUS0->AP_CLK_CTRL[7].CORECLK_DIV, + .modulator_reg = &PIK_CLUS0->AP_CLK_CTRL[7].CORECLK_MOD, + .rate_table = rate_table_cpu_a75, + .rate_count = FWK_ARRAY_SIZE(rate_table_cpu_a75), + .initial_rate = 1750 * FWK_MHZ, + .defer_initialization = false, + }), + }, + /* + * GPU + */ + { + .name = "GPU", + .data = &((struct mod_pik_clock_dev_config) { + .type = MOD_PIK_CLOCK_TYPE_MULTI_SOURCE, + .is_group_member = true, + .control_reg = &PIK_GPU->GPUCLK_CTRL, + .divsys_reg = &PIK_GPU->GPUCLK_DIV1, + .divext_reg = &PIK_GPU->GPUCLK_DIV2, + .rate_table = rate_table_gpu, + .rate_count = FWK_ARRAY_SIZE(rate_table_gpu), + .initial_rate = 600 * FWK_MHZ, + .defer_initialization = false, + }), + }, + /* + * VPU + */ + { + .name = "VPU", + .data = &((struct mod_pik_clock_dev_config) { + .type = MOD_PIK_CLOCK_TYPE_MULTI_SOURCE, + .is_group_member = true, + .control_reg = &PIK_VPU->VIDEOCLK_CTRL, + .divsys_reg = &PIK_VPU->VIDEOCLK_DIV1, + .divext_reg = &PIK_VPU->VIDEOCLK_DIV2, + .rate_table = rate_table_vpu, + .rate_count = FWK_ARRAY_SIZE(rate_table_vpu), + .initial_rate = 600 * FWK_MHZ, + .defer_initialization = false, + }), + }, + /* + * DPU + */ + { + .name = "ACLKDP", + .data = &((struct mod_pik_clock_dev_config) { + .type = MOD_PIK_CLOCK_TYPE_MULTI_SOURCE, + .is_group_member = true, + .control_reg = &PIK_DPU->ACLKDP_CTRL, + .divsys_reg = &PIK_DPU->ACLKDP_DIV1, + .divext_reg = &PIK_DPU->ACLKDP_DIV2, + .rate_table = rate_table_aclkdp, + .rate_count = FWK_ARRAY_SIZE(rate_table_aclkdp), + .initial_rate = (CLOCK_RATE_SYSPLLCLK / 3), + .defer_initialization = false, + }), + }, + { + .name = "DPU_M0", + .data = &((struct mod_pik_clock_dev_config) { + .type = MOD_PIK_CLOCK_TYPE_MULTI_SOURCE, + .is_group_member = true, + .control_reg = &PIK_DPU->M0CLK_CTRL, + .divsys_reg = &PIK_DPU->M0CLK_DIV1, + .divext_reg = &PIK_DPU->M0CLK_DIV2, + .rate_table = rate_table_dpu, + .rate_count = FWK_ARRAY_SIZE(rate_table_dpu), + .initial_rate = 260 * FWK_MHZ, + .defer_initialization = false, + }), + }, + /* + * FCM Clock + */ + { + .name = "FCMCLK", + .data = &((struct mod_pik_clock_dev_config) { + .type = MOD_PIK_CLOCK_TYPE_MULTI_SOURCE, + .is_group_member = false, + .control_reg = &PIK_SYSTEM->FCMCLK_CTRL, + .divsys_reg = &PIK_SYSTEM->FCMCLK_DIV1, + .rate_table = rate_table_sys_fcmclk, + .rate_count = FWK_ARRAY_SIZE(rate_table_sys_fcmclk), + .initial_rate = CLOCK_RATE_SYSPLLCLK, + .defer_initialization = false, + }), + }, + { }, /* Termination description. */ +}; + +static const struct fwk_element *pik_clock_get_element_table + (fwk_id_t module_id) +{ + return pik_clock_element_table; +} + +struct fwk_module_config config_pik_clock = { + .get_element_table = pik_clock_get_element_table, + .data = NULL, +}; diff --git a/product/sgm775/scp_ramfw/config_power_domain.c b/product/sgm775/scp_ramfw/config_power_domain.c new file mode 100644 index 00000000..ea674d40 --- /dev/null +++ b/product/sgm775/scp_ramfw/config_power_domain.c @@ -0,0 +1,258 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <stdint.h> +#include <string.h> +#include <fwk_element.h> +#include <fwk_macros.h> +#include <fwk_mm.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <config_power_domain.h> +#include <config_ppu_v0.h> +#include <mod_system_power.h> +#include <mod_power_domain.h> +#include <mod_ppu_v1.h> +#include <sgm775_core.h> + +static const char *core_pd_name_table[SGM775_CORE_PER_CLUSTER_MAX] = { + "CLUS0CORE0", "CLUS0CORE1", "CLUS0CORE2", "CLUS0CORE3", + "CLUS0CORE4", "CLUS0CORE5", "CLUS0CORE6", "CLUS0CORE7", +}; + +/* 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 sgm775_power_domain_config = { }; + +static struct fwk_element sgm775_power_domain_static_element_table[] = { + [CONFIG_POWER_DOMAIN_SYSTOP_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_1, + 0, + 0, + CONFIG_POWER_DOMAIN_SYSTOP_CHILD_CLUSTER0, + 0), + .api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_PPU_V1, + MOD_PPU_V1_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_SYSTOP_CHILD_DBGTOP] = { + .name = "DBGTOP", + .data = &((struct mod_power_domain_element_config) { + .attributes.pd_type = MOD_PD_TYPE_DEVICE_DEBUG, + .tree_pos = MOD_PD_TREE_POS( + MOD_PD_LEVEL_1, + 0, + 0, + CONFIG_POWER_DOMAIN_SYSTOP_CHILD_DBGTOP, + 0), + .driver_id = FWK_ID_ELEMENT_INIT( + FWK_MODULE_IDX_PPU_V0, PPU_V0_ELEMENT_IDX_DBGTOP), + .api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_PPU_V0, 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_SYSTOP_CHILD_DPU0TOP] = { + .name = "DPU0TOP", + .data = &((struct mod_power_domain_element_config) { + .attributes.pd_type = MOD_PD_TYPE_DEVICE, + .tree_pos = MOD_PD_TREE_POS( + MOD_PD_LEVEL_1, + 0, + 0, + CONFIG_POWER_DOMAIN_SYSTOP_CHILD_DPU0TOP, + 0), + .driver_id = FWK_ID_ELEMENT_INIT( + FWK_MODULE_IDX_PPU_V0, PPU_V0_ELEMENT_IDX_DPU0TOP), + .api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_PPU_V0, 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_SYSTOP_CHILD_DPU1TOP] = { + .name = "DPU1TOP", + .data = &((struct mod_power_domain_element_config) { + .attributes.pd_type = MOD_PD_TYPE_DEVICE, + .tree_pos = MOD_PD_TREE_POS( + MOD_PD_LEVEL_1, + 0, + 0, + CONFIG_POWER_DOMAIN_SYSTOP_CHILD_DPU1TOP, + 0), + .driver_id = FWK_ID_ELEMENT_INIT( + FWK_MODULE_IDX_PPU_V0, PPU_V0_ELEMENT_IDX_DPU1TOP), + .api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_PPU_V0, 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_SYSTOP_CHILD_GPUTOP] = { + .name = "GPUTOP", + .data = &((struct mod_power_domain_element_config) { + .attributes.pd_type = MOD_PD_TYPE_DEVICE, + .tree_pos = MOD_PD_TREE_POS( + MOD_PD_LEVEL_1, + 0, + 0, + CONFIG_POWER_DOMAIN_SYSTOP_CHILD_GPUTOP, + 0), + .driver_id = FWK_ID_ELEMENT_INIT( + FWK_MODULE_IDX_PPU_V0, PPU_V0_ELEMENT_IDX_GPUTOP), + .api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_PPU_V0, 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_SYSTOP_CHILD_VPUTOP] = { + .name = "VPUTOP", + .data = &((struct mod_power_domain_element_config) { + .attributes.pd_type = MOD_PD_TYPE_DEVICE, + .tree_pos = MOD_PD_TREE_POS( + MOD_PD_LEVEL_1, + 0, + 0, + CONFIG_POWER_DOMAIN_SYSTOP_CHILD_VPUTOP, + 0), + .driver_id = FWK_ID_ELEMENT_INIT( + FWK_MODULE_IDX_PPU_V0, PPU_V0_ELEMENT_IDX_VPUTOP), + .api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_PPU_V0, 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_SYSTOP_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 *sgm775_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( + sgm775_core_get_count() + + FWK_ARRAY_SIZE(sgm775_power_domain_static_element_table) + + 1, /* Terminator */ + sizeof(struct fwk_element)); + if (element_table == NULL) + return NULL; + + pd_config_table = fwk_mm_calloc(sgm775_core_get_count(), + sizeof(struct mod_power_domain_element_config)); + if (pd_config_table == NULL) + return NULL; + + for (core_idx = 0; core_idx < sgm775_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_0, + 0, + 0, + CONFIG_POWER_DOMAIN_SYSTOP_CHILD_CLUSTER0, + core_idx), + pd_config->driver_id = FWK_ID_ELEMENT(FWK_MODULE_IDX_PPU_V1, core_idx), + pd_config->api_id = FWK_ID_API( + FWK_MODULE_IDX_PPU_V1, MOD_PPU_V1_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); + } + + pd_config = (struct mod_power_domain_element_config *) + sgm775_power_domain_static_element_table + [CONFIG_POWER_DOMAIN_SYSTOP_CHILD_CLUSTER0] + .data; + pd_config->driver_id = + FWK_ID_ELEMENT(FWK_MODULE_IDX_PPU_V1, sgm775_core_get_count()); + + memcpy(element_table + sgm775_core_get_count(), + sgm775_power_domain_static_element_table, + sizeof(sgm775_power_domain_static_element_table)); + + return element_table; +} + +/* + * Power module configuration data + */ +struct fwk_module_config config_power_domain = { + .get_element_table = sgm775_power_domain_get_element_table, + .data = &sgm775_power_domain_config, +}; diff --git a/product/sgm775/scp_ramfw/config_power_domain.h b/product/sgm775/scp_ramfw/config_power_domain.h new file mode 100644 index 00000000..10a40a47 --- /dev/null +++ b/product/sgm775/scp_ramfw/config_power_domain.h @@ -0,0 +1,21 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef CONFIG_POWER_DOMAIN_H +#define CONFIG_POWER_DOMAIN_H + +enum systop_child_index { + CONFIG_POWER_DOMAIN_SYSTOP_CHILD_CLUSTER0, + CONFIG_POWER_DOMAIN_SYSTOP_CHILD_DBGTOP, + CONFIG_POWER_DOMAIN_SYSTOP_CHILD_DPU0TOP, + CONFIG_POWER_DOMAIN_SYSTOP_CHILD_DPU1TOP, + CONFIG_POWER_DOMAIN_SYSTOP_CHILD_GPUTOP, + CONFIG_POWER_DOMAIN_SYSTOP_CHILD_VPUTOP, + CONFIG_POWER_DOMAIN_SYSTOP_CHILD_COUNT +}; + +#endif /* CONFIG_POWER_DOMAIN_H */ diff --git a/product/sgm775/scp_ramfw/config_ppu_v0.c b/product/sgm775/scp_ramfw/config_ppu_v0.c new file mode 100644 index 00000000..9c65b589 --- /dev/null +++ b/product/sgm775/scp_ramfw/config_ppu_v0.c @@ -0,0 +1,92 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <fwk_element.h> +#include <fwk_module.h> +#include <mod_ppu_v0.h> +#include <config_ppu_v0.h> +#include <sgm775_irq.h> +#include <sgm775_mmap.h> + +static struct fwk_element sgm775_ppu_v0_element_table[] = { + [PPU_V0_ELEMENT_IDX_DBGTOP] = { + .name = "DBGTOP", + .data = &((struct mod_ppu_v0_pd_config) { + .pd_type = MOD_PD_TYPE_DEVICE_DEBUG, + .ppu.reg_base = PPU_DEBUG_BASE, + .ppu.irq = PPU_DEBUG_IRQ + }), + }, + [PPU_V0_ELEMENT_IDX_DPU0TOP] = { + .name = "DPU0TOP", + .data = &((struct mod_ppu_v0_pd_config) { + .pd_type = MOD_PD_TYPE_DEVICE, + .ppu.reg_base = PPU_DPU0_BASE, + .ppu.irq = PPU_DPU0_IRQ, + .default_power_on = true, + }), + }, + [PPU_V0_ELEMENT_IDX_DPU1TOP] = { + .name = "DPU1TOP", + .data = &((struct mod_ppu_v0_pd_config) { + .pd_type = MOD_PD_TYPE_DEVICE, + .ppu.reg_base = PPU_DPU1_BASE, + .ppu.irq = PPU_DPU1_IRQ, + .default_power_on = true, + }), + }, + [PPU_V0_ELEMENT_IDX_GPUTOP] = { + .name = "GPUTOP", + .data = &((struct mod_ppu_v0_pd_config) { + .pd_type = MOD_PD_TYPE_DEVICE, + .ppu.reg_base = PPU_GPU_BASE, + .ppu.irq = PPU_GPU_IRQ, + .default_power_on = true, + }), + }, + [PPU_V0_ELEMENT_IDX_VPUTOP] = { + .name = "VPUTOP", + .data = &((struct mod_ppu_v0_pd_config) { + .pd_type = MOD_PD_TYPE_DEVICE, + .ppu.reg_base = PPU_VPU_BASE, + .ppu.irq = PPU_VPU_IRQ, + .default_power_on = true, + }), + }, + [PPU_V0_ELEMENT_IDX_SYS0] = { + .name = "SYS0", + .data = &((struct mod_ppu_v0_pd_config) { + .pd_type = MOD_PD_TYPE_SYSTEM, + .ppu.reg_base = PPU_SYS0_BASE, + .ppu.irq = PPU_SYS0_IRQ + }), + }, + [PPU_V0_ELEMENT_IDX_SYS1] = { + .name = "SYS1", + .data = &((struct mod_ppu_v0_pd_config) { + .pd_type = MOD_PD_TYPE_SYSTEM, + .ppu.reg_base = PPU_SYS1_BASE, + .ppu.irq = PPU_SYS1_IRQ + }), + }, + [PPU_V0_ELEMENT_IDX_COUNT] = { }, /* Termination entry */ +}; + + +static const struct fwk_element *sgm775_ppu_v0_get_element_table + (fwk_id_t module_id) +{ + return sgm775_ppu_v0_element_table; +} + +/* + * Power module configuration data + */ +struct fwk_module_config config_ppu_v0 = { + .get_element_table = sgm775_ppu_v0_get_element_table, + .data = NULL, +}; diff --git a/product/sgm775/scp_ramfw/config_ppu_v0.h b/product/sgm775/scp_ramfw/config_ppu_v0.h new file mode 100644 index 00000000..80260051 --- /dev/null +++ b/product/sgm775/scp_ramfw/config_ppu_v0.h @@ -0,0 +1,22 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef CONFIG_PPU_V0_H +#define CONFIG_PPU_V0_H + +enum ppu_v0_element_idx { + PPU_V0_ELEMENT_IDX_DBGTOP, + PPU_V0_ELEMENT_IDX_DPU0TOP, + PPU_V0_ELEMENT_IDX_DPU1TOP, + PPU_V0_ELEMENT_IDX_GPUTOP, + PPU_V0_ELEMENT_IDX_VPUTOP, + PPU_V0_ELEMENT_IDX_SYS0, + PPU_V0_ELEMENT_IDX_SYS1, + PPU_V0_ELEMENT_IDX_COUNT +}; + +#endif /* CONFIG_PPU_V0_H */ diff --git a/product/sgm775/scp_ramfw/config_ppu_v1.c b/product/sgm775/scp_ramfw/config_ppu_v1.c new file mode 100644 index 00000000..3f90ccdb --- /dev/null +++ b/product/sgm775/scp_ramfw/config_ppu_v1.c @@ -0,0 +1,105 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <stddef.h> +#include <fwk_element.h> +#include <fwk_mm.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <mod_power_domain.h> +#include <mod_ppu_v1.h> +#include <sgm775_core.h> +#include <sgm775_irq.h> +#include <sgm775_mmap.h> +#include <config_power_domain.h> + +static const char *core_pd_name_table[SGM775_CORE_PER_CLUSTER_MAX] = { + "CLUS0CORE0", "CLUS0CORE1", "CLUS0CORE2", "CLUS0CORE3", + "CLUS0CORE4", "CLUS0CORE5", "CLUS0CORE6", "CLUS0CORE7", +}; + +static uintptr_t core_pd_ppu_base_table[] = { + PPU_CLUS0CORE0_BASE, PPU_CLUS0CORE1_BASE, PPU_CLUS0CORE2_BASE, + PPU_CLUS0CORE3_BASE, PPU_CLUS0CORE4_BASE, PPU_CLUS0CORE5_BASE, + PPU_CLUS0CORE6_BASE, PPU_CLUS0CORE7_BASE +}; + +static unsigned int core_pd_ppu_irq_table[] = { + PPU_CLUS0CORE0_IRQ, PPU_CLUS0CORE1_IRQ, PPU_CLUS0CORE2_IRQ, + PPU_CLUS0CORE3_IRQ, PPU_CLUS0CORE4_IRQ, PPU_CLUS0CORE5_IRQ, + PPU_CLUS0CORE6_IRQ, PPU_CLUS0CORE7_IRQ +}; + +struct mod_ppu_v1_config sgm775_ppu_v1_notification_config = { + .pd_notification_id = FWK_ID_NOTIFICATION_INIT( + FWK_MODULE_IDX_POWER_DOMAIN, + MOD_PD_NOTIFICATION_IDX_POWER_STATE_TRANSITION), +}; + +static const struct fwk_element *sgm775_ppu_v1_get_element_table + (fwk_id_t module_id) +{ + struct fwk_element *element_table, *element; + struct mod_ppu_v1_pd_config *pd_config_table, *pd_config; + unsigned int core_idx; + + /* + * Allocate element descriptors based on: + * Number of cores + * +1 cluster descriptor + * +1 terminator descriptor + */ + element_table = fwk_mm_calloc(sgm775_core_get_count() + 2, + sizeof(struct fwk_element)); + if (element_table == NULL) + return NULL; + + pd_config_table = fwk_mm_calloc(sgm775_core_get_count() + 1, + sizeof(struct mod_ppu_v1_pd_config)); + if (pd_config_table == NULL) + return NULL; + + for (core_idx = 0; core_idx < sgm775_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->pd_type = MOD_PD_TYPE_CORE; + pd_config->ppu.reg_base = core_pd_ppu_base_table[core_idx]; + pd_config->ppu.irq = core_pd_ppu_irq_table[core_idx]; + pd_config->cluster_id = FWK_ID_ELEMENT(FWK_MODULE_IDX_PPU_V1, + sgm775_core_get_count()); + pd_config->observer_id = FWK_ID_NONE; + } + + element = &element_table[sgm775_core_get_count()]; + pd_config = &pd_config_table[sgm775_core_get_count()]; + + element->name = "CLUS0"; + element->data = pd_config; + + pd_config->pd_type = MOD_PD_TYPE_CLUSTER; + pd_config->ppu.reg_base = PPU_CLUS0_BASE; + pd_config->ppu.irq = PPU_CLUS0_IRQ; + pd_config->observer_id = FWK_ID_NONE; + + sgm775_ppu_v1_notification_config.pd_source_id = FWK_ID_ELEMENT( + FWK_MODULE_IDX_POWER_DOMAIN, + CONFIG_POWER_DOMAIN_SYSTOP_CHILD_COUNT + sgm775_core_get_count()); + + return element_table; +} + +/* + * Power module configuration data + */ +struct fwk_module_config config_ppu_v1 = { + .get_element_table = sgm775_ppu_v1_get_element_table, + .data = &sgm775_ppu_v1_notification_config, +}; diff --git a/product/sgm775/scp_ramfw/config_psu.c b/product/sgm775/scp_ramfw/config_psu.c new file mode 100644 index 00000000..80af3a16 --- /dev/null +++ b/product/sgm775/scp_ramfw/config_psu.c @@ -0,0 +1,58 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <fwk_element.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <mod_mock_psu.h> +#include <mod_psu.h> + +static const struct fwk_element element_table[] = { + { + .name = "CPU_GROUP_LITTLE", + .data = &(const struct mod_psu_device_config) { + .driver_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_MOCK_PSU, 0), + .driver_api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_MOCK_PSU, + MOD_MOCK_PSU_API_IDX_PSU_DRIVER) + }, + }, + { + .name = "CPU_GROUP_BIG", + .data = &(const struct mod_psu_device_config) { + .driver_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_MOCK_PSU, 1), + .driver_api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_MOCK_PSU, + MOD_MOCK_PSU_API_IDX_PSU_DRIVER) + }, + }, + { + .name = "GPU", + .data = &(const struct mod_psu_device_config) { + .driver_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_MOCK_PSU, 2), + .driver_api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_MOCK_PSU, + MOD_MOCK_PSU_API_IDX_PSU_DRIVER) + }, + }, + { + .name = "VPU", + .data = &(const struct mod_psu_device_config) { + .driver_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_MOCK_PSU, 3), + .driver_api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_MOCK_PSU, + MOD_MOCK_PSU_API_IDX_PSU_DRIVER) + }, + }, + { } +}; + +static const struct fwk_element *psu_get_element_table(fwk_id_t module_id) +{ + return element_table; +} + +struct fwk_module_config config_psu = { + .get_element_table = psu_get_element_table, + .data = NULL, +}; diff --git a/product/sgm775/scp_ramfw/config_scmi.c b/product/sgm775/scp_ramfw/config_scmi.c new file mode 100644 index 00000000..a79bdab1 --- /dev/null +++ b/product/sgm775/scp_ramfw/config_scmi.c @@ -0,0 +1,77 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <fwk_element.h> +#include <fwk_id.h> +#include <fwk_macros.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <sgm775_scmi.h> +#include <mod_scmi.h> +#include <internal/scmi.h> +#include <mod_smt.h> + +static const struct fwk_element service_table[] = { + [SGM775_SCMI_SERVICE_IDX_PSCI] = { + .name = "SERVICE0", + .data = &((struct mod_scmi_service_config) { + .transport_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_SMT, + SGM775_SCMI_SERVICE_IDX_PSCI), + .transport_api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_SMT, + MOD_SMT_API_IDX_SCMI_TRANSPORT), + .scmi_agent_id = SCMI_AGENT_ID_PSCI, + }), + }, + [SGM775_SCMI_SERVICE_IDX_OSPM_0] = { + .name = "SERVICE1", + .data = &((struct mod_scmi_service_config) { + .transport_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_SMT, + SGM775_SCMI_SERVICE_IDX_OSPM_0), + .transport_api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_SMT, + MOD_SMT_API_IDX_SCMI_TRANSPORT), + .scmi_agent_id = SCMI_AGENT_ID_OSPM, + }), + }, + [SGM775_SCMI_SERVICE_IDX_OSPM_1] = { + .name = "SERVICE2", + .data = &((struct mod_scmi_service_config) { + .transport_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_SMT, + SGM775_SCMI_SERVICE_IDX_OSPM_1), + .transport_api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_SMT, + MOD_SMT_API_IDX_SCMI_TRANSPORT), + .scmi_agent_id = SCMI_AGENT_ID_OSPM, + }), + }, + [SGM775_SCMI_SERVICE_IDX_COUNT] = {} +}; + +static const struct fwk_element *get_service_table(fwk_id_t module_id) +{ + return service_table; +} + +static const struct mod_scmi_agent agent_table[] = { + [SCMI_AGENT_ID_OSPM] = { + .type = SCMI_AGENT_TYPE_OSPM, + .name = "OSPM", + }, + [SCMI_AGENT_ID_PSCI] = { + .type = SCMI_AGENT_TYPE_PSCI, + .name = "PSCI", + }, +}; + +struct fwk_module_config config_scmi = { + .get_element_table = get_service_table, + .data = &((struct mod_scmi_config) { + .protocol_count_max = 9, + .agent_count = FWK_ARRAY_SIZE(agent_table) - 1, + .agent_table = agent_table, + .vendor_identifier = "arm", + .sub_vendor_identifier = "arm", + }), +}; diff --git a/product/sgm775/scp_ramfw/config_scmi_apcore.c b/product/sgm775/scp_ramfw/config_scmi_apcore.c new file mode 100644 index 00000000..689ddaf7 --- /dev/null +++ b/product/sgm775/scp_ramfw/config_scmi_apcore.c @@ -0,0 +1,31 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <fwk_element.h> +#include <fwk_macros.h> +#include <fwk_module.h> +#include <mod_scmi_apcore.h> +#include <sgm775_core.h> +#include <sgm775_pik.h> + +static const struct mod_scmi_apcore_reset_register_group + reset_reg_group_table[] = { + { + .base_register = + (uintptr_t)&PIK_CLUS0->STATIC_CONFIG[0].RVBARADDR_LW, + .register_count = SGM775_CORE_PER_CLUSTER_MAX, + }, + }; + +const struct fwk_module_config config_scmi_apcore = { + .data = &((struct mod_scmi_apcore_config){ + .reset_register_width = MOD_SCMI_APCORE_REG_WIDTH_64, + .reset_register_group_count = + FWK_ARRAY_SIZE(reset_reg_group_table), + .reset_register_group_table = &reset_reg_group_table[0], + }), +}; diff --git a/product/sgm775/scp_ramfw/config_scmi_clock.c b/product/sgm775/scp_ramfw/config_scmi_clock.c new file mode 100644 index 00000000..254c9733 --- /dev/null +++ b/product/sgm775/scp_ramfw/config_scmi_clock.c @@ -0,0 +1,70 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <stdint.h> +#include <fwk_element.h> +#include <fwk_macros.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <mod_scmi_clock.h> +#include <sgm775_scmi.h> +#include <clock_devices.h> + +static const struct mod_scmi_clock_device agent_device_table_ospm[] = { + { + /* VPU */ + .element_id = + FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_CLOCK, CLOCK_DEV_IDX_VPU), + .permissions = MOD_SCMI_CLOCK_PERM_ATTRIBUTES | + MOD_SCMI_CLOCK_PERM_DESCRIBE_RATES | + MOD_SCMI_CLOCK_PERM_GET_RATE | + MOD_SCMI_CLOCK_PERM_SET_RATE, + }, + { + /* DPU */ + .element_id = + FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_CLOCK, CLOCK_DEV_IDX_DPU), + .permissions = MOD_SCMI_CLOCK_PERM_ATTRIBUTES | + MOD_SCMI_CLOCK_PERM_DESCRIBE_RATES | + MOD_SCMI_CLOCK_PERM_GET_RATE | + MOD_SCMI_CLOCK_PERM_SET_RATE, + }, + { + /* PIXEL_0 */ + .element_id = + FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_CLOCK, CLOCK_DEV_IDX_PIXEL_0), + .permissions = MOD_SCMI_CLOCK_PERM_ATTRIBUTES | + MOD_SCMI_CLOCK_PERM_DESCRIBE_RATES | + MOD_SCMI_CLOCK_PERM_GET_RATE | + MOD_SCMI_CLOCK_PERM_SET_RATE, + }, + { + /* PIXEL_1 */ + .element_id = + FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_CLOCK, CLOCK_DEV_IDX_PIXEL_1), + .permissions = MOD_SCMI_CLOCK_PERM_ATTRIBUTES | + MOD_SCMI_CLOCK_PERM_DESCRIBE_RATES | + MOD_SCMI_CLOCK_PERM_GET_RATE | + MOD_SCMI_CLOCK_PERM_SET_RATE, + }, +}; + +static const struct mod_scmi_clock_agent agent_table[SCMI_AGENT_ID_COUNT] = { + [SCMI_AGENT_ID_PSCI] = { /* No access */ }, + [SCMI_AGENT_ID_OSPM] = { + .device_table = agent_device_table_ospm, + .device_count = FWK_ARRAY_SIZE(agent_device_table_ospm), + }, +}; + +struct fwk_module_config config_scmi_clock = { + .data = &((struct mod_scmi_clock_config) { + .max_pending_transactions = 0, + .agent_table = agent_table, + .agent_count = FWK_ARRAY_SIZE(agent_table), + }), +}; diff --git a/product/sgm775/scp_ramfw/config_scmi_perf.c b/product/sgm775/scp_ramfw/config_scmi_perf.c new file mode 100644 index 00000000..0197ae8d --- /dev/null +++ b/product/sgm775/scp_ramfw/config_scmi_perf.c @@ -0,0 +1,44 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <stdint.h> +#include <fwk_element.h> +#include <fwk_module.h> +#include <config_dvfs.h> +#include <sgm775_scmi.h> +#include <mod_scmi_perf.h> + +static const struct mod_scmi_perf_domain_config domains[] = { + [DVFS_ELEMENT_IDX_LITTLE] = { + .permissions = &(uint32_t[]) { + [SCMI_AGENT_ID_OSPM] = MOD_SCMI_PERF_PERMS_SET_LEVEL | + MOD_SCMI_PERF_PERMS_SET_LIMITS, + [SCMI_AGENT_ID_PSCI] = MOD_SCMI_PERF_PERMS_NONE, + } + }, + [DVFS_ELEMENT_IDX_BIG] = { + .permissions = &(uint32_t[]) { + [SCMI_AGENT_ID_OSPM] = MOD_SCMI_PERF_PERMS_SET_LEVEL | + MOD_SCMI_PERF_PERMS_SET_LIMITS, + [SCMI_AGENT_ID_PSCI] = MOD_SCMI_PERF_PERMS_NONE, + } + }, + [DVFS_ELEMENT_IDX_GPU] = { + .permissions = &(uint32_t[]) { + [SCMI_AGENT_ID_OSPM] = MOD_SCMI_PERF_PERMS_SET_LEVEL | + MOD_SCMI_PERF_PERMS_SET_LIMITS, + [SCMI_AGENT_ID_PSCI] = MOD_SCMI_PERF_PERMS_NONE, + } + }, +}; + +struct fwk_module_config config_scmi_perf = { + .get_element_table = NULL, + .data = &((struct mod_scmi_perf_config) { + .domains = &domains, + }), +}; diff --git a/product/sgm775/scp_ramfw/config_scmi_system_power.c b/product/sgm775/scp_ramfw/config_scmi_system_power.c new file mode 100644 index 00000000..5bbc074c --- /dev/null +++ b/product/sgm775/scp_ramfw/config_scmi_system_power.c @@ -0,0 +1,19 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <stddef.h> +#include <fwk_module.h> +#include <mod_scmi_system_power.h> +#include <mod_system_power.h> + +struct fwk_module_config config_scmi_system_power = { + .get_element_table = NULL, + .data = &((struct mod_scmi_system_power_config) { + .system_view = MOD_SCMI_SYSTEM_VIEW_FULL, + .system_suspend_state = MOD_SYSTEM_POWER_POWER_STATE_SLEEP0 + }), +}; diff --git a/product/sgm775/scp_ramfw/config_sds.c b/product/sgm775/scp_ramfw/config_sds.c new file mode 100644 index 00000000..400e56fc --- /dev/null +++ b/product/sgm775/scp_ramfw/config_sds.c @@ -0,0 +1,64 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <assert.h> +#include <stdint.h> +#include <fwk_element.h> +#include <fwk_macros.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <mod_sds.h> +#include <sgm775_mmap.h> +#include <sgm775_sds.h> +#include <clock_devices.h> + +static const uint32_t feature_flags = SGM775_SDS_FEATURE_FIRMWARE_MASK; +static const uint32_t version_packed = FWK_BUILD_VERSION; + +const struct mod_sds_config sds_module_config = { + .region_base_address = TRUSTED_RAM_BASE, + .region_size = 3520, + .clock_id = FWK_ID_ELEMENT_INIT( + FWK_MODULE_IDX_CLOCK, + CLOCK_DEV_IDX_FCMCLK), +}; + +static const struct fwk_element sds_element_table[] = { + { + .name = "RAM Version", + .data = &((struct mod_sds_structure_desc) { + .id = SGM775_SDS_RAM_VERSION, + .size = SGM775_SDS_RAM_VERSION_SIZE, + .payload = &version_packed, + .finalize = true, + }), + }, + { + .name = "Feature Availability", + .data = &((struct mod_sds_structure_desc) { + .id = SGM775_SDS_FEATURE_AVAILABILITY, + .size = sizeof(feature_flags), + .payload = &feature_flags, + .finalize = false, + }), + }, + { }, /* Termination description. */ +}; + +static const struct fwk_element *sds_get_element_table(fwk_id_t module_id) +{ + static_assert(BUILD_VERSION_MAJOR < UINT8_MAX, "Invalid version size"); + static_assert(BUILD_VERSION_MINOR < UINT8_MAX, "Invalid version size"); + static_assert(BUILD_VERSION_PATCH < UINT16_MAX, "Invalid version size"); + + return sds_element_table; +} + +struct fwk_module_config config_sds = { + .get_element_table = sds_get_element_table, + .data = &sds_module_config, +}; diff --git a/product/sgm775/scp_ramfw/config_sensor.c b/product/sgm775/scp_ramfw/config_sensor.c new file mode 100644 index 00000000..439d3c22 --- /dev/null +++ b/product/sgm775/scp_ramfw/config_sensor.c @@ -0,0 +1,74 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <stddef.h> +#include <fwk_element.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <mod_reg_sensor.h> +#include <mod_sensor.h> +#include <system_mmap.h> + +enum REG_SENSOR_DEVICES { + REG_SENSOR_DEV_SOC_TEMP, + REG_SENSOR_DEV_COUNT, +}; + +/* + * Register Sensor driver config + */ +static const struct fwk_element reg_sensor_element_table[] = { + [REG_SENSOR_DEV_SOC_TEMP] = { + .name = "Soc Temperature", + .data = &((struct mod_reg_sensor_dev_config) { + .reg = (uintptr_t)(SENSOR_SOC_TEMP), + }), + }, + [REG_SENSOR_DEV_COUNT] = {}, +}; + +static const struct fwk_element *get_reg_sensor_element_table(fwk_id_t id) +{ + return reg_sensor_element_table; +} + +struct fwk_module_config config_reg_sensor = { + .get_element_table = get_reg_sensor_element_table, +}; + + +/* + * Sensor module config + */ +static const struct mod_sensor_dev_config soctemp_config = { + .driver_id = FWK_ID_ELEMENT(FWK_MODULE_IDX_REG_SENSOR, + REG_SENSOR_DEV_SOC_TEMP), + .info = &((struct mod_sensor_info) { + .type = MOD_SENSOR_TYPE_DEGREES_C, + .update_interval = 0, + .update_interval_multiplier = 0, + .unit_multiplier = 0, + }), +}; + +static const struct fwk_element sensor_element_table[] = { + [0] = { + .name = "Soc Temperature", + .data = &soctemp_config, + }, + [1] = {}, +}; + +static const struct fwk_element *get_sensor_element_table(fwk_id_t module_id) +{ + return sensor_element_table; +} + +struct fwk_module_config config_sensor = { + .get_element_table = get_sensor_element_table, + .data = NULL, +}; diff --git a/product/sgm775/scp_ramfw/config_smt.c b/product/sgm775/scp_ramfw/config_smt.c new file mode 100644 index 00000000..ca506885 --- /dev/null +++ b/product/sgm775/scp_ramfw/config_smt.c @@ -0,0 +1,78 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <stdint.h> +#include <fwk_element.h> +#include <fwk_id.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <mod_clock.h> +#include <mod_smt.h> +#include <sgm775_core.h> +#include <sgm775_mhu.h> +#include <sgm775_scmi.h> +#include <config_power_domain.h> +#include <clock_devices.h> +#include <software_mmap.h> + +static const struct fwk_element smt_element_table[] = { + [SGM775_SCMI_SERVICE_IDX_PSCI] = { + .name = "PSCI", + .data = &((struct mod_smt_channel_config) { + .type = MOD_SMT_CHANNEL_TYPE_SLAVE, + .policies = MOD_SMT_POLICY_INIT_MAILBOX | MOD_SMT_POLICY_SECURE, + .mailbox_address = (uintptr_t)SCMI_PAYLOAD_S_A2P_BASE, + .mailbox_size = SCMI_PAYLOAD_SIZE, + .driver_id = FWK_ID_SUB_ELEMENT_INIT(FWK_MODULE_IDX_MHU, + SGM775_MHU_DEVICE_IDX_S, 0), + .driver_api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_MHU, 0), + }) + }, + [SGM775_SCMI_SERVICE_IDX_OSPM_0] = { + .name = "OSPM0", + .data = &((struct mod_smt_channel_config) { + .type = MOD_SMT_CHANNEL_TYPE_SLAVE, + .policies = MOD_SMT_POLICY_INIT_MAILBOX, + .mailbox_address = (uintptr_t)SCMI_PAYLOAD0_NS_A2P_BASE, + .mailbox_size = SCMI_PAYLOAD_SIZE, + .driver_id = FWK_ID_SUB_ELEMENT_INIT(FWK_MODULE_IDX_MHU, + SGM775_MHU_DEVICE_IDX_NS_L, 0), + .driver_api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_MHU, 0), + }) + }, + [SGM775_SCMI_SERVICE_IDX_OSPM_1] = { + .name = "OSPM1", + .data = &((struct mod_smt_channel_config) { + .type = MOD_SMT_CHANNEL_TYPE_SLAVE, + .policies = MOD_SMT_POLICY_INIT_MAILBOX, + .mailbox_address = (uintptr_t)SCMI_PAYLOAD1_NS_A2P_BASE, + .mailbox_size = SCMI_PAYLOAD_SIZE, + .driver_id = FWK_ID_SUB_ELEMENT_INIT(FWK_MODULE_IDX_MHU, + SGM775_MHU_DEVICE_IDX_NS_H, 0), + .driver_api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_MHU, 0), + }) + }, + [SGM775_SCMI_SERVICE_IDX_COUNT] = {}, +}; + +static const struct fwk_element *smt_get_element_table(fwk_id_t module_id) +{ + unsigned int idx; + struct mod_smt_channel_config *config; + + for (idx = 0; idx < SGM775_SCMI_SERVICE_IDX_COUNT; idx++) { + config = (struct mod_smt_channel_config *)(smt_element_table[idx].data); + config->pd_source_id = FWK_ID_ELEMENT(FWK_MODULE_IDX_POWER_DOMAIN, + CONFIG_POWER_DOMAIN_SYSTOP_CHILD_COUNT + sgm775_core_get_count()); + } + + return smt_element_table; +} + +struct fwk_module_config config_smt = { + .get_element_table = smt_get_element_table, +}; diff --git a/product/sgm775/scp_ramfw/config_system_pll.c b/product/sgm775/scp_ramfw/config_system_pll.c new file mode 100644 index 00000000..a66543e3 --- /dev/null +++ b/product/sgm775/scp_ramfw/config_system_pll.c @@ -0,0 +1,118 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <fwk_element.h> +#include <fwk_id.h> +#include <fwk_macros.h> +#include <fwk_module.h> +#include <mod_system_pll.h> +#include <sgm775_pik.h> +#include <system_mmap.h> + +static const struct fwk_element system_pll_element_table[] = { + { + .name = "CPU_PLL_0", + .data = &((struct mod_system_pll_dev_config) { + .control_reg = (void *)PLL_CLUS0_0, + .status_reg = (void *)&PIK_SCP->PLL_STATUS1, + .lock_flag_mask = PLL_STATUS1_CPUPLLLOCK(0, 0), + .initial_rate = 1330 * FWK_MHZ, + .min_rate = MOD_SYSTEM_PLL_MIN_RATE, + .max_rate = MOD_SYSTEM_PLL_MAX_RATE, + .min_step = MOD_SYSTEM_PLL_MIN_INTERVAL, + .defer_initialization = false, + }), + }, + { + .name = "CPU_PLL_1", + .data = &((struct mod_system_pll_dev_config) { + .control_reg = (void *)PLL_CLUS0_1, + .status_reg = (void *)&PIK_SCP->PLL_STATUS1, + .lock_flag_mask = PLL_STATUS1_CPUPLLLOCK(0, 1), + .initial_rate = 1750 * FWK_MHZ, + .min_rate = MOD_SYSTEM_PLL_MIN_RATE, + .max_rate = MOD_SYSTEM_PLL_MAX_RATE, + .min_step = MOD_SYSTEM_PLL_MIN_INTERVAL, + .defer_initialization = false, + }), + }, + { + .name = "GPU_PLL", + .data = &((struct mod_system_pll_dev_config) { + .control_reg = (void *)PLL_GPU, + .status_reg = (void *)&PIK_SCP->PLL_STATUS0, + .lock_flag_mask = PLL_STATUS0_GPUPLLLOCK, + .initial_rate = 600 * FWK_MHZ, + .min_rate = MOD_SYSTEM_PLL_MIN_RATE, + .max_rate = MOD_SYSTEM_PLL_MAX_RATE, + .min_step = MOD_SYSTEM_PLL_MIN_INTERVAL, + .defer_initialization = false, + }), + }, + { + .name = "DPU_PLL", + .data = &((struct mod_system_pll_dev_config) { + .control_reg = (void *)PLL_DISPLAY, + .status_reg = (void *)&PIK_SCP->PLL_STATUS0, + .lock_flag_mask = PLL_STATUS0_DISPLAYPLLLOCK, + .initial_rate = 260 * FWK_MHZ, + .min_rate = MOD_SYSTEM_PLL_MIN_RATE, + .max_rate = MOD_SYSTEM_PLL_MAX_RATE, + .min_step = MOD_SYSTEM_PLL_MIN_INTERVAL, + .defer_initialization = false, + }), + }, + { + .name = "VPU_PLL", + .data = &((struct mod_system_pll_dev_config) { + .control_reg = (void *)PLL_VIDEO, + .status_reg = (void *)&PIK_SCP->PLL_STATUS0, + .lock_flag_mask = PLL_STATUS0_VIDEOPLLLOCK, + .initial_rate = 600 * FWK_MHZ, + .min_rate = MOD_SYSTEM_PLL_MIN_RATE, + .max_rate = MOD_SYSTEM_PLL_MAX_RATE, + .min_step = MOD_SYSTEM_PLL_MIN_INTERVAL, + .defer_initialization = false, + }), + }, + { + .name = "PIX0_PLL", + .data = &((struct mod_system_pll_dev_config) { + .control_reg = (void *)PIX0_CONTROL, + .status_reg = NULL, + .initial_rate = 594 * FWK_MHZ, + .min_rate = 12500 * FWK_KHZ, + .max_rate = 594 * FWK_MHZ, + .min_step = 250 * FWK_KHZ, + .defer_initialization = false, + }), + }, + { + .name = "PIX1_PLL", + .data = &((struct mod_system_pll_dev_config) { + .control_reg = (void *)PIX1_CONTROL, + .status_reg = NULL, + .initial_rate = 594 * FWK_MHZ, + .min_rate = 12500 * FWK_KHZ, + .max_rate = 594 * FWK_MHZ, + .min_step = 250 * FWK_KHZ, + .defer_initialization = false, + }), + }, + { }, /* Termination description. */ +}; + +static const struct fwk_element *system_pll_get_element_table + (fwk_id_t module_id) +{ + return system_pll_element_table; +} + +struct fwk_module_config config_system_pll = { + .get_element_table = system_pll_get_element_table, + .data = NULL, +}; diff --git a/product/sgm775/scp_ramfw/config_system_power.c b/product/sgm775/scp_ramfw/config_system_power.c new file mode 100644 index 00000000..37498086 --- /dev/null +++ b/product/sgm775/scp_ramfw/config_system_power.c @@ -0,0 +1,62 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <fwk_id.h> +#include <fwk_macros.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <sgm775_irq.h> +#include <config_ppu_v0.h> +#include <mod_system_power.h> +#include <mod_sgm775_system.h> + +/* + * The DPU/GPU/VPU PPUs in this list are there as a temporary workaround, until + * Linux supports handling these domains on its own. + */ +static const struct mod_system_power_ext_ppu_config ext_ppus[] = { + { + .ppu_id = FWK_ID_ELEMENT_INIT( + FWK_MODULE_IDX_PPU_V0, + PPU_V0_ELEMENT_IDX_DPU0TOP), + .api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_PPU_V0, 0), + }, + { + .ppu_id = FWK_ID_ELEMENT_INIT( + FWK_MODULE_IDX_PPU_V0, + PPU_V0_ELEMENT_IDX_DPU1TOP), + .api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_PPU_V0, 0), + }, + { + .ppu_id = FWK_ID_ELEMENT_INIT( + FWK_MODULE_IDX_PPU_V0, + PPU_V0_ELEMENT_IDX_GPUTOP), + .api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_PPU_V0, 0), + }, + { + .ppu_id = FWK_ID_ELEMENT_INIT( + FWK_MODULE_IDX_PPU_V0, + PPU_V0_ELEMENT_IDX_VPUTOP), + .api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_PPU_V0, 0), + }, +}; + +const struct fwk_module_config config_system_power = { + .data = &((struct mod_system_power_config) { + .soc_wakeup_irq = SOC_WAKEUP0_IRQ, + .ppu_sys0_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_PPU_V0, + PPU_V0_ELEMENT_IDX_SYS0), + .ppu_sys1_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_PPU_V0, + PPU_V0_ELEMENT_IDX_SYS1), + .ppu_sys_api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_PPU_V0, 0), + .ext_ppus = ext_ppus, + .ext_ppus_count = FWK_ARRAY_SIZE(ext_ppus), + .driver_id = FWK_ID_MODULE_INIT(FWK_MODULE_IDX_SGM775_SYSTEM), + .driver_api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_SGM775_SYSTEM, + MOD_SGM775_SYSTEM_API_IDX_SYSTEM_POWER_DRIVER) + }) +}; diff --git a/product/sgm775/scp_ramfw/config_timer.c b/product/sgm775/scp_ramfw/config_timer.c new file mode 100644 index 00000000..a4e1c8ea --- /dev/null +++ b/product/sgm775/scp_ramfw/config_timer.c @@ -0,0 +1,71 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <fwk_id.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <mod_clock.h> +#include <mod_gtimer.h> +#include <mod_timer.h> +#include <sgm775_mmap.h> +#include <sgm775_irq.h> +#include <clock_devices.h> +#include <system_clock.h> + +/* + * Generic timer driver config + */ +static const struct fwk_element gtimer_dev_table[] = { + [0] = { + .name = "REFCLK", + .data = &((struct mod_gtimer_dev_config) { + .hw_timer = REFCLK_CNTBASE0_BASE, + .hw_counter = REFCLK_CNTCTL_BASE, + .control = REFCLK_CNTCONTROL_BASE, + .frequency = CLOCK_RATE_REFCLK, + .clock_id = FWK_ID_ELEMENT_INIT( + FWK_MODULE_IDX_CLOCK, + CLOCK_DEV_IDX_FCMCLK), + }) + }, + [1] = {}, +}; + +static const struct fwk_element *gtimer_get_dev_table(fwk_id_t module_id) +{ + return gtimer_dev_table; +}; + +struct fwk_module_config config_gtimer = { + .get_element_table = gtimer_get_dev_table, +}; + +/* + * Timer HAL config + */ +static const struct mod_timer_dev_config refclk_config = { + .id = FWK_ID_ELEMENT(FWK_MODULE_IDX_GTIMER, 0), + .timer_irq = TIMREFCLK_IRQ, +}; + +static const struct fwk_element timer_dev_table[] = { + [0] = { + .name = "REFCLK", + .data = &refclk_config, + .sub_element_count = 8, /* Number of alarms */ + }, + [1] = {}, +}; + +static const struct fwk_element *timer_get_dev_table(fwk_id_t module_id) +{ + return timer_dev_table; +} + +struct fwk_module_config config_timer = { + .get_element_table = timer_get_dev_table, +}; diff --git a/product/sgm775/scp_ramfw/firmware.mk b/product/sgm775/scp_ramfw/firmware.mk new file mode 100644 index 00000000..ed810a8f --- /dev/null +++ b/product/sgm775/scp_ramfw/firmware.mk @@ -0,0 +1,72 @@ +# +# Arm SCP/MCP Software +# Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +BS_FIRMWARE_CPU := cortex-m3 +BS_FIRMWARE_HAS_MULTITHREADING := yes +BS_FIRMWARE_HAS_NOTIFICATION := yes + +BS_FIRMWARE_MODULES := \ + pl011 \ + log \ + gtimer \ + timer \ + ddr_phy500 \ + dmc500 \ + pik_clock \ + clock \ + system_pll \ + css_clock \ + ppu_v0 \ + ppu_v1 \ + system_power \ + sgm775_system \ + power_domain \ + reg_sensor \ + sensor \ + dvfs \ + psu \ + mock_psu \ + mhu \ + smt \ + scmi \ + scmi_power_domain \ + scmi_clock \ + scmi_perf \ + scmi_sensor \ + scmi_system_power \ + scmi_apcore \ + sds + +BS_FIRMWARE_SOURCES := \ + rtx_config.c \ + sgm775_core.c \ + config_log.c \ + config_timer.c \ + config_ddr_phy500.c \ + config_dmc500.c \ + config_sds.c \ + config_pik_clock.c \ + config_clock.c \ + config_system_pll.c \ + config_css_clock.c \ + config_ppu_v0.c \ + config_ppu_v1.c \ + config_power_domain.c \ + config_sensor.c \ + config_dvfs.c \ + config_psu.c \ + config_mock_psu.c \ + config_mhu.c \ + config_smt.c \ + config_scmi.c \ + config_scmi_clock.c \ + config_scmi_perf.c \ + config_scmi_system_power.c \ + config_scmi_apcore.c \ + config_system_power.c + +include $(BS_DIR)/firmware.mk diff --git a/product/sgm775/scp_ramfw/fmw_memory.ld.S b/product/sgm775/scp_ramfw/fmw_memory.ld.S new file mode 100644 index 00000000..481a8c7a --- /dev/null +++ b/product/sgm775/scp_ramfw/fmw_memory.ld.S @@ -0,0 +1,21 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef FMW_MEMORY_LD_S +#define FMW_MEMORY_LD_S + +#include <system_mmap_scp.h> + +#define FIRMWARE_MEM_MODE FWK_MEM_MODE_SINGLE_REGION + +/* RAM */ +#define FIRMWARE_MEM0_BASE SCP_RAM_BASE +#define FIRMWARE_MEM0_SIZE SCP_RAM_SIZE + +#define FIRMWARE_STACK_SIZE (1 * 1024) + +#endif /* FMW_MEMORY_LD_S */ diff --git a/product/sgm775/scp_ramfw/rtx_config.c b/product/sgm775/scp_ramfw/rtx_config.c new file mode 100644 index 00000000..c3c10bfe --- /dev/null +++ b/product/sgm775/scp_ramfw/rtx_config.c @@ -0,0 +1,79 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <stdbool.h> +#include <stdint.h> +#include <cmsis_compiler.h> +#include <rtx_os.h> +#include <system_clock.h> + +#include <rtx_lib.c> + +/* + * Required by RTX to configure the SysTick timer. + */ +uint32_t SystemCoreClock = CLOCK_RATE_REFCLK; + +/* + * Idle thread + */ +__NO_RETURN void osRtxIdleThread(void *argument) +{ + (void)argument; + + while (true) + __WFI(); +} + +/* + * OS error handler + */ +uint32_t osRtxErrorNotify(uint32_t code, void *object_id) +{ + (void)object_id; + + switch (code) { + case osRtxErrorStackUnderflow: + /* + * Stack underflow detected for thread + * thread_id=object_id + */ + break; + + case osRtxErrorISRQueueOverflow: + /* + * ISR Queue overflow detected when inserting object + * object_id + */ + break; + + case osRtxErrorTimerQueueOverflow: + /* + * User Timer Callback Queue overflow detected for timer + * timer_id=object_id + */ + break; + + case osRtxErrorClibSpace: + /* + * Standard C/C++ library libspace not available: + * increase OS_THREAD_LIBSPACE_NUM + */ + break; + + case osRtxErrorClibMutex: + /* + * Standard C/C++ library mutex initialization failed + */ + break; + + default: + break; + } + + osRtxIdleThread(object_id); +} diff --git a/product/sgm775/scp_romfw/clock_devices.h b/product/sgm775/scp_romfw/clock_devices.h new file mode 100644 index 00000000..31fe8b1c --- /dev/null +++ b/product/sgm775/scp_romfw/clock_devices.h @@ -0,0 +1,28 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef CLOCK_DEVICES_H +#define CLOCK_DEVICES_H + +/*! + * \brief Clock device indexes. + */ +enum clock_dev_idx { + CLOCK_DEV_IDX_BIG, + CLOCK_DEV_IDX_LITTLE, + CLOCK_DEV_IDX_GPU, + CLOCK_DEV_IDX_SYS_ACLKNCI, + CLOCK_DEV_IDX_SYS_FCMCLK, + CLOCK_DEV_IDX_SYS_GICCLK, + CLOCK_DEV_IDX_SYS_PCLKSCP, + CLOCK_DEV_IDX_SYS_SYSPERCLK, + CLOCK_DEV_IDX_PLL_SWTCLKTCK, + CLOCK_DEV_IDX_PLL_SYSTEM, + CLOCK_DEV_IDX_COUNT +}; + +#endif /* CLOCK_DEVICES_H */ diff --git a/product/sgm775/scp_romfw/config_bootloader.c b/product/sgm775/scp_romfw/config_bootloader.c new file mode 100644 index 00000000..39db37e2 --- /dev/null +++ b/product/sgm775/scp_romfw/config_bootloader.c @@ -0,0 +1,26 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <fwk_element.h> +#include <fwk_macros.h> +#include <fwk_module.h> +#include <mod_bootloader.h> +#include <system_mmap.h> +#include <system_mmap_scp.h> +#include <sgm775_sds.h> + +static const struct mod_bootloader_config bootloader_module_config = { + .source_base = TRUSTED_RAM_BASE, + .source_size = 256 * 1024, + .destination_base = SCP_RAM_BASE, + .destination_size = SCP_RAM_SIZE, + .sds_struct_id = SGM775_SDS_BOOTLOADER, +}; + +struct fwk_module_config config_bootloader = { + .data = &bootloader_module_config, +}; diff --git a/product/sgm775/scp_romfw/config_clock.c b/product/sgm775/scp_romfw/config_clock.c new file mode 100644 index 00000000..c6bd916e --- /dev/null +++ b/product/sgm775/scp_romfw/config_clock.c @@ -0,0 +1,126 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <stddef.h> +#include <fwk_element.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <mod_clock.h> +#include <mod_css_clock.h> +#include <mod_system_pll.h> +#include <mod_msys_rom.h> +#include <mod_pik_clock.h> +#include <clock_devices.h> + +static const struct fwk_element clock_dev_desc_table[] = { + [CLOCK_DEV_IDX_BIG] = { + .name = "CPU_GROUP_BIG", + .data = &((struct mod_clock_dev_config) { + .driver_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_CSS_CLOCK, 0), + .api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_CSS_CLOCK, + MOD_CSS_CLOCK_API_TYPE_CLOCK), + .pd_source_id = FWK_ID_MODULE_INIT(FWK_MODULE_IDX_MSYS_ROM), + }), + }, + [CLOCK_DEV_IDX_LITTLE] = { + .name = "CPU_GROUP_LITTLE", + .data = &((struct mod_clock_dev_config) { + .driver_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_CSS_CLOCK, 1), + .api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_CSS_CLOCK, + MOD_CSS_CLOCK_API_TYPE_CLOCK), + .pd_source_id = FWK_ID_MODULE_INIT(FWK_MODULE_IDX_MSYS_ROM), + }), + }, + [CLOCK_DEV_IDX_GPU] = { + .name = "GPU", + .data = &((struct mod_clock_dev_config) { + .driver_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_CSS_CLOCK, 2), + .api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_CSS_CLOCK, + MOD_CSS_CLOCK_API_TYPE_CLOCK), + .pd_source_id = FWK_ID_MODULE_INIT(FWK_MODULE_IDX_MSYS_ROM), + }), + }, + [CLOCK_DEV_IDX_SYS_ACLKNCI] = { + .name = "SYS_ACLKNCI", + .data = &((struct mod_clock_dev_config) { + .driver_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_PIK_CLOCK, 0), + .api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_PIK_CLOCK, + MOD_PIK_CLOCK_API_TYPE_CLOCK), + .pd_source_id = FWK_ID_MODULE_INIT(FWK_MODULE_IDX_MSYS_ROM), + }), + }, + [CLOCK_DEV_IDX_SYS_FCMCLK] = { + .name = "SYS_FCMCLK", + .data = &((struct mod_clock_dev_config) { + .driver_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_PIK_CLOCK, 1), + .api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_PIK_CLOCK, + MOD_PIK_CLOCK_API_TYPE_CLOCK), + .pd_source_id = FWK_ID_MODULE_INIT(FWK_MODULE_IDX_MSYS_ROM), + }), + }, + [CLOCK_DEV_IDX_SYS_GICCLK] = { + .name = "SYS_GICCLK", + .data = &((struct mod_clock_dev_config) { + .driver_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_PIK_CLOCK, 2), + .api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_PIK_CLOCK, + MOD_PIK_CLOCK_API_TYPE_CLOCK), + .pd_source_id = FWK_ID_MODULE_INIT(FWK_MODULE_IDX_MSYS_ROM), + }), + }, + [CLOCK_DEV_IDX_SYS_PCLKSCP] = { + .name = "SYS_PCLKSCP", + .data = &((struct mod_clock_dev_config) { + .driver_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_PIK_CLOCK, 3), + .api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_PIK_CLOCK, + MOD_PIK_CLOCK_API_TYPE_CLOCK), + .pd_source_id = FWK_ID_MODULE_INIT(FWK_MODULE_IDX_MSYS_ROM), + }), + }, + [CLOCK_DEV_IDX_SYS_SYSPERCLK] = { + .name = "SYS_SYSPERCLK", + .data = &((struct mod_clock_dev_config) { + .driver_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_PIK_CLOCK, 4), + .api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_PIK_CLOCK, + MOD_PIK_CLOCK_API_TYPE_CLOCK), + .pd_source_id = FWK_ID_MODULE_INIT(FWK_MODULE_IDX_MSYS_ROM), + }), + }, + [CLOCK_DEV_IDX_PLL_SWTCLKTCK] = { + .name = "PLL_SWTCLKTCK", + .data = &((struct mod_clock_dev_config) { + .driver_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_SYSTEM_PLL, 3), + .api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_SYSTEM_PLL, + MOD_SYSTEM_PLL_API_TYPE_DEFAULT), + .pd_source_id = FWK_ID_MODULE_INIT(FWK_MODULE_IDX_MSYS_ROM), + }), + }, + [CLOCK_DEV_IDX_PLL_SYSTEM] = { + .name = "PLL_SYSTEM", + .data = &((struct mod_clock_dev_config) { + .driver_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_SYSTEM_PLL, 4), + .api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_SYSTEM_PLL, + MOD_SYSTEM_PLL_API_TYPE_DEFAULT), + .pd_source_id = FWK_ID_MODULE_INIT(FWK_MODULE_IDX_MSYS_ROM), + }), + }, + [CLOCK_DEV_IDX_COUNT] = { }, /* Termination description. */ +}; + +static const struct fwk_element *clock_get_dev_desc_table(fwk_id_t module_id) +{ + return clock_dev_desc_table; +} + +const struct fwk_module_config config_clock = { + .get_element_table = clock_get_dev_desc_table, + .data = &((struct mod_clock_config) { + .pd_transition_notification_id = FWK_ID_NOTIFICATION_INIT( + FWK_MODULE_IDX_MSYS_ROM, + MOD_MSYS_ROM_NOTIFICATION_IDX_POWER_SYSTOP), + .pd_pre_transition_notification_id = FWK_ID_NONE_INIT, + }), +}; diff --git a/product/sgm775/scp_romfw/config_css_clock.c b/product/sgm775/scp_romfw/config_css_clock.c new file mode 100644 index 00000000..763cf874 --- /dev/null +++ b/product/sgm775/scp_romfw/config_css_clock.c @@ -0,0 +1,134 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <fwk_element.h> +#include <fwk_macros.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <mod_css_clock.h> +#include <mod_system_pll.h> +#include <mod_pik_clock.h> + +static const struct mod_css_clock_rate rate_table_cpu_group_big[] = { + { + .rate = 1750 * FWK_MHZ, + .pll_rate = 1750 * FWK_MHZ, + .clock_source = MOD_PIK_CLOCK_CLUSCLK_SOURCE_PLL1, + .clock_div_type = MOD_PIK_CLOCK_MSCLOCK_DIVIDER_DIV_EXT, + .clock_div = 1, + .clock_mod_numerator = 1, + .clock_mod_denominator = 1, + }, +}; + +static const struct mod_css_clock_rate rate_table_cpu_group_little[] = { + { + .rate = 1330 * FWK_MHZ, + .pll_rate = 1330 * FWK_MHZ, + .clock_source = MOD_PIK_CLOCK_CLUSCLK_SOURCE_PLL0, + .clock_div_type = MOD_PIK_CLOCK_MSCLOCK_DIVIDER_DIV_EXT, + .clock_div = 1, + .clock_mod_numerator = 1, + .clock_mod_denominator = 1, + }, +}; + +static const struct mod_css_clock_rate rate_table_gpu[] = { + { + .rate = 600 * FWK_MHZ, + .pll_rate = 600 * FWK_MHZ, + .clock_source = MOD_PIK_CLOCK_MSCLOCK_SOURCE_PRIVPLLCLK, + .clock_div_type = MOD_PIK_CLOCK_MSCLOCK_DIVIDER_DIV_EXT, + .clock_div = 1, + }, +}; + +static const fwk_id_t member_table_cpu_group_big[] = { + FWK_ID_ELEMENT(FWK_MODULE_IDX_PIK_CLOCK, 9), + FWK_ID_ELEMENT(FWK_MODULE_IDX_PIK_CLOCK, 10), + FWK_ID_ELEMENT(FWK_MODULE_IDX_PIK_CLOCK, 11), + FWK_ID_ELEMENT(FWK_MODULE_IDX_PIK_CLOCK, 12), +}; + +static const fwk_id_t member_table_cpu_group_little[] = { + FWK_ID_ELEMENT(FWK_MODULE_IDX_PIK_CLOCK, 5), + FWK_ID_ELEMENT(FWK_MODULE_IDX_PIK_CLOCK, 6), + FWK_ID_ELEMENT(FWK_MODULE_IDX_PIK_CLOCK, 7), + FWK_ID_ELEMENT(FWK_MODULE_IDX_PIK_CLOCK, 8), +}; + +static const fwk_id_t member_table_gpu[] = { + FWK_ID_ELEMENT(FWK_MODULE_IDX_PIK_CLOCK, 13), +}; + +static const struct fwk_element css_clock_element_table[] = { + { + .name = "CPU_GROUP_BIG", + .data = &((struct mod_css_clock_dev_config) { + .rate_table = rate_table_cpu_group_big, + .rate_count = sizeof(rate_table_cpu_group_big) / + sizeof(struct mod_css_clock_rate), + .clock_switching_source = MOD_PIK_CLOCK_CLUSCLK_SOURCE_SYSREFCLK, + .pll_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_SYSTEM_PLL, 1), + .pll_api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_SYSTEM_PLL, + MOD_SYSTEM_PLL_API_TYPE_DEFAULT), + .member_table = member_table_cpu_group_big, + .member_count = FWK_ARRAY_SIZE(member_table_cpu_group_big), + .member_api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_PIK_CLOCK, + MOD_PIK_CLOCK_API_TYPE_CSS), + .initial_rate = 1750 * FWK_MHZ, + .modulation_supported = true, + }), + }, + { + .name = "CPU_GROUP_LITTLE", + .data = &((struct mod_css_clock_dev_config) { + .rate_table = rate_table_cpu_group_little, + .rate_count = sizeof(rate_table_cpu_group_little) / + sizeof(struct mod_css_clock_rate), + .clock_switching_source = MOD_PIK_CLOCK_CLUSCLK_SOURCE_SYSREFCLK, + .pll_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_SYSTEM_PLL, 0), + .pll_api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_SYSTEM_PLL, + MOD_SYSTEM_PLL_API_TYPE_DEFAULT), + .member_table = member_table_cpu_group_little, + .member_count = FWK_ARRAY_SIZE(member_table_cpu_group_little), + .member_api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_PIK_CLOCK, + MOD_PIK_CLOCK_API_TYPE_CSS), + .initial_rate = 1330 * FWK_MHZ, + .modulation_supported = true, + }), + }, + { + .name = "GPU", + .data = &((struct mod_css_clock_dev_config) { + .rate_table = rate_table_gpu, + .rate_count = sizeof(rate_table_gpu) / + sizeof(struct mod_css_clock_rate), + .clock_switching_source = MOD_PIK_CLOCK_CLUSCLK_SOURCE_SYSREFCLK, + .pll_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_SYSTEM_PLL, 2), + .pll_api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_SYSTEM_PLL, + MOD_SYSTEM_PLL_API_TYPE_DEFAULT), + .member_table = member_table_gpu, + .member_count = FWK_ARRAY_SIZE(member_table_gpu), + .member_api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_PIK_CLOCK, + MOD_PIK_CLOCK_API_TYPE_CSS), + .initial_rate = 600 * FWK_MHZ, + .modulation_supported = false, + }), + }, + { }, /* Termination description. */ +}; + +static const struct fwk_element *css_clock_get_element_table + (fwk_id_t module_id) +{ + return css_clock_element_table; +} + +struct fwk_module_config config_css_clock = { + .get_element_table = css_clock_get_element_table, +}; diff --git a/product/sgm775/scp_romfw/config_log.c b/product/sgm775/scp_romfw/config_log.c new file mode 100644 index 00000000..9fe5259b --- /dev/null +++ b/product/sgm775/scp_romfw/config_log.c @@ -0,0 +1,63 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <fwk_banner.h> +#include <fwk_macros.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <mod_clock.h> +#include <mod_log.h> +#include <mod_pl011.h> +#include <system_mmap.h> +#include <clock_devices.h> + +/* + * PL011 module + */ +static const struct fwk_element pl011_element_desc_table[] = { + [0] = { + .name = "board-uart1", + .data = &((struct mod_pl011_device_config) { + .reg_base = BOARD_UART1_BASE, + .baud_rate_bps = 115200, + .clock_rate_hz = 24 * FWK_MHZ, + .clock_id = FWK_ID_ELEMENT_INIT( + FWK_MODULE_IDX_CLOCK, + CLOCK_DEV_IDX_SYS_FCMCLK), + }), + }, + [1] = {}, +}; + +static const struct fwk_element *get_pl011_table(fwk_id_t module_id) +{ + return pl011_element_desc_table; +} + +struct fwk_module_config config_pl011 = { + .get_element_table = get_pl011_table, +}; + +/* + * Log module + */ +static const struct mod_log_config log_data = { + .device_id = FWK_ID_ELEMENT(FWK_MODULE_IDX_PL011, 0), + .api_id = FWK_ID_API(FWK_MODULE_IDX_PL011, 0), + .log_groups = MOD_LOG_GROUP_ERROR | + MOD_LOG_GROUP_INFO | + MOD_LOG_GROUP_WARNING | + MOD_LOG_GROUP_DEBUG, + .banner = FWK_BANNER_SCP + FWK_BANNER_ROM_FIRMWARE + BUILD_VERSION_DESCRIBE_STRING "\n", +}; + +struct fwk_module_config config_log = { + .get_element_table = NULL, + .data = &log_data, +}; diff --git a/product/sgm775/scp_romfw/config_msys_rom.c b/product/sgm775/scp_romfw/config_msys_rom.c new file mode 100644 index 00000000..12bb5b35 --- /dev/null +++ b/product/sgm775/scp_romfw/config_msys_rom.c @@ -0,0 +1,24 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <fwk_id.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <mod_msys_rom.h> +#include <software_mmap.h> +#include <sgm775_mmap_scp.h> +#include <sgm775_core.h> + +const struct fwk_module_config config_msys_rom = { + .data = &((struct msys_rom_config) { + .ap_context_base = AP_CONTEXT_BASE, + .ap_context_size = AP_CONTEXT_SIZE, + .ramfw_base = SCP_RAM_BASE, + .id_primary_cluster = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_PPU_V1, 0), + .id_primary_core = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_PPU_V1, 1), + }) +}; diff --git a/product/sgm775/scp_romfw/config_pik_clock.c b/product/sgm775/scp_romfw/config_pik_clock.c new file mode 100644 index 00000000..8059865f --- /dev/null +++ b/product/sgm775/scp_romfw/config_pik_clock.c @@ -0,0 +1,306 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <fwk_element.h> +#include <fwk_macros.h> +#include <fwk_module.h> +#include <mod_pik_clock.h> +#include <sgm775_pik.h> +#include <system_clock.h> + +/* + * Rate lookup tables. + */ + +static const struct mod_pik_clock_rate rate_table_sys_fcmclk[] = { + { + .rate = CLOCK_RATE_SYSPLLCLK, + .source = MOD_PIK_CLOCK_MSCLOCK_SOURCE_SYSPLLCLK, + .divider_reg = MOD_PIK_CLOCK_MSCLOCK_DIVIDER_DIV_SYS, + .divider = 1, + }, +}; + +static const struct mod_pik_clock_rate rate_table_sys_aclknci[] = { + { + .rate = 666 * FWK_MHZ, + .source = MOD_PIK_CLOCK_MSCLOCK_SOURCE_SYSPLLCLK, + .divider_reg = MOD_PIK_CLOCK_MSCLOCK_DIVIDER_DIV_SYS, + .divider = DIV_FROM_CLOCK(CLOCK_RATE_SYSPLLCLK, 666 * FWK_MHZ), + }, +}; + +static const struct mod_pik_clock_rate rate_table_sys_gicclk[] = { + { + .rate = 666 * FWK_MHZ, + .source = MOD_PIK_CLOCK_MSCLOCK_SOURCE_SYSPLLCLK, + .divider_reg = MOD_PIK_CLOCK_MSCLOCK_DIVIDER_DIV_SYS, + .divider = DIV_FROM_CLOCK(CLOCK_RATE_SYSPLLCLK, 666 * FWK_MHZ), + }, +}; + +static const struct mod_pik_clock_rate rate_table_sys_pclkscp[] = { + { + .rate = 125 * FWK_MHZ, + .source = MOD_PIK_CLOCK_MSCLOCK_SOURCE_SYSPLLCLK, + .divider_reg = MOD_PIK_CLOCK_MSCLOCK_DIVIDER_DIV_SYS, + .divider = DIV_FROM_CLOCK(CLOCK_RATE_SYSPLLCLK, 125 * FWK_MHZ), + }, +}; + +static const struct mod_pik_clock_rate rate_table_sys_sysperclk[] = { + { + .rate = 125 * FWK_MHZ, + .source = MOD_PIK_CLOCK_MSCLOCK_SOURCE_SYSPLLCLK, + .divider_reg = MOD_PIK_CLOCK_MSCLOCK_DIVIDER_DIV_SYS, + .divider = DIV_FROM_CLOCK(CLOCK_RATE_SYSPLLCLK, 125 * FWK_MHZ), + }, +}; + +static const struct mod_pik_clock_rate rate_table_cpu_a55[] = { + { + .rate = 1330 * FWK_MHZ, + .source = MOD_PIK_CLOCK_CLUSCLK_SOURCE_PLL0, + .divider_reg = MOD_PIK_CLOCK_MSCLOCK_DIVIDER_DIV_EXT, + .divider = 1, /* Rate adjusted via CPU PLL */ + }, +}; + +static const struct mod_pik_clock_rate rate_table_cpu_a75[] = { + { + .rate = 1750 * FWK_MHZ, + .source = MOD_PIK_CLOCK_CLUSCLK_SOURCE_PLL1, + .divider_reg = MOD_PIK_CLOCK_MSCLOCK_DIVIDER_DIV_EXT, + .divider = 1, /* Rate adjusted via CPU PLL */ + }, +}; + +static const struct mod_pik_clock_rate rate_table_gpu[] = { + { + .rate = 600 * FWK_MHZ, + .source = MOD_PIK_CLOCK_MSCLOCK_SOURCE_PRIVPLLCLK, + .divider_reg = MOD_PIK_CLOCK_MSCLOCK_DIVIDER_DIV_EXT, + .divider = 1, /* Rate adjusted via GPU PLL */ + }, +}; + +static const struct fwk_element pik_clock_element_table[] = { + /* + * System Clocks + */ + { + .name = "SYS_ACLKNCI", + .data = &((struct mod_pik_clock_dev_config) { + .type = MOD_PIK_CLOCK_TYPE_MULTI_SOURCE, + .is_group_member = false, + .control_reg = &PIK_SYSTEM->ACLKNCI_CTRL, + .divsys_reg = &PIK_SYSTEM->ACLKNCI_DIV1, + .rate_table = rate_table_sys_aclknci, + .rate_count = FWK_ARRAY_SIZE(rate_table_sys_aclknci), + .initial_rate = 666 * FWK_MHZ, + .defer_initialization = true, + }), + }, + { + .name = "SYS_FCMCLK", + .data = &((struct mod_pik_clock_dev_config) { + .type = MOD_PIK_CLOCK_TYPE_MULTI_SOURCE, + .is_group_member = false, + .control_reg = &PIK_SYSTEM->FCMCLK_CTRL, + .divsys_reg = &PIK_SYSTEM->FCMCLK_DIV1, + .rate_table = rate_table_sys_fcmclk, + .rate_count = FWK_ARRAY_SIZE(rate_table_sys_fcmclk), + .initial_rate = CLOCK_RATE_SYSPLLCLK, + .defer_initialization = true, + }), + }, + { + .name = "SYS_GICCLK", + .data = &((struct mod_pik_clock_dev_config) { + .type = MOD_PIK_CLOCK_TYPE_MULTI_SOURCE, + .is_group_member = false, + .control_reg = &PIK_SYSTEM->GICCLK_CTRL, + .divsys_reg = &PIK_SYSTEM->GICCLK_DIV1, + .rate_table = rate_table_sys_gicclk, + .rate_count = FWK_ARRAY_SIZE(rate_table_sys_gicclk), + .initial_rate = 666 * FWK_MHZ, + .defer_initialization = true, + }), + }, + { + .name = "SYS_PCLKSCP", + .data = &((struct mod_pik_clock_dev_config) { + .type = MOD_PIK_CLOCK_TYPE_MULTI_SOURCE, + .is_group_member = false, + .control_reg = &PIK_SYSTEM->PCLKSCP_CTRL, + .divsys_reg = &PIK_SYSTEM->PCLKSCP_DIV1, + .rate_table = rate_table_sys_pclkscp, + .rate_count = FWK_ARRAY_SIZE(rate_table_sys_pclkscp), + .initial_rate = 125 * FWK_MHZ, + .defer_initialization = true, + }), + }, + { + .name = "SYS_SYSPERCLK", + .data = &((struct mod_pik_clock_dev_config) { + .type = MOD_PIK_CLOCK_TYPE_MULTI_SOURCE, + .is_group_member = false, + .control_reg = &PIK_SYSTEM->SYSPERCLK_CTRL, + .divsys_reg = &PIK_SYSTEM->SYSPERCLK_DIV1, + .rate_table = rate_table_sys_sysperclk, + .rate_count = FWK_ARRAY_SIZE(rate_table_sys_sysperclk), + .initial_rate = 125 * FWK_MHZ, + .defer_initialization = true, + }), + }, + /* + * A55 CPUS + */ + { + .name = "CLUS0_CPU0", + .data = &((struct mod_pik_clock_dev_config) { + .type = MOD_PIK_CLOCK_TYPE_CLUSTER, + .is_group_member = true, + .control_reg = &PIK_CLUS0->AP_CLK_CTRL[0].CORECLK_CTRL, + .divext_reg = &PIK_CLUS0->AP_CLK_CTRL[0].CORECLK_DIV, + .modulator_reg = &PIK_CLUS0->AP_CLK_CTRL[0].CORECLK_MOD, + .rate_table = rate_table_cpu_a55, + .rate_count = FWK_ARRAY_SIZE(rate_table_cpu_a55), + .initial_rate = 1330 * FWK_MHZ, + .defer_initialization = true, + }), + }, + { + .name = "CLUS0_CPU1", + .data = &((struct mod_pik_clock_dev_config) { + .type = MOD_PIK_CLOCK_TYPE_CLUSTER, + .is_group_member = true, + .control_reg = &PIK_CLUS0->AP_CLK_CTRL[1].CORECLK_CTRL, + .divext_reg = &PIK_CLUS0->AP_CLK_CTRL[1].CORECLK_DIV, + .modulator_reg = &PIK_CLUS0->AP_CLK_CTRL[1].CORECLK_MOD, + .rate_table = rate_table_cpu_a55, + .rate_count = FWK_ARRAY_SIZE(rate_table_cpu_a55), + .initial_rate = 1330 * FWK_MHZ, + .defer_initialization = true, + }), + }, + { + .name = "CLUS0_CPU2", + .data = &((struct mod_pik_clock_dev_config) { + .type = MOD_PIK_CLOCK_TYPE_CLUSTER, + .is_group_member = true, + .control_reg = &PIK_CLUS0->AP_CLK_CTRL[2].CORECLK_CTRL, + .divext_reg = &PIK_CLUS0->AP_CLK_CTRL[2].CORECLK_DIV, + .modulator_reg = &PIK_CLUS0->AP_CLK_CTRL[2].CORECLK_MOD, + .rate_table = rate_table_cpu_a55, + .rate_count = FWK_ARRAY_SIZE(rate_table_cpu_a55), + .initial_rate = 1330 * FWK_MHZ, + .defer_initialization = true, + }), + }, + { + .name = "CLUS0_CPU3", + .data = &((struct mod_pik_clock_dev_config) { + .type = MOD_PIK_CLOCK_TYPE_CLUSTER, + .is_group_member = true, + .control_reg = &PIK_CLUS0->AP_CLK_CTRL[3].CORECLK_CTRL, + .divext_reg = &PIK_CLUS0->AP_CLK_CTRL[3].CORECLK_DIV, + .modulator_reg = &PIK_CLUS0->AP_CLK_CTRL[3].CORECLK_MOD, + .rate_table = rate_table_cpu_a55, + .rate_count = FWK_ARRAY_SIZE(rate_table_cpu_a55), + .initial_rate = 1330 * FWK_MHZ, + .defer_initialization = true, + }), + }, + /* + * A75 CPUS + */ + { + .name = "CLUS0_CPU4", + .data = &((struct mod_pik_clock_dev_config) { + .type = MOD_PIK_CLOCK_TYPE_CLUSTER, + .is_group_member = true, + .control_reg = &PIK_CLUS0->AP_CLK_CTRL[4].CORECLK_CTRL, + .divext_reg = &PIK_CLUS0->AP_CLK_CTRL[4].CORECLK_DIV, + .modulator_reg = &PIK_CLUS0->AP_CLK_CTRL[4].CORECLK_MOD, + .rate_table = rate_table_cpu_a75, + .rate_count = FWK_ARRAY_SIZE(rate_table_cpu_a75), + .initial_rate = 1750 * FWK_MHZ, + .defer_initialization = true, + }), + }, + { + .name = "CLUS0_CPU5", + .data = &((struct mod_pik_clock_dev_config) { + .type = MOD_PIK_CLOCK_TYPE_CLUSTER, + .is_group_member = true, + .control_reg = &PIK_CLUS0->AP_CLK_CTRL[5].CORECLK_CTRL, + .divext_reg = &PIK_CLUS0->AP_CLK_CTRL[5].CORECLK_DIV, + .modulator_reg = &PIK_CLUS0->AP_CLK_CTRL[5].CORECLK_MOD, + .rate_table = rate_table_cpu_a75, + .rate_count = FWK_ARRAY_SIZE(rate_table_cpu_a75), + .initial_rate = 1750 * FWK_MHZ, + .defer_initialization = true, + }), + }, + { + .name = "CLUS0_CPU6", + .data = &((struct mod_pik_clock_dev_config) { + .type = MOD_PIK_CLOCK_TYPE_CLUSTER, + .is_group_member = true, + .control_reg = &PIK_CLUS0->AP_CLK_CTRL[6].CORECLK_CTRL, + .divext_reg = &PIK_CLUS0->AP_CLK_CTRL[6].CORECLK_DIV, + .modulator_reg = &PIK_CLUS0->AP_CLK_CTRL[6].CORECLK_MOD, + .rate_table = rate_table_cpu_a75, + .rate_count = FWK_ARRAY_SIZE(rate_table_cpu_a75), + .initial_rate = 1750 * FWK_MHZ, + .defer_initialization = true, + }), + }, + { + .name = "CLUS0_CPU7", + .data = &((struct mod_pik_clock_dev_config) { + .type = MOD_PIK_CLOCK_TYPE_CLUSTER, + .is_group_member = true, + .control_reg = &PIK_CLUS0->AP_CLK_CTRL[7].CORECLK_CTRL, + .divext_reg = &PIK_CLUS0->AP_CLK_CTRL[7].CORECLK_DIV, + .modulator_reg = &PIK_CLUS0->AP_CLK_CTRL[7].CORECLK_MOD, + .rate_table = rate_table_cpu_a75, + .rate_count = FWK_ARRAY_SIZE(rate_table_cpu_a75), + .initial_rate = 1750 * FWK_MHZ, + .defer_initialization = true, + }), + }, + /* + * GPU + */ + { + .name = "GPU", + .data = &((struct mod_pik_clock_dev_config) { + .type = MOD_PIK_CLOCK_TYPE_MULTI_SOURCE, + .is_group_member = true, + .control_reg = &PIK_GPU->GPUCLK_CTRL, + .divsys_reg = &PIK_GPU->GPUCLK_DIV1, + .divext_reg = &PIK_GPU->GPUCLK_DIV2, + .rate_table = rate_table_gpu, + .rate_count = FWK_ARRAY_SIZE(rate_table_gpu), + .initial_rate = 600 * FWK_MHZ, + .defer_initialization = true, + }), + }, + { }, /* Termination description. */ +}; + +static const struct fwk_element *pik_clock_get_element_table + (fwk_id_t module_id) +{ + return pik_clock_element_table; +} + +struct fwk_module_config config_pik_clock = { + .get_element_table = pik_clock_get_element_table, +}; diff --git a/product/sgm775/scp_romfw/config_ppu_v0.c b/product/sgm775/scp_romfw/config_ppu_v0.c new file mode 100644 index 00000000..59051a41 --- /dev/null +++ b/product/sgm775/scp_romfw/config_ppu_v0.c @@ -0,0 +1,48 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <fwk_element.h> +#include <fwk_module.h> +#include <mod_ppu_v0.h> +#include <sgm775_irq.h> +#include <sgm775_mmap.h> + +static struct fwk_element sgm775_ppu_v0_element_table[] = { + { + .name = "SYS0", + .data = &((struct mod_ppu_v0_pd_config) { + .pd_type = MOD_PD_TYPE_SYSTEM, + .ppu.reg_base = PPU_SYS0_BASE, + .ppu.irq = PPU_SYS0_IRQ, + .default_power_on = true, + }), + }, + { + .name = "SYS1", + .data = &((struct mod_ppu_v0_pd_config) { + .pd_type = MOD_PD_TYPE_SYSTEM, + .ppu.reg_base = PPU_SYS1_BASE, + .ppu.irq = PPU_SYS1_IRQ, + .default_power_on = true, + }), + }, + {}, /* Termination entry */ +}; + + +static const struct fwk_element *sgm775_ppu_v0_get_element_table( + fwk_id_t module_id) +{ + return sgm775_ppu_v0_element_table; +} + +/* + * Power module configuration data + */ +struct fwk_module_config config_ppu_v0 = { + .get_element_table = sgm775_ppu_v0_get_element_table, +}; diff --git a/product/sgm775/scp_romfw/config_ppu_v1.c b/product/sgm775/scp_romfw/config_ppu_v1.c new file mode 100644 index 00000000..c1e1e954 --- /dev/null +++ b/product/sgm775/scp_romfw/config_ppu_v1.c @@ -0,0 +1,59 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <stddef.h> +#include <fwk_element.h> +#include <fwk_interrupt.h> +#include <fwk_mm.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <mod_msys_rom.h> +#include <mod_ppu_v1.h> +#include <sgm775_irq.h> +#include <sgm775_mmap.h> + +static struct fwk_element sgm775_ppu_v1_element_table[] = { + { + .name = "CLUS0", + .data = &((struct mod_ppu_v1_pd_config) { + .pd_type = MOD_PD_TYPE_CLUSTER, + .ppu.reg_base = PPU_CLUS0_BASE, + .ppu.irq = FWK_INTERRUPT_NONE, + .observer_id = FWK_ID_NONE_INIT, + }), + }, + { + .name = "CORE0", + .data = &((struct mod_ppu_v1_pd_config) { + .pd_type = MOD_PD_TYPE_CORE, + .ppu.reg_base = PPU_CLUS0CORE0_BASE, + .ppu.irq = FWK_INTERRUPT_NONE, + .cluster_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_PPU_V1, 0), + .observer_id = FWK_ID_NONE_INIT, + }), + }, + {}, /* Termination entry */ +}; + +static const struct fwk_element *sgm775_ppu_v1_get_element_table( + fwk_id_t module_id) +{ + return sgm775_ppu_v1_element_table; +} + +/* + * Power module configuration data + */ +struct fwk_module_config config_ppu_v1 = { + .get_element_table = sgm775_ppu_v1_get_element_table, + .data = &(struct mod_ppu_v1_config) { + .pd_notification_id = FWK_ID_NOTIFICATION_INIT( + FWK_MODULE_IDX_MSYS_ROM, + MOD_MSYS_ROM_NOTIFICATION_IDX_POWER_SYSTOP), + .pd_source_id = FWK_ID_MODULE_INIT(FWK_MODULE_IDX_MSYS_ROM), + }, +}; diff --git a/product/sgm775/scp_romfw/config_sds.c b/product/sgm775/scp_romfw/config_sds.c new file mode 100644 index 00000000..fb8f5e30 --- /dev/null +++ b/product/sgm775/scp_romfw/config_sds.c @@ -0,0 +1,122 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <assert.h> +#include <stdint.h> +#include <fwk_element.h> +#include <fwk_macros.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <mod_sds.h> +#include <sgm775_mmap.h> +#include <sgm775_sds.h> +#include <sgm775_ssc.h> +#include <sgm775_pik.h> +#include <system_mmap.h> +#include <clock_devices.h> + +static const struct mod_sds_config sds_module_config = { + .region_base_address = TRUSTED_RAM_BASE, + .region_size = 3520, + .clock_id = FWK_ID_ELEMENT_INIT( + FWK_MODULE_IDX_CLOCK, + CLOCK_DEV_IDX_SYS_FCMCLK), +}; +static const uint32_t version_packed = FWK_BUILD_VERSION; +static struct sgm775_sds_platid platid; + +static const struct fwk_element sds_element_table[] = { + { + .name = "CPU Info", + .data = &((struct mod_sds_structure_desc) { + .id = SGM775_SDS_CPU_INFO, + .size = SGM775_SDS_CPU_INFO_SIZE, + .finalize = true, + }), + }, + { + .name = "ROM Version", + .data = &((struct mod_sds_structure_desc) { + .id = SGM775_SDS_ROM_VERSION, + .size = SGM775_SDS_ROM_VERSION_SIZE, + .payload = &version_packed, + .finalize = true, + }), + }, + { + .name = "Platform ID", + .data = &((struct mod_sds_structure_desc) { + .id = SGM775_SDS_PLATFORM_ID, + .size = SGM775_SDS_PLATFORM_ID_SIZE, + .payload = &platid, + .finalize = true, + }), + }, + { + .name = "Reset Syndrome", + .data = &((struct mod_sds_structure_desc) { + .id = SGM775_SDS_RESET_SYNDROME, + .size = SGM775_SDS_RESET_SYNDROME_SIZE, + .payload = (void *)(&PIK_SCP->RESET_SYNDROME), + .finalize = true, + }), + }, + { + .name = "Bootloader", + .data = &((struct mod_sds_structure_desc) { + .id = SGM775_SDS_BOOTLOADER, + .size = SGM775_SDS_BOOTLOADER_SIZE, + .finalize = true, + }), + }, + { + .name = "Features", + .data = &((struct mod_sds_structure_desc) { + .id = SGM775_SDS_FEATURE_AVAILABILITY, + .size = SGM775_SDS_FEATURE_AVAILABILITY_SIZE, + .finalize = true, + }), + }, +#ifdef MODE_DEBUG + { + .name = "Boot Counters", + .data = &((struct mod_sds_structure_desc) { + .id = SGM775_SDS_CPU_BOOTCTR, + .size = SGM775_SDS_CPU_BOOTCTR_SIZE, + .finalize = true, + }), + }, + { + .name = "CPU Flags", + .data = &((struct mod_sds_structure_desc) { + .id = SGM775_SDS_CPU_FLAGS, + .size = SGM775_SDS_CPU_FLAGS_SIZE, + .finalize = true, + }), + }, +#endif + {}, /* Termination description. */ +}; + +static const struct fwk_element *sds_get_element_table(fwk_id_t module_id) +{ + struct ssc_reg *ssc_regs = ((struct ssc_reg *)(SSC_BASE)); + + static_assert(BUILD_VERSION_MAJOR < UINT8_MAX, "Invalid version size"); + static_assert(BUILD_VERSION_MINOR < UINT8_MAX, "Invalid version size"); + static_assert(BUILD_VERSION_PATCH < UINT16_MAX, "Invalid version size"); + + platid.platform_identifier = ssc_regs->SSC_VERSION; + platid.platform_type_identifier = *((uint32_t*)PLATFORM_ID); + + return sds_element_table; +} + +struct fwk_module_config config_sds = { + .get_element_table = sds_get_element_table, + .data = &sds_module_config, +}; diff --git a/product/sgm775/scp_romfw/config_system_pll.c b/product/sgm775/scp_romfw/config_system_pll.c new file mode 100644 index 00000000..a709b140 --- /dev/null +++ b/product/sgm775/scp_romfw/config_system_pll.c @@ -0,0 +1,91 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2015-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <fwk_element.h> +#include <fwk_macros.h> +#include <fwk_module.h> +#include <mod_system_pll.h> +#include <sgm775_pik.h> +#include <system_mmap.h> + +static const struct fwk_element system_pll_element_table[] = { + { + .name = "CPU_PLL_0", + .data = &((struct mod_system_pll_dev_config) { + .control_reg = (void *)PLL_CLUS0_0, + .status_reg = (void *)&PIK_SCP->PLL_STATUS1, + .lock_flag_mask = PLL_STATUS1_CPUPLLLOCK(0, 0), + .initial_rate = 1330 * FWK_MHZ, + .min_rate = MOD_SYSTEM_PLL_MIN_RATE, + .max_rate = MOD_SYSTEM_PLL_MAX_RATE, + .min_step = MOD_SYSTEM_PLL_MIN_INTERVAL, + .defer_initialization = false, + }), + }, + { + .name = "CPU_PLL_1", + .data = &((struct mod_system_pll_dev_config) { + .control_reg = (void *)PLL_CLUS0_1, + .status_reg = (void *)&PIK_SCP->PLL_STATUS1, + .lock_flag_mask = PLL_STATUS1_CPUPLLLOCK(0, 1), + .initial_rate = 1750 * FWK_MHZ, + .min_rate = MOD_SYSTEM_PLL_MIN_RATE, + .max_rate = MOD_SYSTEM_PLL_MAX_RATE, + .min_step = MOD_SYSTEM_PLL_MIN_INTERVAL, + .defer_initialization = false, + }), + }, + { + .name = "GPU_PLL", + .data = &((struct mod_system_pll_dev_config) { + .control_reg = (void *)PLL_GPU, + .status_reg = (void *)&PIK_SCP->PLL_STATUS0, + .lock_flag_mask = PLL_STATUS0_GPUPLLLOCK, + .initial_rate = 100 * FWK_MHZ, + .min_rate = MOD_SYSTEM_PLL_MIN_RATE, + .max_rate = MOD_SYSTEM_PLL_MAX_RATE, + .min_step = MOD_SYSTEM_PLL_MIN_INTERVAL, + .defer_initialization = false, + }), + }, + { + .name = "SWTCLKTCK_PLL", + .data = &((struct mod_system_pll_dev_config) { + .control_reg = (void *)SWCLKTCK_CONTROL, + .status_reg = NULL, + .initial_rate = 100 * FWK_MHZ, + .min_rate = MOD_SYSTEM_PLL_MIN_RATE, + .max_rate = MOD_SYSTEM_PLL_MAX_RATE, + .min_step = MOD_SYSTEM_PLL_MIN_INTERVAL, + .defer_initialization = false, + }), + }, + { + .name = "SYS_PLL", + .data = &((struct mod_system_pll_dev_config) { + .control_reg = (void *)PLL_SYSTEM, + .status_reg = (void *)&PIK_SCP->PLL_STATUS0, + .lock_flag_mask = PLL_STATUS0_SYSPLLLOCK, + .initial_rate = 2000 * FWK_MHZ, + .min_rate = MOD_SYSTEM_PLL_MIN_RATE, + .max_rate = MOD_SYSTEM_PLL_MAX_RATE, + .min_step = MOD_SYSTEM_PLL_MIN_INTERVAL, + .defer_initialization = false, + }), + }, + { }, /* Termination description. */ +}; + +static const struct fwk_element *system_pll_get_element_table + (fwk_id_t module_id) +{ + return system_pll_element_table; +} + +struct fwk_module_config config_system_pll = { + .get_element_table = system_pll_get_element_table, +}; diff --git a/product/sgm775/scp_romfw/config_timer.c b/product/sgm775/scp_romfw/config_timer.c new file mode 100644 index 00000000..e58bbf1a --- /dev/null +++ b/product/sgm775/scp_romfw/config_timer.c @@ -0,0 +1,68 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <fwk_id.h> +#include <fwk_module.h> +#include <fwk_module_idx.h> +#include <mod_clock.h> +#include <mod_gtimer.h> +#include <mod_timer.h> +#include <sgm775_mmap.h> +#include <clock_devices.h> +#include <system_clock.h> + +/* + * Generic timer driver config + */ +static const struct fwk_element gtimer_dev_table[] = { + [0] = { + .name = "REFCLK", + .data = &((struct mod_gtimer_dev_config) { + .hw_timer = REFCLK_CNTBASE0_BASE, + .hw_counter = REFCLK_CNTCTL_BASE, + .control = REFCLK_CNTCONTROL_BASE, + .frequency = CLOCK_RATE_REFCLK, + .clock_id = FWK_ID_ELEMENT_INIT( + FWK_MODULE_IDX_CLOCK, + CLOCK_DEV_IDX_SYS_FCMCLK), + }) + }, + [1] = {}, +}; + +static const struct fwk_element *gtimer_get_dev_table(fwk_id_t module_id) +{ + return gtimer_dev_table; +}; + +struct fwk_module_config config_gtimer = { + .get_element_table = gtimer_get_dev_table, +}; + +/* + * Timer HAL config + */ +static const struct mod_timer_dev_config refclk_config = { + .id = FWK_ID_ELEMENT(FWK_MODULE_IDX_GTIMER, 0), +}; + +static const struct fwk_element timer_dev_table[] = { + [0] = { + .name = "REFCLK", + .data = &refclk_config, + }, + [1] = {}, +}; + +static const struct fwk_element *timer_get_dev_table(fwk_id_t module_id) +{ + return timer_dev_table; +} + +struct fwk_module_config config_timer = { + .get_element_table = timer_get_dev_table, +}; diff --git a/product/sgm775/scp_romfw/firmware.mk b/product/sgm775/scp_romfw/firmware.mk new file mode 100644 index 00000000..1250577e --- /dev/null +++ b/product/sgm775/scp_romfw/firmware.mk @@ -0,0 +1,38 @@ +# +# Arm SCP/MCP Software +# Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +BS_FIRMWARE_CPU := cortex-m3 +BS_FIRMWARE_HAS_MULTITHREADING := no +BS_FIRMWARE_HAS_NOTIFICATION := yes +BS_FIRMWARE_MODULE_HEADERS_ONLY := timer \ + power_domain +BS_FIRMWARE_MODULES := ppu_v0 \ + ppu_v1 \ + pl011 \ + log \ + gtimer \ + msys_rom \ + bootloader \ + system_pll \ + css_clock \ + pik_clock \ + clock \ + sds +BS_FIRMWARE_SOURCES := config_log.c \ + config_timer.c \ + config_msys_rom.c \ + config_sds.c \ + config_bootloader.c \ + config_system_pll.c \ + config_css_clock.c \ + config_pik_clock.c \ + config_clock.c \ + sgm775_core.c \ + config_ppu_v0.c \ + config_ppu_v1.c + +include $(BS_DIR)/firmware.mk diff --git a/product/sgm775/scp_romfw/fmw_memory.ld.S b/product/sgm775/scp_romfw/fmw_memory.ld.S new file mode 100644 index 00000000..23b89ab4 --- /dev/null +++ b/product/sgm775/scp_romfw/fmw_memory.ld.S @@ -0,0 +1,36 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + * + * Description: + * ROM firmware memory layout for the linker script. + */ + +#ifndef FMW_MEMORY_LD_S +#define FMW_MEMORY_LD_S + +#include <system_mmap_scp.h> + +#define FIRMWARE_MEM_MODE FWK_MEM_MODE_DUAL_REGION_RELOCATION + +/* + * ROM memory + */ +#define FIRMWARE_MEM0_SIZE SCP_ROM_SIZE +#define FIRMWARE_MEM0_BASE SCP_ROM_BASE + +/* + * RAM memory (16 KiB block at the top of the RAM) + * + * Note: The sections that must go into the RAM memory (i.e. stack, heap, bss + * and data) are placed at the end of the RAM memory to avoid being overwritten + * by the bootloader when loading the RAM firmware image. + */ +#define FIRMWARE_MEM1_SIZE (16 * 1024) +#define FIRMWARE_MEM1_BASE (SCP_RAM_BASE + SCP_RAM_SIZE - FIRMWARE_MEM1_SIZE) + +#define FIRMWARE_STACK_SIZE (1 * 1024) + +#endif /* FMW_MEMORY_LD_S */ diff --git a/product/sgm775/src/sgm775_core.c b/product/sgm775/src/sgm775_core.c new file mode 100644 index 00000000..750f18a0 --- /dev/null +++ b/product/sgm775/src/sgm775_core.c @@ -0,0 +1,14 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include <sgm775_pik.h> +#include <sgm775_core.h> + +unsigned int sgm775_core_get_count(void) +{ + return (PIK_CLUS0->PIK_CONFIG & PIK_CPU_V8_2_PIK_CONFIG_NO_OF_PPU) - 1; +} diff --git a/tools/ci.py b/tools/ci.py index 415d0b30..c0e276b1 100755 --- a/tools/ci.py +++ b/tools/ci.py @@ -69,6 +69,12 @@ def main(): result = subprocess.call(cmd, shell=True) results.append(('Product host build', result)) + banner('Test building sgm775 product') + + cmd = 'CROSS_COMPILE=arm-none-eabi- PRODUCT=sgm775 make' + result = subprocess.call(cmd, shell=True) + results.append(('Product sgm775 build', result)) + banner('Tests summary') total_success = 0 |