Commit | Line | Data |
---|---|---|
771fe6b9 JG |
1 | /* |
2 | * Copyright © 2007 David Airlie | |
3 | * | |
4 | * Permission is hereby granted, free of charge, to any person obtaining a | |
5 | * copy of this software and associated documentation files (the "Software"), | |
6 | * to deal in the Software without restriction, including without limitation | |
7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
8 | * and/or sell copies of the Software, and to permit persons to whom the | |
9 | * Software is furnished to do so, subject to the following conditions: | |
10 | * | |
11 | * The above copyright notice and this permission notice (including the next | |
12 | * paragraph) shall be included in all copies or substantial portions of the | |
13 | * Software. | |
14 | * | |
15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
18 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
20 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | |
21 | * DEALINGS IN THE SOFTWARE. | |
22 | * | |
23 | * Authors: | |
24 | * David Airlie | |
25 | */ | |
26 | /* | |
27 | * Modularization | |
28 | */ | |
29 | ||
30 | #include <linux/module.h> | |
771fe6b9 | 31 | #include <linux/fb.h> |
771fe6b9 JG |
32 | |
33 | #include "drmP.h" | |
34 | #include "drm.h" | |
35 | #include "drm_crtc.h" | |
36 | #include "drm_crtc_helper.h" | |
37 | #include "radeon_drm.h" | |
38 | #include "radeon.h" | |
39 | ||
785b93ef DA |
40 | #include "drm_fb_helper.h" |
41 | ||
771fe6b9 | 42 | struct radeon_fb_device { |
785b93ef | 43 | struct drm_fb_helper helper; |
771fe6b9 | 44 | struct radeon_framebuffer *rfb; |
785b93ef | 45 | struct radeon_device *rdev; |
771fe6b9 JG |
46 | }; |
47 | ||
785b93ef DA |
48 | static int radeon_fb_check_var(struct fb_var_screeninfo *var, |
49 | struct fb_info *info) | |
771fe6b9 | 50 | { |
771fe6b9 | 51 | int ret; |
785b93ef DA |
52 | ret = drm_fb_helper_check_var(var, info); |
53 | if (ret) | |
54 | return ret; | |
55 | ||
56 | /* big endian override for radeon endian workaround */ | |
57 | #ifdef __BIG_ENDIAN | |
58 | { | |
59 | int depth; | |
60 | switch (var->bits_per_pixel) { | |
61 | case 16: | |
62 | depth = (var->green.length == 6) ? 16 : 15; | |
63 | break; | |
64 | case 32: | |
65 | depth = (var->transp.length > 0) ? 32 : 24; | |
66 | break; | |
67 | default: | |
68 | depth = var->bits_per_pixel; | |
69 | break; | |
771fe6b9 | 70 | } |
785b93ef DA |
71 | switch (depth) { |
72 | case 8: | |
73 | var->red.offset = 0; | |
74 | var->green.offset = 0; | |
75 | var->blue.offset = 0; | |
76 | var->red.length = 8; | |
77 | var->green.length = 8; | |
78 | var->blue.length = 8; | |
79 | var->transp.length = 0; | |
80 | var->transp.offset = 0; | |
81 | break; | |
82 | case 24: | |
83 | var->red.offset = 8; | |
84 | var->green.offset = 16; | |
85 | var->blue.offset = 24; | |
86 | var->red.length = 8; | |
87 | var->green.length = 8; | |
88 | var->blue.length = 8; | |
89 | var->transp.length = 0; | |
90 | var->transp.offset = 0; | |
91 | break; | |
92 | case 32: | |
93 | var->red.offset = 8; | |
94 | var->green.offset = 16; | |
95 | var->blue.offset = 24; | |
96 | var->red.length = 8; | |
97 | var->green.length = 8; | |
98 | var->blue.length = 8; | |
99 | var->transp.length = 8; | |
100 | var->transp.offset = 0; | |
101 | break; | |
102 | default: | |
103 | return -EINVAL; | |
771fe6b9 JG |
104 | } |
105 | } | |
785b93ef | 106 | #endif |
771fe6b9 JG |
107 | return 0; |
108 | } | |
109 | ||
110 | static struct fb_ops radeonfb_ops = { | |
111 | .owner = THIS_MODULE, | |
785b93ef DA |
112 | .fb_check_var = radeon_fb_check_var, |
113 | .fb_set_par = drm_fb_helper_set_par, | |
114 | .fb_setcolreg = drm_fb_helper_setcolreg, | |
771fe6b9 JG |
115 | .fb_fillrect = cfb_fillrect, |
116 | .fb_copyarea = cfb_copyarea, | |
117 | .fb_imageblit = cfb_imageblit, | |
785b93ef DA |
118 | .fb_pan_display = drm_fb_helper_pan_display, |
119 | .fb_blank = drm_fb_helper_blank, | |
771fe6b9 JG |
120 | }; |
121 | ||
122 | /** | |
123 | * Curretly it is assumed that the old framebuffer is reused. | |
124 | * | |
125 | * LOCKING | |
126 | * caller should hold the mode config lock. | |
127 | * | |
128 | */ | |
129 | int radeonfb_resize(struct drm_device *dev, struct drm_crtc *crtc) | |
130 | { | |
131 | struct fb_info *info; | |
132 | struct drm_framebuffer *fb; | |
133 | struct drm_display_mode *mode = crtc->desired_mode; | |
134 | ||
135 | fb = crtc->fb; | |
136 | if (fb == NULL) { | |
137 | return 1; | |
138 | } | |
139 | info = fb->fbdev; | |
140 | if (info == NULL) { | |
141 | return 1; | |
142 | } | |
143 | if (mode == NULL) { | |
144 | return 1; | |
145 | } | |
146 | info->var.xres = mode->hdisplay; | |
147 | info->var.right_margin = mode->hsync_start - mode->hdisplay; | |
148 | info->var.hsync_len = mode->hsync_end - mode->hsync_start; | |
149 | info->var.left_margin = mode->htotal - mode->hsync_end; | |
150 | info->var.yres = mode->vdisplay; | |
151 | info->var.lower_margin = mode->vsync_start - mode->vdisplay; | |
152 | info->var.vsync_len = mode->vsync_end - mode->vsync_start; | |
153 | info->var.upper_margin = mode->vtotal - mode->vsync_end; | |
154 | info->var.pixclock = 10000000 / mode->htotal * 1000 / mode->vtotal * 100; | |
155 | /* avoid overflow */ | |
156 | info->var.pixclock = info->var.pixclock * 1000 / mode->vrefresh; | |
157 | ||
158 | return 0; | |
159 | } | |
160 | EXPORT_SYMBOL(radeonfb_resize); | |
161 | ||
e024e110 | 162 | static int radeon_align_pitch(struct radeon_device *rdev, int width, int bpp, bool tiled) |
771fe6b9 JG |
163 | { |
164 | int aligned = width; | |
e024e110 | 165 | int align_large = (ASIC_IS_AVIVO(rdev)) || tiled; |
771fe6b9 JG |
166 | int pitch_mask = 0; |
167 | ||
168 | switch (bpp / 8) { | |
169 | case 1: | |
170 | pitch_mask = align_large ? 255 : 127; | |
171 | break; | |
172 | case 2: | |
173 | pitch_mask = align_large ? 127 : 31; | |
174 | break; | |
175 | case 3: | |
176 | case 4: | |
177 | pitch_mask = align_large ? 63 : 15; | |
178 | break; | |
179 | } | |
180 | ||
181 | aligned += pitch_mask; | |
182 | aligned &= ~pitch_mask; | |
183 | return aligned; | |
184 | } | |
185 | ||
785b93ef DA |
186 | static struct drm_fb_helper_funcs radeon_fb_helper_funcs = { |
187 | .gamma_set = radeon_crtc_fb_gamma_set, | |
188 | }; | |
189 | ||
190 | int radeonfb_create(struct drm_device *dev, | |
771fe6b9 JG |
191 | uint32_t fb_width, uint32_t fb_height, |
192 | uint32_t surface_width, uint32_t surface_height, | |
785b93ef | 193 | struct drm_framebuffer **fb_p) |
771fe6b9 | 194 | { |
785b93ef | 195 | struct radeon_device *rdev = dev->dev_private; |
771fe6b9 JG |
196 | struct fb_info *info; |
197 | struct radeon_fb_device *rfbdev; | |
f92e93eb | 198 | struct drm_framebuffer *fb = NULL; |
771fe6b9 JG |
199 | struct radeon_framebuffer *rfb; |
200 | struct drm_mode_fb_cmd mode_cmd; | |
201 | struct drm_gem_object *gobj = NULL; | |
202 | struct radeon_object *robj = NULL; | |
203 | struct device *device = &rdev->pdev->dev; | |
204 | int size, aligned_size, ret; | |
f92e93eb | 205 | u64 fb_gpuaddr; |
771fe6b9 | 206 | void *fbptr = NULL; |
f92e93eb | 207 | unsigned long tmp; |
e024e110 | 208 | bool fb_tiled = false; /* useful for testing */ |
771fe6b9 JG |
209 | |
210 | mode_cmd.width = surface_width; | |
211 | mode_cmd.height = surface_height; | |
212 | mode_cmd.bpp = 32; | |
213 | /* need to align pitch with crtc limits */ | |
e024e110 | 214 | mode_cmd.pitch = radeon_align_pitch(rdev, mode_cmd.width, mode_cmd.bpp, fb_tiled) * ((mode_cmd.bpp + 1) / 8); |
771fe6b9 JG |
215 | mode_cmd.depth = 24; |
216 | ||
217 | size = mode_cmd.pitch * mode_cmd.height; | |
218 | aligned_size = ALIGN(size, PAGE_SIZE); | |
219 | ||
220 | ret = radeon_gem_object_create(rdev, aligned_size, 0, | |
f92e93eb JG |
221 | RADEON_GEM_DOMAIN_VRAM, |
222 | false, ttm_bo_type_kernel, | |
223 | false, &gobj); | |
771fe6b9 | 224 | if (ret) { |
f92e93eb JG |
225 | printk(KERN_ERR "failed to allocate framebuffer (%d %d)\n", |
226 | surface_width, surface_height); | |
771fe6b9 JG |
227 | ret = -ENOMEM; |
228 | goto out; | |
229 | } | |
230 | robj = gobj->driver_private; | |
231 | ||
e024e110 DA |
232 | if (fb_tiled) |
233 | radeon_object_set_tiling_flags(robj, RADEON_TILING_MACRO|RADEON_TILING_SURFACE, mode_cmd.pitch); | |
771fe6b9 JG |
234 | mutex_lock(&rdev->ddev->struct_mutex); |
235 | fb = radeon_framebuffer_create(rdev->ddev, &mode_cmd, gobj); | |
236 | if (fb == NULL) { | |
237 | DRM_ERROR("failed to allocate fb.\n"); | |
238 | ret = -ENOMEM; | |
239 | goto out_unref; | |
240 | } | |
f92e93eb JG |
241 | ret = radeon_object_pin(robj, RADEON_GEM_DOMAIN_VRAM, &fb_gpuaddr); |
242 | if (ret) { | |
243 | printk(KERN_ERR "failed to pin framebuffer\n"); | |
244 | ret = -ENOMEM; | |
245 | goto out_unref; | |
246 | } | |
771fe6b9 JG |
247 | |
248 | list_add(&fb->filp_head, &rdev->ddev->mode_config.fb_kernel_list); | |
249 | ||
785b93ef | 250 | *fb_p = fb; |
771fe6b9 | 251 | rfb = to_radeon_framebuffer(fb); |
771fe6b9 | 252 | rdev->fbdev_rfb = rfb; |
f92e93eb | 253 | rdev->fbdev_robj = robj; |
771fe6b9 JG |
254 | |
255 | info = framebuffer_alloc(sizeof(struct radeon_fb_device), device); | |
256 | if (info == NULL) { | |
257 | ret = -ENOMEM; | |
258 | goto out_unref; | |
259 | } | |
785b93ef | 260 | |
2f9a60d7 | 261 | rdev->fbdev_info = info; |
771fe6b9 | 262 | rfbdev = info->par; |
785b93ef DA |
263 | rfbdev->helper.funcs = &radeon_fb_helper_funcs; |
264 | rfbdev->helper.dev = dev; | |
265 | ret = drm_fb_helper_init_crtc_count(&rfbdev->helper, 2, | |
266 | RADEONFB_CONN_LIMIT); | |
267 | if (ret) | |
268 | goto out_unref; | |
771fe6b9 | 269 | |
e024e110 DA |
270 | if (fb_tiled) |
271 | radeon_object_check_tiling(robj, 0, 0); | |
272 | ||
771fe6b9 JG |
273 | ret = radeon_object_kmap(robj, &fbptr); |
274 | if (ret) { | |
275 | goto out_unref; | |
276 | } | |
277 | ||
bf8e828b DA |
278 | memset_io(fbptr, 0, aligned_size); |
279 | ||
771fe6b9 | 280 | strcpy(info->fix.id, "radeondrmfb"); |
785b93ef DA |
281 | |
282 | drm_fb_helper_fill_fix(info, fb->pitch); | |
283 | ||
771fe6b9 JG |
284 | info->flags = FBINFO_DEFAULT; |
285 | info->fbops = &radeonfb_ops; | |
785b93ef | 286 | |
f92e93eb JG |
287 | tmp = fb_gpuaddr - rdev->mc.vram_location; |
288 | info->fix.smem_start = rdev->mc.aper_base + tmp; | |
771fe6b9 JG |
289 | info->fix.smem_len = size; |
290 | info->screen_base = fbptr; | |
291 | info->screen_size = size; | |
785b93ef DA |
292 | |
293 | drm_fb_helper_fill_var(info, fb, fb_width, fb_height); | |
ed8f0d9e DA |
294 | |
295 | /* setup aperture base/size for vesafb takeover */ | |
296 | info->aperture_base = rdev->ddev->mode_config.fb_base; | |
297 | info->aperture_size = rdev->mc.real_vram_size; | |
298 | ||
696d4df1 MD |
299 | info->fix.mmio_start = 0; |
300 | info->fix.mmio_len = 0; | |
771fe6b9 JG |
301 | info->pixmap.size = 64*1024; |
302 | info->pixmap.buf_align = 8; | |
303 | info->pixmap.access_align = 32; | |
304 | info->pixmap.flags = FB_PIXMAP_SYSTEM; | |
305 | info->pixmap.scan_align = 1; | |
306 | if (info->screen_base == NULL) { | |
307 | ret = -ENOSPC; | |
308 | goto out_unref; | |
309 | } | |
310 | DRM_INFO("fb mappable at 0x%lX\n", info->fix.smem_start); | |
311 | DRM_INFO("vram apper at 0x%lX\n", (unsigned long)rdev->mc.aper_base); | |
312 | DRM_INFO("size %lu\n", (unsigned long)size); | |
313 | DRM_INFO("fb depth is %d\n", fb->depth); | |
314 | DRM_INFO(" pitch is %d\n", fb->pitch); | |
315 | ||
785b93ef DA |
316 | #ifdef __BIG_ENDIAN |
317 | /* fill var sets defaults for this stuff - override | |
318 | on big endian */ | |
771fe6b9 JG |
319 | switch (fb->depth) { |
320 | case 8: | |
321 | info->var.red.offset = 0; | |
322 | info->var.green.offset = 0; | |
323 | info->var.blue.offset = 0; | |
324 | info->var.red.length = 8; /* 8bit DAC */ | |
325 | info->var.green.length = 8; | |
326 | info->var.blue.length = 8; | |
327 | info->var.transp.offset = 0; | |
328 | info->var.transp.length = 0; | |
329 | break; | |
61b576db MD |
330 | case 24: |
331 | info->var.red.offset = 8; | |
332 | info->var.green.offset = 16; | |
333 | info->var.blue.offset = 24; | |
334 | info->var.red.length = 8; | |
335 | info->var.green.length = 8; | |
336 | info->var.blue.length = 8; | |
337 | info->var.transp.offset = 0; | |
338 | info->var.transp.length = 0; | |
339 | break; | |
340 | case 32: | |
341 | info->var.red.offset = 8; | |
342 | info->var.green.offset = 16; | |
343 | info->var.blue.offset = 24; | |
344 | info->var.red.length = 8; | |
345 | info->var.green.length = 8; | |
346 | info->var.blue.length = 8; | |
347 | info->var.transp.offset = 0; | |
348 | info->var.transp.length = 8; | |
349 | break; | |
771fe6b9 JG |
350 | default: |
351 | break; | |
352 | } | |
785b93ef | 353 | #endif |
771fe6b9 JG |
354 | |
355 | fb->fbdev = info; | |
356 | rfbdev->rfb = rfb; | |
357 | rfbdev->rdev = rdev; | |
358 | ||
359 | mutex_unlock(&rdev->ddev->struct_mutex); | |
360 | return 0; | |
361 | ||
362 | out_unref: | |
363 | if (robj) { | |
364 | radeon_object_kunmap(robj); | |
365 | } | |
f92e93eb | 366 | if (fb && ret) { |
771fe6b9 JG |
367 | list_del(&fb->filp_head); |
368 | drm_gem_object_unreference(gobj); | |
369 | drm_framebuffer_cleanup(fb); | |
370 | kfree(fb); | |
371 | } | |
372 | drm_gem_object_unreference(gobj); | |
373 | mutex_unlock(&rdev->ddev->struct_mutex); | |
374 | out: | |
375 | return ret; | |
376 | } | |
377 | ||
771fe6b9 JG |
378 | int radeonfb_probe(struct drm_device *dev) |
379 | { | |
380 | int ret; | |
785b93ef | 381 | ret = drm_fb_helper_single_fb_probe(dev, &radeonfb_create); |
771fe6b9 JG |
382 | return ret; |
383 | } | |
384 | EXPORT_SYMBOL(radeonfb_probe); | |
385 | ||
386 | int radeonfb_remove(struct drm_device *dev, struct drm_framebuffer *fb) | |
387 | { | |
388 | struct fb_info *info; | |
389 | struct radeon_framebuffer *rfb = to_radeon_framebuffer(fb); | |
390 | struct radeon_object *robj; | |
391 | ||
392 | if (!fb) { | |
393 | return -EINVAL; | |
394 | } | |
395 | info = fb->fbdev; | |
396 | if (info) { | |
785b93ef | 397 | struct radeon_fb_device *rfbdev = info->par; |
771fe6b9 JG |
398 | robj = rfb->obj->driver_private; |
399 | unregister_framebuffer(info); | |
400 | radeon_object_kunmap(robj); | |
f92e93eb | 401 | radeon_object_unpin(robj); |
785b93ef | 402 | drm_fb_helper_free(&rfbdev->helper); |
771fe6b9 JG |
403 | framebuffer_release(info); |
404 | } | |
405 | ||
406 | printk(KERN_INFO "unregistered panic notifier\n"); | |
785b93ef | 407 | |
771fe6b9 JG |
408 | return 0; |
409 | } | |
410 | EXPORT_SYMBOL(radeonfb_remove); | |
411 | MODULE_LICENSE("GPL"); |