diff options
Diffstat (limited to 'drivers/clk/samsung/clk-exynos5-subcmu.c')
-rw-r--r-- | drivers/clk/samsung/clk-exynos5-subcmu.c | 189 |
1 files changed, 189 insertions, 0 deletions
diff --git a/drivers/clk/samsung/clk-exynos5-subcmu.c b/drivers/clk/samsung/clk-exynos5-subcmu.c new file mode 100644 index 000000000000..93306283d764 --- /dev/null +++ b/drivers/clk/samsung/clk-exynos5-subcmu.c | |||
@@ -0,0 +1,189 @@ | |||
1 | // SPDX-License-Identifier: GPL-2.0 | ||
2 | // | ||
3 | // Copyright (c) 2018 Samsung Electronics Co., Ltd. | ||
4 | // Author: Marek Szyprowski <m.szyprowski@samsung.com> | ||
5 | // Common Clock Framework support for Exynos5 power-domain dependent clocks | ||
6 | |||
7 | #include <linux/of_platform.h> | ||
8 | #include <linux/platform_device.h> | ||
9 | #include <linux/pm_domain.h> | ||
10 | #include <linux/pm_runtime.h> | ||
11 | |||
12 | #include "clk.h" | ||
13 | #include "clk-exynos5-subcmu.h" | ||
14 | |||
15 | static struct samsung_clk_provider *ctx; | ||
16 | static const struct exynos5_subcmu_info *cmu; | ||
17 | static int nr_cmus; | ||
18 | |||
19 | static void exynos5_subcmu_clk_save(void __iomem *base, | ||
20 | struct exynos5_subcmu_reg_dump *rd, | ||
21 | unsigned int num_regs) | ||
22 | { | ||
23 | for (; num_regs > 0; --num_regs, ++rd) { | ||
24 | rd->save = readl(base + rd->offset); | ||
25 | writel((rd->save & ~rd->mask) | rd->value, base + rd->offset); | ||
26 | rd->save &= rd->mask; | ||
27 | } | ||
28 | }; | ||
29 | |||
30 | static void exynos5_subcmu_clk_restore(void __iomem *base, | ||
31 | struct exynos5_subcmu_reg_dump *rd, | ||
32 | unsigned int num_regs) | ||
33 | { | ||
34 | for (; num_regs > 0; --num_regs, ++rd) | ||
35 | writel((readl(base + rd->offset) & ~rd->mask) | rd->save, | ||
36 | base + rd->offset); | ||
37 | } | ||
38 | |||
39 | static void exynos5_subcmu_defer_gate(struct samsung_clk_provider *ctx, | ||
40 | const struct samsung_gate_clock *list, int nr_clk) | ||
41 | { | ||
42 | while (nr_clk--) | ||
43 | samsung_clk_add_lookup(ctx, ERR_PTR(-EPROBE_DEFER), list++->id); | ||
44 | } | ||
45 | |||
46 | /* | ||
47 | * Pass the needed clock provider context and register sub-CMU clocks | ||
48 | * | ||
49 | * NOTE: This function has to be called from the main, OF_CLK_DECLARE- | ||
50 | * initialized clock provider driver. This happens very early during boot | ||
51 | * process. Then this driver, during core_initcall registers two platform | ||
52 | * drivers: one which binds to the same device-tree node as OF_CLK_DECLARE | ||
53 | * driver and second, for handling its per-domain child-devices. Those | ||
54 | * platform drivers are bound to their devices a bit later in arch_initcall, | ||
55 | * when OF-core populates all device-tree nodes. | ||
56 | */ | ||
57 | void exynos5_subcmus_init(struct samsung_clk_provider *_ctx, int _nr_cmus, | ||
58 | const struct exynos5_subcmu_info *_cmu) | ||
59 | { | ||
60 | ctx = _ctx; | ||
61 | cmu = _cmu; | ||
62 | nr_cmus = _nr_cmus; | ||
63 | |||
64 | for (; _nr_cmus--; _cmu++) { | ||
65 | exynos5_subcmu_defer_gate(ctx, _cmu->gate_clks, | ||
66 | _cmu->nr_gate_clks); | ||
67 | exynos5_subcmu_clk_save(ctx->reg_base, _cmu->suspend_regs, | ||
68 | _cmu->nr_suspend_regs); | ||
69 | } | ||
70 | } | ||
71 | |||
72 | static int __maybe_unused exynos5_subcmu_suspend(struct device *dev) | ||
73 | { | ||
74 | struct exynos5_subcmu_info *info = dev_get_drvdata(dev); | ||
75 | unsigned long flags; | ||
76 | |||
77 | spin_lock_irqsave(&ctx->lock, flags); | ||
78 | exynos5_subcmu_clk_save(ctx->reg_base, info->suspend_regs, | ||
79 | info->nr_suspend_regs); | ||
80 | spin_unlock_irqrestore(&ctx->lock, flags); | ||
81 | |||
82 | return 0; | ||
83 | } | ||
84 | |||
85 | static int __maybe_unused exynos5_subcmu_resume(struct device *dev) | ||
86 | { | ||
87 | struct exynos5_subcmu_info *info = dev_get_drvdata(dev); | ||
88 | unsigned long flags; | ||
89 | |||
90 | spin_lock_irqsave(&ctx->lock, flags); | ||
91 | exynos5_subcmu_clk_restore(ctx->reg_base, info->suspend_regs, | ||
92 | info->nr_suspend_regs); | ||
93 | spin_unlock_irqrestore(&ctx->lock, flags); | ||
94 | |||
95 | return 0; | ||
96 | } | ||
97 | |||
98 | static int __init exynos5_subcmu_probe(struct platform_device *pdev) | ||
99 | { | ||
100 | struct device *dev = &pdev->dev; | ||
101 | struct exynos5_subcmu_info *info = dev_get_drvdata(dev); | ||
102 | |||
103 | pm_runtime_set_suspended(dev); | ||
104 | pm_runtime_enable(dev); | ||
105 | pm_runtime_get(dev); | ||
106 | |||
107 | ctx->dev = dev; | ||
108 | samsung_clk_register_div(ctx, info->div_clks, info->nr_div_clks); | ||
109 | samsung_clk_register_gate(ctx, info->gate_clks, info->nr_gate_clks); | ||
110 | ctx->dev = NULL; | ||
111 | |||
112 | pm_runtime_put_sync(dev); | ||
113 | |||
114 | return 0; | ||
115 | } | ||
116 | |||
117 | static const struct dev_pm_ops exynos5_subcmu_pm_ops = { | ||
118 | SET_RUNTIME_PM_OPS(exynos5_subcmu_suspend, | ||
119 | exynos5_subcmu_resume, NULL) | ||
120 | SET_LATE_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, | ||
121 | pm_runtime_force_resume) | ||
122 | }; | ||
123 | |||
124 | static struct platform_driver exynos5_subcmu_driver __refdata = { | ||
125 | .driver = { | ||
126 | .name = "exynos5-subcmu", | ||
127 | .suppress_bind_attrs = true, | ||
128 | .pm = &exynos5_subcmu_pm_ops, | ||
129 | }, | ||
130 | .probe = exynos5_subcmu_probe, | ||
131 | }; | ||
132 | |||
133 | static int __init exynos5_clk_register_subcmu(struct device *parent, | ||
134 | const struct exynos5_subcmu_info *info, | ||
135 | struct device_node *pd_node) | ||
136 | { | ||
137 | struct of_phandle_args genpdspec = { .np = pd_node }; | ||
138 | struct platform_device *pdev; | ||
139 | |||
140 | pdev = platform_device_alloc(info->pd_name, -1); | ||
141 | pdev->dev.parent = parent; | ||
142 | pdev->driver_override = "exynos5-subcmu"; | ||
143 | platform_set_drvdata(pdev, (void *)info); | ||
144 | of_genpd_add_device(&genpdspec, &pdev->dev); | ||
145 | platform_device_add(pdev); | ||
146 | |||
147 | return 0; | ||
148 | } | ||
149 | |||
150 | static int __init exynos5_clk_probe(struct platform_device *pdev) | ||
151 | { | ||
152 | struct device_node *np; | ||
153 | const char *name; | ||
154 | int i; | ||
155 | |||
156 | for_each_compatible_node(np, NULL, "samsung,exynos4210-pd") { | ||
157 | if (of_property_read_string(np, "label", &name) < 0) | ||
158 | continue; | ||
159 | for (i = 0; i < nr_cmus; i++) | ||
160 | if (strcmp(cmu[i].pd_name, name) == 0) | ||
161 | exynos5_clk_register_subcmu(&pdev->dev, | ||
162 | &cmu[i], np); | ||
163 | } | ||
164 | return 0; | ||
165 | } | ||
166 | |||
167 | static const struct of_device_id exynos5_clk_of_match[] = { | ||
168 | { .compatible = "samsung,exynos5250-clock", }, | ||
169 | { .compatible = "samsung,exynos5420-clock", }, | ||
170 | { .compatible = "samsung,exynos5800-clock", }, | ||
171 | { }, | ||
172 | }; | ||
173 | |||
174 | static struct platform_driver exynos5_clk_driver __refdata = { | ||
175 | .driver = { | ||
176 | .name = "exynos5-clock", | ||
177 | .of_match_table = exynos5_clk_of_match, | ||
178 | .suppress_bind_attrs = true, | ||
179 | }, | ||
180 | .probe = exynos5_clk_probe, | ||
181 | }; | ||
182 | |||
183 | static int __init exynos5_clk_drv_init(void) | ||
184 | { | ||
185 | platform_driver_register(&exynos5_clk_driver); | ||
186 | platform_driver_register(&exynos5_subcmu_driver); | ||
187 | return 0; | ||
188 | } | ||
189 | core_initcall(exynos5_clk_drv_init); | ||