forked from Minki/linux
7a110b9107
Re-factor master clock driver by splitting it into 2 clocks: prescaller and divider clocks. Based on registered clock flags the prescaler's rate could be changed at runtime. This is necessary for platforms supporting DVFS (e.g. SAMA7G5) where master clock could be changed at run-time. Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com> Link: https://lore.kernel.org/r/1605800597-16720-11-git-send-email-claudiu.beznea@microchip.com Signed-off-by: Stephen Boyd <sboyd@kernel.org>
233 lines
6.1 KiB
C
233 lines
6.1 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
#include <linux/clk-provider.h>
|
|
#include <linux/mfd/syscon.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include <dt-bindings/clock/at91.h>
|
|
|
|
#include "pmc.h"
|
|
|
|
static DEFINE_SPINLOCK(at91sam9g45_mck_lock);
|
|
|
|
static const struct clk_master_characteristics mck_characteristics = {
|
|
.output = { .min = 0, .max = 133333333 },
|
|
.divisors = { 1, 2, 4, 3 },
|
|
};
|
|
|
|
static u8 plla_out[] = { 0, 1, 2, 3, 0, 1, 2, 3 };
|
|
|
|
static u16 plla_icpll[] = { 0, 0, 0, 0, 1, 1, 1, 1 };
|
|
|
|
static const struct clk_range plla_outputs[] = {
|
|
{ .min = 745000000, .max = 800000000 },
|
|
{ .min = 695000000, .max = 750000000 },
|
|
{ .min = 645000000, .max = 700000000 },
|
|
{ .min = 595000000, .max = 650000000 },
|
|
{ .min = 545000000, .max = 600000000 },
|
|
{ .min = 495000000, .max = 555000000 },
|
|
{ .min = 445000000, .max = 500000000 },
|
|
{ .min = 400000000, .max = 450000000 },
|
|
};
|
|
|
|
static const struct clk_pll_characteristics plla_characteristics = {
|
|
.input = { .min = 2000000, .max = 32000000 },
|
|
.num_output = ARRAY_SIZE(plla_outputs),
|
|
.output = plla_outputs,
|
|
.icpll = plla_icpll,
|
|
.out = plla_out,
|
|
};
|
|
|
|
static const struct {
|
|
char *n;
|
|
char *p;
|
|
u8 id;
|
|
} at91sam9g45_systemck[] = {
|
|
{ .n = "ddrck", .p = "masterck_div", .id = 2 },
|
|
{ .n = "uhpck", .p = "usbck", .id = 6 },
|
|
{ .n = "pck0", .p = "prog0", .id = 8 },
|
|
{ .n = "pck1", .p = "prog1", .id = 9 },
|
|
};
|
|
|
|
struct pck {
|
|
char *n;
|
|
u8 id;
|
|
};
|
|
|
|
static const struct pck at91sam9g45_periphck[] = {
|
|
{ .n = "pioA_clk", .id = 2, },
|
|
{ .n = "pioB_clk", .id = 3, },
|
|
{ .n = "pioC_clk", .id = 4, },
|
|
{ .n = "pioDE_clk", .id = 5, },
|
|
{ .n = "trng_clk", .id = 6, },
|
|
{ .n = "usart0_clk", .id = 7, },
|
|
{ .n = "usart1_clk", .id = 8, },
|
|
{ .n = "usart2_clk", .id = 9, },
|
|
{ .n = "usart3_clk", .id = 10, },
|
|
{ .n = "mci0_clk", .id = 11, },
|
|
{ .n = "twi0_clk", .id = 12, },
|
|
{ .n = "twi1_clk", .id = 13, },
|
|
{ .n = "spi0_clk", .id = 14, },
|
|
{ .n = "spi1_clk", .id = 15, },
|
|
{ .n = "ssc0_clk", .id = 16, },
|
|
{ .n = "ssc1_clk", .id = 17, },
|
|
{ .n = "tcb0_clk", .id = 18, },
|
|
{ .n = "pwm_clk", .id = 19, },
|
|
{ .n = "adc_clk", .id = 20, },
|
|
{ .n = "dma0_clk", .id = 21, },
|
|
{ .n = "uhphs_clk", .id = 22, },
|
|
{ .n = "lcd_clk", .id = 23, },
|
|
{ .n = "ac97_clk", .id = 24, },
|
|
{ .n = "macb0_clk", .id = 25, },
|
|
{ .n = "isi_clk", .id = 26, },
|
|
{ .n = "udphs_clk", .id = 27, },
|
|
{ .n = "aestdessha_clk", .id = 28, },
|
|
{ .n = "mci1_clk", .id = 29, },
|
|
{ .n = "vdec_clk", .id = 30, },
|
|
};
|
|
|
|
static void __init at91sam9g45_pmc_setup(struct device_node *np)
|
|
{
|
|
const char *slck_name, *mainxtal_name;
|
|
struct pmc_data *at91sam9g45_pmc;
|
|
const char *parent_names[6];
|
|
struct regmap *regmap;
|
|
struct clk_hw *hw;
|
|
int i;
|
|
bool bypass;
|
|
|
|
i = of_property_match_string(np, "clock-names", "slow_clk");
|
|
if (i < 0)
|
|
return;
|
|
|
|
slck_name = of_clk_get_parent_name(np, i);
|
|
|
|
i = of_property_match_string(np, "clock-names", "main_xtal");
|
|
if (i < 0)
|
|
return;
|
|
mainxtal_name = of_clk_get_parent_name(np, i);
|
|
|
|
regmap = device_node_to_regmap(np);
|
|
if (IS_ERR(regmap))
|
|
return;
|
|
|
|
at91sam9g45_pmc = pmc_data_allocate(PMC_PLLACK + 1,
|
|
nck(at91sam9g45_systemck),
|
|
nck(at91sam9g45_periphck), 0, 2);
|
|
if (!at91sam9g45_pmc)
|
|
return;
|
|
|
|
bypass = of_property_read_bool(np, "atmel,osc-bypass");
|
|
|
|
hw = at91_clk_register_main_osc(regmap, "main_osc", mainxtal_name,
|
|
bypass);
|
|
if (IS_ERR(hw))
|
|
goto err_free;
|
|
|
|
hw = at91_clk_register_rm9200_main(regmap, "mainck", "main_osc");
|
|
if (IS_ERR(hw))
|
|
goto err_free;
|
|
|
|
at91sam9g45_pmc->chws[PMC_MAIN] = hw;
|
|
|
|
hw = at91_clk_register_pll(regmap, "pllack", "mainck", 0,
|
|
&at91rm9200_pll_layout, &plla_characteristics);
|
|
if (IS_ERR(hw))
|
|
goto err_free;
|
|
|
|
hw = at91_clk_register_plldiv(regmap, "plladivck", "pllack");
|
|
if (IS_ERR(hw))
|
|
goto err_free;
|
|
|
|
at91sam9g45_pmc->chws[PMC_PLLACK] = hw;
|
|
|
|
hw = at91_clk_register_utmi(regmap, NULL, "utmick", "mainck");
|
|
if (IS_ERR(hw))
|
|
goto err_free;
|
|
|
|
at91sam9g45_pmc->chws[PMC_UTMI] = hw;
|
|
|
|
parent_names[0] = slck_name;
|
|
parent_names[1] = "mainck";
|
|
parent_names[2] = "plladivck";
|
|
parent_names[3] = "utmick";
|
|
hw = at91_clk_register_master_pres(regmap, "masterck_pres", 4,
|
|
parent_names,
|
|
&at91rm9200_master_layout,
|
|
&mck_characteristics,
|
|
&at91sam9g45_mck_lock,
|
|
CLK_SET_RATE_GATE, INT_MIN);
|
|
if (IS_ERR(hw))
|
|
goto err_free;
|
|
|
|
hw = at91_clk_register_master_div(regmap, "masterck_div",
|
|
"masterck_pres",
|
|
&at91rm9200_master_layout,
|
|
&mck_characteristics,
|
|
&at91sam9g45_mck_lock,
|
|
CLK_SET_RATE_GATE);
|
|
if (IS_ERR(hw))
|
|
goto err_free;
|
|
|
|
at91sam9g45_pmc->chws[PMC_MCK] = hw;
|
|
|
|
parent_names[0] = "plladivck";
|
|
parent_names[1] = "utmick";
|
|
hw = at91sam9x5_clk_register_usb(regmap, "usbck", parent_names, 2);
|
|
if (IS_ERR(hw))
|
|
goto err_free;
|
|
|
|
parent_names[0] = slck_name;
|
|
parent_names[1] = "mainck";
|
|
parent_names[2] = "plladivck";
|
|
parent_names[3] = "utmick";
|
|
parent_names[4] = "masterck_div";
|
|
for (i = 0; i < 2; i++) {
|
|
char name[6];
|
|
|
|
snprintf(name, sizeof(name), "prog%d", i);
|
|
|
|
hw = at91_clk_register_programmable(regmap, name,
|
|
parent_names, 5, i,
|
|
&at91sam9g45_programmable_layout,
|
|
NULL);
|
|
if (IS_ERR(hw))
|
|
goto err_free;
|
|
|
|
at91sam9g45_pmc->pchws[i] = hw;
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(at91sam9g45_systemck); i++) {
|
|
hw = at91_clk_register_system(regmap, at91sam9g45_systemck[i].n,
|
|
at91sam9g45_systemck[i].p,
|
|
at91sam9g45_systemck[i].id);
|
|
if (IS_ERR(hw))
|
|
goto err_free;
|
|
|
|
at91sam9g45_pmc->shws[at91sam9g45_systemck[i].id] = hw;
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(at91sam9g45_periphck); i++) {
|
|
hw = at91_clk_register_peripheral(regmap,
|
|
at91sam9g45_periphck[i].n,
|
|
"masterck_div",
|
|
at91sam9g45_periphck[i].id);
|
|
if (IS_ERR(hw))
|
|
goto err_free;
|
|
|
|
at91sam9g45_pmc->phws[at91sam9g45_periphck[i].id] = hw;
|
|
}
|
|
|
|
of_clk_add_hw_provider(np, of_clk_hw_pmc_get, at91sam9g45_pmc);
|
|
|
|
return;
|
|
|
|
err_free:
|
|
kfree(at91sam9g45_pmc);
|
|
}
|
|
/*
|
|
* The TCB is used as the clocksource so its clock is needed early. This means
|
|
* this can't be a platform driver.
|
|
*/
|
|
CLK_OF_DECLARE_DRIVER(at91sam9g45_pmc, "atmel,at91sam9g45-pmc",
|
|
at91sam9g45_pmc_setup);
|