1d76271d2SHyun Kwon // SPDX-License-Identifier: GPL-2.0
2d76271d2SHyun Kwon /*
3d76271d2SHyun Kwon  * ZynqMP DisplayPort Subsystem Driver
4d76271d2SHyun Kwon  *
5d76271d2SHyun Kwon  * Copyright (C) 2017 - 2020 Xilinx, Inc.
6d76271d2SHyun Kwon  *
7d76271d2SHyun Kwon  * Authors:
8d76271d2SHyun Kwon  * - Hyun Woo Kwon <hyun.kwon@xilinx.com>
9d76271d2SHyun Kwon  * - Laurent Pinchart <laurent.pinchart@ideasonboard.com>
10d76271d2SHyun Kwon  */
11d76271d2SHyun Kwon 
12d76271d2SHyun Kwon #include <linux/clk.h>
13d76271d2SHyun Kwon #include <linux/dma-mapping.h>
14d76271d2SHyun Kwon #include <linux/module.h>
15*52c2cf14SLaurent Pinchart #include <linux/of_graph.h>
16d76271d2SHyun Kwon #include <linux/of_reserved_mem.h>
17d76271d2SHyun Kwon #include <linux/platform_device.h>
18d76271d2SHyun Kwon #include <linux/pm_runtime.h>
196ca91bb4SLaurent Pinchart #include <linux/slab.h>
20d76271d2SHyun Kwon 
21d76271d2SHyun Kwon #include <drm/drm_atomic_helper.h>
22074ef0ceSLaurent Pinchart #include <drm/drm_modeset_helper.h>
23fad54534SJavier Martinez Canillas #include <drm/drm_module.h>
24d76271d2SHyun Kwon 
25d76271d2SHyun Kwon #include "zynqmp_disp.h"
26d76271d2SHyun Kwon #include "zynqmp_dp.h"
27d76271d2SHyun Kwon #include "zynqmp_dpsub.h"
2876c8eeb7SLaurent Pinchart #include "zynqmp_kms.h"
29d76271d2SHyun Kwon 
30d76271d2SHyun Kwon /* -----------------------------------------------------------------------------
31d76271d2SHyun Kwon  * Power Management
32d76271d2SHyun Kwon  */
33d76271d2SHyun Kwon 
34d76271d2SHyun Kwon static int __maybe_unused zynqmp_dpsub_suspend(struct device *dev)
35d76271d2SHyun Kwon {
36d76271d2SHyun Kwon 	struct zynqmp_dpsub *dpsub = dev_get_drvdata(dev);
37d76271d2SHyun Kwon 
38d189835fSLaurent Pinchart 	if (!dpsub->drm)
39d189835fSLaurent Pinchart 		return 0;
40d189835fSLaurent Pinchart 
41d189835fSLaurent Pinchart 	return drm_mode_config_helper_suspend(&dpsub->drm->dev);
42d76271d2SHyun Kwon }
43d76271d2SHyun Kwon 
44d76271d2SHyun Kwon static int __maybe_unused zynqmp_dpsub_resume(struct device *dev)
45d76271d2SHyun Kwon {
46d76271d2SHyun Kwon 	struct zynqmp_dpsub *dpsub = dev_get_drvdata(dev);
47d76271d2SHyun Kwon 
48d189835fSLaurent Pinchart 	if (!dpsub->drm)
49d189835fSLaurent Pinchart 		return 0;
50d189835fSLaurent Pinchart 
51d189835fSLaurent Pinchart 	return drm_mode_config_helper_resume(&dpsub->drm->dev);
52d76271d2SHyun Kwon }
53d76271d2SHyun Kwon 
54d76271d2SHyun Kwon static const struct dev_pm_ops zynqmp_dpsub_pm_ops = {
55d76271d2SHyun Kwon 	SET_SYSTEM_SLEEP_PM_OPS(zynqmp_dpsub_suspend, zynqmp_dpsub_resume)
56d76271d2SHyun Kwon };
57d76271d2SHyun Kwon 
58d76271d2SHyun Kwon /* -----------------------------------------------------------------------------
59c979296eSLaurent Pinchart  * DPSUB Configuration
60c979296eSLaurent Pinchart  */
61c979296eSLaurent Pinchart 
62c979296eSLaurent Pinchart /**
63c979296eSLaurent Pinchart  * zynqmp_dpsub_audio_enabled - If the audio is enabled
64c979296eSLaurent Pinchart  * @dpsub: DisplayPort subsystem
65c979296eSLaurent Pinchart  *
66c979296eSLaurent Pinchart  * Return if the audio is enabled depending on the audio clock.
67c979296eSLaurent Pinchart  *
68c979296eSLaurent Pinchart  * Return: true if audio is enabled, or false.
69c979296eSLaurent Pinchart  */
70c979296eSLaurent Pinchart bool zynqmp_dpsub_audio_enabled(struct zynqmp_dpsub *dpsub)
71c979296eSLaurent Pinchart {
72c979296eSLaurent Pinchart 	return !!dpsub->aud_clk;
73c979296eSLaurent Pinchart }
74c979296eSLaurent Pinchart 
75c979296eSLaurent Pinchart /**
76c979296eSLaurent Pinchart  * zynqmp_dpsub_get_audio_clk_rate - Get the current audio clock rate
77c979296eSLaurent Pinchart  * @dpsub: DisplayPort subsystem
78c979296eSLaurent Pinchart  *
79c979296eSLaurent Pinchart  * Return: the current audio clock rate.
80c979296eSLaurent Pinchart  */
81c979296eSLaurent Pinchart unsigned int zynqmp_dpsub_get_audio_clk_rate(struct zynqmp_dpsub *dpsub)
82c979296eSLaurent Pinchart {
83c979296eSLaurent Pinchart 	if (zynqmp_dpsub_audio_enabled(dpsub))
84c979296eSLaurent Pinchart 		return 0;
85c979296eSLaurent Pinchart 	return clk_get_rate(dpsub->aud_clk);
86c979296eSLaurent Pinchart }
87c979296eSLaurent Pinchart 
88c979296eSLaurent Pinchart /* -----------------------------------------------------------------------------
89d76271d2SHyun Kwon  * Probe & Remove
90d76271d2SHyun Kwon  */
91d76271d2SHyun Kwon 
92d76271d2SHyun Kwon static int zynqmp_dpsub_init_clocks(struct zynqmp_dpsub *dpsub)
93d76271d2SHyun Kwon {
94d76271d2SHyun Kwon 	int ret;
95d76271d2SHyun Kwon 
96d76271d2SHyun Kwon 	dpsub->apb_clk = devm_clk_get(dpsub->dev, "dp_apb_clk");
97d76271d2SHyun Kwon 	if (IS_ERR(dpsub->apb_clk))
98d76271d2SHyun Kwon 		return PTR_ERR(dpsub->apb_clk);
99d76271d2SHyun Kwon 
100d76271d2SHyun Kwon 	ret = clk_prepare_enable(dpsub->apb_clk);
101d76271d2SHyun Kwon 	if (ret) {
102d76271d2SHyun Kwon 		dev_err(dpsub->dev, "failed to enable the APB clock\n");
103d76271d2SHyun Kwon 		return ret;
104d76271d2SHyun Kwon 	}
105d76271d2SHyun Kwon 
106c979296eSLaurent Pinchart 	/*
107c979296eSLaurent Pinchart 	 * Try the live PL video clock, and fall back to the PS clock if the
108c979296eSLaurent Pinchart 	 * live PL video clock isn't valid.
109c979296eSLaurent Pinchart 	 */
1101682ade6SLaurent Pinchart 	dpsub->vid_clk = devm_clk_get(dpsub->dev, "dp_live_video_in_clk");
1111682ade6SLaurent Pinchart 	if (!IS_ERR(dpsub->vid_clk))
1121682ade6SLaurent Pinchart 		dpsub->vid_clk_from_ps = false;
1131682ade6SLaurent Pinchart 	else if (PTR_ERR(dpsub->vid_clk) == -EPROBE_DEFER)
1141682ade6SLaurent Pinchart 		return PTR_ERR(dpsub->vid_clk);
1151682ade6SLaurent Pinchart 
1161682ade6SLaurent Pinchart 	if (IS_ERR_OR_NULL(dpsub->vid_clk)) {
1171682ade6SLaurent Pinchart 		dpsub->vid_clk = devm_clk_get(dpsub->dev, "dp_vtc_pixel_clk_in");
1181682ade6SLaurent Pinchart 		if (IS_ERR(dpsub->vid_clk)) {
1191682ade6SLaurent Pinchart 			dev_err(dpsub->dev, "failed to init any video clock\n");
1201682ade6SLaurent Pinchart 			return PTR_ERR(dpsub->vid_clk);
1211682ade6SLaurent Pinchart 		}
1221682ade6SLaurent Pinchart 		dpsub->vid_clk_from_ps = true;
1231682ade6SLaurent Pinchart 	}
1241682ade6SLaurent Pinchart 
125c979296eSLaurent Pinchart 	/*
126c979296eSLaurent Pinchart 	 * Try the live PL audio clock, and fall back to the PS clock if the
127c979296eSLaurent Pinchart 	 * live PL audio clock isn't valid. Missing audio clock disables audio
128c979296eSLaurent Pinchart 	 * but isn't an error.
129c979296eSLaurent Pinchart 	 */
130c979296eSLaurent Pinchart 	dpsub->aud_clk = devm_clk_get(dpsub->dev, "dp_live_audio_aclk");
131c979296eSLaurent Pinchart 	if (!IS_ERR(dpsub->aud_clk)) {
132c979296eSLaurent Pinchart 		dpsub->aud_clk_from_ps = false;
133c979296eSLaurent Pinchart 		return 0;
134c979296eSLaurent Pinchart 	}
135c979296eSLaurent Pinchart 
136c979296eSLaurent Pinchart 	dpsub->aud_clk = devm_clk_get(dpsub->dev, "dp_aud_clk");
137c979296eSLaurent Pinchart 	if (!IS_ERR(dpsub->aud_clk)) {
138c979296eSLaurent Pinchart 		dpsub->aud_clk_from_ps = true;
139c979296eSLaurent Pinchart 		return 0;
140c979296eSLaurent Pinchart 	}
141c979296eSLaurent Pinchart 
142c979296eSLaurent Pinchart 	dev_info(dpsub->dev, "audio disabled due to missing clock\n");
143d76271d2SHyun Kwon 	return 0;
144d76271d2SHyun Kwon }
145d76271d2SHyun Kwon 
146*52c2cf14SLaurent Pinchart static int zynqmp_dpsub_parse_dt(struct zynqmp_dpsub *dpsub)
147*52c2cf14SLaurent Pinchart {
148*52c2cf14SLaurent Pinchart 	struct device_node *np;
149*52c2cf14SLaurent Pinchart 	unsigned int i;
150*52c2cf14SLaurent Pinchart 
151*52c2cf14SLaurent Pinchart 	/*
152*52c2cf14SLaurent Pinchart 	 * For backward compatibility with old device trees that don't contain
153*52c2cf14SLaurent Pinchart 	 * ports, consider that only the DP output port is connected if no
154*52c2cf14SLaurent Pinchart 	 * ports child no exists.
155*52c2cf14SLaurent Pinchart 	 */
156*52c2cf14SLaurent Pinchart 	np = of_get_child_by_name(dpsub->dev->of_node, "ports");
157*52c2cf14SLaurent Pinchart 	of_node_put(np);
158*52c2cf14SLaurent Pinchart 	if (!np) {
159*52c2cf14SLaurent Pinchart 		dev_warn(dpsub->dev, "missing ports, update DT bindings\n");
160*52c2cf14SLaurent Pinchart 		dpsub->connected_ports = BIT(ZYNQMP_DPSUB_PORT_OUT_DP);
161*52c2cf14SLaurent Pinchart 		return 0;
162*52c2cf14SLaurent Pinchart 	}
163*52c2cf14SLaurent Pinchart 
164*52c2cf14SLaurent Pinchart 	/* Check which ports are connected. */
165*52c2cf14SLaurent Pinchart 	for (i = 0; i < ZYNQMP_DPSUB_NUM_PORTS; ++i) {
166*52c2cf14SLaurent Pinchart 		struct device_node *np;
167*52c2cf14SLaurent Pinchart 
168*52c2cf14SLaurent Pinchart 		np = of_graph_get_remote_node(dpsub->dev->of_node, i, -1);
169*52c2cf14SLaurent Pinchart 		if (np) {
170*52c2cf14SLaurent Pinchart 			dpsub->connected_ports |= BIT(i);
171*52c2cf14SLaurent Pinchart 			of_node_put(np);
172*52c2cf14SLaurent Pinchart 		}
173*52c2cf14SLaurent Pinchart 	}
174*52c2cf14SLaurent Pinchart 
175*52c2cf14SLaurent Pinchart 	/* Sanity checks. */
176*52c2cf14SLaurent Pinchart 	if ((dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_LIVE_VIDEO)) ||
177*52c2cf14SLaurent Pinchart 	    (dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_LIVE_GFX)))
178*52c2cf14SLaurent Pinchart 		dev_warn(dpsub->dev, "live video unsupported, ignoring\n");
179*52c2cf14SLaurent Pinchart 
180*52c2cf14SLaurent Pinchart 	if (dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_LIVE_AUDIO))
181*52c2cf14SLaurent Pinchart 		dev_warn(dpsub->dev, "live audio unsupported, ignoring\n");
182*52c2cf14SLaurent Pinchart 
183*52c2cf14SLaurent Pinchart 	if ((dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_OUT_VIDEO)) ||
184*52c2cf14SLaurent Pinchart 	    (dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_OUT_AUDIO)))
185*52c2cf14SLaurent Pinchart 		dev_warn(dpsub->dev, "output to PL unsupported, ignoring\n");
186*52c2cf14SLaurent Pinchart 
187*52c2cf14SLaurent Pinchart 	if (!(dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_OUT_DP))) {
188*52c2cf14SLaurent Pinchart 		dev_err(dpsub->dev, "DP output port not connected\n");
189*52c2cf14SLaurent Pinchart 		return -EINVAL;
190*52c2cf14SLaurent Pinchart 	}
191*52c2cf14SLaurent Pinchart 
192*52c2cf14SLaurent Pinchart 	return 0;
193*52c2cf14SLaurent Pinchart }
194*52c2cf14SLaurent Pinchart 
195d189835fSLaurent Pinchart void zynqmp_dpsub_release(struct zynqmp_dpsub *dpsub)
1966ca91bb4SLaurent Pinchart {
1976ca91bb4SLaurent Pinchart 	kfree(dpsub->disp);
1986ca91bb4SLaurent Pinchart 	kfree(dpsub->dp);
199d189835fSLaurent Pinchart 	kfree(dpsub);
2006ca91bb4SLaurent Pinchart }
2016ca91bb4SLaurent Pinchart 
202d76271d2SHyun Kwon static int zynqmp_dpsub_probe(struct platform_device *pdev)
203d76271d2SHyun Kwon {
204d76271d2SHyun Kwon 	struct zynqmp_dpsub *dpsub;
205d76271d2SHyun Kwon 	int ret;
206d76271d2SHyun Kwon 
207d76271d2SHyun Kwon 	/* Allocate private data. */
208d189835fSLaurent Pinchart 	dpsub = kzalloc(sizeof(*dpsub), GFP_KERNEL);
209d189835fSLaurent Pinchart 	if (!dpsub)
210d189835fSLaurent Pinchart 		return -ENOMEM;
2116ca91bb4SLaurent Pinchart 
212d76271d2SHyun Kwon 	dpsub->dev = &pdev->dev;
213d76271d2SHyun Kwon 	platform_set_drvdata(pdev, dpsub);
214d76271d2SHyun Kwon 
215d76271d2SHyun Kwon 	dma_set_mask(dpsub->dev, DMA_BIT_MASK(ZYNQMP_DISP_MAX_DMA_BIT));
216d76271d2SHyun Kwon 
217d76271d2SHyun Kwon 	/* Try the reserved memory. Proceed if there's none. */
218d76271d2SHyun Kwon 	of_reserved_mem_device_init(&pdev->dev);
219d76271d2SHyun Kwon 
220d76271d2SHyun Kwon 	ret = zynqmp_dpsub_init_clocks(dpsub);
221d76271d2SHyun Kwon 	if (ret < 0)
222d76271d2SHyun Kwon 		goto err_mem;
223d76271d2SHyun Kwon 
224*52c2cf14SLaurent Pinchart 	ret = zynqmp_dpsub_parse_dt(dpsub);
225*52c2cf14SLaurent Pinchart 	if (ret < 0)
226*52c2cf14SLaurent Pinchart 		goto err_mem;
227*52c2cf14SLaurent Pinchart 
228d76271d2SHyun Kwon 	pm_runtime_enable(&pdev->dev);
229d76271d2SHyun Kwon 
230d76271d2SHyun Kwon 	/*
231d76271d2SHyun Kwon 	 * DP should be probed first so that the zynqmp_disp can set the output
232d76271d2SHyun Kwon 	 * format accordingly.
233d76271d2SHyun Kwon 	 */
2346ca91bb4SLaurent Pinchart 	ret = zynqmp_dp_probe(dpsub);
235d76271d2SHyun Kwon 	if (ret)
236d76271d2SHyun Kwon 		goto err_pm;
237d76271d2SHyun Kwon 
2386ca91bb4SLaurent Pinchart 	ret = zynqmp_disp_probe(dpsub);
239d76271d2SHyun Kwon 	if (ret)
240d76271d2SHyun Kwon 		goto err_dp;
241d76271d2SHyun Kwon 
242d76271d2SHyun Kwon 	ret = zynqmp_dpsub_drm_init(dpsub);
243d76271d2SHyun Kwon 	if (ret)
244d76271d2SHyun Kwon 		goto err_disp;
245d76271d2SHyun Kwon 
246d76271d2SHyun Kwon 	dev_info(&pdev->dev, "ZynqMP DisplayPort Subsystem driver probed");
247d76271d2SHyun Kwon 
248d76271d2SHyun Kwon 	return 0;
249d76271d2SHyun Kwon 
250d76271d2SHyun Kwon err_disp:
251d76271d2SHyun Kwon 	zynqmp_disp_remove(dpsub);
252d76271d2SHyun Kwon err_dp:
253d76271d2SHyun Kwon 	zynqmp_dp_remove(dpsub);
254d76271d2SHyun Kwon err_pm:
255d76271d2SHyun Kwon 	pm_runtime_disable(&pdev->dev);
256d76271d2SHyun Kwon 	clk_disable_unprepare(dpsub->apb_clk);
257d76271d2SHyun Kwon err_mem:
258d76271d2SHyun Kwon 	of_reserved_mem_device_release(&pdev->dev);
259d189835fSLaurent Pinchart 	if (!dpsub->drm)
260d189835fSLaurent Pinchart 		zynqmp_dpsub_release(dpsub);
261d76271d2SHyun Kwon 	return ret;
262d76271d2SHyun Kwon }
263d76271d2SHyun Kwon 
264d76271d2SHyun Kwon static int zynqmp_dpsub_remove(struct platform_device *pdev)
265d76271d2SHyun Kwon {
266d76271d2SHyun Kwon 	struct zynqmp_dpsub *dpsub = platform_get_drvdata(pdev);
267d76271d2SHyun Kwon 
268d189835fSLaurent Pinchart 	if (dpsub->drm)
269074ef0ceSLaurent Pinchart 		zynqmp_dpsub_drm_cleanup(dpsub);
270d76271d2SHyun Kwon 
271d76271d2SHyun Kwon 	zynqmp_disp_remove(dpsub);
272d76271d2SHyun Kwon 	zynqmp_dp_remove(dpsub);
273d76271d2SHyun Kwon 
274d76271d2SHyun Kwon 	pm_runtime_disable(&pdev->dev);
275d76271d2SHyun Kwon 	clk_disable_unprepare(dpsub->apb_clk);
276d76271d2SHyun Kwon 	of_reserved_mem_device_release(&pdev->dev);
277d76271d2SHyun Kwon 
278d189835fSLaurent Pinchart 	if (!dpsub->drm)
279d189835fSLaurent Pinchart 		zynqmp_dpsub_release(dpsub);
280d189835fSLaurent Pinchart 
281d76271d2SHyun Kwon 	return 0;
282d76271d2SHyun Kwon }
283d76271d2SHyun Kwon 
284d76271d2SHyun Kwon static void zynqmp_dpsub_shutdown(struct platform_device *pdev)
285d76271d2SHyun Kwon {
286d76271d2SHyun Kwon 	struct zynqmp_dpsub *dpsub = platform_get_drvdata(pdev);
287d76271d2SHyun Kwon 
288d189835fSLaurent Pinchart 	if (!dpsub->drm)
289d189835fSLaurent Pinchart 		return;
290d189835fSLaurent Pinchart 
291d189835fSLaurent Pinchart 	drm_atomic_helper_shutdown(&dpsub->drm->dev);
292d76271d2SHyun Kwon }
293d76271d2SHyun Kwon 
294d76271d2SHyun Kwon static const struct of_device_id zynqmp_dpsub_of_match[] = {
295d76271d2SHyun Kwon 	{ .compatible = "xlnx,zynqmp-dpsub-1.7", },
296d76271d2SHyun Kwon 	{ /* end of table */ },
297d76271d2SHyun Kwon };
298d76271d2SHyun Kwon MODULE_DEVICE_TABLE(of, zynqmp_dpsub_of_match);
299d76271d2SHyun Kwon 
300d76271d2SHyun Kwon static struct platform_driver zynqmp_dpsub_driver = {
301d76271d2SHyun Kwon 	.probe			= zynqmp_dpsub_probe,
302d76271d2SHyun Kwon 	.remove			= zynqmp_dpsub_remove,
303d76271d2SHyun Kwon 	.shutdown		= zynqmp_dpsub_shutdown,
304d76271d2SHyun Kwon 	.driver			= {
305d76271d2SHyun Kwon 		.name		= "zynqmp-dpsub",
306d76271d2SHyun Kwon 		.pm		= &zynqmp_dpsub_pm_ops,
307d76271d2SHyun Kwon 		.of_match_table	= zynqmp_dpsub_of_match,
308d76271d2SHyun Kwon 	},
309d76271d2SHyun Kwon };
310d76271d2SHyun Kwon 
311fad54534SJavier Martinez Canillas drm_module_platform_driver(zynqmp_dpsub_driver);
312d76271d2SHyun Kwon 
313d76271d2SHyun Kwon MODULE_AUTHOR("Xilinx, Inc.");
314d76271d2SHyun Kwon MODULE_DESCRIPTION("ZynqMP DP Subsystem Driver");
315d76271d2SHyun Kwon MODULE_LICENSE("GPL v2");
316