// SPDX-License-Identifier: GPL-2.0+ /* * Copyright (c) 2022 Nuvoton Technology Corp. * * Formula for calculating clock rate: * Fout = ((Fin / PRE_DIV) / div) / POST_DIV */ #include #include #include #include #include #include "clk_npcm.h" static int clkid_to_clksel(struct npcm_clk_select *selector, int id) { int i; for (i = 0; i < selector->num_parents; i++) { if (selector->parents[i].id == id) return selector->parents[i].clksel; } return -EINVAL; } static int clksel_to_clkid(struct npcm_clk_select *selector, int clksel) { int i; for (i = 0; i < selector->num_parents; i++) { if (selector->parents[i].clksel == clksel) return selector->parents[i].id; } return -EINVAL; } static struct npcm_clk_pll *npcm_clk_pll_get(struct npcm_clk_data *clk_data, int id) { struct npcm_clk_pll *pll = clk_data->clk_plls; int i; for (i = 0; i < clk_data->num_plls; i++) { if (pll->id == id) return pll; pll++; } return NULL; } static struct npcm_clk_select *npcm_clk_selector_get(struct npcm_clk_data *clk_data, int id) { struct npcm_clk_select *selector = clk_data->clk_selectors; int i; for (i = 0; i < clk_data->num_selectors; i++) { if (selector->id == id) return selector; selector++; } return NULL; } static struct npcm_clk_div *npcm_clk_divider_get(struct npcm_clk_data *clk_data, int id) { struct npcm_clk_div *divider = clk_data->clk_dividers; int i; for (i = 0; i < clk_data->num_dividers; i++) { if (divider->id == id) return divider; divider++; } return NULL; } static ulong npcm_clk_get_fin(struct clk *clk) { struct npcm_clk_priv *priv = dev_get_priv(clk->dev); struct npcm_clk_select *selector; struct clk parent; ulong parent_rate; u32 val, clksel; int ret; selector = npcm_clk_selector_get(priv->clk_data, clk->id); if (!selector) return 0; if (selector->flags & FIXED_PARENT) { clksel = 0; } else { val = readl(priv->base + selector->reg); clksel = (val & selector->mask) >> (ffs(selector->mask) - 1); } parent.id = clksel_to_clkid(selector, clksel); ret = clk_request(clk->dev, &parent); if (ret) return 0; parent_rate = clk_get_rate(&parent); debug("fin of clk%lu = %lu\n", clk->id, parent_rate); return parent_rate; } static u32 npcm_clk_get_div(struct clk *clk) { struct npcm_clk_priv *priv = dev_get_priv(clk->dev); struct npcm_clk_div *divider; u32 val, div; divider = npcm_clk_divider_get(priv->clk_data, clk->id); if (!divider) return 0; val = readl(priv->base + divider->reg); div = (val & divider->mask) >> (ffs(divider->mask) - 1); if (divider->flags & DIV_TYPE1) div = div + 1; else div = 1 << div; if (divider->flags & PRE_DIV2) div = div << 1; return div; } static u32 npcm_clk_set_div(struct clk *clk, u32 div) { struct npcm_clk_priv *priv = dev_get_priv(clk->dev); struct npcm_clk_div *divider; u32 val, clkdiv; divider = npcm_clk_divider_get(priv->clk_data, clk->id); if (!divider) return -EINVAL; if (divider->flags & PRE_DIV2) div = div >> 1; if (divider->flags & DIV_TYPE1) clkdiv = div - 1; else clkdiv = ilog2(div); val = readl(priv->base + divider->reg); val &= ~divider->mask; val |= (clkdiv << (ffs(divider->mask) - 1)) & divider->mask; writel(val, priv->base + divider->reg); return 0; } static ulong npcm_clk_get_fout(struct clk *clk) { ulong parent_rate; u32 div; parent_rate = npcm_clk_get_fin(clk); if (!parent_rate) return -EINVAL; div = npcm_clk_get_div(clk); if (!div) return -EINVAL; debug("fout of clk%lu = (%lu / %u)\n", clk->id, parent_rate, div); return (parent_rate / div); } static ulong npcm_clk_get_pll_fout(struct clk *clk) { struct npcm_clk_priv *priv = dev_get_priv(clk->dev); struct npcm_clk_pll *pll; struct clk parent; ulong parent_rate; ulong fbdv, indv, otdv1, otdv2; u32 val; u64 ret; pll = npcm_clk_pll_get(priv->clk_data, clk->id); if (!pll) return -ENODEV; parent.id = pll->parent_id; ret = clk_request(clk->dev, &parent); if (ret) return ret; parent_rate = clk_get_rate(&parent); val = readl(priv->base + pll->reg); indv = FIELD_GET(PLLCON_INDV, val); fbdv = FIELD_GET(PLLCON_FBDV, val); otdv1 = FIELD_GET(PLLCON_OTDV1, val); otdv2 = FIELD_GET(PLLCON_OTDV2, val); ret = (u64)parent_rate * fbdv; do_div(ret, indv * otdv1 * otdv2); if (pll->flags & POST_DIV2) do_div(ret, 2); debug("fout of pll(id %lu) = %llu\n", clk->id, ret); return ret; } static ulong npcm_clk_get_rate(struct clk *clk) { struct npcm_clk_priv *priv = dev_get_priv(clk->dev); struct npcm_clk_data *clk_data = priv->clk_data; struct clk refclk; int ret; debug("%s: id %lu\n", __func__, clk->id); if (clk->id == clk_data->refclk_id) { ret = clk_get_by_name(clk->dev, "refclk", &refclk); if (!ret) return clk_get_rate(&refclk); else return ret; } if (clk->id >= clk_data->pll0_id && clk->id < clk_data->pll0_id + clk_data->num_plls) return npcm_clk_get_pll_fout(clk); else return npcm_clk_get_fout(clk); } static ulong npcm_clk_set_rate(struct clk *clk, ulong rate) { ulong parent_rate; u32 div; int ret; debug("%s: id %lu, rate %lu\n", __func__, clk->id, rate); parent_rate = npcm_clk_get_fin(clk); if (!parent_rate) return -EINVAL; div = DIV_ROUND_UP(parent_rate, rate); ret = npcm_clk_set_div(clk, div); if (ret) return ret; debug("%s: rate %lu, new rate (%lu / %u)\n", __func__, rate, parent_rate, div); return (parent_rate / div); } static int npcm_clk_set_parent(struct clk *clk, struct clk *parent) { struct npcm_clk_priv *priv = dev_get_priv(clk->dev); struct npcm_clk_select *selector; int clksel; u32 val; debug("%s: id %lu, parent %lu\n", __func__, clk->id, parent->id); selector = npcm_clk_selector_get(priv->clk_data, clk->id); if (!selector) return -EINVAL; clksel = clkid_to_clksel(selector, parent->id); if (clksel < 0) return -EINVAL; val = readl(priv->base + selector->reg); val &= ~selector->mask; val |= clksel << (ffs(selector->mask) - 1); writel(val, priv->base + selector->reg); return 0; } static int npcm_clk_request(struct clk *clk) { struct npcm_clk_priv *priv = dev_get_priv(clk->dev); if (clk->id >= priv->num_clks) return -EINVAL; return 0; } const struct clk_ops npcm_clk_ops = { .get_rate = npcm_clk_get_rate, .set_rate = npcm_clk_set_rate, .set_parent = npcm_clk_set_parent, .request = npcm_clk_request, };