diff options
Diffstat (limited to 'drivers/clk/at91')
32 files changed, 12261 insertions, 0 deletions
diff --git a/drivers/clk/at91/Makefile b/drivers/clk/at91/Makefile new file mode 100644 index 0000000000..89061b85e7 --- /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 +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 dt-compat.o +obj-$(CONFIG_SOC_AT91SAM9) += at91sam9260.o at91sam9rl.o at91sam9x5.o dt-compat.o +obj-$(CONFIG_SOC_AT91SAM9) += at91sam9g45.o dt-compat.o +obj-$(CONFIG_SOC_AT91SAM9) += at91sam9n12.o at91sam9x5.o dt-compat.o +obj-$(CONFIG_SOC_SAM9X60) += sam9x60.o +obj-$(CONFIG_SOC_SAMA5D3) += sama5d3.o dt-compat.o +obj-$(CONFIG_SOC_SAMA5D4) += sama5d4.o dt-compat.o +obj-$(CONFIG_SOC_SAMA5D2) += sama5d2.o dt-compat.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 0000000000..3f19e737ae --- /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, NULL, + bypass); + if (IS_ERR(hw)) + goto err_free; + + hw = at91_clk_register_rm9200_main(regmap, "mainck", "main_osc", NULL); + 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, NULL, + &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", NULL, + &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, NULL, 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, NULL, + at91rm9200_systemck[i].id, 0); + 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", NULL, + 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 0000000000..0799a13060 --- /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, NULL, + bypass); + if (IS_ERR(hw)) + goto err_free; + + hw = at91_clk_register_rm9200_main(regmap, "mainck", "main_osc", NULL); + 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, NULL, + &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", NULL, + &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, NULL, 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, NULL, + data->sck[i].id, 0); + 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", NULL, + 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 0000000000..f45a7b80f7 --- /dev/null +++ b/drivers/clk/at91/at91sam9g45.c @@ -0,0 +1,236 @@ +// 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; + unsigned long flags; + u8 id; +} at91sam9g45_systemck[] = { + /* + * ddrck feeds DDR controller and is enabled by bootloader thus we need + * to keep it enabled in case there is no Linux consumer for it. + */ + { .n = "ddrck", .p = "masterck_div", .id = 2, .flags = CLK_IS_CRITICAL }, + { .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, NULL, + bypass); + if (IS_ERR(hw)) + goto err_free; + + hw = at91_clk_register_rm9200_main(regmap, "mainck", "main_osc", NULL); + 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", NULL); + 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, NULL, + &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", NULL, + &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, NULL, 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, NULL, + at91sam9g45_systemck[i].id, + at91sam9g45_systemck[i].flags); + 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", NULL, + 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 0000000000..751786184a --- /dev/null +++ b/drivers/clk/at91/at91sam9n12.c @@ -0,0 +1,263 @@ +// 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; + unsigned long flags; + u8 id; +} at91sam9n12_systemck[] = { + /* + * ddrck feeds DDR controller and is enabled by bootloader thus we need + * to keep it enabled in case there is no Linux consumer for it. + */ + { .n = "ddrck", .p = "masterck_div", .id = 2, .flags = CLK_IS_CRITICAL }, + { .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, NULL, + 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, NULL, 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, NULL, + &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", NULL, + &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, NULL, 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, NULL, + at91sam9n12_systemck[i].id, + at91sam9n12_systemck[i].flags); + 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", NULL, + at91sam9n12_periphck[i].id, + &range, INT_MIN, 0); + 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 0000000000..969f809e7d --- /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, NULL); + 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", NULL); + 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, NULL, + &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", NULL, + &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, NULL, 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, NULL, + at91sam9rl_systemck[i].id, 0); + 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", NULL, + 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 0000000000..3b801d12fa --- /dev/null +++ b/drivers/clk/at91/at91sam9x5.c @@ -0,0 +1,340 @@ +// 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; + unsigned long flags; + u8 id; +} at91sam9x5_systemck[] = { + /* + * ddrck feeds DDR controller and is enabled by bootloader thus we need + * to keep it enabled in case there is no Linux consumer for it. + */ + { .n = "ddrck", .p = "masterck_div", .id = 2, .flags = CLK_IS_CRITICAL }, + { .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, NULL, + 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, NULL, 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", NULL); + 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, NULL, + &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", NULL, + &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, NULL, 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, NULL, + at91sam9x5_systemck[i].id, + at91sam9x5_systemck[i].flags); + 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", + NULL, 3, 0); + 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", NULL, + at91sam9x5_periphck[i].id, + &range, INT_MIN, 0); + 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", NULL, + extra_pcks[i].id, + &range, INT_MIN, 0); + 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 0000000000..a92da64c12 --- /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 0000000000..4b4edeecc8 --- /dev/null +++ b/drivers/clk/at91/clk-generated.c @@ -0,0 +1,368 @@ +// 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, + struct clk_hw **parent_hws, + 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; + + if (!(parent_names || parent_hws)) + return ERR_PTR(-ENOMEM); + + gck = kzalloc(sizeof(*gck), GFP_KERNEL); + if (!gck) + return ERR_PTR(-ENOMEM); + + init.name = name; + init.ops = &generated_ops; + if (parent_hws) + init.parent_hws = (const struct clk_hw **)parent_hws; + else + 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 0000000000..1e6c12eeda --- /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 0000000000..fe6ce172b8 --- /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 0000000000..9b462becc6 --- /dev/null +++ b/drivers/clk/at91/clk-main.c @@ -0,0 +1,596 @@ +// 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, + struct clk_parent_data *parent_data, + bool bypass) +{ + struct clk_main_osc *osc; + struct clk_init_data init = {}; + struct clk_hw *hw; + int ret; + + if (!name || !(parent_name || parent_data)) + return ERR_PTR(-EINVAL); + + osc = kzalloc(sizeof(*osc), GFP_KERNEL); + if (!osc) + return ERR_PTR(-ENOMEM); + + init.name = name; + init.ops = &main_osc_ops; + if (parent_data) + init.parent_data = (const struct clk_parent_data *)parent_data; + else + 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_hw *parent_hw) +{ + 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 || parent_hw)) + return ERR_PTR(-EINVAL); + + clkmain = kzalloc(sizeof(*clkmain), GFP_KERNEL); + if (!clkmain) + return ERR_PTR(-ENOMEM); + + init.name = name; + init.ops = &rm9200_main_ops; + if (parent_hw) + init.parent_hws = (const struct clk_hw **)&parent_hw; + else + 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, + .determine_rate = clk_hw_determine_rate_no_reparent, + .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, + struct clk_hw **parent_hws, + 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_hws || 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; + if (parent_hws) + init.parent_hws = (const struct clk_hw **)parent_hws; + else + 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 0000000000..15c46489ba --- /dev/null +++ b/drivers/clk/at91/clk-master.c @@ -0,0 +1,882 @@ +// 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, + struct clk_hw **parent_hws, + 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 || parent_hws) || !lock) + return ERR_PTR(-EINVAL); + + master = kzalloc(sizeof(*master), GFP_KERNEL); + if (!master) + return ERR_PTR(-ENOMEM); + + init.name = name; + init.ops = ops; + if (parent_hws) + init.parent_hws = (const struct clk_hw **)parent_hws; + else + 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, + struct clk_hw **parent_hws, + 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, parent_hws, 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, + struct clk_hw *parent_hw, 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 ? &parent_name : NULL, + parent_hw ? &parent_hw : NULL, 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, + struct clk_hw **parent_hws, + 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 || parent_hws) || !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; + if (parent_hws) + init.parent_hws = (const struct clk_hw **)parent_hws; + else + 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 0000000000..c173a44c80 --- /dev/null +++ b/drivers/clk/at91/clk-peripheral.c @@ -0,0 +1,503 @@ +// 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, struct clk_hw *parent_hw, + u32 id) +{ + struct clk_peripheral *periph; + struct clk_init_data init = {}; + struct clk_hw *hw; + int ret; + + if (!name || !(parent_name || parent_hw) || 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; + if (parent_hw) + init.parent_hws = (const struct clk_hw **)&parent_hw; + else + 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, + struct clk_hw *parent_hw, + u32 id, const struct clk_range *range, + int chg_pid, unsigned long flags) +{ + struct clk_sam9x5_peripheral *periph; + struct clk_init_data init = {}; + struct clk_hw *hw; + int ret; + + if (!name || !(parent_name || parent_hw)) + return ERR_PTR(-EINVAL); + + periph = kzalloc(sizeof(*periph), GFP_KERNEL); + if (!periph) + return ERR_PTR(-ENOMEM); + + init.name = name; + if (parent_hw) + init.parent_hws = (const struct clk_hw **)&parent_hw; + else + init.parent_names = &parent_name; + init.num_parents = 1; + init.flags = flags; + if (chg_pid < 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 0000000000..249d6a53ce --- /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 0000000000..ba3a1839a9 --- /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 0000000000..1195fb4055 --- /dev/null +++ b/drivers/clk/at91/clk-programmable.c @@ -0,0 +1,281 @@ +// 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, + struct clk_hw **parent_hws, 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 || !(parent_names || parent_hws)) + return ERR_PTR(-EINVAL); + + prog = kzalloc(sizeof(*prog), GFP_KERNEL); + if (!prog) + return ERR_PTR(-ENOMEM); + + init.name = name; + init.ops = &programmable_ops; + if (parent_hws) + init.parent_hws = (const struct clk_hw **)parent_hws; + else + 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 0000000000..ff65f7b916 --- /dev/null +++ b/drivers/clk/at91/clk-sam9x60-pll.c @@ -0,0 +1,763 @@ +// 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; + if (parent_name) + init.parent_names = &parent_name; + else + init.parent_hws = (const struct clk_hw **)&parent_hw; + 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, + struct clk_hw *parent_hw, 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; + if (parent_hw) + init.parent_hws = (const struct clk_hw **)&parent_hw; + else + 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 0000000000..ac9f7a48b7 --- /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 0000000000..09c649c859 --- /dev/null +++ b/drivers/clk/at91/clk-smd.c @@ -0,0 +1,142 @@ +// 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 int at91sam9x5_clk_smd_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + unsigned long div; + unsigned long bestrate; + unsigned long tmp; + + if (req->rate >= req->best_parent_rate) { + req->rate = req->best_parent_rate; + return 0; + } + + div = req->best_parent_rate / req->rate; + if (div > SMD_MAX_DIV) { + req->rate = req->best_parent_rate / (SMD_MAX_DIV + 1); + return 0; + } + + bestrate = req->best_parent_rate / div; + tmp = req->best_parent_rate / (div + 1); + if (bestrate - req->rate > req->rate - tmp) + bestrate = tmp; + + req->rate = bestrate; + return 0; +} + +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, + .determine_rate = at91sam9x5_clk_smd_determine_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 0000000000..90eed39d07 --- /dev/null +++ b/drivers/clk/at91/clk-system.c @@ -0,0 +1,144 @@ +// 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, struct clk_hw *parent_hw, u8 id, + unsigned long flags) +{ + struct clk_system *sys; + struct clk_hw *hw; + struct clk_init_data init = {}; + int ret; + + if (!(parent_name || parent_hw) || 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; + if (parent_hw) + init.parent_hws = (const struct clk_hw **)&parent_hw; + else + init.parent_names = &parent_name; + init.num_parents = 1; + init.flags = CLK_SET_RATE_PARENT | flags; + + 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 0000000000..b0696a928a --- /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 0000000000..40c84f5af5 --- /dev/null +++ b/drivers/clk/at91/clk-utmi.c @@ -0,0 +1,296 @@ +// 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, + struct clk_hw *parent_hw, + const struct clk_ops *ops, unsigned long flags) +{ + struct clk_utmi *utmi; + struct clk_hw *hw; + struct clk_init_data init = {}; + int ret; + + if (!(parent_name || parent_hw)) + return ERR_PTR(-EINVAL); + + utmi = kzalloc(sizeof(*utmi), GFP_KERNEL); + if (!utmi) + return ERR_PTR(-ENOMEM); + + init.name = name; + init.ops = ops; + if (parent_hw) { + init.parent_hws = parent_hw ? (const struct clk_hw **)&parent_hw : NULL; + init.num_parents = parent_hw ? 1 : 0; + } else { + 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, + struct clk_hw *parent_hw) +{ + return at91_clk_register_utmi_internal(regmap_pmc, regmap_sfr, name, + parent_name, parent_hw, &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, struct clk_hw *parent_hw) +{ + return at91_clk_register_utmi_internal(regmap_pmc, NULL, name, + parent_name, parent_hw, &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 0000000000..a32dc2111b --- /dev/null +++ b/drivers/clk/at91/dt-compat.c @@ -0,0 +1,1066 @@ +// 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, 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, NULL, 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, NULL); + 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, NULL, + 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, NULL, layout, + characteristics, &mck_lock); + if (IS_ERR(hw)) + goto out_free_characteristics; + + hw = at91_clk_register_master_div(regmap, name, "masterck_pres", NULL, + 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, NULL, id); + } else { + struct clk_range range = CLK_RANGE(0, 0); + unsigned long flags = 0; + + of_at91_get_clk_range(periphclknp, + "atmel,clk-output-range", + &range); + + /* + * mpddr_clk feed DDR controller and is enabled by + * bootloader thus we need to keep it enabled in case + * there is no Linux consumer for it. + */ + if (!strcmp(periphclknp->name, "mpddr_clk")) + flags = CLK_IS_CRITICAL; + + hw = at91_clk_register_sam9x5_peripheral(regmap, + &pmc_pcr_lock, + &dt_pcr_layout, + name, + parent_name, + NULL, + id, &range, + INT_MIN, + flags); + } + + 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, NULL, 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) { + unsigned long flags = 0; + + 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); + + /* + * ddrck feeds DDR controller and is enabled by bootloader thus + * we need to keep it enabled in case there is no Linux consumer + * for it. + */ + if (!strcmp(sysclknp->name, "ddrck")) + flags = CLK_IS_CRITICAL; + + hw = at91_clk_register_system(regmap, name, parent_name, NULL, + id, flags); + 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, NULL); + 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 0000000000..5aa9c1f1c8 --- /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 0000000000..0f52e80bcd --- /dev/null +++ b/drivers/clk/at91/pmc.h @@ -0,0 +1,280 @@ +/* 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, + struct clk_hw **parent_hws, 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, + struct clk_parent_data *parent_data, bool bypass); +struct clk_hw * __init +at91_clk_register_rm9200_main(struct regmap *regmap, + const char *name, + const char *parent_name, + struct clk_hw *parent_hw); +struct clk_hw * __init +at91_clk_register_sam9x5_main(struct regmap *regmap, const char *name, + const char **parent_names, + struct clk_hw **parent_hws, 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, + struct clk_hw **parent_hws, + 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, struct clk_hw *parent_hw, + 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, + struct clk_hw **parent_hws, 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, struct clk_hw *parent_hw, + 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, + struct clk_hw *parent_hw, + u32 id, const struct clk_range *range, + int chg_pid, unsigned long flags); + +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, + struct clk_hw *parent_hw, 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, struct clk_hw **parent_hws, + 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, struct clk_hw *parent_hw, + u8 id, unsigned long flags); + +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 *parent_hw); + +struct clk_hw * __init +at91_clk_sama7g5_register_utmi(struct regmap *regmap, const char *name, + const char *parent_name, struct clk_hw *parent_hw); + +#endif /* __PMC_H_ */ diff --git a/drivers/clk/at91/sam9x60.c b/drivers/clk/at91/sam9x60.c new file mode 100644 index 0000000000..e309cbf3cb --- /dev/null +++ b/drivers/clk/at91/sam9x60.c @@ -0,0 +1,371 @@ +// 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; + unsigned long flags; + u8 id; +} sam9x60_systemck[] = { + /* + * ddrck feeds DDR controller and is enabled by bootloader thus we need + * to keep it enabled in case there is no Linux consumer for it. + */ + { .n = "ddrck", .p = "masterck_div", .id = 2, .flags = CLK_IS_CRITICAL }, + { .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; + unsigned long flags; + 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, }, + /* + * mpddr_clk feeds DDR controller and is enabled by bootloader thus we + * need to keep it enabled in case there is no Linux consumer for it. + */ + { .n = "mpddr_clk", .id = 49, .flags = CLK_IS_CRITICAL }, +}; + +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, NULL, 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, NULL, 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", NULL, 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", NULL, 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, NULL, &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", NULL, &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, NULL, 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, NULL, + sam9x60_systemck[i].id, + sam9x60_systemck[i].flags); + 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", NULL, + sam9x60_periphck[i].id, + &range, INT_MIN, + sam9x60_periphck[i].flags); + 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, 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 0000000000..c16594fce9 --- /dev/null +++ b/drivers/clk/at91/sama5d2.c @@ -0,0 +1,399 @@ +// 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; + unsigned long flags; + u8 id; +} sama5d2_systemck[] = { + /* + * ddrck feeds DDR controller and is enabled by bootloader thus we need + * to keep it enabled in case there is no Linux consumer for it. + */ + { .n = "ddrck", .p = "masterck_div", .id = 2, .flags = CLK_IS_CRITICAL }, + { .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; + unsigned long flags; + 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, }, + /* + * mpddr_clk feeds DDR controller and is enabled by bootloader thus we + * need to keep it enabled in case there is no Linux consumer for it. + */ + { .n = "mpddr_clk", .id = 13, .flags = CLK_IS_CRITICAL }, + { .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, NULL, + 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, NULL, 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", NULL); + 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, NULL, + &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", NULL, + &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, NULL, 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, NULL, + sama5d2_systemck[i].id, + sama5d2_systemck[i].flags); + 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", NULL, + sama5d2_periphck[i].id, + &range, INT_MIN, + sama5d2_periphck[i].flags); + 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", NULL, + sama5d2_periph32ck[i].id, + &sama5d2_periph32ck[i].r, + INT_MIN, 0); + 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, 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 0000000000..522ce60314 --- /dev/null +++ b/drivers/clk/at91/sama5d3.c @@ -0,0 +1,269 @@ +// 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; + unsigned long flags; + u8 id; +} sama5d3_systemck[] = { + /* + * ddrck feeds DDR controller and is enabled by bootloader thus we need + * to keep it enabled in case there is no Linux consumer for it. + */ + { .n = "ddrck", .p = "masterck_div", .id = 2, .flags = CLK_IS_CRITICAL }, + { .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; + unsigned long flags; +} 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, }, + /* + * mpddr_clk feeds DDR controller and is enabled by bootloader thus we + * need to keep it enabled in case there is no Linux consumer for it. + */ + { .n = "mpddr_clk", .id = 49, .flags = CLK_IS_CRITICAL }, +}; + +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, NULL, + 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, NULL, 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", NULL); + 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, NULL, + &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", NULL, + &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, NULL, 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, NULL, + sama5d3_systemck[i].id, + sama5d3_systemck[i].flags); + 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", NULL, + sama5d3_periphck[i].id, + &sama5d3_periphck[i].r, + INT_MIN, + sama5d3_periphck[i].flags); + 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 0000000000..160c0bddb6 --- /dev/null +++ b/drivers/clk/at91/sama5d4.c @@ -0,0 +1,301 @@ +// 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; + unsigned long flags; + u8 id; +} sama5d4_systemck[] = { + /* + * ddrck feeds DDR controller and is enabled by bootloader thus we need + * to keep it enabled in case there is no Linux consumer for it. + */ + { .n = "ddrck", .p = "masterck_div", .id = 2, .flags = CLK_IS_CRITICAL }, + { .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; + unsigned long flags; + u8 id; +} sama5d4_periphck[] = { + { .n = "dma0_clk", .id = 8 }, + { .n = "cpkcc_clk", .id = 10 }, + { .n = "aesb_clk", .id = 13 }, + /* + * mpddr_clk feeds DDR controller and is enabled by bootloader thus we + * need to keep it enabled in case there is no Linux consumer for it. + */ + { .n = "mpddr_clk", .id = 16, .flags = CLK_IS_CRITICAL }, + { .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, NULL, + 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, NULL, 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", NULL); + 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, NULL, + &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", NULL, + &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, NULL, 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, NULL, + sama5d4_systemck[i].id, + sama5d4_systemck[i].flags); + 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", NULL, + sama5d4_periphck[i].id, + &range, INT_MIN, + sama5d4_periphck[i].flags); + 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", NULL, + sama5d4_periph32ck[i].id, + &range, INT_MIN, 0); + 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 0000000000..91b5c6f148 --- /dev/null +++ b/drivers/clk/at91/sama7g5.c @@ -0,0 +1,1253 @@ +// 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 component identifier + * @PLL_COMPID_FRAC: Fractional PLL component identifier + * @PLL_COMPID_DIV0: 1st PLL divider component identifier + * @PLL_COMPID_DIV1: 2nd PLL divider component identifier + */ +enum pll_component_id { + PLL_COMPID_FRAC, + PLL_COMPID_DIV0, + PLL_COMPID_DIV1, +}; + +/* + * 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, +}; + +/* + * SAMA7G5 PLL possible parents + * @SAMA7G5_PLL_PARENT_MAINCK: MAINCK is PLL a parent + * @SAMA7G5_PLL_PARENT_MAIN_XTAL: MAIN XTAL is a PLL parent + * @SAMA7G5_PLL_PARENT_FRACCK: Frac PLL is a PLL parent (for PLL dividers) + */ +enum sama7g5_pll_parent { + SAMA7G5_PLL_PARENT_MAINCK, + SAMA7G5_PLL_PARENT_MAIN_XTAL, + SAMA7G5_PLL_PARENT_FRACCK, +}; + +/* + * PLL clocks description + * @n: clock name + * @l: clock layout + * @c: clock characteristics + * @hw: pointer to clk_hw + * @t: clock type + * @f: clock flags + * @p: clock parent + * @eid: export index in sama7g5->chws[] array + * @safe_div: intermediate divider need to be set on PRE_RATE_CHANGE + * notification + */ +static struct sama7g5_pll { + const char *n; + const struct clk_pll_layout *l; + const struct clk_pll_characteristics *c; + struct clk_hw *hw; + unsigned long f; + enum sama7g5_pll_parent p; + u8 t; + u8 eid; + u8 safe_div; +} sama7g5_plls[][PLL_ID_MAX] = { + [PLL_ID_CPU] = { + [PLL_COMPID_FRAC] = { + .n = "cpupll_fracck", + .p = SAMA7G5_PLL_PARENT_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, + }, + + [PLL_COMPID_DIV0] = { + .n = "cpupll_divpmcck", + .p = SAMA7G5_PLL_PARENT_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] = { + [PLL_COMPID_FRAC] = { + .n = "syspll_fracck", + .p = SAMA7G5_PLL_PARENT_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, + }, + + [PLL_COMPID_DIV0] = { + .n = "syspll_divpmcck", + .p = SAMA7G5_PLL_PARENT_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] = { + [PLL_COMPID_FRAC] = { + .n = "ddrpll_fracck", + .p = SAMA7G5_PLL_PARENT_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, + }, + + [PLL_COMPID_DIV0] = { + .n = "ddrpll_divpmcck", + .p = SAMA7G5_PLL_PARENT_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] = { + [PLL_COMPID_FRAC] = { + .n = "imgpll_fracck", + .p = SAMA7G5_PLL_PARENT_MAINCK, + .l = &pll_layout_frac, + .c = &pll_characteristics, + .t = PLL_TYPE_FRAC, + .f = CLK_SET_RATE_GATE, + }, + + [PLL_COMPID_DIV0] = { + .n = "imgpll_divpmcck", + .p = SAMA7G5_PLL_PARENT_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] = { + [PLL_COMPID_FRAC] = { + .n = "baudpll_fracck", + .p = SAMA7G5_PLL_PARENT_MAINCK, + .l = &pll_layout_frac, + .c = &pll_characteristics, + .t = PLL_TYPE_FRAC, + .f = CLK_SET_RATE_GATE, }, + + [PLL_COMPID_DIV0] = { + .n = "baudpll_divpmcck", + .p = SAMA7G5_PLL_PARENT_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] = { + [PLL_COMPID_FRAC] = { + .n = "audiopll_fracck", + .p = SAMA7G5_PLL_PARENT_MAIN_XTAL, + .l = &pll_layout_frac, + .c = &pll_characteristics, + .t = PLL_TYPE_FRAC, + .f = CLK_SET_RATE_GATE, + }, + + [PLL_COMPID_DIV0] = { + .n = "audiopll_divpmcck", + .p = SAMA7G5_PLL_PARENT_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, + }, + + [PLL_COMPID_DIV1] = { + .n = "audiopll_diviock", + .p = SAMA7G5_PLL_PARENT_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] = { + [PLL_COMPID_FRAC] = { + .n = "ethpll_fracck", + .p = SAMA7G5_PLL_PARENT_MAIN_XTAL, + .l = &pll_layout_frac, + .c = &pll_characteristics, + .t = PLL_TYPE_FRAC, + .f = CLK_SET_RATE_GATE, + }, + + [PLL_COMPID_DIV0] = { + .n = "ethpll_divpmcck", + .p = SAMA7G5_PLL_PARENT_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, + }, + }, +}; + +/* Used to create an array entry identifying a PLL by its components. */ +#define PLL_IDS_TO_ARR_ENTRY(_id, _comp) { PLL_ID_##_id, PLL_COMPID_##_comp} + +/* + * Master clock (MCK[1..4]) description + * @n: clock name + * @ep: extra parents names array (entry formed by PLL components + * identifiers (see enum pll_component_id)) + * @hw: pointer to clk_hw + * @ep_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 struct { + const char *n; + struct { + int pll_id; + int pll_compid; + } ep[4]; + struct clk_hw *hw; + int ep_chg_id; + u8 ep_count; + u8 ep_mux_table[4]; + u8 id; + u8 eid; + u8 c; +} sama7g5_mckx[] = { + { .n = "mck0", }, /* Dummy entry for MCK0 to store hw in probe. */ + { .n = "mck1", + .id = 1, + .ep = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), }, + .ep_mux_table = { 5, }, + .ep_count = 1, + .ep_chg_id = INT_MIN, + .eid = PMC_MCK1, + .c = 1, }, + + { .n = "mck2", + .id = 2, + .ep = { PLL_IDS_TO_ARR_ENTRY(DDR, DIV0), }, + .ep_mux_table = { 6, }, + .ep_count = 1, + .ep_chg_id = INT_MIN, + .c = 1, }, + + { .n = "mck3", + .id = 3, + .ep = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(DDR, DIV0), + PLL_IDS_TO_ARR_ENTRY(IMG, DIV0), }, + .ep_mux_table = { 5, 6, 7, }, + .ep_count = 3, + .ep_chg_id = 5, }, + + { .n = "mck4", + .id = 4, + .ep = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), }, + .ep_mux_table = { 5, }, + .ep_count = 1, + .ep_chg_id = INT_MIN, + .c = 1, }, +}; + +/* + * System clock description + * @n: clock name + * @id: clock id + */ +static const struct { + const char *n; + u8 id; +} sama7g5_systemck[] = { + { .n = "pck0", .id = 8, }, + { .n = "pck1", .id = 9, }, + { .n = "pck2", .id = 10, }, + { .n = "pck3", .id = 11, }, + { .n = "pck4", .id = 12, }, + { .n = "pck5", .id = 13, }, + { .n = "pck6", .id = 14, }, + { .n = "pck7", .id = 15, }, +}; + +/* Mux table for programmable clocks. */ +static u32 sama7g5_prog_mux_table[] = { 0, 1, 2, 5, 6, 7, 8, 9, 10, }; + +/* + * Peripheral clock parent hw identifier (used to index in sama7g5_mckx[]) + * @PCK_PARENT_HW_MCK0: pck parent hw identifier is MCK0 + * @PCK_PARENT_HW_MCK1: pck parent hw identifier is MCK1 + * @PCK_PARENT_HW_MCK2: pck parent hw identifier is MCK2 + * @PCK_PARENT_HW_MCK3: pck parent hw identifier is MCK3 + * @PCK_PARENT_HW_MCK4: pck parent hw identifier is MCK4 + * @PCK_PARENT_HW_MAX: max identifier + */ +enum sama7g5_pck_parent_hw_id { + PCK_PARENT_HW_MCK0, + PCK_PARENT_HW_MCK1, + PCK_PARENT_HW_MCK2, + PCK_PARENT_HW_MCK3, + PCK_PARENT_HW_MCK4, + PCK_PARENT_HW_MAX, +}; + +/* + * Peripheral clock description + * @n: clock name + * @p: clock parent hw id + * @r: clock range values + * @id: clock id + * @chgp: index in parent array of the changeable parent + */ +static struct { + const char *n; + enum sama7g5_pck_parent_hw_id p; + struct clk_range r; + u8 chgp; + u8 id; +} sama7g5_periphck[] = { + { .n = "pioA_clk", .p = PCK_PARENT_HW_MCK0, .id = 11, }, + { .n = "securam_clk", .p = PCK_PARENT_HW_MCK0, .id = 18, }, + { .n = "sfr_clk", .p = PCK_PARENT_HW_MCK1, .id = 19, }, + { .n = "hsmc_clk", .p = PCK_PARENT_HW_MCK1, .id = 21, }, + { .n = "xdmac0_clk", .p = PCK_PARENT_HW_MCK1, .id = 22, }, + { .n = "xdmac1_clk", .p = PCK_PARENT_HW_MCK1, .id = 23, }, + { .n = "xdmac2_clk", .p = PCK_PARENT_HW_MCK1, .id = 24, }, + { .n = "acc_clk", .p = PCK_PARENT_HW_MCK1, .id = 25, }, + { .n = "aes_clk", .p = PCK_PARENT_HW_MCK1, .id = 27, }, + { .n = "tzaesbasc_clk", .p = PCK_PARENT_HW_MCK1, .id = 28, }, + { .n = "asrc_clk", .p = PCK_PARENT_HW_MCK1, .id = 30, .r = { .max = 200000000, }, }, + { .n = "cpkcc_clk", .p = PCK_PARENT_HW_MCK0, .id = 32, }, + { .n = "csi_clk", .p = PCK_PARENT_HW_MCK3, .id = 33, .r = { .max = 266000000, }, .chgp = 1, }, + { .n = "csi2dc_clk", .p = PCK_PARENT_HW_MCK3, .id = 34, .r = { .max = 266000000, }, .chgp = 1, }, + { .n = "eic_clk", .p = PCK_PARENT_HW_MCK1, .id = 37, }, + { .n = "flex0_clk", .p = PCK_PARENT_HW_MCK1, .id = 38, }, + { .n = "flex1_clk", .p = PCK_PARENT_HW_MCK1, .id = 39, }, + { .n = "flex2_clk", .p = PCK_PARENT_HW_MCK1, .id = 40, }, + { .n = "flex3_clk", .p = PCK_PARENT_HW_MCK1, .id = 41, }, + { .n = "flex4_clk", .p = PCK_PARENT_HW_MCK1, .id = 42, }, + { .n = "flex5_clk", .p = PCK_PARENT_HW_MCK1, .id = 43, }, + { .n = "flex6_clk", .p = PCK_PARENT_HW_MCK1, .id = 44, }, + { .n = "flex7_clk", .p = PCK_PARENT_HW_MCK1, .id = 45, }, + { .n = "flex8_clk", .p = PCK_PARENT_HW_MCK1, .id = 46, }, + { .n = "flex9_clk", .p = PCK_PARENT_HW_MCK1, .id = 47, }, + { .n = "flex10_clk", .p = PCK_PARENT_HW_MCK1, .id = 48, }, + { .n = "flex11_clk", .p = PCK_PARENT_HW_MCK1, .id = 49, }, + { .n = "gmac0_clk", .p = PCK_PARENT_HW_MCK1, .id = 51, }, + { .n = "gmac1_clk", .p = PCK_PARENT_HW_MCK1, .id = 52, }, + { .n = "icm_clk", .p = PCK_PARENT_HW_MCK1, .id = 55, }, + { .n = "isc_clk", .p = PCK_PARENT_HW_MCK3, .id = 56, .r = { .max = 266000000, }, .chgp = 1, }, + { .n = "i2smcc0_clk", .p = PCK_PARENT_HW_MCK1, .id = 57, .r = { .max = 200000000, }, }, + { .n = "i2smcc1_clk", .p = PCK_PARENT_HW_MCK1, .id = 58, .r = { .max = 200000000, }, }, + { .n = "matrix_clk", .p = PCK_PARENT_HW_MCK1, .id = 60, }, + { .n = "mcan0_clk", .p = PCK_PARENT_HW_MCK1, .id = 61, .r = { .max = 200000000, }, }, + { .n = "mcan1_clk", .p = PCK_PARENT_HW_MCK1, .id = 62, .r = { .max = 200000000, }, }, + { .n = "mcan2_clk", .p = PCK_PARENT_HW_MCK1, .id = 63, .r = { .max = 200000000, }, }, + { .n = "mcan3_clk", .p = PCK_PARENT_HW_MCK1, .id = 64, .r = { .max = 200000000, }, }, + { .n = "mcan4_clk", .p = PCK_PARENT_HW_MCK1, .id = 65, .r = { .max = 200000000, }, }, + { .n = "mcan5_clk", .p = PCK_PARENT_HW_MCK1, .id = 66, .r = { .max = 200000000, }, }, + { .n = "pdmc0_clk", .p = PCK_PARENT_HW_MCK1, .id = 68, .r = { .max = 200000000, }, }, + { .n = "pdmc1_clk", .p = PCK_PARENT_HW_MCK1, .id = 69, .r = { .max = 200000000, }, }, + { .n = "pit64b0_clk", .p = PCK_PARENT_HW_MCK1, .id = 70, }, + { .n = "pit64b1_clk", .p = PCK_PARENT_HW_MCK1, .id = 71, }, + { .n = "pit64b2_clk", .p = PCK_PARENT_HW_MCK1, .id = 72, }, + { .n = "pit64b3_clk", .p = PCK_PARENT_HW_MCK1, .id = 73, }, + { .n = "pit64b4_clk", .p = PCK_PARENT_HW_MCK1, .id = 74, }, + { .n = "pit64b5_clk", .p = PCK_PARENT_HW_MCK1, .id = 75, }, + { .n = "pwm_clk", .p = PCK_PARENT_HW_MCK1, .id = 77, }, + { .n = "qspi0_clk", .p = PCK_PARENT_HW_MCK1, .id = 78, }, + { .n = "qspi1_clk", .p = PCK_PARENT_HW_MCK1, .id = 79, }, + { .n = "sdmmc0_clk", .p = PCK_PARENT_HW_MCK1, .id = 80, }, + { .n = "sdmmc1_clk", .p = PCK_PARENT_HW_MCK1, .id = 81, }, + { .n = "sdmmc2_clk", .p = PCK_PARENT_HW_MCK1, .id = 82, }, + { .n = "sha_clk", .p = PCK_PARENT_HW_MCK1, .id = 83, }, + { .n = "spdifrx_clk", .p = PCK_PARENT_HW_MCK1, .id = 84, .r = { .max = 200000000, }, }, + { .n = "spdiftx_clk", .p = PCK_PARENT_HW_MCK1, .id = 85, .r = { .max = 200000000, }, }, + { .n = "ssc0_clk", .p = PCK_PARENT_HW_MCK1, .id = 86, .r = { .max = 200000000, }, }, + { .n = "ssc1_clk", .p = PCK_PARENT_HW_MCK1, .id = 87, .r = { .max = 200000000, }, }, + { .n = "tcb0_ch0_clk", .p = PCK_PARENT_HW_MCK1, .id = 88, .r = { .max = 200000000, }, }, + { .n = "tcb0_ch1_clk", .p = PCK_PARENT_HW_MCK1, .id = 89, .r = { .max = 200000000, }, }, + { .n = "tcb0_ch2_clk", .p = PCK_PARENT_HW_MCK1, .id = 90, .r = { .max = 200000000, }, }, + { .n = "tcb1_ch0_clk", .p = PCK_PARENT_HW_MCK1, .id = 91, .r = { .max = 200000000, }, }, + { .n = "tcb1_ch1_clk", .p = PCK_PARENT_HW_MCK1, .id = 92, .r = { .max = 200000000, }, }, + { .n = "tcb1_ch2_clk", .p = PCK_PARENT_HW_MCK1, .id = 93, .r = { .max = 200000000, }, }, + { .n = "tcpca_clk", .p = PCK_PARENT_HW_MCK1, .id = 94, }, + { .n = "tcpcb_clk", .p = PCK_PARENT_HW_MCK1, .id = 95, }, + { .n = "tdes_clk", .p = PCK_PARENT_HW_MCK1, .id = 96, }, + { .n = "trng_clk", .p = PCK_PARENT_HW_MCK1, .id = 97, }, + { .n = "udphsa_clk", .p = PCK_PARENT_HW_MCK1, .id = 104, }, + { .n = "udphsb_clk", .p = PCK_PARENT_HW_MCK1, .id = 105, }, + { .n = "uhphs_clk", .p = PCK_PARENT_HW_MCK1, .id = 106, }, +}; + +/* + * Generic clock description + * @n: clock name + * @pp: PLL parents (entry formed by PLL components identifiers + * (see enum pll_component_id)) + * @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; + struct { + int pll_id; + int pll_compid; + } 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 = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(IMG, DIV0), + PLL_IDS_TO_ARR_ENTRY(AUDIO, DIV0), }, + .pp_mux_table = { 5, 7, 9, }, + .pp_count = 3, + .pp_chg_id = INT_MIN, }, + + { .n = "asrc_gclk", + .id = 30, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(AUDIO, DIV0), }, + .pp_mux_table = { 9, }, + .pp_count = 1, + .pp_chg_id = 3, }, + + { .n = "csi_gclk", + .id = 33, + .r = { .max = 27000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(DDR, DIV0), PLL_IDS_TO_ARR_ENTRY(IMG, DIV0), }, + .pp_mux_table = { 6, 7, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "flex0_gclk", + .id = 38, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "flex1_gclk", + .id = 39, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "flex2_gclk", + .id = 40, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "flex3_gclk", + .id = 41, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "flex4_gclk", + .id = 42, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "flex5_gclk", + .id = 43, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "flex6_gclk", + .id = 44, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "flex7_gclk", + .id = 45, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "flex8_gclk", + .id = 46, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "flex9_gclk", + .id = 47, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "flex10_gclk", + .id = 48, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "flex11_gclk", + .id = 49, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "gmac0_gclk", + .id = 51, + .r = { .max = 125000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(ETH, DIV0), }, + .pp_mux_table = { 10, }, + .pp_count = 1, + .pp_chg_id = 3, }, + + { .n = "gmac1_gclk", + .id = 52, + .r = { .max = 50000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(ETH, DIV0), }, + .pp_mux_table = { 10, }, + .pp_count = 1, + .pp_chg_id = INT_MIN, }, + + { .n = "gmac0_tsu_gclk", + .id = 53, + .r = { .max = 300000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(AUDIO, DIV0), PLL_IDS_TO_ARR_ENTRY(ETH, DIV0), }, + .pp_mux_table = { 9, 10, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "gmac1_tsu_gclk", + .id = 54, + .r = { .max = 300000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(AUDIO, DIV0), PLL_IDS_TO_ARR_ENTRY(ETH, DIV0), }, + .pp_mux_table = { 9, 10, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "i2smcc0_gclk", + .id = 57, + .r = { .max = 100000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(AUDIO, DIV0), }, + .pp_mux_table = { 5, 9, }, + .pp_count = 2, + .pp_chg_id = 4, }, + + { .n = "i2smcc1_gclk", + .id = 58, + .r = { .max = 100000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(AUDIO, DIV0), }, + .pp_mux_table = { 5, 9, }, + .pp_count = 2, + .pp_chg_id = 4, }, + + { .n = "mcan0_gclk", + .id = 61, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "mcan1_gclk", + .id = 62, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "mcan2_gclk", + .id = 63, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "mcan3_gclk", + .id = 64, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "mcan4_gclk", + .id = 65, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "mcan5_gclk", + .id = 66, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "pdmc0_gclk", + .id = 68, + .r = { .max = 50000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(AUDIO, DIV0), }, + .pp_mux_table = { 5, 9, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "pdmc1_gclk", + .id = 69, + .r = { .max = 50000000, }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(AUDIO, DIV0), }, + .pp_mux_table = { 5, 9, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "pit64b0_gclk", + .id = 70, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(IMG, DIV0), + PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), PLL_IDS_TO_ARR_ENTRY(AUDIO, DIV0), + PLL_IDS_TO_ARR_ENTRY(ETH, DIV0), }, + .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 = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(IMG, DIV0), + PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), PLL_IDS_TO_ARR_ENTRY(AUDIO, DIV0), + PLL_IDS_TO_ARR_ENTRY(ETH, DIV0), }, + .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 = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(IMG, DIV0), + PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), PLL_IDS_TO_ARR_ENTRY(AUDIO, DIV0), + PLL_IDS_TO_ARR_ENTRY(ETH, DIV0), }, + .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 = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(IMG, DIV0), + PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), PLL_IDS_TO_ARR_ENTRY(AUDIO, DIV0), + PLL_IDS_TO_ARR_ENTRY(ETH, DIV0), }, + .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 = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(IMG, DIV0), + PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), PLL_IDS_TO_ARR_ENTRY(AUDIO, DIV0), + PLL_IDS_TO_ARR_ENTRY(ETH, DIV0), }, + .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 = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(IMG, DIV0), + PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), PLL_IDS_TO_ARR_ENTRY(AUDIO, DIV0), + PLL_IDS_TO_ARR_ENTRY(ETH, DIV0), }, + .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 = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "qspi1_gclk", + .id = 79, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = INT_MIN, }, + + { .n = "sdmmc0_gclk", + .id = 80, + .r = { .max = 208000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = 4, }, + + { .n = "sdmmc1_gclk", + .id = 81, + .r = { .max = 208000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = 4, }, + + { .n = "sdmmc2_gclk", + .id = 82, + .r = { .max = 208000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), }, + .pp_mux_table = { 5, 8, }, + .pp_count = 2, + .pp_chg_id = 4, }, + + { .n = "spdifrx_gclk", + .id = 84, + .r = { .max = 150000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(AUDIO, DIV0), }, + .pp_mux_table = { 5, 9, }, + .pp_count = 2, + .pp_chg_id = 4, }, + + { .n = "spdiftx_gclk", + .id = 85, + .r = { .max = 25000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(AUDIO, DIV0), }, + .pp_mux_table = { 5, 9, }, + .pp_count = 2, + .pp_chg_id = 4, }, + + { .n = "tcb0_ch0_gclk", + .id = 88, + .r = { .max = 200000000 }, + .pp = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(IMG, DIV0), + PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), PLL_IDS_TO_ARR_ENTRY(AUDIO, DIV0), + PLL_IDS_TO_ARR_ENTRY(ETH, DIV0), }, + .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 = { PLL_IDS_TO_ARR_ENTRY(SYS, DIV0), PLL_IDS_TO_ARR_ENTRY(IMG, DIV0), + PLL_IDS_TO_ARR_ENTRY(BAUD, DIV0), PLL_IDS_TO_ARR_ENTRY(AUDIO, DIV0), + PLL_IDS_TO_ARR_ENTRY(ETH, DIV0), }, + .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 *main_xtal_name = "main_xtal"; + struct pmc_data *sama7g5_pmc; + void **alloc_mem = NULL; + int alloc_mem_size = 0; + struct regmap *regmap; + struct clk_hw *hw, *main_rc_hw, *main_osc_hw, *main_xtal_hw; + struct clk_hw *td_slck_hw, *md_slck_hw; + static struct clk_parent_data parent_data; + struct clk_hw *parent_hws[10]; + bool bypass; + int i, j; + + td_slck_hw = __clk_get_hw(of_clk_get_by_name(np, "td_slck")); + md_slck_hw = __clk_get_hw(of_clk_get_by_name(np, "md_slck")); + main_xtal_hw = __clk_get_hw(of_clk_get_by_name(np, main_xtal_name)); + + if (!td_slck_hw || !md_slck_hw || !main_xtal_hw) + return; + + 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; + + main_rc_hw = at91_clk_register_main_rc_osc(regmap, "main_rc_osc", 12000000, + 50000000); + if (IS_ERR(main_rc_hw)) + goto err_free; + + bypass = of_property_read_bool(np, "atmel,osc-bypass"); + + parent_data.name = main_xtal_name; + parent_data.fw_name = main_xtal_name; + main_osc_hw = at91_clk_register_main_osc(regmap, "main_osc", NULL, + &parent_data, bypass); + if (IS_ERR(main_osc_hw)) + goto err_free; + + parent_hws[0] = main_rc_hw; + parent_hws[1] = main_osc_hw; + hw = at91_clk_register_sam9x5_main(regmap, "mainck", NULL, parent_hws, 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: + switch (sama7g5_plls[i][j].p) { + case SAMA7G5_PLL_PARENT_MAINCK: + parent_hw = sama7g5_pmc->chws[PMC_MAIN]; + break; + case SAMA7G5_PLL_PARENT_MAIN_XTAL: + parent_hw = main_xtal_hw; + break; + default: + /* Should not happen. */ + parent_hw = NULL; + break; + } + + hw = sam9x60_clk_register_frac_pll(regmap, + &pmc_pll_lock, sama7g5_plls[i][j].n, + NULL, 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, + NULL, sama7g5_plls[i][0].hw, 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; + + sama7g5_plls[i][j].hw = hw; + if (sama7g5_plls[i][j].eid) + sama7g5_pmc->chws[sama7g5_plls[i][j].eid] = hw; + } + } + + hw = at91_clk_register_master_div(regmap, "mck0", NULL, + sama7g5_plls[PLL_ID_CPU][1].hw, + &mck0_layout, &mck0_characteristics, + &pmc_mck0_lock, CLK_GET_RATE_NOCACHE, 5); + if (IS_ERR(hw)) + goto err_free; + + sama7g5_mckx[PCK_PARENT_HW_MCK0].hw = sama7g5_pmc->chws[PMC_MCK] = hw; + + parent_hws[0] = md_slck_hw; + parent_hws[1] = td_slck_hw; + parent_hws[2] = sama7g5_pmc->chws[PMC_MAIN]; + for (i = PCK_PARENT_HW_MCK1; i < ARRAY_SIZE(sama7g5_mckx); i++) { + u8 num_parents = 3 + sama7g5_mckx[i].ep_count; + struct clk_hw *tmp_parent_hws[8]; + 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); + for (j = 0; j < sama7g5_mckx[i].ep_count; j++) { + u8 pll_id = sama7g5_mckx[i].ep[j].pll_id; + u8 pll_compid = sama7g5_mckx[i].ep[j].pll_compid; + + tmp_parent_hws[j] = sama7g5_plls[pll_id][pll_compid].hw; + } + SAMA7G5_FILL_TABLE(&parent_hws[3], tmp_parent_hws, + sama7g5_mckx[i].ep_count); + + hw = at91_clk_sama7g5_register_master(regmap, sama7g5_mckx[i].n, + num_parents, NULL, parent_hws, 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; + + sama7g5_mckx[i].hw = hw; + if (sama7g5_mckx[i].eid) + sama7g5_pmc->chws[sama7g5_mckx[i].eid] = hw; + } + + hw = at91_clk_sama7g5_register_utmi(regmap, "utmick", NULL, main_xtal_hw); + if (IS_ERR(hw)) + goto err_free; + + sama7g5_pmc->chws[PMC_UTMI] = hw; + + parent_hws[0] = md_slck_hw; + parent_hws[1] = td_slck_hw; + parent_hws[2] = sama7g5_pmc->chws[PMC_MAIN]; + parent_hws[3] = sama7g5_plls[PLL_ID_SYS][PLL_COMPID_DIV0].hw; + parent_hws[4] = sama7g5_plls[PLL_ID_DDR][PLL_COMPID_DIV0].hw; + parent_hws[5] = sama7g5_plls[PLL_ID_IMG][PLL_COMPID_DIV0].hw; + parent_hws[6] = sama7g5_plls[PLL_ID_BAUD][PLL_COMPID_DIV0].hw; + parent_hws[7] = sama7g5_plls[PLL_ID_AUDIO][PLL_COMPID_DIV0].hw; + parent_hws[8] = sama7g5_plls[PLL_ID_ETH][PLL_COMPID_DIV0].hw; + for (i = 0; i < 8; i++) { + char name[6]; + + snprintf(name, sizeof(name), "prog%d", i); + + hw = at91_clk_register_programmable(regmap, name, NULL, parent_hws, + 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, + NULL, sama7g5_pmc->pchws[i], + sama7g5_systemck[i].id, 0); + 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, + NULL, + sama7g5_mckx[sama7g5_periphck[i].p].hw, + sama7g5_periphck[i].id, + &sama7g5_periphck[i].r, + sama7g5_periphck[i].chgp ? 0 : + INT_MIN, 0); + if (IS_ERR(hw)) + goto err_free; + + sama7g5_pmc->phws[sama7g5_periphck[i].id] = hw; + } + + parent_hws[0] = md_slck_hw; + parent_hws[1] = td_slck_hw; + parent_hws[2] = sama7g5_pmc->chws[PMC_MAIN]; + for (i = 0; i < ARRAY_SIZE(sama7g5_gck); i++) { + u8 num_parents = 3 + sama7g5_gck[i].pp_count; + struct clk_hw *tmp_parent_hws[8]; + 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); + for (j = 0; j < sama7g5_gck[i].pp_count; j++) { + u8 pll_id = sama7g5_gck[i].pp[j].pll_id; + u8 pll_compid = sama7g5_gck[i].pp[j].pll_compid; + + tmp_parent_hws[j] = sama7g5_plls[pll_id][pll_compid].hw; + } + SAMA7G5_FILL_TABLE(&parent_hws[3], tmp_parent_hws, + sama7g5_gck[i].pp_count); + + hw = at91_clk_register_generated(regmap, &pmc_pcr_lock, + &sama7g5_pcr_layout, + sama7g5_gck[i].n, NULL, + parent_hws, 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 0000000000..7741d8f3db --- /dev/null +++ b/drivers/clk/at91/sckc.c @@ -0,0 +1,655 @@ +// 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 struct clk_parent_data *parent_data, + 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_data) + 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_data = parent_data; + 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 = { + .determine_rate = clk_hw_determine_rate_no_reparent, + .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 struct clk_hw **parent_hws, + 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_hws || !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_hws = parent_hws; + 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) +{ + void __iomem *regbase = of_iomap(np, 0); + struct device_node *child = NULL; + const char *xtal_name; + struct clk_hw *slow_rc, *slow_osc, *slowck; + static struct clk_parent_data parent_data = { + .name = "slow_xtal", + }; + const struct clk_hw *parent_hws[2]; + bool bypass; + int ret; + + if (!regbase) + return; + + slow_rc = at91_clk_register_slow_rc_osc(regbase, "slow_rc_osc", + 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; + + parent_data.fw_name = xtal_name; + + slow_osc = at91_clk_register_slow_osc(regbase, "slow_osc", + &parent_data, 1200000, bypass, bits); + if (IS_ERR(slow_osc)) + goto unregister_slow_rc; + + parent_hws[0] = slow_rc; + parent_hws[1] = slow_osc; + slowck = at91_clk_register_sam9x5_slow(regbase, "slowck", parent_hws, + 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 struct clk_hw *parent_hws[2]; + static struct clk_parent_data parent_data = { + .name = "slow_xtal", + }; + bool bypass; + int ret; + + if (!regbase) + return; + + slow_rc = clk_hw_register_fixed_rate_with_accuracy(NULL, "slow_rc_osc", + 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; + + parent_data.fw_name = xtal_name; + bypass = of_property_read_bool(np, "atmel,osc-bypass"); + slow_osc = at91_clk_register_slow_osc(regbase, "slow_osc", + &parent_data, 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_parent_hw(NULL, "md_slck", + slow_rc, + 0, 32768); + if (IS_ERR(clk_data->hws[0])) + goto clk_data_free; + + parent_hws[0] = slow_rc; + parent_hws[1] = slow_osc; + clk_data->hws[1] = at91_clk_register_sam9x5_slow(regbase, "td_slck", + parent_hws, 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 struct clk_hw *parent_hws[2]; + static struct clk_parent_data parent_data = { + .name = "slow_xtal", + }; + int ret; + + if (!regbase) + return; + + slow_rc = clk_hw_register_fixed_rate_with_accuracy(NULL, + "slow_rc_osc", + NULL, 0, 32768, + 250000000); + if (IS_ERR(slow_rc)) + return; + + xtal_name = of_clk_get_parent_name(np, 0); + if (!xtal_name) + goto unregister_slow_rc; + parent_data.fw_name = xtal_name; + + osc = kzalloc(sizeof(*osc), GFP_KERNEL); + if (!osc) + goto unregister_slow_rc; + + init.name = "slow_osc"; + init.ops = &sama5d4_slow_osc_ops; + init.parent_data = &parent_data; + 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; + + parent_hws[0] = slow_rc; + parent_hws[1] = &osc->hw; + slowck = at91_clk_register_sam9x5_slow(regbase, "slowck", + parent_hws, 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); |