diff options
Diffstat (limited to 'drivers/clk/visconti/pll.c')
-rw-r--r-- | drivers/clk/visconti/pll.c | 339 |
1 files changed, 339 insertions, 0 deletions
diff --git a/drivers/clk/visconti/pll.c b/drivers/clk/visconti/pll.c new file mode 100644 index 000000000000..a484cb945d67 --- /dev/null +++ b/drivers/clk/visconti/pll.c @@ -0,0 +1,339 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Toshiba Visconti PLL driver + * + * Copyright (c) 2021 TOSHIBA CORPORATION + * Copyright (c) 2021 Toshiba Electronic Devices & Storage Corporation + * + * Nobuhiro Iwamatsu <nobuhiro1.iwamatsu@toshiba.co.jp> + */ + +#include <linux/bitfield.h> +#include <linux/clk-provider.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/io.h> + +#include "pll.h" + +struct visconti_pll { + struct clk_hw hw; + void __iomem *pll_base; + spinlock_t *lock; + unsigned long flags; + const struct visconti_pll_rate_table *rate_table; + size_t rate_count; + struct visconti_pll_provider *ctx; +}; + +#define PLL_CONF_REG 0x0000 +#define PLL_CTRL_REG 0x0004 +#define PLL_FRACMODE_REG 0x0010 +#define PLL_INTIN_REG 0x0014 +#define PLL_FRACIN_REG 0x0018 +#define PLL_REFDIV_REG 0x001c +#define PLL_POSTDIV_REG 0x0020 + +#define PLL_CONFIG_SEL BIT(0) +#define PLL_PLLEN BIT(4) +#define PLL_BYPASS BIT(16) +#define PLL_INTIN_MASK GENMASK(11, 0) +#define PLL_FRACIN_MASK GENMASK(23, 0) +#define PLL_REFDIV_MASK GENMASK(5, 0) +#define PLL_POSTDIV_MASK GENMASK(2, 0) + +#define PLL0_FRACMODE_DACEN BIT(4) +#define PLL0_FRACMODE_DSMEN BIT(0) + +#define PLL_CREATE_FRACMODE(table) (table->dacen << 4 | table->dsmen) +#define PLL_CREATE_OSTDIV(table) (table->postdiv2 << 4 | table->postdiv1) + +static inline struct visconti_pll *to_visconti_pll(struct clk_hw *hw) +{ + return container_of(hw, struct visconti_pll, hw); +} + +static void visconti_pll_get_params(struct visconti_pll *pll, + struct visconti_pll_rate_table *rate_table) +{ + u32 postdiv, val; + + val = readl(pll->pll_base + PLL_FRACMODE_REG); + + rate_table->dacen = FIELD_GET(PLL0_FRACMODE_DACEN, val); + rate_table->dsmen = FIELD_GET(PLL0_FRACMODE_DSMEN, val); + + rate_table->fracin = readl(pll->pll_base + PLL_FRACIN_REG) & PLL_FRACIN_MASK; + rate_table->intin = readl(pll->pll_base + PLL_INTIN_REG) & PLL_INTIN_MASK; + rate_table->refdiv = readl(pll->pll_base + PLL_REFDIV_REG) & PLL_REFDIV_MASK; + + postdiv = readl(pll->pll_base + PLL_POSTDIV_REG); + rate_table->postdiv1 = postdiv & PLL_POSTDIV_MASK; + rate_table->postdiv2 = (postdiv >> 4) & PLL_POSTDIV_MASK; +} + +static const struct visconti_pll_rate_table *visconti_get_pll_settings(struct visconti_pll *pll, + unsigned long rate) +{ + const struct visconti_pll_rate_table *rate_table = pll->rate_table; + int i; + + for (i = 0; i < pll->rate_count; i++) + if (rate == rate_table[i].rate) + return &rate_table[i]; + + return NULL; +} + +static unsigned long visconti_get_pll_rate_from_data(struct visconti_pll *pll, + const struct visconti_pll_rate_table *rate) +{ + const struct visconti_pll_rate_table *rate_table = pll->rate_table; + int i; + + for (i = 0; i < pll->rate_count; i++) + if (memcmp(&rate_table[i].dacen, &rate->dacen, + sizeof(*rate) - sizeof(unsigned long)) == 0) + return rate_table[i].rate; + + /* set default */ + return rate_table[0].rate; +} + +static long visconti_pll_round_rate(struct clk_hw *hw, + unsigned long rate, unsigned long *prate) +{ + struct visconti_pll *pll = to_visconti_pll(hw); + const struct visconti_pll_rate_table *rate_table = pll->rate_table; + int i; + + /* Assumming rate_table is in descending order */ + for (i = 0; i < pll->rate_count; i++) + if (rate >= rate_table[i].rate) + return rate_table[i].rate; + + /* return minimum supported value */ + return rate_table[i - 1].rate; +} + +static unsigned long visconti_pll_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct visconti_pll *pll = to_visconti_pll(hw); + struct visconti_pll_rate_table rate_table; + + memset(&rate_table, 0, sizeof(rate_table)); + visconti_pll_get_params(pll, &rate_table); + + return visconti_get_pll_rate_from_data(pll, &rate_table); +} + +static int visconti_pll_set_params(struct visconti_pll *pll, + const struct visconti_pll_rate_table *rate_table) +{ + writel(PLL_CREATE_FRACMODE(rate_table), pll->pll_base + PLL_FRACMODE_REG); + writel(PLL_CREATE_OSTDIV(rate_table), pll->pll_base + PLL_POSTDIV_REG); + writel(rate_table->intin, pll->pll_base + PLL_INTIN_REG); + writel(rate_table->fracin, pll->pll_base + PLL_FRACIN_REG); + writel(rate_table->refdiv, pll->pll_base + PLL_REFDIV_REG); + + return 0; +} + +static int visconti_pll_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct visconti_pll *pll = to_visconti_pll(hw); + const struct visconti_pll_rate_table *rate_table; + + rate_table = visconti_get_pll_settings(pll, rate); + if (!rate_table) + return -EINVAL; + + return visconti_pll_set_params(pll, rate_table); +} + +static int visconti_pll_is_enabled(struct clk_hw *hw) +{ + struct visconti_pll *pll = to_visconti_pll(hw); + u32 reg; + + reg = readl(pll->pll_base + PLL_CTRL_REG); + + return (reg & PLL_PLLEN); +} + +static int visconti_pll_enable(struct clk_hw *hw) +{ + struct visconti_pll *pll = to_visconti_pll(hw); + const struct visconti_pll_rate_table *rate_table = pll->rate_table; + unsigned long flags; + u32 reg; + + if (visconti_pll_is_enabled(hw)) + return 0; + + spin_lock_irqsave(pll->lock, flags); + + writel(PLL_CONFIG_SEL, pll->pll_base + PLL_CONF_REG); + + reg = readl(pll->pll_base + PLL_CTRL_REG); + reg |= PLL_BYPASS; + writel(reg, pll->pll_base + PLL_CTRL_REG); + + visconti_pll_set_params(pll, &rate_table[0]); + + reg = readl(pll->pll_base + PLL_CTRL_REG); + reg &= ~PLL_PLLEN; + writel(reg, pll->pll_base + PLL_CTRL_REG); + + udelay(1); + + reg = readl(pll->pll_base + PLL_CTRL_REG); + reg |= PLL_PLLEN; + writel(reg, pll->pll_base + PLL_CTRL_REG); + + udelay(40); + + reg = readl(pll->pll_base + PLL_CTRL_REG); + reg &= ~PLL_BYPASS; + writel(reg, pll->pll_base + PLL_CTRL_REG); + + spin_unlock_irqrestore(pll->lock, flags); + + return 0; +} + +static void visconti_pll_disable(struct clk_hw *hw) +{ + struct visconti_pll *pll = to_visconti_pll(hw); + unsigned long flags; + u32 reg; + + if (!visconti_pll_is_enabled(hw)) + return; + + spin_lock_irqsave(pll->lock, flags); + + writel(PLL_CONFIG_SEL, pll->pll_base + PLL_CONF_REG); + + reg = readl(pll->pll_base + PLL_CTRL_REG); + reg |= PLL_BYPASS; + writel(reg, pll->pll_base + PLL_CTRL_REG); + + reg = readl(pll->pll_base + PLL_CTRL_REG); + reg &= ~PLL_PLLEN; + writel(reg, pll->pll_base + PLL_CTRL_REG); + + spin_unlock_irqrestore(pll->lock, flags); +} + +static const struct clk_ops visconti_pll_ops = { + .enable = visconti_pll_enable, + .disable = visconti_pll_disable, + .is_enabled = visconti_pll_is_enabled, + .round_rate = visconti_pll_round_rate, + .recalc_rate = visconti_pll_recalc_rate, + .set_rate = visconti_pll_set_rate, +}; + +static struct clk_hw *visconti_register_pll(struct visconti_pll_provider *ctx, + const char *name, + const char *parent_name, + int offset, + const struct visconti_pll_rate_table *rate_table, + spinlock_t *lock) +{ + struct clk_init_data init; + struct visconti_pll *pll; + struct clk_hw *pll_hw_clk; + size_t len; + int ret; + + pll = kzalloc(sizeof(*pll), GFP_KERNEL); + if (!pll) + return ERR_PTR(-ENOMEM); + + init.name = name; + init.flags = CLK_IGNORE_UNUSED; + init.parent_names = &parent_name; + init.num_parents = 1; + + for (len = 0; rate_table[len].rate != 0; ) + len++; + pll->rate_count = len; + pll->rate_table = kmemdup(rate_table, + pll->rate_count * sizeof(struct visconti_pll_rate_table), + GFP_KERNEL); + WARN(!pll->rate_table, "%s: could not allocate rate table for %s\n", __func__, name); + + init.ops = &visconti_pll_ops; + pll->hw.init = &init; + pll->pll_base = ctx->reg_base + offset; + pll->lock = lock; + pll->ctx = ctx; + + pll_hw_clk = &pll->hw; + ret = clk_hw_register(NULL, &pll->hw); + if (ret) { + pr_err("failed to register pll clock %s : %d\n", name, ret); + kfree(pll); + pll_hw_clk = ERR_PTR(ret); + } + + return pll_hw_clk; +} + +static void visconti_pll_add_lookup(struct visconti_pll_provider *ctx, + struct clk_hw *hw_clk, + unsigned int id) +{ + if (id) + ctx->clk_data.hws[id] = hw_clk; +} + +void __init visconti_register_plls(struct visconti_pll_provider *ctx, + const struct visconti_pll_info *list, + unsigned int nr_plls, + spinlock_t *lock) +{ + int idx; + + for (idx = 0; idx < nr_plls; idx++, list++) { + struct clk_hw *clk; + + clk = visconti_register_pll(ctx, + list->name, + list->parent, + list->base_reg, + list->rate_table, + lock); + if (IS_ERR(clk)) { + pr_err("failed to register clock %s\n", list->name); + continue; + } + + visconti_pll_add_lookup(ctx, clk, list->id); + } +} + +struct visconti_pll_provider * __init visconti_init_pll(struct device_node *np, + void __iomem *base, + unsigned long nr_plls) +{ + struct visconti_pll_provider *ctx; + int i; + + ctx = kzalloc(struct_size(ctx, clk_data.hws, nr_plls), GFP_KERNEL); + if (!ctx) + return ERR_PTR(-ENOMEM); + + for (i = 0; i < nr_plls; ++i) + ctx->clk_data.hws[i] = ERR_PTR(-ENOENT); + + ctx->node = np; + ctx->reg_base = base; + ctx->clk_data.num = nr_plls; + + return ctx; +} |