aboutsummaryrefslogtreecommitdiff
path: root/module/pik_clock
diff options
context:
space:
mode:
Diffstat (limited to 'module/pik_clock')
-rw-r--r--module/pik_clock/include/mod_pik_clock.h269
-rw-r--r--module/pik_clock/src/Makefile11
-rw-r--r--module/pik_clock/src/mod_pik_clock.c767
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,
+};