| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (C) 2019 DENX Software Engineering |
| * Lukasz Majewski, DENX Software Engineering, lukma@denx.de |
| * |
| * Copyright (C) 2011 Sascha Hauer, Pengutronix <s.hauer@pengutronix.de> |
| * Copyright (C) 2011 Richard Zhao, Linaro <richard.zhao@linaro.org> |
| * Copyright (C) 2011-2012 Mike Turquette, Linaro Ltd <mturquette@linaro.org> |
| * |
| */ |
| |
| #define LOG_CATEGORY UCLASS_CLK |
| |
| #include <common.h> |
| #include <asm/io.h> |
| #include <malloc.h> |
| #include <clk-uclass.h> |
| #include <log.h> |
| #include <dm/device.h> |
| #include <dm/devres.h> |
| #include <dm/uclass.h> |
| #include <dm/lists.h> |
| #include <dm/device_compat.h> |
| #include <dm/device-internal.h> |
| #include <linux/bug.h> |
| #include <linux/clk-provider.h> |
| #include <linux/err.h> |
| #include <linux/log2.h> |
| #include <div64.h> |
| #include <clk.h> |
| #include "clk.h" |
| |
| #define UBOOT_DM_CLK_CCF_DIVIDER "ccf_clk_divider" |
| |
| unsigned int clk_divider_get_table_div(const struct clk_div_table *table, |
| unsigned int val) |
| { |
| const struct clk_div_table *clkt; |
| |
| for (clkt = table; clkt->div; clkt++) |
| if (clkt->val == val) |
| return clkt->div; |
| return 0; |
| } |
| |
| static unsigned int _get_div(const struct clk_div_table *table, |
| unsigned int val, unsigned long flags, u8 width) |
| { |
| if (flags & CLK_DIVIDER_ONE_BASED) |
| return val; |
| if (flags & CLK_DIVIDER_POWER_OF_TWO) |
| return 1 << val; |
| if (flags & CLK_DIVIDER_MAX_AT_ZERO) |
| return val ? val : clk_div_mask(width) + 1; |
| if (table) |
| return clk_divider_get_table_div(table, val); |
| return val + 1; |
| } |
| |
| unsigned long divider_recalc_rate(struct clk *hw, unsigned long parent_rate, |
| unsigned int val, |
| const struct clk_div_table *table, |
| unsigned long flags, unsigned long width) |
| { |
| unsigned int div; |
| |
| div = _get_div(table, val, flags, width); |
| if (!div) { |
| WARN(!(flags & CLK_DIVIDER_ALLOW_ZERO), |
| "%s: Zero divisor and CLK_DIVIDER_ALLOW_ZERO not set\n", |
| clk_hw_get_name(hw)); |
| return parent_rate; |
| } |
| |
| return DIV_ROUND_UP_ULL((u64)parent_rate, div); |
| } |
| |
| static ulong clk_divider_recalc_rate(struct clk *clk) |
| { |
| struct clk_divider *divider = to_clk_divider(clk); |
| unsigned long parent_rate = clk_get_parent_rate(clk); |
| unsigned int val; |
| |
| #if IS_ENABLED(CONFIG_SANDBOX_CLK_CCF) |
| val = divider->io_divider_val; |
| #else |
| val = readl(divider->reg); |
| #endif |
| val >>= divider->shift; |
| val &= clk_div_mask(divider->width); |
| |
| return divider_recalc_rate(clk, parent_rate, val, divider->table, |
| divider->flags, divider->width); |
| } |
| |
| bool clk_divider_is_valid_table_div(const struct clk_div_table *table, |
| unsigned int div) |
| { |
| const struct clk_div_table *clkt; |
| |
| for (clkt = table; clkt->div; clkt++) |
| if (clkt->div == div) |
| return true; |
| return false; |
| } |
| |
| bool clk_divider_is_valid_div(const struct clk_div_table *table, |
| unsigned int div, unsigned long flags) |
| { |
| if (flags & CLK_DIVIDER_POWER_OF_TWO) |
| return is_power_of_2(div); |
| if (table) |
| return clk_divider_is_valid_table_div(table, div); |
| return true; |
| } |
| |
| unsigned int clk_divider_get_table_val(const struct clk_div_table *table, |
| unsigned int div) |
| { |
| const struct clk_div_table *clkt; |
| |
| for (clkt = table; clkt->div; clkt++) |
| if (clkt->div == div) |
| return clkt->val; |
| return 0; |
| } |
| |
| static unsigned int _get_val(const struct clk_div_table *table, |
| unsigned int div, unsigned long flags, u8 width) |
| { |
| if (flags & CLK_DIVIDER_ONE_BASED) |
| return div; |
| if (flags & CLK_DIVIDER_POWER_OF_TWO) |
| return __ffs(div); |
| if (flags & CLK_DIVIDER_MAX_AT_ZERO) |
| return (div == clk_div_mask(width) + 1) ? 0 : div; |
| if (table) |
| return clk_divider_get_table_val(table, div); |
| return div - 1; |
| } |
| int divider_get_val(unsigned long rate, unsigned long parent_rate, |
| const struct clk_div_table *table, u8 width, |
| unsigned long flags) |
| { |
| unsigned int div, value; |
| |
| div = DIV_ROUND_UP_ULL((u64)parent_rate, rate); |
| |
| if (!clk_divider_is_valid_div(table, div, flags)) |
| return -EINVAL; |
| |
| value = _get_val(table, div, flags, width); |
| |
| return min_t(unsigned int, value, clk_div_mask(width)); |
| } |
| |
| static ulong clk_divider_set_rate(struct clk *clk, unsigned long rate) |
| { |
| struct clk_divider *divider = to_clk_divider(clk); |
| unsigned long parent_rate = clk_get_parent_rate(clk); |
| int value; |
| u32 val; |
| |
| value = divider_get_val(rate, parent_rate, divider->table, |
| divider->width, divider->flags); |
| if (value < 0) |
| return value; |
| |
| if (divider->flags & CLK_DIVIDER_HIWORD_MASK) { |
| val = clk_div_mask(divider->width) << (divider->shift + 16); |
| } else { |
| val = readl(divider->reg); |
| val &= ~(clk_div_mask(divider->width) << divider->shift); |
| } |
| val |= (u32)value << divider->shift; |
| writel(val, divider->reg); |
| |
| return clk_get_rate(clk); |
| } |
| |
| const struct clk_ops clk_divider_ops = { |
| .get_rate = clk_divider_recalc_rate, |
| .set_rate = clk_divider_set_rate, |
| }; |
| |
| static struct clk *_register_divider(struct device *dev, const char *name, |
| const char *parent_name, unsigned long flags, |
| void __iomem *reg, u8 shift, u8 width, |
| u8 clk_divider_flags, const struct clk_div_table *table) |
| { |
| struct clk_divider *div; |
| struct clk *clk; |
| int ret; |
| |
| if (clk_divider_flags & CLK_DIVIDER_HIWORD_MASK) { |
| if (width + shift > 16) { |
| dev_warn(dev, "divider value exceeds LOWORD field\n"); |
| return ERR_PTR(-EINVAL); |
| } |
| } |
| |
| /* allocate the divider */ |
| div = kzalloc(sizeof(*div), GFP_KERNEL); |
| if (!div) |
| return ERR_PTR(-ENOMEM); |
| |
| /* struct clk_divider assignments */ |
| div->reg = reg; |
| div->shift = shift; |
| div->width = width; |
| div->flags = clk_divider_flags; |
| div->table = table; |
| #if IS_ENABLED(CONFIG_SANDBOX_CLK_CCF) |
| div->io_divider_val = *(u32 *)reg; |
| #endif |
| |
| /* register the clock */ |
| clk = &div->clk; |
| clk->flags = flags; |
| |
| ret = clk_register(clk, UBOOT_DM_CLK_CCF_DIVIDER, name, parent_name); |
| if (ret) { |
| kfree(div); |
| return ERR_PTR(ret); |
| } |
| |
| return clk; |
| } |
| |
| struct clk *clk_register_divider(struct device *dev, const char *name, |
| const char *parent_name, unsigned long flags, |
| void __iomem *reg, u8 shift, u8 width, |
| u8 clk_divider_flags) |
| { |
| struct clk *clk; |
| |
| clk = _register_divider(dev, name, parent_name, flags, reg, shift, |
| width, clk_divider_flags, NULL); |
| if (IS_ERR(clk)) |
| return ERR_CAST(clk); |
| return clk; |
| } |
| |
| U_BOOT_DRIVER(ccf_clk_divider) = { |
| .name = UBOOT_DM_CLK_CCF_DIVIDER, |
| .id = UCLASS_CLK, |
| .ops = &clk_divider_ops, |
| .flags = DM_FLAG_PRE_RELOC, |
| }; |