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