sun4i_drv.c 7.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/*
 * Copyright (C) 2015 Free Electrons
 * Copyright (C) 2015 NextThing Co
 *
 * Maxime Ripard <maxime.ripard@free-electrons.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 */

#include <linux/component.h>
#include <linux/of_graph.h>

#include <drm/drmP.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_fb_cma_helper.h>
#include <drm/drm_gem_cma_helper.h>
20
#include <drm/drm_fb_helper.h>
21
#include <drm/drm_of.h>
22 23 24

#include "sun4i_drv.h"
#include "sun4i_framebuffer.h"
25
#include "sun4i_tcon.h"
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53

static const struct file_operations sun4i_drv_fops = {
	.owner		= THIS_MODULE,
	.open		= drm_open,
	.release	= drm_release,
	.unlocked_ioctl	= drm_ioctl,
	.compat_ioctl	= drm_compat_ioctl,
	.poll		= drm_poll,
	.read		= drm_read,
	.llseek		= no_llseek,
	.mmap		= drm_gem_cma_mmap,
};

static struct drm_driver sun4i_drv_driver = {
	.driver_features	= DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME | DRIVER_ATOMIC,

	/* Generic Operations */
	.fops			= &sun4i_drv_fops,
	.name			= "sun4i-drm",
	.desc			= "Allwinner sun4i Display Engine",
	.date			= "20150629",
	.major			= 1,
	.minor			= 0,

	/* GEM Operations */
	.dumb_create		= drm_gem_cma_dumb_create,
	.dumb_destroy		= drm_gem_dumb_destroy,
	.dumb_map_offset	= drm_gem_cma_dumb_map_offset,
54
	.gem_free_object_unlocked = drm_gem_cma_free_object,
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
	.gem_vm_ops		= &drm_gem_cma_vm_ops,

	/* PRIME Operations */
	.prime_handle_to_fd	= drm_gem_prime_handle_to_fd,
	.prime_fd_to_handle	= drm_gem_prime_fd_to_handle,
	.gem_prime_import	= drm_gem_prime_import,
	.gem_prime_export	= drm_gem_prime_export,
	.gem_prime_get_sg_table	= drm_gem_cma_prime_get_sg_table,
	.gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table,
	.gem_prime_vmap		= drm_gem_cma_prime_vmap,
	.gem_prime_vunmap	= drm_gem_cma_prime_vunmap,
	.gem_prime_mmap		= drm_gem_cma_prime_mmap,

	/* Frame Buffer Operations */
};

71 72 73 74 75 76 77 78 79 80 81 82
static void sun4i_remove_framebuffers(void)
{
	struct apertures_struct *ap;

	ap = alloc_apertures(1);
	if (!ap)
		return;

	/* The framebuffer can be located anywhere in RAM */
	ap->ranges[0].base = 0;
	ap->ranges[0].size = ~0;

83
	drm_fb_helper_remove_conflicting_framebuffers(ap, "sun4i-drm-fb", false);
84 85 86
	kfree(ap);
}

87 88 89 90 91 92 93
static int sun4i_drv_bind(struct device *dev)
{
	struct drm_device *drm;
	struct sun4i_drv *drv;
	int ret;

	drm = drm_dev_alloc(&sun4i_drv_driver, dev);
94 95
	if (IS_ERR(drm))
		return PTR_ERR(drm);
96 97 98 99 100 101 102 103

	drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL);
	if (!drv) {
		ret = -ENOMEM;
		goto free_drm;
	}
	drm->dev_private = drv;

104 105 106 107 108
	/* drm_vblank_init calls kcalloc, which can fail */
	ret = drm_vblank_init(drm, 1);
	if (ret)
		goto free_drm;

109 110 111 112 113
	drm_mode_config_init(drm);

	ret = component_bind_all(drm->dev, drm);
	if (ret) {
		dev_err(drm->dev, "Couldn't bind all pipelines components\n");
114
		goto cleanup_mode_config;
115 116 117 118
	}

	drm->irq_enabled = true;

119 120 121
	/* Remove early framebuffers (ie. simplefb) */
	sun4i_remove_framebuffers();

122 123 124 125 126
	/* Create our framebuffer */
	drv->fbdev = sun4i_framebuffer_init(drm);
	if (IS_ERR(drv->fbdev)) {
		dev_err(drm->dev, "Couldn't create our framebuffer\n");
		ret = PTR_ERR(drv->fbdev);
127
		goto cleanup_mode_config;
128 129 130 131 132 133 134
	}

	/* Enable connectors polling */
	drm_kms_helper_poll_init(drm);

	ret = drm_dev_register(drm, 0);
	if (ret)
135
		goto finish_poll;
136 137 138

	return 0;

139 140 141 142 143 144
finish_poll:
	drm_kms_helper_poll_fini(drm);
	sun4i_framebuffer_free(drm);
cleanup_mode_config:
	drm_mode_config_cleanup(drm);
	drm_vblank_cleanup(drm);
145 146 147 148 149 150 151 152 153 154 155 156
free_drm:
	drm_dev_unref(drm);
	return ret;
}

static void sun4i_drv_unbind(struct device *dev)
{
	struct drm_device *drm = dev_get_drvdata(dev);

	drm_dev_unregister(drm);
	drm_kms_helper_poll_fini(drm);
	sun4i_framebuffer_free(drm);
157
	drm_mode_config_cleanup(drm);
158 159 160 161 162 163 164 165 166 167 168
	drm_vblank_cleanup(drm);
	drm_dev_unref(drm);
}

static const struct component_master_ops sun4i_drv_master_ops = {
	.bind	= sun4i_drv_bind,
	.unbind	= sun4i_drv_unbind,
};

static bool sun4i_drv_node_is_frontend(struct device_node *node)
{
M
Maxime Ripard 已提交
169
	return of_device_is_compatible(node, "allwinner,sun5i-a13-display-frontend") ||
170
		of_device_is_compatible(node, "allwinner,sun6i-a31-display-frontend") ||
M
Maxime Ripard 已提交
171
		of_device_is_compatible(node, "allwinner,sun8i-a33-display-frontend");
172 173
}

M
Maxime Ripard 已提交
174 175
static bool sun4i_drv_node_is_tcon(struct device_node *node)
{
M
Maxime Ripard 已提交
176
	return of_device_is_compatible(node, "allwinner,sun5i-a13-tcon") ||
177 178
		of_device_is_compatible(node, "allwinner,sun6i-a31-tcon") ||
		of_device_is_compatible(node, "allwinner,sun6i-a31s-tcon") ||
M
Maxime Ripard 已提交
179
		of_device_is_compatible(node, "allwinner,sun8i-a33-tcon");
M
Maxime Ripard 已提交
180 181
}

182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
static int compare_of(struct device *dev, void *data)
{
	DRM_DEBUG_DRIVER("Comparing of node %s with %s\n",
			 of_node_full_name(dev->of_node),
			 of_node_full_name(data));

	return dev->of_node == data;
}

static int sun4i_drv_add_endpoints(struct device *dev,
				   struct component_match **match,
				   struct device_node *node)
{
	struct device_node *port, *ep, *remote;
	int count = 0;

	/*
	 * We don't support the frontend for now, so we will never
	 * have a device bound. Just skip over it, but we still want
	 * the rest our pipeline to be added.
	 */
	if (!sun4i_drv_node_is_frontend(node) &&
	    !of_device_is_available(node))
		return 0;

	if (!sun4i_drv_node_is_frontend(node)) {
		/* Add current component */
		DRM_DEBUG_DRIVER("Adding component %s\n",
				 of_node_full_name(node));
211
		drm_of_component_match_add(dev, match, compare_of, node);
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229
		count++;
	}

	/* Inputs are listed first, then outputs */
	port = of_graph_get_port_by_id(node, 1);
	if (!port) {
		DRM_DEBUG_DRIVER("No output to bind\n");
		return count;
	}

	for_each_available_child_of_node(port, ep) {
		remote = of_graph_get_remote_port_parent(ep);
		if (!remote) {
			DRM_DEBUG_DRIVER("Error retrieving the output node\n");
			of_node_put(remote);
			continue;
		}

M
Maxime Ripard 已提交
230
		/*
M
Maxime Ripard 已提交
231 232
		 * If the node is our TCON, the first port is used for
		 * panel or bridges, and will not be part of the
M
Maxime Ripard 已提交
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248
		 * component framework.
		 */
		if (sun4i_drv_node_is_tcon(node)) {
			struct of_endpoint endpoint;

			if (of_graph_parse_endpoint(ep, &endpoint)) {
				DRM_DEBUG_DRIVER("Couldn't parse endpoint\n");
				continue;
			}

			if (!endpoint.id) {
				DRM_DEBUG_DRIVER("Endpoint is our panel... skipping\n");
				continue;
			}
		}

249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272
		/* Walk down our tree */
		count += sun4i_drv_add_endpoints(dev, match, remote);

		of_node_put(remote);
	}

	return count;
}

static int sun4i_drv_probe(struct platform_device *pdev)
{
	struct component_match *match = NULL;
	struct device_node *np = pdev->dev.of_node;
	int i, count = 0;

	for (i = 0;; i++) {
		struct device_node *pipeline = of_parse_phandle(np,
								"allwinner,pipelines",
								i);
		if (!pipeline)
			break;

		count += sun4i_drv_add_endpoints(&pdev->dev, &match,
						pipeline);
273
		of_node_put(pipeline);
274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293

		DRM_DEBUG_DRIVER("Queued %d outputs on pipeline %d\n",
				 count, i);
	}

	if (count)
		return component_master_add_with_match(&pdev->dev,
						       &sun4i_drv_master_ops,
						       match);
	else
		return 0;
}

static int sun4i_drv_remove(struct platform_device *pdev)
{
	return 0;
}

static const struct of_device_id sun4i_drv_of_table[] = {
	{ .compatible = "allwinner,sun5i-a13-display-engine" },
294 295
	{ .compatible = "allwinner,sun6i-a31-display-engine" },
	{ .compatible = "allwinner,sun6i-a31s-display-engine" },
M
Maxime Ripard 已提交
296
	{ .compatible = "allwinner,sun8i-a33-display-engine" },
297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
	{ }
};
MODULE_DEVICE_TABLE(of, sun4i_drv_of_table);

static struct platform_driver sun4i_drv_platform_driver = {
	.probe		= sun4i_drv_probe,
	.remove		= sun4i_drv_remove,
	.driver		= {
		.name		= "sun4i-drm",
		.of_match_table	= sun4i_drv_of_table,
	},
};
module_platform_driver(sun4i_drv_platform_driver);

MODULE_AUTHOR("Boris Brezillon <boris.brezillon@free-electrons.com>");
MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com>");
MODULE_DESCRIPTION("Allwinner A10 Display Engine DRM/KMS Driver");
MODULE_LICENSE("GPL");