1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (c) 2014 Samsung Electronics Co., Ltd. |
4 | * Author: Tomasz Figa <t.figa@samsung.com> |
5 | * |
6 | * Clock driver for Exynos clock output |
7 | */ |
8 | |
9 | #include <linux/slab.h> |
10 | #include <linux/clk.h> |
11 | #include <linux/clk-provider.h> |
12 | #include <linux/module.h> |
13 | #include <linux/io.h> |
14 | #include <linux/of.h> |
15 | #include <linux/of_address.h> |
16 | #include <linux/platform_device.h> |
17 | #include <linux/pm.h> |
18 | #include <linux/property.h> |
19 | |
20 | #define EXYNOS_CLKOUT_NR_CLKS 1 |
21 | #define EXYNOS_CLKOUT_PARENTS 32 |
22 | |
23 | #define EXYNOS_PMU_DEBUG_REG 0xa00 |
24 | #define EXYNOS_CLKOUT_DISABLE_SHIFT 0 |
25 | #define EXYNOS_CLKOUT_MUX_SHIFT 8 |
26 | #define EXYNOS4_CLKOUT_MUX_MASK 0xf |
27 | #define EXYNOS5_CLKOUT_MUX_MASK 0x1f |
28 | |
29 | struct exynos_clkout { |
30 | struct clk_gate gate; |
31 | struct clk_mux mux; |
32 | spinlock_t slock; |
33 | void __iomem *reg; |
34 | struct device_node *np; |
35 | u32 pmu_debug_save; |
36 | struct clk_hw_onecell_data data; |
37 | }; |
38 | |
39 | struct exynos_clkout_variant { |
40 | u32 mux_mask; |
41 | }; |
42 | |
43 | static const struct exynos_clkout_variant exynos_clkout_exynos4 = { |
44 | .mux_mask = EXYNOS4_CLKOUT_MUX_MASK, |
45 | }; |
46 | |
47 | static const struct exynos_clkout_variant exynos_clkout_exynos5 = { |
48 | .mux_mask = EXYNOS5_CLKOUT_MUX_MASK, |
49 | }; |
50 | |
51 | static const struct of_device_id exynos_clkout_ids[] = { |
52 | { |
53 | .compatible = "samsung,exynos3250-pmu" , |
54 | .data = &exynos_clkout_exynos4, |
55 | }, { |
56 | .compatible = "samsung,exynos4210-pmu" , |
57 | .data = &exynos_clkout_exynos4, |
58 | }, { |
59 | .compatible = "samsung,exynos4212-pmu" , |
60 | .data = &exynos_clkout_exynos4, |
61 | }, { |
62 | .compatible = "samsung,exynos4412-pmu" , |
63 | .data = &exynos_clkout_exynos4, |
64 | }, { |
65 | .compatible = "samsung,exynos5250-pmu" , |
66 | .data = &exynos_clkout_exynos5, |
67 | }, { |
68 | .compatible = "samsung,exynos5410-pmu" , |
69 | .data = &exynos_clkout_exynos5, |
70 | }, { |
71 | .compatible = "samsung,exynos5420-pmu" , |
72 | .data = &exynos_clkout_exynos5, |
73 | }, { |
74 | .compatible = "samsung,exynos5433-pmu" , |
75 | .data = &exynos_clkout_exynos5, |
76 | }, { } |
77 | }; |
78 | MODULE_DEVICE_TABLE(of, exynos_clkout_ids); |
79 | |
80 | /* |
81 | * Device will be instantiated as child of PMU device without its own |
82 | * device node. Therefore match compatibles against parent. |
83 | */ |
84 | static int exynos_clkout_match_parent_dev(struct device *dev, u32 *mux_mask) |
85 | { |
86 | const struct exynos_clkout_variant *variant; |
87 | |
88 | if (!dev->parent) { |
89 | dev_err(dev, "not instantiated from MFD\n" ); |
90 | return -EINVAL; |
91 | } |
92 | |
93 | variant = device_get_match_data(dev: dev->parent); |
94 | if (!variant) { |
95 | dev_err(dev, "cannot match parent device\n" ); |
96 | return -EINVAL; |
97 | } |
98 | |
99 | *mux_mask = variant->mux_mask; |
100 | |
101 | return 0; |
102 | } |
103 | |
104 | static int exynos_clkout_probe(struct platform_device *pdev) |
105 | { |
106 | const char *parent_names[EXYNOS_CLKOUT_PARENTS]; |
107 | struct clk *parents[EXYNOS_CLKOUT_PARENTS]; |
108 | struct exynos_clkout *clkout; |
109 | int parent_count, ret, i; |
110 | u32 mux_mask; |
111 | |
112 | clkout = devm_kzalloc(dev: &pdev->dev, |
113 | struct_size(clkout, data.hws, EXYNOS_CLKOUT_NR_CLKS), |
114 | GFP_KERNEL); |
115 | if (!clkout) |
116 | return -ENOMEM; |
117 | |
118 | ret = exynos_clkout_match_parent_dev(dev: &pdev->dev, mux_mask: &mux_mask); |
119 | if (ret) |
120 | return ret; |
121 | |
122 | clkout->np = pdev->dev.of_node; |
123 | if (!clkout->np) { |
124 | /* |
125 | * pdev->dev.parent was checked by exynos_clkout_match_parent_dev() |
126 | * so it is not NULL. |
127 | */ |
128 | clkout->np = pdev->dev.parent->of_node; |
129 | } |
130 | |
131 | platform_set_drvdata(pdev, data: clkout); |
132 | |
133 | spin_lock_init(&clkout->slock); |
134 | |
135 | parent_count = 0; |
136 | for (i = 0; i < EXYNOS_CLKOUT_PARENTS; ++i) { |
137 | char name[] = "clkoutXX" ; |
138 | |
139 | snprintf(buf: name, size: sizeof(name), fmt: "clkout%d" , i); |
140 | parents[i] = of_clk_get_by_name(np: clkout->np, name); |
141 | if (IS_ERR(ptr: parents[i])) { |
142 | parent_names[i] = "none" ; |
143 | continue; |
144 | } |
145 | |
146 | parent_names[i] = __clk_get_name(clk: parents[i]); |
147 | parent_count = i + 1; |
148 | } |
149 | |
150 | if (!parent_count) |
151 | return -EINVAL; |
152 | |
153 | clkout->reg = of_iomap(node: clkout->np, index: 0); |
154 | if (!clkout->reg) { |
155 | ret = -ENODEV; |
156 | goto clks_put; |
157 | } |
158 | |
159 | clkout->gate.reg = clkout->reg + EXYNOS_PMU_DEBUG_REG; |
160 | clkout->gate.bit_idx = EXYNOS_CLKOUT_DISABLE_SHIFT; |
161 | clkout->gate.flags = CLK_GATE_SET_TO_DISABLE; |
162 | clkout->gate.lock = &clkout->slock; |
163 | |
164 | clkout->mux.reg = clkout->reg + EXYNOS_PMU_DEBUG_REG; |
165 | clkout->mux.mask = mux_mask; |
166 | clkout->mux.shift = EXYNOS_CLKOUT_MUX_SHIFT; |
167 | clkout->mux.lock = &clkout->slock; |
168 | |
169 | clkout->data.hws[0] = clk_hw_register_composite(NULL, name: "clkout" , |
170 | parent_names, num_parents: parent_count, mux_hw: &clkout->mux.hw, |
171 | mux_ops: &clk_mux_ops, NULL, NULL, gate_hw: &clkout->gate.hw, |
172 | gate_ops: &clk_gate_ops, CLK_SET_RATE_PARENT |
173 | | CLK_SET_RATE_NO_REPARENT); |
174 | if (IS_ERR(ptr: clkout->data.hws[0])) { |
175 | ret = PTR_ERR(ptr: clkout->data.hws[0]); |
176 | goto err_unmap; |
177 | } |
178 | |
179 | clkout->data.num = EXYNOS_CLKOUT_NR_CLKS; |
180 | ret = of_clk_add_hw_provider(np: clkout->np, get: of_clk_hw_onecell_get, data: &clkout->data); |
181 | if (ret) |
182 | goto err_clk_unreg; |
183 | |
184 | return 0; |
185 | |
186 | err_clk_unreg: |
187 | clk_hw_unregister(hw: clkout->data.hws[0]); |
188 | err_unmap: |
189 | iounmap(addr: clkout->reg); |
190 | clks_put: |
191 | for (i = 0; i < EXYNOS_CLKOUT_PARENTS; ++i) |
192 | if (!IS_ERR(ptr: parents[i])) |
193 | clk_put(clk: parents[i]); |
194 | |
195 | dev_err(&pdev->dev, "failed to register clkout clock\n" ); |
196 | |
197 | return ret; |
198 | } |
199 | |
200 | static void exynos_clkout_remove(struct platform_device *pdev) |
201 | { |
202 | struct exynos_clkout *clkout = platform_get_drvdata(pdev); |
203 | |
204 | of_clk_del_provider(np: clkout->np); |
205 | clk_hw_unregister(hw: clkout->data.hws[0]); |
206 | iounmap(addr: clkout->reg); |
207 | } |
208 | |
209 | static int __maybe_unused exynos_clkout_suspend(struct device *dev) |
210 | { |
211 | struct exynos_clkout *clkout = dev_get_drvdata(dev); |
212 | |
213 | clkout->pmu_debug_save = readl(addr: clkout->reg + EXYNOS_PMU_DEBUG_REG); |
214 | |
215 | return 0; |
216 | } |
217 | |
218 | static int __maybe_unused exynos_clkout_resume(struct device *dev) |
219 | { |
220 | struct exynos_clkout *clkout = dev_get_drvdata(dev); |
221 | |
222 | writel(val: clkout->pmu_debug_save, addr: clkout->reg + EXYNOS_PMU_DEBUG_REG); |
223 | |
224 | return 0; |
225 | } |
226 | |
227 | static SIMPLE_DEV_PM_OPS(exynos_clkout_pm_ops, exynos_clkout_suspend, |
228 | exynos_clkout_resume); |
229 | |
230 | static struct platform_driver exynos_clkout_driver = { |
231 | .driver = { |
232 | .name = "exynos-clkout" , |
233 | .of_match_table = exynos_clkout_ids, |
234 | .pm = &exynos_clkout_pm_ops, |
235 | }, |
236 | .probe = exynos_clkout_probe, |
237 | .remove_new = exynos_clkout_remove, |
238 | }; |
239 | module_platform_driver(exynos_clkout_driver); |
240 | |
241 | MODULE_AUTHOR("Krzysztof Kozlowski <krzk@kernel.org>" ); |
242 | MODULE_AUTHOR("Tomasz Figa <tomasz.figa@gmail.com>" ); |
243 | MODULE_DESCRIPTION("Samsung Exynos clock output driver" ); |
244 | MODULE_LICENSE("GPL" ); |
245 | |