diff options
| author | Steven Rostedt <rostedt@goodmis.org> | 2026-09-02 09:55:01 -0400 |
|---|---|---|
| committer | Steven Rostedt <rostedt@goodmis.org> | 2026-09-02 11:08:21 -0400 |
| commit | 4617721c502b2ddaa4e324e86da4997edf738fa5 (patch) | |
| tree | 350aa83b47da0b574a814795392b20b0bed83cbc /drivers/clk/mmp | |
| download | linux-stable-4617721c502b2ddaa4e324e86da4997edf738fa5.tar.gz linux-stable-4617721c502b2ddaa4e324e86da4997edf738fa5.zip | |
ftrace: Synchronize the initialization of ftrace_opsgrafted
There's some internal state that ftrace_ops needs to have set, but since
it can be declared outside of the ftrace.c code, it calls
ftrace_ops_init() on the ops in every global function. The issue is that
if two tasks call it on the same ops at the same time it is possible to
have the initialization of one corrupt the initialization of the other
call.
Create a ops_mutex to use to synchronize every initialization of the
ftrace_ops. The mutex is taken within checking the ftrace_ops flag that
states it was initializied but the flag is checked again after the mutex
has been taken. Checking first outside the mutex allows it to shortcut
having to take the mutex. But then the check needs to be done again after
the mute is taken in case of races.
Cc: stable@vger.kernel.org
Link: https://patch.msgid.link/20260902095501.6b59af20@gandalf.local.home
Fixes: f04f24fb7e48d ("ftrace, kprobes: Fix a deadlock on ftrace_regex_lock")
Reported-by: sashiko-bot@kernel.org
Close: https://lore.kernel.org/all/20260829025528.49A831F000E9@smtp.kernel.org/
Signed-off-by: Steven Rostedt <rostedt@goodmis.org>
Diffstat (limited to 'drivers/clk/mmp')
| -rw-r--r-- | drivers/clk/mmp/Kconfig | 10 | ||||
| -rw-r--r-- | drivers/clk/mmp/Makefile | 17 | ||||
| -rw-r--r-- | drivers/clk/mmp/clk-apbc.c | 148 | ||||
| -rw-r--r-- | drivers/clk/mmp/clk-apmu.c | 93 | ||||
| -rw-r--r-- | drivers/clk/mmp/clk-audio.c | 447 | ||||
| -rw-r--r-- | drivers/clk/mmp/clk-frac.c | 206 | ||||
| -rw-r--r-- | drivers/clk/mmp/clk-gate.c | 127 | ||||
| -rw-r--r-- | drivers/clk/mmp/clk-mix.c | 504 | ||||
| -rw-r--r-- | drivers/clk/mmp/clk-of-mmp2.c | 569 | ||||
| -rw-r--r-- | drivers/clk/mmp/clk-of-pxa168.c | 340 | ||||
| -rw-r--r-- | drivers/clk/mmp/clk-of-pxa1928.c | 267 | ||||
| -rw-r--r-- | drivers/clk/mmp/clk-of-pxa910.c | 323 | ||||
| -rw-r--r-- | drivers/clk/mmp/clk-pll.c | 170 | ||||
| -rw-r--r-- | drivers/clk/mmp/clk-pxa1908-apbc.c | 160 | ||||
| -rw-r--r-- | drivers/clk/mmp/clk-pxa1908-apbcp.c | 107 | ||||
| -rw-r--r-- | drivers/clk/mmp/clk-pxa1908-apmu.c | 128 | ||||
| -rw-r--r-- | drivers/clk/mmp/clk-pxa1908-mpmu.c | 112 | ||||
| -rw-r--r-- | drivers/clk/mmp/clk.c | 192 | ||||
| -rw-r--r-- | drivers/clk/mmp/clk.h | 261 | ||||
| -rw-r--r-- | drivers/clk/mmp/pwr-island.c | 115 | ||||
| -rw-r--r-- | drivers/clk/mmp/reset.c | 100 | ||||
| -rw-r--r-- | drivers/clk/mmp/reset.h | 32 |
22 files changed, 4428 insertions, 0 deletions
diff --git a/drivers/clk/mmp/Kconfig b/drivers/clk/mmp/Kconfig new file mode 100644 index 000000000..b0d2fea3c --- /dev/null +++ b/drivers/clk/mmp/Kconfig @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config COMMON_CLK_PXA1908 + bool "Clock driver for Marvell PXA1908" + depends on ARCH_MMP || COMPILE_TEST + depends on OF + default y if ARCH_MMP && ARM64 + select AUXILIARY_BUS + help + This driver supports the Marvell PXA1908 SoC clocks. diff --git a/drivers/clk/mmp/Makefile b/drivers/clk/mmp/Makefile new file mode 100644 index 000000000..0a94f2f08 --- /dev/null +++ b/drivers/clk/mmp/Makefile @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for mmp specific clk +# + +obj-y += clk-apbc.o clk-apmu.o clk-frac.o clk-mix.o clk-gate.o clk.o + +obj-$(CONFIG_RESET_CONTROLLER) += reset.o + +obj-$(CONFIG_MACH_MMP_DT) += clk-of-pxa168.o clk-of-pxa910.o +obj-$(CONFIG_COMMON_CLK_MMP2) += clk-of-mmp2.o clk-pll.o pwr-island.o +obj-$(CONFIG_COMMON_CLK_MMP2_AUDIO) += clk-audio.o + +obj-$(CONFIG_COMMON_CLK_PXA1908) += clk-pxa1908-apbc.o clk-pxa1908-apbcp.o \ + clk-pxa1908-mpmu.o clk-pxa1908-apmu.o + +obj-$(CONFIG_ARCH_MMP) += clk-of-pxa1928.o diff --git a/drivers/clk/mmp/clk-apbc.c b/drivers/clk/mmp/clk-apbc.c new file mode 100644 index 000000000..84532ce8f --- /dev/null +++ b/drivers/clk/mmp/clk-apbc.c @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * mmp APB clock operation source file + * + * Copyright (C) 2012 Marvell + * Chao Xie <xiechao.mail@gmail.com> + */ + +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/slab.h> + +#include "clk.h" + +/* Common APB clock register bit definitions */ +#define APBC_APBCLK (1 << 0) /* APB Bus Clock Enable */ +#define APBC_FNCLK (1 << 1) /* Functional Clock Enable */ +#define APBC_RST (1 << 2) /* Reset Generation */ +#define APBC_POWER (1 << 7) /* Reset Generation */ + +#define to_clk_apbc(hw) container_of(hw, struct clk_apbc, hw) +struct clk_apbc { + struct clk_hw hw; + void __iomem *base; + unsigned int delay; + unsigned int flags; + spinlock_t *lock; +}; + +static int clk_apbc_prepare(struct clk_hw *hw) +{ + struct clk_apbc *apbc = to_clk_apbc(hw); + unsigned int data; + unsigned long flags = 0; + + /* + * It may share same register as MUX clock, + * and it will impact FNCLK enable. Spinlock is needed + */ + if (apbc->lock) + spin_lock_irqsave(apbc->lock, flags); + + data = readl_relaxed(apbc->base); + if (apbc->flags & APBC_POWER_CTRL) + data |= APBC_POWER; + data |= APBC_FNCLK; + writel_relaxed(data, apbc->base); + + if (apbc->lock) + spin_unlock_irqrestore(apbc->lock, flags); + + udelay(apbc->delay); + + if (apbc->lock) + spin_lock_irqsave(apbc->lock, flags); + + data = readl_relaxed(apbc->base); + data |= APBC_APBCLK; + writel_relaxed(data, apbc->base); + + if (apbc->lock) + spin_unlock_irqrestore(apbc->lock, flags); + + udelay(apbc->delay); + + if (!(apbc->flags & APBC_NO_BUS_CTRL)) { + if (apbc->lock) + spin_lock_irqsave(apbc->lock, flags); + + data = readl_relaxed(apbc->base); + data &= ~APBC_RST; + writel_relaxed(data, apbc->base); + + if (apbc->lock) + spin_unlock_irqrestore(apbc->lock, flags); + } + + return 0; +} + +static void clk_apbc_unprepare(struct clk_hw *hw) +{ + struct clk_apbc *apbc = to_clk_apbc(hw); + unsigned long data; + unsigned long flags = 0; + + if (apbc->lock) + spin_lock_irqsave(apbc->lock, flags); + + data = readl_relaxed(apbc->base); + if (apbc->flags & APBC_POWER_CTRL) + data &= ~APBC_POWER; + data &= ~APBC_FNCLK; + writel_relaxed(data, apbc->base); + + if (apbc->lock) + spin_unlock_irqrestore(apbc->lock, flags); + + udelay(10); + + if (apbc->lock) + spin_lock_irqsave(apbc->lock, flags); + + data = readl_relaxed(apbc->base); + data &= ~APBC_APBCLK; + writel_relaxed(data, apbc->base); + + if (apbc->lock) + spin_unlock_irqrestore(apbc->lock, flags); +} + +static const struct clk_ops clk_apbc_ops = { + .prepare = clk_apbc_prepare, + .unprepare = clk_apbc_unprepare, +}; + +struct clk *mmp_clk_register_apbc(const char *name, const char *parent_name, + void __iomem *base, unsigned int delay, + unsigned int apbc_flags, spinlock_t *lock) +{ + struct clk_apbc *apbc; + struct clk *clk; + struct clk_init_data init; + + apbc = kzalloc_obj(*apbc); + if (!apbc) + return NULL; + + init.name = name; + init.ops = &clk_apbc_ops; + init.flags = CLK_SET_RATE_PARENT; + init.parent_names = (parent_name ? &parent_name : NULL); + init.num_parents = (parent_name ? 1 : 0); + + apbc->base = base; + apbc->delay = delay; + apbc->flags = apbc_flags; + apbc->lock = lock; + apbc->hw.init = &init; + + clk = clk_register(NULL, &apbc->hw); + if (IS_ERR(clk)) + kfree(apbc); + + return clk; +} diff --git a/drivers/clk/mmp/clk-apmu.c b/drivers/clk/mmp/clk-apmu.c new file mode 100644 index 000000000..30bbac1de --- /dev/null +++ b/drivers/clk/mmp/clk-apmu.c @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * mmp AXI peripharal clock operation source file + * + * Copyright (C) 2012 Marvell + * Chao Xie <xiechao.mail@gmail.com> + */ + +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/slab.h> + +#include "clk.h" + +#define to_clk_apmu(clk) (container_of(clk, struct clk_apmu, clk)) +struct clk_apmu { + struct clk_hw hw; + void __iomem *base; + u32 rst_mask; + u32 enable_mask; + spinlock_t *lock; +}; + +static int clk_apmu_enable(struct clk_hw *hw) +{ + struct clk_apmu *apmu = to_clk_apmu(hw); + unsigned long data; + unsigned long flags = 0; + + if (apmu->lock) + spin_lock_irqsave(apmu->lock, flags); + + data = readl_relaxed(apmu->base) | apmu->enable_mask; + writel_relaxed(data, apmu->base); + + if (apmu->lock) + spin_unlock_irqrestore(apmu->lock, flags); + + return 0; +} + +static void clk_apmu_disable(struct clk_hw *hw) +{ + struct clk_apmu *apmu = to_clk_apmu(hw); + unsigned long data; + unsigned long flags = 0; + + if (apmu->lock) + spin_lock_irqsave(apmu->lock, flags); + + data = readl_relaxed(apmu->base) & ~apmu->enable_mask; + writel_relaxed(data, apmu->base); + + if (apmu->lock) + spin_unlock_irqrestore(apmu->lock, flags); +} + +static const struct clk_ops clk_apmu_ops = { + .enable = clk_apmu_enable, + .disable = clk_apmu_disable, +}; + +struct clk *mmp_clk_register_apmu(const char *name, const char *parent_name, + void __iomem *base, u32 enable_mask, spinlock_t *lock) +{ + struct clk_apmu *apmu; + struct clk *clk; + struct clk_init_data init; + + apmu = kzalloc_obj(*apmu); + if (!apmu) + return NULL; + + init.name = name; + init.ops = &clk_apmu_ops; + init.flags = CLK_SET_RATE_PARENT; + init.parent_names = (parent_name ? &parent_name : NULL); + init.num_parents = (parent_name ? 1 : 0); + + apmu->base = base; + apmu->enable_mask = enable_mask; + apmu->lock = lock; + apmu->hw.init = &init; + + clk = clk_register(NULL, &apmu->hw); + + if (IS_ERR(clk)) + kfree(apmu); + + return clk; +} diff --git a/drivers/clk/mmp/clk-audio.c b/drivers/clk/mmp/clk-audio.c new file mode 100644 index 000000000..ed27fc796 --- /dev/null +++ b/drivers/clk/mmp/clk-audio.c @@ -0,0 +1,447 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MMP Audio Clock Controller driver + * + * Copyright (C) 2020 Lubomir Rintel <lkundrak@v3.sk> + */ + +#include <linux/clk-provider.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pm_clock.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> +#include <dt-bindings/clock/marvell,mmp2-audio.h> + +/* Audio Controller Registers */ +#define SSPA_AUD_CTRL 0x04 +#define SSPA_AUD_PLL_CTRL0 0x08 +#define SSPA_AUD_PLL_CTRL1 0x0c + +/* SSPA Audio Control Register */ +#define SSPA_AUD_CTRL_SYSCLK_SHIFT 0 +#define SSPA_AUD_CTRL_SYSCLK_DIV_SHIFT 1 +#define SSPA_AUD_CTRL_SSPA0_MUX_SHIFT 7 +#define SSPA_AUD_CTRL_SSPA0_SHIFT 8 +#define SSPA_AUD_CTRL_SSPA0_DIV_SHIFT 9 +#define SSPA_AUD_CTRL_SSPA1_SHIFT 16 +#define SSPA_AUD_CTRL_SSPA1_DIV_SHIFT 17 +#define SSPA_AUD_CTRL_SSPA1_MUX_SHIFT 23 +#define SSPA_AUD_CTRL_DIV_MASK 0x7e + +/* SSPA Audio PLL Control 0 Register */ +#define SSPA_AUD_PLL_CTRL0_DIV_OCLK_MODULO_MASK (0x7 << 28) +#define SSPA_AUD_PLL_CTRL0_DIV_OCLK_MODULO(x) ((x) << 28) +#define SSPA_AUD_PLL_CTRL0_FRACT_MASK (0xfffff << 8) +#define SSPA_AUD_PLL_CTRL0_FRACT(x) ((x) << 8) +#define SSPA_AUD_PLL_CTRL0_ENA_DITHER (1 << 7) +#define SSPA_AUD_PLL_CTRL0_ICP_2UA (0 << 5) +#define SSPA_AUD_PLL_CTRL0_ICP_5UA (1 << 5) +#define SSPA_AUD_PLL_CTRL0_ICP_7UA (2 << 5) +#define SSPA_AUD_PLL_CTRL0_ICP_10UA (3 << 5) +#define SSPA_AUD_PLL_CTRL0_DIV_FBCCLK_MASK (0x3 << 3) +#define SSPA_AUD_PLL_CTRL0_DIV_FBCCLK(x) ((x) << 3) +#define SSPA_AUD_PLL_CTRL0_DIV_MCLK_MASK (0x1 << 2) +#define SSPA_AUD_PLL_CTRL0_DIV_MCLK(x) ((x) << 2) +#define SSPA_AUD_PLL_CTRL0_PD_OVPROT_DIS (1 << 1) +#define SSPA_AUD_PLL_CTRL0_PU (1 << 0) + +/* SSPA Audio PLL Control 1 Register */ +#define SSPA_AUD_PLL_CTRL1_SEL_FAST_CLK (1 << 24) +#define SSPA_AUD_PLL_CTRL1_CLK_SEL_MASK (1 << 11) +#define SSPA_AUD_PLL_CTRL1_CLK_SEL_AUDIO_PLL (1 << 11) +#define SSPA_AUD_PLL_CTRL1_CLK_SEL_VCXO (0 << 11) +#define SSPA_AUD_PLL_CTRL1_DIV_OCLK_PATTERN_MASK (0x7ff << 0) +#define SSPA_AUD_PLL_CTRL1_DIV_OCLK_PATTERN(x) ((x) << 0) + +#define CLK_AUDIO_NR_CLKS 3 + +struct mmp2_audio_clk { + void __iomem *mmio_base; + + struct clk_hw audio_pll_hw; + struct clk_mux sspa_mux; + struct clk_mux sspa1_mux; + struct clk_divider sysclk_div; + struct clk_divider sspa0_div; + struct clk_divider sspa1_div; + struct clk_gate sysclk_gate; + struct clk_gate sspa0_gate; + struct clk_gate sspa1_gate; + + u32 aud_ctrl; + u32 aud_pll_ctrl0; + u32 aud_pll_ctrl1; + + spinlock_t lock; + + /* Must be last */ + struct clk_hw_onecell_data clk_data; +}; + +static const struct { + unsigned long parent_rate; + unsigned long freq_vco; + unsigned char mclk; + unsigned char fbcclk; + unsigned short fract; +} predivs[] = { + { 26000000, 135475200, 0, 0, 0x8a18 }, + { 26000000, 147456000, 0, 1, 0x0da1 }, + { 38400000, 135475200, 1, 2, 0x8208 }, + { 38400000, 147456000, 1, 3, 0xaaaa }, +}; + +static const struct { + unsigned char divisor; + unsigned char modulo; + unsigned char pattern; +} postdivs[] = { + { 1, 3, 0, }, + { 2, 5, 0, }, + { 4, 0, 0, }, + { 6, 1, 1, }, + { 8, 1, 0, }, + { 9, 1, 2, }, + { 12, 2, 1, }, + { 16, 2, 0, }, + { 18, 2, 2, }, + { 24, 4, 1, }, + { 36, 4, 2, }, + { 48, 6, 1, }, + { 72, 6, 2, }, +}; + +static unsigned long audio_pll_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct mmp2_audio_clk *priv = container_of(hw, struct mmp2_audio_clk, audio_pll_hw); + unsigned int prediv; + unsigned int postdiv; + u32 aud_pll_ctrl0; + u32 aud_pll_ctrl1; + + aud_pll_ctrl0 = readl(priv->mmio_base + SSPA_AUD_PLL_CTRL0); + aud_pll_ctrl0 &= SSPA_AUD_PLL_CTRL0_DIV_OCLK_MODULO_MASK | + SSPA_AUD_PLL_CTRL0_FRACT_MASK | + SSPA_AUD_PLL_CTRL0_ENA_DITHER | + SSPA_AUD_PLL_CTRL0_DIV_FBCCLK_MASK | + SSPA_AUD_PLL_CTRL0_DIV_MCLK_MASK | + SSPA_AUD_PLL_CTRL0_PU; + + aud_pll_ctrl1 = readl(priv->mmio_base + SSPA_AUD_PLL_CTRL1); + aud_pll_ctrl1 &= SSPA_AUD_PLL_CTRL1_CLK_SEL_MASK | + SSPA_AUD_PLL_CTRL1_DIV_OCLK_PATTERN_MASK; + + for (prediv = 0; prediv < ARRAY_SIZE(predivs); prediv++) { + if (predivs[prediv].parent_rate != parent_rate) + continue; + for (postdiv = 0; postdiv < ARRAY_SIZE(postdivs); postdiv++) { + unsigned long freq; + u32 val; + + val = SSPA_AUD_PLL_CTRL0_ENA_DITHER; + val |= SSPA_AUD_PLL_CTRL0_PU; + val |= SSPA_AUD_PLL_CTRL0_DIV_OCLK_MODULO(postdivs[postdiv].modulo); + val |= SSPA_AUD_PLL_CTRL0_FRACT(predivs[prediv].fract); + val |= SSPA_AUD_PLL_CTRL0_DIV_FBCCLK(predivs[prediv].fbcclk); + val |= SSPA_AUD_PLL_CTRL0_DIV_MCLK(predivs[prediv].mclk); + if (val != aud_pll_ctrl0) + continue; + + val = SSPA_AUD_PLL_CTRL1_CLK_SEL_AUDIO_PLL; + val |= SSPA_AUD_PLL_CTRL1_DIV_OCLK_PATTERN(postdivs[postdiv].pattern); + if (val != aud_pll_ctrl1) + continue; + + freq = predivs[prediv].freq_vco; + freq /= postdivs[postdiv].divisor; + return freq; + } + } + + return 0; +} + +static int audio_pll_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + unsigned int prediv; + unsigned int postdiv; + long rounded = 0; + + for (prediv = 0; prediv < ARRAY_SIZE(predivs); prediv++) { + if (predivs[prediv].parent_rate != req->best_parent_rate) + continue; + for (postdiv = 0; postdiv < ARRAY_SIZE(postdivs); postdiv++) { + long freq = predivs[prediv].freq_vco; + + freq /= postdivs[postdiv].divisor; + if (freq == req->rate) + return 0; + if (freq < req->rate) + continue; + if (rounded && freq > rounded) + continue; + rounded = freq; + } + } + + req->rate = rounded; + + return 0; +} + +static int audio_pll_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct mmp2_audio_clk *priv = container_of(hw, struct mmp2_audio_clk, audio_pll_hw); + unsigned int prediv; + unsigned int postdiv; + unsigned long val; + + for (prediv = 0; prediv < ARRAY_SIZE(predivs); prediv++) { + if (predivs[prediv].parent_rate != parent_rate) + continue; + + for (postdiv = 0; postdiv < ARRAY_SIZE(postdivs); postdiv++) { + if (rate * postdivs[postdiv].divisor != predivs[prediv].freq_vco) + continue; + + val = SSPA_AUD_PLL_CTRL0_ENA_DITHER; + val |= SSPA_AUD_PLL_CTRL0_PU; + val |= SSPA_AUD_PLL_CTRL0_DIV_OCLK_MODULO(postdivs[postdiv].modulo); + val |= SSPA_AUD_PLL_CTRL0_FRACT(predivs[prediv].fract); + val |= SSPA_AUD_PLL_CTRL0_DIV_FBCCLK(predivs[prediv].fbcclk); + val |= SSPA_AUD_PLL_CTRL0_DIV_MCLK(predivs[prediv].mclk); + writel(val, priv->mmio_base + SSPA_AUD_PLL_CTRL0); + + val = SSPA_AUD_PLL_CTRL1_CLK_SEL_AUDIO_PLL; + val |= SSPA_AUD_PLL_CTRL1_DIV_OCLK_PATTERN(postdivs[postdiv].pattern); + writel(val, priv->mmio_base + SSPA_AUD_PLL_CTRL1); + + return 0; + } + } + + return -ERANGE; +} + +static const struct clk_ops audio_pll_ops = { + .recalc_rate = audio_pll_recalc_rate, + .determine_rate = audio_pll_determine_rate, + .set_rate = audio_pll_set_rate, +}; + +static int register_clocks(struct mmp2_audio_clk *priv, struct device *dev) +{ + const struct clk_parent_data sspa_mux_parents[] = { + { .hw = &priv->audio_pll_hw }, + { .fw_name = "i2s0" }, + }; + const struct clk_parent_data sspa1_mux_parents[] = { + { .hw = &priv->audio_pll_hw }, + { .fw_name = "i2s1" }, + }; + int ret; + + priv->audio_pll_hw.init = CLK_HW_INIT_FW_NAME("audio_pll", + "vctcxo", &audio_pll_ops, + CLK_SET_RATE_PARENT); + ret = devm_clk_hw_register(dev, &priv->audio_pll_hw); + if (ret) + return ret; + + priv->sspa_mux.hw.init = CLK_HW_INIT_PARENTS_DATA("sspa_mux", + sspa_mux_parents, &clk_mux_ops, + CLK_SET_RATE_PARENT); + priv->sspa_mux.reg = priv->mmio_base + SSPA_AUD_CTRL; + priv->sspa_mux.mask = 1; + priv->sspa_mux.shift = SSPA_AUD_CTRL_SSPA0_MUX_SHIFT; + ret = devm_clk_hw_register(dev, &priv->sspa_mux.hw); + if (ret) + return ret; + + priv->sysclk_div.hw.init = CLK_HW_INIT_HW("sys_div", + &priv->sspa_mux.hw, &clk_divider_ops, + CLK_SET_RATE_PARENT); + priv->sysclk_div.reg = priv->mmio_base + SSPA_AUD_CTRL; + priv->sysclk_div.shift = SSPA_AUD_CTRL_SYSCLK_DIV_SHIFT; + priv->sysclk_div.width = 6; + priv->sysclk_div.flags = CLK_DIVIDER_ONE_BASED; + priv->sysclk_div.flags |= CLK_DIVIDER_ROUND_CLOSEST; + priv->sysclk_div.flags |= CLK_DIVIDER_ALLOW_ZERO; + ret = devm_clk_hw_register(dev, &priv->sysclk_div.hw); + if (ret) + return ret; + + priv->sysclk_gate.hw.init = CLK_HW_INIT_HW("sys_clk", + &priv->sysclk_div.hw, &clk_gate_ops, + CLK_SET_RATE_PARENT); + priv->sysclk_gate.reg = priv->mmio_base + SSPA_AUD_CTRL; + priv->sysclk_gate.bit_idx = SSPA_AUD_CTRL_SYSCLK_SHIFT; + ret = devm_clk_hw_register(dev, &priv->sysclk_gate.hw); + if (ret) + return ret; + + priv->sspa0_div.hw.init = CLK_HW_INIT_HW("sspa0_div", + &priv->sspa_mux.hw, &clk_divider_ops, 0); + priv->sspa0_div.reg = priv->mmio_base + SSPA_AUD_CTRL; + priv->sspa0_div.shift = SSPA_AUD_CTRL_SSPA0_DIV_SHIFT; + priv->sspa0_div.width = 6; + priv->sspa0_div.flags = CLK_DIVIDER_ONE_BASED; + priv->sspa0_div.flags |= CLK_DIVIDER_ROUND_CLOSEST; + priv->sspa0_div.flags |= CLK_DIVIDER_ALLOW_ZERO; + ret = devm_clk_hw_register(dev, &priv->sspa0_div.hw); + if (ret) + return ret; + + priv->sspa0_gate.hw.init = CLK_HW_INIT_HW("sspa0_clk", + &priv->sspa0_div.hw, &clk_gate_ops, + CLK_SET_RATE_PARENT); + priv->sspa0_gate.reg = priv->mmio_base + SSPA_AUD_CTRL; + priv->sspa0_gate.bit_idx = SSPA_AUD_CTRL_SSPA0_SHIFT; + ret = devm_clk_hw_register(dev, &priv->sspa0_gate.hw); + if (ret) + return ret; + + priv->sspa1_mux.hw.init = CLK_HW_INIT_PARENTS_DATA("sspa1_mux", + sspa1_mux_parents, &clk_mux_ops, + CLK_SET_RATE_PARENT); + priv->sspa1_mux.reg = priv->mmio_base + SSPA_AUD_CTRL; + priv->sspa1_mux.mask = 1; + priv->sspa1_mux.shift = SSPA_AUD_CTRL_SSPA1_MUX_SHIFT; + ret = devm_clk_hw_register(dev, &priv->sspa1_mux.hw); + if (ret) + return ret; + + priv->sspa1_div.hw.init = CLK_HW_INIT_HW("sspa1_div", + &priv->sspa1_mux.hw, &clk_divider_ops, 0); + priv->sspa1_div.reg = priv->mmio_base + SSPA_AUD_CTRL; + priv->sspa1_div.shift = SSPA_AUD_CTRL_SSPA1_DIV_SHIFT; + priv->sspa1_div.width = 6; + priv->sspa1_div.flags = CLK_DIVIDER_ONE_BASED; + priv->sspa1_div.flags |= CLK_DIVIDER_ROUND_CLOSEST; + priv->sspa1_div.flags |= CLK_DIVIDER_ALLOW_ZERO; + ret = devm_clk_hw_register(dev, &priv->sspa1_div.hw); + if (ret) + return ret; + + priv->sspa1_gate.hw.init = CLK_HW_INIT_HW("sspa1_clk", + &priv->sspa1_div.hw, &clk_gate_ops, + CLK_SET_RATE_PARENT); + priv->sspa1_gate.reg = priv->mmio_base + SSPA_AUD_CTRL; + priv->sspa1_gate.bit_idx = SSPA_AUD_CTRL_SSPA1_SHIFT; + ret = devm_clk_hw_register(dev, &priv->sspa1_gate.hw); + if (ret) + return ret; + + priv->clk_data.hws[MMP2_CLK_AUDIO_SYSCLK] = &priv->sysclk_gate.hw; + priv->clk_data.hws[MMP2_CLK_AUDIO_SSPA0] = &priv->sspa0_gate.hw; + priv->clk_data.hws[MMP2_CLK_AUDIO_SSPA1] = &priv->sspa1_gate.hw; + priv->clk_data.num = CLK_AUDIO_NR_CLKS; + + return of_clk_add_hw_provider(dev->of_node, of_clk_hw_onecell_get, + &priv->clk_data); +} + +static int mmp2_audio_clk_probe(struct platform_device *pdev) +{ + struct mmp2_audio_clk *priv; + int ret; + + priv = devm_kzalloc(&pdev->dev, + struct_size(priv, clk_data.hws, + CLK_AUDIO_NR_CLKS), + GFP_KERNEL); + if (!priv) + return -ENOMEM; + + spin_lock_init(&priv->lock); + platform_set_drvdata(pdev, priv); + + priv->mmio_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(priv->mmio_base)) + return PTR_ERR(priv->mmio_base); + + pm_runtime_enable(&pdev->dev); + ret = pm_clk_create(&pdev->dev); + if (ret) + goto disable_pm_runtime; + + ret = pm_clk_add(&pdev->dev, "audio"); + if (ret) + goto destroy_pm_clk; + + ret = register_clocks(priv, &pdev->dev); + if (ret) + goto destroy_pm_clk; + + return 0; + +destroy_pm_clk: + pm_clk_destroy(&pdev->dev); +disable_pm_runtime: + pm_runtime_disable(&pdev->dev); + + return ret; +} + +static void mmp2_audio_clk_remove(struct platform_device *pdev) +{ + pm_clk_destroy(&pdev->dev); + pm_runtime_disable(&pdev->dev); +} + +#ifdef CONFIG_PM +static int mmp2_audio_clk_suspend(struct device *dev) +{ + struct mmp2_audio_clk *priv = dev_get_drvdata(dev); + + priv->aud_ctrl = readl(priv->mmio_base + SSPA_AUD_CTRL); + priv->aud_pll_ctrl0 = readl(priv->mmio_base + SSPA_AUD_PLL_CTRL0); + priv->aud_pll_ctrl1 = readl(priv->mmio_base + SSPA_AUD_PLL_CTRL1); + pm_clk_suspend(dev); + + return 0; +} + +static int mmp2_audio_clk_resume(struct device *dev) +{ + struct mmp2_audio_clk *priv = dev_get_drvdata(dev); + + pm_clk_resume(dev); + writel(priv->aud_ctrl, priv->mmio_base + SSPA_AUD_CTRL); + writel(priv->aud_pll_ctrl0, priv->mmio_base + SSPA_AUD_PLL_CTRL0); + writel(priv->aud_pll_ctrl1, priv->mmio_base + SSPA_AUD_PLL_CTRL1); + + return 0; +} +#endif + +static const struct dev_pm_ops mmp2_audio_clk_pm_ops = { + SET_RUNTIME_PM_OPS(mmp2_audio_clk_suspend, mmp2_audio_clk_resume, NULL) +}; + +static const struct of_device_id mmp2_audio_clk_of_match[] = { + { .compatible = "marvell,mmp2-audio-clock" }, + {} +}; + +MODULE_DEVICE_TABLE(of, mmp2_audio_clk_of_match); + +static struct platform_driver mmp2_audio_clk_driver = { + .driver = { + .name = "mmp2-audio-clock", + .of_match_table = of_match_ptr(mmp2_audio_clk_of_match), + .pm = &mmp2_audio_clk_pm_ops, + }, + .probe = mmp2_audio_clk_probe, + .remove = mmp2_audio_clk_remove, +}; +module_platform_driver(mmp2_audio_clk_driver); + +MODULE_AUTHOR("Lubomir Rintel <lkundrak@v3.sk>"); +MODULE_DESCRIPTION("Clock driver for MMP2 Audio subsystem"); +MODULE_LICENSE("GPL"); diff --git a/drivers/clk/mmp/clk-frac.c b/drivers/clk/mmp/clk-frac.c new file mode 100644 index 000000000..13bfc4c99 --- /dev/null +++ b/drivers/clk/mmp/clk-frac.c @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * mmp factor clock operation source file + * + * Copyright (C) 2012 Marvell + * Chao Xie <xiechao.mail@gmail.com> + */ + +#include <linux/clk-provider.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/err.h> + +#include "clk.h" +/* + * It is M/N clock + * + * Fout from synthesizer can be given from two equations: + * numerator/denominator = Fin / (Fout * factor) + */ + +#define to_clk_factor(hw) container_of(hw, struct mmp_clk_factor, hw) + +static int clk_factor_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct mmp_clk_factor *factor = to_clk_factor(hw); + u64 rate = 0, prev_rate; + struct u32_fract *d; + int i; + + for (i = 0; i < factor->ftbl_cnt; i++) { + d = &factor->ftbl[i]; + + prev_rate = rate; + rate = (u64)(req->best_parent_rate) * d->denominator; + do_div(rate, d->numerator * factor->masks->factor); + if (rate > req->rate) + break; + } + + if ((i == 0) || (i == factor->ftbl_cnt)) + req->rate = rate; + else if ((req->rate - prev_rate) > (rate - req->rate)) + req->rate = rate; + else + req->rate = prev_rate; + + return 0; +} + +static unsigned long clk_factor_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct mmp_clk_factor *factor = to_clk_factor(hw); + struct mmp_clk_factor_masks *masks = factor->masks; + struct u32_fract d; + unsigned int val; + u64 rate; + + val = readl_relaxed(factor->base); + + /* calculate numerator */ + d.numerator = (val >> masks->num_shift) & masks->num_mask; + + /* calculate denominator */ + d.denominator = (val >> masks->den_shift) & masks->den_mask; + if (!d.denominator) + return 0; + + rate = (u64)parent_rate * d.denominator; + do_div(rate, d.numerator * factor->masks->factor); + + return rate; +} + +/* Configures new clock rate*/ +static int clk_factor_set_rate(struct clk_hw *hw, unsigned long drate, + unsigned long prate) +{ + struct mmp_clk_factor *factor = to_clk_factor(hw); + struct mmp_clk_factor_masks *masks = factor->masks; + int i; + unsigned long val; + unsigned long flags = 0; + struct u32_fract *d; + u64 rate = 0; + + for (i = 0; i < factor->ftbl_cnt; i++) { + d = &factor->ftbl[i]; + + rate = (u64)prate * d->denominator; + do_div(rate, d->numerator * factor->masks->factor); + if (rate > drate) + break; + } + d = i ? &factor->ftbl[i - 1] : &factor->ftbl[0]; + + if (factor->lock) + spin_lock_irqsave(factor->lock, flags); + + val = readl_relaxed(factor->base); + + val &= ~(masks->num_mask << masks->num_shift); + val |= (d->numerator & masks->num_mask) << masks->num_shift; + + val &= ~(masks->den_mask << masks->den_shift); + val |= (d->denominator & masks->den_mask) << masks->den_shift; + + writel_relaxed(val, factor->base); + + if (factor->lock) + spin_unlock_irqrestore(factor->lock, flags); + + return 0; +} + +static int clk_factor_init(struct clk_hw *hw) +{ + struct mmp_clk_factor *factor = to_clk_factor(hw); + struct mmp_clk_factor_masks *masks = factor->masks; + struct u32_fract d; + u32 val; + int i; + unsigned long flags = 0; + + if (factor->lock) + spin_lock_irqsave(factor->lock, flags); + + val = readl(factor->base); + + /* calculate numerator */ + d.numerator = (val >> masks->num_shift) & masks->num_mask; + + /* calculate denominator */ + d.denominator = (val >> masks->den_shift) & masks->den_mask; + + for (i = 0; i < factor->ftbl_cnt; i++) + if (d.denominator == factor->ftbl[i].denominator && + d.numerator == factor->ftbl[i].numerator) + break; + + if (i >= factor->ftbl_cnt) { + val &= ~(masks->num_mask << masks->num_shift); + val |= (factor->ftbl[0].numerator & masks->num_mask) << masks->num_shift; + + val &= ~(masks->den_mask << masks->den_shift); + val |= (factor->ftbl[0].denominator & masks->den_mask) << masks->den_shift; + } + + if (!(val & masks->enable_mask) || i >= factor->ftbl_cnt) { + val |= masks->enable_mask; + writel(val, factor->base); + } + + if (factor->lock) + spin_unlock_irqrestore(factor->lock, flags); + + return 0; +} + +static const struct clk_ops clk_factor_ops = { + .recalc_rate = clk_factor_recalc_rate, + .determine_rate = clk_factor_determine_rate, + .set_rate = clk_factor_set_rate, + .init = clk_factor_init, +}; + +struct clk *mmp_clk_register_factor(const char *name, const char *parent_name, + unsigned long flags, void __iomem *base, + struct mmp_clk_factor_masks *masks, + struct u32_fract *ftbl, unsigned int ftbl_cnt, spinlock_t *lock) +{ + struct mmp_clk_factor *factor; + struct clk_init_data init; + struct clk *clk; + + if (!masks) { + pr_err("%s: must pass a clk_factor_mask\n", __func__); + return ERR_PTR(-EINVAL); + } + + factor = kzalloc_obj(*factor); + if (!factor) + return ERR_PTR(-ENOMEM); + + /* struct clk_aux assignments */ + factor->base = base; + factor->masks = masks; + factor->ftbl = ftbl; + factor->ftbl_cnt = ftbl_cnt; + factor->hw.init = &init; + factor->lock = lock; + + init.name = name; + init.ops = &clk_factor_ops; + init.flags = flags; + init.parent_names = &parent_name; + init.num_parents = 1; + + clk = clk_register(NULL, &factor->hw); + if (IS_ERR_OR_NULL(clk)) + kfree(factor); + + return clk; +} diff --git a/drivers/clk/mmp/clk-gate.c b/drivers/clk/mmp/clk-gate.c new file mode 100644 index 000000000..cc2f841ae --- /dev/null +++ b/drivers/clk/mmp/clk-gate.c @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * mmp gate clock operation source file + * + * Copyright (C) 2014 Marvell + * Chao Xie <chao.xie@marvell.com> + */ + +#include <linux/clk-provider.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/err.h> +#include <linux/delay.h> + +#include "clk.h" + +/* + * Some clocks will have multiple bits to enable the clocks, and + * the bits to disable the clock is not same as enabling bits. + */ + +#define to_clk_mmp_gate(hw) container_of(hw, struct mmp_clk_gate, hw) + +static int mmp_clk_gate_enable(struct clk_hw *hw) +{ + struct mmp_clk_gate *gate = to_clk_mmp_gate(hw); + unsigned long flags = 0; + unsigned long rate; + u32 tmp; + + if (gate->lock) + spin_lock_irqsave(gate->lock, flags); + + tmp = readl(gate->reg); + tmp &= ~gate->mask; + tmp |= gate->val_enable; + writel(tmp, gate->reg); + + if (gate->lock) + spin_unlock_irqrestore(gate->lock, flags); + + if (gate->flags & MMP_CLK_GATE_NEED_DELAY) { + rate = clk_hw_get_rate(hw); + /* Need delay 2 cycles. */ + udelay(2000000/rate); + } + + return 0; +} + +static void mmp_clk_gate_disable(struct clk_hw *hw) +{ + struct mmp_clk_gate *gate = to_clk_mmp_gate(hw); + unsigned long flags = 0; + u32 tmp; + + if (gate->lock) + spin_lock_irqsave(gate->lock, flags); + + tmp = readl(gate->reg); + tmp &= ~gate->mask; + tmp |= gate->val_disable; + writel(tmp, gate->reg); + + if (gate->lock) + spin_unlock_irqrestore(gate->lock, flags); +} + +static int mmp_clk_gate_is_enabled(struct clk_hw *hw) +{ + struct mmp_clk_gate *gate = to_clk_mmp_gate(hw); + unsigned long flags = 0; + u32 tmp; + + if (gate->lock) + spin_lock_irqsave(gate->lock, flags); + + tmp = readl(gate->reg); + + if (gate->lock) + spin_unlock_irqrestore(gate->lock, flags); + + return (tmp & gate->mask) == gate->val_enable; +} + +const struct clk_ops mmp_clk_gate_ops = { + .enable = mmp_clk_gate_enable, + .disable = mmp_clk_gate_disable, + .is_enabled = mmp_clk_gate_is_enabled, +}; + +struct clk *mmp_clk_register_gate(struct device *dev, const char *name, + const char *parent_name, unsigned long flags, + void __iomem *reg, u32 mask, u32 val_enable, u32 val_disable, + unsigned int gate_flags, spinlock_t *lock) +{ + struct mmp_clk_gate *gate; + struct clk *clk; + struct clk_init_data init; + + /* allocate the gate */ + gate = kzalloc_obj(*gate); + if (!gate) + return ERR_PTR(-ENOMEM); + + init.name = name; + init.ops = &mmp_clk_gate_ops; + init.flags = flags; + init.parent_names = (parent_name ? &parent_name : NULL); + init.num_parents = (parent_name ? 1 : 0); + + /* struct clk_gate assignments */ + gate->reg = reg; + gate->mask = mask; + gate->val_enable = val_enable; + gate->val_disable = val_disable; + gate->flags = gate_flags; + gate->lock = lock; + gate->hw.init = &init; + + clk = clk_register(dev, &gate->hw); + + if (IS_ERR(clk)) + kfree(gate); + + return clk; +} diff --git a/drivers/clk/mmp/clk-mix.c b/drivers/clk/mmp/clk-mix.c new file mode 100644 index 000000000..b1c9899c7 --- /dev/null +++ b/drivers/clk/mmp/clk-mix.c @@ -0,0 +1,504 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * mmp mix(div and mux) clock operation source file + * + * Copyright (C) 2014 Marvell + * Chao Xie <chao.xie@marvell.com> + */ + +#include <linux/clk-provider.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/err.h> + +#include "clk.h" + +/* + * The mix clock is a clock combined mux and div type clock. + * Because the div field and mux field need to be set at same + * time, we can not divide it into 2 types of clock + */ + +#define to_clk_mix(hw) container_of(hw, struct mmp_clk_mix, hw) + +static unsigned int _get_maxdiv(struct mmp_clk_mix *mix) +{ + unsigned int div_mask = (1 << mix->reg_info.width_div) - 1; + unsigned int maxdiv = 0; + struct clk_div_table *clkt; + + if (mix->div_flags & CLK_DIVIDER_ONE_BASED) + return div_mask; + if (mix->div_flags & CLK_DIVIDER_POWER_OF_TWO) + return 1 << div_mask; + if (mix->div_table) { + for (clkt = mix->div_table; clkt->div; clkt++) + if (clkt->div > maxdiv) + maxdiv = clkt->div; + return maxdiv; + } + return div_mask + 1; +} + +static unsigned int _get_div(struct mmp_clk_mix *mix, unsigned int val) +{ + struct clk_div_table *clkt; + + if (mix->div_flags & CLK_DIVIDER_ONE_BASED) + return val; + if (mix->div_flags & CLK_DIVIDER_POWER_OF_TWO) + return 1 << val; + if (mix->div_table) { + for (clkt = mix->div_table; clkt->div; clkt++) + if (clkt->val == val) + return clkt->div; + if (clkt->div == 0) + return 0; + } + return val + 1; +} + +static unsigned int _get_mux(struct mmp_clk_mix *mix, unsigned int val) +{ + int num_parents = clk_hw_get_num_parents(&mix->hw); + int i; + + if (mix->mux_flags & CLK_MUX_INDEX_BIT) + return ffs(val) - 1; + if (mix->mux_flags & CLK_MUX_INDEX_ONE) + return val - 1; + if (mix->mux_table) { + for (i = 0; i < num_parents; i++) + if (mix->mux_table[i] == val) + return i; + if (i == num_parents) + return 0; + } + + return val; +} +static unsigned int _get_div_val(struct mmp_clk_mix *mix, unsigned int div) +{ + struct clk_div_table *clkt; + + if (mix->div_flags & CLK_DIVIDER_ONE_BASED) + return div; + if (mix->div_flags & CLK_DIVIDER_POWER_OF_TWO) + return __ffs(div); + if (mix->div_table) { + for (clkt = mix->div_table; clkt->div; clkt++) + if (clkt->div == div) + return clkt->val; + if (clkt->div == 0) + return 0; + } + + return div - 1; +} + +static unsigned int _get_mux_val(struct mmp_clk_mix *mix, unsigned int mux) +{ + if (mix->mux_table) + return mix->mux_table[mux]; + + return mux; +} + +static void _filter_clk_table(struct mmp_clk_mix *mix, + struct mmp_clk_mix_clk_table *table, + unsigned int table_size) +{ + int i; + struct mmp_clk_mix_clk_table *item; + struct clk_hw *parent, *hw; + unsigned long parent_rate; + + hw = &mix->hw; + + for (i = 0; i < table_size; i++) { + item = &table[i]; + parent = clk_hw_get_parent_by_index(hw, item->parent_index); + parent_rate = clk_hw_get_rate(parent); + if (parent_rate % item->rate) { + item->valid = 0; + } else { + item->divisor = parent_rate / item->rate; + item->valid = 1; + } + } +} + +static int _set_rate(struct mmp_clk_mix *mix, u32 mux_val, u32 div_val, + unsigned int change_mux, unsigned int change_div) +{ + struct mmp_clk_mix_reg_info *ri = &mix->reg_info; + u8 width, shift; + u32 mux_div, fc_req; + int ret, timeout = 50; + unsigned long flags = 0; + + if (!change_mux && !change_div) + return -EINVAL; + + if (mix->lock) + spin_lock_irqsave(mix->lock, flags); + + if (mix->type == MMP_CLK_MIX_TYPE_V1 + || mix->type == MMP_CLK_MIX_TYPE_V2) + mux_div = readl(ri->reg_clk_ctrl); + else + mux_div = readl(ri->reg_clk_sel); + + if (change_div) { + width = ri->width_div; + shift = ri->shift_div; + mux_div &= ~MMP_CLK_BITS_MASK(width, shift); + mux_div |= MMP_CLK_BITS_SET_VAL(div_val, width, shift); + } + + if (change_mux) { + width = ri->width_mux; + shift = ri->shift_mux; + mux_div &= ~MMP_CLK_BITS_MASK(width, shift); + mux_div |= MMP_CLK_BITS_SET_VAL(mux_val, width, shift); + } + + if (mix->type == MMP_CLK_MIX_TYPE_V1) { + writel(mux_div, ri->reg_clk_ctrl); + } else if (mix->type == MMP_CLK_MIX_TYPE_V2) { + mux_div |= (1 << ri->bit_fc); + writel(mux_div, ri->reg_clk_ctrl); + + do { + fc_req = readl(ri->reg_clk_ctrl); + timeout--; + if (!(fc_req & (1 << ri->bit_fc))) + break; + } while (timeout); + + if (timeout == 0) { + pr_err("%s:%s cannot do frequency change\n", + __func__, clk_hw_get_name(&mix->hw)); + ret = -EBUSY; + goto error; + } + } else { + fc_req = readl(ri->reg_clk_ctrl); + fc_req |= 1 << ri->bit_fc; + writel(fc_req, ri->reg_clk_ctrl); + writel(mux_div, ri->reg_clk_sel); + fc_req &= ~(1 << ri->bit_fc); + } + + ret = 0; +error: + if (mix->lock) + spin_unlock_irqrestore(mix->lock, flags); + + return ret; +} + +static int mmp_clk_mix_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct mmp_clk_mix *mix = to_clk_mix(hw); + struct mmp_clk_mix_clk_table *item; + struct clk_hw *parent, *parent_best; + unsigned long parent_rate, mix_rate, mix_rate_best, parent_rate_best; + unsigned long gap, gap_best; + u32 div_val_max; + unsigned int div; + int i, j; + + + mix_rate_best = 0; + parent_rate_best = 0; + gap_best = ULONG_MAX; + parent_best = NULL; + + if (mix->table) { + for (i = 0; i < mix->table_size; i++) { + item = &mix->table[i]; + if (item->valid == 0) + continue; + parent = clk_hw_get_parent_by_index(hw, + item->parent_index); + parent_rate = clk_hw_get_rate(parent); + mix_rate = parent_rate / item->divisor; + gap = abs(mix_rate - req->rate); + if (!parent_best || gap < gap_best) { + parent_best = parent; + parent_rate_best = parent_rate; + mix_rate_best = mix_rate; + gap_best = gap; + if (gap_best == 0) + goto found; + } + } + } else { + for (i = 0; i < clk_hw_get_num_parents(hw); i++) { + parent = clk_hw_get_parent_by_index(hw, i); + parent_rate = clk_hw_get_rate(parent); + div_val_max = _get_maxdiv(mix); + for (j = 0; j < div_val_max; j++) { + div = _get_div(mix, j); + mix_rate = parent_rate / div; + gap = abs(mix_rate - req->rate); + if (!parent_best || gap < gap_best) { + parent_best = parent; + parent_rate_best = parent_rate; + mix_rate_best = mix_rate; + gap_best = gap; + if (gap_best == 0) + goto found; + } + } + } + } + +found: + if (!parent_best) + return -EINVAL; + + req->best_parent_rate = parent_rate_best; + req->best_parent_hw = parent_best; + req->rate = mix_rate_best; + + return 0; +} + +static int mmp_clk_mix_set_rate_and_parent(struct clk_hw *hw, + unsigned long rate, + unsigned long parent_rate, + u8 index) +{ + struct mmp_clk_mix *mix = to_clk_mix(hw); + unsigned int div; + u32 div_val, mux_val; + + div = parent_rate / rate; + div_val = _get_div_val(mix, div); + mux_val = _get_mux_val(mix, index); + + return _set_rate(mix, mux_val, div_val, 1, 1); +} + +static u8 mmp_clk_mix_get_parent(struct clk_hw *hw) +{ + struct mmp_clk_mix *mix = to_clk_mix(hw); + struct mmp_clk_mix_reg_info *ri = &mix->reg_info; + unsigned long flags = 0; + u32 mux_div = 0; + u8 width, shift; + u32 mux_val; + + if (mix->lock) + spin_lock_irqsave(mix->lock, flags); + + if (mix->type == MMP_CLK_MIX_TYPE_V1 + || mix->type == MMP_CLK_MIX_TYPE_V2) + mux_div = readl(ri->reg_clk_ctrl); + else + mux_div = readl(ri->reg_clk_sel); + + if (mix->lock) + spin_unlock_irqrestore(mix->lock, flags); + + width = mix->reg_info.width_mux; + shift = mix->reg_info.shift_mux; + + mux_val = MMP_CLK_BITS_GET_VAL(mux_div, width, shift); + + return _get_mux(mix, mux_val); +} + +static unsigned long mmp_clk_mix_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct mmp_clk_mix *mix = to_clk_mix(hw); + struct mmp_clk_mix_reg_info *ri = &mix->reg_info; + unsigned long flags = 0; + u32 mux_div = 0; + u8 width, shift; + unsigned int div; + + if (mix->lock) + spin_lock_irqsave(mix->lock, flags); + + if (mix->type == MMP_CLK_MIX_TYPE_V1 + || mix->type == MMP_CLK_MIX_TYPE_V2) + mux_div = readl(ri->reg_clk_ctrl); + else + mux_div = readl(ri->reg_clk_sel); + + if (mix->lock) + spin_unlock_irqrestore(mix->lock, flags); + + width = mix->reg_info.width_div; + shift = mix->reg_info.shift_div; + + div = _get_div(mix, MMP_CLK_BITS_GET_VAL(mux_div, width, shift)); + + return parent_rate / div; +} + +static int mmp_clk_set_parent(struct clk_hw *hw, u8 index) +{ + struct mmp_clk_mix *mix = to_clk_mix(hw); + struct mmp_clk_mix_clk_table *item; + int i; + u32 div_val, mux_val; + + if (mix->table) { + for (i = 0; i < mix->table_size; i++) { + item = &mix->table[i]; + if (item->valid == 0) + continue; + if (item->parent_index == index) + break; + } + if (i < mix->table_size) { + div_val = _get_div_val(mix, item->divisor); + mux_val = _get_mux_val(mix, item->parent_index); + } else + return -EINVAL; + } else { + mux_val = _get_mux_val(mix, index); + div_val = 0; + } + + return _set_rate(mix, mux_val, div_val, 1, div_val ? 1 : 0); +} + +static int mmp_clk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long best_parent_rate) +{ + struct mmp_clk_mix *mix = to_clk_mix(hw); + struct mmp_clk_mix_clk_table *item; + unsigned long parent_rate; + unsigned int best_divisor; + struct clk_hw *parent; + int i; + + best_divisor = best_parent_rate / rate; + + if (mix->table) { + for (i = 0; i < mix->table_size; i++) { + item = &mix->table[i]; + if (item->valid == 0) + continue; + parent = clk_hw_get_parent_by_index(hw, + item->parent_index); + parent_rate = clk_hw_get_rate(parent); + if (parent_rate == best_parent_rate + && item->divisor == best_divisor) + break; + } + if (i < mix->table_size) + return _set_rate(mix, + _get_mux_val(mix, item->parent_index), + _get_div_val(mix, item->divisor), + 1, 1); + else + return -EINVAL; + } else { + for (i = 0; i < clk_hw_get_num_parents(hw); i++) { + parent = clk_hw_get_parent_by_index(hw, i); + parent_rate = clk_hw_get_rate(parent); + if (parent_rate == best_parent_rate) + break; + } + if (i < clk_hw_get_num_parents(hw)) + return _set_rate(mix, _get_mux_val(mix, i), + _get_div_val(mix, best_divisor), 1, 1); + else + return -EINVAL; + } +} + +static int mmp_clk_mix_init(struct clk_hw *hw) +{ + struct mmp_clk_mix *mix = to_clk_mix(hw); + + if (mix->table) + _filter_clk_table(mix, mix->table, mix->table_size); + + return 0; +} + +const struct clk_ops mmp_clk_mix_ops = { + .determine_rate = mmp_clk_mix_determine_rate, + .set_rate_and_parent = mmp_clk_mix_set_rate_and_parent, + .set_rate = mmp_clk_set_rate, + .set_parent = mmp_clk_set_parent, + .get_parent = mmp_clk_mix_get_parent, + .recalc_rate = mmp_clk_mix_recalc_rate, + .init = mmp_clk_mix_init, +}; + +struct clk *mmp_clk_register_mix(struct device *dev, + const char *name, + const char * const *parent_names, + u8 num_parents, + unsigned long flags, + struct mmp_clk_mix_config *config, + spinlock_t *lock) +{ + struct mmp_clk_mix *mix; + struct clk *clk; + struct clk_init_data init; + + mix = kzalloc_obj(*mix); + if (!mix) + return ERR_PTR(-ENOMEM); + + init.name = name; + init.flags = flags | CLK_GET_RATE_NOCACHE; + init.parent_names = parent_names; + init.num_parents = num_parents; + init.ops = &mmp_clk_mix_ops; + + memcpy(&mix->reg_info, &config->reg_info, sizeof(config->reg_info)); + if (config->table) { + mix->table = kmemdup_array(config->table, config->table_size, + sizeof(*mix->table), GFP_KERNEL); + if (!mix->table) + goto free_mix; + + mix->table_size = config->table_size; + } + + if (config->mux_table) { + mix->mux_table = kmemdup_array(config->mux_table, num_parents, + sizeof(*mix->mux_table), GFP_KERNEL); + if (!mix->mux_table) { + kfree(mix->table); + goto free_mix; + } + } + + mix->div_flags = config->div_flags; + mix->mux_flags = config->mux_flags; + mix->lock = lock; + mix->hw.init = &init; + + if (config->reg_info.bit_fc >= 32) + mix->type = MMP_CLK_MIX_TYPE_V1; + else if (config->reg_info.reg_clk_sel) + mix->type = MMP_CLK_MIX_TYPE_V3; + else + mix->type = MMP_CLK_MIX_TYPE_V2; + clk = clk_register(dev, &mix->hw); + + if (IS_ERR(clk)) { + kfree(mix->mux_table); + kfree(mix->table); + kfree(mix); + } + + return clk; + +free_mix: + kfree(mix); + return ERR_PTR(-ENOMEM); +} diff --git a/drivers/clk/mmp/clk-of-mmp2.c b/drivers/clk/mmp/clk-of-mmp2.c new file mode 100644 index 000000000..77e9244b5 --- /dev/null +++ b/drivers/clk/mmp/clk-of-mmp2.c @@ -0,0 +1,569 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * mmp2 clock framework source file + * + * Copyright (C) 2012 Marvell + * Chao Xie <xiechao.mail@gmail.com> + * Copyright (C) 2020 Lubomir Rintel <lkundrak@v3.sk> + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/spinlock.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/of_address.h> +#include <linux/clk.h> + +#include <dt-bindings/clock/marvell,mmp2.h> +#include <dt-bindings/power/marvell,mmp2.h> + +#include "clk.h" +#include "reset.h" + +#define APBC_RTC 0x0 +#define APBC_TWSI0 0x4 +#define APBC_TWSI1 0x8 +#define APBC_TWSI2 0xc +#define APBC_TWSI3 0x10 +#define APBC_TWSI4 0x7c +#define APBC_TWSI5 0x80 +#define APBC_KPC 0x18 +#define APBC_TIMER 0x24 +#define APBC_UART0 0x2c +#define APBC_UART1 0x30 +#define APBC_UART2 0x34 +#define APBC_UART3 0x88 +#define APBC_GPIO 0x38 +#define APBC_PWM0 0x3c +#define APBC_PWM1 0x40 +#define APBC_PWM2 0x44 +#define APBC_PWM3 0x48 +#define APBC_SSP0 0x50 +#define APBC_SSP1 0x54 +#define APBC_SSP2 0x58 +#define APBC_SSP3 0x5c +#define APBC_THERMAL0 0x90 +#define APBC_THERMAL1 0x98 +#define APBC_THERMAL2 0x9c +#define APBC_THERMAL3 0xa0 +#define APMU_SDH0 0x54 +#define APMU_SDH1 0x58 +#define APMU_SDH2 0xe8 +#define APMU_SDH3 0xec +#define APMU_SDH4 0x15c +#define APMU_USB 0x5c +#define APMU_DISP0 0x4c +#define APMU_DISP1 0x110 +#define APMU_CCIC0 0x50 +#define APMU_CCIC1 0xf4 +#define APMU_USBHSIC0 0xf8 +#define APMU_USBHSIC1 0xfc +#define APMU_GPU 0xcc +#define APMU_AUDIO 0x10c +#define APMU_CAMERA 0x1fc + +#define MPMU_FCCR 0x8 +#define MPMU_POSR 0x10 +#define MPMU_UART_PLL 0x14 +#define MPMU_PLL2_CR 0x34 +#define MPMU_I2S0_PLL 0x40 +#define MPMU_I2S1_PLL 0x44 +#define MPMU_ACGR 0x1024 +/* MMP3 specific below */ +#define MPMU_PLL3_CR 0x50 +#define MPMU_PLL3_CTRL1 0x58 +#define MPMU_PLL1_CTRL 0x5c +#define MPMU_PLL_DIFF_CTRL 0x68 +#define MPMU_PLL2_CTRL1 0x414 + +#define NR_CLKS 200 + +enum mmp2_clk_model { + CLK_MODEL_MMP2, + CLK_MODEL_MMP3, +}; + +struct mmp2_clk_unit { + struct mmp_clk_unit unit; + enum mmp2_clk_model model; + struct genpd_onecell_data pd_data; + struct generic_pm_domain *pm_domains[MMP2_NR_POWER_DOMAINS]; + void __iomem *mpmu_base; + void __iomem *apmu_base; + void __iomem *apbc_base; +}; + +static struct mmp_param_fixed_rate_clk fixed_rate_clks[] = { + {MMP2_CLK_CLK32, "clk32", NULL, 0, 32768}, + {MMP2_CLK_VCTCXO, "vctcxo", NULL, 0, 26000000}, + {MMP2_CLK_USB_PLL, "usb_pll", NULL, 0, 480000000}, + {0, "i2s_pll", NULL, 0, 99666667}, +}; + +static struct mmp_param_pll_clk pll_clks[] = { + {MMP2_CLK_PLL1, "pll1", 797330000, MPMU_FCCR, 0x4000, MPMU_POSR, 0}, + {MMP2_CLK_PLL2, "pll2", 0, MPMU_PLL2_CR, 0x0300, MPMU_PLL2_CR, 10}, +}; + +static struct mmp_param_pll_clk mmp3_pll_clks[] = { + {MMP2_CLK_PLL2, "pll1", 797330000, MPMU_FCCR, 0x4000, MPMU_POSR, 0, 26000000, MPMU_PLL1_CTRL, 25}, + {MMP2_CLK_PLL2, "pll2", 0, MPMU_PLL2_CR, 0x0300, MPMU_PLL2_CR, 10, 26000000, MPMU_PLL2_CTRL1, 25}, + {MMP3_CLK_PLL1_P, "pll1_p", 0, MPMU_PLL_DIFF_CTRL, 0x0010, 0, 0, 797330000, MPMU_PLL_DIFF_CTRL, 0}, + {MMP3_CLK_PLL2_P, "pll2_p", 0, MPMU_PLL_DIFF_CTRL, 0x0100, MPMU_PLL2_CR, 10, 26000000, MPMU_PLL_DIFF_CTRL, 5}, + {MMP3_CLK_PLL3, "pll3", 0, MPMU_PLL3_CR, 0x0300, MPMU_PLL3_CR, 10, 26000000, MPMU_PLL3_CTRL1, 25}, +}; + +static struct mmp_param_fixed_factor_clk fixed_factor_clks[] = { + {MMP2_CLK_PLL1_2, "pll1_2", "pll1", 1, 2, 0}, + {MMP2_CLK_PLL1_4, "pll1_4", "pll1_2", 1, 2, 0}, + {MMP2_CLK_PLL1_8, "pll1_8", "pll1_4", 1, 2, 0}, + {MMP2_CLK_PLL1_16, "pll1_16", "pll1_8", 1, 2, 0}, + {MMP2_CLK_PLL1_20, "pll1_20", "pll1_4", 1, 5, 0}, + {MMP2_CLK_PLL1_3, "pll1_3", "pll1", 1, 3, 0}, + {MMP2_CLK_PLL1_6, "pll1_6", "pll1_3", 1, 2, 0}, + {MMP2_CLK_PLL1_12, "pll1_12", "pll1_6", 1, 2, 0}, + {MMP2_CLK_PLL2_2, "pll2_2", "pll2", 1, 2, 0}, + {MMP2_CLK_PLL2_4, "pll2_4", "pll2_2", 1, 2, 0}, + {MMP2_CLK_PLL2_8, "pll2_8", "pll2_4", 1, 2, 0}, + {MMP2_CLK_PLL2_16, "pll2_16", "pll2_8", 1, 2, 0}, + {MMP2_CLK_PLL2_3, "pll2_3", "pll2", 1, 3, 0}, + {MMP2_CLK_PLL2_6, "pll2_6", "pll2_3", 1, 2, 0}, + {MMP2_CLK_PLL2_12, "pll2_12", "pll2_6", 1, 2, 0}, + {MMP2_CLK_VCTCXO_2, "vctcxo_2", "vctcxo", 1, 2, 0}, + {MMP2_CLK_VCTCXO_4, "vctcxo_4", "vctcxo_2", 1, 2, 0}, +}; + +static struct mmp_clk_factor_masks uart_factor_masks = { + .factor = 2, + .num_mask = 0x1fff, + .den_mask = 0x1fff, + .num_shift = 16, + .den_shift = 0, +}; + +static struct u32_fract uart_factor_tbl[] = { + { .numerator = 8125, .denominator = 1536 }, /* 14.745MHZ */ + { .numerator = 3521, .denominator = 689 }, /* 19.23MHZ */ +}; + +static struct mmp_clk_factor_masks i2s_factor_masks = { + .factor = 2, + .num_mask = 0x7fff, + .den_mask = 0x1fff, + .num_shift = 0, + .den_shift = 15, + .enable_mask = 0xd0000000, +}; + +static struct u32_fract i2s_factor_tbl[] = { + { .numerator = 24868, .denominator = 511 }, /* 2.0480 MHz */ + { .numerator = 28003, .denominator = 793 }, /* 2.8224 MHz */ + { .numerator = 24941, .denominator = 1025 }, /* 4.0960 MHz */ + { .numerator = 28003, .denominator = 1586 }, /* 5.6448 MHz */ + { .numerator = 31158, .denominator = 2561 }, /* 8.1920 MHz */ + { .numerator = 16288, .denominator = 1845 }, /* 11.2896 MHz */ + { .numerator = 20772, .denominator = 2561 }, /* 12.2880 MHz */ + { .numerator = 8144, .denominator = 1845 }, /* 22.5792 MHz */ + { .numerator = 10386, .denominator = 2561 }, /* 24.5760 MHz */ +}; + +static DEFINE_SPINLOCK(acgr_lock); + +static struct mmp_param_gate_clk mpmu_gate_clks[] = { + {MMP2_CLK_I2S0, "i2s0_clk", "i2s0_pll", CLK_SET_RATE_PARENT, MPMU_ACGR, 0x200000, 0x200000, 0x0, 0, &acgr_lock}, + {MMP2_CLK_I2S1, "i2s1_clk", "i2s1_pll", CLK_SET_RATE_PARENT, MPMU_ACGR, 0x100000, 0x100000, 0x0, 0, &acgr_lock}, +}; + +static void mmp2_main_clk_init(struct mmp2_clk_unit *pxa_unit) +{ + struct clk *clk; + struct mmp_clk_unit *unit = &pxa_unit->unit; + + mmp_register_fixed_rate_clks(unit, fixed_rate_clks, + ARRAY_SIZE(fixed_rate_clks)); + + if (pxa_unit->model == CLK_MODEL_MMP3) { + mmp_register_pll_clks(unit, mmp3_pll_clks, + pxa_unit->mpmu_base, + ARRAY_SIZE(mmp3_pll_clks)); + } else { + mmp_register_pll_clks(unit, pll_clks, + pxa_unit->mpmu_base, + ARRAY_SIZE(pll_clks)); + } + + mmp_register_fixed_factor_clks(unit, fixed_factor_clks, + ARRAY_SIZE(fixed_factor_clks)); + + clk = mmp_clk_register_factor("uart_pll", "pll1_4", + CLK_SET_RATE_PARENT, + pxa_unit->mpmu_base + MPMU_UART_PLL, + &uart_factor_masks, uart_factor_tbl, + ARRAY_SIZE(uart_factor_tbl), NULL); + mmp_clk_add(unit, MMP2_CLK_UART_PLL, clk); + + mmp_clk_register_factor("i2s0_pll", "pll1_4", + CLK_SET_RATE_PARENT, + pxa_unit->mpmu_base + MPMU_I2S0_PLL, + &i2s_factor_masks, i2s_factor_tbl, + ARRAY_SIZE(i2s_factor_tbl), NULL); + mmp_clk_register_factor("i2s1_pll", "pll1_4", + CLK_SET_RATE_PARENT, + pxa_unit->mpmu_base + MPMU_I2S1_PLL, + &i2s_factor_masks, i2s_factor_tbl, + ARRAY_SIZE(i2s_factor_tbl), NULL); + + mmp_register_gate_clks(unit, mpmu_gate_clks, pxa_unit->mpmu_base, + ARRAY_SIZE(mpmu_gate_clks)); +} + +static DEFINE_SPINLOCK(uart0_lock); +static DEFINE_SPINLOCK(uart1_lock); +static DEFINE_SPINLOCK(uart2_lock); +static const char * const uart_parent_names[] = {"uart_pll", "vctcxo"}; + +static DEFINE_SPINLOCK(ssp0_lock); +static DEFINE_SPINLOCK(ssp1_lock); +static DEFINE_SPINLOCK(ssp2_lock); +static DEFINE_SPINLOCK(ssp3_lock); +static const char * const ssp_parent_names[] = {"vctcxo_4", "vctcxo_2", "vctcxo", "pll1_16"}; + +static DEFINE_SPINLOCK(timer_lock); +static const char * const timer_parent_names[] = {"clk32", "vctcxo_4", "vctcxo_2", "vctcxo"}; + +static DEFINE_SPINLOCK(reset_lock); + +static struct mmp_param_mux_clk apbc_mux_clks[] = { + {0, "uart0_mux", uart_parent_names, ARRAY_SIZE(uart_parent_names), CLK_SET_RATE_PARENT, APBC_UART0, 4, 3, 0, &uart0_lock}, + {0, "uart1_mux", uart_parent_names, ARRAY_SIZE(uart_parent_names), CLK_SET_RATE_PARENT, APBC_UART1, 4, 3, 0, &uart1_lock}, + {0, "uart2_mux", uart_parent_names, ARRAY_SIZE(uart_parent_names), CLK_SET_RATE_PARENT, APBC_UART2, 4, 3, 0, &uart2_lock}, + {0, "uart3_mux", uart_parent_names, ARRAY_SIZE(uart_parent_names), CLK_SET_RATE_PARENT, APBC_UART3, 4, 3, 0, &uart2_lock}, + {0, "ssp0_mux", ssp_parent_names, ARRAY_SIZE(ssp_parent_names), CLK_SET_RATE_PARENT, APBC_SSP0, 4, 3, 0, &ssp0_lock}, + {0, "ssp1_mux", ssp_parent_names, ARRAY_SIZE(ssp_parent_names), CLK_SET_RATE_PARENT, APBC_SSP1, 4, 3, 0, &ssp1_lock}, + {0, "ssp2_mux", ssp_parent_names, ARRAY_SIZE(ssp_parent_names), CLK_SET_RATE_PARENT, APBC_SSP2, 4, 3, 0, &ssp2_lock}, + {0, "ssp3_mux", ssp_parent_names, ARRAY_SIZE(ssp_parent_names), CLK_SET_RATE_PARENT, APBC_SSP3, 4, 3, 0, &ssp3_lock}, + {0, "timer_mux", timer_parent_names, ARRAY_SIZE(timer_parent_names), CLK_SET_RATE_PARENT, APBC_TIMER, 4, 3, 0, &timer_lock}, +}; + +static struct mmp_param_gate_clk apbc_gate_clks[] = { + {MMP2_CLK_TWSI0, "twsi0_clk", "vctcxo", CLK_SET_RATE_PARENT, APBC_TWSI0, 0x7, 0x3, 0x0, 0, &reset_lock}, + {MMP2_CLK_TWSI1, "twsi1_clk", "vctcxo", CLK_SET_RATE_PARENT, APBC_TWSI1, 0x7, 0x3, 0x0, 0, &reset_lock}, + {MMP2_CLK_TWSI2, "twsi2_clk", "vctcxo", CLK_SET_RATE_PARENT, APBC_TWSI2, 0x7, 0x3, 0x0, 0, &reset_lock}, + {MMP2_CLK_TWSI3, "twsi3_clk", "vctcxo", CLK_SET_RATE_PARENT, APBC_TWSI3, 0x7, 0x3, 0x0, 0, &reset_lock}, + {MMP2_CLK_TWSI4, "twsi4_clk", "vctcxo", CLK_SET_RATE_PARENT, APBC_TWSI4, 0x7, 0x3, 0x0, 0, &reset_lock}, + {MMP2_CLK_TWSI5, "twsi5_clk", "vctcxo", CLK_SET_RATE_PARENT, APBC_TWSI5, 0x7, 0x3, 0x0, 0, &reset_lock}, + {MMP2_CLK_GPIO, "gpio_clk", "vctcxo", CLK_SET_RATE_PARENT, APBC_GPIO, 0x7, 0x3, 0x0, 0, &reset_lock}, + {MMP2_CLK_KPC, "kpc_clk", "clk32", CLK_SET_RATE_PARENT, APBC_KPC, 0x7, 0x3, 0x0, MMP_CLK_GATE_NEED_DELAY, &reset_lock}, + {MMP2_CLK_RTC, "rtc_clk", "clk32", CLK_SET_RATE_PARENT, APBC_RTC, 0x87, 0x83, 0x0, MMP_CLK_GATE_NEED_DELAY, &reset_lock}, + {MMP2_CLK_PWM0, "pwm0_clk", "pll1_48", CLK_SET_RATE_PARENT, APBC_PWM0, 0x7, 0x3, 0x0, 0, &reset_lock}, + {MMP2_CLK_PWM1, "pwm1_clk", "pll1_48", CLK_SET_RATE_PARENT, APBC_PWM1, 0x7, 0x3, 0x0, 0, &reset_lock}, + {MMP2_CLK_PWM2, "pwm2_clk", "pll1_48", CLK_SET_RATE_PARENT, APBC_PWM2, 0x7, 0x3, 0x0, 0, &reset_lock}, + {MMP2_CLK_PWM3, "pwm3_clk", "pll1_48", CLK_SET_RATE_PARENT, APBC_PWM3, 0x7, 0x3, 0x0, 0, &reset_lock}, + /* The gate clocks has mux parent. */ + {MMP2_CLK_UART0, "uart0_clk", "uart0_mux", CLK_SET_RATE_PARENT, APBC_UART0, 0x7, 0x3, 0x0, 0, &uart0_lock}, + {MMP2_CLK_UART1, "uart1_clk", "uart1_mux", CLK_SET_RATE_PARENT, APBC_UART1, 0x7, 0x3, 0x0, 0, &uart1_lock}, + {MMP2_CLK_UART2, "uart2_clk", "uart2_mux", CLK_SET_RATE_PARENT, APBC_UART2, 0x7, 0x3, 0x0, 0, &uart2_lock}, + {MMP2_CLK_UART3, "uart3_clk", "uart3_mux", CLK_SET_RATE_PARENT, APBC_UART3, 0x7, 0x3, 0x0, 0, &uart2_lock}, + {MMP2_CLK_SSP0, "ssp0_clk", "ssp0_mux", CLK_SET_RATE_PARENT, APBC_SSP0, 0x7, 0x3, 0x0, 0, &ssp0_lock}, + {MMP2_CLK_SSP1, "ssp1_clk", "ssp1_mux", CLK_SET_RATE_PARENT, APBC_SSP1, 0x7, 0x3, 0x0, 0, &ssp1_lock}, + {MMP2_CLK_SSP2, "ssp2_clk", "ssp2_mux", CLK_SET_RATE_PARENT, APBC_SSP2, 0x7, 0x3, 0x0, 0, &ssp2_lock}, + {MMP2_CLK_SSP3, "ssp3_clk", "ssp3_mux", CLK_SET_RATE_PARENT, APBC_SSP3, 0x7, 0x3, 0x0, 0, &ssp3_lock}, + {MMP2_CLK_TIMER, "timer_clk", "timer_mux", CLK_SET_RATE_PARENT, APBC_TIMER, 0x7, 0x3, 0x0, 0, &timer_lock}, + {MMP2_CLK_THERMAL0, "thermal0_clk", "vctcxo", CLK_SET_RATE_PARENT, APBC_THERMAL0, 0x7, 0x3, 0x0, MMP_CLK_GATE_NEED_DELAY, &reset_lock}, +}; + +static struct mmp_param_gate_clk mmp3_apbc_gate_clks[] = { + {MMP3_CLK_THERMAL1, "thermal1_clk", "vctcxo", CLK_SET_RATE_PARENT, APBC_THERMAL1, 0x7, 0x3, 0x0, MMP_CLK_GATE_NEED_DELAY, &reset_lock}, + {MMP3_CLK_THERMAL2, "thermal2_clk", "vctcxo", CLK_SET_RATE_PARENT, APBC_THERMAL2, 0x7, 0x3, 0x0, MMP_CLK_GATE_NEED_DELAY, &reset_lock}, + {MMP3_CLK_THERMAL3, "thermal3_clk", "vctcxo", CLK_SET_RATE_PARENT, APBC_THERMAL3, 0x7, 0x3, 0x0, MMP_CLK_GATE_NEED_DELAY, &reset_lock}, +}; + +static void mmp2_apb_periph_clk_init(struct mmp2_clk_unit *pxa_unit) +{ + struct mmp_clk_unit *unit = &pxa_unit->unit; + + mmp_register_mux_clks(unit, apbc_mux_clks, pxa_unit->apbc_base, + ARRAY_SIZE(apbc_mux_clks)); + + mmp_register_gate_clks(unit, apbc_gate_clks, pxa_unit->apbc_base, + ARRAY_SIZE(apbc_gate_clks)); + + if (pxa_unit->model == CLK_MODEL_MMP3) { + mmp_register_gate_clks(unit, mmp3_apbc_gate_clks, pxa_unit->apbc_base, + ARRAY_SIZE(mmp3_apbc_gate_clks)); + } +} + +static DEFINE_SPINLOCK(sdh_lock); +static const char * const sdh_parent_names[] = {"pll1_4", "pll2", "usb_pll", "pll1"}; +static struct mmp_clk_mix_config sdh_mix_config = { + .reg_info = DEFINE_MIX_REG_INFO(4, 10, 2, 8, 32), +}; + +static DEFINE_SPINLOCK(usb_lock); +static DEFINE_SPINLOCK(usbhsic0_lock); +static DEFINE_SPINLOCK(usbhsic1_lock); + +static DEFINE_SPINLOCK(disp0_lock); +static DEFINE_SPINLOCK(disp1_lock); +static const char * const disp_parent_names[] = {"pll1", "pll1_16", "pll2", "vctcxo"}; + +static DEFINE_SPINLOCK(ccic0_lock); +static DEFINE_SPINLOCK(ccic1_lock); +static const char * const ccic_parent_names[] = {"pll1_2", "pll1_16", "vctcxo"}; + +static DEFINE_SPINLOCK(gpu_lock); +static const char * const mmp2_gpu_gc_parent_names[] = {"pll1_2", "pll1_3", "pll2_2", "pll2_3", "pll2", "usb_pll"}; +static const u32 mmp2_gpu_gc_parent_table[] = { 0x0000, 0x0040, 0x0080, 0x00c0, 0x1000, 0x1040 }; +static const char * const mmp2_gpu_bus_parent_names[] = {"pll1_4", "pll2", "pll2_2", "usb_pll"}; +static const u32 mmp2_gpu_bus_parent_table[] = { 0x0000, 0x0020, 0x0030, 0x4020 }; +static const char * const mmp3_gpu_bus_parent_names[] = {"pll1_4", "pll1_6", "pll1_2", "pll2_2"}; +static const char * const mmp3_gpu_gc_parent_names[] = {"pll1", "pll2", "pll1_p", "pll2_p"}; + +static DEFINE_SPINLOCK(audio_lock); + +static struct mmp_clk_mix_config ccic0_mix_config = { + .reg_info = DEFINE_MIX_REG_INFO(4, 17, 2, 6, 32), +}; +static struct mmp_clk_mix_config ccic1_mix_config = { + .reg_info = DEFINE_MIX_REG_INFO(4, 16, 2, 6, 32), +}; + +static struct mmp_param_mux_clk apmu_mux_clks[] = { + {MMP2_CLK_DISP0_MUX, "disp0_mux", disp_parent_names, ARRAY_SIZE(disp_parent_names), CLK_SET_RATE_PARENT, APMU_DISP0, 6, 2, 0, &disp0_lock}, + {MMP2_CLK_DISP1_MUX, "disp1_mux", disp_parent_names, ARRAY_SIZE(disp_parent_names), CLK_SET_RATE_PARENT, APMU_DISP1, 6, 2, 0, &disp1_lock}, +}; + +static struct mmp_param_mux_clk mmp3_apmu_mux_clks[] = { + {0, "gpu_bus_mux", mmp3_gpu_bus_parent_names, ARRAY_SIZE(mmp3_gpu_bus_parent_names), + CLK_SET_RATE_PARENT, APMU_GPU, 4, 2, 0, &gpu_lock}, + {0, "gpu_3d_mux", mmp3_gpu_gc_parent_names, ARRAY_SIZE(mmp3_gpu_gc_parent_names), + CLK_SET_RATE_PARENT, APMU_GPU, 6, 2, 0, &gpu_lock}, + {0, "gpu_2d_mux", mmp3_gpu_gc_parent_names, ARRAY_SIZE(mmp3_gpu_gc_parent_names), + CLK_SET_RATE_PARENT, APMU_GPU, 12, 2, 0, &gpu_lock}, +}; + +static struct mmp_param_div_clk apmu_div_clks[] = { + {0, "disp0_div", "disp0_mux", CLK_SET_RATE_PARENT, APMU_DISP0, 8, 4, CLK_DIVIDER_ONE_BASED, &disp0_lock}, + {0, "disp0_sphy_div", "disp0_mux", CLK_SET_RATE_PARENT, APMU_DISP0, 15, 5, 0, &disp0_lock}, + {0, "disp1_div", "disp1_mux", CLK_SET_RATE_PARENT, APMU_DISP1, 8, 4, CLK_DIVIDER_ONE_BASED, &disp1_lock}, + {0, "ccic0_sphy_div", "ccic0_mix_clk", CLK_SET_RATE_PARENT, APMU_CCIC0, 10, 5, 0, &ccic0_lock}, + {0, "ccic1_sphy_div", "ccic1_mix_clk", CLK_SET_RATE_PARENT, APMU_CCIC1, 10, 5, 0, &ccic1_lock}, +}; + +static struct mmp_param_div_clk mmp3_apmu_div_clks[] = { + {0, "gpu_3d_div", "gpu_3d_mux", CLK_SET_RATE_PARENT, APMU_GPU, 24, 4, 0, &gpu_lock}, + {0, "gpu_2d_div", "gpu_2d_mux", CLK_SET_RATE_PARENT, APMU_GPU, 28, 4, 0, &gpu_lock}, +}; + +static struct mmp_param_gate_clk apmu_gate_clks[] = { + {MMP2_CLK_USB, "usb_clk", "usb_pll", 0, APMU_USB, 0x9, 0x9, 0x0, 0, &usb_lock}, + {MMP2_CLK_USBHSIC0, "usbhsic0_clk", "usb_pll", 0, APMU_USBHSIC0, 0x1b, 0x1b, 0x0, 0, &usbhsic0_lock}, + {MMP2_CLK_USBHSIC1, "usbhsic1_clk", "usb_pll", 0, APMU_USBHSIC1, 0x1b, 0x1b, 0x0, 0, &usbhsic1_lock}, + /* The gate clocks has mux parent. */ + {MMP2_CLK_SDH0, "sdh0_clk", "sdh_mix_clk", CLK_SET_RATE_PARENT, APMU_SDH0, 0x1b, 0x1b, 0x0, 0, &sdh_lock}, + {MMP2_CLK_SDH1, "sdh1_clk", "sdh_mix_clk", CLK_SET_RATE_PARENT, APMU_SDH1, 0x1b, 0x1b, 0x0, 0, &sdh_lock}, + {MMP2_CLK_SDH2, "sdh2_clk", "sdh_mix_clk", CLK_SET_RATE_PARENT, APMU_SDH2, 0x1b, 0x1b, 0x0, 0, &sdh_lock}, + {MMP2_CLK_SDH3, "sdh3_clk", "sdh_mix_clk", CLK_SET_RATE_PARENT, APMU_SDH3, 0x1b, 0x1b, 0x0, 0, &sdh_lock}, + {MMP2_CLK_DISP0, "disp0_clk", "disp0_div", CLK_SET_RATE_PARENT, APMU_DISP0, 0x12, 0x12, 0x0, 0, &disp0_lock}, + {MMP2_CLK_DISP0_LCDC, "disp0_lcdc_clk", "disp0_mux", CLK_SET_RATE_PARENT, APMU_DISP0, 0x09, 0x09, 0x0, 0, &disp0_lock}, + {MMP2_CLK_DISP0_SPHY, "disp0_sphy_clk", "disp0_sphy_div", CLK_SET_RATE_PARENT, APMU_DISP0, 0x1024, 0x1024, 0x0, 0, &disp0_lock}, + {MMP2_CLK_DISP1, "disp1_clk", "disp1_div", CLK_SET_RATE_PARENT, APMU_DISP1, 0x09, 0x09, 0x0, 0, &disp1_lock}, + {MMP2_CLK_CCIC_ARBITER, "ccic_arbiter", "vctcxo", CLK_SET_RATE_PARENT, APMU_CCIC0, 0x1800, 0x1800, 0x0, 0, &ccic0_lock}, + {MMP2_CLK_CCIC0, "ccic0_clk", "ccic0_mix_clk", CLK_SET_RATE_PARENT, APMU_CCIC0, 0x1b, 0x1b, 0x0, 0, &ccic0_lock}, + {MMP2_CLK_CCIC0_PHY, "ccic0_phy_clk", "ccic0_mix_clk", CLK_SET_RATE_PARENT, APMU_CCIC0, 0x24, 0x24, 0x0, 0, &ccic0_lock}, + {MMP2_CLK_CCIC0_SPHY, "ccic0_sphy_clk", "ccic0_sphy_div", CLK_SET_RATE_PARENT, APMU_CCIC0, 0x300, 0x300, 0x0, 0, &ccic0_lock}, + {MMP2_CLK_CCIC1, "ccic1_clk", "ccic1_mix_clk", CLK_SET_RATE_PARENT, APMU_CCIC1, 0x1b, 0x1b, 0x0, 0, &ccic1_lock}, + {MMP2_CLK_CCIC1_PHY, "ccic1_phy_clk", "ccic1_mix_clk", CLK_SET_RATE_PARENT, APMU_CCIC1, 0x24, 0x24, 0x0, 0, &ccic1_lock}, + {MMP2_CLK_CCIC1_SPHY, "ccic1_sphy_clk", "ccic1_sphy_div", CLK_SET_RATE_PARENT, APMU_CCIC1, 0x300, 0x300, 0x0, 0, &ccic1_lock}, + {MMP2_CLK_GPU_BUS, "gpu_bus_clk", "gpu_bus_mux", CLK_SET_RATE_PARENT, APMU_GPU, 0xa, 0xa, 0x0, MMP_CLK_GATE_NEED_DELAY, &gpu_lock}, + {MMP2_CLK_AUDIO, "audio_clk", "audio_mix_clk", CLK_SET_RATE_PARENT, APMU_AUDIO, 0x12, 0x12, 0x0, 0, &audio_lock}, +}; + +static struct mmp_param_gate_clk mmp2_apmu_gate_clks[] = { + {MMP2_CLK_GPU_3D, "gpu_3d_clk", "gpu_3d_mux", CLK_SET_RATE_PARENT, APMU_GPU, 0x5, 0x5, 0x0, MMP_CLK_GATE_NEED_DELAY, &gpu_lock}, +}; + +static struct mmp_param_gate_clk mmp3_apmu_gate_clks[] = { + {MMP3_CLK_SDH4, "sdh4_clk", "sdh_mix_clk", CLK_SET_RATE_PARENT, APMU_SDH4, 0x1b, 0x1b, 0x0, 0, &sdh_lock}, + {MMP3_CLK_GPU_3D, "gpu_3d_clk", "gpu_3d_div", CLK_SET_RATE_PARENT, APMU_GPU, 0x5, 0x5, 0x0, MMP_CLK_GATE_NEED_DELAY, &gpu_lock}, + {MMP3_CLK_GPU_2D, "gpu_2d_clk", "gpu_2d_div", CLK_SET_RATE_PARENT, APMU_GPU, 0x1c0000, 0x1c0000, 0x0, MMP_CLK_GATE_NEED_DELAY, &gpu_lock}, +}; + +static void mmp2_axi_periph_clk_init(struct mmp2_clk_unit *pxa_unit) +{ + struct clk *clk; + struct mmp_clk_unit *unit = &pxa_unit->unit; + + sdh_mix_config.reg_info.reg_clk_ctrl = pxa_unit->apmu_base + APMU_SDH0; + clk = mmp_clk_register_mix(NULL, "sdh_mix_clk", sdh_parent_names, + ARRAY_SIZE(sdh_parent_names), + CLK_SET_RATE_PARENT, + &sdh_mix_config, &sdh_lock); + + ccic0_mix_config.reg_info.reg_clk_ctrl = pxa_unit->apmu_base + APMU_CCIC0; + clk = mmp_clk_register_mix(NULL, "ccic0_mix_clk", ccic_parent_names, + ARRAY_SIZE(ccic_parent_names), + CLK_SET_RATE_PARENT, + &ccic0_mix_config, &ccic0_lock); + mmp_clk_add(unit, MMP2_CLK_CCIC0_MIX, clk); + + ccic1_mix_config.reg_info.reg_clk_ctrl = pxa_unit->apmu_base + APMU_CCIC1; + clk = mmp_clk_register_mix(NULL, "ccic1_mix_clk", ccic_parent_names, + ARRAY_SIZE(ccic_parent_names), + CLK_SET_RATE_PARENT, + &ccic1_mix_config, &ccic1_lock); + mmp_clk_add(unit, MMP2_CLK_CCIC1_MIX, clk); + + mmp_register_mux_clks(unit, apmu_mux_clks, pxa_unit->apmu_base, + ARRAY_SIZE(apmu_mux_clks)); + + mmp_register_div_clks(unit, apmu_div_clks, pxa_unit->apmu_base, + ARRAY_SIZE(apmu_div_clks)); + + mmp_register_gate_clks(unit, apmu_gate_clks, pxa_unit->apmu_base, + ARRAY_SIZE(apmu_gate_clks)); + + if (pxa_unit->model == CLK_MODEL_MMP3) { + mmp_register_mux_clks(unit, mmp3_apmu_mux_clks, pxa_unit->apmu_base, + ARRAY_SIZE(mmp3_apmu_mux_clks)); + + mmp_register_div_clks(unit, mmp3_apmu_div_clks, pxa_unit->apmu_base, + ARRAY_SIZE(mmp3_apmu_div_clks)); + + mmp_register_gate_clks(unit, mmp3_apmu_gate_clks, pxa_unit->apmu_base, + ARRAY_SIZE(mmp3_apmu_gate_clks)); + } else { + clk_register_mux_table(NULL, "gpu_3d_mux", mmp2_gpu_gc_parent_names, + ARRAY_SIZE(mmp2_gpu_gc_parent_names), + CLK_SET_RATE_PARENT, + pxa_unit->apmu_base + APMU_GPU, + 0, 0x10c0, 0, + mmp2_gpu_gc_parent_table, &gpu_lock); + + clk_register_mux_table(NULL, "gpu_bus_mux", mmp2_gpu_bus_parent_names, + ARRAY_SIZE(mmp2_gpu_bus_parent_names), + CLK_SET_RATE_PARENT, + pxa_unit->apmu_base + APMU_GPU, + 0, 0x4030, 0, + mmp2_gpu_bus_parent_table, &gpu_lock); + + mmp_register_gate_clks(unit, mmp2_apmu_gate_clks, pxa_unit->apmu_base, + ARRAY_SIZE(mmp2_apmu_gate_clks)); + } +} + +static void mmp2_clk_reset_init(struct device_node *np, + struct mmp2_clk_unit *pxa_unit) +{ + struct mmp_clk_reset_cell *cells; + int i, nr_resets; + + nr_resets = ARRAY_SIZE(apbc_gate_clks); + cells = kzalloc_objs(*cells, nr_resets); + if (!cells) + return; + + for (i = 0; i < nr_resets; i++) { + cells[i].clk_id = apbc_gate_clks[i].id; + cells[i].reg = pxa_unit->apbc_base + apbc_gate_clks[i].offset; + cells[i].flags = 0; + cells[i].lock = apbc_gate_clks[i].lock; + cells[i].bits = 0x4; + } + + mmp_clk_reset_register(np, cells, nr_resets); +} + +static void mmp2_pm_domain_init(struct device_node *np, + struct mmp2_clk_unit *pxa_unit) +{ + if (pxa_unit->model == CLK_MODEL_MMP3) { + pxa_unit->pm_domains[MMP2_POWER_DOMAIN_GPU] + = mmp_pm_domain_register("gpu", + pxa_unit->apmu_base + APMU_GPU, + 0x0600, 0x40003, 0x18000c, 0, &gpu_lock); + } else { + pxa_unit->pm_domains[MMP2_POWER_DOMAIN_GPU] + = mmp_pm_domain_register("gpu", + pxa_unit->apmu_base + APMU_GPU, + 0x8600, 0x00003, 0x00000c, + MMP_PM_DOMAIN_NO_DISABLE, &gpu_lock); + } + pxa_unit->pd_data.num_domains++; + + pxa_unit->pm_domains[MMP2_POWER_DOMAIN_AUDIO] + = mmp_pm_domain_register("audio", + pxa_unit->apmu_base + APMU_AUDIO, + 0x600, 0x2, 0, 0, &audio_lock); + pxa_unit->pd_data.num_domains++; + + if (pxa_unit->model == CLK_MODEL_MMP3) { + pxa_unit->pm_domains[MMP3_POWER_DOMAIN_CAMERA] + = mmp_pm_domain_register("camera", + pxa_unit->apmu_base + APMU_CAMERA, + 0x600, 0, 0, 0, NULL); + pxa_unit->pd_data.num_domains++; + } + + pxa_unit->pd_data.domains = pxa_unit->pm_domains; + of_genpd_add_provider_onecell(np, &pxa_unit->pd_data); +} + +static void __init mmp2_clk_init(struct device_node *np) +{ + struct mmp2_clk_unit *pxa_unit; + + pxa_unit = kzalloc_obj(*pxa_unit); + if (!pxa_unit) + return; + + if (of_device_is_compatible(np, "marvell,mmp3-clock")) + pxa_unit->model = CLK_MODEL_MMP3; + else + pxa_unit->model = CLK_MODEL_MMP2; + + pxa_unit->mpmu_base = of_iomap(np, 0); + if (!pxa_unit->mpmu_base) { + pr_err("failed to map mpmu registers\n"); + goto free_memory; + } + + pxa_unit->apmu_base = of_iomap(np, 1); + if (!pxa_unit->apmu_base) { + pr_err("failed to map apmu registers\n"); + goto unmap_mpmu_region; + } + + pxa_unit->apbc_base = of_iomap(np, 2); + if (!pxa_unit->apbc_base) { + pr_err("failed to map apbc registers\n"); + goto unmap_apmu_region; + } + + mmp2_pm_domain_init(np, pxa_unit); + + mmp_clk_init(np, &pxa_unit->unit, NR_CLKS); + + mmp2_main_clk_init(pxa_unit); + + mmp2_apb_periph_clk_init(pxa_unit); + + mmp2_axi_periph_clk_init(pxa_unit); + + mmp2_clk_reset_init(np, pxa_unit); + + return; + +unmap_apmu_region: + iounmap(pxa_unit->apmu_base); +unmap_mpmu_region: + iounmap(pxa_unit->mpmu_base); +free_memory: + kfree(pxa_unit); +} + +CLK_OF_DECLARE(mmp2_clk, "marvell,mmp2-clock", mmp2_clk_init); +CLK_OF_DECLARE(mmp3_clk, "marvell,mmp3-clock", mmp2_clk_init); diff --git a/drivers/clk/mmp/clk-of-pxa168.c b/drivers/clk/mmp/clk-of-pxa168.c new file mode 100644 index 000000000..e3d6f4b40 --- /dev/null +++ b/drivers/clk/mmp/clk-of-pxa168.c @@ -0,0 +1,340 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * pxa168 clock framework source file + * + * Copyright (C) 2012 Marvell + * Chao Xie <xiechao.mail@gmail.com> + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/spinlock.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/of_address.h> + +#include <dt-bindings/clock/marvell,pxa168.h> + +#include "clk.h" +#include "reset.h" + +#define APBC_UART0 0x0 +#define APBC_UART1 0x4 +#define APBC_GPIO 0x8 +#define APBC_PWM0 0xc +#define APBC_PWM1 0x10 +#define APBC_PWM2 0x14 +#define APBC_PWM3 0x18 +#define APBC_RTC 0x28 +#define APBC_TWSI0 0x2c +#define APBC_KPC 0x30 +#define APBC_TIMER 0x34 +#define APBC_AIB 0x3c +#define APBC_SW_JTAG 0x40 +#define APBC_ONEWIRE 0x48 +#define APBC_TWSI1 0x6c +#define APBC_UART2 0x70 +#define APBC_AC97 0x84 +#define APBC_SSP0 0x81c +#define APBC_SSP1 0x820 +#define APBC_SSP2 0x84c +#define APBC_SSP3 0x858 +#define APBC_SSP4 0x85c +#define APMU_DISP0 0x4c +#define APMU_CCIC0 0x50 +#define APMU_SDH0 0x54 +#define APMU_SDH1 0x58 +#define APMU_USB 0x5c +#define APMU_DFC 0x60 +#define APMU_DMA 0x64 +#define APMU_BUS 0x6c +#define APMU_GC 0xcc +#define APMU_SMC 0xd4 +#define APMU_XD 0xdc +#define APMU_SDH2 0xe0 +#define APMU_SDH3 0xe4 +#define APMU_CF 0xf0 +#define APMU_MSP 0xf4 +#define APMU_CMU 0xf8 +#define APMU_FE 0xfc +#define APMU_PCIE 0x100 +#define APMU_EPD 0x104 +#define MPMU_UART_PLL 0x14 + +#define NR_CLKS 200 + +struct pxa168_clk_unit { + struct mmp_clk_unit unit; + void __iomem *mpmu_base; + void __iomem *apmu_base; + void __iomem *apbc_base; +}; + +static struct mmp_param_fixed_rate_clk fixed_rate_clks[] = { + {PXA168_CLK_CLK32, "clk32", NULL, 0, 32768}, + {PXA168_CLK_VCTCXO, "vctcxo", NULL, 0, 26000000}, + {PXA168_CLK_PLL1, "pll1", NULL, 0, 624000000}, + {PXA168_CLK_USB_PLL, "usb_pll", NULL, 0, 480000000}, +}; + +static struct mmp_param_fixed_factor_clk fixed_factor_clks[] = { + {PXA168_CLK_PLL1_2, "pll1_2", "pll1", 1, 2, 0}, + {PXA168_CLK_PLL1_4, "pll1_4", "pll1_2", 1, 2, 0}, + {PXA168_CLK_PLL1_8, "pll1_8", "pll1_4", 1, 2, 0}, + {PXA168_CLK_PLL1_16, "pll1_16", "pll1_8", 1, 2, 0}, + {PXA168_CLK_PLL1_6, "pll1_6", "pll1_2", 1, 3, 0}, + {PXA168_CLK_PLL1_12, "pll1_12", "pll1_6", 1, 2, 0}, + {PXA168_CLK_PLL1_24, "pll1_24", "pll1_12", 1, 2, 0}, + {PXA168_CLK_PLL1_48, "pll1_48", "pll1_24", 1, 2, 0}, + {PXA168_CLK_PLL1_96, "pll1_96", "pll1_48", 1, 2, 0}, + {PXA168_CLK_PLL1_192, "pll1_192", "pll1_96", 1, 2, 0}, + {PXA168_CLK_PLL1_13, "pll1_13", "pll1", 1, 13, 0}, + {PXA168_CLK_PLL1_13_1_5, "pll1_13_1_5", "pll1_13", 1, 5, 0}, + {PXA168_CLK_PLL1_2_1_5, "pll1_2_1_5", "pll1_2", 1, 5, 0}, + {PXA168_CLK_PLL1_3_16, "pll1_3_16", "pll1", 3, 16, 0}, + {PXA168_CLK_PLL1_2_1_10, "pll1_2_1_10", "pll1_2", 1, 10, 0}, + {PXA168_CLK_PLL1_2_3_16, "pll1_2_3_16", "pll1_2", 3, 16, 0}, + {PXA168_CLK_CLK32_2, "clk32_2", "clk32", 1, 2, 0}, +}; + +static struct mmp_clk_factor_masks uart_factor_masks = { + .factor = 2, + .num_mask = 0x1fff, + .den_mask = 0x1fff, + .num_shift = 16, + .den_shift = 0, +}; + +static struct u32_fract uart_factor_tbl[] = { + { .numerator = 8125, .denominator = 1536 }, /* 14.745MHZ */ +}; + +static void pxa168_pll_init(struct pxa168_clk_unit *pxa_unit) +{ + struct clk *clk; + struct mmp_clk_unit *unit = &pxa_unit->unit; + + mmp_register_fixed_rate_clks(unit, fixed_rate_clks, + ARRAY_SIZE(fixed_rate_clks)); + + mmp_register_fixed_factor_clks(unit, fixed_factor_clks, + ARRAY_SIZE(fixed_factor_clks)); + + clk = mmp_clk_register_factor("uart_pll", "pll1_4", + CLK_SET_RATE_PARENT, + pxa_unit->mpmu_base + MPMU_UART_PLL, + &uart_factor_masks, uart_factor_tbl, + ARRAY_SIZE(uart_factor_tbl), NULL); + mmp_clk_add(unit, PXA168_CLK_UART_PLL, clk); +} + +static DEFINE_SPINLOCK(twsi0_lock); +static DEFINE_SPINLOCK(twsi1_lock); +static const char * const twsi_parent_names[] = {"pll1_2_1_10", "pll1_2_1_5"}; + +static DEFINE_SPINLOCK(kpc_lock); +static const char * const kpc_parent_names[] = {"clk32", "clk32_2", "pll1_24"}; + +static DEFINE_SPINLOCK(pwm0_lock); +static DEFINE_SPINLOCK(pwm1_lock); +static DEFINE_SPINLOCK(pwm2_lock); +static DEFINE_SPINLOCK(pwm3_lock); +static const char * const pwm_parent_names[] = {"pll1_48", "clk32"}; + +static DEFINE_SPINLOCK(uart0_lock); +static DEFINE_SPINLOCK(uart1_lock); +static DEFINE_SPINLOCK(uart2_lock); +static const char * const uart_parent_names[] = {"pll1_2_3_16", "uart_pll"}; + +static DEFINE_SPINLOCK(ssp0_lock); +static DEFINE_SPINLOCK(ssp1_lock); +static DEFINE_SPINLOCK(ssp2_lock); +static DEFINE_SPINLOCK(ssp3_lock); +static DEFINE_SPINLOCK(ssp4_lock); +static const char * const ssp_parent_names[] = {"pll1_96", "pll1_48", "pll1_24", "pll1_12"}; + +static DEFINE_SPINLOCK(timer_lock); +static const char * const timer_parent_names[] = {"pll1_48", "clk32", "pll1_96", "pll1_192"}; + +static DEFINE_SPINLOCK(reset_lock); + +static struct mmp_param_mux_clk apbc_mux_clks[] = { + {0, "twsi0_mux", twsi_parent_names, ARRAY_SIZE(twsi_parent_names), CLK_SET_RATE_PARENT, APBC_TWSI0, 4, 3, 0, &twsi0_lock}, + {0, "twsi1_mux", twsi_parent_names, ARRAY_SIZE(twsi_parent_names), CLK_SET_RATE_PARENT, APBC_TWSI1, 4, 3, 0, &twsi1_lock}, + {0, "kpc_mux", kpc_parent_names, ARRAY_SIZE(kpc_parent_names), CLK_SET_RATE_PARENT, APBC_KPC, 4, 3, 0, &kpc_lock}, + {0, "pwm0_mux", pwm_parent_names, ARRAY_SIZE(pwm_parent_names), CLK_SET_RATE_PARENT, APBC_PWM0, 4, 3, 0, &pwm0_lock}, + {0, "pwm1_mux", pwm_parent_names, ARRAY_SIZE(pwm_parent_names), CLK_SET_RATE_PARENT, APBC_PWM1, 4, 3, 0, &pwm1_lock}, + {0, "pwm2_mux", pwm_parent_names, ARRAY_SIZE(pwm_parent_names), CLK_SET_RATE_PARENT, APBC_PWM2, 4, 3, 0, &pwm2_lock}, + {0, "pwm3_mux", pwm_parent_names, ARRAY_SIZE(pwm_parent_names), CLK_SET_RATE_PARENT, APBC_PWM3, 4, 3, 0, &pwm3_lock}, + {0, "uart0_mux", uart_parent_names, ARRAY_SIZE(uart_parent_names), CLK_SET_RATE_PARENT, APBC_UART0, 4, 3, 0, &uart0_lock}, + {0, "uart1_mux", uart_parent_names, ARRAY_SIZE(uart_parent_names), CLK_SET_RATE_PARENT, APBC_UART1, 4, 3, 0, &uart1_lock}, + {0, "uart2_mux", uart_parent_names, ARRAY_SIZE(uart_parent_names), CLK_SET_RATE_PARENT, APBC_UART2, 4, 3, 0, &uart2_lock}, + {0, "ssp0_mux", ssp_parent_names, ARRAY_SIZE(ssp_parent_names), CLK_SET_RATE_PARENT, APBC_SSP0, 4, 3, 0, &ssp0_lock}, + {0, "ssp1_mux", ssp_parent_names, ARRAY_SIZE(ssp_parent_names), CLK_SET_RATE_PARENT, APBC_SSP1, 4, 3, 0, &ssp1_lock}, + {0, "ssp2_mux", ssp_parent_names, ARRAY_SIZE(ssp_parent_names), CLK_SET_RATE_PARENT, APBC_SSP2, 4, 3, 0, &ssp2_lock}, + {0, "ssp3_mux", ssp_parent_names, ARRAY_SIZE(ssp_parent_names), CLK_SET_RATE_PARENT, APBC_SSP3, 4, 3, 0, &ssp3_lock}, + {0, "ssp4_mux", ssp_parent_names, ARRAY_SIZE(ssp_parent_names), CLK_SET_RATE_PARENT, APBC_SSP4, 4, 3, 0, &ssp4_lock}, + {0, "timer_mux", timer_parent_names, ARRAY_SIZE(timer_parent_names), CLK_SET_RATE_PARENT, APBC_TIMER, 4, 3, 0, &timer_lock}, +}; + +static struct mmp_param_gate_clk apbc_gate_clks[] = { + {PXA168_CLK_TWSI0, "twsi0_clk", "twsi0_mux", CLK_SET_RATE_PARENT, APBC_TWSI0, 0x3, 0x3, 0x0, 0, &twsi0_lock}, + {PXA168_CLK_TWSI1, "twsi1_clk", "twsi1_mux", CLK_SET_RATE_PARENT, APBC_TWSI1, 0x3, 0x3, 0x0, 0, &twsi1_lock}, + {PXA168_CLK_GPIO, "gpio_clk", "vctcxo", CLK_SET_RATE_PARENT, APBC_GPIO, 0x1, 0x1, 0x0, 0, &reset_lock}, + {PXA168_CLK_KPC, "kpc_clk", "kpc_mux", CLK_SET_RATE_PARENT, APBC_KPC, 0x3, 0x3, 0x0, MMP_CLK_GATE_NEED_DELAY, &kpc_lock}, + {PXA168_CLK_RTC, "rtc_clk", "clk32", CLK_SET_RATE_PARENT, APBC_RTC, 0x83, 0x83, 0x0, MMP_CLK_GATE_NEED_DELAY, NULL}, + {PXA168_CLK_PWM0, "pwm0_clk", "pwm0_mux", CLK_SET_RATE_PARENT, APBC_PWM0, 0x3, 0x3, 0x0, 0, &pwm0_lock}, + {PXA168_CLK_PWM1, "pwm1_clk", "pwm1_mux", CLK_SET_RATE_PARENT, APBC_PWM1, 0x3, 0x3, 0x0, 0, &pwm1_lock}, + {PXA168_CLK_PWM2, "pwm2_clk", "pwm2_mux", CLK_SET_RATE_PARENT, APBC_PWM2, 0x3, 0x3, 0x0, 0, &pwm2_lock}, + {PXA168_CLK_PWM3, "pwm3_clk", "pwm3_mux", CLK_SET_RATE_PARENT, APBC_PWM3, 0x3, 0x3, 0x0, 0, &pwm3_lock}, + {PXA168_CLK_UART0, "uart0_clk", "uart0_mux", CLK_SET_RATE_PARENT, APBC_UART0, 0x3, 0x3, 0x0, 0, &uart0_lock}, + {PXA168_CLK_UART1, "uart1_clk", "uart1_mux", CLK_SET_RATE_PARENT, APBC_UART1, 0x3, 0x3, 0x0, 0, &uart1_lock}, + {PXA168_CLK_UART2, "uart2_clk", "uart2_mux", CLK_SET_RATE_PARENT, APBC_UART2, 0x3, 0x3, 0x0, 0, &uart2_lock}, + {PXA168_CLK_SSP0, "ssp0_clk", "ssp0_mux", CLK_SET_RATE_PARENT, APBC_SSP0, 0x3, 0x3, 0x0, 0, &ssp0_lock}, + {PXA168_CLK_SSP1, "ssp1_clk", "ssp1_mux", CLK_SET_RATE_PARENT, APBC_SSP1, 0x3, 0x3, 0x0, 0, &ssp1_lock}, + {PXA168_CLK_SSP2, "ssp2_clk", "ssp2_mux", CLK_SET_RATE_PARENT, APBC_SSP2, 0x3, 0x3, 0x0, 0, &ssp2_lock}, + {PXA168_CLK_SSP3, "ssp3_clk", "ssp3_mux", CLK_SET_RATE_PARENT, APBC_SSP3, 0x3, 0x3, 0x0, 0, &ssp3_lock}, + {PXA168_CLK_SSP4, "ssp4_clk", "ssp4_mux", CLK_SET_RATE_PARENT, APBC_SSP4, 0x3, 0x3, 0x0, 0, &ssp4_lock}, + {PXA168_CLK_TIMER, "timer_clk", "timer_mux", CLK_SET_RATE_PARENT, APBC_TIMER, 0x3, 0x3, 0x0, 0, &timer_lock}, +}; + +static void pxa168_apb_periph_clk_init(struct pxa168_clk_unit *pxa_unit) +{ + struct mmp_clk_unit *unit = &pxa_unit->unit; + + mmp_register_mux_clks(unit, apbc_mux_clks, pxa_unit->apbc_base, + ARRAY_SIZE(apbc_mux_clks)); + + mmp_register_gate_clks(unit, apbc_gate_clks, pxa_unit->apbc_base, + ARRAY_SIZE(apbc_gate_clks)); + +} + +static DEFINE_SPINLOCK(dfc_lock); +static const char * const dfc_parent_names[] = {"pll1_4", "pll1_8"}; + +static DEFINE_SPINLOCK(sdh0_lock); +static DEFINE_SPINLOCK(sdh1_lock); +static DEFINE_SPINLOCK(sdh2_lock); +static DEFINE_SPINLOCK(sdh3_lock); +static const char * const sdh_parent_names[] = {"pll1_13", "pll1_12", "pll1_8"}; + +static DEFINE_SPINLOCK(usb_lock); + +static DEFINE_SPINLOCK(disp0_lock); +static const char * const disp_parent_names[] = {"pll1", "pll1_2"}; + +static DEFINE_SPINLOCK(ccic0_lock); +static const char * const ccic_parent_names[] = {"pll1_4", "pll1_8"}; +static const char * const ccic_phy_parent_names[] = {"pll1_6", "pll1_12"}; + +static struct mmp_param_mux_clk apmu_mux_clks[] = { + {0, "dfc_mux", dfc_parent_names, ARRAY_SIZE(dfc_parent_names), CLK_SET_RATE_PARENT, APMU_DFC, 6, 1, 0, &dfc_lock}, + {0, "sdh0_mux", sdh_parent_names, ARRAY_SIZE(sdh_parent_names), CLK_SET_RATE_PARENT, APMU_SDH0, 6, 2, 0, &sdh0_lock}, + {0, "sdh1_mux", sdh_parent_names, ARRAY_SIZE(sdh_parent_names), CLK_SET_RATE_PARENT, APMU_SDH1, 6, 2, 0, &sdh1_lock}, + {0, "sdh2_mux", sdh_parent_names, ARRAY_SIZE(sdh_parent_names), CLK_SET_RATE_PARENT, APMU_SDH2, 6, 2, 0, &sdh2_lock}, + {0, "sdh3_mux", sdh_parent_names, ARRAY_SIZE(sdh_parent_names), CLK_SET_RATE_PARENT, APMU_SDH3, 6, 2, 0, &sdh3_lock}, + {0, "disp0_mux", disp_parent_names, ARRAY_SIZE(disp_parent_names), CLK_SET_RATE_PARENT, APMU_DISP0, 6, 1, 0, &disp0_lock}, + {0, "ccic0_mux", ccic_parent_names, ARRAY_SIZE(ccic_parent_names), CLK_SET_RATE_PARENT, APMU_CCIC0, 6, 1, 0, &ccic0_lock}, + {0, "ccic0_phy_mux", ccic_phy_parent_names, ARRAY_SIZE(ccic_phy_parent_names), CLK_SET_RATE_PARENT, APMU_CCIC0, 7, 1, 0, &ccic0_lock}, +}; + +static struct mmp_param_div_clk apmu_div_clks[] = { + {0, "ccic0_sphy_div", "ccic0_mux", CLK_SET_RATE_PARENT, APMU_CCIC0, 10, 5, 0, &ccic0_lock}, +}; + +static struct mmp_param_gate_clk apmu_gate_clks[] = { + {PXA168_CLK_DFC, "dfc_clk", "dfc_mux", CLK_SET_RATE_PARENT, APMU_DFC, 0x19b, 0x19b, 0x0, 0, &dfc_lock}, + {PXA168_CLK_USB, "usb_clk", "usb_pll", 0, APMU_USB, 0x9, 0x9, 0x0, 0, &usb_lock}, + {PXA168_CLK_SPH, "sph_clk", "usb_pll", 0, APMU_USB, 0x12, 0x12, 0x0, 0, &usb_lock}, + {PXA168_CLK_SDH0, "sdh0_clk", "sdh0_mux", CLK_SET_RATE_PARENT, APMU_SDH0, 0x12, 0x12, 0x0, 0, &sdh0_lock}, + {PXA168_CLK_SDH1, "sdh1_clk", "sdh1_mux", CLK_SET_RATE_PARENT, APMU_SDH1, 0x12, 0x12, 0x0, 0, &sdh1_lock}, + {PXA168_CLK_SDH2, "sdh2_clk", "sdh2_mux", CLK_SET_RATE_PARENT, APMU_SDH2, 0x12, 0x12, 0x0, 0, &sdh2_lock}, + {PXA168_CLK_SDH3, "sdh3_clk", "sdh3_mux", CLK_SET_RATE_PARENT, APMU_SDH3, 0x12, 0x12, 0x0, 0, &sdh3_lock}, + /* SDH0/1 and 2/3 AXI clocks are also gated by common bits in SDH0 and SDH2 registers */ + {PXA168_CLK_SDH01_AXI, "sdh01_axi_clk", NULL, CLK_SET_RATE_PARENT, APMU_SDH0, 0x9, 0x9, 0x0, 0, &sdh0_lock}, + {PXA168_CLK_SDH23_AXI, "sdh23_axi_clk", NULL, CLK_SET_RATE_PARENT, APMU_SDH2, 0x9, 0x9, 0x0, 0, &sdh2_lock}, + {PXA168_CLK_DISP0, "disp0_clk", "disp0_mux", CLK_SET_RATE_PARENT, APMU_DISP0, 0x1b, 0x1b, 0x0, 0, &disp0_lock}, + {PXA168_CLK_CCIC0, "ccic0_clk", "ccic0_mux", CLK_SET_RATE_PARENT, APMU_CCIC0, 0x1b, 0x1b, 0x0, 0, &ccic0_lock}, + {PXA168_CLK_CCIC0_PHY, "ccic0_phy_clk", "ccic0_phy_mux", CLK_SET_RATE_PARENT, APMU_CCIC0, 0x24, 0x24, 0x0, 0, &ccic0_lock}, + {PXA168_CLK_CCIC0_SPHY, "ccic0_sphy_clk", "ccic0_sphy_div", CLK_SET_RATE_PARENT, APMU_CCIC0, 0x300, 0x300, 0x0, 0, &ccic0_lock}, +}; + +static void pxa168_axi_periph_clk_init(struct pxa168_clk_unit *pxa_unit) +{ + struct mmp_clk_unit *unit = &pxa_unit->unit; + + mmp_register_mux_clks(unit, apmu_mux_clks, pxa_unit->apmu_base, + ARRAY_SIZE(apmu_mux_clks)); + + mmp_register_div_clks(unit, apmu_div_clks, pxa_unit->apmu_base, + ARRAY_SIZE(apmu_div_clks)); + + mmp_register_gate_clks(unit, apmu_gate_clks, pxa_unit->apmu_base, + ARRAY_SIZE(apmu_gate_clks)); +} + +static void pxa168_clk_reset_init(struct device_node *np, + struct pxa168_clk_unit *pxa_unit) +{ + struct mmp_clk_reset_cell *cells; + int i, nr_resets; + + nr_resets = ARRAY_SIZE(apbc_gate_clks); + cells = kzalloc_objs(*cells, nr_resets); + if (!cells) + return; + + for (i = 0; i < nr_resets; i++) { + cells[i].clk_id = apbc_gate_clks[i].id; + cells[i].reg = pxa_unit->apbc_base + apbc_gate_clks[i].offset; + cells[i].flags = 0; + cells[i].lock = apbc_gate_clks[i].lock; + cells[i].bits = 0x4; + } + + mmp_clk_reset_register(np, cells, nr_resets); +} + +static void __init pxa168_clk_init(struct device_node *np) +{ + struct pxa168_clk_unit *pxa_unit; + + pxa_unit = kzalloc_obj(*pxa_unit); + if (!pxa_unit) + return; + + pxa_unit->mpmu_base = of_iomap(np, 0); + if (!pxa_unit->mpmu_base) { + pr_err("failed to map mpmu registers\n"); + kfree(pxa_unit); + return; + } + + pxa_unit->apmu_base = of_iomap(np, 1); + if (!pxa_unit->apmu_base) { + pr_err("failed to map apmu registers\n"); + kfree(pxa_unit); + return; + } + + pxa_unit->apbc_base = of_iomap(np, 2); + if (!pxa_unit->apbc_base) { + pr_err("failed to map apbc registers\n"); + kfree(pxa_unit); + return; + } + + mmp_clk_init(np, &pxa_unit->unit, NR_CLKS); + + pxa168_pll_init(pxa_unit); + + pxa168_apb_periph_clk_init(pxa_unit); + + pxa168_axi_periph_clk_init(pxa_unit); + + pxa168_clk_reset_init(np, pxa_unit); +} + +CLK_OF_DECLARE(pxa168_clk, "marvell,pxa168-clock", pxa168_clk_init); diff --git a/drivers/clk/mmp/clk-of-pxa1928.c b/drivers/clk/mmp/clk-of-pxa1928.c new file mode 100644 index 000000000..3bc9866a2 --- /dev/null +++ b/drivers/clk/mmp/clk-of-pxa1928.c @@ -0,0 +1,267 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * pxa1928 clock framework source file + * + * Copyright (C) 2015 Linaro, Ltd. + * Rob Herring <robh@kernel.org> + * + * Based on drivers/clk/mmp/clk-of-mmp2.c: + * Copyright (C) 2012 Marvell + * Chao Xie <xiechao.mail@gmail.com> + */ +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/of_address.h> +#include <linux/slab.h> +#include <linux/spinlock.h> + +#include <dt-bindings/clock/marvell,pxa1928.h> + +#include "clk.h" +#include "reset.h" + +#define MPMU_UART_PLL 0x14 + +#define APBC_NR_CLKS 48 +#define APMU_NR_CLKS 96 + +struct pxa1928_clk_unit { + struct mmp_clk_unit unit; + void __iomem *mpmu_base; + void __iomem *apmu_base; + void __iomem *apbc_base; + void __iomem *apbcp_base; +}; + +static struct mmp_param_fixed_rate_clk fixed_rate_clks[] = { + {0, "clk32", NULL, 0, 32768}, + {0, "vctcxo", NULL, 0, 26000000}, + {0, "pll1_624", NULL, 0, 624000000}, + {0, "pll5p", NULL, 0, 832000000}, + {0, "pll5", NULL, 0, 1248000000}, + {0, "usb_pll", NULL, 0, 480000000}, +}; + +static struct mmp_param_fixed_factor_clk fixed_factor_clks[] = { + {0, "pll1_d2", "pll1_624", 1, 2, 0}, + {0, "pll1_d9", "pll1_624", 1, 9, 0}, + {0, "pll1_d12", "pll1_624", 1, 12, 0}, + {0, "pll1_d16", "pll1_624", 1, 16, 0}, + {0, "pll1_d20", "pll1_624", 1, 20, 0}, + {0, "pll1_416", "pll1_624", 2, 3, 0}, + {0, "vctcxo_d2", "vctcxo", 1, 2, 0}, + {0, "vctcxo_d4", "vctcxo", 1, 4, 0}, +}; + +static struct mmp_clk_factor_masks uart_factor_masks = { + .factor = 2, + .num_mask = 0x1fff, + .den_mask = 0x1fff, + .num_shift = 16, + .den_shift = 0, +}; + +static struct u32_fract uart_factor_tbl[] = { + { .numerator = 832, .denominator = 234 }, /* 58.5MHZ */ + { .numerator = 1, .denominator = 1 }, /* 26MHZ */ +}; + +static void pxa1928_pll_init(struct pxa1928_clk_unit *pxa_unit) +{ + struct mmp_clk_unit *unit = &pxa_unit->unit; + + mmp_register_fixed_rate_clks(unit, fixed_rate_clks, + ARRAY_SIZE(fixed_rate_clks)); + + mmp_register_fixed_factor_clks(unit, fixed_factor_clks, + ARRAY_SIZE(fixed_factor_clks)); + + mmp_clk_register_factor("uart_pll", "pll1_416", + CLK_SET_RATE_PARENT, + pxa_unit->mpmu_base + MPMU_UART_PLL, + &uart_factor_masks, uart_factor_tbl, + ARRAY_SIZE(uart_factor_tbl), NULL); +} + +static DEFINE_SPINLOCK(uart0_lock); +static DEFINE_SPINLOCK(uart1_lock); +static DEFINE_SPINLOCK(uart2_lock); +static DEFINE_SPINLOCK(uart3_lock); +static const char *uart_parent_names[] = {"uart_pll", "vctcxo"}; + +static DEFINE_SPINLOCK(ssp0_lock); +static DEFINE_SPINLOCK(ssp1_lock); +static const char *ssp_parent_names[] = {"vctcxo_d4", "vctcxo_d2", "vctcxo", "pll1_d12"}; + +static DEFINE_SPINLOCK(reset_lock); + +static struct mmp_param_mux_clk apbc_mux_clks[] = { + {0, "uart0_mux", uart_parent_names, ARRAY_SIZE(uart_parent_names), CLK_SET_RATE_PARENT, PXA1928_CLK_UART0 * 4, 4, 3, 0, &uart0_lock}, + {0, "uart1_mux", uart_parent_names, ARRAY_SIZE(uart_parent_names), CLK_SET_RATE_PARENT, PXA1928_CLK_UART1 * 4, 4, 3, 0, &uart1_lock}, + {0, "uart2_mux", uart_parent_names, ARRAY_SIZE(uart_parent_names), CLK_SET_RATE_PARENT, PXA1928_CLK_UART2 * 4, 4, 3, 0, &uart2_lock}, + {0, "uart3_mux", uart_parent_names, ARRAY_SIZE(uart_parent_names), CLK_SET_RATE_PARENT, PXA1928_CLK_UART3 * 4, 4, 3, 0, &uart3_lock}, + {0, "ssp0_mux", ssp_parent_names, ARRAY_SIZE(ssp_parent_names), CLK_SET_RATE_PARENT, PXA1928_CLK_SSP0 * 4, 4, 3, 0, &ssp0_lock}, + {0, "ssp1_mux", ssp_parent_names, ARRAY_SIZE(ssp_parent_names), CLK_SET_RATE_PARENT, PXA1928_CLK_SSP1 * 4, 4, 3, 0, &ssp1_lock}, +}; + +static struct mmp_param_gate_clk apbc_gate_clks[] = { + {PXA1928_CLK_TWSI0, "twsi0_clk", "vctcxo", CLK_SET_RATE_PARENT, PXA1928_CLK_TWSI0 * 4, 0x3, 0x3, 0x0, 0, &reset_lock}, + {PXA1928_CLK_TWSI1, "twsi1_clk", "vctcxo", CLK_SET_RATE_PARENT, PXA1928_CLK_TWSI1 * 4, 0x3, 0x3, 0x0, 0, &reset_lock}, + {PXA1928_CLK_TWSI2, "twsi2_clk", "vctcxo", CLK_SET_RATE_PARENT, PXA1928_CLK_TWSI2 * 4, 0x3, 0x3, 0x0, 0, &reset_lock}, + {PXA1928_CLK_TWSI3, "twsi3_clk", "vctcxo", CLK_SET_RATE_PARENT, PXA1928_CLK_TWSI3 * 4, 0x3, 0x3, 0x0, 0, &reset_lock}, + {PXA1928_CLK_TWSI4, "twsi4_clk", "vctcxo", CLK_SET_RATE_PARENT, PXA1928_CLK_TWSI4 * 4, 0x3, 0x3, 0x0, 0, &reset_lock}, + {PXA1928_CLK_TWSI5, "twsi5_clk", "vctcxo", CLK_SET_RATE_PARENT, PXA1928_CLK_TWSI5 * 4, 0x3, 0x3, 0x0, 0, &reset_lock}, + {PXA1928_CLK_GPIO, "gpio_clk", "vctcxo", CLK_SET_RATE_PARENT, PXA1928_CLK_GPIO * 4, 0x3, 0x3, 0x0, 0, &reset_lock}, + {PXA1928_CLK_KPC, "kpc_clk", "clk32", CLK_SET_RATE_PARENT, PXA1928_CLK_KPC * 4, 0x3, 0x3, 0x0, MMP_CLK_GATE_NEED_DELAY, NULL}, + {PXA1928_CLK_RTC, "rtc_clk", "clk32", CLK_SET_RATE_PARENT, PXA1928_CLK_RTC * 4, 0x83, 0x83, 0x0, MMP_CLK_GATE_NEED_DELAY, NULL}, + {PXA1928_CLK_PWM0, "pwm0_clk", "vctcxo", CLK_SET_RATE_PARENT, PXA1928_CLK_PWM0 * 4, 0x3, 0x3, 0x0, 0, &reset_lock}, + {PXA1928_CLK_PWM1, "pwm1_clk", "vctcxo", CLK_SET_RATE_PARENT, PXA1928_CLK_PWM1 * 4, 0x3, 0x3, 0x0, 0, &reset_lock}, + {PXA1928_CLK_PWM2, "pwm2_clk", "vctcxo", CLK_SET_RATE_PARENT, PXA1928_CLK_PWM2 * 4, 0x3, 0x3, 0x0, 0, &reset_lock}, + {PXA1928_CLK_PWM3, "pwm3_clk", "vctcxo", CLK_SET_RATE_PARENT, PXA1928_CLK_PWM3 * 4, 0x3, 0x3, 0x0, 0, &reset_lock}, + /* The gate clocks has mux parent. */ + {PXA1928_CLK_UART0, "uart0_clk", "uart0_mux", CLK_SET_RATE_PARENT, PXA1928_CLK_UART0 * 4, 0x3, 0x3, 0x0, 0, &uart0_lock}, + {PXA1928_CLK_UART1, "uart1_clk", "uart1_mux", CLK_SET_RATE_PARENT, PXA1928_CLK_UART1 * 4, 0x3, 0x3, 0x0, 0, &uart1_lock}, + {PXA1928_CLK_UART2, "uart2_clk", "uart2_mux", CLK_SET_RATE_PARENT, PXA1928_CLK_UART2 * 4, 0x3, 0x3, 0x0, 0, &uart2_lock}, + {PXA1928_CLK_UART3, "uart3_clk", "uart3_mux", CLK_SET_RATE_PARENT, PXA1928_CLK_UART3 * 4, 0x3, 0x3, 0x0, 0, &uart3_lock}, + {PXA1928_CLK_SSP0, "ssp0_clk", "ssp0_mux", CLK_SET_RATE_PARENT, PXA1928_CLK_SSP0 * 4, 0x3, 0x3, 0x0, 0, &ssp0_lock}, + {PXA1928_CLK_SSP1, "ssp1_clk", "ssp1_mux", CLK_SET_RATE_PARENT, PXA1928_CLK_SSP1 * 4, 0x3, 0x3, 0x0, 0, &ssp1_lock}, +}; + +static void pxa1928_apb_periph_clk_init(struct pxa1928_clk_unit *pxa_unit) +{ + struct mmp_clk_unit *unit = &pxa_unit->unit; + + mmp_register_mux_clks(unit, apbc_mux_clks, pxa_unit->apbc_base, + ARRAY_SIZE(apbc_mux_clks)); + + mmp_register_gate_clks(unit, apbc_gate_clks, pxa_unit->apbc_base, + ARRAY_SIZE(apbc_gate_clks)); +} + +static DEFINE_SPINLOCK(sdh0_lock); +static DEFINE_SPINLOCK(sdh1_lock); +static DEFINE_SPINLOCK(sdh2_lock); +static DEFINE_SPINLOCK(sdh3_lock); +static DEFINE_SPINLOCK(sdh4_lock); +static const char *sdh_parent_names[] = {"pll1_624", "pll5p", "pll5", "pll1_416"}; + +static DEFINE_SPINLOCK(usb_lock); + +static struct mmp_param_mux_clk apmu_mux_clks[] = { + {0, "sdh_mux", sdh_parent_names, ARRAY_SIZE(sdh_parent_names), CLK_SET_RATE_PARENT, PXA1928_CLK_SDH0 * 4, 8, 2, 0, &sdh0_lock}, +}; + +static struct mmp_param_div_clk apmu_div_clks[] = { + {0, "sdh_div", "sdh_mux", 0, PXA1928_CLK_SDH0 * 4, 10, 4, CLK_DIVIDER_ONE_BASED, &sdh0_lock}, +}; + +static struct mmp_param_gate_clk apmu_gate_clks[] = { + {PXA1928_CLK_USB, "usb_clk", "usb_pll", 0, PXA1928_CLK_USB * 4, 0x9, 0x9, 0x0, 0, &usb_lock}, + {PXA1928_CLK_HSIC, "hsic_clk", "usb_pll", 0, PXA1928_CLK_HSIC * 4, 0x9, 0x9, 0x0, 0, &usb_lock}, + /* The gate clocks has mux parent. */ + {PXA1928_CLK_SDH0, "sdh0_clk", "sdh_div", CLK_SET_RATE_PARENT, PXA1928_CLK_SDH0 * 4, 0x1b, 0x1b, 0x0, 0, &sdh0_lock}, + {PXA1928_CLK_SDH1, "sdh1_clk", "sdh_div", CLK_SET_RATE_PARENT, PXA1928_CLK_SDH1 * 4, 0x1b, 0x1b, 0x0, 0, &sdh1_lock}, + {PXA1928_CLK_SDH2, "sdh2_clk", "sdh_div", CLK_SET_RATE_PARENT, PXA1928_CLK_SDH2 * 4, 0x1b, 0x1b, 0x0, 0, &sdh2_lock}, + {PXA1928_CLK_SDH3, "sdh3_clk", "sdh_div", CLK_SET_RATE_PARENT, PXA1928_CLK_SDH3 * 4, 0x1b, 0x1b, 0x0, 0, &sdh3_lock}, + {PXA1928_CLK_SDH4, "sdh4_clk", "sdh_div", CLK_SET_RATE_PARENT, PXA1928_CLK_SDH4 * 4, 0x1b, 0x1b, 0x0, 0, &sdh4_lock}, +}; + +static void pxa1928_axi_periph_clk_init(struct pxa1928_clk_unit *pxa_unit) +{ + struct mmp_clk_unit *unit = &pxa_unit->unit; + + mmp_register_mux_clks(unit, apmu_mux_clks, pxa_unit->apmu_base, + ARRAY_SIZE(apmu_mux_clks)); + + mmp_register_div_clks(unit, apmu_div_clks, pxa_unit->apmu_base, + ARRAY_SIZE(apmu_div_clks)); + + mmp_register_gate_clks(unit, apmu_gate_clks, pxa_unit->apmu_base, + ARRAY_SIZE(apmu_gate_clks)); +} + +static void pxa1928_clk_reset_init(struct device_node *np, + struct pxa1928_clk_unit *pxa_unit) +{ + struct mmp_clk_reset_cell *cells; + int i, base, nr_resets; + + nr_resets = ARRAY_SIZE(apbc_gate_clks); + cells = kzalloc_objs(*cells, nr_resets); + if (!cells) + return; + + base = 0; + for (i = 0; i < nr_resets; i++) { + cells[base + i].clk_id = apbc_gate_clks[i].id; + cells[base + i].reg = + pxa_unit->apbc_base + apbc_gate_clks[i].offset; + cells[base + i].flags = 0; + cells[base + i].lock = apbc_gate_clks[i].lock; + cells[base + i].bits = 0x4; + } + + mmp_clk_reset_register(np, cells, nr_resets); +} + +static void __init pxa1928_mpmu_clk_init(struct device_node *np) +{ + struct pxa1928_clk_unit *pxa_unit; + + pxa_unit = kzalloc_obj(*pxa_unit); + if (!pxa_unit) + return; + + pxa_unit->mpmu_base = of_iomap(np, 0); + if (!pxa_unit->mpmu_base) { + pr_err("failed to map mpmu registers\n"); + kfree(pxa_unit); + return; + } + + pxa1928_pll_init(pxa_unit); +} +CLK_OF_DECLARE(pxa1928_mpmu_clk, "marvell,pxa1928-mpmu", pxa1928_mpmu_clk_init); + +static void __init pxa1928_apmu_clk_init(struct device_node *np) +{ + struct pxa1928_clk_unit *pxa_unit; + + pxa_unit = kzalloc_obj(*pxa_unit); + if (!pxa_unit) + return; + + pxa_unit->apmu_base = of_iomap(np, 0); + if (!pxa_unit->apmu_base) { + pr_err("failed to map apmu registers\n"); + kfree(pxa_unit); + return; + } + + mmp_clk_init(np, &pxa_unit->unit, APMU_NR_CLKS); + + pxa1928_axi_periph_clk_init(pxa_unit); +} +CLK_OF_DECLARE(pxa1928_apmu_clk, "marvell,pxa1928-apmu", pxa1928_apmu_clk_init); + +static void __init pxa1928_apbc_clk_init(struct device_node *np) +{ + struct pxa1928_clk_unit *pxa_unit; + + pxa_unit = kzalloc_obj(*pxa_unit); + if (!pxa_unit) + return; + + pxa_unit->apbc_base = of_iomap(np, 0); + if (!pxa_unit->apbc_base) { + pr_err("failed to map apbc registers\n"); + kfree(pxa_unit); + return; + } + + mmp_clk_init(np, &pxa_unit->unit, APBC_NR_CLKS); + + pxa1928_apb_periph_clk_init(pxa_unit); + pxa1928_clk_reset_init(np, pxa_unit); +} +CLK_OF_DECLARE(pxa1928_apbc_clk, "marvell,pxa1928-apbc", pxa1928_apbc_clk_init); diff --git a/drivers/clk/mmp/clk-of-pxa910.c b/drivers/clk/mmp/clk-of-pxa910.c new file mode 100644 index 000000000..d90bcc9a8 --- /dev/null +++ b/drivers/clk/mmp/clk-of-pxa910.c @@ -0,0 +1,323 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * pxa910 clock framework source file + * + * Copyright (C) 2012 Marvell + * Chao Xie <xiechao.mail@gmail.com> + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/spinlock.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/of_address.h> + +#include <dt-bindings/clock/marvell,pxa910.h> + +#include "clk.h" +#include "reset.h" + +#define APBC_RTC 0x28 +#define APBC_TWSI0 0x2c +#define APBC_KPC 0x18 +#define APBC_UART0 0x0 +#define APBC_UART1 0x4 +#define APBC_GPIO 0x8 +#define APBC_PWM0 0xc +#define APBC_PWM1 0x10 +#define APBC_PWM2 0x14 +#define APBC_PWM3 0x18 +#define APBC_SSP0 0x1c +#define APBC_SSP1 0x20 +#define APBC_SSP2 0x4c +#define APBC_TIMER0 0x30 +#define APBC_TIMER1 0x44 +#define APBCP_TWSI1 0x28 +#define APBCP_UART2 0x1c +#define APMU_SDH0 0x54 +#define APMU_SDH1 0x58 +#define APMU_USB 0x5c +#define APMU_DISP0 0x4c +#define APMU_CCIC0 0x50 +#define APMU_DFC 0x60 +#define MPMU_UART_PLL 0x14 + +#define NR_CLKS 200 + +struct pxa910_clk_unit { + struct mmp_clk_unit unit; + void __iomem *mpmu_base; + void __iomem *apmu_base; + void __iomem *apbc_base; + void __iomem *apbcp_base; +}; + +static struct mmp_param_fixed_rate_clk fixed_rate_clks[] = { + {PXA910_CLK_CLK32, "clk32", NULL, 0, 32768}, + {PXA910_CLK_VCTCXO, "vctcxo", NULL, 0, 26000000}, + {PXA910_CLK_PLL1, "pll1", NULL, 0, 624000000}, + {PXA910_CLK_USB_PLL, "usb_pll", NULL, 0, 480000000}, +}; + +static struct mmp_param_fixed_factor_clk fixed_factor_clks[] = { + {PXA910_CLK_PLL1_2, "pll1_2", "pll1", 1, 2, 0}, + {PXA910_CLK_PLL1_4, "pll1_4", "pll1_2", 1, 2, 0}, + {PXA910_CLK_PLL1_8, "pll1_8", "pll1_4", 1, 2, 0}, + {PXA910_CLK_PLL1_16, "pll1_16", "pll1_8", 1, 2, 0}, + {PXA910_CLK_PLL1_6, "pll1_6", "pll1_2", 1, 3, 0}, + {PXA910_CLK_PLL1_12, "pll1_12", "pll1_6", 1, 2, 0}, + {PXA910_CLK_PLL1_24, "pll1_24", "pll1_12", 1, 2, 0}, + {PXA910_CLK_PLL1_48, "pll1_48", "pll1_24", 1, 2, 0}, + {PXA910_CLK_PLL1_96, "pll1_96", "pll1_48", 1, 2, 0}, + {PXA910_CLK_PLL1_192, "pll1_192", "pll1_96", 1, 2, 0}, + {PXA910_CLK_PLL1_13, "pll1_13", "pll1", 1, 13, 0}, + {PXA910_CLK_PLL1_13_1_5, "pll1_13_1_5", "pll1_13", 2, 3, 0}, + {PXA910_CLK_PLL1_2_1_5, "pll1_2_1_5", "pll1_2", 2, 3, 0}, + {PXA910_CLK_PLL1_3_16, "pll1_3_16", "pll1", 3, 16, 0}, +}; + +static struct mmp_clk_factor_masks uart_factor_masks = { + .factor = 2, + .num_mask = 0x1fff, + .den_mask = 0x1fff, + .num_shift = 16, + .den_shift = 0, +}; + +static struct u32_fract uart_factor_tbl[] = { + { .numerator = 8125, .denominator = 1536 }, /* 14.745MHZ */ +}; + +static void pxa910_pll_init(struct pxa910_clk_unit *pxa_unit) +{ + struct clk *clk; + struct mmp_clk_unit *unit = &pxa_unit->unit; + + mmp_register_fixed_rate_clks(unit, fixed_rate_clks, + ARRAY_SIZE(fixed_rate_clks)); + + mmp_register_fixed_factor_clks(unit, fixed_factor_clks, + ARRAY_SIZE(fixed_factor_clks)); + + clk = mmp_clk_register_factor("uart_pll", "pll1_4", + CLK_SET_RATE_PARENT, + pxa_unit->mpmu_base + MPMU_UART_PLL, + &uart_factor_masks, uart_factor_tbl, + ARRAY_SIZE(uart_factor_tbl), NULL); + mmp_clk_add(unit, PXA910_CLK_UART_PLL, clk); +} + +static DEFINE_SPINLOCK(uart0_lock); +static DEFINE_SPINLOCK(uart1_lock); +static DEFINE_SPINLOCK(uart2_lock); +static const char *uart_parent_names[] = {"pll1_3_16", "uart_pll"}; + +static DEFINE_SPINLOCK(ssp0_lock); +static DEFINE_SPINLOCK(ssp1_lock); +static const char *ssp_parent_names[] = {"pll1_96", "pll1_48", "pll1_24", "pll1_12"}; + +static DEFINE_SPINLOCK(timer0_lock); +static DEFINE_SPINLOCK(timer1_lock); +static const char *timer_parent_names[] = {"pll1_48", "clk32", "pll1_96"}; + +static DEFINE_SPINLOCK(reset_lock); + +static struct mmp_param_mux_clk apbc_mux_clks[] = { + {0, "uart0_mux", uart_parent_names, ARRAY_SIZE(uart_parent_names), CLK_SET_RATE_PARENT, APBC_UART0, 4, 3, 0, &uart0_lock}, + {0, "uart1_mux", uart_parent_names, ARRAY_SIZE(uart_parent_names), CLK_SET_RATE_PARENT, APBC_UART1, 4, 3, 0, &uart1_lock}, + {0, "ssp0_mux", ssp_parent_names, ARRAY_SIZE(ssp_parent_names), CLK_SET_RATE_PARENT, APBC_SSP0, 4, 3, 0, &ssp0_lock}, + {0, "ssp1_mux", ssp_parent_names, ARRAY_SIZE(ssp_parent_names), CLK_SET_RATE_PARENT, APBC_SSP1, 4, 3, 0, &ssp1_lock}, + {0, "timer0_mux", timer_parent_names, ARRAY_SIZE(timer_parent_names), CLK_SET_RATE_PARENT, APBC_TIMER0, 4, 3, 0, &timer0_lock}, + {0, "timer1_mux", timer_parent_names, ARRAY_SIZE(timer_parent_names), CLK_SET_RATE_PARENT, APBC_TIMER1, 4, 3, 0, &timer1_lock}, +}; + +static struct mmp_param_mux_clk apbcp_mux_clks[] = { + {0, "uart2_mux", uart_parent_names, ARRAY_SIZE(uart_parent_names), CLK_SET_RATE_PARENT, APBCP_UART2, 4, 3, 0, &uart2_lock}, +}; + +static struct mmp_param_gate_clk apbc_gate_clks[] = { + {PXA910_CLK_TWSI0, "twsi0_clk", "pll1_13_1_5", CLK_SET_RATE_PARENT, APBC_TWSI0, 0x3, 0x3, 0x0, 0, &reset_lock}, + {PXA910_CLK_GPIO, "gpio_clk", "vctcxo", CLK_SET_RATE_PARENT, APBC_GPIO, 0x3, 0x3, 0x0, 0, &reset_lock}, + {PXA910_CLK_KPC, "kpc_clk", "clk32", CLK_SET_RATE_PARENT, APBC_KPC, 0x3, 0x3, 0x0, MMP_CLK_GATE_NEED_DELAY, NULL}, + {PXA910_CLK_RTC, "rtc_clk", "clk32", CLK_SET_RATE_PARENT, APBC_RTC, 0x83, 0x83, 0x0, MMP_CLK_GATE_NEED_DELAY, NULL}, + {PXA910_CLK_PWM0, "pwm0_clk", "pll1_48", CLK_SET_RATE_PARENT, APBC_PWM0, 0x3, 0x3, 0x0, 0, &reset_lock}, + {PXA910_CLK_PWM1, "pwm1_clk", "pll1_48", CLK_SET_RATE_PARENT, APBC_PWM1, 0x3, 0x3, 0x0, 0, &reset_lock}, + {PXA910_CLK_PWM2, "pwm2_clk", "pll1_48", CLK_SET_RATE_PARENT, APBC_PWM2, 0x3, 0x3, 0x0, 0, &reset_lock}, + {PXA910_CLK_PWM3, "pwm3_clk", "pll1_48", CLK_SET_RATE_PARENT, APBC_PWM3, 0x3, 0x3, 0x0, 0, &reset_lock}, + /* The gate clocks has mux parent. */ + {PXA910_CLK_UART0, "uart0_clk", "uart0_mux", CLK_SET_RATE_PARENT, APBC_UART0, 0x3, 0x3, 0x0, 0, &uart0_lock}, + {PXA910_CLK_UART1, "uart1_clk", "uart1_mux", CLK_SET_RATE_PARENT, APBC_UART1, 0x3, 0x3, 0x0, 0, &uart1_lock}, + {PXA910_CLK_SSP0, "ssp0_clk", "ssp0_mux", CLK_SET_RATE_PARENT, APBC_SSP0, 0x3, 0x3, 0x0, 0, &ssp0_lock}, + {PXA910_CLK_SSP1, "ssp1_clk", "ssp1_mux", CLK_SET_RATE_PARENT, APBC_SSP1, 0x3, 0x3, 0x0, 0, &ssp1_lock}, + {PXA910_CLK_TIMER0, "timer0_clk", "timer0_mux", CLK_SET_RATE_PARENT, APBC_TIMER0, 0x3, 0x3, 0x0, 0, &timer0_lock}, + {PXA910_CLK_TIMER1, "timer1_clk", "timer1_mux", CLK_SET_RATE_PARENT, APBC_TIMER1, 0x3, 0x3, 0x0, 0, &timer1_lock}, +}; + +static struct mmp_param_gate_clk apbcp_gate_clks[] = { + {PXA910_CLK_TWSI1, "twsi1_clk", "pll1_13_1_5", CLK_SET_RATE_PARENT, APBCP_TWSI1, 0x3, 0x3, 0x0, 0, &reset_lock}, + /* The gate clocks has mux parent. */ + {PXA910_CLK_UART2, "uart2_clk", "uart2_mux", CLK_SET_RATE_PARENT, APBCP_UART2, 0x3, 0x3, 0x0, 0, &uart2_lock}, +}; + +static void pxa910_apb_periph_clk_init(struct pxa910_clk_unit *pxa_unit) +{ + struct mmp_clk_unit *unit = &pxa_unit->unit; + + mmp_register_mux_clks(unit, apbc_mux_clks, pxa_unit->apbc_base, + ARRAY_SIZE(apbc_mux_clks)); + + mmp_register_mux_clks(unit, apbcp_mux_clks, pxa_unit->apbcp_base, + ARRAY_SIZE(apbcp_mux_clks)); + + mmp_register_gate_clks(unit, apbc_gate_clks, pxa_unit->apbc_base, + ARRAY_SIZE(apbc_gate_clks)); + + mmp_register_gate_clks(unit, apbcp_gate_clks, pxa_unit->apbcp_base, + ARRAY_SIZE(apbcp_gate_clks)); +} + +static DEFINE_SPINLOCK(sdh0_lock); +static DEFINE_SPINLOCK(sdh1_lock); +static const char *sdh_parent_names[] = {"pll1_12", "pll1_13"}; + +static DEFINE_SPINLOCK(usb_lock); + +static DEFINE_SPINLOCK(disp0_lock); +static const char *disp_parent_names[] = {"pll1_2", "pll1_12"}; + +static DEFINE_SPINLOCK(ccic0_lock); +static const char *ccic_parent_names[] = {"pll1_2", "pll1_12"}; +static const char *ccic_phy_parent_names[] = {"pll1_6", "pll1_12"}; + +static struct mmp_param_mux_clk apmu_mux_clks[] = { + {0, "sdh0_mux", sdh_parent_names, ARRAY_SIZE(sdh_parent_names), CLK_SET_RATE_PARENT, APMU_SDH0, 6, 1, 0, &sdh0_lock}, + {0, "sdh1_mux", sdh_parent_names, ARRAY_SIZE(sdh_parent_names), CLK_SET_RATE_PARENT, APMU_SDH1, 6, 1, 0, &sdh1_lock}, + {0, "disp0_mux", disp_parent_names, ARRAY_SIZE(disp_parent_names), CLK_SET_RATE_PARENT, APMU_DISP0, 6, 1, 0, &disp0_lock}, + {0, "ccic0_mux", ccic_parent_names, ARRAY_SIZE(ccic_parent_names), CLK_SET_RATE_PARENT, APMU_CCIC0, 6, 1, 0, &ccic0_lock}, + {0, "ccic0_phy_mux", ccic_phy_parent_names, ARRAY_SIZE(ccic_phy_parent_names), CLK_SET_RATE_PARENT, APMU_CCIC0, 7, 1, 0, &ccic0_lock}, +}; + +static struct mmp_param_div_clk apmu_div_clks[] = { + {0, "ccic0_sphy_div", "ccic0_mux", CLK_SET_RATE_PARENT, APMU_CCIC0, 10, 5, 0, &ccic0_lock}, +}; + +static struct mmp_param_gate_clk apmu_gate_clks[] = { + {PXA910_CLK_DFC, "dfc_clk", "pll1_4", CLK_SET_RATE_PARENT, APMU_DFC, 0x19b, 0x19b, 0x0, 0, NULL}, + {PXA910_CLK_USB, "usb_clk", "usb_pll", 0, APMU_USB, 0x9, 0x9, 0x0, 0, &usb_lock}, + {PXA910_CLK_SPH, "sph_clk", "usb_pll", 0, APMU_USB, 0x12, 0x12, 0x0, 0, &usb_lock}, + /* The gate clocks has mux parent. */ + {PXA910_CLK_SDH0, "sdh0_clk", "sdh0_mux", CLK_SET_RATE_PARENT, APMU_SDH0, 0x1b, 0x1b, 0x0, 0, &sdh0_lock}, + {PXA910_CLK_SDH1, "sdh1_clk", "sdh1_mux", CLK_SET_RATE_PARENT, APMU_SDH1, 0x1b, 0x1b, 0x0, 0, &sdh1_lock}, + {PXA910_CLK_DISP0, "disp0_clk", "disp0_mux", CLK_SET_RATE_PARENT, APMU_DISP0, 0x1b, 0x1b, 0x0, 0, &disp0_lock}, + {PXA910_CLK_CCIC0, "ccic0_clk", "ccic0_mux", CLK_SET_RATE_PARENT, APMU_CCIC0, 0x1b, 0x1b, 0x0, 0, &ccic0_lock}, + {PXA910_CLK_CCIC0_PHY, "ccic0_phy_clk", "ccic0_phy_mux", CLK_SET_RATE_PARENT, APMU_CCIC0, 0x24, 0x24, 0x0, 0, &ccic0_lock}, + {PXA910_CLK_CCIC0_SPHY, "ccic0_sphy_clk", "ccic0_sphy_div", CLK_SET_RATE_PARENT, APMU_CCIC0, 0x300, 0x300, 0x0, 0, &ccic0_lock}, +}; + +static void pxa910_axi_periph_clk_init(struct pxa910_clk_unit *pxa_unit) +{ + struct mmp_clk_unit *unit = &pxa_unit->unit; + + mmp_register_mux_clks(unit, apmu_mux_clks, pxa_unit->apmu_base, + ARRAY_SIZE(apmu_mux_clks)); + + mmp_register_div_clks(unit, apmu_div_clks, pxa_unit->apmu_base, + ARRAY_SIZE(apmu_div_clks)); + + mmp_register_gate_clks(unit, apmu_gate_clks, pxa_unit->apmu_base, + ARRAY_SIZE(apmu_gate_clks)); +} + +static void pxa910_clk_reset_init(struct device_node *np, + struct pxa910_clk_unit *pxa_unit) +{ + struct mmp_clk_reset_cell *cells; + int i, base, nr_resets_apbc, nr_resets_apbcp, nr_resets; + + nr_resets_apbc = ARRAY_SIZE(apbc_gate_clks); + nr_resets_apbcp = ARRAY_SIZE(apbcp_gate_clks); + nr_resets = nr_resets_apbc + nr_resets_apbcp; + cells = kzalloc_objs(*cells, nr_resets); + if (!cells) + return; + + base = 0; + for (i = 0; i < nr_resets_apbc; i++) { + cells[base + i].clk_id = apbc_gate_clks[i].id; + cells[base + i].reg = + pxa_unit->apbc_base + apbc_gate_clks[i].offset; + cells[base + i].flags = 0; + cells[base + i].lock = apbc_gate_clks[i].lock; + cells[base + i].bits = 0x4; + } + + base = nr_resets_apbc; + for (i = 0; i < nr_resets_apbcp; i++) { + cells[base + i].clk_id = apbcp_gate_clks[i].id; + cells[base + i].reg = + pxa_unit->apbc_base + apbc_gate_clks[i].offset; + cells[base + i].flags = 0; + cells[base + i].lock = apbc_gate_clks[i].lock; + cells[base + i].bits = 0x4; + } + + mmp_clk_reset_register(np, cells, nr_resets); +} + +static void __init pxa910_clk_init(struct device_node *np) +{ + struct pxa910_clk_unit *pxa_unit; + + pxa_unit = kzalloc_obj(*pxa_unit); + if (!pxa_unit) + return; + + pxa_unit->mpmu_base = of_iomap(np, 0); + if (!pxa_unit->mpmu_base) { + pr_err("failed to map mpmu registers\n"); + goto free_memory; + } + + pxa_unit->apmu_base = of_iomap(np, 1); + if (!pxa_unit->apmu_base) { + pr_err("failed to map apmu registers\n"); + goto unmap_mpmu_region; + } + + pxa_unit->apbc_base = of_iomap(np, 2); + if (!pxa_unit->apbc_base) { + pr_err("failed to map apbc registers\n"); + goto unmap_apmu_region; + } + + pxa_unit->apbcp_base = of_iomap(np, 3); + if (!pxa_unit->apbcp_base) { + pr_err("failed to map apbcp registers\n"); + goto unmap_apbc_region; + } + + mmp_clk_init(np, &pxa_unit->unit, NR_CLKS); + + pxa910_pll_init(pxa_unit); + + pxa910_apb_periph_clk_init(pxa_unit); + + pxa910_axi_periph_clk_init(pxa_unit); + + pxa910_clk_reset_init(np, pxa_unit); + + return; + +unmap_apbc_region: + iounmap(pxa_unit->apbc_base); +unmap_apmu_region: + iounmap(pxa_unit->apmu_base); +unmap_mpmu_region: + iounmap(pxa_unit->mpmu_base); +free_memory: + kfree(pxa_unit); +} + +CLK_OF_DECLARE(pxa910_clk, "marvell,pxa910-clock", pxa910_clk_init); diff --git a/drivers/clk/mmp/clk-pll.c b/drivers/clk/mmp/clk-pll.c new file mode 100644 index 000000000..430f5e3c8 --- /dev/null +++ b/drivers/clk/mmp/clk-pll.c @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MMP PLL clock rate calculation + * + * Copyright (C) 2020 Lubomir Rintel <lkundrak@v3.sk> + */ + +#include <linux/clk-provider.h> +#include <linux/slab.h> +#include <linux/io.h> + +#include "clk.h" + +#define to_clk_mmp_pll(hw) container_of(hw, struct mmp_clk_pll, hw) + +struct mmp_clk_pll { + struct clk_hw hw; + unsigned long default_rate; + void __iomem *enable_reg; + u32 enable; + void __iomem *reg; + u8 shift; + + unsigned long input_rate; + void __iomem *postdiv_reg; + u8 postdiv_shift; +}; + +static int mmp_clk_pll_is_enabled(struct clk_hw *hw) +{ + struct mmp_clk_pll *pll = to_clk_mmp_pll(hw); + u32 val; + + val = readl_relaxed(pll->enable_reg); + if ((val & pll->enable) == pll->enable) + return 1; + + /* Some PLLs, if not software controlled, output default clock. */ + if (pll->default_rate > 0) + return 1; + + return 0; +} + +static unsigned long mmp_clk_pll_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct mmp_clk_pll *pll = to_clk_mmp_pll(hw); + u32 fbdiv, refdiv, postdiv; + u64 rate; + u32 val; + + val = readl_relaxed(pll->enable_reg); + if ((val & pll->enable) != pll->enable) + return pll->default_rate; + + if (pll->reg) { + val = readl_relaxed(pll->reg); + fbdiv = (val >> pll->shift) & 0x1ff; + refdiv = (val >> (pll->shift + 9)) & 0x1f; + } else { + fbdiv = 2; + refdiv = 1; + } + + if (pll->postdiv_reg) { + /* MMP3 clock rate calculation */ + static const u8 postdivs[] = {2, 3, 4, 5, 6, 8, 10, 12, 16}; + + val = readl_relaxed(pll->postdiv_reg); + postdiv = (val >> pll->postdiv_shift) & 0x7; + + rate = pll->input_rate; + rate *= 2 * fbdiv; + do_div(rate, refdiv); + do_div(rate, postdivs[postdiv]); + } else { + /* MMP2 clock rate calculation */ + if (refdiv == 3) { + rate = 19200000; + } else if (refdiv == 4) { + rate = 26000000; + } else { + pr_err("bad refdiv: %d (0x%08x)\n", refdiv, val); + return 0; + } + + rate *= fbdiv + 2; + do_div(rate, refdiv + 2); + } + + return (unsigned long)rate; +} + +static const struct clk_ops mmp_clk_pll_ops = { + .is_enabled = mmp_clk_pll_is_enabled, + .recalc_rate = mmp_clk_pll_recalc_rate, +}; + +static struct clk *mmp_clk_register_pll(char *name, + unsigned long default_rate, + void __iomem *enable_reg, u32 enable, + void __iomem *reg, u8 shift, + unsigned long input_rate, + void __iomem *postdiv_reg, u8 postdiv_shift) +{ + struct mmp_clk_pll *pll; + struct clk *clk; + struct clk_init_data init; + + pll = kzalloc_obj(*pll); + if (!pll) + return ERR_PTR(-ENOMEM); + + init.name = name; + init.ops = &mmp_clk_pll_ops; + init.flags = 0; + init.parent_names = NULL; + init.num_parents = 0; + + pll->default_rate = default_rate; + pll->enable_reg = enable_reg; + pll->enable = enable; + pll->reg = reg; + pll->shift = shift; + + pll->input_rate = input_rate; + pll->postdiv_reg = postdiv_reg; + pll->postdiv_shift = postdiv_shift; + + pll->hw.init = &init; + + clk = clk_register(NULL, &pll->hw); + + if (IS_ERR(clk)) + kfree(pll); + + return clk; +} + +void mmp_register_pll_clks(struct mmp_clk_unit *unit, + struct mmp_param_pll_clk *clks, + void __iomem *base, int size) +{ + struct clk *clk; + int i; + + for (i = 0; i < size; i++) { + void __iomem *reg = NULL; + + if (clks[i].offset) + reg = base + clks[i].offset; + + clk = mmp_clk_register_pll(clks[i].name, + clks[i].default_rate, + base + clks[i].enable_offset, + clks[i].enable, + reg, clks[i].shift, + clks[i].input_rate, + base + clks[i].postdiv_offset, + clks[i].postdiv_shift); + if (IS_ERR(clk)) { + pr_err("%s: failed to register clock %s\n", + __func__, clks[i].name); + continue; + } + if (clks[i].id) + unit->clk_table[clks[i].id] = clk; + } +} diff --git a/drivers/clk/mmp/clk-pxa1908-apbc.c b/drivers/clk/mmp/clk-pxa1908-apbc.c new file mode 100644 index 000000000..16c1f13bc --- /dev/null +++ b/drivers/clk/mmp/clk-pxa1908-apbc.c @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include <linux/clk-provider.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> + +#include <dt-bindings/clock/marvell,pxa1908.h> + +#include "clk.h" +#include "reset.h" + +#define APBC_UART0 0x0 +#define APBC_UART1 0x4 +#define APBC_GPIO 0x8 +#define APBC_PWM0 0xc +#define APBC_PWM1 0x10 +#define APBC_PWM2 0x14 +#define APBC_PWM3 0x18 +#define APBC_SSP0 0x1c +#define APBC_SSP1 0x20 +#define APBC_IPC_RST 0x24 +#define APBC_RTC 0x28 +#define APBC_TWSI0 0x2c +#define APBC_KPC 0x30 +#define APBC_SWJTAG 0x40 +#define APBC_SSP2 0x4c +#define APBC_TWSI1 0x60 +#define APBC_THERMAL 0x6c +#define APBC_TWSI3 0x70 + +#define APBC_NR_CLKS 19 + +struct pxa1908_clk_unit { + struct mmp_clk_unit unit; + void __iomem *base; +}; + +static DEFINE_SPINLOCK(pwm0_lock); +static DEFINE_SPINLOCK(pwm2_lock); + +static DEFINE_SPINLOCK(uart0_lock); +static DEFINE_SPINLOCK(uart1_lock); + +static const char * const uart_parent_names[] = {"pll1_117", "uart_pll"}; +static const char * const ssp_parent_names[] = {"pll1_d16", "pll1_d48", "pll1_d24", "pll1_d12"}; + +static struct mmp_param_gate_clk apbc_gate_clks[] = { + {PXA1908_CLK_TWSI0, "twsi0_clk", "pll1_32", CLK_SET_RATE_PARENT, APBC_TWSI0, 0x3, 3, 0, 0, NULL}, + {PXA1908_CLK_TWSI1, "twsi1_clk", "pll1_32", CLK_SET_RATE_PARENT, APBC_TWSI1, 0x3, 3, 0, 0, NULL}, + {PXA1908_CLK_TWSI3, "twsi3_clk", "pll1_32", CLK_SET_RATE_PARENT, APBC_TWSI3, 0x3, 3, 0, 0, NULL}, + {PXA1908_CLK_GPIO, "gpio_clk", "vctcxo", CLK_SET_RATE_PARENT, APBC_GPIO, 0x3, 3, 0, 0, NULL}, + {PXA1908_CLK_KPC, "kpc_clk", "clk32", CLK_SET_RATE_PARENT, APBC_KPC, 0x3, 3, 0, MMP_CLK_GATE_NEED_DELAY, NULL}, + {PXA1908_CLK_RTC, "rtc_clk", "clk32", CLK_SET_RATE_PARENT, APBC_RTC, 0x83, 0x83, 0, MMP_CLK_GATE_NEED_DELAY, NULL}, + {PXA1908_CLK_PWM1, "pwm1_clk", "pwm01_apb_share", CLK_SET_RATE_PARENT, APBC_PWM1, 0x2, 2, 0, 0, NULL}, + {PXA1908_CLK_PWM3, "pwm3_clk", "pwm23_apb_share", CLK_SET_RATE_PARENT, APBC_PWM3, 0x2, 2, 0, 0, NULL}, + {PXA1908_CLK_UART0, "uart0_clk", "uart0_mux", CLK_SET_RATE_PARENT, APBC_UART0, 0x3, 3, 0, 0, &uart0_lock}, + {PXA1908_CLK_UART1, "uart1_clk", "uart1_mux", CLK_SET_RATE_PARENT, APBC_UART1, 0x3, 3, 0, 0, &uart1_lock}, + {PXA1908_CLK_THERMAL, "thermal_clk", NULL, 0, APBC_THERMAL, 0x3, 3, 0, 0, NULL}, + {PXA1908_CLK_IPC_RST, "ipc_clk", NULL, 0, APBC_IPC_RST, 0x3, 3, 0, 0, NULL}, + {PXA1908_CLK_SSP0, "ssp0_clk", "ssp0_mux", 0, APBC_SSP0, 0x3, 3, 0, 0, NULL}, + {PXA1908_CLK_SSP2, "ssp2_clk", "ssp2_mux", 0, APBC_SSP2, 0x3, 3, 0, 0, NULL}, +}; + +static struct mmp_param_gate_clk apbc_gate_no_reset_clks[] = { + {PXA1908_CLK_PWM0, "pwm0_clk", "pwm01_apb_share", CLK_SET_RATE_PARENT, APBC_PWM0, 0x2, 2, 0, 0, &pwm0_lock}, + {PXA1908_CLK_PWM2, "pwm2_clk", "pwm23_apb_share", CLK_SET_RATE_PARENT, APBC_PWM2, 0x2, 2, 0, 0, NULL}, +}; + +static struct mmp_param_mux_clk apbc_mux_clks[] = { + {0, "uart0_mux", uart_parent_names, ARRAY_SIZE(uart_parent_names), CLK_SET_RATE_PARENT, APBC_UART0, 4, 3, 0, &uart0_lock}, + {0, "uart1_mux", uart_parent_names, ARRAY_SIZE(uart_parent_names), CLK_SET_RATE_PARENT, APBC_UART1, 4, 3, 0, &uart1_lock}, + {0, "ssp0_mux", ssp_parent_names, ARRAY_SIZE(ssp_parent_names), 0, APBC_SSP0, 4, 3, 0, NULL}, + {0, "ssp2_mux", ssp_parent_names, ARRAY_SIZE(ssp_parent_names), 0, APBC_SSP2, 4, 3, 0, NULL}, +}; + +static void pxa1908_apb_periph_clk_init(struct pxa1908_clk_unit *pxa_unit) +{ + struct mmp_clk_unit *unit = &pxa_unit->unit; + struct clk *clk; + + mmp_clk_register_gate(NULL, "pwm01_apb_share", "pll1_d48", + CLK_SET_RATE_PARENT, + pxa_unit->base + APBC_PWM0, + 0x5, 1, 0, 0, &pwm0_lock); + mmp_clk_register_gate(NULL, "pwm23_apb_share", "pll1_d48", + CLK_SET_RATE_PARENT, + pxa_unit->base + APBC_PWM2, + 0x5, 1, 0, 0, &pwm2_lock); + clk = mmp_clk_register_apbc("swjtag", NULL, + pxa_unit->base + APBC_SWJTAG, 10, 0, NULL); + mmp_clk_add(unit, PXA1908_CLK_SWJTAG, clk); + mmp_register_mux_clks(unit, apbc_mux_clks, pxa_unit->base, + ARRAY_SIZE(apbc_mux_clks)); + mmp_register_gate_clks(unit, apbc_gate_clks, pxa_unit->base, + ARRAY_SIZE(apbc_gate_clks)); + mmp_register_gate_clks(unit, apbc_gate_no_reset_clks, pxa_unit->base, + ARRAY_SIZE(apbc_gate_no_reset_clks)); +} + +/* Taken from clk-of-pxa1928.c */ +static void pxa1908_clk_reset_init(struct device_node *np, + struct pxa1908_clk_unit *pxa_unit) +{ + struct mmp_clk_reset_cell *cells; + int nr_cells = ARRAY_SIZE(apbc_gate_clks); + + cells = kzalloc_objs(*cells, nr_cells); + if (!cells) + return; + + for (int i = 0; i < nr_cells; i++) { + cells[i].clk_id = apbc_gate_clks[i].id; + cells[i].reg = pxa_unit->base + apbc_gate_clks[i].offset; + cells[i].bits = BIT(2); + cells[i].flags = 0; + cells[i].lock = apbc_gate_clks[i].lock; + } + + mmp_clk_reset_register(np, cells, nr_cells); +} + +static int pxa1908_apbc_probe(struct platform_device *pdev) +{ + struct pxa1908_clk_unit *pxa_unit; + + pxa_unit = devm_kzalloc(&pdev->dev, sizeof(*pxa_unit), GFP_KERNEL); + if (!pxa_unit) + return -ENOMEM; + + pxa_unit->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(pxa_unit->base)) + return PTR_ERR(pxa_unit->base); + + mmp_clk_init(pdev->dev.of_node, &pxa_unit->unit, APBC_NR_CLKS); + + pxa1908_apb_periph_clk_init(pxa_unit); + + pxa1908_clk_reset_init(pdev->dev.of_node, pxa_unit); + + return 0; +} + +static const struct of_device_id pxa1908_apbc_match_table[] = { + { .compatible = "marvell,pxa1908-apbc" }, + { } +}; +MODULE_DEVICE_TABLE(of, pxa1908_apbc_match_table); + +static struct platform_driver pxa1908_apbc_driver = { + .probe = pxa1908_apbc_probe, + .driver = { + .name = "pxa1908-apbc", + .of_match_table = pxa1908_apbc_match_table + } +}; +module_platform_driver(pxa1908_apbc_driver); + +MODULE_AUTHOR("Duje Mihanović <duje.mihanovic@skole.hr>"); +MODULE_DESCRIPTION("Marvell PXA1908 APBC Clock Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/clk/mmp/clk-pxa1908-apbcp.c b/drivers/clk/mmp/clk-pxa1908-apbcp.c new file mode 100644 index 000000000..4e646e8b7 --- /dev/null +++ b/drivers/clk/mmp/clk-pxa1908-apbcp.c @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include <linux/clk-provider.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> + +#include <dt-bindings/clock/marvell,pxa1908.h> + +#include "clk.h" +#include "reset.h" + +#define APBCP_UART2 0x1c +#define APBCP_TWSI2 0x28 +#define APBCP_AICER 0x38 + +#define APBCP_NR_CLKS 4 + +struct pxa1908_clk_unit { + struct mmp_clk_unit unit; + void __iomem *base; +}; + +static DEFINE_SPINLOCK(uart2_lock); + +static const char * const uart_parent_names[] = {"pll1_117", "uart_pll"}; + +static struct mmp_param_gate_clk apbcp_gate_clks[] = { + {PXA1908_CLK_UART2, "uart2_clk", "uart2_mux", CLK_SET_RATE_PARENT, APBCP_UART2, 0x3, 0x3, 0x0, 0, &uart2_lock}, + {PXA1908_CLK_TWSI2, "twsi2_clk", "pll1_32", CLK_SET_RATE_PARENT, APBCP_TWSI2, 0x3, 0x3, 0x0, 0, NULL}, + {PXA1908_CLK_AICER, "ripc_clk", NULL, 0, APBCP_AICER, 0x3, 0x2, 0x0, 0, NULL}, +}; + +static struct mmp_param_mux_clk apbcp_mux_clks[] = { + {0, "uart2_mux", uart_parent_names, ARRAY_SIZE(uart_parent_names), CLK_SET_RATE_PARENT, APBCP_UART2, 4, 3, 0, &uart2_lock}, +}; + +static void pxa1908_apb_p_periph_clk_init(struct pxa1908_clk_unit *pxa_unit) +{ + struct mmp_clk_unit *unit = &pxa_unit->unit; + + mmp_register_mux_clks(unit, apbcp_mux_clks, pxa_unit->base, + ARRAY_SIZE(apbcp_mux_clks)); + mmp_register_gate_clks(unit, apbcp_gate_clks, pxa_unit->base, + ARRAY_SIZE(apbcp_gate_clks)); +} + +/* Taken from clk-of-pxa1928.c */ +static void pxa1908_clk_reset_init(struct device_node *np, + struct pxa1908_clk_unit *pxa_unit) +{ + struct mmp_clk_reset_cell *cells; + int nr_cells = ARRAY_SIZE(apbcp_gate_clks); + + cells = kzalloc_objs(*cells, nr_cells); + if (!cells) + return; + + for (int i = 0; i < nr_cells; i++) { + cells[i].clk_id = apbcp_gate_clks[i].id; + cells[i].reg = pxa_unit->base + apbcp_gate_clks[i].offset; + cells[i].bits = BIT(2); + cells[i].flags = 0; + cells[i].lock = apbcp_gate_clks[i].lock; + } + + mmp_clk_reset_register(np, cells, nr_cells); +} + +static int pxa1908_apbcp_probe(struct platform_device *pdev) +{ + struct pxa1908_clk_unit *pxa_unit; + + pxa_unit = devm_kzalloc(&pdev->dev, sizeof(*pxa_unit), GFP_KERNEL); + if (!pxa_unit) + return -ENOMEM; + + pxa_unit->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(pxa_unit->base)) + return PTR_ERR(pxa_unit->base); + + mmp_clk_init(pdev->dev.of_node, &pxa_unit->unit, APBCP_NR_CLKS); + + pxa1908_apb_p_periph_clk_init(pxa_unit); + + pxa1908_clk_reset_init(pdev->dev.of_node, pxa_unit); + + return 0; +} + +static const struct of_device_id pxa1908_apbcp_match_table[] = { + { .compatible = "marvell,pxa1908-apbcp" }, + { } +}; +MODULE_DEVICE_TABLE(of, pxa1908_apbcp_match_table); + +static struct platform_driver pxa1908_apbcp_driver = { + .probe = pxa1908_apbcp_probe, + .driver = { + .name = "pxa1908-apbcp", + .of_match_table = pxa1908_apbcp_match_table + } +}; +module_platform_driver(pxa1908_apbcp_driver); + +MODULE_AUTHOR("Duje Mihanović <duje.mihanovic@skole.hr>"); +MODULE_DESCRIPTION("Marvell PXA1908 APBCP Clock Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/clk/mmp/clk-pxa1908-apmu.c b/drivers/clk/mmp/clk-pxa1908-apmu.c new file mode 100644 index 000000000..7594a495a --- /dev/null +++ b/drivers/clk/mmp/clk-pxa1908-apmu.c @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include <linux/auxiliary_bus.h> +#include <linux/clk-provider.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> + +#include <dt-bindings/clock/marvell,pxa1908.h> + +#include "clk.h" + +#define APMU_CLK_GATE_CTRL 0x40 +#define APMU_CCIC1 0x24 +#define APMU_ISP 0x38 +#define APMU_DSI1 0x44 +#define APMU_DISP1 0x4c +#define APMU_CCIC0 0x50 +#define APMU_SDH0 0x54 +#define APMU_SDH1 0x58 +#define APMU_USB 0x5c +#define APMU_NF 0x60 +#define APMU_VPU 0xa4 +#define APMU_GC 0xcc +#define APMU_SDH2 0xe0 +#define APMU_GC2D 0xf4 +#define APMU_TRACE 0x108 +#define APMU_DVC_DFC_DEBUG 0x140 + +#define APMU_NR_CLKS 17 + +struct pxa1908_clk_unit { + struct mmp_clk_unit unit; + void __iomem *base; +}; + +static DEFINE_SPINLOCK(pll1_lock); +static struct mmp_param_general_gate_clk pll1_gate_clks[] = { + {PXA1908_CLK_PLL1_D2_GATE, "pll1_d2_gate", "pll1_d2", 0, APMU_CLK_GATE_CTRL, 29, 0, &pll1_lock}, + {PXA1908_CLK_PLL1_416_GATE, "pll1_416_gate", "pll1_416", 0, APMU_CLK_GATE_CTRL, 27, 0, &pll1_lock}, + {PXA1908_CLK_PLL1_624_GATE, "pll1_624_gate", "pll1_624", 0, APMU_CLK_GATE_CTRL, 26, 0, &pll1_lock}, + {PXA1908_CLK_PLL1_832_GATE, "pll1_832_gate", "pll1_832", 0, APMU_CLK_GATE_CTRL, 30, 0, &pll1_lock}, + {PXA1908_CLK_PLL1_1248_GATE, "pll1_1248_gate", "pll1_1248", 0, APMU_CLK_GATE_CTRL, 28, 0, &pll1_lock}, +}; + +static DEFINE_SPINLOCK(sdh0_lock); +static DEFINE_SPINLOCK(sdh1_lock); +static DEFINE_SPINLOCK(sdh2_lock); + +static const char * const sdh_parent_names[] = {"pll1_416", "pll1_624"}; + +static struct mmp_clk_mix_config sdh_mix_config = { + .reg_info = DEFINE_MIX_REG_INFO(3, 8, 2, 6, 11), +}; + +static struct mmp_param_gate_clk apmu_gate_clks[] = { + {PXA1908_CLK_USB, "usb_clk", NULL, 0, APMU_USB, 0x9, 0x9, 0x1, 0, NULL}, + {PXA1908_CLK_SDH0, "sdh0_clk", "sdh0_mix_clk", CLK_SET_RATE_PARENT | CLK_SET_RATE_UNGATE, APMU_SDH0, 0x12, 0x12, 0x0, 0, &sdh0_lock}, + {PXA1908_CLK_SDH1, "sdh1_clk", "sdh1_mix_clk", CLK_SET_RATE_PARENT | CLK_SET_RATE_UNGATE, APMU_SDH1, 0x12, 0x12, 0x0, 0, &sdh1_lock}, + {PXA1908_CLK_SDH2, "sdh2_clk", "sdh2_mix_clk", CLK_SET_RATE_PARENT | CLK_SET_RATE_UNGATE, APMU_SDH2, 0x12, 0x12, 0x0, 0, &sdh2_lock} +}; + +static void pxa1908_axi_periph_clk_init(struct pxa1908_clk_unit *pxa_unit) +{ + struct mmp_clk_unit *unit = &pxa_unit->unit; + + mmp_register_general_gate_clks(unit, pll1_gate_clks, + pxa_unit->base, ARRAY_SIZE(pll1_gate_clks)); + + sdh_mix_config.reg_info.reg_clk_ctrl = pxa_unit->base + APMU_SDH0; + mmp_clk_register_mix(NULL, "sdh0_mix_clk", sdh_parent_names, + ARRAY_SIZE(sdh_parent_names), CLK_SET_RATE_PARENT, + &sdh_mix_config, &sdh0_lock); + sdh_mix_config.reg_info.reg_clk_ctrl = pxa_unit->base + APMU_SDH1; + mmp_clk_register_mix(NULL, "sdh1_mix_clk", sdh_parent_names, + ARRAY_SIZE(sdh_parent_names), CLK_SET_RATE_PARENT, + &sdh_mix_config, &sdh1_lock); + sdh_mix_config.reg_info.reg_clk_ctrl = pxa_unit->base + APMU_SDH2; + mmp_clk_register_mix(NULL, "sdh2_mix_clk", sdh_parent_names, + ARRAY_SIZE(sdh_parent_names), CLK_SET_RATE_PARENT, + &sdh_mix_config, &sdh2_lock); + + mmp_register_gate_clks(unit, apmu_gate_clks, pxa_unit->base, + ARRAY_SIZE(apmu_gate_clks)); +} + +static int pxa1908_apmu_probe(struct platform_device *pdev) +{ + struct pxa1908_clk_unit *pxa_unit; + struct auxiliary_device *adev; + + pxa_unit = devm_kzalloc(&pdev->dev, sizeof(*pxa_unit), GFP_KERNEL); + if (!pxa_unit) + return -ENOMEM; + + pxa_unit->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(pxa_unit->base)) + return PTR_ERR(pxa_unit->base); + + adev = devm_auxiliary_device_create(&pdev->dev, "power", NULL); + if (IS_ERR(adev)) + return dev_err_probe(&pdev->dev, PTR_ERR(adev), + "Failed to register power controller\n"); + + mmp_clk_init(pdev->dev.of_node, &pxa_unit->unit, APMU_NR_CLKS); + + pxa1908_axi_periph_clk_init(pxa_unit); + + return 0; +} + +static const struct of_device_id pxa1908_apmu_match_table[] = { + { .compatible = "marvell,pxa1908-apmu" }, + { } +}; +MODULE_DEVICE_TABLE(of, pxa1908_apmu_match_table); + +static struct platform_driver pxa1908_apmu_driver = { + .probe = pxa1908_apmu_probe, + .driver = { + .name = "pxa1908-apmu", + .of_match_table = pxa1908_apmu_match_table + } +}; +module_platform_driver(pxa1908_apmu_driver); + +MODULE_AUTHOR("Duje Mihanović <duje.mihanovic@skole.hr>"); +MODULE_DESCRIPTION("Marvell PXA1908 APMU Clock Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/clk/mmp/clk-pxa1908-mpmu.c b/drivers/clk/mmp/clk-pxa1908-mpmu.c new file mode 100644 index 000000000..90b4b2488 --- /dev/null +++ b/drivers/clk/mmp/clk-pxa1908-mpmu.c @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include <linux/bits.h> +#include <linux/clk-provider.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/units.h> + +#include <dt-bindings/clock/marvell,pxa1908.h> + +#include "clk.h" + +#define MPMU_UART_PLL 0x14 + +#define MPMU_NR_CLKS 39 + +struct pxa1908_clk_unit { + struct mmp_clk_unit unit; + void __iomem *base; +}; + +static struct mmp_param_fixed_rate_clk fixed_rate_clks[] = { + {PXA1908_CLK_CLK32, "clk32", NULL, 0, 32768}, + {PXA1908_CLK_VCTCXO, "vctcxo", NULL, 0, 26 * HZ_PER_MHZ}, + {PXA1908_CLK_PLL1_624, "pll1_624", NULL, 0, 624 * HZ_PER_MHZ}, + {PXA1908_CLK_PLL1_416, "pll1_416", NULL, 0, 416 * HZ_PER_MHZ}, + {PXA1908_CLK_PLL1_499, "pll1_499", NULL, 0, 499 * HZ_PER_MHZ}, + {PXA1908_CLK_PLL1_832, "pll1_832", NULL, 0, 832 * HZ_PER_MHZ}, + {PXA1908_CLK_PLL1_1248, "pll1_1248", NULL, 0, 1248 * HZ_PER_MHZ}, +}; + +static struct mmp_param_fixed_factor_clk fixed_factor_clks[] = { + {PXA1908_CLK_PLL1_D2, "pll1_d2", "pll1_624", 1, 2, 0}, + {PXA1908_CLK_PLL1_D4, "pll1_d4", "pll1_d2", 1, 2, 0}, + {PXA1908_CLK_PLL1_D6, "pll1_d6", "pll1_d2", 1, 3, 0}, + {PXA1908_CLK_PLL1_D8, "pll1_d8", "pll1_d4", 1, 2, 0}, + {PXA1908_CLK_PLL1_D12, "pll1_d12", "pll1_d6", 1, 2, 0}, + {PXA1908_CLK_PLL1_D13, "pll1_d13", "pll1_624", 1, 13, 0}, + {PXA1908_CLK_PLL1_D16, "pll1_d16", "pll1_d8", 1, 2, 0}, + {PXA1908_CLK_PLL1_D24, "pll1_d24", "pll1_d12", 1, 2, 0}, + {PXA1908_CLK_PLL1_D48, "pll1_d48", "pll1_d24", 1, 2, 0}, + {PXA1908_CLK_PLL1_D96, "pll1_d96", "pll1_d48", 1, 2, 0}, + {PXA1908_CLK_PLL1_32, "pll1_32", "pll1_d13", 2, 3, 0}, + {PXA1908_CLK_PLL1_208, "pll1_208", "pll1_d2", 2, 3, 0}, + {PXA1908_CLK_PLL1_117, "pll1_117", "pll1_624", 3, 16, 0}, +}; + +static struct u32_fract uart_factor_tbl[] = { + {.numerator = 8125, .denominator = 1536}, /* 14.745MHz */ +}; + +static struct mmp_clk_factor_masks uart_factor_masks = { + .factor = 2, + .num_mask = GENMASK(12, 0), + .den_mask = GENMASK(12, 0), + .num_shift = 16, + .den_shift = 0, +}; + +static void pxa1908_pll_init(struct pxa1908_clk_unit *pxa_unit) +{ + struct mmp_clk_unit *unit = &pxa_unit->unit; + + mmp_register_fixed_rate_clks(unit, fixed_rate_clks, + ARRAY_SIZE(fixed_rate_clks)); + + mmp_register_fixed_factor_clks(unit, fixed_factor_clks, + ARRAY_SIZE(fixed_factor_clks)); + + mmp_clk_register_factor("uart_pll", "pll1_d4", + CLK_SET_RATE_PARENT, + pxa_unit->base + MPMU_UART_PLL, + &uart_factor_masks, uart_factor_tbl, + ARRAY_SIZE(uart_factor_tbl), NULL); +} + +static int pxa1908_mpmu_probe(struct platform_device *pdev) +{ + struct pxa1908_clk_unit *pxa_unit; + + pxa_unit = devm_kzalloc(&pdev->dev, sizeof(*pxa_unit), GFP_KERNEL); + if (!pxa_unit) + return -ENOMEM; + + pxa_unit->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(pxa_unit->base)) + return PTR_ERR(pxa_unit->base); + + mmp_clk_init(pdev->dev.of_node, &pxa_unit->unit, MPMU_NR_CLKS); + + pxa1908_pll_init(pxa_unit); + + return 0; +} + +static const struct of_device_id pxa1908_mpmu_match_table[] = { + { .compatible = "marvell,pxa1908-mpmu" }, + { } +}; +MODULE_DEVICE_TABLE(of, pxa1908_mpmu_match_table); + +static struct platform_driver pxa1908_mpmu_driver = { + .probe = pxa1908_mpmu_probe, + .driver = { + .name = "pxa1908-mpmu", + .of_match_table = pxa1908_mpmu_match_table + } +}; +module_platform_driver(pxa1908_mpmu_driver); + +MODULE_AUTHOR("Duje Mihanović <duje.mihanovic@skole.hr>"); +MODULE_DESCRIPTION("Marvell PXA1908 MPMU Clock Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/clk/mmp/clk.c b/drivers/clk/mmp/clk.c new file mode 100644 index 000000000..48b746bef --- /dev/null +++ b/drivers/clk/mmp/clk.c @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <linux/io.h> +#include <linux/clk-provider.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/of_address.h> + +#include "clk.h" + +void mmp_clk_init(struct device_node *np, struct mmp_clk_unit *unit, + int nr_clks) +{ + struct clk **clk_table; + + clk_table = kzalloc_objs(struct clk *, nr_clks); + if (!clk_table) + return; + + unit->clk_table = clk_table; + unit->nr_clks = nr_clks; + unit->clk_data.clks = clk_table; + unit->clk_data.clk_num = nr_clks; + of_clk_add_provider(np, of_clk_src_onecell_get, &unit->clk_data); +} + +void mmp_register_fixed_rate_clks(struct mmp_clk_unit *unit, + struct mmp_param_fixed_rate_clk *clks, + int size) +{ + int i; + struct clk *clk; + + for (i = 0; i < size; i++) { + clk = clk_register_fixed_rate(NULL, clks[i].name, + clks[i].parent_name, + clks[i].flags, + clks[i].fixed_rate); + if (IS_ERR(clk)) { + pr_err("%s: failed to register clock %s\n", + __func__, clks[i].name); + continue; + } + if (clks[i].id) + unit->clk_table[clks[i].id] = clk; + } +} + +void mmp_register_fixed_factor_clks(struct mmp_clk_unit *unit, + struct mmp_param_fixed_factor_clk *clks, + int size) +{ + struct clk *clk; + int i; + + for (i = 0; i < size; i++) { + clk = clk_register_fixed_factor(NULL, clks[i].name, + clks[i].parent_name, + clks[i].flags, clks[i].mult, + clks[i].div); + if (IS_ERR(clk)) { + pr_err("%s: failed to register clock %s\n", + __func__, clks[i].name); + continue; + } + if (clks[i].id) + unit->clk_table[clks[i].id] = clk; + } +} + +void mmp_register_general_gate_clks(struct mmp_clk_unit *unit, + struct mmp_param_general_gate_clk *clks, + void __iomem *base, int size) +{ + struct clk *clk; + int i; + + for (i = 0; i < size; i++) { + clk = clk_register_gate(NULL, clks[i].name, + clks[i].parent_name, + clks[i].flags, + base + clks[i].offset, + clks[i].bit_idx, + clks[i].gate_flags, + clks[i].lock); + + if (IS_ERR(clk)) { + pr_err("%s: failed to register clock %s\n", + __func__, clks[i].name); + continue; + } + if (clks[i].id) + unit->clk_table[clks[i].id] = clk; + } +} + +void mmp_register_gate_clks(struct mmp_clk_unit *unit, + struct mmp_param_gate_clk *clks, + void __iomem *base, int size) +{ + struct clk *clk; + int i; + + for (i = 0; i < size; i++) { + clk = mmp_clk_register_gate(NULL, clks[i].name, + clks[i].parent_name, + clks[i].flags, + base + clks[i].offset, + clks[i].mask, + clks[i].val_enable, + clks[i].val_disable, + clks[i].gate_flags, + clks[i].lock); + + if (IS_ERR(clk)) { + pr_err("%s: failed to register clock %s\n", + __func__, clks[i].name); + continue; + } + if (clks[i].id) + unit->clk_table[clks[i].id] = clk; + } +} + +void mmp_register_mux_clks(struct mmp_clk_unit *unit, + struct mmp_param_mux_clk *clks, + void __iomem *base, int size) +{ + struct clk *clk; + int i; + + for (i = 0; i < size; i++) { + clk = clk_register_mux(NULL, clks[i].name, + clks[i].parent_name, + clks[i].num_parents, + clks[i].flags, + base + clks[i].offset, + clks[i].shift, + clks[i].width, + clks[i].mux_flags, + clks[i].lock); + + if (IS_ERR(clk)) { + pr_err("%s: failed to register clock %s\n", + __func__, clks[i].name); + continue; + } + if (clks[i].id) + unit->clk_table[clks[i].id] = clk; + } +} + +void mmp_register_div_clks(struct mmp_clk_unit *unit, + struct mmp_param_div_clk *clks, + void __iomem *base, int size) +{ + struct clk *clk; + int i; + + for (i = 0; i < size; i++) { + clk = clk_register_divider(NULL, clks[i].name, + clks[i].parent_name, + clks[i].flags, + base + clks[i].offset, + clks[i].shift, + clks[i].width, + clks[i].div_flags, + clks[i].lock); + + if (IS_ERR(clk)) { + pr_err("%s: failed to register clock %s\n", + __func__, clks[i].name); + continue; + } + if (clks[i].id) + unit->clk_table[clks[i].id] = clk; + } +} + +void mmp_clk_add(struct mmp_clk_unit *unit, unsigned int id, + struct clk *clk) +{ + if (IS_ERR_OR_NULL(clk)) { + pr_err("CLK %d has invalid pointer %p\n", id, clk); + return; + } + if (id >= unit->nr_clks) { + pr_err("CLK %d is invalid\n", id); + return; + } + + unit->clk_table[id] = clk; +} diff --git a/drivers/clk/mmp/clk.h b/drivers/clk/mmp/clk.h new file mode 100644 index 000000000..c83cec169 --- /dev/null +++ b/drivers/clk/mmp/clk.h @@ -0,0 +1,261 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __MACH_MMP_CLK_H +#define __MACH_MMP_CLK_H + +#include <linux/clk-provider.h> +#include <linux/math.h> +#include <linux/pm_domain.h> +#include <linux/clkdev.h> + +#define APBC_NO_BUS_CTRL BIT(0) +#define APBC_POWER_CTRL BIT(1) + + +/* Clock type "factor" */ +struct mmp_clk_factor_masks { + unsigned int factor; + unsigned int num_mask; + unsigned int den_mask; + unsigned int num_shift; + unsigned int den_shift; + unsigned int enable_mask; +}; + +struct mmp_clk_factor { + struct clk_hw hw; + void __iomem *base; + struct mmp_clk_factor_masks *masks; + struct u32_fract *ftbl; + unsigned int ftbl_cnt; + spinlock_t *lock; +}; + +extern struct clk *mmp_clk_register_factor(const char *name, + const char *parent_name, unsigned long flags, + void __iomem *base, struct mmp_clk_factor_masks *masks, + struct u32_fract *ftbl, unsigned int ftbl_cnt, + spinlock_t *lock); + +/* Clock type "mix" */ +#define MMP_CLK_BITS_MASK(width, shift) \ + (((1 << (width)) - 1) << (shift)) +#define MMP_CLK_BITS_GET_VAL(data, width, shift) \ + ((data & MMP_CLK_BITS_MASK(width, shift)) >> (shift)) +#define MMP_CLK_BITS_SET_VAL(val, width, shift) \ + (((val) << (shift)) & MMP_CLK_BITS_MASK(width, shift)) + +enum { + MMP_CLK_MIX_TYPE_V1, + MMP_CLK_MIX_TYPE_V2, + MMP_CLK_MIX_TYPE_V3, +}; + +/* The register layout */ +struct mmp_clk_mix_reg_info { + void __iomem *reg_clk_ctrl; + void __iomem *reg_clk_sel; + u8 width_div; + u8 shift_div; + u8 width_mux; + u8 shift_mux; + u8 bit_fc; +}; + +/* The suggested clock table from user. */ +struct mmp_clk_mix_clk_table { + unsigned long rate; + u8 parent_index; + unsigned int divisor; + unsigned int valid; +}; + +struct mmp_clk_mix_config { + struct mmp_clk_mix_reg_info reg_info; + struct mmp_clk_mix_clk_table *table; + unsigned int table_size; + u32 *mux_table; + struct clk_div_table *div_table; + u8 div_flags; + u8 mux_flags; +}; + +struct mmp_clk_mix { + struct clk_hw hw; + struct mmp_clk_mix_reg_info reg_info; + struct mmp_clk_mix_clk_table *table; + u32 *mux_table; + struct clk_div_table *div_table; + unsigned int table_size; + u8 div_flags; + u8 mux_flags; + unsigned int type; + spinlock_t *lock; +}; + +extern const struct clk_ops mmp_clk_mix_ops; +extern struct clk *mmp_clk_register_mix(struct device *dev, + const char *name, + const char * const *parent_names, + u8 num_parents, + unsigned long flags, + struct mmp_clk_mix_config *config, + spinlock_t *lock); + + +/* Clock type "gate". MMP private gate */ +#define MMP_CLK_GATE_NEED_DELAY BIT(0) + +struct mmp_clk_gate { + struct clk_hw hw; + void __iomem *reg; + u32 mask; + u32 val_enable; + u32 val_disable; + unsigned int flags; + spinlock_t *lock; +}; + +extern const struct clk_ops mmp_clk_gate_ops; +extern struct clk *mmp_clk_register_gate(struct device *dev, const char *name, + const char *parent_name, unsigned long flags, + void __iomem *reg, u32 mask, u32 val_enable, + u32 val_disable, unsigned int gate_flags, + spinlock_t *lock); + +extern struct clk *mmp_clk_register_apbc(const char *name, + const char *parent_name, void __iomem *base, + unsigned int delay, unsigned int apbc_flags, spinlock_t *lock); +extern struct clk *mmp_clk_register_apmu(const char *name, + const char *parent_name, void __iomem *base, u32 enable_mask, + spinlock_t *lock); + +struct mmp_clk_unit { + unsigned int nr_clks; + struct clk **clk_table; + struct clk_onecell_data clk_data; +}; + +struct mmp_param_fixed_rate_clk { + unsigned int id; + char *name; + const char *parent_name; + unsigned long flags; + unsigned long fixed_rate; +}; +void mmp_register_fixed_rate_clks(struct mmp_clk_unit *unit, + struct mmp_param_fixed_rate_clk *clks, + int size); + +struct mmp_param_fixed_factor_clk { + unsigned int id; + char *name; + const char *parent_name; + unsigned long mult; + unsigned long div; + unsigned long flags; +}; +void mmp_register_fixed_factor_clks(struct mmp_clk_unit *unit, + struct mmp_param_fixed_factor_clk *clks, + int size); + +struct mmp_param_general_gate_clk { + unsigned int id; + const char *name; + const char *parent_name; + unsigned long flags; + unsigned long offset; + u8 bit_idx; + u8 gate_flags; + spinlock_t *lock; +}; +void mmp_register_general_gate_clks(struct mmp_clk_unit *unit, + struct mmp_param_general_gate_clk *clks, + void __iomem *base, int size); + +struct mmp_param_gate_clk { + unsigned int id; + char *name; + const char *parent_name; + unsigned long flags; + unsigned long offset; + u32 mask; + u32 val_enable; + u32 val_disable; + unsigned int gate_flags; + spinlock_t *lock; +}; +void mmp_register_gate_clks(struct mmp_clk_unit *unit, + struct mmp_param_gate_clk *clks, + void __iomem *base, int size); + +struct mmp_param_mux_clk { + unsigned int id; + char *name; + const char * const *parent_name; + u8 num_parents; + unsigned long flags; + unsigned long offset; + u8 shift; + u8 width; + u8 mux_flags; + spinlock_t *lock; +}; +void mmp_register_mux_clks(struct mmp_clk_unit *unit, + struct mmp_param_mux_clk *clks, + void __iomem *base, int size); + +struct mmp_param_div_clk { + unsigned int id; + char *name; + const char *parent_name; + unsigned long flags; + unsigned long offset; + u8 shift; + u8 width; + u8 div_flags; + spinlock_t *lock; +}; +void mmp_register_div_clks(struct mmp_clk_unit *unit, + struct mmp_param_div_clk *clks, + void __iomem *base, int size); + +struct mmp_param_pll_clk { + unsigned int id; + char *name; + unsigned long default_rate; + unsigned long enable_offset; + u32 enable; + unsigned long offset; + u8 shift; + /* MMP3 specific: */ + unsigned long input_rate; + unsigned long postdiv_offset; + unsigned long postdiv_shift; +}; +void mmp_register_pll_clks(struct mmp_clk_unit *unit, + struct mmp_param_pll_clk *clks, + void __iomem *base, int size); + +#define DEFINE_MIX_REG_INFO(w_d, s_d, w_m, s_m, fc) \ +{ \ + .width_div = (w_d), \ + .shift_div = (s_d), \ + .width_mux = (w_m), \ + .shift_mux = (s_m), \ + .bit_fc = (fc), \ +} + +void mmp_clk_init(struct device_node *np, struct mmp_clk_unit *unit, + int nr_clks); +void mmp_clk_add(struct mmp_clk_unit *unit, unsigned int id, + struct clk *clk); + +/* Power islands */ +#define MMP_PM_DOMAIN_NO_DISABLE BIT(0) + +struct generic_pm_domain *mmp_pm_domain_register(const char *name, + void __iomem *reg, + u32 power_on, u32 reset, u32 clock_enable, + unsigned int flags, spinlock_t *lock); + +#endif diff --git a/drivers/clk/mmp/pwr-island.c b/drivers/clk/mmp/pwr-island.c new file mode 100644 index 000000000..1f5791713 --- /dev/null +++ b/drivers/clk/mmp/pwr-island.c @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MMP PMU power island support + * + * Copyright (C) 2020 Lubomir Rintel <lkundrak@v3.sk> + */ + +#include <linux/pm_domain.h> +#include <linux/slab.h> +#include <linux/io.h> + +#include "clk.h" + +#define to_mmp_pm_domain(genpd) container_of(genpd, struct mmp_pm_domain, genpd) + +struct mmp_pm_domain { + struct generic_pm_domain genpd; + void __iomem *reg; + spinlock_t *lock; + u32 power_on; + u32 reset; + u32 clock_enable; + unsigned int flags; +}; + +static int mmp_pm_domain_power_on(struct generic_pm_domain *genpd) +{ + struct mmp_pm_domain *pm_domain = to_mmp_pm_domain(genpd); + unsigned long flags = 0; + u32 val; + + if (pm_domain->lock) + spin_lock_irqsave(pm_domain->lock, flags); + + val = readl(pm_domain->reg); + + /* Turn on the power island */ + val |= pm_domain->power_on; + writel(val, pm_domain->reg); + + /* Disable isolation */ + val |= 0x100; + writel(val, pm_domain->reg); + + /* Some blocks need to be reset after a power up */ + if (pm_domain->reset || pm_domain->clock_enable) { + u32 after_power_on = val; + + val &= ~pm_domain->reset; + writel(val, pm_domain->reg); + + val |= pm_domain->clock_enable; + writel(val, pm_domain->reg); + + val |= pm_domain->reset; + writel(val, pm_domain->reg); + + writel(after_power_on, pm_domain->reg); + } + + if (pm_domain->lock) + spin_unlock_irqrestore(pm_domain->lock, flags); + + return 0; +} + +static int mmp_pm_domain_power_off(struct generic_pm_domain *genpd) +{ + struct mmp_pm_domain *pm_domain = to_mmp_pm_domain(genpd); + unsigned long flags = 0; + u32 val; + + if (pm_domain->flags & MMP_PM_DOMAIN_NO_DISABLE) + return 0; + + if (pm_domain->lock) + spin_lock_irqsave(pm_domain->lock, flags); + + /* Turn off and isolate the power island. */ + val = readl(pm_domain->reg); + val &= ~pm_domain->power_on; + val &= ~0x100; + writel(val, pm_domain->reg); + + if (pm_domain->lock) + spin_unlock_irqrestore(pm_domain->lock, flags); + + return 0; +} + +struct generic_pm_domain *mmp_pm_domain_register(const char *name, + void __iomem *reg, + u32 power_on, u32 reset, u32 clock_enable, + unsigned int flags, spinlock_t *lock) +{ + struct mmp_pm_domain *pm_domain; + + pm_domain = kzalloc_obj(*pm_domain); + if (!pm_domain) + return ERR_PTR(-ENOMEM); + + pm_domain->reg = reg; + pm_domain->power_on = power_on; + pm_domain->reset = reset; + pm_domain->clock_enable = clock_enable; + pm_domain->flags = flags; + pm_domain->lock = lock; + + pm_domain->genpd.name = name; + pm_domain->genpd.power_on = mmp_pm_domain_power_on; + pm_domain->genpd.power_off = mmp_pm_domain_power_off; + pm_genpd_init(&pm_domain->genpd, NULL, true); + + return &pm_domain->genpd; +} diff --git a/drivers/clk/mmp/reset.c b/drivers/clk/mmp/reset.c new file mode 100644 index 000000000..1e4e105ef --- /dev/null +++ b/drivers/clk/mmp/reset.c @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <linux/slab.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/reset-controller.h> + +#include "reset.h" + +#define rcdev_to_unit(rcdev) container_of(rcdev, struct mmp_clk_reset_unit, rcdev) + +static int mmp_of_reset_xlate(struct reset_controller_dev *rcdev, + const struct of_phandle_args *reset_spec) +{ + struct mmp_clk_reset_unit *unit = rcdev_to_unit(rcdev); + struct mmp_clk_reset_cell *cell; + int i; + + if (WARN_ON(reset_spec->args_count != rcdev->of_reset_n_cells)) + return -EINVAL; + + for (i = 0; i < rcdev->nr_resets; i++) { + cell = &unit->cells[i]; + if (cell->clk_id == reset_spec->args[0]) + break; + } + + if (i == rcdev->nr_resets) + return -EINVAL; + + return i; +} + +static int mmp_clk_reset_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct mmp_clk_reset_unit *unit = rcdev_to_unit(rcdev); + struct mmp_clk_reset_cell *cell; + unsigned long flags = 0; + u32 val; + + cell = &unit->cells[id]; + if (cell->lock) + spin_lock_irqsave(cell->lock, flags); + + val = readl(cell->reg); + val |= cell->bits; + writel(val, cell->reg); + + if (cell->lock) + spin_unlock_irqrestore(cell->lock, flags); + + return 0; +} + +static int mmp_clk_reset_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct mmp_clk_reset_unit *unit = rcdev_to_unit(rcdev); + struct mmp_clk_reset_cell *cell; + unsigned long flags = 0; + u32 val; + + cell = &unit->cells[id]; + if (cell->lock) + spin_lock_irqsave(cell->lock, flags); + + val = readl(cell->reg); + val &= ~cell->bits; + writel(val, cell->reg); + + if (cell->lock) + spin_unlock_irqrestore(cell->lock, flags); + + return 0; +} + +static const struct reset_control_ops mmp_clk_reset_ops = { + .assert = mmp_clk_reset_assert, + .deassert = mmp_clk_reset_deassert, +}; + +void mmp_clk_reset_register(struct device_node *np, + struct mmp_clk_reset_cell *cells, int nr_resets) +{ + struct mmp_clk_reset_unit *unit; + + unit = kzalloc_obj(*unit); + if (!unit) + return; + + unit->cells = cells; + unit->rcdev.of_reset_n_cells = 1; + unit->rcdev.nr_resets = nr_resets; + unit->rcdev.ops = &mmp_clk_reset_ops; + unit->rcdev.of_node = np; + unit->rcdev.of_xlate = mmp_of_reset_xlate; + + reset_controller_register(&unit->rcdev); +} diff --git a/drivers/clk/mmp/reset.h b/drivers/clk/mmp/reset.h new file mode 100644 index 000000000..3d0470ca3 --- /dev/null +++ b/drivers/clk/mmp/reset.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __MACH_MMP_CLK_RESET_H +#define __MACH_MMP_CLK_RESET_H + +#include <linux/reset-controller.h> + +#define MMP_RESET_INVERT 1 + +struct mmp_clk_reset_cell { + unsigned int clk_id; + void __iomem *reg; + u32 bits; + unsigned int flags; + spinlock_t *lock; +}; + +struct mmp_clk_reset_unit { + struct reset_controller_dev rcdev; + struct mmp_clk_reset_cell *cells; +}; + +#ifdef CONFIG_RESET_CONTROLLER +void mmp_clk_reset_register(struct device_node *np, + struct mmp_clk_reset_cell *cells, int nr_resets); +#else +static inline void mmp_clk_reset_register(struct device_node *np, + struct mmp_clk_reset_cell *cells, int nr_resets) +{ +} +#endif + +#endif |
