1
0
Fork 0
alistair23-linux/drivers/clk/imx/clk-frac-pll.c

236 lines
5.4 KiB
C
Raw Permalink Normal View History

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2018 NXP.
*
* This driver supports the fractional plls found in the imx8m SOCs
*
* Documentation for this fractional pll can be found at:
* https://www.nxp.com/docs/en/reference-manual/IMX8MDQLQRM.pdf#page=834
*/
#include <linux/clk-provider.h>
#include <linux/err.h>
clk: Remove io.h from clk-provider.h Now that we've gotten rid of clk_readl() we can remove io.h from the clk-provider header and push out the io.h include to any code that isn't already including the io.h header but using things like readl/writel, etc. Found with this grep: git grep -l clk-provider.h | grep '.c$' | xargs git grep -L 'linux/io.h' | \ xargs git grep -l \ -e '\<__iowrite32_copy\>' --or \ -e '\<__ioread32_copy\>' --or \ -e '\<__iowrite64_copy\>' --or \ -e '\<ioremap_page_range\>' --or \ -e '\<ioremap_huge_init\>' --or \ -e '\<arch_ioremap_pud_supported\>' --or \ -e '\<arch_ioremap_pmd_supported\>' --or \ -e '\<devm_ioport_map\>' --or \ -e '\<devm_ioport_unmap\>' --or \ -e '\<IOMEM_ERR_PTR\>' --or \ -e '\<devm_ioremap\>' --or \ -e '\<devm_ioremap_nocache\>' --or \ -e '\<devm_ioremap_wc\>' --or \ -e '\<devm_iounmap\>' --or \ -e '\<devm_ioremap_release\>' --or \ -e '\<devm_memremap\>' --or \ -e '\<devm_memunmap\>' --or \ -e '\<__devm_memremap_pages\>' --or \ -e '\<pci_remap_cfgspace\>' --or \ -e '\<arch_has_dev_port\>' --or \ -e '\<arch_phys_wc_add\>' --or \ -e '\<arch_phys_wc_del\>' --or \ -e '\<memremap\>' --or \ -e '\<memunmap\>' --or \ -e '\<arch_io_reserve_memtype_wc\>' --or \ -e '\<arch_io_free_memtype_wc\>' --or \ -e '\<__io_aw\>' --or \ -e '\<__io_pbw\>' --or \ -e '\<__io_paw\>' --or \ -e '\<__io_pbr\>' --or \ -e '\<__io_par\>' --or \ -e '\<__raw_readb\>' --or \ -e '\<__raw_readw\>' --or \ -e '\<__raw_readl\>' --or \ -e '\<__raw_readq\>' --or \ -e '\<__raw_writeb\>' --or \ -e '\<__raw_writew\>' --or \ -e '\<__raw_writel\>' --or \ -e '\<__raw_writeq\>' --or \ -e '\<readb\>' --or \ -e '\<readw\>' --or \ -e '\<readl\>' --or \ -e '\<readq\>' --or \ -e '\<writeb\>' --or \ -e '\<writew\>' --or \ -e '\<writel\>' --or \ -e '\<writeq\>' --or \ -e '\<readb_relaxed\>' --or \ -e '\<readw_relaxed\>' --or \ -e '\<readl_relaxed\>' --or \ -e '\<readq_relaxed\>' --or \ -e '\<writeb_relaxed\>' --or \ -e '\<writew_relaxed\>' --or \ -e '\<writel_relaxed\>' --or \ -e '\<writeq_relaxed\>' --or \ -e '\<readsb\>' --or \ -e '\<readsw\>' --or \ -e '\<readsl\>' --or \ -e '\<readsq\>' --or \ -e '\<writesb\>' --or \ -e '\<writesw\>' --or \ -e '\<writesl\>' --or \ -e '\<writesq\>' --or \ -e '\<inb\>' --or \ -e '\<inw\>' --or \ -e '\<inl\>' --or \ -e '\<outb\>' --or \ -e '\<outw\>' --or \ -e '\<outl\>' --or \ -e '\<inb_p\>' --or \ -e '\<inw_p\>' --or \ -e '\<inl_p\>' --or \ -e '\<outb_p\>' --or \ -e '\<outw_p\>' --or \ -e '\<outl_p\>' --or \ -e '\<insb\>' --or \ -e '\<insw\>' --or \ -e '\<insl\>' --or \ -e '\<outsb\>' --or \ -e '\<outsw\>' --or \ -e '\<outsl\>' --or \ -e '\<insb_p\>' --or \ -e '\<insw_p\>' --or \ -e '\<insl_p\>' --or \ -e '\<outsb_p\>' --or \ -e '\<outsw_p\>' --or \ -e '\<outsl_p\>' --or \ -e '\<ioread8\>' --or \ -e '\<ioread16\>' --or \ -e '\<ioread32\>' --or \ -e '\<ioread64\>' --or \ -e '\<iowrite8\>' --or \ -e '\<iowrite16\>' --or \ -e '\<iowrite32\>' --or \ -e '\<iowrite64\>' --or \ -e '\<ioread16be\>' --or \ -e '\<ioread32be\>' --or \ -e '\<ioread64be\>' --or \ -e '\<iowrite16be\>' --or \ -e '\<iowrite32be\>' --or \ -e '\<iowrite64be\>' --or \ -e '\<ioread8_rep\>' --or \ -e '\<ioread16_rep\>' --or \ -e '\<ioread32_rep\>' --or \ -e '\<ioread64_rep\>' --or \ -e '\<iowrite8_rep\>' --or \ -e '\<iowrite16_rep\>' --or \ -e '\<iowrite32_rep\>' --or \ -e '\<iowrite64_rep\>' --or \ -e '\<__io_virt\>' --or \ -e '\<pci_iounmap\>' --or \ -e '\<virt_to_phys\>' --or \ -e '\<phys_to_virt\>' --or \ -e '\<ioremap_uc\>' --or \ -e '\<ioremap\>' --or \ -e '\<__ioremap\>' --or \ -e '\<iounmap\>' --or \ -e '\<ioremap\>' --or \ -e '\<ioremap_nocache\>' --or \ -e '\<ioremap_uc\>' --or \ -e '\<ioremap_wc\>' --or \ -e '\<ioremap_wc\>' --or \ -e '\<ioremap_wt\>' --or \ -e '\<ioport_map\>' --or \ -e '\<ioport_unmap\>' --or \ -e '\<ioport_map\>' --or \ -e '\<ioport_unmap\>' --or \ -e '\<xlate_dev_kmem_ptr\>' --or \ -e '\<xlate_dev_mem_ptr\>' --or \ -e '\<unxlate_dev_mem_ptr\>' --or \ -e '\<virt_to_bus\>' --or \ -e '\<bus_to_virt\>' --or \ -e '\<memset_io\>' --or \ -e '\<memcpy_fromio\>' --or \ -e '\<memcpy_toio\>' I also reordered a couple includes when they weren't alphabetical and removed clk.h from kona, replacing it with clk-provider.h because that driver doesn't use clk consumer APIs. Acked-by: Geert Uytterhoeven <geert+renesas@glider.be> Cc: Chen-Yu Tsai <wens@csie.org> Acked-by: Maxime Ripard <maxime.ripard@bootlin.com> Acked-by: Tero Kristo <t-kristo@ti.com> Acked-by: Sekhar Nori <nsekhar@ti.com> Cc: Krzysztof Kozlowski <krzk@kernel.org> Acked-by: Mark Brown <broonie@kernel.org> Cc: Chris Zankel <chris@zankel.net> Acked-by: Max Filippov <jcmvbkbc@gmail.com> Acked-by: John Crispin <john@phrozen.org> Acked-by: Heiko Stuebner <heiko@sntech.de> Signed-off-by: Stephen Boyd <sboyd@kernel.org>
2019-04-18 16:20:22 -06:00
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/slab.h>
#include <linux/bitfield.h>
#include "clk.h"
#define PLL_CFG0 0x0
#define PLL_CFG1 0x4
#define PLL_LOCK_STATUS BIT(31)
#define PLL_PD_MASK BIT(19)
#define PLL_BYPASS_MASK BIT(14)
#define PLL_NEWDIV_VAL BIT(12)
#define PLL_NEWDIV_ACK BIT(11)
#define PLL_FRAC_DIV_MASK GENMASK(30, 7)
#define PLL_INT_DIV_MASK GENMASK(6, 0)
#define PLL_OUTPUT_DIV_MASK GENMASK(4, 0)
#define PLL_FRAC_DENOM 0x1000000
#define PLL_FRAC_LOCK_TIMEOUT 10000
#define PLL_FRAC_ACK_TIMEOUT 500000
struct clk_frac_pll {
struct clk_hw hw;
void __iomem *base;
};
#define to_clk_frac_pll(_hw) container_of(_hw, struct clk_frac_pll, hw)
static int clk_wait_lock(struct clk_frac_pll *pll)
{
u32 val;
return readl_poll_timeout(pll->base, val, val & PLL_LOCK_STATUS, 0,
PLL_FRAC_LOCK_TIMEOUT);
}
static int clk_wait_ack(struct clk_frac_pll *pll)
{
u32 val;
/* return directly if the pll is in powerdown or in bypass */
if (readl_relaxed(pll->base) & (PLL_PD_MASK | PLL_BYPASS_MASK))
return 0;
/* Wait for the pll's divfi and divff to be reloaded */
return readl_poll_timeout(pll->base, val, val & PLL_NEWDIV_ACK, 0,
PLL_FRAC_ACK_TIMEOUT);
}
static int clk_pll_prepare(struct clk_hw *hw)
{
struct clk_frac_pll *pll = to_clk_frac_pll(hw);
u32 val;
val = readl_relaxed(pll->base + PLL_CFG0);
val &= ~PLL_PD_MASK;
writel_relaxed(val, pll->base + PLL_CFG0);
return clk_wait_lock(pll);
}
static void clk_pll_unprepare(struct clk_hw *hw)
{
struct clk_frac_pll *pll = to_clk_frac_pll(hw);
u32 val;
val = readl_relaxed(pll->base + PLL_CFG0);
val |= PLL_PD_MASK;
writel_relaxed(val, pll->base + PLL_CFG0);
}
static int clk_pll_is_prepared(struct clk_hw *hw)
{
struct clk_frac_pll *pll = to_clk_frac_pll(hw);
u32 val;
val = readl_relaxed(pll->base + PLL_CFG0);
return (val & PLL_PD_MASK) ? 0 : 1;
}
static unsigned long clk_pll_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct clk_frac_pll *pll = to_clk_frac_pll(hw);
u32 val, divff, divfi, divq;
u64 temp64 = parent_rate;
u64 rate;
val = readl_relaxed(pll->base + PLL_CFG0);
divq = (FIELD_GET(PLL_OUTPUT_DIV_MASK, val) + 1) * 2;
val = readl_relaxed(pll->base + PLL_CFG1);
divff = FIELD_GET(PLL_FRAC_DIV_MASK, val);
divfi = FIELD_GET(PLL_INT_DIV_MASK, val);
temp64 *= 8;
temp64 *= divff;
do_div(temp64, PLL_FRAC_DENOM);
do_div(temp64, divq);
rate = parent_rate * 8 * (divfi + 1);
do_div(rate, divq);
rate += temp64;
return rate;
}
static long clk_pll_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *prate)
{
u64 parent_rate = *prate;
u32 divff, divfi;
u64 temp64;
parent_rate *= 8;
rate *= 2;
temp64 = rate;
do_div(temp64, parent_rate);
divfi = temp64;
temp64 = rate - divfi * parent_rate;
temp64 *= PLL_FRAC_DENOM;
do_div(temp64, parent_rate);
divff = temp64;
temp64 = parent_rate;
temp64 *= divff;
do_div(temp64, PLL_FRAC_DENOM);
rate = parent_rate * divfi + temp64;
return rate / 2;
}
/*
* To simplify the clock calculation, we can keep the 'PLL_OUTPUT_VAL' at zero
* (means the PLL output will be divided by 2). So the PLL output can use
* the below formula:
* pllout = parent_rate * 8 / 2 * DIVF_VAL;
* where DIVF_VAL = 1 + DIVFI + DIVFF / 2^24.
*/
static int clk_pll_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct clk_frac_pll *pll = to_clk_frac_pll(hw);
u32 val, divfi, divff;
u64 temp64;
int ret;
parent_rate *= 8;
rate *= 2;
divfi = rate / parent_rate;
temp64 = parent_rate * divfi;
temp64 = rate - temp64;
temp64 *= PLL_FRAC_DENOM;
do_div(temp64, parent_rate);
divff = temp64;
val = readl_relaxed(pll->base + PLL_CFG1);
val &= ~(PLL_FRAC_DIV_MASK | PLL_INT_DIV_MASK);
val |= (divff << 7) | (divfi - 1);
writel_relaxed(val, pll->base + PLL_CFG1);
val = readl_relaxed(pll->base + PLL_CFG0);
val &= ~0x1f;
writel_relaxed(val, pll->base + PLL_CFG0);
/* Set the NEV_DIV_VAL to reload the DIVFI and DIVFF */
val = readl_relaxed(pll->base + PLL_CFG0);
val |= PLL_NEWDIV_VAL;
writel_relaxed(val, pll->base + PLL_CFG0);
ret = clk_wait_ack(pll);
/* clear the NEV_DIV_VAL */
val = readl_relaxed(pll->base + PLL_CFG0);
val &= ~PLL_NEWDIV_VAL;
writel_relaxed(val, pll->base + PLL_CFG0);
return ret;
}
static const struct clk_ops clk_frac_pll_ops = {
.prepare = clk_pll_prepare,
.unprepare = clk_pll_unprepare,
.is_prepared = clk_pll_is_prepared,
.recalc_rate = clk_pll_recalc_rate,
.round_rate = clk_pll_round_rate,
.set_rate = clk_pll_set_rate,
};
struct clk *imx_clk_frac_pll(const char *name, const char *parent_name,
void __iomem *base)
{
struct clk_init_data init;
struct clk_frac_pll *pll;
struct clk_hw *hw;
int ret;
pll = kzalloc(sizeof(*pll), GFP_KERNEL);
if (!pll)
return ERR_PTR(-ENOMEM);
init.name = name;
init.ops = &clk_frac_pll_ops;
init.flags = 0;
init.parent_names = &parent_name;
init.num_parents = 1;
pll->base = base;
pll->hw.init = &init;
hw = &pll->hw;
ret = clk_hw_register(NULL, hw);
if (ret) {
kfree(pll);
return ERR_PTR(ret);
}
return hw->clk;
}
EXPORT_SYMBOL_GPL(imx_clk_frac_pll);