1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
|
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2016 Maxime Ripard
* Maxime Ripard <[email protected]>
*/
#include "ccu.h"
#include "ccu_phase.h"
static int ccu_phase_get_phase(struct clk_hw *hw)
{
struct ccu_phase *phase = hw_to_ccu_phase(hw);
struct clk_hw *parent, *grandparent;
unsigned int parent_rate, grandparent_rate;
u16 step, parent_div;
u32 reg;
u8 delay;
reg = readl(phase->common.base + phase->common.reg);
delay = (reg >> phase->shift);
delay &= (1 << phase->width) - 1;
if (!delay)
{
return 180;
}
/* Get our parent clock, it's the one that can adjust its rate */
parent = clk_hw_get_parent(hw);
if (!parent)
{
return -EINVAL;
}
/* And its rate */
parent_rate = clk_hw_get_rate(parent);
if (!parent_rate)
{
return -EINVAL;
}
/* Now, get our parent's parent (most likely some PLL) */
grandparent = clk_hw_get_parent(parent);
if (!grandparent)
{
return -EINVAL;
}
/* And its rate */
grandparent_rate = clk_hw_get_rate(grandparent);
if (!grandparent_rate)
{
return -EINVAL;
}
/* Get our parent clock divider */
parent_div = grandparent_rate / parent_rate;
step = DIV_ROUND_CLOSEST(360, parent_div);
return delay * step;
}
static int ccu_phase_set_phase(struct clk_hw *hw, int degrees)
{
struct ccu_phase *phase = hw_to_ccu_phase(hw);
struct clk_hw *parent, *grandparent;
unsigned int parent_rate, grandparent_rate;
unsigned long flags;
u32 reg;
u8 delay;
u32 __cspr;
/* Get our parent clock, it's the one that can adjust its rate */
parent = clk_hw_get_parent(hw);
if (!parent)
{
return -EINVAL;
}
/* And its rate */
parent_rate = clk_hw_get_rate(parent);
if (!parent_rate)
{
return -EINVAL;
}
/* Now, get our parent's parent (most likely some PLL) */
grandparent = clk_hw_get_parent(parent);
if (!grandparent)
{
return -EINVAL;
}
/* And its rate */
grandparent_rate = clk_hw_get_rate(grandparent);
if (!grandparent_rate)
{
return -EINVAL;
}
if (degrees != 180)
{
u16 step, parent_div;
/* Get our parent divider */
parent_div = grandparent_rate / parent_rate;
/*
* We can only outphase the clocks by multiple of the
* PLL's period.
*
* Since our parent clock is only a divider, and the
* formula to get the outphasing in degrees is deg =
* 360 * delta / period
*
* If we simplify this formula, we can see that the
* only thing that we're concerned about is the number
* of period we want to outphase our clock from, and
* the divider set by our parent clock.
*/
step = DIV_ROUND_CLOSEST(360, parent_div);
delay = DIV_ROUND_CLOSEST(degrees, step);
}
else
{
delay = 0;
}
__cspr = hal_spin_lock_irqsave(&phase->common.lock);
reg = readl(phase->common.base + phase->common.reg);
reg &= ~GENMASK(phase->width + phase->shift - 1, phase->shift);
writel(reg | (delay << phase->shift),
phase->common.base + phase->common.reg);
hal_spin_unlock_irqrestore(&phase->common.lock, __cspr);
return 0;
}
const struct clk_ops ccu_phase_ops =
{
.get_phase = ccu_phase_get_phase,
.set_phase = ccu_phase_set_phase,
};
|