Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PLL #11

Open
wants to merge 3 commits into
base: histb
Choose a base branch
from
Open

PLL #11

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion drivers/clk/hisilicon/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# Hisilicon Clock specific Makefile
#

obj-y += clk.o clkgate-separated.o clkdivider-hi6220.o clk-hisi-phase.o
obj-y += clk.o clkgate-separated.o clkdivider-hi6220.o clk-hisi-phase.o clk-pll.o

obj-$(CONFIG_ARCH_HI3xxx) += clk-hi3620.o
obj-$(CONFIG_ARCH_HIP04) += clk-hip04.o
Expand Down
4 changes: 2 additions & 2 deletions drivers/clk/hisilicon/clk-hi3559a.c
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,7 @@ static const struct clk_ops hisi_clk_pll_ops = {
.recalc_rate = clk_pll_recalc_rate,
};

static void hisi_clk_register_pll(struct hi3559av100_pll_clock *clks,
static void _hisi_clk_register_pll(struct hi3559av100_pll_clock *clks,
int nums, struct hisi_clock_data *data, struct device *dev)
{
void __iomem *base = data->base;
Expand Down Expand Up @@ -517,7 +517,7 @@ static struct hisi_clock_data *hi3559av100_clk_register(
if (ret)
return ERR_PTR(ret);

hisi_clk_register_pll(hi3559av100_pll_clks,
_hisi_clk_register_pll(hi3559av100_pll_clks,
ARRAY_SIZE(hi3559av100_pll_clks), clk_data, &pdev->dev);

ret = hisi_clk_register_mux(hi3559av100_mux_clks_crg,
Expand Down
171 changes: 171 additions & 0 deletions drivers/clk/hisilicon/clk-pll.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* PLL driver for HiSilicon SoCs
*
* Copyright 2024 (c) Yang Xiwen <[email protected]>
*/

#include <linux/bitops.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/module.h>

#include "clk.h"

/* PLL has two conf regs in total */
#define HISI_PLL_CFG(n) ((n) * 4)

/* reg 0 definitions */
#define HISI_PLL_FRAC GENMASK(23, 0)
#define HISI_PLL_POSTDIV1 GENMASK(26, 24)
#define HISI_PLL_POSTDIV2 GENMASK(30, 28)

/* reg 1 definitions */
#define HISI_PLL_FBDIV GENMASK(11, 0)
#define HISI_PLL_REFDIV GENMASK(17, 12)
#define HISI_PLL_PD BIT(20)
#define HISI_PLL_FOUTVCOPD BIT(21)
#define HISI_PLL_FOUT4PHASEPD BIT(22)
#define HISI_PLL_FOUTPOSTDIVPD BIT(23)
#define HISI_PLL_DACPD BIT(24)
#define HISI_PLL_DSMPD BIT(25)
#define HISI_PLL_BYPASS BIT(26)

/*
* Datasheet said the maximum is 3.2GHz,
* but tests show it can be very high
*
* Leave some margin here (8 GHz should be fine)
*/
#define HISI_PLL_FOUTVCO_MAX_RATE 8000000000
/* 800 MHz */
#define HISI_PLL_FOUTVCO_MIN_RATE 800000000

struct hisi_pll {
struct clk_hw hw;
void __iomem *base;
u8 postdiv1, postdiv2, refdiv;
u32 divisor;
};

#define to_hisi_pll(_hw) container_of(_hw, struct hisi_pll, hw)

static int hisi_pll_prepare(struct clk_hw *hw)
{
struct hisi_pll *pll = to_hisi_pll(hw);
u32 reg;

reg = readl(pll->base + HISI_PLL_CFG(0));
pll->postdiv1 = FIELD_GET(HISI_PLL_POSTDIV1, reg);
pll->postdiv2 = FIELD_GET(HISI_PLL_POSTDIV2, reg);
// We don't use frac, clear it
reg &= ~HISI_PLL_FRAC;
writel(reg, pll->base + HISI_PLL_CFG(0));

reg = readl(pll->base + HISI_PLL_CFG(1));
pll->refdiv = FIELD_GET(HISI_PLL_REFDIV, reg);

pll->divisor = pll->refdiv * pll->postdiv1 * pll->postdiv2;

// return -EINVAL if boot loader does not init PLL correctly
if (pll->divisor == 0) {
pr_err("%s: PLLs are not initialized by boot loader correctly!\n", __func__);
return -EINVAL;
}

return 0;
}

static int hisi_pll_set_rate(struct clk_hw *hw, ulong rate, ulong parent_rate)
{
struct hisi_pll *pll = to_hisi_pll(hw);
u64 fbdiv = rate * pll->divisor;
u32 reg;

do_div(fbdiv, parent_rate);

reg = readl(pll->base + HISI_PLL_CFG(1));
reg &= ~HISI_PLL_FBDIV;
reg |= FIELD_PREP(HISI_PLL_FBDIV, fbdiv);
writel(reg, pll->base + HISI_PLL_CFG(1));

/* TODO: wait for PLL lock? */

return 0;
}

static int hisi_pll_determine_rate(struct clk_hw *hw, struct clk_rate_request *req)
{
struct hisi_pll *pll = to_hisi_pll(hw);
u64 vco, ref_rate = req->best_parent_rate;

if (ref_rate == 0)
return -EINVAL;

do_div(ref_rate, pll->refdiv);
vco = clamp(req->rate * (pll->postdiv1 * pll->postdiv2),
HISI_PLL_FOUTVCO_MIN_RATE, HISI_PLL_FOUTVCO_MAX_RATE);
vco = rounddown(vco, ref_rate);
if (vco < HISI_PLL_FOUTVCO_MIN_RATE)
vco += ref_rate;

do_div(vco, pll->postdiv1 * pll->postdiv2);
req->rate = vco;

return 0;
}

static ulong hisi_pll_recalc_rate(struct clk_hw *hw, ulong parent_rate)
{
struct hisi_pll *pll = to_hisi_pll(hw);
u32 reg, fbdiv;

reg = readl(pll->base + HISI_PLL_CFG(1));
fbdiv = FIELD_GET(HISI_PLL_FBDIV, reg);
parent_rate *= fbdiv;
do_div(parent_rate, pll->divisor);

return parent_rate;
}

static const struct clk_ops hisi_pll_ops = {
.prepare = hisi_pll_prepare,
.set_rate = hisi_pll_set_rate,
.determine_rate = hisi_pll_determine_rate,
.recalc_rate = hisi_pll_recalc_rate,
};

/*
* devm_hisi_pll_register - register a HiSilicon PLL
*
* @dev: clk provider
* @name: clock name
* @parent_name: parent clock, usually 24MHz OSC
* #flags: CCF common flags
* @reg: register address
*/
struct clk *devm_clk_register_hisi_pll(struct device *dev, const char *name, const char *parent,
unsigned int flags, void __iomem *reg)
{
struct hisi_pll *pll;
struct clk_init_data init;

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

if (!parent)
return ERR_PTR(-EINVAL);

init.name = name;
init.ops = &hisi_pll_ops;
init.flags = flags;
init.parent_names = &parent;
init.num_parents = 1;

pll->base = reg;
pll->hw.init = &init;

return devm_clk_register(dev, &pll->hw);
}
EXPORT_SYMBOL_GPL(devm_clk_register_hisi_pll);
24 changes: 24 additions & 0 deletions drivers/clk/hisilicon/clk.c
Original file line number Diff line number Diff line change
Expand Up @@ -341,3 +341,27 @@ void __init hi6220_clk_register_divider(const struct hi6220_divider_clock *clks,
data->clk_data.clks[clks[i].id] = clk;
}
}

int hisi_clk_register_pll(struct device *dev, const struct hisi_pll_clock *clks,
int nums, struct hisi_clock_data *data)
{
struct clk *clk;
void __iomem *base = data->base;
int i;

for (i = 0; i < nums; i++) {
clk = devm_clk_register_hisi_pll(dev, clks[i].name, clks[i].parent_name,
clks[i].flags, base + clks[i].offset);
if (IS_ERR(clk)) {
pr_err("%s: failed to register clock %s\n",
__func__, clks[i].name);
return PTR_ERR(clk);
}


data->clk_data.clks[clks[i].id] = clk;
}

return 0;
}
EXPORT_SYMBOL_GPL(hisi_clk_register_pll);
12 changes: 12 additions & 0 deletions drivers/clk/hisilicon/clk.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,14 @@ struct hisi_gate_clock {
const char *alias;
};

struct hisi_pll_clock {
unsigned int id;
const char *name;
const char *parent_name;
unsigned long flags;
unsigned long offset;
};

struct clk *hisi_register_clkgate_sep(struct device *, const char *,
const char *, unsigned long,
void __iomem *, u8,
Expand All @@ -122,6 +130,8 @@ int hisi_clk_register_mux(const struct hisi_mux_clock *, int,
struct clk *clk_register_hisi_phase(struct device *dev,
const struct hisi_phase_clock *clks,
void __iomem *base, spinlock_t *lock);
struct clk *devm_clk_register_hisi_pll(struct device *dev, const char *name, const char *parent,
unsigned int flags, void __iomem *reg);
int hisi_clk_register_phase(struct device *dev,
const struct hisi_phase_clock *clks,
int nums, struct hisi_clock_data *data);
Expand All @@ -133,6 +143,8 @@ void hisi_clk_register_gate_sep(const struct hisi_gate_clock *,
int, struct hisi_clock_data *);
void hi6220_clk_register_divider(const struct hi6220_divider_clock *,
int, struct hisi_clock_data *);
int hisi_clk_register_pll(struct device *dev, const struct hisi_pll_clock *clks,
int nums, struct hisi_clock_data *data);

#define hisi_clk_unregister(type) \
static inline \
Expand Down