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