Commit | Line | Data |
---|---|---|
1c248b7d ID |
1 | /* exynos_drm_crtc.c |
2 | * | |
3 | * Copyright (c) 2011 Samsung Electronics Co., Ltd. | |
4 | * Authors: | |
5 | * Inki Dae <inki.dae@samsung.com> | |
6 | * Joonyoung Shim <jy0922.shim@samsung.com> | |
7 | * Seung-Woo Kim <sw0312.kim@samsung.com> | |
8 | * | |
9 | * Permission is hereby granted, free of charge, to any person obtaining a | |
10 | * copy of this software and associated documentation files (the "Software"), | |
11 | * to deal in the Software without restriction, including without limitation | |
12 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
13 | * and/or sell copies of the Software, and to permit persons to whom the | |
14 | * Software is furnished to do so, subject to the following conditions: | |
15 | * | |
16 | * The above copyright notice and this permission notice (including the next | |
17 | * paragraph) shall be included in all copies or substantial portions of the | |
18 | * Software. | |
19 | * | |
20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
23 | * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR | |
24 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, | |
25 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | |
26 | * OTHER DEALINGS IN THE SOFTWARE. | |
27 | */ | |
28 | ||
29 | #include "drmP.h" | |
30 | #include "drm_crtc_helper.h" | |
31 | ||
2c871127 | 32 | #include "exynos_drm_crtc.h" |
1c248b7d ID |
33 | #include "exynos_drm_drv.h" |
34 | #include "exynos_drm_fb.h" | |
35 | #include "exynos_drm_encoder.h" | |
2c871127 | 36 | #include "exynos_drm_gem.h" |
1c248b7d ID |
37 | |
38 | #define to_exynos_crtc(x) container_of(x, struct exynos_drm_crtc,\ | |
39 | drm_crtc) | |
40 | ||
1c248b7d ID |
41 | /* |
42 | * Exynos specific crtc structure. | |
43 | * | |
44 | * @drm_crtc: crtc object. | |
45 | * @overlay: contain information common to display controller and hdmi and | |
46 | * contents of this overlay object would be copied to sub driver size. | |
47 | * @pipe: a crtc index created at load() with a new crtc object creation | |
48 | * and the crtc object would be set to private->crtc array | |
49 | * to get a crtc object corresponding to this pipe from private->crtc | |
50 | * array when irq interrupt occured. the reason of using this pipe is that | |
51 | * drm framework doesn't support multiple irq yet. | |
52 | * we can refer to the crtc to current hardware interrupt occured through | |
53 | * this pipe value. | |
ec05da95 | 54 | * @dpms: store the crtc dpms value |
1c248b7d ID |
55 | */ |
56 | struct exynos_drm_crtc { | |
57 | struct drm_crtc drm_crtc; | |
58 | struct exynos_drm_overlay overlay; | |
59 | unsigned int pipe; | |
ec05da95 | 60 | unsigned int dpms; |
1c248b7d ID |
61 | }; |
62 | ||
8e9cc6a1 | 63 | static void exynos_drm_crtc_apply(struct drm_crtc *crtc) |
1c248b7d ID |
64 | { |
65 | struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc); | |
66 | struct exynos_drm_overlay *overlay = &exynos_crtc->overlay; | |
67 | ||
68 | exynos_drm_fn_encoder(crtc, overlay, | |
69 | exynos_drm_encoder_crtc_mode_set); | |
d2716c89 JS |
70 | exynos_drm_fn_encoder(crtc, &exynos_crtc->pipe, |
71 | exynos_drm_encoder_crtc_commit); | |
1c248b7d ID |
72 | } |
73 | ||
2c871127 ID |
74 | int exynos_drm_overlay_update(struct exynos_drm_overlay *overlay, |
75 | struct drm_framebuffer *fb, | |
76 | struct drm_display_mode *mode, | |
77 | struct exynos_drm_crtc_pos *pos) | |
1c248b7d | 78 | { |
2c871127 | 79 | struct exynos_drm_gem_buf *buffer; |
19c8b834 ID |
80 | unsigned int actual_w; |
81 | unsigned int actual_h; | |
229d3534 SWK |
82 | int nr = exynos_drm_format_num_buffers(fb->pixel_format); |
83 | int i; | |
84 | ||
85 | for (i = 0; i < nr; i++) { | |
86 | buffer = exynos_drm_fb_buffer(fb, i); | |
87 | if (!buffer) { | |
88 | DRM_LOG_KMS("buffer is null\n"); | |
89 | return -EFAULT; | |
90 | } | |
91 | ||
92 | overlay->dma_addr[i] = buffer->dma_addr; | |
93 | overlay->vaddr[i] = buffer->kvaddr; | |
94 | ||
95 | DRM_DEBUG_KMS("buffer: %d, vaddr = 0x%lx, dma_addr = 0x%lx\n", | |
96 | i, (unsigned long)overlay->vaddr[i], | |
97 | (unsigned long)overlay->dma_addr[i]); | |
19c8b834 ID |
98 | } |
99 | ||
19c8b834 ID |
100 | actual_w = min((mode->hdisplay - pos->crtc_x), pos->crtc_w); |
101 | actual_h = min((mode->vdisplay - pos->crtc_y), pos->crtc_h); | |
102 | ||
103 | /* set drm framebuffer data. */ | |
104 | overlay->fb_x = pos->fb_x; | |
105 | overlay->fb_y = pos->fb_y; | |
106 | overlay->fb_width = fb->width; | |
107 | overlay->fb_height = fb->height; | |
1c248b7d | 108 | overlay->bpp = fb->bits_per_pixel; |
01f2c773 | 109 | overlay->pitch = fb->pitches[0]; |
229d3534 | 110 | overlay->pixel_format = fb->pixel_format; |
19c8b834 ID |
111 | |
112 | /* set overlay range to be displayed. */ | |
113 | overlay->crtc_x = pos->crtc_x; | |
114 | overlay->crtc_y = pos->crtc_y; | |
115 | overlay->crtc_width = actual_w; | |
116 | overlay->crtc_height = actual_h; | |
117 | ||
118 | /* set drm mode data. */ | |
119 | overlay->mode_width = mode->hdisplay; | |
120 | overlay->mode_height = mode->vdisplay; | |
121 | overlay->refresh = mode->vrefresh; | |
122 | overlay->scan_flag = mode->flags; | |
1c248b7d ID |
123 | |
124 | DRM_DEBUG_KMS("overlay : offset_x/y(%d,%d), width/height(%d,%d)", | |
19c8b834 ID |
125 | overlay->crtc_x, overlay->crtc_y, |
126 | overlay->crtc_width, overlay->crtc_height); | |
1c248b7d | 127 | |
19c8b834 | 128 | return 0; |
1c248b7d ID |
129 | } |
130 | ||
131 | static int exynos_drm_crtc_update(struct drm_crtc *crtc) | |
132 | { | |
133 | struct exynos_drm_crtc *exynos_crtc; | |
134 | struct exynos_drm_overlay *overlay; | |
135 | struct exynos_drm_crtc_pos pos; | |
136 | struct drm_display_mode *mode = &crtc->mode; | |
137 | struct drm_framebuffer *fb = crtc->fb; | |
138 | ||
139 | if (!mode || !fb) | |
140 | return -EINVAL; | |
141 | ||
142 | exynos_crtc = to_exynos_crtc(crtc); | |
143 | overlay = &exynos_crtc->overlay; | |
144 | ||
145 | memset(&pos, 0, sizeof(struct exynos_drm_crtc_pos)); | |
19c8b834 ID |
146 | |
147 | /* it means the offset of framebuffer to be displayed. */ | |
1c248b7d ID |
148 | pos.fb_x = crtc->x; |
149 | pos.fb_y = crtc->y; | |
19c8b834 ID |
150 | |
151 | /* OSD position to be displayed. */ | |
152 | pos.crtc_x = 0; | |
153 | pos.crtc_y = 0; | |
1c248b7d ID |
154 | pos.crtc_w = fb->width - crtc->x; |
155 | pos.crtc_h = fb->height - crtc->y; | |
156 | ||
19c8b834 | 157 | return exynos_drm_overlay_update(overlay, crtc->fb, mode, &pos); |
1c248b7d ID |
158 | } |
159 | ||
160 | static void exynos_drm_crtc_dpms(struct drm_crtc *crtc, int mode) | |
161 | { | |
ec05da95 | 162 | struct drm_device *dev = crtc->dev; |
d2716c89 | 163 | struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc); |
1c248b7d | 164 | |
d2716c89 JS |
165 | DRM_DEBUG_KMS("crtc[%d] mode[%d]\n", crtc->base.id, mode); |
166 | ||
ec05da95 ID |
167 | if (exynos_crtc->dpms == mode) { |
168 | DRM_DEBUG_KMS("desired dpms mode is same as previous one.\n"); | |
169 | return; | |
170 | } | |
171 | ||
172 | mutex_lock(&dev->struct_mutex); | |
173 | ||
d2716c89 JS |
174 | switch (mode) { |
175 | case DRM_MODE_DPMS_ON: | |
ec05da95 ID |
176 | exynos_drm_fn_encoder(crtc, &mode, |
177 | exynos_drm_encoder_crtc_dpms); | |
178 | exynos_crtc->dpms = mode; | |
d2716c89 JS |
179 | break; |
180 | case DRM_MODE_DPMS_STANDBY: | |
181 | case DRM_MODE_DPMS_SUSPEND: | |
182 | case DRM_MODE_DPMS_OFF: | |
ec05da95 ID |
183 | exynos_drm_fn_encoder(crtc, &mode, |
184 | exynos_drm_encoder_crtc_dpms); | |
185 | exynos_crtc->dpms = mode; | |
d2716c89 JS |
186 | break; |
187 | default: | |
ec05da95 | 188 | DRM_ERROR("unspecified mode %d\n", mode); |
d2716c89 JS |
189 | break; |
190 | } | |
ec05da95 ID |
191 | |
192 | mutex_unlock(&dev->struct_mutex); | |
1c248b7d ID |
193 | } |
194 | ||
195 | static void exynos_drm_crtc_prepare(struct drm_crtc *crtc) | |
196 | { | |
197 | DRM_DEBUG_KMS("%s\n", __FILE__); | |
198 | ||
199 | /* drm framework doesn't check NULL. */ | |
200 | } | |
201 | ||
202 | static void exynos_drm_crtc_commit(struct drm_crtc *crtc) | |
203 | { | |
d2716c89 JS |
204 | struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc); |
205 | ||
1c248b7d ID |
206 | DRM_DEBUG_KMS("%s\n", __FILE__); |
207 | ||
ec05da95 ID |
208 | /* |
209 | * when set_crtc is requested from user or at booting time, | |
210 | * crtc->commit would be called without dpms call so if dpms is | |
211 | * no power on then crtc->dpms should be called | |
212 | * with DRM_MODE_DPMS_ON for the hardware power to be on. | |
213 | */ | |
214 | if (exynos_crtc->dpms != DRM_MODE_DPMS_ON) { | |
215 | int mode = DRM_MODE_DPMS_ON; | |
216 | ||
217 | /* | |
218 | * enable hardware(power on) to all encoders hdmi connected | |
219 | * to current crtc. | |
220 | */ | |
221 | exynos_drm_crtc_dpms(crtc, mode); | |
222 | /* | |
223 | * enable dma to all encoders connected to current crtc and | |
224 | * lcd panel. | |
225 | */ | |
226 | exynos_drm_fn_encoder(crtc, &mode, | |
227 | exynos_drm_encoder_dpms_from_crtc); | |
228 | } | |
229 | ||
d2716c89 JS |
230 | exynos_drm_fn_encoder(crtc, &exynos_crtc->pipe, |
231 | exynos_drm_encoder_crtc_commit); | |
1c248b7d ID |
232 | } |
233 | ||
234 | static bool | |
235 | exynos_drm_crtc_mode_fixup(struct drm_crtc *crtc, | |
236 | struct drm_display_mode *mode, | |
237 | struct drm_display_mode *adjusted_mode) | |
238 | { | |
239 | DRM_DEBUG_KMS("%s\n", __FILE__); | |
240 | ||
241 | /* drm framework doesn't check NULL */ | |
242 | return true; | |
243 | } | |
244 | ||
245 | static int | |
246 | exynos_drm_crtc_mode_set(struct drm_crtc *crtc, struct drm_display_mode *mode, | |
247 | struct drm_display_mode *adjusted_mode, int x, int y, | |
248 | struct drm_framebuffer *old_fb) | |
249 | { | |
250 | DRM_DEBUG_KMS("%s\n", __FILE__); | |
251 | ||
1de425b0 ID |
252 | /* |
253 | * copy the mode data adjusted by mode_fixup() into crtc->mode | |
254 | * so that hardware can be seet to proper mode. | |
255 | */ | |
256 | memcpy(&crtc->mode, adjusted_mode, sizeof(*adjusted_mode)); | |
1c248b7d ID |
257 | |
258 | return exynos_drm_crtc_update(crtc); | |
259 | } | |
260 | ||
261 | static int exynos_drm_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y, | |
262 | struct drm_framebuffer *old_fb) | |
263 | { | |
264 | int ret; | |
265 | ||
266 | DRM_DEBUG_KMS("%s\n", __FILE__); | |
267 | ||
268 | ret = exynos_drm_crtc_update(crtc); | |
269 | if (ret) | |
270 | return ret; | |
271 | ||
272 | exynos_drm_crtc_apply(crtc); | |
273 | ||
274 | return ret; | |
275 | } | |
276 | ||
277 | static void exynos_drm_crtc_load_lut(struct drm_crtc *crtc) | |
278 | { | |
279 | DRM_DEBUG_KMS("%s\n", __FILE__); | |
280 | /* drm framework doesn't check NULL */ | |
281 | } | |
282 | ||
283 | static struct drm_crtc_helper_funcs exynos_crtc_helper_funcs = { | |
284 | .dpms = exynos_drm_crtc_dpms, | |
285 | .prepare = exynos_drm_crtc_prepare, | |
286 | .commit = exynos_drm_crtc_commit, | |
287 | .mode_fixup = exynos_drm_crtc_mode_fixup, | |
288 | .mode_set = exynos_drm_crtc_mode_set, | |
289 | .mode_set_base = exynos_drm_crtc_mode_set_base, | |
290 | .load_lut = exynos_drm_crtc_load_lut, | |
291 | }; | |
292 | ||
293 | static int exynos_drm_crtc_page_flip(struct drm_crtc *crtc, | |
294 | struct drm_framebuffer *fb, | |
295 | struct drm_pending_vblank_event *event) | |
296 | { | |
297 | struct drm_device *dev = crtc->dev; | |
298 | struct exynos_drm_private *dev_priv = dev->dev_private; | |
299 | struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc); | |
300 | struct drm_framebuffer *old_fb = crtc->fb; | |
301 | int ret = -EINVAL; | |
302 | ||
303 | DRM_DEBUG_KMS("%s\n", __FILE__); | |
304 | ||
305 | mutex_lock(&dev->struct_mutex); | |
306 | ||
ccf4d883 ID |
307 | if (event) { |
308 | /* | |
309 | * the pipe from user always is 0 so we can set pipe number | |
310 | * of current owner to event. | |
311 | */ | |
312 | event->pipe = exynos_crtc->pipe; | |
313 | ||
1c248b7d ID |
314 | ret = drm_vblank_get(dev, exynos_crtc->pipe); |
315 | if (ret) { | |
316 | DRM_DEBUG("failed to acquire vblank counter\n"); | |
ccf4d883 ID |
317 | list_del(&event->base.link); |
318 | ||
1c248b7d ID |
319 | goto out; |
320 | } | |
321 | ||
c5614ae3 ID |
322 | list_add_tail(&event->base.link, |
323 | &dev_priv->pageflip_event_list); | |
324 | ||
1c248b7d ID |
325 | crtc->fb = fb; |
326 | ret = exynos_drm_crtc_update(crtc); | |
327 | if (ret) { | |
328 | crtc->fb = old_fb; | |
329 | drm_vblank_put(dev, exynos_crtc->pipe); | |
ccf4d883 | 330 | list_del(&event->base.link); |
1c248b7d ID |
331 | |
332 | goto out; | |
333 | } | |
334 | ||
f6b98252 ID |
335 | /* |
336 | * the values related to a buffer of the drm framebuffer | |
337 | * to be applied should be set at here. because these values | |
ccf4d883 | 338 | * first, are set to shadow registers and then to |
f6b98252 ID |
339 | * real registers at vsync front porch period. |
340 | */ | |
8e9cc6a1 | 341 | exynos_drm_crtc_apply(crtc); |
1c248b7d ID |
342 | } |
343 | out: | |
344 | mutex_unlock(&dev->struct_mutex); | |
345 | return ret; | |
346 | } | |
347 | ||
348 | static void exynos_drm_crtc_destroy(struct drm_crtc *crtc) | |
349 | { | |
350 | struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc); | |
351 | struct exynos_drm_private *private = crtc->dev->dev_private; | |
352 | ||
353 | DRM_DEBUG_KMS("%s\n", __FILE__); | |
354 | ||
355 | private->crtc[exynos_crtc->pipe] = NULL; | |
356 | ||
357 | drm_crtc_cleanup(crtc); | |
358 | kfree(exynos_crtc); | |
359 | } | |
360 | ||
361 | static struct drm_crtc_funcs exynos_crtc_funcs = { | |
362 | .set_config = drm_crtc_helper_set_config, | |
363 | .page_flip = exynos_drm_crtc_page_flip, | |
364 | .destroy = exynos_drm_crtc_destroy, | |
365 | }; | |
366 | ||
367 | struct exynos_drm_overlay *get_exynos_drm_overlay(struct drm_device *dev, | |
368 | struct drm_crtc *crtc) | |
369 | { | |
370 | struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc); | |
371 | ||
372 | return &exynos_crtc->overlay; | |
373 | } | |
374 | ||
375 | int exynos_drm_crtc_create(struct drm_device *dev, unsigned int nr) | |
376 | { | |
377 | struct exynos_drm_crtc *exynos_crtc; | |
378 | struct exynos_drm_private *private = dev->dev_private; | |
379 | struct drm_crtc *crtc; | |
380 | ||
381 | DRM_DEBUG_KMS("%s\n", __FILE__); | |
382 | ||
383 | exynos_crtc = kzalloc(sizeof(*exynos_crtc), GFP_KERNEL); | |
384 | if (!exynos_crtc) { | |
385 | DRM_ERROR("failed to allocate exynos crtc\n"); | |
386 | return -ENOMEM; | |
387 | } | |
388 | ||
389 | exynos_crtc->pipe = nr; | |
ec05da95 | 390 | exynos_crtc->dpms = DRM_MODE_DPMS_OFF; |
864ee9e6 | 391 | exynos_crtc->overlay.zpos = DEFAULT_ZPOS; |
1c248b7d ID |
392 | crtc = &exynos_crtc->drm_crtc; |
393 | ||
394 | private->crtc[nr] = crtc; | |
395 | ||
396 | drm_crtc_init(dev, crtc, &exynos_crtc_funcs); | |
397 | drm_crtc_helper_add(crtc, &exynos_crtc_helper_funcs); | |
398 | ||
399 | return 0; | |
400 | } | |
401 | ||
402 | int exynos_drm_crtc_enable_vblank(struct drm_device *dev, int crtc) | |
403 | { | |
404 | struct exynos_drm_private *private = dev->dev_private; | |
ec05da95 ID |
405 | struct exynos_drm_crtc *exynos_crtc = |
406 | to_exynos_crtc(private->crtc[crtc]); | |
1c248b7d ID |
407 | |
408 | DRM_DEBUG_KMS("%s\n", __FILE__); | |
409 | ||
ec05da95 ID |
410 | if (exynos_crtc->dpms != DRM_MODE_DPMS_ON) |
411 | return -EPERM; | |
412 | ||
1c248b7d ID |
413 | exynos_drm_fn_encoder(private->crtc[crtc], &crtc, |
414 | exynos_drm_enable_vblank); | |
415 | ||
416 | return 0; | |
417 | } | |
418 | ||
419 | void exynos_drm_crtc_disable_vblank(struct drm_device *dev, int crtc) | |
420 | { | |
421 | struct exynos_drm_private *private = dev->dev_private; | |
ec05da95 ID |
422 | struct exynos_drm_crtc *exynos_crtc = |
423 | to_exynos_crtc(private->crtc[crtc]); | |
1c248b7d ID |
424 | |
425 | DRM_DEBUG_KMS("%s\n", __FILE__); | |
426 | ||
ec05da95 ID |
427 | if (exynos_crtc->dpms != DRM_MODE_DPMS_ON) |
428 | return; | |
429 | ||
1c248b7d ID |
430 | exynos_drm_fn_encoder(private->crtc[crtc], &crtc, |
431 | exynos_drm_disable_vblank); | |
432 | } |