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_graph.h> |
16 | #include <linux/of_reserved_mem.h> |
17 | #include <linux/platform_device.h> |
18 | #include <linux/pm_runtime.h> |
19 | #include <linux/slab.h> |
20 | |
21 | #include <drm/drm_atomic_helper.h> |
22 | #include <drm/drm_bridge.h> |
23 | #include <drm/drm_modeset_helper.h> |
24 | #include <drm/drm_module.h> |
25 | |
26 | #include "zynqmp_disp.h" |
27 | #include "zynqmp_dp.h" |
28 | #include "zynqmp_dpsub.h" |
29 | #include "zynqmp_kms.h" |
30 | |
31 | /* ----------------------------------------------------------------------------- |
32 | * Power Management |
33 | */ |
34 | |
35 | static int __maybe_unused zynqmp_dpsub_suspend(struct device *dev) |
36 | { |
37 | struct zynqmp_dpsub *dpsub = dev_get_drvdata(dev); |
38 | |
39 | if (!dpsub->drm) |
40 | return 0; |
41 | |
42 | return drm_mode_config_helper_suspend(dev: &dpsub->drm->dev); |
43 | } |
44 | |
45 | static int __maybe_unused zynqmp_dpsub_resume(struct device *dev) |
46 | { |
47 | struct zynqmp_dpsub *dpsub = dev_get_drvdata(dev); |
48 | |
49 | if (!dpsub->drm) |
50 | return 0; |
51 | |
52 | return drm_mode_config_helper_resume(dev: &dpsub->drm->dev); |
53 | } |
54 | |
55 | static const struct dev_pm_ops zynqmp_dpsub_pm_ops = { |
56 | SET_SYSTEM_SLEEP_PM_OPS(zynqmp_dpsub_suspend, zynqmp_dpsub_resume) |
57 | }; |
58 | |
59 | /* ----------------------------------------------------------------------------- |
60 | * DPSUB Configuration |
61 | */ |
62 | |
63 | /** |
64 | * zynqmp_dpsub_audio_enabled - If the audio is enabled |
65 | * @dpsub: DisplayPort subsystem |
66 | * |
67 | * Return if the audio is enabled depending on the audio clock. |
68 | * |
69 | * Return: true if audio is enabled, or false. |
70 | */ |
71 | bool zynqmp_dpsub_audio_enabled(struct zynqmp_dpsub *dpsub) |
72 | { |
73 | return !!dpsub->aud_clk; |
74 | } |
75 | |
76 | /** |
77 | * zynqmp_dpsub_get_audio_clk_rate - Get the current audio clock rate |
78 | * @dpsub: DisplayPort subsystem |
79 | * |
80 | * Return: the current audio clock rate. |
81 | */ |
82 | unsigned int zynqmp_dpsub_get_audio_clk_rate(struct zynqmp_dpsub *dpsub) |
83 | { |
84 | if (zynqmp_dpsub_audio_enabled(dpsub)) |
85 | return 0; |
86 | return clk_get_rate(clk: dpsub->aud_clk); |
87 | } |
88 | |
89 | /* ----------------------------------------------------------------------------- |
90 | * Probe & Remove |
91 | */ |
92 | |
93 | static int zynqmp_dpsub_init_clocks(struct zynqmp_dpsub *dpsub) |
94 | { |
95 | int ret; |
96 | |
97 | dpsub->apb_clk = devm_clk_get(dev: dpsub->dev, id: "dp_apb_clk" ); |
98 | if (IS_ERR(ptr: dpsub->apb_clk)) |
99 | return PTR_ERR(ptr: dpsub->apb_clk); |
100 | |
101 | ret = clk_prepare_enable(clk: dpsub->apb_clk); |
102 | if (ret) { |
103 | dev_err(dpsub->dev, "failed to enable the APB clock\n" ); |
104 | return ret; |
105 | } |
106 | |
107 | /* |
108 | * Try the live PL video clock, and fall back to the PS clock if the |
109 | * live PL video clock isn't valid. |
110 | */ |
111 | dpsub->vid_clk = devm_clk_get(dev: dpsub->dev, id: "dp_live_video_in_clk" ); |
112 | if (!IS_ERR(ptr: dpsub->vid_clk)) |
113 | dpsub->vid_clk_from_ps = false; |
114 | else if (PTR_ERR(ptr: dpsub->vid_clk) == -EPROBE_DEFER) |
115 | return PTR_ERR(ptr: dpsub->vid_clk); |
116 | |
117 | if (IS_ERR_OR_NULL(ptr: dpsub->vid_clk)) { |
118 | dpsub->vid_clk = devm_clk_get(dev: dpsub->dev, id: "dp_vtc_pixel_clk_in" ); |
119 | if (IS_ERR(ptr: dpsub->vid_clk)) { |
120 | dev_err(dpsub->dev, "failed to init any video clock\n" ); |
121 | return PTR_ERR(ptr: dpsub->vid_clk); |
122 | } |
123 | dpsub->vid_clk_from_ps = true; |
124 | } |
125 | |
126 | /* |
127 | * Try the live PL audio clock, and fall back to the PS clock if the |
128 | * live PL audio clock isn't valid. Missing audio clock disables audio |
129 | * but isn't an error. |
130 | */ |
131 | dpsub->aud_clk = devm_clk_get(dev: dpsub->dev, id: "dp_live_audio_aclk" ); |
132 | if (!IS_ERR(ptr: dpsub->aud_clk)) { |
133 | dpsub->aud_clk_from_ps = false; |
134 | return 0; |
135 | } |
136 | |
137 | dpsub->aud_clk = devm_clk_get(dev: dpsub->dev, id: "dp_aud_clk" ); |
138 | if (!IS_ERR(ptr: dpsub->aud_clk)) { |
139 | dpsub->aud_clk_from_ps = true; |
140 | return 0; |
141 | } |
142 | |
143 | dev_info(dpsub->dev, "audio disabled due to missing clock\n" ); |
144 | return 0; |
145 | } |
146 | |
147 | static int zynqmp_dpsub_parse_dt(struct zynqmp_dpsub *dpsub) |
148 | { |
149 | struct device_node *np; |
150 | unsigned int i; |
151 | |
152 | /* |
153 | * For backward compatibility with old device trees that don't contain |
154 | * ports, consider that only the DP output port is connected if no |
155 | * ports child no exists. |
156 | */ |
157 | np = of_get_child_by_name(node: dpsub->dev->of_node, name: "ports" ); |
158 | of_node_put(node: np); |
159 | if (!np) { |
160 | dev_warn(dpsub->dev, "missing ports, update DT bindings\n" ); |
161 | dpsub->connected_ports = BIT(ZYNQMP_DPSUB_PORT_OUT_DP); |
162 | dpsub->dma_enabled = true; |
163 | return 0; |
164 | } |
165 | |
166 | /* Check which ports are connected. */ |
167 | for (i = 0; i < ZYNQMP_DPSUB_NUM_PORTS; ++i) { |
168 | struct device_node *np; |
169 | |
170 | np = of_graph_get_remote_node(node: dpsub->dev->of_node, port: i, endpoint: -1); |
171 | if (np) { |
172 | dpsub->connected_ports |= BIT(i); |
173 | of_node_put(node: np); |
174 | } |
175 | } |
176 | |
177 | /* Sanity checks. */ |
178 | if ((dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_LIVE_VIDEO)) && |
179 | (dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_LIVE_GFX))) { |
180 | dev_err(dpsub->dev, "only one live video input is supported\n" ); |
181 | return -EINVAL; |
182 | } |
183 | |
184 | if ((dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_LIVE_VIDEO)) || |
185 | (dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_LIVE_GFX))) { |
186 | if (dpsub->vid_clk_from_ps) { |
187 | dev_err(dpsub->dev, |
188 | "live video input requires PL clock\n" ); |
189 | return -EINVAL; |
190 | } |
191 | } else { |
192 | dpsub->dma_enabled = true; |
193 | } |
194 | |
195 | if (dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_LIVE_AUDIO)) |
196 | dev_warn(dpsub->dev, "live audio unsupported, ignoring\n" ); |
197 | |
198 | if ((dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_OUT_VIDEO)) || |
199 | (dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_OUT_AUDIO))) |
200 | dev_warn(dpsub->dev, "output to PL unsupported, ignoring\n" ); |
201 | |
202 | if (!(dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_OUT_DP))) { |
203 | dev_err(dpsub->dev, "DP output port not connected\n" ); |
204 | return -EINVAL; |
205 | } |
206 | |
207 | return 0; |
208 | } |
209 | |
210 | void zynqmp_dpsub_release(struct zynqmp_dpsub *dpsub) |
211 | { |
212 | kfree(objp: dpsub->disp); |
213 | kfree(objp: dpsub->dp); |
214 | kfree(objp: dpsub); |
215 | } |
216 | |
217 | static int zynqmp_dpsub_probe(struct platform_device *pdev) |
218 | { |
219 | struct zynqmp_dpsub *dpsub; |
220 | int ret; |
221 | |
222 | /* Allocate private data. */ |
223 | dpsub = kzalloc(size: sizeof(*dpsub), GFP_KERNEL); |
224 | if (!dpsub) |
225 | return -ENOMEM; |
226 | |
227 | dpsub->dev = &pdev->dev; |
228 | platform_set_drvdata(pdev, data: dpsub); |
229 | |
230 | ret = dma_set_mask(dev: dpsub->dev, DMA_BIT_MASK(ZYNQMP_DISP_MAX_DMA_BIT)); |
231 | if (ret) |
232 | return ret; |
233 | |
234 | /* Try the reserved memory. Proceed if there's none. */ |
235 | of_reserved_mem_device_init(dev: &pdev->dev); |
236 | |
237 | ret = zynqmp_dpsub_init_clocks(dpsub); |
238 | if (ret < 0) |
239 | goto err_mem; |
240 | |
241 | ret = zynqmp_dpsub_parse_dt(dpsub); |
242 | if (ret < 0) |
243 | goto err_mem; |
244 | |
245 | pm_runtime_enable(dev: &pdev->dev); |
246 | |
247 | /* |
248 | * DP should be probed first so that the zynqmp_disp can set the output |
249 | * format accordingly. |
250 | */ |
251 | ret = zynqmp_dp_probe(dpsub); |
252 | if (ret) |
253 | goto err_pm; |
254 | |
255 | ret = zynqmp_disp_probe(dpsub); |
256 | if (ret) |
257 | goto err_dp; |
258 | |
259 | if (dpsub->dma_enabled) { |
260 | ret = zynqmp_dpsub_drm_init(dpsub); |
261 | if (ret) |
262 | goto err_disp; |
263 | } else { |
264 | drm_bridge_add(bridge: dpsub->bridge); |
265 | } |
266 | |
267 | dev_info(&pdev->dev, "ZynqMP DisplayPort Subsystem driver probed" ); |
268 | |
269 | return 0; |
270 | |
271 | err_disp: |
272 | zynqmp_disp_remove(dpsub); |
273 | err_dp: |
274 | zynqmp_dp_remove(dpsub); |
275 | err_pm: |
276 | pm_runtime_disable(dev: &pdev->dev); |
277 | clk_disable_unprepare(clk: dpsub->apb_clk); |
278 | err_mem: |
279 | of_reserved_mem_device_release(dev: &pdev->dev); |
280 | if (!dpsub->drm) |
281 | zynqmp_dpsub_release(dpsub); |
282 | return ret; |
283 | } |
284 | |
285 | static void zynqmp_dpsub_remove(struct platform_device *pdev) |
286 | { |
287 | struct zynqmp_dpsub *dpsub = platform_get_drvdata(pdev); |
288 | |
289 | if (dpsub->drm) |
290 | zynqmp_dpsub_drm_cleanup(dpsub); |
291 | else |
292 | drm_bridge_remove(bridge: dpsub->bridge); |
293 | |
294 | zynqmp_disp_remove(dpsub); |
295 | zynqmp_dp_remove(dpsub); |
296 | |
297 | pm_runtime_disable(dev: &pdev->dev); |
298 | clk_disable_unprepare(clk: dpsub->apb_clk); |
299 | of_reserved_mem_device_release(dev: &pdev->dev); |
300 | |
301 | if (!dpsub->drm) |
302 | zynqmp_dpsub_release(dpsub); |
303 | } |
304 | |
305 | static void zynqmp_dpsub_shutdown(struct platform_device *pdev) |
306 | { |
307 | struct zynqmp_dpsub *dpsub = platform_get_drvdata(pdev); |
308 | |
309 | if (!dpsub->drm) |
310 | return; |
311 | |
312 | drm_atomic_helper_shutdown(dev: &dpsub->drm->dev); |
313 | } |
314 | |
315 | static const struct of_device_id zynqmp_dpsub_of_match[] = { |
316 | { .compatible = "xlnx,zynqmp-dpsub-1.7" , }, |
317 | { /* end of table */ }, |
318 | }; |
319 | MODULE_DEVICE_TABLE(of, zynqmp_dpsub_of_match); |
320 | |
321 | static struct platform_driver zynqmp_dpsub_driver = { |
322 | .probe = zynqmp_dpsub_probe, |
323 | .remove_new = zynqmp_dpsub_remove, |
324 | .shutdown = zynqmp_dpsub_shutdown, |
325 | .driver = { |
326 | .name = "zynqmp-dpsub" , |
327 | .pm = &zynqmp_dpsub_pm_ops, |
328 | .of_match_table = zynqmp_dpsub_of_match, |
329 | }, |
330 | }; |
331 | |
332 | drm_module_platform_driver(zynqmp_dpsub_driver); |
333 | |
334 | MODULE_AUTHOR("Xilinx, Inc." ); |
335 | MODULE_DESCRIPTION("ZynqMP DP Subsystem Driver" ); |
336 | MODULE_LICENSE("GPL v2" ); |
337 | |