// SPDX-License-Identifier: GPL-2.0+ /* * Main clock support for AT91 architectures. * * Copyright (C) 2020 Microchip Technology Inc. and its subsidiaries * * Author: Claudiu Beznea * * Based on drivers/clk/at91/clk-main.c from Linux. */ #include #include #include #include #include #include #include #include #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, };