1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* exynos_drm_fimd.c |
3 | * |
4 | * Copyright (C) 2011 Samsung Electronics Co.Ltd |
5 | * Authors: |
6 | * Joonyoung Shim <jy0922.shim@samsung.com> |
7 | * Inki Dae <inki.dae@samsung.com> |
8 | */ |
9 | |
10 | #include <linux/clk.h> |
11 | #include <linux/component.h> |
12 | #include <linux/kernel.h> |
13 | #include <linux/mfd/syscon.h> |
14 | #include <linux/of.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/pm_runtime.h> |
17 | #include <linux/regmap.h> |
18 | |
19 | #include <video/of_display_timing.h> |
20 | #include <video/of_videomode.h> |
21 | #include <video/samsung_fimd.h> |
22 | |
23 | #include <drm/drm_blend.h> |
24 | #include <drm/drm_fourcc.h> |
25 | #include <drm/drm_framebuffer.h> |
26 | #include <drm/drm_vblank.h> |
27 | #include <drm/exynos_drm.h> |
28 | |
29 | #include "exynos_drm_crtc.h" |
30 | #include "exynos_drm_drv.h" |
31 | #include "exynos_drm_fb.h" |
32 | #include "exynos_drm_plane.h" |
33 | |
34 | /* |
35 | * FIMD stands for Fully Interactive Mobile Display and |
36 | * as a display controller, it transfers contents drawn on memory |
37 | * to a LCD Panel through Display Interfaces such as RGB or |
38 | * CPU Interface. |
39 | */ |
40 | |
41 | #define MIN_FB_WIDTH_FOR_16WORD_BURST 128 |
42 | |
43 | /* position control register for hardware window 0, 2 ~ 4.*/ |
44 | #define VIDOSD_A(win) (VIDOSD_BASE + 0x00 + (win) * 16) |
45 | #define VIDOSD_B(win) (VIDOSD_BASE + 0x04 + (win) * 16) |
46 | /* |
47 | * size control register for hardware windows 0 and alpha control register |
48 | * for hardware windows 1 ~ 4 |
49 | */ |
50 | #define VIDOSD_C(win) (VIDOSD_BASE + 0x08 + (win) * 16) |
51 | /* size control register for hardware windows 1 ~ 2. */ |
52 | #define VIDOSD_D(win) (VIDOSD_BASE + 0x0C + (win) * 16) |
53 | |
54 | #define VIDWnALPHA0(win) (VIDW_ALPHA + 0x00 + (win) * 8) |
55 | #define VIDWnALPHA1(win) (VIDW_ALPHA + 0x04 + (win) * 8) |
56 | |
57 | #define VIDWx_BUF_START(win, buf) (VIDW_BUF_START(buf) + (win) * 8) |
58 | #define VIDWx_BUF_START_S(win, buf) (VIDW_BUF_START_S(buf) + (win) * 8) |
59 | #define VIDWx_BUF_END(win, buf) (VIDW_BUF_END(buf) + (win) * 8) |
60 | #define VIDWx_BUF_SIZE(win, buf) (VIDW_BUF_SIZE(buf) + (win) * 4) |
61 | |
62 | /* color key control register for hardware window 1 ~ 4. */ |
63 | #define WKEYCON0_BASE(x) ((WKEYCON0 + 0x140) + ((x - 1) * 8)) |
64 | /* color key value register for hardware window 1 ~ 4. */ |
65 | #define WKEYCON1_BASE(x) ((WKEYCON1 + 0x140) + ((x - 1) * 8)) |
66 | |
67 | /* I80 trigger control register */ |
68 | #define TRIGCON 0x1A4 |
69 | #define TRGMODE_ENABLE (1 << 0) |
70 | #define SWTRGCMD_ENABLE (1 << 1) |
71 | /* Exynos3250, 3472, 5260 5410, 5420 and 5422 only supported. */ |
72 | #define HWTRGEN_ENABLE (1 << 3) |
73 | #define HWTRGMASK_ENABLE (1 << 4) |
74 | /* Exynos3250, 3472, 5260, 5420 and 5422 only supported. */ |
75 | #define HWTRIGEN_PER_ENABLE (1 << 31) |
76 | |
77 | /* display mode change control register except exynos4 */ |
78 | #define VIDOUT_CON 0x000 |
79 | #define VIDOUT_CON_F_I80_LDI0 (0x2 << 8) |
80 | |
81 | /* I80 interface control for main LDI register */ |
82 | #define I80IFCONFAx(x) (0x1B0 + (x) * 4) |
83 | #define I80IFCONFBx(x) (0x1B8 + (x) * 4) |
84 | #define LCD_CS_SETUP(x) ((x) << 16) |
85 | #define LCD_WR_SETUP(x) ((x) << 12) |
86 | #define LCD_WR_ACTIVE(x) ((x) << 8) |
87 | #define LCD_WR_HOLD(x) ((x) << 4) |
88 | #define I80IFEN_ENABLE (1 << 0) |
89 | |
90 | /* FIMD has totally five hardware windows. */ |
91 | #define WINDOWS_NR 5 |
92 | |
93 | /* HW trigger flag on i80 panel. */ |
94 | #define I80_HW_TRG (1 << 1) |
95 | |
96 | struct fimd_driver_data { |
97 | unsigned int timing_base; |
98 | unsigned int lcdblk_offset; |
99 | unsigned int lcdblk_vt_shift; |
100 | unsigned int lcdblk_bypass_shift; |
101 | unsigned int lcdblk_mic_bypass_shift; |
102 | unsigned int trg_type; |
103 | |
104 | unsigned int has_shadowcon:1; |
105 | unsigned int has_clksel:1; |
106 | unsigned int has_limited_fmt:1; |
107 | unsigned int has_vidoutcon:1; |
108 | unsigned int has_vtsel:1; |
109 | unsigned int has_mic_bypass:1; |
110 | unsigned int has_dp_clk:1; |
111 | unsigned int has_hw_trigger:1; |
112 | unsigned int has_trigger_per_te:1; |
113 | unsigned int has_bgr_support:1; |
114 | }; |
115 | |
116 | static struct fimd_driver_data s3c64xx_fimd_driver_data = { |
117 | .timing_base = 0x0, |
118 | .has_clksel = 1, |
119 | .has_limited_fmt = 1, |
120 | }; |
121 | |
122 | static struct fimd_driver_data s5pv210_fimd_driver_data = { |
123 | .timing_base = 0x0, |
124 | .has_shadowcon = 1, |
125 | .has_clksel = 1, |
126 | }; |
127 | |
128 | static struct fimd_driver_data exynos3_fimd_driver_data = { |
129 | .timing_base = 0x20000, |
130 | .lcdblk_offset = 0x210, |
131 | .lcdblk_bypass_shift = 1, |
132 | .has_shadowcon = 1, |
133 | .has_vidoutcon = 1, |
134 | }; |
135 | |
136 | static struct fimd_driver_data exynos4_fimd_driver_data = { |
137 | .timing_base = 0x0, |
138 | .lcdblk_offset = 0x210, |
139 | .lcdblk_vt_shift = 10, |
140 | .lcdblk_bypass_shift = 1, |
141 | .has_shadowcon = 1, |
142 | .has_vtsel = 1, |
143 | .has_bgr_support = 1, |
144 | }; |
145 | |
146 | static struct fimd_driver_data exynos5_fimd_driver_data = { |
147 | .timing_base = 0x20000, |
148 | .lcdblk_offset = 0x214, |
149 | .lcdblk_vt_shift = 24, |
150 | .lcdblk_bypass_shift = 15, |
151 | .has_shadowcon = 1, |
152 | .has_vidoutcon = 1, |
153 | .has_vtsel = 1, |
154 | .has_dp_clk = 1, |
155 | .has_bgr_support = 1, |
156 | }; |
157 | |
158 | static struct fimd_driver_data exynos5420_fimd_driver_data = { |
159 | .timing_base = 0x20000, |
160 | .lcdblk_offset = 0x214, |
161 | .lcdblk_vt_shift = 24, |
162 | .lcdblk_bypass_shift = 15, |
163 | .lcdblk_mic_bypass_shift = 11, |
164 | .has_shadowcon = 1, |
165 | .has_vidoutcon = 1, |
166 | .has_vtsel = 1, |
167 | .has_mic_bypass = 1, |
168 | .has_dp_clk = 1, |
169 | .has_bgr_support = 1, |
170 | }; |
171 | |
172 | struct fimd_context { |
173 | struct device *dev; |
174 | struct drm_device *drm_dev; |
175 | void *dma_priv; |
176 | struct exynos_drm_crtc *crtc; |
177 | struct exynos_drm_plane planes[WINDOWS_NR]; |
178 | struct exynos_drm_plane_config configs[WINDOWS_NR]; |
179 | struct clk *bus_clk; |
180 | struct clk *lcd_clk; |
181 | void __iomem *regs; |
182 | struct regmap *sysreg; |
183 | unsigned long irq_flags; |
184 | u32 vidcon0; |
185 | u32 vidcon1; |
186 | u32 vidout_con; |
187 | u32 i80ifcon; |
188 | bool i80_if; |
189 | bool suspended; |
190 | wait_queue_head_t wait_vsync_queue; |
191 | atomic_t wait_vsync_event; |
192 | atomic_t win_updated; |
193 | atomic_t triggering; |
194 | u32 clkdiv; |
195 | |
196 | const struct fimd_driver_data *driver_data; |
197 | struct drm_encoder *encoder; |
198 | struct exynos_drm_clk dp_clk; |
199 | }; |
200 | |
201 | static const struct of_device_id fimd_driver_dt_match[] = { |
202 | { .compatible = "samsung,s3c6400-fimd" , |
203 | .data = &s3c64xx_fimd_driver_data }, |
204 | { .compatible = "samsung,s5pv210-fimd" , |
205 | .data = &s5pv210_fimd_driver_data }, |
206 | { .compatible = "samsung,exynos3250-fimd" , |
207 | .data = &exynos3_fimd_driver_data }, |
208 | { .compatible = "samsung,exynos4210-fimd" , |
209 | .data = &exynos4_fimd_driver_data }, |
210 | { .compatible = "samsung,exynos5250-fimd" , |
211 | .data = &exynos5_fimd_driver_data }, |
212 | { .compatible = "samsung,exynos5420-fimd" , |
213 | .data = &exynos5420_fimd_driver_data }, |
214 | {}, |
215 | }; |
216 | MODULE_DEVICE_TABLE(of, fimd_driver_dt_match); |
217 | |
218 | static const enum drm_plane_type fimd_win_types[WINDOWS_NR] = { |
219 | DRM_PLANE_TYPE_PRIMARY, |
220 | DRM_PLANE_TYPE_OVERLAY, |
221 | DRM_PLANE_TYPE_OVERLAY, |
222 | DRM_PLANE_TYPE_OVERLAY, |
223 | DRM_PLANE_TYPE_CURSOR, |
224 | }; |
225 | |
226 | static const uint32_t fimd_formats[] = { |
227 | DRM_FORMAT_C8, |
228 | DRM_FORMAT_XRGB1555, |
229 | DRM_FORMAT_RGB565, |
230 | DRM_FORMAT_XRGB8888, |
231 | DRM_FORMAT_ARGB8888, |
232 | }; |
233 | |
234 | static const uint32_t fimd_extended_formats[] = { |
235 | DRM_FORMAT_C8, |
236 | DRM_FORMAT_XRGB1555, |
237 | DRM_FORMAT_XBGR1555, |
238 | DRM_FORMAT_RGB565, |
239 | DRM_FORMAT_BGR565, |
240 | DRM_FORMAT_XRGB8888, |
241 | DRM_FORMAT_XBGR8888, |
242 | DRM_FORMAT_ARGB8888, |
243 | DRM_FORMAT_ABGR8888, |
244 | }; |
245 | |
246 | static const unsigned int capabilities[WINDOWS_NR] = { |
247 | 0, |
248 | EXYNOS_DRM_PLANE_CAP_WIN_BLEND | EXYNOS_DRM_PLANE_CAP_PIX_BLEND, |
249 | EXYNOS_DRM_PLANE_CAP_WIN_BLEND | EXYNOS_DRM_PLANE_CAP_PIX_BLEND, |
250 | EXYNOS_DRM_PLANE_CAP_WIN_BLEND | EXYNOS_DRM_PLANE_CAP_PIX_BLEND, |
251 | EXYNOS_DRM_PLANE_CAP_WIN_BLEND | EXYNOS_DRM_PLANE_CAP_PIX_BLEND, |
252 | }; |
253 | |
254 | static inline void fimd_set_bits(struct fimd_context *ctx, u32 reg, u32 mask, |
255 | u32 val) |
256 | { |
257 | val = (val & mask) | (readl(addr: ctx->regs + reg) & ~mask); |
258 | writel(val, addr: ctx->regs + reg); |
259 | } |
260 | |
261 | static int fimd_enable_vblank(struct exynos_drm_crtc *crtc) |
262 | { |
263 | struct fimd_context *ctx = crtc->ctx; |
264 | u32 val; |
265 | |
266 | if (ctx->suspended) |
267 | return -EPERM; |
268 | |
269 | if (!test_and_set_bit(nr: 0, addr: &ctx->irq_flags)) { |
270 | val = readl(addr: ctx->regs + VIDINTCON0); |
271 | |
272 | val |= VIDINTCON0_INT_ENABLE; |
273 | |
274 | if (ctx->i80_if) { |
275 | val |= VIDINTCON0_INT_I80IFDONE; |
276 | val |= VIDINTCON0_INT_SYSMAINCON; |
277 | val &= ~VIDINTCON0_INT_SYSSUBCON; |
278 | } else { |
279 | val |= VIDINTCON0_INT_FRAME; |
280 | |
281 | val &= ~VIDINTCON0_FRAMESEL0_MASK; |
282 | val |= VIDINTCON0_FRAMESEL0_FRONTPORCH; |
283 | val &= ~VIDINTCON0_FRAMESEL1_MASK; |
284 | val |= VIDINTCON0_FRAMESEL1_NONE; |
285 | } |
286 | |
287 | writel(val, addr: ctx->regs + VIDINTCON0); |
288 | } |
289 | |
290 | return 0; |
291 | } |
292 | |
293 | static void fimd_disable_vblank(struct exynos_drm_crtc *crtc) |
294 | { |
295 | struct fimd_context *ctx = crtc->ctx; |
296 | u32 val; |
297 | |
298 | if (ctx->suspended) |
299 | return; |
300 | |
301 | if (test_and_clear_bit(nr: 0, addr: &ctx->irq_flags)) { |
302 | val = readl(addr: ctx->regs + VIDINTCON0); |
303 | |
304 | val &= ~VIDINTCON0_INT_ENABLE; |
305 | |
306 | if (ctx->i80_if) { |
307 | val &= ~VIDINTCON0_INT_I80IFDONE; |
308 | val &= ~VIDINTCON0_INT_SYSMAINCON; |
309 | val &= ~VIDINTCON0_INT_SYSSUBCON; |
310 | } else |
311 | val &= ~VIDINTCON0_INT_FRAME; |
312 | |
313 | writel(val, addr: ctx->regs + VIDINTCON0); |
314 | } |
315 | } |
316 | |
317 | static void fimd_wait_for_vblank(struct exynos_drm_crtc *crtc) |
318 | { |
319 | struct fimd_context *ctx = crtc->ctx; |
320 | |
321 | if (ctx->suspended) |
322 | return; |
323 | |
324 | atomic_set(v: &ctx->wait_vsync_event, i: 1); |
325 | |
326 | /* |
327 | * wait for FIMD to signal VSYNC interrupt or return after |
328 | * timeout which is set to 50ms (refresh rate of 20). |
329 | */ |
330 | if (!wait_event_timeout(ctx->wait_vsync_queue, |
331 | !atomic_read(&ctx->wait_vsync_event), |
332 | HZ/20)) |
333 | DRM_DEV_DEBUG_KMS(ctx->dev, "vblank wait timed out.\n" ); |
334 | } |
335 | |
336 | static void fimd_enable_video_output(struct fimd_context *ctx, unsigned int win, |
337 | bool enable) |
338 | { |
339 | u32 val = readl(addr: ctx->regs + WINCON(win)); |
340 | |
341 | if (enable) |
342 | val |= WINCONx_ENWIN; |
343 | else |
344 | val &= ~WINCONx_ENWIN; |
345 | |
346 | writel(val, addr: ctx->regs + WINCON(win)); |
347 | } |
348 | |
349 | static void fimd_enable_shadow_channel_path(struct fimd_context *ctx, |
350 | unsigned int win, |
351 | bool enable) |
352 | { |
353 | u32 val = readl(addr: ctx->regs + SHADOWCON); |
354 | |
355 | if (enable) |
356 | val |= SHADOWCON_CHx_ENABLE(win); |
357 | else |
358 | val &= ~SHADOWCON_CHx_ENABLE(win); |
359 | |
360 | writel(val, addr: ctx->regs + SHADOWCON); |
361 | } |
362 | |
363 | static int fimd_clear_channels(struct exynos_drm_crtc *crtc) |
364 | { |
365 | struct fimd_context *ctx = crtc->ctx; |
366 | unsigned int win, ch_enabled = 0; |
367 | int ret; |
368 | |
369 | /* Hardware is in unknown state, so ensure it gets enabled properly */ |
370 | ret = pm_runtime_resume_and_get(dev: ctx->dev); |
371 | if (ret < 0) { |
372 | dev_err(ctx->dev, "failed to enable FIMD device.\n" ); |
373 | return ret; |
374 | } |
375 | |
376 | clk_prepare_enable(clk: ctx->bus_clk); |
377 | clk_prepare_enable(clk: ctx->lcd_clk); |
378 | |
379 | /* Check if any channel is enabled. */ |
380 | for (win = 0; win < WINDOWS_NR; win++) { |
381 | u32 val = readl(addr: ctx->regs + WINCON(win)); |
382 | |
383 | if (val & WINCONx_ENWIN) { |
384 | fimd_enable_video_output(ctx, win, enable: false); |
385 | |
386 | if (ctx->driver_data->has_shadowcon) |
387 | fimd_enable_shadow_channel_path(ctx, win, |
388 | enable: false); |
389 | |
390 | ch_enabled = 1; |
391 | } |
392 | } |
393 | |
394 | /* Wait for vsync, as disable channel takes effect at next vsync */ |
395 | if (ch_enabled) { |
396 | ctx->suspended = false; |
397 | |
398 | fimd_enable_vblank(crtc: ctx->crtc); |
399 | fimd_wait_for_vblank(crtc: ctx->crtc); |
400 | fimd_disable_vblank(crtc: ctx->crtc); |
401 | |
402 | ctx->suspended = true; |
403 | } |
404 | |
405 | clk_disable_unprepare(clk: ctx->lcd_clk); |
406 | clk_disable_unprepare(clk: ctx->bus_clk); |
407 | |
408 | pm_runtime_put(dev: ctx->dev); |
409 | |
410 | return 0; |
411 | } |
412 | |
413 | |
414 | static int fimd_atomic_check(struct exynos_drm_crtc *crtc, |
415 | struct drm_crtc_state *state) |
416 | { |
417 | struct drm_display_mode *mode = &state->adjusted_mode; |
418 | struct fimd_context *ctx = crtc->ctx; |
419 | unsigned long ideal_clk, lcd_rate; |
420 | u32 clkdiv; |
421 | |
422 | if (mode->clock == 0) { |
423 | DRM_DEV_ERROR(ctx->dev, "Mode has zero clock value.\n" ); |
424 | return -EINVAL; |
425 | } |
426 | |
427 | ideal_clk = mode->clock * 1000; |
428 | |
429 | if (ctx->i80_if) { |
430 | /* |
431 | * The frame done interrupt should be occurred prior to the |
432 | * next TE signal. |
433 | */ |
434 | ideal_clk *= 2; |
435 | } |
436 | |
437 | lcd_rate = clk_get_rate(clk: ctx->lcd_clk); |
438 | if (2 * lcd_rate < ideal_clk) { |
439 | DRM_DEV_ERROR(ctx->dev, |
440 | "sclk_fimd clock too low(%lu) for requested pixel clock(%lu)\n" , |
441 | lcd_rate, ideal_clk); |
442 | return -EINVAL; |
443 | } |
444 | |
445 | /* Find the clock divider value that gets us closest to ideal_clk */ |
446 | clkdiv = DIV_ROUND_CLOSEST(lcd_rate, ideal_clk); |
447 | if (clkdiv >= 0x200) { |
448 | DRM_DEV_ERROR(ctx->dev, "requested pixel clock(%lu) too low\n" , |
449 | ideal_clk); |
450 | return -EINVAL; |
451 | } |
452 | |
453 | ctx->clkdiv = (clkdiv < 0x100) ? clkdiv : 0xff; |
454 | |
455 | return 0; |
456 | } |
457 | |
458 | static void fimd_setup_trigger(struct fimd_context *ctx) |
459 | { |
460 | void __iomem *timing_base = ctx->regs + ctx->driver_data->timing_base; |
461 | u32 trg_type = ctx->driver_data->trg_type; |
462 | u32 val = readl(addr: timing_base + TRIGCON); |
463 | |
464 | val &= ~(TRGMODE_ENABLE); |
465 | |
466 | if (trg_type == I80_HW_TRG) { |
467 | if (ctx->driver_data->has_hw_trigger) |
468 | val |= HWTRGEN_ENABLE | HWTRGMASK_ENABLE; |
469 | if (ctx->driver_data->has_trigger_per_te) |
470 | val |= HWTRIGEN_PER_ENABLE; |
471 | } else { |
472 | val |= TRGMODE_ENABLE; |
473 | } |
474 | |
475 | writel(val, addr: timing_base + TRIGCON); |
476 | } |
477 | |
478 | static void fimd_commit(struct exynos_drm_crtc *crtc) |
479 | { |
480 | struct fimd_context *ctx = crtc->ctx; |
481 | struct drm_display_mode *mode = &crtc->base.state->adjusted_mode; |
482 | const struct fimd_driver_data *driver_data = ctx->driver_data; |
483 | void __iomem *timing_base = ctx->regs + driver_data->timing_base; |
484 | u32 val; |
485 | |
486 | if (ctx->suspended) |
487 | return; |
488 | |
489 | /* nothing to do if we haven't set the mode yet */ |
490 | if (mode->htotal == 0 || mode->vtotal == 0) |
491 | return; |
492 | |
493 | if (ctx->i80_if) { |
494 | val = ctx->i80ifcon | I80IFEN_ENABLE; |
495 | writel(val, addr: timing_base + I80IFCONFAx(0)); |
496 | |
497 | /* disable auto frame rate */ |
498 | writel(val: 0, addr: timing_base + I80IFCONFBx(0)); |
499 | |
500 | /* set video type selection to I80 interface */ |
501 | if (driver_data->has_vtsel && ctx->sysreg && |
502 | regmap_update_bits(map: ctx->sysreg, |
503 | reg: driver_data->lcdblk_offset, |
504 | mask: 0x3 << driver_data->lcdblk_vt_shift, |
505 | val: 0x1 << driver_data->lcdblk_vt_shift)) { |
506 | DRM_DEV_ERROR(ctx->dev, |
507 | "Failed to update sysreg for I80 i/f.\n" ); |
508 | return; |
509 | } |
510 | } else { |
511 | int vsync_len, vbpd, vfpd, hsync_len, hbpd, hfpd; |
512 | u32 vidcon1; |
513 | |
514 | /* setup polarity values */ |
515 | vidcon1 = ctx->vidcon1; |
516 | if (mode->flags & DRM_MODE_FLAG_NVSYNC) |
517 | vidcon1 |= VIDCON1_INV_VSYNC; |
518 | if (mode->flags & DRM_MODE_FLAG_NHSYNC) |
519 | vidcon1 |= VIDCON1_INV_HSYNC; |
520 | writel(val: vidcon1, addr: ctx->regs + driver_data->timing_base + VIDCON1); |
521 | |
522 | /* setup vertical timing values. */ |
523 | vsync_len = mode->crtc_vsync_end - mode->crtc_vsync_start; |
524 | vbpd = mode->crtc_vtotal - mode->crtc_vsync_end; |
525 | vfpd = mode->crtc_vsync_start - mode->crtc_vdisplay; |
526 | |
527 | val = VIDTCON0_VBPD(vbpd - 1) | |
528 | VIDTCON0_VFPD(vfpd - 1) | |
529 | VIDTCON0_VSPW(vsync_len - 1); |
530 | writel(val, addr: ctx->regs + driver_data->timing_base + VIDTCON0); |
531 | |
532 | /* setup horizontal timing values. */ |
533 | hsync_len = mode->crtc_hsync_end - mode->crtc_hsync_start; |
534 | hbpd = mode->crtc_htotal - mode->crtc_hsync_end; |
535 | hfpd = mode->crtc_hsync_start - mode->crtc_hdisplay; |
536 | |
537 | val = VIDTCON1_HBPD(hbpd - 1) | |
538 | VIDTCON1_HFPD(hfpd - 1) | |
539 | VIDTCON1_HSPW(hsync_len - 1); |
540 | writel(val, addr: ctx->regs + driver_data->timing_base + VIDTCON1); |
541 | } |
542 | |
543 | if (driver_data->has_vidoutcon) |
544 | writel(val: ctx->vidout_con, addr: timing_base + VIDOUT_CON); |
545 | |
546 | /* set bypass selection */ |
547 | if (ctx->sysreg && regmap_update_bits(map: ctx->sysreg, |
548 | reg: driver_data->lcdblk_offset, |
549 | mask: 0x1 << driver_data->lcdblk_bypass_shift, |
550 | val: 0x1 << driver_data->lcdblk_bypass_shift)) { |
551 | DRM_DEV_ERROR(ctx->dev, |
552 | "Failed to update sysreg for bypass setting.\n" ); |
553 | return; |
554 | } |
555 | |
556 | /* TODO: When MIC is enabled for display path, the lcdblk_mic_bypass |
557 | * bit should be cleared. |
558 | */ |
559 | if (driver_data->has_mic_bypass && ctx->sysreg && |
560 | regmap_update_bits(map: ctx->sysreg, |
561 | reg: driver_data->lcdblk_offset, |
562 | mask: 0x1 << driver_data->lcdblk_mic_bypass_shift, |
563 | val: 0x1 << driver_data->lcdblk_mic_bypass_shift)) { |
564 | DRM_DEV_ERROR(ctx->dev, |
565 | "Failed to update sysreg for bypass mic.\n" ); |
566 | return; |
567 | } |
568 | |
569 | /* setup horizontal and vertical display size. */ |
570 | val = VIDTCON2_LINEVAL(mode->vdisplay - 1) | |
571 | VIDTCON2_HOZVAL(mode->hdisplay - 1) | |
572 | VIDTCON2_LINEVAL_E(mode->vdisplay - 1) | |
573 | VIDTCON2_HOZVAL_E(mode->hdisplay - 1); |
574 | writel(val, addr: ctx->regs + driver_data->timing_base + VIDTCON2); |
575 | |
576 | fimd_setup_trigger(ctx); |
577 | |
578 | /* |
579 | * fields of register with prefix '_F' would be updated |
580 | * at vsync(same as dma start) |
581 | */ |
582 | val = ctx->vidcon0; |
583 | val |= VIDCON0_ENVID | VIDCON0_ENVID_F; |
584 | |
585 | if (ctx->driver_data->has_clksel) |
586 | val |= VIDCON0_CLKSEL_LCD; |
587 | |
588 | if (ctx->clkdiv > 1) |
589 | val |= VIDCON0_CLKVAL_F(ctx->clkdiv - 1) | VIDCON0_CLKDIR; |
590 | |
591 | writel(val, addr: ctx->regs + VIDCON0); |
592 | } |
593 | |
594 | static void fimd_win_set_bldeq(struct fimd_context *ctx, unsigned int win, |
595 | unsigned int alpha, unsigned int pixel_alpha) |
596 | { |
597 | u32 mask = BLENDEQ_A_FUNC_F(0xf) | BLENDEQ_B_FUNC_F(0xf); |
598 | u32 val = 0; |
599 | |
600 | switch (pixel_alpha) { |
601 | case DRM_MODE_BLEND_PIXEL_NONE: |
602 | case DRM_MODE_BLEND_COVERAGE: |
603 | val |= BLENDEQ_A_FUNC_F(BLENDEQ_ALPHA_A); |
604 | val |= BLENDEQ_B_FUNC_F(BLENDEQ_ONE_MINUS_ALPHA_A); |
605 | break; |
606 | case DRM_MODE_BLEND_PREMULTI: |
607 | default: |
608 | if (alpha != DRM_BLEND_ALPHA_OPAQUE) { |
609 | val |= BLENDEQ_A_FUNC_F(BLENDEQ_ALPHA0); |
610 | val |= BLENDEQ_B_FUNC_F(BLENDEQ_ONE_MINUS_ALPHA_A); |
611 | } else { |
612 | val |= BLENDEQ_A_FUNC_F(BLENDEQ_ONE); |
613 | val |= BLENDEQ_B_FUNC_F(BLENDEQ_ONE_MINUS_ALPHA_A); |
614 | } |
615 | break; |
616 | } |
617 | fimd_set_bits(ctx, BLENDEQx(win), mask, val); |
618 | } |
619 | |
620 | static void fimd_win_set_bldmod(struct fimd_context *ctx, unsigned int win, |
621 | unsigned int alpha, unsigned int pixel_alpha) |
622 | { |
623 | u32 win_alpha_l = (alpha >> 8) & 0xf; |
624 | u32 win_alpha_h = alpha >> 12; |
625 | u32 val = 0; |
626 | |
627 | switch (pixel_alpha) { |
628 | case DRM_MODE_BLEND_PIXEL_NONE: |
629 | break; |
630 | case DRM_MODE_BLEND_COVERAGE: |
631 | case DRM_MODE_BLEND_PREMULTI: |
632 | default: |
633 | val |= WINCON1_ALPHA_SEL; |
634 | val |= WINCON1_BLD_PIX; |
635 | val |= WINCON1_ALPHA_MUL; |
636 | break; |
637 | } |
638 | fimd_set_bits(ctx, WINCON(win), WINCONx_BLEND_MODE_MASK, val); |
639 | |
640 | /* OSD alpha */ |
641 | val = VIDISD14C_ALPHA0_R(win_alpha_h) | |
642 | VIDISD14C_ALPHA0_G(win_alpha_h) | |
643 | VIDISD14C_ALPHA0_B(win_alpha_h) | |
644 | VIDISD14C_ALPHA1_R(0x0) | |
645 | VIDISD14C_ALPHA1_G(0x0) | |
646 | VIDISD14C_ALPHA1_B(0x0); |
647 | writel(val, addr: ctx->regs + VIDOSD_C(win)); |
648 | |
649 | val = VIDW_ALPHA_R(win_alpha_l) | VIDW_ALPHA_G(win_alpha_l) | |
650 | VIDW_ALPHA_B(win_alpha_l); |
651 | writel(val, addr: ctx->regs + VIDWnALPHA0(win)); |
652 | |
653 | val = VIDW_ALPHA_R(0x0) | VIDW_ALPHA_G(0x0) | |
654 | VIDW_ALPHA_B(0x0); |
655 | writel(val, addr: ctx->regs + VIDWnALPHA1(win)); |
656 | |
657 | fimd_set_bits(ctx, BLENDCON, BLENDCON_NEW_MASK, |
658 | BLENDCON_NEW_8BIT_ALPHA_VALUE); |
659 | } |
660 | |
661 | static void fimd_win_set_pixfmt(struct fimd_context *ctx, unsigned int win, |
662 | struct drm_framebuffer *fb, int width) |
663 | { |
664 | struct exynos_drm_plane *plane = &ctx->planes[win]; |
665 | struct exynos_drm_plane_state *state = |
666 | to_exynos_plane_state(state: plane->base.state); |
667 | uint32_t pixel_format = fb->format->format; |
668 | unsigned int alpha = state->base.alpha; |
669 | u32 val = WINCONx_ENWIN; |
670 | unsigned int pixel_alpha; |
671 | |
672 | if (fb->format->has_alpha) |
673 | pixel_alpha = state->base.pixel_blend_mode; |
674 | else |
675 | pixel_alpha = DRM_MODE_BLEND_PIXEL_NONE; |
676 | |
677 | /* |
678 | * In case of s3c64xx, window 0 doesn't support alpha channel. |
679 | * So the request format is ARGB8888 then change it to XRGB8888. |
680 | */ |
681 | if (ctx->driver_data->has_limited_fmt && !win) { |
682 | if (pixel_format == DRM_FORMAT_ARGB8888) |
683 | pixel_format = DRM_FORMAT_XRGB8888; |
684 | } |
685 | |
686 | switch (pixel_format) { |
687 | case DRM_FORMAT_C8: |
688 | val |= WINCON0_BPPMODE_8BPP_PALETTE; |
689 | val |= WINCONx_BURSTLEN_8WORD; |
690 | val |= WINCONx_BYTSWP; |
691 | break; |
692 | case DRM_FORMAT_XRGB1555: |
693 | case DRM_FORMAT_XBGR1555: |
694 | val |= WINCON0_BPPMODE_16BPP_1555; |
695 | val |= WINCONx_HAWSWP; |
696 | val |= WINCONx_BURSTLEN_16WORD; |
697 | break; |
698 | case DRM_FORMAT_RGB565: |
699 | case DRM_FORMAT_BGR565: |
700 | val |= WINCON0_BPPMODE_16BPP_565; |
701 | val |= WINCONx_HAWSWP; |
702 | val |= WINCONx_BURSTLEN_16WORD; |
703 | break; |
704 | case DRM_FORMAT_XRGB8888: |
705 | case DRM_FORMAT_XBGR8888: |
706 | val |= WINCON0_BPPMODE_24BPP_888; |
707 | val |= WINCONx_WSWP; |
708 | val |= WINCONx_BURSTLEN_16WORD; |
709 | break; |
710 | case DRM_FORMAT_ARGB8888: |
711 | case DRM_FORMAT_ABGR8888: |
712 | default: |
713 | val |= WINCON1_BPPMODE_25BPP_A1888; |
714 | val |= WINCONx_WSWP; |
715 | val |= WINCONx_BURSTLEN_16WORD; |
716 | break; |
717 | } |
718 | |
719 | switch (pixel_format) { |
720 | case DRM_FORMAT_XBGR1555: |
721 | case DRM_FORMAT_XBGR8888: |
722 | case DRM_FORMAT_ABGR8888: |
723 | case DRM_FORMAT_BGR565: |
724 | writel(WIN_RGB_ORDER_REVERSE, addr: ctx->regs + WIN_RGB_ORDER(win)); |
725 | break; |
726 | default: |
727 | writel(WIN_RGB_ORDER_FORWARD, addr: ctx->regs + WIN_RGB_ORDER(win)); |
728 | break; |
729 | } |
730 | |
731 | /* |
732 | * Setting dma-burst to 16Word causes permanent tearing for very small |
733 | * buffers, e.g. cursor buffer. Burst Mode switching which based on |
734 | * plane size is not recommended as plane size varies alot towards the |
735 | * end of the screen and rapid movement causes unstable DMA, but it is |
736 | * still better to change dma-burst than displaying garbage. |
737 | */ |
738 | |
739 | if (width < MIN_FB_WIDTH_FOR_16WORD_BURST) { |
740 | val &= ~WINCONx_BURSTLEN_MASK; |
741 | val |= WINCONx_BURSTLEN_4WORD; |
742 | } |
743 | fimd_set_bits(ctx, WINCON(win), mask: ~WINCONx_BLEND_MODE_MASK, val); |
744 | |
745 | /* hardware window 0 doesn't support alpha channel. */ |
746 | if (win != 0) { |
747 | fimd_win_set_bldmod(ctx, win, alpha, pixel_alpha); |
748 | fimd_win_set_bldeq(ctx, win, alpha, pixel_alpha); |
749 | } |
750 | } |
751 | |
752 | static void fimd_win_set_colkey(struct fimd_context *ctx, unsigned int win) |
753 | { |
754 | unsigned int keycon0 = 0, keycon1 = 0; |
755 | |
756 | keycon0 = ~(WxKEYCON0_KEYBL_EN | WxKEYCON0_KEYEN_F | |
757 | WxKEYCON0_DIRCON) | WxKEYCON0_COMPKEY(0); |
758 | |
759 | keycon1 = WxKEYCON1_COLVAL(0xffffffff); |
760 | |
761 | writel(val: keycon0, addr: ctx->regs + WKEYCON0_BASE(win)); |
762 | writel(val: keycon1, addr: ctx->regs + WKEYCON1_BASE(win)); |
763 | } |
764 | |
765 | /** |
766 | * fimd_shadow_protect_win() - disable updating values from shadow registers at vsync |
767 | * |
768 | * @ctx: local driver data |
769 | * @win: window to protect registers for |
770 | * @protect: 1 to protect (disable updates) |
771 | */ |
772 | static void fimd_shadow_protect_win(struct fimd_context *ctx, |
773 | unsigned int win, bool protect) |
774 | { |
775 | u32 reg, bits, val; |
776 | |
777 | /* |
778 | * SHADOWCON/PRTCON register is used for enabling timing. |
779 | * |
780 | * for example, once only width value of a register is set, |
781 | * if the dma is started then fimd hardware could malfunction so |
782 | * with protect window setting, the register fields with prefix '_F' |
783 | * wouldn't be updated at vsync also but updated once unprotect window |
784 | * is set. |
785 | */ |
786 | |
787 | if (ctx->driver_data->has_shadowcon) { |
788 | reg = SHADOWCON; |
789 | bits = SHADOWCON_WINx_PROTECT(win); |
790 | } else { |
791 | reg = PRTCON; |
792 | bits = PRTCON_PROTECT; |
793 | } |
794 | |
795 | val = readl(addr: ctx->regs + reg); |
796 | if (protect) |
797 | val |= bits; |
798 | else |
799 | val &= ~bits; |
800 | writel(val, addr: ctx->regs + reg); |
801 | } |
802 | |
803 | static void fimd_atomic_begin(struct exynos_drm_crtc *crtc) |
804 | { |
805 | struct fimd_context *ctx = crtc->ctx; |
806 | int i; |
807 | |
808 | if (ctx->suspended) |
809 | return; |
810 | |
811 | for (i = 0; i < WINDOWS_NR; i++) |
812 | fimd_shadow_protect_win(ctx, win: i, protect: true); |
813 | } |
814 | |
815 | static void fimd_atomic_flush(struct exynos_drm_crtc *crtc) |
816 | { |
817 | struct fimd_context *ctx = crtc->ctx; |
818 | int i; |
819 | |
820 | if (ctx->suspended) |
821 | return; |
822 | |
823 | for (i = 0; i < WINDOWS_NR; i++) |
824 | fimd_shadow_protect_win(ctx, win: i, protect: false); |
825 | |
826 | exynos_crtc_handle_event(exynos_crtc: crtc); |
827 | } |
828 | |
829 | static void fimd_update_plane(struct exynos_drm_crtc *crtc, |
830 | struct exynos_drm_plane *plane) |
831 | { |
832 | struct exynos_drm_plane_state *state = |
833 | to_exynos_plane_state(state: plane->base.state); |
834 | struct fimd_context *ctx = crtc->ctx; |
835 | struct drm_framebuffer *fb = state->base.fb; |
836 | dma_addr_t dma_addr; |
837 | unsigned long val, size, offset; |
838 | unsigned int last_x, last_y, buf_offsize, line_size; |
839 | unsigned int win = plane->index; |
840 | unsigned int cpp = fb->format->cpp[0]; |
841 | unsigned int pitch = fb->pitches[0]; |
842 | |
843 | if (ctx->suspended) |
844 | return; |
845 | |
846 | offset = state->src.x * cpp; |
847 | offset += state->src.y * pitch; |
848 | |
849 | /* buffer start address */ |
850 | dma_addr = exynos_drm_fb_dma_addr(fb, index: 0) + offset; |
851 | val = (unsigned long)dma_addr; |
852 | writel(val, addr: ctx->regs + VIDWx_BUF_START(win, 0)); |
853 | |
854 | /* buffer end address */ |
855 | size = pitch * state->crtc.h; |
856 | val = (unsigned long)(dma_addr + size); |
857 | writel(val, addr: ctx->regs + VIDWx_BUF_END(win, 0)); |
858 | |
859 | DRM_DEV_DEBUG_KMS(ctx->dev, |
860 | "start addr = 0x%lx, end addr = 0x%lx, size = 0x%lx\n" , |
861 | (unsigned long)dma_addr, val, size); |
862 | DRM_DEV_DEBUG_KMS(ctx->dev, "ovl_width = %d, ovl_height = %d\n" , |
863 | state->crtc.w, state->crtc.h); |
864 | |
865 | /* buffer size */ |
866 | buf_offsize = pitch - (state->crtc.w * cpp); |
867 | line_size = state->crtc.w * cpp; |
868 | val = VIDW_BUF_SIZE_OFFSET(buf_offsize) | |
869 | VIDW_BUF_SIZE_PAGEWIDTH(line_size) | |
870 | VIDW_BUF_SIZE_OFFSET_E(buf_offsize) | |
871 | VIDW_BUF_SIZE_PAGEWIDTH_E(line_size); |
872 | writel(val, addr: ctx->regs + VIDWx_BUF_SIZE(win, 0)); |
873 | |
874 | /* OSD position */ |
875 | val = VIDOSDxA_TOPLEFT_X(state->crtc.x) | |
876 | VIDOSDxA_TOPLEFT_Y(state->crtc.y) | |
877 | VIDOSDxA_TOPLEFT_X_E(state->crtc.x) | |
878 | VIDOSDxA_TOPLEFT_Y_E(state->crtc.y); |
879 | writel(val, addr: ctx->regs + VIDOSD_A(win)); |
880 | |
881 | last_x = state->crtc.x + state->crtc.w; |
882 | if (last_x) |
883 | last_x--; |
884 | last_y = state->crtc.y + state->crtc.h; |
885 | if (last_y) |
886 | last_y--; |
887 | |
888 | val = VIDOSDxB_BOTRIGHT_X(last_x) | VIDOSDxB_BOTRIGHT_Y(last_y) | |
889 | VIDOSDxB_BOTRIGHT_X_E(last_x) | VIDOSDxB_BOTRIGHT_Y_E(last_y); |
890 | |
891 | writel(val, addr: ctx->regs + VIDOSD_B(win)); |
892 | |
893 | DRM_DEV_DEBUG_KMS(ctx->dev, |
894 | "osd pos: tx = %d, ty = %d, bx = %d, by = %d\n" , |
895 | state->crtc.x, state->crtc.y, last_x, last_y); |
896 | |
897 | /* OSD size */ |
898 | if (win != 3 && win != 4) { |
899 | u32 offset = VIDOSD_D(win); |
900 | if (win == 0) |
901 | offset = VIDOSD_C(win); |
902 | val = state->crtc.w * state->crtc.h; |
903 | writel(val, addr: ctx->regs + offset); |
904 | |
905 | DRM_DEV_DEBUG_KMS(ctx->dev, "osd size = 0x%x\n" , |
906 | (unsigned int)val); |
907 | } |
908 | |
909 | fimd_win_set_pixfmt(ctx, win, fb, width: state->src.w); |
910 | |
911 | /* hardware window 0 doesn't support color key. */ |
912 | if (win != 0) |
913 | fimd_win_set_colkey(ctx, win); |
914 | |
915 | fimd_enable_video_output(ctx, win, enable: true); |
916 | |
917 | if (ctx->driver_data->has_shadowcon) |
918 | fimd_enable_shadow_channel_path(ctx, win, enable: true); |
919 | |
920 | if (ctx->i80_if) |
921 | atomic_set(v: &ctx->win_updated, i: 1); |
922 | } |
923 | |
924 | static void fimd_disable_plane(struct exynos_drm_crtc *crtc, |
925 | struct exynos_drm_plane *plane) |
926 | { |
927 | struct fimd_context *ctx = crtc->ctx; |
928 | unsigned int win = plane->index; |
929 | |
930 | if (ctx->suspended) |
931 | return; |
932 | |
933 | fimd_enable_video_output(ctx, win, enable: false); |
934 | |
935 | if (ctx->driver_data->has_shadowcon) |
936 | fimd_enable_shadow_channel_path(ctx, win, enable: false); |
937 | } |
938 | |
939 | static void fimd_atomic_enable(struct exynos_drm_crtc *crtc) |
940 | { |
941 | struct fimd_context *ctx = crtc->ctx; |
942 | |
943 | if (!ctx->suspended) |
944 | return; |
945 | |
946 | ctx->suspended = false; |
947 | |
948 | if (pm_runtime_resume_and_get(dev: ctx->dev) < 0) { |
949 | dev_warn(ctx->dev, "failed to enable FIMD device.\n" ); |
950 | return; |
951 | } |
952 | |
953 | /* if vblank was enabled status, enable it again. */ |
954 | if (test_and_clear_bit(nr: 0, addr: &ctx->irq_flags)) |
955 | fimd_enable_vblank(crtc: ctx->crtc); |
956 | |
957 | fimd_commit(crtc: ctx->crtc); |
958 | } |
959 | |
960 | static void fimd_atomic_disable(struct exynos_drm_crtc *crtc) |
961 | { |
962 | struct fimd_context *ctx = crtc->ctx; |
963 | int i; |
964 | |
965 | if (ctx->suspended) |
966 | return; |
967 | |
968 | /* |
969 | * We need to make sure that all windows are disabled before we |
970 | * suspend that connector. Otherwise we might try to scan from |
971 | * a destroyed buffer later. |
972 | */ |
973 | for (i = 0; i < WINDOWS_NR; i++) |
974 | fimd_disable_plane(crtc, plane: &ctx->planes[i]); |
975 | |
976 | fimd_enable_vblank(crtc); |
977 | fimd_wait_for_vblank(crtc); |
978 | fimd_disable_vblank(crtc); |
979 | |
980 | writel(val: 0, addr: ctx->regs + VIDCON0); |
981 | |
982 | pm_runtime_put_sync(dev: ctx->dev); |
983 | ctx->suspended = true; |
984 | } |
985 | |
986 | static void fimd_trigger(struct device *dev) |
987 | { |
988 | struct fimd_context *ctx = dev_get_drvdata(dev); |
989 | const struct fimd_driver_data *driver_data = ctx->driver_data; |
990 | void *timing_base = ctx->regs + driver_data->timing_base; |
991 | u32 reg; |
992 | |
993 | /* |
994 | * Skips triggering if in triggering state, because multiple triggering |
995 | * requests can cause panel reset. |
996 | */ |
997 | if (atomic_read(v: &ctx->triggering)) |
998 | return; |
999 | |
1000 | /* Enters triggering mode */ |
1001 | atomic_set(v: &ctx->triggering, i: 1); |
1002 | |
1003 | reg = readl(addr: timing_base + TRIGCON); |
1004 | reg |= (TRGMODE_ENABLE | SWTRGCMD_ENABLE); |
1005 | writel(val: reg, addr: timing_base + TRIGCON); |
1006 | |
1007 | /* |
1008 | * Exits triggering mode if vblank is not enabled yet, because when the |
1009 | * VIDINTCON0 register is not set, it can not exit from triggering mode. |
1010 | */ |
1011 | if (!test_bit(0, &ctx->irq_flags)) |
1012 | atomic_set(v: &ctx->triggering, i: 0); |
1013 | } |
1014 | |
1015 | static void fimd_te_handler(struct exynos_drm_crtc *crtc) |
1016 | { |
1017 | struct fimd_context *ctx = crtc->ctx; |
1018 | u32 trg_type = ctx->driver_data->trg_type; |
1019 | |
1020 | /* Checks the crtc is detached already from encoder */ |
1021 | if (!ctx->drm_dev) |
1022 | return; |
1023 | |
1024 | if (trg_type == I80_HW_TRG) |
1025 | goto out; |
1026 | |
1027 | /* |
1028 | * If there is a page flip request, triggers and handles the page flip |
1029 | * event so that current fb can be updated into panel GRAM. |
1030 | */ |
1031 | if (atomic_add_unless(v: &ctx->win_updated, a: -1, u: 0)) |
1032 | fimd_trigger(dev: ctx->dev); |
1033 | |
1034 | out: |
1035 | /* Wakes up vsync event queue */ |
1036 | if (atomic_read(v: &ctx->wait_vsync_event)) { |
1037 | atomic_set(v: &ctx->wait_vsync_event, i: 0); |
1038 | wake_up(&ctx->wait_vsync_queue); |
1039 | } |
1040 | |
1041 | if (test_bit(0, &ctx->irq_flags)) |
1042 | drm_crtc_handle_vblank(crtc: &ctx->crtc->base); |
1043 | } |
1044 | |
1045 | static void fimd_dp_clock_enable(struct exynos_drm_clk *clk, bool enable) |
1046 | { |
1047 | struct fimd_context *ctx = container_of(clk, struct fimd_context, |
1048 | dp_clk); |
1049 | u32 val = enable ? DP_MIE_CLK_DP_ENABLE : DP_MIE_CLK_DISABLE; |
1050 | writel(val, addr: ctx->regs + DP_MIE_CLKCON); |
1051 | } |
1052 | |
1053 | static const struct exynos_drm_crtc_ops fimd_crtc_ops = { |
1054 | .atomic_enable = fimd_atomic_enable, |
1055 | .atomic_disable = fimd_atomic_disable, |
1056 | .enable_vblank = fimd_enable_vblank, |
1057 | .disable_vblank = fimd_disable_vblank, |
1058 | .atomic_begin = fimd_atomic_begin, |
1059 | .update_plane = fimd_update_plane, |
1060 | .disable_plane = fimd_disable_plane, |
1061 | .atomic_flush = fimd_atomic_flush, |
1062 | .atomic_check = fimd_atomic_check, |
1063 | .te_handler = fimd_te_handler, |
1064 | }; |
1065 | |
1066 | static irqreturn_t fimd_irq_handler(int irq, void *dev_id) |
1067 | { |
1068 | struct fimd_context *ctx = (struct fimd_context *)dev_id; |
1069 | u32 val, clear_bit; |
1070 | |
1071 | val = readl(addr: ctx->regs + VIDINTCON1); |
1072 | |
1073 | clear_bit = ctx->i80_if ? VIDINTCON1_INT_I80 : VIDINTCON1_INT_FRAME; |
1074 | if (val & clear_bit) |
1075 | writel(val: clear_bit, addr: ctx->regs + VIDINTCON1); |
1076 | |
1077 | /* check the crtc is detached already from encoder */ |
1078 | if (!ctx->drm_dev) |
1079 | goto out; |
1080 | |
1081 | if (!ctx->i80_if) |
1082 | drm_crtc_handle_vblank(crtc: &ctx->crtc->base); |
1083 | |
1084 | if (ctx->i80_if) { |
1085 | /* Exits triggering mode */ |
1086 | atomic_set(v: &ctx->triggering, i: 0); |
1087 | } else { |
1088 | /* set wait vsync event to zero and wake up queue. */ |
1089 | if (atomic_read(v: &ctx->wait_vsync_event)) { |
1090 | atomic_set(v: &ctx->wait_vsync_event, i: 0); |
1091 | wake_up(&ctx->wait_vsync_queue); |
1092 | } |
1093 | } |
1094 | |
1095 | out: |
1096 | return IRQ_HANDLED; |
1097 | } |
1098 | |
1099 | static int fimd_bind(struct device *dev, struct device *master, void *data) |
1100 | { |
1101 | struct fimd_context *ctx = dev_get_drvdata(dev); |
1102 | struct drm_device *drm_dev = data; |
1103 | struct exynos_drm_plane *exynos_plane; |
1104 | unsigned int i; |
1105 | int ret; |
1106 | |
1107 | ctx->drm_dev = drm_dev; |
1108 | |
1109 | for (i = 0; i < WINDOWS_NR; i++) { |
1110 | if (ctx->driver_data->has_bgr_support) { |
1111 | ctx->configs[i].pixel_formats = fimd_extended_formats; |
1112 | ctx->configs[i].num_pixel_formats = ARRAY_SIZE(fimd_extended_formats); |
1113 | } else { |
1114 | ctx->configs[i].pixel_formats = fimd_formats; |
1115 | ctx->configs[i].num_pixel_formats = ARRAY_SIZE(fimd_formats); |
1116 | } |
1117 | |
1118 | ctx->configs[i].zpos = i; |
1119 | ctx->configs[i].type = fimd_win_types[i]; |
1120 | ctx->configs[i].capabilities = capabilities[i]; |
1121 | ret = exynos_plane_init(dev: drm_dev, exynos_plane: &ctx->planes[i], index: i, |
1122 | config: &ctx->configs[i]); |
1123 | if (ret) |
1124 | return ret; |
1125 | } |
1126 | |
1127 | exynos_plane = &ctx->planes[DEFAULT_WIN]; |
1128 | ctx->crtc = exynos_drm_crtc_create(drm_dev, plane: &exynos_plane->base, |
1129 | out_type: EXYNOS_DISPLAY_TYPE_LCD, ops: &fimd_crtc_ops, context: ctx); |
1130 | if (IS_ERR(ptr: ctx->crtc)) |
1131 | return PTR_ERR(ptr: ctx->crtc); |
1132 | |
1133 | if (ctx->driver_data->has_dp_clk) { |
1134 | ctx->dp_clk.enable = fimd_dp_clock_enable; |
1135 | ctx->crtc->pipe_clk = &ctx->dp_clk; |
1136 | } |
1137 | |
1138 | if (ctx->encoder) |
1139 | exynos_dpi_bind(dev: drm_dev, encoder: ctx->encoder); |
1140 | |
1141 | if (is_drm_iommu_supported(drm_dev)) { |
1142 | int ret; |
1143 | |
1144 | ret = fimd_clear_channels(crtc: ctx->crtc); |
1145 | if (ret < 0) |
1146 | return ret; |
1147 | } |
1148 | |
1149 | return exynos_drm_register_dma(drm: drm_dev, dev, dma_priv: &ctx->dma_priv); |
1150 | } |
1151 | |
1152 | static void fimd_unbind(struct device *dev, struct device *master, |
1153 | void *data) |
1154 | { |
1155 | struct fimd_context *ctx = dev_get_drvdata(dev); |
1156 | |
1157 | fimd_atomic_disable(crtc: ctx->crtc); |
1158 | |
1159 | exynos_drm_unregister_dma(drm: ctx->drm_dev, dev: ctx->dev, dma_priv: &ctx->dma_priv); |
1160 | |
1161 | if (ctx->encoder) |
1162 | exynos_dpi_remove(encoder: ctx->encoder); |
1163 | } |
1164 | |
1165 | static const struct component_ops fimd_component_ops = { |
1166 | .bind = fimd_bind, |
1167 | .unbind = fimd_unbind, |
1168 | }; |
1169 | |
1170 | static int fimd_probe(struct platform_device *pdev) |
1171 | { |
1172 | struct device *dev = &pdev->dev; |
1173 | struct fimd_context *ctx; |
1174 | struct device_node *i80_if_timings; |
1175 | int ret; |
1176 | |
1177 | if (!dev->of_node) |
1178 | return -ENODEV; |
1179 | |
1180 | ctx = devm_kzalloc(dev, size: sizeof(*ctx), GFP_KERNEL); |
1181 | if (!ctx) |
1182 | return -ENOMEM; |
1183 | |
1184 | ctx->dev = dev; |
1185 | ctx->suspended = true; |
1186 | ctx->driver_data = of_device_get_match_data(dev); |
1187 | |
1188 | if (of_property_read_bool(np: dev->of_node, propname: "samsung,invert-vden" )) |
1189 | ctx->vidcon1 |= VIDCON1_INV_VDEN; |
1190 | if (of_property_read_bool(np: dev->of_node, propname: "samsung,invert-vclk" )) |
1191 | ctx->vidcon1 |= VIDCON1_INV_VCLK; |
1192 | |
1193 | i80_if_timings = of_get_child_by_name(node: dev->of_node, name: "i80-if-timings" ); |
1194 | if (i80_if_timings) { |
1195 | u32 val; |
1196 | |
1197 | ctx->i80_if = true; |
1198 | |
1199 | if (ctx->driver_data->has_vidoutcon) |
1200 | ctx->vidout_con |= VIDOUT_CON_F_I80_LDI0; |
1201 | else |
1202 | ctx->vidcon0 |= VIDCON0_VIDOUT_I80_LDI0; |
1203 | /* |
1204 | * The user manual describes that this "DSI_EN" bit is required |
1205 | * to enable I80 24-bit data interface. |
1206 | */ |
1207 | ctx->vidcon0 |= VIDCON0_DSI_EN; |
1208 | |
1209 | if (of_property_read_u32(np: i80_if_timings, propname: "cs-setup" , out_value: &val)) |
1210 | val = 0; |
1211 | ctx->i80ifcon = LCD_CS_SETUP(val); |
1212 | if (of_property_read_u32(np: i80_if_timings, propname: "wr-setup" , out_value: &val)) |
1213 | val = 0; |
1214 | ctx->i80ifcon |= LCD_WR_SETUP(val); |
1215 | if (of_property_read_u32(np: i80_if_timings, propname: "wr-active" , out_value: &val)) |
1216 | val = 1; |
1217 | ctx->i80ifcon |= LCD_WR_ACTIVE(val); |
1218 | if (of_property_read_u32(np: i80_if_timings, propname: "wr-hold" , out_value: &val)) |
1219 | val = 0; |
1220 | ctx->i80ifcon |= LCD_WR_HOLD(val); |
1221 | } |
1222 | of_node_put(node: i80_if_timings); |
1223 | |
1224 | ctx->sysreg = syscon_regmap_lookup_by_phandle(np: dev->of_node, |
1225 | property: "samsung,sysreg" ); |
1226 | if (IS_ERR(ptr: ctx->sysreg)) { |
1227 | dev_warn(dev, "failed to get system register.\n" ); |
1228 | ctx->sysreg = NULL; |
1229 | } |
1230 | |
1231 | ctx->bus_clk = devm_clk_get(dev, id: "fimd" ); |
1232 | if (IS_ERR(ptr: ctx->bus_clk)) { |
1233 | dev_err(dev, "failed to get bus clock\n" ); |
1234 | return PTR_ERR(ptr: ctx->bus_clk); |
1235 | } |
1236 | |
1237 | ctx->lcd_clk = devm_clk_get(dev, id: "sclk_fimd" ); |
1238 | if (IS_ERR(ptr: ctx->lcd_clk)) { |
1239 | dev_err(dev, "failed to get lcd clock\n" ); |
1240 | return PTR_ERR(ptr: ctx->lcd_clk); |
1241 | } |
1242 | |
1243 | ctx->regs = devm_platform_ioremap_resource(pdev, index: 0); |
1244 | if (IS_ERR(ptr: ctx->regs)) |
1245 | return PTR_ERR(ptr: ctx->regs); |
1246 | |
1247 | ret = platform_get_irq_byname(pdev, ctx->i80_if ? "lcd_sys" : "vsync" ); |
1248 | if (ret < 0) |
1249 | return ret; |
1250 | |
1251 | ret = devm_request_irq(dev, irq: ret, handler: fimd_irq_handler, irqflags: 0, devname: "drm_fimd" , dev_id: ctx); |
1252 | if (ret) { |
1253 | dev_err(dev, "irq request failed.\n" ); |
1254 | return ret; |
1255 | } |
1256 | |
1257 | init_waitqueue_head(&ctx->wait_vsync_queue); |
1258 | atomic_set(v: &ctx->wait_vsync_event, i: 0); |
1259 | |
1260 | platform_set_drvdata(pdev, data: ctx); |
1261 | |
1262 | ctx->encoder = exynos_dpi_probe(dev); |
1263 | if (IS_ERR(ptr: ctx->encoder)) |
1264 | return PTR_ERR(ptr: ctx->encoder); |
1265 | |
1266 | pm_runtime_enable(dev); |
1267 | |
1268 | ret = component_add(dev, &fimd_component_ops); |
1269 | if (ret) |
1270 | goto err_disable_pm_runtime; |
1271 | |
1272 | return ret; |
1273 | |
1274 | err_disable_pm_runtime: |
1275 | pm_runtime_disable(dev); |
1276 | |
1277 | return ret; |
1278 | } |
1279 | |
1280 | static void fimd_remove(struct platform_device *pdev) |
1281 | { |
1282 | pm_runtime_disable(dev: &pdev->dev); |
1283 | |
1284 | component_del(&pdev->dev, &fimd_component_ops); |
1285 | } |
1286 | |
1287 | static int exynos_fimd_suspend(struct device *dev) |
1288 | { |
1289 | struct fimd_context *ctx = dev_get_drvdata(dev); |
1290 | |
1291 | clk_disable_unprepare(clk: ctx->lcd_clk); |
1292 | clk_disable_unprepare(clk: ctx->bus_clk); |
1293 | |
1294 | return 0; |
1295 | } |
1296 | |
1297 | static int exynos_fimd_resume(struct device *dev) |
1298 | { |
1299 | struct fimd_context *ctx = dev_get_drvdata(dev); |
1300 | int ret; |
1301 | |
1302 | ret = clk_prepare_enable(clk: ctx->bus_clk); |
1303 | if (ret < 0) { |
1304 | DRM_DEV_ERROR(dev, |
1305 | "Failed to prepare_enable the bus clk [%d]\n" , |
1306 | ret); |
1307 | return ret; |
1308 | } |
1309 | |
1310 | ret = clk_prepare_enable(clk: ctx->lcd_clk); |
1311 | if (ret < 0) { |
1312 | DRM_DEV_ERROR(dev, |
1313 | "Failed to prepare_enable the lcd clk [%d]\n" , |
1314 | ret); |
1315 | return ret; |
1316 | } |
1317 | |
1318 | return 0; |
1319 | } |
1320 | |
1321 | static DEFINE_RUNTIME_DEV_PM_OPS(exynos_fimd_pm_ops, exynos_fimd_suspend, |
1322 | exynos_fimd_resume, NULL); |
1323 | |
1324 | struct platform_driver fimd_driver = { |
1325 | .probe = fimd_probe, |
1326 | .remove_new = fimd_remove, |
1327 | .driver = { |
1328 | .name = "exynos4-fb" , |
1329 | .owner = THIS_MODULE, |
1330 | .pm = pm_ptr(&exynos_fimd_pm_ops), |
1331 | .of_match_table = fimd_driver_dt_match, |
1332 | }, |
1333 | }; |
1334 | |