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>
15d76271d2SHyun Kwon #include <linux/of_reserved_mem.h>
16d76271d2SHyun Kwon #include <linux/platform_device.h>
17d76271d2SHyun Kwon #include <linux/pm_runtime.h>
186ca91bb4SLaurent Pinchart #include <linux/slab.h>
19d76271d2SHyun Kwon 
20d76271d2SHyun Kwon #include <drm/drm_atomic_helper.h>
21d76271d2SHyun Kwon #include <drm/drm_drv.h>
22d76271d2SHyun Kwon #include <drm/drm_managed.h>
23*074ef0ceSLaurent Pinchart #include <drm/drm_modeset_helper.h>
24fad54534SJavier Martinez Canillas #include <drm/drm_module.h>
25d76271d2SHyun Kwon 
26d76271d2SHyun Kwon #include "zynqmp_disp.h"
27d76271d2SHyun Kwon #include "zynqmp_dp.h"
28d76271d2SHyun Kwon #include "zynqmp_dpsub.h"
2976c8eeb7SLaurent Pinchart #include "zynqmp_kms.h"
30d76271d2SHyun Kwon 
31d76271d2SHyun Kwon /* -----------------------------------------------------------------------------
32d76271d2SHyun Kwon  * Power Management
33d76271d2SHyun Kwon  */
34d76271d2SHyun Kwon 
35d76271d2SHyun Kwon static int __maybe_unused zynqmp_dpsub_suspend(struct device *dev)
36d76271d2SHyun Kwon {
37d76271d2SHyun Kwon 	struct zynqmp_dpsub *dpsub = dev_get_drvdata(dev);
38d76271d2SHyun Kwon 
39d76271d2SHyun Kwon 	return drm_mode_config_helper_suspend(&dpsub->drm);
40d76271d2SHyun Kwon }
41d76271d2SHyun Kwon 
42d76271d2SHyun Kwon static int __maybe_unused zynqmp_dpsub_resume(struct device *dev)
43d76271d2SHyun Kwon {
44d76271d2SHyun Kwon 	struct zynqmp_dpsub *dpsub = dev_get_drvdata(dev);
45d76271d2SHyun Kwon 
46d76271d2SHyun Kwon 	return drm_mode_config_helper_resume(&dpsub->drm);
47d76271d2SHyun Kwon }
48d76271d2SHyun Kwon 
49d76271d2SHyun Kwon static const struct dev_pm_ops zynqmp_dpsub_pm_ops = {
50d76271d2SHyun Kwon 	SET_SYSTEM_SLEEP_PM_OPS(zynqmp_dpsub_suspend, zynqmp_dpsub_resume)
51d76271d2SHyun Kwon };
52d76271d2SHyun Kwon 
53d76271d2SHyun Kwon /* -----------------------------------------------------------------------------
54c979296eSLaurent Pinchart  * DPSUB Configuration
55c979296eSLaurent Pinchart  */
56c979296eSLaurent Pinchart 
57c979296eSLaurent Pinchart /**
58c979296eSLaurent Pinchart  * zynqmp_dpsub_audio_enabled - If the audio is enabled
59c979296eSLaurent Pinchart  * @dpsub: DisplayPort subsystem
60c979296eSLaurent Pinchart  *
61c979296eSLaurent Pinchart  * Return if the audio is enabled depending on the audio clock.
62c979296eSLaurent Pinchart  *
63c979296eSLaurent Pinchart  * Return: true if audio is enabled, or false.
64c979296eSLaurent Pinchart  */
65c979296eSLaurent Pinchart bool zynqmp_dpsub_audio_enabled(struct zynqmp_dpsub *dpsub)
66c979296eSLaurent Pinchart {
67c979296eSLaurent Pinchart 	return !!dpsub->aud_clk;
68c979296eSLaurent Pinchart }
69c979296eSLaurent Pinchart 
70c979296eSLaurent Pinchart /**
71c979296eSLaurent Pinchart  * zynqmp_dpsub_get_audio_clk_rate - Get the current audio clock rate
72c979296eSLaurent Pinchart  * @dpsub: DisplayPort subsystem
73c979296eSLaurent Pinchart  *
74c979296eSLaurent Pinchart  * Return: the current audio clock rate.
75c979296eSLaurent Pinchart  */
76c979296eSLaurent Pinchart unsigned int zynqmp_dpsub_get_audio_clk_rate(struct zynqmp_dpsub *dpsub)
77c979296eSLaurent Pinchart {
78c979296eSLaurent Pinchart 	if (zynqmp_dpsub_audio_enabled(dpsub))
79c979296eSLaurent Pinchart 		return 0;
80c979296eSLaurent Pinchart 	return clk_get_rate(dpsub->aud_clk);
81c979296eSLaurent Pinchart }
82c979296eSLaurent Pinchart 
83c979296eSLaurent Pinchart /* -----------------------------------------------------------------------------
84d76271d2SHyun Kwon  * Probe & Remove
85d76271d2SHyun Kwon  */
86d76271d2SHyun Kwon 
87d76271d2SHyun Kwon static int zynqmp_dpsub_init_clocks(struct zynqmp_dpsub *dpsub)
88d76271d2SHyun Kwon {
89d76271d2SHyun Kwon 	int ret;
90d76271d2SHyun Kwon 
91d76271d2SHyun Kwon 	dpsub->apb_clk = devm_clk_get(dpsub->dev, "dp_apb_clk");
92d76271d2SHyun Kwon 	if (IS_ERR(dpsub->apb_clk))
93d76271d2SHyun Kwon 		return PTR_ERR(dpsub->apb_clk);
94d76271d2SHyun Kwon 
95d76271d2SHyun Kwon 	ret = clk_prepare_enable(dpsub->apb_clk);
96d76271d2SHyun Kwon 	if (ret) {
97d76271d2SHyun Kwon 		dev_err(dpsub->dev, "failed to enable the APB clock\n");
98d76271d2SHyun Kwon 		return ret;
99d76271d2SHyun Kwon 	}
100d76271d2SHyun Kwon 
101c979296eSLaurent Pinchart 	/*
102c979296eSLaurent Pinchart 	 * Try the live PL video clock, and fall back to the PS clock if the
103c979296eSLaurent Pinchart 	 * live PL video clock isn't valid.
104c979296eSLaurent Pinchart 	 */
1051682ade6SLaurent Pinchart 	dpsub->vid_clk = devm_clk_get(dpsub->dev, "dp_live_video_in_clk");
1061682ade6SLaurent Pinchart 	if (!IS_ERR(dpsub->vid_clk))
1071682ade6SLaurent Pinchart 		dpsub->vid_clk_from_ps = false;
1081682ade6SLaurent Pinchart 	else if (PTR_ERR(dpsub->vid_clk) == -EPROBE_DEFER)
1091682ade6SLaurent Pinchart 		return PTR_ERR(dpsub->vid_clk);
1101682ade6SLaurent Pinchart 
1111682ade6SLaurent Pinchart 	if (IS_ERR_OR_NULL(dpsub->vid_clk)) {
1121682ade6SLaurent Pinchart 		dpsub->vid_clk = devm_clk_get(dpsub->dev, "dp_vtc_pixel_clk_in");
1131682ade6SLaurent Pinchart 		if (IS_ERR(dpsub->vid_clk)) {
1141682ade6SLaurent Pinchart 			dev_err(dpsub->dev, "failed to init any video clock\n");
1151682ade6SLaurent Pinchart 			return PTR_ERR(dpsub->vid_clk);
1161682ade6SLaurent Pinchart 		}
1171682ade6SLaurent Pinchart 		dpsub->vid_clk_from_ps = true;
1181682ade6SLaurent Pinchart 	}
1191682ade6SLaurent Pinchart 
120c979296eSLaurent Pinchart 	/*
121c979296eSLaurent Pinchart 	 * Try the live PL audio clock, and fall back to the PS clock if the
122c979296eSLaurent Pinchart 	 * live PL audio clock isn't valid. Missing audio clock disables audio
123c979296eSLaurent Pinchart 	 * but isn't an error.
124c979296eSLaurent Pinchart 	 */
125c979296eSLaurent Pinchart 	dpsub->aud_clk = devm_clk_get(dpsub->dev, "dp_live_audio_aclk");
126c979296eSLaurent Pinchart 	if (!IS_ERR(dpsub->aud_clk)) {
127c979296eSLaurent Pinchart 		dpsub->aud_clk_from_ps = false;
128c979296eSLaurent Pinchart 		return 0;
129c979296eSLaurent Pinchart 	}
130c979296eSLaurent Pinchart 
131c979296eSLaurent Pinchart 	dpsub->aud_clk = devm_clk_get(dpsub->dev, "dp_aud_clk");
132c979296eSLaurent Pinchart 	if (!IS_ERR(dpsub->aud_clk)) {
133c979296eSLaurent Pinchart 		dpsub->aud_clk_from_ps = true;
134c979296eSLaurent Pinchart 		return 0;
135c979296eSLaurent Pinchart 	}
136c979296eSLaurent Pinchart 
137c979296eSLaurent Pinchart 	dev_info(dpsub->dev, "audio disabled due to missing clock\n");
138d76271d2SHyun Kwon 	return 0;
139d76271d2SHyun Kwon }
140d76271d2SHyun Kwon 
1416ca91bb4SLaurent Pinchart static void zynqmp_dpsub_release(struct drm_device *drm, void *res)
1426ca91bb4SLaurent Pinchart {
1436ca91bb4SLaurent Pinchart 	struct zynqmp_dpsub *dpsub = res;
1446ca91bb4SLaurent Pinchart 
1456ca91bb4SLaurent Pinchart 	kfree(dpsub->disp);
1466ca91bb4SLaurent Pinchart 	kfree(dpsub->dp);
1476ca91bb4SLaurent Pinchart }
1486ca91bb4SLaurent Pinchart 
149d76271d2SHyun Kwon static int zynqmp_dpsub_probe(struct platform_device *pdev)
150d76271d2SHyun Kwon {
151d76271d2SHyun Kwon 	struct zynqmp_dpsub *dpsub;
152d76271d2SHyun Kwon 	int ret;
153d76271d2SHyun Kwon 
154d76271d2SHyun Kwon 	/* Allocate private data. */
155075342eaSDaniel Vetter 	dpsub = devm_drm_dev_alloc(&pdev->dev, &zynqmp_dpsub_drm_driver,
156075342eaSDaniel Vetter 				   struct zynqmp_dpsub, drm);
157075342eaSDaniel Vetter 	if (IS_ERR(dpsub))
158075342eaSDaniel Vetter 		return PTR_ERR(dpsub);
159d76271d2SHyun Kwon 
1606ca91bb4SLaurent Pinchart 	ret = drmm_add_action(&dpsub->drm, zynqmp_dpsub_release, dpsub);
1616ca91bb4SLaurent Pinchart 	if (ret < 0)
1626ca91bb4SLaurent Pinchart 		return ret;
1636ca91bb4SLaurent Pinchart 
164d76271d2SHyun Kwon 	dpsub->dev = &pdev->dev;
165d76271d2SHyun Kwon 	platform_set_drvdata(pdev, dpsub);
166d76271d2SHyun Kwon 
167d76271d2SHyun Kwon 	dma_set_mask(dpsub->dev, DMA_BIT_MASK(ZYNQMP_DISP_MAX_DMA_BIT));
168d76271d2SHyun Kwon 
169d76271d2SHyun Kwon 	/* Try the reserved memory. Proceed if there's none. */
170d76271d2SHyun Kwon 	of_reserved_mem_device_init(&pdev->dev);
171d76271d2SHyun Kwon 
172d76271d2SHyun Kwon 	ret = zynqmp_dpsub_init_clocks(dpsub);
173d76271d2SHyun Kwon 	if (ret < 0)
174d76271d2SHyun Kwon 		goto err_mem;
175d76271d2SHyun Kwon 
176d76271d2SHyun Kwon 	pm_runtime_enable(&pdev->dev);
177d76271d2SHyun Kwon 
178d76271d2SHyun Kwon 	/*
179d76271d2SHyun Kwon 	 * DP should be probed first so that the zynqmp_disp can set the output
180d76271d2SHyun Kwon 	 * format accordingly.
181d76271d2SHyun Kwon 	 */
1826ca91bb4SLaurent Pinchart 	ret = zynqmp_dp_probe(dpsub);
183d76271d2SHyun Kwon 	if (ret)
184d76271d2SHyun Kwon 		goto err_pm;
185d76271d2SHyun Kwon 
1866ca91bb4SLaurent Pinchart 	ret = zynqmp_disp_probe(dpsub);
187d76271d2SHyun Kwon 	if (ret)
188d76271d2SHyun Kwon 		goto err_dp;
189d76271d2SHyun Kwon 
190d76271d2SHyun Kwon 	ret = zynqmp_dpsub_drm_init(dpsub);
191d76271d2SHyun Kwon 	if (ret)
192d76271d2SHyun Kwon 		goto err_disp;
193d76271d2SHyun Kwon 
194d76271d2SHyun Kwon 	dev_info(&pdev->dev, "ZynqMP DisplayPort Subsystem driver probed");
195d76271d2SHyun Kwon 
196d76271d2SHyun Kwon 	return 0;
197d76271d2SHyun Kwon 
198d76271d2SHyun Kwon err_disp:
199d76271d2SHyun Kwon 	zynqmp_disp_remove(dpsub);
200d76271d2SHyun Kwon err_dp:
201d76271d2SHyun Kwon 	zynqmp_dp_remove(dpsub);
202d76271d2SHyun Kwon err_pm:
203d76271d2SHyun Kwon 	pm_runtime_disable(&pdev->dev);
204d76271d2SHyun Kwon 	clk_disable_unprepare(dpsub->apb_clk);
205d76271d2SHyun Kwon err_mem:
206d76271d2SHyun Kwon 	of_reserved_mem_device_release(&pdev->dev);
207d76271d2SHyun Kwon 	return ret;
208d76271d2SHyun Kwon }
209d76271d2SHyun Kwon 
210d76271d2SHyun Kwon static int zynqmp_dpsub_remove(struct platform_device *pdev)
211d76271d2SHyun Kwon {
212d76271d2SHyun Kwon 	struct zynqmp_dpsub *dpsub = platform_get_drvdata(pdev);
213d76271d2SHyun Kwon 
214*074ef0ceSLaurent Pinchart 	zynqmp_dpsub_drm_cleanup(dpsub);
215d76271d2SHyun Kwon 
216d76271d2SHyun Kwon 	zynqmp_disp_remove(dpsub);
217d76271d2SHyun Kwon 	zynqmp_dp_remove(dpsub);
218d76271d2SHyun Kwon 
219d76271d2SHyun Kwon 	pm_runtime_disable(&pdev->dev);
220d76271d2SHyun Kwon 	clk_disable_unprepare(dpsub->apb_clk);
221d76271d2SHyun Kwon 	of_reserved_mem_device_release(&pdev->dev);
222d76271d2SHyun Kwon 
223d76271d2SHyun Kwon 	return 0;
224d76271d2SHyun Kwon }
225d76271d2SHyun Kwon 
226d76271d2SHyun Kwon static void zynqmp_dpsub_shutdown(struct platform_device *pdev)
227d76271d2SHyun Kwon {
228d76271d2SHyun Kwon 	struct zynqmp_dpsub *dpsub = platform_get_drvdata(pdev);
229d76271d2SHyun Kwon 
230d76271d2SHyun Kwon 	drm_atomic_helper_shutdown(&dpsub->drm);
231d76271d2SHyun Kwon }
232d76271d2SHyun Kwon 
233d76271d2SHyun Kwon static const struct of_device_id zynqmp_dpsub_of_match[] = {
234d76271d2SHyun Kwon 	{ .compatible = "xlnx,zynqmp-dpsub-1.7", },
235d76271d2SHyun Kwon 	{ /* end of table */ },
236d76271d2SHyun Kwon };
237d76271d2SHyun Kwon MODULE_DEVICE_TABLE(of, zynqmp_dpsub_of_match);
238d76271d2SHyun Kwon 
239d76271d2SHyun Kwon static struct platform_driver zynqmp_dpsub_driver = {
240d76271d2SHyun Kwon 	.probe			= zynqmp_dpsub_probe,
241d76271d2SHyun Kwon 	.remove			= zynqmp_dpsub_remove,
242d76271d2SHyun Kwon 	.shutdown		= zynqmp_dpsub_shutdown,
243d76271d2SHyun Kwon 	.driver			= {
244d76271d2SHyun Kwon 		.name		= "zynqmp-dpsub",
245d76271d2SHyun Kwon 		.pm		= &zynqmp_dpsub_pm_ops,
246d76271d2SHyun Kwon 		.of_match_table	= zynqmp_dpsub_of_match,
247d76271d2SHyun Kwon 	},
248d76271d2SHyun Kwon };
249d76271d2SHyun Kwon 
250fad54534SJavier Martinez Canillas drm_module_platform_driver(zynqmp_dpsub_driver);
251d76271d2SHyun Kwon 
252d76271d2SHyun Kwon MODULE_AUTHOR("Xilinx, Inc.");
253d76271d2SHyun Kwon MODULE_DESCRIPTION("ZynqMP DP Subsystem Driver");
254d76271d2SHyun Kwon MODULE_LICENSE("GPL v2");
255