Commit | Line | Data |
---|---|---|
9026e0d1 MR |
1 | /* |
2 | * Copyright (C) 2016 Free Electrons | |
3 | * Copyright (C) 2016 NextThing Co | |
4 | * | |
5 | * Maxime Ripard <maxime.ripard@free-electrons.com> | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or | |
8 | * modify it under the terms of the GNU General Public License as | |
9 | * published by the Free Software Foundation; either version 2 of | |
10 | * the License, or (at your option) any later version. | |
11 | */ | |
12 | ||
13 | #include <linux/clk-provider.h> | |
14 | #include <linux/regmap.h> | |
15 | ||
16 | #include "sun4i_tcon.h" | |
17 | ||
18 | struct sun4i_dclk { | |
19 | struct clk_hw hw; | |
20 | struct regmap *regmap; | |
21 | }; | |
22 | ||
23 | static inline struct sun4i_dclk *hw_to_dclk(struct clk_hw *hw) | |
24 | { | |
25 | return container_of(hw, struct sun4i_dclk, hw); | |
26 | } | |
27 | ||
28 | static void sun4i_dclk_disable(struct clk_hw *hw) | |
29 | { | |
30 | struct sun4i_dclk *dclk = hw_to_dclk(hw); | |
31 | ||
32 | regmap_update_bits(dclk->regmap, SUN4I_TCON0_DCLK_REG, | |
33 | BIT(SUN4I_TCON0_DCLK_GATE_BIT), 0); | |
34 | } | |
35 | ||
36 | static int sun4i_dclk_enable(struct clk_hw *hw) | |
37 | { | |
38 | struct sun4i_dclk *dclk = hw_to_dclk(hw); | |
39 | ||
40 | return regmap_update_bits(dclk->regmap, SUN4I_TCON0_DCLK_REG, | |
41 | BIT(SUN4I_TCON0_DCLK_GATE_BIT), | |
42 | BIT(SUN4I_TCON0_DCLK_GATE_BIT)); | |
43 | } | |
44 | ||
45 | static int sun4i_dclk_is_enabled(struct clk_hw *hw) | |
46 | { | |
47 | struct sun4i_dclk *dclk = hw_to_dclk(hw); | |
48 | u32 val; | |
49 | ||
50 | regmap_read(dclk->regmap, SUN4I_TCON0_DCLK_REG, &val); | |
51 | ||
52 | return val & BIT(SUN4I_TCON0_DCLK_GATE_BIT); | |
53 | } | |
54 | ||
55 | static unsigned long sun4i_dclk_recalc_rate(struct clk_hw *hw, | |
56 | unsigned long parent_rate) | |
57 | { | |
58 | struct sun4i_dclk *dclk = hw_to_dclk(hw); | |
59 | u32 val; | |
60 | ||
61 | regmap_read(dclk->regmap, SUN4I_TCON0_DCLK_REG, &val); | |
62 | ||
63 | val >>= SUN4I_TCON0_DCLK_DIV_SHIFT; | |
64 | val &= SUN4I_TCON0_DCLK_DIV_WIDTH; | |
65 | ||
66 | if (!val) | |
67 | val = 1; | |
68 | ||
69 | return parent_rate / val; | |
70 | } | |
71 | ||
72 | static long sun4i_dclk_round_rate(struct clk_hw *hw, unsigned long rate, | |
73 | unsigned long *parent_rate) | |
74 | { | |
4731a72d MR |
75 | unsigned long best_parent = 0; |
76 | u8 best_div = 1; | |
77 | int i; | |
78 | ||
79 | for (i = 6; i < 127; i++) { | |
80 | unsigned long ideal = rate * i; | |
81 | unsigned long rounded; | |
82 | ||
83 | rounded = clk_hw_round_rate(clk_hw_get_parent(hw), | |
84 | ideal); | |
85 | ||
86 | if (rounded == ideal) { | |
87 | best_parent = rounded; | |
88 | best_div = i; | |
89 | goto out; | |
90 | } | |
91 | ||
92 | if ((rounded < ideal) && (rounded > best_parent)) { | |
93 | best_parent = rounded; | |
94 | best_div = i; | |
95 | } | |
96 | } | |
97 | ||
98 | out: | |
99 | *parent_rate = best_parent; | |
100 | ||
101 | return best_parent / best_div; | |
9026e0d1 MR |
102 | } |
103 | ||
104 | static int sun4i_dclk_set_rate(struct clk_hw *hw, unsigned long rate, | |
105 | unsigned long parent_rate) | |
106 | { | |
107 | struct sun4i_dclk *dclk = hw_to_dclk(hw); | |
4731a72d | 108 | u8 div = parent_rate / rate; |
9026e0d1 MR |
109 | |
110 | return regmap_update_bits(dclk->regmap, SUN4I_TCON0_DCLK_REG, | |
111 | GENMASK(6, 0), div); | |
112 | } | |
113 | ||
114 | static int sun4i_dclk_get_phase(struct clk_hw *hw) | |
115 | { | |
116 | struct sun4i_dclk *dclk = hw_to_dclk(hw); | |
117 | u32 val; | |
118 | ||
119 | regmap_read(dclk->regmap, SUN4I_TCON0_IO_POL_REG, &val); | |
120 | ||
121 | val >>= 28; | |
122 | val &= 3; | |
123 | ||
124 | return val * 120; | |
125 | } | |
126 | ||
127 | static int sun4i_dclk_set_phase(struct clk_hw *hw, int degrees) | |
128 | { | |
129 | struct sun4i_dclk *dclk = hw_to_dclk(hw); | |
130 | ||
131 | regmap_update_bits(dclk->regmap, SUN4I_TCON0_IO_POL_REG, | |
132 | GENMASK(29, 28), | |
133 | degrees / 120); | |
134 | ||
135 | return 0; | |
136 | } | |
137 | ||
138 | static const struct clk_ops sun4i_dclk_ops = { | |
139 | .disable = sun4i_dclk_disable, | |
140 | .enable = sun4i_dclk_enable, | |
141 | .is_enabled = sun4i_dclk_is_enabled, | |
142 | ||
143 | .recalc_rate = sun4i_dclk_recalc_rate, | |
144 | .round_rate = sun4i_dclk_round_rate, | |
145 | .set_rate = sun4i_dclk_set_rate, | |
146 | ||
147 | .get_phase = sun4i_dclk_get_phase, | |
148 | .set_phase = sun4i_dclk_set_phase, | |
149 | }; | |
150 | ||
151 | int sun4i_dclk_create(struct device *dev, struct sun4i_tcon *tcon) | |
152 | { | |
153 | const char *clk_name, *parent_name; | |
154 | struct clk_init_data init; | |
155 | struct sun4i_dclk *dclk; | |
9fa2568d | 156 | int ret; |
9026e0d1 MR |
157 | |
158 | parent_name = __clk_get_name(tcon->sclk0); | |
9fa2568d AB |
159 | ret = of_property_read_string_index(dev->of_node, |
160 | "clock-output-names", 0, | |
161 | &clk_name); | |
162 | if (ret) | |
163 | return ret; | |
9026e0d1 MR |
164 | |
165 | dclk = devm_kzalloc(dev, sizeof(*dclk), GFP_KERNEL); | |
166 | if (!dclk) | |
167 | return -ENOMEM; | |
168 | ||
169 | init.name = clk_name; | |
170 | init.ops = &sun4i_dclk_ops; | |
171 | init.parent_names = &parent_name; | |
172 | init.num_parents = 1; | |
4731a72d | 173 | init.flags = CLK_SET_RATE_PARENT; |
9026e0d1 MR |
174 | |
175 | dclk->regmap = tcon->regs; | |
176 | dclk->hw.init = &init; | |
177 | ||
178 | tcon->dclk = clk_register(dev, &dclk->hw); | |
179 | if (IS_ERR(tcon->dclk)) | |
180 | return PTR_ERR(tcon->dclk); | |
181 | ||
182 | return 0; | |
183 | } | |
184 | EXPORT_SYMBOL(sun4i_dclk_create); | |
185 | ||
186 | int sun4i_dclk_free(struct sun4i_tcon *tcon) | |
187 | { | |
188 | clk_unregister(tcon->dclk); | |
189 | return 0; | |
190 | } | |
191 | EXPORT_SYMBOL(sun4i_dclk_free); |