| // 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); |