diff --git a/drivers/clk/at91/Makefile b/drivers/clk/at91/Makefile
index 7083ce2890d6..805f67677a9b 100644
--- a/drivers/clk/at91/Makefile
+++ b/drivers/clk/at91/Makefile
@@ -3,7 +3,7 @@
 #
 
 ifdef CONFIG_CLK_CCF
-obj-y += pmc.o sckc.o
+obj-y += pmc.o sckc.o clk-main.o
 else
 obj-y += compat.o
 endif
diff --git a/drivers/clk/at91/clk-main.c b/drivers/clk/at91/clk-main.c
new file mode 100644
index 000000000000..b52d926f3390
--- /dev/null
+++ b/drivers/clk/at91/clk-main.c
@@ -0,0 +1,387 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * 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 "pmc.h"
+
+#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"
+
+#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 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 (!(val & AT91_PMC_MOSCRCEN))
+		return 0;
+
+	pmc_update_bits(reg, AT91_CKGR_MOR, MOR_KEY_MASK | AT91_PMC_MOSCRCEN,
+			AT91_PMC_KEY);
+
+	return 0;
+}
+
+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)
+{
+	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;
+}
+
+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 clk_main_osc_enable(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);
+	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 clk_ops main_osc_clk_ops = {
+	.enable = clk_main_osc_enable,
+	.disable = clk_main_osc_disable,
+	.get_rate = clk_generic_get_rate,
+};
+
+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,
+	.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/pmc.h b/drivers/clk/at91/pmc.h
index 33ded02156c8..62bfc4b3054c 100644
--- a/drivers/clk/at91/pmc.h
+++ b/drivers/clk/at91/pmc.h
@@ -14,6 +14,16 @@
 #define AT91_TO_CLK_ID(_t, _i)		(((_t) << 8) | ((_i) & 0xff))
 #define AT91_CLK_ID_TO_DID(_i)		((_i) & 0xff)
 
+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);
+
 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);
 
