diff options
Diffstat (limited to 'drivers/clk/sunxi-ng/ccu_div.c')
-rw-r--r-- | drivers/clk/sunxi-ng/ccu_div.c | 136 |
1 files changed, 136 insertions, 0 deletions
diff --git a/drivers/clk/sunxi-ng/ccu_div.c b/drivers/clk/sunxi-ng/ccu_div.c new file mode 100644 index 000000000000..8659b4cb6c20 --- /dev/null +++ b/drivers/clk/sunxi-ng/ccu_div.c | |||
@@ -0,0 +1,136 @@ | |||
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 | |||
13 | #include "ccu_gate.h" | ||
14 | #include "ccu_div.h" | ||
15 | |||
16 | static unsigned long ccu_div_round_rate(struct ccu_mux_internal *mux, | ||
17 | unsigned long parent_rate, | ||
18 | unsigned long rate, | ||
19 | void *data) | ||
20 | { | ||
21 | struct ccu_div *cd = data; | ||
22 | unsigned long val; | ||
23 | |||
24 | /* | ||
25 | * We can't use divider_round_rate that assumes that there's | ||
26 | * several parents, while we might be called to evaluate | ||
27 | * several different parents. | ||
28 | */ | ||
29 | val = divider_get_val(rate, parent_rate, cd->div.table, cd->div.width, | ||
30 | cd->div.flags); | ||
31 | |||
32 | return divider_recalc_rate(&cd->common.hw, parent_rate, val, | ||
33 | cd->div.table, cd->div.flags); | ||
34 | } | ||
35 | |||
36 | static void ccu_div_disable(struct clk_hw *hw) | ||
37 | { | ||
38 | struct ccu_div *cd = hw_to_ccu_div(hw); | ||
39 | |||
40 | return ccu_gate_helper_disable(&cd->common, cd->enable); | ||
41 | } | ||
42 | |||
43 | static int ccu_div_enable(struct clk_hw *hw) | ||
44 | { | ||
45 | struct ccu_div *cd = hw_to_ccu_div(hw); | ||
46 | |||
47 | return ccu_gate_helper_enable(&cd->common, cd->enable); | ||
48 | } | ||
49 | |||
50 | static int ccu_div_is_enabled(struct clk_hw *hw) | ||
51 | { | ||
52 | struct ccu_div *cd = hw_to_ccu_div(hw); | ||
53 | |||
54 | return ccu_gate_helper_is_enabled(&cd->common, cd->enable); | ||
55 | } | ||
56 | |||
57 | static unsigned long ccu_div_recalc_rate(struct clk_hw *hw, | ||
58 | unsigned long parent_rate) | ||
59 | { | ||
60 | struct ccu_div *cd = hw_to_ccu_div(hw); | ||
61 | unsigned long val; | ||
62 | u32 reg; | ||
63 | |||
64 | reg = readl(cd->common.base + cd->common.reg); | ||
65 | val = reg >> cd->div.shift; | ||
66 | val &= (1 << cd->div.width) - 1; | ||
67 | |||
68 | ccu_mux_helper_adjust_parent_for_prediv(&cd->common, &cd->mux, -1, | ||
69 | &parent_rate); | ||
70 | |||
71 | return divider_recalc_rate(hw, parent_rate, val, cd->div.table, | ||
72 | cd->div.flags); | ||
73 | } | ||
74 | |||
75 | static int ccu_div_determine_rate(struct clk_hw *hw, | ||
76 | struct clk_rate_request *req) | ||
77 | { | ||
78 | struct ccu_div *cd = hw_to_ccu_div(hw); | ||
79 | |||
80 | return ccu_mux_helper_determine_rate(&cd->common, &cd->mux, | ||
81 | req, ccu_div_round_rate, cd); | ||
82 | } | ||
83 | |||
84 | static int ccu_div_set_rate(struct clk_hw *hw, unsigned long rate, | ||
85 | unsigned long parent_rate) | ||
86 | { | ||
87 | struct ccu_div *cd = hw_to_ccu_div(hw); | ||
88 | unsigned long flags; | ||
89 | unsigned long val; | ||
90 | u32 reg; | ||
91 | |||
92 | ccu_mux_helper_adjust_parent_for_prediv(&cd->common, &cd->mux, -1, | ||
93 | &parent_rate); | ||
94 | |||
95 | val = divider_get_val(rate, parent_rate, cd->div.table, cd->div.width, | ||
96 | cd->div.flags); | ||
97 | |||
98 | spin_lock_irqsave(cd->common.lock, flags); | ||
99 | |||
100 | reg = readl(cd->common.base + cd->common.reg); | ||
101 | reg &= ~GENMASK(cd->div.width + cd->div.shift - 1, cd->div.shift); | ||
102 | |||
103 | writel(reg | (val << cd->div.shift), | ||
104 | cd->common.base + cd->common.reg); | ||
105 | |||
106 | spin_unlock_irqrestore(cd->common.lock, flags); | ||
107 | |||
108 | return 0; | ||
109 | } | ||
110 | |||
111 | static u8 ccu_div_get_parent(struct clk_hw *hw) | ||
112 | { | ||
113 | struct ccu_div *cd = hw_to_ccu_div(hw); | ||
114 | |||
115 | return ccu_mux_helper_get_parent(&cd->common, &cd->mux); | ||
116 | } | ||
117 | |||
118 | static int ccu_div_set_parent(struct clk_hw *hw, u8 index) | ||
119 | { | ||
120 | struct ccu_div *cd = hw_to_ccu_div(hw); | ||
121 | |||
122 | return ccu_mux_helper_set_parent(&cd->common, &cd->mux, index); | ||
123 | } | ||
124 | |||
125 | const struct clk_ops ccu_div_ops = { | ||
126 | .disable = ccu_div_disable, | ||
127 | .enable = ccu_div_enable, | ||
128 | .is_enabled = ccu_div_is_enabled, | ||
129 | |||
130 | .get_parent = ccu_div_get_parent, | ||
131 | .set_parent = ccu_div_set_parent, | ||
132 | |||
133 | .determine_rate = ccu_div_determine_rate, | ||
134 | .recalc_rate = ccu_div_recalc_rate, | ||
135 | .set_rate = ccu_div_set_rate, | ||
136 | }; | ||