Merge tag 'clk-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/clk/linux
[linux-2.6-block.git] / drivers / clk / sophgo / clk-cv18xx-pll.c
diff --git a/drivers/clk/sophgo/clk-cv18xx-pll.c b/drivers/clk/sophgo/clk-cv18xx-pll.c
new file mode 100644 (file)
index 0000000..29e2409
--- /dev/null
@@ -0,0 +1,419 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2023 Inochi Amaoto <inochiama@outlook.com>
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/io.h>
+#include <linux/limits.h>
+#include <linux/spinlock.h>
+
+#include "clk-cv18xx-pll.h"
+
+static inline struct cv1800_clk_pll *hw_to_cv1800_clk_pll(struct clk_hw *hw)
+{
+       struct cv1800_clk_common *common = hw_to_cv1800_clk_common(hw);
+
+       return container_of(common, struct cv1800_clk_pll, common);
+}
+
+static unsigned long ipll_calc_rate(unsigned long parent_rate,
+                                   unsigned long pre_div_sel,
+                                   unsigned long div_sel,
+                                   unsigned long post_div_sel)
+{
+       uint64_t rate = parent_rate;
+
+       rate *= div_sel;
+       do_div(rate, pre_div_sel * post_div_sel);
+
+       return rate;
+}
+
+static unsigned long ipll_recalc_rate(struct clk_hw *hw,
+                                     unsigned long parent_rate)
+{
+       struct cv1800_clk_pll *pll = hw_to_cv1800_clk_pll(hw);
+       u32 value;
+
+       value = readl(pll->common.base + pll->pll_reg);
+
+       return ipll_calc_rate(parent_rate,
+                             PLL_GET_PRE_DIV_SEL(value),
+                             PLL_GET_DIV_SEL(value),
+                             PLL_GET_POST_DIV_SEL(value));
+}
+
+static int ipll_find_rate(const struct cv1800_clk_pll_limit *limit,
+                         unsigned long prate, unsigned long *rate,
+                         u32 *value)
+{
+       unsigned long best_rate = 0;
+       unsigned long trate = *rate;
+       unsigned long pre_div_sel = 0, div_sel = 0, post_div_sel = 0;
+       unsigned long pre, div, post;
+       u32 detected = *value;
+       unsigned long tmp;
+
+       for_each_pll_limit_range(pre, &limit->pre_div) {
+               for_each_pll_limit_range(div, &limit->div) {
+                       for_each_pll_limit_range(post, &limit->post_div) {
+                               tmp = ipll_calc_rate(prate, pre, div, post);
+
+                               if (tmp > trate)
+                                       continue;
+
+                               if ((trate - tmp) < (trate - best_rate)) {
+                                       best_rate = tmp;
+                                       pre_div_sel = pre;
+                                       div_sel = div;
+                                       post_div_sel = post;
+                               }
+                       }
+               }
+       }
+
+       if (best_rate) {
+               detected = PLL_SET_PRE_DIV_SEL(detected, pre_div_sel);
+               detected = PLL_SET_POST_DIV_SEL(detected, post_div_sel);
+               detected = PLL_SET_DIV_SEL(detected, div_sel);
+               *value = detected;
+               *rate = best_rate;
+               return 0;
+       }
+
+       return -EINVAL;
+}
+
+static int ipll_determine_rate(struct clk_hw *hw, struct clk_rate_request *req)
+{
+       u32 val;
+       struct cv1800_clk_pll *pll = hw_to_cv1800_clk_pll(hw);
+
+       return ipll_find_rate(pll->pll_limit, req->best_parent_rate,
+                             &req->rate, &val);
+}
+
+static void pll_get_mode_ctrl(unsigned long div_sel,
+                             bool (*mode_ctrl_check)(unsigned long,
+                                                     unsigned long,
+                                                     unsigned long),
+                             const struct cv1800_clk_pll_limit *limit,
+                             u32 *value)
+{
+       unsigned long ictrl = 0, mode = 0;
+       u32 detected = *value;
+
+       for_each_pll_limit_range(mode, &limit->mode) {
+               for_each_pll_limit_range(ictrl, &limit->ictrl) {
+                       if (mode_ctrl_check(div_sel, ictrl, mode)) {
+                               detected = PLL_SET_SEL_MODE(detected, mode);
+                               detected = PLL_SET_ICTRL(detected, ictrl);
+                               *value = detected;
+                               return;
+                       }
+               }
+       }
+}
+
+static bool ipll_check_mode_ctrl_restrict(unsigned long div_sel,
+                                         unsigned long ictrl,
+                                         unsigned long mode)
+{
+       unsigned long left_rest = 20 * div_sel;
+       unsigned long right_rest = 35 * div_sel;
+       unsigned long test = 184 * (1 + mode) * (1 + ictrl) / 2;
+
+       return test > left_rest && test <= right_rest;
+}
+
+static int ipll_set_rate(struct clk_hw *hw, unsigned long rate,
+                        unsigned long parent_rate)
+{
+       u32 regval, detected = 0;
+       unsigned long flags;
+       struct cv1800_clk_pll *pll = hw_to_cv1800_clk_pll(hw);
+
+       ipll_find_rate(pll->pll_limit, parent_rate, &rate, &detected);
+       pll_get_mode_ctrl(PLL_GET_DIV_SEL(detected),
+                         ipll_check_mode_ctrl_restrict,
+                         pll->pll_limit, &detected);
+
+       spin_lock_irqsave(pll->common.lock, flags);
+
+       regval = readl(pll->common.base + pll->pll_reg);
+       regval = PLL_COPY_REG(regval, detected);
+
+       writel(regval, pll->common.base + pll->pll_reg);
+
+       spin_unlock_irqrestore(pll->common.lock, flags);
+
+       cv1800_clk_wait_for_lock(&pll->common, pll->pll_status.reg,
+                             BIT(pll->pll_status.shift));
+
+       return 0;
+}
+
+static int pll_enable(struct clk_hw *hw)
+{
+       struct cv1800_clk_pll *pll = hw_to_cv1800_clk_pll(hw);
+
+       return cv1800_clk_clearbit(&pll->common, &pll->pll_pwd);
+}
+
+static void pll_disable(struct clk_hw *hw)
+{
+       struct cv1800_clk_pll *pll = hw_to_cv1800_clk_pll(hw);
+
+       cv1800_clk_setbit(&pll->common, &pll->pll_pwd);
+}
+
+static int pll_is_enable(struct clk_hw *hw)
+{
+       struct cv1800_clk_pll *pll = hw_to_cv1800_clk_pll(hw);
+
+       return cv1800_clk_checkbit(&pll->common, &pll->pll_pwd) == 0;
+}
+
+const struct clk_ops cv1800_clk_ipll_ops = {
+       .disable = pll_disable,
+       .enable = pll_enable,
+       .is_enabled = pll_is_enable,
+
+       .recalc_rate = ipll_recalc_rate,
+       .determine_rate = ipll_determine_rate,
+       .set_rate = ipll_set_rate,
+};
+
+#define PLL_SYN_FACTOR_DOT_POS         26
+#define PLL_SYN_FACTOR_MINIMUM         ((4 << PLL_SYN_FACTOR_DOT_POS) + 1)
+
+static bool fpll_is_factional_mode(struct cv1800_clk_pll *pll)
+{
+       return cv1800_clk_checkbit(&pll->common, &pll->pll_syn->en);
+}
+
+static unsigned long fpll_calc_rate(unsigned long parent_rate,
+                                   unsigned long pre_div_sel,
+                                   unsigned long div_sel,
+                                   unsigned long post_div_sel,
+                                   unsigned long ssc_syn_set,
+                                   bool is_full_parent)
+{
+       u64 dividend = parent_rate * div_sel;
+       u64 factor = ssc_syn_set * pre_div_sel * post_div_sel;
+       unsigned long rate;
+
+       dividend <<= PLL_SYN_FACTOR_DOT_POS - 1;
+       rate = div64_u64_rem(dividend, factor, &dividend);
+
+       if (is_full_parent) {
+               dividend <<= 1;
+               rate <<= 1;
+       }
+
+       rate += DIV64_U64_ROUND_CLOSEST(dividend, factor);
+
+       return rate;
+}
+
+static unsigned long fpll_recalc_rate(struct clk_hw *hw,
+                                         unsigned long parent_rate)
+{
+       struct cv1800_clk_pll *pll = hw_to_cv1800_clk_pll(hw);
+       u32 value;
+       bool clk_full;
+       u32 syn_set;
+
+       if (!fpll_is_factional_mode(pll))
+               return ipll_recalc_rate(hw, parent_rate);
+
+       syn_set = readl(pll->common.base + pll->pll_syn->set);
+
+       if (syn_set == 0)
+               return 0;
+
+       clk_full = cv1800_clk_checkbit(&pll->common,
+                                         &pll->pll_syn->clk_half);
+
+       value = readl(pll->common.base + pll->pll_reg);
+
+       return fpll_calc_rate(parent_rate,
+                             PLL_GET_PRE_DIV_SEL(value),
+                             PLL_GET_DIV_SEL(value),
+                             PLL_GET_POST_DIV_SEL(value),
+                             syn_set, clk_full);
+}
+
+static unsigned long fpll_find_synthesizer(unsigned long parent,
+                                          unsigned long rate,
+                                          unsigned long pre_div,
+                                          unsigned long div,
+                                          unsigned long post_div,
+                                          bool is_full_parent,
+                                          u32 *ssc_syn_set)
+{
+       u32 test_max = U32_MAX, test_min = PLL_SYN_FACTOR_MINIMUM;
+       unsigned long trate;
+
+       while (test_min < test_max) {
+               u32 tssc = (test_max + test_min) / 2;
+
+               trate = fpll_calc_rate(parent, pre_div, div, post_div,
+                                      tssc, is_full_parent);
+
+               if (trate == rate) {
+                       test_min = tssc;
+                       break;
+               }
+
+               if (trate > rate)
+                       test_min = tssc + 1;
+               else
+                       test_max = tssc - 1;
+       }
+
+       if (trate != 0)
+               *ssc_syn_set = test_min;
+
+       return trate;
+}
+
+static int fpll_find_rate(struct cv1800_clk_pll *pll,
+                         const struct cv1800_clk_pll_limit *limit,
+                         unsigned long prate,
+                         unsigned long *rate,
+                         u32 *value, u32 *ssc_syn_set)
+{
+       unsigned long best_rate = 0;
+       unsigned long pre_div_sel = 0, div_sel = 0, post_div_sel = 0;
+       unsigned long pre, div, post;
+       unsigned long trate = *rate;
+       u32 detected = *value;
+       unsigned long tmp;
+       bool clk_full = cv1800_clk_checkbit(&pll->common,
+                                              &pll->pll_syn->clk_half);
+
+       for_each_pll_limit_range(pre, &limit->pre_div) {
+               for_each_pll_limit_range(post, &limit->post_div) {
+                       for_each_pll_limit_range(div, &limit->div) {
+                               tmp = fpll_find_synthesizer(prate, trate,
+                                                           pre, div, post,
+                                                           clk_full,
+                                                           ssc_syn_set);
+
+                               if ((trate - tmp) < (trate - best_rate)) {
+                                       best_rate = tmp;
+                                       pre_div_sel = pre;
+                                       div_sel = div;
+                                       post_div_sel = post;
+                               }
+                       }
+               }
+       }
+
+       if (best_rate) {
+               detected = PLL_SET_PRE_DIV_SEL(detected, pre_div_sel);
+               detected = PLL_SET_POST_DIV_SEL(detected, post_div_sel);
+               detected = PLL_SET_DIV_SEL(detected, div_sel);
+               *value = detected;
+               *rate = best_rate;
+               return 0;
+       }
+
+       return -EINVAL;
+}
+
+static int fpll_determine_rate(struct clk_hw *hw, struct clk_rate_request *req)
+{
+       struct cv1800_clk_pll *pll = hw_to_cv1800_clk_pll(hw);
+       u32 val, ssc_syn_set;
+
+       if (!fpll_is_factional_mode(pll))
+               return ipll_determine_rate(hw, req);
+
+       fpll_find_rate(pll, &pll->pll_limit[2], req->best_parent_rate,
+                      &req->rate, &val, &ssc_syn_set);
+
+       return 0;
+}
+
+static bool fpll_check_mode_ctrl_restrict(unsigned long div_sel,
+                                         unsigned long ictrl,
+                                         unsigned long mode)
+{
+       unsigned long left_rest = 10 * div_sel;
+       unsigned long right_rest = 24 * div_sel;
+       unsigned long test = 184 * (1 + mode) * (1 + ictrl) / 2;
+
+       return test > left_rest && test <= right_rest;
+}
+
+static int fpll_set_rate(struct clk_hw *hw, unsigned long rate,
+                        unsigned long parent_rate)
+{
+       u32 regval;
+       u32 detected = 0, detected_ssc = 0;
+       unsigned long flags;
+       struct cv1800_clk_pll *pll = hw_to_cv1800_clk_pll(hw);
+
+       if (!fpll_is_factional_mode(pll))
+               return ipll_set_rate(hw, rate, parent_rate);
+
+       fpll_find_rate(pll, &pll->pll_limit[2], parent_rate,
+                      &rate, &detected, &detected_ssc);
+       pll_get_mode_ctrl(PLL_GET_DIV_SEL(detected),
+                         fpll_check_mode_ctrl_restrict,
+                         pll->pll_limit, &detected);
+
+       spin_lock_irqsave(pll->common.lock, flags);
+
+       writel(detected_ssc, pll->common.base + pll->pll_syn->set);
+
+       regval = readl(pll->common.base + pll->pll_reg);
+       regval = PLL_COPY_REG(regval, detected);
+
+       writel(regval, pll->common.base + pll->pll_reg);
+
+       spin_unlock_irqrestore(pll->common.lock, flags);
+
+       cv1800_clk_wait_for_lock(&pll->common, pll->pll_status.reg,
+                             BIT(pll->pll_status.shift));
+
+       return 0;
+}
+
+static u8 fpll_get_parent(struct clk_hw *hw)
+{
+       struct cv1800_clk_pll *pll = hw_to_cv1800_clk_pll(hw);
+
+       if (fpll_is_factional_mode(pll))
+               return 1;
+
+       return 0;
+}
+
+static int fpll_set_parent(struct clk_hw *hw, u8 index)
+{
+       struct cv1800_clk_pll *pll = hw_to_cv1800_clk_pll(hw);
+
+       if (index)
+               cv1800_clk_setbit(&pll->common, &pll->pll_syn->en);
+       else
+               cv1800_clk_clearbit(&pll->common, &pll->pll_syn->en);
+
+       return 0;
+}
+
+const struct clk_ops cv1800_clk_fpll_ops = {
+       .disable = pll_disable,
+       .enable = pll_enable,
+       .is_enabled = pll_is_enable,
+
+       .recalc_rate = fpll_recalc_rate,
+       .determine_rate = fpll_determine_rate,
+       .set_rate = fpll_set_rate,
+
+       .set_parent = fpll_set_parent,
+       .get_parent = fpll_get_parent,
+};