]>
Commit | Line | Data |
---|---|---|
d76271d2 HK |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * ZynqMP DisplayPort Subsystem Driver | |
4 | * | |
5 | * Copyright (C) 2017 - 2020 Xilinx, Inc. | |
6 | * | |
7 | * Authors: | |
8 | * - Hyun Woo Kwon <hyun.kwon@xilinx.com> | |
9 | * - Laurent Pinchart <laurent.pinchart@ideasonboard.com> | |
10 | */ | |
11 | ||
12 | #include <linux/clk.h> | |
13 | #include <linux/dma-mapping.h> | |
14 | #include <linux/module.h> | |
15 | #include <linux/of_reserved_mem.h> | |
16 | #include <linux/platform_device.h> | |
17 | #include <linux/pm_runtime.h> | |
18 | ||
19 | #include <drm/drm_atomic_helper.h> | |
20 | #include <drm/drm_device.h> | |
21 | #include <drm/drm_drv.h> | |
22 | #include <drm/drm_fb_helper.h> | |
23 | #include <drm/drm_fourcc.h> | |
24 | #include <drm/drm_gem_cma_helper.h> | |
25 | #include <drm/drm_gem_framebuffer_helper.h> | |
26 | #include <drm/drm_managed.h> | |
27 | #include <drm/drm_mode_config.h> | |
28 | #include <drm/drm_probe_helper.h> | |
29 | #include <drm/drm_vblank.h> | |
30 | ||
31 | #include "zynqmp_disp.h" | |
32 | #include "zynqmp_dp.h" | |
33 | #include "zynqmp_dpsub.h" | |
34 | ||
35 | /* ----------------------------------------------------------------------------- | |
36 | * Dumb Buffer & Framebuffer Allocation | |
37 | */ | |
38 | ||
39 | static int zynqmp_dpsub_dumb_create(struct drm_file *file_priv, | |
40 | struct drm_device *drm, | |
41 | struct drm_mode_create_dumb *args) | |
42 | { | |
43 | struct zynqmp_dpsub *dpsub = to_zynqmp_dpsub(drm); | |
44 | unsigned int pitch = DIV_ROUND_UP(args->width * args->bpp, 8); | |
45 | ||
46 | /* Enforce the alignment constraints of the DMA engine. */ | |
47 | args->pitch = ALIGN(pitch, dpsub->dma_align); | |
48 | ||
49 | return drm_gem_cma_dumb_create_internal(file_priv, drm, args); | |
50 | } | |
51 | ||
52 | static struct drm_framebuffer * | |
53 | zynqmp_dpsub_fb_create(struct drm_device *drm, struct drm_file *file_priv, | |
54 | const struct drm_mode_fb_cmd2 *mode_cmd) | |
55 | { | |
56 | struct zynqmp_dpsub *dpsub = to_zynqmp_dpsub(drm); | |
57 | struct drm_mode_fb_cmd2 cmd = *mode_cmd; | |
58 | unsigned int i; | |
59 | ||
60 | /* Enforce the alignment constraints of the DMA engine. */ | |
61 | for (i = 0; i < ARRAY_SIZE(cmd.pitches); ++i) | |
62 | cmd.pitches[i] = ALIGN(cmd.pitches[i], dpsub->dma_align); | |
63 | ||
64 | return drm_gem_fb_create(drm, file_priv, &cmd); | |
65 | } | |
66 | ||
67 | static const struct drm_mode_config_funcs zynqmp_dpsub_mode_config_funcs = { | |
68 | .fb_create = zynqmp_dpsub_fb_create, | |
69 | .atomic_check = drm_atomic_helper_check, | |
70 | .atomic_commit = drm_atomic_helper_commit, | |
71 | }; | |
72 | ||
73 | /* ----------------------------------------------------------------------------- | |
74 | * DRM/KMS Driver | |
75 | */ | |
76 | ||
77 | DEFINE_DRM_GEM_CMA_FOPS(zynqmp_dpsub_drm_fops); | |
78 | ||
79 | static struct drm_driver zynqmp_dpsub_drm_driver = { | |
80 | .driver_features = DRIVER_MODESET | DRIVER_GEM | | |
81 | DRIVER_ATOMIC, | |
82 | ||
83 | .prime_handle_to_fd = drm_gem_prime_handle_to_fd, | |
84 | .prime_fd_to_handle = drm_gem_prime_fd_to_handle, | |
85 | .gem_prime_export = drm_gem_prime_export, | |
86 | .gem_prime_import = drm_gem_prime_import, | |
87 | .gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table, | |
88 | .gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table, | |
89 | .gem_prime_vmap = drm_gem_cma_prime_vmap, | |
90 | .gem_prime_vunmap = drm_gem_cma_prime_vunmap, | |
91 | .gem_prime_mmap = drm_gem_cma_prime_mmap, | |
92 | .gem_free_object_unlocked = drm_gem_cma_free_object, | |
93 | .gem_vm_ops = &drm_gem_cma_vm_ops, | |
94 | .dumb_create = zynqmp_dpsub_dumb_create, | |
95 | .dumb_destroy = drm_gem_dumb_destroy, | |
96 | ||
97 | .fops = &zynqmp_dpsub_drm_fops, | |
98 | ||
99 | .name = "zynqmp-dpsub", | |
100 | .desc = "Xilinx DisplayPort Subsystem Driver", | |
101 | .date = "20130509", | |
102 | .major = 1, | |
103 | .minor = 0, | |
104 | }; | |
105 | ||
106 | static int zynqmp_dpsub_drm_init(struct zynqmp_dpsub *dpsub) | |
107 | { | |
108 | struct drm_device *drm = &dpsub->drm; | |
109 | int ret; | |
110 | ||
111 | /* Initialize mode config, vblank and the KMS poll helper. */ | |
112 | ret = drmm_mode_config_init(drm); | |
113 | if (ret < 0) | |
075342ea | 114 | return ret; |
d76271d2 HK |
115 | |
116 | drm->mode_config.funcs = &zynqmp_dpsub_mode_config_funcs; | |
117 | drm->mode_config.min_width = 0; | |
118 | drm->mode_config.min_height = 0; | |
119 | drm->mode_config.max_width = ZYNQMP_DISP_MAX_WIDTH; | |
120 | drm->mode_config.max_height = ZYNQMP_DISP_MAX_HEIGHT; | |
121 | ||
122 | ret = drm_vblank_init(drm, 1); | |
123 | if (ret) | |
075342ea | 124 | return ret; |
d76271d2 HK |
125 | |
126 | drm->irq_enabled = 1; | |
127 | ||
128 | drm_kms_helper_poll_init(drm); | |
129 | ||
130 | /* | |
131 | * Initialize the DISP and DP components. This will creates planes, | |
132 | * CRTC, encoder and connector. The DISP should be initialized first as | |
133 | * the DP encoder needs the CRTC. | |
134 | */ | |
135 | ret = zynqmp_disp_drm_init(dpsub); | |
136 | if (ret) | |
137 | goto err_poll_fini; | |
138 | ||
139 | ret = zynqmp_dp_drm_init(dpsub); | |
140 | if (ret) | |
141 | goto err_poll_fini; | |
142 | ||
143 | /* Reset all components and register the DRM device. */ | |
144 | drm_mode_config_reset(drm); | |
145 | ||
146 | ret = drm_dev_register(drm, 0); | |
147 | if (ret < 0) | |
148 | goto err_poll_fini; | |
149 | ||
150 | /* Initialize fbdev generic emulation. */ | |
151 | drm_fbdev_generic_setup(drm, 24); | |
152 | ||
153 | return 0; | |
154 | ||
155 | err_poll_fini: | |
156 | drm_kms_helper_poll_fini(drm); | |
d76271d2 HK |
157 | return ret; |
158 | } | |
159 | ||
160 | /* ----------------------------------------------------------------------------- | |
161 | * Power Management | |
162 | */ | |
163 | ||
164 | static int __maybe_unused zynqmp_dpsub_suspend(struct device *dev) | |
165 | { | |
166 | struct zynqmp_dpsub *dpsub = dev_get_drvdata(dev); | |
167 | ||
168 | return drm_mode_config_helper_suspend(&dpsub->drm); | |
169 | } | |
170 | ||
171 | static int __maybe_unused zynqmp_dpsub_resume(struct device *dev) | |
172 | { | |
173 | struct zynqmp_dpsub *dpsub = dev_get_drvdata(dev); | |
174 | ||
175 | return drm_mode_config_helper_resume(&dpsub->drm); | |
176 | } | |
177 | ||
178 | static const struct dev_pm_ops zynqmp_dpsub_pm_ops = { | |
179 | SET_SYSTEM_SLEEP_PM_OPS(zynqmp_dpsub_suspend, zynqmp_dpsub_resume) | |
180 | }; | |
181 | ||
182 | /* ----------------------------------------------------------------------------- | |
183 | * Probe & Remove | |
184 | */ | |
185 | ||
186 | static int zynqmp_dpsub_init_clocks(struct zynqmp_dpsub *dpsub) | |
187 | { | |
188 | int ret; | |
189 | ||
190 | dpsub->apb_clk = devm_clk_get(dpsub->dev, "dp_apb_clk"); | |
191 | if (IS_ERR(dpsub->apb_clk)) | |
192 | return PTR_ERR(dpsub->apb_clk); | |
193 | ||
194 | ret = clk_prepare_enable(dpsub->apb_clk); | |
195 | if (ret) { | |
196 | dev_err(dpsub->dev, "failed to enable the APB clock\n"); | |
197 | return ret; | |
198 | } | |
199 | ||
200 | return 0; | |
201 | } | |
202 | ||
203 | static int zynqmp_dpsub_probe(struct platform_device *pdev) | |
204 | { | |
205 | struct zynqmp_dpsub *dpsub; | |
206 | int ret; | |
207 | ||
208 | /* Allocate private data. */ | |
075342ea DV |
209 | dpsub = devm_drm_dev_alloc(&pdev->dev, &zynqmp_dpsub_drm_driver, |
210 | struct zynqmp_dpsub, drm); | |
211 | if (IS_ERR(dpsub)) | |
212 | return PTR_ERR(dpsub); | |
d76271d2 HK |
213 | |
214 | dpsub->dev = &pdev->dev; | |
215 | platform_set_drvdata(pdev, dpsub); | |
216 | ||
217 | dma_set_mask(dpsub->dev, DMA_BIT_MASK(ZYNQMP_DISP_MAX_DMA_BIT)); | |
218 | ||
d76271d2 HK |
219 | /* Try the reserved memory. Proceed if there's none. */ |
220 | of_reserved_mem_device_init(&pdev->dev); | |
221 | ||
222 | ret = zynqmp_dpsub_init_clocks(dpsub); | |
223 | if (ret < 0) | |
224 | goto err_mem; | |
225 | ||
226 | pm_runtime_enable(&pdev->dev); | |
227 | ||
228 | /* | |
229 | * DP should be probed first so that the zynqmp_disp can set the output | |
230 | * format accordingly. | |
231 | */ | |
232 | ret = zynqmp_dp_probe(dpsub, &dpsub->drm); | |
233 | if (ret) | |
234 | goto err_pm; | |
235 | ||
236 | ret = zynqmp_disp_probe(dpsub, &dpsub->drm); | |
237 | if (ret) | |
238 | goto err_dp; | |
239 | ||
240 | ret = zynqmp_dpsub_drm_init(dpsub); | |
241 | if (ret) | |
242 | goto err_disp; | |
243 | ||
244 | dev_info(&pdev->dev, "ZynqMP DisplayPort Subsystem driver probed"); | |
245 | ||
246 | return 0; | |
247 | ||
248 | err_disp: | |
249 | zynqmp_disp_remove(dpsub); | |
250 | err_dp: | |
251 | zynqmp_dp_remove(dpsub); | |
252 | err_pm: | |
253 | pm_runtime_disable(&pdev->dev); | |
254 | clk_disable_unprepare(dpsub->apb_clk); | |
255 | err_mem: | |
256 | of_reserved_mem_device_release(&pdev->dev); | |
257 | return ret; | |
258 | } | |
259 | ||
260 | static int zynqmp_dpsub_remove(struct platform_device *pdev) | |
261 | { | |
262 | struct zynqmp_dpsub *dpsub = platform_get_drvdata(pdev); | |
263 | struct drm_device *drm = &dpsub->drm; | |
264 | ||
265 | drm_dev_unregister(drm); | |
266 | drm_atomic_helper_shutdown(drm); | |
267 | drm_kms_helper_poll_fini(drm); | |
268 | ||
269 | zynqmp_disp_remove(dpsub); | |
270 | zynqmp_dp_remove(dpsub); | |
271 | ||
272 | pm_runtime_disable(&pdev->dev); | |
273 | clk_disable_unprepare(dpsub->apb_clk); | |
274 | of_reserved_mem_device_release(&pdev->dev); | |
275 | ||
d76271d2 HK |
276 | return 0; |
277 | } | |
278 | ||
279 | static void zynqmp_dpsub_shutdown(struct platform_device *pdev) | |
280 | { | |
281 | struct zynqmp_dpsub *dpsub = platform_get_drvdata(pdev); | |
282 | ||
283 | drm_atomic_helper_shutdown(&dpsub->drm); | |
284 | } | |
285 | ||
286 | static const struct of_device_id zynqmp_dpsub_of_match[] = { | |
287 | { .compatible = "xlnx,zynqmp-dpsub-1.7", }, | |
288 | { /* end of table */ }, | |
289 | }; | |
290 | MODULE_DEVICE_TABLE(of, zynqmp_dpsub_of_match); | |
291 | ||
292 | static struct platform_driver zynqmp_dpsub_driver = { | |
293 | .probe = zynqmp_dpsub_probe, | |
294 | .remove = zynqmp_dpsub_remove, | |
295 | .shutdown = zynqmp_dpsub_shutdown, | |
296 | .driver = { | |
297 | .name = "zynqmp-dpsub", | |
298 | .pm = &zynqmp_dpsub_pm_ops, | |
299 | .of_match_table = zynqmp_dpsub_of_match, | |
300 | }, | |
301 | }; | |
302 | ||
303 | module_platform_driver(zynqmp_dpsub_driver); | |
304 | ||
305 | MODULE_AUTHOR("Xilinx, Inc."); | |
306 | MODULE_DESCRIPTION("ZynqMP DP Subsystem Driver"); | |
307 | MODULE_LICENSE("GPL v2"); |