root/drivers/clk/eswin/clk.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright 2026, Beijing ESWIN Computing Technology Co., Ltd..
 * All rights reserved.
 *
 * Authors:
 *      Yifeng Huang <huangyifeng@eswincomputing.com>
 *      Xuyang Dong <dongxuyang@eswincomputing.com>
 */

#include <linux/bitfield.h>
#include <linux/clk-provider.h>
#include <linux/iopoll.h>
#include <linux/math.h>
#include <linux/platform_device.h>
#include <linux/slab.h>

#include "common.h"

#define PLL_EN_MASK             GENMASK(1, 0)
#define PLL_REFDIV_MASK         GENMASK(17, 12)
#define PLL_FBDIV_MASK          GENMASK(31, 20)
#define PLL_FRAC_MASK           GENMASK(27, 4)
#define PLL_POSTDIV1_MASK       GENMASK(10, 8)
#define PLL_POSTDIV2_MASK       GENMASK(18, 16)

struct eswin_clock_data *eswin_clk_init(struct platform_device *pdev,
                                        size_t nr_clks)
{
        struct eswin_clock_data *eclk_data;

        eclk_data = devm_kzalloc(&pdev->dev,
                                 struct_size(eclk_data, clk_data.hws, nr_clks),
                                 GFP_KERNEL);
        if (!eclk_data)
                return ERR_PTR(-ENOMEM);

        eclk_data->base = devm_platform_ioremap_resource(pdev, 0);
        if (IS_ERR(eclk_data->base))
                return ERR_PTR(-EINVAL);

        eclk_data->clk_data.num = nr_clks;
        spin_lock_init(&eclk_data->lock);

        return eclk_data;
}
EXPORT_SYMBOL_GPL(eswin_clk_init);

/**
 * eswin_calc_pll - calculate PLL values
 * @frac_val: fractional divider
 * @fbdiv_val: feedback divider
 * @rate: reference rate
 * @parent_rate: parent rate
 *
 *   Calculate PLL values for frac and fbdiv:
 *      fbdiv = rate * 4 / parent_rate
 *      frac = (rate * 4 % parent_rate * (2 ^ 24)) / parent_rate
 */
static void eswin_calc_pll(u32 *frac_val, u32 *fbdiv_val, unsigned long rate,
                           unsigned long parent_rate)
{
        u32 rem;
        u64 tmp;

        /* step 1: rate * 4 */
        tmp = rate * 4;
        /* step 2: use do_div() to get the quotient(tmp) and remainder(rem) */
        rem = do_div(tmp, (u32)parent_rate);
        /* fbdiv = rate * 4 / parent_rate */
        *fbdiv_val = (u32)tmp;
        /*
         * step 3: rem << 24
         * 24: 24-bit fractional accuracy
         */
        tmp = (u64)rem << 24;
        /* step 4: use do_div() to get the quotient(tmp) */
        do_div(tmp, (u32)parent_rate);
        /* frac = (rate * 4 % parent_rate * (2 ^ 24)) / parent_rate */
        *frac_val = (u32)tmp;
}

static inline struct eswin_clk_pll *to_pll_clk(struct clk_hw *hw)
{
        return container_of(hw, struct eswin_clk_pll, hw);
}

static int clk_pll_set_rate(struct clk_hw *hw, unsigned long rate,
                            unsigned long parent_rate)
{
        struct eswin_clk_pll *clk = to_pll_clk(hw);
        u32 frac_val, fbdiv_val, val, mask;
        int ret;

        eswin_calc_pll(&frac_val, &fbdiv_val, rate, parent_rate);

        /* First, disable pll */
        val = readl_relaxed(clk->ctrl_reg0);
        val &= ~PLL_EN_MASK;
        val |= FIELD_PREP(PLL_EN_MASK, 0);
        writel_relaxed(val, clk->ctrl_reg0);

        val = readl_relaxed(clk->ctrl_reg0);
        val &= ~(PLL_REFDIV_MASK | PLL_FBDIV_MASK);
        val |= FIELD_PREP(PLL_FBDIV_MASK, fbdiv_val);
        val |= FIELD_PREP(PLL_REFDIV_MASK, 1);
        writel_relaxed(val, clk->ctrl_reg0);

        val = readl_relaxed(clk->ctrl_reg1);
        val &= ~PLL_FRAC_MASK;
        val |= FIELD_PREP(PLL_FRAC_MASK, frac_val);
        writel_relaxed(val, clk->ctrl_reg1);

        val = readl_relaxed(clk->ctrl_reg2);
        val &= ~(PLL_POSTDIV1_MASK | PLL_POSTDIV2_MASK);
        val |= FIELD_PREP(PLL_POSTDIV1_MASK, 1);
        val |= FIELD_PREP(PLL_POSTDIV2_MASK, 1);
        writel_relaxed(val, clk->ctrl_reg2);

        /* Last, enable pll */
        val = readl_relaxed(clk->ctrl_reg0);
        val &= ~PLL_EN_MASK;
        val |= FIELD_PREP(PLL_EN_MASK, 1);
        writel_relaxed(val, clk->ctrl_reg0);

        /* Usually the pll will lock in 50us */
        mask = GENMASK(clk->lock_shift + clk->lock_width - 1, clk->lock_shift);
        ret = readl_poll_timeout(clk->status_reg, val, val & mask, 1, 50 * 2);
        if (ret)
                pr_err("failed to lock the pll!\n");

        return ret;
}

static unsigned long clk_pll_recalc_rate(struct clk_hw *hw,
                                         unsigned long parent_rate)
{
        struct eswin_clk_pll *clk = to_pll_clk(hw);
        u64 fbdiv_val, frac_val, tmp;
        u32 rem, val;

        val = readl_relaxed(clk->ctrl_reg0);
        val &= PLL_FBDIV_MASK;
        fbdiv_val = (val >> clk->fbdiv_shift);

        val = readl_relaxed(clk->ctrl_reg1);
        val &= PLL_FRAC_MASK;
        frac_val = (val >> clk->frac_shift);

        /* rate = 24000000 * (fbdiv + frac / (2 ^ 24)) / 4 */
        tmp = parent_rate * frac_val;
        rem = do_div(tmp, BIT(24));
        if (rem)
                tmp = parent_rate * fbdiv_val + tmp + 1;
        else
                tmp = parent_rate * fbdiv_val + tmp;

        do_div(tmp, 4);

        return tmp;
}

static int clk_pll_determine_rate(struct clk_hw *hw,
                                  struct clk_rate_request *req)
{
        struct eswin_clk_pll *clk = to_pll_clk(hw);

        req->rate = clamp(req->rate, clk->min_rate, clk->max_rate);
        req->min_rate = clk->min_rate;
        req->max_rate = clk->max_rate;

        return 0;
}

int eswin_clk_register_fixed_rate(struct device *dev,
                                  struct eswin_fixed_rate_clock *clks,
                                  int nums, struct eswin_clock_data *data)
{
        struct clk_hw *clk_hw;
        int i;

        for (i = 0; i < nums; i++) {
                clk_hw = devm_clk_hw_register_fixed_rate(dev, clks[i].name,
                                                         NULL, clks[i].flags,
                                                         clks[i].rate);
                if (IS_ERR(clk_hw))
                        return PTR_ERR(clk_hw);

                clks[i].hw = *clk_hw;
                data->clk_data.hws[clks[i].id] = clk_hw;
        }

        return 0;
}
EXPORT_SYMBOL_GPL(eswin_clk_register_fixed_rate);

static const struct clk_ops eswin_clk_pll_ops = {
        .set_rate = clk_pll_set_rate,
        .recalc_rate = clk_pll_recalc_rate,
        .determine_rate = clk_pll_determine_rate,
};

int eswin_clk_register_pll(struct device *dev, struct eswin_pll_clock *clks,
                           int nums, struct eswin_clock_data *data)
{
        struct eswin_clk_pll *p_clk = NULL;
        struct clk_init_data init;
        struct clk_hw *clk_hw;
        int i, ret;

        p_clk = devm_kzalloc(dev, sizeof(*p_clk) * nums, GFP_KERNEL);
        if (!p_clk)
                return -ENOMEM;

        for (i = 0; i < nums; i++) {
                p_clk->id = clks[i].id;
                p_clk->ctrl_reg0 = data->base + clks[i].ctrl_reg0;
                p_clk->fbdiv_shift = clks[i].fbdiv_shift;

                p_clk->ctrl_reg1 = data->base + clks[i].ctrl_reg1;
                p_clk->frac_shift = clks[i].frac_shift;

                p_clk->ctrl_reg2 = data->base + clks[i].ctrl_reg2;

                p_clk->status_reg = data->base + clks[i].status_reg;
                p_clk->lock_shift = clks[i].lock_shift;
                p_clk->lock_width = clks[i].lock_width;

                p_clk->max_rate = clks[i].max_rate;
                p_clk->min_rate = clks[i].min_rate;

                init.name = clks[i].name;
                init.flags = 0;
                init.parent_data = clks[i].parent_data;
                init.num_parents = 1;
                init.ops = &eswin_clk_pll_ops;
                p_clk->hw.init = &init;

                clk_hw = &p_clk->hw;

                ret = devm_clk_hw_register(dev, clk_hw);
                if (ret)
                        return ret;

                clks[i].hw = *clk_hw;
                data->clk_data.hws[clks[i].id] = clk_hw;
                p_clk++;
        }

        return 0;
}
EXPORT_SYMBOL_GPL(eswin_clk_register_pll);

int eswin_clk_register_fixed_factor(struct device *dev,
                                    struct eswin_fixed_factor_clock *clks,
                                    int nums, struct eswin_clock_data *data)
{
        struct clk_hw *clk_hw;
        int i;

        for (i = 0; i < nums; i++) {
                clk_hw = devm_clk_hw_register_fixed_factor_index(dev, clks[i].name,
                                                                 clks[i].parent_data->index,
                                                                 clks[i].flags, clks[i].mult,
                                                                 clks[i].div);

                if (IS_ERR(clk_hw))
                        return PTR_ERR(clk_hw);

                clks[i].hw = *clk_hw;
                data->clk_data.hws[clks[i].id] = clk_hw;
        }

        return 0;
}
EXPORT_SYMBOL_GPL(eswin_clk_register_fixed_factor);

int eswin_clk_register_mux(struct device *dev, struct eswin_mux_clock *clks,
                           int nums, struct eswin_clock_data *data)
{
        struct clk_hw *clk_hw;
        int i;

        for (i = 0; i < nums; i++) {
                clk_hw = devm_clk_hw_register_mux_parent_data_table(dev, clks[i].name,
                                                                    clks[i].parent_data,
                                                                    clks[i].num_parents,
                                                                    clks[i].flags,
                                                                    data->base + clks[i].reg,
                                                                    clks[i].shift, clks[i].width,
                                                                    clks[i].mux_flags,
                                                                    clks[i].table, &data->lock);

                if (IS_ERR(clk_hw))
                        return PTR_ERR(clk_hw);

                clks[i].hw = *clk_hw;
                data->clk_data.hws[clks[i].id] = clk_hw;
        }

        return 0;
}
EXPORT_SYMBOL_GPL(eswin_clk_register_mux);

static unsigned int _eswin_get_val(unsigned int div, unsigned long flags,
                                   u8 width)
{
        unsigned int maxdiv;

        maxdiv = clk_div_mask(width);
        div = div > maxdiv ? maxdiv : div;

        if (flags & ESWIN_PRIV_DIV_MIN_2)
                return (div < 2) ? 2 : div;

        return div;
}

static unsigned int eswin_div_get_val(unsigned long rate,
                                      unsigned long parent_rate, u8 width,
                                      unsigned long flags)
{
        unsigned int div;

        div = DIV_ROUND_UP_ULL((u64)parent_rate, rate);

        return _eswin_get_val(div, flags, width);
}

static inline struct eswin_divider_clock *to_div_clk(struct clk_hw *hw)
{
        return container_of(hw, struct eswin_divider_clock, hw);
}

static int clk_div_set_rate(struct clk_hw *hw, unsigned long rate,
                            unsigned long parent_rate)
{
        struct eswin_divider_clock *dclk = to_div_clk(hw);
        unsigned long flags;
        unsigned int value;
        u32 val;

        value = eswin_div_get_val(rate, parent_rate, dclk->width,
                                  dclk->priv_flag);

        spin_lock_irqsave(dclk->lock, flags);

        val = readl_relaxed(dclk->ctrl_reg);
        val &= ~(clk_div_mask(dclk->width) << dclk->shift);
        val |= (u32)value << dclk->shift;
        writel_relaxed(val, dclk->ctrl_reg);

        spin_unlock_irqrestore(dclk->lock, flags);

        return 0;
}

static unsigned long clk_div_recalc_rate(struct clk_hw *hw,
                                         unsigned long parent_rate)
{
        struct eswin_divider_clock *dclk = to_div_clk(hw);
        unsigned int div, val;

        val = readl_relaxed(dclk->ctrl_reg) >> dclk->shift;
        val &= clk_div_mask(dclk->width);
        div = _eswin_get_val(val, dclk->priv_flag, dclk->width);

        return DIV_ROUND_UP_ULL((u64)parent_rate, div);
}

static int eswin_clk_bestdiv(unsigned long rate,
                             unsigned long best_parent_rate, u8 width,
                             unsigned long flags)
{
        unsigned long bestdiv, up_rate, down_rate;
        int up, down;

        if (!rate)
                rate = 1;

        /* closest round */
        up = DIV_ROUND_UP_ULL((u64)best_parent_rate, rate);
        down = best_parent_rate / rate;

        up_rate = DIV_ROUND_UP_ULL((u64)best_parent_rate, up);
        down_rate = DIV_ROUND_UP_ULL((u64)best_parent_rate, down);

        bestdiv = (rate - up_rate) <= (down_rate - rate) ? up : down;

        return bestdiv;
}

static int clk_div_determine_rate(struct clk_hw *hw,
                                  struct clk_rate_request *req)
{
        struct eswin_divider_clock *dclk = to_div_clk(hw);
        int div;

        div = eswin_clk_bestdiv(req->rate, req->best_parent_rate, dclk->width,
                                dclk->priv_flag);
        div = _eswin_get_val(div, dclk->priv_flag, dclk->width);
        req->rate = DIV_ROUND_UP_ULL((u64)req->best_parent_rate, div);

        return 0;
}

static const struct clk_ops eswin_clk_div_ops = {
        .set_rate = clk_div_set_rate,
        .recalc_rate = clk_div_recalc_rate,
        .determine_rate = clk_div_determine_rate,
};

struct clk_hw *eswin_register_clkdiv(struct device *dev, unsigned int id,
                                     const char *name,
                                     const struct clk_hw *parent_hw,
                                     unsigned long flags, void __iomem *reg,
                                     u8 shift, u8 width,
                                     unsigned long clk_divider_flags,
                                     unsigned long priv_flag, spinlock_t *lock)
{
        struct eswin_divider_clock *dclk;
        struct clk_init_data init;
        struct clk_hw *clk_hw;
        int ret;

        dclk = devm_kzalloc(dev, sizeof(*dclk), GFP_KERNEL);
        if (!dclk)
                return ERR_PTR(-ENOMEM);

        init.name = name;
        init.ops = &eswin_clk_div_ops;
        init.flags = flags;
        init.parent_hws = &parent_hw;
        init.num_parents = 1;

        /* struct clk_divider assignments */
        dclk->id = id;
        dclk->ctrl_reg = reg;
        dclk->shift = shift;
        dclk->width = width;
        dclk->div_flags = clk_divider_flags;
        dclk->priv_flag = priv_flag;
        dclk->lock = lock;
        dclk->hw.init = &init;

        /* register the clock */
        clk_hw = &dclk->hw;
        ret = devm_clk_hw_register(dev, clk_hw);
        if (ret) {
                dev_err(dev, "failed to register divider clock!\n");
                return ERR_PTR(ret);
        }

        return clk_hw;
}
EXPORT_SYMBOL_GPL(eswin_register_clkdiv);

int eswin_clk_register_divider(struct device *dev,
                               struct eswin_divider_clock *clks,
                               int nums, struct eswin_clock_data *data)
{
        struct clk_hw *clk_hw;
        int i;

        for (i = 0; i < nums; i++) {
                clk_hw = devm_clk_hw_register_divider_parent_data(dev, clks[i].name,
                                                                  clks[i].parent_data,
                                                                  clks[i].flags,
                                                                  data->base + clks[i].reg,
                                                                  clks[i].shift, clks[i].width,
                                                                  clks[i].div_flags, &data->lock);

                if (IS_ERR(clk_hw))
                        return PTR_ERR(clk_hw);

                clks[i].hw = *clk_hw;
                data->clk_data.hws[clks[i].id] = clk_hw;
        }

        return 0;
}
EXPORT_SYMBOL_GPL(eswin_clk_register_divider);

int eswin_clk_register_gate(struct device *dev, struct eswin_gate_clock *clks,
                            int nums, struct eswin_clock_data *data)
{
        struct clk_hw *clk_hw;
        int i;

        for (i = 0; i < nums; i++) {
                clk_hw = devm_clk_hw_register_gate_parent_data(dev, clks[i].name,
                                                               clks[i].parent_data,
                                                               clks[i].flags,
                                                               data->base + clks[i].reg,
                                                               clks[i].bit_idx, clks[i].gate_flags,
                                                               &data->lock);

                if (IS_ERR(clk_hw))
                        return PTR_ERR(clk_hw);

                clks[i].hw = *clk_hw;
                data->clk_data.hws[clks[i].id] = clk_hw;
        }

        return 0;
}
EXPORT_SYMBOL_GPL(eswin_clk_register_gate);

int eswin_clk_register_clks(struct device *dev, struct eswin_clk_info *clks,
                            int nums, struct eswin_clock_data *data)
{
        struct eswin_clk_info *info;
        const struct clk_hw *phw = NULL;
        struct clk_hw *hw;
        int i;

        for (i = 0; i < nums; i++) {
                info = &clks[i];
                switch (info->type) {
                case CLK_FIXED_FACTOR: {
                        const struct eswin_fixed_factor_clock *factor;

                        factor = &info->data.factor;
                        phw = data->clk_data.hws[info->pid];
                        hw = devm_clk_hw_register_fixed_factor_parent_hw(dev, factor->name, phw,
                                                                         factor->flags,
                                                                         factor->mult,
                                                                         factor->div);
                        break;
                }
                case CLK_MUX: {
                        const struct eswin_mux_clock *mux = &info->data.mux;

                        hw = devm_clk_hw_register_mux_parent_data_table(dev, mux->name,
                                                                        mux->parent_data,
                                                                        mux->num_parents,
                                                                        mux->flags,
                                                                        data->base + mux->reg,
                                                                        mux->shift, mux->width,
                                                                        mux->mux_flags,
                                                                        mux->table, &data->lock);
                        break;
                }
                case CLK_DIVIDER: {
                        const struct eswin_divider_clock *div = &info->data.div;

                        phw = data->clk_data.hws[info->pid];
                        if (div->priv_flag)
                                hw = eswin_register_clkdiv(dev, div->id, div->name, phw,
                                                           div->flags, data->base + div->reg,
                                                           div->shift, div->width, div->div_flags,
                                                           div->priv_flag, &data->lock);
                        else
                                hw = devm_clk_hw_register_divider_parent_hw(dev, div->name, phw,
                                                                            div->flags,
                                                                            data->base + div->reg,
                                                                            div->shift, div->width,
                                                                            div->div_flags,
                                                                            &data->lock);
                        break;
                }
                case CLK_GATE: {
                        const struct eswin_gate_clock *gate = &info->data.gate;

                        phw = data->clk_data.hws[info->pid];
                        hw = devm_clk_hw_register_gate_parent_hw(dev, gate->name, phw,
                                                                 gate->flags,
                                                                 data->base + gate->reg,
                                                                 gate->bit_idx, gate->gate_flags,
                                                                 &data->lock);
                        break;
                }
                default:
                        dev_err(dev, "Unidentifiable clock type!\n");
                        return -EINVAL;
                }
                if (IS_ERR(hw))
                        return PTR_ERR(hw);

                info->hw = *hw;
                data->clk_data.hws[info->id] = hw;
        }

        return 0;
}
EXPORT_SYMBOL_GPL(eswin_clk_register_clks);