diff options
Diffstat (limited to 'module/pik_clock')
-rw-r--r-- | module/pik_clock/include/mod_pik_clock.h | 269 | ||||
-rw-r--r-- | module/pik_clock/src/Makefile | 11 | ||||
-rw-r--r-- | module/pik_clock/src/mod_pik_clock.c | 767 |
3 files changed, 1047 insertions, 0 deletions
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, +}; |