1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * Copyright (C) 2016 Maxime Ripard
4 * Maxime Ripard <maxime.ripard@free-electrons.com>
7 #include <linux/clk-provider.h>
9 #include <linux/spinlock.h>
11 #include "ccu_phase.h"
13 static int ccu_phase_get_phase(struct clk_hw
*hw
)
15 struct ccu_phase
*phase
= hw_to_ccu_phase(hw
);
16 struct clk_hw
*parent
, *grandparent
;
17 unsigned int parent_rate
, grandparent_rate
;
22 reg
= readl(phase
->common
.base
+ phase
->common
.reg
);
23 delay
= (reg
>> phase
->shift
);
24 delay
&= (1 << phase
->width
) - 1;
29 /* Get our parent clock, it's the one that can adjust its rate */
30 parent
= clk_hw_get_parent(hw
);
35 parent_rate
= clk_hw_get_rate(parent
);
39 /* Now, get our parent's parent (most likely some PLL) */
40 grandparent
= clk_hw_get_parent(parent
);
45 grandparent_rate
= clk_hw_get_rate(grandparent
);
46 if (!grandparent_rate
)
49 /* Get our parent clock divider */
50 parent_div
= grandparent_rate
/ parent_rate
;
52 step
= DIV_ROUND_CLOSEST(360, parent_div
);
56 static int ccu_phase_set_phase(struct clk_hw
*hw
, int degrees
)
58 struct ccu_phase
*phase
= hw_to_ccu_phase(hw
);
59 struct clk_hw
*parent
, *grandparent
;
60 unsigned int parent_rate
, grandparent_rate
;
65 /* Get our parent clock, it's the one that can adjust its rate */
66 parent
= clk_hw_get_parent(hw
);
71 parent_rate
= clk_hw_get_rate(parent
);
75 /* Now, get our parent's parent (most likely some PLL) */
76 grandparent
= clk_hw_get_parent(parent
);
81 grandparent_rate
= clk_hw_get_rate(grandparent
);
82 if (!grandparent_rate
)
88 /* Get our parent divider */
89 parent_div
= grandparent_rate
/ parent_rate
;
92 * We can only outphase the clocks by multiple of the
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
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.
104 step
= DIV_ROUND_CLOSEST(360, parent_div
);
105 delay
= DIV_ROUND_CLOSEST(degrees
, step
);
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
);
120 const struct clk_ops ccu_phase_ops
= {
121 .get_phase
= ccu_phase_get_phase
,
122 .set_phase
= ccu_phase_set_phase
,
124 EXPORT_SYMBOL_NS_GPL(ccu_phase_ops
, "SUNXI_CCU");