/* $OpenBSD: tvtwo.c,v 1.12 2008/03/15 21:10:38 miod Exp $ */ /* * Copyright (c) 2003, 2006, 2008, Miodrag Vallat. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * */ /* * Driver for the Parallax XVideo and PowerVideo graphics boards. * * Some details about these board used to be available at: * http://www.jlw.com/~woolsey/parallax/support/developers/xvideotech.html */ /* * The Parallax XVideo series frame buffers are 8/24-bit accelerated * frame buffers, with hardware MPEG capabilities using a CCube chipset. */ /* * Currently, this driver can only handle the 8-bit and 24-bit planes of the * frame buffer, in an unaccelerated mode. * * TODO: * - nvram handling * - use the accelerator * - interface to the c^3 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * The memory layout of the board is as follows: * * PROM0 000000 - 00ffff * overlay plane 010000 - 037fff * registers 040000 - 0404d0 * CCube 050000 - 05ffff * 8-bit plane 080000 - 17ffff * 24-bit plane 200000 - 6fffff * PROM1 7f0000 - 7fffff */ #define PX_PROM0_OFFSET 0x000000 #define PX_OVERLAY_OFFSET 0x010000 #define PX_REG_OFFSET 0x040000 #define PX_CCUBE_OFFSET 0x050000 #define PX_PLANE8_OFFSET 0x080000 #define PX_PLANE24_OFFSET 0x200000 #define PX_PROM1_OFFSET 0x7f0000 #define PX_MAP_SIZE 0x800000 /* * Partial registers layout */ #define PX_REG_DISPKLUGE 0x00b8 /* write only */ #define DISPKLUGE_DEFAULT 0xc41f #define DISPKLUGE_BLANK (1 << 12) #define DISPKLUGE_SYNC (1 << 13) #define PX_REG_BT463_RED 0x0480 #define PX_REG_BT463_GREEN 0x0490 #define PX_REG_BT463_BLUE 0x04a0 #define PX_REG_BT463_ALL 0x04b0 #define PX_REG_SIZE 0x04d0 /* per-display variables */ struct tvtwo_softc { struct sunfb sc_sunfb; /* common base device */ bus_space_tag_t sc_bustag; bus_addr_t sc_paddr; volatile u_int8_t *sc_m8; volatile u_int8_t *sc_m24; volatile u_int8_t *sc_regs; int sc_nscreens; }; int tvtwo_ioctl(void *, u_long, caddr_t, int, struct proc *); int tvtwo_alloc_screen(void *, const struct wsscreen_descr *, void **, int *, int *, long *); void tvtwo_free_screen(void *, void *); int tvtwo_show_screen(void *, void *, int, void (*cb)(void *, int, int), void *); paddr_t tvtwo_mmap(void *, off_t, int); void tvtwo_burner(void *, u_int, u_int); struct wsdisplay_accessops tvtwo_accessops = { tvtwo_ioctl, tvtwo_mmap, tvtwo_alloc_screen, tvtwo_free_screen, tvtwo_show_screen, NULL, /* load_font */ NULL, /* scrollback */ NULL, /* getchar */ tvtwo_burner, }; void tvtwo_directcmap(struct tvtwo_softc *); static __inline__ void tvtwo_ramdac_wraddr(struct tvtwo_softc *, u_int32_t); void tvtwo_reset(struct tvtwo_softc *, u_int); void tvtwo_setcolor(void *, u_int, u_int8_t, u_int8_t, u_int8_t); int tvtwomatch(struct device *, void *, void *); void tvtwoattach(struct device *, struct device *, void *); struct cfattach tvtwo_ca = { sizeof(struct tvtwo_softc), tvtwomatch, tvtwoattach }; struct cfdriver tvtwo_cd = { NULL, "tvtwo", DV_DULL }; /* * Default frame buffer resolution, depending upon the "freqcode" */ #define NFREQCODE 5 const int defwidth[NFREQCODE] = { 1152, 1152, 1152, 1024, 640 }; const int defheight[NFREQCODE] = { 900, 900, 900, 768, 480 }; /* * Match an XVideo or PowerVideo card. */ int tvtwomatch(struct device *parent, void *vcf, void *aux) { struct sbus_attach_args *sa = aux; if (strcmp(sa->sa_name, "PGI,tvtwo") == 0 || strcmp(sa->sa_name, "PGI,tvthree") == 0) return (1); return (0); } /* * Attach a display. */ void tvtwoattach(struct device *parent, struct device *self, void *args) { struct tvtwo_softc *sc = (struct tvtwo_softc *)self; struct sbus_attach_args *sa = args; bus_space_tag_t bt; bus_space_handle_t bh; int node, width, height, freqcode; int isconsole; char *freqstring, *revision; bt = sa->sa_bustag; node = sa->sa_node; printf(": %s", getpropstring(node, "model")); revision = getpropstring(node, "revision"); if (*revision != '\0') printf(", revision %s", revision); /* Older XVideo provide two sets of SBus registers: * R0 040000 - 040800 * R1 080000 - 17d200 * While the more recent revisions provide only one register: * R0 000000 - 7fffff * * We'll simply ``rewrite'' R0 on older boards and handle them as * recent boards. */ if (sa->sa_nreg > 1) { sa->sa_offset -= PX_REG_OFFSET; sa->sa_size = PX_MAP_SIZE; } isconsole = node == fbnode; /* Map registers. */ sc->sc_bustag = bt; if (sbus_bus_map(bt, sa->sa_slot, sa->sa_offset + PX_REG_OFFSET, PX_REG_SIZE, BUS_SPACE_MAP_LINEAR, 0, &bh) != 0) { printf("%s: couldn't map registers\n", self->dv_xname); return; } sc->sc_regs = bus_space_vaddr(bt, bh); /* * Compute framebuffer size. * Older boards do not have the ``freqcode'' property and are * restricted to 1152x900. */ freqstring = getpropstring(node, "freqcode"); if (*freqstring != '\0') { freqcode = (int)*freqstring; if (freqcode == 'g') { width = height = 1024; } else { if (freqcode < '1' || freqcode > '6') freqcode = 0; else freqcode -= '1'; width = defwidth[freqcode]; height = defheight[freqcode]; /* in case our table is wrong or incomplete... */ width = getpropint(node, "hres", width); height = getpropint(node, "vres", height); } } else { width = 1152; height = 900; } /* * Since the depth property is missing, we could do * fb_setsize(&sc->sc_sunfb, 8, width, height, node, 0); * but for safety in case it would exist and be set to 32, do it * manually... */ sc->sc_sunfb.sf_depth = 8; sc->sc_sunfb.sf_width = width; sc->sc_sunfb.sf_height = height; sc->sc_sunfb.sf_linebytes = width >= 1024 ? width : 1024; sc->sc_sunfb.sf_fbsize = sc->sc_sunfb.sf_linebytes * height; printf(", %dx%d\n", sc->sc_sunfb.sf_width, sc->sc_sunfb.sf_height); /* Map the frame buffer memory area we're interested in. */ sc->sc_paddr = sbus_bus_addr(bt, sa->sa_slot, sa->sa_offset); if (sbus_bus_map(bt, sa->sa_slot, sa->sa_offset + PX_PLANE8_OFFSET, round_page(sc->sc_sunfb.sf_fbsize), BUS_SPACE_MAP_LINEAR, 0, &bh) != 0) { printf("%s: couldn't map 8-bit video plane\n", self->dv_xname); return; } sc->sc_m8 = bus_space_vaddr(bt, bh); if (sbus_bus_map(bt, sa->sa_slot, sa->sa_offset + PX_PLANE24_OFFSET, round_page(4 * sc->sc_sunfb.sf_fbsize), BUS_SPACE_MAP_LINEAR, 0, &bh) != 0) { printf("%s: couldn't map 32-bit video plane\n", self->dv_xname); return; } sc->sc_m24 = bus_space_vaddr(bt, bh); /* Enable video. */ tvtwo_burner(sc, 1, 0); sc->sc_sunfb.sf_ro.ri_hw = sc; sc->sc_sunfb.sf_ro.ri_bits = (u_char *)sc->sc_m8; /* * If the framebuffer width is under 1024, we will switch from * the PROM font to the more adequate 8x16 font here. */ fbwscons_init(&sc->sc_sunfb, isconsole && (width >= 1024) ? RI_CLEARMARGINS : RI_CLEAR); fbwscons_setcolormap(&sc->sc_sunfb, tvtwo_setcolor); if (isconsole) { fbwscons_console_init(&sc->sc_sunfb, width >= 1024 ? -1 : 0); } fbwscons_attach(&sc->sc_sunfb, &tvtwo_accessops, isconsole); } int tvtwo_ioctl(void *dev, u_long cmd, caddr_t data, int flags, struct proc *p) { struct tvtwo_softc *sc = dev; struct wsdisplay_fbinfo *wdf; /* * Note that, although the emulation (text) mode is running in a * 8-bit plane, we advertize the frame buffer as 32-bit. */ switch (cmd) { case WSDISPLAYIO_GTYPE: *(u_int *)data = WSDISPLAY_TYPE_SUN24; break; case WSDISPLAYIO_GINFO: wdf = (struct wsdisplay_fbinfo *)data; wdf->height = sc->sc_sunfb.sf_height; wdf->width = sc->sc_sunfb.sf_width; wdf->depth = 32; wdf->cmsize = 0; break; case WSDISPLAYIO_GETSUPPORTEDDEPTH: *(u_int *)data = WSDISPLAYIO_DEPTH_24_32; break; case WSDISPLAYIO_LINEBYTES: *(u_int *)data = sc->sc_sunfb.sf_linebytes * 4; break; case WSDISPLAYIO_GETCMAP: case WSDISPLAYIO_PUTCMAP: break; case WSDISPLAYIO_SMODE: if (*(int *)data == WSDISPLAYIO_MODE_EMUL) { /* Back from X11 to text mode */ tvtwo_reset(sc, 8); } else { /* Starting X11, initialize 32-bit mode */ tvtwo_reset(sc, 32); } break; case WSDISPLAYIO_SVIDEO: case WSDISPLAYIO_GVIDEO: break; case WSDISPLAYIO_GCURPOS: case WSDISPLAYIO_SCURPOS: case WSDISPLAYIO_GCURMAX: case WSDISPLAYIO_GCURSOR: case WSDISPLAYIO_SCURSOR: default: return (-1); } return (0); } /* * Return the address that would map the given device at the given * offset, allowing for the given protection, or return -1 for error. */ paddr_t tvtwo_mmap(void *v, off_t offset, int prot) { struct tvtwo_softc *sc = v; if (offset & PGOFSET) return (-1); /* Allow mapping as a dumb framebuffer from offset 0 */ if (offset >= 0 && offset < sc->sc_sunfb.sf_fbsize * 4) { return (bus_space_mmap(sc->sc_bustag, sc->sc_paddr, PX_PLANE24_OFFSET + offset, prot, BUS_SPACE_MAP_LINEAR)); } return (-1); } int tvtwo_alloc_screen(void *v, const struct wsscreen_descr *type, void **cookiep, int *curxp, int *curyp, long *attrp) { struct tvtwo_softc *sc = v; if (sc->sc_nscreens > 0) return (ENOMEM); *cookiep = &sc->sc_sunfb.sf_ro; *curyp = 0; *curxp = 0; sc->sc_sunfb.sf_ro.ri_ops.alloc_attr(&sc->sc_sunfb.sf_ro, 0, 0, 0, attrp); sc->sc_nscreens++; return (0); } void tvtwo_free_screen(void *v, void *cookie) { struct tvtwo_softc *sc = v; sc->sc_nscreens--; } int tvtwo_show_screen(void *v, void *cookie, int waitok, void (*cb)(void *, int, int), void *cbarg) { return (0); } void tvtwo_burner(void *v, u_int on, u_int flags) { struct tvtwo_softc *sc = v; u_int32_t dispkluge; if (on) dispkluge = DISPKLUGE_DEFAULT & ~DISPKLUGE_BLANK; else { dispkluge = DISPKLUGE_DEFAULT | DISPKLUGE_BLANK; if (flags & WSDISPLAY_BURN_VBLANK) dispkluge |= DISPKLUGE_SYNC; } *(volatile u_int32_t *)(sc->sc_regs + PX_REG_DISPKLUGE) = dispkluge; } void tvtwo_reset(struct tvtwo_softc *sc, u_int depth) { if (depth == 32) { /* Initialize a direct color map. */ tvtwo_directcmap(sc); } else { fbwscons_setcolormap(&sc->sc_sunfb, tvtwo_setcolor); } } /* * Simple Bt463 programming routines. */ static __inline__ void tvtwo_ramdac_wraddr(struct tvtwo_softc *sc, u_int32_t addr) { volatile u_int32_t *dac = (u_int32_t *)(sc->sc_regs + PX_REG_BT463_RED); dac[0] = (addr & 0xff); /* lo addr */ dac[1] = ((addr >> 8) & 0xff); /* hi addr */ } void tvtwo_directcmap(struct tvtwo_softc *sc) { volatile u_int32_t *dac = (u_int32_t *)(sc->sc_regs + PX_REG_BT463_RED); u_int32_t c; tvtwo_ramdac_wraddr(sc, 0); for (c = 0; c < 256; c++) { dac[3] = c; /* R */ dac[3] = c; /* G */ dac[3] = c; /* B */ } } void tvtwo_setcolor(void *v, u_int index, u_int8_t r, u_int8_t g, u_int8_t b) { struct tvtwo_softc *sc = v; volatile u_int32_t *dac = (u_int32_t *)(sc->sc_regs + PX_REG_BT463_RED); tvtwo_ramdac_wraddr(sc, index); dac[3] = r; dac[3] = g; dac[3] = b; }