clk: at91: clk-main: add driver compatible with ccf
Add clk-main driver compatible with common clock framework. Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
This commit is contained in:
parent
e9885aa7cc
commit
f1218f0b4f
@ -3,7 +3,7 @@
|
||||
#
|
||||
|
||||
ifdef CONFIG_CLK_CCF
|
||||
obj-y += pmc.o sckc.o
|
||||
obj-y += pmc.o sckc.o clk-main.o
|
||||
else
|
||||
obj-y += compat.o
|
||||
endif
|
||||
|
387
drivers/clk/at91/clk-main.c
Normal file
387
drivers/clk/at91/clk-main.c
Normal file
@ -0,0 +1,387 @@
|
||||
// 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,
|
||||
};
|
@ -14,6 +14,16 @@
|
||||
#define AT91_TO_CLK_ID(_t, _i) (((_t) << 8) | ((_i) & 0xff))
|
||||
#define AT91_CLK_ID_TO_DID(_i) ((_i) & 0xff)
|
||||
|
||||
struct clk *at91_clk_main_rc(void __iomem *reg, const char *name,
|
||||
const char *parent_name);
|
||||
struct clk *at91_clk_main_osc(void __iomem *reg, const char *name,
|
||||
const char *parent_name, bool bypass);
|
||||
struct clk *at91_clk_rm9200_main(void __iomem *reg, const char *name,
|
||||
const char *parent_name);
|
||||
struct clk *at91_clk_sam9x5_main(void __iomem *reg, const char *name,
|
||||
const char * const *parent_names, int num_parents,
|
||||
const u32 *mux_table, int type);
|
||||
|
||||
int at91_clk_mux_val_to_index(const u32 *table, u32 num_parents, u32 val);
|
||||
int at91_clk_mux_index_to_val(const u32 *table, u32 num_parents, u32 index);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user