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