diff options
author | Maxime Ripard <maxime.ripard@free-electrons.com> | 2016-06-29 15:05:33 -0400 |
---|---|---|
committer | Michael Turquette <mturquette@baylibre.com> | 2016-07-08 21:05:06 -0400 |
commit | 4f728b5db7cb125af71e5da8154ac3b72653d819 (patch) | |
tree | cf8883d002ee6d51f9558796ee471280407f7f5f | |
parent | df6561e60244c0283340286664b0baf67e846599 (diff) |
clk: sunxi-ng: Add N-K-M-P factor clock
Introduce support for clocks that use a combination of two linear
multipliers (N and K factors), one linear divider (M) and one power of two
divider (P).
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-13-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_nkmp.c | 167 | ||||
-rw-r--r-- | drivers/clk/sunxi-ng/ccu_nkmp.h | 71 |
4 files changed, 244 insertions, 0 deletions
diff --git a/drivers/clk/sunxi-ng/Kconfig b/drivers/clk/sunxi-ng/Kconfig index 4c571b86bfd1..24fc9e4cc546 100644 --- a/drivers/clk/sunxi-ng/Kconfig +++ b/drivers/clk/sunxi-ng/Kconfig | |||
@@ -33,6 +33,11 @@ config SUNXI_CCU_NKM | |||
33 | select RATIONAL | 33 | select RATIONAL |
34 | select SUNXI_CCU_GATE | 34 | select SUNXI_CCU_GATE |
35 | 35 | ||
36 | config SUNXI_CCU_NKMP | ||
37 | bool | ||
38 | select RATIONAL | ||
39 | select SUNXI_CCU_GATE | ||
40 | |||
36 | config SUNXI_CCU_NM | 41 | config SUNXI_CCU_NM |
37 | bool | 42 | bool |
38 | select RATIONAL | 43 | select RATIONAL |
diff --git a/drivers/clk/sunxi-ng/Makefile b/drivers/clk/sunxi-ng/Makefile index 2d6fdca0d3cc..5c342cc698d2 100644 --- a/drivers/clk/sunxi-ng/Makefile +++ b/drivers/clk/sunxi-ng/Makefile | |||
@@ -12,5 +12,6 @@ obj-$(CONFIG_SUNXI_CCU_PHASE) += ccu_phase.o | |||
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_NKM) += ccu_nkm.o |
15 | obj-$(CONFIG_SUNXI_CCU_NKMP) += ccu_nkmp.o | ||
15 | obj-$(CONFIG_SUNXI_CCU_NM) += ccu_nm.o | 16 | obj-$(CONFIG_SUNXI_CCU_NM) += ccu_nm.o |
16 | obj-$(CONFIG_SUNXI_CCU_MP) += ccu_mp.o | 17 | obj-$(CONFIG_SUNXI_CCU_MP) += ccu_mp.o |
diff --git a/drivers/clk/sunxi-ng/ccu_nkmp.c b/drivers/clk/sunxi-ng/ccu_nkmp.c new file mode 100644 index 000000000000..9f2b98e19dc9 --- /dev/null +++ b/drivers/clk/sunxi-ng/ccu_nkmp.c | |||
@@ -0,0 +1,167 @@ | |||
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_nkmp.h" | ||
16 | |||
17 | struct _ccu_nkmp { | ||
18 | unsigned long n, max_n; | ||
19 | unsigned long k, max_k; | ||
20 | unsigned long m, max_m; | ||
21 | unsigned long p, max_p; | ||
22 | }; | ||
23 | |||
24 | static void ccu_nkmp_find_best(unsigned long parent, unsigned long rate, | ||
25 | struct _ccu_nkmp *nkmp) | ||
26 | { | ||
27 | unsigned long best_rate = 0; | ||
28 | unsigned long best_n = 0, best_k = 0, best_m = 0, best_p = 0; | ||
29 | unsigned long _n, _k, _m, _p; | ||
30 | |||
31 | for (_k = 1; _k <= nkmp->max_k; _k++) { | ||
32 | for (_p = 0; _p <= nkmp->max_p; _p++) { | ||
33 | unsigned long tmp_rate; | ||
34 | |||
35 | rational_best_approximation(rate / _k, parent >> _p, | ||
36 | nkmp->max_n, nkmp->max_m, | ||
37 | &_n, &_m); | ||
38 | |||
39 | tmp_rate = (parent * _n * _k >> _p) / _m; | ||
40 | |||
41 | if (tmp_rate > rate) | ||
42 | continue; | ||
43 | |||
44 | if ((rate - tmp_rate) < (rate - best_rate)) { | ||
45 | best_rate = tmp_rate; | ||
46 | best_n = _n; | ||
47 | best_k = _k; | ||
48 | best_m = _m; | ||
49 | best_p = _p; | ||
50 | } | ||
51 | } | ||
52 | } | ||
53 | |||
54 | nkmp->n = best_n; | ||
55 | nkmp->k = best_k; | ||
56 | nkmp->m = best_m; | ||
57 | nkmp->p = best_p; | ||
58 | } | ||
59 | |||
60 | static void ccu_nkmp_disable(struct clk_hw *hw) | ||
61 | { | ||
62 | struct ccu_nkmp *nkmp = hw_to_ccu_nkmp(hw); | ||
63 | |||
64 | return ccu_gate_helper_disable(&nkmp->common, nkmp->enable); | ||
65 | } | ||
66 | |||
67 | static int ccu_nkmp_enable(struct clk_hw *hw) | ||
68 | { | ||
69 | struct ccu_nkmp *nkmp = hw_to_ccu_nkmp(hw); | ||
70 | |||
71 | return ccu_gate_helper_enable(&nkmp->common, nkmp->enable); | ||
72 | } | ||
73 | |||
74 | static int ccu_nkmp_is_enabled(struct clk_hw *hw) | ||
75 | { | ||
76 | struct ccu_nkmp *nkmp = hw_to_ccu_nkmp(hw); | ||
77 | |||
78 | return ccu_gate_helper_is_enabled(&nkmp->common, nkmp->enable); | ||
79 | } | ||
80 | |||
81 | static unsigned long ccu_nkmp_recalc_rate(struct clk_hw *hw, | ||
82 | unsigned long parent_rate) | ||
83 | { | ||
84 | struct ccu_nkmp *nkmp = hw_to_ccu_nkmp(hw); | ||
85 | unsigned long n, m, k, p; | ||
86 | u32 reg; | ||
87 | |||
88 | reg = readl(nkmp->common.base + nkmp->common.reg); | ||
89 | |||
90 | n = reg >> nkmp->n.shift; | ||
91 | n &= (1 << nkmp->n.width) - 1; | ||
92 | |||
93 | k = reg >> nkmp->k.shift; | ||
94 | k &= (1 << nkmp->k.width) - 1; | ||
95 | |||
96 | m = reg >> nkmp->m.shift; | ||
97 | m &= (1 << nkmp->m.width) - 1; | ||
98 | |||
99 | p = reg >> nkmp->p.shift; | ||
100 | p &= (1 << nkmp->p.width) - 1; | ||
101 | |||
102 | return (parent_rate * (n + 1) * (k + 1) >> p) / (m + 1); | ||
103 | } | ||
104 | |||
105 | static long ccu_nkmp_round_rate(struct clk_hw *hw, unsigned long rate, | ||
106 | unsigned long *parent_rate) | ||
107 | { | ||
108 | struct ccu_nkmp *nkmp = hw_to_ccu_nkmp(hw); | ||
109 | struct _ccu_nkmp _nkmp; | ||
110 | |||
111 | _nkmp.max_n = 1 << nkmp->n.width; | ||
112 | _nkmp.max_k = 1 << nkmp->k.width; | ||
113 | _nkmp.max_m = 1 << nkmp->m.width; | ||
114 | _nkmp.max_p = (1 << nkmp->p.width) - 1; | ||
115 | |||
116 | ccu_nkmp_find_best(*parent_rate, rate, | ||
117 | &_nkmp); | ||
118 | |||
119 | return (*parent_rate * _nkmp.n * _nkmp.k >> _nkmp.p) / _nkmp.m; | ||
120 | } | ||
121 | |||
122 | static int ccu_nkmp_set_rate(struct clk_hw *hw, unsigned long rate, | ||
123 | unsigned long parent_rate) | ||
124 | { | ||
125 | struct ccu_nkmp *nkmp = hw_to_ccu_nkmp(hw); | ||
126 | struct _ccu_nkmp _nkmp; | ||
127 | unsigned long flags; | ||
128 | u32 reg; | ||
129 | |||
130 | _nkmp.max_n = 1 << nkmp->n.width; | ||
131 | _nkmp.max_k = 1 << nkmp->k.width; | ||
132 | _nkmp.max_m = 1 << nkmp->m.width; | ||
133 | _nkmp.max_p = (1 << nkmp->p.width) - 1; | ||
134 | |||
135 | ccu_nkmp_find_best(parent_rate, rate, &_nkmp); | ||
136 | |||
137 | spin_lock_irqsave(nkmp->common.lock, flags); | ||
138 | |||
139 | reg = readl(nkmp->common.base + nkmp->common.reg); | ||
140 | reg &= ~GENMASK(nkmp->n.width + nkmp->n.shift - 1, nkmp->n.shift); | ||
141 | reg &= ~GENMASK(nkmp->k.width + nkmp->k.shift - 1, nkmp->k.shift); | ||
142 | reg &= ~GENMASK(nkmp->m.width + nkmp->m.shift - 1, nkmp->m.shift); | ||
143 | reg &= ~GENMASK(nkmp->p.width + nkmp->p.shift - 1, nkmp->p.shift); | ||
144 | |||
145 | reg |= (_nkmp.n - 1) << nkmp->n.shift; | ||
146 | reg |= (_nkmp.k - 1) << nkmp->k.shift; | ||
147 | reg |= (_nkmp.m - 1) << nkmp->m.shift; | ||
148 | reg |= _nkmp.p << nkmp->p.shift; | ||
149 | |||
150 | writel(reg, nkmp->common.base + nkmp->common.reg); | ||
151 | |||
152 | spin_unlock_irqrestore(nkmp->common.lock, flags); | ||
153 | |||
154 | ccu_helper_wait_for_lock(&nkmp->common, nkmp->lock); | ||
155 | |||
156 | return 0; | ||
157 | } | ||
158 | |||
159 | const struct clk_ops ccu_nkmp_ops = { | ||
160 | .disable = ccu_nkmp_disable, | ||
161 | .enable = ccu_nkmp_enable, | ||
162 | .is_enabled = ccu_nkmp_is_enabled, | ||
163 | |||
164 | .recalc_rate = ccu_nkmp_recalc_rate, | ||
165 | .round_rate = ccu_nkmp_round_rate, | ||
166 | .set_rate = ccu_nkmp_set_rate, | ||
167 | }; | ||
diff --git a/drivers/clk/sunxi-ng/ccu_nkmp.h b/drivers/clk/sunxi-ng/ccu_nkmp.h new file mode 100644 index 000000000000..5adb0c92a614 --- /dev/null +++ b/drivers/clk/sunxi-ng/ccu_nkmp.h | |||
@@ -0,0 +1,71 @@ | |||
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_NKMP_H_ | ||
15 | #define _CCU_NKMP_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_nkmp - Definition of an N-K-M-P clock | ||
25 | * | ||
26 | * Clocks based on the formula parent * N * K >> P / M | ||
27 | */ | ||
28 | struct ccu_nkmp { | ||
29 | u32 enable; | ||
30 | u32 lock; | ||
31 | |||
32 | struct _ccu_mult n; | ||
33 | struct _ccu_mult k; | ||
34 | struct _ccu_div m; | ||
35 | struct _ccu_div p; | ||
36 | |||
37 | struct ccu_common common; | ||
38 | }; | ||
39 | |||
40 | #define SUNXI_CCU_NKMP_WITH_GATE_LOCK(_struct, _name, _parent, _reg, \ | ||
41 | _nshift, _nwidth, \ | ||
42 | _kshift, _kwidth, \ | ||
43 | _mshift, _mwidth, \ | ||
44 | _pshift, _pwidth, \ | ||
45 | _gate, _lock, _flags) \ | ||
46 | struct ccu_nkmp _struct = { \ | ||
47 | .enable = _gate, \ | ||
48 | .lock = _lock, \ | ||
49 | .n = _SUNXI_CCU_MULT(_nshift, _nwidth), \ | ||
50 | .k = _SUNXI_CCU_MULT(_kshift, _kwidth), \ | ||
51 | .m = _SUNXI_CCU_DIV(_mshift, _mwidth), \ | ||
52 | .p = _SUNXI_CCU_DIV(_pshift, _pwidth), \ | ||
53 | .common = { \ | ||
54 | .reg = _reg, \ | ||
55 | .hw.init = CLK_HW_INIT(_name, \ | ||
56 | _parent, \ | ||
57 | &ccu_nkmp_ops, \ | ||
58 | _flags), \ | ||
59 | }, \ | ||
60 | } | ||
61 | |||
62 | static inline struct ccu_nkmp *hw_to_ccu_nkmp(struct clk_hw *hw) | ||
63 | { | ||
64 | struct ccu_common *common = hw_to_ccu_common(hw); | ||
65 | |||
66 | return container_of(common, struct ccu_nkmp, common); | ||
67 | } | ||
68 | |||
69 | extern const struct clk_ops ccu_nkmp_ops; | ||
70 | |||
71 | #endif /* _CCU_NKMP_H_ */ | ||