1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (c) 2022 MediaTek Inc. |
4 | * Author: Ping-Hsun Wu <ping-hsun.wu@mediatek.com> |
5 | */ |
6 | |
7 | #include <linux/clk.h> |
8 | #include <linux/module.h> |
9 | #include <linux/of_platform.h> |
10 | #include <linux/platform_device.h> |
11 | #include <linux/pm_runtime.h> |
12 | #include <linux/remoteproc.h> |
13 | #include <linux/remoteproc/mtk_scp.h> |
14 | #include <media/videobuf2-dma-contig.h> |
15 | |
16 | #include "mtk-mdp3-core.h" |
17 | #include "mtk-mdp3-cfg.h" |
18 | #include "mtk-mdp3-m2m.h" |
19 | |
20 | static const struct of_device_id mdp_of_ids[] = { |
21 | { .compatible = "mediatek,mt8183-mdp3-rdma" , |
22 | .data = &mt8183_mdp_driver_data, |
23 | }, |
24 | { .compatible = "mediatek,mt8195-mdp3-rdma" , |
25 | .data = &mt8195_mdp_driver_data, |
26 | }, |
27 | { .compatible = "mediatek,mt8195-mdp3-wrot" , |
28 | .data = &mt8195_mdp_driver_data, |
29 | }, |
30 | {}, |
31 | }; |
32 | MODULE_DEVICE_TABLE(of, mdp_of_ids); |
33 | |
34 | static struct platform_device *__get_pdev_by_id(struct platform_device *pdev, |
35 | struct platform_device *from, |
36 | enum mdp_infra_id id) |
37 | { |
38 | struct device_node *node, *f = NULL; |
39 | struct platform_device *mdp_pdev = NULL; |
40 | const struct mtk_mdp_driver_data *mdp_data; |
41 | const char *compat; |
42 | |
43 | if (!pdev) |
44 | return NULL; |
45 | |
46 | if (id < MDP_INFRA_MMSYS || id >= MDP_INFRA_MAX) { |
47 | dev_err(&pdev->dev, "Illegal infra id %d\n" , id); |
48 | return NULL; |
49 | } |
50 | |
51 | mdp_data = of_device_get_match_data(dev: &pdev->dev); |
52 | if (!mdp_data) { |
53 | dev_err(&pdev->dev, "have no driver data to find node\n" ); |
54 | return NULL; |
55 | } |
56 | |
57 | compat = mdp_data->mdp_probe_infra[id].compatible; |
58 | if (strlen(compat) == 0) |
59 | return NULL; |
60 | |
61 | if (from) |
62 | f = from->dev.of_node; |
63 | node = of_find_compatible_node(from: f, NULL, compat); |
64 | if (WARN_ON(!node)) { |
65 | dev_err(&pdev->dev, "find node from id %d failed\n" , id); |
66 | return NULL; |
67 | } |
68 | |
69 | mdp_pdev = of_find_device_by_node(np: node); |
70 | of_node_put(node); |
71 | if (WARN_ON(!mdp_pdev)) { |
72 | dev_err(&pdev->dev, "find pdev from id %d failed\n" , id); |
73 | return NULL; |
74 | } |
75 | |
76 | return mdp_pdev; |
77 | } |
78 | |
79 | struct platform_device *mdp_get_plat_device(struct platform_device *pdev) |
80 | { |
81 | struct device *dev = &pdev->dev; |
82 | struct device_node *mdp_node; |
83 | struct platform_device *mdp_pdev; |
84 | |
85 | mdp_node = of_parse_phandle(np: dev->of_node, MDP_PHANDLE_NAME, index: 0); |
86 | if (!mdp_node) { |
87 | dev_err(dev, "can't get node %s\n" , MDP_PHANDLE_NAME); |
88 | return NULL; |
89 | } |
90 | |
91 | mdp_pdev = of_find_device_by_node(np: mdp_node); |
92 | of_node_put(node: mdp_node); |
93 | |
94 | return mdp_pdev; |
95 | } |
96 | EXPORT_SYMBOL_GPL(mdp_get_plat_device); |
97 | |
98 | int mdp_vpu_get_locked(struct mdp_dev *mdp) |
99 | { |
100 | int ret = 0; |
101 | |
102 | if (mdp->vpu_count++ == 0) { |
103 | ret = rproc_boot(rproc: mdp->rproc_handle); |
104 | if (ret) { |
105 | dev_err(&mdp->pdev->dev, |
106 | "vpu_load_firmware failed %d\n" , ret); |
107 | goto err_load_vpu; |
108 | } |
109 | ret = mdp_vpu_register(mdp); |
110 | if (ret) { |
111 | dev_err(&mdp->pdev->dev, |
112 | "mdp_vpu register failed %d\n" , ret); |
113 | goto err_reg_vpu; |
114 | } |
115 | ret = mdp_vpu_dev_init(vpu: &mdp->vpu, scp: mdp->scp, lock: &mdp->vpu_lock); |
116 | if (ret) { |
117 | dev_err(&mdp->pdev->dev, |
118 | "mdp_vpu device init failed %d\n" , ret); |
119 | goto err_init_vpu; |
120 | } |
121 | } |
122 | return 0; |
123 | |
124 | err_init_vpu: |
125 | mdp_vpu_unregister(mdp); |
126 | err_reg_vpu: |
127 | err_load_vpu: |
128 | mdp->vpu_count--; |
129 | return ret; |
130 | } |
131 | |
132 | void mdp_vpu_put_locked(struct mdp_dev *mdp) |
133 | { |
134 | if (--mdp->vpu_count == 0) { |
135 | mdp_vpu_dev_deinit(vpu: &mdp->vpu); |
136 | mdp_vpu_unregister(mdp); |
137 | } |
138 | } |
139 | |
140 | void mdp_video_device_release(struct video_device *vdev) |
141 | { |
142 | struct mdp_dev *mdp = (struct mdp_dev *)video_get_drvdata(vdev); |
143 | int i; |
144 | |
145 | for (i = 0; i < mdp->mdp_data->pp_used; i++) |
146 | if (mdp->cmdq_clt[i]) |
147 | cmdq_mbox_destroy(client: mdp->cmdq_clt[i]); |
148 | |
149 | scp_put(scp: mdp->scp); |
150 | |
151 | destroy_workqueue(wq: mdp->job_wq); |
152 | destroy_workqueue(wq: mdp->clock_wq); |
153 | |
154 | pm_runtime_disable(dev: &mdp->pdev->dev); |
155 | |
156 | vb2_dma_contig_clear_max_seg_size(dev: &mdp->pdev->dev); |
157 | |
158 | mdp_comp_destroy(mdp); |
159 | for (i = 0; i < mdp->mdp_data->pipe_info_len; i++) { |
160 | enum mdp_mm_subsys_id idx; |
161 | struct mtk_mutex *m; |
162 | u32 m_id; |
163 | |
164 | idx = mdp->mdp_data->pipe_info[i].sub_id; |
165 | m_id = mdp->mdp_data->pipe_info[i].mutex_id; |
166 | m = mdp->mm_subsys[idx].mdp_mutex[m_id]; |
167 | if (!IS_ERR_OR_NULL(ptr: m)) |
168 | mtk_mutex_put(mutex: m); |
169 | } |
170 | |
171 | mdp_vpu_shared_mem_free(vpu: &mdp->vpu); |
172 | v4l2_m2m_release(m2m_dev: mdp->m2m_dev); |
173 | kfree(objp: mdp); |
174 | } |
175 | |
176 | static int mdp_mm_subsys_deploy(struct mdp_dev *mdp, enum mdp_infra_id id) |
177 | { |
178 | struct platform_device *mm_pdev = NULL; |
179 | struct device **dev; |
180 | int i; |
181 | |
182 | if (!mdp) |
183 | return -EINVAL; |
184 | |
185 | for (i = 0; i < MDP_MM_SUBSYS_MAX; i++) { |
186 | const char *compat; |
187 | enum mdp_infra_id sub_id = id + i; |
188 | |
189 | switch (id) { |
190 | case MDP_INFRA_MMSYS: |
191 | dev = &mdp->mm_subsys[i].mmsys; |
192 | break; |
193 | case MDP_INFRA_MUTEX: |
194 | dev = &mdp->mm_subsys[i].mutex; |
195 | break; |
196 | default: |
197 | dev_err(&mdp->pdev->dev, "Unknown infra id %d" , id); |
198 | return -EINVAL; |
199 | } |
200 | |
201 | /* |
202 | * Not every chip has multiple multimedia subsystems, so |
203 | * the config may be null. |
204 | */ |
205 | compat = mdp->mdp_data->mdp_probe_infra[sub_id].compatible; |
206 | if (strlen(compat) == 0) |
207 | continue; |
208 | |
209 | mm_pdev = __get_pdev_by_id(pdev: mdp->pdev, from: mm_pdev, id: sub_id); |
210 | if (WARN_ON(!mm_pdev)) |
211 | return -ENODEV; |
212 | |
213 | *dev = &mm_pdev->dev; |
214 | } |
215 | |
216 | return 0; |
217 | } |
218 | |
219 | static int mdp_probe(struct platform_device *pdev) |
220 | { |
221 | struct device *dev = &pdev->dev; |
222 | struct mdp_dev *mdp; |
223 | struct platform_device *mm_pdev; |
224 | struct resource *res; |
225 | int ret, i, mutex_id; |
226 | |
227 | mdp = kzalloc(size: sizeof(*mdp), GFP_KERNEL); |
228 | if (!mdp) { |
229 | ret = -ENOMEM; |
230 | goto err_return; |
231 | } |
232 | |
233 | mdp->pdev = pdev; |
234 | mdp->mdp_data = of_device_get_match_data(dev: &pdev->dev); |
235 | |
236 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
237 | if (res->start != mdp->mdp_data->mdp_con_res) { |
238 | platform_set_drvdata(pdev, data: mdp); |
239 | goto success_return; |
240 | } |
241 | |
242 | ret = mdp_mm_subsys_deploy(mdp, id: MDP_INFRA_MMSYS); |
243 | if (ret) |
244 | goto err_destroy_device; |
245 | |
246 | ret = mdp_mm_subsys_deploy(mdp, id: MDP_INFRA_MUTEX); |
247 | if (ret) |
248 | goto err_destroy_device; |
249 | |
250 | for (i = 0; i < mdp->mdp_data->pipe_info_len; i++) { |
251 | enum mdp_mm_subsys_id idx; |
252 | struct mtk_mutex **m; |
253 | |
254 | idx = mdp->mdp_data->pipe_info[i].sub_id; |
255 | mutex_id = mdp->mdp_data->pipe_info[i].mutex_id; |
256 | m = &mdp->mm_subsys[idx].mdp_mutex[mutex_id]; |
257 | |
258 | if (!IS_ERR_OR_NULL(ptr: *m)) |
259 | continue; |
260 | |
261 | *m = mtk_mutex_get(dev: mdp->mm_subsys[idx].mutex); |
262 | if (IS_ERR(ptr: *m)) { |
263 | ret = PTR_ERR(ptr: *m); |
264 | goto err_free_mutex; |
265 | } |
266 | } |
267 | |
268 | ret = mdp_comp_config(mdp); |
269 | if (ret) { |
270 | dev_err(dev, "Failed to config mdp components\n" ); |
271 | goto err_free_mutex; |
272 | } |
273 | |
274 | mdp->job_wq = alloc_workqueue(MDP_MODULE_NAME, flags: WQ_FREEZABLE, max_active: 0); |
275 | if (!mdp->job_wq) { |
276 | dev_err(dev, "Unable to create job workqueue\n" ); |
277 | ret = -ENOMEM; |
278 | goto err_deinit_comp; |
279 | } |
280 | |
281 | mdp->clock_wq = alloc_workqueue(MDP_MODULE_NAME "-clock" , flags: WQ_FREEZABLE, |
282 | max_active: 0); |
283 | if (!mdp->clock_wq) { |
284 | dev_err(dev, "Unable to create clock workqueue\n" ); |
285 | ret = -ENOMEM; |
286 | goto err_destroy_job_wq; |
287 | } |
288 | |
289 | mdp->scp = scp_get(pdev); |
290 | if (!mdp->scp) { |
291 | mm_pdev = __get_pdev_by_id(pdev, NULL, id: MDP_INFRA_SCP); |
292 | if (WARN_ON(!mm_pdev)) { |
293 | dev_err(&pdev->dev, "Could not get scp device\n" ); |
294 | ret = -ENODEV; |
295 | goto err_destroy_clock_wq; |
296 | } |
297 | mdp->scp = platform_get_drvdata(pdev: mm_pdev); |
298 | } |
299 | |
300 | mdp->rproc_handle = scp_get_rproc(scp: mdp->scp); |
301 | dev_dbg(&pdev->dev, "MDP rproc_handle: %pK" , mdp->rproc_handle); |
302 | |
303 | mutex_init(&mdp->vpu_lock); |
304 | mutex_init(&mdp->m2m_lock); |
305 | |
306 | for (i = 0; i < mdp->mdp_data->pp_used; i++) { |
307 | mdp->cmdq_clt[i] = cmdq_mbox_create(dev, index: i); |
308 | if (IS_ERR(ptr: mdp->cmdq_clt[i])) { |
309 | ret = PTR_ERR(ptr: mdp->cmdq_clt[i]); |
310 | goto err_mbox_destroy; |
311 | } |
312 | } |
313 | |
314 | init_waitqueue_head(&mdp->callback_wq); |
315 | ida_init(ida: &mdp->mdp_ida); |
316 | platform_set_drvdata(pdev, data: mdp); |
317 | |
318 | vb2_dma_contig_set_max_seg_size(dev: &pdev->dev, DMA_BIT_MASK(32)); |
319 | |
320 | ret = v4l2_device_register(dev, v4l2_dev: &mdp->v4l2_dev); |
321 | if (ret) { |
322 | dev_err(dev, "Failed to register v4l2 device\n" ); |
323 | ret = -EINVAL; |
324 | goto err_mbox_destroy; |
325 | } |
326 | |
327 | ret = mdp_m2m_device_register(mdp); |
328 | if (ret) { |
329 | v4l2_err(&mdp->v4l2_dev, "Failed to register m2m device\n" ); |
330 | goto err_unregister_device; |
331 | } |
332 | |
333 | success_return: |
334 | dev_dbg(dev, "mdp-%d registered successfully\n" , pdev->id); |
335 | return 0; |
336 | |
337 | err_unregister_device: |
338 | v4l2_device_unregister(v4l2_dev: &mdp->v4l2_dev); |
339 | err_mbox_destroy: |
340 | while (--i >= 0) |
341 | cmdq_mbox_destroy(client: mdp->cmdq_clt[i]); |
342 | scp_put(scp: mdp->scp); |
343 | err_destroy_clock_wq: |
344 | destroy_workqueue(wq: mdp->clock_wq); |
345 | err_destroy_job_wq: |
346 | destroy_workqueue(wq: mdp->job_wq); |
347 | err_deinit_comp: |
348 | mdp_comp_destroy(mdp); |
349 | err_free_mutex: |
350 | for (i = 0; i < mdp->mdp_data->pipe_info_len; i++) { |
351 | enum mdp_mm_subsys_id idx; |
352 | struct mtk_mutex *m; |
353 | |
354 | idx = mdp->mdp_data->pipe_info[i].sub_id; |
355 | mutex_id = mdp->mdp_data->pipe_info[i].mutex_id; |
356 | m = mdp->mm_subsys[idx].mdp_mutex[mutex_id]; |
357 | if (!IS_ERR_OR_NULL(ptr: m)) |
358 | mtk_mutex_put(mutex: m); |
359 | } |
360 | err_destroy_device: |
361 | kfree(objp: mdp); |
362 | err_return: |
363 | dev_dbg(dev, "Errno %d\n" , ret); |
364 | return ret; |
365 | } |
366 | |
367 | static void mdp_remove(struct platform_device *pdev) |
368 | { |
369 | struct mdp_dev *mdp = platform_get_drvdata(pdev); |
370 | |
371 | v4l2_device_unregister(v4l2_dev: &mdp->v4l2_dev); |
372 | |
373 | dev_dbg(&pdev->dev, "%s driver unloaded\n" , pdev->name); |
374 | } |
375 | |
376 | static int __maybe_unused mdp_suspend(struct device *dev) |
377 | { |
378 | struct mdp_dev *mdp = dev_get_drvdata(dev); |
379 | int ret; |
380 | |
381 | atomic_set(v: &mdp->suspended, i: 1); |
382 | |
383 | if (atomic_read(v: &mdp->job_count)) { |
384 | ret = wait_event_timeout(mdp->callback_wq, |
385 | !atomic_read(&mdp->job_count), |
386 | 2 * HZ); |
387 | if (ret == 0) { |
388 | dev_err(dev, |
389 | "%s:flushed cmdq task incomplete, count=%d\n" , |
390 | __func__, atomic_read(&mdp->job_count)); |
391 | return -EBUSY; |
392 | } |
393 | } |
394 | |
395 | return 0; |
396 | } |
397 | |
398 | static int __maybe_unused mdp_resume(struct device *dev) |
399 | { |
400 | struct mdp_dev *mdp = dev_get_drvdata(dev); |
401 | |
402 | atomic_set(v: &mdp->suspended, i: 0); |
403 | |
404 | return 0; |
405 | } |
406 | |
407 | static const struct dev_pm_ops mdp_pm_ops = { |
408 | SET_SYSTEM_SLEEP_PM_OPS(mdp_suspend, mdp_resume) |
409 | }; |
410 | |
411 | static struct platform_driver mdp_driver = { |
412 | .probe = mdp_probe, |
413 | .remove_new = mdp_remove, |
414 | .driver = { |
415 | .name = MDP_MODULE_NAME, |
416 | .pm = &mdp_pm_ops, |
417 | .of_match_table = mdp_of_ids, |
418 | }, |
419 | }; |
420 | |
421 | module_platform_driver(mdp_driver); |
422 | |
423 | MODULE_AUTHOR("Ping-Hsun Wu <ping-hsun.wu@mediatek.com>" ); |
424 | MODULE_DESCRIPTION("MediaTek image processor 3 driver" ); |
425 | MODULE_LICENSE("GPL" ); |
426 | |