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/berlin | |
| 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/berlin')
| -rw-r--r-- | drivers/clk/berlin/Makefile | 5 | ||||
| -rw-r--r-- | drivers/clk/berlin/berlin2-avpll.c | 382 | ||||
| -rw-r--r-- | drivers/clk/berlin/berlin2-avpll.h | 21 | ||||
| -rw-r--r-- | drivers/clk/berlin/berlin2-div.c | 256 | ||||
| -rw-r--r-- | drivers/clk/berlin/berlin2-div.h | 78 | ||||
| -rw-r--r-- | drivers/clk/berlin/berlin2-pll.c | 99 | ||||
| -rw-r--r-- | drivers/clk/berlin/berlin2-pll.h | 23 | ||||
| -rw-r--r-- | drivers/clk/berlin/bg2.c | 687 | ||||
| -rw-r--r-- | drivers/clk/berlin/bg2q.c | 386 | ||||
| -rw-r--r-- | drivers/clk/berlin/common.h | 18 |
10 files changed, 1955 insertions, 0 deletions
diff --git a/drivers/clk/berlin/Makefile b/drivers/clk/berlin/Makefile new file mode 100644 index 000000000..3733733a4 --- /dev/null +++ b/drivers/clk/berlin/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-y += berlin2-avpll.o berlin2-pll.o berlin2-div.o +obj-$(CONFIG_MACH_BERLIN_BG2) += bg2.o +obj-$(CONFIG_MACH_BERLIN_BG2CD) += bg2.o +obj-$(CONFIG_MACH_BERLIN_BG2Q) += bg2q.o diff --git a/drivers/clk/berlin/berlin2-avpll.c b/drivers/clk/berlin/berlin2-avpll.c new file mode 100644 index 000000000..c801d4239 --- /dev/null +++ b/drivers/clk/berlin/berlin2-avpll.c @@ -0,0 +1,382 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2014 Marvell Technology Group Ltd. + * + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com> + * Alexandre Belloni <alexandre.belloni@free-electrons.com> + */ +#include <linux/clk-provider.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/slab.h> + +#include "berlin2-avpll.h" + +/* + * Berlin2 SoCs comprise up to two PLLs called AVPLL built upon a + * VCO with 8 channels each, channel 8 is the odd-one-out and does + * not provide mul/div. + * + * Unfortunately, its registers are not named but just numbered. To + * get in at least some kind of structure, we split each AVPLL into + * the VCOs and each channel into separate clock drivers. + * + * Also, here and there the VCO registers are a bit different with + * respect to bit shifts. Make sure to add a comment for those. + */ +#define NUM_CHANNELS 8 + +#define AVPLL_CTRL(x) ((x) * 0x4) + +#define VCO_CTRL0 AVPLL_CTRL(0) +/* BG2/BG2CDs VCO_B has an additional shift of 4 for its VCO_CTRL0 reg */ +#define VCO_RESET BIT(0) +#define VCO_POWERUP BIT(1) +#define VCO_INTERPOL_SHIFT 2 +#define VCO_INTERPOL_MASK (0xf << VCO_INTERPOL_SHIFT) +#define VCO_REG1V45_SEL_SHIFT 6 +#define VCO_REG1V45_SEL(x) ((x) << VCO_REG1V45_SEL_SHIFT) +#define VCO_REG1V45_SEL_1V40 VCO_REG1V45_SEL(0) +#define VCO_REG1V45_SEL_1V45 VCO_REG1V45_SEL(1) +#define VCO_REG1V45_SEL_1V50 VCO_REG1V45_SEL(2) +#define VCO_REG1V45_SEL_1V55 VCO_REG1V45_SEL(3) +#define VCO_REG1V45_SEL_MASK VCO_REG1V45_SEL(3) +#define VCO_REG0V9_SEL_SHIFT 8 +#define VCO_REG0V9_SEL_MASK (0xf << VCO_REG0V9_SEL_SHIFT) +#define VCO_VTHCAL_SHIFT 12 +#define VCO_VTHCAL(x) ((x) << VCO_VTHCAL_SHIFT) +#define VCO_VTHCAL_0V90 VCO_VTHCAL(0) +#define VCO_VTHCAL_0V95 VCO_VTHCAL(1) +#define VCO_VTHCAL_1V00 VCO_VTHCAL(2) +#define VCO_VTHCAL_1V05 VCO_VTHCAL(3) +#define VCO_VTHCAL_MASK VCO_VTHCAL(3) +#define VCO_KVCOEXT_SHIFT 14 +#define VCO_KVCOEXT_MASK (0x3 << VCO_KVCOEXT_SHIFT) +#define VCO_KVCOEXT_ENABLE BIT(17) +#define VCO_V2IEXT_SHIFT 18 +#define VCO_V2IEXT_MASK (0xf << VCO_V2IEXT_SHIFT) +#define VCO_V2IEXT_ENABLE BIT(22) +#define VCO_SPEED_SHIFT 23 +#define VCO_SPEED(x) ((x) << VCO_SPEED_SHIFT) +#define VCO_SPEED_1G08_1G21 VCO_SPEED(0) +#define VCO_SPEED_1G21_1G40 VCO_SPEED(1) +#define VCO_SPEED_1G40_1G61 VCO_SPEED(2) +#define VCO_SPEED_1G61_1G86 VCO_SPEED(3) +#define VCO_SPEED_1G86_2G00 VCO_SPEED(4) +#define VCO_SPEED_2G00_2G22 VCO_SPEED(5) +#define VCO_SPEED_2G22 VCO_SPEED(6) +#define VCO_SPEED_MASK VCO_SPEED(0x7) +#define VCO_CLKDET_ENABLE BIT(26) +#define VCO_CTRL1 AVPLL_CTRL(1) +#define VCO_REFDIV_SHIFT 0 +#define VCO_REFDIV(x) ((x) << VCO_REFDIV_SHIFT) +#define VCO_REFDIV_1 VCO_REFDIV(0) +#define VCO_REFDIV_2 VCO_REFDIV(1) +#define VCO_REFDIV_4 VCO_REFDIV(2) +#define VCO_REFDIV_3 VCO_REFDIV(3) +#define VCO_REFDIV_MASK VCO_REFDIV(0x3f) +#define VCO_FBDIV_SHIFT 6 +#define VCO_FBDIV(x) ((x) << VCO_FBDIV_SHIFT) +#define VCO_FBDIV_MASK VCO_FBDIV(0xff) +#define VCO_ICP_SHIFT 14 +/* PLL Charge Pump Current = 10uA * (x + 1) */ +#define VCO_ICP(x) ((x) << VCO_ICP_SHIFT) +#define VCO_ICP_MASK VCO_ICP(0xf) +#define VCO_LOAD_CAP BIT(18) +#define VCO_CALIBRATION_START BIT(19) +#define VCO_FREQOFFSETn(x) AVPLL_CTRL(3 + (x)) +#define VCO_FREQOFFSET_MASK 0x7ffff +#define VCO_CTRL10 AVPLL_CTRL(10) +#define VCO_POWERUP_CH1 BIT(20) +#define VCO_CTRL11 AVPLL_CTRL(11) +#define VCO_CTRL12 AVPLL_CTRL(12) +#define VCO_CTRL13 AVPLL_CTRL(13) +#define VCO_CTRL14 AVPLL_CTRL(14) +#define VCO_CTRL15 AVPLL_CTRL(15) +#define VCO_SYNC1n(x) AVPLL_CTRL(15 + (x)) +#define VCO_SYNC1_MASK 0x1ffff +#define VCO_SYNC2n(x) AVPLL_CTRL(23 + (x)) +#define VCO_SYNC2_MASK 0x1ffff +#define VCO_CTRL30 AVPLL_CTRL(30) +#define VCO_DPLL_CH1_ENABLE BIT(17) + +struct berlin2_avpll_vco { + struct clk_hw hw; + void __iomem *base; + u8 flags; +}; + +#define to_avpll_vco(hw) container_of(hw, struct berlin2_avpll_vco, hw) + +static int berlin2_avpll_vco_is_enabled(struct clk_hw *hw) +{ + struct berlin2_avpll_vco *vco = to_avpll_vco(hw); + u32 reg; + + reg = readl_relaxed(vco->base + VCO_CTRL0); + if (vco->flags & BERLIN2_AVPLL_BIT_QUIRK) + reg >>= 4; + + return !!(reg & VCO_POWERUP); +} + +static int berlin2_avpll_vco_enable(struct clk_hw *hw) +{ + struct berlin2_avpll_vco *vco = to_avpll_vco(hw); + u32 reg; + + reg = readl_relaxed(vco->base + VCO_CTRL0); + if (vco->flags & BERLIN2_AVPLL_BIT_QUIRK) + reg |= VCO_POWERUP << 4; + else + reg |= VCO_POWERUP; + writel_relaxed(reg, vco->base + VCO_CTRL0); + + return 0; +} + +static void berlin2_avpll_vco_disable(struct clk_hw *hw) +{ + struct berlin2_avpll_vco *vco = to_avpll_vco(hw); + u32 reg; + + reg = readl_relaxed(vco->base + VCO_CTRL0); + if (vco->flags & BERLIN2_AVPLL_BIT_QUIRK) + reg &= ~(VCO_POWERUP << 4); + else + reg &= ~VCO_POWERUP; + writel_relaxed(reg, vco->base + VCO_CTRL0); +} + +static u8 vco_refdiv[] = { 1, 2, 4, 3 }; + +static unsigned long +berlin2_avpll_vco_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) +{ + struct berlin2_avpll_vco *vco = to_avpll_vco(hw); + u32 reg, refdiv, fbdiv; + u64 freq = parent_rate; + + /* AVPLL VCO frequency: Fvco = (Fref / refdiv) * fbdiv */ + reg = readl_relaxed(vco->base + VCO_CTRL1); + refdiv = (reg & VCO_REFDIV_MASK) >> VCO_REFDIV_SHIFT; + refdiv = vco_refdiv[refdiv]; + fbdiv = (reg & VCO_FBDIV_MASK) >> VCO_FBDIV_SHIFT; + freq *= fbdiv; + do_div(freq, refdiv); + + return (unsigned long)freq; +} + +static const struct clk_ops berlin2_avpll_vco_ops = { + .is_enabled = berlin2_avpll_vco_is_enabled, + .enable = berlin2_avpll_vco_enable, + .disable = berlin2_avpll_vco_disable, + .recalc_rate = berlin2_avpll_vco_recalc_rate, +}; + +int __init berlin2_avpll_vco_register(void __iomem *base, + const char *name, const char *parent_name, + u8 vco_flags, unsigned long flags) +{ + struct berlin2_avpll_vco *vco; + struct clk_init_data init; + + vco = kzalloc_obj(*vco); + if (!vco) + return -ENOMEM; + + vco->base = base; + vco->flags = vco_flags; + vco->hw.init = &init; + init.name = name; + init.ops = &berlin2_avpll_vco_ops; + init.parent_names = &parent_name; + init.num_parents = 1; + init.flags = flags; + + return clk_hw_register(NULL, &vco->hw); +} + +struct berlin2_avpll_channel { + struct clk_hw hw; + void __iomem *base; + u8 flags; + u8 index; +}; + +#define to_avpll_channel(hw) container_of(hw, struct berlin2_avpll_channel, hw) + +static int berlin2_avpll_channel_is_enabled(struct clk_hw *hw) +{ + struct berlin2_avpll_channel *ch = to_avpll_channel(hw); + u32 reg; + + if (ch->index == 7) + return 1; + + reg = readl_relaxed(ch->base + VCO_CTRL10); + reg &= VCO_POWERUP_CH1 << ch->index; + + return !!reg; +} + +static int berlin2_avpll_channel_enable(struct clk_hw *hw) +{ + struct berlin2_avpll_channel *ch = to_avpll_channel(hw); + u32 reg; + + reg = readl_relaxed(ch->base + VCO_CTRL10); + reg |= VCO_POWERUP_CH1 << ch->index; + writel_relaxed(reg, ch->base + VCO_CTRL10); + + return 0; +} + +static void berlin2_avpll_channel_disable(struct clk_hw *hw) +{ + struct berlin2_avpll_channel *ch = to_avpll_channel(hw); + u32 reg; + + reg = readl_relaxed(ch->base + VCO_CTRL10); + reg &= ~(VCO_POWERUP_CH1 << ch->index); + writel_relaxed(reg, ch->base + VCO_CTRL10); +} + +static const u8 div_hdmi[] = { 1, 2, 4, 6 }; +static const u8 div_av1[] = { 1, 2, 5, 5 }; + +static unsigned long +berlin2_avpll_channel_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) +{ + struct berlin2_avpll_channel *ch = to_avpll_channel(hw); + u32 reg, div_av2, div_av3, divider = 1; + u64 freq = parent_rate; + + reg = readl_relaxed(ch->base + VCO_CTRL30); + if ((reg & (VCO_DPLL_CH1_ENABLE << ch->index)) == 0) + goto skip_div; + + /* + * Fch = (Fref * sync2) / + * (sync1 * div_hdmi * div_av1 * div_av2 * div_av3) + */ + + reg = readl_relaxed(ch->base + VCO_SYNC1n(ch->index)); + /* BG2/BG2CDs SYNC1 reg on AVPLL_B channel 1 is shifted by 4 */ + if (ch->flags & BERLIN2_AVPLL_BIT_QUIRK && ch->index == 0) + reg >>= 4; + divider = reg & VCO_SYNC1_MASK; + + reg = readl_relaxed(ch->base + VCO_SYNC2n(ch->index)); + freq *= reg & VCO_SYNC2_MASK; + + /* Channel 8 has no dividers */ + if (ch->index == 7) + goto skip_div; + + /* + * HDMI divider start at VCO_CTRL11, bit 7; MSB is enable, lower 2 bit + * determine divider. + */ + reg = readl_relaxed(ch->base + VCO_CTRL11) >> 7; + reg = (reg >> (ch->index * 3)); + if (reg & BIT(2)) + divider *= div_hdmi[reg & 0x3]; + + /* + * AV1 divider start at VCO_CTRL11, bit 28; MSB is enable, lower 2 bit + * determine divider. + */ + if (ch->index == 0) { + reg = readl_relaxed(ch->base + VCO_CTRL11); + reg >>= 28; + } else { + reg = readl_relaxed(ch->base + VCO_CTRL12); + reg >>= (ch->index-1) * 3; + } + if (reg & BIT(2)) + divider *= div_av1[reg & 0x3]; + + /* + * AV2 divider start at VCO_CTRL12, bit 18; each 7 bits wide, + * zero is not a valid value. + */ + if (ch->index < 2) { + reg = readl_relaxed(ch->base + VCO_CTRL12); + reg >>= 18 + (ch->index * 7); + } else if (ch->index < 7) { + reg = readl_relaxed(ch->base + VCO_CTRL13); + reg >>= (ch->index - 2) * 7; + } else { + reg = readl_relaxed(ch->base + VCO_CTRL14); + } + div_av2 = reg & 0x7f; + if (div_av2) + divider *= div_av2; + + /* + * AV3 divider start at VCO_CTRL14, bit 7; each 4 bits wide. + * AV2/AV3 form a fractional divider, where only specific values for AV3 + * are allowed. AV3 != 0 divides by AV2/2, AV3=0 is bypass. + */ + if (ch->index < 6) { + reg = readl_relaxed(ch->base + VCO_CTRL14); + reg >>= 7 + (ch->index * 4); + } else { + reg = readl_relaxed(ch->base + VCO_CTRL15); + } + div_av3 = reg & 0xf; + if (div_av2 && div_av3) + freq *= 2; + +skip_div: + do_div(freq, divider); + return (unsigned long)freq; +} + +static const struct clk_ops berlin2_avpll_channel_ops = { + .is_enabled = berlin2_avpll_channel_is_enabled, + .enable = berlin2_avpll_channel_enable, + .disable = berlin2_avpll_channel_disable, + .recalc_rate = berlin2_avpll_channel_recalc_rate, +}; + +/* + * Another nice quirk: + * On some production SoCs, AVPLL channels are scrambled with respect + * to the channel numbering in the registers but still referenced by + * their original channel numbers. We deal with it by having a flag + * and a translation table for the index. + */ +static const u8 quirk_index[] __initconst = { 0, 6, 5, 4, 3, 2, 1, 7 }; + +int __init berlin2_avpll_channel_register(void __iomem *base, + const char *name, u8 index, const char *parent_name, + u8 ch_flags, unsigned long flags) +{ + struct berlin2_avpll_channel *ch; + struct clk_init_data init; + + ch = kzalloc_obj(*ch); + if (!ch) + return -ENOMEM; + + ch->base = base; + if (ch_flags & BERLIN2_AVPLL_SCRAMBLE_QUIRK) + ch->index = quirk_index[index]; + else + ch->index = index; + + ch->flags = ch_flags; + ch->hw.init = &init; + init.name = name; + init.ops = &berlin2_avpll_channel_ops; + init.parent_names = &parent_name; + init.num_parents = 1; + init.flags = flags; + + return clk_hw_register(NULL, &ch->hw); +} diff --git a/drivers/clk/berlin/berlin2-avpll.h b/drivers/clk/berlin/berlin2-avpll.h new file mode 100644 index 000000000..f3af34dc2 --- /dev/null +++ b/drivers/clk/berlin/berlin2-avpll.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2014 Marvell Technology Group Ltd. + * + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com> + * Alexandre Belloni <alexandre.belloni@free-electrons.com> + */ +#ifndef __BERLIN2_AVPLL_H +#define __BERLIN2_AVPLL_H + +#define BERLIN2_AVPLL_BIT_QUIRK BIT(0) +#define BERLIN2_AVPLL_SCRAMBLE_QUIRK BIT(1) + +int berlin2_avpll_vco_register(void __iomem *base, const char *name, + const char *parent_name, u8 vco_flags, unsigned long flags); + +int berlin2_avpll_channel_register(void __iomem *base, const char *name, + u8 index, const char *parent_name, u8 ch_flags, + unsigned long flags); + +#endif /* __BERLIN2_AVPLL_H */ diff --git a/drivers/clk/berlin/berlin2-div.c b/drivers/clk/berlin/berlin2-div.c new file mode 100644 index 000000000..8d2123f0b --- /dev/null +++ b/drivers/clk/berlin/berlin2-div.c @@ -0,0 +1,256 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2014 Marvell Technology Group Ltd. + * + * Alexandre Belloni <alexandre.belloni@free-electrons.com> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com> + */ +#include <linux/bitops.h> +#include <linux/clk-provider.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/slab.h> +#include <linux/spinlock.h> + +#include "berlin2-div.h" + +/* + * Clock dividers in Berlin2 SoCs comprise a complex cell to select + * input pll and divider. The virtual structure as it is used in Marvell + * BSP code can be seen as: + * + * +---+ + * pll0 --------------->| 0 | +---+ + * +---+ |(B)|--+--------------->| 0 | +---+ + * pll1.0 -->| 0 | +-->| 1 | | +--------+ |(E)|----->| 0 | +---+ + * pll1.1 -->| 1 | | +---+ +-->|(C) 1:M |-->| 1 | |(F)|-->|(G)|-> + * ... -->|(A)|--+ | +--------+ +---+ +-->| 1 | +---+ + * ... -->| | +-->|(D) 1:3 |----------+ +---+ + * pll1.N -->| N | +--------- + * +---+ + * + * (A) input pll clock mux controlled by <PllSelect[1:n]> + * (B) input pll bypass mux controlled by <PllSwitch> + * (C) programmable clock divider controlled by <Select[1:n]> + * (D) constant div-by-3 clock divider + * (E) programmable clock divider bypass controlled by <Switch> + * (F) constant div-by-3 clock mux controlled by <D3Switch> + * (G) clock gate controlled by <Enable> + * + * For whatever reason, above control signals come in two flavors: + * - single register dividers with all bits in one register + * - shared register dividers with bits spread over multiple registers + * (including signals for the same cell spread over consecutive registers) + * + * Also, clock gate and pll mux is not available on every div cell, so + * we have to deal with those, too. We reuse common clock composite driver + * for it. + */ + +#define PLL_SELECT_MASK 0x7 +#define DIV_SELECT_MASK 0x7 + +struct berlin2_div { + struct clk_hw hw; + void __iomem *base; + struct berlin2_div_map map; + spinlock_t *lock; +}; + +#define to_berlin2_div(hw) container_of(hw, struct berlin2_div, hw) + +static u8 clk_div[] = { 1, 2, 4, 6, 8, 12, 1, 1 }; + +static int berlin2_div_is_enabled(struct clk_hw *hw) +{ + struct berlin2_div *div = to_berlin2_div(hw); + struct berlin2_div_map *map = &div->map; + u32 reg; + + if (div->lock) + spin_lock(div->lock); + + reg = readl_relaxed(div->base + map->gate_offs); + reg >>= map->gate_shift; + + if (div->lock) + spin_unlock(div->lock); + + return (reg & 0x1); +} + +static int berlin2_div_enable(struct clk_hw *hw) +{ + struct berlin2_div *div = to_berlin2_div(hw); + struct berlin2_div_map *map = &div->map; + u32 reg; + + if (div->lock) + spin_lock(div->lock); + + reg = readl_relaxed(div->base + map->gate_offs); + reg |= BIT(map->gate_shift); + writel_relaxed(reg, div->base + map->gate_offs); + + if (div->lock) + spin_unlock(div->lock); + + return 0; +} + +static void berlin2_div_disable(struct clk_hw *hw) +{ + struct berlin2_div *div = to_berlin2_div(hw); + struct berlin2_div_map *map = &div->map; + u32 reg; + + if (div->lock) + spin_lock(div->lock); + + reg = readl_relaxed(div->base + map->gate_offs); + reg &= ~BIT(map->gate_shift); + writel_relaxed(reg, div->base + map->gate_offs); + + if (div->lock) + spin_unlock(div->lock); +} + +static int berlin2_div_set_parent(struct clk_hw *hw, u8 index) +{ + struct berlin2_div *div = to_berlin2_div(hw); + struct berlin2_div_map *map = &div->map; + u32 reg; + + if (div->lock) + spin_lock(div->lock); + + /* index == 0 is PLL_SWITCH */ + reg = readl_relaxed(div->base + map->pll_switch_offs); + if (index == 0) + reg &= ~BIT(map->pll_switch_shift); + else + reg |= BIT(map->pll_switch_shift); + writel_relaxed(reg, div->base + map->pll_switch_offs); + + /* index > 0 is PLL_SELECT */ + if (index > 0) { + reg = readl_relaxed(div->base + map->pll_select_offs); + reg &= ~(PLL_SELECT_MASK << map->pll_select_shift); + reg |= (index - 1) << map->pll_select_shift; + writel_relaxed(reg, div->base + map->pll_select_offs); + } + + if (div->lock) + spin_unlock(div->lock); + + return 0; +} + +static u8 berlin2_div_get_parent(struct clk_hw *hw) +{ + struct berlin2_div *div = to_berlin2_div(hw); + struct berlin2_div_map *map = &div->map; + u32 reg; + u8 index = 0; + + if (div->lock) + spin_lock(div->lock); + + /* PLL_SWITCH == 0 is index 0 */ + reg = readl_relaxed(div->base + map->pll_switch_offs); + reg &= BIT(map->pll_switch_shift); + if (reg) { + reg = readl_relaxed(div->base + map->pll_select_offs); + reg >>= map->pll_select_shift; + reg &= PLL_SELECT_MASK; + index = 1 + reg; + } + + if (div->lock) + spin_unlock(div->lock); + + return index; +} + +static unsigned long berlin2_div_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct berlin2_div *div = to_berlin2_div(hw); + struct berlin2_div_map *map = &div->map; + u32 divsw, div3sw, divider = 1; + + if (div->lock) + spin_lock(div->lock); + + divsw = readl_relaxed(div->base + map->div_switch_offs) & + (1 << map->div_switch_shift); + div3sw = readl_relaxed(div->base + map->div3_switch_offs) & + (1 << map->div3_switch_shift); + + /* constant divide-by-3 (dominant) */ + if (div3sw != 0) { + divider = 3; + /* divider can be bypassed with DIV_SWITCH == 0 */ + } else if (divsw == 0) { + divider = 1; + /* clock divider determined by DIV_SELECT */ + } else { + u32 reg; + reg = readl_relaxed(div->base + map->div_select_offs); + reg >>= map->div_select_shift; + reg &= DIV_SELECT_MASK; + divider = clk_div[reg]; + } + + if (div->lock) + spin_unlock(div->lock); + + return parent_rate / divider; +} + +static const struct clk_ops berlin2_div_rate_ops = { + .determine_rate = clk_hw_determine_rate_no_reparent, + .recalc_rate = berlin2_div_recalc_rate, +}; + +static const struct clk_ops berlin2_div_gate_ops = { + .is_enabled = berlin2_div_is_enabled, + .enable = berlin2_div_enable, + .disable = berlin2_div_disable, +}; + +static const struct clk_ops berlin2_div_mux_ops = { + .set_parent = berlin2_div_set_parent, + .get_parent = berlin2_div_get_parent, +}; + +struct clk_hw * __init +berlin2_div_register(const struct berlin2_div_map *map, + void __iomem *base, const char *name, u8 div_flags, + const char **parent_names, int num_parents, + unsigned long flags, spinlock_t *lock) +{ + const struct clk_ops *mux_ops = &berlin2_div_mux_ops; + const struct clk_ops *rate_ops = &berlin2_div_rate_ops; + const struct clk_ops *gate_ops = &berlin2_div_gate_ops; + struct berlin2_div *div; + + div = kzalloc_obj(*div); + if (!div) + return ERR_PTR(-ENOMEM); + + /* copy div_map to allow __initconst */ + memcpy(&div->map, map, sizeof(*map)); + div->base = base; + div->lock = lock; + + if ((div_flags & BERLIN2_DIV_HAS_GATE) == 0) + gate_ops = NULL; + if ((div_flags & BERLIN2_DIV_HAS_MUX) == 0) + mux_ops = NULL; + + return clk_hw_register_composite(NULL, name, parent_names, num_parents, + &div->hw, mux_ops, &div->hw, rate_ops, + &div->hw, gate_ops, flags); +} diff --git a/drivers/clk/berlin/berlin2-div.h b/drivers/clk/berlin/berlin2-div.h new file mode 100644 index 000000000..d4da64325 --- /dev/null +++ b/drivers/clk/berlin/berlin2-div.h @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2014 Marvell Technology Group Ltd. + * + * Alexandre Belloni <alexandre.belloni@free-electrons.com> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com> + */ +#ifndef __BERLIN2_DIV_H +#define __BERLIN2_DIV_H + +struct clk_hw; + +#define BERLIN2_DIV_HAS_GATE BIT(0) +#define BERLIN2_DIV_HAS_MUX BIT(1) + +#define BERLIN2_PLL_SELECT(_off, _sh) \ + .pll_select_offs = _off, \ + .pll_select_shift = _sh + +#define BERLIN2_PLL_SWITCH(_off, _sh) \ + .pll_switch_offs = _off, \ + .pll_switch_shift = _sh + +#define BERLIN2_DIV_SELECT(_off, _sh) \ + .div_select_offs = _off, \ + .div_select_shift = _sh + +#define BERLIN2_DIV_SWITCH(_off, _sh) \ + .div_switch_offs = _off, \ + .div_switch_shift = _sh + +#define BERLIN2_DIV_D3SWITCH(_off, _sh) \ + .div3_switch_offs = _off, \ + .div3_switch_shift = _sh + +#define BERLIN2_DIV_GATE(_off, _sh) \ + .gate_offs = _off, \ + .gate_shift = _sh + +#define BERLIN2_SINGLE_DIV(_off) \ + BERLIN2_DIV_GATE(_off, 0), \ + BERLIN2_PLL_SELECT(_off, 1), \ + BERLIN2_PLL_SWITCH(_off, 4), \ + BERLIN2_DIV_SWITCH(_off, 5), \ + BERLIN2_DIV_D3SWITCH(_off, 6), \ + BERLIN2_DIV_SELECT(_off, 7) + +struct berlin2_div_map { + u16 pll_select_offs; + u16 pll_switch_offs; + u16 div_select_offs; + u16 div_switch_offs; + u16 div3_switch_offs; + u16 gate_offs; + u8 pll_select_shift; + u8 pll_switch_shift; + u8 div_select_shift; + u8 div_switch_shift; + u8 div3_switch_shift; + u8 gate_shift; +}; + +struct berlin2_div_data { + const char *name; + const u8 *parent_ids; + int num_parents; + unsigned long flags; + struct berlin2_div_map map; + u8 div_flags; +}; + +struct clk_hw * +berlin2_div_register(const struct berlin2_div_map *map, + void __iomem *base, const char *name, u8 div_flags, + const char **parent_names, int num_parents, + unsigned long flags, spinlock_t *lock); + +#endif /* __BERLIN2_DIV_H */ diff --git a/drivers/clk/berlin/berlin2-pll.c b/drivers/clk/berlin/berlin2-pll.c new file mode 100644 index 000000000..d0ec3d0bc --- /dev/null +++ b/drivers/clk/berlin/berlin2-pll.c @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2014 Marvell Technology Group Ltd. + * + * Alexandre Belloni <alexandre.belloni@free-electrons.com> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com> + */ +#include <linux/clk-provider.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/slab.h> +#include <asm/div64.h> + +#include "berlin2-div.h" +#include "berlin2-pll.h" + +struct berlin2_pll { + struct clk_hw hw; + void __iomem *base; + struct berlin2_pll_map map; +}; + +#define to_berlin2_pll(hw) container_of(hw, struct berlin2_pll, hw) + +#define SPLL_CTRL0 0x00 +#define SPLL_CTRL1 0x04 +#define SPLL_CTRL2 0x08 +#define SPLL_CTRL3 0x0c +#define SPLL_CTRL4 0x10 + +#define FBDIV_MASK 0x1ff +#define RFDIV_MASK 0x1f +#define DIVSEL_MASK 0xf + +/* + * The output frequency formula for the pll is: + * clkout = fbdiv / refdiv * parent / vcodiv + */ +static unsigned long +berlin2_pll_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) +{ + struct berlin2_pll *pll = to_berlin2_pll(hw); + struct berlin2_pll_map *map = &pll->map; + u32 val, fbdiv, rfdiv, vcodivsel, vcodiv; + u64 rate = parent_rate; + + val = readl_relaxed(pll->base + SPLL_CTRL0); + fbdiv = (val >> map->fbdiv_shift) & FBDIV_MASK; + rfdiv = (val >> map->rfdiv_shift) & RFDIV_MASK; + if (rfdiv == 0) { + pr_warn("%s has zero rfdiv\n", clk_hw_get_name(hw)); + rfdiv = 1; + } + + val = readl_relaxed(pll->base + SPLL_CTRL1); + vcodivsel = (val >> map->divsel_shift) & DIVSEL_MASK; + vcodiv = map->vcodiv[vcodivsel]; + if (vcodiv == 0) { + pr_warn("%s has zero vcodiv (index %d)\n", + clk_hw_get_name(hw), vcodivsel); + vcodiv = 1; + } + + rate *= fbdiv * map->mult; + do_div(rate, rfdiv * vcodiv); + + return (unsigned long)rate; +} + +static const struct clk_ops berlin2_pll_ops = { + .recalc_rate = berlin2_pll_recalc_rate, +}; + +int __init +berlin2_pll_register(const struct berlin2_pll_map *map, + void __iomem *base, const char *name, + const char *parent_name, unsigned long flags) +{ + struct clk_init_data init; + struct berlin2_pll *pll; + + pll = kzalloc_obj(*pll); + if (!pll) + return -ENOMEM; + + /* copy pll_map to allow __initconst */ + memcpy(&pll->map, map, sizeof(*map)); + pll->base = base; + pll->hw.init = &init; + init.name = name; + init.ops = &berlin2_pll_ops; + init.parent_names = &parent_name; + init.num_parents = 1; + init.flags = flags; + + return clk_hw_register(NULL, &pll->hw); +} diff --git a/drivers/clk/berlin/berlin2-pll.h b/drivers/clk/berlin/berlin2-pll.h new file mode 100644 index 000000000..3757fb25c --- /dev/null +++ b/drivers/clk/berlin/berlin2-pll.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2014 Marvell Technology Group Ltd. + * + * Alexandre Belloni <alexandre.belloni@free-electrons.com> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com> + */ +#ifndef __BERLIN2_PLL_H +#define __BERLIN2_PLL_H + +struct berlin2_pll_map { + const u8 vcodiv[16]; + u8 mult; + u8 fbdiv_shift; + u8 rfdiv_shift; + u8 divsel_shift; +}; + +int berlin2_pll_register(const struct berlin2_pll_map *map, + void __iomem *base, const char *name, + const char *parent_name, unsigned long flags); + +#endif /* __BERLIN2_PLL_H */ diff --git a/drivers/clk/berlin/bg2.c b/drivers/clk/berlin/bg2.c new file mode 100644 index 000000000..9bd1c91f5 --- /dev/null +++ b/drivers/clk/berlin/bg2.c @@ -0,0 +1,687 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2014 Marvell Technology Group Ltd. + * + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com> + * Alexandre Belloni <alexandre.belloni@free-electrons.com> + */ + +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/slab.h> + +#include <dt-bindings/clock/berlin2.h> + +#include "berlin2-avpll.h" +#include "berlin2-div.h" +#include "berlin2-pll.h" +#include "common.h" + +#define REG_PINMUX0 0x0000 +#define REG_PINMUX1 0x0004 +#define REG_SYSPLLCTL0 0x0014 +#define REG_SYSPLLCTL4 0x0024 +#define REG_MEMPLLCTL0 0x0028 +#define REG_MEMPLLCTL4 0x0038 +#define REG_CPUPLLCTL0 0x003c +#define REG_CPUPLLCTL4 0x004c +#define REG_AVPLLCTL0 0x0050 +#define REG_AVPLLCTL31 0x00cc +#define REG_AVPLLCTL62 0x0148 +#define REG_PLLSTATUS 0x014c +#define REG_CLKENABLE 0x0150 +#define REG_CLKSELECT0 0x0154 +#define REG_CLKSELECT1 0x0158 +#define REG_CLKSELECT2 0x015c +#define REG_CLKSELECT3 0x0160 +#define REG_CLKSWITCH0 0x0164 +#define REG_CLKSWITCH1 0x0168 +#define REG_RESET_TRIGGER 0x0178 +#define REG_RESET_STATUS0 0x017c +#define REG_RESET_STATUS1 0x0180 +#define REG_SW_GENERIC0 0x0184 +#define REG_SW_GENERIC3 0x0190 +#define REG_PRODUCTID 0x01cc +#define REG_PRODUCTID_EXT 0x01d0 +#define REG_GFX3DCORE_CLKCTL 0x022c +#define REG_GFX3DSYS_CLKCTL 0x0230 +#define REG_ARC_CLKCTL 0x0234 +#define REG_VIP_CLKCTL 0x0238 +#define REG_SDIO0XIN_CLKCTL 0x023c +#define REG_SDIO1XIN_CLKCTL 0x0240 +#define REG_GFX3DEXTRA_CLKCTL 0x0244 +#define REG_GFX3D_RESET 0x0248 +#define REG_GC360_CLKCTL 0x024c +#define REG_SDIO_DLLMST_CLKCTL 0x0250 + +/* + * BG2/BG2CD SoCs have the following audio/video I/O units: + * + * audiohd: HDMI TX audio + * audio0: 7.1ch TX + * audio1: 2ch TX + * audio2: 2ch RX + * audio3: SPDIF TX + * video0: HDMI video + * video1: Secondary video + * video2: SD auxiliary video + * + * There are no external audio clocks (ACLKI0, ACLKI1) and + * only one external video clock (VCLKI0). + * + * Currently missing bits and pieces: + * - audio_fast_pll is unknown + * - audiohd_pll is unknown + * - video0_pll is unknown + * - audio[023], audiohd parent pll is assumed to be audio_fast_pll + * + */ + +#define MAX_CLKS 41 +static struct clk_hw_onecell_data *clk_data; +static DEFINE_SPINLOCK(lock); +static void __iomem *gbase; + +enum { + REFCLK, VIDEO_EXT0, + SYSPLL, MEMPLL, CPUPLL, + AVPLL_A1, AVPLL_A2, AVPLL_A3, AVPLL_A4, + AVPLL_A5, AVPLL_A6, AVPLL_A7, AVPLL_A8, + AVPLL_B1, AVPLL_B2, AVPLL_B3, AVPLL_B4, + AVPLL_B5, AVPLL_B6, AVPLL_B7, AVPLL_B8, + AUDIO1_PLL, AUDIO_FAST_PLL, + VIDEO0_PLL, VIDEO0_IN, + VIDEO1_PLL, VIDEO1_IN, + VIDEO2_PLL, VIDEO2_IN, +}; + +static const char *clk_names[] = { + [REFCLK] = "refclk", + [VIDEO_EXT0] = "video_ext0", + [SYSPLL] = "syspll", + [MEMPLL] = "mempll", + [CPUPLL] = "cpupll", + [AVPLL_A1] = "avpll_a1", + [AVPLL_A2] = "avpll_a2", + [AVPLL_A3] = "avpll_a3", + [AVPLL_A4] = "avpll_a4", + [AVPLL_A5] = "avpll_a5", + [AVPLL_A6] = "avpll_a6", + [AVPLL_A7] = "avpll_a7", + [AVPLL_A8] = "avpll_a8", + [AVPLL_B1] = "avpll_b1", + [AVPLL_B2] = "avpll_b2", + [AVPLL_B3] = "avpll_b3", + [AVPLL_B4] = "avpll_b4", + [AVPLL_B5] = "avpll_b5", + [AVPLL_B6] = "avpll_b6", + [AVPLL_B7] = "avpll_b7", + [AVPLL_B8] = "avpll_b8", + [AUDIO1_PLL] = "audio1_pll", + [AUDIO_FAST_PLL] = "audio_fast_pll", + [VIDEO0_PLL] = "video0_pll", + [VIDEO0_IN] = "video0_in", + [VIDEO1_PLL] = "video1_pll", + [VIDEO1_IN] = "video1_in", + [VIDEO2_PLL] = "video2_pll", + [VIDEO2_IN] = "video2_in", +}; + +static const struct berlin2_pll_map bg2_pll_map __initconst = { + .vcodiv = {10, 15, 20, 25, 30, 40, 50, 60, 80}, + .mult = 10, + .fbdiv_shift = 6, + .rfdiv_shift = 1, + .divsel_shift = 7, +}; + +static const u8 default_parent_ids[] = { + SYSPLL, AVPLL_B4, AVPLL_A5, AVPLL_B6, AVPLL_B7, SYSPLL +}; + +static const struct berlin2_div_data bg2_divs[] __initconst = { + { + .name = "sys", + .parent_ids = (const u8 []){ + SYSPLL, AVPLL_B4, AVPLL_B5, AVPLL_B6, AVPLL_B7, SYSPLL + }, + .num_parents = 6, + .map = { + BERLIN2_DIV_GATE(REG_CLKENABLE, 0), + BERLIN2_PLL_SELECT(REG_CLKSELECT0, 0), + BERLIN2_DIV_SELECT(REG_CLKSELECT0, 3), + BERLIN2_PLL_SWITCH(REG_CLKSWITCH0, 3), + BERLIN2_DIV_SWITCH(REG_CLKSWITCH0, 4), + BERLIN2_DIV_D3SWITCH(REG_CLKSWITCH0, 5), + }, + .div_flags = BERLIN2_DIV_HAS_GATE | BERLIN2_DIV_HAS_MUX, + .flags = CLK_IGNORE_UNUSED, + }, + { + .name = "cpu", + .parent_ids = (const u8 []){ + CPUPLL, MEMPLL, MEMPLL, MEMPLL, MEMPLL + }, + .num_parents = 5, + .map = { + BERLIN2_PLL_SELECT(REG_CLKSELECT0, 6), + BERLIN2_DIV_SELECT(REG_CLKSELECT0, 9), + BERLIN2_PLL_SWITCH(REG_CLKSWITCH0, 6), + BERLIN2_DIV_SWITCH(REG_CLKSWITCH0, 7), + BERLIN2_DIV_D3SWITCH(REG_CLKSWITCH0, 8), + }, + .div_flags = BERLIN2_DIV_HAS_MUX, + .flags = 0, + }, + { + .name = "drmfigo", + .parent_ids = default_parent_ids, + .num_parents = ARRAY_SIZE(default_parent_ids), + .map = { + BERLIN2_DIV_GATE(REG_CLKENABLE, 16), + BERLIN2_PLL_SELECT(REG_CLKSELECT0, 17), + BERLIN2_DIV_SELECT(REG_CLKSELECT0, 20), + BERLIN2_PLL_SWITCH(REG_CLKSWITCH0, 12), + BERLIN2_DIV_SWITCH(REG_CLKSWITCH0, 13), + BERLIN2_DIV_D3SWITCH(REG_CLKSWITCH0, 14), + }, + .div_flags = BERLIN2_DIV_HAS_GATE | BERLIN2_DIV_HAS_MUX, + .flags = 0, + }, + { + .name = "cfg", + .parent_ids = default_parent_ids, + .num_parents = ARRAY_SIZE(default_parent_ids), + .map = { + BERLIN2_DIV_GATE(REG_CLKENABLE, 1), + BERLIN2_PLL_SELECT(REG_CLKSELECT0, 23), + BERLIN2_DIV_SELECT(REG_CLKSELECT0, 26), + BERLIN2_PLL_SWITCH(REG_CLKSWITCH0, 15), + BERLIN2_DIV_SWITCH(REG_CLKSWITCH0, 16), + BERLIN2_DIV_D3SWITCH(REG_CLKSWITCH0, 17), + }, + .div_flags = BERLIN2_DIV_HAS_GATE | BERLIN2_DIV_HAS_MUX, + .flags = 0, + }, + { + .name = "gfx", + .parent_ids = default_parent_ids, + .num_parents = ARRAY_SIZE(default_parent_ids), + .map = { + BERLIN2_DIV_GATE(REG_CLKENABLE, 4), + BERLIN2_PLL_SELECT(REG_CLKSELECT0, 29), + BERLIN2_DIV_SELECT(REG_CLKSELECT1, 0), + BERLIN2_PLL_SWITCH(REG_CLKSWITCH0, 18), + BERLIN2_DIV_SWITCH(REG_CLKSWITCH0, 19), + BERLIN2_DIV_D3SWITCH(REG_CLKSWITCH0, 20), + }, + .div_flags = BERLIN2_DIV_HAS_GATE | BERLIN2_DIV_HAS_MUX, + .flags = 0, + }, + { + .name = "zsp", + .parent_ids = default_parent_ids, + .num_parents = ARRAY_SIZE(default_parent_ids), + .map = { + BERLIN2_DIV_GATE(REG_CLKENABLE, 5), + BERLIN2_PLL_SELECT(REG_CLKSELECT1, 3), + BERLIN2_DIV_SELECT(REG_CLKSELECT1, 6), + BERLIN2_PLL_SWITCH(REG_CLKSWITCH0, 21), + BERLIN2_DIV_SWITCH(REG_CLKSWITCH0, 22), + BERLIN2_DIV_D3SWITCH(REG_CLKSWITCH0, 23), + }, + .div_flags = BERLIN2_DIV_HAS_GATE | BERLIN2_DIV_HAS_MUX, + .flags = 0, + }, + { + .name = "perif", + .parent_ids = default_parent_ids, + .num_parents = ARRAY_SIZE(default_parent_ids), + .map = { + BERLIN2_DIV_GATE(REG_CLKENABLE, 6), + BERLIN2_PLL_SELECT(REG_CLKSELECT1, 9), + BERLIN2_DIV_SELECT(REG_CLKSELECT1, 12), + BERLIN2_PLL_SWITCH(REG_CLKSWITCH0, 24), + BERLIN2_DIV_SWITCH(REG_CLKSWITCH0, 25), + BERLIN2_DIV_D3SWITCH(REG_CLKSWITCH0, 26), + }, + .div_flags = BERLIN2_DIV_HAS_GATE | BERLIN2_DIV_HAS_MUX, + .flags = CLK_IGNORE_UNUSED, + }, + { + .name = "pcube", + .parent_ids = default_parent_ids, + .num_parents = ARRAY_SIZE(default_parent_ids), + .map = { + BERLIN2_DIV_GATE(REG_CLKENABLE, 2), + BERLIN2_PLL_SELECT(REG_CLKSELECT1, 15), + BERLIN2_DIV_SELECT(REG_CLKSELECT1, 18), + BERLIN2_PLL_SWITCH(REG_CLKSWITCH0, 27), + BERLIN2_DIV_SWITCH(REG_CLKSWITCH0, 28), + BERLIN2_DIV_D3SWITCH(REG_CLKSWITCH0, 29), + }, + .div_flags = BERLIN2_DIV_HAS_GATE | BERLIN2_DIV_HAS_MUX, + .flags = 0, + }, + { + .name = "vscope", + .parent_ids = default_parent_ids, + .num_parents = ARRAY_SIZE(default_parent_ids), + .map = { + BERLIN2_DIV_GATE(REG_CLKENABLE, 3), + BERLIN2_PLL_SELECT(REG_CLKSELECT1, 21), + BERLIN2_DIV_SELECT(REG_CLKSELECT1, 24), + BERLIN2_PLL_SWITCH(REG_CLKSWITCH0, 30), + BERLIN2_DIV_SWITCH(REG_CLKSWITCH0, 31), + BERLIN2_DIV_D3SWITCH(REG_CLKSWITCH1, 0), + }, + .div_flags = BERLIN2_DIV_HAS_GATE | BERLIN2_DIV_HAS_MUX, + .flags = 0, + }, + { + .name = "nfc_ecc", + .parent_ids = default_parent_ids, + .num_parents = ARRAY_SIZE(default_parent_ids), + .map = { + BERLIN2_DIV_GATE(REG_CLKENABLE, 18), + BERLIN2_PLL_SELECT(REG_CLKSELECT1, 27), + BERLIN2_DIV_SELECT(REG_CLKSELECT2, 0), + BERLIN2_PLL_SWITCH(REG_CLKSWITCH1, 1), + BERLIN2_DIV_SWITCH(REG_CLKSWITCH1, 2), + BERLIN2_DIV_D3SWITCH(REG_CLKSWITCH1, 3), + }, + .div_flags = BERLIN2_DIV_HAS_GATE | BERLIN2_DIV_HAS_MUX, + .flags = 0, + }, + { + .name = "vpp", + .parent_ids = default_parent_ids, + .num_parents = ARRAY_SIZE(default_parent_ids), + .map = { + BERLIN2_DIV_GATE(REG_CLKENABLE, 21), + BERLIN2_PLL_SELECT(REG_CLKSELECT2, 3), + BERLIN2_DIV_SELECT(REG_CLKSELECT2, 6), + BERLIN2_PLL_SWITCH(REG_CLKSWITCH1, 4), + BERLIN2_DIV_SWITCH(REG_CLKSWITCH1, 5), + BERLIN2_DIV_D3SWITCH(REG_CLKSWITCH1, 6), + }, + .div_flags = BERLIN2_DIV_HAS_GATE | BERLIN2_DIV_HAS_MUX, + .flags = 0, + }, + { + .name = "app", + .parent_ids = default_parent_ids, + .num_parents = ARRAY_SIZE(default_parent_ids), + .map = { + BERLIN2_DIV_GATE(REG_CLKENABLE, 20), + BERLIN2_PLL_SELECT(REG_CLKSELECT2, 9), + BERLIN2_DIV_SELECT(REG_CLKSELECT2, 12), + BERLIN2_PLL_SWITCH(REG_CLKSWITCH1, 7), + BERLIN2_DIV_SWITCH(REG_CLKSWITCH1, 8), + BERLIN2_DIV_D3SWITCH(REG_CLKSWITCH1, 9), + }, + .div_flags = BERLIN2_DIV_HAS_GATE | BERLIN2_DIV_HAS_MUX, + .flags = 0, + }, + { + .name = "audio0", + .parent_ids = (const u8 []){ AUDIO_FAST_PLL }, + .num_parents = 1, + .map = { + BERLIN2_DIV_GATE(REG_CLKENABLE, 22), + BERLIN2_DIV_SELECT(REG_CLKSELECT2, 17), + BERLIN2_DIV_SWITCH(REG_CLKSWITCH1, 10), + BERLIN2_DIV_D3SWITCH(REG_CLKSWITCH1, 11), + }, + .div_flags = BERLIN2_DIV_HAS_GATE, + .flags = 0, + }, + { + .name = "audio2", + .parent_ids = (const u8 []){ AUDIO_FAST_PLL }, + .num_parents = 1, + .map = { + BERLIN2_DIV_GATE(REG_CLKENABLE, 24), + BERLIN2_DIV_SELECT(REG_CLKSELECT2, 20), + BERLIN2_DIV_SWITCH(REG_CLKSWITCH1, 14), + BERLIN2_DIV_D3SWITCH(REG_CLKSWITCH1, 15), + }, + .div_flags = BERLIN2_DIV_HAS_GATE, + .flags = 0, + }, + { + .name = "audio3", + .parent_ids = (const u8 []){ AUDIO_FAST_PLL }, + .num_parents = 1, + .map = { + BERLIN2_DIV_GATE(REG_CLKENABLE, 25), + BERLIN2_DIV_SELECT(REG_CLKSELECT2, 23), + BERLIN2_DIV_SWITCH(REG_CLKSWITCH1, 16), + BERLIN2_DIV_D3SWITCH(REG_CLKSWITCH1, 17), + }, + .div_flags = BERLIN2_DIV_HAS_GATE, + .flags = 0, + }, + { + .name = "audio1", + .parent_ids = (const u8 []){ AUDIO1_PLL }, + .num_parents = 1, + .map = { + BERLIN2_DIV_GATE(REG_CLKENABLE, 23), + BERLIN2_DIV_SELECT(REG_CLKSELECT3, 0), + BERLIN2_DIV_SWITCH(REG_CLKSWITCH1, 12), + BERLIN2_DIV_D3SWITCH(REG_CLKSWITCH1, 13), + }, + .div_flags = BERLIN2_DIV_HAS_GATE, + .flags = 0, + }, + { + .name = "gfx3d_core", + .parent_ids = default_parent_ids, + .num_parents = ARRAY_SIZE(default_parent_ids), + .map = { + BERLIN2_SINGLE_DIV(REG_GFX3DCORE_CLKCTL), + }, + .div_flags = BERLIN2_DIV_HAS_GATE | BERLIN2_DIV_HAS_MUX, + .flags = 0, + }, + { + .name = "gfx3d_sys", + .parent_ids = default_parent_ids, + .num_parents = ARRAY_SIZE(default_parent_ids), + .map = { + BERLIN2_SINGLE_DIV(REG_GFX3DSYS_CLKCTL), + }, + .div_flags = BERLIN2_DIV_HAS_GATE | BERLIN2_DIV_HAS_MUX, + .flags = 0, + }, + { + .name = "arc", + .parent_ids = default_parent_ids, + .num_parents = ARRAY_SIZE(default_parent_ids), + .map = { + BERLIN2_SINGLE_DIV(REG_ARC_CLKCTL), + }, + .div_flags = BERLIN2_DIV_HAS_GATE | BERLIN2_DIV_HAS_MUX, + .flags = 0, + }, + { + .name = "vip", + .parent_ids = default_parent_ids, + .num_parents = ARRAY_SIZE(default_parent_ids), + .map = { + BERLIN2_SINGLE_DIV(REG_VIP_CLKCTL), + }, + .div_flags = BERLIN2_DIV_HAS_GATE | BERLIN2_DIV_HAS_MUX, + .flags = 0, + }, + { + .name = "sdio0xin", + .parent_ids = default_parent_ids, + .num_parents = ARRAY_SIZE(default_parent_ids), + .map = { + BERLIN2_SINGLE_DIV(REG_SDIO0XIN_CLKCTL), + }, + .div_flags = BERLIN2_DIV_HAS_GATE | BERLIN2_DIV_HAS_MUX, + .flags = 0, + }, + { + .name = "sdio1xin", + .parent_ids = default_parent_ids, + .num_parents = ARRAY_SIZE(default_parent_ids), + .map = { + BERLIN2_SINGLE_DIV(REG_SDIO1XIN_CLKCTL), + }, + .div_flags = BERLIN2_DIV_HAS_GATE | BERLIN2_DIV_HAS_MUX, + .flags = 0, + }, + { + .name = "gfx3d_extra", + .parent_ids = default_parent_ids, + .num_parents = ARRAY_SIZE(default_parent_ids), + .map = { + BERLIN2_SINGLE_DIV(REG_GFX3DEXTRA_CLKCTL), + }, + .div_flags = BERLIN2_DIV_HAS_GATE | BERLIN2_DIV_HAS_MUX, + .flags = 0, + }, + { + .name = "gc360", + .parent_ids = default_parent_ids, + .num_parents = ARRAY_SIZE(default_parent_ids), + .map = { + BERLIN2_SINGLE_DIV(REG_GC360_CLKCTL), + }, + .div_flags = BERLIN2_DIV_HAS_GATE | BERLIN2_DIV_HAS_MUX, + .flags = 0, + }, + { + .name = "sdio_dllmst", + .parent_ids = default_parent_ids, + .num_parents = ARRAY_SIZE(default_parent_ids), + .map = { + BERLIN2_SINGLE_DIV(REG_SDIO_DLLMST_CLKCTL), + }, + .div_flags = BERLIN2_DIV_HAS_GATE | BERLIN2_DIV_HAS_MUX, + .flags = 0, + }, +}; + +static const struct berlin2_gate_data bg2_gates[] __initconst = { + { "geth0", "perif", 7 }, + { "geth1", "perif", 8 }, + { "sata", "perif", 9 }, + { "ahbapb", "perif", 10, CLK_IGNORE_UNUSED }, + { "usb0", "perif", 11 }, + { "usb1", "perif", 12 }, + { "pbridge", "perif", 13, CLK_IGNORE_UNUSED }, + { "sdio0", "perif", 14 }, + { "sdio1", "perif", 15 }, + { "nfc", "perif", 17 }, + { "smemc", "perif", 19 }, + { "audiohd", "audiohd_pll", 26 }, + { "video0", "video0_in", 27 }, + { "video1", "video1_in", 28 }, + { "video2", "video2_in", 29 }, +}; + +static void __init berlin2_clock_setup(struct device_node *np) +{ + struct device_node *parent_np = of_get_parent(np); + const char *parent_names[9]; + struct clk *clk; + struct clk_hw *hw; + struct clk_hw **hws; + u8 avpll_flags = 0; + int n, ret; + + clk_data = kzalloc_flex(*clk_data, hws, MAX_CLKS); + if (!clk_data) { + of_node_put(parent_np); + return; + } + clk_data->num = MAX_CLKS; + hws = clk_data->hws; + + gbase = of_iomap(parent_np, 0); + of_node_put(parent_np); + if (!gbase) + return; + + /* overwrite default clock names with DT provided ones */ + clk = of_clk_get_by_name(np, clk_names[REFCLK]); + if (!IS_ERR(clk)) { + clk_names[REFCLK] = __clk_get_name(clk); + clk_put(clk); + } + + clk = of_clk_get_by_name(np, clk_names[VIDEO_EXT0]); + if (!IS_ERR(clk)) { + clk_names[VIDEO_EXT0] = __clk_get_name(clk); + clk_put(clk); + } + + /* simple register PLLs */ + ret = berlin2_pll_register(&bg2_pll_map, gbase + REG_SYSPLLCTL0, + clk_names[SYSPLL], clk_names[REFCLK], 0); + if (ret) + goto bg2_fail; + + ret = berlin2_pll_register(&bg2_pll_map, gbase + REG_MEMPLLCTL0, + clk_names[MEMPLL], clk_names[REFCLK], 0); + if (ret) + goto bg2_fail; + + ret = berlin2_pll_register(&bg2_pll_map, gbase + REG_CPUPLLCTL0, + clk_names[CPUPLL], clk_names[REFCLK], 0); + if (ret) + goto bg2_fail; + + if (of_device_is_compatible(np, "marvell,berlin2-global-register")) + avpll_flags |= BERLIN2_AVPLL_SCRAMBLE_QUIRK; + + /* audio/video VCOs */ + ret = berlin2_avpll_vco_register(gbase + REG_AVPLLCTL0, "avpll_vcoA", + clk_names[REFCLK], avpll_flags, 0); + if (ret) + goto bg2_fail; + + for (n = 0; n < 8; n++) { + ret = berlin2_avpll_channel_register(gbase + REG_AVPLLCTL0, + clk_names[AVPLL_A1 + n], n, "avpll_vcoA", + avpll_flags, 0); + if (ret) + goto bg2_fail; + } + + ret = berlin2_avpll_vco_register(gbase + REG_AVPLLCTL31, "avpll_vcoB", + clk_names[REFCLK], BERLIN2_AVPLL_BIT_QUIRK | + avpll_flags, 0); + if (ret) + goto bg2_fail; + + for (n = 0; n < 8; n++) { + ret = berlin2_avpll_channel_register(gbase + REG_AVPLLCTL31, + clk_names[AVPLL_B1 + n], n, "avpll_vcoB", + BERLIN2_AVPLL_BIT_QUIRK | avpll_flags, 0); + if (ret) + goto bg2_fail; + } + + /* reference clock bypass switches */ + parent_names[0] = clk_names[SYSPLL]; + parent_names[1] = clk_names[REFCLK]; + hw = clk_hw_register_mux(NULL, "syspll_byp", parent_names, 2, + 0, gbase + REG_CLKSWITCH0, 0, 1, 0, &lock); + if (IS_ERR(hw)) + goto bg2_fail; + clk_names[SYSPLL] = clk_hw_get_name(hw); + + parent_names[0] = clk_names[MEMPLL]; + parent_names[1] = clk_names[REFCLK]; + hw = clk_hw_register_mux(NULL, "mempll_byp", parent_names, 2, + 0, gbase + REG_CLKSWITCH0, 1, 1, 0, &lock); + if (IS_ERR(hw)) + goto bg2_fail; + clk_names[MEMPLL] = clk_hw_get_name(hw); + + parent_names[0] = clk_names[CPUPLL]; + parent_names[1] = clk_names[REFCLK]; + hw = clk_hw_register_mux(NULL, "cpupll_byp", parent_names, 2, + 0, gbase + REG_CLKSWITCH0, 2, 1, 0, &lock); + if (IS_ERR(hw)) + goto bg2_fail; + clk_names[CPUPLL] = clk_hw_get_name(hw); + + /* clock muxes */ + parent_names[0] = clk_names[AVPLL_B3]; + parent_names[1] = clk_names[AVPLL_A3]; + hw = clk_hw_register_mux(NULL, clk_names[AUDIO1_PLL], parent_names, 2, + 0, gbase + REG_CLKSELECT2, 29, 1, 0, &lock); + if (IS_ERR(hw)) + goto bg2_fail; + + parent_names[0] = clk_names[VIDEO0_PLL]; + parent_names[1] = clk_names[VIDEO_EXT0]; + hw = clk_hw_register_mux(NULL, clk_names[VIDEO0_IN], parent_names, 2, + 0, gbase + REG_CLKSELECT3, 4, 1, 0, &lock); + if (IS_ERR(hw)) + goto bg2_fail; + + parent_names[0] = clk_names[VIDEO1_PLL]; + parent_names[1] = clk_names[VIDEO_EXT0]; + hw = clk_hw_register_mux(NULL, clk_names[VIDEO1_IN], parent_names, 2, + 0, gbase + REG_CLKSELECT3, 6, 1, 0, &lock); + if (IS_ERR(hw)) + goto bg2_fail; + + parent_names[0] = clk_names[AVPLL_A2]; + parent_names[1] = clk_names[AVPLL_B2]; + hw = clk_hw_register_mux(NULL, clk_names[VIDEO1_PLL], parent_names, 2, + 0, gbase + REG_CLKSELECT3, 7, 1, 0, &lock); + if (IS_ERR(hw)) + goto bg2_fail; + + parent_names[0] = clk_names[VIDEO2_PLL]; + parent_names[1] = clk_names[VIDEO_EXT0]; + hw = clk_hw_register_mux(NULL, clk_names[VIDEO2_IN], parent_names, 2, + 0, gbase + REG_CLKSELECT3, 9, 1, 0, &lock); + if (IS_ERR(hw)) + goto bg2_fail; + + parent_names[0] = clk_names[AVPLL_B1]; + parent_names[1] = clk_names[AVPLL_A5]; + hw = clk_hw_register_mux(NULL, clk_names[VIDEO2_PLL], parent_names, 2, + 0, gbase + REG_CLKSELECT3, 10, 1, 0, &lock); + if (IS_ERR(hw)) + goto bg2_fail; + + /* clock divider cells */ + for (n = 0; n < ARRAY_SIZE(bg2_divs); n++) { + const struct berlin2_div_data *dd = &bg2_divs[n]; + int k; + + for (k = 0; k < dd->num_parents; k++) + parent_names[k] = clk_names[dd->parent_ids[k]]; + + hws[CLKID_SYS + n] = berlin2_div_register(&dd->map, gbase, + dd->name, dd->div_flags, parent_names, + dd->num_parents, dd->flags, &lock); + } + + /* clock gate cells */ + for (n = 0; n < ARRAY_SIZE(bg2_gates); n++) { + const struct berlin2_gate_data *gd = &bg2_gates[n]; + + hws[CLKID_GETH0 + n] = clk_hw_register_gate(NULL, gd->name, + gd->parent_name, gd->flags, gbase + REG_CLKENABLE, + gd->bit_idx, 0, &lock); + } + + /* twdclk is derived from cpu/3 */ + hws[CLKID_TWD] = + clk_hw_register_fixed_factor(NULL, "twd", "cpu", 0, 1, 3); + + /* check for errors on leaf clocks */ + for (n = 0; n < MAX_CLKS; n++) { + if (!IS_ERR(hws[n])) + continue; + + pr_err("%pOF: Unable to register leaf clock %d\n", np, n); + goto bg2_fail; + } + + /* register clk-provider */ + of_clk_add_hw_provider(np, of_clk_hw_onecell_get, clk_data); + + return; + +bg2_fail: + iounmap(gbase); +} +CLK_OF_DECLARE(berlin2_clk, "marvell,berlin2-clk", + berlin2_clock_setup); diff --git a/drivers/clk/berlin/bg2q.c b/drivers/clk/berlin/bg2q.c new file mode 100644 index 000000000..4d407c8c3 --- /dev/null +++ b/drivers/clk/berlin/bg2q.c @@ -0,0 +1,386 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2014 Marvell Technology Group Ltd. + * + * Alexandre Belloni <alexandre.belloni@free-electrons.com> + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com> + */ + +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/slab.h> + +#include <dt-bindings/clock/berlin2q.h> + +#include "berlin2-div.h" +#include "berlin2-pll.h" +#include "common.h" + +#define REG_PINMUX0 0x0018 +#define REG_PINMUX5 0x002c +#define REG_SYSPLLCTL0 0x0030 +#define REG_SYSPLLCTL4 0x0040 +#define REG_CLKENABLE 0x00e8 +#define REG_CLKSELECT0 0x00ec +#define REG_CLKSELECT1 0x00f0 +#define REG_CLKSELECT2 0x00f4 +#define REG_CLKSWITCH0 0x00f8 +#define REG_CLKSWITCH1 0x00fc +#define REG_SW_GENERIC0 0x0110 +#define REG_SW_GENERIC3 0x011c +#define REG_SDIO0XIN_CLKCTL 0x0158 +#define REG_SDIO1XIN_CLKCTL 0x015c + +#define MAX_CLKS 28 +static struct clk_hw_onecell_data *clk_data; +static DEFINE_SPINLOCK(lock); +static void __iomem *gbase; +static void __iomem *cpupll_base; + +enum { + REFCLK, + SYSPLL, CPUPLL, + AVPLL_B1, AVPLL_B2, AVPLL_B3, AVPLL_B4, + AVPLL_B5, AVPLL_B6, AVPLL_B7, AVPLL_B8, +}; + +static const char *clk_names[] = { + [REFCLK] = "refclk", + [SYSPLL] = "syspll", + [CPUPLL] = "cpupll", + [AVPLL_B1] = "avpll_b1", + [AVPLL_B2] = "avpll_b2", + [AVPLL_B3] = "avpll_b3", + [AVPLL_B4] = "avpll_b4", + [AVPLL_B5] = "avpll_b5", + [AVPLL_B6] = "avpll_b6", + [AVPLL_B7] = "avpll_b7", + [AVPLL_B8] = "avpll_b8", +}; + +static const struct berlin2_pll_map bg2q_pll_map __initconst = { + .vcodiv = {1, 0, 2, 0, 3, 4, 0, 6, 8}, + .mult = 1, + .fbdiv_shift = 7, + .rfdiv_shift = 2, + .divsel_shift = 9, +}; + +static const u8 default_parent_ids[] = { + SYSPLL, AVPLL_B4, AVPLL_B5, AVPLL_B6, AVPLL_B7, SYSPLL +}; + +static const struct berlin2_div_data bg2q_divs[] __initconst = { + { + .name = "sys", + .parent_ids = default_parent_ids, + .num_parents = ARRAY_SIZE(default_parent_ids), + .map = { + BERLIN2_DIV_GATE(REG_CLKENABLE, 0), + BERLIN2_PLL_SELECT(REG_CLKSELECT0, 0), + BERLIN2_DIV_SELECT(REG_CLKSELECT0, 3), + BERLIN2_PLL_SWITCH(REG_CLKSWITCH0, 3), + BERLIN2_DIV_SWITCH(REG_CLKSWITCH0, 4), + BERLIN2_DIV_D3SWITCH(REG_CLKSWITCH0, 5), + }, + .div_flags = BERLIN2_DIV_HAS_GATE | BERLIN2_DIV_HAS_MUX, + .flags = CLK_IGNORE_UNUSED, + }, + { + .name = "drmfigo", + .parent_ids = default_parent_ids, + .num_parents = ARRAY_SIZE(default_parent_ids), + .map = { + BERLIN2_DIV_GATE(REG_CLKENABLE, 17), + BERLIN2_PLL_SELECT(REG_CLKSELECT0, 6), + BERLIN2_DIV_SELECT(REG_CLKSELECT0, 9), + BERLIN2_PLL_SWITCH(REG_CLKSWITCH0, 6), + BERLIN2_DIV_SWITCH(REG_CLKSWITCH0, 7), + BERLIN2_DIV_D3SWITCH(REG_CLKSWITCH0, 8), + }, + .div_flags = BERLIN2_DIV_HAS_GATE | BERLIN2_DIV_HAS_MUX, + .flags = 0, + }, + { + .name = "cfg", + .parent_ids = default_parent_ids, + .num_parents = ARRAY_SIZE(default_parent_ids), + .map = { + BERLIN2_DIV_GATE(REG_CLKENABLE, 1), + BERLIN2_PLL_SELECT(REG_CLKSELECT0, 12), + BERLIN2_DIV_SELECT(REG_CLKSELECT0, 15), + BERLIN2_PLL_SWITCH(REG_CLKSWITCH0, 9), + BERLIN2_DIV_SWITCH(REG_CLKSWITCH0, 10), + BERLIN2_DIV_D3SWITCH(REG_CLKSWITCH0, 11), + }, + .div_flags = BERLIN2_DIV_HAS_GATE | BERLIN2_DIV_HAS_MUX, + .flags = 0, + }, + { + .name = "gfx2d", + .parent_ids = default_parent_ids, + .num_parents = ARRAY_SIZE(default_parent_ids), + .map = { + BERLIN2_DIV_GATE(REG_CLKENABLE, 4), + BERLIN2_PLL_SELECT(REG_CLKSELECT0, 18), + BERLIN2_DIV_SELECT(REG_CLKSELECT0, 21), + BERLIN2_PLL_SWITCH(REG_CLKSWITCH0, 12), + BERLIN2_DIV_SWITCH(REG_CLKSWITCH0, 13), + BERLIN2_DIV_D3SWITCH(REG_CLKSWITCH0, 14), + }, + .div_flags = BERLIN2_DIV_HAS_GATE | BERLIN2_DIV_HAS_MUX, + .flags = 0, + }, + { + .name = "zsp", + .parent_ids = default_parent_ids, + .num_parents = ARRAY_SIZE(default_parent_ids), + .map = { + BERLIN2_DIV_GATE(REG_CLKENABLE, 6), + BERLIN2_PLL_SELECT(REG_CLKSELECT0, 24), + BERLIN2_DIV_SELECT(REG_CLKSELECT0, 27), + BERLIN2_PLL_SWITCH(REG_CLKSWITCH0, 15), + BERLIN2_DIV_SWITCH(REG_CLKSWITCH0, 16), + BERLIN2_DIV_D3SWITCH(REG_CLKSWITCH0, 17), + }, + .div_flags = BERLIN2_DIV_HAS_GATE | BERLIN2_DIV_HAS_MUX, + .flags = 0, + }, + { + .name = "perif", + .parent_ids = default_parent_ids, + .num_parents = ARRAY_SIZE(default_parent_ids), + .map = { + BERLIN2_DIV_GATE(REG_CLKENABLE, 7), + BERLIN2_PLL_SELECT(REG_CLKSELECT1, 0), + BERLIN2_DIV_SELECT(REG_CLKSELECT1, 3), + BERLIN2_PLL_SWITCH(REG_CLKSWITCH0, 18), + BERLIN2_DIV_SWITCH(REG_CLKSWITCH0, 19), + BERLIN2_DIV_D3SWITCH(REG_CLKSWITCH0, 20), + }, + .div_flags = BERLIN2_DIV_HAS_GATE | BERLIN2_DIV_HAS_MUX, + .flags = CLK_IGNORE_UNUSED, + }, + { + .name = "pcube", + .parent_ids = default_parent_ids, + .num_parents = ARRAY_SIZE(default_parent_ids), + .map = { + BERLIN2_DIV_GATE(REG_CLKENABLE, 2), + BERLIN2_PLL_SELECT(REG_CLKSELECT1, 6), + BERLIN2_DIV_SELECT(REG_CLKSELECT1, 9), + BERLIN2_PLL_SWITCH(REG_CLKSWITCH0, 21), + BERLIN2_DIV_SWITCH(REG_CLKSWITCH0, 22), + BERLIN2_DIV_D3SWITCH(REG_CLKSWITCH0, 23), + }, + .div_flags = BERLIN2_DIV_HAS_GATE | BERLIN2_DIV_HAS_MUX, + .flags = 0, + }, + { + .name = "vscope", + .parent_ids = default_parent_ids, + .num_parents = ARRAY_SIZE(default_parent_ids), + .map = { + BERLIN2_DIV_GATE(REG_CLKENABLE, 3), + BERLIN2_PLL_SELECT(REG_CLKSELECT1, 12), + BERLIN2_DIV_SELECT(REG_CLKSELECT1, 15), + BERLIN2_PLL_SWITCH(REG_CLKSWITCH0, 24), + BERLIN2_DIV_SWITCH(REG_CLKSWITCH0, 25), + BERLIN2_DIV_D3SWITCH(REG_CLKSWITCH0, 26), + }, + .div_flags = BERLIN2_DIV_HAS_GATE | BERLIN2_DIV_HAS_MUX, + .flags = 0, + }, + { + .name = "nfc_ecc", + .parent_ids = default_parent_ids, + .num_parents = ARRAY_SIZE(default_parent_ids), + .map = { + BERLIN2_DIV_GATE(REG_CLKENABLE, 19), + BERLIN2_PLL_SELECT(REG_CLKSELECT1, 18), + BERLIN2_DIV_SELECT(REG_CLKSELECT1, 21), + BERLIN2_PLL_SWITCH(REG_CLKSWITCH0, 27), + BERLIN2_DIV_SWITCH(REG_CLKSWITCH0, 28), + BERLIN2_DIV_D3SWITCH(REG_CLKSWITCH0, 29), + }, + .div_flags = BERLIN2_DIV_HAS_GATE | BERLIN2_DIV_HAS_MUX, + .flags = 0, + }, + { + .name = "vpp", + .parent_ids = default_parent_ids, + .num_parents = ARRAY_SIZE(default_parent_ids), + .map = { + BERLIN2_DIV_GATE(REG_CLKENABLE, 21), + BERLIN2_PLL_SELECT(REG_CLKSELECT1, 24), + BERLIN2_DIV_SELECT(REG_CLKSELECT1, 27), + BERLIN2_PLL_SWITCH(REG_CLKSWITCH0, 30), + BERLIN2_DIV_SWITCH(REG_CLKSWITCH0, 31), + BERLIN2_DIV_D3SWITCH(REG_CLKSWITCH1, 0), + }, + .div_flags = BERLIN2_DIV_HAS_GATE | BERLIN2_DIV_HAS_MUX, + .flags = 0, + }, + { + .name = "app", + .parent_ids = default_parent_ids, + .num_parents = ARRAY_SIZE(default_parent_ids), + .map = { + BERLIN2_DIV_GATE(REG_CLKENABLE, 20), + BERLIN2_PLL_SELECT(REG_CLKSELECT2, 0), + BERLIN2_DIV_SELECT(REG_CLKSELECT2, 3), + BERLIN2_PLL_SWITCH(REG_CLKSWITCH1, 1), + BERLIN2_DIV_SWITCH(REG_CLKSWITCH1, 2), + BERLIN2_DIV_D3SWITCH(REG_CLKSWITCH1, 3), + }, + .div_flags = BERLIN2_DIV_HAS_GATE | BERLIN2_DIV_HAS_MUX, + .flags = 0, + }, + { + .name = "sdio0xin", + .parent_ids = default_parent_ids, + .num_parents = ARRAY_SIZE(default_parent_ids), + .map = { + BERLIN2_SINGLE_DIV(REG_SDIO0XIN_CLKCTL), + }, + .div_flags = BERLIN2_DIV_HAS_GATE | BERLIN2_DIV_HAS_MUX, + .flags = 0, + }, + { + .name = "sdio1xin", + .parent_ids = default_parent_ids, + .num_parents = ARRAY_SIZE(default_parent_ids), + .map = { + BERLIN2_SINGLE_DIV(REG_SDIO1XIN_CLKCTL), + }, + .div_flags = BERLIN2_DIV_HAS_GATE | BERLIN2_DIV_HAS_MUX, + .flags = 0, + }, +}; + +static const struct berlin2_gate_data bg2q_gates[] __initconst = { + { "gfx2daxi", "perif", 5 }, + { "geth0", "perif", 8 }, + { "sata", "perif", 9 }, + { "ahbapb", "perif", 10, CLK_IGNORE_UNUSED }, + { "usb0", "perif", 11 }, + { "usb1", "perif", 12 }, + { "usb2", "perif", 13 }, + { "usb3", "perif", 14 }, + { "pbridge", "perif", 15, CLK_IGNORE_UNUSED }, + { "sdio", "perif", 16 }, + { "nfc", "perif", 18 }, + { "pcie", "perif", 22 }, +}; + +static void __init berlin2q_clock_setup(struct device_node *np) +{ + struct device_node *parent_np = of_get_parent(np); + const char *parent_names[9]; + struct clk *clk; + struct clk_hw **hws; + int n, ret; + + clk_data = kzalloc_flex(*clk_data, hws, MAX_CLKS); + if (!clk_data) { + of_node_put(parent_np); + return; + } + clk_data->num = MAX_CLKS; + hws = clk_data->hws; + + gbase = of_iomap(parent_np, 0); + if (!gbase) { + of_node_put(parent_np); + pr_err("%pOF: Unable to map global base\n", np); + return; + } + + /* BG2Q CPU PLL is not part of global registers */ + cpupll_base = of_iomap(parent_np, 1); + of_node_put(parent_np); + if (!cpupll_base) { + pr_err("%pOF: Unable to map cpupll base\n", np); + iounmap(gbase); + return; + } + + /* overwrite default clock names with DT provided ones */ + clk = of_clk_get_by_name(np, clk_names[REFCLK]); + if (!IS_ERR(clk)) { + clk_names[REFCLK] = __clk_get_name(clk); + clk_put(clk); + } + + /* simple register PLLs */ + ret = berlin2_pll_register(&bg2q_pll_map, gbase + REG_SYSPLLCTL0, + clk_names[SYSPLL], clk_names[REFCLK], 0); + if (ret) + goto bg2q_fail; + + ret = berlin2_pll_register(&bg2q_pll_map, cpupll_base, + clk_names[CPUPLL], clk_names[REFCLK], 0); + if (ret) + goto bg2q_fail; + + /* TODO: add BG2Q AVPLL */ + + /* + * TODO: add reference clock bypass switches: + * memPLLSWBypass, cpuPLLSWBypass, and sysPLLSWBypass + */ + + /* clock divider cells */ + for (n = 0; n < ARRAY_SIZE(bg2q_divs); n++) { + const struct berlin2_div_data *dd = &bg2q_divs[n]; + int k; + + for (k = 0; k < dd->num_parents; k++) + parent_names[k] = clk_names[dd->parent_ids[k]]; + + hws[CLKID_SYS + n] = berlin2_div_register(&dd->map, gbase, + dd->name, dd->div_flags, parent_names, + dd->num_parents, dd->flags, &lock); + } + + /* clock gate cells */ + for (n = 0; n < ARRAY_SIZE(bg2q_gates); n++) { + const struct berlin2_gate_data *gd = &bg2q_gates[n]; + + hws[CLKID_GFX2DAXI + n] = clk_hw_register_gate(NULL, gd->name, + gd->parent_name, gd->flags, gbase + REG_CLKENABLE, + gd->bit_idx, 0, &lock); + } + + /* cpuclk divider is fixed to 1 */ + hws[CLKID_CPU] = + clk_hw_register_fixed_factor(NULL, "cpu", clk_names[CPUPLL], + 0, 1, 1); + /* twdclk is derived from cpu/3 */ + hws[CLKID_TWD] = + clk_hw_register_fixed_factor(NULL, "twd", "cpu", 0, 1, 3); + + /* check for errors on leaf clocks */ + for (n = 0; n < MAX_CLKS; n++) { + if (!IS_ERR(hws[n])) + continue; + + pr_err("%pOF: Unable to register leaf clock %d\n", np, n); + goto bg2q_fail; + } + + /* register clk-provider */ + of_clk_add_hw_provider(np, of_clk_hw_onecell_get, clk_data); + + return; + +bg2q_fail: + iounmap(cpupll_base); + iounmap(gbase); +} +CLK_OF_DECLARE(berlin2q_clk, "marvell,berlin2q-clk", + berlin2q_clock_setup); diff --git a/drivers/clk/berlin/common.h b/drivers/clk/berlin/common.h new file mode 100644 index 000000000..1afb3c29b --- /dev/null +++ b/drivers/clk/berlin/common.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2014 Marvell Technology Group Ltd. + * + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com> + * Alexandre Belloni <alexandre.belloni@free-electrons.com> + */ +#ifndef __BERLIN2_COMMON_H +#define __BERLIN2_COMMON_H + +struct berlin2_gate_data { + const char *name; + const char *parent_name; + u8 bit_idx; + unsigned long flags; +}; + +#endif /* BERLIN2_COMMON_H */ |
