aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/drivers/clk/mmp
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/clk/mmp')
-rw-r--r--drivers/clk/mmp/Makefile3
-rw-r--r--drivers/clk/mmp/clk-audio.c443
-rw-r--r--drivers/clk/mmp/clk-frac.c27
-rw-r--r--drivers/clk/mmp/clk-of-mmp2.c104
-rw-r--r--drivers/clk/mmp/clk.h11
-rw-r--r--drivers/clk/mmp/pwr-island.c115
6 files changed, 688 insertions, 15 deletions
diff --git a/drivers/clk/mmp/Makefile b/drivers/clk/mmp/Makefile
index 14dc8a8a9d08..cbcc2f8430a2 100644
--- a/drivers/clk/mmp/Makefile
+++ b/drivers/clk/mmp/Makefile
@@ -8,7 +8,8 @@ obj-y += clk-apbc.o clk-apmu.o clk-frac.o clk-mix.o clk-gate.o clk.o
obj-$(CONFIG_RESET_CONTROLLER) += reset.o
obj-$(CONFIG_MACH_MMP_DT) += clk-of-pxa168.o clk-of-pxa910.o
-obj-$(CONFIG_COMMON_CLK_MMP2) += clk-of-mmp2.o clk-pll.o
+obj-$(CONFIG_COMMON_CLK_MMP2) += clk-of-mmp2.o clk-pll.o pwr-island.o
+obj-$(CONFIG_COMMON_CLK_MMP2_AUDIO) += clk-audio.o
obj-$(CONFIG_CPU_PXA168) += clk-pxa168.o
obj-$(CONFIG_CPU_PXA910) += clk-pxa910.o
diff --git a/drivers/clk/mmp/clk-audio.c b/drivers/clk/mmp/clk-audio.c
new file mode 100644
index 000000000000..eea69d498bd2
--- /dev/null
+++ b/drivers/clk/mmp/clk-audio.c
@@ -0,0 +1,443 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * MMP Audio Clock Controller driver
+ *
+ * Copyright (C) 2020 Lubomir Rintel <lkundrak@v3.sk>
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm_clock.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+#include <dt-bindings/clock/marvell,mmp2-audio.h>
+
+/* Audio Controller Registers */
+#define SSPA_AUD_CTRL 0x04
+#define SSPA_AUD_PLL_CTRL0 0x08
+#define SSPA_AUD_PLL_CTRL1 0x0c
+
+/* SSPA Audio Control Register */
+#define SSPA_AUD_CTRL_SYSCLK_SHIFT 0
+#define SSPA_AUD_CTRL_SYSCLK_DIV_SHIFT 1
+#define SSPA_AUD_CTRL_SSPA0_MUX_SHIFT 7
+#define SSPA_AUD_CTRL_SSPA0_SHIFT 8
+#define SSPA_AUD_CTRL_SSPA0_DIV_SHIFT 9
+#define SSPA_AUD_CTRL_SSPA1_SHIFT 16
+#define SSPA_AUD_CTRL_SSPA1_DIV_SHIFT 17
+#define SSPA_AUD_CTRL_SSPA1_MUX_SHIFT 23
+#define SSPA_AUD_CTRL_DIV_MASK 0x7e
+
+/* SSPA Audio PLL Control 0 Register */
+#define SSPA_AUD_PLL_CTRL0_DIV_OCLK_MODULO_MASK (0x7 << 28)
+#define SSPA_AUD_PLL_CTRL0_DIV_OCLK_MODULO(x) ((x) << 28)
+#define SSPA_AUD_PLL_CTRL0_FRACT_MASK (0xfffff << 8)
+#define SSPA_AUD_PLL_CTRL0_FRACT(x) ((x) << 8)
+#define SSPA_AUD_PLL_CTRL0_ENA_DITHER (1 << 7)
+#define SSPA_AUD_PLL_CTRL0_ICP_2UA (0 << 5)
+#define SSPA_AUD_PLL_CTRL0_ICP_5UA (1 << 5)
+#define SSPA_AUD_PLL_CTRL0_ICP_7UA (2 << 5)
+#define SSPA_AUD_PLL_CTRL0_ICP_10UA (3 << 5)
+#define SSPA_AUD_PLL_CTRL0_DIV_FBCCLK_MASK (0x3 << 3)
+#define SSPA_AUD_PLL_CTRL0_DIV_FBCCLK(x) ((x) << 3)
+#define SSPA_AUD_PLL_CTRL0_DIV_MCLK_MASK (0x1 << 2)
+#define SSPA_AUD_PLL_CTRL0_DIV_MCLK(x) ((x) << 2)
+#define SSPA_AUD_PLL_CTRL0_PD_OVPROT_DIS (1 << 1)
+#define SSPA_AUD_PLL_CTRL0_PU (1 << 0)
+
+/* SSPA Audio PLL Control 1 Register */
+#define SSPA_AUD_PLL_CTRL1_SEL_FAST_CLK (1 << 24)
+#define SSPA_AUD_PLL_CTRL1_CLK_SEL_MASK (1 << 11)
+#define SSPA_AUD_PLL_CTRL1_CLK_SEL_AUDIO_PLL (1 << 11)
+#define SSPA_AUD_PLL_CTRL1_CLK_SEL_VCXO (0 << 11)
+#define SSPA_AUD_PLL_CTRL1_DIV_OCLK_PATTERN_MASK (0x7ff << 0)
+#define SSPA_AUD_PLL_CTRL1_DIV_OCLK_PATTERN(x) ((x) << 0)
+
+struct mmp2_audio_clk {
+ void __iomem *mmio_base;
+
+ struct clk_hw audio_pll_hw;
+ struct clk_mux sspa_mux;
+ struct clk_mux sspa1_mux;
+ struct clk_divider sysclk_div;
+ struct clk_divider sspa0_div;
+ struct clk_divider sspa1_div;
+ struct clk_gate sysclk_gate;
+ struct clk_gate sspa0_gate;
+ struct clk_gate sspa1_gate;
+
+ u32 aud_ctrl;
+ u32 aud_pll_ctrl0;
+ u32 aud_pll_ctrl1;
+
+ spinlock_t lock;
+
+ /* Must be last */
+ struct clk_hw_onecell_data clk_data;
+};
+
+static const struct {
+ unsigned long parent_rate;
+ unsigned long freq_vco;
+ unsigned char mclk;
+ unsigned char fbcclk;
+ unsigned short fract;
+} predivs[] = {
+ { 26000000, 135475200, 0, 0, 0x8a18 },
+ { 26000000, 147456000, 0, 1, 0x0da1 },
+ { 38400000, 135475200, 1, 2, 0x8208 },
+ { 38400000, 147456000, 1, 3, 0xaaaa },
+};
+
+static const struct {
+ unsigned char divisor;
+ unsigned char modulo;
+ unsigned char pattern;
+} postdivs[] = {
+ { 1, 3, 0, },
+ { 2, 5, 0, },
+ { 4, 0, 0, },
+ { 6, 1, 1, },
+ { 8, 1, 0, },
+ { 9, 1, 2, },
+ { 12, 2, 1, },
+ { 16, 2, 0, },
+ { 18, 2, 2, },
+ { 24, 4, 1, },
+ { 36, 4, 2, },
+ { 48, 6, 1, },
+ { 72, 6, 2, },
+};
+
+static unsigned long audio_pll_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct mmp2_audio_clk *priv = container_of(hw, struct mmp2_audio_clk, audio_pll_hw);
+ unsigned int prediv;
+ unsigned int postdiv;
+ u32 aud_pll_ctrl0;
+ u32 aud_pll_ctrl1;
+
+ aud_pll_ctrl0 = readl(priv->mmio_base + SSPA_AUD_PLL_CTRL0);
+ aud_pll_ctrl0 &= SSPA_AUD_PLL_CTRL0_DIV_OCLK_MODULO_MASK |
+ SSPA_AUD_PLL_CTRL0_FRACT_MASK |
+ SSPA_AUD_PLL_CTRL0_ENA_DITHER |
+ SSPA_AUD_PLL_CTRL0_DIV_FBCCLK_MASK |
+ SSPA_AUD_PLL_CTRL0_DIV_MCLK_MASK |
+ SSPA_AUD_PLL_CTRL0_PU;
+
+ aud_pll_ctrl1 = readl(priv->mmio_base + SSPA_AUD_PLL_CTRL1);
+ aud_pll_ctrl1 &= SSPA_AUD_PLL_CTRL1_CLK_SEL_MASK |
+ SSPA_AUD_PLL_CTRL1_DIV_OCLK_PATTERN_MASK;
+
+ for (prediv = 0; prediv < ARRAY_SIZE(predivs); prediv++) {
+ if (predivs[prediv].parent_rate != parent_rate)
+ continue;
+ for (postdiv = 0; postdiv < ARRAY_SIZE(postdivs); postdiv++) {
+ unsigned long freq;
+ u32 val;
+
+ val = SSPA_AUD_PLL_CTRL0_ENA_DITHER;
+ val |= SSPA_AUD_PLL_CTRL0_PU;
+ val |= SSPA_AUD_PLL_CTRL0_DIV_OCLK_MODULO(postdivs[postdiv].modulo);
+ val |= SSPA_AUD_PLL_CTRL0_FRACT(predivs[prediv].fract);
+ val |= SSPA_AUD_PLL_CTRL0_DIV_FBCCLK(predivs[prediv].fbcclk);
+ val |= SSPA_AUD_PLL_CTRL0_DIV_MCLK(predivs[prediv].mclk);
+ if (val != aud_pll_ctrl0)
+ continue;
+
+ val = SSPA_AUD_PLL_CTRL1_CLK_SEL_AUDIO_PLL;
+ val |= SSPA_AUD_PLL_CTRL1_DIV_OCLK_PATTERN(postdivs[postdiv].pattern);
+ if (val != aud_pll_ctrl1)
+ continue;
+
+ freq = predivs[prediv].freq_vco;
+ freq /= postdivs[postdiv].divisor;
+ return freq;
+ }
+ }
+
+ return 0;
+}
+
+static long audio_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ unsigned int prediv;
+ unsigned int postdiv;
+ long rounded = 0;
+
+ for (prediv = 0; prediv < ARRAY_SIZE(predivs); prediv++) {
+ if (predivs[prediv].parent_rate != *parent_rate)
+ continue;
+ for (postdiv = 0; postdiv < ARRAY_SIZE(postdivs); postdiv++) {
+ long freq = predivs[prediv].freq_vco;
+
+ freq /= postdivs[postdiv].divisor;
+ if (freq == rate)
+ return rate;
+ if (freq < rate)
+ continue;
+ if (rounded && freq > rounded)
+ continue;
+ rounded = freq;
+ }
+ }
+
+ return rounded;
+}
+
+static int audio_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct mmp2_audio_clk *priv = container_of(hw, struct mmp2_audio_clk, audio_pll_hw);
+ unsigned int prediv;
+ unsigned int postdiv;
+ unsigned long val;
+
+ for (prediv = 0; prediv < ARRAY_SIZE(predivs); prediv++) {
+ if (predivs[prediv].parent_rate != parent_rate)
+ continue;
+
+ for (postdiv = 0; postdiv < ARRAY_SIZE(postdivs); postdiv++) {
+ if (rate * postdivs[postdiv].divisor != predivs[prediv].freq_vco)
+ continue;
+
+ val = SSPA_AUD_PLL_CTRL0_ENA_DITHER;
+ val |= SSPA_AUD_PLL_CTRL0_PU;
+ val |= SSPA_AUD_PLL_CTRL0_DIV_OCLK_MODULO(postdivs[postdiv].modulo);
+ val |= SSPA_AUD_PLL_CTRL0_FRACT(predivs[prediv].fract);
+ val |= SSPA_AUD_PLL_CTRL0_DIV_FBCCLK(predivs[prediv].fbcclk);
+ val |= SSPA_AUD_PLL_CTRL0_DIV_MCLK(predivs[prediv].mclk);
+ writel(val, priv->mmio_base + SSPA_AUD_PLL_CTRL0);
+
+ val = SSPA_AUD_PLL_CTRL1_CLK_SEL_AUDIO_PLL;
+ val |= SSPA_AUD_PLL_CTRL1_DIV_OCLK_PATTERN(postdivs[postdiv].pattern);
+ writel(val, priv->mmio_base + SSPA_AUD_PLL_CTRL1);
+
+ return 0;
+ }
+ }
+
+ return -ERANGE;
+}
+
+static const struct clk_ops audio_pll_ops = {
+ .recalc_rate = audio_pll_recalc_rate,
+ .round_rate = audio_pll_round_rate,
+ .set_rate = audio_pll_set_rate,
+};
+
+static int register_clocks(struct mmp2_audio_clk *priv, struct device *dev)
+{
+ const struct clk_parent_data sspa_mux_parents[] = {
+ { .hw = &priv->audio_pll_hw },
+ { .fw_name = "i2s0" },
+ };
+ const struct clk_parent_data sspa1_mux_parents[] = {
+ { .hw = &priv->audio_pll_hw },
+ { .fw_name = "i2s1" },
+ };
+ int ret;
+
+ priv->audio_pll_hw.init = CLK_HW_INIT_FW_NAME("audio_pll",
+ "vctcxo", &audio_pll_ops,
+ CLK_SET_RATE_PARENT);
+ ret = devm_clk_hw_register(dev, &priv->audio_pll_hw);
+ if (ret)
+ return ret;
+
+ priv->sspa_mux.hw.init = CLK_HW_INIT_PARENTS_DATA("sspa_mux",
+ sspa_mux_parents, &clk_mux_ops,
+ CLK_SET_RATE_PARENT);
+ priv->sspa_mux.reg = priv->mmio_base + SSPA_AUD_CTRL;
+ priv->sspa_mux.mask = 1;
+ priv->sspa_mux.shift = SSPA_AUD_CTRL_SSPA0_MUX_SHIFT;
+ ret = devm_clk_hw_register(dev, &priv->sspa_mux.hw);
+ if (ret)
+ return ret;
+
+ priv->sysclk_div.hw.init = CLK_HW_INIT_HW("sys_div",
+ &priv->sspa_mux.hw, &clk_divider_ops,
+ CLK_SET_RATE_PARENT);
+ priv->sysclk_div.reg = priv->mmio_base + SSPA_AUD_CTRL;
+ priv->sysclk_div.shift = SSPA_AUD_CTRL_SYSCLK_DIV_SHIFT;
+ priv->sysclk_div.width = 6;
+ priv->sysclk_div.flags = CLK_DIVIDER_ONE_BASED;
+ priv->sysclk_div.flags |= CLK_DIVIDER_ROUND_CLOSEST;
+ priv->sysclk_div.flags |= CLK_DIVIDER_ALLOW_ZERO;
+ ret = devm_clk_hw_register(dev, &priv->sysclk_div.hw);
+ if (ret)
+ return ret;
+
+ priv->sysclk_gate.hw.init = CLK_HW_INIT_HW("sys_clk",
+ &priv->sysclk_div.hw, &clk_gate_ops,
+ CLK_SET_RATE_PARENT);
+ priv->sysclk_gate.reg = priv->mmio_base + SSPA_AUD_CTRL;
+ priv->sysclk_gate.bit_idx = SSPA_AUD_CTRL_SYSCLK_SHIFT;
+ ret = devm_clk_hw_register(dev, &priv->sysclk_gate.hw);
+ if (ret)
+ return ret;
+
+ priv->sspa0_div.hw.init = CLK_HW_INIT_HW("sspa0_div",
+ &priv->sspa_mux.hw, &clk_divider_ops, 0);
+ priv->sspa0_div.reg = priv->mmio_base + SSPA_AUD_CTRL;
+ priv->sspa0_div.shift = SSPA_AUD_CTRL_SSPA0_DIV_SHIFT;
+ priv->sspa0_div.width = 6;
+ priv->sspa0_div.flags = CLK_DIVIDER_ONE_BASED;
+ priv->sspa0_div.flags |= CLK_DIVIDER_ROUND_CLOSEST;
+ priv->sspa0_div.flags |= CLK_DIVIDER_ALLOW_ZERO;
+ ret = devm_clk_hw_register(dev, &priv->sspa0_div.hw);
+ if (ret)
+ return ret;
+
+ priv->sspa0_gate.hw.init = CLK_HW_INIT_HW("sspa0_clk",
+ &priv->sspa0_div.hw, &clk_gate_ops,
+ CLK_SET_RATE_PARENT);
+ priv->sspa0_gate.reg = priv->mmio_base + SSPA_AUD_CTRL;
+ priv->sspa0_gate.bit_idx = SSPA_AUD_CTRL_SSPA0_SHIFT;
+ ret = devm_clk_hw_register(dev, &priv->sspa0_gate.hw);
+ if (ret)
+ return ret;
+
+ priv->sspa1_mux.hw.init = CLK_HW_INIT_PARENTS_DATA("sspa1_mux",
+ sspa1_mux_parents, &clk_mux_ops,
+ CLK_SET_RATE_PARENT);
+ priv->sspa1_mux.reg = priv->mmio_base + SSPA_AUD_CTRL;
+ priv->sspa1_mux.mask = 1;
+ priv->sspa1_mux.shift = SSPA_AUD_CTRL_SSPA1_MUX_SHIFT;
+ ret = devm_clk_hw_register(dev, &priv->sspa1_mux.hw);
+ if (ret)
+ return ret;
+
+ priv->sspa1_div.hw.init = CLK_HW_INIT_HW("sspa1_div",
+ &priv->sspa1_mux.hw, &clk_divider_ops, 0);
+ priv->sspa1_div.reg = priv->mmio_base + SSPA_AUD_CTRL;
+ priv->sspa1_div.shift = SSPA_AUD_CTRL_SSPA1_DIV_SHIFT;
+ priv->sspa1_div.width = 6;
+ priv->sspa1_div.flags = CLK_DIVIDER_ONE_BASED;
+ priv->sspa1_div.flags |= CLK_DIVIDER_ROUND_CLOSEST;
+ priv->sspa1_div.flags |= CLK_DIVIDER_ALLOW_ZERO;
+ ret = devm_clk_hw_register(dev, &priv->sspa1_div.hw);
+ if (ret)
+ return ret;
+
+ priv->sspa1_gate.hw.init = CLK_HW_INIT_HW("sspa1_clk",
+ &priv->sspa1_div.hw, &clk_gate_ops,
+ CLK_SET_RATE_PARENT);
+ priv->sspa1_gate.reg = priv->mmio_base + SSPA_AUD_CTRL;
+ priv->sspa1_gate.bit_idx = SSPA_AUD_CTRL_SSPA1_SHIFT;
+ ret = devm_clk_hw_register(dev, &priv->sspa1_gate.hw);
+ if (ret)
+ return ret;
+
+ priv->clk_data.hws[MMP2_CLK_AUDIO_SYSCLK] = &priv->sysclk_gate.hw;
+ priv->clk_data.hws[MMP2_CLK_AUDIO_SSPA0] = &priv->sspa0_gate.hw;
+ priv->clk_data.hws[MMP2_CLK_AUDIO_SSPA1] = &priv->sspa1_gate.hw;
+ priv->clk_data.num = MMP2_CLK_AUDIO_NR_CLKS;
+
+ return of_clk_add_hw_provider(dev->of_node, of_clk_hw_onecell_get,
+ &priv->clk_data);
+}
+
+static int mmp2_audio_clk_probe(struct platform_device *pdev)
+{
+ struct mmp2_audio_clk *priv;
+ int ret;
+
+ priv = devm_kzalloc(&pdev->dev,
+ struct_size(priv, clk_data.hws,
+ MMP2_CLK_AUDIO_NR_CLKS),
+ GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ spin_lock_init(&priv->lock);
+ platform_set_drvdata(pdev, priv);
+
+ priv->mmio_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(priv->mmio_base))
+ return PTR_ERR(priv->mmio_base);
+
+ pm_runtime_enable(&pdev->dev);
+ ret = pm_clk_create(&pdev->dev);
+ if (ret)
+ goto disable_pm_runtime;
+
+ ret = pm_clk_add(&pdev->dev, "audio");
+ if (ret)
+ goto destroy_pm_clk;
+
+ ret = register_clocks(priv, &pdev->dev);
+ if (ret)
+ goto destroy_pm_clk;
+
+ return 0;
+
+destroy_pm_clk:
+ pm_clk_destroy(&pdev->dev);
+disable_pm_runtime:
+ pm_runtime_disable(&pdev->dev);
+
+ return ret;
+}
+
+static int mmp2_audio_clk_remove(struct platform_device *pdev)
+{
+ pm_clk_destroy(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+static int __maybe_unused mmp2_audio_clk_suspend(struct device *dev)
+{
+ struct mmp2_audio_clk *priv = dev_get_drvdata(dev);
+
+ priv->aud_ctrl = readl(priv->mmio_base + SSPA_AUD_CTRL);
+ priv->aud_pll_ctrl0 = readl(priv->mmio_base + SSPA_AUD_PLL_CTRL0);
+ priv->aud_pll_ctrl1 = readl(priv->mmio_base + SSPA_AUD_PLL_CTRL1);
+ pm_clk_suspend(dev);
+
+ return 0;
+}
+
+static int __maybe_unused mmp2_audio_clk_resume(struct device *dev)
+{
+ struct mmp2_audio_clk *priv = dev_get_drvdata(dev);
+
+ pm_clk_resume(dev);
+ writel(priv->aud_ctrl, priv->mmio_base + SSPA_AUD_CTRL);
+ writel(priv->aud_pll_ctrl0, priv->mmio_base + SSPA_AUD_PLL_CTRL0);
+ writel(priv->aud_pll_ctrl1, priv->mmio_base + SSPA_AUD_PLL_CTRL1);
+
+ return 0;
+}
+
+static const struct dev_pm_ops mmp2_audio_clk_pm_ops = {
+ SET_RUNTIME_PM_OPS(mmp2_audio_clk_suspend, mmp2_audio_clk_resume, NULL)
+};
+
+static const struct of_device_id mmp2_audio_clk_of_match[] = {
+ { .compatible = "marvell,mmp2-audio-clock" },
+ {}
+};
+
+MODULE_DEVICE_TABLE(of, mmp2_audio_clk_of_match);
+
+static struct platform_driver mmp2_audio_clk_driver = {
+ .driver = {
+ .name = "mmp2-audio-clock",
+ .of_match_table = of_match_ptr(mmp2_audio_clk_of_match),
+ .pm = &mmp2_audio_clk_pm_ops,
+ },
+ .probe = mmp2_audio_clk_probe,
+ .remove = mmp2_audio_clk_remove,
+};
+module_platform_driver(mmp2_audio_clk_driver);
+
+MODULE_AUTHOR("Lubomir Rintel <lkundrak@v3.sk>");
+MODULE_DESCRIPTION("Clock driver for MMP2 Audio subsystem");
+MODULE_LICENSE("GPL");
diff --git a/drivers/clk/mmp/clk-frac.c b/drivers/clk/mmp/clk-frac.c
index fabc09aca6c4..48f592bd633d 100644
--- a/drivers/clk/mmp/clk-frac.c
+++ b/drivers/clk/mmp/clk-frac.c
@@ -28,13 +28,15 @@ static long clk_factor_round_rate(struct clk_hw *hw, unsigned long drate,
unsigned long *prate)
{
struct mmp_clk_factor *factor = to_clk_factor(hw);
- unsigned long rate = 0, prev_rate;
+ u64 rate = 0, prev_rate;
int i;
for (i = 0; i < factor->ftbl_cnt; i++) {
prev_rate = rate;
- rate = (((*prate / 10000) * factor->ftbl[i].den) /
- (factor->ftbl[i].num * factor->masks->factor)) * 10000;
+ rate = *prate;
+ rate *= factor->ftbl[i].den;
+ do_div(rate, factor->ftbl[i].num * factor->masks->factor);
+
if (rate > drate)
break;
}
@@ -54,6 +56,7 @@ static unsigned long clk_factor_recalc_rate(struct clk_hw *hw,
struct mmp_clk_factor *factor = to_clk_factor(hw);
struct mmp_clk_factor_masks *masks = factor->masks;
unsigned int val, num, den;
+ u64 rate;
val = readl_relaxed(factor->base);
@@ -66,8 +69,11 @@ static unsigned long clk_factor_recalc_rate(struct clk_hw *hw,
if (!den)
return 0;
- return (((parent_rate / 10000) * den) /
- (num * factor->masks->factor)) * 10000;
+ rate = parent_rate;
+ rate *= den;
+ do_div(rate, num * factor->masks->factor);
+
+ return rate;
}
/* Configures new clock rate*/
@@ -78,12 +84,14 @@ static int clk_factor_set_rate(struct clk_hw *hw, unsigned long drate,
struct mmp_clk_factor_masks *masks = factor->masks;
int i;
unsigned long val;
- unsigned long rate = 0;
unsigned long flags = 0;
+ u64 rate = 0;
for (i = 0; i < factor->ftbl_cnt; i++) {
- rate = (((prate / 10000) * factor->ftbl[i].den) /
- (factor->ftbl[i].num * factor->masks->factor)) * 10000;
+ rate = prate;
+ rate *= factor->ftbl[i].den;
+ do_div(rate, factor->ftbl[i].num * factor->masks->factor);
+
if (rate > drate)
break;
}
@@ -140,7 +148,10 @@ static int clk_factor_init(struct clk_hw *hw)
val &= ~(masks->den_mask << masks->den_shift);
val |= (factor->ftbl[0].den & masks->den_mask) <<
masks->den_shift;
+ }
+ if (!(val & masks->enable_mask) || i >= factor->ftbl_cnt) {
+ val |= masks->enable_mask;
writel(val, factor->base);
}
diff --git a/drivers/clk/mmp/clk-of-mmp2.c b/drivers/clk/mmp/clk-of-mmp2.c
index 52dc8b43acd9..67208aea94c5 100644
--- a/drivers/clk/mmp/clk-of-mmp2.c
+++ b/drivers/clk/mmp/clk-of-mmp2.c
@@ -17,8 +17,10 @@
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/of_address.h>
+#include <linux/clk.h>
#include <dt-bindings/clock/marvell,mmp2.h>
+#include <dt-bindings/power/marvell,mmp2.h>
#include "clk.h"
#include "reset.h"
@@ -45,6 +47,10 @@
#define APBC_SSP1 0x54
#define APBC_SSP2 0x58
#define APBC_SSP3 0x5c
+#define APBC_THERMAL0 0x90
+#define APBC_THERMAL1 0x98
+#define APBC_THERMAL2 0x9c
+#define APBC_THERMAL3 0xa0
#define APMU_SDH0 0x54
#define APMU_SDH1 0x58
#define APMU_SDH2 0xe8
@@ -55,18 +61,19 @@
#define APMU_DISP1 0x110
#define APMU_CCIC0 0x50
#define APMU_CCIC1 0xf4
-#define APBC_THERMAL0 0x90
-#define APBC_THERMAL1 0x98
-#define APBC_THERMAL2 0x9c
-#define APBC_THERMAL3 0xa0
#define APMU_USBHSIC0 0xf8
#define APMU_USBHSIC1 0xfc
#define APMU_GPU 0xcc
+#define APMU_AUDIO 0x10c
+#define APMU_CAMERA 0x1fc
#define MPMU_FCCR 0x8
#define MPMU_POSR 0x10
#define MPMU_UART_PLL 0x14
#define MPMU_PLL2_CR 0x34
+#define MPMU_I2S0_PLL 0x40
+#define MPMU_I2S1_PLL 0x44
+#define MPMU_ACGR 0x1024
/* MMP3 specific below */
#define MPMU_PLL3_CR 0x50
#define MPMU_PLL3_CTRL1 0x58
@@ -82,6 +89,8 @@ enum mmp2_clk_model {
struct mmp2_clk_unit {
struct mmp_clk_unit unit;
enum mmp2_clk_model model;
+ struct genpd_onecell_data pd_data;
+ struct generic_pm_domain *pm_domains[MMP2_NR_POWER_DOMAINS];
void __iomem *mpmu_base;
void __iomem *apmu_base;
void __iomem *apbc_base;
@@ -91,6 +100,7 @@ static struct mmp_param_fixed_rate_clk fixed_rate_clks[] = {
{MMP2_CLK_CLK32, "clk32", NULL, 0, 32768},
{MMP2_CLK_VCTCXO, "vctcxo", NULL, 0, 26000000},
{MMP2_CLK_USB_PLL, "usb_pll", NULL, 0, 480000000},
+ {0, "i2s_pll", NULL, 0, 99666667},
};
static struct mmp_param_pll_clk pll_clks[] = {
@@ -139,7 +149,35 @@ static struct mmp_clk_factor_tbl uart_factor_tbl[] = {
{.num = 3521, .den = 689}, /*19.23MHZ */
};
-static void mmp2_pll_init(struct mmp2_clk_unit *pxa_unit)
+static struct mmp_clk_factor_masks i2s_factor_masks = {
+ .factor = 2,
+ .num_mask = 0x7fff,
+ .den_mask = 0x1fff,
+ .num_shift = 0,
+ .den_shift = 15,
+ .enable_mask = 0xd0000000,
+};
+
+static struct mmp_clk_factor_tbl i2s_factor_tbl[] = {
+ {.num = 24868, .den = 511}, /* 2.0480 MHz */
+ {.num = 28003, .den = 793}, /* 2.8224 MHz */
+ {.num = 24941, .den = 1025}, /* 4.0960 MHz */
+ {.num = 28003, .den = 1586}, /* 5.6448 MHz */
+ {.num = 31158, .den = 2561}, /* 8.1920 MHz */
+ {.num = 16288, .den = 1845}, /* 11.2896 MHz */
+ {.num = 20772, .den = 2561}, /* 12.2880 MHz */
+ {.num = 8144, .den = 1845}, /* 22.5792 MHz */
+ {.num = 10386, .den = 2561}, /* 24.5760 MHz */
+};
+
+static DEFINE_SPINLOCK(acgr_lock);
+
+static struct mmp_param_gate_clk mpmu_gate_clks[] = {
+ {MMP2_CLK_I2S0, "i2s0_clk", "i2s0_pll", CLK_SET_RATE_PARENT, MPMU_ACGR, 0x200000, 0x200000, 0x0, 0, &acgr_lock},
+ {MMP2_CLK_I2S1, "i2s1_clk", "i2s1_pll", CLK_SET_RATE_PARENT, MPMU_ACGR, 0x100000, 0x100000, 0x0, 0, &acgr_lock},
+};
+
+static void mmp2_main_clk_init(struct mmp2_clk_unit *pxa_unit)
{
struct clk *clk;
struct mmp_clk_unit *unit = &pxa_unit->unit;
@@ -166,6 +204,20 @@ static void mmp2_pll_init(struct mmp2_clk_unit *pxa_unit)
&uart_factor_masks, uart_factor_tbl,
ARRAY_SIZE(uart_factor_tbl), NULL);
mmp_clk_add(unit, MMP2_CLK_UART_PLL, clk);
+
+ mmp_clk_register_factor("i2s0_pll", "pll1_4",
+ CLK_SET_RATE_PARENT,
+ pxa_unit->mpmu_base + MPMU_I2S0_PLL,
+ &i2s_factor_masks, i2s_factor_tbl,
+ ARRAY_SIZE(i2s_factor_tbl), NULL);
+ mmp_clk_register_factor("i2s1_pll", "pll1_4",
+ CLK_SET_RATE_PARENT,
+ pxa_unit->mpmu_base + MPMU_I2S1_PLL,
+ &i2s_factor_masks, i2s_factor_tbl,
+ ARRAY_SIZE(i2s_factor_tbl), NULL);
+
+ mmp_register_gate_clks(unit, mpmu_gate_clks, pxa_unit->mpmu_base,
+ ARRAY_SIZE(mpmu_gate_clks));
}
static DEFINE_SPINLOCK(uart0_lock);
@@ -271,6 +323,8 @@ static u32 mmp2_gpu_bus_parent_table[] = { 0x0000, 0x0020, 0x0030,
static const char * const mmp3_gpu_bus_parent_names[] = {"pll1_4", "pll1_6", "pll1_2", "pll2_2"};
static const char * const mmp3_gpu_gc_parent_names[] = {"pll1", "pll2", "pll1_p", "pll2_p"};
+static DEFINE_SPINLOCK(audio_lock);
+
static struct mmp_clk_mix_config ccic0_mix_config = {
.reg_info = DEFINE_MIX_REG_INFO(4, 17, 2, 6, 32),
};
@@ -326,6 +380,7 @@ static struct mmp_param_gate_clk apmu_gate_clks[] = {
{MMP2_CLK_CCIC1_PHY, "ccic1_phy_clk", "ccic1_mix_clk", CLK_SET_RATE_PARENT, APMU_CCIC1, 0x24, 0x24, 0x0, 0, &ccic1_lock},
{MMP2_CLK_CCIC1_SPHY, "ccic1_sphy_clk", "ccic1_sphy_div", CLK_SET_RATE_PARENT, APMU_CCIC1, 0x300, 0x300, 0x0, 0, &ccic1_lock},
{MMP2_CLK_GPU_BUS, "gpu_bus_clk", "gpu_bus_mux", CLK_SET_RATE_PARENT, APMU_GPU, 0xa, 0xa, 0x0, MMP_CLK_GATE_NEED_DELAY, &gpu_lock},
+ {MMP2_CLK_AUDIO, "audio_clk", "audio_mix_clk", CLK_SET_RATE_PARENT, APMU_AUDIO, 0x12, 0x12, 0x0, 0, &audio_lock},
};
static struct mmp_param_gate_clk mmp2_apmu_gate_clks[] = {
@@ -423,6 +478,41 @@ static void mmp2_clk_reset_init(struct device_node *np,
mmp_clk_reset_register(np, cells, nr_resets);
}
+static void mmp2_pm_domain_init(struct device_node *np,
+ struct mmp2_clk_unit *pxa_unit)
+{
+ if (pxa_unit->model == CLK_MODEL_MMP3) {
+ pxa_unit->pm_domains[MMP2_POWER_DOMAIN_GPU]
+ = mmp_pm_domain_register("gpu",
+ pxa_unit->apmu_base + APMU_GPU,
+ 0x0600, 0x40003, 0x18000c, 0, &gpu_lock);
+ } else {
+ pxa_unit->pm_domains[MMP2_POWER_DOMAIN_GPU]
+ = mmp_pm_domain_register("gpu",
+ pxa_unit->apmu_base + APMU_GPU,
+ 0x8600, 0x00003, 0x00000c,
+ MMP_PM_DOMAIN_NO_DISABLE, &gpu_lock);
+ }
+ pxa_unit->pd_data.num_domains++;
+
+ pxa_unit->pm_domains[MMP2_POWER_DOMAIN_AUDIO]
+ = mmp_pm_domain_register("audio",
+ pxa_unit->apmu_base + APMU_AUDIO,
+ 0x600, 0x2, 0, 0, &audio_lock);
+ pxa_unit->pd_data.num_domains++;
+
+ if (pxa_unit->model == CLK_MODEL_MMP3) {
+ pxa_unit->pm_domains[MMP3_POWER_DOMAIN_CAMERA]
+ = mmp_pm_domain_register("camera",
+ pxa_unit->apmu_base + APMU_CAMERA,
+ 0x600, 0, 0, 0, NULL);
+ pxa_unit->pd_data.num_domains++;
+ }
+
+ pxa_unit->pd_data.domains = pxa_unit->pm_domains;
+ of_genpd_add_provider_onecell(np, &pxa_unit->pd_data);
+}
+
static void __init mmp2_clk_init(struct device_node *np)
{
struct mmp2_clk_unit *pxa_unit;
@@ -454,9 +544,11 @@ static void __init mmp2_clk_init(struct device_node *np)
goto unmap_apmu_region;
}
+ mmp2_pm_domain_init(np, pxa_unit);
+
mmp_clk_init(np, &pxa_unit->unit, MMP2_NR_CLKS);
- mmp2_pll_init(pxa_unit);
+ mmp2_main_clk_init(pxa_unit);
mmp2_apb_periph_clk_init(pxa_unit);
diff --git a/drivers/clk/mmp/clk.h b/drivers/clk/mmp/clk.h
index 20dc1e5dd756..55ac05379781 100644
--- a/drivers/clk/mmp/clk.h
+++ b/drivers/clk/mmp/clk.h
@@ -3,6 +3,7 @@
#define __MACH_MMP_CLK_H
#include <linux/clk-provider.h>
+#include <linux/pm_domain.h>
#include <linux/clkdev.h>
#define APBC_NO_BUS_CTRL BIT(0)
@@ -16,6 +17,7 @@ struct mmp_clk_factor_masks {
unsigned int den_mask;
unsigned int num_shift;
unsigned int den_shift;
+ unsigned int enable_mask;
};
struct mmp_clk_factor_tbl {
@@ -251,4 +253,13 @@ void mmp_clk_init(struct device_node *np, struct mmp_clk_unit *unit,
int nr_clks);
void mmp_clk_add(struct mmp_clk_unit *unit, unsigned int id,
struct clk *clk);
+
+/* Power islands */
+#define MMP_PM_DOMAIN_NO_DISABLE BIT(0)
+
+struct generic_pm_domain *mmp_pm_domain_register(const char *name,
+ void __iomem *reg,
+ u32 power_on, u32 reset, u32 clock_enable,
+ unsigned int flags, spinlock_t *lock);
+
#endif
diff --git a/drivers/clk/mmp/pwr-island.c b/drivers/clk/mmp/pwr-island.c
new file mode 100644
index 000000000000..ab57c0e995c1
--- /dev/null
+++ b/drivers/clk/mmp/pwr-island.c
@@ -0,0 +1,115 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * MMP PMU power island support
+ *
+ * Copyright (C) 2020 Lubomir Rintel <lkundrak@v3.sk>
+ */
+
+#include <linux/pm_domain.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+
+#include "clk.h"
+
+#define to_mmp_pm_domain(genpd) container_of(genpd, struct mmp_pm_domain, genpd)
+
+struct mmp_pm_domain {
+ struct generic_pm_domain genpd;
+ void __iomem *reg;
+ spinlock_t *lock;
+ u32 power_on;
+ u32 reset;
+ u32 clock_enable;
+ unsigned int flags;
+};
+
+static int mmp_pm_domain_power_on(struct generic_pm_domain *genpd)
+{
+ struct mmp_pm_domain *pm_domain = to_mmp_pm_domain(genpd);
+ unsigned long flags = 0;
+ u32 val;
+
+ if (pm_domain->lock)
+ spin_lock_irqsave(pm_domain->lock, flags);
+
+ val = readl(pm_domain->reg);
+
+ /* Turn on the power island */
+ val |= pm_domain->power_on;
+ writel(val, pm_domain->reg);
+
+ /* Disable isolation */
+ val |= 0x100;
+ writel(val, pm_domain->reg);
+
+ /* Some blocks need to be reset after a power up */
+ if (pm_domain->reset || pm_domain->clock_enable) {
+ u32 after_power_on = val;
+
+ val &= ~pm_domain->reset;
+ writel(val, pm_domain->reg);
+
+ val |= pm_domain->clock_enable;
+ writel(val, pm_domain->reg);
+
+ val |= pm_domain->reset;
+ writel(val, pm_domain->reg);
+
+ writel(after_power_on, pm_domain->reg);
+ }
+
+ if (pm_domain->lock)
+ spin_unlock_irqrestore(pm_domain->lock, flags);
+
+ return 0;
+}
+
+static int mmp_pm_domain_power_off(struct generic_pm_domain *genpd)
+{
+ struct mmp_pm_domain *pm_domain = to_mmp_pm_domain(genpd);
+ unsigned long flags = 0;
+ u32 val;
+
+ if (pm_domain->flags & MMP_PM_DOMAIN_NO_DISABLE)
+ return 0;
+
+ if (pm_domain->lock)
+ spin_lock_irqsave(pm_domain->lock, flags);
+
+ /* Turn off and isolate the the power island. */
+ val = readl(pm_domain->reg);
+ val &= ~pm_domain->power_on;
+ val &= ~0x100;
+ writel(val, pm_domain->reg);
+
+ if (pm_domain->lock)
+ spin_unlock_irqrestore(pm_domain->lock, flags);
+
+ return 0;
+}
+
+struct generic_pm_domain *mmp_pm_domain_register(const char *name,
+ void __iomem *reg,
+ u32 power_on, u32 reset, u32 clock_enable,
+ unsigned int flags, spinlock_t *lock)
+{
+ struct mmp_pm_domain *pm_domain;
+
+ pm_domain = kzalloc(sizeof(*pm_domain), GFP_KERNEL);
+ if (!pm_domain)
+ return ERR_PTR(-ENOMEM);
+
+ pm_domain->reg = reg;
+ pm_domain->power_on = power_on;
+ pm_domain->reset = reset;
+ pm_domain->clock_enable = clock_enable;
+ pm_domain->flags = flags;
+ pm_domain->lock = lock;
+
+ pm_genpd_init(&pm_domain->genpd, NULL, true);
+ pm_domain->genpd.name = name;
+ pm_domain->genpd.power_on = mmp_pm_domain_power_on;
+ pm_domain->genpd.power_off = mmp_pm_domain_power_off;
+
+ return &pm_domain->genpd;
+}