Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+28
View File
@@ -0,0 +1,28 @@
# 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_SAM9X7) += sam9x7.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_SAMA7D65) += sama7d65.o
obj-$(CONFIG_SOC_SAMA7G5) += sama7g5.o
+217
View File
@@ -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);
+515
View File
@@ -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);
+236
View File
@@ -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);
+263
View File
@@ -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);
+189
View File
@@ -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);
+340
View File
@@ -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);
@@ -0,0 +1,545 @@
// 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 int clk_audio_pll_pad_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
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__,
req->rate, req->best_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,
req->rate * tmp_qd * div);
tmp_rate = best_parent_rate / (div * tmp_qd);
tmp_diff = abs(req->rate - tmp_rate);
if (best_diff < 0 || best_diff > tmp_diff) {
req->best_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);
req->rate = best_rate;
return 0;
}
static int clk_audio_pll_pmc_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
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__,
req->rate, req->best_parent_rate);
if (!req->rate)
return 0;
best_parent_rate = clk_round_rate(pclk->clk, 1);
div = max(best_parent_rate / req->rate, 1UL);
for (; div <= AUDIO_PLL_QDPMC_MAX; div++) {
best_parent_rate = clk_round_rate(pclk->clk, req->rate * div);
tmp_rate = best_parent_rate / div;
tmp_diff = abs(req->rate - tmp_rate);
if (best_diff < 0 || best_diff > tmp_diff) {
req->best_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, req->best_parent_rate, tmp_qd - 1);
req->rate = best_rate;
return 0;
}
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,
.determine_rate = clk_audio_pll_pad_determine_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,
.determine_rate = clk_audio_pll_pmc_determine_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_obj(*frac_ck);
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_obj(*apad_ck);
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_obj(*apmc_ck);
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;
}
@@ -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_obj(*gck);
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;
}
+123
View File
@@ -0,0 +1,123 @@
// 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 int clk_sama5d4_h32mx_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
unsigned long div;
if (req->rate > req->best_parent_rate) {
req->rate = req->best_parent_rate;
return 0;
}
div = req->best_parent_rate / 2;
if (req->rate < div) {
req->rate = div;
return 0;
}
if (req->rate - div < req->best_parent_rate - req->rate) {
req->rate = div;
return 0;
}
req->rate = req->best_parent_rate;
return 0;
}
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,
.determine_rate = clk_sama5d4_h32mx_determine_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_obj(*h32mxclk);
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;
}
@@ -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_obj(*i2s_ck);
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;
}
+596
View File
@@ -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_obj(*osc);
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_obj(*osc);
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_obj(*clkmain);
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_obj(*clkmain);
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;
}
+885
View File
@@ -0,0 +1,885 @@
// 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 9
#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_obj(*master);
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);
if (master->div == MASTER_PRES_MAX)
return DIV_ROUND_CLOSEST_ULL(parent_rate, 3);
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_obj(*master);
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,
};
@@ -0,0 +1,514 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com>
*/
#include <linux/bitfield.h>
#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_obj(*periph);
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) {
req->rate = parent_rate;
return 0;
}
/* 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 int clk_sam9x5_peripheral_no_parent_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
int shift = 0;
unsigned long best_rate;
unsigned long best_diff;
unsigned long cur_rate = req->best_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) {
req->rate = req->best_parent_rate;
return 0;
}
if (periph->range.max) {
for (; shift <= PERIPHERAL_MAX_SHIFT; shift++) {
cur_rate = req->best_parent_rate >> shift;
if (cur_rate <= periph->range.max)
break;
}
}
if (req->rate >= cur_rate) {
req->rate = cur_rate;
return 0;
}
best_diff = cur_rate - req->rate;
best_rate = cur_rate;
for (; shift <= PERIPHERAL_MAX_SHIFT; shift++) {
cur_rate = req->best_parent_rate >> shift;
if (cur_rate < req->rate)
cur_diff = req->rate - cur_rate;
else
cur_diff = cur_rate - req->rate;
if (cur_diff < best_diff) {
best_diff = cur_diff;
best_rate = cur_rate;
}
if (!best_diff || cur_rate < req->rate)
break;
}
req->rate = best_rate;
return 0;
}
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,
.determine_rate = clk_sam9x5_peripheral_no_parent_determine_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_obj(*periph);
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;
}
+381
View File
@@ -0,0 +1,381 @@
// 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 int clk_pll_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
struct clk_pll *pll = to_clk_pll(hw);
req->rate = clk_pll_get_best_div_mul(pll, req->rate, req->best_parent_rate,
NULL, NULL, NULL);
return 0;
}
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,
.determine_rate = clk_pll_determine_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_obj(*pll);
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,
};
+115
View File
@@ -0,0 +1,115 @@
// 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 int clk_plldiv_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
unsigned long div;
if (req->rate > req->best_parent_rate) {
req->rate = req->best_parent_rate;
return 0;
}
div = req->best_parent_rate / 2;
if (req->rate < div) {
req->rate = div;
return 0;
}
if (req->rate - div < req->best_parent_rate - req->rate) {
req->rate = div;
return 0;
}
req->rate = req->best_parent_rate;
return 0;
}
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,
.determine_rate = clk_plldiv_determine_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_obj(*plldiv);
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;
}
@@ -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_obj(*prog);
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,
};
@@ -0,0 +1,796 @@
// 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 PLL_MAX_ID 9
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);
unsigned long freq;
freq = parent_rate * (frac->mul + 1) +
DIV_ROUND_CLOSEST_ULL((u64)parent_rate * frac->frac, (1 << 22));
if (core->layout->div2)
freq >>= 1;
return freq;
}
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_write_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;
/* Load recommended value for PMC_PLL_ACR */
val = core->characteristics->acr;
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_write_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_write_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_write_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_write_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 < core->characteristics->core_output[0].min ||
rate > core->characteristics->core_output[0].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 < core->characteristics->core_output[0].min ||
tmprate > core->characteristics->core_output[0].max)
return -ERANGE;
if (update) {
frac->mul = nmul - 1;
frac->frac = nfrac;
}
return tmprate;
}
static int sam9x60_frac_pll_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
req->rate = sam9x60_frac_pll_compute_mul_frac(core, req->rate,
req->best_parent_rate,
false);
return 0;
}
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_write_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_write_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_write_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,
.determine_rate = sam9x60_frac_pll_determine_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,
.determine_rate = sam9x60_frac_pll_determine_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.
* Warning: this function must be called only if the same PLL ID was set in
* PLL_UPDT register previously.
*/
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_write_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_write_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_write_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_write_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 unsigned long sam9x60_fixed_div_pll_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
return parent_rate >> 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 int sam9x60_div_pll_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
struct sam9x60_pll_core *core = to_sam9x60_pll_core(hw);
req->rate = sam9x60_div_pll_compute_div(core, &req->best_parent_rate,
req->rate);
return 0;
}
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_write_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_write_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,
.determine_rate = sam9x60_div_pll_determine_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,
.determine_rate = sam9x60_div_pll_determine_rate,
.set_rate = sam9x60_div_pll_set_rate_chg,
.save_context = sam9x60_div_pll_save_context,
.restore_context = sam9x60_div_pll_restore_context,
};
static const struct clk_ops sam9x60_fixed_div_pll_ops = {
.prepare = sam9x60_div_pll_prepare,
.unprepare = sam9x60_div_pll_unprepare,
.is_prepared = sam9x60_div_pll_is_prepared,
.recalc_rate = sam9x60_fixed_div_pll_recalc_rate,
.determine_rate = sam9x60_div_pll_determine_rate,
.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_obj(*frac);
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,
characteristics->core_output[0].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_obj(*div);
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 (layout->div2)
init.ops = &sam9x60_fixed_div_pll_ops;
else 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;
}
+76
View File
@@ -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_obj(*slowck);
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;
}
+142
View File
@@ -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_obj(*smd);
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;
}
+144
View File
@@ -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_obj(*sys);
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;
}
+424
View File
@@ -0,0 +1,424 @@
// 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_obj(*usb);
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_obj(*usb);
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 int at91rm9200_clk_usb_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
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 = req->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 < req->rate)
tmpdiff = req->rate - tmprate;
else
tmpdiff = tmprate - req->rate;
if (bestdiff < 0 || bestdiff > tmpdiff) {
bestrate = tmprate;
bestdiff = tmpdiff;
req->best_parent_rate = tmp_parent_rate;
}
if (!bestdiff)
break;
}
req->rate = bestrate;
return 0;
}
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,
.determine_rate = at91rm9200_clk_usb_determine_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_obj(*usb);
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;
}
+294
View File
@@ -0,0 +1,294 @@
// 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_obj(*utmi);
if (!utmi)
return ERR_PTR(-ENOMEM);
init.name = name;
init.ops = 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 = 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);
}
File diff suppressed because it is too large Load Diff
+197
View File
@@ -0,0 +1,197 @@
// 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_flex(*pmc_data, hwtable, num_clks);
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 *data)
{
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 *data)
{
unsigned int backup;
if (!at91_pmc_backup_suspend)
return;
backup = readl_relaxed(at91_pmc_backup_suspend);
if (!backup)
return;
clk_restore_context();
}
static const struct syscore_ops pmc_syscore_ops = {
.suspend = at91_pmc_suspend,
.resume = at91_pmc_resume,
};
static struct syscore pmc_syscore = {
.ops = &pmc_syscore_ops,
};
static const struct of_device_id pmc_dt_ids[] = {
{ .compatible = "atmel,sama5d2-pmc" },
{ .compatible = "microchip,sama7g5-pmc", },
{ .compatible = "microchip,sama7d65-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;
}
at91_pmc_backup_suspend = of_iomap(np, 0);
of_node_put(np);
if (!at91_pmc_backup_suspend) {
pr_warn("%s(): unable to map securam\n", __func__);
return -ENOMEM;
}
register_syscore(&pmc_syscore);
return 0;
}
/* This has to happen before arch_initcall because of the tcb_clksrc driver */
postcore_initcall(pmc_register_ops);
#endif
+296
View File
@@ -0,0 +1,296 @@
/* 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;
u8 div2;
};
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;
const struct clk_range *core_output;
u16 *icpll;
u8 *out;
u8 upll : 1;
u32 acr;
};
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 ndck(a, s) (a[s - 1].id + 1)
#define nck(a) (a[ARRAY_SIZE(a) - 1].id + 1)
#define PMC_INIT_TABLE(_table, _count) \
do { \
u8 _i; \
for (_i = 0; _i < (_count); _i++) \
(_table)[_i] = _i; \
} while (0)
#define PMC_FILL_TABLE(_to, _from, _count) \
do { \
u8 _i; \
for (_i = 0; _i < (_count); _i++) { \
(_to)[_i] = (_from)[_i]; \
} \
} while (0)
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_ */
+380
View File
@@ -0,0 +1,380 @@
// 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 },
};
/* Fractional PLL core output range. */
static const struct clk_range core_outputs[] = {
{ .min = 600000000, .max = 1200000000 },
};
static const struct clk_pll_characteristics plla_characteristics = {
.input = { .min = 12000000, .max = 48000000 },
.num_output = ARRAY_SIZE(plla_outputs),
.output = plla_outputs,
.core_output = core_outputs,
.acr = UL(0x00020010),
};
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,
.core_output = core_outputs,
.upll = true,
.acr = UL(0x12023010), /* fIN = [18 MHz, 32 MHz]*/
};
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);
+951
View File
@@ -0,0 +1,951 @@
// SPDX-License-Identifier: GPL-2.0
/*
* SAM9X7 PMC code.
*
* Copyright (C) 2023 Microchip Technology Inc. and its subsidiaries
*
* Author: Varshini Rajendran <varshini.rajendran@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"
static DEFINE_SPINLOCK(pmc_pll_lock);
static DEFINE_SPINLOCK(mck_lock);
/**
* enum pll_ids - PLL clocks identifiers
* @PLL_ID_PLLA: PLLA identifier
* @PLL_ID_UPLL: UPLL identifier
* @PLL_ID_AUDIO: Audio PLL identifier
* @PLL_ID_LVDS: LVDS PLL identifier
* @PLL_ID_PLLA_DIV2: PLLA DIV2 identifier
* @PLL_ID_MAX: Max PLL Identifier
*/
enum pll_ids {
PLL_ID_PLLA,
PLL_ID_UPLL,
PLL_ID_AUDIO,
PLL_ID_LVDS,
PLL_ID_PLLA_DIV2,
PLL_ID_MAX,
};
/**
* enum pll_type - PLL type identifiers
* @PLL_TYPE_FRAC: fractional PLL identifier
* @PLL_TYPE_DIV: divider PLL identifier
*/
enum pll_type {
PLL_TYPE_FRAC,
PLL_TYPE_DIV,
};
static const struct clk_master_characteristics mck_characteristics = {
.output = { .min = 32000000, .max = 266666667 },
.divisors = { 1, 2, 4, 3, 5},
.have_div3_pres = 1,
};
static const struct clk_master_layout sam9x7_master_layout = {
.mask = 0x373,
.pres_shift = 4,
.offset = 0x28,
};
/* Fractional PLL core output range. */
static const struct clk_range plla_core_outputs[] = {
{ .min = 800000000, .max = 1600000000 },
};
static const struct clk_range upll_core_outputs[] = {
{ .min = 600000000, .max = 960000000 },
};
static const struct clk_range lvdspll_core_outputs[] = {
{ .min = 600000000, .max = 1200000000 },
};
static const struct clk_range audiopll_core_outputs[] = {
{ .min = 600000000, .max = 1200000000 },
};
static const struct clk_range plladiv2_core_outputs[] = {
{ .min = 800000000, .max = 1600000000 },
};
/* Fractional PLL output range. */
static const struct clk_range plla_outputs[] = {
{ .min = 400000000, .max = 800000000 },
};
static const struct clk_range upll_outputs[] = {
{ .min = 300000000, .max = 480000000 },
};
static const struct clk_range lvdspll_outputs[] = {
{ .min = 175000000, .max = 550000000 },
};
static const struct clk_range audiopll_outputs[] = {
{ .min = 0, .max = 300000000 },
};
static const struct clk_range plladiv2_outputs[] = {
{ .min = 200000000, .max = 400000000 },
};
/* PLL characteristics. */
static const struct clk_pll_characteristics plla_characteristics = {
.input = { .min = 20000000, .max = 50000000 },
.num_output = ARRAY_SIZE(plla_outputs),
.output = plla_outputs,
.core_output = plla_core_outputs,
.acr = UL(0x00020010), /* Old ACR_DEFAULT_PLLA value */
};
static const struct clk_pll_characteristics upll_characteristics = {
.input = { .min = 20000000, .max = 50000000 },
.num_output = ARRAY_SIZE(upll_outputs),
.output = upll_outputs,
.core_output = upll_core_outputs,
.upll = true,
.acr = UL(0x12023010), /* fIN=[20 MHz, 32 MHz] */
};
static const struct clk_pll_characteristics lvdspll_characteristics = {
.input = { .min = 20000000, .max = 50000000 },
.num_output = ARRAY_SIZE(lvdspll_outputs),
.output = lvdspll_outputs,
.core_output = lvdspll_core_outputs,
.acr = UL(0x12023010), /* fIN=[20 MHz, 32 MHz] */
};
static const struct clk_pll_characteristics audiopll_characteristics = {
.input = { .min = 20000000, .max = 50000000 },
.num_output = ARRAY_SIZE(audiopll_outputs),
.output = audiopll_outputs,
.core_output = audiopll_core_outputs,
.acr = UL(0x12023010), /* fIN=[20 MHz, 32 MHz] */
};
static const struct clk_pll_characteristics plladiv2_characteristics = {
.input = { .min = 20000000, .max = 50000000 },
.num_output = ARRAY_SIZE(plladiv2_outputs),
.output = plladiv2_outputs,
.core_output = plladiv2_core_outputs,
.acr = UL(0x00020010), /* Old ACR_DEFAULT_PLLA value */
};
/* Layout for fractional PLL ID PLLA. */
static const struct clk_pll_layout plla_frac_layout = {
.mul_mask = GENMASK(31, 24),
.frac_mask = GENMASK(21, 0),
.mul_shift = 24,
.frac_shift = 0,
.div2 = 1,
};
/* Layout for fractional PLLs. */
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,
};
/* Layout for DIV PLLs. */
static const struct clk_pll_layout pll_divpmc_layout = {
.div_mask = GENMASK(7, 0),
.endiv_mask = BIT(29),
.div_shift = 0,
.endiv_shift = 29,
};
/* Layout for DIV PLL ID PLLADIV2. */
static const struct clk_pll_layout plladiv2_divpmc_layout = {
.div_mask = GENMASK(7, 0),
.endiv_mask = BIT(29),
.div_shift = 0,
.endiv_shift = 29,
.div2 = 1,
};
/* Layout for DIVIO dividers. */
static const struct clk_pll_layout pll_divio_layout = {
.div_mask = GENMASK(19, 12),
.endiv_mask = BIT(30),
.div_shift = 12,
.endiv_shift = 30,
};
/*
* PLL clocks description
* @n: clock name
* @p: clock parent
* @l: clock layout
* @t: clock type
* @c: pll characteristics
* @f: clock flags
* @eid: export index in sam9x7->chws[] array
*/
static const struct {
const char *n;
const char *p;
const struct clk_pll_layout *l;
u8 t;
const struct clk_pll_characteristics *c;
unsigned long f;
u8 eid;
} sam9x7_plls[][3] = {
[PLL_ID_PLLA] = {
{
.n = "plla_fracck",
.p = "mainck",
.l = &plla_frac_layout,
.t = PLL_TYPE_FRAC,
/*
* This feeds plla_divpmcck which feeds CPU. It should
* not be disabled.
*/
.f = CLK_IS_CRITICAL | CLK_SET_RATE_GATE,
.c = &plla_characteristics,
},
{
.n = "plla_divpmcck",
.p = "plla_fracck",
.l = &pll_divpmc_layout,
.t = PLL_TYPE_DIV,
/* This feeds CPU. It should not be disabled */
.f = CLK_IS_CRITICAL | CLK_SET_RATE_GATE,
.eid = PMC_PLLACK,
.c = &plla_characteristics,
},
},
[PLL_ID_UPLL] = {
{
.n = "upll_fracck",
.p = "main_osc",
.l = &pll_frac_layout,
.t = PLL_TYPE_FRAC,
.f = CLK_SET_RATE_GATE,
.c = &upll_characteristics,
},
{
.n = "upll_divpmcck",
.p = "upll_fracck",
.l = &pll_divpmc_layout,
.t = PLL_TYPE_DIV,
.f = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE |
CLK_SET_RATE_PARENT,
.eid = PMC_UTMI,
.c = &upll_characteristics,
},
},
[PLL_ID_AUDIO] = {
{
.n = "audiopll_fracck",
.p = "main_osc",
.l = &pll_frac_layout,
.f = CLK_SET_RATE_GATE,
.c = &audiopll_characteristics,
.t = PLL_TYPE_FRAC,
},
{
.n = "audiopll_divpmcck",
.p = "audiopll_fracck",
.l = &pll_divpmc_layout,
.f = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE |
CLK_SET_RATE_PARENT,
.c = &audiopll_characteristics,
.eid = PMC_AUDIOPMCPLL,
.t = PLL_TYPE_DIV,
},
{
.n = "audiopll_diviock",
.p = "audiopll_fracck",
.l = &pll_divio_layout,
.f = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE |
CLK_SET_RATE_PARENT,
.c = &audiopll_characteristics,
.eid = PMC_AUDIOIOPLL,
.t = PLL_TYPE_DIV,
},
},
[PLL_ID_LVDS] = {
{
.n = "lvdspll_fracck",
.p = "main_osc",
.l = &pll_frac_layout,
.f = CLK_SET_RATE_GATE,
.c = &lvdspll_characteristics,
.t = PLL_TYPE_FRAC,
},
{
.n = "lvdspll_divpmcck",
.p = "lvdspll_fracck",
.l = &pll_divpmc_layout,
.f = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE |
CLK_SET_RATE_PARENT,
.c = &lvdspll_characteristics,
.eid = PMC_LVDSPLL,
.t = PLL_TYPE_DIV,
},
},
[PLL_ID_PLLA_DIV2] = {
{
.n = "plla_div2pmcck",
.p = "plla_fracck",
.l = &plladiv2_divpmc_layout,
/*
* This may feed critical parts of the system like timers.
* It should not be disabled.
*/
.f = CLK_IS_CRITICAL | CLK_SET_RATE_GATE,
.c = &plladiv2_characteristics,
.eid = PMC_PLLADIV2,
.t = PLL_TYPE_DIV,
},
},
};
static const struct clk_programmable_layout sam9x7_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 sam9x7_pcr_layout = {
.offset = 0x88,
.cmd = BIT(31),
.gckcss_mask = GENMASK(12, 8),
.pid_mask = GENMASK(6, 0),
};
static const struct {
char *n;
char *p;
u8 id;
unsigned long flags;
} sam9x7_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 },
};
/*
* Peripheral clocks description
* @n: clock name
* @f: clock flags
* @id: peripheral id
*/
static const struct {
char *n;
unsigned long f;
u8 id;
} sam9x7_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 = "uhphs_clk", .id = 22, },
{ .n = "udphs_clk", .id = 23, },
{ .n = "gmac_clk", .id = 24, },
{ .n = "lcd_clk", .id = 25, },
{ .n = "sdmmc1_clk", .id = 26, },
{ .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 = "pit64b0_clk", .id = 37, },
{ .n = "trng_clk", .id = 38, },
{ .n = "aes_clk", .id = 39, },
{ .n = "tdes_clk", .id = 40, },
{ .n = "sha_clk", .id = 41, },
{ .n = "classd_clk", .id = 42, },
{ .n = "isi_clk", .id = 43, },
{ .n = "pioD_clk", .id = 44, },
{ .n = "tcb1_clk", .id = 45, },
{ .n = "dbgu_clk", .id = 47, },
{ .n = "pmecc_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, .f = CLK_IS_CRITICAL },
{ .n = "csi2dc_clk", .id = 52, },
{ .n = "csi4l_clk", .id = 53, },
{ .n = "dsi4l_clk", .id = 54, },
{ .n = "lvdsc_clk", .id = 56, },
{ .n = "pit64b1_clk", .id = 58, },
{ .n = "puf_clk", .id = 59, },
};
/*
* Generic clock description
* @n: clock name
* @pp: PLL parents
* @pp_mux_table: PLL parents mux table
* @r: clock output range
* @pp_chg_id: id in parent array of changeable PLL parent
* @pp_count: PLL parents count
* @id: clock id
*/
static const struct {
const char *n;
const char *pp[8];
const char pp_mux_table[8];
struct clk_range r;
int pp_chg_id;
u8 pp_count;
u8 id;
} sam9x7_gck[] = {
{
.n = "flex0_gclk",
.id = 5,
.pp = { "plla_div2pmcck", },
.pp_mux_table = { 8, },
.pp_count = 1,
.pp_chg_id = INT_MIN,
},
{
.n = "flex1_gclk",
.id = 6,
.pp = { "plla_div2pmcck", },
.pp_mux_table = { 8, },
.pp_count = 1,
.pp_chg_id = INT_MIN,
},
{
.n = "flex2_gclk",
.id = 7,
.pp = { "plla_div2pmcck", },
.pp_mux_table = { 8, },
.pp_count = 1,
.pp_chg_id = INT_MIN,
},
{
.n = "flex3_gclk",
.id = 8,
.pp = { "plla_div2pmcck", },
.pp_mux_table = { 8, },
.pp_count = 1,
.pp_chg_id = INT_MIN,
},
{
.n = "flex6_gclk",
.id = 9,
.pp = { "plla_div2pmcck", },
.pp_mux_table = { 8, },
.pp_count = 1,
.pp_chg_id = INT_MIN,
},
{
.n = "flex7_gclk",
.id = 10,
.pp = { "plla_div2pmcck", },
.pp_mux_table = { 8, },
.pp_count = 1,
.pp_chg_id = INT_MIN,
},
{
.n = "flex8_gclk",
.id = 11,
.pp = { "plla_div2pmcck", },
.pp_mux_table = { 8, },
.pp_count = 1,
.pp_chg_id = INT_MIN,
},
{
.n = "sdmmc0_gclk",
.id = 12,
.r = { .max = 105000000 },
.pp = { "audiopll_divpmcck", "plla_div2pmcck", },
.pp_mux_table = { 6, 8, },
.pp_count = 2,
.pp_chg_id = INT_MIN,
},
{
.n = "flex4_gclk",
.id = 13,
.pp = { "plla_div2pmcck", },
.pp_mux_table = { 8, },
.pp_count = 1,
.pp_chg_id = INT_MIN,
},
{
.n = "flex5_gclk",
.id = 14,
.pp = { "plla_div2pmcck", },
.pp_mux_table = { 8, },
.pp_count = 1,
.pp_chg_id = INT_MIN,
},
{
.n = "flex9_gclk",
.id = 15,
.pp = { "plla_div2pmcck", },
.pp_mux_table = { 8, },
.pp_count = 1,
.pp_chg_id = INT_MIN,
},
{
.n = "flex10_gclk",
.id = 16,
.pp = { "plla_div2pmcck", },
.pp_mux_table = { 8, },
.pp_count = 1,
.pp_chg_id = INT_MIN,
},
{
.n = "tcb0_gclk",
.id = 17,
.pp = { "audiopll_divpmcck", "plla_div2pmcck", },
.pp_mux_table = { 6, 8, },
.pp_count = 2,
.pp_chg_id = INT_MIN,
},
{
.n = "adc_gclk",
.id = 19,
.pp = { "upll_divpmcck", "plla_div2pmcck", },
.pp_mux_table = { 5, 8, },
.pp_count = 2,
.pp_chg_id = INT_MIN,
},
{
.n = "gmac_gclk",
.id = 24,
.pp = { "audiopll_divpmcck", "plla_div2pmcck", },
.pp_mux_table = { 6, 8, },
.pp_count = 2,
.pp_chg_id = INT_MIN,
},
{
.n = "lcd_gclk",
.id = 25,
.r = { .max = 75000000 },
.pp = { "audiopll_divpmcck", "plla_div2pmcck", },
.pp_mux_table = { 6, 8, },
.pp_count = 2,
.pp_chg_id = INT_MIN,
},
{
.n = "sdmmc1_gclk",
.id = 26,
.r = { .max = 105000000 },
.pp = { "audiopll_divpmcck", "plla_div2pmcck", },
.pp_mux_table = { 6, 8, },
.pp_count = 2,
.pp_chg_id = INT_MIN,
},
{
.n = "mcan0_gclk",
.id = 29,
.r = { .max = 80000000 },
.pp = { "upll_divpmcck", "plla_div2pmcck", },
.pp_mux_table = { 5, 8, },
.pp_count = 2,
.pp_chg_id = INT_MIN,
},
{
.n = "mcan1_gclk",
.id = 30,
.r = { .max = 80000000 },
.pp = { "upll_divpmcck", "plla_div2pmcck", },
.pp_mux_table = { 5, 8, },
.pp_count = 2,
.pp_chg_id = INT_MIN,
},
{
.n = "flex11_gclk",
.id = 32,
.pp = { "plla_div2pmcck", },
.pp_mux_table = { 8, },
.pp_count = 1,
.pp_chg_id = INT_MIN,
},
{
.n = "flex12_gclk",
.id = 33,
.pp = { "plla_div2pmcck", },
.pp_mux_table = { 8, },
.pp_count = 1,
.pp_chg_id = INT_MIN,
},
{
.n = "i2s_gclk",
.id = 34,
.r = { .max = 100000000 },
.pp = { "audiopll_divpmcck", "plla_div2pmcck", },
.pp_mux_table = { 6, 8, },
.pp_count = 2,
.pp_chg_id = INT_MIN,
},
{
.n = "qspi_gclk",
.id = 35,
.r = { .max = 200000000 },
.pp = { "audiopll_divpmcck", "plla_div2pmcck", },
.pp_mux_table = { 6, 8, },
.pp_count = 2,
.pp_chg_id = INT_MIN,
},
{
.n = "pit64b0_gclk",
.id = 37,
.pp = { "plla_div2pmcck", },
.pp_mux_table = { 8, },
.pp_count = 1,
.pp_chg_id = INT_MIN,
},
{
.n = "classd_gclk",
.id = 42,
.r = { .max = 100000000 },
.pp = { "audiopll_divpmcck", "plla_div2pmcck", },
.pp_mux_table = { 6, 8, },
.pp_count = 2,
.pp_chg_id = INT_MIN,
},
{
.n = "tcb1_gclk",
.id = 45,
.pp = { "audiopll_divpmcck", "plla_div2pmcck", },
.pp_mux_table = { 6, 8, },
.pp_count = 2,
.pp_chg_id = INT_MIN,
},
{
.n = "dbgu_gclk",
.id = 47,
.pp = { "plla_div2pmcck", },
.pp_mux_table = { 8, },
.pp_count = 1,
.pp_chg_id = INT_MIN,
},
{
.n = "mipiphy_gclk",
.id = 55,
.r = { .max = 27000000 },
.pp = { "plla_div2pmcck", },
.pp_mux_table = { 8, },
.pp_count = 1,
.pp_chg_id = INT_MIN,
},
{
.n = "pit64b1_gclk",
.id = 58,
.pp = { "plla_div2pmcck", },
.pp_mux_table = { 8, },
.pp_count = 1,
.pp_chg_id = INT_MIN,
},
};
static void __init sam9x7_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 *sam9x7_pmc;
const char *parent_names[9];
void **clk_mux_buffer = NULL;
int clk_mux_buffer_size = 0;
struct clk_hw *main_osc_hw;
struct regmap *regmap;
struct clk_hw *hw;
int i, j;
i = of_property_match_string(np, "clock-names", "td_slck");
if (i < 0)
return;
td_slck_name = of_clk_get_parent_name(np, i);
i = of_property_match_string(np, "clock-names", "md_slck");
if (i < 0)
return;
md_slck_name = of_clk_get_parent_name(np, i);
i = of_property_match_string(np, "clock-names", "main_xtal");
if (i < 0)
return;
mainxtal_name = of_clk_get_parent_name(np, i);
regmap = device_node_to_regmap(np);
if (IS_ERR(regmap))
return;
sam9x7_pmc = pmc_data_allocate(PMC_LVDSPLL + 1,
nck(sam9x7_systemck),
nck(sam9x7_periphck),
nck(sam9x7_gck), 8);
if (!sam9x7_pmc)
return;
clk_mux_buffer = kmalloc(sizeof(void *) *
(ARRAY_SIZE(sam9x7_gck)),
GFP_KERNEL);
if (!clk_mux_buffer)
goto err_free;
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;
sam9x7_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 (!sam9x7_plls[i][j].n)
continue;
switch (sam9x7_plls[i][j].t) {
case PLL_TYPE_FRAC:
if (!strcmp(sam9x7_plls[i][j].p, "mainck"))
parent_hw = sam9x7_pmc->chws[PMC_MAIN];
else if (!strcmp(sam9x7_plls[i][j].p, "main_osc"))
parent_hw = main_osc_hw;
else
parent_hw = __clk_get_hw(of_clk_get_by_name
(np, sam9x7_plls[i][j].p));
hw = sam9x60_clk_register_frac_pll(regmap,
&pmc_pll_lock,
sam9x7_plls[i][j].n,
sam9x7_plls[i][j].p,
parent_hw, i,
sam9x7_plls[i][j].c,
sam9x7_plls[i][j].l,
sam9x7_plls[i][j].f);
break;
case PLL_TYPE_DIV:
hw = sam9x60_clk_register_div_pll(regmap,
&pmc_pll_lock,
sam9x7_plls[i][j].n,
sam9x7_plls[i][j].p, NULL, i,
sam9x7_plls[i][j].c,
sam9x7_plls[i][j].l,
sam9x7_plls[i][j].f, 0);
break;
default:
continue;
}
if (IS_ERR(hw))
goto err_free;
if (sam9x7_plls[i][j].eid)
sam9x7_pmc->chws[sam9x7_plls[i][j].eid] = hw;
}
}
parent_names[0] = md_slck_name;
parent_names[1] = "mainck";
parent_names[2] = "plla_divpmcck";
parent_names[3] = "upll_divpmcck";
hw = at91_clk_register_master_pres(regmap, "masterck_pres", 4,
parent_names, NULL, &sam9x7_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, &sam9x7_master_layout,
&mck_characteristics, &mck_lock,
CLK_SET_RATE_GATE, 0);
if (IS_ERR(hw))
goto err_free;
sam9x7_pmc->chws[PMC_MCK] = hw;
parent_names[0] = "plla_divpmcck";
parent_names[1] = "upll_divpmcck";
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] = "plla_divpmcck";
parent_names[5] = "upll_divpmcck";
parent_names[6] = "audiopll_divpmcck";
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, 7, i,
&sam9x7_programmable_layout,
NULL);
if (IS_ERR(hw))
goto err_free;
sam9x7_pmc->pchws[i] = hw;
}
for (i = 0; i < ARRAY_SIZE(sam9x7_systemck); i++) {
hw = at91_clk_register_system(regmap, sam9x7_systemck[i].n,
sam9x7_systemck[i].p, NULL,
sam9x7_systemck[i].id,
sam9x7_systemck[i].flags);
if (IS_ERR(hw))
goto err_free;
sam9x7_pmc->shws[sam9x7_systemck[i].id] = hw;
}
for (i = 0; i < ARRAY_SIZE(sam9x7_periphck); i++) {
hw = at91_clk_register_sam9x5_peripheral(regmap, &pmc_pcr_lock,
&sam9x7_pcr_layout,
sam9x7_periphck[i].n,
"masterck_div", NULL,
sam9x7_periphck[i].id,
&range, INT_MIN,
sam9x7_periphck[i].f);
if (IS_ERR(hw))
goto err_free;
sam9x7_pmc->phws[sam9x7_periphck[i].id] = hw;
}
parent_names[0] = md_slck_name;
parent_names[1] = td_slck_name;
parent_names[2] = "mainck";
parent_names[3] = "masterck_div";
for (i = 0; i < ARRAY_SIZE(sam9x7_gck); i++) {
u8 num_parents = 4 + sam9x7_gck[i].pp_count;
u32 *mux_table;
mux_table = kmalloc_array(num_parents, sizeof(*mux_table),
GFP_KERNEL);
if (!mux_table)
goto err_free;
PMC_INIT_TABLE(mux_table, 4);
PMC_FILL_TABLE(&mux_table[4], sam9x7_gck[i].pp_mux_table,
sam9x7_gck[i].pp_count);
PMC_FILL_TABLE(&parent_names[4], sam9x7_gck[i].pp,
sam9x7_gck[i].pp_count);
hw = at91_clk_register_generated(regmap, &pmc_pcr_lock,
&sam9x7_pcr_layout,
sam9x7_gck[i].n,
parent_names, NULL, mux_table,
num_parents,
sam9x7_gck[i].id,
&sam9x7_gck[i].r,
sam9x7_gck[i].pp_chg_id);
if (IS_ERR(hw))
goto err_free;
sam9x7_pmc->ghws[sam9x7_gck[i].id] = hw;
clk_mux_buffer[clk_mux_buffer_size++] = mux_table;
}
of_clk_add_hw_provider(np, of_clk_hw_pmc_get, sam9x7_pmc);
kfree(clk_mux_buffer);
return;
err_free:
if (clk_mux_buffer) {
for (i = 0; i < clk_mux_buffer_size; i++)
kfree(clk_mux_buffer[i]);
kfree(clk_mux_buffer);
}
kfree(sam9x7_pmc);
}
/* Some clks are used for a clocksource */
CLK_OF_DECLARE(sam9x7_pmc, "microchip,sam9x7-pmc", sam9x7_pmc_setup);
+399
View File
@@ -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);
+269
View File
@@ -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);
+301
View File
@@ -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);
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+657
View File
@@ -0,0 +1,657 @@
// 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>
#include <dt-bindings/clock/at91.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_obj(*osc);
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_obj(*osc);
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_obj(*slowck);
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, *hw;
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_flex(*clk_data, hws, 2);
if (!clk_data)
goto unregister_slow_osc;
/* MD_SLCK and TD_SLCK. */
clk_data->num = 2;
hw = clk_hw_register_fixed_rate_parent_hw(NULL, "md_slck", slow_rc,
0, 32768);
if (IS_ERR(hw))
goto clk_data_free;
clk_data->hws[SCKC_MD_SLCK] = hw;
parent_hws[0] = slow_rc;
parent_hws[1] = slow_osc;
hw = at91_clk_register_sam9x5_slow(regbase, "td_slck", parent_hws,
2, &at91sam9x60_bits);
if (IS_ERR(hw))
goto unregister_md_slck;
clk_data->hws[SCKC_TD_SLCK] = hw;
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[SCKC_TD_SLCK]);
unregister_md_slck:
clk_hw_unregister(clk_data->hws[SCKC_MD_SLCK]);
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_obj(*osc);
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);