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