diff options
Diffstat (limited to 'drivers/clk/sunxi-ng/ccu_nkm.c')
| -rw-r--r-- | drivers/clk/sunxi-ng/ccu_nkm.c | 89 |
1 files changed, 74 insertions, 15 deletions
diff --git a/drivers/clk/sunxi-ng/ccu_nkm.c b/drivers/clk/sunxi-ng/ccu_nkm.c index 841840e35e61..784eec9ac997 100644 --- a/drivers/clk/sunxi-ng/ccu_nkm.c +++ b/drivers/clk/sunxi-ng/ccu_nkm.c @@ -1,14 +1,11 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2016 Maxime Ripard * Maxime Ripard <maxime.ripard@free-electrons.com> - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. */ #include <linux/clk-provider.h> +#include <linux/io.h> #include "ccu_gate.h" #include "ccu_nkm.h" @@ -19,8 +16,64 @@ struct _ccu_nkm { unsigned long m, min_m, max_m; }; -static void ccu_nkm_find_best(unsigned long parent, unsigned long rate, - struct _ccu_nkm *nkm) +static bool ccu_nkm_is_valid_rate(struct ccu_common *common, unsigned long parent, + unsigned long n, unsigned long m) +{ + struct ccu_nkm *nkm = container_of(common, struct ccu_nkm, common); + + if (nkm->max_m_n_ratio && (m > nkm->max_m_n_ratio * n)) + return false; + + if (nkm->min_parent_m_ratio && (parent < nkm->min_parent_m_ratio * m)) + return false; + + return true; +} + +static unsigned long ccu_nkm_find_best_with_parent_adj(struct ccu_common *common, + struct clk_hw *parent_hw, + unsigned long *parent, unsigned long rate, + struct _ccu_nkm *nkm) +{ + unsigned long best_rate = 0, best_parent_rate = *parent; + unsigned long best_n = 0, best_k = 0, best_m = 0; + unsigned long _n, _k, _m; + + for (_k = nkm->min_k; _k <= nkm->max_k; _k++) { + for (_n = nkm->min_n; _n <= nkm->max_n; _n++) { + for (_m = nkm->min_m; _m <= nkm->max_m; _m++) { + unsigned long tmp_rate, tmp_parent; + + tmp_parent = clk_hw_round_rate(parent_hw, rate * _m / (_n * _k)); + + if (!ccu_nkm_is_valid_rate(common, tmp_parent, _n, _m)) + continue; + + tmp_rate = tmp_parent * _n * _k / _m; + + if (ccu_is_better_rate(common, rate, tmp_rate, best_rate) || + (tmp_parent == *parent && tmp_rate == best_rate)) { + best_rate = tmp_rate; + best_parent_rate = tmp_parent; + best_n = _n; + best_k = _k; + best_m = _m; + } + } + } + } + + nkm->n = best_n; + nkm->k = best_k; + nkm->m = best_m; + + *parent = best_parent_rate; + + return best_rate; +} + +static unsigned long ccu_nkm_find_best(unsigned long parent, unsigned long rate, + struct _ccu_nkm *nkm, struct ccu_common *common) { unsigned long best_rate = 0; unsigned long best_n = 0, best_k = 0, best_m = 0; @@ -29,13 +82,14 @@ static void ccu_nkm_find_best(unsigned long parent, unsigned long rate, for (_k = nkm->min_k; _k <= nkm->max_k; _k++) { for (_n = nkm->min_n; _n <= nkm->max_n; _n++) { for (_m = nkm->min_m; _m <= nkm->max_m; _m++) { + if (!ccu_nkm_is_valid_rate(common, parent, _n, _m)) + continue; + unsigned long tmp_rate; tmp_rate = parent * _n * _k / _m; - if (tmp_rate > rate) - continue; - if ((rate - tmp_rate) < (rate - best_rate)) { + if (ccu_is_better_rate(common, rate, tmp_rate, best_rate)) { best_rate = tmp_rate; best_n = _n; best_k = _k; @@ -48,6 +102,8 @@ static void ccu_nkm_find_best(unsigned long parent, unsigned long rate, nkm->n = best_n; nkm->k = best_k; nkm->m = best_m; + + return best_rate; } static void ccu_nkm_disable(struct clk_hw *hw) @@ -107,7 +163,7 @@ static unsigned long ccu_nkm_recalc_rate(struct clk_hw *hw, } static unsigned long ccu_nkm_round_rate(struct ccu_mux_internal *mux, - struct clk_hw *hw, + struct clk_hw *parent_hw, unsigned long *parent_rate, unsigned long rate, void *data) @@ -125,9 +181,11 @@ static unsigned long ccu_nkm_round_rate(struct ccu_mux_internal *mux, if (nkm->common.features & CCU_FEATURE_FIXED_POSTDIV) rate *= nkm->fixed_post_div; - ccu_nkm_find_best(*parent_rate, rate, &_nkm); - - rate = *parent_rate * _nkm.n * _nkm.k / _nkm.m; + if (!clk_hw_can_set_rate_parent(&nkm->common.hw)) + rate = ccu_nkm_find_best(*parent_rate, rate, &_nkm, &nkm->common); + else + rate = ccu_nkm_find_best_with_parent_adj(&nkm->common, parent_hw, parent_rate, rate, + &_nkm); if (nkm->common.features & CCU_FEATURE_FIXED_POSTDIV) rate /= nkm->fixed_post_div; @@ -162,7 +220,7 @@ static int ccu_nkm_set_rate(struct clk_hw *hw, unsigned long rate, _nkm.min_m = 1; _nkm.max_m = nkm->m.max ?: 1 << nkm->m.width; - ccu_nkm_find_best(parent_rate, rate, &_nkm); + ccu_nkm_find_best(parent_rate, rate, &_nkm, &nkm->common); spin_lock_irqsave(nkm->common.lock, flags); @@ -209,3 +267,4 @@ const struct clk_ops ccu_nkm_ops = { .recalc_rate = ccu_nkm_recalc_rate, .set_rate = ccu_nkm_set_rate, }; +EXPORT_SYMBOL_NS_GPL(ccu_nkm_ops, "SUNXI_CCU"); |
