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