diff --git a/module/dvfs/include/mod_dvfs.h b/module/dvfs/include/mod_dvfs.h index 2c4a672ec67de2bb446aeb309aea3b284debf5db..964445d056358e0b52f231bdd178c35b851e557b 100644 --- a/module/dvfs/include/mod_dvfs.h +++ b/module/dvfs/include/mod_dvfs.h @@ -67,6 +67,16 @@ struct mod_dvfs_domain_config { */ fwk_id_t clock_id; + /*! + * \brief Alarm identifier. + * + * \warning This identifier must refer to an alarm of the \c timer module. + */ + fwk_id_t alarm_id; + + /*! Delay in milliseconds before retrying a request */ + uint16_t retry_ms; + /*! Worst-case transition latency in microseconds */ uint16_t latency; @@ -149,16 +159,6 @@ struct mod_dvfs_domain_api { */ 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. * @@ -178,18 +178,6 @@ struct mod_dvfs_domain_api { 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); }; /*! @@ -202,23 +190,19 @@ struct mod_dvfs_domain_api { */ /*! - * \brief Set operating point event response parameters. + * \brief Get current OPP event response parameters. */ -struct mod_dvfs_event_params_set_frequency_response { - int status; /*!< Status of the request */ -}; +struct mod_dvfs_params_response { + /*! Event response status */ + int status; -/*! - * \brief Set limits event response parameters. - */ -struct mod_dvfs_event_params_set_frequency_limits_response { - int status; /*!< Status of the request */ + /*! Event response frequency */ + uint64_t performance_level; }; /*! * \} */ - /*! * \defgroup GroupDvfsIds Identifiers * \{ @@ -240,25 +224,18 @@ static const fwk_id_t mod_dvfs_api_id_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 + MOD_DVFS_EVENT_IDX_SET, /*!< Set level/limits */ + MOD_DVFS_EVENT_IDX_GET_OPP, /*!< Get frequency */ + MOD_DVFS_EVENT_IDX_COUNT, /*!< event count */ }; -/*! Set operating point 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); +/*! Set operating point/limits event identifier */ +static const fwk_id_t mod_dvfs_event_id_set = + FWK_ID_EVENT_INIT(FWK_MODULE_IDX_DVFS, MOD_DVFS_EVENT_IDX_SET); -/*! Set frequency limits 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); +/*! Get current OPP event identifier */ +static const fwk_id_t mod_dvfs_event_id_get_opp = + FWK_ID_EVENT_INIT(FWK_MODULE_IDX_DVFS, MOD_DVFS_EVENT_IDX_GET_OPP); /*! * \} diff --git a/module/dvfs/src/Makefile b/module/dvfs/src/Makefile index 433bbcc3159cc73cf9bc0abe9186a64de1c04dd2..9e63ba5d9cdf7cc8c09878eb0ba79dc9e9b02580 100644 --- a/module/dvfs/src/Makefile +++ b/module/dvfs/src/Makefile @@ -6,10 +6,6 @@ # BS_LIB_NAME := dvfs -BS_LIB_SOURCES := \ - mod_dvfs_domain_api.c \ - mod_dvfs_event.c \ - mod_dvfs_module.c \ - mod_dvfs_util.c \ +BS_LIB_SOURCES := mod_dvfs.c include $(BS_DIR)/lib.mk diff --git a/module/dvfs/src/mod_dvfs.c b/module/dvfs/src/mod_dvfs.c new file mode 100644 index 0000000000000000000000000000000000000000..dc3b2eca989f5f8902b103ee6d687dfc3dea3783 --- /dev/null +++ b/module/dvfs/src/mod_dvfs.c @@ -0,0 +1,1110 @@ +/* + * Arm SCP/MCP Software + * Copyright (c) 2017-2019, Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#ifdef BUILD_HAS_MULTITHREADING +#include +#endif +#include +#include +#include +#include +#include +#include + +/* + * Maximum number of attempts to complete a request + */ +#define DVFS_MAX_RETRIES 4 + +enum mod_dvfs_internal_event_idx { + /* retry request */ + MOD_DVFS_INTERNAL_EVENT_IDX_RETRY = MOD_DVFS_EVENT_IDX_COUNT, + MOD_DVFS_INTERNAL_EVENT_IDX_COUNT, +}; + +/* Retry request event identifier */ +static const fwk_id_t mod_dvfs_event_id_retry = + FWK_ID_EVENT_INIT(FWK_MODULE_IDX_DVFS, + MOD_DVFS_INTERNAL_EVENT_IDX_RETRY); + +/*! + * \brief Domain states for GET_OPP/SET_OPP. + */ +enum mod_dvfs_domain_state { + DVFS_DOMAIN_STATE_IDLE = 0, + + /* Waiting for GET_OPP request */ + DVFS_DOMAIN_GET_OPP, + + /* Waiting for SET_OPP request */ + DVFS_DOMAIN_SET_OPP, + + /* set_rate() in progress, set_voltage() next */ + DVFS_DOMAIN_SET_VOLTAGE, + + /* set_voltage() in progress, set_frequency() next */ + DVFS_DOMAIN_SET_FREQUENCY, + + /* waiting for SET_OPP to complete */ + DVFS_DOMAIN_SET_OPP_DONE, + + /* waiting for alarm callback to start a retry */ + DVFS_DOMAIN_STATE_RETRY, +}; + +/*! + * \brief Request for SET_OPP. + */ +struct mod_dvfs_request { + /* New operating point data for the request */ + struct mod_dvfs_opp new_opp; + + /* Response expected for this request */ + bool response_required; + + /* Retry the request on failure */ + bool retry_request; + + /* This request requires the event source_id to be set */ + bool set_source_id; + + /* Retry count */ + uint8_t num_retries; +}; + +/*! + * \brief Domain context. + */ +struct mod_dvfs_domain_ctx { + /* Context Domain ID */ + fwk_id_t domain_id; + + /* 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; + + /* Alarm API for pending requests */ + const struct mod_timer_alarm_api *alarm_api; + + } apis; + + /* Number of operating points */ + size_t opp_count; + + /* Current operating point */ + struct mod_dvfs_opp current_opp; + + /* Current frequency limits */ + struct mod_dvfs_frequency_limits frequency_limits; + + /* Current request details */ + struct mod_dvfs_request request; + + /* State */ + enum mod_dvfs_domain_state state; + + /* Cookie for deferred request response */ + uint32_t cookie; + + /* Pending request details */ + struct mod_dvfs_request pending_request; + + /* SET_OPP Request is pending for this domain */ + bool request_pending; +}; + +struct mod_dvfs_ctx { + /* Number of DVFS domains */ + uint32_t dvfs_domain_element_count; + + /* DVFS device context table */ + struct mod_dvfs_domain_ctx (*domain_ctx)[]; +} dvfs_ctx; + +/* + * DVFS Helper Functions + */ +static struct mod_dvfs_domain_ctx *get_domain_ctx(fwk_id_t domain_id) +{ + uint32_t idx = fwk_id_get_element_idx(domain_id); + + if (idx < dvfs_ctx.dvfs_domain_element_count) + return &(*dvfs_ctx.domain_ctx)[idx]; + else + return NULL; +} + +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 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; + + /* + * If both parameters are zero we have nothing to do. + */ + if ((frequency == 0) && (voltage == 0)) + return NULL; + + 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); +} + +/* + * Helper to create events to process requests asynchronously + */ +static int put_event_request(struct mod_dvfs_domain_ctx *ctx, + fwk_id_t event_id, + enum mod_dvfs_domain_state state) +{ + struct fwk_event req; + + req = (struct fwk_event) { + .target_id = ctx->domain_id, + .id = event_id, + .response_requested = ctx->request.response_required, + }; + + if (ctx->request.set_source_id) + req.source_id = ctx->domain_id; + + ctx->state = state; + + return fwk_thread_put_event(&req); +} + +static int dvfs_set_frequency_start(struct mod_dvfs_domain_ctx *ctx, + const struct mod_dvfs_opp *new_opp, + bool retry_request) +{ + ctx->request.new_opp = *new_opp; + ctx->request.retry_request = retry_request; + ctx->request.response_required = false; + + return put_event_request(ctx, mod_dvfs_event_id_set, + DVFS_DOMAIN_SET_OPP); +} + +/* + * Handle pending requests + */ +static void dvfs_flush_pending_request(struct mod_dvfs_domain_ctx *ctx) +{ + if (ctx->request_pending) { + ctx->request_pending = false; + dvfs_set_frequency_start(ctx, &ctx->pending_request.new_opp, + ctx->pending_request.retry_request); + } + ctx->pending_request = (struct mod_dvfs_request){ 0 }; +} + +static void alarm_callback(uintptr_t param) +{ + struct fwk_event req; + struct mod_dvfs_domain_ctx *ctx = + (struct mod_dvfs_domain_ctx *)param; + + req = (struct fwk_event) { + .target_id = ctx->domain_id, + .source_id = ctx->domain_id, + .id = mod_dvfs_event_id_retry, + .response_requested = ctx->pending_request.response_required, + }; + + fwk_thread_put_event(&req); +} + +static int dvfs_handle_pending_request(struct mod_dvfs_domain_ctx *ctx) +{ + int status = FWK_SUCCESS; + + if (ctx->state == DVFS_DOMAIN_STATE_RETRY) + return FWK_SUCCESS; + + if (ctx->config->retry_ms > 0) { + status = ctx->apis.alarm_api->start(ctx->config->alarm_id, + ctx->config->retry_ms, MOD_TIMER_ALARM_TYPE_ONCE, + alarm_callback, (uintptr_t)ctx); + if (status == FWK_SUCCESS) + ctx->state = DVFS_DOMAIN_STATE_RETRY; + } else { + /* + * If this domain does not have a timeout configured we start + * processing the request immediately. + */ + dvfs_flush_pending_request(ctx); + } + return status; +} + +static int dvfs_create_pending_level_request(struct mod_dvfs_domain_ctx *ctx, + const struct mod_dvfs_opp *new_opp, bool retry_request) +{ + + if (ctx->request_pending) { + if ((new_opp->frequency == ctx->pending_request.new_opp.frequency) && + (new_opp->voltage == ctx->pending_request.new_opp.voltage)) + return FWK_SUCCESS; + } else { + if ((new_opp->frequency == ctx->current_opp.frequency) && + (new_opp->voltage == ctx->current_opp.voltage)) { + return FWK_SUCCESS; + + ctx->pending_request.num_retries = 0; + ctx->pending_request.set_source_id = false; + ctx->pending_request.response_required = false; + ctx->request_pending = true; + } + } + + ctx->pending_request.new_opp = *new_opp; + + /* + * Set the retry flag only if we have to retry the request. Otherwise don't + * overwrite it. + */ + if (retry_request) + ctx->pending_request.retry_request = retry_request; + + return FWK_SUCCESS; +} + +/* + * DVFS module synchronous API functions + */ +static int dvfs_get_sustained_opp(fwk_id_t domain_id, struct mod_dvfs_opp *opp) +{ + const struct mod_dvfs_domain_ctx *ctx; + + ctx = get_domain_ctx(domain_id); + if (ctx == NULL) + return FWK_E_PARAM; + + if (ctx->config->sustained_idx >= ctx->opp_count) + return FWK_E_PARAM; + + *opp = ctx->config->opps[ctx->config->sustained_idx]; + + return FWK_SUCCESS; +} + +static int dvfs_get_nth_opp(fwk_id_t domain_id, + size_t n, + struct mod_dvfs_opp *opp) +{ + const struct mod_dvfs_domain_ctx *ctx; + + if (opp == NULL) + return FWK_E_PARAM; + + ctx = get_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 dvfs_get_opp_count(fwk_id_t domain_id, size_t *opp_count) +{ + const struct mod_dvfs_domain_ctx *ctx; + + if (opp_count == NULL) + return FWK_E_PARAM; + + ctx = get_domain_ctx(domain_id); + if (ctx == NULL) + return FWK_E_PARAM; + + *opp_count = ctx->opp_count; + + return FWK_SUCCESS; +} + +static int dvfs_get_latency(fwk_id_t domain_id, uint16_t *latency) +{ + const struct mod_dvfs_domain_ctx *ctx; + + if (latency == NULL) + return FWK_E_PARAM; + + ctx = get_domain_ctx(domain_id); + if (ctx == NULL) + return FWK_E_PARAM; + + *latency = ctx->config->latency; + + return FWK_SUCCESS; +} + +static int dvfs_get_frequency_limits(fwk_id_t domain_id, + struct mod_dvfs_frequency_limits *limits) +{ + struct mod_dvfs_domain_ctx *ctx; + + if (limits == NULL) + return FWK_E_PARAM; + + ctx = get_domain_ctx(domain_id); + if (ctx == NULL) + return FWK_E_PARAM; + + *limits = ctx->frequency_limits; + return FWK_SUCCESS; +} + +/* + * dvfs_get_current_opp() may be either synchronous or asynchronous + */ +static int dvfs_get_current_opp(fwk_id_t domain_id, struct mod_dvfs_opp *opp) +{ + int status; + struct mod_dvfs_domain_ctx *ctx; + + if (opp == NULL) + return FWK_E_PARAM; + + ctx = get_domain_ctx(domain_id); + if (ctx == NULL) + return FWK_E_PARAM; + + if (ctx->current_opp.frequency != 0) { + opp->frequency = ctx->current_opp.frequency; + opp->voltage = ctx->current_opp.voltage; + return FWK_SUCCESS; + } + + if (ctx->state != DVFS_DOMAIN_STATE_IDLE) + return FWK_E_BUSY; + + ctx->request.response_required = true; + status = put_event_request(ctx, mod_dvfs_event_id_get_opp, + DVFS_DOMAIN_GET_OPP); + if (status == FWK_SUCCESS) { + /* + * We return FWK_PENDING here to indicate to the caller that the + * result of the request is pending and will arrive later through + * an event. + */ + return FWK_PENDING; + } + + return status; +} + +/* + * DVFS module asynchronous API functions + */ +static int dvfs_set_frequency(fwk_id_t domain_id, uint64_t frequency) +{ + struct mod_dvfs_domain_ctx *ctx; + const struct mod_dvfs_opp *new_opp; + + ctx = get_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; + + if (ctx->state != DVFS_DOMAIN_STATE_IDLE) + return dvfs_create_pending_level_request(ctx, new_opp, false); + + if ((new_opp->frequency == ctx->current_opp.frequency) && + (new_opp->voltage == ctx->current_opp.voltage)) + return FWK_SUCCESS; + + return dvfs_set_frequency_start(ctx, new_opp, false); +} + +static int dvfs_set_frequency_limits(fwk_id_t domain_id, + const struct mod_dvfs_frequency_limits *limits) +{ + struct mod_dvfs_domain_ctx *ctx; + const struct mod_dvfs_opp *new_opp; + + ctx = get_domain_ctx(domain_id); + if (ctx == NULL) + return FWK_E_PARAM; + + if (!are_limits_valid(ctx, limits)) + return FWK_E_PARAM; + + new_opp = adjust_opp_for_new_limits(ctx, &ctx->current_opp, limits); + if (new_opp == NULL) + return FWK_E_PARAM; + + ctx->frequency_limits = *limits; + + if ((new_opp->frequency == ctx->current_opp.frequency) && + (new_opp->voltage == ctx->current_opp.voltage)) { + return FWK_SUCCESS; + } + + if (ctx->state != DVFS_DOMAIN_STATE_IDLE) + return dvfs_create_pending_level_request(ctx, new_opp, true); + + return dvfs_set_frequency_start(ctx, new_opp, true); +} + +static const struct mod_dvfs_domain_api mod_dvfs_domain_api = { + .get_current_opp = dvfs_get_current_opp, + .get_sustained_opp = dvfs_get_sustained_opp, + .get_nth_opp = dvfs_get_nth_opp, + .get_opp_count = dvfs_get_opp_count, + .get_latency = dvfs_get_latency, + .set_frequency = dvfs_set_frequency, + .get_frequency_limits = dvfs_get_frequency_limits, + .set_frequency_limits = dvfs_set_frequency_limits, +}; + +/* + * DVFS utility functions + */ + +/* + * DVFS Request Complete handling. + */ +static void dvfs_complete_respond(struct mod_dvfs_domain_ctx *ctx, + struct fwk_event *resp_event, + int req_status) +{ + int status; + struct fwk_event read_req_event; + struct mod_dvfs_params_response *resp_params; + bool return_opp; + + if ((ctx->state == DVFS_DOMAIN_GET_OPP) && (req_status == FWK_SUCCESS)) + return_opp = true; + + if (ctx->cookie != 0) { + /* + * The request was handled asynchronously, retrieve + * the delayed_response and return it to the caller + * with the data. + */ + resp_params = (struct mod_dvfs_params_response *) + &read_req_event.params; + status = fwk_thread_get_delayed_response(ctx->domain_id, + ctx->cookie, + &read_req_event); + + if (status == FWK_SUCCESS) { + resp_params->status = req_status; + if (return_opp) + resp_params->performance_level = ctx->current_opp.frequency; + fwk_thread_put_event(&read_req_event); + } + ctx->cookie = 0; + } else if (resp_event != NULL) { + /* + * The request is being handled synchronously, return + * the data to the caller in the resp_event. + */ + resp_params = + (struct mod_dvfs_params_response *)resp_event->params; + resp_params->status = req_status; + if (return_opp) + resp_params->performance_level = ctx->current_opp.frequency; + } +} + +static int dvfs_complete(struct mod_dvfs_domain_ctx *ctx, + struct fwk_event *resp_event, + int req_status) +{ + int status = req_status; + + if (ctx->request.response_required) { + /* + * If the DVFS request requires a response we send it now, no retries + * are attempted. + */ + dvfs_complete_respond(ctx, resp_event, req_status); + + } else if ((req_status != FWK_SUCCESS) && ctx->request.retry_request) { + /* + * No response required, request has failed, a retry is necessary. + */ + if (ctx->request.num_retries++ < DVFS_MAX_RETRIES) { + ctx->pending_request.retry_request = ctx->request.retry_request; + ctx->pending_request.num_retries = ctx->request.num_retries; + if (!ctx->request_pending) { + ctx->pending_request.new_opp = ctx->request.new_opp; + ctx->request_pending = true; + } + } + } + + /* + * Now we need to start processing the pending request if any, + * note that we do not set the state to DOMAIN_STATE_IDLE + * here to prevent another request being processed ahead of this one. + */ + if (ctx->request_pending) { + dvfs_handle_pending_request(ctx); + } else { + /* + * The request has completed and there are no pending requests. + * Clean up the domain ctx here. + */ + ctx->pending_request = (struct mod_dvfs_request){ 0 }; + ctx->request = (struct mod_dvfs_request){ 0 }; + ctx->state = DVFS_DOMAIN_STATE_IDLE; + } + + return status; +} + +/* + * The SET_OPP() request has successfully completed the first step, + * reading the voltage. + */ +static int dvfs_handle_set_opp(struct mod_dvfs_domain_ctx *ctx, + uint64_t voltage) +{ + int status = FWK_SUCCESS; + + if (ctx->request.new_opp.voltage > voltage) { + /* + * Current < request, increase voltage then set frequency + */ + status = ctx->apis.psu->set_voltage( + ctx->config->psu_id, ctx->request.new_opp.voltage); + + if (status == FWK_PENDING) { + ctx->state = DVFS_DOMAIN_SET_FREQUENCY; + return status; + } + + if (status != FWK_SUCCESS) + return dvfs_complete(ctx, NULL, status); + + /* + * Voltage set successsfully, continue to set the frequency + */ + status = ctx->apis.clock->set_rate( + ctx->config->clock_id, + ctx->request.new_opp.frequency, + MOD_CLOCK_ROUND_MODE_NONE); + + if (status == FWK_PENDING) { + ctx->state = DVFS_DOMAIN_SET_OPP_DONE; + return status; + } + } else if (ctx->request.new_opp.voltage < voltage) { + /* + * Current > request, decrease frequency then set voltage + */ + status = ctx->apis.clock->set_rate( + ctx->config->clock_id, + ctx->request.new_opp.frequency, + MOD_CLOCK_ROUND_MODE_NONE); + + if (status == FWK_PENDING) { + ctx->state = DVFS_DOMAIN_SET_VOLTAGE; + return status; + } + + if (status != FWK_SUCCESS) + return dvfs_complete(ctx, NULL, status); + + /* + * Clock set_rate() completed successfully, continue to set_voltage() + */ + status = ctx->apis.psu->set_voltage( + ctx->config->psu_id, ctx->request.new_opp.voltage); + + if (status == FWK_PENDING) { + ctx->state = DVFS_DOMAIN_SET_OPP_DONE; + return status; + } + } else if (ctx->current_opp.frequency == 0) { + /* + * At startup the voltage may be set without the frequency having + * been set. In this case we must set the frequency regardless of + * the voltage. + */ + status = ctx->apis.clock->set_rate( + ctx->config->clock_id, + ctx->request.new_opp.frequency, + MOD_CLOCK_ROUND_MODE_NONE); + + if (status == FWK_PENDING) { + ctx->state = DVFS_DOMAIN_SET_OPP_DONE; + return status; + } + } + + /* + * SET_OPP() completed, return to caller. + */ + if (status == FWK_SUCCESS) + ctx->current_opp = ctx->request.new_opp; + + return dvfs_complete(ctx, NULL, status); +} + +/* + * The current voltage has been read. This is the first step of a SET_OPP() + * request and the only step of a GET_OPP() request. It may have been handled + * synchronously or asynchronously. Note that resp_event will only be set + * by a GET_OPP(), it will always be NULL for SET_OPP(). + */ +static int dvfs_handle_psu_get_voltage_resp(struct mod_dvfs_domain_ctx *ctx, + struct fwk_event *resp_event, + int req_status, + uint64_t voltage) +{ + const struct mod_dvfs_opp *opp; + + if (req_status != FWK_SUCCESS) + return dvfs_complete(ctx, resp_event, req_status); + + if (ctx->state == DVFS_DOMAIN_SET_OPP) + return dvfs_handle_set_opp(ctx, voltage); + + /* + * We have the actual voltage, get the frequency from the + * corresponding OPP in the domain context table. + */ + opp = get_opp_for_values(ctx, 0, voltage); + if (opp == NULL) + return dvfs_complete(ctx, resp_event, FWK_E_DEVICE); + + /* + * We have successfully found the frequency, save it in the domain context. + */ + ctx->current_opp.voltage = voltage; + ctx->current_opp.frequency = opp->frequency; + + /* + * This is a GET_OPP(), we are done, return the frequency to caller + */ + return dvfs_complete(ctx, resp_event, FWK_SUCCESS); +} + +/* + * Note that dvfs_handle_psu_set_voltage_resp() is only called after an + * asynchronous set_voltage() operation. + */ +static int dvfs_handle_psu_set_voltage_resp(struct mod_dvfs_domain_ctx *ctx, + const struct fwk_event *event) +{ + int status = FWK_SUCCESS; + struct mod_psu_driver_response *psu_response = + (struct mod_psu_driver_response *)event->params; + + if (psu_response->status != FWK_SUCCESS) + return dvfs_complete(ctx, NULL, psu_response->status); + + if (ctx->state == DVFS_DOMAIN_SET_FREQUENCY) { + status = ctx->apis.clock->set_rate( + ctx->config->clock_id, + ctx->request.new_opp.frequency, + MOD_CLOCK_ROUND_MODE_NONE); + if (status == FWK_PENDING) { + ctx->state = DVFS_DOMAIN_SET_OPP_DONE; + return status; + } + } else if (ctx->state == DVFS_DOMAIN_SET_OPP_DONE) { + status = FWK_SUCCESS; + } else + status = FWK_E_DEVICE; + + /* + * SET_OPP() completed, return to caller. + */ + if (status == FWK_SUCCESS) + ctx->current_opp = ctx->request.new_opp; + + return dvfs_complete(ctx, NULL, status); +} + +/* + * Note that dvfs_handle_clk_set_freq_resp() is only called after an + * asynchronous set_rate() operation. + */ +static int dvfs_handle_clk_set_freq_resp(struct mod_dvfs_domain_ctx *ctx, + const struct fwk_event *event) +{ + int status; + struct mod_clock_driver_resp_params *clock_response = + (struct mod_clock_driver_resp_params *)event->params; + + if (clock_response->status != FWK_SUCCESS) + return dvfs_complete(ctx, NULL, clock_response->status); + + if (ctx->state == DVFS_DOMAIN_SET_VOLTAGE) { + /* + * Clock set_rate() completed successfully, continue to set_voltage() + */ + status = ctx->apis.psu->set_voltage( + ctx->config->psu_id, + ctx->request.new_opp.voltage); + if (status == FWK_PENDING) { + ctx->state = DVFS_DOMAIN_SET_OPP_DONE; + return status; + } + + } else if (ctx->state == DVFS_DOMAIN_SET_OPP_DONE) { + status = FWK_SUCCESS; + } else + status = FWK_E_DEVICE; + + /* + * SET_OPP() completed, return to caller. + */ + if (status == FWK_SUCCESS) + ctx->current_opp = ctx->request.new_opp; + + return dvfs_complete(ctx, NULL, status); +} + +/* + * DVFS Module Framework Support + */ +static int mod_dvfs_process_event(const struct fwk_event *event, + struct fwk_event *resp_event) +{ + int status = FWK_SUCCESS; + struct mod_dvfs_domain_ctx *ctx; + struct mod_psu_driver_response *psu_response; + uint64_t voltage; + + ctx = get_domain_ctx(event->target_id); + if (ctx == NULL) + return FWK_E_PARAM; + + /* + * local DVFS event from dvfs_get_current_opp() + */ + if (fwk_id_is_equal(event->id, mod_dvfs_event_id_get_opp)) { + status = ctx->apis.psu->get_voltage(ctx->config->psu_id, + &ctx->request.new_opp.voltage); + if (status == FWK_PENDING) { + ctx->cookie = event->cookie; + resp_event->is_delayed_response = true; + return FWK_SUCCESS; + } + + /* + * Handle get_voltage() synchronously + */ + return dvfs_handle_psu_get_voltage_resp(ctx, + resp_event, status, ctx->request.new_opp.voltage); + } + + /* + * local DVFS event from dvfs_set_frequency() + */ + if (fwk_id_is_equal(event->id, mod_dvfs_event_id_set)) { + if (ctx->current_opp.voltage != 0) { + voltage = ctx->current_opp.voltage; + status = FWK_SUCCESS; + } else { + status = ctx->apis.psu->get_voltage(ctx->config->psu_id, + &voltage); + if (status == FWK_PENDING) + return FWK_SUCCESS; + } + + /* + * Handle get_voltage() synchronously + */ + return dvfs_handle_psu_get_voltage_resp(ctx, NULL, + status, voltage); + } + + /* + * local DVFS event from dvfs_flush_pending_request() + */ + if (fwk_id_is_equal(event->id, mod_dvfs_event_id_retry)) { + ctx->request.set_source_id = false; + return dvfs_set_frequency_start(ctx, &ctx->pending_request.new_opp, + ctx->pending_request.retry_request); + } + + /* + * response event from PSU get_voltage() + */ + if (fwk_id_is_equal(event->id, mod_psu_event_id_get_voltage)) { + /* + * Handle get_voltage() asynchronously, cookie will have been saved + * above so we can safely discard the resp_event. + */ + psu_response = (struct mod_psu_driver_response *)event->params; + return dvfs_handle_psu_get_voltage_resp(ctx, NULL, + psu_response->status, psu_response->voltage); + } + + /* + * response event from SET_OPP() PSU set_voltage() + */ + if (fwk_id_is_equal(event->id, mod_psu_event_id_set_voltage)) { + /* + * Handle set_voltage() asynchronously, no response required for + * a SET_OPP() request so resp_event discarded. + */ + return dvfs_handle_psu_set_voltage_resp(ctx, event); + } + + /* + * response event from SET_OPP() Clock set_rate() + */ + if (fwk_id_is_equal(event->id, mod_clock_event_id_request)) { + /* + * Handle set_frequency() asynchronously, no response required for + * a SET_OPP() request so resp_event discarded. + */ + return dvfs_handle_clk_set_freq_resp(ctx, event); + } + + return FWK_E_PARAM; +} + +/* + * Module framework support + */ + +/* + * The DVFS voltage/frequency must be set to the sustained OPP at startup + * for each domain. + */ +static int dvfs_start(fwk_id_t id) +{ + int status; + struct mod_dvfs_opp sustained_opp; + struct mod_dvfs_domain_ctx *ctx; + + if (!fwk_id_is_type(id, FWK_ID_TYPE_ELEMENT)) + return FWK_SUCCESS; + + status = dvfs_get_sustained_opp(id, &sustained_opp); + if (status == FWK_SUCCESS) { + ctx = get_domain_ctx(id); + ctx->request.set_source_id = true; + ctx->request.num_retries = 0; + dvfs_set_frequency_start(ctx, &sustained_opp, true); + } + + return status; +} + +static int dvfs_init(fwk_id_t module_id, unsigned int element_count, + const void *data) +{ + dvfs_ctx.domain_ctx = fwk_mm_calloc(element_count, + sizeof((*dvfs_ctx.domain_ctx)[0])); + if (dvfs_ctx.domain_ctx == NULL) + return FWK_E_NOMEM; + + dvfs_ctx.dvfs_domain_element_count = element_count; + + 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); + + fwk_assert(sub_element_count == 0); + + ctx->domain_id = domain_id; + + /* Initialize the configuration */ + ctx->config = data; + fwk_assert(ctx->config->opps != NULL); + + /* Initialize the context */ + ctx->opp_count = count_opps(ctx->config->opps); + fwk_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, + }; + + 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_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; + + /* Bind to the alarm HAL if required */ + if (ctx->config->retry_ms > 0) { + status = fwk_module_bind(ctx->config->alarm_id, MOD_TIMER_API_ID_ALARM, + &ctx->apis.alarm_api); + if (status != FWK_SUCCESS) + return FWK_E_PANIC; + } + + return FWK_SUCCESS; +} + +static int +dvfs_bind(fwk_id_t id, unsigned int round) +{ + /* Bind 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); +} + +/* Module description */ +const struct fwk_module module_dvfs = { + .name = "DVFS", + .type = FWK_MODULE_TYPE_HAL, + .init = dvfs_init, + .element_init = dvfs_element_init, + .start = dvfs_start, + .bind = dvfs_bind, + .process_bind_request = dvfs_process_bind_request, + .process_event = mod_dvfs_process_event, + .api_count = MOD_DVFS_API_IDX_COUNT, + .event_count = MOD_DVFS_INTERNAL_EVENT_IDX_COUNT, +}; diff --git a/module/dvfs/src/mod_dvfs_domain_api.c b/module/dvfs/src/mod_dvfs_domain_api.c deleted file mode 100644 index 7e76442358feb1a39a25e0195e2c8e6697a25cff..0000000000000000000000000000000000000000 --- a/module/dvfs/src/mod_dvfs_domain_api.c +++ /dev/null @@ -1,265 +0,0 @@ -/* - * Arm SCP/MCP Software - * Copyright (c) 2017-2019, Arm Limited and Contributors. All rights reserved. - * - * SPDX-License-Identifier: BSD-3-Clause - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -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 deleted file mode 100644 index 5c1b28187bfae3c82c398632342b05a772b4e9db..0000000000000000000000000000000000000000 --- a/module/dvfs/src/mod_dvfs_domain_api_private.h +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Arm SCP/MCP Software - * Copyright (c) 2017-2019, 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 - -/* 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 deleted file mode 100644 index f0224d99e6a8f1832e2ee652766d5961b19fc384..0000000000000000000000000000000000000000 --- a/module/dvfs/src/mod_dvfs_event.c +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Arm SCP/MCP Software - * Copyright (c) 2017-2019, Arm Limited and Contributors. All rights reserved. - * - * SPDX-License-Identifier: BSD-3-Clause - */ - -#include -#include - -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 deleted file mode 100644 index cb6c30c949349ab02d27f6b5280b3191f785d3ec..0000000000000000000000000000000000000000 --- a/module/dvfs/src/mod_dvfs_event_private.h +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Arm SCP/MCP Software - * Copyright (c) 2017-2019, 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 - -/* 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 deleted file mode 100644 index e2de6a60be4f325d08e14414764466b4fd16551b..0000000000000000000000000000000000000000 --- a/module/dvfs/src/mod_dvfs_module.c +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Arm SCP/MCP Software - * Copyright (c) 2017-2019, Arm Limited and Contributors. All rights reserved. - * - * SPDX-License-Identifier: BSD-3-Clause - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -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_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) -{ - 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 deleted file mode 100644 index 6dc39ed83ad1c84a022fcc08b614463bb935a90c..0000000000000000000000000000000000000000 --- a/module/dvfs/src/mod_dvfs_module_private.h +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Arm SCP/MCP Software - * Copyright (c) 2017-2019, 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 -#include -#include - -/* 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 deleted file mode 100644 index bdd6ceb0bbcc343532041aaef55e0d2c8d12d6a6..0000000000000000000000000000000000000000 --- a/module/dvfs/src/mod_dvfs_private.h +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Arm SCP/MCP Software - * Copyright (c) 2017-2019, Arm Limited and Contributors. All rights reserved. - * - * SPDX-License-Identifier: BSD-3-Clause - */ - -#ifndef MOD_DVFS_PRIVATE_H -#define MOD_DVFS_PRIVATE_H - -#include -#include -#include -#include - -#endif /* MOD_DVFS_PRIVATE_H */ diff --git a/module/dvfs/src/mod_dvfs_util.c b/module/dvfs/src/mod_dvfs_util.c deleted file mode 100644 index a9982dffbc023cc1f41bb111b8586402fdae7757..0000000000000000000000000000000000000000 --- a/module/dvfs/src/mod_dvfs_util.c +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Arm SCP/MCP Software - * Copyright (c) 2017-2019, Arm Limited and Contributors. All rights reserved. - * - * SPDX-License-Identifier: BSD-3-Clause - */ - -#include -#include - -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 deleted file mode 100644 index 7d8d37d0eaf715c235dffee9af9565540f3b25ba..0000000000000000000000000000000000000000 --- a/module/dvfs/src/mod_dvfs_util_private.h +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Arm SCP/MCP Software - * Copyright (c) 2017-2019, 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 -#include - -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/scmi_perf/src/mod_scmi_perf.c b/module/scmi_perf/src/mod_scmi_perf.c index 9edf57860f084cc2baf89603b2b61d2cc5d86174..e990b5b608eb7d66221cc381a81318ff12816c92 100644 --- a/module/scmi_perf/src/mod_scmi_perf.c +++ b/module/scmi_perf/src/mod_scmi_perf.c @@ -11,8 +11,12 @@ #include #include #include +#include #include #include +#ifdef BUILD_HAS_MULTITHREADING +#include +#endif #include #include #include @@ -20,20 +24,6 @@ #include #include -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( @@ -53,8 +43,6 @@ static int scmi_perf_limits_set_handler( 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, @@ -95,6 +83,53 @@ static unsigned int payload_size_table[] = { sizeof(struct scmi_perf_limits_get_a2p), }; +struct perf_operations { + /* + * Service identifier currently requesting operation. + * A 'none' value means that there is no pending request. + */ + fwk_id_t service_id; +}; + + +struct scmi_perf_ctx { + /* SCMI Performance Module Configuration */ + const struct mod_scmi_perf_config *config; + + /* Number of power domains */ + uint32_t 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; + + /* Pointer to a table of operations */ + struct perf_operations *perf_ops_table; +}; + +static struct scmi_perf_ctx scmi_perf_ctx; + +/* Event indices */ +enum scmi_perf_event_idx { + SCMI_PERF_EVENT_IDX_LEVEL_GET_REQUEST, + SCMI_PERF_EVENT_IDX_LIMITS_GET_REQUEST, + SCMI_PERF_EVENT_IDX_COUNT, +}; + +struct scmi_perf_event_parameters { + fwk_id_t domain_id; +}; + +static const fwk_id_t scmi_perf_get_level = + FWK_ID_EVENT_INIT(FWK_MODULE_IDX_SCMI_PERF, + SCMI_PERF_EVENT_IDX_LEVEL_GET_REQUEST); + +static const fwk_id_t scmi_perf_get_limits = + FWK_ID_EVENT_INIT(FWK_MODULE_IDX_SCMI_PERF, + SCMI_PERF_EVENT_IDX_LIMITS_GET_REQUEST); + /* * Protocol command handlers */ @@ -135,15 +170,13 @@ 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; + parameters = (const struct scmi_protocol_message_attributes_a2p *) + payload; - if ((message_id < FWK_ARRAY_SIZE(handler_table)) && - (handler_table[message_id] != NULL)) { + 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, .attributes = 0, /* All commands have an attributes value of 0 */ @@ -165,15 +198,16 @@ static int scmi_perf_domain_attributes_handler(fwk_id_t service_id, 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; + struct scmi_perf_domain_attributes_p2a return_values = { + .status = SCMI_GENERIC_ERROR, + }; - return_values.status = SCMI_GENERIC_ERROR; + parameters = (const struct scmi_perf_domain_attributes_a2p *)payload; /* 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; @@ -188,7 +222,7 @@ static int scmi_perf_domain_attributes_handler(fwk_id_t service_id, 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), + 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; @@ -206,8 +240,8 @@ static int scmi_perf_domain_attributes_handler(fwk_id_t service_id, }; /* Copy the domain name into the mailbox */ - strncpy((char *)return_values.name, fwk_module_get_name(domain_id), - sizeof(return_values.name) - 1); + 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, @@ -223,7 +257,6 @@ static int scmi_perf_describe_levels_handler(fwk_id_t service_id, 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; @@ -231,8 +264,10 @@ static int scmi_perf_describe_levels_handler(fwk_id_t service_id, size_t opp_count; struct mod_dvfs_opp opp; uint16_t latency; + struct scmi_perf_describe_levels_p2a return_values = { + .status = SCMI_GENERIC_ERROR, + }; - return_values.status = SCMI_GENERIC_ERROR; payload_size = sizeof(return_values); status = scmi_perf_ctx.scmi_api->get_max_payload_size(service_id, @@ -242,13 +277,12 @@ static int scmi_perf_describe_levels_handler(fwk_id_t service_id, status = (SCMI_PERF_LEVELS_MAX(max_payload_size) > 0) ? FWK_SUCCESS : FWK_E_SIZE; - assert(status == FWK_SUCCESS); if (status != FWK_SUCCESS) goto exit; + parameters = (const struct scmi_perf_describe_levels_a2p *)payload; /* 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; @@ -326,12 +360,13 @@ static int scmi_perf_limits_set_handler(fwk_id_t service_id, 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; + struct scmi_perf_limits_set_p2a 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; @@ -358,23 +393,21 @@ static int scmi_perf_limits_set_handler(fwk_id_t service_id, 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; + .minimum = parameters->range_min, + .maximum = parameters->range_max + })); - goto exit; - } + /* + * Return immediately to the caller, fire-and-forget. + */ - return_values = (struct scmi_perf_limits_set_p2a) { - .status = SCMI_SUCCESS, - }; + if ((status == FWK_SUCCESS) || (status == FWK_PENDING)) + return_values.status = SCMI_SUCCESS; + else + return_values.status = SCMI_OUT_OF_RANGE; exit: scmi_perf_ctx.scmi_api->respond(service_id, &return_values, @@ -388,11 +421,12 @@ static int scmi_perf_limits_get_handler(fwk_id_t service_id, const uint32_t *payload) { int status; + fwk_id_t domain_id; 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; + struct scmi_perf_event_parameters *evt_params; + struct scmi_perf_limits_get_p2a return_values = { + .status = SCMI_GENERIC_ERROR, + }; parameters = (const struct scmi_perf_limits_get_a2p *)payload; if (parameters->domain_id >= scmi_perf_ctx.domain_count) { @@ -402,21 +436,42 @@ static int scmi_perf_limits_get_handler(fwk_id_t service_id, 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) + domain_id = FWK_ID_ELEMENT(FWK_MODULE_IDX_DVFS, parameters->domain_id); + + /* Check if there is already a request pending for this domain */ + if (!fwk_id_is_equal( + scmi_perf_ctx.perf_ops_table[parameters->domain_id].service_id, + FWK_ID_NONE)){ + return_values.status = SCMI_BUSY; + 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 + /* The get_limits request is processed within the event being generated */ + struct fwk_event event = { + .target_id = fwk_module_id_scmi_perf, + .id = scmi_perf_get_limits, }; + evt_params = (struct scmi_perf_event_parameters *)event.params; + evt_params->domain_id = domain_id; + + status = fwk_thread_put_event(&event); + if (status != FWK_SUCCESS) { + return_values.status = SCMI_GENERIC_ERROR; + + goto exit; + } + + /* Store service identifier to indicate there is a pending request */ + scmi_perf_ctx.perf_ops_table[parameters->domain_id].service_id = service_id; + + return FWK_SUCCESS; + exit: scmi_perf_ctx.scmi_api->respond(service_id, &return_values, - (return_values.status == SCMI_SUCCESS) ? - sizeof(return_values) : sizeof(return_values.status)); + sizeof(return_values.status)); return status; } @@ -428,12 +483,13 @@ static int scmi_perf_level_set_handler(fwk_id_t service_id, 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; + struct scmi_perf_level_set_p2a 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; @@ -454,21 +510,17 @@ static int scmi_perf_level_set_handler(fwk_id_t service_id, 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; + FWK_ID_ELEMENT(FWK_MODULE_IDX_DVFS, parameters->domain_id), + (uint64_t)parameters->performance_level); + + /* + * Return immediately to the caller, fire-and-forget. + */ + if ((status == FWK_SUCCESS) || (status == FWK_PENDING)) + return_values.status = SCMI_SUCCESS; + else if (status == FWK_E_RANGE) 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, @@ -483,8 +535,10 @@ static int scmi_perf_level_get_handler(fwk_id_t service_id, { int status; const struct scmi_perf_level_get_a2p *parameters; - struct scmi_perf_level_get_p2a return_values; - struct mod_dvfs_opp opp; + struct scmi_perf_event_parameters *evt_params; + struct scmi_perf_level_get_p2a return_values = { + .status = SCMI_GENERIC_ERROR, + }; return_values.status = SCMI_GENERIC_ERROR; @@ -496,17 +550,38 @@ static int scmi_perf_level_get_handler(fwk_id_t service_id, 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) + /* Check if there is already a request pending for this domain */ + if (!fwk_id_is_equal( + scmi_perf_ctx.perf_ops_table[parameters->domain_id].service_id, + FWK_ID_NONE)){ + return_values.status = SCMI_BUSY; + 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, + /* The get_level request is processed within the event being generated */ + struct fwk_event event = { + .target_id = fwk_module_id_scmi_perf, + .id = scmi_perf_get_level, }; + evt_params = (struct scmi_perf_event_parameters *)event.params; + evt_params->domain_id = FWK_ID_ELEMENT(FWK_MODULE_IDX_DVFS, + parameters->domain_id); + + status = fwk_thread_put_event(&event); + if (status != FWK_SUCCESS) { + return_values.status = SCMI_GENERIC_ERROR; + + goto exit; + } + + /* Store service identifier to indicate there is a pending request */ + scmi_perf_ctx.perf_ops_table[parameters->domain_id].service_id = service_id; + + return FWK_SUCCESS; + exit: scmi_perf_ctx.scmi_api->respond(service_id, &return_values, (return_values.status == SCMI_SUCCESS) ? @@ -561,6 +636,33 @@ static struct mod_scmi_to_protocol_api scmi_perf_mod_scmi_to_protocol_api = { .message_handler = scmi_perf_message_handler }; +/* + * Static helpers for responding to SCMI. + */ +static void scmi_perf_respond( + void *return_values, + fwk_id_t domain_id, + int size) +{ + int idx = fwk_id_get_element_idx(domain_id); + fwk_id_t service_id; + + /* + * The service identifier used for the response is retrieved from the + * domain operations table. + */ + service_id = scmi_perf_ctx.perf_ops_table[idx].service_id; + + scmi_perf_ctx.scmi_api->respond(service_id, + return_values, size); + + /* + * Set the service identifier to 'none' to indicate the domain is + * available again. + */ + scmi_perf_ctx.perf_ops_table[idx].service_id = FWK_ID_NONE; +} + /* * Framework handlers */ @@ -568,6 +670,7 @@ static int scmi_perf_init(fwk_id_t module_id, unsigned int element_count, const void *data) { int return_val; + int i; const struct mod_scmi_perf_config *config = (const struct mod_scmi_perf_config *)data; @@ -579,9 +682,18 @@ static int scmi_perf_init(fwk_id_t module_id, unsigned int element_count, if (return_val <= 0) return FWK_E_SUPPORT; + scmi_perf_ctx.perf_ops_table = fwk_mm_calloc(return_val, + sizeof(struct perf_operations)); + if (scmi_perf_ctx.perf_ops_table == NULL) + return FWK_E_NOMEM; + scmi_perf_ctx.config = config; scmi_perf_ctx.domain_count = return_val; + /* Initialize table */ + for (i = 0; i < return_val; i++) + scmi_perf_ctx.perf_ops_table[i].service_id = FWK_ID_NONE; + return FWK_SUCCESS; } @@ -612,12 +724,135 @@ static int scmi_perf_process_bind_request(fwk_id_t source_id, return FWK_SUCCESS; } + +/* + * Handle a request for get_level/limits. + */ +static int process_request_event(const struct fwk_event *event) +{ + int status; + struct mod_dvfs_frequency_limits limits; + struct scmi_perf_event_parameters *params; + struct scmi_perf_level_get_p2a return_values_level; + struct scmi_perf_limits_get_p2a return_values_limits; + struct mod_dvfs_opp opp; + + /* request event to DVFS HAL */ + if (fwk_id_is_equal(event->id, scmi_perf_get_level)) { + params = (struct scmi_perf_event_parameters *)event->params; + + status = scmi_perf_ctx.dvfs_api->get_current_opp(params->domain_id, + &opp); + if (status == FWK_SUCCESS) { + /* DVFS value is ready */ + return_values_level = (struct scmi_perf_level_get_p2a) { + .status = SCMI_SUCCESS, + .performance_level = (uint32_t)opp.frequency, + }; + + scmi_perf_respond(&return_values_level, params->domain_id, + sizeof(return_values_level)); + + return status; + } else if (status == FWK_PENDING) { + /* DVFS value will be provided through a response event */ + return FWK_SUCCESS; + } else { + return_values_level = (struct scmi_perf_level_get_p2a) { + .status = SCMI_HARDWARE_ERROR, + }; + + scmi_perf_respond(&return_values_level, params->domain_id, + sizeof(return_values_level.status)); + + return FWK_E_DEVICE; + } + } + + if (fwk_id_is_equal(event->id, scmi_perf_get_limits)) { + params = (struct scmi_perf_event_parameters *)event->params; + + status = scmi_perf_ctx.dvfs_api->get_frequency_limits(params->domain_id, + &limits); + if (status == FWK_SUCCESS) { + /* DVFS value is ready */ + return_values_limits = (struct scmi_perf_limits_get_p2a) { + .status = SCMI_SUCCESS, + .range_min = limits.minimum, + .range_max = limits.maximum, + }; + + scmi_perf_respond(&return_values_limits, params->domain_id, + sizeof(return_values_limits)); + + return status; + } else if (status == FWK_PENDING) { + /* DVFS value will be provided through a response event */ + return FWK_SUCCESS; + } else { + return_values_limits = (struct scmi_perf_limits_get_p2a) { + .status = SCMI_HARDWARE_ERROR, + }; + + scmi_perf_respond(&return_values_limits, params->domain_id, + sizeof(return_values_limits.status)); + + return FWK_E_DEVICE; + } + } + + return FWK_E_PARAM; +} + +/* + * Handle a response event from the HAL which indicates that the + * requested operation has completed. + */ +static int process_response_event(const struct fwk_event *event) +{ + struct mod_dvfs_params_response *params_level; + struct scmi_perf_level_get_p2a return_values_level; + + if (fwk_id_is_equal(event->id, mod_dvfs_event_id_get_opp)) { + params_level = (struct mod_dvfs_params_response *) + event->params; + return_values_level = (struct scmi_perf_level_get_p2a) { + .status = params_level->status, + .performance_level = params_level->performance_level, + }; + scmi_perf_respond(&return_values_level, event->source_id, + (return_values_level.status == SCMI_SUCCESS) ? + sizeof(return_values_level) : + sizeof(return_values_level.status)); + } + + return FWK_SUCCESS; +} + +static int scmi_perf_process_event(const struct fwk_event *event, + struct fwk_event *resp_event) +{ + /* Request events from SCMI */ + if (fwk_id_get_module_idx(event->source_id) == + fwk_id_get_module_idx(fwk_module_id_scmi)) + return process_request_event(event); + + /* Response events from DVFS */ + if (fwk_id_get_module_idx(event->source_id) == + fwk_id_get_module_idx(fwk_module_id_dvfs)) + return process_response_event(event); + + return FWK_E_PARAM; +} + /* SCMI Performance Management Protocol Definition */ const struct fwk_module module_scmi_perf = { .name = "SCMI Performance Management Protocol", .api_count = 1, + .event_count = SCMI_PERF_EVENT_IDX_COUNT, .type = FWK_MODULE_TYPE_PROTOCOL, .init = scmi_perf_init, .bind = scmi_perf_bind, .process_bind_request = scmi_perf_process_bind_request, + .process_event = scmi_perf_process_event, }; diff --git a/product/juno/include/juno_alarm_idx.h b/product/juno/include/juno_alarm_idx.h index 26bffa3af5c2278a717bc1b77721859f8ecdda5d..0c71e9e07ef5b451545f85f73d202cc39fc77e29 100644 --- a/product/juno/include/juno_alarm_idx.h +++ b/product/juno/include/juno_alarm_idx.h @@ -17,7 +17,15 @@ enum juno_xrp7724_alarm_idx { JUNO_XRP7724_ALARM_IDX_COUNT, }; +/* Alarm indices for DVFS */ +enum juno_dvfs_alarm_idx { + JUNO_DVFS_ALARM_VLITTLE_IDX = JUNO_XRP7724_ALARM_IDX_COUNT, + JUNO_DVFS_ALARM_BIG_IDX, + JUNO_DVFS_ALARM_GPU_IDX, + JUNO_DVFS_ALARM_IDX_CNT, +}; + /* Total count of alarms */ -#define JUNO_ALARM_IDX_COUNT JUNO_XRP7724_ALARM_IDX_COUNT +#define JUNO_ALARM_IDX_COUNT JUNO_DVFS_ALARM_IDX_CNT #endif /* JUNO_ALARM_IDX_H */ diff --git a/product/juno/module/juno_cdcel937/src/juno_cdcel937.h b/product/juno/module/juno_cdcel937/src/juno_cdcel937.h index 288d3eb59e564f187d12f7db8e68336ff500f9f3..e9044fc462f4268c2bac70f354009c29c4ba753d 100644 --- a/product/juno/module/juno_cdcel937/src/juno_cdcel937.h +++ b/product/juno/module/juno_cdcel937/src/juno_cdcel937.h @@ -241,7 +241,6 @@ struct juno_cdcel937_dev_ctx { struct pll_cfg_reg pll_config; struct cfg_reg_y1 y1_config; uint64_t rate; - uint64_t *rate_addr; int index; }; diff --git a/product/juno/module/juno_cdcel937/src/mod_juno_cdcel937.c b/product/juno/module/juno_cdcel937/src/mod_juno_cdcel937.c index 17f5697d8b30abb8a62bd014d333c7cb56b5a427..fdf37a2a4102548e342e29a4c34da75f9b906b6e 100644 --- a/product/juno/module/juno_cdcel937/src/mod_juno_cdcel937.c +++ b/product/juno/module/juno_cdcel937/src/mod_juno_cdcel937.c @@ -714,7 +714,6 @@ static int juno_cdcel937_get_rate(fwk_id_t clock_id, ctx = &ctx_table[fwk_id_get_element_idx(clock_id)]; if (ctx->rate_set == false) { - ctx->rate_addr = rate; status = create_get_rate_request(clock_id, ctx); if ((status != FWK_PENDING) && (status != FWK_SUCCESS)) return FWK_E_DEVICE; @@ -1110,10 +1109,6 @@ static int juno_cdcel937_process_event(const struct fwk_event *event, module_ctx.state = JUNO_CDCEL937_DEVICE_IDLE; response_param.status = status; response_param.value.rate = ctx->rate_hz; - if (ctx->rate_addr) { - *(ctx->rate_addr) = ctx->rate; - ctx->rate_addr = NULL; - } ctx->driver_response_api->request_complete(ctx->config->clock_hal_id, &response_param); diff --git a/product/juno/module/juno_hdlcd/src/mod_juno_hdlcd.c b/product/juno/module/juno_hdlcd/src/mod_juno_hdlcd.c index 89a94a0d86e26fb80140b85b06c01dafae284e29..42820fea176db3dee2ae313f6e36bd8560281ba1 100644 --- a/product/juno/module/juno_hdlcd/src/mod_juno_hdlcd.c +++ b/product/juno/module/juno_hdlcd/src/mod_juno_hdlcd.c @@ -142,6 +142,8 @@ void juno_hdlcd_request_complete(fwk_id_t dev_id, ctx->driver_response_api->request_complete(ctx->config->clock_hal_id, response_param); + + module_ctx.request_clock_id = FWK_ID_NONE; } static const struct mod_clock_driver_response_api hdlcd_driver_response_api = { @@ -171,6 +173,11 @@ static int juno_hdlcd_set_rate(fwk_id_t clock_id, uint64_t rate, (rounded_rate > ctx->config->max_rate)) return FWK_E_RANGE; + if (platform == JUNO_IDX_PLATFORM_RTL) { + if (!fwk_id_is_equal(module_ctx.request_clock_id, FWK_ID_NONE)) + return FWK_E_BUSY; + } + /* * Clock rate is always twice the pixel clock rate. This is because Juno has * an implicit "divide by 2" stage. @@ -233,7 +240,8 @@ static int juno_hdlcd_set_rate(fwk_id_t clock_id, uint64_t rate, "[HDLCD] Failed to set board clock\n"); return FWK_E_DEVICE; } - module_ctx.request_clock_id = clock_id; + if (status == FWK_PENDING) + module_ctx.request_clock_id = clock_id; return status; } @@ -438,6 +446,7 @@ static int juno_hdlcd_start(fwk_id_t id) module_ctx.current_pll_rate = ((uint64_t)(PXL_REF_CLK_RATE) * nf) / (nr * od); + module_ctx.request_clock_id = FWK_ID_NONE; return FWK_SUCCESS; }