| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * Programmable 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-programmable.c from Linux. |
| */ |
| #include <clk-uclass.h> |
| #include <dm.h> |
| #include <linux/clk-provider.h> |
| #include <linux/clk/at91_pmc.h> |
| |
| #include "pmc.h" |
| |
| #define UBOOT_DM_CLK_AT91_PROG "at91-prog-clk" |
| |
| #define PROG_ID_MAX 7 |
| |
| #define PROG_STATUS_MASK(id) (1 << ((id) + 8)) |
| #define PROG_PRES(_l, _p) (((_p) >> (_l)->pres_shift) & (_l)->pres_mask) |
| #define PROG_MAX_RM9200_CSS 3 |
| |
| struct clk_programmable { |
| void __iomem *base; |
| const u32 *clk_mux_table; |
| const u32 *mux_table; |
| const struct clk_programmable_layout *layout; |
| u32 num_parents; |
| struct clk clk; |
| u8 id; |
| }; |
| |
| #define to_clk_programmable(_c) container_of(_c, struct clk_programmable, clk) |
| |
| static ulong clk_programmable_get_rate(struct clk *clk) |
| { |
| struct clk_programmable *prog = to_clk_programmable(clk); |
| const struct clk_programmable_layout *layout = prog->layout; |
| ulong rate, parent_rate = clk_get_parent_rate(clk); |
| unsigned int pckr; |
| |
| pmc_read(prog->base, AT91_PMC_PCKR(prog->id), &pckr); |
| |
| if (layout->is_pres_direct) |
| rate = parent_rate / (PROG_PRES(layout, pckr) + 1); |
| else |
| rate = parent_rate >> PROG_PRES(layout, pckr); |
| |
| return rate; |
| } |
| |
| static int clk_programmable_set_parent(struct clk *clk, struct clk *parent) |
| { |
| struct clk_programmable *prog = to_clk_programmable(clk); |
| const struct clk_programmable_layout *layout = prog->layout; |
| unsigned int mask = layout->css_mask; |
| int index; |
| |
| index = at91_clk_mux_val_to_index(prog->clk_mux_table, |
| prog->num_parents, parent->id); |
| if (index < 0) |
| return index; |
| |
| index = at91_clk_mux_index_to_val(prog->mux_table, prog->num_parents, |
| index); |
| if (index < 0) |
| return index; |
| |
| if (layout->have_slck_mck) |
| mask |= AT91_PMC_CSSMCK_MCK; |
| |
| if (index > layout->css_mask) { |
| if (index > PROG_MAX_RM9200_CSS && !layout->have_slck_mck) |
| return -EINVAL; |
| |
| index |= AT91_PMC_CSSMCK_MCK; |
| } |
| |
| pmc_update_bits(prog->base, AT91_PMC_PCKR(prog->id), mask, index); |
| |
| return 0; |
| } |
| |
| static ulong clk_programmable_set_rate(struct clk *clk, ulong rate) |
| { |
| struct clk_programmable *prog = to_clk_programmable(clk); |
| const struct clk_programmable_layout *layout = prog->layout; |
| ulong parent_rate = clk_get_parent_rate(clk); |
| ulong div = parent_rate / rate; |
| int shift = 0; |
| |
| if (!parent_rate || !div) |
| return -EINVAL; |
| |
| if (layout->is_pres_direct) { |
| shift = div - 1; |
| |
| if (shift > layout->pres_mask) |
| return -EINVAL; |
| } else { |
| shift = fls(div) - 1; |
| |
| if (div != (1 << shift)) |
| return -EINVAL; |
| |
| if (shift >= layout->pres_mask) |
| return -EINVAL; |
| } |
| |
| pmc_update_bits(prog->base, AT91_PMC_PCKR(prog->id), |
| layout->pres_mask << layout->pres_shift, |
| shift << layout->pres_shift); |
| |
| if (layout->is_pres_direct) |
| return (parent_rate / shift + 1); |
| |
| return parent_rate >> shift; |
| } |
| |
| static const struct clk_ops programmable_ops = { |
| .get_rate = clk_programmable_get_rate, |
| .set_parent = clk_programmable_set_parent, |
| .set_rate = clk_programmable_set_rate, |
| }; |
| |
| struct clk *at91_clk_register_programmable(void __iomem *base, const char *name, |
| const char *const *parent_names, u8 num_parents, u8 id, |
| const struct clk_programmable_layout *layout, |
| const u32 *clk_mux_table, const u32 *mux_table) |
| { |
| struct clk_programmable *prog; |
| struct clk *clk; |
| u32 val, tmp; |
| int ret; |
| |
| if (!base || !name || !parent_names || !num_parents || |
| !layout || !clk_mux_table || !mux_table || id > PROG_ID_MAX) |
| return ERR_PTR(-EINVAL); |
| |
| prog = kzalloc(sizeof(*prog), GFP_KERNEL); |
| if (!prog) |
| return ERR_PTR(-ENOMEM); |
| |
| prog->id = id; |
| prog->layout = layout; |
| prog->base = base; |
| prog->clk_mux_table = clk_mux_table; |
| prog->mux_table = mux_table; |
| prog->num_parents = num_parents; |
| |
| pmc_read(prog->base, AT91_PMC_PCKR(prog->id), &tmp); |
| val = tmp & prog->layout->css_mask; |
| if (layout->have_slck_mck && (tmp & AT91_PMC_CSSMCK_MCK) && !val) |
| ret = PROG_MAX_RM9200_CSS + 1; |
| else |
| ret = at91_clk_mux_val_to_index(prog->mux_table, |
| prog->num_parents, val); |
| if (ret < 0) { |
| kfree(prog); |
| return ERR_PTR(ret); |
| } |
| |
| clk = &prog->clk; |
| clk->flags = CLK_GET_RATE_NOCACHE; |
| ret = clk_register(clk, UBOOT_DM_CLK_AT91_PROG, name, |
| parent_names[ret]); |
| if (ret) { |
| kfree(prog); |
| clk = ERR_PTR(ret); |
| } |
| |
| return clk; |
| } |
| |
| U_BOOT_DRIVER(at91_prog_clk) = { |
| .name = UBOOT_DM_CLK_AT91_PROG, |
| .id = UCLASS_CLK, |
| .ops = &programmable_ops, |
| .flags = DM_FLAG_PRE_RELOC, |
| }; |
| |
| const struct clk_programmable_layout at91rm9200_programmable_layout = { |
| .pres_mask = 0x7, |
| .pres_shift = 2, |
| .css_mask = 0x3, |
| .have_slck_mck = 0, |
| .is_pres_direct = 0, |
| }; |
| |
| const struct clk_programmable_layout at91sam9g45_programmable_layout = { |
| .pres_mask = 0x7, |
| .pres_shift = 2, |
| .css_mask = 0x3, |
| .have_slck_mck = 1, |
| .is_pres_direct = 0, |
| }; |
| |
| const struct clk_programmable_layout at91sam9x5_programmable_layout = { |
| .pres_mask = 0x7, |
| .pres_shift = 4, |
| .css_mask = 0x7, |
| .have_slck_mck = 0, |
| .is_pres_direct = 0, |
| }; |