imx-drm-core.c 10.4 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
/*
 * Freescale i.MX drm driver
 *
 * Copyright (C) 2011 Sascha Hauer, Pengutronix
 *
 * 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.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */
16
#include <linux/component.h>
17
#include <linux/device.h>
18
#include <linux/dma-buf.h>
19
#include <linux/module.h>
20 21
#include <linux/platform_device.h>
#include <drm/drmP.h>
22 23
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
24 25 26 27
#include <drm/drm_fb_helper.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_gem_cma_helper.h>
#include <drm/drm_fb_cma_helper.h>
28
#include <drm/drm_plane_helper.h>
29
#include <drm/drm_of.h>
30
#include <video/imx-ipu-v3.h>
31 32 33 34 35

#include "imx-drm.h"

#define MAX_CRTC	4

36 37 38 39 40
struct imx_drm_component {
	struct device_node *of_node;
	struct list_head list;
};

41 42
struct imx_drm_device {
	struct drm_device			*drm;
43
	unsigned int				pipes;
44
	struct drm_fbdev_cma			*fbhelper;
45
	struct drm_atomic_state			*state;
46 47
};

48
#if IS_ENABLED(CONFIG_DRM_FBDEV_EMULATION)
49 50
static int legacyfb_depth = 16;
module_param(legacyfb_depth, int, 0444);
51
#endif
52

53 54 55 56
static void imx_drm_driver_lastclose(struct drm_device *drm)
{
	struct imx_drm_device *imxdrm = drm->dev_private;

57
	drm_fbdev_cma_restore_mode(imxdrm->fbhelper);
58 59 60 61 62 63 64 65 66 67 68 69 70
}

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

71 72
void imx_drm_connector_destroy(struct drm_connector *connector)
{
73
	drm_connector_unregister(connector);
74 75 76 77 78 79 80 81 82 83
	drm_connector_cleanup(connector);
}
EXPORT_SYMBOL_GPL(imx_drm_connector_destroy);

void imx_drm_encoder_destroy(struct drm_encoder *encoder)
{
	drm_encoder_cleanup(encoder);
}
EXPORT_SYMBOL_GPL(imx_drm_encoder_destroy);

84 85 86 87 88 89 90
static void imx_drm_output_poll_changed(struct drm_device *drm)
{
	struct imx_drm_device *imxdrm = drm->dev_private;

	drm_fbdev_cma_hotplug_event(imxdrm->fbhelper);
}

91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
static int imx_drm_atomic_check(struct drm_device *dev,
				struct drm_atomic_state *state)
{
	int ret;

	ret = drm_atomic_helper_check_modeset(dev, state);
	if (ret)
		return ret;

	ret = drm_atomic_helper_check_planes(dev, state);
	if (ret)
		return ret;

	/*
	 * Check modeset again in case crtc_state->mode_changed is
	 * updated in plane's ->atomic_check callback.
	 */
	ret = drm_atomic_helper_check_modeset(dev, state);
	if (ret)
		return ret;

	return ret;
}

115
static const struct drm_mode_config_funcs imx_drm_mode_config_funcs = {
R
Russell King 已提交
116
	.fb_create = drm_fb_cma_create,
117
	.output_poll_changed = imx_drm_output_poll_changed,
118
	.atomic_check = imx_drm_atomic_check,
119
	.atomic_commit = drm_atomic_helper_commit,
120 121 122 123 124 125 126 127
};

static void imx_drm_atomic_commit_tail(struct drm_atomic_state *state)
{
	struct drm_device *dev = state->dev;

	drm_atomic_helper_commit_modeset_disables(dev, state);

128
	drm_atomic_helper_commit_planes(dev, state,
129 130
				DRM_PLANE_COMMIT_ACTIVE_ONLY |
				DRM_PLANE_COMMIT_NO_DISABLE_AFTER_MODESET);
131 132 133 134 135 136

	drm_atomic_helper_commit_modeset_enables(dev, state);

	drm_atomic_helper_commit_hw_done(state);
}

137
static const struct drm_mode_config_helper_funcs imx_drm_mode_config_helpers = {
138
	.atomic_commit_tail = imx_drm_atomic_commit_tail,
R
Russell King 已提交
139 140
};

141

142 143
int imx_drm_encoder_parse_of(struct drm_device *drm,
	struct drm_encoder *encoder, struct device_node *np)
144
{
145
	uint32_t crtc_mask = drm_of_find_possible_crtcs(drm, np);
146

147 148 149 150 151 152 153 154
	/*
	 * If we failed to find the CRTC(s) which this encoder is
	 * supposed to be connected to, it's because the CRTC has
	 * not been registered yet.  Defer probing, and hope that
	 * the required CRTC is added later.
	 */
	if (crtc_mask == 0)
		return -EPROBE_DEFER;
155

156
	encoder->possible_crtcs = crtc_mask;
157

158 159
	/* FIXME: this is the mask of outputs which can clone this output. */
	encoder->possible_clones = ~0;
160 161 162

	return 0;
}
163
EXPORT_SYMBOL_GPL(imx_drm_encoder_parse_of);
164

R
Rob Clark 已提交
165
static const struct drm_ioctl_desc imx_drm_ioctls[] = {
166 167 168 169
	/* none so far */
};

static struct drm_driver imx_drm_driver = {
170 171
	.driver_features	= DRIVER_MODESET | DRIVER_GEM | DRIVER_PRIME |
				  DRIVER_ATOMIC,
172
	.lastclose		= imx_drm_driver_lastclose,
173
	.gem_free_object_unlocked = drm_gem_cma_free_object,
174 175 176
	.gem_vm_ops		= &drm_gem_cma_vm_ops,
	.dumb_create		= drm_gem_cma_dumb_create,
	.dumb_map_offset	= drm_gem_cma_dumb_map_offset,
177
	.dumb_destroy		= drm_gem_dumb_destroy,
178

179 180 181 182 183 184 185 186 187
	.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,
188 189 190 191 192 193 194 195 196 197 198
	.ioctls			= imx_drm_ioctls,
	.num_ioctls		= ARRAY_SIZE(imx_drm_ioctls),
	.fops			= &imx_drm_driver_fops,
	.name			= "imx-drm",
	.desc			= "i.MX DRM graphics",
	.date			= "20120507",
	.major			= 1,
	.minor			= 0,
	.patchlevel		= 0,
};

199 200
static int compare_of(struct device *dev, void *data)
{
201
	struct device_node *np = data;
202

203 204 205 206 207 208 209
	/* Special case for DI, dev->of_node may not be set yet */
	if (strcmp(dev->driver->name, "imx-ipuv3-crtc") == 0) {
		struct ipu_client_platformdata *pdata = dev->platform_data;

		return pdata->of_node == np;
	}

210 211 212 213
	/* Special case for LDB, one device for two channels */
	if (of_node_cmp(np->name, "lvds-channel") == 0) {
		np = of_get_parent(np);
		of_node_put(np);
214 215
	}

216 217
	return dev->of_node == np;
}
218 219 220

static int imx_drm_bind(struct device *dev)
{
221 222 223 224 225
	struct drm_device *drm;
	struct imx_drm_device *imxdrm;
	int ret;

	drm = drm_dev_alloc(&imx_drm_driver, dev);
226 227
	if (IS_ERR(drm))
		return PTR_ERR(drm);
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253

	imxdrm = devm_kzalloc(dev, sizeof(*imxdrm), GFP_KERNEL);
	if (!imxdrm) {
		ret = -ENOMEM;
		goto err_unref;
	}

	imxdrm->drm = drm;
	drm->dev_private = imxdrm;

	/*
	 * enable drm irq mode.
	 * - with irq_enabled = true, we can use the vblank feature.
	 *
	 * P.S. note that we wouldn't use drm irq handler but
	 *      just specific driver own one instead because
	 *      drm framework supports only one irq handler and
	 *      drivers can well take care of their interrupts
	 */
	drm->irq_enabled = true;

	/*
	 * set max width and height as default value(4096x4096).
	 * this value would be used to check framebuffer size limitation
	 * at drm_mode_addfb().
	 */
254 255
	drm->mode_config.min_width = 1;
	drm->mode_config.min_height = 1;
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285
	drm->mode_config.max_width = 4096;
	drm->mode_config.max_height = 4096;
	drm->mode_config.funcs = &imx_drm_mode_config_funcs;
	drm->mode_config.helper_private = &imx_drm_mode_config_helpers;

	drm_mode_config_init(drm);

	ret = drm_vblank_init(drm, MAX_CRTC);
	if (ret)
		goto err_kms;

	dev_set_drvdata(dev, drm);

	/* Now try and bind all our sub-components */
	ret = component_bind_all(dev, drm);
	if (ret)
		goto err_vblank;

	drm_mode_config_reset(drm);

	/*
	 * All components are now initialised, so setup the fb helper.
	 * The fb helper takes copies of key hardware information, so the
	 * crtcs/connectors/encoders must not change after this point.
	 */
#if IS_ENABLED(CONFIG_DRM_FBDEV_EMULATION)
	if (legacyfb_depth != 16 && legacyfb_depth != 32) {
		dev_warn(dev, "Invalid legacyfb_depth.  Defaulting to 16bpp\n");
		legacyfb_depth = 16;
	}
286
	imxdrm->fbhelper = drm_fbdev_cma_init(drm, legacyfb_depth, MAX_CRTC);
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
	if (IS_ERR(imxdrm->fbhelper)) {
		ret = PTR_ERR(imxdrm->fbhelper);
		imxdrm->fbhelper = NULL;
		goto err_unbind;
	}
#endif

	drm_kms_helper_poll_init(drm);

	ret = drm_dev_register(drm, 0);
	if (ret)
		goto err_fbhelper;

	return 0;

err_fbhelper:
	drm_kms_helper_poll_fini(drm);
A
Arnd Bergmann 已提交
304
#if IS_ENABLED(CONFIG_DRM_FBDEV_EMULATION)
305 306 307
	if (imxdrm->fbhelper)
		drm_fbdev_cma_fini(imxdrm->fbhelper);
err_unbind:
A
Arnd Bergmann 已提交
308
#endif
309 310 311 312 313 314 315 316 317
	component_unbind_all(drm->dev, drm);
err_vblank:
	drm_vblank_cleanup(drm);
err_kms:
	drm_mode_config_cleanup(drm);
err_unref:
	drm_dev_unref(drm);

	return ret;
318 319 320 321
}

static void imx_drm_unbind(struct device *dev)
{
322 323 324 325 326 327 328 329 330 331
	struct drm_device *drm = dev_get_drvdata(dev);
	struct imx_drm_device *imxdrm = drm->dev_private;

	drm_dev_unregister(drm);

	drm_kms_helper_poll_fini(drm);

	if (imxdrm->fbhelper)
		drm_fbdev_cma_fini(imxdrm->fbhelper);

332 333
	drm_mode_config_cleanup(drm);

334 335 336 337
	component_unbind_all(drm->dev, drm);
	dev_set_drvdata(dev, NULL);

	drm_dev_unref(drm);
338 339 340 341 342 343 344
}

static const struct component_master_ops imx_drm_ops = {
	.bind = imx_drm_bind,
	.unbind = imx_drm_unbind,
};

345 346
static int imx_drm_platform_probe(struct platform_device *pdev)
{
347
	int ret = drm_of_component_probe(&pdev->dev, compare_of, &imx_drm_ops);
348

349 350
	if (!ret)
		ret = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32));
351

352
	return ret;
353 354 355 356
}

static int imx_drm_platform_remove(struct platform_device *pdev)
{
357
	component_master_del(&pdev->dev, &imx_drm_ops);
358 359 360
	return 0;
}

361 362 363 364
#ifdef CONFIG_PM_SLEEP
static int imx_drm_suspend(struct device *dev)
{
	struct drm_device *drm_dev = dev_get_drvdata(dev);
365
	struct imx_drm_device *imxdrm;
366 367 368 369 370 371 372

	/* The drm_dev is NULL before .load hook is called */
	if (drm_dev == NULL)
		return 0;

	drm_kms_helper_poll_disable(drm_dev);

373 374 375 376 377 378 379
	imxdrm = drm_dev->dev_private;
	imxdrm->state = drm_atomic_helper_suspend(drm_dev);
	if (IS_ERR(imxdrm->state)) {
		drm_kms_helper_poll_enable(drm_dev);
		return PTR_ERR(imxdrm->state);
	}

380 381 382 383 384 385
	return 0;
}

static int imx_drm_resume(struct device *dev)
{
	struct drm_device *drm_dev = dev_get_drvdata(dev);
386
	struct imx_drm_device *imx_drm;
387 388 389 390

	if (drm_dev == NULL)
		return 0;

391 392
	imx_drm = drm_dev->dev_private;
	drm_atomic_helper_resume(drm_dev, imx_drm->state);
393 394 395 396 397 398 399 400
	drm_kms_helper_poll_enable(drm_dev);

	return 0;
}
#endif

static SIMPLE_DEV_PM_OPS(imx_drm_pm_ops, imx_drm_suspend, imx_drm_resume);

401
static const struct of_device_id imx_drm_dt_ids[] = {
402
	{ .compatible = "fsl,imx-display-subsystem", },
403 404 405 406
	{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, imx_drm_dt_ids);

407 408
static struct platform_driver imx_drm_pdrv = {
	.probe		= imx_drm_platform_probe,
409
	.remove		= imx_drm_platform_remove,
410 411
	.driver		= {
		.name	= "imx-drm",
412
		.pm	= &imx_drm_pm_ops,
413
		.of_match_table = imx_drm_dt_ids,
414 415
	},
};
416
module_platform_driver(imx_drm_pdrv);
417 418 419 420

MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
MODULE_DESCRIPTION("i.MX drm driver core");
MODULE_LICENSE("GPL");