ccu_phase.c (3148B)
1// SPDX-License-Identifier: GPL-2.0-or-later 2/* 3 * Copyright (C) 2016 Maxime Ripard 4 * 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 the 93 * PLL's period. 94 * 95 * Since our parent clock is only a divider, and the 96 * formula to get the outphasing in degrees is deg = 97 * 360 * delta / period 98 * 99 * If we simplify this formula, we can see that the 100 * only thing that we're concerned about is the number 101 * of period we want to outphase our clock from, and 102 * 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);