summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--arch/arm/mach-at91/Kconfig8
-rw-r--r--drivers/clk/at91/Makefile2
-rw-r--r--drivers/clk/at91/clk-audio-pll.c512
-rw-r--r--drivers/clk/at91/clk-generated.c5
-rw-r--r--drivers/clk/at91/clk-i2s-mux.c87
-rw-r--r--drivers/clk/at91/pmc.c2
-rw-r--r--drivers/clk/at91/pmc.h17
-rw-r--r--drivers/clk/at91/sama5d2.c49
-rw-r--r--include/linux/clk/at91_pmc.h25
9 files changed, 702 insertions, 5 deletions
diff --git a/arch/arm/mach-at91/Kconfig b/arch/arm/mach-at91/Kconfig
index eb14cd2c28..0226353810 100644
--- a/arch/arm/mach-at91/Kconfig
+++ b/arch/arm/mach-at91/Kconfig
@@ -34,6 +34,12 @@ config HAVE_AT91_GENERATED_CLK
config HAVE_AT91_BOOTSTRAP
bool
+config HAVE_AT91_AUDIO_PLL
+ bool
+
+config HAVE_AT91_I2S_MUX_CLK
+ bool
+
# Select if board uses the common at91sam926x_board_init
config AT91SAM926X_BOARD_INIT
bool
@@ -71,6 +77,8 @@ config SOC_SAMA5D2
select HAVE_AT91_USB_CLK
select HAVE_AT91_GENERATED_CLK
select PINCTRL
+ select HAVE_AT91_AUDIO_PLL
+ select HAVE_AT91_I2S_MUX_CLK
select PINCTRL_AT91PIO4
select HAS_MACB
select HAVE_MACH_ARM_HEAD
diff --git a/drivers/clk/at91/Makefile b/drivers/clk/at91/Makefile
index d07baedf58..605f698439 100644
--- a/drivers/clk/at91/Makefile
+++ b/drivers/clk/at91/Makefile
@@ -7,11 +7,13 @@ obj-y += pmc.o sckc.o
obj-y += clk-slow.o clk-main.o clk-pll.o clk-plldiv.o clk-master.o
obj-y += clk-system.o clk-peripheral.o clk-programmable.o
+obj-$(CONFIG_HAVE_AT91_AUDIO_PLL) += clk-audio-pll.o
obj-$(CONFIG_HAVE_AT91_UTMI) += clk-utmi.o
obj-$(CONFIG_HAVE_AT91_USB_CLK) += clk-usb.o
obj-$(CONFIG_HAVE_AT91_SMD) += clk-smd.o
obj-$(CONFIG_HAVE_AT91_H32MX) += clk-h32mx.o
obj-$(CONFIG_HAVE_AT91_GENERATED_CLK) += clk-generated.o
+obj-$(CONFIG_HAVE_AT91_I2S_MUX_CLK) += clk-i2s-mux.o
obj-$(CONFIG_SOC_AT91SAM9) += at91sam9260.o at91sam9rl.o at91sam9x5.o dt-compat.o
obj-$(CONFIG_SOC_SAMA5D4) += sama5d4.o
obj-$(CONFIG_SOC_SAMA5D3) += dt-compat.o
diff --git a/drivers/clk/at91/clk-audio-pll.c b/drivers/clk/at91/clk-audio-pll.c
new file mode 100644
index 0000000000..47bff32fe8
--- /dev/null
+++ b/drivers/clk/at91/clk-audio-pll.c
@@ -0,0 +1,512 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2016 Atmel Corporation,
+ * Songjun Wu <songjun.wu@atmel.com>,
+ * Nicolas Ferre <nicolas.ferre@atmel.com>
+ * Copyright (C) 2017 Free Electrons,
+ * Quentin Schulz <quentin.schulz@free-electrons.com>
+ *
+ * The Sama5d2 SoC has two audio PLLs (PMC and PAD) that shares the same parent
+ * (FRAC). FRAC can output between 620 and 700MHz and only multiply the rate of
+ * its own parent. PMC and PAD can then divide the FRAC rate to best match the
+ * asked rate.
+ *
+ * Traits of FRAC clock:
+ * enable - clk_enable writes nd, fracr parameters and enables PLL
+ * rate - rate is adjustable.
+ * clk->rate = parent->rate * ((nd + 1) + (fracr / 2^22))
+ * parent - fixed parent. No clk_set_parent support
+ *
+ * Traits of PMC clock:
+ * enable - clk_enable writes qdpmc, and enables PMC output
+ * rate - rate is adjustable.
+ * clk->rate = parent->rate / (qdpmc + 1)
+ * parent - fixed parent. No clk_set_parent support
+ *
+ * Traits of PAD clock:
+ * enable - clk_enable writes divisors and enables PAD output
+ * rate - rate is adjustable.
+ * clk->rate = parent->rate / (qdaudio * div))
+ * parent - fixed parent. No clk_set_parent support
+ */
+
+#include <common.h>
+#include <clock.h>
+#include <of.h>
+#include <linux/list.h>
+#include <linux/clk.h>
+#include <linux/clk/at91_pmc.h>
+#include <mfd/syscon.h>
+#include <regmap.h>
+
+#include "pmc.h"
+
+#define AUDIO_PLL_DIV_FRAC BIT(22)
+#define AUDIO_PLL_ND_MAX (AT91_PMC_AUDIO_PLL_ND_MASK >> \
+ AT91_PMC_AUDIO_PLL_ND_OFFSET)
+
+#define AUDIO_PLL_QDPAD(qd, div) ((AT91_PMC_AUDIO_PLL_QDPAD_EXTDIV(qd) & \
+ AT91_PMC_AUDIO_PLL_QDPAD_EXTDIV_MASK) | \
+ (AT91_PMC_AUDIO_PLL_QDPAD_DIV(div) & \
+ AT91_PMC_AUDIO_PLL_QDPAD_DIV_MASK))
+
+#define AUDIO_PLL_QDPMC_MAX (AT91_PMC_AUDIO_PLL_QDPMC_MASK >> \
+ AT91_PMC_AUDIO_PLL_QDPMC_OFFSET)
+
+#define AUDIO_PLL_FOUT_MIN 620000000UL
+#define AUDIO_PLL_FOUT_MAX 700000000UL
+
+struct clk_audio_frac {
+ struct clk clk;
+ struct regmap *regmap;
+ u32 fracr;
+ u8 nd;
+ const char *parent_name;
+};
+
+struct clk_audio_pad {
+ struct clk clk;
+ struct regmap *regmap;
+ u8 qdaudio;
+ u8 div;
+ const char *parent_name;
+};
+
+struct clk_audio_pmc {
+ struct clk clk;
+ struct regmap *regmap;
+ u8 qdpmc;
+ const char *parent_name;
+};
+
+#define to_clk_audio_frac(clk) container_of(clk, struct clk_audio_frac, clk)
+#define to_clk_audio_pad(clk) container_of(clk, struct clk_audio_pad, clk)
+#define to_clk_audio_pmc(clk) container_of(clk, struct clk_audio_pmc, clk)
+
+static int clk_audio_pll_frac_enable(struct clk *clk)
+{
+ struct clk_audio_frac *frac = to_clk_audio_frac(clk);
+
+ regmap_update_bits(frac->regmap, AT91_PMC_AUDIO_PLL0,
+ AT91_PMC_AUDIO_PLL_RESETN, 0);
+ regmap_update_bits(frac->regmap, AT91_PMC_AUDIO_PLL0,
+ AT91_PMC_AUDIO_PLL_RESETN,
+ AT91_PMC_AUDIO_PLL_RESETN);
+ regmap_update_bits(frac->regmap, AT91_PMC_AUDIO_PLL1,
+ AT91_PMC_AUDIO_PLL_FRACR_MASK, frac->fracr);
+
+ /*
+ * reset and enable have to be done in 2 separated writes
+ * for AT91_PMC_AUDIO_PLL0
+ */
+ regmap_update_bits(frac->regmap, AT91_PMC_AUDIO_PLL0,
+ AT91_PMC_AUDIO_PLL_PLLEN |
+ AT91_PMC_AUDIO_PLL_ND_MASK,
+ AT91_PMC_AUDIO_PLL_PLLEN |
+ AT91_PMC_AUDIO_PLL_ND(frac->nd));
+
+ return 0;
+}
+
+static int clk_audio_pll_pad_enable(struct clk *clk)
+{
+ struct clk_audio_pad *apad_ck = to_clk_audio_pad(clk);
+
+ regmap_update_bits(apad_ck->regmap, AT91_PMC_AUDIO_PLL1,
+ AT91_PMC_AUDIO_PLL_QDPAD_MASK,
+ AUDIO_PLL_QDPAD(apad_ck->qdaudio, apad_ck->div));
+ regmap_update_bits(apad_ck->regmap, AT91_PMC_AUDIO_PLL0,
+ AT91_PMC_AUDIO_PLL_PADEN, AT91_PMC_AUDIO_PLL_PADEN);
+
+ return 0;
+}
+
+static int clk_audio_pll_pmc_enable(struct clk *clk)
+{
+ struct clk_audio_pmc *apmc_ck = to_clk_audio_pmc(clk);
+
+ regmap_update_bits(apmc_ck->regmap, AT91_PMC_AUDIO_PLL0,
+ AT91_PMC_AUDIO_PLL_PMCEN |
+ AT91_PMC_AUDIO_PLL_QDPMC_MASK,
+ AT91_PMC_AUDIO_PLL_PMCEN |
+ AT91_PMC_AUDIO_PLL_QDPMC(apmc_ck->qdpmc));
+ return 0;
+}
+
+static void clk_audio_pll_frac_disable(struct clk *clk)
+{
+ struct clk_audio_frac *frac = to_clk_audio_frac(clk);
+
+ regmap_update_bits(frac->regmap, AT91_PMC_AUDIO_PLL0,
+ AT91_PMC_AUDIO_PLL_PLLEN, 0);
+ /* do it in 2 separated writes */
+ regmap_update_bits(frac->regmap, AT91_PMC_AUDIO_PLL0,
+ AT91_PMC_AUDIO_PLL_RESETN, 0);
+}
+
+static void clk_audio_pll_pad_disable(struct clk *clk)
+{
+ struct clk_audio_pad *apad_ck = to_clk_audio_pad(clk);
+
+ regmap_update_bits(apad_ck->regmap, AT91_PMC_AUDIO_PLL0,
+ AT91_PMC_AUDIO_PLL_PADEN, 0);
+}
+
+static void clk_audio_pll_pmc_disable(struct clk *clk)
+{
+ struct clk_audio_pmc *apmc_ck = to_clk_audio_pmc(clk);
+
+ regmap_update_bits(apmc_ck->regmap, AT91_PMC_AUDIO_PLL0,
+ AT91_PMC_AUDIO_PLL_PMCEN, 0);
+}
+
+static unsigned long clk_audio_pll_fout(unsigned long parent_rate,
+ unsigned long nd, unsigned long fracr)
+{
+ unsigned long long fr = (unsigned long long)parent_rate * fracr;
+
+ pr_debug("A PLL: %s, fr = %llu\n", __func__, fr);
+
+ fr = DIV_ROUND_CLOSEST_ULL(fr, AUDIO_PLL_DIV_FRAC);
+
+ pr_debug("A PLL: %s, fr = %llu\n", __func__, fr);
+
+ return parent_rate * (nd + 1) + fr;
+}
+
+static unsigned long clk_audio_pll_frac_recalc_rate(struct clk *clk,
+ unsigned long parent_rate)
+{
+ struct clk_audio_frac *frac = to_clk_audio_frac(clk);
+ unsigned long fout;
+
+ fout = clk_audio_pll_fout(parent_rate, frac->nd, frac->fracr);
+
+ pr_debug("A PLL: %s, fout = %lu (nd = %u, fracr = %lu)\n", __func__,
+ fout, frac->nd, (unsigned long)frac->fracr);
+
+ return fout;
+}
+
+static unsigned long clk_audio_pll_pad_recalc_rate(struct clk *clk,
+ unsigned long parent_rate)
+{
+ struct clk_audio_pad *apad_ck = to_clk_audio_pad(clk);
+ unsigned long apad_rate = 0;
+
+ if (apad_ck->qdaudio && apad_ck->div)
+ apad_rate = parent_rate / (apad_ck->qdaudio * apad_ck->div);
+
+ pr_debug("A PLL/PAD: %s, apad_rate = %lu (div = %u, qdaudio = %u)\n",
+ __func__, apad_rate, apad_ck->div, apad_ck->qdaudio);
+
+ return apad_rate;
+}
+
+static unsigned long clk_audio_pll_pmc_recalc_rate(struct clk *clk,
+ unsigned long parent_rate)
+{
+ struct clk_audio_pmc *apmc_ck = to_clk_audio_pmc(clk);
+ unsigned long apmc_rate = 0;
+
+ apmc_rate = parent_rate / (apmc_ck->qdpmc + 1);
+
+ pr_debug("A PLL/PMC: %s, apmc_rate = %lu (qdpmc = %u)\n", __func__,
+ apmc_rate, apmc_ck->qdpmc);
+
+ return apmc_rate;
+}
+
+static int clk_audio_pll_frac_compute_frac(unsigned long rate,
+ unsigned long parent_rate,
+ unsigned long *nd,
+ unsigned long *fracr)
+{
+ unsigned long long tmp, rem;
+
+ if (!rate)
+ return -EINVAL;
+
+ tmp = rate;
+ rem = do_div(tmp, parent_rate);
+ if (!tmp || tmp >= AUDIO_PLL_ND_MAX)
+ return -EINVAL;
+
+ *nd = tmp - 1;
+
+ tmp = rem * AUDIO_PLL_DIV_FRAC;
+ tmp = DIV_ROUND_CLOSEST_ULL(tmp, parent_rate);
+ if (tmp > AT91_PMC_AUDIO_PLL_FRACR_MASK)
+ return -EINVAL;
+
+ /* we can cast here as we verified the bounds just above */
+ *fracr = (unsigned long)tmp;
+
+ return 0;
+}
+
+static long clk_audio_pll_pad_round_rate(struct clk *clk, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ struct clk *pclk = clk_get_parent(clk);
+ long best_rate = -EINVAL;
+ unsigned long best_parent_rate;
+ unsigned long tmp_qd;
+ u32 div;
+ long tmp_rate;
+ int tmp_diff;
+ int best_diff = -1;
+
+ pr_debug("A PLL/PAD: %s, rate = %lu (parent_rate = %lu)\n", __func__,
+ rate, *parent_rate);
+
+ /*
+ * Rate divisor is actually made of two different divisors, multiplied
+ * between themselves before dividing the rate.
+ * tmp_qd goes from 1 to 31 and div is either 2 or 3.
+ * In order to avoid testing twice the rate divisor (e.g. divisor 12 can
+ * be found with (tmp_qd, div) = (2, 6) or (3, 4)), we remove any loop
+ * for a rate divisor when div is 2 and tmp_qd is a multiple of 3.
+ * We cannot inverse it (condition div is 3 and tmp_qd is even) or we
+ * would miss some rate divisor that aren't reachable with div being 2
+ * (e.g. rate divisor 90 is made with div = 3 and tmp_qd = 30, thus
+ * tmp_qd is even so we skip it because we think div 2 could make this
+ * rate divisor which isn't possible since tmp_qd has to be <= 31).
+ */
+ for (tmp_qd = 1; tmp_qd < AT91_PMC_AUDIO_PLL_QDPAD_EXTDIV_MAX; tmp_qd++)
+ for (div = 2; div <= 3; div++) {
+ if (div == 2 && tmp_qd % 3 == 0)
+ continue;
+
+ best_parent_rate = clk_round_rate(pclk,
+ rate * tmp_qd * div);
+ tmp_rate = best_parent_rate / (div * tmp_qd);
+ tmp_diff = abs(rate - tmp_rate);
+
+ if (best_diff < 0 || best_diff > tmp_diff) {
+ *parent_rate = best_parent_rate;
+ best_rate = tmp_rate;
+ best_diff = tmp_diff;
+ }
+ }
+
+ pr_debug("A PLL/PAD: %s, best_rate = %ld, best_parent_rate = %lu\n",
+ __func__, best_rate, best_parent_rate);
+
+ return best_rate;
+}
+
+static long clk_audio_pll_pmc_round_rate(struct clk *clk, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ struct clk *pclk = clk_get_parent(clk);
+ long best_rate = -EINVAL;
+ unsigned long best_parent_rate = 0;
+ u32 tmp_qd = 0, div;
+ long tmp_rate;
+ int tmp_diff;
+ int best_diff = -1;
+
+ pr_debug("A PLL/PMC: %s, rate = %lu (parent_rate = %lu)\n", __func__,
+ rate, *parent_rate);
+
+ if (!rate)
+ return 0;
+
+ best_parent_rate = clk_round_rate(pclk, 1);
+ div = max(best_parent_rate / rate, 1UL);
+ for (; div <= AUDIO_PLL_QDPMC_MAX; div++) {
+ best_parent_rate = clk_round_rate(pclk, rate * div);
+ tmp_rate = best_parent_rate / div;
+ tmp_diff = abs(rate - tmp_rate);
+
+ if (best_diff < 0 || best_diff > tmp_diff) {
+ *parent_rate = best_parent_rate;
+ best_rate = tmp_rate;
+ best_diff = tmp_diff;
+ tmp_qd = div;
+ if (!best_diff)
+ break; /* got exact match */
+ }
+ }
+
+ pr_debug("A PLL/PMC: %s, best_rate = %ld, best_parent_rate = %lu (qd = %d)\n",
+ __func__, best_rate, *parent_rate, tmp_qd - 1);
+
+ return best_rate;
+}
+
+static int clk_audio_pll_frac_set_rate(struct clk *clk, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct clk_audio_frac *frac = to_clk_audio_frac(clk);
+ unsigned long fracr, nd;
+ int ret;
+
+ pr_debug("A PLL: %s, rate = %lu (parent_rate = %lu)\n", __func__, rate,
+ parent_rate);
+
+ if (rate < AUDIO_PLL_FOUT_MIN || rate > AUDIO_PLL_FOUT_MAX)
+ return -EINVAL;
+
+ ret = clk_audio_pll_frac_compute_frac(rate, parent_rate, &nd, &fracr);
+ if (ret)
+ return ret;
+
+ frac->nd = nd;
+ frac->fracr = fracr;
+
+ return 0;
+}
+
+static int clk_audio_pll_pad_set_rate(struct clk *clk, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct clk_audio_pad *apad_ck = to_clk_audio_pad(clk);
+ u8 tmp_div;
+
+ pr_debug("A PLL/PAD: %s, rate = %lu (parent_rate = %lu)\n", __func__,
+ rate, parent_rate);
+
+ if (!rate)
+ return -EINVAL;
+
+ tmp_div = parent_rate / rate;
+ if (tmp_div % 3 == 0) {
+ apad_ck->qdaudio = tmp_div / 3;
+ apad_ck->div = 3;
+ } else {
+ apad_ck->qdaudio = tmp_div / 2;
+ apad_ck->div = 2;
+ }
+
+ return 0;
+}
+
+static int clk_audio_pll_pmc_set_rate(struct clk *clk, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct clk_audio_pmc *apmc_ck = to_clk_audio_pmc(clk);
+
+ if (!rate)
+ return -EINVAL;
+
+ pr_debug("A PLL/PMC: %s, rate = %lu (parent_rate = %lu)\n", __func__,
+ rate, parent_rate);
+
+ apmc_ck->qdpmc = parent_rate / rate - 1;
+
+ return 0;
+}
+
+static const struct clk_ops audio_pll_frac_ops = {
+ .enable = clk_audio_pll_frac_enable,
+ .disable = clk_audio_pll_frac_disable,
+ .recalc_rate = clk_audio_pll_frac_recalc_rate,
+ .set_rate = clk_audio_pll_frac_set_rate,
+};
+
+static const struct clk_ops audio_pll_pad_ops = {
+ .enable = clk_audio_pll_pad_enable,
+ .disable = clk_audio_pll_pad_disable,
+ .recalc_rate = clk_audio_pll_pad_recalc_rate,
+ .round_rate = clk_audio_pll_pad_round_rate,
+ .set_rate = clk_audio_pll_pad_set_rate,
+};
+
+static const struct clk_ops audio_pll_pmc_ops = {
+ .enable = clk_audio_pll_pmc_enable,
+ .disable = clk_audio_pll_pmc_disable,
+ .recalc_rate = clk_audio_pll_pmc_recalc_rate,
+ .round_rate = clk_audio_pll_pmc_round_rate,
+ .set_rate = clk_audio_pll_pmc_set_rate,
+};
+
+struct clk * __init
+at91_clk_register_audio_pll_frac(struct regmap *regmap, const char *name,
+ const char *parent_name)
+{
+ struct clk_audio_frac *frac_ck;
+ int ret;
+
+ frac_ck = kzalloc(sizeof(*frac_ck), GFP_KERNEL);
+ if (!frac_ck)
+ return ERR_PTR(-ENOMEM);
+
+ frac_ck->clk.name = name;
+ frac_ck->clk.ops = &audio_pll_frac_ops;
+ frac_ck->parent_name = parent_name;
+ frac_ck->clk.parent_names = &frac_ck->parent_name;
+ frac_ck->clk.num_parents = 1;
+ /* frac_ck->clk.flags = CLK_SET_RATE_GATE; */
+
+ frac_ck->regmap = regmap;
+
+ ret = clk_register(&frac_ck->clk);
+ if (ret) {
+ kfree(frac_ck);
+ return ERR_PTR(ret);
+ }
+
+ return &frac_ck->clk;
+}
+
+struct clk * __init
+at91_clk_register_audio_pll_pad(struct regmap *regmap, const char *name,
+ const char *parent_name)
+{
+ struct clk_audio_pad *apad_ck;
+ int ret;
+
+ apad_ck = kzalloc(sizeof(*apad_ck), GFP_KERNEL);
+ if (!apad_ck)
+ return ERR_PTR(-ENOMEM);
+
+ apad_ck->clk.name = name;
+ apad_ck->clk.ops = &audio_pll_pad_ops;
+ apad_ck->parent_name = parent_name;
+ apad_ck->clk.parent_names = &apad_ck->parent_name;
+ apad_ck->clk.num_parents = 1;
+ /* apad_ck->clk.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE |
+ CLK_SET_RATE_PARENT; */
+
+ apad_ck->regmap = regmap;
+
+ ret = clk_register(&apad_ck->clk);
+ if (ret) {
+ kfree(apad_ck);
+ return ERR_PTR(ret);
+ }
+
+ return &apad_ck->clk;
+}
+
+struct clk * __init
+at91_clk_register_audio_pll_pmc(struct regmap *regmap, const char *name,
+ const char *parent_name)
+{
+ struct clk_audio_pmc *apmc_ck;
+ int ret;
+
+ apmc_ck = kzalloc(sizeof(*apmc_ck), GFP_KERNEL);
+ if (!apmc_ck)
+ return ERR_PTR(-ENOMEM);
+
+ apmc_ck->clk.name = name;
+ apmc_ck->clk.ops = &audio_pll_pmc_ops;
+ apmc_ck->parent_name = parent_name;
+ apmc_ck->clk.parent_names = &apmc_ck->parent_name;
+ apmc_ck->clk.num_parents = 1;
+ /* apmc_ck.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE |
+ CLK_SET_RATE_PARENT; */
+
+ apmc_ck->regmap = regmap;
+
+ ret = clk_register(&apmc_ck->clk);
+ if (ret) {
+ kfree(apmc_ck);
+ return ERR_PTR(ret);
+ }
+
+ return &apmc_ck->clk;
+}
diff --git a/drivers/clk/at91/clk-generated.c b/drivers/clk/at91/clk-generated.c
index 909c4a4530..79b83b45ab 100644
--- a/drivers/clk/at91/clk-generated.c
+++ b/drivers/clk/at91/clk-generated.c
@@ -19,6 +19,8 @@
#define GENERATED_MAX_DIV 255
+#define GCK_INDEX_DT_AUDIO_PLL 5
+
struct clk_generated {
struct clk hw;
struct regmap *regmap;
@@ -26,6 +28,7 @@ struct clk_generated {
u32 id;
u32 gckdiv;
u8 parent_id;
+ bool audio_pll_allowed;
};
#define to_clk_generated(hw) \
@@ -182,7 +185,7 @@ at91_clk_register_generated(struct regmap *regmap,
/* gck->hw.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE; */
gck->regmap = regmap;
gck->range = *range;
- /* gck->audio_pll_allowed = pll_audio; */
+ gck->audio_pll_allowed = pll_audio;
hw = &gck->hw;
ret = clk_register(&gck->hw);
diff --git a/drivers/clk/at91/clk-i2s-mux.c b/drivers/clk/at91/clk-i2s-mux.c
new file mode 100644
index 0000000000..1418ec8662
--- /dev/null
+++ b/drivers/clk/at91/clk-i2s-mux.c
@@ -0,0 +1,87 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Microchip Technology Inc,
+ * Codrin Ciubotariu <codrin.ciubotariu@microchip.com>
+ *
+ *
+ */
+
+#include <common.h>
+#include <clock.h>
+#include <of.h>
+#include <linux/list.h>
+#include <linux/clk.h>
+#include <linux/clk/at91_pmc.h>
+#include <mfd/syscon.h>
+#include <regmap.h>
+
+#include <soc/at91/atmel-sfr.h>
+
+#include "pmc.h"
+
+#define I2S_MUX_SOURCE_MAX 2
+
+struct clk_i2s_mux {
+ struct clk clk;
+ struct regmap *regmap;
+ u8 bus_id;
+ const char *parent_names[I2S_MUX_SOURCE_MAX];
+};
+
+#define to_clk_i2s_mux(clk) container_of(clk, struct clk_i2s_mux, clk)
+
+static int clk_i2s_mux_get_parent(struct clk *clk)
+{
+ struct clk_i2s_mux *mux = to_clk_i2s_mux(clk);
+ u32 val;
+
+ regmap_read(mux->regmap, AT91_SFR_I2SCLKSEL, &val);
+
+ return (val & BIT(mux->bus_id)) >> mux->bus_id;
+}
+
+static int clk_i2s_mux_set_parent(struct clk *clk, u8 index)
+{
+ struct clk_i2s_mux *mux = to_clk_i2s_mux(clk);
+
+ return regmap_update_bits(mux->regmap, AT91_SFR_I2SCLKSEL,
+ BIT(mux->bus_id), index << mux->bus_id);
+}
+
+static const struct clk_ops clk_i2s_mux_ops = {
+ .set_rate = clk_parent_set_rate,
+ .round_rate = clk_parent_round_rate,
+ .get_parent = clk_i2s_mux_get_parent,
+ .set_parent = clk_i2s_mux_set_parent,
+};
+
+struct clk * __init
+at91_clk_i2s_mux_register(struct regmap *regmap, const char *name,
+ const char * const *parent_names,
+ unsigned int num_parents, u8 bus_id)
+{
+ struct clk_i2s_mux *i2s_ck;
+ int ret;
+
+ i2s_ck = kzalloc(sizeof(*i2s_ck), GFP_KERNEL);
+ if (!i2s_ck)
+ return ERR_PTR(-ENOMEM);
+
+ i2s_ck->clk.name = name;
+ i2s_ck->clk.ops = &clk_i2s_mux_ops;
+ memcpy(i2s_ck->parent_names, parent_names,
+ num_parents * sizeof(i2s_ck->parent_names[0]));
+ i2s_ck->clk.parent_names = &i2s_ck->parent_names[0];
+ i2s_ck->clk.num_parents = num_parents;
+
+ i2s_ck->bus_id = bus_id;
+ i2s_ck->regmap = regmap;
+
+ ret = clk_register(&i2s_ck->clk);
+ if (ret) {
+ kfree(i2s_ck);
+ return ERR_PTR(ret);
+ }
+
+ return &i2s_ck->clk;
+}
diff --git a/drivers/clk/at91/pmc.c b/drivers/clk/at91/pmc.c
index bea3c89e2c..3f7aabbb55 100644
--- a/drivers/clk/at91/pmc.c
+++ b/drivers/clk/at91/pmc.c
@@ -135,6 +135,8 @@ static struct
u32 imr;
u32 pcsr1;
u32 pcr[PMC_MAX_IDS];
+ u32 audio_pll0;
+ u32 audio_pll1;
u32 pckr[PMC_MAX_PCKS];
} pmc_cache;
diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h
index 705486a1f4..0efd70b7fc 100644
--- a/drivers/clk/at91/pmc.h
+++ b/drivers/clk/at91/pmc.h
@@ -85,6 +85,18 @@ int of_at91_get_clk_range(struct device_node *np, const char *propname,
struct clk *of_clk_hw_pmc_get(struct of_phandle_args *clkspec, void *data);
struct clk * __init
+at91_clk_register_audio_pll_frac(struct regmap *regmap, const char *name,
+ const char *parent_name);
+
+struct clk * __init
+at91_clk_register_audio_pll_pad(struct regmap *regmap, const char *name,
+ const char *parent_name);
+
+struct clk * __init
+at91_clk_register_audio_pll_pmc(struct regmap *regmap, const char *name,
+ const char *parent_name);
+
+struct clk * __init
at91_clk_register_generated(struct regmap *regmap,
const char *name, const char **parent_names,
u8 num_parents, u8 id, bool pll_audio,
@@ -95,6 +107,11 @@ at91_clk_register_h32mx(struct regmap *regmap, const char *name,
const char *parent_name);
struct clk * __init
+at91_clk_i2s_mux_register(struct regmap *regmap, const char *name,
+ const char * const *parent_names,
+ unsigned int num_parents, u8 bus_id);
+
+struct clk * __init
at91_clk_register_main_rc_osc(struct regmap *regmap, const char *name,
u32 frequency, u32 accuracy);
struct clk * __init
diff --git a/drivers/clk/at91/sama5d2.c b/drivers/clk/at91/sama5d2.c
index 61fdf256c0..f17a88cd88 100644
--- a/drivers/clk/at91/sama5d2.c
+++ b/drivers/clk/at91/sama5d2.c
@@ -84,6 +84,8 @@ static const struct {
{ .n = "trng_clk", .id = 47, .r = { .min = 0, .max = 83000000 }, },
{ .n = "pdmic_clk", .id = 48, .r = { .min = 0, .max = 83000000 }, },
{ .n = "securam_clk", .id = 51, },
+ { .n = "i2s0_clk", .id = 54, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "i2s1_clk", .id = 55, .r = { .min = 0, .max = 83000000 }, },
{ .n = "can0_clk", .id = 56, .r = { .min = 0, .max = 83000000 }, },
{ .n = "can1_clk", .id = 57, .r = { .min = 0, .max = 83000000 }, },
{ .n = "classd_clk", .id = 59, .r = { .min = 0, .max = 83000000 }, },
@@ -121,6 +123,8 @@ static const struct {
{ .n = "pwm_gclk", .id = 38, .r = { .min = 0, .max = 83000000 }, },
{ .n = "isc_gclk", .id = 46, },
{ .n = "pdmic_gclk", .id = 48, },
+ { .n = "i2s0_gclk", .id = 54, .pll = true },
+ { .n = "i2s1_gclk", .id = 55, .pll = true },
{ .n = "can0_gclk", .id = 56, .r = { .min = 0, .max = 80000000 }, },
{ .n = "can1_gclk", .id = 57, .r = { .min = 0, .max = 80000000 }, },
{ .n = "classd_gclk", .id = 59, .r = { .min = 0, .max = 100000000 },
@@ -132,7 +136,7 @@ static void __init sama5d2_pmc_setup(struct device_node *np)
struct clk_range range = CLK_RANGE(0, 0);
const char *slck_name, *mainxtal_name;
struct pmc_data *sama5d2_pmc;
- const char *parent_names[5];
+ const char *parent_names[6];
struct regmap *regmap, *regmap_sfr;
struct clk *hw;
int i;
@@ -153,7 +157,7 @@ static void __init sama5d2_pmc_setup(struct device_node *np)
if (IS_ERR(regmap))
return;
- sama5d2_pmc = pmc_data_allocate(PMC_MCK2 + 1,
+ sama5d2_pmc = pmc_data_allocate(PMC_I2S1_MUX + 1,
nck(sama5d2_systemck),
nck(sama5d2_periph32ck),
nck(sama5d2_gck));
@@ -189,6 +193,21 @@ static void __init sama5d2_pmc_setup(struct device_node *np)
if (IS_ERR(hw))
goto err_free;
+ hw = at91_clk_register_audio_pll_frac(regmap, "audiopll_fracck",
+ "mainck");
+ if (IS_ERR(hw))
+ goto err_free;
+
+ hw = at91_clk_register_audio_pll_pad(regmap, "audiopll_padck",
+ "audiopll_fracck");
+ if (IS_ERR(hw))
+ goto err_free;
+
+ hw = at91_clk_register_audio_pll_pmc(regmap, "audiopll_pmcck",
+ "audiopll_fracck");
+ if (IS_ERR(hw))
+ goto err_free;
+
regmap_sfr = syscon_regmap_lookup_by_compatible("atmel,sama5d2-sfr");
if (IS_ERR(regmap_sfr))
regmap_sfr = NULL;
@@ -228,13 +247,14 @@ static void __init sama5d2_pmc_setup(struct device_node *np)
parent_names[2] = "plladivck";
parent_names[3] = "utmick";
parent_names[4] = "masterck";
+ parent_names[5] = "audiopll_pmcck";
for (i = 0; i < 3; i++) {
char *name;
name = xasprintf("prog%d", i);
hw = at91_clk_register_programmable(regmap, name,
- parent_names, 5, i,
+ parent_names, 6, i,
&at91sam9x5_programmable_layout);
if (IS_ERR(hw))
goto err_free;
@@ -279,10 +299,11 @@ static void __init sama5d2_pmc_setup(struct device_node *np)
parent_names[2] = "plladivck";
parent_names[3] = "utmick";
parent_names[4] = "masterck";
+ parent_names[5] = "audiopll_pmcck";
for (i = 0; i < ARRAY_SIZE(sama5d2_gck); i++) {
hw = at91_clk_register_generated(regmap,
sama5d2_gck[i].n,
- parent_names, 5,
+ parent_names, 6,
sama5d2_gck[i].id,
sama5d2_gck[i].pll,
&sama5d2_gck[i].r);
@@ -292,6 +313,26 @@ static void __init sama5d2_pmc_setup(struct device_node *np)
sama5d2_pmc->ghws[sama5d2_gck[i].id] = hw;
}
+ if (regmap_sfr) {
+ parent_names[0] = "i2s0_clk";
+ parent_names[1] = "i2s0_gclk";
+ hw = at91_clk_i2s_mux_register(regmap_sfr, "i2s0_muxclk",
+ parent_names, 2, 0);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sama5d2_pmc->chws[PMC_I2S0_MUX] = hw;
+
+ parent_names[0] = "i2s1_clk";
+ parent_names[1] = "i2s1_gclk";
+ hw = at91_clk_i2s_mux_register(regmap_sfr, "i2s1_muxclk",
+ parent_names, 2, 1);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sama5d2_pmc->chws[PMC_I2S1_MUX] = hw;
+ }
+
of_clk_add_provider(np, of_clk_hw_pmc_get, sama5d2_pmc);
return;
diff --git a/include/linux/clk/at91_pmc.h b/include/linux/clk/at91_pmc.h
index d02dba1501..08a0755656 100644
--- a/include/linux/clk/at91_pmc.h
+++ b/include/linux/clk/at91_pmc.h
@@ -181,4 +181,29 @@
#define AT91_PMC_PCR_EN (0x1 << 28) /* Enable */
#define AT91_PMC_PCR_GCKEN (0x1 << 29) /* GCK Enable */
+#define AT91_PMC_AUDIO_PLL0 0x14c
+#define AT91_PMC_AUDIO_PLL_PLLEN (1 << 0)
+#define AT91_PMC_AUDIO_PLL_PADEN (1 << 1)
+#define AT91_PMC_AUDIO_PLL_PMCEN (1 << 2)
+#define AT91_PMC_AUDIO_PLL_RESETN (1 << 3)
+#define AT91_PMC_AUDIO_PLL_ND_OFFSET 8
+#define AT91_PMC_AUDIO_PLL_ND_MASK (0x7f << AT91_PMC_AUDIO_PLL_ND_OFFSET)
+#define AT91_PMC_AUDIO_PLL_ND(n) ((n) << AT91_PMC_AUDIO_PLL_ND_OFFSET)
+#define AT91_PMC_AUDIO_PLL_QDPMC_OFFSET 16
+#define AT91_PMC_AUDIO_PLL_QDPMC_MASK (0x7f << AT91_PMC_AUDIO_PLL_QDPMC_OFFSET)
+#define AT91_PMC_AUDIO_PLL_QDPMC(n) ((n) << AT91_PMC_AUDIO_PLL_QDPMC_OFFSET)
+
+#define AT91_PMC_AUDIO_PLL1 0x150
+#define AT91_PMC_AUDIO_PLL_FRACR_MASK 0x3fffff
+#define AT91_PMC_AUDIO_PLL_QDPAD_OFFSET 24
+#define AT91_PMC_AUDIO_PLL_QDPAD_MASK (0x7f << AT91_PMC_AUDIO_PLL_QDPAD_OFFSET)
+#define AT91_PMC_AUDIO_PLL_QDPAD(n) ((n) << AT91_PMC_AUDIO_PLL_QDPAD_OFFSET)
+#define AT91_PMC_AUDIO_PLL_QDPAD_DIV_OFFSET AT91_PMC_AUDIO_PLL_QDPAD_OFFSET
+#define AT91_PMC_AUDIO_PLL_QDPAD_DIV_MASK (0x3 << AT91_PMC_AUDIO_PLL_QDPAD_DIV_OFFSET)
+#define AT91_PMC_AUDIO_PLL_QDPAD_DIV(n) ((n) << AT91_PMC_AUDIO_PLL_QDPAD_DIV_OFFSET)
+#define AT91_PMC_AUDIO_PLL_QDPAD_EXTDIV_OFFSET 26
+#define AT91_PMC_AUDIO_PLL_QDPAD_EXTDIV_MAX 0x1f
+#define AT91_PMC_AUDIO_PLL_QDPAD_EXTDIV_MASK (AT91_PMC_AUDIO_PLL_QDPAD_EXTDIV_MAX << AT91_PMC_AUDIO_PLL_QDPAD_EXTDIV_OFFSET)
+#define AT91_PMC_AUDIO_PLL_QDPAD_EXTDIV(n) ((n) << AT91_PMC_AUDIO_PLL_QDPAD_EXTDIV_OFFSET)
+
#endif