| // 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 <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 <linux/time.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 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, |
| }; |