1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Clock driver for Palmas device. |
4 | * |
5 | * Copyright (c) 2013, NVIDIA Corporation. |
6 | * Copyright (c) 2013-2014 Texas Instruments, Inc. |
7 | * |
8 | * Author: Laxman Dewangan <ldewangan@nvidia.com> |
9 | * Peter Ujfalusi <peter.ujfalusi@ti.com> |
10 | */ |
11 | |
12 | #include <linux/clk.h> |
13 | #include <linux/clk-provider.h> |
14 | #include <linux/mfd/palmas.h> |
15 | #include <linux/module.h> |
16 | #include <linux/of.h> |
17 | #include <linux/platform_device.h> |
18 | #include <linux/slab.h> |
19 | |
20 | #define PALMAS_CLOCK_DT_EXT_CONTROL_ENABLE1 1 |
21 | #define PALMAS_CLOCK_DT_EXT_CONTROL_ENABLE2 2 |
22 | #define PALMAS_CLOCK_DT_EXT_CONTROL_NSLEEP 3 |
23 | |
24 | struct palmas_clk32k_desc { |
25 | const char *clk_name; |
26 | unsigned int control_reg; |
27 | unsigned int enable_mask; |
28 | unsigned int sleep_mask; |
29 | unsigned int sleep_reqstr_id; |
30 | int delay; |
31 | }; |
32 | |
33 | struct palmas_clock_info { |
34 | struct device *dev; |
35 | struct clk_hw hw; |
36 | struct palmas *palmas; |
37 | const struct palmas_clk32k_desc *clk_desc; |
38 | int ext_control_pin; |
39 | }; |
40 | |
41 | static inline struct palmas_clock_info *to_palmas_clks_info(struct clk_hw *hw) |
42 | { |
43 | return container_of(hw, struct palmas_clock_info, hw); |
44 | } |
45 | |
46 | static unsigned long palmas_clks_recalc_rate(struct clk_hw *hw, |
47 | unsigned long parent_rate) |
48 | { |
49 | return 32768; |
50 | } |
51 | |
52 | static int palmas_clks_prepare(struct clk_hw *hw) |
53 | { |
54 | struct palmas_clock_info *cinfo = to_palmas_clks_info(hw); |
55 | int ret; |
56 | |
57 | ret = palmas_update_bits(palmas: cinfo->palmas, PALMAS_RESOURCE_BASE, |
58 | reg: cinfo->clk_desc->control_reg, |
59 | mask: cinfo->clk_desc->enable_mask, |
60 | val: cinfo->clk_desc->enable_mask); |
61 | if (ret < 0) |
62 | dev_err(cinfo->dev, "Reg 0x%02x update failed, %d\n" , |
63 | cinfo->clk_desc->control_reg, ret); |
64 | else if (cinfo->clk_desc->delay) |
65 | udelay(cinfo->clk_desc->delay); |
66 | |
67 | return ret; |
68 | } |
69 | |
70 | static void palmas_clks_unprepare(struct clk_hw *hw) |
71 | { |
72 | struct palmas_clock_info *cinfo = to_palmas_clks_info(hw); |
73 | int ret; |
74 | |
75 | /* |
76 | * Clock can be disabled through external pin if it is externally |
77 | * controlled. |
78 | */ |
79 | if (cinfo->ext_control_pin) |
80 | return; |
81 | |
82 | ret = palmas_update_bits(palmas: cinfo->palmas, PALMAS_RESOURCE_BASE, |
83 | reg: cinfo->clk_desc->control_reg, |
84 | mask: cinfo->clk_desc->enable_mask, val: 0); |
85 | if (ret < 0) |
86 | dev_err(cinfo->dev, "Reg 0x%02x update failed, %d\n" , |
87 | cinfo->clk_desc->control_reg, ret); |
88 | } |
89 | |
90 | static int palmas_clks_is_prepared(struct clk_hw *hw) |
91 | { |
92 | struct palmas_clock_info *cinfo = to_palmas_clks_info(hw); |
93 | int ret; |
94 | u32 val; |
95 | |
96 | if (cinfo->ext_control_pin) |
97 | return 1; |
98 | |
99 | ret = palmas_read(palmas: cinfo->palmas, PALMAS_RESOURCE_BASE, |
100 | reg: cinfo->clk_desc->control_reg, val: &val); |
101 | if (ret < 0) { |
102 | dev_err(cinfo->dev, "Reg 0x%02x read failed, %d\n" , |
103 | cinfo->clk_desc->control_reg, ret); |
104 | return ret; |
105 | } |
106 | return !!(val & cinfo->clk_desc->enable_mask); |
107 | } |
108 | |
109 | static const struct clk_ops palmas_clks_ops = { |
110 | .prepare = palmas_clks_prepare, |
111 | .unprepare = palmas_clks_unprepare, |
112 | .is_prepared = palmas_clks_is_prepared, |
113 | .recalc_rate = palmas_clks_recalc_rate, |
114 | }; |
115 | |
116 | struct palmas_clks_of_match_data { |
117 | struct clk_init_data init; |
118 | const struct palmas_clk32k_desc desc; |
119 | }; |
120 | |
121 | static const struct palmas_clks_of_match_data palmas_of_clk32kg = { |
122 | .init = { |
123 | .name = "clk32kg" , |
124 | .ops = &palmas_clks_ops, |
125 | .flags = CLK_IGNORE_UNUSED, |
126 | }, |
127 | .desc = { |
128 | .clk_name = "clk32kg" , |
129 | .control_reg = PALMAS_CLK32KG_CTRL, |
130 | .enable_mask = PALMAS_CLK32KG_CTRL_MODE_ACTIVE, |
131 | .sleep_mask = PALMAS_CLK32KG_CTRL_MODE_SLEEP, |
132 | .sleep_reqstr_id = PALMAS_EXTERNAL_REQSTR_ID_CLK32KG, |
133 | .delay = 200, |
134 | }, |
135 | }; |
136 | |
137 | static const struct palmas_clks_of_match_data palmas_of_clk32kgaudio = { |
138 | .init = { |
139 | .name = "clk32kgaudio" , |
140 | .ops = &palmas_clks_ops, |
141 | .flags = CLK_IGNORE_UNUSED, |
142 | }, |
143 | .desc = { |
144 | .clk_name = "clk32kgaudio" , |
145 | .control_reg = PALMAS_CLK32KGAUDIO_CTRL, |
146 | .enable_mask = PALMAS_CLK32KG_CTRL_MODE_ACTIVE, |
147 | .sleep_mask = PALMAS_CLK32KG_CTRL_MODE_SLEEP, |
148 | .sleep_reqstr_id = PALMAS_EXTERNAL_REQSTR_ID_CLK32KGAUDIO, |
149 | .delay = 200, |
150 | }, |
151 | }; |
152 | |
153 | static const struct of_device_id palmas_clks_of_match[] = { |
154 | { |
155 | .compatible = "ti,palmas-clk32kg" , |
156 | .data = &palmas_of_clk32kg, |
157 | }, |
158 | { |
159 | .compatible = "ti,palmas-clk32kgaudio" , |
160 | .data = &palmas_of_clk32kgaudio, |
161 | }, |
162 | { }, |
163 | }; |
164 | MODULE_DEVICE_TABLE(of, palmas_clks_of_match); |
165 | |
166 | static void palmas_clks_get_clk_data(struct platform_device *pdev, |
167 | struct palmas_clock_info *cinfo) |
168 | { |
169 | struct device_node *node = pdev->dev.of_node; |
170 | unsigned int prop; |
171 | int ret; |
172 | |
173 | ret = of_property_read_u32(np: node, propname: "ti,external-sleep-control" , |
174 | out_value: &prop); |
175 | if (ret) |
176 | return; |
177 | |
178 | switch (prop) { |
179 | case PALMAS_CLOCK_DT_EXT_CONTROL_ENABLE1: |
180 | prop = PALMAS_EXT_CONTROL_ENABLE1; |
181 | break; |
182 | case PALMAS_CLOCK_DT_EXT_CONTROL_ENABLE2: |
183 | prop = PALMAS_EXT_CONTROL_ENABLE2; |
184 | break; |
185 | case PALMAS_CLOCK_DT_EXT_CONTROL_NSLEEP: |
186 | prop = PALMAS_EXT_CONTROL_NSLEEP; |
187 | break; |
188 | default: |
189 | dev_warn(&pdev->dev, "%pOFn: Invalid ext control option: %u\n" , |
190 | node, prop); |
191 | prop = 0; |
192 | break; |
193 | } |
194 | cinfo->ext_control_pin = prop; |
195 | } |
196 | |
197 | static int palmas_clks_init_configure(struct palmas_clock_info *cinfo) |
198 | { |
199 | int ret; |
200 | |
201 | ret = palmas_update_bits(palmas: cinfo->palmas, PALMAS_RESOURCE_BASE, |
202 | reg: cinfo->clk_desc->control_reg, |
203 | mask: cinfo->clk_desc->sleep_mask, val: 0); |
204 | if (ret < 0) { |
205 | dev_err(cinfo->dev, "Reg 0x%02x update failed, %d\n" , |
206 | cinfo->clk_desc->control_reg, ret); |
207 | return ret; |
208 | } |
209 | |
210 | if (cinfo->ext_control_pin) { |
211 | ret = clk_prepare(clk: cinfo->hw.clk); |
212 | if (ret < 0) { |
213 | dev_err(cinfo->dev, "Clock prep failed, %d\n" , ret); |
214 | return ret; |
215 | } |
216 | |
217 | ret = palmas_ext_control_req_config(palmas: cinfo->palmas, |
218 | ext_control_req_id: cinfo->clk_desc->sleep_reqstr_id, |
219 | ext_ctrl: cinfo->ext_control_pin, enable: true); |
220 | if (ret < 0) { |
221 | dev_err(cinfo->dev, "Ext config for %s failed, %d\n" , |
222 | cinfo->clk_desc->clk_name, ret); |
223 | clk_unprepare(clk: cinfo->hw.clk); |
224 | return ret; |
225 | } |
226 | } |
227 | |
228 | return ret; |
229 | } |
230 | static int palmas_clks_probe(struct platform_device *pdev) |
231 | { |
232 | struct palmas *palmas = dev_get_drvdata(dev: pdev->dev.parent); |
233 | struct device_node *node = pdev->dev.of_node; |
234 | const struct palmas_clks_of_match_data *match_data; |
235 | struct palmas_clock_info *cinfo; |
236 | int ret; |
237 | |
238 | match_data = of_device_get_match_data(dev: &pdev->dev); |
239 | if (!match_data) |
240 | return 1; |
241 | |
242 | cinfo = devm_kzalloc(dev: &pdev->dev, size: sizeof(*cinfo), GFP_KERNEL); |
243 | if (!cinfo) |
244 | return -ENOMEM; |
245 | |
246 | palmas_clks_get_clk_data(pdev, cinfo); |
247 | platform_set_drvdata(pdev, data: cinfo); |
248 | |
249 | cinfo->dev = &pdev->dev; |
250 | cinfo->palmas = palmas; |
251 | |
252 | cinfo->clk_desc = &match_data->desc; |
253 | cinfo->hw.init = &match_data->init; |
254 | ret = devm_clk_hw_register(dev: &pdev->dev, hw: &cinfo->hw); |
255 | if (ret) { |
256 | dev_err(&pdev->dev, "Fail to register clock %s, %d\n" , |
257 | match_data->desc.clk_name, ret); |
258 | return ret; |
259 | } |
260 | |
261 | ret = palmas_clks_init_configure(cinfo); |
262 | if (ret < 0) { |
263 | dev_err(&pdev->dev, "Clock config failed, %d\n" , ret); |
264 | return ret; |
265 | } |
266 | |
267 | ret = of_clk_add_hw_provider(np: node, get: of_clk_hw_simple_get, data: &cinfo->hw); |
268 | if (ret < 0) |
269 | dev_err(&pdev->dev, "Fail to add clock driver, %d\n" , ret); |
270 | return ret; |
271 | } |
272 | |
273 | static void palmas_clks_remove(struct platform_device *pdev) |
274 | { |
275 | of_clk_del_provider(np: pdev->dev.of_node); |
276 | } |
277 | |
278 | static struct platform_driver palmas_clks_driver = { |
279 | .driver = { |
280 | .name = "palmas-clk" , |
281 | .of_match_table = palmas_clks_of_match, |
282 | }, |
283 | .probe = palmas_clks_probe, |
284 | .remove_new = palmas_clks_remove, |
285 | }; |
286 | |
287 | module_platform_driver(palmas_clks_driver); |
288 | |
289 | MODULE_DESCRIPTION("Clock driver for Palmas Series Devices" ); |
290 | MODULE_ALIAS("platform:palmas-clk" ); |
291 | MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@ti.com>" ); |
292 | MODULE_LICENSE("GPL v2" ); |
293 | |