rockchip_drm_drv.c 12.4 KB
Newer Older
Mark Yao's avatar
Mark Yao committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
 * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
 * Author:Mark Yao <mark.yao@rock-chips.com>
 *
 * based on exynos_drm_drv.c
 *
 * This software is licensed under the terms of the GNU General Public
 * License version 2, as published by the Free Software Foundation, and
 * may be copied, distributed, and modified under those terms.
 *
 * 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.
 */

#include <drm/drmP.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_fb_helper.h>
20
#include <drm/drm_gem_cma_helper.h>
21
#include <drm/drm_of.h>
Mark Yao's avatar
Mark Yao committed
22
#include <linux/dma-mapping.h>
23
#include <linux/dma-iommu.h>
Mark Yao's avatar
Mark Yao committed
24
#include <linux/pm_runtime.h>
25
#include <linux/module.h>
Mark Yao's avatar
Mark Yao committed
26
#include <linux/of_graph.h>
27
#include <linux/of_platform.h>
Mark Yao's avatar
Mark Yao committed
28
#include <linux/component.h>
29
#include <linux/console.h>
30
#include <linux/iommu.h>
Mark Yao's avatar
Mark Yao committed
31
32
33
34
35
36
37
38
39
40
41
42

#include "rockchip_drm_drv.h"
#include "rockchip_drm_fb.h"
#include "rockchip_drm_fbdev.h"
#include "rockchip_drm_gem.h"

#define DRIVER_NAME	"rockchip"
#define DRIVER_DESC	"RockChip Soc DRM"
#define DRIVER_DATE	"20140818"
#define DRIVER_MAJOR	1
#define DRIVER_MINOR	0

43
static bool is_support_iommu = true;
44
static struct drm_driver rockchip_drm_driver;
45

Mark Yao's avatar
Mark Yao committed
46
47
48
49
50
51
52
53
/*
 * Attach a (component) device to the shared drm dma mapping from master drm
 * device.  This is used by the VOPs to map GEM buffers to a common DMA
 * mapping.
 */
int rockchip_drm_dma_attach_device(struct drm_device *drm_dev,
				   struct device *dev)
{
54
	struct rockchip_drm_private *private = drm_dev->dev_private;
Mark Yao's avatar
Mark Yao committed
55
56
	int ret;

57
58
59
	if (!is_support_iommu)
		return 0;

60
61
	ret = iommu_attach_device(private->domain, dev);
	if (ret) {
62
		DRM_DEV_ERROR(dev, "Failed to attach iommu device\n");
Mark Yao's avatar
Mark Yao committed
63
		return ret;
64
	}
Mark Yao's avatar
Mark Yao committed
65

66
	return 0;
Mark Yao's avatar
Mark Yao committed
67
68
69
70
71
}

void rockchip_drm_dma_detach_device(struct drm_device *drm_dev,
				    struct device *dev)
{
72
73
74
	struct rockchip_drm_private *private = drm_dev->dev_private;
	struct iommu_domain *domain = private->domain;

75
76
77
	if (!is_support_iommu)
		return;

78
	iommu_detach_device(domain, dev);
Mark Yao's avatar
Mark Yao committed
79
80
}

81
82
83
84
85
86
87
88
89
90
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 rockchip_drm_init_iommu(struct drm_device *drm_dev)
{
	struct rockchip_drm_private *private = drm_dev->dev_private;
	struct iommu_domain_geometry *geometry;
	u64 start, end;

	if (!is_support_iommu)
		return 0;

	private->domain = iommu_domain_alloc(&platform_bus_type);
	if (!private->domain)
		return -ENOMEM;

	geometry = &private->domain->geometry;
	start = geometry->aperture_start;
	end = geometry->aperture_end;

	DRM_DEBUG("IOMMU context initialized (aperture: %#llx-%#llx)\n",
		  start, end);
	drm_mm_init(&private->mm, start, end - start + 1);
	mutex_init(&private->mm_lock);

	return 0;
}

static void rockchip_iommu_cleanup(struct drm_device *drm_dev)
{
	struct rockchip_drm_private *private = drm_dev->dev_private;

	if (!is_support_iommu)
		return;

	drm_mm_takedown(&private->mm);
	iommu_domain_free(private->domain);
Mark Yao's avatar
Mark Yao committed
115
116
}

117
static int rockchip_drm_bind(struct device *dev)
Mark Yao's avatar
Mark Yao committed
118
{
119
	struct drm_device *drm_dev;
Mark Yao's avatar
Mark Yao committed
120
121
122
	struct rockchip_drm_private *private;
	int ret;

123
	drm_dev = drm_dev_alloc(&rockchip_drm_driver, dev);
124
125
	if (IS_ERR(drm_dev))
		return PTR_ERR(drm_dev);
Mark Yao's avatar
Mark Yao committed
126

127
128
129
130
131
	dev_set_drvdata(dev, drm_dev);

	private = devm_kzalloc(drm_dev->dev, sizeof(*private), GFP_KERNEL);
	if (!private) {
		ret = -ENOMEM;
132
		goto err_free;
133
134
	}

Mark Yao's avatar
Mark Yao committed
135
136
	drm_dev->dev_private = private;

137
	INIT_LIST_HEAD(&private->psr_list);
138
	mutex_init(&private->psr_list_lock);
139

140
141
142
143
	ret = rockchip_drm_init_iommu(drm_dev);
	if (ret)
		goto err_free;

Mark Yao's avatar
Mark Yao committed
144
145
146
147
148
149
150
	drm_mode_config_init(drm_dev);

	rockchip_drm_mode_config_init(drm_dev);

	/* Try to bind all sub drivers. */
	ret = component_bind_all(dev, drm_dev);
	if (ret)
151
		goto err_mode_config_cleanup;
Mark Yao's avatar
Mark Yao committed
152

153
154
155
156
157
	ret = drm_vblank_init(drm_dev, drm_dev->mode_config.num_crtc);
	if (ret)
		goto err_unbind_all;

	drm_mode_config_reset(drm_dev);
Mark Yao's avatar
Mark Yao committed
158
159
160
161
162
163
164
165
166

	/*
	 * enable drm irq mode.
	 * - with irq_enabled = true, we can use the vblank feature.
	 */
	drm_dev->irq_enabled = true;

	ret = rockchip_drm_fbdev_init(drm_dev);
	if (ret)
167
168
169
170
		goto err_unbind_all;

	/* init kms poll for handling hpd */
	drm_kms_helper_poll_init(drm_dev);
Mark Yao's avatar
Mark Yao committed
171

172
173
	ret = drm_dev_register(drm_dev, 0);
	if (ret)
174
		goto err_kms_helper_poll_fini;
175

Mark Yao's avatar
Mark Yao committed
176
177
178
	return 0;
err_kms_helper_poll_fini:
	drm_kms_helper_poll_fini(drm_dev);
179
	rockchip_drm_fbdev_fini(drm_dev);
180
err_unbind_all:
Mark Yao's avatar
Mark Yao committed
181
	component_unbind_all(dev, drm_dev);
182
err_mode_config_cleanup:
Mark Yao's avatar
Mark Yao committed
183
	drm_mode_config_cleanup(drm_dev);
184
	rockchip_iommu_cleanup(drm_dev);
185
err_free:
186
187
	drm_dev->dev_private = NULL;
	dev_set_drvdata(dev, NULL);
188
	drm_dev_put(drm_dev);
Mark Yao's avatar
Mark Yao committed
189
190
191
	return ret;
}

192
static void rockchip_drm_unbind(struct device *dev)
Mark Yao's avatar
Mark Yao committed
193
{
194
	struct drm_device *drm_dev = dev_get_drvdata(dev);
Mark Yao's avatar
Mark Yao committed
195

196
197
	drm_dev_unregister(drm_dev);

Mark Yao's avatar
Mark Yao committed
198
199
	rockchip_drm_fbdev_fini(drm_dev);
	drm_kms_helper_poll_fini(drm_dev);
200

201
	drm_atomic_helper_shutdown(drm_dev);
Mark Yao's avatar
Mark Yao committed
202
203
	component_unbind_all(dev, drm_dev);
	drm_mode_config_cleanup(drm_dev);
204
205
	rockchip_iommu_cleanup(drm_dev);

Mark Yao's avatar
Mark Yao committed
206
	drm_dev->dev_private = NULL;
207
	dev_set_drvdata(dev, NULL);
208
	drm_dev_put(drm_dev);
Mark Yao's avatar
Mark Yao committed
209
210
211
212
213
214
215
216
217
218
219
220
221
222
}

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

static struct drm_driver rockchip_drm_driver = {
223
224
	.driver_features	= DRIVER_MODESET | DRIVER_GEM |
				  DRIVER_PRIME | DRIVER_ATOMIC,
225
	.lastclose		= drm_fb_helper_lastclose,
226
	.gem_vm_ops		= &drm_gem_cma_vm_ops,
227
	.gem_free_object_unlocked = rockchip_gem_free_object,
Mark Yao's avatar
Mark Yao committed
228
229
230
231
232
233
	.dumb_create		= rockchip_gem_dumb_create,
	.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	= rockchip_gem_prime_get_sg_table,
234
	.gem_prime_import_sg_table	= rockchip_gem_prime_import_sg_table,
Mark Yao's avatar
Mark Yao committed
235
236
237
238
239
240
241
242
243
244
245
246
	.gem_prime_vmap		= rockchip_gem_prime_vmap,
	.gem_prime_vunmap	= rockchip_gem_prime_vunmap,
	.gem_prime_mmap		= rockchip_gem_mmap_buf,
	.fops			= &rockchip_drm_driver_fops,
	.name	= DRIVER_NAME,
	.desc	= DRIVER_DESC,
	.date	= DRIVER_DATE,
	.major	= DRIVER_MAJOR,
	.minor	= DRIVER_MINOR,
};

#ifdef CONFIG_PM_SLEEP
247
248
249
250
static int rockchip_drm_sys_suspend(struct device *dev)
{
	struct drm_device *drm = dev_get_drvdata(dev);

251
	return drm_mode_config_helper_suspend(drm);
Mark Yao's avatar
Mark Yao committed
252
253
254
255
256
}

static int rockchip_drm_sys_resume(struct device *dev)
{
	struct drm_device *drm = dev_get_drvdata(dev);
257

258
	return drm_mode_config_helper_resume(drm);
Mark Yao's avatar
Mark Yao committed
259
260
261
262
263
264
265
266
}
#endif

static const struct dev_pm_ops rockchip_drm_pm_ops = {
	SET_SYSTEM_SLEEP_PM_OPS(rockchip_drm_sys_suspend,
				rockchip_drm_sys_resume)
};

267
268
269
#define MAX_ROCKCHIP_SUB_DRIVERS 16
static struct platform_driver *rockchip_sub_drivers[MAX_ROCKCHIP_SUB_DRIVERS];
static int num_rockchip_sub_drivers;
Mark Yao's avatar
Mark Yao committed
270

271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
/*
 * Check if a vop endpoint is leading to a rockchip subdriver or bridge.
 * Should be called from the component bind stage of the drivers
 * to ensure that all subdrivers are probed.
 *
 * @ep: endpoint of a rockchip vop
 *
 * returns true if subdriver, false if external bridge and -ENODEV
 * if remote port does not contain a device.
 */
int rockchip_drm_endpoint_is_subdriver(struct device_node *ep)
{
	struct device_node *node = of_graph_get_remote_port_parent(ep);
	struct platform_device *pdev;
	struct device_driver *drv;
	int i;

	if (!node)
		return -ENODEV;

	/* status disabled will prevent creation of platform-devices */
	pdev = of_find_device_by_node(node);
	of_node_put(node);
	if (!pdev)
		return -ENODEV;

	/*
	 * All rockchip subdrivers have probed at this point, so
	 * any device not having a driver now is an external bridge.
	 */
	drv = pdev->dev.driver;
	if (!drv) {
		platform_device_put(pdev);
		return false;
	}

	for (i = 0; i < num_rockchip_sub_drivers; i++) {
		if (rockchip_sub_drivers[i] == to_platform_driver(drv)) {
			platform_device_put(pdev);
			return true;
		}
	}

	platform_device_put(pdev);
	return false;
}

318
319
320
static int compare_dev(struct device *dev, void *data)
{
	return dev == (struct device *)data;
Mark Yao's avatar
Mark Yao committed
321
322
}

323
324
325
326
327
328
329
330
static void rockchip_drm_match_remove(struct device *dev)
{
	struct device_link *link;

	list_for_each_entry(link, &dev->links.consumers, s_node)
		device_link_del(link);
}

331
static struct component_match *rockchip_drm_match_add(struct device *dev)
332
{
333
334
	struct component_match *match = NULL;
	int i;
335

336
337
338
339
340
341
342
343
344
	for (i = 0; i < num_rockchip_sub_drivers; i++) {
		struct platform_driver *drv = rockchip_sub_drivers[i];
		struct device *p = NULL, *d;

		do {
			d = bus_find_device(&platform_bus_type, p, &drv->driver,
					    (void *)platform_bus_type.match);
			put_device(p);
			p = d;
345

346
347
			if (!d)
				break;
348
349

			device_link_add(dev, d, DL_FLAG_STATELESS);
350
351
			component_match_add(dev, &match, compare_dev, d);
		} while (true);
352
	}
353

354
355
356
	if (IS_ERR(match))
		rockchip_drm_match_remove(dev);

357
	return match ?: ERR_PTR(-ENODEV);
358
359
}

Mark Yao's avatar
Mark Yao committed
360
361
362
363
364
static const struct component_master_ops rockchip_drm_ops = {
	.bind = rockchip_drm_bind,
	.unbind = rockchip_drm_unbind,
};

365
static int rockchip_drm_platform_of_probe(struct device *dev)
Mark Yao's avatar
Mark Yao committed
366
{
367
368
	struct device_node *np = dev->of_node;
	struct device_node *port;
369
	bool found = false;
370
	int i;
Mark Yao's avatar
Mark Yao committed
371

372
	if (!np)
Mark Yao's avatar
Mark Yao committed
373
		return -ENODEV;
374

375
	for (i = 0;; i++) {
376
377
		struct device_node *iommu;

378
379
380
381
382
383
384
385
		port = of_parse_phandle(np, "ports", i);
		if (!port)
			break;

		if (!of_device_is_available(port->parent)) {
			of_node_put(port);
			continue;
		}
Mark Yao's avatar
Mark Yao committed
386

387
388
		iommu = of_parse_phandle(port->parent, "iommus", 0);
		if (!iommu || !of_device_is_available(iommu->parent)) {
389
390
391
			DRM_DEV_DEBUG(dev,
				      "no iommu attached for %pOF, using non-iommu buffers\n",
				      port->parent);
392
393
394
395
396
397
398
			/*
			 * if there is a crtc not support iommu, force set all
			 * crtc use non-iommu buffer.
			 */
			is_support_iommu = false;
		}

399
400
		found = true;

401
		of_node_put(iommu);
402
403
404
405
		of_node_put(port);
	}

	if (i == 0) {
406
		DRM_DEV_ERROR(dev, "missing 'ports' property\n");
407
408
409
		return -ENODEV;
	}

410
	if (!found) {
411
412
		DRM_DEV_ERROR(dev,
			      "No available vop found for display-subsystem.\n");
413
414
415
		return -ENODEV;
	}

416
417
	return 0;
}
418

419
420
421
422
423
424
425
426
427
428
429
430
431
static int rockchip_drm_platform_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct component_match *match = NULL;
	int ret;

	ret = rockchip_drm_platform_of_probe(dev);
	if (ret)
		return ret;

	match = rockchip_drm_match_add(dev);
	if (IS_ERR(match))
		return PTR_ERR(match);
432

433
434
435
436
437
438
439
	ret = component_master_add_with_match(dev, &rockchip_drm_ops, match);
	if (ret < 0) {
		rockchip_drm_match_remove(dev);
		return ret;
	}

	return 0;
Mark Yao's avatar
Mark Yao committed
440
441
442
443
444
445
}

static int rockchip_drm_platform_remove(struct platform_device *pdev)
{
	component_master_del(&pdev->dev, &rockchip_drm_ops);

446
447
	rockchip_drm_match_remove(&pdev->dev);

Mark Yao's avatar
Mark Yao committed
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
	return 0;
}

static const struct of_device_id rockchip_drm_dt_ids[] = {
	{ .compatible = "rockchip,display-subsystem", },
	{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, rockchip_drm_dt_ids);

static struct platform_driver rockchip_drm_platform_driver = {
	.probe = rockchip_drm_platform_probe,
	.remove = rockchip_drm_platform_remove,
	.driver = {
		.name = "rockchip-drm",
		.of_match_table = rockchip_drm_dt_ids,
		.pm = &rockchip_drm_pm_ops,
	},
};

467
468
469
470
471
472
473
474
475
476
477
478
#define ADD_ROCKCHIP_SUB_DRIVER(drv, cond) { \
	if (IS_ENABLED(cond) && \
	    !WARN_ON(num_rockchip_sub_drivers >= MAX_ROCKCHIP_SUB_DRIVERS)) \
		rockchip_sub_drivers[num_rockchip_sub_drivers++] = &drv; \
}

static int __init rockchip_drm_init(void)
{
	int ret;

	num_rockchip_sub_drivers = 0;
	ADD_ROCKCHIP_SUB_DRIVER(vop_platform_driver, CONFIG_DRM_ROCKCHIP);
479
480
	ADD_ROCKCHIP_SUB_DRIVER(rockchip_lvds_driver,
				CONFIG_ROCKCHIP_LVDS);
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
	ADD_ROCKCHIP_SUB_DRIVER(rockchip_dp_driver,
				CONFIG_ROCKCHIP_ANALOGIX_DP);
	ADD_ROCKCHIP_SUB_DRIVER(cdn_dp_driver, CONFIG_ROCKCHIP_CDN_DP);
	ADD_ROCKCHIP_SUB_DRIVER(dw_hdmi_rockchip_pltfm_driver,
				CONFIG_ROCKCHIP_DW_HDMI);
	ADD_ROCKCHIP_SUB_DRIVER(dw_mipi_dsi_driver,
				CONFIG_ROCKCHIP_DW_MIPI_DSI);
	ADD_ROCKCHIP_SUB_DRIVER(inno_hdmi_driver, CONFIG_ROCKCHIP_INNO_HDMI);

	ret = platform_register_drivers(rockchip_sub_drivers,
					num_rockchip_sub_drivers);
	if (ret)
		return ret;

	ret = platform_driver_register(&rockchip_drm_platform_driver);
	if (ret)
		goto err_unreg_drivers;

	return 0;

err_unreg_drivers:
	platform_unregister_drivers(rockchip_sub_drivers,
				    num_rockchip_sub_drivers);
	return ret;
}

static void __exit rockchip_drm_fini(void)
{
	platform_driver_unregister(&rockchip_drm_platform_driver);

	platform_unregister_drivers(rockchip_sub_drivers,
				    num_rockchip_sub_drivers);
}

module_init(rockchip_drm_init);
module_exit(rockchip_drm_fini);
Mark Yao's avatar
Mark Yao committed
517
518
519
520

MODULE_AUTHOR("Mark Yao <mark.yao@rock-chips.com>");
MODULE_DESCRIPTION("ROCKCHIP DRM Driver");
MODULE_LICENSE("GPL v2");