xref: /openbmc/linux/drivers/gpu/drm/sun4i/sun4i_drv.c (revision d665e3c9)
12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
29026e0d1SMaxime Ripard /*
39026e0d1SMaxime Ripard  * Copyright (C) 2015 Free Electrons
49026e0d1SMaxime Ripard  * Copyright (C) 2015 NextThing Co
59026e0d1SMaxime Ripard  *
69026e0d1SMaxime Ripard  * Maxime Ripard <maxime.ripard@free-electrons.com>
79026e0d1SMaxime Ripard  */
89026e0d1SMaxime Ripard 
99026e0d1SMaxime Ripard #include <linux/component.h>
10f5aa1680SJernej Skrabec #include <linux/dma-mapping.h>
118b11aafaSMaxime Ripard #include <linux/kfifo.h>
129c25a297SSam Ravnborg #include <linux/module.h>
139026e0d1SMaxime Ripard #include <linux/of_graph.h>
14596afb6fSMaxime Ripard #include <linux/of_reserved_mem.h>
159c25a297SSam Ravnborg #include <linux/platform_device.h>
169026e0d1SMaxime Ripard 
176848c291SThomas Zimmermann #include <drm/drm_aperture.h>
1871adf60fSPaul Kocialkowski #include <drm/drm_atomic_helper.h>
199c25a297SSam Ravnborg #include <drm/drm_drv.h>
20a5b179acSThomas Zimmermann #include <drm/drm_fbdev_dma.h>
214a83c26aSDanilo Krummrich #include <drm/drm_gem_dma_helper.h>
22ab41e6aaSJavier Martinez Canillas #include <drm/drm_module.h>
2397ac0e47SRussell King #include <drm/drm_of.h>
24fcd70cd3SDaniel Vetter #include <drm/drm_probe_helper.h>
259c25a297SSam Ravnborg #include <drm/drm_vblank.h>
269026e0d1SMaxime Ripard 
279026e0d1SMaxime Ripard #include "sun4i_drv.h"
28dd0421f4SMaxime Ripard #include "sun4i_frontend.h"
299026e0d1SMaxime Ripard #include "sun4i_framebuffer.h"
30b3f266e4SChen-Yu Tsai #include "sun4i_tcon.h"
31ef0cf644SJernej Skrabec #include "sun8i_tcon_top.h"
329026e0d1SMaxime Ripard 
drm_sun4i_gem_dumb_create(struct drm_file * file_priv,struct drm_device * drm,struct drm_mode_create_dumb * args)3331cf282aSPaul Kocialkowski static int drm_sun4i_gem_dumb_create(struct drm_file *file_priv,
3431cf282aSPaul Kocialkowski 				     struct drm_device *drm,
3531cf282aSPaul Kocialkowski 				     struct drm_mode_create_dumb *args)
3631cf282aSPaul Kocialkowski {
3731cf282aSPaul Kocialkowski 	/* The hardware only allows even pitches for YUV buffers. */
3831cf282aSPaul Kocialkowski 	args->pitch = ALIGN(DIV_ROUND_UP(args->width * args->bpp, 8), 2);
3931cf282aSPaul Kocialkowski 
404a83c26aSDanilo Krummrich 	return drm_gem_dma_dumb_create_internal(file_priv, drm, args);
4131cf282aSPaul Kocialkowski }
4231cf282aSPaul Kocialkowski 
434a83c26aSDanilo Krummrich DEFINE_DRM_GEM_DMA_FOPS(sun4i_drv_fops);
449026e0d1SMaxime Ripard 
4570a59dd8SDaniel Vetter static const struct drm_driver sun4i_drv_driver = {
460424fdafSDaniel Vetter 	.driver_features	= DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
479026e0d1SMaxime Ripard 
489026e0d1SMaxime Ripard 	/* Generic Operations */
499026e0d1SMaxime Ripard 	.fops			= &sun4i_drv_fops,
509026e0d1SMaxime Ripard 	.name			= "sun4i-drm",
519026e0d1SMaxime Ripard 	.desc			= "Allwinner sun4i Display Engine",
529026e0d1SMaxime Ripard 	.date			= "20150629",
539026e0d1SMaxime Ripard 	.major			= 1,
549026e0d1SMaxime Ripard 	.minor			= 0,
559026e0d1SMaxime Ripard 
569026e0d1SMaxime Ripard 	/* GEM Operations */
574a83c26aSDanilo Krummrich 	DRM_GEM_DMA_DRIVER_OPS_WITH_DUMB_CREATE(drm_sun4i_gem_dumb_create),
589026e0d1SMaxime Ripard };
599026e0d1SMaxime Ripard 
sun4i_drv_bind(struct device * dev)609026e0d1SMaxime Ripard static int sun4i_drv_bind(struct device *dev)
619026e0d1SMaxime Ripard {
629026e0d1SMaxime Ripard 	struct drm_device *drm;
639026e0d1SMaxime Ripard 	struct sun4i_drv *drv;
649026e0d1SMaxime Ripard 	int ret;
659026e0d1SMaxime Ripard 
669026e0d1SMaxime Ripard 	drm = drm_dev_alloc(&sun4i_drv_driver, dev);
670f288605STom Gundersen 	if (IS_ERR(drm))
680f288605STom Gundersen 		return PTR_ERR(drm);
699026e0d1SMaxime Ripard 
709026e0d1SMaxime Ripard 	drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL);
719026e0d1SMaxime Ripard 	if (!drv) {
729026e0d1SMaxime Ripard 		ret = -ENOMEM;
739026e0d1SMaxime Ripard 		goto free_drm;
749026e0d1SMaxime Ripard 	}
7502b92adbSPaul Kocialkowski 
769026e0d1SMaxime Ripard 	drm->dev_private = drv;
77dd0421f4SMaxime Ripard 	INIT_LIST_HEAD(&drv->frontend_list);
7887969338SIcenowy Zheng 	INIT_LIST_HEAD(&drv->engine_list);
7980a58240SChen-Yu Tsai 	INIT_LIST_HEAD(&drv->tcon_list);
809026e0d1SMaxime Ripard 
81596afb6fSMaxime Ripard 	ret = of_reserved_mem_device_init(dev);
82596afb6fSMaxime Ripard 	if (ret && ret != -ENODEV) {
83596afb6fSMaxime Ripard 		dev_err(drm->dev, "Couldn't claim our memory region\n");
84596afb6fSMaxime Ripard 		goto free_drm;
85596afb6fSMaxime Ripard 	}
86596afb6fSMaxime Ripard 
879026e0d1SMaxime Ripard 	drm_mode_config_init(drm);
889026e0d1SMaxime Ripard 
899026e0d1SMaxime Ripard 	ret = component_bind_all(drm->dev, drm);
909026e0d1SMaxime Ripard 	if (ret) {
919026e0d1SMaxime Ripard 		dev_err(drm->dev, "Couldn't bind all pipelines components\n");
929d56defbSChen-Yu Tsai 		goto cleanup_mode_config;
939026e0d1SMaxime Ripard 	}
949026e0d1SMaxime Ripard 
95070badfaSChen-Yu Tsai 	/* drm_vblank_init calls kcalloc, which can fail */
96070badfaSChen-Yu Tsai 	ret = drm_vblank_init(drm, drm->mode_config.num_crtc);
97070badfaSChen-Yu Tsai 	if (ret)
98c22f2ff8SJohan Hovold 		goto unbind_all;
99070badfaSChen-Yu Tsai 
1003d6bd906SMaxime Ripard 	/* Remove early framebuffers (ie. simplefb) */
10162aeaeaaSDaniel Vetter 	ret = drm_aperture_remove_framebuffers(&sun4i_drv_driver);
1026848c291SThomas Zimmermann 	if (ret)
103c22f2ff8SJohan Hovold 		goto unbind_all;
1043d6bd906SMaxime Ripard 
10594ebfc07SNoralf Trønnes 	sun4i_framebuffer_init(drm);
1069026e0d1SMaxime Ripard 
1079026e0d1SMaxime Ripard 	/* Enable connectors polling */
1089026e0d1SMaxime Ripard 	drm_kms_helper_poll_init(drm);
1099026e0d1SMaxime Ripard 
1109026e0d1SMaxime Ripard 	ret = drm_dev_register(drm, 0);
1119026e0d1SMaxime Ripard 	if (ret)
1129d56defbSChen-Yu Tsai 		goto finish_poll;
1139026e0d1SMaxime Ripard 
114a5b179acSThomas Zimmermann 	drm_fbdev_dma_setup(drm, 32);
11594ebfc07SNoralf Trønnes 
1161342b5b2SSamuel Holland 	dev_set_drvdata(dev, drm);
1171342b5b2SSamuel Holland 
1189026e0d1SMaxime Ripard 	return 0;
1199026e0d1SMaxime Ripard 
1209d56defbSChen-Yu Tsai finish_poll:
1219d56defbSChen-Yu Tsai 	drm_kms_helper_poll_fini(drm);
122c22f2ff8SJohan Hovold unbind_all:
123c22f2ff8SJohan Hovold 	component_unbind_all(dev, NULL);
1249d56defbSChen-Yu Tsai cleanup_mode_config:
1259d56defbSChen-Yu Tsai 	drm_mode_config_cleanup(drm);
126596afb6fSMaxime Ripard 	of_reserved_mem_device_release(dev);
1279026e0d1SMaxime Ripard free_drm:
1284c2ae34fSThomas Zimmermann 	drm_dev_put(drm);
1299026e0d1SMaxime Ripard 	return ret;
1309026e0d1SMaxime Ripard }
1319026e0d1SMaxime Ripard 
sun4i_drv_unbind(struct device * dev)1329026e0d1SMaxime Ripard static void sun4i_drv_unbind(struct device *dev)
1339026e0d1SMaxime Ripard {
1349026e0d1SMaxime Ripard 	struct drm_device *drm = dev_get_drvdata(dev);
1359026e0d1SMaxime Ripard 
1361342b5b2SSamuel Holland 	dev_set_drvdata(dev, NULL);
1379026e0d1SMaxime Ripard 	drm_dev_unregister(drm);
1389026e0d1SMaxime Ripard 	drm_kms_helper_poll_fini(drm);
13971adf60fSPaul Kocialkowski 	drm_atomic_helper_shutdown(drm);
14092caf9beSChen-Yu Tsai 	drm_mode_config_cleanup(drm);
141f5a9ed86SPaul Kocialkowski 
142f5a9ed86SPaul Kocialkowski 	component_unbind_all(dev, NULL);
143596afb6fSMaxime Ripard 	of_reserved_mem_device_release(dev);
144e02bc29bSPaul Kocialkowski 
1454c2ae34fSThomas Zimmermann 	drm_dev_put(drm);
1469026e0d1SMaxime Ripard }
1479026e0d1SMaxime Ripard 
1489026e0d1SMaxime Ripard static const struct component_master_ops sun4i_drv_master_ops = {
1499026e0d1SMaxime Ripard 	.bind	= sun4i_drv_bind,
1509026e0d1SMaxime Ripard 	.unbind	= sun4i_drv_unbind,
1519026e0d1SMaxime Ripard };
1529026e0d1SMaxime Ripard 
sun4i_drv_node_is_connector(struct device_node * node)15349baeb07SMaxime Ripard static bool sun4i_drv_node_is_connector(struct device_node *node)
15449baeb07SMaxime Ripard {
15549baeb07SMaxime Ripard 	return of_device_is_compatible(node, "hdmi-connector");
15649baeb07SMaxime Ripard }
15749baeb07SMaxime Ripard 
sun4i_drv_node_is_frontend(struct device_node * node)1589026e0d1SMaxime Ripard static bool sun4i_drv_node_is_frontend(struct device_node *node)
1599026e0d1SMaxime Ripard {
1609a8187c0SChen-Yu Tsai 	return of_device_is_compatible(node, "allwinner,sun4i-a10-display-frontend") ||
1619a8187c0SChen-Yu Tsai 		of_device_is_compatible(node, "allwinner,sun5i-a13-display-frontend") ||
16249c440e8SChen-Yu Tsai 		of_device_is_compatible(node, "allwinner,sun6i-a31-display-frontend") ||
163aaddb6d2SJonathan Liu 		of_device_is_compatible(node, "allwinner,sun7i-a20-display-frontend") ||
164d0ec0a3eSChen-Yu Tsai 		of_device_is_compatible(node, "allwinner,sun8i-a23-display-frontend") ||
16533478959SChen-Yu Tsai 		of_device_is_compatible(node, "allwinner,sun8i-a33-display-frontend") ||
16633478959SChen-Yu Tsai 		of_device_is_compatible(node, "allwinner,sun9i-a80-display-frontend");
16733478959SChen-Yu Tsai }
16833478959SChen-Yu Tsai 
sun4i_drv_node_is_deu(struct device_node * node)16933478959SChen-Yu Tsai static bool sun4i_drv_node_is_deu(struct device_node *node)
17033478959SChen-Yu Tsai {
17133478959SChen-Yu Tsai 	return of_device_is_compatible(node, "allwinner,sun9i-a80-deu");
1729026e0d1SMaxime Ripard }
1739026e0d1SMaxime Ripard 
sun4i_drv_node_is_supported_frontend(struct device_node * node)174dd0421f4SMaxime Ripard static bool sun4i_drv_node_is_supported_frontend(struct device_node *node)
175dd0421f4SMaxime Ripard {
176dd0421f4SMaxime Ripard 	if (IS_ENABLED(CONFIG_DRM_SUN4I_BACKEND))
177dd0421f4SMaxime Ripard 		return !!of_match_node(sun4i_frontend_of_table, node);
178dd0421f4SMaxime Ripard 
179dd0421f4SMaxime Ripard 	return false;
180dd0421f4SMaxime Ripard }
181dd0421f4SMaxime Ripard 
sun4i_drv_node_is_tcon(struct device_node * node)18229e57fabSMaxime Ripard static bool sun4i_drv_node_is_tcon(struct device_node *node)
18329e57fabSMaxime Ripard {
184ff71c2cfSChen-Yu Tsai 	return !!of_match_node(sun4i_tcon_of_table, node);
18529e57fabSMaxime Ripard }
18629e57fabSMaxime Ripard 
sun4i_drv_node_is_tcon_with_ch0(struct device_node * node)187c5cf04dfSJernej Skrabec static bool sun4i_drv_node_is_tcon_with_ch0(struct device_node *node)
188c5cf04dfSJernej Skrabec {
189c5cf04dfSJernej Skrabec 	const struct of_device_id *match;
190c5cf04dfSJernej Skrabec 
191c5cf04dfSJernej Skrabec 	match = of_match_node(sun4i_tcon_of_table, node);
192c5cf04dfSJernej Skrabec 	if (match) {
193c5cf04dfSJernej Skrabec 		struct sun4i_tcon_quirks *quirks;
194c5cf04dfSJernej Skrabec 
195c5cf04dfSJernej Skrabec 		quirks = (struct sun4i_tcon_quirks *)match->data;
196c5cf04dfSJernej Skrabec 
197c5cf04dfSJernej Skrabec 		return quirks->has_channel_0;
198c5cf04dfSJernej Skrabec 	}
199c5cf04dfSJernej Skrabec 
200c5cf04dfSJernej Skrabec 	return false;
201c5cf04dfSJernej Skrabec }
202c5cf04dfSJernej Skrabec 
sun4i_drv_node_is_tcon_top(struct device_node * node)203ef0cf644SJernej Skrabec static bool sun4i_drv_node_is_tcon_top(struct device_node *node)
204ef0cf644SJernej Skrabec {
20558d4d298SArnd Bergmann 	return IS_ENABLED(CONFIG_DRM_SUN8I_TCON_TOP) &&
20658d4d298SArnd Bergmann 		!!of_match_node(sun8i_tcon_top_of_table, node);
207ef0cf644SJernej Skrabec }
208ef0cf644SJernej Skrabec 
209da82b878SChen-Yu Tsai /*
210da82b878SChen-Yu Tsai  * The encoder drivers use drm_of_find_possible_crtcs to get upstream
211da82b878SChen-Yu Tsai  * crtcs from the device tree using of_graph. For the results to be
212da82b878SChen-Yu Tsai  * correct, encoders must be probed/bound after _all_ crtcs have been
213da82b878SChen-Yu Tsai  * created. The existing code uses a depth first recursive traversal
214da82b878SChen-Yu Tsai  * of the of_graph, which means the encoders downstream of the TCON
215da82b878SChen-Yu Tsai  * get add right after the first TCON. The second TCON or CRTC will
216da82b878SChen-Yu Tsai  * never be properly associated with encoders connected to it.
217da82b878SChen-Yu Tsai  *
218da82b878SChen-Yu Tsai  * Also, in a dual display pipeline setup, both frontends can feed
219da82b878SChen-Yu Tsai  * either backend, and both backends can feed either TCON, we want
220da82b878SChen-Yu Tsai  * all components of the same type to be added before the next type
221da82b878SChen-Yu Tsai  * in the pipeline. Fortunately, the pipelines are perfectly symmetric,
222da82b878SChen-Yu Tsai  * i.e. components of the same type are at the same depth when counted
223da82b878SChen-Yu Tsai  * from the frontend. The only exception is the third pipeline in
224da82b878SChen-Yu Tsai  * the A80 SoC, which we do not support anyway.
225da82b878SChen-Yu Tsai  *
226da82b878SChen-Yu Tsai  * Hence we can use a breadth first search traversal order to add
227da82b878SChen-Yu Tsai  * components. We do not need to check for duplicates. The component
228da82b878SChen-Yu Tsai  * matching system handles this for us.
229da82b878SChen-Yu Tsai  */
230da82b878SChen-Yu Tsai struct endpoint_list {
2318b11aafaSMaxime Ripard 	DECLARE_KFIFO(fifo, struct device_node *, 16);
232da82b878SChen-Yu Tsai };
233da82b878SChen-Yu Tsai 
sun4i_drv_traverse_endpoints(struct endpoint_list * list,struct device_node * node,int port_id)23471f4796aSJernej Skrabec static void sun4i_drv_traverse_endpoints(struct endpoint_list *list,
23571f4796aSJernej Skrabec 					 struct device_node *node,
23671f4796aSJernej Skrabec 					 int port_id)
23771f4796aSJernej Skrabec {
23871f4796aSJernej Skrabec 	struct device_node *ep, *remote, *port;
23971f4796aSJernej Skrabec 
24071f4796aSJernej Skrabec 	port = of_graph_get_port_by_id(node, port_id);
24171f4796aSJernej Skrabec 	if (!port) {
24271f4796aSJernej Skrabec 		DRM_DEBUG_DRIVER("No output to bind on port %d\n", port_id);
24371f4796aSJernej Skrabec 		return;
24471f4796aSJernej Skrabec 	}
24571f4796aSJernej Skrabec 
24671f4796aSJernej Skrabec 	for_each_available_child_of_node(port, ep) {
24771f4796aSJernej Skrabec 		remote = of_graph_get_remote_port_parent(ep);
24871f4796aSJernej Skrabec 		if (!remote) {
24971f4796aSJernej Skrabec 			DRM_DEBUG_DRIVER("Error retrieving the output node\n");
25071f4796aSJernej Skrabec 			continue;
25171f4796aSJernej Skrabec 		}
25271f4796aSJernej Skrabec 
25371f4796aSJernej Skrabec 		if (sun4i_drv_node_is_tcon(node)) {
254ef0cf644SJernej Skrabec 			/*
255ef0cf644SJernej Skrabec 			 * TCON TOP is always probed before TCON. However, TCON
256ef0cf644SJernej Skrabec 			 * points back to TCON TOP when it is source for HDMI.
257ef0cf644SJernej Skrabec 			 * We have to skip it here to prevent infinite looping
258ef0cf644SJernej Skrabec 			 * between TCON TOP and TCON.
259ef0cf644SJernej Skrabec 			 */
260ef0cf644SJernej Skrabec 			if (sun4i_drv_node_is_tcon_top(remote)) {
261ef0cf644SJernej Skrabec 				DRM_DEBUG_DRIVER("TCON output endpoint is TCON TOP... skipping\n");
262ef0cf644SJernej Skrabec 				of_node_put(remote);
263ef0cf644SJernej Skrabec 				continue;
264ef0cf644SJernej Skrabec 			}
265ef0cf644SJernej Skrabec 
266c5cf04dfSJernej Skrabec 			/*
267c5cf04dfSJernej Skrabec 			 * If the node is our TCON with channel 0, the first
268c5cf04dfSJernej Skrabec 			 * port is used for panel or bridges, and will not be
269c5cf04dfSJernej Skrabec 			 * part of the component framework.
270c5cf04dfSJernej Skrabec 			 */
271c5cf04dfSJernej Skrabec 			if (sun4i_drv_node_is_tcon_with_ch0(node)) {
272c5cf04dfSJernej Skrabec 				struct of_endpoint endpoint;
273c5cf04dfSJernej Skrabec 
27471f4796aSJernej Skrabec 				if (of_graph_parse_endpoint(ep, &endpoint)) {
27571f4796aSJernej Skrabec 					DRM_DEBUG_DRIVER("Couldn't parse endpoint\n");
27671f4796aSJernej Skrabec 					of_node_put(remote);
27771f4796aSJernej Skrabec 					continue;
27871f4796aSJernej Skrabec 				}
27971f4796aSJernej Skrabec 
28071f4796aSJernej Skrabec 				if (!endpoint.id) {
28171f4796aSJernej Skrabec 					DRM_DEBUG_DRIVER("Endpoint is our panel... skipping\n");
28271f4796aSJernej Skrabec 					of_node_put(remote);
28371f4796aSJernej Skrabec 					continue;
28471f4796aSJernej Skrabec 				}
28571f4796aSJernej Skrabec 			}
286c5cf04dfSJernej Skrabec 		}
28771f4796aSJernej Skrabec 
28871f4796aSJernej Skrabec 		kfifo_put(&list->fifo, remote);
28971f4796aSJernej Skrabec 	}
29071f4796aSJernej Skrabec }
29171f4796aSJernej Skrabec 
sun4i_drv_add_endpoints(struct device * dev,struct endpoint_list * list,struct component_match ** match,struct device_node * node)2929026e0d1SMaxime Ripard static int sun4i_drv_add_endpoints(struct device *dev,
2938b11aafaSMaxime Ripard 				   struct endpoint_list *list,
2949026e0d1SMaxime Ripard 				   struct component_match **match,
2959026e0d1SMaxime Ripard 				   struct device_node *node)
2969026e0d1SMaxime Ripard {
2979026e0d1SMaxime Ripard 	int count = 0;
2989026e0d1SMaxime Ripard 
2999026e0d1SMaxime Ripard 	/*
300dd0421f4SMaxime Ripard 	 * The frontend has been disabled in some of our old device
301dd0421f4SMaxime Ripard 	 * trees. If we find a node that is the frontend and is
302dd0421f4SMaxime Ripard 	 * disabled, we should just follow through and parse its
303dd0421f4SMaxime Ripard 	 * child, but without adding it to the component list.
304dd0421f4SMaxime Ripard 	 * Otherwise, we obviously want to add it to the list.
3059026e0d1SMaxime Ripard 	 */
3069026e0d1SMaxime Ripard 	if (!sun4i_drv_node_is_frontend(node) &&
3079026e0d1SMaxime Ripard 	    !of_device_is_available(node))
3089026e0d1SMaxime Ripard 		return 0;
3099026e0d1SMaxime Ripard 
31049baeb07SMaxime Ripard 	/*
31149baeb07SMaxime Ripard 	 * The connectors will be the last nodes in our pipeline, we
31249baeb07SMaxime Ripard 	 * can just bail out.
31349baeb07SMaxime Ripard 	 */
31449baeb07SMaxime Ripard 	if (sun4i_drv_node_is_connector(node))
31549baeb07SMaxime Ripard 		return 0;
31649baeb07SMaxime Ripard 
317dd0421f4SMaxime Ripard 	/*
318dd0421f4SMaxime Ripard 	 * If the device is either just a regular device, or an
319dd0421f4SMaxime Ripard 	 * enabled frontend supported by the driver, we add it to our
320dd0421f4SMaxime Ripard 	 * component list.
321dd0421f4SMaxime Ripard 	 */
32233478959SChen-Yu Tsai 	if (!(sun4i_drv_node_is_frontend(node) ||
32333478959SChen-Yu Tsai 	      sun4i_drv_node_is_deu(node)) ||
324dd0421f4SMaxime Ripard 	    (sun4i_drv_node_is_supported_frontend(node) &&
325dd0421f4SMaxime Ripard 	     of_device_is_available(node))) {
3269026e0d1SMaxime Ripard 		/* Add current component */
3274bf99144SRob Herring 		DRM_DEBUG_DRIVER("Adding component %pOF\n", node);
3286817222eSYong Wu 		drm_of_component_match_add(dev, match, component_compare_of, node);
3299026e0d1SMaxime Ripard 		count++;
3309026e0d1SMaxime Ripard 	}
3319026e0d1SMaxime Ripard 
33271f4796aSJernej Skrabec 	/* each node has at least one output */
33371f4796aSJernej Skrabec 	sun4i_drv_traverse_endpoints(list, node, 1);
3349026e0d1SMaxime Ripard 
335ef0cf644SJernej Skrabec 	/* TCON TOP has second and third output */
336ef0cf644SJernej Skrabec 	if (sun4i_drv_node_is_tcon_top(node)) {
337ef0cf644SJernej Skrabec 		sun4i_drv_traverse_endpoints(list, node, 3);
338ef0cf644SJernej Skrabec 		sun4i_drv_traverse_endpoints(list, node, 5);
339ef0cf644SJernej Skrabec 	}
340ef0cf644SJernej Skrabec 
3419026e0d1SMaxime Ripard 	return count;
3429026e0d1SMaxime Ripard }
3439026e0d1SMaxime Ripard 
344624b4b48SOndrej Jirman #ifdef CONFIG_PM_SLEEP
sun4i_drv_drm_sys_suspend(struct device * dev)345624b4b48SOndrej Jirman static int sun4i_drv_drm_sys_suspend(struct device *dev)
346624b4b48SOndrej Jirman {
347624b4b48SOndrej Jirman 	struct drm_device *drm = dev_get_drvdata(dev);
348624b4b48SOndrej Jirman 
349624b4b48SOndrej Jirman 	return drm_mode_config_helper_suspend(drm);
350624b4b48SOndrej Jirman }
351624b4b48SOndrej Jirman 
sun4i_drv_drm_sys_resume(struct device * dev)352624b4b48SOndrej Jirman static int sun4i_drv_drm_sys_resume(struct device *dev)
353624b4b48SOndrej Jirman {
354624b4b48SOndrej Jirman 	struct drm_device *drm = dev_get_drvdata(dev);
355624b4b48SOndrej Jirman 
356624b4b48SOndrej Jirman 	return drm_mode_config_helper_resume(drm);
357624b4b48SOndrej Jirman }
358624b4b48SOndrej Jirman #endif
359624b4b48SOndrej Jirman 
360624b4b48SOndrej Jirman static const struct dev_pm_ops sun4i_drv_drm_pm_ops = {
361624b4b48SOndrej Jirman 	SET_SYSTEM_SLEEP_PM_OPS(sun4i_drv_drm_sys_suspend,
362624b4b48SOndrej Jirman 				sun4i_drv_drm_sys_resume)
363624b4b48SOndrej Jirman };
364624b4b48SOndrej Jirman 
sun4i_drv_probe(struct platform_device * pdev)3659026e0d1SMaxime Ripard static int sun4i_drv_probe(struct platform_device *pdev)
3669026e0d1SMaxime Ripard {
3679026e0d1SMaxime Ripard 	struct component_match *match = NULL;
3688b11aafaSMaxime Ripard 	struct device_node *np = pdev->dev.of_node, *endpoint;
3698b11aafaSMaxime Ripard 	struct endpoint_list list;
370da82b878SChen-Yu Tsai 	int i, ret, count = 0;
3718b11aafaSMaxime Ripard 
3728b11aafaSMaxime Ripard 	INIT_KFIFO(list.fifo);
3739026e0d1SMaxime Ripard 
374f5aa1680SJernej Skrabec 	/*
375f5aa1680SJernej Skrabec 	 * DE2 and DE3 cores actually supports 40-bit addresses, but
376f5aa1680SJernej Skrabec 	 * driver does not.
377f5aa1680SJernej Skrabec 	 */
378f5aa1680SJernej Skrabec 	dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
379f5aa1680SJernej Skrabec 	dma_set_max_seg_size(&pdev->dev, UINT_MAX);
380f5aa1680SJernej Skrabec 
3819026e0d1SMaxime Ripard 	for (i = 0;; i++) {
3829026e0d1SMaxime Ripard 		struct device_node *pipeline = of_parse_phandle(np,
3839026e0d1SMaxime Ripard 								"allwinner,pipelines",
3849026e0d1SMaxime Ripard 								i);
3859026e0d1SMaxime Ripard 		if (!pipeline)
3869026e0d1SMaxime Ripard 			break;
3879026e0d1SMaxime Ripard 
3888b11aafaSMaxime Ripard 		kfifo_put(&list.fifo, pipeline);
389da82b878SChen-Yu Tsai 	}
3909026e0d1SMaxime Ripard 
3918b11aafaSMaxime Ripard 	while (kfifo_get(&list.fifo, &endpoint)) {
392da82b878SChen-Yu Tsai 		/* process this endpoint */
3938b11aafaSMaxime Ripard 		ret = sun4i_drv_add_endpoints(&pdev->dev, &list, &match,
3948b11aafaSMaxime Ripard 					      endpoint);
395da82b878SChen-Yu Tsai 
396da82b878SChen-Yu Tsai 		/* sun4i_drv_add_endpoints can fail to allocate memory */
397da82b878SChen-Yu Tsai 		if (ret < 0)
3988b11aafaSMaxime Ripard 			return ret;
399da82b878SChen-Yu Tsai 
400da82b878SChen-Yu Tsai 		count += ret;
4019026e0d1SMaxime Ripard 	}
4029026e0d1SMaxime Ripard 
4039026e0d1SMaxime Ripard 	if (count)
4049026e0d1SMaxime Ripard 		return component_master_add_with_match(&pdev->dev,
4059026e0d1SMaxime Ripard 						       &sun4i_drv_master_ops,
4069026e0d1SMaxime Ripard 						       match);
4079026e0d1SMaxime Ripard 	else
4089026e0d1SMaxime Ripard 		return 0;
4099026e0d1SMaxime Ripard }
4109026e0d1SMaxime Ripard 
sun4i_drv_remove(struct platform_device * pdev)411*d665e3c9SUwe Kleine-König static void sun4i_drv_remove(struct platform_device *pdev)
4129026e0d1SMaxime Ripard {
413f5a9ed86SPaul Kocialkowski 	component_master_del(&pdev->dev, &sun4i_drv_master_ops);
4149026e0d1SMaxime Ripard }
4159026e0d1SMaxime Ripard 
4169026e0d1SMaxime Ripard static const struct of_device_id sun4i_drv_of_table[] = {
4179a8187c0SChen-Yu Tsai 	{ .compatible = "allwinner,sun4i-a10-display-engine" },
418110d33ddSMaxime Ripard 	{ .compatible = "allwinner,sun5i-a10s-display-engine" },
4199026e0d1SMaxime Ripard 	{ .compatible = "allwinner,sun5i-a13-display-engine" },
42049c440e8SChen-Yu Tsai 	{ .compatible = "allwinner,sun6i-a31-display-engine" },
42149c440e8SChen-Yu Tsai 	{ .compatible = "allwinner,sun6i-a31s-display-engine" },
422aaddb6d2SJonathan Liu 	{ .compatible = "allwinner,sun7i-a20-display-engine" },
423d0ec0a3eSChen-Yu Tsai 	{ .compatible = "allwinner,sun8i-a23-display-engine" },
4244a408f1fSMaxime Ripard 	{ .compatible = "allwinner,sun8i-a33-display-engine" },
4252f0d7bb1SMaxime Ripard 	{ .compatible = "allwinner,sun8i-a83t-display-engine" },
4261ceb5f1bSJernej Skrabec 	{ .compatible = "allwinner,sun8i-h3-display-engine" },
4273dcf0f30SChen-Yu Tsai 	{ .compatible = "allwinner,sun8i-r40-display-engine" },
4289df90c25SIcenowy Zheng 	{ .compatible = "allwinner,sun8i-v3s-display-engine" },
42933478959SChen-Yu Tsai 	{ .compatible = "allwinner,sun9i-a80-display-engine" },
4302deb9739SSamuel Holland 	{ .compatible = "allwinner,sun20i-d1-display-engine" },
431dd8bd547SJagan Teki 	{ .compatible = "allwinner,sun50i-a64-display-engine" },
43297f2930fSJernej Skrabec 	{ .compatible = "allwinner,sun50i-h6-display-engine" },
4339026e0d1SMaxime Ripard 	{ }
4349026e0d1SMaxime Ripard };
4359026e0d1SMaxime Ripard MODULE_DEVICE_TABLE(of, sun4i_drv_of_table);
4369026e0d1SMaxime Ripard 
4379026e0d1SMaxime Ripard static struct platform_driver sun4i_drv_platform_driver = {
4389026e0d1SMaxime Ripard 	.probe		= sun4i_drv_probe,
439*d665e3c9SUwe Kleine-König 	.remove_new	= sun4i_drv_remove,
4409026e0d1SMaxime Ripard 	.driver		= {
4419026e0d1SMaxime Ripard 		.name		= "sun4i-drm",
4429026e0d1SMaxime Ripard 		.of_match_table	= sun4i_drv_of_table,
443624b4b48SOndrej Jirman 		.pm = &sun4i_drv_drm_pm_ops,
4449026e0d1SMaxime Ripard 	},
4459026e0d1SMaxime Ripard };
446ab41e6aaSJavier Martinez Canillas drm_module_platform_driver(sun4i_drv_platform_driver);
4479026e0d1SMaxime Ripard 
4489026e0d1SMaxime Ripard MODULE_AUTHOR("Boris Brezillon <boris.brezillon@free-electrons.com>");
4499026e0d1SMaxime Ripard MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
4509026e0d1SMaxime Ripard MODULE_DESCRIPTION("Allwinner A10 Display Engine DRM/KMS Driver");
4519026e0d1SMaxime Ripard MODULE_LICENSE("GPL");
452