1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright 2019 NXP. |
4 | */ |
5 | |
6 | #include <linux/clk.h> |
7 | #include <linux/of.h> |
8 | #include <linux/of_graph.h> |
9 | #include <linux/platform_device.h> |
10 | #include <linux/pm_runtime.h> |
11 | #include <linux/slab.h> |
12 | #include <drm/drm_bridge_connector.h> |
13 | #include <drm/drm_device.h> |
14 | #include <drm/drm_modeset_helper.h> |
15 | |
16 | #include "dcss-dev.h" |
17 | #include "dcss-kms.h" |
18 | |
19 | static void dcss_clocks_enable(struct dcss_dev *dcss) |
20 | { |
21 | clk_prepare_enable(clk: dcss->axi_clk); |
22 | clk_prepare_enable(clk: dcss->apb_clk); |
23 | clk_prepare_enable(clk: dcss->rtrm_clk); |
24 | clk_prepare_enable(clk: dcss->dtrc_clk); |
25 | clk_prepare_enable(clk: dcss->pix_clk); |
26 | } |
27 | |
28 | static void dcss_clocks_disable(struct dcss_dev *dcss) |
29 | { |
30 | clk_disable_unprepare(clk: dcss->pix_clk); |
31 | clk_disable_unprepare(clk: dcss->dtrc_clk); |
32 | clk_disable_unprepare(clk: dcss->rtrm_clk); |
33 | clk_disable_unprepare(clk: dcss->apb_clk); |
34 | clk_disable_unprepare(clk: dcss->axi_clk); |
35 | } |
36 | |
37 | static void dcss_disable_dtg_and_ss_cb(void *data) |
38 | { |
39 | struct dcss_dev *dcss = data; |
40 | |
41 | dcss->disable_callback = NULL; |
42 | |
43 | dcss_ss_shutoff(ss: dcss->ss); |
44 | dcss_dtg_shutoff(dtg: dcss->dtg); |
45 | |
46 | complete(&dcss->disable_completion); |
47 | } |
48 | |
49 | void dcss_disable_dtg_and_ss(struct dcss_dev *dcss) |
50 | { |
51 | dcss->disable_callback = dcss_disable_dtg_and_ss_cb; |
52 | } |
53 | |
54 | void dcss_enable_dtg_and_ss(struct dcss_dev *dcss) |
55 | { |
56 | if (dcss->disable_callback) |
57 | dcss->disable_callback = NULL; |
58 | |
59 | dcss_dtg_enable(dtg: dcss->dtg); |
60 | dcss_ss_enable(ss: dcss->ss); |
61 | } |
62 | |
63 | static int dcss_submodules_init(struct dcss_dev *dcss) |
64 | { |
65 | int ret = 0; |
66 | u32 base_addr = dcss->start_addr; |
67 | const struct dcss_type_data *devtype = dcss->devtype; |
68 | |
69 | dcss_clocks_enable(dcss); |
70 | |
71 | ret = dcss_blkctl_init(dcss, blkctl_base: base_addr + devtype->blkctl_ofs); |
72 | if (ret) |
73 | return ret; |
74 | |
75 | ret = dcss_ctxld_init(dcss, ctxld_base: base_addr + devtype->ctxld_ofs); |
76 | if (ret) |
77 | goto ctxld_err; |
78 | |
79 | ret = dcss_dtg_init(dcss, dtg_base: base_addr + devtype->dtg_ofs); |
80 | if (ret) |
81 | goto dtg_err; |
82 | |
83 | ret = dcss_ss_init(dcss, subsam_base: base_addr + devtype->ss_ofs); |
84 | if (ret) |
85 | goto ss_err; |
86 | |
87 | ret = dcss_dpr_init(dcss, dpr_base: base_addr + devtype->dpr_ofs); |
88 | if (ret) |
89 | goto dpr_err; |
90 | |
91 | ret = dcss_scaler_init(dcss, scaler_base: base_addr + devtype->scaler_ofs); |
92 | if (ret) |
93 | goto scaler_err; |
94 | |
95 | dcss_clocks_disable(dcss); |
96 | |
97 | return 0; |
98 | |
99 | scaler_err: |
100 | dcss_dpr_exit(dpr: dcss->dpr); |
101 | |
102 | dpr_err: |
103 | dcss_ss_exit(ss: dcss->ss); |
104 | |
105 | ss_err: |
106 | dcss_dtg_exit(dtg: dcss->dtg); |
107 | |
108 | dtg_err: |
109 | dcss_ctxld_exit(ctxld: dcss->ctxld); |
110 | |
111 | ctxld_err: |
112 | dcss_clocks_disable(dcss); |
113 | |
114 | return ret; |
115 | } |
116 | |
117 | static void dcss_submodules_stop(struct dcss_dev *dcss) |
118 | { |
119 | dcss_clocks_enable(dcss); |
120 | dcss_scaler_exit(scl: dcss->scaler); |
121 | dcss_dpr_exit(dpr: dcss->dpr); |
122 | dcss_ss_exit(ss: dcss->ss); |
123 | dcss_dtg_exit(dtg: dcss->dtg); |
124 | dcss_ctxld_exit(ctxld: dcss->ctxld); |
125 | dcss_clocks_disable(dcss); |
126 | } |
127 | |
128 | static int dcss_clks_init(struct dcss_dev *dcss) |
129 | { |
130 | int i; |
131 | struct { |
132 | const char *id; |
133 | struct clk **clk; |
134 | } clks[] = { |
135 | {"apb" , &dcss->apb_clk}, |
136 | {"axi" , &dcss->axi_clk}, |
137 | {"pix" , &dcss->pix_clk}, |
138 | {"rtrm" , &dcss->rtrm_clk}, |
139 | {"dtrc" , &dcss->dtrc_clk}, |
140 | }; |
141 | |
142 | for (i = 0; i < ARRAY_SIZE(clks); i++) { |
143 | *clks[i].clk = devm_clk_get(dev: dcss->dev, id: clks[i].id); |
144 | if (IS_ERR(ptr: *clks[i].clk)) { |
145 | dev_err(dcss->dev, "failed to get %s clock\n" , |
146 | clks[i].id); |
147 | return PTR_ERR(ptr: *clks[i].clk); |
148 | } |
149 | } |
150 | |
151 | return 0; |
152 | } |
153 | |
154 | static void dcss_clks_release(struct dcss_dev *dcss) |
155 | { |
156 | devm_clk_put(dev: dcss->dev, clk: dcss->dtrc_clk); |
157 | devm_clk_put(dev: dcss->dev, clk: dcss->rtrm_clk); |
158 | devm_clk_put(dev: dcss->dev, clk: dcss->pix_clk); |
159 | devm_clk_put(dev: dcss->dev, clk: dcss->axi_clk); |
160 | devm_clk_put(dev: dcss->dev, clk: dcss->apb_clk); |
161 | } |
162 | |
163 | struct dcss_dev *dcss_dev_create(struct device *dev, bool hdmi_output) |
164 | { |
165 | struct platform_device *pdev = to_platform_device(dev); |
166 | int ret; |
167 | struct resource *res; |
168 | struct dcss_dev *dcss; |
169 | const struct dcss_type_data *devtype; |
170 | |
171 | devtype = of_device_get_match_data(dev); |
172 | if (!devtype) { |
173 | dev_err(dev, "no device match found\n" ); |
174 | return ERR_PTR(error: -ENODEV); |
175 | } |
176 | |
177 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
178 | if (!res) { |
179 | dev_err(dev, "cannot get memory resource\n" ); |
180 | return ERR_PTR(error: -EINVAL); |
181 | } |
182 | |
183 | if (!devm_request_mem_region(dev, res->start, resource_size(res), "dcss" )) { |
184 | dev_err(dev, "cannot request memory region\n" ); |
185 | return ERR_PTR(error: -EBUSY); |
186 | } |
187 | |
188 | dcss = devm_kzalloc(dev, size: sizeof(*dcss), GFP_KERNEL); |
189 | if (!dcss) |
190 | return ERR_PTR(error: -ENOMEM); |
191 | |
192 | dcss->dev = dev; |
193 | dcss->devtype = devtype; |
194 | dcss->hdmi_output = hdmi_output; |
195 | |
196 | ret = dcss_clks_init(dcss); |
197 | if (ret) { |
198 | dev_err(dev, "clocks initialization failed\n" ); |
199 | return ERR_PTR(error: ret); |
200 | } |
201 | |
202 | dcss->of_port = of_graph_get_port_by_id(node: dev->of_node, id: 0); |
203 | if (!dcss->of_port) { |
204 | dev_err(dev, "no port@0 node in %pOF\n" , dev->of_node); |
205 | ret = -ENODEV; |
206 | goto clks_err; |
207 | } |
208 | |
209 | dcss->start_addr = res->start; |
210 | |
211 | ret = dcss_submodules_init(dcss); |
212 | if (ret) { |
213 | of_node_put(node: dcss->of_port); |
214 | dev_err(dev, "submodules initialization failed\n" ); |
215 | goto clks_err; |
216 | } |
217 | |
218 | init_completion(x: &dcss->disable_completion); |
219 | |
220 | pm_runtime_set_autosuspend_delay(dev, delay: 100); |
221 | pm_runtime_use_autosuspend(dev); |
222 | pm_runtime_set_suspended(dev); |
223 | pm_runtime_allow(dev); |
224 | pm_runtime_enable(dev); |
225 | |
226 | return dcss; |
227 | |
228 | clks_err: |
229 | dcss_clks_release(dcss); |
230 | |
231 | return ERR_PTR(error: ret); |
232 | } |
233 | |
234 | void dcss_dev_destroy(struct dcss_dev *dcss) |
235 | { |
236 | if (!pm_runtime_suspended(dev: dcss->dev)) { |
237 | dcss_ctxld_suspend(dcss_ctxld: dcss->ctxld); |
238 | dcss_clocks_disable(dcss); |
239 | } |
240 | |
241 | of_node_put(node: dcss->of_port); |
242 | |
243 | pm_runtime_disable(dev: dcss->dev); |
244 | |
245 | dcss_submodules_stop(dcss); |
246 | |
247 | dcss_clks_release(dcss); |
248 | } |
249 | |
250 | static int dcss_dev_suspend(struct device *dev) |
251 | { |
252 | struct dcss_dev *dcss = dcss_drv_dev_to_dcss(dev); |
253 | struct drm_device *ddev = dcss_drv_dev_to_drm(dev); |
254 | int ret; |
255 | |
256 | drm_mode_config_helper_suspend(dev: ddev); |
257 | |
258 | if (pm_runtime_suspended(dev)) |
259 | return 0; |
260 | |
261 | ret = dcss_ctxld_suspend(dcss_ctxld: dcss->ctxld); |
262 | if (ret) |
263 | return ret; |
264 | |
265 | dcss_clocks_disable(dcss); |
266 | |
267 | return 0; |
268 | } |
269 | |
270 | static int dcss_dev_resume(struct device *dev) |
271 | { |
272 | struct dcss_dev *dcss = dcss_drv_dev_to_dcss(dev); |
273 | struct drm_device *ddev = dcss_drv_dev_to_drm(dev); |
274 | |
275 | if (pm_runtime_suspended(dev)) { |
276 | drm_mode_config_helper_resume(dev: ddev); |
277 | return 0; |
278 | } |
279 | |
280 | dcss_clocks_enable(dcss); |
281 | |
282 | dcss_blkctl_cfg(blkctl: dcss->blkctl); |
283 | |
284 | dcss_ctxld_resume(dcss_ctxld: dcss->ctxld); |
285 | |
286 | drm_mode_config_helper_resume(dev: ddev); |
287 | |
288 | return 0; |
289 | } |
290 | |
291 | static int dcss_dev_runtime_suspend(struct device *dev) |
292 | { |
293 | struct dcss_dev *dcss = dcss_drv_dev_to_dcss(dev); |
294 | int ret; |
295 | |
296 | ret = dcss_ctxld_suspend(dcss_ctxld: dcss->ctxld); |
297 | if (ret) |
298 | return ret; |
299 | |
300 | dcss_clocks_disable(dcss); |
301 | |
302 | return 0; |
303 | } |
304 | |
305 | static int dcss_dev_runtime_resume(struct device *dev) |
306 | { |
307 | struct dcss_dev *dcss = dcss_drv_dev_to_dcss(dev); |
308 | |
309 | dcss_clocks_enable(dcss); |
310 | |
311 | dcss_blkctl_cfg(blkctl: dcss->blkctl); |
312 | |
313 | dcss_ctxld_resume(dcss_ctxld: dcss->ctxld); |
314 | |
315 | return 0; |
316 | } |
317 | |
318 | EXPORT_GPL_DEV_PM_OPS(dcss_dev_pm_ops) = { |
319 | RUNTIME_PM_OPS(dcss_dev_runtime_suspend, dcss_dev_runtime_resume, NULL) |
320 | SYSTEM_SLEEP_PM_OPS(dcss_dev_suspend, dcss_dev_resume) |
321 | }; |
322 | |