xref: /openbmc/linux/drivers/clk/sunxi-ng/ccu_phase.c (revision e58e871b)
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