mirror of
https://github.com/torvalds/linux.git
synced 2026-05-12 16:18:45 +02:00
clk: eswin: Add eic7700 clock driver
Add clock drivers for the EIC7700 SoC. The clock controller on the ESWIN EIC7700 provides various clocks to different IP blocks within the SoC. Signed-off-by: Yifeng Huang <huangyifeng@eswincomputing.com> Tested-by: Marcel Ziswiler <marcel@ziswiler.com> # ebc77 Reviewed-by: Brian Masney <bmasney@redhat.com> Signed-off-by: Xuyang Dong <dongxuyang@eswincomputing.com> Tested-by: Bo Gan <ganboing@gmail.com> # hfp550 Signed-off-by: Stephen Boyd <sboyd@kernel.org>
This commit is contained in:
parent
8add6d87dc
commit
cd44f127c1
|
|
@ -504,6 +504,7 @@ source "drivers/clk/analogbits/Kconfig"
|
|||
source "drivers/clk/aspeed/Kconfig"
|
||||
source "drivers/clk/baikal-t1/Kconfig"
|
||||
source "drivers/clk/bcm/Kconfig"
|
||||
source "drivers/clk/eswin/Kconfig"
|
||||
source "drivers/clk/hisilicon/Kconfig"
|
||||
source "drivers/clk/imgtec/Kconfig"
|
||||
source "drivers/clk/imx/Kconfig"
|
||||
|
|
|
|||
|
|
@ -120,6 +120,7 @@ obj-$(CONFIG_CLK_BAIKAL_T1) += baikal-t1/
|
|||
obj-y += bcm/
|
||||
obj-$(CONFIG_ARCH_BERLIN) += berlin/
|
||||
obj-$(CONFIG_ARCH_DAVINCI) += davinci/
|
||||
obj-$(CONFIG_COMMON_CLK_ESWIN) += eswin/
|
||||
obj-$(CONFIG_ARCH_HISI) += hisilicon/
|
||||
obj-y += imgtec/
|
||||
obj-y += imx/
|
||||
|
|
|
|||
15
drivers/clk/eswin/Kconfig
Normal file
15
drivers/clk/eswin/Kconfig
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
config COMMON_CLK_ESWIN
|
||||
bool
|
||||
|
||||
config COMMON_CLK_EIC7700
|
||||
tristate "EIC7700 Clock Driver"
|
||||
depends on ARCH_ESWIN || COMPILE_TEST
|
||||
select COMMON_CLK_ESWIN
|
||||
default ARCH_ESWIN
|
||||
help
|
||||
This driver provides support for clock controller on ESWIN EIC7700
|
||||
SoC. The clock controller generates and supplies clocks to various
|
||||
peripherals within the SoC.
|
||||
Say yes here to support the clock controller on the EIC7700 SoC.
|
||||
8
drivers/clk/eswin/Makefile
Normal file
8
drivers/clk/eswin/Makefile
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
#
|
||||
# Eswin Clock specific Makefile
|
||||
#
|
||||
|
||||
obj-$(CONFIG_COMMON_CLK_ESWIN) += clk.o
|
||||
|
||||
obj-$(CONFIG_COMMON_CLK_EIC7700) += clk-eic7700.o
|
||||
1376
drivers/clk/eswin/clk-eic7700.c
Normal file
1376
drivers/clk/eswin/clk-eic7700.c
Normal file
File diff suppressed because it is too large
Load Diff
586
drivers/clk/eswin/clk.c
Normal file
586
drivers/clk/eswin/clk.c
Normal file
|
|
@ -0,0 +1,586 @@
|
|||
// 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);
|
||||
340
drivers/clk/eswin/common.h
Normal file
340
drivers/clk/eswin/common.h
Normal file
|
|
@ -0,0 +1,340 @@
|
|||
/* 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>
|
||||
*/
|
||||
|
||||
#ifndef __ESWIN_COMMON_H__
|
||||
#define __ESWIN_COMMON_H__
|
||||
|
||||
#define APLL_HIGH_FREQ 983040000
|
||||
#define APLL_LOW_FREQ 225792000
|
||||
#define PLL_HIGH_FREQ 1800000000
|
||||
#define PLL_LOW_FREQ 24000000
|
||||
|
||||
/*
|
||||
* ESWIN_PRIV_DIV_MIN_2: If ESWIN_PRIV_DIV_MIN_2 is set, the minimum value of
|
||||
* the register is 2, i.e. the minimum division ratio is 2.
|
||||
*/
|
||||
#define ESWIN_PRIV_DIV_MIN_2 BIT(0)
|
||||
|
||||
enum eswin_clk_type {
|
||||
CLK_FIXED_FACTOR,
|
||||
CLK_MUX,
|
||||
CLK_DIVIDER,
|
||||
CLK_GATE,
|
||||
};
|
||||
|
||||
struct eswin_clock_data {
|
||||
void __iomem *base;
|
||||
struct clk_hw *original_clk;
|
||||
struct notifier_block pll_nb;
|
||||
spinlock_t lock; /* protect register read-modify-write cycle */
|
||||
struct clk_hw_onecell_data clk_data;
|
||||
};
|
||||
|
||||
struct eswin_divider_clock {
|
||||
struct clk_hw hw;
|
||||
unsigned int id;
|
||||
const char *name;
|
||||
const struct clk_parent_data *parent_data;
|
||||
void __iomem *ctrl_reg; /* register address of the divider clock */
|
||||
unsigned long flags;
|
||||
unsigned long reg; /* register offset */
|
||||
u8 shift;
|
||||
u8 width;
|
||||
unsigned long div_flags;
|
||||
unsigned long priv_flag;
|
||||
spinlock_t *lock; /* protect register read-modify-write cycle */
|
||||
};
|
||||
|
||||
struct eswin_fixed_rate_clock {
|
||||
struct clk_hw hw;
|
||||
unsigned int id;
|
||||
const char *name;
|
||||
unsigned long flags;
|
||||
unsigned long rate;
|
||||
};
|
||||
|
||||
struct eswin_fixed_factor_clock {
|
||||
struct clk_hw hw;
|
||||
unsigned int id;
|
||||
const char *name;
|
||||
const struct clk_parent_data *parent_data;
|
||||
unsigned long mult;
|
||||
unsigned long div;
|
||||
unsigned long flags;
|
||||
};
|
||||
|
||||
struct eswin_gate_clock {
|
||||
struct clk_hw hw;
|
||||
unsigned int id;
|
||||
const char *name;
|
||||
const struct clk_parent_data *parent_data;
|
||||
unsigned long flags;
|
||||
unsigned long reg;
|
||||
u8 bit_idx;
|
||||
u8 gate_flags;
|
||||
};
|
||||
|
||||
struct eswin_mux_clock {
|
||||
struct clk_hw hw;
|
||||
unsigned int id;
|
||||
const char *name;
|
||||
const struct clk_parent_data *parent_data;
|
||||
u8 num_parents;
|
||||
unsigned long flags;
|
||||
unsigned long reg;
|
||||
u8 shift;
|
||||
u8 width;
|
||||
u8 mux_flags;
|
||||
u32 *table;
|
||||
};
|
||||
|
||||
struct eswin_pll_clock {
|
||||
struct clk_hw hw;
|
||||
u32 id;
|
||||
const char *name;
|
||||
const struct clk_parent_data *parent_data;
|
||||
const u32 ctrl_reg0;
|
||||
const u8 fbdiv_shift;
|
||||
|
||||
const u32 ctrl_reg1;
|
||||
const u8 frac_shift;
|
||||
|
||||
const u32 ctrl_reg2;
|
||||
|
||||
const u32 status_reg;
|
||||
const u8 lock_shift;
|
||||
const u8 lock_width;
|
||||
|
||||
const u64 max_rate;
|
||||
const u64 min_rate;
|
||||
};
|
||||
|
||||
struct eswin_clk_pll {
|
||||
struct clk_hw hw;
|
||||
u32 id;
|
||||
void __iomem *ctrl_reg0;
|
||||
u8 fbdiv_shift;
|
||||
|
||||
void __iomem *ctrl_reg1;
|
||||
u8 frac_shift;
|
||||
|
||||
void __iomem *ctrl_reg2;
|
||||
|
||||
void __iomem *status_reg;
|
||||
u8 lock_shift;
|
||||
u8 lock_width;
|
||||
|
||||
u64 max_rate;
|
||||
u64 min_rate;
|
||||
};
|
||||
|
||||
struct eswin_clk_info {
|
||||
unsigned int type;
|
||||
unsigned int pid;
|
||||
unsigned int id;
|
||||
struct clk_hw hw;
|
||||
union {
|
||||
struct eswin_divider_clock div;
|
||||
struct eswin_fixed_factor_clock factor;
|
||||
struct eswin_gate_clock gate;
|
||||
struct eswin_mux_clock mux;
|
||||
} data;
|
||||
};
|
||||
|
||||
struct eswin_clock_data *eswin_clk_init(struct platform_device *pdev,
|
||||
size_t nr_clks);
|
||||
int eswin_clk_register_fixed_rate(struct device *dev,
|
||||
struct eswin_fixed_rate_clock *clks,
|
||||
int nums, struct eswin_clock_data *data);
|
||||
int eswin_clk_register_pll(struct device *dev, struct eswin_pll_clock *clks,
|
||||
int nums, struct eswin_clock_data *data);
|
||||
int eswin_clk_register_fixed_factor(struct device *dev,
|
||||
struct eswin_fixed_factor_clock *clks,
|
||||
int nums, struct eswin_clock_data *data);
|
||||
int eswin_clk_register_mux(struct device *dev, struct eswin_mux_clock *clks,
|
||||
int nums, struct eswin_clock_data *data);
|
||||
int eswin_clk_register_divider(struct device *dev,
|
||||
struct eswin_divider_clock *clks,
|
||||
int nums, struct eswin_clock_data *data);
|
||||
int eswin_clk_register_gate(struct device *dev, struct eswin_gate_clock *clks,
|
||||
int nums, struct eswin_clock_data *data);
|
||||
int eswin_clk_register_clks(struct device *dev, struct eswin_clk_info *clks,
|
||||
int nums, struct eswin_clock_data *data);
|
||||
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);
|
||||
|
||||
#define ESWIN_DIV(_id, _name, _pdata, _flags, _reg, _shift, _width, \
|
||||
_dflags, _pflag) \
|
||||
{ \
|
||||
.id = _id, \
|
||||
.name = _name, \
|
||||
.parent_data = _pdata, \
|
||||
.flags = _flags, \
|
||||
.reg = _reg, \
|
||||
.shift = _shift, \
|
||||
.width = _width, \
|
||||
.div_flags = _dflags, \
|
||||
.priv_flag = _pflag, \
|
||||
}
|
||||
|
||||
#define ESWIN_DIV_TYPE(_id, _name, _pid, _flags, _reg, _shift, _width, \
|
||||
_dflags, _pflag) \
|
||||
{ \
|
||||
.type = CLK_DIVIDER, \
|
||||
.pid = _pid, \
|
||||
.id = _id, \
|
||||
.data = { \
|
||||
.div = { \
|
||||
.name = _name, \
|
||||
.flags = _flags, \
|
||||
.reg = _reg, \
|
||||
.shift = _shift, \
|
||||
.width = _width, \
|
||||
.div_flags = _dflags, \
|
||||
.priv_flag = _pflag, \
|
||||
}, \
|
||||
}, \
|
||||
}
|
||||
|
||||
#define ESWIN_FACTOR(_id, _name, _pdata, _mult, _div, _flags) \
|
||||
{ \
|
||||
.id = _id, \
|
||||
.name = _name, \
|
||||
.parent_data = _pdata, \
|
||||
.mult = _mult, \
|
||||
.div = _div, \
|
||||
.flags = _flags, \
|
||||
}
|
||||
|
||||
#define ESWIN_FACTOR_TYPE(_id, _name, _pid, _mult, _div, _flags) \
|
||||
{ \
|
||||
.type = CLK_FIXED_FACTOR, \
|
||||
.pid = _pid, \
|
||||
.id = _id, \
|
||||
.data = { \
|
||||
.factor = { \
|
||||
.name = _name, \
|
||||
.mult = _mult, \
|
||||
.div = _div, \
|
||||
.flags = _flags, \
|
||||
}, \
|
||||
}, \
|
||||
}
|
||||
|
||||
#define ESWIN_FIXED(_id, _name, _flags, _rate) \
|
||||
{ \
|
||||
.id = _id, \
|
||||
.name = _name, \
|
||||
.flags = _flags, \
|
||||
.rate = _rate, \
|
||||
}
|
||||
|
||||
#define ESWIN_GATE(_id, _name, _pdata, _flags, _reg, _idx, _gflags) \
|
||||
{ \
|
||||
.id = _id, \
|
||||
.name = _name, \
|
||||
.parent_data = _pdata, \
|
||||
.flags = _flags, \
|
||||
.reg = _reg, \
|
||||
.bit_idx = _idx, \
|
||||
.gate_flags = _gflags, \
|
||||
}
|
||||
|
||||
#define ESWIN_GATE_TYPE(_id, _name, _pid, _flags, _reg, _idx, _gflags) \
|
||||
{ \
|
||||
.type = CLK_GATE, \
|
||||
.pid = _pid, \
|
||||
.id = _id, \
|
||||
.data = { \
|
||||
.gate = { \
|
||||
.name = _name, \
|
||||
.flags = _flags, \
|
||||
.reg = _reg, \
|
||||
.bit_idx = _idx, \
|
||||
.gate_flags = _gflags, \
|
||||
}, \
|
||||
}, \
|
||||
}
|
||||
|
||||
#define ESWIN_MUX(_id, _name, _pdata, _num_parents, _flags, _reg, \
|
||||
_shift, _width, _mflags) \
|
||||
{ \
|
||||
.id = _id, \
|
||||
.name = _name, \
|
||||
.parent_data = _pdata, \
|
||||
.num_parents = _num_parents, \
|
||||
.flags = _flags, \
|
||||
.reg = _reg, \
|
||||
.shift = _shift, \
|
||||
.width = _width, \
|
||||
.mux_flags = _mflags, \
|
||||
.table = NULL, \
|
||||
}
|
||||
|
||||
#define ESWIN_MUX_TBL(_id, _name, _pdata, _num_parents, _flags, _reg, \
|
||||
_shift, _width, _mflags, _table) \
|
||||
{ \
|
||||
.id = _id, \
|
||||
.name = _name, \
|
||||
.parent_data = _pdata, \
|
||||
.num_parents = _num_parents, \
|
||||
.flags = _flags, \
|
||||
.reg = _reg, \
|
||||
.shift = _shift, \
|
||||
.width = _width, \
|
||||
.mux_flags = _mflags, \
|
||||
.table = _table, \
|
||||
}
|
||||
|
||||
#define ESWIN_MUX_TYPE(_id, _name, _pdata, _num_parents, _flags, _reg, \
|
||||
_shift, _width, _mflags, _table) \
|
||||
{ \
|
||||
.type = CLK_MUX, \
|
||||
.id = _id, \
|
||||
.data = { \
|
||||
.mux = { \
|
||||
.name = _name, \
|
||||
.parent_data = _pdata, \
|
||||
.num_parents = _num_parents, \
|
||||
.flags = _flags, \
|
||||
.reg = _reg, \
|
||||
.shift = _shift, \
|
||||
.width = _width, \
|
||||
.mux_flags = _mflags, \
|
||||
.table = _table, \
|
||||
}, \
|
||||
}, \
|
||||
}
|
||||
|
||||
#define ESWIN_PLL(_id, _name, _pdata, _reg0, _fb_shift, _reg1, \
|
||||
_frac_shift, _reg2, _reg, _lock_shift, _lock_width, \
|
||||
_max_rate, _min_rate) \
|
||||
{ \
|
||||
.id = _id, \
|
||||
.name = _name, \
|
||||
.parent_data = _pdata, \
|
||||
.ctrl_reg0 = _reg0, \
|
||||
.fbdiv_shift = _fb_shift, \
|
||||
.ctrl_reg1 = _reg1, \
|
||||
.frac_shift = _frac_shift, \
|
||||
.ctrl_reg2 = _reg2, \
|
||||
.status_reg = _reg, \
|
||||
.lock_shift = _lock_shift, \
|
||||
.lock_width = _lock_width, \
|
||||
.max_rate = _max_rate, \
|
||||
.min_rate = _min_rate, \
|
||||
}
|
||||
|
||||
#endif /* __ESWIN_COMMON_H__ */
|
||||
Loading…
Reference in New Issue
Block a user