diff options
| author | Kukjin Kim <kgene.kim@samsung.com> | 2014-05-30 13:36:49 -0400 |
|---|---|---|
| committer | Kukjin Kim <kgene.kim@samsung.com> | 2014-05-30 13:36:49 -0400 |
| commit | fced6dee29f6fb143fe16ea90331176ff77e6120 (patch) | |
| tree | 5b6e57e7a757adc2a6518ce291a4d2914397b917 /drivers/video/fbdev/core | |
| parent | bfed1074f213051e94648bfad0d0611a16d81366 (diff) | |
| parent | be1f7c8d7e2bc8b8c76846aa6f276e8d2ef8975a (diff) | |
Merge branch 'v3.16-next/cleanup-samsung' into v3.16-next/platform-exynos
Diffstat (limited to 'drivers/video/fbdev/core')
| -rw-r--r-- | drivers/video/fbdev/core/Makefile | 16 | ||||
| -rw-r--r-- | drivers/video/fbdev/core/cfbcopyarea.c | 434 | ||||
| -rw-r--r-- | drivers/video/fbdev/core/cfbfillrect.c | 371 | ||||
| -rw-r--r-- | drivers/video/fbdev/core/cfbimgblt.c | 313 | ||||
| -rw-r--r-- | drivers/video/fbdev/core/fb_ddc.c | 119 | ||||
| -rw-r--r-- | drivers/video/fbdev/core/fb_defio.c | 245 | ||||
| -rw-r--r-- | drivers/video/fbdev/core/fb_draw.h | 186 | ||||
| -rw-r--r-- | drivers/video/fbdev/core/fb_notify.c | 47 | ||||
| -rw-r--r-- | drivers/video/fbdev/core/fb_sys_fops.c | 104 | ||||
| -rw-r--r-- | drivers/video/fbdev/core/fbcmap.c | 362 | ||||
| -rw-r--r-- | drivers/video/fbdev/core/fbcvt.c | 379 | ||||
| -rw-r--r-- | drivers/video/fbdev/core/fbmem.c | 2002 | ||||
| -rw-r--r-- | drivers/video/fbdev/core/fbmon.c | 1592 | ||||
| -rw-r--r-- | drivers/video/fbdev/core/fbsysfs.c | 586 | ||||
| -rw-r--r-- | drivers/video/fbdev/core/modedb.c | 1137 | ||||
| -rw-r--r-- | drivers/video/fbdev/core/svgalib.c | 672 | ||||
| -rw-r--r-- | drivers/video/fbdev/core/syscopyarea.c | 377 | ||||
| -rw-r--r-- | drivers/video/fbdev/core/sysfillrect.c | 335 | ||||
| -rw-r--r-- | drivers/video/fbdev/core/sysimgblt.c | 288 |
19 files changed, 9565 insertions, 0 deletions
diff --git a/drivers/video/fbdev/core/Makefile b/drivers/video/fbdev/core/Makefile new file mode 100644 index 000000000000..fa306538dac2 --- /dev/null +++ b/drivers/video/fbdev/core/Makefile | |||
| @@ -0,0 +1,16 @@ | |||
| 1 | obj-y += fb_notify.o | ||
| 2 | obj-$(CONFIG_FB) += fb.o | ||
| 3 | fb-y := fbmem.o fbmon.o fbcmap.o fbsysfs.o \ | ||
| 4 | modedb.o fbcvt.o | ||
| 5 | fb-objs := $(fb-y) | ||
| 6 | |||
| 7 | obj-$(CONFIG_FB_CFB_FILLRECT) += cfbfillrect.o | ||
| 8 | obj-$(CONFIG_FB_CFB_COPYAREA) += cfbcopyarea.o | ||
| 9 | obj-$(CONFIG_FB_CFB_IMAGEBLIT) += cfbimgblt.o | ||
| 10 | obj-$(CONFIG_FB_SYS_FILLRECT) += sysfillrect.o | ||
| 11 | obj-$(CONFIG_FB_SYS_COPYAREA) += syscopyarea.o | ||
| 12 | obj-$(CONFIG_FB_SYS_IMAGEBLIT) += sysimgblt.o | ||
| 13 | obj-$(CONFIG_FB_SYS_FOPS) += fb_sys_fops.o | ||
| 14 | obj-$(CONFIG_FB_SVGALIB) += svgalib.o | ||
| 15 | obj-$(CONFIG_FB_DDC) += fb_ddc.o | ||
| 16 | obj-$(CONFIG_FB_DEFERRED_IO) += fb_defio.o | ||
diff --git a/drivers/video/fbdev/core/cfbcopyarea.c b/drivers/video/fbdev/core/cfbcopyarea.c new file mode 100644 index 000000000000..bcb57235fcc7 --- /dev/null +++ b/drivers/video/fbdev/core/cfbcopyarea.c | |||
| @@ -0,0 +1,434 @@ | |||
| 1 | /* | ||
| 2 | * Generic function for frame buffer with packed pixels of any depth. | ||
| 3 | * | ||
| 4 | * Copyright (C) 1999-2005 James Simmons <jsimmons@www.infradead.org> | ||
| 5 | * | ||
| 6 | * This file is subject to the terms and conditions of the GNU General Public | ||
| 7 | * License. See the file COPYING in the main directory of this archive for | ||
| 8 | * more details. | ||
| 9 | * | ||
| 10 | * NOTES: | ||
| 11 | * | ||
| 12 | * This is for cfb packed pixels. Iplan and such are incorporated in the | ||
| 13 | * drivers that need them. | ||
| 14 | * | ||
| 15 | * FIXME | ||
| 16 | * | ||
| 17 | * Also need to add code to deal with cards endians that are different than | ||
| 18 | * the native cpu endians. I also need to deal with MSB position in the word. | ||
| 19 | * | ||
| 20 | * The two functions or copying forward and backward could be split up like | ||
| 21 | * the ones for filling, i.e. in aligned and unaligned versions. This would | ||
| 22 | * help moving some redundant computations and branches out of the loop, too. | ||
| 23 | */ | ||
| 24 | |||
| 25 | #include <linux/module.h> | ||
| 26 | #include <linux/kernel.h> | ||
| 27 | #include <linux/string.h> | ||
| 28 | #include <linux/fb.h> | ||
| 29 | #include <asm/types.h> | ||
| 30 | #include <asm/io.h> | ||
| 31 | #include "fb_draw.h" | ||
| 32 | |||
| 33 | #if BITS_PER_LONG == 32 | ||
| 34 | # define FB_WRITEL fb_writel | ||
| 35 | # define FB_READL fb_readl | ||
| 36 | #else | ||
| 37 | # define FB_WRITEL fb_writeq | ||
| 38 | # define FB_READL fb_readq | ||
| 39 | #endif | ||
| 40 | |||
| 41 | /* | ||
| 42 | * Generic bitwise copy algorithm | ||
| 43 | */ | ||
| 44 | |||
| 45 | static void | ||
| 46 | bitcpy(struct fb_info *p, unsigned long __iomem *dst, unsigned dst_idx, | ||
| 47 | const unsigned long __iomem *src, unsigned src_idx, int bits, | ||
| 48 | unsigned n, u32 bswapmask) | ||
| 49 | { | ||
| 50 | unsigned long first, last; | ||
| 51 | int const shift = dst_idx-src_idx; | ||
| 52 | |||
| 53 | #if 0 | ||
| 54 | /* | ||
| 55 | * If you suspect bug in this function, compare it with this simple | ||
| 56 | * memmove implementation. | ||
| 57 | */ | ||
| 58 | fb_memmove((char *)dst + ((dst_idx & (bits - 1))) / 8, | ||
| 59 | (char *)src + ((src_idx & (bits - 1))) / 8, n / 8); | ||
| 60 | return; | ||
| 61 | #endif | ||
| 62 | |||
| 63 | first = fb_shifted_pixels_mask_long(p, dst_idx, bswapmask); | ||
| 64 | last = ~fb_shifted_pixels_mask_long(p, (dst_idx+n) % bits, bswapmask); | ||
| 65 | |||
| 66 | if (!shift) { | ||
| 67 | // Same alignment for source and dest | ||
| 68 | |||
| 69 | if (dst_idx+n <= bits) { | ||
| 70 | // Single word | ||
| 71 | if (last) | ||
| 72 | first &= last; | ||
| 73 | FB_WRITEL( comp( FB_READL(src), FB_READL(dst), first), dst); | ||
| 74 | } else { | ||
| 75 | // Multiple destination words | ||
| 76 | |||
| 77 | // Leading bits | ||
| 78 | if (first != ~0UL) { | ||
| 79 | FB_WRITEL( comp( FB_READL(src), FB_READL(dst), first), dst); | ||
| 80 | dst++; | ||
| 81 | src++; | ||
| 82 | n -= bits - dst_idx; | ||
| 83 | } | ||
| 84 | |||
| 85 | // Main chunk | ||
| 86 | n /= bits; | ||
| 87 | while (n >= 8) { | ||
| 88 | FB_WRITEL(FB_READL(src++), dst++); | ||
| 89 | FB_WRITEL(FB_READL(src++), dst++); | ||
| 90 | FB_WRITEL(FB_READL(src++), dst++); | ||
| 91 | FB_WRITEL(FB_READL(src++), dst++); | ||
| 92 | FB_WRITEL(FB_READL(src++), dst++); | ||
| 93 | FB_WRITEL(FB_READL(src++), dst++); | ||
| 94 | FB_WRITEL(FB_READL(src++), dst++); | ||
| 95 | FB_WRITEL(FB_READL(src++), dst++); | ||
| 96 | n -= 8; | ||
| 97 | } | ||
| 98 | while (n--) | ||
| 99 | FB_WRITEL(FB_READL(src++), dst++); | ||
| 100 | |||
| 101 | // Trailing bits | ||
| 102 | if (last) | ||
| 103 | FB_WRITEL( comp( FB_READL(src), FB_READL(dst), last), dst); | ||
| 104 | } | ||
| 105 | } else { | ||
| 106 | /* Different alignment for source and dest */ | ||
| 107 | unsigned long d0, d1; | ||
| 108 | int m; | ||
| 109 | |||
| 110 | int const left = shift & (bits - 1); | ||
| 111 | int const right = -shift & (bits - 1); | ||
| 112 | |||
| 113 | if (dst_idx+n <= bits) { | ||
| 114 | // Single destination word | ||
| 115 | if (last) | ||
| 116 | first &= last; | ||
| 117 | d0 = FB_READL(src); | ||
| 118 | d0 = fb_rev_pixels_in_long(d0, bswapmask); | ||
| 119 | if (shift > 0) { | ||
| 120 | // Single source word | ||
| 121 | d0 <<= left; | ||
| 122 | } else if (src_idx+n <= bits) { | ||
| 123 | // Single source word | ||
| 124 | d0 >>= right; | ||
| 125 | } else { | ||
| 126 | // 2 source words | ||
| 127 | d1 = FB_READL(src + 1); | ||
| 128 | d1 = fb_rev_pixels_in_long(d1, bswapmask); | ||
| 129 | d0 = d0 >> right | d1 << left; | ||
| 130 | } | ||
| 131 | d0 = fb_rev_pixels_in_long(d0, bswapmask); | ||
| 132 | FB_WRITEL(comp(d0, FB_READL(dst), first), dst); | ||
| 133 | } else { | ||
| 134 | // Multiple destination words | ||
| 135 | /** We must always remember the last value read, because in case | ||
| 136 | SRC and DST overlap bitwise (e.g. when moving just one pixel in | ||
| 137 | 1bpp), we always collect one full long for DST and that might | ||
| 138 | overlap with the current long from SRC. We store this value in | ||
| 139 | 'd0'. */ | ||
| 140 | d0 = FB_READL(src++); | ||
| 141 | d0 = fb_rev_pixels_in_long(d0, bswapmask); | ||
| 142 | // Leading bits | ||
| 143 | if (shift > 0) { | ||
| 144 | // Single source word | ||
| 145 | d1 = d0; | ||
| 146 | d0 <<= left; | ||
| 147 | n -= bits - dst_idx; | ||
| 148 | } else { | ||
| 149 | // 2 source words | ||
| 150 | d1 = FB_READL(src++); | ||
| 151 | d1 = fb_rev_pixels_in_long(d1, bswapmask); | ||
| 152 | |||
| 153 | d0 = d0 >> right | d1 << left; | ||
| 154 | n -= bits - dst_idx; | ||
| 155 | } | ||
| 156 | d0 = fb_rev_pixels_in_long(d0, bswapmask); | ||
| 157 | FB_WRITEL(comp(d0, FB_READL(dst), first), dst); | ||
| 158 | d0 = d1; | ||
| 159 | dst++; | ||
| 160 | |||
| 161 | // Main chunk | ||
| 162 | m = n % bits; | ||
| 163 | n /= bits; | ||
| 164 | while ((n >= 4) && !bswapmask) { | ||
| 165 | d1 = FB_READL(src++); | ||
| 166 | FB_WRITEL(d0 >> right | d1 << left, dst++); | ||
| 167 | d0 = d1; | ||
| 168 | d1 = FB_READL(src++); | ||
| 169 | FB_WRITEL(d0 >> right | d1 << left, dst++); | ||
| 170 | d0 = d1; | ||
| 171 | d1 = FB_READL(src++); | ||
| 172 | FB_WRITEL(d0 >> right | d1 << left, dst++); | ||
| 173 | d0 = d1; | ||
| 174 | d1 = FB_READL(src++); | ||
| 175 | FB_WRITEL(d0 >> right | d1 << left, dst++); | ||
| 176 | d0 = d1; | ||
| 177 | n -= 4; | ||
| 178 | } | ||
| 179 | while (n--) { | ||
| 180 | d1 = FB_READL(src++); | ||
| 181 | d1 = fb_rev_pixels_in_long(d1, bswapmask); | ||
| 182 | d0 = d0 >> right | d1 << left; | ||
| 183 | d0 = fb_rev_pixels_in_long(d0, bswapmask); | ||
| 184 | FB_WRITEL(d0, dst++); | ||
| 185 | d0 = d1; | ||
| 186 | } | ||
| 187 | |||
| 188 | // Trailing bits | ||
| 189 | if (m) { | ||
| 190 | if (m <= bits - right) { | ||
| 191 | // Single source word | ||
| 192 | d0 >>= right; | ||
| 193 | } else { | ||
| 194 | // 2 source words | ||
| 195 | d1 = FB_READL(src); | ||
| 196 | d1 = fb_rev_pixels_in_long(d1, | ||
| 197 | bswapmask); | ||
| 198 | d0 = d0 >> right | d1 << left; | ||
| 199 | } | ||
| 200 | d0 = fb_rev_pixels_in_long(d0, bswapmask); | ||
| 201 | FB_WRITEL(comp(d0, FB_READL(dst), last), dst); | ||
| 202 | } | ||
| 203 | } | ||
| 204 | } | ||
| 205 | } | ||
| 206 | |||
| 207 | /* | ||
| 208 | * Generic bitwise copy algorithm, operating backward | ||
| 209 | */ | ||
| 210 | |||
| 211 | static void | ||
| 212 | bitcpy_rev(struct fb_info *p, unsigned long __iomem *dst, unsigned dst_idx, | ||
| 213 | const unsigned long __iomem *src, unsigned src_idx, int bits, | ||
| 214 | unsigned n, u32 bswapmask) | ||
| 215 | { | ||
| 216 | unsigned long first, last; | ||
| 217 | int shift; | ||
| 218 | |||
| 219 | #if 0 | ||
| 220 | /* | ||
| 221 | * If you suspect bug in this function, compare it with this simple | ||
| 222 | * memmove implementation. | ||
| 223 | */ | ||
| 224 | fb_memmove((char *)dst + ((dst_idx & (bits - 1))) / 8, | ||
| 225 | (char *)src + ((src_idx & (bits - 1))) / 8, n / 8); | ||
| 226 | return; | ||
| 227 | #endif | ||
| 228 | |||
| 229 | dst += (dst_idx + n - 1) / bits; | ||
| 230 | src += (src_idx + n - 1) / bits; | ||
| 231 | dst_idx = (dst_idx + n - 1) % bits; | ||
| 232 | src_idx = (src_idx + n - 1) % bits; | ||
| 233 | |||
| 234 | shift = dst_idx-src_idx; | ||
| 235 | |||
| 236 | first = ~fb_shifted_pixels_mask_long(p, (dst_idx + 1) % bits, bswapmask); | ||
| 237 | last = fb_shifted_pixels_mask_long(p, (bits + dst_idx + 1 - n) % bits, bswapmask); | ||
| 238 | |||
| 239 | if (!shift) { | ||
| 240 | // Same alignment for source and dest | ||
| 241 | |||
| 242 | if ((unsigned long)dst_idx+1 >= n) { | ||
| 243 | // Single word | ||
| 244 | if (first) | ||
| 245 | last &= first; | ||
| 246 | FB_WRITEL( comp( FB_READL(src), FB_READL(dst), last), dst); | ||
| 247 | } else { | ||
| 248 | // Multiple destination words | ||
| 249 | |||
| 250 | // Leading bits | ||
| 251 | if (first) { | ||
| 252 | FB_WRITEL( comp( FB_READL(src), FB_READL(dst), first), dst); | ||
| 253 | dst--; | ||
| 254 | src--; | ||
| 255 | n -= dst_idx+1; | ||
| 256 | } | ||
| 257 | |||
| 258 | // Main chunk | ||
| 259 | n /= bits; | ||
| 260 | while (n >= 8) { | ||
| 261 | FB_WRITEL(FB_READL(src--), dst--); | ||
| 262 | FB_WRITEL(FB_READL(src--), dst--); | ||
| 263 | FB_WRITEL(FB_READL(src--), dst--); | ||
| 264 | FB_WRITEL(FB_READL(src--), dst--); | ||
| 265 | FB_WRITEL(FB_READL(src--), dst--); | ||
| 266 | FB_WRITEL(FB_READL(src--), dst--); | ||
| 267 | FB_WRITEL(FB_READL(src--), dst--); | ||
| 268 | FB_WRITEL(FB_READL(src--), dst--); | ||
| 269 | n -= 8; | ||
| 270 | } | ||
| 271 | while (n--) | ||
| 272 | FB_WRITEL(FB_READL(src--), dst--); | ||
| 273 | |||
| 274 | // Trailing bits | ||
| 275 | if (last != -1UL) | ||
| 276 | FB_WRITEL( comp( FB_READL(src), FB_READL(dst), last), dst); | ||
| 277 | } | ||
| 278 | } else { | ||
| 279 | // Different alignment for source and dest | ||
| 280 | unsigned long d0, d1; | ||
| 281 | int m; | ||
| 282 | |||
| 283 | int const left = shift & (bits-1); | ||
| 284 | int const right = -shift & (bits-1); | ||
| 285 | |||
| 286 | if ((unsigned long)dst_idx+1 >= n) { | ||
| 287 | // Single destination word | ||
| 288 | if (first) | ||
| 289 | last &= first; | ||
| 290 | d0 = FB_READL(src); | ||
| 291 | if (shift < 0) { | ||
| 292 | // Single source word | ||
| 293 | d0 >>= right; | ||
| 294 | } else if (1+(unsigned long)src_idx >= n) { | ||
| 295 | // Single source word | ||
| 296 | d0 <<= left; | ||
| 297 | } else { | ||
| 298 | // 2 source words | ||
| 299 | d1 = FB_READL(src - 1); | ||
| 300 | d1 = fb_rev_pixels_in_long(d1, bswapmask); | ||
| 301 | d0 = d0 << left | d1 >> right; | ||
| 302 | } | ||
| 303 | d0 = fb_rev_pixels_in_long(d0, bswapmask); | ||
| 304 | FB_WRITEL(comp(d0, FB_READL(dst), last), dst); | ||
| 305 | } else { | ||
| 306 | // Multiple destination words | ||
| 307 | /** We must always remember the last value read, because in case | ||
| 308 | SRC and DST overlap bitwise (e.g. when moving just one pixel in | ||
| 309 | 1bpp), we always collect one full long for DST and that might | ||
| 310 | overlap with the current long from SRC. We store this value in | ||
| 311 | 'd0'. */ | ||
| 312 | |||
| 313 | d0 = FB_READL(src--); | ||
| 314 | d0 = fb_rev_pixels_in_long(d0, bswapmask); | ||
| 315 | // Leading bits | ||
| 316 | if (shift < 0) { | ||
| 317 | // Single source word | ||
| 318 | d1 = d0; | ||
| 319 | d0 >>= right; | ||
| 320 | } else { | ||
| 321 | // 2 source words | ||
| 322 | d1 = FB_READL(src--); | ||
| 323 | d1 = fb_rev_pixels_in_long(d1, bswapmask); | ||
| 324 | d0 = d0 << left | d1 >> right; | ||
| 325 | } | ||
| 326 | d0 = fb_rev_pixels_in_long(d0, bswapmask); | ||
| 327 | FB_WRITEL(comp(d0, FB_READL(dst), first), dst); | ||
| 328 | d0 = d1; | ||
| 329 | dst--; | ||
| 330 | n -= dst_idx+1; | ||
| 331 | |||
| 332 | // Main chunk | ||
| 333 | m = n % bits; | ||
| 334 | n /= bits; | ||
| 335 | while ((n >= 4) && !bswapmask) { | ||
| 336 | d1 = FB_READL(src--); | ||
| 337 | FB_WRITEL(d0 << left | d1 >> right, dst--); | ||
| 338 | d0 = d1; | ||
| 339 | d1 = FB_READL(src--); | ||
| 340 | FB_WRITEL(d0 << left | d1 >> right, dst--); | ||
| 341 | d0 = d1; | ||
| 342 | d1 = FB_READL(src--); | ||
| 343 | FB_WRITEL(d0 << left | d1 >> right, dst--); | ||
| 344 | d0 = d1; | ||
| 345 | d1 = FB_READL(src--); | ||
| 346 | FB_WRITEL(d0 << left | d1 >> right, dst--); | ||
| 347 | d0 = d1; | ||
| 348 | n -= 4; | ||
| 349 | } | ||
| 350 | while (n--) { | ||
| 351 | d1 = FB_READL(src--); | ||
| 352 | d1 = fb_rev_pixels_in_long(d1, bswapmask); | ||
| 353 | d0 = d0 << left | d1 >> right; | ||
| 354 | d0 = fb_rev_pixels_in_long(d0, bswapmask); | ||
| 355 | FB_WRITEL(d0, dst--); | ||
| 356 | d0 = d1; | ||
| 357 | } | ||
| 358 | |||
| 359 | // Trailing bits | ||
| 360 | if (m) { | ||
| 361 | if (m <= bits - left) { | ||
| 362 | // Single source word | ||
| 363 | d0 <<= left; | ||
| 364 | } else { | ||
| 365 | // 2 source words | ||
| 366 | d1 = FB_READL(src); | ||
| 367 | d1 = fb_rev_pixels_in_long(d1, | ||
| 368 | bswapmask); | ||
| 369 | d0 = d0 << left | d1 >> right; | ||
| 370 | } | ||
| 371 | d0 = fb_rev_pixels_in_long(d0, bswapmask); | ||
| 372 | FB_WRITEL(comp(d0, FB_READL(dst), last), dst); | ||
| 373 | } | ||
| 374 | } | ||
| 375 | } | ||
| 376 | } | ||
| 377 | |||
| 378 | void cfb_copyarea(struct fb_info *p, const struct fb_copyarea *area) | ||
| 379 | { | ||
| 380 | u32 dx = area->dx, dy = area->dy, sx = area->sx, sy = area->sy; | ||
| 381 | u32 height = area->height, width = area->width; | ||
| 382 | unsigned long const bits_per_line = p->fix.line_length*8u; | ||
| 383 | unsigned long __iomem *base = NULL; | ||
| 384 | int bits = BITS_PER_LONG, bytes = bits >> 3; | ||
| 385 | unsigned dst_idx = 0, src_idx = 0, rev_copy = 0; | ||
| 386 | u32 bswapmask = fb_compute_bswapmask(p); | ||
| 387 | |||
| 388 | if (p->state != FBINFO_STATE_RUNNING) | ||
| 389 | return; | ||
| 390 | |||
| 391 | /* if the beginning of the target area might overlap with the end of | ||
| 392 | the source area, be have to copy the area reverse. */ | ||
| 393 | if ((dy == sy && dx > sx) || (dy > sy)) { | ||
| 394 | dy += height; | ||
| 395 | sy += height; | ||
| 396 | rev_copy = 1; | ||
| 397 | } | ||
| 398 | |||
| 399 | // split the base of the framebuffer into a long-aligned address and the | ||
| 400 | // index of the first bit | ||
| 401 | base = (unsigned long __iomem *)((unsigned long)p->screen_base & ~(bytes-1)); | ||
| 402 | dst_idx = src_idx = 8*((unsigned long)p->screen_base & (bytes-1)); | ||
| 403 | // add offset of source and target area | ||
| 404 | dst_idx += dy*bits_per_line + dx*p->var.bits_per_pixel; | ||
| 405 | src_idx += sy*bits_per_line + sx*p->var.bits_per_pixel; | ||
| 406 | |||
| 407 | if (p->fbops->fb_sync) | ||
| 408 | p->fbops->fb_sync(p); | ||
| 409 | |||
| 410 | if (rev_copy) { | ||
| 411 | while (height--) { | ||
| 412 | dst_idx -= bits_per_line; | ||
| 413 | src_idx -= bits_per_line; | ||
| 414 | bitcpy_rev(p, base + (dst_idx / bits), dst_idx % bits, | ||
| 415 | base + (src_idx / bits), src_idx % bits, bits, | ||
| 416 | width*p->var.bits_per_pixel, bswapmask); | ||
| 417 | } | ||
| 418 | } else { | ||
| 419 | while (height--) { | ||
| 420 | bitcpy(p, base + (dst_idx / bits), dst_idx % bits, | ||
| 421 | base + (src_idx / bits), src_idx % bits, bits, | ||
| 422 | width*p->var.bits_per_pixel, bswapmask); | ||
| 423 | dst_idx += bits_per_line; | ||
| 424 | src_idx += bits_per_line; | ||
| 425 | } | ||
| 426 | } | ||
| 427 | } | ||
| 428 | |||
| 429 | EXPORT_SYMBOL(cfb_copyarea); | ||
| 430 | |||
| 431 | MODULE_AUTHOR("James Simmons <jsimmons@users.sf.net>"); | ||
| 432 | MODULE_DESCRIPTION("Generic software accelerated copyarea"); | ||
| 433 | MODULE_LICENSE("GPL"); | ||
| 434 | |||
diff --git a/drivers/video/fbdev/core/cfbfillrect.c b/drivers/video/fbdev/core/cfbfillrect.c new file mode 100644 index 000000000000..ba9f58b2a5e8 --- /dev/null +++ b/drivers/video/fbdev/core/cfbfillrect.c | |||
| @@ -0,0 +1,371 @@ | |||
| 1 | /* | ||
| 2 | * Generic fillrect for frame buffers with packed pixels of any depth. | ||
| 3 | * | ||
| 4 | * Copyright (C) 2000 James Simmons (jsimmons@linux-fbdev.org) | ||
| 5 | * | ||
| 6 | * This file is subject to the terms and conditions of the GNU General Public | ||
| 7 | * License. See the file COPYING in the main directory of this archive for | ||
| 8 | * more details. | ||
| 9 | * | ||
| 10 | * NOTES: | ||
| 11 | * | ||
| 12 | * Also need to add code to deal with cards endians that are different than | ||
| 13 | * the native cpu endians. I also need to deal with MSB position in the word. | ||
| 14 | * | ||
| 15 | */ | ||
| 16 | #include <linux/module.h> | ||
| 17 | #include <linux/string.h> | ||
| 18 | #include <linux/fb.h> | ||
| 19 | #include <asm/types.h> | ||
| 20 | #include "fb_draw.h" | ||
| 21 | |||
| 22 | #if BITS_PER_LONG == 32 | ||
| 23 | # define FB_WRITEL fb_writel | ||
| 24 | # define FB_READL fb_readl | ||
| 25 | #else | ||
| 26 | # define FB_WRITEL fb_writeq | ||
| 27 | # define FB_READL fb_readq | ||
| 28 | #endif | ||
| 29 | |||
| 30 | /* | ||
| 31 | * Aligned pattern fill using 32/64-bit memory accesses | ||
| 32 | */ | ||
| 33 | |||
| 34 | static void | ||
| 35 | bitfill_aligned(struct fb_info *p, unsigned long __iomem *dst, int dst_idx, | ||
| 36 | unsigned long pat, unsigned n, int bits, u32 bswapmask) | ||
| 37 | { | ||
| 38 | unsigned long first, last; | ||
| 39 | |||
| 40 | if (!n) | ||
| 41 | return; | ||
| 42 | |||
| 43 | first = fb_shifted_pixels_mask_long(p, dst_idx, bswapmask); | ||
| 44 | last = ~fb_shifted_pixels_mask_long(p, (dst_idx+n) % bits, bswapmask); | ||
| 45 | |||
| 46 | if (dst_idx+n <= bits) { | ||
| 47 | // Single word | ||
| 48 | if (last) | ||
| 49 | first &= last; | ||
| 50 | FB_WRITEL(comp(pat, FB_READL(dst), first), dst); | ||
| 51 | } else { | ||
| 52 | // Multiple destination words | ||
| 53 | |||
| 54 | // Leading bits | ||
| 55 | if (first!= ~0UL) { | ||
| 56 | FB_WRITEL(comp(pat, FB_READL(dst), first), dst); | ||
| 57 | dst++; | ||
| 58 | n -= bits - dst_idx; | ||
| 59 | } | ||
| 60 | |||
| 61 | // Main chunk | ||
| 62 | n /= bits; | ||
| 63 | while (n >= 8) { | ||
| 64 | FB_WRITEL(pat, dst++); | ||
| 65 | FB_WRITEL(pat, dst++); | ||
| 66 | FB_WRITEL(pat, dst++); | ||
| 67 | FB_WRITEL(pat, dst++); | ||
| 68 | FB_WRITEL(pat, dst++); | ||
| 69 | FB_WRITEL(pat, dst++); | ||
| 70 | FB_WRITEL(pat, dst++); | ||
| 71 | FB_WRITEL(pat, dst++); | ||
| 72 | n -= 8; | ||
| 73 | } | ||
| 74 | while (n--) | ||
| 75 | FB_WRITEL(pat, dst++); | ||
| 76 | |||
| 77 | // Trailing bits | ||
| 78 | if (last) | ||
| 79 | FB_WRITEL(comp(pat, FB_READL(dst), last), dst); | ||
| 80 | } | ||
| 81 | } | ||
| 82 | |||
| 83 | |||
| 84 | /* | ||
| 85 | * Unaligned generic pattern fill using 32/64-bit memory accesses | ||
| 86 | * The pattern must have been expanded to a full 32/64-bit value | ||
| 87 | * Left/right are the appropriate shifts to convert to the pattern to be | ||
| 88 | * used for the next 32/64-bit word | ||
| 89 | */ | ||
| 90 | |||
| 91 | static void | ||
| 92 | bitfill_unaligned(struct fb_info *p, unsigned long __iomem *dst, int dst_idx, | ||
| 93 | unsigned long pat, int left, int right, unsigned n, int bits) | ||
| 94 | { | ||
| 95 | unsigned long first, last; | ||
| 96 | |||
| 97 | if (!n) | ||
| 98 | return; | ||
| 99 | |||
| 100 | first = FB_SHIFT_HIGH(p, ~0UL, dst_idx); | ||
| 101 | last = ~(FB_SHIFT_HIGH(p, ~0UL, (dst_idx+n) % bits)); | ||
| 102 | |||
| 103 | if (dst_idx+n <= bits) { | ||
| 104 | // Single word | ||
| 105 | if (last) | ||
| 106 | first &= last; | ||
| 107 | FB_WRITEL(comp(pat, FB_READL(dst), first), dst); | ||
| 108 | } else { | ||
| 109 | // Multiple destination words | ||
| 110 | // Leading bits | ||
| 111 | if (first) { | ||
| 112 | FB_WRITEL(comp(pat, FB_READL(dst), first), dst); | ||
| 113 | dst++; | ||
| 114 | pat = pat << left | pat >> right; | ||
| 115 | n -= bits - dst_idx; | ||
| 116 | } | ||
| 117 | |||
| 118 | // Main chunk | ||
| 119 | n /= bits; | ||
| 120 | while (n >= 4) { | ||
| 121 | FB_WRITEL(pat, dst++); | ||
| 122 | pat = pat << left | pat >> right; | ||
| 123 | FB_WRITEL(pat, dst++); | ||
| 124 | pat = pat << left | pat >> right; | ||
| 125 | FB_WRITEL(pat, dst++); | ||
| 126 | pat = pat << left | pat >> right; | ||
| 127 | FB_WRITEL(pat, dst++); | ||
| 128 | pat = pat << left | pat >> right; | ||
| 129 | n -= 4; | ||
| 130 | } | ||
| 131 | while (n--) { | ||
| 132 | FB_WRITEL(pat, dst++); | ||
| 133 | pat = pat << left | pat >> right; | ||
| 134 | } | ||
| 135 | |||
| 136 | // Trailing bits | ||
| 137 | if (last) | ||
| 138 | FB_WRITEL(comp(pat, FB_READL(dst), last), dst); | ||
| 139 | } | ||
| 140 | } | ||
| 141 | |||
| 142 | /* | ||
| 143 | * Aligned pattern invert using 32/64-bit memory accesses | ||
| 144 | */ | ||
| 145 | static void | ||
| 146 | bitfill_aligned_rev(struct fb_info *p, unsigned long __iomem *dst, | ||
| 147 | int dst_idx, unsigned long pat, unsigned n, int bits, | ||
| 148 | u32 bswapmask) | ||
| 149 | { | ||
| 150 | unsigned long val = pat, dat; | ||
| 151 | unsigned long first, last; | ||
| 152 | |||
| 153 | if (!n) | ||
| 154 | return; | ||
| 155 | |||
| 156 | first = fb_shifted_pixels_mask_long(p, dst_idx, bswapmask); | ||
| 157 | last = ~fb_shifted_pixels_mask_long(p, (dst_idx+n) % bits, bswapmask); | ||
| 158 | |||
| 159 | if (dst_idx+n <= bits) { | ||
| 160 | // Single word | ||
| 161 | if (last) | ||
| 162 | first &= last; | ||
| 163 | dat = FB_READL(dst); | ||
| 164 | FB_WRITEL(comp(dat ^ val, dat, first), dst); | ||
| 165 | } else { | ||
| 166 | // Multiple destination words | ||
| 167 | // Leading bits | ||
| 168 | if (first!=0UL) { | ||
| 169 | dat = FB_READL(dst); | ||
| 170 | FB_WRITEL(comp(dat ^ val, dat, first), dst); | ||
| 171 | dst++; | ||
| 172 | n -= bits - dst_idx; | ||
| 173 | } | ||
| 174 | |||
| 175 | // Main chunk | ||
| 176 | n /= bits; | ||
| 177 | while (n >= 8) { | ||
| 178 | FB_WRITEL(FB_READL(dst) ^ val, dst); | ||
| 179 | dst++; | ||
| 180 | FB_WRITEL(FB_READL(dst) ^ val, dst); | ||
| 181 | dst++; | ||
| 182 | FB_WRITEL(FB_READL(dst) ^ val, dst); | ||
| 183 | dst++; | ||
| 184 | FB_WRITEL(FB_READL(dst) ^ val, dst); | ||
| 185 | dst++; | ||
| 186 | FB_WRITEL(FB_READL(dst) ^ val, dst); | ||
| 187 | dst++; | ||
| 188 | FB_WRITEL(FB_READL(dst) ^ val, dst); | ||
| 189 | dst++; | ||
| 190 | FB_WRITEL(FB_READL(dst) ^ val, dst); | ||
| 191 | dst++; | ||
| 192 | FB_WRITEL(FB_READL(dst) ^ val, dst); | ||
| 193 | dst++; | ||
| 194 | n -= 8; | ||
| 195 | } | ||
| 196 | while (n--) { | ||
| 197 | FB_WRITEL(FB_READL(dst) ^ val, dst); | ||
| 198 | dst++; | ||
| 199 | } | ||
| 200 | // Trailing bits | ||
| 201 | if (last) { | ||
| 202 | dat = FB_READL(dst); | ||
| 203 | FB_WRITEL(comp(dat ^ val, dat, last), dst); | ||
| 204 | } | ||
| 205 | } | ||
| 206 | } | ||
| 207 | |||
| 208 | |||
| 209 | /* | ||
| 210 | * Unaligned generic pattern invert using 32/64-bit memory accesses | ||
| 211 | * The pattern must have been expanded to a full 32/64-bit value | ||
| 212 | * Left/right are the appropriate shifts to convert to the pattern to be | ||
| 213 | * used for the next 32/64-bit word | ||
| 214 | */ | ||
| 215 | |||
| 216 | static void | ||
| 217 | bitfill_unaligned_rev(struct fb_info *p, unsigned long __iomem *dst, | ||
| 218 | int dst_idx, unsigned long pat, int left, int right, | ||
| 219 | unsigned n, int bits) | ||
| 220 | { | ||
| 221 | unsigned long first, last, dat; | ||
| 222 | |||
| 223 | if (!n) | ||
| 224 | return; | ||
| 225 | |||
| 226 | first = FB_SHIFT_HIGH(p, ~0UL, dst_idx); | ||
| 227 | last = ~(FB_SHIFT_HIGH(p, ~0UL, (dst_idx+n) % bits)); | ||
| 228 | |||
| 229 | if (dst_idx+n <= bits) { | ||
| 230 | // Single word | ||
| 231 | if (last) | ||
| 232 | first &= last; | ||
| 233 | dat = FB_READL(dst); | ||
| 234 | FB_WRITEL(comp(dat ^ pat, dat, first), dst); | ||
| 235 | } else { | ||
| 236 | // Multiple destination words | ||
| 237 | |||
| 238 | // Leading bits | ||
| 239 | if (first != 0UL) { | ||
| 240 | dat = FB_READL(dst); | ||
| 241 | FB_WRITEL(comp(dat ^ pat, dat, first), dst); | ||
| 242 | dst++; | ||
| 243 | pat = pat << left | pat >> right; | ||
| 244 | n -= bits - dst_idx; | ||
| 245 | } | ||
| 246 | |||
| 247 | // Main chunk | ||
| 248 | n /= bits; | ||
| 249 | while (n >= 4) { | ||
| 250 | FB_WRITEL(FB_READL(dst) ^ pat, dst); | ||
| 251 | dst++; | ||
| 252 | pat = pat << left | pat >> right; | ||
| 253 | FB_WRITEL(FB_READL(dst) ^ pat, dst); | ||
| 254 | dst++; | ||
| 255 | pat = pat << left | pat >> right; | ||
| 256 | FB_WRITEL(FB_READL(dst) ^ pat, dst); | ||
| 257 | dst++; | ||
| 258 | pat = pat << left | pat >> right; | ||
| 259 | FB_WRITEL(FB_READL(dst) ^ pat, dst); | ||
| 260 | dst++; | ||
| 261 | pat = pat << left | pat >> right; | ||
| 262 | n -= 4; | ||
| 263 | } | ||
| 264 | while (n--) { | ||
| 265 | FB_WRITEL(FB_READL(dst) ^ pat, dst); | ||
| 266 | dst++; | ||
| 267 | pat = pat << left | pat >> right; | ||
| 268 | } | ||
| 269 | |||
| 270 | // Trailing bits | ||
| 271 | if (last) { | ||
| 272 | dat = FB_READL(dst); | ||
| 273 | FB_WRITEL(comp(dat ^ pat, dat, last), dst); | ||
| 274 | } | ||
| 275 | } | ||
| 276 | } | ||
| 277 | |||
| 278 | void cfb_fillrect(struct fb_info *p, const struct fb_fillrect *rect) | ||
| 279 | { | ||
| 280 | unsigned long pat, pat2, fg; | ||
| 281 | unsigned long width = rect->width, height = rect->height; | ||
| 282 | int bits = BITS_PER_LONG, bytes = bits >> 3; | ||
| 283 | u32 bpp = p->var.bits_per_pixel; | ||
| 284 | unsigned long __iomem *dst; | ||
| 285 | int dst_idx, left; | ||
| 286 | |||
| 287 | if (p->state != FBINFO_STATE_RUNNING) | ||
| 288 | return; | ||
| 289 | |||
| 290 | if (p->fix.visual == FB_VISUAL_TRUECOLOR || | ||
| 291 | p->fix.visual == FB_VISUAL_DIRECTCOLOR ) | ||
| 292 | fg = ((u32 *) (p->pseudo_palette))[rect->color]; | ||
| 293 | else | ||
| 294 | fg = rect->color; | ||
| 295 | |||
| 296 | pat = pixel_to_pat(bpp, fg); | ||
| 297 | |||
| 298 | dst = (unsigned long __iomem *)((unsigned long)p->screen_base & ~(bytes-1)); | ||
| 299 | dst_idx = ((unsigned long)p->screen_base & (bytes - 1))*8; | ||
| 300 | dst_idx += rect->dy*p->fix.line_length*8+rect->dx*bpp; | ||
| 301 | /* FIXME For now we support 1-32 bpp only */ | ||
| 302 | left = bits % bpp; | ||
| 303 | if (p->fbops->fb_sync) | ||
| 304 | p->fbops->fb_sync(p); | ||
| 305 | if (!left) { | ||
| 306 | u32 bswapmask = fb_compute_bswapmask(p); | ||
| 307 | void (*fill_op32)(struct fb_info *p, | ||
| 308 | unsigned long __iomem *dst, int dst_idx, | ||
| 309 | unsigned long pat, unsigned n, int bits, | ||
| 310 | u32 bswapmask) = NULL; | ||
| 311 | |||
| 312 | switch (rect->rop) { | ||
| 313 | case ROP_XOR: | ||
| 314 | fill_op32 = bitfill_aligned_rev; | ||
| 315 | break; | ||
| 316 | case ROP_COPY: | ||
| 317 | fill_op32 = bitfill_aligned; | ||
| 318 | break; | ||
| 319 | default: | ||
| 320 | printk( KERN_ERR "cfb_fillrect(): unknown rop, defaulting to ROP_COPY\n"); | ||
| 321 | fill_op32 = bitfill_aligned; | ||
| 322 | break; | ||
| 323 | } | ||
| 324 | while (height--) { | ||
| 325 | dst += dst_idx >> (ffs(bits) - 1); | ||
| 326 | dst_idx &= (bits - 1); | ||
| 327 | fill_op32(p, dst, dst_idx, pat, width*bpp, bits, | ||
| 328 | bswapmask); | ||
| 329 | dst_idx += p->fix.line_length*8; | ||
| 330 | } | ||
| 331 | } else { | ||
| 332 | int right, r; | ||
| 333 | void (*fill_op)(struct fb_info *p, unsigned long __iomem *dst, | ||
| 334 | int dst_idx, unsigned long pat, int left, | ||
| 335 | int right, unsigned n, int bits) = NULL; | ||
| 336 | #ifdef __LITTLE_ENDIAN | ||
| 337 | right = left; | ||
| 338 | left = bpp - right; | ||
| 339 | #else | ||
| 340 | right = bpp - left; | ||
| 341 | #endif | ||
| 342 | switch (rect->rop) { | ||
| 343 | case ROP_XOR: | ||
| 344 | fill_op = bitfill_unaligned_rev; | ||
| 345 | break; | ||
| 346 | case ROP_COPY: | ||
| 347 | fill_op = bitfill_unaligned; | ||
| 348 | break; | ||
| 349 | default: | ||
| 350 | printk(KERN_ERR "cfb_fillrect(): unknown rop, defaulting to ROP_COPY\n"); | ||
| 351 | fill_op = bitfill_unaligned; | ||
| 352 | break; | ||
| 353 | } | ||
| 354 | while (height--) { | ||
| 355 | dst += dst_idx / bits; | ||
| 356 | dst_idx &= (bits - 1); | ||
| 357 | r = dst_idx % bpp; | ||
| 358 | /* rotate pattern to the correct start position */ | ||
| 359 | pat2 = le_long_to_cpu(rolx(cpu_to_le_long(pat), r, bpp)); | ||
| 360 | fill_op(p, dst, dst_idx, pat2, left, right, | ||
| 361 | width*bpp, bits); | ||
| 362 | dst_idx += p->fix.line_length*8; | ||
| 363 | } | ||
| 364 | } | ||
| 365 | } | ||
| 366 | |||
| 367 | EXPORT_SYMBOL(cfb_fillrect); | ||
| 368 | |||
| 369 | MODULE_AUTHOR("James Simmons <jsimmons@users.sf.net>"); | ||
| 370 | MODULE_DESCRIPTION("Generic software accelerated fill rectangle"); | ||
| 371 | MODULE_LICENSE("GPL"); | ||
diff --git a/drivers/video/fbdev/core/cfbimgblt.c b/drivers/video/fbdev/core/cfbimgblt.c new file mode 100644 index 000000000000..a2bb276a8b24 --- /dev/null +++ b/drivers/video/fbdev/core/cfbimgblt.c | |||
| @@ -0,0 +1,313 @@ | |||
| 1 | /* | ||
| 2 | * Generic BitBLT function for frame buffer with packed pixels of any depth. | ||
| 3 | * | ||
| 4 | * Copyright (C) June 1999 James Simmons | ||
| 5 | * | ||
| 6 | * This file is subject to the terms and conditions of the GNU General Public | ||
| 7 | * License. See the file COPYING in the main directory of this archive for | ||
| 8 | * more details. | ||
| 9 | * | ||
| 10 | * NOTES: | ||
| 11 | * | ||
| 12 | * This function copys a image from system memory to video memory. The | ||
| 13 | * image can be a bitmap where each 0 represents the background color and | ||
| 14 | * each 1 represents the foreground color. Great for font handling. It can | ||
| 15 | * also be a color image. This is determined by image_depth. The color image | ||
| 16 | * must be laid out exactly in the same format as the framebuffer. Yes I know | ||
| 17 | * their are cards with hardware that coverts images of various depths to the | ||
| 18 | * framebuffer depth. But not every card has this. All images must be rounded | ||
| 19 | * up to the nearest byte. For example a bitmap 12 bits wide must be two | ||
| 20 | * bytes width. | ||
| 21 | * | ||
| 22 | * Tony: | ||
| 23 | * Incorporate mask tables similar to fbcon-cfb*.c in 2.4 API. This speeds | ||
| 24 | * up the code significantly. | ||
| 25 | * | ||
| 26 | * Code for depths not multiples of BITS_PER_LONG is still kludgy, which is | ||
| 27 | * still processed a bit at a time. | ||
| 28 | * | ||
| 29 | * Also need to add code to deal with cards endians that are different than | ||
| 30 | * the native cpu endians. I also need to deal with MSB position in the word. | ||
| 31 | */ | ||
| 32 | #include <linux/module.h> | ||
| 33 | #include <linux/string.h> | ||
| 34 | #include <linux/fb.h> | ||
| 35 | #include <asm/types.h> | ||
| 36 | #include "fb_draw.h" | ||
| 37 | |||
| 38 | #define DEBUG | ||
| 39 | |||
| 40 | #ifdef DEBUG | ||
| 41 | #define DPRINTK(fmt, args...) printk(KERN_DEBUG "%s: " fmt,__func__,## args) | ||
| 42 | #else | ||
| 43 | #define DPRINTK(fmt, args...) | ||
| 44 | #endif | ||
| 45 | |||
| 46 | static const u32 cfb_tab8_be[] = { | ||
| 47 | 0x00000000,0x000000ff,0x0000ff00,0x0000ffff, | ||
| 48 | 0x00ff0000,0x00ff00ff,0x00ffff00,0x00ffffff, | ||
| 49 | 0xff000000,0xff0000ff,0xff00ff00,0xff00ffff, | ||
| 50 | 0xffff0000,0xffff00ff,0xffffff00,0xffffffff | ||
| 51 | }; | ||
| 52 | |||
| 53 | static const u32 cfb_tab8_le[] = { | ||
| 54 | 0x00000000,0xff000000,0x00ff0000,0xffff0000, | ||
| 55 | 0x0000ff00,0xff00ff00,0x00ffff00,0xffffff00, | ||
| 56 | 0x000000ff,0xff0000ff,0x00ff00ff,0xffff00ff, | ||
| 57 | 0x0000ffff,0xff00ffff,0x00ffffff,0xffffffff | ||
| 58 | }; | ||
| 59 | |||
| 60 | static const u32 cfb_tab16_be[] = { | ||
| 61 | 0x00000000, 0x0000ffff, 0xffff0000, 0xffffffff | ||
| 62 | }; | ||
| 63 | |||
| 64 | static const u32 cfb_tab16_le[] = { | ||
| 65 | 0x00000000, 0xffff0000, 0x0000ffff, 0xffffffff | ||
| 66 | }; | ||
| 67 | |||
| 68 | static const u32 cfb_tab32[] = { | ||
| 69 | 0x00000000, 0xffffffff | ||
| 70 | }; | ||
| 71 | |||
| 72 | #define FB_WRITEL fb_writel | ||
| 73 | #define FB_READL fb_readl | ||
| 74 | |||
| 75 | static inline void color_imageblit(const struct fb_image *image, | ||
| 76 | struct fb_info *p, u8 __iomem *dst1, | ||
| 77 | u32 start_index, | ||
| 78 | u32 pitch_index) | ||
| 79 | { | ||
| 80 | /* Draw the penguin */ | ||
| 81 | u32 __iomem *dst, *dst2; | ||
| 82 | u32 color = 0, val, shift; | ||
| 83 | int i, n, bpp = p->var.bits_per_pixel; | ||
| 84 | u32 null_bits = 32 - bpp; | ||
| 85 | u32 *palette = (u32 *) p->pseudo_palette; | ||
| 86 | const u8 *src = image->data; | ||
| 87 | u32 bswapmask = fb_compute_bswapmask(p); | ||
| 88 | |||
| 89 | dst2 = (u32 __iomem *) dst1; | ||
| 90 | for (i = image->height; i--; ) { | ||
| 91 | n = image->width; | ||
| 92 | dst = (u32 __iomem *) dst1; | ||
| 93 | shift = 0; | ||
| 94 | val = 0; | ||
| 95 | |||
| 96 | if (start_index) { | ||
| 97 | u32 start_mask = ~fb_shifted_pixels_mask_u32(p, | ||
| 98 | start_index, bswapmask); | ||
| 99 | val = FB_READL(dst) & start_mask; | ||
| 100 | shift = start_index; | ||
| 101 | } | ||
| 102 | while (n--) { | ||
| 103 | if (p->fix.visual == FB_VISUAL_TRUECOLOR || | ||
| 104 | p->fix.visual == FB_VISUAL_DIRECTCOLOR ) | ||
| 105 | color = palette[*src]; | ||
| 106 | else | ||
| 107 | color = *src; | ||
| 108 | color <<= FB_LEFT_POS(p, bpp); | ||
| 109 | val |= FB_SHIFT_HIGH(p, color, shift ^ bswapmask); | ||
| 110 | if (shift >= null_bits) { | ||
| 111 | FB_WRITEL(val, dst++); | ||
| 112 | |||
| 113 | val = (shift == null_bits) ? 0 : | ||
| 114 | FB_SHIFT_LOW(p, color, 32 - shift); | ||
| 115 | } | ||
| 116 | shift += bpp; | ||
| 117 | shift &= (32 - 1); | ||
| 118 | src++; | ||
| 119 | } | ||
| 120 | if (shift) { | ||
| 121 | u32 end_mask = fb_shifted_pixels_mask_u32(p, shift, | ||
| 122 | bswapmask); | ||
| 123 | |||
| 124 | FB_WRITEL((FB_READL(dst) & end_mask) | val, dst); | ||
| 125 | } | ||
| 126 | dst1 += p->fix.line_length; | ||
| 127 | if (pitch_index) { | ||
| 128 | dst2 += p->fix.line_length; | ||
| 129 | dst1 = (u8 __iomem *)((long __force)dst2 & ~(sizeof(u32) - 1)); | ||
| 130 | |||
| 131 | start_index += pitch_index; | ||
| 132 | start_index &= 32 - 1; | ||
| 133 | } | ||
| 134 | } | ||
| 135 | } | ||
| 136 | |||
| 137 | static inline void slow_imageblit(const struct fb_image *image, struct fb_info *p, | ||
| 138 | u8 __iomem *dst1, u32 fgcolor, | ||
| 139 | u32 bgcolor, | ||
| 140 | u32 start_index, | ||
| 141 | u32 pitch_index) | ||
| 142 | { | ||
| 143 | u32 shift, color = 0, bpp = p->var.bits_per_pixel; | ||
| 144 | u32 __iomem *dst, *dst2; | ||
| 145 | u32 val, pitch = p->fix.line_length; | ||
| 146 | u32 null_bits = 32 - bpp; | ||
| 147 | u32 spitch = (image->width+7)/8; | ||
| 148 | const u8 *src = image->data, *s; | ||
| 149 | u32 i, j, l; | ||
| 150 | u32 bswapmask = fb_compute_bswapmask(p); | ||
| 151 | |||
| 152 | dst2 = (u32 __iomem *) dst1; | ||
| 153 | fgcolor <<= FB_LEFT_POS(p, bpp); | ||
| 154 | bgcolor <<= FB_LEFT_POS(p, bpp); | ||
| 155 | |||
| 156 | for (i = image->height; i--; ) { | ||
| 157 | shift = val = 0; | ||
| 158 | l = 8; | ||
| 159 | j = image->width; | ||
| 160 | dst = (u32 __iomem *) dst1; | ||
| 161 | s = src; | ||
| 162 | |||
| 163 | /* write leading bits */ | ||
| 164 | if (start_index) { | ||
| 165 | u32 start_mask = ~fb_shifted_pixels_mask_u32(p, | ||
| 166 | start_index, bswapmask); | ||
| 167 | val = FB_READL(dst) & start_mask; | ||
| 168 | shift = start_index; | ||
| 169 | } | ||
| 170 | |||
| 171 | while (j--) { | ||
| 172 | l--; | ||
| 173 | color = (*s & (1 << l)) ? fgcolor : bgcolor; | ||
| 174 | val |= FB_SHIFT_HIGH(p, color, shift ^ bswapmask); | ||
| 175 | |||
| 176 | /* Did the bitshift spill bits to the next long? */ | ||
| 177 | if (shift >= null_bits) { | ||
| 178 | FB_WRITEL(val, dst++); | ||
| 179 | val = (shift == null_bits) ? 0 : | ||
| 180 | FB_SHIFT_LOW(p, color, 32 - shift); | ||
| 181 | } | ||
| 182 | shift += bpp; | ||
| 183 | shift &= (32 - 1); | ||
| 184 | if (!l) { l = 8; s++; } | ||
| 185 | } | ||
| 186 | |||
| 187 | /* write trailing bits */ | ||
| 188 | if (shift) { | ||
| 189 | u32 end_mask = fb_shifted_pixels_mask_u32(p, shift, | ||
| 190 | bswapmask); | ||
| 191 | |||
| 192 | FB_WRITEL((FB_READL(dst) & end_mask) | val, dst); | ||
| 193 | } | ||
| 194 | |||
| 195 | dst1 += pitch; | ||
| 196 | src += spitch; | ||
| 197 | if (pitch_index) { | ||
| 198 | dst2 += pitch; | ||
| 199 | dst1 = (u8 __iomem *)((long __force)dst2 & ~(sizeof(u32) - 1)); | ||
| 200 | start_index += pitch_index; | ||
| 201 | start_index &= 32 - 1; | ||
| 202 | } | ||
| 203 | |||
| 204 | } | ||
| 205 | } | ||
| 206 | |||
| 207 | /* | ||
| 208 | * fast_imageblit - optimized monochrome color expansion | ||
| 209 | * | ||
| 210 | * Only if: bits_per_pixel == 8, 16, or 32 | ||
| 211 | * image->width is divisible by pixel/dword (ppw); | ||
| 212 | * fix->line_legth is divisible by 4; | ||
| 213 | * beginning and end of a scanline is dword aligned | ||
| 214 | */ | ||
| 215 | static inline void fast_imageblit(const struct fb_image *image, struct fb_info *p, | ||
| 216 | u8 __iomem *dst1, u32 fgcolor, | ||
| 217 | u32 bgcolor) | ||
| 218 | { | ||
| 219 | u32 fgx = fgcolor, bgx = bgcolor, bpp = p->var.bits_per_pixel; | ||
| 220 | u32 ppw = 32/bpp, spitch = (image->width + 7)/8; | ||
| 221 | u32 bit_mask, end_mask, eorx, shift; | ||
| 222 | const char *s = image->data, *src; | ||
| 223 | u32 __iomem *dst; | ||
| 224 | const u32 *tab = NULL; | ||
| 225 | int i, j, k; | ||
| 226 | |||
| 227 | switch (bpp) { | ||
| 228 | case 8: | ||
| 229 | tab = fb_be_math(p) ? cfb_tab8_be : cfb_tab8_le; | ||
| 230 | break; | ||
| 231 | case 16: | ||
| 232 | tab = fb_be_math(p) ? cfb_tab16_be : cfb_tab16_le; | ||
| 233 | break; | ||
| 234 | case 32: | ||
| 235 | default: | ||
| 236 | tab = cfb_tab32; | ||
| 237 | break; | ||
| 238 | } | ||
| 239 | |||
| 240 | for (i = ppw-1; i--; ) { | ||
| 241 | fgx <<= bpp; | ||
| 242 | bgx <<= bpp; | ||
| 243 | fgx |= fgcolor; | ||
| 244 | bgx |= bgcolor; | ||
| 245 | } | ||
| 246 | |||
| 247 | bit_mask = (1 << ppw) - 1; | ||
| 248 | eorx = fgx ^ bgx; | ||
| 249 | k = image->width/ppw; | ||
| 250 | |||
| 251 | for (i = image->height; i--; ) { | ||
| 252 | dst = (u32 __iomem *) dst1, shift = 8; src = s; | ||
| 253 | |||
| 254 | for (j = k; j--; ) { | ||
| 255 | shift -= ppw; | ||
| 256 | end_mask = tab[(*src >> shift) & bit_mask]; | ||
| 257 | FB_WRITEL((end_mask & eorx)^bgx, dst++); | ||
| 258 | if (!shift) { shift = 8; src++; } | ||
| 259 | } | ||
| 260 | dst1 += p->fix.line_length; | ||
| 261 | s += spitch; | ||
| 262 | } | ||
| 263 | } | ||
| 264 | |||
| 265 | void cfb_imageblit(struct fb_info *p, const struct fb_image *image) | ||
| 266 | { | ||
| 267 | u32 fgcolor, bgcolor, start_index, bitstart, pitch_index = 0; | ||
| 268 | u32 bpl = sizeof(u32), bpp = p->var.bits_per_pixel; | ||
| 269 | u32 width = image->width; | ||
| 270 | u32 dx = image->dx, dy = image->dy; | ||
| 271 | u8 __iomem *dst1; | ||
| 272 | |||
| 273 | if (p->state != FBINFO_STATE_RUNNING) | ||
| 274 | return; | ||
| 275 | |||
| 276 | bitstart = (dy * p->fix.line_length * 8) + (dx * bpp); | ||
| 277 | start_index = bitstart & (32 - 1); | ||
| 278 | pitch_index = (p->fix.line_length & (bpl - 1)) * 8; | ||
| 279 | |||
| 280 | bitstart /= 8; | ||
| 281 | bitstart &= ~(bpl - 1); | ||
| 282 | dst1 = p->screen_base + bitstart; | ||
| 283 | |||
| 284 | if (p->fbops->fb_sync) | ||
| 285 | p->fbops->fb_sync(p); | ||
| 286 | |||
| 287 | if (image->depth == 1) { | ||
| 288 | if (p->fix.visual == FB_VISUAL_TRUECOLOR || | ||
| 289 | p->fix.visual == FB_VISUAL_DIRECTCOLOR) { | ||
| 290 | fgcolor = ((u32*)(p->pseudo_palette))[image->fg_color]; | ||
| 291 | bgcolor = ((u32*)(p->pseudo_palette))[image->bg_color]; | ||
| 292 | } else { | ||
| 293 | fgcolor = image->fg_color; | ||
| 294 | bgcolor = image->bg_color; | ||
| 295 | } | ||
| 296 | |||
| 297 | if (32 % bpp == 0 && !start_index && !pitch_index && | ||
| 298 | ((width & (32/bpp-1)) == 0) && | ||
| 299 | bpp >= 8 && bpp <= 32) | ||
| 300 | fast_imageblit(image, p, dst1, fgcolor, bgcolor); | ||
| 301 | else | ||
| 302 | slow_imageblit(image, p, dst1, fgcolor, bgcolor, | ||
| 303 | start_index, pitch_index); | ||
| 304 | } else | ||
| 305 | color_imageblit(image, p, dst1, start_index, pitch_index); | ||
| 306 | } | ||
| 307 | |||
| 308 | EXPORT_SYMBOL(cfb_imageblit); | ||
| 309 | |||
| 310 | MODULE_AUTHOR("James Simmons <jsimmons@users.sf.net>"); | ||
| 311 | MODULE_DESCRIPTION("Generic software accelerated imaging drawing"); | ||
| 312 | MODULE_LICENSE("GPL"); | ||
| 313 | |||
diff --git a/drivers/video/fbdev/core/fb_ddc.c b/drivers/video/fbdev/core/fb_ddc.c new file mode 100644 index 000000000000..94322ccfedde --- /dev/null +++ b/drivers/video/fbdev/core/fb_ddc.c | |||
| @@ -0,0 +1,119 @@ | |||
| 1 | /* | ||
| 2 | * drivers/video/fb_ddc.c - DDC/EDID read support. | ||
| 3 | * | ||
| 4 | * Copyright (C) 2006 Dennis Munsie <dmunsie@cecropia.com> | ||
| 5 | * | ||
| 6 | * This file is subject to the terms and conditions of the GNU General Public | ||
| 7 | * License. See the file COPYING in the main directory of this archive | ||
| 8 | * for more details. | ||
| 9 | */ | ||
| 10 | |||
| 11 | #include <linux/delay.h> | ||
| 12 | #include <linux/device.h> | ||
| 13 | #include <linux/module.h> | ||
| 14 | #include <linux/fb.h> | ||
| 15 | #include <linux/i2c-algo-bit.h> | ||
| 16 | #include <linux/slab.h> | ||
| 17 | |||
| 18 | #include "../edid.h" | ||
| 19 | |||
| 20 | #define DDC_ADDR 0x50 | ||
| 21 | |||
| 22 | static unsigned char *fb_do_probe_ddc_edid(struct i2c_adapter *adapter) | ||
| 23 | { | ||
| 24 | unsigned char start = 0x0; | ||
| 25 | unsigned char *buf = kmalloc(EDID_LENGTH, GFP_KERNEL); | ||
| 26 | struct i2c_msg msgs[] = { | ||
| 27 | { | ||
| 28 | .addr = DDC_ADDR, | ||
| 29 | .flags = 0, | ||
| 30 | .len = 1, | ||
| 31 | .buf = &start, | ||
| 32 | }, { | ||
| 33 | .addr = DDC_ADDR, | ||
| 34 | .flags = I2C_M_RD, | ||
| 35 | .len = EDID_LENGTH, | ||
| 36 | .buf = buf, | ||
| 37 | } | ||
| 38 | }; | ||
| 39 | |||
| 40 | if (!buf) { | ||
| 41 | dev_warn(&adapter->dev, "unable to allocate memory for EDID " | ||
| 42 | "block.\n"); | ||
| 43 | return NULL; | ||
| 44 | } | ||
| 45 | |||
| 46 | if (i2c_transfer(adapter, msgs, 2) == 2) | ||
| 47 | return buf; | ||
| 48 | |||
| 49 | dev_warn(&adapter->dev, "unable to read EDID block.\n"); | ||
| 50 | kfree(buf); | ||
| 51 | return NULL; | ||
| 52 | } | ||
| 53 | |||
| 54 | unsigned char *fb_ddc_read(struct i2c_adapter *adapter) | ||
| 55 | { | ||
| 56 | struct i2c_algo_bit_data *algo_data = adapter->algo_data; | ||
| 57 | unsigned char *edid = NULL; | ||
| 58 | int i, j; | ||
| 59 | |||
| 60 | algo_data->setscl(algo_data->data, 1); | ||
| 61 | |||
| 62 | for (i = 0; i < 3; i++) { | ||
| 63 | /* For some old monitors we need the | ||
| 64 | * following process to initialize/stop DDC | ||
| 65 | */ | ||
| 66 | algo_data->setsda(algo_data->data, 1); | ||
| 67 | msleep(13); | ||
| 68 | |||
| 69 | algo_data->setscl(algo_data->data, 1); | ||
| 70 | for (j = 0; j < 5; j++) { | ||
| 71 | msleep(10); | ||
| 72 | if (algo_data->getscl(algo_data->data)) | ||
| 73 | break; | ||
| 74 | } | ||
| 75 | if (j == 5) | ||
| 76 | continue; | ||
| 77 | |||
| 78 | algo_data->setsda(algo_data->data, 0); | ||
| 79 | msleep(15); | ||
| 80 | algo_data->setscl(algo_data->data, 0); | ||
| 81 | msleep(15); | ||
| 82 | algo_data->setsda(algo_data->data, 1); | ||
| 83 | msleep(15); | ||
| 84 | |||
| 85 | /* Do the real work */ | ||
| 86 | edid = fb_do_probe_ddc_edid(adapter); | ||
| 87 | algo_data->setsda(algo_data->data, 0); | ||
| 88 | algo_data->setscl(algo_data->data, 0); | ||
| 89 | msleep(15); | ||
| 90 | |||
| 91 | algo_data->setscl(algo_data->data, 1); | ||
| 92 | for (j = 0; j < 10; j++) { | ||
| 93 | msleep(10); | ||
| 94 | if (algo_data->getscl(algo_data->data)) | ||
| 95 | break; | ||
| 96 | } | ||
| 97 | |||
| 98 | algo_data->setsda(algo_data->data, 1); | ||
| 99 | msleep(15); | ||
| 100 | algo_data->setscl(algo_data->data, 0); | ||
| 101 | algo_data->setsda(algo_data->data, 0); | ||
| 102 | if (edid) | ||
| 103 | break; | ||
| 104 | } | ||
| 105 | /* Release the DDC lines when done or the Apple Cinema HD display | ||
| 106 | * will switch off | ||
| 107 | */ | ||
| 108 | algo_data->setsda(algo_data->data, 1); | ||
| 109 | algo_data->setscl(algo_data->data, 1); | ||
| 110 | |||
| 111 | adapter->class |= I2C_CLASS_DDC; | ||
| 112 | return edid; | ||
| 113 | } | ||
| 114 | |||
| 115 | EXPORT_SYMBOL_GPL(fb_ddc_read); | ||
| 116 | |||
| 117 | MODULE_AUTHOR("Dennis Munsie <dmunsie@cecropia.com>"); | ||
| 118 | MODULE_DESCRIPTION("DDC/EDID reading support"); | ||
| 119 | MODULE_LICENSE("GPL"); | ||
diff --git a/drivers/video/fbdev/core/fb_defio.c b/drivers/video/fbdev/core/fb_defio.c new file mode 100644 index 000000000000..900aa4ecd617 --- /dev/null +++ b/drivers/video/fbdev/core/fb_defio.c | |||
| @@ -0,0 +1,245 @@ | |||
| 1 | /* | ||
| 2 | * linux/drivers/video/fb_defio.c | ||
| 3 | * | ||
| 4 | * Copyright (C) 2006 Jaya Kumar | ||
| 5 | * | ||
| 6 | * This file is subject to the terms and conditions of the GNU General Public | ||
| 7 | * License. See the file COPYING in the main directory of this archive | ||
| 8 | * for more details. | ||
| 9 | */ | ||
| 10 | |||
| 11 | #include <linux/module.h> | ||
| 12 | #include <linux/kernel.h> | ||
| 13 | #include <linux/errno.h> | ||
| 14 | #include <linux/string.h> | ||
| 15 | #include <linux/mm.h> | ||
| 16 | #include <linux/vmalloc.h> | ||
| 17 | #include <linux/delay.h> | ||
| 18 | #include <linux/interrupt.h> | ||
| 19 | #include <linux/fb.h> | ||
| 20 | #include <linux/list.h> | ||
| 21 | |||
| 22 | /* to support deferred IO */ | ||
| 23 | #include <linux/rmap.h> | ||
| 24 | #include <linux/pagemap.h> | ||
| 25 | |||
| 26 | static struct page *fb_deferred_io_page(struct fb_info *info, unsigned long offs) | ||
| 27 | { | ||
| 28 | void *screen_base = (void __force *) info->screen_base; | ||
| 29 | struct page *page; | ||
| 30 | |||
| 31 | if (is_vmalloc_addr(screen_base + offs)) | ||
| 32 | page = vmalloc_to_page(screen_base + offs); | ||
| 33 | else | ||
| 34 | page = pfn_to_page((info->fix.smem_start + offs) >> PAGE_SHIFT); | ||
| 35 | |||
| 36 | return page; | ||
| 37 | } | ||
| 38 | |||
| 39 | /* this is to find and return the vmalloc-ed fb pages */ | ||
| 40 | static int fb_deferred_io_fault(struct vm_area_struct *vma, | ||
| 41 | struct vm_fault *vmf) | ||
| 42 | { | ||
| 43 | unsigned long offset; | ||
| 44 | struct page *page; | ||
| 45 | struct fb_info *info = vma->vm_private_data; | ||
| 46 | |||
| 47 | offset = vmf->pgoff << PAGE_SHIFT; | ||
| 48 | if (offset >= info->fix.smem_len) | ||
| 49 | return VM_FAULT_SIGBUS; | ||
| 50 | |||
| 51 | page = fb_deferred_io_page(info, offset); | ||
| 52 | if (!page) | ||
| 53 | return VM_FAULT_SIGBUS; | ||
| 54 | |||
| 55 | get_page(page); | ||
| 56 | |||
| 57 | if (vma->vm_file) | ||
| 58 | page->mapping = vma->vm_file->f_mapping; | ||
| 59 | else | ||
| 60 | printk(KERN_ERR "no mapping available\n"); | ||
| 61 | |||
| 62 | BUG_ON(!page->mapping); | ||
| 63 | page->index = vmf->pgoff; | ||
| 64 | |||
| 65 | vmf->page = page; | ||
| 66 | return 0; | ||
| 67 | } | ||
| 68 | |||
| 69 | int fb_deferred_io_fsync(struct file *file, loff_t start, loff_t end, int datasync) | ||
| 70 | { | ||
| 71 | struct fb_info *info = file->private_data; | ||
| 72 | struct inode *inode = file_inode(file); | ||
| 73 | int err = filemap_write_and_wait_range(inode->i_mapping, start, end); | ||
| 74 | if (err) | ||
| 75 | return err; | ||
| 76 | |||
| 77 | /* Skip if deferred io is compiled-in but disabled on this fbdev */ | ||
| 78 | if (!info->fbdefio) | ||
| 79 | return 0; | ||
| 80 | |||
| 81 | mutex_lock(&inode->i_mutex); | ||
| 82 | /* Kill off the delayed work */ | ||
| 83 | cancel_delayed_work_sync(&info->deferred_work); | ||
| 84 | |||
| 85 | /* Run it immediately */ | ||
| 86 | err = schedule_delayed_work(&info->deferred_work, 0); | ||
| 87 | mutex_unlock(&inode->i_mutex); | ||
| 88 | return err; | ||
| 89 | } | ||
| 90 | EXPORT_SYMBOL_GPL(fb_deferred_io_fsync); | ||
| 91 | |||
| 92 | /* vm_ops->page_mkwrite handler */ | ||
| 93 | static int fb_deferred_io_mkwrite(struct vm_area_struct *vma, | ||
| 94 | struct vm_fault *vmf) | ||
| 95 | { | ||
| 96 | struct page *page = vmf->page; | ||
| 97 | struct fb_info *info = vma->vm_private_data; | ||
| 98 | struct fb_deferred_io *fbdefio = info->fbdefio; | ||
| 99 | struct page *cur; | ||
| 100 | |||
| 101 | /* this is a callback we get when userspace first tries to | ||
| 102 | write to the page. we schedule a workqueue. that workqueue | ||
| 103 | will eventually mkclean the touched pages and execute the | ||
| 104 | deferred framebuffer IO. then if userspace touches a page | ||
| 105 | again, we repeat the same scheme */ | ||
| 106 | |||
| 107 | file_update_time(vma->vm_file); | ||
| 108 | |||
| 109 | /* protect against the workqueue changing the page list */ | ||
| 110 | mutex_lock(&fbdefio->lock); | ||
| 111 | |||
| 112 | /* first write in this cycle, notify the driver */ | ||
| 113 | if (fbdefio->first_io && list_empty(&fbdefio->pagelist)) | ||
| 114 | fbdefio->first_io(info); | ||
| 115 | |||
| 116 | /* | ||
| 117 | * We want the page to remain locked from ->page_mkwrite until | ||
| 118 | * the PTE is marked dirty to avoid page_mkclean() being called | ||
| 119 | * before the PTE is updated, which would leave the page ignored | ||
| 120 | * by defio. | ||
| 121 | * Do this by locking the page here and informing the caller | ||
| 122 | * about it with VM_FAULT_LOCKED. | ||
| 123 | */ | ||
| 124 | lock_page(page); | ||
| 125 | |||
| 126 | /* we loop through the pagelist before adding in order | ||
| 127 | to keep the pagelist sorted */ | ||
| 128 | list_for_each_entry(cur, &fbdefio->pagelist, lru) { | ||
| 129 | /* this check is to catch the case where a new | ||
| 130 | process could start writing to the same page | ||
| 131 | through a new pte. this new access can cause the | ||
| 132 | mkwrite even when the original ps's pte is marked | ||
| 133 | writable */ | ||
| 134 | if (unlikely(cur == page)) | ||
| 135 | goto page_already_added; | ||
| 136 | else if (cur->index > page->index) | ||
| 137 | break; | ||
| 138 | } | ||
| 139 | |||
| 140 | list_add_tail(&page->lru, &cur->lru); | ||
| 141 | |||
| 142 | page_already_added: | ||
| 143 | mutex_unlock(&fbdefio->lock); | ||
| 144 | |||
| 145 | /* come back after delay to process the deferred IO */ | ||
| 146 | schedule_delayed_work(&info->deferred_work, fbdefio->delay); | ||
| 147 | return VM_FAULT_LOCKED; | ||
| 148 | } | ||
| 149 | |||
| 150 | static const struct vm_operations_struct fb_deferred_io_vm_ops = { | ||
| 151 | .fault = fb_deferred_io_fault, | ||
| 152 | .page_mkwrite = fb_deferred_io_mkwrite, | ||
| 153 | }; | ||
| 154 | |||
| 155 | static int fb_deferred_io_set_page_dirty(struct page *page) | ||
| 156 | { | ||
| 157 | if (!PageDirty(page)) | ||
| 158 | SetPageDirty(page); | ||
| 159 | return 0; | ||
| 160 | } | ||
| 161 | |||
| 162 | static const struct address_space_operations fb_deferred_io_aops = { | ||
| 163 | .set_page_dirty = fb_deferred_io_set_page_dirty, | ||
| 164 | }; | ||
| 165 | |||
| 166 | static int fb_deferred_io_mmap(struct fb_info *info, struct vm_area_struct *vma) | ||
| 167 | { | ||
| 168 | vma->vm_ops = &fb_deferred_io_vm_ops; | ||
| 169 | vma->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP; | ||
| 170 | if (!(info->flags & FBINFO_VIRTFB)) | ||
| 171 | vma->vm_flags |= VM_IO; | ||
| 172 | vma->vm_private_data = info; | ||
| 173 | return 0; | ||
| 174 | } | ||
| 175 | |||
| 176 | /* workqueue callback */ | ||
| 177 | static void fb_deferred_io_work(struct work_struct *work) | ||
| 178 | { | ||
| 179 | struct fb_info *info = container_of(work, struct fb_info, | ||
| 180 | deferred_work.work); | ||
| 181 | struct list_head *node, *next; | ||
| 182 | struct page *cur; | ||
| 183 | struct fb_deferred_io *fbdefio = info->fbdefio; | ||
| 184 | |||
| 185 | /* here we mkclean the pages, then do all deferred IO */ | ||
| 186 | mutex_lock(&fbdefio->lock); | ||
| 187 | list_for_each_entry(cur, &fbdefio->pagelist, lru) { | ||
| 188 | lock_page(cur); | ||
| 189 | page_mkclean(cur); | ||
| 190 | unlock_page(cur); | ||
| 191 | } | ||
| 192 | |||
| 193 | /* driver's callback with pagelist */ | ||
| 194 | fbdefio->deferred_io(info, &fbdefio->pagelist); | ||
| 195 | |||
| 196 | /* clear the list */ | ||
| 197 | list_for_each_safe(node, next, &fbdefio->pagelist) { | ||
| 198 | list_del(node); | ||
| 199 | } | ||
| 200 | mutex_unlock(&fbdefio->lock); | ||
| 201 | } | ||
| 202 | |||
| 203 | void fb_deferred_io_init(struct fb_info *info) | ||
| 204 | { | ||
| 205 | struct fb_deferred_io *fbdefio = info->fbdefio; | ||
| 206 | |||
| 207 | BUG_ON(!fbdefio); | ||
| 208 | mutex_init(&fbdefio->lock); | ||
| 209 | info->fbops->fb_mmap = fb_deferred_io_mmap; | ||
| 210 | INIT_DELAYED_WORK(&info->deferred_work, fb_deferred_io_work); | ||
| 211 | INIT_LIST_HEAD(&fbdefio->pagelist); | ||
| 212 | if (fbdefio->delay == 0) /* set a default of 1 s */ | ||
| 213 | fbdefio->delay = HZ; | ||
| 214 | } | ||
| 215 | EXPORT_SYMBOL_GPL(fb_deferred_io_init); | ||
| 216 | |||
| 217 | void fb_deferred_io_open(struct fb_info *info, | ||
| 218 | struct inode *inode, | ||
| 219 | struct file *file) | ||
| 220 | { | ||
| 221 | file->f_mapping->a_ops = &fb_deferred_io_aops; | ||
| 222 | } | ||
| 223 | EXPORT_SYMBOL_GPL(fb_deferred_io_open); | ||
| 224 | |||
| 225 | void fb_deferred_io_cleanup(struct fb_info *info) | ||
| 226 | { | ||
| 227 | struct fb_deferred_io *fbdefio = info->fbdefio; | ||
| 228 | struct page *page; | ||
| 229 | int i; | ||
| 230 | |||
| 231 | BUG_ON(!fbdefio); | ||
| 232 | cancel_delayed_work_sync(&info->deferred_work); | ||
| 233 | |||
| 234 | /* clear out the mapping that we setup */ | ||
| 235 | for (i = 0 ; i < info->fix.smem_len; i += PAGE_SIZE) { | ||
| 236 | page = fb_deferred_io_page(info, i); | ||
| 237 | page->mapping = NULL; | ||
| 238 | } | ||
| 239 | |||
| 240 | info->fbops->fb_mmap = NULL; | ||
| 241 | mutex_destroy(&fbdefio->lock); | ||
| 242 | } | ||
| 243 | EXPORT_SYMBOL_GPL(fb_deferred_io_cleanup); | ||
| 244 | |||
| 245 | MODULE_LICENSE("GPL"); | ||
diff --git a/drivers/video/fbdev/core/fb_draw.h b/drivers/video/fbdev/core/fb_draw.h new file mode 100644 index 000000000000..624ee115f129 --- /dev/null +++ b/drivers/video/fbdev/core/fb_draw.h | |||
| @@ -0,0 +1,186 @@ | |||
| 1 | #ifndef _FB_DRAW_H | ||
| 2 | #define _FB_DRAW_H | ||
| 3 | |||
| 4 | #include <asm/types.h> | ||
| 5 | #include <linux/fb.h> | ||
| 6 | #include <linux/bug.h> | ||
| 7 | |||
| 8 | /* | ||
| 9 | * Compose two values, using a bitmask as decision value | ||
| 10 | * This is equivalent to (a & mask) | (b & ~mask) | ||
| 11 | */ | ||
| 12 | |||
| 13 | static inline unsigned long | ||
| 14 | comp(unsigned long a, unsigned long b, unsigned long mask) | ||
| 15 | { | ||
| 16 | return ((a ^ b) & mask) ^ b; | ||
| 17 | } | ||
| 18 | |||
| 19 | /* | ||
| 20 | * Create a pattern with the given pixel's color | ||
| 21 | */ | ||
| 22 | |||
| 23 | #if BITS_PER_LONG == 64 | ||
| 24 | static inline unsigned long | ||
| 25 | pixel_to_pat( u32 bpp, u32 pixel) | ||
| 26 | { | ||
| 27 | switch (bpp) { | ||
| 28 | case 1: | ||
| 29 | return 0xfffffffffffffffful*pixel; | ||
| 30 | case 2: | ||
| 31 | return 0x5555555555555555ul*pixel; | ||
| 32 | case 4: | ||
| 33 | return 0x1111111111111111ul*pixel; | ||
| 34 | case 8: | ||
| 35 | return 0x0101010101010101ul*pixel; | ||
| 36 | case 12: | ||
| 37 | return 0x1001001001001001ul*pixel; | ||
| 38 | case 16: | ||
| 39 | return 0x0001000100010001ul*pixel; | ||
| 40 | case 24: | ||
| 41 | return 0x0001000001000001ul*pixel; | ||
| 42 | case 32: | ||
| 43 | return 0x0000000100000001ul*pixel; | ||
| 44 | default: | ||
| 45 | WARN(1, "pixel_to_pat(): unsupported pixelformat %d\n", bpp); | ||
| 46 | return 0; | ||
| 47 | } | ||
| 48 | } | ||
| 49 | #else | ||
| 50 | static inline unsigned long | ||
| 51 | pixel_to_pat( u32 bpp, u32 pixel) | ||
| 52 | { | ||
| 53 | switch (bpp) { | ||
| 54 | case 1: | ||
| 55 | return 0xfffffffful*pixel; | ||
| 56 | case 2: | ||
| 57 | return 0x55555555ul*pixel; | ||
| 58 | case 4: | ||
| 59 | return 0x11111111ul*pixel; | ||
| 60 | case 8: | ||
| 61 | return 0x01010101ul*pixel; | ||
| 62 | case 12: | ||
| 63 | return 0x01001001ul*pixel; | ||
| 64 | case 16: | ||
| 65 | return 0x00010001ul*pixel; | ||
| 66 | case 24: | ||
| 67 | return 0x01000001ul*pixel; | ||
| 68 | case 32: | ||
| 69 | return 0x00000001ul*pixel; | ||
| 70 | default: | ||
| 71 | WARN(1, "pixel_to_pat(): unsupported pixelformat %d\n", bpp); | ||
| 72 | return 0; | ||
| 73 | } | ||
| 74 | } | ||
| 75 | #endif | ||
| 76 | |||
| 77 | #ifdef CONFIG_FB_CFB_REV_PIXELS_IN_BYTE | ||
| 78 | #if BITS_PER_LONG == 64 | ||
| 79 | #define REV_PIXELS_MASK1 0x5555555555555555ul | ||
| 80 | #define REV_PIXELS_MASK2 0x3333333333333333ul | ||
| 81 | #define REV_PIXELS_MASK4 0x0f0f0f0f0f0f0f0ful | ||
| 82 | #else | ||
| 83 | #define REV_PIXELS_MASK1 0x55555555ul | ||
| 84 | #define REV_PIXELS_MASK2 0x33333333ul | ||
| 85 | #define REV_PIXELS_MASK4 0x0f0f0f0ful | ||
| 86 | #endif | ||
| 87 | |||
| 88 | static inline unsigned long fb_rev_pixels_in_long(unsigned long val, | ||
| 89 | u32 bswapmask) | ||
| 90 | { | ||
| 91 | if (bswapmask & 1) | ||
| 92 | val = comp(val >> 1, val << 1, REV_PIXELS_MASK1); | ||
| 93 | if (bswapmask & 2) | ||
| 94 | val = comp(val >> 2, val << 2, REV_PIXELS_MASK2); | ||
| 95 | if (bswapmask & 3) | ||
| 96 | val = comp(val >> 4, val << 4, REV_PIXELS_MASK4); | ||
| 97 | return val; | ||
| 98 | } | ||
| 99 | |||
| 100 | static inline u32 fb_shifted_pixels_mask_u32(struct fb_info *p, u32 index, | ||
| 101 | u32 bswapmask) | ||
| 102 | { | ||
| 103 | u32 mask; | ||
| 104 | |||
| 105 | if (!bswapmask) { | ||
| 106 | mask = FB_SHIFT_HIGH(p, ~(u32)0, index); | ||
| 107 | } else { | ||
| 108 | mask = 0xff << FB_LEFT_POS(p, 8); | ||
| 109 | mask = FB_SHIFT_LOW(p, mask, index & (bswapmask)) & mask; | ||
| 110 | mask = FB_SHIFT_HIGH(p, mask, index & ~(bswapmask)); | ||
| 111 | #if defined(__i386__) || defined(__x86_64__) | ||
| 112 | /* Shift argument is limited to 0 - 31 on x86 based CPU's */ | ||
| 113 | if(index + bswapmask < 32) | ||
| 114 | #endif | ||
| 115 | mask |= FB_SHIFT_HIGH(p, ~(u32)0, | ||
| 116 | (index + bswapmask) & ~(bswapmask)); | ||
| 117 | } | ||
| 118 | return mask; | ||
| 119 | } | ||
| 120 | |||
| 121 | static inline unsigned long fb_shifted_pixels_mask_long(struct fb_info *p, | ||
| 122 | u32 index, | ||
| 123 | u32 bswapmask) | ||
| 124 | { | ||
| 125 | unsigned long mask; | ||
| 126 | |||
| 127 | if (!bswapmask) { | ||
| 128 | mask = FB_SHIFT_HIGH(p, ~0UL, index); | ||
| 129 | } else { | ||
| 130 | mask = 0xff << FB_LEFT_POS(p, 8); | ||
| 131 | mask = FB_SHIFT_LOW(p, mask, index & (bswapmask)) & mask; | ||
| 132 | mask = FB_SHIFT_HIGH(p, mask, index & ~(bswapmask)); | ||
| 133 | #if defined(__i386__) || defined(__x86_64__) | ||
| 134 | /* Shift argument is limited to 0 - 31 on x86 based CPU's */ | ||
| 135 | if(index + bswapmask < BITS_PER_LONG) | ||
| 136 | #endif | ||
| 137 | mask |= FB_SHIFT_HIGH(p, ~0UL, | ||
| 138 | (index + bswapmask) & ~(bswapmask)); | ||
| 139 | } | ||
| 140 | return mask; | ||
| 141 | } | ||
| 142 | |||
| 143 | |||
| 144 | static inline u32 fb_compute_bswapmask(struct fb_info *info) | ||
| 145 | { | ||
| 146 | u32 bswapmask = 0; | ||
| 147 | unsigned bpp = info->var.bits_per_pixel; | ||
| 148 | |||
| 149 | if ((bpp < 8) && (info->var.nonstd & FB_NONSTD_REV_PIX_IN_B)) { | ||
| 150 | /* | ||
| 151 | * Reversed order of pixel layout in bytes | ||
| 152 | * works only for 1, 2 and 4 bpp | ||
| 153 | */ | ||
| 154 | bswapmask = 7 - bpp + 1; | ||
| 155 | } | ||
| 156 | return bswapmask; | ||
| 157 | } | ||
| 158 | |||
| 159 | #else /* CONFIG_FB_CFB_REV_PIXELS_IN_BYTE */ | ||
| 160 | |||
| 161 | static inline unsigned long fb_rev_pixels_in_long(unsigned long val, | ||
| 162 | u32 bswapmask) | ||
| 163 | { | ||
| 164 | return val; | ||
| 165 | } | ||
| 166 | |||
| 167 | #define fb_shifted_pixels_mask_u32(p, i, b) FB_SHIFT_HIGH((p), ~(u32)0, (i)) | ||
| 168 | #define fb_shifted_pixels_mask_long(p, i, b) FB_SHIFT_HIGH((p), ~0UL, (i)) | ||
| 169 | #define fb_compute_bswapmask(...) 0 | ||
| 170 | |||
| 171 | #endif /* CONFIG_FB_CFB_REV_PIXELS_IN_BYTE */ | ||
| 172 | |||
| 173 | #define cpu_to_le_long _cpu_to_le_long(BITS_PER_LONG) | ||
| 174 | #define _cpu_to_le_long(x) __cpu_to_le_long(x) | ||
| 175 | #define __cpu_to_le_long(x) cpu_to_le##x | ||
| 176 | |||
| 177 | #define le_long_to_cpu _le_long_to_cpu(BITS_PER_LONG) | ||
| 178 | #define _le_long_to_cpu(x) __le_long_to_cpu(x) | ||
| 179 | #define __le_long_to_cpu(x) le##x##_to_cpu | ||
| 180 | |||
| 181 | static inline unsigned long rolx(unsigned long word, unsigned int shift, unsigned int x) | ||
| 182 | { | ||
| 183 | return (word << shift) | (word >> (x - shift)); | ||
| 184 | } | ||
| 185 | |||
| 186 | #endif /* FB_DRAW_H */ | ||
diff --git a/drivers/video/fbdev/core/fb_notify.c b/drivers/video/fbdev/core/fb_notify.c new file mode 100644 index 000000000000..74c2da528884 --- /dev/null +++ b/drivers/video/fbdev/core/fb_notify.c | |||
| @@ -0,0 +1,47 @@ | |||
| 1 | /* | ||
| 2 | * linux/drivers/video/fb_notify.c | ||
| 3 | * | ||
| 4 | * Copyright (C) 2006 Antonino Daplas <adaplas@pol.net> | ||
| 5 | * | ||
| 6 | * 2001 - Documented with DocBook | ||
| 7 | * - Brad Douglas <brad@neruo.com> | ||
| 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 | #include <linux/fb.h> | ||
| 14 | #include <linux/notifier.h> | ||
| 15 | #include <linux/export.h> | ||
| 16 | |||
| 17 | static BLOCKING_NOTIFIER_HEAD(fb_notifier_list); | ||
| 18 | |||
| 19 | /** | ||
| 20 | * fb_register_client - register a client notifier | ||
| 21 | * @nb: notifier block to callback on events | ||
| 22 | */ | ||
| 23 | int fb_register_client(struct notifier_block *nb) | ||
| 24 | { | ||
| 25 | return blocking_notifier_chain_register(&fb_notifier_list, nb); | ||
| 26 | } | ||
| 27 | EXPORT_SYMBOL(fb_register_client); | ||
| 28 | |||
| 29 | /** | ||
| 30 | * fb_unregister_client - unregister a client notifier | ||
| 31 | * @nb: notifier block to callback on events | ||
| 32 | */ | ||
| 33 | int fb_unregister_client(struct notifier_block *nb) | ||
| 34 | { | ||
| 35 | return blocking_notifier_chain_unregister(&fb_notifier_list, nb); | ||
| 36 | } | ||
| 37 | EXPORT_SYMBOL(fb_unregister_client); | ||
| 38 | |||
| 39 | /** | ||
| 40 | * fb_notifier_call_chain - notify clients of fb_events | ||
| 41 | * | ||
| 42 | */ | ||
| 43 | int fb_notifier_call_chain(unsigned long val, void *v) | ||
| 44 | { | ||
| 45 | return blocking_notifier_call_chain(&fb_notifier_list, val, v); | ||
| 46 | } | ||
| 47 | EXPORT_SYMBOL_GPL(fb_notifier_call_chain); | ||
diff --git a/drivers/video/fbdev/core/fb_sys_fops.c b/drivers/video/fbdev/core/fb_sys_fops.c new file mode 100644 index 000000000000..ff275d7f3eaf --- /dev/null +++ b/drivers/video/fbdev/core/fb_sys_fops.c | |||
| @@ -0,0 +1,104 @@ | |||
| 1 | /* | ||
| 2 | * linux/drivers/video/fb_sys_read.c - Generic file operations where | ||
| 3 | * framebuffer is in system RAM | ||
| 4 | * | ||
| 5 | * Copyright (C) 2007 Antonino Daplas <adaplas@pol.net> | ||
| 6 | * | ||
| 7 | * This file is subject to the terms and conditions of the GNU General Public | ||
| 8 | * License. See the file COPYING in the main directory of this archive | ||
| 9 | * for more details. | ||
| 10 | * | ||
| 11 | */ | ||
| 12 | #include <linux/fb.h> | ||
| 13 | #include <linux/module.h> | ||
| 14 | #include <linux/uaccess.h> | ||
| 15 | |||
| 16 | ssize_t fb_sys_read(struct fb_info *info, char __user *buf, size_t count, | ||
| 17 | loff_t *ppos) | ||
| 18 | { | ||
| 19 | unsigned long p = *ppos; | ||
| 20 | void *src; | ||
| 21 | int err = 0; | ||
| 22 | unsigned long total_size; | ||
| 23 | |||
| 24 | if (info->state != FBINFO_STATE_RUNNING) | ||
| 25 | return -EPERM; | ||
| 26 | |||
| 27 | total_size = info->screen_size; | ||
| 28 | |||
| 29 | if (total_size == 0) | ||
| 30 | total_size = info->fix.smem_len; | ||
| 31 | |||
| 32 | if (p >= total_size) | ||
| 33 | return 0; | ||
| 34 | |||
| 35 | if (count >= total_size) | ||
| 36 | count = total_size; | ||
| 37 | |||
| 38 | if (count + p > total_size) | ||
| 39 | count = total_size - p; | ||
| 40 | |||
| 41 | src = (void __force *)(info->screen_base + p); | ||
| 42 | |||
| 43 | if (info->fbops->fb_sync) | ||
| 44 | info->fbops->fb_sync(info); | ||
| 45 | |||
| 46 | if (copy_to_user(buf, src, count)) | ||
| 47 | err = -EFAULT; | ||
| 48 | |||
| 49 | if (!err) | ||
| 50 | *ppos += count; | ||
| 51 | |||
| 52 | return (err) ? err : count; | ||
| 53 | } | ||
| 54 | EXPORT_SYMBOL_GPL(fb_sys_read); | ||
| 55 | |||
| 56 | ssize_t fb_sys_write(struct fb_info *info, const char __user *buf, | ||
| 57 | size_t count, loff_t *ppos) | ||
| 58 | { | ||
| 59 | unsigned long p = *ppos; | ||
| 60 | void *dst; | ||
| 61 | int err = 0; | ||
| 62 | unsigned long total_size; | ||
| 63 | |||
| 64 | if (info->state != FBINFO_STATE_RUNNING) | ||
| 65 | return -EPERM; | ||
| 66 | |||
| 67 | total_size = info->screen_size; | ||
| 68 | |||
| 69 | if (total_size == 0) | ||
| 70 | total_size = info->fix.smem_len; | ||
| 71 | |||
| 72 | if (p > total_size) | ||
| 73 | return -EFBIG; | ||
| 74 | |||
| 75 | if (count > total_size) { | ||
| 76 | err = -EFBIG; | ||
| 77 | count = total_size; | ||
| 78 | } | ||
| 79 | |||
| 80 | if (count + p > total_size) { | ||
| 81 | if (!err) | ||
| 82 | err = -ENOSPC; | ||
| 83 | |||
| 84 | count = total_size - p; | ||
| 85 | } | ||
| 86 | |||
| 87 | dst = (void __force *) (info->screen_base + p); | ||
| 88 | |||
| 89 | if (info->fbops->fb_sync) | ||
| 90 | info->fbops->fb_sync(info); | ||
| 91 | |||
| 92 | if (copy_from_user(dst, buf, count)) | ||
| 93 | err = -EFAULT; | ||
| 94 | |||
| 95 | if (!err) | ||
| 96 | *ppos += count; | ||
| 97 | |||
| 98 | return (err) ? err : count; | ||
| 99 | } | ||
| 100 | EXPORT_SYMBOL_GPL(fb_sys_write); | ||
| 101 | |||
| 102 | MODULE_AUTHOR("Antonino Daplas <adaplas@pol.net>"); | ||
| 103 | MODULE_DESCRIPTION("Generic file read (fb in system RAM)"); | ||
| 104 | MODULE_LICENSE("GPL"); | ||
diff --git a/drivers/video/fbdev/core/fbcmap.c b/drivers/video/fbdev/core/fbcmap.c new file mode 100644 index 000000000000..f89245b8ba8e --- /dev/null +++ b/drivers/video/fbdev/core/fbcmap.c | |||
| @@ -0,0 +1,362 @@ | |||
| 1 | /* | ||
| 2 | * linux/drivers/video/fbcmap.c -- Colormap handling for frame buffer devices | ||
| 3 | * | ||
| 4 | * Created 15 Jun 1997 by Geert Uytterhoeven | ||
| 5 | * | ||
| 6 | * 2001 - Documented with DocBook | ||
| 7 | * - Brad Douglas <brad@neruo.com> | ||
| 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 for | ||
| 11 | * more details. | ||
| 12 | */ | ||
| 13 | |||
| 14 | #include <linux/string.h> | ||
| 15 | #include <linux/module.h> | ||
| 16 | #include <linux/fb.h> | ||
| 17 | #include <linux/slab.h> | ||
| 18 | #include <linux/uaccess.h> | ||
| 19 | |||
| 20 | static u16 red2[] __read_mostly = { | ||
| 21 | 0x0000, 0xaaaa | ||
| 22 | }; | ||
| 23 | static u16 green2[] __read_mostly = { | ||
| 24 | 0x0000, 0xaaaa | ||
| 25 | }; | ||
| 26 | static u16 blue2[] __read_mostly = { | ||
| 27 | 0x0000, 0xaaaa | ||
| 28 | }; | ||
| 29 | |||
| 30 | static u16 red4[] __read_mostly = { | ||
| 31 | 0x0000, 0xaaaa, 0x5555, 0xffff | ||
| 32 | }; | ||
| 33 | static u16 green4[] __read_mostly = { | ||
| 34 | 0x0000, 0xaaaa, 0x5555, 0xffff | ||
| 35 | }; | ||
| 36 | static u16 blue4[] __read_mostly = { | ||
| 37 | 0x0000, 0xaaaa, 0x5555, 0xffff | ||
| 38 | }; | ||
| 39 | |||
| 40 | static u16 red8[] __read_mostly = { | ||
| 41 | 0x0000, 0x0000, 0x0000, 0x0000, 0xaaaa, 0xaaaa, 0xaaaa, 0xaaaa | ||
| 42 | }; | ||
| 43 | static u16 green8[] __read_mostly = { | ||
| 44 | 0x0000, 0x0000, 0xaaaa, 0xaaaa, 0x0000, 0x0000, 0x5555, 0xaaaa | ||
| 45 | }; | ||
| 46 | static u16 blue8[] __read_mostly = { | ||
| 47 | 0x0000, 0xaaaa, 0x0000, 0xaaaa, 0x0000, 0xaaaa, 0x0000, 0xaaaa | ||
| 48 | }; | ||
| 49 | |||
| 50 | static u16 red16[] __read_mostly = { | ||
| 51 | 0x0000, 0x0000, 0x0000, 0x0000, 0xaaaa, 0xaaaa, 0xaaaa, 0xaaaa, | ||
| 52 | 0x5555, 0x5555, 0x5555, 0x5555, 0xffff, 0xffff, 0xffff, 0xffff | ||
| 53 | }; | ||
| 54 | static u16 green16[] __read_mostly = { | ||
| 55 | 0x0000, 0x0000, 0xaaaa, 0xaaaa, 0x0000, 0x0000, 0x5555, 0xaaaa, | ||
| 56 | 0x5555, 0x5555, 0xffff, 0xffff, 0x5555, 0x5555, 0xffff, 0xffff | ||
| 57 | }; | ||
| 58 | static u16 blue16[] __read_mostly = { | ||
| 59 | 0x0000, 0xaaaa, 0x0000, 0xaaaa, 0x0000, 0xaaaa, 0x0000, 0xaaaa, | ||
| 60 | 0x5555, 0xffff, 0x5555, 0xffff, 0x5555, 0xffff, 0x5555, 0xffff | ||
| 61 | }; | ||
| 62 | |||
| 63 | static const struct fb_cmap default_2_colors = { | ||
| 64 | .len=2, .red=red2, .green=green2, .blue=blue2 | ||
| 65 | }; | ||
| 66 | static const struct fb_cmap default_8_colors = { | ||
| 67 | .len=8, .red=red8, .green=green8, .blue=blue8 | ||
| 68 | }; | ||
| 69 | static const struct fb_cmap default_4_colors = { | ||
| 70 | .len=4, .red=red4, .green=green4, .blue=blue4 | ||
| 71 | }; | ||
| 72 | static const struct fb_cmap default_16_colors = { | ||
| 73 | .len=16, .red=red16, .green=green16, .blue=blue16 | ||
| 74 | }; | ||
| 75 | |||
| 76 | |||
| 77 | |||
| 78 | /** | ||
| 79 | * fb_alloc_cmap - allocate a colormap | ||
| 80 | * @cmap: frame buffer colormap structure | ||
| 81 | * @len: length of @cmap | ||
| 82 | * @transp: boolean, 1 if there is transparency, 0 otherwise | ||
| 83 | * @flags: flags for kmalloc memory allocation | ||
| 84 | * | ||
| 85 | * Allocates memory for a colormap @cmap. @len is the | ||
| 86 | * number of entries in the palette. | ||
| 87 | * | ||
| 88 | * Returns negative errno on error, or zero on success. | ||
| 89 | * | ||
| 90 | */ | ||
| 91 | |||
| 92 | int fb_alloc_cmap_gfp(struct fb_cmap *cmap, int len, int transp, gfp_t flags) | ||
| 93 | { | ||
| 94 | int size = len * sizeof(u16); | ||
| 95 | int ret = -ENOMEM; | ||
| 96 | |||
| 97 | if (cmap->len != len) { | ||
| 98 | fb_dealloc_cmap(cmap); | ||
| 99 | if (!len) | ||
| 100 | return 0; | ||
| 101 | |||
| 102 | cmap->red = kmalloc(size, flags); | ||
| 103 | if (!cmap->red) | ||
| 104 | goto fail; | ||
| 105 | cmap->green = kmalloc(size, flags); | ||
| 106 | if (!cmap->green) | ||
| 107 | goto fail; | ||
| 108 | cmap->blue = kmalloc(size, flags); | ||
| 109 | if (!cmap->blue) | ||
| 110 | goto fail; | ||
| 111 | if (transp) { | ||
| 112 | cmap->transp = kmalloc(size, flags); | ||
| 113 | if (!cmap->transp) | ||
| 114 | goto fail; | ||
| 115 | } else { | ||
| 116 | cmap->transp = NULL; | ||
| 117 | } | ||
| 118 | } | ||
| 119 | cmap->start = 0; | ||
| 120 | cmap->len = len; | ||
| 121 | ret = fb_copy_cmap(fb_default_cmap(len), cmap); | ||
| 122 | if (ret) | ||
| 123 | goto fail; | ||
| 124 | return 0; | ||
| 125 | |||
| 126 | fail: | ||
| 127 | fb_dealloc_cmap(cmap); | ||
| 128 | return ret; | ||
| 129 | } | ||
| 130 | |||
| 131 | int fb_alloc_cmap(struct fb_cmap *cmap, int len, int transp) | ||
| 132 | { | ||
| 133 | return fb_alloc_cmap_gfp(cmap, len, transp, GFP_ATOMIC); | ||
| 134 | } | ||
| 135 | |||
| 136 | /** | ||
| 137 | * fb_dealloc_cmap - deallocate a colormap | ||
| 138 | * @cmap: frame buffer colormap structure | ||
| 139 | * | ||
| 140 | * Deallocates a colormap that was previously allocated with | ||
| 141 | * fb_alloc_cmap(). | ||
| 142 | * | ||
| 143 | */ | ||
| 144 | |||
| 145 | void fb_dealloc_cmap(struct fb_cmap *cmap) | ||
| 146 | { | ||
| 147 | kfree(cmap->red); | ||
| 148 | kfree(cmap->green); | ||
| 149 | kfree(cmap->blue); | ||
| 150 | kfree(cmap->transp); | ||
| 151 | |||
| 152 | cmap->red = cmap->green = cmap->blue = cmap->transp = NULL; | ||
| 153 | cmap->len = 0; | ||
| 154 | } | ||
| 155 | |||
| 156 | /** | ||
| 157 | * fb_copy_cmap - copy a colormap | ||
| 158 | * @from: frame buffer colormap structure | ||
| 159 | * @to: frame buffer colormap structure | ||
| 160 | * | ||
| 161 | * Copy contents of colormap from @from to @to. | ||
| 162 | */ | ||
| 163 | |||
| 164 | int fb_copy_cmap(const struct fb_cmap *from, struct fb_cmap *to) | ||
| 165 | { | ||
| 166 | int tooff = 0, fromoff = 0; | ||
| 167 | int size; | ||
| 168 | |||
| 169 | if (to->start > from->start) | ||
| 170 | fromoff = to->start - from->start; | ||
| 171 | else | ||
| 172 | tooff = from->start - to->start; | ||
| 173 | size = to->len - tooff; | ||
| 174 | if (size > (int) (from->len - fromoff)) | ||
| 175 | size = from->len - fromoff; | ||
| 176 | if (size <= 0) | ||
| 177 | return -EINVAL; | ||
| 178 | size *= sizeof(u16); | ||
| 179 | |||
| 180 | memcpy(to->red+tooff, from->red+fromoff, size); | ||
| 181 | memcpy(to->green+tooff, from->green+fromoff, size); | ||
| 182 | memcpy(to->blue+tooff, from->blue+fromoff, size); | ||
| 183 | if (from->transp && to->transp) | ||
| 184 | memcpy(to->transp+tooff, from->transp+fromoff, size); | ||
| 185 | return 0; | ||
| 186 | } | ||
| 187 | |||
| 188 | int fb_cmap_to_user(const struct fb_cmap *from, struct fb_cmap_user *to) | ||
| 189 | { | ||
| 190 | int tooff = 0, fromoff = 0; | ||
| 191 | int size; | ||
| 192 | |||
| 193 | if (to->start > from->start) | ||
| 194 | fromoff = to->start - from->start; | ||
| 195 | else | ||
| 196 | tooff = from->start - to->start; | ||
| 197 | size = to->len - tooff; | ||
| 198 | if (size > (int) (from->len - fromoff)) | ||
| 199 | size = from->len - fromoff; | ||
| 200 | if (size <= 0) | ||
| 201 | return -EINVAL; | ||
| 202 | size *= sizeof(u16); | ||
| 203 | |||
| 204 | if (copy_to_user(to->red+tooff, from->red+fromoff, size)) | ||
| 205 | return -EFAULT; | ||
| 206 | if (copy_to_user(to->green+tooff, from->green+fromoff, size)) | ||
| 207 | return -EFAULT; | ||
| 208 | if (copy_to_user(to->blue+tooff, from->blue+fromoff, size)) | ||
| 209 | return -EFAULT; | ||
| 210 | if (from->transp && to->transp) | ||
| 211 | if (copy_to_user(to->transp+tooff, from->transp+fromoff, size)) | ||
| 212 | return -EFAULT; | ||
| 213 | return 0; | ||
| 214 | } | ||
| 215 | |||
| 216 | /** | ||
| 217 | * fb_set_cmap - set the colormap | ||
| 218 | * @cmap: frame buffer colormap structure | ||
| 219 | * @info: frame buffer info structure | ||
| 220 | * | ||
| 221 | * Sets the colormap @cmap for a screen of device @info. | ||
| 222 | * | ||
| 223 | * Returns negative errno on error, or zero on success. | ||
| 224 | * | ||
| 225 | */ | ||
| 226 | |||
| 227 | int fb_set_cmap(struct fb_cmap *cmap, struct fb_info *info) | ||
| 228 | { | ||
| 229 | int i, start, rc = 0; | ||
| 230 | u16 *red, *green, *blue, *transp; | ||
| 231 | u_int hred, hgreen, hblue, htransp = 0xffff; | ||
| 232 | |||
| 233 | red = cmap->red; | ||
| 234 | green = cmap->green; | ||
| 235 | blue = cmap->blue; | ||
| 236 | transp = cmap->transp; | ||
| 237 | start = cmap->start; | ||
| 238 | |||
| 239 | if (start < 0 || (!info->fbops->fb_setcolreg && | ||
| 240 | !info->fbops->fb_setcmap)) | ||
| 241 | return -EINVAL; | ||
| 242 | if (info->fbops->fb_setcmap) { | ||
| 243 | rc = info->fbops->fb_setcmap(cmap, info); | ||
| 244 | } else { | ||
| 245 | for (i = 0; i < cmap->len; i++) { | ||
| 246 | hred = *red++; | ||
| 247 | hgreen = *green++; | ||
| 248 | hblue = *blue++; | ||
| 249 | if (transp) | ||
| 250 | htransp = *transp++; | ||
| 251 | if (info->fbops->fb_setcolreg(start++, | ||
| 252 | hred, hgreen, hblue, | ||
| 253 | htransp, info)) | ||
| 254 | break; | ||
| 255 | } | ||
| 256 | } | ||
| 257 | if (rc == 0) | ||
| 258 | fb_copy_cmap(cmap, &info->cmap); | ||
| 259 | |||
| 260 | return rc; | ||
| 261 | } | ||
| 262 | |||
| 263 | int fb_set_user_cmap(struct fb_cmap_user *cmap, struct fb_info *info) | ||
| 264 | { | ||
| 265 | int rc, size = cmap->len * sizeof(u16); | ||
| 266 | struct fb_cmap umap; | ||
| 267 | |||
| 268 | if (size < 0 || size < cmap->len) | ||
| 269 | return -E2BIG; | ||
| 270 | |||
| 271 | memset(&umap, 0, sizeof(struct fb_cmap)); | ||
| 272 | rc = fb_alloc_cmap_gfp(&umap, cmap->len, cmap->transp != NULL, | ||
| 273 | GFP_KERNEL); | ||
| 274 | if (rc) | ||
| 275 | return rc; | ||
| 276 | if (copy_from_user(umap.red, cmap->red, size) || | ||
| 277 | copy_from_user(umap.green, cmap->green, size) || | ||
| 278 | copy_from_user(umap.blue, cmap->blue, size) || | ||
| 279 | (cmap->transp && copy_from_user(umap.transp, cmap->transp, size))) { | ||
| 280 | rc = -EFAULT; | ||
| 281 | goto out; | ||
| 282 | } | ||
| 283 | umap.start = cmap->start; | ||
| 284 | if (!lock_fb_info(info)) { | ||
| 285 | rc = -ENODEV; | ||
| 286 | goto out; | ||
| 287 | } | ||
| 288 | |||
| 289 | rc = fb_set_cmap(&umap, info); | ||
| 290 | unlock_fb_info(info); | ||
| 291 | out: | ||
| 292 | fb_dealloc_cmap(&umap); | ||
| 293 | return rc; | ||
| 294 | } | ||
| 295 | |||
| 296 | /** | ||
| 297 | * fb_default_cmap - get default colormap | ||
| 298 | * @len: size of palette for a depth | ||
| 299 | * | ||
| 300 | * Gets the default colormap for a specific screen depth. @len | ||
| 301 | * is the size of the palette for a particular screen depth. | ||
| 302 | * | ||
| 303 | * Returns pointer to a frame buffer colormap structure. | ||
| 304 | * | ||
| 305 | */ | ||
| 306 | |||
| 307 | const struct fb_cmap *fb_default_cmap(int len) | ||
| 308 | { | ||
| 309 | if (len <= 2) | ||
| 310 | return &default_2_colors; | ||
| 311 | if (len <= 4) | ||
| 312 | return &default_4_colors; | ||
| 313 | if (len <= 8) | ||
| 314 | return &default_8_colors; | ||
| 315 | return &default_16_colors; | ||
| 316 | } | ||
| 317 | |||
| 318 | |||
| 319 | /** | ||
| 320 | * fb_invert_cmaps - invert all defaults colormaps | ||
| 321 | * | ||
| 322 | * Invert all default colormaps. | ||
| 323 | * | ||
| 324 | */ | ||
| 325 | |||
| 326 | void fb_invert_cmaps(void) | ||
| 327 | { | ||
| 328 | u_int i; | ||
| 329 | |||
| 330 | for (i = 0; i < ARRAY_SIZE(red2); i++) { | ||
| 331 | red2[i] = ~red2[i]; | ||
| 332 | green2[i] = ~green2[i]; | ||
| 333 | blue2[i] = ~blue2[i]; | ||
| 334 | } | ||
| 335 | for (i = 0; i < ARRAY_SIZE(red4); i++) { | ||
| 336 | red4[i] = ~red4[i]; | ||
| 337 | green4[i] = ~green4[i]; | ||
| 338 | blue4[i] = ~blue4[i]; | ||
| 339 | } | ||
| 340 | for (i = 0; i < ARRAY_SIZE(red8); i++) { | ||
| 341 | red8[i] = ~red8[i]; | ||
| 342 | green8[i] = ~green8[i]; | ||
| 343 | blue8[i] = ~blue8[i]; | ||
| 344 | } | ||
| 345 | for (i = 0; i < ARRAY_SIZE(red16); i++) { | ||
| 346 | red16[i] = ~red16[i]; | ||
| 347 | green16[i] = ~green16[i]; | ||
| 348 | blue16[i] = ~blue16[i]; | ||
| 349 | } | ||
| 350 | } | ||
| 351 | |||
| 352 | |||
| 353 | /* | ||
| 354 | * Visible symbols for modules | ||
| 355 | */ | ||
| 356 | |||
| 357 | EXPORT_SYMBOL(fb_alloc_cmap); | ||
| 358 | EXPORT_SYMBOL(fb_dealloc_cmap); | ||
| 359 | EXPORT_SYMBOL(fb_copy_cmap); | ||
| 360 | EXPORT_SYMBOL(fb_set_cmap); | ||
| 361 | EXPORT_SYMBOL(fb_default_cmap); | ||
| 362 | EXPORT_SYMBOL(fb_invert_cmaps); | ||
diff --git a/drivers/video/fbdev/core/fbcvt.c b/drivers/video/fbdev/core/fbcvt.c new file mode 100644 index 000000000000..7cb715dfc0e1 --- /dev/null +++ b/drivers/video/fbdev/core/fbcvt.c | |||
| @@ -0,0 +1,379 @@ | |||
| 1 | /* | ||
| 2 | * linux/drivers/video/fbcvt.c - VESA(TM) Coordinated Video Timings | ||
| 3 | * | ||
| 4 | * Copyright (C) 2005 Antonino Daplas <adaplas@pol.net> | ||
| 5 | * | ||
| 6 | * Based from the VESA(TM) Coordinated Video Timing Generator by | ||
| 7 | * Graham Loveridge April 9, 2003 available at | ||
| 8 | * http://www.elo.utfsm.cl/~elo212/docs/CVTd6r1.xls | ||
| 9 | * | ||
| 10 | * This file is subject to the terms and conditions of the GNU General Public | ||
| 11 | * License. See the file COPYING in the main directory of this archive | ||
| 12 | * for more details. | ||
| 13 | * | ||
| 14 | */ | ||
| 15 | #include <linux/fb.h> | ||
| 16 | #include <linux/slab.h> | ||
| 17 | |||
| 18 | #define FB_CVT_CELLSIZE 8 | ||
| 19 | #define FB_CVT_GTF_C 40 | ||
| 20 | #define FB_CVT_GTF_J 20 | ||
| 21 | #define FB_CVT_GTF_K 128 | ||
| 22 | #define FB_CVT_GTF_M 600 | ||
| 23 | #define FB_CVT_MIN_VSYNC_BP 550 | ||
| 24 | #define FB_CVT_MIN_VPORCH 3 | ||
| 25 | #define FB_CVT_MIN_BPORCH 6 | ||
| 26 | |||
| 27 | #define FB_CVT_RB_MIN_VBLANK 460 | ||
| 28 | #define FB_CVT_RB_HBLANK 160 | ||
| 29 | #define FB_CVT_RB_V_FPORCH 3 | ||
| 30 | |||
| 31 | #define FB_CVT_FLAG_REDUCED_BLANK 1 | ||
| 32 | #define FB_CVT_FLAG_MARGINS 2 | ||
| 33 | #define FB_CVT_FLAG_INTERLACED 4 | ||
| 34 | |||
| 35 | struct fb_cvt_data { | ||
| 36 | u32 xres; | ||
| 37 | u32 yres; | ||
| 38 | u32 refresh; | ||
| 39 | u32 f_refresh; | ||
| 40 | u32 pixclock; | ||
| 41 | u32 hperiod; | ||
| 42 | u32 hblank; | ||
| 43 | u32 hfreq; | ||
| 44 | u32 htotal; | ||
| 45 | u32 vtotal; | ||
| 46 | u32 vsync; | ||
| 47 | u32 hsync; | ||
| 48 | u32 h_front_porch; | ||
| 49 | u32 h_back_porch; | ||
| 50 | u32 v_front_porch; | ||
| 51 | u32 v_back_porch; | ||
| 52 | u32 h_margin; | ||
| 53 | u32 v_margin; | ||
| 54 | u32 interlace; | ||
| 55 | u32 aspect_ratio; | ||
| 56 | u32 active_pixels; | ||
| 57 | u32 flags; | ||
| 58 | u32 status; | ||
| 59 | }; | ||
| 60 | |||
| 61 | static const unsigned char fb_cvt_vbi_tab[] = { | ||
| 62 | 4, /* 4:3 */ | ||
| 63 | 5, /* 16:9 */ | ||
| 64 | 6, /* 16:10 */ | ||
| 65 | 7, /* 5:4 */ | ||
| 66 | 7, /* 15:9 */ | ||
| 67 | 8, /* reserved */ | ||
| 68 | 9, /* reserved */ | ||
| 69 | 10 /* custom */ | ||
| 70 | }; | ||
| 71 | |||
| 72 | /* returns hperiod * 1000 */ | ||
| 73 | static u32 fb_cvt_hperiod(struct fb_cvt_data *cvt) | ||
| 74 | { | ||
| 75 | u32 num = 1000000000/cvt->f_refresh; | ||
| 76 | u32 den; | ||
| 77 | |||
| 78 | if (cvt->flags & FB_CVT_FLAG_REDUCED_BLANK) { | ||
| 79 | num -= FB_CVT_RB_MIN_VBLANK * 1000; | ||
| 80 | den = 2 * (cvt->yres/cvt->interlace + 2 * cvt->v_margin); | ||
| 81 | } else { | ||
| 82 | num -= FB_CVT_MIN_VSYNC_BP * 1000; | ||
| 83 | den = 2 * (cvt->yres/cvt->interlace + cvt->v_margin * 2 | ||
| 84 | + FB_CVT_MIN_VPORCH + cvt->interlace/2); | ||
| 85 | } | ||
| 86 | |||
| 87 | return 2 * (num/den); | ||
| 88 | } | ||
| 89 | |||
| 90 | /* returns ideal duty cycle * 1000 */ | ||
| 91 | static u32 fb_cvt_ideal_duty_cycle(struct fb_cvt_data *cvt) | ||
| 92 | { | ||
| 93 | u32 c_prime = (FB_CVT_GTF_C - FB_CVT_GTF_J) * | ||
| 94 | (FB_CVT_GTF_K) + 256 * FB_CVT_GTF_J; | ||
| 95 | u32 m_prime = (FB_CVT_GTF_K * FB_CVT_GTF_M); | ||
| 96 | u32 h_period_est = cvt->hperiod; | ||
| 97 | |||
| 98 | return (1000 * c_prime - ((m_prime * h_period_est)/1000))/256; | ||
| 99 | } | ||
| 100 | |||
| 101 | static u32 fb_cvt_hblank(struct fb_cvt_data *cvt) | ||
| 102 | { | ||
| 103 | u32 hblank = 0; | ||
| 104 | |||
| 105 | if (cvt->flags & FB_CVT_FLAG_REDUCED_BLANK) | ||
| 106 | hblank = FB_CVT_RB_HBLANK; | ||
| 107 | else { | ||
| 108 | u32 ideal_duty_cycle = fb_cvt_ideal_duty_cycle(cvt); | ||
| 109 | u32 active_pixels = cvt->active_pixels; | ||
| 110 | |||
| 111 | if (ideal_duty_cycle < 20000) | ||
| 112 | hblank = (active_pixels * 20000)/ | ||
| 113 | (100000 - 20000); | ||
| 114 | else { | ||
| 115 | hblank = (active_pixels * ideal_duty_cycle)/ | ||
| 116 | (100000 - ideal_duty_cycle); | ||
| 117 | } | ||
| 118 | } | ||
| 119 | |||
| 120 | hblank &= ~((2 * FB_CVT_CELLSIZE) - 1); | ||
| 121 | |||
| 122 | return hblank; | ||
| 123 | } | ||
| 124 | |||
| 125 | static u32 fb_cvt_hsync(struct fb_cvt_data *cvt) | ||
| 126 | { | ||
| 127 | u32 hsync; | ||
| 128 | |||
| 129 | if (cvt->flags & FB_CVT_FLAG_REDUCED_BLANK) | ||
| 130 | hsync = 32; | ||
| 131 | else | ||
| 132 | hsync = (FB_CVT_CELLSIZE * cvt->htotal)/100; | ||
| 133 | |||
| 134 | hsync &= ~(FB_CVT_CELLSIZE - 1); | ||
| 135 | return hsync; | ||
| 136 | } | ||
| 137 | |||
| 138 | static u32 fb_cvt_vbi_lines(struct fb_cvt_data *cvt) | ||
| 139 | { | ||
| 140 | u32 vbi_lines, min_vbi_lines, act_vbi_lines; | ||
| 141 | |||
| 142 | if (cvt->flags & FB_CVT_FLAG_REDUCED_BLANK) { | ||
| 143 | vbi_lines = (1000 * FB_CVT_RB_MIN_VBLANK)/cvt->hperiod + 1; | ||
| 144 | min_vbi_lines = FB_CVT_RB_V_FPORCH + cvt->vsync + | ||
| 145 | FB_CVT_MIN_BPORCH; | ||
| 146 | |||
| 147 | } else { | ||
| 148 | vbi_lines = (FB_CVT_MIN_VSYNC_BP * 1000)/cvt->hperiod + 1 + | ||
| 149 | FB_CVT_MIN_VPORCH; | ||
| 150 | min_vbi_lines = cvt->vsync + FB_CVT_MIN_BPORCH + | ||
| 151 | FB_CVT_MIN_VPORCH; | ||
| 152 | } | ||
| 153 | |||
| 154 | if (vbi_lines < min_vbi_lines) | ||
| 155 | act_vbi_lines = min_vbi_lines; | ||
| 156 | else | ||
| 157 | act_vbi_lines = vbi_lines; | ||
| 158 | |||
| 159 | return act_vbi_lines; | ||
| 160 | } | ||
| 161 | |||
| 162 | static u32 fb_cvt_vtotal(struct fb_cvt_data *cvt) | ||
| 163 | { | ||
| 164 | u32 vtotal = cvt->yres/cvt->interlace; | ||
| 165 | |||
| 166 | vtotal += 2 * cvt->v_margin + cvt->interlace/2 + fb_cvt_vbi_lines(cvt); | ||
| 167 | vtotal |= cvt->interlace/2; | ||
| 168 | |||
| 169 | return vtotal; | ||
| 170 | } | ||
| 171 | |||
| 172 | static u32 fb_cvt_pixclock(struct fb_cvt_data *cvt) | ||
| 173 | { | ||
| 174 | u32 pixclock; | ||
| 175 | |||
| 176 | if (cvt->flags & FB_CVT_FLAG_REDUCED_BLANK) | ||
| 177 | pixclock = (cvt->f_refresh * cvt->vtotal * cvt->htotal)/1000; | ||
| 178 | else | ||
| 179 | pixclock = (cvt->htotal * 1000000)/cvt->hperiod; | ||
| 180 | |||
| 181 | pixclock /= 250; | ||
| 182 | pixclock *= 250; | ||
| 183 | pixclock *= 1000; | ||
| 184 | |||
| 185 | return pixclock; | ||
| 186 | } | ||
| 187 | |||
| 188 | static u32 fb_cvt_aspect_ratio(struct fb_cvt_data *cvt) | ||
| 189 | { | ||
| 190 | u32 xres = cvt->xres; | ||
| 191 | u32 yres = cvt->yres; | ||
| 192 | u32 aspect = -1; | ||
| 193 | |||
| 194 | if (xres == (yres * 4)/3 && !((yres * 4) % 3)) | ||
| 195 | aspect = 0; | ||
| 196 | else if (xres == (yres * 16)/9 && !((yres * 16) % 9)) | ||
| 197 | aspect = 1; | ||
| 198 | else if (xres == (yres * 16)/10 && !((yres * 16) % 10)) | ||
| 199 | aspect = 2; | ||
| 200 | else if (xres == (yres * 5)/4 && !((yres * 5) % 4)) | ||
| 201 | aspect = 3; | ||
| 202 | else if (xres == (yres * 15)/9 && !((yres * 15) % 9)) | ||
| 203 | aspect = 4; | ||
| 204 | else { | ||
| 205 | printk(KERN_INFO "fbcvt: Aspect ratio not CVT " | ||
| 206 | "standard\n"); | ||
| 207 | aspect = 7; | ||
| 208 | cvt->status = 1; | ||
| 209 | } | ||
| 210 | |||
| 211 | return aspect; | ||
| 212 | } | ||
| 213 | |||
| 214 | static void fb_cvt_print_name(struct fb_cvt_data *cvt) | ||
| 215 | { | ||
| 216 | u32 pixcount, pixcount_mod; | ||
| 217 | int cnt = 255, offset = 0, read = 0; | ||
| 218 | u8 *buf = kzalloc(256, GFP_KERNEL); | ||
| 219 | |||
| 220 | if (!buf) | ||
| 221 | return; | ||
| 222 | |||
| 223 | pixcount = (cvt->xres * (cvt->yres/cvt->interlace))/1000000; | ||
| 224 | pixcount_mod = (cvt->xres * (cvt->yres/cvt->interlace)) % 1000000; | ||
| 225 | pixcount_mod /= 1000; | ||
| 226 | |||
| 227 | read = snprintf(buf+offset, cnt, "fbcvt: %dx%d@%d: CVT Name - ", | ||
| 228 | cvt->xres, cvt->yres, cvt->refresh); | ||
| 229 | offset += read; | ||
| 230 | cnt -= read; | ||
| 231 | |||
| 232 | if (cvt->status) | ||
| 233 | snprintf(buf+offset, cnt, "Not a CVT standard - %d.%03d Mega " | ||
| 234 | "Pixel Image\n", pixcount, pixcount_mod); | ||
| 235 | else { | ||
| 236 | if (pixcount) { | ||
| 237 | read = snprintf(buf+offset, cnt, "%d", pixcount); | ||
| 238 | cnt -= read; | ||
| 239 | offset += read; | ||
| 240 | } | ||
| 241 | |||
| 242 | read = snprintf(buf+offset, cnt, ".%03dM", pixcount_mod); | ||
| 243 | cnt -= read; | ||
| 244 | offset += read; | ||
| 245 | |||
| 246 | if (cvt->aspect_ratio == 0) | ||
| 247 | read = snprintf(buf+offset, cnt, "3"); | ||
| 248 | else if (cvt->aspect_ratio == 3) | ||
| 249 | read = snprintf(buf+offset, cnt, "4"); | ||
| 250 | else if (cvt->aspect_ratio == 1 || cvt->aspect_ratio == 4) | ||
| 251 | read = snprintf(buf+offset, cnt, "9"); | ||
| 252 | else if (cvt->aspect_ratio == 2) | ||
| 253 | read = snprintf(buf+offset, cnt, "A"); | ||
| 254 | else | ||
| 255 | read = 0; | ||
| 256 | cnt -= read; | ||
| 257 | offset += read; | ||
| 258 | |||
| 259 | if (cvt->flags & FB_CVT_FLAG_REDUCED_BLANK) { | ||
| 260 | read = snprintf(buf+offset, cnt, "-R"); | ||
| 261 | cnt -= read; | ||
| 262 | offset += read; | ||
| 263 | } | ||
| 264 | } | ||
| 265 | |||
| 266 | printk(KERN_INFO "%s\n", buf); | ||
| 267 | kfree(buf); | ||
| 268 | } | ||
| 269 | |||
| 270 | static void fb_cvt_convert_to_mode(struct fb_cvt_data *cvt, | ||
| 271 | struct fb_videomode *mode) | ||
| 272 | { | ||
| 273 | mode->refresh = cvt->f_refresh; | ||
| 274 | mode->pixclock = KHZ2PICOS(cvt->pixclock/1000); | ||
| 275 | mode->left_margin = cvt->h_back_porch; | ||
| 276 | mode->right_margin = cvt->h_front_porch; | ||
| 277 | mode->hsync_len = cvt->hsync; | ||
| 278 | mode->upper_margin = cvt->v_back_porch; | ||
| 279 | mode->lower_margin = cvt->v_front_porch; | ||
| 280 | mode->vsync_len = cvt->vsync; | ||
| 281 | |||
| 282 | mode->sync &= ~(FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT); | ||
| 283 | |||
| 284 | if (cvt->flags & FB_CVT_FLAG_REDUCED_BLANK) | ||
| 285 | mode->sync |= FB_SYNC_HOR_HIGH_ACT; | ||
| 286 | else | ||
| 287 | mode->sync |= FB_SYNC_VERT_HIGH_ACT; | ||
| 288 | } | ||
| 289 | |||
| 290 | /* | ||
| 291 | * fb_find_mode_cvt - calculate mode using VESA(TM) CVT | ||
| 292 | * @mode: pointer to fb_videomode; xres, yres, refresh and vmode must be | ||
| 293 | * pre-filled with the desired values | ||
| 294 | * @margins: add margin to calculation (1.8% of xres and yres) | ||
| 295 | * @rb: compute with reduced blanking (for flatpanels) | ||
| 296 | * | ||
| 297 | * RETURNS: | ||
| 298 | * 0 for success | ||
| 299 | * @mode is filled with computed values. If interlaced, the refresh field | ||
| 300 | * will be filled with the field rate (2x the frame rate) | ||
| 301 | * | ||
| 302 | * DESCRIPTION: | ||
| 303 | * Computes video timings using VESA(TM) Coordinated Video Timings | ||
| 304 | */ | ||
| 305 | int fb_find_mode_cvt(struct fb_videomode *mode, int margins, int rb) | ||
| 306 | { | ||
| 307 | struct fb_cvt_data cvt; | ||
| 308 | |||
| 309 | memset(&cvt, 0, sizeof(cvt)); | ||
| 310 | |||
| 311 | if (margins) | ||
| 312 | cvt.flags |= FB_CVT_FLAG_MARGINS; | ||
| 313 | |||
| 314 | if (rb) | ||
| 315 | cvt.flags |= FB_CVT_FLAG_REDUCED_BLANK; | ||
| 316 | |||
| 317 | if (mode->vmode & FB_VMODE_INTERLACED) | ||
| 318 | cvt.flags |= FB_CVT_FLAG_INTERLACED; | ||
| 319 | |||
| 320 | cvt.xres = mode->xres; | ||
| 321 | cvt.yres = mode->yres; | ||
| 322 | cvt.refresh = mode->refresh; | ||
| 323 | cvt.f_refresh = cvt.refresh; | ||
| 324 | cvt.interlace = 1; | ||
| 325 | |||
| 326 | if (!cvt.xres || !cvt.yres || !cvt.refresh) { | ||
| 327 | printk(KERN_INFO "fbcvt: Invalid input parameters\n"); | ||
| 328 | return 1; | ||
| 329 | } | ||
| 330 | |||
| 331 | if (!(cvt.refresh == 50 || cvt.refresh == 60 || cvt.refresh == 70 || | ||
| 332 | cvt.refresh == 85)) { | ||
| 333 | printk(KERN_INFO "fbcvt: Refresh rate not CVT " | ||
| 334 | "standard\n"); | ||
| 335 | cvt.status = 1; | ||
| 336 | } | ||
| 337 | |||
| 338 | cvt.xres &= ~(FB_CVT_CELLSIZE - 1); | ||
| 339 | |||
| 340 | if (cvt.flags & FB_CVT_FLAG_INTERLACED) { | ||
| 341 | cvt.interlace = 2; | ||
| 342 | cvt.f_refresh *= 2; | ||
| 343 | } | ||
| 344 | |||
| 345 | if (cvt.flags & FB_CVT_FLAG_REDUCED_BLANK) { | ||
| 346 | if (cvt.refresh != 60) { | ||
| 347 | printk(KERN_INFO "fbcvt: 60Hz refresh rate " | ||
| 348 | "advised for reduced blanking\n"); | ||
| 349 | cvt.status = 1; | ||
| 350 | } | ||
| 351 | } | ||
| 352 | |||
| 353 | if (cvt.flags & FB_CVT_FLAG_MARGINS) { | ||
| 354 | cvt.h_margin = (cvt.xres * 18)/1000; | ||
| 355 | cvt.h_margin &= ~(FB_CVT_CELLSIZE - 1); | ||
| 356 | cvt.v_margin = ((cvt.yres/cvt.interlace)* 18)/1000; | ||
| 357 | } | ||
| 358 | |||
| 359 | cvt.aspect_ratio = fb_cvt_aspect_ratio(&cvt); | ||
| 360 | cvt.active_pixels = cvt.xres + 2 * cvt.h_margin; | ||
| 361 | cvt.hperiod = fb_cvt_hperiod(&cvt); | ||
| 362 | cvt.vsync = fb_cvt_vbi_tab[cvt.aspect_ratio]; | ||
| 363 | cvt.vtotal = fb_cvt_vtotal(&cvt); | ||
| 364 | cvt.hblank = fb_cvt_hblank(&cvt); | ||
| 365 | cvt.htotal = cvt.active_pixels + cvt.hblank; | ||
| 366 | cvt.hsync = fb_cvt_hsync(&cvt); | ||
| 367 | cvt.pixclock = fb_cvt_pixclock(&cvt); | ||
| 368 | cvt.hfreq = cvt.pixclock/cvt.htotal; | ||
| 369 | cvt.h_back_porch = cvt.hblank/2 + cvt.h_margin; | ||
| 370 | cvt.h_front_porch = cvt.hblank - cvt.hsync - cvt.h_back_porch + | ||
| 371 | 2 * cvt.h_margin; | ||
| 372 | cvt.v_back_porch = 3 + cvt.v_margin; | ||
| 373 | cvt.v_front_porch = cvt.vtotal - cvt.yres/cvt.interlace - | ||
| 374 | cvt.v_back_porch - cvt.vsync; | ||
| 375 | fb_cvt_print_name(&cvt); | ||
| 376 | fb_cvt_convert_to_mode(&cvt, mode); | ||
| 377 | |||
| 378 | return 0; | ||
| 379 | } | ||
diff --git a/drivers/video/fbdev/core/fbmem.c b/drivers/video/fbdev/core/fbmem.c new file mode 100644 index 000000000000..b6d5008f361f --- /dev/null +++ b/drivers/video/fbdev/core/fbmem.c | |||
| @@ -0,0 +1,2002 @@ | |||
| 1 | /* | ||
| 2 | * linux/drivers/video/fbmem.c | ||
| 3 | * | ||
| 4 | * Copyright (C) 1994 Martin Schaller | ||
| 5 | * | ||
| 6 | * 2001 - Documented with DocBook | ||
| 7 | * - Brad Douglas <brad@neruo.com> | ||
| 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/module.h> | ||
| 15 | |||
| 16 | #include <linux/compat.h> | ||
| 17 | #include <linux/types.h> | ||
| 18 | #include <linux/errno.h> | ||
| 19 | #include <linux/kernel.h> | ||
| 20 | #include <linux/major.h> | ||
| 21 | #include <linux/slab.h> | ||
| 22 | #include <linux/mm.h> | ||
| 23 | #include <linux/mman.h> | ||
| 24 | #include <linux/vt.h> | ||
| 25 | #include <linux/init.h> | ||
| 26 | #include <linux/linux_logo.h> | ||
| 27 | #include <linux/proc_fs.h> | ||
| 28 | #include <linux/seq_file.h> | ||
| 29 | #include <linux/console.h> | ||
| 30 | #include <linux/kmod.h> | ||
| 31 | #include <linux/err.h> | ||
| 32 | #include <linux/device.h> | ||
| 33 | #include <linux/efi.h> | ||
| 34 | #include <linux/fb.h> | ||
| 35 | |||
| 36 | #include <asm/fb.h> | ||
| 37 | |||
| 38 | |||
| 39 | /* | ||
| 40 | * Frame buffer device initialization and setup routines | ||
| 41 | */ | ||
| 42 | |||
| 43 | #define FBPIXMAPSIZE (1024 * 8) | ||
| 44 | |||
| 45 | static DEFINE_MUTEX(registration_lock); | ||
| 46 | |||
| 47 | struct fb_info *registered_fb[FB_MAX] __read_mostly; | ||
| 48 | EXPORT_SYMBOL(registered_fb); | ||
| 49 | |||
| 50 | int num_registered_fb __read_mostly; | ||
| 51 | EXPORT_SYMBOL(num_registered_fb); | ||
| 52 | |||
| 53 | static struct fb_info *get_fb_info(unsigned int idx) | ||
| 54 | { | ||
| 55 | struct fb_info *fb_info; | ||
| 56 | |||
| 57 | if (idx >= FB_MAX) | ||
| 58 | return ERR_PTR(-ENODEV); | ||
| 59 | |||
| 60 | mutex_lock(®istration_lock); | ||
| 61 | fb_info = registered_fb[idx]; | ||
| 62 | if (fb_info) | ||
| 63 | atomic_inc(&fb_info->count); | ||
| 64 | mutex_unlock(®istration_lock); | ||
| 65 | |||
| 66 | return fb_info; | ||
| 67 | } | ||
| 68 | |||
| 69 | static void put_fb_info(struct fb_info *fb_info) | ||
| 70 | { | ||
| 71 | if (!atomic_dec_and_test(&fb_info->count)) | ||
| 72 | return; | ||
| 73 | if (fb_info->fbops->fb_destroy) | ||
| 74 | fb_info->fbops->fb_destroy(fb_info); | ||
| 75 | } | ||
| 76 | |||
| 77 | int lock_fb_info(struct fb_info *info) | ||
| 78 | { | ||
| 79 | mutex_lock(&info->lock); | ||
| 80 | if (!info->fbops) { | ||
| 81 | mutex_unlock(&info->lock); | ||
| 82 | return 0; | ||
| 83 | } | ||
| 84 | return 1; | ||
| 85 | } | ||
| 86 | EXPORT_SYMBOL(lock_fb_info); | ||
| 87 | |||
| 88 | /* | ||
| 89 | * Helpers | ||
| 90 | */ | ||
| 91 | |||
| 92 | int fb_get_color_depth(struct fb_var_screeninfo *var, | ||
| 93 | struct fb_fix_screeninfo *fix) | ||
| 94 | { | ||
| 95 | int depth = 0; | ||
| 96 | |||
| 97 | if (fix->visual == FB_VISUAL_MONO01 || | ||
| 98 | fix->visual == FB_VISUAL_MONO10) | ||
| 99 | depth = 1; | ||
| 100 | else { | ||
| 101 | if (var->green.length == var->blue.length && | ||
| 102 | var->green.length == var->red.length && | ||
| 103 | var->green.offset == var->blue.offset && | ||
| 104 | var->green.offset == var->red.offset) | ||
| 105 | depth = var->green.length; | ||
| 106 | else | ||
| 107 | depth = var->green.length + var->red.length + | ||
| 108 | var->blue.length; | ||
| 109 | } | ||
| 110 | |||
| 111 | return depth; | ||
| 112 | } | ||
| 113 | EXPORT_SYMBOL(fb_get_color_depth); | ||
| 114 | |||
| 115 | /* | ||
| 116 | * Data padding functions. | ||
| 117 | */ | ||
| 118 | void fb_pad_aligned_buffer(u8 *dst, u32 d_pitch, u8 *src, u32 s_pitch, u32 height) | ||
| 119 | { | ||
| 120 | __fb_pad_aligned_buffer(dst, d_pitch, src, s_pitch, height); | ||
| 121 | } | ||
| 122 | EXPORT_SYMBOL(fb_pad_aligned_buffer); | ||
| 123 | |||
| 124 | void fb_pad_unaligned_buffer(u8 *dst, u32 d_pitch, u8 *src, u32 idx, u32 height, | ||
| 125 | u32 shift_high, u32 shift_low, u32 mod) | ||
| 126 | { | ||
| 127 | u8 mask = (u8) (0xfff << shift_high), tmp; | ||
| 128 | int i, j; | ||
| 129 | |||
| 130 | for (i = height; i--; ) { | ||
| 131 | for (j = 0; j < idx; j++) { | ||
| 132 | tmp = dst[j]; | ||
| 133 | tmp &= mask; | ||
| 134 | tmp |= *src >> shift_low; | ||
| 135 | dst[j] = tmp; | ||
| 136 | tmp = *src << shift_high; | ||
| 137 | dst[j+1] = tmp; | ||
| 138 | src++; | ||
| 139 | } | ||
| 140 | tmp = dst[idx]; | ||
| 141 | tmp &= mask; | ||
| 142 | tmp |= *src >> shift_low; | ||
| 143 | dst[idx] = tmp; | ||
| 144 | if (shift_high < mod) { | ||
| 145 | tmp = *src << shift_high; | ||
| 146 | dst[idx+1] = tmp; | ||
| 147 | } | ||
| 148 | src++; | ||
| 149 | dst += d_pitch; | ||
| 150 | } | ||
| 151 | } | ||
| 152 | EXPORT_SYMBOL(fb_pad_unaligned_buffer); | ||
| 153 | |||
| 154 | /* | ||
| 155 | * we need to lock this section since fb_cursor | ||
| 156 | * may use fb_imageblit() | ||
| 157 | */ | ||
| 158 | char* fb_get_buffer_offset(struct fb_info *info, struct fb_pixmap *buf, u32 size) | ||
| 159 | { | ||
| 160 | u32 align = buf->buf_align - 1, offset; | ||
| 161 | char *addr = buf->addr; | ||
| 162 | |||
| 163 | /* If IO mapped, we need to sync before access, no sharing of | ||
| 164 | * the pixmap is done | ||
| 165 | */ | ||
| 166 | if (buf->flags & FB_PIXMAP_IO) { | ||
| 167 | if (info->fbops->fb_sync && (buf->flags & FB_PIXMAP_SYNC)) | ||
| 168 | info->fbops->fb_sync(info); | ||
| 169 | return addr; | ||
| 170 | } | ||
| 171 | |||
| 172 | /* See if we fit in the remaining pixmap space */ | ||
| 173 | offset = buf->offset + align; | ||
| 174 | offset &= ~align; | ||
| 175 | if (offset + size > buf->size) { | ||
| 176 | /* We do not fit. In order to be able to re-use the buffer, | ||
| 177 | * we must ensure no asynchronous DMA'ing or whatever operation | ||
| 178 | * is in progress, we sync for that. | ||
| 179 | */ | ||
| 180 | if (info->fbops->fb_sync && (buf->flags & FB_PIXMAP_SYNC)) | ||
| 181 | info->fbops->fb_sync(info); | ||
| 182 | offset = 0; | ||
| 183 | } | ||
| 184 | buf->offset = offset + size; | ||
| 185 | addr += offset; | ||
| 186 | |||
| 187 | return addr; | ||
| 188 | } | ||
| 189 | EXPORT_SYMBOL(fb_get_buffer_offset); | ||
| 190 | |||
| 191 | #ifdef CONFIG_LOGO | ||
| 192 | |||
| 193 | static inline unsigned safe_shift(unsigned d, int n) | ||
| 194 | { | ||
| 195 | return n < 0 ? d >> -n : d << n; | ||
| 196 | } | ||
| 197 | |||
| 198 | static void fb_set_logocmap(struct fb_info *info, | ||
| 199 | const struct linux_logo *logo) | ||
| 200 | { | ||
| 201 | struct fb_cmap palette_cmap; | ||
| 202 | u16 palette_green[16]; | ||
| 203 | u16 palette_blue[16]; | ||
| 204 | u16 palette_red[16]; | ||
| 205 | int i, j, n; | ||
| 206 | const unsigned char *clut = logo->clut; | ||
| 207 | |||
| 208 | palette_cmap.start = 0; | ||
| 209 | palette_cmap.len = 16; | ||
| 210 | palette_cmap.red = palette_red; | ||
| 211 | palette_cmap.green = palette_green; | ||
| 212 | palette_cmap.blue = palette_blue; | ||
| 213 | palette_cmap.transp = NULL; | ||
| 214 | |||
| 215 | for (i = 0; i < logo->clutsize; i += n) { | ||
| 216 | n = logo->clutsize - i; | ||
| 217 | /* palette_cmap provides space for only 16 colors at once */ | ||
| 218 | if (n > 16) | ||
| 219 | n = 16; | ||
| 220 | palette_cmap.start = 32 + i; | ||
| 221 | palette_cmap.len = n; | ||
| 222 | for (j = 0; j < n; ++j) { | ||
| 223 | palette_cmap.red[j] = clut[0] << 8 | clut[0]; | ||
| 224 | palette_cmap.green[j] = clut[1] << 8 | clut[1]; | ||
| 225 | palette_cmap.blue[j] = clut[2] << 8 | clut[2]; | ||
| 226 | clut += 3; | ||
| 227 | } | ||
| 228 | fb_set_cmap(&palette_cmap, info); | ||
| 229 | } | ||
| 230 | } | ||
| 231 | |||
| 232 | static void fb_set_logo_truepalette(struct fb_info *info, | ||
| 233 | const struct linux_logo *logo, | ||
| 234 | u32 *palette) | ||
| 235 | { | ||
| 236 | static const unsigned char mask[] = { 0,0x80,0xc0,0xe0,0xf0,0xf8,0xfc,0xfe,0xff }; | ||
| 237 | unsigned char redmask, greenmask, bluemask; | ||
| 238 | int redshift, greenshift, blueshift; | ||
| 239 | int i; | ||
| 240 | const unsigned char *clut = logo->clut; | ||
| 241 | |||
| 242 | /* | ||
| 243 | * We have to create a temporary palette since console palette is only | ||
| 244 | * 16 colors long. | ||
| 245 | */ | ||
| 246 | /* Bug: Doesn't obey msb_right ... (who needs that?) */ | ||
| 247 | redmask = mask[info->var.red.length < 8 ? info->var.red.length : 8]; | ||
| 248 | greenmask = mask[info->var.green.length < 8 ? info->var.green.length : 8]; | ||
| 249 | bluemask = mask[info->var.blue.length < 8 ? info->var.blue.length : 8]; | ||
| 250 | redshift = info->var.red.offset - (8 - info->var.red.length); | ||
| 251 | greenshift = info->var.green.offset - (8 - info->var.green.length); | ||
| 252 | blueshift = info->var.blue.offset - (8 - info->var.blue.length); | ||
| 253 | |||
| 254 | for ( i = 0; i < logo->clutsize; i++) { | ||
| 255 | palette[i+32] = (safe_shift((clut[0] & redmask), redshift) | | ||
| 256 | safe_shift((clut[1] & greenmask), greenshift) | | ||
| 257 | safe_shift((clut[2] & bluemask), blueshift)); | ||
| 258 | clut += 3; | ||
| 259 | } | ||
| 260 | } | ||
| 261 | |||
| 262 | static void fb_set_logo_directpalette(struct fb_info *info, | ||
| 263 | const struct linux_logo *logo, | ||
| 264 | u32 *palette) | ||
| 265 | { | ||
| 266 | int redshift, greenshift, blueshift; | ||
| 267 | int i; | ||
| 268 | |||
| 269 | redshift = info->var.red.offset; | ||
| 270 | greenshift = info->var.green.offset; | ||
| 271 | blueshift = info->var.blue.offset; | ||
| 272 | |||
| 273 | for (i = 32; i < 32 + logo->clutsize; i++) | ||
| 274 | palette[i] = i << redshift | i << greenshift | i << blueshift; | ||
| 275 | } | ||
| 276 | |||
| 277 | static void fb_set_logo(struct fb_info *info, | ||
| 278 | const struct linux_logo *logo, u8 *dst, | ||
| 279 | int depth) | ||
| 280 | { | ||
| 281 | int i, j, k; | ||
| 282 | const u8 *src = logo->data; | ||
| 283 | u8 xor = (info->fix.visual == FB_VISUAL_MONO01) ? 0xff : 0; | ||
| 284 | u8 fg = 1, d; | ||
| 285 | |||
| 286 | switch (fb_get_color_depth(&info->var, &info->fix)) { | ||
| 287 | case 1: | ||
| 288 | fg = 1; | ||
| 289 | break; | ||
| 290 | case 2: | ||
| 291 | fg = 3; | ||
| 292 | break; | ||
| 293 | default: | ||
| 294 | fg = 7; | ||
| 295 | break; | ||
| 296 | } | ||
| 297 | |||
| 298 | if (info->fix.visual == FB_VISUAL_MONO01 || | ||
| 299 | info->fix.visual == FB_VISUAL_MONO10) | ||
| 300 | fg = ~((u8) (0xfff << info->var.green.length)); | ||
| 301 | |||
| 302 | switch (depth) { | ||
| 303 | case 4: | ||
| 304 | for (i = 0; i < logo->height; i++) | ||
| 305 | for (j = 0; j < logo->width; src++) { | ||
| 306 | *dst++ = *src >> 4; | ||
| 307 | j++; | ||
| 308 | if (j < logo->width) { | ||
| 309 | *dst++ = *src & 0x0f; | ||
| 310 | j++; | ||
| 311 | } | ||
| 312 | } | ||
| 313 | break; | ||
| 314 | case 1: | ||
| 315 | for (i = 0; i < logo->height; i++) { | ||
| 316 | for (j = 0; j < logo->width; src++) { | ||
| 317 | d = *src ^ xor; | ||
| 318 | for (k = 7; k >= 0; k--) { | ||
| 319 | *dst++ = ((d >> k) & 1) ? fg : 0; | ||
| 320 | j++; | ||
| 321 | } | ||
| 322 | } | ||
| 323 | } | ||
| 324 | break; | ||
| 325 | } | ||
| 326 | } | ||
| 327 | |||
| 328 | /* | ||
| 329 | * Three (3) kinds of logo maps exist. linux_logo_clut224 (>16 colors), | ||
| 330 | * linux_logo_vga16 (16 colors) and linux_logo_mono (2 colors). Depending on | ||
| 331 | * the visual format and color depth of the framebuffer, the DAC, the | ||
| 332 | * pseudo_palette, and the logo data will be adjusted accordingly. | ||
| 333 | * | ||
| 334 | * Case 1 - linux_logo_clut224: | ||
| 335 | * Color exceeds the number of console colors (16), thus we set the hardware DAC | ||
| 336 | * using fb_set_cmap() appropriately. The "needs_cmapreset" flag will be set. | ||
| 337 | * | ||
| 338 | * For visuals that require color info from the pseudo_palette, we also construct | ||
| 339 | * one for temporary use. The "needs_directpalette" or "needs_truepalette" flags | ||
| 340 | * will be set. | ||
| 341 | * | ||
| 342 | * Case 2 - linux_logo_vga16: | ||
| 343 | * The number of colors just matches the console colors, thus there is no need | ||
| 344 | * to set the DAC or the pseudo_palette. However, the bitmap is packed, ie, | ||
| 345 | * each byte contains color information for two pixels (upper and lower nibble). | ||
| 346 | * To be consistent with fb_imageblit() usage, we therefore separate the two | ||
| 347 | * nibbles into separate bytes. The "depth" flag will be set to 4. | ||
| 348 | * | ||
| 349 | * Case 3 - linux_logo_mono: | ||
| 350 | * This is similar with Case 2. Each byte contains information for 8 pixels. | ||
| 351 | * We isolate each bit and expand each into a byte. The "depth" flag will | ||
| 352 | * be set to 1. | ||
| 353 | */ | ||
| 354 | static struct logo_data { | ||
| 355 | int depth; | ||
| 356 | int needs_directpalette; | ||
| 357 | int needs_truepalette; | ||
| 358 | int needs_cmapreset; | ||
| 359 | const struct linux_logo *logo; | ||
| 360 | } fb_logo __read_mostly; | ||
| 361 | |||
| 362 | static void fb_rotate_logo_ud(const u8 *in, u8 *out, u32 width, u32 height) | ||
| 363 | { | ||
| 364 | u32 size = width * height, i; | ||
| 365 | |||
| 366 | out += size - 1; | ||
| 367 | |||
| 368 | for (i = size; i--; ) | ||
| 369 | *out-- = *in++; | ||
| 370 | } | ||
| 371 | |||
| 372 | static void fb_rotate_logo_cw(const u8 *in, u8 *out, u32 width, u32 height) | ||
| 373 | { | ||
| 374 | int i, j, h = height - 1; | ||
| 375 | |||
| 376 | for (i = 0; i < height; i++) | ||
| 377 | for (j = 0; j < width; j++) | ||
| 378 | out[height * j + h - i] = *in++; | ||
| 379 | } | ||
| 380 | |||
| 381 | static void fb_rotate_logo_ccw(const u8 *in, u8 *out, u32 width, u32 height) | ||
| 382 | { | ||
| 383 | int i, j, w = width - 1; | ||
| 384 | |||
| 385 | for (i = 0; i < height; i++) | ||
| 386 | for (j = 0; j < width; j++) | ||
| 387 | out[height * (w - j) + i] = *in++; | ||
| 388 | } | ||
| 389 | |||
| 390 | static void fb_rotate_logo(struct fb_info *info, u8 *dst, | ||
| 391 | struct fb_image *image, int rotate) | ||
| 392 | { | ||
| 393 | u32 tmp; | ||
| 394 | |||
| 395 | if (rotate == FB_ROTATE_UD) { | ||
| 396 | fb_rotate_logo_ud(image->data, dst, image->width, | ||
| 397 | image->height); | ||
| 398 | image->dx = info->var.xres - image->width - image->dx; | ||
| 399 | image->dy = info->var.yres - image->height - image->dy; | ||
| 400 | } else if (rotate == FB_ROTATE_CW) { | ||
| 401 | fb_rotate_logo_cw(image->data, dst, image->width, | ||
| 402 | image->height); | ||
| 403 | tmp = image->width; | ||
| 404 | image->width = image->height; | ||
| 405 | image->height = tmp; | ||
| 406 | tmp = image->dy; | ||
| 407 | image->dy = image->dx; | ||
| 408 | image->dx = info->var.xres - image->width - tmp; | ||
| 409 | } else if (rotate == FB_ROTATE_CCW) { | ||
| 410 | fb_rotate_logo_ccw(image->data, dst, image->width, | ||
| 411 | image->height); | ||
| 412 | tmp = image->width; | ||
| 413 | image->width = image->height; | ||
| 414 | image->height = tmp; | ||
| 415 | tmp = image->dx; | ||
| 416 | image->dx = image->dy; | ||
| 417 | image->dy = info->var.yres - image->height - tmp; | ||
| 418 | } | ||
| 419 | |||
| 420 | image->data = dst; | ||
| 421 | } | ||
| 422 | |||
| 423 | static void fb_do_show_logo(struct fb_info *info, struct fb_image *image, | ||
| 424 | int rotate, unsigned int num) | ||
| 425 | { | ||
| 426 | unsigned int x; | ||
| 427 | |||
| 428 | if (rotate == FB_ROTATE_UR) { | ||
| 429 | for (x = 0; | ||
| 430 | x < num && image->dx + image->width <= info->var.xres; | ||
| 431 | x++) { | ||
| 432 | info->fbops->fb_imageblit(info, image); | ||
| 433 | image->dx += image->width + 8; | ||
| 434 | } | ||
| 435 | } else if (rotate == FB_ROTATE_UD) { | ||
| 436 | for (x = 0; x < num && image->dx >= 0; x++) { | ||
| 437 | info->fbops->fb_imageblit(info, image); | ||
| 438 | image->dx -= image->width + 8; | ||
| 439 | } | ||
| 440 | } else if (rotate == FB_ROTATE_CW) { | ||
| 441 | for (x = 0; | ||
| 442 | x < num && image->dy + image->height <= info->var.yres; | ||
| 443 | x++) { | ||
| 444 | info->fbops->fb_imageblit(info, image); | ||
| 445 | image->dy += image->height + 8; | ||
| 446 | } | ||
| 447 | } else if (rotate == FB_ROTATE_CCW) { | ||
| 448 | for (x = 0; x < num && image->dy >= 0; x++) { | ||
| 449 | info->fbops->fb_imageblit(info, image); | ||
| 450 | image->dy -= image->height + 8; | ||
| 451 | } | ||
| 452 | } | ||
| 453 | } | ||
| 454 | |||
| 455 | static int fb_show_logo_line(struct fb_info *info, int rotate, | ||
| 456 | const struct linux_logo *logo, int y, | ||
| 457 | unsigned int n) | ||
| 458 | { | ||
| 459 | u32 *palette = NULL, *saved_pseudo_palette = NULL; | ||
| 460 | unsigned char *logo_new = NULL, *logo_rotate = NULL; | ||
| 461 | struct fb_image image; | ||
| 462 | |||
| 463 | /* Return if the frame buffer is not mapped or suspended */ | ||
| 464 | if (logo == NULL || info->state != FBINFO_STATE_RUNNING || | ||
| 465 | info->flags & FBINFO_MODULE) | ||
| 466 | return 0; | ||
| 467 | |||
| 468 | image.depth = 8; | ||
| 469 | image.data = logo->data; | ||
| 470 | |||
| 471 | if (fb_logo.needs_cmapreset) | ||
| 472 | fb_set_logocmap(info, logo); | ||
| 473 | |||
| 474 | if (fb_logo.needs_truepalette || | ||
| 475 | fb_logo.needs_directpalette) { | ||
| 476 | palette = kmalloc(256 * 4, GFP_KERNEL); | ||
| 477 | if (palette == NULL) | ||
| 478 | return 0; | ||
| 479 | |||
| 480 | if (fb_logo.needs_truepalette) | ||
| 481 | fb_set_logo_truepalette(info, logo, palette); | ||
| 482 | else | ||
| 483 | fb_set_logo_directpalette(info, logo, palette); | ||
| 484 | |||
| 485 | saved_pseudo_palette = info->pseudo_palette; | ||
| 486 | info->pseudo_palette = palette; | ||
| 487 | } | ||
| 488 | |||
| 489 | if (fb_logo.depth <= 4) { | ||
| 490 | logo_new = kmalloc(logo->width * logo->height, GFP_KERNEL); | ||
| 491 | if (logo_new == NULL) { | ||
| 492 | kfree(palette); | ||
| 493 | if (saved_pseudo_palette) | ||
| 494 | info->pseudo_palette = saved_pseudo_palette; | ||
| 495 | return 0; | ||
| 496 | } | ||
| 497 | image.data = logo_new; | ||
| 498 | fb_set_logo(info, logo, logo_new, fb_logo.depth); | ||
| 499 | } | ||
| 500 | |||
| 501 | image.dx = 0; | ||
| 502 | image.dy = y; | ||
| 503 | image.width = logo->width; | ||
| 504 | image.height = logo->height; | ||
| 505 | |||
| 506 | if (rotate) { | ||
| 507 | logo_rotate = kmalloc(logo->width * | ||
| 508 | logo->height, GFP_KERNEL); | ||
| 509 | if (logo_rotate) | ||
| 510 | fb_rotate_logo(info, logo_rotate, &image, rotate); | ||
| 511 | } | ||
| 512 | |||
| 513 | fb_do_show_logo(info, &image, rotate, n); | ||
| 514 | |||
| 515 | kfree(palette); | ||
| 516 | if (saved_pseudo_palette != NULL) | ||
| 517 | info->pseudo_palette = saved_pseudo_palette; | ||
| 518 | kfree(logo_new); | ||
| 519 | kfree(logo_rotate); | ||
| 520 | return logo->height; | ||
| 521 | } | ||
| 522 | |||
| 523 | |||
| 524 | #ifdef CONFIG_FB_LOGO_EXTRA | ||
| 525 | |||
| 526 | #define FB_LOGO_EX_NUM_MAX 10 | ||
| 527 | static struct logo_data_extra { | ||
| 528 | const struct linux_logo *logo; | ||
| 529 | unsigned int n; | ||
| 530 | } fb_logo_ex[FB_LOGO_EX_NUM_MAX]; | ||
| 531 | static unsigned int fb_logo_ex_num; | ||
| 532 | |||
| 533 | void fb_append_extra_logo(const struct linux_logo *logo, unsigned int n) | ||
| 534 | { | ||
| 535 | if (!n || fb_logo_ex_num == FB_LOGO_EX_NUM_MAX) | ||
| 536 | return; | ||
| 537 | |||
| 538 | fb_logo_ex[fb_logo_ex_num].logo = logo; | ||
| 539 | fb_logo_ex[fb_logo_ex_num].n = n; | ||
| 540 | fb_logo_ex_num++; | ||
| 541 | } | ||
| 542 | |||
| 543 | static int fb_prepare_extra_logos(struct fb_info *info, unsigned int height, | ||
| 544 | unsigned int yres) | ||
| 545 | { | ||
| 546 | unsigned int i; | ||
| 547 | |||
| 548 | /* FIXME: logo_ex supports only truecolor fb. */ | ||
| 549 | if (info->fix.visual != FB_VISUAL_TRUECOLOR) | ||
| 550 | fb_logo_ex_num = 0; | ||
| 551 | |||
| 552 | for (i = 0; i < fb_logo_ex_num; i++) { | ||
| 553 | if (fb_logo_ex[i].logo->type != fb_logo.logo->type) { | ||
| 554 | fb_logo_ex[i].logo = NULL; | ||
| 555 | continue; | ||
| 556 | } | ||
| 557 | height += fb_logo_ex[i].logo->height; | ||
| 558 | if (height > yres) { | ||
| 559 | height -= fb_logo_ex[i].logo->height; | ||
| 560 | fb_logo_ex_num = i; | ||
| 561 | break; | ||
| 562 | } | ||
| 563 | } | ||
| 564 | return height; | ||
| 565 | } | ||
| 566 | |||
| 567 | static int fb_show_extra_logos(struct fb_info *info, int y, int rotate) | ||
| 568 | { | ||
| 569 | unsigned int i; | ||
| 570 | |||
| 571 | for (i = 0; i < fb_logo_ex_num; i++) | ||
| 572 | y += fb_show_logo_line(info, rotate, | ||
| 573 | fb_logo_ex[i].logo, y, fb_logo_ex[i].n); | ||
| 574 | |||
| 575 | return y; | ||
| 576 | } | ||
| 577 | |||
| 578 | #else /* !CONFIG_FB_LOGO_EXTRA */ | ||
| 579 | |||
| 580 | static inline int fb_prepare_extra_logos(struct fb_info *info, | ||
| 581 | unsigned int height, | ||
| 582 | unsigned int yres) | ||
| 583 | { | ||
| 584 | return height; | ||
| 585 | } | ||
| 586 | |||
| 587 | static inline int fb_show_extra_logos(struct fb_info *info, int y, int rotate) | ||
| 588 | { | ||
| 589 | return y; | ||
| 590 | } | ||
| 591 | |||
| 592 | #endif /* CONFIG_FB_LOGO_EXTRA */ | ||
| 593 | |||
| 594 | |||
| 595 | int fb_prepare_logo(struct fb_info *info, int rotate) | ||
| 596 | { | ||
| 597 | int depth = fb_get_color_depth(&info->var, &info->fix); | ||
| 598 | unsigned int yres; | ||
| 599 | |||
| 600 | memset(&fb_logo, 0, sizeof(struct logo_data)); | ||
| 601 | |||
| 602 | if (info->flags & FBINFO_MISC_TILEBLITTING || | ||
| 603 | info->flags & FBINFO_MODULE) | ||
| 604 | return 0; | ||
| 605 | |||
| 606 | if (info->fix.visual == FB_VISUAL_DIRECTCOLOR) { | ||
| 607 | depth = info->var.blue.length; | ||
| 608 | if (info->var.red.length < depth) | ||
| 609 | depth = info->var.red.length; | ||
| 610 | if (info->var.green.length < depth) | ||
| 611 | depth = info->var.green.length; | ||
| 612 | } | ||
| 613 | |||
| 614 | if (info->fix.visual == FB_VISUAL_STATIC_PSEUDOCOLOR && depth > 4) { | ||
| 615 | /* assume console colormap */ | ||
| 616 | depth = 4; | ||
| 617 | } | ||
| 618 | |||
| 619 | /* Return if no suitable logo was found */ | ||
| 620 | fb_logo.logo = fb_find_logo(depth); | ||
| 621 | |||
| 622 | if (!fb_logo.logo) { | ||
| 623 | return 0; | ||
| 624 | } | ||
| 625 | |||
| 626 | if (rotate == FB_ROTATE_UR || rotate == FB_ROTATE_UD) | ||
| 627 | yres = info->var.yres; | ||
| 628 | else | ||
| 629 | yres = info->var.xres; | ||
| 630 | |||
| 631 | if (fb_logo.logo->height > yres) { | ||
| 632 | fb_logo.logo = NULL; | ||
| 633 | return 0; | ||
| 634 | } | ||
| 635 | |||
| 636 | /* What depth we asked for might be different from what we get */ | ||
| 637 | if (fb_logo.logo->type == LINUX_LOGO_CLUT224) | ||
| 638 | fb_logo.depth = 8; | ||
| 639 | else if (fb_logo.logo->type == LINUX_LOGO_VGA16) | ||
| 640 | fb_logo.depth = 4; | ||
| 641 | else | ||
| 642 | fb_logo.depth = 1; | ||
| 643 | |||
| 644 | |||
| 645 | if (fb_logo.depth > 4 && depth > 4) { | ||
| 646 | switch (info->fix.visual) { | ||
| 647 | case FB_VISUAL_TRUECOLOR: | ||
| 648 | fb_logo.needs_truepalette = 1; | ||
| 649 | break; | ||
| 650 | case FB_VISUAL_DIRECTCOLOR: | ||
| 651 | fb_logo.needs_directpalette = 1; | ||
| 652 | fb_logo.needs_cmapreset = 1; | ||
| 653 | break; | ||
| 654 | case FB_VISUAL_PSEUDOCOLOR: | ||
| 655 | fb_logo.needs_cmapreset = 1; | ||
| 656 | break; | ||
| 657 | } | ||
| 658 | } | ||
| 659 | |||
| 660 | return fb_prepare_extra_logos(info, fb_logo.logo->height, yres); | ||
| 661 | } | ||
| 662 | |||
| 663 | int fb_show_logo(struct fb_info *info, int rotate) | ||
| 664 | { | ||
| 665 | int y; | ||
| 666 | |||
| 667 | y = fb_show_logo_line(info, rotate, fb_logo.logo, 0, | ||
| 668 | num_online_cpus()); | ||
| 669 | y = fb_show_extra_logos(info, y, rotate); | ||
| 670 | |||
| 671 | return y; | ||
| 672 | } | ||
| 673 | #else | ||
| 674 | int fb_prepare_logo(struct fb_info *info, int rotate) { return 0; } | ||
| 675 | int fb_show_logo(struct fb_info *info, int rotate) { return 0; } | ||
| 676 | #endif /* CONFIG_LOGO */ | ||
| 677 | EXPORT_SYMBOL(fb_show_logo); | ||
| 678 | |||
| 679 | static void *fb_seq_start(struct seq_file *m, loff_t *pos) | ||
| 680 | { | ||
| 681 | mutex_lock(®istration_lock); | ||
| 682 | return (*pos < FB_MAX) ? pos : NULL; | ||
| 683 | } | ||
| 684 | |||
| 685 | static void *fb_seq_next(struct seq_file *m, void *v, loff_t *pos) | ||
| 686 | { | ||
| 687 | (*pos)++; | ||
| 688 | return (*pos < FB_MAX) ? pos : NULL; | ||
| 689 | } | ||
| 690 | |||
| 691 | static void fb_seq_stop(struct seq_file *m, void *v) | ||
| 692 | { | ||
| 693 | mutex_unlock(®istration_lock); | ||
| 694 | } | ||
| 695 | |||
| 696 | static int fb_seq_show(struct seq_file *m, void *v) | ||
| 697 | { | ||
| 698 | int i = *(loff_t *)v; | ||
| 699 | struct fb_info *fi = registered_fb[i]; | ||
| 700 | |||
| 701 | if (fi) | ||
| 702 | seq_printf(m, "%d %s\n", fi->node, fi->fix.id); | ||
| 703 | return 0; | ||
| 704 | } | ||
| 705 | |||
| 706 | static const struct seq_operations proc_fb_seq_ops = { | ||
| 707 | .start = fb_seq_start, | ||
| 708 | .next = fb_seq_next, | ||
| 709 | .stop = fb_seq_stop, | ||
| 710 | .show = fb_seq_show, | ||
| 711 | }; | ||
| 712 | |||
| 713 | static int proc_fb_open(struct inode *inode, struct file *file) | ||
| 714 | { | ||
| 715 | return seq_open(file, &proc_fb_seq_ops); | ||
| 716 | } | ||
| 717 | |||
| 718 | static const struct file_operations fb_proc_fops = { | ||
| 719 | .owner = THIS_MODULE, | ||
| 720 | .open = proc_fb_open, | ||
| 721 | .read = seq_read, | ||
| 722 | .llseek = seq_lseek, | ||
| 723 | .release = seq_release, | ||
| 724 | }; | ||
| 725 | |||
| 726 | /* | ||
| 727 | * We hold a reference to the fb_info in file->private_data, | ||
| 728 | * but if the current registered fb has changed, we don't | ||
| 729 | * actually want to use it. | ||
| 730 | * | ||
| 731 | * So look up the fb_info using the inode minor number, | ||
| 732 | * and just verify it against the reference we have. | ||
| 733 | */ | ||
| 734 | static struct fb_info *file_fb_info(struct file *file) | ||
| 735 | { | ||
| 736 | struct inode *inode = file_inode(file); | ||
| 737 | int fbidx = iminor(inode); | ||
| 738 | struct fb_info *info = registered_fb[fbidx]; | ||
| 739 | |||
| 740 | if (info != file->private_data) | ||
| 741 | info = NULL; | ||
| 742 | return info; | ||
| 743 | } | ||
| 744 | |||
| 745 | static ssize_t | ||
| 746 | fb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) | ||
| 747 | { | ||
| 748 | unsigned long p = *ppos; | ||
| 749 | struct fb_info *info = file_fb_info(file); | ||
| 750 | u8 *buffer, *dst; | ||
| 751 | u8 __iomem *src; | ||
| 752 | int c, cnt = 0, err = 0; | ||
| 753 | unsigned long total_size; | ||
| 754 | |||
| 755 | if (!info || ! info->screen_base) | ||
| 756 | return -ENODEV; | ||
| 757 | |||
| 758 | if (info->state != FBINFO_STATE_RUNNING) | ||
| 759 | return -EPERM; | ||
| 760 | |||
| 761 | if (info->fbops->fb_read) | ||
| 762 | return info->fbops->fb_read(info, buf, count, ppos); | ||
| 763 | |||
| 764 | total_size = info->screen_size; | ||
| 765 | |||
| 766 | if (total_size == 0) | ||
| 767 | total_size = info->fix.smem_len; | ||
| 768 | |||
| 769 | if (p >= total_size) | ||
| 770 | return 0; | ||
| 771 | |||
| 772 | if (count >= total_size) | ||
| 773 | count = total_size; | ||
| 774 | |||
| 775 | if (count + p > total_size) | ||
| 776 | count = total_size - p; | ||
| 777 | |||
| 778 | buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count, | ||
| 779 | GFP_KERNEL); | ||
| 780 | if (!buffer) | ||
| 781 | return -ENOMEM; | ||
| 782 | |||
| 783 | src = (u8 __iomem *) (info->screen_base + p); | ||
| 784 | |||
| 785 | if (info->fbops->fb_sync) | ||
| 786 | info->fbops->fb_sync(info); | ||
| 787 | |||
| 788 | while (count) { | ||
| 789 | c = (count > PAGE_SIZE) ? PAGE_SIZE : count; | ||
| 790 | dst = buffer; | ||
| 791 | fb_memcpy_fromfb(dst, src, c); | ||
| 792 | dst += c; | ||
| 793 | src += c; | ||
| 794 | |||
| 795 | if (copy_to_user(buf, buffer, c)) { | ||
| 796 | err = -EFAULT; | ||
| 797 | break; | ||
| 798 | } | ||
| 799 | *ppos += c; | ||
| 800 | buf += c; | ||
| 801 | cnt += c; | ||
| 802 | count -= c; | ||
| 803 | } | ||
| 804 | |||
| 805 | kfree(buffer); | ||
| 806 | |||
| 807 | return (err) ? err : cnt; | ||
| 808 | } | ||
| 809 | |||
| 810 | static ssize_t | ||
| 811 | fb_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) | ||
| 812 | { | ||
| 813 | unsigned long p = *ppos; | ||
| 814 | struct fb_info *info = file_fb_info(file); | ||
| 815 | u8 *buffer, *src; | ||
| 816 | u8 __iomem *dst; | ||
| 817 | int c, cnt = 0, err = 0; | ||
| 818 | unsigned long total_size; | ||
| 819 | |||
| 820 | if (!info || !info->screen_base) | ||
| 821 | return -ENODEV; | ||
| 822 | |||
| 823 | if (info->state != FBINFO_STATE_RUNNING) | ||
| 824 | return -EPERM; | ||
| 825 | |||
| 826 | if (info->fbops->fb_write) | ||
| 827 | return info->fbops->fb_write(info, buf, count, ppos); | ||
| 828 | |||
| 829 | total_size = info->screen_size; | ||
| 830 | |||
| 831 | if (total_size == 0) | ||
| 832 | total_size = info->fix.smem_len; | ||
| 833 | |||
| 834 | if (p > total_size) | ||
| 835 | return -EFBIG; | ||
| 836 | |||
| 837 | if (count > total_size) { | ||
| 838 | err = -EFBIG; | ||
| 839 | count = total_size; | ||
| 840 | } | ||
| 841 | |||
| 842 | if (count + p > total_size) { | ||
| 843 | if (!err) | ||
| 844 | err = -ENOSPC; | ||
| 845 | |||
| 846 | count = total_size - p; | ||
| 847 | } | ||
| 848 | |||
| 849 | buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count, | ||
| 850 | GFP_KERNEL); | ||
| 851 | if (!buffer) | ||
| 852 | return -ENOMEM; | ||
| 853 | |||
| 854 | dst = (u8 __iomem *) (info->screen_base + p); | ||
| 855 | |||
| 856 | if (info->fbops->fb_sync) | ||
| 857 | info->fbops->fb_sync(info); | ||
| 858 | |||
| 859 | while (count) { | ||
| 860 | c = (count > PAGE_SIZE) ? PAGE_SIZE : count; | ||
| 861 | src = buffer; | ||
| 862 | |||
| 863 | if (copy_from_user(src, buf, c)) { | ||
| 864 | err = -EFAULT; | ||
| 865 | break; | ||
| 866 | } | ||
| 867 | |||
| 868 | fb_memcpy_tofb(dst, src, c); | ||
| 869 | dst += c; | ||
| 870 | src += c; | ||
| 871 | *ppos += c; | ||
| 872 | buf += c; | ||
| 873 | cnt += c; | ||
| 874 | count -= c; | ||
| 875 | } | ||
| 876 | |||
| 877 | kfree(buffer); | ||
| 878 | |||
| 879 | return (cnt) ? cnt : err; | ||
| 880 | } | ||
| 881 | |||
| 882 | int | ||
| 883 | fb_pan_display(struct fb_info *info, struct fb_var_screeninfo *var) | ||
| 884 | { | ||
| 885 | struct fb_fix_screeninfo *fix = &info->fix; | ||
| 886 | unsigned int yres = info->var.yres; | ||
| 887 | int err = 0; | ||
| 888 | |||
| 889 | if (var->yoffset > 0) { | ||
| 890 | if (var->vmode & FB_VMODE_YWRAP) { | ||
| 891 | if (!fix->ywrapstep || (var->yoffset % fix->ywrapstep)) | ||
| 892 | err = -EINVAL; | ||
| 893 | else | ||
| 894 | yres = 0; | ||
| 895 | } else if (!fix->ypanstep || (var->yoffset % fix->ypanstep)) | ||
| 896 | err = -EINVAL; | ||
| 897 | } | ||
| 898 | |||
| 899 | if (var->xoffset > 0 && (!fix->xpanstep || | ||
| 900 | (var->xoffset % fix->xpanstep))) | ||
| 901 | err = -EINVAL; | ||
| 902 | |||
| 903 | if (err || !info->fbops->fb_pan_display || | ||
| 904 | var->yoffset > info->var.yres_virtual - yres || | ||
| 905 | var->xoffset > info->var.xres_virtual - info->var.xres) | ||
| 906 | return -EINVAL; | ||
| 907 | |||
| 908 | if ((err = info->fbops->fb_pan_display(var, info))) | ||
| 909 | return err; | ||
| 910 | info->var.xoffset = var->xoffset; | ||
| 911 | info->var.yoffset = var->yoffset; | ||
| 912 | if (var->vmode & FB_VMODE_YWRAP) | ||
| 913 | info->var.vmode |= FB_VMODE_YWRAP; | ||
| 914 | else | ||
| 915 | info->var.vmode &= ~FB_VMODE_YWRAP; | ||
| 916 | return 0; | ||
| 917 | } | ||
| 918 | EXPORT_SYMBOL(fb_pan_display); | ||
| 919 | |||
| 920 | static int fb_check_caps(struct fb_info *info, struct fb_var_screeninfo *var, | ||
| 921 | u32 activate) | ||
| 922 | { | ||
| 923 | struct fb_event event; | ||
| 924 | struct fb_blit_caps caps, fbcaps; | ||
| 925 | int err = 0; | ||
| 926 | |||
| 927 | memset(&caps, 0, sizeof(caps)); | ||
| 928 | memset(&fbcaps, 0, sizeof(fbcaps)); | ||
| 929 | caps.flags = (activate & FB_ACTIVATE_ALL) ? 1 : 0; | ||
| 930 | event.info = info; | ||
| 931 | event.data = ∩︀ | ||
| 932 | fb_notifier_call_chain(FB_EVENT_GET_REQ, &event); | ||
| 933 | info->fbops->fb_get_caps(info, &fbcaps, var); | ||
| 934 | |||
| 935 | if (((fbcaps.x ^ caps.x) & caps.x) || | ||
| 936 | ((fbcaps.y ^ caps.y) & caps.y) || | ||
| 937 | (fbcaps.len < caps.len)) | ||
| 938 | err = -EINVAL; | ||
| 939 | |||
| 940 | return err; | ||
| 941 | } | ||
| 942 | |||
| 943 | int | ||
| 944 | fb_set_var(struct fb_info *info, struct fb_var_screeninfo *var) | ||
| 945 | { | ||
| 946 | int flags = info->flags; | ||
| 947 | int ret = 0; | ||
| 948 | |||
| 949 | if (var->activate & FB_ACTIVATE_INV_MODE) { | ||
| 950 | struct fb_videomode mode1, mode2; | ||
| 951 | |||
| 952 | fb_var_to_videomode(&mode1, var); | ||
| 953 | fb_var_to_videomode(&mode2, &info->var); | ||
| 954 | /* make sure we don't delete the videomode of current var */ | ||
| 955 | ret = fb_mode_is_equal(&mode1, &mode2); | ||
| 956 | |||
| 957 | if (!ret) { | ||
| 958 | struct fb_event event; | ||
| 959 | |||
| 960 | event.info = info; | ||
| 961 | event.data = &mode1; | ||
| 962 | ret = fb_notifier_call_chain(FB_EVENT_MODE_DELETE, &event); | ||
| 963 | } | ||
| 964 | |||
| 965 | if (!ret) | ||
| 966 | fb_delete_videomode(&mode1, &info->modelist); | ||
| 967 | |||
| 968 | |||
| 969 | ret = (ret) ? -EINVAL : 0; | ||
| 970 | goto done; | ||
| 971 | } | ||
| 972 | |||
| 973 | if ((var->activate & FB_ACTIVATE_FORCE) || | ||
| 974 | memcmp(&info->var, var, sizeof(struct fb_var_screeninfo))) { | ||
| 975 | u32 activate = var->activate; | ||
| 976 | |||
| 977 | /* When using FOURCC mode, make sure the red, green, blue and | ||
| 978 | * transp fields are set to 0. | ||
| 979 | */ | ||
| 980 | if ((info->fix.capabilities & FB_CAP_FOURCC) && | ||
| 981 | var->grayscale > 1) { | ||
| 982 | if (var->red.offset || var->green.offset || | ||
| 983 | var->blue.offset || var->transp.offset || | ||
| 984 | var->red.length || var->green.length || | ||
| 985 | var->blue.length || var->transp.length || | ||
| 986 | var->red.msb_right || var->green.msb_right || | ||
| 987 | var->blue.msb_right || var->transp.msb_right) | ||
| 988 | return -EINVAL; | ||
| 989 | } | ||
| 990 | |||
| 991 | if (!info->fbops->fb_check_var) { | ||
| 992 | *var = info->var; | ||
| 993 | goto done; | ||
| 994 | } | ||
| 995 | |||
| 996 | ret = info->fbops->fb_check_var(var, info); | ||
| 997 | |||
| 998 | if (ret) | ||
| 999 | goto done; | ||
| 1000 | |||
| 1001 | if ((var->activate & FB_ACTIVATE_MASK) == FB_ACTIVATE_NOW) { | ||
| 1002 | struct fb_var_screeninfo old_var; | ||
| 1003 | struct fb_videomode mode; | ||
| 1004 | |||
| 1005 | if (info->fbops->fb_get_caps) { | ||
| 1006 | ret = fb_check_caps(info, var, activate); | ||
| 1007 | |||
| 1008 | if (ret) | ||
| 1009 | goto done; | ||
| 1010 | } | ||
| 1011 | |||
| 1012 | old_var = info->var; | ||
| 1013 | info->var = *var; | ||
| 1014 | |||
| 1015 | if (info->fbops->fb_set_par) { | ||
| 1016 | ret = info->fbops->fb_set_par(info); | ||
| 1017 | |||
| 1018 | if (ret) { | ||
| 1019 | info->var = old_var; | ||
| 1020 | printk(KERN_WARNING "detected " | ||
| 1021 | "fb_set_par error, " | ||
| 1022 | "error code: %d\n", ret); | ||
| 1023 | goto done; | ||
| 1024 | } | ||
| 1025 | } | ||
| 1026 | |||
| 1027 | fb_pan_display(info, &info->var); | ||
| 1028 | fb_set_cmap(&info->cmap, info); | ||
| 1029 | fb_var_to_videomode(&mode, &info->var); | ||
| 1030 | |||
| 1031 | if (info->modelist.prev && info->modelist.next && | ||
| 1032 | !list_empty(&info->modelist)) | ||
| 1033 | ret = fb_add_videomode(&mode, &info->modelist); | ||
| 1034 | |||
| 1035 | if (!ret && (flags & FBINFO_MISC_USEREVENT)) { | ||
| 1036 | struct fb_event event; | ||
| 1037 | int evnt = (activate & FB_ACTIVATE_ALL) ? | ||
| 1038 | FB_EVENT_MODE_CHANGE_ALL : | ||
| 1039 | FB_EVENT_MODE_CHANGE; | ||
| 1040 | |||
| 1041 | info->flags &= ~FBINFO_MISC_USEREVENT; | ||
| 1042 | event.info = info; | ||
| 1043 | event.data = &mode; | ||
| 1044 | fb_notifier_call_chain(evnt, &event); | ||
| 1045 | } | ||
| 1046 | } | ||
| 1047 | } | ||
| 1048 | |||
| 1049 | done: | ||
| 1050 | return ret; | ||
| 1051 | } | ||
| 1052 | EXPORT_SYMBOL(fb_set_var); | ||
| 1053 | |||
| 1054 | int | ||
| 1055 | fb_blank(struct fb_info *info, int blank) | ||
| 1056 | { | ||
| 1057 | struct fb_event event; | ||
| 1058 | int ret = -EINVAL, early_ret; | ||
| 1059 | |||
| 1060 | if (blank > FB_BLANK_POWERDOWN) | ||
| 1061 | blank = FB_BLANK_POWERDOWN; | ||
| 1062 | |||
| 1063 | event.info = info; | ||
| 1064 | event.data = ␣ | ||
| 1065 | |||
| 1066 | early_ret = fb_notifier_call_chain(FB_EARLY_EVENT_BLANK, &event); | ||
| 1067 | |||
| 1068 | if (info->fbops->fb_blank) | ||
| 1069 | ret = info->fbops->fb_blank(blank, info); | ||
| 1070 | |||
| 1071 | if (!ret) | ||
| 1072 | fb_notifier_call_chain(FB_EVENT_BLANK, &event); | ||
| 1073 | else { | ||
| 1074 | /* | ||
| 1075 | * if fb_blank is failed then revert effects of | ||
| 1076 | * the early blank event. | ||
| 1077 | */ | ||
| 1078 | if (!early_ret) | ||
| 1079 | fb_notifier_call_chain(FB_R_EARLY_EVENT_BLANK, &event); | ||
| 1080 | } | ||
| 1081 | |||
| 1082 | return ret; | ||
| 1083 | } | ||
| 1084 | EXPORT_SYMBOL(fb_blank); | ||
| 1085 | |||
| 1086 | static long do_fb_ioctl(struct fb_info *info, unsigned int cmd, | ||
| 1087 | unsigned long arg) | ||
| 1088 | { | ||
| 1089 | struct fb_ops *fb; | ||
| 1090 | struct fb_var_screeninfo var; | ||
| 1091 | struct fb_fix_screeninfo fix; | ||
| 1092 | struct fb_con2fbmap con2fb; | ||
| 1093 | struct fb_cmap cmap_from; | ||
| 1094 | struct fb_cmap_user cmap; | ||
| 1095 | struct fb_event event; | ||
| 1096 | void __user *argp = (void __user *)arg; | ||
| 1097 | long ret = 0; | ||
| 1098 | |||
| 1099 | switch (cmd) { | ||
| 1100 | case FBIOGET_VSCREENINFO: | ||
| 1101 | if (!lock_fb_info(info)) | ||
| 1102 | return -ENODEV; | ||
| 1103 | var = info->var; | ||
| 1104 | unlock_fb_info(info); | ||
| 1105 | |||
| 1106 | ret = copy_to_user(argp, &var, sizeof(var)) ? -EFAULT : 0; | ||
| 1107 | break; | ||
| 1108 | case FBIOPUT_VSCREENINFO: | ||
| 1109 | if (copy_from_user(&var, argp, sizeof(var))) | ||
| 1110 | return -EFAULT; | ||
| 1111 | console_lock(); | ||
| 1112 | if (!lock_fb_info(info)) { | ||
| 1113 | console_unlock(); | ||
| 1114 | return -ENODEV; | ||
| 1115 | } | ||
| 1116 | info->flags |= FBINFO_MISC_USEREVENT; | ||
| 1117 | ret = fb_set_var(info, &var); | ||
| 1118 | info->flags &= ~FBINFO_MISC_USEREVENT; | ||
| 1119 | unlock_fb_info(info); | ||
| 1120 | console_unlock(); | ||
| 1121 | if (!ret && copy_to_user(argp, &var, sizeof(var))) | ||
| 1122 | ret = -EFAULT; | ||
| 1123 | break; | ||
| 1124 | case FBIOGET_FSCREENINFO: | ||
| 1125 | if (!lock_fb_info(info)) | ||
| 1126 | return -ENODEV; | ||
| 1127 | fix = info->fix; | ||
| 1128 | unlock_fb_info(info); | ||
| 1129 | |||
| 1130 | ret = copy_to_user(argp, &fix, sizeof(fix)) ? -EFAULT : 0; | ||
| 1131 | break; | ||
| 1132 | case FBIOPUTCMAP: | ||
| 1133 | if (copy_from_user(&cmap, argp, sizeof(cmap))) | ||
| 1134 | return -EFAULT; | ||
| 1135 | ret = fb_set_user_cmap(&cmap, info); | ||
| 1136 | break; | ||
| 1137 | case FBIOGETCMAP: | ||
| 1138 | if (copy_from_user(&cmap, argp, sizeof(cmap))) | ||
| 1139 | return -EFAULT; | ||
| 1140 | if (!lock_fb_info(info)) | ||
| 1141 | return -ENODEV; | ||
| 1142 | cmap_from = info->cmap; | ||
| 1143 | unlock_fb_info(info); | ||
| 1144 | ret = fb_cmap_to_user(&cmap_from, &cmap); | ||
| 1145 | break; | ||
| 1146 | case FBIOPAN_DISPLAY: | ||
| 1147 | if (copy_from_user(&var, argp, sizeof(var))) | ||
| 1148 | return -EFAULT; | ||
| 1149 | console_lock(); | ||
| 1150 | if (!lock_fb_info(info)) { | ||
| 1151 | console_unlock(); | ||
| 1152 | return -ENODEV; | ||
| 1153 | } | ||
| 1154 | ret = fb_pan_display(info, &var); | ||
| 1155 | unlock_fb_info(info); | ||
| 1156 | console_unlock(); | ||
| 1157 | if (ret == 0 && copy_to_user(argp, &var, sizeof(var))) | ||
| 1158 | return -EFAULT; | ||
| 1159 | break; | ||
| 1160 | case FBIO_CURSOR: | ||
| 1161 | ret = -EINVAL; | ||
| 1162 | break; | ||
| 1163 | case FBIOGET_CON2FBMAP: | ||
| 1164 | if (copy_from_user(&con2fb, argp, sizeof(con2fb))) | ||
| 1165 | return -EFAULT; | ||
| 1166 | if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES) | ||
| 1167 | return -EINVAL; | ||
| 1168 | con2fb.framebuffer = -1; | ||
| 1169 | event.data = &con2fb; | ||
| 1170 | if (!lock_fb_info(info)) | ||
| 1171 | return -ENODEV; | ||
| 1172 | event.info = info; | ||
| 1173 | fb_notifier_call_chain(FB_EVENT_GET_CONSOLE_MAP, &event); | ||
| 1174 | unlock_fb_info(info); | ||
| 1175 | ret = copy_to_user(argp, &con2fb, sizeof(con2fb)) ? -EFAULT : 0; | ||
| 1176 | break; | ||
| 1177 | case FBIOPUT_CON2FBMAP: | ||
| 1178 | if (copy_from_user(&con2fb, argp, sizeof(con2fb))) | ||
| 1179 | return -EFAULT; | ||
| 1180 | if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES) | ||
| 1181 | return -EINVAL; | ||
| 1182 | if (con2fb.framebuffer < 0 || con2fb.framebuffer >= FB_MAX) | ||
| 1183 | return -EINVAL; | ||
| 1184 | if (!registered_fb[con2fb.framebuffer]) | ||
| 1185 | request_module("fb%d", con2fb.framebuffer); | ||
| 1186 | if (!registered_fb[con2fb.framebuffer]) { | ||
| 1187 | ret = -EINVAL; | ||
| 1188 | break; | ||
| 1189 | } | ||
| 1190 | event.data = &con2fb; | ||
| 1191 | console_lock(); | ||
| 1192 | if (!lock_fb_info(info)) { | ||
| 1193 | console_unlock(); | ||
| 1194 | return -ENODEV; | ||
| 1195 | } | ||
| 1196 | event.info = info; | ||
| 1197 | ret = fb_notifier_call_chain(FB_EVENT_SET_CONSOLE_MAP, &event); | ||
| 1198 | unlock_fb_info(info); | ||
| 1199 | console_unlock(); | ||
| 1200 | break; | ||
| 1201 | case FBIOBLANK: | ||
| 1202 | console_lock(); | ||
| 1203 | if (!lock_fb_info(info)) { | ||
| 1204 | console_unlock(); | ||
| 1205 | return -ENODEV; | ||
| 1206 | } | ||
| 1207 | info->flags |= FBINFO_MISC_USEREVENT; | ||
| 1208 | ret = fb_blank(info, arg); | ||
| 1209 | info->flags &= ~FBINFO_MISC_USEREVENT; | ||
| 1210 | unlock_fb_info(info); | ||
| 1211 | console_unlock(); | ||
| 1212 | break; | ||
| 1213 | default: | ||
| 1214 | if (!lock_fb_info(info)) | ||
| 1215 | return -ENODEV; | ||
| 1216 | fb = info->fbops; | ||
| 1217 | if (fb->fb_ioctl) | ||
| 1218 | ret = fb->fb_ioctl(info, cmd, arg); | ||
| 1219 | else | ||
| 1220 | ret = -ENOTTY; | ||
| 1221 | unlock_fb_info(info); | ||
| 1222 | } | ||
| 1223 | return ret; | ||
| 1224 | } | ||
| 1225 | |||
| 1226 | static long fb_ioctl(struct file *file, unsigned int cmd, unsigned long arg) | ||
| 1227 | { | ||
| 1228 | struct fb_info *info = file_fb_info(file); | ||
| 1229 | |||
| 1230 | if (!info) | ||
| 1231 | return -ENODEV; | ||
| 1232 | return do_fb_ioctl(info, cmd, arg); | ||
| 1233 | } | ||
| 1234 | |||
| 1235 | #ifdef CONFIG_COMPAT | ||
| 1236 | struct fb_fix_screeninfo32 { | ||
| 1237 | char id[16]; | ||
| 1238 | compat_caddr_t smem_start; | ||
| 1239 | u32 smem_len; | ||
| 1240 | u32 type; | ||
| 1241 | u32 type_aux; | ||
| 1242 | u32 visual; | ||
| 1243 | u16 xpanstep; | ||
| 1244 | u16 ypanstep; | ||
| 1245 | u16 ywrapstep; | ||
| 1246 | u32 line_length; | ||
| 1247 | compat_caddr_t mmio_start; | ||
| 1248 | u32 mmio_len; | ||
| 1249 | u32 accel; | ||
| 1250 | u16 reserved[3]; | ||
| 1251 | }; | ||
| 1252 | |||
| 1253 | struct fb_cmap32 { | ||
| 1254 | u32 start; | ||
| 1255 | u32 len; | ||
| 1256 | compat_caddr_t red; | ||
| 1257 | compat_caddr_t green; | ||
| 1258 | compat_caddr_t blue; | ||
| 1259 | compat_caddr_t transp; | ||
| 1260 | }; | ||
| 1261 | |||
| 1262 | static int fb_getput_cmap(struct fb_info *info, unsigned int cmd, | ||
| 1263 | unsigned long arg) | ||
| 1264 | { | ||
| 1265 | struct fb_cmap_user __user *cmap; | ||
| 1266 | struct fb_cmap32 __user *cmap32; | ||
| 1267 | __u32 data; | ||
| 1268 | int err; | ||
| 1269 | |||
| 1270 | cmap = compat_alloc_user_space(sizeof(*cmap)); | ||
| 1271 | cmap32 = compat_ptr(arg); | ||
| 1272 | |||
| 1273 | if (copy_in_user(&cmap->start, &cmap32->start, 2 * sizeof(__u32))) | ||
| 1274 | return -EFAULT; | ||
| 1275 | |||
| 1276 | if (get_user(data, &cmap32->red) || | ||
| 1277 | put_user(compat_ptr(data), &cmap->red) || | ||
| 1278 | get_user(data, &cmap32->green) || | ||
| 1279 | put_user(compat_ptr(data), &cmap->green) || | ||
| 1280 | get_user(data, &cmap32->blue) || | ||
| 1281 | put_user(compat_ptr(data), &cmap->blue) || | ||
| 1282 | get_user(data, &cmap32->transp) || | ||
| 1283 | put_user(compat_ptr(data), &cmap->transp)) | ||
| 1284 | return -EFAULT; | ||
| 1285 | |||
| 1286 | err = do_fb_ioctl(info, cmd, (unsigned long) cmap); | ||
| 1287 | |||
| 1288 | if (!err) { | ||
| 1289 | if (copy_in_user(&cmap32->start, | ||
| 1290 | &cmap->start, | ||
| 1291 | 2 * sizeof(__u32))) | ||
| 1292 | err = -EFAULT; | ||
| 1293 | } | ||
| 1294 | return err; | ||
| 1295 | } | ||
| 1296 | |||
| 1297 | static int do_fscreeninfo_to_user(struct fb_fix_screeninfo *fix, | ||
| 1298 | struct fb_fix_screeninfo32 __user *fix32) | ||
| 1299 | { | ||
| 1300 | __u32 data; | ||
| 1301 | int err; | ||
| 1302 | |||
| 1303 | err = copy_to_user(&fix32->id, &fix->id, sizeof(fix32->id)); | ||
| 1304 | |||
| 1305 | data = (__u32) (unsigned long) fix->smem_start; | ||
| 1306 | err |= put_user(data, &fix32->smem_start); | ||
| 1307 | |||
| 1308 | err |= put_user(fix->smem_len, &fix32->smem_len); | ||
| 1309 | err |= put_user(fix->type, &fix32->type); | ||
| 1310 | err |= put_user(fix->type_aux, &fix32->type_aux); | ||
| 1311 | err |= put_user(fix->visual, &fix32->visual); | ||
| 1312 | err |= put_user(fix->xpanstep, &fix32->xpanstep); | ||
| 1313 | err |= put_user(fix->ypanstep, &fix32->ypanstep); | ||
| 1314 | err |= put_user(fix->ywrapstep, &fix32->ywrapstep); | ||
| 1315 | err |= put_user(fix->line_length, &fix32->line_length); | ||
| 1316 | |||
| 1317 | data = (__u32) (unsigned long) fix->mmio_start; | ||
| 1318 | err |= put_user(data, &fix32->mmio_start); | ||
| 1319 | |||
| 1320 | err |= put_user(fix->mmio_len, &fix32->mmio_len); | ||
| 1321 | err |= put_user(fix->accel, &fix32->accel); | ||
| 1322 | err |= copy_to_user(fix32->reserved, fix->reserved, | ||
| 1323 | sizeof(fix->reserved)); | ||
| 1324 | |||
| 1325 | if (err) | ||
| 1326 | return -EFAULT; | ||
| 1327 | return 0; | ||
| 1328 | } | ||
| 1329 | |||
| 1330 | static int fb_get_fscreeninfo(struct fb_info *info, unsigned int cmd, | ||
| 1331 | unsigned long arg) | ||
| 1332 | { | ||
| 1333 | mm_segment_t old_fs; | ||
| 1334 | struct fb_fix_screeninfo fix; | ||
| 1335 | struct fb_fix_screeninfo32 __user *fix32; | ||
| 1336 | int err; | ||
| 1337 | |||
| 1338 | fix32 = compat_ptr(arg); | ||
| 1339 | |||
| 1340 | old_fs = get_fs(); | ||
| 1341 | set_fs(KERNEL_DS); | ||
| 1342 | err = do_fb_ioctl(info, cmd, (unsigned long) &fix); | ||
| 1343 | set_fs(old_fs); | ||
| 1344 | |||
| 1345 | if (!err) | ||
| 1346 | err = do_fscreeninfo_to_user(&fix, fix32); | ||
| 1347 | |||
| 1348 | return err; | ||
| 1349 | } | ||
| 1350 | |||
| 1351 | static long fb_compat_ioctl(struct file *file, unsigned int cmd, | ||
| 1352 | unsigned long arg) | ||
| 1353 | { | ||
| 1354 | struct fb_info *info = file_fb_info(file); | ||
| 1355 | struct fb_ops *fb; | ||
| 1356 | long ret = -ENOIOCTLCMD; | ||
| 1357 | |||
| 1358 | if (!info) | ||
| 1359 | return -ENODEV; | ||
| 1360 | fb = info->fbops; | ||
| 1361 | switch(cmd) { | ||
| 1362 | case FBIOGET_VSCREENINFO: | ||
| 1363 | case FBIOPUT_VSCREENINFO: | ||
| 1364 | case FBIOPAN_DISPLAY: | ||
| 1365 | case FBIOGET_CON2FBMAP: | ||
| 1366 | case FBIOPUT_CON2FBMAP: | ||
| 1367 | arg = (unsigned long) compat_ptr(arg); | ||
| 1368 | case FBIOBLANK: | ||
| 1369 | ret = do_fb_ioctl(info, cmd, arg); | ||
| 1370 | break; | ||
| 1371 | |||
| 1372 | case FBIOGET_FSCREENINFO: | ||
| 1373 | ret = fb_get_fscreeninfo(info, cmd, arg); | ||
| 1374 | break; | ||
| 1375 | |||
| 1376 | case FBIOGETCMAP: | ||
| 1377 | case FBIOPUTCMAP: | ||
| 1378 | ret = fb_getput_cmap(info, cmd, arg); | ||
| 1379 | break; | ||
| 1380 | |||
| 1381 | default: | ||
| 1382 | if (fb->fb_compat_ioctl) | ||
| 1383 | ret = fb->fb_compat_ioctl(info, cmd, arg); | ||
| 1384 | break; | ||
| 1385 | } | ||
| 1386 | return ret; | ||
| 1387 | } | ||
| 1388 | #endif | ||
| 1389 | |||
| 1390 | static int | ||
| 1391 | fb_mmap(struct file *file, struct vm_area_struct * vma) | ||
| 1392 | { | ||
| 1393 | struct fb_info *info = file_fb_info(file); | ||
| 1394 | struct fb_ops *fb; | ||
| 1395 | unsigned long mmio_pgoff; | ||
| 1396 | unsigned long start; | ||
| 1397 | u32 len; | ||
| 1398 | |||
| 1399 | if (!info) | ||
| 1400 | return -ENODEV; | ||
| 1401 | fb = info->fbops; | ||
| 1402 | if (!fb) | ||
| 1403 | return -ENODEV; | ||
| 1404 | mutex_lock(&info->mm_lock); | ||
| 1405 | if (fb->fb_mmap) { | ||
| 1406 | int res; | ||
| 1407 | res = fb->fb_mmap(info, vma); | ||
| 1408 | mutex_unlock(&info->mm_lock); | ||
| 1409 | return res; | ||
| 1410 | } | ||
| 1411 | |||
| 1412 | /* | ||
| 1413 | * Ugh. This can be either the frame buffer mapping, or | ||
| 1414 | * if pgoff points past it, the mmio mapping. | ||
| 1415 | */ | ||
| 1416 | start = info->fix.smem_start; | ||
| 1417 | len = info->fix.smem_len; | ||
| 1418 | mmio_pgoff = PAGE_ALIGN((start & ~PAGE_MASK) + len) >> PAGE_SHIFT; | ||
| 1419 | if (vma->vm_pgoff >= mmio_pgoff) { | ||
| 1420 | if (info->var.accel_flags) { | ||
| 1421 | mutex_unlock(&info->mm_lock); | ||
| 1422 | return -EINVAL; | ||
| 1423 | } | ||
| 1424 | |||
| 1425 | vma->vm_pgoff -= mmio_pgoff; | ||
| 1426 | start = info->fix.mmio_start; | ||
| 1427 | len = info->fix.mmio_len; | ||
| 1428 | } | ||
| 1429 | mutex_unlock(&info->mm_lock); | ||
| 1430 | |||
| 1431 | vma->vm_page_prot = vm_get_page_prot(vma->vm_flags); | ||
| 1432 | fb_pgprotect(file, vma, start); | ||
| 1433 | |||
| 1434 | return vm_iomap_memory(vma, start, len); | ||
| 1435 | } | ||
| 1436 | |||
| 1437 | static int | ||
| 1438 | fb_open(struct inode *inode, struct file *file) | ||
| 1439 | __acquires(&info->lock) | ||
| 1440 | __releases(&info->lock) | ||
| 1441 | { | ||
| 1442 | int fbidx = iminor(inode); | ||
| 1443 | struct fb_info *info; | ||
| 1444 | int res = 0; | ||
| 1445 | |||
| 1446 | info = get_fb_info(fbidx); | ||
| 1447 | if (!info) { | ||
| 1448 | request_module("fb%d", fbidx); | ||
| 1449 | info = get_fb_info(fbidx); | ||
| 1450 | if (!info) | ||
| 1451 | return -ENODEV; | ||
| 1452 | } | ||
| 1453 | if (IS_ERR(info)) | ||
| 1454 | return PTR_ERR(info); | ||
| 1455 | |||
| 1456 | mutex_lock(&info->lock); | ||
| 1457 | if (!try_module_get(info->fbops->owner)) { | ||
| 1458 | res = -ENODEV; | ||
| 1459 | goto out; | ||
| 1460 | } | ||
| 1461 | file->private_data = info; | ||
| 1462 | if (info->fbops->fb_open) { | ||
| 1463 | res = info->fbops->fb_open(info,1); | ||
| 1464 | if (res) | ||
| 1465 | module_put(info->fbops->owner); | ||
| 1466 | } | ||
| 1467 | #ifdef CONFIG_FB_DEFERRED_IO | ||
| 1468 | if (info->fbdefio) | ||
| 1469 | fb_deferred_io_open(info, inode, file); | ||
| 1470 | #endif | ||
| 1471 | out: | ||
| 1472 | mutex_unlock(&info->lock); | ||
| 1473 | if (res) | ||
| 1474 | put_fb_info(info); | ||
| 1475 | return res; | ||
| 1476 | } | ||
| 1477 | |||
| 1478 | static int | ||
| 1479 | fb_release(struct inode *inode, struct file *file) | ||
| 1480 | __acquires(&info->lock) | ||
| 1481 | __releases(&info->lock) | ||
| 1482 | { | ||
| 1483 | struct fb_info * const info = file->private_data; | ||
| 1484 | |||
| 1485 | mutex_lock(&info->lock); | ||
| 1486 | if (info->fbops->fb_release) | ||
| 1487 | info->fbops->fb_release(info,1); | ||
| 1488 | module_put(info->fbops->owner); | ||
| 1489 | mutex_unlock(&info->lock); | ||
| 1490 | put_fb_info(info); | ||
| 1491 | return 0; | ||
| 1492 | } | ||
| 1493 | |||
| 1494 | static const struct file_operations fb_fops = { | ||
| 1495 | .owner = THIS_MODULE, | ||
| 1496 | .read = fb_read, | ||
| 1497 | .write = fb_write, | ||
| 1498 | .unlocked_ioctl = fb_ioctl, | ||
| 1499 | #ifdef CONFIG_COMPAT | ||
| 1500 | .compat_ioctl = fb_compat_ioctl, | ||
| 1501 | #endif | ||
| 1502 | .mmap = fb_mmap, | ||
| 1503 | .open = fb_open, | ||
| 1504 | .release = fb_release, | ||
| 1505 | #ifdef HAVE_ARCH_FB_UNMAPPED_AREA | ||
| 1506 | .get_unmapped_area = get_fb_unmapped_area, | ||
| 1507 | #endif | ||
| 1508 | #ifdef CONFIG_FB_DEFERRED_IO | ||
| 1509 | .fsync = fb_deferred_io_fsync, | ||
| 1510 | #endif | ||
| 1511 | .llseek = default_llseek, | ||
| 1512 | }; | ||
| 1513 | |||
| 1514 | struct class *fb_class; | ||
| 1515 | EXPORT_SYMBOL(fb_class); | ||
| 1516 | |||
| 1517 | static int fb_check_foreignness(struct fb_info *fi) | ||
| 1518 | { | ||
| 1519 | const bool foreign_endian = fi->flags & FBINFO_FOREIGN_ENDIAN; | ||
| 1520 | |||
| 1521 | fi->flags &= ~FBINFO_FOREIGN_ENDIAN; | ||
| 1522 | |||
| 1523 | #ifdef __BIG_ENDIAN | ||
| 1524 | fi->flags |= foreign_endian ? 0 : FBINFO_BE_MATH; | ||
| 1525 | #else | ||
| 1526 | fi->flags |= foreign_endian ? FBINFO_BE_MATH : 0; | ||
| 1527 | #endif /* __BIG_ENDIAN */ | ||
| 1528 | |||
| 1529 | if (fi->flags & FBINFO_BE_MATH && !fb_be_math(fi)) { | ||
| 1530 | pr_err("%s: enable CONFIG_FB_BIG_ENDIAN to " | ||
| 1531 | "support this framebuffer\n", fi->fix.id); | ||
| 1532 | return -ENOSYS; | ||
| 1533 | } else if (!(fi->flags & FBINFO_BE_MATH) && fb_be_math(fi)) { | ||
| 1534 | pr_err("%s: enable CONFIG_FB_LITTLE_ENDIAN to " | ||
| 1535 | "support this framebuffer\n", fi->fix.id); | ||
| 1536 | return -ENOSYS; | ||
| 1537 | } | ||
| 1538 | |||
| 1539 | return 0; | ||
| 1540 | } | ||
| 1541 | |||
| 1542 | static bool apertures_overlap(struct aperture *gen, struct aperture *hw) | ||
| 1543 | { | ||
| 1544 | /* is the generic aperture base the same as the HW one */ | ||
| 1545 | if (gen->base == hw->base) | ||
| 1546 | return true; | ||
| 1547 | /* is the generic aperture base inside the hw base->hw base+size */ | ||
| 1548 | if (gen->base > hw->base && gen->base < hw->base + hw->size) | ||
| 1549 | return true; | ||
| 1550 | return false; | ||
| 1551 | } | ||
| 1552 | |||
| 1553 | static bool fb_do_apertures_overlap(struct apertures_struct *gena, | ||
| 1554 | struct apertures_struct *hwa) | ||
| 1555 | { | ||
| 1556 | int i, j; | ||
| 1557 | if (!hwa || !gena) | ||
| 1558 | return false; | ||
| 1559 | |||
| 1560 | for (i = 0; i < hwa->count; ++i) { | ||
| 1561 | struct aperture *h = &hwa->ranges[i]; | ||
| 1562 | for (j = 0; j < gena->count; ++j) { | ||
| 1563 | struct aperture *g = &gena->ranges[j]; | ||
| 1564 | printk(KERN_DEBUG "checking generic (%llx %llx) vs hw (%llx %llx)\n", | ||
| 1565 | (unsigned long long)g->base, | ||
| 1566 | (unsigned long long)g->size, | ||
| 1567 | (unsigned long long)h->base, | ||
| 1568 | (unsigned long long)h->size); | ||
| 1569 | if (apertures_overlap(g, h)) | ||
| 1570 | return true; | ||
| 1571 | } | ||
| 1572 | } | ||
| 1573 | |||
| 1574 | return false; | ||
| 1575 | } | ||
| 1576 | |||
| 1577 | static int do_unregister_framebuffer(struct fb_info *fb_info); | ||
| 1578 | |||
| 1579 | #define VGA_FB_PHYS 0xA0000 | ||
| 1580 | static int do_remove_conflicting_framebuffers(struct apertures_struct *a, | ||
| 1581 | const char *name, bool primary) | ||
| 1582 | { | ||
| 1583 | int i, ret; | ||
| 1584 | |||
| 1585 | /* check all firmware fbs and kick off if the base addr overlaps */ | ||
| 1586 | for (i = 0 ; i < FB_MAX; i++) { | ||
| 1587 | struct apertures_struct *gen_aper; | ||
| 1588 | if (!registered_fb[i]) | ||
| 1589 | continue; | ||
| 1590 | |||
| 1591 | if (!(registered_fb[i]->flags & FBINFO_MISC_FIRMWARE)) | ||
| 1592 | continue; | ||
| 1593 | |||
| 1594 | gen_aper = registered_fb[i]->apertures; | ||
| 1595 | if (fb_do_apertures_overlap(gen_aper, a) || | ||
| 1596 | (primary && gen_aper && gen_aper->count && | ||
| 1597 | gen_aper->ranges[0].base == VGA_FB_PHYS)) { | ||
| 1598 | |||
| 1599 | printk(KERN_INFO "fb: switching to %s from %s\n", | ||
| 1600 | name, registered_fb[i]->fix.id); | ||
| 1601 | ret = do_unregister_framebuffer(registered_fb[i]); | ||
| 1602 | if (ret) | ||
| 1603 | return ret; | ||
| 1604 | } | ||
| 1605 | } | ||
| 1606 | |||
| 1607 | return 0; | ||
| 1608 | } | ||
| 1609 | |||
| 1610 | static int do_register_framebuffer(struct fb_info *fb_info) | ||
| 1611 | { | ||
| 1612 | int i, ret; | ||
| 1613 | struct fb_event event; | ||
| 1614 | struct fb_videomode mode; | ||
| 1615 | |||
| 1616 | if (fb_check_foreignness(fb_info)) | ||
| 1617 | return -ENOSYS; | ||
| 1618 | |||
| 1619 | ret = do_remove_conflicting_framebuffers(fb_info->apertures, | ||
| 1620 | fb_info->fix.id, | ||
| 1621 | fb_is_primary_device(fb_info)); | ||
| 1622 | if (ret) | ||
| 1623 | return ret; | ||
| 1624 | |||
| 1625 | if (num_registered_fb == FB_MAX) | ||
| 1626 | return -ENXIO; | ||
| 1627 | |||
| 1628 | num_registered_fb++; | ||
| 1629 | for (i = 0 ; i < FB_MAX; i++) | ||
| 1630 | if (!registered_fb[i]) | ||
| 1631 | break; | ||
| 1632 | fb_info->node = i; | ||
| 1633 | atomic_set(&fb_info->count, 1); | ||
| 1634 | mutex_init(&fb_info->lock); | ||
| 1635 | mutex_init(&fb_info->mm_lock); | ||
| 1636 | |||
| 1637 | fb_info->dev = device_create(fb_class, fb_info->device, | ||
| 1638 | MKDEV(FB_MAJOR, i), NULL, "fb%d", i); | ||
| 1639 | if (IS_ERR(fb_info->dev)) { | ||
| 1640 | /* Not fatal */ | ||
| 1641 | printk(KERN_WARNING "Unable to create device for framebuffer %d; errno = %ld\n", i, PTR_ERR(fb_info->dev)); | ||
| 1642 | fb_info->dev = NULL; | ||
| 1643 | } else | ||
| 1644 | fb_init_device(fb_info); | ||
| 1645 | |||
| 1646 | if (fb_info->pixmap.addr == NULL) { | ||
| 1647 | fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL); | ||
| 1648 | if (fb_info->pixmap.addr) { | ||
| 1649 | fb_info->pixmap.size = FBPIXMAPSIZE; | ||
| 1650 | fb_info->pixmap.buf_align = 1; | ||
| 1651 | fb_info->pixmap.scan_align = 1; | ||
| 1652 | fb_info->pixmap.access_align = 32; | ||
| 1653 | fb_info->pixmap.flags = FB_PIXMAP_DEFAULT; | ||
| 1654 | } | ||
| 1655 | } | ||
| 1656 | fb_info->pixmap.offset = 0; | ||
| 1657 | |||
| 1658 | if (!fb_info->pixmap.blit_x) | ||
| 1659 | fb_info->pixmap.blit_x = ~(u32)0; | ||
| 1660 | |||
| 1661 | if (!fb_info->pixmap.blit_y) | ||
| 1662 | fb_info->pixmap.blit_y = ~(u32)0; | ||
| 1663 | |||
| 1664 | if (!fb_info->modelist.prev || !fb_info->modelist.next) | ||
| 1665 | INIT_LIST_HEAD(&fb_info->modelist); | ||
| 1666 | |||
| 1667 | if (fb_info->skip_vt_switch) | ||
| 1668 | pm_vt_switch_required(fb_info->dev, false); | ||
| 1669 | else | ||
| 1670 | pm_vt_switch_required(fb_info->dev, true); | ||
| 1671 | |||
| 1672 | fb_var_to_videomode(&mode, &fb_info->var); | ||
| 1673 | fb_add_videomode(&mode, &fb_info->modelist); | ||
| 1674 | registered_fb[i] = fb_info; | ||
| 1675 | |||
| 1676 | event.info = fb_info; | ||
| 1677 | console_lock(); | ||
| 1678 | if (!lock_fb_info(fb_info)) { | ||
| 1679 | console_unlock(); | ||
| 1680 | return -ENODEV; | ||
| 1681 | } | ||
| 1682 | |||
| 1683 | fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event); | ||
| 1684 | unlock_fb_info(fb_info); | ||
| 1685 | console_unlock(); | ||
| 1686 | return 0; | ||
| 1687 | } | ||
| 1688 | |||
| 1689 | static int do_unregister_framebuffer(struct fb_info *fb_info) | ||
| 1690 | { | ||
| 1691 | struct fb_event event; | ||
| 1692 | int i, ret = 0; | ||
| 1693 | |||
| 1694 | i = fb_info->node; | ||
| 1695 | if (i < 0 || i >= FB_MAX || registered_fb[i] != fb_info) | ||
| 1696 | return -EINVAL; | ||
| 1697 | |||
| 1698 | console_lock(); | ||
| 1699 | if (!lock_fb_info(fb_info)) { | ||
| 1700 | console_unlock(); | ||
| 1701 | return -ENODEV; | ||
| 1702 | } | ||
| 1703 | |||
| 1704 | event.info = fb_info; | ||
| 1705 | ret = fb_notifier_call_chain(FB_EVENT_FB_UNBIND, &event); | ||
| 1706 | unlock_fb_info(fb_info); | ||
| 1707 | console_unlock(); | ||
| 1708 | |||
| 1709 | if (ret) | ||
| 1710 | return -EINVAL; | ||
| 1711 | |||
| 1712 | pm_vt_switch_unregister(fb_info->dev); | ||
| 1713 | |||
| 1714 | unlink_framebuffer(fb_info); | ||
| 1715 | if (fb_info->pixmap.addr && | ||
| 1716 | (fb_info->pixmap.flags & FB_PIXMAP_DEFAULT)) | ||
| 1717 | kfree(fb_info->pixmap.addr); | ||
| 1718 | fb_destroy_modelist(&fb_info->modelist); | ||
| 1719 | registered_fb[i] = NULL; | ||
| 1720 | num_registered_fb--; | ||
| 1721 | fb_cleanup_device(fb_info); | ||
| 1722 | event.info = fb_info; | ||
| 1723 | console_lock(); | ||
| 1724 | fb_notifier_call_chain(FB_EVENT_FB_UNREGISTERED, &event); | ||
| 1725 | console_unlock(); | ||
| 1726 | |||
| 1727 | /* this may free fb info */ | ||
| 1728 | put_fb_info(fb_info); | ||
| 1729 | return 0; | ||
| 1730 | } | ||
| 1731 | |||
| 1732 | int unlink_framebuffer(struct fb_info *fb_info) | ||
| 1733 | { | ||
| 1734 | int i; | ||
| 1735 | |||
| 1736 | i = fb_info->node; | ||
| 1737 | if (i < 0 || i >= FB_MAX || registered_fb[i] != fb_info) | ||
| 1738 | return -EINVAL; | ||
| 1739 | |||
| 1740 | if (fb_info->dev) { | ||
| 1741 | device_destroy(fb_class, MKDEV(FB_MAJOR, i)); | ||
| 1742 | fb_info->dev = NULL; | ||
| 1743 | } | ||
| 1744 | return 0; | ||
| 1745 | } | ||
| 1746 | EXPORT_SYMBOL(unlink_framebuffer); | ||
| 1747 | |||
| 1748 | int remove_conflicting_framebuffers(struct apertures_struct *a, | ||
| 1749 | const char *name, bool primary) | ||
| 1750 | { | ||
| 1751 | int ret; | ||
| 1752 | |||
| 1753 | mutex_lock(®istration_lock); | ||
| 1754 | ret = do_remove_conflicting_framebuffers(a, name, primary); | ||
| 1755 | mutex_unlock(®istration_lock); | ||
| 1756 | |||
| 1757 | return ret; | ||
| 1758 | } | ||
| 1759 | EXPORT_SYMBOL(remove_conflicting_framebuffers); | ||
| 1760 | |||
| 1761 | /** | ||
| 1762 | * register_framebuffer - registers a frame buffer device | ||
| 1763 | * @fb_info: frame buffer info structure | ||
| 1764 | * | ||
| 1765 | * Registers a frame buffer device @fb_info. | ||
| 1766 | * | ||
| 1767 | * Returns negative errno on error, or zero for success. | ||
| 1768 | * | ||
| 1769 | */ | ||
| 1770 | int | ||
| 1771 | register_framebuffer(struct fb_info *fb_info) | ||
| 1772 | { | ||
| 1773 | int ret; | ||
| 1774 | |||
| 1775 | mutex_lock(®istration_lock); | ||
| 1776 | ret = do_register_framebuffer(fb_info); | ||
| 1777 | mutex_unlock(®istration_lock); | ||
| 1778 | |||
| 1779 | return ret; | ||
| 1780 | } | ||
| 1781 | EXPORT_SYMBOL(register_framebuffer); | ||
| 1782 | |||
| 1783 | /** | ||
| 1784 | * unregister_framebuffer - releases a frame buffer device | ||
| 1785 | * @fb_info: frame buffer info structure | ||
| 1786 | * | ||
| 1787 | * Unregisters a frame buffer device @fb_info. | ||
| 1788 | * | ||
| 1789 | * Returns negative errno on error, or zero for success. | ||
| 1790 | * | ||
| 1791 | * This function will also notify the framebuffer console | ||
| 1792 | * to release the driver. | ||
| 1793 | * | ||
| 1794 | * This is meant to be called within a driver's module_exit() | ||
| 1795 | * function. If this is called outside module_exit(), ensure | ||
| 1796 | * that the driver implements fb_open() and fb_release() to | ||
| 1797 | * check that no processes are using the device. | ||
| 1798 | */ | ||
| 1799 | int | ||
| 1800 | unregister_framebuffer(struct fb_info *fb_info) | ||
| 1801 | { | ||
| 1802 | int ret; | ||
| 1803 | |||
| 1804 | mutex_lock(®istration_lock); | ||
| 1805 | ret = do_unregister_framebuffer(fb_info); | ||
| 1806 | mutex_unlock(®istration_lock); | ||
| 1807 | |||
| 1808 | return ret; | ||
| 1809 | } | ||
| 1810 | EXPORT_SYMBOL(unregister_framebuffer); | ||
| 1811 | |||
| 1812 | /** | ||
| 1813 | * fb_set_suspend - low level driver signals suspend | ||
| 1814 | * @info: framebuffer affected | ||
| 1815 | * @state: 0 = resuming, !=0 = suspending | ||
| 1816 | * | ||
| 1817 | * This is meant to be used by low level drivers to | ||
| 1818 | * signal suspend/resume to the core & clients. | ||
| 1819 | * It must be called with the console semaphore held | ||
| 1820 | */ | ||
| 1821 | void fb_set_suspend(struct fb_info *info, int state) | ||
| 1822 | { | ||
| 1823 | struct fb_event event; | ||
| 1824 | |||
| 1825 | event.info = info; | ||
| 1826 | if (state) { | ||
| 1827 | fb_notifier_call_chain(FB_EVENT_SUSPEND, &event); | ||
| 1828 | info->state = FBINFO_STATE_SUSPENDED; | ||
| 1829 | } else { | ||
| 1830 | info->state = FBINFO_STATE_RUNNING; | ||
| 1831 | fb_notifier_call_chain(FB_EVENT_RESUME, &event); | ||
| 1832 | } | ||
| 1833 | } | ||
| 1834 | EXPORT_SYMBOL(fb_set_suspend); | ||
| 1835 | |||
| 1836 | /** | ||
| 1837 | * fbmem_init - init frame buffer subsystem | ||
| 1838 | * | ||
| 1839 | * Initialize the frame buffer subsystem. | ||
| 1840 | * | ||
| 1841 | * NOTE: This function is _only_ to be called by drivers/char/mem.c. | ||
| 1842 | * | ||
| 1843 | */ | ||
| 1844 | |||
| 1845 | static int __init | ||
| 1846 | fbmem_init(void) | ||
| 1847 | { | ||
| 1848 | proc_create("fb", 0, NULL, &fb_proc_fops); | ||
| 1849 | |||
| 1850 | if (register_chrdev(FB_MAJOR,"fb",&fb_fops)) | ||
| 1851 | printk("unable to get major %d for fb devs\n", FB_MAJOR); | ||
| 1852 | |||
| 1853 | fb_class = class_create(THIS_MODULE, "graphics"); | ||
| 1854 | if (IS_ERR(fb_class)) { | ||
| 1855 | printk(KERN_WARNING "Unable to create fb class; errno = %ld\n", PTR_ERR(fb_class)); | ||
| 1856 | fb_class = NULL; | ||
| 1857 | } | ||
| 1858 | return 0; | ||
| 1859 | } | ||
| 1860 | |||
| 1861 | #ifdef MODULE | ||
| 1862 | module_init(fbmem_init); | ||
| 1863 | static void __exit | ||
| 1864 | fbmem_exit(void) | ||
| 1865 | { | ||
| 1866 | remove_proc_entry("fb", NULL); | ||
| 1867 | class_destroy(fb_class); | ||
| 1868 | unregister_chrdev(FB_MAJOR, "fb"); | ||
| 1869 | } | ||
| 1870 | |||
| 1871 | module_exit(fbmem_exit); | ||
| 1872 | MODULE_LICENSE("GPL"); | ||
| 1873 | MODULE_DESCRIPTION("Framebuffer base"); | ||
| 1874 | #else | ||
| 1875 | subsys_initcall(fbmem_init); | ||
| 1876 | #endif | ||
| 1877 | |||
| 1878 | int fb_new_modelist(struct fb_info *info) | ||
| 1879 | { | ||
| 1880 | struct fb_event event; | ||
| 1881 | struct fb_var_screeninfo var = info->var; | ||
| 1882 | struct list_head *pos, *n; | ||
| 1883 | struct fb_modelist *modelist; | ||
| 1884 | struct fb_videomode *m, mode; | ||
| 1885 | int err = 1; | ||
| 1886 | |||
| 1887 | list_for_each_safe(pos, n, &info->modelist) { | ||
| 1888 | modelist = list_entry(pos, struct fb_modelist, list); | ||
| 1889 | m = &modelist->mode; | ||
| 1890 | fb_videomode_to_var(&var, m); | ||
| 1891 | var.activate = FB_ACTIVATE_TEST; | ||
| 1892 | err = fb_set_var(info, &var); | ||
| 1893 | fb_var_to_videomode(&mode, &var); | ||
| 1894 | if (err || !fb_mode_is_equal(m, &mode)) { | ||
| 1895 | list_del(pos); | ||
| 1896 | kfree(pos); | ||
| 1897 | } | ||
| 1898 | } | ||
| 1899 | |||
| 1900 | err = 1; | ||
| 1901 | |||
| 1902 | if (!list_empty(&info->modelist)) { | ||
| 1903 | event.info = info; | ||
| 1904 | err = fb_notifier_call_chain(FB_EVENT_NEW_MODELIST, &event); | ||
| 1905 | } | ||
| 1906 | |||
| 1907 | return err; | ||
| 1908 | } | ||
| 1909 | |||
| 1910 | static char *video_options[FB_MAX] __read_mostly; | ||
| 1911 | static int ofonly __read_mostly; | ||
| 1912 | |||
| 1913 | /** | ||
| 1914 | * fb_get_options - get kernel boot parameters | ||
| 1915 | * @name: framebuffer name as it would appear in | ||
| 1916 | * the boot parameter line | ||
| 1917 | * (video=<name>:<options>) | ||
| 1918 | * @option: the option will be stored here | ||
| 1919 | * | ||
| 1920 | * NOTE: Needed to maintain backwards compatibility | ||
| 1921 | */ | ||
| 1922 | int fb_get_options(const char *name, char **option) | ||
| 1923 | { | ||
| 1924 | char *opt, *options = NULL; | ||
| 1925 | int retval = 0; | ||
| 1926 | int name_len = strlen(name), i; | ||
| 1927 | |||
| 1928 | if (name_len && ofonly && strncmp(name, "offb", 4)) | ||
| 1929 | retval = 1; | ||
| 1930 | |||
| 1931 | if (name_len && !retval) { | ||
| 1932 | for (i = 0; i < FB_MAX; i++) { | ||
| 1933 | if (video_options[i] == NULL) | ||
| 1934 | continue; | ||
| 1935 | if (!video_options[i][0]) | ||
| 1936 | continue; | ||
| 1937 | opt = video_options[i]; | ||
| 1938 | if (!strncmp(name, opt, name_len) && | ||
| 1939 | opt[name_len] == ':') | ||
| 1940 | options = opt + name_len + 1; | ||
| 1941 | } | ||
| 1942 | } | ||
| 1943 | /* No match, pass global option */ | ||
| 1944 | if (!options && option && fb_mode_option) | ||
| 1945 | options = kstrdup(fb_mode_option, GFP_KERNEL); | ||
| 1946 | if (options && !strncmp(options, "off", 3)) | ||
| 1947 | retval = 1; | ||
| 1948 | |||
| 1949 | if (option) | ||
| 1950 | *option = options; | ||
| 1951 | |||
| 1952 | return retval; | ||
| 1953 | } | ||
| 1954 | EXPORT_SYMBOL(fb_get_options); | ||
| 1955 | |||
| 1956 | #ifndef MODULE | ||
| 1957 | /** | ||
| 1958 | * video_setup - process command line options | ||
| 1959 | * @options: string of options | ||
| 1960 | * | ||
| 1961 | * Process command line options for frame buffer subsystem. | ||
| 1962 | * | ||
| 1963 | * NOTE: This function is a __setup and __init function. | ||
| 1964 | * It only stores the options. Drivers have to call | ||
| 1965 | * fb_get_options() as necessary. | ||
| 1966 | * | ||
| 1967 | * Returns zero. | ||
| 1968 | * | ||
| 1969 | */ | ||
| 1970 | static int __init video_setup(char *options) | ||
| 1971 | { | ||
| 1972 | int i, global = 0; | ||
| 1973 | |||
| 1974 | if (!options || !*options) | ||
| 1975 | global = 1; | ||
| 1976 | |||
| 1977 | if (!global && !strncmp(options, "ofonly", 6)) { | ||
| 1978 | ofonly = 1; | ||
| 1979 | global = 1; | ||
| 1980 | } | ||
| 1981 | |||
| 1982 | if (!global && !strchr(options, ':')) { | ||
| 1983 | fb_mode_option = options; | ||
| 1984 | global = 1; | ||
| 1985 | } | ||
| 1986 | |||
| 1987 | if (!global) { | ||
| 1988 | for (i = 0; i < FB_MAX; i++) { | ||
| 1989 | if (video_options[i] == NULL) { | ||
| 1990 | video_options[i] = options; | ||
| 1991 | break; | ||
| 1992 | } | ||
| 1993 | |||
| 1994 | } | ||
| 1995 | } | ||
| 1996 | |||
| 1997 | return 1; | ||
| 1998 | } | ||
| 1999 | __setup("video=", video_setup); | ||
| 2000 | #endif | ||
| 2001 | |||
| 2002 | MODULE_LICENSE("GPL"); | ||
diff --git a/drivers/video/fbdev/core/fbmon.c b/drivers/video/fbdev/core/fbmon.c new file mode 100644 index 000000000000..c204ebe6187e --- /dev/null +++ b/drivers/video/fbdev/core/fbmon.c | |||
| @@ -0,0 +1,1592 @@ | |||
| 1 | /* | ||
| 2 | * linux/drivers/video/fbmon.c | ||
| 3 | * | ||
| 4 | * Copyright (C) 2002 James Simmons <jsimmons@users.sf.net> | ||
| 5 | * | ||
| 6 | * Credits: | ||
| 7 | * | ||
| 8 | * The EDID Parser is a conglomeration from the following sources: | ||
| 9 | * | ||
| 10 | * 1. SciTech SNAP Graphics Architecture | ||
| 11 | * Copyright (C) 1991-2002 SciTech Software, Inc. All rights reserved. | ||
| 12 | * | ||
| 13 | * 2. XFree86 4.3.0, interpret_edid.c | ||
| 14 | * Copyright 1998 by Egbert Eich <Egbert.Eich@Physik.TU-Darmstadt.DE> | ||
| 15 | * | ||
| 16 | * 3. John Fremlin <vii@users.sourceforge.net> and | ||
| 17 | * Ani Joshi <ajoshi@unixbox.com> | ||
| 18 | * | ||
| 19 | * Generalized Timing Formula is derived from: | ||
| 20 | * | ||
| 21 | * GTF Spreadsheet by Andy Morrish (1/5/97) | ||
| 22 | * available at http://www.vesa.org | ||
| 23 | * | ||
| 24 | * This file is subject to the terms and conditions of the GNU General Public | ||
| 25 | * License. See the file COPYING in the main directory of this archive | ||
| 26 | * for more details. | ||
| 27 | * | ||
| 28 | */ | ||
| 29 | #include <linux/fb.h> | ||
| 30 | #include <linux/module.h> | ||
| 31 | #include <linux/pci.h> | ||
| 32 | #include <linux/slab.h> | ||
| 33 | #include <video/edid.h> | ||
| 34 | #include <video/of_videomode.h> | ||
| 35 | #include <video/videomode.h> | ||
| 36 | #ifdef CONFIG_PPC_OF | ||
| 37 | #include <asm/prom.h> | ||
| 38 | #include <asm/pci-bridge.h> | ||
| 39 | #endif | ||
| 40 | #include "../edid.h" | ||
| 41 | |||
| 42 | /* | ||
| 43 | * EDID parser | ||
| 44 | */ | ||
| 45 | |||
| 46 | #undef DEBUG /* define this for verbose EDID parsing output */ | ||
| 47 | |||
| 48 | #ifdef DEBUG | ||
| 49 | #define DPRINTK(fmt, args...) printk(fmt,## args) | ||
| 50 | #else | ||
| 51 | #define DPRINTK(fmt, args...) | ||
| 52 | #endif | ||
| 53 | |||
| 54 | #define FBMON_FIX_HEADER 1 | ||
| 55 | #define FBMON_FIX_INPUT 2 | ||
| 56 | #define FBMON_FIX_TIMINGS 3 | ||
| 57 | |||
| 58 | #ifdef CONFIG_FB_MODE_HELPERS | ||
| 59 | struct broken_edid { | ||
| 60 | u8 manufacturer[4]; | ||
| 61 | u32 model; | ||
| 62 | u32 fix; | ||
| 63 | }; | ||
| 64 | |||
| 65 | static const struct broken_edid brokendb[] = { | ||
| 66 | /* DEC FR-PCXAV-YZ */ | ||
| 67 | { | ||
| 68 | .manufacturer = "DEC", | ||
| 69 | .model = 0x073a, | ||
| 70 | .fix = FBMON_FIX_HEADER, | ||
| 71 | }, | ||
| 72 | /* ViewSonic PF775a */ | ||
| 73 | { | ||
| 74 | .manufacturer = "VSC", | ||
| 75 | .model = 0x5a44, | ||
| 76 | .fix = FBMON_FIX_INPUT, | ||
| 77 | }, | ||
| 78 | /* Sharp UXGA? */ | ||
| 79 | { | ||
| 80 | .manufacturer = "SHP", | ||
| 81 | .model = 0x138e, | ||
| 82 | .fix = FBMON_FIX_TIMINGS, | ||
| 83 | }, | ||
| 84 | }; | ||
| 85 | |||
| 86 | static const unsigned char edid_v1_header[] = { 0x00, 0xff, 0xff, 0xff, | ||
| 87 | 0xff, 0xff, 0xff, 0x00 | ||
| 88 | }; | ||
| 89 | |||
| 90 | static void copy_string(unsigned char *c, unsigned char *s) | ||
| 91 | { | ||
| 92 | int i; | ||
| 93 | c = c + 5; | ||
| 94 | for (i = 0; (i < 13 && *c != 0x0A); i++) | ||
| 95 | *(s++) = *(c++); | ||
| 96 | *s = 0; | ||
| 97 | while (i-- && (*--s == 0x20)) *s = 0; | ||
| 98 | } | ||
| 99 | |||
| 100 | static int edid_is_serial_block(unsigned char *block) | ||
| 101 | { | ||
| 102 | if ((block[0] == 0x00) && (block[1] == 0x00) && | ||
| 103 | (block[2] == 0x00) && (block[3] == 0xff) && | ||
| 104 | (block[4] == 0x00)) | ||
| 105 | return 1; | ||
| 106 | else | ||
| 107 | return 0; | ||
| 108 | } | ||
| 109 | |||
| 110 | static int edid_is_ascii_block(unsigned char *block) | ||
| 111 | { | ||
| 112 | if ((block[0] == 0x00) && (block[1] == 0x00) && | ||
| 113 | (block[2] == 0x00) && (block[3] == 0xfe) && | ||
| 114 | (block[4] == 0x00)) | ||
| 115 | return 1; | ||
| 116 | else | ||
| 117 | return 0; | ||
| 118 | } | ||
| 119 | |||
| 120 | static int edid_is_limits_block(unsigned char *block) | ||
| 121 | { | ||
| 122 | if ((block[0] == 0x00) && (block[1] == 0x00) && | ||
| 123 | (block[2] == 0x00) && (block[3] == 0xfd) && | ||
| 124 | (block[4] == 0x00)) | ||
| 125 | return 1; | ||
| 126 | else | ||
| 127 | return 0; | ||
| 128 | } | ||
| 129 | |||
| 130 | static int edid_is_monitor_block(unsigned char *block) | ||
| 131 | { | ||
| 132 | if ((block[0] == 0x00) && (block[1] == 0x00) && | ||
| 133 | (block[2] == 0x00) && (block[3] == 0xfc) && | ||
| 134 | (block[4] == 0x00)) | ||
| 135 | return 1; | ||
| 136 | else | ||
| 137 | return 0; | ||
| 138 | } | ||
| 139 | |||
| 140 | static int edid_is_timing_block(unsigned char *block) | ||
| 141 | { | ||
| 142 | if ((block[0] != 0x00) || (block[1] != 0x00) || | ||
| 143 | (block[2] != 0x00) || (block[4] != 0x00)) | ||
| 144 | return 1; | ||
| 145 | else | ||
| 146 | return 0; | ||
| 147 | } | ||
| 148 | |||
| 149 | static int check_edid(unsigned char *edid) | ||
| 150 | { | ||
| 151 | unsigned char *block = edid + ID_MANUFACTURER_NAME, manufacturer[4]; | ||
| 152 | unsigned char *b; | ||
| 153 | u32 model; | ||
| 154 | int i, fix = 0, ret = 0; | ||
| 155 | |||
| 156 | manufacturer[0] = ((block[0] & 0x7c) >> 2) + '@'; | ||
| 157 | manufacturer[1] = ((block[0] & 0x03) << 3) + | ||
| 158 | ((block[1] & 0xe0) >> 5) + '@'; | ||
| 159 | manufacturer[2] = (block[1] & 0x1f) + '@'; | ||
| 160 | manufacturer[3] = 0; | ||
| 161 | model = block[2] + (block[3] << 8); | ||
| 162 | |||
| 163 | for (i = 0; i < ARRAY_SIZE(brokendb); i++) { | ||
| 164 | if (!strncmp(manufacturer, brokendb[i].manufacturer, 4) && | ||
| 165 | brokendb[i].model == model) { | ||
| 166 | fix = brokendb[i].fix; | ||
| 167 | break; | ||
| 168 | } | ||
| 169 | } | ||
| 170 | |||
| 171 | switch (fix) { | ||
| 172 | case FBMON_FIX_HEADER: | ||
| 173 | for (i = 0; i < 8; i++) { | ||
| 174 | if (edid[i] != edid_v1_header[i]) { | ||
| 175 | ret = fix; | ||
| 176 | break; | ||
| 177 | } | ||
| 178 | } | ||
| 179 | break; | ||
| 180 | case FBMON_FIX_INPUT: | ||
| 181 | b = edid + EDID_STRUCT_DISPLAY; | ||
| 182 | /* Only if display is GTF capable will | ||
| 183 | the input type be reset to analog */ | ||
| 184 | if (b[4] & 0x01 && b[0] & 0x80) | ||
| 185 | ret = fix; | ||
| 186 | break; | ||
| 187 | case FBMON_FIX_TIMINGS: | ||
| 188 | b = edid + DETAILED_TIMING_DESCRIPTIONS_START; | ||
| 189 | ret = fix; | ||
| 190 | |||
| 191 | for (i = 0; i < 4; i++) { | ||
| 192 | if (edid_is_limits_block(b)) { | ||
| 193 | ret = 0; | ||
| 194 | break; | ||
| 195 | } | ||
| 196 | |||
| 197 | b += DETAILED_TIMING_DESCRIPTION_SIZE; | ||
| 198 | } | ||
| 199 | |||
| 200 | break; | ||
| 201 | } | ||
| 202 | |||
| 203 | if (ret) | ||
| 204 | printk("fbmon: The EDID Block of " | ||
| 205 | "Manufacturer: %s Model: 0x%x is known to " | ||
| 206 | "be broken,\n", manufacturer, model); | ||
| 207 | |||
| 208 | return ret; | ||
| 209 | } | ||
| 210 | |||
| 211 | static void fix_edid(unsigned char *edid, int fix) | ||
| 212 | { | ||
| 213 | int i; | ||
| 214 | unsigned char *b, csum = 0; | ||
| 215 | |||
| 216 | switch (fix) { | ||
| 217 | case FBMON_FIX_HEADER: | ||
| 218 | printk("fbmon: trying a header reconstruct\n"); | ||
| 219 | memcpy(edid, edid_v1_header, 8); | ||
| 220 | break; | ||
| 221 | case FBMON_FIX_INPUT: | ||
| 222 | printk("fbmon: trying to fix input type\n"); | ||
| 223 | b = edid + EDID_STRUCT_DISPLAY; | ||
| 224 | b[0] &= ~0x80; | ||
| 225 | edid[127] += 0x80; | ||
| 226 | break; | ||
| 227 | case FBMON_FIX_TIMINGS: | ||
| 228 | printk("fbmon: trying to fix monitor timings\n"); | ||
| 229 | b = edid + DETAILED_TIMING_DESCRIPTIONS_START; | ||
| 230 | for (i = 0; i < 4; i++) { | ||
| 231 | if (!(edid_is_serial_block(b) || | ||
| 232 | edid_is_ascii_block(b) || | ||
| 233 | edid_is_monitor_block(b) || | ||
| 234 | edid_is_timing_block(b))) { | ||
| 235 | b[0] = 0x00; | ||
| 236 | b[1] = 0x00; | ||
| 237 | b[2] = 0x00; | ||
| 238 | b[3] = 0xfd; | ||
| 239 | b[4] = 0x00; | ||
| 240 | b[5] = 60; /* vfmin */ | ||
| 241 | b[6] = 60; /* vfmax */ | ||
| 242 | b[7] = 30; /* hfmin */ | ||
| 243 | b[8] = 75; /* hfmax */ | ||
| 244 | b[9] = 17; /* pixclock - 170 MHz*/ | ||
| 245 | b[10] = 0; /* GTF */ | ||
| 246 | break; | ||
| 247 | } | ||
| 248 | |||
| 249 | b += DETAILED_TIMING_DESCRIPTION_SIZE; | ||
| 250 | } | ||
| 251 | |||
| 252 | for (i = 0; i < EDID_LENGTH - 1; i++) | ||
| 253 | csum += edid[i]; | ||
| 254 | |||
| 255 | edid[127] = 256 - csum; | ||
| 256 | break; | ||
| 257 | } | ||
| 258 | } | ||
| 259 | |||
| 260 | static int edid_checksum(unsigned char *edid) | ||
| 261 | { | ||
| 262 | unsigned char csum = 0, all_null = 0; | ||
| 263 | int i, err = 0, fix = check_edid(edid); | ||
| 264 | |||
| 265 | if (fix) | ||
| 266 | fix_edid(edid, fix); | ||
| 267 | |||
| 268 | for (i = 0; i < EDID_LENGTH; i++) { | ||
| 269 | csum += edid[i]; | ||
| 270 | all_null |= edid[i]; | ||
| 271 | } | ||
| 272 | |||
| 273 | if (csum == 0x00 && all_null) { | ||
| 274 | /* checksum passed, everything's good */ | ||
| 275 | err = 1; | ||
| 276 | } | ||
| 277 | |||
| 278 | return err; | ||
| 279 | } | ||
| 280 | |||
| 281 | static int edid_check_header(unsigned char *edid) | ||
| 282 | { | ||
| 283 | int i, err = 1, fix = check_edid(edid); | ||
| 284 | |||
| 285 | if (fix) | ||
| 286 | fix_edid(edid, fix); | ||
| 287 | |||
| 288 | for (i = 0; i < 8; i++) { | ||
| 289 | if (edid[i] != edid_v1_header[i]) | ||
| 290 | err = 0; | ||
| 291 | } | ||
| 292 | |||
| 293 | return err; | ||
| 294 | } | ||
| 295 | |||
| 296 | static void parse_vendor_block(unsigned char *block, struct fb_monspecs *specs) | ||
| 297 | { | ||
| 298 | specs->manufacturer[0] = ((block[0] & 0x7c) >> 2) + '@'; | ||
| 299 | specs->manufacturer[1] = ((block[0] & 0x03) << 3) + | ||
| 300 | ((block[1] & 0xe0) >> 5) + '@'; | ||
| 301 | specs->manufacturer[2] = (block[1] & 0x1f) + '@'; | ||
| 302 | specs->manufacturer[3] = 0; | ||
| 303 | specs->model = block[2] + (block[3] << 8); | ||
| 304 | specs->serial = block[4] + (block[5] << 8) + | ||
| 305 | (block[6] << 16) + (block[7] << 24); | ||
| 306 | specs->year = block[9] + 1990; | ||
| 307 | specs->week = block[8]; | ||
| 308 | DPRINTK(" Manufacturer: %s\n", specs->manufacturer); | ||
| 309 | DPRINTK(" Model: %x\n", specs->model); | ||
| 310 | DPRINTK(" Serial#: %u\n", specs->serial); | ||
| 311 | DPRINTK(" Year: %u Week %u\n", specs->year, specs->week); | ||
| 312 | } | ||
| 313 | |||
| 314 | static void get_dpms_capabilities(unsigned char flags, | ||
| 315 | struct fb_monspecs *specs) | ||
| 316 | { | ||
| 317 | specs->dpms = 0; | ||
| 318 | if (flags & DPMS_ACTIVE_OFF) | ||
| 319 | specs->dpms |= FB_DPMS_ACTIVE_OFF; | ||
| 320 | if (flags & DPMS_SUSPEND) | ||
| 321 | specs->dpms |= FB_DPMS_SUSPEND; | ||
| 322 | if (flags & DPMS_STANDBY) | ||
| 323 | specs->dpms |= FB_DPMS_STANDBY; | ||
| 324 | DPRINTK(" DPMS: Active %s, Suspend %s, Standby %s\n", | ||
| 325 | (flags & DPMS_ACTIVE_OFF) ? "yes" : "no", | ||
| 326 | (flags & DPMS_SUSPEND) ? "yes" : "no", | ||
| 327 | (flags & DPMS_STANDBY) ? "yes" : "no"); | ||
| 328 | } | ||
| 329 | |||
| 330 | static void get_chroma(unsigned char *block, struct fb_monspecs *specs) | ||
| 331 | { | ||
| 332 | int tmp; | ||
| 333 | |||
| 334 | DPRINTK(" Chroma\n"); | ||
| 335 | /* Chromaticity data */ | ||
| 336 | tmp = ((block[5] & (3 << 6)) >> 6) | (block[0x7] << 2); | ||
| 337 | tmp *= 1000; | ||
| 338 | tmp += 512; | ||
| 339 | specs->chroma.redx = tmp/1024; | ||
| 340 | DPRINTK(" RedX: 0.%03d ", specs->chroma.redx); | ||
| 341 | |||
| 342 | tmp = ((block[5] & (3 << 4)) >> 4) | (block[0x8] << 2); | ||
| 343 | tmp *= 1000; | ||
| 344 | tmp += 512; | ||
| 345 | specs->chroma.redy = tmp/1024; | ||
| 346 | DPRINTK("RedY: 0.%03d\n", specs->chroma.redy); | ||
| 347 | |||
| 348 | tmp = ((block[5] & (3 << 2)) >> 2) | (block[0x9] << 2); | ||
| 349 | tmp *= 1000; | ||
| 350 | tmp += 512; | ||
| 351 | specs->chroma.greenx = tmp/1024; | ||
| 352 | DPRINTK(" GreenX: 0.%03d ", specs->chroma.greenx); | ||
| 353 | |||
| 354 | tmp = (block[5] & 3) | (block[0xa] << 2); | ||
| 355 | tmp *= 1000; | ||
| 356 | tmp += 512; | ||
| 357 | specs->chroma.greeny = tmp/1024; | ||
| 358 | DPRINTK("GreenY: 0.%03d\n", specs->chroma.greeny); | ||
| 359 | |||
| 360 | tmp = ((block[6] & (3 << 6)) >> 6) | (block[0xb] << 2); | ||
| 361 | tmp *= 1000; | ||
| 362 | tmp += 512; | ||
| 363 | specs->chroma.bluex = tmp/1024; | ||
| 364 | DPRINTK(" BlueX: 0.%03d ", specs->chroma.bluex); | ||
| 365 | |||
| 366 | tmp = ((block[6] & (3 << 4)) >> 4) | (block[0xc] << 2); | ||
| 367 | tmp *= 1000; | ||
| 368 | tmp += 512; | ||
| 369 | specs->chroma.bluey = tmp/1024; | ||
| 370 | DPRINTK("BlueY: 0.%03d\n", specs->chroma.bluey); | ||
| 371 | |||
| 372 | tmp = ((block[6] & (3 << 2)) >> 2) | (block[0xd] << 2); | ||
| 373 | tmp *= 1000; | ||
| 374 | tmp += 512; | ||
| 375 | specs->chroma.whitex = tmp/1024; | ||
| 376 | DPRINTK(" WhiteX: 0.%03d ", specs->chroma.whitex); | ||
| 377 | |||
| 378 | tmp = (block[6] & 3) | (block[0xe] << 2); | ||
| 379 | tmp *= 1000; | ||
| 380 | tmp += 512; | ||
| 381 | specs->chroma.whitey = tmp/1024; | ||
| 382 | DPRINTK("WhiteY: 0.%03d\n", specs->chroma.whitey); | ||
| 383 | } | ||
| 384 | |||
| 385 | static void calc_mode_timings(int xres, int yres, int refresh, | ||
| 386 | struct fb_videomode *mode) | ||
| 387 | { | ||
| 388 | struct fb_var_screeninfo *var; | ||
| 389 | |||
| 390 | var = kzalloc(sizeof(struct fb_var_screeninfo), GFP_KERNEL); | ||
| 391 | |||
| 392 | if (var) { | ||
| 393 | var->xres = xres; | ||
| 394 | var->yres = yres; | ||
| 395 | fb_get_mode(FB_VSYNCTIMINGS | FB_IGNOREMON, | ||
| 396 | refresh, var, NULL); | ||
| 397 | mode->xres = xres; | ||
| 398 | mode->yres = yres; | ||
| 399 | mode->pixclock = var->pixclock; | ||
| 400 | mode->refresh = refresh; | ||
| 401 | mode->left_margin = var->left_margin; | ||
| 402 | mode->right_margin = var->right_margin; | ||
| 403 | mode->upper_margin = var->upper_margin; | ||
| 404 | mode->lower_margin = var->lower_margin; | ||
| 405 | mode->hsync_len = var->hsync_len; | ||
| 406 | mode->vsync_len = var->vsync_len; | ||
| 407 | mode->vmode = 0; | ||
| 408 | mode->sync = 0; | ||
| 409 | kfree(var); | ||
| 410 | } | ||
| 411 | } | ||
| 412 | |||
| 413 | static int get_est_timing(unsigned char *block, struct fb_videomode *mode) | ||
| 414 | { | ||
| 415 | int num = 0; | ||
| 416 | unsigned char c; | ||
| 417 | |||
| 418 | c = block[0]; | ||
| 419 | if (c&0x80) { | ||
| 420 | calc_mode_timings(720, 400, 70, &mode[num]); | ||
| 421 | mode[num++].flag = FB_MODE_IS_CALCULATED; | ||
| 422 | DPRINTK(" 720x400@70Hz\n"); | ||
| 423 | } | ||
| 424 | if (c&0x40) { | ||
| 425 | calc_mode_timings(720, 400, 88, &mode[num]); | ||
| 426 | mode[num++].flag = FB_MODE_IS_CALCULATED; | ||
| 427 | DPRINTK(" 720x400@88Hz\n"); | ||
| 428 | } | ||
| 429 | if (c&0x20) { | ||
| 430 | mode[num++] = vesa_modes[3]; | ||
| 431 | DPRINTK(" 640x480@60Hz\n"); | ||
| 432 | } | ||
| 433 | if (c&0x10) { | ||
| 434 | calc_mode_timings(640, 480, 67, &mode[num]); | ||
| 435 | mode[num++].flag = FB_MODE_IS_CALCULATED; | ||
| 436 | DPRINTK(" 640x480@67Hz\n"); | ||
| 437 | } | ||
| 438 | if (c&0x08) { | ||
| 439 | mode[num++] = vesa_modes[4]; | ||
| 440 | DPRINTK(" 640x480@72Hz\n"); | ||
| 441 | } | ||
| 442 | if (c&0x04) { | ||
| 443 | mode[num++] = vesa_modes[5]; | ||
| 444 | DPRINTK(" 640x480@75Hz\n"); | ||
| 445 | } | ||
| 446 | if (c&0x02) { | ||
| 447 | mode[num++] = vesa_modes[7]; | ||
| 448 | DPRINTK(" 800x600@56Hz\n"); | ||
| 449 | } | ||
| 450 | if (c&0x01) { | ||
| 451 | mode[num++] = vesa_modes[8]; | ||
| 452 | DPRINTK(" 800x600@60Hz\n"); | ||
| 453 | } | ||
| 454 | |||
| 455 | c = block[1]; | ||
| 456 | if (c&0x80) { | ||
| 457 | mode[num++] = vesa_modes[9]; | ||
| 458 | DPRINTK(" 800x600@72Hz\n"); | ||
| 459 | } | ||
| 460 | if (c&0x40) { | ||
| 461 | mode[num++] = vesa_modes[10]; | ||
| 462 | DPRINTK(" 800x600@75Hz\n"); | ||
| 463 | } | ||
| 464 | if (c&0x20) { | ||
| 465 | calc_mode_timings(832, 624, 75, &mode[num]); | ||
| 466 | mode[num++].flag = FB_MODE_IS_CALCULATED; | ||
| 467 | DPRINTK(" 832x624@75Hz\n"); | ||
| 468 | } | ||
| 469 | if (c&0x10) { | ||
| 470 | mode[num++] = vesa_modes[12]; | ||
| 471 | DPRINTK(" 1024x768@87Hz Interlaced\n"); | ||
| 472 | } | ||
| 473 | if (c&0x08) { | ||
| 474 | mode[num++] = vesa_modes[13]; | ||
| 475 | DPRINTK(" 1024x768@60Hz\n"); | ||
| 476 | } | ||
| 477 | if (c&0x04) { | ||
| 478 | mode[num++] = vesa_modes[14]; | ||
| 479 | DPRINTK(" 1024x768@70Hz\n"); | ||
| 480 | } | ||
| 481 | if (c&0x02) { | ||
| 482 | mode[num++] = vesa_modes[15]; | ||
| 483 | DPRINTK(" 1024x768@75Hz\n"); | ||
| 484 | } | ||
| 485 | if (c&0x01) { | ||
| 486 | mode[num++] = vesa_modes[21]; | ||
| 487 | DPRINTK(" 1280x1024@75Hz\n"); | ||
| 488 | } | ||
| 489 | c = block[2]; | ||
| 490 | if (c&0x80) { | ||
| 491 | mode[num++] = vesa_modes[17]; | ||
| 492 | DPRINTK(" 1152x870@75Hz\n"); | ||
| 493 | } | ||
| 494 | DPRINTK(" Manufacturer's mask: %x\n",c&0x7F); | ||
| 495 | return num; | ||
| 496 | } | ||
| 497 | |||
| 498 | static int get_std_timing(unsigned char *block, struct fb_videomode *mode, | ||
| 499 | int ver, int rev) | ||
| 500 | { | ||
| 501 | int xres, yres = 0, refresh, ratio, i; | ||
| 502 | |||
| 503 | xres = (block[0] + 31) * 8; | ||
| 504 | if (xres <= 256) | ||
| 505 | return 0; | ||
| 506 | |||
| 507 | ratio = (block[1] & 0xc0) >> 6; | ||
| 508 | switch (ratio) { | ||
| 509 | case 0: | ||
| 510 | /* in EDID 1.3 the meaning of 0 changed to 16:10 (prior 1:1) */ | ||
| 511 | if (ver < 1 || (ver == 1 && rev < 3)) | ||
| 512 | yres = xres; | ||
| 513 | else | ||
| 514 | yres = (xres * 10)/16; | ||
| 515 | break; | ||
| 516 | case 1: | ||
| 517 | yres = (xres * 3)/4; | ||
| 518 | break; | ||
| 519 | case 2: | ||
| 520 | yres = (xres * 4)/5; | ||
| 521 | break; | ||
| 522 | case 3: | ||
| 523 | yres = (xres * 9)/16; | ||
| 524 | break; | ||
| 525 | } | ||
| 526 | refresh = (block[1] & 0x3f) + 60; | ||
| 527 | |||
| 528 | DPRINTK(" %dx%d@%dHz\n", xres, yres, refresh); | ||
| 529 | for (i = 0; i < VESA_MODEDB_SIZE; i++) { | ||
| 530 | if (vesa_modes[i].xres == xres && | ||
| 531 | vesa_modes[i].yres == yres && | ||
| 532 | vesa_modes[i].refresh == refresh) { | ||
| 533 | *mode = vesa_modes[i]; | ||
| 534 | mode->flag |= FB_MODE_IS_STANDARD; | ||
| 535 | return 1; | ||
| 536 | } | ||
| 537 | } | ||
| 538 | calc_mode_timings(xres, yres, refresh, mode); | ||
| 539 | return 1; | ||
| 540 | } | ||
| 541 | |||
| 542 | static int get_dst_timing(unsigned char *block, | ||
| 543 | struct fb_videomode *mode, int ver, int rev) | ||
| 544 | { | ||
| 545 | int j, num = 0; | ||
| 546 | |||
| 547 | for (j = 0; j < 6; j++, block += STD_TIMING_DESCRIPTION_SIZE) | ||
| 548 | num += get_std_timing(block, &mode[num], ver, rev); | ||
| 549 | |||
| 550 | return num; | ||
| 551 | } | ||
| 552 | |||
| 553 | static void get_detailed_timing(unsigned char *block, | ||
| 554 | struct fb_videomode *mode) | ||
| 555 | { | ||
| 556 | mode->xres = H_ACTIVE; | ||
| 557 | mode->yres = V_ACTIVE; | ||
| 558 | mode->pixclock = PIXEL_CLOCK; | ||
| 559 | mode->pixclock /= 1000; | ||
| 560 | mode->pixclock = KHZ2PICOS(mode->pixclock); | ||
| 561 | mode->right_margin = H_SYNC_OFFSET; | ||
| 562 | mode->left_margin = (H_ACTIVE + H_BLANKING) - | ||
| 563 | (H_ACTIVE + H_SYNC_OFFSET + H_SYNC_WIDTH); | ||
| 564 | mode->upper_margin = V_BLANKING - V_SYNC_OFFSET - | ||
| 565 | V_SYNC_WIDTH; | ||
| 566 | mode->lower_margin = V_SYNC_OFFSET; | ||
| 567 | mode->hsync_len = H_SYNC_WIDTH; | ||
| 568 | mode->vsync_len = V_SYNC_WIDTH; | ||
| 569 | if (HSYNC_POSITIVE) | ||
| 570 | mode->sync |= FB_SYNC_HOR_HIGH_ACT; | ||
| 571 | if (VSYNC_POSITIVE) | ||
| 572 | mode->sync |= FB_SYNC_VERT_HIGH_ACT; | ||
| 573 | mode->refresh = PIXEL_CLOCK/((H_ACTIVE + H_BLANKING) * | ||
| 574 | (V_ACTIVE + V_BLANKING)); | ||
| 575 | if (INTERLACED) { | ||
| 576 | mode->yres *= 2; | ||
| 577 | mode->upper_margin *= 2; | ||
| 578 | mode->lower_margin *= 2; | ||
| 579 | mode->vsync_len *= 2; | ||
| 580 | mode->vmode |= FB_VMODE_INTERLACED; | ||
| 581 | } | ||
| 582 | mode->flag = FB_MODE_IS_DETAILED; | ||
| 583 | |||
| 584 | DPRINTK(" %d MHz ", PIXEL_CLOCK/1000000); | ||
| 585 | DPRINTK("%d %d %d %d ", H_ACTIVE, H_ACTIVE + H_SYNC_OFFSET, | ||
| 586 | H_ACTIVE + H_SYNC_OFFSET + H_SYNC_WIDTH, H_ACTIVE + H_BLANKING); | ||
| 587 | DPRINTK("%d %d %d %d ", V_ACTIVE, V_ACTIVE + V_SYNC_OFFSET, | ||
| 588 | V_ACTIVE + V_SYNC_OFFSET + V_SYNC_WIDTH, V_ACTIVE + V_BLANKING); | ||
| 589 | DPRINTK("%sHSync %sVSync\n\n", (HSYNC_POSITIVE) ? "+" : "-", | ||
| 590 | (VSYNC_POSITIVE) ? "+" : "-"); | ||
| 591 | } | ||
| 592 | |||
| 593 | /** | ||
| 594 | * fb_create_modedb - create video mode database | ||
| 595 | * @edid: EDID data | ||
| 596 | * @dbsize: database size | ||
| 597 | * | ||
| 598 | * RETURNS: struct fb_videomode, @dbsize contains length of database | ||
| 599 | * | ||
| 600 | * DESCRIPTION: | ||
| 601 | * This function builds a mode database using the contents of the EDID | ||
| 602 | * data | ||
| 603 | */ | ||
| 604 | static struct fb_videomode *fb_create_modedb(unsigned char *edid, int *dbsize) | ||
| 605 | { | ||
| 606 | struct fb_videomode *mode, *m; | ||
| 607 | unsigned char *block; | ||
| 608 | int num = 0, i, first = 1; | ||
| 609 | int ver, rev; | ||
| 610 | |||
| 611 | ver = edid[EDID_STRUCT_VERSION]; | ||
| 612 | rev = edid[EDID_STRUCT_REVISION]; | ||
| 613 | |||
| 614 | mode = kzalloc(50 * sizeof(struct fb_videomode), GFP_KERNEL); | ||
| 615 | if (mode == NULL) | ||
| 616 | return NULL; | ||
| 617 | |||
| 618 | if (edid == NULL || !edid_checksum(edid) || | ||
| 619 | !edid_check_header(edid)) { | ||
| 620 | kfree(mode); | ||
| 621 | return NULL; | ||
| 622 | } | ||
| 623 | |||
| 624 | *dbsize = 0; | ||
| 625 | |||
| 626 | DPRINTK(" Detailed Timings\n"); | ||
| 627 | block = edid + DETAILED_TIMING_DESCRIPTIONS_START; | ||
| 628 | for (i = 0; i < 4; i++, block+= DETAILED_TIMING_DESCRIPTION_SIZE) { | ||
| 629 | if (!(block[0] == 0x00 && block[1] == 0x00)) { | ||
| 630 | get_detailed_timing(block, &mode[num]); | ||
| 631 | if (first) { | ||
| 632 | mode[num].flag |= FB_MODE_IS_FIRST; | ||
| 633 | first = 0; | ||
| 634 | } | ||
| 635 | num++; | ||
| 636 | } | ||
| 637 | } | ||
| 638 | |||
| 639 | DPRINTK(" Supported VESA Modes\n"); | ||
| 640 | block = edid + ESTABLISHED_TIMING_1; | ||
| 641 | num += get_est_timing(block, &mode[num]); | ||
| 642 | |||
| 643 | DPRINTK(" Standard Timings\n"); | ||
| 644 | block = edid + STD_TIMING_DESCRIPTIONS_START; | ||
| 645 | for (i = 0; i < STD_TIMING; i++, block += STD_TIMING_DESCRIPTION_SIZE) | ||
| 646 | num += get_std_timing(block, &mode[num], ver, rev); | ||
| 647 | |||
| 648 | block = edid + DETAILED_TIMING_DESCRIPTIONS_START; | ||
| 649 | for (i = 0; i < 4; i++, block+= DETAILED_TIMING_DESCRIPTION_SIZE) { | ||
| 650 | if (block[0] == 0x00 && block[1] == 0x00 && block[3] == 0xfa) | ||
| 651 | num += get_dst_timing(block + 5, &mode[num], ver, rev); | ||
| 652 | } | ||
| 653 | |||
| 654 | /* Yikes, EDID data is totally useless */ | ||
| 655 | if (!num) { | ||
| 656 | kfree(mode); | ||
| 657 | return NULL; | ||
| 658 | } | ||
| 659 | |||
| 660 | *dbsize = num; | ||
| 661 | m = kmalloc(num * sizeof(struct fb_videomode), GFP_KERNEL); | ||
| 662 | if (!m) | ||
| 663 | return mode; | ||
| 664 | memmove(m, mode, num * sizeof(struct fb_videomode)); | ||
| 665 | kfree(mode); | ||
| 666 | return m; | ||
| 667 | } | ||
| 668 | |||
| 669 | /** | ||
| 670 | * fb_destroy_modedb - destroys mode database | ||
| 671 | * @modedb: mode database to destroy | ||
| 672 | * | ||
| 673 | * DESCRIPTION: | ||
| 674 | * Destroy mode database created by fb_create_modedb | ||
| 675 | */ | ||
| 676 | void fb_destroy_modedb(struct fb_videomode *modedb) | ||
| 677 | { | ||
| 678 | kfree(modedb); | ||
| 679 | } | ||
| 680 | |||
| 681 | static int fb_get_monitor_limits(unsigned char *edid, struct fb_monspecs *specs) | ||
| 682 | { | ||
| 683 | int i, retval = 1; | ||
| 684 | unsigned char *block; | ||
| 685 | |||
| 686 | block = edid + DETAILED_TIMING_DESCRIPTIONS_START; | ||
| 687 | |||
| 688 | DPRINTK(" Monitor Operating Limits: "); | ||
| 689 | |||
| 690 | for (i = 0; i < 4; i++, block += DETAILED_TIMING_DESCRIPTION_SIZE) { | ||
| 691 | if (edid_is_limits_block(block)) { | ||
| 692 | specs->hfmin = H_MIN_RATE * 1000; | ||
| 693 | specs->hfmax = H_MAX_RATE * 1000; | ||
| 694 | specs->vfmin = V_MIN_RATE; | ||
| 695 | specs->vfmax = V_MAX_RATE; | ||
| 696 | specs->dclkmax = MAX_PIXEL_CLOCK * 1000000; | ||
| 697 | specs->gtf = (GTF_SUPPORT) ? 1 : 0; | ||
| 698 | retval = 0; | ||
| 699 | DPRINTK("From EDID\n"); | ||
| 700 | break; | ||
| 701 | } | ||
| 702 | } | ||
| 703 | |||
| 704 | /* estimate monitor limits based on modes supported */ | ||
| 705 | if (retval) { | ||
| 706 | struct fb_videomode *modes, *mode; | ||
| 707 | int num_modes, hz, hscan, pixclock; | ||
| 708 | int vtotal, htotal; | ||
| 709 | |||
| 710 | modes = fb_create_modedb(edid, &num_modes); | ||
| 711 | if (!modes) { | ||
| 712 | DPRINTK("None Available\n"); | ||
| 713 | return 1; | ||
| 714 | } | ||
| 715 | |||
| 716 | retval = 0; | ||
| 717 | for (i = 0; i < num_modes; i++) { | ||
| 718 | mode = &modes[i]; | ||
| 719 | pixclock = PICOS2KHZ(modes[i].pixclock) * 1000; | ||
| 720 | htotal = mode->xres + mode->right_margin + mode->hsync_len | ||
| 721 | + mode->left_margin; | ||
| 722 | vtotal = mode->yres + mode->lower_margin + mode->vsync_len | ||
| 723 | + mode->upper_margin; | ||
| 724 | |||
| 725 | if (mode->vmode & FB_VMODE_INTERLACED) | ||
| 726 | vtotal /= 2; | ||
| 727 | |||
| 728 | if (mode->vmode & FB_VMODE_DOUBLE) | ||
| 729 | vtotal *= 2; | ||
| 730 | |||
| 731 | hscan = (pixclock + htotal / 2) / htotal; | ||
| 732 | hscan = (hscan + 500) / 1000 * 1000; | ||
| 733 | hz = (hscan + vtotal / 2) / vtotal; | ||
| 734 | |||
| 735 | if (specs->dclkmax == 0 || specs->dclkmax < pixclock) | ||
| 736 | specs->dclkmax = pixclock; | ||
| 737 | |||
| 738 | if (specs->dclkmin == 0 || specs->dclkmin > pixclock) | ||
| 739 | specs->dclkmin = pixclock; | ||
| 740 | |||
| 741 | if (specs->hfmax == 0 || specs->hfmax < hscan) | ||
| 742 | specs->hfmax = hscan; | ||
| 743 | |||
| 744 | if (specs->hfmin == 0 || specs->hfmin > hscan) | ||
| 745 | specs->hfmin = hscan; | ||
| 746 | |||
| 747 | if (specs->vfmax == 0 || specs->vfmax < hz) | ||
| 748 | specs->vfmax = hz; | ||
| 749 | |||
| 750 | if (specs->vfmin == 0 || specs->vfmin > hz) | ||
| 751 | specs->vfmin = hz; | ||
| 752 | } | ||
| 753 | DPRINTK("Extrapolated\n"); | ||
| 754 | fb_destroy_modedb(modes); | ||
| 755 | } | ||
| 756 | DPRINTK(" H: %d-%dKHz V: %d-%dHz DCLK: %dMHz\n", | ||
| 757 | specs->hfmin/1000, specs->hfmax/1000, specs->vfmin, | ||
| 758 | specs->vfmax, specs->dclkmax/1000000); | ||
| 759 | return retval; | ||
| 760 | } | ||
| 761 | |||
| 762 | static void get_monspecs(unsigned char *edid, struct fb_monspecs *specs) | ||
| 763 | { | ||
| 764 | unsigned char c, *block; | ||
| 765 | |||
| 766 | block = edid + EDID_STRUCT_DISPLAY; | ||
| 767 | |||
| 768 | fb_get_monitor_limits(edid, specs); | ||
| 769 | |||
| 770 | c = block[0] & 0x80; | ||
| 771 | specs->input = 0; | ||
| 772 | if (c) { | ||
| 773 | specs->input |= FB_DISP_DDI; | ||
| 774 | DPRINTK(" Digital Display Input"); | ||
| 775 | } else { | ||
| 776 | DPRINTK(" Analog Display Input: Input Voltage - "); | ||
| 777 | switch ((block[0] & 0x60) >> 5) { | ||
| 778 | case 0: | ||
| 779 | DPRINTK("0.700V/0.300V"); | ||
| 780 | specs->input |= FB_DISP_ANA_700_300; | ||
| 781 | break; | ||
| 782 | case 1: | ||
| 783 | DPRINTK("0.714V/0.286V"); | ||
| 784 | specs->input |= FB_DISP_ANA_714_286; | ||
| 785 | break; | ||
| 786 | case 2: | ||
| 787 | DPRINTK("1.000V/0.400V"); | ||
| 788 | specs->input |= FB_DISP_ANA_1000_400; | ||
| 789 | break; | ||
| 790 | case 3: | ||
| 791 | DPRINTK("0.700V/0.000V"); | ||
| 792 | specs->input |= FB_DISP_ANA_700_000; | ||
| 793 | break; | ||
| 794 | } | ||
| 795 | } | ||
| 796 | DPRINTK("\n Sync: "); | ||
| 797 | c = block[0] & 0x10; | ||
| 798 | if (c) | ||
| 799 | DPRINTK(" Configurable signal level\n"); | ||
| 800 | c = block[0] & 0x0f; | ||
| 801 | specs->signal = 0; | ||
| 802 | if (c & 0x10) { | ||
| 803 | DPRINTK("Blank to Blank "); | ||
| 804 | specs->signal |= FB_SIGNAL_BLANK_BLANK; | ||
| 805 | } | ||
| 806 | if (c & 0x08) { | ||
| 807 | DPRINTK("Separate "); | ||
| 808 | specs->signal |= FB_SIGNAL_SEPARATE; | ||
| 809 | } | ||
| 810 | if (c & 0x04) { | ||
| 811 | DPRINTK("Composite "); | ||
| 812 | specs->signal |= FB_SIGNAL_COMPOSITE; | ||
| 813 | } | ||
| 814 | if (c & 0x02) { | ||
| 815 | DPRINTK("Sync on Green "); | ||
| 816 | specs->signal |= FB_SIGNAL_SYNC_ON_GREEN; | ||
| 817 | } | ||
| 818 | if (c & 0x01) { | ||
| 819 | DPRINTK("Serration on "); | ||
| 820 | specs->signal |= FB_SIGNAL_SERRATION_ON; | ||
| 821 | } | ||
| 822 | DPRINTK("\n"); | ||
| 823 | specs->max_x = block[1]; | ||
| 824 | specs->max_y = block[2]; | ||
| 825 | DPRINTK(" Max H-size in cm: "); | ||
| 826 | if (specs->max_x) | ||
| 827 | DPRINTK("%d\n", specs->max_x); | ||
| 828 | else | ||
| 829 | DPRINTK("variable\n"); | ||
| 830 | DPRINTK(" Max V-size in cm: "); | ||
| 831 | if (specs->max_y) | ||
| 832 | DPRINTK("%d\n", specs->max_y); | ||
| 833 | else | ||
| 834 | DPRINTK("variable\n"); | ||
| 835 | |||
| 836 | c = block[3]; | ||
| 837 | specs->gamma = c+100; | ||
| 838 | DPRINTK(" Gamma: "); | ||
| 839 | DPRINTK("%d.%d\n", specs->gamma/100, specs->gamma % 100); | ||
| 840 | |||
| 841 | get_dpms_capabilities(block[4], specs); | ||
| 842 | |||
| 843 | switch ((block[4] & 0x18) >> 3) { | ||
| 844 | case 0: | ||
| 845 | DPRINTK(" Monochrome/Grayscale\n"); | ||
| 846 | specs->input |= FB_DISP_MONO; | ||
| 847 | break; | ||
| 848 | case 1: | ||
| 849 | DPRINTK(" RGB Color Display\n"); | ||
| 850 | specs->input |= FB_DISP_RGB; | ||
| 851 | break; | ||
| 852 | case 2: | ||
| 853 | DPRINTK(" Non-RGB Multicolor Display\n"); | ||
| 854 | specs->input |= FB_DISP_MULTI; | ||
| 855 | break; | ||
| 856 | default: | ||
| 857 | DPRINTK(" Unknown\n"); | ||
| 858 | specs->input |= FB_DISP_UNKNOWN; | ||
| 859 | break; | ||
| 860 | } | ||
| 861 | |||
| 862 | get_chroma(block, specs); | ||
| 863 | |||
| 864 | specs->misc = 0; | ||
| 865 | c = block[4] & 0x7; | ||
| 866 | if (c & 0x04) { | ||
| 867 | DPRINTK(" Default color format is primary\n"); | ||
| 868 | specs->misc |= FB_MISC_PRIM_COLOR; | ||
| 869 | } | ||
| 870 | if (c & 0x02) { | ||
| 871 | DPRINTK(" First DETAILED Timing is preferred\n"); | ||
| 872 | specs->misc |= FB_MISC_1ST_DETAIL; | ||
| 873 | } | ||
| 874 | if (c & 0x01) { | ||
| 875 | printk(" Display is GTF capable\n"); | ||
| 876 | specs->gtf = 1; | ||
| 877 | } | ||
| 878 | } | ||
| 879 | |||
| 880 | int fb_parse_edid(unsigned char *edid, struct fb_var_screeninfo *var) | ||
| 881 | { | ||
| 882 | int i; | ||
| 883 | unsigned char *block; | ||
| 884 | |||
| 885 | if (edid == NULL || var == NULL) | ||
| 886 | return 1; | ||
| 887 | |||
| 888 | if (!(edid_checksum(edid))) | ||
| 889 | return 1; | ||
| 890 | |||
| 891 | if (!(edid_check_header(edid))) | ||
| 892 | return 1; | ||
| 893 | |||
| 894 | block = edid + DETAILED_TIMING_DESCRIPTIONS_START; | ||
| 895 | |||
| 896 | for (i = 0; i < 4; i++, block += DETAILED_TIMING_DESCRIPTION_SIZE) { | ||
| 897 | if (edid_is_timing_block(block)) { | ||
| 898 | var->xres = var->xres_virtual = H_ACTIVE; | ||
| 899 | var->yres = var->yres_virtual = V_ACTIVE; | ||
| 900 | var->height = var->width = 0; | ||
| 901 | var->right_margin = H_SYNC_OFFSET; | ||
| 902 | var->left_margin = (H_ACTIVE + H_BLANKING) - | ||
| 903 | (H_ACTIVE + H_SYNC_OFFSET + H_SYNC_WIDTH); | ||
| 904 | var->upper_margin = V_BLANKING - V_SYNC_OFFSET - | ||
| 905 | V_SYNC_WIDTH; | ||
| 906 | var->lower_margin = V_SYNC_OFFSET; | ||
| 907 | var->hsync_len = H_SYNC_WIDTH; | ||
| 908 | var->vsync_len = V_SYNC_WIDTH; | ||
| 909 | var->pixclock = PIXEL_CLOCK; | ||
| 910 | var->pixclock /= 1000; | ||
| 911 | var->pixclock = KHZ2PICOS(var->pixclock); | ||
| 912 | |||
| 913 | if (HSYNC_POSITIVE) | ||
| 914 | var->sync |= FB_SYNC_HOR_HIGH_ACT; | ||
| 915 | if (VSYNC_POSITIVE) | ||
| 916 | var->sync |= FB_SYNC_VERT_HIGH_ACT; | ||
| 917 | return 0; | ||
| 918 | } | ||
| 919 | } | ||
| 920 | return 1; | ||
| 921 | } | ||
| 922 | |||
| 923 | void fb_edid_to_monspecs(unsigned char *edid, struct fb_monspecs *specs) | ||
| 924 | { | ||
| 925 | unsigned char *block; | ||
| 926 | int i, found = 0; | ||
| 927 | |||
| 928 | if (edid == NULL) | ||
| 929 | return; | ||
| 930 | |||
| 931 | if (!(edid_checksum(edid))) | ||
| 932 | return; | ||
| 933 | |||
| 934 | if (!(edid_check_header(edid))) | ||
| 935 | return; | ||
| 936 | |||
| 937 | memset(specs, 0, sizeof(struct fb_monspecs)); | ||
| 938 | |||
| 939 | specs->version = edid[EDID_STRUCT_VERSION]; | ||
| 940 | specs->revision = edid[EDID_STRUCT_REVISION]; | ||
| 941 | |||
| 942 | DPRINTK("========================================\n"); | ||
| 943 | DPRINTK("Display Information (EDID)\n"); | ||
| 944 | DPRINTK("========================================\n"); | ||
| 945 | DPRINTK(" EDID Version %d.%d\n", (int) specs->version, | ||
| 946 | (int) specs->revision); | ||
| 947 | |||
| 948 | parse_vendor_block(edid + ID_MANUFACTURER_NAME, specs); | ||
| 949 | |||
| 950 | block = edid + DETAILED_TIMING_DESCRIPTIONS_START; | ||
| 951 | for (i = 0; i < 4; i++, block += DETAILED_TIMING_DESCRIPTION_SIZE) { | ||
| 952 | if (edid_is_serial_block(block)) { | ||
| 953 | copy_string(block, specs->serial_no); | ||
| 954 | DPRINTK(" Serial Number: %s\n", specs->serial_no); | ||
| 955 | } else if (edid_is_ascii_block(block)) { | ||
| 956 | copy_string(block, specs->ascii); | ||
| 957 | DPRINTK(" ASCII Block: %s\n", specs->ascii); | ||
| 958 | } else if (edid_is_monitor_block(block)) { | ||
| 959 | copy_string(block, specs->monitor); | ||
| 960 | DPRINTK(" Monitor Name: %s\n", specs->monitor); | ||
| 961 | } | ||
| 962 | } | ||
| 963 | |||
| 964 | DPRINTK(" Display Characteristics:\n"); | ||
| 965 | get_monspecs(edid, specs); | ||
| 966 | |||
| 967 | specs->modedb = fb_create_modedb(edid, &specs->modedb_len); | ||
| 968 | |||
| 969 | /* | ||
| 970 | * Workaround for buggy EDIDs that sets that the first | ||
| 971 | * detailed timing is preferred but has not detailed | ||
| 972 | * timing specified | ||
| 973 | */ | ||
| 974 | for (i = 0; i < specs->modedb_len; i++) { | ||
| 975 | if (specs->modedb[i].flag & FB_MODE_IS_DETAILED) { | ||
| 976 | found = 1; | ||
| 977 | break; | ||
| 978 | } | ||
| 979 | } | ||
| 980 | |||
| 981 | if (!found) | ||
| 982 | specs->misc &= ~FB_MISC_1ST_DETAIL; | ||
| 983 | |||
| 984 | DPRINTK("========================================\n"); | ||
| 985 | } | ||
| 986 | |||
| 987 | /** | ||
| 988 | * fb_edid_add_monspecs() - add monitor video modes from E-EDID data | ||
| 989 | * @edid: 128 byte array with an E-EDID block | ||
| 990 | * @spacs: monitor specs to be extended | ||
| 991 | */ | ||
| 992 | void fb_edid_add_monspecs(unsigned char *edid, struct fb_monspecs *specs) | ||
| 993 | { | ||
| 994 | unsigned char *block; | ||
| 995 | struct fb_videomode *m; | ||
| 996 | int num = 0, i; | ||
| 997 | u8 svd[64], edt[(128 - 4) / DETAILED_TIMING_DESCRIPTION_SIZE]; | ||
| 998 | u8 pos = 4, svd_n = 0; | ||
| 999 | |||
| 1000 | if (!edid) | ||
| 1001 | return; | ||
| 1002 | |||
| 1003 | if (!edid_checksum(edid)) | ||
| 1004 | return; | ||
| 1005 | |||
| 1006 | if (edid[0] != 0x2 || | ||
| 1007 | edid[2] < 4 || edid[2] > 128 - DETAILED_TIMING_DESCRIPTION_SIZE) | ||
| 1008 | return; | ||
| 1009 | |||
| 1010 | DPRINTK(" Short Video Descriptors\n"); | ||
| 1011 | |||
| 1012 | while (pos < edid[2]) { | ||
| 1013 | u8 len = edid[pos] & 0x1f, type = (edid[pos] >> 5) & 7; | ||
| 1014 | pr_debug("Data block %u of %u bytes\n", type, len); | ||
| 1015 | if (type == 2) | ||
| 1016 | for (i = pos; i < pos + len; i++) { | ||
| 1017 | u8 idx = edid[pos + i] & 0x7f; | ||
| 1018 | svd[svd_n++] = idx; | ||
| 1019 | pr_debug("N%sative mode #%d\n", | ||
| 1020 | edid[pos + i] & 0x80 ? "" : "on-n", idx); | ||
| 1021 | } | ||
| 1022 | pos += len + 1; | ||
| 1023 | } | ||
| 1024 | |||
| 1025 | block = edid + edid[2]; | ||
| 1026 | |||
| 1027 | DPRINTK(" Extended Detailed Timings\n"); | ||
| 1028 | |||
| 1029 | for (i = 0; i < (128 - edid[2]) / DETAILED_TIMING_DESCRIPTION_SIZE; | ||
| 1030 | i++, block += DETAILED_TIMING_DESCRIPTION_SIZE) | ||
| 1031 | if (PIXEL_CLOCK) | ||
| 1032 | edt[num++] = block - edid; | ||
| 1033 | |||
| 1034 | /* Yikes, EDID data is totally useless */ | ||
| 1035 | if (!(num + svd_n)) | ||
| 1036 | return; | ||
| 1037 | |||
| 1038 | m = kzalloc((specs->modedb_len + num + svd_n) * | ||
| 1039 | sizeof(struct fb_videomode), GFP_KERNEL); | ||
| 1040 | |||
| 1041 | if (!m) | ||
| 1042 | return; | ||
| 1043 | |||
| 1044 | memcpy(m, specs->modedb, specs->modedb_len * sizeof(struct fb_videomode)); | ||
| 1045 | |||
| 1046 | for (i = specs->modedb_len; i < specs->modedb_len + num; i++) { | ||
| 1047 | get_detailed_timing(edid + edt[i - specs->modedb_len], &m[i]); | ||
| 1048 | if (i == specs->modedb_len) | ||
| 1049 | m[i].flag |= FB_MODE_IS_FIRST; | ||
| 1050 | pr_debug("Adding %ux%u@%u\n", m[i].xres, m[i].yres, m[i].refresh); | ||
| 1051 | } | ||
| 1052 | |||
| 1053 | for (i = specs->modedb_len + num; i < specs->modedb_len + num + svd_n; i++) { | ||
| 1054 | int idx = svd[i - specs->modedb_len - num]; | ||
| 1055 | if (!idx || idx > 63) { | ||
| 1056 | pr_warning("Reserved SVD code %d\n", idx); | ||
| 1057 | } else if (idx > ARRAY_SIZE(cea_modes) || !cea_modes[idx].xres) { | ||
| 1058 | pr_warning("Unimplemented SVD code %d\n", idx); | ||
| 1059 | } else { | ||
| 1060 | memcpy(&m[i], cea_modes + idx, sizeof(m[i])); | ||
| 1061 | pr_debug("Adding SVD #%d: %ux%u@%u\n", idx, | ||
| 1062 | m[i].xres, m[i].yres, m[i].refresh); | ||
| 1063 | } | ||
| 1064 | } | ||
| 1065 | |||
| 1066 | kfree(specs->modedb); | ||
| 1067 | specs->modedb = m; | ||
| 1068 | specs->modedb_len = specs->modedb_len + num + svd_n; | ||
| 1069 | } | ||
| 1070 | |||
| 1071 | /* | ||
| 1072 | * VESA Generalized Timing Formula (GTF) | ||
| 1073 | */ | ||
| 1074 | |||
| 1075 | #define FLYBACK 550 | ||
| 1076 | #define V_FRONTPORCH 1 | ||
| 1077 | #define H_OFFSET 40 | ||
| 1078 | #define H_SCALEFACTOR 20 | ||
| 1079 | #define H_BLANKSCALE 128 | ||
| 1080 | #define H_GRADIENT 600 | ||
| 1081 | #define C_VAL 30 | ||
| 1082 | #define M_VAL 300 | ||
| 1083 | |||
| 1084 | struct __fb_timings { | ||
| 1085 | u32 dclk; | ||
| 1086 | u32 hfreq; | ||
| 1087 | u32 vfreq; | ||
| 1088 | u32 hactive; | ||
| 1089 | u32 vactive; | ||
| 1090 | u32 hblank; | ||
| 1091 | u32 vblank; | ||
| 1092 | u32 htotal; | ||
| 1093 | u32 vtotal; | ||
| 1094 | }; | ||
| 1095 | |||
| 1096 | /** | ||
| 1097 | * fb_get_vblank - get vertical blank time | ||
| 1098 | * @hfreq: horizontal freq | ||
| 1099 | * | ||
| 1100 | * DESCRIPTION: | ||
| 1101 | * vblank = right_margin + vsync_len + left_margin | ||
| 1102 | * | ||
| 1103 | * given: right_margin = 1 (V_FRONTPORCH) | ||
| 1104 | * vsync_len = 3 | ||
| 1105 | * flyback = 550 | ||
| 1106 | * | ||
| 1107 | * flyback * hfreq | ||
| 1108 | * left_margin = --------------- - vsync_len | ||
| 1109 | * 1000000 | ||
| 1110 | */ | ||
| 1111 | static u32 fb_get_vblank(u32 hfreq) | ||
| 1112 | { | ||
| 1113 | u32 vblank; | ||
| 1114 | |||
| 1115 | vblank = (hfreq * FLYBACK)/1000; | ||
| 1116 | vblank = (vblank + 500)/1000; | ||
| 1117 | return (vblank + V_FRONTPORCH); | ||
| 1118 | } | ||
| 1119 | |||
| 1120 | /** | ||
| 1121 | * fb_get_hblank_by_freq - get horizontal blank time given hfreq | ||
| 1122 | * @hfreq: horizontal freq | ||
| 1123 | * @xres: horizontal resolution in pixels | ||
| 1124 | * | ||
| 1125 | * DESCRIPTION: | ||
| 1126 | * | ||
| 1127 | * xres * duty_cycle | ||
| 1128 | * hblank = ------------------ | ||
| 1129 | * 100 - duty_cycle | ||
| 1130 | * | ||
| 1131 | * duty cycle = percent of htotal assigned to inactive display | ||
| 1132 | * duty cycle = C - (M/Hfreq) | ||
| 1133 | * | ||
| 1134 | * where: C = ((offset - scale factor) * blank_scale) | ||
| 1135 | * -------------------------------------- + scale factor | ||
| 1136 | * 256 | ||
| 1137 | * M = blank_scale * gradient | ||
| 1138 | * | ||
| 1139 | */ | ||
| 1140 | static u32 fb_get_hblank_by_hfreq(u32 hfreq, u32 xres) | ||
| 1141 | { | ||
| 1142 | u32 c_val, m_val, duty_cycle, hblank; | ||
| 1143 | |||
| 1144 | c_val = (((H_OFFSET - H_SCALEFACTOR) * H_BLANKSCALE)/256 + | ||
| 1145 | H_SCALEFACTOR) * 1000; | ||
| 1146 | m_val = (H_BLANKSCALE * H_GRADIENT)/256; | ||
| 1147 | m_val = (m_val * 1000000)/hfreq; | ||
| 1148 | duty_cycle = c_val - m_val; | ||
| 1149 | hblank = (xres * duty_cycle)/(100000 - duty_cycle); | ||
| 1150 | return (hblank); | ||
| 1151 | } | ||
| 1152 | |||
| 1153 | /** | ||
| 1154 | * fb_get_hblank_by_dclk - get horizontal blank time given pixelclock | ||
| 1155 | * @dclk: pixelclock in Hz | ||
| 1156 | * @xres: horizontal resolution in pixels | ||
| 1157 | * | ||
| 1158 | * DESCRIPTION: | ||
| 1159 | * | ||
| 1160 | * xres * duty_cycle | ||
| 1161 | * hblank = ------------------ | ||
| 1162 | * 100 - duty_cycle | ||
| 1163 | * | ||
| 1164 | * duty cycle = percent of htotal assigned to inactive display | ||
| 1165 | * duty cycle = C - (M * h_period) | ||
| 1166 | * | ||
| 1167 | * where: h_period = SQRT(100 - C + (0.4 * xres * M)/dclk) + C - 100 | ||
| 1168 | * ----------------------------------------------- | ||
| 1169 | * 2 * M | ||
| 1170 | * M = 300; | ||
| 1171 | * C = 30; | ||
| 1172 | |||
| 1173 | */ | ||
| 1174 | static u32 fb_get_hblank_by_dclk(u32 dclk, u32 xres) | ||
| 1175 | { | ||
| 1176 | u32 duty_cycle, h_period, hblank; | ||
| 1177 | |||
| 1178 | dclk /= 1000; | ||
| 1179 | h_period = 100 - C_VAL; | ||
| 1180 | h_period *= h_period; | ||
| 1181 | h_period += (M_VAL * xres * 2 * 1000)/(5 * dclk); | ||
| 1182 | h_period *= 10000; | ||
| 1183 | |||
| 1184 | h_period = int_sqrt(h_period); | ||
| 1185 | h_period -= (100 - C_VAL) * 100; | ||
| 1186 | h_period *= 1000; | ||
| 1187 | h_period /= 2 * M_VAL; | ||
| 1188 | |||
| 1189 | duty_cycle = C_VAL * 1000 - (M_VAL * h_period)/100; | ||
| 1190 | hblank = (xres * duty_cycle)/(100000 - duty_cycle) + 8; | ||
| 1191 | hblank &= ~15; | ||
| 1192 | return (hblank); | ||
| 1193 | } | ||
| 1194 | |||
| 1195 | /** | ||
| 1196 | * fb_get_hfreq - estimate hsync | ||
| 1197 | * @vfreq: vertical refresh rate | ||
| 1198 | * @yres: vertical resolution | ||
| 1199 | * | ||
| 1200 | * DESCRIPTION: | ||
| 1201 | * | ||
| 1202 | * (yres + front_port) * vfreq * 1000000 | ||
| 1203 | * hfreq = ------------------------------------- | ||
| 1204 | * (1000000 - (vfreq * FLYBACK) | ||
| 1205 | * | ||
| 1206 | */ | ||
| 1207 | |||
| 1208 | static u32 fb_get_hfreq(u32 vfreq, u32 yres) | ||
| 1209 | { | ||
| 1210 | u32 divisor, hfreq; | ||
| 1211 | |||
| 1212 | divisor = (1000000 - (vfreq * FLYBACK))/1000; | ||
| 1213 | hfreq = (yres + V_FRONTPORCH) * vfreq * 1000; | ||
| 1214 | return (hfreq/divisor); | ||
| 1215 | } | ||
| 1216 | |||
| 1217 | static void fb_timings_vfreq(struct __fb_timings *timings) | ||
| 1218 | { | ||
| 1219 | timings->hfreq = fb_get_hfreq(timings->vfreq, timings->vactive); | ||
| 1220 | timings->vblank = fb_get_vblank(timings->hfreq); | ||
| 1221 | timings->vtotal = timings->vactive + timings->vblank; | ||
| 1222 | timings->hblank = fb_get_hblank_by_hfreq(timings->hfreq, | ||
| 1223 | timings->hactive); | ||
| 1224 | timings->htotal = timings->hactive + timings->hblank; | ||
| 1225 | timings->dclk = timings->htotal * timings->hfreq; | ||
| 1226 | } | ||
| 1227 | |||
| 1228 | static void fb_timings_hfreq(struct __fb_timings *timings) | ||
| 1229 | { | ||
| 1230 | timings->vblank = fb_get_vblank(timings->hfreq); | ||
| 1231 | timings->vtotal = timings->vactive + timings->vblank; | ||
| 1232 | timings->vfreq = timings->hfreq/timings->vtotal; | ||
| 1233 | timings->hblank = fb_get_hblank_by_hfreq(timings->hfreq, | ||
| 1234 | timings->hactive); | ||
| 1235 | timings->htotal = timings->hactive + timings->hblank; | ||
| 1236 | timings->dclk = timings->htotal * timings->hfreq; | ||
| 1237 | } | ||
| 1238 | |||
| 1239 | static void fb_timings_dclk(struct __fb_timings *timings) | ||
| 1240 | { | ||
| 1241 | timings->hblank = fb_get_hblank_by_dclk(timings->dclk, | ||
| 1242 | timings->hactive); | ||
| 1243 | timings->htotal = timings->hactive + timings->hblank; | ||
| 1244 | timings->hfreq = timings->dclk/timings->htotal; | ||
| 1245 | timings->vblank = fb_get_vblank(timings->hfreq); | ||
| 1246 | timings->vtotal = timings->vactive + timings->vblank; | ||
| 1247 | timings->vfreq = timings->hfreq/timings->vtotal; | ||
| 1248 | } | ||
| 1249 | |||
| 1250 | /* | ||
| 1251 | * fb_get_mode - calculates video mode using VESA GTF | ||
| 1252 | * @flags: if: 0 - maximize vertical refresh rate | ||
| 1253 | * 1 - vrefresh-driven calculation; | ||
| 1254 | * 2 - hscan-driven calculation; | ||
| 1255 | * 3 - pixelclock-driven calculation; | ||
| 1256 | * @val: depending on @flags, ignored, vrefresh, hsync or pixelclock | ||
| 1257 | * @var: pointer to fb_var_screeninfo | ||
| 1258 | * @info: pointer to fb_info | ||
| 1259 | * | ||
| 1260 | * DESCRIPTION: | ||
| 1261 | * Calculates video mode based on monitor specs using VESA GTF. | ||
| 1262 | * The GTF is best for VESA GTF compliant monitors but is | ||
| 1263 | * specifically formulated to work for older monitors as well. | ||
| 1264 | * | ||
| 1265 | * If @flag==0, the function will attempt to maximize the | ||
| 1266 | * refresh rate. Otherwise, it will calculate timings based on | ||
| 1267 | * the flag and accompanying value. | ||
| 1268 | * | ||
| 1269 | * If FB_IGNOREMON bit is set in @flags, monitor specs will be | ||
| 1270 | * ignored and @var will be filled with the calculated timings. | ||
| 1271 | * | ||
| 1272 | * All calculations are based on the VESA GTF Spreadsheet | ||
| 1273 | * available at VESA's public ftp (http://www.vesa.org). | ||
| 1274 | * | ||
| 1275 | * NOTES: | ||
| 1276 | * The timings generated by the GTF will be different from VESA | ||
| 1277 | * DMT. It might be a good idea to keep a table of standard | ||
| 1278 | * VESA modes as well. The GTF may also not work for some displays, | ||
| 1279 | * such as, and especially, analog TV. | ||
| 1280 | * | ||
| 1281 | * REQUIRES: | ||
| 1282 | * A valid info->monspecs, otherwise 'safe numbers' will be used. | ||
| 1283 | */ | ||
| 1284 | int fb_get_mode(int flags, u32 val, struct fb_var_screeninfo *var, struct fb_info *info) | ||
| 1285 | { | ||
| 1286 | struct __fb_timings *timings; | ||
| 1287 | u32 interlace = 1, dscan = 1; | ||
| 1288 | u32 hfmin, hfmax, vfmin, vfmax, dclkmin, dclkmax, err = 0; | ||
| 1289 | |||
| 1290 | |||
| 1291 | timings = kzalloc(sizeof(struct __fb_timings), GFP_KERNEL); | ||
| 1292 | |||
| 1293 | if (!timings) | ||
| 1294 | return -ENOMEM; | ||
| 1295 | |||
| 1296 | /* | ||
| 1297 | * If monspecs are invalid, use values that are enough | ||
| 1298 | * for 640x480@60 | ||
| 1299 | */ | ||
| 1300 | if (!info || !info->monspecs.hfmax || !info->monspecs.vfmax || | ||
| 1301 | !info->monspecs.dclkmax || | ||
| 1302 | info->monspecs.hfmax < info->monspecs.hfmin || | ||
| 1303 | info->monspecs.vfmax < info->monspecs.vfmin || | ||
| 1304 | info->monspecs.dclkmax < info->monspecs.dclkmin) { | ||
| 1305 | hfmin = 29000; hfmax = 30000; | ||
| 1306 | vfmin = 60; vfmax = 60; | ||
| 1307 | dclkmin = 0; dclkmax = 25000000; | ||
| 1308 | } else { | ||
| 1309 | hfmin = info->monspecs.hfmin; | ||
| 1310 | hfmax = info->monspecs.hfmax; | ||
| 1311 | vfmin = info->monspecs.vfmin; | ||
| 1312 | vfmax = info->monspecs.vfmax; | ||
| 1313 | dclkmin = info->monspecs.dclkmin; | ||
| 1314 | dclkmax = info->monspecs.dclkmax; | ||
| 1315 | } | ||
| 1316 | |||
| 1317 | timings->hactive = var->xres; | ||
| 1318 | timings->vactive = var->yres; | ||
| 1319 | if (var->vmode & FB_VMODE_INTERLACED) { | ||
| 1320 | timings->vactive /= 2; | ||
| 1321 | interlace = 2; | ||
| 1322 | } | ||
| 1323 | if (var->vmode & FB_VMODE_DOUBLE) { | ||
| 1324 | timings->vactive *= 2; | ||
| 1325 | dscan = 2; | ||
| 1326 | } | ||
| 1327 | |||
| 1328 | switch (flags & ~FB_IGNOREMON) { | ||
| 1329 | case FB_MAXTIMINGS: /* maximize refresh rate */ | ||
| 1330 | timings->hfreq = hfmax; | ||
| 1331 | fb_timings_hfreq(timings); | ||
| 1332 | if (timings->vfreq > vfmax) { | ||
| 1333 | timings->vfreq = vfmax; | ||
| 1334 | fb_timings_vfreq(timings); | ||
| 1335 | } | ||
| 1336 | if (timings->dclk > dclkmax) { | ||
| 1337 | timings->dclk = dclkmax; | ||
| 1338 | fb_timings_dclk(timings); | ||
| 1339 | } | ||
| 1340 | break; | ||
| 1341 | case FB_VSYNCTIMINGS: /* vrefresh driven */ | ||
| 1342 | timings->vfreq = val; | ||
| 1343 | fb_timings_vfreq(timings); | ||
| 1344 | break; | ||
| 1345 | case FB_HSYNCTIMINGS: /* hsync driven */ | ||
| 1346 | timings->hfreq = val; | ||
| 1347 | fb_timings_hfreq(timings); | ||
| 1348 | break; | ||
| 1349 | case FB_DCLKTIMINGS: /* pixelclock driven */ | ||
| 1350 | timings->dclk = PICOS2KHZ(val) * 1000; | ||
| 1351 | fb_timings_dclk(timings); | ||
| 1352 | break; | ||
| 1353 | default: | ||
| 1354 | err = -EINVAL; | ||
| 1355 | |||
| 1356 | } | ||
| 1357 | |||
| 1358 | if (err || (!(flags & FB_IGNOREMON) && | ||
| 1359 | (timings->vfreq < vfmin || timings->vfreq > vfmax || | ||
| 1360 | timings->hfreq < hfmin || timings->hfreq > hfmax || | ||
| 1361 | timings->dclk < dclkmin || timings->dclk > dclkmax))) { | ||
| 1362 | err = -EINVAL; | ||
| 1363 | } else { | ||
| 1364 | var->pixclock = KHZ2PICOS(timings->dclk/1000); | ||
| 1365 | var->hsync_len = (timings->htotal * 8)/100; | ||
| 1366 | var->right_margin = (timings->hblank/2) - var->hsync_len; | ||
| 1367 | var->left_margin = timings->hblank - var->right_margin - | ||
| 1368 | var->hsync_len; | ||
| 1369 | var->vsync_len = (3 * interlace)/dscan; | ||
| 1370 | var->lower_margin = (1 * interlace)/dscan; | ||
| 1371 | var->upper_margin = (timings->vblank * interlace)/dscan - | ||
| 1372 | (var->vsync_len + var->lower_margin); | ||
| 1373 | } | ||
| 1374 | |||
| 1375 | kfree(timings); | ||
| 1376 | return err; | ||
| 1377 | } | ||
| 1378 | |||
| 1379 | #ifdef CONFIG_VIDEOMODE_HELPERS | ||
| 1380 | int fb_videomode_from_videomode(const struct videomode *vm, | ||
| 1381 | struct fb_videomode *fbmode) | ||
| 1382 | { | ||
| 1383 | unsigned int htotal, vtotal; | ||
| 1384 | |||
| 1385 | fbmode->xres = vm->hactive; | ||
| 1386 | fbmode->left_margin = vm->hback_porch; | ||
| 1387 | fbmode->right_margin = vm->hfront_porch; | ||
| 1388 | fbmode->hsync_len = vm->hsync_len; | ||
| 1389 | |||
| 1390 | fbmode->yres = vm->vactive; | ||
| 1391 | fbmode->upper_margin = vm->vback_porch; | ||
| 1392 | fbmode->lower_margin = vm->vfront_porch; | ||
| 1393 | fbmode->vsync_len = vm->vsync_len; | ||
| 1394 | |||
| 1395 | /* prevent division by zero in KHZ2PICOS macro */ | ||
| 1396 | fbmode->pixclock = vm->pixelclock ? | ||
| 1397 | KHZ2PICOS(vm->pixelclock / 1000) : 0; | ||
| 1398 | |||
| 1399 | fbmode->sync = 0; | ||
| 1400 | fbmode->vmode = 0; | ||
| 1401 | if (vm->flags & DISPLAY_FLAGS_HSYNC_HIGH) | ||
| 1402 | fbmode->sync |= FB_SYNC_HOR_HIGH_ACT; | ||
| 1403 | if (vm->flags & DISPLAY_FLAGS_VSYNC_HIGH) | ||
| 1404 | fbmode->sync |= FB_SYNC_VERT_HIGH_ACT; | ||
| 1405 | if (vm->flags & DISPLAY_FLAGS_INTERLACED) | ||
| 1406 | fbmode->vmode |= FB_VMODE_INTERLACED; | ||
| 1407 | if (vm->flags & DISPLAY_FLAGS_DOUBLESCAN) | ||
| 1408 | fbmode->vmode |= FB_VMODE_DOUBLE; | ||
| 1409 | fbmode->flag = 0; | ||
| 1410 | |||
| 1411 | htotal = vm->hactive + vm->hfront_porch + vm->hback_porch + | ||
| 1412 | vm->hsync_len; | ||
| 1413 | vtotal = vm->vactive + vm->vfront_porch + vm->vback_porch + | ||
| 1414 | vm->vsync_len; | ||
| 1415 | /* prevent division by zero */ | ||
| 1416 | if (htotal && vtotal) { | ||
| 1417 | fbmode->refresh = vm->pixelclock / (htotal * vtotal); | ||
| 1418 | /* a mode must have htotal and vtotal != 0 or it is invalid */ | ||
| 1419 | } else { | ||
| 1420 | fbmode->refresh = 0; | ||
| 1421 | return -EINVAL; | ||
| 1422 | } | ||
| 1423 | |||
| 1424 | return 0; | ||
| 1425 | } | ||
| 1426 | EXPORT_SYMBOL_GPL(fb_videomode_from_videomode); | ||
| 1427 | |||
| 1428 | #ifdef CONFIG_OF | ||
| 1429 | static inline void dump_fb_videomode(const struct fb_videomode *m) | ||
| 1430 | { | ||
| 1431 | pr_debug("fb_videomode = %ux%u@%uHz (%ukHz) %u %u %u %u %u %u %u %u %u\n", | ||
| 1432 | m->xres, m->yres, m->refresh, m->pixclock, m->left_margin, | ||
| 1433 | m->right_margin, m->upper_margin, m->lower_margin, | ||
| 1434 | m->hsync_len, m->vsync_len, m->sync, m->vmode, m->flag); | ||
| 1435 | } | ||
| 1436 | |||
| 1437 | /** | ||
| 1438 | * of_get_fb_videomode - get a fb_videomode from devicetree | ||
| 1439 | * @np: device_node with the timing specification | ||
| 1440 | * @fb: will be set to the return value | ||
| 1441 | * @index: index into the list of display timings in devicetree | ||
| 1442 | * | ||
| 1443 | * DESCRIPTION: | ||
| 1444 | * This function is expensive and should only be used, if only one mode is to be | ||
| 1445 | * read from DT. To get multiple modes start with of_get_display_timings ond | ||
| 1446 | * work with that instead. | ||
| 1447 | */ | ||
| 1448 | int of_get_fb_videomode(struct device_node *np, struct fb_videomode *fb, | ||
| 1449 | int index) | ||
| 1450 | { | ||
| 1451 | struct videomode vm; | ||
| 1452 | int ret; | ||
| 1453 | |||
| 1454 | ret = of_get_videomode(np, &vm, index); | ||
| 1455 | if (ret) | ||
| 1456 | return ret; | ||
| 1457 | |||
| 1458 | fb_videomode_from_videomode(&vm, fb); | ||
| 1459 | |||
| 1460 | pr_debug("%s: got %dx%d display mode from %s\n", | ||
| 1461 | of_node_full_name(np), vm.hactive, vm.vactive, np->name); | ||
| 1462 | dump_fb_videomode(fb); | ||
| 1463 | |||
| 1464 | return 0; | ||
| 1465 | } | ||
| 1466 | EXPORT_SYMBOL_GPL(of_get_fb_videomode); | ||
| 1467 | #endif /* CONFIG_OF */ | ||
| 1468 | #endif /* CONFIG_VIDEOMODE_HELPERS */ | ||
| 1469 | |||
| 1470 | #else | ||
| 1471 | int fb_parse_edid(unsigned char *edid, struct fb_var_screeninfo *var) | ||
| 1472 | { | ||
| 1473 | return 1; | ||
| 1474 | } | ||
| 1475 | void fb_edid_to_monspecs(unsigned char *edid, struct fb_monspecs *specs) | ||
| 1476 | { | ||
| 1477 | specs = NULL; | ||
| 1478 | } | ||
| 1479 | void fb_edid_add_monspecs(unsigned char *edid, struct fb_monspecs *specs) | ||
| 1480 | { | ||
| 1481 | } | ||
| 1482 | void fb_destroy_modedb(struct fb_videomode *modedb) | ||
| 1483 | { | ||
| 1484 | } | ||
| 1485 | int fb_get_mode(int flags, u32 val, struct fb_var_screeninfo *var, | ||
| 1486 | struct fb_info *info) | ||
| 1487 | { | ||
| 1488 | return -EINVAL; | ||
| 1489 | } | ||
| 1490 | #endif /* CONFIG_FB_MODE_HELPERS */ | ||
| 1491 | |||
| 1492 | /* | ||
| 1493 | * fb_validate_mode - validates var against monitor capabilities | ||
| 1494 | * @var: pointer to fb_var_screeninfo | ||
| 1495 | * @info: pointer to fb_info | ||
| 1496 | * | ||
| 1497 | * DESCRIPTION: | ||
| 1498 | * Validates video mode against monitor capabilities specified in | ||
| 1499 | * info->monspecs. | ||
| 1500 | * | ||
| 1501 | * REQUIRES: | ||
| 1502 | * A valid info->monspecs. | ||
| 1503 | */ | ||
| 1504 | int fb_validate_mode(const struct fb_var_screeninfo *var, struct fb_info *info) | ||
| 1505 | { | ||
| 1506 | u32 hfreq, vfreq, htotal, vtotal, pixclock; | ||
| 1507 | u32 hfmin, hfmax, vfmin, vfmax, dclkmin, dclkmax; | ||
| 1508 | |||
| 1509 | /* | ||
| 1510 | * If monspecs are invalid, use values that are enough | ||
| 1511 | * for 640x480@60 | ||
| 1512 | */ | ||
| 1513 | if (!info->monspecs.hfmax || !info->monspecs.vfmax || | ||
| 1514 | !info->monspecs.dclkmax || | ||
| 1515 | info->monspecs.hfmax < info->monspecs.hfmin || | ||
| 1516 | info->monspecs.vfmax < info->monspecs.vfmin || | ||
| 1517 | info->monspecs.dclkmax < info->monspecs.dclkmin) { | ||
| 1518 | hfmin = 29000; hfmax = 30000; | ||
| 1519 | vfmin = 60; vfmax = 60; | ||
| 1520 | dclkmin = 0; dclkmax = 25000000; | ||
| 1521 | } else { | ||
| 1522 | hfmin = info->monspecs.hfmin; | ||
| 1523 | hfmax = info->monspecs.hfmax; | ||
| 1524 | vfmin = info->monspecs.vfmin; | ||
| 1525 | vfmax = info->monspecs.vfmax; | ||
| 1526 | dclkmin = info->monspecs.dclkmin; | ||
| 1527 | dclkmax = info->monspecs.dclkmax; | ||
| 1528 | } | ||
| 1529 | |||
| 1530 | if (!var->pixclock) | ||
| 1531 | return -EINVAL; | ||
| 1532 | pixclock = PICOS2KHZ(var->pixclock) * 1000; | ||
| 1533 | |||
| 1534 | htotal = var->xres + var->right_margin + var->hsync_len + | ||
| 1535 | var->left_margin; | ||
| 1536 | vtotal = var->yres + var->lower_margin + var->vsync_len + | ||
| 1537 | var->upper_margin; | ||
| 1538 | |||
| 1539 | if (var->vmode & FB_VMODE_INTERLACED) | ||
| 1540 | vtotal /= 2; | ||
| 1541 | if (var->vmode & FB_VMODE_DOUBLE) | ||
| 1542 | vtotal *= 2; | ||
| 1543 | |||
| 1544 | hfreq = pixclock/htotal; | ||
| 1545 | hfreq = (hfreq + 500) / 1000 * 1000; | ||
| 1546 | |||
| 1547 | vfreq = hfreq/vtotal; | ||
| 1548 | |||
| 1549 | return (vfreq < vfmin || vfreq > vfmax || | ||
| 1550 | hfreq < hfmin || hfreq > hfmax || | ||
| 1551 | pixclock < dclkmin || pixclock > dclkmax) ? | ||
| 1552 | -EINVAL : 0; | ||
| 1553 | } | ||
| 1554 | |||
| 1555 | #if defined(CONFIG_FIRMWARE_EDID) && defined(CONFIG_X86) | ||
| 1556 | |||
| 1557 | /* | ||
| 1558 | * We need to ensure that the EDID block is only returned for | ||
| 1559 | * the primary graphics adapter. | ||
| 1560 | */ | ||
| 1561 | |||
| 1562 | const unsigned char *fb_firmware_edid(struct device *device) | ||
| 1563 | { | ||
| 1564 | struct pci_dev *dev = NULL; | ||
| 1565 | struct resource *res = NULL; | ||
| 1566 | unsigned char *edid = NULL; | ||
| 1567 | |||
| 1568 | if (device) | ||
| 1569 | dev = to_pci_dev(device); | ||
| 1570 | |||
| 1571 | if (dev) | ||
| 1572 | res = &dev->resource[PCI_ROM_RESOURCE]; | ||
| 1573 | |||
| 1574 | if (res && res->flags & IORESOURCE_ROM_SHADOW) | ||
| 1575 | edid = edid_info.dummy; | ||
| 1576 | |||
| 1577 | return edid; | ||
| 1578 | } | ||
| 1579 | #else | ||
| 1580 | const unsigned char *fb_firmware_edid(struct device *device) | ||
| 1581 | { | ||
| 1582 | return NULL; | ||
| 1583 | } | ||
| 1584 | #endif | ||
| 1585 | EXPORT_SYMBOL(fb_firmware_edid); | ||
| 1586 | |||
| 1587 | EXPORT_SYMBOL(fb_parse_edid); | ||
| 1588 | EXPORT_SYMBOL(fb_edid_to_monspecs); | ||
| 1589 | EXPORT_SYMBOL(fb_edid_add_monspecs); | ||
| 1590 | EXPORT_SYMBOL(fb_get_mode); | ||
| 1591 | EXPORT_SYMBOL(fb_validate_mode); | ||
| 1592 | EXPORT_SYMBOL(fb_destroy_modedb); | ||
diff --git a/drivers/video/fbdev/core/fbsysfs.c b/drivers/video/fbdev/core/fbsysfs.c new file mode 100644 index 000000000000..53444ac19fe0 --- /dev/null +++ b/drivers/video/fbdev/core/fbsysfs.c | |||
| @@ -0,0 +1,586 @@ | |||
| 1 | /* | ||
| 2 | * fbsysfs.c - framebuffer device class and attributes | ||
| 3 | * | ||
| 4 | * Copyright (c) 2004 James Simmons <jsimmons@infradead.org> | ||
| 5 | * | ||
| 6 | * This program is free software you can redistribute it and/or | ||
| 7 | * modify it under the terms of the GNU General Public License | ||
| 8 | * as published by the Free Software Foundation; either version | ||
| 9 | * 2 of the License, or (at your option) any later version. | ||
| 10 | */ | ||
| 11 | |||
| 12 | /* | ||
| 13 | * Note: currently there's only stubs for framebuffer_alloc and | ||
| 14 | * framebuffer_release here. The reson for that is that until all drivers | ||
| 15 | * are converted to use it a sysfsification will open OOPSable races. | ||
| 16 | */ | ||
| 17 | |||
| 18 | #include <linux/kernel.h> | ||
| 19 | #include <linux/slab.h> | ||
| 20 | #include <linux/fb.h> | ||
| 21 | #include <linux/console.h> | ||
| 22 | #include <linux/module.h> | ||
| 23 | |||
| 24 | #define FB_SYSFS_FLAG_ATTR 1 | ||
| 25 | |||
| 26 | /** | ||
| 27 | * framebuffer_alloc - creates a new frame buffer info structure | ||
| 28 | * | ||
| 29 | * @size: size of driver private data, can be zero | ||
| 30 | * @dev: pointer to the device for this fb, this can be NULL | ||
| 31 | * | ||
| 32 | * Creates a new frame buffer info structure. Also reserves @size bytes | ||
| 33 | * for driver private data (info->par). info->par (if any) will be | ||
| 34 | * aligned to sizeof(long). | ||
| 35 | * | ||
| 36 | * Returns the new structure, or NULL if an error occurred. | ||
| 37 | * | ||
| 38 | */ | ||
| 39 | struct fb_info *framebuffer_alloc(size_t size, struct device *dev) | ||
| 40 | { | ||
| 41 | #define BYTES_PER_LONG (BITS_PER_LONG/8) | ||
| 42 | #define PADDING (BYTES_PER_LONG - (sizeof(struct fb_info) % BYTES_PER_LONG)) | ||
| 43 | int fb_info_size = sizeof(struct fb_info); | ||
| 44 | struct fb_info *info; | ||
| 45 | char *p; | ||
| 46 | |||
| 47 | if (size) | ||
| 48 | fb_info_size += PADDING; | ||
| 49 | |||
| 50 | p = kzalloc(fb_info_size + size, GFP_KERNEL); | ||
| 51 | |||
| 52 | if (!p) | ||
| 53 | return NULL; | ||
| 54 | |||
| 55 | info = (struct fb_info *) p; | ||
| 56 | |||
| 57 | if (size) | ||
| 58 | info->par = p + fb_info_size; | ||
| 59 | |||
| 60 | info->device = dev; | ||
| 61 | |||
| 62 | #ifdef CONFIG_FB_BACKLIGHT | ||
| 63 | mutex_init(&info->bl_curve_mutex); | ||
| 64 | #endif | ||
| 65 | |||
| 66 | return info; | ||
| 67 | #undef PADDING | ||
| 68 | #undef BYTES_PER_LONG | ||
| 69 | } | ||
| 70 | EXPORT_SYMBOL(framebuffer_alloc); | ||
| 71 | |||
| 72 | /** | ||
| 73 | * framebuffer_release - marks the structure available for freeing | ||
| 74 | * | ||
| 75 | * @info: frame buffer info structure | ||
| 76 | * | ||
| 77 | * Drop the reference count of the device embedded in the | ||
| 78 | * framebuffer info structure. | ||
| 79 | * | ||
| 80 | */ | ||
| 81 | void framebuffer_release(struct fb_info *info) | ||
| 82 | { | ||
| 83 | if (!info) | ||
| 84 | return; | ||
| 85 | kfree(info->apertures); | ||
| 86 | kfree(info); | ||
| 87 | } | ||
| 88 | EXPORT_SYMBOL(framebuffer_release); | ||
| 89 | |||
| 90 | static int activate(struct fb_info *fb_info, struct fb_var_screeninfo *var) | ||
| 91 | { | ||
| 92 | int err; | ||
| 93 | |||
| 94 | var->activate |= FB_ACTIVATE_FORCE; | ||
| 95 | console_lock(); | ||
| 96 | fb_info->flags |= FBINFO_MISC_USEREVENT; | ||
| 97 | err = fb_set_var(fb_info, var); | ||
| 98 | fb_info->flags &= ~FBINFO_MISC_USEREVENT; | ||
| 99 | console_unlock(); | ||
| 100 | if (err) | ||
| 101 | return err; | ||
| 102 | return 0; | ||
| 103 | } | ||
| 104 | |||
| 105 | static int mode_string(char *buf, unsigned int offset, | ||
| 106 | const struct fb_videomode *mode) | ||
| 107 | { | ||
| 108 | char m = 'U'; | ||
| 109 | char v = 'p'; | ||
| 110 | |||
| 111 | if (mode->flag & FB_MODE_IS_DETAILED) | ||
| 112 | m = 'D'; | ||
| 113 | if (mode->flag & FB_MODE_IS_VESA) | ||
| 114 | m = 'V'; | ||
| 115 | if (mode->flag & FB_MODE_IS_STANDARD) | ||
| 116 | m = 'S'; | ||
| 117 | |||
| 118 | if (mode->vmode & FB_VMODE_INTERLACED) | ||
| 119 | v = 'i'; | ||
| 120 | if (mode->vmode & FB_VMODE_DOUBLE) | ||
| 121 | v = 'd'; | ||
| 122 | |||
| 123 | return snprintf(&buf[offset], PAGE_SIZE - offset, "%c:%dx%d%c-%d\n", | ||
| 124 | m, mode->xres, mode->yres, v, mode->refresh); | ||
| 125 | } | ||
| 126 | |||
| 127 | static ssize_t store_mode(struct device *device, struct device_attribute *attr, | ||
| 128 | const char *buf, size_t count) | ||
| 129 | { | ||
| 130 | struct fb_info *fb_info = dev_get_drvdata(device); | ||
| 131 | char mstr[100]; | ||
| 132 | struct fb_var_screeninfo var; | ||
| 133 | struct fb_modelist *modelist; | ||
| 134 | struct fb_videomode *mode; | ||
| 135 | struct list_head *pos; | ||
| 136 | size_t i; | ||
| 137 | int err; | ||
| 138 | |||
| 139 | memset(&var, 0, sizeof(var)); | ||
| 140 | |||
| 141 | list_for_each(pos, &fb_info->modelist) { | ||
| 142 | modelist = list_entry(pos, struct fb_modelist, list); | ||
| 143 | mode = &modelist->mode; | ||
| 144 | i = mode_string(mstr, 0, mode); | ||
| 145 | if (strncmp(mstr, buf, max(count, i)) == 0) { | ||
| 146 | |||
| 147 | var = fb_info->var; | ||
| 148 | fb_videomode_to_var(&var, mode); | ||
| 149 | if ((err = activate(fb_info, &var))) | ||
| 150 | return err; | ||
| 151 | fb_info->mode = mode; | ||
| 152 | return count; | ||
| 153 | } | ||
| 154 | } | ||
| 155 | return -EINVAL; | ||
| 156 | } | ||
| 157 | |||
| 158 | static ssize_t show_mode(struct device *device, struct device_attribute *attr, | ||
| 159 | char *buf) | ||
| 160 | { | ||
| 161 | struct fb_info *fb_info = dev_get_drvdata(device); | ||
| 162 | |||
| 163 | if (!fb_info->mode) | ||
| 164 | return 0; | ||
| 165 | |||
| 166 | return mode_string(buf, 0, fb_info->mode); | ||
| 167 | } | ||
| 168 | |||
| 169 | static ssize_t store_modes(struct device *device, | ||
| 170 | struct device_attribute *attr, | ||
| 171 | const char *buf, size_t count) | ||
| 172 | { | ||
| 173 | struct fb_info *fb_info = dev_get_drvdata(device); | ||
| 174 | LIST_HEAD(old_list); | ||
| 175 | int i = count / sizeof(struct fb_videomode); | ||
| 176 | |||
| 177 | if (i * sizeof(struct fb_videomode) != count) | ||
| 178 | return -EINVAL; | ||
| 179 | |||
| 180 | console_lock(); | ||
| 181 | if (!lock_fb_info(fb_info)) { | ||
| 182 | console_unlock(); | ||
| 183 | return -ENODEV; | ||
| 184 | } | ||
| 185 | |||
| 186 | list_splice(&fb_info->modelist, &old_list); | ||
| 187 | fb_videomode_to_modelist((const struct fb_videomode *)buf, i, | ||
| 188 | &fb_info->modelist); | ||
| 189 | if (fb_new_modelist(fb_info)) { | ||
| 190 | fb_destroy_modelist(&fb_info->modelist); | ||
| 191 | list_splice(&old_list, &fb_info->modelist); | ||
| 192 | } else | ||
| 193 | fb_destroy_modelist(&old_list); | ||
| 194 | |||
| 195 | unlock_fb_info(fb_info); | ||
| 196 | console_unlock(); | ||
| 197 | |||
| 198 | return 0; | ||
| 199 | } | ||
| 200 | |||
| 201 | static ssize_t show_modes(struct device *device, struct device_attribute *attr, | ||
| 202 | char *buf) | ||
| 203 | { | ||
| 204 | struct fb_info *fb_info = dev_get_drvdata(device); | ||
| 205 | unsigned int i; | ||
| 206 | struct list_head *pos; | ||
| 207 | struct fb_modelist *modelist; | ||
| 208 | const struct fb_videomode *mode; | ||
| 209 | |||
| 210 | i = 0; | ||
| 211 | list_for_each(pos, &fb_info->modelist) { | ||
| 212 | modelist = list_entry(pos, struct fb_modelist, list); | ||
| 213 | mode = &modelist->mode; | ||
| 214 | i += mode_string(buf, i, mode); | ||
| 215 | } | ||
| 216 | return i; | ||
| 217 | } | ||
| 218 | |||
| 219 | static ssize_t store_bpp(struct device *device, struct device_attribute *attr, | ||
| 220 | const char *buf, size_t count) | ||
| 221 | { | ||
| 222 | struct fb_info *fb_info = dev_get_drvdata(device); | ||
| 223 | struct fb_var_screeninfo var; | ||
| 224 | char ** last = NULL; | ||
| 225 | int err; | ||
| 226 | |||
| 227 | var = fb_info->var; | ||
| 228 | var.bits_per_pixel = simple_strtoul(buf, last, 0); | ||
| 229 | if ((err = activate(fb_info, &var))) | ||
| 230 | return err; | ||
| 231 | return count; | ||
| 232 | } | ||
| 233 | |||
| 234 | static ssize_t show_bpp(struct device *device, struct device_attribute *attr, | ||
| 235 | char *buf) | ||
| 236 | { | ||
| 237 | struct fb_info *fb_info = dev_get_drvdata(device); | ||
| 238 | return snprintf(buf, PAGE_SIZE, "%d\n", fb_info->var.bits_per_pixel); | ||
| 239 | } | ||
| 240 | |||
| 241 | static ssize_t store_rotate(struct device *device, | ||
| 242 | struct device_attribute *attr, | ||
| 243 | const char *buf, size_t count) | ||
| 244 | { | ||
| 245 | struct fb_info *fb_info = dev_get_drvdata(device); | ||
| 246 | struct fb_var_screeninfo var; | ||
| 247 | char **last = NULL; | ||
| 248 | int err; | ||
| 249 | |||
| 250 | var = fb_info->var; | ||
| 251 | var.rotate = simple_strtoul(buf, last, 0); | ||
| 252 | |||
| 253 | if ((err = activate(fb_info, &var))) | ||
| 254 | return err; | ||
| 255 | |||
| 256 | return count; | ||
| 257 | } | ||
| 258 | |||
| 259 | |||
| 260 | static ssize_t show_rotate(struct device *device, | ||
| 261 | struct device_attribute *attr, char *buf) | ||
| 262 | { | ||
| 263 | struct fb_info *fb_info = dev_get_drvdata(device); | ||
| 264 | |||
| 265 | return snprintf(buf, PAGE_SIZE, "%d\n", fb_info->var.rotate); | ||
| 266 | } | ||
| 267 | |||
| 268 | static ssize_t store_virtual(struct device *device, | ||
| 269 | struct device_attribute *attr, | ||
| 270 | const char *buf, size_t count) | ||
| 271 | { | ||
| 272 | struct fb_info *fb_info = dev_get_drvdata(device); | ||
| 273 | struct fb_var_screeninfo var; | ||
| 274 | char *last = NULL; | ||
| 275 | int err; | ||
| 276 | |||
| 277 | var = fb_info->var; | ||
| 278 | var.xres_virtual = simple_strtoul(buf, &last, 0); | ||
| 279 | last++; | ||
| 280 | if (last - buf >= count) | ||
| 281 | return -EINVAL; | ||
| 282 | var.yres_virtual = simple_strtoul(last, &last, 0); | ||
| 283 | |||
| 284 | if ((err = activate(fb_info, &var))) | ||
| 285 | return err; | ||
| 286 | return count; | ||
| 287 | } | ||
| 288 | |||
| 289 | static ssize_t show_virtual(struct device *device, | ||
| 290 | struct device_attribute *attr, char *buf) | ||
| 291 | { | ||
| 292 | struct fb_info *fb_info = dev_get_drvdata(device); | ||
| 293 | return snprintf(buf, PAGE_SIZE, "%d,%d\n", fb_info->var.xres_virtual, | ||
| 294 | fb_info->var.yres_virtual); | ||
| 295 | } | ||
| 296 | |||
| 297 | static ssize_t show_stride(struct device *device, | ||
| 298 | struct device_attribute *attr, char *buf) | ||
| 299 | { | ||
| 300 | struct fb_info *fb_info = dev_get_drvdata(device); | ||
| 301 | return snprintf(buf, PAGE_SIZE, "%d\n", fb_info->fix.line_length); | ||
| 302 | } | ||
| 303 | |||
| 304 | static ssize_t store_blank(struct device *device, | ||
| 305 | struct device_attribute *attr, | ||
| 306 | const char *buf, size_t count) | ||
| 307 | { | ||
| 308 | struct fb_info *fb_info = dev_get_drvdata(device); | ||
| 309 | char *last = NULL; | ||
| 310 | int err; | ||
| 311 | |||
| 312 | console_lock(); | ||
| 313 | fb_info->flags |= FBINFO_MISC_USEREVENT; | ||
| 314 | err = fb_blank(fb_info, simple_strtoul(buf, &last, 0)); | ||
| 315 | fb_info->flags &= ~FBINFO_MISC_USEREVENT; | ||
| 316 | console_unlock(); | ||
| 317 | if (err < 0) | ||
| 318 | return err; | ||
| 319 | return count; | ||
| 320 | } | ||
| 321 | |||
| 322 | static ssize_t show_blank(struct device *device, | ||
| 323 | struct device_attribute *attr, char *buf) | ||
| 324 | { | ||
| 325 | // struct fb_info *fb_info = dev_get_drvdata(device); | ||
| 326 | return 0; | ||
| 327 | } | ||
| 328 | |||
| 329 | static ssize_t store_console(struct device *device, | ||
| 330 | struct device_attribute *attr, | ||
| 331 | const char *buf, size_t count) | ||
| 332 | { | ||
| 333 | // struct fb_info *fb_info = dev_get_drvdata(device); | ||
| 334 | return 0; | ||
| 335 | } | ||
| 336 | |||
| 337 | static ssize_t show_console(struct device *device, | ||
| 338 | struct device_attribute *attr, char *buf) | ||
| 339 | { | ||
| 340 | // struct fb_info *fb_info = dev_get_drvdata(device); | ||
| 341 | return 0; | ||
| 342 | } | ||
| 343 | |||
| 344 | static ssize_t store_cursor(struct device *device, | ||
| 345 | struct device_attribute *attr, | ||
| 346 | const char *buf, size_t count) | ||
| 347 | { | ||
| 348 | // struct fb_info *fb_info = dev_get_drvdata(device); | ||
| 349 | return 0; | ||
| 350 | } | ||
| 351 | |||
| 352 | static ssize_t show_cursor(struct device *device, | ||
| 353 | struct device_attribute *attr, char *buf) | ||
| 354 | { | ||
| 355 | // struct fb_info *fb_info = dev_get_drvdata(device); | ||
| 356 | return 0; | ||
| 357 | } | ||
| 358 | |||
| 359 | static ssize_t store_pan(struct device *device, | ||
| 360 | struct device_attribute *attr, | ||
| 361 | const char *buf, size_t count) | ||
| 362 | { | ||
| 363 | struct fb_info *fb_info = dev_get_drvdata(device); | ||
| 364 | struct fb_var_screeninfo var; | ||
| 365 | char *last = NULL; | ||
| 366 | int err; | ||
| 367 | |||
| 368 | var = fb_info->var; | ||
| 369 | var.xoffset = simple_strtoul(buf, &last, 0); | ||
| 370 | last++; | ||
| 371 | if (last - buf >= count) | ||
| 372 | return -EINVAL; | ||
| 373 | var.yoffset = simple_strtoul(last, &last, 0); | ||
| 374 | |||
| 375 | console_lock(); | ||
| 376 | err = fb_pan_display(fb_info, &var); | ||
| 377 | console_unlock(); | ||
| 378 | |||
| 379 | if (err < 0) | ||
| 380 | return err; | ||
| 381 | return count; | ||
| 382 | } | ||
| 383 | |||
| 384 | static ssize_t show_pan(struct device *device, | ||
| 385 | struct device_attribute *attr, char *buf) | ||
| 386 | { | ||
| 387 | struct fb_info *fb_info = dev_get_drvdata(device); | ||
| 388 | return snprintf(buf, PAGE_SIZE, "%d,%d\n", fb_info->var.xoffset, | ||
| 389 | fb_info->var.yoffset); | ||
| 390 | } | ||
| 391 | |||
| 392 | static ssize_t show_name(struct device *device, | ||
| 393 | struct device_attribute *attr, char *buf) | ||
| 394 | { | ||
| 395 | struct fb_info *fb_info = dev_get_drvdata(device); | ||
| 396 | |||
| 397 | return snprintf(buf, PAGE_SIZE, "%s\n", fb_info->fix.id); | ||
| 398 | } | ||
| 399 | |||
| 400 | static ssize_t store_fbstate(struct device *device, | ||
| 401 | struct device_attribute *attr, | ||
| 402 | const char *buf, size_t count) | ||
| 403 | { | ||
| 404 | struct fb_info *fb_info = dev_get_drvdata(device); | ||
| 405 | u32 state; | ||
| 406 | char *last = NULL; | ||
| 407 | |||
| 408 | state = simple_strtoul(buf, &last, 0); | ||
| 409 | |||
| 410 | console_lock(); | ||
| 411 | if (!lock_fb_info(fb_info)) { | ||
| 412 | console_unlock(); | ||
| 413 | return -ENODEV; | ||
| 414 | } | ||
| 415 | |||
| 416 | fb_set_suspend(fb_info, (int)state); | ||
| 417 | |||
| 418 | unlock_fb_info(fb_info); | ||
| 419 | console_unlock(); | ||
| 420 | |||
| 421 | return count; | ||
| 422 | } | ||
| 423 | |||
| 424 | static ssize_t show_fbstate(struct device *device, | ||
| 425 | struct device_attribute *attr, char *buf) | ||
| 426 | { | ||
| 427 | struct fb_info *fb_info = dev_get_drvdata(device); | ||
| 428 | return snprintf(buf, PAGE_SIZE, "%d\n", fb_info->state); | ||
| 429 | } | ||
| 430 | |||
| 431 | #ifdef CONFIG_FB_BACKLIGHT | ||
| 432 | static ssize_t store_bl_curve(struct device *device, | ||
| 433 | struct device_attribute *attr, | ||
| 434 | const char *buf, size_t count) | ||
| 435 | { | ||
| 436 | struct fb_info *fb_info = dev_get_drvdata(device); | ||
| 437 | u8 tmp_curve[FB_BACKLIGHT_LEVELS]; | ||
| 438 | unsigned int i; | ||
| 439 | |||
| 440 | /* Some drivers don't use framebuffer_alloc(), but those also | ||
| 441 | * don't have backlights. | ||
| 442 | */ | ||
| 443 | if (!fb_info || !fb_info->bl_dev) | ||
| 444 | return -ENODEV; | ||
| 445 | |||
| 446 | if (count != (FB_BACKLIGHT_LEVELS / 8 * 24)) | ||
| 447 | return -EINVAL; | ||
| 448 | |||
| 449 | for (i = 0; i < (FB_BACKLIGHT_LEVELS / 8); ++i) | ||
| 450 | if (sscanf(&buf[i * 24], | ||
| 451 | "%2hhx %2hhx %2hhx %2hhx %2hhx %2hhx %2hhx %2hhx\n", | ||
| 452 | &tmp_curve[i * 8 + 0], | ||
| 453 | &tmp_curve[i * 8 + 1], | ||
| 454 | &tmp_curve[i * 8 + 2], | ||
| 455 | &tmp_curve[i * 8 + 3], | ||
| 456 | &tmp_curve[i * 8 + 4], | ||
| 457 | &tmp_curve[i * 8 + 5], | ||
| 458 | &tmp_curve[i * 8 + 6], | ||
| 459 | &tmp_curve[i * 8 + 7]) != 8) | ||
| 460 | return -EINVAL; | ||
| 461 | |||
| 462 | /* If there has been an error in the input data, we won't | ||
| 463 | * reach this loop. | ||
| 464 | */ | ||
| 465 | mutex_lock(&fb_info->bl_curve_mutex); | ||
| 466 | for (i = 0; i < FB_BACKLIGHT_LEVELS; ++i) | ||
| 467 | fb_info->bl_curve[i] = tmp_curve[i]; | ||
| 468 | mutex_unlock(&fb_info->bl_curve_mutex); | ||
| 469 | |||
| 470 | return count; | ||
| 471 | } | ||
| 472 | |||
| 473 | static ssize_t show_bl_curve(struct device *device, | ||
| 474 | struct device_attribute *attr, char *buf) | ||
| 475 | { | ||
| 476 | struct fb_info *fb_info = dev_get_drvdata(device); | ||
| 477 | ssize_t len = 0; | ||
| 478 | unsigned int i; | ||
| 479 | |||
| 480 | /* Some drivers don't use framebuffer_alloc(), but those also | ||
| 481 | * don't have backlights. | ||
| 482 | */ | ||
| 483 | if (!fb_info || !fb_info->bl_dev) | ||
| 484 | return -ENODEV; | ||
| 485 | |||
| 486 | mutex_lock(&fb_info->bl_curve_mutex); | ||
| 487 | for (i = 0; i < FB_BACKLIGHT_LEVELS; i += 8) | ||
| 488 | len += snprintf(&buf[len], PAGE_SIZE, | ||
| 489 | "%02x %02x %02x %02x %02x %02x %02x %02x\n", | ||
| 490 | fb_info->bl_curve[i + 0], | ||
| 491 | fb_info->bl_curve[i + 1], | ||
| 492 | fb_info->bl_curve[i + 2], | ||
| 493 | fb_info->bl_curve[i + 3], | ||
| 494 | fb_info->bl_curve[i + 4], | ||
| 495 | fb_info->bl_curve[i + 5], | ||
| 496 | fb_info->bl_curve[i + 6], | ||
| 497 | fb_info->bl_curve[i + 7]); | ||
| 498 | mutex_unlock(&fb_info->bl_curve_mutex); | ||
| 499 | |||
| 500 | return len; | ||
| 501 | } | ||
| 502 | #endif | ||
| 503 | |||
| 504 | /* When cmap is added back in it should be a binary attribute | ||
| 505 | * not a text one. Consideration should also be given to converting | ||
| 506 | * fbdev to use configfs instead of sysfs */ | ||
| 507 | static struct device_attribute device_attrs[] = { | ||
| 508 | __ATTR(bits_per_pixel, S_IRUGO|S_IWUSR, show_bpp, store_bpp), | ||
| 509 | __ATTR(blank, S_IRUGO|S_IWUSR, show_blank, store_blank), | ||
| 510 | __ATTR(console, S_IRUGO|S_IWUSR, show_console, store_console), | ||
| 511 | __ATTR(cursor, S_IRUGO|S_IWUSR, show_cursor, store_cursor), | ||
| 512 | __ATTR(mode, S_IRUGO|S_IWUSR, show_mode, store_mode), | ||
| 513 | __ATTR(modes, S_IRUGO|S_IWUSR, show_modes, store_modes), | ||
| 514 | __ATTR(pan, S_IRUGO|S_IWUSR, show_pan, store_pan), | ||
| 515 | __ATTR(virtual_size, S_IRUGO|S_IWUSR, show_virtual, store_virtual), | ||
| 516 | __ATTR(name, S_IRUGO, show_name, NULL), | ||
| 517 | __ATTR(stride, S_IRUGO, show_stride, NULL), | ||
| 518 | __ATTR(rotate, S_IRUGO|S_IWUSR, show_rotate, store_rotate), | ||
| 519 | __ATTR(state, S_IRUGO|S_IWUSR, show_fbstate, store_fbstate), | ||
| 520 | #ifdef CONFIG_FB_BACKLIGHT | ||
| 521 | __ATTR(bl_curve, S_IRUGO|S_IWUSR, show_bl_curve, store_bl_curve), | ||
| 522 | #endif | ||
| 523 | }; | ||
| 524 | |||
| 525 | int fb_init_device(struct fb_info *fb_info) | ||
| 526 | { | ||
| 527 | int i, error = 0; | ||
| 528 | |||
| 529 | dev_set_drvdata(fb_info->dev, fb_info); | ||
| 530 | |||
| 531 | fb_info->class_flag |= FB_SYSFS_FLAG_ATTR; | ||
| 532 | |||
| 533 | for (i = 0; i < ARRAY_SIZE(device_attrs); i++) { | ||
| 534 | error = device_create_file(fb_info->dev, &device_attrs[i]); | ||
| 535 | |||
| 536 | if (error) | ||
| 537 | break; | ||
| 538 | } | ||
| 539 | |||
| 540 | if (error) { | ||
| 541 | while (--i >= 0) | ||
| 542 | device_remove_file(fb_info->dev, &device_attrs[i]); | ||
| 543 | fb_info->class_flag &= ~FB_SYSFS_FLAG_ATTR; | ||
| 544 | } | ||
| 545 | |||
| 546 | return 0; | ||
| 547 | } | ||
| 548 | |||
| 549 | void fb_cleanup_device(struct fb_info *fb_info) | ||
| 550 | { | ||
| 551 | unsigned int i; | ||
| 552 | |||
| 553 | if (fb_info->class_flag & FB_SYSFS_FLAG_ATTR) { | ||
| 554 | for (i = 0; i < ARRAY_SIZE(device_attrs); i++) | ||
| 555 | device_remove_file(fb_info->dev, &device_attrs[i]); | ||
| 556 | |||
| 557 | fb_info->class_flag &= ~FB_SYSFS_FLAG_ATTR; | ||
| 558 | } | ||
| 559 | } | ||
| 560 | |||
| 561 | #ifdef CONFIG_FB_BACKLIGHT | ||
| 562 | /* This function generates a linear backlight curve | ||
| 563 | * | ||
| 564 | * 0: off | ||
| 565 | * 1-7: min | ||
| 566 | * 8-127: linear from min to max | ||
| 567 | */ | ||
| 568 | void fb_bl_default_curve(struct fb_info *fb_info, u8 off, u8 min, u8 max) | ||
| 569 | { | ||
| 570 | unsigned int i, flat, count, range = (max - min); | ||
| 571 | |||
| 572 | mutex_lock(&fb_info->bl_curve_mutex); | ||
| 573 | |||
| 574 | fb_info->bl_curve[0] = off; | ||
| 575 | |||
| 576 | for (flat = 1; flat < (FB_BACKLIGHT_LEVELS / 16); ++flat) | ||
| 577 | fb_info->bl_curve[flat] = min; | ||
| 578 | |||
| 579 | count = FB_BACKLIGHT_LEVELS * 15 / 16; | ||
| 580 | for (i = 0; i < count; ++i) | ||
| 581 | fb_info->bl_curve[flat + i] = min + (range * (i + 1) / count); | ||
| 582 | |||
| 583 | mutex_unlock(&fb_info->bl_curve_mutex); | ||
| 584 | } | ||
| 585 | EXPORT_SYMBOL_GPL(fb_bl_default_curve); | ||
| 586 | #endif | ||
diff --git a/drivers/video/fbdev/core/modedb.c b/drivers/video/fbdev/core/modedb.c new file mode 100644 index 000000000000..a9a907c440d7 --- /dev/null +++ b/drivers/video/fbdev/core/modedb.c | |||
| @@ -0,0 +1,1137 @@ | |||
| 1 | /* | ||
| 2 | * linux/drivers/video/modedb.c -- Standard video mode database management | ||
| 3 | * | ||
| 4 | * Copyright (C) 1999 Geert Uytterhoeven | ||
| 5 | * | ||
| 6 | * 2001 - Documented with DocBook | ||
| 7 | * - Brad Douglas <brad@neruo.com> | ||
| 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 for | ||
| 11 | * more details. | ||
| 12 | */ | ||
| 13 | |||
| 14 | #include <linux/module.h> | ||
| 15 | #include <linux/slab.h> | ||
| 16 | #include <linux/fb.h> | ||
| 17 | #include <linux/kernel.h> | ||
| 18 | |||
| 19 | #undef DEBUG | ||
| 20 | |||
| 21 | #define name_matches(v, s, l) \ | ||
| 22 | ((v).name && !strncmp((s), (v).name, (l)) && strlen((v).name) == (l)) | ||
| 23 | #define res_matches(v, x, y) \ | ||
| 24 | ((v).xres == (x) && (v).yres == (y)) | ||
| 25 | |||
| 26 | #ifdef DEBUG | ||
| 27 | #define DPRINTK(fmt, args...) printk("modedb %s: " fmt, __func__ , ## args) | ||
| 28 | #else | ||
| 29 | #define DPRINTK(fmt, args...) | ||
| 30 | #endif | ||
| 31 | |||
| 32 | const char *fb_mode_option; | ||
| 33 | EXPORT_SYMBOL_GPL(fb_mode_option); | ||
| 34 | |||
| 35 | /* | ||
| 36 | * Standard video mode definitions (taken from XFree86) | ||
| 37 | */ | ||
| 38 | |||
| 39 | static const struct fb_videomode modedb[] = { | ||
| 40 | |||
| 41 | /* 640x400 @ 70 Hz, 31.5 kHz hsync */ | ||
| 42 | { NULL, 70, 640, 400, 39721, 40, 24, 39, 9, 96, 2, 0, | ||
| 43 | FB_VMODE_NONINTERLACED }, | ||
| 44 | |||
| 45 | /* 640x480 @ 60 Hz, 31.5 kHz hsync */ | ||
| 46 | { NULL, 60, 640, 480, 39721, 40, 24, 32, 11, 96, 2, 0, | ||
| 47 | FB_VMODE_NONINTERLACED }, | ||
| 48 | |||
| 49 | /* 800x600 @ 56 Hz, 35.15 kHz hsync */ | ||
| 50 | { NULL, 56, 800, 600, 27777, 128, 24, 22, 1, 72, 2, 0, | ||
| 51 | FB_VMODE_NONINTERLACED }, | ||
| 52 | |||
| 53 | /* 1024x768 @ 87 Hz interlaced, 35.5 kHz hsync */ | ||
| 54 | { NULL, 87, 1024, 768, 22271, 56, 24, 33, 8, 160, 8, 0, | ||
| 55 | FB_VMODE_INTERLACED }, | ||
| 56 | |||
| 57 | /* 640x400 @ 85 Hz, 37.86 kHz hsync */ | ||
| 58 | { NULL, 85, 640, 400, 31746, 96, 32, 41, 1, 64, 3, | ||
| 59 | FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED }, | ||
| 60 | |||
| 61 | /* 640x480 @ 72 Hz, 36.5 kHz hsync */ | ||
| 62 | { NULL, 72, 640, 480, 31746, 144, 40, 30, 8, 40, 3, 0, | ||
| 63 | FB_VMODE_NONINTERLACED }, | ||
| 64 | |||
| 65 | /* 640x480 @ 75 Hz, 37.50 kHz hsync */ | ||
| 66 | { NULL, 75, 640, 480, 31746, 120, 16, 16, 1, 64, 3, 0, | ||
| 67 | FB_VMODE_NONINTERLACED }, | ||
| 68 | |||
| 69 | /* 800x600 @ 60 Hz, 37.8 kHz hsync */ | ||
| 70 | { NULL, 60, 800, 600, 25000, 88, 40, 23, 1, 128, 4, | ||
| 71 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
| 72 | FB_VMODE_NONINTERLACED }, | ||
| 73 | |||
| 74 | /* 640x480 @ 85 Hz, 43.27 kHz hsync */ | ||
| 75 | { NULL, 85, 640, 480, 27777, 80, 56, 25, 1, 56, 3, 0, | ||
| 76 | FB_VMODE_NONINTERLACED }, | ||
| 77 | |||
| 78 | /* 1152x864 @ 89 Hz interlaced, 44 kHz hsync */ | ||
| 79 | { NULL, 89, 1152, 864, 15384, 96, 16, 110, 1, 216, 10, 0, | ||
| 80 | FB_VMODE_INTERLACED }, | ||
| 81 | /* 800x600 @ 72 Hz, 48.0 kHz hsync */ | ||
| 82 | { NULL, 72, 800, 600, 20000, 64, 56, 23, 37, 120, 6, | ||
| 83 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
| 84 | FB_VMODE_NONINTERLACED }, | ||
| 85 | |||
| 86 | /* 1024x768 @ 60 Hz, 48.4 kHz hsync */ | ||
| 87 | { NULL, 60, 1024, 768, 15384, 168, 8, 29, 3, 144, 6, 0, | ||
| 88 | FB_VMODE_NONINTERLACED }, | ||
| 89 | |||
| 90 | /* 640x480 @ 100 Hz, 53.01 kHz hsync */ | ||
| 91 | { NULL, 100, 640, 480, 21834, 96, 32, 36, 8, 96, 6, 0, | ||
| 92 | FB_VMODE_NONINTERLACED }, | ||
| 93 | |||
| 94 | /* 1152x864 @ 60 Hz, 53.5 kHz hsync */ | ||
| 95 | { NULL, 60, 1152, 864, 11123, 208, 64, 16, 4, 256, 8, 0, | ||
| 96 | FB_VMODE_NONINTERLACED }, | ||
| 97 | |||
| 98 | /* 800x600 @ 85 Hz, 55.84 kHz hsync */ | ||
| 99 | { NULL, 85, 800, 600, 16460, 160, 64, 36, 16, 64, 5, 0, | ||
| 100 | FB_VMODE_NONINTERLACED }, | ||
| 101 | |||
| 102 | /* 1024x768 @ 70 Hz, 56.5 kHz hsync */ | ||
| 103 | { NULL, 70, 1024, 768, 13333, 144, 24, 29, 3, 136, 6, 0, | ||
| 104 | FB_VMODE_NONINTERLACED }, | ||
| 105 | |||
| 106 | /* 1280x1024 @ 87 Hz interlaced, 51 kHz hsync */ | ||
| 107 | { NULL, 87, 1280, 1024, 12500, 56, 16, 128, 1, 216, 12, 0, | ||
| 108 | FB_VMODE_INTERLACED }, | ||
| 109 | |||
| 110 | /* 800x600 @ 100 Hz, 64.02 kHz hsync */ | ||
| 111 | { NULL, 100, 800, 600, 14357, 160, 64, 30, 4, 64, 6, 0, | ||
| 112 | FB_VMODE_NONINTERLACED }, | ||
| 113 | |||
| 114 | /* 1024x768 @ 76 Hz, 62.5 kHz hsync */ | ||
| 115 | { NULL, 76, 1024, 768, 11764, 208, 8, 36, 16, 120, 3, 0, | ||
| 116 | FB_VMODE_NONINTERLACED }, | ||
| 117 | |||
| 118 | /* 1152x864 @ 70 Hz, 62.4 kHz hsync */ | ||
| 119 | { NULL, 70, 1152, 864, 10869, 106, 56, 20, 1, 160, 10, 0, | ||
| 120 | FB_VMODE_NONINTERLACED }, | ||
| 121 | |||
| 122 | /* 1280x1024 @ 61 Hz, 64.2 kHz hsync */ | ||
| 123 | { NULL, 61, 1280, 1024, 9090, 200, 48, 26, 1, 184, 3, 0, | ||
| 124 | FB_VMODE_NONINTERLACED }, | ||
| 125 | |||
| 126 | /* 1400x1050 @ 60Hz, 63.9 kHz hsync */ | ||
| 127 | { NULL, 60, 1400, 1050, 9259, 136, 40, 13, 1, 112, 3, 0, | ||
| 128 | FB_VMODE_NONINTERLACED }, | ||
| 129 | |||
| 130 | /* 1400x1050 @ 75,107 Hz, 82,392 kHz +hsync +vsync*/ | ||
| 131 | { NULL, 75, 1400, 1050, 7190, 120, 56, 23, 10, 112, 13, | ||
| 132 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
| 133 | FB_VMODE_NONINTERLACED }, | ||
| 134 | |||
| 135 | /* 1400x1050 @ 60 Hz, ? kHz +hsync +vsync*/ | ||
| 136 | { NULL, 60, 1400, 1050, 9259, 128, 40, 12, 0, 112, 3, | ||
| 137 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
| 138 | FB_VMODE_NONINTERLACED }, | ||
| 139 | |||
| 140 | /* 1024x768 @ 85 Hz, 70.24 kHz hsync */ | ||
| 141 | { NULL, 85, 1024, 768, 10111, 192, 32, 34, 14, 160, 6, 0, | ||
| 142 | FB_VMODE_NONINTERLACED }, | ||
| 143 | |||
| 144 | /* 1152x864 @ 78 Hz, 70.8 kHz hsync */ | ||
| 145 | { NULL, 78, 1152, 864, 9090, 228, 88, 32, 0, 84, 12, 0, | ||
| 146 | FB_VMODE_NONINTERLACED }, | ||
| 147 | |||
| 148 | /* 1280x1024 @ 70 Hz, 74.59 kHz hsync */ | ||
| 149 | { NULL, 70, 1280, 1024, 7905, 224, 32, 28, 8, 160, 8, 0, | ||
| 150 | FB_VMODE_NONINTERLACED }, | ||
| 151 | |||
| 152 | /* 1600x1200 @ 60Hz, 75.00 kHz hsync */ | ||
| 153 | { NULL, 60, 1600, 1200, 6172, 304, 64, 46, 1, 192, 3, | ||
| 154 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
| 155 | FB_VMODE_NONINTERLACED }, | ||
| 156 | |||
| 157 | /* 1152x864 @ 84 Hz, 76.0 kHz hsync */ | ||
| 158 | { NULL, 84, 1152, 864, 7407, 184, 312, 32, 0, 128, 12, 0, | ||
| 159 | FB_VMODE_NONINTERLACED }, | ||
| 160 | |||
| 161 | /* 1280x1024 @ 74 Hz, 78.85 kHz hsync */ | ||
| 162 | { NULL, 74, 1280, 1024, 7407, 256, 32, 34, 3, 144, 3, 0, | ||
| 163 | FB_VMODE_NONINTERLACED }, | ||
| 164 | |||
| 165 | /* 1024x768 @ 100Hz, 80.21 kHz hsync */ | ||
| 166 | { NULL, 100, 1024, 768, 8658, 192, 32, 21, 3, 192, 10, 0, | ||
| 167 | FB_VMODE_NONINTERLACED }, | ||
| 168 | |||
| 169 | /* 1280x1024 @ 76 Hz, 81.13 kHz hsync */ | ||
| 170 | { NULL, 76, 1280, 1024, 7407, 248, 32, 34, 3, 104, 3, 0, | ||
| 171 | FB_VMODE_NONINTERLACED }, | ||
| 172 | |||
| 173 | /* 1600x1200 @ 70 Hz, 87.50 kHz hsync */ | ||
| 174 | { NULL, 70, 1600, 1200, 5291, 304, 64, 46, 1, 192, 3, 0, | ||
| 175 | FB_VMODE_NONINTERLACED }, | ||
| 176 | |||
| 177 | /* 1152x864 @ 100 Hz, 89.62 kHz hsync */ | ||
| 178 | { NULL, 100, 1152, 864, 7264, 224, 32, 17, 2, 128, 19, 0, | ||
| 179 | FB_VMODE_NONINTERLACED }, | ||
| 180 | |||
| 181 | /* 1280x1024 @ 85 Hz, 91.15 kHz hsync */ | ||
| 182 | { NULL, 85, 1280, 1024, 6349, 224, 64, 44, 1, 160, 3, | ||
| 183 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
| 184 | FB_VMODE_NONINTERLACED }, | ||
| 185 | |||
| 186 | /* 1600x1200 @ 75 Hz, 93.75 kHz hsync */ | ||
| 187 | { NULL, 75, 1600, 1200, 4938, 304, 64, 46, 1, 192, 3, | ||
| 188 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
| 189 | FB_VMODE_NONINTERLACED }, | ||
| 190 | |||
| 191 | /* 1680x1050 @ 60 Hz, 65.191 kHz hsync */ | ||
| 192 | { NULL, 60, 1680, 1050, 6848, 280, 104, 30, 3, 176, 6, | ||
| 193 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
| 194 | FB_VMODE_NONINTERLACED }, | ||
| 195 | |||
| 196 | /* 1600x1200 @ 85 Hz, 105.77 kHz hsync */ | ||
| 197 | { NULL, 85, 1600, 1200, 4545, 272, 16, 37, 4, 192, 3, | ||
| 198 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
| 199 | FB_VMODE_NONINTERLACED }, | ||
| 200 | |||
| 201 | /* 1280x1024 @ 100 Hz, 107.16 kHz hsync */ | ||
| 202 | { NULL, 100, 1280, 1024, 5502, 256, 32, 26, 7, 128, 15, 0, | ||
| 203 | FB_VMODE_NONINTERLACED }, | ||
| 204 | |||
| 205 | /* 1800x1440 @ 64Hz, 96.15 kHz hsync */ | ||
| 206 | { NULL, 64, 1800, 1440, 4347, 304, 96, 46, 1, 192, 3, | ||
| 207 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
| 208 | FB_VMODE_NONINTERLACED }, | ||
| 209 | |||
| 210 | /* 1800x1440 @ 70Hz, 104.52 kHz hsync */ | ||
| 211 | { NULL, 70, 1800, 1440, 4000, 304, 96, 46, 1, 192, 3, | ||
| 212 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
| 213 | FB_VMODE_NONINTERLACED }, | ||
| 214 | |||
| 215 | /* 512x384 @ 78 Hz, 31.50 kHz hsync */ | ||
| 216 | { NULL, 78, 512, 384, 49603, 48, 16, 16, 1, 64, 3, 0, | ||
| 217 | FB_VMODE_NONINTERLACED }, | ||
| 218 | |||
| 219 | /* 512x384 @ 85 Hz, 34.38 kHz hsync */ | ||
| 220 | { NULL, 85, 512, 384, 45454, 48, 16, 16, 1, 64, 3, 0, | ||
| 221 | FB_VMODE_NONINTERLACED }, | ||
| 222 | |||
| 223 | /* 320x200 @ 70 Hz, 31.5 kHz hsync, 8:5 aspect ratio */ | ||
| 224 | { NULL, 70, 320, 200, 79440, 16, 16, 20, 4, 48, 1, 0, | ||
| 225 | FB_VMODE_DOUBLE }, | ||
| 226 | |||
| 227 | /* 320x240 @ 60 Hz, 31.5 kHz hsync, 4:3 aspect ratio */ | ||
| 228 | { NULL, 60, 320, 240, 79440, 16, 16, 16, 5, 48, 1, 0, | ||
| 229 | FB_VMODE_DOUBLE }, | ||
| 230 | |||
| 231 | /* 320x240 @ 72 Hz, 36.5 kHz hsync */ | ||
| 232 | { NULL, 72, 320, 240, 63492, 16, 16, 16, 4, 48, 2, 0, | ||
| 233 | FB_VMODE_DOUBLE }, | ||
| 234 | |||
| 235 | /* 400x300 @ 56 Hz, 35.2 kHz hsync, 4:3 aspect ratio */ | ||
| 236 | { NULL, 56, 400, 300, 55555, 64, 16, 10, 1, 32, 1, 0, | ||
| 237 | FB_VMODE_DOUBLE }, | ||
| 238 | |||
| 239 | /* 400x300 @ 60 Hz, 37.8 kHz hsync */ | ||
| 240 | { NULL, 60, 400, 300, 50000, 48, 16, 11, 1, 64, 2, 0, | ||
| 241 | FB_VMODE_DOUBLE }, | ||
| 242 | |||
| 243 | /* 400x300 @ 72 Hz, 48.0 kHz hsync */ | ||
| 244 | { NULL, 72, 400, 300, 40000, 32, 24, 11, 19, 64, 3, 0, | ||
| 245 | FB_VMODE_DOUBLE }, | ||
| 246 | |||
| 247 | /* 480x300 @ 56 Hz, 35.2 kHz hsync, 8:5 aspect ratio */ | ||
| 248 | { NULL, 56, 480, 300, 46176, 80, 16, 10, 1, 40, 1, 0, | ||
| 249 | FB_VMODE_DOUBLE }, | ||
| 250 | |||
| 251 | /* 480x300 @ 60 Hz, 37.8 kHz hsync */ | ||
| 252 | { NULL, 60, 480, 300, 41858, 56, 16, 11, 1, 80, 2, 0, | ||
| 253 | FB_VMODE_DOUBLE }, | ||
| 254 | |||
| 255 | /* 480x300 @ 63 Hz, 39.6 kHz hsync */ | ||
| 256 | { NULL, 63, 480, 300, 40000, 56, 16, 11, 1, 80, 2, 0, | ||
| 257 | FB_VMODE_DOUBLE }, | ||
| 258 | |||
| 259 | /* 480x300 @ 72 Hz, 48.0 kHz hsync */ | ||
| 260 | { NULL, 72, 480, 300, 33386, 40, 24, 11, 19, 80, 3, 0, | ||
| 261 | FB_VMODE_DOUBLE }, | ||
| 262 | |||
| 263 | /* 1920x1200 @ 60 Hz, 74.5 Khz hsync */ | ||
| 264 | { NULL, 60, 1920, 1200, 5177, 128, 336, 1, 38, 208, 3, | ||
| 265 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
| 266 | FB_VMODE_NONINTERLACED }, | ||
| 267 | |||
| 268 | /* 1152x768, 60 Hz, PowerBook G4 Titanium I and II */ | ||
| 269 | { NULL, 60, 1152, 768, 14047, 158, 26, 29, 3, 136, 6, | ||
| 270 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
| 271 | FB_VMODE_NONINTERLACED }, | ||
| 272 | |||
| 273 | /* 1366x768, 60 Hz, 47.403 kHz hsync, WXGA 16:9 aspect ratio */ | ||
| 274 | { NULL, 60, 1366, 768, 13806, 120, 10, 14, 3, 32, 5, 0, | ||
| 275 | FB_VMODE_NONINTERLACED }, | ||
| 276 | |||
| 277 | /* 1280x800, 60 Hz, 47.403 kHz hsync, WXGA 16:10 aspect ratio */ | ||
| 278 | { NULL, 60, 1280, 800, 12048, 200, 64, 24, 1, 136, 3, 0, | ||
| 279 | FB_VMODE_NONINTERLACED }, | ||
| 280 | |||
| 281 | /* 720x576i @ 50 Hz, 15.625 kHz hsync (PAL RGB) */ | ||
| 282 | { NULL, 50, 720, 576, 74074, 64, 16, 39, 5, 64, 5, 0, | ||
| 283 | FB_VMODE_INTERLACED }, | ||
| 284 | |||
| 285 | /* 800x520i @ 50 Hz, 15.625 kHz hsync (PAL RGB) */ | ||
| 286 | { NULL, 50, 800, 520, 58823, 144, 64, 72, 28, 80, 5, 0, | ||
| 287 | FB_VMODE_INTERLACED }, | ||
| 288 | |||
| 289 | /* 864x480 @ 60 Hz, 35.15 kHz hsync */ | ||
| 290 | { NULL, 60, 864, 480, 27777, 1, 1, 1, 1, 0, 0, | ||
| 291 | 0, FB_VMODE_NONINTERLACED }, | ||
| 292 | }; | ||
| 293 | |||
| 294 | #ifdef CONFIG_FB_MODE_HELPERS | ||
| 295 | const struct fb_videomode cea_modes[64] = { | ||
| 296 | /* #1: 640x480p@59.94/60Hz */ | ||
| 297 | [1] = { | ||
| 298 | NULL, 60, 640, 480, 39722, 48, 16, 33, 10, 96, 2, 0, | ||
| 299 | FB_VMODE_NONINTERLACED, 0, | ||
| 300 | }, | ||
| 301 | /* #3: 720x480p@59.94/60Hz */ | ||
| 302 | [3] = { | ||
| 303 | NULL, 60, 720, 480, 37037, 60, 16, 30, 9, 62, 6, 0, | ||
| 304 | FB_VMODE_NONINTERLACED, 0, | ||
| 305 | }, | ||
| 306 | /* #5: 1920x1080i@59.94/60Hz */ | ||
| 307 | [5] = { | ||
| 308 | NULL, 60, 1920, 1080, 13763, 148, 88, 15, 2, 44, 5, | ||
| 309 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
| 310 | FB_VMODE_INTERLACED, 0, | ||
| 311 | }, | ||
| 312 | /* #7: 720(1440)x480iH@59.94/60Hz */ | ||
| 313 | [7] = { | ||
| 314 | NULL, 60, 1440, 480, 18554/*37108*/, 114, 38, 15, 4, 124, 3, 0, | ||
| 315 | FB_VMODE_INTERLACED, 0, | ||
| 316 | }, | ||
| 317 | /* #9: 720(1440)x240pH@59.94/60Hz */ | ||
| 318 | [9] = { | ||
| 319 | NULL, 60, 1440, 240, 18554, 114, 38, 16, 4, 124, 3, 0, | ||
| 320 | FB_VMODE_NONINTERLACED, 0, | ||
| 321 | }, | ||
| 322 | /* #18: 720x576pH@50Hz */ | ||
| 323 | [18] = { | ||
| 324 | NULL, 50, 720, 576, 37037, 68, 12, 39, 5, 64, 5, 0, | ||
| 325 | FB_VMODE_NONINTERLACED, 0, | ||
| 326 | }, | ||
| 327 | /* #19: 1280x720p@50Hz */ | ||
| 328 | [19] = { | ||
| 329 | NULL, 50, 1280, 720, 13468, 220, 440, 20, 5, 40, 5, | ||
| 330 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
| 331 | FB_VMODE_NONINTERLACED, 0, | ||
| 332 | }, | ||
| 333 | /* #20: 1920x1080i@50Hz */ | ||
| 334 | [20] = { | ||
| 335 | NULL, 50, 1920, 1080, 13480, 148, 528, 15, 5, 528, 5, | ||
| 336 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
| 337 | FB_VMODE_INTERLACED, 0, | ||
| 338 | }, | ||
| 339 | /* #32: 1920x1080p@23.98/24Hz */ | ||
| 340 | [32] = { | ||
| 341 | NULL, 24, 1920, 1080, 13468, 148, 638, 36, 4, 44, 5, | ||
| 342 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
| 343 | FB_VMODE_NONINTERLACED, 0, | ||
| 344 | }, | ||
| 345 | /* #35: (2880)x480p4x@59.94/60Hz */ | ||
| 346 | [35] = { | ||
| 347 | NULL, 60, 2880, 480, 9250, 240, 64, 30, 9, 248, 6, 0, | ||
| 348 | FB_VMODE_NONINTERLACED, 0, | ||
| 349 | }, | ||
| 350 | }; | ||
| 351 | |||
| 352 | const struct fb_videomode vesa_modes[] = { | ||
| 353 | /* 0 640x350-85 VESA */ | ||
| 354 | { NULL, 85, 640, 350, 31746, 96, 32, 60, 32, 64, 3, | ||
| 355 | FB_SYNC_HOR_HIGH_ACT, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA}, | ||
| 356 | /* 1 640x400-85 VESA */ | ||
| 357 | { NULL, 85, 640, 400, 31746, 96, 32, 41, 01, 64, 3, | ||
| 358 | FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, | ||
| 359 | /* 2 720x400-85 VESA */ | ||
| 360 | { NULL, 85, 721, 400, 28169, 108, 36, 42, 01, 72, 3, | ||
| 361 | FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, | ||
| 362 | /* 3 640x480-60 VESA */ | ||
| 363 | { NULL, 60, 640, 480, 39682, 48, 16, 33, 10, 96, 2, | ||
| 364 | 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, | ||
| 365 | /* 4 640x480-72 VESA */ | ||
| 366 | { NULL, 72, 640, 480, 31746, 128, 24, 29, 9, 40, 2, | ||
| 367 | 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, | ||
| 368 | /* 5 640x480-75 VESA */ | ||
| 369 | { NULL, 75, 640, 480, 31746, 120, 16, 16, 01, 64, 3, | ||
| 370 | 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, | ||
| 371 | /* 6 640x480-85 VESA */ | ||
| 372 | { NULL, 85, 640, 480, 27777, 80, 56, 25, 01, 56, 3, | ||
| 373 | 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, | ||
| 374 | /* 7 800x600-56 VESA */ | ||
| 375 | { NULL, 56, 800, 600, 27777, 128, 24, 22, 01, 72, 2, | ||
| 376 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
| 377 | FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, | ||
| 378 | /* 8 800x600-60 VESA */ | ||
| 379 | { NULL, 60, 800, 600, 25000, 88, 40, 23, 01, 128, 4, | ||
| 380 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
| 381 | FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, | ||
| 382 | /* 9 800x600-72 VESA */ | ||
| 383 | { NULL, 72, 800, 600, 20000, 64, 56, 23, 37, 120, 6, | ||
| 384 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
| 385 | FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, | ||
| 386 | /* 10 800x600-75 VESA */ | ||
| 387 | { NULL, 75, 800, 600, 20202, 160, 16, 21, 01, 80, 3, | ||
| 388 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
| 389 | FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, | ||
| 390 | /* 11 800x600-85 VESA */ | ||
| 391 | { NULL, 85, 800, 600, 17761, 152, 32, 27, 01, 64, 3, | ||
| 392 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
| 393 | FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, | ||
| 394 | /* 12 1024x768i-43 VESA */ | ||
| 395 | { NULL, 43, 1024, 768, 22271, 56, 8, 41, 0, 176, 8, | ||
| 396 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
| 397 | FB_VMODE_INTERLACED, FB_MODE_IS_VESA }, | ||
| 398 | /* 13 1024x768-60 VESA */ | ||
| 399 | { NULL, 60, 1024, 768, 15384, 160, 24, 29, 3, 136, 6, | ||
| 400 | 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, | ||
| 401 | /* 14 1024x768-70 VESA */ | ||
| 402 | { NULL, 70, 1024, 768, 13333, 144, 24, 29, 3, 136, 6, | ||
| 403 | 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, | ||
| 404 | /* 15 1024x768-75 VESA */ | ||
| 405 | { NULL, 75, 1024, 768, 12690, 176, 16, 28, 1, 96, 3, | ||
| 406 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
| 407 | FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, | ||
| 408 | /* 16 1024x768-85 VESA */ | ||
| 409 | { NULL, 85, 1024, 768, 10582, 208, 48, 36, 1, 96, 3, | ||
| 410 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
| 411 | FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, | ||
| 412 | /* 17 1152x864-75 VESA */ | ||
| 413 | { NULL, 75, 1152, 864, 9259, 256, 64, 32, 1, 128, 3, | ||
| 414 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
| 415 | FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, | ||
| 416 | /* 18 1280x960-60 VESA */ | ||
| 417 | { NULL, 60, 1280, 960, 9259, 312, 96, 36, 1, 112, 3, | ||
| 418 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
| 419 | FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, | ||
| 420 | /* 19 1280x960-85 VESA */ | ||
| 421 | { NULL, 85, 1280, 960, 6734, 224, 64, 47, 1, 160, 3, | ||
| 422 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
| 423 | FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, | ||
| 424 | /* 20 1280x1024-60 VESA */ | ||
| 425 | { NULL, 60, 1280, 1024, 9259, 248, 48, 38, 1, 112, 3, | ||
| 426 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
| 427 | FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, | ||
| 428 | /* 21 1280x1024-75 VESA */ | ||
| 429 | { NULL, 75, 1280, 1024, 7407, 248, 16, 38, 1, 144, 3, | ||
| 430 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
| 431 | FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, | ||
| 432 | /* 22 1280x1024-85 VESA */ | ||
| 433 | { NULL, 85, 1280, 1024, 6349, 224, 64, 44, 1, 160, 3, | ||
| 434 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
| 435 | FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, | ||
| 436 | /* 23 1600x1200-60 VESA */ | ||
| 437 | { NULL, 60, 1600, 1200, 6172, 304, 64, 46, 1, 192, 3, | ||
| 438 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
| 439 | FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, | ||
| 440 | /* 24 1600x1200-65 VESA */ | ||
| 441 | { NULL, 65, 1600, 1200, 5698, 304, 64, 46, 1, 192, 3, | ||
| 442 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
| 443 | FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, | ||
| 444 | /* 25 1600x1200-70 VESA */ | ||
| 445 | { NULL, 70, 1600, 1200, 5291, 304, 64, 46, 1, 192, 3, | ||
| 446 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
| 447 | FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, | ||
| 448 | /* 26 1600x1200-75 VESA */ | ||
| 449 | { NULL, 75, 1600, 1200, 4938, 304, 64, 46, 1, 192, 3, | ||
| 450 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
| 451 | FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, | ||
| 452 | /* 27 1600x1200-85 VESA */ | ||
| 453 | { NULL, 85, 1600, 1200, 4357, 304, 64, 46, 1, 192, 3, | ||
| 454 | FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
| 455 | FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, | ||
| 456 | /* 28 1792x1344-60 VESA */ | ||
| 457 | { NULL, 60, 1792, 1344, 4882, 328, 128, 46, 1, 200, 3, | ||
| 458 | FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, | ||
| 459 | /* 29 1792x1344-75 VESA */ | ||
| 460 | { NULL, 75, 1792, 1344, 3831, 352, 96, 69, 1, 216, 3, | ||
| 461 | FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, | ||
| 462 | /* 30 1856x1392-60 VESA */ | ||
| 463 | { NULL, 60, 1856, 1392, 4580, 352, 96, 43, 1, 224, 3, | ||
| 464 | FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, | ||
| 465 | /* 31 1856x1392-75 VESA */ | ||
| 466 | { NULL, 75, 1856, 1392, 3472, 352, 128, 104, 1, 224, 3, | ||
| 467 | FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, | ||
| 468 | /* 32 1920x1440-60 VESA */ | ||
| 469 | { NULL, 60, 1920, 1440, 4273, 344, 128, 56, 1, 200, 3, | ||
| 470 | FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, | ||
| 471 | /* 33 1920x1440-75 VESA */ | ||
| 472 | { NULL, 75, 1920, 1440, 3367, 352, 144, 56, 1, 224, 3, | ||
| 473 | FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, | ||
| 474 | }; | ||
| 475 | EXPORT_SYMBOL(vesa_modes); | ||
| 476 | #endif /* CONFIG_FB_MODE_HELPERS */ | ||
| 477 | |||
| 478 | /** | ||
| 479 | * fb_try_mode - test a video mode | ||
| 480 | * @var: frame buffer user defined part of display | ||
| 481 | * @info: frame buffer info structure | ||
| 482 | * @mode: frame buffer video mode structure | ||
| 483 | * @bpp: color depth in bits per pixel | ||
| 484 | * | ||
| 485 | * Tries a video mode to test it's validity for device @info. | ||
| 486 | * | ||
| 487 | * Returns 1 on success. | ||
| 488 | * | ||
| 489 | */ | ||
| 490 | |||
| 491 | static int fb_try_mode(struct fb_var_screeninfo *var, struct fb_info *info, | ||
| 492 | const struct fb_videomode *mode, unsigned int bpp) | ||
| 493 | { | ||
| 494 | int err = 0; | ||
| 495 | |||
| 496 | DPRINTK("Trying mode %s %dx%d-%d@%d\n", | ||
| 497 | mode->name ? mode->name : "noname", | ||
| 498 | mode->xres, mode->yres, bpp, mode->refresh); | ||
| 499 | var->xres = mode->xres; | ||
| 500 | var->yres = mode->yres; | ||
| 501 | var->xres_virtual = mode->xres; | ||
| 502 | var->yres_virtual = mode->yres; | ||
| 503 | var->xoffset = 0; | ||
| 504 | var->yoffset = 0; | ||
| 505 | var->bits_per_pixel = bpp; | ||
| 506 | var->activate |= FB_ACTIVATE_TEST; | ||
| 507 | var->pixclock = mode->pixclock; | ||
| 508 | var->left_margin = mode->left_margin; | ||
| 509 | var->right_margin = mode->right_margin; | ||
| 510 | var->upper_margin = mode->upper_margin; | ||
| 511 | var->lower_margin = mode->lower_margin; | ||
| 512 | var->hsync_len = mode->hsync_len; | ||
| 513 | var->vsync_len = mode->vsync_len; | ||
| 514 | var->sync = mode->sync; | ||
| 515 | var->vmode = mode->vmode; | ||
| 516 | if (info->fbops->fb_check_var) | ||
| 517 | err = info->fbops->fb_check_var(var, info); | ||
| 518 | var->activate &= ~FB_ACTIVATE_TEST; | ||
| 519 | return err; | ||
| 520 | } | ||
| 521 | |||
| 522 | /** | ||
| 523 | * fb_find_mode - finds a valid video mode | ||
| 524 | * @var: frame buffer user defined part of display | ||
| 525 | * @info: frame buffer info structure | ||
| 526 | * @mode_option: string video mode to find | ||
| 527 | * @db: video mode database | ||
| 528 | * @dbsize: size of @db | ||
| 529 | * @default_mode: default video mode to fall back to | ||
| 530 | * @default_bpp: default color depth in bits per pixel | ||
| 531 | * | ||
| 532 | * Finds a suitable video mode, starting with the specified mode | ||
| 533 | * in @mode_option with fallback to @default_mode. If | ||
| 534 | * @default_mode fails, all modes in the video mode database will | ||
| 535 | * be tried. | ||
| 536 | * | ||
| 537 | * Valid mode specifiers for @mode_option: | ||
| 538 | * | ||
| 539 | * <xres>x<yres>[M][R][-<bpp>][@<refresh>][i][m] or | ||
| 540 | * <name>[-<bpp>][@<refresh>] | ||
| 541 | * | ||
| 542 | * with <xres>, <yres>, <bpp> and <refresh> decimal numbers and | ||
| 543 | * <name> a string. | ||
| 544 | * | ||
| 545 | * If 'M' is present after yres (and before refresh/bpp if present), | ||
| 546 | * the function will compute the timings using VESA(tm) Coordinated | ||
| 547 | * Video Timings (CVT). If 'R' is present after 'M', will compute with | ||
| 548 | * reduced blanking (for flatpanels). If 'i' is present, compute | ||
| 549 | * interlaced mode. If 'm' is present, add margins equal to 1.8% | ||
| 550 | * of xres rounded down to 8 pixels, and 1.8% of yres. The char | ||
| 551 | * 'i' and 'm' must be after 'M' and 'R'. Example: | ||
| 552 | * | ||
| 553 | * 1024x768MR-8@60m - Reduced blank with margins at 60Hz. | ||
| 554 | * | ||
| 555 | * NOTE: The passed struct @var is _not_ cleared! This allows you | ||
| 556 | * to supply values for e.g. the grayscale and accel_flags fields. | ||
| 557 | * | ||
| 558 | * Returns zero for failure, 1 if using specified @mode_option, | ||
| 559 | * 2 if using specified @mode_option with an ignored refresh rate, | ||
| 560 | * 3 if default mode is used, 4 if fall back to any valid mode. | ||
| 561 | * | ||
| 562 | */ | ||
| 563 | |||
| 564 | int fb_find_mode(struct fb_var_screeninfo *var, | ||
| 565 | struct fb_info *info, const char *mode_option, | ||
| 566 | const struct fb_videomode *db, unsigned int dbsize, | ||
| 567 | const struct fb_videomode *default_mode, | ||
| 568 | unsigned int default_bpp) | ||
| 569 | { | ||
| 570 | int i; | ||
| 571 | |||
| 572 | /* Set up defaults */ | ||
| 573 | if (!db) { | ||
| 574 | db = modedb; | ||
| 575 | dbsize = ARRAY_SIZE(modedb); | ||
| 576 | } | ||
| 577 | |||
| 578 | if (!default_mode) | ||
| 579 | default_mode = &db[0]; | ||
| 580 | |||
| 581 | if (!default_bpp) | ||
| 582 | default_bpp = 8; | ||
| 583 | |||
| 584 | /* Did the user specify a video mode? */ | ||
| 585 | if (!mode_option) | ||
| 586 | mode_option = fb_mode_option; | ||
| 587 | if (mode_option) { | ||
| 588 | const char *name = mode_option; | ||
| 589 | unsigned int namelen = strlen(name); | ||
| 590 | int res_specified = 0, bpp_specified = 0, refresh_specified = 0; | ||
| 591 | unsigned int xres = 0, yres = 0, bpp = default_bpp, refresh = 0; | ||
| 592 | int yres_specified = 0, cvt = 0, rb = 0, interlace = 0; | ||
| 593 | int margins = 0; | ||
| 594 | u32 best, diff, tdiff; | ||
| 595 | |||
| 596 | for (i = namelen-1; i >= 0; i--) { | ||
| 597 | switch (name[i]) { | ||
| 598 | case '@': | ||
| 599 | namelen = i; | ||
| 600 | if (!refresh_specified && !bpp_specified && | ||
| 601 | !yres_specified) { | ||
| 602 | refresh = simple_strtol(&name[i+1], NULL, | ||
| 603 | 10); | ||
| 604 | refresh_specified = 1; | ||
| 605 | if (cvt || rb) | ||
| 606 | cvt = 0; | ||
| 607 | } else | ||
| 608 | goto done; | ||
| 609 | break; | ||
| 610 | case '-': | ||
| 611 | namelen = i; | ||
| 612 | if (!bpp_specified && !yres_specified) { | ||
| 613 | bpp = simple_strtol(&name[i+1], NULL, | ||
| 614 | 10); | ||
| 615 | bpp_specified = 1; | ||
| 616 | if (cvt || rb) | ||
| 617 | cvt = 0; | ||
| 618 | } else | ||
| 619 | goto done; | ||
| 620 | break; | ||
| 621 | case 'x': | ||
| 622 | if (!yres_specified) { | ||
| 623 | yres = simple_strtol(&name[i+1], NULL, | ||
| 624 | 10); | ||
| 625 | yres_specified = 1; | ||
| 626 | } else | ||
| 627 | goto done; | ||
| 628 | break; | ||
| 629 | case '0' ... '9': | ||
| 630 | break; | ||
| 631 | case 'M': | ||
| 632 | if (!yres_specified) | ||
| 633 | cvt = 1; | ||
| 634 | break; | ||
| 635 | case 'R': | ||
| 636 | if (!cvt) | ||
| 637 | rb = 1; | ||
| 638 | break; | ||
| 639 | case 'm': | ||
| 640 | if (!cvt) | ||
| 641 | margins = 1; | ||
| 642 | break; | ||
| 643 | case 'i': | ||
| 644 | if (!cvt) | ||
| 645 | interlace = 1; | ||
| 646 | break; | ||
| 647 | default: | ||
| 648 | goto done; | ||
| 649 | } | ||
| 650 | } | ||
| 651 | if (i < 0 && yres_specified) { | ||
| 652 | xres = simple_strtol(name, NULL, 10); | ||
| 653 | res_specified = 1; | ||
| 654 | } | ||
| 655 | done: | ||
| 656 | if (cvt) { | ||
| 657 | struct fb_videomode cvt_mode; | ||
| 658 | int ret; | ||
| 659 | |||
| 660 | DPRINTK("CVT mode %dx%d@%dHz%s%s%s\n", xres, yres, | ||
| 661 | (refresh) ? refresh : 60, | ||
| 662 | (rb) ? " reduced blanking" : "", | ||
| 663 | (margins) ? " with margins" : "", | ||
| 664 | (interlace) ? " interlaced" : ""); | ||
| 665 | |||
| 666 | memset(&cvt_mode, 0, sizeof(cvt_mode)); | ||
| 667 | cvt_mode.xres = xres; | ||
| 668 | cvt_mode.yres = yres; | ||
| 669 | cvt_mode.refresh = (refresh) ? refresh : 60; | ||
| 670 | |||
| 671 | if (interlace) | ||
| 672 | cvt_mode.vmode |= FB_VMODE_INTERLACED; | ||
| 673 | else | ||
| 674 | cvt_mode.vmode &= ~FB_VMODE_INTERLACED; | ||
| 675 | |||
| 676 | ret = fb_find_mode_cvt(&cvt_mode, margins, rb); | ||
| 677 | |||
| 678 | if (!ret && !fb_try_mode(var, info, &cvt_mode, bpp)) { | ||
| 679 | DPRINTK("modedb CVT: CVT mode ok\n"); | ||
| 680 | return 1; | ||
| 681 | } | ||
| 682 | |||
| 683 | DPRINTK("CVT mode invalid, getting mode from database\n"); | ||
| 684 | } | ||
| 685 | |||
| 686 | DPRINTK("Trying specified video mode%s %ix%i\n", | ||
| 687 | refresh_specified ? "" : " (ignoring refresh rate)", | ||
| 688 | xres, yres); | ||
| 689 | |||
| 690 | if (!refresh_specified) { | ||
| 691 | /* | ||
| 692 | * If the caller has provided a custom mode database and | ||
| 693 | * a valid monspecs structure, we look for the mode with | ||
| 694 | * the highest refresh rate. Otherwise we play it safe | ||
| 695 | * it and try to find a mode with a refresh rate closest | ||
| 696 | * to the standard 60 Hz. | ||
| 697 | */ | ||
| 698 | if (db != modedb && | ||
| 699 | info->monspecs.vfmin && info->monspecs.vfmax && | ||
| 700 | info->monspecs.hfmin && info->monspecs.hfmax && | ||
| 701 | info->monspecs.dclkmax) { | ||
| 702 | refresh = 1000; | ||
| 703 | } else { | ||
| 704 | refresh = 60; | ||
| 705 | } | ||
| 706 | } | ||
| 707 | |||
| 708 | diff = -1; | ||
| 709 | best = -1; | ||
| 710 | for (i = 0; i < dbsize; i++) { | ||
| 711 | if ((name_matches(db[i], name, namelen) || | ||
| 712 | (res_specified && res_matches(db[i], xres, yres))) && | ||
| 713 | !fb_try_mode(var, info, &db[i], bpp)) { | ||
| 714 | if (refresh_specified && db[i].refresh == refresh) | ||
| 715 | return 1; | ||
| 716 | |||
| 717 | if (abs(db[i].refresh - refresh) < diff) { | ||
| 718 | diff = abs(db[i].refresh - refresh); | ||
| 719 | best = i; | ||
| 720 | } | ||
| 721 | } | ||
| 722 | } | ||
| 723 | if (best != -1) { | ||
| 724 | fb_try_mode(var, info, &db[best], bpp); | ||
| 725 | return (refresh_specified) ? 2 : 1; | ||
| 726 | } | ||
| 727 | |||
| 728 | diff = 2 * (xres + yres); | ||
| 729 | best = -1; | ||
| 730 | DPRINTK("Trying best-fit modes\n"); | ||
| 731 | for (i = 0; i < dbsize; i++) { | ||
| 732 | DPRINTK("Trying %ix%i\n", db[i].xres, db[i].yres); | ||
| 733 | if (!fb_try_mode(var, info, &db[i], bpp)) { | ||
| 734 | tdiff = abs(db[i].xres - xres) + | ||
| 735 | abs(db[i].yres - yres); | ||
| 736 | |||
| 737 | /* | ||
| 738 | * Penalize modes with resolutions smaller | ||
| 739 | * than requested. | ||
| 740 | */ | ||
| 741 | if (xres > db[i].xres || yres > db[i].yres) | ||
| 742 | tdiff += xres + yres; | ||
| 743 | |||
| 744 | if (diff > tdiff) { | ||
| 745 | diff = tdiff; | ||
| 746 | best = i; | ||
| 747 | } | ||
| 748 | } | ||
| 749 | } | ||
| 750 | if (best != -1) { | ||
| 751 | fb_try_mode(var, info, &db[best], bpp); | ||
| 752 | return 5; | ||
| 753 | } | ||
| 754 | } | ||
| 755 | |||
| 756 | DPRINTK("Trying default video mode\n"); | ||
| 757 | if (!fb_try_mode(var, info, default_mode, default_bpp)) | ||
| 758 | return 3; | ||
| 759 | |||
| 760 | DPRINTK("Trying all modes\n"); | ||
| 761 | for (i = 0; i < dbsize; i++) | ||
| 762 | if (!fb_try_mode(var, info, &db[i], default_bpp)) | ||
| 763 | return 4; | ||
| 764 | |||
| 765 | DPRINTK("No valid mode found\n"); | ||
| 766 | return 0; | ||
| 767 | } | ||
| 768 | |||
| 769 | /** | ||
| 770 | * fb_var_to_videomode - convert fb_var_screeninfo to fb_videomode | ||
| 771 | * @mode: pointer to struct fb_videomode | ||
| 772 | * @var: pointer to struct fb_var_screeninfo | ||
| 773 | */ | ||
| 774 | void fb_var_to_videomode(struct fb_videomode *mode, | ||
| 775 | const struct fb_var_screeninfo *var) | ||
| 776 | { | ||
| 777 | u32 pixclock, hfreq, htotal, vtotal; | ||
| 778 | |||
| 779 | mode->name = NULL; | ||
| 780 | mode->xres = var->xres; | ||
| 781 | mode->yres = var->yres; | ||
| 782 | mode->pixclock = var->pixclock; | ||
| 783 | mode->hsync_len = var->hsync_len; | ||
| 784 | mode->vsync_len = var->vsync_len; | ||
| 785 | mode->left_margin = var->left_margin; | ||
| 786 | mode->right_margin = var->right_margin; | ||
| 787 | mode->upper_margin = var->upper_margin; | ||
| 788 | mode->lower_margin = var->lower_margin; | ||
| 789 | mode->sync = var->sync; | ||
| 790 | mode->vmode = var->vmode & FB_VMODE_MASK; | ||
| 791 | mode->flag = FB_MODE_IS_FROM_VAR; | ||
| 792 | mode->refresh = 0; | ||
| 793 | |||
| 794 | if (!var->pixclock) | ||
| 795 | return; | ||
| 796 | |||
| 797 | pixclock = PICOS2KHZ(var->pixclock) * 1000; | ||
| 798 | |||
| 799 | htotal = var->xres + var->right_margin + var->hsync_len + | ||
| 800 | var->left_margin; | ||
| 801 | vtotal = var->yres + var->lower_margin + var->vsync_len + | ||
| 802 | var->upper_margin; | ||
| 803 | |||
| 804 | if (var->vmode & FB_VMODE_INTERLACED) | ||
| 805 | vtotal /= 2; | ||
| 806 | if (var->vmode & FB_VMODE_DOUBLE) | ||
| 807 | vtotal *= 2; | ||
| 808 | |||
| 809 | hfreq = pixclock/htotal; | ||
| 810 | mode->refresh = hfreq/vtotal; | ||
| 811 | } | ||
| 812 | |||
| 813 | /** | ||
| 814 | * fb_videomode_to_var - convert fb_videomode to fb_var_screeninfo | ||
| 815 | * @var: pointer to struct fb_var_screeninfo | ||
| 816 | * @mode: pointer to struct fb_videomode | ||
| 817 | */ | ||
| 818 | void fb_videomode_to_var(struct fb_var_screeninfo *var, | ||
| 819 | const struct fb_videomode *mode) | ||
| 820 | { | ||
| 821 | var->xres = mode->xres; | ||
| 822 | var->yres = mode->yres; | ||
| 823 | var->xres_virtual = mode->xres; | ||
| 824 | var->yres_virtual = mode->yres; | ||
| 825 | var->xoffset = 0; | ||
| 826 | var->yoffset = 0; | ||
| 827 | var->pixclock = mode->pixclock; | ||
| 828 | var->left_margin = mode->left_margin; | ||
| 829 | var->right_margin = mode->right_margin; | ||
| 830 | var->upper_margin = mode->upper_margin; | ||
| 831 | var->lower_margin = mode->lower_margin; | ||
| 832 | var->hsync_len = mode->hsync_len; | ||
| 833 | var->vsync_len = mode->vsync_len; | ||
| 834 | var->sync = mode->sync; | ||
| 835 | var->vmode = mode->vmode & FB_VMODE_MASK; | ||
| 836 | } | ||
| 837 | |||
| 838 | /** | ||
| 839 | * fb_mode_is_equal - compare 2 videomodes | ||
| 840 | * @mode1: first videomode | ||
| 841 | * @mode2: second videomode | ||
| 842 | * | ||
| 843 | * RETURNS: | ||
| 844 | * 1 if equal, 0 if not | ||
| 845 | */ | ||
| 846 | int fb_mode_is_equal(const struct fb_videomode *mode1, | ||
| 847 | const struct fb_videomode *mode2) | ||
| 848 | { | ||
| 849 | return (mode1->xres == mode2->xres && | ||
| 850 | mode1->yres == mode2->yres && | ||
| 851 | mode1->pixclock == mode2->pixclock && | ||
| 852 | mode1->hsync_len == mode2->hsync_len && | ||
| 853 | mode1->vsync_len == mode2->vsync_len && | ||
| 854 | mode1->left_margin == mode2->left_margin && | ||
| 855 | mode1->right_margin == mode2->right_margin && | ||
| 856 | mode1->upper_margin == mode2->upper_margin && | ||
| 857 | mode1->lower_margin == mode2->lower_margin && | ||
| 858 | mode1->sync == mode2->sync && | ||
| 859 | mode1->vmode == mode2->vmode); | ||
| 860 | } | ||
| 861 | |||
| 862 | /** | ||
| 863 | * fb_find_best_mode - find best matching videomode | ||
| 864 | * @var: pointer to struct fb_var_screeninfo | ||
| 865 | * @head: pointer to struct list_head of modelist | ||
| 866 | * | ||
| 867 | * RETURNS: | ||
| 868 | * struct fb_videomode, NULL if none found | ||
| 869 | * | ||
| 870 | * IMPORTANT: | ||
| 871 | * This function assumes that all modelist entries in | ||
| 872 | * info->modelist are valid. | ||
| 873 | * | ||
| 874 | * NOTES: | ||
| 875 | * Finds best matching videomode which has an equal or greater dimension than | ||
| 876 | * var->xres and var->yres. If more than 1 videomode is found, will return | ||
| 877 | * the videomode with the highest refresh rate | ||
| 878 | */ | ||
| 879 | const struct fb_videomode *fb_find_best_mode(const struct fb_var_screeninfo *var, | ||
| 880 | struct list_head *head) | ||
| 881 | { | ||
| 882 | struct list_head *pos; | ||
| 883 | struct fb_modelist *modelist; | ||
| 884 | struct fb_videomode *mode, *best = NULL; | ||
| 885 | u32 diff = -1; | ||
| 886 | |||
| 887 | list_for_each(pos, head) { | ||
| 888 | u32 d; | ||
| 889 | |||
| 890 | modelist = list_entry(pos, struct fb_modelist, list); | ||
| 891 | mode = &modelist->mode; | ||
| 892 | |||
| 893 | if (mode->xres >= var->xres && mode->yres >= var->yres) { | ||
| 894 | d = (mode->xres - var->xres) + | ||
| 895 | (mode->yres - var->yres); | ||
| 896 | if (diff > d) { | ||
| 897 | diff = d; | ||
| 898 | best = mode; | ||
| 899 | } else if (diff == d && best && | ||
| 900 | mode->refresh > best->refresh) | ||
| 901 | best = mode; | ||
| 902 | } | ||
| 903 | } | ||
| 904 | return best; | ||
| 905 | } | ||
| 906 | |||
| 907 | /** | ||
| 908 | * fb_find_nearest_mode - find closest videomode | ||
| 909 | * | ||
| 910 | * @mode: pointer to struct fb_videomode | ||
| 911 | * @head: pointer to modelist | ||
| 912 | * | ||
| 913 | * Finds best matching videomode, smaller or greater in dimension. | ||
| 914 | * If more than 1 videomode is found, will return the videomode with | ||
| 915 | * the closest refresh rate. | ||
| 916 | */ | ||
| 917 | const struct fb_videomode *fb_find_nearest_mode(const struct fb_videomode *mode, | ||
| 918 | struct list_head *head) | ||
| 919 | { | ||
| 920 | struct list_head *pos; | ||
| 921 | struct fb_modelist *modelist; | ||
| 922 | struct fb_videomode *cmode, *best = NULL; | ||
| 923 | u32 diff = -1, diff_refresh = -1; | ||
| 924 | |||
| 925 | list_for_each(pos, head) { | ||
| 926 | u32 d; | ||
| 927 | |||
| 928 | modelist = list_entry(pos, struct fb_modelist, list); | ||
| 929 | cmode = &modelist->mode; | ||
| 930 | |||
| 931 | d = abs(cmode->xres - mode->xres) + | ||
| 932 | abs(cmode->yres - mode->yres); | ||
| 933 | if (diff > d) { | ||
| 934 | diff = d; | ||
| 935 | diff_refresh = abs(cmode->refresh - mode->refresh); | ||
| 936 | best = cmode; | ||
| 937 | } else if (diff == d) { | ||
| 938 | d = abs(cmode->refresh - mode->refresh); | ||
| 939 | if (diff_refresh > d) { | ||
| 940 | diff_refresh = d; | ||
| 941 | best = cmode; | ||
| 942 | } | ||
| 943 | } | ||
| 944 | } | ||
| 945 | |||
| 946 | return best; | ||
| 947 | } | ||
| 948 | |||
| 949 | /** | ||
| 950 | * fb_match_mode - find a videomode which exactly matches the timings in var | ||
| 951 | * @var: pointer to struct fb_var_screeninfo | ||
| 952 | * @head: pointer to struct list_head of modelist | ||
| 953 | * | ||
| 954 | * RETURNS: | ||
| 955 | * struct fb_videomode, NULL if none found | ||
| 956 | */ | ||
| 957 | const struct fb_videomode *fb_match_mode(const struct fb_var_screeninfo *var, | ||
| 958 | struct list_head *head) | ||
| 959 | { | ||
| 960 | struct list_head *pos; | ||
| 961 | struct fb_modelist *modelist; | ||
| 962 | struct fb_videomode *m, mode; | ||
| 963 | |||
| 964 | fb_var_to_videomode(&mode, var); | ||
| 965 | list_for_each(pos, head) { | ||
| 966 | modelist = list_entry(pos, struct fb_modelist, list); | ||
| 967 | m = &modelist->mode; | ||
| 968 | if (fb_mode_is_equal(m, &mode)) | ||
| 969 | return m; | ||
| 970 | } | ||
| 971 | return NULL; | ||
| 972 | } | ||
| 973 | |||
| 974 | /** | ||
| 975 | * fb_add_videomode - adds videomode entry to modelist | ||
| 976 | * @mode: videomode to add | ||
| 977 | * @head: struct list_head of modelist | ||
| 978 | * | ||
| 979 | * NOTES: | ||
| 980 | * Will only add unmatched mode entries | ||
| 981 | */ | ||
| 982 | int fb_add_videomode(const struct fb_videomode *mode, struct list_head *head) | ||
| 983 | { | ||
| 984 | struct list_head *pos; | ||
| 985 | struct fb_modelist *modelist; | ||
| 986 | struct fb_videomode *m; | ||
| 987 | int found = 0; | ||
| 988 | |||
| 989 | list_for_each(pos, head) { | ||
| 990 | modelist = list_entry(pos, struct fb_modelist, list); | ||
| 991 | m = &modelist->mode; | ||
| 992 | if (fb_mode_is_equal(m, mode)) { | ||
| 993 | found = 1; | ||
| 994 | break; | ||
| 995 | } | ||
| 996 | } | ||
| 997 | if (!found) { | ||
| 998 | modelist = kmalloc(sizeof(struct fb_modelist), | ||
| 999 | GFP_KERNEL); | ||
| 1000 | |||
| 1001 | if (!modelist) | ||
| 1002 | return -ENOMEM; | ||
| 1003 | modelist->mode = *mode; | ||
| 1004 | list_add(&modelist->list, head); | ||
| 1005 | } | ||
| 1006 | return 0; | ||
| 1007 | } | ||
| 1008 | |||
| 1009 | /** | ||
| 1010 | * fb_delete_videomode - removed videomode entry from modelist | ||
| 1011 | * @mode: videomode to remove | ||
| 1012 | * @head: struct list_head of modelist | ||
| 1013 | * | ||
| 1014 | * NOTES: | ||
| 1015 | * Will remove all matching mode entries | ||
| 1016 | */ | ||
| 1017 | void fb_delete_videomode(const struct fb_videomode *mode, | ||
| 1018 | struct list_head *head) | ||
| 1019 | { | ||
| 1020 | struct list_head *pos, *n; | ||
| 1021 | struct fb_modelist *modelist; | ||
| 1022 | struct fb_videomode *m; | ||
| 1023 | |||
| 1024 | list_for_each_safe(pos, n, head) { | ||
| 1025 | modelist = list_entry(pos, struct fb_modelist, list); | ||
| 1026 | m = &modelist->mode; | ||
| 1027 | if (fb_mode_is_equal(m, mode)) { | ||
| 1028 | list_del(pos); | ||
| 1029 | kfree(pos); | ||
| 1030 | } | ||
| 1031 | } | ||
| 1032 | } | ||
| 1033 | |||
| 1034 | /** | ||
| 1035 | * fb_destroy_modelist - destroy modelist | ||
| 1036 | * @head: struct list_head of modelist | ||
| 1037 | */ | ||
| 1038 | void fb_destroy_modelist(struct list_head *head) | ||
| 1039 | { | ||
| 1040 | struct list_head *pos, *n; | ||
| 1041 | |||
| 1042 | list_for_each_safe(pos, n, head) { | ||
| 1043 | list_del(pos); | ||
| 1044 | kfree(pos); | ||
| 1045 | } | ||
| 1046 | } | ||
| 1047 | EXPORT_SYMBOL_GPL(fb_destroy_modelist); | ||
| 1048 | |||
| 1049 | /** | ||
| 1050 | * fb_videomode_to_modelist - convert mode array to mode list | ||
| 1051 | * @modedb: array of struct fb_videomode | ||
| 1052 | * @num: number of entries in array | ||
| 1053 | * @head: struct list_head of modelist | ||
| 1054 | */ | ||
| 1055 | void fb_videomode_to_modelist(const struct fb_videomode *modedb, int num, | ||
| 1056 | struct list_head *head) | ||
| 1057 | { | ||
| 1058 | int i; | ||
| 1059 | |||
| 1060 | INIT_LIST_HEAD(head); | ||
| 1061 | |||
| 1062 | for (i = 0; i < num; i++) { | ||
| 1063 | if (fb_add_videomode(&modedb[i], head)) | ||
| 1064 | return; | ||
| 1065 | } | ||
| 1066 | } | ||
| 1067 | |||
| 1068 | const struct fb_videomode *fb_find_best_display(const struct fb_monspecs *specs, | ||
| 1069 | struct list_head *head) | ||
| 1070 | { | ||
| 1071 | struct list_head *pos; | ||
| 1072 | struct fb_modelist *modelist; | ||
| 1073 | const struct fb_videomode *m, *m1 = NULL, *md = NULL, *best = NULL; | ||
| 1074 | int first = 0; | ||
| 1075 | |||
| 1076 | if (!head->prev || !head->next || list_empty(head)) | ||
| 1077 | goto finished; | ||
| 1078 | |||
| 1079 | /* get the first detailed mode and the very first mode */ | ||
| 1080 | list_for_each(pos, head) { | ||
| 1081 | modelist = list_entry(pos, struct fb_modelist, list); | ||
| 1082 | m = &modelist->mode; | ||
| 1083 | |||
| 1084 | if (!first) { | ||
| 1085 | m1 = m; | ||
| 1086 | first = 1; | ||
| 1087 | } | ||
| 1088 | |||
| 1089 | if (m->flag & FB_MODE_IS_FIRST) { | ||
| 1090 | md = m; | ||
| 1091 | break; | ||
| 1092 | } | ||
| 1093 | } | ||
| 1094 | |||
| 1095 | /* first detailed timing is preferred */ | ||
| 1096 | if (specs->misc & FB_MISC_1ST_DETAIL) { | ||
| 1097 | best = md; | ||
| 1098 | goto finished; | ||
| 1099 | } | ||
| 1100 | |||
| 1101 | /* find best mode based on display width and height */ | ||
| 1102 | if (specs->max_x && specs->max_y) { | ||
| 1103 | struct fb_var_screeninfo var; | ||
| 1104 | |||
| 1105 | memset(&var, 0, sizeof(struct fb_var_screeninfo)); | ||
| 1106 | var.xres = (specs->max_x * 7200)/254; | ||
| 1107 | var.yres = (specs->max_y * 7200)/254; | ||
| 1108 | m = fb_find_best_mode(&var, head); | ||
| 1109 | if (m) { | ||
| 1110 | best = m; | ||
| 1111 | goto finished; | ||
| 1112 | } | ||
| 1113 | } | ||
| 1114 | |||
| 1115 | /* use first detailed mode */ | ||
| 1116 | if (md) { | ||
| 1117 | best = md; | ||
| 1118 | goto finished; | ||
| 1119 | } | ||
| 1120 | |||
| 1121 | /* last resort, use the very first mode */ | ||
| 1122 | best = m1; | ||
| 1123 | finished: | ||
| 1124 | return best; | ||
| 1125 | } | ||
| 1126 | EXPORT_SYMBOL(fb_find_best_display); | ||
| 1127 | |||
| 1128 | EXPORT_SYMBOL(fb_videomode_to_var); | ||
| 1129 | EXPORT_SYMBOL(fb_var_to_videomode); | ||
| 1130 | EXPORT_SYMBOL(fb_mode_is_equal); | ||
| 1131 | EXPORT_SYMBOL(fb_add_videomode); | ||
| 1132 | EXPORT_SYMBOL(fb_match_mode); | ||
| 1133 | EXPORT_SYMBOL(fb_find_best_mode); | ||
| 1134 | EXPORT_SYMBOL(fb_find_nearest_mode); | ||
| 1135 | EXPORT_SYMBOL(fb_videomode_to_modelist); | ||
| 1136 | EXPORT_SYMBOL(fb_find_mode); | ||
| 1137 | EXPORT_SYMBOL(fb_find_mode_cvt); | ||
diff --git a/drivers/video/fbdev/core/svgalib.c b/drivers/video/fbdev/core/svgalib.c new file mode 100644 index 000000000000..9e01322fabe3 --- /dev/null +++ b/drivers/video/fbdev/core/svgalib.c | |||
| @@ -0,0 +1,672 @@ | |||
| 1 | /* | ||
| 2 | * Common utility functions for VGA-based graphics cards. | ||
| 3 | * | ||
| 4 | * Copyright (c) 2006-2007 Ondrej Zajicek <santiago@crfreenet.org> | ||
| 5 | * | ||
| 6 | * This file is subject to the terms and conditions of the GNU General Public | ||
| 7 | * License. See the file COPYING in the main directory of this archive for | ||
| 8 | * more details. | ||
| 9 | * | ||
| 10 | * Some parts are based on David Boucher's viafb (http://davesdomain.org.uk/viafb/) | ||
| 11 | */ | ||
| 12 | |||
| 13 | #include <linux/module.h> | ||
| 14 | #include <linux/kernel.h> | ||
| 15 | #include <linux/string.h> | ||
| 16 | #include <linux/fb.h> | ||
| 17 | #include <linux/svga.h> | ||
| 18 | #include <asm/types.h> | ||
| 19 | #include <asm/io.h> | ||
| 20 | |||
| 21 | |||
| 22 | /* Write a CRT register value spread across multiple registers */ | ||
| 23 | void svga_wcrt_multi(void __iomem *regbase, const struct vga_regset *regset, u32 value) | ||
| 24 | { | ||
| 25 | u8 regval, bitval, bitnum; | ||
| 26 | |||
| 27 | while (regset->regnum != VGA_REGSET_END_VAL) { | ||
| 28 | regval = vga_rcrt(regbase, regset->regnum); | ||
| 29 | bitnum = regset->lowbit; | ||
| 30 | while (bitnum <= regset->highbit) { | ||
| 31 | bitval = 1 << bitnum; | ||
| 32 | regval = regval & ~bitval; | ||
| 33 | if (value & 1) regval = regval | bitval; | ||
| 34 | bitnum ++; | ||
| 35 | value = value >> 1; | ||
| 36 | } | ||
| 37 | vga_wcrt(regbase, regset->regnum, regval); | ||
| 38 | regset ++; | ||
| 39 | } | ||
| 40 | } | ||
| 41 | |||
| 42 | /* Write a sequencer register value spread across multiple registers */ | ||
| 43 | void svga_wseq_multi(void __iomem *regbase, const struct vga_regset *regset, u32 value) | ||
| 44 | { | ||
| 45 | u8 regval, bitval, bitnum; | ||
| 46 | |||
| 47 | while (regset->regnum != VGA_REGSET_END_VAL) { | ||
| 48 | regval = vga_rseq(regbase, regset->regnum); | ||
| 49 | bitnum = regset->lowbit; | ||
| 50 | while (bitnum <= regset->highbit) { | ||
| 51 | bitval = 1 << bitnum; | ||
| 52 | regval = regval & ~bitval; | ||
| 53 | if (value & 1) regval = regval | bitval; | ||
| 54 | bitnum ++; | ||
| 55 | value = value >> 1; | ||
| 56 | } | ||
| 57 | vga_wseq(regbase, regset->regnum, regval); | ||
| 58 | regset ++; | ||
| 59 | } | ||
| 60 | } | ||
| 61 | |||
| 62 | static unsigned int svga_regset_size(const struct vga_regset *regset) | ||
| 63 | { | ||
| 64 | u8 count = 0; | ||
| 65 | |||
| 66 | while (regset->regnum != VGA_REGSET_END_VAL) { | ||
| 67 | count += regset->highbit - regset->lowbit + 1; | ||
| 68 | regset ++; | ||
| 69 | } | ||
| 70 | return 1 << count; | ||
| 71 | } | ||
| 72 | |||
| 73 | |||
| 74 | /* ------------------------------------------------------------------------- */ | ||
| 75 | |||
| 76 | |||
| 77 | /* Set graphics controller registers to sane values */ | ||
| 78 | void svga_set_default_gfx_regs(void __iomem *regbase) | ||
| 79 | { | ||
| 80 | /* All standard GFX registers (GR00 - GR08) */ | ||
| 81 | vga_wgfx(regbase, VGA_GFX_SR_VALUE, 0x00); | ||
| 82 | vga_wgfx(regbase, VGA_GFX_SR_ENABLE, 0x00); | ||
| 83 | vga_wgfx(regbase, VGA_GFX_COMPARE_VALUE, 0x00); | ||
| 84 | vga_wgfx(regbase, VGA_GFX_DATA_ROTATE, 0x00); | ||
| 85 | vga_wgfx(regbase, VGA_GFX_PLANE_READ, 0x00); | ||
| 86 | vga_wgfx(regbase, VGA_GFX_MODE, 0x00); | ||
| 87 | /* vga_wgfx(regbase, VGA_GFX_MODE, 0x20); */ | ||
| 88 | /* vga_wgfx(regbase, VGA_GFX_MODE, 0x40); */ | ||
| 89 | vga_wgfx(regbase, VGA_GFX_MISC, 0x05); | ||
| 90 | /* vga_wgfx(regbase, VGA_GFX_MISC, 0x01); */ | ||
| 91 | vga_wgfx(regbase, VGA_GFX_COMPARE_MASK, 0x0F); | ||
| 92 | vga_wgfx(regbase, VGA_GFX_BIT_MASK, 0xFF); | ||
| 93 | } | ||
| 94 | |||
| 95 | /* Set attribute controller registers to sane values */ | ||
| 96 | void svga_set_default_atc_regs(void __iomem *regbase) | ||
| 97 | { | ||
| 98 | u8 count; | ||
| 99 | |||
| 100 | vga_r(regbase, 0x3DA); | ||
| 101 | vga_w(regbase, VGA_ATT_W, 0x00); | ||
| 102 | |||
| 103 | /* All standard ATC registers (AR00 - AR14) */ | ||
| 104 | for (count = 0; count <= 0xF; count ++) | ||
| 105 | svga_wattr(regbase, count, count); | ||
| 106 | |||
| 107 | svga_wattr(regbase, VGA_ATC_MODE, 0x01); | ||
| 108 | /* svga_wattr(regbase, VGA_ATC_MODE, 0x41); */ | ||
| 109 | svga_wattr(regbase, VGA_ATC_OVERSCAN, 0x00); | ||
| 110 | svga_wattr(regbase, VGA_ATC_PLANE_ENABLE, 0x0F); | ||
| 111 | svga_wattr(regbase, VGA_ATC_PEL, 0x00); | ||
| 112 | svga_wattr(regbase, VGA_ATC_COLOR_PAGE, 0x00); | ||
| 113 | |||
| 114 | vga_r(regbase, 0x3DA); | ||
| 115 | vga_w(regbase, VGA_ATT_W, 0x20); | ||
| 116 | } | ||
| 117 | |||
| 118 | /* Set sequencer registers to sane values */ | ||
| 119 | void svga_set_default_seq_regs(void __iomem *regbase) | ||
| 120 | { | ||
| 121 | /* Standard sequencer registers (SR01 - SR04), SR00 is not set */ | ||
| 122 | vga_wseq(regbase, VGA_SEQ_CLOCK_MODE, VGA_SR01_CHAR_CLK_8DOTS); | ||
| 123 | vga_wseq(regbase, VGA_SEQ_PLANE_WRITE, VGA_SR02_ALL_PLANES); | ||
| 124 | vga_wseq(regbase, VGA_SEQ_CHARACTER_MAP, 0x00); | ||
| 125 | /* vga_wseq(regbase, VGA_SEQ_MEMORY_MODE, VGA_SR04_EXT_MEM | VGA_SR04_SEQ_MODE | VGA_SR04_CHN_4M); */ | ||
| 126 | vga_wseq(regbase, VGA_SEQ_MEMORY_MODE, VGA_SR04_EXT_MEM | VGA_SR04_SEQ_MODE); | ||
| 127 | } | ||
| 128 | |||
| 129 | /* Set CRTC registers to sane values */ | ||
| 130 | void svga_set_default_crt_regs(void __iomem *regbase) | ||
| 131 | { | ||
| 132 | /* Standard CRT registers CR03 CR08 CR09 CR14 CR17 */ | ||
| 133 | svga_wcrt_mask(regbase, 0x03, 0x80, 0x80); /* Enable vertical retrace EVRA */ | ||
| 134 | vga_wcrt(regbase, VGA_CRTC_PRESET_ROW, 0); | ||
| 135 | svga_wcrt_mask(regbase, VGA_CRTC_MAX_SCAN, 0, 0x1F); | ||
| 136 | vga_wcrt(regbase, VGA_CRTC_UNDERLINE, 0); | ||
| 137 | vga_wcrt(regbase, VGA_CRTC_MODE, 0xE3); | ||
| 138 | } | ||
| 139 | |||
| 140 | void svga_set_textmode_vga_regs(void __iomem *regbase) | ||
| 141 | { | ||
| 142 | /* svga_wseq_mask(regbase, 0x1, 0x00, 0x01); */ /* Switch 8/9 pixel per char */ | ||
| 143 | vga_wseq(regbase, VGA_SEQ_MEMORY_MODE, VGA_SR04_EXT_MEM); | ||
| 144 | vga_wseq(regbase, VGA_SEQ_PLANE_WRITE, 0x03); | ||
| 145 | |||
| 146 | vga_wcrt(regbase, VGA_CRTC_MAX_SCAN, 0x0f); /* 0x4f */ | ||
| 147 | vga_wcrt(regbase, VGA_CRTC_UNDERLINE, 0x1f); | ||
| 148 | svga_wcrt_mask(regbase, VGA_CRTC_MODE, 0x23, 0x7f); | ||
| 149 | |||
| 150 | vga_wcrt(regbase, VGA_CRTC_CURSOR_START, 0x0d); | ||
| 151 | vga_wcrt(regbase, VGA_CRTC_CURSOR_END, 0x0e); | ||
| 152 | vga_wcrt(regbase, VGA_CRTC_CURSOR_HI, 0x00); | ||
| 153 | vga_wcrt(regbase, VGA_CRTC_CURSOR_LO, 0x00); | ||
| 154 | |||
| 155 | vga_wgfx(regbase, VGA_GFX_MODE, 0x10); /* Odd/even memory mode */ | ||
| 156 | vga_wgfx(regbase, VGA_GFX_MISC, 0x0E); /* Misc graphics register - text mode enable */ | ||
| 157 | vga_wgfx(regbase, VGA_GFX_COMPARE_MASK, 0x00); | ||
| 158 | |||
| 159 | vga_r(regbase, 0x3DA); | ||
| 160 | vga_w(regbase, VGA_ATT_W, 0x00); | ||
| 161 | |||
| 162 | svga_wattr(regbase, 0x10, 0x0C); /* Attribute Mode Control Register - text mode, blinking and line graphics */ | ||
| 163 | svga_wattr(regbase, 0x13, 0x08); /* Horizontal Pixel Panning Register */ | ||
| 164 | |||
| 165 | vga_r(regbase, 0x3DA); | ||
| 166 | vga_w(regbase, VGA_ATT_W, 0x20); | ||
| 167 | } | ||
| 168 | |||
| 169 | #if 0 | ||
| 170 | void svga_dump_var(struct fb_var_screeninfo *var, int node) | ||
| 171 | { | ||
| 172 | pr_debug("fb%d: var.vmode : 0x%X\n", node, var->vmode); | ||
| 173 | pr_debug("fb%d: var.xres : %d\n", node, var->xres); | ||
| 174 | pr_debug("fb%d: var.yres : %d\n", node, var->yres); | ||
| 175 | pr_debug("fb%d: var.bits_per_pixel: %d\n", node, var->bits_per_pixel); | ||
| 176 | pr_debug("fb%d: var.xres_virtual : %d\n", node, var->xres_virtual); | ||
| 177 | pr_debug("fb%d: var.yres_virtual : %d\n", node, var->yres_virtual); | ||
| 178 | pr_debug("fb%d: var.left_margin : %d\n", node, var->left_margin); | ||
| 179 | pr_debug("fb%d: var.right_margin : %d\n", node, var->right_margin); | ||
| 180 | pr_debug("fb%d: var.upper_margin : %d\n", node, var->upper_margin); | ||
| 181 | pr_debug("fb%d: var.lower_margin : %d\n", node, var->lower_margin); | ||
| 182 | pr_debug("fb%d: var.hsync_len : %d\n", node, var->hsync_len); | ||
| 183 | pr_debug("fb%d: var.vsync_len : %d\n", node, var->vsync_len); | ||
| 184 | pr_debug("fb%d: var.sync : 0x%X\n", node, var->sync); | ||
| 185 | pr_debug("fb%d: var.pixclock : %d\n\n", node, var->pixclock); | ||
| 186 | } | ||
| 187 | #endif /* 0 */ | ||
| 188 | |||
| 189 | |||
| 190 | /* ------------------------------------------------------------------------- */ | ||
| 191 | |||
| 192 | |||
| 193 | void svga_settile(struct fb_info *info, struct fb_tilemap *map) | ||
| 194 | { | ||
| 195 | const u8 *font = map->data; | ||
| 196 | u8 __iomem *fb = (u8 __iomem *)info->screen_base; | ||
| 197 | int i, c; | ||
| 198 | |||
| 199 | if ((map->width != 8) || (map->height != 16) || | ||
| 200 | (map->depth != 1) || (map->length != 256)) { | ||
| 201 | fb_err(info, "unsupported font parameters: width %d, height %d, depth %d, length %d\n", | ||
| 202 | map->width, map->height, map->depth, map->length); | ||
| 203 | return; | ||
| 204 | } | ||
| 205 | |||
| 206 | fb += 2; | ||
| 207 | for (c = 0; c < map->length; c++) { | ||
| 208 | for (i = 0; i < map->height; i++) { | ||
| 209 | fb_writeb(font[i], fb + i * 4); | ||
| 210 | // fb[i * 4] = font[i]; | ||
| 211 | } | ||
| 212 | fb += 128; | ||
| 213 | font += map->height; | ||
| 214 | } | ||
| 215 | } | ||
| 216 | |||
| 217 | /* Copy area in text (tileblit) mode */ | ||
| 218 | void svga_tilecopy(struct fb_info *info, struct fb_tilearea *area) | ||
| 219 | { | ||
| 220 | int dx, dy; | ||
| 221 | /* colstride is halved in this function because u16 are used */ | ||
| 222 | int colstride = 1 << (info->fix.type_aux & FB_AUX_TEXT_SVGA_MASK); | ||
| 223 | int rowstride = colstride * (info->var.xres_virtual / 8); | ||
| 224 | u16 __iomem *fb = (u16 __iomem *) info->screen_base; | ||
| 225 | u16 __iomem *src, *dst; | ||
| 226 | |||
| 227 | if ((area->sy > area->dy) || | ||
| 228 | ((area->sy == area->dy) && (area->sx > area->dx))) { | ||
| 229 | src = fb + area->sx * colstride + area->sy * rowstride; | ||
| 230 | dst = fb + area->dx * colstride + area->dy * rowstride; | ||
| 231 | } else { | ||
| 232 | src = fb + (area->sx + area->width - 1) * colstride | ||
| 233 | + (area->sy + area->height - 1) * rowstride; | ||
| 234 | dst = fb + (area->dx + area->width - 1) * colstride | ||
| 235 | + (area->dy + area->height - 1) * rowstride; | ||
| 236 | |||
| 237 | colstride = -colstride; | ||
| 238 | rowstride = -rowstride; | ||
| 239 | } | ||
| 240 | |||
| 241 | for (dy = 0; dy < area->height; dy++) { | ||
| 242 | u16 __iomem *src2 = src; | ||
| 243 | u16 __iomem *dst2 = dst; | ||
| 244 | for (dx = 0; dx < area->width; dx++) { | ||
| 245 | fb_writew(fb_readw(src2), dst2); | ||
| 246 | // *dst2 = *src2; | ||
| 247 | src2 += colstride; | ||
| 248 | dst2 += colstride; | ||
| 249 | } | ||
| 250 | src += rowstride; | ||
| 251 | dst += rowstride; | ||
| 252 | } | ||
| 253 | } | ||
| 254 | |||
| 255 | /* Fill area in text (tileblit) mode */ | ||
| 256 | void svga_tilefill(struct fb_info *info, struct fb_tilerect *rect) | ||
| 257 | { | ||
| 258 | int dx, dy; | ||
| 259 | int colstride = 2 << (info->fix.type_aux & FB_AUX_TEXT_SVGA_MASK); | ||
| 260 | int rowstride = colstride * (info->var.xres_virtual / 8); | ||
| 261 | int attr = (0x0F & rect->bg) << 4 | (0x0F & rect->fg); | ||
| 262 | u8 __iomem *fb = (u8 __iomem *)info->screen_base; | ||
| 263 | fb += rect->sx * colstride + rect->sy * rowstride; | ||
| 264 | |||
| 265 | for (dy = 0; dy < rect->height; dy++) { | ||
| 266 | u8 __iomem *fb2 = fb; | ||
| 267 | for (dx = 0; dx < rect->width; dx++) { | ||
| 268 | fb_writeb(rect->index, fb2); | ||
| 269 | fb_writeb(attr, fb2 + 1); | ||
| 270 | fb2 += colstride; | ||
| 271 | } | ||
| 272 | fb += rowstride; | ||
| 273 | } | ||
| 274 | } | ||
| 275 | |||
| 276 | /* Write text in text (tileblit) mode */ | ||
| 277 | void svga_tileblit(struct fb_info *info, struct fb_tileblit *blit) | ||
| 278 | { | ||
| 279 | int dx, dy, i; | ||
| 280 | int colstride = 2 << (info->fix.type_aux & FB_AUX_TEXT_SVGA_MASK); | ||
| 281 | int rowstride = colstride * (info->var.xres_virtual / 8); | ||
| 282 | int attr = (0x0F & blit->bg) << 4 | (0x0F & blit->fg); | ||
| 283 | u8 __iomem *fb = (u8 __iomem *)info->screen_base; | ||
| 284 | fb += blit->sx * colstride + blit->sy * rowstride; | ||
| 285 | |||
| 286 | i=0; | ||
| 287 | for (dy=0; dy < blit->height; dy ++) { | ||
| 288 | u8 __iomem *fb2 = fb; | ||
| 289 | for (dx = 0; dx < blit->width; dx ++) { | ||
| 290 | fb_writeb(blit->indices[i], fb2); | ||
| 291 | fb_writeb(attr, fb2 + 1); | ||
| 292 | fb2 += colstride; | ||
| 293 | i ++; | ||
| 294 | if (i == blit->length) return; | ||
| 295 | } | ||
| 296 | fb += rowstride; | ||
| 297 | } | ||
| 298 | |||
| 299 | } | ||
| 300 | |||
| 301 | /* Set cursor in text (tileblit) mode */ | ||
| 302 | void svga_tilecursor(void __iomem *regbase, struct fb_info *info, struct fb_tilecursor *cursor) | ||
| 303 | { | ||
| 304 | u8 cs = 0x0d; | ||
| 305 | u8 ce = 0x0e; | ||
| 306 | u16 pos = cursor->sx + (info->var.xoffset / 8) | ||
| 307 | + (cursor->sy + (info->var.yoffset / 16)) | ||
| 308 | * (info->var.xres_virtual / 8); | ||
| 309 | |||
| 310 | if (! cursor -> mode) | ||
| 311 | return; | ||
| 312 | |||
| 313 | svga_wcrt_mask(regbase, 0x0A, 0x20, 0x20); /* disable cursor */ | ||
| 314 | |||
| 315 | if (cursor -> shape == FB_TILE_CURSOR_NONE) | ||
| 316 | return; | ||
| 317 | |||
| 318 | switch (cursor -> shape) { | ||
| 319 | case FB_TILE_CURSOR_UNDERLINE: | ||
| 320 | cs = 0x0d; | ||
| 321 | break; | ||
| 322 | case FB_TILE_CURSOR_LOWER_THIRD: | ||
| 323 | cs = 0x09; | ||
| 324 | break; | ||
| 325 | case FB_TILE_CURSOR_LOWER_HALF: | ||
| 326 | cs = 0x07; | ||
| 327 | break; | ||
| 328 | case FB_TILE_CURSOR_TWO_THIRDS: | ||
| 329 | cs = 0x05; | ||
| 330 | break; | ||
| 331 | case FB_TILE_CURSOR_BLOCK: | ||
| 332 | cs = 0x01; | ||
| 333 | break; | ||
| 334 | } | ||
| 335 | |||
| 336 | /* set cursor position */ | ||
| 337 | vga_wcrt(regbase, 0x0E, pos >> 8); | ||
| 338 | vga_wcrt(regbase, 0x0F, pos & 0xFF); | ||
| 339 | |||
| 340 | vga_wcrt(regbase, 0x0B, ce); /* set cursor end */ | ||
| 341 | vga_wcrt(regbase, 0x0A, cs); /* set cursor start and enable it */ | ||
| 342 | } | ||
| 343 | |||
| 344 | int svga_get_tilemax(struct fb_info *info) | ||
| 345 | { | ||
| 346 | return 256; | ||
| 347 | } | ||
| 348 | |||
| 349 | /* Get capabilities of accelerator based on the mode */ | ||
| 350 | |||
| 351 | void svga_get_caps(struct fb_info *info, struct fb_blit_caps *caps, | ||
| 352 | struct fb_var_screeninfo *var) | ||
| 353 | { | ||
| 354 | if (var->bits_per_pixel == 0) { | ||
| 355 | /* can only support 256 8x16 bitmap */ | ||
| 356 | caps->x = 1 << (8 - 1); | ||
| 357 | caps->y = 1 << (16 - 1); | ||
| 358 | caps->len = 256; | ||
| 359 | } else { | ||
| 360 | caps->x = (var->bits_per_pixel == 4) ? 1 << (8 - 1) : ~(u32)0; | ||
| 361 | caps->y = ~(u32)0; | ||
| 362 | caps->len = ~(u32)0; | ||
| 363 | } | ||
| 364 | } | ||
| 365 | EXPORT_SYMBOL(svga_get_caps); | ||
| 366 | |||
| 367 | /* ------------------------------------------------------------------------- */ | ||
| 368 | |||
| 369 | |||
| 370 | /* | ||
| 371 | * Compute PLL settings (M, N, R) | ||
| 372 | * F_VCO = (F_BASE * M) / N | ||
| 373 | * F_OUT = F_VCO / (2^R) | ||
| 374 | */ | ||
| 375 | |||
| 376 | static inline u32 abs_diff(u32 a, u32 b) | ||
| 377 | { | ||
| 378 | return (a > b) ? (a - b) : (b - a); | ||
| 379 | } | ||
| 380 | |||
| 381 | int svga_compute_pll(const struct svga_pll *pll, u32 f_wanted, u16 *m, u16 *n, u16 *r, int node) | ||
| 382 | { | ||
| 383 | u16 am, an, ar; | ||
| 384 | u32 f_vco, f_current, delta_current, delta_best; | ||
| 385 | |||
| 386 | pr_debug("fb%d: ideal frequency: %d kHz\n", node, (unsigned int) f_wanted); | ||
| 387 | |||
| 388 | ar = pll->r_max; | ||
| 389 | f_vco = f_wanted << ar; | ||
| 390 | |||
| 391 | /* overflow check */ | ||
| 392 | if ((f_vco >> ar) != f_wanted) | ||
| 393 | return -EINVAL; | ||
| 394 | |||
| 395 | /* It is usually better to have greater VCO clock | ||
| 396 | because of better frequency stability. | ||
| 397 | So first try r_max, then r smaller. */ | ||
| 398 | while ((ar > pll->r_min) && (f_vco > pll->f_vco_max)) { | ||
| 399 | ar--; | ||
| 400 | f_vco = f_vco >> 1; | ||
| 401 | } | ||
| 402 | |||
| 403 | /* VCO bounds check */ | ||
| 404 | if ((f_vco < pll->f_vco_min) || (f_vco > pll->f_vco_max)) | ||
| 405 | return -EINVAL; | ||
| 406 | |||
| 407 | delta_best = 0xFFFFFFFF; | ||
| 408 | *m = 0; | ||
| 409 | *n = 0; | ||
| 410 | *r = ar; | ||
| 411 | |||
| 412 | am = pll->m_min; | ||
| 413 | an = pll->n_min; | ||
| 414 | |||
| 415 | while ((am <= pll->m_max) && (an <= pll->n_max)) { | ||
| 416 | f_current = (pll->f_base * am) / an; | ||
| 417 | delta_current = abs_diff (f_current, f_vco); | ||
| 418 | |||
| 419 | if (delta_current < delta_best) { | ||
| 420 | delta_best = delta_current; | ||
| 421 | *m = am; | ||
| 422 | *n = an; | ||
| 423 | } | ||
| 424 | |||
| 425 | if (f_current <= f_vco) { | ||
| 426 | am ++; | ||
| 427 | } else { | ||
| 428 | an ++; | ||
| 429 | } | ||
| 430 | } | ||
| 431 | |||
| 432 | f_current = (pll->f_base * *m) / *n; | ||
| 433 | pr_debug("fb%d: found frequency: %d kHz (VCO %d kHz)\n", node, (int) (f_current >> ar), (int) f_current); | ||
| 434 | pr_debug("fb%d: m = %d n = %d r = %d\n", node, (unsigned int) *m, (unsigned int) *n, (unsigned int) *r); | ||
| 435 | return 0; | ||
| 436 | } | ||
| 437 | |||
| 438 | |||
| 439 | /* ------------------------------------------------------------------------- */ | ||
| 440 | |||
| 441 | |||
| 442 | /* Check CRT timing values */ | ||
| 443 | int svga_check_timings(const struct svga_timing_regs *tm, struct fb_var_screeninfo *var, int node) | ||
| 444 | { | ||
| 445 | u32 value; | ||
| 446 | |||
| 447 | var->xres = (var->xres+7)&~7; | ||
| 448 | var->left_margin = (var->left_margin+7)&~7; | ||
| 449 | var->right_margin = (var->right_margin+7)&~7; | ||
| 450 | var->hsync_len = (var->hsync_len+7)&~7; | ||
| 451 | |||
| 452 | /* Check horizontal total */ | ||
| 453 | value = var->xres + var->left_margin + var->right_margin + var->hsync_len; | ||
| 454 | if (((value / 8) - 5) >= svga_regset_size (tm->h_total_regs)) | ||
| 455 | return -EINVAL; | ||
| 456 | |||
| 457 | /* Check horizontal display and blank start */ | ||
| 458 | value = var->xres; | ||
| 459 | if (((value / 8) - 1) >= svga_regset_size (tm->h_display_regs)) | ||
| 460 | return -EINVAL; | ||
| 461 | if (((value / 8) - 1) >= svga_regset_size (tm->h_blank_start_regs)) | ||
| 462 | return -EINVAL; | ||
| 463 | |||
| 464 | /* Check horizontal sync start */ | ||
| 465 | value = var->xres + var->right_margin; | ||
| 466 | if (((value / 8) - 1) >= svga_regset_size (tm->h_sync_start_regs)) | ||
| 467 | return -EINVAL; | ||
| 468 | |||
| 469 | /* Check horizontal blank end (or length) */ | ||
| 470 | value = var->left_margin + var->right_margin + var->hsync_len; | ||
| 471 | if ((value == 0) || ((value / 8) >= svga_regset_size (tm->h_blank_end_regs))) | ||
| 472 | return -EINVAL; | ||
| 473 | |||
| 474 | /* Check horizontal sync end (or length) */ | ||
| 475 | value = var->hsync_len; | ||
| 476 | if ((value == 0) || ((value / 8) >= svga_regset_size (tm->h_sync_end_regs))) | ||
| 477 | return -EINVAL; | ||
| 478 | |||
| 479 | /* Check vertical total */ | ||
| 480 | value = var->yres + var->upper_margin + var->lower_margin + var->vsync_len; | ||
| 481 | if ((value - 1) >= svga_regset_size(tm->v_total_regs)) | ||
| 482 | return -EINVAL; | ||
| 483 | |||
| 484 | /* Check vertical display and blank start */ | ||
| 485 | value = var->yres; | ||
| 486 | if ((value - 1) >= svga_regset_size(tm->v_display_regs)) | ||
| 487 | return -EINVAL; | ||
| 488 | if ((value - 1) >= svga_regset_size(tm->v_blank_start_regs)) | ||
| 489 | return -EINVAL; | ||
| 490 | |||
| 491 | /* Check vertical sync start */ | ||
| 492 | value = var->yres + var->lower_margin; | ||
| 493 | if ((value - 1) >= svga_regset_size(tm->v_sync_start_regs)) | ||
| 494 | return -EINVAL; | ||
| 495 | |||
| 496 | /* Check vertical blank end (or length) */ | ||
| 497 | value = var->upper_margin + var->lower_margin + var->vsync_len; | ||
| 498 | if ((value == 0) || (value >= svga_regset_size (tm->v_blank_end_regs))) | ||
| 499 | return -EINVAL; | ||
| 500 | |||
| 501 | /* Check vertical sync end (or length) */ | ||
| 502 | value = var->vsync_len; | ||
| 503 | if ((value == 0) || (value >= svga_regset_size (tm->v_sync_end_regs))) | ||
| 504 | return -EINVAL; | ||
| 505 | |||
| 506 | return 0; | ||
| 507 | } | ||
| 508 | |||
| 509 | /* Set CRT timing registers */ | ||
| 510 | void svga_set_timings(void __iomem *regbase, const struct svga_timing_regs *tm, | ||
| 511 | struct fb_var_screeninfo *var, | ||
| 512 | u32 hmul, u32 hdiv, u32 vmul, u32 vdiv, u32 hborder, int node) | ||
| 513 | { | ||
| 514 | u8 regval; | ||
| 515 | u32 value; | ||
| 516 | |||
| 517 | value = var->xres + var->left_margin + var->right_margin + var->hsync_len; | ||
| 518 | value = (value * hmul) / hdiv; | ||
| 519 | pr_debug("fb%d: horizontal total : %d\n", node, value); | ||
| 520 | svga_wcrt_multi(regbase, tm->h_total_regs, (value / 8) - 5); | ||
| 521 | |||
| 522 | value = var->xres; | ||
| 523 | value = (value * hmul) / hdiv; | ||
| 524 | pr_debug("fb%d: horizontal display : %d\n", node, value); | ||
| 525 | svga_wcrt_multi(regbase, tm->h_display_regs, (value / 8) - 1); | ||
| 526 | |||
| 527 | value = var->xres; | ||
| 528 | value = (value * hmul) / hdiv; | ||
| 529 | pr_debug("fb%d: horizontal blank start: %d\n", node, value); | ||
| 530 | svga_wcrt_multi(regbase, tm->h_blank_start_regs, (value / 8) - 1 + hborder); | ||
| 531 | |||
| 532 | value = var->xres + var->left_margin + var->right_margin + var->hsync_len; | ||
| 533 | value = (value * hmul) / hdiv; | ||
| 534 | pr_debug("fb%d: horizontal blank end : %d\n", node, value); | ||
| 535 | svga_wcrt_multi(regbase, tm->h_blank_end_regs, (value / 8) - 1 - hborder); | ||
| 536 | |||
| 537 | value = var->xres + var->right_margin; | ||
| 538 | value = (value * hmul) / hdiv; | ||
| 539 | pr_debug("fb%d: horizontal sync start : %d\n", node, value); | ||
| 540 | svga_wcrt_multi(regbase, tm->h_sync_start_regs, (value / 8)); | ||
| 541 | |||
| 542 | value = var->xres + var->right_margin + var->hsync_len; | ||
| 543 | value = (value * hmul) / hdiv; | ||
| 544 | pr_debug("fb%d: horizontal sync end : %d\n", node, value); | ||
| 545 | svga_wcrt_multi(regbase, tm->h_sync_end_regs, (value / 8)); | ||
| 546 | |||
| 547 | value = var->yres + var->upper_margin + var->lower_margin + var->vsync_len; | ||
| 548 | value = (value * vmul) / vdiv; | ||
| 549 | pr_debug("fb%d: vertical total : %d\n", node, value); | ||
| 550 | svga_wcrt_multi(regbase, tm->v_total_regs, value - 2); | ||
| 551 | |||
| 552 | value = var->yres; | ||
| 553 | value = (value * vmul) / vdiv; | ||
| 554 | pr_debug("fb%d: vertical display : %d\n", node, value); | ||
| 555 | svga_wcrt_multi(regbase, tm->v_display_regs, value - 1); | ||
| 556 | |||
| 557 | value = var->yres; | ||
| 558 | value = (value * vmul) / vdiv; | ||
| 559 | pr_debug("fb%d: vertical blank start : %d\n", node, value); | ||
| 560 | svga_wcrt_multi(regbase, tm->v_blank_start_regs, value); | ||
| 561 | |||
| 562 | value = var->yres + var->upper_margin + var->lower_margin + var->vsync_len; | ||
| 563 | value = (value * vmul) / vdiv; | ||
| 564 | pr_debug("fb%d: vertical blank end : %d\n", node, value); | ||
| 565 | svga_wcrt_multi(regbase, tm->v_blank_end_regs, value - 2); | ||
| 566 | |||
| 567 | value = var->yres + var->lower_margin; | ||
| 568 | value = (value * vmul) / vdiv; | ||
| 569 | pr_debug("fb%d: vertical sync start : %d\n", node, value); | ||
| 570 | svga_wcrt_multi(regbase, tm->v_sync_start_regs, value); | ||
| 571 | |||
| 572 | value = var->yres + var->lower_margin + var->vsync_len; | ||
| 573 | value = (value * vmul) / vdiv; | ||
| 574 | pr_debug("fb%d: vertical sync end : %d\n", node, value); | ||
| 575 | svga_wcrt_multi(regbase, tm->v_sync_end_regs, value); | ||
| 576 | |||
| 577 | /* Set horizontal and vertical sync pulse polarity in misc register */ | ||
| 578 | |||
| 579 | regval = vga_r(regbase, VGA_MIS_R); | ||
| 580 | if (var->sync & FB_SYNC_HOR_HIGH_ACT) { | ||
| 581 | pr_debug("fb%d: positive horizontal sync\n", node); | ||
| 582 | regval = regval & ~0x80; | ||
| 583 | } else { | ||
| 584 | pr_debug("fb%d: negative horizontal sync\n", node); | ||
| 585 | regval = regval | 0x80; | ||
| 586 | } | ||
| 587 | if (var->sync & FB_SYNC_VERT_HIGH_ACT) { | ||
| 588 | pr_debug("fb%d: positive vertical sync\n", node); | ||
| 589 | regval = regval & ~0x40; | ||
| 590 | } else { | ||
| 591 | pr_debug("fb%d: negative vertical sync\n\n", node); | ||
| 592 | regval = regval | 0x40; | ||
| 593 | } | ||
| 594 | vga_w(regbase, VGA_MIS_W, regval); | ||
| 595 | } | ||
| 596 | |||
| 597 | |||
| 598 | /* ------------------------------------------------------------------------- */ | ||
| 599 | |||
| 600 | |||
| 601 | static inline int match_format(const struct svga_fb_format *frm, | ||
| 602 | struct fb_var_screeninfo *var) | ||
| 603 | { | ||
| 604 | int i = 0; | ||
| 605 | int stored = -EINVAL; | ||
| 606 | |||
| 607 | while (frm->bits_per_pixel != SVGA_FORMAT_END_VAL) | ||
| 608 | { | ||
| 609 | if ((var->bits_per_pixel == frm->bits_per_pixel) && | ||
| 610 | (var->red.length <= frm->red.length) && | ||
| 611 | (var->green.length <= frm->green.length) && | ||
| 612 | (var->blue.length <= frm->blue.length) && | ||
| 613 | (var->transp.length <= frm->transp.length) && | ||
| 614 | (var->nonstd == frm->nonstd)) | ||
| 615 | return i; | ||
| 616 | if (var->bits_per_pixel == frm->bits_per_pixel) | ||
| 617 | stored = i; | ||
| 618 | i++; | ||
| 619 | frm++; | ||
| 620 | } | ||
| 621 | return stored; | ||
| 622 | } | ||
| 623 | |||
| 624 | int svga_match_format(const struct svga_fb_format *frm, | ||
| 625 | struct fb_var_screeninfo *var, | ||
| 626 | struct fb_fix_screeninfo *fix) | ||
| 627 | { | ||
| 628 | int i = match_format(frm, var); | ||
| 629 | |||
| 630 | if (i >= 0) { | ||
| 631 | var->bits_per_pixel = frm[i].bits_per_pixel; | ||
| 632 | var->red = frm[i].red; | ||
| 633 | var->green = frm[i].green; | ||
| 634 | var->blue = frm[i].blue; | ||
| 635 | var->transp = frm[i].transp; | ||
| 636 | var->nonstd = frm[i].nonstd; | ||
| 637 | if (fix != NULL) { | ||
| 638 | fix->type = frm[i].type; | ||
| 639 | fix->type_aux = frm[i].type_aux; | ||
| 640 | fix->visual = frm[i].visual; | ||
| 641 | fix->xpanstep = frm[i].xpanstep; | ||
| 642 | } | ||
| 643 | } | ||
| 644 | |||
| 645 | return i; | ||
| 646 | } | ||
| 647 | |||
| 648 | |||
| 649 | EXPORT_SYMBOL(svga_wcrt_multi); | ||
| 650 | EXPORT_SYMBOL(svga_wseq_multi); | ||
| 651 | |||
| 652 | EXPORT_SYMBOL(svga_set_default_gfx_regs); | ||
| 653 | EXPORT_SYMBOL(svga_set_default_atc_regs); | ||
| 654 | EXPORT_SYMBOL(svga_set_default_seq_regs); | ||
| 655 | EXPORT_SYMBOL(svga_set_default_crt_regs); | ||
| 656 | EXPORT_SYMBOL(svga_set_textmode_vga_regs); | ||
| 657 | |||
| 658 | EXPORT_SYMBOL(svga_settile); | ||
| 659 | EXPORT_SYMBOL(svga_tilecopy); | ||
| 660 | EXPORT_SYMBOL(svga_tilefill); | ||
| 661 | EXPORT_SYMBOL(svga_tileblit); | ||
| 662 | EXPORT_SYMBOL(svga_tilecursor); | ||
| 663 | EXPORT_SYMBOL(svga_get_tilemax); | ||
| 664 | |||
| 665 | EXPORT_SYMBOL(svga_compute_pll); | ||
| 666 | EXPORT_SYMBOL(svga_check_timings); | ||
| 667 | EXPORT_SYMBOL(svga_set_timings); | ||
| 668 | EXPORT_SYMBOL(svga_match_format); | ||
| 669 | |||
| 670 | MODULE_AUTHOR("Ondrej Zajicek <santiago@crfreenet.org>"); | ||
| 671 | MODULE_DESCRIPTION("Common utility functions for VGA-based graphics cards"); | ||
| 672 | MODULE_LICENSE("GPL"); | ||
diff --git a/drivers/video/fbdev/core/syscopyarea.c b/drivers/video/fbdev/core/syscopyarea.c new file mode 100644 index 000000000000..844a32fd38ed --- /dev/null +++ b/drivers/video/fbdev/core/syscopyarea.c | |||
| @@ -0,0 +1,377 @@ | |||
| 1 | /* | ||
| 2 | * Generic Bit Block Transfer for frame buffers located in system RAM with | ||
| 3 | * packed pixels of any depth. | ||
| 4 | * | ||
| 5 | * Based almost entirely from cfbcopyarea.c (which is based almost entirely | ||
| 6 | * on Geert Uytterhoeven's copyarea routine) | ||
| 7 | * | ||
| 8 | * Copyright (C) 2007 Antonino Daplas <adaplas@pol.net> | ||
| 9 | * | ||
| 10 | * This file is subject to the terms and conditions of the GNU General Public | ||
| 11 | * License. See the file COPYING in the main directory of this archive for | ||
| 12 | * more details. | ||
| 13 | * | ||
| 14 | */ | ||
| 15 | #include <linux/module.h> | ||
| 16 | #include <linux/kernel.h> | ||
| 17 | #include <linux/string.h> | ||
| 18 | #include <linux/fb.h> | ||
| 19 | #include <asm/types.h> | ||
| 20 | #include <asm/io.h> | ||
| 21 | #include "fb_draw.h" | ||
| 22 | |||
| 23 | /* | ||
| 24 | * Generic bitwise copy algorithm | ||
| 25 | */ | ||
| 26 | |||
| 27 | static void | ||
| 28 | bitcpy(struct fb_info *p, unsigned long *dst, int dst_idx, | ||
| 29 | const unsigned long *src, int src_idx, int bits, unsigned n) | ||
| 30 | { | ||
| 31 | unsigned long first, last; | ||
| 32 | int const shift = dst_idx-src_idx; | ||
| 33 | int left, right; | ||
| 34 | |||
| 35 | first = FB_SHIFT_HIGH(p, ~0UL, dst_idx); | ||
| 36 | last = ~(FB_SHIFT_HIGH(p, ~0UL, (dst_idx+n) % bits)); | ||
| 37 | |||
| 38 | if (!shift) { | ||
| 39 | /* Same alignment for source and dest */ | ||
| 40 | if (dst_idx+n <= bits) { | ||
| 41 | /* Single word */ | ||
| 42 | if (last) | ||
| 43 | first &= last; | ||
| 44 | *dst = comp(*src, *dst, first); | ||
| 45 | } else { | ||
| 46 | /* Multiple destination words */ | ||
| 47 | /* Leading bits */ | ||
| 48 | if (first != ~0UL) { | ||
| 49 | *dst = comp(*src, *dst, first); | ||
| 50 | dst++; | ||
| 51 | src++; | ||
| 52 | n -= bits - dst_idx; | ||
| 53 | } | ||
| 54 | |||
| 55 | /* Main chunk */ | ||
| 56 | n /= bits; | ||
| 57 | while (n >= 8) { | ||
| 58 | *dst++ = *src++; | ||
| 59 | *dst++ = *src++; | ||
| 60 | *dst++ = *src++; | ||
| 61 | *dst++ = *src++; | ||
| 62 | *dst++ = *src++; | ||
| 63 | *dst++ = *src++; | ||
| 64 | *dst++ = *src++; | ||
| 65 | *dst++ = *src++; | ||
| 66 | n -= 8; | ||
| 67 | } | ||
| 68 | while (n--) | ||
| 69 | *dst++ = *src++; | ||
| 70 | |||
| 71 | /* Trailing bits */ | ||
| 72 | if (last) | ||
| 73 | *dst = comp(*src, *dst, last); | ||
| 74 | } | ||
| 75 | } else { | ||
| 76 | unsigned long d0, d1; | ||
| 77 | int m; | ||
| 78 | |||
| 79 | /* Different alignment for source and dest */ | ||
| 80 | right = shift & (bits - 1); | ||
| 81 | left = -shift & (bits - 1); | ||
| 82 | |||
| 83 | if (dst_idx+n <= bits) { | ||
| 84 | /* Single destination word */ | ||
| 85 | if (last) | ||
| 86 | first &= last; | ||
| 87 | if (shift > 0) { | ||
| 88 | /* Single source word */ | ||
| 89 | *dst = comp(*src >> right, *dst, first); | ||
| 90 | } else if (src_idx+n <= bits) { | ||
| 91 | /* Single source word */ | ||
| 92 | *dst = comp(*src << left, *dst, first); | ||
| 93 | } else { | ||
| 94 | /* 2 source words */ | ||
| 95 | d0 = *src++; | ||
| 96 | d1 = *src; | ||
| 97 | *dst = comp(d0 << left | d1 >> right, *dst, | ||
| 98 | first); | ||
| 99 | } | ||
| 100 | } else { | ||
| 101 | /* Multiple destination words */ | ||
| 102 | /** We must always remember the last value read, | ||
| 103 | because in case SRC and DST overlap bitwise (e.g. | ||
| 104 | when moving just one pixel in 1bpp), we always | ||
| 105 | collect one full long for DST and that might | ||
| 106 | overlap with the current long from SRC. We store | ||
| 107 | this value in 'd0'. */ | ||
| 108 | d0 = *src++; | ||
| 109 | /* Leading bits */ | ||
| 110 | if (shift > 0) { | ||
| 111 | /* Single source word */ | ||
| 112 | *dst = comp(d0 >> right, *dst, first); | ||
| 113 | dst++; | ||
| 114 | n -= bits - dst_idx; | ||
| 115 | } else { | ||
| 116 | /* 2 source words */ | ||
| 117 | d1 = *src++; | ||
| 118 | *dst = comp(d0 << left | *dst >> right, *dst, first); | ||
| 119 | d0 = d1; | ||
| 120 | dst++; | ||
| 121 | n -= bits - dst_idx; | ||
| 122 | } | ||
| 123 | |||
| 124 | /* Main chunk */ | ||
| 125 | m = n % bits; | ||
| 126 | n /= bits; | ||
| 127 | while (n >= 4) { | ||
| 128 | d1 = *src++; | ||
| 129 | *dst++ = d0 << left | d1 >> right; | ||
| 130 | d0 = d1; | ||
| 131 | d1 = *src++; | ||
| 132 | *dst++ = d0 << left | d1 >> right; | ||
| 133 | d0 = d1; | ||
| 134 | d1 = *src++; | ||
| 135 | *dst++ = d0 << left | d1 >> right; | ||
| 136 | d0 = d1; | ||
| 137 | d1 = *src++; | ||
| 138 | *dst++ = d0 << left | d1 >> right; | ||
| 139 | d0 = d1; | ||
| 140 | n -= 4; | ||
| 141 | } | ||
| 142 | while (n--) { | ||
| 143 | d1 = *src++; | ||
| 144 | *dst++ = d0 << left | d1 >> right; | ||
| 145 | d0 = d1; | ||
| 146 | } | ||
| 147 | |||
| 148 | /* Trailing bits */ | ||
| 149 | if (last) { | ||
| 150 | if (m <= right) { | ||
| 151 | /* Single source word */ | ||
| 152 | *dst = comp(d0 << left, *dst, last); | ||
| 153 | } else { | ||
| 154 | /* 2 source words */ | ||
| 155 | d1 = *src; | ||
| 156 | *dst = comp(d0 << left | d1 >> right, | ||
| 157 | *dst, last); | ||
| 158 | } | ||
| 159 | } | ||
| 160 | } | ||
| 161 | } | ||
| 162 | } | ||
| 163 | |||
| 164 | /* | ||
| 165 | * Generic bitwise copy algorithm, operating backward | ||
| 166 | */ | ||
| 167 | |||
| 168 | static void | ||
| 169 | bitcpy_rev(struct fb_info *p, unsigned long *dst, int dst_idx, | ||
| 170 | const unsigned long *src, int src_idx, int bits, unsigned n) | ||
| 171 | { | ||
| 172 | unsigned long first, last; | ||
| 173 | int shift; | ||
| 174 | |||
| 175 | dst += (n-1)/bits; | ||
| 176 | src += (n-1)/bits; | ||
| 177 | if ((n-1) % bits) { | ||
| 178 | dst_idx += (n-1) % bits; | ||
| 179 | dst += dst_idx >> (ffs(bits) - 1); | ||
| 180 | dst_idx &= bits - 1; | ||
| 181 | src_idx += (n-1) % bits; | ||
| 182 | src += src_idx >> (ffs(bits) - 1); | ||
| 183 | src_idx &= bits - 1; | ||
| 184 | } | ||
| 185 | |||
| 186 | shift = dst_idx-src_idx; | ||
| 187 | |||
| 188 | first = FB_SHIFT_LOW(p, ~0UL, bits - 1 - dst_idx); | ||
| 189 | last = ~(FB_SHIFT_LOW(p, ~0UL, bits - 1 - ((dst_idx-n) % bits))); | ||
| 190 | |||
| 191 | if (!shift) { | ||
| 192 | /* Same alignment for source and dest */ | ||
| 193 | if ((unsigned long)dst_idx+1 >= n) { | ||
| 194 | /* Single word */ | ||
| 195 | if (last) | ||
| 196 | first &= last; | ||
| 197 | *dst = comp(*src, *dst, first); | ||
| 198 | } else { | ||
| 199 | /* Multiple destination words */ | ||
| 200 | |||
| 201 | /* Leading bits */ | ||
| 202 | if (first != ~0UL) { | ||
| 203 | *dst = comp(*src, *dst, first); | ||
| 204 | dst--; | ||
| 205 | src--; | ||
| 206 | n -= dst_idx+1; | ||
| 207 | } | ||
| 208 | |||
| 209 | /* Main chunk */ | ||
| 210 | n /= bits; | ||
| 211 | while (n >= 8) { | ||
| 212 | *dst-- = *src--; | ||
| 213 | *dst-- = *src--; | ||
| 214 | *dst-- = *src--; | ||
| 215 | *dst-- = *src--; | ||
| 216 | *dst-- = *src--; | ||
| 217 | *dst-- = *src--; | ||
| 218 | *dst-- = *src--; | ||
| 219 | *dst-- = *src--; | ||
| 220 | n -= 8; | ||
| 221 | } | ||
| 222 | while (n--) | ||
| 223 | *dst-- = *src--; | ||
| 224 | /* Trailing bits */ | ||
| 225 | if (last) | ||
| 226 | *dst = comp(*src, *dst, last); | ||
| 227 | } | ||
| 228 | } else { | ||
| 229 | /* Different alignment for source and dest */ | ||
| 230 | |||
| 231 | int const left = -shift & (bits-1); | ||
| 232 | int const right = shift & (bits-1); | ||
| 233 | |||
| 234 | if ((unsigned long)dst_idx+1 >= n) { | ||
| 235 | /* Single destination word */ | ||
| 236 | if (last) | ||
| 237 | first &= last; | ||
| 238 | if (shift < 0) { | ||
| 239 | /* Single source word */ | ||
| 240 | *dst = comp(*src << left, *dst, first); | ||
| 241 | } else if (1+(unsigned long)src_idx >= n) { | ||
| 242 | /* Single source word */ | ||
| 243 | *dst = comp(*src >> right, *dst, first); | ||
| 244 | } else { | ||
| 245 | /* 2 source words */ | ||
| 246 | *dst = comp(*src >> right | *(src-1) << left, | ||
| 247 | *dst, first); | ||
| 248 | } | ||
| 249 | } else { | ||
| 250 | /* Multiple destination words */ | ||
| 251 | /** We must always remember the last value read, | ||
| 252 | because in case SRC and DST overlap bitwise (e.g. | ||
| 253 | when moving just one pixel in 1bpp), we always | ||
| 254 | collect one full long for DST and that might | ||
| 255 | overlap with the current long from SRC. We store | ||
| 256 | this value in 'd0'. */ | ||
| 257 | unsigned long d0, d1; | ||
| 258 | int m; | ||
| 259 | |||
| 260 | d0 = *src--; | ||
| 261 | /* Leading bits */ | ||
| 262 | if (shift < 0) { | ||
| 263 | /* Single source word */ | ||
| 264 | *dst = comp(d0 << left, *dst, first); | ||
| 265 | } else { | ||
| 266 | /* 2 source words */ | ||
| 267 | d1 = *src--; | ||
| 268 | *dst = comp(d0 >> right | d1 << left, *dst, | ||
| 269 | first); | ||
| 270 | d0 = d1; | ||
| 271 | } | ||
| 272 | dst--; | ||
| 273 | n -= dst_idx+1; | ||
| 274 | |||
| 275 | /* Main chunk */ | ||
| 276 | m = n % bits; | ||
| 277 | n /= bits; | ||
| 278 | while (n >= 4) { | ||
| 279 | d1 = *src--; | ||
| 280 | *dst-- = d0 >> right | d1 << left; | ||
| 281 | d0 = d1; | ||
| 282 | d1 = *src--; | ||
| 283 | *dst-- = d0 >> right | d1 << left; | ||
| 284 | d0 = d1; | ||
| 285 | d1 = *src--; | ||
| 286 | *dst-- = d0 >> right | d1 << left; | ||
| 287 | d0 = d1; | ||
| 288 | d1 = *src--; | ||
| 289 | *dst-- = d0 >> right | d1 << left; | ||
| 290 | d0 = d1; | ||
| 291 | n -= 4; | ||
| 292 | } | ||
| 293 | while (n--) { | ||
| 294 | d1 = *src--; | ||
| 295 | *dst-- = d0 >> right | d1 << left; | ||
| 296 | d0 = d1; | ||
| 297 | } | ||
| 298 | |||
| 299 | /* Trailing bits */ | ||
| 300 | if (last) { | ||
| 301 | if (m <= left) { | ||
| 302 | /* Single source word */ | ||
| 303 | *dst = comp(d0 >> right, *dst, last); | ||
| 304 | } else { | ||
| 305 | /* 2 source words */ | ||
| 306 | d1 = *src; | ||
| 307 | *dst = comp(d0 >> right | d1 << left, | ||
| 308 | *dst, last); | ||
| 309 | } | ||
| 310 | } | ||
| 311 | } | ||
| 312 | } | ||
| 313 | } | ||
| 314 | |||
| 315 | void sys_copyarea(struct fb_info *p, const struct fb_copyarea *area) | ||
| 316 | { | ||
| 317 | u32 dx = area->dx, dy = area->dy, sx = area->sx, sy = area->sy; | ||
| 318 | u32 height = area->height, width = area->width; | ||
| 319 | unsigned long const bits_per_line = p->fix.line_length*8u; | ||
| 320 | unsigned long *dst = NULL, *src = NULL; | ||
| 321 | int bits = BITS_PER_LONG, bytes = bits >> 3; | ||
| 322 | int dst_idx = 0, src_idx = 0, rev_copy = 0; | ||
| 323 | |||
| 324 | if (p->state != FBINFO_STATE_RUNNING) | ||
| 325 | return; | ||
| 326 | |||
| 327 | /* if the beginning of the target area might overlap with the end of | ||
| 328 | the source area, be have to copy the area reverse. */ | ||
| 329 | if ((dy == sy && dx > sx) || (dy > sy)) { | ||
| 330 | dy += height; | ||
| 331 | sy += height; | ||
| 332 | rev_copy = 1; | ||
| 333 | } | ||
| 334 | |||
| 335 | /* split the base of the framebuffer into a long-aligned address and | ||
| 336 | the index of the first bit */ | ||
| 337 | dst = src = (unsigned long *)((unsigned long)p->screen_base & | ||
| 338 | ~(bytes-1)); | ||
| 339 | dst_idx = src_idx = 8*((unsigned long)p->screen_base & (bytes-1)); | ||
| 340 | /* add offset of source and target area */ | ||
| 341 | dst_idx += dy*bits_per_line + dx*p->var.bits_per_pixel; | ||
| 342 | src_idx += sy*bits_per_line + sx*p->var.bits_per_pixel; | ||
| 343 | |||
| 344 | if (p->fbops->fb_sync) | ||
| 345 | p->fbops->fb_sync(p); | ||
| 346 | |||
| 347 | if (rev_copy) { | ||
| 348 | while (height--) { | ||
| 349 | dst_idx -= bits_per_line; | ||
| 350 | src_idx -= bits_per_line; | ||
| 351 | dst += dst_idx >> (ffs(bits) - 1); | ||
| 352 | dst_idx &= (bytes - 1); | ||
| 353 | src += src_idx >> (ffs(bits) - 1); | ||
| 354 | src_idx &= (bytes - 1); | ||
| 355 | bitcpy_rev(p, dst, dst_idx, src, src_idx, bits, | ||
| 356 | width*p->var.bits_per_pixel); | ||
| 357 | } | ||
| 358 | } else { | ||
| 359 | while (height--) { | ||
| 360 | dst += dst_idx >> (ffs(bits) - 1); | ||
| 361 | dst_idx &= (bytes - 1); | ||
| 362 | src += src_idx >> (ffs(bits) - 1); | ||
| 363 | src_idx &= (bytes - 1); | ||
| 364 | bitcpy(p, dst, dst_idx, src, src_idx, bits, | ||
| 365 | width*p->var.bits_per_pixel); | ||
| 366 | dst_idx += bits_per_line; | ||
| 367 | src_idx += bits_per_line; | ||
| 368 | } | ||
| 369 | } | ||
| 370 | } | ||
| 371 | |||
| 372 | EXPORT_SYMBOL(sys_copyarea); | ||
| 373 | |||
| 374 | MODULE_AUTHOR("Antonino Daplas <adaplas@pol.net>"); | ||
| 375 | MODULE_DESCRIPTION("Generic copyarea (sys-to-sys)"); | ||
| 376 | MODULE_LICENSE("GPL"); | ||
| 377 | |||
diff --git a/drivers/video/fbdev/core/sysfillrect.c b/drivers/video/fbdev/core/sysfillrect.c new file mode 100644 index 000000000000..33ee3d34f9d2 --- /dev/null +++ b/drivers/video/fbdev/core/sysfillrect.c | |||
| @@ -0,0 +1,335 @@ | |||
| 1 | /* | ||
| 2 | * Generic fillrect for frame buffers in system RAM with packed pixels of | ||
| 3 | * any depth. | ||
| 4 | * | ||
| 5 | * Based almost entirely from cfbfillrect.c (which is based almost entirely | ||
| 6 | * on Geert Uytterhoeven's fillrect routine) | ||
| 7 | * | ||
| 8 | * Copyright (C) 2007 Antonino Daplas <adaplas@pol.net> | ||
| 9 | * | ||
| 10 | * This file is subject to the terms and conditions of the GNU General Public | ||
| 11 | * License. See the file COPYING in the main directory of this archive for | ||
| 12 | * more details. | ||
| 13 | */ | ||
| 14 | #include <linux/module.h> | ||
| 15 | #include <linux/string.h> | ||
| 16 | #include <linux/fb.h> | ||
| 17 | #include <asm/types.h> | ||
| 18 | #include "fb_draw.h" | ||
| 19 | |||
| 20 | /* | ||
| 21 | * Aligned pattern fill using 32/64-bit memory accesses | ||
| 22 | */ | ||
| 23 | |||
| 24 | static void | ||
| 25 | bitfill_aligned(struct fb_info *p, unsigned long *dst, int dst_idx, | ||
| 26 | unsigned long pat, unsigned n, int bits) | ||
| 27 | { | ||
| 28 | unsigned long first, last; | ||
| 29 | |||
| 30 | if (!n) | ||
| 31 | return; | ||
| 32 | |||
| 33 | first = FB_SHIFT_HIGH(p, ~0UL, dst_idx); | ||
| 34 | last = ~(FB_SHIFT_HIGH(p, ~0UL, (dst_idx+n) % bits)); | ||
| 35 | |||
| 36 | if (dst_idx+n <= bits) { | ||
| 37 | /* Single word */ | ||
| 38 | if (last) | ||
| 39 | first &= last; | ||
| 40 | *dst = comp(pat, *dst, first); | ||
| 41 | } else { | ||
| 42 | /* Multiple destination words */ | ||
| 43 | |||
| 44 | /* Leading bits */ | ||
| 45 | if (first!= ~0UL) { | ||
| 46 | *dst = comp(pat, *dst, first); | ||
| 47 | dst++; | ||
| 48 | n -= bits - dst_idx; | ||
| 49 | } | ||
| 50 | |||
| 51 | /* Main chunk */ | ||
| 52 | n /= bits; | ||
| 53 | while (n >= 8) { | ||
| 54 | *dst++ = pat; | ||
| 55 | *dst++ = pat; | ||
| 56 | *dst++ = pat; | ||
| 57 | *dst++ = pat; | ||
| 58 | *dst++ = pat; | ||
| 59 | *dst++ = pat; | ||
| 60 | *dst++ = pat; | ||
| 61 | *dst++ = pat; | ||
| 62 | n -= 8; | ||
| 63 | } | ||
| 64 | while (n--) | ||
| 65 | *dst++ = pat; | ||
| 66 | /* Trailing bits */ | ||
| 67 | if (last) | ||
| 68 | *dst = comp(pat, *dst, last); | ||
| 69 | } | ||
| 70 | } | ||
| 71 | |||
| 72 | |||
| 73 | /* | ||
| 74 | * Unaligned generic pattern fill using 32/64-bit memory accesses | ||
| 75 | * The pattern must have been expanded to a full 32/64-bit value | ||
| 76 | * Left/right are the appropriate shifts to convert to the pattern to be | ||
| 77 | * used for the next 32/64-bit word | ||
| 78 | */ | ||
| 79 | |||
| 80 | static void | ||
| 81 | bitfill_unaligned(struct fb_info *p, unsigned long *dst, int dst_idx, | ||
| 82 | unsigned long pat, int left, int right, unsigned n, int bits) | ||
| 83 | { | ||
| 84 | unsigned long first, last; | ||
| 85 | |||
| 86 | if (!n) | ||
| 87 | return; | ||
| 88 | |||
| 89 | first = FB_SHIFT_HIGH(p, ~0UL, dst_idx); | ||
| 90 | last = ~(FB_SHIFT_HIGH(p, ~0UL, (dst_idx+n) % bits)); | ||
| 91 | |||
| 92 | if (dst_idx+n <= bits) { | ||
| 93 | /* Single word */ | ||
| 94 | if (last) | ||
| 95 | first &= last; | ||
| 96 | *dst = comp(pat, *dst, first); | ||
| 97 | } else { | ||
| 98 | /* Multiple destination words */ | ||
| 99 | /* Leading bits */ | ||
| 100 | if (first) { | ||
| 101 | *dst = comp(pat, *dst, first); | ||
| 102 | dst++; | ||
| 103 | pat = pat << left | pat >> right; | ||
| 104 | n -= bits - dst_idx; | ||
| 105 | } | ||
| 106 | |||
| 107 | /* Main chunk */ | ||
| 108 | n /= bits; | ||
| 109 | while (n >= 4) { | ||
| 110 | *dst++ = pat; | ||
| 111 | pat = pat << left | pat >> right; | ||
| 112 | *dst++ = pat; | ||
| 113 | pat = pat << left | pat >> right; | ||
| 114 | *dst++ = pat; | ||
| 115 | pat = pat << left | pat >> right; | ||
| 116 | *dst++ = pat; | ||
| 117 | pat = pat << left | pat >> right; | ||
| 118 | n -= 4; | ||
| 119 | } | ||
| 120 | while (n--) { | ||
| 121 | *dst++ = pat; | ||
| 122 | pat = pat << left | pat >> right; | ||
| 123 | } | ||
| 124 | |||
| 125 | /* Trailing bits */ | ||
| 126 | if (last) | ||
| 127 | *dst = comp(pat, *dst, last); | ||
| 128 | } | ||
| 129 | } | ||
| 130 | |||
| 131 | /* | ||
| 132 | * Aligned pattern invert using 32/64-bit memory accesses | ||
| 133 | */ | ||
| 134 | static void | ||
| 135 | bitfill_aligned_rev(struct fb_info *p, unsigned long *dst, int dst_idx, | ||
| 136 | unsigned long pat, unsigned n, int bits) | ||
| 137 | { | ||
| 138 | unsigned long val = pat; | ||
| 139 | unsigned long first, last; | ||
| 140 | |||
| 141 | if (!n) | ||
| 142 | return; | ||
| 143 | |||
| 144 | first = FB_SHIFT_HIGH(p, ~0UL, dst_idx); | ||
| 145 | last = ~(FB_SHIFT_HIGH(p, ~0UL, (dst_idx+n) % bits)); | ||
| 146 | |||
| 147 | if (dst_idx+n <= bits) { | ||
| 148 | /* Single word */ | ||
| 149 | if (last) | ||
| 150 | first &= last; | ||
| 151 | *dst = comp(*dst ^ val, *dst, first); | ||
| 152 | } else { | ||
| 153 | /* Multiple destination words */ | ||
| 154 | /* Leading bits */ | ||
| 155 | if (first!=0UL) { | ||
| 156 | *dst = comp(*dst ^ val, *dst, first); | ||
| 157 | dst++; | ||
| 158 | n -= bits - dst_idx; | ||
| 159 | } | ||
| 160 | |||
| 161 | /* Main chunk */ | ||
| 162 | n /= bits; | ||
| 163 | while (n >= 8) { | ||
| 164 | *dst++ ^= val; | ||
| 165 | *dst++ ^= val; | ||
| 166 | *dst++ ^= val; | ||
| 167 | *dst++ ^= val; | ||
| 168 | *dst++ ^= val; | ||
| 169 | *dst++ ^= val; | ||
| 170 | *dst++ ^= val; | ||
| 171 | *dst++ ^= val; | ||
| 172 | n -= 8; | ||
| 173 | } | ||
| 174 | while (n--) | ||
| 175 | *dst++ ^= val; | ||
| 176 | /* Trailing bits */ | ||
| 177 | if (last) | ||
| 178 | *dst = comp(*dst ^ val, *dst, last); | ||
| 179 | } | ||
| 180 | } | ||
| 181 | |||
| 182 | |||
| 183 | /* | ||
| 184 | * Unaligned generic pattern invert using 32/64-bit memory accesses | ||
| 185 | * The pattern must have been expanded to a full 32/64-bit value | ||
| 186 | * Left/right are the appropriate shifts to convert to the pattern to be | ||
| 187 | * used for the next 32/64-bit word | ||
| 188 | */ | ||
| 189 | |||
| 190 | static void | ||
| 191 | bitfill_unaligned_rev(struct fb_info *p, unsigned long *dst, int dst_idx, | ||
| 192 | unsigned long pat, int left, int right, unsigned n, | ||
| 193 | int bits) | ||
| 194 | { | ||
| 195 | unsigned long first, last; | ||
| 196 | |||
| 197 | if (!n) | ||
| 198 | return; | ||
| 199 | |||
| 200 | first = FB_SHIFT_HIGH(p, ~0UL, dst_idx); | ||
| 201 | last = ~(FB_SHIFT_HIGH(p, ~0UL, (dst_idx+n) % bits)); | ||
| 202 | |||
| 203 | if (dst_idx+n <= bits) { | ||
| 204 | /* Single word */ | ||
| 205 | if (last) | ||
| 206 | first &= last; | ||
| 207 | *dst = comp(*dst ^ pat, *dst, first); | ||
| 208 | } else { | ||
| 209 | /* Multiple destination words */ | ||
| 210 | |||
| 211 | /* Leading bits */ | ||
| 212 | if (first != 0UL) { | ||
| 213 | *dst = comp(*dst ^ pat, *dst, first); | ||
| 214 | dst++; | ||
| 215 | pat = pat << left | pat >> right; | ||
| 216 | n -= bits - dst_idx; | ||
| 217 | } | ||
| 218 | |||
| 219 | /* Main chunk */ | ||
| 220 | n /= bits; | ||
| 221 | while (n >= 4) { | ||
| 222 | *dst++ ^= pat; | ||
| 223 | pat = pat << left | pat >> right; | ||
| 224 | *dst++ ^= pat; | ||
| 225 | pat = pat << left | pat >> right; | ||
| 226 | *dst++ ^= pat; | ||
| 227 | pat = pat << left | pat >> right; | ||
| 228 | *dst++ ^= pat; | ||
| 229 | pat = pat << left | pat >> right; | ||
| 230 | n -= 4; | ||
| 231 | } | ||
| 232 | while (n--) { | ||
| 233 | *dst ^= pat; | ||
| 234 | pat = pat << left | pat >> right; | ||
| 235 | } | ||
| 236 | |||
| 237 | /* Trailing bits */ | ||
| 238 | if (last) | ||
| 239 | *dst = comp(*dst ^ pat, *dst, last); | ||
| 240 | } | ||
| 241 | } | ||
| 242 | |||
| 243 | void sys_fillrect(struct fb_info *p, const struct fb_fillrect *rect) | ||
| 244 | { | ||
| 245 | unsigned long pat, pat2, fg; | ||
| 246 | unsigned long width = rect->width, height = rect->height; | ||
| 247 | int bits = BITS_PER_LONG, bytes = bits >> 3; | ||
| 248 | u32 bpp = p->var.bits_per_pixel; | ||
| 249 | unsigned long *dst; | ||
| 250 | int dst_idx, left; | ||
| 251 | |||
| 252 | if (p->state != FBINFO_STATE_RUNNING) | ||
| 253 | return; | ||
| 254 | |||
| 255 | if (p->fix.visual == FB_VISUAL_TRUECOLOR || | ||
| 256 | p->fix.visual == FB_VISUAL_DIRECTCOLOR ) | ||
| 257 | fg = ((u32 *) (p->pseudo_palette))[rect->color]; | ||
| 258 | else | ||
| 259 | fg = rect->color; | ||
| 260 | |||
| 261 | pat = pixel_to_pat( bpp, fg); | ||
| 262 | |||
| 263 | dst = (unsigned long *)((unsigned long)p->screen_base & ~(bytes-1)); | ||
| 264 | dst_idx = ((unsigned long)p->screen_base & (bytes - 1))*8; | ||
| 265 | dst_idx += rect->dy*p->fix.line_length*8+rect->dx*bpp; | ||
| 266 | /* FIXME For now we support 1-32 bpp only */ | ||
| 267 | left = bits % bpp; | ||
| 268 | if (p->fbops->fb_sync) | ||
| 269 | p->fbops->fb_sync(p); | ||
| 270 | if (!left) { | ||
| 271 | void (*fill_op32)(struct fb_info *p, unsigned long *dst, | ||
| 272 | int dst_idx, unsigned long pat, unsigned n, | ||
| 273 | int bits) = NULL; | ||
| 274 | |||
| 275 | switch (rect->rop) { | ||
| 276 | case ROP_XOR: | ||
| 277 | fill_op32 = bitfill_aligned_rev; | ||
| 278 | break; | ||
| 279 | case ROP_COPY: | ||
| 280 | fill_op32 = bitfill_aligned; | ||
| 281 | break; | ||
| 282 | default: | ||
| 283 | printk( KERN_ERR "cfb_fillrect(): unknown rop, " | ||
| 284 | "defaulting to ROP_COPY\n"); | ||
| 285 | fill_op32 = bitfill_aligned; | ||
| 286 | break; | ||
| 287 | } | ||
| 288 | while (height--) { | ||
| 289 | dst += dst_idx >> (ffs(bits) - 1); | ||
| 290 | dst_idx &= (bits - 1); | ||
| 291 | fill_op32(p, dst, dst_idx, pat, width*bpp, bits); | ||
| 292 | dst_idx += p->fix.line_length*8; | ||
| 293 | } | ||
| 294 | } else { | ||
| 295 | int right, r; | ||
| 296 | void (*fill_op)(struct fb_info *p, unsigned long *dst, | ||
| 297 | int dst_idx, unsigned long pat, int left, | ||
| 298 | int right, unsigned n, int bits) = NULL; | ||
| 299 | #ifdef __LITTLE_ENDIAN | ||
| 300 | right = left; | ||
| 301 | left = bpp - right; | ||
| 302 | #else | ||
| 303 | right = bpp - left; | ||
| 304 | #endif | ||
| 305 | switch (rect->rop) { | ||
| 306 | case ROP_XOR: | ||
| 307 | fill_op = bitfill_unaligned_rev; | ||
| 308 | break; | ||
| 309 | case ROP_COPY: | ||
| 310 | fill_op = bitfill_unaligned; | ||
| 311 | break; | ||
| 312 | default: | ||
| 313 | printk(KERN_ERR "sys_fillrect(): unknown rop, " | ||
| 314 | "defaulting to ROP_COPY\n"); | ||
| 315 | fill_op = bitfill_unaligned; | ||
| 316 | break; | ||
| 317 | } | ||
| 318 | while (height--) { | ||
| 319 | dst += dst_idx / bits; | ||
| 320 | dst_idx &= (bits - 1); | ||
| 321 | r = dst_idx % bpp; | ||
| 322 | /* rotate pattern to the correct start position */ | ||
| 323 | pat2 = le_long_to_cpu(rolx(cpu_to_le_long(pat), r, bpp)); | ||
| 324 | fill_op(p, dst, dst_idx, pat2, left, right, | ||
| 325 | width*bpp, bits); | ||
| 326 | dst_idx += p->fix.line_length*8; | ||
| 327 | } | ||
| 328 | } | ||
| 329 | } | ||
| 330 | |||
| 331 | EXPORT_SYMBOL(sys_fillrect); | ||
| 332 | |||
| 333 | MODULE_AUTHOR("Antonino Daplas <adaplas@pol.net>"); | ||
| 334 | MODULE_DESCRIPTION("Generic fill rectangle (sys-to-sys)"); | ||
| 335 | MODULE_LICENSE("GPL"); | ||
diff --git a/drivers/video/fbdev/core/sysimgblt.c b/drivers/video/fbdev/core/sysimgblt.c new file mode 100644 index 000000000000..a4d05b1b17d7 --- /dev/null +++ b/drivers/video/fbdev/core/sysimgblt.c | |||
| @@ -0,0 +1,288 @@ | |||
| 1 | /* | ||
| 2 | * Generic 1-bit or 8-bit source to 1-32 bit destination expansion | ||
| 3 | * for frame buffer located in system RAM with packed pixels of any depth. | ||
| 4 | * | ||
| 5 | * Based almost entirely on cfbimgblt.c | ||
| 6 | * | ||
| 7 | * Copyright (C) April 2007 Antonino Daplas <adaplas@pol.net> | ||
| 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 for | ||
| 11 | * more details. | ||
| 12 | */ | ||
| 13 | #include <linux/module.h> | ||
| 14 | #include <linux/string.h> | ||
| 15 | #include <linux/fb.h> | ||
| 16 | #include <asm/types.h> | ||
| 17 | |||
| 18 | #define DEBUG | ||
| 19 | |||
| 20 | #ifdef DEBUG | ||
| 21 | #define DPRINTK(fmt, args...) printk(KERN_DEBUG "%s: " fmt,__func__,## args) | ||
| 22 | #else | ||
| 23 | #define DPRINTK(fmt, args...) | ||
| 24 | #endif | ||
| 25 | |||
| 26 | static const u32 cfb_tab8_be[] = { | ||
| 27 | 0x00000000,0x000000ff,0x0000ff00,0x0000ffff, | ||
| 28 | 0x00ff0000,0x00ff00ff,0x00ffff00,0x00ffffff, | ||
| 29 | 0xff000000,0xff0000ff,0xff00ff00,0xff00ffff, | ||
| 30 | 0xffff0000,0xffff00ff,0xffffff00,0xffffffff | ||
| 31 | }; | ||
| 32 | |||
| 33 | static const u32 cfb_tab8_le[] = { | ||
| 34 | 0x00000000,0xff000000,0x00ff0000,0xffff0000, | ||
| 35 | 0x0000ff00,0xff00ff00,0x00ffff00,0xffffff00, | ||
| 36 | 0x000000ff,0xff0000ff,0x00ff00ff,0xffff00ff, | ||
| 37 | 0x0000ffff,0xff00ffff,0x00ffffff,0xffffffff | ||
| 38 | }; | ||
| 39 | |||
| 40 | static const u32 cfb_tab16_be[] = { | ||
| 41 | 0x00000000, 0x0000ffff, 0xffff0000, 0xffffffff | ||
| 42 | }; | ||
| 43 | |||
| 44 | static const u32 cfb_tab16_le[] = { | ||
| 45 | 0x00000000, 0xffff0000, 0x0000ffff, 0xffffffff | ||
| 46 | }; | ||
| 47 | |||
| 48 | static const u32 cfb_tab32[] = { | ||
| 49 | 0x00000000, 0xffffffff | ||
| 50 | }; | ||
| 51 | |||
| 52 | static void color_imageblit(const struct fb_image *image, struct fb_info *p, | ||
| 53 | void *dst1, u32 start_index, u32 pitch_index) | ||
| 54 | { | ||
| 55 | /* Draw the penguin */ | ||
| 56 | u32 *dst, *dst2; | ||
| 57 | u32 color = 0, val, shift; | ||
| 58 | int i, n, bpp = p->var.bits_per_pixel; | ||
| 59 | u32 null_bits = 32 - bpp; | ||
| 60 | u32 *palette = (u32 *) p->pseudo_palette; | ||
| 61 | const u8 *src = image->data; | ||
| 62 | |||
| 63 | dst2 = dst1; | ||
| 64 | for (i = image->height; i--; ) { | ||
| 65 | n = image->width; | ||
| 66 | dst = dst1; | ||
| 67 | shift = 0; | ||
| 68 | val = 0; | ||
| 69 | |||
| 70 | if (start_index) { | ||
| 71 | u32 start_mask = ~(FB_SHIFT_HIGH(p, ~(u32)0, | ||
| 72 | start_index)); | ||
| 73 | val = *dst & start_mask; | ||
| 74 | shift = start_index; | ||
| 75 | } | ||
| 76 | while (n--) { | ||
| 77 | if (p->fix.visual == FB_VISUAL_TRUECOLOR || | ||
| 78 | p->fix.visual == FB_VISUAL_DIRECTCOLOR ) | ||
| 79 | color = palette[*src]; | ||
| 80 | else | ||
| 81 | color = *src; | ||
| 82 | color <<= FB_LEFT_POS(p, bpp); | ||
| 83 | val |= FB_SHIFT_HIGH(p, color, shift); | ||
| 84 | if (shift >= null_bits) { | ||
| 85 | *dst++ = val; | ||
| 86 | |||
| 87 | val = (shift == null_bits) ? 0 : | ||
| 88 | FB_SHIFT_LOW(p, color, 32 - shift); | ||
| 89 | } | ||
| 90 | shift += bpp; | ||
| 91 | shift &= (32 - 1); | ||
| 92 | src++; | ||
| 93 | } | ||
| 94 | if (shift) { | ||
| 95 | u32 end_mask = FB_SHIFT_HIGH(p, ~(u32)0, shift); | ||
| 96 | |||
| 97 | *dst &= end_mask; | ||
| 98 | *dst |= val; | ||
| 99 | } | ||
| 100 | dst1 += p->fix.line_length; | ||
| 101 | if (pitch_index) { | ||
| 102 | dst2 += p->fix.line_length; | ||
| 103 | dst1 = (u8 *)((long)dst2 & ~(sizeof(u32) - 1)); | ||
| 104 | |||
| 105 | start_index += pitch_index; | ||
| 106 | start_index &= 32 - 1; | ||
| 107 | } | ||
| 108 | } | ||
| 109 | } | ||
| 110 | |||
| 111 | static void slow_imageblit(const struct fb_image *image, struct fb_info *p, | ||
| 112 | void *dst1, u32 fgcolor, u32 bgcolor, | ||
| 113 | u32 start_index, u32 pitch_index) | ||
| 114 | { | ||
| 115 | u32 shift, color = 0, bpp = p->var.bits_per_pixel; | ||
| 116 | u32 *dst, *dst2; | ||
| 117 | u32 val, pitch = p->fix.line_length; | ||
| 118 | u32 null_bits = 32 - bpp; | ||
| 119 | u32 spitch = (image->width+7)/8; | ||
| 120 | const u8 *src = image->data, *s; | ||
| 121 | u32 i, j, l; | ||
| 122 | |||
| 123 | dst2 = dst1; | ||
| 124 | fgcolor <<= FB_LEFT_POS(p, bpp); | ||
| 125 | bgcolor <<= FB_LEFT_POS(p, bpp); | ||
| 126 | |||
| 127 | for (i = image->height; i--; ) { | ||
| 128 | shift = val = 0; | ||
| 129 | l = 8; | ||
| 130 | j = image->width; | ||
| 131 | dst = dst1; | ||
| 132 | s = src; | ||
| 133 | |||
| 134 | /* write leading bits */ | ||
| 135 | if (start_index) { | ||
| 136 | u32 start_mask = ~(FB_SHIFT_HIGH(p, ~(u32)0, | ||
| 137 | start_index)); | ||
| 138 | val = *dst & start_mask; | ||
| 139 | shift = start_index; | ||
| 140 | } | ||
| 141 | |||
| 142 | while (j--) { | ||
| 143 | l--; | ||
| 144 | color = (*s & (1 << l)) ? fgcolor : bgcolor; | ||
| 145 | val |= FB_SHIFT_HIGH(p, color, shift); | ||
| 146 | |||
| 147 | /* Did the bitshift spill bits to the next long? */ | ||
| 148 | if (shift >= null_bits) { | ||
| 149 | *dst++ = val; | ||
| 150 | val = (shift == null_bits) ? 0 : | ||
| 151 | FB_SHIFT_LOW(p, color, 32 - shift); | ||
| 152 | } | ||
| 153 | shift += bpp; | ||
| 154 | shift &= (32 - 1); | ||
| 155 | if (!l) { l = 8; s++; } | ||
| 156 | } | ||
| 157 | |||
| 158 | /* write trailing bits */ | ||
| 159 | if (shift) { | ||
| 160 | u32 end_mask = FB_SHIFT_HIGH(p, ~(u32)0, shift); | ||
| 161 | |||
| 162 | *dst &= end_mask; | ||
| 163 | *dst |= val; | ||
| 164 | } | ||
| 165 | |||
| 166 | dst1 += pitch; | ||
| 167 | src += spitch; | ||
| 168 | if (pitch_index) { | ||
| 169 | dst2 += pitch; | ||
| 170 | dst1 = (u8 *)((long)dst2 & ~(sizeof(u32) - 1)); | ||
| 171 | start_index += pitch_index; | ||
| 172 | start_index &= 32 - 1; | ||
| 173 | } | ||
| 174 | |||
| 175 | } | ||
| 176 | } | ||
| 177 | |||
| 178 | /* | ||
| 179 | * fast_imageblit - optimized monochrome color expansion | ||
| 180 | * | ||
| 181 | * Only if: bits_per_pixel == 8, 16, or 32 | ||
| 182 | * image->width is divisible by pixel/dword (ppw); | ||
| 183 | * fix->line_legth is divisible by 4; | ||
| 184 | * beginning and end of a scanline is dword aligned | ||
| 185 | */ | ||
| 186 | static void fast_imageblit(const struct fb_image *image, struct fb_info *p, | ||
| 187 | void *dst1, u32 fgcolor, u32 bgcolor) | ||
| 188 | { | ||
| 189 | u32 fgx = fgcolor, bgx = bgcolor, bpp = p->var.bits_per_pixel; | ||
| 190 | u32 ppw = 32/bpp, spitch = (image->width + 7)/8; | ||
| 191 | u32 bit_mask, end_mask, eorx, shift; | ||
| 192 | const char *s = image->data, *src; | ||
| 193 | u32 *dst; | ||
| 194 | const u32 *tab = NULL; | ||
| 195 | int i, j, k; | ||
| 196 | |||
| 197 | switch (bpp) { | ||
| 198 | case 8: | ||
| 199 | tab = fb_be_math(p) ? cfb_tab8_be : cfb_tab8_le; | ||
| 200 | break; | ||
| 201 | case 16: | ||
| 202 | tab = fb_be_math(p) ? cfb_tab16_be : cfb_tab16_le; | ||
| 203 | break; | ||
| 204 | case 32: | ||
| 205 | default: | ||
| 206 | tab = cfb_tab32; | ||
| 207 | break; | ||
| 208 | } | ||
| 209 | |||
| 210 | for (i = ppw-1; i--; ) { | ||
| 211 | fgx <<= bpp; | ||
| 212 | bgx <<= bpp; | ||
| 213 | fgx |= fgcolor; | ||
| 214 | bgx |= bgcolor; | ||
| 215 | } | ||
| 216 | |||
| 217 | bit_mask = (1 << ppw) - 1; | ||
| 218 | eorx = fgx ^ bgx; | ||
| 219 | k = image->width/ppw; | ||
| 220 | |||
| 221 | for (i = image->height; i--; ) { | ||
| 222 | dst = dst1; | ||
| 223 | shift = 8; | ||
| 224 | src = s; | ||
| 225 | |||
| 226 | for (j = k; j--; ) { | ||
| 227 | shift -= ppw; | ||
| 228 | end_mask = tab[(*src >> shift) & bit_mask]; | ||
| 229 | *dst++ = (end_mask & eorx) ^ bgx; | ||
| 230 | if (!shift) { | ||
| 231 | shift = 8; | ||
| 232 | src++; | ||
| 233 | } | ||
| 234 | } | ||
| 235 | dst1 += p->fix.line_length; | ||
| 236 | s += spitch; | ||
| 237 | } | ||
| 238 | } | ||
| 239 | |||
| 240 | void sys_imageblit(struct fb_info *p, const struct fb_image *image) | ||
| 241 | { | ||
| 242 | u32 fgcolor, bgcolor, start_index, bitstart, pitch_index = 0; | ||
| 243 | u32 bpl = sizeof(u32), bpp = p->var.bits_per_pixel; | ||
| 244 | u32 width = image->width; | ||
| 245 | u32 dx = image->dx, dy = image->dy; | ||
| 246 | void *dst1; | ||
| 247 | |||
| 248 | if (p->state != FBINFO_STATE_RUNNING) | ||
| 249 | return; | ||
| 250 | |||
| 251 | bitstart = (dy * p->fix.line_length * 8) + (dx * bpp); | ||
| 252 | start_index = bitstart & (32 - 1); | ||
| 253 | pitch_index = (p->fix.line_length & (bpl - 1)) * 8; | ||
| 254 | |||
| 255 | bitstart /= 8; | ||
| 256 | bitstart &= ~(bpl - 1); | ||
| 257 | dst1 = (void __force *)p->screen_base + bitstart; | ||
| 258 | |||
| 259 | if (p->fbops->fb_sync) | ||
| 260 | p->fbops->fb_sync(p); | ||
| 261 | |||
| 262 | if (image->depth == 1) { | ||
| 263 | if (p->fix.visual == FB_VISUAL_TRUECOLOR || | ||
| 264 | p->fix.visual == FB_VISUAL_DIRECTCOLOR) { | ||
| 265 | fgcolor = ((u32*)(p->pseudo_palette))[image->fg_color]; | ||
| 266 | bgcolor = ((u32*)(p->pseudo_palette))[image->bg_color]; | ||
| 267 | } else { | ||
| 268 | fgcolor = image->fg_color; | ||
| 269 | bgcolor = image->bg_color; | ||
| 270 | } | ||
| 271 | |||
| 272 | if (32 % bpp == 0 && !start_index && !pitch_index && | ||
| 273 | ((width & (32/bpp-1)) == 0) && | ||
| 274 | bpp >= 8 && bpp <= 32) | ||
| 275 | fast_imageblit(image, p, dst1, fgcolor, bgcolor); | ||
| 276 | else | ||
| 277 | slow_imageblit(image, p, dst1, fgcolor, bgcolor, | ||
| 278 | start_index, pitch_index); | ||
| 279 | } else | ||
| 280 | color_imageblit(image, p, dst1, start_index, pitch_index); | ||
| 281 | } | ||
| 282 | |||
| 283 | EXPORT_SYMBOL(sys_imageblit); | ||
| 284 | |||
| 285 | MODULE_AUTHOR("Antonino Daplas <adaplas@pol.net>"); | ||
| 286 | MODULE_DESCRIPTION("1-bit/8-bit to 1-32 bit color expansion (sys-to-sys)"); | ||
| 287 | MODULE_LICENSE("GPL"); | ||
| 288 | |||
