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>
1552c2cf14SLaurent 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 
14652c2cf14SLaurent Pinchart static int zynqmp_dpsub_parse_dt(struct zynqmp_dpsub *dpsub)
14752c2cf14SLaurent Pinchart {
14852c2cf14SLaurent Pinchart 	struct device_node *np;
14952c2cf14SLaurent Pinchart 	unsigned int i;
15052c2cf14SLaurent Pinchart 
15152c2cf14SLaurent Pinchart 	/*
15252c2cf14SLaurent Pinchart 	 * For backward compatibility with old device trees that don't contain
15352c2cf14SLaurent Pinchart 	 * ports, consider that only the DP output port is connected if no
15452c2cf14SLaurent Pinchart 	 * ports child no exists.
15552c2cf14SLaurent Pinchart 	 */
15652c2cf14SLaurent Pinchart 	np = of_get_child_by_name(dpsub->dev->of_node, "ports");
15752c2cf14SLaurent Pinchart 	of_node_put(np);
15852c2cf14SLaurent Pinchart 	if (!np) {
15952c2cf14SLaurent Pinchart 		dev_warn(dpsub->dev, "missing ports, update DT bindings\n");
16052c2cf14SLaurent Pinchart 		dpsub->connected_ports = BIT(ZYNQMP_DPSUB_PORT_OUT_DP);
161*51ae3bd4SLaurent Pinchart 		dpsub->dma_enabled = true;
16252c2cf14SLaurent Pinchart 		return 0;
16352c2cf14SLaurent Pinchart 	}
16452c2cf14SLaurent Pinchart 
16552c2cf14SLaurent Pinchart 	/* Check which ports are connected. */
16652c2cf14SLaurent Pinchart 	for (i = 0; i < ZYNQMP_DPSUB_NUM_PORTS; ++i) {
16752c2cf14SLaurent Pinchart 		struct device_node *np;
16852c2cf14SLaurent Pinchart 
16952c2cf14SLaurent Pinchart 		np = of_graph_get_remote_node(dpsub->dev->of_node, i, -1);
17052c2cf14SLaurent Pinchart 		if (np) {
17152c2cf14SLaurent Pinchart 			dpsub->connected_ports |= BIT(i);
17252c2cf14SLaurent Pinchart 			of_node_put(np);
17352c2cf14SLaurent Pinchart 		}
17452c2cf14SLaurent Pinchart 	}
17552c2cf14SLaurent Pinchart 
17652c2cf14SLaurent Pinchart 	/* Sanity checks. */
17752c2cf14SLaurent Pinchart 	if ((dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_LIVE_VIDEO)) ||
17852c2cf14SLaurent Pinchart 	    (dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_LIVE_GFX)))
17952c2cf14SLaurent Pinchart 		dev_warn(dpsub->dev, "live video unsupported, ignoring\n");
18052c2cf14SLaurent Pinchart 
181*51ae3bd4SLaurent Pinchart 	dpsub->dma_enabled = true;
182*51ae3bd4SLaurent Pinchart 
18352c2cf14SLaurent Pinchart 	if (dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_LIVE_AUDIO))
18452c2cf14SLaurent Pinchart 		dev_warn(dpsub->dev, "live audio unsupported, ignoring\n");
18552c2cf14SLaurent Pinchart 
18652c2cf14SLaurent Pinchart 	if ((dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_OUT_VIDEO)) ||
18752c2cf14SLaurent Pinchart 	    (dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_OUT_AUDIO)))
18852c2cf14SLaurent Pinchart 		dev_warn(dpsub->dev, "output to PL unsupported, ignoring\n");
18952c2cf14SLaurent Pinchart 
19052c2cf14SLaurent Pinchart 	if (!(dpsub->connected_ports & BIT(ZYNQMP_DPSUB_PORT_OUT_DP))) {
19152c2cf14SLaurent Pinchart 		dev_err(dpsub->dev, "DP output port not connected\n");
19252c2cf14SLaurent Pinchart 		return -EINVAL;
19352c2cf14SLaurent Pinchart 	}
19452c2cf14SLaurent Pinchart 
19552c2cf14SLaurent Pinchart 	return 0;
19652c2cf14SLaurent Pinchart }
19752c2cf14SLaurent Pinchart 
198d189835fSLaurent Pinchart void zynqmp_dpsub_release(struct zynqmp_dpsub *dpsub)
1996ca91bb4SLaurent Pinchart {
2006ca91bb4SLaurent Pinchart 	kfree(dpsub->disp);
2016ca91bb4SLaurent Pinchart 	kfree(dpsub->dp);
202d189835fSLaurent Pinchart 	kfree(dpsub);
2036ca91bb4SLaurent Pinchart }
2046ca91bb4SLaurent Pinchart 
205d76271d2SHyun Kwon static int zynqmp_dpsub_probe(struct platform_device *pdev)
206d76271d2SHyun Kwon {
207d76271d2SHyun Kwon 	struct zynqmp_dpsub *dpsub;
208d76271d2SHyun Kwon 	int ret;
209d76271d2SHyun Kwon 
210d76271d2SHyun Kwon 	/* Allocate private data. */
211d189835fSLaurent Pinchart 	dpsub = kzalloc(sizeof(*dpsub), GFP_KERNEL);
212d189835fSLaurent Pinchart 	if (!dpsub)
213d189835fSLaurent Pinchart 		return -ENOMEM;
2146ca91bb4SLaurent Pinchart 
215d76271d2SHyun Kwon 	dpsub->dev = &pdev->dev;
216d76271d2SHyun Kwon 	platform_set_drvdata(pdev, dpsub);
217d76271d2SHyun Kwon 
218d76271d2SHyun Kwon 	dma_set_mask(dpsub->dev, DMA_BIT_MASK(ZYNQMP_DISP_MAX_DMA_BIT));
219d76271d2SHyun Kwon 
220d76271d2SHyun Kwon 	/* Try the reserved memory. Proceed if there's none. */
221d76271d2SHyun Kwon 	of_reserved_mem_device_init(&pdev->dev);
222d76271d2SHyun Kwon 
223d76271d2SHyun Kwon 	ret = zynqmp_dpsub_init_clocks(dpsub);
224d76271d2SHyun Kwon 	if (ret < 0)
225d76271d2SHyun Kwon 		goto err_mem;
226d76271d2SHyun Kwon 
22752c2cf14SLaurent Pinchart 	ret = zynqmp_dpsub_parse_dt(dpsub);
22852c2cf14SLaurent Pinchart 	if (ret < 0)
22952c2cf14SLaurent Pinchart 		goto err_mem;
23052c2cf14SLaurent Pinchart 
231d76271d2SHyun Kwon 	pm_runtime_enable(&pdev->dev);
232d76271d2SHyun Kwon 
233d76271d2SHyun Kwon 	/*
234d76271d2SHyun Kwon 	 * DP should be probed first so that the zynqmp_disp can set the output
235d76271d2SHyun Kwon 	 * format accordingly.
236d76271d2SHyun Kwon 	 */
2376ca91bb4SLaurent Pinchart 	ret = zynqmp_dp_probe(dpsub);
238d76271d2SHyun Kwon 	if (ret)
239d76271d2SHyun Kwon 		goto err_pm;
240d76271d2SHyun Kwon 
2416ca91bb4SLaurent Pinchart 	ret = zynqmp_disp_probe(dpsub);
242d76271d2SHyun Kwon 	if (ret)
243d76271d2SHyun Kwon 		goto err_dp;
244d76271d2SHyun Kwon 
245d76271d2SHyun Kwon 	ret = zynqmp_dpsub_drm_init(dpsub);
246d76271d2SHyun Kwon 	if (ret)
247d76271d2SHyun Kwon 		goto err_disp;
248d76271d2SHyun Kwon 
249d76271d2SHyun Kwon 	dev_info(&pdev->dev, "ZynqMP DisplayPort Subsystem driver probed");
250d76271d2SHyun Kwon 
251d76271d2SHyun Kwon 	return 0;
252d76271d2SHyun Kwon 
253d76271d2SHyun Kwon err_disp:
254d76271d2SHyun Kwon 	zynqmp_disp_remove(dpsub);
255d76271d2SHyun Kwon err_dp:
256d76271d2SHyun Kwon 	zynqmp_dp_remove(dpsub);
257d76271d2SHyun Kwon err_pm:
258d76271d2SHyun Kwon 	pm_runtime_disable(&pdev->dev);
259d76271d2SHyun Kwon 	clk_disable_unprepare(dpsub->apb_clk);
260d76271d2SHyun Kwon err_mem:
261d76271d2SHyun Kwon 	of_reserved_mem_device_release(&pdev->dev);
262d189835fSLaurent Pinchart 	if (!dpsub->drm)
263d189835fSLaurent Pinchart 		zynqmp_dpsub_release(dpsub);
264d76271d2SHyun Kwon 	return ret;
265d76271d2SHyun Kwon }
266d76271d2SHyun Kwon 
267d76271d2SHyun Kwon static int zynqmp_dpsub_remove(struct platform_device *pdev)
268d76271d2SHyun Kwon {
269d76271d2SHyun Kwon 	struct zynqmp_dpsub *dpsub = platform_get_drvdata(pdev);
270d76271d2SHyun Kwon 
271d189835fSLaurent Pinchart 	if (dpsub->drm)
272074ef0ceSLaurent Pinchart 		zynqmp_dpsub_drm_cleanup(dpsub);
273d76271d2SHyun Kwon 
274d76271d2SHyun Kwon 	zynqmp_disp_remove(dpsub);
275d76271d2SHyun Kwon 	zynqmp_dp_remove(dpsub);
276d76271d2SHyun Kwon 
277d76271d2SHyun Kwon 	pm_runtime_disable(&pdev->dev);
278d76271d2SHyun Kwon 	clk_disable_unprepare(dpsub->apb_clk);
279d76271d2SHyun Kwon 	of_reserved_mem_device_release(&pdev->dev);
280d76271d2SHyun Kwon 
281d189835fSLaurent Pinchart 	if (!dpsub->drm)
282d189835fSLaurent Pinchart 		zynqmp_dpsub_release(dpsub);
283d189835fSLaurent Pinchart 
284d76271d2SHyun Kwon 	return 0;
285d76271d2SHyun Kwon }
286d76271d2SHyun Kwon 
287d76271d2SHyun Kwon static void zynqmp_dpsub_shutdown(struct platform_device *pdev)
288d76271d2SHyun Kwon {
289d76271d2SHyun Kwon 	struct zynqmp_dpsub *dpsub = platform_get_drvdata(pdev);
290d76271d2SHyun Kwon 
291d189835fSLaurent Pinchart 	if (!dpsub->drm)
292d189835fSLaurent Pinchart 		return;
293d189835fSLaurent Pinchart 
294d189835fSLaurent Pinchart 	drm_atomic_helper_shutdown(&dpsub->drm->dev);
295d76271d2SHyun Kwon }
296d76271d2SHyun Kwon 
297d76271d2SHyun Kwon static const struct of_device_id zynqmp_dpsub_of_match[] = {
298d76271d2SHyun Kwon 	{ .compatible = "xlnx,zynqmp-dpsub-1.7", },
299d76271d2SHyun Kwon 	{ /* end of table */ },
300d76271d2SHyun Kwon };
301d76271d2SHyun Kwon MODULE_DEVICE_TABLE(of, zynqmp_dpsub_of_match);
302d76271d2SHyun Kwon 
303d76271d2SHyun Kwon static struct platform_driver zynqmp_dpsub_driver = {
304d76271d2SHyun Kwon 	.probe			= zynqmp_dpsub_probe,
305d76271d2SHyun Kwon 	.remove			= zynqmp_dpsub_remove,
306d76271d2SHyun Kwon 	.shutdown		= zynqmp_dpsub_shutdown,
307d76271d2SHyun Kwon 	.driver			= {
308d76271d2SHyun Kwon 		.name		= "zynqmp-dpsub",
309d76271d2SHyun Kwon 		.pm		= &zynqmp_dpsub_pm_ops,
310d76271d2SHyun Kwon 		.of_match_table	= zynqmp_dpsub_of_match,
311d76271d2SHyun Kwon 	},
312d76271d2SHyun Kwon };
313d76271d2SHyun Kwon 
314fad54534SJavier Martinez Canillas drm_module_platform_driver(zynqmp_dpsub_driver);
315d76271d2SHyun Kwon 
316d76271d2SHyun Kwon MODULE_AUTHOR("Xilinx, Inc.");
317d76271d2SHyun Kwon MODULE_DESCRIPTION("ZynqMP DP Subsystem Driver");
318d76271d2SHyun Kwon MODULE_LICENSE("GPL v2");
319