mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-08-05 16:54:27 +00:00

Return 0 instead of -EINVAL if function ccu_pll_recalc_rate() fails to
get correct rate entry. Follow .recalc_rate callback documentation
as mentioned in include/linux/clk-provider.h for error return value.
Signed-off-by: Akhilesh Patil <akhilesh@ee.iitb.ac.in>
Fixes: 1b72c59db0
("clk: spacemit: Add clock support for SpacemiT K1 SoC")
Reviewed-by: Haylen Chu <heylenay@4d2.org>
Reviewed-by: Alex Elder <elder@riscstar.com>
Link: https://lore.kernel.org/r/aIBzVClNQOBrjIFG@bhairav-test.ee.iitb.ac.in
Signed-off-by: Stephen Boyd <sboyd@kernel.org>
157 lines
3.7 KiB
C
157 lines
3.7 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2024 SpacemiT Technology Co. Ltd
|
|
* Copyright (c) 2024-2025 Haylen Chu <heylenay@4d2.org>
|
|
*/
|
|
|
|
#include <linux/clk-provider.h>
|
|
#include <linux/math.h>
|
|
#include <linux/regmap.h>
|
|
|
|
#include "ccu_common.h"
|
|
#include "ccu_pll.h"
|
|
|
|
#define PLL_TIMEOUT_US 3000
|
|
#define PLL_DELAY_US 5
|
|
|
|
#define PLL_SWCR3_EN ((u32)BIT(31))
|
|
#define PLL_SWCR3_MASK GENMASK(30, 0)
|
|
|
|
static const struct ccu_pll_rate_tbl *ccu_pll_lookup_best_rate(struct ccu_pll *pll,
|
|
unsigned long rate)
|
|
{
|
|
struct ccu_pll_config *config = &pll->config;
|
|
const struct ccu_pll_rate_tbl *best_entry;
|
|
unsigned long best_delta = ULONG_MAX;
|
|
int i;
|
|
|
|
for (i = 0; i < config->tbl_num; i++) {
|
|
const struct ccu_pll_rate_tbl *entry = &config->rate_tbl[i];
|
|
unsigned long delta = abs_diff(entry->rate, rate);
|
|
|
|
if (delta < best_delta) {
|
|
best_delta = delta;
|
|
best_entry = entry;
|
|
}
|
|
}
|
|
|
|
return best_entry;
|
|
}
|
|
|
|
static const struct ccu_pll_rate_tbl *ccu_pll_lookup_matched_entry(struct ccu_pll *pll)
|
|
{
|
|
struct ccu_pll_config *config = &pll->config;
|
|
u32 swcr1, swcr3;
|
|
int i;
|
|
|
|
swcr1 = ccu_read(&pll->common, swcr1);
|
|
swcr3 = ccu_read(&pll->common, swcr3);
|
|
swcr3 &= PLL_SWCR3_MASK;
|
|
|
|
for (i = 0; i < config->tbl_num; i++) {
|
|
const struct ccu_pll_rate_tbl *entry = &config->rate_tbl[i];
|
|
|
|
if (swcr1 == entry->swcr1 && swcr3 == entry->swcr3)
|
|
return entry;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void ccu_pll_update_param(struct ccu_pll *pll, const struct ccu_pll_rate_tbl *entry)
|
|
{
|
|
struct ccu_common *common = &pll->common;
|
|
|
|
regmap_write(common->regmap, common->reg_swcr1, entry->swcr1);
|
|
ccu_update(common, swcr3, PLL_SWCR3_MASK, entry->swcr3);
|
|
}
|
|
|
|
static int ccu_pll_is_enabled(struct clk_hw *hw)
|
|
{
|
|
struct ccu_common *common = hw_to_ccu_common(hw);
|
|
|
|
return ccu_read(common, swcr3) & PLL_SWCR3_EN;
|
|
}
|
|
|
|
static int ccu_pll_enable(struct clk_hw *hw)
|
|
{
|
|
struct ccu_pll *pll = hw_to_ccu_pll(hw);
|
|
struct ccu_common *common = &pll->common;
|
|
unsigned int tmp;
|
|
|
|
ccu_update(common, swcr3, PLL_SWCR3_EN, PLL_SWCR3_EN);
|
|
|
|
/* check lock status */
|
|
return regmap_read_poll_timeout_atomic(common->lock_regmap,
|
|
pll->config.reg_lock,
|
|
tmp,
|
|
tmp & pll->config.mask_lock,
|
|
PLL_DELAY_US, PLL_TIMEOUT_US);
|
|
}
|
|
|
|
static void ccu_pll_disable(struct clk_hw *hw)
|
|
{
|
|
struct ccu_common *common = hw_to_ccu_common(hw);
|
|
|
|
ccu_update(common, swcr3, PLL_SWCR3_EN, 0);
|
|
}
|
|
|
|
/*
|
|
* PLLs must be gated before changing rate, which is ensured by
|
|
* flag CLK_SET_RATE_GATE.
|
|
*/
|
|
static int ccu_pll_set_rate(struct clk_hw *hw, unsigned long rate,
|
|
unsigned long parent_rate)
|
|
{
|
|
struct ccu_pll *pll = hw_to_ccu_pll(hw);
|
|
const struct ccu_pll_rate_tbl *entry;
|
|
|
|
entry = ccu_pll_lookup_best_rate(pll, rate);
|
|
ccu_pll_update_param(pll, entry);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static unsigned long ccu_pll_recalc_rate(struct clk_hw *hw,
|
|
unsigned long parent_rate)
|
|
{
|
|
struct ccu_pll *pll = hw_to_ccu_pll(hw);
|
|
const struct ccu_pll_rate_tbl *entry;
|
|
|
|
entry = ccu_pll_lookup_matched_entry(pll);
|
|
|
|
WARN_ON_ONCE(!entry);
|
|
|
|
return entry ? entry->rate : 0;
|
|
}
|
|
|
|
static long ccu_pll_round_rate(struct clk_hw *hw, unsigned long rate,
|
|
unsigned long *prate)
|
|
{
|
|
struct ccu_pll *pll = hw_to_ccu_pll(hw);
|
|
|
|
return ccu_pll_lookup_best_rate(pll, rate)->rate;
|
|
}
|
|
|
|
static int ccu_pll_init(struct clk_hw *hw)
|
|
{
|
|
struct ccu_pll *pll = hw_to_ccu_pll(hw);
|
|
|
|
if (ccu_pll_lookup_matched_entry(pll))
|
|
return 0;
|
|
|
|
ccu_pll_disable(hw);
|
|
ccu_pll_update_param(pll, &pll->config.rate_tbl[0]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
const struct clk_ops spacemit_ccu_pll_ops = {
|
|
.init = ccu_pll_init,
|
|
.enable = ccu_pll_enable,
|
|
.disable = ccu_pll_disable,
|
|
.set_rate = ccu_pll_set_rate,
|
|
.recalc_rate = ccu_pll_recalc_rate,
|
|
.round_rate = ccu_pll_round_rate,
|
|
.is_enabled = ccu_pll_is_enabled,
|
|
};
|