clk-divider-gate.c (5508B)
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 extra 172 * clk_divider_flags, however it's fixed to CLK_DIVIDER_ONE_BASED by 173 * default as our HW is. Besides that it supports only CLK_DIVIDER_READ_ONLY 174 * 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 else 196 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}