root/drivers/clk/sophgo/clk-sg2044-pll.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Sophgo SG2044 PLL clock controller driver
 *
 * Copyright (C) 2025 Inochi Amaoto <inochiama@gmail.com>
 */

#include <linux/array_size.h>
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/cleanup.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/math64.h>
#include <linux/mfd/syscon.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/spinlock.h>

#include <dt-bindings/clock/sophgo,sg2044-pll.h>

/* Low Control part */
#define PLL_VCOSEL_MASK         GENMASK(17, 16)

/* High Control part */
#define PLL_FBDIV_MASK          GENMASK(11, 0)
#define PLL_REFDIV_MASK         GENMASK(17, 12)
#define PLL_POSTDIV1_MASK       GENMASK(20, 18)
#define PLL_POSTDIV2_MASK       GENMASK(23, 21)

#define PLL_CALIBRATE_EN        BIT(24)
#define PLL_CALIBRATE_MASK      GENMASK(29, 27)
#define PLL_CALIBRATE_DEFAULT   FIELD_PREP(PLL_CALIBRATE_MASK, 2)
#define PLL_UPDATE_EN           BIT(30)

#define PLL_HIGH_CTRL_MASK      \
        (PLL_FBDIV_MASK | PLL_REFDIV_MASK | \
         PLL_POSTDIV1_MASK | PLL_POSTDIV2_MASK | \
         PLL_CALIBRATE_EN | PLL_CALIBRATE_MASK | \
         PLL_UPDATE_EN)

#define PLL_HIGH_CTRL_OFFSET    4

#define PLL_VCOSEL_1G6          0x2
#define PLL_VCOSEL_2G4          0x3

#define PLL_LIMIT_FOUTVCO       0
#define PLL_LIMIT_FOUT          1
#define PLL_LIMIT_REFDIV        2
#define PLL_LIMIT_FBDIV         3
#define PLL_LIMIT_POSTDIV1      4
#define PLL_LIMIT_POSTDIV2      5

#define for_each_pll_limit_range(_var, _limit) \
        for (_var = (_limit)->min; _var <= (_limit)->max; _var++)

struct sg2044_pll_limit {
        u64 min;
        u64 max;
};

struct sg2044_pll_internal {
        u32 ctrl_offset;
        u32 status_offset;
        u32 enable_offset;

        u8 status_lock_bit;
        u8 status_updating_bit;
        u8 enable_bit;

        const struct sg2044_pll_limit *limits;
};

struct sg2044_clk_common {
        struct clk_hw   hw;
        struct regmap   *regmap;
        spinlock_t      *lock;
        unsigned int    id;
};

struct sg2044_pll {
        struct sg2044_clk_common        common;
        struct sg2044_pll_internal      pll;
        unsigned int                    syscon_offset;
};

struct sg2044_pll_desc_data {
        struct sg2044_clk_common        * const *pll;
        u16                             num_pll;
};

#define SG2044_SYSCON_PLL_OFFSET        0x98

struct sg2044_pll_ctrl {
        spinlock_t                      lock;
        struct clk_hw_onecell_data      data;
};

#define hw_to_sg2044_clk_common(_hw)                                    \
        container_of((_hw), struct sg2044_clk_common, hw)

static inline bool sg2044_clk_fit_limit(u64 value,
                                        const struct sg2044_pll_limit *limit)
{
        return value >= limit->min && value <= limit->max;
}

static inline struct sg2044_pll *hw_to_sg2044_pll(struct clk_hw *hw)
{
        return container_of(hw_to_sg2044_clk_common(hw),
                            struct sg2044_pll, common);
}

static unsigned long sg2044_pll_calc_vco_rate(unsigned long parent_rate,
                                              unsigned long refdiv,
                                              unsigned long fbdiv)
{
        u64 numerator = parent_rate * fbdiv;

        return div64_ul(numerator, refdiv);
}

static unsigned long sg2044_pll_calc_rate(unsigned long parent_rate,
                                          unsigned long refdiv,
                                          unsigned long fbdiv,
                                          unsigned long postdiv1,
                                          unsigned long postdiv2)
{
        u64 numerator, denominator;

        numerator = parent_rate * fbdiv;
        denominator = refdiv * (postdiv1 + 1) * (postdiv2 + 1);

        return div64_u64(numerator, denominator);
}

static unsigned long sg2044_pll_recalc_rate(struct clk_hw *hw,
                                            unsigned long parent_rate)
{
        struct sg2044_pll *pll = hw_to_sg2044_pll(hw);
        u32 value;
        int ret;

        ret = regmap_read(pll->common.regmap,
                          pll->syscon_offset + pll->pll.ctrl_offset + PLL_HIGH_CTRL_OFFSET,
                          &value);
        if (ret < 0)
                return 0;

        return sg2044_pll_calc_rate(parent_rate,
                                    FIELD_GET(PLL_REFDIV_MASK, value),
                                    FIELD_GET(PLL_FBDIV_MASK, value),
                                    FIELD_GET(PLL_POSTDIV1_MASK, value),
                                    FIELD_GET(PLL_POSTDIV2_MASK, value));
}

static bool pll_is_better_rate(unsigned long target, unsigned long now,
                               unsigned long best)
{
        return abs_diff(target, now) < abs_diff(target, best);
}

static int sg2042_pll_compute_postdiv(const struct sg2044_pll_limit *limits,
                                      unsigned long target,
                                      unsigned long parent_rate,
                                      unsigned int refdiv,
                                      unsigned int fbdiv,
                                      unsigned int *postdiv1,
                                      unsigned int *postdiv2)
{
        unsigned int div1, div2;
        unsigned long tmp, best_rate = 0;
        unsigned int best_div1 = 0, best_div2 = 0;

        for_each_pll_limit_range(div2, &limits[PLL_LIMIT_POSTDIV2]) {
                for_each_pll_limit_range(div1, &limits[PLL_LIMIT_POSTDIV1]) {
                        tmp = sg2044_pll_calc_rate(parent_rate,
                                                   refdiv, fbdiv,
                                                   div1, div2);

                        if (tmp > target)
                                continue;

                        if (pll_is_better_rate(target, tmp, best_rate)) {
                                best_div1 = div1;
                                best_div2 = div2;
                                best_rate = tmp;

                                if (tmp == target)
                                        goto find;
                        }
                }
        }

find:
        if (best_rate) {
                *postdiv1 = best_div1;
                *postdiv2 = best_div2;
                return 0;
        }

        return -EINVAL;
}

static int sg2044_compute_pll_setting(const struct sg2044_pll_limit *limits,
                                      unsigned long req_rate,
                                      unsigned long parent_rate,
                                      unsigned int *value)
{
        unsigned int refdiv, fbdiv, postdiv1, postdiv2;
        unsigned int best_refdiv, best_fbdiv, best_postdiv1, best_postdiv2;
        unsigned long tmp, best_rate = 0;
        int ret;

        for_each_pll_limit_range(fbdiv, &limits[PLL_LIMIT_FBDIV]) {
                for_each_pll_limit_range(refdiv, &limits[PLL_LIMIT_REFDIV]) {
                        u64 vco = sg2044_pll_calc_vco_rate(parent_rate,
                                                           refdiv, fbdiv);
                        if (!sg2044_clk_fit_limit(vco, &limits[PLL_LIMIT_FOUTVCO]))
                                continue;

                        ret = sg2042_pll_compute_postdiv(limits,
                                                         req_rate, parent_rate,
                                                         refdiv, fbdiv,
                                                         &postdiv1, &postdiv2);
                        if (ret)
                                continue;

                        tmp = sg2044_pll_calc_rate(parent_rate,
                                                   refdiv, fbdiv,
                                                   postdiv1, postdiv2);

                        if (pll_is_better_rate(req_rate, tmp, best_rate)) {
                                best_refdiv = refdiv;
                                best_fbdiv = fbdiv;
                                best_postdiv1 = postdiv1;
                                best_postdiv2 = postdiv2;
                                best_rate = tmp;

                                if (tmp == req_rate)
                                        goto find;
                        }
                }
        }

find:
        if (best_rate) {
                *value = FIELD_PREP(PLL_REFDIV_MASK, best_refdiv) |
                         FIELD_PREP(PLL_FBDIV_MASK, best_fbdiv) |
                         FIELD_PREP(PLL_POSTDIV1_MASK, best_postdiv1) |
                         FIELD_PREP(PLL_POSTDIV2_MASK, best_postdiv2);
                return 0;
        }

        return -EINVAL;
}

static int sg2044_pll_determine_rate(struct clk_hw *hw,
                                     struct clk_rate_request *req)
{
        struct sg2044_pll *pll = hw_to_sg2044_pll(hw);
        unsigned int value;
        u64 target;
        int ret;

        target = clamp(req->rate, pll->pll.limits[PLL_LIMIT_FOUT].min,
                       pll->pll.limits[PLL_LIMIT_FOUT].max);

        ret = sg2044_compute_pll_setting(pll->pll.limits, target,
                                         req->best_parent_rate, &value);
        if (ret < 0)
                return ret;

        req->rate = sg2044_pll_calc_rate(req->best_parent_rate,
                                         FIELD_GET(PLL_REFDIV_MASK, value),
                                         FIELD_GET(PLL_FBDIV_MASK, value),
                                         FIELD_GET(PLL_POSTDIV1_MASK, value),
                                         FIELD_GET(PLL_POSTDIV2_MASK, value));

        return 0;
}

static int sg2044_pll_poll_update(struct sg2044_pll *pll)
{
        int ret;
        unsigned int value;

        ret = regmap_read_poll_timeout_atomic(pll->common.regmap,
                                              pll->syscon_offset + pll->pll.status_offset,
                                              value,
                                              (value & BIT(pll->pll.status_lock_bit)),
                                              1, 100000);
        if (ret)
                return ret;

        return regmap_read_poll_timeout_atomic(pll->common.regmap,
                                               pll->syscon_offset + pll->pll.status_offset,
                                               value,
                                               (!(value & BIT(pll->pll.status_updating_bit))),
                                               1, 100000);
}

static int sg2044_pll_enable(struct sg2044_pll *pll, bool en)
{
        if (en) {
                if (sg2044_pll_poll_update(pll) < 0)
                        pr_warn("%s: fail to lock pll\n", clk_hw_get_name(&pll->common.hw));

                return regmap_set_bits(pll->common.regmap,
                                       pll->syscon_offset + pll->pll.enable_offset,
                                       BIT(pll->pll.enable_bit));
        }

        return regmap_clear_bits(pll->common.regmap,
                                 pll->syscon_offset + pll->pll.enable_offset,
                                 BIT(pll->pll.enable_bit));
}

static int sg2044_pll_update_vcosel(struct sg2044_pll *pll, u64 rate)
{
        unsigned int sel;

        if (rate < U64_C(2400000000))
                sel = PLL_VCOSEL_1G6;
        else
                sel = PLL_VCOSEL_2G4;

        return regmap_write_bits(pll->common.regmap,
                                 pll->syscon_offset + pll->pll.ctrl_offset,
                                 PLL_VCOSEL_MASK,
                                 FIELD_PREP(PLL_VCOSEL_MASK, sel));
}

static int sg2044_pll_set_rate(struct clk_hw *hw,
                               unsigned long rate, unsigned long parent_rate)
{
        struct sg2044_pll *pll = hw_to_sg2044_pll(hw);
        unsigned int value;
        u64 vco;
        int ret;

        ret = sg2044_compute_pll_setting(pll->pll.limits, rate,
                                         parent_rate, &value);
        if (ret < 0)
                return ret;

        vco = sg2044_pll_calc_vco_rate(parent_rate,
                                       FIELD_GET(PLL_REFDIV_MASK, value),
                                       FIELD_GET(PLL_FBDIV_MASK, value));

        value |= PLL_CALIBRATE_EN;
        value |= PLL_CALIBRATE_DEFAULT;
        value |= PLL_UPDATE_EN;

        guard(spinlock_irqsave)(pll->common.lock);

        ret = sg2044_pll_enable(pll, false);
        if (ret)
                return ret;

        sg2044_pll_update_vcosel(pll, vco);

        regmap_write_bits(pll->common.regmap,
                          pll->syscon_offset + pll->pll.ctrl_offset +
                          PLL_HIGH_CTRL_OFFSET,
                          PLL_HIGH_CTRL_MASK, value);

        sg2044_pll_enable(pll, true);

        return ret;
}

static const struct clk_ops sg2044_pll_ops = {
        .recalc_rate = sg2044_pll_recalc_rate,
        .determine_rate = sg2044_pll_determine_rate,
        .set_rate = sg2044_pll_set_rate,
};

static const struct clk_ops sg2044_pll_ro_ops = {
        .recalc_rate = sg2044_pll_recalc_rate,
};

#define SG2044_CLK_COMMON_PDATA(_id, _name, _parents, _op, _flags)      \
        {                                                               \
                .hw.init = CLK_HW_INIT_PARENTS_DATA(_name, _parents,    \
                                                    _op, (_flags)),     \
                .id = (_id),                                            \
        }

#define DEFINE_SG2044_PLL(_id, _name, _parent, _flags,                  \
                          _ctrl_offset,                                 \
                          _status_offset, _status_lock_bit,             \
                          _status_updating_bit,                         \
                          _enable_offset, _enable_bit,                  \
                          _limits)                                      \
        struct sg2044_pll _name = {                                     \
                .common = SG2044_CLK_COMMON_PDATA(_id, #_name, _parent, \
                                                  &sg2044_pll_ops,      \
                                                  (_flags)),            \
                .pll = {                                                \
                        .ctrl_offset = (_ctrl_offset),                  \
                        .status_offset = (_status_offset),              \
                        .enable_offset = (_enable_offset),              \
                        .status_lock_bit = (_status_lock_bit),          \
                        .status_updating_bit = (_status_updating_bit),  \
                        .enable_bit = (_enable_bit),                    \
                        .limits = (_limits),                            \
                },                                                      \
        }

#define DEFINE_SG2044_PLL_RO(_id, _name, _parent, _flags,               \
                             _ctrl_offset,                              \
                             _status_offset, _status_lock_bit,          \
                             _status_updating_bit,                      \
                             _enable_offset, _enable_bit,               \
                             _limits)                                   \
        struct sg2044_pll _name = {                                     \
                .common = SG2044_CLK_COMMON_PDATA(_id, #_name, _parent, \
                                                  &sg2044_pll_ro_ops,   \
                                                  (_flags)),            \
                .pll = {                                                \
                        .ctrl_offset = (_ctrl_offset),                  \
                        .status_offset = (_status_offset),              \
                        .enable_offset = (_enable_offset),              \
                        .status_lock_bit = (_status_lock_bit),          \
                        .status_updating_bit = (_status_updating_bit),  \
                        .enable_bit = (_enable_bit),                    \
                        .limits = (_limits),                            \
                },                                                      \
        }

static const struct clk_parent_data osc_parents[] = {
        { .index = 0 },
};

static const struct sg2044_pll_limit pll_limits[] = {
        [PLL_LIMIT_FOUTVCO] = {
                .min = U64_C(1600000000),
                .max = U64_C(3200000000),
        },
        [PLL_LIMIT_FOUT] = {
                .min = U64_C(25000),
                .max = U64_C(3200000000),
        },
        [PLL_LIMIT_REFDIV] = {
                .min = U64_C(1),
                .max = U64_C(63),
        },
        [PLL_LIMIT_FBDIV] = {
                .min = U64_C(8),
                .max = U64_C(1066),
        },
        [PLL_LIMIT_POSTDIV1] = {
                .min = U64_C(0),
                .max = U64_C(7),
        },
        [PLL_LIMIT_POSTDIV2] = {
                .min = U64_C(0),
                .max = U64_C(7),
        },
};

static DEFINE_SG2044_PLL_RO(CLK_FPLL0, clk_fpll0, osc_parents, CLK_IS_CRITICAL,
                            0x58, 0x00, 22, 6,
                            0x04, 6, pll_limits);

static DEFINE_SG2044_PLL_RO(CLK_FPLL1, clk_fpll1, osc_parents, CLK_IS_CRITICAL,
                            0x60, 0x00, 23, 7,
                            0x04, 7, pll_limits);

static DEFINE_SG2044_PLL_RO(CLK_FPLL2, clk_fpll2, osc_parents, CLK_IS_CRITICAL,
                            0x20, 0x08, 16, 0,
                            0x0c, 0, pll_limits);

static DEFINE_SG2044_PLL_RO(CLK_DPLL0, clk_dpll0, osc_parents, CLK_IS_CRITICAL,
                            0x68, 0x00, 24, 8,
                            0x04, 8, pll_limits);

static DEFINE_SG2044_PLL_RO(CLK_DPLL1, clk_dpll1, osc_parents, CLK_IS_CRITICAL,
                            0x70, 0x00, 25, 9,
                            0x04, 9, pll_limits);

static DEFINE_SG2044_PLL_RO(CLK_DPLL2, clk_dpll2, osc_parents, CLK_IS_CRITICAL,
                            0x78, 0x00, 26, 10,
                            0x04, 10, pll_limits);

static DEFINE_SG2044_PLL_RO(CLK_DPLL3, clk_dpll3, osc_parents, CLK_IS_CRITICAL,
                            0x80, 0x00, 27, 11,
                            0x04, 11, pll_limits);

static DEFINE_SG2044_PLL_RO(CLK_DPLL4, clk_dpll4, osc_parents, CLK_IS_CRITICAL,
                            0x88, 0x00, 28, 12,
                            0x04, 12, pll_limits);

static DEFINE_SG2044_PLL_RO(CLK_DPLL5, clk_dpll5, osc_parents, CLK_IS_CRITICAL,
                            0x90, 0x00, 29, 13,
                            0x04, 13, pll_limits);

static DEFINE_SG2044_PLL_RO(CLK_DPLL6, clk_dpll6, osc_parents, CLK_IS_CRITICAL,
                            0x98, 0x00, 30, 14,
                            0x04, 14, pll_limits);

static DEFINE_SG2044_PLL_RO(CLK_DPLL7, clk_dpll7, osc_parents, CLK_IS_CRITICAL,
                            0xa0, 0x00, 31, 15,
                            0x04, 15, pll_limits);

static DEFINE_SG2044_PLL(CLK_MPLL0, clk_mpll0, osc_parents, CLK_IS_CRITICAL,
                         0x28, 0x00, 16, 0,
                         0x04, 0, pll_limits);

static DEFINE_SG2044_PLL(CLK_MPLL1, clk_mpll1, osc_parents, CLK_IS_CRITICAL,
                         0x30, 0x00, 17, 1,
                         0x04, 1, pll_limits);

static DEFINE_SG2044_PLL(CLK_MPLL2, clk_mpll2, osc_parents, CLK_IS_CRITICAL,
                         0x38, 0x00, 18, 2,
                         0x04, 2, pll_limits);

static DEFINE_SG2044_PLL(CLK_MPLL3, clk_mpll3, osc_parents, CLK_IS_CRITICAL,
                         0x40, 0x00, 19, 3,
                         0x04, 3, pll_limits);

static DEFINE_SG2044_PLL(CLK_MPLL4, clk_mpll4, osc_parents, CLK_IS_CRITICAL,
                         0x48, 0x00, 20, 4,
                         0x04, 4, pll_limits);

static DEFINE_SG2044_PLL(CLK_MPLL5, clk_mpll5, osc_parents, CLK_IS_CRITICAL,
                         0x50, 0x00, 21, 5,
                         0x04, 5, pll_limits);

static struct sg2044_clk_common * const sg2044_pll_commons[] = {
        &clk_fpll0.common,
        &clk_fpll1.common,
        &clk_fpll2.common,
        &clk_dpll0.common,
        &clk_dpll1.common,
        &clk_dpll2.common,
        &clk_dpll3.common,
        &clk_dpll4.common,
        &clk_dpll5.common,
        &clk_dpll6.common,
        &clk_dpll7.common,
        &clk_mpll0.common,
        &clk_mpll1.common,
        &clk_mpll2.common,
        &clk_mpll3.common,
        &clk_mpll4.common,
        &clk_mpll5.common,
};

static int sg2044_pll_init_ctrl(struct device *dev, struct regmap *regmap,
                                struct sg2044_pll_ctrl *ctrl,
                                const struct sg2044_pll_desc_data *desc)
{
        int ret, i;

        spin_lock_init(&ctrl->lock);

        for (i = 0; i < desc->num_pll; i++) {
                struct sg2044_clk_common *common = desc->pll[i];
                struct sg2044_pll *pll = hw_to_sg2044_pll(&common->hw);

                common->lock = &ctrl->lock;
                common->regmap = regmap;
                pll->syscon_offset = SG2044_SYSCON_PLL_OFFSET;

                ret = devm_clk_hw_register(dev, &common->hw);
                if (ret)
                        return ret;

                ctrl->data.hws[common->id] = &common->hw;
        }

        return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get,
                                           &ctrl->data);
}

static int sg2044_pll_probe(struct platform_device *pdev)
{
        struct device *dev = &pdev->dev;
        struct sg2044_pll_ctrl *ctrl;
        const struct sg2044_pll_desc_data *desc;
        struct regmap *regmap;

        regmap = device_node_to_regmap(pdev->dev.parent->of_node);
        if (IS_ERR(regmap))
                return dev_err_probe(dev, PTR_ERR(regmap),
                                     "fail to get the regmap for PLL\n");

        desc = (const struct sg2044_pll_desc_data *)platform_get_device_id(pdev)->driver_data;
        if (!desc)
                return dev_err_probe(dev, -EINVAL, "no match data for platform\n");

        ctrl = devm_kzalloc(dev, struct_size(ctrl, data.hws, desc->num_pll), GFP_KERNEL);
        if (!ctrl)
                return -ENOMEM;

        ctrl->data.num = desc->num_pll;

        return sg2044_pll_init_ctrl(dev, regmap, ctrl, desc);
}

static const struct sg2044_pll_desc_data sg2044_pll_desc_data = {
        .pll = sg2044_pll_commons,
        .num_pll = ARRAY_SIZE(sg2044_pll_commons),
};

static const struct platform_device_id sg2044_pll_match[] = {
        { .name = "sg2044-pll",
          .driver_data = (unsigned long)&sg2044_pll_desc_data },
        { /* sentinel */ }
};
MODULE_DEVICE_TABLE(platform, sg2044_pll_match);

static struct platform_driver sg2044_clk_driver = {
        .probe = sg2044_pll_probe,
        .driver = {
                .name = "sg2044-pll",
        },
        .id_table = sg2044_pll_match,
};
module_platform_driver(sg2044_clk_driver);

MODULE_AUTHOR("Inochi Amaoto <inochiama@gmail.com>");
MODULE_DESCRIPTION("Sophgo SG2044 pll clock driver");
MODULE_LICENSE("GPL");