rt-thread/bsp/allwinner/libraries/sunxi-hal/hal/source/ccmu/sunxi-ng/ccu.c

1032 lines
22 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* Copyright (c) 2019-2025 Allwinner Technology Co., Ltd. ALL rights reserved.
*
* Allwinner is a trademark of Allwinner Technology Co.,Ltd., registered in
*the the People's Republic of China and other countries.
* All Allwinner Technology Co.,Ltd. trademarks are used with permission.
*
* DISCLAIMER
* THIRD PARTY LICENCES MAY BE REQUIRED TO IMPLEMENT THE SOLUTION/PRODUCT.
* IF YOU NEED TO INTEGRATE THIRD PARTYS TECHNOLOGY (SONY, DTS, DOLBY, AVS OR MPEGLA, ETC.)
* IN ALLWINNERSSDK OR PRODUCTS, YOU SHALL BE SOLELY RESPONSIBLE TO OBTAIN
* ALL APPROPRIATELY REQUIRED THIRD PARTY LICENCES.
* ALLWINNER SHALL HAVE NO WARRANTY, INDEMNITY OR OTHER OBLIGATIONS WITH RESPECT TO MATTERS
* COVERED UNDER ANY REQUIRED THIRD PARTY LICENSE.
* YOU ARE SOLELY RESPONSIBLE FOR YOUR USAGE OF THIRD PARTYS TECHNOLOGY.
*
*
* THIS SOFTWARE IS PROVIDED BY ALLWINNER"AS IS" AND TO THE MAXIMUM EXTENT
* PERMITTED BY LAW, ALLWINNER EXPRESSLY DISCLAIMS ALL WARRANTIES OF ANY KIND,
* WHETHER EXPRESS, IMPLIED OR STATUTORY, INCLUDING WITHOUT LIMITATION REGARDING
* THE TITLE, NON-INFRINGEMENT, ACCURACY, CONDITION, COMPLETENESS, PERFORMANCE
* OR MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
* IN NO EVENT SHALL ALLWINNER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS, OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "ccu.h"
#include <stdlib.h>
#include <hal_clk.h>
#include <sunxi_hal_common.h>
static LIST_HEAD(clk_root_list);
const char *clk_hw_get_name(const struct clk_hw *hw)
{
if (!hw || !hw->core)
return NULL;
return hw->core->name;
}
unsigned long clk_hw_get_flags(const struct clk_hw *hw)
{
if (!hw || !hw->core)
return 0;
return hw->core->flags;
}
struct clk_core *clk_hw_get_core(const struct clk_hw *hw)
{
if (!hw || !hw->core)
return NULL;
return hw->core;
}
struct clk_core *clk_core_get_by_name(const char *name)
{
struct clk_core *core = NULL;
list_for_each_entry(core, &clk_root_list, node)
{
if (strcmp(name, core->name))
{
continue;
}
return core;
}
return NULL;
}
static struct clk_core *clk_core_get_by_pindex(struct clk_core *core, u8 p_index)
{
if (p_index > core->num_parents)
{
return NULL;
}
return clk_core_get_by_name(core->parents[p_index].name);
}
static void clk_core_fill_parent_index(struct clk_core *core, u8 index)
{
struct clk_parent_map *entry = &core->parents[index];
struct clk_core *parent = NULL;
if (entry->hw)
{
parent = entry->hw->core;
/*
* We have a direct reference but it isn't registered yet?
* Orphan it and let clk_reparent() update the orphan status
* when the parent is registered.
*/
}
else
{
parent = clk_core_get_by_pindex(core, index);
}
/* Only cache it if it's not an error */
if (parent)
{
entry->core = parent;
}
}
static struct clk_core *clk_core_get_parent_by_index(struct clk_core *core,
u8 index)
{
if (!core || index >= core->num_parents || !core->parents)
{
return NULL;
}
if (!core->parents[index].core)
{
clk_core_fill_parent_index(core, index);
}
return core->parents[index].core;
}
struct clk_hw *
clk_hw_get_parent_by_index(const struct clk_hw *hw, unsigned int index)
{
struct clk_core *parent;
parent = clk_core_get_parent_by_index(hw->core, index);
return !parent ? NULL : parent->hw;
}
unsigned int clk_hw_get_num_parents(const struct clk_hw *hw)
{
return hw->core->num_parents;
}
struct clk_hw *clk_hw_get_parent(const struct clk_hw *hw)
{
return hw->core->parent ? hw->core->parent->hw : NULL;
}
static u32 __clk_get_accuracy(struct clk_core *core)
{
if (!core)
{
return 0;
}
return core->accuracy;
}
static unsigned long clk_core_get_rate_nolock(struct clk_core *core)
{
if (!core)
{
return 0;
}
if (!core->num_parents || core->parent)
{
return core->rate;
}
/*
* Clk must have a parent because num_parents > 0 but the parent isn't
* known yet. Best to return 0 as the rate of this clk until we can
* properly recalc the rate based on the parent's rate.
*/
return 0;
}
static int clk_core_determine_round_nolock(struct clk_core *core,
struct clk_rate_request *req)
{
long rate;
if (!core)
{
return 0;
}
/*
* At this point, core protection will be disabled if
* - if the provider is not protected at all
* - if the calling consumer is the only one which has exclusivity
* over the provider
*/
if (core->ops->determine_rate)
{
return core->ops->determine_rate(core->hw, req);
}
else if (core->ops->round_rate)
{
rate = core->ops->round_rate(core->hw, req->rate,
&req->best_parent_rate);
if (rate < 0)
{
return rate;
}
req->rate = rate;
}
else
{
return -1;
}
return 0;
}
static void clk_core_init_rate_req(struct clk_core *const core,
struct clk_rate_request *req)
{
struct clk_core *parent;
if (!core || !req)
{
return;
}
parent = core->parent;
if (parent)
{
req->best_parent_hw = parent->hw;
req->best_parent_rate = parent->rate;
}
else
{
req->best_parent_hw = NULL;
req->best_parent_rate = 0;
}
}
static u8 clk_core_can_round(struct clk_core *const core)
{
return core->ops->determine_rate || core->ops->round_rate;
}
static int clk_core_round_rate_nolock(struct clk_core *core,
struct clk_rate_request *req)
{
if (!core)
{
req->rate = 0;
return 0;
}
clk_core_init_rate_req(core, req);
if (clk_core_can_round(core))
{
return clk_core_determine_round_nolock(core, req);
}
else if (core->flags & CLK_SET_RATE_PARENT)
{
return clk_core_round_rate_nolock(core->parent, req);
}
req->rate = core->rate;
return 0;
}
/**
* __clk_determine_rate - get the closest rate actually supported by a clock
* @hw: determine the rate of this clock
* @req: target rate request
*
* Useful for clk_ops such as .set_rate and .determine_rate.
*/
int __clk_determine_rate(struct clk_hw *hw, struct clk_rate_request *req)
{
if (!hw)
{
req->rate = 0;
return 0;
}
return clk_core_round_rate_nolock(hw->core, req);
}
static u8 mux_is_better_rate(unsigned long rate, unsigned long now,
unsigned long best, unsigned long flags)
{
if (flags & CLK_MUX_ROUND_CLOSEST)
{
return abs(now - rate) < abs(best - rate);
}
return now <= rate && now > best;
}
int clk_mux_determine_rate_flags(struct clk_hw *hw,
struct clk_rate_request *req,
unsigned long flags)
{
struct clk_core *core = hw->core, *parent, *best_parent = NULL;
int i, num_parents, ret;
unsigned long best = 0;
struct clk_rate_request parent_req = *req;
/* if NO_REPARENT flag set, pass through to current parent */
if (core->flags & CLK_SET_RATE_NO_REPARENT)
{
parent = core->parent;
if (core->flags & CLK_SET_RATE_PARENT)
{
ret = __clk_determine_rate(parent ? parent->hw : NULL,
&parent_req);
if (ret)
{
return ret;
}
best = parent_req.rate;
}
else if (parent)
{
best = clk_core_get_rate_nolock(parent);
}
else
{
best = clk_core_get_rate_nolock(core);
}
goto out;
}
/* find the parent that can provide the fastest rate <= rate */
num_parents = core->num_parents;
for (i = 0; i < num_parents; i++)
{
parent = clk_core_get_parent_by_index(core, i);
if (!parent)
{
continue;
}
if (core->flags & CLK_SET_RATE_PARENT)
{
parent_req = *req;
ret = __clk_determine_rate(parent->hw, &parent_req);
if (ret)
{
continue;
}
}
else
{
parent_req.rate = clk_core_get_rate_nolock(parent);
}
if (mux_is_better_rate(req->rate, parent_req.rate,
best, flags))
{
best_parent = parent;
best = parent_req.rate;
}
}
if (!best_parent)
{
return -1;
}
out:
if (best_parent)
{
req->best_parent_hw = best_parent->hw;
}
req->best_parent_rate = best;
req->rate = best;
return 0;
}
/*
* __clk_mux_determine_rate - clk_ops::determine_rate implementation for a mux type clk
* @hw: mux type clk to determine rate on
* @req: rate request, also used to return preferred parent and frequencies
*
* Helper for finding best parent to provide a given frequency. This can be used
* directly as a determine_rate callback (e.g. for a mux), or from a more
* complex clock that may combine a mux with other operations.
*
* Returns: 0 on success, -EERROR value on error
*/
int __clk_mux_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
return clk_mux_determine_rate_flags(hw, req, 0);
}
static void clk_core_get_boundaries(struct clk_core *core,
unsigned long *min_rate,
unsigned long *max_rate)
{
*min_rate = core->min_rate;
*max_rate = core->max_rate;
}
unsigned long clk_hw_round_rate(struct clk_hw *hw, unsigned long rate)
{
int ret;
struct clk_rate_request req;
if (!hw)
{
return 0;
}
clk_core_get_boundaries(hw->core, &req.min_rate, &req.max_rate);
req.rate = rate;
ret = clk_core_round_rate_nolock(hw->core, &req);
if (ret)
{
return 0;
}
return req.rate;
}
static struct clk_core *__clk_init_parent(struct clk_core *core)
{
u8 index = 0;
if (!core->num_parents)
{
return NULL;
}
if (core->num_parents > 1 && core->ops->get_parent)
{
index = core->ops->get_parent(core->hw);
}
return clk_core_get_parent_by_index(core, index);
}
static int __clk_core_init(struct clk_core *core)
{
u32 rate;
int ret = -1;
if (!core)
{
return ret;
}
if (core->ops->init)
{
core->ops->init(core->hw);
}
core->parent = __clk_init_parent(core);
if (core->ops->recalc_accuracy)
{
core->accuracy = core->ops->recalc_accuracy(core->hw,
__clk_get_accuracy(core->parent));
}
else if (core->parent)
{
core->accuracy = core->parent->accuracy;
}
else
{
core->accuracy = 0;
}
if (core->ops->recalc_rate)
{
rate = core->ops->recalc_rate(core->hw, clk_core_get_rate(core->parent));
}
else if (core->parent)
{
rate = core->parent->rate;
}
else
{
rate = 0;
}
if (core->parent)
{
core->p_rate = core->parent->rate;
}
else
{
core->p_rate = 0;
}
core->rate = rate;
if (core->flags & CLK_IS_CRITICAL)
{
ret = clk_core_enable(core);
if (ret)
{
return ret;
}
}
return 0;
}
static int clk_core_populate_parent_map(struct clk_core *core,
const struct clk_init_data *init)
{
u8 num_parents = init->num_parents;
const char *const *parent_names = init->parent_names;
const struct clk_hw **parent_hws = init->parent_hws;
const struct clk_parent_data *parent_data = init->parent_data;
int i, ret = 0;
struct clk_parent_map *parents, *parent;
if (!num_parents)
{
return 0;
}
/*
* Avoid unnecessary string look-ups of clk_core's possible parents by
* having a cache of names/clk_hw pointers to clk_core pointers.
*/
parents = (struct clk_parent_map *)malloc(num_parents * sizeof(*parents));
core->parents = parents;
if (!parents)
{
return -1;
}
/* Copy everything over because it might be __initdata */
for (i = 0, parent = parents; i < num_parents; i++, parent++)
{
parent->index = -1;
parent->core = NULL;
parent->hw = NULL;
parent->name = NULL;
parent->fw_name = NULL;
if (parent_names)
{
if (!parent_names[i])
{
hal_log_err("invalid NULL in %s's .parent_names\n", core->name);
}
else
{
parent->name = parent_names[i];
}
}
else if (parent_data)
{
parent->hw = parent_data[i].hw;
parent->index = parent_data[i].index;
if (parent_data[i].name)
{
parent->name = parent_data[i].name;
}
else
{
parent->name = parent_data[i].fw_name;
}
}
else if (parent_hws)
{
parent->hw = parent_hws[i];
}
else
{
ret = -1;
hal_log_err("Must specify parents if num_parents > 0\n");
}
if (ret)
{
core->parents = NULL;
free(parents);
return ret;
}
}
return 0;
}
#if 0
static void clk_core_free_parent_map(struct clk_core *core)
{
if (!core->num_parents)
{
return;
}
free(core->parents);
}
#endif
int clk_hw_register(struct clk_hw *hw)
{
int ret = -1;
struct clk_core *core;
struct clk_init_data *init;
if (!hw)
{
return 0;
}
init = hw->init;
core = (struct clk_core *)malloc(sizeof(*core));
if (!core)
{
hal_log_err("out of memory\n");
return -1;
}
core->name = init->name;
core->num_parents = init->num_parents;
core->flags = init->flags;
core->hw = hw;
core->ops = hw->init->ops;
core->min_rate = 0;
core->max_rate = ULONG_MAX;
core->enable_count = 0;
core->clk = (struct clk *)malloc(sizeof(struct clk));
if (!core->clk)
{
hal_log_err("out of memory\n");
goto fail_clk;
}
core->clk->core = core;
core->clk->name = core->name;
core->clk->count = 0;
ret = clk_core_populate_parent_map(core, init);
if (ret)
{
goto fail_parents;
}
ret = __clk_core_init(core);
if (ret)
{
free(core);
return -1;
}
hw->core = core;
list_add_tail(&core->node, &clk_root_list);
return ret;
fail_parents:
free(core->clk);
core->clk = NULL;
fail_clk:
free(core);
core = NULL;
return ret;
}
int clk_hw_unregister(struct clk_hw *hw)
{
struct clk_core *core;
if (!hw || !hw->core)
{
return 0;
}
list_for_each_entry(core, &clk_root_list, node)
{
if (core->hw->type != hw->type)
{
continue;
}
if (core->hw->id != hw->id)
{
continue;
}
list_del(&core->node);
free(core->clk);
free(core);
return 0;
}
return 0;
}
struct clk_core *clk_core_get(hal_clk_type_t type, hal_clk_id_t id)
{
struct clk_core *core = NULL;
list_for_each_entry(core, &clk_root_list, node)
{
if (core->hw->type != type)
{
continue;
}
if (core->hw->id != id)
{
continue;
}
return core;
}
return NULL;
}
hal_clk_status_t clk_core_is_enabled(struct clk_core *core)
{
/*
* .is_enabled is only mandatory for clocks that gate
* fall back to software usage counter if .is_enabled is missing
*/
if (!core->ops->is_enabled)
{
return !!core->enable_count;
}
return core->ops->is_enabled(core->hw);
}
hal_clk_status_t clk_core_enable(struct clk_core *core)
{
int ret = 0;
if (!core)
{
return 0;
}
if (core->enable_count == 0)
{
ret = clk_core_enable(core->parent);
if (ret)
{
return ret;
}
if (core->ops->enable)
{
ret = core->ops->enable(core->hw);
}
if (ret)
{
clk_core_disable(core->parent);
return ret;
}
}
core->enable_count++;
return 0;
}
hal_clk_status_t clk_core_disable(struct clk_core *core)
{
if (!core)
{
return 0;
}
if (core->enable_count == 0)
{
return 0;
}
if (--core->enable_count > 0)
{
return 0;
}
if (core->ops->disable)
{
core->ops->disable(core->hw);
}
clk_core_disable(core->parent);
return 0;
}
u32 clk_core_round_rate(struct clk_core *core, u32 rate)
{
struct clk_rate_request req;
int ret;
if (!core)
{
return 0;
}
clk_core_get_boundaries(core, &req.min_rate, &req.max_rate);
req.rate = rate;
ret = clk_core_round_rate_nolock(core, &req);
if (ret)
{
return ret;
}
return req.rate;
}
u32 clk_core_recalc_rate(struct clk_core *core, struct clk_core *p_core)
{
u32 p_rate = 0;
if (!core)
{
return 0;
}
if (!p_core)
{
return core->rate;
}
clk_core_recalc_rate(p_core, p_core->parent);
p_rate = p_core->rate;
if (core->ops->recalc_rate)
{
return core->ops->recalc_rate(core->hw, p_rate);
}
return core->rate;
}
u32 clk_core_get_rate(struct clk_core *core)
{
if (!core)
{
return 0;
}
core->parent = __clk_init_parent(core);
return clk_core_recalc_rate(core, core->parent);
}
u32 clk_hw_get_rate(const struct clk_hw *hw)
{
return clk_core_get_rate(hw->core);
}
hal_clk_status_t clk_core_set_rate(struct clk_core *core, struct clk_core *p_core, unsigned long rate)
{
u8 ret = -1;
if (!core || !p_core)
{
hal_log_err("core or p_core is NULL\n");
return ret;
}
if (core->ops->set_rate)
{
ret = core->ops->set_rate(core->hw, rate, p_core->rate);
}
if (ret)
{
return ret;
}
core->rate = rate;
return ret;
}
hal_clk_status_t clk_hw_set_rate(struct clk_hw *hw, unsigned long rate)
{
struct clk_core *core, *p_core;
core = clk_hw_get_core(hw);
p_core = clk_core_get_parent(core);
return clk_core_set_rate(core, p_core, rate);
}
static int __clk_set_parent(struct clk_core *core, struct clk_core *parent,
u8 p_index)
{
int ret = 0;
/* change clock input source */
if (parent && core->ops->set_parent)
{
ret = core->ops->set_parent(core->hw, p_index);
}
return ret;
}
static int clk_fetch_parent_index(struct clk_core *core,
struct clk_core *parent)
{
int i;
if (!parent)
{
return -1;
}
for (i = 0; i < core->num_parents; i++)
{
/* Found it first try! */
if (core->parents[i].core)
{
if (core->parents[i].core == parent)
{
return i;
}
}
/* Something else is here, so keep looking */
if (core->parents[i].core)
{
continue;
}
/* Maybe core hasn't been cached but the hw is all we know? */
if (core->parents[i].hw)
{
if (core->parents[i].hw == parent->hw)
{
break;
}
/* Didn't match, but we're expecting a clk_hw */
continue;
}
/* Fallback to comparing globally unique names */
if (core->parents[i].name &&
!strcmp(parent->name, core->parents[i].name))
{
break;
}
}
if (i == core->num_parents)
{
return -1;
}
core->parents[i].core = parent;
return i;
}
struct clk_core *clk_core_get_parent(struct clk_core *core)
{
if (!core)
{
return NULL;
}
if (!core->parent)
core->parent = __clk_init_parent(core);
return core->parent;
}
hal_clk_status_t clk_core_set_parent(struct clk_core *core, struct clk_core *parent)
{
int ret = 0;
int p_index = 0;
uint32_t p_rate = 0;
if (!core)
{
return 0;
}
if (core->parent == parent)
{
return 0;
}
/* verify ops for for multi-parent clks */
if ((core->num_parents > 1) && (!core->ops->set_parent))
{
ret = -1;
goto out;
}
/* try finding the new parent index */
if (parent)
{
p_index = clk_fetch_parent_index(core, parent);
if (p_index < 0)
{
printf("%s: clk %s can not be parent of clk %s\n",
__func__, parent->name, core->name);
ret = p_index;
goto out;
}
p_rate = parent->rate;
}
/* do the re-parent */
ret = __clk_set_parent(core, parent, p_index);
/* propagate rate an accuracy recalculation accordingly */
if (!ret)
{
core->parent = parent;
core->p_rate = p_rate;
core->rate = clk_core_recalc_rate(core, parent);
}
out:
return ret;
}