mirror of
https://github.com/torvalds/linux.git
synced 2024-11-23 12:42:02 +00:00
6f691a5862
As part of the effort to improve the MediaTek clk drivers, the next step is to switch from the old 'struct clk' clk prodivder APIs to the new 'struct clk_hw' ones. In a previous patch, 'struct clk_onecell_data' was replaced with 'struct clk_hw_onecell_data', with (struct clk_hw *)->clk and __clk_get_hw() bridging the new data structures and old code. Now switch from the old 'clk_(un)?register*()' APIs to the new 'clk_hw_(un)?register*()' ones. This is done with the coccinelle script below. Unfortunately this also leaves clk-mt8173.c with a compile error that would need a coccinelle script longer than the actual diff to fix. This last part is fixed up by hand. // Fix prototypes @@ identifier F =~ "^mtk_clk_register_"; @@ - struct clk * + struct clk_hw * F(...); // Fix calls to mtk_clk_register_<singular> @ reg @ identifier F =~ "^mtk_clk_register_"; identifier FS =~ "^mtk_clk_register_[a-z_]*s"; identifier I; expression clk_data; expression E; @@ FS(...) { ... - struct clk *I; + struct clk_hw *hw; ... for (...;...;...) { ... ( - I + hw = - clk_register_fixed_rate( + clk_hw_register_fixed_rate( ... ); | - I + hw = - clk_register_fixed_factor( + clk_hw_register_fixed_factor( ... ); | - I + hw = - clk_register_divider( + clk_hw_register_divider( ... ); | - I + hw = F(...); ) ... if ( - IS_ERR(I) + IS_ERR(hw) ) { pr_err(..., - I + hw ,...); ... } - clk_data->hws[E] = __clk_get_hw(I); + clk_data->hws[E] = hw; } ... } @ depends on reg @ identifier reg.I; @@ return PTR_ERR( - I + hw ); // Fix mtk_clk_register_composite to return clk_hw instead of clk @@ identifier I, R; expression E; @@ - struct clk * + struct clk_hw * mtk_clk_register_composite(...) { ... - struct clk *I; + struct clk_hw *hw; ... - I = clk_register_composite( + hw = clk_hw_register_composite( ...); if (IS_ERR( - I + hw )) { ... R = PTR_ERR( - I + hw ); ... } return - I + hw ; ... } // Fix other mtk_clk_register_<singular> to return clk_hw instead of clk @@ identifier F =~ "^mtk_clk_register_"; identifier I, D, C; expression E; @@ - struct clk * + struct clk_hw * F(...) { ... - struct clk *I; + int ret; ... - I = clk_register(D, E); + ret = clk_hw_register(D, E); ... ( - if (IS_ERR(I)) + if (ret) { kfree(C); + return ERR_PTR(ret); + } | - if (IS_ERR(I)) + if (ret) { kfree(C); - return I; + return ERR_PTR(ret); } ) - return I; + return E; } // Fix mtk_clk_unregister_<singular> to take clk_hw instead of clk @@ identifier F =~ "^mtk_clk_unregister_"; identifier I, I2; @@ static void F( - struct clk *I + struct clk_hw *I2 ) { ... - struct clk_hw *I2; ... - I2 = __clk_get_hw(I); ... ( - clk_unregister(I); + clk_hw_unregister(I2); | - clk_unregister_composite(I); + clk_hw_unregister_composite(I2); ) ... } // Fix calls to mtk_clk_unregister_*() @@ identifier F =~ "^mtk_clk_unregister_"; expression I; expression E; @@ - F(I->hws[E]->clk); + F(I->hws[E]); Signed-off-by: Chen-Yu Tsai <wenst@chromium.org> Reviewed-by: Miles Chen <miles.chen@mediatek.com> Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com> Tested-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com> Tested-by: Miles Chen <miles.chen@mediatek.com> Link: https://lore.kernel.org/r/20220519071610.423372-5-wenst@chromium.org Signed-off-by: Stephen Boyd <sboyd@kernel.org>
466 lines
10 KiB
C
466 lines
10 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2014 MediaTek Inc.
|
|
* Author: James Liao <jamesjj.liao@mediatek.com>
|
|
*/
|
|
|
|
#include <linux/clk-provider.h>
|
|
#include <linux/container_of.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/err.h>
|
|
#include <linux/io.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include "clk-pll.h"
|
|
|
|
#define MHZ (1000 * 1000)
|
|
|
|
#define REG_CON0 0
|
|
#define REG_CON1 4
|
|
|
|
#define CON0_BASE_EN BIT(0)
|
|
#define CON0_PWR_ON BIT(0)
|
|
#define CON0_ISO_EN BIT(1)
|
|
#define PCW_CHG_MASK BIT(31)
|
|
|
|
#define AUDPLL_TUNER_EN BIT(31)
|
|
|
|
#define POSTDIV_MASK 0x7
|
|
|
|
/* default 7 bits integer, can be overridden with pcwibits. */
|
|
#define INTEGER_BITS 7
|
|
|
|
/*
|
|
* MediaTek PLLs are configured through their pcw value. The pcw value describes
|
|
* a divider in the PLL feedback loop which consists of 7 bits for the integer
|
|
* part and the remaining bits (if present) for the fractional part. Also they
|
|
* have a 3 bit power-of-two post divider.
|
|
*/
|
|
|
|
struct mtk_clk_pll {
|
|
struct clk_hw hw;
|
|
void __iomem *base_addr;
|
|
void __iomem *pd_addr;
|
|
void __iomem *pwr_addr;
|
|
void __iomem *tuner_addr;
|
|
void __iomem *tuner_en_addr;
|
|
void __iomem *pcw_addr;
|
|
void __iomem *pcw_chg_addr;
|
|
void __iomem *en_addr;
|
|
const struct mtk_pll_data *data;
|
|
};
|
|
|
|
static inline struct mtk_clk_pll *to_mtk_clk_pll(struct clk_hw *hw)
|
|
{
|
|
return container_of(hw, struct mtk_clk_pll, hw);
|
|
}
|
|
|
|
static int mtk_pll_is_prepared(struct clk_hw *hw)
|
|
{
|
|
struct mtk_clk_pll *pll = to_mtk_clk_pll(hw);
|
|
|
|
return (readl(pll->en_addr) & BIT(pll->data->pll_en_bit)) != 0;
|
|
}
|
|
|
|
static unsigned long __mtk_pll_recalc_rate(struct mtk_clk_pll *pll, u32 fin,
|
|
u32 pcw, int postdiv)
|
|
{
|
|
int pcwbits = pll->data->pcwbits;
|
|
int pcwfbits = 0;
|
|
int ibits;
|
|
u64 vco;
|
|
u8 c = 0;
|
|
|
|
/* The fractional part of the PLL divider. */
|
|
ibits = pll->data->pcwibits ? pll->data->pcwibits : INTEGER_BITS;
|
|
if (pcwbits > ibits)
|
|
pcwfbits = pcwbits - ibits;
|
|
|
|
vco = (u64)fin * pcw;
|
|
|
|
if (pcwfbits && (vco & GENMASK(pcwfbits - 1, 0)))
|
|
c = 1;
|
|
|
|
vco >>= pcwfbits;
|
|
|
|
if (c)
|
|
vco++;
|
|
|
|
return ((unsigned long)vco + postdiv - 1) / postdiv;
|
|
}
|
|
|
|
static void __mtk_pll_tuner_enable(struct mtk_clk_pll *pll)
|
|
{
|
|
u32 r;
|
|
|
|
if (pll->tuner_en_addr) {
|
|
r = readl(pll->tuner_en_addr) | BIT(pll->data->tuner_en_bit);
|
|
writel(r, pll->tuner_en_addr);
|
|
} else if (pll->tuner_addr) {
|
|
r = readl(pll->tuner_addr) | AUDPLL_TUNER_EN;
|
|
writel(r, pll->tuner_addr);
|
|
}
|
|
}
|
|
|
|
static void __mtk_pll_tuner_disable(struct mtk_clk_pll *pll)
|
|
{
|
|
u32 r;
|
|
|
|
if (pll->tuner_en_addr) {
|
|
r = readl(pll->tuner_en_addr) & ~BIT(pll->data->tuner_en_bit);
|
|
writel(r, pll->tuner_en_addr);
|
|
} else if (pll->tuner_addr) {
|
|
r = readl(pll->tuner_addr) & ~AUDPLL_TUNER_EN;
|
|
writel(r, pll->tuner_addr);
|
|
}
|
|
}
|
|
|
|
static void mtk_pll_set_rate_regs(struct mtk_clk_pll *pll, u32 pcw,
|
|
int postdiv)
|
|
{
|
|
u32 chg, val;
|
|
|
|
/* disable tuner */
|
|
__mtk_pll_tuner_disable(pll);
|
|
|
|
/* set postdiv */
|
|
val = readl(pll->pd_addr);
|
|
val &= ~(POSTDIV_MASK << pll->data->pd_shift);
|
|
val |= (ffs(postdiv) - 1) << pll->data->pd_shift;
|
|
|
|
/* postdiv and pcw need to set at the same time if on same register */
|
|
if (pll->pd_addr != pll->pcw_addr) {
|
|
writel(val, pll->pd_addr);
|
|
val = readl(pll->pcw_addr);
|
|
}
|
|
|
|
/* set pcw */
|
|
val &= ~GENMASK(pll->data->pcw_shift + pll->data->pcwbits - 1,
|
|
pll->data->pcw_shift);
|
|
val |= pcw << pll->data->pcw_shift;
|
|
writel(val, pll->pcw_addr);
|
|
chg = readl(pll->pcw_chg_addr) | PCW_CHG_MASK;
|
|
writel(chg, pll->pcw_chg_addr);
|
|
if (pll->tuner_addr)
|
|
writel(val + 1, pll->tuner_addr);
|
|
|
|
/* restore tuner_en */
|
|
__mtk_pll_tuner_enable(pll);
|
|
|
|
udelay(20);
|
|
}
|
|
|
|
/*
|
|
* mtk_pll_calc_values - calculate good values for a given input frequency.
|
|
* @pll: The pll
|
|
* @pcw: The pcw value (output)
|
|
* @postdiv: The post divider (output)
|
|
* @freq: The desired target frequency
|
|
* @fin: The input frequency
|
|
*
|
|
*/
|
|
static void mtk_pll_calc_values(struct mtk_clk_pll *pll, u32 *pcw, u32 *postdiv,
|
|
u32 freq, u32 fin)
|
|
{
|
|
unsigned long fmin = pll->data->fmin ? pll->data->fmin : (1000 * MHZ);
|
|
const struct mtk_pll_div_table *div_table = pll->data->div_table;
|
|
u64 _pcw;
|
|
int ibits;
|
|
u32 val;
|
|
|
|
if (freq > pll->data->fmax)
|
|
freq = pll->data->fmax;
|
|
|
|
if (div_table) {
|
|
if (freq > div_table[0].freq)
|
|
freq = div_table[0].freq;
|
|
|
|
for (val = 0; div_table[val + 1].freq != 0; val++) {
|
|
if (freq > div_table[val + 1].freq)
|
|
break;
|
|
}
|
|
*postdiv = 1 << val;
|
|
} else {
|
|
for (val = 0; val < 5; val++) {
|
|
*postdiv = 1 << val;
|
|
if ((u64)freq * *postdiv >= fmin)
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* _pcw = freq * postdiv / fin * 2^pcwfbits */
|
|
ibits = pll->data->pcwibits ? pll->data->pcwibits : INTEGER_BITS;
|
|
_pcw = ((u64)freq << val) << (pll->data->pcwbits - ibits);
|
|
do_div(_pcw, fin);
|
|
|
|
*pcw = (u32)_pcw;
|
|
}
|
|
|
|
static int mtk_pll_set_rate(struct clk_hw *hw, unsigned long rate,
|
|
unsigned long parent_rate)
|
|
{
|
|
struct mtk_clk_pll *pll = to_mtk_clk_pll(hw);
|
|
u32 pcw = 0;
|
|
u32 postdiv;
|
|
|
|
mtk_pll_calc_values(pll, &pcw, &postdiv, rate, parent_rate);
|
|
mtk_pll_set_rate_regs(pll, pcw, postdiv);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static unsigned long mtk_pll_recalc_rate(struct clk_hw *hw,
|
|
unsigned long parent_rate)
|
|
{
|
|
struct mtk_clk_pll *pll = to_mtk_clk_pll(hw);
|
|
u32 postdiv;
|
|
u32 pcw;
|
|
|
|
postdiv = (readl(pll->pd_addr) >> pll->data->pd_shift) & POSTDIV_MASK;
|
|
postdiv = 1 << postdiv;
|
|
|
|
pcw = readl(pll->pcw_addr) >> pll->data->pcw_shift;
|
|
pcw &= GENMASK(pll->data->pcwbits - 1, 0);
|
|
|
|
return __mtk_pll_recalc_rate(pll, parent_rate, pcw, postdiv);
|
|
}
|
|
|
|
static long mtk_pll_round_rate(struct clk_hw *hw, unsigned long rate,
|
|
unsigned long *prate)
|
|
{
|
|
struct mtk_clk_pll *pll = to_mtk_clk_pll(hw);
|
|
u32 pcw = 0;
|
|
int postdiv;
|
|
|
|
mtk_pll_calc_values(pll, &pcw, &postdiv, rate, *prate);
|
|
|
|
return __mtk_pll_recalc_rate(pll, *prate, pcw, postdiv);
|
|
}
|
|
|
|
static int mtk_pll_prepare(struct clk_hw *hw)
|
|
{
|
|
struct mtk_clk_pll *pll = to_mtk_clk_pll(hw);
|
|
u32 r;
|
|
|
|
r = readl(pll->pwr_addr) | CON0_PWR_ON;
|
|
writel(r, pll->pwr_addr);
|
|
udelay(1);
|
|
|
|
r = readl(pll->pwr_addr) & ~CON0_ISO_EN;
|
|
writel(r, pll->pwr_addr);
|
|
udelay(1);
|
|
|
|
r = readl(pll->en_addr) | BIT(pll->data->pll_en_bit);
|
|
writel(r, pll->en_addr);
|
|
|
|
if (pll->data->en_mask) {
|
|
r = readl(pll->base_addr + REG_CON0) | pll->data->en_mask;
|
|
writel(r, pll->base_addr + REG_CON0);
|
|
}
|
|
|
|
__mtk_pll_tuner_enable(pll);
|
|
|
|
udelay(20);
|
|
|
|
if (pll->data->flags & HAVE_RST_BAR) {
|
|
r = readl(pll->base_addr + REG_CON0);
|
|
r |= pll->data->rst_bar_mask;
|
|
writel(r, pll->base_addr + REG_CON0);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void mtk_pll_unprepare(struct clk_hw *hw)
|
|
{
|
|
struct mtk_clk_pll *pll = to_mtk_clk_pll(hw);
|
|
u32 r;
|
|
|
|
if (pll->data->flags & HAVE_RST_BAR) {
|
|
r = readl(pll->base_addr + REG_CON0);
|
|
r &= ~pll->data->rst_bar_mask;
|
|
writel(r, pll->base_addr + REG_CON0);
|
|
}
|
|
|
|
__mtk_pll_tuner_disable(pll);
|
|
|
|
if (pll->data->en_mask) {
|
|
r = readl(pll->base_addr + REG_CON0) & ~pll->data->en_mask;
|
|
writel(r, pll->base_addr + REG_CON0);
|
|
}
|
|
|
|
r = readl(pll->en_addr) & ~BIT(pll->data->pll_en_bit);
|
|
writel(r, pll->en_addr);
|
|
|
|
r = readl(pll->pwr_addr) | CON0_ISO_EN;
|
|
writel(r, pll->pwr_addr);
|
|
|
|
r = readl(pll->pwr_addr) & ~CON0_PWR_ON;
|
|
writel(r, pll->pwr_addr);
|
|
}
|
|
|
|
static const struct clk_ops mtk_pll_ops = {
|
|
.is_prepared = mtk_pll_is_prepared,
|
|
.prepare = mtk_pll_prepare,
|
|
.unprepare = mtk_pll_unprepare,
|
|
.recalc_rate = mtk_pll_recalc_rate,
|
|
.round_rate = mtk_pll_round_rate,
|
|
.set_rate = mtk_pll_set_rate,
|
|
};
|
|
|
|
static struct clk_hw *mtk_clk_register_pll(const struct mtk_pll_data *data,
|
|
void __iomem *base)
|
|
{
|
|
struct mtk_clk_pll *pll;
|
|
struct clk_init_data init = {};
|
|
int ret;
|
|
const char *parent_name = "clk26m";
|
|
|
|
pll = kzalloc(sizeof(*pll), GFP_KERNEL);
|
|
if (!pll)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
pll->base_addr = base + data->reg;
|
|
pll->pwr_addr = base + data->pwr_reg;
|
|
pll->pd_addr = base + data->pd_reg;
|
|
pll->pcw_addr = base + data->pcw_reg;
|
|
if (data->pcw_chg_reg)
|
|
pll->pcw_chg_addr = base + data->pcw_chg_reg;
|
|
else
|
|
pll->pcw_chg_addr = pll->base_addr + REG_CON1;
|
|
if (data->tuner_reg)
|
|
pll->tuner_addr = base + data->tuner_reg;
|
|
if (data->tuner_en_reg || data->tuner_en_bit)
|
|
pll->tuner_en_addr = base + data->tuner_en_reg;
|
|
if (data->en_reg)
|
|
pll->en_addr = base + data->en_reg;
|
|
else
|
|
pll->en_addr = pll->base_addr + REG_CON0;
|
|
pll->hw.init = &init;
|
|
pll->data = data;
|
|
|
|
init.name = data->name;
|
|
init.flags = (data->flags & PLL_AO) ? CLK_IS_CRITICAL : 0;
|
|
init.ops = &mtk_pll_ops;
|
|
if (data->parent_name)
|
|
init.parent_names = &data->parent_name;
|
|
else
|
|
init.parent_names = &parent_name;
|
|
init.num_parents = 1;
|
|
|
|
ret = clk_hw_register(NULL, &pll->hw);
|
|
|
|
if (ret) {
|
|
kfree(pll);
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
return &pll->hw;
|
|
}
|
|
|
|
static void mtk_clk_unregister_pll(struct clk_hw *hw)
|
|
{
|
|
struct mtk_clk_pll *pll;
|
|
|
|
if (!hw)
|
|
return;
|
|
|
|
pll = to_mtk_clk_pll(hw);
|
|
|
|
clk_hw_unregister(hw);
|
|
kfree(pll);
|
|
}
|
|
|
|
int mtk_clk_register_plls(struct device_node *node,
|
|
const struct mtk_pll_data *plls, int num_plls,
|
|
struct clk_hw_onecell_data *clk_data)
|
|
{
|
|
void __iomem *base;
|
|
int i;
|
|
struct clk_hw *hw;
|
|
|
|
base = of_iomap(node, 0);
|
|
if (!base) {
|
|
pr_err("%s(): ioremap failed\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (i = 0; i < num_plls; i++) {
|
|
const struct mtk_pll_data *pll = &plls[i];
|
|
|
|
if (!IS_ERR_OR_NULL(clk_data->hws[pll->id])) {
|
|
pr_warn("%pOF: Trying to register duplicate clock ID: %d\n",
|
|
node, pll->id);
|
|
continue;
|
|
}
|
|
|
|
hw = mtk_clk_register_pll(pll, base);
|
|
|
|
if (IS_ERR(hw)) {
|
|
pr_err("Failed to register clk %s: %pe\n", pll->name,
|
|
hw);
|
|
goto err;
|
|
}
|
|
|
|
clk_data->hws[pll->id] = hw;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err:
|
|
while (--i >= 0) {
|
|
const struct mtk_pll_data *pll = &plls[i];
|
|
|
|
mtk_clk_unregister_pll(clk_data->hws[pll->id]);
|
|
clk_data->hws[pll->id] = ERR_PTR(-ENOENT);
|
|
}
|
|
|
|
iounmap(base);
|
|
|
|
return PTR_ERR(hw);
|
|
}
|
|
EXPORT_SYMBOL_GPL(mtk_clk_register_plls);
|
|
|
|
static __iomem void *mtk_clk_pll_get_base(struct clk_hw *hw,
|
|
const struct mtk_pll_data *data)
|
|
{
|
|
struct mtk_clk_pll *pll = to_mtk_clk_pll(hw);
|
|
|
|
return pll->base_addr - data->reg;
|
|
}
|
|
|
|
void mtk_clk_unregister_plls(const struct mtk_pll_data *plls, int num_plls,
|
|
struct clk_hw_onecell_data *clk_data)
|
|
{
|
|
__iomem void *base = NULL;
|
|
int i;
|
|
|
|
if (!clk_data)
|
|
return;
|
|
|
|
for (i = num_plls; i > 0; i--) {
|
|
const struct mtk_pll_data *pll = &plls[i - 1];
|
|
|
|
if (IS_ERR_OR_NULL(clk_data->hws[pll->id]))
|
|
continue;
|
|
|
|
/*
|
|
* This is quite ugly but unfortunately the clks don't have
|
|
* any device tied to them, so there's no place to store the
|
|
* pointer to the I/O region base address. We have to fetch
|
|
* it from one of the registered clks.
|
|
*/
|
|
base = mtk_clk_pll_get_base(clk_data->hws[pll->id], pll);
|
|
|
|
mtk_clk_unregister_pll(clk_data->hws[pll->id]);
|
|
clk_data->hws[pll->id] = ERR_PTR(-ENOENT);
|
|
}
|
|
|
|
iounmap(base);
|
|
}
|
|
EXPORT_SYMBOL_GPL(mtk_clk_unregister_plls);
|
|
|
|
MODULE_LICENSE("GPL");
|