]>
Commit | Line | Data |
---|---|---|
f9aa76a8 DA |
1 | /* |
2 | * Copyright 2012 Red Hat | |
3 | * | |
4 | * This file is subject to the terms and conditions of the GNU General | |
5 | * Public License version 2. See the file COPYING in the main | |
6 | * directory of this archive for more details. | |
7 | * | |
8 | * Authors: Matthew Garrett | |
9 | * Dave Airlie | |
10 | */ | |
11 | #include <linux/module.h> | |
760285e7 DH |
12 | #include <drm/drmP.h> |
13 | #include <drm/drm_fb_helper.h> | |
76a39dbf | 14 | #include <drm/drm_crtc_helper.h> |
f9aa76a8 | 15 | |
f9aa76a8 DA |
16 | #include "cirrus_drv.h" |
17 | ||
18 | static void cirrus_dirty_update(struct cirrus_fbdev *afbdev, | |
19 | int x, int y, int width, int height) | |
20 | { | |
21 | int i; | |
22 | struct drm_gem_object *obj; | |
23 | struct cirrus_bo *bo; | |
24 | int src_offset, dst_offset; | |
272725c7 | 25 | int bpp = afbdev->gfb.base.format->cpp[0]; |
19d4b72c | 26 | int ret = -EBUSY; |
f9aa76a8 | 27 | bool unmap = false; |
f3b2bbdc DA |
28 | bool store_for_later = false; |
29 | int x2, y2; | |
30 | unsigned long flags; | |
f9aa76a8 DA |
31 | |
32 | obj = afbdev->gfb.obj; | |
33 | bo = gem_to_cirrus_bo(obj); | |
34 | ||
f3b2bbdc DA |
35 | /* |
36 | * try and reserve the BO, if we fail with busy | |
37 | * then the BO is being moved and we should | |
38 | * store up the damage until later. | |
39 | */ | |
8b7ad1bb | 40 | if (drm_can_sleep()) |
19d4b72c | 41 | ret = cirrus_bo_reserve(bo, true); |
f9aa76a8 | 42 | if (ret) { |
f3b2bbdc DA |
43 | if (ret != -EBUSY) |
44 | return; | |
45 | store_for_later = true; | |
46 | } | |
47 | ||
48 | x2 = x + width - 1; | |
49 | y2 = y + height - 1; | |
50 | spin_lock_irqsave(&afbdev->dirty_lock, flags); | |
51 | ||
52 | if (afbdev->y1 < y) | |
53 | y = afbdev->y1; | |
54 | if (afbdev->y2 > y2) | |
55 | y2 = afbdev->y2; | |
56 | if (afbdev->x1 < x) | |
57 | x = afbdev->x1; | |
58 | if (afbdev->x2 > x2) | |
59 | x2 = afbdev->x2; | |
60 | ||
61 | if (store_for_later) { | |
62 | afbdev->x1 = x; | |
63 | afbdev->x2 = x2; | |
64 | afbdev->y1 = y; | |
65 | afbdev->y2 = y2; | |
66 | spin_unlock_irqrestore(&afbdev->dirty_lock, flags); | |
f9aa76a8 DA |
67 | return; |
68 | } | |
69 | ||
f3b2bbdc DA |
70 | afbdev->x1 = afbdev->y1 = INT_MAX; |
71 | afbdev->x2 = afbdev->y2 = 0; | |
72 | spin_unlock_irqrestore(&afbdev->dirty_lock, flags); | |
73 | ||
f9aa76a8 DA |
74 | if (!bo->kmap.virtual) { |
75 | ret = ttm_bo_kmap(&bo->bo, 0, bo->bo.num_pages, &bo->kmap); | |
76 | if (ret) { | |
77 | DRM_ERROR("failed to kmap fb updates\n"); | |
78 | cirrus_bo_unreserve(bo); | |
79 | return; | |
80 | } | |
81 | unmap = true; | |
82 | } | |
83 | for (i = y; i < y + height; i++) { | |
84 | /* assume equal stride for now */ | |
85 | src_offset = dst_offset = i * afbdev->gfb.base.pitches[0] + (x * bpp); | |
86 | memcpy_toio(bo->kmap.virtual + src_offset, afbdev->sysram + src_offset, width * bpp); | |
87 | ||
88 | } | |
89 | if (unmap) | |
90 | ttm_bo_kunmap(&bo->kmap); | |
91 | ||
92 | cirrus_bo_unreserve(bo); | |
93 | } | |
94 | ||
95 | static void cirrus_fillrect(struct fb_info *info, | |
96 | const struct fb_fillrect *rect) | |
97 | { | |
98 | struct cirrus_fbdev *afbdev = info->par; | |
2b9e6e37 | 99 | drm_fb_helper_sys_fillrect(info, rect); |
f9aa76a8 DA |
100 | cirrus_dirty_update(afbdev, rect->dx, rect->dy, rect->width, |
101 | rect->height); | |
102 | } | |
103 | ||
104 | static void cirrus_copyarea(struct fb_info *info, | |
105 | const struct fb_copyarea *area) | |
106 | { | |
107 | struct cirrus_fbdev *afbdev = info->par; | |
2b9e6e37 | 108 | drm_fb_helper_sys_copyarea(info, area); |
f9aa76a8 DA |
109 | cirrus_dirty_update(afbdev, area->dx, area->dy, area->width, |
110 | area->height); | |
111 | } | |
112 | ||
113 | static void cirrus_imageblit(struct fb_info *info, | |
114 | const struct fb_image *image) | |
115 | { | |
116 | struct cirrus_fbdev *afbdev = info->par; | |
2b9e6e37 | 117 | drm_fb_helper_sys_imageblit(info, image); |
f9aa76a8 DA |
118 | cirrus_dirty_update(afbdev, image->dx, image->dy, image->width, |
119 | image->height); | |
120 | } | |
121 | ||
122 | ||
123 | static struct fb_ops cirrusfb_ops = { | |
124 | .owner = THIS_MODULE, | |
125 | .fb_check_var = drm_fb_helper_check_var, | |
126 | .fb_set_par = drm_fb_helper_set_par, | |
127 | .fb_fillrect = cirrus_fillrect, | |
128 | .fb_copyarea = cirrus_copyarea, | |
129 | .fb_imageblit = cirrus_imageblit, | |
130 | .fb_pan_display = drm_fb_helper_pan_display, | |
131 | .fb_blank = drm_fb_helper_blank, | |
132 | .fb_setcmap = drm_fb_helper_setcmap, | |
133 | }; | |
134 | ||
135 | static int cirrusfb_create_object(struct cirrus_fbdev *afbdev, | |
1eb83451 | 136 | const struct drm_mode_fb_cmd2 *mode_cmd, |
f9aa76a8 DA |
137 | struct drm_gem_object **gobj_p) |
138 | { | |
139 | struct drm_device *dev = afbdev->helper.dev; | |
8975626e | 140 | struct cirrus_device *cdev = dev->dev_private; |
b7f9745c | 141 | u32 bpp; |
f9aa76a8 DA |
142 | u32 size; |
143 | struct drm_gem_object *gobj; | |
f9aa76a8 | 144 | int ret = 0; |
b7f9745c LP |
145 | |
146 | bpp = drm_format_plane_cpp(mode_cmd->pixel_format, 0) * 8; | |
f9aa76a8 | 147 | |
8975626e ZR |
148 | if (!cirrus_check_framebuffer(cdev, mode_cmd->width, mode_cmd->height, |
149 | bpp, mode_cmd->pitches[0])) | |
f9aa76a8 | 150 | return -EINVAL; |
8975626e | 151 | |
f9aa76a8 DA |
152 | size = mode_cmd->pitches[0] * mode_cmd->height; |
153 | ret = cirrus_gem_create(dev, size, true, &gobj); | |
154 | if (ret) | |
155 | return ret; | |
156 | ||
157 | *gobj_p = gobj; | |
158 | return ret; | |
159 | } | |
160 | ||
cd5428a5 | 161 | static int cirrusfb_create(struct drm_fb_helper *helper, |
f9aa76a8 DA |
162 | struct drm_fb_helper_surface_size *sizes) |
163 | { | |
ea0622cf FF |
164 | struct cirrus_fbdev *gfbdev = |
165 | container_of(helper, struct cirrus_fbdev, helper); | |
f9aa76a8 DA |
166 | struct cirrus_device *cdev = gfbdev->helper.dev->dev_private; |
167 | struct fb_info *info; | |
168 | struct drm_framebuffer *fb; | |
169 | struct drm_mode_fb_cmd2 mode_cmd; | |
f9aa76a8 DA |
170 | void *sysram; |
171 | struct drm_gem_object *gobj = NULL; | |
172 | struct cirrus_bo *bo = NULL; | |
173 | int size, ret; | |
174 | ||
175 | mode_cmd.width = sizes->surface_width; | |
176 | mode_cmd.height = sizes->surface_height; | |
177 | mode_cmd.pitches[0] = mode_cmd.width * ((sizes->surface_bpp + 7) / 8); | |
178 | mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, | |
179 | sizes->surface_depth); | |
180 | size = mode_cmd.pitches[0] * mode_cmd.height; | |
181 | ||
182 | ret = cirrusfb_create_object(gfbdev, &mode_cmd, &gobj); | |
183 | if (ret) { | |
184 | DRM_ERROR("failed to create fbcon backing object %d\n", ret); | |
185 | return ret; | |
186 | } | |
187 | ||
188 | bo = gem_to_cirrus_bo(gobj); | |
189 | ||
190 | sysram = vmalloc(size); | |
191 | if (!sysram) | |
192 | return -ENOMEM; | |
193 | ||
2b9e6e37 AT |
194 | info = drm_fb_helper_alloc_fbi(helper); |
195 | if (IS_ERR(info)) | |
196 | return PTR_ERR(info); | |
f9aa76a8 DA |
197 | |
198 | info->par = gfbdev; | |
199 | ||
200 | ret = cirrus_framebuffer_init(cdev->dev, &gfbdev->gfb, &mode_cmd, gobj); | |
201 | if (ret) | |
202 | return ret; | |
203 | ||
204 | gfbdev->sysram = sysram; | |
205 | gfbdev->size = size; | |
206 | ||
207 | fb = &gfbdev->gfb.base; | |
208 | if (!fb) { | |
209 | DRM_INFO("fb is NULL\n"); | |
210 | return -EINVAL; | |
211 | } | |
212 | ||
213 | /* setup helper */ | |
214 | gfbdev->helper.fb = fb; | |
f9aa76a8 DA |
215 | |
216 | strcpy(info->fix.id, "cirrusdrmfb"); | |
217 | ||
f9aa76a8 DA |
218 | info->fbops = &cirrusfb_ops; |
219 | ||
b00c600e | 220 | drm_fb_helper_fill_fix(info, fb->pitches[0], fb->format->depth); |
f9aa76a8 DA |
221 | drm_fb_helper_fill_var(info, &gfbdev->helper, sizes->fb_width, |
222 | sizes->fb_height); | |
223 | ||
224 | /* setup aperture base/size for vesafb takeover */ | |
f9aa76a8 DA |
225 | info->apertures->ranges[0].base = cdev->dev->mode_config.fb_base; |
226 | info->apertures->ranges[0].size = cdev->mc.vram_size; | |
227 | ||
99d4a8ae MK |
228 | info->fix.smem_start = cdev->dev->mode_config.fb_base; |
229 | info->fix.smem_len = cdev->mc.vram_size; | |
230 | ||
f9aa76a8 DA |
231 | info->screen_base = sysram; |
232 | info->screen_size = size; | |
233 | ||
234 | info->fix.mmio_start = 0; | |
235 | info->fix.mmio_len = 0; | |
236 | ||
f9aa76a8 DA |
237 | DRM_INFO("fb mappable at 0x%lX\n", info->fix.smem_start); |
238 | DRM_INFO("vram aper at 0x%lX\n", (unsigned long)info->fix.smem_start); | |
239 | DRM_INFO("size %lu\n", (unsigned long)info->fix.smem_len); | |
b00c600e | 240 | DRM_INFO("fb depth is %d\n", fb->format->depth); |
f9aa76a8 DA |
241 | DRM_INFO(" pitch is %d\n", fb->pitches[0]); |
242 | ||
243 | return 0; | |
f9aa76a8 DA |
244 | } |
245 | ||
f9aa76a8 DA |
246 | static int cirrus_fbdev_destroy(struct drm_device *dev, |
247 | struct cirrus_fbdev *gfbdev) | |
248 | { | |
f9aa76a8 DA |
249 | struct cirrus_framebuffer *gfb = &gfbdev->gfb; |
250 | ||
2b9e6e37 | 251 | drm_fb_helper_unregister_fbi(&gfbdev->helper); |
f9aa76a8 DA |
252 | |
253 | if (gfb->obj) { | |
254 | drm_gem_object_unreference_unlocked(gfb->obj); | |
255 | gfb->obj = NULL; | |
256 | } | |
257 | ||
258 | vfree(gfbdev->sysram); | |
259 | drm_fb_helper_fini(&gfbdev->helper); | |
36206361 | 260 | drm_framebuffer_unregister_private(&gfb->base); |
f9aa76a8 DA |
261 | drm_framebuffer_cleanup(&gfb->base); |
262 | ||
263 | return 0; | |
264 | } | |
265 | ||
3a493879 | 266 | static const struct drm_fb_helper_funcs cirrus_fb_helper_funcs = { |
f9aa76a8 DA |
267 | .gamma_set = cirrus_crtc_fb_gamma_set, |
268 | .gamma_get = cirrus_crtc_fb_gamma_get, | |
cd5428a5 | 269 | .fb_probe = cirrusfb_create, |
f9aa76a8 DA |
270 | }; |
271 | ||
272 | int cirrus_fbdev_init(struct cirrus_device *cdev) | |
273 | { | |
274 | struct cirrus_fbdev *gfbdev; | |
275 | int ret; | |
276 | int bpp_sel = 24; | |
277 | ||
278 | /*bpp_sel = 8;*/ | |
279 | gfbdev = kzalloc(sizeof(struct cirrus_fbdev), GFP_KERNEL); | |
280 | if (!gfbdev) | |
281 | return -ENOMEM; | |
282 | ||
283 | cdev->mode_info.gfbdev = gfbdev; | |
f3b2bbdc | 284 | spin_lock_init(&gfbdev->dirty_lock); |
f9aa76a8 | 285 | |
10a23102 TR |
286 | drm_fb_helper_prepare(cdev->dev, &gfbdev->helper, |
287 | &cirrus_fb_helper_funcs); | |
288 | ||
f9aa76a8 | 289 | ret = drm_fb_helper_init(cdev->dev, &gfbdev->helper, |
e4563f6b | 290 | CIRRUSFB_CONN_LIMIT); |
01934c2a TR |
291 | if (ret) |
292 | return ret; | |
293 | ||
294 | ret = drm_fb_helper_single_add_all_connectors(&gfbdev->helper); | |
295 | if (ret) | |
f9aa76a8 | 296 | return ret; |
76a39dbf DV |
297 | |
298 | /* disable all the possible outputs/crtcs before entering KMS mode */ | |
299 | drm_helper_disable_unused_functions(cdev->dev); | |
f9aa76a8 | 300 | |
01934c2a | 301 | return drm_fb_helper_initial_config(&gfbdev->helper, bpp_sel); |
f9aa76a8 DA |
302 | } |
303 | ||
304 | void cirrus_fbdev_fini(struct cirrus_device *cdev) | |
305 | { | |
306 | if (!cdev->mode_info.gfbdev) | |
307 | return; | |
308 | ||
309 | cirrus_fbdev_destroy(cdev->dev, cdev->mode_info.gfbdev); | |
310 | kfree(cdev->mode_info.gfbdev); | |
311 | cdev->mode_info.gfbdev = NULL; | |
312 | } |