root/drivers/clk/sophgo/clk-cv18xx-pll.c
// 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,
};