]>
Commit | Line | Data |
---|---|---|
1c248b7d ID |
1 | /* exynos_drm_fimd.c |
2 | * | |
3 | * Copyright (C) 2011 Samsung Electronics Co.Ltd | |
4 | * Authors: | |
5 | * Joonyoung Shim <jy0922.shim@samsung.com> | |
6 | * Inki Dae <inki.dae@samsung.com> | |
7 | * | |
8 | * This program is free software; you can redistribute it and/or modify it | |
9 | * under the terms of the GNU General Public License as published by the | |
10 | * Free Software Foundation; either version 2 of the License, or (at your | |
11 | * option) any later version. | |
12 | * | |
13 | */ | |
760285e7 | 14 | #include <drm/drmP.h> |
1c248b7d ID |
15 | |
16 | #include <linux/kernel.h> | |
1c248b7d ID |
17 | #include <linux/platform_device.h> |
18 | #include <linux/clk.h> | |
3f1c781d | 19 | #include <linux/of.h> |
d636ead8 | 20 | #include <linux/of_device.h> |
cb91f6a0 | 21 | #include <linux/pm_runtime.h> |
1c248b7d | 22 | |
7f4596f4 | 23 | #include <video/of_display_timing.h> |
111e6055 | 24 | #include <video/of_videomode.h> |
5a213a55 | 25 | #include <video/samsung_fimd.h> |
1c248b7d | 26 | #include <drm/exynos_drm.h> |
1c248b7d ID |
27 | |
28 | #include "exynos_drm_drv.h" | |
29 | #include "exynos_drm_fbdev.h" | |
30 | #include "exynos_drm_crtc.h" | |
bcc5cd1c | 31 | #include "exynos_drm_iommu.h" |
1c248b7d ID |
32 | |
33 | /* | |
b8654b37 | 34 | * FIMD stands for Fully Interactive Mobile Display and |
1c248b7d ID |
35 | * as a display controller, it transfers contents drawn on memory |
36 | * to a LCD Panel through Display Interfaces such as RGB or | |
37 | * CPU Interface. | |
38 | */ | |
39 | ||
111e6055 AH |
40 | #define FIMD_DEFAULT_FRAMERATE 60 |
41 | ||
1c248b7d ID |
42 | /* position control register for hardware window 0, 2 ~ 4.*/ |
43 | #define VIDOSD_A(win) (VIDOSD_BASE + 0x00 + (win) * 16) | |
44 | #define VIDOSD_B(win) (VIDOSD_BASE + 0x04 + (win) * 16) | |
0f10cf14 LKA |
45 | /* |
46 | * size control register for hardware windows 0 and alpha control register | |
47 | * for hardware windows 1 ~ 4 | |
48 | */ | |
49 | #define VIDOSD_C(win) (VIDOSD_BASE + 0x08 + (win) * 16) | |
50 | /* size control register for hardware windows 1 ~ 2. */ | |
1c248b7d ID |
51 | #define VIDOSD_D(win) (VIDOSD_BASE + 0x0C + (win) * 16) |
52 | ||
53 | #define VIDWx_BUF_START(win, buf) (VIDW_BUF_START(buf) + (win) * 8) | |
54 | #define VIDWx_BUF_END(win, buf) (VIDW_BUF_END(buf) + (win) * 8) | |
55 | #define VIDWx_BUF_SIZE(win, buf) (VIDW_BUF_SIZE(buf) + (win) * 4) | |
56 | ||
57 | /* color key control register for hardware window 1 ~ 4. */ | |
0f10cf14 | 58 | #define WKEYCON0_BASE(x) ((WKEYCON0 + 0x140) + ((x - 1) * 8)) |
1c248b7d | 59 | /* color key value register for hardware window 1 ~ 4. */ |
0f10cf14 | 60 | #define WKEYCON1_BASE(x) ((WKEYCON1 + 0x140) + ((x - 1) * 8)) |
1c248b7d ID |
61 | |
62 | /* FIMD has totally five hardware windows. */ | |
63 | #define WINDOWS_NR 5 | |
64 | ||
bb7704d6 | 65 | #define get_fimd_manager(mgr) platform_get_drvdata(to_platform_device(dev)) |
1c248b7d | 66 | |
e2e13389 LKA |
67 | struct fimd_driver_data { |
68 | unsigned int timing_base; | |
de7af100 TF |
69 | |
70 | unsigned int has_shadowcon:1; | |
411d9ed4 | 71 | unsigned int has_clksel:1; |
5cc4621a | 72 | unsigned int has_limited_fmt:1; |
e2e13389 LKA |
73 | }; |
74 | ||
725ddead TF |
75 | static struct fimd_driver_data s3c64xx_fimd_driver_data = { |
76 | .timing_base = 0x0, | |
77 | .has_clksel = 1, | |
5cc4621a | 78 | .has_limited_fmt = 1, |
725ddead TF |
79 | }; |
80 | ||
6ecf18f9 | 81 | static struct fimd_driver_data exynos4_fimd_driver_data = { |
e2e13389 | 82 | .timing_base = 0x0, |
de7af100 | 83 | .has_shadowcon = 1, |
e2e13389 LKA |
84 | }; |
85 | ||
6ecf18f9 | 86 | static struct fimd_driver_data exynos5_fimd_driver_data = { |
e2e13389 | 87 | .timing_base = 0x20000, |
de7af100 | 88 | .has_shadowcon = 1, |
e2e13389 LKA |
89 | }; |
90 | ||
1c248b7d ID |
91 | struct fimd_win_data { |
92 | unsigned int offset_x; | |
93 | unsigned int offset_y; | |
19c8b834 ID |
94 | unsigned int ovl_width; |
95 | unsigned int ovl_height; | |
96 | unsigned int fb_width; | |
97 | unsigned int fb_height; | |
1c248b7d | 98 | unsigned int bpp; |
a4f38a80 | 99 | unsigned int pixel_format; |
2c871127 | 100 | dma_addr_t dma_addr; |
1c248b7d ID |
101 | unsigned int buf_offsize; |
102 | unsigned int line_size; /* bytes */ | |
ec05da95 | 103 | bool enabled; |
db7e55ae | 104 | bool resume; |
1c248b7d ID |
105 | }; |
106 | ||
107 | struct fimd_context { | |
bb7704d6 | 108 | struct device *dev; |
40c8ab4b | 109 | struct drm_device *drm_dev; |
1c248b7d ID |
110 | int irq; |
111 | struct drm_crtc *crtc; | |
112 | struct clk *bus_clk; | |
113 | struct clk *lcd_clk; | |
1c248b7d | 114 | void __iomem *regs; |
a968e727 | 115 | struct drm_display_mode mode; |
1c248b7d | 116 | struct fimd_win_data win_data[WINDOWS_NR]; |
1c248b7d ID |
117 | unsigned int default_win; |
118 | unsigned long irq_flags; | |
119 | u32 vidcon0; | |
120 | u32 vidcon1; | |
cb91f6a0 | 121 | bool suspended; |
080be03d | 122 | int pipe; |
c32b06ef | 123 | struct mutex lock; |
01ce113c P |
124 | wait_queue_head_t wait_vsync_queue; |
125 | atomic_t wait_vsync_event; | |
1c248b7d | 126 | |
562ad9f4 | 127 | struct exynos_drm_panel_info panel; |
18873465 | 128 | struct fimd_driver_data *driver_data; |
1c248b7d ID |
129 | }; |
130 | ||
d636ead8 | 131 | static const struct of_device_id fimd_driver_dt_match[] = { |
725ddead TF |
132 | { .compatible = "samsung,s3c6400-fimd", |
133 | .data = &s3c64xx_fimd_driver_data }, | |
5830daf8 | 134 | { .compatible = "samsung,exynos4210-fimd", |
d636ead8 | 135 | .data = &exynos4_fimd_driver_data }, |
5830daf8 | 136 | { .compatible = "samsung,exynos5250-fimd", |
d636ead8 JS |
137 | .data = &exynos5_fimd_driver_data }, |
138 | {}, | |
139 | }; | |
d636ead8 | 140 | |
e2e13389 LKA |
141 | static inline struct fimd_driver_data *drm_fimd_get_driver_data( |
142 | struct platform_device *pdev) | |
143 | { | |
d636ead8 JS |
144 | const struct of_device_id *of_id = |
145 | of_match_device(fimd_driver_dt_match, &pdev->dev); | |
146 | ||
2d3f173c | 147 | return (struct fimd_driver_data *)of_id->data; |
e2e13389 LKA |
148 | } |
149 | ||
080be03d | 150 | static bool fimd_display_is_connected(struct exynos_drm_display *display) |
1c248b7d | 151 | { |
1c248b7d ID |
152 | /* TODO. */ |
153 | ||
154 | return true; | |
155 | } | |
156 | ||
080be03d | 157 | static void *fimd_get_panel(struct exynos_drm_display *display) |
1c248b7d | 158 | { |
080be03d | 159 | struct fimd_context *ctx = display->ctx; |
1c248b7d | 160 | |
562ad9f4 | 161 | return &ctx->panel; |
1c248b7d ID |
162 | } |
163 | ||
080be03d SP |
164 | static int fimd_check_mode(struct exynos_drm_display *display, |
165 | struct drm_display_mode *mode) | |
1c248b7d | 166 | { |
1c248b7d ID |
167 | /* TODO. */ |
168 | ||
169 | return 0; | |
170 | } | |
171 | ||
74ccc539 | 172 | static struct exynos_drm_display_ops fimd_display_ops = { |
1c248b7d | 173 | .is_connected = fimd_display_is_connected, |
607c50d4 | 174 | .get_panel = fimd_get_panel, |
16844fb1 | 175 | .check_mode = fimd_check_mode, |
1c248b7d ID |
176 | }; |
177 | ||
080be03d SP |
178 | static struct exynos_drm_display fimd_display = { |
179 | .type = EXYNOS_DISPLAY_TYPE_LCD, | |
180 | .ops = &fimd_display_ops, | |
181 | }; | |
182 | ||
bb7704d6 | 183 | static int fimd_mgr_initialize(struct exynos_drm_manager *mgr, |
080be03d | 184 | struct drm_device *drm_dev, int pipe) |
40c8ab4b | 185 | { |
bb7704d6 | 186 | struct fimd_context *ctx = mgr->ctx; |
40c8ab4b SP |
187 | |
188 | ctx->drm_dev = drm_dev; | |
080be03d | 189 | ctx->pipe = pipe; |
40c8ab4b | 190 | |
080be03d SP |
191 | /* |
192 | * enable drm irq mode. | |
193 | * - with irq_enabled = true, we can use the vblank feature. | |
194 | * | |
195 | * P.S. note that we wouldn't use drm irq handler but | |
196 | * just specific driver own one instead because | |
197 | * drm framework supports only one irq handler. | |
198 | */ | |
199 | drm_dev->irq_enabled = true; | |
ec05da95 | 200 | |
080be03d SP |
201 | /* |
202 | * with vblank_disable_allowed = true, vblank interrupt will be disabled | |
203 | * by drm timer once a current process gives up ownership of | |
204 | * vblank event.(after drm_vblank_put function is called) | |
205 | */ | |
206 | drm_dev->vblank_disable_allowed = true; | |
c32b06ef | 207 | |
080be03d SP |
208 | /* attach this sub driver to iommu mapping if supported. */ |
209 | if (is_drm_iommu_supported(ctx->drm_dev)) | |
210 | drm_iommu_attach_device(ctx->drm_dev, ctx->dev); | |
c32b06ef | 211 | |
080be03d | 212 | return 0; |
ec05da95 ID |
213 | } |
214 | ||
080be03d | 215 | static void fimd_mgr_remove(struct exynos_drm_manager *mgr) |
ec05da95 | 216 | { |
bb7704d6 | 217 | struct fimd_context *ctx = mgr->ctx; |
ec05da95 | 218 | |
080be03d SP |
219 | /* detach this sub driver from iommu mapping if supported. */ |
220 | if (is_drm_iommu_supported(ctx->drm_dev)) | |
221 | drm_iommu_detach_device(ctx->drm_dev, ctx->dev); | |
ec05da95 ID |
222 | } |
223 | ||
a968e727 SP |
224 | static u32 fimd_calc_clkdiv(struct fimd_context *ctx, |
225 | const struct drm_display_mode *mode) | |
226 | { | |
227 | unsigned long ideal_clk = mode->htotal * mode->vtotal * mode->vrefresh; | |
228 | u32 clkdiv; | |
229 | ||
230 | /* Find the clock divider value that gets us closest to ideal_clk */ | |
231 | clkdiv = DIV_ROUND_UP(clk_get_rate(ctx->lcd_clk), ideal_clk); | |
232 | ||
233 | return (clkdiv < 0x100) ? clkdiv : 0xff; | |
234 | } | |
235 | ||
236 | static bool fimd_mode_fixup(struct exynos_drm_manager *mgr, | |
237 | const struct drm_display_mode *mode, | |
238 | struct drm_display_mode *adjusted_mode) | |
239 | { | |
240 | if (adjusted_mode->vrefresh == 0) | |
241 | adjusted_mode->vrefresh = FIMD_DEFAULT_FRAMERATE; | |
242 | ||
243 | return true; | |
244 | } | |
245 | ||
246 | static void fimd_mode_set(struct exynos_drm_manager *mgr, | |
247 | const struct drm_display_mode *in_mode) | |
248 | { | |
249 | struct fimd_context *ctx = mgr->ctx; | |
250 | ||
251 | drm_mode_copy(&ctx->mode, in_mode); | |
252 | } | |
253 | ||
bb7704d6 | 254 | static void fimd_commit(struct exynos_drm_manager *mgr) |
1c248b7d | 255 | { |
bb7704d6 | 256 | struct fimd_context *ctx = mgr->ctx; |
a968e727 | 257 | struct drm_display_mode *mode = &ctx->mode; |
e2e13389 | 258 | struct fimd_driver_data *driver_data; |
a968e727 SP |
259 | u32 val, clkdiv; |
260 | int hblank, vblank, vsync_len, vbpd, vfpd, hsync_len, hbpd, hfpd; | |
1c248b7d | 261 | |
18873465 | 262 | driver_data = ctx->driver_data; |
e30d4bcf ID |
263 | if (ctx->suspended) |
264 | return; | |
265 | ||
a968e727 SP |
266 | /* nothing to do if we haven't set the mode yet */ |
267 | if (mode->htotal == 0 || mode->vtotal == 0) | |
268 | return; | |
269 | ||
1c248b7d | 270 | /* setup polarity values from machine code. */ |
e2e13389 | 271 | writel(ctx->vidcon1, ctx->regs + driver_data->timing_base + VIDCON1); |
1c248b7d ID |
272 | |
273 | /* setup vertical timing values. */ | |
a968e727 SP |
274 | vblank = mode->crtc_vblank_end - mode->crtc_vblank_start; |
275 | vsync_len = mode->crtc_vsync_end - mode->crtc_vsync_start; | |
276 | vbpd = (vblank - vsync_len) / 2; | |
277 | vfpd = vblank - vsync_len - vbpd; | |
278 | ||
279 | val = VIDTCON0_VBPD(vbpd - 1) | | |
280 | VIDTCON0_VFPD(vfpd - 1) | | |
281 | VIDTCON0_VSPW(vsync_len - 1); | |
e2e13389 | 282 | writel(val, ctx->regs + driver_data->timing_base + VIDTCON0); |
1c248b7d ID |
283 | |
284 | /* setup horizontal timing values. */ | |
a968e727 SP |
285 | hblank = mode->crtc_hblank_end - mode->crtc_hblank_start; |
286 | hsync_len = mode->crtc_hsync_end - mode->crtc_hsync_start; | |
287 | hbpd = (hblank - hsync_len) / 2; | |
288 | hfpd = hblank - hsync_len - hbpd; | |
289 | ||
290 | val = VIDTCON1_HBPD(hbpd - 1) | | |
291 | VIDTCON1_HFPD(hfpd - 1) | | |
292 | VIDTCON1_HSPW(hsync_len - 1); | |
e2e13389 | 293 | writel(val, ctx->regs + driver_data->timing_base + VIDTCON1); |
1c248b7d ID |
294 | |
295 | /* setup horizontal and vertical display size. */ | |
a968e727 SP |
296 | val = VIDTCON2_LINEVAL(mode->vdisplay - 1) | |
297 | VIDTCON2_HOZVAL(mode->hdisplay - 1) | | |
298 | VIDTCON2_LINEVAL_E(mode->vdisplay - 1) | | |
299 | VIDTCON2_HOZVAL_E(mode->hdisplay - 1); | |
e2e13389 | 300 | writel(val, ctx->regs + driver_data->timing_base + VIDTCON2); |
1c248b7d ID |
301 | |
302 | /* setup clock source, clock divider, enable dma. */ | |
303 | val = ctx->vidcon0; | |
304 | val &= ~(VIDCON0_CLKVAL_F_MASK | VIDCON0_CLKDIR); | |
305 | ||
411d9ed4 TF |
306 | if (ctx->driver_data->has_clksel) { |
307 | val &= ~VIDCON0_CLKSEL_MASK; | |
308 | val |= VIDCON0_CLKSEL_LCD; | |
309 | } | |
310 | ||
a968e727 SP |
311 | clkdiv = fimd_calc_clkdiv(ctx, mode); |
312 | if (clkdiv > 1) | |
313 | val |= VIDCON0_CLKVAL_F(clkdiv - 1) | VIDCON0_CLKDIR; | |
1c248b7d ID |
314 | else |
315 | val &= ~VIDCON0_CLKDIR; /* 1:1 clock */ | |
316 | ||
317 | /* | |
318 | * fields of register with prefix '_F' would be updated | |
319 | * at vsync(same as dma start) | |
320 | */ | |
321 | val |= VIDCON0_ENVID | VIDCON0_ENVID_F; | |
322 | writel(val, ctx->regs + VIDCON0); | |
323 | } | |
324 | ||
bb7704d6 | 325 | static int fimd_enable_vblank(struct exynos_drm_manager *mgr) |
1c248b7d | 326 | { |
bb7704d6 | 327 | struct fimd_context *ctx = mgr->ctx; |
1c248b7d ID |
328 | u32 val; |
329 | ||
cb91f6a0 JS |
330 | if (ctx->suspended) |
331 | return -EPERM; | |
332 | ||
1c248b7d ID |
333 | if (!test_and_set_bit(0, &ctx->irq_flags)) { |
334 | val = readl(ctx->regs + VIDINTCON0); | |
335 | ||
336 | val |= VIDINTCON0_INT_ENABLE; | |
337 | val |= VIDINTCON0_INT_FRAME; | |
338 | ||
339 | val &= ~VIDINTCON0_FRAMESEL0_MASK; | |
340 | val |= VIDINTCON0_FRAMESEL0_VSYNC; | |
341 | val &= ~VIDINTCON0_FRAMESEL1_MASK; | |
342 | val |= VIDINTCON0_FRAMESEL1_NONE; | |
343 | ||
344 | writel(val, ctx->regs + VIDINTCON0); | |
345 | } | |
346 | ||
347 | return 0; | |
348 | } | |
349 | ||
bb7704d6 | 350 | static void fimd_disable_vblank(struct exynos_drm_manager *mgr) |
1c248b7d | 351 | { |
bb7704d6 | 352 | struct fimd_context *ctx = mgr->ctx; |
1c248b7d ID |
353 | u32 val; |
354 | ||
cb91f6a0 JS |
355 | if (ctx->suspended) |
356 | return; | |
357 | ||
1c248b7d ID |
358 | if (test_and_clear_bit(0, &ctx->irq_flags)) { |
359 | val = readl(ctx->regs + VIDINTCON0); | |
360 | ||
361 | val &= ~VIDINTCON0_INT_FRAME; | |
362 | val &= ~VIDINTCON0_INT_ENABLE; | |
363 | ||
364 | writel(val, ctx->regs + VIDINTCON0); | |
365 | } | |
366 | } | |
367 | ||
bb7704d6 | 368 | static void fimd_wait_for_vblank(struct exynos_drm_manager *mgr) |
07033970 | 369 | { |
bb7704d6 | 370 | struct fimd_context *ctx = mgr->ctx; |
07033970 | 371 | |
01ce113c P |
372 | if (ctx->suspended) |
373 | return; | |
374 | ||
375 | atomic_set(&ctx->wait_vsync_event, 1); | |
376 | ||
377 | /* | |
378 | * wait for FIMD to signal VSYNC interrupt or return after | |
379 | * timeout which is set to 50ms (refresh rate of 20). | |
380 | */ | |
381 | if (!wait_event_timeout(ctx->wait_vsync_queue, | |
382 | !atomic_read(&ctx->wait_vsync_event), | |
8dd9ad5d | 383 | HZ/20)) |
07033970 P |
384 | DRM_DEBUG_KMS("vblank wait timed out.\n"); |
385 | } | |
386 | ||
bb7704d6 SP |
387 | static void fimd_win_mode_set(struct exynos_drm_manager *mgr, |
388 | struct exynos_drm_overlay *overlay) | |
1c248b7d | 389 | { |
bb7704d6 | 390 | struct fimd_context *ctx = mgr->ctx; |
1c248b7d | 391 | struct fimd_win_data *win_data; |
864ee9e6 | 392 | int win; |
19c8b834 | 393 | unsigned long offset; |
1c248b7d | 394 | |
1c248b7d | 395 | if (!overlay) { |
bb7704d6 | 396 | DRM_ERROR("overlay is NULL\n"); |
1c248b7d ID |
397 | return; |
398 | } | |
399 | ||
864ee9e6 JS |
400 | win = overlay->zpos; |
401 | if (win == DEFAULT_ZPOS) | |
402 | win = ctx->default_win; | |
403 | ||
37b006e8 | 404 | if (win < 0 || win >= WINDOWS_NR) |
864ee9e6 JS |
405 | return; |
406 | ||
19c8b834 ID |
407 | offset = overlay->fb_x * (overlay->bpp >> 3); |
408 | offset += overlay->fb_y * overlay->pitch; | |
409 | ||
410 | DRM_DEBUG_KMS("offset = 0x%lx, pitch = %x\n", offset, overlay->pitch); | |
411 | ||
864ee9e6 | 412 | win_data = &ctx->win_data[win]; |
1c248b7d | 413 | |
19c8b834 ID |
414 | win_data->offset_x = overlay->crtc_x; |
415 | win_data->offset_y = overlay->crtc_y; | |
416 | win_data->ovl_width = overlay->crtc_width; | |
417 | win_data->ovl_height = overlay->crtc_height; | |
418 | win_data->fb_width = overlay->fb_width; | |
419 | win_data->fb_height = overlay->fb_height; | |
229d3534 | 420 | win_data->dma_addr = overlay->dma_addr[0] + offset; |
1c248b7d | 421 | win_data->bpp = overlay->bpp; |
a4f38a80 | 422 | win_data->pixel_format = overlay->pixel_format; |
19c8b834 ID |
423 | win_data->buf_offsize = (overlay->fb_width - overlay->crtc_width) * |
424 | (overlay->bpp >> 3); | |
425 | win_data->line_size = overlay->crtc_width * (overlay->bpp >> 3); | |
426 | ||
427 | DRM_DEBUG_KMS("offset_x = %d, offset_y = %d\n", | |
428 | win_data->offset_x, win_data->offset_y); | |
429 | DRM_DEBUG_KMS("ovl_width = %d, ovl_height = %d\n", | |
430 | win_data->ovl_width, win_data->ovl_height); | |
ddd8e959 | 431 | DRM_DEBUG_KMS("paddr = 0x%lx\n", (unsigned long)win_data->dma_addr); |
19c8b834 ID |
432 | DRM_DEBUG_KMS("fb_width = %d, crtc_width = %d\n", |
433 | overlay->fb_width, overlay->crtc_width); | |
1c248b7d ID |
434 | } |
435 | ||
bb7704d6 | 436 | static void fimd_win_set_pixfmt(struct fimd_context *ctx, unsigned int win) |
1c248b7d | 437 | { |
1c248b7d ID |
438 | struct fimd_win_data *win_data = &ctx->win_data[win]; |
439 | unsigned long val; | |
440 | ||
1c248b7d ID |
441 | val = WINCONx_ENWIN; |
442 | ||
5cc4621a ID |
443 | /* |
444 | * In case of s3c64xx, window 0 doesn't support alpha channel. | |
445 | * So the request format is ARGB8888 then change it to XRGB8888. | |
446 | */ | |
447 | if (ctx->driver_data->has_limited_fmt && !win) { | |
448 | if (win_data->pixel_format == DRM_FORMAT_ARGB8888) | |
449 | win_data->pixel_format = DRM_FORMAT_XRGB8888; | |
450 | } | |
451 | ||
a4f38a80 ID |
452 | switch (win_data->pixel_format) { |
453 | case DRM_FORMAT_C8: | |
1c248b7d ID |
454 | val |= WINCON0_BPPMODE_8BPP_PALETTE; |
455 | val |= WINCONx_BURSTLEN_8WORD; | |
456 | val |= WINCONx_BYTSWP; | |
457 | break; | |
a4f38a80 ID |
458 | case DRM_FORMAT_XRGB1555: |
459 | val |= WINCON0_BPPMODE_16BPP_1555; | |
460 | val |= WINCONx_HAWSWP; | |
461 | val |= WINCONx_BURSTLEN_16WORD; | |
462 | break; | |
463 | case DRM_FORMAT_RGB565: | |
1c248b7d ID |
464 | val |= WINCON0_BPPMODE_16BPP_565; |
465 | val |= WINCONx_HAWSWP; | |
466 | val |= WINCONx_BURSTLEN_16WORD; | |
467 | break; | |
a4f38a80 | 468 | case DRM_FORMAT_XRGB8888: |
1c248b7d ID |
469 | val |= WINCON0_BPPMODE_24BPP_888; |
470 | val |= WINCONx_WSWP; | |
471 | val |= WINCONx_BURSTLEN_16WORD; | |
472 | break; | |
a4f38a80 ID |
473 | case DRM_FORMAT_ARGB8888: |
474 | val |= WINCON1_BPPMODE_25BPP_A1888 | |
1c248b7d ID |
475 | | WINCON1_BLD_PIX | WINCON1_ALPHA_SEL; |
476 | val |= WINCONx_WSWP; | |
477 | val |= WINCONx_BURSTLEN_16WORD; | |
478 | break; | |
479 | default: | |
480 | DRM_DEBUG_KMS("invalid pixel size so using unpacked 24bpp.\n"); | |
481 | ||
482 | val |= WINCON0_BPPMODE_24BPP_888; | |
483 | val |= WINCONx_WSWP; | |
484 | val |= WINCONx_BURSTLEN_16WORD; | |
485 | break; | |
486 | } | |
487 | ||
488 | DRM_DEBUG_KMS("bpp = %d\n", win_data->bpp); | |
489 | ||
490 | writel(val, ctx->regs + WINCON(win)); | |
491 | } | |
492 | ||
bb7704d6 | 493 | static void fimd_win_set_colkey(struct fimd_context *ctx, unsigned int win) |
1c248b7d | 494 | { |
1c248b7d ID |
495 | unsigned int keycon0 = 0, keycon1 = 0; |
496 | ||
1c248b7d ID |
497 | keycon0 = ~(WxKEYCON0_KEYBL_EN | WxKEYCON0_KEYEN_F | |
498 | WxKEYCON0_DIRCON) | WxKEYCON0_COMPKEY(0); | |
499 | ||
500 | keycon1 = WxKEYCON1_COLVAL(0xffffffff); | |
501 | ||
502 | writel(keycon0, ctx->regs + WKEYCON0_BASE(win)); | |
503 | writel(keycon1, ctx->regs + WKEYCON1_BASE(win)); | |
504 | } | |
505 | ||
de7af100 TF |
506 | /** |
507 | * shadow_protect_win() - disable updating values from shadow registers at vsync | |
508 | * | |
509 | * @win: window to protect registers for | |
510 | * @protect: 1 to protect (disable updates) | |
511 | */ | |
512 | static void fimd_shadow_protect_win(struct fimd_context *ctx, | |
513 | int win, bool protect) | |
514 | { | |
515 | u32 reg, bits, val; | |
516 | ||
517 | if (ctx->driver_data->has_shadowcon) { | |
518 | reg = SHADOWCON; | |
519 | bits = SHADOWCON_WINx_PROTECT(win); | |
520 | } else { | |
521 | reg = PRTCON; | |
522 | bits = PRTCON_PROTECT; | |
523 | } | |
524 | ||
525 | val = readl(ctx->regs + reg); | |
526 | if (protect) | |
527 | val |= bits; | |
528 | else | |
529 | val &= ~bits; | |
530 | writel(val, ctx->regs + reg); | |
531 | } | |
532 | ||
bb7704d6 | 533 | static void fimd_win_commit(struct exynos_drm_manager *mgr, int zpos) |
1c248b7d | 534 | { |
bb7704d6 | 535 | struct fimd_context *ctx = mgr->ctx; |
1c248b7d | 536 | struct fimd_win_data *win_data; |
864ee9e6 | 537 | int win = zpos; |
1c248b7d | 538 | unsigned long val, alpha, size; |
f56aad3a JS |
539 | unsigned int last_x; |
540 | unsigned int last_y; | |
1c248b7d | 541 | |
e30d4bcf ID |
542 | if (ctx->suspended) |
543 | return; | |
544 | ||
864ee9e6 JS |
545 | if (win == DEFAULT_ZPOS) |
546 | win = ctx->default_win; | |
547 | ||
37b006e8 | 548 | if (win < 0 || win >= WINDOWS_NR) |
1c248b7d ID |
549 | return; |
550 | ||
551 | win_data = &ctx->win_data[win]; | |
552 | ||
553 | /* | |
de7af100 | 554 | * SHADOWCON/PRTCON register is used for enabling timing. |
1c248b7d ID |
555 | * |
556 | * for example, once only width value of a register is set, | |
557 | * if the dma is started then fimd hardware could malfunction so | |
558 | * with protect window setting, the register fields with prefix '_F' | |
559 | * wouldn't be updated at vsync also but updated once unprotect window | |
560 | * is set. | |
561 | */ | |
562 | ||
563 | /* protect windows */ | |
de7af100 | 564 | fimd_shadow_protect_win(ctx, win, true); |
1c248b7d ID |
565 | |
566 | /* buffer start address */ | |
2c871127 | 567 | val = (unsigned long)win_data->dma_addr; |
1c248b7d ID |
568 | writel(val, ctx->regs + VIDWx_BUF_START(win, 0)); |
569 | ||
570 | /* buffer end address */ | |
19c8b834 | 571 | size = win_data->fb_width * win_data->ovl_height * (win_data->bpp >> 3); |
2c871127 | 572 | val = (unsigned long)(win_data->dma_addr + size); |
1c248b7d ID |
573 | writel(val, ctx->regs + VIDWx_BUF_END(win, 0)); |
574 | ||
575 | DRM_DEBUG_KMS("start addr = 0x%lx, end addr = 0x%lx, size = 0x%lx\n", | |
2c871127 | 576 | (unsigned long)win_data->dma_addr, val, size); |
19c8b834 ID |
577 | DRM_DEBUG_KMS("ovl_width = %d, ovl_height = %d\n", |
578 | win_data->ovl_width, win_data->ovl_height); | |
1c248b7d ID |
579 | |
580 | /* buffer size */ | |
581 | val = VIDW_BUF_SIZE_OFFSET(win_data->buf_offsize) | | |
ca555e5a JS |
582 | VIDW_BUF_SIZE_PAGEWIDTH(win_data->line_size) | |
583 | VIDW_BUF_SIZE_OFFSET_E(win_data->buf_offsize) | | |
584 | VIDW_BUF_SIZE_PAGEWIDTH_E(win_data->line_size); | |
1c248b7d ID |
585 | writel(val, ctx->regs + VIDWx_BUF_SIZE(win, 0)); |
586 | ||
587 | /* OSD position */ | |
588 | val = VIDOSDxA_TOPLEFT_X(win_data->offset_x) | | |
ca555e5a JS |
589 | VIDOSDxA_TOPLEFT_Y(win_data->offset_y) | |
590 | VIDOSDxA_TOPLEFT_X_E(win_data->offset_x) | | |
591 | VIDOSDxA_TOPLEFT_Y_E(win_data->offset_y); | |
1c248b7d ID |
592 | writel(val, ctx->regs + VIDOSD_A(win)); |
593 | ||
f56aad3a JS |
594 | last_x = win_data->offset_x + win_data->ovl_width; |
595 | if (last_x) | |
596 | last_x--; | |
597 | last_y = win_data->offset_y + win_data->ovl_height; | |
598 | if (last_y) | |
599 | last_y--; | |
600 | ||
ca555e5a JS |
601 | val = VIDOSDxB_BOTRIGHT_X(last_x) | VIDOSDxB_BOTRIGHT_Y(last_y) | |
602 | VIDOSDxB_BOTRIGHT_X_E(last_x) | VIDOSDxB_BOTRIGHT_Y_E(last_y); | |
603 | ||
1c248b7d ID |
604 | writel(val, ctx->regs + VIDOSD_B(win)); |
605 | ||
19c8b834 | 606 | DRM_DEBUG_KMS("osd pos: tx = %d, ty = %d, bx = %d, by = %d\n", |
f56aad3a | 607 | win_data->offset_x, win_data->offset_y, last_x, last_y); |
1c248b7d ID |
608 | |
609 | /* hardware window 0 doesn't support alpha channel. */ | |
610 | if (win != 0) { | |
611 | /* OSD alpha */ | |
612 | alpha = VIDISD14C_ALPHA1_R(0xf) | | |
613 | VIDISD14C_ALPHA1_G(0xf) | | |
614 | VIDISD14C_ALPHA1_B(0xf); | |
615 | ||
616 | writel(alpha, ctx->regs + VIDOSD_C(win)); | |
617 | } | |
618 | ||
619 | /* OSD size */ | |
620 | if (win != 3 && win != 4) { | |
621 | u32 offset = VIDOSD_D(win); | |
622 | if (win == 0) | |
0f10cf14 | 623 | offset = VIDOSD_C(win); |
19c8b834 | 624 | val = win_data->ovl_width * win_data->ovl_height; |
1c248b7d ID |
625 | writel(val, ctx->regs + offset); |
626 | ||
627 | DRM_DEBUG_KMS("osd size = 0x%x\n", (unsigned int)val); | |
628 | } | |
629 | ||
bb7704d6 | 630 | fimd_win_set_pixfmt(ctx, win); |
1c248b7d ID |
631 | |
632 | /* hardware window 0 doesn't support color key. */ | |
633 | if (win != 0) | |
bb7704d6 | 634 | fimd_win_set_colkey(ctx, win); |
1c248b7d | 635 | |
ec05da95 ID |
636 | /* wincon */ |
637 | val = readl(ctx->regs + WINCON(win)); | |
638 | val |= WINCONx_ENWIN; | |
639 | writel(val, ctx->regs + WINCON(win)); | |
640 | ||
1c248b7d | 641 | /* Enable DMA channel and unprotect windows */ |
de7af100 TF |
642 | fimd_shadow_protect_win(ctx, win, false); |
643 | ||
644 | if (ctx->driver_data->has_shadowcon) { | |
645 | val = readl(ctx->regs + SHADOWCON); | |
646 | val |= SHADOWCON_CHx_ENABLE(win); | |
647 | writel(val, ctx->regs + SHADOWCON); | |
648 | } | |
ec05da95 ID |
649 | |
650 | win_data->enabled = true; | |
1c248b7d ID |
651 | } |
652 | ||
bb7704d6 | 653 | static void fimd_win_disable(struct exynos_drm_manager *mgr, int zpos) |
1c248b7d | 654 | { |
bb7704d6 | 655 | struct fimd_context *ctx = mgr->ctx; |
ec05da95 | 656 | struct fimd_win_data *win_data; |
864ee9e6 | 657 | int win = zpos; |
1c248b7d ID |
658 | u32 val; |
659 | ||
864ee9e6 JS |
660 | if (win == DEFAULT_ZPOS) |
661 | win = ctx->default_win; | |
662 | ||
37b006e8 | 663 | if (win < 0 || win >= WINDOWS_NR) |
1c248b7d ID |
664 | return; |
665 | ||
ec05da95 ID |
666 | win_data = &ctx->win_data[win]; |
667 | ||
db7e55ae P |
668 | if (ctx->suspended) { |
669 | /* do not resume this window*/ | |
670 | win_data->resume = false; | |
671 | return; | |
672 | } | |
673 | ||
1c248b7d | 674 | /* protect windows */ |
de7af100 | 675 | fimd_shadow_protect_win(ctx, win, true); |
1c248b7d ID |
676 | |
677 | /* wincon */ | |
678 | val = readl(ctx->regs + WINCON(win)); | |
679 | val &= ~WINCONx_ENWIN; | |
680 | writel(val, ctx->regs + WINCON(win)); | |
681 | ||
682 | /* unprotect windows */ | |
de7af100 TF |
683 | if (ctx->driver_data->has_shadowcon) { |
684 | val = readl(ctx->regs + SHADOWCON); | |
685 | val &= ~SHADOWCON_CHx_ENABLE(win); | |
686 | writel(val, ctx->regs + SHADOWCON); | |
687 | } | |
688 | ||
689 | fimd_shadow_protect_win(ctx, win, false); | |
ec05da95 ID |
690 | |
691 | win_data->enabled = false; | |
1c248b7d ID |
692 | } |
693 | ||
080be03d SP |
694 | static void fimd_dpms(struct exynos_drm_manager *mgr, int mode) |
695 | { | |
696 | struct fimd_context *ctx = mgr->ctx; | |
697 | ||
698 | DRM_DEBUG_KMS("%d\n", mode); | |
699 | ||
700 | mutex_lock(&ctx->lock); | |
701 | ||
702 | switch (mode) { | |
703 | case DRM_MODE_DPMS_ON: | |
704 | /* | |
705 | * enable fimd hardware only if suspended status. | |
706 | * | |
707 | * P.S. fimd_dpms function would be called at booting time so | |
708 | * clk_enable could be called double time. | |
709 | */ | |
710 | if (ctx->suspended) | |
711 | pm_runtime_get_sync(ctx->dev); | |
712 | break; | |
713 | case DRM_MODE_DPMS_STANDBY: | |
714 | case DRM_MODE_DPMS_SUSPEND: | |
715 | case DRM_MODE_DPMS_OFF: | |
716 | if (!ctx->suspended) | |
717 | pm_runtime_put_sync(ctx->dev); | |
718 | break; | |
719 | default: | |
720 | DRM_DEBUG_KMS("unspecified mode %d\n", mode); | |
721 | break; | |
722 | } | |
723 | ||
724 | mutex_unlock(&ctx->lock); | |
725 | } | |
726 | ||
1c6244c3 | 727 | static struct exynos_drm_manager_ops fimd_manager_ops = { |
40c8ab4b | 728 | .initialize = fimd_mgr_initialize, |
080be03d | 729 | .remove = fimd_mgr_remove, |
1c6244c3 | 730 | .dpms = fimd_dpms, |
a968e727 SP |
731 | .mode_fixup = fimd_mode_fixup, |
732 | .mode_set = fimd_mode_set, | |
1c6244c3 SP |
733 | .commit = fimd_commit, |
734 | .enable_vblank = fimd_enable_vblank, | |
735 | .disable_vblank = fimd_disable_vblank, | |
736 | .wait_for_vblank = fimd_wait_for_vblank, | |
737 | .win_mode_set = fimd_win_mode_set, | |
738 | .win_commit = fimd_win_commit, | |
739 | .win_disable = fimd_win_disable, | |
1c248b7d ID |
740 | }; |
741 | ||
677e84c1 | 742 | static struct exynos_drm_manager fimd_manager = { |
080be03d SP |
743 | .type = EXYNOS_DISPLAY_TYPE_LCD, |
744 | .ops = &fimd_manager_ops, | |
677e84c1 JS |
745 | }; |
746 | ||
1c248b7d ID |
747 | static irqreturn_t fimd_irq_handler(int irq, void *dev_id) |
748 | { | |
749 | struct fimd_context *ctx = (struct fimd_context *)dev_id; | |
1c248b7d ID |
750 | u32 val; |
751 | ||
752 | val = readl(ctx->regs + VIDINTCON1); | |
753 | ||
754 | if (val & VIDINTCON1_INT_FRAME) | |
755 | /* VSYNC interrupt */ | |
756 | writel(VIDINTCON1_INT_FRAME, ctx->regs + VIDINTCON1); | |
757 | ||
ec05da95 | 758 | /* check the crtc is detached already from encoder */ |
080be03d | 759 | if (ctx->pipe < 0 || !ctx->drm_dev) |
ec05da95 | 760 | goto out; |
483b88f8 | 761 | |
080be03d SP |
762 | drm_handle_vblank(ctx->drm_dev, ctx->pipe); |
763 | exynos_drm_crtc_finish_pageflip(ctx->drm_dev, ctx->pipe); | |
1c248b7d | 764 | |
01ce113c P |
765 | /* set wait vsync event to zero and wake up queue. */ |
766 | if (atomic_read(&ctx->wait_vsync_event)) { | |
767 | atomic_set(&ctx->wait_vsync_event, 0); | |
8dd9ad5d | 768 | wake_up(&ctx->wait_vsync_queue); |
01ce113c | 769 | } |
ec05da95 | 770 | out: |
1c248b7d ID |
771 | return IRQ_HANDLED; |
772 | } | |
773 | ||
1c248b7d ID |
774 | static void fimd_clear_win(struct fimd_context *ctx, int win) |
775 | { | |
1c248b7d ID |
776 | writel(0, ctx->regs + WINCON(win)); |
777 | writel(0, ctx->regs + VIDOSD_A(win)); | |
778 | writel(0, ctx->regs + VIDOSD_B(win)); | |
779 | writel(0, ctx->regs + VIDOSD_C(win)); | |
780 | ||
781 | if (win == 1 || win == 2) | |
782 | writel(0, ctx->regs + VIDOSD_D(win)); | |
783 | ||
de7af100 | 784 | fimd_shadow_protect_win(ctx, win, false); |
1c248b7d ID |
785 | } |
786 | ||
5d55393a | 787 | static int fimd_clock(struct fimd_context *ctx, bool enable) |
373af0c0 | 788 | { |
373af0c0 ID |
789 | if (enable) { |
790 | int ret; | |
791 | ||
11963a63 | 792 | ret = clk_prepare_enable(ctx->bus_clk); |
373af0c0 ID |
793 | if (ret < 0) |
794 | return ret; | |
795 | ||
11963a63 | 796 | ret = clk_prepare_enable(ctx->lcd_clk); |
373af0c0 | 797 | if (ret < 0) { |
11963a63 | 798 | clk_disable_unprepare(ctx->bus_clk); |
373af0c0 ID |
799 | return ret; |
800 | } | |
5d55393a | 801 | } else { |
11963a63 VS |
802 | clk_disable_unprepare(ctx->lcd_clk); |
803 | clk_disable_unprepare(ctx->bus_clk); | |
5d55393a ID |
804 | } |
805 | ||
806 | return 0; | |
807 | } | |
808 | ||
080be03d | 809 | static void fimd_window_suspend(struct exynos_drm_manager *mgr) |
db7e55ae | 810 | { |
bb7704d6 | 811 | struct fimd_context *ctx = mgr->ctx; |
db7e55ae P |
812 | struct fimd_win_data *win_data; |
813 | int i; | |
814 | ||
815 | for (i = 0; i < WINDOWS_NR; i++) { | |
816 | win_data = &ctx->win_data[i]; | |
817 | win_data->resume = win_data->enabled; | |
bb7704d6 | 818 | fimd_win_disable(mgr, i); |
db7e55ae | 819 | } |
bb7704d6 | 820 | fimd_wait_for_vblank(mgr); |
db7e55ae P |
821 | } |
822 | ||
080be03d | 823 | static void fimd_window_resume(struct exynos_drm_manager *mgr) |
db7e55ae | 824 | { |
bb7704d6 | 825 | struct fimd_context *ctx = mgr->ctx; |
db7e55ae P |
826 | struct fimd_win_data *win_data; |
827 | int i; | |
828 | ||
829 | for (i = 0; i < WINDOWS_NR; i++) { | |
830 | win_data = &ctx->win_data[i]; | |
831 | win_data->enabled = win_data->resume; | |
832 | win_data->resume = false; | |
833 | } | |
834 | } | |
835 | ||
080be03d SP |
836 | static void fimd_apply(struct exynos_drm_manager *mgr) |
837 | { | |
838 | struct fimd_context *ctx = mgr->ctx; | |
839 | struct fimd_win_data *win_data; | |
840 | int i; | |
841 | ||
842 | for (i = 0; i < WINDOWS_NR; i++) { | |
843 | win_data = &ctx->win_data[i]; | |
844 | if (win_data->enabled) | |
845 | fimd_win_commit(mgr, i); | |
846 | } | |
847 | ||
848 | fimd_commit(mgr); | |
849 | } | |
850 | ||
bb7704d6 | 851 | static int fimd_activate(struct exynos_drm_manager *mgr, bool enable) |
5d55393a | 852 | { |
bb7704d6 | 853 | struct fimd_context *ctx = mgr->ctx; |
bb7704d6 | 854 | |
5d55393a ID |
855 | if (enable) { |
856 | int ret; | |
5d55393a ID |
857 | |
858 | ret = fimd_clock(ctx, true); | |
859 | if (ret < 0) | |
860 | return ret; | |
373af0c0 ID |
861 | |
862 | ctx->suspended = false; | |
863 | ||
864 | /* if vblank was enabled status, enable it again. */ | |
865 | if (test_and_clear_bit(0, &ctx->irq_flags)) | |
bb7704d6 | 866 | fimd_enable_vblank(mgr); |
db7e55ae | 867 | |
080be03d | 868 | fimd_window_resume(mgr); |
87244fa6 SP |
869 | |
870 | fimd_apply(mgr); | |
373af0c0 | 871 | } else { |
080be03d | 872 | fimd_window_suspend(mgr); |
db7e55ae | 873 | |
5d55393a | 874 | fimd_clock(ctx, false); |
373af0c0 ID |
875 | ctx->suspended = true; |
876 | } | |
877 | ||
878 | return 0; | |
879 | } | |
880 | ||
562ad9f4 | 881 | static int fimd_get_platform_data(struct fimd_context *ctx, struct device *dev) |
1c248b7d | 882 | { |
2d3f173c SK |
883 | struct videomode *vm; |
884 | int ret; | |
7f4596f4 | 885 | |
2d3f173c SK |
886 | vm = &ctx->panel.vm; |
887 | ret = of_get_videomode(dev->of_node, vm, OF_USE_NATIVE_MODE); | |
888 | if (ret) { | |
889 | DRM_ERROR("failed: of_get_videomode() : %d\n", ret); | |
890 | return ret; | |
1c248b7d ID |
891 | } |
892 | ||
2d3f173c SK |
893 | if (vm->flags & DISPLAY_FLAGS_VSYNC_LOW) |
894 | ctx->vidcon1 |= VIDCON1_INV_VSYNC; | |
895 | if (vm->flags & DISPLAY_FLAGS_HSYNC_LOW) | |
896 | ctx->vidcon1 |= VIDCON1_INV_HSYNC; | |
897 | if (vm->flags & DISPLAY_FLAGS_DE_LOW) | |
898 | ctx->vidcon1 |= VIDCON1_INV_VDEN; | |
899 | if (vm->flags & DISPLAY_FLAGS_PIXDATA_NEGEDGE) | |
900 | ctx->vidcon1 |= VIDCON1_INV_VCLK; | |
901 | ||
562ad9f4 AH |
902 | return 0; |
903 | } | |
904 | ||
905 | static int fimd_probe(struct platform_device *pdev) | |
906 | { | |
907 | struct device *dev = &pdev->dev; | |
908 | struct fimd_context *ctx; | |
562ad9f4 AH |
909 | struct resource *res; |
910 | int win; | |
911 | int ret = -EINVAL; | |
1c248b7d | 912 | |
2d3f173c SK |
913 | if (!dev->of_node) |
914 | return -ENODEV; | |
915 | ||
d873ab99 | 916 | ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); |
1c248b7d ID |
917 | if (!ctx) |
918 | return -ENOMEM; | |
919 | ||
bb7704d6 SP |
920 | ctx->dev = dev; |
921 | ||
562ad9f4 AH |
922 | ret = fimd_get_platform_data(ctx, dev); |
923 | if (ret) | |
924 | return ret; | |
925 | ||
a968e727 SP |
926 | ctx->bus_clk = devm_clk_get(dev, "fimd"); |
927 | if (IS_ERR(ctx->bus_clk)) { | |
928 | dev_err(dev, "failed to get bus clock\n"); | |
929 | return PTR_ERR(ctx->bus_clk); | |
930 | } | |
931 | ||
932 | ctx->lcd_clk = devm_clk_get(dev, "sclk_fimd"); | |
933 | if (IS_ERR(ctx->lcd_clk)) { | |
934 | dev_err(dev, "failed to get lcd clock\n"); | |
935 | return PTR_ERR(ctx->lcd_clk); | |
936 | } | |
1c248b7d | 937 | |
1c248b7d | 938 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
1c248b7d | 939 | |
d873ab99 | 940 | ctx->regs = devm_ioremap_resource(dev, res); |
d4ed6025 TR |
941 | if (IS_ERR(ctx->regs)) |
942 | return PTR_ERR(ctx->regs); | |
1c248b7d | 943 | |
1977e6d8 | 944 | res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "vsync"); |
1c248b7d ID |
945 | if (!res) { |
946 | dev_err(dev, "irq request failed.\n"); | |
a4d8de5f | 947 | return -ENXIO; |
1c248b7d ID |
948 | } |
949 | ||
950 | ctx->irq = res->start; | |
951 | ||
d873ab99 | 952 | ret = devm_request_irq(dev, ctx->irq, fimd_irq_handler, |
edc57266 SK |
953 | 0, "drm_fimd", ctx); |
954 | if (ret) { | |
1c248b7d | 955 | dev_err(dev, "irq request failed.\n"); |
a4d8de5f | 956 | return ret; |
1c248b7d ID |
957 | } |
958 | ||
18873465 | 959 | ctx->driver_data = drm_fimd_get_driver_data(pdev); |
57ed0f7b | 960 | init_waitqueue_head(&ctx->wait_vsync_queue); |
01ce113c | 961 | atomic_set(&ctx->wait_vsync_event, 0); |
1c248b7d | 962 | |
c32b06ef ID |
963 | mutex_init(&ctx->lock); |
964 | ||
bb7704d6 | 965 | platform_set_drvdata(pdev, &fimd_manager); |
c32b06ef | 966 | |
080be03d SP |
967 | fimd_manager.ctx = ctx; |
968 | exynos_drm_manager_register(&fimd_manager); | |
969 | ||
970 | fimd_display.ctx = ctx; | |
971 | exynos_drm_display_register(&fimd_display); | |
972 | ||
c32b06ef ID |
973 | pm_runtime_enable(dev); |
974 | pm_runtime_get_sync(dev); | |
975 | ||
976 | for (win = 0; win < WINDOWS_NR; win++) | |
977 | fimd_clear_win(ctx, win); | |
978 | ||
1c248b7d | 979 | return 0; |
1c248b7d ID |
980 | } |
981 | ||
56550d94 | 982 | static int fimd_remove(struct platform_device *pdev) |
1c248b7d | 983 | { |
cb91f6a0 | 984 | struct device *dev = &pdev->dev; |
bb7704d6 SP |
985 | struct exynos_drm_manager *mgr = platform_get_drvdata(pdev); |
986 | struct fimd_context *ctx = mgr->ctx; | |
1c248b7d | 987 | |
080be03d SP |
988 | exynos_drm_display_unregister(&fimd_display); |
989 | exynos_drm_manager_unregister(&fimd_manager); | |
1c248b7d | 990 | |
cb91f6a0 JS |
991 | if (ctx->suspended) |
992 | goto out; | |
993 | ||
cb91f6a0 JS |
994 | pm_runtime_set_suspended(dev); |
995 | pm_runtime_put_sync(dev); | |
996 | ||
997 | out: | |
998 | pm_runtime_disable(dev); | |
999 | ||
1c248b7d ID |
1000 | return 0; |
1001 | } | |
1002 | ||
e30d4bcf ID |
1003 | #ifdef CONFIG_PM_SLEEP |
1004 | static int fimd_suspend(struct device *dev) | |
1005 | { | |
bb7704d6 | 1006 | struct exynos_drm_manager *mgr = get_fimd_manager(dev); |
e30d4bcf | 1007 | |
373af0c0 ID |
1008 | /* |
1009 | * do not use pm_runtime_suspend(). if pm_runtime_suspend() is | |
1010 | * called here, an error would be returned by that interface | |
1011 | * because the usage_count of pm runtime is more than 1. | |
1012 | */ | |
5d55393a | 1013 | if (!pm_runtime_suspended(dev)) |
bb7704d6 | 1014 | return fimd_activate(mgr, false); |
5d55393a ID |
1015 | |
1016 | return 0; | |
e30d4bcf ID |
1017 | } |
1018 | ||
1019 | static int fimd_resume(struct device *dev) | |
1020 | { | |
bb7704d6 | 1021 | struct exynos_drm_manager *mgr = get_fimd_manager(dev); |
e30d4bcf | 1022 | |
373af0c0 ID |
1023 | /* |
1024 | * if entered to sleep when lcd panel was on, the usage_count | |
1025 | * of pm runtime would still be 1 so in this case, fimd driver | |
1026 | * should be on directly not drawing on pm runtime interface. | |
1027 | */ | |
87244fa6 SP |
1028 | if (pm_runtime_suspended(dev)) |
1029 | return 0; | |
5d55393a | 1030 | |
87244fa6 | 1031 | return fimd_activate(mgr, true); |
e30d4bcf ID |
1032 | } |
1033 | #endif | |
1034 | ||
cb91f6a0 JS |
1035 | #ifdef CONFIG_PM_RUNTIME |
1036 | static int fimd_runtime_suspend(struct device *dev) | |
1037 | { | |
bb7704d6 | 1038 | struct exynos_drm_manager *mgr = get_fimd_manager(dev); |
cb91f6a0 | 1039 | |
bb7704d6 | 1040 | return fimd_activate(mgr, false); |
cb91f6a0 JS |
1041 | } |
1042 | ||
1043 | static int fimd_runtime_resume(struct device *dev) | |
1044 | { | |
bb7704d6 | 1045 | struct exynos_drm_manager *mgr = get_fimd_manager(dev); |
cb91f6a0 | 1046 | |
bb7704d6 | 1047 | return fimd_activate(mgr, true); |
cb91f6a0 JS |
1048 | } |
1049 | #endif | |
1050 | ||
1051 | static const struct dev_pm_ops fimd_pm_ops = { | |
e30d4bcf | 1052 | SET_SYSTEM_SLEEP_PM_OPS(fimd_suspend, fimd_resume) |
cb91f6a0 JS |
1053 | SET_RUNTIME_PM_OPS(fimd_runtime_suspend, fimd_runtime_resume, NULL) |
1054 | }; | |
1055 | ||
132a5b91 | 1056 | struct platform_driver fimd_driver = { |
1c248b7d | 1057 | .probe = fimd_probe, |
56550d94 | 1058 | .remove = fimd_remove, |
1c248b7d ID |
1059 | .driver = { |
1060 | .name = "exynos4-fb", | |
1061 | .owner = THIS_MODULE, | |
cb91f6a0 | 1062 | .pm = &fimd_pm_ops, |
2d3f173c | 1063 | .of_match_table = fimd_driver_dt_match, |
1c248b7d ID |
1064 | }, |
1065 | }; |