diff options
author | Maxime Ripard <maxime.ripard@free-electrons.com> | 2016-06-29 15:05:27 -0400 |
---|---|---|
committer | Michael Turquette <mturquette@baylibre.com> | 2016-07-08 21:04:45 -0400 |
commit | 6f9f7f876ec050ae1c352a6561616fee050dfc42 (patch) | |
tree | 6015cd133510abf78b92a86785835c09653e5687 /drivers/clk/sunxi-ng/ccu_phase.c | |
parent | 2a65ed42dca8721fb7aa397cc3c7321fbb3b7dba (diff) |
clk: sunxi-ng: Add phase clock support
Add support for the clocks in the CCU that introduce a phase shift from
their parent clock.
Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Signed-off-by: Michael Turquette <mturquette@baylibre.com>
Link: lkml.kernel.org/r/20160629190535.11855-7-maxime.ripard@free-electrons.com
Diffstat (limited to 'drivers/clk/sunxi-ng/ccu_phase.c')
-rw-r--r-- | drivers/clk/sunxi-ng/ccu_phase.c | 126 |
1 files changed, 126 insertions, 0 deletions
diff --git a/drivers/clk/sunxi-ng/ccu_phase.c b/drivers/clk/sunxi-ng/ccu_phase.c new file mode 100644 index 000000000000..400c58ad72fd --- /dev/null +++ b/drivers/clk/sunxi-ng/ccu_phase.c | |||
@@ -0,0 +1,126 @@ | |||
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 | }; | ||