xref: /openbmc/linux/drivers/clk/sunxi-ng/ccu_phase.c (revision 20e8ef5c)
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 
13 static 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 
56 static 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 
120 const struct clk_ops ccu_phase_ops = {
121 	.get_phase	= ccu_phase_get_phase,
122 	.set_phase	= ccu_phase_set_phase,
123 };
124 EXPORT_SYMBOL_NS_GPL(ccu_phase_ops, SUNXI_CCU);
125