diff options
author | Maxime Ripard <maxime.ripard@free-electrons.com> | 2016-06-29 15:05:32 -0400 |
---|---|---|
committer | Michael Turquette <mturquette@baylibre.com> | 2016-07-08 21:05:03 -0400 |
commit | df6561e60244c0283340286664b0baf67e846599 (patch) | |
tree | 856a3f8de7b763362c765bcb36ab80f9ce646e2a | |
parent | 6174a1e24b0d13f85f64ff570e9d4efc6b0d6287 (diff) |
clk: sunxi-ng: Add N-K-M Factor clock
Introduce support for clocks that multiply and divide using two linear
multipliers and one linear divider.
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-12-maxime.ripard@free-electrons.com
-rw-r--r-- | drivers/clk/sunxi-ng/Kconfig | 5 | ||||
-rw-r--r-- | drivers/clk/sunxi-ng/Makefile | 1 | ||||
-rw-r--r-- | drivers/clk/sunxi-ng/ccu_nkm.c | 153 | ||||
-rw-r--r-- | drivers/clk/sunxi-ng/ccu_nkm.h | 68 |
4 files changed, 227 insertions, 0 deletions
diff --git a/drivers/clk/sunxi-ng/Kconfig b/drivers/clk/sunxi-ng/Kconfig index d8a5da270778..4c571b86bfd1 100644 --- a/drivers/clk/sunxi-ng/Kconfig +++ b/drivers/clk/sunxi-ng/Kconfig | |||
@@ -28,6 +28,11 @@ config SUNXI_CCU_NK | |||
28 | bool | 28 | bool |
29 | select SUNXI_CCU_GATE | 29 | select SUNXI_CCU_GATE |
30 | 30 | ||
31 | config SUNXI_CCU_NKM | ||
32 | bool | ||
33 | select RATIONAL | ||
34 | select SUNXI_CCU_GATE | ||
35 | |||
31 | config SUNXI_CCU_NM | 36 | config SUNXI_CCU_NM |
32 | bool | 37 | bool |
33 | select RATIONAL | 38 | select RATIONAL |
diff --git a/drivers/clk/sunxi-ng/Makefile b/drivers/clk/sunxi-ng/Makefile index 7d2cd2a47a39..2d6fdca0d3cc 100644 --- a/drivers/clk/sunxi-ng/Makefile +++ b/drivers/clk/sunxi-ng/Makefile | |||
@@ -11,5 +11,6 @@ obj-$(CONFIG_SUNXI_CCU_PHASE) += ccu_phase.o | |||
11 | 11 | ||
12 | # Multi-factor clocks | 12 | # Multi-factor clocks |
13 | obj-$(CONFIG_SUNXI_CCU_NK) += ccu_nk.o | 13 | obj-$(CONFIG_SUNXI_CCU_NK) += ccu_nk.o |
14 | obj-$(CONFIG_SUNXI_CCU_NKM) += ccu_nkm.o | ||
14 | obj-$(CONFIG_SUNXI_CCU_NM) += ccu_nm.o | 15 | obj-$(CONFIG_SUNXI_CCU_NM) += ccu_nm.o |
15 | obj-$(CONFIG_SUNXI_CCU_MP) += ccu_mp.o | 16 | obj-$(CONFIG_SUNXI_CCU_MP) += ccu_mp.o |
diff --git a/drivers/clk/sunxi-ng/ccu_nkm.c b/drivers/clk/sunxi-ng/ccu_nkm.c new file mode 100644 index 000000000000..2071822b1e9c --- /dev/null +++ b/drivers/clk/sunxi-ng/ccu_nkm.c | |||
@@ -0,0 +1,153 @@ | |||
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/rational.h> | ||
13 | |||
14 | #include "ccu_gate.h" | ||
15 | #include "ccu_nkm.h" | ||
16 | |||
17 | struct _ccu_nkm { | ||
18 | unsigned long n, max_n; | ||
19 | unsigned long k, max_k; | ||
20 | unsigned long m, max_m; | ||
21 | }; | ||
22 | |||
23 | static void ccu_nkm_find_best(unsigned long parent, unsigned long rate, | ||
24 | struct _ccu_nkm *nkm) | ||
25 | { | ||
26 | unsigned long best_rate = 0; | ||
27 | unsigned long best_n = 0, best_k = 0, best_m = 0; | ||
28 | unsigned long _n, _k, _m; | ||
29 | |||
30 | for (_k = 1; _k <= nkm->max_k; _k++) { | ||
31 | unsigned long tmp_rate; | ||
32 | |||
33 | rational_best_approximation(rate / _k, parent, | ||
34 | nkm->max_n, nkm->max_m, &_n, &_m); | ||
35 | |||
36 | tmp_rate = parent * _n * _k / _m; | ||
37 | |||
38 | if (tmp_rate > rate) | ||
39 | continue; | ||
40 | |||
41 | if ((rate - tmp_rate) < (rate - best_rate)) { | ||
42 | best_rate = tmp_rate; | ||
43 | best_n = _n; | ||
44 | best_k = _k; | ||
45 | best_m = _m; | ||
46 | } | ||
47 | } | ||
48 | |||
49 | nkm->n = best_n; | ||
50 | nkm->k = best_k; | ||
51 | nkm->m = best_m; | ||
52 | } | ||
53 | |||
54 | static void ccu_nkm_disable(struct clk_hw *hw) | ||
55 | { | ||
56 | struct ccu_nkm *nkm = hw_to_ccu_nkm(hw); | ||
57 | |||
58 | return ccu_gate_helper_disable(&nkm->common, nkm->enable); | ||
59 | } | ||
60 | |||
61 | static int ccu_nkm_enable(struct clk_hw *hw) | ||
62 | { | ||
63 | struct ccu_nkm *nkm = hw_to_ccu_nkm(hw); | ||
64 | |||
65 | return ccu_gate_helper_enable(&nkm->common, nkm->enable); | ||
66 | } | ||
67 | |||
68 | static int ccu_nkm_is_enabled(struct clk_hw *hw) | ||
69 | { | ||
70 | struct ccu_nkm *nkm = hw_to_ccu_nkm(hw); | ||
71 | |||
72 | return ccu_gate_helper_is_enabled(&nkm->common, nkm->enable); | ||
73 | } | ||
74 | |||
75 | static unsigned long ccu_nkm_recalc_rate(struct clk_hw *hw, | ||
76 | unsigned long parent_rate) | ||
77 | { | ||
78 | struct ccu_nkm *nkm = hw_to_ccu_nkm(hw); | ||
79 | unsigned long n, m, k; | ||
80 | u32 reg; | ||
81 | |||
82 | reg = readl(nkm->common.base + nkm->common.reg); | ||
83 | |||
84 | n = reg >> nkm->n.shift; | ||
85 | n &= (1 << nkm->n.width) - 1; | ||
86 | |||
87 | k = reg >> nkm->k.shift; | ||
88 | k &= (1 << nkm->k.width) - 1; | ||
89 | |||
90 | m = reg >> nkm->m.shift; | ||
91 | m &= (1 << nkm->m.width) - 1; | ||
92 | |||
93 | return parent_rate * (n + 1) * (k + 1) / (m + 1); | ||
94 | } | ||
95 | |||
96 | static long ccu_nkm_round_rate(struct clk_hw *hw, unsigned long rate, | ||
97 | unsigned long *parent_rate) | ||
98 | { | ||
99 | struct ccu_nkm *nkm = hw_to_ccu_nkm(hw); | ||
100 | struct _ccu_nkm _nkm; | ||
101 | |||
102 | _nkm.max_n = 1 << nkm->n.width; | ||
103 | _nkm.max_k = 1 << nkm->k.width; | ||
104 | _nkm.max_m = 1 << nkm->m.width; | ||
105 | |||
106 | ccu_nkm_find_best(*parent_rate, rate, &_nkm); | ||
107 | |||
108 | return *parent_rate * _nkm.n * _nkm.k / _nkm.m; | ||
109 | } | ||
110 | |||
111 | static int ccu_nkm_set_rate(struct clk_hw *hw, unsigned long rate, | ||
112 | unsigned long parent_rate) | ||
113 | { | ||
114 | struct ccu_nkm *nkm = hw_to_ccu_nkm(hw); | ||
115 | struct _ccu_nkm _nkm; | ||
116 | unsigned long flags; | ||
117 | u32 reg; | ||
118 | |||
119 | _nkm.max_n = 1 << nkm->n.width; | ||
120 | _nkm.max_k = 1 << nkm->k.width; | ||
121 | _nkm.max_m = 1 << nkm->m.width; | ||
122 | |||
123 | ccu_nkm_find_best(parent_rate, rate, &_nkm); | ||
124 | |||
125 | spin_lock_irqsave(nkm->common.lock, flags); | ||
126 | |||
127 | reg = readl(nkm->common.base + nkm->common.reg); | ||
128 | reg &= ~GENMASK(nkm->n.width + nkm->n.shift - 1, nkm->n.shift); | ||
129 | reg &= ~GENMASK(nkm->k.width + nkm->k.shift - 1, nkm->k.shift); | ||
130 | reg &= ~GENMASK(nkm->m.width + nkm->m.shift - 1, nkm->m.shift); | ||
131 | |||
132 | reg |= (_nkm.n - 1) << nkm->n.shift; | ||
133 | reg |= (_nkm.k - 1) << nkm->k.shift; | ||
134 | reg |= (_nkm.m - 1) << nkm->m.shift; | ||
135 | |||
136 | writel(reg, nkm->common.base + nkm->common.reg); | ||
137 | |||
138 | spin_unlock_irqrestore(nkm->common.lock, flags); | ||
139 | |||
140 | ccu_helper_wait_for_lock(&nkm->common, nkm->lock); | ||
141 | |||
142 | return 0; | ||
143 | } | ||
144 | |||
145 | const struct clk_ops ccu_nkm_ops = { | ||
146 | .disable = ccu_nkm_disable, | ||
147 | .enable = ccu_nkm_enable, | ||
148 | .is_enabled = ccu_nkm_is_enabled, | ||
149 | |||
150 | .recalc_rate = ccu_nkm_recalc_rate, | ||
151 | .round_rate = ccu_nkm_round_rate, | ||
152 | .set_rate = ccu_nkm_set_rate, | ||
153 | }; | ||
diff --git a/drivers/clk/sunxi-ng/ccu_nkm.h b/drivers/clk/sunxi-ng/ccu_nkm.h new file mode 100644 index 000000000000..1936ac1c6b37 --- /dev/null +++ b/drivers/clk/sunxi-ng/ccu_nkm.h | |||
@@ -0,0 +1,68 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2016 Maxime Ripard. All rights reserved. | ||
3 | * | ||
4 | * This software is licensed under the terms of the GNU General Public | ||
5 | * License version 2, as published by the Free Software Foundation, and | ||
6 | * may be copied, distributed, and modified under those terms. | ||
7 | * | ||
8 | * This program is distributed in the hope that it will be useful, | ||
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
11 | * GNU General Public License for more details. | ||
12 | */ | ||
13 | |||
14 | #ifndef _CCU_NKM_H_ | ||
15 | #define _CCU_NKM_H_ | ||
16 | |||
17 | #include <linux/clk-provider.h> | ||
18 | |||
19 | #include "ccu_common.h" | ||
20 | #include "ccu_div.h" | ||
21 | #include "ccu_mult.h" | ||
22 | |||
23 | /* | ||
24 | * struct ccu_nkm - Definition of an N-K-M clock | ||
25 | * | ||
26 | * Clocks based on the formula parent * N * K / M | ||
27 | */ | ||
28 | struct ccu_nkm { | ||
29 | u32 enable; | ||
30 | u32 lock; | ||
31 | |||
32 | struct _ccu_mult n; | ||
33 | struct _ccu_mult k; | ||
34 | struct _ccu_div m; | ||
35 | |||
36 | struct ccu_common common; | ||
37 | }; | ||
38 | |||
39 | #define SUNXI_CCU_NKM_WITH_GATE_LOCK(_struct, _name, _parent, _reg, \ | ||
40 | _nshift, _nwidth, \ | ||
41 | _kshift, _kwidth, \ | ||
42 | _mshift, _mwidth, \ | ||
43 | _gate, _lock, _flags) \ | ||
44 | struct ccu_nkm _struct = { \ | ||
45 | .enable = _gate, \ | ||
46 | .lock = _lock, \ | ||
47 | .k = _SUNXI_CCU_MULT(_kshift, _kwidth), \ | ||
48 | .n = _SUNXI_CCU_MULT(_nshift, _nwidth), \ | ||
49 | .m = _SUNXI_CCU_DIV(_mshift, _mwidth), \ | ||
50 | .common = { \ | ||
51 | .reg = _reg, \ | ||
52 | .hw.init = CLK_HW_INIT(_name, \ | ||
53 | _parent, \ | ||
54 | &ccu_nkm_ops, \ | ||
55 | _flags), \ | ||
56 | }, \ | ||
57 | } | ||
58 | |||
59 | static inline struct ccu_nkm *hw_to_ccu_nkm(struct clk_hw *hw) | ||
60 | { | ||
61 | struct ccu_common *common = hw_to_ccu_common(hw); | ||
62 | |||
63 | return container_of(common, struct ccu_nkm, common); | ||
64 | } | ||
65 | |||
66 | extern const struct clk_ops ccu_nkm_ops; | ||
67 | |||
68 | #endif /* _CCU_NKM_H_ */ | ||