125 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Copyright (C) 2016 Maxime Ripard4 * Maxime Ripard <maxime.ripard@free-electrons.com>5 */6 7#include <linux/clk-provider.h>8#include <linux/io.h>9#include <linux/spinlock.h>10 11#include "ccu_phase.h"12 13static int ccu_phase_get_phase(struct clk_hw *hw)14{15 struct ccu_phase *phase = hw_to_ccu_phase(hw);16 struct clk_hw *parent, *grandparent;17 unsigned int parent_rate, grandparent_rate;18 u16 step, parent_div;19 u32 reg;20 u8 delay;21 22 reg = readl(phase->common.base + phase->common.reg);23 delay = (reg >> phase->shift);24 delay &= (1 << phase->width) - 1;25 26 if (!delay)27 return 180;28 29 /* Get our parent clock, it's the one that can adjust its rate */30 parent = clk_hw_get_parent(hw);31 if (!parent)32 return -EINVAL;33 34 /* And its rate */35 parent_rate = clk_hw_get_rate(parent);36 if (!parent_rate)37 return -EINVAL;38 39 /* Now, get our parent's parent (most likely some PLL) */40 grandparent = clk_hw_get_parent(parent);41 if (!grandparent)42 return -EINVAL;43 44 /* And its rate */45 grandparent_rate = clk_hw_get_rate(grandparent);46 if (!grandparent_rate)47 return -EINVAL;48 49 /* Get our parent clock divider */50 parent_div = grandparent_rate / parent_rate;51 52 step = DIV_ROUND_CLOSEST(360, parent_div);53 return delay * step;54}55 56static int ccu_phase_set_phase(struct clk_hw *hw, int degrees)57{58 struct ccu_phase *phase = hw_to_ccu_phase(hw);59 struct clk_hw *parent, *grandparent;60 unsigned int parent_rate, grandparent_rate;61 unsigned long flags;62 u32 reg;63 u8 delay;64 65 /* Get our parent clock, it's the one that can adjust its rate */66 parent = clk_hw_get_parent(hw);67 if (!parent)68 return -EINVAL;69 70 /* And its rate */71 parent_rate = clk_hw_get_rate(parent);72 if (!parent_rate)73 return -EINVAL;74 75 /* Now, get our parent's parent (most likely some PLL) */76 grandparent = clk_hw_get_parent(parent);77 if (!grandparent)78 return -EINVAL;79 80 /* And its rate */81 grandparent_rate = clk_hw_get_rate(grandparent);82 if (!grandparent_rate)83 return -EINVAL;84 85 if (degrees != 180) {86 u16 step, parent_div;87 88 /* Get our parent divider */89 parent_div = grandparent_rate / parent_rate;90 91 /*92 * We can only outphase the clocks by multiple of the93 * PLL's period.94 *95 * Since our parent clock is only a divider, and the96 * formula to get the outphasing in degrees is deg =97 * 360 * delta / period98 *99 * If we simplify this formula, we can see that the100 * only thing that we're concerned about is the number101 * of period we want to outphase our clock from, and102 * the divider set by our parent clock.103 */104 step = DIV_ROUND_CLOSEST(360, parent_div);105 delay = DIV_ROUND_CLOSEST(degrees, step);106 } else {107 delay = 0;108 }109 110 spin_lock_irqsave(phase->common.lock, flags);111 reg = readl(phase->common.base + phase->common.reg);112 reg &= ~GENMASK(phase->width + phase->shift - 1, phase->shift);113 writel(reg | (delay << phase->shift),114 phase->common.base + phase->common.reg);115 spin_unlock_irqrestore(phase->common.lock, flags);116 117 return 0;118}119 120const struct clk_ops ccu_phase_ops = {121 .get_phase = ccu_phase_get_phase,122 .set_phase = ccu_phase_set_phase,123};124EXPORT_SYMBOL_NS_GPL(ccu_phase_ops, SUNXI_CCU);125