diff options
Diffstat (limited to 'drivers/clk/mmp')
-rw-r--r-- | drivers/clk/mmp/Makefile | 3 | ||||
-rw-r--r-- | drivers/clk/mmp/clk-audio.c | 443 | ||||
-rw-r--r-- | drivers/clk/mmp/clk-frac.c | 27 | ||||
-rw-r--r-- | drivers/clk/mmp/clk-of-mmp2.c | 104 | ||||
-rw-r--r-- | drivers/clk/mmp/clk.h | 11 | ||||
-rw-r--r-- | drivers/clk/mmp/pwr-island.c | 115 |
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; +} |