| 1 | |
| 2 | |
| 3 | *
|
| 4 | * Copyright (c) 2006 Simtec Electronics
|
| 5 | * Vincent Sanders <[email protected]>
|
| 6 | * Ben Dooks <[email protected]>
|
| 7 | *
|
| 8 | * Framebuffer driver for the Silicon Motion SM501
|
| 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/tty.h>
|
| 17 | #include <linux/slab.h>
|
| 18 | #include <linux/delay.h>
|
| 19 | #include <linux/fb.h>
|
| 20 | #include <linux/init.h>
|
| 21 | #include <linux/vmalloc.h>
|
| 22 | #include <linux/dma-mapping.h>
|
| 23 | #include <linux/interrupt.h>
|
| 24 | #include <linux/workqueue.h>
|
| 25 | #include <linux/wait.h>
|
| 26 | #include <linux/platform_device.h>
|
| 27 | #include <linux/clk.h>
|
| 28 | #include <linux/console.h>
|
| 29 | #include <linux/io.h>
|
| 30 | #include <linux/string_choices.h>
|
| 31 |
|
| 32 | #include <linux/uaccess.h>
|
| 33 | #include <asm/div64.h>
|
| 34 |
|
| 35 | #ifdef CONFIG_PM
|
| 36 | #include <linux/pm.h>
|
| 37 | #endif
|
| 38 |
|
| 39 | #include <linux/sm501.h>
|
| 40 | #include <linux/sm501-regs.h>
|
| 41 |
|
| 42 | #include "edid.h"
|
| 43 |
|
| 44 | static char *fb_mode = "640x480-16@60";
|
| 45 | static unsigned long default_bpp = 16;
|
| 46 |
|
| 47 | static const struct fb_videomode sm501_default_mode = {
|
| 48 | .refresh = 60,
|
| 49 | .xres = 640,
|
| 50 | .yres = 480,
|
| 51 | .pixclock = 20833,
|
| 52 | .left_margin = 142,
|
| 53 | .right_margin = 13,
|
| 54 | .upper_margin = 21,
|
| 55 | .lower_margin = 1,
|
| 56 | .hsync_len = 69,
|
| 57 | .vsync_len = 3,
|
| 58 | .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
|
| 59 | .vmode = FB_VMODE_NONINTERLACED
|
| 60 | };
|
| 61 |
|
| 62 | #define NR_PALETTE 256
|
| 63 |
|
| 64 | enum sm501_controller {
|
| 65 | HEAD_CRT = 0,
|
| 66 | HEAD_PANEL = 1,
|
| 67 | };
|
| 68 |
|
| 69 | |
| 70 | *
|
| 71 | * This structure is used to track memory usage within the SM501 framebuffer
|
| 72 | * allocation. The sm_addr field is stored as an offset as it is often used
|
| 73 | * against both the physical and mapped addresses.
|
| 74 |
|
| 75 | struct sm501_mem {
|
| 76 | unsigned long size;
|
| 77 | unsigned long sm_addr;
|
| 78 | void __iomem *k_addr;
|
| 79 | };
|
| 80 |
|
| 81 |
|
| 82 | struct sm501fb_info {
|
| 83 | struct device *dev;
|
| 84 | struct fb_info *fb[2];
|
| 85 | struct resource *fbmem_res;
|
| 86 | struct resource *regs_res;
|
| 87 | struct resource *regs2d_res;
|
| 88 | struct sm501_platdata_fb *pdata;
|
| 89 |
|
| 90 | unsigned long pm_crt_ctrl;
|
| 91 |
|
| 92 | int irq;
|
| 93 | int swap_endian;
|
| 94 | void __iomem *regs;
|
| 95 | void __iomem *regs2d;
|
| 96 | void __iomem *fbmem;
|
| 97 | size_t fbmem_len;
|
| 98 | u8 *edid_data;
|
| 99 | char *fb_mode;
|
| 100 | };
|
| 101 |
|
| 102 |
|
| 103 | struct sm501fb_par {
|
| 104 | u32 pseudo_palette[16];
|
| 105 |
|
| 106 | enum sm501_controller head;
|
| 107 | struct sm501_mem cursor;
|
| 108 | struct sm501_mem screen;
|
| 109 | struct fb_ops ops;
|
| 110 |
|
| 111 | void *store_fb;
|
| 112 | void *store_cursor;
|
| 113 | void __iomem *cursor_regs;
|
| 114 | struct sm501fb_info *info;
|
| 115 | };
|
| 116 |
|
| 117 |
|
| 118 |
|
| 119 | static inline int h_total(struct fb_var_screeninfo *var)
|
| 120 | {
|
| 121 | return var->xres + var->left_margin +
|
| 122 | var->right_margin + var->hsync_len;
|
| 123 | }
|
| 124 |
|
| 125 | static inline int v_total(struct fb_var_screeninfo *var)
|
| 126 | {
|
| 127 | return var->yres + var->upper_margin +
|
| 128 | var->lower_margin + var->vsync_len;
|
| 129 | }
|
| 130 |
|
| 131 | |
| 132 | *
|
| 133 | * This call is mainly for PCI bus systems where we need to
|
| 134 | * ensure that any writes to the bus are completed before the
|
| 135 | * next phase, or after completing a function.
|
| 136 |
|
| 137 |
|
| 138 | static inline void sm501fb_sync_regs(struct sm501fb_info *info)
|
| 139 | {
|
| 140 | smc501_readl(info->regs);
|
| 141 | }
|
| 142 |
|
| 143 | |
| 144 | *
|
| 145 | * This is an attempt to lay out memory for the two framebuffers and
|
| 146 | * everything else
|
| 147 | *
|
| 148 | * |fbmem_res->start fbmem_res->end|
|
| 149 | * | |
|
| 150 | * |fb[0].fix.smem_start | |fb[1].fix.smem_start | 2K |
|
| 151 | * |-> fb[0].fix.smem_len <-| spare |-> fb[1].fix.smem_len <-|-> cursors <-|
|
| 152 | *
|
| 153 | * The "spare" space is for the 2d engine data
|
| 154 | * the fixed is space for the cursors (2x1Kbyte)
|
| 155 | *
|
| 156 | * we need to allocate memory for the 2D acceleration engine
|
| 157 | * command list and the data for the engine to deal with.
|
| 158 | *
|
| 159 | * - all allocations must be 128bit aligned
|
| 160 | * - cursors are 64x64x2 bits (1Kbyte)
|
| 161 | *
|
| 162 |
|
| 163 |
|
| 164 | #define SM501_MEMF_CURSOR (1)
|
| 165 | #define SM501_MEMF_PANEL (2)
|
| 166 | #define SM501_MEMF_CRT (4)
|
| 167 | #define SM501_MEMF_ACCEL (8)
|
| 168 |
|
| 169 | static int sm501_alloc_mem(struct sm501fb_info *inf, struct sm501_mem *mem,
|
| 170 | unsigned int why, size_t size, u32 smem_len)
|
| 171 | {
|
| 172 | struct sm501fb_par *par;
|
| 173 | struct fb_info *fbi;
|
| 174 | unsigned int ptr;
|
| 175 | unsigned int end;
|
| 176 |
|
| 177 | switch (why) {
|
| 178 | case SM501_MEMF_CURSOR:
|
| 179 | ptr = inf->fbmem_len - size;
|
| 180 | inf->fbmem_len = ptr;
|
| 181 | break;
|
| 182 |
|
| 183 | case SM501_MEMF_PANEL:
|
| 184 | if (size > inf->fbmem_len)
|
| 185 | return -ENOMEM;
|
| 186 |
|
| 187 | ptr = inf->fbmem_len - size;
|
| 188 | fbi = inf->fb[HEAD_CRT];
|
| 189 |
|
| 190 | |
| 191 | * 0,0 correctly unless the start is aligned to a page start.
|
| 192 |
|
| 193 |
|
| 194 | if (ptr > 0)
|
| 195 | ptr &= ~(PAGE_SIZE - 1);
|
| 196 |
|
| 197 | if (fbi && ptr < smem_len)
|
| 198 | return -ENOMEM;
|
| 199 |
|
| 200 | break;
|
| 201 |
|
| 202 | case SM501_MEMF_CRT:
|
| 203 | ptr = 0;
|
| 204 |
|
| 205 | |
| 206 | * which would put an limit on available memory.
|
| 207 |
|
| 208 | fbi = inf->fb[HEAD_PANEL];
|
| 209 | if (fbi) {
|
| 210 | par = fbi->par;
|
| 211 | end = par->screen.k_addr ? par->screen.sm_addr : inf->fbmem_len;
|
| 212 | } else
|
| 213 | end = inf->fbmem_len;
|
| 214 |
|
| 215 | if ((ptr + size) > end)
|
| 216 | return -ENOMEM;
|
| 217 |
|
| 218 | break;
|
| 219 |
|
| 220 | case SM501_MEMF_ACCEL:
|
| 221 | fbi = inf->fb[HEAD_CRT];
|
| 222 | ptr = fbi ? smem_len : 0;
|
| 223 |
|
| 224 | fbi = inf->fb[HEAD_PANEL];
|
| 225 | if (fbi) {
|
| 226 | par = fbi->par;
|
| 227 | end = par->screen.sm_addr;
|
| 228 | } else
|
| 229 | end = inf->fbmem_len;
|
| 230 |
|
| 231 | if ((ptr + size) > end)
|
| 232 | return -ENOMEM;
|
| 233 |
|
| 234 | break;
|
| 235 |
|
| 236 | default:
|
| 237 | return -EINVAL;
|
| 238 | }
|
| 239 |
|
| 240 | mem->size = size;
|
| 241 | mem->sm_addr = ptr;
|
| 242 | mem->k_addr = inf->fbmem + ptr;
|
| 243 |
|
| 244 | dev_dbg(inf->dev, "%s: result %08lx, %p - %u, %zd\n",
|
| 245 | __func__, mem->sm_addr, mem->k_addr, why, size);
|
| 246 |
|
| 247 | return 0;
|
| 248 | }
|
| 249 |
|
| 250 | |
| 251 | *
|
| 252 | * Converts a period in picoseconds to Hz.
|
| 253 | *
|
| 254 | * Note, we try to keep this in Hz to minimise rounding with
|
| 255 | * the limited PLL settings on the SM501.
|
| 256 |
|
| 257 |
|
| 258 | static unsigned long sm501fb_ps_to_hz(unsigned long psvalue)
|
| 259 | {
|
| 260 | unsigned long long numerator=1000000000000ULL;
|
| 261 |
|
| 262 |
|
| 263 | do_div(numerator, psvalue);
|
| 264 | return (unsigned long)numerator;
|
| 265 | }
|
| 266 |
|
| 267 |
|
| 268 |
|
| 269 | #define sm501fb_hz_to_ps(x) sm501fb_ps_to_hz(x)
|
| 270 |
|
| 271 | |
| 272 | *
|
| 273 | * Programs a linear 1.0 gamma ramp in case the gamma
|
| 274 | * correction is enabled without programming anything else.
|
| 275 |
|
| 276 |
|
| 277 | static void sm501fb_setup_gamma(struct sm501fb_info *fbi,
|
| 278 | unsigned long palette)
|
| 279 | {
|
| 280 | unsigned long value = 0;
|
| 281 | int offset;
|
| 282 |
|
| 283 |
|
| 284 | for (offset = 0; offset < 256 * 4; offset += 4) {
|
| 285 | smc501_writel(value, fbi->regs + palette + offset);
|
| 286 | value += 0x010101;
|
| 287 | }
|
| 288 | }
|
| 289 |
|
| 290 | |
| 291 | *
|
| 292 | * check common variables for both panel and crt
|
| 293 |
|
| 294 |
|
| 295 | static int sm501fb_check_var(struct fb_var_screeninfo *var,
|
| 296 | struct fb_info *info)
|
| 297 | {
|
| 298 | struct sm501fb_par *par = info->par;
|
| 299 | struct sm501fb_info *sm = par->info;
|
| 300 | unsigned long tmp;
|
| 301 |
|
| 302 |
|
| 303 |
|
| 304 | if (var->hsync_len > 255 || var->vsync_len > 63)
|
| 305 | return -EINVAL;
|
| 306 |
|
| 307 |
|
| 308 | if ((var->xres + var->right_margin) > 4096)
|
| 309 | return -EINVAL;
|
| 310 |
|
| 311 |
|
| 312 | if ((var->yres + var->lower_margin) > 2048)
|
| 313 | return -EINVAL;
|
| 314 |
|
| 315 |
|
| 316 |
|
| 317 | if (h_total(var) > 4096 || v_total(var) > 2048)
|
| 318 | return -EINVAL;
|
| 319 |
|
| 320 |
|
| 321 |
|
| 322 | tmp = (var->xres * var->bits_per_pixel) / 8;
|
| 323 | if ((tmp & 15) != 0)
|
| 324 | return -EINVAL;
|
| 325 |
|
| 326 |
|
| 327 |
|
| 328 | if (var->xres_virtual > 4096 || var->yres_virtual > 2048)
|
| 329 | return -EINVAL;
|
| 330 |
|
| 331 |
|
| 332 | if (var->xres + var->xoffset > var->xres_virtual)
|
| 333 | return -EINVAL;
|
| 334 |
|
| 335 | if (var->yres + var->yoffset > var->yres_virtual)
|
| 336 | return -EINVAL;
|
| 337 |
|
| 338 |
|
| 339 |
|
| 340 | if (var->bits_per_pixel <= 8)
|
| 341 | var->bits_per_pixel = 8;
|
| 342 | else if (var->bits_per_pixel <= 16)
|
| 343 | var->bits_per_pixel = 16;
|
| 344 | else if (var->bits_per_pixel == 24)
|
| 345 | var->bits_per_pixel = 32;
|
| 346 |
|
| 347 |
|
| 348 | switch(var->bits_per_pixel) {
|
| 349 | case 8:
|
| 350 | var->red.length = var->bits_per_pixel;
|
| 351 | var->red.offset = 0;
|
| 352 | var->green.length = var->bits_per_pixel;
|
| 353 | var->green.offset = 0;
|
| 354 | var->blue.length = var->bits_per_pixel;
|
| 355 | var->blue.offset = 0;
|
| 356 | var->transp.length = 0;
|
| 357 | var->transp.offset = 0;
|
| 358 |
|
| 359 | break;
|
| 360 |
|
| 361 | case 16:
|
| 362 | if (sm->pdata->flags & SM501_FBPD_SWAP_FB_ENDIAN) {
|
| 363 | var->blue.offset = 11;
|
| 364 | var->green.offset = 5;
|
| 365 | var->red.offset = 0;
|
| 366 | } else {
|
| 367 | var->red.offset = 11;
|
| 368 | var->green.offset = 5;
|
| 369 | var->blue.offset = 0;
|
| 370 | }
|
| 371 | var->transp.offset = 0;
|
| 372 |
|
| 373 | var->red.length = 5;
|
| 374 | var->green.length = 6;
|
| 375 | var->blue.length = 5;
|
| 376 | var->transp.length = 0;
|
| 377 | break;
|
| 378 |
|
| 379 | case 32:
|
| 380 | if (sm->pdata->flags & SM501_FBPD_SWAP_FB_ENDIAN) {
|
| 381 | var->transp.offset = 0;
|
| 382 | var->red.offset = 8;
|
| 383 | var->green.offset = 16;
|
| 384 | var->blue.offset = 24;
|
| 385 | } else {
|
| 386 | var->transp.offset = 24;
|
| 387 | var->red.offset = 16;
|
| 388 | var->green.offset = 8;
|
| 389 | var->blue.offset = 0;
|
| 390 | }
|
| 391 |
|
| 392 | var->red.length = 8;
|
| 393 | var->green.length = 8;
|
| 394 | var->blue.length = 8;
|
| 395 | var->transp.length = 0;
|
| 396 | break;
|
| 397 |
|
| 398 | default:
|
| 399 | return -EINVAL;
|
| 400 | }
|
| 401 |
|
| 402 | return 0;
|
| 403 | }
|
| 404 |
|
| 405 | |
| 406 | * sm501fb_check_var_crt():
|
| 407 | *
|
| 408 | * check the parameters for the CRT head, and either bring them
|
| 409 | * back into range, or return -EINVAL.
|
| 410 |
|
| 411 |
|
| 412 | static int sm501fb_check_var_crt(struct fb_var_screeninfo *var,
|
| 413 | struct fb_info *info)
|
| 414 | {
|
| 415 | return sm501fb_check_var(var, info);
|
| 416 | }
|
| 417 |
|
| 418 | |
| 419 | *
|
| 420 | * check the parameters for the CRT head, and either bring them
|
| 421 | * back into range, or return -EINVAL.
|
| 422 |
|
| 423 |
|
| 424 | static int sm501fb_check_var_pnl(struct fb_var_screeninfo *var,
|
| 425 | struct fb_info *info)
|
| 426 | {
|
| 427 | return sm501fb_check_var(var, info);
|
| 428 | }
|
| 429 |
|
| 430 | |
| 431 | *
|
| 432 | * set common registers for framebuffers
|
| 433 |
|
| 434 |
|
| 435 | static int sm501fb_set_par_common(struct fb_info *info,
|
| 436 | struct fb_var_screeninfo *var)
|
| 437 | {
|
| 438 | struct sm501fb_par *par = info->par;
|
| 439 | struct sm501fb_info *fbi = par->info;
|
| 440 | unsigned long pixclock;
|
| 441 | unsigned long sm501pixclock;
|
| 442 | unsigned int mem_type;
|
| 443 | unsigned int clock_type;
|
| 444 | unsigned int head_addr;
|
| 445 | unsigned int smem_len;
|
| 446 |
|
| 447 | dev_dbg(fbi->dev, "%s: %dx%d, bpp = %d, virtual %dx%d\n",
|
| 448 | __func__, var->xres, var->yres, var->bits_per_pixel,
|
| 449 | var->xres_virtual, var->yres_virtual);
|
| 450 |
|
| 451 | switch (par->head) {
|
| 452 | case HEAD_CRT:
|
| 453 | mem_type = SM501_MEMF_CRT;
|
| 454 | clock_type = SM501_CLOCK_V2XCLK;
|
| 455 | head_addr = SM501_DC_CRT_FB_ADDR;
|
| 456 | break;
|
| 457 |
|
| 458 | case HEAD_PANEL:
|
| 459 | mem_type = SM501_MEMF_PANEL;
|
| 460 | clock_type = SM501_CLOCK_P2XCLK;
|
| 461 | head_addr = SM501_DC_PANEL_FB_ADDR;
|
| 462 | break;
|
| 463 |
|
| 464 | default:
|
| 465 | mem_type = 0;
|
| 466 | head_addr = 0;
|
| 467 | clock_type = 0;
|
| 468 | }
|
| 469 |
|
| 470 | switch (var->bits_per_pixel) {
|
| 471 | case 8:
|
| 472 | info->fix.visual = FB_VISUAL_PSEUDOCOLOR;
|
| 473 | break;
|
| 474 |
|
| 475 | case 16:
|
| 476 | info->fix.visual = FB_VISUAL_TRUECOLOR;
|
| 477 | break;
|
| 478 |
|
| 479 | case 32:
|
| 480 | info->fix.visual = FB_VISUAL_TRUECOLOR;
|
| 481 | break;
|
| 482 | }
|
| 483 |
|
| 484 |
|
| 485 | info->fix.line_length = (var->xres_virtual * var->bits_per_pixel)/8;
|
| 486 | smem_len = info->fix.line_length * var->yres_virtual;
|
| 487 |
|
| 488 | dev_dbg(fbi->dev, "%s: line length = %u\n", __func__,
|
| 489 | info->fix.line_length);
|
| 490 |
|
| 491 | if (sm501_alloc_mem(fbi, &par->screen, mem_type, smem_len, smem_len)) {
|
| 492 | dev_err(fbi->dev, "no memory available\n");
|
| 493 | return -ENOMEM;
|
| 494 | }
|
| 495 |
|
| 496 | mutex_lock(&info->mm_lock);
|
| 497 | info->fix.smem_start = fbi->fbmem_res->start + par->screen.sm_addr;
|
| 498 | info->fix.smem_len = smem_len;
|
| 499 | mutex_unlock(&info->mm_lock);
|
| 500 |
|
| 501 | info->screen_base = fbi->fbmem + par->screen.sm_addr;
|
| 502 | info->screen_size = info->fix.smem_len;
|
| 503 |
|
| 504 |
|
| 505 |
|
| 506 | smc501_writel(par->screen.sm_addr | SM501_ADDR_FLIP,
|
| 507 | fbi->regs + head_addr);
|
| 508 |
|
| 509 |
|
| 510 |
|
| 511 | pixclock = sm501fb_ps_to_hz(var->pixclock);
|
| 512 |
|
| 513 | sm501pixclock = sm501_set_clock(fbi->dev->parent, clock_type,
|
| 514 | pixclock);
|
| 515 |
|
| 516 |
|
| 517 | var->pixclock = sm501fb_hz_to_ps(sm501pixclock);
|
| 518 |
|
| 519 | dev_dbg(fbi->dev, "%s: pixclock(ps) = %u, pixclock(Hz) = %lu, "
|
| 520 | "sm501pixclock = %lu, error = %ld%%\n",
|
| 521 | __func__, var->pixclock, pixclock, sm501pixclock,
|
| 522 | ((pixclock - sm501pixclock)*100)/pixclock);
|
| 523 |
|
| 524 | return 0;
|
| 525 | }
|
| 526 |
|
| 527 | |
| 528 | *
|
| 529 | * set the geometry registers for specified framebuffer.
|
| 530 |
|
| 531 |
|
| 532 | static void sm501fb_set_par_geometry(struct fb_info *info,
|
| 533 | struct fb_var_screeninfo *var)
|
| 534 | {
|
| 535 | struct sm501fb_par *par = info->par;
|
| 536 | struct sm501fb_info *fbi = par->info;
|
| 537 | void __iomem *base = fbi->regs;
|
| 538 | unsigned long reg;
|
| 539 |
|
| 540 | if (par->head == HEAD_CRT)
|
| 541 | base += SM501_DC_CRT_H_TOT;
|
| 542 | else
|
| 543 | base += SM501_DC_PANEL_H_TOT;
|
| 544 |
|
| 545 |
|
| 546 |
|
| 547 | reg = info->fix.line_length;
|
| 548 | reg |= ((var->xres * var->bits_per_pixel)/8) << 16;
|
| 549 |
|
| 550 | smc501_writel(reg, fbi->regs + (par->head == HEAD_CRT ?
|
| 551 | SM501_DC_CRT_FB_OFFSET : SM501_DC_PANEL_FB_OFFSET));
|
| 552 |
|
| 553 |
|
| 554 |
|
| 555 | reg = (h_total(var) - 1) << 16;
|
| 556 | reg |= (var->xres - 1);
|
| 557 |
|
| 558 | smc501_writel(reg, base + SM501_OFF_DC_H_TOT);
|
| 559 |
|
| 560 |
|
| 561 |
|
| 562 | reg = var->hsync_len << 16;
|
| 563 | reg |= var->xres + var->right_margin - 1;
|
| 564 |
|
| 565 | smc501_writel(reg, base + SM501_OFF_DC_H_SYNC);
|
| 566 |
|
| 567 |
|
| 568 |
|
| 569 | reg = (v_total(var) - 1) << 16;
|
| 570 | reg |= (var->yres - 1);
|
| 571 |
|
| 572 | smc501_writel(reg, base + SM501_OFF_DC_V_TOT);
|
| 573 |
|
| 574 |
|
| 575 | reg = var->vsync_len << 16;
|
| 576 | reg |= var->yres + var->lower_margin - 1;
|
| 577 |
|
| 578 | smc501_writel(reg, base + SM501_OFF_DC_V_SYNC);
|
| 579 | }
|
| 580 |
|
| 581 | |
| 582 | *
|
| 583 | * pan the CRT display output within an virtual framebuffer
|
| 584 |
|
| 585 |
|
| 586 | static int sm501fb_pan_crt(struct fb_var_screeninfo *var,
|
| 587 | struct fb_info *info)
|
| 588 | {
|
| 589 | struct sm501fb_par *par = info->par;
|
| 590 | struct sm501fb_info *fbi = par->info;
|
| 591 | unsigned int bytes_pixel = info->var.bits_per_pixel / 8;
|
| 592 | unsigned long reg;
|
| 593 | unsigned long xoffs;
|
| 594 |
|
| 595 | xoffs = var->xoffset * bytes_pixel;
|
| 596 |
|
| 597 | reg = smc501_readl(fbi->regs + SM501_DC_CRT_CONTROL);
|
| 598 |
|
| 599 | reg &= ~SM501_DC_CRT_CONTROL_PIXEL_MASK;
|
| 600 | reg |= ((xoffs & 15) / bytes_pixel) << 4;
|
| 601 | smc501_writel(reg, fbi->regs + SM501_DC_CRT_CONTROL);
|
| 602 |
|
| 603 | reg = (par->screen.sm_addr + xoffs +
|
| 604 | var->yoffset * info->fix.line_length);
|
| 605 | smc501_writel(reg | SM501_ADDR_FLIP, fbi->regs + SM501_DC_CRT_FB_ADDR);
|
| 606 |
|
| 607 | sm501fb_sync_regs(fbi);
|
| 608 | return 0;
|
| 609 | }
|
| 610 |
|
| 611 | |
| 612 | *
|
| 613 | * pan the panel display output within an virtual framebuffer
|
| 614 |
|
| 615 |
|
| 616 | static int sm501fb_pan_pnl(struct fb_var_screeninfo *var,
|
| 617 | struct fb_info *info)
|
| 618 | {
|
| 619 | struct sm501fb_par *par = info->par;
|
| 620 | struct sm501fb_info *fbi = par->info;
|
| 621 | unsigned long reg;
|
| 622 |
|
| 623 | reg = var->xoffset | (info->var.xres_virtual << 16);
|
| 624 | smc501_writel(reg, fbi->regs + SM501_DC_PANEL_FB_WIDTH);
|
| 625 |
|
| 626 | reg = var->yoffset | (info->var.yres_virtual << 16);
|
| 627 | smc501_writel(reg, fbi->regs + SM501_DC_PANEL_FB_HEIGHT);
|
| 628 |
|
| 629 | sm501fb_sync_regs(fbi);
|
| 630 | return 0;
|
| 631 | }
|
| 632 |
|
| 633 | |
| 634 | *
|
| 635 | * Set the CRT video mode from the fb_info structure
|
| 636 |
|
| 637 |
|
| 638 | static int sm501fb_set_par_crt(struct fb_info *info)
|
| 639 | {
|
| 640 | struct sm501fb_par *par = info->par;
|
| 641 | struct sm501fb_info *fbi = par->info;
|
| 642 | struct fb_var_screeninfo *var = &info->var;
|
| 643 | unsigned long control;
|
| 644 | int ret;
|
| 645 |
|
| 646 |
|
| 647 |
|
| 648 | dev_dbg(fbi->dev, "%s(%p)\n", __func__, info);
|
| 649 |
|
| 650 |
|
| 651 | sm501_misc_control(fbi->dev->parent, 0, SM501_MISC_DAC_POWER);
|
| 652 |
|
| 653 | control = smc501_readl(fbi->regs + SM501_DC_CRT_CONTROL);
|
| 654 |
|
| 655 | control &= (SM501_DC_CRT_CONTROL_PIXEL_MASK |
|
| 656 | SM501_DC_CRT_CONTROL_GAMMA |
|
| 657 | SM501_DC_CRT_CONTROL_BLANK |
|
| 658 | SM501_DC_CRT_CONTROL_SEL |
|
| 659 | SM501_DC_CRT_CONTROL_CP |
|
| 660 | SM501_DC_CRT_CONTROL_TVP);
|
| 661 |
|
| 662 |
|
| 663 |
|
| 664 | if ((var->sync & FB_SYNC_HOR_HIGH_ACT) == 0)
|
| 665 | control |= SM501_DC_CRT_CONTROL_HSP;
|
| 666 |
|
| 667 | if ((var->sync & FB_SYNC_VERT_HIGH_ACT) == 0)
|
| 668 | control |= SM501_DC_CRT_CONTROL_VSP;
|
| 669 |
|
| 670 | if ((control & SM501_DC_CRT_CONTROL_SEL) == 0) {
|
| 671 |
|
| 672 |
|
| 673 | sm501_alloc_mem(fbi, &par->screen, SM501_MEMF_CRT, 0,
|
| 674 | info->fix.smem_len);
|
| 675 | goto out_update;
|
| 676 | }
|
| 677 |
|
| 678 | ret = sm501fb_set_par_common(info, var);
|
| 679 | if (ret) {
|
| 680 | dev_err(fbi->dev, "failed to set common parameters\n");
|
| 681 | return ret;
|
| 682 | }
|
| 683 |
|
| 684 | sm501fb_pan_crt(var, info);
|
| 685 | sm501fb_set_par_geometry(info, var);
|
| 686 |
|
| 687 | control |= SM501_FIFO_3;
|
| 688 |
|
| 689 | switch(var->bits_per_pixel) {
|
| 690 | case 8:
|
| 691 | control |= SM501_DC_CRT_CONTROL_8BPP;
|
| 692 | break;
|
| 693 |
|
| 694 | case 16:
|
| 695 | control |= SM501_DC_CRT_CONTROL_16BPP;
|
| 696 | sm501fb_setup_gamma(fbi, SM501_DC_CRT_PALETTE);
|
| 697 | break;
|
| 698 |
|
| 699 | case 32:
|
| 700 | control |= SM501_DC_CRT_CONTROL_32BPP;
|
| 701 | sm501fb_setup_gamma(fbi, SM501_DC_CRT_PALETTE);
|
| 702 | break;
|
| 703 |
|
| 704 | default:
|
| 705 | BUG();
|
| 706 | }
|
| 707 |
|
| 708 | control |= SM501_DC_CRT_CONTROL_SEL;
|
| 709 | control |= SM501_DC_CRT_CONTROL_TE;
|
| 710 | control |= SM501_DC_CRT_CONTROL_ENABLE;
|
| 711 |
|
| 712 | out_update:
|
| 713 | dev_dbg(fbi->dev, "new control is %08lx\n", control);
|
| 714 |
|
| 715 | smc501_writel(control, fbi->regs + SM501_DC_CRT_CONTROL);
|
| 716 | sm501fb_sync_regs(fbi);
|
| 717 |
|
| 718 | return 0;
|
| 719 | }
|
| 720 |
|
| 721 | static void sm501fb_panel_power(struct sm501fb_info *fbi, int to)
|
| 722 | {
|
| 723 | unsigned long control;
|
| 724 | void __iomem *ctrl_reg = fbi->regs + SM501_DC_PANEL_CONTROL;
|
| 725 | struct sm501_platdata_fbsub *pd = fbi->pdata->fb_pnl;
|
| 726 |
|
| 727 | control = smc501_readl(ctrl_reg);
|
| 728 |
|
| 729 | if (to && (control & SM501_DC_PANEL_CONTROL_VDD) == 0) {
|
| 730 |
|
| 731 |
|
| 732 | control |= SM501_DC_PANEL_CONTROL_VDD;
|
| 733 | smc501_writel(control, ctrl_reg);
|
| 734 | sm501fb_sync_regs(fbi);
|
| 735 | mdelay(10);
|
| 736 |
|
| 737 | control |= SM501_DC_PANEL_CONTROL_DATA;
|
| 738 | smc501_writel(control, ctrl_reg);
|
| 739 | sm501fb_sync_regs(fbi);
|
| 740 | mdelay(10);
|
| 741 |
|
| 742 |
|
| 743 |
|
| 744 | if (!(pd->flags & SM501FB_FLAG_PANEL_NO_VBIASEN)) {
|
| 745 | if (pd->flags & SM501FB_FLAG_PANEL_INV_VBIASEN)
|
| 746 | control &= ~SM501_DC_PANEL_CONTROL_BIAS;
|
| 747 | else
|
| 748 | control |= SM501_DC_PANEL_CONTROL_BIAS;
|
| 749 |
|
| 750 | smc501_writel(control, ctrl_reg);
|
| 751 | sm501fb_sync_regs(fbi);
|
| 752 | mdelay(10);
|
| 753 | }
|
| 754 |
|
| 755 | if (!(pd->flags & SM501FB_FLAG_PANEL_NO_FPEN)) {
|
| 756 | if (pd->flags & SM501FB_FLAG_PANEL_INV_FPEN)
|
| 757 | control &= ~SM501_DC_PANEL_CONTROL_FPEN;
|
| 758 | else
|
| 759 | control |= SM501_DC_PANEL_CONTROL_FPEN;
|
| 760 |
|
| 761 | smc501_writel(control, ctrl_reg);
|
| 762 | sm501fb_sync_regs(fbi);
|
| 763 | mdelay(10);
|
| 764 | }
|
| 765 | } else if (!to && (control & SM501_DC_PANEL_CONTROL_VDD) != 0) {
|
| 766 |
|
| 767 | if (!(pd->flags & SM501FB_FLAG_PANEL_NO_FPEN)) {
|
| 768 | if (pd->flags & SM501FB_FLAG_PANEL_INV_FPEN)
|
| 769 | control |= SM501_DC_PANEL_CONTROL_FPEN;
|
| 770 | else
|
| 771 | control &= ~SM501_DC_PANEL_CONTROL_FPEN;
|
| 772 |
|
| 773 | smc501_writel(control, ctrl_reg);
|
| 774 | sm501fb_sync_regs(fbi);
|
| 775 | mdelay(10);
|
| 776 | }
|
| 777 |
|
| 778 | if (!(pd->flags & SM501FB_FLAG_PANEL_NO_VBIASEN)) {
|
| 779 | if (pd->flags & SM501FB_FLAG_PANEL_INV_VBIASEN)
|
| 780 | control |= SM501_DC_PANEL_CONTROL_BIAS;
|
| 781 | else
|
| 782 | control &= ~SM501_DC_PANEL_CONTROL_BIAS;
|
| 783 |
|
| 784 | smc501_writel(control, ctrl_reg);
|
| 785 | sm501fb_sync_regs(fbi);
|
| 786 | mdelay(10);
|
| 787 | }
|
| 788 |
|
| 789 | control &= ~SM501_DC_PANEL_CONTROL_DATA;
|
| 790 | smc501_writel(control, ctrl_reg);
|
| 791 | sm501fb_sync_regs(fbi);
|
| 792 | mdelay(10);
|
| 793 |
|
| 794 | control &= ~SM501_DC_PANEL_CONTROL_VDD;
|
| 795 | smc501_writel(control, ctrl_reg);
|
| 796 | sm501fb_sync_regs(fbi);
|
| 797 | mdelay(10);
|
| 798 | }
|
| 799 |
|
| 800 | sm501fb_sync_regs(fbi);
|
| 801 | }
|
| 802 |
|
| 803 | |
| 804 | *
|
| 805 | * Set the panel video mode from the fb_info structure
|
| 806 |
|
| 807 |
|
| 808 | static int sm501fb_set_par_pnl(struct fb_info *info)
|
| 809 | {
|
| 810 | struct sm501fb_par *par = info->par;
|
| 811 | struct sm501fb_info *fbi = par->info;
|
| 812 | struct fb_var_screeninfo *var = &info->var;
|
| 813 | unsigned long control;
|
| 814 | unsigned long reg;
|
| 815 | int ret;
|
| 816 |
|
| 817 | dev_dbg(fbi->dev, "%s(%p)\n", __func__, info);
|
| 818 |
|
| 819 |
|
| 820 |
|
| 821 | ret = sm501fb_set_par_common(info, var);
|
| 822 | if (ret)
|
| 823 | return ret;
|
| 824 |
|
| 825 | sm501fb_pan_pnl(var, info);
|
| 826 | sm501fb_set_par_geometry(info, var);
|
| 827 |
|
| 828 |
|
| 829 |
|
| 830 | control = smc501_readl(fbi->regs + SM501_DC_PANEL_CONTROL);
|
| 831 | control &= (SM501_DC_PANEL_CONTROL_GAMMA |
|
| 832 | SM501_DC_PANEL_CONTROL_VDD |
|
| 833 | SM501_DC_PANEL_CONTROL_DATA |
|
| 834 | SM501_DC_PANEL_CONTROL_BIAS |
|
| 835 | SM501_DC_PANEL_CONTROL_FPEN |
|
| 836 | SM501_DC_PANEL_CONTROL_CP |
|
| 837 | SM501_DC_PANEL_CONTROL_CK |
|
| 838 | SM501_DC_PANEL_CONTROL_HP |
|
| 839 | SM501_DC_PANEL_CONTROL_VP |
|
| 840 | SM501_DC_PANEL_CONTROL_HPD |
|
| 841 | SM501_DC_PANEL_CONTROL_VPD);
|
| 842 |
|
| 843 | control |= SM501_FIFO_3;
|
| 844 |
|
| 845 | switch(var->bits_per_pixel) {
|
| 846 | case 8:
|
| 847 | control |= SM501_DC_PANEL_CONTROL_8BPP;
|
| 848 | break;
|
| 849 |
|
| 850 | case 16:
|
| 851 | control |= SM501_DC_PANEL_CONTROL_16BPP;
|
| 852 | sm501fb_setup_gamma(fbi, SM501_DC_PANEL_PALETTE);
|
| 853 | break;
|
| 854 |
|
| 855 | case 32:
|
| 856 | control |= SM501_DC_PANEL_CONTROL_32BPP;
|
| 857 | sm501fb_setup_gamma(fbi, SM501_DC_PANEL_PALETTE);
|
| 858 | break;
|
| 859 |
|
| 860 | default:
|
| 861 | BUG();
|
| 862 | }
|
| 863 |
|
| 864 | smc501_writel(0x0, fbi->regs + SM501_DC_PANEL_PANNING_CONTROL);
|
| 865 |
|
| 866 |
|
| 867 |
|
| 868 | smc501_writel(0x00, fbi->regs + SM501_DC_PANEL_TL_LOC);
|
| 869 |
|
| 870 | reg = var->xres - 1;
|
| 871 | reg |= (var->yres - 1) << 16;
|
| 872 |
|
| 873 | smc501_writel(reg, fbi->regs + SM501_DC_PANEL_BR_LOC);
|
| 874 |
|
| 875 |
|
| 876 |
|
| 877 | control |= SM501_DC_PANEL_CONTROL_TE;
|
| 878 | control |= SM501_DC_PANEL_CONTROL_EN;
|
| 879 |
|
| 880 | if ((var->sync & FB_SYNC_HOR_HIGH_ACT) == 0)
|
| 881 | control |= SM501_DC_PANEL_CONTROL_HSP;
|
| 882 |
|
| 883 | if ((var->sync & FB_SYNC_VERT_HIGH_ACT) == 0)
|
| 884 | control |= SM501_DC_PANEL_CONTROL_VSP;
|
| 885 |
|
| 886 | smc501_writel(control, fbi->regs + SM501_DC_PANEL_CONTROL);
|
| 887 | sm501fb_sync_regs(fbi);
|
| 888 |
|
| 889 |
|
| 890 |
|
| 891 | sm501_modify_reg(fbi->dev->parent, SM501_SYSTEM_CONTROL,
|
| 892 | 0, SM501_SYSCTRL_PANEL_TRISTATE);
|
| 893 |
|
| 894 |
|
| 895 | sm501fb_panel_power(fbi, 1);
|
| 896 | return 0;
|
| 897 | }
|
| 898 |
|
| 899 |
|
| 900 | |
| 901 | *
|
| 902 | * convert a colour value into a field position
|
| 903 | *
|
| 904 | * from pxafb.c
|
| 905 |
|
| 906 |
|
| 907 | static inline unsigned int chan_to_field(unsigned int chan,
|
| 908 | struct fb_bitfield *bf)
|
| 909 | {
|
| 910 | chan &= 0xffff;
|
| 911 | chan >>= 16 - bf->length;
|
| 912 | return chan << bf->offset;
|
| 913 | }
|
| 914 |
|
| 915 | |
| 916 | *
|
| 917 | * set the colour mapping for modes that support palettised data
|
| 918 |
|
| 919 |
|
| 920 | static int sm501fb_setcolreg(unsigned regno,
|
| 921 | unsigned red, unsigned green, unsigned blue,
|
| 922 | unsigned transp, struct fb_info *info)
|
| 923 | {
|
| 924 | struct sm501fb_par *par = info->par;
|
| 925 | struct sm501fb_info *fbi = par->info;
|
| 926 | void __iomem *base = fbi->regs;
|
| 927 | unsigned int val;
|
| 928 |
|
| 929 | if (par->head == HEAD_CRT)
|
| 930 | base += SM501_DC_CRT_PALETTE;
|
| 931 | else
|
| 932 | base += SM501_DC_PANEL_PALETTE;
|
| 933 |
|
| 934 | switch (info->fix.visual) {
|
| 935 | case FB_VISUAL_TRUECOLOR:
|
| 936 |
|
| 937 |
|
| 938 | if (regno < 16) {
|
| 939 | u32 *pal = par->pseudo_palette;
|
| 940 |
|
| 941 | val = chan_to_field(red, &info->var.red);
|
| 942 | val |= chan_to_field(green, &info->var.green);
|
| 943 | val |= chan_to_field(blue, &info->var.blue);
|
| 944 |
|
| 945 | pal[regno] = val;
|
| 946 | }
|
| 947 | break;
|
| 948 |
|
| 949 | case FB_VISUAL_PSEUDOCOLOR:
|
| 950 | if (regno < 256) {
|
| 951 | val = (red >> 8) << 16;
|
| 952 | val |= (green >> 8) << 8;
|
| 953 | val |= blue >> 8;
|
| 954 |
|
| 955 | smc501_writel(val, base + (regno * 4));
|
| 956 | }
|
| 957 |
|
| 958 | break;
|
| 959 |
|
| 960 | default:
|
| 961 | return 1;
|
| 962 | }
|
| 963 |
|
| 964 | return 0;
|
| 965 | }
|
| 966 |
|
| 967 | |
| 968 | *
|
| 969 | * Blank or un-blank the panel interface
|
| 970 |
|
| 971 |
|
| 972 | static int sm501fb_blank_pnl(int blank_mode, struct fb_info *info)
|
| 973 | {
|
| 974 | struct sm501fb_par *par = info->par;
|
| 975 | struct sm501fb_info *fbi = par->info;
|
| 976 |
|
| 977 | dev_dbg(fbi->dev, "%s(mode=%d, %p)\n", __func__, blank_mode, info);
|
| 978 |
|
| 979 | switch (blank_mode) {
|
| 980 | case FB_BLANK_POWERDOWN:
|
| 981 | sm501fb_panel_power(fbi, 0);
|
| 982 | break;
|
| 983 |
|
| 984 | case FB_BLANK_UNBLANK:
|
| 985 | sm501fb_panel_power(fbi, 1);
|
| 986 | break;
|
| 987 |
|
| 988 | case FB_BLANK_NORMAL:
|
| 989 | case FB_BLANK_VSYNC_SUSPEND:
|
| 990 | case FB_BLANK_HSYNC_SUSPEND:
|
| 991 | default:
|
| 992 | return 1;
|
| 993 | }
|
| 994 |
|
| 995 | return 0;
|
| 996 | }
|
| 997 |
|
| 998 | |
| 999 | *
|
| 1000 | * Blank or un-blank the crt interface
|
| 1001 |
|
| 1002 |
|
| 1003 | static int sm501fb_blank_crt(int blank_mode, struct fb_info *info)
|
| 1004 | {
|
| 1005 | struct sm501fb_par *par = info->par;
|
| 1006 | struct sm501fb_info *fbi = par->info;
|
| 1007 | unsigned long ctrl;
|
| 1008 |
|
| 1009 | dev_dbg(fbi->dev, "%s(mode=%d, %p)\n", __func__, blank_mode, info);
|
| 1010 |
|
| 1011 | ctrl = smc501_readl(fbi->regs + SM501_DC_CRT_CONTROL);
|
| 1012 |
|
| 1013 | switch (blank_mode) {
|
| 1014 | case FB_BLANK_POWERDOWN:
|
| 1015 | ctrl &= ~SM501_DC_CRT_CONTROL_ENABLE;
|
| 1016 | sm501_misc_control(fbi->dev->parent, SM501_MISC_DAC_POWER, 0);
|
| 1017 | fallthrough;
|
| 1018 |
|
| 1019 | case FB_BLANK_NORMAL:
|
| 1020 | ctrl |= SM501_DC_CRT_CONTROL_BLANK;
|
| 1021 | break;
|
| 1022 |
|
| 1023 | case FB_BLANK_UNBLANK:
|
| 1024 | ctrl &= ~SM501_DC_CRT_CONTROL_BLANK;
|
| 1025 | ctrl |= SM501_DC_CRT_CONTROL_ENABLE;
|
| 1026 | sm501_misc_control(fbi->dev->parent, 0, SM501_MISC_DAC_POWER);
|
| 1027 | break;
|
| 1028 |
|
| 1029 | case FB_BLANK_VSYNC_SUSPEND:
|
| 1030 | case FB_BLANK_HSYNC_SUSPEND:
|
| 1031 | default:
|
| 1032 | return 1;
|
| 1033 |
|
| 1034 | }
|
| 1035 |
|
| 1036 | smc501_writel(ctrl, fbi->regs + SM501_DC_CRT_CONTROL);
|
| 1037 | sm501fb_sync_regs(fbi);
|
| 1038 |
|
| 1039 | return 0;
|
| 1040 | }
|
| 1041 |
|
| 1042 | |
| 1043 | *
|
| 1044 | * set or change the hardware cursor parameters
|
| 1045 |
|
| 1046 |
|
| 1047 | static int sm501fb_cursor(struct fb_info *info, struct fb_cursor *cursor)
|
| 1048 | {
|
| 1049 | struct sm501fb_par *par = info->par;
|
| 1050 | struct sm501fb_info *fbi = par->info;
|
| 1051 | void __iomem *base = fbi->regs;
|
| 1052 | unsigned long hwc_addr;
|
| 1053 | unsigned long fg, bg;
|
| 1054 |
|
| 1055 | dev_dbg(fbi->dev, "%s(%p,%p)\n", __func__, info, cursor);
|
| 1056 |
|
| 1057 | if (par->head == HEAD_CRT)
|
| 1058 | base += SM501_DC_CRT_HWC_BASE;
|
| 1059 | else
|
| 1060 | base += SM501_DC_PANEL_HWC_BASE;
|
| 1061 |
|
| 1062 |
|
| 1063 |
|
| 1064 | if (cursor->image.width > 64)
|
| 1065 | return -EINVAL;
|
| 1066 |
|
| 1067 | if (cursor->image.height > 64)
|
| 1068 | return -EINVAL;
|
| 1069 |
|
| 1070 | if (cursor->image.depth > 1)
|
| 1071 | return -EINVAL;
|
| 1072 |
|
| 1073 | hwc_addr = smc501_readl(base + SM501_OFF_HWC_ADDR);
|
| 1074 |
|
| 1075 | if (cursor->enable)
|
| 1076 | smc501_writel(hwc_addr | SM501_HWC_EN,
|
| 1077 | base + SM501_OFF_HWC_ADDR);
|
| 1078 | else
|
| 1079 | smc501_writel(hwc_addr & ~SM501_HWC_EN,
|
| 1080 | base + SM501_OFF_HWC_ADDR);
|
| 1081 |
|
| 1082 |
|
| 1083 | if (cursor->set & FB_CUR_SETPOS) {
|
| 1084 | unsigned int x = cursor->image.dx;
|
| 1085 | unsigned int y = cursor->image.dy;
|
| 1086 |
|
| 1087 | if (x >= 2048 || y >= 2048 )
|
| 1088 | return -EINVAL;
|
| 1089 |
|
| 1090 | dev_dbg(fbi->dev, "set position %d,%d\n", x, y);
|
| 1091 |
|
| 1092 | |
| 1093 |
|
| 1094 | smc501_writel(x | (y << 16), base + SM501_OFF_HWC_LOC);
|
| 1095 | }
|
| 1096 |
|
| 1097 | if (cursor->set & FB_CUR_SETCMAP) {
|
| 1098 | unsigned int bg_col = cursor->image.bg_color;
|
| 1099 | unsigned int fg_col = cursor->image.fg_color;
|
| 1100 |
|
| 1101 | dev_dbg(fbi->dev, "%s: update cmap (%08x,%08x)\n",
|
| 1102 | __func__, bg_col, fg_col);
|
| 1103 |
|
| 1104 | bg = ((info->cmap.red[bg_col] & 0xF8) << 8) |
|
| 1105 | ((info->cmap.green[bg_col] & 0xFC) << 3) |
|
| 1106 | ((info->cmap.blue[bg_col] & 0xF8) >> 3);
|
| 1107 |
|
| 1108 | fg = ((info->cmap.red[fg_col] & 0xF8) << 8) |
|
| 1109 | ((info->cmap.green[fg_col] & 0xFC) << 3) |
|
| 1110 | ((info->cmap.blue[fg_col] & 0xF8) >> 3);
|
| 1111 |
|
| 1112 | dev_dbg(fbi->dev, "fgcol %08lx, bgcol %08lx\n", fg, bg);
|
| 1113 |
|
| 1114 | smc501_writel(bg, base + SM501_OFF_HWC_COLOR_1_2);
|
| 1115 | smc501_writel(fg, base + SM501_OFF_HWC_COLOR_3);
|
| 1116 | }
|
| 1117 |
|
| 1118 | if (cursor->set & FB_CUR_SETSIZE ||
|
| 1119 | cursor->set & (FB_CUR_SETIMAGE | FB_CUR_SETSHAPE)) {
|
| 1120 | |
| 1121 | * clears it to transparent then combines the cursor
|
| 1122 | * shape plane with the colour plane to set the
|
| 1123 | * cursor
|
| 1124 | int x, y;
|
| 1125 | const unsigned char *pcol = cursor->image.data;
|
| 1126 | const unsigned char *pmsk = cursor->mask;
|
| 1127 | void __iomem *dst = par->cursor.k_addr;
|
| 1128 | unsigned char dcol = 0;
|
| 1129 | unsigned char dmsk = 0;
|
| 1130 | unsigned int op;
|
| 1131 |
|
| 1132 | dev_dbg(fbi->dev, "%s: setting shape (%d,%d)\n",
|
| 1133 | __func__, cursor->image.width, cursor->image.height);
|
| 1134 |
|
| 1135 | for (op = 0; op < (64*64*2)/8; op+=4)
|
| 1136 | smc501_writel(0x0, dst + op);
|
| 1137 |
|
| 1138 | for (y = 0; y < cursor->image.height; y++) {
|
| 1139 | for (x = 0; x < cursor->image.width; x++) {
|
| 1140 | if ((x % 8) == 0) {
|
| 1141 | dcol = *pcol++;
|
| 1142 | dmsk = *pmsk++;
|
| 1143 | } else {
|
| 1144 | dcol >>= 1;
|
| 1145 | dmsk >>= 1;
|
| 1146 | }
|
| 1147 |
|
| 1148 | if (dmsk & 1) {
|
| 1149 | op = (dcol & 1) ? 1 : 3;
|
| 1150 | op <<= ((x % 4) * 2);
|
| 1151 |
|
| 1152 | op |= readb(dst + (x / 4));
|
| 1153 | writeb(op, dst + (x / 4));
|
| 1154 | }
|
| 1155 | }
|
| 1156 | dst += (64*2)/8;
|
| 1157 | }
|
| 1158 | }
|
| 1159 |
|
| 1160 | sm501fb_sync_regs(fbi);
|
| 1161 | return 0;
|
| 1162 | }
|
| 1163 |
|
| 1164 | |
| 1165 | *
|
| 1166 | * device attribute code to show where the crt output is sourced from
|
| 1167 |
|
| 1168 |
|
| 1169 | static ssize_t sm501fb_crtsrc_show(struct device *dev,
|
| 1170 | struct device_attribute *attr, char *buf)
|
| 1171 | {
|
| 1172 | struct sm501fb_info *info = dev_get_drvdata(dev);
|
| 1173 | unsigned long ctrl;
|
| 1174 |
|
| 1175 | ctrl = smc501_readl(info->regs + SM501_DC_CRT_CONTROL);
|
| 1176 | ctrl &= SM501_DC_CRT_CONTROL_SEL;
|
| 1177 |
|
| 1178 | return sysfs_emit(buf, "%s\n", ctrl ? "crt" : "panel");
|
| 1179 | }
|
| 1180 |
|
| 1181 | |
| 1182 | *
|
| 1183 | * device attribute code to set where the crt output is sourced from
|
| 1184 |
|
| 1185 |
|
| 1186 | static ssize_t sm501fb_crtsrc_store(struct device *dev,
|
| 1187 | struct device_attribute *attr,
|
| 1188 | const char *buf, size_t len)
|
| 1189 | {
|
| 1190 | struct sm501fb_info *info = dev_get_drvdata(dev);
|
| 1191 | enum sm501_controller head;
|
| 1192 | unsigned long ctrl;
|
| 1193 |
|
| 1194 | if (len < 1)
|
| 1195 | return -EINVAL;
|
| 1196 |
|
| 1197 | if (strncasecmp(buf, "crt", 3) == 0)
|
| 1198 | head = HEAD_CRT;
|
| 1199 | else if (strncasecmp(buf, "panel", 5) == 0)
|
| 1200 | head = HEAD_PANEL;
|
| 1201 | else
|
| 1202 | return -EINVAL;
|
| 1203 |
|
| 1204 | dev_info(dev, "setting crt source to head %d\n", head);
|
| 1205 |
|
| 1206 | ctrl = smc501_readl(info->regs + SM501_DC_CRT_CONTROL);
|
| 1207 |
|
| 1208 | if (head == HEAD_CRT) {
|
| 1209 | ctrl |= SM501_DC_CRT_CONTROL_SEL;
|
| 1210 | ctrl |= SM501_DC_CRT_CONTROL_ENABLE;
|
| 1211 | ctrl |= SM501_DC_CRT_CONTROL_TE;
|
| 1212 | } else {
|
| 1213 | ctrl &= ~SM501_DC_CRT_CONTROL_SEL;
|
| 1214 | ctrl &= ~SM501_DC_CRT_CONTROL_ENABLE;
|
| 1215 | ctrl &= ~SM501_DC_CRT_CONTROL_TE;
|
| 1216 | }
|
| 1217 |
|
| 1218 | smc501_writel(ctrl, info->regs + SM501_DC_CRT_CONTROL);
|
| 1219 | sm501fb_sync_regs(info);
|
| 1220 |
|
| 1221 | return len;
|
| 1222 | }
|
| 1223 |
|
| 1224 |
|
| 1225 | static DEVICE_ATTR(crt_src, 0664, sm501fb_crtsrc_show, sm501fb_crtsrc_store);
|
| 1226 |
|
| 1227 | |
| 1228 | *
|
| 1229 | * show the primary sm501 registers
|
| 1230 |
|
| 1231 | static int sm501fb_show_regs(struct sm501fb_info *info, char *ptr,
|
| 1232 | unsigned int start, unsigned int len)
|
| 1233 | {
|
| 1234 | void __iomem *mem = info->regs;
|
| 1235 | char *buf = ptr;
|
| 1236 | unsigned int reg;
|
| 1237 |
|
| 1238 | for (reg = start; reg < (len + start); reg += 4)
|
| 1239 | ptr += sprintf(ptr, "%08x = %08x\n", reg,
|
| 1240 | smc501_readl(mem + reg));
|
| 1241 |
|
| 1242 | return ptr - buf;
|
| 1243 | }
|
| 1244 |
|
| 1245 | |
| 1246 | *
|
| 1247 | * show the crt control and cursor registers
|
| 1248 |
|
| 1249 |
|
| 1250 | static ssize_t sm501fb_debug_show_crt(struct device *dev,
|
| 1251 | struct device_attribute *attr, char *buf)
|
| 1252 | {
|
| 1253 | struct sm501fb_info *info = dev_get_drvdata(dev);
|
| 1254 | char *ptr = buf;
|
| 1255 |
|
| 1256 | ptr += sm501fb_show_regs(info, ptr, SM501_DC_CRT_CONTROL, 0x40);
|
| 1257 | ptr += sm501fb_show_regs(info, ptr, SM501_DC_CRT_HWC_BASE, 0x10);
|
| 1258 |
|
| 1259 | return ptr - buf;
|
| 1260 | }
|
| 1261 |
|
| 1262 | static DEVICE_ATTR(fbregs_crt, 0444, sm501fb_debug_show_crt, NULL);
|
| 1263 |
|
| 1264 | |
| 1265 | *
|
| 1266 | * show the panel control and cursor registers
|
| 1267 |
|
| 1268 |
|
| 1269 | static ssize_t sm501fb_debug_show_pnl(struct device *dev,
|
| 1270 | struct device_attribute *attr, char *buf)
|
| 1271 | {
|
| 1272 | struct sm501fb_info *info = dev_get_drvdata(dev);
|
| 1273 | char *ptr = buf;
|
| 1274 |
|
| 1275 | ptr += sm501fb_show_regs(info, ptr, 0x0, 0x40);
|
| 1276 | ptr += sm501fb_show_regs(info, ptr, SM501_DC_PANEL_HWC_BASE, 0x10);
|
| 1277 |
|
| 1278 | return ptr - buf;
|
| 1279 | }
|
| 1280 |
|
| 1281 | static DEVICE_ATTR(fbregs_pnl, 0444, sm501fb_debug_show_pnl, NULL);
|
| 1282 |
|
| 1283 | static struct attribute *sm501fb_attrs[] = {
|
| 1284 | &dev_attr_crt_src.attr,
|
| 1285 | &dev_attr_fbregs_pnl.attr,
|
| 1286 | &dev_attr_fbregs_crt.attr,
|
| 1287 | NULL,
|
| 1288 | };
|
| 1289 | ATTRIBUTE_GROUPS(sm501fb);
|
| 1290 |
|
| 1291 |
|
| 1292 | static int sm501fb_sync(struct fb_info *info)
|
| 1293 | {
|
| 1294 | int count = 1000000;
|
| 1295 | struct sm501fb_par *par = info->par;
|
| 1296 | struct sm501fb_info *fbi = par->info;
|
| 1297 |
|
| 1298 |
|
| 1299 | while ((count > 0) &&
|
| 1300 | (smc501_readl(fbi->regs + SM501_SYSTEM_CONTROL) &
|
| 1301 | SM501_SYSCTRL_2D_ENGINE_STATUS) != 0)
|
| 1302 | count--;
|
| 1303 |
|
| 1304 | if (count <= 0) {
|
| 1305 | fb_err(info, "Timeout waiting for 2d engine sync\n");
|
| 1306 | return 1;
|
| 1307 | }
|
| 1308 | return 0;
|
| 1309 | }
|
| 1310 |
|
| 1311 | static void sm501fb_copyarea(struct fb_info *info, const struct fb_copyarea *area)
|
| 1312 | {
|
| 1313 | struct sm501fb_par *par = info->par;
|
| 1314 | struct sm501fb_info *fbi = par->info;
|
| 1315 | int width = area->width;
|
| 1316 | int height = area->height;
|
| 1317 | int sx = area->sx;
|
| 1318 | int sy = area->sy;
|
| 1319 | int dx = area->dx;
|
| 1320 | int dy = area->dy;
|
| 1321 | unsigned long rtl = 0;
|
| 1322 |
|
| 1323 |
|
| 1324 | if ((sx >= info->var.xres_virtual) ||
|
| 1325 | (sy >= info->var.yres_virtual))
|
| 1326 |
|
| 1327 | return;
|
| 1328 | if ((sx + width) >= info->var.xres_virtual)
|
| 1329 | width = info->var.xres_virtual - sx - 1;
|
| 1330 | if ((sy + height) >= info->var.yres_virtual)
|
| 1331 | height = info->var.yres_virtual - sy - 1;
|
| 1332 |
|
| 1333 |
|
| 1334 | if ((dx >= info->var.xres_virtual) ||
|
| 1335 | (dy >= info->var.yres_virtual))
|
| 1336 |
|
| 1337 | return;
|
| 1338 | if ((dx + width) >= info->var.xres_virtual)
|
| 1339 | width = info->var.xres_virtual - dx - 1;
|
| 1340 | if ((dy + height) >= info->var.yres_virtual)
|
| 1341 | height = info->var.yres_virtual - dy - 1;
|
| 1342 |
|
| 1343 | if ((sx < dx) || (sy < dy)) {
|
| 1344 | rtl = 1 << 27;
|
| 1345 | sx += width - 1;
|
| 1346 | dx += width - 1;
|
| 1347 | sy += height - 1;
|
| 1348 | dy += height - 1;
|
| 1349 | }
|
| 1350 |
|
| 1351 | if (sm501fb_sync(info))
|
| 1352 | return;
|
| 1353 |
|
| 1354 |
|
| 1355 | smc501_writel(par->screen.sm_addr, fbi->regs2d + SM501_2D_SOURCE_BASE);
|
| 1356 | smc501_writel(par->screen.sm_addr,
|
| 1357 | fbi->regs2d + SM501_2D_DESTINATION_BASE);
|
| 1358 |
|
| 1359 |
|
| 1360 | smc501_writel((info->var.xres << 16) | info->var.xres,
|
| 1361 | fbi->regs2d + SM501_2D_WINDOW_WIDTH);
|
| 1362 |
|
| 1363 |
|
| 1364 | smc501_writel((info->var.xres_virtual << 16) | info->var.xres_virtual,
|
| 1365 | fbi->regs2d + SM501_2D_PITCH);
|
| 1366 |
|
| 1367 |
|
| 1368 | switch (info->var.bits_per_pixel) {
|
| 1369 | case 8:
|
| 1370 | smc501_writel(0, fbi->regs2d + SM501_2D_STRETCH);
|
| 1371 | break;
|
| 1372 | case 16:
|
| 1373 | smc501_writel(0x00100000, fbi->regs2d + SM501_2D_STRETCH);
|
| 1374 | break;
|
| 1375 | case 32:
|
| 1376 | smc501_writel(0x00200000, fbi->regs2d + SM501_2D_STRETCH);
|
| 1377 | break;
|
| 1378 | }
|
| 1379 |
|
| 1380 |
|
| 1381 | smc501_writel(0xffffffff, fbi->regs2d + SM501_2D_COLOR_COMPARE_MASK);
|
| 1382 |
|
| 1383 |
|
| 1384 | smc501_writel(0xffffffff, fbi->regs2d + SM501_2D_MASK);
|
| 1385 |
|
| 1386 |
|
| 1387 | smc501_writel((sx << 16) | sy, fbi->regs2d + SM501_2D_SOURCE);
|
| 1388 | smc501_writel((dx << 16) | dy, fbi->regs2d + SM501_2D_DESTINATION);
|
| 1389 |
|
| 1390 |
|
| 1391 | smc501_writel((width << 16) | height, fbi->regs2d + SM501_2D_DIMENSION);
|
| 1392 |
|
| 1393 |
|
| 1394 | smc501_writel(0x800000cc | rtl, fbi->regs2d + SM501_2D_CONTROL);
|
| 1395 | }
|
| 1396 |
|
| 1397 | static void sm501fb_fillrect(struct fb_info *info, const struct fb_fillrect *rect)
|
| 1398 | {
|
| 1399 | struct sm501fb_par *par = info->par;
|
| 1400 | struct sm501fb_info *fbi = par->info;
|
| 1401 | int width = rect->width, height = rect->height;
|
| 1402 |
|
| 1403 | if ((rect->dx >= info->var.xres_virtual) ||
|
| 1404 | (rect->dy >= info->var.yres_virtual))
|
| 1405 |
|
| 1406 | return;
|
| 1407 | if ((rect->dx + width) >= info->var.xres_virtual)
|
| 1408 | width = info->var.xres_virtual - rect->dx - 1;
|
| 1409 | if ((rect->dy + height) >= info->var.yres_virtual)
|
| 1410 | height = info->var.yres_virtual - rect->dy - 1;
|
| 1411 |
|
| 1412 | if (sm501fb_sync(info))
|
| 1413 | return;
|
| 1414 |
|
| 1415 |
|
| 1416 | smc501_writel(par->screen.sm_addr, fbi->regs2d + SM501_2D_SOURCE_BASE);
|
| 1417 | smc501_writel(par->screen.sm_addr,
|
| 1418 | fbi->regs2d + SM501_2D_DESTINATION_BASE);
|
| 1419 |
|
| 1420 |
|
| 1421 | smc501_writel((info->var.xres << 16) | info->var.xres,
|
| 1422 | fbi->regs2d + SM501_2D_WINDOW_WIDTH);
|
| 1423 |
|
| 1424 |
|
| 1425 | smc501_writel((info->var.xres_virtual << 16) | info->var.xres_virtual,
|
| 1426 | fbi->regs2d + SM501_2D_PITCH);
|
| 1427 |
|
| 1428 |
|
| 1429 | switch (info->var.bits_per_pixel) {
|
| 1430 | case 8:
|
| 1431 | smc501_writel(0, fbi->regs2d + SM501_2D_STRETCH);
|
| 1432 | break;
|
| 1433 | case 16:
|
| 1434 | smc501_writel(0x00100000, fbi->regs2d + SM501_2D_STRETCH);
|
| 1435 | break;
|
| 1436 | case 32:
|
| 1437 | smc501_writel(0x00200000, fbi->regs2d + SM501_2D_STRETCH);
|
| 1438 | break;
|
| 1439 | }
|
| 1440 |
|
| 1441 |
|
| 1442 | smc501_writel(0xffffffff, fbi->regs2d + SM501_2D_COLOR_COMPARE_MASK);
|
| 1443 |
|
| 1444 |
|
| 1445 | smc501_writel(0xffffffff, fbi->regs2d + SM501_2D_MASK);
|
| 1446 |
|
| 1447 |
|
| 1448 | smc501_writel(rect->color, fbi->regs2d + SM501_2D_FOREGROUND);
|
| 1449 |
|
| 1450 |
|
| 1451 | smc501_writel((rect->dx << 16) | rect->dy,
|
| 1452 | fbi->regs2d + SM501_2D_DESTINATION);
|
| 1453 |
|
| 1454 |
|
| 1455 | smc501_writel((width << 16) | height, fbi->regs2d + SM501_2D_DIMENSION);
|
| 1456 |
|
| 1457 |
|
| 1458 | smc501_writel(0x800100cc, fbi->regs2d + SM501_2D_CONTROL);
|
| 1459 | }
|
| 1460 |
|
| 1461 |
|
| 1462 | static struct fb_ops sm501fb_ops_crt = {
|
| 1463 | .owner = THIS_MODULE,
|
| 1464 | __FB_DEFAULT_IOMEM_OPS_RDWR,
|
| 1465 | .fb_check_var = sm501fb_check_var_crt,
|
| 1466 | .fb_set_par = sm501fb_set_par_crt,
|
| 1467 | .fb_blank = sm501fb_blank_crt,
|
| 1468 | .fb_setcolreg = sm501fb_setcolreg,
|
| 1469 | .fb_pan_display = sm501fb_pan_crt,
|
| 1470 | .fb_cursor = sm501fb_cursor,
|
| 1471 | .fb_fillrect = sm501fb_fillrect,
|
| 1472 | .fb_copyarea = sm501fb_copyarea,
|
| 1473 | .fb_imageblit = cfb_imageblit,
|
| 1474 | .fb_sync = sm501fb_sync,
|
| 1475 | __FB_DEFAULT_IOMEM_OPS_MMAP,
|
| 1476 | };
|
| 1477 |
|
| 1478 | static struct fb_ops sm501fb_ops_pnl = {
|
| 1479 | .owner = THIS_MODULE,
|
| 1480 | __FB_DEFAULT_IOMEM_OPS_RDWR,
|
| 1481 | .fb_check_var = sm501fb_check_var_pnl,
|
| 1482 | .fb_set_par = sm501fb_set_par_pnl,
|
| 1483 | .fb_pan_display = sm501fb_pan_pnl,
|
| 1484 | .fb_blank = sm501fb_blank_pnl,
|
| 1485 | .fb_setcolreg = sm501fb_setcolreg,
|
| 1486 | .fb_cursor = sm501fb_cursor,
|
| 1487 | .fb_fillrect = sm501fb_fillrect,
|
| 1488 | .fb_copyarea = sm501fb_copyarea,
|
| 1489 | .fb_imageblit = cfb_imageblit,
|
| 1490 | .fb_sync = sm501fb_sync,
|
| 1491 | __FB_DEFAULT_IOMEM_OPS_MMAP,
|
| 1492 | };
|
| 1493 |
|
| 1494 | |
| 1495 | *
|
| 1496 | * initialise hw cursor parameters
|
| 1497 |
|
| 1498 |
|
| 1499 | static int sm501_init_cursor(struct fb_info *fbi, unsigned int reg_base)
|
| 1500 | {
|
| 1501 | struct sm501fb_par *par;
|
| 1502 | struct sm501fb_info *info;
|
| 1503 | int ret;
|
| 1504 |
|
| 1505 | if (fbi == NULL)
|
| 1506 | return 0;
|
| 1507 |
|
| 1508 | par = fbi->par;
|
| 1509 | info = par->info;
|
| 1510 |
|
| 1511 | par->cursor_regs = info->regs + reg_base;
|
| 1512 |
|
| 1513 | ret = sm501_alloc_mem(info, &par->cursor, SM501_MEMF_CURSOR, 1024,
|
| 1514 | fbi->fix.smem_len);
|
| 1515 | if (ret < 0)
|
| 1516 | return ret;
|
| 1517 |
|
| 1518 |
|
| 1519 |
|
| 1520 | smc501_writel(par->cursor.sm_addr,
|
| 1521 | par->cursor_regs + SM501_OFF_HWC_ADDR);
|
| 1522 |
|
| 1523 | smc501_writel(0x00, par->cursor_regs + SM501_OFF_HWC_LOC);
|
| 1524 | smc501_writel(0x00, par->cursor_regs + SM501_OFF_HWC_COLOR_1_2);
|
| 1525 | smc501_writel(0x00, par->cursor_regs + SM501_OFF_HWC_COLOR_3);
|
| 1526 | sm501fb_sync_regs(info);
|
| 1527 |
|
| 1528 | return 0;
|
| 1529 | }
|
| 1530 |
|
| 1531 | |
| 1532 | *
|
| 1533 | * fills the par structure claiming resources and remapping etc.
|
| 1534 |
|
| 1535 |
|
| 1536 | static int sm501fb_start(struct sm501fb_info *info,
|
| 1537 | struct platform_device *pdev)
|
| 1538 | {
|
| 1539 | struct resource *res;
|
| 1540 | struct device *dev = &pdev->dev;
|
| 1541 | int k;
|
| 1542 | int ret;
|
| 1543 |
|
| 1544 | info->irq = ret = platform_get_irq(pdev, 0);
|
| 1545 | if (ret < 0) {
|
| 1546 |
|
| 1547 | dev_warn(dev, "no irq for device\n");
|
| 1548 | }
|
| 1549 |
|
| 1550 | |
| 1551 | * controller registers
|
| 1552 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
| 1553 | if (res == NULL) {
|
| 1554 | dev_err(dev, "no resource definition for registers\n");
|
| 1555 | ret = -ENOENT;
|
| 1556 | goto err_release;
|
| 1557 | }
|
| 1558 |
|
| 1559 | info->regs_res = request_mem_region(res->start,
|
| 1560 | resource_size(res),
|
| 1561 | pdev->name);
|
| 1562 |
|
| 1563 | if (info->regs_res == NULL) {
|
| 1564 | dev_err(dev, "cannot claim registers\n");
|
| 1565 | ret = -ENXIO;
|
| 1566 | goto err_release;
|
| 1567 | }
|
| 1568 |
|
| 1569 | info->regs = ioremap(res->start, resource_size(res));
|
| 1570 | if (info->regs == NULL) {
|
| 1571 | dev_err(dev, "cannot remap registers\n");
|
| 1572 | ret = -ENXIO;
|
| 1573 | goto err_regs_res;
|
| 1574 | }
|
| 1575 |
|
| 1576 | |
| 1577 | * controller registers
|
| 1578 | res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
|
| 1579 | if (res == NULL) {
|
| 1580 | dev_err(dev, "no resource definition for 2d registers\n");
|
| 1581 | ret = -ENOENT;
|
| 1582 | goto err_regs_map;
|
| 1583 | }
|
| 1584 |
|
| 1585 | info->regs2d_res = request_mem_region(res->start,
|
| 1586 | resource_size(res),
|
| 1587 | pdev->name);
|
| 1588 |
|
| 1589 | if (info->regs2d_res == NULL) {
|
| 1590 | dev_err(dev, "cannot claim registers\n");
|
| 1591 | ret = -ENXIO;
|
| 1592 | goto err_regs_map;
|
| 1593 | }
|
| 1594 |
|
| 1595 | info->regs2d = ioremap(res->start, resource_size(res));
|
| 1596 | if (info->regs2d == NULL) {
|
| 1597 | dev_err(dev, "cannot remap registers\n");
|
| 1598 | ret = -ENXIO;
|
| 1599 | goto err_regs2d_res;
|
| 1600 | }
|
| 1601 |
|
| 1602 |
|
| 1603 | res = platform_get_resource(pdev, IORESOURCE_MEM, 2);
|
| 1604 | if (res == NULL) {
|
| 1605 | dev_err(dev, "no memory resource defined\n");
|
| 1606 | ret = -ENXIO;
|
| 1607 | goto err_regs2d_map;
|
| 1608 | }
|
| 1609 |
|
| 1610 | info->fbmem_res = request_mem_region(res->start,
|
| 1611 | resource_size(res),
|
| 1612 | pdev->name);
|
| 1613 | if (info->fbmem_res == NULL) {
|
| 1614 | dev_err(dev, "cannot claim framebuffer\n");
|
| 1615 | ret = -ENXIO;
|
| 1616 | goto err_regs2d_map;
|
| 1617 | }
|
| 1618 |
|
| 1619 | info->fbmem = ioremap(res->start, resource_size(res));
|
| 1620 | if (info->fbmem == NULL) {
|
| 1621 | dev_err(dev, "cannot remap framebuffer\n");
|
| 1622 | ret = -ENXIO;
|
| 1623 | goto err_mem_res;
|
| 1624 | }
|
| 1625 |
|
| 1626 | info->fbmem_len = resource_size(res);
|
| 1627 |
|
| 1628 |
|
| 1629 | memset_io(info->fbmem, 0, info->fbmem_len);
|
| 1630 |
|
| 1631 |
|
| 1632 | for (k = 0; k < (256 * 3); k++)
|
| 1633 | smc501_writel(0, info->regs + SM501_DC_PANEL_PALETTE + (k * 4));
|
| 1634 |
|
| 1635 |
|
| 1636 | sm501_unit_power(dev->parent, SM501_GATE_DISPLAY, 1);
|
| 1637 |
|
| 1638 |
|
| 1639 | sm501_unit_power(dev->parent, SM501_GATE_2D_ENGINE, 1);
|
| 1640 |
|
| 1641 |
|
| 1642 | sm501_init_cursor(info->fb[HEAD_CRT], SM501_DC_CRT_HWC_ADDR);
|
| 1643 | sm501_init_cursor(info->fb[HEAD_PANEL], SM501_DC_PANEL_HWC_ADDR);
|
| 1644 |
|
| 1645 | return 0;
|
| 1646 |
|
| 1647 | err_mem_res:
|
| 1648 | release_mem_region(info->fbmem_res->start,
|
| 1649 | resource_size(info->fbmem_res));
|
| 1650 |
|
| 1651 | err_regs2d_map:
|
| 1652 | iounmap(info->regs2d);
|
| 1653 |
|
| 1654 | err_regs2d_res:
|
| 1655 | release_mem_region(info->regs2d_res->start,
|
| 1656 | resource_size(info->regs2d_res));
|
| 1657 |
|
| 1658 | err_regs_map:
|
| 1659 | iounmap(info->regs);
|
| 1660 |
|
| 1661 | err_regs_res:
|
| 1662 | release_mem_region(info->regs_res->start,
|
| 1663 | resource_size(info->regs_res));
|
| 1664 |
|
| 1665 | err_release:
|
| 1666 | return ret;
|
| 1667 | }
|
| 1668 |
|
| 1669 | static void sm501fb_stop(struct sm501fb_info *info)
|
| 1670 | {
|
| 1671 |
|
| 1672 | sm501_unit_power(info->dev->parent, SM501_GATE_DISPLAY, 0);
|
| 1673 |
|
| 1674 | iounmap(info->fbmem);
|
| 1675 | release_mem_region(info->fbmem_res->start,
|
| 1676 | resource_size(info->fbmem_res));
|
| 1677 |
|
| 1678 | iounmap(info->regs2d);
|
| 1679 | release_mem_region(info->regs2d_res->start,
|
| 1680 | resource_size(info->regs2d_res));
|
| 1681 |
|
| 1682 | iounmap(info->regs);
|
| 1683 | release_mem_region(info->regs_res->start,
|
| 1684 | resource_size(info->regs_res));
|
| 1685 | }
|
| 1686 |
|
| 1687 | static int sm501fb_init_fb(struct fb_info *fb, enum sm501_controller head,
|
| 1688 | const char *fbname)
|
| 1689 | {
|
| 1690 | struct sm501_platdata_fbsub *pd;
|
| 1691 | struct sm501fb_par *par = fb->par;
|
| 1692 | struct sm501fb_info *info = par->info;
|
| 1693 | unsigned long ctrl;
|
| 1694 | unsigned int enable;
|
| 1695 | int ret;
|
| 1696 |
|
| 1697 | switch (head) {
|
| 1698 | case HEAD_CRT:
|
| 1699 | pd = info->pdata->fb_crt;
|
| 1700 | ctrl = smc501_readl(info->regs + SM501_DC_CRT_CONTROL);
|
| 1701 | enable = (ctrl & SM501_DC_CRT_CONTROL_ENABLE) ? 1 : 0;
|
| 1702 |
|
| 1703 |
|
| 1704 | if (info->pdata->fb_route != SM501_FB_CRT_PANEL) {
|
| 1705 | ctrl |= SM501_DC_CRT_CONTROL_SEL;
|
| 1706 | smc501_writel(ctrl, info->regs + SM501_DC_CRT_CONTROL);
|
| 1707 | }
|
| 1708 |
|
| 1709 | break;
|
| 1710 |
|
| 1711 | case HEAD_PANEL:
|
| 1712 | pd = info->pdata->fb_pnl;
|
| 1713 | ctrl = smc501_readl(info->regs + SM501_DC_PANEL_CONTROL);
|
| 1714 | enable = (ctrl & SM501_DC_PANEL_CONTROL_EN) ? 1 : 0;
|
| 1715 | break;
|
| 1716 |
|
| 1717 | default:
|
| 1718 | pd = NULL;
|
| 1719 | ctrl = 0;
|
| 1720 | enable = 0;
|
| 1721 | BUG();
|
| 1722 | }
|
| 1723 |
|
| 1724 | dev_info(info->dev, "fb %s %s at start\n",
|
| 1725 | fbname, str_enabled_disabled(enable));
|
| 1726 |
|
| 1727 |
|
| 1728 |
|
| 1729 | if (head == HEAD_CRT && info->pdata->fb_route == SM501_FB_CRT_PANEL) {
|
| 1730 | ctrl &= ~SM501_DC_CRT_CONTROL_SEL;
|
| 1731 | smc501_writel(ctrl, info->regs + SM501_DC_CRT_CONTROL);
|
| 1732 | enable = 0;
|
| 1733 | }
|
| 1734 |
|
| 1735 | strscpy(fb->fix.id, fbname, sizeof(fb->fix.id));
|
| 1736 |
|
| 1737 | memcpy(&par->ops,
|
| 1738 | (head == HEAD_CRT) ? &sm501fb_ops_crt : &sm501fb_ops_pnl,
|
| 1739 | sizeof(struct fb_ops));
|
| 1740 |
|
| 1741 |
|
| 1742 |
|
| 1743 | if ((pd->flags & SM501FB_FLAG_USE_HWCURSOR) == 0)
|
| 1744 | par->ops.fb_cursor = NULL;
|
| 1745 |
|
| 1746 | fb->fbops = &par->ops;
|
| 1747 | fb->flags = FBINFO_READS_FAST |
|
| 1748 | FBINFO_HWACCEL_COPYAREA | FBINFO_HWACCEL_FILLRECT |
|
| 1749 | FBINFO_HWACCEL_XPAN | FBINFO_HWACCEL_YPAN;
|
| 1750 |
|
| 1751 | #if defined(CONFIG_OF)
|
| 1752 | #ifdef __BIG_ENDIAN
|
| 1753 | if (of_property_read_bool(info->dev->parent->of_node, "little-endian"))
|
| 1754 | fb->flags |= FBINFO_FOREIGN_ENDIAN;
|
| 1755 | #else
|
| 1756 | if (of_property_read_bool(info->dev->parent->of_node, "big-endian"))
|
| 1757 | fb->flags |= FBINFO_FOREIGN_ENDIAN;
|
| 1758 | #endif
|
| 1759 | #endif
|
| 1760 |
|
| 1761 |
|
| 1762 | fb->fix.type = FB_TYPE_PACKED_PIXELS;
|
| 1763 | fb->fix.type_aux = 0;
|
| 1764 | fb->fix.xpanstep = 1;
|
| 1765 | fb->fix.ypanstep = 1;
|
| 1766 | fb->fix.ywrapstep = 0;
|
| 1767 | fb->fix.accel = FB_ACCEL_NONE;
|
| 1768 |
|
| 1769 |
|
| 1770 |
|
| 1771 | fb->var.nonstd = 0;
|
| 1772 | fb->var.activate = FB_ACTIVATE_NOW;
|
| 1773 | fb->var.accel_flags = 0;
|
| 1774 | fb->var.vmode = FB_VMODE_NONINTERLACED;
|
| 1775 | fb->var.bits_per_pixel = 16;
|
| 1776 |
|
| 1777 | if (info->edid_data) {
|
| 1778 |
|
| 1779 | fb_edid_to_monspecs(info->edid_data, &fb->monspecs);
|
| 1780 | fb_videomode_to_modelist(fb->monspecs.modedb,
|
| 1781 | fb->monspecs.modedb_len,
|
| 1782 | &fb->modelist);
|
| 1783 | }
|
| 1784 |
|
| 1785 | if (enable && (pd->flags & SM501FB_FLAG_USE_INIT_MODE) && 0) {
|
| 1786 |
|
| 1787 | } else {
|
| 1788 | if (pd->def_mode) {
|
| 1789 | dev_info(info->dev, "using supplied mode\n");
|
| 1790 | fb_videomode_to_var(&fb->var, pd->def_mode);
|
| 1791 |
|
| 1792 | fb->var.bits_per_pixel = pd->def_bpp ? pd->def_bpp : 8;
|
| 1793 | fb->var.xres_virtual = fb->var.xres;
|
| 1794 | fb->var.yres_virtual = fb->var.yres;
|
| 1795 | } else {
|
| 1796 | if (info->edid_data) {
|
| 1797 | ret = fb_find_mode(&fb->var, fb,
|
| 1798 | info->fb_mode ?: fb_mode,
|
| 1799 | fb->monspecs.modedb,
|
| 1800 | fb->monspecs.modedb_len,
|
| 1801 | &sm501_default_mode, default_bpp);
|
| 1802 | } else {
|
| 1803 | ret = fb_find_mode(&fb->var, fb,
|
| 1804 | NULL, NULL, 0, NULL, 8);
|
| 1805 | }
|
| 1806 |
|
| 1807 | switch (ret) {
|
| 1808 | case 1:
|
| 1809 | dev_info(info->dev, "using mode specified in "
|
| 1810 | "@mode\n");
|
| 1811 | break;
|
| 1812 | case 2:
|
| 1813 | dev_info(info->dev, "using mode specified in "
|
| 1814 | "@mode with ignored refresh rate\n");
|
| 1815 | break;
|
| 1816 | case 3:
|
| 1817 | dev_info(info->dev, "using mode default "
|
| 1818 | "mode\n");
|
| 1819 | break;
|
| 1820 | case 4:
|
| 1821 | dev_info(info->dev, "using mode from list\n");
|
| 1822 | break;
|
| 1823 | default:
|
| 1824 | dev_info(info->dev, "ret = %d\n", ret);
|
| 1825 | dev_info(info->dev, "failed to find mode\n");
|
| 1826 | return -EINVAL;
|
| 1827 | }
|
| 1828 | }
|
| 1829 | }
|
| 1830 |
|
| 1831 |
|
| 1832 | if (fb_alloc_cmap(&fb->cmap, NR_PALETTE, 0)) {
|
| 1833 | dev_err(info->dev, "failed to allocate cmap memory\n");
|
| 1834 | return -ENOMEM;
|
| 1835 | }
|
| 1836 | fb_set_cmap(&fb->cmap, fb);
|
| 1837 |
|
| 1838 | ret = (fb->fbops->fb_check_var)(&fb->var, fb);
|
| 1839 | if (ret)
|
| 1840 | dev_err(info->dev, "check_var() failed on initial setup?\n");
|
| 1841 |
|
| 1842 | return 0;
|
| 1843 | }
|
| 1844 |
|
| 1845 |
|
| 1846 |
|
| 1847 | static struct sm501_platdata_fbsub sm501fb_pdata_crt = {
|
| 1848 | .flags = (SM501FB_FLAG_USE_INIT_MODE |
|
| 1849 | SM501FB_FLAG_USE_HWCURSOR |
|
| 1850 | SM501FB_FLAG_USE_HWACCEL |
|
| 1851 | SM501FB_FLAG_DISABLE_AT_EXIT),
|
| 1852 |
|
| 1853 | };
|
| 1854 |
|
| 1855 | static struct sm501_platdata_fbsub sm501fb_pdata_pnl = {
|
| 1856 | .flags = (SM501FB_FLAG_USE_INIT_MODE |
|
| 1857 | SM501FB_FLAG_USE_HWCURSOR |
|
| 1858 | SM501FB_FLAG_USE_HWACCEL |
|
| 1859 | SM501FB_FLAG_DISABLE_AT_EXIT),
|
| 1860 | };
|
| 1861 |
|
| 1862 | static struct sm501_platdata_fb sm501fb_def_pdata = {
|
| 1863 | .fb_route = SM501_FB_OWN,
|
| 1864 | .fb_crt = &sm501fb_pdata_crt,
|
| 1865 | .fb_pnl = &sm501fb_pdata_pnl,
|
| 1866 | };
|
| 1867 |
|
| 1868 | static char driver_name_crt[] = "sm501fb-crt";
|
| 1869 | static char driver_name_pnl[] = "sm501fb-panel";
|
| 1870 |
|
| 1871 | static int sm501fb_probe_one(struct sm501fb_info *info,
|
| 1872 | enum sm501_controller head)
|
| 1873 | {
|
| 1874 | unsigned char *name = (head == HEAD_CRT) ? "crt" : "panel";
|
| 1875 | struct sm501_platdata_fbsub *pd;
|
| 1876 | struct sm501fb_par *par;
|
| 1877 | struct fb_info *fbi;
|
| 1878 |
|
| 1879 | pd = (head == HEAD_CRT) ? info->pdata->fb_crt : info->pdata->fb_pnl;
|
| 1880 |
|
| 1881 |
|
| 1882 | if (pd == NULL) {
|
| 1883 | dev_info(info->dev, "no data for fb %s (disabled)\n", name);
|
| 1884 | return 0;
|
| 1885 | }
|
| 1886 |
|
| 1887 | fbi = framebuffer_alloc(sizeof(struct sm501fb_par), info->dev);
|
| 1888 | if (!fbi)
|
| 1889 | return -ENOMEM;
|
| 1890 |
|
| 1891 | par = fbi->par;
|
| 1892 | par->info = info;
|
| 1893 | par->head = head;
|
| 1894 | fbi->pseudo_palette = &par->pseudo_palette;
|
| 1895 |
|
| 1896 | info->fb[head] = fbi;
|
| 1897 |
|
| 1898 | return 0;
|
| 1899 | }
|
| 1900 |
|
| 1901 |
|
| 1902 |
|
| 1903 | static void sm501_free_init_fb(struct sm501fb_info *info,
|
| 1904 | enum sm501_controller head)
|
| 1905 | {
|
| 1906 | struct fb_info *fbi = info->fb[head];
|
| 1907 |
|
| 1908 | if (!fbi)
|
| 1909 | return;
|
| 1910 |
|
| 1911 | fb_dealloc_cmap(&fbi->cmap);
|
| 1912 | }
|
| 1913 |
|
| 1914 | static int sm501fb_start_one(struct sm501fb_info *info,
|
| 1915 | enum sm501_controller head, const char *drvname)
|
| 1916 | {
|
| 1917 | struct fb_info *fbi = info->fb[head];
|
| 1918 | int ret;
|
| 1919 |
|
| 1920 | if (!fbi)
|
| 1921 | return 0;
|
| 1922 |
|
| 1923 | mutex_init(&info->fb[head]->mm_lock);
|
| 1924 |
|
| 1925 | ret = sm501fb_init_fb(info->fb[head], head, drvname);
|
| 1926 | if (ret) {
|
| 1927 | dev_err(info->dev, "cannot initialise fb %s\n", drvname);
|
| 1928 | return ret;
|
| 1929 | }
|
| 1930 |
|
| 1931 | ret = register_framebuffer(info->fb[head]);
|
| 1932 | if (ret) {
|
| 1933 | dev_err(info->dev, "failed to register fb %s\n", drvname);
|
| 1934 | sm501_free_init_fb(info, head);
|
| 1935 | return ret;
|
| 1936 | }
|
| 1937 |
|
| 1938 | dev_info(info->dev, "fb%d: %s frame buffer\n", fbi->node, fbi->fix.id);
|
| 1939 |
|
| 1940 | return 0;
|
| 1941 | }
|
| 1942 |
|
| 1943 | static int sm501fb_probe(struct platform_device *pdev)
|
| 1944 | {
|
| 1945 | struct sm501fb_info *info;
|
| 1946 | struct device *dev = &pdev->dev;
|
| 1947 | int ret;
|
| 1948 |
|
| 1949 |
|
| 1950 | info = kzalloc_obj(*info);
|
| 1951 | if (!info) {
|
| 1952 | dev_err(dev, "failed to allocate state\n");
|
| 1953 | return -ENOMEM;
|
| 1954 | }
|
| 1955 |
|
| 1956 | info->dev = dev = &pdev->dev;
|
| 1957 | platform_set_drvdata(pdev, info);
|
| 1958 |
|
| 1959 | if (dev->parent->platform_data) {
|
| 1960 | struct sm501_platdata *pd = dev->parent->platform_data;
|
| 1961 | info->pdata = pd->fb;
|
| 1962 | }
|
| 1963 |
|
| 1964 | if (info->pdata == NULL) {
|
| 1965 | int found = 0;
|
| 1966 | #if defined(CONFIG_OF)
|
| 1967 | struct device_node *np = pdev->dev.parent->of_node;
|
| 1968 | const u8 *prop;
|
| 1969 | const char *cp;
|
| 1970 | int len;
|
| 1971 |
|
| 1972 | info->pdata = &sm501fb_def_pdata;
|
| 1973 | if (np) {
|
| 1974 |
|
| 1975 | cp = of_get_property(np, "mode", &len);
|
| 1976 | if (cp)
|
| 1977 | info->fb_mode = kstrdup(cp, GFP_KERNEL);
|
| 1978 | prop = of_get_property(np, "edid", &len);
|
| 1979 | if (prop && len == EDID_LENGTH) {
|
| 1980 | info->edid_data = kmemdup(prop, EDID_LENGTH,
|
| 1981 | GFP_KERNEL);
|
| 1982 | if (info->edid_data)
|
| 1983 | found = 1;
|
| 1984 | }
|
| 1985 | }
|
| 1986 | #endif
|
| 1987 | if (!found) {
|
| 1988 | dev_info(dev, "using default configuration data\n");
|
| 1989 | info->pdata = &sm501fb_def_pdata;
|
| 1990 | }
|
| 1991 | }
|
| 1992 |
|
| 1993 |
|
| 1994 |
|
| 1995 | ret = sm501fb_probe_one(info, HEAD_CRT);
|
| 1996 | if (ret < 0) {
|
| 1997 | dev_err(dev, "failed to probe CRT\n");
|
| 1998 | goto err_alloc;
|
| 1999 | }
|
| 2000 |
|
| 2001 | ret = sm501fb_probe_one(info, HEAD_PANEL);
|
| 2002 | if (ret < 0) {
|
| 2003 | dev_err(dev, "failed to probe PANEL\n");
|
| 2004 | goto err_probed_crt;
|
| 2005 | }
|
| 2006 |
|
| 2007 | if (info->fb[HEAD_PANEL] == NULL &&
|
| 2008 | info->fb[HEAD_CRT] == NULL) {
|
| 2009 | dev_err(dev, "no framebuffers found\n");
|
| 2010 | ret = -ENODEV;
|
| 2011 | goto err_alloc;
|
| 2012 | }
|
| 2013 |
|
| 2014 |
|
| 2015 |
|
| 2016 | ret = sm501fb_start(info, pdev);
|
| 2017 | if (ret) {
|
| 2018 | dev_err(dev, "cannot initialise SM501\n");
|
| 2019 | goto err_probed_panel;
|
| 2020 | }
|
| 2021 |
|
| 2022 | ret = sm501fb_start_one(info, HEAD_CRT, driver_name_crt);
|
| 2023 | if (ret) {
|
| 2024 | dev_err(dev, "failed to start CRT\n");
|
| 2025 | goto err_started;
|
| 2026 | }
|
| 2027 |
|
| 2028 | ret = sm501fb_start_one(info, HEAD_PANEL, driver_name_pnl);
|
| 2029 | if (ret) {
|
| 2030 | dev_err(dev, "failed to start Panel\n");
|
| 2031 | goto err_started_crt;
|
| 2032 | }
|
| 2033 |
|
| 2034 |
|
| 2035 | kfree(info->edid_data);
|
| 2036 | kfree(info->fb_mode);
|
| 2037 | info->edid_data = NULL;
|
| 2038 | info->fb_mode = NULL;
|
| 2039 |
|
| 2040 |
|
| 2041 | return 0;
|
| 2042 |
|
| 2043 | err_started_crt:
|
| 2044 | unregister_framebuffer(info->fb[HEAD_CRT]);
|
| 2045 | sm501_free_init_fb(info, HEAD_CRT);
|
| 2046 |
|
| 2047 | err_started:
|
| 2048 | sm501fb_stop(info);
|
| 2049 |
|
| 2050 | err_probed_panel:
|
| 2051 | framebuffer_release(info->fb[HEAD_PANEL]);
|
| 2052 |
|
| 2053 | err_probed_crt:
|
| 2054 | framebuffer_release(info->fb[HEAD_CRT]);
|
| 2055 |
|
| 2056 | err_alloc:
|
| 2057 | kfree(info->edid_data);
|
| 2058 | kfree(info->fb_mode);
|
| 2059 | kfree(info);
|
| 2060 |
|
| 2061 | return ret;
|
| 2062 | }
|
| 2063 |
|
| 2064 |
|
| 2065 | |
| 2066 | * Cleanup
|
| 2067 |
|
| 2068 | static void sm501fb_remove(struct platform_device *pdev)
|
| 2069 | {
|
| 2070 | struct sm501fb_info *info = platform_get_drvdata(pdev);
|
| 2071 | struct fb_info *fbinfo_crt = info->fb[0];
|
| 2072 | struct fb_info *fbinfo_pnl = info->fb[1];
|
| 2073 |
|
| 2074 | sm501_free_init_fb(info, HEAD_CRT);
|
| 2075 | sm501_free_init_fb(info, HEAD_PANEL);
|
| 2076 |
|
| 2077 | if (fbinfo_crt)
|
| 2078 | unregister_framebuffer(fbinfo_crt);
|
| 2079 | if (fbinfo_pnl)
|
| 2080 | unregister_framebuffer(fbinfo_pnl);
|
| 2081 |
|
| 2082 | sm501fb_stop(info);
|
| 2083 | kfree(info);
|
| 2084 |
|
| 2085 | framebuffer_release(fbinfo_pnl);
|
| 2086 | framebuffer_release(fbinfo_crt);
|
| 2087 | }
|
| 2088 |
|
| 2089 | #ifdef CONFIG_PM
|
| 2090 |
|
| 2091 | static int sm501fb_suspend_fb(struct sm501fb_info *info,
|
| 2092 | enum sm501_controller head)
|
| 2093 | {
|
| 2094 | struct fb_info *fbi = info->fb[head];
|
| 2095 | struct sm501fb_par *par;
|
| 2096 |
|
| 2097 | if (!fbi)
|
| 2098 | return 0;
|
| 2099 |
|
| 2100 | par = fbi->par;
|
| 2101 | if (par->screen.size == 0)
|
| 2102 | return 0;
|
| 2103 |
|
| 2104 |
|
| 2105 | (par->ops.fb_blank)(FB_BLANK_POWERDOWN, fbi);
|
| 2106 |
|
| 2107 |
|
| 2108 |
|
| 2109 | console_lock();
|
| 2110 | fb_set_suspend(fbi, 1);
|
| 2111 | console_unlock();
|
| 2112 |
|
| 2113 |
|
| 2114 |
|
| 2115 | par->store_fb = vmalloc(par->screen.size);
|
| 2116 | if (par->store_fb == NULL) {
|
| 2117 | dev_err(info->dev, "no memory to store screen\n");
|
| 2118 | return -ENOMEM;
|
| 2119 | }
|
| 2120 |
|
| 2121 | par->store_cursor = vmalloc(par->cursor.size);
|
| 2122 | if (par->store_cursor == NULL) {
|
| 2123 | dev_err(info->dev, "no memory to store cursor\n");
|
| 2124 | goto err_nocursor;
|
| 2125 | }
|
| 2126 |
|
| 2127 | dev_dbg(info->dev, "suspending screen to %p\n", par->store_fb);
|
| 2128 | dev_dbg(info->dev, "suspending cursor to %p\n", par->store_cursor);
|
| 2129 |
|
| 2130 | memcpy_fromio(par->store_fb, par->screen.k_addr, par->screen.size);
|
| 2131 | memcpy_fromio(par->store_cursor, par->cursor.k_addr, par->cursor.size);
|
| 2132 |
|
| 2133 | return 0;
|
| 2134 |
|
| 2135 | err_nocursor:
|
| 2136 | vfree(par->store_fb);
|
| 2137 | par->store_fb = NULL;
|
| 2138 |
|
| 2139 | return -ENOMEM;
|
| 2140 | }
|
| 2141 |
|
| 2142 | static void sm501fb_resume_fb(struct sm501fb_info *info,
|
| 2143 | enum sm501_controller head)
|
| 2144 | {
|
| 2145 | struct fb_info *fbi = info->fb[head];
|
| 2146 | struct sm501fb_par *par;
|
| 2147 |
|
| 2148 | if (!fbi)
|
| 2149 | return;
|
| 2150 |
|
| 2151 | par = fbi->par;
|
| 2152 | if (par->screen.size == 0)
|
| 2153 | return;
|
| 2154 |
|
| 2155 |
|
| 2156 |
|
| 2157 | (par->ops.fb_set_par)(fbi);
|
| 2158 |
|
| 2159 |
|
| 2160 |
|
| 2161 | dev_dbg(info->dev, "restoring screen from %p\n", par->store_fb);
|
| 2162 | dev_dbg(info->dev, "restoring cursor from %p\n", par->store_cursor);
|
| 2163 |
|
| 2164 | if (par->store_fb)
|
| 2165 | memcpy_toio(par->screen.k_addr, par->store_fb,
|
| 2166 | par->screen.size);
|
| 2167 |
|
| 2168 | if (par->store_cursor)
|
| 2169 | memcpy_toio(par->cursor.k_addr, par->store_cursor,
|
| 2170 | par->cursor.size);
|
| 2171 |
|
| 2172 | console_lock();
|
| 2173 | fb_set_suspend(fbi, 0);
|
| 2174 | console_unlock();
|
| 2175 |
|
| 2176 | vfree(par->store_fb);
|
| 2177 | vfree(par->store_cursor);
|
| 2178 | }
|
| 2179 |
|
| 2180 |
|
| 2181 |
|
| 2182 |
|
| 2183 | static int sm501fb_suspend(struct platform_device *pdev, pm_message_t state)
|
| 2184 | {
|
| 2185 | struct sm501fb_info *info = platform_get_drvdata(pdev);
|
| 2186 |
|
| 2187 |
|
| 2188 | info->pm_crt_ctrl = smc501_readl(info->regs + SM501_DC_CRT_CONTROL);
|
| 2189 |
|
| 2190 | sm501fb_suspend_fb(info, HEAD_CRT);
|
| 2191 | sm501fb_suspend_fb(info, HEAD_PANEL);
|
| 2192 |
|
| 2193 |
|
| 2194 | sm501_unit_power(info->dev->parent, SM501_GATE_DISPLAY, 0);
|
| 2195 |
|
| 2196 | return 0;
|
| 2197 | }
|
| 2198 |
|
| 2199 | #define SM501_CRT_CTRL_SAVE (SM501_DC_CRT_CONTROL_TVP | \
|
| 2200 | SM501_DC_CRT_CONTROL_SEL)
|
| 2201 |
|
| 2202 |
|
| 2203 | static int sm501fb_resume(struct platform_device *pdev)
|
| 2204 | {
|
| 2205 | struct sm501fb_info *info = platform_get_drvdata(pdev);
|
| 2206 | unsigned long crt_ctrl;
|
| 2207 |
|
| 2208 | sm501_unit_power(info->dev->parent, SM501_GATE_DISPLAY, 1);
|
| 2209 |
|
| 2210 |
|
| 2211 |
|
| 2212 | crt_ctrl = smc501_readl(info->regs + SM501_DC_CRT_CONTROL);
|
| 2213 | crt_ctrl &= ~SM501_CRT_CTRL_SAVE;
|
| 2214 | crt_ctrl |= info->pm_crt_ctrl & SM501_CRT_CTRL_SAVE;
|
| 2215 | smc501_writel(crt_ctrl, info->regs + SM501_DC_CRT_CONTROL);
|
| 2216 |
|
| 2217 | sm501fb_resume_fb(info, HEAD_CRT);
|
| 2218 | sm501fb_resume_fb(info, HEAD_PANEL);
|
| 2219 |
|
| 2220 | return 0;
|
| 2221 | }
|
| 2222 |
|
| 2223 | #else
|
| 2224 | #define sm501fb_suspend NULL
|
| 2225 | #define sm501fb_resume NULL
|
| 2226 | #endif
|
| 2227 |
|
| 2228 | static struct platform_driver sm501fb_driver = {
|
| 2229 | .probe = sm501fb_probe,
|
| 2230 | .remove = sm501fb_remove,
|
| 2231 | .suspend = sm501fb_suspend,
|
| 2232 | .resume = sm501fb_resume,
|
| 2233 | .driver = {
|
| 2234 | .name = "sm501-fb",
|
| 2235 | .dev_groups = sm501fb_groups,
|
| 2236 | },
|
| 2237 | };
|
| 2238 |
|
| 2239 | module_platform_driver(sm501fb_driver);
|
| 2240 |
|
| 2241 | module_param_named(mode, fb_mode, charp, 0);
|
| 2242 | MODULE_PARM_DESC(mode,
|
| 2243 | "Specify resolution as \"<xres>x<yres>[-<bpp>][@<refresh>]\" ");
|
| 2244 | module_param_named(bpp, default_bpp, ulong, 0);
|
| 2245 | MODULE_PARM_DESC(bpp, "Specify bit-per-pixel if not specified mode");
|
| 2246 | MODULE_AUTHOR("Ben Dooks, Vincent Sanders");
|
| 2247 | MODULE_DESCRIPTION("SM501 Framebuffer driver");
|
| 2248 | MODULE_LICENSE("GPL v2");
|