1#include <linux/version.h> 2#include<linux/module.h> 3#include<linux/kernel.h> 4#include<linux/errno.h> 5#include<linux/string.h> 6#include<linux/mm.h> 7#include<linux/slab.h> 8#include<linux/delay.h> 9#include<linux/fb.h> 10#include<linux/ioport.h> 11#include<linux/init.h> 12#include<linux/pci.h> 13#include<linux/vmalloc.h> 14#include<linux/pagemap.h> 15#include <linux/console.h> 16#ifdef CONFIG_MTRR 17#include <asm/mtrr.h> 18#endif 19#include<linux/platform_device.h> 20#include<linux/screen_info.h> 21 22#include "sm750.h" 23#include "sm750_hw.h" 24#include "ddk750.h" 25#include "sm750_accel.h" 26 27int hw_sm750_map(struct lynx_share* share, struct pci_dev* pdev) 28{ 29 int ret; 30 struct sm750_share * spec_share; 31 32 33 spec_share = container_of(share, struct sm750_share,share); 34 ret = 0; 35 36 share->vidreg_start = pci_resource_start(pdev, 1); 37 share->vidreg_size = MB(2); 38 39 pr_info("mmio phyAddr = %lx\n", share->vidreg_start); 40 41 /* reserve the vidreg space of smi adaptor 42 * if you do this, u need to add release region code 43 * in lynxfb_remove, or memory will not be mapped again 44 * successfully 45 * */ 46 47 if((ret = pci_request_region(pdev, 1, "sm750fb"))) 48 { 49 pr_err("Can not request PCI regions.\n"); 50 goto exit; 51 } 52 53 /* now map mmio and vidmem*/ 54 share->pvReg = ioremap_nocache(share->vidreg_start, share->vidreg_size); 55 if(!share->pvReg){ 56 pr_err("mmio failed\n"); 57 ret = -EFAULT; 58 goto exit; 59 }else{ 60 pr_info("mmio virtual addr = %p\n", share->pvReg); 61 } 62 63 64 share->accel.dprBase = share->pvReg + DE_BASE_ADDR_TYPE1; 65 share->accel.dpPortBase = share->pvReg + DE_PORT_ADDR_TYPE1; 66 67 ddk750_set_mmio(share->pvReg,share->devid, share->revid); 68 69 share->vidmem_start = pci_resource_start(pdev, 0); 70 /* don't use pdev_resource[x].end - resource[x].start to 71 * calculate the resource size,its only the maximum available 72 * size but not the actual size,use 73 * @hw_sm750_getVMSize function can be safe. 74 * */ 75 share->vidmem_size = hw_sm750_getVMSize(share); 76 pr_info("video memory phyAddr = %lx, size = %u bytes\n", 77 share->vidmem_start, share->vidmem_size); 78 79 /* reserve the vidmem space of smi adaptor */ 80#if 0 81 if((ret = pci_request_region(pdev,0,_moduleName_))) 82 { 83 pr_err("Can not request PCI regions.\n"); 84 goto exit; 85 } 86#endif 87 88 share->pvMem = ioremap(share->vidmem_start, 89 share->vidmem_size); 90 91 if(!share->pvMem){ 92 pr_err("Map video memory failed\n"); 93 ret = -EFAULT; 94 goto exit; 95 }else{ 96 pr_info("video memory vaddr = %p\n", share->pvMem); 97 } 98exit: 99 return ret; 100} 101 102 103 104int hw_sm750_inithw(struct lynx_share* share, struct pci_dev * pdev) 105{ 106 struct sm750_share * spec_share; 107 struct init_status * parm; 108 109 spec_share = container_of(share, struct sm750_share,share); 110 parm = &spec_share->state.initParm; 111 if(parm->chip_clk == 0) 112 parm->chip_clk = (getChipType() == SM750LE)? 113 DEFAULT_SM750LE_CHIP_CLOCK : 114 DEFAULT_SM750_CHIP_CLOCK; 115 116 if(parm->mem_clk == 0) 117 parm->mem_clk = parm->chip_clk; 118 if(parm->master_clk == 0) 119 parm->master_clk = parm->chip_clk/3; 120 121 ddk750_initHw((initchip_param_t *)&spec_share->state.initParm); 122 /* for sm718,open pci burst */ 123 if(share->devid == 0x718){ 124 POKE32(SYSTEM_CTRL, 125 FIELD_SET(PEEK32(SYSTEM_CTRL), SYSTEM_CTRL, PCI_BURST, ON)); 126 } 127 128 /* sm750 use sii164, it can be setup with default value 129 * by on power, so initDVIDisp can be skipped */ 130#if 0 131 ddk750_initDVIDisp(); 132#endif 133 134 if(getChipType() != SM750LE) 135 { 136 /* does user need CRT ?*/ 137 if(spec_share->state.nocrt){ 138 POKE32(MISC_CTRL, 139 FIELD_SET(PEEK32(MISC_CTRL), 140 MISC_CTRL, 141 DAC_POWER, OFF)); 142 /* shut off dpms */ 143 POKE32(SYSTEM_CTRL, 144 FIELD_SET(PEEK32(SYSTEM_CTRL), 145 SYSTEM_CTRL, 146 DPMS, VNHN)); 147 }else{ 148 POKE32(MISC_CTRL, 149 FIELD_SET(PEEK32(MISC_CTRL), 150 MISC_CTRL, 151 DAC_POWER, ON)); 152 /* turn on dpms */ 153 POKE32(SYSTEM_CTRL, 154 FIELD_SET(PEEK32(SYSTEM_CTRL), 155 SYSTEM_CTRL, 156 DPMS, VPHP)); 157 } 158 159 switch (spec_share->state.pnltype){ 160 case sm750_doubleTFT: 161 case sm750_24TFT: 162 case sm750_dualTFT: 163 POKE32(PANEL_DISPLAY_CTRL, 164 FIELD_VALUE(PEEK32(PANEL_DISPLAY_CTRL), 165 PANEL_DISPLAY_CTRL, 166 TFT_DISP, 167 spec_share->state.pnltype)); 168 break; 169 } 170 }else{ 171 /* for 750LE ,no DVI chip initilization makes Monitor no signal */ 172 /* Set up GPIO for software I2C to program DVI chip in the 173 Xilinx SP605 board, in order to have video signal. 174 */ 175 swI2CInit(0,1); 176 177 178 /* Customer may NOT use CH7301 DVI chip, which has to be 179 initialized differently. 180 */ 181 if (swI2CReadReg(0xec, 0x4a) == 0x95) 182 { 183 /* The following register values for CH7301 are from 184 Chrontel app note and our experiment. 185 */ 186 pr_info("yes,CH7301 DVI chip found\n"); 187 swI2CWriteReg(0xec, 0x1d, 0x16); 188 swI2CWriteReg(0xec, 0x21, 0x9); 189 swI2CWriteReg(0xec, 0x49, 0xC0); 190 pr_info("okay,CH7301 DVI chip setup done\n"); 191 } 192 } 193 194 /* init 2d engine */ 195 if(!share->accel_off){ 196 hw_sm750_initAccel(share); 197// share->accel.de_wait = hw_sm750_deWait; 198 } 199 200 return 0; 201} 202 203 204resource_size_t hw_sm750_getVMSize(struct lynx_share * share) 205{ 206 resource_size_t ret; 207 208 ret = ddk750_getVMSize(); 209 return ret; 210} 211 212 213 214int hw_sm750_output_checkMode(struct lynxfb_output* output, struct fb_var_screeninfo* var) 215{ 216 217 return 0; 218} 219 220 221int hw_sm750_output_setMode(struct lynxfb_output* output, 222 struct fb_var_screeninfo* var, struct fb_fix_screeninfo* fix) 223{ 224 int ret; 225 disp_output_t dispSet; 226 int channel; 227 228 ret = 0; 229 dispSet = 0; 230 channel = *output->channel; 231 232 233 if(getChipType() != SM750LE){ 234 if(channel == sm750_primary){ 235 pr_info("primary channel\n"); 236 if(output->paths & sm750_panel) 237 dispSet |= do_LCD1_PRI; 238 if(output->paths & sm750_crt) 239 dispSet |= do_CRT_PRI; 240 241 }else{ 242 pr_info("secondary channel\n"); 243 if(output->paths & sm750_panel) 244 dispSet |= do_LCD1_SEC; 245 if(output->paths & sm750_crt) 246 dispSet |= do_CRT_SEC; 247 248 } 249 ddk750_setLogicalDispOut(dispSet); 250 }else{ 251 /* just open DISPLAY_CONTROL_750LE register bit 3:0*/ 252 u32 reg; 253 reg = PEEK32(DISPLAY_CONTROL_750LE); 254 reg |= 0xf; 255 POKE32(DISPLAY_CONTROL_750LE, reg); 256 } 257 258 pr_info("ddk setlogicdispout done \n"); 259 return ret; 260} 261 262void hw_sm750_output_clear(struct lynxfb_output* output) 263{ 264 265 return; 266} 267 268int hw_sm750_crtc_checkMode(struct lynxfb_crtc* crtc, struct fb_var_screeninfo* var) 269{ 270 struct lynx_share * share; 271 272 273 share = container_of(crtc, struct lynxfb_par,crtc)->share; 274 275 switch (var->bits_per_pixel){ 276 case 8: 277 case 16: 278 break; 279 case 32: 280 if (share->revid == SM750LE_REVISION_ID) { 281 pr_debug("750le do not support 32bpp\n"); 282 return -EINVAL; 283 } 284 break; 285 default: 286 return -EINVAL; 287 288 } 289 290 return 0; 291} 292 293 294/* 295 set the controller's mode for @crtc charged with @var and @fix parameters 296*/ 297int hw_sm750_crtc_setMode(struct lynxfb_crtc* crtc, 298 struct fb_var_screeninfo* var, 299 struct fb_fix_screeninfo* fix) 300{ 301 int ret,fmt; 302 u32 reg; 303 mode_parameter_t modparm; 304 clock_type_t clock; 305 struct lynx_share * share; 306 struct lynxfb_par * par; 307 308 309 ret = 0; 310 par = container_of(crtc, struct lynxfb_par, crtc); 311 share = par->share; 312#if 1 313 if(!share->accel_off){ 314 /* set 2d engine pixel format according to mode bpp */ 315 switch(var->bits_per_pixel){ 316 case 8: 317 fmt = 0; 318 break; 319 case 16: 320 fmt = 1; 321 break; 322 case 32: 323 default: 324 fmt = 2; 325 break; 326 } 327 hw_set2dformat(&share->accel, fmt); 328 } 329#endif 330 331 /* set timing */ 332// modparm.pixel_clock = PS_TO_HZ(var->pixclock); 333 modparm.pixel_clock = ps_to_hz(var->pixclock); 334 modparm.vertical_sync_polarity = (var->sync & FB_SYNC_HOR_HIGH_ACT) ? POS:NEG; 335 modparm.horizontal_sync_polarity = (var->sync & FB_SYNC_VERT_HIGH_ACT) ? POS:NEG; 336 modparm.clock_phase_polarity = (var->sync& FB_SYNC_COMP_HIGH_ACT) ? POS:NEG; 337 modparm.horizontal_display_end = var->xres; 338 modparm.horizontal_sync_width = var->hsync_len; 339 modparm.horizontal_sync_start = var->xres + var->right_margin; 340 modparm.horizontal_total = var->xres + var->left_margin + var->right_margin + var->hsync_len; 341 modparm.vertical_display_end = var->yres; 342 modparm.vertical_sync_height = var->vsync_len; 343 modparm.vertical_sync_start = var->yres + var->lower_margin; 344 modparm.vertical_total = var->yres + var->upper_margin + var->lower_margin + var->vsync_len; 345 346 /* choose pll */ 347 if(crtc->channel != sm750_secondary) 348 clock = PRIMARY_PLL; 349 else 350 clock = SECONDARY_PLL; 351 352 pr_debug("Request pixel clock = %lu\n", modparm.pixel_clock); 353 ret = ddk750_setModeTiming(&modparm, clock); 354 if(ret){ 355 pr_err("Set mode timing failed\n"); 356 goto exit; 357 } 358 359 if(crtc->channel != sm750_secondary){ 360 /* set pitch, offset ,width,start address ,etc... */ 361 POKE32(PANEL_FB_ADDRESS, 362 FIELD_SET(0, PANEL_FB_ADDRESS, STATUS, CURRENT)| 363 FIELD_SET(0, PANEL_FB_ADDRESS, EXT, LOCAL)| 364 FIELD_VALUE(0, PANEL_FB_ADDRESS, ADDRESS, crtc->oScreen)); 365 366 reg = var->xres * (var->bits_per_pixel >> 3); 367 /* crtc->channel is not equal to par->index on numeric,be aware of that */ 368 reg = PADDING(crtc->line_pad,reg); 369 370 POKE32(PANEL_FB_WIDTH, 371 FIELD_VALUE(0, PANEL_FB_WIDTH, WIDTH, reg)| 372 FIELD_VALUE(0, PANEL_FB_WIDTH, OFFSET, fix->line_length)); 373 374 POKE32(PANEL_WINDOW_WIDTH, 375 FIELD_VALUE(0, PANEL_WINDOW_WIDTH, WIDTH, var->xres -1)| 376 FIELD_VALUE(0, PANEL_WINDOW_WIDTH, X, var->xoffset)); 377 378 POKE32(PANEL_WINDOW_HEIGHT, 379 FIELD_VALUE(0, PANEL_WINDOW_HEIGHT, HEIGHT, var->yres_virtual - 1)| 380 FIELD_VALUE(0, PANEL_WINDOW_HEIGHT, Y, var->yoffset)); 381 382 POKE32(PANEL_PLANE_TL, 0); 383 384 POKE32(PANEL_PLANE_BR, 385 FIELD_VALUE(0, PANEL_PLANE_BR, BOTTOM, var->yres - 1)| 386 FIELD_VALUE(0, PANEL_PLANE_BR,RIGHT, var->xres - 1)); 387 388 /* set pixel format */ 389 reg = PEEK32(PANEL_DISPLAY_CTRL); 390 POKE32(PANEL_DISPLAY_CTRL, 391 FIELD_VALUE(reg, 392 PANEL_DISPLAY_CTRL, FORMAT, 393 (var->bits_per_pixel >> 4) 394 )); 395 }else{ 396 /* not implemented now */ 397 POKE32(CRT_FB_ADDRESS, crtc->oScreen); 398 reg = var->xres * (var->bits_per_pixel >> 3); 399 /* crtc->channel is not equal to par->index on numeric,be aware of that */ 400 reg = PADDING(crtc->line_pad, reg); 401 402 POKE32(CRT_FB_WIDTH, 403 FIELD_VALUE(0, CRT_FB_WIDTH, WIDTH, reg)| 404 FIELD_VALUE(0, CRT_FB_WIDTH, OFFSET, fix->line_length)); 405 406 /* SET PIXEL FORMAT */ 407 reg = PEEK32(CRT_DISPLAY_CTRL); 408 reg = FIELD_VALUE(reg, CRT_DISPLAY_CTRL, FORMAT, var->bits_per_pixel >> 4); 409 POKE32(CRT_DISPLAY_CTRL, reg); 410 411 } 412 413 414exit: 415 return ret; 416} 417 418void hw_sm750_crtc_clear(struct lynxfb_crtc* crtc) 419{ 420 421 return; 422} 423 424int hw_sm750_setColReg(struct lynxfb_crtc* crtc, ushort index, 425 ushort red, ushort green, ushort blue) 426{ 427 static unsigned int add[]={PANEL_PALETTE_RAM,CRT_PALETTE_RAM}; 428 POKE32(add[crtc->channel] + index*4, (red<<16)|(green<<8)|blue); 429 return 0; 430} 431 432int hw_sm750le_setBLANK(struct lynxfb_output * output, int blank){ 433 int dpms,crtdb; 434 435 switch(blank) 436 { 437#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,10) 438 case FB_BLANK_UNBLANK: 439#else 440 case VESA_NO_BLANKING: 441#endif 442 dpms = CRT_DISPLAY_CTRL_DPMS_0; 443 crtdb = CRT_DISPLAY_CTRL_BLANK_OFF; 444 break; 445#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,10) 446 case FB_BLANK_NORMAL: 447 dpms = CRT_DISPLAY_CTRL_DPMS_0; 448 crtdb = CRT_DISPLAY_CTRL_BLANK_ON; 449 break; 450#endif 451#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,10) 452 case FB_BLANK_VSYNC_SUSPEND: 453#else 454 case VESA_VSYNC_SUSPEND: 455#endif 456 dpms = CRT_DISPLAY_CTRL_DPMS_2; 457 crtdb = CRT_DISPLAY_CTRL_BLANK_ON; 458 break; 459#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,10) 460 case FB_BLANK_HSYNC_SUSPEND: 461#else 462 case VESA_HSYNC_SUSPEND: 463#endif 464 dpms = CRT_DISPLAY_CTRL_DPMS_1; 465 crtdb = CRT_DISPLAY_CTRL_BLANK_ON; 466 break; 467#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,10) 468 case FB_BLANK_POWERDOWN: 469#else 470 case VESA_POWERDOWN: 471#endif 472 dpms = CRT_DISPLAY_CTRL_DPMS_3; 473 crtdb = CRT_DISPLAY_CTRL_BLANK_ON; 474 break; 475 default: 476 return -EINVAL; 477 } 478 479 if(output->paths & sm750_crt){ 480 POKE32(CRT_DISPLAY_CTRL, FIELD_VALUE(PEEK32(CRT_DISPLAY_CTRL), CRT_DISPLAY_CTRL, DPMS, dpms)); 481 POKE32(CRT_DISPLAY_CTRL, FIELD_VALUE(PEEK32(CRT_DISPLAY_CTRL), CRT_DISPLAY_CTRL, BLANK, crtdb)); 482 } 483 return 0; 484} 485 486int hw_sm750_setBLANK(struct lynxfb_output* output,int blank) 487{ 488 unsigned int dpms, pps, crtdb; 489 490 dpms = pps = crtdb = 0; 491 492 switch (blank) 493 { 494#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,10) 495 case FB_BLANK_UNBLANK: 496#else 497 case VESA_NO_BLANKING: 498#endif 499 pr_info("flag = FB_BLANK_UNBLANK \n"); 500 dpms = SYSTEM_CTRL_DPMS_VPHP; 501 pps = PANEL_DISPLAY_CTRL_DATA_ENABLE; 502 crtdb = CRT_DISPLAY_CTRL_BLANK_OFF; 503 break; 504#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,10) 505 case FB_BLANK_NORMAL: 506 pr_info("flag = FB_BLANK_NORMAL \n"); 507 dpms = SYSTEM_CTRL_DPMS_VPHP; 508 pps = PANEL_DISPLAY_CTRL_DATA_DISABLE; 509 crtdb = CRT_DISPLAY_CTRL_BLANK_ON; 510 break; 511#endif 512#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,10) 513 case FB_BLANK_VSYNC_SUSPEND: 514#else 515 case VESA_VSYNC_SUSPEND: 516#endif 517 dpms = SYSTEM_CTRL_DPMS_VNHP; 518 pps = PANEL_DISPLAY_CTRL_DATA_DISABLE; 519 crtdb = CRT_DISPLAY_CTRL_BLANK_ON; 520 break; 521#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,10) 522 case FB_BLANK_HSYNC_SUSPEND: 523#else 524 case VESA_HSYNC_SUSPEND: 525#endif 526 dpms = SYSTEM_CTRL_DPMS_VPHN; 527 pps = PANEL_DISPLAY_CTRL_DATA_DISABLE; 528 crtdb = CRT_DISPLAY_CTRL_BLANK_ON; 529 break; 530#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,10) 531 case FB_BLANK_POWERDOWN: 532#else 533 case VESA_POWERDOWN: 534#endif 535 dpms = SYSTEM_CTRL_DPMS_VNHN; 536 pps = PANEL_DISPLAY_CTRL_DATA_DISABLE; 537 crtdb = CRT_DISPLAY_CTRL_BLANK_ON; 538 break; 539 } 540 541 if(output->paths & sm750_crt){ 542 543 POKE32(SYSTEM_CTRL,FIELD_VALUE(PEEK32(SYSTEM_CTRL), SYSTEM_CTRL, DPMS, dpms)); 544 POKE32(CRT_DISPLAY_CTRL,FIELD_VALUE(PEEK32(CRT_DISPLAY_CTRL), CRT_DISPLAY_CTRL,BLANK, crtdb)); 545 } 546 547 if(output->paths & sm750_panel){ 548 POKE32(PANEL_DISPLAY_CTRL, FIELD_VALUE(PEEK32(PANEL_DISPLAY_CTRL), PANEL_DISPLAY_CTRL, DATA, pps)); 549 } 550 551 return 0; 552} 553 554 555void hw_sm750_initAccel(struct lynx_share * share) 556{ 557 u32 reg; 558 enable2DEngine(1); 559 560 if(getChipType() == SM750LE){ 561 reg = PEEK32(DE_STATE1); 562 reg = FIELD_SET(reg, DE_STATE1, DE_ABORT,ON); 563 POKE32(DE_STATE1,reg); 564 565 reg = PEEK32(DE_STATE1); 566 reg = FIELD_SET(reg, DE_STATE1, DE_ABORT,OFF); 567 POKE32(DE_STATE1, reg); 568 569 }else{ 570 /* engine reset */ 571 reg = PEEK32(SYSTEM_CTRL); 572 reg = FIELD_SET(reg, SYSTEM_CTRL, DE_ABORT,ON); 573 POKE32(SYSTEM_CTRL, reg); 574 575 reg = PEEK32(SYSTEM_CTRL); 576 reg = FIELD_SET(reg, SYSTEM_CTRL, DE_ABORT,OFF); 577 POKE32(SYSTEM_CTRL, reg); 578 } 579 580 /* call 2d init */ 581 share->accel.de_init(&share->accel); 582} 583 584int hw_sm750le_deWait(void) 585{ 586 int i=0x10000000; 587 while(i--){ 588 unsigned int dwVal = PEEK32(DE_STATE2); 589 if((FIELD_GET(dwVal, DE_STATE2, DE_STATUS) == DE_STATE2_DE_STATUS_IDLE) && 590 (FIELD_GET(dwVal, DE_STATE2, DE_FIFO) == DE_STATE2_DE_FIFO_EMPTY) && 591 (FIELD_GET(dwVal, DE_STATE2, DE_MEM_FIFO) == DE_STATE2_DE_MEM_FIFO_EMPTY)) 592 { 593 return 0; 594 } 595 } 596 /* timeout error */ 597 return -1; 598} 599 600 601int hw_sm750_deWait(void) 602{ 603 int i=0x10000000; 604 while(i--){ 605 unsigned int dwVal = PEEK32(SYSTEM_CTRL); 606 if((FIELD_GET(dwVal,SYSTEM_CTRL,DE_STATUS) == SYSTEM_CTRL_DE_STATUS_IDLE) && 607 (FIELD_GET(dwVal,SYSTEM_CTRL,DE_FIFO) == SYSTEM_CTRL_DE_FIFO_EMPTY) && 608 (FIELD_GET(dwVal,SYSTEM_CTRL,DE_MEM_FIFO) == SYSTEM_CTRL_DE_MEM_FIFO_EMPTY)) 609 { 610 return 0; 611 } 612 } 613 /* timeout error */ 614 return -1; 615} 616 617int hw_sm750_pan_display(struct lynxfb_crtc *crtc, 618 const struct fb_var_screeninfo *var, 619 const struct fb_info *info) 620{ 621 uint32_t total; 622 //check params 623 if ((var->xoffset + var->xres > var->xres_virtual) || 624 (var->yoffset + var->yres > var->yres_virtual)) { 625 return -EINVAL; 626 } 627 628 total = var->yoffset * info->fix.line_length + 629 ((var->xoffset * var->bits_per_pixel) >> 3); 630 total += crtc->oScreen; 631 if (crtc->channel == sm750_primary) { 632 POKE32(PANEL_FB_ADDRESS, 633 FIELD_VALUE(PEEK32(PANEL_FB_ADDRESS), 634 PANEL_FB_ADDRESS, ADDRESS, total)); 635 } else { 636 POKE32(CRT_FB_ADDRESS, 637 FIELD_VALUE(PEEK32(CRT_FB_ADDRESS), 638 CRT_FB_ADDRESS, ADDRESS, total)); 639 } 640 return 0; 641} 642 643