Commit | Line | Data |
---|---|---|
f793d1e5 GU |
1 | /* |
2 | * Renesas Clock Pulse Generator / Module Standby and Software Reset | |
3 | * | |
4 | * Copyright (C) 2015 Glider bvba | |
5 | * | |
6 | * Based on clk-mstp.c, clk-rcar-gen2.c, and clk-rcar-gen3.c | |
7 | * | |
8 | * Copyright (C) 2013 Ideas On Board SPRL | |
9 | * Copyright (C) 2015 Renesas Electronics Corp. | |
10 | * | |
11 | * This program is free software; you can redistribute it and/or modify | |
12 | * it under the terms of the GNU General Public License as published by | |
13 | * the Free Software Foundation; version 2 of the License. | |
14 | */ | |
15 | ||
16 | #include <linux/clk.h> | |
17 | #include <linux/clk-provider.h> | |
18 | #include <linux/device.h> | |
19 | #include <linux/init.h> | |
20 | #include <linux/mod_devicetable.h> | |
21 | #include <linux/module.h> | |
22 | #include <linux/of_address.h> | |
23 | #include <linux/of_device.h> | |
24 | #include <linux/platform_device.h> | |
25 | #include <linux/pm_clock.h> | |
26 | #include <linux/pm_domain.h> | |
27 | #include <linux/slab.h> | |
28 | ||
29 | #include <dt-bindings/clock/renesas-cpg-mssr.h> | |
30 | ||
31 | #include "renesas-cpg-mssr.h" | |
32 | #include "clk-div6.h" | |
33 | ||
34 | #ifdef DEBUG | |
35 | #define WARN_DEBUG(x) do { } while (0) | |
36 | #else | |
37 | #define WARN_DEBUG(x) WARN_ON(x) | |
38 | #endif | |
39 | ||
40 | ||
41 | /* | |
42 | * Module Standby and Software Reset register offets. | |
43 | * | |
44 | * If the registers exist, these are valid for SH-Mobile, R-Mobile, | |
45 | * R-Car Gen 2, and R-Car Gen 3. | |
46 | * These are NOT valid for R-Car Gen1 and RZ/A1! | |
47 | */ | |
48 | ||
49 | /* | |
50 | * Module Stop Status Register offsets | |
51 | */ | |
52 | ||
53 | static const u16 mstpsr[] = { | |
54 | 0x030, 0x038, 0x040, 0x048, 0x04C, 0x03C, 0x1C0, 0x1C4, | |
55 | 0x9A0, 0x9A4, 0x9A8, 0x9AC, | |
56 | }; | |
57 | ||
58 | #define MSTPSR(i) mstpsr[i] | |
59 | ||
60 | ||
61 | /* | |
62 | * System Module Stop Control Register offsets | |
63 | */ | |
64 | ||
65 | static const u16 smstpcr[] = { | |
66 | 0x130, 0x134, 0x138, 0x13C, 0x140, 0x144, 0x148, 0x14C, | |
67 | 0x990, 0x994, 0x998, 0x99C, | |
68 | }; | |
69 | ||
70 | #define SMSTPCR(i) smstpcr[i] | |
71 | ||
72 | ||
73 | /* | |
74 | * Software Reset Register offsets | |
75 | */ | |
76 | ||
77 | static const u16 srcr[] = { | |
78 | 0x0A0, 0x0A8, 0x0B0, 0x0B8, 0x0BC, 0x0C4, 0x1C8, 0x1CC, | |
79 | 0x920, 0x924, 0x928, 0x92C, | |
80 | }; | |
81 | ||
82 | #define SRCR(i) srcr[i] | |
83 | ||
84 | ||
85 | /* Realtime Module Stop Control Register offsets */ | |
86 | #define RMSTPCR(i) (smstpcr[i] - 0x20) | |
87 | ||
88 | /* Modem Module Stop Control Register offsets (r8a73a4) */ | |
89 | #define MMSTPCR(i) (smstpcr[i] + 0x20) | |
90 | ||
91 | /* Software Reset Clearing Register offsets */ | |
92 | #define SRSTCLR(i) (0x940 + (i) * 4) | |
93 | ||
94 | ||
95 | /** | |
96 | * Clock Pulse Generator / Module Standby and Software Reset Private Data | |
97 | * | |
98 | * @dev: CPG/MSSR device | |
99 | * @base: CPG/MSSR register block base address | |
100 | * @mstp_lock: protects writes to SMSTPCR | |
101 | * @clks: Array containing all Core and Module Clocks | |
102 | * @num_core_clks: Number of Core Clocks in clks[] | |
103 | * @num_mod_clks: Number of Module Clocks in clks[] | |
104 | * @last_dt_core_clk: ID of the last Core Clock exported to DT | |
105 | */ | |
106 | struct cpg_mssr_priv { | |
107 | struct device *dev; | |
108 | void __iomem *base; | |
109 | spinlock_t mstp_lock; | |
110 | ||
111 | struct clk **clks; | |
112 | unsigned int num_core_clks; | |
113 | unsigned int num_mod_clks; | |
114 | unsigned int last_dt_core_clk; | |
115 | }; | |
116 | ||
117 | ||
118 | /** | |
119 | * struct mstp_clock - MSTP gating clock | |
120 | * @hw: handle between common and hardware-specific interfaces | |
121 | * @index: MSTP clock number | |
122 | * @priv: CPG/MSSR private data | |
123 | */ | |
124 | struct mstp_clock { | |
125 | struct clk_hw hw; | |
126 | u32 index; | |
127 | struct cpg_mssr_priv *priv; | |
128 | }; | |
129 | ||
130 | #define to_mstp_clock(_hw) container_of(_hw, struct mstp_clock, hw) | |
131 | ||
132 | static int cpg_mstp_clock_endisable(struct clk_hw *hw, bool enable) | |
133 | { | |
134 | struct mstp_clock *clock = to_mstp_clock(hw); | |
135 | struct cpg_mssr_priv *priv = clock->priv; | |
136 | unsigned int reg = clock->index / 32; | |
137 | unsigned int bit = clock->index % 32; | |
138 | struct device *dev = priv->dev; | |
139 | u32 bitmask = BIT(bit); | |
140 | unsigned long flags; | |
141 | unsigned int i; | |
142 | u32 value; | |
143 | ||
144 | dev_dbg(dev, "MSTP %u%02u/%pC %s\n", reg, bit, hw->clk, | |
145 | enable ? "ON" : "OFF"); | |
146 | spin_lock_irqsave(&priv->mstp_lock, flags); | |
147 | ||
148 | value = clk_readl(priv->base + SMSTPCR(reg)); | |
149 | if (enable) | |
150 | value &= ~bitmask; | |
151 | else | |
152 | value |= bitmask; | |
153 | clk_writel(value, priv->base + SMSTPCR(reg)); | |
154 | ||
155 | spin_unlock_irqrestore(&priv->mstp_lock, flags); | |
156 | ||
157 | if (!enable) | |
158 | return 0; | |
159 | ||
160 | for (i = 1000; i > 0; --i) { | |
161 | if (!(clk_readl(priv->base + MSTPSR(reg)) & | |
162 | bitmask)) | |
163 | break; | |
164 | cpu_relax(); | |
165 | } | |
166 | ||
167 | if (!i) { | |
168 | dev_err(dev, "Failed to enable SMSTP %p[%d]\n", | |
169 | priv->base + SMSTPCR(reg), bit); | |
170 | return -ETIMEDOUT; | |
171 | } | |
172 | ||
173 | return 0; | |
174 | } | |
175 | ||
176 | static int cpg_mstp_clock_enable(struct clk_hw *hw) | |
177 | { | |
178 | return cpg_mstp_clock_endisable(hw, true); | |
179 | } | |
180 | ||
181 | static void cpg_mstp_clock_disable(struct clk_hw *hw) | |
182 | { | |
183 | cpg_mstp_clock_endisable(hw, false); | |
184 | } | |
185 | ||
186 | static int cpg_mstp_clock_is_enabled(struct clk_hw *hw) | |
187 | { | |
188 | struct mstp_clock *clock = to_mstp_clock(hw); | |
189 | struct cpg_mssr_priv *priv = clock->priv; | |
190 | u32 value; | |
191 | ||
192 | value = clk_readl(priv->base + MSTPSR(clock->index / 32)); | |
193 | ||
194 | return !(value & BIT(clock->index % 32)); | |
195 | } | |
196 | ||
197 | static const struct clk_ops cpg_mstp_clock_ops = { | |
198 | .enable = cpg_mstp_clock_enable, | |
199 | .disable = cpg_mstp_clock_disable, | |
200 | .is_enabled = cpg_mstp_clock_is_enabled, | |
201 | }; | |
202 | ||
203 | static | |
204 | struct clk *cpg_mssr_clk_src_twocell_get(struct of_phandle_args *clkspec, | |
205 | void *data) | |
206 | { | |
207 | unsigned int clkidx = clkspec->args[1]; | |
208 | struct cpg_mssr_priv *priv = data; | |
209 | struct device *dev = priv->dev; | |
210 | unsigned int idx; | |
211 | const char *type; | |
212 | struct clk *clk; | |
213 | ||
214 | switch (clkspec->args[0]) { | |
215 | case CPG_CORE: | |
216 | type = "core"; | |
217 | if (clkidx > priv->last_dt_core_clk) { | |
218 | dev_err(dev, "Invalid %s clock index %u\n", type, | |
219 | clkidx); | |
220 | return ERR_PTR(-EINVAL); | |
221 | } | |
222 | clk = priv->clks[clkidx]; | |
223 | break; | |
224 | ||
225 | case CPG_MOD: | |
226 | type = "module"; | |
227 | idx = MOD_CLK_PACK(clkidx); | |
228 | if (clkidx % 100 > 31 || idx >= priv->num_mod_clks) { | |
229 | dev_err(dev, "Invalid %s clock index %u\n", type, | |
230 | clkidx); | |
231 | return ERR_PTR(-EINVAL); | |
232 | } | |
233 | clk = priv->clks[priv->num_core_clks + idx]; | |
234 | break; | |
235 | ||
236 | default: | |
237 | dev_err(dev, "Invalid CPG clock type %u\n", clkspec->args[0]); | |
238 | return ERR_PTR(-EINVAL); | |
239 | } | |
240 | ||
241 | if (IS_ERR(clk)) | |
242 | dev_err(dev, "Cannot get %s clock %u: %ld", type, clkidx, | |
243 | PTR_ERR(clk)); | |
244 | else | |
245 | dev_dbg(dev, "clock (%u, %u) is %pC at %pCr Hz\n", | |
246 | clkspec->args[0], clkspec->args[1], clk, clk); | |
247 | return clk; | |
248 | } | |
249 | ||
250 | static void __init cpg_mssr_register_core_clk(const struct cpg_core_clk *core, | |
251 | const struct cpg_mssr_info *info, | |
252 | struct cpg_mssr_priv *priv) | |
253 | { | |
254 | struct clk *clk = NULL, *parent; | |
255 | struct device *dev = priv->dev; | |
256 | unsigned int id = core->id; | |
257 | const char *parent_name; | |
258 | ||
259 | WARN_DEBUG(id >= priv->num_core_clks); | |
260 | WARN_DEBUG(PTR_ERR(priv->clks[id]) != -ENOENT); | |
261 | ||
262 | switch (core->type) { | |
263 | case CLK_TYPE_IN: | |
264 | clk = of_clk_get_by_name(priv->dev->of_node, core->name); | |
265 | break; | |
266 | ||
267 | case CLK_TYPE_FF: | |
268 | case CLK_TYPE_DIV6P1: | |
269 | WARN_DEBUG(core->parent >= priv->num_core_clks); | |
270 | parent = priv->clks[core->parent]; | |
271 | if (IS_ERR(parent)) { | |
272 | clk = parent; | |
273 | goto fail; | |
274 | } | |
275 | ||
276 | parent_name = __clk_get_name(parent); | |
277 | if (core->type == CLK_TYPE_FF) { | |
278 | clk = clk_register_fixed_factor(NULL, core->name, | |
279 | parent_name, 0, | |
280 | core->mult, core->div); | |
281 | } else { | |
282 | clk = cpg_div6_register(core->name, 1, &parent_name, | |
283 | priv->base + core->offset); | |
284 | } | |
285 | break; | |
286 | ||
287 | default: | |
288 | if (info->cpg_clk_register) | |
289 | clk = info->cpg_clk_register(dev, core, info, | |
290 | priv->clks, priv->base); | |
291 | else | |
292 | dev_err(dev, "%s has unsupported core clock type %u\n", | |
293 | core->name, core->type); | |
294 | break; | |
295 | } | |
296 | ||
297 | if (IS_ERR_OR_NULL(clk)) | |
298 | goto fail; | |
299 | ||
300 | dev_dbg(dev, "Core clock %pC at %pCr Hz\n", clk, clk); | |
301 | priv->clks[id] = clk; | |
302 | return; | |
303 | ||
304 | fail: | |
305 | dev_err(dev, "Failed to register %s clock %s: %ld\n", "core,", | |
306 | core->name, PTR_ERR(clk)); | |
307 | } | |
308 | ||
309 | static void __init cpg_mssr_register_mod_clk(const struct mssr_mod_clk *mod, | |
310 | const struct cpg_mssr_info *info, | |
311 | struct cpg_mssr_priv *priv) | |
312 | { | |
313 | struct mstp_clock *clock = NULL; | |
314 | struct device *dev = priv->dev; | |
315 | unsigned int id = mod->id; | |
316 | struct clk_init_data init; | |
317 | struct clk *parent, *clk; | |
318 | const char *parent_name; | |
319 | unsigned int i; | |
320 | ||
321 | WARN_DEBUG(id < priv->num_core_clks); | |
322 | WARN_DEBUG(id >= priv->num_core_clks + priv->num_mod_clks); | |
323 | WARN_DEBUG(mod->parent >= priv->num_core_clks + priv->num_mod_clks); | |
324 | WARN_DEBUG(PTR_ERR(priv->clks[id]) != -ENOENT); | |
325 | ||
326 | parent = priv->clks[mod->parent]; | |
327 | if (IS_ERR(parent)) { | |
328 | clk = parent; | |
329 | goto fail; | |
330 | } | |
331 | ||
332 | clock = kzalloc(sizeof(*clock), GFP_KERNEL); | |
333 | if (!clock) { | |
334 | clk = ERR_PTR(-ENOMEM); | |
335 | goto fail; | |
336 | } | |
337 | ||
338 | init.name = mod->name; | |
339 | init.ops = &cpg_mstp_clock_ops; | |
340 | init.flags = CLK_IS_BASIC | CLK_SET_RATE_PARENT; | |
341 | for (i = 0; i < info->num_crit_mod_clks; i++) | |
342 | if (id == info->crit_mod_clks[i]) { | |
343 | #ifdef CLK_ENABLE_HAND_OFF | |
344 | dev_dbg(dev, "MSTP %s setting CLK_ENABLE_HAND_OFF\n", | |
345 | mod->name); | |
346 | init.flags |= CLK_ENABLE_HAND_OFF; | |
347 | break; | |
348 | #else | |
349 | dev_dbg(dev, "Ignoring MSTP %s to prevent disabling\n", | |
350 | mod->name); | |
73f3f138 | 351 | kfree(clock); |
f793d1e5 GU |
352 | return; |
353 | #endif | |
354 | } | |
355 | ||
356 | parent_name = __clk_get_name(parent); | |
357 | init.parent_names = &parent_name; | |
358 | init.num_parents = 1; | |
359 | ||
360 | clock->index = id - priv->num_core_clks; | |
361 | clock->priv = priv; | |
362 | clock->hw.init = &init; | |
363 | ||
364 | clk = clk_register(NULL, &clock->hw); | |
365 | if (IS_ERR(clk)) | |
366 | goto fail; | |
367 | ||
368 | dev_dbg(dev, "Module clock %pC at %pCr Hz\n", clk, clk); | |
369 | priv->clks[id] = clk; | |
370 | return; | |
371 | ||
372 | fail: | |
373 | dev_err(dev, "Failed to register %s clock %s: %ld\n", "module,", | |
374 | mod->name, PTR_ERR(clk)); | |
375 | kfree(clock); | |
376 | } | |
377 | ||
378 | ||
379 | #ifdef CONFIG_PM_GENERIC_DOMAINS_OF | |
380 | struct cpg_mssr_clk_domain { | |
381 | struct generic_pm_domain genpd; | |
382 | struct device_node *np; | |
383 | unsigned int num_core_pm_clks; | |
384 | unsigned int core_pm_clks[0]; | |
385 | }; | |
386 | ||
387 | static bool cpg_mssr_is_pm_clk(const struct of_phandle_args *clkspec, | |
388 | struct cpg_mssr_clk_domain *pd) | |
389 | { | |
390 | unsigned int i; | |
391 | ||
392 | if (clkspec->np != pd->np || clkspec->args_count != 2) | |
393 | return false; | |
394 | ||
395 | switch (clkspec->args[0]) { | |
396 | case CPG_CORE: | |
397 | for (i = 0; i < pd->num_core_pm_clks; i++) | |
398 | if (clkspec->args[1] == pd->core_pm_clks[i]) | |
399 | return true; | |
400 | return false; | |
401 | ||
402 | case CPG_MOD: | |
403 | return true; | |
404 | ||
405 | default: | |
406 | return false; | |
407 | } | |
408 | } | |
409 | ||
410 | static int cpg_mssr_attach_dev(struct generic_pm_domain *genpd, | |
411 | struct device *dev) | |
412 | { | |
413 | struct cpg_mssr_clk_domain *pd = | |
414 | container_of(genpd, struct cpg_mssr_clk_domain, genpd); | |
415 | struct device_node *np = dev->of_node; | |
416 | struct of_phandle_args clkspec; | |
417 | struct clk *clk; | |
418 | int i = 0; | |
419 | int error; | |
420 | ||
421 | while (!of_parse_phandle_with_args(np, "clocks", "#clock-cells", i, | |
422 | &clkspec)) { | |
423 | if (cpg_mssr_is_pm_clk(&clkspec, pd)) | |
424 | goto found; | |
425 | ||
426 | of_node_put(clkspec.np); | |
427 | i++; | |
428 | } | |
429 | ||
430 | return 0; | |
431 | ||
432 | found: | |
433 | clk = of_clk_get_from_provider(&clkspec); | |
434 | of_node_put(clkspec.np); | |
435 | ||
436 | if (IS_ERR(clk)) | |
437 | return PTR_ERR(clk); | |
438 | ||
439 | error = pm_clk_create(dev); | |
440 | if (error) { | |
441 | dev_err(dev, "pm_clk_create failed %d\n", error); | |
442 | goto fail_put; | |
443 | } | |
444 | ||
445 | error = pm_clk_add_clk(dev, clk); | |
446 | if (error) { | |
447 | dev_err(dev, "pm_clk_add_clk %pC failed %d\n", clk, error); | |
448 | goto fail_destroy; | |
449 | } | |
450 | ||
451 | return 0; | |
452 | ||
453 | fail_destroy: | |
454 | pm_clk_destroy(dev); | |
455 | fail_put: | |
456 | clk_put(clk); | |
457 | return error; | |
458 | } | |
459 | ||
460 | static void cpg_mssr_detach_dev(struct generic_pm_domain *genpd, | |
461 | struct device *dev) | |
462 | { | |
463 | if (!list_empty(&dev->power.subsys_data->clock_list)) | |
464 | pm_clk_destroy(dev); | |
465 | } | |
466 | ||
467 | static int __init cpg_mssr_add_clk_domain(struct device *dev, | |
468 | const unsigned int *core_pm_clks, | |
469 | unsigned int num_core_pm_clks) | |
470 | { | |
471 | struct device_node *np = dev->of_node; | |
472 | struct generic_pm_domain *genpd; | |
473 | struct cpg_mssr_clk_domain *pd; | |
474 | size_t pm_size = num_core_pm_clks * sizeof(core_pm_clks[0]); | |
475 | ||
476 | pd = devm_kzalloc(dev, sizeof(*pd) + pm_size, GFP_KERNEL); | |
477 | if (!pd) | |
478 | return -ENOMEM; | |
479 | ||
480 | pd->np = np; | |
481 | pd->num_core_pm_clks = num_core_pm_clks; | |
482 | memcpy(pd->core_pm_clks, core_pm_clks, pm_size); | |
483 | ||
484 | genpd = &pd->genpd; | |
485 | genpd->name = np->name; | |
486 | genpd->flags = GENPD_FLAG_PM_CLK; | |
487 | pm_genpd_init(genpd, &simple_qos_governor, false); | |
488 | genpd->attach_dev = cpg_mssr_attach_dev; | |
489 | genpd->detach_dev = cpg_mssr_detach_dev; | |
490 | ||
491 | of_genpd_add_provider_simple(np, genpd); | |
492 | return 0; | |
493 | } | |
494 | #else | |
495 | static inline int cpg_mssr_add_clk_domain(struct device *dev, | |
496 | const unsigned int *core_pm_clks, | |
497 | unsigned int num_core_pm_clks) | |
498 | { | |
499 | return 0; | |
500 | } | |
501 | #endif /* !CONFIG_PM_GENERIC_DOMAINS_OF */ | |
502 | ||
503 | ||
504 | static const struct of_device_id cpg_mssr_match[] = { | |
c5dae0df GU |
505 | #ifdef CONFIG_ARCH_R8A7795 |
506 | { | |
507 | .compatible = "renesas,r8a7795-cpg-mssr", | |
508 | .data = &r8a7795_cpg_mssr_info, | |
509 | }, | |
510 | #endif | |
f793d1e5 GU |
511 | { /* sentinel */ } |
512 | }; | |
513 | ||
514 | static void cpg_mssr_del_clk_provider(void *data) | |
515 | { | |
516 | of_clk_del_provider(data); | |
517 | } | |
518 | ||
519 | static int __init cpg_mssr_probe(struct platform_device *pdev) | |
520 | { | |
521 | struct device *dev = &pdev->dev; | |
522 | struct device_node *np = dev->of_node; | |
523 | const struct cpg_mssr_info *info; | |
524 | struct cpg_mssr_priv *priv; | |
525 | unsigned int nclks, i; | |
526 | struct resource *res; | |
527 | struct clk **clks; | |
528 | int error; | |
529 | ||
530 | info = of_match_node(cpg_mssr_match, np)->data; | |
531 | if (info->init) { | |
532 | error = info->init(dev); | |
533 | if (error) | |
534 | return error; | |
535 | } | |
536 | ||
537 | priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); | |
538 | if (!priv) | |
539 | return -ENOMEM; | |
540 | ||
541 | priv->dev = dev; | |
542 | spin_lock_init(&priv->mstp_lock); | |
543 | ||
544 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
545 | priv->base = devm_ioremap_resource(dev, res); | |
546 | if (IS_ERR(priv->base)) | |
547 | return PTR_ERR(priv->base); | |
548 | ||
549 | nclks = info->num_total_core_clks + info->num_hw_mod_clks; | |
550 | clks = devm_kmalloc_array(dev, nclks, sizeof(*clks), GFP_KERNEL); | |
551 | if (!clks) | |
552 | return -ENOMEM; | |
553 | ||
554 | priv->clks = clks; | |
555 | priv->num_core_clks = info->num_total_core_clks; | |
556 | priv->num_mod_clks = info->num_hw_mod_clks; | |
557 | priv->last_dt_core_clk = info->last_dt_core_clk; | |
558 | ||
559 | for (i = 0; i < nclks; i++) | |
560 | clks[i] = ERR_PTR(-ENOENT); | |
561 | ||
562 | for (i = 0; i < info->num_core_clks; i++) | |
563 | cpg_mssr_register_core_clk(&info->core_clks[i], info, priv); | |
564 | ||
565 | for (i = 0; i < info->num_mod_clks; i++) | |
566 | cpg_mssr_register_mod_clk(&info->mod_clks[i], info, priv); | |
567 | ||
568 | error = of_clk_add_provider(np, cpg_mssr_clk_src_twocell_get, priv); | |
569 | if (error) | |
570 | return error; | |
571 | ||
c7f23180 SM |
572 | error = devm_add_action_or_reset(dev, |
573 | cpg_mssr_del_clk_provider, | |
574 | np); | |
575 | if (error) | |
576 | return error; | |
f793d1e5 GU |
577 | |
578 | error = cpg_mssr_add_clk_domain(dev, info->core_pm_clks, | |
579 | info->num_core_pm_clks); | |
580 | if (error) | |
581 | return error; | |
582 | ||
583 | return 0; | |
584 | } | |
585 | ||
586 | static struct platform_driver cpg_mssr_driver = { | |
587 | .driver = { | |
588 | .name = "renesas-cpg-mssr", | |
589 | .of_match_table = cpg_mssr_match, | |
590 | }, | |
591 | }; | |
592 | ||
593 | static int __init cpg_mssr_init(void) | |
594 | { | |
595 | return platform_driver_probe(&cpg_mssr_driver, cpg_mssr_probe); | |
596 | } | |
597 | ||
598 | subsys_initcall(cpg_mssr_init); | |
599 | ||
600 | MODULE_DESCRIPTION("Renesas CPG/MSSR Driver"); | |
601 | MODULE_LICENSE("GPL v2"); |