diff options
Diffstat (limited to '')
-rw-r--r-- | drivers/clk/imx/clk-divider-gate.c | 221 |
1 files changed, 221 insertions, 0 deletions
diff --git a/drivers/clk/imx/clk-divider-gate.c b/drivers/clk/imx/clk-divider-gate.c new file mode 100644 index 000000000..0322a843d --- /dev/null +++ b/drivers/clk/imx/clk-divider-gate.c @@ -0,0 +1,221 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2018 NXP. + * Dong Aisheng <aisheng.dong@nxp.com> + */ + +#include <linux/clk-provider.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/slab.h> + +#include "clk.h" + +struct clk_divider_gate { + struct clk_divider divider; + u32 cached_val; +}; + +static inline struct clk_divider_gate *to_clk_divider_gate(struct clk_hw *hw) +{ + struct clk_divider *div = to_clk_divider(hw); + + return container_of(div, struct clk_divider_gate, divider); +} + +static unsigned long clk_divider_gate_recalc_rate_ro(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_divider *div = to_clk_divider(hw); + unsigned int val; + + val = readl(div->reg) >> div->shift; + val &= clk_div_mask(div->width); + if (!val) + return 0; + + return divider_recalc_rate(hw, parent_rate, val, div->table, + div->flags, div->width); +} + +static unsigned long clk_divider_gate_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_divider_gate *div_gate = to_clk_divider_gate(hw); + struct clk_divider *div = to_clk_divider(hw); + unsigned long flags; + unsigned int val; + + spin_lock_irqsave(div->lock, flags); + + if (!clk_hw_is_enabled(hw)) { + val = div_gate->cached_val; + } else { + val = readl(div->reg) >> div->shift; + val &= clk_div_mask(div->width); + } + + spin_unlock_irqrestore(div->lock, flags); + + if (!val) + return 0; + + return divider_recalc_rate(hw, parent_rate, val, div->table, + div->flags, div->width); +} + +static long clk_divider_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *prate) +{ + return clk_divider_ops.round_rate(hw, rate, prate); +} + +static int clk_divider_gate_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct clk_divider_gate *div_gate = to_clk_divider_gate(hw); + struct clk_divider *div = to_clk_divider(hw); + unsigned long flags; + int value; + u32 val; + + value = divider_get_val(rate, parent_rate, div->table, + div->width, div->flags); + if (value < 0) + return value; + + spin_lock_irqsave(div->lock, flags); + + if (clk_hw_is_enabled(hw)) { + val = readl(div->reg); + val &= ~(clk_div_mask(div->width) << div->shift); + val |= (u32)value << div->shift; + writel(val, div->reg); + } else { + div_gate->cached_val = value; + } + + spin_unlock_irqrestore(div->lock, flags); + + return 0; +} + +static int clk_divider_enable(struct clk_hw *hw) +{ + struct clk_divider_gate *div_gate = to_clk_divider_gate(hw); + struct clk_divider *div = to_clk_divider(hw); + unsigned long flags; + u32 val; + + if (!div_gate->cached_val) { + pr_err("%s: no valid preset rate\n", clk_hw_get_name(hw)); + return -EINVAL; + } + + spin_lock_irqsave(div->lock, flags); + /* restore div val */ + val = readl(div->reg); + val |= div_gate->cached_val << div->shift; + writel(val, div->reg); + + spin_unlock_irqrestore(div->lock, flags); + + return 0; +} + +static void clk_divider_disable(struct clk_hw *hw) +{ + struct clk_divider_gate *div_gate = to_clk_divider_gate(hw); + struct clk_divider *div = to_clk_divider(hw); + unsigned long flags; + u32 val; + + spin_lock_irqsave(div->lock, flags); + + /* store the current div val */ + val = readl(div->reg) >> div->shift; + val &= clk_div_mask(div->width); + div_gate->cached_val = val; + writel(0, div->reg); + + spin_unlock_irqrestore(div->lock, flags); +} + +static int clk_divider_is_enabled(struct clk_hw *hw) +{ + struct clk_divider *div = to_clk_divider(hw); + u32 val; + + val = readl(div->reg) >> div->shift; + val &= clk_div_mask(div->width); + + return val ? 1 : 0; +} + +static const struct clk_ops clk_divider_gate_ro_ops = { + .recalc_rate = clk_divider_gate_recalc_rate_ro, + .round_rate = clk_divider_round_rate, +}; + +static const struct clk_ops clk_divider_gate_ops = { + .recalc_rate = clk_divider_gate_recalc_rate, + .round_rate = clk_divider_round_rate, + .set_rate = clk_divider_gate_set_rate, + .enable = clk_divider_enable, + .disable = clk_divider_disable, + .is_enabled = clk_divider_is_enabled, +}; + +/* + * NOTE: In order to reuse the most code from the common divider, + * we also design our divider following the way that provids an extra + * clk_divider_flags, however it's fixed to CLK_DIVIDER_ONE_BASED by + * default as our HW is. Besides that it supports only CLK_DIVIDER_READ_ONLY + * flag which can be specified by user flexibly. + */ +struct clk_hw *imx_clk_hw_divider_gate(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, + spinlock_t *lock) +{ + struct clk_init_data init; + struct clk_divider_gate *div_gate; + struct clk_hw *hw; + u32 val; + int ret; + + div_gate = kzalloc(sizeof(*div_gate), GFP_KERNEL); + if (!div_gate) + return ERR_PTR(-ENOMEM); + + init.name = name; + if (clk_divider_flags & CLK_DIVIDER_READ_ONLY) + init.ops = &clk_divider_gate_ro_ops; + else + init.ops = &clk_divider_gate_ops; + init.flags = flags; + init.parent_names = parent_name ? &parent_name : NULL; + init.num_parents = parent_name ? 1 : 0; + + div_gate->divider.reg = reg; + div_gate->divider.shift = shift; + div_gate->divider.width = width; + div_gate->divider.lock = lock; + div_gate->divider.table = table; + div_gate->divider.hw.init = &init; + div_gate->divider.flags = CLK_DIVIDER_ONE_BASED | clk_divider_flags; + /* cache gate status */ + val = readl(reg) >> shift; + val &= clk_div_mask(width); + div_gate->cached_val = val; + + hw = &div_gate->divider.hw; + ret = clk_hw_register(NULL, hw); + if (ret) { + kfree(div_gate); + hw = ERR_PTR(ret); + } + + return hw; +} |