xref: /linux/drivers/staging/sm750fb/sm750_hw.c (revision a100922a3855eb35ecd465f1d558546b1e144445)
1 // SPDX-License-Identifier: GPL-2.0
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/sizes.h>
21 
22 #include "sm750.h"
23 #include "ddk750.h"
24 #include "sm750_accel.h"
25 
26 void __iomem *mmio750;
27 
hw_sm750_map(struct sm750_dev * sm750_dev,struct pci_dev * pdev)28 int hw_sm750_map(struct sm750_dev *sm750_dev, struct pci_dev *pdev)
29 {
30 	int ret;
31 
32 	ret = 0;
33 
34 	sm750_dev->vidreg_start = pci_resource_start(pdev, 1);
35 	sm750_dev->vidreg_size = SZ_2M;
36 
37 	pr_info("mmio phyAddr = %lx\n", sm750_dev->vidreg_start);
38 
39 	/*
40 	 * reserve the vidreg space of smi adaptor
41 	 * if you do this, you need to add release region code
42 	 * in lynxfb_remove, or memory will not be mapped again
43 	 * successfully
44 	 */
45 	ret = pci_request_region(pdev, 1, "sm750fb");
46 	if (ret) {
47 		pr_err("Can not request PCI regions.\n");
48 		goto exit;
49 	}
50 
51 	/* now map mmio and vidmem */
52 	sm750_dev->pvReg =
53 		ioremap(sm750_dev->vidreg_start, sm750_dev->vidreg_size);
54 	if (!sm750_dev->pvReg) {
55 		pr_err("mmio failed\n");
56 		ret = -EFAULT;
57 		goto exit;
58 	}
59 	pr_info("mmio virtual addr = %p\n", sm750_dev->pvReg);
60 
61 	sm750_dev->accel.dprBase = sm750_dev->pvReg + DE_BASE_ADDR_TYPE1;
62 	sm750_dev->accel.dpPortBase = sm750_dev->pvReg + DE_PORT_ADDR_TYPE1;
63 
64 	mmio750 = sm750_dev->pvReg;
65 	sm750_set_chip_type(sm750_dev->devid, sm750_dev->revid);
66 
67 	sm750_dev->vidmem_start = pci_resource_start(pdev, 0);
68 	/*
69 	 * don't use pdev_resource[x].end - resource[x].start to
70 	 * calculate the resource size, it's only the maximum available
71 	 * size but not the actual size, using
72 	 * @ddk750_get_vm_size function can be safe.
73 	 */
74 	sm750_dev->vidmem_size = ddk750_get_vm_size();
75 	pr_info("video memory phyAddr = %lx, size = %u bytes\n",
76 		sm750_dev->vidmem_start, sm750_dev->vidmem_size);
77 
78 	/* reserve the vidmem space of smi adaptor */
79 	sm750_dev->pvMem =
80 		ioremap_wc(sm750_dev->vidmem_start, sm750_dev->vidmem_size);
81 	if (!sm750_dev->pvMem) {
82 		iounmap(sm750_dev->pvReg);
83 		pr_err("Map video memory failed\n");
84 		ret = -EFAULT;
85 		goto exit;
86 	}
87 	pr_info("video memory vaddr = %p\n", sm750_dev->pvMem);
88 exit:
89 	return ret;
90 }
91 
hw_sm750_inithw(struct sm750_dev * sm750_dev,struct pci_dev * pdev)92 int hw_sm750_inithw(struct sm750_dev *sm750_dev, struct pci_dev *pdev)
93 {
94 	struct init_status *parm;
95 
96 	parm = &sm750_dev->initParm;
97 	if (parm->chip_clk == 0)
98 		parm->chip_clk = (sm750_get_chip_type() == SM750LE) ?
99 					       DEFAULT_SM750LE_CHIP_CLOCK :
100 					       DEFAULT_SM750_CHIP_CLOCK;
101 
102 	if (parm->mem_clk == 0)
103 		parm->mem_clk = parm->chip_clk;
104 	if (parm->master_clk == 0)
105 		parm->master_clk = parm->chip_clk / 3;
106 
107 	ddk750_init_hw((struct initchip_param *)&sm750_dev->initParm);
108 	/* for sm718, open pci burst */
109 	if (sm750_dev->devid == 0x718) {
110 		poke32(SYSTEM_CTRL,
111 		       peek32(SYSTEM_CTRL) | SYSTEM_CTRL_PCI_BURST);
112 	}
113 
114 	if (sm750_get_chip_type() != SM750LE) {
115 		unsigned int val;
116 		/* does user need CRT? */
117 		if (sm750_dev->nocrt) {
118 			poke32(MISC_CTRL,
119 			       peek32(MISC_CTRL) | MISC_CTRL_DAC_POWER_OFF);
120 			/* shut off dpms */
121 			val = peek32(SYSTEM_CTRL) & ~SYSTEM_CTRL_DPMS_MASK;
122 			val |= SYSTEM_CTRL_DPMS_VPHN;
123 			poke32(SYSTEM_CTRL, val);
124 		} else {
125 			poke32(MISC_CTRL,
126 			       peek32(MISC_CTRL) & ~MISC_CTRL_DAC_POWER_OFF);
127 			/* turn on dpms */
128 			val = peek32(SYSTEM_CTRL) & ~SYSTEM_CTRL_DPMS_MASK;
129 			val |= SYSTEM_CTRL_DPMS_VPHP;
130 			poke32(SYSTEM_CTRL, val);
131 		}
132 
133 		val = peek32(PANEL_DISPLAY_CTRL) &
134 		      ~(PANEL_DISPLAY_CTRL_DUAL_DISPLAY |
135 			PANEL_DISPLAY_CTRL_DOUBLE_PIXEL);
136 		switch (sm750_dev->pnltype) {
137 		case sm750_24TFT:
138 			break;
139 		case sm750_doubleTFT:
140 			val |= PANEL_DISPLAY_CTRL_DOUBLE_PIXEL;
141 			break;
142 		case sm750_dualTFT:
143 			val |= PANEL_DISPLAY_CTRL_DUAL_DISPLAY;
144 			break;
145 		}
146 		poke32(PANEL_DISPLAY_CTRL, val);
147 	} else {
148 		/*
149 		 * for 750LE, no DVI chip initialization
150 		 * makes Monitor no signal
151 		 *
152 		 * Set up GPIO for software I2C to program DVI chip in the
153 		 * Xilinx SP605 board, in order to have video signal.
154 		 */
155 		sm750_sw_i2c_init(0, 1);
156 
157 		/*
158 		 * Customer may NOT use CH7301 DVI chip, which has to be
159 		 * initialized differently.
160 		 */
161 		if (sm750_sw_i2c_read_reg(0xec, 0x4a) == 0x95) {
162 			/*
163 			 * The following register values for CH7301 are from
164 			 * Chrontel app note and our experiment.
165 			 */
166 			pr_info("yes,CH7301 DVI chip found\n");
167 			sm750_sw_i2c_write_reg(0xec, 0x1d, 0x16);
168 			sm750_sw_i2c_write_reg(0xec, 0x21, 0x9);
169 			sm750_sw_i2c_write_reg(0xec, 0x49, 0xC0);
170 			pr_info("okay,CH7301 DVI chip setup done\n");
171 		}
172 	}
173 
174 	/* init 2d engine */
175 	if (!sm750_dev->accel_off)
176 		hw_sm750_init_accel(sm750_dev);
177 
178 	return 0;
179 }
180 
hw_sm750_output_set_mode(struct lynxfb_output * output,struct fb_var_screeninfo * var,struct fb_fix_screeninfo * fix)181 int hw_sm750_output_set_mode(struct lynxfb_output *output,
182 			     struct fb_var_screeninfo *var,
183 			     struct fb_fix_screeninfo *fix)
184 {
185 	int ret;
186 	enum disp_output disp_set;
187 	int channel;
188 
189 	ret = 0;
190 	disp_set = 0;
191 	channel = *output->channel;
192 
193 	if (sm750_get_chip_type() != SM750LE) {
194 		if (channel == sm750_primary) {
195 			pr_info("primary channel\n");
196 			if (output->paths & sm750_panel)
197 				disp_set |= do_LCD1_PRI;
198 			if (output->paths & sm750_crt)
199 				disp_set |= do_CRT_PRI;
200 
201 		} else {
202 			pr_info("secondary channel\n");
203 			if (output->paths & sm750_panel)
204 				disp_set |= do_LCD1_SEC;
205 			if (output->paths & sm750_crt)
206 				disp_set |= do_CRT_SEC;
207 		}
208 		ddk750_set_logical_disp_out(disp_set);
209 	} else {
210 		/* just open DISPLAY_CONTROL_750LE register bit 3:0 */
211 		u32 reg;
212 
213 		reg = peek32(DISPLAY_CONTROL_750LE);
214 		reg |= 0xf;
215 		poke32(DISPLAY_CONTROL_750LE, reg);
216 	}
217 
218 	pr_info("ddk setlogicdispout done\n");
219 	return ret;
220 }
221 
hw_sm750_crtc_check_mode(struct lynxfb_crtc * crtc,struct fb_var_screeninfo * var)222 int hw_sm750_crtc_check_mode(struct lynxfb_crtc *crtc,
223 			     struct fb_var_screeninfo *var)
224 {
225 	struct sm750_dev *sm750_dev;
226 	struct lynxfb_par *par = container_of(crtc, struct lynxfb_par, crtc);
227 
228 	sm750_dev = par->dev;
229 
230 	switch (var->bits_per_pixel) {
231 	case 8:
232 	case 16:
233 		break;
234 	case 32:
235 		if (sm750_dev->revid == SM750LE_REVISION_ID) {
236 			pr_debug("750le do not support 32bpp\n");
237 			return -EINVAL;
238 		}
239 		break;
240 	default:
241 		return -EINVAL;
242 	}
243 
244 	return 0;
245 }
246 
247 /* set the controller's mode for @crtc charged with @var and @fix parameters */
hw_sm750_crtc_set_mode(struct lynxfb_crtc * crtc,struct fb_var_screeninfo * var,struct fb_fix_screeninfo * fix)248 int hw_sm750_crtc_set_mode(struct lynxfb_crtc *crtc,
249 			   struct fb_var_screeninfo *var,
250 			   struct fb_fix_screeninfo *fix)
251 {
252 	int ret, fmt;
253 	u32 reg;
254 	struct mode_parameter modparm;
255 	enum clock_type clock;
256 	struct sm750_dev *sm750_dev;
257 	struct lynxfb_par *par;
258 
259 	ret = 0;
260 	par = container_of(crtc, struct lynxfb_par, crtc);
261 	sm750_dev = par->dev;
262 
263 	if (!sm750_dev->accel_off) {
264 		/* set 2d engine pixel format according to mode bpp */
265 		switch (var->bits_per_pixel) {
266 		case 8:
267 			fmt = 0;
268 			break;
269 		case 16:
270 			fmt = 1;
271 			break;
272 		case 32:
273 		default:
274 			fmt = 2;
275 			break;
276 		}
277 		sm750_hw_set2dformat(&sm750_dev->accel, fmt);
278 	}
279 
280 	/* set timing */
281 	modparm.pixel_clock = ps_to_hz(var->pixclock);
282 	modparm.vertical_sync_polarity =
283 		(var->sync & FB_SYNC_HOR_HIGH_ACT) ? POS : NEG;
284 	modparm.horizontal_sync_polarity =
285 		(var->sync & FB_SYNC_VERT_HIGH_ACT) ? POS : NEG;
286 	modparm.clock_phase_polarity =
287 		(var->sync & FB_SYNC_COMP_HIGH_ACT) ? POS : NEG;
288 	modparm.horizontal_display_end = var->xres;
289 	modparm.horizontal_sync_width = var->hsync_len;
290 	modparm.horizontal_sync_start = var->xres + var->right_margin;
291 	modparm.horizontal_total = var->xres + var->left_margin +
292 				   var->right_margin + var->hsync_len;
293 	modparm.vertical_display_end = var->yres;
294 	modparm.vertical_sync_height = var->vsync_len;
295 	modparm.vertical_sync_start = var->yres + var->lower_margin;
296 	modparm.vertical_total = var->yres + var->upper_margin +
297 				 var->lower_margin + var->vsync_len;
298 
299 	/* choose pll */
300 	if (crtc->channel != sm750_secondary)
301 		clock = PRIMARY_PLL;
302 	else
303 		clock = SECONDARY_PLL;
304 
305 	pr_debug("Request pixel clock = %lu\n", modparm.pixel_clock);
306 	ret = ddk750_set_mode_timing(&modparm, clock);
307 	if (ret) {
308 		pr_err("Set mode timing failed\n");
309 		goto exit;
310 	}
311 
312 	if (crtc->channel != sm750_secondary) {
313 		/* set pitch, offset, width, start address, etc... */
314 		poke32(PANEL_FB_ADDRESS,
315 		       crtc->o_screen & PANEL_FB_ADDRESS_ADDRESS_MASK);
316 
317 		reg = var->xres * (var->bits_per_pixel >> 3);
318 		/*
319 		 * crtc->channel is not equal to par->index on numeric,
320 		 * be aware of that
321 		 */
322 		reg = ALIGN(reg, crtc->line_pad);
323 		reg = (reg << PANEL_FB_WIDTH_WIDTH_SHIFT) &
324 		      PANEL_FB_WIDTH_WIDTH_MASK;
325 		reg |= (fix->line_length & PANEL_FB_WIDTH_OFFSET_MASK);
326 		poke32(PANEL_FB_WIDTH, reg);
327 
328 		reg = ((var->xres - 1) << PANEL_WINDOW_WIDTH_WIDTH_SHIFT) &
329 		      PANEL_WINDOW_WIDTH_WIDTH_MASK;
330 		reg |= (var->xoffset & PANEL_WINDOW_WIDTH_X_MASK);
331 		poke32(PANEL_WINDOW_WIDTH, reg);
332 
333 		reg = (var->yres_virtual - 1)
334 		      << PANEL_WINDOW_HEIGHT_HEIGHT_SHIFT;
335 		reg &= PANEL_WINDOW_HEIGHT_HEIGHT_MASK;
336 		reg |= (var->yoffset & PANEL_WINDOW_HEIGHT_Y_MASK);
337 		poke32(PANEL_WINDOW_HEIGHT, reg);
338 
339 		poke32(PANEL_PLANE_TL, 0);
340 
341 		reg = ((var->yres - 1) << PANEL_PLANE_BR_BOTTOM_SHIFT) &
342 		      PANEL_PLANE_BR_BOTTOM_MASK;
343 		reg |= ((var->xres - 1) & PANEL_PLANE_BR_RIGHT_MASK);
344 		poke32(PANEL_PLANE_BR, reg);
345 
346 		/* set pixel format */
347 		reg = peek32(PANEL_DISPLAY_CTRL);
348 		poke32(PANEL_DISPLAY_CTRL, reg | (var->bits_per_pixel >> 4));
349 	} else {
350 		/* not implemented now */
351 		poke32(CRT_FB_ADDRESS, crtc->o_screen);
352 		reg = var->xres * (var->bits_per_pixel >> 3);
353 		/*
354 		 * crtc->channel is not equal to par->index on numeric,
355 		 * be aware of that
356 		 */
357 		reg = ALIGN(reg, crtc->line_pad) << CRT_FB_WIDTH_WIDTH_SHIFT;
358 		reg &= CRT_FB_WIDTH_WIDTH_MASK;
359 		reg |= (fix->line_length & CRT_FB_WIDTH_OFFSET_MASK);
360 		poke32(CRT_FB_WIDTH, reg);
361 
362 		/* SET PIXEL FORMAT */
363 		reg = peek32(CRT_DISPLAY_CTRL);
364 		reg |= ((var->bits_per_pixel >> 4) &
365 			CRT_DISPLAY_CTRL_FORMAT_MASK);
366 		poke32(CRT_DISPLAY_CTRL, reg);
367 	}
368 
369 exit:
370 	return ret;
371 }
372 
hw_sm750_set_col_reg(struct lynxfb_crtc * crtc,ushort index,ushort red,ushort green,ushort blue)373 int hw_sm750_set_col_reg(struct lynxfb_crtc *crtc, ushort index, ushort red,
374 			 ushort green, ushort blue)
375 {
376 	static unsigned int add[] = { PANEL_PALETTE_RAM, CRT_PALETTE_RAM };
377 
378 	poke32(add[crtc->channel] + index * 4,
379 	       (red << 16) | (green << 8) | blue);
380 	return 0;
381 }
382 
hw_sm750le_set_blank(struct lynxfb_output * output,int blank)383 int hw_sm750le_set_blank(struct lynxfb_output *output, int blank)
384 {
385 	int dpms, crtdb;
386 
387 	switch (blank) {
388 	case FB_BLANK_UNBLANK:
389 		dpms = CRT_DISPLAY_CTRL_DPMS_0;
390 		crtdb = 0;
391 		break;
392 	case FB_BLANK_NORMAL:
393 		dpms = CRT_DISPLAY_CTRL_DPMS_0;
394 		crtdb = CRT_DISPLAY_CTRL_BLANK;
395 		break;
396 	case FB_BLANK_VSYNC_SUSPEND:
397 		dpms = CRT_DISPLAY_CTRL_DPMS_2;
398 		crtdb = CRT_DISPLAY_CTRL_BLANK;
399 		break;
400 	case FB_BLANK_HSYNC_SUSPEND:
401 		dpms = CRT_DISPLAY_CTRL_DPMS_1;
402 		crtdb = CRT_DISPLAY_CTRL_BLANK;
403 		break;
404 	case FB_BLANK_POWERDOWN:
405 		dpms = CRT_DISPLAY_CTRL_DPMS_3;
406 		crtdb = CRT_DISPLAY_CTRL_BLANK;
407 		break;
408 	default:
409 		return -EINVAL;
410 	}
411 
412 	if (output->paths & sm750_crt) {
413 		unsigned int val;
414 
415 		val = peek32(CRT_DISPLAY_CTRL) & ~CRT_DISPLAY_CTRL_DPMS_MASK;
416 		poke32(CRT_DISPLAY_CTRL, val | dpms);
417 
418 		val = peek32(CRT_DISPLAY_CTRL) & ~CRT_DISPLAY_CTRL_BLANK;
419 		poke32(CRT_DISPLAY_CTRL, val | crtdb);
420 	}
421 	return 0;
422 }
423 
hw_sm750_set_blank(struct lynxfb_output * output,int blank)424 int hw_sm750_set_blank(struct lynxfb_output *output, int blank)
425 {
426 	unsigned int dpms, pps, crtdb;
427 
428 	dpms = 0;
429 	pps = 0;
430 	crtdb = 0;
431 
432 	switch (blank) {
433 	case FB_BLANK_UNBLANK:
434 		pr_debug("flag = FB_BLANK_UNBLANK\n");
435 		dpms = SYSTEM_CTRL_DPMS_VPHP;
436 		pps = PANEL_DISPLAY_CTRL_DATA;
437 		break;
438 	case FB_BLANK_NORMAL:
439 		pr_debug("flag = FB_BLANK_NORMAL\n");
440 		dpms = SYSTEM_CTRL_DPMS_VPHP;
441 		crtdb = CRT_DISPLAY_CTRL_BLANK;
442 		break;
443 	case FB_BLANK_VSYNC_SUSPEND:
444 		dpms = SYSTEM_CTRL_DPMS_VNHP;
445 		crtdb = CRT_DISPLAY_CTRL_BLANK;
446 		break;
447 	case FB_BLANK_HSYNC_SUSPEND:
448 		dpms = SYSTEM_CTRL_DPMS_VPHN;
449 		crtdb = CRT_DISPLAY_CTRL_BLANK;
450 		break;
451 	case FB_BLANK_POWERDOWN:
452 		dpms = SYSTEM_CTRL_DPMS_VNHN;
453 		crtdb = CRT_DISPLAY_CTRL_BLANK;
454 		break;
455 	}
456 
457 	if (output->paths & sm750_crt) {
458 		unsigned int val = peek32(SYSTEM_CTRL) & ~SYSTEM_CTRL_DPMS_MASK;
459 
460 		poke32(SYSTEM_CTRL, val | dpms);
461 
462 		val = peek32(CRT_DISPLAY_CTRL) & ~CRT_DISPLAY_CTRL_BLANK;
463 		poke32(CRT_DISPLAY_CTRL, val | crtdb);
464 	}
465 
466 	if (output->paths & sm750_panel) {
467 		unsigned int val = peek32(PANEL_DISPLAY_CTRL);
468 
469 		val &= ~PANEL_DISPLAY_CTRL_DATA;
470 		val |= pps;
471 		poke32(PANEL_DISPLAY_CTRL, val);
472 	}
473 
474 	return 0;
475 }
476 
hw_sm750_init_accel(struct sm750_dev * sm750_dev)477 void hw_sm750_init_accel(struct sm750_dev *sm750_dev)
478 {
479 	u32 reg;
480 
481 	sm750_enable_2d_engine(1);
482 
483 	if (sm750_get_chip_type() == SM750LE) {
484 		reg = peek32(DE_STATE1);
485 		reg |= DE_STATE1_DE_ABORT;
486 		poke32(DE_STATE1, reg);
487 
488 		reg = peek32(DE_STATE1);
489 		reg &= ~DE_STATE1_DE_ABORT;
490 		poke32(DE_STATE1, reg);
491 
492 	} else {
493 		/* engine reset */
494 		reg = peek32(SYSTEM_CTRL);
495 		reg |= SYSTEM_CTRL_DE_ABORT;
496 		poke32(SYSTEM_CTRL, reg);
497 
498 		reg = peek32(SYSTEM_CTRL);
499 		reg &= ~SYSTEM_CTRL_DE_ABORT;
500 		poke32(SYSTEM_CTRL, reg);
501 	}
502 
503 	/* call 2d init */
504 	sm750_dev->accel.de_init(&sm750_dev->accel);
505 }
506 
hw_sm750le_de_wait(void)507 int hw_sm750le_de_wait(void)
508 {
509 	int i = 0x10000000;
510 	unsigned int mask = DE_STATE2_DE_STATUS_BUSY | DE_STATE2_DE_FIFO_EMPTY |
511 			    DE_STATE2_DE_MEM_FIFO_EMPTY;
512 
513 	while (i--) {
514 		unsigned int val = peek32(DE_STATE2);
515 
516 		if ((val & mask) ==
517 		    (DE_STATE2_DE_FIFO_EMPTY | DE_STATE2_DE_MEM_FIFO_EMPTY))
518 			return 0;
519 	}
520 	/* timeout error */
521 	return -1;
522 }
523 
hw_sm750_de_wait(void)524 int hw_sm750_de_wait(void)
525 {
526 	int i = 0x10000000;
527 	unsigned int mask = SYSTEM_CTRL_DE_STATUS_BUSY |
528 			    SYSTEM_CTRL_DE_FIFO_EMPTY |
529 			    SYSTEM_CTRL_DE_MEM_FIFO_EMPTY;
530 
531 	while (i--) {
532 		unsigned int val = peek32(SYSTEM_CTRL);
533 
534 		if ((val & mask) ==
535 		    (SYSTEM_CTRL_DE_FIFO_EMPTY | SYSTEM_CTRL_DE_MEM_FIFO_EMPTY))
536 			return 0;
537 	}
538 	/* timeout error */
539 	return -1;
540 }
541 
hw_sm750_pan_display(struct lynxfb_crtc * crtc,const struct fb_var_screeninfo * var,const struct fb_info * info)542 int hw_sm750_pan_display(struct lynxfb_crtc *crtc,
543 			 const struct fb_var_screeninfo *var,
544 			 const struct fb_info *info)
545 {
546 	u32 total;
547 	/* check params */
548 	if ((var->xoffset + var->xres > var->xres_virtual) ||
549 	    (var->yoffset + var->yres > var->yres_virtual)) {
550 		return -EINVAL;
551 	}
552 
553 	total = var->yoffset * info->fix.line_length +
554 		((var->xoffset * var->bits_per_pixel) >> 3);
555 	total += crtc->o_screen;
556 	if (crtc->channel == sm750_primary) {
557 		poke32(PANEL_FB_ADDRESS,
558 		       peek32(PANEL_FB_ADDRESS) |
559 			       (total & PANEL_FB_ADDRESS_ADDRESS_MASK));
560 	} else {
561 		poke32(CRT_FB_ADDRESS,
562 		       peek32(CRT_FB_ADDRESS) |
563 			       (total & CRT_FB_ADDRESS_ADDRESS_MASK));
564 	}
565 	return 0;
566 }
567