aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMaxime Ripard <maxime.ripard@free-electrons.com>2016-06-29 15:05:32 -0400
committerMichael Turquette <mturquette@baylibre.com>2016-07-08 21:05:03 -0400
commitdf6561e60244c0283340286664b0baf67e846599 (patch)
tree856a3f8de7b763362c765bcb36ab80f9ce646e2a
parent6174a1e24b0d13f85f64ff570e9d4efc6b0d6287 (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/Kconfig5
-rw-r--r--drivers/clk/sunxi-ng/Makefile1
-rw-r--r--drivers/clk/sunxi-ng/ccu_nkm.c153
-rw-r--r--drivers/clk/sunxi-ng/ccu_nkm.h68
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
31config SUNXI_CCU_NKM
32 bool
33 select RATIONAL
34 select SUNXI_CCU_GATE
35
31config SUNXI_CCU_NM 36config 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
13obj-$(CONFIG_SUNXI_CCU_NK) += ccu_nk.o 13obj-$(CONFIG_SUNXI_CCU_NK) += ccu_nk.o
14obj-$(CONFIG_SUNXI_CCU_NKM) += ccu_nkm.o
14obj-$(CONFIG_SUNXI_CCU_NM) += ccu_nm.o 15obj-$(CONFIG_SUNXI_CCU_NM) += ccu_nm.o
15obj-$(CONFIG_SUNXI_CCU_MP) += ccu_mp.o 16obj-$(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
17struct _ccu_nkm {
18 unsigned long n, max_n;
19 unsigned long k, max_k;
20 unsigned long m, max_m;
21};
22
23static 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
54static 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
61static 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
68static 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
75static 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
96static 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
111static 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
145const 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 */
28struct 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
59static 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
66extern const struct clk_ops ccu_nkm_ops;
67
68#endif /* _CCU_NKM_H_ */