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
13static 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(addr: 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(hw: 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(hw: parent);
41 if (!grandparent)
42 return -EINVAL;
43
44 /* And its rate */
45 grandparent_rate = clk_hw_get_rate(hw: 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
56static 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(hw: 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(hw: parent);
77 if (!grandparent)
78 return -EINVAL;
79
80 /* And its rate */
81 grandparent_rate = clk_hw_get_rate(hw: 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(addr: phase->common.base + phase->common.reg);
112 reg &= ~GENMASK(phase->width + phase->shift - 1, phase->shift);
113 writel(val: reg | (delay << phase->shift),
114 addr: phase->common.base + phase->common.reg);
115 spin_unlock_irqrestore(lock: phase->common.lock, flags);
116
117 return 0;
118}
119
120const struct clk_ops ccu_phase_ops = {
121 .get_phase = ccu_phase_get_phase,
122 .set_phase = ccu_phase_set_phase,
123};
124EXPORT_SYMBOL_NS_GPL(ccu_phase_ops, SUNXI_CCU);
125

source code of linux/drivers/clk/sunxi-ng/ccu_phase.c