xcfb.c revision 1.1 1 /* $NetBSD: xcfb.c,v 1.1 1998/10/29 12:24:25 nisimura Exp $ */
2
3 #include <sys/cdefs.h> /* RCS ID & Copyright macro defns */
4
5 __KERNEL_RCSID(0, "$NetBSD: xcfb.c,v 1.1 1998/10/29 12:24:25 nisimura Exp $");
6
7 #include <sys/param.h>
8 #include <sys/systm.h>
9 #include <sys/kernel.h>
10 #include <sys/device.h>
11 #include <sys/malloc.h>
12 #include <sys/buf.h>
13 #include <sys/ioctl.h>
14 #include <vm/vm.h>
15
16 #include <machine/bus.h>
17 #include <machine/intr.h>
18
19 #include <dev/rcons/raster.h>
20 #include <dev/wscons/wsconsio.h>
21 #include <dev/wscons/wscons_raster.h>
22 #include <dev/wscons/wsdisplayvar.h>
23 #include <machine/autoconf.h>
24
25 #include <dev/tc/tcvar.h>
26 #include <dev/ic/ims332reg.h>
27
28 #include "opt_uvm.h"
29 #if defined(UVM)
30 #include <uvm/uvm_extern.h>
31 #define useracc uvm_useracc
32 #endif
33
34 struct fb_devconfig {
35 vaddr_t dc_vaddr; /* memory space virtual base address */
36 paddr_t dc_paddr; /* memory space physical base address */
37 vsize_t dc_size; /* size of slot memory */
38 int dc_wid; /* width of frame buffer */
39 int dc_ht; /* height of frame buffer */
40 int dc_depth; /* depth, bits per pixel */
41 int dc_rowbytes; /* bytes in a FB scan line */
42 vaddr_t dc_videobase; /* base of flat frame buffer */
43 struct raster dc_raster; /* raster description */
44 struct rcons dc_rcons; /* raster blitter control info */
45 int dc_blanked; /* currently has video disabled */
46 };
47
48 struct hwcmap {
49 #define CMAP_SIZE 256 /* 256 R/G/B entries */
50 u_int8_t r[CMAP_SIZE];
51 u_int8_t g[CMAP_SIZE];
52 u_int8_t b[CMAP_SIZE];
53 };
54
55 struct hwcursor {
56 struct wsdisplay_curpos cc_pos;
57 struct wsdisplay_curpos cc_hot;
58 struct wsdisplay_curpos cc_size;
59 #define CURSOR_MAX_SIZE 64
60 u_int8_t cc_color[6];
61 u_int64_t cc_image[64 + 64];
62 };
63
64 struct xcfb_softc {
65 struct device sc_dev;
66 struct fb_devconfig *sc_dc; /* device configuration */
67 struct hwcmap sc_cmap; /* software copy of colormap */
68 struct hwcursor sc_cursor; /* software copy of cursor */
69 /* no sc_change field because PMAG-DV does not emit interrupt */
70 /* XXX XXX real-ly !? See MACH code XXX XXX */
71 int nscreens;
72 };
73
74 int xcfbmatch __P((struct device *, struct cfdata *, void *));
75 void xcfbattach __P((struct device *, struct device *, void *));
76
77 struct cfattach xcfb_ca = {
78 sizeof(struct xcfb_softc), xcfbmatch, xcfbattach,
79 };
80
81 void xcfb_getdevconfig __P((tc_addr_t, struct fb_devconfig *));
82 struct fb_devconfig xcfb_console_dc;
83 tc_addr_t xcfb_consaddr;
84
85 struct wsdisplay_emulops xcfb_emulops = {
86 rcons_cursor,
87 rcons_mapchar,
88 rcons_putchar,
89 rcons_copycols,
90 rcons_erasecols,
91 rcons_copyrows,
92 rcons_eraserows,
93 rcons_alloc_attr
94 };
95
96 struct wsscreen_descr xcfb_stdscreen = {
97 "std",
98 0, 0, /* will be filled in -- XXX shouldn't, it's global */
99 &xcfb_emulops,
100 0, 0,
101 0
102 };
103
104 const struct wsscreen_descr *_xcfb_scrlist[] = {
105 &xcfb_stdscreen,
106 };
107
108 struct wsscreen_list xcfb_screenlist = {
109 sizeof(_xcfb_scrlist) / sizeof(struct wsscreen_descr *), _xcfb_scrlist
110 };
111
112 int xcfbioctl __P((void *, u_long, caddr_t, int, struct proc *));
113 int xcfbmmap __P((void *, off_t, int));
114
115 int xcfb_alloc_screen __P((void *, const struct wsscreen_descr *,
116 void **, int *, int *, long *));
117 void xcfb_free_screen __P((void *, void *));
118 void xcfb_show_screen __P((void *, void *));
119 int xcfb_load_font __P((void *, void *, int, int, int, void *));
120
121 struct wsdisplay_accessops xcfb_accessops = {
122 xcfbioctl,
123 xcfbmmap,
124 xcfb_alloc_screen,
125 xcfb_free_screen,
126 xcfb_show_screen,
127 xcfb_load_font
128 };
129
130 int xcfb_cnattach __P((tc_addr_t));
131 void xcfbinit __P((struct fb_devconfig *));
132 void xcfb_blank __P((struct xcfb_softc *));
133 void xcfb_unblank __P((struct xcfb_softc *));
134
135 static int set_cmap __P((struct xcfb_softc *, struct wsdisplay_cmap *));
136 static int get_cmap __P((struct xcfb_softc *, struct wsdisplay_cmap *));
137 static int set_cursor __P((struct xcfb_softc *, struct wsdisplay_cursor *));
138 static int get_cursor __P((struct xcfb_softc *, struct wsdisplay_cursor *));
139 static void set_curpos __P((struct xcfb_softc *, struct wsdisplay_curpos *));
140 void ims332_loadcmap __P((struct hwcmap *));
141 void ims332_set_cursor __P((struct xcfb_softc *));
142 void ims332_set_curpos __P((struct xcfb_softc *));
143 void ims332_load_curcmap __P((struct xcfb_softc *));
144 void ims332_load_curshape __P((struct xcfb_softc *));
145 u_int32_t ims332_read_reg __P((int));
146 void ims332_write_reg __P((int, u_int32_t));
147
148 #define XCFB_FB_OFFSET 0x2000000 /* from module's base */
149 #define XCFB_FB_SIZE 0x100000 /* frame buffer size */
150
151 #define IMS332_ADDRESS 0xbc140000
152 #define IMS332_RPTR 0xbc1c0000
153 #define IMS332_WPTR 0xbc1e0000
154
155 int
156 xcfbmatch(parent, match, aux)
157 struct device *parent;
158 struct cfdata *match;
159 void *aux;
160 {
161 struct tc_attach_args *ta = aux;
162
163 if (strncmp("PMAG-DV ", ta->ta_modname, TC_ROM_LLEN) != 0)
164 return (0);
165
166 return (1);
167 }
168
169 void
170 xcfb_getdevconfig(dense_addr, dc)
171 tc_addr_t dense_addr;
172 struct fb_devconfig *dc;
173 {
174 struct raster *rap;
175 struct rcons *rcp;
176 int i;
177
178 dc->dc_vaddr = dense_addr;
179 dc->dc_paddr = MIPS_KSEG1_TO_PHYS(dc->dc_vaddr + XCFB_FB_OFFSET);
180
181 dc->dc_wid = 1024;
182 dc->dc_ht = 768;
183 dc->dc_depth = 8;
184 dc->dc_rowbytes = 1024;
185 dc->dc_videobase = dc->dc_vaddr + XCFB_FB_OFFSET;
186 dc->dc_blanked = 0;
187
188 /* initialize colormap and cursor resource */
189 xcfbinit(dc);
190
191 /* clear the screen */
192 for (i = 0; i < dc->dc_ht * dc->dc_rowbytes; i += sizeof(u_int32_t))
193 *(u_int32_t *)(dc->dc_videobase + i) = 0;
194
195 /* initialize the raster */
196 rap = &dc->dc_raster;
197 rap->width = dc->dc_wid;
198 rap->height = dc->dc_ht;
199 rap->depth = dc->dc_depth;
200 rap->linelongs = dc->dc_rowbytes / sizeof(u_int32_t);
201 rap->pixels = (u_int32_t *)dc->dc_videobase;
202
203 /* initialize the raster console blitter */
204 rcp = &dc->dc_rcons;
205 rcp->rc_sp = rap;
206 rcp->rc_crow = rcp->rc_ccol = -1;
207 rcp->rc_crowp = &rcp->rc_crow;
208 rcp->rc_ccolp = &rcp->rc_ccol;
209 rcons_init(rcp, 34, 80);
210
211 xcfb_stdscreen.nrows = dc->dc_rcons.rc_maxrow;
212 xcfb_stdscreen.ncols = dc->dc_rcons.rc_maxcol;
213 }
214
215 void
216 xcfbattach(parent, self, aux)
217 struct device *parent, *self;
218 void *aux;
219 {
220 struct xcfb_softc *sc = (struct xcfb_softc *)self;
221 struct tc_attach_args *ta = aux;
222 struct wsemuldisplaydev_attach_args waa;
223 struct hwcmap *cm;
224 int console, i;
225
226 console = (ta->ta_addr == xcfb_consaddr);
227 if (console) {
228 sc->sc_dc = &xcfb_console_dc;
229 sc->nscreens = 1;
230 }
231 else {
232 sc->sc_dc = (struct fb_devconfig *)
233 malloc(sizeof(struct fb_devconfig), M_DEVBUF, M_WAITOK);
234 xcfb_getdevconfig(ta->ta_addr, sc->sc_dc);
235 }
236 printf(": %d x %d, %dbpp\n", sc->sc_dc->dc_wid, sc->sc_dc->dc_ht,
237 sc->sc_dc->dc_depth);
238
239 cm = &sc->sc_cmap;
240 cm->r[0] = cm->g[0] = cm->b[0] = 0;
241 for (i = 1; i < CMAP_SIZE; i++) {
242 cm->r[i] = cm->g[i] = cm->b[i] = 0xff;
243 }
244
245 /* PMAG-DV emits no interrupt */
246
247 waa.console = console;
248 waa.scrdata = &xcfb_screenlist;
249 waa.accessops = &xcfb_accessops;
250 waa.accesscookie = sc;
251
252 config_found(self, &waa, wsemuldisplaydevprint);
253 }
254
255 int
256 xcfbioctl(v, cmd, data, flag, p)
257 void *v;
258 u_long cmd;
259 caddr_t data;
260 int flag;
261 struct proc *p;
262 {
263 struct xcfb_softc *sc = v;
264 struct fb_devconfig *dc = sc->sc_dc;
265 int error;
266
267 switch (cmd) {
268 case WSDISPLAYIO_GTYPE:
269 *(u_int *)data = WSDISPLAY_TYPE_XCFB;
270 return (0);
271
272 case WSDISPLAYIO_GINFO:
273 #define wsd_fbip ((struct wsdisplay_fbinfo *)data)
274 wsd_fbip->height = sc->sc_dc->dc_ht;
275 wsd_fbip->width = sc->sc_dc->dc_wid;
276 wsd_fbip->depth = sc->sc_dc->dc_depth;
277 wsd_fbip->cmsize = CMAP_SIZE;
278 #undef fbt
279 return (0);
280
281 case WSDISPLAYIO_GETCMAP:
282 return get_cmap(sc, (struct wsdisplay_cmap *)data);
283
284 case WSDISPLAYIO_PUTCMAP:
285 error = set_cmap(sc, (struct wsdisplay_cmap *)data);
286 if (error == 0)
287 ims332_loadcmap(&sc->sc_cmap);
288 return (error);
289
290 case WSDISPLAYIO_SVIDEO:
291 if (*(int *)data == WSDISPLAYIO_VIDEO_OFF)
292 xcfb_blank(sc);
293 else
294 xcfb_unblank(sc);
295 return (0);
296
297 case WSDISPLAYIO_GVIDEO:
298 *(u_int *)data = dc->dc_blanked ?
299 WSDISPLAYIO_VIDEO_OFF : WSDISPLAYIO_VIDEO_ON;
300 return (0);
301
302 case WSDISPLAYIO_GCURPOS:
303 *(struct wsdisplay_curpos *)data = sc->sc_cursor.cc_pos;
304 return (0);
305
306 case WSDISPLAYIO_SCURPOS:
307 set_curpos(sc, (struct wsdisplay_curpos *)data);
308 ims332_set_curpos(sc);
309 return (0);
310
311 case WSDISPLAYIO_GCURMAX:
312 ((struct wsdisplay_curpos *)data)->x =
313 ((struct wsdisplay_curpos *)data)->y = CURSOR_MAX_SIZE;
314 return (0);
315
316 case WSDISPLAYIO_GCURSOR:
317 return get_cursor(sc, (struct wsdisplay_cursor *)data);
318
319 case WSDISPLAYIO_SCURSOR:
320 return set_cursor(sc, (struct wsdisplay_cursor *)data);
321 }
322 return ENOTTY;
323 }
324
325 int
326 xcfbmmap(v, offset, prot)
327 void *v;
328 off_t offset;
329 int prot;
330 {
331 struct xcfb_softc *sc = v;
332
333 if (offset > XCFB_FB_SIZE)
334 return -1;
335 return mips_btop(sc->sc_dc->dc_paddr + offset);
336 }
337
338 int
339 xcfb_alloc_screen(v, type, cookiep, curxp, curyp, attrp)
340 void *v;
341 const struct wsscreen_descr *type;
342 void **cookiep;
343 int *curxp, *curyp;
344 long *attrp;
345 {
346 struct xcfb_softc *sc = v;
347 long defattr;
348
349 if (sc->nscreens > 0)
350 return (ENOMEM);
351
352 *cookiep = &sc->sc_dc->dc_rcons; /* one and only for now */
353 *curxp = 0;
354 *curyp = 0;
355 rcons_alloc_attr(&sc->sc_dc->dc_rcons, 0, 0, 0, &defattr);
356 *attrp = defattr;
357 sc->nscreens++;
358 return (0);
359 }
360
361 void
362 xcfb_free_screen(v, cookie)
363 void *v;
364 void *cookie;
365 {
366 struct xcfb_softc *sc = v;
367
368 if (sc->sc_dc == &xcfb_console_dc)
369 panic("xcfb_free_screen: console");
370
371 sc->nscreens--;
372 }
373
374 void
375 xcfb_show_screen(v, cookie)
376 void *v;
377 void *cookie;
378 {
379 }
380
381 int
382 xcfb_load_font(v, cookie, first, num, stride, data)
383 void *v;
384 void *cookie;
385 int first, num, stride;
386 void *data;
387 {
388 return (EINVAL);
389 }
390
391 int
392 xcfb_cnattach(addr)
393 tc_addr_t addr;
394 {
395 struct fb_devconfig *dcp = &xcfb_console_dc;
396 long defattr;
397
398 xcfb_getdevconfig(addr, dcp);
399
400 rcons_alloc_attr(&dcp->dc_rcons, 0, 0, 0, &defattr);
401
402 wsdisplay_cnattach(&xcfb_stdscreen, &dcp->dc_rcons,
403 0, 0, defattr);
404 xcfb_consaddr = addr;
405 return(0);
406 }
407
408 void
409 xcfbinit(dc)
410 struct fb_devconfig *dc;
411 {
412 u_int32_t csr;
413 int i;
414
415 ims332_write_reg(IMS332_REG_LUT_BASE, 0);
416 for (i = 1; i < CMAP_SIZE; i++)
417 ims332_write_reg(IMS332_REG_LUT_BASE + i, 0xffffff);
418
419 csr = IMS332_BPP_8 | IMS332_CSR_A_DMA_DISABLE
420 | IMS332_CSR_A_VTG_ENABLE | IMS332_CSR_A_DISABLE_CURSOR;
421 ims332_write_reg(IMS332_REG_CSR_A, csr);
422
423 ims332_write_reg(IMS332_REG_COLOR_MASK, 0xffffff);
424 }
425
426 void
427 xcfb_blank(sc)
428 struct xcfb_softc *sc;
429 {
430 struct fb_devconfig *dc = sc->sc_dc;
431
432 if (dc->dc_blanked)
433 return;
434 dc->dc_blanked = 1;
435
436 /* blank screen */
437 ims332_write_reg(IMS332_REG_LUT_BASE, 0);
438 ims332_write_reg(IMS332_REG_COLOR_MASK, 0);
439 #if 0
440 /* turnoff hardware cursor */
441 csr = ims332_read_reg(IMS332_REG_CSR_A);
442 csr |= IMS332_CSR_A_DISABLE_CURSOR;
443 ims332_write_reg(IMS332_REG_CSR_A, csr);
444 #endif /* pratically unnecessary */
445 }
446
447 void
448 xcfb_unblank(sc)
449 struct xcfb_softc *sc;
450 {
451 struct fb_devconfig *dc = sc->sc_dc;
452
453 if (!dc->dc_blanked)
454 return;
455 dc->dc_blanked = 0;
456
457 /* restore current colormap */
458 ims332_loadcmap(&sc->sc_cmap);
459 #if 0
460 /* turnon hardware cursor */
461 csr = ims332_read_reg(IMS332_REG_CSR_A);
462 csr &= ~IMS332_CSR_A_DISABLE_CURSOR;
463 ims332_write_reg(IMS332_REG_CSR_A, csr);
464 #endif /* pratically unnecessary */
465 }
466
467 static int
468 get_cmap(sc, p)
469 struct xcfb_softc *sc;
470 struct wsdisplay_cmap *p;
471 {
472 u_int index = p->index, count = p->count;
473
474 if (index >= CMAP_SIZE || (index + count) > CMAP_SIZE)
475 return (EINVAL);
476
477 if (!useracc(p->red, count, B_WRITE) ||
478 !useracc(p->green, count, B_WRITE) ||
479 !useracc(p->blue, count, B_WRITE))
480 return (EFAULT);
481
482 copyout(&sc->sc_cmap.r[index], p->red, count);
483 copyout(&sc->sc_cmap.g[index], p->green, count);
484 copyout(&sc->sc_cmap.b[index], p->blue, count);
485
486 return (0);
487 }
488
489 static int
490 set_cmap(sc, p)
491 struct xcfb_softc *sc;
492 struct wsdisplay_cmap *p;
493 {
494 u_int index = p->index, count = p->count;
495
496 if (index >= CMAP_SIZE || (index + count) > CMAP_SIZE)
497 return (EINVAL);
498
499 if (!useracc(p->red, count, B_READ) ||
500 !useracc(p->green, count, B_READ) ||
501 !useracc(p->blue, count, B_READ))
502 return (EFAULT);
503
504 copyin(p->red, &sc->sc_cmap.r[index], count);
505 copyin(p->green, &sc->sc_cmap.g[index], count);
506 copyin(p->blue, &sc->sc_cmap.b[index], count);
507
508 return (0);
509 }
510
511 static int
512 set_cursor(sc, p)
513 struct xcfb_softc *sc;
514 struct wsdisplay_cursor *p;
515 {
516 #define cc (&sc->sc_cursor)
517 int v, index, count;
518 u_int32_t csr;
519
520 v = p->which;
521 if (v & WSDISPLAY_CURSOR_DOCMAP) {
522 index = p->cmap.index;
523 count = p->cmap.count;
524
525 if (index >= 2 || index + count > 2)
526 return (EINVAL);
527 if (!useracc(p->cmap.red, count, B_READ) ||
528 !useracc(p->cmap.green, count, B_READ) ||
529 !useracc(p->cmap.blue, count, B_READ))
530 return (EFAULT);
531
532 copyin(p->cmap.red, &cc->cc_color[index], count);
533 copyin(p->cmap.green, &cc->cc_color[index + 2], count);
534 copyin(p->cmap.blue, &cc->cc_color[index + 4], count);
535
536 ims332_load_curcmap(sc);
537 }
538 if (v & WSDISPLAY_CURSOR_DOSHAPE) {
539 if (p->size.x > CURSOR_MAX_SIZE || p->size.y > CURSOR_MAX_SIZE)
540 return (EINVAL);
541 count = (CURSOR_MAX_SIZE / NBBY) * p->size.y;
542 if (!useracc(p->image, count, B_READ) ||
543 !useracc(p->mask, count, B_READ))
544 return (EFAULT);
545 cc->cc_size = p->size;
546 memset(cc->cc_image, 0, sizeof cc->cc_image);
547 copyin(p->image, cc->cc_image, count);
548 copyin(p->mask, &cc->cc_image[CURSOR_MAX_SIZE], count);
549
550 ims332_load_curshape(sc);
551 }
552 if (v & WSDISPLAY_CURSOR_DOCUR) {
553 cc->cc_hot = p->hot;
554 csr = ims332_read_reg(IMS332_REG_CSR_A);
555 if (p->enable)
556 csr &= ~IMS332_CSR_A_DISABLE_CURSOR;
557 else
558 csr |= IMS332_CSR_A_DISABLE_CURSOR;
559 ims332_write_reg(IMS332_REG_CSR_A, csr);
560 }
561 if (v & WSDISPLAY_CURSOR_DOPOS) {
562 set_curpos(sc, &p->pos);
563 ims332_set_curpos(sc);
564 }
565
566 return (0);
567 #undef cc
568 }
569
570 static int
571 get_cursor(sc, p)
572 struct xcfb_softc *sc;
573 struct wsdisplay_cursor *p;
574 {
575 return (ENOTTY); /* XXX */
576 }
577
578 static void
579 set_curpos(sc, curpos)
580 struct xcfb_softc *sc;
581 struct wsdisplay_curpos *curpos;
582 {
583 struct fb_devconfig *dc = sc->sc_dc;
584 int x = curpos->x, y = curpos->y;
585
586 if (y < 0)
587 y = 0;
588 else if (y > dc->dc_ht - sc->sc_cursor.cc_size.y - 1)
589 y = dc->dc_ht - sc->sc_cursor.cc_size.y - 1;
590 if (x < 0)
591 x = 0;
592 else if (x > dc->dc_wid - sc->sc_cursor.cc_size.x - 1)
593 x = dc->dc_wid - sc->sc_cursor.cc_size.x - 1;
594 sc->sc_cursor.cc_pos.x = x;
595 sc->sc_cursor.cc_pos.y = y;
596 }
597
598 void
599 ims332_loadcmap(cm)
600 struct hwcmap *cm;
601 {
602 int i;
603 u_int32_t rgb;
604
605 for (i = 0; i < CMAP_SIZE; i++) {
606 rgb = cm->b[i] << 16 | cm->g[i] << 8 | cm->r[i];
607 ims332_write_reg(IMS332_REG_LUT_BASE + i, rgb);
608 }
609 }
610
611 void
612 ims332_set_curpos(sc)
613 struct xcfb_softc *sc;
614 {
615 struct wsdisplay_curpos *curpos = &sc->sc_cursor.cc_pos;
616 u_int32_t pos;
617
618 pos = (curpos->x & 0xfff) << 12 | (curpos->y & 0xfff);
619 ims332_write_reg(IMS332_REG_CURSOR_LOC, pos);
620 }
621
622 void
623 ims332_load_curcmap(sc)
624 struct xcfb_softc *sc;
625 {
626 u_int8_t *cp = sc->sc_cursor.cc_color;
627 u_int32_t rgb;
628
629 rgb = cp[5] << 16 | cp[3] << 8 | cp[1];
630 ims332_write_reg(IMS332_REG_CURSOR_LUT_0, rgb);
631 rgb = cp[4] << 16 | cp[2] << 8 | cp[0];
632 ims332_write_reg(IMS332_REG_CURSOR_LUT_1, rgb);
633 ims332_write_reg(IMS332_REG_CURSOR_LUT_2, rgb);
634 }
635
636 void
637 ims332_load_curshape(sc)
638 struct xcfb_softc *sc;
639 {
640 int i;
641 u_int16_t *word;
642
643 word = (u_int16_t *)sc->sc_cursor.cc_image;
644 for (i = 0; i < sizeof(sc->sc_cursor.cc_image)/sizeof(u_int16_t); i++)
645 ims332_write_reg(IMS332_REG_CURSOR_RAM + i, *word++);
646 }
647
648 u_int32_t
649 ims332_read_reg(regno)
650 int regno;
651 {
652 caddr_t imsreg = (caddr_t)IMS332_ADDRESS;
653 caddr_t rptr = (caddr_t)IMS332_RPTR + (regno << 4);
654 u_int v0, v1;
655
656 v1 = *(volatile u_int32_t *)imsreg;
657 v0 = *(volatile u_int16_t *)rptr;
658 return (v1 & 0xff00) << 8 | v0;
659 }
660
661 void
662 ims332_write_reg(regno, val)
663 int regno;
664 u_int32_t val;
665 {
666 caddr_t imsreg = (caddr_t)IMS332_ADDRESS;
667 caddr_t wptr = (caddr_t)IMS332_WPTR + (regno << 4);
668
669 *(volatile u_int32_t *)imsreg = (val & 0xff0000) >> 8;
670 *(volatile u_int16_t *)wptr = val;
671 }
672