1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* drivers/gpu/drm/exynos/exynos7_drm_decon.c |
3 | * |
4 | * Copyright (C) 2014 Samsung Electronics Co.Ltd |
5 | * Authors: |
6 | * Akshu Agarwal <akshua@gmail.com> |
7 | * Ajay Kumar <ajaykumar.rs@samsung.com> |
8 | */ |
9 | |
10 | #include <linux/clk.h> |
11 | #include <linux/component.h> |
12 | #include <linux/kernel.h> |
13 | #include <linux/of.h> |
14 | #include <linux/of_address.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/pm_runtime.h> |
17 | |
18 | #include <video/of_display_timing.h> |
19 | #include <video/of_videomode.h> |
20 | |
21 | #include <drm/drm_fourcc.h> |
22 | #include <drm/drm_framebuffer.h> |
23 | #include <drm/drm_vblank.h> |
24 | #include <drm/exynos_drm.h> |
25 | |
26 | #include "exynos_drm_crtc.h" |
27 | #include "exynos_drm_drv.h" |
28 | #include "exynos_drm_fb.h" |
29 | #include "exynos_drm_plane.h" |
30 | #include "regs-decon7.h" |
31 | |
32 | /* |
33 | * DECON stands for Display and Enhancement controller. |
34 | */ |
35 | |
36 | #define MIN_FB_WIDTH_FOR_16WORD_BURST 128 |
37 | |
38 | #define WINDOWS_NR 2 |
39 | |
40 | struct decon_context { |
41 | struct device *dev; |
42 | struct drm_device *drm_dev; |
43 | void *dma_priv; |
44 | struct exynos_drm_crtc *crtc; |
45 | struct exynos_drm_plane planes[WINDOWS_NR]; |
46 | struct exynos_drm_plane_config configs[WINDOWS_NR]; |
47 | struct clk *pclk; |
48 | struct clk *aclk; |
49 | struct clk *eclk; |
50 | struct clk *vclk; |
51 | void __iomem *regs; |
52 | unsigned long irq_flags; |
53 | bool i80_if; |
54 | bool suspended; |
55 | wait_queue_head_t wait_vsync_queue; |
56 | atomic_t wait_vsync_event; |
57 | |
58 | struct drm_encoder *encoder; |
59 | }; |
60 | |
61 | static const struct of_device_id decon_driver_dt_match[] = { |
62 | {.compatible = "samsung,exynos7-decon" }, |
63 | {}, |
64 | }; |
65 | MODULE_DEVICE_TABLE(of, decon_driver_dt_match); |
66 | |
67 | static const uint32_t decon_formats[] = { |
68 | DRM_FORMAT_RGB565, |
69 | DRM_FORMAT_XRGB8888, |
70 | DRM_FORMAT_XBGR8888, |
71 | DRM_FORMAT_RGBX8888, |
72 | DRM_FORMAT_BGRX8888, |
73 | DRM_FORMAT_ARGB8888, |
74 | DRM_FORMAT_ABGR8888, |
75 | DRM_FORMAT_RGBA8888, |
76 | DRM_FORMAT_BGRA8888, |
77 | }; |
78 | |
79 | static const enum drm_plane_type decon_win_types[WINDOWS_NR] = { |
80 | DRM_PLANE_TYPE_PRIMARY, |
81 | DRM_PLANE_TYPE_CURSOR, |
82 | }; |
83 | |
84 | static void decon_wait_for_vblank(struct exynos_drm_crtc *crtc) |
85 | { |
86 | struct decon_context *ctx = crtc->ctx; |
87 | |
88 | if (ctx->suspended) |
89 | return; |
90 | |
91 | atomic_set(v: &ctx->wait_vsync_event, i: 1); |
92 | |
93 | /* |
94 | * wait for DECON to signal VSYNC interrupt or return after |
95 | * timeout which is set to 50ms (refresh rate of 20). |
96 | */ |
97 | if (!wait_event_timeout(ctx->wait_vsync_queue, |
98 | !atomic_read(&ctx->wait_vsync_event), |
99 | HZ/20)) |
100 | DRM_DEV_DEBUG_KMS(ctx->dev, "vblank wait timed out.\n" ); |
101 | } |
102 | |
103 | static void decon_clear_channels(struct exynos_drm_crtc *crtc) |
104 | { |
105 | struct decon_context *ctx = crtc->ctx; |
106 | unsigned int win, ch_enabled = 0; |
107 | |
108 | /* Check if any channel is enabled. */ |
109 | for (win = 0; win < WINDOWS_NR; win++) { |
110 | u32 val = readl(addr: ctx->regs + WINCON(win)); |
111 | |
112 | if (val & WINCONx_ENWIN) { |
113 | val &= ~WINCONx_ENWIN; |
114 | writel(val, addr: ctx->regs + WINCON(win)); |
115 | ch_enabled = 1; |
116 | } |
117 | } |
118 | |
119 | /* Wait for vsync, as disable channel takes effect at next vsync */ |
120 | if (ch_enabled) |
121 | decon_wait_for_vblank(crtc: ctx->crtc); |
122 | } |
123 | |
124 | static int decon_ctx_initialize(struct decon_context *ctx, |
125 | struct drm_device *drm_dev) |
126 | { |
127 | ctx->drm_dev = drm_dev; |
128 | |
129 | decon_clear_channels(crtc: ctx->crtc); |
130 | |
131 | return exynos_drm_register_dma(drm: drm_dev, dev: ctx->dev, dma_priv: &ctx->dma_priv); |
132 | } |
133 | |
134 | static void decon_ctx_remove(struct decon_context *ctx) |
135 | { |
136 | /* detach this sub driver from iommu mapping if supported. */ |
137 | exynos_drm_unregister_dma(drm: ctx->drm_dev, dev: ctx->dev, dma_priv: &ctx->dma_priv); |
138 | } |
139 | |
140 | static u32 decon_calc_clkdiv(struct decon_context *ctx, |
141 | const struct drm_display_mode *mode) |
142 | { |
143 | unsigned long ideal_clk = mode->clock; |
144 | u32 clkdiv; |
145 | |
146 | /* Find the clock divider value that gets us closest to ideal_clk */ |
147 | clkdiv = DIV_ROUND_UP(clk_get_rate(ctx->vclk), ideal_clk); |
148 | |
149 | return (clkdiv < 0x100) ? clkdiv : 0xff; |
150 | } |
151 | |
152 | static void decon_commit(struct exynos_drm_crtc *crtc) |
153 | { |
154 | struct decon_context *ctx = crtc->ctx; |
155 | struct drm_display_mode *mode = &crtc->base.state->adjusted_mode; |
156 | u32 val, clkdiv; |
157 | |
158 | if (ctx->suspended) |
159 | return; |
160 | |
161 | /* nothing to do if we haven't set the mode yet */ |
162 | if (mode->htotal == 0 || mode->vtotal == 0) |
163 | return; |
164 | |
165 | if (!ctx->i80_if) { |
166 | int vsync_len, vbpd, vfpd, hsync_len, hbpd, hfpd; |
167 | /* setup vertical timing values. */ |
168 | vsync_len = mode->crtc_vsync_end - mode->crtc_vsync_start; |
169 | vbpd = mode->crtc_vtotal - mode->crtc_vsync_end; |
170 | vfpd = mode->crtc_vsync_start - mode->crtc_vdisplay; |
171 | |
172 | val = VIDTCON0_VBPD(vbpd - 1) | VIDTCON0_VFPD(vfpd - 1); |
173 | writel(val, addr: ctx->regs + VIDTCON0); |
174 | |
175 | val = VIDTCON1_VSPW(vsync_len - 1); |
176 | writel(val, addr: ctx->regs + VIDTCON1); |
177 | |
178 | /* setup horizontal timing values. */ |
179 | hsync_len = mode->crtc_hsync_end - mode->crtc_hsync_start; |
180 | hbpd = mode->crtc_htotal - mode->crtc_hsync_end; |
181 | hfpd = mode->crtc_hsync_start - mode->crtc_hdisplay; |
182 | |
183 | /* setup horizontal timing values. */ |
184 | val = VIDTCON2_HBPD(hbpd - 1) | VIDTCON2_HFPD(hfpd - 1); |
185 | writel(val, addr: ctx->regs + VIDTCON2); |
186 | |
187 | val = VIDTCON3_HSPW(hsync_len - 1); |
188 | writel(val, addr: ctx->regs + VIDTCON3); |
189 | } |
190 | |
191 | /* setup horizontal and vertical display size. */ |
192 | val = VIDTCON4_LINEVAL(mode->vdisplay - 1) | |
193 | VIDTCON4_HOZVAL(mode->hdisplay - 1); |
194 | writel(val, addr: ctx->regs + VIDTCON4); |
195 | |
196 | writel(val: mode->vdisplay - 1, addr: ctx->regs + LINECNT_OP_THRESHOLD); |
197 | |
198 | /* |
199 | * fields of register with prefix '_F' would be updated |
200 | * at vsync(same as dma start) |
201 | */ |
202 | val = VIDCON0_ENVID | VIDCON0_ENVID_F; |
203 | writel(val, addr: ctx->regs + VIDCON0); |
204 | |
205 | clkdiv = decon_calc_clkdiv(ctx, mode); |
206 | if (clkdiv > 1) { |
207 | val = VCLKCON1_CLKVAL_NUM_VCLK(clkdiv - 1); |
208 | writel(val, addr: ctx->regs + VCLKCON1); |
209 | writel(val, addr: ctx->regs + VCLKCON2); |
210 | } |
211 | |
212 | val = readl(addr: ctx->regs + DECON_UPDATE); |
213 | val |= DECON_UPDATE_STANDALONE_F; |
214 | writel(val, addr: ctx->regs + DECON_UPDATE); |
215 | } |
216 | |
217 | static int decon_enable_vblank(struct exynos_drm_crtc *crtc) |
218 | { |
219 | struct decon_context *ctx = crtc->ctx; |
220 | u32 val; |
221 | |
222 | if (ctx->suspended) |
223 | return -EPERM; |
224 | |
225 | if (!test_and_set_bit(nr: 0, addr: &ctx->irq_flags)) { |
226 | val = readl(addr: ctx->regs + VIDINTCON0); |
227 | |
228 | val |= VIDINTCON0_INT_ENABLE; |
229 | |
230 | if (!ctx->i80_if) { |
231 | val |= VIDINTCON0_INT_FRAME; |
232 | val &= ~VIDINTCON0_FRAMESEL0_MASK; |
233 | val |= VIDINTCON0_FRAMESEL0_VSYNC; |
234 | } |
235 | |
236 | writel(val, addr: ctx->regs + VIDINTCON0); |
237 | } |
238 | |
239 | return 0; |
240 | } |
241 | |
242 | static void decon_disable_vblank(struct exynos_drm_crtc *crtc) |
243 | { |
244 | struct decon_context *ctx = crtc->ctx; |
245 | u32 val; |
246 | |
247 | if (ctx->suspended) |
248 | return; |
249 | |
250 | if (test_and_clear_bit(nr: 0, addr: &ctx->irq_flags)) { |
251 | val = readl(addr: ctx->regs + VIDINTCON0); |
252 | |
253 | val &= ~VIDINTCON0_INT_ENABLE; |
254 | if (!ctx->i80_if) |
255 | val &= ~VIDINTCON0_INT_FRAME; |
256 | |
257 | writel(val, addr: ctx->regs + VIDINTCON0); |
258 | } |
259 | } |
260 | |
261 | static void decon_win_set_pixfmt(struct decon_context *ctx, unsigned int win, |
262 | struct drm_framebuffer *fb) |
263 | { |
264 | unsigned long val; |
265 | int padding; |
266 | |
267 | val = readl(addr: ctx->regs + WINCON(win)); |
268 | val &= ~WINCONx_BPPMODE_MASK; |
269 | |
270 | switch (fb->format->format) { |
271 | case DRM_FORMAT_RGB565: |
272 | val |= WINCONx_BPPMODE_16BPP_565; |
273 | val |= WINCONx_BURSTLEN_16WORD; |
274 | break; |
275 | case DRM_FORMAT_XRGB8888: |
276 | val |= WINCONx_BPPMODE_24BPP_xRGB; |
277 | val |= WINCONx_BURSTLEN_16WORD; |
278 | break; |
279 | case DRM_FORMAT_XBGR8888: |
280 | val |= WINCONx_BPPMODE_24BPP_xBGR; |
281 | val |= WINCONx_BURSTLEN_16WORD; |
282 | break; |
283 | case DRM_FORMAT_RGBX8888: |
284 | val |= WINCONx_BPPMODE_24BPP_RGBx; |
285 | val |= WINCONx_BURSTLEN_16WORD; |
286 | break; |
287 | case DRM_FORMAT_BGRX8888: |
288 | val |= WINCONx_BPPMODE_24BPP_BGRx; |
289 | val |= WINCONx_BURSTLEN_16WORD; |
290 | break; |
291 | case DRM_FORMAT_ARGB8888: |
292 | val |= WINCONx_BPPMODE_32BPP_ARGB | WINCONx_BLD_PIX | |
293 | WINCONx_ALPHA_SEL; |
294 | val |= WINCONx_BURSTLEN_16WORD; |
295 | break; |
296 | case DRM_FORMAT_ABGR8888: |
297 | val |= WINCONx_BPPMODE_32BPP_ABGR | WINCONx_BLD_PIX | |
298 | WINCONx_ALPHA_SEL; |
299 | val |= WINCONx_BURSTLEN_16WORD; |
300 | break; |
301 | case DRM_FORMAT_RGBA8888: |
302 | val |= WINCONx_BPPMODE_32BPP_RGBA | WINCONx_BLD_PIX | |
303 | WINCONx_ALPHA_SEL; |
304 | val |= WINCONx_BURSTLEN_16WORD; |
305 | break; |
306 | case DRM_FORMAT_BGRA8888: |
307 | default: |
308 | val |= WINCONx_BPPMODE_32BPP_BGRA | WINCONx_BLD_PIX | |
309 | WINCONx_ALPHA_SEL; |
310 | val |= WINCONx_BURSTLEN_16WORD; |
311 | break; |
312 | } |
313 | |
314 | DRM_DEV_DEBUG_KMS(ctx->dev, "cpp = %d\n" , fb->format->cpp[0]); |
315 | |
316 | /* |
317 | * In case of exynos, setting dma-burst to 16Word causes permanent |
318 | * tearing for very small buffers, e.g. cursor buffer. Burst Mode |
319 | * switching which is based on plane size is not recommended as |
320 | * plane size varies a lot towards the end of the screen and rapid |
321 | * movement causes unstable DMA which results into iommu crash/tear. |
322 | */ |
323 | |
324 | padding = (fb->pitches[0] / fb->format->cpp[0]) - fb->width; |
325 | if (fb->width + padding < MIN_FB_WIDTH_FOR_16WORD_BURST) { |
326 | val &= ~WINCONx_BURSTLEN_MASK; |
327 | val |= WINCONx_BURSTLEN_8WORD; |
328 | } |
329 | |
330 | writel(val, addr: ctx->regs + WINCON(win)); |
331 | } |
332 | |
333 | static void decon_win_set_colkey(struct decon_context *ctx, unsigned int win) |
334 | { |
335 | unsigned int keycon0 = 0, keycon1 = 0; |
336 | |
337 | keycon0 = ~(WxKEYCON0_KEYBL_EN | WxKEYCON0_KEYEN_F | |
338 | WxKEYCON0_DIRCON) | WxKEYCON0_COMPKEY(0); |
339 | |
340 | keycon1 = WxKEYCON1_COLVAL(0xffffffff); |
341 | |
342 | writel(val: keycon0, addr: ctx->regs + WKEYCON0_BASE(win)); |
343 | writel(val: keycon1, addr: ctx->regs + WKEYCON1_BASE(win)); |
344 | } |
345 | |
346 | /** |
347 | * decon_shadow_protect_win() - disable updating values from shadow registers at vsync |
348 | * |
349 | * @ctx: display and enhancement controller context |
350 | * @win: window to protect registers for |
351 | * @protect: 1 to protect (disable updates) |
352 | */ |
353 | static void decon_shadow_protect_win(struct decon_context *ctx, |
354 | unsigned int win, bool protect) |
355 | { |
356 | u32 bits, val; |
357 | |
358 | bits = SHADOWCON_WINx_PROTECT(win); |
359 | |
360 | val = readl(addr: ctx->regs + SHADOWCON); |
361 | if (protect) |
362 | val |= bits; |
363 | else |
364 | val &= ~bits; |
365 | writel(val, addr: ctx->regs + SHADOWCON); |
366 | } |
367 | |
368 | static void decon_atomic_begin(struct exynos_drm_crtc *crtc) |
369 | { |
370 | struct decon_context *ctx = crtc->ctx; |
371 | int i; |
372 | |
373 | if (ctx->suspended) |
374 | return; |
375 | |
376 | for (i = 0; i < WINDOWS_NR; i++) |
377 | decon_shadow_protect_win(ctx, win: i, protect: true); |
378 | } |
379 | |
380 | static void decon_update_plane(struct exynos_drm_crtc *crtc, |
381 | struct exynos_drm_plane *plane) |
382 | { |
383 | struct exynos_drm_plane_state *state = |
384 | to_exynos_plane_state(state: plane->base.state); |
385 | struct decon_context *ctx = crtc->ctx; |
386 | struct drm_framebuffer *fb = state->base.fb; |
387 | int padding; |
388 | unsigned long val, alpha; |
389 | unsigned int last_x; |
390 | unsigned int last_y; |
391 | unsigned int win = plane->index; |
392 | unsigned int cpp = fb->format->cpp[0]; |
393 | unsigned int pitch = fb->pitches[0]; |
394 | |
395 | if (ctx->suspended) |
396 | return; |
397 | |
398 | /* |
399 | * SHADOWCON/PRTCON register is used for enabling timing. |
400 | * |
401 | * for example, once only width value of a register is set, |
402 | * if the dma is started then decon hardware could malfunction so |
403 | * with protect window setting, the register fields with prefix '_F' |
404 | * wouldn't be updated at vsync also but updated once unprotect window |
405 | * is set. |
406 | */ |
407 | |
408 | /* buffer start address */ |
409 | val = (unsigned long)exynos_drm_fb_dma_addr(fb, index: 0); |
410 | writel(val, addr: ctx->regs + VIDW_BUF_START(win)); |
411 | |
412 | padding = (pitch / cpp) - fb->width; |
413 | |
414 | /* buffer size */ |
415 | writel(val: fb->width + padding, addr: ctx->regs + VIDW_WHOLE_X(win)); |
416 | writel(val: fb->height, addr: ctx->regs + VIDW_WHOLE_Y(win)); |
417 | |
418 | /* offset from the start of the buffer to read */ |
419 | writel(val: state->src.x, addr: ctx->regs + VIDW_OFFSET_X(win)); |
420 | writel(val: state->src.y, addr: ctx->regs + VIDW_OFFSET_Y(win)); |
421 | |
422 | DRM_DEV_DEBUG_KMS(ctx->dev, "start addr = 0x%lx\n" , |
423 | (unsigned long)val); |
424 | DRM_DEV_DEBUG_KMS(ctx->dev, "ovl_width = %d, ovl_height = %d\n" , |
425 | state->crtc.w, state->crtc.h); |
426 | |
427 | val = VIDOSDxA_TOPLEFT_X(state->crtc.x) | |
428 | VIDOSDxA_TOPLEFT_Y(state->crtc.y); |
429 | writel(val, addr: ctx->regs + VIDOSD_A(win)); |
430 | |
431 | last_x = state->crtc.x + state->crtc.w; |
432 | if (last_x) |
433 | last_x--; |
434 | last_y = state->crtc.y + state->crtc.h; |
435 | if (last_y) |
436 | last_y--; |
437 | |
438 | val = VIDOSDxB_BOTRIGHT_X(last_x) | VIDOSDxB_BOTRIGHT_Y(last_y); |
439 | |
440 | writel(val, addr: ctx->regs + VIDOSD_B(win)); |
441 | |
442 | DRM_DEV_DEBUG_KMS(ctx->dev, "osd pos: tx = %d, ty = %d, bx = %d, by = %d\n" , |
443 | state->crtc.x, state->crtc.y, last_x, last_y); |
444 | |
445 | /* OSD alpha */ |
446 | alpha = VIDOSDxC_ALPHA0_R_F(0x0) | |
447 | VIDOSDxC_ALPHA0_G_F(0x0) | |
448 | VIDOSDxC_ALPHA0_B_F(0x0); |
449 | |
450 | writel(val: alpha, addr: ctx->regs + VIDOSD_C(win)); |
451 | |
452 | alpha = VIDOSDxD_ALPHA1_R_F(0xff) | |
453 | VIDOSDxD_ALPHA1_G_F(0xff) | |
454 | VIDOSDxD_ALPHA1_B_F(0xff); |
455 | |
456 | writel(val: alpha, addr: ctx->regs + VIDOSD_D(win)); |
457 | |
458 | decon_win_set_pixfmt(ctx, win, fb); |
459 | |
460 | /* hardware window 0 doesn't support color key. */ |
461 | if (win != 0) |
462 | decon_win_set_colkey(ctx, win); |
463 | |
464 | /* wincon */ |
465 | val = readl(addr: ctx->regs + WINCON(win)); |
466 | val |= WINCONx_TRIPLE_BUF_MODE; |
467 | val |= WINCONx_ENWIN; |
468 | writel(val, addr: ctx->regs + WINCON(win)); |
469 | |
470 | /* Enable DMA channel and unprotect windows */ |
471 | decon_shadow_protect_win(ctx, win, protect: false); |
472 | |
473 | val = readl(addr: ctx->regs + DECON_UPDATE); |
474 | val |= DECON_UPDATE_STANDALONE_F; |
475 | writel(val, addr: ctx->regs + DECON_UPDATE); |
476 | } |
477 | |
478 | static void decon_disable_plane(struct exynos_drm_crtc *crtc, |
479 | struct exynos_drm_plane *plane) |
480 | { |
481 | struct decon_context *ctx = crtc->ctx; |
482 | unsigned int win = plane->index; |
483 | u32 val; |
484 | |
485 | if (ctx->suspended) |
486 | return; |
487 | |
488 | /* protect windows */ |
489 | decon_shadow_protect_win(ctx, win, protect: true); |
490 | |
491 | /* wincon */ |
492 | val = readl(addr: ctx->regs + WINCON(win)); |
493 | val &= ~WINCONx_ENWIN; |
494 | writel(val, addr: ctx->regs + WINCON(win)); |
495 | |
496 | val = readl(addr: ctx->regs + DECON_UPDATE); |
497 | val |= DECON_UPDATE_STANDALONE_F; |
498 | writel(val, addr: ctx->regs + DECON_UPDATE); |
499 | } |
500 | |
501 | static void decon_atomic_flush(struct exynos_drm_crtc *crtc) |
502 | { |
503 | struct decon_context *ctx = crtc->ctx; |
504 | int i; |
505 | |
506 | if (ctx->suspended) |
507 | return; |
508 | |
509 | for (i = 0; i < WINDOWS_NR; i++) |
510 | decon_shadow_protect_win(ctx, win: i, protect: false); |
511 | exynos_crtc_handle_event(exynos_crtc: crtc); |
512 | } |
513 | |
514 | static void decon_init(struct decon_context *ctx) |
515 | { |
516 | u32 val; |
517 | |
518 | writel(VIDCON0_SWRESET, addr: ctx->regs + VIDCON0); |
519 | |
520 | val = VIDOUTCON0_DISP_IF_0_ON; |
521 | if (!ctx->i80_if) |
522 | val |= VIDOUTCON0_RGBIF; |
523 | writel(val, addr: ctx->regs + VIDOUTCON0); |
524 | |
525 | writel(VCLKCON0_CLKVALUP | VCLKCON0_VCLKFREE, addr: ctx->regs + VCLKCON0); |
526 | |
527 | if (!ctx->i80_if) |
528 | writel(VIDCON1_VCLK_HOLD, addr: ctx->regs + VIDCON1(0)); |
529 | } |
530 | |
531 | static void decon_atomic_enable(struct exynos_drm_crtc *crtc) |
532 | { |
533 | struct decon_context *ctx = crtc->ctx; |
534 | int ret; |
535 | |
536 | if (!ctx->suspended) |
537 | return; |
538 | |
539 | ret = pm_runtime_resume_and_get(dev: ctx->dev); |
540 | if (ret < 0) { |
541 | DRM_DEV_ERROR(ctx->dev, "failed to enable DECON device.\n" ); |
542 | return; |
543 | } |
544 | |
545 | decon_init(ctx); |
546 | |
547 | /* if vblank was enabled status, enable it again. */ |
548 | if (test_and_clear_bit(nr: 0, addr: &ctx->irq_flags)) |
549 | decon_enable_vblank(crtc: ctx->crtc); |
550 | |
551 | decon_commit(crtc: ctx->crtc); |
552 | |
553 | ctx->suspended = false; |
554 | } |
555 | |
556 | static void decon_atomic_disable(struct exynos_drm_crtc *crtc) |
557 | { |
558 | struct decon_context *ctx = crtc->ctx; |
559 | int i; |
560 | |
561 | if (ctx->suspended) |
562 | return; |
563 | |
564 | /* |
565 | * We need to make sure that all windows are disabled before we |
566 | * suspend that connector. Otherwise we might try to scan from |
567 | * a destroyed buffer later. |
568 | */ |
569 | for (i = 0; i < WINDOWS_NR; i++) |
570 | decon_disable_plane(crtc, plane: &ctx->planes[i]); |
571 | |
572 | pm_runtime_put_sync(dev: ctx->dev); |
573 | |
574 | ctx->suspended = true; |
575 | } |
576 | |
577 | static const struct exynos_drm_crtc_ops decon_crtc_ops = { |
578 | .atomic_enable = decon_atomic_enable, |
579 | .atomic_disable = decon_atomic_disable, |
580 | .enable_vblank = decon_enable_vblank, |
581 | .disable_vblank = decon_disable_vblank, |
582 | .atomic_begin = decon_atomic_begin, |
583 | .update_plane = decon_update_plane, |
584 | .disable_plane = decon_disable_plane, |
585 | .atomic_flush = decon_atomic_flush, |
586 | }; |
587 | |
588 | |
589 | static irqreturn_t decon_irq_handler(int irq, void *dev_id) |
590 | { |
591 | struct decon_context *ctx = (struct decon_context *)dev_id; |
592 | u32 val, clear_bit; |
593 | |
594 | val = readl(addr: ctx->regs + VIDINTCON1); |
595 | |
596 | clear_bit = ctx->i80_if ? VIDINTCON1_INT_I80 : VIDINTCON1_INT_FRAME; |
597 | if (val & clear_bit) |
598 | writel(val: clear_bit, addr: ctx->regs + VIDINTCON1); |
599 | |
600 | /* check the crtc is detached already from encoder */ |
601 | if (!ctx->drm_dev) |
602 | goto out; |
603 | |
604 | if (!ctx->i80_if) { |
605 | drm_crtc_handle_vblank(crtc: &ctx->crtc->base); |
606 | |
607 | /* set wait vsync event to zero and wake up queue. */ |
608 | if (atomic_read(v: &ctx->wait_vsync_event)) { |
609 | atomic_set(v: &ctx->wait_vsync_event, i: 0); |
610 | wake_up(&ctx->wait_vsync_queue); |
611 | } |
612 | } |
613 | out: |
614 | return IRQ_HANDLED; |
615 | } |
616 | |
617 | static int decon_bind(struct device *dev, struct device *master, void *data) |
618 | { |
619 | struct decon_context *ctx = dev_get_drvdata(dev); |
620 | struct drm_device *drm_dev = data; |
621 | struct exynos_drm_plane *exynos_plane; |
622 | unsigned int i; |
623 | int ret; |
624 | |
625 | ret = decon_ctx_initialize(ctx, drm_dev); |
626 | if (ret) { |
627 | DRM_DEV_ERROR(dev, "decon_ctx_initialize failed.\n" ); |
628 | return ret; |
629 | } |
630 | |
631 | for (i = 0; i < WINDOWS_NR; i++) { |
632 | ctx->configs[i].pixel_formats = decon_formats; |
633 | ctx->configs[i].num_pixel_formats = ARRAY_SIZE(decon_formats); |
634 | ctx->configs[i].zpos = i; |
635 | ctx->configs[i].type = decon_win_types[i]; |
636 | |
637 | ret = exynos_plane_init(dev: drm_dev, exynos_plane: &ctx->planes[i], index: i, |
638 | config: &ctx->configs[i]); |
639 | if (ret) |
640 | return ret; |
641 | } |
642 | |
643 | exynos_plane = &ctx->planes[DEFAULT_WIN]; |
644 | ctx->crtc = exynos_drm_crtc_create(drm_dev, plane: &exynos_plane->base, |
645 | out_type: EXYNOS_DISPLAY_TYPE_LCD, ops: &decon_crtc_ops, context: ctx); |
646 | if (IS_ERR(ptr: ctx->crtc)) { |
647 | decon_ctx_remove(ctx); |
648 | return PTR_ERR(ptr: ctx->crtc); |
649 | } |
650 | |
651 | if (ctx->encoder) |
652 | exynos_dpi_bind(dev: drm_dev, encoder: ctx->encoder); |
653 | |
654 | return 0; |
655 | |
656 | } |
657 | |
658 | static void decon_unbind(struct device *dev, struct device *master, |
659 | void *data) |
660 | { |
661 | struct decon_context *ctx = dev_get_drvdata(dev); |
662 | |
663 | decon_atomic_disable(crtc: ctx->crtc); |
664 | |
665 | if (ctx->encoder) |
666 | exynos_dpi_remove(encoder: ctx->encoder); |
667 | |
668 | decon_ctx_remove(ctx); |
669 | } |
670 | |
671 | static const struct component_ops decon_component_ops = { |
672 | .bind = decon_bind, |
673 | .unbind = decon_unbind, |
674 | }; |
675 | |
676 | static int decon_probe(struct platform_device *pdev) |
677 | { |
678 | struct device *dev = &pdev->dev; |
679 | struct decon_context *ctx; |
680 | struct device_node *i80_if_timings; |
681 | int ret; |
682 | |
683 | if (!dev->of_node) |
684 | return -ENODEV; |
685 | |
686 | ctx = devm_kzalloc(dev, size: sizeof(*ctx), GFP_KERNEL); |
687 | if (!ctx) |
688 | return -ENOMEM; |
689 | |
690 | ctx->dev = dev; |
691 | ctx->suspended = true; |
692 | |
693 | i80_if_timings = of_get_child_by_name(node: dev->of_node, name: "i80-if-timings" ); |
694 | if (i80_if_timings) |
695 | ctx->i80_if = true; |
696 | of_node_put(node: i80_if_timings); |
697 | |
698 | ctx->regs = of_iomap(node: dev->of_node, index: 0); |
699 | if (!ctx->regs) |
700 | return -ENOMEM; |
701 | |
702 | ctx->pclk = devm_clk_get(dev, id: "pclk_decon0" ); |
703 | if (IS_ERR(ptr: ctx->pclk)) { |
704 | dev_err(dev, "failed to get bus clock pclk\n" ); |
705 | ret = PTR_ERR(ptr: ctx->pclk); |
706 | goto err_iounmap; |
707 | } |
708 | |
709 | ctx->aclk = devm_clk_get(dev, id: "aclk_decon0" ); |
710 | if (IS_ERR(ptr: ctx->aclk)) { |
711 | dev_err(dev, "failed to get bus clock aclk\n" ); |
712 | ret = PTR_ERR(ptr: ctx->aclk); |
713 | goto err_iounmap; |
714 | } |
715 | |
716 | ctx->eclk = devm_clk_get(dev, id: "decon0_eclk" ); |
717 | if (IS_ERR(ptr: ctx->eclk)) { |
718 | dev_err(dev, "failed to get eclock\n" ); |
719 | ret = PTR_ERR(ptr: ctx->eclk); |
720 | goto err_iounmap; |
721 | } |
722 | |
723 | ctx->vclk = devm_clk_get(dev, id: "decon0_vclk" ); |
724 | if (IS_ERR(ptr: ctx->vclk)) { |
725 | dev_err(dev, "failed to get vclock\n" ); |
726 | ret = PTR_ERR(ptr: ctx->vclk); |
727 | goto err_iounmap; |
728 | } |
729 | |
730 | ret = platform_get_irq_byname(pdev, ctx->i80_if ? "lcd_sys" : "vsync" ); |
731 | if (ret < 0) |
732 | goto err_iounmap; |
733 | |
734 | ret = devm_request_irq(dev, irq: ret, handler: decon_irq_handler, irqflags: 0, devname: "drm_decon" , dev_id: ctx); |
735 | if (ret) { |
736 | dev_err(dev, "irq request failed.\n" ); |
737 | goto err_iounmap; |
738 | } |
739 | |
740 | init_waitqueue_head(&ctx->wait_vsync_queue); |
741 | atomic_set(v: &ctx->wait_vsync_event, i: 0); |
742 | |
743 | platform_set_drvdata(pdev, data: ctx); |
744 | |
745 | ctx->encoder = exynos_dpi_probe(dev); |
746 | if (IS_ERR(ptr: ctx->encoder)) { |
747 | ret = PTR_ERR(ptr: ctx->encoder); |
748 | goto err_iounmap; |
749 | } |
750 | |
751 | pm_runtime_enable(dev); |
752 | |
753 | ret = component_add(dev, &decon_component_ops); |
754 | if (ret) |
755 | goto err_disable_pm_runtime; |
756 | |
757 | return ret; |
758 | |
759 | err_disable_pm_runtime: |
760 | pm_runtime_disable(dev); |
761 | |
762 | err_iounmap: |
763 | iounmap(addr: ctx->regs); |
764 | |
765 | return ret; |
766 | } |
767 | |
768 | static void decon_remove(struct platform_device *pdev) |
769 | { |
770 | struct decon_context *ctx = dev_get_drvdata(dev: &pdev->dev); |
771 | |
772 | pm_runtime_disable(dev: &pdev->dev); |
773 | |
774 | iounmap(addr: ctx->regs); |
775 | |
776 | component_del(&pdev->dev, &decon_component_ops); |
777 | } |
778 | |
779 | static int exynos7_decon_suspend(struct device *dev) |
780 | { |
781 | struct decon_context *ctx = dev_get_drvdata(dev); |
782 | |
783 | clk_disable_unprepare(clk: ctx->vclk); |
784 | clk_disable_unprepare(clk: ctx->eclk); |
785 | clk_disable_unprepare(clk: ctx->aclk); |
786 | clk_disable_unprepare(clk: ctx->pclk); |
787 | |
788 | return 0; |
789 | } |
790 | |
791 | static int exynos7_decon_resume(struct device *dev) |
792 | { |
793 | struct decon_context *ctx = dev_get_drvdata(dev); |
794 | int ret; |
795 | |
796 | ret = clk_prepare_enable(clk: ctx->pclk); |
797 | if (ret < 0) { |
798 | DRM_DEV_ERROR(dev, "Failed to prepare_enable the pclk [%d]\n" , |
799 | ret); |
800 | goto err_pclk_enable; |
801 | } |
802 | |
803 | ret = clk_prepare_enable(clk: ctx->aclk); |
804 | if (ret < 0) { |
805 | DRM_DEV_ERROR(dev, "Failed to prepare_enable the aclk [%d]\n" , |
806 | ret); |
807 | goto err_aclk_enable; |
808 | } |
809 | |
810 | ret = clk_prepare_enable(clk: ctx->eclk); |
811 | if (ret < 0) { |
812 | DRM_DEV_ERROR(dev, "Failed to prepare_enable the eclk [%d]\n" , |
813 | ret); |
814 | goto err_eclk_enable; |
815 | } |
816 | |
817 | ret = clk_prepare_enable(clk: ctx->vclk); |
818 | if (ret < 0) { |
819 | DRM_DEV_ERROR(dev, "Failed to prepare_enable the vclk [%d]\n" , |
820 | ret); |
821 | goto err_vclk_enable; |
822 | } |
823 | |
824 | return 0; |
825 | |
826 | err_vclk_enable: |
827 | clk_disable_unprepare(clk: ctx->eclk); |
828 | err_eclk_enable: |
829 | clk_disable_unprepare(clk: ctx->aclk); |
830 | err_aclk_enable: |
831 | clk_disable_unprepare(clk: ctx->pclk); |
832 | err_pclk_enable: |
833 | return ret; |
834 | } |
835 | |
836 | static DEFINE_RUNTIME_DEV_PM_OPS(exynos7_decon_pm_ops, exynos7_decon_suspend, |
837 | exynos7_decon_resume, NULL); |
838 | |
839 | struct platform_driver decon_driver = { |
840 | .probe = decon_probe, |
841 | .remove_new = decon_remove, |
842 | .driver = { |
843 | .name = "exynos-decon" , |
844 | .pm = pm_ptr(&exynos7_decon_pm_ops), |
845 | .of_match_table = decon_driver_dt_match, |
846 | }, |
847 | }; |
848 | |