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