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