diff options
Diffstat (limited to 'roms/u-boot/drivers/clk/at91/clk-master.c')
-rw-r--r-- | roms/u-boot/drivers/clk/at91/clk-master.c | 332 |
1 files changed, 332 insertions, 0 deletions
diff --git a/roms/u-boot/drivers/clk/at91/clk-master.c b/roms/u-boot/drivers/clk/at91/clk-master.c new file mode 100644 index 000000000..5d93e6a7e --- /dev/null +++ b/roms/u-boot/drivers/clk/at91/clk-master.c @@ -0,0 +1,332 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Master 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-master.c from Linux. + */ + +#include <asm/processor.h> +#include <clk-uclass.h> +#include <common.h> +#include <dm.h> +#include <linux/clk-provider.h> +#include <linux/clk/at91_pmc.h> + +#include "pmc.h" + +#define UBOOT_DM_CLK_AT91_MASTER "at91-master-clk" +#define UBOOT_DM_CLK_AT91_SAMA7G5_MASTER "at91-sama7g5-master-clk" + +#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 0x30 +#define PMC_MCR_ID_MSK GENMASK(3, 0) +#define PMC_MCR_CMD BIT(7) +#define PMC_MCR_DIV GENMASK(10, 8) +#define PMC_MCR_CSS GENMASK(20, 16) +#define PMC_MCR_CSS_SHIFT (16) +#define PMC_MCR_EN BIT(28) + +#define PMC_MCR_ID(x) ((x) & PMC_MCR_ID_MSK) + +#define MASTER_MAX_ID 4 + +struct clk_master { + void __iomem *base; + const struct clk_master_layout *layout; + const struct clk_master_characteristics *characteristics; + const u32 *mux_table; + const u32 *clk_mux_table; + u32 num_parents; + struct clk clk; + u8 id; +}; + +#define to_clk_master(_clk) container_of(_clk, struct clk_master, clk) + +static inline bool clk_master_ready(struct clk_master *master) +{ + unsigned int bit = master->id ? AT91_PMC_MCKXRDY : AT91_PMC_MCKRDY; + unsigned int status; + + pmc_read(master->base, AT91_PMC_SR, &status); + + return !!(status & bit); +} + +static int clk_master_enable(struct clk *clk) +{ + struct clk_master *master = to_clk_master(clk); + + while (!clk_master_ready(master)) { + debug("waiting for mck %d\n", master->id); + cpu_relax(); + } + + return 0; +} + +static ulong clk_master_get_rate(struct clk *clk) +{ + struct clk_master *master = to_clk_master(clk); + const struct clk_master_layout *layout = master->layout; + const struct clk_master_characteristics *characteristics = + master->characteristics; + ulong rate = clk_get_parent_rate(clk); + unsigned int mckr; + u8 pres, div; + + if (!rate) + return 0; + + pmc_read(master->base, master->layout->offset, &mckr); + mckr &= layout->mask; + + pres = (mckr >> layout->pres_shift) & MASTER_PRES_MASK; + div = (mckr >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK; + + if (characteristics->have_div3_pres && pres == MASTER_PRES_MAX) + rate /= 3; + else + rate >>= pres; + + rate /= characteristics->divisors[div]; + + if (rate < characteristics->output.min) + pr_warn("master clk is underclocked"); + else if (rate > characteristics->output.max) + pr_warn("master clk is overclocked"); + + return rate; +} + +static const struct clk_ops master_ops = { + .enable = clk_master_enable, + .get_rate = clk_master_get_rate, +}; + +struct clk *at91_clk_register_master(void __iomem *base, + const char *name, const char * const *parent_names, + int num_parents, const struct clk_master_layout *layout, + const struct clk_master_characteristics *characteristics, + const u32 *mux_table) +{ + struct clk_master *master; + struct clk *clk; + unsigned int val; + int ret; + + if (!base || !name || !num_parents || !parent_names || + !layout || !characteristics || !mux_table) + return ERR_PTR(-EINVAL); + + master = kzalloc(sizeof(*master), GFP_KERNEL); + if (!master) + return ERR_PTR(-ENOMEM); + + master->layout = layout; + master->characteristics = characteristics; + master->base = base; + master->num_parents = num_parents; + master->mux_table = mux_table; + + pmc_read(master->base, master->layout->offset, &val); + clk = &master->clk; + clk->flags = CLK_GET_RATE_NOCACHE | CLK_IS_CRITICAL; + ret = clk_register(clk, UBOOT_DM_CLK_AT91_MASTER, name, + parent_names[val & AT91_PMC_CSS]); + if (ret) { + kfree(master); + clk = ERR_PTR(ret); + } + + return clk; +} + +U_BOOT_DRIVER(at91_master_clk) = { + .name = UBOOT_DM_CLK_AT91_MASTER, + .id = UCLASS_CLK, + .ops = &master_ops, + .flags = DM_FLAG_PRE_RELOC, +}; + +static int clk_sama7g5_master_set_parent(struct clk *clk, struct clk *parent) +{ + struct clk_master *master = to_clk_master(clk); + int index; + + index = at91_clk_mux_val_to_index(master->clk_mux_table, + master->num_parents, parent->id); + if (index < 0) + return index; + + index = at91_clk_mux_index_to_val(master->mux_table, + master->num_parents, index); + if (index < 0) + return index; + + pmc_write(master->base, PMC_MCR, PMC_MCR_ID(master->id)); + pmc_update_bits(master->base, PMC_MCR, + PMC_MCR_CSS | PMC_MCR_CMD | PMC_MCR_ID_MSK, + (index << PMC_MCR_CSS_SHIFT) | PMC_MCR_CMD | + PMC_MCR_ID(master->id)); + return 0; +} + +static int clk_sama7g5_master_enable(struct clk *clk) +{ + struct clk_master *master = to_clk_master(clk); + + pmc_write(master->base, PMC_MCR, PMC_MCR_ID(master->id)); + pmc_update_bits(master->base, PMC_MCR, + PMC_MCR_EN | PMC_MCR_CMD | PMC_MCR_ID_MSK, + PMC_MCR_EN | PMC_MCR_CMD | PMC_MCR_ID(master->id)); + + return 0; +} + +static int clk_sama7g5_master_disable(struct clk *clk) +{ + struct clk_master *master = to_clk_master(clk); + + pmc_write(master->base, PMC_MCR, master->id); + pmc_update_bits(master->base, PMC_MCR, + PMC_MCR_EN | PMC_MCR_CMD | PMC_MCR_ID_MSK, + PMC_MCR_CMD | PMC_MCR_ID(master->id)); + + return 0; +} + +static ulong clk_sama7g5_master_set_rate(struct clk *clk, ulong rate) +{ + struct clk_master *master = to_clk_master(clk); + ulong parent_rate = clk_get_parent_rate(clk); + ulong div, rrate; + + if (!parent_rate) + return 0; + + div = DIV_ROUND_CLOSEST(parent_rate, rate); + if ((div > (1 << (MASTER_PRES_MAX - 1))) || (div & (div - 1))) { + return 0; + } else if (div == 3) { + rrate = DIV_ROUND_CLOSEST(parent_rate, MASTER_PRES_MAX); + div = MASTER_PRES_MAX; + } else { + rrate = DIV_ROUND_CLOSEST(parent_rate, div); + div = ffs(div) - 1; + } + + pmc_write(master->base, PMC_MCR, master->id); + pmc_update_bits(master->base, PMC_MCR, + PMC_MCR_DIV | PMC_MCR_CMD | PMC_MCR_ID_MSK, + (div << MASTER_DIV_SHIFT) | PMC_MCR_CMD | + PMC_MCR_ID(master->id)); + + return rrate; +} + +static ulong clk_sama7g5_master_get_rate(struct clk *clk) +{ + struct clk_master *master = to_clk_master(clk); + ulong parent_rate = clk_get_parent_rate(clk); + unsigned int val; + ulong div; + + if (!parent_rate) + return 0; + + pmc_write(master->base, PMC_MCR, master->id); + pmc_read(master->base, PMC_MCR, &val); + + div = (val >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK; + + if (div == MASTER_PRES_MAX) + div = 3; + else + div = 1 << div; + + return DIV_ROUND_CLOSEST(parent_rate, div); +} + +static const struct clk_ops sama7g5_master_ops = { + .enable = clk_sama7g5_master_enable, + .disable = clk_sama7g5_master_disable, + .set_rate = clk_sama7g5_master_set_rate, + .get_rate = clk_sama7g5_master_get_rate, + .set_parent = clk_sama7g5_master_set_parent, +}; + +struct clk *at91_clk_sama7g5_register_master(void __iomem *base, + const char *name, const char * const *parent_names, + int num_parents, const u32 *mux_table, const u32 *clk_mux_table, + bool critical, u8 id) +{ + struct clk_master *master; + struct clk *clk; + u32 val, index; + int ret; + + if (!base || !name || !num_parents || !parent_names || + !mux_table || !clk_mux_table || id > MASTER_MAX_ID) + return ERR_PTR(-EINVAL); + + master = kzalloc(sizeof(*master), GFP_KERNEL); + if (!master) + return ERR_PTR(-ENOMEM); + + master->base = base; + master->id = id; + master->mux_table = mux_table; + master->clk_mux_table = clk_mux_table; + master->num_parents = num_parents; + + pmc_write(master->base, PMC_MCR, master->id); + pmc_read(master->base, PMC_MCR, &val); + + index = at91_clk_mux_val_to_index(master->mux_table, + master->num_parents, + (val & PMC_MCR_CSS) >> PMC_MCR_CSS_SHIFT); + if (index < 0) { + kfree(master); + return ERR_PTR(index); + } + + clk = &master->clk; + clk->flags = CLK_GET_RATE_NOCACHE | (critical ? CLK_IS_CRITICAL : 0); + + ret = clk_register(clk, UBOOT_DM_CLK_AT91_SAMA7G5_MASTER, name, + parent_names[index]); + if (ret) { + kfree(master); + clk = ERR_PTR(ret); + } + + return clk; +} + +U_BOOT_DRIVER(at91_sama7g5_master_clk) = { + .name = UBOOT_DM_CLK_AT91_SAMA7G5_MASTER, + .id = UCLASS_CLK, + .ops = &sama7g5_master_ops, + .flags = DM_FLAG_PRE_RELOC, +}; + +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, +}; |