// SPDX-License-Identifier: GPL-2.0+ /* * Copyright (C) 2020 Microchip Technology Inc. * Padmarao Begari */ #include #include #include #include #include #include #include #include #include #include "mpfs_clk.h" #define MPFS_CFG_CLOCK "mpfs_cfg_clock" #define REG_CLOCK_CONFIG_CR 0x08 /* CPU and AXI clock divisors */ static const struct clk_div_table mpfs_div_cpu_axi_table[] = { { 0, 1 }, { 1, 2 }, { 2, 4 }, { 3, 8 }, { 0, 0 } }; /* AHB clock divisors */ static const struct clk_div_table mpfs_div_ahb_table[] = { { 1, 2 }, { 2, 4}, { 3, 8 }, { 0, 0 } }; /** * struct mpfs_cfg_clock - per instance of configuration clock * @id: index of a configuration clock * @name: name of a configuration clock * @shift: shift to the divider bit field of a configuration clock * @width: width of the divider bit field of a configation clock * @table: clock divider table instance * @flags: common clock framework flags */ struct mpfs_cfg_clock { unsigned int id; const char *name; u8 shift; u8 width; const struct clk_div_table *table; unsigned long flags; }; /** * struct mpfs_cfg_hw_clock - hardware configuration clock (cpu, axi, ahb) * @cfg: configuration clock instance * @sys_base: base address of the mpfs system register * @prate: the pll clock rate * @hw: clock instance */ struct mpfs_cfg_hw_clock { struct mpfs_cfg_clock cfg; void __iomem *sys_base; u32 prate; struct clk hw; }; #define to_mpfs_cfg_clk(_hw) container_of(_hw, struct mpfs_cfg_hw_clock, hw) static ulong mpfs_cfg_clk_recalc_rate(struct clk *hw) { struct mpfs_cfg_hw_clock *cfg_hw = to_mpfs_cfg_clk(hw); struct mpfs_cfg_clock *cfg = &cfg_hw->cfg; void __iomem *base_addr = cfg_hw->sys_base; unsigned long rate; u32 val; val = readl(base_addr + REG_CLOCK_CONFIG_CR) >> cfg->shift; val &= clk_div_mask(cfg->width); rate = cfg_hw->prate / (1u << val); hw->rate = rate; return rate; } static ulong mpfs_cfg_clk_set_rate(struct clk *hw, ulong rate) { struct mpfs_cfg_hw_clock *cfg_hw = to_mpfs_cfg_clk(hw); struct mpfs_cfg_clock *cfg = &cfg_hw->cfg; void __iomem *base_addr = cfg_hw->sys_base; u32 val; int divider_setting; divider_setting = divider_get_val(rate, cfg_hw->prate, cfg->table, cfg->width, cfg->flags); if (divider_setting < 0) return divider_setting; val = readl(base_addr + REG_CLOCK_CONFIG_CR); val &= ~(clk_div_mask(cfg->width) << cfg_hw->cfg.shift); val |= divider_setting << cfg->shift; writel(val, base_addr + REG_CLOCK_CONFIG_CR); return clk_get_rate(hw); } #define CLK_CFG(_id, _name, _shift, _width, _table, _flags) { \ .cfg.id = _id, \ .cfg.name = _name, \ .cfg.shift = _shift, \ .cfg.width = _width, \ .cfg.table = _table, \ .cfg.flags = _flags, \ } static struct mpfs_cfg_hw_clock mpfs_cfg_clks[] = { CLK_CFG(CLK_CPU, "clk_cpu", 0, 2, mpfs_div_cpu_axi_table, 0), CLK_CFG(CLK_AXI, "clk_axi", 2, 2, mpfs_div_cpu_axi_table, 0), CLK_CFG(CLK_AHB, "clk_ahb", 4, 2, mpfs_div_ahb_table, 0), }; int mpfs_clk_register_cfgs(void __iomem *base, struct clk *parent) { int ret; int i, id, num_clks; const char *name; struct clk *hw; num_clks = ARRAY_SIZE(mpfs_cfg_clks); for (i = 0; i < num_clks; i++) { hw = &mpfs_cfg_clks[i].hw; mpfs_cfg_clks[i].sys_base = base; mpfs_cfg_clks[i].prate = clk_get_rate(parent); name = mpfs_cfg_clks[i].cfg.name; ret = clk_register(hw, MPFS_CFG_CLOCK, name, parent->dev->name); if (ret) ERR_PTR(ret); id = mpfs_cfg_clks[i].cfg.id; clk_dm(id, hw); } return 0; } const struct clk_ops mpfs_cfg_clk_ops = { .set_rate = mpfs_cfg_clk_set_rate, .get_rate = mpfs_cfg_clk_recalc_rate, }; U_BOOT_DRIVER(mpfs_cfg_clock) = { .name = MPFS_CFG_CLOCK, .id = UCLASS_CLK, .ops = &mpfs_cfg_clk_ops, };