1 /* $NetBSD: mgx.c,v 1.9 2016/05/07 15:32:08 macallan Exp $ */ 2 3 /*- 4 * Copyright (c) 2014 Michael Lorenz 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 17 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 18 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 20 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 /* a console driver for the SSB 4096V-MGX graphics card */ 30 31 #include <sys/cdefs.h> 32 __KERNEL_RCSID(0, "$NetBSD: mgx.c,v 1.9 2016/05/07 15:32:08 macallan Exp $"); 33 34 #include <sys/param.h> 35 #include <sys/systm.h> 36 #include <sys/buf.h> 37 #include <sys/device.h> 38 #include <sys/ioctl.h> 39 #include <sys/conf.h> 40 #include <sys/kmem.h> 41 #include <sys/kauth.h> 42 43 #include <sys/bus.h> 44 #include <machine/autoconf.h> 45 46 #include <dev/sbus/sbusvar.h> 47 #include <dev/sun/fbio.h> 48 #include <dev/sun/fbvar.h> 49 50 #include <dev/wscons/wsdisplayvar.h> 51 #include <dev/wscons/wsconsio.h> 52 #include <dev/wsfont/wsfont.h> 53 #include <dev/rasops/rasops.h> 54 55 #include <dev/wscons/wsdisplay_vconsvar.h> 56 #include <dev/wscons/wsdisplay_glyphcachevar.h> 57 58 #include <dev/ic/vgareg.h> 59 #include <dev/sbus/mgxreg.h> 60 61 #include "ioconf.h" 62 63 #include "opt_wsemul.h" 64 #include "opt_mgx.h" 65 66 struct mgx_softc { 67 device_t sc_dev; 68 struct fbdevice sc_fb; /* frame buffer device */ 69 bus_space_tag_t sc_tag; 70 bus_space_handle_t sc_blith; 71 bus_space_handle_t sc_vgah; 72 bus_addr_t sc_paddr, sc_rpaddr; 73 void *sc_fbaddr; 74 uint8_t *sc_cursor; 75 int sc_width; 76 int sc_height; 77 int sc_stride; 78 int sc_depth; 79 int sc_fbsize; 80 int sc_mode; 81 char sc_name[8]; 82 uint32_t sc_dec; 83 u_char sc_cmap_red[256]; 84 u_char sc_cmap_green[256]; 85 u_char sc_cmap_blue[256]; 86 int sc_cursor_x, sc_cursor_y; 87 int sc_hotspot_x, sc_hotspot_y; 88 int sc_video; 89 void (*sc_putchar)(void *, int, int, u_int, long); 90 struct vcons_screen sc_console_screen; 91 struct wsscreen_descr sc_defaultscreen_descr; 92 const struct wsscreen_descr *sc_screens[1]; 93 struct wsscreen_list sc_screenlist; 94 struct vcons_data vd; 95 glyphcache sc_gc; 96 }; 97 98 static int mgx_match(device_t, cfdata_t, void *); 99 static void mgx_attach(device_t, device_t, void *); 100 static int mgx_ioctl(void *, void *, u_long, void *, int, 101 struct lwp*); 102 static paddr_t mgx_mmap(void *, void *, off_t, int); 103 static void mgx_init_screen(void *, struct vcons_screen *, int, 104 long *); 105 static void mgx_write_dac(struct mgx_softc *, int, int, int, int); 106 static void mgx_setup(struct mgx_softc *, int); 107 static void mgx_init_palette(struct mgx_softc *); 108 static int mgx_putcmap(struct mgx_softc *, struct wsdisplay_cmap *); 109 static int mgx_getcmap(struct mgx_softc *, struct wsdisplay_cmap *); 110 static int mgx_wait_engine(struct mgx_softc *); 111 __unused static int mgx_wait_host(struct mgx_softc *); 112 static int mgx_wait_fifo(struct mgx_softc *, unsigned int); 113 114 static void mgx_bitblt(void *, int, int, int, int, int, int, int); 115 static void mgx_rectfill(void *, int, int, int, int, long); 116 117 static void mgx_putchar(void *, int, int, u_int, long); 118 static void mgx_cursor(void *, int, int, int); 119 static void mgx_copycols(void *, int, int, int, int); 120 static void mgx_erasecols(void *, int, int, int, long); 121 static void mgx_copyrows(void *, int, int, int); 122 static void mgx_eraserows(void *, int, int, long); 123 124 static int mgx_do_cursor(struct mgx_softc *, struct wsdisplay_cursor *); 125 static void mgx_set_cursor(struct mgx_softc *); 126 static void mgx_set_video(struct mgx_softc *, int); 127 128 CFATTACH_DECL_NEW(mgx, sizeof(struct mgx_softc), 129 mgx_match, mgx_attach, NULL, NULL); 130 131 struct wsdisplay_accessops mgx_accessops = { 132 mgx_ioctl, 133 mgx_mmap, 134 NULL, /* vcons_alloc_screen */ 135 NULL, /* vcons_free_screen */ 136 NULL, /* vcons_show_screen */ 137 NULL, /* load_font */ 138 NULL, /* polls */ 139 NULL, /* scroll */ 140 }; 141 142 static void mgx_unblank(device_t); 143 144 dev_type_open(mgxopen); 145 dev_type_close(mgxclose); 146 dev_type_ioctl(mgxioctl); 147 dev_type_mmap(mgxmmap); 148 149 const struct cdevsw mgx_cdevsw = { 150 .d_open = mgxopen, 151 .d_close = mgxclose, 152 .d_read = noread, 153 .d_write = nowrite, 154 .d_ioctl = mgxioctl, 155 .d_stop = nostop, 156 .d_tty = notty, 157 .d_poll = nopoll, 158 .d_mmap = mgxmmap, 159 .d_kqfilter = nokqfilter, 160 .d_discard = nodiscard, 161 .d_flag = D_OTHER 162 }; 163 164 /* frame buffer generic driver */ 165 static struct fbdriver mgx_fbdriver = { 166 mgx_unblank, mgxopen, mgxclose, mgxioctl, nopoll, mgxmmap, 167 nokqfilter 168 }; 169 170 171 static inline void 172 mgx_write_vga(struct mgx_softc *sc, uint32_t reg, uint8_t val) 173 { 174 bus_space_write_1(sc->sc_tag, sc->sc_vgah, reg ^ 3, val); 175 } 176 177 static inline uint8_t 178 mgx_read_vga(struct mgx_softc *sc, uint32_t reg) 179 { 180 return bus_space_read_1(sc->sc_tag, sc->sc_vgah, reg ^ 3); 181 } 182 183 static inline void 184 mgx_write_1(struct mgx_softc *sc, uint32_t reg, uint8_t val) 185 { 186 bus_space_write_1(sc->sc_tag, sc->sc_blith, reg ^ 3, val); 187 } 188 189 static inline uint8_t 190 mgx_read_1(struct mgx_softc *sc, uint32_t reg) 191 { 192 return bus_space_read_1(sc->sc_tag, sc->sc_blith, reg ^ 3); 193 } 194 195 #if 0 196 static inline uint32_t 197 mgx_read_4(struct mgx_softc *sc, uint32_t reg) 198 { 199 return bus_space_read_4(sc->sc_tag, sc->sc_blith, reg); 200 } 201 #endif 202 203 static inline void 204 mgx_write_2(struct mgx_softc *sc, uint32_t reg, uint16_t val) 205 { 206 bus_space_write_2(sc->sc_tag, sc->sc_blith, reg ^ 2, val); 207 } 208 209 static inline void 210 mgx_write_4(struct mgx_softc *sc, uint32_t reg, uint32_t val) 211 { 212 bus_space_write_4(sc->sc_tag, sc->sc_blith, reg, val); 213 } 214 215 static int 216 mgx_match(device_t parent, cfdata_t cf, void *aux) 217 { 218 struct sbus_attach_args *sa = aux; 219 220 if (strcmp("SMSI,mgx", sa->sa_name) == 0) 221 return 100; 222 return 0; 223 } 224 225 /* 226 * Attach a display. We need to notice if it is the console, too. 227 */ 228 static void 229 mgx_attach(device_t parent, device_t self, void *args) 230 { 231 struct mgx_softc *sc = device_private(self); 232 struct sbus_attach_args *sa = args; 233 struct wsemuldisplaydev_attach_args aa; 234 struct fbdevice *fb = &sc->sc_fb; 235 struct rasops_info *ri; 236 unsigned long defattr; 237 bus_space_handle_t bh; 238 int node = sa->sa_node; 239 int isconsole; 240 241 aprint_normal("\n"); 242 sc->sc_dev = self; 243 sc->sc_tag = sa->sa_bustag; 244 245 sc->sc_paddr = sbus_bus_addr(sa->sa_bustag, sa->sa_slot, 246 sa->sa_reg[8].oa_base); 247 sc->sc_rpaddr = sbus_bus_addr(sa->sa_bustag, sa->sa_slot, 248 sa->sa_reg[5].oa_base + MGX_REG_ATREG_OFFSET); 249 250 /* read geometry information from the device tree */ 251 sc->sc_width = prom_getpropint(sa->sa_node, "width", 1152); 252 sc->sc_height = prom_getpropint(sa->sa_node, "height", 900); 253 sc->sc_stride = prom_getpropint(sa->sa_node, "linebytes", 1152); 254 sc->sc_fbsize = prom_getpropint(sa->sa_node, "fb_size", 0x00400000); 255 sc->sc_fbaddr = NULL; 256 if (sc->sc_fbaddr == NULL) { 257 if (sbus_bus_map(sa->sa_bustag, 258 sa->sa_slot, 259 sa->sa_reg[8].oa_base, 260 sc->sc_fbsize, 261 BUS_SPACE_MAP_LINEAR | BUS_SPACE_MAP_LARGE, 262 &bh) != 0) { 263 aprint_error_dev(self, "couldn't map framebuffer\n"); 264 return; 265 } 266 sc->sc_fbaddr = bus_space_vaddr(sa->sa_bustag, bh); 267 } 268 269 if (sbus_bus_map(sa->sa_bustag, 270 sa->sa_slot, 271 sa->sa_reg[4].oa_base, 0x1000, 0, 272 &sc->sc_vgah) != 0) { 273 aprint_error("%s: couldn't map VGA registers\n", 274 device_xname(sc->sc_dev)); 275 return; 276 } 277 278 if (sbus_bus_map(sa->sa_bustag, 279 sa->sa_slot, 280 sa->sa_reg[5].oa_base + MGX_REG_ATREG_OFFSET, 0x1000, 281 0, &sc->sc_blith) != 0) { 282 aprint_error("%s: couldn't map blitter registers\n", 283 device_xname(sc->sc_dev)); 284 return; 285 } 286 287 mgx_setup(sc, MGX_DEPTH); 288 289 aprint_normal_dev(self, "[%s] %d MB framebuffer, %d x %d\n", 290 sc->sc_name, sc->sc_fbsize >> 20, sc->sc_width, sc->sc_height); 291 292 293 sc->sc_defaultscreen_descr = (struct wsscreen_descr) { 294 "default", 295 0, 0, 296 NULL, 297 8, 16, 298 WSSCREEN_WSCOLORS | WSSCREEN_HILIT, 299 NULL 300 }; 301 302 sc->sc_cursor_x = 0; 303 sc->sc_cursor_y = 0; 304 sc->sc_hotspot_x = 0; 305 sc->sc_hotspot_y = 0; 306 sc->sc_video = WSDISPLAYIO_VIDEO_ON; 307 308 sc->sc_screens[0] = &sc->sc_defaultscreen_descr; 309 sc->sc_screenlist = (struct wsscreen_list){1, sc->sc_screens}; 310 311 isconsole = fb_is_console(node); 312 313 sc->sc_mode = WSDISPLAYIO_MODE_EMUL; 314 wsfont_init(); 315 316 vcons_init(&sc->vd, sc, &sc->sc_defaultscreen_descr, &mgx_accessops); 317 sc->vd.init_screen = mgx_init_screen; 318 319 vcons_init_screen(&sc->vd, &sc->sc_console_screen, 1, &defattr); 320 sc->sc_console_screen.scr_flags |= VCONS_SCREEN_IS_STATIC; 321 322 ri = &sc->sc_console_screen.scr_ri; 323 324 sc->sc_defaultscreen_descr.nrows = ri->ri_rows; 325 sc->sc_defaultscreen_descr.ncols = ri->ri_cols; 326 sc->sc_defaultscreen_descr.textops = &ri->ri_ops; 327 sc->sc_defaultscreen_descr.capabilities = ri->ri_caps; 328 329 sc->sc_gc.gc_bitblt = mgx_bitblt; 330 sc->sc_gc.gc_rectfill = mgx_rectfill; 331 sc->sc_gc.gc_blitcookie = sc; 332 sc->sc_gc.gc_rop = ROP_SRC; 333 334 glyphcache_init(&sc->sc_gc, 335 sc->sc_height + 5, 336 (0x400000 / sc->sc_stride) - sc->sc_height - 5, 337 sc->sc_width, 338 ri->ri_font->fontwidth, 339 ri->ri_font->fontheight, 340 defattr); 341 342 mgx_init_palette(sc); 343 344 if(isconsole) { 345 wsdisplay_cnattach(&sc->sc_defaultscreen_descr, ri, 0, 0, 346 defattr); 347 vcons_replay_msgbuf(&sc->sc_console_screen); 348 } 349 350 aa.console = isconsole; 351 aa.scrdata = &sc->sc_screenlist; 352 aa.accessops = &mgx_accessops; 353 aa.accesscookie = &sc->vd; 354 355 config_found(self, &aa, wsemuldisplaydevprint); 356 357 /* now the Sun fb goop */ 358 fb->fb_driver = &mgx_fbdriver; 359 fb->fb_device = sc->sc_dev; 360 fb->fb_flags = device_cfdata(sc->sc_dev)->cf_flags & FB_USERMASK; 361 fb->fb_type.fb_type = FBTYPE_MGX; 362 fb->fb_pixels = NULL; 363 364 fb->fb_type.fb_depth = 32; 365 fb->fb_type.fb_width = sc->sc_width; 366 fb->fb_type.fb_height = sc->sc_height; 367 fb->fb_linebytes = sc->sc_stride * 4; 368 369 fb->fb_type.fb_cmsize = 256; 370 fb->fb_type.fb_size = sc->sc_fbsize; 371 fb_attach(&sc->sc_fb, isconsole); 372 373 #if 0 374 { 375 uint32_t ap; 376 /* reads 0xfd210000 */ 377 mgx_write_4(sc, ATR_APERTURE, 0x00000000); 378 ap = mgx_read_4(sc, ATR_APERTURE); 379 printf("aperture: %08x\n", ap); 380 } 381 #endif 382 } 383 384 static void 385 mgx_write_dac(struct mgx_softc *sc, int idx, int r, int g, int b) 386 { 387 mgx_write_vga(sc, VGA_BASE + VGA_DAC_ADDRW, idx); 388 mgx_write_vga(sc, VGA_BASE + VGA_DAC_PALETTE, r); 389 mgx_write_vga(sc, VGA_BASE + VGA_DAC_PALETTE, g); 390 mgx_write_vga(sc, VGA_BASE + VGA_DAC_PALETTE, b); 391 } 392 393 static void 394 mgx_init_palette(struct mgx_softc *sc) 395 { 396 struct rasops_info *ri = &sc->sc_console_screen.scr_ri; 397 int i, j = 0; 398 uint8_t cmap[768]; 399 400 if (sc->sc_depth == 8) { 401 rasops_get_cmap(ri, cmap, sizeof(cmap)); 402 for (i = 0; i < 256; i++) { 403 sc->sc_cmap_red[i] = cmap[j]; 404 sc->sc_cmap_green[i] = cmap[j + 1]; 405 sc->sc_cmap_blue[i] = cmap[j + 2]; 406 mgx_write_dac(sc, i, cmap[j], cmap[j + 1], cmap[j + 2]); 407 j += 3; 408 } 409 } else { 410 /* linear ramp for true colour modes */ 411 for (i = 0; i < 256; i++) { 412 mgx_write_dac(sc, i, i, i, i); 413 } 414 } 415 } 416 417 static int 418 mgx_putcmap(struct mgx_softc *sc, struct wsdisplay_cmap *cm) 419 { 420 u_char *r, *g, *b; 421 u_int index = cm->index; 422 u_int count = cm->count; 423 int i, error; 424 u_char rbuf[256], gbuf[256], bbuf[256]; 425 426 if (cm->index >= 256 || cm->count > 256 || 427 (cm->index + cm->count) > 256) 428 return EINVAL; 429 error = copyin(cm->red, &rbuf[index], count); 430 if (error) 431 return error; 432 error = copyin(cm->green, &gbuf[index], count); 433 if (error) 434 return error; 435 error = copyin(cm->blue, &bbuf[index], count); 436 if (error) 437 return error; 438 439 memcpy(&sc->sc_cmap_red[index], &rbuf[index], count); 440 memcpy(&sc->sc_cmap_green[index], &gbuf[index], count); 441 memcpy(&sc->sc_cmap_blue[index], &bbuf[index], count); 442 443 r = &sc->sc_cmap_red[index]; 444 g = &sc->sc_cmap_green[index]; 445 b = &sc->sc_cmap_blue[index]; 446 447 for (i = 0; i < count; i++) { 448 mgx_write_dac(sc, index, *r, *g, *b); 449 index++; 450 r++, g++, b++; 451 } 452 return 0; 453 } 454 455 static int 456 mgx_getcmap(struct mgx_softc *sc, struct wsdisplay_cmap *cm) 457 { 458 u_int index = cm->index; 459 u_int count = cm->count; 460 int error; 461 462 if (index >= 255 || count > 256 || index + count > 256) 463 return EINVAL; 464 465 error = copyout(&sc->sc_cmap_red[index], cm->red, count); 466 if (error) 467 return error; 468 error = copyout(&sc->sc_cmap_green[index], cm->green, count); 469 if (error) 470 return error; 471 error = copyout(&sc->sc_cmap_blue[index], cm->blue, count); 472 if (error) 473 return error; 474 475 return 0; 476 } 477 478 static int 479 mgx_wait_engine(struct mgx_softc *sc) 480 { 481 unsigned int i; 482 uint8_t stat; 483 484 for (i = 100000; i != 0; i--) { 485 stat = mgx_read_1(sc, ATR_BLT_STATUS); 486 if ((stat & (BLT_HOST_BUSY | BLT_ENGINE_BUSY)) == 0) 487 break; 488 } 489 490 return i; 491 } 492 493 static inline int 494 mgx_wait_host(struct mgx_softc *sc) 495 { 496 unsigned int i; 497 uint8_t stat; 498 499 for (i = 10000; i != 0; i--) { 500 stat = mgx_read_1(sc, ATR_BLT_STATUS); 501 if ((stat & BLT_HOST_BUSY) == 0) 502 break; 503 } 504 505 return i; 506 } 507 508 static int 509 mgx_wait_fifo(struct mgx_softc *sc, unsigned int nfifo) 510 { 511 unsigned int i; 512 uint8_t stat; 513 514 for (i = 100000; i != 0; i--) { 515 stat = mgx_read_1(sc, ATR_FIFO_STATUS); 516 stat = (stat & FIFO_MASK) >> FIFO_SHIFT; 517 if (stat >= nfifo) 518 break; 519 mgx_write_1(sc, ATR_FIFO_STATUS, 0); 520 } 521 522 return i; 523 } 524 525 static void 526 mgx_setup(struct mgx_softc *sc, int depth) 527 { 528 uint32_t stride; 529 int i; 530 uint8_t reg; 531 532 /* wait for everything to go idle */ 533 if (mgx_wait_engine(sc) == 0) 534 return; 535 if (mgx_wait_fifo(sc, FIFO_AT24) == 0) 536 return; 537 538 /* read name from sequencer */ 539 for (i = 0; i < 8; i++) { 540 mgx_write_vga(sc, SEQ_INDEX, i + 0x11); 541 sc->sc_name[i] = mgx_read_vga(sc, SEQ_DATA); 542 } 543 sc->sc_name[7] = 0; 544 545 reg = mgx_read_1(sc, ATR_PIXEL); 546 reg &= ~PIXEL_DEPTH_MASK; 547 548 switch (depth) { 549 case 8: 550 sc->sc_dec = DEC_DEPTH_8 << DEC_DEPTH_SHIFT; 551 reg |= PIXEL_8; 552 break; 553 case 15: 554 sc->sc_dec = DEC_DEPTH_16 << DEC_DEPTH_SHIFT; 555 reg |= PIXEL_15; 556 break; 557 case 16: 558 sc->sc_dec = DEC_DEPTH_16 << DEC_DEPTH_SHIFT; 559 reg |= PIXEL_16; 560 break; 561 case 32: 562 sc->sc_dec = DEC_DEPTH_32 << DEC_DEPTH_SHIFT; 563 reg |= PIXEL_32; 564 break; 565 default: 566 return; /* not supported */ 567 } 568 569 /* the chip wants stride in units of 8 bytes */ 570 sc->sc_stride = sc->sc_width * (depth >> 3); 571 stride = sc->sc_stride >> 3; 572 #ifdef MGX_DEBUG 573 sc->sc_height = 600; 574 #endif 575 576 sc->sc_depth = depth; 577 578 switch (sc->sc_width) { 579 case 640: 580 sc->sc_dec |= DEC_WIDTH_640 << DEC_WIDTH_SHIFT; 581 break; 582 case 800: 583 sc->sc_dec |= DEC_WIDTH_800 << DEC_WIDTH_SHIFT; 584 break; 585 case 1024: 586 sc->sc_dec |= DEC_WIDTH_1024 << DEC_WIDTH_SHIFT; 587 break; 588 case 1152: 589 sc->sc_dec |= DEC_WIDTH_1152 << DEC_WIDTH_SHIFT; 590 break; 591 case 1280: 592 sc->sc_dec |= DEC_WIDTH_1280 << DEC_WIDTH_SHIFT; 593 break; 594 case 1600: 595 sc->sc_dec |= DEC_WIDTH_1600 << DEC_WIDTH_SHIFT; 596 break; 597 default: 598 return; /* not supported */ 599 } 600 mgx_write_1(sc, ATR_CLIP_CONTROL, 0); 601 mgx_write_1(sc, ATR_BYTEMASK, 0xff); 602 mgx_write_1(sc, ATR_PIXEL, reg); 603 mgx_write_vga(sc, CRTC_INDEX, 0x13); 604 mgx_write_vga(sc, CRTC_DATA, stride & 0xff); 605 mgx_write_vga(sc, CRTC_INDEX, 0x1c); 606 mgx_write_vga(sc, CRTC_DATA, (stride & 0xf00) >> 4); 607 608 /* clean up the screen if we're switching to != 8bit */ 609 if (depth != MGX_DEPTH) 610 mgx_rectfill(sc, 0, 0, sc->sc_width, sc->sc_height, 0); 611 612 /* initialize hardware cursor stuff */ 613 mgx_write_2(sc, ATR_CURSOR_ADDRESS, (sc->sc_fbsize - 1024) >> 10); 614 mgx_write_1(sc, ATR_CURSOR_ENABLE, 0); 615 sc->sc_cursor = (uint8_t *)sc->sc_fbaddr + sc->sc_fbsize - 1024; 616 memset(sc->sc_cursor, 0xf0, 1024); 617 618 #ifdef MGX_DEBUG 619 int j; 620 mgx_write_vga(sc, SEQ_INDEX, 0x10); 621 mgx_write_vga(sc, SEQ_DATA, 0x12); 622 for (i = 0x10; i < 0x30; i += 16) { 623 printf("%02x:", i); 624 for (j = 0; j < 16; j++) { 625 mgx_write_vga(sc, SEQ_INDEX, i + j); 626 printf(" %02x", mgx_read_vga(sc, SEQ_DATA)); 627 } 628 printf("\n"); 629 } 630 #if 0 631 mgx_write_vga(sc, SEQ_INDEX, 0x1a); 632 mgx_write_vga(sc, SEQ_DATA, 0x0f); 633 #endif 634 #endif 635 } 636 637 static void 638 mgx_bitblt(void *cookie, int xs, int ys, int xd, int yd, int wi, int he, 639 int rop) 640 { 641 struct mgx_softc *sc = cookie; 642 uint32_t dec = sc->sc_dec; 643 644 dec |= (DEC_COMMAND_BLT << DEC_COMMAND_SHIFT) | 645 (DEC_START_DIMX << DEC_START_SHIFT); 646 if (xs < xd) { 647 xs += wi - 1; 648 xd += wi - 1; 649 dec |= DEC_DIR_X_REVERSE; 650 } 651 if (ys < yd) { 652 ys += he - 1; 653 yd += he - 1; 654 dec |= DEC_DIR_Y_REVERSE; 655 } 656 mgx_wait_fifo(sc, 5); 657 mgx_write_1(sc, ATR_ROP, rop); 658 mgx_write_4(sc, ATR_DEC, dec); 659 mgx_write_4(sc, ATR_SRC_XY, (ys << 16) | xs); 660 mgx_write_4(sc, ATR_DST_XY, (yd << 16) | xd); 661 mgx_write_4(sc, ATR_WH, (he << 16) | wi); 662 } 663 664 static void 665 mgx_rectfill(void *cookie, int x, int y, int wi, int he, long fg) 666 { 667 struct mgx_softc *sc = cookie; 668 struct vcons_screen *scr = sc->vd.active; 669 uint32_t dec = sc->sc_dec; 670 uint32_t col; 671 672 if (scr == NULL) 673 return; 674 col = scr->scr_ri.ri_devcmap[fg]; 675 676 dec = sc->sc_dec; 677 dec |= (DEC_COMMAND_RECT << DEC_COMMAND_SHIFT) | 678 (DEC_START_DIMX << DEC_START_SHIFT); 679 mgx_wait_fifo(sc, 5); 680 mgx_write_1(sc, ATR_ROP, ROP_SRC); 681 mgx_write_4(sc, ATR_FG, col); 682 mgx_write_4(sc, ATR_DEC, dec); 683 mgx_write_4(sc, ATR_DST_XY, (y << 16) | x); 684 mgx_write_4(sc, ATR_WH, (he << 16) | wi); 685 } 686 687 static void 688 mgx_putchar(void *cookie, int row, int col, u_int c, long attr) 689 { 690 struct rasops_info *ri = cookie; 691 struct wsdisplay_font *font = PICK_FONT(ri, c); 692 struct vcons_screen *scr = ri->ri_hw; 693 struct mgx_softc *sc = scr->scr_cookie; 694 uint32_t fg, bg; 695 int x, y, wi, he, rv; 696 697 wi = font->fontwidth; 698 he = font->fontheight; 699 700 bg = (attr >> 16) & 0xf; 701 fg = (attr >> 24) & 0xf; 702 703 x = ri->ri_xorigin + col * wi; 704 y = ri->ri_yorigin + row * he; 705 706 if (c == 0x20) { 707 mgx_rectfill(sc, x, y, wi, he, bg); 708 if (attr & 1) 709 mgx_rectfill(sc, x, y + he - 2, wi, 1, fg); 710 return; 711 } 712 rv = glyphcache_try(&sc->sc_gc, c, x, y, attr); 713 if (rv != GC_OK) { 714 volatile uint32_t junk; 715 716 mgx_wait_engine(sc); 717 sc->sc_putchar(cookie, row, col, c, attr & ~1); 718 if (rv == GC_ADD) { 719 /* 720 * try to make sure the glyph made it all the way to 721 * video memory before trying to blit it into the cache 722 */ 723 junk = *(uint32_t *)sc->sc_fbaddr; 724 __USE(junk); 725 glyphcache_add(&sc->sc_gc, c, x, y); 726 } 727 } 728 if (attr & 1) 729 mgx_rectfill(sc, x, y + he - 2, wi, 1, fg); 730 } 731 732 static void 733 mgx_cursor(void *cookie, int on, int row, int col) 734 { 735 struct rasops_info *ri = cookie; 736 struct vcons_screen *scr = ri->ri_hw; 737 struct mgx_softc *sc = scr->scr_cookie; 738 int x, y, wi,he; 739 740 wi = ri->ri_font->fontwidth; 741 he = ri->ri_font->fontheight; 742 743 if (ri->ri_flg & RI_CURSOR) { 744 x = ri->ri_ccol * wi + ri->ri_xorigin; 745 y = ri->ri_crow * he + ri->ri_yorigin; 746 mgx_bitblt(sc, x, y, x, y, wi, he, ROP_INV); 747 ri->ri_flg &= ~RI_CURSOR; 748 } 749 750 ri->ri_crow = row; 751 ri->ri_ccol = col; 752 753 if (on) 754 { 755 x = ri->ri_ccol * wi + ri->ri_xorigin; 756 y = ri->ri_crow * he + ri->ri_yorigin; 757 mgx_bitblt(sc, x, y, x, y, wi, he, ROP_INV); 758 ri->ri_flg |= RI_CURSOR; 759 } 760 } 761 762 static void 763 mgx_copycols(void *cookie, int row, int srccol, int dstcol, int ncols) 764 { 765 struct rasops_info *ri = cookie; 766 struct vcons_screen *scr = ri->ri_hw; 767 struct mgx_softc *sc = scr->scr_cookie; 768 int32_t xs, xd, y, width, height; 769 770 xs = ri->ri_xorigin + ri->ri_font->fontwidth * srccol; 771 xd = ri->ri_xorigin + ri->ri_font->fontwidth * dstcol; 772 y = ri->ri_yorigin + ri->ri_font->fontheight * row; 773 width = ri->ri_font->fontwidth * ncols; 774 height = ri->ri_font->fontheight; 775 mgx_bitblt(sc, xs, y, xd, y, width, height, ROP_SRC); 776 } 777 778 static void 779 mgx_erasecols(void *cookie, int row, int startcol, int ncols, long fillattr) 780 { 781 struct rasops_info *ri = cookie; 782 struct vcons_screen *scr = ri->ri_hw; 783 struct mgx_softc *sc = scr->scr_cookie; 784 int32_t x, y, width, height, bg; 785 786 x = ri->ri_xorigin + ri->ri_font->fontwidth * startcol; 787 y = ri->ri_yorigin + ri->ri_font->fontheight * row; 788 width = ri->ri_font->fontwidth * ncols; 789 height = ri->ri_font->fontheight; 790 bg = (fillattr >> 16) & 0xff; 791 mgx_rectfill(sc, x, y, width, height, bg); 792 } 793 794 static void 795 mgx_copyrows(void *cookie, int srcrow, int dstrow, int nrows) 796 { 797 struct rasops_info *ri = cookie; 798 struct vcons_screen *scr = ri->ri_hw; 799 struct mgx_softc *sc = scr->scr_cookie; 800 int32_t x, ys, yd, width, height; 801 802 x = ri->ri_xorigin; 803 ys = ri->ri_yorigin + ri->ri_font->fontheight * srcrow; 804 yd = ri->ri_yorigin + ri->ri_font->fontheight * dstrow; 805 width = ri->ri_emuwidth; 806 height = ri->ri_font->fontheight * nrows; 807 mgx_bitblt(sc, x, ys, x, yd, width, height, ROP_SRC); 808 } 809 810 static void 811 mgx_eraserows(void *cookie, int row, int nrows, long fillattr) 812 { 813 struct rasops_info *ri = cookie; 814 struct vcons_screen *scr = ri->ri_hw; 815 struct mgx_softc *sc = scr->scr_cookie; 816 int32_t x, y, width, height, bg; 817 818 if ((row == 0) && (nrows == ri->ri_rows)) { 819 x = y = 0; 820 width = ri->ri_width; 821 height = ri->ri_height; 822 } else { 823 x = ri->ri_xorigin; 824 y = ri->ri_yorigin + ri->ri_font->fontheight * row; 825 width = ri->ri_emuwidth; 826 height = ri->ri_font->fontheight * nrows; 827 } 828 bg = (fillattr >> 16) & 0xff; 829 mgx_rectfill(sc, x, y, width, height, bg); 830 } 831 832 static void 833 mgx_init_screen(void *cookie, struct vcons_screen *scr, 834 int existing, long *defattr) 835 { 836 struct mgx_softc *sc = cookie; 837 struct rasops_info *ri = &scr->scr_ri; 838 839 ri->ri_depth = sc->sc_depth; 840 ri->ri_width = sc->sc_width; 841 ri->ri_height = sc->sc_height; 842 ri->ri_stride = sc->sc_stride; 843 ri->ri_flg = RI_CENTER | RI_ENABLE_ALPHA; 844 845 if (ri->ri_depth == 8) 846 ri->ri_flg |= RI_8BIT_IS_RGB; 847 848 #ifdef MGX_NOACCEL 849 scr->scr_flags |= VCONS_DONT_READ; 850 #endif 851 852 ri->ri_rnum = 8; 853 ri->ri_rpos = 0; 854 ri->ri_gnum = 8; 855 ri->ri_gpos = 8; 856 ri->ri_bnum = 8; 857 ri->ri_bpos = 16; 858 859 ri->ri_bits = sc->sc_fbaddr; 860 861 rasops_init(ri, 0, 0); 862 sc->sc_putchar = ri->ri_ops.putchar; 863 864 ri->ri_caps = WSSCREEN_REVERSE | WSSCREEN_WSCOLORS; 865 866 rasops_reconfig(ri, ri->ri_height / ri->ri_font->fontheight, 867 ri->ri_width / ri->ri_font->fontwidth); 868 869 ri->ri_hw = scr; 870 871 #ifdef MGX_NOACCEL 872 if (0) 873 #endif 874 { 875 ri->ri_ops.putchar = mgx_putchar; 876 ri->ri_ops.cursor = mgx_cursor; 877 ri->ri_ops.copyrows = mgx_copyrows; 878 ri->ri_ops.eraserows = mgx_eraserows; 879 ri->ri_ops.copycols = mgx_copycols; 880 ri->ri_ops.erasecols = mgx_erasecols; 881 } 882 } 883 884 static int 885 mgx_ioctl(void *v, void *vs, u_long cmd, void *data, int flag, 886 struct lwp *l) 887 { 888 struct vcons_data *vd = v; 889 struct mgx_softc *sc = vd->cookie; 890 struct wsdisplay_fbinfo *wdf; 891 struct vcons_screen *ms = vd->active; 892 893 switch (cmd) { 894 case WSDISPLAYIO_GTYPE: 895 *(u_int *)data = WSDISPLAY_TYPE_MGX; 896 return 0; 897 898 case WSDISPLAYIO_GINFO: 899 wdf = (void *)data; 900 wdf->height = sc->sc_height; 901 wdf->width = sc->sc_width; 902 wdf->depth = 8; 903 wdf->cmsize = 256; 904 return 0; 905 906 case FBIOGTYPE: 907 *(struct fbtype *)data = sc->sc_fb.fb_type; 908 break; 909 910 case FBIOGATTR: 911 #define fba ((struct fbgattr *)data) 912 fba->real_type = sc->sc_fb.fb_type.fb_type; 913 fba->owner = 0; /* XXX ??? */ 914 fba->fbtype = sc->sc_fb.fb_type; 915 fba->sattr.flags = 0; 916 fba->sattr.emu_type = sc->sc_fb.fb_type.fb_type; 917 fba->sattr.dev_specific[0] = -1; 918 fba->emu_types[0] = sc->sc_fb.fb_type.fb_type; 919 fba->emu_types[1] = -1; 920 #undef fba 921 break; 922 case FBIOGVIDEO: 923 case WSDISPLAYIO_GVIDEO: 924 *(int *)data = sc->sc_video; 925 return 0; 926 927 case WSDISPLAYIO_SVIDEO: 928 case FBIOSVIDEO: 929 mgx_set_video(sc, *(int *)data); 930 return 0; 931 932 case WSDISPLAYIO_LINEBYTES: 933 { 934 int *ret = (int *)data; 935 *ret = sc->sc_stride; 936 } 937 return 0; 938 939 case WSDISPLAYIO_SMODE: 940 { 941 int new_mode = *(int*)data; 942 if (new_mode != sc->sc_mode) 943 { 944 sc->sc_mode = new_mode; 945 if (new_mode == WSDISPLAYIO_MODE_EMUL) 946 { 947 mgx_setup(sc, MGX_DEPTH); 948 glyphcache_wipe(&sc->sc_gc); 949 mgx_init_palette(sc); 950 vcons_redraw_screen(ms); 951 } else { 952 mgx_setup(sc, 32); 953 mgx_init_palette(sc); 954 } 955 } 956 } 957 return 0; 958 959 case WSDISPLAYIO_GETCMAP: 960 return mgx_getcmap(sc, (struct wsdisplay_cmap *)data); 961 962 case WSDISPLAYIO_PUTCMAP: 963 return mgx_putcmap(sc, (struct wsdisplay_cmap *)data); 964 965 case WSDISPLAYIO_GCURPOS: 966 { 967 struct wsdisplay_curpos *cp = (void *)data; 968 969 cp->x = sc->sc_cursor_x; 970 cp->y = sc->sc_cursor_y; 971 } 972 return 0; 973 974 case WSDISPLAYIO_SCURPOS: 975 { 976 struct wsdisplay_curpos *cp = (void *)data; 977 978 sc->sc_cursor_x = cp->x; 979 sc->sc_cursor_y = cp->y; 980 mgx_set_cursor(sc); 981 } 982 return 0; 983 984 case WSDISPLAYIO_GCURMAX: 985 { 986 struct wsdisplay_curpos *cp = (void *)data; 987 988 cp->x = 64; 989 cp->y = 64; 990 } 991 return 0; 992 993 case WSDISPLAYIO_SCURSOR: 994 { 995 struct wsdisplay_cursor *cursor = (void *)data; 996 997 return mgx_do_cursor(sc, cursor); 998 } 999 case WSDISPLAYIO_GET_FBINFO: 1000 { 1001 struct wsdisplayio_fbinfo *fbi = data; 1002 1003 fbi->fbi_fbsize = sc->sc_fbsize - 1024; 1004 fbi->fbi_width = sc->sc_width; 1005 fbi->fbi_height = sc->sc_height; 1006 fbi->fbi_bitsperpixel = sc->sc_depth; 1007 fbi->fbi_stride = sc->sc_stride; 1008 fbi->fbi_pixeltype = WSFB_RGB; 1009 fbi->fbi_subtype.fbi_rgbmasks.red_offset = 8; 1010 fbi->fbi_subtype.fbi_rgbmasks.red_size = 8; 1011 fbi->fbi_subtype.fbi_rgbmasks.green_offset = 16; 1012 fbi->fbi_subtype.fbi_rgbmasks.green_size = 8; 1013 fbi->fbi_subtype.fbi_rgbmasks.blue_offset = 24; 1014 fbi->fbi_subtype.fbi_rgbmasks.blue_size = 8; 1015 fbi->fbi_subtype.fbi_rgbmasks.alpha_offset = 0; 1016 fbi->fbi_subtype.fbi_rgbmasks.alpha_size = 8; 1017 return 0; 1018 } 1019 } 1020 return EPASSTHROUGH; 1021 } 1022 1023 static paddr_t 1024 mgx_mmap(void *v, void *vs, off_t offset, int prot) 1025 { 1026 struct vcons_data *vd = v; 1027 struct mgx_softc *sc = vd->cookie; 1028 1029 /* regular fb mapping at 0 */ 1030 if ((offset >= 0) && (offset < sc->sc_fbsize)) { 1031 return bus_space_mmap(sc->sc_tag, sc->sc_paddr, 1032 offset, prot, BUS_SPACE_MAP_LINEAR); 1033 } 1034 1035 /* 1036 * Blitter registers at 0x80000000, only in mapped mode. 1037 * Restrict to root, even though I'm fairly sure the DMA engine lives 1038 * elsewhere ( and isn't documented anyway ) 1039 */ 1040 if (kauth_authorize_machdep(kauth_cred_get(), 1041 KAUTH_MACHDEP_UNMANAGEDMEM, 1042 NULL, NULL, NULL, NULL) != 0) { 1043 aprint_normal("%s: mmap() rejected.\n", 1044 device_xname(sc->sc_dev)); 1045 return -1; 1046 } 1047 if ((sc->sc_mode == WSDISPLAYIO_MODE_MAPPED) && 1048 (offset >= 0x80000000) && (offset < 0x80001000)) { 1049 return bus_space_mmap(sc->sc_tag, sc->sc_rpaddr, 1050 offset, prot, BUS_SPACE_MAP_LINEAR); 1051 } 1052 return -1; 1053 } 1054 1055 static int 1056 mgx_do_cursor(struct mgx_softc *sc, struct wsdisplay_cursor *cur) 1057 { 1058 int i; 1059 if (cur->which & WSDISPLAY_CURSOR_DOCUR) { 1060 1061 if (cur->enable) { 1062 mgx_set_cursor(sc); 1063 mgx_write_1(sc, ATR_CURSOR_ENABLE, 1); 1064 } else { 1065 mgx_write_1(sc, ATR_CURSOR_ENABLE, 0); 1066 } 1067 } 1068 if (cur->which & WSDISPLAY_CURSOR_DOHOT) { 1069 1070 sc->sc_hotspot_x = cur->hot.x; 1071 sc->sc_hotspot_y = cur->hot.y; 1072 mgx_set_cursor(sc); 1073 } 1074 if (cur->which & WSDISPLAY_CURSOR_DOPOS) { 1075 1076 sc->sc_cursor_x = cur->pos.x; 1077 sc->sc_cursor_y = cur->pos.y; 1078 mgx_set_cursor(sc); 1079 } 1080 if (cur->which & WSDISPLAY_CURSOR_DOCMAP) { 1081 int cnt = min(2, cur->cmap.count); 1082 uint8_t c; 1083 uint8_t r[2], g[2], b[2]; 1084 1085 copyin(cur->cmap.red, r, cnt); 1086 copyin(cur->cmap.green, g, cnt); 1087 copyin(cur->cmap.blue, b, cnt); 1088 1089 for (i = 0; i < cnt; i++) { 1090 c = r[i] & 0xe0; 1091 c |= (g[i] & 0xe0) >> 3; 1092 c |= (b[i] & 0xc0) >> 6; 1093 mgx_write_1(sc, ATR_CURSOR_FG + i, c); 1094 } 1095 } 1096 if (cur->which & WSDISPLAY_CURSOR_DOSHAPE) { 1097 int j; 1098 uint8_t *fb = sc->sc_cursor; 1099 uint8_t temp; 1100 uint8_t im, ma, px; 1101 1102 for (i = 0; i < 512; i++) { 1103 temp = 0; 1104 copyin(&cur->image[i], &im, 1); 1105 copyin(&cur->mask[i], &ma, 1); 1106 for (j = 0; j < 4; j++) { 1107 temp >>= 2; 1108 px = (ma & 1) ? 0 : 0x80; 1109 if (px == 0) 1110 px |= (im & 1) ? 0 : 0x40; 1111 temp |= px; 1112 im >>= 1; 1113 ma >>= 1; 1114 } 1115 *fb = temp; 1116 fb++; 1117 temp = 0; 1118 for (j = 0; j < 4; j++) { 1119 temp >>= 2; 1120 px = (ma & 1) ? 0 : 0x80; 1121 if (px == 0) 1122 px |= (im & 1) ? 0 : 0x40; 1123 temp |= px; 1124 im >>= 1; 1125 ma >>= 1; 1126 } 1127 *fb = temp; 1128 fb++; 1129 } 1130 } 1131 return 0; 1132 } 1133 1134 static void 1135 mgx_set_cursor(struct mgx_softc *sc) 1136 { 1137 uint32_t reg; 1138 uint16_t hot; 1139 1140 reg = (sc->sc_cursor_y << 16) | (sc->sc_cursor_x & 0xffff); 1141 mgx_write_4(sc, ATR_CURSOR_POSITION, reg); 1142 hot = (sc->sc_hotspot_y << 8) | (sc->sc_hotspot_x & 0xff); 1143 mgx_write_2(sc, ATR_CURSOR_HOTSPOT, hot); 1144 } 1145 1146 static void 1147 mgx_set_video(struct mgx_softc *sc, int v) 1148 { 1149 uint8_t reg; 1150 1151 if (sc->sc_video == v) 1152 return; 1153 1154 sc->sc_video = v; 1155 reg = mgx_read_1(sc, ATR_DPMS); 1156 1157 if (v == WSDISPLAYIO_VIDEO_ON) { 1158 reg &= ~DPMS_SYNC_DISABLE_ALL; 1159 } else { 1160 reg |= DPMS_SYNC_DISABLE_ALL; 1161 } 1162 mgx_write_1(sc, ATR_DPMS, reg); 1163 } 1164 1165 /* Sun fb dev goop */ 1166 static void 1167 mgx_unblank(device_t dev) 1168 { 1169 struct mgx_softc *sc = device_private(dev); 1170 1171 mgx_set_video(sc, WSDISPLAYIO_VIDEO_ON); 1172 } 1173 1174 paddr_t 1175 mgxmmap(dev_t dev, off_t offset, int prot) 1176 { 1177 struct mgx_softc *sc = device_lookup_private(&mgx_cd, minor(dev)); 1178 1179 /* regular fb mapping at 0 */ 1180 if ((offset >= 0) && (offset < sc->sc_fbsize)) { 1181 return bus_space_mmap(sc->sc_tag, sc->sc_paddr, 1182 offset, prot, BUS_SPACE_MAP_LINEAR); 1183 } 1184 1185 /* 1186 * Blitter registers at 0x80000000, only in mapped mode. 1187 * Restrict to root, even though I'm fairly sure the DMA engine lives 1188 * elsewhere ( and isn't documented anyway ) 1189 */ 1190 if (kauth_authorize_machdep(kauth_cred_get(), 1191 KAUTH_MACHDEP_UNMANAGEDMEM, 1192 NULL, NULL, NULL, NULL) != 0) { 1193 aprint_normal("%s: mmap() rejected.\n", 1194 device_xname(sc->sc_dev)); 1195 return -1; 1196 } 1197 if ((sc->sc_mode == WSDISPLAYIO_MODE_MAPPED) && 1198 (offset >= 0x80000000) && (offset < 0x80001000)) { 1199 return bus_space_mmap(sc->sc_tag, sc->sc_rpaddr, 1200 offset, prot, BUS_SPACE_MAP_LINEAR); 1201 } 1202 return -1; 1203 } 1204 1205 int 1206 mgxopen(dev_t dev, int flags, int mode, struct lwp *l) 1207 { 1208 device_t dv = device_lookup(&mgx_cd, minor(dev)); 1209 struct mgx_softc *sc = device_private(dv); 1210 1211 if (dv == NULL) 1212 return ENXIO; 1213 if (sc->sc_mode == WSDISPLAYIO_MODE_MAPPED) 1214 return 0; 1215 sc->sc_mode = WSDISPLAYIO_MODE_MAPPED; 1216 mgx_setup(sc, 32); 1217 mgx_init_palette(sc); 1218 return 0; 1219 } 1220 1221 int 1222 mgxclose(dev_t dev, int flags, int mode, struct lwp *l) 1223 { 1224 device_t dv = device_lookup(&mgx_cd, minor(dev)); 1225 struct mgx_softc *sc = device_private(dv); 1226 struct vcons_screen *ms = sc->vd.active; 1227 1228 if (sc->sc_mode == WSDISPLAYIO_MODE_EMUL) 1229 return 0; 1230 1231 sc->sc_mode = WSDISPLAYIO_MODE_EMUL; 1232 1233 mgx_setup(sc, MGX_DEPTH); 1234 glyphcache_wipe(&sc->sc_gc); 1235 mgx_init_palette(sc); 1236 if (ms != NULL) { 1237 vcons_redraw_screen(ms); 1238 } 1239 return 0; 1240 } 1241 1242 int 1243 mgxioctl(dev_t dev, u_long cmd, void *data, int flags, struct lwp *l) 1244 { 1245 struct mgx_softc *sc = device_lookup_private(&mgx_cd, minor(dev)); 1246 1247 return mgx_ioctl(&sc->vd, NULL, cmd, data, flags, l); 1248 } 1249