diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2016-07-30 14:20:02 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2016-07-30 14:20:02 -0400 |
commit | 1056c9bd2702ea1bb79abf9bd1e78c578589d247 (patch) | |
tree | faada7d658151c059a845cdb9d9d521817d1e611 /drivers/clk/sunxi-ng/ccu_phase.c | |
parent | 797cee982eef9195736afc5e7f3b8f613c41d19a (diff) | |
parent | d22527fed2f094c2e4f9a66f35b68a090c3d906a (diff) |
Merge tag 'clk-for-linus-4.8' of git://git.kernel.org/pub/scm/linux/kernel/git/clk/linux
Pull clk updates from Michael Turquette:
"The bulk of the changes are updates and fixes to existing clk provider
drivers, along with a pretty standard number of new drivers. The core
recieved a small number of updates as well.
Core changes of note:
- removed CLK_IS_ROOT flag
New clk provider drivers:
- Renesas r8a7796 clock pulse generator / module standby and
software reset
- Allwinner sun8i H3 clock controller unit
- AmLogic meson8b clock controller (rewritten)
- AmLogic gxbb clock controller
- support for some new ICs was added by simple changes to static
data tables for chips sharing the same family
Driver updates of note:
- the Allwinner sunxi clock driver infrastucture was rewritten to
comform to the state of the art at drivers/clk/sunxi-ng. The old
implementation is still supported for backwards compatibility with
the DT ABI"
* tag 'clk-for-linus-4.8' of git://git.kernel.org/pub/scm/linux/kernel/git/clk/linux: (162 commits)
clk: Makefile: re-sort and clean up
Revert "clk: gxbb: expose CLKID_MMC_PCLK"
clk: samsung: Allow modular build of the Audio Subsystem CLKCON driver
clk: samsung: make clk-s5pv210-audss explicitly non-modular
clk: exynos5433: remove CLK_IGNORE_UNUSED flag from SPI clocks
clk: oxnas: Add hardware dependencies
clk: imx7d: do not set parent of ethernet time/ref clocks
ARM: dt: sun8i: switch the H3 to the new CCU driver
clk: sunxi-ng: h3: Fix Kconfig symbol typo
clk: sunxi-ng: h3: Fix audio clock divider offset
clk: sunxi-ng: Add H3 clocks
clk: sunxi-ng: Add N-K-M-P factor clock
clk: sunxi-ng: Add N-K-M Factor clock
clk: sunxi-ng: Add N-M-factor clock support
clk: sunxi-ng: Add N-K-factor clock support
clk: sunxi-ng: Add M-P factor clock support
clk: sunxi-ng: Add divider
clk: sunxi-ng: Add phase clock support
clk: sunxi-ng: Add mux clock support
clk: sunxi-ng: Add gate clock support
...
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 | }; | ||