exynos_drm_fimd.c 31.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13
/* exynos_drm_fimd.c
 *
 * Copyright (C) 2011 Samsung Electronics Co.Ltd
 * Authors:
 *	Joonyoung Shim <jy0922.shim@samsung.com>
 *	Inki Dae <inki.dae@samsung.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.
 *
 */
14
#include <drm/drmP.h>
15 16 17 18

#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
19
#include <linux/of.h>
20
#include <linux/of_device.h>
21
#include <linux/pm_runtime.h>
22
#include <linux/component.h>
23 24
#include <linux/mfd/syscon.h>
#include <linux/regmap.h>
25

26
#include <video/of_display_timing.h>
27
#include <video/of_videomode.h>
28
#include <video/samsung_fimd.h>
29 30 31 32 33
#include <drm/exynos_drm.h>

#include "exynos_drm_drv.h"
#include "exynos_drm_fbdev.h"
#include "exynos_drm_crtc.h"
34
#include "exynos_drm_iommu.h"
35 36

/*
37
 * FIMD stands for Fully Interactive Mobile Display and
38 39 40 41 42
 * as a display controller, it transfers contents drawn on memory
 * to a LCD Panel through Display Interfaces such as RGB or
 * CPU Interface.
 */

43
#define FIMD_DEFAULT_FRAMERATE 60
44
#define MIN_FB_WIDTH_FOR_16WORD_BURST 128
45

46 47 48
/* position control register for hardware window 0, 2 ~ 4.*/
#define VIDOSD_A(win)		(VIDOSD_BASE + 0x00 + (win) * 16)
#define VIDOSD_B(win)		(VIDOSD_BASE + 0x04 + (win) * 16)
49 50 51 52 53 54
/*
 * size control register for hardware windows 0 and alpha control register
 * for hardware windows 1 ~ 4
 */
#define VIDOSD_C(win)		(VIDOSD_BASE + 0x08 + (win) * 16)
/* size control register for hardware windows 1 ~ 2. */
55 56 57 58 59 60 61
#define VIDOSD_D(win)		(VIDOSD_BASE + 0x0C + (win) * 16)

#define VIDWx_BUF_START(win, buf)	(VIDW_BUF_START(buf) + (win) * 8)
#define VIDWx_BUF_END(win, buf)		(VIDW_BUF_END(buf) + (win) * 8)
#define VIDWx_BUF_SIZE(win, buf)	(VIDW_BUF_SIZE(buf) + (win) * 4)

/* color key control register for hardware window 1 ~ 4. */
62
#define WKEYCON0_BASE(x)		((WKEYCON0 + 0x140) + ((x - 1) * 8))
63
/* color key value register for hardware window 1 ~ 4. */
64
#define WKEYCON1_BASE(x)		((WKEYCON1 + 0x140) + ((x - 1) * 8))
65

66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
/* I80 / RGB trigger control register */
#define TRIGCON				0x1A4
#define TRGMODE_I80_RGB_ENABLE_I80	(1 << 0)
#define SWTRGCMD_I80_RGB_ENABLE		(1 << 1)

/* display mode change control register except exynos4 */
#define VIDOUT_CON			0x000
#define VIDOUT_CON_F_I80_LDI0		(0x2 << 8)

/* I80 interface control for main LDI register */
#define I80IFCONFAx(x)			(0x1B0 + (x) * 4)
#define I80IFCONFBx(x)			(0x1B8 + (x) * 4)
#define LCD_CS_SETUP(x)			((x) << 16)
#define LCD_WR_SETUP(x)			((x) << 12)
#define LCD_WR_ACTIVE(x)		((x) << 8)
#define LCD_WR_HOLD(x)			((x) << 4)
#define I80IFEN_ENABLE			(1 << 0)

84 85 86
/* FIMD has totally five hardware windows. */
#define WINDOWS_NR	5

87 88
struct fimd_driver_data {
	unsigned int timing_base;
89 90 91
	unsigned int lcdblk_offset;
	unsigned int lcdblk_vt_shift;
	unsigned int lcdblk_bypass_shift;
92 93

	unsigned int has_shadowcon:1;
94
	unsigned int has_clksel:1;
95
	unsigned int has_limited_fmt:1;
96
	unsigned int has_vidoutcon:1;
J
Joonyoung Shim 已提交
97
	unsigned int has_vtsel:1;
98 99
};

100 101 102
static struct fimd_driver_data s3c64xx_fimd_driver_data = {
	.timing_base = 0x0,
	.has_clksel = 1,
103
	.has_limited_fmt = 1,
104 105
};

106 107 108 109 110 111 112 113
static struct fimd_driver_data exynos3_fimd_driver_data = {
	.timing_base = 0x20000,
	.lcdblk_offset = 0x210,
	.lcdblk_bypass_shift = 1,
	.has_shadowcon = 1,
	.has_vidoutcon = 1,
};

114
static struct fimd_driver_data exynos4_fimd_driver_data = {
115
	.timing_base = 0x0,
116 117 118
	.lcdblk_offset = 0x210,
	.lcdblk_vt_shift = 10,
	.lcdblk_bypass_shift = 1,
119
	.has_shadowcon = 1,
J
Joonyoung Shim 已提交
120
	.has_vtsel = 1,
121 122
};

123 124 125 126 127 128 129
static struct fimd_driver_data exynos4415_fimd_driver_data = {
	.timing_base = 0x20000,
	.lcdblk_offset = 0x210,
	.lcdblk_vt_shift = 10,
	.lcdblk_bypass_shift = 1,
	.has_shadowcon = 1,
	.has_vidoutcon = 1,
J
Joonyoung Shim 已提交
130
	.has_vtsel = 1,
131 132
};

133
static struct fimd_driver_data exynos5_fimd_driver_data = {
134
	.timing_base = 0x20000,
135 136 137
	.lcdblk_offset = 0x214,
	.lcdblk_vt_shift = 24,
	.lcdblk_bypass_shift = 15,
138
	.has_shadowcon = 1,
139
	.has_vidoutcon = 1,
J
Joonyoung Shim 已提交
140
	.has_vtsel = 1,
141 142
};

143 144 145
struct fimd_win_data {
	unsigned int		offset_x;
	unsigned int		offset_y;
146 147 148 149
	unsigned int		ovl_width;
	unsigned int		ovl_height;
	unsigned int		fb_width;
	unsigned int		fb_height;
150
	unsigned int		bpp;
151
	unsigned int		pixel_format;
I
Inki Dae 已提交
152
	dma_addr_t		dma_addr;
153 154
	unsigned int		buf_offsize;
	unsigned int		line_size;	/* bytes */
155
	bool			enabled;
156
	bool			resume;
157 158 159
};

struct fimd_context {
160
	struct exynos_drm_manager	manager;
161
	struct device			*dev;
162
	struct drm_device		*drm_dev;
163 164 165
	struct clk			*bus_clk;
	struct clk			*lcd_clk;
	void __iomem			*regs;
166
	struct regmap			*sysreg;
167
	struct drm_display_mode		mode;
168 169 170
	struct fimd_win_data		win_data[WINDOWS_NR];
	unsigned int			default_win;
	unsigned long			irq_flags;
171
	u32				vidcon0;
172
	u32				vidcon1;
173 174 175
	u32				vidout_con;
	u32				i80ifcon;
	bool				i80_if;
176
	bool				suspended;
177
	int				pipe;
178 179
	wait_queue_head_t		wait_vsync_queue;
	atomic_t			wait_vsync_event;
180 181
	atomic_t			win_updated;
	atomic_t			triggering;
182

183
	struct exynos_drm_panel_info panel;
184
	struct fimd_driver_data *driver_data;
185
	struct exynos_drm_display *display;
186 187
};

188
static const struct of_device_id fimd_driver_dt_match[] = {
189 190
	{ .compatible = "samsung,s3c6400-fimd",
	  .data = &s3c64xx_fimd_driver_data },
191 192
	{ .compatible = "samsung,exynos3250-fimd",
	  .data = &exynos3_fimd_driver_data },
193
	{ .compatible = "samsung,exynos4210-fimd",
194
	  .data = &exynos4_fimd_driver_data },
195 196
	{ .compatible = "samsung,exynos4415-fimd",
	  .data = &exynos4415_fimd_driver_data },
197
	{ .compatible = "samsung,exynos5250-fimd",
198 199 200
	  .data = &exynos5_fimd_driver_data },
	{},
};
201
MODULE_DEVICE_TABLE(of, fimd_driver_dt_match);
202

203 204 205
static inline struct fimd_driver_data *drm_fimd_get_driver_data(
	struct platform_device *pdev)
{
206 207 208
	const struct of_device_id *of_id =
			of_match_device(fimd_driver_dt_match, &pdev->dev);

209
	return (struct fimd_driver_data *)of_id->data;
210 211
}

212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
static void fimd_wait_for_vblank(struct exynos_drm_manager *mgr)
{
	struct fimd_context *ctx = mgr->ctx;

	if (ctx->suspended)
		return;

	atomic_set(&ctx->wait_vsync_event, 1);

	/*
	 * wait for FIMD to signal VSYNC interrupt or return after
	 * timeout which is set to 50ms (refresh rate of 20).
	 */
	if (!wait_event_timeout(ctx->wait_vsync_queue,
				!atomic_read(&ctx->wait_vsync_event),
				HZ/20))
		DRM_DEBUG_KMS("vblank wait timed out.\n");
}

231 232 233 234 235 236 237 238 239 240 241 242 243
static void fimd_enable_video_output(struct fimd_context *ctx, int win,
					bool enable)
{
	u32 val = readl(ctx->regs + WINCON(win));

	if (enable)
		val |= WINCONx_ENWIN;
	else
		val &= ~WINCONx_ENWIN;

	writel(val, ctx->regs + WINCON(win));
}

244 245 246 247 248 249 250 251 252 253 254 255 256
static void fimd_enable_shadow_channel_path(struct fimd_context *ctx, int win,
						bool enable)
{
	u32 val = readl(ctx->regs + SHADOWCON);

	if (enable)
		val |= SHADOWCON_CHx_ENABLE(win);
	else
		val &= ~SHADOWCON_CHx_ENABLE(win);

	writel(val, ctx->regs + SHADOWCON);
}

257 258 259 260 261 262 263 264 265
static void fimd_clear_channel(struct exynos_drm_manager *mgr)
{
	struct fimd_context *ctx = mgr->ctx;
	int win, ch_enabled = 0;

	DRM_DEBUG_KMS("%s\n", __FILE__);

	/* Check if any channel is enabled. */
	for (win = 0; win < WINDOWS_NR; win++) {
266 267 268
		u32 val = readl(ctx->regs + WINCON(win));

		if (val & WINCONx_ENWIN) {
269
			fimd_enable_video_output(ctx, win, false);
270

271 272 273 274
			if (ctx->driver_data->has_shadowcon)
				fimd_enable_shadow_channel_path(ctx, win,
								false);

275 276 277 278 279
			ch_enabled = 1;
		}
	}

	/* Wait for vsync, as disable channel takes effect at next vsync */
280 281 282 283
	if (ch_enabled) {
		unsigned int state = ctx->suspended;

		ctx->suspended = 0;
284
		fimd_wait_for_vblank(mgr);
285 286
		ctx->suspended = state;
	}
287 288
}

289
static int fimd_mgr_initialize(struct exynos_drm_manager *mgr,
290
			struct drm_device *drm_dev)
291
{
292
	struct fimd_context *ctx = mgr->ctx;
293 294
	struct exynos_drm_private *priv;
	priv = drm_dev->dev_private;
295

296 297
	mgr->drm_dev = ctx->drm_dev = drm_dev;
	mgr->pipe = ctx->pipe = priv->pipe++;
298

299
	/* attach this sub driver to iommu mapping if supported. */
300 301 302 303 304 305
	if (is_drm_iommu_supported(ctx->drm_dev)) {
		/*
		 * If any channel is already active, iommu will throw
		 * a PAGE FAULT when enabled. So clear any channel if enabled.
		 */
		fimd_clear_channel(mgr);
306
		drm_iommu_attach_device(ctx->drm_dev, ctx->dev);
307
	}
308

309
	return 0;
310 311
}

312
static void fimd_mgr_remove(struct exynos_drm_manager *mgr)
313
{
314
	struct fimd_context *ctx = mgr->ctx;
315

316 317 318
	/* detach this sub driver from iommu mapping if supported. */
	if (is_drm_iommu_supported(ctx->drm_dev))
		drm_iommu_detach_device(ctx->drm_dev, ctx->dev);
319 320
}

321 322 323 324 325 326
static u32 fimd_calc_clkdiv(struct fimd_context *ctx,
		const struct drm_display_mode *mode)
{
	unsigned long ideal_clk = mode->htotal * mode->vtotal * mode->vrefresh;
	u32 clkdiv;

327 328 329 330 331 332 333 334
	if (ctx->i80_if) {
		/*
		 * The frame done interrupt should be occurred prior to the
		 * next TE signal.
		 */
		ideal_clk *= 2;
	}

335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358
	/* Find the clock divider value that gets us closest to ideal_clk */
	clkdiv = DIV_ROUND_UP(clk_get_rate(ctx->lcd_clk), ideal_clk);

	return (clkdiv < 0x100) ? clkdiv : 0xff;
}

static bool fimd_mode_fixup(struct exynos_drm_manager *mgr,
		const struct drm_display_mode *mode,
		struct drm_display_mode *adjusted_mode)
{
	if (adjusted_mode->vrefresh == 0)
		adjusted_mode->vrefresh = FIMD_DEFAULT_FRAMERATE;

	return true;
}

static void fimd_mode_set(struct exynos_drm_manager *mgr,
		const struct drm_display_mode *in_mode)
{
	struct fimd_context *ctx = mgr->ctx;

	drm_mode_copy(&ctx->mode, in_mode);
}

359
static void fimd_commit(struct exynos_drm_manager *mgr)
360
{
361
	struct fimd_context *ctx = mgr->ctx;
362
	struct drm_display_mode *mode = &ctx->mode;
363 364 365
	struct fimd_driver_data *driver_data = ctx->driver_data;
	void *timing_base = ctx->regs + driver_data->timing_base;
	u32 val, clkdiv;
366

I
Inki Dae 已提交
367 368 369
	if (ctx->suspended)
		return;

370 371 372 373
	/* nothing to do if we haven't set the mode yet */
	if (mode->htotal == 0 || mode->vtotal == 0)
		return;

374 375 376 377 378 379 380 381
	if (ctx->i80_if) {
		val = ctx->i80ifcon | I80IFEN_ENABLE;
		writel(val, timing_base + I80IFCONFAx(0));

		/* disable auto frame rate */
		writel(0, timing_base + I80IFCONFBx(0));

		/* set video type selection to I80 interface */
J
Joonyoung Shim 已提交
382 383
		if (driver_data->has_vtsel && ctx->sysreg &&
				regmap_update_bits(ctx->sysreg,
384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433
					driver_data->lcdblk_offset,
					0x3 << driver_data->lcdblk_vt_shift,
					0x1 << driver_data->lcdblk_vt_shift)) {
			DRM_ERROR("Failed to update sysreg for I80 i/f.\n");
			return;
		}
	} else {
		int vsync_len, vbpd, vfpd, hsync_len, hbpd, hfpd;
		u32 vidcon1;

		/* setup polarity values */
		vidcon1 = ctx->vidcon1;
		if (mode->flags & DRM_MODE_FLAG_NVSYNC)
			vidcon1 |= VIDCON1_INV_VSYNC;
		if (mode->flags & DRM_MODE_FLAG_NHSYNC)
			vidcon1 |= VIDCON1_INV_HSYNC;
		writel(vidcon1, ctx->regs + driver_data->timing_base + VIDCON1);

		/* setup vertical timing values. */
		vsync_len = mode->crtc_vsync_end - mode->crtc_vsync_start;
		vbpd = mode->crtc_vtotal - mode->crtc_vsync_end;
		vfpd = mode->crtc_vsync_start - mode->crtc_vdisplay;

		val = VIDTCON0_VBPD(vbpd - 1) |
			VIDTCON0_VFPD(vfpd - 1) |
			VIDTCON0_VSPW(vsync_len - 1);
		writel(val, ctx->regs + driver_data->timing_base + VIDTCON0);

		/* setup horizontal timing values.  */
		hsync_len = mode->crtc_hsync_end - mode->crtc_hsync_start;
		hbpd = mode->crtc_htotal - mode->crtc_hsync_end;
		hfpd = mode->crtc_hsync_start - mode->crtc_hdisplay;

		val = VIDTCON1_HBPD(hbpd - 1) |
			VIDTCON1_HFPD(hfpd - 1) |
			VIDTCON1_HSPW(hsync_len - 1);
		writel(val, ctx->regs + driver_data->timing_base + VIDTCON1);
	}

	if (driver_data->has_vidoutcon)
		writel(ctx->vidout_con, timing_base + VIDOUT_CON);

	/* set bypass selection */
	if (ctx->sysreg && regmap_update_bits(ctx->sysreg,
				driver_data->lcdblk_offset,
				0x1 << driver_data->lcdblk_bypass_shift,
				0x1 << driver_data->lcdblk_bypass_shift)) {
		DRM_ERROR("Failed to update sysreg for bypass setting.\n");
		return;
	}
434 435

	/* setup horizontal and vertical display size. */
436 437 438 439
	val = VIDTCON2_LINEVAL(mode->vdisplay - 1) |
	       VIDTCON2_HOZVAL(mode->hdisplay - 1) |
	       VIDTCON2_LINEVAL_E(mode->vdisplay - 1) |
	       VIDTCON2_HOZVAL_E(mode->hdisplay - 1);
440
	writel(val, ctx->regs + driver_data->timing_base + VIDTCON2);
441

442 443 444 445
	/*
	 * fields of register with prefix '_F' would be updated
	 * at vsync(same as dma start)
	 */
446 447
	val = ctx->vidcon0;
	val |= VIDCON0_ENVID | VIDCON0_ENVID_F;
448

449
	if (ctx->driver_data->has_clksel)
450 451
		val |= VIDCON0_CLKSEL_LCD;

452 453 454
	clkdiv = fimd_calc_clkdiv(ctx, mode);
	if (clkdiv > 1)
		val |= VIDCON0_CLKVAL_F(clkdiv - 1) | VIDCON0_CLKDIR;
455 456 457 458

	writel(val, ctx->regs + VIDCON0);
}

459
static int fimd_enable_vblank(struct exynos_drm_manager *mgr)
460
{
461
	struct fimd_context *ctx = mgr->ctx;
462 463
	u32 val;

464 465 466
	if (ctx->suspended)
		return -EPERM;

467 468 469 470 471
	if (!test_and_set_bit(0, &ctx->irq_flags)) {
		val = readl(ctx->regs + VIDINTCON0);

		val |= VIDINTCON0_INT_ENABLE;

472 473 474 475 476 477 478 479 480 481 482 483
		if (ctx->i80_if) {
			val |= VIDINTCON0_INT_I80IFDONE;
			val |= VIDINTCON0_INT_SYSMAINCON;
			val &= ~VIDINTCON0_INT_SYSSUBCON;
		} else {
			val |= VIDINTCON0_INT_FRAME;

			val &= ~VIDINTCON0_FRAMESEL0_MASK;
			val |= VIDINTCON0_FRAMESEL0_VSYNC;
			val &= ~VIDINTCON0_FRAMESEL1_MASK;
			val |= VIDINTCON0_FRAMESEL1_NONE;
		}
484 485 486 487 488 489 490

		writel(val, ctx->regs + VIDINTCON0);
	}

	return 0;
}

491
static void fimd_disable_vblank(struct exynos_drm_manager *mgr)
492
{
493
	struct fimd_context *ctx = mgr->ctx;
494 495
	u32 val;

496 497 498
	if (ctx->suspended)
		return;

499 500 501 502 503
	if (test_and_clear_bit(0, &ctx->irq_flags)) {
		val = readl(ctx->regs + VIDINTCON0);

		val &= ~VIDINTCON0_INT_ENABLE;

504 505 506 507 508 509 510
		if (ctx->i80_if) {
			val &= ~VIDINTCON0_INT_I80IFDONE;
			val &= ~VIDINTCON0_INT_SYSMAINCON;
			val &= ~VIDINTCON0_INT_SYSSUBCON;
		} else
			val &= ~VIDINTCON0_INT_FRAME;

511 512 513 514
		writel(val, ctx->regs + VIDINTCON0);
	}
}

515 516
static void fimd_win_mode_set(struct exynos_drm_manager *mgr,
			struct exynos_drm_overlay *overlay)
517
{
518
	struct fimd_context *ctx = mgr->ctx;
519
	struct fimd_win_data *win_data;
520
	int win;
521
	unsigned long offset;
522 523

	if (!overlay) {
524
		DRM_ERROR("overlay is NULL\n");
525 526 527
		return;
	}

528 529 530 531
	win = overlay->zpos;
	if (win == DEFAULT_ZPOS)
		win = ctx->default_win;

532
	if (win < 0 || win >= WINDOWS_NR)
533 534
		return;

535 536 537 538 539
	offset = overlay->fb_x * (overlay->bpp >> 3);
	offset += overlay->fb_y * overlay->pitch;

	DRM_DEBUG_KMS("offset = 0x%lx, pitch = %x\n", offset, overlay->pitch);

540
	win_data = &ctx->win_data[win];
541

542 543 544 545 546 547
	win_data->offset_x = overlay->crtc_x;
	win_data->offset_y = overlay->crtc_y;
	win_data->ovl_width = overlay->crtc_width;
	win_data->ovl_height = overlay->crtc_height;
	win_data->fb_width = overlay->fb_width;
	win_data->fb_height = overlay->fb_height;
S
Seung-Woo Kim 已提交
548
	win_data->dma_addr = overlay->dma_addr[0] + offset;
549
	win_data->bpp = overlay->bpp;
550
	win_data->pixel_format = overlay->pixel_format;
551 552 553 554 555 556 557 558
	win_data->buf_offsize = (overlay->fb_width - overlay->crtc_width) *
				(overlay->bpp >> 3);
	win_data->line_size = overlay->crtc_width * (overlay->bpp >> 3);

	DRM_DEBUG_KMS("offset_x = %d, offset_y = %d\n",
			win_data->offset_x, win_data->offset_y);
	DRM_DEBUG_KMS("ovl_width = %d, ovl_height = %d\n",
			win_data->ovl_width, win_data->ovl_height);
559
	DRM_DEBUG_KMS("paddr = 0x%lx\n", (unsigned long)win_data->dma_addr);
560 561
	DRM_DEBUG_KMS("fb_width = %d, crtc_width = %d\n",
			overlay->fb_width, overlay->crtc_width);
562 563
}

564
static void fimd_win_set_pixfmt(struct fimd_context *ctx, unsigned int win)
565 566 567 568 569 570
{
	struct fimd_win_data *win_data = &ctx->win_data[win];
	unsigned long val;

	val = WINCONx_ENWIN;

571 572 573 574 575 576 577 578 579
	/*
	 * In case of s3c64xx, window 0 doesn't support alpha channel.
	 * So the request format is ARGB8888 then change it to XRGB8888.
	 */
	if (ctx->driver_data->has_limited_fmt && !win) {
		if (win_data->pixel_format == DRM_FORMAT_ARGB8888)
			win_data->pixel_format = DRM_FORMAT_XRGB8888;
	}

580 581
	switch (win_data->pixel_format) {
	case DRM_FORMAT_C8:
582 583 584 585
		val |= WINCON0_BPPMODE_8BPP_PALETTE;
		val |= WINCONx_BURSTLEN_8WORD;
		val |= WINCONx_BYTSWP;
		break;
586 587 588 589 590 591
	case DRM_FORMAT_XRGB1555:
		val |= WINCON0_BPPMODE_16BPP_1555;
		val |= WINCONx_HAWSWP;
		val |= WINCONx_BURSTLEN_16WORD;
		break;
	case DRM_FORMAT_RGB565:
592 593 594 595
		val |= WINCON0_BPPMODE_16BPP_565;
		val |= WINCONx_HAWSWP;
		val |= WINCONx_BURSTLEN_16WORD;
		break;
596
	case DRM_FORMAT_XRGB8888:
597 598 599 600
		val |= WINCON0_BPPMODE_24BPP_888;
		val |= WINCONx_WSWP;
		val |= WINCONx_BURSTLEN_16WORD;
		break;
601 602
	case DRM_FORMAT_ARGB8888:
		val |= WINCON1_BPPMODE_25BPP_A1888
603 604 605 606 607 608 609 610 611 612 613 614 615 616 617
			| WINCON1_BLD_PIX | WINCON1_ALPHA_SEL;
		val |= WINCONx_WSWP;
		val |= WINCONx_BURSTLEN_16WORD;
		break;
	default:
		DRM_DEBUG_KMS("invalid pixel size so using unpacked 24bpp.\n");

		val |= WINCON0_BPPMODE_24BPP_888;
		val |= WINCONx_WSWP;
		val |= WINCONx_BURSTLEN_16WORD;
		break;
	}

	DRM_DEBUG_KMS("bpp = %d\n", win_data->bpp);

618 619 620 621 622 623 624 625 626 627 628 629 630
	/*
	 * In case of exynos, setting dma-burst to 16Word causes permanent
	 * tearing for very small buffers, e.g. cursor buffer. Burst Mode
	 * switching which is based on overlay size is not recommended as
	 * overlay size varies alot towards the end of the screen and rapid
	 * movement causes unstable DMA which results into iommu crash/tear.
	 */

	if (win_data->fb_width < MIN_FB_WIDTH_FOR_16WORD_BURST) {
		val &= ~WINCONx_BURSTLEN_MASK;
		val |= WINCONx_BURSTLEN_4WORD;
	}

631 632 633
	writel(val, ctx->regs + WINCON(win));
}

634
static void fimd_win_set_colkey(struct fimd_context *ctx, unsigned int win)
635 636 637 638 639 640 641 642 643 644 645 646
{
	unsigned int keycon0 = 0, keycon1 = 0;

	keycon0 = ~(WxKEYCON0_KEYBL_EN | WxKEYCON0_KEYEN_F |
			WxKEYCON0_DIRCON) | WxKEYCON0_COMPKEY(0);

	keycon1 = WxKEYCON1_COLVAL(0xffffffff);

	writel(keycon0, ctx->regs + WKEYCON0_BASE(win));
	writel(keycon1, ctx->regs + WKEYCON1_BASE(win));
}

647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673
/**
 * shadow_protect_win() - disable updating values from shadow registers at vsync
 *
 * @win: window to protect registers for
 * @protect: 1 to protect (disable updates)
 */
static void fimd_shadow_protect_win(struct fimd_context *ctx,
							int win, bool protect)
{
	u32 reg, bits, val;

	if (ctx->driver_data->has_shadowcon) {
		reg = SHADOWCON;
		bits = SHADOWCON_WINx_PROTECT(win);
	} else {
		reg = PRTCON;
		bits = PRTCON_PROTECT;
	}

	val = readl(ctx->regs + reg);
	if (protect)
		val |= bits;
	else
		val &= ~bits;
	writel(val, ctx->regs + reg);
}

674
static void fimd_win_commit(struct exynos_drm_manager *mgr, int zpos)
675
{
676
	struct fimd_context *ctx = mgr->ctx;
677
	struct fimd_win_data *win_data;
678
	int win = zpos;
679
	unsigned long val, alpha, size;
680 681
	unsigned int last_x;
	unsigned int last_y;
682

I
Inki Dae 已提交
683 684 685
	if (ctx->suspended)
		return;

686 687 688
	if (win == DEFAULT_ZPOS)
		win = ctx->default_win;

689
	if (win < 0 || win >= WINDOWS_NR)
690 691 692 693
		return;

	win_data = &ctx->win_data[win];

694 695 696 697 698 699
	/* If suspended, enable this on resume */
	if (ctx->suspended) {
		win_data->resume = true;
		return;
	}

700
	/*
701
	 * SHADOWCON/PRTCON register is used for enabling timing.
702 703 704 705 706 707 708 709 710
	 *
	 * for example, once only width value of a register is set,
	 * if the dma is started then fimd hardware could malfunction so
	 * with protect window setting, the register fields with prefix '_F'
	 * wouldn't be updated at vsync also but updated once unprotect window
	 * is set.
	 */

	/* protect windows */
711
	fimd_shadow_protect_win(ctx, win, true);
712 713

	/* buffer start address */
I
Inki Dae 已提交
714
	val = (unsigned long)win_data->dma_addr;
715 716 717
	writel(val, ctx->regs + VIDWx_BUF_START(win, 0));

	/* buffer end address */
718
	size = win_data->fb_width * win_data->ovl_height * (win_data->bpp >> 3);
I
Inki Dae 已提交
719
	val = (unsigned long)(win_data->dma_addr + size);
720 721 722
	writel(val, ctx->regs + VIDWx_BUF_END(win, 0));

	DRM_DEBUG_KMS("start addr = 0x%lx, end addr = 0x%lx, size = 0x%lx\n",
I
Inki Dae 已提交
723
			(unsigned long)win_data->dma_addr, val, size);
724 725
	DRM_DEBUG_KMS("ovl_width = %d, ovl_height = %d\n",
			win_data->ovl_width, win_data->ovl_height);
726 727 728

	/* buffer size */
	val = VIDW_BUF_SIZE_OFFSET(win_data->buf_offsize) |
729 730 731
		VIDW_BUF_SIZE_PAGEWIDTH(win_data->line_size) |
		VIDW_BUF_SIZE_OFFSET_E(win_data->buf_offsize) |
		VIDW_BUF_SIZE_PAGEWIDTH_E(win_data->line_size);
732 733 734 735
	writel(val, ctx->regs + VIDWx_BUF_SIZE(win, 0));

	/* OSD position */
	val = VIDOSDxA_TOPLEFT_X(win_data->offset_x) |
736 737 738
		VIDOSDxA_TOPLEFT_Y(win_data->offset_y) |
		VIDOSDxA_TOPLEFT_X_E(win_data->offset_x) |
		VIDOSDxA_TOPLEFT_Y_E(win_data->offset_y);
739 740
	writel(val, ctx->regs + VIDOSD_A(win));

741 742 743 744 745 746 747
	last_x = win_data->offset_x + win_data->ovl_width;
	if (last_x)
		last_x--;
	last_y = win_data->offset_y + win_data->ovl_height;
	if (last_y)
		last_y--;

748 749 750
	val = VIDOSDxB_BOTRIGHT_X(last_x) | VIDOSDxB_BOTRIGHT_Y(last_y) |
		VIDOSDxB_BOTRIGHT_X_E(last_x) | VIDOSDxB_BOTRIGHT_Y_E(last_y);

751 752
	writel(val, ctx->regs + VIDOSD_B(win));

753
	DRM_DEBUG_KMS("osd pos: tx = %d, ty = %d, bx = %d, by = %d\n",
754
			win_data->offset_x, win_data->offset_y, last_x, last_y);
755 756 757 758 759 760 761 762 763 764 765 766 767 768 769

	/* hardware window 0 doesn't support alpha channel. */
	if (win != 0) {
		/* OSD alpha */
		alpha = VIDISD14C_ALPHA1_R(0xf) |
			VIDISD14C_ALPHA1_G(0xf) |
			VIDISD14C_ALPHA1_B(0xf);

		writel(alpha, ctx->regs + VIDOSD_C(win));
	}

	/* OSD size */
	if (win != 3 && win != 4) {
		u32 offset = VIDOSD_D(win);
		if (win == 0)
770
			offset = VIDOSD_C(win);
771
		val = win_data->ovl_width * win_data->ovl_height;
772 773 774 775 776
		writel(val, ctx->regs + offset);

		DRM_DEBUG_KMS("osd size = 0x%x\n", (unsigned int)val);
	}

777
	fimd_win_set_pixfmt(ctx, win);
778 779 780

	/* hardware window 0 doesn't support color key. */
	if (win != 0)
781
		fimd_win_set_colkey(ctx, win);
782

783
	fimd_enable_video_output(ctx, win, true);
784

785 786
	if (ctx->driver_data->has_shadowcon)
		fimd_enable_shadow_channel_path(ctx, win, true);
787

788 789 790
	/* Enable DMA channel and unprotect windows */
	fimd_shadow_protect_win(ctx, win, false);

791
	win_data->enabled = true;
792 793 794

	if (ctx->i80_if)
		atomic_set(&ctx->win_updated, 1);
795 796
}

797
static void fimd_win_disable(struct exynos_drm_manager *mgr, int zpos)
798
{
799
	struct fimd_context *ctx = mgr->ctx;
800
	struct fimd_win_data *win_data;
801
	int win = zpos;
802

803 804 805
	if (win == DEFAULT_ZPOS)
		win = ctx->default_win;

806
	if (win < 0 || win >= WINDOWS_NR)
807 808
		return;

809 810
	win_data = &ctx->win_data[win];

811 812 813 814 815 816
	if (ctx->suspended) {
		/* do not resume this window*/
		win_data->resume = false;
		return;
	}

817
	/* protect windows */
818
	fimd_shadow_protect_win(ctx, win, true);
819

820
	fimd_enable_video_output(ctx, win, false);
821

822 823
	if (ctx->driver_data->has_shadowcon)
		fimd_enable_shadow_channel_path(ctx, win, false);
824

825
	/* unprotect windows */
826
	fimd_shadow_protect_win(ctx, win, false);
827 828

	win_data->enabled = false;
829 830
}

831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867
static void fimd_window_suspend(struct exynos_drm_manager *mgr)
{
	struct fimd_context *ctx = mgr->ctx;
	struct fimd_win_data *win_data;
	int i;

	for (i = 0; i < WINDOWS_NR; i++) {
		win_data = &ctx->win_data[i];
		win_data->resume = win_data->enabled;
		if (win_data->enabled)
			fimd_win_disable(mgr, i);
	}
}

static void fimd_window_resume(struct exynos_drm_manager *mgr)
{
	struct fimd_context *ctx = mgr->ctx;
	struct fimd_win_data *win_data;
	int i;

	for (i = 0; i < WINDOWS_NR; i++) {
		win_data = &ctx->win_data[i];
		win_data->enabled = win_data->resume;
		win_data->resume = false;
	}
}

static void fimd_apply(struct exynos_drm_manager *mgr)
{
	struct fimd_context *ctx = mgr->ctx;
	struct fimd_win_data *win_data;
	int i;

	for (i = 0; i < WINDOWS_NR; i++) {
		win_data = &ctx->win_data[i];
		if (win_data->enabled)
			fimd_win_commit(mgr, i);
868 869
		else
			fimd_win_disable(mgr, i);
870 871 872 873 874 875 876 877 878 879 880 881 882 883 884
	}

	fimd_commit(mgr);
}

static int fimd_poweron(struct exynos_drm_manager *mgr)
{
	struct fimd_context *ctx = mgr->ctx;
	int ret;

	if (!ctx->suspended)
		return 0;

	ctx->suspended = false;

885 886
	pm_runtime_get_sync(ctx->dev);

887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939
	ret = clk_prepare_enable(ctx->bus_clk);
	if (ret < 0) {
		DRM_ERROR("Failed to prepare_enable the bus clk [%d]\n", ret);
		goto bus_clk_err;
	}

	ret = clk_prepare_enable(ctx->lcd_clk);
	if  (ret < 0) {
		DRM_ERROR("Failed to prepare_enable the lcd clk [%d]\n", ret);
		goto lcd_clk_err;
	}

	/* if vblank was enabled status, enable it again. */
	if (test_and_clear_bit(0, &ctx->irq_flags)) {
		ret = fimd_enable_vblank(mgr);
		if (ret) {
			DRM_ERROR("Failed to re-enable vblank [%d]\n", ret);
			goto enable_vblank_err;
		}
	}

	fimd_window_resume(mgr);

	fimd_apply(mgr);

	return 0;

enable_vblank_err:
	clk_disable_unprepare(ctx->lcd_clk);
lcd_clk_err:
	clk_disable_unprepare(ctx->bus_clk);
bus_clk_err:
	ctx->suspended = true;
	return ret;
}

static int fimd_poweroff(struct exynos_drm_manager *mgr)
{
	struct fimd_context *ctx = mgr->ctx;

	if (ctx->suspended)
		return 0;

	/*
	 * We need to make sure that all windows are disabled before we
	 * suspend that connector. Otherwise we might try to scan from
	 * a destroyed buffer later.
	 */
	fimd_window_suspend(mgr);

	clk_disable_unprepare(ctx->lcd_clk);
	clk_disable_unprepare(ctx->bus_clk);

940 941
	pm_runtime_put_sync(ctx->dev);

942 943 944 945
	ctx->suspended = true;
	return 0;
}

946 947
static void fimd_dpms(struct exynos_drm_manager *mgr, int mode)
{
948
	DRM_DEBUG_KMS("%s, %d\n", __FILE__, mode);
949 950 951

	switch (mode) {
	case DRM_MODE_DPMS_ON:
952
		fimd_poweron(mgr);
953 954 955 956
		break;
	case DRM_MODE_DPMS_STANDBY:
	case DRM_MODE_DPMS_SUSPEND:
	case DRM_MODE_DPMS_OFF:
957
		fimd_poweroff(mgr);
958 959 960 961 962 963 964
		break;
	default:
		DRM_DEBUG_KMS("unspecified mode %d\n", mode);
		break;
	}
}

965 966
static void fimd_trigger(struct device *dev)
{
967
	struct fimd_context *ctx = dev_get_drvdata(dev);
968 969 970 971
	struct fimd_driver_data *driver_data = ctx->driver_data;
	void *timing_base = ctx->regs + driver_data->timing_base;
	u32 reg;

972
	 /*
973 974 975
	  * Skips triggering if in triggering state, because multiple triggering
	  * requests can cause panel reset.
	  */
976 977 978
	if (atomic_read(&ctx->triggering))
		return;

979
	/* Enters triggering mode */
980 981 982 983 984
	atomic_set(&ctx->triggering, 1);

	reg = readl(timing_base + TRIGCON);
	reg |= (TRGMODE_I80_RGB_ENABLE_I80 | SWTRGCMD_I80_RGB_ENABLE);
	writel(reg, timing_base + TRIGCON);
985 986 987 988 989 990 991

	/*
	 * Exits triggering mode if vblank is not enabled yet, because when the
	 * VIDINTCON0 register is not set, it can not exit from triggering mode.
	 */
	if (!test_bit(0, &ctx->irq_flags))
		atomic_set(&ctx->triggering, 0);
992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013
}

static void fimd_te_handler(struct exynos_drm_manager *mgr)
{
	struct fimd_context *ctx = mgr->ctx;

	/* Checks the crtc is detached already from encoder */
	if (ctx->pipe < 0 || !ctx->drm_dev)
		return;

	/*
	 * If there is a page flip request, triggers and handles the page flip
	 * event so that current fb can be updated into panel GRAM.
	 */
	if (atomic_add_unless(&ctx->win_updated, -1, 0))
		fimd_trigger(ctx->dev);

	/* Wakes up vsync event queue */
	if (atomic_read(&ctx->wait_vsync_event)) {
		atomic_set(&ctx->wait_vsync_event, 0);
		wake_up(&ctx->wait_vsync_queue);
	}
1014

1015
	if (test_bit(0, &ctx->irq_flags))
1016
		drm_handle_vblank(ctx->drm_dev, ctx->pipe);
1017 1018
}

1019 1020
static struct exynos_drm_manager_ops fimd_manager_ops = {
	.dpms = fimd_dpms,
1021 1022
	.mode_fixup = fimd_mode_fixup,
	.mode_set = fimd_mode_set,
1023 1024 1025 1026 1027 1028 1029
	.commit = fimd_commit,
	.enable_vblank = fimd_enable_vblank,
	.disable_vblank = fimd_disable_vblank,
	.wait_for_vblank = fimd_wait_for_vblank,
	.win_mode_set = fimd_win_mode_set,
	.win_commit = fimd_win_commit,
	.win_disable = fimd_win_disable,
1030
	.te_handler = fimd_te_handler,
1031 1032 1033 1034 1035
};

static irqreturn_t fimd_irq_handler(int irq, void *dev_id)
{
	struct fimd_context *ctx = (struct fimd_context *)dev_id;
1036
	u32 val, clear_bit;
1037 1038 1039

	val = readl(ctx->regs + VIDINTCON1);

1040 1041 1042
	clear_bit = ctx->i80_if ? VIDINTCON1_INT_I80 : VIDINTCON1_INT_FRAME;
	if (val & clear_bit)
		writel(clear_bit, ctx->regs + VIDINTCON1);
1043

1044
	/* check the crtc is detached already from encoder */
1045
	if (ctx->pipe < 0 || !ctx->drm_dev)
1046
		goto out;
I
Inki Dae 已提交
1047

1048
	if (ctx->i80_if) {
1049 1050
		exynos_drm_crtc_finish_pageflip(ctx->drm_dev, ctx->pipe);

1051
		/* Exits triggering mode */
1052 1053
		atomic_set(&ctx->triggering, 0);
	} else {
1054 1055 1056
		drm_handle_vblank(ctx->drm_dev, ctx->pipe);
		exynos_drm_crtc_finish_pageflip(ctx->drm_dev, ctx->pipe);

1057 1058 1059 1060 1061
		/* set wait vsync event to zero and wake up queue. */
		if (atomic_read(&ctx->wait_vsync_event)) {
			atomic_set(&ctx->wait_vsync_event, 0);
			wake_up(&ctx->wait_vsync_queue);
		}
1062
	}
1063

1064
out:
1065 1066 1067
	return IRQ_HANDLED;
}

1068
static int fimd_bind(struct device *dev, struct device *master, void *data)
1069
{
1070
	struct fimd_context *ctx = dev_get_drvdata(dev);
1071
	struct drm_device *drm_dev = data;
1072

1073 1074
	fimd_mgr_initialize(&ctx->manager, drm_dev);
	exynos_drm_crtc_create(&ctx->manager);
1075 1076 1077 1078 1079 1080 1081 1082 1083 1084
	if (ctx->display)
		exynos_drm_create_enc_conn(drm_dev, ctx->display);

	return 0;

}

static void fimd_unbind(struct device *dev, struct device *master,
			void *data)
{
1085
	struct fimd_context *ctx = dev_get_drvdata(dev);
1086

1087
	fimd_dpms(&ctx->manager, DRM_MODE_DPMS_OFF);
1088 1089 1090 1091

	if (ctx->display)
		exynos_dpi_remove(dev);

1092
	fimd_mgr_remove(&ctx->manager);
1093 1094 1095 1096 1097 1098 1099 1100 1101 1102
}

static const struct component_ops fimd_component_ops = {
	.bind	= fimd_bind,
	.unbind = fimd_unbind,
};

static int fimd_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
1103
	struct fimd_context *ctx;
1104
	struct device_node *i80_if_timings;
1105 1106
	struct resource *res;
	int ret = -EINVAL;
1107

1108 1109
	if (!dev->of_node)
		return -ENODEV;
1110

1111
	ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
1112 1113 1114 1115 1116 1117 1118 1119 1120 1121
	if (!ctx)
		return -ENOMEM;

	ctx->manager.type = EXYNOS_DISPLAY_TYPE_LCD;
	ctx->manager.ops = &fimd_manager_ops;

	ret = exynos_drm_component_add(dev, EXYNOS_DEVICE_TYPE_CRTC,
				       ctx->manager.type);
	if (ret)
		return ret;
1122

1123
	ctx->dev = dev;
1124
	ctx->suspended = true;
1125
	ctx->driver_data = drm_fimd_get_driver_data(pdev);
1126

1127 1128 1129 1130
	if (of_property_read_bool(dev->of_node, "samsung,invert-vden"))
		ctx->vidcon1 |= VIDCON1_INV_VDEN;
	if (of_property_read_bool(dev->of_node, "samsung,invert-vclk"))
		ctx->vidcon1 |= VIDCON1_INV_VCLK;
1131

1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169
	i80_if_timings = of_get_child_by_name(dev->of_node, "i80-if-timings");
	if (i80_if_timings) {
		u32 val;

		ctx->i80_if = true;

		if (ctx->driver_data->has_vidoutcon)
			ctx->vidout_con |= VIDOUT_CON_F_I80_LDI0;
		else
			ctx->vidcon0 |= VIDCON0_VIDOUT_I80_LDI0;
		/*
		 * The user manual describes that this "DSI_EN" bit is required
		 * to enable I80 24-bit data interface.
		 */
		ctx->vidcon0 |= VIDCON0_DSI_EN;

		if (of_property_read_u32(i80_if_timings, "cs-setup", &val))
			val = 0;
		ctx->i80ifcon = LCD_CS_SETUP(val);
		if (of_property_read_u32(i80_if_timings, "wr-setup", &val))
			val = 0;
		ctx->i80ifcon |= LCD_WR_SETUP(val);
		if (of_property_read_u32(i80_if_timings, "wr-active", &val))
			val = 1;
		ctx->i80ifcon |= LCD_WR_ACTIVE(val);
		if (of_property_read_u32(i80_if_timings, "wr-hold", &val))
			val = 0;
		ctx->i80ifcon |= LCD_WR_HOLD(val);
	}
	of_node_put(i80_if_timings);

	ctx->sysreg = syscon_regmap_lookup_by_phandle(dev->of_node,
							"samsung,sysreg");
	if (IS_ERR(ctx->sysreg)) {
		dev_warn(dev, "failed to get system register.\n");
		ctx->sysreg = NULL;
	}

1170 1171 1172
	ctx->bus_clk = devm_clk_get(dev, "fimd");
	if (IS_ERR(ctx->bus_clk)) {
		dev_err(dev, "failed to get bus clock\n");
1173 1174
		ret = PTR_ERR(ctx->bus_clk);
		goto err_del_component;
1175 1176 1177 1178 1179
	}

	ctx->lcd_clk = devm_clk_get(dev, "sclk_fimd");
	if (IS_ERR(ctx->lcd_clk)) {
		dev_err(dev, "failed to get lcd clock\n");
1180 1181
		ret = PTR_ERR(ctx->lcd_clk);
		goto err_del_component;
1182
	}
1183 1184 1185

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);

1186
	ctx->regs = devm_ioremap_resource(dev, res);
1187 1188 1189 1190
	if (IS_ERR(ctx->regs)) {
		ret = PTR_ERR(ctx->regs);
		goto err_del_component;
	}
1191

1192 1193
	res = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
					   ctx->i80_if ? "lcd_sys" : "vsync");
1194 1195
	if (!res) {
		dev_err(dev, "irq request failed.\n");
1196 1197
		ret = -ENXIO;
		goto err_del_component;
1198 1199
	}

1200
	ret = devm_request_irq(dev, res->start, fimd_irq_handler,
1201 1202
							0, "drm_fimd", ctx);
	if (ret) {
1203
		dev_err(dev, "irq request failed.\n");
1204
		goto err_del_component;
1205 1206
	}

1207
	init_waitqueue_head(&ctx->wait_vsync_queue);
1208
	atomic_set(&ctx->wait_vsync_event, 0);
1209
	ctx->manager.ctx = ctx;
1210

1211
	platform_set_drvdata(pdev, ctx);
1212

1213 1214 1215
	ctx->display = exynos_dpi_probe(dev);
	if (IS_ERR(ctx->display))
		return PTR_ERR(ctx->display);
1216

1217
	pm_runtime_enable(dev);
1218

1219
	ret = component_add(dev, &fimd_component_ops);
1220 1221 1222 1223 1224 1225
	if (ret)
		goto err_disable_pm_runtime;

	return ret;

err_disable_pm_runtime:
1226
	pm_runtime_disable(dev);
1227 1228

err_del_component:
1229
	exynos_drm_component_del(dev, EXYNOS_DEVICE_TYPE_CRTC);
1230
	return ret;
1231
}
1232

1233 1234
static int fimd_remove(struct platform_device *pdev)
{
1235
	pm_runtime_disable(&pdev->dev);
1236

1237 1238 1239
	component_del(&pdev->dev, &fimd_component_ops);
	exynos_drm_component_del(&pdev->dev, EXYNOS_DEVICE_TYPE_CRTC);

1240
	return 0;
I
Inki Dae 已提交
1241 1242
}

1243
struct platform_driver fimd_driver = {
1244
	.probe		= fimd_probe,
1245
	.remove		= fimd_remove,
1246 1247 1248
	.driver		= {
		.name	= "exynos4-fb",
		.owner	= THIS_MODULE,
1249
		.of_match_table = fimd_driver_dt_match,
1250 1251
	},
};