xref: /linux/drivers/gpu/drm/rockchip/rockchip_drm_drv.c (revision 4f9786035f9e519db41375818e1d0b5f20da2f10)
19c92ab61SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
22048e328SMark Yao /*
3041c664dSAndy Yan  * Copyright (C) Rockchip Electronics Co., Ltd.
42048e328SMark Yao  * Author:Mark Yao <mark.yao@rock-chips.com>
52048e328SMark Yao  *
62048e328SMark Yao  * based on exynos_drm_drv.c
72048e328SMark Yao  */
82048e328SMark Yao 
9634bd298SThomas Zimmermann #include <linux/aperture.h>
102048e328SMark Yao #include <linux/dma-mapping.h>
11722d4f06SRob Herring #include <linux/platform_device.h>
122048e328SMark Yao #include <linux/pm_runtime.h>
1300fe6148SPaul Gortmaker #include <linux/module.h>
142048e328SMark Yao #include <linux/of_graph.h>
153880f62eSHeiko Stuebner #include <linux/of_platform.h>
162048e328SMark Yao #include <linux/component.h>
175a587383STomeu Vizoso #include <linux/console.h>
181aa5ca6eSShunqian Zheng #include <linux/iommu.h>
192048e328SMark Yao 
20b86711c6SThomas Zimmermann #include <drm/clients/drm_client_setup.h>
21c2156ccdSSam Ravnborg #include <drm/drm_drv.h>
22b4c3fe53SThomas Zimmermann #include <drm/drm_fbdev_dma.h>
234a83c26aSDanilo Krummrich #include <drm/drm_gem_dma_helper.h>
24c2156ccdSSam Ravnborg #include <drm/drm_of.h>
25c2156ccdSSam Ravnborg #include <drm/drm_probe_helper.h>
26c2156ccdSSam Ravnborg #include <drm/drm_vblank.h>
27c2156ccdSSam Ravnborg 
288490cad4SSteven Price #if defined(CONFIG_ARM_DMA_USE_IOMMU)
298490cad4SSteven Price #include <asm/dma-iommu.h>
308490cad4SSteven Price #else
318490cad4SSteven Price #define arm_iommu_detach_device(...)	({ })
328490cad4SSteven Price #define arm_iommu_release_mapping(...)	({ })
338490cad4SSteven Price #define to_dma_iommu_mapping(dev) NULL
348490cad4SSteven Price #endif
358490cad4SSteven Price 
362048e328SMark Yao #include "rockchip_drm_drv.h"
372048e328SMark Yao #include "rockchip_drm_fb.h"
382048e328SMark Yao #include "rockchip_drm_gem.h"
392048e328SMark Yao 
402048e328SMark Yao #define DRIVER_NAME	"rockchip"
412048e328SMark Yao #define DRIVER_DESC	"RockChip Soc DRM"
422048e328SMark Yao #define DRIVER_MAJOR	1
432048e328SMark Yao #define DRIVER_MINOR	0
442048e328SMark Yao 
4570a59dd8SDaniel Vetter static const struct drm_driver rockchip_drm_driver;
462d90d477SMark Yao 
472048e328SMark Yao /*
482048e328SMark Yao  * Attach a (component) device to the shared drm dma mapping from master drm
492048e328SMark Yao  * device.  This is used by the VOPs to map GEM buffers to a common DMA
502048e328SMark Yao  * mapping.
512048e328SMark Yao  */
rockchip_drm_dma_attach_device(struct drm_device * drm_dev,struct device * dev)522048e328SMark Yao int rockchip_drm_dma_attach_device(struct drm_device *drm_dev,
532048e328SMark Yao 				   struct device *dev)
542048e328SMark Yao {
551aa5ca6eSShunqian Zheng 	struct rockchip_drm_private *private = drm_dev->dev_private;
562048e328SMark Yao 	int ret;
572048e328SMark Yao 
58421be3eeSRobin Murphy 	if (!private->domain)
592d90d477SMark Yao 		return 0;
602d90d477SMark Yao 
618490cad4SSteven Price 	if (IS_ENABLED(CONFIG_ARM_DMA_USE_IOMMU)) {
628490cad4SSteven Price 		struct dma_iommu_mapping *mapping = to_dma_iommu_mapping(dev);
638490cad4SSteven Price 
648490cad4SSteven Price 		if (mapping) {
658490cad4SSteven Price 			arm_iommu_detach_device(dev);
668490cad4SSteven Price 			arm_iommu_release_mapping(mapping);
678490cad4SSteven Price 		}
688490cad4SSteven Price 	}
698490cad4SSteven Price 
701aa5ca6eSShunqian Zheng 	ret = iommu_attach_device(private->domain, dev);
711aa5ca6eSShunqian Zheng 	if (ret) {
72d8dd6804SHaneen Mohammed 		DRM_DEV_ERROR(dev, "Failed to attach iommu device\n");
732048e328SMark Yao 		return ret;
741aa5ca6eSShunqian Zheng 	}
752048e328SMark Yao 
761aa5ca6eSShunqian Zheng 	return 0;
772048e328SMark Yao }
782048e328SMark Yao 
rockchip_drm_dma_detach_device(struct drm_device * drm_dev,struct device * dev)792048e328SMark Yao void rockchip_drm_dma_detach_device(struct drm_device *drm_dev,
802048e328SMark Yao 				    struct device *dev)
812048e328SMark Yao {
821aa5ca6eSShunqian Zheng 	struct rockchip_drm_private *private = drm_dev->dev_private;
831aa5ca6eSShunqian Zheng 
84421be3eeSRobin Murphy 	if (!private->domain)
852d90d477SMark Yao 		return;
862d90d477SMark Yao 
87421be3eeSRobin Murphy 	iommu_detach_device(private->domain, dev);
88421be3eeSRobin Murphy }
89421be3eeSRobin Murphy 
rockchip_drm_dma_init_device(struct drm_device * drm_dev,struct device * dev)90421be3eeSRobin Murphy void rockchip_drm_dma_init_device(struct drm_device *drm_dev,
91421be3eeSRobin Murphy 				  struct device *dev)
92421be3eeSRobin Murphy {
93421be3eeSRobin Murphy 	struct rockchip_drm_private *private = drm_dev->dev_private;
94421be3eeSRobin Murphy 
95421be3eeSRobin Murphy 	if (!device_iommu_mapped(dev))
96421be3eeSRobin Murphy 		private->iommu_dev = ERR_PTR(-ENODEV);
97421be3eeSRobin Murphy 	else if (!private->iommu_dev)
98421be3eeSRobin Murphy 		private->iommu_dev = dev;
992048e328SMark Yao }
1002048e328SMark Yao 
rockchip_drm_init_iommu(struct drm_device * drm_dev)1011aa5ca6eSShunqian Zheng static int rockchip_drm_init_iommu(struct drm_device *drm_dev)
1021aa5ca6eSShunqian Zheng {
1031aa5ca6eSShunqian Zheng 	struct rockchip_drm_private *private = drm_dev->dev_private;
1041aa5ca6eSShunqian Zheng 	struct iommu_domain_geometry *geometry;
1051aa5ca6eSShunqian Zheng 	u64 start, end;
106d8c07beeSLu Baolu 	int ret;
1071aa5ca6eSShunqian Zheng 
108421be3eeSRobin Murphy 	if (IS_ERR_OR_NULL(private->iommu_dev))
1091aa5ca6eSShunqian Zheng 		return 0;
1101aa5ca6eSShunqian Zheng 
111d8c07beeSLu Baolu 	private->domain = iommu_paging_domain_alloc(private->iommu_dev);
112d8c07beeSLu Baolu 	if (IS_ERR(private->domain)) {
113d8c07beeSLu Baolu 		ret = PTR_ERR(private->domain);
114d8c07beeSLu Baolu 		private->domain = NULL;
115d8c07beeSLu Baolu 		return ret;
116d8c07beeSLu Baolu 	}
1171aa5ca6eSShunqian Zheng 
1181aa5ca6eSShunqian Zheng 	geometry = &private->domain->geometry;
1191aa5ca6eSShunqian Zheng 	start = geometry->aperture_start;
1201aa5ca6eSShunqian Zheng 	end = geometry->aperture_end;
1211aa5ca6eSShunqian Zheng 
1221aa5ca6eSShunqian Zheng 	DRM_DEBUG("IOMMU context initialized (aperture: %#llx-%#llx)\n",
1231aa5ca6eSShunqian Zheng 		  start, end);
1241aa5ca6eSShunqian Zheng 	drm_mm_init(&private->mm, start, end - start + 1);
1251aa5ca6eSShunqian Zheng 	mutex_init(&private->mm_lock);
1261aa5ca6eSShunqian Zheng 
1271aa5ca6eSShunqian Zheng 	return 0;
1281aa5ca6eSShunqian Zheng }
1291aa5ca6eSShunqian Zheng 
rockchip_iommu_cleanup(struct drm_device * drm_dev)1301aa5ca6eSShunqian Zheng static void rockchip_iommu_cleanup(struct drm_device *drm_dev)
1311aa5ca6eSShunqian Zheng {
1321aa5ca6eSShunqian Zheng 	struct rockchip_drm_private *private = drm_dev->dev_private;
1331aa5ca6eSShunqian Zheng 
134421be3eeSRobin Murphy 	if (!private->domain)
1351aa5ca6eSShunqian Zheng 		return;
1361aa5ca6eSShunqian Zheng 
1371aa5ca6eSShunqian Zheng 	drm_mm_takedown(&private->mm);
1381aa5ca6eSShunqian Zheng 	iommu_domain_free(private->domain);
1392048e328SMark Yao }
1402048e328SMark Yao 
rockchip_drm_bind(struct device * dev)141f706974aSTomeu Vizoso static int rockchip_drm_bind(struct device *dev)
1422048e328SMark Yao {
143f706974aSTomeu Vizoso 	struct drm_device *drm_dev;
1442048e328SMark Yao 	struct rockchip_drm_private *private;
1452048e328SMark Yao 	int ret;
1462048e328SMark Yao 
1473c1ed51aSJavier Martinez Canillas 	/* Remove existing drivers that may own the framebuffer memory. */
148634bd298SThomas Zimmermann 	ret = aperture_remove_all_conflicting_devices(rockchip_drm_driver.name);
1493c1ed51aSJavier Martinez Canillas 	if (ret) {
1503c1ed51aSJavier Martinez Canillas 		DRM_DEV_ERROR(dev,
1513c1ed51aSJavier Martinez Canillas 			      "Failed to remove existing framebuffers - %d.\n",
1523c1ed51aSJavier Martinez Canillas 			      ret);
1533c1ed51aSJavier Martinez Canillas 		return ret;
1543c1ed51aSJavier Martinez Canillas 	}
1553c1ed51aSJavier Martinez Canillas 
156f706974aSTomeu Vizoso 	drm_dev = drm_dev_alloc(&rockchip_drm_driver, dev);
1570f288605STom Gundersen 	if (IS_ERR(drm_dev))
1580f288605STom Gundersen 		return PTR_ERR(drm_dev);
1592048e328SMark Yao 
160f706974aSTomeu Vizoso 	dev_set_drvdata(dev, drm_dev);
161f706974aSTomeu Vizoso 
162f706974aSTomeu Vizoso 	private = devm_kzalloc(drm_dev->dev, sizeof(*private), GFP_KERNEL);
163f706974aSTomeu Vizoso 	if (!private) {
164f706974aSTomeu Vizoso 		ret = -ENOMEM;
1659127f99cSTomasz Figa 		goto err_free;
166f706974aSTomeu Vizoso 	}
167f706974aSTomeu Vizoso 
1682048e328SMark Yao 	drm_dev->dev_private = private;
1692048e328SMark Yao 
17056e35f85SDaniel Vetter 	ret = drmm_mode_config_init(drm_dev);
17156e35f85SDaniel Vetter 	if (ret)
172421be3eeSRobin Murphy 		goto err_free;
1732048e328SMark Yao 
1742048e328SMark Yao 	rockchip_drm_mode_config_init(drm_dev);
1752048e328SMark Yao 
1762048e328SMark Yao 	/* Try to bind all sub drivers. */
1772048e328SMark Yao 	ret = component_bind_all(dev, drm_dev);
1782048e328SMark Yao 	if (ret)
179421be3eeSRobin Murphy 		goto err_free;
180421be3eeSRobin Murphy 
181421be3eeSRobin Murphy 	ret = rockchip_drm_init_iommu(drm_dev);
182421be3eeSRobin Murphy 	if (ret)
183421be3eeSRobin Murphy 		goto err_unbind_all;
1842048e328SMark Yao 
185ccea9199SJeffy Chen 	ret = drm_vblank_init(drm_dev, drm_dev->mode_config.num_crtc);
186ccea9199SJeffy Chen 	if (ret)
187421be3eeSRobin Murphy 		goto err_iommu_cleanup;
188ccea9199SJeffy Chen 
189ccea9199SJeffy Chen 	drm_mode_config_reset(drm_dev);
1902048e328SMark Yao 
191ccea9199SJeffy Chen 	/* init kms poll for handling hpd */
192ccea9199SJeffy Chen 	drm_kms_helper_poll_init(drm_dev);
19363ebb9faSMark Yao 
1948415ab56SMark yao 	ret = drm_dev_register(drm_dev, 0);
1952048e328SMark Yao 	if (ret)
196ccea9199SJeffy Chen 		goto err_kms_helper_poll_fini;
1972048e328SMark Yao 
198905df8f1SThomas Zimmermann 	drm_client_setup(drm_dev, NULL);
19924af7c34SJohn Keeping 
2002048e328SMark Yao 	return 0;
2012048e328SMark Yao err_kms_helper_poll_fini:
2022048e328SMark Yao 	drm_kms_helper_poll_fini(drm_dev);
20356e35f85SDaniel Vetter err_iommu_cleanup:
204ccea9199SJeffy Chen 	rockchip_iommu_cleanup(drm_dev);
205421be3eeSRobin Murphy err_unbind_all:
206421be3eeSRobin Murphy 	component_unbind_all(dev, drm_dev);
207f706974aSTomeu Vizoso err_free:
208574e0fbfSThomas Zimmermann 	drm_dev_put(drm_dev);
2092048e328SMark Yao 	return ret;
2102048e328SMark Yao }
2112048e328SMark Yao 
rockchip_drm_unbind(struct device * dev)212f706974aSTomeu Vizoso static void rockchip_drm_unbind(struct device *dev)
2132048e328SMark Yao {
214f706974aSTomeu Vizoso 	struct drm_device *drm_dev = dev_get_drvdata(dev);
2152048e328SMark Yao 
216f706974aSTomeu Vizoso 	drm_dev_unregister(drm_dev);
217ccea9199SJeffy Chen 
218ccea9199SJeffy Chen 	drm_kms_helper_poll_fini(drm_dev);
219ccea9199SJeffy Chen 
220c1bb8188SJeffy Chen 	drm_atomic_helper_shutdown(drm_dev);
221ccea9199SJeffy Chen 	component_unbind_all(dev, drm_dev);
222ccea9199SJeffy Chen 	rockchip_iommu_cleanup(drm_dev);
223ccea9199SJeffy Chen 
224574e0fbfSThomas Zimmermann 	drm_dev_put(drm_dev);
2252048e328SMark Yao }
2262048e328SMark Yao 
227f8b53070SThomas Zimmermann DEFINE_DRM_GEM_FOPS(rockchip_drm_driver_fops);
2282048e328SMark Yao 
22970a59dd8SDaniel Vetter static const struct drm_driver rockchip_drm_driver = {
2300424fdafSDaniel Vetter 	.driver_features	= DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
2312048e328SMark Yao 	.dumb_create		= rockchip_gem_dumb_create,
2326fd0bfe2SHaixia Shi 	.gem_prime_import_sg_table	= rockchip_gem_prime_import_sg_table,
233905df8f1SThomas Zimmermann 	DRM_FBDEV_DMA_DRIVER_OPS,
2342048e328SMark Yao 	.fops			= &rockchip_drm_driver_fops,
2352048e328SMark Yao 	.name	= DRIVER_NAME,
2362048e328SMark Yao 	.desc	= DRIVER_DESC,
2372048e328SMark Yao 	.major	= DRIVER_MAJOR,
2382048e328SMark Yao 	.minor	= DRIVER_MINOR,
2392048e328SMark Yao };
2402048e328SMark Yao 
2412048e328SMark Yao #ifdef CONFIG_PM_SLEEP
rockchip_drm_sys_suspend(struct device * dev)2422048e328SMark Yao static int rockchip_drm_sys_suspend(struct device *dev)
2432048e328SMark Yao {
2442048e328SMark Yao 	struct drm_device *drm = dev_get_drvdata(dev);
2450fa375e6SJeffy Chen 
246e7941cc2SSouptick Joarder 	return drm_mode_config_helper_suspend(drm);
2472048e328SMark Yao }
2482048e328SMark Yao 
rockchip_drm_sys_resume(struct device * dev)2492048e328SMark Yao static int rockchip_drm_sys_resume(struct device *dev)
2502048e328SMark Yao {
2512048e328SMark Yao 	struct drm_device *drm = dev_get_drvdata(dev);
2522048e328SMark Yao 
253e7941cc2SSouptick Joarder 	return drm_mode_config_helper_resume(drm);
2542048e328SMark Yao }
2552048e328SMark Yao #endif
2562048e328SMark Yao 
2572048e328SMark Yao static const struct dev_pm_ops rockchip_drm_pm_ops = {
2582048e328SMark Yao 	SET_SYSTEM_SLEEP_PM_OPS(rockchip_drm_sys_suspend,
2592048e328SMark Yao 				rockchip_drm_sys_resume)
2602048e328SMark Yao };
2612048e328SMark Yao 
2628820b68bSJeffy Chen #define MAX_ROCKCHIP_SUB_DRIVERS 16
2638820b68bSJeffy Chen static struct platform_driver *rockchip_sub_drivers[MAX_ROCKCHIP_SUB_DRIVERS];
2648820b68bSJeffy Chen static int num_rockchip_sub_drivers;
2658820b68bSJeffy Chen 
2663880f62eSHeiko Stuebner /*
267cf544c6aSSascha Hauer  * Get the endpoint id of the remote endpoint of the given encoder. This
268cf544c6aSSascha Hauer  * information is used by the VOP2 driver to identify the encoder.
269cf544c6aSSascha Hauer  *
270cf544c6aSSascha Hauer  * @rkencoder: The encoder to get the remote endpoint id from
271cf544c6aSSascha Hauer  * @np: The encoder device node
272cf544c6aSSascha Hauer  * @port: The number of the port leading to the VOP2
273cf544c6aSSascha Hauer  * @reg: The endpoint number leading to the VOP2
274cf544c6aSSascha Hauer  */
rockchip_drm_encoder_set_crtc_endpoint_id(struct rockchip_encoder * rkencoder,struct device_node * np,int port,int reg)275cf544c6aSSascha Hauer int rockchip_drm_encoder_set_crtc_endpoint_id(struct rockchip_encoder *rkencoder,
276cf544c6aSSascha Hauer 					      struct device_node *np, int port, int reg)
277cf544c6aSSascha Hauer {
278cf544c6aSSascha Hauer 	struct of_endpoint ep;
279cf544c6aSSascha Hauer 	struct device_node *en, *ren;
280cf544c6aSSascha Hauer 	int ret;
281cf544c6aSSascha Hauer 
282cf544c6aSSascha Hauer 	en = of_graph_get_endpoint_by_regs(np, port, reg);
283cf544c6aSSascha Hauer 	if (!en)
284cf544c6aSSascha Hauer 		return -ENOENT;
285cf544c6aSSascha Hauer 
286cf544c6aSSascha Hauer 	ren = of_graph_get_remote_endpoint(en);
287cf544c6aSSascha Hauer 	if (!ren)
288cf544c6aSSascha Hauer 		return -ENOENT;
289cf544c6aSSascha Hauer 
290cf544c6aSSascha Hauer 	ret = of_graph_parse_endpoint(ren, &ep);
291cf544c6aSSascha Hauer 	if (ret)
292cf544c6aSSascha Hauer 		return ret;
293cf544c6aSSascha Hauer 
294cf544c6aSSascha Hauer 	rkencoder->crtc_endpoint_id = ep.id;
295cf544c6aSSascha Hauer 
296cf544c6aSSascha Hauer 	return 0;
297cf544c6aSSascha Hauer }
298cf544c6aSSascha Hauer 
299cf544c6aSSascha Hauer /*
3003880f62eSHeiko Stuebner  * Check if a vop endpoint is leading to a rockchip subdriver or bridge.
3013880f62eSHeiko Stuebner  * Should be called from the component bind stage of the drivers
3023880f62eSHeiko Stuebner  * to ensure that all subdrivers are probed.
3033880f62eSHeiko Stuebner  *
3043880f62eSHeiko Stuebner  * @ep: endpoint of a rockchip vop
3053880f62eSHeiko Stuebner  *
3063880f62eSHeiko Stuebner  * returns true if subdriver, false if external bridge and -ENODEV
3073880f62eSHeiko Stuebner  * if remote port does not contain a device.
3083880f62eSHeiko Stuebner  */
rockchip_drm_endpoint_is_subdriver(struct device_node * ep)3093880f62eSHeiko Stuebner int rockchip_drm_endpoint_is_subdriver(struct device_node *ep)
3103880f62eSHeiko Stuebner {
3113880f62eSHeiko Stuebner 	struct device_node *node = of_graph_get_remote_port_parent(ep);
3123880f62eSHeiko Stuebner 	struct platform_device *pdev;
3133880f62eSHeiko Stuebner 	struct device_driver *drv;
3143880f62eSHeiko Stuebner 	int i;
3153880f62eSHeiko Stuebner 
3163880f62eSHeiko Stuebner 	if (!node)
3173880f62eSHeiko Stuebner 		return -ENODEV;
3183880f62eSHeiko Stuebner 
3193880f62eSHeiko Stuebner 	/* status disabled will prevent creation of platform-devices */
32037825e07SAlex Bee 	if (!of_device_is_available(node)) {
32137825e07SAlex Bee 		of_node_put(node);
32237825e07SAlex Bee 		return -ENODEV;
32337825e07SAlex Bee 	}
32437825e07SAlex Bee 
3253880f62eSHeiko Stuebner 	pdev = of_find_device_by_node(node);
3263880f62eSHeiko Stuebner 	of_node_put(node);
32737825e07SAlex Bee 
32837825e07SAlex Bee 	/* enabled non-platform-devices can immediately return here */
3293880f62eSHeiko Stuebner 	if (!pdev)
33037825e07SAlex Bee 		return false;
3313880f62eSHeiko Stuebner 
3323880f62eSHeiko Stuebner 	/*
3333880f62eSHeiko Stuebner 	 * All rockchip subdrivers have probed at this point, so
3343880f62eSHeiko Stuebner 	 * any device not having a driver now is an external bridge.
3353880f62eSHeiko Stuebner 	 */
3363880f62eSHeiko Stuebner 	drv = pdev->dev.driver;
3373880f62eSHeiko Stuebner 	if (!drv) {
3383880f62eSHeiko Stuebner 		platform_device_put(pdev);
3393880f62eSHeiko Stuebner 		return false;
3403880f62eSHeiko Stuebner 	}
3413880f62eSHeiko Stuebner 
3423880f62eSHeiko Stuebner 	for (i = 0; i < num_rockchip_sub_drivers; i++) {
3433880f62eSHeiko Stuebner 		if (rockchip_sub_drivers[i] == to_platform_driver(drv)) {
3443880f62eSHeiko Stuebner 			platform_device_put(pdev);
3453880f62eSHeiko Stuebner 			return true;
3463880f62eSHeiko Stuebner 		}
3473880f62eSHeiko Stuebner 	}
3483880f62eSHeiko Stuebner 
3493880f62eSHeiko Stuebner 	platform_device_put(pdev);
3503880f62eSHeiko Stuebner 	return false;
3513880f62eSHeiko Stuebner }
3523880f62eSHeiko Stuebner 
rockchip_drm_match_remove(struct device * dev)3537d4e981dSJeffy Chen static void rockchip_drm_match_remove(struct device *dev)
3547d4e981dSJeffy Chen {
3557d4e981dSJeffy Chen 	struct device_link *link;
3567d4e981dSJeffy Chen 
3577d4e981dSJeffy Chen 	list_for_each_entry(link, &dev->links.consumers, s_node)
3587d4e981dSJeffy Chen 		device_link_del(link);
3597d4e981dSJeffy Chen }
3607d4e981dSJeffy Chen 
3610c4558a1SJonas Karlman /* list of preferred vop devices */
3620c4558a1SJonas Karlman static const char *const rockchip_drm_match_preferred[] = {
3630c4558a1SJonas Karlman 	"rockchip,rk3399-vop-big",
3640c4558a1SJonas Karlman 	NULL,
3650c4558a1SJonas Karlman };
3660c4558a1SJonas Karlman 
rockchip_drm_match_add(struct device * dev)3678820b68bSJeffy Chen static struct component_match *rockchip_drm_match_add(struct device *dev)
3685bad7d29SMark Yao {
3698820b68bSJeffy Chen 	struct component_match *match = NULL;
3700c4558a1SJonas Karlman 	struct device_node *port;
3718820b68bSJeffy Chen 	int i;
3725bad7d29SMark Yao 
3730c4558a1SJonas Karlman 	/* add preferred vop device match before adding driver device matches */
3740c4558a1SJonas Karlman 	for (i = 0; ; i++) {
3750c4558a1SJonas Karlman 		port = of_parse_phandle(dev->of_node, "ports", i);
3760c4558a1SJonas Karlman 		if (!port)
3770c4558a1SJonas Karlman 			break;
3780c4558a1SJonas Karlman 
3790c4558a1SJonas Karlman 		if (of_device_is_available(port->parent) &&
3800c4558a1SJonas Karlman 		    of_device_compatible_match(port->parent,
3810c4558a1SJonas Karlman 					       rockchip_drm_match_preferred))
3820c4558a1SJonas Karlman 			drm_of_component_match_add(dev, &match,
3830c4558a1SJonas Karlman 						   component_compare_of,
3840c4558a1SJonas Karlman 						   port->parent);
3850c4558a1SJonas Karlman 
3860c4558a1SJonas Karlman 		of_node_put(port);
3870c4558a1SJonas Karlman 	}
3880c4558a1SJonas Karlman 
3898820b68bSJeffy Chen 	for (i = 0; i < num_rockchip_sub_drivers; i++) {
3908820b68bSJeffy Chen 		struct platform_driver *drv = rockchip_sub_drivers[i];
3918820b68bSJeffy Chen 		struct device *p = NULL, *d;
3928820b68bSJeffy Chen 
3938820b68bSJeffy Chen 		do {
39436f3313dSSuzuki K Poulose 			d = platform_find_device_by_driver(p, &drv->driver);
3958820b68bSJeffy Chen 			put_device(p);
3968820b68bSJeffy Chen 			p = d;
3978820b68bSJeffy Chen 
3988820b68bSJeffy Chen 			if (!d)
3998820b68bSJeffy Chen 				break;
4007d4e981dSJeffy Chen 
4017d4e981dSJeffy Chen 			device_link_add(dev, d, DL_FLAG_STATELESS);
402f798aa41SYong Wu 			component_match_add(dev, &match, component_compare_dev, d);
4038820b68bSJeffy Chen 		} while (true);
4045bad7d29SMark Yao 	}
4055bad7d29SMark Yao 
4067d4e981dSJeffy Chen 	if (IS_ERR(match))
4077d4e981dSJeffy Chen 		rockchip_drm_match_remove(dev);
4087d4e981dSJeffy Chen 
4098820b68bSJeffy Chen 	return match ?: ERR_PTR(-ENODEV);
4105bad7d29SMark Yao }
4115bad7d29SMark Yao 
4122048e328SMark Yao static const struct component_master_ops rockchip_drm_ops = {
4132048e328SMark Yao 	.bind = rockchip_drm_bind,
4142048e328SMark Yao 	.unbind = rockchip_drm_unbind,
4152048e328SMark Yao };
4162048e328SMark Yao 
rockchip_drm_platform_of_probe(struct device * dev)4178820b68bSJeffy Chen static int rockchip_drm_platform_of_probe(struct device *dev)
4182048e328SMark Yao {
4195bad7d29SMark Yao 	struct device_node *np = dev->of_node;
4205bad7d29SMark Yao 	struct device_node *port;
4218820b68bSJeffy Chen 	bool found = false;
4225bad7d29SMark Yao 	int i;
4232048e328SMark Yao 
4245bad7d29SMark Yao 	if (!np)
4252048e328SMark Yao 		return -ENODEV;
4268820b68bSJeffy Chen 
4275bad7d29SMark Yao 	for (i = 0;; i++) {
4285bad7d29SMark Yao 		port = of_parse_phandle(np, "ports", i);
4295bad7d29SMark Yao 		if (!port)
4305bad7d29SMark Yao 			break;
4312048e328SMark Yao 
4325bad7d29SMark Yao 		if (!of_device_is_available(port->parent)) {
4335bad7d29SMark Yao 			of_node_put(port);
4345bad7d29SMark Yao 			continue;
4355bad7d29SMark Yao 		}
4365bad7d29SMark Yao 
4378820b68bSJeffy Chen 		found = true;
4385bad7d29SMark Yao 		of_node_put(port);
4395bad7d29SMark Yao 	}
4405bad7d29SMark Yao 
4415bad7d29SMark Yao 	if (i == 0) {
442d8dd6804SHaneen Mohammed 		DRM_DEV_ERROR(dev, "missing 'ports' property\n");
4435bad7d29SMark Yao 		return -ENODEV;
4445bad7d29SMark Yao 	}
4455bad7d29SMark Yao 
4468820b68bSJeffy Chen 	if (!found) {
447d8dd6804SHaneen Mohammed 		DRM_DEV_ERROR(dev,
448d8dd6804SHaneen Mohammed 			      "No available vop found for display-subsystem.\n");
4495bad7d29SMark Yao 		return -ENODEV;
4505bad7d29SMark Yao 	}
4515bad7d29SMark Yao 
4528820b68bSJeffy Chen 	return 0;
4535bad7d29SMark Yao }
4545bad7d29SMark Yao 
rockchip_drm_platform_probe(struct platform_device * pdev)4558820b68bSJeffy Chen static int rockchip_drm_platform_probe(struct platform_device *pdev)
4568820b68bSJeffy Chen {
4578820b68bSJeffy Chen 	struct device *dev = &pdev->dev;
4588820b68bSJeffy Chen 	struct component_match *match = NULL;
4598820b68bSJeffy Chen 	int ret;
4608820b68bSJeffy Chen 
4618820b68bSJeffy Chen 	ret = rockchip_drm_platform_of_probe(dev);
4628820b68bSJeffy Chen 	if (ret)
4638820b68bSJeffy Chen 		return ret;
4648820b68bSJeffy Chen 
4658820b68bSJeffy Chen 	match = rockchip_drm_match_add(dev);
4668820b68bSJeffy Chen 	if (IS_ERR(match))
4678820b68bSJeffy Chen 		return PTR_ERR(match);
4685bad7d29SMark Yao 
4697d4e981dSJeffy Chen 	ret = component_master_add_with_match(dev, &rockchip_drm_ops, match);
4707d4e981dSJeffy Chen 	if (ret < 0) {
4717d4e981dSJeffy Chen 		rockchip_drm_match_remove(dev);
4727d4e981dSJeffy Chen 		return ret;
4737d4e981dSJeffy Chen 	}
4747d4e981dSJeffy Chen 
4757d4e981dSJeffy Chen 	return 0;
4762048e328SMark Yao }
4772048e328SMark Yao 
rockchip_drm_platform_remove(struct platform_device * pdev)4783c855610SUwe Kleine-König static void rockchip_drm_platform_remove(struct platform_device *pdev)
4792048e328SMark Yao {
4802048e328SMark Yao 	component_master_del(&pdev->dev, &rockchip_drm_ops);
4812048e328SMark Yao 
4827d4e981dSJeffy Chen 	rockchip_drm_match_remove(&pdev->dev);
4832048e328SMark Yao }
4842048e328SMark Yao 
rockchip_drm_platform_shutdown(struct platform_device * pdev)485b8f9d7f3SVicente Bergas static void rockchip_drm_platform_shutdown(struct platform_device *pdev)
486b8f9d7f3SVicente Bergas {
487*4444e4d7SHeiko Stuebner 	if (component_master_is_bound(&pdev->dev, &rockchip_drm_ops)) {
488b8f9d7f3SVicente Bergas 		struct drm_device *drm = platform_get_drvdata(pdev);
489b8f9d7f3SVicente Bergas 
490b8f9d7f3SVicente Bergas 		drm_atomic_helper_shutdown(drm);
491b8f9d7f3SVicente Bergas 	}
492*4444e4d7SHeiko Stuebner }
493b8f9d7f3SVicente Bergas 
4942048e328SMark Yao static const struct of_device_id rockchip_drm_dt_ids[] = {
4952048e328SMark Yao 	{ .compatible = "rockchip,display-subsystem", },
4962048e328SMark Yao 	{ /* sentinel */ },
4972048e328SMark Yao };
4982048e328SMark Yao MODULE_DEVICE_TABLE(of, rockchip_drm_dt_ids);
4992048e328SMark Yao 
5002048e328SMark Yao static struct platform_driver rockchip_drm_platform_driver = {
5012048e328SMark Yao 	.probe = rockchip_drm_platform_probe,
502e70140baSLinus Torvalds 	.remove = rockchip_drm_platform_remove,
503b8f9d7f3SVicente Bergas 	.shutdown = rockchip_drm_platform_shutdown,
5042048e328SMark Yao 	.driver = {
5052048e328SMark Yao 		.name = "rockchip-drm",
5062048e328SMark Yao 		.of_match_table = rockchip_drm_dt_ids,
5072048e328SMark Yao 		.pm = &rockchip_drm_pm_ops,
5082048e328SMark Yao 	},
5092048e328SMark Yao };
5102048e328SMark Yao 
5118820b68bSJeffy Chen #define ADD_ROCKCHIP_SUB_DRIVER(drv, cond) { \
5128820b68bSJeffy Chen 	if (IS_ENABLED(cond) && \
5138820b68bSJeffy Chen 	    !WARN_ON(num_rockchip_sub_drivers >= MAX_ROCKCHIP_SUB_DRIVERS)) \
5148820b68bSJeffy Chen 		rockchip_sub_drivers[num_rockchip_sub_drivers++] = &drv; \
5158820b68bSJeffy Chen }
5168820b68bSJeffy Chen 
rockchip_drm_init(void)5178820b68bSJeffy Chen static int __init rockchip_drm_init(void)
5188820b68bSJeffy Chen {
5198820b68bSJeffy Chen 	int ret;
5208820b68bSJeffy Chen 
52109037781SJavier Martinez Canillas 	if (drm_firmware_drivers_only())
52209037781SJavier Martinez Canillas 		return -ENODEV;
52309037781SJavier Martinez Canillas 
5248820b68bSJeffy Chen 	num_rockchip_sub_drivers = 0;
525b382406aSSascha Hauer 	ADD_ROCKCHIP_SUB_DRIVER(vop_platform_driver, CONFIG_ROCKCHIP_VOP);
526604be855SAndy Yan 	ADD_ROCKCHIP_SUB_DRIVER(vop2_platform_driver, CONFIG_ROCKCHIP_VOP2);
52734cc0aa2SSandy Huang 	ADD_ROCKCHIP_SUB_DRIVER(rockchip_lvds_driver,
52834cc0aa2SSandy Huang 				CONFIG_ROCKCHIP_LVDS);
5298820b68bSJeffy Chen 	ADD_ROCKCHIP_SUB_DRIVER(rockchip_dp_driver,
5308820b68bSJeffy Chen 				CONFIG_ROCKCHIP_ANALOGIX_DP);
5318820b68bSJeffy Chen 	ADD_ROCKCHIP_SUB_DRIVER(cdn_dp_driver, CONFIG_ROCKCHIP_CDN_DP);
5328820b68bSJeffy Chen 	ADD_ROCKCHIP_SUB_DRIVER(dw_hdmi_rockchip_pltfm_driver,
5338820b68bSJeffy Chen 				CONFIG_ROCKCHIP_DW_HDMI);
534128a9bf8SCristian Ciocaltea 	ADD_ROCKCHIP_SUB_DRIVER(dw_hdmi_qp_rockchip_pltfm_driver,
535128a9bf8SCristian Ciocaltea 				CONFIG_ROCKCHIP_DW_HDMI_QP);
5362d4f7bdaSNickey Yang 	ADD_ROCKCHIP_SUB_DRIVER(dw_mipi_dsi_rockchip_driver,
5378820b68bSJeffy Chen 				CONFIG_ROCKCHIP_DW_MIPI_DSI);
5389f1e1e14SHeiko Stuebner 	ADD_ROCKCHIP_SUB_DRIVER(dw_mipi_dsi2_rockchip_driver,
5399f1e1e14SHeiko Stuebner 				CONFIG_ROCKCHIP_DW_MIPI_DSI2);
5408820b68bSJeffy Chen 	ADD_ROCKCHIP_SUB_DRIVER(inno_hdmi_driver, CONFIG_ROCKCHIP_INNO_HDMI);
541f84d3d37SZheng Yang 	ADD_ROCKCHIP_SUB_DRIVER(rk3066_hdmi_driver,
542f84d3d37SZheng Yang 				CONFIG_ROCKCHIP_RK3066_HDMI);
5438820b68bSJeffy Chen 
5448820b68bSJeffy Chen 	ret = platform_register_drivers(rockchip_sub_drivers,
5458820b68bSJeffy Chen 					num_rockchip_sub_drivers);
5468820b68bSJeffy Chen 	if (ret)
5478820b68bSJeffy Chen 		return ret;
5488820b68bSJeffy Chen 
5498820b68bSJeffy Chen 	ret = platform_driver_register(&rockchip_drm_platform_driver);
5508820b68bSJeffy Chen 	if (ret)
5518820b68bSJeffy Chen 		goto err_unreg_drivers;
5528820b68bSJeffy Chen 
5538820b68bSJeffy Chen 	return 0;
5548820b68bSJeffy Chen 
5558820b68bSJeffy Chen err_unreg_drivers:
5568820b68bSJeffy Chen 	platform_unregister_drivers(rockchip_sub_drivers,
5578820b68bSJeffy Chen 				    num_rockchip_sub_drivers);
5588820b68bSJeffy Chen 	return ret;
5598820b68bSJeffy Chen }
5608820b68bSJeffy Chen 
rockchip_drm_fini(void)5618820b68bSJeffy Chen static void __exit rockchip_drm_fini(void)
5628820b68bSJeffy Chen {
5638820b68bSJeffy Chen 	platform_driver_unregister(&rockchip_drm_platform_driver);
5648820b68bSJeffy Chen 
5658820b68bSJeffy Chen 	platform_unregister_drivers(rockchip_sub_drivers,
5668820b68bSJeffy Chen 				    num_rockchip_sub_drivers);
5678820b68bSJeffy Chen }
5688820b68bSJeffy Chen 
5698820b68bSJeffy Chen module_init(rockchip_drm_init);
5708820b68bSJeffy Chen module_exit(rockchip_drm_fini);
5712048e328SMark Yao 
5722048e328SMark Yao MODULE_AUTHOR("Mark Yao <mark.yao@rock-chips.com>");
5732048e328SMark Yao MODULE_DESCRIPTION("ROCKCHIP DRM Driver");
5742048e328SMark Yao MODULE_LICENSE("GPL v2");
575