aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMarek Szyprowski <m.szyprowski@samsung.com>2018-03-06 09:33:08 -0500
committerSylwester Nawrocki <s.nawrocki@samsung.com>2018-03-06 11:38:46 -0500
commitb06a532bf1fa99af0d9364e5dfb8654fa78d490b (patch)
tree3576eb91b9e2a042095c82dbc34e134e7e95aa1c
parent9fb1636c213541fbb3cd0a4491891bd5c5399406 (diff)
clk: samsung: Add Exynos5 sub-CMU clock driver
Exynos5250/5420/5800 have only one clock controller, but some of their clock depends on respective power domains. Handling integration of clock controller and power domain can be done using runtime PM feature of CCF framework. This however needs a separate struct device for each power domain. This patch adds such separate driver for a group of such clocks, which can be instantiated more than once, each time for a different power domain. Signed-off-by: Marek Szyprowski <m.szyprowski@samsung.com> Reviewed-by: Krzysztof Kozlowski <krzk@kernel.org> Signed-off-by: Sylwester Nawrocki <s.nawrocki@samsung.com>
-rw-r--r--drivers/clk/samsung/clk-exynos5-subcmu.c186
-rw-r--r--drivers/clk/samsung/clk-exynos5-subcmu.h26
2 files changed, 212 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..ac3983c8adf2
--- /dev/null
+++ b/drivers/clk/samsung/clk-exynos5-subcmu.c
@@ -0,0 +1,186 @@
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
15static struct samsung_clk_provider *ctx;
16static const struct exynos5_subcmu_info *cmu;
17static int nr_cmus;
18
19static 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
30static 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
39static 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 */
57void 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
72static 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
85static 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
98static 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
117static 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
124static 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
133static 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
150static 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
167static const struct of_device_id exynos5_clk_of_match[] = {
168 { },
169};
170
171static struct platform_driver exynos5_clk_driver __refdata = {
172 .driver = {
173 .name = "exynos5-clock",
174 .of_match_table = exynos5_clk_of_match,
175 .suppress_bind_attrs = true,
176 },
177 .probe = exynos5_clk_probe,
178};
179
180static int __init exynos5_clk_drv_init(void)
181{
182 platform_driver_register(&exynos5_clk_driver);
183 platform_driver_register(&exynos5_subcmu_driver);
184 return 0;
185}
186core_initcall(exynos5_clk_drv_init);
diff --git a/drivers/clk/samsung/clk-exynos5-subcmu.h b/drivers/clk/samsung/clk-exynos5-subcmu.h
new file mode 100644
index 000000000000..755ee8aaa3de
--- /dev/null
+++ b/drivers/clk/samsung/clk-exynos5-subcmu.h
@@ -0,0 +1,26 @@
1/* SPDX-License-Identifier: GPL-2.0 */
2
3#ifndef __CLK_EXYNOS5_SUBCMU_H
4#define __CLK_EXYNOS5_SUBCMU_H
5
6struct exynos5_subcmu_reg_dump {
7 u32 offset;
8 u32 value;
9 u32 mask;
10 u32 save;
11};
12
13struct exynos5_subcmu_info {
14 const struct samsung_div_clock *div_clks;
15 unsigned int nr_div_clks;
16 const struct samsung_gate_clock *gate_clks;
17 unsigned int nr_gate_clks;
18 struct exynos5_subcmu_reg_dump *suspend_regs;
19 unsigned int nr_suspend_regs;
20 const char *pd_name;
21};
22
23void exynos5_subcmus_init(struct samsung_clk_provider *ctx, int nr_cmus,
24 const struct exynos5_subcmu_info *cmu);
25
26#endif