| 1 | |
| 2 | * linux/drivers/video/fbmem.c
|
| 3 | *
|
| 4 | * Copyright (C) 1994 Martin Schaller
|
| 5 | *
|
| 6 | * 2001 - Documented with DocBook
|
| 7 | * - Brad Douglas <[email protected]>
|
| 8 | *
|
| 9 | * This file is subject to the terms and conditions of the GNU General Public
|
| 10 | * License. See the file COPYING in the main directory of this archive
|
| 11 | * for more details.
|
| 12 |
|
| 13 |
|
| 14 | #include <linux/console.h>
|
| 15 | #include <linux/export.h>
|
| 16 | #include <linux/fb.h>
|
| 17 | #include <linux/lcd.h>
|
| 18 | #include <linux/leds.h>
|
| 19 |
|
| 20 | #include <video/nomodeset.h>
|
| 21 |
|
| 22 | #include "fb_internal.h"
|
| 23 | #include "fbcon.h"
|
| 24 |
|
| 25 | |
| 26 | * Frame buffer device initialization and setup routines
|
| 27 |
|
| 28 |
|
| 29 | #define FBPIXMAPSIZE (1024 * 8)
|
| 30 |
|
| 31 | struct class *fb_class;
|
| 32 |
|
| 33 | DEFINE_MUTEX(registration_lock);
|
| 34 | struct fb_info *registered_fb[FB_MAX] __read_mostly;
|
| 35 | int num_registered_fb __read_mostly;
|
| 36 | #define for_each_registered_fb(i) \
|
| 37 | for (i = 0; i < FB_MAX; i++) \
|
| 38 | if (!registered_fb[i]) {} else
|
| 39 |
|
| 40 | struct fb_info *get_fb_info(unsigned int idx)
|
| 41 | {
|
| 42 | struct fb_info *fb_info;
|
| 43 |
|
| 44 | if (idx >= FB_MAX)
|
| 45 | return ERR_PTR(-ENODEV);
|
| 46 |
|
| 47 | mutex_lock(®istration_lock);
|
| 48 | fb_info = registered_fb[idx];
|
| 49 | if (fb_info)
|
| 50 | refcount_inc(&fb_info->count);
|
| 51 | mutex_unlock(®istration_lock);
|
| 52 |
|
| 53 | return fb_info;
|
| 54 | }
|
| 55 |
|
| 56 | void put_fb_info(struct fb_info *fb_info)
|
| 57 | {
|
| 58 | if (!refcount_dec_and_test(&fb_info->count))
|
| 59 | return;
|
| 60 | if (fb_info->fbops->fb_destroy)
|
| 61 | fb_info->fbops->fb_destroy(fb_info);
|
| 62 | }
|
| 63 |
|
| 64 | |
| 65 | * Helpers
|
| 66 |
|
| 67 |
|
| 68 | int fb_get_color_depth(struct fb_var_screeninfo *var,
|
| 69 | struct fb_fix_screeninfo *fix)
|
| 70 | {
|
| 71 | int depth = 0;
|
| 72 |
|
| 73 | if (fix->visual == FB_VISUAL_MONO01 ||
|
| 74 | fix->visual == FB_VISUAL_MONO10)
|
| 75 | depth = 1;
|
| 76 | else {
|
| 77 | if (var->green.length == var->blue.length &&
|
| 78 | var->green.length == var->red.length &&
|
| 79 | var->green.offset == var->blue.offset &&
|
| 80 | var->green.offset == var->red.offset)
|
| 81 | depth = var->green.length;
|
| 82 | else
|
| 83 | depth = var->green.length + var->red.length +
|
| 84 | var->blue.length;
|
| 85 | }
|
| 86 |
|
| 87 | return depth;
|
| 88 | }
|
| 89 | EXPORT_SYMBOL(fb_get_color_depth);
|
| 90 |
|
| 91 | |
| 92 | * Data padding functions.
|
| 93 |
|
| 94 | void fb_pad_aligned_buffer(u8 *dst, u32 d_pitch, const u8 *src, u32 s_pitch, u32 height)
|
| 95 | {
|
| 96 | __fb_pad_aligned_buffer(dst, d_pitch, src, s_pitch, height);
|
| 97 | }
|
| 98 | EXPORT_SYMBOL(fb_pad_aligned_buffer);
|
| 99 |
|
| 100 | void fb_pad_unaligned_buffer(u8 *dst, u32 d_pitch, const u8 *src, u32 idx, u32 height,
|
| 101 | u32 shift_high, u32 shift_low, u32 mod)
|
| 102 | {
|
| 103 | u8 mask = (u8) (0xff << shift_high), tmp;
|
| 104 | int i, j;
|
| 105 |
|
| 106 | for (i = height; i--; ) {
|
| 107 | for (j = 0; j < idx; j++) {
|
| 108 | tmp = dst[j];
|
| 109 | tmp &= mask;
|
| 110 | tmp |= *src >> shift_low;
|
| 111 | dst[j] = tmp;
|
| 112 | tmp = *src << shift_high;
|
| 113 | dst[j+1] = tmp;
|
| 114 | src++;
|
| 115 | }
|
| 116 | tmp = dst[idx];
|
| 117 | tmp &= mask;
|
| 118 | tmp |= *src >> shift_low;
|
| 119 | dst[idx] = tmp;
|
| 120 | if (shift_high < mod) {
|
| 121 | tmp = *src << shift_high;
|
| 122 | dst[idx+1] = tmp;
|
| 123 | }
|
| 124 | src++;
|
| 125 | dst += d_pitch;
|
| 126 | }
|
| 127 | }
|
| 128 | EXPORT_SYMBOL(fb_pad_unaligned_buffer);
|
| 129 |
|
| 130 | |
| 131 | * we need to lock this section since fb_cursor
|
| 132 | * may use fb_imageblit()
|
| 133 |
|
| 134 | char* fb_get_buffer_offset(struct fb_info *info, struct fb_pixmap *buf, u32 size)
|
| 135 | {
|
| 136 | u32 align = buf->buf_align - 1, offset;
|
| 137 | char *addr = buf->addr;
|
| 138 |
|
| 139 | |
| 140 | * the pixmap is done
|
| 141 |
|
| 142 | if (buf->flags & FB_PIXMAP_IO) {
|
| 143 | if (info->fbops->fb_sync && (buf->flags & FB_PIXMAP_SYNC))
|
| 144 | info->fbops->fb_sync(info);
|
| 145 | return addr;
|
| 146 | }
|
| 147 |
|
| 148 |
|
| 149 | offset = buf->offset + align;
|
| 150 | offset &= ~align;
|
| 151 | if (offset + size > buf->size) {
|
| 152 | |
| 153 | * we must ensure no asynchronous DMA'ing or whatever operation
|
| 154 | * is in progress, we sync for that.
|
| 155 |
|
| 156 | if (info->fbops->fb_sync && (buf->flags & FB_PIXMAP_SYNC))
|
| 157 | info->fbops->fb_sync(info);
|
| 158 | offset = 0;
|
| 159 | }
|
| 160 | buf->offset = offset + size;
|
| 161 | addr += offset;
|
| 162 |
|
| 163 | return addr;
|
| 164 | }
|
| 165 | EXPORT_SYMBOL(fb_get_buffer_offset);
|
| 166 |
|
| 167 | int
|
| 168 | fb_pan_display(struct fb_info *info, struct fb_var_screeninfo *var)
|
| 169 | {
|
| 170 | struct fb_fix_screeninfo *fix = &info->fix;
|
| 171 | unsigned int yres = info->var.yres;
|
| 172 | int err = 0;
|
| 173 |
|
| 174 | if (var->yoffset > 0) {
|
| 175 | if (var->vmode & FB_VMODE_YWRAP) {
|
| 176 | if (!fix->ywrapstep || (var->yoffset % fix->ywrapstep))
|
| 177 | err = -EINVAL;
|
| 178 | else
|
| 179 | yres = 0;
|
| 180 | } else if (!fix->ypanstep || (var->yoffset % fix->ypanstep))
|
| 181 | err = -EINVAL;
|
| 182 | }
|
| 183 |
|
| 184 | if (var->xoffset > 0 && (!fix->xpanstep ||
|
| 185 | (var->xoffset % fix->xpanstep)))
|
| 186 | err = -EINVAL;
|
| 187 |
|
| 188 | if (err || !info->fbops->fb_pan_display ||
|
| 189 | var->yoffset > info->var.yres_virtual - yres ||
|
| 190 | var->xoffset > info->var.xres_virtual - info->var.xres)
|
| 191 | return -EINVAL;
|
| 192 |
|
| 193 | if ((err = info->fbops->fb_pan_display(var, info)))
|
| 194 | return err;
|
| 195 | info->var.xoffset = var->xoffset;
|
| 196 | info->var.yoffset = var->yoffset;
|
| 197 | if (var->vmode & FB_VMODE_YWRAP)
|
| 198 | info->var.vmode |= FB_VMODE_YWRAP;
|
| 199 | else
|
| 200 | info->var.vmode &= ~FB_VMODE_YWRAP;
|
| 201 | return 0;
|
| 202 | }
|
| 203 | EXPORT_SYMBOL(fb_pan_display);
|
| 204 |
|
| 205 | static int fb_check_caps(struct fb_info *info, struct fb_var_screeninfo *var,
|
| 206 | u32 activate)
|
| 207 | {
|
| 208 | struct fb_blit_caps caps, fbcaps;
|
| 209 | int err = 0;
|
| 210 |
|
| 211 | memset(&caps, 0, sizeof(caps));
|
| 212 | memset(&fbcaps, 0, sizeof(fbcaps));
|
| 213 | caps.flags = (activate & FB_ACTIVATE_ALL) ? 1 : 0;
|
| 214 | fbcon_get_requirement(info, &caps);
|
| 215 | info->fbops->fb_get_caps(info, &fbcaps, var);
|
| 216 |
|
| 217 | if (!bitmap_subset(caps.x, fbcaps.x, FB_MAX_BLIT_WIDTH) ||
|
| 218 | !bitmap_subset(caps.y, fbcaps.y, FB_MAX_BLIT_HEIGHT) ||
|
| 219 | (fbcaps.len < caps.len))
|
| 220 | err = -EINVAL;
|
| 221 |
|
| 222 | return err;
|
| 223 | }
|
| 224 |
|
| 225 | static void fb_lcd_notify_mode_change(struct fb_info *info,
|
| 226 | struct fb_videomode *mode)
|
| 227 | {
|
| 228 | lcd_notify_mode_change_all(info->device, mode->xres, mode->yres);
|
| 229 | }
|
| 230 |
|
| 231 | int
|
| 232 | fb_set_var(struct fb_info *info, struct fb_var_screeninfo *var)
|
| 233 | {
|
| 234 | int ret = 0;
|
| 235 | u32 activate;
|
| 236 | struct fb_var_screeninfo old_var;
|
| 237 | struct fb_videomode mode;
|
| 238 | u32 unused;
|
| 239 |
|
| 240 | if (var->activate & FB_ACTIVATE_INV_MODE) {
|
| 241 | struct fb_videomode mode1, mode2;
|
| 242 |
|
| 243 | fb_var_to_videomode(&mode1, var);
|
| 244 | fb_var_to_videomode(&mode2, &info->var);
|
| 245 |
|
| 246 | ret = fb_mode_is_equal(&mode1, &mode2);
|
| 247 | if (!ret) {
|
| 248 | ret = fbcon_mode_deleted(info, &mode1);
|
| 249 | if (!ret) {
|
| 250 | if (info->mode && fb_mode_is_equal(info->mode, &mode1))
|
| 251 | info->mode = NULL;
|
| 252 | fb_delete_videomode(&mode1, &info->modelist);
|
| 253 | }
|
| 254 | }
|
| 255 |
|
| 256 | return ret ? -EINVAL : 0;
|
| 257 | }
|
| 258 |
|
| 259 | if (!(var->activate & FB_ACTIVATE_FORCE) &&
|
| 260 | !memcmp(&info->var, var, sizeof(struct fb_var_screeninfo)))
|
| 261 | return 0;
|
| 262 |
|
| 263 | activate = var->activate;
|
| 264 |
|
| 265 | |
| 266 | * transp fields are set to 0.
|
| 267 |
|
| 268 | if ((info->fix.capabilities & FB_CAP_FOURCC) &&
|
| 269 | var->grayscale > 1) {
|
| 270 | if (var->red.offset || var->green.offset ||
|
| 271 | var->blue.offset || var->transp.offset ||
|
| 272 | var->red.length || var->green.length ||
|
| 273 | var->blue.length || var->transp.length ||
|
| 274 | var->red.msb_right || var->green.msb_right ||
|
| 275 | var->blue.msb_right || var->transp.msb_right)
|
| 276 | return -EINVAL;
|
| 277 | }
|
| 278 |
|
| 279 | if (!info->fbops->fb_check_var) {
|
| 280 | *var = info->var;
|
| 281 | return 0;
|
| 282 | }
|
| 283 |
|
| 284 |
|
| 285 | if (var->xres < 8 || var->yres < 8)
|
| 286 | return -EINVAL;
|
| 287 |
|
| 288 |
|
| 289 | if (check_mul_overflow(var->xres, var->yres, &unused) ||
|
| 290 | check_mul_overflow(var->xres_virtual, var->yres_virtual, &unused))
|
| 291 | return -EINVAL;
|
| 292 |
|
| 293 | ret = info->fbops->fb_check_var(var, info);
|
| 294 |
|
| 295 | if (ret)
|
| 296 | return ret;
|
| 297 |
|
| 298 |
|
| 299 | if (var->xres_virtual < var->xres ||
|
| 300 | var->yres_virtual < var->yres) {
|
| 301 | pr_warn("WARNING: fbcon: Driver '%s' missed to adjust virtual screen size (%ux%u vs. %ux%u)\n",
|
| 302 | info->fix.id,
|
| 303 | var->xres_virtual, var->yres_virtual,
|
| 304 | var->xres, var->yres);
|
| 305 | return -EINVAL;
|
| 306 | }
|
| 307 |
|
| 308 | if ((var->activate & FB_ACTIVATE_MASK) != FB_ACTIVATE_NOW)
|
| 309 | return 0;
|
| 310 |
|
| 311 | if (info->fbops->fb_get_caps) {
|
| 312 | ret = fb_check_caps(info, var, activate);
|
| 313 |
|
| 314 | if (ret)
|
| 315 | return ret;
|
| 316 | }
|
| 317 |
|
| 318 | old_var = info->var;
|
| 319 | info->var = *var;
|
| 320 |
|
| 321 | if (info->fbops->fb_set_par) {
|
| 322 | ret = info->fbops->fb_set_par(info);
|
| 323 |
|
| 324 | if (ret) {
|
| 325 | info->var = old_var;
|
| 326 | printk(KERN_WARNING "detected "
|
| 327 | "fb_set_par error, "
|
| 328 | "error code: %d\n", ret);
|
| 329 | return ret;
|
| 330 | }
|
| 331 | }
|
| 332 |
|
| 333 | fb_pan_display(info, &info->var);
|
| 334 | fb_set_cmap(&info->cmap, info);
|
| 335 | fb_var_to_videomode(&mode, &info->var);
|
| 336 |
|
| 337 | if (info->modelist.prev && info->modelist.next &&
|
| 338 | !list_empty(&info->modelist))
|
| 339 | ret = fb_add_videomode(&mode, &info->modelist);
|
| 340 |
|
| 341 | if (ret) {
|
| 342 | info->var = old_var;
|
| 343 | return ret;
|
| 344 | }
|
| 345 |
|
| 346 | fb_lcd_notify_mode_change(info, &mode);
|
| 347 |
|
| 348 | return 0;
|
| 349 | }
|
| 350 | EXPORT_SYMBOL(fb_set_var);
|
| 351 |
|
| 352 | int fb_set_var_from_user(struct fb_info *info, struct fb_var_screeninfo *var)
|
| 353 | {
|
| 354 | int ret = fbcon_modechange_possible(info, var);
|
| 355 |
|
| 356 | if (!ret)
|
| 357 | ret = fb_set_var(info, var);
|
| 358 | if (!ret)
|
| 359 | fbcon_update_vcs(info, var->activate & FB_ACTIVATE_ALL);
|
| 360 |
|
| 361 | return ret;
|
| 362 | }
|
| 363 | EXPORT_SYMBOL(fb_set_var_from_user);
|
| 364 |
|
| 365 | static void fb_lcd_notify_blank(struct fb_info *info)
|
| 366 | {
|
| 367 | int power;
|
| 368 |
|
| 369 | switch (info->blank) {
|
| 370 | case FB_BLANK_UNBLANK:
|
| 371 | power = LCD_POWER_ON;
|
| 372 | break;
|
| 373 |
|
| 374 | case FB_BLANK_NORMAL:
|
| 375 | power = LCD_POWER_REDUCED;
|
| 376 | break;
|
| 377 | case FB_BLANK_VSYNC_SUSPEND:
|
| 378 | power = LCD_POWER_REDUCED_VSYNC_SUSPEND;
|
| 379 | break;
|
| 380 |
|
| 381 | case FB_BLANK_HSYNC_SUSPEND:
|
| 382 | case FB_BLANK_POWERDOWN:
|
| 383 | default:
|
| 384 | power = LCD_POWER_OFF;
|
| 385 | break;
|
| 386 | }
|
| 387 |
|
| 388 | lcd_notify_blank_all(info->device, power);
|
| 389 | }
|
| 390 |
|
| 391 | static void fb_ledtrig_backlight_notify_blank(struct fb_info *info)
|
| 392 | {
|
| 393 | if (info->blank == FB_BLANK_UNBLANK)
|
| 394 | ledtrig_backlight_blank(false);
|
| 395 | else
|
| 396 | ledtrig_backlight_blank(true);
|
| 397 | }
|
| 398 |
|
| 399 | int fb_blank(struct fb_info *info, int blank)
|
| 400 | {
|
| 401 | int old_blank = info->blank;
|
| 402 | int ret;
|
| 403 |
|
| 404 | if (!info->fbops->fb_blank)
|
| 405 | return -EINVAL;
|
| 406 |
|
| 407 | if (blank > FB_BLANK_POWERDOWN)
|
| 408 | blank = FB_BLANK_POWERDOWN;
|
| 409 |
|
| 410 | info->blank = blank;
|
| 411 |
|
| 412 | ret = info->fbops->fb_blank(blank, info);
|
| 413 | if (ret)
|
| 414 | goto err;
|
| 415 |
|
| 416 | fb_bl_notify_blank(info, old_blank);
|
| 417 | fb_lcd_notify_blank(info);
|
| 418 | fb_ledtrig_backlight_notify_blank(info);
|
| 419 |
|
| 420 | return 0;
|
| 421 |
|
| 422 | err:
|
| 423 | info->blank = old_blank;
|
| 424 | return ret;
|
| 425 | }
|
| 426 | EXPORT_SYMBOL(fb_blank);
|
| 427 |
|
| 428 | int fb_blank_from_user(struct fb_info *info, int blank)
|
| 429 | {
|
| 430 | int ret = fb_blank(info, blank);
|
| 431 |
|
| 432 |
|
| 433 | fbcon_fb_blanked(info, blank);
|
| 434 |
|
| 435 | return ret;
|
| 436 | }
|
| 437 |
|
| 438 | static int fb_check_foreignness(struct fb_info *fi)
|
| 439 | {
|
| 440 | const bool foreign_endian = fi->flags & FBINFO_FOREIGN_ENDIAN;
|
| 441 |
|
| 442 | fi->flags &= ~FBINFO_FOREIGN_ENDIAN;
|
| 443 |
|
| 444 | #ifdef __BIG_ENDIAN
|
| 445 | fi->flags |= foreign_endian ? 0 : FBINFO_BE_MATH;
|
| 446 | #else
|
| 447 | fi->flags |= foreign_endian ? FBINFO_BE_MATH : 0;
|
| 448 | #endif
|
| 449 |
|
| 450 | if (fi->flags & FBINFO_BE_MATH && !fb_be_math(fi)) {
|
| 451 | pr_err("%s: enable CONFIG_FB_BIG_ENDIAN to "
|
| 452 | "support this framebuffer\n", fi->fix.id);
|
| 453 | return -ENOSYS;
|
| 454 | } else if (!(fi->flags & FBINFO_BE_MATH) && fb_be_math(fi)) {
|
| 455 | pr_err("%s: enable CONFIG_FB_LITTLE_ENDIAN to "
|
| 456 | "support this framebuffer\n", fi->fix.id);
|
| 457 | return -ENOSYS;
|
| 458 | }
|
| 459 |
|
| 460 | return 0;
|
| 461 | }
|
| 462 |
|
| 463 | static int do_register_framebuffer(struct fb_info *fb_info)
|
| 464 | {
|
| 465 | int i, err = 0;
|
| 466 | struct fb_videomode mode;
|
| 467 |
|
| 468 | if (fb_check_foreignness(fb_info))
|
| 469 | return -ENOSYS;
|
| 470 |
|
| 471 | if (num_registered_fb == FB_MAX)
|
| 472 | return -ENXIO;
|
| 473 |
|
| 474 | for (i = 0 ; i < FB_MAX; i++)
|
| 475 | if (!registered_fb[i])
|
| 476 | break;
|
| 477 |
|
| 478 | if (i >= FB_MAX)
|
| 479 | return -ENXIO;
|
| 480 |
|
| 481 | if (!fb_info->modelist.prev || !fb_info->modelist.next)
|
| 482 | INIT_LIST_HEAD(&fb_info->modelist);
|
| 483 |
|
| 484 | fb_var_to_videomode(&mode, &fb_info->var);
|
| 485 | err = fb_add_videomode(&mode, &fb_info->modelist);
|
| 486 | if (err < 0)
|
| 487 | return err;
|
| 488 |
|
| 489 | fb_info->node = i;
|
| 490 | refcount_set(&fb_info->count, 1);
|
| 491 | mutex_init(&fb_info->lock);
|
| 492 | mutex_init(&fb_info->mm_lock);
|
| 493 |
|
| 494 | |
| 495 | * With an fb_blank callback present, we assume that the
|
| 496 | * display is blank, so that fb_blank() enables it on the
|
| 497 | * first modeset.
|
| 498 |
|
| 499 | if (fb_info->fbops->fb_blank)
|
| 500 | fb_info->blank = FB_BLANK_POWERDOWN;
|
| 501 |
|
| 502 | fb_device_create(fb_info);
|
| 503 |
|
| 504 | if (fb_info->pixmap.addr == NULL) {
|
| 505 | fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL);
|
| 506 | if (fb_info->pixmap.addr) {
|
| 507 | fb_info->pixmap.size = FBPIXMAPSIZE;
|
| 508 | fb_info->pixmap.buf_align = 1;
|
| 509 | fb_info->pixmap.scan_align = 1;
|
| 510 | fb_info->pixmap.access_align = 32;
|
| 511 | fb_info->pixmap.flags = FB_PIXMAP_DEFAULT;
|
| 512 | }
|
| 513 | }
|
| 514 | fb_info->pixmap.offset = 0;
|
| 515 |
|
| 516 | if (bitmap_empty(fb_info->pixmap.blit_x, FB_MAX_BLIT_WIDTH))
|
| 517 | bitmap_fill(fb_info->pixmap.blit_x, FB_MAX_BLIT_WIDTH);
|
| 518 |
|
| 519 | if (bitmap_empty(fb_info->pixmap.blit_y, FB_MAX_BLIT_HEIGHT))
|
| 520 | bitmap_fill(fb_info->pixmap.blit_y, FB_MAX_BLIT_HEIGHT);
|
| 521 |
|
| 522 | if (fb_info->skip_vt_switch)
|
| 523 | pm_vt_switch_required(fb_info->device, false);
|
| 524 | else
|
| 525 | pm_vt_switch_required(fb_info->device, true);
|
| 526 |
|
| 527 | num_registered_fb++;
|
| 528 | registered_fb[i] = fb_info;
|
| 529 |
|
| 530 | #ifdef CONFIG_GUMSTIX_AM200EPD
|
| 531 | {
|
| 532 | struct fb_event event;
|
| 533 | event.info = fb_info;
|
| 534 | fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
|
| 535 | }
|
| 536 | #endif
|
| 537 |
|
| 538 | return fbcon_fb_registered(fb_info);
|
| 539 | }
|
| 540 |
|
| 541 | static void unbind_console(struct fb_info *fb_info)
|
| 542 | {
|
| 543 | int i = fb_info->node;
|
| 544 |
|
| 545 | if (WARN_ON(i < 0 || i >= FB_MAX || registered_fb[i] != fb_info))
|
| 546 | return;
|
| 547 |
|
| 548 | fbcon_fb_unbind(fb_info);
|
| 549 | }
|
| 550 |
|
| 551 | static void unlink_framebuffer(struct fb_info *fb_info)
|
| 552 | {
|
| 553 | int i;
|
| 554 |
|
| 555 | i = fb_info->node;
|
| 556 | if (WARN_ON(i < 0 || i >= FB_MAX || registered_fb[i] != fb_info))
|
| 557 | return;
|
| 558 |
|
| 559 | fb_device_destroy(fb_info);
|
| 560 | pm_vt_switch_unregister(fb_info->device);
|
| 561 | unbind_console(fb_info);
|
| 562 | }
|
| 563 |
|
| 564 | static void do_unregister_framebuffer(struct fb_info *fb_info)
|
| 565 | {
|
| 566 | unlink_framebuffer(fb_info);
|
| 567 | if (fb_info->pixmap.addr &&
|
| 568 | (fb_info->pixmap.flags & FB_PIXMAP_DEFAULT)) {
|
| 569 | kfree(fb_info->pixmap.addr);
|
| 570 | fb_info->pixmap.addr = NULL;
|
| 571 | }
|
| 572 |
|
| 573 | fbcon_delete_modelist(&fb_info->modelist);
|
| 574 | fb_destroy_modelist(&fb_info->modelist);
|
| 575 | registered_fb[fb_info->node] = NULL;
|
| 576 | num_registered_fb--;
|
| 577 | #ifdef CONFIG_GUMSTIX_AM200EPD
|
| 578 | {
|
| 579 | struct fb_event event;
|
| 580 | event.info = fb_info;
|
| 581 | fb_notifier_call_chain(FB_EVENT_FB_UNREGISTERED, &event);
|
| 582 | }
|
| 583 | #endif
|
| 584 | fbcon_fb_unregistered(fb_info);
|
| 585 |
|
| 586 |
|
| 587 | put_fb_info(fb_info);
|
| 588 | }
|
| 589 |
|
| 590 | |
| 591 | * register_framebuffer - registers a frame buffer device
|
| 592 | * @fb_info: frame buffer info structure
|
| 593 | *
|
| 594 | * Registers a frame buffer device @fb_info.
|
| 595 | *
|
| 596 | * Returns negative errno on error, or zero for success.
|
| 597 | *
|
| 598 |
|
| 599 | int
|
| 600 | register_framebuffer(struct fb_info *fb_info)
|
| 601 | {
|
| 602 | int ret;
|
| 603 |
|
| 604 | mutex_lock(®istration_lock);
|
| 605 | ret = do_register_framebuffer(fb_info);
|
| 606 | mutex_unlock(®istration_lock);
|
| 607 |
|
| 608 | return ret;
|
| 609 | }
|
| 610 | EXPORT_SYMBOL(register_framebuffer);
|
| 611 |
|
| 612 | |
| 613 | * unregister_framebuffer - releases a frame buffer device
|
| 614 | * @fb_info: frame buffer info structure
|
| 615 | *
|
| 616 | * Unregisters a frame buffer device @fb_info.
|
| 617 | *
|
| 618 | * Returns negative errno on error, or zero for success.
|
| 619 | *
|
| 620 | * This function will also notify the framebuffer console
|
| 621 | * to release the driver.
|
| 622 | *
|
| 623 | * This is meant to be called within a driver's module_exit()
|
| 624 | * function. If this is called outside module_exit(), ensure
|
| 625 | * that the driver implements fb_open() and fb_release() to
|
| 626 | * check that no processes are using the device.
|
| 627 |
|
| 628 | void
|
| 629 | unregister_framebuffer(struct fb_info *fb_info)
|
| 630 | {
|
| 631 | mutex_lock(®istration_lock);
|
| 632 | do_unregister_framebuffer(fb_info);
|
| 633 | mutex_unlock(®istration_lock);
|
| 634 | }
|
| 635 | EXPORT_SYMBOL(unregister_framebuffer);
|
| 636 |
|
| 637 | static void devm_unregister_framebuffer(void *data)
|
| 638 | {
|
| 639 | struct fb_info *info = data;
|
| 640 |
|
| 641 | unregister_framebuffer(info);
|
| 642 | }
|
| 643 |
|
| 644 | |
| 645 | * devm_register_framebuffer - resource-managed frame buffer device registration
|
| 646 | * @dev: device the framebuffer belongs to
|
| 647 | * @fb_info: frame buffer info structure
|
| 648 | *
|
| 649 | * Registers a frame buffer device @fb_info to device @dev.
|
| 650 | *
|
| 651 | * Returns negative errno on error, or zero for success.
|
| 652 | *
|
| 653 |
|
| 654 | int
|
| 655 | devm_register_framebuffer(struct device *dev, struct fb_info *fb_info)
|
| 656 | {
|
| 657 | int ret;
|
| 658 |
|
| 659 | ret = register_framebuffer(fb_info);
|
| 660 | if (ret)
|
| 661 | return ret;
|
| 662 |
|
| 663 | return devm_add_action_or_reset(dev, devm_unregister_framebuffer, fb_info);
|
| 664 | }
|
| 665 | EXPORT_SYMBOL(devm_register_framebuffer);
|
| 666 |
|
| 667 | |
| 668 | * fb_set_suspend - low level driver signals suspend
|
| 669 | * @info: framebuffer affected
|
| 670 | * @state: 0 = resuming, !=0 = suspending
|
| 671 | *
|
| 672 | * This is meant to be used by low level drivers to
|
| 673 | * signal suspend/resume to the core & clients.
|
| 674 | * It must be called with the console semaphore held
|
| 675 |
|
| 676 | void fb_set_suspend(struct fb_info *info, int state)
|
| 677 | {
|
| 678 | WARN_CONSOLE_UNLOCKED();
|
| 679 |
|
| 680 | if (state) {
|
| 681 | fbcon_suspended(info);
|
| 682 | info->state = FBINFO_STATE_SUSPENDED;
|
| 683 | } else {
|
| 684 | info->state = FBINFO_STATE_RUNNING;
|
| 685 | fbcon_resumed(info);
|
| 686 | }
|
| 687 | }
|
| 688 | EXPORT_SYMBOL(fb_set_suspend);
|
| 689 |
|
| 690 | |
| 691 | * fb_switch_outputs - framebuffer got the outputs from vga-switcheroo
|
| 692 | * @info: framebuffer
|
| 693 |
|
| 694 | void fb_switch_outputs(struct fb_info *info)
|
| 695 | {
|
| 696 | fbcon_remap_all(info);
|
| 697 | }
|
| 698 | EXPORT_SYMBOL(fb_switch_outputs);
|
| 699 |
|
| 700 | static int __init fbmem_init(void)
|
| 701 | {
|
| 702 | int ret;
|
| 703 |
|
| 704 | fb_class = class_create("graphics");
|
| 705 | if (IS_ERR(fb_class)) {
|
| 706 | ret = PTR_ERR(fb_class);
|
| 707 | pr_err("Unable to create fb class; errno = %d\n", ret);
|
| 708 | goto err_fb_class;
|
| 709 | }
|
| 710 |
|
| 711 | ret = fb_init_procfs();
|
| 712 | if (ret)
|
| 713 | goto err_class_destroy;
|
| 714 |
|
| 715 | ret = fb_register_chrdev();
|
| 716 | if (ret)
|
| 717 | goto err_fb_cleanup_procfs;
|
| 718 |
|
| 719 | fb_console_init();
|
| 720 |
|
| 721 | return 0;
|
| 722 |
|
| 723 | err_fb_cleanup_procfs:
|
| 724 | fb_cleanup_procfs();
|
| 725 | err_class_destroy:
|
| 726 | class_destroy(fb_class);
|
| 727 | err_fb_class:
|
| 728 | fb_class = NULL;
|
| 729 | return ret;
|
| 730 | }
|
| 731 |
|
| 732 | #ifdef MODULE
|
| 733 | static void __exit fbmem_exit(void)
|
| 734 | {
|
| 735 | fb_console_exit();
|
| 736 | fb_unregister_chrdev();
|
| 737 | fb_cleanup_procfs();
|
| 738 | class_destroy(fb_class);
|
| 739 | }
|
| 740 |
|
| 741 | module_init(fbmem_init);
|
| 742 | module_exit(fbmem_exit);
|
| 743 | MODULE_LICENSE("GPL");
|
| 744 | MODULE_DESCRIPTION("Framebuffer base");
|
| 745 | #else
|
| 746 | subsys_initcall(fbmem_init);
|
| 747 | #endif
|
| 748 |
|
| 749 | int fb_new_modelist(struct fb_info *info)
|
| 750 | {
|
| 751 | struct fb_var_screeninfo var = info->var;
|
| 752 | struct list_head *pos, *n;
|
| 753 | struct fb_modelist *modelist;
|
| 754 | struct fb_videomode *m, mode;
|
| 755 | int err;
|
| 756 |
|
| 757 | list_for_each_safe(pos, n, &info->modelist) {
|
| 758 | modelist = list_entry(pos, struct fb_modelist, list);
|
| 759 | m = &modelist->mode;
|
| 760 | fb_videomode_to_var(&var, m);
|
| 761 | var.activate = FB_ACTIVATE_TEST;
|
| 762 | err = fb_set_var(info, &var);
|
| 763 | fb_var_to_videomode(&mode, &var);
|
| 764 | if (err || !fb_mode_is_equal(m, &mode)) {
|
| 765 | list_del(pos);
|
| 766 | kfree(pos);
|
| 767 | }
|
| 768 | }
|
| 769 |
|
| 770 | if (list_empty(&info->modelist))
|
| 771 | return 1;
|
| 772 |
|
| 773 | |
| 774 | * The new modelist may not contain the current mode (info->var), and
|
| 775 | * fbcon_new_modelist() below only re-points consoles mapped to this
|
| 776 | * framebuffer. Add the current mode here so info->var keeps a match
|
| 777 | * even when fbcon is unbound.
|
| 778 |
|
| 779 | if (!fb_match_mode(&info->var, &info->modelist)) {
|
| 780 | fb_var_to_videomode(&mode, &info->var);
|
| 781 | if (fb_add_videomode(&mode, &info->modelist))
|
| 782 | return 1;
|
| 783 | }
|
| 784 |
|
| 785 | fbcon_new_modelist(info);
|
| 786 |
|
| 787 | return 0;
|
| 788 | }
|
| 789 |
|
| 790 | bool fb_modesetting_disabled(const char *drvname)
|
| 791 | {
|
| 792 | bool fwonly = video_firmware_drivers_only();
|
| 793 |
|
| 794 | if (fwonly)
|
| 795 | pr_warn("Driver %s not loading because of nomodeset parameter\n",
|
| 796 | drvname);
|
| 797 |
|
| 798 | return fwonly;
|
| 799 | }
|
| 800 | EXPORT_SYMBOL(fb_modesetting_disabled);
|
| 801 |
|
| 802 | MODULE_LICENSE("GPL");
|