258 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Copyright 2016 Maxime Ripard4 *5 * Maxime Ripard <maxime.ripard@free-electrons.com>6 */7 8#include <linux/clk.h>9#include <linux/clk-provider.h>10#include <linux/device.h>11#include <linux/iopoll.h>12#include <linux/module.h>13#include <linux/slab.h>14 15#include "ccu_common.h"16#include "ccu_gate.h"17#include "ccu_reset.h"18 19struct sunxi_ccu {20 const struct sunxi_ccu_desc *desc;21 spinlock_t lock;22 struct ccu_reset reset;23};24 25void ccu_helper_wait_for_lock(struct ccu_common *common, u32 lock)26{27 void __iomem *addr;28 u32 reg;29 30 if (!lock)31 return;32 33 if (common->features & CCU_FEATURE_LOCK_REG)34 addr = common->base + common->lock_reg;35 else36 addr = common->base + common->reg;37 38 WARN_ON(readl_relaxed_poll_timeout(addr, reg, reg & lock, 100, 70000));39}40EXPORT_SYMBOL_NS_GPL(ccu_helper_wait_for_lock, SUNXI_CCU);41 42bool ccu_is_better_rate(struct ccu_common *common,43 unsigned long target_rate,44 unsigned long current_rate,45 unsigned long best_rate)46{47 unsigned long min_rate, max_rate;48 49 clk_hw_get_rate_range(&common->hw, &min_rate, &max_rate);50 51 if (current_rate > max_rate)52 return false;53 54 if (current_rate < min_rate)55 return false;56 57 if (common->features & CCU_FEATURE_CLOSEST_RATE)58 return abs(current_rate - target_rate) < abs(best_rate - target_rate);59 60 return current_rate <= target_rate && current_rate > best_rate;61}62EXPORT_SYMBOL_NS_GPL(ccu_is_better_rate, SUNXI_CCU);63 64/*65 * This clock notifier is called when the frequency of a PLL clock is66 * changed. In common PLL designs, changes to the dividers take effect67 * almost immediately, while changes to the multipliers (implemented68 * as dividers in the feedback loop) take a few cycles to work into69 * the feedback loop for the PLL to stablize.70 *71 * Sometimes when the PLL clock rate is changed, the decrease in the72 * divider is too much for the decrease in the multiplier to catch up.73 * The PLL clock rate will spike, and in some cases, might lock up74 * completely.75 *76 * This notifier callback will gate and then ungate the clock,77 * effectively resetting it, so it proceeds to work. Care must be78 * taken to reparent consumers to other temporary clocks during the79 * rate change, and that this notifier callback must be the first80 * to be registered.81 */82static int ccu_pll_notifier_cb(struct notifier_block *nb,83 unsigned long event, void *data)84{85 struct ccu_pll_nb *pll = to_ccu_pll_nb(nb);86 int ret = 0;87 88 if (event != POST_RATE_CHANGE)89 goto out;90 91 ccu_gate_helper_disable(pll->common, pll->enable);92 93 ret = ccu_gate_helper_enable(pll->common, pll->enable);94 if (ret)95 goto out;96 97 ccu_helper_wait_for_lock(pll->common, pll->lock);98 99out:100 return notifier_from_errno(ret);101}102 103int ccu_pll_notifier_register(struct ccu_pll_nb *pll_nb)104{105 pll_nb->clk_nb.notifier_call = ccu_pll_notifier_cb;106 107 return clk_notifier_register(pll_nb->common->hw.clk,108 &pll_nb->clk_nb);109}110EXPORT_SYMBOL_NS_GPL(ccu_pll_notifier_register, SUNXI_CCU);111 112static int sunxi_ccu_probe(struct sunxi_ccu *ccu, struct device *dev,113 struct device_node *node, void __iomem *reg,114 const struct sunxi_ccu_desc *desc)115{116 struct ccu_reset *reset;117 int i, ret;118 119 ccu->desc = desc;120 121 spin_lock_init(&ccu->lock);122 123 for (i = 0; i < desc->num_ccu_clks; i++) {124 struct ccu_common *cclk = desc->ccu_clks[i];125 126 if (!cclk)127 continue;128 129 cclk->base = reg;130 cclk->lock = &ccu->lock;131 }132 133 for (i = 0; i < desc->hw_clks->num ; i++) {134 struct clk_hw *hw = desc->hw_clks->hws[i];135 const char *name;136 137 if (!hw)138 continue;139 140 name = hw->init->name;141 if (dev)142 ret = clk_hw_register(dev, hw);143 else144 ret = of_clk_hw_register(node, hw);145 if (ret) {146 pr_err("Couldn't register clock %d - %s\n", i, name);147 goto err_clk_unreg;148 }149 }150 151 for (i = 0; i < desc->num_ccu_clks; i++) {152 struct ccu_common *cclk = desc->ccu_clks[i];153 154 if (!cclk)155 continue;156 157 if (cclk->max_rate)158 clk_hw_set_rate_range(&cclk->hw, cclk->min_rate,159 cclk->max_rate);160 else161 WARN(cclk->min_rate,162 "No max_rate, ignoring min_rate of clock %d - %s\n",163 i, clk_hw_get_name(&cclk->hw));164 }165 166 ret = of_clk_add_hw_provider(node, of_clk_hw_onecell_get,167 desc->hw_clks);168 if (ret)169 goto err_clk_unreg;170 171 reset = &ccu->reset;172 reset->rcdev.of_node = node;173 reset->rcdev.ops = &ccu_reset_ops;174 reset->rcdev.owner = dev ? dev->driver->owner : THIS_MODULE;175 reset->rcdev.nr_resets = desc->num_resets;176 reset->base = reg;177 reset->lock = &ccu->lock;178 reset->reset_map = desc->resets;179 180 ret = reset_controller_register(&reset->rcdev);181 if (ret)182 goto err_del_provider;183 184 return 0;185 186err_del_provider:187 of_clk_del_provider(node);188err_clk_unreg:189 while (--i >= 0) {190 struct clk_hw *hw = desc->hw_clks->hws[i];191 192 if (!hw)193 continue;194 clk_hw_unregister(hw);195 }196 return ret;197}198 199static void devm_sunxi_ccu_release(struct device *dev, void *res)200{201 struct sunxi_ccu *ccu = res;202 const struct sunxi_ccu_desc *desc = ccu->desc;203 int i;204 205 reset_controller_unregister(&ccu->reset.rcdev);206 of_clk_del_provider(dev->of_node);207 208 for (i = 0; i < desc->hw_clks->num; i++) {209 struct clk_hw *hw = desc->hw_clks->hws[i];210 211 if (!hw)212 continue;213 clk_hw_unregister(hw);214 }215}216 217int devm_sunxi_ccu_probe(struct device *dev, void __iomem *reg,218 const struct sunxi_ccu_desc *desc)219{220 struct sunxi_ccu *ccu;221 int ret;222 223 ccu = devres_alloc(devm_sunxi_ccu_release, sizeof(*ccu), GFP_KERNEL);224 if (!ccu)225 return -ENOMEM;226 227 ret = sunxi_ccu_probe(ccu, dev, dev->of_node, reg, desc);228 if (ret) {229 devres_free(ccu);230 return ret;231 }232 233 devres_add(dev, ccu);234 235 return 0;236}237EXPORT_SYMBOL_NS_GPL(devm_sunxi_ccu_probe, SUNXI_CCU);238 239void of_sunxi_ccu_probe(struct device_node *node, void __iomem *reg,240 const struct sunxi_ccu_desc *desc)241{242 struct sunxi_ccu *ccu;243 int ret;244 245 ccu = kzalloc(sizeof(*ccu), GFP_KERNEL);246 if (!ccu)247 return;248 249 ret = sunxi_ccu_probe(ccu, NULL, node, reg, desc);250 if (ret) {251 pr_err("%pOF: probing clocks failed: %d\n", node, ret);252 kfree(ccu);253 }254}255 256MODULE_DESCRIPTION("Common clock support for Allwinner SoCs");257MODULE_LICENSE("GPL");258