1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | // |
3 | // clk-max77686.c - Clock driver for Maxim 77686/MAX77802 |
4 | // |
5 | // Copyright (C) 2012 Samsung Electornics |
6 | // Jonghwa Lee <jonghwa3.lee@samsung.com> |
7 | |
8 | #include <linux/kernel.h> |
9 | #include <linux/slab.h> |
10 | #include <linux/err.h> |
11 | #include <linux/module.h> |
12 | #include <linux/platform_device.h> |
13 | #include <linux/mfd/max77620.h> |
14 | #include <linux/mfd/max77686.h> |
15 | #include <linux/mfd/max77686-private.h> |
16 | #include <linux/clk-provider.h> |
17 | #include <linux/mutex.h> |
18 | #include <linux/clkdev.h> |
19 | #include <linux/of.h> |
20 | #include <linux/regmap.h> |
21 | |
22 | #include <dt-bindings/clock/maxim,max77686.h> |
23 | #include <dt-bindings/clock/maxim,max77802.h> |
24 | #include <dt-bindings/clock/maxim,max77620.h> |
25 | |
26 | #define MAX77802_CLOCK_LOW_JITTER_SHIFT 0x3 |
27 | |
28 | enum max77686_chip_name { |
29 | CHIP_MAX77686, |
30 | CHIP_MAX77802, |
31 | CHIP_MAX77620, |
32 | }; |
33 | |
34 | struct max77686_hw_clk_info { |
35 | const char *name; |
36 | u32 clk_reg; |
37 | u32 clk_enable_mask; |
38 | u32 flags; |
39 | }; |
40 | |
41 | struct max77686_clk_init_data { |
42 | struct regmap *regmap; |
43 | struct clk_hw hw; |
44 | struct clk_init_data clk_idata; |
45 | const struct max77686_hw_clk_info *clk_info; |
46 | }; |
47 | |
48 | struct max77686_clk_driver_data { |
49 | enum max77686_chip_name chip; |
50 | struct max77686_clk_init_data *max_clk_data; |
51 | size_t num_clks; |
52 | }; |
53 | |
54 | static const struct |
55 | max77686_hw_clk_info max77686_hw_clks_info[MAX77686_CLKS_NUM] = { |
56 | [MAX77686_CLK_AP] = { |
57 | .name = "32khz_ap" , |
58 | .clk_reg = MAX77686_REG_32KHZ, |
59 | .clk_enable_mask = BIT(MAX77686_CLK_AP), |
60 | }, |
61 | [MAX77686_CLK_CP] = { |
62 | .name = "32khz_cp" , |
63 | .clk_reg = MAX77686_REG_32KHZ, |
64 | .clk_enable_mask = BIT(MAX77686_CLK_CP), |
65 | }, |
66 | [MAX77686_CLK_PMIC] = { |
67 | .name = "32khz_pmic" , |
68 | .clk_reg = MAX77686_REG_32KHZ, |
69 | .clk_enable_mask = BIT(MAX77686_CLK_PMIC), |
70 | }, |
71 | }; |
72 | |
73 | static const struct |
74 | max77686_hw_clk_info max77802_hw_clks_info[MAX77802_CLKS_NUM] = { |
75 | [MAX77802_CLK_32K_AP] = { |
76 | .name = "32khz_ap" , |
77 | .clk_reg = MAX77802_REG_32KHZ, |
78 | .clk_enable_mask = BIT(MAX77802_CLK_32K_AP), |
79 | }, |
80 | [MAX77802_CLK_32K_CP] = { |
81 | .name = "32khz_cp" , |
82 | .clk_reg = MAX77802_REG_32KHZ, |
83 | .clk_enable_mask = BIT(MAX77802_CLK_32K_CP), |
84 | }, |
85 | }; |
86 | |
87 | static const struct |
88 | max77686_hw_clk_info max77620_hw_clks_info[MAX77620_CLKS_NUM] = { |
89 | [MAX77620_CLK_32K_OUT0] = { |
90 | .name = "32khz_out0" , |
91 | .clk_reg = MAX77620_REG_CNFG1_32K, |
92 | .clk_enable_mask = MAX77620_CNFG1_32K_OUT0_EN, |
93 | }, |
94 | }; |
95 | |
96 | static struct max77686_clk_init_data *to_max77686_clk_init_data( |
97 | struct clk_hw *hw) |
98 | { |
99 | return container_of(hw, struct max77686_clk_init_data, hw); |
100 | } |
101 | |
102 | static int max77686_clk_prepare(struct clk_hw *hw) |
103 | { |
104 | struct max77686_clk_init_data *max77686 = to_max77686_clk_init_data(hw); |
105 | |
106 | return regmap_update_bits(map: max77686->regmap, reg: max77686->clk_info->clk_reg, |
107 | mask: max77686->clk_info->clk_enable_mask, |
108 | val: max77686->clk_info->clk_enable_mask); |
109 | } |
110 | |
111 | static void max77686_clk_unprepare(struct clk_hw *hw) |
112 | { |
113 | struct max77686_clk_init_data *max77686 = to_max77686_clk_init_data(hw); |
114 | |
115 | regmap_update_bits(map: max77686->regmap, reg: max77686->clk_info->clk_reg, |
116 | mask: max77686->clk_info->clk_enable_mask, |
117 | val: ~max77686->clk_info->clk_enable_mask); |
118 | } |
119 | |
120 | static int max77686_clk_is_prepared(struct clk_hw *hw) |
121 | { |
122 | struct max77686_clk_init_data *max77686 = to_max77686_clk_init_data(hw); |
123 | int ret; |
124 | u32 val; |
125 | |
126 | ret = regmap_read(map: max77686->regmap, reg: max77686->clk_info->clk_reg, val: &val); |
127 | |
128 | if (ret < 0) |
129 | return -EINVAL; |
130 | |
131 | return val & max77686->clk_info->clk_enable_mask; |
132 | } |
133 | |
134 | static unsigned long max77686_recalc_rate(struct clk_hw *hw, |
135 | unsigned long parent_rate) |
136 | { |
137 | return 32768; |
138 | } |
139 | |
140 | static const struct clk_ops max77686_clk_ops = { |
141 | .prepare = max77686_clk_prepare, |
142 | .unprepare = max77686_clk_unprepare, |
143 | .is_prepared = max77686_clk_is_prepared, |
144 | .recalc_rate = max77686_recalc_rate, |
145 | }; |
146 | |
147 | static struct clk_hw * |
148 | of_clk_max77686_get(struct of_phandle_args *clkspec, void *data) |
149 | { |
150 | struct max77686_clk_driver_data *drv_data = data; |
151 | unsigned int idx = clkspec->args[0]; |
152 | |
153 | if (idx >= drv_data->num_clks) { |
154 | pr_err("%s: invalid index %u\n" , __func__, idx); |
155 | return ERR_PTR(error: -EINVAL); |
156 | } |
157 | |
158 | return &drv_data->max_clk_data[idx].hw; |
159 | } |
160 | |
161 | static int max77686_clk_probe(struct platform_device *pdev) |
162 | { |
163 | struct device *dev = &pdev->dev; |
164 | struct device *parent = dev->parent; |
165 | const struct platform_device_id *id = platform_get_device_id(pdev); |
166 | struct max77686_clk_driver_data *drv_data; |
167 | const struct max77686_hw_clk_info *hw_clks; |
168 | struct regmap *regmap; |
169 | int i, ret, num_clks; |
170 | |
171 | drv_data = devm_kzalloc(dev, size: sizeof(*drv_data), GFP_KERNEL); |
172 | if (!drv_data) |
173 | return -ENOMEM; |
174 | |
175 | regmap = dev_get_regmap(dev: parent, NULL); |
176 | if (!regmap) { |
177 | dev_err(dev, "Failed to get rtc regmap\n" ); |
178 | return -ENODEV; |
179 | } |
180 | |
181 | drv_data->chip = id->driver_data; |
182 | |
183 | switch (drv_data->chip) { |
184 | case CHIP_MAX77686: |
185 | num_clks = MAX77686_CLKS_NUM; |
186 | hw_clks = max77686_hw_clks_info; |
187 | break; |
188 | |
189 | case CHIP_MAX77802: |
190 | num_clks = MAX77802_CLKS_NUM; |
191 | hw_clks = max77802_hw_clks_info; |
192 | break; |
193 | |
194 | case CHIP_MAX77620: |
195 | num_clks = MAX77620_CLKS_NUM; |
196 | hw_clks = max77620_hw_clks_info; |
197 | break; |
198 | |
199 | default: |
200 | dev_err(dev, "Unknown Chip ID\n" ); |
201 | return -EINVAL; |
202 | } |
203 | |
204 | drv_data->num_clks = num_clks; |
205 | drv_data->max_clk_data = devm_kcalloc(dev, n: num_clks, |
206 | size: sizeof(*drv_data->max_clk_data), |
207 | GFP_KERNEL); |
208 | if (!drv_data->max_clk_data) |
209 | return -ENOMEM; |
210 | |
211 | for (i = 0; i < num_clks; i++) { |
212 | struct max77686_clk_init_data *max_clk_data; |
213 | const char *clk_name; |
214 | |
215 | max_clk_data = &drv_data->max_clk_data[i]; |
216 | |
217 | max_clk_data->regmap = regmap; |
218 | max_clk_data->clk_info = &hw_clks[i]; |
219 | max_clk_data->clk_idata.flags = hw_clks[i].flags; |
220 | max_clk_data->clk_idata.ops = &max77686_clk_ops; |
221 | |
222 | if (parent->of_node && |
223 | !of_property_read_string_index(np: parent->of_node, |
224 | propname: "clock-output-names" , |
225 | index: i, output: &clk_name)) |
226 | max_clk_data->clk_idata.name = clk_name; |
227 | else |
228 | max_clk_data->clk_idata.name = hw_clks[i].name; |
229 | |
230 | max_clk_data->hw.init = &max_clk_data->clk_idata; |
231 | |
232 | ret = devm_clk_hw_register(dev, hw: &max_clk_data->hw); |
233 | if (ret) { |
234 | dev_err(dev, "Failed to clock register: %d\n" , ret); |
235 | return ret; |
236 | } |
237 | |
238 | ret = devm_clk_hw_register_clkdev(dev, hw: &max_clk_data->hw, |
239 | con_id: max_clk_data->clk_idata.name, |
240 | NULL); |
241 | if (ret < 0) { |
242 | dev_err(dev, "Failed to clkdev register: %d\n" , ret); |
243 | return ret; |
244 | } |
245 | } |
246 | |
247 | if (parent->of_node) { |
248 | ret = devm_of_clk_add_hw_provider(dev, get: of_clk_max77686_get, |
249 | data: drv_data); |
250 | |
251 | if (ret < 0) { |
252 | dev_err(dev, "Failed to register OF clock provider: %d\n" , |
253 | ret); |
254 | return ret; |
255 | } |
256 | } |
257 | |
258 | /* MAX77802: Enable low-jitter mode on the 32khz clocks. */ |
259 | if (drv_data->chip == CHIP_MAX77802) { |
260 | ret = regmap_update_bits(map: regmap, reg: MAX77802_REG_32KHZ, |
261 | mask: 1 << MAX77802_CLOCK_LOW_JITTER_SHIFT, |
262 | val: 1 << MAX77802_CLOCK_LOW_JITTER_SHIFT); |
263 | if (ret < 0) { |
264 | dev_err(dev, "Failed to config low-jitter: %d\n" , ret); |
265 | return ret; |
266 | } |
267 | } |
268 | |
269 | return 0; |
270 | } |
271 | |
272 | static const struct platform_device_id max77686_clk_id[] = { |
273 | { "max77686-clk" , .driver_data = CHIP_MAX77686, }, |
274 | { "max77802-clk" , .driver_data = CHIP_MAX77802, }, |
275 | { "max77620-clock" , .driver_data = CHIP_MAX77620, }, |
276 | {}, |
277 | }; |
278 | MODULE_DEVICE_TABLE(platform, max77686_clk_id); |
279 | |
280 | static struct platform_driver max77686_clk_driver = { |
281 | .driver = { |
282 | .name = "max77686-clk" , |
283 | }, |
284 | .probe = max77686_clk_probe, |
285 | .id_table = max77686_clk_id, |
286 | }; |
287 | |
288 | module_platform_driver(max77686_clk_driver); |
289 | |
290 | MODULE_DESCRIPTION("MAXIM 77686 Clock Driver" ); |
291 | MODULE_AUTHOR("Jonghwa Lee <jonghwa3.lee@samsung.com>" ); |
292 | MODULE_LICENSE("GPL" ); |
293 | |