222 lines · c
1// SPDX-License-Identifier: GPL-2.0+2/*3 * Copyright 2018 NXP.4 * Dong Aisheng <aisheng.dong@nxp.com>5 */6 7#include <linux/clk-provider.h>8#include <linux/err.h>9#include <linux/io.h>10#include <linux/slab.h>11 12#include "clk.h"13 14struct clk_divider_gate {15 struct clk_divider divider;16 u32 cached_val;17};18 19static inline struct clk_divider_gate *to_clk_divider_gate(struct clk_hw *hw)20{21 struct clk_divider *div = to_clk_divider(hw);22 23 return container_of(div, struct clk_divider_gate, divider);24}25 26static unsigned long clk_divider_gate_recalc_rate_ro(struct clk_hw *hw,27 unsigned long parent_rate)28{29 struct clk_divider *div = to_clk_divider(hw);30 unsigned int val;31 32 val = readl(div->reg) >> div->shift;33 val &= clk_div_mask(div->width);34 if (!val)35 return 0;36 37 return divider_recalc_rate(hw, parent_rate, val, div->table,38 div->flags, div->width);39}40 41static unsigned long clk_divider_gate_recalc_rate(struct clk_hw *hw,42 unsigned long parent_rate)43{44 struct clk_divider_gate *div_gate = to_clk_divider_gate(hw);45 struct clk_divider *div = to_clk_divider(hw);46 unsigned long flags;47 unsigned int val;48 49 spin_lock_irqsave(div->lock, flags);50 51 if (!clk_hw_is_enabled(hw)) {52 val = div_gate->cached_val;53 } else {54 val = readl(div->reg) >> div->shift;55 val &= clk_div_mask(div->width);56 }57 58 spin_unlock_irqrestore(div->lock, flags);59 60 if (!val)61 return 0;62 63 return divider_recalc_rate(hw, parent_rate, val, div->table,64 div->flags, div->width);65}66 67static int clk_divider_determine_rate(struct clk_hw *hw,68 struct clk_rate_request *req)69{70 return clk_divider_ops.determine_rate(hw, req);71}72 73static int clk_divider_gate_set_rate(struct clk_hw *hw, unsigned long rate,74 unsigned long parent_rate)75{76 struct clk_divider_gate *div_gate = to_clk_divider_gate(hw);77 struct clk_divider *div = to_clk_divider(hw);78 unsigned long flags;79 int value;80 u32 val;81 82 value = divider_get_val(rate, parent_rate, div->table,83 div->width, div->flags);84 if (value < 0)85 return value;86 87 spin_lock_irqsave(div->lock, flags);88 89 if (clk_hw_is_enabled(hw)) {90 val = readl(div->reg);91 val &= ~(clk_div_mask(div->width) << div->shift);92 val |= (u32)value << div->shift;93 writel(val, div->reg);94 } else {95 div_gate->cached_val = value;96 }97 98 spin_unlock_irqrestore(div->lock, flags);99 100 return 0;101}102 103static int clk_divider_enable(struct clk_hw *hw)104{105 struct clk_divider_gate *div_gate = to_clk_divider_gate(hw);106 struct clk_divider *div = to_clk_divider(hw);107 unsigned long flags;108 u32 val;109 110 if (!div_gate->cached_val) {111 pr_err("%s: no valid preset rate\n", clk_hw_get_name(hw));112 return -EINVAL;113 }114 115 spin_lock_irqsave(div->lock, flags);116 /* restore div val */117 val = readl(div->reg);118 val |= div_gate->cached_val << div->shift;119 writel(val, div->reg);120 121 spin_unlock_irqrestore(div->lock, flags);122 123 return 0;124}125 126static void clk_divider_disable(struct clk_hw *hw)127{128 struct clk_divider_gate *div_gate = to_clk_divider_gate(hw);129 struct clk_divider *div = to_clk_divider(hw);130 unsigned long flags;131 u32 val;132 133 spin_lock_irqsave(div->lock, flags);134 135 /* store the current div val */136 val = readl(div->reg) >> div->shift;137 val &= clk_div_mask(div->width);138 div_gate->cached_val = val;139 writel(0, div->reg);140 141 spin_unlock_irqrestore(div->lock, flags);142}143 144static int clk_divider_is_enabled(struct clk_hw *hw)145{146 struct clk_divider *div = to_clk_divider(hw);147 u32 val;148 149 val = readl(div->reg) >> div->shift;150 val &= clk_div_mask(div->width);151 152 return val ? 1 : 0;153}154 155static const struct clk_ops clk_divider_gate_ro_ops = {156 .recalc_rate = clk_divider_gate_recalc_rate_ro,157 .determine_rate = clk_divider_determine_rate,158};159 160static const struct clk_ops clk_divider_gate_ops = {161 .recalc_rate = clk_divider_gate_recalc_rate,162 .determine_rate = clk_divider_determine_rate,163 .set_rate = clk_divider_gate_set_rate,164 .enable = clk_divider_enable,165 .disable = clk_divider_disable,166 .is_enabled = clk_divider_is_enabled,167};168 169/*170 * NOTE: In order to reuse the most code from the common divider,171 * we also design our divider following the way that provids an extra172 * clk_divider_flags, however it's fixed to CLK_DIVIDER_ONE_BASED by173 * default as our HW is. Besides that it supports only CLK_DIVIDER_READ_ONLY174 * flag which can be specified by user flexibly.175 */176struct clk_hw *imx_clk_hw_divider_gate(const char *name, const char *parent_name,177 unsigned long flags, void __iomem *reg,178 u8 shift, u8 width, u8 clk_divider_flags,179 const struct clk_div_table *table,180 spinlock_t *lock)181{182 struct clk_init_data init;183 struct clk_divider_gate *div_gate;184 struct clk_hw *hw;185 u32 val;186 int ret;187 188 div_gate = kzalloc(sizeof(*div_gate), GFP_KERNEL);189 if (!div_gate)190 return ERR_PTR(-ENOMEM);191 192 init.name = name;193 if (clk_divider_flags & CLK_DIVIDER_READ_ONLY)194 init.ops = &clk_divider_gate_ro_ops;195 else196 init.ops = &clk_divider_gate_ops;197 init.flags = flags;198 init.parent_names = parent_name ? &parent_name : NULL;199 init.num_parents = parent_name ? 1 : 0;200 201 div_gate->divider.reg = reg;202 div_gate->divider.shift = shift;203 div_gate->divider.width = width;204 div_gate->divider.lock = lock;205 div_gate->divider.table = table;206 div_gate->divider.hw.init = &init;207 div_gate->divider.flags = CLK_DIVIDER_ONE_BASED | clk_divider_flags;208 /* cache gate status */209 val = readl(reg) >> shift;210 val &= clk_div_mask(width);211 div_gate->cached_val = val;212 213 hw = &div_gate->divider.hw;214 ret = clk_hw_register(NULL, hw);215 if (ret) {216 kfree(div_gate);217 hw = ERR_PTR(ret);218 }219 220 return hw;221}222