summaryrefslogtreecommitdiffstats
path: root/drivers/clk/at91
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:49:45 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:49:45 +0000
commit2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch)
tree848558de17fb3008cdf4d861b01ac7781903ce39 /drivers/clk/at91
parentInitial commit. (diff)
downloadlinux-2c3c1048746a4622d8c89a29670120dc8fab93c4.tar.xz
linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.zip
Adding upstream version 6.1.76.upstream/6.1.76
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/clk/at91')
-rw-r--r--drivers/clk/at91/Makefile26
-rw-r--r--drivers/clk/at91/at91rm9200.c217
-rw-r--r--drivers/clk/at91/at91sam9260.c515
-rw-r--r--drivers/clk/at91/at91sam9g45.c230
-rw-r--r--drivers/clk/at91/at91sam9n12.c257
-rw-r--r--drivers/clk/at91/at91sam9rl.c189
-rw-r--r--drivers/clk/at91/at91sam9x5.c333
-rw-r--r--drivers/clk/at91/clk-audio-pll.c541
-rw-r--r--drivers/clk/at91/clk-generated.c361
-rw-r--r--drivers/clk/at91/clk-h32mx.c112
-rw-r--r--drivers/clk/at91/clk-i2s-mux.c80
-rw-r--r--drivers/clk/at91/clk-main.c583
-rw-r--r--drivers/clk/at91/clk-master.c872
-rw-r--r--drivers/clk/at91/clk-peripheral.c495
-rw-r--r--drivers/clk/at91/clk-pll.c379
-rw-r--r--drivers/clk/at91/clk-plldiv.c103
-rw-r--r--drivers/clk/at91/clk-programmable.c278
-rw-r--r--drivers/clk/at91/clk-sam9x60-pll.c756
-rw-r--r--drivers/clk/at91/clk-slow.c76
-rw-r--r--drivers/clk/at91/clk-smd.c137
-rw-r--r--drivers/clk/at91/clk-system.c140
-rw-r--r--drivers/clk/at91/clk-usb.c422
-rw-r--r--drivers/clk/at91/clk-utmi.c286
-rw-r--r--drivers/clk/at91/dt-compat.c1044
-rw-r--r--drivers/clk/at91/pmc.c193
-rw-r--r--drivers/clk/at91/pmc.h268
-rw-r--r--drivers/clk/at91/sam9x60.c359
-rw-r--r--drivers/clk/at91/sama5d2.c387
-rw-r--r--drivers/clk/at91/sama5d3.c257
-rw-r--r--drivers/clk/at91/sama5d4.c289
-rw-r--r--drivers/clk/at91/sama7g5.c1141
-rw-r--r--drivers/clk/at91/sckc.c633
32 files changed, 11959 insertions, 0 deletions
diff --git a/drivers/clk/at91/Makefile b/drivers/clk/at91/Makefile
new file mode 100644
index 000000000..79301e1c1
--- /dev/null
+++ b/drivers/clk/at91/Makefile
@@ -0,0 +1,26 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for at91 specific clk
+#
+
+obj-y += pmc.o sckc.o dt-compat.o
+obj-y += clk-slow.o clk-main.o clk-pll.o clk-plldiv.o clk-master.o
+obj-y += clk-system.o clk-peripheral.o clk-programmable.o
+
+obj-$(CONFIG_HAVE_AT91_AUDIO_PLL) += clk-audio-pll.o
+obj-$(CONFIG_HAVE_AT91_UTMI) += clk-utmi.o
+obj-$(CONFIG_HAVE_AT91_USB_CLK) += clk-usb.o
+obj-$(CONFIG_HAVE_AT91_SMD) += clk-smd.o
+obj-$(CONFIG_HAVE_AT91_H32MX) += clk-h32mx.o
+obj-$(CONFIG_HAVE_AT91_GENERATED_CLK) += clk-generated.o
+obj-$(CONFIG_HAVE_AT91_I2S_MUX_CLK) += clk-i2s-mux.o
+obj-$(CONFIG_HAVE_AT91_SAM9X60_PLL) += clk-sam9x60-pll.o
+obj-$(CONFIG_SOC_AT91RM9200) += at91rm9200.o
+obj-$(CONFIG_SOC_AT91SAM9) += at91sam9260.o at91sam9rl.o at91sam9x5.o
+obj-$(CONFIG_SOC_AT91SAM9) += at91sam9g45.o
+obj-$(CONFIG_SOC_AT91SAM9) += at91sam9n12.o at91sam9x5.o
+obj-$(CONFIG_SOC_SAM9X60) += sam9x60.o
+obj-$(CONFIG_SOC_SAMA5D3) += sama5d3.o
+obj-$(CONFIG_SOC_SAMA5D4) += sama5d4.o
+obj-$(CONFIG_SOC_SAMA5D2) += sama5d2.o
+obj-$(CONFIG_SOC_SAMA7G5) += sama7g5.o
diff --git a/drivers/clk/at91/at91rm9200.c b/drivers/clk/at91/at91rm9200.c
new file mode 100644
index 000000000..16870943a
--- /dev/null
+++ b/drivers/clk/at91/at91rm9200.c
@@ -0,0 +1,217 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/clk-provider.h>
+#include <linux/mfd/syscon.h>
+#include <linux/slab.h>
+
+#include <dt-bindings/clock/at91.h>
+
+#include "pmc.h"
+
+static DEFINE_SPINLOCK(rm9200_mck_lock);
+
+struct sck {
+ char *n;
+ char *p;
+ u8 id;
+};
+
+struct pck {
+ char *n;
+ u8 id;
+};
+
+static const struct clk_master_characteristics rm9200_mck_characteristics = {
+ .output = { .min = 0, .max = 80000000 },
+ .divisors = { 1, 2, 3, 4 },
+};
+
+static u8 rm9200_pll_out[] = { 0, 2 };
+
+static const struct clk_range rm9200_pll_outputs[] = {
+ { .min = 80000000, .max = 160000000 },
+ { .min = 150000000, .max = 180000000 },
+};
+
+static const struct clk_pll_characteristics rm9200_pll_characteristics = {
+ .input = { .min = 1000000, .max = 32000000 },
+ .num_output = ARRAY_SIZE(rm9200_pll_outputs),
+ .output = rm9200_pll_outputs,
+ .out = rm9200_pll_out,
+};
+
+static const struct sck at91rm9200_systemck[] = {
+ { .n = "udpck", .p = "usbck", .id = 1 },
+ { .n = "uhpck", .p = "usbck", .id = 4 },
+ { .n = "pck0", .p = "prog0", .id = 8 },
+ { .n = "pck1", .p = "prog1", .id = 9 },
+ { .n = "pck2", .p = "prog2", .id = 10 },
+ { .n = "pck3", .p = "prog3", .id = 11 },
+};
+
+static const struct pck at91rm9200_periphck[] = {
+ { .n = "pioA_clk", .id = 2 },
+ { .n = "pioB_clk", .id = 3 },
+ { .n = "pioC_clk", .id = 4 },
+ { .n = "pioD_clk", .id = 5 },
+ { .n = "usart0_clk", .id = 6 },
+ { .n = "usart1_clk", .id = 7 },
+ { .n = "usart2_clk", .id = 8 },
+ { .n = "usart3_clk", .id = 9 },
+ { .n = "mci0_clk", .id = 10 },
+ { .n = "udc_clk", .id = 11 },
+ { .n = "twi0_clk", .id = 12 },
+ { .n = "spi0_clk", .id = 13 },
+ { .n = "ssc0_clk", .id = 14 },
+ { .n = "ssc1_clk", .id = 15 },
+ { .n = "ssc2_clk", .id = 16 },
+ { .n = "tc0_clk", .id = 17 },
+ { .n = "tc1_clk", .id = 18 },
+ { .n = "tc2_clk", .id = 19 },
+ { .n = "tc3_clk", .id = 20 },
+ { .n = "tc4_clk", .id = 21 },
+ { .n = "tc5_clk", .id = 22 },
+ { .n = "ohci_clk", .id = 23 },
+ { .n = "macb0_clk", .id = 24 },
+};
+
+static void __init at91rm9200_pmc_setup(struct device_node *np)
+{
+ const char *slowxtal_name, *mainxtal_name;
+ struct pmc_data *at91rm9200_pmc;
+ u32 usb_div[] = { 1, 2, 0, 0 };
+ const char *parent_names[6];
+ struct regmap *regmap;
+ struct clk_hw *hw;
+ int i;
+ bool bypass;
+
+ i = of_property_match_string(np, "clock-names", "slow_xtal");
+ if (i < 0)
+ return;
+
+ slowxtal_name = of_clk_get_parent_name(np, i);
+
+ i = of_property_match_string(np, "clock-names", "main_xtal");
+ if (i < 0)
+ return;
+ mainxtal_name = of_clk_get_parent_name(np, i);
+
+ regmap = device_node_to_regmap(np);
+ if (IS_ERR(regmap))
+ return;
+
+ at91rm9200_pmc = pmc_data_allocate(PMC_PLLBCK + 1,
+ nck(at91rm9200_systemck),
+ nck(at91rm9200_periphck), 0, 4);
+ if (!at91rm9200_pmc)
+ return;
+
+ bypass = of_property_read_bool(np, "atmel,osc-bypass");
+
+ hw = at91_clk_register_main_osc(regmap, "main_osc", mainxtal_name,
+ bypass);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ hw = at91_clk_register_rm9200_main(regmap, "mainck", "main_osc");
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91rm9200_pmc->chws[PMC_MAIN] = hw;
+
+ hw = at91_clk_register_pll(regmap, "pllack", "mainck", 0,
+ &at91rm9200_pll_layout,
+ &rm9200_pll_characteristics);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91rm9200_pmc->chws[PMC_PLLACK] = hw;
+
+ hw = at91_clk_register_pll(regmap, "pllbck", "mainck", 1,
+ &at91rm9200_pll_layout,
+ &rm9200_pll_characteristics);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91rm9200_pmc->chws[PMC_PLLBCK] = hw;
+
+ parent_names[0] = slowxtal_name;
+ parent_names[1] = "mainck";
+ parent_names[2] = "pllack";
+ parent_names[3] = "pllbck";
+ hw = at91_clk_register_master_pres(regmap, "masterck_pres", 4,
+ parent_names,
+ &at91rm9200_master_layout,
+ &rm9200_mck_characteristics,
+ &rm9200_mck_lock);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ hw = at91_clk_register_master_div(regmap, "masterck_div",
+ "masterck_pres",
+ &at91rm9200_master_layout,
+ &rm9200_mck_characteristics,
+ &rm9200_mck_lock, CLK_SET_RATE_GATE, 0);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91rm9200_pmc->chws[PMC_MCK] = hw;
+
+ hw = at91rm9200_clk_register_usb(regmap, "usbck", "pllbck", usb_div);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ parent_names[0] = slowxtal_name;
+ parent_names[1] = "mainck";
+ parent_names[2] = "pllack";
+ parent_names[3] = "pllbck";
+ for (i = 0; i < 4; i++) {
+ char name[6];
+
+ snprintf(name, sizeof(name), "prog%d", i);
+
+ hw = at91_clk_register_programmable(regmap, name,
+ parent_names, 4, i,
+ &at91rm9200_programmable_layout,
+ NULL);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91rm9200_pmc->pchws[i] = hw;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(at91rm9200_systemck); i++) {
+ hw = at91_clk_register_system(regmap, at91rm9200_systemck[i].n,
+ at91rm9200_systemck[i].p,
+ at91rm9200_systemck[i].id);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91rm9200_pmc->shws[at91rm9200_systemck[i].id] = hw;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(at91rm9200_periphck); i++) {
+ hw = at91_clk_register_peripheral(regmap,
+ at91rm9200_periphck[i].n,
+ "masterck_div",
+ at91rm9200_periphck[i].id);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91rm9200_pmc->phws[at91rm9200_periphck[i].id] = hw;
+ }
+
+ of_clk_add_hw_provider(np, of_clk_hw_pmc_get, at91rm9200_pmc);
+
+ return;
+
+err_free:
+ kfree(at91rm9200_pmc);
+}
+/*
+ * While the TCB can be used as the clocksource, the system timer is most likely
+ * to be used instead. However, the pinctrl driver doesn't support probe
+ * deferring properly. Once this is fixed, this can be switched to a platform
+ * driver.
+ */
+CLK_OF_DECLARE(at91rm9200_pmc, "atmel,at91rm9200-pmc", at91rm9200_pmc_setup);
diff --git a/drivers/clk/at91/at91sam9260.c b/drivers/clk/at91/at91sam9260.c
new file mode 100644
index 000000000..11550e50c
--- /dev/null
+++ b/drivers/clk/at91/at91sam9260.c
@@ -0,0 +1,515 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/clk-provider.h>
+#include <linux/mfd/syscon.h>
+#include <linux/slab.h>
+
+#include <dt-bindings/clock/at91.h>
+
+#include "pmc.h"
+
+struct sck {
+ char *n;
+ char *p;
+ u8 id;
+};
+
+struct pck {
+ char *n;
+ u8 id;
+};
+
+struct at91sam926x_data {
+ const struct clk_pll_layout *plla_layout;
+ const struct clk_pll_characteristics *plla_characteristics;
+ const struct clk_pll_layout *pllb_layout;
+ const struct clk_pll_characteristics *pllb_characteristics;
+ const struct clk_master_characteristics *mck_characteristics;
+ const struct sck *sck;
+ const struct pck *pck;
+ u8 num_sck;
+ u8 num_pck;
+ u8 num_progck;
+ bool has_slck;
+};
+
+static DEFINE_SPINLOCK(at91sam9260_mck_lock);
+
+static const struct clk_master_characteristics sam9260_mck_characteristics = {
+ .output = { .min = 0, .max = 105000000 },
+ .divisors = { 1, 2, 4, 0 },
+};
+
+static u8 sam9260_plla_out[] = { 0, 2 };
+
+static u16 sam9260_plla_icpll[] = { 1, 1 };
+
+static const struct clk_range sam9260_plla_outputs[] = {
+ { .min = 80000000, .max = 160000000 },
+ { .min = 150000000, .max = 240000000 },
+};
+
+static const struct clk_pll_characteristics sam9260_plla_characteristics = {
+ .input = { .min = 1000000, .max = 32000000 },
+ .num_output = ARRAY_SIZE(sam9260_plla_outputs),
+ .output = sam9260_plla_outputs,
+ .icpll = sam9260_plla_icpll,
+ .out = sam9260_plla_out,
+};
+
+static u8 sam9260_pllb_out[] = { 1 };
+
+static u16 sam9260_pllb_icpll[] = { 1 };
+
+static const struct clk_range sam9260_pllb_outputs[] = {
+ { .min = 70000000, .max = 130000000 },
+};
+
+static const struct clk_pll_characteristics sam9260_pllb_characteristics = {
+ .input = { .min = 1000000, .max = 5000000 },
+ .num_output = ARRAY_SIZE(sam9260_pllb_outputs),
+ .output = sam9260_pllb_outputs,
+ .icpll = sam9260_pllb_icpll,
+ .out = sam9260_pllb_out,
+};
+
+static const struct sck at91sam9260_systemck[] = {
+ { .n = "uhpck", .p = "usbck", .id = 6 },
+ { .n = "udpck", .p = "usbck", .id = 7 },
+ { .n = "pck0", .p = "prog0", .id = 8 },
+ { .n = "pck1", .p = "prog1", .id = 9 },
+};
+
+static const struct pck at91sam9260_periphck[] = {
+ { .n = "pioA_clk", .id = 2 },
+ { .n = "pioB_clk", .id = 3 },
+ { .n = "pioC_clk", .id = 4 },
+ { .n = "adc_clk", .id = 5 },
+ { .n = "usart0_clk", .id = 6 },
+ { .n = "usart1_clk", .id = 7 },
+ { .n = "usart2_clk", .id = 8 },
+ { .n = "mci0_clk", .id = 9 },
+ { .n = "udc_clk", .id = 10 },
+ { .n = "twi0_clk", .id = 11 },
+ { .n = "spi0_clk", .id = 12 },
+ { .n = "spi1_clk", .id = 13 },
+ { .n = "ssc0_clk", .id = 14 },
+ { .n = "tc0_clk", .id = 17 },
+ { .n = "tc1_clk", .id = 18 },
+ { .n = "tc2_clk", .id = 19 },
+ { .n = "ohci_clk", .id = 20 },
+ { .n = "macb0_clk", .id = 21 },
+ { .n = "isi_clk", .id = 22 },
+ { .n = "usart3_clk", .id = 23 },
+ { .n = "uart0_clk", .id = 24 },
+ { .n = "uart1_clk", .id = 25 },
+ { .n = "tc3_clk", .id = 26 },
+ { .n = "tc4_clk", .id = 27 },
+ { .n = "tc5_clk", .id = 28 },
+};
+
+static struct at91sam926x_data at91sam9260_data = {
+ .plla_layout = &at91rm9200_pll_layout,
+ .plla_characteristics = &sam9260_plla_characteristics,
+ .pllb_layout = &at91rm9200_pll_layout,
+ .pllb_characteristics = &sam9260_pllb_characteristics,
+ .mck_characteristics = &sam9260_mck_characteristics,
+ .sck = at91sam9260_systemck,
+ .num_sck = ARRAY_SIZE(at91sam9260_systemck),
+ .pck = at91sam9260_periphck,
+ .num_pck = ARRAY_SIZE(at91sam9260_periphck),
+ .num_progck = 2,
+ .has_slck = true,
+};
+
+static const struct clk_master_characteristics sam9g20_mck_characteristics = {
+ .output = { .min = 0, .max = 133000000 },
+ .divisors = { 1, 2, 4, 6 },
+};
+
+static u8 sam9g20_plla_out[] = { 0, 1, 2, 3, 0, 1, 2, 3 };
+
+static u16 sam9g20_plla_icpll[] = { 0, 0, 0, 0, 1, 1, 1, 1 };
+
+static const struct clk_range sam9g20_plla_outputs[] = {
+ { .min = 745000000, .max = 800000000 },
+ { .min = 695000000, .max = 750000000 },
+ { .min = 645000000, .max = 700000000 },
+ { .min = 595000000, .max = 650000000 },
+ { .min = 545000000, .max = 600000000 },
+ { .min = 495000000, .max = 550000000 },
+ { .min = 445000000, .max = 500000000 },
+ { .min = 400000000, .max = 450000000 },
+};
+
+static const struct clk_pll_characteristics sam9g20_plla_characteristics = {
+ .input = { .min = 2000000, .max = 32000000 },
+ .num_output = ARRAY_SIZE(sam9g20_plla_outputs),
+ .output = sam9g20_plla_outputs,
+ .icpll = sam9g20_plla_icpll,
+ .out = sam9g20_plla_out,
+};
+
+static u8 sam9g20_pllb_out[] = { 0 };
+
+static u16 sam9g20_pllb_icpll[] = { 0 };
+
+static const struct clk_range sam9g20_pllb_outputs[] = {
+ { .min = 30000000, .max = 100000000 },
+};
+
+static const struct clk_pll_characteristics sam9g20_pllb_characteristics = {
+ .input = { .min = 2000000, .max = 32000000 },
+ .num_output = ARRAY_SIZE(sam9g20_pllb_outputs),
+ .output = sam9g20_pllb_outputs,
+ .icpll = sam9g20_pllb_icpll,
+ .out = sam9g20_pllb_out,
+};
+
+static struct at91sam926x_data at91sam9g20_data = {
+ .plla_layout = &at91sam9g45_pll_layout,
+ .plla_characteristics = &sam9g20_plla_characteristics,
+ .pllb_layout = &at91sam9g20_pllb_layout,
+ .pllb_characteristics = &sam9g20_pllb_characteristics,
+ .mck_characteristics = &sam9g20_mck_characteristics,
+ .sck = at91sam9260_systemck,
+ .num_sck = ARRAY_SIZE(at91sam9260_systemck),
+ .pck = at91sam9260_periphck,
+ .num_pck = ARRAY_SIZE(at91sam9260_periphck),
+ .num_progck = 2,
+ .has_slck = true,
+};
+
+static const struct clk_master_characteristics sam9261_mck_characteristics = {
+ .output = { .min = 0, .max = 94000000 },
+ .divisors = { 1, 2, 4, 0 },
+};
+
+static const struct clk_range sam9261_plla_outputs[] = {
+ { .min = 80000000, .max = 200000000 },
+ { .min = 190000000, .max = 240000000 },
+};
+
+static const struct clk_pll_characteristics sam9261_plla_characteristics = {
+ .input = { .min = 1000000, .max = 32000000 },
+ .num_output = ARRAY_SIZE(sam9261_plla_outputs),
+ .output = sam9261_plla_outputs,
+ .icpll = sam9260_plla_icpll,
+ .out = sam9260_plla_out,
+};
+
+static u8 sam9261_pllb_out[] = { 1 };
+
+static u16 sam9261_pllb_icpll[] = { 1 };
+
+static const struct clk_range sam9261_pllb_outputs[] = {
+ { .min = 70000000, .max = 130000000 },
+};
+
+static const struct clk_pll_characteristics sam9261_pllb_characteristics = {
+ .input = { .min = 1000000, .max = 5000000 },
+ .num_output = ARRAY_SIZE(sam9261_pllb_outputs),
+ .output = sam9261_pllb_outputs,
+ .icpll = sam9261_pllb_icpll,
+ .out = sam9261_pllb_out,
+};
+
+static const struct sck at91sam9261_systemck[] = {
+ { .n = "uhpck", .p = "usbck", .id = 6 },
+ { .n = "udpck", .p = "usbck", .id = 7 },
+ { .n = "pck0", .p = "prog0", .id = 8 },
+ { .n = "pck1", .p = "prog1", .id = 9 },
+ { .n = "pck2", .p = "prog2", .id = 10 },
+ { .n = "pck3", .p = "prog3", .id = 11 },
+ { .n = "hclk0", .p = "masterck_div", .id = 16 },
+ { .n = "hclk1", .p = "masterck_div", .id = 17 },
+};
+
+static const struct pck at91sam9261_periphck[] = {
+ { .n = "pioA_clk", .id = 2, },
+ { .n = "pioB_clk", .id = 3, },
+ { .n = "pioC_clk", .id = 4, },
+ { .n = "usart0_clk", .id = 6, },
+ { .n = "usart1_clk", .id = 7, },
+ { .n = "usart2_clk", .id = 8, },
+ { .n = "mci0_clk", .id = 9, },
+ { .n = "udc_clk", .id = 10, },
+ { .n = "twi0_clk", .id = 11, },
+ { .n = "spi0_clk", .id = 12, },
+ { .n = "spi1_clk", .id = 13, },
+ { .n = "ssc0_clk", .id = 14, },
+ { .n = "ssc1_clk", .id = 15, },
+ { .n = "ssc2_clk", .id = 16, },
+ { .n = "tc0_clk", .id = 17, },
+ { .n = "tc1_clk", .id = 18, },
+ { .n = "tc2_clk", .id = 19, },
+ { .n = "ohci_clk", .id = 20, },
+ { .n = "lcd_clk", .id = 21, },
+};
+
+static struct at91sam926x_data at91sam9261_data = {
+ .plla_layout = &at91rm9200_pll_layout,
+ .plla_characteristics = &sam9261_plla_characteristics,
+ .pllb_layout = &at91rm9200_pll_layout,
+ .pllb_characteristics = &sam9261_pllb_characteristics,
+ .mck_characteristics = &sam9261_mck_characteristics,
+ .sck = at91sam9261_systemck,
+ .num_sck = ARRAY_SIZE(at91sam9261_systemck),
+ .pck = at91sam9261_periphck,
+ .num_pck = ARRAY_SIZE(at91sam9261_periphck),
+ .num_progck = 4,
+};
+
+static const struct clk_master_characteristics sam9263_mck_characteristics = {
+ .output = { .min = 0, .max = 120000000 },
+ .divisors = { 1, 2, 4, 0 },
+};
+
+static const struct clk_range sam9263_pll_outputs[] = {
+ { .min = 80000000, .max = 200000000 },
+ { .min = 190000000, .max = 240000000 },
+};
+
+static const struct clk_pll_characteristics sam9263_pll_characteristics = {
+ .input = { .min = 1000000, .max = 32000000 },
+ .num_output = ARRAY_SIZE(sam9263_pll_outputs),
+ .output = sam9263_pll_outputs,
+ .icpll = sam9260_plla_icpll,
+ .out = sam9260_plla_out,
+};
+
+static const struct sck at91sam9263_systemck[] = {
+ { .n = "uhpck", .p = "usbck", .id = 6 },
+ { .n = "udpck", .p = "usbck", .id = 7 },
+ { .n = "pck0", .p = "prog0", .id = 8 },
+ { .n = "pck1", .p = "prog1", .id = 9 },
+ { .n = "pck2", .p = "prog2", .id = 10 },
+ { .n = "pck3", .p = "prog3", .id = 11 },
+};
+
+static const struct pck at91sam9263_periphck[] = {
+ { .n = "pioA_clk", .id = 2, },
+ { .n = "pioB_clk", .id = 3, },
+ { .n = "pioCDE_clk", .id = 4, },
+ { .n = "usart0_clk", .id = 7, },
+ { .n = "usart1_clk", .id = 8, },
+ { .n = "usart2_clk", .id = 9, },
+ { .n = "mci0_clk", .id = 10, },
+ { .n = "mci1_clk", .id = 11, },
+ { .n = "can_clk", .id = 12, },
+ { .n = "twi0_clk", .id = 13, },
+ { .n = "spi0_clk", .id = 14, },
+ { .n = "spi1_clk", .id = 15, },
+ { .n = "ssc0_clk", .id = 16, },
+ { .n = "ssc1_clk", .id = 17, },
+ { .n = "ac97_clk", .id = 18, },
+ { .n = "tcb_clk", .id = 19, },
+ { .n = "pwm_clk", .id = 20, },
+ { .n = "macb0_clk", .id = 21, },
+ { .n = "g2de_clk", .id = 23, },
+ { .n = "udc_clk", .id = 24, },
+ { .n = "isi_clk", .id = 25, },
+ { .n = "lcd_clk", .id = 26, },
+ { .n = "dma_clk", .id = 27, },
+ { .n = "ohci_clk", .id = 29, },
+};
+
+static struct at91sam926x_data at91sam9263_data = {
+ .plla_layout = &at91rm9200_pll_layout,
+ .plla_characteristics = &sam9263_pll_characteristics,
+ .pllb_layout = &at91rm9200_pll_layout,
+ .pllb_characteristics = &sam9263_pll_characteristics,
+ .mck_characteristics = &sam9263_mck_characteristics,
+ .sck = at91sam9263_systemck,
+ .num_sck = ARRAY_SIZE(at91sam9263_systemck),
+ .pck = at91sam9263_periphck,
+ .num_pck = ARRAY_SIZE(at91sam9263_periphck),
+ .num_progck = 4,
+};
+
+static void __init at91sam926x_pmc_setup(struct device_node *np,
+ struct at91sam926x_data *data)
+{
+ const char *slowxtal_name, *mainxtal_name;
+ struct pmc_data *at91sam9260_pmc;
+ u32 usb_div[] = { 1, 2, 4, 0 };
+ const char *parent_names[6];
+ const char *slck_name;
+ struct regmap *regmap;
+ struct clk_hw *hw;
+ int i;
+ bool bypass;
+
+ i = of_property_match_string(np, "clock-names", "slow_xtal");
+ if (i < 0)
+ return;
+
+ slowxtal_name = of_clk_get_parent_name(np, i);
+
+ i = of_property_match_string(np, "clock-names", "main_xtal");
+ if (i < 0)
+ return;
+ mainxtal_name = of_clk_get_parent_name(np, i);
+
+ regmap = device_node_to_regmap(np);
+ if (IS_ERR(regmap))
+ return;
+
+ at91sam9260_pmc = pmc_data_allocate(PMC_PLLBCK + 1,
+ ndck(data->sck, data->num_sck),
+ ndck(data->pck, data->num_pck),
+ 0, data->num_progck);
+ if (!at91sam9260_pmc)
+ return;
+
+ bypass = of_property_read_bool(np, "atmel,osc-bypass");
+
+ hw = at91_clk_register_main_osc(regmap, "main_osc", mainxtal_name,
+ bypass);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ hw = at91_clk_register_rm9200_main(regmap, "mainck", "main_osc");
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91sam9260_pmc->chws[PMC_MAIN] = hw;
+
+ if (data->has_slck) {
+ hw = clk_hw_register_fixed_rate_with_accuracy(NULL,
+ "slow_rc_osc",
+ NULL, 0, 32768,
+ 50000000);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ parent_names[0] = "slow_rc_osc";
+ parent_names[1] = "slow_xtal";
+ hw = at91_clk_register_sam9260_slow(regmap, "slck",
+ parent_names, 2);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91sam9260_pmc->chws[PMC_SLOW] = hw;
+ slck_name = "slck";
+ } else {
+ slck_name = slowxtal_name;
+ }
+
+ hw = at91_clk_register_pll(regmap, "pllack", "mainck", 0,
+ data->plla_layout,
+ data->plla_characteristics);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91sam9260_pmc->chws[PMC_PLLACK] = hw;
+
+ hw = at91_clk_register_pll(regmap, "pllbck", "mainck", 1,
+ data->pllb_layout,
+ data->pllb_characteristics);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91sam9260_pmc->chws[PMC_PLLBCK] = hw;
+
+ parent_names[0] = slck_name;
+ parent_names[1] = "mainck";
+ parent_names[2] = "pllack";
+ parent_names[3] = "pllbck";
+ hw = at91_clk_register_master_pres(regmap, "masterck_pres", 4,
+ parent_names,
+ &at91rm9200_master_layout,
+ data->mck_characteristics,
+ &at91sam9260_mck_lock);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ hw = at91_clk_register_master_div(regmap, "masterck_div",
+ "masterck_pres",
+ &at91rm9200_master_layout,
+ data->mck_characteristics,
+ &at91sam9260_mck_lock,
+ CLK_SET_RATE_GATE, 0);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91sam9260_pmc->chws[PMC_MCK] = hw;
+
+ hw = at91rm9200_clk_register_usb(regmap, "usbck", "pllbck", usb_div);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ parent_names[0] = slck_name;
+ parent_names[1] = "mainck";
+ parent_names[2] = "pllack";
+ parent_names[3] = "pllbck";
+ for (i = 0; i < data->num_progck; i++) {
+ char name[6];
+
+ snprintf(name, sizeof(name), "prog%d", i);
+
+ hw = at91_clk_register_programmable(regmap, name,
+ parent_names, 4, i,
+ &at91rm9200_programmable_layout,
+ NULL);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91sam9260_pmc->pchws[i] = hw;
+ }
+
+ for (i = 0; i < data->num_sck; i++) {
+ hw = at91_clk_register_system(regmap, data->sck[i].n,
+ data->sck[i].p,
+ data->sck[i].id);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91sam9260_pmc->shws[data->sck[i].id] = hw;
+ }
+
+ for (i = 0; i < data->num_pck; i++) {
+ hw = at91_clk_register_peripheral(regmap,
+ data->pck[i].n,
+ "masterck_div",
+ data->pck[i].id);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91sam9260_pmc->phws[data->pck[i].id] = hw;
+ }
+
+ of_clk_add_hw_provider(np, of_clk_hw_pmc_get, at91sam9260_pmc);
+
+ return;
+
+err_free:
+ kfree(at91sam9260_pmc);
+}
+
+static void __init at91sam9260_pmc_setup(struct device_node *np)
+{
+ at91sam926x_pmc_setup(np, &at91sam9260_data);
+}
+
+CLK_OF_DECLARE(at91sam9260_pmc, "atmel,at91sam9260-pmc", at91sam9260_pmc_setup);
+
+static void __init at91sam9261_pmc_setup(struct device_node *np)
+{
+ at91sam926x_pmc_setup(np, &at91sam9261_data);
+}
+
+CLK_OF_DECLARE(at91sam9261_pmc, "atmel,at91sam9261-pmc", at91sam9261_pmc_setup);
+
+static void __init at91sam9263_pmc_setup(struct device_node *np)
+{
+ at91sam926x_pmc_setup(np, &at91sam9263_data);
+}
+
+CLK_OF_DECLARE(at91sam9263_pmc, "atmel,at91sam9263-pmc", at91sam9263_pmc_setup);
+
+static void __init at91sam9g20_pmc_setup(struct device_node *np)
+{
+ at91sam926x_pmc_setup(np, &at91sam9g20_data);
+}
+
+CLK_OF_DECLARE(at91sam9g20_pmc, "atmel,at91sam9g20-pmc", at91sam9g20_pmc_setup);
diff --git a/drivers/clk/at91/at91sam9g45.c b/drivers/clk/at91/at91sam9g45.c
new file mode 100644
index 000000000..8c9344451
--- /dev/null
+++ b/drivers/clk/at91/at91sam9g45.c
@@ -0,0 +1,230 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/clk-provider.h>
+#include <linux/mfd/syscon.h>
+#include <linux/slab.h>
+
+#include <dt-bindings/clock/at91.h>
+
+#include "pmc.h"
+
+static DEFINE_SPINLOCK(at91sam9g45_mck_lock);
+
+static const struct clk_master_characteristics mck_characteristics = {
+ .output = { .min = 0, .max = 133333333 },
+ .divisors = { 1, 2, 4, 3 },
+};
+
+static u8 plla_out[] = { 0, 1, 2, 3, 0, 1, 2, 3 };
+
+static u16 plla_icpll[] = { 0, 0, 0, 0, 1, 1, 1, 1 };
+
+static const struct clk_range plla_outputs[] = {
+ { .min = 745000000, .max = 800000000 },
+ { .min = 695000000, .max = 750000000 },
+ { .min = 645000000, .max = 700000000 },
+ { .min = 595000000, .max = 650000000 },
+ { .min = 545000000, .max = 600000000 },
+ { .min = 495000000, .max = 555000000 },
+ { .min = 445000000, .max = 500000000 },
+ { .min = 400000000, .max = 450000000 },
+};
+
+static const struct clk_pll_characteristics plla_characteristics = {
+ .input = { .min = 2000000, .max = 32000000 },
+ .num_output = ARRAY_SIZE(plla_outputs),
+ .output = plla_outputs,
+ .icpll = plla_icpll,
+ .out = plla_out,
+};
+
+static const struct {
+ char *n;
+ char *p;
+ u8 id;
+} at91sam9g45_systemck[] = {
+ { .n = "ddrck", .p = "masterck_div", .id = 2 },
+ { .n = "uhpck", .p = "usbck", .id = 6 },
+ { .n = "pck0", .p = "prog0", .id = 8 },
+ { .n = "pck1", .p = "prog1", .id = 9 },
+};
+
+struct pck {
+ char *n;
+ u8 id;
+};
+
+static const struct pck at91sam9g45_periphck[] = {
+ { .n = "pioA_clk", .id = 2, },
+ { .n = "pioB_clk", .id = 3, },
+ { .n = "pioC_clk", .id = 4, },
+ { .n = "pioDE_clk", .id = 5, },
+ { .n = "trng_clk", .id = 6, },
+ { .n = "usart0_clk", .id = 7, },
+ { .n = "usart1_clk", .id = 8, },
+ { .n = "usart2_clk", .id = 9, },
+ { .n = "usart3_clk", .id = 10, },
+ { .n = "mci0_clk", .id = 11, },
+ { .n = "twi0_clk", .id = 12, },
+ { .n = "twi1_clk", .id = 13, },
+ { .n = "spi0_clk", .id = 14, },
+ { .n = "spi1_clk", .id = 15, },
+ { .n = "ssc0_clk", .id = 16, },
+ { .n = "ssc1_clk", .id = 17, },
+ { .n = "tcb0_clk", .id = 18, },
+ { .n = "pwm_clk", .id = 19, },
+ { .n = "adc_clk", .id = 20, },
+ { .n = "dma0_clk", .id = 21, },
+ { .n = "uhphs_clk", .id = 22, },
+ { .n = "lcd_clk", .id = 23, },
+ { .n = "ac97_clk", .id = 24, },
+ { .n = "macb0_clk", .id = 25, },
+ { .n = "isi_clk", .id = 26, },
+ { .n = "udphs_clk", .id = 27, },
+ { .n = "aestdessha_clk", .id = 28, },
+ { .n = "mci1_clk", .id = 29, },
+ { .n = "vdec_clk", .id = 30, },
+};
+
+static void __init at91sam9g45_pmc_setup(struct device_node *np)
+{
+ const char *slck_name, *mainxtal_name;
+ struct pmc_data *at91sam9g45_pmc;
+ const char *parent_names[6];
+ struct regmap *regmap;
+ struct clk_hw *hw;
+ int i;
+ bool bypass;
+
+ i = of_property_match_string(np, "clock-names", "slow_clk");
+ if (i < 0)
+ return;
+
+ slck_name = of_clk_get_parent_name(np, i);
+
+ i = of_property_match_string(np, "clock-names", "main_xtal");
+ if (i < 0)
+ return;
+ mainxtal_name = of_clk_get_parent_name(np, i);
+
+ regmap = device_node_to_regmap(np);
+ if (IS_ERR(regmap))
+ return;
+
+ at91sam9g45_pmc = pmc_data_allocate(PMC_PLLACK + 1,
+ nck(at91sam9g45_systemck),
+ nck(at91sam9g45_periphck), 0, 2);
+ if (!at91sam9g45_pmc)
+ return;
+
+ bypass = of_property_read_bool(np, "atmel,osc-bypass");
+
+ hw = at91_clk_register_main_osc(regmap, "main_osc", mainxtal_name,
+ bypass);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ hw = at91_clk_register_rm9200_main(regmap, "mainck", "main_osc");
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91sam9g45_pmc->chws[PMC_MAIN] = hw;
+
+ hw = at91_clk_register_pll(regmap, "pllack", "mainck", 0,
+ &at91rm9200_pll_layout, &plla_characteristics);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ hw = at91_clk_register_plldiv(regmap, "plladivck", "pllack");
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91sam9g45_pmc->chws[PMC_PLLACK] = hw;
+
+ hw = at91_clk_register_utmi(regmap, NULL, "utmick", "mainck");
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91sam9g45_pmc->chws[PMC_UTMI] = hw;
+
+ parent_names[0] = slck_name;
+ parent_names[1] = "mainck";
+ parent_names[2] = "plladivck";
+ parent_names[3] = "utmick";
+ hw = at91_clk_register_master_pres(regmap, "masterck_pres", 4,
+ parent_names,
+ &at91rm9200_master_layout,
+ &mck_characteristics,
+ &at91sam9g45_mck_lock);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ hw = at91_clk_register_master_div(regmap, "masterck_div",
+ "masterck_pres",
+ &at91rm9200_master_layout,
+ &mck_characteristics,
+ &at91sam9g45_mck_lock,
+ CLK_SET_RATE_GATE, 0);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91sam9g45_pmc->chws[PMC_MCK] = hw;
+
+ parent_names[0] = "plladivck";
+ parent_names[1] = "utmick";
+ hw = at91sam9x5_clk_register_usb(regmap, "usbck", parent_names, 2);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ parent_names[0] = slck_name;
+ parent_names[1] = "mainck";
+ parent_names[2] = "plladivck";
+ parent_names[3] = "utmick";
+ parent_names[4] = "masterck_div";
+ for (i = 0; i < 2; i++) {
+ char name[6];
+
+ snprintf(name, sizeof(name), "prog%d", i);
+
+ hw = at91_clk_register_programmable(regmap, name,
+ parent_names, 5, i,
+ &at91sam9g45_programmable_layout,
+ NULL);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91sam9g45_pmc->pchws[i] = hw;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(at91sam9g45_systemck); i++) {
+ hw = at91_clk_register_system(regmap, at91sam9g45_systemck[i].n,
+ at91sam9g45_systemck[i].p,
+ at91sam9g45_systemck[i].id);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91sam9g45_pmc->shws[at91sam9g45_systemck[i].id] = hw;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(at91sam9g45_periphck); i++) {
+ hw = at91_clk_register_peripheral(regmap,
+ at91sam9g45_periphck[i].n,
+ "masterck_div",
+ at91sam9g45_periphck[i].id);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91sam9g45_pmc->phws[at91sam9g45_periphck[i].id] = hw;
+ }
+
+ of_clk_add_hw_provider(np, of_clk_hw_pmc_get, at91sam9g45_pmc);
+
+ return;
+
+err_free:
+ kfree(at91sam9g45_pmc);
+}
+/*
+ * The TCB is used as the clocksource so its clock is needed early. This means
+ * this can't be a platform driver.
+ */
+CLK_OF_DECLARE(at91sam9g45_pmc, "atmel,at91sam9g45-pmc", at91sam9g45_pmc_setup);
diff --git a/drivers/clk/at91/at91sam9n12.c b/drivers/clk/at91/at91sam9n12.c
new file mode 100644
index 000000000..0bb19400d
--- /dev/null
+++ b/drivers/clk/at91/at91sam9n12.c
@@ -0,0 +1,257 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/clk-provider.h>
+#include <linux/mfd/syscon.h>
+#include <linux/slab.h>
+
+#include <dt-bindings/clock/at91.h>
+
+#include "pmc.h"
+
+static DEFINE_SPINLOCK(at91sam9n12_mck_lock);
+
+static const struct clk_master_characteristics mck_characteristics = {
+ .output = { .min = 0, .max = 133333333 },
+ .divisors = { 1, 2, 4, 3 },
+ .have_div3_pres = 1,
+};
+
+static u8 plla_out[] = { 0, 1, 2, 3, 0, 1, 2, 3 };
+
+static u16 plla_icpll[] = { 0, 0, 0, 0, 1, 1, 1, 1 };
+
+static const struct clk_range plla_outputs[] = {
+ { .min = 745000000, .max = 800000000 },
+ { .min = 695000000, .max = 750000000 },
+ { .min = 645000000, .max = 700000000 },
+ { .min = 595000000, .max = 650000000 },
+ { .min = 545000000, .max = 600000000 },
+ { .min = 495000000, .max = 555000000 },
+ { .min = 445000000, .max = 500000000 },
+ { .min = 400000000, .max = 450000000 },
+};
+
+static const struct clk_pll_characteristics plla_characteristics = {
+ .input = { .min = 2000000, .max = 32000000 },
+ .num_output = ARRAY_SIZE(plla_outputs),
+ .output = plla_outputs,
+ .icpll = plla_icpll,
+ .out = plla_out,
+};
+
+static u8 pllb_out[] = { 0 };
+
+static const struct clk_range pllb_outputs[] = {
+ { .min = 30000000, .max = 100000000 },
+};
+
+static const struct clk_pll_characteristics pllb_characteristics = {
+ .input = { .min = 2000000, .max = 32000000 },
+ .num_output = ARRAY_SIZE(pllb_outputs),
+ .output = pllb_outputs,
+ .out = pllb_out,
+};
+
+static const struct {
+ char *n;
+ char *p;
+ u8 id;
+} at91sam9n12_systemck[] = {
+ { .n = "ddrck", .p = "masterck_div", .id = 2 },
+ { .n = "lcdck", .p = "masterck_div", .id = 3 },
+ { .n = "uhpck", .p = "usbck", .id = 6 },
+ { .n = "udpck", .p = "usbck", .id = 7 },
+ { .n = "pck0", .p = "prog0", .id = 8 },
+ { .n = "pck1", .p = "prog1", .id = 9 },
+};
+
+static const struct clk_pcr_layout at91sam9n12_pcr_layout = {
+ .offset = 0x10c,
+ .cmd = BIT(12),
+ .pid_mask = GENMASK(5, 0),
+ .div_mask = GENMASK(17, 16),
+};
+
+struct pck {
+ char *n;
+ u8 id;
+};
+
+static const struct pck at91sam9n12_periphck[] = {
+ { .n = "pioAB_clk", .id = 2, },
+ { .n = "pioCD_clk", .id = 3, },
+ { .n = "fuse_clk", .id = 4, },
+ { .n = "usart0_clk", .id = 5, },
+ { .n = "usart1_clk", .id = 6, },
+ { .n = "usart2_clk", .id = 7, },
+ { .n = "usart3_clk", .id = 8, },
+ { .n = "twi0_clk", .id = 9, },
+ { .n = "twi1_clk", .id = 10, },
+ { .n = "mci0_clk", .id = 12, },
+ { .n = "spi0_clk", .id = 13, },
+ { .n = "spi1_clk", .id = 14, },
+ { .n = "uart0_clk", .id = 15, },
+ { .n = "uart1_clk", .id = 16, },
+ { .n = "tcb_clk", .id = 17, },
+ { .n = "pwm_clk", .id = 18, },
+ { .n = "adc_clk", .id = 19, },
+ { .n = "dma0_clk", .id = 20, },
+ { .n = "uhphs_clk", .id = 22, },
+ { .n = "udphs_clk", .id = 23, },
+ { .n = "lcdc_clk", .id = 25, },
+ { .n = "sha_clk", .id = 27, },
+ { .n = "ssc0_clk", .id = 28, },
+ { .n = "aes_clk", .id = 29, },
+ { .n = "trng_clk", .id = 30, },
+};
+
+static void __init at91sam9n12_pmc_setup(struct device_node *np)
+{
+ struct clk_range range = CLK_RANGE(0, 0);
+ const char *slck_name, *mainxtal_name;
+ struct pmc_data *at91sam9n12_pmc;
+ const char *parent_names[6];
+ struct regmap *regmap;
+ struct clk_hw *hw;
+ int i;
+ bool bypass;
+
+ i = of_property_match_string(np, "clock-names", "slow_clk");
+ if (i < 0)
+ return;
+
+ slck_name = of_clk_get_parent_name(np, i);
+
+ i = of_property_match_string(np, "clock-names", "main_xtal");
+ if (i < 0)
+ return;
+ mainxtal_name = of_clk_get_parent_name(np, i);
+
+ regmap = device_node_to_regmap(np);
+ if (IS_ERR(regmap))
+ return;
+
+ at91sam9n12_pmc = pmc_data_allocate(PMC_PLLBCK + 1,
+ nck(at91sam9n12_systemck), 31, 0, 2);
+ if (!at91sam9n12_pmc)
+ return;
+
+ hw = at91_clk_register_main_rc_osc(regmap, "main_rc_osc", 12000000,
+ 50000000);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ bypass = of_property_read_bool(np, "atmel,osc-bypass");
+
+ hw = at91_clk_register_main_osc(regmap, "main_osc", mainxtal_name,
+ bypass);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ parent_names[0] = "main_rc_osc";
+ parent_names[1] = "main_osc";
+ hw = at91_clk_register_sam9x5_main(regmap, "mainck", parent_names, 2);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91sam9n12_pmc->chws[PMC_MAIN] = hw;
+
+ hw = at91_clk_register_pll(regmap, "pllack", "mainck", 0,
+ &at91rm9200_pll_layout, &plla_characteristics);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ hw = at91_clk_register_plldiv(regmap, "plladivck", "pllack");
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91sam9n12_pmc->chws[PMC_PLLACK] = hw;
+
+ hw = at91_clk_register_pll(regmap, "pllbck", "mainck", 1,
+ &at91rm9200_pll_layout, &pllb_characteristics);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91sam9n12_pmc->chws[PMC_PLLBCK] = hw;
+
+ parent_names[0] = slck_name;
+ parent_names[1] = "mainck";
+ parent_names[2] = "plladivck";
+ parent_names[3] = "pllbck";
+ hw = at91_clk_register_master_pres(regmap, "masterck_pres", 4,
+ parent_names,
+ &at91sam9x5_master_layout,
+ &mck_characteristics,
+ &at91sam9n12_mck_lock);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ hw = at91_clk_register_master_div(regmap, "masterck_div",
+ "masterck_pres",
+ &at91sam9x5_master_layout,
+ &mck_characteristics,
+ &at91sam9n12_mck_lock,
+ CLK_SET_RATE_GATE, 0);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91sam9n12_pmc->chws[PMC_MCK] = hw;
+
+ hw = at91sam9n12_clk_register_usb(regmap, "usbck", "pllbck");
+ if (IS_ERR(hw))
+ goto err_free;
+
+ parent_names[0] = slck_name;
+ parent_names[1] = "mainck";
+ parent_names[2] = "plladivck";
+ parent_names[3] = "pllbck";
+ parent_names[4] = "masterck_div";
+ for (i = 0; i < 2; i++) {
+ char name[6];
+
+ snprintf(name, sizeof(name), "prog%d", i);
+
+ hw = at91_clk_register_programmable(regmap, name,
+ parent_names, 5, i,
+ &at91sam9x5_programmable_layout,
+ NULL);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91sam9n12_pmc->pchws[i] = hw;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(at91sam9n12_systemck); i++) {
+ hw = at91_clk_register_system(regmap, at91sam9n12_systemck[i].n,
+ at91sam9n12_systemck[i].p,
+ at91sam9n12_systemck[i].id);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91sam9n12_pmc->shws[at91sam9n12_systemck[i].id] = hw;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(at91sam9n12_periphck); i++) {
+ hw = at91_clk_register_sam9x5_peripheral(regmap, &pmc_pcr_lock,
+ &at91sam9n12_pcr_layout,
+ at91sam9n12_periphck[i].n,
+ "masterck_div",
+ at91sam9n12_periphck[i].id,
+ &range, INT_MIN);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91sam9n12_pmc->phws[at91sam9n12_periphck[i].id] = hw;
+ }
+
+ of_clk_add_hw_provider(np, of_clk_hw_pmc_get, at91sam9n12_pmc);
+
+ return;
+
+err_free:
+ kfree(at91sam9n12_pmc);
+}
+/*
+ * The TCB is used as the clocksource so its clock is needed early. This means
+ * this can't be a platform driver.
+ */
+CLK_OF_DECLARE(at91sam9n12_pmc, "atmel,at91sam9n12-pmc", at91sam9n12_pmc_setup);
diff --git a/drivers/clk/at91/at91sam9rl.c b/drivers/clk/at91/at91sam9rl.c
new file mode 100644
index 000000000..b992137ba
--- /dev/null
+++ b/drivers/clk/at91/at91sam9rl.c
@@ -0,0 +1,189 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/clk-provider.h>
+#include <linux/mfd/syscon.h>
+#include <linux/slab.h>
+
+#include <dt-bindings/clock/at91.h>
+
+#include "pmc.h"
+
+static DEFINE_SPINLOCK(sam9rl_mck_lock);
+
+static const struct clk_master_characteristics sam9rl_mck_characteristics = {
+ .output = { .min = 0, .max = 94000000 },
+ .divisors = { 1, 2, 4, 0 },
+};
+
+static u8 sam9rl_plla_out[] = { 0, 2 };
+
+static const struct clk_range sam9rl_plla_outputs[] = {
+ { .min = 80000000, .max = 200000000 },
+ { .min = 190000000, .max = 240000000 },
+};
+
+static const struct clk_pll_characteristics sam9rl_plla_characteristics = {
+ .input = { .min = 1000000, .max = 32000000 },
+ .num_output = ARRAY_SIZE(sam9rl_plla_outputs),
+ .output = sam9rl_plla_outputs,
+ .out = sam9rl_plla_out,
+};
+
+static const struct {
+ char *n;
+ char *p;
+ u8 id;
+} at91sam9rl_systemck[] = {
+ { .n = "pck0", .p = "prog0", .id = 8 },
+ { .n = "pck1", .p = "prog1", .id = 9 },
+};
+
+static const struct {
+ char *n;
+ u8 id;
+} at91sam9rl_periphck[] = {
+ { .n = "pioA_clk", .id = 2, },
+ { .n = "pioB_clk", .id = 3, },
+ { .n = "pioC_clk", .id = 4, },
+ { .n = "pioD_clk", .id = 5, },
+ { .n = "usart0_clk", .id = 6, },
+ { .n = "usart1_clk", .id = 7, },
+ { .n = "usart2_clk", .id = 8, },
+ { .n = "usart3_clk", .id = 9, },
+ { .n = "mci0_clk", .id = 10, },
+ { .n = "twi0_clk", .id = 11, },
+ { .n = "twi1_clk", .id = 12, },
+ { .n = "spi0_clk", .id = 13, },
+ { .n = "ssc0_clk", .id = 14, },
+ { .n = "ssc1_clk", .id = 15, },
+ { .n = "tc0_clk", .id = 16, },
+ { .n = "tc1_clk", .id = 17, },
+ { .n = "tc2_clk", .id = 18, },
+ { .n = "pwm_clk", .id = 19, },
+ { .n = "adc_clk", .id = 20, },
+ { .n = "dma0_clk", .id = 21, },
+ { .n = "udphs_clk", .id = 22, },
+ { .n = "lcd_clk", .id = 23, },
+};
+
+static void __init at91sam9rl_pmc_setup(struct device_node *np)
+{
+ const char *slck_name, *mainxtal_name;
+ struct pmc_data *at91sam9rl_pmc;
+ const char *parent_names[6];
+ struct regmap *regmap;
+ struct clk_hw *hw;
+ int i;
+
+ i = of_property_match_string(np, "clock-names", "slow_clk");
+ if (i < 0)
+ return;
+
+ slck_name = of_clk_get_parent_name(np, i);
+
+ i = of_property_match_string(np, "clock-names", "main_xtal");
+ if (i < 0)
+ return;
+ mainxtal_name = of_clk_get_parent_name(np, i);
+
+ regmap = device_node_to_regmap(np);
+ if (IS_ERR(regmap))
+ return;
+
+ at91sam9rl_pmc = pmc_data_allocate(PMC_PLLACK + 1,
+ nck(at91sam9rl_systemck),
+ nck(at91sam9rl_periphck), 0, 2);
+ if (!at91sam9rl_pmc)
+ return;
+
+ hw = at91_clk_register_rm9200_main(regmap, "mainck", mainxtal_name);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91sam9rl_pmc->chws[PMC_MAIN] = hw;
+
+ hw = at91_clk_register_pll(regmap, "pllack", "mainck", 0,
+ &at91rm9200_pll_layout,
+ &sam9rl_plla_characteristics);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91sam9rl_pmc->chws[PMC_PLLACK] = hw;
+
+ hw = at91_clk_register_utmi(regmap, NULL, "utmick", "mainck");
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91sam9rl_pmc->chws[PMC_UTMI] = hw;
+
+ parent_names[0] = slck_name;
+ parent_names[1] = "mainck";
+ parent_names[2] = "pllack";
+ parent_names[3] = "utmick";
+ hw = at91_clk_register_master_pres(regmap, "masterck_pres", 4,
+ parent_names,
+ &at91rm9200_master_layout,
+ &sam9rl_mck_characteristics,
+ &sam9rl_mck_lock);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ hw = at91_clk_register_master_div(regmap, "masterck_div",
+ "masterck_pres",
+ &at91rm9200_master_layout,
+ &sam9rl_mck_characteristics,
+ &sam9rl_mck_lock, CLK_SET_RATE_GATE, 0);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91sam9rl_pmc->chws[PMC_MCK] = hw;
+
+ parent_names[0] = slck_name;
+ parent_names[1] = "mainck";
+ parent_names[2] = "pllack";
+ parent_names[3] = "utmick";
+ parent_names[4] = "masterck_div";
+ for (i = 0; i < 2; i++) {
+ char name[6];
+
+ snprintf(name, sizeof(name), "prog%d", i);
+
+ hw = at91_clk_register_programmable(regmap, name,
+ parent_names, 5, i,
+ &at91rm9200_programmable_layout,
+ NULL);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91sam9rl_pmc->pchws[i] = hw;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(at91sam9rl_systemck); i++) {
+ hw = at91_clk_register_system(regmap, at91sam9rl_systemck[i].n,
+ at91sam9rl_systemck[i].p,
+ at91sam9rl_systemck[i].id);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91sam9rl_pmc->shws[at91sam9rl_systemck[i].id] = hw;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(at91sam9rl_periphck); i++) {
+ hw = at91_clk_register_peripheral(regmap,
+ at91sam9rl_periphck[i].n,
+ "masterck_div",
+ at91sam9rl_periphck[i].id);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91sam9rl_pmc->phws[at91sam9rl_periphck[i].id] = hw;
+ }
+
+ of_clk_add_hw_provider(np, of_clk_hw_pmc_get, at91sam9rl_pmc);
+
+ return;
+
+err_free:
+ kfree(at91sam9rl_pmc);
+}
+
+CLK_OF_DECLARE(at91sam9rl_pmc, "atmel,at91sam9rl-pmc", at91sam9rl_pmc_setup);
diff --git a/drivers/clk/at91/at91sam9x5.c b/drivers/clk/at91/at91sam9x5.c
new file mode 100644
index 000000000..3857db2e1
--- /dev/null
+++ b/drivers/clk/at91/at91sam9x5.c
@@ -0,0 +1,333 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/clk-provider.h>
+#include <linux/mfd/syscon.h>
+#include <linux/slab.h>
+
+#include <dt-bindings/clock/at91.h>
+
+#include "pmc.h"
+
+static DEFINE_SPINLOCK(mck_lock);
+
+static const struct clk_master_characteristics mck_characteristics = {
+ .output = { .min = 0, .max = 133333333 },
+ .divisors = { 1, 2, 4, 3 },
+ .have_div3_pres = 1,
+};
+
+static u8 plla_out[] = { 0, 1, 2, 3, 0, 1, 2, 3 };
+
+static u16 plla_icpll[] = { 0, 0, 0, 0, 1, 1, 1, 1 };
+
+static const struct clk_range plla_outputs[] = {
+ { .min = 745000000, .max = 800000000 },
+ { .min = 695000000, .max = 750000000 },
+ { .min = 645000000, .max = 700000000 },
+ { .min = 595000000, .max = 650000000 },
+ { .min = 545000000, .max = 600000000 },
+ { .min = 495000000, .max = 555000000 },
+ { .min = 445000000, .max = 500000000 },
+ { .min = 400000000, .max = 450000000 },
+};
+
+static const struct clk_pll_characteristics plla_characteristics = {
+ .input = { .min = 2000000, .max = 32000000 },
+ .num_output = ARRAY_SIZE(plla_outputs),
+ .output = plla_outputs,
+ .icpll = plla_icpll,
+ .out = plla_out,
+};
+
+static const struct {
+ char *n;
+ char *p;
+ u8 id;
+} at91sam9x5_systemck[] = {
+ { .n = "ddrck", .p = "masterck_div", .id = 2 },
+ { .n = "smdck", .p = "smdclk", .id = 4 },
+ { .n = "uhpck", .p = "usbck", .id = 6 },
+ { .n = "udpck", .p = "usbck", .id = 7 },
+ { .n = "pck0", .p = "prog0", .id = 8 },
+ { .n = "pck1", .p = "prog1", .id = 9 },
+};
+
+static const struct clk_pcr_layout at91sam9x5_pcr_layout = {
+ .offset = 0x10c,
+ .cmd = BIT(12),
+ .pid_mask = GENMASK(5, 0),
+ .div_mask = GENMASK(17, 16),
+};
+
+struct pck {
+ char *n;
+ u8 id;
+};
+
+static const struct pck at91sam9x5_periphck[] = {
+ { .n = "pioAB_clk", .id = 2, },
+ { .n = "pioCD_clk", .id = 3, },
+ { .n = "smd_clk", .id = 4, },
+ { .n = "usart0_clk", .id = 5, },
+ { .n = "usart1_clk", .id = 6, },
+ { .n = "usart2_clk", .id = 7, },
+ { .n = "twi0_clk", .id = 9, },
+ { .n = "twi1_clk", .id = 10, },
+ { .n = "twi2_clk", .id = 11, },
+ { .n = "mci0_clk", .id = 12, },
+ { .n = "spi0_clk", .id = 13, },
+ { .n = "spi1_clk", .id = 14, },
+ { .n = "uart0_clk", .id = 15, },
+ { .n = "uart1_clk", .id = 16, },
+ { .n = "tcb0_clk", .id = 17, },
+ { .n = "pwm_clk", .id = 18, },
+ { .n = "adc_clk", .id = 19, },
+ { .n = "dma0_clk", .id = 20, },
+ { .n = "dma1_clk", .id = 21, },
+ { .n = "uhphs_clk", .id = 22, },
+ { .n = "udphs_clk", .id = 23, },
+ { .n = "mci1_clk", .id = 26, },
+ { .n = "ssc0_clk", .id = 28, },
+};
+
+static const struct pck at91sam9g15_periphck[] = {
+ { .n = "lcdc_clk", .id = 25, },
+ { /* sentinel */}
+};
+
+static const struct pck at91sam9g25_periphck[] = {
+ { .n = "usart3_clk", .id = 8, },
+ { .n = "macb0_clk", .id = 24, },
+ { .n = "isi_clk", .id = 25, },
+ { /* sentinel */}
+};
+
+static const struct pck at91sam9g35_periphck[] = {
+ { .n = "macb0_clk", .id = 24, },
+ { .n = "lcdc_clk", .id = 25, },
+ { /* sentinel */}
+};
+
+static const struct pck at91sam9x25_periphck[] = {
+ { .n = "usart3_clk", .id = 8, },
+ { .n = "macb0_clk", .id = 24, },
+ { .n = "macb1_clk", .id = 27, },
+ { .n = "can0_clk", .id = 29, },
+ { .n = "can1_clk", .id = 30, },
+ { /* sentinel */}
+};
+
+static const struct pck at91sam9x35_periphck[] = {
+ { .n = "macb0_clk", .id = 24, },
+ { .n = "lcdc_clk", .id = 25, },
+ { .n = "can0_clk", .id = 29, },
+ { .n = "can1_clk", .id = 30, },
+ { /* sentinel */}
+};
+
+static void __init at91sam9x5_pmc_setup(struct device_node *np,
+ const struct pck *extra_pcks,
+ bool has_lcdck)
+{
+ struct clk_range range = CLK_RANGE(0, 0);
+ const char *slck_name, *mainxtal_name;
+ struct pmc_data *at91sam9x5_pmc;
+ const char *parent_names[6];
+ struct regmap *regmap;
+ struct clk_hw *hw;
+ int i;
+ bool bypass;
+
+ i = of_property_match_string(np, "clock-names", "slow_clk");
+ if (i < 0)
+ return;
+
+ slck_name = of_clk_get_parent_name(np, i);
+
+ i = of_property_match_string(np, "clock-names", "main_xtal");
+ if (i < 0)
+ return;
+ mainxtal_name = of_clk_get_parent_name(np, i);
+
+ regmap = device_node_to_regmap(np);
+ if (IS_ERR(regmap))
+ return;
+
+ at91sam9x5_pmc = pmc_data_allocate(PMC_PLLACK + 1,
+ nck(at91sam9x5_systemck), 31, 0, 2);
+ if (!at91sam9x5_pmc)
+ return;
+
+ hw = at91_clk_register_main_rc_osc(regmap, "main_rc_osc", 12000000,
+ 50000000);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ bypass = of_property_read_bool(np, "atmel,osc-bypass");
+
+ hw = at91_clk_register_main_osc(regmap, "main_osc", mainxtal_name,
+ bypass);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ parent_names[0] = "main_rc_osc";
+ parent_names[1] = "main_osc";
+ hw = at91_clk_register_sam9x5_main(regmap, "mainck", parent_names, 2);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91sam9x5_pmc->chws[PMC_MAIN] = hw;
+
+ hw = at91_clk_register_pll(regmap, "pllack", "mainck", 0,
+ &at91rm9200_pll_layout, &plla_characteristics);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ hw = at91_clk_register_plldiv(regmap, "plladivck", "pllack");
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91sam9x5_pmc->chws[PMC_PLLACK] = hw;
+
+ hw = at91_clk_register_utmi(regmap, NULL, "utmick", "mainck");
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91sam9x5_pmc->chws[PMC_UTMI] = hw;
+
+ parent_names[0] = slck_name;
+ parent_names[1] = "mainck";
+ parent_names[2] = "plladivck";
+ parent_names[3] = "utmick";
+ hw = at91_clk_register_master_pres(regmap, "masterck_pres", 4,
+ parent_names,
+ &at91sam9x5_master_layout,
+ &mck_characteristics, &mck_lock);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ hw = at91_clk_register_master_div(regmap, "masterck_div",
+ "masterck_pres",
+ &at91sam9x5_master_layout,
+ &mck_characteristics, &mck_lock,
+ CLK_SET_RATE_GATE, 0);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91sam9x5_pmc->chws[PMC_MCK] = hw;
+
+ parent_names[0] = "plladivck";
+ parent_names[1] = "utmick";
+ hw = at91sam9x5_clk_register_usb(regmap, "usbck", parent_names, 2);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ hw = at91sam9x5_clk_register_smd(regmap, "smdclk", parent_names, 2);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ parent_names[0] = slck_name;
+ parent_names[1] = "mainck";
+ parent_names[2] = "plladivck";
+ parent_names[3] = "utmick";
+ parent_names[4] = "masterck_div";
+ for (i = 0; i < 2; i++) {
+ char name[6];
+
+ snprintf(name, sizeof(name), "prog%d", i);
+
+ hw = at91_clk_register_programmable(regmap, name,
+ parent_names, 5, i,
+ &at91sam9x5_programmable_layout,
+ NULL);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91sam9x5_pmc->pchws[i] = hw;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(at91sam9x5_systemck); i++) {
+ hw = at91_clk_register_system(regmap, at91sam9x5_systemck[i].n,
+ at91sam9x5_systemck[i].p,
+ at91sam9x5_systemck[i].id);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91sam9x5_pmc->shws[at91sam9x5_systemck[i].id] = hw;
+ }
+
+ if (has_lcdck) {
+ hw = at91_clk_register_system(regmap, "lcdck", "masterck_div", 3);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91sam9x5_pmc->shws[3] = hw;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(at91sam9x5_periphck); i++) {
+ hw = at91_clk_register_sam9x5_peripheral(regmap, &pmc_pcr_lock,
+ &at91sam9x5_pcr_layout,
+ at91sam9x5_periphck[i].n,
+ "masterck_div",
+ at91sam9x5_periphck[i].id,
+ &range, INT_MIN);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91sam9x5_pmc->phws[at91sam9x5_periphck[i].id] = hw;
+ }
+
+ for (i = 0; extra_pcks[i].id; i++) {
+ hw = at91_clk_register_sam9x5_peripheral(regmap, &pmc_pcr_lock,
+ &at91sam9x5_pcr_layout,
+ extra_pcks[i].n,
+ "masterck_div",
+ extra_pcks[i].id,
+ &range, INT_MIN);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ at91sam9x5_pmc->phws[extra_pcks[i].id] = hw;
+ }
+
+ of_clk_add_hw_provider(np, of_clk_hw_pmc_get, at91sam9x5_pmc);
+
+ return;
+
+err_free:
+ kfree(at91sam9x5_pmc);
+}
+
+static void __init at91sam9g15_pmc_setup(struct device_node *np)
+{
+ at91sam9x5_pmc_setup(np, at91sam9g15_periphck, true);
+}
+
+CLK_OF_DECLARE(at91sam9g15_pmc, "atmel,at91sam9g15-pmc", at91sam9g15_pmc_setup);
+
+static void __init at91sam9g25_pmc_setup(struct device_node *np)
+{
+ at91sam9x5_pmc_setup(np, at91sam9g25_periphck, false);
+}
+
+CLK_OF_DECLARE(at91sam9g25_pmc, "atmel,at91sam9g25-pmc", at91sam9g25_pmc_setup);
+
+static void __init at91sam9g35_pmc_setup(struct device_node *np)
+{
+ at91sam9x5_pmc_setup(np, at91sam9g35_periphck, true);
+}
+
+CLK_OF_DECLARE(at91sam9g35_pmc, "atmel,at91sam9g35-pmc", at91sam9g35_pmc_setup);
+
+static void __init at91sam9x25_pmc_setup(struct device_node *np)
+{
+ at91sam9x5_pmc_setup(np, at91sam9x25_periphck, false);
+}
+
+CLK_OF_DECLARE(at91sam9x25_pmc, "atmel,at91sam9x25-pmc", at91sam9x25_pmc_setup);
+
+static void __init at91sam9x35_pmc_setup(struct device_node *np)
+{
+ at91sam9x5_pmc_setup(np, at91sam9x35_periphck, true);
+}
+
+CLK_OF_DECLARE(at91sam9x35_pmc, "atmel,at91sam9x35-pmc", at91sam9x35_pmc_setup);
diff --git a/drivers/clk/at91/clk-audio-pll.c b/drivers/clk/at91/clk-audio-pll.c
new file mode 100644
index 000000000..a92da64c1
--- /dev/null
+++ b/drivers/clk/at91/clk-audio-pll.c
@@ -0,0 +1,541 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2016 Atmel Corporation,
+ * Songjun Wu <songjun.wu@atmel.com>,
+ * Nicolas Ferre <nicolas.ferre@atmel.com>
+ * Copyright (C) 2017 Free Electrons,
+ * Quentin Schulz <quentin.schulz@free-electrons.com>
+ *
+ * The Sama5d2 SoC has two audio PLLs (PMC and PAD) that shares the same parent
+ * (FRAC). FRAC can output between 620 and 700MHz and only multiply the rate of
+ * its own parent. PMC and PAD can then divide the FRAC rate to best match the
+ * asked rate.
+ *
+ * Traits of FRAC clock:
+ * enable - clk_enable writes nd, fracr parameters and enables PLL
+ * rate - rate is adjustable.
+ * clk->rate = parent->rate * ((nd + 1) + (fracr / 2^22))
+ * parent - fixed parent. No clk_set_parent support
+ *
+ * Traits of PMC clock:
+ * enable - clk_enable writes qdpmc, and enables PMC output
+ * rate - rate is adjustable.
+ * clk->rate = parent->rate / (qdpmc + 1)
+ * parent - fixed parent. No clk_set_parent support
+ *
+ * Traits of PAD clock:
+ * enable - clk_enable writes divisors and enables PAD output
+ * rate - rate is adjustable.
+ * clk->rate = parent->rate / (qdaudio * div))
+ * parent - fixed parent. No clk_set_parent support
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/clk/at91_pmc.h>
+#include <linux/of.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#include "pmc.h"
+
+#define AUDIO_PLL_DIV_FRAC BIT(22)
+#define AUDIO_PLL_ND_MAX (AT91_PMC_AUDIO_PLL_ND_MASK >> \
+ AT91_PMC_AUDIO_PLL_ND_OFFSET)
+
+#define AUDIO_PLL_QDPAD(qd, div) ((AT91_PMC_AUDIO_PLL_QDPAD_EXTDIV(qd) & \
+ AT91_PMC_AUDIO_PLL_QDPAD_EXTDIV_MASK) | \
+ (AT91_PMC_AUDIO_PLL_QDPAD_DIV(div) & \
+ AT91_PMC_AUDIO_PLL_QDPAD_DIV_MASK))
+
+#define AUDIO_PLL_QDPMC_MAX (AT91_PMC_AUDIO_PLL_QDPMC_MASK >> \
+ AT91_PMC_AUDIO_PLL_QDPMC_OFFSET)
+
+#define AUDIO_PLL_FOUT_MIN 620000000UL
+#define AUDIO_PLL_FOUT_MAX 700000000UL
+
+struct clk_audio_frac {
+ struct clk_hw hw;
+ struct regmap *regmap;
+ u32 fracr;
+ u8 nd;
+};
+
+struct clk_audio_pad {
+ struct clk_hw hw;
+ struct regmap *regmap;
+ u8 qdaudio;
+ u8 div;
+};
+
+struct clk_audio_pmc {
+ struct clk_hw hw;
+ struct regmap *regmap;
+ u8 qdpmc;
+};
+
+#define to_clk_audio_frac(hw) container_of(hw, struct clk_audio_frac, hw)
+#define to_clk_audio_pad(hw) container_of(hw, struct clk_audio_pad, hw)
+#define to_clk_audio_pmc(hw) container_of(hw, struct clk_audio_pmc, hw)
+
+static int clk_audio_pll_frac_enable(struct clk_hw *hw)
+{
+ struct clk_audio_frac *frac = to_clk_audio_frac(hw);
+
+ regmap_update_bits(frac->regmap, AT91_PMC_AUDIO_PLL0,
+ AT91_PMC_AUDIO_PLL_RESETN, 0);
+ regmap_update_bits(frac->regmap, AT91_PMC_AUDIO_PLL0,
+ AT91_PMC_AUDIO_PLL_RESETN,
+ AT91_PMC_AUDIO_PLL_RESETN);
+ regmap_update_bits(frac->regmap, AT91_PMC_AUDIO_PLL1,
+ AT91_PMC_AUDIO_PLL_FRACR_MASK, frac->fracr);
+
+ /*
+ * reset and enable have to be done in 2 separated writes
+ * for AT91_PMC_AUDIO_PLL0
+ */
+ regmap_update_bits(frac->regmap, AT91_PMC_AUDIO_PLL0,
+ AT91_PMC_AUDIO_PLL_PLLEN |
+ AT91_PMC_AUDIO_PLL_ND_MASK,
+ AT91_PMC_AUDIO_PLL_PLLEN |
+ AT91_PMC_AUDIO_PLL_ND(frac->nd));
+
+ return 0;
+}
+
+static int clk_audio_pll_pad_enable(struct clk_hw *hw)
+{
+ struct clk_audio_pad *apad_ck = to_clk_audio_pad(hw);
+
+ regmap_update_bits(apad_ck->regmap, AT91_PMC_AUDIO_PLL1,
+ AT91_PMC_AUDIO_PLL_QDPAD_MASK,
+ AUDIO_PLL_QDPAD(apad_ck->qdaudio, apad_ck->div));
+ regmap_update_bits(apad_ck->regmap, AT91_PMC_AUDIO_PLL0,
+ AT91_PMC_AUDIO_PLL_PADEN, AT91_PMC_AUDIO_PLL_PADEN);
+
+ return 0;
+}
+
+static int clk_audio_pll_pmc_enable(struct clk_hw *hw)
+{
+ struct clk_audio_pmc *apmc_ck = to_clk_audio_pmc(hw);
+
+ regmap_update_bits(apmc_ck->regmap, AT91_PMC_AUDIO_PLL0,
+ AT91_PMC_AUDIO_PLL_PMCEN |
+ AT91_PMC_AUDIO_PLL_QDPMC_MASK,
+ AT91_PMC_AUDIO_PLL_PMCEN |
+ AT91_PMC_AUDIO_PLL_QDPMC(apmc_ck->qdpmc));
+ return 0;
+}
+
+static void clk_audio_pll_frac_disable(struct clk_hw *hw)
+{
+ struct clk_audio_frac *frac = to_clk_audio_frac(hw);
+
+ regmap_update_bits(frac->regmap, AT91_PMC_AUDIO_PLL0,
+ AT91_PMC_AUDIO_PLL_PLLEN, 0);
+ /* do it in 2 separated writes */
+ regmap_update_bits(frac->regmap, AT91_PMC_AUDIO_PLL0,
+ AT91_PMC_AUDIO_PLL_RESETN, 0);
+}
+
+static void clk_audio_pll_pad_disable(struct clk_hw *hw)
+{
+ struct clk_audio_pad *apad_ck = to_clk_audio_pad(hw);
+
+ regmap_update_bits(apad_ck->regmap, AT91_PMC_AUDIO_PLL0,
+ AT91_PMC_AUDIO_PLL_PADEN, 0);
+}
+
+static void clk_audio_pll_pmc_disable(struct clk_hw *hw)
+{
+ struct clk_audio_pmc *apmc_ck = to_clk_audio_pmc(hw);
+
+ regmap_update_bits(apmc_ck->regmap, AT91_PMC_AUDIO_PLL0,
+ AT91_PMC_AUDIO_PLL_PMCEN, 0);
+}
+
+static unsigned long clk_audio_pll_fout(unsigned long parent_rate,
+ unsigned long nd, unsigned long fracr)
+{
+ unsigned long long fr = (unsigned long long)parent_rate * fracr;
+
+ pr_debug("A PLL: %s, fr = %llu\n", __func__, fr);
+
+ fr = DIV_ROUND_CLOSEST_ULL(fr, AUDIO_PLL_DIV_FRAC);
+
+ pr_debug("A PLL: %s, fr = %llu\n", __func__, fr);
+
+ return parent_rate * (nd + 1) + fr;
+}
+
+static unsigned long clk_audio_pll_frac_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct clk_audio_frac *frac = to_clk_audio_frac(hw);
+ unsigned long fout;
+
+ fout = clk_audio_pll_fout(parent_rate, frac->nd, frac->fracr);
+
+ pr_debug("A PLL: %s, fout = %lu (nd = %u, fracr = %lu)\n", __func__,
+ fout, frac->nd, (unsigned long)frac->fracr);
+
+ return fout;
+}
+
+static unsigned long clk_audio_pll_pad_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct clk_audio_pad *apad_ck = to_clk_audio_pad(hw);
+ unsigned long apad_rate = 0;
+
+ if (apad_ck->qdaudio && apad_ck->div)
+ apad_rate = parent_rate / (apad_ck->qdaudio * apad_ck->div);
+
+ pr_debug("A PLL/PAD: %s, apad_rate = %lu (div = %u, qdaudio = %u)\n",
+ __func__, apad_rate, apad_ck->div, apad_ck->qdaudio);
+
+ return apad_rate;
+}
+
+static unsigned long clk_audio_pll_pmc_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct clk_audio_pmc *apmc_ck = to_clk_audio_pmc(hw);
+ unsigned long apmc_rate = 0;
+
+ apmc_rate = parent_rate / (apmc_ck->qdpmc + 1);
+
+ pr_debug("A PLL/PMC: %s, apmc_rate = %lu (qdpmc = %u)\n", __func__,
+ apmc_rate, apmc_ck->qdpmc);
+
+ return apmc_rate;
+}
+
+static int clk_audio_pll_frac_compute_frac(unsigned long rate,
+ unsigned long parent_rate,
+ unsigned long *nd,
+ unsigned long *fracr)
+{
+ unsigned long long tmp, rem;
+
+ if (!rate)
+ return -EINVAL;
+
+ tmp = rate;
+ rem = do_div(tmp, parent_rate);
+ if (!tmp || tmp >= AUDIO_PLL_ND_MAX)
+ return -EINVAL;
+
+ *nd = tmp - 1;
+
+ tmp = rem * AUDIO_PLL_DIV_FRAC;
+ tmp = DIV_ROUND_CLOSEST_ULL(tmp, parent_rate);
+ if (tmp > AT91_PMC_AUDIO_PLL_FRACR_MASK)
+ return -EINVAL;
+
+ /* we can cast here as we verified the bounds just above */
+ *fracr = (unsigned long)tmp;
+
+ return 0;
+}
+
+static int clk_audio_pll_frac_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ unsigned long fracr, nd;
+ int ret;
+
+ pr_debug("A PLL: %s, rate = %lu (parent_rate = %lu)\n", __func__,
+ req->rate, req->best_parent_rate);
+
+ req->rate = clamp(req->rate, AUDIO_PLL_FOUT_MIN, AUDIO_PLL_FOUT_MAX);
+
+ req->min_rate = max(req->min_rate, AUDIO_PLL_FOUT_MIN);
+ req->max_rate = min(req->max_rate, AUDIO_PLL_FOUT_MAX);
+
+ ret = clk_audio_pll_frac_compute_frac(req->rate, req->best_parent_rate,
+ &nd, &fracr);
+ if (ret)
+ return ret;
+
+ req->rate = clk_audio_pll_fout(req->best_parent_rate, nd, fracr);
+
+ req->best_parent_hw = clk_hw_get_parent(hw);
+
+ pr_debug("A PLL: %s, best_rate = %lu (nd = %lu, fracr = %lu)\n",
+ __func__, req->rate, nd, fracr);
+
+ return 0;
+}
+
+static long clk_audio_pll_pad_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ struct clk_hw *pclk = clk_hw_get_parent(hw);
+ long best_rate = -EINVAL;
+ unsigned long best_parent_rate;
+ unsigned long tmp_qd;
+ u32 div;
+ long tmp_rate;
+ int tmp_diff;
+ int best_diff = -1;
+
+ pr_debug("A PLL/PAD: %s, rate = %lu (parent_rate = %lu)\n", __func__,
+ rate, *parent_rate);
+
+ /*
+ * Rate divisor is actually made of two different divisors, multiplied
+ * between themselves before dividing the rate.
+ * tmp_qd goes from 1 to 31 and div is either 2 or 3.
+ * In order to avoid testing twice the rate divisor (e.g. divisor 12 can
+ * be found with (tmp_qd, div) = (2, 6) or (3, 4)), we remove any loop
+ * for a rate divisor when div is 2 and tmp_qd is a multiple of 3.
+ * We cannot inverse it (condition div is 3 and tmp_qd is even) or we
+ * would miss some rate divisor that aren't reachable with div being 2
+ * (e.g. rate divisor 90 is made with div = 3 and tmp_qd = 30, thus
+ * tmp_qd is even so we skip it because we think div 2 could make this
+ * rate divisor which isn't possible since tmp_qd has to be <= 31).
+ */
+ for (tmp_qd = 1; tmp_qd < AT91_PMC_AUDIO_PLL_QDPAD_EXTDIV_MAX; tmp_qd++)
+ for (div = 2; div <= 3; div++) {
+ if (div == 2 && tmp_qd % 3 == 0)
+ continue;
+
+ best_parent_rate = clk_hw_round_rate(pclk,
+ rate * tmp_qd * div);
+ tmp_rate = best_parent_rate / (div * tmp_qd);
+ tmp_diff = abs(rate - tmp_rate);
+
+ if (best_diff < 0 || best_diff > tmp_diff) {
+ *parent_rate = best_parent_rate;
+ best_rate = tmp_rate;
+ best_diff = tmp_diff;
+ }
+ }
+
+ pr_debug("A PLL/PAD: %s, best_rate = %ld, best_parent_rate = %lu\n",
+ __func__, best_rate, best_parent_rate);
+
+ return best_rate;
+}
+
+static long clk_audio_pll_pmc_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ struct clk_hw *pclk = clk_hw_get_parent(hw);
+ long best_rate = -EINVAL;
+ unsigned long best_parent_rate = 0;
+ u32 tmp_qd = 0, div;
+ long tmp_rate;
+ int tmp_diff;
+ int best_diff = -1;
+
+ pr_debug("A PLL/PMC: %s, rate = %lu (parent_rate = %lu)\n", __func__,
+ rate, *parent_rate);
+
+ if (!rate)
+ return 0;
+
+ best_parent_rate = clk_round_rate(pclk->clk, 1);
+ div = max(best_parent_rate / rate, 1UL);
+ for (; div <= AUDIO_PLL_QDPMC_MAX; div++) {
+ best_parent_rate = clk_round_rate(pclk->clk, rate * div);
+ tmp_rate = best_parent_rate / div;
+ tmp_diff = abs(rate - tmp_rate);
+
+ if (best_diff < 0 || best_diff > tmp_diff) {
+ *parent_rate = best_parent_rate;
+ best_rate = tmp_rate;
+ best_diff = tmp_diff;
+ tmp_qd = div;
+ if (!best_diff)
+ break; /* got exact match */
+ }
+ }
+
+ pr_debug("A PLL/PMC: %s, best_rate = %ld, best_parent_rate = %lu (qd = %d)\n",
+ __func__, best_rate, *parent_rate, tmp_qd - 1);
+
+ return best_rate;
+}
+
+static int clk_audio_pll_frac_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct clk_audio_frac *frac = to_clk_audio_frac(hw);
+ unsigned long fracr, nd;
+ int ret;
+
+ pr_debug("A PLL: %s, rate = %lu (parent_rate = %lu)\n", __func__, rate,
+ parent_rate);
+
+ if (rate < AUDIO_PLL_FOUT_MIN || rate > AUDIO_PLL_FOUT_MAX)
+ return -EINVAL;
+
+ ret = clk_audio_pll_frac_compute_frac(rate, parent_rate, &nd, &fracr);
+ if (ret)
+ return ret;
+
+ frac->nd = nd;
+ frac->fracr = fracr;
+
+ return 0;
+}
+
+static int clk_audio_pll_pad_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct clk_audio_pad *apad_ck = to_clk_audio_pad(hw);
+ u8 tmp_div;
+
+ pr_debug("A PLL/PAD: %s, rate = %lu (parent_rate = %lu)\n", __func__,
+ rate, parent_rate);
+
+ if (!rate)
+ return -EINVAL;
+
+ tmp_div = parent_rate / rate;
+ if (tmp_div % 3 == 0) {
+ apad_ck->qdaudio = tmp_div / 3;
+ apad_ck->div = 3;
+ } else {
+ apad_ck->qdaudio = tmp_div / 2;
+ apad_ck->div = 2;
+ }
+
+ return 0;
+}
+
+static int clk_audio_pll_pmc_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct clk_audio_pmc *apmc_ck = to_clk_audio_pmc(hw);
+
+ if (!rate)
+ return -EINVAL;
+
+ pr_debug("A PLL/PMC: %s, rate = %lu (parent_rate = %lu)\n", __func__,
+ rate, parent_rate);
+
+ apmc_ck->qdpmc = parent_rate / rate - 1;
+
+ return 0;
+}
+
+static const struct clk_ops audio_pll_frac_ops = {
+ .enable = clk_audio_pll_frac_enable,
+ .disable = clk_audio_pll_frac_disable,
+ .recalc_rate = clk_audio_pll_frac_recalc_rate,
+ .determine_rate = clk_audio_pll_frac_determine_rate,
+ .set_rate = clk_audio_pll_frac_set_rate,
+};
+
+static const struct clk_ops audio_pll_pad_ops = {
+ .enable = clk_audio_pll_pad_enable,
+ .disable = clk_audio_pll_pad_disable,
+ .recalc_rate = clk_audio_pll_pad_recalc_rate,
+ .round_rate = clk_audio_pll_pad_round_rate,
+ .set_rate = clk_audio_pll_pad_set_rate,
+};
+
+static const struct clk_ops audio_pll_pmc_ops = {
+ .enable = clk_audio_pll_pmc_enable,
+ .disable = clk_audio_pll_pmc_disable,
+ .recalc_rate = clk_audio_pll_pmc_recalc_rate,
+ .round_rate = clk_audio_pll_pmc_round_rate,
+ .set_rate = clk_audio_pll_pmc_set_rate,
+};
+
+struct clk_hw * __init
+at91_clk_register_audio_pll_frac(struct regmap *regmap, const char *name,
+ const char *parent_name)
+{
+ struct clk_audio_frac *frac_ck;
+ struct clk_init_data init = {};
+ int ret;
+
+ frac_ck = kzalloc(sizeof(*frac_ck), GFP_KERNEL);
+ if (!frac_ck)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &audio_pll_frac_ops;
+ init.parent_names = &parent_name;
+ init.num_parents = 1;
+ init.flags = CLK_SET_RATE_GATE;
+
+ frac_ck->hw.init = &init;
+ frac_ck->regmap = regmap;
+
+ ret = clk_hw_register(NULL, &frac_ck->hw);
+ if (ret) {
+ kfree(frac_ck);
+ return ERR_PTR(ret);
+ }
+
+ return &frac_ck->hw;
+}
+
+struct clk_hw * __init
+at91_clk_register_audio_pll_pad(struct regmap *regmap, const char *name,
+ const char *parent_name)
+{
+ struct clk_audio_pad *apad_ck;
+ struct clk_init_data init;
+ int ret;
+
+ apad_ck = kzalloc(sizeof(*apad_ck), GFP_KERNEL);
+ if (!apad_ck)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &audio_pll_pad_ops;
+ init.parent_names = &parent_name;
+ init.num_parents = 1;
+ init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE |
+ CLK_SET_RATE_PARENT;
+
+ apad_ck->hw.init = &init;
+ apad_ck->regmap = regmap;
+
+ ret = clk_hw_register(NULL, &apad_ck->hw);
+ if (ret) {
+ kfree(apad_ck);
+ return ERR_PTR(ret);
+ }
+
+ return &apad_ck->hw;
+}
+
+struct clk_hw * __init
+at91_clk_register_audio_pll_pmc(struct regmap *regmap, const char *name,
+ const char *parent_name)
+{
+ struct clk_audio_pmc *apmc_ck;
+ struct clk_init_data init;
+ int ret;
+
+ apmc_ck = kzalloc(sizeof(*apmc_ck), GFP_KERNEL);
+ if (!apmc_ck)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &audio_pll_pmc_ops;
+ init.parent_names = &parent_name;
+ init.num_parents = 1;
+ init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE |
+ CLK_SET_RATE_PARENT;
+
+ apmc_ck->hw.init = &init;
+ apmc_ck->regmap = regmap;
+
+ ret = clk_hw_register(NULL, &apmc_ck->hw);
+ if (ret) {
+ kfree(apmc_ck);
+ return ERR_PTR(ret);
+ }
+
+ return &apmc_ck->hw;
+}
diff --git a/drivers/clk/at91/clk-generated.c b/drivers/clk/at91/clk-generated.c
new file mode 100644
index 000000000..943ea67bf
--- /dev/null
+++ b/drivers/clk/at91/clk-generated.c
@@ -0,0 +1,361 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2015 Atmel Corporation,
+ * Nicolas Ferre <nicolas.ferre@atmel.com>
+ *
+ * Based on clk-programmable & clk-peripheral drivers by Boris BREZILLON.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/clk/at91_pmc.h>
+#include <linux/of.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+
+#include "pmc.h"
+
+#define GENERATED_MAX_DIV 255
+
+struct clk_generated {
+ struct clk_hw hw;
+ struct regmap *regmap;
+ struct clk_range range;
+ spinlock_t *lock;
+ u32 *mux_table;
+ u32 id;
+ u32 gckdiv;
+ const struct clk_pcr_layout *layout;
+ struct at91_clk_pms pms;
+ u8 parent_id;
+ int chg_pid;
+};
+
+#define to_clk_generated(hw) \
+ container_of(hw, struct clk_generated, hw)
+
+static int clk_generated_set(struct clk_generated *gck, int status)
+{
+ unsigned long flags;
+ unsigned int enable = status ? AT91_PMC_PCR_GCKEN : 0;
+
+ spin_lock_irqsave(gck->lock, flags);
+ regmap_write(gck->regmap, gck->layout->offset,
+ (gck->id & gck->layout->pid_mask));
+ regmap_update_bits(gck->regmap, gck->layout->offset,
+ AT91_PMC_PCR_GCKDIV_MASK | gck->layout->gckcss_mask |
+ gck->layout->cmd | enable,
+ field_prep(gck->layout->gckcss_mask, gck->parent_id) |
+ gck->layout->cmd |
+ FIELD_PREP(AT91_PMC_PCR_GCKDIV_MASK, gck->gckdiv) |
+ enable);
+ spin_unlock_irqrestore(gck->lock, flags);
+
+ return 0;
+}
+
+static int clk_generated_enable(struct clk_hw *hw)
+{
+ struct clk_generated *gck = to_clk_generated(hw);
+
+ pr_debug("GCLK: %s, gckdiv = %d, parent id = %d\n",
+ __func__, gck->gckdiv, gck->parent_id);
+
+ clk_generated_set(gck, 1);
+
+ return 0;
+}
+
+static void clk_generated_disable(struct clk_hw *hw)
+{
+ struct clk_generated *gck = to_clk_generated(hw);
+ unsigned long flags;
+
+ spin_lock_irqsave(gck->lock, flags);
+ regmap_write(gck->regmap, gck->layout->offset,
+ (gck->id & gck->layout->pid_mask));
+ regmap_update_bits(gck->regmap, gck->layout->offset,
+ gck->layout->cmd | AT91_PMC_PCR_GCKEN,
+ gck->layout->cmd);
+ spin_unlock_irqrestore(gck->lock, flags);
+}
+
+static int clk_generated_is_enabled(struct clk_hw *hw)
+{
+ struct clk_generated *gck = to_clk_generated(hw);
+ unsigned long flags;
+ unsigned int status;
+
+ spin_lock_irqsave(gck->lock, flags);
+ regmap_write(gck->regmap, gck->layout->offset,
+ (gck->id & gck->layout->pid_mask));
+ regmap_read(gck->regmap, gck->layout->offset, &status);
+ spin_unlock_irqrestore(gck->lock, flags);
+
+ return !!(status & AT91_PMC_PCR_GCKEN);
+}
+
+static unsigned long
+clk_generated_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct clk_generated *gck = to_clk_generated(hw);
+
+ return DIV_ROUND_CLOSEST(parent_rate, gck->gckdiv + 1);
+}
+
+static void clk_generated_best_diff(struct clk_rate_request *req,
+ struct clk_hw *parent,
+ unsigned long parent_rate, u32 div,
+ int *best_diff, long *best_rate)
+{
+ unsigned long tmp_rate;
+ int tmp_diff;
+
+ if (!div)
+ tmp_rate = parent_rate;
+ else
+ tmp_rate = parent_rate / div;
+
+ if (tmp_rate < req->min_rate || tmp_rate > req->max_rate)
+ return;
+
+ tmp_diff = abs(req->rate - tmp_rate);
+
+ if (*best_diff < 0 || *best_diff >= tmp_diff) {
+ *best_rate = tmp_rate;
+ *best_diff = tmp_diff;
+ req->best_parent_rate = parent_rate;
+ req->best_parent_hw = parent;
+ }
+}
+
+static int clk_generated_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ struct clk_generated *gck = to_clk_generated(hw);
+ struct clk_hw *parent = NULL;
+ long best_rate = -EINVAL;
+ unsigned long min_rate, parent_rate;
+ int best_diff = -1;
+ int i;
+ u32 div;
+
+ /* do not look for a rate that is outside of our range */
+ if (gck->range.max && req->rate > gck->range.max)
+ req->rate = gck->range.max;
+ if (gck->range.min && req->rate < gck->range.min)
+ req->rate = gck->range.min;
+
+ for (i = 0; i < clk_hw_get_num_parents(hw); i++) {
+ if (gck->chg_pid == i)
+ continue;
+
+ parent = clk_hw_get_parent_by_index(hw, i);
+ if (!parent)
+ continue;
+
+ parent_rate = clk_hw_get_rate(parent);
+ min_rate = DIV_ROUND_CLOSEST(parent_rate, GENERATED_MAX_DIV + 1);
+ if (!parent_rate ||
+ (gck->range.max && min_rate > gck->range.max))
+ continue;
+
+ div = DIV_ROUND_CLOSEST(parent_rate, req->rate);
+ if (div > GENERATED_MAX_DIV + 1)
+ div = GENERATED_MAX_DIV + 1;
+
+ clk_generated_best_diff(req, parent, parent_rate, div,
+ &best_diff, &best_rate);
+
+ if (!best_diff)
+ break;
+ }
+
+ /*
+ * The audio_pll rate can be modified, unlike the five others clocks
+ * that should never be altered.
+ * The audio_pll can technically be used by multiple consumers. However,
+ * with the rate locking, the first consumer to enable to clock will be
+ * the one definitely setting the rate of the clock.
+ * Since audio IPs are most likely to request the same rate, we enforce
+ * that the only clks able to modify gck rate are those of audio IPs.
+ */
+
+ if (gck->chg_pid < 0)
+ goto end;
+
+ parent = clk_hw_get_parent_by_index(hw, gck->chg_pid);
+ if (!parent)
+ goto end;
+
+ for (div = 1; div < GENERATED_MAX_DIV + 2; div++) {
+ struct clk_rate_request req_parent;
+
+ clk_hw_forward_rate_request(hw, req, parent, &req_parent, req->rate * div);
+ if (__clk_determine_rate(parent, &req_parent))
+ continue;
+ clk_generated_best_diff(req, parent, req_parent.rate, div,
+ &best_diff, &best_rate);
+
+ if (!best_diff)
+ break;
+ }
+
+end:
+ pr_debug("GCLK: %s, best_rate = %ld, parent clk: %s @ %ld\n",
+ __func__, best_rate,
+ __clk_get_name((req->best_parent_hw)->clk),
+ req->best_parent_rate);
+
+ if (best_rate < 0 || (gck->range.max && best_rate > gck->range.max))
+ return -EINVAL;
+
+ req->rate = best_rate;
+ return 0;
+}
+
+/* No modification of hardware as we have the flag CLK_SET_PARENT_GATE set */
+static int clk_generated_set_parent(struct clk_hw *hw, u8 index)
+{
+ struct clk_generated *gck = to_clk_generated(hw);
+
+ if (index >= clk_hw_get_num_parents(hw))
+ return -EINVAL;
+
+ if (gck->mux_table)
+ gck->parent_id = clk_mux_index_to_val(gck->mux_table, 0, index);
+ else
+ gck->parent_id = index;
+
+ return 0;
+}
+
+static u8 clk_generated_get_parent(struct clk_hw *hw)
+{
+ struct clk_generated *gck = to_clk_generated(hw);
+
+ return gck->parent_id;
+}
+
+/* No modification of hardware as we have the flag CLK_SET_RATE_GATE set */
+static int clk_generated_set_rate(struct clk_hw *hw,
+ unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct clk_generated *gck = to_clk_generated(hw);
+ u32 div;
+
+ if (!rate)
+ return -EINVAL;
+
+ if (gck->range.max && rate > gck->range.max)
+ return -EINVAL;
+
+ div = DIV_ROUND_CLOSEST(parent_rate, rate);
+ if (div > GENERATED_MAX_DIV + 1 || !div)
+ return -EINVAL;
+
+ gck->gckdiv = div - 1;
+ return 0;
+}
+
+static int clk_generated_save_context(struct clk_hw *hw)
+{
+ struct clk_generated *gck = to_clk_generated(hw);
+
+ gck->pms.status = clk_generated_is_enabled(&gck->hw);
+
+ return 0;
+}
+
+static void clk_generated_restore_context(struct clk_hw *hw)
+{
+ struct clk_generated *gck = to_clk_generated(hw);
+
+ if (gck->pms.status)
+ clk_generated_set(gck, gck->pms.status);
+}
+
+static const struct clk_ops generated_ops = {
+ .enable = clk_generated_enable,
+ .disable = clk_generated_disable,
+ .is_enabled = clk_generated_is_enabled,
+ .recalc_rate = clk_generated_recalc_rate,
+ .determine_rate = clk_generated_determine_rate,
+ .get_parent = clk_generated_get_parent,
+ .set_parent = clk_generated_set_parent,
+ .set_rate = clk_generated_set_rate,
+ .save_context = clk_generated_save_context,
+ .restore_context = clk_generated_restore_context,
+};
+
+/**
+ * clk_generated_startup - Initialize a given clock to its default parent and
+ * divisor parameter.
+ *
+ * @gck: Generated clock to set the startup parameters for.
+ *
+ * Take parameters from the hardware and update local clock configuration
+ * accordingly.
+ */
+static void clk_generated_startup(struct clk_generated *gck)
+{
+ u32 tmp;
+ unsigned long flags;
+
+ spin_lock_irqsave(gck->lock, flags);
+ regmap_write(gck->regmap, gck->layout->offset,
+ (gck->id & gck->layout->pid_mask));
+ regmap_read(gck->regmap, gck->layout->offset, &tmp);
+ spin_unlock_irqrestore(gck->lock, flags);
+
+ gck->parent_id = field_get(gck->layout->gckcss_mask, tmp);
+ gck->gckdiv = FIELD_GET(AT91_PMC_PCR_GCKDIV_MASK, tmp);
+}
+
+struct clk_hw * __init
+at91_clk_register_generated(struct regmap *regmap, spinlock_t *lock,
+ const struct clk_pcr_layout *layout,
+ const char *name, const char **parent_names,
+ u32 *mux_table, u8 num_parents, u8 id,
+ const struct clk_range *range,
+ int chg_pid)
+{
+ struct clk_generated *gck;
+ struct clk_init_data init;
+ struct clk_hw *hw;
+ int ret;
+
+ gck = kzalloc(sizeof(*gck), GFP_KERNEL);
+ if (!gck)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &generated_ops;
+ init.parent_names = parent_names;
+ init.num_parents = num_parents;
+ init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE;
+ if (chg_pid >= 0)
+ init.flags |= CLK_SET_RATE_PARENT;
+
+ gck->id = id;
+ gck->hw.init = &init;
+ gck->regmap = regmap;
+ gck->lock = lock;
+ gck->range = *range;
+ gck->chg_pid = chg_pid;
+ gck->layout = layout;
+ gck->mux_table = mux_table;
+
+ clk_generated_startup(gck);
+ hw = &gck->hw;
+ ret = clk_hw_register(NULL, &gck->hw);
+ if (ret) {
+ kfree(gck);
+ hw = ERR_PTR(ret);
+ }
+
+ return hw;
+}
diff --git a/drivers/clk/at91/clk-h32mx.c b/drivers/clk/at91/clk-h32mx.c
new file mode 100644
index 000000000..1e6c12eed
--- /dev/null
+++ b/drivers/clk/at91/clk-h32mx.c
@@ -0,0 +1,112 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * clk-h32mx.c
+ *
+ * Copyright (C) 2014 Atmel
+ *
+ * Alexandre Belloni <alexandre.belloni@free-electrons.com>
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/clk/at91_pmc.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+#include <linux/mfd/syscon.h>
+
+#include "pmc.h"
+
+#define H32MX_MAX_FREQ 90000000
+
+struct clk_sama5d4_h32mx {
+ struct clk_hw hw;
+ struct regmap *regmap;
+};
+
+#define to_clk_sama5d4_h32mx(hw) container_of(hw, struct clk_sama5d4_h32mx, hw)
+
+static unsigned long clk_sama5d4_h32mx_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct clk_sama5d4_h32mx *h32mxclk = to_clk_sama5d4_h32mx(hw);
+ unsigned int mckr;
+
+ regmap_read(h32mxclk->regmap, AT91_PMC_MCKR, &mckr);
+ if (mckr & AT91_PMC_H32MXDIV)
+ return parent_rate / 2;
+
+ if (parent_rate > H32MX_MAX_FREQ)
+ pr_warn("H32MX clock is too fast\n");
+ return parent_rate;
+}
+
+static long clk_sama5d4_h32mx_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ unsigned long div;
+
+ if (rate > *parent_rate)
+ return *parent_rate;
+ div = *parent_rate / 2;
+ if (rate < div)
+ return div;
+
+ if (rate - div < *parent_rate - rate)
+ return div;
+
+ return *parent_rate;
+}
+
+static int clk_sama5d4_h32mx_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct clk_sama5d4_h32mx *h32mxclk = to_clk_sama5d4_h32mx(hw);
+ u32 mckr = 0;
+
+ if (parent_rate != rate && (parent_rate / 2) != rate)
+ return -EINVAL;
+
+ if ((parent_rate / 2) == rate)
+ mckr = AT91_PMC_H32MXDIV;
+
+ regmap_update_bits(h32mxclk->regmap, AT91_PMC_MCKR,
+ AT91_PMC_H32MXDIV, mckr);
+
+ return 0;
+}
+
+static const struct clk_ops h32mx_ops = {
+ .recalc_rate = clk_sama5d4_h32mx_recalc_rate,
+ .round_rate = clk_sama5d4_h32mx_round_rate,
+ .set_rate = clk_sama5d4_h32mx_set_rate,
+};
+
+struct clk_hw * __init
+at91_clk_register_h32mx(struct regmap *regmap, const char *name,
+ const char *parent_name)
+{
+ struct clk_sama5d4_h32mx *h32mxclk;
+ struct clk_init_data init;
+ int ret;
+
+ h32mxclk = kzalloc(sizeof(*h32mxclk), GFP_KERNEL);
+ if (!h32mxclk)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &h32mx_ops;
+ init.parent_names = parent_name ? &parent_name : NULL;
+ init.num_parents = parent_name ? 1 : 0;
+ init.flags = CLK_SET_RATE_GATE;
+
+ h32mxclk->hw.init = &init;
+ h32mxclk->regmap = regmap;
+
+ ret = clk_hw_register(NULL, &h32mxclk->hw);
+ if (ret) {
+ kfree(h32mxclk);
+ return ERR_PTR(ret);
+ }
+
+ return &h32mxclk->hw;
+}
diff --git a/drivers/clk/at91/clk-i2s-mux.c b/drivers/clk/at91/clk-i2s-mux.c
new file mode 100644
index 000000000..fe6ce172b
--- /dev/null
+++ b/drivers/clk/at91/clk-i2s-mux.c
@@ -0,0 +1,80 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Microchip Technology Inc,
+ * Codrin Ciubotariu <codrin.ciubotariu@microchip.com>
+ *
+ *
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/of.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#include <soc/at91/atmel-sfr.h>
+
+#include "pmc.h"
+
+struct clk_i2s_mux {
+ struct clk_hw hw;
+ struct regmap *regmap;
+ u8 bus_id;
+};
+
+#define to_clk_i2s_mux(hw) container_of(hw, struct clk_i2s_mux, hw)
+
+static u8 clk_i2s_mux_get_parent(struct clk_hw *hw)
+{
+ struct clk_i2s_mux *mux = to_clk_i2s_mux(hw);
+ u32 val;
+
+ regmap_read(mux->regmap, AT91_SFR_I2SCLKSEL, &val);
+
+ return (val & BIT(mux->bus_id)) >> mux->bus_id;
+}
+
+static int clk_i2s_mux_set_parent(struct clk_hw *hw, u8 index)
+{
+ struct clk_i2s_mux *mux = to_clk_i2s_mux(hw);
+
+ return regmap_update_bits(mux->regmap, AT91_SFR_I2SCLKSEL,
+ BIT(mux->bus_id), index << mux->bus_id);
+}
+
+static const struct clk_ops clk_i2s_mux_ops = {
+ .get_parent = clk_i2s_mux_get_parent,
+ .set_parent = clk_i2s_mux_set_parent,
+ .determine_rate = __clk_mux_determine_rate,
+};
+
+struct clk_hw * __init
+at91_clk_i2s_mux_register(struct regmap *regmap, const char *name,
+ const char * const *parent_names,
+ unsigned int num_parents, u8 bus_id)
+{
+ struct clk_init_data init = {};
+ struct clk_i2s_mux *i2s_ck;
+ int ret;
+
+ i2s_ck = kzalloc(sizeof(*i2s_ck), GFP_KERNEL);
+ if (!i2s_ck)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &clk_i2s_mux_ops;
+ init.parent_names = parent_names;
+ init.num_parents = num_parents;
+
+ i2s_ck->hw.init = &init;
+ i2s_ck->bus_id = bus_id;
+ i2s_ck->regmap = regmap;
+
+ ret = clk_hw_register(NULL, &i2s_ck->hw);
+ if (ret) {
+ kfree(i2s_ck);
+ return ERR_PTR(ret);
+ }
+
+ return &i2s_ck->hw;
+}
diff --git a/drivers/clk/at91/clk-main.c b/drivers/clk/at91/clk-main.c
new file mode 100644
index 000000000..8601b27c1
--- /dev/null
+++ b/drivers/clk/at91/clk-main.c
@@ -0,0 +1,583 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com>
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/clk/at91_pmc.h>
+#include <linux/delay.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+
+#include "pmc.h"
+
+#define SLOW_CLOCK_FREQ 32768
+#define MAINF_DIV 16
+#define MAINFRDY_TIMEOUT (((MAINF_DIV + 1) * USEC_PER_SEC) / \
+ SLOW_CLOCK_FREQ)
+#define MAINF_LOOP_MIN_WAIT (USEC_PER_SEC / SLOW_CLOCK_FREQ)
+#define MAINF_LOOP_MAX_WAIT MAINFRDY_TIMEOUT
+
+#define MOR_KEY_MASK (0xff << 16)
+
+#define clk_main_parent_select(s) (((s) & \
+ (AT91_PMC_MOSCEN | \
+ AT91_PMC_OSCBYPASS)) ? 1 : 0)
+
+struct clk_main_osc {
+ struct clk_hw hw;
+ struct regmap *regmap;
+ struct at91_clk_pms pms;
+};
+
+#define to_clk_main_osc(hw) container_of(hw, struct clk_main_osc, hw)
+
+struct clk_main_rc_osc {
+ struct clk_hw hw;
+ struct regmap *regmap;
+ unsigned long frequency;
+ unsigned long accuracy;
+ struct at91_clk_pms pms;
+};
+
+#define to_clk_main_rc_osc(hw) container_of(hw, struct clk_main_rc_osc, hw)
+
+struct clk_rm9200_main {
+ struct clk_hw hw;
+ struct regmap *regmap;
+};
+
+#define to_clk_rm9200_main(hw) container_of(hw, struct clk_rm9200_main, hw)
+
+struct clk_sam9x5_main {
+ struct clk_hw hw;
+ struct regmap *regmap;
+ struct at91_clk_pms pms;
+ u8 parent;
+};
+
+#define to_clk_sam9x5_main(hw) container_of(hw, struct clk_sam9x5_main, hw)
+
+static inline bool clk_main_osc_ready(struct regmap *regmap)
+{
+ unsigned int status;
+
+ regmap_read(regmap, AT91_PMC_SR, &status);
+
+ return status & AT91_PMC_MOSCS;
+}
+
+static int clk_main_osc_prepare(struct clk_hw *hw)
+{
+ struct clk_main_osc *osc = to_clk_main_osc(hw);
+ struct regmap *regmap = osc->regmap;
+ u32 tmp;
+
+ regmap_read(regmap, AT91_CKGR_MOR, &tmp);
+ tmp &= ~MOR_KEY_MASK;
+
+ if (tmp & AT91_PMC_OSCBYPASS)
+ return 0;
+
+ if (!(tmp & AT91_PMC_MOSCEN)) {
+ tmp |= AT91_PMC_MOSCEN | AT91_PMC_KEY;
+ regmap_write(regmap, AT91_CKGR_MOR, tmp);
+ }
+
+ while (!clk_main_osc_ready(regmap))
+ cpu_relax();
+
+ return 0;
+}
+
+static void clk_main_osc_unprepare(struct clk_hw *hw)
+{
+ struct clk_main_osc *osc = to_clk_main_osc(hw);
+ struct regmap *regmap = osc->regmap;
+ u32 tmp;
+
+ regmap_read(regmap, AT91_CKGR_MOR, &tmp);
+ if (tmp & AT91_PMC_OSCBYPASS)
+ return;
+
+ if (!(tmp & AT91_PMC_MOSCEN))
+ return;
+
+ tmp &= ~(AT91_PMC_KEY | AT91_PMC_MOSCEN);
+ regmap_write(regmap, AT91_CKGR_MOR, tmp | AT91_PMC_KEY);
+}
+
+static int clk_main_osc_is_prepared(struct clk_hw *hw)
+{
+ struct clk_main_osc *osc = to_clk_main_osc(hw);
+ struct regmap *regmap = osc->regmap;
+ u32 tmp, status;
+
+ regmap_read(regmap, AT91_CKGR_MOR, &tmp);
+ if (tmp & AT91_PMC_OSCBYPASS)
+ return 1;
+
+ regmap_read(regmap, AT91_PMC_SR, &status);
+
+ return (status & AT91_PMC_MOSCS) && clk_main_parent_select(tmp);
+}
+
+static int clk_main_osc_save_context(struct clk_hw *hw)
+{
+ struct clk_main_osc *osc = to_clk_main_osc(hw);
+
+ osc->pms.status = clk_main_osc_is_prepared(hw);
+
+ return 0;
+}
+
+static void clk_main_osc_restore_context(struct clk_hw *hw)
+{
+ struct clk_main_osc *osc = to_clk_main_osc(hw);
+
+ if (osc->pms.status)
+ clk_main_osc_prepare(hw);
+}
+
+static const struct clk_ops main_osc_ops = {
+ .prepare = clk_main_osc_prepare,
+ .unprepare = clk_main_osc_unprepare,
+ .is_prepared = clk_main_osc_is_prepared,
+ .save_context = clk_main_osc_save_context,
+ .restore_context = clk_main_osc_restore_context,
+};
+
+struct clk_hw * __init
+at91_clk_register_main_osc(struct regmap *regmap,
+ const char *name,
+ const char *parent_name,
+ bool bypass)
+{
+ struct clk_main_osc *osc;
+ struct clk_init_data init;
+ struct clk_hw *hw;
+ int ret;
+
+ if (!name || !parent_name)
+ return ERR_PTR(-EINVAL);
+
+ osc = kzalloc(sizeof(*osc), GFP_KERNEL);
+ if (!osc)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &main_osc_ops;
+ init.parent_names = &parent_name;
+ init.num_parents = 1;
+ init.flags = CLK_IGNORE_UNUSED;
+
+ osc->hw.init = &init;
+ osc->regmap = regmap;
+
+ if (bypass)
+ regmap_update_bits(regmap,
+ AT91_CKGR_MOR, MOR_KEY_MASK |
+ AT91_PMC_OSCBYPASS,
+ AT91_PMC_OSCBYPASS | AT91_PMC_KEY);
+
+ hw = &osc->hw;
+ ret = clk_hw_register(NULL, &osc->hw);
+ if (ret) {
+ kfree(osc);
+ hw = ERR_PTR(ret);
+ }
+
+ return hw;
+}
+
+static bool clk_main_rc_osc_ready(struct regmap *regmap)
+{
+ unsigned int status;
+
+ regmap_read(regmap, AT91_PMC_SR, &status);
+
+ return !!(status & AT91_PMC_MOSCRCS);
+}
+
+static int clk_main_rc_osc_prepare(struct clk_hw *hw)
+{
+ struct clk_main_rc_osc *osc = to_clk_main_rc_osc(hw);
+ struct regmap *regmap = osc->regmap;
+ unsigned int mor;
+
+ regmap_read(regmap, AT91_CKGR_MOR, &mor);
+
+ if (!(mor & AT91_PMC_MOSCRCEN))
+ regmap_update_bits(regmap, AT91_CKGR_MOR,
+ MOR_KEY_MASK | AT91_PMC_MOSCRCEN,
+ AT91_PMC_MOSCRCEN | AT91_PMC_KEY);
+
+ while (!clk_main_rc_osc_ready(regmap))
+ cpu_relax();
+
+ return 0;
+}
+
+static void clk_main_rc_osc_unprepare(struct clk_hw *hw)
+{
+ struct clk_main_rc_osc *osc = to_clk_main_rc_osc(hw);
+ struct regmap *regmap = osc->regmap;
+ unsigned int mor;
+
+ regmap_read(regmap, AT91_CKGR_MOR, &mor);
+
+ if (!(mor & AT91_PMC_MOSCRCEN))
+ return;
+
+ regmap_update_bits(regmap, AT91_CKGR_MOR,
+ MOR_KEY_MASK | AT91_PMC_MOSCRCEN, AT91_PMC_KEY);
+}
+
+static int clk_main_rc_osc_is_prepared(struct clk_hw *hw)
+{
+ struct clk_main_rc_osc *osc = to_clk_main_rc_osc(hw);
+ struct regmap *regmap = osc->regmap;
+ unsigned int mor, status;
+
+ regmap_read(regmap, AT91_CKGR_MOR, &mor);
+ regmap_read(regmap, AT91_PMC_SR, &status);
+
+ return (mor & AT91_PMC_MOSCRCEN) && (status & AT91_PMC_MOSCRCS);
+}
+
+static unsigned long clk_main_rc_osc_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct clk_main_rc_osc *osc = to_clk_main_rc_osc(hw);
+
+ return osc->frequency;
+}
+
+static unsigned long clk_main_rc_osc_recalc_accuracy(struct clk_hw *hw,
+ unsigned long parent_acc)
+{
+ struct clk_main_rc_osc *osc = to_clk_main_rc_osc(hw);
+
+ return osc->accuracy;
+}
+
+static int clk_main_rc_osc_save_context(struct clk_hw *hw)
+{
+ struct clk_main_rc_osc *osc = to_clk_main_rc_osc(hw);
+
+ osc->pms.status = clk_main_rc_osc_is_prepared(hw);
+
+ return 0;
+}
+
+static void clk_main_rc_osc_restore_context(struct clk_hw *hw)
+{
+ struct clk_main_rc_osc *osc = to_clk_main_rc_osc(hw);
+
+ if (osc->pms.status)
+ clk_main_rc_osc_prepare(hw);
+}
+
+static const struct clk_ops main_rc_osc_ops = {
+ .prepare = clk_main_rc_osc_prepare,
+ .unprepare = clk_main_rc_osc_unprepare,
+ .is_prepared = clk_main_rc_osc_is_prepared,
+ .recalc_rate = clk_main_rc_osc_recalc_rate,
+ .recalc_accuracy = clk_main_rc_osc_recalc_accuracy,
+ .save_context = clk_main_rc_osc_save_context,
+ .restore_context = clk_main_rc_osc_restore_context,
+};
+
+struct clk_hw * __init
+at91_clk_register_main_rc_osc(struct regmap *regmap,
+ const char *name,
+ u32 frequency, u32 accuracy)
+{
+ struct clk_main_rc_osc *osc;
+ struct clk_init_data init;
+ struct clk_hw *hw;
+ int ret;
+
+ if (!name || !frequency)
+ return ERR_PTR(-EINVAL);
+
+ osc = kzalloc(sizeof(*osc), GFP_KERNEL);
+ if (!osc)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &main_rc_osc_ops;
+ init.parent_names = NULL;
+ init.num_parents = 0;
+ init.flags = CLK_IGNORE_UNUSED;
+
+ osc->hw.init = &init;
+ osc->regmap = regmap;
+ osc->frequency = frequency;
+ osc->accuracy = accuracy;
+
+ hw = &osc->hw;
+ ret = clk_hw_register(NULL, hw);
+ if (ret) {
+ kfree(osc);
+ hw = ERR_PTR(ret);
+ }
+
+ return hw;
+}
+
+static int clk_main_probe_frequency(struct regmap *regmap)
+{
+ unsigned long prep_time, timeout;
+ unsigned int mcfr;
+
+ timeout = jiffies + usecs_to_jiffies(MAINFRDY_TIMEOUT);
+ do {
+ prep_time = jiffies;
+ regmap_read(regmap, AT91_CKGR_MCFR, &mcfr);
+ if (mcfr & AT91_PMC_MAINRDY)
+ return 0;
+ if (system_state < SYSTEM_RUNNING)
+ udelay(MAINF_LOOP_MIN_WAIT);
+ else
+ usleep_range(MAINF_LOOP_MIN_WAIT, MAINF_LOOP_MAX_WAIT);
+ } while (time_before(prep_time, timeout));
+
+ return -ETIMEDOUT;
+}
+
+static unsigned long clk_main_recalc_rate(struct regmap *regmap,
+ unsigned long parent_rate)
+{
+ unsigned int mcfr;
+
+ if (parent_rate)
+ return parent_rate;
+
+ pr_warn("Main crystal frequency not set, using approximate value\n");
+ regmap_read(regmap, AT91_CKGR_MCFR, &mcfr);
+ if (!(mcfr & AT91_PMC_MAINRDY))
+ return 0;
+
+ return ((mcfr & AT91_PMC_MAINF) * SLOW_CLOCK_FREQ) / MAINF_DIV;
+}
+
+static int clk_rm9200_main_prepare(struct clk_hw *hw)
+{
+ struct clk_rm9200_main *clkmain = to_clk_rm9200_main(hw);
+
+ return clk_main_probe_frequency(clkmain->regmap);
+}
+
+static int clk_rm9200_main_is_prepared(struct clk_hw *hw)
+{
+ struct clk_rm9200_main *clkmain = to_clk_rm9200_main(hw);
+ unsigned int status;
+
+ regmap_read(clkmain->regmap, AT91_CKGR_MCFR, &status);
+
+ return !!(status & AT91_PMC_MAINRDY);
+}
+
+static unsigned long clk_rm9200_main_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct clk_rm9200_main *clkmain = to_clk_rm9200_main(hw);
+
+ return clk_main_recalc_rate(clkmain->regmap, parent_rate);
+}
+
+static const struct clk_ops rm9200_main_ops = {
+ .prepare = clk_rm9200_main_prepare,
+ .is_prepared = clk_rm9200_main_is_prepared,
+ .recalc_rate = clk_rm9200_main_recalc_rate,
+};
+
+struct clk_hw * __init
+at91_clk_register_rm9200_main(struct regmap *regmap,
+ const char *name,
+ const char *parent_name)
+{
+ struct clk_rm9200_main *clkmain;
+ struct clk_init_data init;
+ struct clk_hw *hw;
+ int ret;
+
+ if (!name)
+ return ERR_PTR(-EINVAL);
+
+ if (!parent_name)
+ return ERR_PTR(-EINVAL);
+
+ clkmain = kzalloc(sizeof(*clkmain), GFP_KERNEL);
+ if (!clkmain)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &rm9200_main_ops;
+ init.parent_names = &parent_name;
+ init.num_parents = 1;
+ init.flags = 0;
+
+ clkmain->hw.init = &init;
+ clkmain->regmap = regmap;
+
+ hw = &clkmain->hw;
+ ret = clk_hw_register(NULL, &clkmain->hw);
+ if (ret) {
+ kfree(clkmain);
+ hw = ERR_PTR(ret);
+ }
+
+ return hw;
+}
+
+static inline bool clk_sam9x5_main_ready(struct regmap *regmap)
+{
+ unsigned int status;
+
+ regmap_read(regmap, AT91_PMC_SR, &status);
+
+ return !!(status & AT91_PMC_MOSCSELS);
+}
+
+static int clk_sam9x5_main_prepare(struct clk_hw *hw)
+{
+ struct clk_sam9x5_main *clkmain = to_clk_sam9x5_main(hw);
+ struct regmap *regmap = clkmain->regmap;
+
+ while (!clk_sam9x5_main_ready(regmap))
+ cpu_relax();
+
+ return clk_main_probe_frequency(regmap);
+}
+
+static int clk_sam9x5_main_is_prepared(struct clk_hw *hw)
+{
+ struct clk_sam9x5_main *clkmain = to_clk_sam9x5_main(hw);
+
+ return clk_sam9x5_main_ready(clkmain->regmap);
+}
+
+static unsigned long clk_sam9x5_main_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct clk_sam9x5_main *clkmain = to_clk_sam9x5_main(hw);
+
+ return clk_main_recalc_rate(clkmain->regmap, parent_rate);
+}
+
+static int clk_sam9x5_main_set_parent(struct clk_hw *hw, u8 index)
+{
+ struct clk_sam9x5_main *clkmain = to_clk_sam9x5_main(hw);
+ struct regmap *regmap = clkmain->regmap;
+ unsigned int tmp;
+
+ if (index > 1)
+ return -EINVAL;
+
+ regmap_read(regmap, AT91_CKGR_MOR, &tmp);
+
+ if (index && !(tmp & AT91_PMC_MOSCSEL))
+ tmp = AT91_PMC_MOSCSEL;
+ else if (!index && (tmp & AT91_PMC_MOSCSEL))
+ tmp = 0;
+ else
+ return 0;
+
+ regmap_update_bits(regmap, AT91_CKGR_MOR,
+ AT91_PMC_MOSCSEL | MOR_KEY_MASK,
+ tmp | AT91_PMC_KEY);
+
+ while (!clk_sam9x5_main_ready(regmap))
+ cpu_relax();
+
+ return 0;
+}
+
+static u8 clk_sam9x5_main_get_parent(struct clk_hw *hw)
+{
+ struct clk_sam9x5_main *clkmain = to_clk_sam9x5_main(hw);
+ unsigned int status;
+
+ regmap_read(clkmain->regmap, AT91_CKGR_MOR, &status);
+
+ return clk_main_parent_select(status);
+}
+
+static int clk_sam9x5_main_save_context(struct clk_hw *hw)
+{
+ struct clk_sam9x5_main *clkmain = to_clk_sam9x5_main(hw);
+
+ clkmain->pms.status = clk_main_rc_osc_is_prepared(&clkmain->hw);
+ clkmain->pms.parent = clk_sam9x5_main_get_parent(&clkmain->hw);
+
+ return 0;
+}
+
+static void clk_sam9x5_main_restore_context(struct clk_hw *hw)
+{
+ struct clk_sam9x5_main *clkmain = to_clk_sam9x5_main(hw);
+ int ret;
+
+ ret = clk_sam9x5_main_set_parent(hw, clkmain->pms.parent);
+ if (ret)
+ return;
+
+ if (clkmain->pms.status)
+ clk_sam9x5_main_prepare(hw);
+}
+
+static const struct clk_ops sam9x5_main_ops = {
+ .prepare = clk_sam9x5_main_prepare,
+ .is_prepared = clk_sam9x5_main_is_prepared,
+ .recalc_rate = clk_sam9x5_main_recalc_rate,
+ .set_parent = clk_sam9x5_main_set_parent,
+ .get_parent = clk_sam9x5_main_get_parent,
+ .save_context = clk_sam9x5_main_save_context,
+ .restore_context = clk_sam9x5_main_restore_context,
+};
+
+struct clk_hw * __init
+at91_clk_register_sam9x5_main(struct regmap *regmap,
+ const char *name,
+ const char **parent_names,
+ int num_parents)
+{
+ struct clk_sam9x5_main *clkmain;
+ struct clk_init_data init;
+ unsigned int status;
+ struct clk_hw *hw;
+ int ret;
+
+ if (!name)
+ return ERR_PTR(-EINVAL);
+
+ if (!parent_names || !num_parents)
+ return ERR_PTR(-EINVAL);
+
+ clkmain = kzalloc(sizeof(*clkmain), GFP_KERNEL);
+ if (!clkmain)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &sam9x5_main_ops;
+ init.parent_names = parent_names;
+ init.num_parents = num_parents;
+ init.flags = CLK_SET_PARENT_GATE;
+
+ clkmain->hw.init = &init;
+ clkmain->regmap = regmap;
+ regmap_read(clkmain->regmap, AT91_CKGR_MOR, &status);
+ clkmain->parent = clk_main_parent_select(status);
+
+ hw = &clkmain->hw;
+ ret = clk_hw_register(NULL, &clkmain->hw);
+ if (ret) {
+ kfree(clkmain);
+ hw = ERR_PTR(ret);
+ }
+
+ return hw;
+}
diff --git a/drivers/clk/at91/clk-master.c b/drivers/clk/at91/clk-master.c
new file mode 100644
index 000000000..b7cd1924d
--- /dev/null
+++ b/drivers/clk/at91/clk-master.c
@@ -0,0 +1,872 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com>
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/clk.h>
+#include <linux/clk/at91_pmc.h>
+#include <linux/of.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+
+#include "pmc.h"
+
+#define MASTER_PRES_MASK 0x7
+#define MASTER_PRES_MAX MASTER_PRES_MASK
+#define MASTER_DIV_SHIFT 8
+#define MASTER_DIV_MASK 0x7
+
+#define PMC_MCR_CSS_SHIFT (16)
+
+#define MASTER_MAX_ID 4
+
+#define to_clk_master(hw) container_of(hw, struct clk_master, hw)
+
+struct clk_master {
+ struct clk_hw hw;
+ struct regmap *regmap;
+ spinlock_t *lock;
+ const struct clk_master_layout *layout;
+ const struct clk_master_characteristics *characteristics;
+ struct at91_clk_pms pms;
+ u32 *mux_table;
+ u32 mckr;
+ int chg_pid;
+ u8 id;
+ u8 parent;
+ u8 div;
+ u32 safe_div;
+};
+
+/* MCK div reference to be used by notifier. */
+static struct clk_master *master_div;
+
+static inline bool clk_master_ready(struct clk_master *master)
+{
+ unsigned int bit = master->id ? AT91_PMC_MCKXRDY : AT91_PMC_MCKRDY;
+ unsigned int status;
+
+ regmap_read(master->regmap, AT91_PMC_SR, &status);
+
+ return !!(status & bit);
+}
+
+static int clk_master_prepare(struct clk_hw *hw)
+{
+ struct clk_master *master = to_clk_master(hw);
+ unsigned long flags;
+
+ spin_lock_irqsave(master->lock, flags);
+
+ while (!clk_master_ready(master))
+ cpu_relax();
+
+ spin_unlock_irqrestore(master->lock, flags);
+
+ return 0;
+}
+
+static int clk_master_is_prepared(struct clk_hw *hw)
+{
+ struct clk_master *master = to_clk_master(hw);
+ unsigned long flags;
+ bool status;
+
+ spin_lock_irqsave(master->lock, flags);
+ status = clk_master_ready(master);
+ spin_unlock_irqrestore(master->lock, flags);
+
+ return status;
+}
+
+static unsigned long clk_master_div_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ u8 div;
+ unsigned long flags, rate = parent_rate;
+ struct clk_master *master = to_clk_master(hw);
+ const struct clk_master_layout *layout = master->layout;
+ const struct clk_master_characteristics *characteristics =
+ master->characteristics;
+ unsigned int mckr;
+
+ spin_lock_irqsave(master->lock, flags);
+ regmap_read(master->regmap, master->layout->offset, &mckr);
+ spin_unlock_irqrestore(master->lock, flags);
+
+ mckr &= layout->mask;
+
+ div = (mckr >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK;
+
+ rate /= characteristics->divisors[div];
+
+ if (rate < characteristics->output.min)
+ pr_warn("master clk div is underclocked");
+ else if (rate > characteristics->output.max)
+ pr_warn("master clk div is overclocked");
+
+ return rate;
+}
+
+static int clk_master_div_save_context(struct clk_hw *hw)
+{
+ struct clk_master *master = to_clk_master(hw);
+ struct clk_hw *parent_hw = clk_hw_get_parent(hw);
+ unsigned long flags;
+ unsigned int mckr, div;
+
+ spin_lock_irqsave(master->lock, flags);
+ regmap_read(master->regmap, master->layout->offset, &mckr);
+ spin_unlock_irqrestore(master->lock, flags);
+
+ mckr &= master->layout->mask;
+ div = (mckr >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK;
+ div = master->characteristics->divisors[div];
+
+ master->pms.parent_rate = clk_hw_get_rate(parent_hw);
+ master->pms.rate = DIV_ROUND_CLOSEST(master->pms.parent_rate, div);
+
+ return 0;
+}
+
+static void clk_master_div_restore_context(struct clk_hw *hw)
+{
+ struct clk_master *master = to_clk_master(hw);
+ unsigned long flags;
+ unsigned int mckr;
+ u8 div;
+
+ spin_lock_irqsave(master->lock, flags);
+ regmap_read(master->regmap, master->layout->offset, &mckr);
+ spin_unlock_irqrestore(master->lock, flags);
+
+ mckr &= master->layout->mask;
+ div = (mckr >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK;
+ div = master->characteristics->divisors[div];
+
+ if (div != DIV_ROUND_CLOSEST(master->pms.parent_rate, master->pms.rate))
+ pr_warn("MCKR DIV not configured properly by firmware!\n");
+}
+
+static const struct clk_ops master_div_ops = {
+ .prepare = clk_master_prepare,
+ .is_prepared = clk_master_is_prepared,
+ .recalc_rate = clk_master_div_recalc_rate,
+ .save_context = clk_master_div_save_context,
+ .restore_context = clk_master_div_restore_context,
+};
+
+/* This function must be called with lock acquired. */
+static int clk_master_div_set(struct clk_master *master,
+ unsigned long parent_rate, int div)
+{
+ const struct clk_master_characteristics *characteristics =
+ master->characteristics;
+ unsigned long rate = parent_rate;
+ unsigned int max_div = 0, div_index = 0, max_div_index = 0;
+ unsigned int i, mckr, tmp;
+ int ret;
+
+ for (i = 0; i < ARRAY_SIZE(characteristics->divisors); i++) {
+ if (!characteristics->divisors[i])
+ break;
+
+ if (div == characteristics->divisors[i])
+ div_index = i;
+
+ if (max_div < characteristics->divisors[i]) {
+ max_div = characteristics->divisors[i];
+ max_div_index = i;
+ }
+ }
+
+ if (div > max_div)
+ div_index = max_div_index;
+
+ ret = regmap_read(master->regmap, master->layout->offset, &mckr);
+ if (ret)
+ return ret;
+
+ mckr &= master->layout->mask;
+ tmp = (mckr >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK;
+ if (tmp == div_index)
+ return 0;
+
+ rate /= characteristics->divisors[div_index];
+ if (rate < characteristics->output.min)
+ pr_warn("master clk div is underclocked");
+ else if (rate > characteristics->output.max)
+ pr_warn("master clk div is overclocked");
+
+ mckr &= ~(MASTER_DIV_MASK << MASTER_DIV_SHIFT);
+ mckr |= (div_index << MASTER_DIV_SHIFT);
+ ret = regmap_write(master->regmap, master->layout->offset, mckr);
+ if (ret)
+ return ret;
+
+ while (!clk_master_ready(master))
+ cpu_relax();
+
+ master->div = characteristics->divisors[div_index];
+
+ return 0;
+}
+
+static unsigned long clk_master_div_recalc_rate_chg(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct clk_master *master = to_clk_master(hw);
+
+ return DIV_ROUND_CLOSEST_ULL(parent_rate, master->div);
+}
+
+static void clk_master_div_restore_context_chg(struct clk_hw *hw)
+{
+ struct clk_master *master = to_clk_master(hw);
+ unsigned long flags;
+ int ret;
+
+ spin_lock_irqsave(master->lock, flags);
+ ret = clk_master_div_set(master, master->pms.parent_rate,
+ DIV_ROUND_CLOSEST(master->pms.parent_rate,
+ master->pms.rate));
+ spin_unlock_irqrestore(master->lock, flags);
+ if (ret)
+ pr_warn("Failed to restore MCK DIV clock\n");
+}
+
+static const struct clk_ops master_div_ops_chg = {
+ .prepare = clk_master_prepare,
+ .is_prepared = clk_master_is_prepared,
+ .recalc_rate = clk_master_div_recalc_rate_chg,
+ .save_context = clk_master_div_save_context,
+ .restore_context = clk_master_div_restore_context_chg,
+};
+
+static int clk_master_div_notifier_fn(struct notifier_block *notifier,
+ unsigned long code, void *data)
+{
+ const struct clk_master_characteristics *characteristics =
+ master_div->characteristics;
+ struct clk_notifier_data *cnd = data;
+ unsigned long flags, new_parent_rate, new_rate;
+ unsigned int mckr, div, new_div = 0;
+ int ret, i;
+ long tmp_diff;
+ long best_diff = -1;
+
+ spin_lock_irqsave(master_div->lock, flags);
+ switch (code) {
+ case PRE_RATE_CHANGE:
+ /*
+ * We want to avoid any overclocking of MCK DIV domain. To do
+ * this we set a safe divider (the underclocking is not of
+ * interest as we can go as low as 32KHz). The relation
+ * b/w this clock and its parents are as follows:
+ *
+ * FRAC PLL -> DIV PLL -> MCK DIV
+ *
+ * With the proper safe divider we should be good even with FRAC
+ * PLL at its maximum value.
+ */
+ ret = regmap_read(master_div->regmap, master_div->layout->offset,
+ &mckr);
+ if (ret) {
+ ret = NOTIFY_STOP_MASK;
+ goto unlock;
+ }
+
+ mckr &= master_div->layout->mask;
+ div = (mckr >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK;
+
+ /* Switch to safe divider. */
+ clk_master_div_set(master_div,
+ cnd->old_rate * characteristics->divisors[div],
+ master_div->safe_div);
+ break;
+
+ case POST_RATE_CHANGE:
+ /*
+ * At this point we want to restore MCK DIV domain to its maximum
+ * allowed rate.
+ */
+ ret = regmap_read(master_div->regmap, master_div->layout->offset,
+ &mckr);
+ if (ret) {
+ ret = NOTIFY_STOP_MASK;
+ goto unlock;
+ }
+
+ mckr &= master_div->layout->mask;
+ div = (mckr >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK;
+ new_parent_rate = cnd->new_rate * characteristics->divisors[div];
+
+ for (i = 0; i < ARRAY_SIZE(characteristics->divisors); i++) {
+ if (!characteristics->divisors[i])
+ break;
+
+ new_rate = DIV_ROUND_CLOSEST_ULL(new_parent_rate,
+ characteristics->divisors[i]);
+
+ tmp_diff = characteristics->output.max - new_rate;
+ if (tmp_diff < 0)
+ continue;
+
+ if (best_diff < 0 || best_diff > tmp_diff) {
+ new_div = characteristics->divisors[i];
+ best_diff = tmp_diff;
+ }
+
+ if (!tmp_diff)
+ break;
+ }
+
+ if (!new_div) {
+ ret = NOTIFY_STOP_MASK;
+ goto unlock;
+ }
+
+ /* Update the div to preserve MCK DIV clock rate. */
+ clk_master_div_set(master_div, new_parent_rate,
+ new_div);
+
+ ret = NOTIFY_OK;
+ break;
+
+ default:
+ ret = NOTIFY_DONE;
+ break;
+ }
+
+unlock:
+ spin_unlock_irqrestore(master_div->lock, flags);
+
+ return ret;
+}
+
+static struct notifier_block clk_master_div_notifier = {
+ .notifier_call = clk_master_div_notifier_fn,
+};
+
+static void clk_sama7g5_master_best_diff(struct clk_rate_request *req,
+ struct clk_hw *parent,
+ unsigned long parent_rate,
+ long *best_rate,
+ long *best_diff,
+ u32 div)
+{
+ unsigned long tmp_rate, tmp_diff;
+
+ if (div == MASTER_PRES_MAX)
+ tmp_rate = parent_rate / 3;
+ else
+ tmp_rate = parent_rate >> div;
+
+ tmp_diff = abs(req->rate - tmp_rate);
+
+ if (*best_diff < 0 || *best_diff >= tmp_diff) {
+ *best_rate = tmp_rate;
+ *best_diff = tmp_diff;
+ req->best_parent_rate = parent_rate;
+ req->best_parent_hw = parent;
+ }
+}
+
+static unsigned long clk_master_pres_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct clk_master *master = to_clk_master(hw);
+ const struct clk_master_characteristics *characteristics =
+ master->characteristics;
+ unsigned long flags;
+ unsigned int val, pres;
+
+ spin_lock_irqsave(master->lock, flags);
+ regmap_read(master->regmap, master->layout->offset, &val);
+ spin_unlock_irqrestore(master->lock, flags);
+
+ val &= master->layout->mask;
+ pres = (val >> master->layout->pres_shift) & MASTER_PRES_MASK;
+ if (pres == MASTER_PRES_MAX && characteristics->have_div3_pres)
+ pres = 3;
+ else
+ pres = (1 << pres);
+
+ return DIV_ROUND_CLOSEST_ULL(parent_rate, pres);
+}
+
+static u8 clk_master_pres_get_parent(struct clk_hw *hw)
+{
+ struct clk_master *master = to_clk_master(hw);
+ unsigned long flags;
+ unsigned int mckr;
+
+ spin_lock_irqsave(master->lock, flags);
+ regmap_read(master->regmap, master->layout->offset, &mckr);
+ spin_unlock_irqrestore(master->lock, flags);
+
+ mckr &= master->layout->mask;
+
+ return mckr & AT91_PMC_CSS;
+}
+
+static int clk_master_pres_save_context(struct clk_hw *hw)
+{
+ struct clk_master *master = to_clk_master(hw);
+ struct clk_hw *parent_hw = clk_hw_get_parent(hw);
+ unsigned long flags;
+ unsigned int val, pres;
+
+ spin_lock_irqsave(master->lock, flags);
+ regmap_read(master->regmap, master->layout->offset, &val);
+ spin_unlock_irqrestore(master->lock, flags);
+
+ val &= master->layout->mask;
+ pres = (val >> master->layout->pres_shift) & MASTER_PRES_MASK;
+ if (pres == MASTER_PRES_MAX && master->characteristics->have_div3_pres)
+ pres = 3;
+ else
+ pres = (1 << pres);
+
+ master->pms.parent = val & AT91_PMC_CSS;
+ master->pms.parent_rate = clk_hw_get_rate(parent_hw);
+ master->pms.rate = DIV_ROUND_CLOSEST_ULL(master->pms.parent_rate, pres);
+
+ return 0;
+}
+
+static void clk_master_pres_restore_context(struct clk_hw *hw)
+{
+ struct clk_master *master = to_clk_master(hw);
+ unsigned long flags;
+ unsigned int val, pres;
+
+ spin_lock_irqsave(master->lock, flags);
+ regmap_read(master->regmap, master->layout->offset, &val);
+ spin_unlock_irqrestore(master->lock, flags);
+
+ val &= master->layout->mask;
+ pres = (val >> master->layout->pres_shift) & MASTER_PRES_MASK;
+ if (pres == MASTER_PRES_MAX && master->characteristics->have_div3_pres)
+ pres = 3;
+ else
+ pres = (1 << pres);
+
+ if (master->pms.rate !=
+ DIV_ROUND_CLOSEST_ULL(master->pms.parent_rate, pres) ||
+ (master->pms.parent != (val & AT91_PMC_CSS)))
+ pr_warn("MCKR PRES was not configured properly by firmware!\n");
+}
+
+static const struct clk_ops master_pres_ops = {
+ .prepare = clk_master_prepare,
+ .is_prepared = clk_master_is_prepared,
+ .recalc_rate = clk_master_pres_recalc_rate,
+ .get_parent = clk_master_pres_get_parent,
+ .save_context = clk_master_pres_save_context,
+ .restore_context = clk_master_pres_restore_context,
+};
+
+static struct clk_hw * __init
+at91_clk_register_master_internal(struct regmap *regmap,
+ const char *name, int num_parents,
+ const char **parent_names,
+ const struct clk_master_layout *layout,
+ const struct clk_master_characteristics *characteristics,
+ const struct clk_ops *ops, spinlock_t *lock, u32 flags)
+{
+ struct clk_master *master;
+ struct clk_init_data init;
+ struct clk_hw *hw;
+ unsigned int mckr;
+ unsigned long irqflags;
+ int ret;
+
+ if (!name || !num_parents || !parent_names || !lock)
+ return ERR_PTR(-EINVAL);
+
+ master = kzalloc(sizeof(*master), GFP_KERNEL);
+ if (!master)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = ops;
+ init.parent_names = parent_names;
+ init.num_parents = num_parents;
+ init.flags = flags;
+
+ master->hw.init = &init;
+ master->layout = layout;
+ master->characteristics = characteristics;
+ master->regmap = regmap;
+ master->lock = lock;
+
+ if (ops == &master_div_ops_chg) {
+ spin_lock_irqsave(master->lock, irqflags);
+ regmap_read(master->regmap, master->layout->offset, &mckr);
+ spin_unlock_irqrestore(master->lock, irqflags);
+
+ mckr &= layout->mask;
+ mckr = (mckr >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK;
+ master->div = characteristics->divisors[mckr];
+ }
+
+ hw = &master->hw;
+ ret = clk_hw_register(NULL, &master->hw);
+ if (ret) {
+ kfree(master);
+ hw = ERR_PTR(ret);
+ }
+
+ return hw;
+}
+
+struct clk_hw * __init
+at91_clk_register_master_pres(struct regmap *regmap,
+ const char *name, int num_parents,
+ const char **parent_names,
+ const struct clk_master_layout *layout,
+ const struct clk_master_characteristics *characteristics,
+ spinlock_t *lock)
+{
+ return at91_clk_register_master_internal(regmap, name, num_parents,
+ parent_names, layout,
+ characteristics,
+ &master_pres_ops,
+ lock, CLK_SET_RATE_GATE);
+}
+
+struct clk_hw * __init
+at91_clk_register_master_div(struct regmap *regmap,
+ const char *name, const char *parent_name,
+ const struct clk_master_layout *layout,
+ const struct clk_master_characteristics *characteristics,
+ spinlock_t *lock, u32 flags, u32 safe_div)
+{
+ const struct clk_ops *ops;
+ struct clk_hw *hw;
+
+ if (flags & CLK_SET_RATE_GATE)
+ ops = &master_div_ops;
+ else
+ ops = &master_div_ops_chg;
+
+ hw = at91_clk_register_master_internal(regmap, name, 1,
+ &parent_name, layout,
+ characteristics, ops,
+ lock, flags);
+
+ if (!IS_ERR(hw) && safe_div) {
+ master_div = to_clk_master(hw);
+ master_div->safe_div = safe_div;
+ clk_notifier_register(hw->clk,
+ &clk_master_div_notifier);
+ }
+
+ return hw;
+}
+
+static unsigned long
+clk_sama7g5_master_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct clk_master *master = to_clk_master(hw);
+
+ return DIV_ROUND_CLOSEST_ULL(parent_rate, (1 << master->div));
+}
+
+static int clk_sama7g5_master_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ struct clk_master *master = to_clk_master(hw);
+ struct clk_hw *parent;
+ long best_rate = LONG_MIN, best_diff = LONG_MIN;
+ unsigned long parent_rate;
+ unsigned int div, i;
+
+ /* First: check the dividers of MCR. */
+ for (i = 0; i < clk_hw_get_num_parents(hw); i++) {
+ parent = clk_hw_get_parent_by_index(hw, i);
+ if (!parent)
+ continue;
+
+ parent_rate = clk_hw_get_rate(parent);
+ if (!parent_rate)
+ continue;
+
+ for (div = 0; div < MASTER_PRES_MAX + 1; div++) {
+ clk_sama7g5_master_best_diff(req, parent, parent_rate,
+ &best_rate, &best_diff,
+ div);
+ if (!best_diff)
+ break;
+ }
+
+ if (!best_diff)
+ break;
+ }
+
+ /* Second: try to request rate form changeable parent. */
+ if (master->chg_pid < 0)
+ goto end;
+
+ parent = clk_hw_get_parent_by_index(hw, master->chg_pid);
+ if (!parent)
+ goto end;
+
+ for (div = 0; div < MASTER_PRES_MAX + 1; div++) {
+ struct clk_rate_request req_parent;
+ unsigned long req_rate;
+
+ if (div == MASTER_PRES_MAX)
+ req_rate = req->rate * 3;
+ else
+ req_rate = req->rate << div;
+
+ clk_hw_forward_rate_request(hw, req, parent, &req_parent, req_rate);
+ if (__clk_determine_rate(parent, &req_parent))
+ continue;
+
+ clk_sama7g5_master_best_diff(req, parent, req_parent.rate,
+ &best_rate, &best_diff, div);
+
+ if (!best_diff)
+ break;
+ }
+
+end:
+ pr_debug("MCK: %s, best_rate = %ld, parent clk: %s @ %ld\n",
+ __func__, best_rate,
+ __clk_get_name((req->best_parent_hw)->clk),
+ req->best_parent_rate);
+
+ if (best_rate < 0)
+ return -EINVAL;
+
+ req->rate = best_rate;
+
+ return 0;
+}
+
+static u8 clk_sama7g5_master_get_parent(struct clk_hw *hw)
+{
+ struct clk_master *master = to_clk_master(hw);
+ unsigned long flags;
+ u8 index;
+
+ spin_lock_irqsave(master->lock, flags);
+ index = clk_mux_val_to_index(&master->hw, master->mux_table, 0,
+ master->parent);
+ spin_unlock_irqrestore(master->lock, flags);
+
+ return index;
+}
+
+static int clk_sama7g5_master_set_parent(struct clk_hw *hw, u8 index)
+{
+ struct clk_master *master = to_clk_master(hw);
+ unsigned long flags;
+
+ if (index >= clk_hw_get_num_parents(hw))
+ return -EINVAL;
+
+ spin_lock_irqsave(master->lock, flags);
+ master->parent = clk_mux_index_to_val(master->mux_table, 0, index);
+ spin_unlock_irqrestore(master->lock, flags);
+
+ return 0;
+}
+
+static void clk_sama7g5_master_set(struct clk_master *master,
+ unsigned int status)
+{
+ unsigned long flags;
+ unsigned int val, cparent;
+ unsigned int enable = status ? AT91_PMC_MCR_V2_EN : 0;
+ unsigned int parent = master->parent << PMC_MCR_CSS_SHIFT;
+ unsigned int div = master->div << MASTER_DIV_SHIFT;
+
+ spin_lock_irqsave(master->lock, flags);
+
+ regmap_write(master->regmap, AT91_PMC_MCR_V2,
+ AT91_PMC_MCR_V2_ID(master->id));
+ regmap_read(master->regmap, AT91_PMC_MCR_V2, &val);
+ regmap_update_bits(master->regmap, AT91_PMC_MCR_V2,
+ enable | AT91_PMC_MCR_V2_CSS | AT91_PMC_MCR_V2_DIV |
+ AT91_PMC_MCR_V2_CMD | AT91_PMC_MCR_V2_ID_MSK,
+ enable | parent | div | AT91_PMC_MCR_V2_CMD |
+ AT91_PMC_MCR_V2_ID(master->id));
+
+ cparent = (val & AT91_PMC_MCR_V2_CSS) >> PMC_MCR_CSS_SHIFT;
+
+ /* Wait here only if parent is being changed. */
+ while ((cparent != master->parent) && !clk_master_ready(master))
+ cpu_relax();
+
+ spin_unlock_irqrestore(master->lock, flags);
+}
+
+static int clk_sama7g5_master_enable(struct clk_hw *hw)
+{
+ struct clk_master *master = to_clk_master(hw);
+
+ clk_sama7g5_master_set(master, 1);
+
+ return 0;
+}
+
+static void clk_sama7g5_master_disable(struct clk_hw *hw)
+{
+ struct clk_master *master = to_clk_master(hw);
+ unsigned long flags;
+
+ spin_lock_irqsave(master->lock, flags);
+
+ regmap_write(master->regmap, AT91_PMC_MCR_V2, master->id);
+ regmap_update_bits(master->regmap, AT91_PMC_MCR_V2,
+ AT91_PMC_MCR_V2_EN | AT91_PMC_MCR_V2_CMD |
+ AT91_PMC_MCR_V2_ID_MSK,
+ AT91_PMC_MCR_V2_CMD |
+ AT91_PMC_MCR_V2_ID(master->id));
+
+ spin_unlock_irqrestore(master->lock, flags);
+}
+
+static int clk_sama7g5_master_is_enabled(struct clk_hw *hw)
+{
+ struct clk_master *master = to_clk_master(hw);
+ unsigned long flags;
+ unsigned int val;
+
+ spin_lock_irqsave(master->lock, flags);
+
+ regmap_write(master->regmap, AT91_PMC_MCR_V2, master->id);
+ regmap_read(master->regmap, AT91_PMC_MCR_V2, &val);
+
+ spin_unlock_irqrestore(master->lock, flags);
+
+ return !!(val & AT91_PMC_MCR_V2_EN);
+}
+
+static int clk_sama7g5_master_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct clk_master *master = to_clk_master(hw);
+ unsigned long div, flags;
+
+ div = DIV_ROUND_CLOSEST(parent_rate, rate);
+ if ((div > (1 << (MASTER_PRES_MAX - 1))) || (div & (div - 1)))
+ return -EINVAL;
+
+ if (div == 3)
+ div = MASTER_PRES_MAX;
+ else if (div)
+ div = ffs(div) - 1;
+
+ spin_lock_irqsave(master->lock, flags);
+ master->div = div;
+ spin_unlock_irqrestore(master->lock, flags);
+
+ return 0;
+}
+
+static int clk_sama7g5_master_save_context(struct clk_hw *hw)
+{
+ struct clk_master *master = to_clk_master(hw);
+
+ master->pms.status = clk_sama7g5_master_is_enabled(hw);
+
+ return 0;
+}
+
+static void clk_sama7g5_master_restore_context(struct clk_hw *hw)
+{
+ struct clk_master *master = to_clk_master(hw);
+
+ if (master->pms.status)
+ clk_sama7g5_master_set(master, master->pms.status);
+}
+
+static const struct clk_ops sama7g5_master_ops = {
+ .enable = clk_sama7g5_master_enable,
+ .disable = clk_sama7g5_master_disable,
+ .is_enabled = clk_sama7g5_master_is_enabled,
+ .recalc_rate = clk_sama7g5_master_recalc_rate,
+ .determine_rate = clk_sama7g5_master_determine_rate,
+ .set_rate = clk_sama7g5_master_set_rate,
+ .get_parent = clk_sama7g5_master_get_parent,
+ .set_parent = clk_sama7g5_master_set_parent,
+ .save_context = clk_sama7g5_master_save_context,
+ .restore_context = clk_sama7g5_master_restore_context,
+};
+
+struct clk_hw * __init
+at91_clk_sama7g5_register_master(struct regmap *regmap,
+ const char *name, int num_parents,
+ const char **parent_names,
+ u32 *mux_table,
+ spinlock_t *lock, u8 id,
+ bool critical, int chg_pid)
+{
+ struct clk_master *master;
+ struct clk_hw *hw;
+ struct clk_init_data init;
+ unsigned long flags;
+ unsigned int val;
+ int ret;
+
+ if (!name || !num_parents || !parent_names || !mux_table ||
+ !lock || id > MASTER_MAX_ID)
+ return ERR_PTR(-EINVAL);
+
+ master = kzalloc(sizeof(*master), GFP_KERNEL);
+ if (!master)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &sama7g5_master_ops;
+ init.parent_names = parent_names;
+ init.num_parents = num_parents;
+ init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE;
+ if (chg_pid >= 0)
+ init.flags |= CLK_SET_RATE_PARENT;
+ if (critical)
+ init.flags |= CLK_IS_CRITICAL;
+
+ master->hw.init = &init;
+ master->regmap = regmap;
+ master->id = id;
+ master->chg_pid = chg_pid;
+ master->lock = lock;
+ master->mux_table = mux_table;
+
+ spin_lock_irqsave(master->lock, flags);
+ regmap_write(master->regmap, AT91_PMC_MCR_V2, master->id);
+ regmap_read(master->regmap, AT91_PMC_MCR_V2, &val);
+ master->parent = (val & AT91_PMC_MCR_V2_CSS) >> PMC_MCR_CSS_SHIFT;
+ master->div = (val & AT91_PMC_MCR_V2_DIV) >> MASTER_DIV_SHIFT;
+ spin_unlock_irqrestore(master->lock, flags);
+
+ hw = &master->hw;
+ ret = clk_hw_register(NULL, &master->hw);
+ if (ret) {
+ kfree(master);
+ hw = ERR_PTR(ret);
+ }
+
+ return hw;
+}
+
+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
new file mode 100644
index 000000000..5104d4025
--- /dev/null
+++ b/drivers/clk/at91/clk-peripheral.c
@@ -0,0 +1,495 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com>
+ */
+
+#include <linux/bitops.h>
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/clk/at91_pmc.h>
+#include <linux/of.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+
+#include "pmc.h"
+
+DEFINE_SPINLOCK(pmc_pcr_lock);
+
+#define PERIPHERAL_ID_MIN 2
+#define PERIPHERAL_ID_MAX 31
+#define PERIPHERAL_MASK(id) (1 << ((id) & PERIPHERAL_ID_MAX))
+
+#define PERIPHERAL_MAX_SHIFT 3
+
+struct clk_peripheral {
+ struct clk_hw hw;
+ struct regmap *regmap;
+ u32 id;
+};
+
+#define to_clk_peripheral(hw) container_of(hw, struct clk_peripheral, hw)
+
+struct clk_sam9x5_peripheral {
+ struct clk_hw hw;
+ struct regmap *regmap;
+ struct clk_range range;
+ spinlock_t *lock;
+ u32 id;
+ u32 div;
+ const struct clk_pcr_layout *layout;
+ struct at91_clk_pms pms;
+ bool auto_div;
+ int chg_pid;
+};
+
+#define to_clk_sam9x5_peripheral(hw) \
+ container_of(hw, struct clk_sam9x5_peripheral, hw)
+
+static int clk_peripheral_enable(struct clk_hw *hw)
+{
+ struct clk_peripheral *periph = to_clk_peripheral(hw);
+ 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;
+ regmap_write(periph->regmap, offset, PERIPHERAL_MASK(id));
+
+ return 0;
+}
+
+static void clk_peripheral_disable(struct clk_hw *hw)
+{
+ struct clk_peripheral *periph = to_clk_peripheral(hw);
+ int offset = AT91_PMC_PCDR;
+ u32 id = periph->id;
+
+ if (id < PERIPHERAL_ID_MIN)
+ return;
+ if (id > PERIPHERAL_ID_MAX)
+ offset = AT91_PMC_PCDR1;
+ regmap_write(periph->regmap, offset, PERIPHERAL_MASK(id));
+}
+
+static int clk_peripheral_is_enabled(struct clk_hw *hw)
+{
+ struct clk_peripheral *periph = to_clk_peripheral(hw);
+ int offset = AT91_PMC_PCSR;
+ unsigned int status;
+ u32 id = periph->id;
+
+ if (id < PERIPHERAL_ID_MIN)
+ return 1;
+ if (id > PERIPHERAL_ID_MAX)
+ offset = AT91_PMC_PCSR1;
+ regmap_read(periph->regmap, offset, &status);
+
+ return status & PERIPHERAL_MASK(id) ? 1 : 0;
+}
+
+static const struct clk_ops peripheral_ops = {
+ .enable = clk_peripheral_enable,
+ .disable = clk_peripheral_disable,
+ .is_enabled = clk_peripheral_is_enabled,
+};
+
+struct clk_hw * __init
+at91_clk_register_peripheral(struct regmap *regmap, const char *name,
+ const char *parent_name, u32 id)
+{
+ struct clk_peripheral *periph;
+ struct clk_init_data init;
+ struct clk_hw *hw;
+ int ret;
+
+ if (!name || !parent_name || id > PERIPHERAL_ID_MAX)
+ return ERR_PTR(-EINVAL);
+
+ periph = kzalloc(sizeof(*periph), GFP_KERNEL);
+ if (!periph)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &peripheral_ops;
+ init.parent_names = &parent_name;
+ init.num_parents = 1;
+ init.flags = 0;
+
+ periph->id = id;
+ periph->hw.init = &init;
+ periph->regmap = regmap;
+
+ hw = &periph->hw;
+ ret = clk_hw_register(NULL, &periph->hw);
+ if (ret) {
+ kfree(periph);
+ hw = ERR_PTR(ret);
+ }
+
+ return hw;
+}
+
+static void clk_sam9x5_peripheral_autodiv(struct clk_sam9x5_peripheral *periph)
+{
+ struct clk_hw *parent;
+ unsigned long parent_rate;
+ int shift = 0;
+
+ if (!periph->auto_div)
+ return;
+
+ if (periph->range.max) {
+ parent = clk_hw_get_parent_by_index(&periph->hw, 0);
+ parent_rate = clk_hw_get_rate(parent);
+ if (!parent_rate)
+ return;
+
+ for (; shift < PERIPHERAL_MAX_SHIFT; shift++) {
+ if (parent_rate >> shift <= periph->range.max)
+ break;
+ }
+ }
+
+ periph->auto_div = false;
+ periph->div = shift;
+}
+
+static int clk_sam9x5_peripheral_set(struct clk_sam9x5_peripheral *periph,
+ unsigned int status)
+{
+ unsigned long flags;
+ unsigned int enable = status ? AT91_PMC_PCR_EN : 0;
+
+ if (periph->id < PERIPHERAL_ID_MIN)
+ return 0;
+
+ spin_lock_irqsave(periph->lock, flags);
+ regmap_write(periph->regmap, periph->layout->offset,
+ (periph->id & periph->layout->pid_mask));
+ regmap_update_bits(periph->regmap, periph->layout->offset,
+ periph->layout->div_mask | periph->layout->cmd |
+ enable,
+ field_prep(periph->layout->div_mask, periph->div) |
+ periph->layout->cmd | enable);
+ spin_unlock_irqrestore(periph->lock, flags);
+
+ return 0;
+}
+
+static int clk_sam9x5_peripheral_enable(struct clk_hw *hw)
+{
+ struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
+
+ return clk_sam9x5_peripheral_set(periph, 1);
+}
+
+static void clk_sam9x5_peripheral_disable(struct clk_hw *hw)
+{
+ struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
+ unsigned long flags;
+
+ if (periph->id < PERIPHERAL_ID_MIN)
+ return;
+
+ spin_lock_irqsave(periph->lock, flags);
+ regmap_write(periph->regmap, periph->layout->offset,
+ (periph->id & periph->layout->pid_mask));
+ regmap_update_bits(periph->regmap, periph->layout->offset,
+ AT91_PMC_PCR_EN | periph->layout->cmd,
+ periph->layout->cmd);
+ spin_unlock_irqrestore(periph->lock, flags);
+}
+
+static int clk_sam9x5_peripheral_is_enabled(struct clk_hw *hw)
+{
+ struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
+ unsigned long flags;
+ unsigned int status;
+
+ if (periph->id < PERIPHERAL_ID_MIN)
+ return 1;
+
+ spin_lock_irqsave(periph->lock, flags);
+ regmap_write(periph->regmap, periph->layout->offset,
+ (periph->id & periph->layout->pid_mask));
+ regmap_read(periph->regmap, periph->layout->offset, &status);
+ spin_unlock_irqrestore(periph->lock, flags);
+
+ return !!(status & AT91_PMC_PCR_EN);
+}
+
+static unsigned long
+clk_sam9x5_peripheral_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
+ unsigned long flags;
+ unsigned int status;
+
+ if (periph->id < PERIPHERAL_ID_MIN)
+ return parent_rate;
+
+ spin_lock_irqsave(periph->lock, flags);
+ regmap_write(periph->regmap, periph->layout->offset,
+ (periph->id & periph->layout->pid_mask));
+ regmap_read(periph->regmap, periph->layout->offset, &status);
+ spin_unlock_irqrestore(periph->lock, flags);
+
+ if (status & AT91_PMC_PCR_EN) {
+ periph->div = field_get(periph->layout->div_mask, status);
+ periph->auto_div = false;
+ } else {
+ clk_sam9x5_peripheral_autodiv(periph);
+ }
+
+ return parent_rate >> periph->div;
+}
+
+static void clk_sam9x5_peripheral_best_diff(struct clk_rate_request *req,
+ struct clk_hw *parent,
+ unsigned long parent_rate,
+ u32 shift, long *best_diff,
+ long *best_rate)
+{
+ unsigned long tmp_rate = parent_rate >> shift;
+ unsigned long tmp_diff = abs(req->rate - tmp_rate);
+
+ if (*best_diff < 0 || *best_diff >= tmp_diff) {
+ *best_rate = tmp_rate;
+ *best_diff = tmp_diff;
+ req->best_parent_rate = parent_rate;
+ req->best_parent_hw = parent;
+ }
+}
+
+static int clk_sam9x5_peripheral_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
+ struct clk_hw *parent = clk_hw_get_parent(hw);
+ unsigned long parent_rate = clk_hw_get_rate(parent);
+ unsigned long tmp_rate;
+ long best_rate = LONG_MIN;
+ long best_diff = LONG_MIN;
+ u32 shift;
+
+ if (periph->id < PERIPHERAL_ID_MIN || !periph->range.max)
+ return parent_rate;
+
+ /* Fist step: check the available dividers. */
+ for (shift = 0; shift <= PERIPHERAL_MAX_SHIFT; shift++) {
+ tmp_rate = parent_rate >> shift;
+
+ if (periph->range.max && tmp_rate > periph->range.max)
+ continue;
+
+ clk_sam9x5_peripheral_best_diff(req, parent, parent_rate,
+ shift, &best_diff, &best_rate);
+
+ if (!best_diff || best_rate <= req->rate)
+ break;
+ }
+
+ if (periph->chg_pid < 0)
+ goto end;
+
+ /* Step two: try to request rate from parent. */
+ parent = clk_hw_get_parent_by_index(hw, periph->chg_pid);
+ if (!parent)
+ goto end;
+
+ for (shift = 0; shift <= PERIPHERAL_MAX_SHIFT; shift++) {
+ struct clk_rate_request req_parent;
+
+ clk_hw_forward_rate_request(hw, req, parent, &req_parent, req->rate << shift);
+ if (__clk_determine_rate(parent, &req_parent))
+ continue;
+
+ clk_sam9x5_peripheral_best_diff(req, parent, req_parent.rate,
+ shift, &best_diff, &best_rate);
+
+ if (!best_diff)
+ break;
+ }
+end:
+ if (best_rate < 0 ||
+ (periph->range.max && best_rate > periph->range.max))
+ return -EINVAL;
+
+ pr_debug("PCK: %s, best_rate = %ld, parent clk: %s @ %ld\n",
+ __func__, best_rate,
+ __clk_get_name((req->best_parent_hw)->clk),
+ req->best_parent_rate);
+
+ req->rate = best_rate;
+
+ return 0;
+}
+
+static long clk_sam9x5_peripheral_round_rate(struct clk_hw *hw,
+ unsigned long rate,
+ unsigned long *parent_rate)
+{
+ int shift = 0;
+ unsigned long best_rate;
+ unsigned long best_diff;
+ unsigned long cur_rate = *parent_rate;
+ unsigned long cur_diff;
+ struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
+
+ if (periph->id < PERIPHERAL_ID_MIN || !periph->range.max)
+ return *parent_rate;
+
+ if (periph->range.max) {
+ for (; shift <= PERIPHERAL_MAX_SHIFT; shift++) {
+ cur_rate = *parent_rate >> shift;
+ if (cur_rate <= periph->range.max)
+ break;
+ }
+ }
+
+ if (rate >= cur_rate)
+ return cur_rate;
+
+ best_diff = cur_rate - rate;
+ best_rate = cur_rate;
+ for (; shift <= PERIPHERAL_MAX_SHIFT; shift++) {
+ cur_rate = *parent_rate >> shift;
+ if (cur_rate < rate)
+ cur_diff = rate - cur_rate;
+ else
+ cur_diff = cur_rate - rate;
+
+ if (cur_diff < best_diff) {
+ best_diff = cur_diff;
+ best_rate = cur_rate;
+ }
+
+ if (!best_diff || cur_rate < rate)
+ break;
+ }
+
+ return best_rate;
+}
+
+static int clk_sam9x5_peripheral_set_rate(struct clk_hw *hw,
+ unsigned long rate,
+ unsigned long parent_rate)
+{
+ int shift;
+ struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
+ if (periph->id < PERIPHERAL_ID_MIN || !periph->range.max) {
+ if (parent_rate == rate)
+ return 0;
+ else
+ return -EINVAL;
+ }
+
+ if (periph->range.max && rate > periph->range.max)
+ return -EINVAL;
+
+ for (shift = 0; shift <= PERIPHERAL_MAX_SHIFT; shift++) {
+ if (parent_rate >> shift == rate) {
+ periph->auto_div = false;
+ periph->div = shift;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int clk_sam9x5_peripheral_save_context(struct clk_hw *hw)
+{
+ struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
+
+ periph->pms.status = clk_sam9x5_peripheral_is_enabled(hw);
+
+ return 0;
+}
+
+static void clk_sam9x5_peripheral_restore_context(struct clk_hw *hw)
+{
+ struct clk_sam9x5_peripheral *periph = to_clk_sam9x5_peripheral(hw);
+
+ if (periph->pms.status)
+ clk_sam9x5_peripheral_set(periph, periph->pms.status);
+}
+
+static const struct clk_ops sam9x5_peripheral_ops = {
+ .enable = clk_sam9x5_peripheral_enable,
+ .disable = clk_sam9x5_peripheral_disable,
+ .is_enabled = clk_sam9x5_peripheral_is_enabled,
+ .recalc_rate = clk_sam9x5_peripheral_recalc_rate,
+ .round_rate = clk_sam9x5_peripheral_round_rate,
+ .set_rate = clk_sam9x5_peripheral_set_rate,
+ .save_context = clk_sam9x5_peripheral_save_context,
+ .restore_context = clk_sam9x5_peripheral_restore_context,
+};
+
+static const struct clk_ops sam9x5_peripheral_chg_ops = {
+ .enable = clk_sam9x5_peripheral_enable,
+ .disable = clk_sam9x5_peripheral_disable,
+ .is_enabled = clk_sam9x5_peripheral_is_enabled,
+ .recalc_rate = clk_sam9x5_peripheral_recalc_rate,
+ .determine_rate = clk_sam9x5_peripheral_determine_rate,
+ .set_rate = clk_sam9x5_peripheral_set_rate,
+ .save_context = clk_sam9x5_peripheral_save_context,
+ .restore_context = clk_sam9x5_peripheral_restore_context,
+};
+
+struct clk_hw * __init
+at91_clk_register_sam9x5_peripheral(struct regmap *regmap, spinlock_t *lock,
+ const struct clk_pcr_layout *layout,
+ const char *name, const char *parent_name,
+ u32 id, const struct clk_range *range,
+ int chg_pid)
+{
+ struct clk_sam9x5_peripheral *periph;
+ struct clk_init_data init;
+ struct clk_hw *hw;
+ int ret;
+
+ if (!name || !parent_name)
+ return ERR_PTR(-EINVAL);
+
+ periph = kzalloc(sizeof(*periph), GFP_KERNEL);
+ if (!periph)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.parent_names = &parent_name;
+ init.num_parents = 1;
+ if (chg_pid < 0) {
+ init.flags = 0;
+ init.ops = &sam9x5_peripheral_ops;
+ } else {
+ init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE |
+ CLK_SET_RATE_PARENT;
+ init.ops = &sam9x5_peripheral_chg_ops;
+ }
+
+ periph->id = id;
+ periph->hw.init = &init;
+ periph->div = 0;
+ periph->regmap = regmap;
+ periph->lock = lock;
+ if (layout->div_mask)
+ periph->auto_div = true;
+ periph->layout = layout;
+ periph->range = *range;
+ periph->chg_pid = chg_pid;
+
+ hw = &periph->hw;
+ ret = clk_hw_register(NULL, &periph->hw);
+ if (ret) {
+ kfree(periph);
+ hw = ERR_PTR(ret);
+ } else {
+ clk_sam9x5_peripheral_autodiv(periph);
+ }
+
+ return hw;
+}
diff --git a/drivers/clk/at91/clk-pll.c b/drivers/clk/at91/clk-pll.c
new file mode 100644
index 000000000..249d6a53c
--- /dev/null
+++ b/drivers/clk/at91/clk-pll.c
@@ -0,0 +1,379 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com>
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/clk/at91_pmc.h>
+#include <linux/of.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+
+#include "pmc.h"
+
+#define PLL_STATUS_MASK(id) (1 << (1 + (id)))
+#define PLL_REG(id) (AT91_CKGR_PLLAR + ((id) * 4))
+#define PLL_DIV_MASK 0xff
+#define PLL_DIV_MAX PLL_DIV_MASK
+#define PLL_DIV(reg) ((reg) & PLL_DIV_MASK)
+#define PLL_MUL(reg, layout) (((reg) >> (layout)->mul_shift) & \
+ (layout)->mul_mask)
+#define PLL_MUL_MIN 2
+#define PLL_MUL_MASK(layout) ((layout)->mul_mask)
+#define PLL_MUL_MAX(layout) (PLL_MUL_MASK(layout) + 1)
+#define PLL_ICPR_SHIFT(id) ((id) * 16)
+#define PLL_ICPR_MASK(id) (0xffff << PLL_ICPR_SHIFT(id))
+#define PLL_MAX_COUNT 0x3f
+#define PLL_COUNT_SHIFT 8
+#define PLL_OUT_SHIFT 14
+#define PLL_MAX_ID 1
+
+#define to_clk_pll(hw) container_of(hw, struct clk_pll, hw)
+
+struct clk_pll {
+ struct clk_hw hw;
+ struct regmap *regmap;
+ u8 id;
+ u8 div;
+ u8 range;
+ u16 mul;
+ const struct clk_pll_layout *layout;
+ const struct clk_pll_characteristics *characteristics;
+ struct at91_clk_pms pms;
+};
+
+static inline bool clk_pll_ready(struct regmap *regmap, int id)
+{
+ unsigned int status;
+
+ regmap_read(regmap, AT91_PMC_SR, &status);
+
+ return status & PLL_STATUS_MASK(id) ? 1 : 0;
+}
+
+static int clk_pll_prepare(struct clk_hw *hw)
+{
+ struct clk_pll *pll = to_clk_pll(hw);
+ struct regmap *regmap = pll->regmap;
+ const struct clk_pll_layout *layout = pll->layout;
+ const struct clk_pll_characteristics *characteristics =
+ pll->characteristics;
+ u8 id = pll->id;
+ u32 mask = PLL_STATUS_MASK(id);
+ int offset = PLL_REG(id);
+ u8 out = 0;
+ unsigned int pllr;
+ unsigned int status;
+ u8 div;
+ u16 mul;
+
+ regmap_read(regmap, offset, &pllr);
+ div = PLL_DIV(pllr);
+ mul = PLL_MUL(pllr, layout);
+
+ regmap_read(regmap, AT91_PMC_SR, &status);
+ if ((status & mask) &&
+ (div == pll->div && mul == pll->mul))
+ return 0;
+
+ if (characteristics->out)
+ out = characteristics->out[pll->range];
+
+ if (characteristics->icpll)
+ regmap_update_bits(regmap, AT91_PMC_PLLICPR, PLL_ICPR_MASK(id),
+ characteristics->icpll[pll->range] << PLL_ICPR_SHIFT(id));
+
+ regmap_update_bits(regmap, offset, layout->pllr_mask,
+ pll->div | (PLL_MAX_COUNT << PLL_COUNT_SHIFT) |
+ (out << PLL_OUT_SHIFT) |
+ ((pll->mul & layout->mul_mask) << layout->mul_shift));
+
+ while (!clk_pll_ready(regmap, pll->id))
+ cpu_relax();
+
+ return 0;
+}
+
+static int clk_pll_is_prepared(struct clk_hw *hw)
+{
+ struct clk_pll *pll = to_clk_pll(hw);
+
+ return clk_pll_ready(pll->regmap, pll->id);
+}
+
+static void clk_pll_unprepare(struct clk_hw *hw)
+{
+ struct clk_pll *pll = to_clk_pll(hw);
+ unsigned int mask = pll->layout->pllr_mask;
+
+ regmap_update_bits(pll->regmap, PLL_REG(pll->id), mask, ~mask);
+}
+
+static unsigned long clk_pll_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct clk_pll *pll = to_clk_pll(hw);
+
+ if (!pll->div || !pll->mul)
+ return 0;
+
+ return (parent_rate / pll->div) * (pll->mul + 1);
+}
+
+static long clk_pll_get_best_div_mul(struct clk_pll *pll, unsigned long rate,
+ unsigned long parent_rate,
+ u32 *div, u32 *mul,
+ u32 *index) {
+ const struct clk_pll_layout *layout = pll->layout;
+ const struct clk_pll_characteristics *characteristics =
+ pll->characteristics;
+ unsigned long bestremainder = ULONG_MAX;
+ unsigned long maxdiv, mindiv, tmpdiv;
+ long bestrate = -ERANGE;
+ unsigned long bestdiv;
+ unsigned long bestmul;
+ int i = 0;
+
+ /* Check if parent_rate is a valid input rate */
+ if (parent_rate < characteristics->input.min)
+ return -ERANGE;
+
+ /*
+ * Calculate minimum divider based on the minimum multiplier, the
+ * parent_rate and the requested rate.
+ * Should always be 2 according to the input and output characteristics
+ * of the PLL blocks.
+ */
+ mindiv = (parent_rate * PLL_MUL_MIN) / rate;
+ if (!mindiv)
+ mindiv = 1;
+
+ if (parent_rate > characteristics->input.max) {
+ tmpdiv = DIV_ROUND_UP(parent_rate, characteristics->input.max);
+ if (tmpdiv > PLL_DIV_MAX)
+ return -ERANGE;
+
+ if (tmpdiv > mindiv)
+ mindiv = tmpdiv;
+ }
+
+ /*
+ * Calculate the maximum divider which is limited by PLL register
+ * layout (limited by the MUL or DIV field size).
+ */
+ maxdiv = DIV_ROUND_UP(parent_rate * PLL_MUL_MAX(layout), rate);
+ if (maxdiv > PLL_DIV_MAX)
+ maxdiv = PLL_DIV_MAX;
+
+ /*
+ * Iterate over the acceptable divider values to find the best
+ * divider/multiplier pair (the one that generates the closest
+ * rate to the requested one).
+ */
+ for (tmpdiv = mindiv; tmpdiv <= maxdiv; tmpdiv++) {
+ unsigned long remainder;
+ unsigned long tmprate;
+ unsigned long tmpmul;
+
+ /*
+ * Calculate the multiplier associated with the current
+ * divider that provide the closest rate to the requested one.
+ */
+ tmpmul = DIV_ROUND_CLOSEST(rate, parent_rate / tmpdiv);
+ tmprate = (parent_rate / tmpdiv) * tmpmul;
+ if (tmprate > rate)
+ remainder = tmprate - rate;
+ else
+ remainder = rate - tmprate;
+
+ /*
+ * Compare the remainder with the best remainder found until
+ * now and elect a new best multiplier/divider pair if the
+ * current remainder is smaller than the best one.
+ */
+ if (remainder < bestremainder) {
+ bestremainder = remainder;
+ bestdiv = tmpdiv;
+ bestmul = tmpmul;
+ bestrate = tmprate;
+ }
+
+ /*
+ * We've found a perfect match!
+ * Stop searching now and use this multiplier/divider pair.
+ */
+ if (!remainder)
+ break;
+ }
+
+ /* We haven't found any multiplier/divider pair => return -ERANGE */
+ if (bestrate < 0)
+ return bestrate;
+
+ /* Check if bestrate is a valid output rate */
+ for (i = 0; i < characteristics->num_output; i++) {
+ if (bestrate >= characteristics->output[i].min &&
+ bestrate <= characteristics->output[i].max)
+ break;
+ }
+
+ if (i >= characteristics->num_output)
+ return -ERANGE;
+
+ if (div)
+ *div = bestdiv;
+ if (mul)
+ *mul = bestmul - 1;
+ if (index)
+ *index = i;
+
+ return bestrate;
+}
+
+static long clk_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ struct clk_pll *pll = to_clk_pll(hw);
+
+ return clk_pll_get_best_div_mul(pll, rate, *parent_rate,
+ NULL, NULL, NULL);
+}
+
+static int clk_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct clk_pll *pll = to_clk_pll(hw);
+ long ret;
+ u32 div;
+ u32 mul;
+ u32 index;
+
+ ret = clk_pll_get_best_div_mul(pll, rate, parent_rate,
+ &div, &mul, &index);
+ if (ret < 0)
+ return ret;
+
+ pll->range = index;
+ pll->div = div;
+ pll->mul = mul;
+
+ return 0;
+}
+
+static int clk_pll_save_context(struct clk_hw *hw)
+{
+ struct clk_pll *pll = to_clk_pll(hw);
+ struct clk_hw *parent_hw = clk_hw_get_parent(hw);
+
+ pll->pms.parent_rate = clk_hw_get_rate(parent_hw);
+ pll->pms.rate = clk_pll_recalc_rate(&pll->hw, pll->pms.parent_rate);
+ pll->pms.status = clk_pll_ready(pll->regmap, PLL_REG(pll->id));
+
+ return 0;
+}
+
+static void clk_pll_restore_context(struct clk_hw *hw)
+{
+ struct clk_pll *pll = to_clk_pll(hw);
+ unsigned long calc_rate;
+ unsigned int pllr, pllr_out, pllr_count;
+ u8 out = 0;
+
+ if (pll->characteristics->out)
+ out = pll->characteristics->out[pll->range];
+
+ regmap_read(pll->regmap, PLL_REG(pll->id), &pllr);
+
+ calc_rate = (pll->pms.parent_rate / PLL_DIV(pllr)) *
+ (PLL_MUL(pllr, pll->layout) + 1);
+ pllr_count = (pllr >> PLL_COUNT_SHIFT) & PLL_MAX_COUNT;
+ pllr_out = (pllr >> PLL_OUT_SHIFT) & out;
+
+ if (pll->pms.rate != calc_rate ||
+ pll->pms.status != clk_pll_ready(pll->regmap, PLL_REG(pll->id)) ||
+ pllr_count != PLL_MAX_COUNT ||
+ (out && pllr_out != out))
+ pr_warn("PLLAR was not configured properly by firmware\n");
+}
+
+static const struct clk_ops pll_ops = {
+ .prepare = clk_pll_prepare,
+ .unprepare = clk_pll_unprepare,
+ .is_prepared = clk_pll_is_prepared,
+ .recalc_rate = clk_pll_recalc_rate,
+ .round_rate = clk_pll_round_rate,
+ .set_rate = clk_pll_set_rate,
+ .save_context = clk_pll_save_context,
+ .restore_context = clk_pll_restore_context,
+};
+
+struct clk_hw * __init
+at91_clk_register_pll(struct regmap *regmap, const char *name,
+ const char *parent_name, u8 id,
+ const struct clk_pll_layout *layout,
+ const struct clk_pll_characteristics *characteristics)
+{
+ struct clk_pll *pll;
+ struct clk_hw *hw;
+ struct clk_init_data init;
+ int offset = PLL_REG(id);
+ unsigned int pllr;
+ int ret;
+
+ if (id > PLL_MAX_ID)
+ return ERR_PTR(-EINVAL);
+
+ pll = kzalloc(sizeof(*pll), GFP_KERNEL);
+ if (!pll)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &pll_ops;
+ init.parent_names = &parent_name;
+ init.num_parents = 1;
+ init.flags = CLK_SET_RATE_GATE;
+
+ pll->id = id;
+ pll->hw.init = &init;
+ pll->layout = layout;
+ pll->characteristics = characteristics;
+ pll->regmap = regmap;
+ regmap_read(regmap, offset, &pllr);
+ pll->div = PLL_DIV(pllr);
+ pll->mul = PLL_MUL(pllr, layout);
+
+ hw = &pll->hw;
+ ret = clk_hw_register(NULL, &pll->hw);
+ if (ret) {
+ kfree(pll);
+ hw = ERR_PTR(ret);
+ }
+
+ return hw;
+}
+
+
+const struct clk_pll_layout at91rm9200_pll_layout = {
+ .pllr_mask = 0x7FFFFFF,
+ .mul_shift = 16,
+ .mul_mask = 0x7FF,
+};
+
+const struct clk_pll_layout at91sam9g45_pll_layout = {
+ .pllr_mask = 0xFFFFFF,
+ .mul_shift = 16,
+ .mul_mask = 0xFF,
+};
+
+const struct clk_pll_layout at91sam9g20_pllb_layout = {
+ .pllr_mask = 0x3FFFFF,
+ .mul_shift = 16,
+ .mul_mask = 0x3F,
+};
+
+const struct clk_pll_layout sama5d3_pll_layout = {
+ .pllr_mask = 0x1FFFFFF,
+ .mul_shift = 18,
+ .mul_mask = 0x7F,
+};
diff --git a/drivers/clk/at91/clk-plldiv.c b/drivers/clk/at91/clk-plldiv.c
new file mode 100644
index 000000000..ba3a1839a
--- /dev/null
+++ b/drivers/clk/at91/clk-plldiv.c
@@ -0,0 +1,103 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com>
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/clk/at91_pmc.h>
+#include <linux/of.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+
+#include "pmc.h"
+
+#define to_clk_plldiv(hw) container_of(hw, struct clk_plldiv, hw)
+
+struct clk_plldiv {
+ struct clk_hw hw;
+ struct regmap *regmap;
+};
+
+static unsigned long clk_plldiv_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct clk_plldiv *plldiv = to_clk_plldiv(hw);
+ unsigned int mckr;
+
+ regmap_read(plldiv->regmap, AT91_PMC_MCKR, &mckr);
+
+ if (mckr & AT91_PMC_PLLADIV2)
+ return parent_rate / 2;
+
+ return parent_rate;
+}
+
+static long clk_plldiv_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ unsigned long div;
+
+ if (rate > *parent_rate)
+ return *parent_rate;
+ div = *parent_rate / 2;
+ if (rate < div)
+ return div;
+
+ if (rate - div < *parent_rate - rate)
+ return div;
+
+ return *parent_rate;
+}
+
+static int clk_plldiv_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct clk_plldiv *plldiv = to_clk_plldiv(hw);
+
+ if ((parent_rate != rate) && (parent_rate / 2 != rate))
+ return -EINVAL;
+
+ regmap_update_bits(plldiv->regmap, AT91_PMC_MCKR, AT91_PMC_PLLADIV2,
+ parent_rate != rate ? AT91_PMC_PLLADIV2 : 0);
+
+ return 0;
+}
+
+static const struct clk_ops plldiv_ops = {
+ .recalc_rate = clk_plldiv_recalc_rate,
+ .round_rate = clk_plldiv_round_rate,
+ .set_rate = clk_plldiv_set_rate,
+};
+
+struct clk_hw * __init
+at91_clk_register_plldiv(struct regmap *regmap, const char *name,
+ const char *parent_name)
+{
+ struct clk_plldiv *plldiv;
+ struct clk_hw *hw;
+ struct clk_init_data init;
+ int ret;
+
+ plldiv = kzalloc(sizeof(*plldiv), GFP_KERNEL);
+ if (!plldiv)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &plldiv_ops;
+ init.parent_names = parent_name ? &parent_name : NULL;
+ init.num_parents = parent_name ? 1 : 0;
+ init.flags = CLK_SET_RATE_GATE;
+
+ plldiv->hw.init = &init;
+ plldiv->regmap = regmap;
+
+ hw = &plldiv->hw;
+ ret = clk_hw_register(NULL, &plldiv->hw);
+ if (ret) {
+ kfree(plldiv);
+ hw = ERR_PTR(ret);
+ }
+
+ return hw;
+}
diff --git a/drivers/clk/at91/clk-programmable.c b/drivers/clk/at91/clk-programmable.c
new file mode 100644
index 000000000..6c4b259d3
--- /dev/null
+++ b/drivers/clk/at91/clk-programmable.c
@@ -0,0 +1,278 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com>
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/clk/at91_pmc.h>
+#include <linux/of.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+
+#include "pmc.h"
+
+#define PROG_ID_MAX 7
+
+#define PROG_STATUS_MASK(id) (1 << ((id) + 8))
+#define PROG_PRES(layout, pckr) ((pckr >> layout->pres_shift) & layout->pres_mask)
+#define PROG_MAX_RM9200_CSS 3
+
+struct clk_programmable {
+ struct clk_hw hw;
+ struct regmap *regmap;
+ u32 *mux_table;
+ u8 id;
+ const struct clk_programmable_layout *layout;
+ struct at91_clk_pms pms;
+};
+
+#define to_clk_programmable(hw) container_of(hw, struct clk_programmable, hw)
+
+static unsigned long clk_programmable_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct clk_programmable *prog = to_clk_programmable(hw);
+ const struct clk_programmable_layout *layout = prog->layout;
+ unsigned int pckr;
+ unsigned long rate;
+
+ regmap_read(prog->regmap, 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_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ struct clk_programmable *prog = to_clk_programmable(hw);
+ const struct clk_programmable_layout *layout = prog->layout;
+ struct clk_hw *parent;
+ long best_rate = -EINVAL;
+ unsigned long parent_rate;
+ unsigned long tmp_rate = 0;
+ int shift;
+ int i;
+
+ for (i = 0; i < clk_hw_get_num_parents(hw); i++) {
+ parent = clk_hw_get_parent_by_index(hw, i);
+ if (!parent)
+ continue;
+
+ parent_rate = clk_hw_get_rate(parent);
+ if (layout->is_pres_direct) {
+ for (shift = 0; shift <= layout->pres_mask; shift++) {
+ tmp_rate = parent_rate / (shift + 1);
+ if (tmp_rate <= req->rate)
+ break;
+ }
+ } else {
+ for (shift = 0; shift < layout->pres_mask; shift++) {
+ tmp_rate = parent_rate >> shift;
+ if (tmp_rate <= req->rate)
+ break;
+ }
+ }
+
+ if (tmp_rate > req->rate)
+ continue;
+
+ if (best_rate < 0 ||
+ (req->rate - tmp_rate) < (req->rate - best_rate)) {
+ best_rate = tmp_rate;
+ req->best_parent_rate = parent_rate;
+ req->best_parent_hw = parent;
+ }
+
+ if (!best_rate)
+ break;
+ }
+
+ if (best_rate < 0)
+ return best_rate;
+
+ req->rate = best_rate;
+ return 0;
+}
+
+static int clk_programmable_set_parent(struct clk_hw *hw, u8 index)
+{
+ struct clk_programmable *prog = to_clk_programmable(hw);
+ const struct clk_programmable_layout *layout = prog->layout;
+ unsigned int mask = layout->css_mask;
+ unsigned int pckr = index;
+
+ if (layout->have_slck_mck)
+ mask |= AT91_PMC_CSSMCK_MCK;
+
+ if (prog->mux_table)
+ pckr = clk_mux_index_to_val(prog->mux_table, 0, index);
+
+ if (index > layout->css_mask) {
+ if (index > PROG_MAX_RM9200_CSS && !layout->have_slck_mck)
+ return -EINVAL;
+
+ pckr |= AT91_PMC_CSSMCK_MCK;
+ }
+
+ regmap_update_bits(prog->regmap, AT91_PMC_PCKR(prog->id), mask, pckr);
+
+ return 0;
+}
+
+static u8 clk_programmable_get_parent(struct clk_hw *hw)
+{
+ struct clk_programmable *prog = to_clk_programmable(hw);
+ const struct clk_programmable_layout *layout = prog->layout;
+ unsigned int pckr;
+ u8 ret;
+
+ regmap_read(prog->regmap, AT91_PMC_PCKR(prog->id), &pckr);
+
+ ret = pckr & layout->css_mask;
+
+ if (layout->have_slck_mck && (pckr & AT91_PMC_CSSMCK_MCK) && !ret)
+ ret = PROG_MAX_RM9200_CSS + 1;
+
+ if (prog->mux_table)
+ ret = clk_mux_val_to_index(&prog->hw, prog->mux_table, 0, ret);
+
+ return ret;
+}
+
+static int clk_programmable_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct clk_programmable *prog = to_clk_programmable(hw);
+ const struct clk_programmable_layout *layout = prog->layout;
+ unsigned long div = parent_rate / rate;
+ int shift = 0;
+
+ if (!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;
+ }
+
+ regmap_update_bits(prog->regmap, AT91_PMC_PCKR(prog->id),
+ layout->pres_mask << layout->pres_shift,
+ shift << layout->pres_shift);
+
+ return 0;
+}
+
+static int clk_programmable_save_context(struct clk_hw *hw)
+{
+ struct clk_programmable *prog = to_clk_programmable(hw);
+ struct clk_hw *parent_hw = clk_hw_get_parent(hw);
+
+ prog->pms.parent = clk_programmable_get_parent(hw);
+ prog->pms.parent_rate = clk_hw_get_rate(parent_hw);
+ prog->pms.rate = clk_programmable_recalc_rate(hw, prog->pms.parent_rate);
+
+ return 0;
+}
+
+static void clk_programmable_restore_context(struct clk_hw *hw)
+{
+ struct clk_programmable *prog = to_clk_programmable(hw);
+ int ret;
+
+ ret = clk_programmable_set_parent(hw, prog->pms.parent);
+ if (ret)
+ return;
+
+ clk_programmable_set_rate(hw, prog->pms.rate, prog->pms.parent_rate);
+}
+
+static const struct clk_ops programmable_ops = {
+ .recalc_rate = clk_programmable_recalc_rate,
+ .determine_rate = clk_programmable_determine_rate,
+ .get_parent = clk_programmable_get_parent,
+ .set_parent = clk_programmable_set_parent,
+ .set_rate = clk_programmable_set_rate,
+ .save_context = clk_programmable_save_context,
+ .restore_context = clk_programmable_restore_context,
+};
+
+struct clk_hw * __init
+at91_clk_register_programmable(struct regmap *regmap,
+ const char *name, const char **parent_names,
+ u8 num_parents, u8 id,
+ const struct clk_programmable_layout *layout,
+ u32 *mux_table)
+{
+ struct clk_programmable *prog;
+ struct clk_hw *hw;
+ struct clk_init_data init;
+ int ret;
+
+ if (id > PROG_ID_MAX)
+ return ERR_PTR(-EINVAL);
+
+ prog = kzalloc(sizeof(*prog), GFP_KERNEL);
+ if (!prog)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &programmable_ops;
+ init.parent_names = parent_names;
+ init.num_parents = num_parents;
+ init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE;
+
+ prog->id = id;
+ prog->layout = layout;
+ prog->hw.init = &init;
+ prog->regmap = regmap;
+ prog->mux_table = mux_table;
+
+ hw = &prog->hw;
+ ret = clk_hw_register(NULL, &prog->hw);
+ if (ret) {
+ kfree(prog);
+ hw = ERR_PTR(ret);
+ }
+
+ return hw;
+}
+
+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 000000000..0882ed01d
--- /dev/null
+++ b/drivers/clk/at91/clk-sam9x60-pll.c
@@ -0,0 +1,756 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2019 Microchip Technology Inc.
+ *
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/clk/at91_pmc.h>
+#include <linux/of.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+
+#include "pmc.h"
+
+#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_core {
+ struct regmap *regmap;
+ spinlock_t *lock;
+ const struct clk_pll_characteristics *characteristics;
+ const struct clk_pll_layout *layout;
+ struct clk_hw hw;
+ u8 id;
+};
+
+struct sam9x60_frac {
+ struct sam9x60_pll_core core;
+ struct at91_clk_pms pms;
+ u32 frac;
+ u16 mul;
+};
+
+struct sam9x60_div {
+ struct sam9x60_pll_core core;
+ struct at91_clk_pms pms;
+ u8 div;
+ u8 safe_div;
+};
+
+#define to_sam9x60_pll_core(hw) container_of(hw, struct sam9x60_pll_core, hw)
+#define to_sam9x60_frac(core) container_of(core, struct sam9x60_frac, core)
+#define to_sam9x60_div(core) container_of(core, struct sam9x60_div, core)
+
+static struct sam9x60_div *notifier_div;
+
+static inline bool sam9x60_pll_ready(struct regmap *regmap, int id)
+{
+ unsigned int status;
+
+ regmap_read(regmap, AT91_PMC_PLL_ISR0, &status);
+
+ return !!(status & BIT(id));
+}
+
+static bool sam9x60_frac_pll_ready(struct regmap *regmap, u8 id)
+{
+ return sam9x60_pll_ready(regmap, id);
+}
+
+static unsigned long sam9x60_frac_pll_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
+ struct sam9x60_frac *frac = to_sam9x60_frac(core);
+
+ return parent_rate * (frac->mul + 1) +
+ DIV_ROUND_CLOSEST_ULL((u64)parent_rate * frac->frac, (1 << 22));
+}
+
+static int sam9x60_frac_pll_set(struct sam9x60_pll_core *core)
+{
+ struct sam9x60_frac *frac = to_sam9x60_frac(core);
+ struct regmap *regmap = core->regmap;
+ unsigned int val, cfrac, cmul;
+ unsigned long flags;
+
+ spin_lock_irqsave(core->lock, flags);
+
+ regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,
+ AT91_PMC_PLL_UPDT_ID_MSK, core->id);
+ regmap_read(regmap, AT91_PMC_PLL_CTRL1, &val);
+ cmul = (val & core->layout->mul_mask) >> core->layout->mul_shift;
+ cfrac = (val & core->layout->frac_mask) >> core->layout->frac_shift;
+
+ if (sam9x60_frac_pll_ready(regmap, core->id) &&
+ (cmul == frac->mul && cfrac == frac->frac))
+ goto unlock;
+
+ /* Recommended value for PMC_PLL_ACR */
+ if (core->characteristics->upll)
+ val = AT91_PMC_PLL_ACR_DEFAULT_UPLL;
+ else
+ val = AT91_PMC_PLL_ACR_DEFAULT_PLLA;
+ regmap_write(regmap, AT91_PMC_PLL_ACR, val);
+
+ regmap_write(regmap, AT91_PMC_PLL_CTRL1,
+ (frac->mul << core->layout->mul_shift) |
+ (frac->frac << core->layout->frac_shift));
+
+ if (core->characteristics->upll) {
+ /* Enable the UTMI internal bandgap */
+ val |= AT91_PMC_PLL_ACR_UTMIBG;
+ regmap_write(regmap, AT91_PMC_PLL_ACR, val);
+
+ udelay(10);
+
+ /* Enable the UTMI internal regulator */
+ val |= AT91_PMC_PLL_ACR_UTMIVR;
+ regmap_write(regmap, AT91_PMC_PLL_ACR, val);
+
+ udelay(10);
+ }
+
+ regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,
+ AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK,
+ AT91_PMC_PLL_UPDT_UPDATE | core->id);
+
+ regmap_update_bits(regmap, AT91_PMC_PLL_CTRL0,
+ AT91_PMC_PLL_CTRL0_ENLOCK | AT91_PMC_PLL_CTRL0_ENPLL,
+ AT91_PMC_PLL_CTRL0_ENLOCK | AT91_PMC_PLL_CTRL0_ENPLL);
+
+ regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,
+ AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK,
+ AT91_PMC_PLL_UPDT_UPDATE | core->id);
+
+ while (!sam9x60_pll_ready(regmap, core->id))
+ cpu_relax();
+
+unlock:
+ spin_unlock_irqrestore(core->lock, flags);
+
+ return 0;
+}
+
+static int sam9x60_frac_pll_prepare(struct clk_hw *hw)
+{
+ struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
+
+ return sam9x60_frac_pll_set(core);
+}
+
+static void sam9x60_frac_pll_unprepare(struct clk_hw *hw)
+{
+ struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
+ struct regmap *regmap = core->regmap;
+ unsigned long flags;
+
+ spin_lock_irqsave(core->lock, flags);
+
+ regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,
+ AT91_PMC_PLL_UPDT_ID_MSK, core->id);
+
+ regmap_update_bits(regmap, AT91_PMC_PLL_CTRL0, AT91_PMC_PLL_CTRL0_ENPLL, 0);
+
+ if (core->characteristics->upll)
+ regmap_update_bits(regmap, AT91_PMC_PLL_ACR,
+ AT91_PMC_PLL_ACR_UTMIBG | AT91_PMC_PLL_ACR_UTMIVR, 0);
+
+ regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,
+ AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK,
+ AT91_PMC_PLL_UPDT_UPDATE | core->id);
+
+ spin_unlock_irqrestore(core->lock, flags);
+}
+
+static int sam9x60_frac_pll_is_prepared(struct clk_hw *hw)
+{
+ struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
+
+ return sam9x60_pll_ready(core->regmap, core->id);
+}
+
+static long sam9x60_frac_pll_compute_mul_frac(struct sam9x60_pll_core *core,
+ unsigned long rate,
+ unsigned long parent_rate,
+ bool update)
+{
+ struct sam9x60_frac *frac = to_sam9x60_frac(core);
+ 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 a valid. */
+ if (tmprate < FCORE_MIN || tmprate > FCORE_MAX)
+ return -ERANGE;
+
+ if (update) {
+ frac->mul = nmul - 1;
+ frac->frac = nfrac;
+ }
+
+ return tmprate;
+}
+
+static long sam9x60_frac_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
+
+ return sam9x60_frac_pll_compute_mul_frac(core, rate, *parent_rate, false);
+}
+
+static int sam9x60_frac_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
+
+ return sam9x60_frac_pll_compute_mul_frac(core, rate, parent_rate, true);
+}
+
+static int sam9x60_frac_pll_set_rate_chg(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
+ struct sam9x60_frac *frac = to_sam9x60_frac(core);
+ struct regmap *regmap = core->regmap;
+ unsigned long irqflags;
+ unsigned int val, cfrac, cmul;
+ long ret;
+
+ ret = sam9x60_frac_pll_compute_mul_frac(core, rate, parent_rate, true);
+ if (ret <= 0)
+ return ret;
+
+ spin_lock_irqsave(core->lock, irqflags);
+
+ regmap_update_bits(regmap, AT91_PMC_PLL_UPDT, AT91_PMC_PLL_UPDT_ID_MSK,
+ core->id);
+ regmap_read(regmap, AT91_PMC_PLL_CTRL1, &val);
+ cmul = (val & core->layout->mul_mask) >> core->layout->mul_shift;
+ cfrac = (val & core->layout->frac_mask) >> core->layout->frac_shift;
+
+ if (cmul == frac->mul && cfrac == frac->frac)
+ goto unlock;
+
+ regmap_write(regmap, AT91_PMC_PLL_CTRL1,
+ (frac->mul << core->layout->mul_shift) |
+ (frac->frac << core->layout->frac_shift));
+
+ regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,
+ AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK,
+ AT91_PMC_PLL_UPDT_UPDATE | core->id);
+
+ regmap_update_bits(regmap, AT91_PMC_PLL_CTRL0,
+ AT91_PMC_PLL_CTRL0_ENLOCK | AT91_PMC_PLL_CTRL0_ENPLL,
+ AT91_PMC_PLL_CTRL0_ENLOCK |
+ AT91_PMC_PLL_CTRL0_ENPLL);
+
+ regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,
+ AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK,
+ AT91_PMC_PLL_UPDT_UPDATE | core->id);
+
+ while (!sam9x60_pll_ready(regmap, core->id))
+ cpu_relax();
+
+unlock:
+ spin_unlock_irqrestore(core->lock, irqflags);
+
+ return ret;
+}
+
+static int sam9x60_frac_pll_save_context(struct clk_hw *hw)
+{
+ struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
+ struct sam9x60_frac *frac = to_sam9x60_frac(core);
+
+ frac->pms.status = sam9x60_pll_ready(core->regmap, core->id);
+
+ return 0;
+}
+
+static void sam9x60_frac_pll_restore_context(struct clk_hw *hw)
+{
+ struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
+ struct sam9x60_frac *frac = to_sam9x60_frac(core);
+
+ if (frac->pms.status)
+ sam9x60_frac_pll_set(core);
+}
+
+static const struct clk_ops sam9x60_frac_pll_ops = {
+ .prepare = sam9x60_frac_pll_prepare,
+ .unprepare = sam9x60_frac_pll_unprepare,
+ .is_prepared = sam9x60_frac_pll_is_prepared,
+ .recalc_rate = sam9x60_frac_pll_recalc_rate,
+ .round_rate = sam9x60_frac_pll_round_rate,
+ .set_rate = sam9x60_frac_pll_set_rate,
+ .save_context = sam9x60_frac_pll_save_context,
+ .restore_context = sam9x60_frac_pll_restore_context,
+};
+
+static const struct clk_ops sam9x60_frac_pll_ops_chg = {
+ .prepare = sam9x60_frac_pll_prepare,
+ .unprepare = sam9x60_frac_pll_unprepare,
+ .is_prepared = sam9x60_frac_pll_is_prepared,
+ .recalc_rate = sam9x60_frac_pll_recalc_rate,
+ .round_rate = sam9x60_frac_pll_round_rate,
+ .set_rate = sam9x60_frac_pll_set_rate_chg,
+ .save_context = sam9x60_frac_pll_save_context,
+ .restore_context = sam9x60_frac_pll_restore_context,
+};
+
+/* This function should be called with spinlock acquired. */
+static void sam9x60_div_pll_set_div(struct sam9x60_pll_core *core, u32 div,
+ bool enable)
+{
+ struct regmap *regmap = core->regmap;
+ u32 ena_msk = enable ? core->layout->endiv_mask : 0;
+ u32 ena_val = enable ? (1 << core->layout->endiv_shift) : 0;
+
+ regmap_update_bits(regmap, AT91_PMC_PLL_CTRL0,
+ core->layout->div_mask | ena_msk,
+ (div << core->layout->div_shift) | ena_val);
+
+ regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,
+ AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK,
+ AT91_PMC_PLL_UPDT_UPDATE | core->id);
+
+ while (!sam9x60_pll_ready(regmap, core->id))
+ cpu_relax();
+}
+
+static int sam9x60_div_pll_set(struct sam9x60_pll_core *core)
+{
+ struct sam9x60_div *div = to_sam9x60_div(core);
+ struct regmap *regmap = core->regmap;
+ unsigned long flags;
+ unsigned int val, cdiv;
+
+ spin_lock_irqsave(core->lock, flags);
+ regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,
+ AT91_PMC_PLL_UPDT_ID_MSK, core->id);
+ regmap_read(regmap, AT91_PMC_PLL_CTRL0, &val);
+ cdiv = (val & core->layout->div_mask) >> core->layout->div_shift;
+
+ /* Stop if enabled an nothing changed. */
+ if (!!(val & core->layout->endiv_mask) && cdiv == div->div)
+ goto unlock;
+
+ sam9x60_div_pll_set_div(core, div->div, 1);
+
+unlock:
+ spin_unlock_irqrestore(core->lock, flags);
+
+ return 0;
+}
+
+static int sam9x60_div_pll_prepare(struct clk_hw *hw)
+{
+ struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
+
+ return sam9x60_div_pll_set(core);
+}
+
+static void sam9x60_div_pll_unprepare(struct clk_hw *hw)
+{
+ struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
+ struct regmap *regmap = core->regmap;
+ unsigned long flags;
+
+ spin_lock_irqsave(core->lock, flags);
+
+ regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,
+ AT91_PMC_PLL_UPDT_ID_MSK, core->id);
+
+ regmap_update_bits(regmap, AT91_PMC_PLL_CTRL0,
+ core->layout->endiv_mask, 0);
+
+ regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,
+ AT91_PMC_PLL_UPDT_UPDATE | AT91_PMC_PLL_UPDT_ID_MSK,
+ AT91_PMC_PLL_UPDT_UPDATE | core->id);
+
+ spin_unlock_irqrestore(core->lock, flags);
+}
+
+static int sam9x60_div_pll_is_prepared(struct clk_hw *hw)
+{
+ struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
+ struct regmap *regmap = core->regmap;
+ unsigned long flags;
+ unsigned int val;
+
+ spin_lock_irqsave(core->lock, flags);
+
+ regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,
+ AT91_PMC_PLL_UPDT_ID_MSK, core->id);
+ regmap_read(regmap, AT91_PMC_PLL_CTRL0, &val);
+
+ spin_unlock_irqrestore(core->lock, flags);
+
+ return !!(val & core->layout->endiv_mask);
+}
+
+static unsigned long sam9x60_div_pll_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
+ struct sam9x60_div *div = to_sam9x60_div(core);
+
+ return DIV_ROUND_CLOSEST_ULL(parent_rate, (div->div + 1));
+}
+
+static long sam9x60_div_pll_compute_div(struct sam9x60_pll_core *core,
+ unsigned long *parent_rate,
+ unsigned long rate)
+{
+ const struct clk_pll_characteristics *characteristics =
+ core->characteristics;
+ struct clk_hw *parent = clk_hw_get_parent(&core->hw);
+ unsigned long tmp_rate, tmp_parent_rate, tmp_diff;
+ long best_diff = -1, best_rate = -EINVAL;
+ u32 divid;
+
+ if (!rate)
+ return 0;
+
+ if (rate < characteristics->output[0].min ||
+ rate > characteristics->output[0].max)
+ return -ERANGE;
+
+ for (divid = 1; divid < core->layout->div_mask; divid++) {
+ tmp_parent_rate = clk_hw_round_rate(parent, rate * divid);
+ if (!tmp_parent_rate)
+ continue;
+
+ tmp_rate = DIV_ROUND_CLOSEST_ULL(tmp_parent_rate, divid);
+ tmp_diff = abs(rate - tmp_rate);
+
+ if (best_diff < 0 || best_diff > tmp_diff) {
+ *parent_rate = tmp_parent_rate;
+ best_rate = tmp_rate;
+ best_diff = tmp_diff;
+ }
+
+ if (!best_diff)
+ break;
+ }
+
+ if (best_rate < characteristics->output[0].min ||
+ best_rate > characteristics->output[0].max)
+ return -ERANGE;
+
+ return best_rate;
+}
+
+static long sam9x60_div_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
+
+ return sam9x60_div_pll_compute_div(core, parent_rate, rate);
+}
+
+static int sam9x60_div_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
+ struct sam9x60_div *div = to_sam9x60_div(core);
+
+ div->div = DIV_ROUND_CLOSEST(parent_rate, rate) - 1;
+
+ return 0;
+}
+
+static int sam9x60_div_pll_set_rate_chg(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
+ struct sam9x60_div *div = to_sam9x60_div(core);
+ struct regmap *regmap = core->regmap;
+ unsigned long irqflags;
+ unsigned int val, cdiv;
+
+ div->div = DIV_ROUND_CLOSEST(parent_rate, rate) - 1;
+
+ spin_lock_irqsave(core->lock, irqflags);
+ regmap_update_bits(regmap, AT91_PMC_PLL_UPDT, AT91_PMC_PLL_UPDT_ID_MSK,
+ core->id);
+ regmap_read(regmap, AT91_PMC_PLL_CTRL0, &val);
+ cdiv = (val & core->layout->div_mask) >> core->layout->div_shift;
+
+ /* Stop if nothing changed. */
+ if (cdiv == div->div)
+ goto unlock;
+
+ sam9x60_div_pll_set_div(core, div->div, 0);
+
+unlock:
+ spin_unlock_irqrestore(core->lock, irqflags);
+
+ return 0;
+}
+
+static int sam9x60_div_pll_save_context(struct clk_hw *hw)
+{
+ struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
+ struct sam9x60_div *div = to_sam9x60_div(core);
+
+ div->pms.status = sam9x60_div_pll_is_prepared(hw);
+
+ return 0;
+}
+
+static void sam9x60_div_pll_restore_context(struct clk_hw *hw)
+{
+ struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
+ struct sam9x60_div *div = to_sam9x60_div(core);
+
+ if (div->pms.status)
+ sam9x60_div_pll_set(core);
+}
+
+static int sam9x60_div_pll_notifier_fn(struct notifier_block *notifier,
+ unsigned long code, void *data)
+{
+ struct sam9x60_div *div = notifier_div;
+ struct sam9x60_pll_core core = div->core;
+ struct regmap *regmap = core.regmap;
+ unsigned long irqflags;
+ u32 val, cdiv;
+ int ret = NOTIFY_DONE;
+
+ if (code != PRE_RATE_CHANGE)
+ return ret;
+
+ /*
+ * We switch to safe divider to avoid overclocking of other domains
+ * feed by us while the frac PLL (our parent) is changed.
+ */
+ div->div = div->safe_div;
+
+ spin_lock_irqsave(core.lock, irqflags);
+ regmap_update_bits(regmap, AT91_PMC_PLL_UPDT, AT91_PMC_PLL_UPDT_ID_MSK,
+ core.id);
+ regmap_read(regmap, AT91_PMC_PLL_CTRL0, &val);
+ cdiv = (val & core.layout->div_mask) >> core.layout->div_shift;
+
+ /* Stop if nothing changed. */
+ if (cdiv == div->safe_div)
+ goto unlock;
+
+ sam9x60_div_pll_set_div(&core, div->div, 0);
+ ret = NOTIFY_OK;
+
+unlock:
+ spin_unlock_irqrestore(core.lock, irqflags);
+
+ return ret;
+}
+
+static struct notifier_block sam9x60_div_pll_notifier = {
+ .notifier_call = sam9x60_div_pll_notifier_fn,
+};
+
+static const struct clk_ops sam9x60_div_pll_ops = {
+ .prepare = sam9x60_div_pll_prepare,
+ .unprepare = sam9x60_div_pll_unprepare,
+ .is_prepared = sam9x60_div_pll_is_prepared,
+ .recalc_rate = sam9x60_div_pll_recalc_rate,
+ .round_rate = sam9x60_div_pll_round_rate,
+ .set_rate = sam9x60_div_pll_set_rate,
+ .save_context = sam9x60_div_pll_save_context,
+ .restore_context = sam9x60_div_pll_restore_context,
+};
+
+static const struct clk_ops sam9x60_div_pll_ops_chg = {
+ .prepare = sam9x60_div_pll_prepare,
+ .unprepare = sam9x60_div_pll_unprepare,
+ .is_prepared = sam9x60_div_pll_is_prepared,
+ .recalc_rate = sam9x60_div_pll_recalc_rate,
+ .round_rate = sam9x60_div_pll_round_rate,
+ .set_rate = sam9x60_div_pll_set_rate_chg,
+ .save_context = sam9x60_div_pll_save_context,
+ .restore_context = sam9x60_div_pll_restore_context,
+};
+
+struct clk_hw * __init
+sam9x60_clk_register_frac_pll(struct regmap *regmap, spinlock_t *lock,
+ const char *name, const char *parent_name,
+ struct clk_hw *parent_hw, u8 id,
+ const struct clk_pll_characteristics *characteristics,
+ const struct clk_pll_layout *layout, u32 flags)
+{
+ struct sam9x60_frac *frac;
+ struct clk_hw *hw;
+ struct clk_init_data init;
+ unsigned long parent_rate, irqflags;
+ unsigned int val;
+ int ret;
+
+ if (id > PLL_MAX_ID || !lock || !parent_hw)
+ return ERR_PTR(-EINVAL);
+
+ frac = kzalloc(sizeof(*frac), GFP_KERNEL);
+ if (!frac)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.parent_names = &parent_name;
+ init.num_parents = 1;
+ if (flags & CLK_SET_RATE_GATE)
+ init.ops = &sam9x60_frac_pll_ops;
+ else
+ init.ops = &sam9x60_frac_pll_ops_chg;
+
+ init.flags = flags;
+
+ frac->core.id = id;
+ frac->core.hw.init = &init;
+ frac->core.characteristics = characteristics;
+ frac->core.layout = layout;
+ frac->core.regmap = regmap;
+ frac->core.lock = lock;
+
+ spin_lock_irqsave(frac->core.lock, irqflags);
+ if (sam9x60_pll_ready(regmap, id)) {
+ regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,
+ AT91_PMC_PLL_UPDT_ID_MSK, id);
+ regmap_read(regmap, AT91_PMC_PLL_CTRL1, &val);
+ frac->mul = FIELD_GET(PMC_PLL_CTRL1_MUL_MSK, val);
+ frac->frac = FIELD_GET(PMC_PLL_CTRL1_FRACR_MSK, val);
+ } else {
+ /*
+ * This means the PLL is not setup by bootloaders. In this
+ * case we need to set the minimum rate for it. Otherwise
+ * a clock child of this PLL may be enabled before setting
+ * its rate leading to enabling this PLL with unsupported
+ * rate. This will lead to PLL not being locked at all.
+ */
+ parent_rate = clk_hw_get_rate(parent_hw);
+ if (!parent_rate) {
+ hw = ERR_PTR(-EINVAL);
+ goto free;
+ }
+
+ ret = sam9x60_frac_pll_compute_mul_frac(&frac->core, FCORE_MIN,
+ parent_rate, true);
+ if (ret < 0) {
+ hw = ERR_PTR(ret);
+ goto free;
+ }
+ }
+ spin_unlock_irqrestore(frac->core.lock, irqflags);
+
+ hw = &frac->core.hw;
+ ret = clk_hw_register(NULL, hw);
+ if (ret) {
+ kfree(frac);
+ hw = ERR_PTR(ret);
+ }
+
+ return hw;
+
+free:
+ spin_unlock_irqrestore(frac->core.lock, irqflags);
+ kfree(frac);
+ return hw;
+}
+
+struct clk_hw * __init
+sam9x60_clk_register_div_pll(struct regmap *regmap, spinlock_t *lock,
+ const char *name, const char *parent_name, u8 id,
+ const struct clk_pll_characteristics *characteristics,
+ const struct clk_pll_layout *layout, u32 flags,
+ u32 safe_div)
+{
+ struct sam9x60_div *div;
+ struct clk_hw *hw;
+ struct clk_init_data init;
+ unsigned long irqflags;
+ unsigned int val;
+ int ret;
+
+ /* We only support one changeable PLL. */
+ if (id > PLL_MAX_ID || !lock || (safe_div && notifier_div))
+ return ERR_PTR(-EINVAL);
+
+ if (safe_div >= PLL_DIV_MAX)
+ safe_div = PLL_DIV_MAX - 1;
+
+ div = kzalloc(sizeof(*div), GFP_KERNEL);
+ if (!div)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.parent_names = &parent_name;
+ init.num_parents = 1;
+ if (flags & CLK_SET_RATE_GATE)
+ init.ops = &sam9x60_div_pll_ops;
+ else
+ init.ops = &sam9x60_div_pll_ops_chg;
+ init.flags = flags;
+
+ div->core.id = id;
+ div->core.hw.init = &init;
+ div->core.characteristics = characteristics;
+ div->core.layout = layout;
+ div->core.regmap = regmap;
+ div->core.lock = lock;
+ div->safe_div = safe_div;
+
+ spin_lock_irqsave(div->core.lock, irqflags);
+
+ regmap_update_bits(regmap, AT91_PMC_PLL_UPDT,
+ AT91_PMC_PLL_UPDT_ID_MSK, id);
+ regmap_read(regmap, AT91_PMC_PLL_CTRL0, &val);
+ div->div = FIELD_GET(PMC_PLL_CTRL0_DIV_MSK, val);
+
+ spin_unlock_irqrestore(div->core.lock, irqflags);
+
+ hw = &div->core.hw;
+ ret = clk_hw_register(NULL, hw);
+ if (ret) {
+ kfree(div);
+ hw = ERR_PTR(ret);
+ } else if (div->safe_div) {
+ notifier_div = div;
+ clk_notifier_register(hw->clk, &sam9x60_div_pll_notifier);
+ }
+
+ return hw;
+}
+
diff --git a/drivers/clk/at91/clk-slow.c b/drivers/clk/at91/clk-slow.c
new file mode 100644
index 000000000..ac9f7a48b
--- /dev/null
+++ b/drivers/clk/at91/clk-slow.c
@@ -0,0 +1,76 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * drivers/clk/at91/clk-slow.c
+ *
+ * Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com>
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/clk/at91_pmc.h>
+#include <linux/of.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+
+#include "pmc.h"
+
+struct clk_sam9260_slow {
+ struct clk_hw hw;
+ struct regmap *regmap;
+};
+
+#define to_clk_sam9260_slow(hw) container_of(hw, struct clk_sam9260_slow, hw)
+
+static u8 clk_sam9260_slow_get_parent(struct clk_hw *hw)
+{
+ struct clk_sam9260_slow *slowck = to_clk_sam9260_slow(hw);
+ unsigned int status;
+
+ regmap_read(slowck->regmap, AT91_PMC_SR, &status);
+
+ return status & AT91_PMC_OSCSEL ? 1 : 0;
+}
+
+static const struct clk_ops sam9260_slow_ops = {
+ .get_parent = clk_sam9260_slow_get_parent,
+};
+
+struct clk_hw * __init
+at91_clk_register_sam9260_slow(struct regmap *regmap,
+ const char *name,
+ const char **parent_names,
+ int num_parents)
+{
+ struct clk_sam9260_slow *slowck;
+ struct clk_hw *hw;
+ struct clk_init_data init;
+ int ret;
+
+ if (!name)
+ return ERR_PTR(-EINVAL);
+
+ if (!parent_names || !num_parents)
+ return ERR_PTR(-EINVAL);
+
+ slowck = kzalloc(sizeof(*slowck), GFP_KERNEL);
+ if (!slowck)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &sam9260_slow_ops;
+ init.parent_names = parent_names;
+ init.num_parents = num_parents;
+ init.flags = 0;
+
+ slowck->hw.init = &init;
+ slowck->regmap = regmap;
+
+ hw = &slowck->hw;
+ ret = clk_hw_register(NULL, &slowck->hw);
+ if (ret) {
+ kfree(slowck);
+ hw = ERR_PTR(ret);
+ }
+
+ return hw;
+}
diff --git a/drivers/clk/at91/clk-smd.c b/drivers/clk/at91/clk-smd.c
new file mode 100644
index 000000000..160378438
--- /dev/null
+++ b/drivers/clk/at91/clk-smd.c
@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com>
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/clk/at91_pmc.h>
+#include <linux/of.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+
+#include "pmc.h"
+
+#define SMD_DIV_SHIFT 8
+#define SMD_MAX_DIV 0xf
+
+struct at91sam9x5_clk_smd {
+ struct clk_hw hw;
+ struct regmap *regmap;
+};
+
+#define to_at91sam9x5_clk_smd(hw) \
+ container_of(hw, struct at91sam9x5_clk_smd, hw)
+
+static unsigned long at91sam9x5_clk_smd_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct at91sam9x5_clk_smd *smd = to_at91sam9x5_clk_smd(hw);
+ unsigned int smdr;
+ u8 smddiv;
+
+ regmap_read(smd->regmap, AT91_PMC_SMD, &smdr);
+ smddiv = (smdr & AT91_PMC_SMD_DIV) >> SMD_DIV_SHIFT;
+
+ return parent_rate / (smddiv + 1);
+}
+
+static long at91sam9x5_clk_smd_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ unsigned long div;
+ unsigned long bestrate;
+ unsigned long tmp;
+
+ if (rate >= *parent_rate)
+ return *parent_rate;
+
+ div = *parent_rate / rate;
+ if (div > SMD_MAX_DIV)
+ return *parent_rate / (SMD_MAX_DIV + 1);
+
+ bestrate = *parent_rate / div;
+ tmp = *parent_rate / (div + 1);
+ if (bestrate - rate > rate - tmp)
+ bestrate = tmp;
+
+ return bestrate;
+}
+
+static int at91sam9x5_clk_smd_set_parent(struct clk_hw *hw, u8 index)
+{
+ struct at91sam9x5_clk_smd *smd = to_at91sam9x5_clk_smd(hw);
+
+ if (index > 1)
+ return -EINVAL;
+
+ regmap_update_bits(smd->regmap, AT91_PMC_SMD, AT91_PMC_SMDS,
+ index ? AT91_PMC_SMDS : 0);
+
+ return 0;
+}
+
+static u8 at91sam9x5_clk_smd_get_parent(struct clk_hw *hw)
+{
+ struct at91sam9x5_clk_smd *smd = to_at91sam9x5_clk_smd(hw);
+ unsigned int smdr;
+
+ regmap_read(smd->regmap, AT91_PMC_SMD, &smdr);
+
+ return smdr & AT91_PMC_SMDS;
+}
+
+static int at91sam9x5_clk_smd_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct at91sam9x5_clk_smd *smd = to_at91sam9x5_clk_smd(hw);
+ unsigned long div = parent_rate / rate;
+
+ if (parent_rate % rate || div < 1 || div > (SMD_MAX_DIV + 1))
+ return -EINVAL;
+
+ regmap_update_bits(smd->regmap, AT91_PMC_SMD, AT91_PMC_SMD_DIV,
+ (div - 1) << SMD_DIV_SHIFT);
+
+ return 0;
+}
+
+static const struct clk_ops at91sam9x5_smd_ops = {
+ .recalc_rate = at91sam9x5_clk_smd_recalc_rate,
+ .round_rate = at91sam9x5_clk_smd_round_rate,
+ .get_parent = at91sam9x5_clk_smd_get_parent,
+ .set_parent = at91sam9x5_clk_smd_set_parent,
+ .set_rate = at91sam9x5_clk_smd_set_rate,
+};
+
+struct clk_hw * __init
+at91sam9x5_clk_register_smd(struct regmap *regmap, const char *name,
+ const char **parent_names, u8 num_parents)
+{
+ struct at91sam9x5_clk_smd *smd;
+ struct clk_hw *hw;
+ struct clk_init_data init;
+ int ret;
+
+ smd = kzalloc(sizeof(*smd), GFP_KERNEL);
+ if (!smd)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &at91sam9x5_smd_ops;
+ init.parent_names = parent_names;
+ init.num_parents = num_parents;
+ init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE;
+
+ smd->hw.init = &init;
+ smd->regmap = regmap;
+
+ hw = &smd->hw;
+ ret = clk_hw_register(NULL, &smd->hw);
+ if (ret) {
+ kfree(smd);
+ hw = ERR_PTR(ret);
+ }
+
+ return hw;
+}
diff --git a/drivers/clk/at91/clk-system.c b/drivers/clk/at91/clk-system.c
new file mode 100644
index 000000000..80720fd1a
--- /dev/null
+++ b/drivers/clk/at91/clk-system.c
@@ -0,0 +1,140 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com>
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/clk/at91_pmc.h>
+#include <linux/of.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+
+#include "pmc.h"
+
+#define SYSTEM_MAX_ID 31
+
+#define SYSTEM_MAX_NAME_SZ 32
+
+#define to_clk_system(hw) container_of(hw, struct clk_system, hw)
+struct clk_system {
+ struct clk_hw hw;
+ struct regmap *regmap;
+ struct at91_clk_pms pms;
+ u8 id;
+};
+
+static inline int is_pck(int id)
+{
+ return (id >= 8) && (id <= 15);
+}
+
+static inline bool clk_system_ready(struct regmap *regmap, int id)
+{
+ unsigned int status;
+
+ regmap_read(regmap, AT91_PMC_SR, &status);
+
+ return !!(status & (1 << id));
+}
+
+static int clk_system_prepare(struct clk_hw *hw)
+{
+ struct clk_system *sys = to_clk_system(hw);
+
+ regmap_write(sys->regmap, AT91_PMC_SCER, 1 << sys->id);
+
+ if (!is_pck(sys->id))
+ return 0;
+
+ while (!clk_system_ready(sys->regmap, sys->id))
+ cpu_relax();
+
+ return 0;
+}
+
+static void clk_system_unprepare(struct clk_hw *hw)
+{
+ struct clk_system *sys = to_clk_system(hw);
+
+ regmap_write(sys->regmap, AT91_PMC_SCDR, 1 << sys->id);
+}
+
+static int clk_system_is_prepared(struct clk_hw *hw)
+{
+ struct clk_system *sys = to_clk_system(hw);
+ unsigned int status;
+
+ regmap_read(sys->regmap, AT91_PMC_SCSR, &status);
+
+ if (!(status & (1 << sys->id)))
+ return 0;
+
+ if (!is_pck(sys->id))
+ return 1;
+
+ regmap_read(sys->regmap, AT91_PMC_SR, &status);
+
+ return !!(status & (1 << sys->id));
+}
+
+static int clk_system_save_context(struct clk_hw *hw)
+{
+ struct clk_system *sys = to_clk_system(hw);
+
+ sys->pms.status = clk_system_is_prepared(hw);
+
+ return 0;
+}
+
+static void clk_system_restore_context(struct clk_hw *hw)
+{
+ struct clk_system *sys = to_clk_system(hw);
+
+ if (sys->pms.status)
+ clk_system_prepare(&sys->hw);
+}
+
+static const struct clk_ops system_ops = {
+ .prepare = clk_system_prepare,
+ .unprepare = clk_system_unprepare,
+ .is_prepared = clk_system_is_prepared,
+ .save_context = clk_system_save_context,
+ .restore_context = clk_system_restore_context,
+};
+
+struct clk_hw * __init
+at91_clk_register_system(struct regmap *regmap, const char *name,
+ const char *parent_name, u8 id)
+{
+ struct clk_system *sys;
+ struct clk_hw *hw;
+ struct clk_init_data init;
+ int ret;
+
+ if (!parent_name || id > SYSTEM_MAX_ID)
+ return ERR_PTR(-EINVAL);
+
+ sys = kzalloc(sizeof(*sys), GFP_KERNEL);
+ if (!sys)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &system_ops;
+ init.parent_names = &parent_name;
+ init.num_parents = 1;
+ init.flags = CLK_SET_RATE_PARENT;
+
+ sys->id = id;
+ sys->hw.init = &init;
+ sys->regmap = regmap;
+
+ hw = &sys->hw;
+ ret = clk_hw_register(NULL, &sys->hw);
+ if (ret) {
+ kfree(sys);
+ hw = ERR_PTR(ret);
+ }
+
+ return hw;
+}
diff --git a/drivers/clk/at91/clk-usb.c b/drivers/clk/at91/clk-usb.c
new file mode 100644
index 000000000..b0696a928
--- /dev/null
+++ b/drivers/clk/at91/clk-usb.c
@@ -0,0 +1,422 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com>
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/clk/at91_pmc.h>
+#include <linux/of.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+
+#include "pmc.h"
+
+#define SAM9X5_USB_DIV_SHIFT 8
+#define SAM9X5_USB_MAX_DIV 0xf
+
+#define RM9200_USB_DIV_SHIFT 28
+#define RM9200_USB_DIV_TAB_SIZE 4
+
+#define SAM9X5_USBS_MASK GENMASK(0, 0)
+#define SAM9X60_USBS_MASK GENMASK(1, 0)
+
+struct at91sam9x5_clk_usb {
+ struct clk_hw hw;
+ struct regmap *regmap;
+ struct at91_clk_pms pms;
+ u32 usbs_mask;
+ u8 num_parents;
+};
+
+#define to_at91sam9x5_clk_usb(hw) \
+ container_of(hw, struct at91sam9x5_clk_usb, hw)
+
+struct at91rm9200_clk_usb {
+ struct clk_hw hw;
+ struct regmap *regmap;
+ u32 divisors[4];
+};
+
+#define to_at91rm9200_clk_usb(hw) \
+ container_of(hw, struct at91rm9200_clk_usb, hw)
+
+static unsigned long at91sam9x5_clk_usb_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(hw);
+ unsigned int usbr;
+ u8 usbdiv;
+
+ regmap_read(usb->regmap, AT91_PMC_USB, &usbr);
+ usbdiv = (usbr & AT91_PMC_OHCIUSBDIV) >> SAM9X5_USB_DIV_SHIFT;
+
+ return DIV_ROUND_CLOSEST(parent_rate, (usbdiv + 1));
+}
+
+static int at91sam9x5_clk_usb_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ struct clk_hw *parent;
+ long best_rate = -EINVAL;
+ unsigned long tmp_rate;
+ int best_diff = -1;
+ int tmp_diff;
+ int i;
+
+ for (i = 0; i < clk_hw_get_num_parents(hw); i++) {
+ int div;
+
+ parent = clk_hw_get_parent_by_index(hw, i);
+ if (!parent)
+ continue;
+
+ for (div = 1; div < SAM9X5_USB_MAX_DIV + 2; div++) {
+ unsigned long tmp_parent_rate;
+
+ tmp_parent_rate = req->rate * div;
+ tmp_parent_rate = clk_hw_round_rate(parent,
+ tmp_parent_rate);
+ if (!tmp_parent_rate)
+ continue;
+
+ tmp_rate = DIV_ROUND_CLOSEST(tmp_parent_rate, div);
+ if (tmp_rate < req->rate)
+ tmp_diff = req->rate - tmp_rate;
+ else
+ tmp_diff = tmp_rate - req->rate;
+
+ if (best_diff < 0 || best_diff > tmp_diff) {
+ best_rate = tmp_rate;
+ best_diff = tmp_diff;
+ req->best_parent_rate = tmp_parent_rate;
+ req->best_parent_hw = parent;
+ }
+
+ if (!best_diff || tmp_rate < req->rate)
+ break;
+ }
+
+ if (!best_diff)
+ break;
+ }
+
+ if (best_rate < 0)
+ return best_rate;
+
+ req->rate = best_rate;
+ return 0;
+}
+
+static int at91sam9x5_clk_usb_set_parent(struct clk_hw *hw, u8 index)
+{
+ struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(hw);
+
+ if (index >= usb->num_parents)
+ return -EINVAL;
+
+ regmap_update_bits(usb->regmap, AT91_PMC_USB, usb->usbs_mask, index);
+
+ return 0;
+}
+
+static u8 at91sam9x5_clk_usb_get_parent(struct clk_hw *hw)
+{
+ struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(hw);
+ unsigned int usbr;
+
+ regmap_read(usb->regmap, AT91_PMC_USB, &usbr);
+
+ return usbr & usb->usbs_mask;
+}
+
+static int at91sam9x5_clk_usb_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(hw);
+ unsigned long div;
+
+ if (!rate)
+ return -EINVAL;
+
+ div = DIV_ROUND_CLOSEST(parent_rate, rate);
+ if (div > SAM9X5_USB_MAX_DIV + 1 || !div)
+ return -EINVAL;
+
+ regmap_update_bits(usb->regmap, AT91_PMC_USB, AT91_PMC_OHCIUSBDIV,
+ (div - 1) << SAM9X5_USB_DIV_SHIFT);
+
+ return 0;
+}
+
+static int at91sam9x5_usb_save_context(struct clk_hw *hw)
+{
+ struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(hw);
+ struct clk_hw *parent_hw = clk_hw_get_parent(hw);
+
+ usb->pms.parent = at91sam9x5_clk_usb_get_parent(hw);
+ usb->pms.parent_rate = clk_hw_get_rate(parent_hw);
+ usb->pms.rate = at91sam9x5_clk_usb_recalc_rate(hw, usb->pms.parent_rate);
+
+ return 0;
+}
+
+static void at91sam9x5_usb_restore_context(struct clk_hw *hw)
+{
+ struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(hw);
+ int ret;
+
+ ret = at91sam9x5_clk_usb_set_parent(hw, usb->pms.parent);
+ if (ret)
+ return;
+
+ at91sam9x5_clk_usb_set_rate(hw, usb->pms.rate, usb->pms.parent_rate);
+}
+
+static const struct clk_ops at91sam9x5_usb_ops = {
+ .recalc_rate = at91sam9x5_clk_usb_recalc_rate,
+ .determine_rate = at91sam9x5_clk_usb_determine_rate,
+ .get_parent = at91sam9x5_clk_usb_get_parent,
+ .set_parent = at91sam9x5_clk_usb_set_parent,
+ .set_rate = at91sam9x5_clk_usb_set_rate,
+ .save_context = at91sam9x5_usb_save_context,
+ .restore_context = at91sam9x5_usb_restore_context,
+};
+
+static int at91sam9n12_clk_usb_enable(struct clk_hw *hw)
+{
+ struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(hw);
+
+ regmap_update_bits(usb->regmap, AT91_PMC_USB, AT91_PMC_USBS,
+ AT91_PMC_USBS);
+
+ return 0;
+}
+
+static void at91sam9n12_clk_usb_disable(struct clk_hw *hw)
+{
+ struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(hw);
+
+ regmap_update_bits(usb->regmap, AT91_PMC_USB, AT91_PMC_USBS, 0);
+}
+
+static int at91sam9n12_clk_usb_is_enabled(struct clk_hw *hw)
+{
+ struct at91sam9x5_clk_usb *usb = to_at91sam9x5_clk_usb(hw);
+ unsigned int usbr;
+
+ regmap_read(usb->regmap, AT91_PMC_USB, &usbr);
+
+ return usbr & AT91_PMC_USBS;
+}
+
+static const struct clk_ops at91sam9n12_usb_ops = {
+ .enable = at91sam9n12_clk_usb_enable,
+ .disable = at91sam9n12_clk_usb_disable,
+ .is_enabled = at91sam9n12_clk_usb_is_enabled,
+ .recalc_rate = at91sam9x5_clk_usb_recalc_rate,
+ .determine_rate = at91sam9x5_clk_usb_determine_rate,
+ .set_rate = at91sam9x5_clk_usb_set_rate,
+};
+
+static struct clk_hw * __init
+_at91sam9x5_clk_register_usb(struct regmap *regmap, const char *name,
+ const char **parent_names, u8 num_parents,
+ u32 usbs_mask)
+{
+ struct at91sam9x5_clk_usb *usb;
+ struct clk_hw *hw;
+ struct clk_init_data init;
+ int ret;
+
+ usb = kzalloc(sizeof(*usb), GFP_KERNEL);
+ if (!usb)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &at91sam9x5_usb_ops;
+ init.parent_names = parent_names;
+ init.num_parents = num_parents;
+ init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE |
+ CLK_SET_RATE_PARENT;
+
+ usb->hw.init = &init;
+ usb->regmap = regmap;
+ usb->usbs_mask = usbs_mask;
+ usb->num_parents = num_parents;
+
+ hw = &usb->hw;
+ ret = clk_hw_register(NULL, &usb->hw);
+ if (ret) {
+ kfree(usb);
+ hw = ERR_PTR(ret);
+ }
+
+ return hw;
+}
+
+struct clk_hw * __init
+at91sam9x5_clk_register_usb(struct regmap *regmap, const char *name,
+ const char **parent_names, u8 num_parents)
+{
+ return _at91sam9x5_clk_register_usb(regmap, name, parent_names,
+ num_parents, SAM9X5_USBS_MASK);
+}
+
+struct clk_hw * __init
+sam9x60_clk_register_usb(struct regmap *regmap, const char *name,
+ const char **parent_names, u8 num_parents)
+{
+ return _at91sam9x5_clk_register_usb(regmap, name, parent_names,
+ num_parents, SAM9X60_USBS_MASK);
+}
+
+struct clk_hw * __init
+at91sam9n12_clk_register_usb(struct regmap *regmap, const char *name,
+ const char *parent_name)
+{
+ struct at91sam9x5_clk_usb *usb;
+ struct clk_hw *hw;
+ struct clk_init_data init;
+ int ret;
+
+ usb = kzalloc(sizeof(*usb), GFP_KERNEL);
+ if (!usb)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &at91sam9n12_usb_ops;
+ init.parent_names = &parent_name;
+ init.num_parents = 1;
+ init.flags = CLK_SET_RATE_GATE | CLK_SET_RATE_PARENT;
+
+ usb->hw.init = &init;
+ usb->regmap = regmap;
+
+ hw = &usb->hw;
+ ret = clk_hw_register(NULL, &usb->hw);
+ if (ret) {
+ kfree(usb);
+ hw = ERR_PTR(ret);
+ }
+
+ return hw;
+}
+
+static unsigned long at91rm9200_clk_usb_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct at91rm9200_clk_usb *usb = to_at91rm9200_clk_usb(hw);
+ unsigned int pllbr;
+ u8 usbdiv;
+
+ regmap_read(usb->regmap, AT91_CKGR_PLLBR, &pllbr);
+
+ usbdiv = (pllbr & AT91_PMC_USBDIV) >> RM9200_USB_DIV_SHIFT;
+ if (usb->divisors[usbdiv])
+ return parent_rate / usb->divisors[usbdiv];
+
+ return 0;
+}
+
+static long at91rm9200_clk_usb_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ struct at91rm9200_clk_usb *usb = to_at91rm9200_clk_usb(hw);
+ struct clk_hw *parent = clk_hw_get_parent(hw);
+ unsigned long bestrate = 0;
+ int bestdiff = -1;
+ unsigned long tmprate;
+ int tmpdiff;
+ int i = 0;
+
+ for (i = 0; i < RM9200_USB_DIV_TAB_SIZE; i++) {
+ unsigned long tmp_parent_rate;
+
+ if (!usb->divisors[i])
+ continue;
+
+ tmp_parent_rate = rate * usb->divisors[i];
+ tmp_parent_rate = clk_hw_round_rate(parent, tmp_parent_rate);
+ tmprate = DIV_ROUND_CLOSEST(tmp_parent_rate, usb->divisors[i]);
+ if (tmprate < rate)
+ tmpdiff = rate - tmprate;
+ else
+ tmpdiff = tmprate - rate;
+
+ if (bestdiff < 0 || bestdiff > tmpdiff) {
+ bestrate = tmprate;
+ bestdiff = tmpdiff;
+ *parent_rate = tmp_parent_rate;
+ }
+
+ if (!bestdiff)
+ break;
+ }
+
+ return bestrate;
+}
+
+static int at91rm9200_clk_usb_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ int i;
+ struct at91rm9200_clk_usb *usb = to_at91rm9200_clk_usb(hw);
+ unsigned long div;
+
+ if (!rate)
+ return -EINVAL;
+
+ div = DIV_ROUND_CLOSEST(parent_rate, rate);
+
+ for (i = 0; i < RM9200_USB_DIV_TAB_SIZE; i++) {
+ if (usb->divisors[i] == div) {
+ regmap_update_bits(usb->regmap, AT91_CKGR_PLLBR,
+ AT91_PMC_USBDIV,
+ i << RM9200_USB_DIV_SHIFT);
+
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static const struct clk_ops at91rm9200_usb_ops = {
+ .recalc_rate = at91rm9200_clk_usb_recalc_rate,
+ .round_rate = at91rm9200_clk_usb_round_rate,
+ .set_rate = at91rm9200_clk_usb_set_rate,
+};
+
+struct clk_hw * __init
+at91rm9200_clk_register_usb(struct regmap *regmap, const char *name,
+ const char *parent_name, const u32 *divisors)
+{
+ struct at91rm9200_clk_usb *usb;
+ struct clk_hw *hw;
+ struct clk_init_data init;
+ int ret;
+
+ usb = kzalloc(sizeof(*usb), GFP_KERNEL);
+ if (!usb)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &at91rm9200_usb_ops;
+ init.parent_names = &parent_name;
+ init.num_parents = 1;
+ init.flags = CLK_SET_RATE_PARENT;
+
+ usb->hw.init = &init;
+ usb->regmap = regmap;
+ memcpy(usb->divisors, divisors, sizeof(usb->divisors));
+
+ hw = &usb->hw;
+ ret = clk_hw_register(NULL, &usb->hw);
+ if (ret) {
+ kfree(usb);
+ hw = ERR_PTR(ret);
+ }
+
+ return hw;
+}
diff --git a/drivers/clk/at91/clk-utmi.c b/drivers/clk/at91/clk-utmi.c
new file mode 100644
index 000000000..a22c10d9a
--- /dev/null
+++ b/drivers/clk/at91/clk-utmi.c
@@ -0,0 +1,286 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com>
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/clk/at91_pmc.h>
+#include <linux/of.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+#include <soc/at91/atmel-sfr.h>
+
+#include "pmc.h"
+
+/*
+ * The purpose of this clock is to generate a 480 MHz signal. A different
+ * rate can't be configured.
+ */
+#define UTMI_RATE 480000000
+
+struct clk_utmi {
+ struct clk_hw hw;
+ struct regmap *regmap_pmc;
+ struct regmap *regmap_sfr;
+ struct at91_clk_pms pms;
+};
+
+#define to_clk_utmi(hw) container_of(hw, struct clk_utmi, hw)
+
+static inline bool clk_utmi_ready(struct regmap *regmap)
+{
+ unsigned int status;
+
+ regmap_read(regmap, AT91_PMC_SR, &status);
+
+ return status & AT91_PMC_LOCKU;
+}
+
+static int clk_utmi_prepare(struct clk_hw *hw)
+{
+ struct clk_hw *hw_parent;
+ struct clk_utmi *utmi = to_clk_utmi(hw);
+ unsigned int uckr = AT91_PMC_UPLLEN | AT91_PMC_UPLLCOUNT |
+ AT91_PMC_BIASEN;
+ unsigned int utmi_ref_clk_freq;
+ unsigned long parent_rate;
+
+ /*
+ * 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.
+ */
+ hw_parent = clk_hw_get_parent(hw);
+ parent_rate = clk_hw_get_rate(hw_parent);
+
+ switch (parent_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:
+ pr_err("UTMICK: unsupported mainck rate\n");
+ 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) {
+ pr_err("UTMICK: sfr node required\n");
+ return -EINVAL;
+ }
+
+ regmap_update_bits(utmi->regmap_pmc, AT91_CKGR_UCKR, uckr, uckr);
+
+ while (!clk_utmi_ready(utmi->regmap_pmc))
+ cpu_relax();
+
+ return 0;
+}
+
+static int clk_utmi_is_prepared(struct clk_hw *hw)
+{
+ struct clk_utmi *utmi = to_clk_utmi(hw);
+
+ return clk_utmi_ready(utmi->regmap_pmc);
+}
+
+static void clk_utmi_unprepare(struct clk_hw *hw)
+{
+ struct clk_utmi *utmi = to_clk_utmi(hw);
+
+ regmap_update_bits(utmi->regmap_pmc, AT91_CKGR_UCKR,
+ AT91_PMC_UPLLEN, 0);
+}
+
+static unsigned long clk_utmi_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ /* UTMI clk rate is fixed. */
+ return UTMI_RATE;
+}
+
+static int clk_utmi_save_context(struct clk_hw *hw)
+{
+ struct clk_utmi *utmi = to_clk_utmi(hw);
+
+ utmi->pms.status = clk_utmi_is_prepared(hw);
+
+ return 0;
+}
+
+static void clk_utmi_restore_context(struct clk_hw *hw)
+{
+ struct clk_utmi *utmi = to_clk_utmi(hw);
+
+ if (utmi->pms.status)
+ clk_utmi_prepare(hw);
+}
+
+static const struct clk_ops utmi_ops = {
+ .prepare = clk_utmi_prepare,
+ .unprepare = clk_utmi_unprepare,
+ .is_prepared = clk_utmi_is_prepared,
+ .recalc_rate = clk_utmi_recalc_rate,
+ .save_context = clk_utmi_save_context,
+ .restore_context = clk_utmi_restore_context,
+};
+
+static struct clk_hw * __init
+at91_clk_register_utmi_internal(struct regmap *regmap_pmc,
+ struct regmap *regmap_sfr,
+ const char *name, const char *parent_name,
+ const struct clk_ops *ops, unsigned long flags)
+{
+ struct clk_utmi *utmi;
+ struct clk_hw *hw;
+ struct clk_init_data init;
+ int ret;
+
+ utmi = kzalloc(sizeof(*utmi), GFP_KERNEL);
+ if (!utmi)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = ops;
+ init.parent_names = parent_name ? &parent_name : NULL;
+ init.num_parents = parent_name ? 1 : 0;
+ init.flags = flags;
+
+ utmi->hw.init = &init;
+ utmi->regmap_pmc = regmap_pmc;
+ utmi->regmap_sfr = regmap_sfr;
+
+ hw = &utmi->hw;
+ ret = clk_hw_register(NULL, &utmi->hw);
+ if (ret) {
+ kfree(utmi);
+ hw = ERR_PTR(ret);
+ }
+
+ return hw;
+}
+
+struct clk_hw * __init
+at91_clk_register_utmi(struct regmap *regmap_pmc, struct regmap *regmap_sfr,
+ const char *name, const char *parent_name)
+{
+ return at91_clk_register_utmi_internal(regmap_pmc, regmap_sfr, name,
+ parent_name, &utmi_ops, CLK_SET_RATE_GATE);
+}
+
+static int clk_utmi_sama7g5_prepare(struct clk_hw *hw)
+{
+ struct clk_utmi *utmi = to_clk_utmi(hw);
+ struct clk_hw *hw_parent;
+ unsigned long parent_rate;
+ unsigned int val;
+
+ hw_parent = clk_hw_get_parent(hw);
+ parent_rate = clk_hw_get_rate(hw_parent);
+
+ switch (parent_rate) {
+ case 16000000:
+ val = 0;
+ break;
+ case 20000000:
+ val = 2;
+ break;
+ case 24000000:
+ val = 3;
+ break;
+ case 32000000:
+ val = 5;
+ break;
+ default:
+ pr_err("UTMICK: unsupported main_xtal rate\n");
+ return -EINVAL;
+ }
+
+ regmap_write(utmi->regmap_pmc, AT91_PMC_XTALF, val);
+
+ return 0;
+
+}
+
+static int clk_utmi_sama7g5_is_prepared(struct clk_hw *hw)
+{
+ struct clk_utmi *utmi = to_clk_utmi(hw);
+ struct clk_hw *hw_parent;
+ unsigned long parent_rate;
+ unsigned int val;
+
+ hw_parent = clk_hw_get_parent(hw);
+ parent_rate = clk_hw_get_rate(hw_parent);
+
+ regmap_read(utmi->regmap_pmc, AT91_PMC_XTALF, &val);
+ switch (val & 0x7) {
+ case 0:
+ if (parent_rate == 16000000)
+ return 1;
+ break;
+ case 2:
+ if (parent_rate == 20000000)
+ return 1;
+ break;
+ case 3:
+ if (parent_rate == 24000000)
+ return 1;
+ break;
+ case 5:
+ if (parent_rate == 32000000)
+ return 1;
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int clk_utmi_sama7g5_save_context(struct clk_hw *hw)
+{
+ struct clk_utmi *utmi = to_clk_utmi(hw);
+
+ utmi->pms.status = clk_utmi_sama7g5_is_prepared(hw);
+
+ return 0;
+}
+
+static void clk_utmi_sama7g5_restore_context(struct clk_hw *hw)
+{
+ struct clk_utmi *utmi = to_clk_utmi(hw);
+
+ if (utmi->pms.status)
+ clk_utmi_sama7g5_prepare(hw);
+}
+
+static const struct clk_ops sama7g5_utmi_ops = {
+ .prepare = clk_utmi_sama7g5_prepare,
+ .is_prepared = clk_utmi_sama7g5_is_prepared,
+ .recalc_rate = clk_utmi_recalc_rate,
+ .save_context = clk_utmi_sama7g5_save_context,
+ .restore_context = clk_utmi_sama7g5_restore_context,
+};
+
+struct clk_hw * __init
+at91_clk_sama7g5_register_utmi(struct regmap *regmap_pmc, const char *name,
+ const char *parent_name)
+{
+ return at91_clk_register_utmi_internal(regmap_pmc, NULL, name,
+ parent_name, &sama7g5_utmi_ops, 0);
+}
diff --git a/drivers/clk/at91/dt-compat.c b/drivers/clk/at91/dt-compat.c
new file mode 100644
index 000000000..85a964cb2
--- /dev/null
+++ b/drivers/clk/at91/dt-compat.c
@@ -0,0 +1,1044 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/clk-provider.h>
+#include <linux/clk/at91_pmc.h>
+#include <linux/of.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#include "pmc.h"
+
+#define MASTER_SOURCE_MAX 4
+
+#define PERIPHERAL_AT91RM9200 0
+#define PERIPHERAL_AT91SAM9X5 1
+
+#define PERIPHERAL_MAX 64
+
+#define PERIPHERAL_ID_MIN 2
+
+#define PROG_SOURCE_MAX 5
+#define PROG_ID_MAX 7
+
+#define SYSTEM_MAX_ID 31
+
+#define GCK_INDEX_DT_AUDIO_PLL 5
+
+static DEFINE_SPINLOCK(mck_lock);
+
+#ifdef CONFIG_HAVE_AT91_AUDIO_PLL
+static void __init of_sama5d2_clk_audio_pll_frac_setup(struct device_node *np)
+{
+ struct clk_hw *hw;
+ const char *name = np->name;
+ const char *parent_name;
+ struct regmap *regmap;
+ struct device_node *parent_np;
+
+ parent_np = of_get_parent(np);
+ regmap = syscon_node_to_regmap(parent_np);
+ of_node_put(parent_np);
+ if (IS_ERR(regmap))
+ return;
+
+ parent_name = of_clk_get_parent_name(np, 0);
+
+ hw = at91_clk_register_audio_pll_frac(regmap, name, parent_name);
+ if (IS_ERR(hw))
+ return;
+
+ of_clk_add_hw_provider(np, of_clk_hw_simple_get, hw);
+}
+CLK_OF_DECLARE(of_sama5d2_clk_audio_pll_frac_setup,
+ "atmel,sama5d2-clk-audio-pll-frac",
+ of_sama5d2_clk_audio_pll_frac_setup);
+
+static void __init of_sama5d2_clk_audio_pll_pad_setup(struct device_node *np)
+{
+ struct clk_hw *hw;
+ const char *name = np->name;
+ const char *parent_name;
+ struct regmap *regmap;
+ struct device_node *parent_np;
+
+ parent_np = of_get_parent(np);
+ regmap = syscon_node_to_regmap(parent_np);
+ of_node_put(parent_np);
+ if (IS_ERR(regmap))
+ return;
+
+ parent_name = of_clk_get_parent_name(np, 0);
+
+ hw = at91_clk_register_audio_pll_pad(regmap, name, parent_name);
+ if (IS_ERR(hw))
+ return;
+
+ of_clk_add_hw_provider(np, of_clk_hw_simple_get, hw);
+}
+CLK_OF_DECLARE(of_sama5d2_clk_audio_pll_pad_setup,
+ "atmel,sama5d2-clk-audio-pll-pad",
+ of_sama5d2_clk_audio_pll_pad_setup);
+
+static void __init of_sama5d2_clk_audio_pll_pmc_setup(struct device_node *np)
+{
+ struct clk_hw *hw;
+ const char *name = np->name;
+ const char *parent_name;
+ struct regmap *regmap;
+ struct device_node *parent_np;
+
+ parent_np = of_get_parent(np);
+ regmap = syscon_node_to_regmap(parent_np);
+ of_node_put(parent_np);
+ if (IS_ERR(regmap))
+ return;
+
+ parent_name = of_clk_get_parent_name(np, 0);
+
+ hw = at91_clk_register_audio_pll_pmc(regmap, name, parent_name);
+ if (IS_ERR(hw))
+ return;
+
+ of_clk_add_hw_provider(np, of_clk_hw_simple_get, hw);
+}
+CLK_OF_DECLARE(of_sama5d2_clk_audio_pll_pmc_setup,
+ "atmel,sama5d2-clk-audio-pll-pmc",
+ of_sama5d2_clk_audio_pll_pmc_setup);
+#endif /* CONFIG_HAVE_AT91_AUDIO_PLL */
+
+static const struct clk_pcr_layout dt_pcr_layout = {
+ .offset = 0x10c,
+ .cmd = BIT(12),
+ .pid_mask = GENMASK(5, 0),
+ .div_mask = GENMASK(17, 16),
+ .gckcss_mask = GENMASK(10, 8),
+};
+
+#ifdef CONFIG_HAVE_AT91_GENERATED_CLK
+#define GENERATED_SOURCE_MAX 6
+
+#define GCK_ID_I2S0 54
+#define GCK_ID_I2S1 55
+#define GCK_ID_CLASSD 59
+
+static void __init of_sama5d2_clk_generated_setup(struct device_node *np)
+{
+ int num;
+ u32 id;
+ const char *name;
+ struct clk_hw *hw;
+ unsigned int num_parents;
+ const char *parent_names[GENERATED_SOURCE_MAX];
+ struct device_node *gcknp, *parent_np;
+ struct clk_range range = CLK_RANGE(0, 0);
+ struct regmap *regmap;
+
+ num_parents = of_clk_get_parent_count(np);
+ if (num_parents == 0 || num_parents > GENERATED_SOURCE_MAX)
+ return;
+
+ of_clk_parent_fill(np, parent_names, num_parents);
+
+ num = of_get_child_count(np);
+ if (!num || num > PERIPHERAL_MAX)
+ return;
+
+ parent_np = of_get_parent(np);
+ regmap = syscon_node_to_regmap(parent_np);
+ of_node_put(parent_np);
+ if (IS_ERR(regmap))
+ return;
+
+ for_each_child_of_node(np, gcknp) {
+ int chg_pid = INT_MIN;
+
+ if (of_property_read_u32(gcknp, "reg", &id))
+ continue;
+
+ if (id < PERIPHERAL_ID_MIN || id >= PERIPHERAL_MAX)
+ continue;
+
+ if (of_property_read_string(np, "clock-output-names", &name))
+ name = gcknp->name;
+
+ of_at91_get_clk_range(gcknp, "atmel,clk-output-range",
+ &range);
+
+ if (of_device_is_compatible(np, "atmel,sama5d2-clk-generated") &&
+ (id == GCK_ID_I2S0 || id == GCK_ID_I2S1 ||
+ id == GCK_ID_CLASSD))
+ chg_pid = GCK_INDEX_DT_AUDIO_PLL;
+
+ hw = at91_clk_register_generated(regmap, &pmc_pcr_lock,
+ &dt_pcr_layout, name,
+ parent_names, NULL,
+ num_parents, id, &range,
+ chg_pid);
+ if (IS_ERR(hw))
+ continue;
+
+ of_clk_add_hw_provider(gcknp, of_clk_hw_simple_get, hw);
+ }
+}
+CLK_OF_DECLARE(of_sama5d2_clk_generated_setup, "atmel,sama5d2-clk-generated",
+ of_sama5d2_clk_generated_setup);
+#endif /* CONFIG_HAVE_AT91_GENERATED_CLK */
+
+#ifdef CONFIG_HAVE_AT91_H32MX
+static void __init of_sama5d4_clk_h32mx_setup(struct device_node *np)
+{
+ struct clk_hw *hw;
+ const char *name = np->name;
+ const char *parent_name;
+ struct regmap *regmap;
+ struct device_node *parent_np;
+
+ parent_np = of_get_parent(np);
+ regmap = syscon_node_to_regmap(parent_np);
+ of_node_put(parent_np);
+ if (IS_ERR(regmap))
+ return;
+
+ parent_name = of_clk_get_parent_name(np, 0);
+
+ hw = at91_clk_register_h32mx(regmap, name, parent_name);
+ if (IS_ERR(hw))
+ return;
+
+ of_clk_add_hw_provider(np, of_clk_hw_simple_get, hw);
+}
+CLK_OF_DECLARE(of_sama5d4_clk_h32mx_setup, "atmel,sama5d4-clk-h32mx",
+ of_sama5d4_clk_h32mx_setup);
+#endif /* CONFIG_HAVE_AT91_H32MX */
+
+#ifdef CONFIG_HAVE_AT91_I2S_MUX_CLK
+#define I2S_BUS_NR 2
+
+static void __init of_sama5d2_clk_i2s_mux_setup(struct device_node *np)
+{
+ struct regmap *regmap_sfr;
+ u8 bus_id;
+ const char *parent_names[2];
+ struct device_node *i2s_mux_np;
+ struct clk_hw *hw;
+ int ret;
+
+ regmap_sfr = syscon_regmap_lookup_by_compatible("atmel,sama5d2-sfr");
+ if (IS_ERR(regmap_sfr))
+ return;
+
+ for_each_child_of_node(np, i2s_mux_np) {
+ if (of_property_read_u8(i2s_mux_np, "reg", &bus_id))
+ continue;
+
+ if (bus_id > I2S_BUS_NR)
+ continue;
+
+ ret = of_clk_parent_fill(i2s_mux_np, parent_names, 2);
+ if (ret != 2)
+ continue;
+
+ hw = at91_clk_i2s_mux_register(regmap_sfr, i2s_mux_np->name,
+ parent_names, 2, bus_id);
+ if (IS_ERR(hw))
+ continue;
+
+ of_clk_add_hw_provider(i2s_mux_np, of_clk_hw_simple_get, hw);
+ }
+}
+CLK_OF_DECLARE(sama5d2_clk_i2s_mux, "atmel,sama5d2-clk-i2s-mux",
+ of_sama5d2_clk_i2s_mux_setup);
+#endif /* CONFIG_HAVE_AT91_I2S_MUX_CLK */
+
+static void __init of_at91rm9200_clk_main_osc_setup(struct device_node *np)
+{
+ struct clk_hw *hw;
+ const char *name = np->name;
+ const char *parent_name;
+ struct regmap *regmap;
+ bool bypass;
+ struct device_node *parent_np;
+
+ of_property_read_string(np, "clock-output-names", &name);
+ bypass = of_property_read_bool(np, "atmel,osc-bypass");
+ parent_name = of_clk_get_parent_name(np, 0);
+
+ parent_np = of_get_parent(np);
+ regmap = syscon_node_to_regmap(parent_np);
+ of_node_put(parent_np);
+ if (IS_ERR(regmap))
+ return;
+
+ hw = at91_clk_register_main_osc(regmap, name, parent_name, bypass);
+ if (IS_ERR(hw))
+ return;
+
+ of_clk_add_hw_provider(np, of_clk_hw_simple_get, hw);
+}
+CLK_OF_DECLARE(at91rm9200_clk_main_osc, "atmel,at91rm9200-clk-main-osc",
+ of_at91rm9200_clk_main_osc_setup);
+
+static void __init of_at91sam9x5_clk_main_rc_osc_setup(struct device_node *np)
+{
+ struct clk_hw *hw;
+ u32 frequency = 0;
+ u32 accuracy = 0;
+ const char *name = np->name;
+ struct regmap *regmap;
+ struct device_node *parent_np;
+
+ of_property_read_string(np, "clock-output-names", &name);
+ of_property_read_u32(np, "clock-frequency", &frequency);
+ of_property_read_u32(np, "clock-accuracy", &accuracy);
+
+ parent_np = of_get_parent(np);
+ regmap = syscon_node_to_regmap(parent_np);
+ of_node_put(parent_np);
+ if (IS_ERR(regmap))
+ return;
+
+ hw = at91_clk_register_main_rc_osc(regmap, name, frequency, accuracy);
+ if (IS_ERR(hw))
+ return;
+
+ of_clk_add_hw_provider(np, of_clk_hw_simple_get, hw);
+}
+CLK_OF_DECLARE(at91sam9x5_clk_main_rc_osc, "atmel,at91sam9x5-clk-main-rc-osc",
+ of_at91sam9x5_clk_main_rc_osc_setup);
+
+static void __init of_at91rm9200_clk_main_setup(struct device_node *np)
+{
+ struct clk_hw *hw;
+ const char *parent_name;
+ const char *name = np->name;
+ struct regmap *regmap;
+ struct device_node *parent_np;
+
+ parent_name = of_clk_get_parent_name(np, 0);
+ of_property_read_string(np, "clock-output-names", &name);
+
+ parent_np = of_get_parent(np);
+ regmap = syscon_node_to_regmap(parent_np);
+ of_node_put(parent_np);
+ if (IS_ERR(regmap))
+ return;
+
+ hw = at91_clk_register_rm9200_main(regmap, name, parent_name);
+ if (IS_ERR(hw))
+ return;
+
+ of_clk_add_hw_provider(np, of_clk_hw_simple_get, hw);
+}
+CLK_OF_DECLARE(at91rm9200_clk_main, "atmel,at91rm9200-clk-main",
+ of_at91rm9200_clk_main_setup);
+
+static void __init of_at91sam9x5_clk_main_setup(struct device_node *np)
+{
+ struct clk_hw *hw;
+ const char *parent_names[2];
+ unsigned int num_parents;
+ const char *name = np->name;
+ struct regmap *regmap;
+ struct device_node *parent_np;
+
+ num_parents = of_clk_get_parent_count(np);
+ if (num_parents == 0 || num_parents > 2)
+ return;
+
+ of_clk_parent_fill(np, parent_names, num_parents);
+ parent_np = of_get_parent(np);
+ regmap = syscon_node_to_regmap(parent_np);
+ of_node_put(parent_np);
+ if (IS_ERR(regmap))
+ return;
+
+ of_property_read_string(np, "clock-output-names", &name);
+
+ hw = at91_clk_register_sam9x5_main(regmap, name, parent_names,
+ num_parents);
+ if (IS_ERR(hw))
+ return;
+
+ of_clk_add_hw_provider(np, of_clk_hw_simple_get, hw);
+}
+CLK_OF_DECLARE(at91sam9x5_clk_main, "atmel,at91sam9x5-clk-main",
+ of_at91sam9x5_clk_main_setup);
+
+static struct clk_master_characteristics * __init
+of_at91_clk_master_get_characteristics(struct device_node *np)
+{
+ struct clk_master_characteristics *characteristics;
+
+ characteristics = kzalloc(sizeof(*characteristics), GFP_KERNEL);
+ if (!characteristics)
+ return NULL;
+
+ if (of_at91_get_clk_range(np, "atmel,clk-output-range", &characteristics->output))
+ goto out_free_characteristics;
+
+ of_property_read_u32_array(np, "atmel,clk-divisors",
+ characteristics->divisors, 4);
+
+ characteristics->have_div3_pres =
+ of_property_read_bool(np, "atmel,master-clk-have-div3-pres");
+
+ return characteristics;
+
+out_free_characteristics:
+ kfree(characteristics);
+ return NULL;
+}
+
+static void __init
+of_at91_clk_master_setup(struct device_node *np,
+ const struct clk_master_layout *layout)
+{
+ struct clk_hw *hw;
+ unsigned int num_parents;
+ const char *parent_names[MASTER_SOURCE_MAX];
+ const char *name = np->name;
+ struct clk_master_characteristics *characteristics;
+ struct regmap *regmap;
+ struct device_node *parent_np;
+
+ num_parents = of_clk_get_parent_count(np);
+ if (num_parents == 0 || num_parents > MASTER_SOURCE_MAX)
+ return;
+
+ of_clk_parent_fill(np, parent_names, num_parents);
+
+ of_property_read_string(np, "clock-output-names", &name);
+
+ characteristics = of_at91_clk_master_get_characteristics(np);
+ if (!characteristics)
+ return;
+
+ parent_np = of_get_parent(np);
+ regmap = syscon_node_to_regmap(parent_np);
+ of_node_put(parent_np);
+ if (IS_ERR(regmap))
+ return;
+
+ hw = at91_clk_register_master_pres(regmap, "masterck_pres", num_parents,
+ parent_names, layout,
+ characteristics, &mck_lock);
+ if (IS_ERR(hw))
+ goto out_free_characteristics;
+
+ hw = at91_clk_register_master_div(regmap, name, "masterck_pres",
+ layout, characteristics,
+ &mck_lock, CLK_SET_RATE_GATE, 0);
+ if (IS_ERR(hw))
+ goto out_free_characteristics;
+
+ of_clk_add_hw_provider(np, of_clk_hw_simple_get, hw);
+ return;
+
+out_free_characteristics:
+ kfree(characteristics);
+}
+
+static void __init of_at91rm9200_clk_master_setup(struct device_node *np)
+{
+ of_at91_clk_master_setup(np, &at91rm9200_master_layout);
+}
+CLK_OF_DECLARE(at91rm9200_clk_master, "atmel,at91rm9200-clk-master",
+ of_at91rm9200_clk_master_setup);
+
+static void __init of_at91sam9x5_clk_master_setup(struct device_node *np)
+{
+ of_at91_clk_master_setup(np, &at91sam9x5_master_layout);
+}
+CLK_OF_DECLARE(at91sam9x5_clk_master, "atmel,at91sam9x5-clk-master",
+ of_at91sam9x5_clk_master_setup);
+
+static void __init
+of_at91_clk_periph_setup(struct device_node *np, u8 type)
+{
+ int num;
+ u32 id;
+ struct clk_hw *hw;
+ const char *parent_name;
+ const char *name;
+ struct device_node *periphclknp;
+ struct regmap *regmap;
+ struct device_node *parent_np;
+
+ parent_name = of_clk_get_parent_name(np, 0);
+ if (!parent_name)
+ return;
+
+ num = of_get_child_count(np);
+ if (!num || num > PERIPHERAL_MAX)
+ return;
+
+ parent_np = of_get_parent(np);
+ regmap = syscon_node_to_regmap(parent_np);
+ of_node_put(parent_np);
+ if (IS_ERR(regmap))
+ return;
+
+ for_each_child_of_node(np, periphclknp) {
+ if (of_property_read_u32(periphclknp, "reg", &id))
+ continue;
+
+ if (id >= PERIPHERAL_MAX)
+ continue;
+
+ if (of_property_read_string(np, "clock-output-names", &name))
+ name = periphclknp->name;
+
+ if (type == PERIPHERAL_AT91RM9200) {
+ hw = at91_clk_register_peripheral(regmap, name,
+ parent_name, id);
+ } else {
+ struct clk_range range = CLK_RANGE(0, 0);
+
+ of_at91_get_clk_range(periphclknp,
+ "atmel,clk-output-range",
+ &range);
+
+ hw = at91_clk_register_sam9x5_peripheral(regmap,
+ &pmc_pcr_lock,
+ &dt_pcr_layout,
+ name,
+ parent_name,
+ id, &range,
+ INT_MIN);
+ }
+
+ if (IS_ERR(hw))
+ continue;
+
+ of_clk_add_hw_provider(periphclknp, of_clk_hw_simple_get, hw);
+ }
+}
+
+static void __init of_at91rm9200_clk_periph_setup(struct device_node *np)
+{
+ of_at91_clk_periph_setup(np, PERIPHERAL_AT91RM9200);
+}
+CLK_OF_DECLARE(at91rm9200_clk_periph, "atmel,at91rm9200-clk-peripheral",
+ of_at91rm9200_clk_periph_setup);
+
+static void __init of_at91sam9x5_clk_periph_setup(struct device_node *np)
+{
+ of_at91_clk_periph_setup(np, PERIPHERAL_AT91SAM9X5);
+}
+CLK_OF_DECLARE(at91sam9x5_clk_periph, "atmel,at91sam9x5-clk-peripheral",
+ of_at91sam9x5_clk_periph_setup);
+
+static struct clk_pll_characteristics * __init
+of_at91_clk_pll_get_characteristics(struct device_node *np)
+{
+ int i;
+ int offset;
+ u32 tmp;
+ int num_output;
+ u32 num_cells;
+ struct clk_range input;
+ struct clk_range *output;
+ u8 *out = NULL;
+ u16 *icpll = NULL;
+ struct clk_pll_characteristics *characteristics;
+
+ if (of_at91_get_clk_range(np, "atmel,clk-input-range", &input))
+ return NULL;
+
+ if (of_property_read_u32(np, "#atmel,pll-clk-output-range-cells",
+ &num_cells))
+ return NULL;
+
+ if (num_cells < 2 || num_cells > 4)
+ return NULL;
+
+ if (!of_get_property(np, "atmel,pll-clk-output-ranges", &tmp))
+ return NULL;
+ num_output = tmp / (sizeof(u32) * num_cells);
+
+ characteristics = kzalloc(sizeof(*characteristics), GFP_KERNEL);
+ if (!characteristics)
+ return NULL;
+
+ output = kcalloc(num_output, sizeof(*output), GFP_KERNEL);
+ if (!output)
+ goto out_free_characteristics;
+
+ if (num_cells > 2) {
+ out = kcalloc(num_output, sizeof(*out), GFP_KERNEL);
+ if (!out)
+ goto out_free_output;
+ }
+
+ if (num_cells > 3) {
+ icpll = kcalloc(num_output, sizeof(*icpll), GFP_KERNEL);
+ if (!icpll)
+ goto out_free_output;
+ }
+
+ for (i = 0; i < num_output; i++) {
+ offset = i * num_cells;
+ if (of_property_read_u32_index(np,
+ "atmel,pll-clk-output-ranges",
+ offset, &tmp))
+ goto out_free_output;
+ output[i].min = tmp;
+ if (of_property_read_u32_index(np,
+ "atmel,pll-clk-output-ranges",
+ offset + 1, &tmp))
+ goto out_free_output;
+ output[i].max = tmp;
+
+ if (num_cells == 2)
+ continue;
+
+ if (of_property_read_u32_index(np,
+ "atmel,pll-clk-output-ranges",
+ offset + 2, &tmp))
+ goto out_free_output;
+ out[i] = tmp;
+
+ if (num_cells == 3)
+ continue;
+
+ if (of_property_read_u32_index(np,
+ "atmel,pll-clk-output-ranges",
+ offset + 3, &tmp))
+ goto out_free_output;
+ icpll[i] = tmp;
+ }
+
+ characteristics->input = input;
+ characteristics->num_output = num_output;
+ characteristics->output = output;
+ characteristics->out = out;
+ characteristics->icpll = icpll;
+ return characteristics;
+
+out_free_output:
+ kfree(icpll);
+ kfree(out);
+ kfree(output);
+out_free_characteristics:
+ kfree(characteristics);
+ return NULL;
+}
+
+static void __init
+of_at91_clk_pll_setup(struct device_node *np,
+ const struct clk_pll_layout *layout)
+{
+ u32 id;
+ struct clk_hw *hw;
+ struct regmap *regmap;
+ const char *parent_name;
+ const char *name = np->name;
+ struct device_node *parent_np;
+ struct clk_pll_characteristics *characteristics;
+
+ if (of_property_read_u32(np, "reg", &id))
+ return;
+
+ parent_name = of_clk_get_parent_name(np, 0);
+
+ of_property_read_string(np, "clock-output-names", &name);
+
+ parent_np = of_get_parent(np);
+ regmap = syscon_node_to_regmap(parent_np);
+ of_node_put(parent_np);
+ if (IS_ERR(regmap))
+ return;
+
+ characteristics = of_at91_clk_pll_get_characteristics(np);
+ if (!characteristics)
+ return;
+
+ hw = at91_clk_register_pll(regmap, name, parent_name, id, layout,
+ characteristics);
+ if (IS_ERR(hw))
+ goto out_free_characteristics;
+
+ of_clk_add_hw_provider(np, of_clk_hw_simple_get, hw);
+ return;
+
+out_free_characteristics:
+ kfree(characteristics);
+}
+
+static void __init of_at91rm9200_clk_pll_setup(struct device_node *np)
+{
+ of_at91_clk_pll_setup(np, &at91rm9200_pll_layout);
+}
+CLK_OF_DECLARE(at91rm9200_clk_pll, "atmel,at91rm9200-clk-pll",
+ of_at91rm9200_clk_pll_setup);
+
+static void __init of_at91sam9g45_clk_pll_setup(struct device_node *np)
+{
+ of_at91_clk_pll_setup(np, &at91sam9g45_pll_layout);
+}
+CLK_OF_DECLARE(at91sam9g45_clk_pll, "atmel,at91sam9g45-clk-pll",
+ of_at91sam9g45_clk_pll_setup);
+
+static void __init of_at91sam9g20_clk_pllb_setup(struct device_node *np)
+{
+ of_at91_clk_pll_setup(np, &at91sam9g20_pllb_layout);
+}
+CLK_OF_DECLARE(at91sam9g20_clk_pllb, "atmel,at91sam9g20-clk-pllb",
+ of_at91sam9g20_clk_pllb_setup);
+
+static void __init of_sama5d3_clk_pll_setup(struct device_node *np)
+{
+ of_at91_clk_pll_setup(np, &sama5d3_pll_layout);
+}
+CLK_OF_DECLARE(sama5d3_clk_pll, "atmel,sama5d3-clk-pll",
+ of_sama5d3_clk_pll_setup);
+
+static void __init
+of_at91sam9x5_clk_plldiv_setup(struct device_node *np)
+{
+ struct clk_hw *hw;
+ const char *parent_name;
+ const char *name = np->name;
+ struct regmap *regmap;
+ struct device_node *parent_np;
+
+ parent_name = of_clk_get_parent_name(np, 0);
+
+ of_property_read_string(np, "clock-output-names", &name);
+
+ parent_np = of_get_parent(np);
+ regmap = syscon_node_to_regmap(parent_np);
+ of_node_put(parent_np);
+ if (IS_ERR(regmap))
+ return;
+
+ hw = at91_clk_register_plldiv(regmap, name, parent_name);
+ if (IS_ERR(hw))
+ return;
+
+ of_clk_add_hw_provider(np, of_clk_hw_simple_get, hw);
+}
+CLK_OF_DECLARE(at91sam9x5_clk_plldiv, "atmel,at91sam9x5-clk-plldiv",
+ of_at91sam9x5_clk_plldiv_setup);
+
+static void __init
+of_at91_clk_prog_setup(struct device_node *np,
+ const struct clk_programmable_layout *layout,
+ u32 *mux_table)
+{
+ int num;
+ u32 id;
+ struct clk_hw *hw;
+ unsigned int num_parents;
+ const char *parent_names[PROG_SOURCE_MAX];
+ const char *name;
+ struct device_node *progclknp, *parent_np;
+ struct regmap *regmap;
+
+ num_parents = of_clk_get_parent_count(np);
+ if (num_parents == 0 || num_parents > PROG_SOURCE_MAX)
+ return;
+
+ of_clk_parent_fill(np, parent_names, num_parents);
+
+ num = of_get_child_count(np);
+ if (!num || num > (PROG_ID_MAX + 1))
+ return;
+
+ parent_np = of_get_parent(np);
+ regmap = syscon_node_to_regmap(parent_np);
+ of_node_put(parent_np);
+ if (IS_ERR(regmap))
+ return;
+
+ for_each_child_of_node(np, progclknp) {
+ if (of_property_read_u32(progclknp, "reg", &id))
+ continue;
+
+ if (of_property_read_string(np, "clock-output-names", &name))
+ name = progclknp->name;
+
+ hw = at91_clk_register_programmable(regmap, name,
+ parent_names, num_parents,
+ id, layout, mux_table);
+ if (IS_ERR(hw))
+ continue;
+
+ of_clk_add_hw_provider(progclknp, of_clk_hw_simple_get, hw);
+ }
+}
+
+static void __init of_at91rm9200_clk_prog_setup(struct device_node *np)
+{
+ of_at91_clk_prog_setup(np, &at91rm9200_programmable_layout, NULL);
+}
+CLK_OF_DECLARE(at91rm9200_clk_prog, "atmel,at91rm9200-clk-programmable",
+ of_at91rm9200_clk_prog_setup);
+
+static void __init of_at91sam9g45_clk_prog_setup(struct device_node *np)
+{
+ of_at91_clk_prog_setup(np, &at91sam9g45_programmable_layout, NULL);
+}
+CLK_OF_DECLARE(at91sam9g45_clk_prog, "atmel,at91sam9g45-clk-programmable",
+ of_at91sam9g45_clk_prog_setup);
+
+static void __init of_at91sam9x5_clk_prog_setup(struct device_node *np)
+{
+ of_at91_clk_prog_setup(np, &at91sam9x5_programmable_layout, NULL);
+}
+CLK_OF_DECLARE(at91sam9x5_clk_prog, "atmel,at91sam9x5-clk-programmable",
+ of_at91sam9x5_clk_prog_setup);
+
+static void __init of_at91sam9260_clk_slow_setup(struct device_node *np)
+{
+ struct clk_hw *hw;
+ const char *parent_names[2];
+ unsigned int num_parents;
+ const char *name = np->name;
+ struct regmap *regmap;
+ struct device_node *parent_np;
+
+ num_parents = of_clk_get_parent_count(np);
+ if (num_parents != 2)
+ return;
+
+ of_clk_parent_fill(np, parent_names, num_parents);
+ parent_np = of_get_parent(np);
+ regmap = syscon_node_to_regmap(parent_np);
+ of_node_put(parent_np);
+ if (IS_ERR(regmap))
+ return;
+
+ of_property_read_string(np, "clock-output-names", &name);
+
+ hw = at91_clk_register_sam9260_slow(regmap, name, parent_names,
+ num_parents);
+ if (IS_ERR(hw))
+ return;
+
+ of_clk_add_hw_provider(np, of_clk_hw_simple_get, hw);
+}
+CLK_OF_DECLARE(at91sam9260_clk_slow, "atmel,at91sam9260-clk-slow",
+ of_at91sam9260_clk_slow_setup);
+
+#ifdef CONFIG_HAVE_AT91_SMD
+#define SMD_SOURCE_MAX 2
+
+static void __init of_at91sam9x5_clk_smd_setup(struct device_node *np)
+{
+ struct clk_hw *hw;
+ unsigned int num_parents;
+ const char *parent_names[SMD_SOURCE_MAX];
+ const char *name = np->name;
+ struct regmap *regmap;
+ struct device_node *parent_np;
+
+ num_parents = of_clk_get_parent_count(np);
+ if (num_parents == 0 || num_parents > SMD_SOURCE_MAX)
+ return;
+
+ of_clk_parent_fill(np, parent_names, num_parents);
+
+ of_property_read_string(np, "clock-output-names", &name);
+
+ parent_np = of_get_parent(np);
+ regmap = syscon_node_to_regmap(parent_np);
+ of_node_put(parent_np);
+ if (IS_ERR(regmap))
+ return;
+
+ hw = at91sam9x5_clk_register_smd(regmap, name, parent_names,
+ num_parents);
+ if (IS_ERR(hw))
+ return;
+
+ of_clk_add_hw_provider(np, of_clk_hw_simple_get, hw);
+}
+CLK_OF_DECLARE(at91sam9x5_clk_smd, "atmel,at91sam9x5-clk-smd",
+ of_at91sam9x5_clk_smd_setup);
+#endif /* CONFIG_HAVE_AT91_SMD */
+
+static void __init of_at91rm9200_clk_sys_setup(struct device_node *np)
+{
+ int num;
+ u32 id;
+ struct clk_hw *hw;
+ const char *name;
+ struct device_node *sysclknp, *parent_np;
+ const char *parent_name;
+ struct regmap *regmap;
+
+ num = of_get_child_count(np);
+ if (num > (SYSTEM_MAX_ID + 1))
+ return;
+
+ parent_np = of_get_parent(np);
+ regmap = syscon_node_to_regmap(parent_np);
+ of_node_put(parent_np);
+ if (IS_ERR(regmap))
+ return;
+
+ for_each_child_of_node(np, sysclknp) {
+ if (of_property_read_u32(sysclknp, "reg", &id))
+ continue;
+
+ if (of_property_read_string(np, "clock-output-names", &name))
+ name = sysclknp->name;
+
+ parent_name = of_clk_get_parent_name(sysclknp, 0);
+
+ hw = at91_clk_register_system(regmap, name, parent_name, id);
+ if (IS_ERR(hw))
+ continue;
+
+ of_clk_add_hw_provider(sysclknp, of_clk_hw_simple_get, hw);
+ }
+}
+CLK_OF_DECLARE(at91rm9200_clk_sys, "atmel,at91rm9200-clk-system",
+ of_at91rm9200_clk_sys_setup);
+
+#ifdef CONFIG_HAVE_AT91_USB_CLK
+#define USB_SOURCE_MAX 2
+
+static void __init of_at91sam9x5_clk_usb_setup(struct device_node *np)
+{
+ struct clk_hw *hw;
+ unsigned int num_parents;
+ const char *parent_names[USB_SOURCE_MAX];
+ const char *name = np->name;
+ struct regmap *regmap;
+ struct device_node *parent_np;
+
+ num_parents = of_clk_get_parent_count(np);
+ if (num_parents == 0 || num_parents > USB_SOURCE_MAX)
+ return;
+
+ of_clk_parent_fill(np, parent_names, num_parents);
+
+ of_property_read_string(np, "clock-output-names", &name);
+
+ parent_np = of_get_parent(np);
+ regmap = syscon_node_to_regmap(parent_np);
+ of_node_put(parent_np);
+ if (IS_ERR(regmap))
+ return;
+
+ hw = at91sam9x5_clk_register_usb(regmap, name, parent_names,
+ num_parents);
+ if (IS_ERR(hw))
+ return;
+
+ of_clk_add_hw_provider(np, of_clk_hw_simple_get, hw);
+}
+CLK_OF_DECLARE(at91sam9x5_clk_usb, "atmel,at91sam9x5-clk-usb",
+ of_at91sam9x5_clk_usb_setup);
+
+static void __init of_at91sam9n12_clk_usb_setup(struct device_node *np)
+{
+ struct clk_hw *hw;
+ const char *parent_name;
+ const char *name = np->name;
+ struct regmap *regmap;
+ struct device_node *parent_np;
+
+ parent_name = of_clk_get_parent_name(np, 0);
+ if (!parent_name)
+ return;
+
+ of_property_read_string(np, "clock-output-names", &name);
+
+ parent_np = of_get_parent(np);
+ regmap = syscon_node_to_regmap(parent_np);
+ of_node_put(parent_np);
+ if (IS_ERR(regmap))
+ return;
+
+ hw = at91sam9n12_clk_register_usb(regmap, name, parent_name);
+ if (IS_ERR(hw))
+ return;
+
+ of_clk_add_hw_provider(np, of_clk_hw_simple_get, hw);
+}
+CLK_OF_DECLARE(at91sam9n12_clk_usb, "atmel,at91sam9n12-clk-usb",
+ of_at91sam9n12_clk_usb_setup);
+
+static void __init of_at91rm9200_clk_usb_setup(struct device_node *np)
+{
+ struct clk_hw *hw;
+ const char *parent_name;
+ const char *name = np->name;
+ u32 divisors[4] = {0, 0, 0, 0};
+ struct regmap *regmap;
+ struct device_node *parent_np;
+
+ parent_name = of_clk_get_parent_name(np, 0);
+ if (!parent_name)
+ return;
+
+ of_property_read_u32_array(np, "atmel,clk-divisors", divisors, 4);
+ if (!divisors[0])
+ return;
+
+ of_property_read_string(np, "clock-output-names", &name);
+
+ parent_np = of_get_parent(np);
+ regmap = syscon_node_to_regmap(parent_np);
+ of_node_put(parent_np);
+ if (IS_ERR(regmap))
+ return;
+ hw = at91rm9200_clk_register_usb(regmap, name, parent_name, divisors);
+ if (IS_ERR(hw))
+ return;
+
+ of_clk_add_hw_provider(np, of_clk_hw_simple_get, hw);
+}
+CLK_OF_DECLARE(at91rm9200_clk_usb, "atmel,at91rm9200-clk-usb",
+ of_at91rm9200_clk_usb_setup);
+#endif /* CONFIG_HAVE_AT91_USB_CLK */
+
+#ifdef CONFIG_HAVE_AT91_UTMI
+static void __init of_at91sam9x5_clk_utmi_setup(struct device_node *np)
+{
+ struct clk_hw *hw;
+ const char *parent_name;
+ const char *name = np->name;
+ struct regmap *regmap_pmc, *regmap_sfr;
+ struct device_node *parent_np;
+
+ parent_name = of_clk_get_parent_name(np, 0);
+
+ of_property_read_string(np, "clock-output-names", &name);
+
+ parent_np = of_get_parent(np);
+ regmap_pmc = syscon_node_to_regmap(parent_np);
+ of_node_put(parent_np);
+ if (IS_ERR(regmap_pmc))
+ return;
+
+ /*
+ * If the device supports different mainck rates, this value has to be
+ * set in the UTMI Clock Trimming register.
+ * - 9x5: mainck supports several rates but it is indicated that a
+ * 12 MHz is needed in case of USB.
+ * - sama5d3 and sama5d2: mainck supports several rates. Configuring
+ * the FREQ field of the UTMI Clock Trimming register is mandatory.
+ * - sama5d4: mainck is at 12 MHz.
+ *
+ * We only need to retrieve sama5d3 or sama5d2 sfr regmap.
+ */
+ regmap_sfr = syscon_regmap_lookup_by_compatible("atmel,sama5d3-sfr");
+ if (IS_ERR(regmap_sfr)) {
+ regmap_sfr = syscon_regmap_lookup_by_compatible("atmel,sama5d2-sfr");
+ if (IS_ERR(regmap_sfr))
+ regmap_sfr = NULL;
+ }
+
+ hw = at91_clk_register_utmi(regmap_pmc, regmap_sfr, name, parent_name);
+ if (IS_ERR(hw))
+ return;
+
+ of_clk_add_hw_provider(np, of_clk_hw_simple_get, hw);
+}
+CLK_OF_DECLARE(at91sam9x5_clk_utmi, "atmel,at91sam9x5-clk-utmi",
+ of_at91sam9x5_clk_utmi_setup);
+#endif /* CONFIG_HAVE_AT91_UTMI */
diff --git a/drivers/clk/at91/pmc.c b/drivers/clk/at91/pmc.c
new file mode 100644
index 000000000..5aa9c1f1c
--- /dev/null
+++ b/drivers/clk/at91/pmc.c
@@ -0,0 +1,193 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/clk/at91_pmc.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/mfd/syscon.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/syscore_ops.h>
+
+#include <asm/proc-fns.h>
+
+#include "pmc.h"
+
+#define PMC_MAX_IDS 128
+#define PMC_MAX_PCKS 8
+
+int of_at91_get_clk_range(struct device_node *np, const char *propname,
+ struct clk_range *range)
+{
+ u32 min, max;
+ int ret;
+
+ ret = of_property_read_u32_index(np, propname, 0, &min);
+ if (ret)
+ return ret;
+
+ ret = of_property_read_u32_index(np, propname, 1, &max);
+ if (ret)
+ return ret;
+
+ if (range) {
+ range->min = min;
+ range->max = max;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(of_at91_get_clk_range);
+
+struct clk_hw *of_clk_hw_pmc_get(struct of_phandle_args *clkspec, void *data)
+{
+ unsigned int type = clkspec->args[0];
+ unsigned int idx = clkspec->args[1];
+ struct pmc_data *pmc_data = data;
+
+ switch (type) {
+ case PMC_TYPE_CORE:
+ if (idx < pmc_data->ncore)
+ return pmc_data->chws[idx];
+ break;
+ case PMC_TYPE_SYSTEM:
+ if (idx < pmc_data->nsystem)
+ return pmc_data->shws[idx];
+ break;
+ case PMC_TYPE_PERIPHERAL:
+ if (idx < pmc_data->nperiph)
+ return pmc_data->phws[idx];
+ break;
+ case PMC_TYPE_GCK:
+ if (idx < pmc_data->ngck)
+ return pmc_data->ghws[idx];
+ break;
+ case PMC_TYPE_PROGRAMMABLE:
+ if (idx < pmc_data->npck)
+ return pmc_data->pchws[idx];
+ break;
+ default:
+ break;
+ }
+
+ pr_err("%s: invalid type (%u) or index (%u)\n", __func__, type, idx);
+
+ return ERR_PTR(-EINVAL);
+}
+
+struct pmc_data *pmc_data_allocate(unsigned int ncore, unsigned int nsystem,
+ unsigned int nperiph, unsigned int ngck,
+ unsigned int npck)
+{
+ unsigned int num_clks = ncore + nsystem + nperiph + ngck + npck;
+ struct pmc_data *pmc_data;
+
+ pmc_data = kzalloc(struct_size(pmc_data, hwtable, num_clks),
+ GFP_KERNEL);
+ if (!pmc_data)
+ return NULL;
+
+ pmc_data->ncore = ncore;
+ pmc_data->chws = pmc_data->hwtable;
+
+ pmc_data->nsystem = nsystem;
+ pmc_data->shws = pmc_data->chws + ncore;
+
+ pmc_data->nperiph = nperiph;
+ pmc_data->phws = pmc_data->shws + nsystem;
+
+ pmc_data->ngck = ngck;
+ pmc_data->ghws = pmc_data->phws + nperiph;
+
+ pmc_data->npck = npck;
+ pmc_data->pchws = pmc_data->ghws + ngck;
+
+ return pmc_data;
+}
+
+#ifdef CONFIG_PM
+
+/* Address in SECURAM that say if we suspend to backup mode. */
+static void __iomem *at91_pmc_backup_suspend;
+
+static int at91_pmc_suspend(void)
+{
+ unsigned int backup;
+
+ if (!at91_pmc_backup_suspend)
+ return 0;
+
+ backup = readl_relaxed(at91_pmc_backup_suspend);
+ if (!backup)
+ return 0;
+
+ return clk_save_context();
+}
+
+static void at91_pmc_resume(void)
+{
+ unsigned int backup;
+
+ if (!at91_pmc_backup_suspend)
+ return;
+
+ backup = readl_relaxed(at91_pmc_backup_suspend);
+ if (!backup)
+ return;
+
+ clk_restore_context();
+}
+
+static struct syscore_ops pmc_syscore_ops = {
+ .suspend = at91_pmc_suspend,
+ .resume = at91_pmc_resume,
+};
+
+static const struct of_device_id pmc_dt_ids[] = {
+ { .compatible = "atmel,sama5d2-pmc" },
+ { .compatible = "microchip,sama7g5-pmc", },
+ { /* sentinel */ }
+};
+
+static int __init pmc_register_ops(void)
+{
+ struct device_node *np;
+
+ np = of_find_matching_node(NULL, pmc_dt_ids);
+ if (!np)
+ return -ENODEV;
+
+ if (!of_device_is_available(np)) {
+ of_node_put(np);
+ return -ENODEV;
+ }
+ of_node_put(np);
+
+ np = of_find_compatible_node(NULL, NULL, "atmel,sama5d2-securam");
+ if (!np)
+ return -ENODEV;
+
+ if (!of_device_is_available(np)) {
+ of_node_put(np);
+ return -ENODEV;
+ }
+ of_node_put(np);
+
+ at91_pmc_backup_suspend = of_iomap(np, 0);
+ if (!at91_pmc_backup_suspend) {
+ pr_warn("%s(): unable to map securam\n", __func__);
+ return -ENOMEM;
+ }
+
+ register_syscore_ops(&pmc_syscore_ops);
+
+ return 0;
+}
+/* This has to happen before arch_initcall because of the tcb_clksrc driver */
+postcore_initcall(pmc_register_ops);
+#endif
diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h
new file mode 100644
index 000000000..efe4975bd
--- /dev/null
+++ b/drivers/clk/at91/pmc.h
@@ -0,0 +1,268 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * drivers/clk/at91/pmc.h
+ *
+ * Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com>
+ */
+
+#ifndef __PMC_H_
+#define __PMC_H_
+
+#include <linux/io.h>
+#include <linux/irqdomain.h>
+#include <linux/regmap.h>
+#include <linux/spinlock.h>
+
+#include <dt-bindings/clock/at91.h>
+
+extern spinlock_t pmc_pcr_lock;
+
+struct pmc_data {
+ unsigned int ncore;
+ struct clk_hw **chws;
+ unsigned int nsystem;
+ struct clk_hw **shws;
+ unsigned int nperiph;
+ struct clk_hw **phws;
+ unsigned int ngck;
+ struct clk_hw **ghws;
+ unsigned int npck;
+ struct clk_hw **pchws;
+
+ struct clk_hw *hwtable[];
+};
+
+struct clk_range {
+ unsigned long min;
+ unsigned long max;
+};
+
+#define CLK_RANGE(MIN, MAX) {.min = MIN, .max = MAX,}
+
+struct clk_master_layout {
+ u32 offset;
+ u32 mask;
+ u8 pres_shift;
+};
+
+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[5];
+ u8 have_div3_pres;
+};
+
+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;
+};
+
+extern const struct clk_pll_layout at91rm9200_pll_layout;
+extern const struct clk_pll_layout at91sam9g45_pll_layout;
+extern const struct clk_pll_layout at91sam9g20_pllb_layout;
+extern const struct clk_pll_layout sama5d3_pll_layout;
+
+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_programmable_layout {
+ u8 pres_mask;
+ u8 pres_shift;
+ u8 css_mask;
+ u8 have_slck_mck;
+ u8 is_pres_direct;
+};
+
+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;
+
+struct clk_pcr_layout {
+ u32 offset;
+ u32 cmd;
+ u32 div_mask;
+ u32 gckcss_mask;
+ u32 pid_mask;
+};
+
+/**
+ * struct at91_clk_pms - Power management state for AT91 clock
+ * @rate: clock rate
+ * @parent_rate: clock parent rate
+ * @status: clock status (enabled or disabled)
+ * @parent: clock parent index
+ */
+struct at91_clk_pms {
+ unsigned long rate;
+ unsigned long parent_rate;
+ unsigned int status;
+ unsigned int parent;
+};
+
+#define field_get(_mask, _reg) (((_reg) & (_mask)) >> (ffs(_mask) - 1))
+#define field_prep(_mask, _val) (((_val) << (ffs(_mask) - 1)) & (_mask))
+
+#define ndck(a, s) (a[s - 1].id + 1)
+#define nck(a) (a[ARRAY_SIZE(a) - 1].id + 1)
+struct pmc_data *pmc_data_allocate(unsigned int ncore, unsigned int nsystem,
+ unsigned int nperiph, unsigned int ngck,
+ unsigned int npck);
+
+int of_at91_get_clk_range(struct device_node *np, const char *propname,
+ struct clk_range *range);
+
+struct clk_hw *of_clk_hw_pmc_get(struct of_phandle_args *clkspec, void *data);
+
+struct clk_hw * __init
+at91_clk_register_audio_pll_frac(struct regmap *regmap, const char *name,
+ const char *parent_name);
+
+struct clk_hw * __init
+at91_clk_register_audio_pll_pad(struct regmap *regmap, const char *name,
+ const char *parent_name);
+
+struct clk_hw * __init
+at91_clk_register_audio_pll_pmc(struct regmap *regmap, const char *name,
+ const char *parent_name);
+
+struct clk_hw * __init
+at91_clk_register_generated(struct regmap *regmap, spinlock_t *lock,
+ const struct clk_pcr_layout *layout,
+ const char *name, const char **parent_names,
+ u32 *mux_table, u8 num_parents, u8 id,
+ const struct clk_range *range, int chg_pid);
+
+struct clk_hw * __init
+at91_clk_register_h32mx(struct regmap *regmap, const char *name,
+ const char *parent_name);
+
+struct clk_hw * __init
+at91_clk_i2s_mux_register(struct regmap *regmap, const char *name,
+ const char * const *parent_names,
+ unsigned int num_parents, u8 bus_id);
+
+struct clk_hw * __init
+at91_clk_register_main_rc_osc(struct regmap *regmap, const char *name,
+ u32 frequency, u32 accuracy);
+struct clk_hw * __init
+at91_clk_register_main_osc(struct regmap *regmap, const char *name,
+ const char *parent_name, bool bypass);
+struct clk_hw * __init
+at91_clk_register_rm9200_main(struct regmap *regmap,
+ const char *name,
+ const char *parent_name);
+struct clk_hw * __init
+at91_clk_register_sam9x5_main(struct regmap *regmap, const char *name,
+ const char **parent_names, int num_parents);
+
+struct clk_hw * __init
+at91_clk_register_master_pres(struct regmap *regmap, const char *name,
+ int num_parents, const char **parent_names,
+ const struct clk_master_layout *layout,
+ const struct clk_master_characteristics *characteristics,
+ spinlock_t *lock);
+
+struct clk_hw * __init
+at91_clk_register_master_div(struct regmap *regmap, const char *name,
+ const char *parent_names,
+ const struct clk_master_layout *layout,
+ const struct clk_master_characteristics *characteristics,
+ spinlock_t *lock, u32 flags, u32 safe_div);
+
+struct clk_hw * __init
+at91_clk_sama7g5_register_master(struct regmap *regmap,
+ const char *name, int num_parents,
+ const char **parent_names, u32 *mux_table,
+ spinlock_t *lock, u8 id, bool critical,
+ int chg_pid);
+
+struct clk_hw * __init
+at91_clk_register_peripheral(struct regmap *regmap, const char *name,
+ const char *parent_name, u32 id);
+struct clk_hw * __init
+at91_clk_register_sam9x5_peripheral(struct regmap *regmap, spinlock_t *lock,
+ const struct clk_pcr_layout *layout,
+ const char *name, const char *parent_name,
+ u32 id, const struct clk_range *range,
+ int chg_pid);
+
+struct clk_hw * __init
+at91_clk_register_pll(struct regmap *regmap, const char *name,
+ const char *parent_name, u8 id,
+ const struct clk_pll_layout *layout,
+ const struct clk_pll_characteristics *characteristics);
+struct clk_hw * __init
+at91_clk_register_plldiv(struct regmap *regmap, const char *name,
+ const char *parent_name);
+
+struct clk_hw * __init
+sam9x60_clk_register_div_pll(struct regmap *regmap, spinlock_t *lock,
+ const char *name, const char *parent_name, u8 id,
+ const struct clk_pll_characteristics *characteristics,
+ const struct clk_pll_layout *layout, u32 flags,
+ u32 safe_div);
+
+struct clk_hw * __init
+sam9x60_clk_register_frac_pll(struct regmap *regmap, spinlock_t *lock,
+ const char *name, const char *parent_name,
+ struct clk_hw *parent_hw, u8 id,
+ const struct clk_pll_characteristics *characteristics,
+ const struct clk_pll_layout *layout, u32 flags);
+
+struct clk_hw * __init
+at91_clk_register_programmable(struct regmap *regmap, const char *name,
+ const char **parent_names, u8 num_parents, u8 id,
+ const struct clk_programmable_layout *layout,
+ u32 *mux_table);
+
+struct clk_hw * __init
+at91_clk_register_sam9260_slow(struct regmap *regmap,
+ const char *name,
+ const char **parent_names,
+ int num_parents);
+
+struct clk_hw * __init
+at91sam9x5_clk_register_smd(struct regmap *regmap, const char *name,
+ const char **parent_names, u8 num_parents);
+
+struct clk_hw * __init
+at91_clk_register_system(struct regmap *regmap, const char *name,
+ const char *parent_name, u8 id);
+
+struct clk_hw * __init
+at91sam9x5_clk_register_usb(struct regmap *regmap, const char *name,
+ const char **parent_names, u8 num_parents);
+struct clk_hw * __init
+at91sam9n12_clk_register_usb(struct regmap *regmap, const char *name,
+ const char *parent_name);
+struct clk_hw * __init
+sam9x60_clk_register_usb(struct regmap *regmap, const char *name,
+ const char **parent_names, u8 num_parents);
+struct clk_hw * __init
+at91rm9200_clk_register_usb(struct regmap *regmap, const char *name,
+ const char *parent_name, const u32 *divisors);
+
+struct clk_hw * __init
+at91_clk_register_utmi(struct regmap *regmap_pmc, struct regmap *regmap_sfr,
+ const char *name, const char *parent_name);
+
+struct clk_hw * __init
+at91_clk_sama7g5_register_utmi(struct regmap *regmap, const char *name,
+ const char *parent_name);
+
+#endif /* __PMC_H_ */
diff --git a/drivers/clk/at91/sam9x60.c b/drivers/clk/at91/sam9x60.c
new file mode 100644
index 000000000..9ea4ce501
--- /dev/null
+++ b/drivers/clk/at91/sam9x60.c
@@ -0,0 +1,359 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/clk-provider.h>
+#include <linux/mfd/syscon.h>
+#include <linux/slab.h>
+
+#include <dt-bindings/clock/at91.h>
+
+#include "pmc.h"
+
+static DEFINE_SPINLOCK(pmc_pll_lock);
+static DEFINE_SPINLOCK(mck_lock);
+
+static const struct clk_master_characteristics mck_characteristics = {
+ .output = { .min = 140000000, .max = 200000000 },
+ .divisors = { 1, 2, 4, 3 },
+ .have_div3_pres = 1,
+};
+
+static const struct clk_master_layout sam9x60_master_layout = {
+ .mask = 0x373,
+ .pres_shift = 4,
+ .offset = 0x28,
+};
+
+static const struct clk_range plla_outputs[] = {
+ { .min = 2343750, .max = 1200000000 },
+};
+
+static const struct clk_pll_characteristics plla_characteristics = {
+ .input = { .min = 12000000, .max = 48000000 },
+ .num_output = ARRAY_SIZE(plla_outputs),
+ .output = plla_outputs,
+};
+
+static const struct clk_range upll_outputs[] = {
+ { .min = 300000000, .max = 500000000 },
+};
+
+static const struct clk_pll_characteristics upll_characteristics = {
+ .input = { .min = 12000000, .max = 48000000 },
+ .num_output = ARRAY_SIZE(upll_outputs),
+ .output = upll_outputs,
+ .upll = true,
+};
+
+static const struct clk_pll_layout pll_frac_layout = {
+ .mul_mask = GENMASK(31, 24),
+ .frac_mask = GENMASK(21, 0),
+ .mul_shift = 24,
+ .frac_shift = 0,
+};
+
+static const struct clk_pll_layout pll_div_layout = {
+ .div_mask = GENMASK(7, 0),
+ .endiv_mask = BIT(29),
+ .div_shift = 0,
+ .endiv_shift = 29,
+};
+
+static const struct clk_programmable_layout sam9x60_programmable_layout = {
+ .pres_mask = 0xff,
+ .pres_shift = 8,
+ .css_mask = 0x1f,
+ .have_slck_mck = 0,
+ .is_pres_direct = 1,
+};
+
+static const struct clk_pcr_layout sam9x60_pcr_layout = {
+ .offset = 0x88,
+ .cmd = BIT(31),
+ .gckcss_mask = GENMASK(12, 8),
+ .pid_mask = GENMASK(6, 0),
+};
+
+static const struct {
+ char *n;
+ char *p;
+ u8 id;
+} sam9x60_systemck[] = {
+ { .n = "ddrck", .p = "masterck_div", .id = 2 },
+ { .n = "uhpck", .p = "usbck", .id = 6 },
+ { .n = "pck0", .p = "prog0", .id = 8 },
+ { .n = "pck1", .p = "prog1", .id = 9 },
+ { .n = "qspick", .p = "masterck_div", .id = 19 },
+};
+
+static const struct {
+ char *n;
+ u8 id;
+} sam9x60_periphck[] = {
+ { .n = "pioA_clk", .id = 2, },
+ { .n = "pioB_clk", .id = 3, },
+ { .n = "pioC_clk", .id = 4, },
+ { .n = "flex0_clk", .id = 5, },
+ { .n = "flex1_clk", .id = 6, },
+ { .n = "flex2_clk", .id = 7, },
+ { .n = "flex3_clk", .id = 8, },
+ { .n = "flex6_clk", .id = 9, },
+ { .n = "flex7_clk", .id = 10, },
+ { .n = "flex8_clk", .id = 11, },
+ { .n = "sdmmc0_clk", .id = 12, },
+ { .n = "flex4_clk", .id = 13, },
+ { .n = "flex5_clk", .id = 14, },
+ { .n = "flex9_clk", .id = 15, },
+ { .n = "flex10_clk", .id = 16, },
+ { .n = "tcb0_clk", .id = 17, },
+ { .n = "pwm_clk", .id = 18, },
+ { .n = "adc_clk", .id = 19, },
+ { .n = "dma0_clk", .id = 20, },
+ { .n = "matrix_clk", .id = 21, },
+ { .n = "uhphs_clk", .id = 22, },
+ { .n = "udphs_clk", .id = 23, },
+ { .n = "macb0_clk", .id = 24, },
+ { .n = "lcd_clk", .id = 25, },
+ { .n = "sdmmc1_clk", .id = 26, },
+ { .n = "macb1_clk", .id = 27, },
+ { .n = "ssc_clk", .id = 28, },
+ { .n = "can0_clk", .id = 29, },
+ { .n = "can1_clk", .id = 30, },
+ { .n = "flex11_clk", .id = 32, },
+ { .n = "flex12_clk", .id = 33, },
+ { .n = "i2s_clk", .id = 34, },
+ { .n = "qspi_clk", .id = 35, },
+ { .n = "gfx2d_clk", .id = 36, },
+ { .n = "pit64b_clk", .id = 37, },
+ { .n = "trng_clk", .id = 38, },
+ { .n = "aes_clk", .id = 39, },
+ { .n = "tdes_clk", .id = 40, },
+ { .n = "sha_clk", .id = 41, },
+ { .n = "classd_clk", .id = 42, },
+ { .n = "isi_clk", .id = 43, },
+ { .n = "pioD_clk", .id = 44, },
+ { .n = "tcb1_clk", .id = 45, },
+ { .n = "dbgu_clk", .id = 47, },
+ { .n = "mpddr_clk", .id = 49, },
+};
+
+static const struct {
+ char *n;
+ u8 id;
+ struct clk_range r;
+} sam9x60_gck[] = {
+ { .n = "flex0_gclk", .id = 5, },
+ { .n = "flex1_gclk", .id = 6, },
+ { .n = "flex2_gclk", .id = 7, },
+ { .n = "flex3_gclk", .id = 8, },
+ { .n = "flex6_gclk", .id = 9, },
+ { .n = "flex7_gclk", .id = 10, },
+ { .n = "flex8_gclk", .id = 11, },
+ { .n = "sdmmc0_gclk", .id = 12, .r = { .min = 0, .max = 105000000 }, },
+ { .n = "flex4_gclk", .id = 13, },
+ { .n = "flex5_gclk", .id = 14, },
+ { .n = "flex9_gclk", .id = 15, },
+ { .n = "flex10_gclk", .id = 16, },
+ { .n = "tcb0_gclk", .id = 17, },
+ { .n = "adc_gclk", .id = 19, },
+ { .n = "lcd_gclk", .id = 25, .r = { .min = 0, .max = 140000000 }, },
+ { .n = "sdmmc1_gclk", .id = 26, .r = { .min = 0, .max = 105000000 }, },
+ { .n = "flex11_gclk", .id = 32, },
+ { .n = "flex12_gclk", .id = 33, },
+ { .n = "i2s_gclk", .id = 34, .r = { .min = 0, .max = 105000000 }, },
+ { .n = "pit64b_gclk", .id = 37, },
+ { .n = "classd_gclk", .id = 42, .r = { .min = 0, .max = 100000000 }, },
+ { .n = "tcb1_gclk", .id = 45, },
+ { .n = "dbgu_gclk", .id = 47, },
+};
+
+static void __init sam9x60_pmc_setup(struct device_node *np)
+{
+ struct clk_range range = CLK_RANGE(0, 0);
+ const char *td_slck_name, *md_slck_name, *mainxtal_name;
+ struct pmc_data *sam9x60_pmc;
+ const char *parent_names[6];
+ struct clk_hw *main_osc_hw;
+ struct regmap *regmap;
+ struct clk_hw *hw;
+ int i;
+
+ i = of_property_match_string(np, "clock-names", "td_slck");
+ if (i < 0)
+ return;
+
+ td_slck_name = of_clk_get_parent_name(np, i);
+
+ i = of_property_match_string(np, "clock-names", "md_slck");
+ if (i < 0)
+ return;
+
+ md_slck_name = of_clk_get_parent_name(np, i);
+
+ i = of_property_match_string(np, "clock-names", "main_xtal");
+ if (i < 0)
+ return;
+ mainxtal_name = of_clk_get_parent_name(np, i);
+
+ regmap = device_node_to_regmap(np);
+ if (IS_ERR(regmap))
+ return;
+
+ sam9x60_pmc = pmc_data_allocate(PMC_PLLACK + 1,
+ nck(sam9x60_systemck),
+ nck(sam9x60_periphck),
+ nck(sam9x60_gck), 8);
+ if (!sam9x60_pmc)
+ return;
+
+ hw = at91_clk_register_main_rc_osc(regmap, "main_rc_osc", 12000000,
+ 50000000);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ hw = at91_clk_register_main_osc(regmap, "main_osc", mainxtal_name, 0);
+ if (IS_ERR(hw))
+ goto err_free;
+ main_osc_hw = hw;
+
+ parent_names[0] = "main_rc_osc";
+ parent_names[1] = "main_osc";
+ hw = at91_clk_register_sam9x5_main(regmap, "mainck", parent_names, 2);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sam9x60_pmc->chws[PMC_MAIN] = hw;
+
+ hw = sam9x60_clk_register_frac_pll(regmap, &pmc_pll_lock, "pllack_fracck",
+ "mainck", sam9x60_pmc->chws[PMC_MAIN],
+ 0, &plla_characteristics,
+ &pll_frac_layout,
+ /*
+ * This feeds pllack_divck which
+ * feeds CPU. It should not be
+ * disabled.
+ */
+ CLK_IS_CRITICAL | CLK_SET_RATE_GATE);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ hw = sam9x60_clk_register_div_pll(regmap, &pmc_pll_lock, "pllack_divck",
+ "pllack_fracck", 0, &plla_characteristics,
+ &pll_div_layout,
+ /*
+ * This feeds CPU. It should not
+ * be disabled.
+ */
+ CLK_IS_CRITICAL | CLK_SET_RATE_GATE, 0);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sam9x60_pmc->chws[PMC_PLLACK] = hw;
+
+ hw = sam9x60_clk_register_frac_pll(regmap, &pmc_pll_lock, "upllck_fracck",
+ "main_osc", main_osc_hw, 1,
+ &upll_characteristics,
+ &pll_frac_layout, CLK_SET_RATE_GATE);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ hw = sam9x60_clk_register_div_pll(regmap, &pmc_pll_lock, "upllck_divck",
+ "upllck_fracck", 1, &upll_characteristics,
+ &pll_div_layout,
+ CLK_SET_RATE_GATE |
+ CLK_SET_PARENT_GATE |
+ CLK_SET_RATE_PARENT, 0);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sam9x60_pmc->chws[PMC_UTMI] = hw;
+
+ parent_names[0] = md_slck_name;
+ parent_names[1] = "mainck";
+ parent_names[2] = "pllack_divck";
+ hw = at91_clk_register_master_pres(regmap, "masterck_pres", 3,
+ parent_names, &sam9x60_master_layout,
+ &mck_characteristics, &mck_lock);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ hw = at91_clk_register_master_div(regmap, "masterck_div",
+ "masterck_pres", &sam9x60_master_layout,
+ &mck_characteristics, &mck_lock,
+ CLK_SET_RATE_GATE, 0);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sam9x60_pmc->chws[PMC_MCK] = hw;
+
+ parent_names[0] = "pllack_divck";
+ parent_names[1] = "upllck_divck";
+ parent_names[2] = "main_osc";
+ hw = sam9x60_clk_register_usb(regmap, "usbck", parent_names, 3);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ parent_names[0] = md_slck_name;
+ parent_names[1] = td_slck_name;
+ parent_names[2] = "mainck";
+ parent_names[3] = "masterck_div";
+ parent_names[4] = "pllack_divck";
+ parent_names[5] = "upllck_divck";
+ for (i = 0; i < 2; i++) {
+ char name[6];
+
+ snprintf(name, sizeof(name), "prog%d", i);
+
+ hw = at91_clk_register_programmable(regmap, name,
+ parent_names, 6, i,
+ &sam9x60_programmable_layout,
+ NULL);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sam9x60_pmc->pchws[i] = hw;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(sam9x60_systemck); i++) {
+ hw = at91_clk_register_system(regmap, sam9x60_systemck[i].n,
+ sam9x60_systemck[i].p,
+ sam9x60_systemck[i].id);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sam9x60_pmc->shws[sam9x60_systemck[i].id] = hw;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(sam9x60_periphck); i++) {
+ hw = at91_clk_register_sam9x5_peripheral(regmap, &pmc_pcr_lock,
+ &sam9x60_pcr_layout,
+ sam9x60_periphck[i].n,
+ "masterck_div",
+ sam9x60_periphck[i].id,
+ &range, INT_MIN);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sam9x60_pmc->phws[sam9x60_periphck[i].id] = hw;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(sam9x60_gck); i++) {
+ hw = at91_clk_register_generated(regmap, &pmc_pcr_lock,
+ &sam9x60_pcr_layout,
+ sam9x60_gck[i].n,
+ parent_names, NULL, 6,
+ sam9x60_gck[i].id,
+ &sam9x60_gck[i].r, INT_MIN);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sam9x60_pmc->ghws[sam9x60_gck[i].id] = hw;
+ }
+
+ of_clk_add_hw_provider(np, of_clk_hw_pmc_get, sam9x60_pmc);
+
+ return;
+
+err_free:
+ kfree(sam9x60_pmc);
+}
+/* Some clks are used for a clocksource */
+CLK_OF_DECLARE(sam9x60_pmc, "microchip,sam9x60-pmc", sam9x60_pmc_setup);
diff --git a/drivers/clk/at91/sama5d2.c b/drivers/clk/at91/sama5d2.c
new file mode 100644
index 000000000..84156dc52
--- /dev/null
+++ b/drivers/clk/at91/sama5d2.c
@@ -0,0 +1,387 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/clk-provider.h>
+#include <linux/mfd/syscon.h>
+#include <linux/slab.h>
+
+#include <dt-bindings/clock/at91.h>
+
+#include "pmc.h"
+
+static DEFINE_SPINLOCK(mck_lock);
+
+static const struct clk_master_characteristics mck_characteristics = {
+ .output = { .min = 124000000, .max = 166000000 },
+ .divisors = { 1, 2, 4, 3 },
+};
+
+static u8 plla_out[] = { 0 };
+
+static u16 plla_icpll[] = { 0 };
+
+static const struct clk_range plla_outputs[] = {
+ { .min = 600000000, .max = 1200000000 },
+};
+
+static const struct clk_pll_characteristics plla_characteristics = {
+ .input = { .min = 12000000, .max = 24000000 },
+ .num_output = ARRAY_SIZE(plla_outputs),
+ .output = plla_outputs,
+ .icpll = plla_icpll,
+ .out = plla_out,
+};
+
+static const struct clk_pcr_layout sama5d2_pcr_layout = {
+ .offset = 0x10c,
+ .cmd = BIT(12),
+ .gckcss_mask = GENMASK(10, 8),
+ .pid_mask = GENMASK(6, 0),
+};
+
+static const struct {
+ char *n;
+ char *p;
+ u8 id;
+} sama5d2_systemck[] = {
+ { .n = "ddrck", .p = "masterck_div", .id = 2 },
+ { .n = "lcdck", .p = "masterck_div", .id = 3 },
+ { .n = "uhpck", .p = "usbck", .id = 6 },
+ { .n = "udpck", .p = "usbck", .id = 7 },
+ { .n = "pck0", .p = "prog0", .id = 8 },
+ { .n = "pck1", .p = "prog1", .id = 9 },
+ { .n = "pck2", .p = "prog2", .id = 10 },
+ { .n = "iscck", .p = "masterck_div", .id = 18 },
+};
+
+static const struct {
+ char *n;
+ u8 id;
+ struct clk_range r;
+} sama5d2_periph32ck[] = {
+ { .n = "macb0_clk", .id = 5, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "tdes_clk", .id = 11, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "matrix1_clk", .id = 14, },
+ { .n = "hsmc_clk", .id = 17, },
+ { .n = "pioA_clk", .id = 18, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "flx0_clk", .id = 19, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "flx1_clk", .id = 20, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "flx2_clk", .id = 21, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "flx3_clk", .id = 22, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "flx4_clk", .id = 23, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "uart0_clk", .id = 24, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "uart1_clk", .id = 25, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "uart2_clk", .id = 26, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "uart3_clk", .id = 27, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "uart4_clk", .id = 28, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "twi0_clk", .id = 29, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "twi1_clk", .id = 30, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "spi0_clk", .id = 33, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "spi1_clk", .id = 34, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "tcb0_clk", .id = 35, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "tcb1_clk", .id = 36, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "pwm_clk", .id = 38, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "adc_clk", .id = 40, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "uhphs_clk", .id = 41, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "udphs_clk", .id = 42, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "ssc0_clk", .id = 43, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "ssc1_clk", .id = 44, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "trng_clk", .id = 47, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "pdmic_clk", .id = 48, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "securam_clk", .id = 51, },
+ { .n = "i2s0_clk", .id = 54, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "i2s1_clk", .id = 55, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "can0_clk", .id = 56, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "can1_clk", .id = 57, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "ptc_clk", .id = 58, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "classd_clk", .id = 59, .r = { .min = 0, .max = 83000000 }, },
+};
+
+static const struct {
+ char *n;
+ u8 id;
+} sama5d2_periphck[] = {
+ { .n = "dma0_clk", .id = 6, },
+ { .n = "dma1_clk", .id = 7, },
+ { .n = "aes_clk", .id = 9, },
+ { .n = "aesb_clk", .id = 10, },
+ { .n = "sha_clk", .id = 12, },
+ { .n = "mpddr_clk", .id = 13, },
+ { .n = "matrix0_clk", .id = 15, },
+ { .n = "sdmmc0_hclk", .id = 31, },
+ { .n = "sdmmc1_hclk", .id = 32, },
+ { .n = "lcdc_clk", .id = 45, },
+ { .n = "isc_clk", .id = 46, },
+ { .n = "qspi0_clk", .id = 52, },
+ { .n = "qspi1_clk", .id = 53, },
+};
+
+static const struct {
+ char *n;
+ u8 id;
+ struct clk_range r;
+ int chg_pid;
+} sama5d2_gck[] = {
+ { .n = "flx0_gclk", .id = 19, .chg_pid = INT_MIN, .r = { .min = 0, .max = 27666666 }, },
+ { .n = "flx1_gclk", .id = 20, .chg_pid = INT_MIN, .r = { .min = 0, .max = 27666666 }, },
+ { .n = "flx2_gclk", .id = 21, .chg_pid = INT_MIN, .r = { .min = 0, .max = 27666666 }, },
+ { .n = "flx3_gclk", .id = 22, .chg_pid = INT_MIN, .r = { .min = 0, .max = 27666666 }, },
+ { .n = "flx4_gclk", .id = 23, .chg_pid = INT_MIN, .r = { .min = 0, .max = 27666666 }, },
+ { .n = "uart0_gclk", .id = 24, .chg_pid = INT_MIN, .r = { .min = 0, .max = 27666666 }, },
+ { .n = "uart1_gclk", .id = 25, .chg_pid = INT_MIN, .r = { .min = 0, .max = 27666666 }, },
+ { .n = "uart2_gclk", .id = 26, .chg_pid = INT_MIN, .r = { .min = 0, .max = 27666666 }, },
+ { .n = "uart3_gclk", .id = 27, .chg_pid = INT_MIN, .r = { .min = 0, .max = 27666666 }, },
+ { .n = "uart4_gclk", .id = 28, .chg_pid = INT_MIN, .r = { .min = 0, .max = 27666666 }, },
+ { .n = "sdmmc0_gclk", .id = 31, .chg_pid = INT_MIN, },
+ { .n = "sdmmc1_gclk", .id = 32, .chg_pid = INT_MIN, },
+ { .n = "tcb0_gclk", .id = 35, .chg_pid = INT_MIN, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "tcb1_gclk", .id = 36, .chg_pid = INT_MIN, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "pwm_gclk", .id = 38, .chg_pid = INT_MIN, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "isc_gclk", .id = 46, .chg_pid = INT_MIN, },
+ { .n = "pdmic_gclk", .id = 48, .chg_pid = INT_MIN, },
+ { .n = "i2s0_gclk", .id = 54, .chg_pid = 5, },
+ { .n = "i2s1_gclk", .id = 55, .chg_pid = 5, },
+ { .n = "can0_gclk", .id = 56, .chg_pid = INT_MIN, .r = { .min = 0, .max = 80000000 }, },
+ { .n = "can1_gclk", .id = 57, .chg_pid = INT_MIN, .r = { .min = 0, .max = 80000000 }, },
+ { .n = "classd_gclk", .id = 59, .chg_pid = 5, .r = { .min = 0, .max = 100000000 }, },
+};
+
+static const struct clk_programmable_layout sama5d2_programmable_layout = {
+ .pres_mask = 0xff,
+ .pres_shift = 4,
+ .css_mask = 0x7,
+ .have_slck_mck = 0,
+ .is_pres_direct = 1,
+};
+
+static void __init sama5d2_pmc_setup(struct device_node *np)
+{
+ struct clk_range range = CLK_RANGE(0, 0);
+ const char *slck_name, *mainxtal_name;
+ struct pmc_data *sama5d2_pmc;
+ const char *parent_names[6];
+ struct regmap *regmap, *regmap_sfr;
+ struct clk_hw *hw;
+ int i;
+ bool bypass;
+
+ i = of_property_match_string(np, "clock-names", "slow_clk");
+ if (i < 0)
+ return;
+
+ slck_name = of_clk_get_parent_name(np, i);
+
+ i = of_property_match_string(np, "clock-names", "main_xtal");
+ if (i < 0)
+ return;
+ mainxtal_name = of_clk_get_parent_name(np, i);
+
+ regmap = device_node_to_regmap(np);
+ if (IS_ERR(regmap))
+ return;
+
+ sama5d2_pmc = pmc_data_allocate(PMC_AUDIOPINCK + 1,
+ nck(sama5d2_systemck),
+ nck(sama5d2_periph32ck),
+ nck(sama5d2_gck), 3);
+ if (!sama5d2_pmc)
+ return;
+
+ hw = at91_clk_register_main_rc_osc(regmap, "main_rc_osc", 12000000,
+ 100000000);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ bypass = of_property_read_bool(np, "atmel,osc-bypass");
+
+ hw = at91_clk_register_main_osc(regmap, "main_osc", mainxtal_name,
+ bypass);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ parent_names[0] = "main_rc_osc";
+ parent_names[1] = "main_osc";
+ hw = at91_clk_register_sam9x5_main(regmap, "mainck", parent_names, 2);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sama5d2_pmc->chws[PMC_MAIN] = hw;
+
+ hw = at91_clk_register_pll(regmap, "pllack", "mainck", 0,
+ &sama5d3_pll_layout, &plla_characteristics);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ hw = at91_clk_register_plldiv(regmap, "plladivck", "pllack");
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sama5d2_pmc->chws[PMC_PLLACK] = hw;
+
+ hw = at91_clk_register_audio_pll_frac(regmap, "audiopll_fracck",
+ "mainck");
+ if (IS_ERR(hw))
+ goto err_free;
+
+ hw = at91_clk_register_audio_pll_pad(regmap, "audiopll_padck",
+ "audiopll_fracck");
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sama5d2_pmc->chws[PMC_AUDIOPINCK] = hw;
+
+ hw = at91_clk_register_audio_pll_pmc(regmap, "audiopll_pmcck",
+ "audiopll_fracck");
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sama5d2_pmc->chws[PMC_AUDIOPLLCK] = hw;
+
+ regmap_sfr = syscon_regmap_lookup_by_compatible("atmel,sama5d2-sfr");
+ if (IS_ERR(regmap_sfr))
+ regmap_sfr = NULL;
+
+ hw = at91_clk_register_utmi(regmap, regmap_sfr, "utmick", "mainck");
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sama5d2_pmc->chws[PMC_UTMI] = hw;
+
+ parent_names[0] = slck_name;
+ parent_names[1] = "mainck";
+ parent_names[2] = "plladivck";
+ parent_names[3] = "utmick";
+ hw = at91_clk_register_master_pres(regmap, "masterck_pres", 4,
+ parent_names,
+ &at91sam9x5_master_layout,
+ &mck_characteristics, &mck_lock);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ hw = at91_clk_register_master_div(regmap, "masterck_div",
+ "masterck_pres",
+ &at91sam9x5_master_layout,
+ &mck_characteristics, &mck_lock,
+ CLK_SET_RATE_GATE, 0);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sama5d2_pmc->chws[PMC_MCK] = hw;
+
+ hw = at91_clk_register_h32mx(regmap, "h32mxck", "masterck_div");
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sama5d2_pmc->chws[PMC_MCK2] = hw;
+
+ parent_names[0] = "plladivck";
+ parent_names[1] = "utmick";
+ hw = at91sam9x5_clk_register_usb(regmap, "usbck", parent_names, 2);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ parent_names[0] = slck_name;
+ parent_names[1] = "mainck";
+ parent_names[2] = "plladivck";
+ parent_names[3] = "utmick";
+ parent_names[4] = "masterck_div";
+ parent_names[5] = "audiopll_pmcck";
+ for (i = 0; i < 3; i++) {
+ char name[6];
+
+ snprintf(name, sizeof(name), "prog%d", i);
+
+ hw = at91_clk_register_programmable(regmap, name,
+ parent_names, 6, i,
+ &sama5d2_programmable_layout,
+ NULL);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sama5d2_pmc->pchws[i] = hw;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(sama5d2_systemck); i++) {
+ hw = at91_clk_register_system(regmap, sama5d2_systemck[i].n,
+ sama5d2_systemck[i].p,
+ sama5d2_systemck[i].id);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sama5d2_pmc->shws[sama5d2_systemck[i].id] = hw;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(sama5d2_periphck); i++) {
+ hw = at91_clk_register_sam9x5_peripheral(regmap, &pmc_pcr_lock,
+ &sama5d2_pcr_layout,
+ sama5d2_periphck[i].n,
+ "masterck_div",
+ sama5d2_periphck[i].id,
+ &range, INT_MIN);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sama5d2_pmc->phws[sama5d2_periphck[i].id] = hw;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(sama5d2_periph32ck); i++) {
+ hw = at91_clk_register_sam9x5_peripheral(regmap, &pmc_pcr_lock,
+ &sama5d2_pcr_layout,
+ sama5d2_periph32ck[i].n,
+ "h32mxck",
+ sama5d2_periph32ck[i].id,
+ &sama5d2_periph32ck[i].r,
+ INT_MIN);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sama5d2_pmc->phws[sama5d2_periph32ck[i].id] = hw;
+ }
+
+ parent_names[0] = slck_name;
+ parent_names[1] = "mainck";
+ parent_names[2] = "plladivck";
+ parent_names[3] = "utmick";
+ parent_names[4] = "masterck_div";
+ parent_names[5] = "audiopll_pmcck";
+ for (i = 0; i < ARRAY_SIZE(sama5d2_gck); i++) {
+ hw = at91_clk_register_generated(regmap, &pmc_pcr_lock,
+ &sama5d2_pcr_layout,
+ sama5d2_gck[i].n,
+ parent_names, NULL, 6,
+ sama5d2_gck[i].id,
+ &sama5d2_gck[i].r,
+ sama5d2_gck[i].chg_pid);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sama5d2_pmc->ghws[sama5d2_gck[i].id] = hw;
+ }
+
+ if (regmap_sfr) {
+ parent_names[0] = "i2s0_clk";
+ parent_names[1] = "i2s0_gclk";
+ hw = at91_clk_i2s_mux_register(regmap_sfr, "i2s0_muxclk",
+ parent_names, 2, 0);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sama5d2_pmc->chws[PMC_I2S0_MUX] = hw;
+
+ parent_names[0] = "i2s1_clk";
+ parent_names[1] = "i2s1_gclk";
+ hw = at91_clk_i2s_mux_register(regmap_sfr, "i2s1_muxclk",
+ parent_names, 2, 1);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sama5d2_pmc->chws[PMC_I2S1_MUX] = hw;
+ }
+
+ of_clk_add_hw_provider(np, of_clk_hw_pmc_get, sama5d2_pmc);
+
+ return;
+
+err_free:
+ kfree(sama5d2_pmc);
+}
+
+CLK_OF_DECLARE(sama5d2_pmc, "atmel,sama5d2-pmc", sama5d2_pmc_setup);
diff --git a/drivers/clk/at91/sama5d3.c b/drivers/clk/at91/sama5d3.c
new file mode 100644
index 000000000..7e93c6edf
--- /dev/null
+++ b/drivers/clk/at91/sama5d3.c
@@ -0,0 +1,257 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/clk-provider.h>
+#include <linux/mfd/syscon.h>
+#include <linux/slab.h>
+
+#include <dt-bindings/clock/at91.h>
+
+#include "pmc.h"
+
+static DEFINE_SPINLOCK(mck_lock);
+
+static const struct clk_master_characteristics mck_characteristics = {
+ .output = { .min = 0, .max = 166000000 },
+ .divisors = { 1, 2, 4, 3 },
+};
+
+static u8 plla_out[] = { 0 };
+
+static u16 plla_icpll[] = { 0 };
+
+static const struct clk_range plla_outputs[] = {
+ { .min = 400000000, .max = 1000000000 },
+};
+
+static const struct clk_pll_characteristics plla_characteristics = {
+ .input = { .min = 8000000, .max = 50000000 },
+ .num_output = ARRAY_SIZE(plla_outputs),
+ .output = plla_outputs,
+ .icpll = plla_icpll,
+ .out = plla_out,
+};
+
+static const struct clk_pcr_layout sama5d3_pcr_layout = {
+ .offset = 0x10c,
+ .cmd = BIT(12),
+ .pid_mask = GENMASK(6, 0),
+ .div_mask = GENMASK(17, 16),
+};
+
+static const struct {
+ char *n;
+ char *p;
+ u8 id;
+} sama5d3_systemck[] = {
+ { .n = "ddrck", .p = "masterck_div", .id = 2 },
+ { .n = "lcdck", .p = "masterck_div", .id = 3 },
+ { .n = "smdck", .p = "smdclk", .id = 4 },
+ { .n = "uhpck", .p = "usbck", .id = 6 },
+ { .n = "udpck", .p = "usbck", .id = 7 },
+ { .n = "pck0", .p = "prog0", .id = 8 },
+ { .n = "pck1", .p = "prog1", .id = 9 },
+ { .n = "pck2", .p = "prog2", .id = 10 },
+};
+
+static const struct {
+ char *n;
+ u8 id;
+ struct clk_range r;
+} sama5d3_periphck[] = {
+ { .n = "dbgu_clk", .id = 2, },
+ { .n = "hsmc_clk", .id = 5, },
+ { .n = "pioA_clk", .id = 6, },
+ { .n = "pioB_clk", .id = 7, },
+ { .n = "pioC_clk", .id = 8, },
+ { .n = "pioD_clk", .id = 9, },
+ { .n = "pioE_clk", .id = 10, },
+ { .n = "usart0_clk", .id = 12, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "usart1_clk", .id = 13, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "usart2_clk", .id = 14, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "usart3_clk", .id = 15, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "uart0_clk", .id = 16, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "uart1_clk", .id = 17, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "twi0_clk", .id = 18, .r = { .min = 0, .max = 41500000 }, },
+ { .n = "twi1_clk", .id = 19, .r = { .min = 0, .max = 41500000 }, },
+ { .n = "twi2_clk", .id = 20, .r = { .min = 0, .max = 41500000 }, },
+ { .n = "mci0_clk", .id = 21, },
+ { .n = "mci1_clk", .id = 22, },
+ { .n = "mci2_clk", .id = 23, },
+ { .n = "spi0_clk", .id = 24, .r = { .min = 0, .max = 166000000 }, },
+ { .n = "spi1_clk", .id = 25, .r = { .min = 0, .max = 166000000 }, },
+ { .n = "tcb0_clk", .id = 26, .r = { .min = 0, .max = 166000000 }, },
+ { .n = "tcb1_clk", .id = 27, .r = { .min = 0, .max = 166000000 }, },
+ { .n = "pwm_clk", .id = 28, },
+ { .n = "adc_clk", .id = 29, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "dma0_clk", .id = 30, },
+ { .n = "dma1_clk", .id = 31, },
+ { .n = "uhphs_clk", .id = 32, },
+ { .n = "udphs_clk", .id = 33, },
+ { .n = "macb0_clk", .id = 34, },
+ { .n = "macb1_clk", .id = 35, },
+ { .n = "lcdc_clk", .id = 36, },
+ { .n = "isi_clk", .id = 37, },
+ { .n = "ssc0_clk", .id = 38, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "ssc1_clk", .id = 39, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "can0_clk", .id = 40, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "can1_clk", .id = 41, .r = { .min = 0, .max = 83000000 }, },
+ { .n = "sha_clk", .id = 42, },
+ { .n = "aes_clk", .id = 43, },
+ { .n = "tdes_clk", .id = 44, },
+ { .n = "trng_clk", .id = 45, },
+ { .n = "fuse_clk", .id = 48, },
+ { .n = "mpddr_clk", .id = 49, },
+};
+
+static void __init sama5d3_pmc_setup(struct device_node *np)
+{
+ const char *slck_name, *mainxtal_name;
+ struct pmc_data *sama5d3_pmc;
+ const char *parent_names[5];
+ struct regmap *regmap;
+ struct clk_hw *hw;
+ int i;
+ bool bypass;
+
+ i = of_property_match_string(np, "clock-names", "slow_clk");
+ if (i < 0)
+ return;
+
+ slck_name = of_clk_get_parent_name(np, i);
+
+ i = of_property_match_string(np, "clock-names", "main_xtal");
+ if (i < 0)
+ return;
+ mainxtal_name = of_clk_get_parent_name(np, i);
+
+ regmap = device_node_to_regmap(np);
+ if (IS_ERR(regmap))
+ return;
+
+ sama5d3_pmc = pmc_data_allocate(PMC_PLLACK + 1,
+ nck(sama5d3_systemck),
+ nck(sama5d3_periphck), 0, 3);
+ if (!sama5d3_pmc)
+ return;
+
+ hw = at91_clk_register_main_rc_osc(regmap, "main_rc_osc", 12000000,
+ 50000000);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ bypass = of_property_read_bool(np, "atmel,osc-bypass");
+
+ hw = at91_clk_register_main_osc(regmap, "main_osc", mainxtal_name,
+ bypass);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ parent_names[0] = "main_rc_osc";
+ parent_names[1] = "main_osc";
+ hw = at91_clk_register_sam9x5_main(regmap, "mainck", parent_names, 2);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ hw = at91_clk_register_pll(regmap, "pllack", "mainck", 0,
+ &sama5d3_pll_layout, &plla_characteristics);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ hw = at91_clk_register_plldiv(regmap, "plladivck", "pllack");
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sama5d3_pmc->chws[PMC_PLLACK] = hw;
+
+ hw = at91_clk_register_utmi(regmap, NULL, "utmick", "mainck");
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sama5d3_pmc->chws[PMC_UTMI] = hw;
+
+ parent_names[0] = slck_name;
+ parent_names[1] = "mainck";
+ parent_names[2] = "plladivck";
+ parent_names[3] = "utmick";
+ hw = at91_clk_register_master_pres(regmap, "masterck_pres", 4,
+ parent_names,
+ &at91sam9x5_master_layout,
+ &mck_characteristics, &mck_lock);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ hw = at91_clk_register_master_div(regmap, "masterck_div",
+ "masterck_pres",
+ &at91sam9x5_master_layout,
+ &mck_characteristics, &mck_lock,
+ CLK_SET_RATE_GATE, 0);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sama5d3_pmc->chws[PMC_MCK] = hw;
+
+ parent_names[0] = "plladivck";
+ parent_names[1] = "utmick";
+ hw = at91sam9x5_clk_register_usb(regmap, "usbck", parent_names, 2);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ hw = at91sam9x5_clk_register_smd(regmap, "smdclk", parent_names, 2);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ parent_names[0] = slck_name;
+ parent_names[1] = "mainck";
+ parent_names[2] = "plladivck";
+ parent_names[3] = "utmick";
+ parent_names[4] = "masterck_div";
+ for (i = 0; i < 3; i++) {
+ char name[6];
+
+ snprintf(name, sizeof(name), "prog%d", i);
+
+ hw = at91_clk_register_programmable(regmap, name,
+ parent_names, 5, i,
+ &at91sam9x5_programmable_layout,
+ NULL);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sama5d3_pmc->pchws[i] = hw;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(sama5d3_systemck); i++) {
+ hw = at91_clk_register_system(regmap, sama5d3_systemck[i].n,
+ sama5d3_systemck[i].p,
+ sama5d3_systemck[i].id);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sama5d3_pmc->shws[sama5d3_systemck[i].id] = hw;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(sama5d3_periphck); i++) {
+ hw = at91_clk_register_sam9x5_peripheral(regmap, &pmc_pcr_lock,
+ &sama5d3_pcr_layout,
+ sama5d3_periphck[i].n,
+ "masterck_div",
+ sama5d3_periphck[i].id,
+ &sama5d3_periphck[i].r,
+ INT_MIN);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sama5d3_pmc->phws[sama5d3_periphck[i].id] = hw;
+ }
+
+ of_clk_add_hw_provider(np, of_clk_hw_pmc_get, sama5d3_pmc);
+
+ return;
+
+err_free:
+ kfree(sama5d3_pmc);
+}
+/*
+ * The TCB is used as the clocksource so its clock is needed early. This means
+ * this can't be a platform driver.
+ */
+CLK_OF_DECLARE(sama5d3_pmc, "atmel,sama5d3-pmc", sama5d3_pmc_setup);
diff --git a/drivers/clk/at91/sama5d4.c b/drivers/clk/at91/sama5d4.c
new file mode 100644
index 000000000..1a14a9bce
--- /dev/null
+++ b/drivers/clk/at91/sama5d4.c
@@ -0,0 +1,289 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/clk-provider.h>
+#include <linux/mfd/syscon.h>
+#include <linux/slab.h>
+
+#include <dt-bindings/clock/at91.h>
+
+#include "pmc.h"
+
+static DEFINE_SPINLOCK(mck_lock);
+
+static const struct clk_master_characteristics mck_characteristics = {
+ .output = { .min = 125000000, .max = 200000000 },
+ .divisors = { 1, 2, 4, 3 },
+};
+
+static u8 plla_out[] = { 0 };
+
+static u16 plla_icpll[] = { 0 };
+
+static const struct clk_range plla_outputs[] = {
+ { .min = 600000000, .max = 1200000000 },
+};
+
+static const struct clk_pll_characteristics plla_characteristics = {
+ .input = { .min = 12000000, .max = 12000000 },
+ .num_output = ARRAY_SIZE(plla_outputs),
+ .output = plla_outputs,
+ .icpll = plla_icpll,
+ .out = plla_out,
+};
+
+static const struct clk_pcr_layout sama5d4_pcr_layout = {
+ .offset = 0x10c,
+ .cmd = BIT(12),
+ .pid_mask = GENMASK(6, 0),
+};
+
+static const struct {
+ char *n;
+ char *p;
+ u8 id;
+} sama5d4_systemck[] = {
+ { .n = "ddrck", .p = "masterck_div", .id = 2 },
+ { .n = "lcdck", .p = "masterck_div", .id = 3 },
+ { .n = "smdck", .p = "smdclk", .id = 4 },
+ { .n = "uhpck", .p = "usbck", .id = 6 },
+ { .n = "udpck", .p = "usbck", .id = 7 },
+ { .n = "pck0", .p = "prog0", .id = 8 },
+ { .n = "pck1", .p = "prog1", .id = 9 },
+ { .n = "pck2", .p = "prog2", .id = 10 },
+};
+
+static const struct {
+ char *n;
+ u8 id;
+} sama5d4_periph32ck[] = {
+ { .n = "pioD_clk", .id = 5 },
+ { .n = "usart0_clk", .id = 6 },
+ { .n = "usart1_clk", .id = 7 },
+ { .n = "icm_clk", .id = 9 },
+ { .n = "aes_clk", .id = 12 },
+ { .n = "tdes_clk", .id = 14 },
+ { .n = "sha_clk", .id = 15 },
+ { .n = "matrix1_clk", .id = 17 },
+ { .n = "hsmc_clk", .id = 22 },
+ { .n = "pioA_clk", .id = 23 },
+ { .n = "pioB_clk", .id = 24 },
+ { .n = "pioC_clk", .id = 25 },
+ { .n = "pioE_clk", .id = 26 },
+ { .n = "uart0_clk", .id = 27 },
+ { .n = "uart1_clk", .id = 28 },
+ { .n = "usart2_clk", .id = 29 },
+ { .n = "usart3_clk", .id = 30 },
+ { .n = "usart4_clk", .id = 31 },
+ { .n = "twi0_clk", .id = 32 },
+ { .n = "twi1_clk", .id = 33 },
+ { .n = "twi2_clk", .id = 34 },
+ { .n = "mci0_clk", .id = 35 },
+ { .n = "mci1_clk", .id = 36 },
+ { .n = "spi0_clk", .id = 37 },
+ { .n = "spi1_clk", .id = 38 },
+ { .n = "spi2_clk", .id = 39 },
+ { .n = "tcb0_clk", .id = 40 },
+ { .n = "tcb1_clk", .id = 41 },
+ { .n = "tcb2_clk", .id = 42 },
+ { .n = "pwm_clk", .id = 43 },
+ { .n = "adc_clk", .id = 44 },
+ { .n = "dbgu_clk", .id = 45 },
+ { .n = "uhphs_clk", .id = 46 },
+ { .n = "udphs_clk", .id = 47 },
+ { .n = "ssc0_clk", .id = 48 },
+ { .n = "ssc1_clk", .id = 49 },
+ { .n = "trng_clk", .id = 53 },
+ { .n = "macb0_clk", .id = 54 },
+ { .n = "macb1_clk", .id = 55 },
+ { .n = "fuse_clk", .id = 57 },
+ { .n = "securam_clk", .id = 59 },
+ { .n = "smd_clk", .id = 61 },
+ { .n = "twi3_clk", .id = 62 },
+ { .n = "catb_clk", .id = 63 },
+};
+
+static const struct {
+ char *n;
+ u8 id;
+} sama5d4_periphck[] = {
+ { .n = "dma0_clk", .id = 8 },
+ { .n = "cpkcc_clk", .id = 10 },
+ { .n = "aesb_clk", .id = 13 },
+ { .n = "mpddr_clk", .id = 16 },
+ { .n = "matrix0_clk", .id = 18 },
+ { .n = "vdec_clk", .id = 19 },
+ { .n = "dma1_clk", .id = 50 },
+ { .n = "lcdc_clk", .id = 51 },
+ { .n = "isi_clk", .id = 52 },
+};
+
+static void __init sama5d4_pmc_setup(struct device_node *np)
+{
+ struct clk_range range = CLK_RANGE(0, 0);
+ const char *slck_name, *mainxtal_name;
+ struct pmc_data *sama5d4_pmc;
+ const char *parent_names[5];
+ struct regmap *regmap;
+ struct clk_hw *hw;
+ int i;
+ bool bypass;
+
+ i = of_property_match_string(np, "clock-names", "slow_clk");
+ if (i < 0)
+ return;
+
+ slck_name = of_clk_get_parent_name(np, i);
+
+ i = of_property_match_string(np, "clock-names", "main_xtal");
+ if (i < 0)
+ return;
+ mainxtal_name = of_clk_get_parent_name(np, i);
+
+ regmap = device_node_to_regmap(np);
+ if (IS_ERR(regmap))
+ return;
+
+ sama5d4_pmc = pmc_data_allocate(PMC_PLLACK + 1,
+ nck(sama5d4_systemck),
+ nck(sama5d4_periph32ck), 0, 3);
+ if (!sama5d4_pmc)
+ return;
+
+ hw = at91_clk_register_main_rc_osc(regmap, "main_rc_osc", 12000000,
+ 100000000);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ bypass = of_property_read_bool(np, "atmel,osc-bypass");
+
+ hw = at91_clk_register_main_osc(regmap, "main_osc", mainxtal_name,
+ bypass);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ parent_names[0] = "main_rc_osc";
+ parent_names[1] = "main_osc";
+ hw = at91_clk_register_sam9x5_main(regmap, "mainck", parent_names, 2);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ hw = at91_clk_register_pll(regmap, "pllack", "mainck", 0,
+ &sama5d3_pll_layout, &plla_characteristics);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ hw = at91_clk_register_plldiv(regmap, "plladivck", "pllack");
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sama5d4_pmc->chws[PMC_PLLACK] = hw;
+
+ hw = at91_clk_register_utmi(regmap, NULL, "utmick", "mainck");
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sama5d4_pmc->chws[PMC_UTMI] = hw;
+
+ parent_names[0] = slck_name;
+ parent_names[1] = "mainck";
+ parent_names[2] = "plladivck";
+ parent_names[3] = "utmick";
+ hw = at91_clk_register_master_pres(regmap, "masterck_pres", 4,
+ parent_names,
+ &at91sam9x5_master_layout,
+ &mck_characteristics, &mck_lock);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ hw = at91_clk_register_master_div(regmap, "masterck_div",
+ "masterck_pres",
+ &at91sam9x5_master_layout,
+ &mck_characteristics, &mck_lock,
+ CLK_SET_RATE_GATE, 0);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sama5d4_pmc->chws[PMC_MCK] = hw;
+
+ hw = at91_clk_register_h32mx(regmap, "h32mxck", "masterck_div");
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sama5d4_pmc->chws[PMC_MCK2] = hw;
+
+ parent_names[0] = "plladivck";
+ parent_names[1] = "utmick";
+ hw = at91sam9x5_clk_register_usb(regmap, "usbck", parent_names, 2);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ parent_names[0] = "plladivck";
+ parent_names[1] = "utmick";
+ hw = at91sam9x5_clk_register_smd(regmap, "smdclk", parent_names, 2);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ parent_names[0] = slck_name;
+ parent_names[1] = "mainck";
+ parent_names[2] = "plladivck";
+ parent_names[3] = "utmick";
+ parent_names[4] = "masterck_div";
+ for (i = 0; i < 3; i++) {
+ char name[6];
+
+ snprintf(name, sizeof(name), "prog%d", i);
+
+ hw = at91_clk_register_programmable(regmap, name,
+ parent_names, 5, i,
+ &at91sam9x5_programmable_layout,
+ NULL);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sama5d4_pmc->pchws[i] = hw;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(sama5d4_systemck); i++) {
+ hw = at91_clk_register_system(regmap, sama5d4_systemck[i].n,
+ sama5d4_systemck[i].p,
+ sama5d4_systemck[i].id);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sama5d4_pmc->shws[sama5d4_systemck[i].id] = hw;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(sama5d4_periphck); i++) {
+ hw = at91_clk_register_sam9x5_peripheral(regmap, &pmc_pcr_lock,
+ &sama5d4_pcr_layout,
+ sama5d4_periphck[i].n,
+ "masterck_div",
+ sama5d4_periphck[i].id,
+ &range, INT_MIN);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sama5d4_pmc->phws[sama5d4_periphck[i].id] = hw;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(sama5d4_periph32ck); i++) {
+ hw = at91_clk_register_sam9x5_peripheral(regmap, &pmc_pcr_lock,
+ &sama5d4_pcr_layout,
+ sama5d4_periph32ck[i].n,
+ "h32mxck",
+ sama5d4_periph32ck[i].id,
+ &range, INT_MIN);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sama5d4_pmc->phws[sama5d4_periph32ck[i].id] = hw;
+ }
+
+ of_clk_add_hw_provider(np, of_clk_hw_pmc_get, sama5d4_pmc);
+
+ return;
+
+err_free:
+ kfree(sama5d4_pmc);
+}
+
+CLK_OF_DECLARE(sama5d4_pmc, "atmel,sama5d4-pmc", sama5d4_pmc_setup);
diff --git a/drivers/clk/at91/sama7g5.c b/drivers/clk/at91/sama7g5.c
new file mode 100644
index 000000000..9a213ba9e
--- /dev/null
+++ b/drivers/clk/at91/sama7g5.c
@@ -0,0 +1,1141 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * SAMA7G5 PMC code.
+ *
+ * Copyright (C) 2020 Microchip Technology Inc. and its subsidiaries
+ *
+ * Author: Claudiu Beznea <claudiu.beznea@microchip.com>
+ *
+ */
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/mfd/syscon.h>
+#include <linux/slab.h>
+
+#include <dt-bindings/clock/at91.h>
+
+#include "pmc.h"
+
+#define SAMA7G5_INIT_TABLE(_table, _count) \
+ do { \
+ u8 _i; \
+ for (_i = 0; _i < (_count); _i++) \
+ (_table)[_i] = _i; \
+ } while (0)
+
+#define SAMA7G5_FILL_TABLE(_to, _from, _count) \
+ do { \
+ u8 _i; \
+ for (_i = 0; _i < (_count); _i++) { \
+ (_to)[_i] = (_from)[_i]; \
+ } \
+ } while (0)
+
+static DEFINE_SPINLOCK(pmc_pll_lock);
+static DEFINE_SPINLOCK(pmc_mck0_lock);
+static DEFINE_SPINLOCK(pmc_mckX_lock);
+
+/*
+ * PLL clocks identifiers
+ * @PLL_ID_CPU: CPU PLL identifier
+ * @PLL_ID_SYS: System PLL identifier
+ * @PLL_ID_DDR: DDR PLL identifier
+ * @PLL_ID_IMG: Image subsystem PLL identifier
+ * @PLL_ID_BAUD: Baud PLL identifier
+ * @PLL_ID_AUDIO: Audio PLL identifier
+ * @PLL_ID_ETH: Ethernet PLL identifier
+ */
+enum pll_ids {
+ PLL_ID_CPU,
+ PLL_ID_SYS,
+ PLL_ID_DDR,
+ PLL_ID_IMG,
+ PLL_ID_BAUD,
+ PLL_ID_AUDIO,
+ PLL_ID_ETH,
+ PLL_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,
+};
+
+/* 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,
+};
+
+/*
+ * CPU PLL output range.
+ * Notice: The upper limit has been setup to 1000000002 due to hardware
+ * block which cannot output exactly 1GHz.
+ */
+static const struct clk_range cpu_pll_outputs[] = {
+ { .min = 2343750, .max = 1000000002 },
+};
+
+/* PLL output range. */
+static const struct clk_range pll_outputs[] = {
+ { .min = 2343750, .max = 1200000000 },
+};
+
+/* CPU PLL characteristics. */
+static const struct clk_pll_characteristics cpu_pll_characteristics = {
+ .input = { .min = 12000000, .max = 50000000 },
+ .num_output = ARRAY_SIZE(cpu_pll_outputs),
+ .output = cpu_pll_outputs,
+};
+
+/* PLL characteristics. */
+static const struct clk_pll_characteristics pll_characteristics = {
+ .input = { .min = 12000000, .max = 50000000 },
+ .num_output = ARRAY_SIZE(pll_outputs),
+ .output = pll_outputs,
+};
+
+/*
+ * PLL clocks description
+ * @n: clock name
+ * @p: clock parent
+ * @l: clock layout
+ * @c: clock characteristics
+ * @t: clock type
+ * @f: clock flags
+ * @eid: export index in sama7g5->chws[] array
+ * @safe_div: intermediate divider need to be set on PRE_RATE_CHANGE
+ * notification
+ */
+static const struct {
+ const char *n;
+ const char *p;
+ const struct clk_pll_layout *l;
+ const struct clk_pll_characteristics *c;
+ unsigned long f;
+ u8 t;
+ u8 eid;
+ u8 safe_div;
+} sama7g5_plls[][PLL_ID_MAX] = {
+ [PLL_ID_CPU] = {
+ { .n = "cpupll_fracck",
+ .p = "mainck",
+ .l = &pll_layout_frac,
+ .c = &cpu_pll_characteristics,
+ .t = PLL_TYPE_FRAC,
+ /*
+ * This feeds cpupll_divpmcck which feeds CPU. It should
+ * not be disabled.
+ */
+ .f = CLK_IS_CRITICAL, },
+
+ { .n = "cpupll_divpmcck",
+ .p = "cpupll_fracck",
+ .l = &pll_layout_divpmc,
+ .c = &cpu_pll_characteristics,
+ .t = PLL_TYPE_DIV,
+ /* This feeds CPU. It should not be disabled. */
+ .f = CLK_IS_CRITICAL | CLK_SET_RATE_PARENT,
+ .eid = PMC_CPUPLL,
+ /*
+ * Safe div=15 should be safe even for switching b/w 1GHz and
+ * 90MHz (frac pll might go up to 1.2GHz).
+ */
+ .safe_div = 15, },
+ },
+
+ [PLL_ID_SYS] = {
+ { .n = "syspll_fracck",
+ .p = "mainck",
+ .l = &pll_layout_frac,
+ .c = &pll_characteristics,
+ .t = PLL_TYPE_FRAC,
+ /*
+ * This feeds syspll_divpmcck which may feed critical parts
+ * of the systems like timers. Therefore it should not be
+ * disabled.
+ */
+ .f = CLK_IS_CRITICAL | CLK_SET_RATE_GATE, },
+
+ { .n = "syspll_divpmcck",
+ .p = "syspll_fracck",
+ .l = &pll_layout_divpmc,
+ .c = &pll_characteristics,
+ .t = PLL_TYPE_DIV,
+ /*
+ * This may feed critical parts of the systems like timers.
+ * Therefore it should not be disabled.
+ */
+ .f = CLK_IS_CRITICAL | CLK_SET_RATE_GATE,
+ .eid = PMC_SYSPLL, },
+ },
+
+ [PLL_ID_DDR] = {
+ { .n = "ddrpll_fracck",
+ .p = "mainck",
+ .l = &pll_layout_frac,
+ .c = &pll_characteristics,
+ .t = PLL_TYPE_FRAC,
+ /*
+ * This feeds ddrpll_divpmcck which feeds DDR. It should not
+ * be disabled.
+ */
+ .f = CLK_IS_CRITICAL | CLK_SET_RATE_GATE, },
+
+ { .n = "ddrpll_divpmcck",
+ .p = "ddrpll_fracck",
+ .l = &pll_layout_divpmc,
+ .c = &pll_characteristics,
+ .t = PLL_TYPE_DIV,
+ /* This feeds DDR. It should not be disabled. */
+ .f = CLK_IS_CRITICAL | CLK_SET_RATE_GATE, },
+ },
+
+ [PLL_ID_IMG] = {
+ { .n = "imgpll_fracck",
+ .p = "mainck",
+ .l = &pll_layout_frac,
+ .c = &pll_characteristics,
+ .t = PLL_TYPE_FRAC,
+ .f = CLK_SET_RATE_GATE, },
+
+ { .n = "imgpll_divpmcck",
+ .p = "imgpll_fracck",
+ .l = &pll_layout_divpmc,
+ .c = &pll_characteristics,
+ .t = PLL_TYPE_DIV,
+ .f = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE |
+ CLK_SET_RATE_PARENT, },
+ },
+
+ [PLL_ID_BAUD] = {
+ { .n = "baudpll_fracck",
+ .p = "mainck",
+ .l = &pll_layout_frac,
+ .c = &pll_characteristics,
+ .t = PLL_TYPE_FRAC,
+ .f = CLK_SET_RATE_GATE, },
+
+ { .n = "baudpll_divpmcck",
+ .p = "baudpll_fracck",
+ .l = &pll_layout_divpmc,
+ .c = &pll_characteristics,
+ .t = PLL_TYPE_DIV,
+ .f = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE |
+ CLK_SET_RATE_PARENT, },
+ },
+
+ [PLL_ID_AUDIO] = {
+ { .n = "audiopll_fracck",
+ .p = "main_xtal",
+ .l = &pll_layout_frac,
+ .c = &pll_characteristics,
+ .t = PLL_TYPE_FRAC,
+ .f = CLK_SET_RATE_GATE, },
+
+ { .n = "audiopll_divpmcck",
+ .p = "audiopll_fracck",
+ .l = &pll_layout_divpmc,
+ .c = &pll_characteristics,
+ .t = PLL_TYPE_DIV,
+ .f = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE |
+ CLK_SET_RATE_PARENT,
+ .eid = PMC_AUDIOPMCPLL, },
+
+ { .n = "audiopll_diviock",
+ .p = "audiopll_fracck",
+ .l = &pll_layout_divio,
+ .c = &pll_characteristics,
+ .t = PLL_TYPE_DIV,
+ .f = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE |
+ CLK_SET_RATE_PARENT,
+ .eid = PMC_AUDIOIOPLL, },
+ },
+
+ [PLL_ID_ETH] = {
+ { .n = "ethpll_fracck",
+ .p = "main_xtal",
+ .l = &pll_layout_frac,
+ .c = &pll_characteristics,
+ .t = PLL_TYPE_FRAC,
+ .f = CLK_SET_RATE_GATE, },
+
+ { .n = "ethpll_divpmcck",
+ .p = "ethpll_fracck",
+ .l = &pll_layout_divpmc,
+ .c = &pll_characteristics,
+ .t = PLL_TYPE_DIV,
+ .f = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE |
+ CLK_SET_RATE_PARENT, },
+ },
+};
+
+/*
+ * 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
+ * @id: clock id
+ * @eid: export index in sama7g5->chws[] array
+ * @c: true if clock is critical and cannot be disabled
+ */
+static const struct {
+ const char *n;
+ const char *ep[4];
+ int ep_chg_id;
+ u8 ep_count;
+ u8 ep_mux_table[4];
+ u8 id;
+ u8 eid;
+ u8 c;
+} sama7g5_mckx[] = {
+ { .n = "mck1",
+ .id = 1,
+ .ep = { "syspll_divpmcck", },
+ .ep_mux_table = { 5, },
+ .ep_count = 1,
+ .ep_chg_id = INT_MIN,
+ .eid = PMC_MCK1,
+ .c = 1, },
+
+ { .n = "mck2",
+ .id = 2,
+ .ep = { "ddrpll_divpmcck", },
+ .ep_mux_table = { 6, },
+ .ep_count = 1,
+ .ep_chg_id = INT_MIN,
+ .c = 1, },
+
+ { .n = "mck3",
+ .id = 3,
+ .ep = { "syspll_divpmcck", "ddrpll_divpmcck", "imgpll_divpmcck", },
+ .ep_mux_table = { 5, 6, 7, },
+ .ep_count = 3,
+ .ep_chg_id = 5, },
+
+ { .n = "mck4",
+ .id = 4,
+ .ep = { "syspll_divpmcck", },
+ .ep_mux_table = { 5, },
+ .ep_count = 1,
+ .ep_chg_id = INT_MIN,
+ .c = 1, },
+};
+
+/*
+ * System clock description
+ * @n: clock name
+ * @p: clock parent name
+ * @id: clock id
+ */
+static const struct {
+ const char *n;
+ const char *p;
+ u8 id;
+} sama7g5_systemck[] = {
+ { .n = "pck0", .p = "prog0", .id = 8, },
+ { .n = "pck1", .p = "prog1", .id = 9, },
+ { .n = "pck2", .p = "prog2", .id = 10, },
+ { .n = "pck3", .p = "prog3", .id = 11, },
+ { .n = "pck4", .p = "prog4", .id = 12, },
+ { .n = "pck5", .p = "prog5", .id = 13, },
+ { .n = "pck6", .p = "prog6", .id = 14, },
+ { .n = "pck7", .p = "prog7", .id = 15, },
+};
+
+/* Mux table for programmable clocks. */
+static u32 sama7g5_prog_mux_table[] = { 0, 1, 2, 5, 6, 7, 8, 9, 10, };
+
+/*
+ * Peripheral clock description
+ * @n: clock name
+ * @p: clock parent name
+ * @r: clock range values
+ * @id: clock id
+ * @chgp: index in parent array of the changeable parent
+ */
+static const struct {
+ const char *n;
+ const char *p;
+ struct clk_range r;
+ u8 chgp;
+ u8 id;
+} sama7g5_periphck[] = {
+ { .n = "pioA_clk", .p = "mck0", .id = 11, },
+ { .n = "securam_clk", .p = "mck0", .id = 18, },
+ { .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, }, .chgp = 1, },
+ { .n = "csi2dc_clk", .p = "mck3", .id = 34, .r = { .max = 266000000, }, .chgp = 1, },
+ { .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 = "icm_clk", .p = "mck1", .id = 55, },
+ { .n = "isc_clk", .p = "mck3", .id = 56, .r = { .max = 266000000, }, .chgp = 1, },
+ { .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
+ * @pp: PLL parents
+ * @pp_mux_table: PLL parents mux table
+ * @r: clock output range
+ * @pp_chg_id: id in parent array of changeable PLL parent
+ * @pp_count: PLL parents count
+ * @id: clock id
+ */
+static const struct {
+ const char *n;
+ const char *pp[8];
+ const char pp_mux_table[8];
+ struct clk_range r;
+ int pp_chg_id;
+ u8 pp_count;
+ u8 id;
+} sama7g5_gck[] = {
+ { .n = "adc_gclk",
+ .id = 26,
+ .r = { .max = 100000000, },
+ .pp = { "syspll_divpmcck", "imgpll_divpmcck", "audiopll_divpmcck", },
+ .pp_mux_table = { 5, 7, 9, },
+ .pp_count = 3,
+ .pp_chg_id = INT_MIN, },
+
+ { .n = "asrc_gclk",
+ .id = 30,
+ .r = { .max = 200000000 },
+ .pp = { "audiopll_divpmcck", },
+ .pp_mux_table = { 9, },
+ .pp_count = 1,
+ .pp_chg_id = 3, },
+
+ { .n = "csi_gclk",
+ .id = 33,
+ .r = { .max = 27000000 },
+ .pp = { "ddrpll_divpmcck", "imgpll_divpmcck", },
+ .pp_mux_table = { 6, 7, },
+ .pp_count = 2,
+ .pp_chg_id = INT_MIN, },
+
+ { .n = "flex0_gclk",
+ .id = 38,
+ .r = { .max = 200000000 },
+ .pp = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .pp_mux_table = { 5, 8, },
+ .pp_count = 2,
+ .pp_chg_id = INT_MIN, },
+
+ { .n = "flex1_gclk",
+ .id = 39,
+ .r = { .max = 200000000 },
+ .pp = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .pp_mux_table = { 5, 8, },
+ .pp_count = 2,
+ .pp_chg_id = INT_MIN, },
+
+ { .n = "flex2_gclk",
+ .id = 40,
+ .r = { .max = 200000000 },
+ .pp = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .pp_mux_table = { 5, 8, },
+ .pp_count = 2,
+ .pp_chg_id = INT_MIN, },
+
+ { .n = "flex3_gclk",
+ .id = 41,
+ .r = { .max = 200000000 },
+ .pp = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .pp_mux_table = { 5, 8, },
+ .pp_count = 2,
+ .pp_chg_id = INT_MIN, },
+
+ { .n = "flex4_gclk",
+ .id = 42,
+ .r = { .max = 200000000 },
+ .pp = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .pp_mux_table = { 5, 8, },
+ .pp_count = 2,
+ .pp_chg_id = INT_MIN, },
+
+ { .n = "flex5_gclk",
+ .id = 43,
+ .r = { .max = 200000000 },
+ .pp = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .pp_mux_table = { 5, 8, },
+ .pp_count = 2,
+ .pp_chg_id = INT_MIN, },
+
+ { .n = "flex6_gclk",
+ .id = 44,
+ .r = { .max = 200000000 },
+ .pp = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .pp_mux_table = { 5, 8, },
+ .pp_count = 2,
+ .pp_chg_id = INT_MIN, },
+
+ { .n = "flex7_gclk",
+ .id = 45,
+ .r = { .max = 200000000 },
+ .pp = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .pp_mux_table = { 5, 8, },
+ .pp_count = 2,
+ .pp_chg_id = INT_MIN, },
+
+ { .n = "flex8_gclk",
+ .id = 46,
+ .r = { .max = 200000000 },
+ .pp = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .pp_mux_table = { 5, 8, },
+ .pp_count = 2,
+ .pp_chg_id = INT_MIN, },
+
+ { .n = "flex9_gclk",
+ .id = 47,
+ .r = { .max = 200000000 },
+ .pp = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .pp_mux_table = { 5, 8, },
+ .pp_count = 2,
+ .pp_chg_id = INT_MIN, },
+
+ { .n = "flex10_gclk",
+ .id = 48,
+ .r = { .max = 200000000 },
+ .pp = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .pp_mux_table = { 5, 8, },
+ .pp_count = 2,
+ .pp_chg_id = INT_MIN, },
+
+ { .n = "flex11_gclk",
+ .id = 49,
+ .r = { .max = 200000000 },
+ .pp = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .pp_mux_table = { 5, 8, },
+ .pp_count = 2,
+ .pp_chg_id = INT_MIN, },
+
+ { .n = "gmac0_gclk",
+ .id = 51,
+ .r = { .max = 125000000 },
+ .pp = { "ethpll_divpmcck", },
+ .pp_mux_table = { 10, },
+ .pp_count = 1,
+ .pp_chg_id = 3, },
+
+ { .n = "gmac1_gclk",
+ .id = 52,
+ .r = { .max = 50000000 },
+ .pp = { "ethpll_divpmcck", },
+ .pp_mux_table = { 10, },
+ .pp_count = 1,
+ .pp_chg_id = INT_MIN, },
+
+ { .n = "gmac0_tsu_gclk",
+ .id = 53,
+ .r = { .max = 300000000 },
+ .pp = { "audiopll_divpmcck", "ethpll_divpmcck", },
+ .pp_mux_table = { 9, 10, },
+ .pp_count = 2,
+ .pp_chg_id = INT_MIN, },
+
+ { .n = "gmac1_tsu_gclk",
+ .id = 54,
+ .r = { .max = 300000000 },
+ .pp = { "audiopll_divpmcck", "ethpll_divpmcck", },
+ .pp_mux_table = { 9, 10, },
+ .pp_count = 2,
+ .pp_chg_id = INT_MIN, },
+
+ { .n = "i2smcc0_gclk",
+ .id = 57,
+ .r = { .max = 100000000 },
+ .pp = { "syspll_divpmcck", "audiopll_divpmcck", },
+ .pp_mux_table = { 5, 9, },
+ .pp_count = 2,
+ .pp_chg_id = 4, },
+
+ { .n = "i2smcc1_gclk",
+ .id = 58,
+ .r = { .max = 100000000 },
+ .pp = { "syspll_divpmcck", "audiopll_divpmcck", },
+ .pp_mux_table = { 5, 9, },
+ .pp_count = 2,
+ .pp_chg_id = 4, },
+
+ { .n = "mcan0_gclk",
+ .id = 61,
+ .r = { .max = 200000000 },
+ .pp = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .pp_mux_table = { 5, 8, },
+ .pp_count = 2,
+ .pp_chg_id = INT_MIN, },
+
+ { .n = "mcan1_gclk",
+ .id = 62,
+ .r = { .max = 200000000 },
+ .pp = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .pp_mux_table = { 5, 8, },
+ .pp_count = 2,
+ .pp_chg_id = INT_MIN, },
+
+ { .n = "mcan2_gclk",
+ .id = 63,
+ .r = { .max = 200000000 },
+ .pp = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .pp_mux_table = { 5, 8, },
+ .pp_count = 2,
+ .pp_chg_id = INT_MIN, },
+
+ { .n = "mcan3_gclk",
+ .id = 64,
+ .r = { .max = 200000000 },
+ .pp = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .pp_mux_table = { 5, 8, },
+ .pp_count = 2,
+ .pp_chg_id = INT_MIN, },
+
+ { .n = "mcan4_gclk",
+ .id = 65,
+ .r = { .max = 200000000 },
+ .pp = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .pp_mux_table = { 5, 8, },
+ .pp_count = 2,
+ .pp_chg_id = INT_MIN, },
+
+ { .n = "mcan5_gclk",
+ .id = 66,
+ .r = { .max = 200000000 },
+ .pp = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .pp_mux_table = { 5, 8, },
+ .pp_count = 2,
+ .pp_chg_id = INT_MIN, },
+
+ { .n = "pdmc0_gclk",
+ .id = 68,
+ .r = { .max = 50000000 },
+ .pp = { "syspll_divpmcck", "audiopll_divpmcck", },
+ .pp_mux_table = { 5, 9, },
+ .pp_count = 2,
+ .pp_chg_id = INT_MIN, },
+
+ { .n = "pdmc1_gclk",
+ .id = 69,
+ .r = { .max = 50000000, },
+ .pp = { "syspll_divpmcck", "audiopll_divpmcck", },
+ .pp_mux_table = { 5, 9, },
+ .pp_count = 2,
+ .pp_chg_id = INT_MIN, },
+
+ { .n = "pit64b0_gclk",
+ .id = 70,
+ .r = { .max = 200000000 },
+ .pp = { "syspll_divpmcck", "imgpll_divpmcck", "baudpll_divpmcck",
+ "audiopll_divpmcck", "ethpll_divpmcck", },
+ .pp_mux_table = { 5, 7, 8, 9, 10, },
+ .pp_count = 5,
+ .pp_chg_id = INT_MIN, },
+
+ { .n = "pit64b1_gclk",
+ .id = 71,
+ .r = { .max = 200000000 },
+ .pp = { "syspll_divpmcck", "imgpll_divpmcck", "baudpll_divpmcck",
+ "audiopll_divpmcck", "ethpll_divpmcck", },
+ .pp_mux_table = { 5, 7, 8, 9, 10, },
+ .pp_count = 5,
+ .pp_chg_id = INT_MIN, },
+
+ { .n = "pit64b2_gclk",
+ .id = 72,
+ .r = { .max = 200000000 },
+ .pp = { "syspll_divpmcck", "imgpll_divpmcck", "baudpll_divpmcck",
+ "audiopll_divpmcck", "ethpll_divpmcck", },
+ .pp_mux_table = { 5, 7, 8, 9, 10, },
+ .pp_count = 5,
+ .pp_chg_id = INT_MIN, },
+
+ { .n = "pit64b3_gclk",
+ .id = 73,
+ .r = { .max = 200000000 },
+ .pp = { "syspll_divpmcck", "imgpll_divpmcck", "baudpll_divpmcck",
+ "audiopll_divpmcck", "ethpll_divpmcck", },
+ .pp_mux_table = { 5, 7, 8, 9, 10, },
+ .pp_count = 5,
+ .pp_chg_id = INT_MIN, },
+
+ { .n = "pit64b4_gclk",
+ .id = 74,
+ .r = { .max = 200000000 },
+ .pp = { "syspll_divpmcck", "imgpll_divpmcck", "baudpll_divpmcck",
+ "audiopll_divpmcck", "ethpll_divpmcck", },
+ .pp_mux_table = { 5, 7, 8, 9, 10, },
+ .pp_count = 5,
+ .pp_chg_id = INT_MIN, },
+
+ { .n = "pit64b5_gclk",
+ .id = 75,
+ .r = { .max = 200000000 },
+ .pp = { "syspll_divpmcck", "imgpll_divpmcck", "baudpll_divpmcck",
+ "audiopll_divpmcck", "ethpll_divpmcck", },
+ .pp_mux_table = { 5, 7, 8, 9, 10, },
+ .pp_count = 5,
+ .pp_chg_id = INT_MIN, },
+
+ { .n = "qspi0_gclk",
+ .id = 78,
+ .r = { .max = 200000000 },
+ .pp = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .pp_mux_table = { 5, 8, },
+ .pp_count = 2,
+ .pp_chg_id = INT_MIN, },
+
+ { .n = "qspi1_gclk",
+ .id = 79,
+ .r = { .max = 200000000 },
+ .pp = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .pp_mux_table = { 5, 8, },
+ .pp_count = 2,
+ .pp_chg_id = INT_MIN, },
+
+ { .n = "sdmmc0_gclk",
+ .id = 80,
+ .r = { .max = 208000000 },
+ .pp = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .pp_mux_table = { 5, 8, },
+ .pp_count = 2,
+ .pp_chg_id = 4, },
+
+ { .n = "sdmmc1_gclk",
+ .id = 81,
+ .r = { .max = 208000000 },
+ .pp = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .pp_mux_table = { 5, 8, },
+ .pp_count = 2,
+ .pp_chg_id = 4, },
+
+ { .n = "sdmmc2_gclk",
+ .id = 82,
+ .r = { .max = 208000000 },
+ .pp = { "syspll_divpmcck", "baudpll_divpmcck", },
+ .pp_mux_table = { 5, 8, },
+ .pp_count = 2,
+ .pp_chg_id = 4, },
+
+ { .n = "spdifrx_gclk",
+ .id = 84,
+ .r = { .max = 150000000 },
+ .pp = { "syspll_divpmcck", "audiopll_divpmcck", },
+ .pp_mux_table = { 5, 9, },
+ .pp_count = 2,
+ .pp_chg_id = 4, },
+
+ { .n = "spdiftx_gclk",
+ .id = 85,
+ .r = { .max = 25000000 },
+ .pp = { "syspll_divpmcck", "audiopll_divpmcck", },
+ .pp_mux_table = { 5, 9, },
+ .pp_count = 2,
+ .pp_chg_id = 4, },
+
+ { .n = "tcb0_ch0_gclk",
+ .id = 88,
+ .r = { .max = 200000000 },
+ .pp = { "syspll_divpmcck", "imgpll_divpmcck", "baudpll_divpmcck",
+ "audiopll_divpmcck", "ethpll_divpmcck", },
+ .pp_mux_table = { 5, 7, 8, 9, 10, },
+ .pp_count = 5,
+ .pp_chg_id = INT_MIN, },
+
+ { .n = "tcb1_ch0_gclk",
+ .id = 91,
+ .r = { .max = 200000000 },
+ .pp = { "syspll_divpmcck", "imgpll_divpmcck", "baudpll_divpmcck",
+ "audiopll_divpmcck", "ethpll_divpmcck", },
+ .pp_mux_table = { 5, 7, 8, 9, 10, },
+ .pp_count = 5,
+ .pp_chg_id = INT_MIN, },
+
+ { .n = "tcpca_gclk",
+ .id = 94,
+ .r = { .max = 32768, },
+ .pp_chg_id = INT_MIN, },
+
+ { .n = "tcpcb_gclk",
+ .id = 95,
+ .r = { .max = 32768, },
+ .pp_chg_id = INT_MIN, },
+};
+
+/* MCK0 characteristics. */
+static const struct clk_master_characteristics mck0_characteristics = {
+ .output = { .min = 32768, .max = 200000000 },
+ .divisors = { 1, 2, 4, 3, 5 },
+ .have_div3_pres = 1,
+};
+
+/* MCK0 layout. */
+static const struct clk_master_layout mck0_layout = {
+ .mask = 0x773,
+ .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),
+};
+
+static void __init sama7g5_pmc_setup(struct device_node *np)
+{
+ const char *td_slck_name, *md_slck_name, *mainxtal_name;
+ struct pmc_data *sama7g5_pmc;
+ const char *parent_names[10];
+ void **alloc_mem = NULL;
+ int alloc_mem_size = 0;
+ struct regmap *regmap;
+ struct clk_hw *hw;
+ bool bypass;
+ int i, j;
+
+ i = of_property_match_string(np, "clock-names", "td_slck");
+ if (i < 0)
+ return;
+
+ td_slck_name = of_clk_get_parent_name(np, i);
+
+ i = of_property_match_string(np, "clock-names", "md_slck");
+ if (i < 0)
+ return;
+
+ md_slck_name = of_clk_get_parent_name(np, i);
+
+ i = of_property_match_string(np, "clock-names", "main_xtal");
+ if (i < 0)
+ return;
+
+ mainxtal_name = of_clk_get_parent_name(np, i);
+
+ regmap = device_node_to_regmap(np);
+ if (IS_ERR(regmap))
+ return;
+
+ sama7g5_pmc = pmc_data_allocate(PMC_MCK1 + 1,
+ nck(sama7g5_systemck),
+ nck(sama7g5_periphck),
+ nck(sama7g5_gck), 8);
+ if (!sama7g5_pmc)
+ return;
+
+ alloc_mem = kmalloc(sizeof(void *) *
+ (ARRAY_SIZE(sama7g5_mckx) + ARRAY_SIZE(sama7g5_gck)),
+ GFP_KERNEL);
+ if (!alloc_mem)
+ goto err_free;
+
+ hw = at91_clk_register_main_rc_osc(regmap, "main_rc_osc", 12000000,
+ 50000000);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ bypass = of_property_read_bool(np, "atmel,osc-bypass");
+
+ hw = at91_clk_register_main_osc(regmap, "main_osc", mainxtal_name,
+ bypass);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ parent_names[0] = "main_rc_osc";
+ parent_names[1] = "main_osc";
+ hw = at91_clk_register_sam9x5_main(regmap, "mainck", parent_names, 2);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sama7g5_pmc->chws[PMC_MAIN] = hw;
+
+ for (i = 0; i < PLL_ID_MAX; i++) {
+ for (j = 0; j < 3; j++) {
+ struct clk_hw *parent_hw;
+
+ if (!sama7g5_plls[i][j].n)
+ continue;
+
+ switch (sama7g5_plls[i][j].t) {
+ case PLL_TYPE_FRAC:
+ if (!strcmp(sama7g5_plls[i][j].p, "mainck"))
+ parent_hw = sama7g5_pmc->chws[PMC_MAIN];
+ else
+ parent_hw = __clk_get_hw(of_clk_get_by_name(np,
+ sama7g5_plls[i][j].p));
+
+ hw = sam9x60_clk_register_frac_pll(regmap,
+ &pmc_pll_lock, sama7g5_plls[i][j].n,
+ sama7g5_plls[i][j].p, parent_hw, i,
+ sama7g5_plls[i][j].c,
+ sama7g5_plls[i][j].l,
+ sama7g5_plls[i][j].f);
+ break;
+
+ case PLL_TYPE_DIV:
+ hw = sam9x60_clk_register_div_pll(regmap,
+ &pmc_pll_lock, sama7g5_plls[i][j].n,
+ sama7g5_plls[i][j].p, i,
+ sama7g5_plls[i][j].c,
+ sama7g5_plls[i][j].l,
+ sama7g5_plls[i][j].f,
+ sama7g5_plls[i][j].safe_div);
+ break;
+
+ default:
+ continue;
+ }
+
+ if (IS_ERR(hw))
+ goto err_free;
+
+ if (sama7g5_plls[i][j].eid)
+ sama7g5_pmc->chws[sama7g5_plls[i][j].eid] = hw;
+ }
+ }
+
+ parent_names[0] = "cpupll_divpmcck";
+ hw = at91_clk_register_master_div(regmap, "mck0", "cpupll_divpmcck",
+ &mck0_layout, &mck0_characteristics,
+ &pmc_mck0_lock, CLK_GET_RATE_NOCACHE, 5);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sama7g5_pmc->chws[PMC_MCK] = hw;
+
+ parent_names[0] = md_slck_name;
+ parent_names[1] = td_slck_name;
+ parent_names[2] = "mainck";
+ for (i = 0; i < ARRAY_SIZE(sama7g5_mckx); i++) {
+ u8 num_parents = 3 + sama7g5_mckx[i].ep_count;
+ u32 *mux_table;
+
+ mux_table = kmalloc_array(num_parents, sizeof(*mux_table),
+ GFP_KERNEL);
+ if (!mux_table)
+ goto err_free;
+
+ SAMA7G5_INIT_TABLE(mux_table, 3);
+ SAMA7G5_FILL_TABLE(&mux_table[3], sama7g5_mckx[i].ep_mux_table,
+ sama7g5_mckx[i].ep_count);
+ SAMA7G5_FILL_TABLE(&parent_names[3], sama7g5_mckx[i].ep,
+ sama7g5_mckx[i].ep_count);
+
+ hw = at91_clk_sama7g5_register_master(regmap, sama7g5_mckx[i].n,
+ num_parents, parent_names, mux_table,
+ &pmc_mckX_lock, sama7g5_mckx[i].id,
+ sama7g5_mckx[i].c,
+ sama7g5_mckx[i].ep_chg_id);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ alloc_mem[alloc_mem_size++] = mux_table;
+
+ if (sama7g5_mckx[i].eid)
+ sama7g5_pmc->chws[sama7g5_mckx[i].eid] = hw;
+ }
+
+ hw = at91_clk_sama7g5_register_utmi(regmap, "utmick", "main_xtal");
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sama7g5_pmc->chws[PMC_UTMI] = hw;
+
+ parent_names[0] = md_slck_name;
+ parent_names[1] = td_slck_name;
+ parent_names[2] = "mainck";
+ parent_names[3] = "syspll_divpmcck";
+ parent_names[4] = "ddrpll_divpmcck";
+ parent_names[5] = "imgpll_divpmcck";
+ parent_names[6] = "baudpll_divpmcck";
+ parent_names[7] = "audiopll_divpmcck";
+ parent_names[8] = "ethpll_divpmcck";
+ for (i = 0; i < 8; i++) {
+ char name[6];
+
+ snprintf(name, sizeof(name), "prog%d", i);
+
+ hw = at91_clk_register_programmable(regmap, name, parent_names,
+ 9, i,
+ &programmable_layout,
+ sama7g5_prog_mux_table);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sama7g5_pmc->pchws[i] = hw;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(sama7g5_systemck); i++) {
+ hw = at91_clk_register_system(regmap, sama7g5_systemck[i].n,
+ sama7g5_systemck[i].p,
+ sama7g5_systemck[i].id);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sama7g5_pmc->shws[sama7g5_systemck[i].id] = hw;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(sama7g5_periphck); i++) {
+ hw = at91_clk_register_sam9x5_peripheral(regmap, &pmc_pcr_lock,
+ &sama7g5_pcr_layout,
+ sama7g5_periphck[i].n,
+ sama7g5_periphck[i].p,
+ sama7g5_periphck[i].id,
+ &sama7g5_periphck[i].r,
+ sama7g5_periphck[i].chgp ? 0 :
+ INT_MIN);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sama7g5_pmc->phws[sama7g5_periphck[i].id] = hw;
+ }
+
+ parent_names[0] = md_slck_name;
+ parent_names[1] = td_slck_name;
+ parent_names[2] = "mainck";
+ for (i = 0; i < ARRAY_SIZE(sama7g5_gck); i++) {
+ u8 num_parents = 3 + sama7g5_gck[i].pp_count;
+ u32 *mux_table;
+
+ mux_table = kmalloc_array(num_parents, sizeof(*mux_table),
+ GFP_KERNEL);
+ if (!mux_table)
+ goto err_free;
+
+ SAMA7G5_INIT_TABLE(mux_table, 3);
+ SAMA7G5_FILL_TABLE(&mux_table[3], sama7g5_gck[i].pp_mux_table,
+ sama7g5_gck[i].pp_count);
+ SAMA7G5_FILL_TABLE(&parent_names[3], sama7g5_gck[i].pp,
+ sama7g5_gck[i].pp_count);
+
+ hw = at91_clk_register_generated(regmap, &pmc_pcr_lock,
+ &sama7g5_pcr_layout,
+ sama7g5_gck[i].n,
+ parent_names, mux_table,
+ num_parents,
+ sama7g5_gck[i].id,
+ &sama7g5_gck[i].r,
+ sama7g5_gck[i].pp_chg_id);
+ if (IS_ERR(hw))
+ goto err_free;
+
+ sama7g5_pmc->ghws[sama7g5_gck[i].id] = hw;
+ alloc_mem[alloc_mem_size++] = mux_table;
+ }
+
+ of_clk_add_hw_provider(np, of_clk_hw_pmc_get, sama7g5_pmc);
+
+ return;
+
+err_free:
+ if (alloc_mem) {
+ for (i = 0; i < alloc_mem_size; i++)
+ kfree(alloc_mem[i]);
+ kfree(alloc_mem);
+ }
+
+ kfree(sama7g5_pmc);
+}
+
+/* Some clks are used for a clocksource */
+CLK_OF_DECLARE(sama7g5_pmc, "microchip,sama7g5-pmc", sama7g5_pmc_setup);
diff --git a/drivers/clk/at91/sckc.c b/drivers/clk/at91/sckc.c
new file mode 100644
index 000000000..fdc9b669f
--- /dev/null
+++ b/drivers/clk/at91/sckc.c
@@ -0,0 +1,633 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * drivers/clk/at91/sckc.c
+ *
+ * Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com>
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/delay.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/io.h>
+
+#define SLOW_CLOCK_FREQ 32768
+#define SLOWCK_SW_CYCLES 5
+#define SLOWCK_SW_TIME_USEC ((SLOWCK_SW_CYCLES * USEC_PER_SEC) / \
+ SLOW_CLOCK_FREQ)
+
+#define AT91_SCKC_CR 0x00
+
+struct clk_slow_bits {
+ u32 cr_rcen;
+ u32 cr_osc32en;
+ u32 cr_osc32byp;
+ u32 cr_oscsel;
+};
+
+struct clk_slow_osc {
+ struct clk_hw hw;
+ void __iomem *sckcr;
+ const struct clk_slow_bits *bits;
+ unsigned long startup_usec;
+};
+
+#define to_clk_slow_osc(hw) container_of(hw, struct clk_slow_osc, hw)
+
+struct clk_sama5d4_slow_osc {
+ struct clk_hw hw;
+ void __iomem *sckcr;
+ const struct clk_slow_bits *bits;
+ unsigned long startup_usec;
+ bool prepared;
+};
+
+#define to_clk_sama5d4_slow_osc(hw) container_of(hw, struct clk_sama5d4_slow_osc, hw)
+
+struct clk_slow_rc_osc {
+ struct clk_hw hw;
+ void __iomem *sckcr;
+ const struct clk_slow_bits *bits;
+ unsigned long frequency;
+ unsigned long accuracy;
+ unsigned long startup_usec;
+};
+
+#define to_clk_slow_rc_osc(hw) container_of(hw, struct clk_slow_rc_osc, hw)
+
+struct clk_sam9x5_slow {
+ struct clk_hw hw;
+ void __iomem *sckcr;
+ const struct clk_slow_bits *bits;
+ u8 parent;
+};
+
+#define to_clk_sam9x5_slow(hw) container_of(hw, struct clk_sam9x5_slow, hw)
+
+static int clk_slow_osc_prepare(struct clk_hw *hw)
+{
+ struct clk_slow_osc *osc = to_clk_slow_osc(hw);
+ void __iomem *sckcr = osc->sckcr;
+ u32 tmp = readl(sckcr);
+
+ if (tmp & (osc->bits->cr_osc32byp | osc->bits->cr_osc32en))
+ return 0;
+
+ writel(tmp | osc->bits->cr_osc32en, sckcr);
+
+ if (system_state < SYSTEM_RUNNING)
+ udelay(osc->startup_usec);
+ else
+ usleep_range(osc->startup_usec, osc->startup_usec + 1);
+
+ return 0;
+}
+
+static void clk_slow_osc_unprepare(struct clk_hw *hw)
+{
+ struct clk_slow_osc *osc = to_clk_slow_osc(hw);
+ void __iomem *sckcr = osc->sckcr;
+ u32 tmp = readl(sckcr);
+
+ if (tmp & osc->bits->cr_osc32byp)
+ return;
+
+ writel(tmp & ~osc->bits->cr_osc32en, sckcr);
+}
+
+static int clk_slow_osc_is_prepared(struct clk_hw *hw)
+{
+ struct clk_slow_osc *osc = to_clk_slow_osc(hw);
+ void __iomem *sckcr = osc->sckcr;
+ u32 tmp = readl(sckcr);
+
+ if (tmp & osc->bits->cr_osc32byp)
+ return 1;
+
+ return !!(tmp & osc->bits->cr_osc32en);
+}
+
+static const struct clk_ops slow_osc_ops = {
+ .prepare = clk_slow_osc_prepare,
+ .unprepare = clk_slow_osc_unprepare,
+ .is_prepared = clk_slow_osc_is_prepared,
+};
+
+static struct clk_hw * __init
+at91_clk_register_slow_osc(void __iomem *sckcr,
+ const char *name,
+ const char *parent_name,
+ unsigned long startup,
+ bool bypass,
+ const struct clk_slow_bits *bits)
+{
+ struct clk_slow_osc *osc;
+ struct clk_hw *hw;
+ struct clk_init_data init;
+ int ret;
+
+ if (!sckcr || !name || !parent_name)
+ return ERR_PTR(-EINVAL);
+
+ osc = kzalloc(sizeof(*osc), GFP_KERNEL);
+ if (!osc)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &slow_osc_ops;
+ init.parent_names = &parent_name;
+ init.num_parents = 1;
+ init.flags = CLK_IGNORE_UNUSED;
+
+ osc->hw.init = &init;
+ osc->sckcr = sckcr;
+ osc->startup_usec = startup;
+ osc->bits = bits;
+
+ if (bypass)
+ writel((readl(sckcr) & ~osc->bits->cr_osc32en) |
+ osc->bits->cr_osc32byp, sckcr);
+
+ hw = &osc->hw;
+ ret = clk_hw_register(NULL, &osc->hw);
+ if (ret) {
+ kfree(osc);
+ hw = ERR_PTR(ret);
+ }
+
+ return hw;
+}
+
+static void at91_clk_unregister_slow_osc(struct clk_hw *hw)
+{
+ struct clk_slow_osc *osc = to_clk_slow_osc(hw);
+
+ clk_hw_unregister(hw);
+ kfree(osc);
+}
+
+static unsigned long clk_slow_rc_osc_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct clk_slow_rc_osc *osc = to_clk_slow_rc_osc(hw);
+
+ return osc->frequency;
+}
+
+static unsigned long clk_slow_rc_osc_recalc_accuracy(struct clk_hw *hw,
+ unsigned long parent_acc)
+{
+ struct clk_slow_rc_osc *osc = to_clk_slow_rc_osc(hw);
+
+ return osc->accuracy;
+}
+
+static int clk_slow_rc_osc_prepare(struct clk_hw *hw)
+{
+ struct clk_slow_rc_osc *osc = to_clk_slow_rc_osc(hw);
+ void __iomem *sckcr = osc->sckcr;
+
+ writel(readl(sckcr) | osc->bits->cr_rcen, sckcr);
+
+ if (system_state < SYSTEM_RUNNING)
+ udelay(osc->startup_usec);
+ else
+ usleep_range(osc->startup_usec, osc->startup_usec + 1);
+
+ return 0;
+}
+
+static void clk_slow_rc_osc_unprepare(struct clk_hw *hw)
+{
+ struct clk_slow_rc_osc *osc = to_clk_slow_rc_osc(hw);
+ void __iomem *sckcr = osc->sckcr;
+
+ writel(readl(sckcr) & ~osc->bits->cr_rcen, sckcr);
+}
+
+static int clk_slow_rc_osc_is_prepared(struct clk_hw *hw)
+{
+ struct clk_slow_rc_osc *osc = to_clk_slow_rc_osc(hw);
+
+ return !!(readl(osc->sckcr) & osc->bits->cr_rcen);
+}
+
+static const struct clk_ops slow_rc_osc_ops = {
+ .prepare = clk_slow_rc_osc_prepare,
+ .unprepare = clk_slow_rc_osc_unprepare,
+ .is_prepared = clk_slow_rc_osc_is_prepared,
+ .recalc_rate = clk_slow_rc_osc_recalc_rate,
+ .recalc_accuracy = clk_slow_rc_osc_recalc_accuracy,
+};
+
+static struct clk_hw * __init
+at91_clk_register_slow_rc_osc(void __iomem *sckcr,
+ const char *name,
+ unsigned long frequency,
+ unsigned long accuracy,
+ unsigned long startup,
+ const struct clk_slow_bits *bits)
+{
+ struct clk_slow_rc_osc *osc;
+ struct clk_hw *hw;
+ struct clk_init_data init;
+ int ret;
+
+ if (!sckcr || !name)
+ return ERR_PTR(-EINVAL);
+
+ osc = kzalloc(sizeof(*osc), GFP_KERNEL);
+ if (!osc)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &slow_rc_osc_ops;
+ init.parent_names = NULL;
+ init.num_parents = 0;
+ init.flags = CLK_IGNORE_UNUSED;
+
+ osc->hw.init = &init;
+ osc->sckcr = sckcr;
+ osc->bits = bits;
+ osc->frequency = frequency;
+ osc->accuracy = accuracy;
+ osc->startup_usec = startup;
+
+ hw = &osc->hw;
+ ret = clk_hw_register(NULL, &osc->hw);
+ if (ret) {
+ kfree(osc);
+ hw = ERR_PTR(ret);
+ }
+
+ return hw;
+}
+
+static void at91_clk_unregister_slow_rc_osc(struct clk_hw *hw)
+{
+ struct clk_slow_rc_osc *osc = to_clk_slow_rc_osc(hw);
+
+ clk_hw_unregister(hw);
+ kfree(osc);
+}
+
+static int clk_sam9x5_slow_set_parent(struct clk_hw *hw, u8 index)
+{
+ struct clk_sam9x5_slow *slowck = to_clk_sam9x5_slow(hw);
+ void __iomem *sckcr = slowck->sckcr;
+ u32 tmp;
+
+ if (index > 1)
+ return -EINVAL;
+
+ tmp = readl(sckcr);
+
+ if ((!index && !(tmp & slowck->bits->cr_oscsel)) ||
+ (index && (tmp & slowck->bits->cr_oscsel)))
+ return 0;
+
+ if (index)
+ tmp |= slowck->bits->cr_oscsel;
+ else
+ tmp &= ~slowck->bits->cr_oscsel;
+
+ writel(tmp, sckcr);
+
+ if (system_state < SYSTEM_RUNNING)
+ udelay(SLOWCK_SW_TIME_USEC);
+ else
+ usleep_range(SLOWCK_SW_TIME_USEC, SLOWCK_SW_TIME_USEC + 1);
+
+ return 0;
+}
+
+static u8 clk_sam9x5_slow_get_parent(struct clk_hw *hw)
+{
+ struct clk_sam9x5_slow *slowck = to_clk_sam9x5_slow(hw);
+
+ return !!(readl(slowck->sckcr) & slowck->bits->cr_oscsel);
+}
+
+static const struct clk_ops sam9x5_slow_ops = {
+ .set_parent = clk_sam9x5_slow_set_parent,
+ .get_parent = clk_sam9x5_slow_get_parent,
+};
+
+static struct clk_hw * __init
+at91_clk_register_sam9x5_slow(void __iomem *sckcr,
+ const char *name,
+ const char **parent_names,
+ int num_parents,
+ const struct clk_slow_bits *bits)
+{
+ struct clk_sam9x5_slow *slowck;
+ struct clk_hw *hw;
+ struct clk_init_data init;
+ int ret;
+
+ if (!sckcr || !name || !parent_names || !num_parents)
+ return ERR_PTR(-EINVAL);
+
+ slowck = kzalloc(sizeof(*slowck), GFP_KERNEL);
+ if (!slowck)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &sam9x5_slow_ops;
+ init.parent_names = parent_names;
+ init.num_parents = num_parents;
+ init.flags = 0;
+
+ slowck->hw.init = &init;
+ slowck->sckcr = sckcr;
+ slowck->bits = bits;
+ slowck->parent = !!(readl(sckcr) & slowck->bits->cr_oscsel);
+
+ hw = &slowck->hw;
+ ret = clk_hw_register(NULL, &slowck->hw);
+ if (ret) {
+ kfree(slowck);
+ hw = ERR_PTR(ret);
+ }
+
+ return hw;
+}
+
+static void at91_clk_unregister_sam9x5_slow(struct clk_hw *hw)
+{
+ struct clk_sam9x5_slow *slowck = to_clk_sam9x5_slow(hw);
+
+ clk_hw_unregister(hw);
+ kfree(slowck);
+}
+
+static void __init at91sam9x5_sckc_register(struct device_node *np,
+ unsigned int rc_osc_startup_us,
+ const struct clk_slow_bits *bits)
+{
+ const char *parent_names[2] = { "slow_rc_osc", "slow_osc" };
+ void __iomem *regbase = of_iomap(np, 0);
+ struct device_node *child = NULL;
+ const char *xtal_name;
+ struct clk_hw *slow_rc, *slow_osc, *slowck;
+ bool bypass;
+ int ret;
+
+ if (!regbase)
+ return;
+
+ slow_rc = at91_clk_register_slow_rc_osc(regbase, parent_names[0],
+ 32768, 50000000,
+ rc_osc_startup_us, bits);
+ if (IS_ERR(slow_rc))
+ return;
+
+ xtal_name = of_clk_get_parent_name(np, 0);
+ if (!xtal_name) {
+ /* DT backward compatibility */
+ child = of_get_compatible_child(np, "atmel,at91sam9x5-clk-slow-osc");
+ if (!child)
+ goto unregister_slow_rc;
+
+ xtal_name = of_clk_get_parent_name(child, 0);
+ bypass = of_property_read_bool(child, "atmel,osc-bypass");
+
+ child = of_get_compatible_child(np, "atmel,at91sam9x5-clk-slow");
+ } else {
+ bypass = of_property_read_bool(np, "atmel,osc-bypass");
+ }
+
+ if (!xtal_name)
+ goto unregister_slow_rc;
+
+ slow_osc = at91_clk_register_slow_osc(regbase, parent_names[1],
+ xtal_name, 1200000, bypass, bits);
+ if (IS_ERR(slow_osc))
+ goto unregister_slow_rc;
+
+ slowck = at91_clk_register_sam9x5_slow(regbase, "slowck", parent_names,
+ 2, bits);
+ if (IS_ERR(slowck))
+ goto unregister_slow_osc;
+
+ /* DT backward compatibility */
+ if (child)
+ ret = of_clk_add_hw_provider(child, of_clk_hw_simple_get,
+ slowck);
+ else
+ ret = of_clk_add_hw_provider(np, of_clk_hw_simple_get, slowck);
+
+ if (WARN_ON(ret))
+ goto unregister_slowck;
+
+ return;
+
+unregister_slowck:
+ at91_clk_unregister_sam9x5_slow(slowck);
+unregister_slow_osc:
+ at91_clk_unregister_slow_osc(slow_osc);
+unregister_slow_rc:
+ at91_clk_unregister_slow_rc_osc(slow_rc);
+}
+
+static const struct clk_slow_bits at91sam9x5_bits = {
+ .cr_rcen = BIT(0),
+ .cr_osc32en = BIT(1),
+ .cr_osc32byp = BIT(2),
+ .cr_oscsel = BIT(3),
+};
+
+static void __init of_at91sam9x5_sckc_setup(struct device_node *np)
+{
+ at91sam9x5_sckc_register(np, 75, &at91sam9x5_bits);
+}
+CLK_OF_DECLARE(at91sam9x5_clk_sckc, "atmel,at91sam9x5-sckc",
+ of_at91sam9x5_sckc_setup);
+
+static void __init of_sama5d3_sckc_setup(struct device_node *np)
+{
+ at91sam9x5_sckc_register(np, 500, &at91sam9x5_bits);
+}
+CLK_OF_DECLARE(sama5d3_clk_sckc, "atmel,sama5d3-sckc",
+ of_sama5d3_sckc_setup);
+
+static const struct clk_slow_bits at91sam9x60_bits = {
+ .cr_osc32en = BIT(1),
+ .cr_osc32byp = BIT(2),
+ .cr_oscsel = BIT(24),
+};
+
+static void __init of_sam9x60_sckc_setup(struct device_node *np)
+{
+ void __iomem *regbase = of_iomap(np, 0);
+ struct clk_hw_onecell_data *clk_data;
+ struct clk_hw *slow_rc, *slow_osc;
+ const char *xtal_name;
+ const char *parent_names[2] = { "slow_rc_osc", "slow_osc" };
+ bool bypass;
+ int ret;
+
+ if (!regbase)
+ return;
+
+ slow_rc = clk_hw_register_fixed_rate_with_accuracy(NULL, parent_names[0],
+ NULL, 0, 32768,
+ 93750000);
+ if (IS_ERR(slow_rc))
+ return;
+
+ xtal_name = of_clk_get_parent_name(np, 0);
+ if (!xtal_name)
+ goto unregister_slow_rc;
+
+ bypass = of_property_read_bool(np, "atmel,osc-bypass");
+ slow_osc = at91_clk_register_slow_osc(regbase, parent_names[1],
+ xtal_name, 5000000, bypass,
+ &at91sam9x60_bits);
+ if (IS_ERR(slow_osc))
+ goto unregister_slow_rc;
+
+ clk_data = kzalloc(struct_size(clk_data, hws, 2), GFP_KERNEL);
+ if (!clk_data)
+ goto unregister_slow_osc;
+
+ /* MD_SLCK and TD_SLCK. */
+ clk_data->num = 2;
+ clk_data->hws[0] = clk_hw_register_fixed_rate(NULL, "md_slck",
+ parent_names[0],
+ 0, 32768);
+ if (IS_ERR(clk_data->hws[0]))
+ goto clk_data_free;
+
+ clk_data->hws[1] = at91_clk_register_sam9x5_slow(regbase, "td_slck",
+ parent_names, 2,
+ &at91sam9x60_bits);
+ if (IS_ERR(clk_data->hws[1]))
+ goto unregister_md_slck;
+
+ ret = of_clk_add_hw_provider(np, of_clk_hw_onecell_get, clk_data);
+ if (WARN_ON(ret))
+ goto unregister_td_slck;
+
+ return;
+
+unregister_td_slck:
+ at91_clk_unregister_sam9x5_slow(clk_data->hws[1]);
+unregister_md_slck:
+ clk_hw_unregister(clk_data->hws[0]);
+clk_data_free:
+ kfree(clk_data);
+unregister_slow_osc:
+ at91_clk_unregister_slow_osc(slow_osc);
+unregister_slow_rc:
+ clk_hw_unregister(slow_rc);
+}
+CLK_OF_DECLARE(sam9x60_clk_sckc, "microchip,sam9x60-sckc",
+ of_sam9x60_sckc_setup);
+
+static int clk_sama5d4_slow_osc_prepare(struct clk_hw *hw)
+{
+ struct clk_sama5d4_slow_osc *osc = to_clk_sama5d4_slow_osc(hw);
+
+ if (osc->prepared)
+ return 0;
+
+ /*
+ * Assume that if it has already been selected (for example by the
+ * bootloader), enough time has already passed.
+ */
+ if ((readl(osc->sckcr) & osc->bits->cr_oscsel)) {
+ osc->prepared = true;
+ return 0;
+ }
+
+ if (system_state < SYSTEM_RUNNING)
+ udelay(osc->startup_usec);
+ else
+ usleep_range(osc->startup_usec, osc->startup_usec + 1);
+ osc->prepared = true;
+
+ return 0;
+}
+
+static int clk_sama5d4_slow_osc_is_prepared(struct clk_hw *hw)
+{
+ struct clk_sama5d4_slow_osc *osc = to_clk_sama5d4_slow_osc(hw);
+
+ return osc->prepared;
+}
+
+static const struct clk_ops sama5d4_slow_osc_ops = {
+ .prepare = clk_sama5d4_slow_osc_prepare,
+ .is_prepared = clk_sama5d4_slow_osc_is_prepared,
+};
+
+static const struct clk_slow_bits at91sama5d4_bits = {
+ .cr_oscsel = BIT(3),
+};
+
+static void __init of_sama5d4_sckc_setup(struct device_node *np)
+{
+ void __iomem *regbase = of_iomap(np, 0);
+ struct clk_hw *slow_rc, *slowck;
+ struct clk_sama5d4_slow_osc *osc;
+ struct clk_init_data init;
+ const char *xtal_name;
+ const char *parent_names[2] = { "slow_rc_osc", "slow_osc" };
+ int ret;
+
+ if (!regbase)
+ return;
+
+ slow_rc = clk_hw_register_fixed_rate_with_accuracy(NULL,
+ parent_names[0],
+ NULL, 0, 32768,
+ 250000000);
+ if (IS_ERR(slow_rc))
+ return;
+
+ xtal_name = of_clk_get_parent_name(np, 0);
+
+ osc = kzalloc(sizeof(*osc), GFP_KERNEL);
+ if (!osc)
+ goto unregister_slow_rc;
+
+ init.name = parent_names[1];
+ init.ops = &sama5d4_slow_osc_ops;
+ init.parent_names = &xtal_name;
+ init.num_parents = 1;
+ init.flags = CLK_IGNORE_UNUSED;
+
+ osc->hw.init = &init;
+ osc->sckcr = regbase;
+ osc->startup_usec = 1200000;
+ osc->bits = &at91sama5d4_bits;
+
+ ret = clk_hw_register(NULL, &osc->hw);
+ if (ret)
+ goto free_slow_osc_data;
+
+ slowck = at91_clk_register_sam9x5_slow(regbase, "slowck",
+ parent_names, 2,
+ &at91sama5d4_bits);
+ if (IS_ERR(slowck))
+ goto unregister_slow_osc;
+
+ ret = of_clk_add_hw_provider(np, of_clk_hw_simple_get, slowck);
+ if (WARN_ON(ret))
+ goto unregister_slowck;
+
+ return;
+
+unregister_slowck:
+ at91_clk_unregister_sam9x5_slow(slowck);
+unregister_slow_osc:
+ clk_hw_unregister(&osc->hw);
+free_slow_osc_data:
+ kfree(osc);
+unregister_slow_rc:
+ clk_hw_unregister(slow_rc);
+}
+CLK_OF_DECLARE(sama5d4_clk_sckc, "atmel,sama5d4-sckc",
+ of_sama5d4_sckc_setup);