summaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
authorTom Rini <trini@konsulko.com>2020-10-05 10:54:27 -0400
committerTom Rini <trini@konsulko.com>2020-10-05 10:54:27 -0400
commitcaebff09efe8c061b4d99b82262c67fb2db9bbcf (patch)
tree24fcdb0737bea1d87c0a36f7eb371017af83e5c2 /drivers
parent17e76b33cc0ec2eb2c519b66b6f6c491718e8046 (diff)
parent01c35f269f21398fa9d1db1b90b73f7e95a3bf22 (diff)
downloadu-boot-caebff09efe8c061b4d99b82262c67fb2db9bbcf.tar.gz
u-boot-caebff09efe8c061b4d99b82262c67fb2db9bbcf.tar.xz
u-boot-caebff09efe8c061b4d99b82262c67fb2db9bbcf.zip
Merge tag 'u-boot-atmel-2021.01-a' of https://gitlab.denx.de/u-boot/custodians/u-boot-atmel into next
First set of u-boot-atmel features for 2021.01 cycle: This feature set includes a new CPU driver for at91 family, new driver for PIT64B hardware timer, support for new at91 family SoC named sama7g5 which adds: clock support, including conversion of the clock tree to CCF; SoC support in mach-at91, pinctrl and mmc drivers update. The feature set also includes updates for mmc driver and some other minor fixes and features regarding building without the old Atmel PIT and the possibility to read a secondary MAC address from a second i2c EEPROM.
Diffstat (limited to 'drivers')
-rw-r--r--drivers/clk/at91/Kconfig7
-rw-r--r--drivers/clk/at91/Makefile15
-rw-r--r--drivers/clk/at91/clk-generated.c178
-rw-r--r--drivers/clk/at91/clk-generic.c202
-rw-r--r--drivers/clk/at91/clk-h32mx.c56
-rw-r--r--drivers/clk/at91/clk-main.c381
-rw-r--r--drivers/clk/at91/clk-master.c331
-rw-r--r--drivers/clk/at91/clk-peripheral.c291
-rw-r--r--drivers/clk/at91/clk-plla.c54
-rw-r--r--drivers/clk/at91/clk-plladiv.c85
-rw-r--r--drivers/clk/at91/clk-programmable.c208
-rw-r--r--drivers/clk/at91/clk-sam9x60-pll.c442
-rw-r--r--drivers/clk/at91/clk-slow.c36
-rw-r--r--drivers/clk/at91/clk-system.c143
-rw-r--r--drivers/clk/at91/clk-usb.c147
-rw-r--r--drivers/clk/at91/clk-utmi.c234
-rw-r--r--drivers/clk/at91/compat.c1023
-rw-r--r--drivers/clk/at91/pmc.c218
-rw-r--r--drivers/clk/at91/pmc.h140
-rw-r--r--drivers/clk/at91/sama7g5.c1401
-rw-r--r--drivers/clk/at91/sckc.c169
-rw-r--r--drivers/clk/clk-uclass.c51
-rw-r--r--drivers/clk/clk.c3
-rw-r--r--drivers/core/device.c22
-rw-r--r--drivers/cpu/Makefile1
-rw-r--r--drivers/cpu/at91_cpu.c123
-rw-r--r--drivers/mmc/atmel_sdhci.c13
-rw-r--r--drivers/pinctrl/pinctrl-at91-pio4.c1
-rw-r--r--drivers/timer/Kconfig7
-rw-r--r--drivers/timer/Makefile1
-rw-r--r--drivers/timer/mchp-pit64b-timer.c109
31 files changed, 5164 insertions, 928 deletions
diff --git a/drivers/clk/at91/Kconfig b/drivers/clk/at91/Kconfig
index 8d482a2752..4abc8026b4 100644
--- a/drivers/clk/at91/Kconfig
+++ b/drivers/clk/at91/Kconfig
@@ -54,3 +54,10 @@ config AT91_GENERIC_CLK
that may be different from the system clock. This second
clock is the generic clock (GCLK) and is managed by
the PMC via PMC_PCR register.
+
+config AT91_SAM9X60_PLL
+ bool "PLL support for SAM9X60 SoCs"
+ depends on CLK_AT91
+ help
+ This option is used to enable the AT91 SAM9X60's PLL clock
+ driver.
diff --git a/drivers/clk/at91/Makefile b/drivers/clk/at91/Makefile
index 8c197ff949..2453c38af1 100644
--- a/drivers/clk/at91/Makefile
+++ b/drivers/clk/at91/Makefile
@@ -2,11 +2,14 @@
# Makefile for at91 specific clk
#
-obj-y += pmc.o sckc.o
-obj-y += clk-slow.o clk-main.o clk-plla.o clk-plladiv.o clk-master.o
-obj-y += clk-system.o clk-peripheral.o
+ifdef CONFIG_CLK_CCF
+obj-y += pmc.o sckc.o clk-main.o clk-master.o clk-programmable.o clk-system.o
+obj-y += clk-peripheral.o
+obj-$(CONFIG_AT91_GENERIC_CLK) += clk-generic.o
obj-$(CONFIG_AT91_UTMI) += clk-utmi.o
-obj-$(CONFIG_AT91_USB_CLK) += clk-usb.o
-obj-$(CONFIG_AT91_H32MX) += clk-h32mx.o
-obj-$(CONFIG_AT91_GENERIC_CLK) += clk-generated.o
+obj-$(CONFIG_AT91_SAM9X60_PLL) += clk-sam9x60-pll.o
+obj-$(CONFIG_SAMA7G5) += sama7g5.o
+else
+obj-y += compat.o
+endif
diff --git a/drivers/clk/at91/clk-generated.c b/drivers/clk/at91/clk-generated.c
deleted file mode 100644
index c0610940c3..0000000000
--- a/drivers/clk/at91/clk-generated.c
+++ /dev/null
@@ -1,178 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0+
-/*
- * Copyright (C) 2016 Atmel Corporation
- * Wenyou.Yang <wenyou.yang@atmel.com>
- */
-
-#include <common.h>
-#include <clk-uclass.h>
-#include <dm.h>
-#include <log.h>
-#include <malloc.h>
-#include <linux/err.h>
-#include <linux/io.h>
-#include <mach/at91_pmc.h>
-#include "pmc.h"
-
-DECLARE_GLOBAL_DATA_PTR;
-
-#define GENERATED_SOURCE_MAX 6
-#define GENERATED_MAX_DIV 255
-
-/**
- * generated_clk_bind() - for the generated clock driver
- * Recursively bind its children as clk devices.
- *
- * @return: 0 on success, or negative error code on failure
- */
-static int generated_clk_bind(struct udevice *dev)
-{
- return at91_clk_sub_device_bind(dev, "generic-clk");
-}
-
-static const struct udevice_id generated_clk_match[] = {
- { .compatible = "atmel,sama5d2-clk-generated" },
- {}
-};
-
-U_BOOT_DRIVER(generated_clk) = {
- .name = "generated-clk",
- .id = UCLASS_MISC,
- .of_match = generated_clk_match,
- .bind = generated_clk_bind,
-};
-
-/*-------------------------------------------------------------*/
-
-struct generic_clk_priv {
- u32 num_parents;
-};
-
-static ulong generic_clk_get_rate(struct clk *clk)
-{
- struct pmc_platdata *plat = dev_get_platdata(clk->dev);
- struct at91_pmc *pmc = plat->reg_base;
- struct clk parent;
- ulong clk_rate;
- u32 tmp, gckdiv;
- u8 clock_source, parent_index;
- int ret;
-
- writel(clk->id & AT91_PMC_PCR_PID_MASK, &pmc->pcr);
- tmp = readl(&pmc->pcr);
- clock_source = (tmp >> AT91_PMC_PCR_GCKCSS_OFFSET) &
- AT91_PMC_PCR_GCKCSS_MASK;
- gckdiv = (tmp >> AT91_PMC_PCR_GCKDIV_OFFSET) & AT91_PMC_PCR_GCKDIV_MASK;
-
- parent_index = clock_source - 1;
- ret = clk_get_by_index(dev_get_parent(clk->dev), parent_index, &parent);
- if (ret)
- return 0;
-
- clk_rate = clk_get_rate(&parent) / (gckdiv + 1);
-
- clk_free(&parent);
-
- return clk_rate;
-}
-
-static ulong generic_clk_set_rate(struct clk *clk, ulong rate)
-{
- struct pmc_platdata *plat = dev_get_platdata(clk->dev);
- struct at91_pmc *pmc = plat->reg_base;
- struct generic_clk_priv *priv = dev_get_priv(clk->dev);
- struct clk parent, best_parent;
- ulong tmp_rate, best_rate = rate, parent_rate;
- int tmp_diff, best_diff = -1;
- u32 div, best_div = 0;
- u8 best_parent_index, best_clock_source = 0;
- u8 i;
- u32 tmp;
- int ret;
-
- for (i = 0; i < priv->num_parents; i++) {
- ret = clk_get_by_index(dev_get_parent(clk->dev), i, &parent);
- if (ret)
- return ret;
-
- parent_rate = clk_get_rate(&parent);
- if (IS_ERR_VALUE(parent_rate))
- return parent_rate;
-
- for (div = 1; div < GENERATED_MAX_DIV + 2; div++) {
- tmp_rate = DIV_ROUND_CLOSEST(parent_rate, div);
- tmp_diff = abs(rate - tmp_rate);
-
- if (best_diff < 0 || best_diff > tmp_diff) {
- best_rate = tmp_rate;
- best_diff = tmp_diff;
-
- best_div = div - 1;
- best_parent = parent;
- best_parent_index = i;
- best_clock_source = best_parent_index + 1;
- }
-
- if (!best_diff || tmp_rate < rate)
- break;
- }
-
- if (!best_diff)
- break;
- }
-
- debug("GCK: best parent: %s, best_rate = %ld, best_div = %d\n",
- best_parent.dev->name, best_rate, best_div);
-
- ret = clk_enable(&best_parent);
- if (ret)
- return ret;
-
- writel(clk->id & AT91_PMC_PCR_PID_MASK, &pmc->pcr);
- tmp = readl(&pmc->pcr);
- tmp &= ~(AT91_PMC_PCR_GCKDIV | AT91_PMC_PCR_GCKCSS);
- tmp |= AT91_PMC_PCR_GCKCSS_(best_clock_source) |
- AT91_PMC_PCR_CMD_WRITE |
- AT91_PMC_PCR_GCKDIV_(best_div) |
- AT91_PMC_PCR_GCKEN;
- writel(tmp, &pmc->pcr);
-
- while (!(readl(&pmc->sr) & AT91_PMC_GCKRDY))
- ;
-
- return 0;
-}
-
-static struct clk_ops generic_clk_ops = {
- .of_xlate = at91_clk_of_xlate,
- .get_rate = generic_clk_get_rate,
- .set_rate = generic_clk_set_rate,
-};
-
-static int generic_clk_ofdata_to_platdata(struct udevice *dev)
-{
- struct generic_clk_priv *priv = dev_get_priv(dev);
- u32 cells[GENERATED_SOURCE_MAX];
- u32 num_parents;
-
- num_parents = fdtdec_get_int_array_count(gd->fdt_blob,
- dev_of_offset(dev_get_parent(dev)), "clocks", cells,
- GENERATED_SOURCE_MAX);
-
- if (!num_parents)
- return -1;
-
- priv->num_parents = num_parents;
-
- return 0;
-}
-
-U_BOOT_DRIVER(generic_clk) = {
- .name = "generic-clk",
- .id = UCLASS_CLK,
- .probe = at91_clk_probe,
- .ofdata_to_platdata = generic_clk_ofdata_to_platdata,
- .priv_auto_alloc_size = sizeof(struct generic_clk_priv),
- .platdata_auto_alloc_size = sizeof(struct pmc_platdata),
- .ops = &generic_clk_ops,
-};
diff --git a/drivers/clk/at91/clk-generic.c b/drivers/clk/at91/clk-generic.c
new file mode 100644
index 0000000000..87738b7b5b
--- /dev/null
+++ b/drivers/clk/at91/clk-generic.c
@@ -0,0 +1,202 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Generic clock support for AT91 architectures.
+ *
+ * Copyright (C) 2020 Microchip Technology Inc. and its subsidiaries
+ *
+ * Author: Claudiu Beznea <claudiu.beznea@microchip.com>
+ *
+ * Based on drivers/clk/at91/clk-generated.c from Linux.
+ */
+#include <common.h>
+#include <clk-uclass.h>
+#include <dm.h>
+#include <linux/io.h>
+#include <linux/clk-provider.h>
+#include <linux/clk/at91_pmc.h>
+
+#include "pmc.h"
+
+#define UBOOT_DM_CLK_AT91_GCK "at91-gck-clk"
+
+#define GENERATED_MAX_DIV 255
+
+struct clk_gck {
+ void __iomem *base;
+ const u32 *clk_mux_table;
+ const u32 *mux_table;
+ const struct clk_pcr_layout *layout;
+ struct clk_range range;
+ struct clk clk;
+ u32 num_parents;
+ u32 id;
+};
+
+#define to_clk_gck(_c) container_of(_c, struct clk_gck, clk)
+
+static int clk_gck_enable(struct clk *clk)
+{
+ struct clk_gck *gck = to_clk_gck(clk);
+
+ pmc_write(gck->base, gck->layout->offset,
+ (gck->id & gck->layout->pid_mask));
+ pmc_update_bits(gck->base, gck->layout->offset,
+ gck->layout->cmd | AT91_PMC_PCR_GCKEN,
+ gck->layout->cmd | AT91_PMC_PCR_GCKEN);
+
+ return 0;
+}
+
+static int clk_gck_disable(struct clk *clk)
+{
+ struct clk_gck *gck = to_clk_gck(clk);
+
+ pmc_write(gck->base, gck->layout->offset,
+ (gck->id & gck->layout->pid_mask));
+ pmc_update_bits(gck->base, gck->layout->offset,
+ gck->layout->cmd | AT91_PMC_PCR_GCKEN,
+ gck->layout->cmd);
+
+ return 0;
+}
+
+static int clk_gck_set_parent(struct clk *clk, struct clk *parent)
+{
+ struct clk_gck *gck = to_clk_gck(clk);
+ int index;
+
+ index = at91_clk_mux_val_to_index(gck->clk_mux_table, gck->num_parents,
+ parent->id);
+ if (index < 0)
+ return index;
+
+ index = at91_clk_mux_index_to_val(gck->mux_table, gck->num_parents,
+ index);
+ if (index < 0)
+ return index;
+
+ pmc_write(gck->base, gck->layout->offset,
+ (gck->id & gck->layout->pid_mask));
+ pmc_update_bits(gck->base, gck->layout->offset,
+ gck->layout->gckcss_mask | gck->layout->cmd,
+ (index << (ffs(gck->layout->gckcss_mask) - 1)) |
+ gck->layout->cmd);
+
+ return 0;
+}
+
+static ulong clk_gck_set_rate(struct clk *clk, ulong rate)
+{
+ struct clk_gck *gck = to_clk_gck(clk);
+ ulong parent_rate = clk_get_parent_rate(clk);
+ u32 div;
+
+ if (!rate || !parent_rate)
+ return 0;
+
+ if (gck->range.max && rate > gck->range.max)
+ return 0;
+
+ div = DIV_ROUND_CLOSEST(parent_rate, rate);
+ if (div > GENERATED_MAX_DIV + 1 || !div)
+ return 0;
+
+ pmc_write(gck->base, gck->layout->offset,
+ (gck->id & gck->layout->pid_mask));
+ pmc_update_bits(gck->base, gck->layout->offset,
+ AT91_PMC_PCR_GCKDIV_MASK | gck->layout->cmd,
+ ((div - 1) << (ffs(AT91_PMC_PCR_GCKDIV_MASK) - 1)) |
+ gck->layout->cmd);
+
+ return parent_rate / div;
+}
+
+static ulong clk_gck_get_rate(struct clk *clk)
+{
+ struct clk_gck *gck = to_clk_gck(clk);
+ ulong parent_rate = clk_get_parent_rate(clk);
+ u32 val, div;
+
+ if (!parent_rate)
+ return 0;
+
+ pmc_write(gck->base, gck->layout->offset,
+ (gck->id & gck->layout->pid_mask));
+ pmc_read(gck->base, gck->layout->offset, &val);
+
+ div = (val & AT91_PMC_PCR_GCKDIV_MASK) >>
+ (ffs(AT91_PMC_PCR_GCKDIV_MASK) - 1);
+
+ return parent_rate / (div + 1);
+}
+
+static const struct clk_ops gck_ops = {
+ .enable = clk_gck_enable,
+ .disable = clk_gck_disable,
+ .set_parent = clk_gck_set_parent,
+ .set_rate = clk_gck_set_rate,
+ .get_rate = clk_gck_get_rate,
+};
+
+struct clk *
+at91_clk_register_generic(void __iomem *base,
+ const struct clk_pcr_layout *layout,
+ const char *name, const char * const *parent_names,
+ const u32 *clk_mux_table, const u32 *mux_table,
+ u8 num_parents, u8 id,
+ const struct clk_range *range)
+{
+ struct clk_gck *gck;
+ struct clk *clk;
+ int ret, index;
+ u32 val;
+
+ if (!base || !layout || !name || !parent_names || !num_parents ||
+ !clk_mux_table || !mux_table || !range)
+ return ERR_PTR(-EINVAL);
+
+ gck = kzalloc(sizeof(*gck), GFP_KERNEL);
+ if (!gck)
+ return ERR_PTR(-ENOMEM);
+
+ gck->id = id;
+ gck->base = base;
+ gck->range = *range;
+ gck->layout = layout;
+ gck->clk_mux_table = clk_mux_table;
+ gck->mux_table = mux_table;
+ gck->num_parents = num_parents;
+
+ clk = &gck->clk;
+ clk->flags = CLK_GET_RATE_NOCACHE;
+
+ pmc_write(gck->base, gck->layout->offset,
+ (gck->id & gck->layout->pid_mask));
+ pmc_read(gck->base, gck->layout->offset, &val);
+
+ val = (val & gck->layout->gckcss_mask) >>
+ (ffs(gck->layout->gckcss_mask) - 1);
+
+ index = at91_clk_mux_val_to_index(gck->mux_table, gck->num_parents,
+ val);
+ if (index < 0) {
+ kfree(gck);
+ return ERR_PTR(index);
+ }
+
+ ret = clk_register(clk, UBOOT_DM_CLK_AT91_GCK, name,
+ parent_names[index]);
+ if (ret) {
+ kfree(gck);
+ clk = ERR_PTR(ret);
+ }
+
+ return clk;
+}
+
+U_BOOT_DRIVER(at91_gck_clk) = {
+ .name = UBOOT_DM_CLK_AT91_GCK,
+ .id = UCLASS_CLK,
+ .ops = &gck_ops,
+ .flags = DM_FLAG_PRE_RELOC,
+};
diff --git a/drivers/clk/at91/clk-h32mx.c b/drivers/clk/at91/clk-h32mx.c
deleted file mode 100644
index 86bb71f612..0000000000
--- a/drivers/clk/at91/clk-h32mx.c
+++ /dev/null
@@ -1,56 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0+
-/*
- * Copyright (C) 2016 Atmel Corporation
- * Wenyou.Yang <wenyou.yang@atmel.com>
- */
-
-#include <common.h>
-#include <clk-uclass.h>
-#include <dm.h>
-#include <dm/device_compat.h>
-#include <dm/util.h>
-#include <linux/io.h>
-#include <mach/at91_pmc.h>
-#include "pmc.h"
-
-DECLARE_GLOBAL_DATA_PTR;
-
-#define H32MX_MAX_FREQ 90000000
-
-static ulong sama5d4_h32mx_clk_get_rate(struct clk *clk)
-{
- struct pmc_platdata *plat = dev_get_platdata(clk->dev);
- struct at91_pmc *pmc = plat->reg_base;
- ulong rate = gd->arch.mck_rate_hz;
-
- if (readl(&pmc->mckr) & AT91_PMC_MCKR_H32MXDIV)
- rate /= 2;
-
- if (rate > H32MX_MAX_FREQ)
- dev_dbg(clk->dev, "H32MX clock is too fast\n");
-
- return rate;
-}
-
-static struct clk_ops sama5d4_h32mx_clk_ops = {
- .get_rate = sama5d4_h32mx_clk_get_rate,
-};
-
-static int sama5d4_h32mx_clk_probe(struct udevice *dev)
-{
- return at91_pmc_core_probe(dev);
-}
-
-static const struct udevice_id sama5d4_h32mx_clk_match[] = {
- { .compatible = "atmel,sama5d4-clk-h32mx" },
- {}
-};
-
-U_BOOT_DRIVER(sama5d4_h32mx_clk) = {
- .name = "sama5d4-h32mx-clk",
- .id = UCLASS_CLK,
- .of_match = sama5d4_h32mx_clk_match,
- .probe = sama5d4_h32mx_clk_probe,
- .platdata_auto_alloc_size = sizeof(struct pmc_platdata),
- .ops = &sama5d4_h32mx_clk_ops,
-};
diff --git a/drivers/clk/at91/clk-main.c b/drivers/clk/at91/clk-main.c
index b31a1cb682..b52d926f33 100644
--- a/drivers/clk/at91/clk-main.c
+++ b/drivers/clk/at91/clk-main.c
@@ -1,54 +1,387 @@
// SPDX-License-Identifier: GPL-2.0+
/*
- * Copyright (C) 2016 Atmel Corporation
- * Wenyou.Yang <wenyou.yang@atmel.com>
+ * Main clock support for AT91 architectures.
+ *
+ * Copyright (C) 2020 Microchip Technology Inc. and its subsidiaries
+ *
+ * Author: Claudiu Beznea <claudiu.beznea@microchip.com>
+ *
+ * Based on drivers/clk/at91/clk-main.c from Linux.
*/
+#include <asm/processor.h>
#include <common.h>
#include <clk-uclass.h>
#include <dm.h>
+#include <linux/clk-provider.h>
+#include <linux/clk/at91_pmc.h>
+#include <linux/delay.h>
#include <linux/io.h>
-#include <mach/at91_pmc.h>
#include "pmc.h"
-DECLARE_GLOBAL_DATA_PTR;
+#define UBOOT_DM_CLK_AT91_MAIN_RC "at91-main-rc-clk"
+#define UBOOT_DM_CLK_AT91_MAIN_OSC "at91-main-osc-clk"
+#define UBOOT_DM_CLK_AT91_RM9200_MAIN "at91-rm9200-main-clk"
+#define UBOOT_DM_CLK_AT91_SAM9X5_MAIN "at91-sam9x5-main-clk"
-static int main_osc_clk_enable(struct clk *clk)
+#define MOR_KEY_MASK GENMASK(23, 16)
+#define USEC_PER_SEC 1000000UL
+#define SLOW_CLOCK_FREQ 32768
+
+#define clk_main_parent_select(s) (((s) & \
+ (AT91_PMC_MOSCEN | \
+ AT91_PMC_OSCBYPASS)) ? 1 : 0)
+
+struct clk_main_rc {
+ void __iomem *reg;
+ struct clk clk;
+};
+
+#define to_clk_main_rc(_clk) container_of(_clk, struct clk_main_rc, clk)
+
+struct clk_main_osc {
+ void __iomem *reg;
+ struct clk clk;
+};
+
+#define to_clk_main_osc(_clk) container_of(_clk, struct clk_main_osc, clk)
+
+struct clk_main {
+ void __iomem *reg;
+ const unsigned int *clk_mux_table;
+ const char * const *parent_names;
+ unsigned int num_parents;
+ int type;
+ struct clk clk;
+};
+
+#define to_clk_main(_clk) container_of(_clk, struct clk_main, clk)
+
+static int main_rc_enable(struct clk *clk)
+{
+ struct clk_main_rc *main_rc = to_clk_main_rc(clk);
+ void __iomem *reg = main_rc->reg;
+ unsigned int val;
+
+ pmc_read(reg, AT91_CKGR_MOR, &val);
+
+ if (!(val & AT91_PMC_MOSCRCEN)) {
+ pmc_update_bits(reg, AT91_CKGR_MOR,
+ MOR_KEY_MASK | AT91_PMC_MOSCRCEN,
+ AT91_PMC_KEY | AT91_PMC_MOSCRCEN);
+ }
+
+ pmc_read(reg, AT91_PMC_SR, &val);
+ while (!(val & AT91_PMC_MOSCRCS)) {
+ pmc_read(reg, AT91_PMC_SR, &val);
+ debug("waiting for main rc...\n");
+ cpu_relax();
+ }
+
+ return 0;
+}
+
+static int main_rc_disable(struct clk *clk)
{
- struct pmc_platdata *plat = dev_get_platdata(clk->dev);
- struct at91_pmc *pmc = plat->reg_base;
+ struct clk_main_rc *main_rc = to_clk_main_rc(clk);
+ struct reg *reg = main_rc->reg;
+ unsigned int val;
+
+ pmc_read(reg, AT91_CKGR_MOR, &val);
- if (readl(&pmc->sr) & AT91_PMC_MOSCSELS)
+ if (!(val & AT91_PMC_MOSCRCEN))
return 0;
- return -EINVAL;
+ pmc_update_bits(reg, AT91_CKGR_MOR, MOR_KEY_MASK | AT91_PMC_MOSCRCEN,
+ AT91_PMC_KEY);
+
+ return 0;
}
-static ulong main_osc_clk_get_rate(struct clk *clk)
+static const struct clk_ops main_rc_clk_ops = {
+ .enable = main_rc_enable,
+ .disable = main_rc_disable,
+ .get_rate = clk_generic_get_rate,
+};
+
+struct clk *at91_clk_main_rc(void __iomem *reg, const char *name,
+ const char *parent_name)
{
- return gd->arch.main_clk_rate_hz;
+ struct clk_main_rc *main_rc;
+ struct clk *clk;
+ int ret;
+
+ if (!reg || !name || !parent_name)
+ return ERR_PTR(-EINVAL);
+
+ main_rc = kzalloc(sizeof(*main_rc), GFP_KERNEL);
+ if (!main_rc)
+ return ERR_PTR(-ENOMEM);
+
+ main_rc->reg = reg;
+ clk = &main_rc->clk;
+
+ ret = clk_register(clk, UBOOT_DM_CLK_AT91_MAIN_RC, name,
+ parent_name);
+ if (ret) {
+ kfree(main_rc);
+ clk = ERR_PTR(ret);
+ }
+
+ return clk;
}
-static struct clk_ops main_osc_clk_ops = {
- .enable = main_osc_clk_enable,
- .get_rate = main_osc_clk_get_rate,
+U_BOOT_DRIVER(at91_main_rc_clk) = {
+ .name = UBOOT_DM_CLK_AT91_MAIN_RC,
+ .id = UCLASS_CLK,
+ .ops = &main_rc_clk_ops,
+ .flags = DM_FLAG_PRE_RELOC,
};
-static int main_osc_clk_probe(struct udevice *dev)
+static int clk_main_osc_enable(struct clk *clk)
{
- return at91_pmc_core_probe(dev);
+ struct clk_main_osc *main = to_clk_main_osc(clk);
+ void __iomem *reg = main->reg;
+ unsigned int val;
+
+ pmc_read(reg, AT91_CKGR_MOR, &val);
+ val &= ~MOR_KEY_MASK;
+
+ if (val & AT91_PMC_OSCBYPASS)
+ return 0;
+
+ if (!(val & AT91_PMC_MOSCEN)) {
+ val |= AT91_PMC_MOSCEN | AT91_PMC_KEY;
+ pmc_write(reg, AT91_CKGR_MOR, val);
+ }
+
+ pmc_read(reg, AT91_PMC_SR, &val);
+ while (!(val & AT91_PMC_MOSCS)) {
+ pmc_read(reg, AT91_PMC_SR, &val);
+ debug("waiting for main osc..\n");
+ cpu_relax();
+ }
+
+ return 0;
+}
+
+static int clk_main_osc_disable(struct clk *clk)
+{
+ struct clk_main_osc *main = to_clk_main_osc(clk);
+ void __iomem *reg = main->reg;
+ unsigned int val;
+
+ pmc_read(reg, AT91_CKGR_MOR, &val);
+ if (val & AT91_PMC_OSCBYPASS)
+ return 0;
+
+ if (!(val & AT91_PMC_MOSCEN))
+ return 0;
+
+ val &= ~(AT91_PMC_KEY | AT91_PMC_MOSCEN);
+ pmc_write(reg, AT91_CKGR_MOR, val | AT91_PMC_KEY);
+
+ return 0;
}
-static const struct udevice_id main_osc_clk_match[] = {
- { .compatible = "atmel,at91sam9x5-clk-main" },
- {}
+static const struct clk_ops main_osc_clk_ops = {
+ .enable = clk_main_osc_enable,
+ .disable = clk_main_osc_disable,
+ .get_rate = clk_generic_get_rate,
};
-U_BOOT_DRIVER(at91sam9x5_main_osc_clk) = {
- .name = "at91sam9x5-main-osc-clk",
+struct clk *at91_clk_main_osc(void __iomem *reg, const char *name,
+ const char *parent_name, bool bypass)
+{
+ struct clk_main_osc *main;
+ struct clk *clk;
+ int ret;
+
+ if (!reg || !name || !parent_name)
+ return ERR_PTR(-EINVAL);
+
+ main = kzalloc(sizeof(*main), GFP_KERNEL);
+ if (!main)
+ return ERR_PTR(-ENOMEM);
+
+ main->reg = reg;
+ clk = &main->clk;
+
+ if (bypass) {
+ pmc_update_bits(reg, AT91_CKGR_MOR,
+ MOR_KEY_MASK | AT91_PMC_OSCBYPASS,
+ AT91_PMC_KEY | AT91_PMC_OSCBYPASS);
+ }
+
+ ret = clk_register(clk, UBOOT_DM_CLK_AT91_MAIN_OSC, name, parent_name);
+ if (ret) {
+ kfree(main);
+ clk = ERR_PTR(ret);
+ }
+
+ return clk;
+}
+
+U_BOOT_DRIVER(at91_main_osc_clk) = {
+ .name = UBOOT_DM_CLK_AT91_MAIN_OSC,
.id = UCLASS_CLK,
- .of_match = main_osc_clk_match,
- .probe = main_osc_clk_probe,
- .platdata_auto_alloc_size = sizeof(struct pmc_platdata),
.ops = &main_osc_clk_ops,
+ .flags = DM_FLAG_PRE_RELOC,
+};
+
+static int clk_main_probe_frequency(void __iomem *reg)
+{
+ unsigned int cycles = 16;
+ unsigned int cycle = DIV_ROUND_UP(USEC_PER_SEC, SLOW_CLOCK_FREQ);
+ unsigned int mcfr;
+
+ while (cycles--) {
+ pmc_read(reg, AT91_CKGR_MCFR, &mcfr);
+ if (mcfr & AT91_PMC_MAINRDY)
+ return 0;
+ udelay(cycle);
+ }
+
+ return -ETIMEDOUT;
+}
+
+static int clk_rm9200_main_enable(struct clk *clk)
+{
+ struct clk_main *main = to_clk_main(clk);
+
+ return clk_main_probe_frequency(main->reg);
+}
+
+static const struct clk_ops rm9200_main_clk_ops = {
+ .enable = clk_rm9200_main_enable,
+};
+
+struct clk *at91_clk_rm9200_main(void __iomem *reg, const char *name,
+ const char *parent_name)
+{
+ struct clk_main *main;
+ struct clk *clk;
+ int ret;
+
+ if (!reg || !name || !parent_name)
+ return ERR_PTR(-EINVAL);
+
+ main = kzalloc(sizeof(*main), GFP_KERNEL);
+ if (!main)
+ return ERR_PTR(-ENOMEM);
+
+ main->reg = reg;
+ clk = &main->clk;
+
+ ret = clk_register(clk, UBOOT_DM_CLK_AT91_RM9200_MAIN, name,
+ parent_name);
+ if (ret) {
+ kfree(main);
+ clk = ERR_PTR(ret);
+ }
+
+ return clk;
+}
+
+U_BOOT_DRIVER(at91_rm9200_main_clk) = {
+ .name = UBOOT_DM_CLK_AT91_RM9200_MAIN,
+ .id = UCLASS_CLK,
+ .ops = &rm9200_main_clk_ops,
+ .flags = DM_FLAG_PRE_RELOC,
+};
+
+static inline bool clk_sam9x5_main_ready(void __iomem *reg)
+{
+ unsigned int val;
+
+ pmc_read(reg, AT91_PMC_SR, &val);
+
+ return !!(val & AT91_PMC_MOSCSELS);
+}
+
+static int clk_sam9x5_main_enable(struct clk *clk)
+{
+ struct clk_main *main = to_clk_main(clk);
+ void __iomem *reg = main->reg;
+
+ while (!clk_sam9x5_main_ready(reg)) {
+ debug("waiting for main...");
+ cpu_relax();
+ }
+
+ return clk_main_probe_frequency(reg);
+}
+
+static int clk_sam9x5_main_set_parent(struct clk *clk, struct clk *parent)
+{
+ struct clk_main *main = to_clk_main(clk);
+ void __iomem *reg = main->reg;
+ unsigned int tmp, index;
+
+ index = at91_clk_mux_val_to_index(main->clk_mux_table,
+ main->num_parents, AT91_CLK_ID_TO_DID(parent->id));
+ if (index < 0)
+ return index;
+
+ pmc_read(reg, AT91_CKGR_MOR, &tmp);
+ tmp &= ~MOR_KEY_MASK;
+ tmp |= AT91_PMC_KEY;
+
+ if (index && !(tmp & AT91_PMC_MOSCSEL))
+ pmc_write(reg, AT91_CKGR_MOR, tmp | AT91_PMC_MOSCSEL);
+ else if (!index && (tmp & AT91_PMC_MOSCSEL))
+ pmc_write(reg, AT91_CKGR_MOR, tmp & ~AT91_PMC_MOSCSEL);
+
+ while (!clk_sam9x5_main_ready(reg))
+ cpu_relax();
+
+ return 0;
+}
+
+static const struct clk_ops sam9x5_main_clk_ops = {
+ .enable = clk_sam9x5_main_enable,
+ .set_parent = clk_sam9x5_main_set_parent,
+ .get_rate = clk_generic_get_rate,
+};
+
+struct clk *at91_clk_sam9x5_main(void __iomem *reg, const char *name,
+ const char * const *parent_names,
+ int num_parents, const u32 *clk_mux_table,
+ int type)
+{
+ struct clk *clk = ERR_PTR(-ENOMEM);
+ struct clk_main *main = NULL;
+ unsigned int val;
+ int ret;
+
+ if (!reg || !name || !parent_names || !num_parents || !clk_mux_table)
+ return ERR_PTR(-EINVAL);
+
+ main = kzalloc(sizeof(*main), GFP_KERNEL);
+ if (!main)
+ return ERR_PTR(-ENOMEM);
+
+ main->reg = reg;
+ main->parent_names = parent_names;
+ main->num_parents = num_parents;
+ main->clk_mux_table = clk_mux_table;
+ main->type = type;
+ clk = &main->clk;
+ clk->flags = CLK_GET_RATE_NOCACHE;
+ pmc_read(reg, AT91_CKGR_MOR, &val);
+ ret = clk_register(clk, UBOOT_DM_CLK_AT91_SAM9X5_MAIN, name,
+ main->parent_names[clk_main_parent_select(val)]);
+ if (ret) {
+ kfree(main);
+ clk = ERR_PTR(ret);
+ }
+
+ return clk;
+}
+
+U_BOOT_DRIVER(at91_sam9x5_main_clk) = {
+ .name = UBOOT_DM_CLK_AT91_SAM9X5_MAIN,
+ .id = UCLASS_CLK,
+ .ops = &sam9x5_main_clk_ops,
+ .flags = DM_FLAG_PRE_RELOC,
};
diff --git a/drivers/clk/at91/clk-master.c b/drivers/clk/at91/clk-master.c
index e078fab7b4..759df93697 100644
--- a/drivers/clk/at91/clk-master.c
+++ b/drivers/clk/at91/clk-master.c
@@ -1,33 +1,332 @@
// SPDX-License-Identifier: GPL-2.0+
/*
- * Copyright (C) 2016 Atmel Corporation
- * Wenyou.Yang <wenyou.yang@atmel.com>
+ * Master clock support for AT91 architectures.
+ *
+ * Copyright (C) 2020 Microchip Technology Inc. and its subsidiaries
+ *
+ * Author: Claudiu Beznea <claudiu.beznea@microchip.com>
+ *
+ * Based on drivers/clk/at91/clk-master.c from Linux.
*/
-#include <common.h>
+#include <asm/processor.h>
#include <clk-uclass.h>
+#include <common.h>
#include <dm.h>
+#include <linux/clk-provider.h>
+#include <linux/clk/at91_pmc.h>
+
+#include "pmc.h"
+
+#define UBOOT_DM_CLK_AT91_MASTER "at91-master-clk"
+#define UBOOT_DM_CLK_AT91_SAMA7G5_MASTER "at91-sama7g5-master-clk"
+
+#define MASTER_PRES_MASK 0x7
+#define MASTER_PRES_MAX MASTER_PRES_MASK
+#define MASTER_DIV_SHIFT 8
+#define MASTER_DIV_MASK 0x3
+
+#define PMC_MCR 0x30
+#define PMC_MCR_ID_MSK GENMASK(3, 0)
+#define PMC_MCR_CMD BIT(7)
+#define PMC_MCR_DIV GENMASK(10, 8)
+#define PMC_MCR_CSS GENMASK(20, 16)
+#define PMC_MCR_CSS_SHIFT (16)
+#define PMC_MCR_EN BIT(28)
+
+#define PMC_MCR_ID(x) ((x) & PMC_MCR_ID_MSK)
+
+#define MASTER_MAX_ID 4
+
+struct clk_master {
+ void __iomem *base;
+ const struct clk_master_layout *layout;
+ const struct clk_master_characteristics *characteristics;
+ const u32 *mux_table;
+ const u32 *clk_mux_table;
+ u32 num_parents;
+ struct clk clk;
+ u8 id;
+};
+
+#define to_clk_master(_clk) container_of(_clk, struct clk_master, clk)
+
+static inline bool clk_master_ready(struct clk_master *master)
+{
+ unsigned int bit = master->id ? AT91_PMC_MCKXRDY : AT91_PMC_MCKRDY;
+ unsigned int status;
+
+ pmc_read(master->base, AT91_PMC_SR, &status);
+
+ return !!(status & bit);
+}
+
+static int clk_master_enable(struct clk *clk)
+{
+ struct clk_master *master = to_clk_master(clk);
+
+ while (!clk_master_ready(master)) {
+ debug("waiting for mck %d\n", master->id);
+ cpu_relax();
+ }
+
+ return 0;
+}
-DECLARE_GLOBAL_DATA_PTR;
+static ulong clk_master_get_rate(struct clk *clk)
+{
+ struct clk_master *master = to_clk_master(clk);
+ const struct clk_master_layout *layout = master->layout;
+ const struct clk_master_characteristics *characteristics =
+ master->characteristics;
+ ulong rate = clk_get_parent_rate(clk);
+ unsigned int mckr;
+ u8 pres, div;
+
+ if (!rate)
+ return 0;
+
+ pmc_read(master->base, master->layout->offset, &mckr);
+ mckr &= layout->mask;
+
+ pres = (mckr >> layout->pres_shift) & MASTER_PRES_MASK;
+ div = (mckr >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK;
+
+ if (characteristics->have_div3_pres && pres == MASTER_PRES_MAX)
+ rate /= 3;
+ else
+ rate >>= pres;
+
+ rate /= characteristics->divisors[div];
+
+ if (rate < characteristics->output.min)
+ pr_warn("master clk is underclocked");
+ else if (rate > characteristics->output.max)
+ pr_warn("master clk is overclocked");
+
+ return rate;
+}
+
+static const struct clk_ops master_ops = {
+ .enable = clk_master_enable,
+ .get_rate = clk_master_get_rate,
+};
-static ulong at91_master_clk_get_rate(struct clk *clk)
+struct clk *at91_clk_register_master(void __iomem *base,
+ const char *name, const char * const *parent_names,
+ int num_parents, const struct clk_master_layout *layout,
+ const struct clk_master_characteristics *characteristics,
+ const u32 *mux_table)
{
- return gd->arch.mck_rate_hz;
+ struct clk_master *master;
+ struct clk *clk;
+ unsigned int val;
+ int ret;
+
+ if (!base || !name || !num_parents || !parent_names ||
+ !layout || !characteristics || !mux_table)
+ return ERR_PTR(-EINVAL);
+
+ master = kzalloc(sizeof(*master), GFP_KERNEL);
+ if (!master)
+ return ERR_PTR(-ENOMEM);
+
+ master->layout = layout;
+ master->characteristics = characteristics;
+ master->base = base;
+ master->num_parents = num_parents;
+ master->mux_table = mux_table;
+
+ pmc_read(master->base, master->layout->offset, &val);
+ clk = &master->clk;
+ clk->flags = CLK_GET_RATE_NOCACHE | CLK_IS_CRITICAL;
+ ret = clk_register(clk, UBOOT_DM_CLK_AT91_MASTER, name,
+ parent_names[val & AT91_PMC_CSS]);
+ if (ret) {
+ kfree(master);
+ clk = ERR_PTR(ret);
+ }
+
+ return clk;
}
-static struct clk_ops at91_master_clk_ops = {
- .get_rate = at91_master_clk_get_rate,
+U_BOOT_DRIVER(at91_master_clk) = {
+ .name = UBOOT_DM_CLK_AT91_MASTER,
+ .id = UCLASS_CLK,
+ .ops = &master_ops,
+ .flags = DM_FLAG_PRE_RELOC,
};
-static const struct udevice_id at91_master_clk_match[] = {
- { .compatible = "atmel,at91rm9200-clk-master" },
- { .compatible = "atmel,at91sam9x5-clk-master" },
- {}
+static int clk_sama7g5_master_set_parent(struct clk *clk, struct clk *parent)
+{
+ struct clk_master *master = to_clk_master(clk);
+ int index;
+
+ index = at91_clk_mux_val_to_index(master->clk_mux_table,
+ master->num_parents, parent->id);
+ if (index < 0)
+ return index;
+
+ index = at91_clk_mux_index_to_val(master->mux_table,
+ master->num_parents, index);
+ if (index < 0)
+ return index;
+
+ pmc_write(master->base, PMC_MCR, PMC_MCR_ID(master->id));
+ pmc_update_bits(master->base, PMC_MCR,
+ PMC_MCR_CSS | PMC_MCR_CMD | PMC_MCR_ID_MSK,
+ (index << PMC_MCR_CSS_SHIFT) | PMC_MCR_CMD |
+ PMC_MCR_ID(master->id));
+ return 0;
+}
+
+static int clk_sama7g5_master_enable(struct clk *clk)
+{
+ struct clk_master *master = to_clk_master(clk);
+
+ pmc_write(master->base, PMC_MCR, PMC_MCR_ID(master->id));
+ pmc_update_bits(master->base, PMC_MCR,
+ PMC_MCR_EN | PMC_MCR_CMD | PMC_MCR_ID_MSK,
+ PMC_MCR_EN | PMC_MCR_CMD | PMC_MCR_ID(master->id));
+
+ return 0;
+}
+
+static int clk_sama7g5_master_disable(struct clk *clk)
+{
+ struct clk_master *master = to_clk_master(clk);
+
+ pmc_write(master->base, PMC_MCR, master->id);
+ pmc_update_bits(master->base, PMC_MCR,
+ PMC_MCR_EN | PMC_MCR_CMD | PMC_MCR_ID_MSK,
+ PMC_MCR_CMD | PMC_MCR_ID(master->id));
+
+ return 0;
+}
+
+static ulong clk_sama7g5_master_set_rate(struct clk *clk, ulong rate)
+{
+ struct clk_master *master = to_clk_master(clk);
+ ulong parent_rate = clk_get_parent_rate(clk);
+ ulong div, rrate;
+
+ if (!parent_rate)
+ return 0;
+
+ div = DIV_ROUND_CLOSEST(parent_rate, rate);
+ if ((div > (1 << (MASTER_PRES_MAX - 1))) || (div & (div - 1))) {
+ return 0;
+ } else if (div == 3) {
+ rrate = DIV_ROUND_CLOSEST(parent_rate, MASTER_PRES_MAX);
+ div = MASTER_PRES_MAX;
+ } else {
+ rrate = DIV_ROUND_CLOSEST(parent_rate, div);
+ div = ffs(div) - 1;
+ }
+
+ pmc_write(master->base, PMC_MCR, master->id);
+ pmc_update_bits(master->base, PMC_MCR,
+ PMC_MCR_DIV | PMC_MCR_CMD | PMC_MCR_ID_MSK,
+ (div << MASTER_DIV_SHIFT) | PMC_MCR_CMD |
+ PMC_MCR_ID(master->id));
+
+ return rrate;
+}
+
+static ulong clk_sama7g5_master_get_rate(struct clk *clk)
+{
+ struct clk_master *master = to_clk_master(clk);
+ ulong parent_rate = clk_get_parent_rate(clk);
+ unsigned int val;
+ ulong div;
+
+ if (!parent_rate)
+ return 0;
+
+ pmc_write(master->base, PMC_MCR, master->id);
+ pmc_read(master->base, PMC_MCR, &val);
+
+ div = (val >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK;
+
+ if (div == MASTER_PRES_MAX)
+ div = 3;
+ else
+ div = 1 << div;
+
+ return DIV_ROUND_CLOSEST(parent_rate, div);
+}
+
+static const struct clk_ops sama7g5_master_ops = {
+ .enable = clk_sama7g5_master_enable,
+ .disable = clk_sama7g5_master_disable,
+ .set_rate = clk_sama7g5_master_set_rate,
+ .get_rate = clk_sama7g5_master_get_rate,
+ .set_parent = clk_sama7g5_master_set_parent,
};
-U_BOOT_DRIVER(atmel_at91rm9200_clk_master) = {
- .name = "atmel_at91rm9200_clk_master",
+struct clk *at91_clk_sama7g5_register_master(void __iomem *base,
+ const char *name, const char * const *parent_names,
+ int num_parents, const u32 *mux_table, const u32 *clk_mux_table,
+ bool critical, u8 id)
+{
+ struct clk_master *master;
+ struct clk *clk;
+ u32 val, index;
+ int ret;
+
+ if (!base || !name || !num_parents || !parent_names ||
+ !mux_table || !clk_mux_table || id > MASTER_MAX_ID)
+ return ERR_PTR(-EINVAL);
+
+ master = kzalloc(sizeof(*master), GFP_KERNEL);
+ if (!master)
+ return ERR_PTR(-ENOMEM);
+
+ master->base = base;
+ master->id = id;
+ master->mux_table = mux_table;
+ master->clk_mux_table = clk_mux_table;
+ master->num_parents = num_parents;
+
+ pmc_write(master->base, PMC_MCR, master->id);
+ pmc_read(master->base, PMC_MCR, &val);
+
+ index = at91_clk_mux_val_to_index(master->mux_table,
+ master->num_parents,
+ (val & PMC_MCR_CSS) >> PMC_MCR_CSS_SHIFT);
+ if (index < 0) {
+ kfree(master);
+ return ERR_PTR(index);
+ }
+
+ clk = &master->clk;
+ clk->flags = CLK_GET_RATE_NOCACHE | (critical ? CLK_IS_CRITICAL : 0);
+
+ ret = clk_register(clk, UBOOT_DM_CLK_AT91_SAMA7G5_MASTER, name,
+ parent_names[index]);
+ if (ret) {
+ kfree(master);
+ clk = ERR_PTR(ret);
+ }
+
+ return clk;
+}
+
+U_BOOT_DRIVER(at91_sama7g5_master_clk) = {
+ .name = UBOOT_DM_CLK_AT91_SAMA7G5_MASTER,
.id = UCLASS_CLK,
- .of_match = at91_master_clk_match,
- .ops = &at91_master_clk_ops,
+ .ops = &sama7g5_master_ops,
+ .flags = DM_FLAG_PRE_RELOC,
+};
+
+const struct clk_master_layout at91rm9200_master_layout = {
+ .mask = 0x31F,
+ .pres_shift = 2,
+ .offset = AT91_PMC_MCKR,
+};
+
+const struct clk_master_layout at91sam9x5_master_layout = {
+ .mask = 0x373,
+ .pres_shift = 4,
+ .offset = AT91_PMC_MCKR,
};
diff --git a/drivers/clk/at91/clk-peripheral.c b/drivers/clk/at91/clk-peripheral.c
index cd9d5e77c0..52cbc520ce 100644
--- a/drivers/clk/at91/clk-peripheral.c
+++ b/drivers/clk/at91/clk-peripheral.c
@@ -1,113 +1,254 @@
// SPDX-License-Identifier: GPL-2.0+
/*
- * Copyright (C) 2016 Atmel Corporation
- * Wenyou.Yang <wenyou.yang@atmel.com>
+ * Peripheral clock support for AT91 architectures.
+ *
+ * Copyright (C) 2020 Microchip Technology Inc. and its subsidiaries
+ *
+ * Author: Claudiu Beznea <claudiu.beznea@microchip.com>
+ *
+ * Based on drivers/clk/at91/clk-peripheral.c from Linux.
*/
-
#include <common.h>
#include <clk-uclass.h>
#include <dm.h>
-#include <malloc.h>
#include <linux/io.h>
-#include <mach/at91_pmc.h>
+#include <linux/clk-provider.h>
+#include <linux/clk/at91_pmc.h>
+
#include "pmc.h"
+#define UBOOT_DM_CLK_AT91_PERIPH "at91-periph-clk"
+#define UBOOT_DM_CLK_AT91_SAM9X5_PERIPH "at91-sam9x5-periph-clk"
+
#define PERIPHERAL_ID_MIN 2
#define PERIPHERAL_ID_MAX 31
#define PERIPHERAL_MASK(id) (1 << ((id) & PERIPHERAL_ID_MAX))
-enum periph_clk_type {
- CLK_PERIPH_AT91RM9200 = 0,
- CLK_PERIPH_AT91SAM9X5,
+#define PERIPHERAL_MAX_SHIFT 3
+
+struct clk_peripheral {
+ void __iomem *base;
+ struct clk clk;
+ u32 id;
};
-/**
- * sam9x5_periph_clk_bind() - for the periph clock driver
- * Recursively bind its children as clk devices.
- *
- * @return: 0 on success, or negative error code on failure
- */
-static int sam9x5_periph_clk_bind(struct udevice *dev)
+
+#define to_clk_peripheral(_c) container_of(_c, struct clk_peripheral, clk)
+
+struct clk_sam9x5_peripheral {
+ const struct clk_pcr_layout *layout;
+ void __iomem *base;
+ struct clk clk;
+ struct clk_range range;
+ u32 id;
+ u32 div;
+ bool auto_div;
+};
+
+#define to_clk_sam9x5_peripheral(_c) \
+ container_of(_c, struct clk_sam9x5_peripheral, clk)
+
+static int clk_peripheral_enable(struct clk *clk)
{
- return at91_clk_sub_device_bind(dev, "periph-clk");
+ struct clk_peripheral *periph = to_clk_peripheral(clk);
+ int offset = AT91_PMC_PCER;
+ u32 id = periph->id;
+
+ if (id < PERIPHERAL_ID_MIN)
+ return 0;
+ if (id > PERIPHERAL_ID_MAX)
+ offset = AT91_PMC_PCER1;
+ pmc_write(periph->base, offset, PERIPHERAL_MASK(id));
+
+ return 0;
+}
+
+static int clk_peripheral_disable(struct clk *clk)
+{
+ struct clk_peripheral *periph = to_clk_peripheral(clk);
+ int offset = AT91_PMC_PCDR;
+ u32 id = periph->id;
+
+ if (id < PERIPHERAL_ID_MIN)
+ return -EINVAL;
+
+ if (id > PERIPHERAL_ID_MAX)
+ offset = AT91_PMC_PCDR1;
+ pmc_write(periph->base, offset, PERIPHERAL_MASK(id));
+
+ return 0;
}
-static const struct udevice_id sam9x5_periph_clk_match[] = {
- {
- .compatible = "atmel,at91rm9200-clk-peripheral",
- .data = CLK_PERIPH_AT91RM9200,
- },
- {
- .compatible = "atmel,at91sam9x5-clk-peripheral",
- .data = CLK_PERIPH_AT91SAM9X5,
- },
- {}
+static const struct clk_ops peripheral_ops = {
+ .enable = clk_peripheral_enable,
+ .disable = clk_peripheral_disable,
+ .get_rate = clk_generic_get_rate,
};
-U_BOOT_DRIVER(atmel_at91rm9200_clk_peripheral) = {
- .name = "atmel_at91rm9200_clk_peripheral",
- .id = UCLASS_MISC,
- .of_match = sam9x5_periph_clk_match,
- .bind = sam9x5_periph_clk_bind,
+struct clk *
+at91_clk_register_peripheral(void __iomem *base, const char *name,
+ const char *parent_name, u32 id)
+{
+ struct clk_peripheral *periph;
+ struct clk *clk;
+ int ret;
+
+ if (!base || !name || !parent_name || id > PERIPHERAL_ID_MAX)
+ return ERR_PTR(-EINVAL);
+
+ periph = kzalloc(sizeof(*periph), GFP_KERNEL);
+ if (!periph)
+ return ERR_PTR(-ENOMEM);
+
+ periph->id = id;
+ periph->base = base;
+
+ clk = &periph->clk;
+ clk->flags = CLK_GET_RATE_NOCACHE;
+ ret = clk_register(clk, UBOOT_DM_CLK_AT91_PERIPH, name, parent_name);
+ if (ret) {
+ kfree(periph);
+ clk = ERR_PTR(ret);
+ }
+
+ return clk;
+}
+
+U_BOOT_DRIVER(at91_periph_clk) = {
+ .name = UBOOT_DM_CLK_AT91_PERIPH,
+ .id = UCLASS_CLK,
+ .ops = &peripheral_ops,
+ .flags = DM_FLAG_PRE_RELOC,
};
-/*---------------------------------------------------------*/
+static int clk_sam9x5_peripheral_enable(struct clk *clk)
+{
+ struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(clk);
+
+ if (periph->id < PERIPHERAL_ID_MIN)
+ return 0;
+
+ pmc_write(periph->base, periph->layout->offset,
+ (periph->id & periph->layout->pid_mask));
+ pmc_update_bits(periph->base, periph->layout->offset,
+ periph->layout->cmd | AT91_PMC_PCR_EN,
+ periph->layout->cmd | AT91_PMC_PCR_EN);
-static int periph_clk_enable(struct clk *clk)
+ return 0;
+}
+
+static int clk_sam9x5_peripheral_disable(struct clk *clk)
{
- struct pmc_platdata *plat = dev_get_platdata(clk->dev);
- struct at91_pmc *pmc = plat->reg_base;
- enum periph_clk_type clk_type;
- void *addr;
-
- if (clk->id < PERIPHERAL_ID_MIN)
- return -1;
-
- clk_type = dev_get_driver_data(dev_get_parent(clk->dev));
- if (clk_type == CLK_PERIPH_AT91RM9200) {
- addr = &pmc->pcer;
- if (clk->id > PERIPHERAL_ID_MAX)
- addr = &pmc->pcer1;
-
- setbits_le32(addr, PERIPHERAL_MASK(clk->id));
- } else {
- writel(clk->id & AT91_PMC_PCR_PID_MASK, &pmc->pcr);
- setbits_le32(&pmc->pcr,
- AT91_PMC_PCR_CMD_WRITE | AT91_PMC_PCR_EN);
- }
+ struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(clk);
+
+ if (periph->id < PERIPHERAL_ID_MIN)
+ return -EINVAL;
+
+ pmc_write(periph->base, periph->layout->offset,
+ (periph->id & periph->layout->pid_mask));
+ pmc_update_bits(periph->base, periph->layout->offset,
+ AT91_PMC_PCR_EN | periph->layout->cmd,
+ periph->layout->cmd);
return 0;
}
-static ulong periph_get_rate(struct clk *clk)
+static ulong clk_sam9x5_peripheral_get_rate(struct clk *clk)
{
- struct udevice *dev;
- struct clk clk_dev;
- ulong clk_rate;
- int ret;
+ struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(clk);
+ ulong parent_rate = clk_get_parent_rate(clk);
+ u32 val, shift = ffs(periph->layout->div_mask) - 1;
- dev = dev_get_parent(clk->dev);
+ if (!parent_rate)
+ return 0;
- ret = clk_get_by_index(dev, 0, &clk_dev);
- if (ret)
- return ret;
+ pmc_write(periph->base, periph->layout->offset,
+ (periph->id & periph->layout->pid_mask));
+ pmc_read(periph->base, periph->layout->offset, &val);
+ shift = (val & periph->layout->div_mask) >> shift;
- clk_rate = clk_get_rate(&clk_dev);
+ return parent_rate >> shift;
+}
+
+static ulong clk_sam9x5_peripheral_set_rate(struct clk *clk, ulong rate)
+{
+ struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(clk);
+ ulong parent_rate = clk_get_parent_rate(clk);
+ int shift;
+
+ if (!parent_rate)
+ return 0;
+
+ if (periph->id < PERIPHERAL_ID_MIN || !periph->range.max) {
+ if (parent_rate == rate)
+ return rate;
+ else
+ return 0;
+ }
+
+ if (periph->range.max && rate > periph->range.max)
+ return 0;
+
+ for (shift = 0; shift <= PERIPHERAL_MAX_SHIFT; shift++) {
+ if (parent_rate >> shift <= rate)
+ break;
+ }
+ if (shift == PERIPHERAL_MAX_SHIFT + 1)
+ return 0;
- clk_free(&clk_dev);
+ pmc_write(periph->base, periph->layout->offset,
+ (periph->id & periph->layout->pid_mask));
+ pmc_update_bits(periph->base, periph->layout->offset,
+ periph->layout->div_mask | periph->layout->cmd,
+ (shift << (ffs(periph->layout->div_mask) - 1)) |
+ periph->layout->cmd);
- return clk_rate;
+ return parent_rate >> shift;
}
-static struct clk_ops periph_clk_ops = {
- .of_xlate = at91_clk_of_xlate,
- .enable = periph_clk_enable,
- .get_rate = periph_get_rate,
+static const struct clk_ops sam9x5_peripheral_ops = {
+ .enable = clk_sam9x5_peripheral_enable,
+ .disable = clk_sam9x5_peripheral_disable,
+ .get_rate = clk_sam9x5_peripheral_get_rate,
+ .set_rate = clk_sam9x5_peripheral_set_rate,
};
-U_BOOT_DRIVER(clk_periph) = {
- .name = "periph-clk",
- .id = UCLASS_CLK,
- .platdata_auto_alloc_size = sizeof(struct pmc_platdata),
- .probe = at91_clk_probe,
- .ops = &periph_clk_ops,
+struct clk *
+at91_clk_register_sam9x5_peripheral(void __iomem *base,
+ const struct clk_pcr_layout *layout,
+ const char *name, const char *parent_name,
+ u32 id, const struct clk_range *range)
+{
+ struct clk_sam9x5_peripheral *periph;
+ struct clk *clk;
+ int ret;
+
+ if (!base || !layout || !name || !parent_name || !range)
+ return ERR_PTR(-EINVAL);
+
+ periph = kzalloc(sizeof(*periph), GFP_KERNEL);
+ if (!periph)
+ return ERR_PTR(-ENOMEM);
+
+ periph->id = id;
+ periph->base = base;
+ periph->layout = layout;
+ periph->range = *range;
+
+ clk = &periph->clk;
+ clk->flags = CLK_GET_RATE_NOCACHE;
+ ret = clk_register(clk, UBOOT_DM_CLK_AT91_SAM9X5_PERIPH, name,
+ parent_name);
+ if (ret) {
+ kfree(periph);
+ clk = ERR_PTR(ret);
+ }
+
+ return clk;
+}
+
+U_BOOT_DRIVER(at91_sam9x5_periph_clk) = {
+ .name = UBOOT_DM_CLK_AT91_SAM9X5_PERIPH,
+ .id = UCLASS_CLK,
+ .ops = &sam9x5_peripheral_ops,
+ .flags = DM_FLAG_PRE_RELOC,
};
diff --git a/drivers/clk/at91/clk-plla.c b/drivers/clk/at91/clk-plla.c
deleted file mode 100644
index 79d725819f..0000000000
--- a/drivers/clk/at91/clk-plla.c
+++ /dev/null
@@ -1,54 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0+
-/*
- * Copyright (C) 2016 Atmel Corporation
- * Wenyou.Yang <wenyou.yang@atmel.com>
- */
-
-#include <common.h>
-#include <clk-uclass.h>
-#include <dm.h>
-#include <linux/io.h>
-#include <mach/at91_pmc.h>
-#include "pmc.h"
-
-DECLARE_GLOBAL_DATA_PTR;
-
-static int plla_clk_enable(struct clk *clk)
-{
- struct pmc_platdata *plat = dev_get_platdata(clk->dev);
- struct at91_pmc *pmc = plat->reg_base;
-
- if (readl(&pmc->sr) & AT91_PMC_LOCKA)
- return 0;
-
- return -EINVAL;
-}
-
-static ulong plla_clk_get_rate(struct clk *clk)
-{
- return gd->arch.plla_rate_hz;
-}
-
-static struct clk_ops plla_clk_ops = {
- .enable = plla_clk_enable,
- .get_rate = plla_clk_get_rate,
-};
-
-static int plla_clk_probe(struct udevice *dev)
-{
- return at91_pmc_core_probe(dev);
-}
-
-static const struct udevice_id plla_clk_match[] = {
- { .compatible = "atmel,sama5d3-clk-pll" },
- {}
-};
-
-U_BOOT_DRIVER(at91_plla_clk) = {
- .name = "at91-plla-clk",
- .id = UCLASS_CLK,
- .of_match = plla_clk_match,
- .probe = plla_clk_probe,
- .platdata_auto_alloc_size = sizeof(struct pmc_platdata),
- .ops = &plla_clk_ops,
-};
diff --git a/drivers/clk/at91/clk-plladiv.c b/drivers/clk/at91/clk-plladiv.c
deleted file mode 100644
index ca6158ef6a..0000000000
--- a/drivers/clk/at91/clk-plladiv.c
+++ /dev/null
@@ -1,85 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0+
-/*
- * Copyright (C) 2018 Microhip / Atmel Corporation
- * Wenyou.Yang <wenyou.yang@microchip.com>
- */
-
-#include <common.h>
-#include <clk-uclass.h>
-#include <dm/device.h>
-#include <linux/io.h>
-#include <mach/at91_pmc.h>
-#include "pmc.h"
-
-static int at91_plladiv_clk_enable(struct clk *clk)
-{
- return 0;
-}
-
-static ulong at91_plladiv_clk_get_rate(struct clk *clk)
-{
- struct pmc_platdata *plat = dev_get_platdata(clk->dev);
- struct at91_pmc *pmc = plat->reg_base;
- struct clk source;
- ulong clk_rate;
- int ret;
-
- ret = clk_get_by_index(clk->dev, 0, &source);
- if (ret)
- return -EINVAL;
-
- clk_rate = clk_get_rate(&source);
- if (readl(&pmc->mckr) & AT91_PMC_MCKR_PLLADIV_2)
- clk_rate /= 2;
-
- return clk_rate;
-}
-
-static ulong at91_plladiv_clk_set_rate(struct clk *clk, ulong rate)
-{
- struct pmc_platdata *plat = dev_get_platdata(clk->dev);
- struct at91_pmc *pmc = plat->reg_base;
- struct clk source;
- ulong parent_rate;
- int ret;
-
- ret = clk_get_by_index(clk->dev, 0, &source);
- if (ret)
- return -EINVAL;
-
- parent_rate = clk_get_rate(&source);
- if ((parent_rate != rate) && ((parent_rate) / 2 != rate))
- return -EINVAL;
-
- if (parent_rate != rate) {
- writel((readl(&pmc->mckr) | AT91_PMC_MCKR_PLLADIV_2),
- &pmc->mckr);
- }
-
- return 0;
-}
-
-static struct clk_ops at91_plladiv_clk_ops = {
- .enable = at91_plladiv_clk_enable,
- .get_rate = at91_plladiv_clk_get_rate,
- .set_rate = at91_plladiv_clk_set_rate,
-};
-
-static int at91_plladiv_clk_probe(struct udevice *dev)
-{
- return at91_pmc_core_probe(dev);
-}
-
-static const struct udevice_id at91_plladiv_clk_match[] = {
- { .compatible = "atmel,at91sam9x5-clk-plldiv" },
- {}
-};
-
-U_BOOT_DRIVER(at91_plladiv_clk) = {
- .name = "at91-plladiv-clk",
- .id = UCLASS_CLK,
- .of_match = at91_plladiv_clk_match,
- .probe = at91_plladiv_clk_probe,
- .platdata_auto_alloc_size = sizeof(struct pmc_platdata),
- .ops = &at91_plladiv_clk_ops,
-};
diff --git a/drivers/clk/at91/clk-programmable.c b/drivers/clk/at91/clk-programmable.c
new file mode 100644
index 0000000000..868de4b177
--- /dev/null
+++ b/drivers/clk/at91/clk-programmable.c
@@ -0,0 +1,208 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Programmable clock support for AT91 architectures.
+ *
+ * Copyright (C) 2020 Microchip Technology Inc. and its subsidiaries
+ *
+ * Author: Claudiu Beznea <claudiu.beznea@microchip.com>
+ *
+ * Based on drivers/clk/at91/clk-programmable.c from Linux.
+ */
+#include <common.h>
+#include <clk-uclass.h>
+#include <dm.h>
+#include <linux/clk-provider.h>
+#include <linux/clk/at91_pmc.h>
+
+#include "pmc.h"
+
+#define UBOOT_DM_CLK_AT91_PROG "at91-prog-clk"
+
+#define PROG_ID_MAX 7
+
+#define PROG_STATUS_MASK(id) (1 << ((id) + 8))
+#define PROG_PRES(_l, _p) (((_p) >> (_l)->pres_shift) & (_l)->pres_mask)
+#define PROG_MAX_RM9200_CSS 3
+
+struct clk_programmable {
+ void __iomem *base;
+ const u32 *clk_mux_table;
+ const u32 *mux_table;
+ const struct clk_programmable_layout *layout;
+ u32 num_parents;
+ struct clk clk;
+ u8 id;
+};
+
+#define to_clk_programmable(_c) container_of(_c, struct clk_programmable, clk)
+
+static ulong clk_programmable_get_rate(struct clk *clk)
+{
+ struct clk_programmable *prog = to_clk_programmable(clk);
+ const struct clk_programmable_layout *layout = prog->layout;
+ ulong rate, parent_rate = clk_get_parent_rate(clk);
+ unsigned int pckr;
+
+ pmc_read(prog->base, AT91_PMC_PCKR(prog->id), &pckr);
+
+ if (layout->is_pres_direct)
+ rate = parent_rate / (PROG_PRES(layout, pckr) + 1);
+ else
+ rate = parent_rate >> PROG_PRES(layout, pckr);
+
+ return rate;
+}
+
+static int clk_programmable_set_parent(struct clk *clk, struct clk *parent)
+{
+ struct clk_programmable *prog = to_clk_programmable(clk);
+ const struct clk_programmable_layout *layout = prog->layout;
+ unsigned int mask = layout->css_mask;
+ int index;
+
+ index = at91_clk_mux_val_to_index(prog->clk_mux_table,
+ prog->num_parents, parent->id);
+ if (index < 0)
+ return index;
+
+ index = at91_clk_mux_index_to_val(prog->mux_table, prog->num_parents,
+ index);
+ if (index < 0)
+ return index;
+
+ if (layout->have_slck_mck)
+ mask |= AT91_PMC_CSSMCK_MCK;
+
+ if (index > layout->css_mask) {
+ if (index > PROG_MAX_RM9200_CSS && !layout->have_slck_mck)
+ return -EINVAL;
+
+ index |= AT91_PMC_CSSMCK_MCK;
+ }
+
+ pmc_update_bits(prog->base, AT91_PMC_PCKR(prog->id), mask, index);
+
+ return 0;
+}
+
+static ulong clk_programmable_set_rate(struct clk *clk, ulong rate)
+{
+ struct clk_programmable *prog = to_clk_programmable(clk);
+ const struct clk_programmable_layout *layout = prog->layout;
+ ulong parent_rate = clk_get_parent_rate(clk);
+ ulong div = parent_rate / rate;
+ int shift = 0;
+
+ if (!parent_rate || !div)
+ return -EINVAL;
+
+ if (layout->is_pres_direct) {
+ shift = div - 1;
+
+ if (shift > layout->pres_mask)
+ return -EINVAL;
+ } else {
+ shift = fls(div) - 1;
+
+ if (div != (1 << shift))
+ return -EINVAL;
+
+ if (shift >= layout->pres_mask)
+ return -EINVAL;
+ }
+
+ pmc_update_bits(prog->base, AT91_PMC_PCKR(prog->id),
+ layout->pres_mask << layout->pres_shift,
+ shift << layout->pres_shift);
+
+ if (layout->is_pres_direct)
+ return (parent_rate / shift + 1);
+
+ return parent_rate >> shift;
+}
+
+static const struct clk_ops programmable_ops = {
+ .get_rate = clk_programmable_get_rate,
+ .set_parent = clk_programmable_set_parent,
+ .set_rate = clk_programmable_set_rate,
+};
+
+struct clk *at91_clk_register_programmable(void __iomem *base, const char *name,
+ const char *const *parent_names, u8 num_parents, u8 id,
+ const struct clk_programmable_layout *layout,
+ const u32 *clk_mux_table, const u32 *mux_table)
+{
+ struct clk_programmable *prog;
+ struct clk *clk;
+ u32 val, tmp;
+ int ret;
+
+ if (!base || !name || !parent_names || !num_parents ||
+ !layout || !clk_mux_table || !mux_table || id > PROG_ID_MAX)
+ return ERR_PTR(-EINVAL);
+
+ prog = kzalloc(sizeof(*prog), GFP_KERNEL);
+ if (!prog)
+ return ERR_PTR(-ENOMEM);
+
+ prog->id = id;
+ prog->layout = layout;
+ prog->base = base;
+ prog->clk_mux_table = clk_mux_table;
+ prog->mux_table = mux_table;
+ prog->num_parents = num_parents;
+
+ pmc_read(prog->base, AT91_PMC_PCKR(prog->id), &tmp);
+ val = tmp & prog->layout->css_mask;
+ if (layout->have_slck_mck && (tmp & AT91_PMC_CSSMCK_MCK) && !val)
+ ret = PROG_MAX_RM9200_CSS + 1;
+ else
+ ret = at91_clk_mux_val_to_index(prog->mux_table,
+ prog->num_parents, val);
+ if (ret < 0) {
+ kfree(prog);
+ return ERR_PTR(ret);
+ }
+
+ clk = &prog->clk;
+ clk->flags = CLK_GET_RATE_NOCACHE;
+ ret = clk_register(clk, UBOOT_DM_CLK_AT91_PROG, name,
+ parent_names[ret]);
+ if (ret) {
+ kfree(prog);
+ clk = ERR_PTR(ret);
+ }
+
+ return clk;
+}
+
+U_BOOT_DRIVER(at91_prog_clk) = {
+ .name = UBOOT_DM_CLK_AT91_PROG,
+ .id = UCLASS_CLK,
+ .ops = &programmable_ops,
+ .flags = DM_FLAG_PRE_RELOC,
+};
+
+const struct clk_programmable_layout at91rm9200_programmable_layout = {
+ .pres_mask = 0x7,
+ .pres_shift = 2,
+ .css_mask = 0x3,
+ .have_slck_mck = 0,
+ .is_pres_direct = 0,
+};
+
+const struct clk_programmable_layout at91sam9g45_programmable_layout = {
+ .pres_mask = 0x7,
+ .pres_shift = 2,
+ .css_mask = 0x3,
+ .have_slck_mck = 1,
+ .is_pres_direct = 0,
+};
+
+const struct clk_programmable_layout at91sam9x5_programmable_layout = {
+ .pres_mask = 0x7,
+ .pres_shift = 4,
+ .css_mask = 0x7,
+ .have_slck_mck = 0,
+ .is_pres_direct = 0,
+};
diff --git a/drivers/clk/at91/clk-sam9x60-pll.c b/drivers/clk/at91/clk-sam9x60-pll.c
new file mode 100644
index 0000000000..1bfae5fd01
--- /dev/null
+++ b/drivers/clk/at91/clk-sam9x60-pll.c
@@ -0,0 +1,442 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * SAM9X60's PLL clock support.
+ *
+ * Copyright (C) 2020 Microchip Technology Inc. and its subsidiaries
+ *
+ * Author: Claudiu Beznea <claudiu.beznea@microchip.com>
+ *
+ * Based on drivers/clk/at91/clk-sam9x60-pll.c from Linux.
+ *
+ */
+
+#include <asm/processor.h>
+#include <common.h>
+#include <clk-uclass.h>
+#include <div64.h>
+#include <dm.h>
+#include <linux/clk-provider.h>
+#include <linux/clk/at91_pmc.h>
+#include <linux/delay.h>
+
+#include "pmc.h"
+
+#define UBOOT_DM_CLK_AT91_SAM9X60_DIV_PLL "at91-sam9x60-div-pll-clk"
+#define UBOOT_DM_CLK_AT91_SAM9X60_FRAC_PLL "at91-sam9x60-frac-pll-clk"
+
+#define PMC_PLL_CTRL0_DIV_MSK GENMASK(7, 0)
+#define PMC_PLL_CTRL1_MUL_MSK GENMASK(31, 24)
+#define PMC_PLL_CTRL1_FRACR_MSK GENMASK(21, 0)
+
+#define PLL_DIV_MAX (FIELD_GET(PMC_PLL_CTRL0_DIV_MSK, UINT_MAX) + 1)
+#define UPLL_DIV 2
+#define PLL_MUL_MAX (FIELD_GET(PMC_PLL_CTRL1_MUL_MSK, UINT_MAX) + 1)
+
+#define FCORE_MIN (600000000)
+#define FCORE_MAX (1200000000)
+
+#define PLL_MAX_ID 7
+
+struct sam9x60_pll {
+ void __iomem *base;
+ const struct clk_pll_characteristics *characteristics;
+ const struct clk_pll_layout *layout;
+ struct clk clk;
+ u8 id;
+};
+
+#define to_sam9x60_pll(_clk) container_of(_clk, struct sam9x60_pll, clk)
+
+static inline bool sam9x60_pll_ready(void __iomem *base, int id)
+{
+ unsigned int status;
+
+ pmc_read(base, AT91_PMC_PLL_ISR0, &status);
+
+ return !!(status & BIT(id));
+}
+
+static long sam9x60_frac_pll_compute_mul_frac(u32 *mul, u32 *frac, ulong rate,
+ ulong parent_rate)
+{
+ unsigned long tmprate, remainder;
+ unsigned long nmul = 0;
+ unsigned long nfrac = 0;
+
+ if (rate < FCORE_MIN || rate > FCORE_MAX)
+ return -ERANGE;
+
+ /*
+ * Calculate the multiplier associated with the current
+ * divider that provide the closest rate to the requested one.
+ */
+ nmul = mult_frac(rate, 1, parent_rate);
+ tmprate = mult_frac(parent_rate, nmul, 1);
+ remainder = rate - tmprate;
+
+ if (remainder) {
+ nfrac = DIV_ROUND_CLOSEST_ULL((u64)remainder * (1 << 22),
+ parent_rate);
+
+ tmprate += DIV_ROUND_CLOSEST_ULL((u64)nfrac * parent_rate,
+ (1 << 22));
+ }
+
+ /* Check if resulted rate is valid. */
+ if (tmprate < FCORE_MIN || tmprate > FCORE_MAX)
+ return -ERANGE;
+
+ *mul = nmul - 1;
+ *frac = nfrac;
+
+ return tmprate;
+}
+
+static ulong sam9x60_frac_pll_set_rate(struct clk *clk, ulong rate)
+{
+ struct sam9x60_pll *pll = to_sam9x60_pll(clk);
+ void __iomem *base = pll->base;
+ ulong parent_rate = clk_get_parent_rate(clk);
+ u32 nmul, cmul, nfrac, cfrac, val;
+ bool ready = sam9x60_pll_ready(base, pll->id);
+ long ret;
+
+ if (!parent_rate)
+ return 0;
+
+ ret = sam9x60_frac_pll_compute_mul_frac(&nmul, &nfrac, rate,
+ parent_rate);
+ if (ret < 0)
+ return 0;
+
+ pmc_update_bits(base, AT91_PMC_PLL_UPDT, AT91_PMC_PLL_UPDT_ID_MSK,
+ pll->id);
+ pmc_read(base, AT91_PMC_PLL_CTRL1, &val);
+ cmul = (val & pll->layout->mul_mask) >> pll->layout->mul_shift;
+ cfrac = (val & pll->layout->frac_mask) >> pll->layout->frac_shift;
+
+ /* Check against current values. */
+ if (sam9x60_pll_ready(base, pll->id) &&
+ nmul == cmul && nfrac == cfrac)
+ return 0;
+
+ /* Update it to hardware. */
+ pmc_write(base, AT91_PMC_PLL_CTRL1,
+ (nmul << pll->layout->mul_shift) |
+ (nfrac << pll->layout->frac_shift));
+
+ pmc_update_bits(base, AT91_PMC_PLL_UPDT,
+ AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK,
+ AT91_PMC_PLL_UPDT_UPDATE | pll->id);
+
+ while (ready && !sam9x60_pll_ready(base, pll->id)) {
+ debug("waiting for pll %u...\n", pll->id);
+ cpu_relax();
+ }
+
+ return parent_rate * (nmul + 1) + ((u64)parent_rate * nfrac >> 22);
+}
+
+static ulong sam9x60_frac_pll_get_rate(struct clk *clk)
+{
+ struct sam9x60_pll *pll = to_sam9x60_pll(clk);
+ void __iomem *base = pll->base;
+ ulong parent_rate = clk_get_parent_rate(clk);
+ u32 mul, frac, val;
+
+ if (!parent_rate)
+ return 0;
+
+ pmc_update_bits(base, AT91_PMC_PLL_UPDT, AT91_PMC_PLL_UPDT_ID_MSK,
+ pll->id);
+ pmc_read(base, AT91_PMC_PLL_CTRL1, &val);
+ mul = (val & pll->layout->mul_mask) >> pll->layout->mul_shift;
+ frac = (val & pll->layout->frac_mask) >> pll->layout->frac_shift;
+
+ return (parent_rate * (mul + 1) + ((u64)parent_rate * frac >> 22));
+}
+
+static int sam9x60_frac_pll_enable(struct clk *clk)
+{
+ struct sam9x60_pll *pll = to_sam9x60_pll(clk);
+ void __iomem *base = pll->base;
+ unsigned int val;
+ ulong crate;
+
+ crate = sam9x60_frac_pll_get_rate(clk);
+ if (crate < FCORE_MIN || crate > FCORE_MAX)
+ return -ERANGE;
+
+ pmc_update_bits(base, AT91_PMC_PLL_UPDT, AT91_PMC_PLL_UPDT_ID_MSK,
+ pll->id);
+ pmc_read(base, AT91_PMC_PLL_CTRL1, &val);
+
+ if (sam9x60_pll_ready(base, pll->id))
+ return 0;
+
+ pmc_update_bits(base, AT91_PMC_PLL_UPDT,
+ AT91_PMC_PMM_UPDT_STUPTIM_MSK |
+ AT91_PMC_PLL_UPDT_ID_MSK,
+ AT91_PMC_PLL_UPDT_STUPTIM(0x3f) | pll->id);
+
+ /* Recommended value for AT91_PMC_PLL_ACR */
+ if (pll->characteristics->upll)
+ val = AT91_PMC_PLL_ACR_DEFAULT_UPLL;
+ else
+ val = AT91_PMC_PLL_ACR_DEFAULT_PLLA;
+ pmc_write(base, AT91_PMC_PLL_ACR, val);
+
+ if (pll->characteristics->upll) {
+ /* Enable the UTMI internal bandgap */
+ val |= AT91_PMC_PLL_ACR_UTMIBG;
+ pmc_write(base, AT91_PMC_PLL_ACR, val);
+
+ udelay(10);
+
+ /* Enable the UTMI internal regulator */
+ val |= AT91_PMC_PLL_ACR_UTMIVR;
+ pmc_write(base, AT91_PMC_PLL_ACR, val);
+
+ udelay(10);
+
+ pmc_update_bits(base, AT91_PMC_PLL_UPDT,
+ AT91_PMC_PLL_UPDT_UPDATE |
+ AT91_PMC_PLL_UPDT_ID_MSK,
+ AT91_PMC_PLL_UPDT_UPDATE | pll->id);
+ }
+
+ pmc_update_bits(base, AT91_PMC_PLL_CTRL0,
+ AT91_PMC_PLL_CTRL0_ENLOCK | AT91_PMC_PLL_CTRL0_ENPLL,
+ AT91_PMC_PLL_CTRL0_ENLOCK | AT91_PMC_PLL_CTRL0_ENPLL);
+
+ pmc_update_bits(base, AT91_PMC_PLL_UPDT,
+ AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK,
+ AT91_PMC_PLL_UPDT_UPDATE | pll->id);
+
+ while (!sam9x60_pll_ready(base, pll->id)) {
+ debug("waiting for pll %u...\n", pll->id);
+ cpu_relax();
+ }
+
+ return 0;
+}
+
+static int sam9x60_frac_pll_disable(struct clk *clk)
+{
+ struct sam9x60_pll *pll = to_sam9x60_pll(clk);
+ void __iomem *base = pll->base;
+
+ pmc_update_bits(base, AT91_PMC_PLL_UPDT, AT91_PMC_PLL_UPDT_ID_MSK,
+ pll->id);
+
+ pmc_update_bits(base, AT91_PMC_PLL_CTRL0,
+ AT91_PMC_PLL_CTRL0_ENPLL, 0);
+
+ if (pll->characteristics->upll)
+ pmc_update_bits(base, AT91_PMC_PLL_ACR,
+ AT91_PMC_PLL_ACR_UTMIBG |
+ AT91_PMC_PLL_ACR_UTMIVR, 0);
+
+ pmc_update_bits(base, AT91_PMC_PLL_UPDT,
+ AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK,
+ AT91_PMC_PLL_UPDT_UPDATE | pll->id);
+
+ return 0;
+}
+
+static const struct clk_ops sam9x60_frac_pll_ops = {
+ .enable = sam9x60_frac_pll_enable,
+ .disable = sam9x60_frac_pll_disable,
+ .set_rate = sam9x60_frac_pll_set_rate,
+ .get_rate = sam9x60_frac_pll_get_rate,
+};
+
+static int sam9x60_div_pll_enable(struct clk *clk)
+{
+ struct sam9x60_pll *pll = to_sam9x60_pll(clk);
+ void __iomem *base = pll->base;
+ unsigned int val;
+
+ pmc_update_bits(base, AT91_PMC_PLL_UPDT, AT91_PMC_PLL_UPDT_ID_MSK,
+ pll->id);
+ pmc_read(base, AT91_PMC_PLL_CTRL0, &val);
+
+ /* Stop if enabled. */
+ if (val & pll->layout->endiv_mask)
+ return 0;
+
+ pmc_update_bits(base, AT91_PMC_PLL_CTRL0,
+ pll->layout->endiv_mask,
+ (1 << pll->layout->endiv_shift));
+
+ pmc_update_bits(base, AT91_PMC_PLL_UPDT,
+ AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK,
+ AT91_PMC_PLL_UPDT_UPDATE | pll->id);
+
+ while (!sam9x60_pll_ready(base, pll->id)) {
+ debug("waiting for pll %u...\n", pll->id);
+ cpu_relax();
+ }
+
+ return 0;
+}
+
+static int sam9x60_div_pll_disable(struct clk *clk)
+{
+ struct sam9x60_pll *pll = to_sam9x60_pll(clk);
+ void __iomem *base = pll->base;
+
+ pmc_update_bits(base, AT91_PMC_PLL_UPDT, AT91_PMC_PLL_UPDT_ID_MSK,
+ pll->id);
+
+ pmc_update_bits(base, AT91_PMC_PLL_CTRL0,
+ pll->layout->endiv_mask, 0);
+
+ pmc_update_bits(base, AT91_PMC_PLL_UPDT,
+ AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK,
+ AT91_PMC_PLL_UPDT_UPDATE | pll->id);
+
+ return 0;
+}
+
+static ulong sam9x60_div_pll_set_rate(struct clk *clk, ulong rate)
+{
+ struct sam9x60_pll *pll = to_sam9x60_pll(clk);
+ void __iomem *base = pll->base;
+ const struct clk_pll_characteristics *characteristics =
+ pll->characteristics;
+ ulong parent_rate = clk_get_parent_rate(clk);
+ u8 div = DIV_ROUND_CLOSEST_ULL(parent_rate, rate) - 1;
+ ulong req_rate = parent_rate / (div + 1);
+ bool ready = sam9x60_pll_ready(base, pll->id);
+ u32 val;
+
+ if (!parent_rate || div > pll->layout->div_mask ||
+ req_rate < characteristics->output[0].min ||
+ req_rate > characteristics->output[0].max)
+ return 0;
+
+ pmc_update_bits(base, AT91_PMC_PLL_UPDT, AT91_PMC_PLL_UPDT_ID_MSK,
+ pll->id);
+ pmc_read(base, AT91_PMC_PLL_CTRL0, &val);
+ /* Compare against current value. */
+ if (div == ((val & pll->layout->div_mask) >> pll->layout->div_shift))
+ return 0;
+
+ /* Update it to hardware. */
+ pmc_update_bits(base, AT91_PMC_PLL_CTRL0,
+ pll->layout->div_mask,
+ div << pll->layout->div_shift);
+
+ pmc_update_bits(base, AT91_PMC_PLL_UPDT,
+ AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK,
+ AT91_PMC_PLL_UPDT_UPDATE | pll->id);
+
+ while (ready && !sam9x60_pll_ready(base, pll->id)) {
+ debug("waiting for pll %u...\n", pll->id);
+ cpu_relax();
+ }
+
+ return req_rate;
+}
+
+static ulong sam9x60_div_pll_get_rate(struct clk *clk)
+{
+ struct sam9x60_pll *pll = to_sam9x60_pll(clk);
+ void __iomem *base = pll->base;
+ ulong parent_rate = clk_get_parent_rate(clk);
+ u32 val;
+ u8 div;
+
+ if (!parent_rate)
+ return 0;
+
+ pmc_update_bits(base, AT91_PMC_PLL_UPDT, AT91_PMC_PLL_UPDT_ID_MSK,
+ pll->id);
+
+ pmc_read(base, AT91_PMC_PLL_CTRL0, &val);
+
+ div = (val & pll->layout->div_mask) >> pll->layout->div_shift;
+
+ return parent_rate / (div + 1);
+}
+
+static const struct clk_ops sam9x60_div_pll_ops = {
+ .enable = sam9x60_div_pll_enable,
+ .disable = sam9x60_div_pll_disable,
+ .set_rate = sam9x60_div_pll_set_rate,
+ .get_rate = sam9x60_div_pll_get_rate,
+};
+
+static struct clk *
+sam9x60_clk_register_pll(void __iomem *base, const char *type,
+ const char *name, const char *parent_name, u8 id,
+ const struct clk_pll_characteristics *characteristics,
+ const struct clk_pll_layout *layout, u32 flags)
+{
+ struct sam9x60_pll *pll;
+ struct clk *clk;
+ int ret;
+
+ if (!base || !type || !name || !parent_name || !characteristics ||
+ !layout || id > PLL_MAX_ID)
+ return ERR_PTR(-EINVAL);
+
+ pll = kzalloc(sizeof(*pll), GFP_KERNEL);
+ if (!pll)
+ return ERR_PTR(-ENOMEM);
+
+ pll->id = id;
+ pll->characteristics = characteristics;
+ pll->layout = layout;
+ pll->base = base;
+ clk = &pll->clk;
+ clk->flags = flags;
+
+ ret = clk_register(clk, type, name, parent_name);
+ if (ret) {
+ kfree(pll);
+ clk = ERR_PTR(ret);
+ }
+
+ return clk;
+}
+
+struct clk *
+sam9x60_clk_register_div_pll(void __iomem *base, const char *name,
+ const char *parent_name, u8 id,
+ const struct clk_pll_characteristics *characteristics,
+ const struct clk_pll_layout *layout, bool critical)
+{
+ return sam9x60_clk_register_pll(base,
+ UBOOT_DM_CLK_AT91_SAM9X60_DIV_PLL, name, parent_name, id,
+ characteristics, layout,
+ CLK_GET_RATE_NOCACHE | (critical ? CLK_IS_CRITICAL : 0));
+}
+
+struct clk *
+sam9x60_clk_register_frac_pll(void __iomem *base, const char *name,
+ const char *parent_name, u8 id,
+ const struct clk_pll_characteristics *characteristics,
+ const struct clk_pll_layout *layout, bool critical)
+{
+ return sam9x60_clk_register_pll(base,
+ UBOOT_DM_CLK_AT91_SAM9X60_FRAC_PLL, name, parent_name, id,
+ characteristics, layout,
+ CLK_GET_RATE_NOCACHE | (critical ? CLK_IS_CRITICAL : 0));
+}
+
+U_BOOT_DRIVER(at91_sam9x60_div_pll_clk) = {
+ .name = UBOOT_DM_CLK_AT91_SAM9X60_DIV_PLL,
+ .id = UCLASS_CLK,
+ .ops = &sam9x60_div_pll_ops,
+ .flags = DM_FLAG_PRE_RELOC,
+};
+
+U_BOOT_DRIVER(at91_sam9x60_frac_pll_clk) = {
+ .name = UBOOT_DM_CLK_AT91_SAM9X60_FRAC_PLL,
+ .id = UCLASS_CLK,
+ .ops = &sam9x60_frac_pll_ops,
+ .flags = DM_FLAG_PRE_RELOC,
+};
+
diff --git a/drivers/clk/at91/clk-slow.c b/drivers/clk/at91/clk-slow.c
deleted file mode 100644
index 1f8665768b..0000000000
--- a/drivers/clk/at91/clk-slow.c
+++ /dev/null
@@ -1,36 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0+
-/*
- * Copyright (C) 2016 Atmel Corporation
- * Wenyou.Yang <wenyou.yang@atmel.com>
- */
-
-#include <common.h>
-#include <clk-uclass.h>
-#include <dm.h>
-
-static int at91_slow_clk_enable(struct clk *clk)
-{
- return 0;
-}
-
-static ulong at91_slow_clk_get_rate(struct clk *clk)
-{
- return CONFIG_SYS_AT91_SLOW_CLOCK;
-}
-
-static struct clk_ops at91_slow_clk_ops = {
- .enable = at91_slow_clk_enable,
- .get_rate = at91_slow_clk_get_rate,
-};
-
-static const struct udevice_id at91_slow_clk_match[] = {
- { .compatible = "atmel,at91sam9x5-clk-slow" },
- {}
-};
-
-U_BOOT_DRIVER(at91_slow_clk) = {
- .name = "at91-slow-clk",
- .id = UCLASS_CLK,
- .of_match = at91_slow_clk_match,
- .ops = &at91_slow_clk_ops,
-};
diff --git a/drivers/clk/at91/clk-system.c b/drivers/clk/at91/clk-system.c
index 76b1958670..82f79e74a1 100644
--- a/drivers/clk/at91/clk-system.c
+++ b/drivers/clk/at91/clk-system.c
@@ -1,111 +1,112 @@
// SPDX-License-Identifier: GPL-2.0+
/*
- * Copyright (C) 2016 Atmel Corporation
- * Wenyou.Yang <wenyou.yang@atmel.com>
+ * System clock support for AT91 architectures.
+ *
+ * Copyright (C) Microchip Technology Inc. and its subsidiaries
+ *
+ * Author: Claudiu Beznea <claudiu.beznea@microchip.com>
+ *
+ * Based on drivers/clk/at91/clk-system.c from Linux.
*/
-
+#include <asm/processor.h>
#include <common.h>
#include <clk-uclass.h>
#include <dm.h>
-#include <linux/bitops.h>
#include <linux/io.h>
-#include <mach/at91_pmc.h>
+#include <linux/clk-provider.h>
+#include <linux/clk/at91_pmc.h>
+
#include "pmc.h"
-#define SYSTEM_MAX_ID 31
+#define UBOOT_DM_CLK_AT91_SYSTEM "at91-system-clk"
-/**
- * at91_system_clk_bind() - for the system clock driver
- * Recursively bind its children as clk devices.
- *
- * @return: 0 on success, or negative error code on failure
- */
-static int at91_system_clk_bind(struct udevice *dev)
-{
- return at91_clk_sub_device_bind(dev, "system-clk");
-}
-
-static const struct udevice_id at91_system_clk_match[] = {
- { .compatible = "atmel,at91rm9200-clk-system" },
- {}
-};
+#define SYSTEM_MAX_ID 31
-U_BOOT_DRIVER(at91_system_clk) = {
- .name = "at91-system-clk",
- .id = UCLASS_MISC,
- .of_match = at91_system_clk_match,
- .bind = at91_system_clk_bind,
+struct clk_system {
+ void __iomem *base;
+ struct clk clk;
+ u8 id;
};
-/*----------------------------------------------------------*/
+#define to_clk_system(_c) container_of(_c, struct clk_system, clk)
static inline int is_pck(int id)
{
return (id >= 8) && (id <= 15);
}
-static ulong system_clk_get_rate(struct clk *clk)
+static inline bool clk_system_ready(void __iomem *base, int id)
{
- struct clk clk_dev;
- int ret;
+ unsigned int status;
- ret = clk_get_by_index(clk->dev, 0, &clk_dev);
- if (ret)
- return -EINVAL;
+ pmc_read(base, AT91_PMC_SR, &status);
- return clk_get_rate(&clk_dev);
+ return !!(status & (1 << id));
}
-static ulong system_clk_set_rate(struct clk *clk, ulong rate)
+static int clk_system_enable(struct clk *clk)
{
- struct clk clk_dev;
- int ret;
+ struct clk_system *sys = to_clk_system(clk);
- ret = clk_get_by_index(clk->dev, 0, &clk_dev);
- if (ret)
- return -EINVAL;
+ pmc_write(sys->base, AT91_PMC_SCER, 1 << sys->id);
- return clk_set_rate(&clk_dev, rate);
+ if (!is_pck(sys->id))
+ return 0;
+
+ while (!clk_system_ready(sys->base, sys->id)) {
+ debug("waiting for pck%u\n", sys->id);
+ cpu_relax();
+ }
+
+ return 0;
}
-static int system_clk_enable(struct clk *clk)
+static int clk_system_disable(struct clk *clk)
{
- struct pmc_platdata *plat = dev_get_platdata(clk->dev);
- struct at91_pmc *pmc = plat->reg_base;
- u32 mask;
+ struct clk_system *sys = to_clk_system(clk);
- if (clk->id > SYSTEM_MAX_ID)
- return -EINVAL;
+ pmc_write(sys->base, AT91_PMC_SCDR, 1 << sys->id);
- mask = BIT(clk->id);
+ return 0;
+}
- writel(mask, &pmc->scer);
+static const struct clk_ops system_ops = {
+ .enable = clk_system_enable,
+ .disable = clk_system_disable,
+ .get_rate = clk_generic_get_rate,
+};
- /**
- * For the programmable clocks the Ready status in the PMC
- * status register should be checked after enabling.
- * For other clocks this is unnecessary.
- */
- if (!is_pck(clk->id))
- return 0;
+struct clk *at91_clk_register_system(void __iomem *base, const char *name,
+ const char *parent_name, u8 id)
+{
+ struct clk_system *sys;
+ struct clk *clk;
+ int ret;
- while (!(readl(&pmc->sr) & mask))
- ;
+ if (!base || !name || !parent_name || id > SYSTEM_MAX_ID)
+ return ERR_PTR(-EINVAL);
- return 0;
-}
+ sys = kzalloc(sizeof(*sys), GFP_KERNEL);
+ if (!sys)
+ return ERR_PTR(-ENOMEM);
-static struct clk_ops system_clk_ops = {
- .of_xlate = at91_clk_of_xlate,
- .get_rate = system_clk_get_rate,
- .set_rate = system_clk_set_rate,
- .enable = system_clk_enable,
-};
+ sys->id = id;
+ sys->base = base;
-U_BOOT_DRIVER(system_clk) = {
- .name = "system-clk",
+ clk = &sys->clk;
+ clk->flags = CLK_GET_RATE_NOCACHE;
+ ret = clk_register(clk, UBOOT_DM_CLK_AT91_SYSTEM, name, parent_name);
+ if (ret) {
+ kfree(sys);
+ clk = ERR_PTR(ret);
+ }
+
+ return clk;
+}
+
+U_BOOT_DRIVER(at91_system_clk) = {
+ .name = UBOOT_DM_CLK_AT91_SYSTEM,
.id = UCLASS_CLK,
- .probe = at91_clk_probe,
- .platdata_auto_alloc_size = sizeof(struct pmc_platdata),
- .ops = &system_clk_ops,
+ .ops = &system_ops,
+ .flags = DM_FLAG_PRE_RELOC,
};
diff --git a/drivers/clk/at91/clk-usb.c b/drivers/clk/at91/clk-usb.c
deleted file mode 100644
index af9d724369..0000000000
--- a/drivers/clk/at91/clk-usb.c
+++ /dev/null
@@ -1,147 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0+
-/*
- * Copyright (C) 2018 Microhip / Atmel Corporation
- * Wenyou.Yang <wenyou.yang@microchip.com>
- */
-
-#include <common.h>
-#include <clk-uclass.h>
-#include <log.h>
-#include <dm/device.h>
-#include <linux/err.h>
-#include <linux/io.h>
-#include <mach/at91_pmc.h>
-#include "pmc.h"
-
-DECLARE_GLOBAL_DATA_PTR;
-
-#define AT91_USB_CLK_SOURCE_MAX 2
-#define AT91_USB_CLK_MAX_DIV 15
-
-struct at91_usb_clk_priv {
- u32 num_clksource;
-};
-
-static ulong at91_usb_clk_get_rate(struct clk *clk)
-{
- struct pmc_platdata *plat = dev_get_platdata(clk->dev);
- struct at91_pmc *pmc = plat->reg_base;
- struct clk source;
- u32 tmp, usbdiv;
- u8 source_index;
- int ret;
-
- tmp = readl(&pmc->pcr);
- source_index = (tmp >> AT91_PMC_USB_USBS_OFFSET) &
- AT91_PMC_USB_USBS_MASK;
- usbdiv = (tmp >> AT91_PMC_USB_DIV_OFFSET) & AT91_PMC_USB_DIV_MASK;
-
- ret = clk_get_by_index(clk->dev, source_index, &source);
- if (ret)
- return 0;
-
- return clk_get_rate(&source) / (usbdiv + 1);
-}
-
-static ulong at91_usb_clk_set_rate(struct clk *clk, ulong rate)
-{
- struct pmc_platdata *plat = dev_get_platdata(clk->dev);
- struct at91_pmc *pmc = plat->reg_base;
- struct at91_usb_clk_priv *priv = dev_get_priv(clk->dev);
- struct clk source, best_source;
- ulong tmp_rate, best_rate = rate, source_rate;
- int tmp_diff, best_diff = -1;
- u32 div, best_div = 0;
- u8 best_source_index = 0;
- u8 i;
- u32 tmp;
- int ret;
-
- for (i = 0; i < priv->num_clksource; i++) {
- ret = clk_get_by_index(clk->dev, i, &source);
- if (ret)
- return ret;
-
- source_rate = clk_get_rate(&source);
- if (IS_ERR_VALUE(source_rate))
- return source_rate;
-
- for (div = 1; div < AT91_USB_CLK_MAX_DIV + 2; div++) {
- tmp_rate = DIV_ROUND_CLOSEST(source_rate, div);
- tmp_diff = abs(rate - tmp_rate);
-
- if (best_diff < 0 || best_diff > tmp_diff) {
- best_rate = tmp_rate;
- best_diff = tmp_diff;
-
- best_div = div - 1;
- best_source = source;
- best_source_index = i;
- }
-
- if (!best_diff || tmp_rate < rate)
- break;
- }
-
- if (!best_diff)
- break;
- }
-
- debug("AT91 USB: best sourc: %s, best_rate = %ld, best_div = %d\n",
- best_source.dev->name, best_rate, best_div);
-
- ret = clk_enable(&best_source);
- if (ret)
- return ret;
-
- tmp = AT91_PMC_USB_USBS_(best_source_index) |
- AT91_PMC_USB_DIV_(best_div);
- writel(tmp, &pmc->usb);
-
- return 0;
-}
-
-static struct clk_ops at91_usb_clk_ops = {
- .get_rate = at91_usb_clk_get_rate,
- .set_rate = at91_usb_clk_set_rate,
-};
-
-static int at91_usb_clk_ofdata_to_platdata(struct udevice *dev)
-{
- struct at91_usb_clk_priv *priv = dev_get_priv(dev);
- u32 cells[AT91_USB_CLK_SOURCE_MAX];
- u32 num_clksource;
-
- num_clksource = fdtdec_get_int_array_count(gd->fdt_blob,
- dev_of_offset(dev),
- "clocks", cells,
- AT91_USB_CLK_SOURCE_MAX);
-
- if (!num_clksource)
- return -1;
-
- priv->num_clksource = num_clksource;
-
- return 0;
-}
-
-static int at91_usb_clk_probe(struct udevice *dev)
-{
- return at91_pmc_core_probe(dev);
-}
-
-static const struct udevice_id at91_usb_clk_match[] = {
- { .compatible = "atmel,at91sam9x5-clk-usb" },
- {}
-};
-
-U_BOOT_DRIVER(at91_usb_clk) = {
- .name = "at91-usb-clk",
- .id = UCLASS_CLK,
- .of_match = at91_usb_clk_match,
- .probe = at91_usb_clk_probe,
- .ofdata_to_platdata = at91_usb_clk_ofdata_to_platdata,
- .priv_auto_alloc_size = sizeof(struct at91_usb_clk_priv),
- .platdata_auto_alloc_size = sizeof(struct pmc_platdata),
- .ops = &at91_usb_clk_ops,
-};
diff --git a/drivers/clk/at91/clk-utmi.c b/drivers/clk/at91/clk-utmi.c
index 18af0bfeaa..7c8bcfb51d 100644
--- a/drivers/clk/at91/clk-utmi.c
+++ b/drivers/clk/at91/clk-utmi.c
@@ -1,49 +1,65 @@
// SPDX-License-Identifier: GPL-2.0+
/*
- * Copyright (C) 2016 Atmel Corporation
- * Wenyou.Yang <wenyou.yang@atmel.com>
+ * UTMI clock support for AT91 architectures.
+ *
+ * Copyright (C) 2020 Microchip Technology Inc. and its subsidiaries
+ *
+ * Author: Claudiu Beznea <claudiu.beznea@microchip.com>
+ *
+ * Based on drivers/clk/at91/clk-utmi.c from Linux.
*/
-
+#include <asm/processor.h>
#include <common.h>
#include <clk-uclass.h>
#include <dm.h>
-#include <syscon.h>
-#include <linux/io.h>
-#include <mach/at91_pmc.h>
+#include <linux/clk-provider.h>
+#include <linux/clk/at91_pmc.h>
#include <mach/at91_sfr.h>
+#include <regmap.h>
+#include <syscon.h>
+
#include "pmc.h"
+#define UBOOT_DM_CLK_AT91_UTMI "at91-utmi-clk"
+#define UBOOT_DM_CLK_AT91_SAMA7G5_UTMI "at91-sama7g5-utmi-clk"
+
/*
* The purpose of this clock is to generate a 480 MHz signal. A different
* rate can't be configured.
*/
#define UTMI_RATE 480000000
-static int utmi_clk_enable(struct clk *clk)
+struct clk_utmi {
+ void __iomem *base;
+ struct regmap *regmap_sfr;
+ struct clk clk;
+};
+
+#define to_clk_utmi(_clk) container_of(_clk, struct clk_utmi, clk)
+
+static inline bool clk_utmi_ready(struct regmap *regmap)
+{
+ unsigned int status;
+
+ pmc_read(regmap, AT91_PMC_SR, &status);
+
+ return !!(status & AT91_PMC_LOCKU);
+}
+
+static int clk_utmi_enable(struct clk *clk)
{
- struct pmc_platdata *plat = dev_get_platdata(clk->dev);
- struct at91_pmc *pmc = plat->reg_base;
- struct clk clk_dev;
- ulong clk_rate;
- u32 utmi_ref_clk_freq;
- u32 tmp;
- int err;
- int timeout = 2000000;
-
- if (readl(&pmc->sr) & AT91_PMC_LOCKU)
- return 0;
+ struct clk_utmi *utmi = to_clk_utmi(clk);
+ unsigned int uckr = AT91_PMC_UPLLEN | AT91_PMC_UPLLCOUNT |
+ AT91_PMC_BIASEN;
+ unsigned int utmi_ref_clk_freq;
+ ulong parent_rate = clk_get_parent_rate(clk);
/*
* If mainck rate is different from 12 MHz, we have to configure the
* FREQ field of the SFR_UTMICKTRIM register to generate properly
* the utmi clock.
*/
- err = clk_get_by_index(clk->dev, 0, &clk_dev);
- if (err)
- return -EINVAL;
-
- clk_rate = clk_get_rate(&clk_dev);
- switch (clk_rate) {
+ switch (parent_rate) {
case 12000000:
utmi_ref_clk_freq = 0;
break;
@@ -61,82 +77,158 @@ static int utmi_clk_enable(struct clk *clk)
utmi_ref_clk_freq = 3;
break;
default:
- printf("UTMICK: unsupported mainck rate\n");
+ debug("UTMICK: unsupported mainck rate\n");
return -EINVAL;
}
- if (plat->regmap_sfr) {
- err = regmap_read(plat->regmap_sfr, AT91_SFR_UTMICKTRIM, &tmp);
- if (err)
- return -EINVAL;
-
- tmp &= ~AT91_UTMICKTRIM_FREQ;
- tmp |= utmi_ref_clk_freq;
- err = regmap_write(plat->regmap_sfr, AT91_SFR_UTMICKTRIM, tmp);
- if (err)
- return -EINVAL;
+ if (utmi->regmap_sfr) {
+ regmap_update_bits(utmi->regmap_sfr, AT91_SFR_UTMICKTRIM,
+ AT91_UTMICKTRIM_FREQ, utmi_ref_clk_freq);
} else if (utmi_ref_clk_freq) {
- printf("UTMICK: sfr node required\n");
+ debug("UTMICK: sfr node required\n");
return -EINVAL;
}
- tmp = readl(&pmc->uckr);
- tmp |= AT91_PMC_UPLLEN |
- AT91_PMC_UPLLCOUNT |
- AT91_PMC_BIASEN;
- writel(tmp, &pmc->uckr);
-
- while ((--timeout) && !(readl(&pmc->sr) & AT91_PMC_LOCKU))
- ;
- if (!timeout) {
- printf("UTMICK: timeout waiting for UPLL lock\n");
- return -ETIMEDOUT;
+ pmc_update_bits(utmi->base, AT91_CKGR_UCKR, uckr, uckr);
+
+ while (!clk_utmi_ready(utmi->base)) {
+ debug("waiting for utmi...\n");
+ cpu_relax();
}
return 0;
}
-static ulong utmi_clk_get_rate(struct clk *clk)
+static int clk_utmi_disable(struct clk *clk)
+{
+ struct clk_utmi *utmi = to_clk_utmi(clk);
+
+ pmc_update_bits(utmi->base, AT91_CKGR_UCKR, AT91_PMC_UPLLEN, 0);
+
+ return 0;
+}
+
+static ulong clk_utmi_get_rate(struct clk *clk)
{
/* UTMI clk rate is fixed. */
return UTMI_RATE;
}
-static struct clk_ops utmi_clk_ops = {
- .enable = utmi_clk_enable,
- .get_rate = utmi_clk_get_rate,
+static const struct clk_ops utmi_ops = {
+ .enable = clk_utmi_enable,
+ .disable = clk_utmi_disable,
+ .get_rate = clk_utmi_get_rate,
};
-static int utmi_clk_ofdata_to_platdata(struct udevice *dev)
+struct clk *at91_clk_register_utmi(void __iomem *base, struct udevice *dev,
+ const char *name, const char *parent_name)
{
- struct pmc_platdata *plat = dev_get_platdata(dev);
struct udevice *syscon;
+ struct clk_utmi *utmi;
+ struct clk *clk;
+ int ret;
+
+ if (!base || !dev || !name || !parent_name)
+ return ERR_PTR(-EINVAL);
+
+ ret = uclass_get_device_by_phandle(UCLASS_SYSCON, dev,
+ "regmap-sfr", &syscon);
+ if (ret)
+ return ERR_PTR(ret);
+
+ utmi = kzalloc(sizeof(*utmi), GFP_KERNEL);
+ if (!utmi)
+ return ERR_PTR(-ENOMEM);
+
+ utmi->base = base;
+ utmi->regmap_sfr = syscon_get_regmap(syscon);
+ if (!utmi->regmap_sfr) {
+ kfree(utmi);
+ return ERR_PTR(-ENODEV);
+ }
- uclass_get_device_by_phandle(UCLASS_SYSCON, dev,
- "regmap-sfr", &syscon);
-
- if (syscon)
- plat->regmap_sfr = syscon_get_regmap(syscon);
+ clk = &utmi->clk;
+ clk->flags = CLK_GET_RATE_NOCACHE;
+ ret = clk_register(clk, UBOOT_DM_CLK_AT91_UTMI, name, parent_name);
+ if (ret) {
+ kfree(utmi);
+ clk = ERR_PTR(ret);
+ }
- return 0;
+ return clk;
}
-static int utmi_clk_probe(struct udevice *dev)
+U_BOOT_DRIVER(at91_utmi_clk) = {
+ .name = UBOOT_DM_CLK_AT91_UTMI,
+ .id = UCLASS_CLK,
+ .ops = &utmi_ops,
+ .flags = DM_FLAG_PRE_RELOC,
+};
+
+static int clk_utmi_sama7g5_enable(struct clk *clk)
{
- return at91_pmc_core_probe(dev);
+ struct clk_utmi *utmi = to_clk_utmi(clk);
+ ulong parent_rate = clk_get_parent_rate(clk);
+ unsigned int val;
+
+ switch (parent_rate) {
+ case 16000000:
+ val = 0;
+ break;
+ case 20000000:
+ val = 2;
+ break;
+ case 24000000:
+ val = 3;
+ break;
+ case 32000000:
+ val = 5;
+ break;
+ default:
+ debug("UTMICK: unsupported main_xtal rate\n");
+ return -EINVAL;
+ }
+
+ pmc_write(utmi->base, AT91_PMC_XTALF, val);
+
+ return 0;
}
-static const struct udevice_id utmi_clk_match[] = {
- { .compatible = "atmel,at91sam9x5-clk-utmi" },
- {}
+static const struct clk_ops sama7g5_utmi_ops = {
+ .enable = clk_utmi_sama7g5_enable,
+ .get_rate = clk_utmi_get_rate,
};
-U_BOOT_DRIVER(at91sam9x5_utmi_clk) = {
- .name = "at91sam9x5-utmi-clk",
+struct clk *at91_clk_sama7g5_register_utmi(void __iomem *base,
+ const char *name, const char *parent_name)
+{
+ struct clk_utmi *utmi;
+ struct clk *clk;
+ int ret;
+
+ if (!base || !name || !parent_name)
+ return ERR_PTR(-EINVAL);
+
+ utmi = kzalloc(sizeof(*utmi), GFP_KERNEL);
+ if (!utmi)
+ return ERR_PTR(-ENOMEM);
+
+ utmi->base = base;
+
+ clk = &utmi->clk;
+ ret = clk_register(clk, UBOOT_DM_CLK_AT91_SAMA7G5_UTMI, name,
+ parent_name);
+ if (ret) {
+ kfree(utmi);
+ clk = ERR_PTR(ret);
+ }
+
+ return clk;
+}
+
+U_BOOT_DRIVER(at91_sama7g5_utmi_clk) = {
+ .name = UBOOT_DM_CLK_AT91_SAMA7G5_UTMI,
.id = UCLASS_CLK,
- .of_match = utmi_clk_match,
- .probe = utmi_clk_probe,
- .ofdata_to_platdata = utmi_clk_ofdata_to_platdata,
- .platdata_auto_alloc_size = sizeof(struct pmc_platdata),
- .ops = &utmi_clk_ops,
+ .ops = &sama7g5_utmi_ops,
+ .flags = DM_FLAG_PRE_RELOC,
};
diff --git a/drivers/clk/at91/compat.c b/drivers/clk/at91/compat.c
new file mode 100644
index 0000000000..8cf6254046
--- /dev/null
+++ b/drivers/clk/at91/compat.c
@@ -0,0 +1,1023 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Compatible code for non CCF AT91 platforms.
+ *
+ * Copyright (C) 2020 Microchip Technology Inc. and its subsidiaries
+ *
+ * Author: Claudiu Beznea <claudiu.beznea@microchip.com>
+ */
+#include <common.h>
+#include <clk-uclass.h>
+#include <dm.h>
+#include <dm/lists.h>
+#include <dm/util.h>
+#include <mach/at91_pmc.h>
+#include <mach/at91_sfr.h>
+#include <regmap.h>
+#include <syscon.h>
+
+#include "pmc.h"
+
+DECLARE_GLOBAL_DATA_PTR;
+
+struct pmc_platdata {
+ struct at91_pmc *reg_base;
+ struct regmap *regmap_sfr;
+};
+
+static const struct udevice_id at91_pmc_match[] = {
+ { .compatible = "atmel,at91rm9200-pmc" },
+ { .compatible = "atmel,at91sam9260-pmc" },
+ { .compatible = "atmel,at91sam9g45-pmc" },
+ { .compatible = "atmel,at91sam9n12-pmc" },
+ { .compatible = "atmel,at91sam9x5-pmc" },
+ { .compatible = "atmel,sama5d3-pmc" },
+ { .compatible = "atmel,sama5d2-pmc" },
+ {}
+};
+
+U_BOOT_DRIVER(at91_pmc) = {
+ .name = "at91-pmc",
+ .id = UCLASS_SIMPLE_BUS,
+ .of_match = at91_pmc_match,
+};
+
+static int at91_pmc_core_probe(struct udevice *dev)
+{
+ struct pmc_platdata *plat = dev_get_platdata(dev);
+
+ dev = dev_get_parent(dev);
+
+ plat->reg_base = dev_read_addr_ptr(dev);
+
+ return 0;
+}
+
+/**
+ * at91_clk_sub_device_bind() - for the at91 clock driver
+ * Recursively bind its children as clk devices.
+ *
+ * @return: 0 on success, or negative error code on failure
+ */
+int at91_clk_sub_device_bind(struct udevice *dev, const char *drv_name)
+{
+ const void *fdt = gd->fdt_blob;
+ int offset = dev_of_offset(dev);
+ bool pre_reloc_only = !(gd->flags & GD_FLG_RELOC);
+ const char *name;
+ int ret;
+
+ for (offset = fdt_first_subnode(fdt, offset);
+ offset > 0;
+ offset = fdt_next_subnode(fdt, offset)) {
+ if (pre_reloc_only &&
+ !ofnode_pre_reloc(offset_to_ofnode(offset)))
+ continue;
+ /*
+ * If this node has "compatible" property, this is not
+ * a clock sub-node, but a normal device. skip.
+ */
+ fdt_get_property(fdt, offset, "compatible", &ret);
+ if (ret >= 0)
+ continue;
+
+ if (ret != -FDT_ERR_NOTFOUND)
+ return ret;
+
+ name = fdt_get_name(fdt, offset, NULL);
+ if (!name)
+ return -EINVAL;
+ ret = device_bind_driver_to_node(dev, drv_name, name,
+ offset_to_ofnode(offset), NULL);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+int at91_clk_of_xlate(struct clk *clk, struct ofnode_phandle_args *args)
+{
+ int periph;
+
+ if (args->args_count) {
+ debug("Invalid args_count: %d\n", args->args_count);
+ return -EINVAL;
+ }
+
+ periph = fdtdec_get_uint(gd->fdt_blob, dev_of_offset(clk->dev), "reg",
+ -1);
+ if (periph < 0)
+ return -EINVAL;
+
+ clk->id = periph;
+
+ return 0;
+}
+
+int at91_clk_probe(struct udevice *dev)
+{
+ struct udevice *dev_periph_container, *dev_pmc;
+ struct pmc_platdata *plat = dev_get_platdata(dev);
+
+ dev_periph_container = dev_get_parent(dev);
+ dev_pmc = dev_get_parent(dev_periph_container);
+
+ plat->reg_base = dev_read_addr_ptr(dev_pmc);
+
+ return 0;
+}
+
+/* SCKC specific code. */
+static const struct udevice_id at91_sckc_match[] = {
+ { .compatible = "atmel,at91sam9x5-sckc" },
+ {}
+};
+
+U_BOOT_DRIVER(at91_sckc) = {
+ .name = "at91-sckc",
+ .id = UCLASS_SIMPLE_BUS,
+ .of_match = at91_sckc_match,
+};
+
+/* Slow clock specific code. */
+static int at91_slow_clk_enable(struct clk *clk)
+{
+ return 0;
+}
+
+static ulong at91_slow_clk_get_rate(struct clk *clk)
+{
+ return CONFIG_SYS_AT91_SLOW_CLOCK;
+}
+
+static struct clk_ops at91_slow_clk_ops = {
+ .enable = at91_slow_clk_enable,
+ .get_rate = at91_slow_clk_get_rate,
+};
+
+static const struct udevice_id at91_slow_clk_match[] = {
+ { .compatible = "atmel,at91sam9x5-clk-slow" },
+ {}
+};
+
+U_BOOT_DRIVER(at91_slow_clk) = {
+ .name = "at91-slow-clk",
+ .id = UCLASS_CLK,
+ .of_match = at91_slow_clk_match,
+ .ops = &at91_slow_clk_ops,
+};
+
+/* Master clock specific code. */
+static ulong at91_master_clk_get_rate(struct clk *clk)
+{
+ return gd->arch.mck_rate_hz;
+}
+
+static struct clk_ops at91_master_clk_ops = {
+ .get_rate = at91_master_clk_get_rate,
+};
+
+static const struct udevice_id at91_master_clk_match[] = {
+ { .compatible = "atmel,at91rm9200-clk-master" },
+ { .compatible = "atmel,at91sam9x5-clk-master" },
+ {}
+};
+
+U_BOOT_DRIVER(at91_master_clk) = {
+ .name = "at91-master-clk",
+ .id = UCLASS_CLK,
+ .of_match = at91_master_clk_match,
+ .ops = &at91_master_clk_ops,
+};
+
+/* Main osc clock specific code. */
+static int main_osc_clk_enable(struct clk *clk)
+{
+ struct pmc_platdata *plat = dev_get_platdata(clk->dev);
+ struct at91_pmc *pmc = plat->reg_base;
+
+ if (readl(&pmc->sr) & AT91_PMC_MOSCSELS)
+ return 0;
+
+ return -EINVAL;
+}
+
+static ulong main_osc_clk_get_rate(struct clk *clk)
+{
+ return gd->arch.main_clk_rate_hz;
+}
+
+static struct clk_ops main_osc_clk_ops = {
+ .enable = main_osc_clk_enable,
+ .get_rate = main_osc_clk_get_rate,
+};
+
+static int main_osc_clk_probe(struct udevice *dev)
+{
+ return at91_pmc_core_probe(dev);
+}
+
+static const struct udevice_id main_osc_clk_match[] = {
+ { .compatible = "atmel,at91sam9x5-clk-main" },
+ {}
+};
+
+U_BOOT_DRIVER(at91sam9x5_main_osc_clk) = {
+ .name = "at91sam9x5-main-osc-clk",
+ .id = UCLASS_CLK,
+ .of_match = main_osc_clk_match,
+ .probe = main_osc_clk_probe,
+ .platdata_auto_alloc_size = sizeof(struct pmc_platdata),
+ .ops = &main_osc_clk_ops,
+};
+
+/* PLLA clock specific code. */
+static int plla_clk_enable(struct clk *clk)
+{
+ struct pmc_platdata *plat = dev_get_platdata(clk->dev);
+ struct at91_pmc *pmc = plat->reg_base;
+
+ if (readl(&pmc->sr) & AT91_PMC_LOCKA)
+ return 0;
+
+ return -EINVAL;
+}
+
+static ulong plla_clk_get_rate(struct clk *clk)
+{
+ return gd->arch.plla_rate_hz;
+}
+
+static struct clk_ops plla_clk_ops = {
+ .enable = plla_clk_enable,
+ .get_rate = plla_clk_get_rate,
+};
+
+static int plla_clk_probe(struct udevice *dev)
+{
+ return at91_pmc_core_probe(dev);
+}
+
+static const struct udevice_id plla_clk_match[] = {
+ { .compatible = "atmel,sama5d3-clk-pll" },
+ {}
+};
+
+U_BOOT_DRIVER(at91_plla_clk) = {
+ .name = "at91-plla-clk",
+ .id = UCLASS_CLK,
+ .of_match = plla_clk_match,
+ .probe = plla_clk_probe,
+ .platdata_auto_alloc_size = sizeof(struct pmc_platdata),
+ .ops = &plla_clk_ops,
+};
+
+/* PLLA DIV clock specific code. */
+static int at91_plladiv_clk_enable(struct clk *clk)
+{
+ return 0;
+}
+
+static ulong at91_plladiv_clk_get_rate(struct clk *clk)
+{
+ struct pmc_platdata *plat = dev_get_platdata(clk->dev);
+ struct at91_pmc *pmc = plat->reg_base;
+ struct clk source;
+ ulong clk_rate;
+ int ret;
+
+ ret = clk_get_by_index(clk->dev, 0, &source);
+ if (ret)
+ return -EINVAL;
+
+ clk_rate = clk_get_rate(&source);
+ if (readl(&pmc->mckr) & AT91_PMC_MCKR_PLLADIV_2)
+ clk_rate /= 2;
+
+ return clk_rate;
+}
+
+static ulong at91_plladiv_clk_set_rate(struct clk *clk, ulong rate)
+{
+ struct pmc_platdata *plat = dev_get_platdata(clk->dev);
+ struct at91_pmc *pmc = plat->reg_base;
+ struct clk source;
+ ulong parent_rate;
+ int ret;
+
+ ret = clk_get_by_index(clk->dev, 0, &source);
+ if (ret)
+ return -EINVAL;
+
+ parent_rate = clk_get_rate(&source);
+ if ((parent_rate != rate) && ((parent_rate) / 2 != rate))
+ return -EINVAL;
+
+ if (parent_rate != rate) {
+ writel((readl(&pmc->mckr) | AT91_PMC_MCKR_PLLADIV_2),
+ &pmc->mckr);
+ }
+
+ return 0;
+}
+
+static struct clk_ops at91_plladiv_clk_ops = {
+ .enable = at91_plladiv_clk_enable,
+ .get_rate = at91_plladiv_clk_get_rate,
+ .set_rate = at91_plladiv_clk_set_rate,
+};
+
+static int at91_plladiv_clk_probe(struct udevice *dev)
+{
+ return at91_pmc_core_probe(dev);
+}
+
+static const struct udevice_id at91_plladiv_clk_match[] = {
+ { .compatible = "atmel,at91sam9x5-clk-plldiv" },
+ {}
+};
+
+U_BOOT_DRIVER(at91_plladiv_clk) = {
+ .name = "at91-plladiv-clk",
+ .id = UCLASS_CLK,
+ .of_match = at91_plladiv_clk_match,
+ .probe = at91_plladiv_clk_probe,
+ .platdata_auto_alloc_size = sizeof(struct pmc_platdata),
+ .ops = &at91_plladiv_clk_ops,
+};
+
+/* System clock specific code. */
+#define SYSTEM_MAX_ID 31
+
+/**
+ * at91_system_clk_bind() - for the system clock driver
+ * Recursively bind its children as clk devices.
+ *
+ * @return: 0 on success, or negative error code on failure
+ */
+static int at91_system_clk_bind(struct udevice *dev)
+{
+ return at91_clk_sub_device_bind(dev, "system-clk");
+}
+
+static const struct udevice_id at91_system_clk_match[] = {
+ { .compatible = "atmel,at91rm9200-clk-system" },
+ {}
+};
+
+U_BOOT_DRIVER(at91_system_clk) = {
+ .name = "at91-system-clk",
+ .id = UCLASS_MISC,
+ .of_match = at91_system_clk_match,
+ .bind = at91_system_clk_bind,
+};
+
+static inline int is_pck(int id)
+{
+ return (id >= 8) && (id <= 15);
+}
+
+static ulong system_clk_get_rate(struct clk *clk)
+{
+ struct clk clk_dev;
+ int ret;
+
+ ret = clk_get_by_index(clk->dev, 0, &clk_dev);
+ if (ret)
+ return -EINVAL;
+
+ return clk_get_rate(&clk_dev);
+}
+
+static ulong system_clk_set_rate(struct clk *clk, ulong rate)
+{
+ struct clk clk_dev;
+ int ret;
+
+ ret = clk_get_by_index(clk->dev, 0, &clk_dev);
+ if (ret)
+ return -EINVAL;
+
+ return clk_set_rate(&clk_dev, rate);
+}
+
+static int system_clk_enable(struct clk *clk)
+{
+ struct pmc_platdata *plat = dev_get_platdata(clk->dev);
+ struct at91_pmc *pmc = plat->reg_base;
+ u32 mask;
+
+ if (clk->id > SYSTEM_MAX_ID)
+ return -EINVAL;
+
+ mask = BIT(clk->id);
+
+ writel(mask, &pmc->scer);
+
+ /**
+ * For the programmable clocks the Ready status in the PMC
+ * status register should be checked after enabling.
+ * For other clocks this is unnecessary.
+ */
+ if (!is_pck(clk->id))
+ return 0;
+
+ while (!(readl(&pmc->sr) & mask))
+ ;
+
+ return 0;
+}
+
+static struct clk_ops system_clk_ops = {
+ .of_xlate = at91_clk_of_xlate,
+ .get_rate = system_clk_get_rate,
+ .set_rate = system_clk_set_rate,
+ .enable = system_clk_enable,
+};
+
+U_BOOT_DRIVER(system_clk) = {
+ .name = "system-clk",
+ .id = UCLASS_CLK,
+ .probe = at91_clk_probe,
+ .platdata_auto_alloc_size = sizeof(struct pmc_platdata),
+ .ops = &system_clk_ops,
+};
+
+/* Peripheral clock specific code. */
+#define PERIPHERAL_ID_MIN 2
+#define PERIPHERAL_ID_MAX 31
+#define PERIPHERAL_MASK(id) (1 << ((id) & PERIPHERAL_ID_MAX))
+
+enum periph_clk_type {
+ CLK_PERIPH_AT91RM9200 = 0,
+ CLK_PERIPH_AT91SAM9X5,
+};
+
+/**
+ * sam9x5_periph_clk_bind() - for the periph clock driver
+ * Recursively bind its children as clk devices.
+ *
+ * @return: 0 on success, or negative error code on failure
+ */
+static int sam9x5_periph_clk_bind(struct udevice *dev)
+{
+ return at91_clk_sub_device_bind(dev, "periph-clk");
+}
+
+static const struct udevice_id sam9x5_periph_clk_match[] = {
+ {
+ .compatible = "atmel,at91rm9200-clk-peripheral",
+ .data = CLK_PERIPH_AT91RM9200,
+ },
+ {
+ .compatible = "atmel,at91sam9x5-clk-peripheral",
+ .data = CLK_PERIPH_AT91SAM9X5,
+ },
+ {}
+};
+
+U_BOOT_DRIVER(sam9x5_periph_clk) = {
+ .name = "sam9x5-periph-clk",
+ .id = UCLASS_MISC,
+ .of_match = sam9x5_periph_clk_match,
+ .bind = sam9x5_periph_clk_bind,
+};
+
+static int periph_clk_enable(struct clk *clk)
+{
+ struct pmc_platdata *plat = dev_get_platdata(clk->dev);
+ struct at91_pmc *pmc = plat->reg_base;
+ enum periph_clk_type clk_type;
+ void *addr;
+
+ if (clk->id < PERIPHERAL_ID_MIN)
+ return -1;
+
+ clk_type = dev_get_driver_data(dev_get_parent(clk->dev));
+ if (clk_type == CLK_PERIPH_AT91RM9200) {
+ addr = &pmc->pcer;
+ if (clk->id > PERIPHERAL_ID_MAX)
+ addr = &pmc->pcer1;
+
+ setbits_le32(addr, PERIPHERAL_MASK(clk->id));
+ } else {
+ writel(clk->id & AT91_PMC_PCR_PID_MASK, &pmc->pcr);
+ setbits_le32(&pmc->pcr,
+ AT91_PMC_PCR_CMD_WRITE | AT91_PMC_PCR_EN);
+ }
+
+ return 0;
+}
+
+static ulong periph_get_rate(struct clk *clk)
+{
+ struct udevice *dev;
+ struct clk clk_dev;
+ ulong clk_rate;
+ int ret;
+
+ dev = dev_get_parent(clk->dev);
+
+ ret = clk_get_by_index(dev, 0, &clk_dev);
+ if (ret)
+ return ret;
+
+ clk_rate = clk_get_rate(&clk_dev);
+
+ clk_free(&clk_dev);
+
+ return clk_rate;
+}
+
+static struct clk_ops periph_clk_ops = {
+ .of_xlate = at91_clk_of_xlate,
+ .enable = periph_clk_enable,
+ .get_rate = periph_get_rate,
+};
+
+U_BOOT_DRIVER(clk_periph) = {
+ .name = "periph-clk",
+ .id = UCLASS_CLK,
+ .platdata_auto_alloc_size = sizeof(struct pmc_platdata),
+ .probe = at91_clk_probe,
+ .ops = &periph_clk_ops,
+};
+
+/* UTMI clock specific code. */
+#ifdef CONFIG_AT91_UTMI
+
+/*
+ * The purpose of this clock is to generate a 480 MHz signal. A different
+ * rate can't be configured.
+ */
+#define UTMI_RATE 480000000
+
+static int utmi_clk_enable(struct clk *clk)
+{
+ struct pmc_platdata *plat = dev_get_platdata(clk->dev);
+ struct at91_pmc *pmc = plat->reg_base;
+ struct clk clk_dev;
+ ulong clk_rate;
+ u32 utmi_ref_clk_freq;
+ u32 tmp;
+ int err;
+ int timeout = 2000000;
+
+ if (readl(&pmc->sr) & AT91_PMC_LOCKU)
+ return 0;
+
+ /*
+ * If mainck rate is different from 12 MHz, we have to configure the
+ * FREQ field of the SFR_UTMICKTRIM register to generate properly
+ * the utmi clock.
+ */
+ err = clk_get_by_index(clk->dev, 0, &clk_dev);
+ if (err)
+ return -EINVAL;
+
+ clk_rate = clk_get_rate(&clk_dev);
+ switch (clk_rate) {
+ case 12000000:
+ utmi_ref_clk_freq = 0;
+ break;
+ case 16000000:
+ utmi_ref_clk_freq = 1;
+ break;
+ case 24000000:
+ utmi_ref_clk_freq = 2;
+ break;
+ /*
+ * Not supported on SAMA5D2 but it's not an issue since MAINCK
+ * maximum value is 24 MHz.
+ */
+ case 48000000:
+ utmi_ref_clk_freq = 3;
+ break;
+ default:
+ printf("UTMICK: unsupported mainck rate\n");
+ return -EINVAL;
+ }
+
+ if (plat->regmap_sfr) {
+ err = regmap_read(plat->regmap_sfr, AT91_SFR_UTMICKTRIM, &tmp);
+ if (err)
+ return -EINVAL;
+
+ tmp &= ~AT91_UTMICKTRIM_FREQ;
+ tmp |= utmi_ref_clk_freq;
+ err = regmap_write(plat->regmap_sfr, AT91_SFR_UTMICKTRIM, tmp);
+ if (err)
+ return -EINVAL;
+ } else if (utmi_ref_clk_freq) {
+ printf("UTMICK: sfr node required\n");
+ return -EINVAL;
+ }
+
+ tmp = readl(&pmc->uckr);
+ tmp |= AT91_PMC_UPLLEN |
+ AT91_PMC_UPLLCOUNT |
+ AT91_PMC_BIASEN;
+ writel(tmp, &pmc->uckr);
+
+ while ((--timeout) && !(readl(&pmc->sr) & AT91_PMC_LOCKU))
+ ;
+ if (!timeout) {
+ printf("UTMICK: timeout waiting for UPLL lock\n");
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+static ulong utmi_clk_get_rate(struct clk *clk)
+{
+ /* UTMI clk rate is fixed. */
+ return UTMI_RATE;
+}
+
+static struct clk_ops utmi_clk_ops = {
+ .enable = utmi_clk_enable,
+ .get_rate = utmi_clk_get_rate,
+};
+
+static int utmi_clk_ofdata_to_platdata(struct udevice *dev)
+{
+ struct pmc_platdata *plat = dev_get_platdata(dev);
+ struct udevice *syscon;
+
+ uclass_get_device_by_phandle(UCLASS_SYSCON, dev,
+ "regmap-sfr", &syscon);
+
+ if (syscon)
+ plat->regmap_sfr = syscon_get_regmap(syscon);
+
+ return 0;
+}
+
+static int utmi_clk_probe(struct udevice *dev)
+{
+ return at91_pmc_core_probe(dev);
+}
+
+static const struct udevice_id utmi_clk_match[] = {
+ { .compatible = "atmel,at91sam9x5-clk-utmi" },
+ {}
+};
+
+U_BOOT_DRIVER(at91sam9x5_utmi_clk) = {
+ .name = "at91sam9x5-utmi-clk",
+ .id = UCLASS_CLK,
+ .of_match = utmi_clk_match,
+ .probe = utmi_clk_probe,
+ .ofdata_to_platdata = utmi_clk_ofdata_to_platdata,
+ .platdata_auto_alloc_size = sizeof(struct pmc_platdata),
+ .ops = &utmi_clk_ops,
+};
+
+#endif /* CONFIG_AT91_UTMI */
+
+/* H32MX clock specific code. */
+#ifdef CONFIG_AT91_H32MX
+
+#define H32MX_MAX_FREQ 90000000
+
+static ulong sama5d4_h32mx_clk_get_rate(struct clk *clk)
+{
+ struct pmc_platdata *plat = dev_get_platdata(clk->dev);
+ struct at91_pmc *pmc = plat->reg_base;
+ ulong rate = gd->arch.mck_rate_hz;
+
+ if (readl(&pmc->mckr) & AT91_PMC_MCKR_H32MXDIV)
+ rate /= 2;
+
+ if (rate > H32MX_MAX_FREQ)
+ dev_dbg(clk->dev, "H32MX clock is too fast\n");
+
+ return rate;
+}
+
+static struct clk_ops sama5d4_h32mx_clk_ops = {
+ .get_rate = sama5d4_h32mx_clk_get_rate,
+};
+
+static int sama5d4_h32mx_clk_probe(struct udevice *dev)
+{
+ return at91_pmc_core_probe(dev);
+}
+
+static const struct udevice_id sama5d4_h32mx_clk_match[] = {
+ { .compatible = "atmel,sama5d4-clk-h32mx" },
+ {}
+};
+
+U_BOOT_DRIVER(sama5d4_h32mx_clk) = {
+ .name = "sama5d4-h32mx-clk",
+ .id = UCLASS_CLK,
+ .of_match = sama5d4_h32mx_clk_match,
+ .probe = sama5d4_h32mx_clk_probe,
+ .platdata_auto_alloc_size = sizeof(struct pmc_platdata),
+ .ops = &sama5d4_h32mx_clk_ops,
+};
+
+#endif /* CONFIG_AT91_H32MX */
+
+/* Generic clock specific code. */
+#ifdef CONFIG_AT91_GENERIC_CLK
+
+#define GENERATED_SOURCE_MAX 6
+#define GENERATED_MAX_DIV 255
+
+/**
+ * generated_clk_bind() - for the generated clock driver
+ * Recursively bind its children as clk devices.
+ *
+ * @return: 0 on success, or negative error code on failure
+ */
+static int generated_clk_bind(struct udevice *dev)
+{
+ return at91_clk_sub_device_bind(dev, "generic-clk");
+}
+
+static const struct udevice_id generated_clk_match[] = {
+ { .compatible = "atmel,sama5d2-clk-generated" },
+ {}
+};
+
+U_BOOT_DRIVER(generated_clk) = {
+ .name = "generated-clk",
+ .id = UCLASS_MISC,
+ .of_match = generated_clk_match,
+ .bind = generated_clk_bind,
+};
+
+struct generic_clk_priv {
+ u32 num_parents;
+};
+
+static ulong generic_clk_get_rate(struct clk *clk)
+{
+ struct pmc_platdata *plat = dev_get_platdata(clk->dev);
+ struct at91_pmc *pmc = plat->reg_base;
+ struct clk parent;
+ ulong clk_rate;
+ u32 tmp, gckdiv;
+ u8 clock_source, parent_index;
+ int ret;
+
+ writel(clk->id & AT91_PMC_PCR_PID_MASK, &pmc->pcr);
+ tmp = readl(&pmc->pcr);
+ clock_source = (tmp >> AT91_PMC_PCR_GCKCSS_OFFSET) &
+ AT91_PMC_PCR_GCKCSS_MASK;
+ gckdiv = (tmp >> AT91_PMC_PCR_GCKDIV_OFFSET) & AT91_PMC_PCR_GCKDIV_MASK;
+
+ parent_index = clock_source - 1;
+ ret = clk_get_by_index(dev_get_parent(clk->dev), parent_index, &parent);
+ if (ret)
+ return 0;
+
+ clk_rate = clk_get_rate(&parent) / (gckdiv + 1);
+
+ clk_free(&parent);
+
+ return clk_rate;
+}
+
+static ulong generic_clk_set_rate(struct clk *clk, ulong rate)
+{
+ struct pmc_platdata *plat = dev_get_platdata(clk->dev);
+ struct at91_pmc *pmc = plat->reg_base;
+ struct generic_clk_priv *priv = dev_get_priv(clk->dev);
+ struct clk parent, best_parent;
+ ulong tmp_rate, best_rate = rate, parent_rate;
+ int tmp_diff, best_diff = -1;
+ u32 div, best_div = 0;
+ u8 best_parent_index, best_clock_source = 0;
+ u8 i;
+ u32 tmp;
+ int ret;
+
+ for (i = 0; i < priv->num_parents; i++) {
+ ret = clk_get_by_index(dev_get_parent(clk->dev), i, &parent);
+ if (ret)
+ return ret;
+
+ parent_rate = clk_get_rate(&parent);
+ if (IS_ERR_VALUE(parent_rate))
+ return parent_rate;
+
+ for (div = 1; div < GENERATED_MAX_DIV + 2; div++) {
+ tmp_rate = DIV_ROUND_CLOSEST(parent_rate, div);
+ tmp_diff = abs(rate - tmp_rate);
+
+ if (best_diff < 0 || best_diff > tmp_diff) {
+ best_rate = tmp_rate;
+ best_diff = tmp_diff;
+
+ best_div = div - 1;
+ best_parent = parent;
+ best_parent_index = i;
+ best_clock_source = best_parent_index + 1;
+ }
+
+ if (!best_diff || tmp_rate < rate)
+ break;
+ }
+
+ if (!best_diff)
+ break;
+ }
+
+ debug("GCK: best parent: %s, best_rate = %ld, best_div = %d\n",
+ best_parent.dev->name, best_rate, best_div);
+
+ ret = clk_enable(&best_parent);
+ if (ret)
+ return ret;
+
+ writel(clk->id & AT91_PMC_PCR_PID_MASK, &pmc->pcr);
+ tmp = readl(&pmc->pcr);
+ tmp &= ~(AT91_PMC_PCR_GCKDIV | AT91_PMC_PCR_GCKCSS);
+ tmp |= AT91_PMC_PCR_GCKCSS_(best_clock_source) |
+ AT91_PMC_PCR_CMD_WRITE |
+ AT91_PMC_PCR_GCKDIV_(best_div) |
+ AT91_PMC_PCR_GCKEN;
+ writel(tmp, &pmc->pcr);
+
+ while (!(readl(&pmc->sr) & AT91_PMC_GCKRDY))
+ ;
+
+ return 0;
+}
+
+static struct clk_ops generic_clk_ops = {
+ .of_xlate = at91_clk_of_xlate,
+ .get_rate = generic_clk_get_rate,
+ .set_rate = generic_clk_set_rate,
+};
+
+static int generic_clk_ofdata_to_platdata(struct udevice *dev)
+{
+ struct generic_clk_priv *priv = dev_get_priv(dev);
+ u32 cells[GENERATED_SOURCE_MAX];
+ u32 num_parents;
+
+ num_parents = fdtdec_get_int_array_count(gd->fdt_blob,
+ dev_of_offset(dev_get_parent(dev)), "clocks", cells,
+ GENERATED_SOURCE_MAX);
+
+ if (!num_parents)
+ return -1;
+
+ priv->num_parents = num_parents;
+
+ return 0;
+}
+
+U_BOOT_DRIVER(generic_clk) = {
+ .name = "generic-clk",
+ .id = UCLASS_CLK,
+ .probe = at91_clk_probe,
+ .ofdata_to_platdata = generic_clk_ofdata_to_platdata,
+ .priv_auto_alloc_size = sizeof(struct generic_clk_priv),
+ .platdata_auto_alloc_size = sizeof(struct pmc_platdata),
+ .ops = &generic_clk_ops,
+};
+
+#endif /* CONFIG_AT91_GENERIC_CLK */
+
+/* USB clock specific code. */
+#ifdef CONFIG_AT91_USB_CLK
+
+#define AT91_USB_CLK_SOURCE_MAX 2
+#define AT91_USB_CLK_MAX_DIV 15
+
+struct at91_usb_clk_priv {
+ u32 num_clksource;
+};
+
+static ulong at91_usb_clk_get_rate(struct clk *clk)
+{
+ struct pmc_platdata *plat = dev_get_platdata(clk->dev);
+ struct at91_pmc *pmc = plat->reg_base;
+ struct clk source;
+ u32 tmp, usbdiv;
+ u8 source_index;
+ int ret;
+
+ tmp = readl(&pmc->pcr);
+ source_index = (tmp >> AT91_PMC_USB_USBS_OFFSET) &
+ AT91_PMC_USB_USBS_MASK;
+ usbdiv = (tmp >> AT91_PMC_USB_DIV_OFFSET) & AT91_PMC_USB_DIV_MASK;
+
+ ret = clk_get_by_index(clk->dev, source_index, &source);
+ if (ret)
+ return 0;
+
+ return clk_get_rate(&source) / (usbdiv + 1);
+}
+
+static ulong at91_usb_clk_set_rate(struct clk *clk, ulong rate)
+{
+ struct pmc_platdata *plat = dev_get_platdata(clk->dev);
+ struct at91_pmc *pmc = plat->reg_base;
+ struct at91_usb_clk_priv *priv = dev_get_priv(clk->dev);
+ struct clk source, best_source;
+ ulong tmp_rate, best_rate = rate, source_rate;
+ int tmp_diff, best_diff = -1;
+ u32 div, best_div = 0;
+ u8 best_source_index = 0;
+ u8 i;
+ u32 tmp;
+ int ret;
+
+ for (i = 0; i < priv->num_clksource; i++) {
+ ret = clk_get_by_index(clk->dev, i, &source);
+ if (ret)
+ return ret;
+
+ source_rate = clk_get_rate(&source);
+ if (IS_ERR_VALUE(source_rate))
+ return source_rate;
+
+ for (div = 1; div < AT91_USB_CLK_MAX_DIV + 2; div++) {
+ tmp_rate = DIV_ROUND_CLOSEST(source_rate, div);
+ tmp_diff = abs(rate - tmp_rate);
+
+ if (best_diff < 0 || best_diff > tmp_diff) {
+ best_rate = tmp_rate;
+ best_diff = tmp_diff;
+
+ best_div = div - 1;
+ best_source = source;
+ best_source_index = i;
+ }
+
+ if (!best_diff || tmp_rate < rate)
+ break;
+ }
+
+ if (!best_diff)
+ break;
+ }
+
+ debug("AT91 USB: best sourc: %s, best_rate = %ld, best_div = %d\n",
+ best_source.dev->name, best_rate, best_div);
+
+ ret = clk_enable(&best_source);
+ if (ret)
+ return ret;
+
+ tmp = AT91_PMC_USB_USBS_(best_source_index) |
+ AT91_PMC_USB_DIV_(best_div);
+ writel(tmp, &pmc->usb);
+
+ return 0;
+}
+
+static struct clk_ops at91_usb_clk_ops = {
+ .get_rate = at91_usb_clk_get_rate,
+ .set_rate = at91_usb_clk_set_rate,
+};
+
+static int at91_usb_clk_ofdata_to_platdata(struct udevice *dev)
+{
+ struct at91_usb_clk_priv *priv = dev_get_priv(dev);
+ u32 cells[AT91_USB_CLK_SOURCE_MAX];
+ u32 num_clksource;
+
+ num_clksource = fdtdec_get_int_array_count(gd->fdt_blob,
+ dev_of_offset(dev),
+ "clocks", cells,
+ AT91_USB_CLK_SOURCE_MAX);
+
+ if (!num_clksource)
+ return -1;
+
+ priv->num_clksource = num_clksource;
+
+ return 0;
+}
+
+static int at91_usb_clk_probe(struct udevice *dev)
+{
+ return at91_pmc_core_probe(dev);
+}
+
+static const struct udevice_id at91_usb_clk_match[] = {
+ { .compatible = "atmel,at91sam9x5-clk-usb" },
+ {}
+};
+
+U_BOOT_DRIVER(at91_usb_clk) = {
+ .name = "at91-usb-clk",
+ .id = UCLASS_CLK,
+ .of_match = at91_usb_clk_match,
+ .probe = at91_usb_clk_probe,
+ .ofdata_to_platdata = at91_usb_clk_ofdata_to_platdata,
+ .priv_auto_alloc_size = sizeof(struct at91_usb_clk_priv),
+ .platdata_auto_alloc_size = sizeof(struct pmc_platdata),
+ .ops = &at91_usb_clk_ops,
+};
+
+#endif /* CONFIG_AT91_USB_CLK */
diff --git a/drivers/clk/at91/pmc.c b/drivers/clk/at91/pmc.c
index ca90abef2d..660e231921 100644
--- a/drivers/clk/at91/pmc.c
+++ b/drivers/clk/at91/pmc.c
@@ -4,119 +4,167 @@
* Wenyou.Yang <wenyou.yang@atmel.com>
*/
-#include <common.h>
+#include <asm/io.h>
#include <clk-uclass.h>
-#include <dm.h>
-#include <log.h>
-#include <dm/lists.h>
-#include <dm/util.h>
+#include <common.h>
+
#include "pmc.h"
-DECLARE_GLOBAL_DATA_PTR;
-
-static const struct udevice_id at91_pmc_match[] = {
- { .compatible = "atmel,at91rm9200-pmc" },
- { .compatible = "atmel,at91sam9260-pmc" },
- { .compatible = "atmel,at91sam9g45-pmc" },
- { .compatible = "atmel,at91sam9n12-pmc" },
- { .compatible = "atmel,at91sam9x5-pmc" },
- { .compatible = "atmel,sama5d3-pmc" },
- { .compatible = "atmel,sama5d2-pmc" },
- {}
-};
+static int at91_clk_of_xlate(struct clk *clk, struct ofnode_phandle_args *args)
+{
+ if (args->args_count != 2) {
+ debug("AT91: clk: Invalid args_count: %d\n", args->args_count);
+ return -EINVAL;
+ }
-U_BOOT_DRIVER(atmel_at91rm9200_pmc) = {
- .name = "atmel_at91rm9200_pmc",
- .id = UCLASS_SIMPLE_BUS,
- .of_match = at91_pmc_match,
-};
+ clk->id = AT91_TO_CLK_ID(args->args[0], args->args[1]);
+
+ return 0;
+}
+
+static ulong at91_clk_get_rate(struct clk *clk)
+{
+ struct clk *c;
+ int ret;
+
+ ret = clk_get_by_id(clk->id, &c);
+ if (ret)
+ return ret;
+
+ return clk_get_rate(c);
+}
+
+static ulong at91_clk_set_rate(struct clk *clk, ulong rate)
+{
+ struct clk *c;
+ int ret;
-U_BOOT_DRIVER_ALIAS(atmel_at91rm9200_pmc, atmel_at91sam9260_pmc)
+ ret = clk_get_by_id(clk->id, &c);
+ if (ret)
+ return ret;
-/*---------------------------------------------------------*/
+ return clk_set_rate(c, rate);
+}
-int at91_pmc_core_probe(struct udevice *dev)
+static int at91_clk_enable(struct clk *clk)
{
- struct pmc_platdata *plat = dev_get_platdata(dev);
+ struct clk *c;
+ int ret;
- dev = dev_get_parent(dev);
+ ret = clk_get_by_id(clk->id, &c);
+ if (ret)
+ return ret;
- plat->reg_base = dev_read_addr_ptr(dev);
+ return clk_enable(c);
+}
- return 0;
+static int at91_clk_disable(struct clk *clk)
+{
+ struct clk *c;
+ int ret;
+
+ ret = clk_get_by_id(clk->id, &c);
+ if (ret)
+ return ret;
+
+ return clk_disable(c);
}
+const struct clk_ops at91_clk_ops = {
+ .of_xlate = at91_clk_of_xlate,
+ .set_rate = at91_clk_set_rate,
+ .get_rate = at91_clk_get_rate,
+ .enable = at91_clk_enable,
+ .disable = at91_clk_disable,
+};
+
/**
- * at91_clk_sub_device_bind() - for the at91 clock driver
- * Recursively bind its children as clk devices.
+ * pmc_read() - read content at address base + off into val
*
- * @return: 0 on success, or negative error code on failure
+ * @base: base address
+ * @off: offset to read from
+ * @val: where the content of base + off is stored
+ *
+ * @return: void
*/
-int at91_clk_sub_device_bind(struct udevice *dev, const char *drv_name)
+void pmc_read(void __iomem *base, unsigned int off, unsigned int *val)
{
- const void *fdt = gd->fdt_blob;
- int offset = dev_of_offset(dev);
- bool pre_reloc_only = !(gd->flags & GD_FLG_RELOC);
- const char *name;
- int ret;
-
- for (offset = fdt_first_subnode(fdt, offset);
- offset > 0;
- offset = fdt_next_subnode(fdt, offset)) {
- if (pre_reloc_only &&
- !ofnode_pre_reloc(offset_to_ofnode(offset)))
- continue;
- /*
- * If this node has "compatible" property, this is not
- * a clock sub-node, but a normal device. skip.
- */
- fdt_get_property(fdt, offset, "compatible", &ret);
- if (ret >= 0)
- continue;
-
- if (ret != -FDT_ERR_NOTFOUND)
- return ret;
-
- name = fdt_get_name(fdt, offset, NULL);
- if (!name)
- return -EINVAL;
- ret = device_bind_driver_to_node(dev, drv_name, name,
- offset_to_ofnode(offset), NULL);
- if (ret)
- return ret;
- }
+ *val = readl(base + off);
+}
- return 0;
+/**
+ * pmc_write() - write content of val at address base + off
+ *
+ * @base: base address
+ * @off: offset to write to
+ * @val: content to be written at base + off
+ *
+ * @return: void
+ */
+void pmc_write(void __iomem *base, unsigned int off, unsigned int val)
+{
+ writel(val, base + off);
}
-int at91_clk_of_xlate(struct clk *clk, struct ofnode_phandle_args *args)
+/**
+ * pmc_update_bits() - update a set of bits at address base + off
+ *
+ * @base: base address
+ * @off: offset to be updated
+ * @mask: mask of bits to be updated
+ * @bits: the new value to be updated
+ *
+ * @return: void
+ */
+void pmc_update_bits(void __iomem *base, unsigned int off,
+ unsigned int mask, unsigned int bits)
{
- int periph;
+ unsigned int tmp;
- if (args->args_count) {
- debug("Invalid args_count: %d\n", args->args_count);
- return -EINVAL;
- }
+ tmp = readl(base + off);
+ tmp &= ~mask;
+ writel(tmp | (bits & mask), base + off);
+}
- periph = fdtdec_get_uint(gd->fdt_blob, dev_of_offset(clk->dev), "reg",
- -1);
- if (periph < 0)
+/**
+ * at91_clk_mux_val_to_index() - get parent index in mux table
+ *
+ * @table: clock mux table
+ * @num_parents: clock number of parents
+ * @val: clock id who's mux index should be retrieved
+ *
+ * @return: clock index in mux table or a negative error number in case of
+ * failure
+ */
+int at91_clk_mux_val_to_index(const u32 *table, u32 num_parents, u32 val)
+{
+ int i;
+
+ if (!table || !num_parents)
return -EINVAL;
- clk->id = periph;
+ for (i = 0; i < num_parents; i++) {
+ if (table[i] == val)
+ return i;
+ }
- return 0;
+ return -EINVAL;
}
-int at91_clk_probe(struct udevice *dev)
+/**
+ * at91_clk_mux_index_to_val() - get parent ID corresponding to an entry in
+ * clock's mux table
+ *
+ * @table: clock's mux table
+ * @num_parents: clock's number of parents
+ * @index: index in mux table which clock's ID should be retrieved
+ *
+ * @return: clock ID or a negative error number in case of failure
+ */
+int at91_clk_mux_index_to_val(const u32 *table, u32 num_parents, u32 index)
{
- struct udevice *dev_periph_container, *dev_pmc;
- struct pmc_platdata *plat = dev_get_platdata(dev);
-
- dev_periph_container = dev_get_parent(dev);
- dev_pmc = dev_get_parent(dev_periph_container);
-
- plat->reg_base = dev_read_addr_ptr(dev_pmc);
+ if (!table || !num_parents || index < 0 || index > num_parents)
+ return -EINVAL;
- return 0;
+ return table[index];
}
diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h
index 517ba1d6b4..a6a714fd22 100644
--- a/drivers/clk/at91/pmc.h
+++ b/drivers/clk/at91/pmc.h
@@ -7,17 +7,141 @@
#ifndef __AT91_PMC_H__
#define __AT91_PMC_H__
-#include <regmap.h>
+#include <linux/bitops.h>
+#include <linux/io.h>
-struct pmc_platdata {
- struct at91_pmc *reg_base;
- struct regmap *regmap_sfr;
+/* Keep a range of 256 available clocks for every clock type. */
+#define AT91_TO_CLK_ID(_t, _i) (((_t) << 8) | ((_i) & 0xff))
+#define AT91_CLK_ID_TO_DID(_i) ((_i) & 0xff)
+
+struct clk_range {
+ unsigned long min;
+ unsigned long max;
+};
+
+struct clk_master_layout {
+ u32 offset;
+ u32 mask;
+ u8 pres_shift;
};
-int at91_pmc_core_probe(struct udevice *dev);
-int at91_clk_sub_device_bind(struct udevice *dev, const char *drv_name);
+extern const struct clk_master_layout at91rm9200_master_layout;
+extern const struct clk_master_layout at91sam9x5_master_layout;
+
+struct clk_master_characteristics {
+ struct clk_range output;
+ u32 divisors[4];
+ u8 have_div3_pres;
+};
+
+struct clk_pll_characteristics {
+ struct clk_range input;
+ int num_output;
+ const struct clk_range *output;
+ u16 *icpll;
+ u8 *out;
+ u8 upll : 1;
+};
+
+struct clk_pll_layout {
+ u32 pllr_mask;
+ u32 mul_mask;
+ u32 frac_mask;
+ u32 div_mask;
+ u32 endiv_mask;
+ u8 mul_shift;
+ u8 frac_shift;
+ u8 div_shift;
+ u8 endiv_shift;
+};
+
+struct clk_programmable_layout {
+ u8 pres_mask;
+ u8 pres_shift;
+ u8 css_mask;
+ u8 have_slck_mck;
+ u8 is_pres_direct;
+};
+
+struct clk_pcr_layout {
+ u32 offset;
+ u32 cmd;
+ u32 div_mask;
+ u32 gckcss_mask;
+ u32 pid_mask;
+};
+
+extern const struct clk_programmable_layout at91rm9200_programmable_layout;
+extern const struct clk_programmable_layout at91sam9g45_programmable_layout;
+extern const struct clk_programmable_layout at91sam9x5_programmable_layout;
+
+extern const struct clk_ops at91_clk_ops;
+
+struct clk *at91_clk_main_rc(void __iomem *reg, const char *name,
+ const char *parent_name);
+struct clk *at91_clk_main_osc(void __iomem *reg, const char *name,
+ const char *parent_name, bool bypass);
+struct clk *at91_clk_rm9200_main(void __iomem *reg, const char *name,
+ const char *parent_name);
+struct clk *at91_clk_sam9x5_main(void __iomem *reg, const char *name,
+ const char * const *parent_names, int num_parents,
+ const u32 *mux_table, int type);
+struct clk *
+sam9x60_clk_register_div_pll(void __iomem *base, const char *name,
+ const char *parent_name, u8 id,
+ const struct clk_pll_characteristics *characteristics,
+ const struct clk_pll_layout *layout, bool critical);
+struct clk *
+sam9x60_clk_register_frac_pll(void __iomem *base, const char *name,
+ const char *parent_name, u8 id,
+ const struct clk_pll_characteristics *characteristics,
+ const struct clk_pll_layout *layout, bool critical);
+struct clk *
+at91_clk_register_master(void __iomem *base, const char *name,
+ const char * const *parent_names, int num_parents,
+ const struct clk_master_layout *layout,
+ const struct clk_master_characteristics *characteristics,
+ const u32 *mux_table);
+struct clk *
+at91_clk_sama7g5_register_master(void __iomem *base, const char *name,
+ const char * const *parent_names, int num_parents,
+ const u32 *mux_table, const u32 *clk_mux_table,
+ bool critical, u8 id);
+struct clk *
+at91_clk_register_utmi(void __iomem *base, struct udevice *dev,
+ const char *name, const char *parent_name);
+struct clk *
+at91_clk_sama7g5_register_utmi(void __iomem *base, const char *name,
+ const char *parent_name);
+struct clk *
+at91_clk_register_programmable(void __iomem *base, const char *name,
+ const char * const *parent_names, u8 num_parents, u8 id,
+ const struct clk_programmable_layout *layout,
+ const u32 *clk_mux_table, const u32 *mux_table);
+struct clk *
+at91_clk_register_system(void __iomem *base, const char *name,
+ const char *parent_name, u8 id);
+struct clk *
+at91_clk_register_peripheral(void __iomem *base, const char *name,
+ const char *parent_name, u32 id);
+struct clk *
+at91_clk_register_sam9x5_peripheral(void __iomem *base,
+ const struct clk_pcr_layout *layout,
+ const char *name, const char *parent_name,
+ u32 id, const struct clk_range *range);
+struct clk *
+at91_clk_register_generic(void __iomem *base,
+ const struct clk_pcr_layout *layout, const char *name,
+ const char * const *parent_names,
+ const u32 *clk_mux_table, const u32 *mux_table,
+ u8 num_parents, u8 id, const struct clk_range *range);
+
+int at91_clk_mux_val_to_index(const u32 *table, u32 num_parents, u32 val);
+int at91_clk_mux_index_to_val(const u32 *table, u32 num_parents, u32 index);
-int at91_clk_of_xlate(struct clk *clk, struct ofnode_phandle_args *args);
-int at91_clk_probe(struct udevice *dev);
+void pmc_read(void __iomem *base, unsigned int off, unsigned int *val);
+void pmc_write(void __iomem *base, unsigned int off, unsigned int val);
+void pmc_update_bits(void __iomem *base, unsigned int off, unsigned int mask,
+ unsigned int bits);
#endif
diff --git a/drivers/clk/at91/sama7g5.c b/drivers/clk/at91/sama7g5.c
new file mode 100644
index 0000000000..b96937673b
--- /dev/null
+++ b/drivers/clk/at91/sama7g5.c
@@ -0,0 +1,1401 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * SAMA7G5 PMC clock support.
+ *
+ * Copyright (C) 2020 Microchip Technology Inc. and its subsidiaries
+ *
+ * Author: Claudiu Beznea <claudiu.beznea@microchip.com>
+ *
+ * Based on drivers/clk/at91/sama7g5.c from Linux.
+ */
+
+#include <common.h>
+#include <clk-uclass.h>
+#include <dm.h>
+#include <dt-bindings/clk/at91.h>
+#include <linux/clk-provider.h>
+
+#include "pmc.h"
+
+/**
+ * Clock identifiers to be used in conjunction with macros like
+ * AT91_TO_CLK_ID()
+ *
+ * @ID_MD_SLCK: TD slow clock identifier
+ * @ID_TD_SLCK: MD slow clock identifier
+ * @ID_MAIN_XTAL: Main Xtal clock identifier
+ * @ID_MAIN_RC: Main RC clock identifier
+ * @ID_MAIN_RC_OSC: Main RC Oscillator clock identifier
+ * @ID_MAIN_OSC: Main Oscillator clock identifier
+ * @ID_MAINCK: MAINCK clock identifier
+ * @ID_PLL_CPU_FRAC: CPU PLL fractional clock identifier
+ * @ID_PLL_CPU_DIV: CPU PLL divider clock identifier
+ * @ID_PLL_SYS_FRAC: SYS PLL fractional clock identifier
+ * @ID_PLL_SYS_DIV: SYS PLL divider clock identifier
+ * @ID_PLL_DDR_FRAC: DDR PLL fractional clock identifier
+ * @ID_PLL_DDR_DIV: DDR PLL divider clock identifier
+ * @ID_PLL_IMG_FRAC: IMC PLL fractional clock identifier
+ * @ID_PLL_IMG_DIV: IMG PLL divider clock identifier
+ * @ID_PLL_BAUD_FRAC: Baud PLL fractional clock identifier
+ * @ID_PLL_BAUD_DIV: Baud PLL divider clock identifier
+ * @ID_PLL_AUDIO_FRAC: Audio PLL fractional clock identifier
+ * @ID_PLL_AUDIO_DIVPMC: Audio PLL PMC divider clock identifier
+ * @ID_PLL_AUDIO_DIVIO: Audio PLL IO divider clock identifier
+ * @ID_PLL_ETH_FRAC: Ethernet PLL fractional clock identifier
+ * @ID_PLL_ETH_DIV: Ethernet PLL divider clock identifier
+
+ * @ID_MCK0: MCK0 clock identifier
+ * @ID_MCK1: MCK1 clock identifier
+ * @ID_MCK2: MCK2 clock identifier
+ * @ID_MCK3: MCK3 clock identifier
+ * @ID_MCK4: MCK4 clock identifier
+
+ * @ID_UTMI: UTMI clock identifier
+
+ * @ID_PROG0: Programmable 0 clock identifier
+ * @ID_PROG1: Programmable 1 clock identifier
+ * @ID_PROG2: Programmable 2 clock identifier
+ * @ID_PROG3: Programmable 3 clock identifier
+ * @ID_PROG4: Programmable 4 clock identifier
+ * @ID_PROG5: Programmable 5 clock identifier
+ * @ID_PROG6: Programmable 6 clock identifier
+ * @ID_PROG7: Programmable 7 clock identifier
+
+ * @ID_PCK0: System clock 0 clock identifier
+ * @ID_PCK1: System clock 1 clock identifier
+ * @ID_PCK2: System clock 2 clock identifier
+ * @ID_PCK3: System clock 3 clock identifier
+ * @ID_PCK4: System clock 4 clock identifier
+ * @ID_PCK5: System clock 5 clock identifier
+ * @ID_PCK6: System clock 6 clock identifier
+ * @ID_PCK7: System clock 7 clock identifier
+ */
+enum pmc_clk_ids {
+ ID_MD_SLCK = 0,
+ ID_TD_SLCK = 1,
+ ID_MAIN_XTAL = 2,
+ ID_MAIN_RC = 3,
+ ID_MAIN_RC_OSC = 4,
+ ID_MAIN_OSC = 5,
+ ID_MAINCK = 6,
+
+ ID_PLL_CPU_FRAC = 7,
+ ID_PLL_CPU_DIV = 8,
+ ID_PLL_SYS_FRAC = 9,
+ ID_PLL_SYS_DIV = 10,
+ ID_PLL_DDR_FRAC = 11,
+ ID_PLL_DDR_DIV = 12,
+ ID_PLL_IMG_FRAC = 13,
+ ID_PLL_IMG_DIV = 14,
+ ID_PLL_BAUD_FRAC = 15,
+ ID_PLL_BAUD_DIV = 16,
+ ID_PLL_AUDIO_FRAC = 17,
+ ID_PLL_AUDIO_DIVPMC = 18,
+ ID_PLL_AUDIO_DIVIO = 19,
+ ID_PLL_ETH_FRAC = 20,
+ ID_PLL_ETH_DIV = 21,
+
+ ID_MCK0 = 22,
+ ID_MCK1 = 23,
+ ID_MCK2 = 24,
+ ID_MCK3 = 25,
+ ID_MCK4 = 26,
+
+ ID_UTMI = 27,
+
+ ID_PROG0 = 28,
+ ID_PROG1 = 29,
+ ID_PROG2 = 30,
+ ID_PROG3 = 31,
+ ID_PROG4 = 32,
+ ID_PROG5 = 33,
+ ID_PROG6 = 34,
+ ID_PROG7 = 35,
+
+ ID_PCK0 = 36,
+ ID_PCK1 = 37,
+ ID_PCK2 = 38,
+ ID_PCK3 = 39,
+ ID_PCK4 = 40,
+ ID_PCK5 = 41,
+ ID_PCK6 = 42,
+ ID_PCK7 = 43,
+
+ ID_MAX,
+};
+
+/**
+ * PLL type identifiers
+ * @PLL_TYPE_FRAC: fractional PLL identifier
+ * @PLL_TYPE_DIV: divider PLL identifier
+ */
+enum pll_type {
+ PLL_TYPE_FRAC,
+ PLL_TYPE_DIV,
+};
+
+/* Clock names used as parents for multiple clocks. */
+static const char *clk_names[] = {
+ [ID_MAIN_RC_OSC] = "main_rc_osc",
+ [ID_MAIN_OSC] = "main_osc",
+ [ID_MAINCK] = "mainck",
+ [ID_PLL_CPU_DIV] = "cpupll_divpmcck",
+ [ID_PLL_SYS_DIV] = "syspll_divpmcck",
+ [ID_PLL_DDR_DIV] = "ddrpll_divpmcck",
+ [ID_PLL_IMG_DIV] = "imgpll_divpmcck",
+ [ID_PLL_BAUD_DIV] = "baudpll_divpmcck",
+ [ID_PLL_AUDIO_DIVPMC] = "audiopll_divpmcck",
+ [ID_PLL_AUDIO_DIVIO] = "audiopll_diviock",
+ [ID_PLL_ETH_DIV] = "ethpll_divpmcck",
+ [ID_MCK0] = "mck0",
+};
+
+/* Fractional PLL output range. */
+static const struct clk_range pll_outputs[] = {
+ { .min = 2343750, .max = 1200000000 },
+};
+
+/* PLL characteristics. */
+static const struct clk_pll_characteristics pll_characteristics = {
+ .input = { .min = 12000000, .max = 50000000 },
+ .num_output = ARRAY_SIZE(pll_outputs),
+ .output = pll_outputs,
+};
+
+/* Layout for fractional PLLs. */
+static const struct clk_pll_layout pll_layout_frac = {
+ .mul_mask = GENMASK(31, 24),
+ .frac_mask = GENMASK(21, 0),
+ .mul_shift = 24,
+ .frac_shift = 0,
+};
+
+/* Layout for DIVPMC dividers. */
+static const struct clk_pll_layout pll_layout_divpmc = {
+ .div_mask = GENMASK(7, 0),
+ .endiv_mask = BIT(29),
+ .div_shift = 0,
+ .endiv_shift = 29,
+};
+
+/* Layout for DIVIO dividers. */
+static const struct clk_pll_layout pll_layout_divio = {
+ .div_mask = GENMASK(19, 12),
+ .endiv_mask = BIT(30),
+ .div_shift = 12,
+ .endiv_shift = 30,
+};
+
+/* MCK0 characteristics. */
+static const struct clk_master_characteristics mck0_characteristics = {
+ .output = { .min = 140000000, .max = 200000000 },
+ .divisors = { 1, 2, 4, 3 },
+ .have_div3_pres = 1,
+};
+
+/* MCK0 layout. */
+static const struct clk_master_layout mck0_layout = {
+ .mask = 0x373,
+ .pres_shift = 4,
+ .offset = 0x28,
+};
+
+/* Programmable clock layout. */
+static const struct clk_programmable_layout programmable_layout = {
+ .pres_mask = 0xff,
+ .pres_shift = 8,
+ .css_mask = 0x1f,
+ .have_slck_mck = 0,
+ .is_pres_direct = 1,
+};
+
+/* Peripheral clock layout. */
+static const struct clk_pcr_layout sama7g5_pcr_layout = {
+ .offset = 0x88,
+ .cmd = BIT(31),
+ .gckcss_mask = GENMASK(12, 8),
+ .pid_mask = GENMASK(6, 0),
+ .div_mask = GENMASK(15, 14),
+};
+
+/**
+ * PLL clocks description
+ * @n: clock name
+ * @p: clock parent
+ * @l: clock layout
+ * @t: clock type
+ * @c: true if clock is critical and cannot be disabled
+ * @id: clock id corresponding to PLL driver
+ * @cid: clock id corresponding to clock subsystem
+ */
+static const struct {
+ const char *n;
+ const char *p;
+ const struct clk_pll_layout *l;
+ u8 t;
+ u8 c;
+ u8 id;
+ u8 cid;
+} sama7g5_plls[] = {
+ {
+ .n = "cpupll_fracck",
+ .p = "mainck",
+ .l = &pll_layout_frac,
+ .t = PLL_TYPE_FRAC,
+ .c = 1,
+ .id = 0,
+ .cid = ID_PLL_CPU_FRAC,
+ },
+
+ {
+ .n = "cpupll_divpmcck",
+ .p = "cpupll_fracck",
+ .l = &pll_layout_divpmc,
+ .t = PLL_TYPE_DIV,
+ .c = 1,
+ .id = 0,
+ .cid = ID_PLL_CPU_DIV,
+ },
+
+ {
+ .n = "syspll_fracck",
+ .p = "mainck",
+ .l = &pll_layout_frac,
+ .t = PLL_TYPE_FRAC,
+ .c = 1,
+ .id = 1,
+ .cid = ID_PLL_SYS_FRAC,
+ },
+
+ {
+ .n = "syspll_divpmcck",
+ .p = "syspll_fracck",
+ .l = &pll_layout_divpmc,
+ .t = PLL_TYPE_DIV,
+ .c = 1,
+ .id = 1,
+ .cid = ID_PLL_SYS_DIV,
+ },
+
+ {
+ .n = "ddrpll_fracck",
+ .p = "mainck",
+ .l = &pll_layout_frac,
+ .t = PLL_TYPE_FRAC,
+ .c = 1,
+ .id = 2,
+ .cid = ID_PLL_DDR_FRAC,
+ },
+
+ {
+ .n = "ddrpll_divpmcck",
+ .p = "ddrpll_fracck",
+ .l = &pll_layout_divpmc,
+ .t = PLL_TYPE_DIV,
+ .c = 1,
+ .id = 2,
+ .cid = ID_PLL_DDR_DIV,
+ },
+
+ {
+ .n = "imgpll_fracck",
+ .p = "mainck",
+ .l = &pll_layout_frac,
+ .t = PLL_TYPE_FRAC,
+ .id = 3,
+ .cid = ID_PLL_IMG_FRAC,
+ },
+
+ {
+ .n = "imgpll_divpmcck",
+ .p = "imgpll_fracck",
+ .l = &pll_layout_divpmc,
+ .t = PLL_TYPE_DIV,
+ .id = 3,
+ .cid = ID_PLL_IMG_DIV
+ },
+
+ {
+ .n = "baudpll_fracck",
+ .p = "mainck",
+ .l = &pll_layout_frac,
+ .t = PLL_TYPE_FRAC,
+ .id = 4,
+ .cid = ID_PLL_BAUD_FRAC,
+ },
+
+ {
+ .n = "baudpll_divpmcck",
+ .p = "baudpll_fracck",
+ .l = &pll_layout_divpmc,
+ .t = PLL_TYPE_DIV,
+ .id = 4,
+ .cid = ID_PLL_BAUD_DIV,
+ },
+
+ {
+ .n = "audiopll_fracck",
+ .p = "main_osc",
+ .l = &pll_layout_frac,
+ .t = PLL_TYPE_FRAC,
+ .id = 5,
+ .cid = ID_PLL_AUDIO_FRAC,
+ },
+
+ {
+ .n = "audiopll_divpmcck",
+ .p = "audiopll_fracck",
+ .l = &pll_layout_divpmc,
+ .t = PLL_TYPE_DIV,
+ .id = 5,
+ .cid = ID_PLL_AUDIO_DIVPMC,
+ },
+
+ {
+ .n = "audiopll_diviock",
+ .p = "audiopll_fracck",
+ .l = &pll_layout_divio,
+ .t = PLL_TYPE_DIV,
+ .id = 5,
+ .cid = ID_PLL_AUDIO_DIVIO,
+ },
+
+ {
+ .n = "ethpll_fracck",
+ .p = "main_osc",
+ .l = &pll_layout_frac,
+ .t = PLL_TYPE_FRAC,
+ .id = 6,
+ .cid = ID_PLL_ETH_FRAC,
+ },
+
+ {
+ .n = "ethpll_divpmcck",
+ .p = "ethpll_fracck",
+ .l = &pll_layout_divpmc,
+ .t = PLL_TYPE_DIV,
+ .id = 6,
+ .cid = ID_PLL_ETH_DIV,
+ },
+};
+
+/**
+ * Master clock (MCK[1..4]) description
+ * @n: clock name
+ * @ep: extra parents names array
+ * @ep_chg_chg_id: index in parents array that specifies the changeable
+ * parent
+ * @ep_count: extra parents count
+ * @ep_mux_table: mux table for extra parents
+ * @ep_clk_mux_table: mux table to deal with subsystem clock ids
+ * @id: clock id corresponding to MCK driver
+ * @cid: clock id corresponding to clock subsystem
+ * @c: true if clock is critical and cannot be disabled
+ */
+static const struct {
+ const char *n;
+ const char *ep[4];
+ u8 ep_count;
+ u8 ep_mux_table[4];
+ u8 ep_clk_mux_table[4];
+ u8 id;
+ u8 cid;
+ u8 c;
+} sama7g5_mckx[] = {
+ {
+ .n = "mck1",
+ .id = 1,
+ .cid = ID_MCK1,
+ .ep = { "syspll_divpmcck", },
+ .ep_mux_table = { 5, },
+ .ep_clk_mux_table = { ID_PLL_SYS_DIV, },
+ .ep_count = 1,
+ .c = 1,
+ },
+
+ {
+ .n = "mck2",
+ .id = 2,
+ .cid = ID_MCK2,
+ .ep = { "ddrpll_divpmcck", },
+ .ep_mux_table = { 6, },
+ .ep_clk_mux_table = { ID_PLL_DDR_DIV, },
+ .ep_count = 1,
+ .c = 1,
+ },
+
+ {
+ .n = "mck3",
+ .id = 3,
+ .cid = ID_MCK3,
+ .ep = { "syspll_divpmcck", "ddrpll_divpmcck", "imgpll_divpmcck", },
+ .ep_mux_table = { 5, 6, 7, },
+ .ep_clk_mux_table = { ID_PLL_SYS_DIV, ID_PLL_DDR_DIV, ID_PLL_IMG_DIV, },
+ .ep_count = 3,
+ },
+
+ {
+ .n = "mck4",
+ .id = 4,
+ .cid = ID_MCK4,
+ .ep = { "syspll_divpmcck", },
+ .ep_mux_table = { 5, },
+ .ep_clk_mux_table = { ID_PLL_SYS_DIV, },
+ .ep_count = 1,
+ .c = 1,
+ },
+};
+
+/**
+ * Programmable clock description
+ * @n: clock name
+ * @cid: clock id corresponding to clock subsystem
+ */
+static const struct {
+ const char *n;
+ u8 cid;
+} sama7g5_prog[] = {
+ { .n = "prog0", .cid = ID_PROG0, },
+ { .n = "prog1", .cid = ID_PROG1, },
+ { .n = "prog2", .cid = ID_PROG2, },
+ { .n = "prog3", .cid = ID_PROG3, },
+ { .n = "prog4", .cid = ID_PROG4, },
+ { .n = "prog5", .cid = ID_PROG5, },
+ { .n = "prog6", .cid = ID_PROG6, },
+ { .n = "prog7", .cid = ID_PROG7, },
+};
+
+/* Mux table for programmable clocks. */
+static u32 sama7g5_prog_mux_table[] = { 0, 1, 2, 3, 5, 6, 7, 8, 9, 10, };
+
+/**
+ * System clock description
+ * @n: clock name
+ * @p: parent clock name
+ * @id: clock id corresponding to system clock driver
+ * @cid: clock id corresponding to clock subsystem
+ */
+static const struct {
+ const char *n;
+ const char *p;
+ u8 id;
+ u8 cid;
+} sama7g5_systemck[] = {
+ { .n = "pck0", .p = "prog0", .id = 8, .cid = ID_PCK0, },
+ { .n = "pck1", .p = "prog1", .id = 9, .cid = ID_PCK1, },
+ { .n = "pck2", .p = "prog2", .id = 10, .cid = ID_PCK2, },
+ { .n = "pck3", .p = "prog3", .id = 11, .cid = ID_PCK3, },
+ { .n = "pck4", .p = "prog4", .id = 12, .cid = ID_PCK4, },
+ { .n = "pck5", .p = "prog5", .id = 13, .cid = ID_PCK5, },
+ { .n = "pck6", .p = "prog6", .id = 14, .cid = ID_PCK6, },
+ { .n = "pck7", .p = "prog7", .id = 15, .cid = ID_PCK7, },
+};
+
+/**
+ * Peripheral clock description
+ * @n: clock name
+ * @p: clock parent name
+ * @r: clock range values
+ * @id: clock id
+ */
+static const struct {
+ const char *n;
+ const char *p;
+ struct clk_range r;
+ u8 id;
+} sama7g5_periphck[] = {
+ { .n = "pioA_clk", .p = "mck0", .id = 11, },
+ { .n = "sfr_clk", .p = "mck1", .id = 19, },
+ { .n = "hsmc_clk", .p = "mck1", .id = 21, },
+ { .n = "xdmac0_clk", .p = "mck1", .id = 22, },
+ { .n = "xdmac1_clk", .p = "mck1", .id = 23, },
+ { .n = "xdmac2_clk", .p = "mck1", .id = 24, },
+ { .n = "acc_clk", .p = "mck1", .id = 25, },
+ { .n = "aes_clk", .p = "mck1", .id = 27, },
+ { .n = "tzaesbasc_clk", .p = "mck1", .id = 28, },
+ { .n = "asrc_clk", .p = "mck1", .id = 30, .r = { .max = 200000000, }, },
+ { .n = "cpkcc_clk", .p = "mck0", .id = 32, },
+ { .n = "csi_clk", .p = "mck3", .id = 33, .r = { .max = 266000000, }, },
+ { .n = "csi2dc_clk", .p = "mck3", .id = 34, .r = { .max = 266000000, }, },
+ { .n = "eic_clk", .p = "mck1", .id = 37, },
+ { .n = "flex0_clk", .p = "mck1", .id = 38, },
+ { .n = "flex1_clk", .p = "mck1", .id = 39, },
+ { .n = "flex2_clk", .p = "mck1", .id = 40, },
+ { .n = "flex3_clk", .p = "mck1", .id = 41, },
+ { .n = "flex4_clk", .p = "mck1", .id = 42, },
+ { .n = "flex5_clk", .p = "mck1", .id = 43, },
+ { .n = "flex6_clk", .p = "mck1", .id = 44, },
+ { .n = "flex7_clk", .p = "mck1", .id = 45, },
+ { .n = "flex8_clk", .p = "mck1", .id = 46, },
+ { .n = "flex9_clk", .p = "mck1", .id = 47, },
+ { .n = "flex10_clk", .p = "mck1", .id = 48, },
+ { .n = "flex11_clk", .p = "mck1", .id = 49, },
+ { .n = "gmac0_clk", .p = "mck1", .id = 51, },
+ { .n = "gmac1_clk", .p = "mck1", .id = 52, },
+ { .n = "gmac0_tsu_clk", .p = "mck1", .id = 53, },
+ { .n = "gmac1_tsu_clk", .p = "mck1", .id = 54, },
+ { .n = "icm_clk", .p = "mck1", .id = 55, },
+ { .n = "isc_clk", .p = "mck3", .id = 56, .r = { .max = 266000000, }, },
+ { .n = "i2smcc0_clk", .p = "mck1", .id = 57, .r = { .max = 200000000, }, },
+ { .n = "i2smcc1_clk", .p = "mck1", .id = 58, .r = { .max = 200000000, }, },
+ { .n = "matrix_clk", .p = "mck1", .id = 60, },
+ { .n = "mcan0_clk", .p = "mck1", .id = 61, .r = { .max = 200000000, }, },
+ { .n = "mcan1_clk", .p = "mck1", .id = 62, .r = { .max = 200000000, }, },
+ { .n = "mcan2_clk", .p = "mck1", .id = 63, .r = { .max = 200000000, }, },
+ { .n = "mcan3_clk", .p = "mck1", .id = 64, .r = { .max = 200000000, }, },
+ { .n = "mcan4_clk", .p = "mck1", .id = 65, .r = { .max = 200000000, }, },
+ { .n = "mcan5_clk", .p = "mck1", .id = 66, .r = { .max = 200000000, }, },
+ { .n = "pdmc0_clk", .p = "mck1", .id = 68, .r = { .max = 200000000, }, },
+ { .n = "pdmc1_clk", .p = "mck1", .id = 69, .r = { .max = 200000000, }, },
+ { .n = "pit64b0_clk", .p = "mck1", .id = 70, },
+ { .n = "pit64b1_clk", .p = "mck1", .id = 71, },
+ { .n = "pit64b2_clk", .p = "mck1", .id = 72, },
+ { .n = "pit64b3_clk", .p = "mck1", .id = 73, },
+ { .n = "pit64b4_clk", .p = "mck1", .id = 74, },
+ { .n = "pit64b5_clk", .p = "mck1", .id = 75, },
+ { .n = "pwm_clk", .p = "mck1", .id = 77, },
+ { .n = "qspi0_clk", .p = "mck1", .id = 78, },
+ { .n = "qspi1_clk", .p = "mck1", .id = 79, },
+ { .n = "sdmmc0_clk", .p = "mck1", .id = 80, },
+ { .n = "sdmmc1_clk", .p = "mck1", .id = 81, },
+ { .n = "sdmmc2_clk", .p = "mck1", .id = 82, },
+ { .n = "sha_clk", .p = "mck1", .id = 83, },
+ { .n = "spdifrx_clk", .p = "mck1", .id = 84, .r = { .max = 200000000, }, },
+ { .n = "spdiftx_clk", .p = "mck1", .id = 85, .r = { .max = 200000000, }, },
+ { .n = "ssc0_clk", .p = "mck1", .id = 86, .r = { .max = 200000000, }, },
+ { .n = "ssc1_clk", .p = "mck1", .id = 87, .r = { .max = 200000000, }, },
+ { .n = "tcb0_ch0_clk", .p = "mck1", .id = 88, .r = { .max = 200000000, }, },
+ { .n = "tcb0_ch1_clk", .p = "mck1", .id = 89, .r = { .max = 200000000, }, },
+ { .n = "tcb0_ch2_clk", .p = "mck1", .id = 90, .r = { .max = 200000000, }, },
+ { .n = "tcb1_ch0_clk", .p = "mck1", .id = 91, .r = { .max = 200000000, }, },
+ { .n = "tcb1_ch1_clk", .p = "mck1", .id = 92, .r = { .max = 200000000, }, },
+ { .n = "tcb1_ch2_clk", .p = "mck1", .id = 93, .r = { .max = 200000000, }, },
+ { .n = "tcpca_clk", .p = "mck1", .id = 94, },
+ { .n = "tcpcb_clk", .p = "mck1", .id = 95, },
+ { .n = "tdes_clk", .p = "mck1", .id = 96, },
+ { .n = "trng_clk", .p = "mck1", .id = 97, },
+ { .n = "udphsa_clk", .p = "mck1", .id = 104, },
+ { .n = "udphsb_clk", .p = "mck1", .id = 105, },
+ { .n = "uhphs_clk", .p = "mck1", .id = 106, },
+};
+
+/**
+ * Generic clock description
+ * @n: clock name
+ * @ep: extra parents names
+ * @ep_mux_table: extra parents mux table
+ * @ep_clk_mux_table: extra parents clock mux table (for CCF)
+ * @r: clock output range
+ * @ep_count: extra parents count
+ * @id: clock id
+ */
+static const struct {
+ const char *n;
+ const char *ep[8];
+ const char ep_mux_table[8];
+ const char ep_clk_mux_table[8];
+ struct clk_range r;
+ u8 ep_count;
+ u8 id;
+} sama7g5_gck[] = {
+ {
+ .n = "adc_gclk",
+ .id = 26,
+ .r = { .max = 100000000, },
+ .ep = { "syspll_divpmcck", "imgpll_divpmcck", "audiopll_divpmcck", },
+ .ep_mux_table = { 5, 7, 9, },
+ .ep_clk_mux_table = { ID_PLL_SYS_DIV, ID_PLL_IMG_DIV,
+ ID_PLL_AUDIO_DIVPMC, },
+ .ep_count = 3,
+ },
+
+ {
+ .n = "asrc_gclk",
+ .id = 30,
+ .r = { .max = 200000000 },
+ .ep = { "audiopll_divpmcck", },
+ .ep_mux_table = { 9, },
+ .ep_clk_mux_table = { ID_PLL_AUDIO_DIVPMC, },
+ .ep_count = 1,
+ },
+
+ {
+ .n = "csi_gclk",
+ .id = 33,
+ .r = { .max = 27000000 },
+ .ep = { "ddrpll_divpmcck", "imgpll_divpmcck", },
+ .ep_clk_mux_table = { ID_PLL_DDR_DIV, ID_PLL_IMG_DIV, },
+ .ep_mux_table = { 6, 7, },
+ .ep_count = 2,
+ },
+
+ {
+ .n = "flex0_gclk",
+ .id = 38,
+ .r = { .max = 200000000 },
+ .ep = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .ep_mux_table = { 5, 8, },
+ .ep_clk_mux_table = { ID_PLL_SYS_DIV, ID_PLL_BAUD_DIV, },
+ .ep_count = 2,
+ },
+
+ {
+ .n = "flex1_gclk",
+ .id = 39,
+ .r = { .max = 200000000 },
+ .ep = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .ep_mux_table = { 5, 8, },
+ .ep_clk_mux_table = { ID_PLL_SYS_DIV, ID_PLL_BAUD_DIV, },
+ .ep_count = 2,
+ },
+
+ {
+ .n = "flex2_gclk",
+ .id = 40,
+ .r = { .max = 200000000 },
+ .ep = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .ep_mux_table = { 5, 8, },
+ .ep_clk_mux_table = { ID_PLL_SYS_DIV, ID_PLL_BAUD_DIV, },
+ .ep_count = 2,
+ },
+
+ {
+ .n = "flex3_gclk",
+ .id = 41,
+ .r = { .max = 200000000 },
+ .ep = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .ep_mux_table = { 5, 8, },
+ .ep_clk_mux_table = { ID_PLL_SYS_DIV, ID_PLL_BAUD_DIV, },
+ .ep_count = 2,
+ },
+
+ {
+ .n = "flex4_gclk",
+ .id = 42,
+ .r = { .max = 200000000 },
+ .ep = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .ep_mux_table = { 5, 8, },
+ .ep_clk_mux_table = { ID_PLL_SYS_DIV, ID_PLL_BAUD_DIV, },
+ .ep_count = 2,
+ },
+
+ {
+ .n = "flex5_gclk",
+ .id = 43,
+ .r = { .max = 200000000 },
+ .ep = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .ep_mux_table = { 5, 8, },
+ .ep_clk_mux_table = { ID_PLL_SYS_DIV, ID_PLL_BAUD_DIV, },
+ .ep_count = 2,
+ },
+
+ {
+ .n = "flex6_gclk",
+ .id = 44,
+ .r = { .max = 200000000 },
+ .ep = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .ep_mux_table = { 5, 8, },
+ .ep_clk_mux_table = { ID_PLL_SYS_DIV, ID_PLL_BAUD_DIV, },
+ .ep_count = 2,
+ },
+
+ {
+ .n = "flex7_gclk",
+ .id = 45,
+ .r = { .max = 200000000 },
+ .ep = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .ep_mux_table = { 5, 8, },
+ .ep_clk_mux_table = { ID_PLL_SYS_DIV, ID_PLL_BAUD_DIV, },
+ .ep_count = 2,
+ },
+
+ {
+ .n = "flex8_gclk",
+ .id = 46,
+ .r = { .max = 200000000 },
+ .ep = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .ep_mux_table = { 5, 8, },
+ .ep_clk_mux_table = { ID_PLL_SYS_DIV, ID_PLL_BAUD_DIV, },
+ .ep_count = 2,
+ },
+
+ {
+ .n = "flex9_gclk",
+ .id = 47,
+ .r = { .max = 200000000 },
+ .ep = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .ep_mux_table = { 5, 8, },
+ .ep_clk_mux_table = { ID_PLL_SYS_DIV, ID_PLL_BAUD_DIV, },
+ .ep_count = 2,
+ },
+
+ {
+ .n = "flex10_gclk",
+ .id = 48,
+ .r = { .max = 200000000 },
+ .ep = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .ep_mux_table = { 5, 8, },
+ .ep_clk_mux_table = { ID_PLL_SYS_DIV, ID_PLL_BAUD_DIV, },
+ .ep_count = 2,
+ },
+
+ {
+ .n = "flex11_gclk",
+ .id = 49,
+ .r = { .max = 200000000 },
+ .ep = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .ep_mux_table = { 5, 8, },
+ .ep_clk_mux_table = { ID_PLL_SYS_DIV, ID_PLL_BAUD_DIV, },
+ .ep_count = 2,
+ },
+
+ {
+ .n = "gmac0_gclk",
+ .id = 51,
+ .r = { .max = 125000000 },
+ .ep = { "ethpll_divpmcck", },
+ .ep_clk_mux_table = { ID_PLL_ETH_DIV, },
+ .ep_mux_table = { 10, },
+ .ep_count = 1,
+ },
+
+ {
+ .n = "gmac1_gclk",
+ .id = 52,
+ .r = { .max = 50000000 },
+ .ep = { "ethpll_divpmcck", },
+ .ep_mux_table = { 10, },
+ .ep_clk_mux_table = { ID_PLL_ETH_DIV, },
+ .ep_count = 1,
+ },
+
+ {
+ .n = "gmac0_tsu_gclk",
+ .id = 53,
+ .r = { .max = 300000000 },
+ .ep = { "audiopll_divpmcck", "ethpll_divpmcck", },
+ .ep_mux_table = { 9, 10, },
+ .ep_clk_mux_table = { ID_PLL_AUDIO_DIVPMC, ID_PLL_ETH_DIV, },
+ .ep_count = 2,
+ },
+
+ {
+ .n = "gmac1_tsu_gclk",
+ .id = 54,
+ .r = { .max = 300000000 },
+ .ep = { "audiopll_divpmcck", "ethpll_divpmcck", },
+ .ep_mux_table = { 9, 10, },
+ .ep_clk_mux_table = { ID_PLL_AUDIO_DIVPMC, ID_PLL_ETH_DIV, },
+ .ep_count = 2,
+ },
+
+ {
+ .n = "i2smcc0_gclk",
+ .id = 57,
+ .r = { .max = 100000000 },
+ .ep = { "syspll_divpmcck", "audiopll_divpmcck", },
+ .ep_mux_table = { 5, 9, },
+ .ep_clk_mux_table = { ID_PLL_SYS_DIV, ID_PLL_AUDIO_DIVPMC, },
+ .ep_count = 2,
+ },
+
+ {
+ .n = "i2smcc1_gclk",
+ .id = 58,
+ .r = { .max = 100000000 },
+ .ep = { "syspll_divpmcck", "audiopll_divpmcck", },
+ .ep_mux_table = { 5, 9, },
+ .ep_clk_mux_table = { ID_PLL_SYS_DIV, ID_PLL_AUDIO_DIVPMC, },
+ .ep_count = 2,
+ },
+
+ {
+ .n = "mcan0_gclk",
+ .id = 61,
+ .r = { .max = 200000000 },
+ .ep = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .ep_mux_table = { 5, 8, },
+ .ep_clk_mux_table = { ID_PLL_SYS_DIV, ID_PLL_BAUD_DIV, },
+ .ep_count = 2,
+ },
+
+ {
+ .n = "mcan1_gclk",
+ .id = 62,
+ .r = { .max = 200000000 },
+ .ep = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .ep_mux_table = { 5, 8, },
+ .ep_clk_mux_table = { ID_PLL_SYS_DIV, ID_PLL_BAUD_DIV, },
+ .ep_count = 2,
+ },
+
+ {
+ .n = "mcan2_gclk",
+ .id = 63,
+ .r = { .max = 200000000 },
+ .ep = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .ep_mux_table = { 5, 8, },
+ .ep_clk_mux_table = { ID_PLL_SYS_DIV, ID_PLL_BAUD_DIV, },
+ .ep_count = 2,
+ },
+
+ {
+ .n = "mcan3_gclk",
+ .id = 64,
+ .r = { .max = 200000000 },
+ .ep = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .ep_mux_table = { 5, 8, },
+ .ep_clk_mux_table = { ID_PLL_SYS_DIV, ID_PLL_BAUD_DIV, },
+ .ep_count = 2,
+ },
+
+ {
+ .n = "mcan4_gclk",
+ .id = 65,
+ .r = { .max = 200000000 },
+ .ep = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .ep_mux_table = { 5, 8, },
+ .ep_clk_mux_table = { ID_PLL_SYS_DIV, ID_PLL_BAUD_DIV, },
+ .ep_count = 2,
+ },
+
+ {
+ .n = "mcan5_gclk",
+ .id = 66,
+ .r = { .max = 200000000 },
+ .ep = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .ep_mux_table = { 5, 8, },
+ .ep_clk_mux_table = { ID_PLL_SYS_DIV, ID_PLL_BAUD_DIV, },
+ .ep_count = 2,
+ },
+
+ {
+ .n = "pdmc0_gclk",
+ .id = 68,
+ .r = { .max = 50000000 },
+ .ep = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .ep_mux_table = { 5, 8, },
+ .ep_clk_mux_table = { ID_PLL_SYS_DIV, ID_PLL_BAUD_DIV, },
+ .ep_count = 2,
+ },
+
+ {
+ .n = "pdmc1_gclk",
+ .id = 69,
+ .r = { .max = 50000000, },
+ .ep = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .ep_mux_table = { 5, 8, },
+ .ep_clk_mux_table = { ID_PLL_SYS_DIV, ID_PLL_BAUD_DIV, },
+ .ep_count = 2,
+ },
+
+ {
+ .n = "pit64b0_gclk",
+ .id = 70,
+ .r = { .max = 200000000 },
+ .ep = { "syspll_divpmcck", "imgpll_divpmcck", "baudpll_divpmcck",
+ "audiopll_divpmcck", "ethpll_divpmcck", },
+ .ep_mux_table = { 5, 7, 8, 9, 10, },
+ .ep_clk_mux_table = { ID_PLL_SYS_DIV, ID_PLL_IMG_DIV,
+ ID_PLL_BAUD_DIV, ID_PLL_AUDIO_DIVPMC,
+ ID_PLL_ETH_DIV, },
+ .ep_count = 5,
+ },
+
+ {
+ .n = "pit64b1_gclk",
+ .id = 71,
+ .r = { .max = 200000000 },
+ .ep = { "syspll_divpmcck", "imgpll_divpmcck", "baudpll_divpmcck",
+ "audiopll_divpmcck", "ethpll_divpmcck", },
+ .ep_mux_table = { 5, 7, 8, 9, 10, },
+ .ep_clk_mux_table = { ID_PLL_SYS_DIV, ID_PLL_IMG_DIV,
+ ID_PLL_BAUD_DIV, ID_PLL_AUDIO_DIVPMC,
+ ID_PLL_ETH_DIV, },
+ .ep_count = 5,
+ },
+
+ {
+ .n = "pit64b2_gclk",
+ .id = 72,
+ .r = { .max = 200000000 },
+ .ep = { "syspll_divpmcck", "imgpll_divpmcck", "baudpll_divpmcck",
+ "audiopll_divpmcck", "ethpll_divpmcck", },
+ .ep_mux_table = { 5, 7, 8, 9, 10, },
+ .ep_clk_mux_table = { ID_PLL_SYS_DIV, ID_PLL_IMG_DIV,
+ ID_PLL_BAUD_DIV, ID_PLL_AUDIO_DIVPMC,
+ ID_PLL_ETH_DIV, },
+ .ep_count = 5,
+ },
+
+ {
+ .n = "pit64b3_gclk",
+ .id = 73,
+ .r = { .max = 200000000 },
+ .ep = { "syspll_divpmcck", "imgpll_divpmcck", "baudpll_divpmcck",
+ "audiopll_divpmcck", "ethpll_divpmcck", },
+ .ep_mux_table = { 5, 7, 8, 9, 10, },
+ .ep_clk_mux_table = { ID_PLL_SYS_DIV, ID_PLL_IMG_DIV,
+ ID_PLL_BAUD_DIV, ID_PLL_AUDIO_DIVPMC,
+ ID_PLL_ETH_DIV, },
+ .ep_count = 5,
+ },
+
+ {
+ .n = "pit64b4_gclk",
+ .id = 74,
+ .r = { .max = 200000000 },
+ .ep = { "syspll_divpmcck", "imgpll_divpmcck", "baudpll_divpmcck",
+ "audiopll_divpmcck", "ethpll_divpmcck", },
+ .ep_mux_table = { 5, 7, 8, 9, 10, },
+ .ep_clk_mux_table = { ID_PLL_SYS_DIV, ID_PLL_IMG_DIV,
+ ID_PLL_BAUD_DIV, ID_PLL_AUDIO_DIVPMC,
+ ID_PLL_ETH_DIV, },
+ .ep_count = 5,
+ },
+
+ {
+ .n = "pit64b5_gclk",
+ .id = 75,
+ .r = { .max = 200000000 },
+ .ep = { "syspll_divpmcck", "imgpll_divpmcck", "baudpll_divpmcck",
+ "audiopll_divpmcck", "ethpll_divpmcck", },
+ .ep_mux_table = { 5, 7, 8, 9, 10, },
+ .ep_clk_mux_table = { ID_PLL_SYS_DIV, ID_PLL_IMG_DIV,
+ ID_PLL_BAUD_DIV, ID_PLL_AUDIO_DIVPMC,
+ ID_PLL_ETH_DIV, },
+ .ep_count = 5,
+ },
+
+ {
+ .n = "qspi0_gclk",
+ .id = 78,
+ .r = { .max = 200000000 },
+ .ep = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .ep_mux_table = { 5, 8, },
+ .ep_clk_mux_table = { ID_PLL_SYS_DIV, ID_PLL_IMG_DIV,
+ ID_PLL_BAUD_DIV, ID_PLL_AUDIO_DIVPMC,
+ ID_PLL_ETH_DIV, },
+ .ep_count = 2,
+ },
+
+ {
+ .n = "qspi1_gclk",
+ .id = 79,
+ .r = { .max = 200000000 },
+ .ep = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .ep_mux_table = { 5, 8, },
+ .ep_clk_mux_table = { ID_PLL_SYS_DIV, ID_PLL_BAUD_DIV, },
+ .ep_count = 2,
+ },
+
+ {
+ .n = "sdmmc0_gclk",
+ .id = 80,
+ .r = { .max = 208000000 },
+ .ep = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .ep_mux_table = { 5, 8, },
+ .ep_clk_mux_table = { ID_PLL_SYS_DIV, ID_PLL_BAUD_DIV, },
+ .ep_count = 2,
+ },
+
+ {
+ .n = "sdmmc1_gclk",
+ .id = 81,
+ .r = { .max = 208000000 },
+ .ep = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .ep_mux_table = { 5, 8, },
+ .ep_clk_mux_table = { ID_PLL_SYS_DIV, ID_PLL_BAUD_DIV, },
+ .ep_count = 2,
+ },
+
+ {
+ .n = "sdmmc2_gclk",
+ .id = 82,
+ .r = { .max = 208000000 },
+ .ep = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .ep_mux_table = { 5, 8, },
+ .ep_clk_mux_table = { ID_PLL_SYS_DIV, ID_PLL_BAUD_DIV, },
+ .ep_count = 2,
+ },
+
+ {
+ .n = "spdifrx_gclk",
+ .id = 84,
+ .r = { .max = 150000000 },
+ .ep = { "syspll_divpmcck", "audiopll_divpmcck", },
+ .ep_mux_table = { 5, 9, },
+ .ep_clk_mux_table = { ID_PLL_SYS_DIV, ID_PLL_AUDIO_DIVPMC, },
+ .ep_count = 2,
+ },
+
+ {
+ .n = "spdiftx_gclk",
+ .id = 85,
+ .r = { .max = 25000000 },
+ .ep = { "syspll_divpmcck", "audiopll_divpmcck", },
+ .ep_mux_table = { 5, 9, },
+ .ep_clk_mux_table = { ID_PLL_SYS_DIV, ID_PLL_AUDIO_DIVPMC, },
+ .ep_count = 2,
+ },
+
+ {
+ .n = "tcb0_ch0_gclk",
+ .id = 88,
+ .r = { .max = 200000000 },
+ .ep = { "syspll_divpmcck", "imgpll_divpmcck", "baudpll_divpmcck",
+ "audiopll_divpmcck", "ethpll_divpmcck", },
+ .ep_mux_table = { 5, 7, 8, 9, 10, },
+ .ep_clk_mux_table = { ID_PLL_SYS_DIV, ID_PLL_IMG_DIV,
+ ID_PLL_BAUD_DIV, ID_PLL_AUDIO_DIVPMC,
+ ID_PLL_ETH_DIV, },
+ .ep_count = 5,
+ },
+
+ {
+ .n = "tcb1_ch0_gclk",
+ .id = 91,
+ .r = { .max = 200000000 },
+ .ep = { "syspll_divpmcck", "imgpll_divpmcck", "baudpll_divpmcck",
+ "audiopll_divpmcck", "ethpll_divpmcck", },
+ .ep_mux_table = { 5, 7, 8, 9, 10, },
+ .ep_clk_mux_table = { ID_PLL_SYS_DIV, ID_PLL_IMG_DIV,
+ ID_PLL_BAUD_DIV, ID_PLL_AUDIO_DIVPMC,
+ ID_PLL_ETH_DIV, },
+ .ep_count = 5,
+ },
+};
+
+/**
+ * Clock setup description
+ * @cid: clock id corresponding to clock subsystem
+ * @pid: parent clock id corresponding to clock subsystem
+ * @rate: clock rate
+ * @prate: parent rate
+ */
+static const struct pmc_clk_setup {
+ unsigned int cid;
+ unsigned int pid;
+ unsigned long rate;
+ unsigned long prate;
+} sama7g5_clk_setup[] = {
+ {
+ .cid = AT91_TO_CLK_ID(PMC_TYPE_CORE, ID_PLL_ETH_FRAC),
+ .rate = 625000000,
+ },
+
+ {
+ .cid = AT91_TO_CLK_ID(PMC_TYPE_CORE, ID_PLL_ETH_DIV),
+ .rate = 625000000,
+ },
+};
+
+#define SAMA7G5_MAX_MUX_ALLOCS (64)
+
+#define prepare_mux_table(_allocs, _index, _dst, _src, _num, _label) \
+ do { \
+ int _i; \
+ if ((_index) >= SAMA7G5_MAX_MUX_ALLOCS) { \
+ debug("%s(): AT91: MUX: insufficient space\n", \
+ __func__); \
+ goto _label; \
+ } \
+ (_dst) = kzalloc(sizeof(*(_dst)) * (_num), GFP_KERNEL); \
+ if (!(_dst)) \
+ goto _label; \
+ (_allocs)[(_index)++] = (_dst); \
+ for (_i = 0; _i < (_num); _i++) \
+ (_dst)[_i] = (_src)[_i]; \
+ } while (0)
+
+static int sama7g5_clk_probe(struct udevice *dev)
+{
+ void __iomem *base = (void *)devfdt_get_addr(dev);
+ unsigned int *clkmuxallocs[SAMA7G5_MAX_MUX_ALLOCS];
+ unsigned int *muxallocs[SAMA7G5_MAX_MUX_ALLOCS];
+ const char *p[10];
+ unsigned int cm[10], m[10], *tmpclkmux, *tmpmux;
+ struct clk clk, *c, *parent;
+ bool main_osc_bypass;
+ int ret, muxallocindex = 0, clkmuxallocindex = 0, i, j;
+
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ memset(muxallocs, 0, ARRAY_SIZE(muxallocs));
+ memset(clkmuxallocs, 0, ARRAY_SIZE(clkmuxallocs));
+
+ ret = clk_get_by_index(dev, 0, &clk);
+ if (ret)
+ return ret;
+ ret = clk_get_by_id(clk.id, &c);
+ if (ret)
+ return ret;
+ clk_names[ID_TD_SLCK] = kmemdup(clk_hw_get_name(c),
+ strlen(clk_hw_get_name(c)) + 1, GFP_KERNEL);
+ if (!clk_names[ID_TD_SLCK])
+ return -ENOMEM;
+
+ ret = clk_get_by_index(dev, 1, &clk);
+ if (ret)
+ return ret;
+ ret = clk_get_by_id(clk.id, &c);
+ if (ret)
+ return ret;
+ clk_names[ID_MD_SLCK] = kmemdup(clk_hw_get_name(c),
+ strlen(clk_hw_get_name(c)) + 1, GFP_KERNEL);
+ if (!clk_names[ID_MD_SLCK])
+ return -ENOMEM;
+
+ ret = clk_get_by_index(dev, 2, &clk);
+ if (ret)
+ return ret;
+ clk_names[ID_MAIN_XTAL] = kmemdup(clk_hw_get_name(&clk),
+ strlen(clk_hw_get_name(&clk)) + 1, GFP_KERNEL);
+ if (!clk_names[ID_MAIN_XTAL])
+ return -ENOMEM;
+
+ ret = clk_get_by_index(dev, 3, &clk);
+ if (ret)
+ goto fail;
+ clk_names[ID_MAIN_RC] = kmemdup(clk_hw_get_name(&clk),
+ strlen(clk_hw_get_name(&clk)) + 1, GFP_KERNEL);
+ if (ret)
+ goto fail;
+
+ main_osc_bypass = dev_read_bool(dev, "atmel,main-osc-bypass");
+
+ /* Register main rc oscillator. */
+ clk_dm(AT91_TO_CLK_ID(PMC_TYPE_CORE, ID_MAIN_RC_OSC),
+ at91_clk_main_rc(base, clk_names[ID_MAIN_RC_OSC],
+ clk_names[ID_MAIN_RC]));
+
+ /* Register main oscillator. */
+ clk_dm(AT91_TO_CLK_ID(PMC_TYPE_CORE, ID_MAIN_OSC),
+ at91_clk_main_osc(base, clk_names[ID_MAIN_OSC],
+ clk_names[ID_MAIN_XTAL], main_osc_bypass));
+
+ /* Register mainck. */
+ p[0] = clk_names[ID_MAIN_RC_OSC];
+ p[1] = clk_names[ID_MAIN_OSC];
+ cm[0] = AT91_TO_CLK_ID(PMC_TYPE_CORE, ID_MAIN_RC_OSC);
+ cm[1] = AT91_TO_CLK_ID(PMC_TYPE_CORE, ID_MAIN_OSC);
+ prepare_mux_table(clkmuxallocs, clkmuxallocindex, tmpclkmux, cm, 2,
+ fail);
+ clk_dm(AT91_TO_CLK_ID(PMC_TYPE_CORE, ID_MAINCK),
+ at91_clk_sam9x5_main(base, clk_names[ID_MAINCK], p,
+ 2, tmpclkmux, PMC_TYPE_CORE));
+
+ /* Register PLL fracs clocks. */
+ for (i = 0; i < ARRAY_SIZE(sama7g5_plls); i++) {
+ if (sama7g5_plls[i].t != PLL_TYPE_FRAC)
+ continue;
+
+ clk_dm(AT91_TO_CLK_ID(PMC_TYPE_CORE, sama7g5_plls[i].cid),
+ sam9x60_clk_register_frac_pll(base, sama7g5_plls[i].n,
+ sama7g5_plls[i].p, sama7g5_plls[i].id,
+ &pll_characteristics, sama7g5_plls[i].l,
+ sama7g5_plls[i].c));
+ }
+
+ /* Register PLL div clocks. */
+ for (i = 0; i < ARRAY_SIZE(sama7g5_plls); i++) {
+ if (sama7g5_plls[i].t != PLL_TYPE_DIV)
+ continue;
+
+ clk_dm(AT91_TO_CLK_ID(PMC_TYPE_CORE, sama7g5_plls[i].cid),
+ sam9x60_clk_register_div_pll(base, sama7g5_plls[i].n,
+ sama7g5_plls[i].p, sama7g5_plls[i].id,
+ &pll_characteristics, sama7g5_plls[i].l,
+ sama7g5_plls[i].c));
+ }
+
+ /* Register MCK0 clock. */
+ p[0] = clk_names[ID_MD_SLCK];
+ p[1] = clk_names[ID_MAINCK];
+ p[2] = clk_names[ID_PLL_CPU_DIV];
+ p[3] = clk_names[ID_PLL_SYS_DIV];
+ cm[0] = AT91_TO_CLK_ID(PMC_TYPE_CORE, ID_MD_SLCK);
+ cm[1] = AT91_TO_CLK_ID(PMC_TYPE_CORE, ID_MAINCK);
+ cm[2] = AT91_TO_CLK_ID(PMC_TYPE_CORE, ID_PLL_CPU_DIV);
+ cm[3] = AT91_TO_CLK_ID(PMC_TYPE_CORE, ID_PLL_SYS_DIV);
+ prepare_mux_table(clkmuxallocs, clkmuxallocindex, tmpclkmux, cm, 2,
+ fail);
+ clk_dm(AT91_TO_CLK_ID(PMC_TYPE_CORE, ID_MCK0),
+ at91_clk_register_master(base, clk_names[ID_MCK0], p,
+ 4, &mck0_layout, &mck0_characteristics, tmpclkmux));
+
+ /* Register MCK1-4 clocks. */
+ p[0] = clk_names[ID_MD_SLCK];
+ p[1] = clk_names[ID_TD_SLCK];
+ p[2] = clk_names[ID_MAINCK];
+ p[3] = clk_names[ID_MCK0];
+ m[0] = 0;
+ m[1] = 1;
+ m[2] = 2;
+ m[3] = 3;
+ cm[0] = AT91_TO_CLK_ID(PMC_TYPE_CORE, ID_MD_SLCK);
+ cm[1] = AT91_TO_CLK_ID(PMC_TYPE_CORE, ID_TD_SLCK);
+ cm[2] = AT91_TO_CLK_ID(PMC_TYPE_CORE, ID_MAINCK);
+ cm[3] = AT91_TO_CLK_ID(PMC_TYPE_CORE, ID_MCK0);
+ for (i = 0; i < ARRAY_SIZE(sama7g5_mckx); i++) {
+ for (j = 0; j < sama7g5_mckx[i].ep_count; j++) {
+ p[4 + j] = sama7g5_mckx[i].ep[j];
+ m[4 + j] = sama7g5_mckx[i].ep_mux_table[j];
+ cm[4 + j] = AT91_TO_CLK_ID(PMC_TYPE_CORE,
+ sama7g5_mckx[i].ep_clk_mux_table[j]);
+ }
+
+ prepare_mux_table(clkmuxallocs, clkmuxallocindex, tmpclkmux, cm,
+ 4 + sama7g5_mckx[i].ep_count, fail);
+ prepare_mux_table(muxallocs, muxallocindex, tmpmux, m,
+ 4 + sama7g5_mckx[i].ep_count, fail);
+
+ clk_dm(AT91_TO_CLK_ID(PMC_TYPE_CORE, sama7g5_mckx[i].cid),
+ at91_clk_sama7g5_register_master(base,
+ sama7g5_mckx[i].n, p, 4 + sama7g5_mckx[i].ep_count,
+ tmpmux, tmpclkmux, sama7g5_mckx[i].c,
+ sama7g5_mckx[i].id));
+ }
+
+ /* Register UTMI clock. */
+ clk_dm(AT91_TO_CLK_ID(PMC_TYPE_CORE, ID_UTMI),
+ at91_clk_sama7g5_register_utmi(base, "utmick",
+ clk_names[ID_MAIN_XTAL]));
+
+ /* Register programmable clocks. */
+ p[0] = clk_names[ID_MD_SLCK];
+ p[1] = clk_names[ID_TD_SLCK];
+ p[2] = clk_names[ID_MAINCK];
+ p[3] = clk_names[ID_MCK0];
+ p[4] = clk_names[ID_PLL_SYS_DIV];
+ p[5] = clk_names[ID_PLL_DDR_DIV];
+ p[6] = clk_names[ID_PLL_IMG_DIV];
+ p[7] = clk_names[ID_PLL_BAUD_DIV];
+ p[8] = clk_names[ID_PLL_AUDIO_DIVPMC];
+ p[9] = clk_names[ID_PLL_ETH_DIV];
+ cm[0] = AT91_TO_CLK_ID(PMC_TYPE_CORE, ID_MD_SLCK);
+ cm[1] = AT91_TO_CLK_ID(PMC_TYPE_CORE, ID_TD_SLCK);
+ cm[2] = AT91_TO_CLK_ID(PMC_TYPE_CORE, ID_MAINCK);
+ cm[3] = AT91_TO_CLK_ID(PMC_TYPE_CORE, ID_MCK0);
+ cm[4] = AT91_TO_CLK_ID(PMC_TYPE_CORE, ID_PLL_SYS_DIV);
+ cm[5] = AT91_TO_CLK_ID(PMC_TYPE_CORE, ID_PLL_DDR_DIV);
+ cm[6] = AT91_TO_CLK_ID(PMC_TYPE_CORE, ID_PLL_IMG_DIV);
+ cm[7] = AT91_TO_CLK_ID(PMC_TYPE_CORE, ID_PLL_BAUD_DIV);
+ cm[8] = AT91_TO_CLK_ID(PMC_TYPE_CORE, ID_PLL_AUDIO_DIVPMC);
+ cm[9] = AT91_TO_CLK_ID(PMC_TYPE_CORE, ID_PLL_ETH_DIV);
+ for (i = 0; i < ARRAY_SIZE(sama7g5_prog); i++) {
+ prepare_mux_table(clkmuxallocs, clkmuxallocindex, tmpclkmux, cm,
+ 10, fail);
+
+ clk_dm(AT91_TO_CLK_ID(PMC_TYPE_CORE, sama7g5_prog[i].cid),
+ at91_clk_register_programmable(base, sama7g5_prog[i].n,
+ p, 10, i, &programmable_layout, tmpclkmux,
+ sama7g5_prog_mux_table));
+ }
+
+ /* System clocks. */
+ for (i = 0; i < ARRAY_SIZE(sama7g5_systemck); i++) {
+ clk_dm(AT91_TO_CLK_ID(PMC_TYPE_SYSTEM, sama7g5_systemck[i].cid),
+ at91_clk_register_system(base, sama7g5_systemck[i].n,
+ sama7g5_systemck[i].p, sama7g5_systemck[i].id));
+ }
+
+ /* Peripheral clocks. */
+ for (i = 0; i < ARRAY_SIZE(sama7g5_periphck); i++) {
+ clk_dm(AT91_TO_CLK_ID(PMC_TYPE_PERIPHERAL,
+ sama7g5_periphck[i].id),
+ at91_clk_register_sam9x5_peripheral(base,
+ &sama7g5_pcr_layout, sama7g5_periphck[i].n,
+ sama7g5_periphck[i].p, sama7g5_periphck[i].id,
+ &sama7g5_periphck[i].r));
+ }
+
+ /* Generic clocks. */
+ p[0] = clk_names[ID_MD_SLCK];
+ p[1] = clk_names[ID_TD_SLCK];
+ p[2] = clk_names[ID_MAINCK];
+ p[3] = clk_names[ID_MCK0];
+ m[0] = 0;
+ m[1] = 1;
+ m[2] = 2;
+ m[3] = 3;
+ cm[0] = AT91_TO_CLK_ID(PMC_TYPE_CORE, ID_MD_SLCK);
+ cm[1] = AT91_TO_CLK_ID(PMC_TYPE_CORE, ID_TD_SLCK);
+ cm[2] = AT91_TO_CLK_ID(PMC_TYPE_CORE, ID_MAINCK);
+ cm[3] = AT91_TO_CLK_ID(PMC_TYPE_CORE, ID_MCK0);
+ for (i = 0; i < ARRAY_SIZE(sama7g5_gck); i++) {
+ for (j = 0; j < sama7g5_gck[i].ep_count; j++) {
+ p[4 + j] = sama7g5_gck[i].ep[j];
+ m[4 + j] = sama7g5_gck[i].ep_mux_table[j];
+ cm[4 + j] = AT91_TO_CLK_ID(PMC_TYPE_CORE,
+ sama7g5_gck[i].ep_clk_mux_table[j]);
+ }
+
+ prepare_mux_table(clkmuxallocs, clkmuxallocindex, tmpclkmux, cm,
+ 4 + sama7g5_gck[i].ep_count, fail);
+ prepare_mux_table(muxallocs, muxallocindex, tmpmux, m,
+ 4 + sama7g5_gck[i].ep_count, fail);
+
+ clk_dm(AT91_TO_CLK_ID(PMC_TYPE_GCK, sama7g5_gck[i].id),
+ at91_clk_register_generic(base, &sama7g5_pcr_layout,
+ sama7g5_gck[i].n, p, tmpclkmux, tmpmux,
+ 4 + sama7g5_gck[i].ep_count, sama7g5_gck[i].id,
+ &sama7g5_gck[i].r));
+ }
+
+ /* Setup clocks. */
+ for (i = 0; i < ARRAY_SIZE(sama7g5_clk_setup); i++) {
+ ret = clk_get_by_id(sama7g5_clk_setup[i].cid, &c);
+ if (ret)
+ goto fail;
+
+ if (sama7g5_clk_setup[i].pid) {
+ ret = clk_get_by_id(sama7g5_clk_setup[i].pid, &parent);
+ if (ret)
+ goto fail;
+
+ ret = clk_set_parent(c, parent);
+ if (ret)
+ goto fail;
+
+ if (sama7g5_clk_setup[i].prate) {
+ ret = clk_set_rate(parent,
+ sama7g5_clk_setup[i].prate);
+ if (ret < 0)
+ goto fail;
+ }
+ }
+
+ if (sama7g5_clk_setup[i].rate) {
+ ret = clk_set_rate(c, sama7g5_clk_setup[i].rate);
+ if (ret < 0)
+ goto fail;
+ }
+ }
+
+ return 0;
+
+fail:
+ for (i = 0; i < ARRAY_SIZE(muxallocs); i++)
+ kfree(muxallocs[i]);
+
+ for (i = 0; i < ARRAY_SIZE(clkmuxallocs); i++)
+ kfree(clkmuxallocs[i]);
+
+ return -ENOMEM;
+}
+
+static const struct udevice_id sama7g5_clk_ids[] = {
+ { .compatible = "microchip,sama7g5-pmc" },
+ { /* Sentinel. */ },
+};
+
+U_BOOT_DRIVER(at91_sama7g5_pmc) = {
+ .name = "at91-sama7g5-pmc",
+ .id = UCLASS_CLK,
+ .of_match = sama7g5_clk_ids,
+ .ops = &at91_clk_ops,
+ .probe = sama7g5_clk_probe,
+ .flags = DM_FLAG_PRE_RELOC,
+};
diff --git a/drivers/clk/at91/sckc.c b/drivers/clk/at91/sckc.c
index a879b008ff..dd62dc5510 100644
--- a/drivers/clk/at91/sckc.c
+++ b/drivers/clk/at91/sckc.c
@@ -1,19 +1,172 @@
// SPDX-License-Identifier: GPL-2.0+
/*
- * Copyright (C) 2016 Atmel Corporation
- * Wenyou.Yang <wenyou.yang@atmel.com>
+ * Slow clock support for AT91 architectures.
+ *
+ * Copyright (C) 2020 Microchip Technology Inc. and its subsidiaries
+ *
+ * Author: Claudiu Beznea <claudiu.beznea@microchip.com>
*/
#include <common.h>
+#include <clk-uclass.h>
#include <dm.h>
+#include <dt-bindings/clk/at91.h>
+#include <linux/clk-provider.h>
-static const struct udevice_id at91_sckc_match[] = {
- { .compatible = "atmel,at91sam9x5-sckc" },
- {}
+#include "pmc.h"
+
+#define UBOOT_DM_CLK_AT91_SAM9X60_TD_SLCK "at91-sam9x60-td-slck"
+#define UBOOT_DM_CLK_AT91_SCKC "at91-sckc"
+
+#define AT91_OSC_SEL BIT(24)
+#define AT91_OSC_SEL_SHIFT (24)
+
+struct sam9x60_sckc {
+ void __iomem *reg;
+ const char **parent_names;
+ unsigned int num_parents;
+ struct clk clk;
+};
+
+#define to_sam9x60_sckc(c) container_of(c, struct sam9x60_sckc, clk)
+
+static int sam9x60_sckc_of_xlate(struct clk *clk,
+ struct ofnode_phandle_args *args)
+{
+ if (args->args_count != 1) {
+ debug("AT91: SCKC: Invalid args_count: %d\n", args->args_count);
+ return -EINVAL;
+ }
+
+ clk->id = AT91_TO_CLK_ID(PMC_TYPE_SLOW, args->args[0]);
+
+ return 0;
+}
+
+static const struct clk_ops sam9x60_sckc_ops = {
+ .of_xlate = sam9x60_sckc_of_xlate,
+ .get_rate = clk_generic_get_rate,
+};
+
+static int sam9x60_td_slck_set_parent(struct clk *clk, struct clk *parent)
+{
+ struct sam9x60_sckc *sckc = to_sam9x60_sckc(clk);
+ u32 i;
+
+ for (i = 0; i < sckc->num_parents; i++) {
+ if (!strcmp(parent->dev->name, sckc->parent_names[i]))
+ break;
+ }
+ if (i == sckc->num_parents)
+ return -EINVAL;
+
+ pmc_update_bits(sckc->reg, 0, AT91_OSC_SEL, (i << AT91_OSC_SEL_SHIFT));
+
+ return 0;
+}
+
+static const struct clk_ops sam9x60_td_slck_ops = {
+ .get_rate = clk_generic_get_rate,
+ .set_parent = sam9x60_td_slck_set_parent,
+};
+
+static struct clk *at91_sam9x60_clk_register_td_slck(struct sam9x60_sckc *sckc,
+ const char *name, const char * const *parent_names,
+ int num_parents)
+{
+ struct clk *clk;
+ int ret = -ENOMEM;
+ u32 val, i;
+
+ if (!sckc || !name || !parent_names || num_parents != 2)
+ return ERR_PTR(-EINVAL);
+
+ sckc->parent_names = kzalloc(sizeof(*sckc->parent_names) * num_parents,
+ GFP_KERNEL);
+ if (!sckc->parent_names)
+ return ERR_PTR(ret);
+
+ for (i = 0; i < num_parents; i++) {
+ sckc->parent_names[i] = kmemdup(parent_names[i],
+ strlen(parent_names[i]) + 1, GFP_KERNEL);
+ if (!sckc->parent_names[i])
+ goto free;
+ }
+ sckc->num_parents = num_parents;
+
+ pmc_read(sckc->reg, 0, &val);
+ val = (val & AT91_OSC_SEL) >> AT91_OSC_SEL_SHIFT;
+
+ clk = &sckc->clk;
+ ret = clk_register(clk, UBOOT_DM_CLK_AT91_SAM9X60_TD_SLCK, name,
+ parent_names[val]);
+ if (ret)
+ goto free;
+
+ return clk;
+
+free:
+ for (; i >= 0; i--)
+ kfree(sckc->parent_names[i]);
+ kfree(sckc->parent_names);
+
+ return ERR_PTR(ret);
+}
+
+U_BOOT_DRIVER(at91_sam9x60_td_slck) = {
+ .name = UBOOT_DM_CLK_AT91_SAM9X60_TD_SLCK,
+ .id = UCLASS_CLK,
+ .ops = &sam9x60_td_slck_ops,
+ .flags = DM_FLAG_PRE_RELOC,
+};
+
+static int at91_sam9x60_sckc_probe(struct udevice *dev)
+{
+ struct sam9x60_sckc *sckc = dev_get_priv(dev);
+ void __iomem *base = (void *)devfdt_get_addr(dev);
+ const char *slow_rc_osc, *slow_osc;
+ const char *parents[2];
+ struct clk *clk, c;
+ int ret;
+
+ ret = clk_get_by_index(dev, 0, &c);
+ if (ret)
+ return ret;
+ slow_rc_osc = clk_hw_get_name(&c);
+
+ ret = clk_get_by_index(dev, 1, &c);
+ if (ret)
+ return ret;
+ slow_osc = clk_hw_get_name(&c);
+
+ clk = clk_register_fixed_factor(NULL, "md_slck", slow_rc_osc, 0, 1, 1);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+ clk_dm(AT91_TO_CLK_ID(PMC_TYPE_SLOW, 0), clk);
+
+ parents[0] = slow_rc_osc;
+ parents[1] = slow_osc;
+ sckc[1].reg = base;
+ clk = at91_sam9x60_clk_register_td_slck(&sckc[1], "td_slck",
+ parents, 2);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+ clk_dm(AT91_TO_CLK_ID(PMC_TYPE_SLOW, 1), clk);
+
+ return 0;
+}
+
+static const struct udevice_id sam9x60_sckc_ids[] = {
+ { .compatible = "microchip,sam9x60-sckc" },
+ { /* Sentinel. */ },
};
U_BOOT_DRIVER(at91_sckc) = {
- .name = "at91-sckc",
- .id = UCLASS_SIMPLE_BUS,
- .of_match = at91_sckc_match,
+ .name = UBOOT_DM_CLK_AT91_SCKC,
+ .id = UCLASS_CLK,
+ .of_match = sam9x60_sckc_ids,
+ .priv_auto_alloc_size = sizeof(struct sam9x60_sckc) * 2,
+ .ops = &sam9x60_sckc_ops,
+ .probe = at91_sam9x60_sckc_probe,
+ .flags = DM_FLAG_PRE_RELOC,
};
diff --git a/drivers/clk/clk-uclass.c b/drivers/clk/clk-uclass.c
index 934cd5787a..4076535271 100644
--- a/drivers/clk/clk-uclass.c
+++ b/drivers/clk/clk-uclass.c
@@ -14,6 +14,7 @@
#include <errno.h>
#include <log.h>
#include <malloc.h>
+#include <dm/device-internal.h>
#include <dm/devres.h>
#include <dm/read.h>
#include <linux/bug.h>
@@ -187,9 +188,26 @@ bulk_get_err:
return ret;
}
+static struct clk *clk_set_default_get_by_id(struct clk *clk)
+{
+ struct clk *c = clk;
+
+ if (CONFIG_IS_ENABLED(CLK_CCF)) {
+ int ret = clk_get_by_id(clk->id, &c);
+
+ if (ret) {
+ debug("%s(): could not get parent clock pointer, id %lu\n",
+ __func__, clk->id);
+ ERR_PTR(ret);
+ }
+ }
+
+ return c;
+}
+
static int clk_set_default_parents(struct udevice *dev, int stage)
{
- struct clk clk, parent_clk;
+ struct clk clk, parent_clk, *c, *p;
int index;
int num_parents;
int ret;
@@ -215,6 +233,10 @@ static int clk_set_default_parents(struct udevice *dev, int stage)
return ret;
}
+ p = clk_set_default_get_by_id(&parent_clk);
+ if (IS_ERR(p))
+ return PTR_ERR(p);
+
ret = clk_get_by_indexed_prop(dev, "assigned-clocks",
index, &clk);
if (ret) {
@@ -234,7 +256,11 @@ static int clk_set_default_parents(struct udevice *dev, int stage)
/* do not setup twice the parent clocks */
continue;
- ret = clk_set_parent(&clk, &parent_clk);
+ c = clk_set_default_get_by_id(&clk);
+ if (IS_ERR(c))
+ return PTR_ERR(c);
+
+ ret = clk_set_parent(c, p);
/*
* Not all drivers may support clock-reparenting (as of now).
* Ignore errors due to this.
@@ -254,7 +280,7 @@ static int clk_set_default_parents(struct udevice *dev, int stage)
static int clk_set_default_rates(struct udevice *dev, int stage)
{
- struct clk clk;
+ struct clk clk, *c;
int index;
int num_rates;
int size;
@@ -298,7 +324,11 @@ static int clk_set_default_rates(struct udevice *dev, int stage)
/* do not setup twice the parent clocks */
continue;
- ret = clk_set_rate(&clk, rates[index]);
+ c = clk_set_default_get_by_id(&clk);
+ if (IS_ERR(c))
+ return PTR_ERR(c);
+
+ ret = clk_set_rate(c, rates[index]);
if (ret < 0) {
debug("%s: failed to set rate on clock index %d (%ld) for %s\n",
@@ -512,6 +542,7 @@ ulong clk_set_rate(struct clk *clk, ulong rate)
int clk_set_parent(struct clk *clk, struct clk *parent)
{
const struct clk_ops *ops;
+ int ret;
debug("%s(clk=%p, parent=%p)\n", __func__, clk, parent);
if (!clk_valid(clk))
@@ -521,7 +552,14 @@ int clk_set_parent(struct clk *clk, struct clk *parent)
if (!ops->set_parent)
return -ENOSYS;
- return ops->set_parent(clk, parent);
+ ret = ops->set_parent(clk, parent);
+ if (ret)
+ return ret;
+
+ if (CONFIG_IS_ENABLED(CLK_CCF))
+ ret = device_reparent(clk->dev, parent->dev);
+
+ return ret;
}
int clk_enable(struct clk *clk)
@@ -597,6 +635,9 @@ int clk_disable(struct clk *clk)
if (CONFIG_IS_ENABLED(CLK_CCF)) {
if (clk->id && !clk_get_by_id(clk->id, &clkp)) {
+ if (clkp->flags & CLK_IS_CRITICAL)
+ return 0;
+
if (clkp->enable_count == 0) {
printf("clk %s already disabled\n",
clkp->dev->name);
diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
index 786f4e887e..319808d433 100644
--- a/drivers/clk/clk.c
+++ b/drivers/clk/clk.c
@@ -57,6 +57,9 @@ ulong clk_generic_get_rate(struct clk *clk)
const char *clk_hw_get_name(const struct clk *hw)
{
+ assert(hw);
+ assert(hw->dev);
+
return hw->dev->name;
}
diff --git a/drivers/core/device.c b/drivers/core/device.c
index 355dbd147a..e90d70101c 100644
--- a/drivers/core/device.c
+++ b/drivers/core/device.c
@@ -276,6 +276,28 @@ int device_bind_by_name(struct udevice *parent, bool pre_reloc_only,
return ret;
}
+int device_reparent(struct udevice *dev, struct udevice *new_parent)
+{
+ struct udevice *pos, *n;
+
+ assert(dev);
+ assert(new_parent);
+
+ list_for_each_entry_safe(pos, n, &dev->parent->child_head,
+ sibling_node) {
+ if (pos->driver != dev->driver)
+ continue;
+
+ list_del(&dev->sibling_node);
+ list_add_tail(&dev->sibling_node, &new_parent->child_head);
+ dev->parent = new_parent;
+
+ break;
+ }
+
+ return 0;
+}
+
static void *alloc_priv(int size, uint flags)
{
void *priv;
diff --git a/drivers/cpu/Makefile b/drivers/cpu/Makefile
index 0b5dbc7c88..c8532637ca 100644
--- a/drivers/cpu/Makefile
+++ b/drivers/cpu/Makefile
@@ -8,6 +8,7 @@ obj-$(CONFIG_CPU) += cpu-uclass.o
obj-$(CONFIG_ARCH_BMIPS) += bmips_cpu.o
obj-$(CONFIG_ARCH_IMX8) += imx8_cpu.o
+obj-$(CONFIG_ARCH_AT91) += at91_cpu.o
obj-$(CONFIG_CPU_MPC83XX) += mpc83xx_cpu.o
obj-$(CONFIG_CPU_RISCV) += riscv_cpu.o
obj-$(CONFIG_SANDBOX) += cpu_sandbox.o
diff --git a/drivers/cpu/at91_cpu.c b/drivers/cpu/at91_cpu.c
new file mode 100644
index 0000000000..058ae3a811
--- /dev/null
+++ b/drivers/cpu/at91_cpu.c
@@ -0,0 +1,123 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2020 Microchip Technology Inc. and its subsidiaries
+ *
+ * Author: Claudiu Beznea <claudiu.beznea@microchip.com>
+ */
+
+#include <common.h>
+#include <cpu.h>
+#include <dm.h>
+#include <div64.h>
+#include <linux/clk-provider.h>
+
+struct at91_cpu_platdata {
+ const char *name;
+ ulong cpufreq_mhz;
+ ulong mckfreq_mhz;
+ ulong xtalfreq_mhz;
+};
+
+extern char *get_cpu_name(void);
+
+const char *at91_cpu_get_name(void)
+{
+ return get_cpu_name();
+}
+
+int at91_cpu_get_desc(const struct udevice *dev, char *buf, int size)
+{
+ struct at91_cpu_platdata *plat = dev_get_platdata(dev);
+
+ snprintf(buf, size, "%s\n"
+ "Crystal frequency: %8lu MHz\n"
+ "CPU clock : %8lu MHz\n"
+ "Master clock : %8lu MHz\n",
+ plat->name, plat->xtalfreq_mhz, plat->cpufreq_mhz,
+ plat->mckfreq_mhz);
+
+ return 0;
+}
+
+static int at91_cpu_get_info(const struct udevice *dev, struct cpu_info *info)
+{
+ struct at91_cpu_platdata *plat = dev_get_platdata(dev);
+
+ info->cpu_freq = plat->cpufreq_mhz * 1000000;
+ info->features = BIT(CPU_FEAT_L1_CACHE);
+
+ return 0;
+}
+
+static int at91_cpu_get_count(const struct udevice *dev)
+{
+ return 1;
+}
+
+static int at91_cpu_get_vendor(const struct udevice *dev, char *buf, int size)
+{
+ snprintf(buf, size, "Microchip Technology Inc.");
+
+ return 0;
+}
+
+static const struct cpu_ops at91_cpu_ops = {
+ .get_desc = at91_cpu_get_desc,
+ .get_info = at91_cpu_get_info,
+ .get_count = at91_cpu_get_count,
+ .get_vendor = at91_cpu_get_vendor,
+};
+
+static const struct udevice_id at91_cpu_ids[] = {
+ { .compatible = "arm,cortex-a7" },
+ { /* Sentinel. */ }
+};
+
+static int at91_cpu_probe(struct udevice *dev)
+{
+ struct at91_cpu_platdata *plat = dev_get_platdata(dev);
+ struct clk clk;
+ ulong rate;
+ int ret;
+
+ ret = clk_get_by_index(dev, 0, &clk);
+ if (ret)
+ return ret;
+
+ rate = clk_get_rate(&clk);
+ if (!rate)
+ return -ENOTSUPP;
+ plat->cpufreq_mhz = DIV_ROUND_CLOSEST_ULL(rate, 1000000);
+
+ ret = clk_get_by_index(dev, 1, &clk);
+ if (ret)
+ return ret;
+
+ rate = clk_get_rate(&clk);
+ if (!rate)
+ return -ENOTSUPP;
+ plat->mckfreq_mhz = DIV_ROUND_CLOSEST_ULL(rate, 1000000);
+
+ ret = clk_get_by_index(dev, 2, &clk);
+ if (ret)
+ return ret;
+
+ rate = clk_get_rate(&clk);
+ if (!rate)
+ return -ENOTSUPP;
+ plat->xtalfreq_mhz = DIV_ROUND_CLOSEST_ULL(rate, 1000000);
+
+ plat->name = get_cpu_name();
+
+ return 0;
+}
+
+U_BOOT_DRIVER(cpu_at91_drv) = {
+ .name = "at91-cpu",
+ .id = UCLASS_CPU,
+ .of_match = at91_cpu_ids,
+ .ops = &at91_cpu_ops,
+ .probe = at91_cpu_probe,
+ .platdata_auto_alloc_size = sizeof(struct at91_cpu_platdata),
+ .flags = DM_FLAG_PRE_RELOC,
+};
diff --git a/drivers/mmc/atmel_sdhci.c b/drivers/mmc/atmel_sdhci.c
index 0c53caf448..f56ae63bc2 100644
--- a/drivers/mmc/atmel_sdhci.c
+++ b/drivers/mmc/atmel_sdhci.c
@@ -79,14 +79,20 @@ static int atmel_sdhci_probe(struct udevice *dev)
if (ret)
return ret;
- ret = clk_set_rate(&clk, ATMEL_SDHC_GCK_RATE);
- if (ret)
- return ret;
+ clk_set_rate(&clk, ATMEL_SDHC_GCK_RATE);
max_clk = clk_get_rate(&clk);
if (!max_clk)
return -EINVAL;
+ ret = clk_enable(&clk);
+ if (ret)
+ return ret;
+
+ ret = mmc_of_parse(dev, &plat->cfg);
+ if (ret)
+ return ret;
+
host->max_clk = max_clk;
host->mmc = &plat->mmc;
host->mmc->dev = dev;
@@ -113,6 +119,7 @@ static int atmel_sdhci_bind(struct udevice *dev)
static const struct udevice_id atmel_sdhci_ids[] = {
{ .compatible = "atmel,sama5d2-sdhci" },
{ .compatible = "microchip,sam9x60-sdhci" },
+ { .compatible = "microchip,sama7g5-sdhci" },
{ }
};
diff --git a/drivers/pinctrl/pinctrl-at91-pio4.c b/drivers/pinctrl/pinctrl-at91-pio4.c
index fdb7920b55..bf85cc916a 100644
--- a/drivers/pinctrl/pinctrl-at91-pio4.c
+++ b/drivers/pinctrl/pinctrl-at91-pio4.c
@@ -169,6 +169,7 @@ static int atmel_pinctrl_probe(struct udevice *dev)
static const struct udevice_id atmel_pinctrl_match[] = {
{ .compatible = "atmel,sama5d2-pinctrl" },
+ { .compatible = "microchip,sama7g5-pinctrl" },
{}
};
diff --git a/drivers/timer/Kconfig b/drivers/timer/Kconfig
index d40d313011..f8fa4aa71f 100644
--- a/drivers/timer/Kconfig
+++ b/drivers/timer/Kconfig
@@ -213,4 +213,11 @@ config MTK_TIMER
Select this to enable support for the timer found on
MediaTek devices.
+config MCHP_PIT64B_TIMER
+ bool "Microchip 64-bit periodic interval timer support"
+ depends on TIMER
+ help
+ Select this to enable support for Microchip 64-bit periodic
+ interval timer.
+
endmenu
diff --git a/drivers/timer/Makefile b/drivers/timer/Makefile
index c22ffebcde..3a4d74b996 100644
--- a/drivers/timer/Makefile
+++ b/drivers/timer/Makefile
@@ -22,3 +22,4 @@ obj-$(CONFIG_STI_TIMER) += sti-timer.o
obj-$(CONFIG_STM32_TIMER) += stm32_timer.o
obj-$(CONFIG_X86_TSC_TIMER) += tsc_timer.o
obj-$(CONFIG_MTK_TIMER) += mtk_timer.o
+obj-$(CONFIG_MCHP_PIT64B_TIMER) += mchp-pit64b-timer.o
diff --git a/drivers/timer/mchp-pit64b-timer.c b/drivers/timer/mchp-pit64b-timer.c
new file mode 100644
index 0000000000..ead8c9b84a
--- /dev/null
+++ b/drivers/timer/mchp-pit64b-timer.c
@@ -0,0 +1,109 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * 64-bit Periodic Interval Timer driver
+ *
+ * Copyright (C) 2020 Microchip Technology Inc. and its subsidiaries
+ *
+ * Author: Claudiu Beznea <claudiu.beznea@microchip.com>
+ */
+
+#include <common.h>
+#include <clk.h>
+#include <dm.h>
+#include <timer.h>
+#include <asm/io.h>
+
+#define MCHP_PIT64B_CR 0x00 /* Control Register */
+#define MCHP_PIT64B_CR_START BIT(0)
+#define MCHP_PIT64B_CR_SWRST BIT(8)
+#define MCHP_PIT64B_MR 0x04 /* Mode Register */
+#define MCHP_PIT64B_MR_CONT BIT(0)
+#define MCHP_PIT64B_LSB_PR 0x08 /* LSB Period Register */
+#define MCHP_PIT64B_MSB_PR 0x0C /* MSB Period Register */
+#define MCHP_PIT64B_TLSBR 0x20 /* Timer LSB Register */
+#define MCHP_PIT64B_TMSBR 0x24 /* Timer MSB Register */
+
+struct mchp_pit64b_priv {
+ void __iomem *base;
+};
+
+static int mchp_pit64b_get_count(struct udevice *dev, u64 *count)
+{
+ struct mchp_pit64b_priv *priv = dev_get_priv(dev);
+
+ u32 lsb = readl(priv->base + MCHP_PIT64B_TLSBR);
+ u32 msb = readl(priv->base + MCHP_PIT64B_TMSBR);
+
+ *count = ((u64)msb << 32) | lsb;
+
+ return 0;
+}
+
+static int mchp_pit64b_probe(struct udevice *dev)
+{
+ struct timer_dev_priv *uc_priv = dev_get_uclass_priv(dev);
+ struct mchp_pit64b_priv *priv = dev_get_priv(dev);
+ struct clk clk;
+ ulong rate;
+ int ret;
+
+ priv->base = dev_read_addr_ptr(dev);
+ if (IS_ERR(priv->base))
+ return PTR_ERR(priv->base);
+
+ ret = clk_get_by_index(dev, 0, &clk);
+ if (ret)
+ return ret;
+
+ ret = clk_enable(&clk);
+ if (ret)
+ return ret;
+
+ rate = clk_get_rate(&clk);
+ if (!rate) {
+ clk_disable(&clk);
+ return -ENOTSUPP;
+ }
+
+ /* Reset the timer in case it was used by previous bootloaders. */
+ writel(MCHP_PIT64B_CR_SWRST, priv->base + MCHP_PIT64B_CR);
+
+ /*
+ * Use highest prescaller (for a peripheral clock running at 200MHz
+ * this will lead to the timer running at 12.5MHz) and continuous mode.
+ */
+ writel((15 << 8) | MCHP_PIT64B_MR_CONT, priv->base + MCHP_PIT64B_MR);
+ uc_priv->clock_rate = rate / 16;
+
+ /*
+ * Simulate free running counter by setting max values to period
+ * registers.
+ */
+ writel(~0UL, priv->base + MCHP_PIT64B_MSB_PR);
+ writel(~0UL, priv->base + MCHP_PIT64B_LSB_PR);
+
+ /* Start the timer. */
+ writel(MCHP_PIT64B_CR_START, priv->base + MCHP_PIT64B_CR);
+
+ return 0;
+}
+
+static const struct timer_ops mchp_pit64b_ops = {
+ .get_count = mchp_pit64b_get_count,
+};
+
+static const struct udevice_id mchp_pit64b_ids[] = {
+ { .compatible = "microchip,sam9x60-pit64b", },
+ { .compatible = "microchip,sama7g5-pit64b", },
+ { }
+};
+
+U_BOOT_DRIVER(mchp_pit64b) = {
+ .name = "mchp-pit64b",
+ .id = UCLASS_TIMER,
+ .of_match = mchp_pit64b_ids,
+ .priv_auto_alloc_size = sizeof(struct mchp_pit64b_priv),
+ .probe = mchp_pit64b_probe,
+ .ops = &mchp_pit64b_ops,
+ .flags = DM_FLAG_PRE_RELOC,
+};