mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-12-27 13:33:40 +00:00
f1218f0b4f
Add clk-main driver compatible with common clock framework. Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
387 lines
8.3 KiB
C
387 lines
8.3 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* Main clock support for AT91 architectures.
|
|
*
|
|
* Copyright (C) 2020 Microchip Technology Inc. and its subsidiaries
|
|
*
|
|
* Author: Claudiu Beznea <claudiu.beznea@microchip.com>
|
|
*
|
|
* Based on drivers/clk/at91/clk-main.c from Linux.
|
|
*/
|
|
|
|
#include <asm/processor.h>
|
|
#include <common.h>
|
|
#include <clk-uclass.h>
|
|
#include <dm.h>
|
|
#include <linux/clk-provider.h>
|
|
#include <linux/clk/at91_pmc.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/io.h>
|
|
#include "pmc.h"
|
|
|
|
#define UBOOT_DM_CLK_AT91_MAIN_RC "at91-main-rc-clk"
|
|
#define UBOOT_DM_CLK_AT91_MAIN_OSC "at91-main-osc-clk"
|
|
#define UBOOT_DM_CLK_AT91_RM9200_MAIN "at91-rm9200-main-clk"
|
|
#define UBOOT_DM_CLK_AT91_SAM9X5_MAIN "at91-sam9x5-main-clk"
|
|
|
|
#define MOR_KEY_MASK GENMASK(23, 16)
|
|
#define USEC_PER_SEC 1000000UL
|
|
#define SLOW_CLOCK_FREQ 32768
|
|
|
|
#define clk_main_parent_select(s) (((s) & \
|
|
(AT91_PMC_MOSCEN | \
|
|
AT91_PMC_OSCBYPASS)) ? 1 : 0)
|
|
|
|
struct clk_main_rc {
|
|
void __iomem *reg;
|
|
struct clk clk;
|
|
};
|
|
|
|
#define to_clk_main_rc(_clk) container_of(_clk, struct clk_main_rc, clk)
|
|
|
|
struct clk_main_osc {
|
|
void __iomem *reg;
|
|
struct clk clk;
|
|
};
|
|
|
|
#define to_clk_main_osc(_clk) container_of(_clk, struct clk_main_osc, clk)
|
|
|
|
struct clk_main {
|
|
void __iomem *reg;
|
|
const unsigned int *clk_mux_table;
|
|
const char * const *parent_names;
|
|
unsigned int num_parents;
|
|
int type;
|
|
struct clk clk;
|
|
};
|
|
|
|
#define to_clk_main(_clk) container_of(_clk, struct clk_main, clk)
|
|
|
|
static int main_rc_enable(struct clk *clk)
|
|
{
|
|
struct clk_main_rc *main_rc = to_clk_main_rc(clk);
|
|
void __iomem *reg = main_rc->reg;
|
|
unsigned int val;
|
|
|
|
pmc_read(reg, AT91_CKGR_MOR, &val);
|
|
|
|
if (!(val & AT91_PMC_MOSCRCEN)) {
|
|
pmc_update_bits(reg, AT91_CKGR_MOR,
|
|
MOR_KEY_MASK | AT91_PMC_MOSCRCEN,
|
|
AT91_PMC_KEY | AT91_PMC_MOSCRCEN);
|
|
}
|
|
|
|
pmc_read(reg, AT91_PMC_SR, &val);
|
|
while (!(val & AT91_PMC_MOSCRCS)) {
|
|
pmc_read(reg, AT91_PMC_SR, &val);
|
|
debug("waiting for main rc...\n");
|
|
cpu_relax();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int main_rc_disable(struct clk *clk)
|
|
{
|
|
struct clk_main_rc *main_rc = to_clk_main_rc(clk);
|
|
struct reg *reg = main_rc->reg;
|
|
unsigned int val;
|
|
|
|
pmc_read(reg, AT91_CKGR_MOR, &val);
|
|
|
|
if (!(val & AT91_PMC_MOSCRCEN))
|
|
return 0;
|
|
|
|
pmc_update_bits(reg, AT91_CKGR_MOR, MOR_KEY_MASK | AT91_PMC_MOSCRCEN,
|
|
AT91_PMC_KEY);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct clk_ops main_rc_clk_ops = {
|
|
.enable = main_rc_enable,
|
|
.disable = main_rc_disable,
|
|
.get_rate = clk_generic_get_rate,
|
|
};
|
|
|
|
struct clk *at91_clk_main_rc(void __iomem *reg, const char *name,
|
|
const char *parent_name)
|
|
{
|
|
struct clk_main_rc *main_rc;
|
|
struct clk *clk;
|
|
int ret;
|
|
|
|
if (!reg || !name || !parent_name)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
main_rc = kzalloc(sizeof(*main_rc), GFP_KERNEL);
|
|
if (!main_rc)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
main_rc->reg = reg;
|
|
clk = &main_rc->clk;
|
|
|
|
ret = clk_register(clk, UBOOT_DM_CLK_AT91_MAIN_RC, name,
|
|
parent_name);
|
|
if (ret) {
|
|
kfree(main_rc);
|
|
clk = ERR_PTR(ret);
|
|
}
|
|
|
|
return clk;
|
|
}
|
|
|
|
U_BOOT_DRIVER(at91_main_rc_clk) = {
|
|
.name = UBOOT_DM_CLK_AT91_MAIN_RC,
|
|
.id = UCLASS_CLK,
|
|
.ops = &main_rc_clk_ops,
|
|
.flags = DM_FLAG_PRE_RELOC,
|
|
};
|
|
|
|
static int clk_main_osc_enable(struct clk *clk)
|
|
{
|
|
struct clk_main_osc *main = to_clk_main_osc(clk);
|
|
void __iomem *reg = main->reg;
|
|
unsigned int val;
|
|
|
|
pmc_read(reg, AT91_CKGR_MOR, &val);
|
|
val &= ~MOR_KEY_MASK;
|
|
|
|
if (val & AT91_PMC_OSCBYPASS)
|
|
return 0;
|
|
|
|
if (!(val & AT91_PMC_MOSCEN)) {
|
|
val |= AT91_PMC_MOSCEN | AT91_PMC_KEY;
|
|
pmc_write(reg, AT91_CKGR_MOR, val);
|
|
}
|
|
|
|
pmc_read(reg, AT91_PMC_SR, &val);
|
|
while (!(val & AT91_PMC_MOSCS)) {
|
|
pmc_read(reg, AT91_PMC_SR, &val);
|
|
debug("waiting for main osc..\n");
|
|
cpu_relax();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int clk_main_osc_disable(struct clk *clk)
|
|
{
|
|
struct clk_main_osc *main = to_clk_main_osc(clk);
|
|
void __iomem *reg = main->reg;
|
|
unsigned int val;
|
|
|
|
pmc_read(reg, AT91_CKGR_MOR, &val);
|
|
if (val & AT91_PMC_OSCBYPASS)
|
|
return 0;
|
|
|
|
if (!(val & AT91_PMC_MOSCEN))
|
|
return 0;
|
|
|
|
val &= ~(AT91_PMC_KEY | AT91_PMC_MOSCEN);
|
|
pmc_write(reg, AT91_CKGR_MOR, val | AT91_PMC_KEY);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct clk_ops main_osc_clk_ops = {
|
|
.enable = clk_main_osc_enable,
|
|
.disable = clk_main_osc_disable,
|
|
.get_rate = clk_generic_get_rate,
|
|
};
|
|
|
|
struct clk *at91_clk_main_osc(void __iomem *reg, const char *name,
|
|
const char *parent_name, bool bypass)
|
|
{
|
|
struct clk_main_osc *main;
|
|
struct clk *clk;
|
|
int ret;
|
|
|
|
if (!reg || !name || !parent_name)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
main = kzalloc(sizeof(*main), GFP_KERNEL);
|
|
if (!main)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
main->reg = reg;
|
|
clk = &main->clk;
|
|
|
|
if (bypass) {
|
|
pmc_update_bits(reg, AT91_CKGR_MOR,
|
|
MOR_KEY_MASK | AT91_PMC_OSCBYPASS,
|
|
AT91_PMC_KEY | AT91_PMC_OSCBYPASS);
|
|
}
|
|
|
|
ret = clk_register(clk, UBOOT_DM_CLK_AT91_MAIN_OSC, name, parent_name);
|
|
if (ret) {
|
|
kfree(main);
|
|
clk = ERR_PTR(ret);
|
|
}
|
|
|
|
return clk;
|
|
}
|
|
|
|
U_BOOT_DRIVER(at91_main_osc_clk) = {
|
|
.name = UBOOT_DM_CLK_AT91_MAIN_OSC,
|
|
.id = UCLASS_CLK,
|
|
.ops = &main_osc_clk_ops,
|
|
.flags = DM_FLAG_PRE_RELOC,
|
|
};
|
|
|
|
static int clk_main_probe_frequency(void __iomem *reg)
|
|
{
|
|
unsigned int cycles = 16;
|
|
unsigned int cycle = DIV_ROUND_UP(USEC_PER_SEC, SLOW_CLOCK_FREQ);
|
|
unsigned int mcfr;
|
|
|
|
while (cycles--) {
|
|
pmc_read(reg, AT91_CKGR_MCFR, &mcfr);
|
|
if (mcfr & AT91_PMC_MAINRDY)
|
|
return 0;
|
|
udelay(cycle);
|
|
}
|
|
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
static int clk_rm9200_main_enable(struct clk *clk)
|
|
{
|
|
struct clk_main *main = to_clk_main(clk);
|
|
|
|
return clk_main_probe_frequency(main->reg);
|
|
}
|
|
|
|
static const struct clk_ops rm9200_main_clk_ops = {
|
|
.enable = clk_rm9200_main_enable,
|
|
};
|
|
|
|
struct clk *at91_clk_rm9200_main(void __iomem *reg, const char *name,
|
|
const char *parent_name)
|
|
{
|
|
struct clk_main *main;
|
|
struct clk *clk;
|
|
int ret;
|
|
|
|
if (!reg || !name || !parent_name)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
main = kzalloc(sizeof(*main), GFP_KERNEL);
|
|
if (!main)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
main->reg = reg;
|
|
clk = &main->clk;
|
|
|
|
ret = clk_register(clk, UBOOT_DM_CLK_AT91_RM9200_MAIN, name,
|
|
parent_name);
|
|
if (ret) {
|
|
kfree(main);
|
|
clk = ERR_PTR(ret);
|
|
}
|
|
|
|
return clk;
|
|
}
|
|
|
|
U_BOOT_DRIVER(at91_rm9200_main_clk) = {
|
|
.name = UBOOT_DM_CLK_AT91_RM9200_MAIN,
|
|
.id = UCLASS_CLK,
|
|
.ops = &rm9200_main_clk_ops,
|
|
.flags = DM_FLAG_PRE_RELOC,
|
|
};
|
|
|
|
static inline bool clk_sam9x5_main_ready(void __iomem *reg)
|
|
{
|
|
unsigned int val;
|
|
|
|
pmc_read(reg, AT91_PMC_SR, &val);
|
|
|
|
return !!(val & AT91_PMC_MOSCSELS);
|
|
}
|
|
|
|
static int clk_sam9x5_main_enable(struct clk *clk)
|
|
{
|
|
struct clk_main *main = to_clk_main(clk);
|
|
void __iomem *reg = main->reg;
|
|
|
|
while (!clk_sam9x5_main_ready(reg)) {
|
|
debug("waiting for main...");
|
|
cpu_relax();
|
|
}
|
|
|
|
return clk_main_probe_frequency(reg);
|
|
}
|
|
|
|
static int clk_sam9x5_main_set_parent(struct clk *clk, struct clk *parent)
|
|
{
|
|
struct clk_main *main = to_clk_main(clk);
|
|
void __iomem *reg = main->reg;
|
|
unsigned int tmp, index;
|
|
|
|
index = at91_clk_mux_val_to_index(main->clk_mux_table,
|
|
main->num_parents, AT91_CLK_ID_TO_DID(parent->id));
|
|
if (index < 0)
|
|
return index;
|
|
|
|
pmc_read(reg, AT91_CKGR_MOR, &tmp);
|
|
tmp &= ~MOR_KEY_MASK;
|
|
tmp |= AT91_PMC_KEY;
|
|
|
|
if (index && !(tmp & AT91_PMC_MOSCSEL))
|
|
pmc_write(reg, AT91_CKGR_MOR, tmp | AT91_PMC_MOSCSEL);
|
|
else if (!index && (tmp & AT91_PMC_MOSCSEL))
|
|
pmc_write(reg, AT91_CKGR_MOR, tmp & ~AT91_PMC_MOSCSEL);
|
|
|
|
while (!clk_sam9x5_main_ready(reg))
|
|
cpu_relax();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct clk_ops sam9x5_main_clk_ops = {
|
|
.enable = clk_sam9x5_main_enable,
|
|
.set_parent = clk_sam9x5_main_set_parent,
|
|
.get_rate = clk_generic_get_rate,
|
|
};
|
|
|
|
struct clk *at91_clk_sam9x5_main(void __iomem *reg, const char *name,
|
|
const char * const *parent_names,
|
|
int num_parents, const u32 *clk_mux_table,
|
|
int type)
|
|
{
|
|
struct clk *clk = ERR_PTR(-ENOMEM);
|
|
struct clk_main *main = NULL;
|
|
unsigned int val;
|
|
int ret;
|
|
|
|
if (!reg || !name || !parent_names || !num_parents || !clk_mux_table)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
main = kzalloc(sizeof(*main), GFP_KERNEL);
|
|
if (!main)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
main->reg = reg;
|
|
main->parent_names = parent_names;
|
|
main->num_parents = num_parents;
|
|
main->clk_mux_table = clk_mux_table;
|
|
main->type = type;
|
|
clk = &main->clk;
|
|
clk->flags = CLK_GET_RATE_NOCACHE;
|
|
pmc_read(reg, AT91_CKGR_MOR, &val);
|
|
ret = clk_register(clk, UBOOT_DM_CLK_AT91_SAM9X5_MAIN, name,
|
|
main->parent_names[clk_main_parent_select(val)]);
|
|
if (ret) {
|
|
kfree(main);
|
|
clk = ERR_PTR(ret);
|
|
}
|
|
|
|
return clk;
|
|
}
|
|
|
|
U_BOOT_DRIVER(at91_sam9x5_main_clk) = {
|
|
.name = UBOOT_DM_CLK_AT91_SAM9X5_MAIN,
|
|
.id = UCLASS_CLK,
|
|
.ops = &sam9x5_main_clk_ops,
|
|
.flags = DM_FLAG_PRE_RELOC,
|
|
};
|