aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/clk/sunxi-ng/ccu_phase.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2016-07-30 14:20:02 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2016-07-30 14:20:02 -0400
commit1056c9bd2702ea1bb79abf9bd1e78c578589d247 (patch)
treefaada7d658151c059a845cdb9d9d521817d1e611 /drivers/clk/sunxi-ng/ccu_phase.c
parent797cee982eef9195736afc5e7f3b8f613c41d19a (diff)
parentd22527fed2f094c2e4f9a66f35b68a090c3d906a (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.c126
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
16static 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
59static 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
123const struct clk_ops ccu_phase_ops = {
124 .get_phase = ccu_phase_get_phase,
125 .set_phase = ccu_phase_set_phase,
126};