summaryrefslogtreecommitdiffstats
path: root/drivers/clk/berlin
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2014-06-07 20:27:30 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2014-06-07 20:27:30 -0700
commit1a5700bc2d10cd379a795fd2bb377a190af5acd4 (patch)
treee9f754cbc34020dd23c1d2e3e45fb6890ba7593c /drivers/clk/berlin
parenta68a7509d3af8ee458d32b2416b0c2aaf2a4a7e3 (diff)
parent3cbcb16095f916f50a5a55066fcc4be06946ce1e (diff)
downloadlinux-next.tar.bz2
Merge tag 'clk-for-linus-3.16' of git://git.linaro.org/people/mike.turquette/linux into nextnext
Pull clock framework updates from Mike Turquette: "The clock framework changes for 3.16 are pretty typical: mostly clock driver additions and fixes. There are additions to the clock core code for some of the basic types (e.g. the common divider type has some fixes and featured added to it). One minor annoyance is a last-minute dependency that wasn't handled quite right. Commit ba0fae3b06a6 ("clk: berlin: add core clock driver for BG2/BG2CD") in this pull request depends on include/dt-bindings/clock/berlin2.h, which is already in your tree via the arm-soc pull request. Building for the berlin platform will break when the clk tree is built on it's own, but merged into your master branch everything should be fine" * tag 'clk-for-linus-3.16' of git://git.linaro.org/people/mike.turquette/linux: (75 commits) mmc: sunxi: Add driver for SD/MMC hosts found on Allwinner sunxi SoCs clk: export __clk_round_rate for providers clk: versatile: free icst on error return clk: qcom: Return error pointers for unimplemented clocks clk: qcom: Support msm8974pro global clock control hardware clk: qcom: Properly support display clocks on msm8974 clk: qcom: Support display RCG clocks clk: qcom: Return highest rate when round_rate() exceeds plan clk: qcom: Fix mmcc-8974's PLL configurations clk: qcom: Fix clk_rcg2_is_enabled() check clk: berlin: add core clock driver for BG2Q clk: berlin: add core clock driver for BG2/BG2CD clk: berlin: add driver for BG2x complex divider cells clk: berlin: add driver for BG2x simple PLLs clk: berlin: add driver for BG2x audio/video PLL clk: st: Terminate of match table clk/exynos4: Fix compilation warning ARM: shmobile: r8a7779: Add clock index macros for DT sources clk: divider: Fix overflow in clk_divider_bestdiv clk: u300: Terminate of match table ...
Diffstat (limited to 'drivers/clk/berlin')
-rw-r--r--drivers/clk/berlin/Makefile4
-rw-r--r--drivers/clk/berlin/berlin2-avpll.c393
-rw-r--r--drivers/clk/berlin/berlin2-avpll.h36
-rw-r--r--drivers/clk/berlin/berlin2-div.c265
-rw-r--r--drivers/clk/berlin/berlin2-div.h89
-rw-r--r--drivers/clk/berlin/berlin2-pll.c117
-rw-r--r--drivers/clk/berlin/berlin2-pll.h37
-rw-r--r--drivers/clk/berlin/bg2.c691
-rw-r--r--drivers/clk/berlin/bg2q.c389
-rw-r--r--drivers/clk/berlin/common.h29
10 files changed, 2050 insertions, 0 deletions
diff --git a/drivers/clk/berlin/Makefile b/drivers/clk/berlin/Makefile
new file mode 100644
index 000000000000..2a36ab710a07
--- /dev/null
+++ b/drivers/clk/berlin/Makefile
@@ -0,0 +1,4 @@
+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 000000000000..fd0f26c38465
--- /dev/null
+++ b/drivers/clk/berlin/berlin2-avpll.c
@@ -0,0 +1,393 @@
+/*
+ * Copyright (c) 2014 Marvell Technology Group Ltd.
+ *
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ * Alexandre Belloni <alexandre.belloni@free-electrons.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#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,
+};
+
+struct clk * __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(sizeof(*vco), GFP_KERNEL);
+ if (!vco)
+ return ERR_PTR(-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_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 specfic 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 };
+
+struct clk * __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(sizeof(*ch), GFP_KERNEL);
+ if (!ch)
+ return ERR_PTR(-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_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 000000000000..a37f5068d299
--- /dev/null
+++ b/drivers/clk/berlin/berlin2-avpll.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2014 Marvell Technology Group Ltd.
+ *
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ * Alexandre Belloni <alexandre.belloni@free-electrons.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef __BERLIN2_AVPLL_H
+#define __BERLIN2_AVPLL_H
+
+struct clk;
+
+#define BERLIN2_AVPLL_BIT_QUIRK BIT(0)
+#define BERLIN2_AVPLL_SCRAMBLE_QUIRK BIT(1)
+
+struct clk * __init
+berlin2_avpll_vco_register(void __iomem *base, const char *name,
+ const char *parent_name, u8 vco_flags, unsigned long flags);
+
+struct clk * __init
+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 000000000000..81ff97f8aa0b
--- /dev/null
+++ b/drivers/clk/berlin/berlin2-div.c
@@ -0,0 +1,265 @@
+/*
+ * Copyright (c) 2014 Marvell Technology Group Ltd.
+ *
+ * Alexandre Belloni <alexandre.belloni@free-electrons.com>
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#include <linux/bitops.h>
+#include <linux/clk-provider.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 = {
+ .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 * __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(sizeof(*div), GFP_KERNEL);
+ 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_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 000000000000..15e3384f3116
--- /dev/null
+++ b/drivers/clk/berlin/berlin2-div.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2014 Marvell Technology Group Ltd.
+ *
+ * Alexandre Belloni <alexandre.belloni@free-electrons.com>
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef __BERLIN2_DIV_H
+#define __BERLIN2_DIV_H
+
+struct clk;
+
+#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 * __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);
+
+#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 000000000000..bdc506b03824
--- /dev/null
+++ b/drivers/clk/berlin/berlin2-pll.c
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2014 Marvell Technology Group Ltd.
+ *
+ * Alexandre Belloni <alexandre.belloni@free-electrons.com>
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#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"
+
+struct berlin2_pll_map {
+ const u8 vcodiv[16];
+ u8 mult;
+ u8 fbdiv_shift;
+ u8 rfdiv_shift;
+ u8 divsel_shift;
+};
+
+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_get_name(hw->clk));
+ 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_get_name(hw->clk), 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,
+};
+
+struct clk * __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(sizeof(*pll), GFP_KERNEL);
+ if (!pll)
+ return ERR_PTR(-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_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 000000000000..8831ce27ac1e
--- /dev/null
+++ b/drivers/clk/berlin/berlin2-pll.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2014 Marvell Technology Group Ltd.
+ *
+ * Alexandre Belloni <alexandre.belloni@free-electrons.com>
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef __BERLIN2_PLL_H
+#define __BERLIN2_PLL_H
+
+struct clk;
+
+struct berlin2_pll_map {
+ const u8 vcodiv[16];
+ u8 mult;
+ u8 fbdiv_shift;
+ u8 rfdiv_shift;
+ u8 divsel_shift;
+};
+
+struct clk * __init
+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 000000000000..515fb133495c
--- /dev/null
+++ b/drivers/clk/berlin/bg2.c
@@ -0,0 +1,691 @@
+/*
+ * Copyright (c) 2014 Marvell Technology Group Ltd.
+ *
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ * Alexandre Belloni <alexandre.belloni@free-electrons.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.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 *clks[MAX_CLKS];
+static struct clk_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, CLK_IGNORE_UNUSED },
+ { "sdio1", "perif", 15, CLK_IGNORE_UNUSED },
+ { "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)
+{
+ const char *parent_names[9];
+ struct clk *clk;
+ u8 avpll_flags = 0;
+ int n;
+
+ gbase = of_iomap(np, 0);
+ 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 */
+ clk = berlin2_pll_register(&bg2_pll_map, gbase + REG_SYSPLLCTL0,
+ clk_names[SYSPLL], clk_names[REFCLK], 0);
+ if (IS_ERR(clk))
+ goto bg2_fail;
+
+ clk = berlin2_pll_register(&bg2_pll_map, gbase + REG_MEMPLLCTL0,
+ clk_names[MEMPLL], clk_names[REFCLK], 0);
+ if (IS_ERR(clk))
+ goto bg2_fail;
+
+ clk = berlin2_pll_register(&bg2_pll_map, gbase + REG_CPUPLLCTL0,
+ clk_names[CPUPLL], clk_names[REFCLK], 0);
+ if (IS_ERR(clk))
+ goto bg2_fail;
+
+ if (of_device_is_compatible(np, "marvell,berlin2-global-register"))
+ avpll_flags |= BERLIN2_AVPLL_SCRAMBLE_QUIRK;
+
+ /* audio/video VCOs */
+ clk = berlin2_avpll_vco_register(gbase + REG_AVPLLCTL0, "avpll_vcoA",
+ clk_names[REFCLK], avpll_flags, 0);
+ if (IS_ERR(clk))
+ goto bg2_fail;
+
+ for (n = 0; n < 8; n++) {
+ clk = berlin2_avpll_channel_register(gbase + REG_AVPLLCTL0,
+ clk_names[AVPLL_A1 + n], n, "avpll_vcoA",
+ avpll_flags, 0);
+ if (IS_ERR(clk))
+ goto bg2_fail;
+ }
+
+ clk = berlin2_avpll_vco_register(gbase + REG_AVPLLCTL31, "avpll_vcoB",
+ clk_names[REFCLK], BERLIN2_AVPLL_BIT_QUIRK |
+ avpll_flags, 0);
+ if (IS_ERR(clk))
+ goto bg2_fail;
+
+ for (n = 0; n < 8; n++) {
+ clk = berlin2_avpll_channel_register(gbase + REG_AVPLLCTL31,
+ clk_names[AVPLL_B1 + n], n, "avpll_vcoB",
+ BERLIN2_AVPLL_BIT_QUIRK | avpll_flags, 0);
+ if (IS_ERR(clk))
+ goto bg2_fail;
+ }
+
+ /* reference clock bypass switches */
+ parent_names[0] = clk_names[SYSPLL];
+ parent_names[1] = clk_names[REFCLK];
+ clk = clk_register_mux(NULL, "syspll_byp", parent_names, 2,
+ 0, gbase + REG_CLKSWITCH0, 0, 1, 0, &lock);
+ if (IS_ERR(clk))
+ goto bg2_fail;
+ clk_names[SYSPLL] = __clk_get_name(clk);
+
+ parent_names[0] = clk_names[MEMPLL];
+ parent_names[1] = clk_names[REFCLK];
+ clk = clk_register_mux(NULL, "mempll_byp", parent_names, 2,
+ 0, gbase + REG_CLKSWITCH0, 1, 1, 0, &lock);
+ if (IS_ERR(clk))
+ goto bg2_fail;
+ clk_names[MEMPLL] = __clk_get_name(clk);
+
+ parent_names[0] = clk_names[CPUPLL];
+ parent_names[1] = clk_names[REFCLK];
+ clk = clk_register_mux(NULL, "cpupll_byp", parent_names, 2,
+ 0, gbase + REG_CLKSWITCH0, 2, 1, 0, &lock);
+ if (IS_ERR(clk))
+ goto bg2_fail;
+ clk_names[CPUPLL] = __clk_get_name(clk);
+
+ /* clock muxes */
+ parent_names[0] = clk_names[AVPLL_B3];
+ parent_names[1] = clk_names[AVPLL_A3];
+ clk = clk_register_mux(NULL, clk_names[AUDIO1_PLL], parent_names, 2,
+ 0, gbase + REG_CLKSELECT2, 29, 1, 0, &lock);
+ if (IS_ERR(clk))
+ goto bg2_fail;
+
+ parent_names[0] = clk_names[VIDEO0_PLL];
+ parent_names[1] = clk_names[VIDEO_EXT0];
+ clk = clk_register_mux(NULL, clk_names[VIDEO0_IN], parent_names, 2,
+ 0, gbase + REG_CLKSELECT3, 4, 1, 0, &lock);
+ if (IS_ERR(clk))
+ goto bg2_fail;
+
+ parent_names[0] = clk_names[VIDEO1_PLL];
+ parent_names[1] = clk_names[VIDEO_EXT0];
+ clk = clk_register_mux(NULL, clk_names[VIDEO1_IN], parent_names, 2,
+ 0, gbase + REG_CLKSELECT3, 6, 1, 0, &lock);
+ if (IS_ERR(clk))
+ goto bg2_fail;
+
+ parent_names[0] = clk_names[AVPLL_A2];
+ parent_names[1] = clk_names[AVPLL_B2];
+ clk = clk_register_mux(NULL, clk_names[VIDEO1_PLL], parent_names, 2,
+ 0, gbase + REG_CLKSELECT3, 7, 1, 0, &lock);
+ if (IS_ERR(clk))
+ goto bg2_fail;
+
+ parent_names[0] = clk_names[VIDEO2_PLL];
+ parent_names[1] = clk_names[VIDEO_EXT0];
+ clk = clk_register_mux(NULL, clk_names[VIDEO2_IN], parent_names, 2,
+ 0, gbase + REG_CLKSELECT3, 9, 1, 0, &lock);
+ if (IS_ERR(clk))
+ goto bg2_fail;
+
+ parent_names[0] = clk_names[AVPLL_B1];
+ parent_names[1] = clk_names[AVPLL_A5];
+ clk = clk_register_mux(NULL, clk_names[VIDEO2_PLL], parent_names, 2,
+ 0, gbase + REG_CLKSELECT3, 10, 1, 0, &lock);
+ if (IS_ERR(clk))
+ 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]];
+
+ clks[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];
+
+ clks[CLKID_GETH0 + n] = clk_register_gate(NULL, gd->name,
+ gd->parent_name, gd->flags, gbase + REG_CLKENABLE,
+ gd->bit_idx, 0, &lock);
+ }
+
+ /* twdclk is derived from cpu/3 */
+ clks[CLKID_TWD] =
+ clk_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(clks[n]))
+ continue;
+
+ pr_err("%s: Unable to register leaf clock %d\n",
+ np->full_name, n);
+ goto bg2_fail;
+ }
+
+ /* register clk-provider */
+ clk_data.clks = clks;
+ clk_data.clk_num = MAX_CLKS;
+ of_clk_add_provider(np, of_clk_src_onecell_get, &clk_data);
+
+ return;
+
+bg2_fail:
+ iounmap(gbase);
+}
+CLK_OF_DECLARE(berlin2_clock, "marvell,berlin2-chip-ctrl",
+ berlin2_clock_setup);
+CLK_OF_DECLARE(berlin2cd_clock, "marvell,berlin2cd-chip-ctrl",
+ berlin2_clock_setup);
diff --git a/drivers/clk/berlin/bg2q.c b/drivers/clk/berlin/bg2q.c
new file mode 100644
index 000000000000..21784e4eb3f0
--- /dev/null
+++ b/drivers/clk/berlin/bg2q.c
@@ -0,0 +1,389 @@
+/*
+ * Copyright (c) 2014 Marvell Technology Group Ltd.
+ *
+ * Alexandre Belloni <alexandre.belloni@free-electrons.com>
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.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 27
+static struct clk *clks[MAX_CLKS];
+static struct clk_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, CLK_IGNORE_UNUSED },
+ { "nfc", "perif", 18 },
+ { "smemc", "perif", 19 },
+ { "pcie", "perif", 22 },
+};
+
+static void __init berlin2q_clock_setup(struct device_node *np)
+{
+ const char *parent_names[9];
+ struct clk *clk;
+ int n;
+
+ gbase = of_iomap(np, 0);
+ if (!gbase) {
+ pr_err("%s: Unable to map global base\n", np->full_name);
+ return;
+ }
+
+ /* BG2Q CPU PLL is not part of global registers */
+ cpupll_base = of_iomap(np, 1);
+ if (!cpupll_base) {
+ pr_err("%s: Unable to map cpupll base\n", np->full_name);
+ 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 */
+ clk = berlin2_pll_register(&bg2q_pll_map, gbase + REG_SYSPLLCTL0,
+ clk_names[SYSPLL], clk_names[REFCLK], 0);
+ if (IS_ERR(clk))
+ goto bg2q_fail;
+
+ clk = berlin2_pll_register(&bg2q_pll_map, cpupll_base,
+ clk_names[CPUPLL], clk_names[REFCLK], 0);
+ if (IS_ERR(clk))
+ 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]];
+
+ clks[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];
+
+ clks[CLKID_GFX2DAXI + n] = clk_register_gate(NULL, gd->name,
+ gd->parent_name, gd->flags, gbase + REG_CLKENABLE,
+ gd->bit_idx, 0, &lock);
+ }
+
+ /*
+ * twdclk is derived from cpu/3
+ * TODO: use cpupll until cpuclk is not available
+ */
+ clks[CLKID_TWD] =
+ clk_register_fixed_factor(NULL, "twd", clk_names[CPUPLL],
+ 0, 1, 3);
+
+ /* check for errors on leaf clocks */
+ for (n = 0; n < MAX_CLKS; n++) {
+ if (!IS_ERR(clks[n]))
+ continue;
+
+ pr_err("%s: Unable to register leaf clock %d\n",
+ np->full_name, n);
+ goto bg2q_fail;
+ }
+
+ /* register clk-provider */
+ clk_data.clks = clks;
+ clk_data.clk_num = MAX_CLKS;
+ of_clk_add_provider(np, of_clk_src_onecell_get, &clk_data);
+
+ return;
+
+bg2q_fail:
+ iounmap(cpupll_base);
+ iounmap(gbase);
+}
+CLK_OF_DECLARE(berlin2q_clock, "marvell,berlin2q-chip-ctrl",
+ berlin2q_clock_setup);
diff --git a/drivers/clk/berlin/common.h b/drivers/clk/berlin/common.h
new file mode 100644
index 000000000000..bc68a14c4550
--- /dev/null
+++ b/drivers/clk/berlin/common.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2014 Marvell Technology Group Ltd.
+ *
+ * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
+ * Alexandre Belloni <alexandre.belloni@free-electrons.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#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 */