/* $OpenBSD: smfb.c,v 1.16 2013/10/21 10:36:14 miod Exp $ */ /* * Copyright (c) 2009, 2010 Miodrag Vallat. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * SiliconMotion SM502 and SM712 frame buffer driver. * * Assumes its video output is an LCD panel, in 5:6:5 mode, and fixed * 1024x600 or 800x480 resolution, depending on the system model. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct smfb_softc; /* minimal frame buffer information, suitable for early console */ struct smfb { struct smfb_softc *sc; struct rasops_info ri; int is5xx; /* DPR registers */ bus_space_tag_t dprt; bus_space_handle_t dprh; /* MMIO space (SM7xx) or control registers (SM5xx) */ bus_space_tag_t mmiot; bus_space_handle_t mmioh; /* DCR registers (SM5xx) */ bus_space_tag_t dcrt; bus_space_handle_t dcrh; struct wsscreen_descr wsd; }; #define DCR_READ(fb, reg) \ bus_space_read_4((fb)->dcrt, (fb)->dcrh, (reg)) #define DCR_WRITE(fb, reg, val) \ bus_space_write_4((fb)->dcrt, (fb)->dcrh, (reg), (val)) #define DPR_READ(fb, reg) \ bus_space_read_4((fb)->dprt, (fb)->dprh, (reg)) #define DPR_WRITE(fb, reg, val) \ bus_space_write_4((fb)->dprt, (fb)->dprh, (reg), (val)) struct smfb_softc { struct device sc_dev; struct smfb *sc_fb; struct smfb sc_fb_store; struct wsscreen_list sc_wsl; struct wsscreen_descr *sc_scrlist[1]; int sc_nscr; }; int smfb_pci_match(struct device *, void *, void *); void smfb_pci_attach(struct device *, struct device *, void *); int smfb_voyager_match(struct device *, void *, void *); void smfb_voyager_attach(struct device *, struct device *, void *); int smfb_activate(struct device *, int); const struct cfattach smfb_pci_ca = { sizeof(struct smfb_softc), smfb_pci_match, smfb_pci_attach, NULL, smfb_activate }; const struct cfattach smfb_voyager_ca = { sizeof(struct smfb_softc), smfb_voyager_match, smfb_voyager_attach, smfb_activate }; struct cfdriver smfb_cd = { NULL, "smfb", DV_DULL }; int smfb_alloc_screen(void *, const struct wsscreen_descr *, void **, int *, int *, long *); void smfb_burner(void *, uint, uint); void smfb_free_screen(void *, void *); int smfb_ioctl(void *, u_long, caddr_t, int, struct proc *); int smfb_list_font(void *, struct wsdisplay_font *); int smfb_load_font(void *, void *, struct wsdisplay_font *); paddr_t smfb_mmap(void *, off_t, int); int smfb_show_screen(void *, void *, int, void (*)(void *, int, int), void *); struct wsdisplay_accessops smfb_accessops = { .ioctl = smfb_ioctl, .mmap = smfb_mmap, .alloc_screen = smfb_alloc_screen, .free_screen = smfb_free_screen, .show_screen = smfb_show_screen, .load_font = smfb_load_font, .list_font = smfb_list_font, .burn_screen = smfb_burner }; int smfb_setup(struct smfb *, bus_space_tag_t, bus_space_handle_t, bus_space_tag_t, bus_space_handle_t); void smfb_copyrect(struct smfb *, int, int, int, int, int, int); void smfb_fillrect(struct smfb *, int, int, int, int, int); int smfb_copyrows(void *, int, int, int); int smfb_copycols(void *, int, int, int, int); int smfb_do_cursor(struct rasops_info *); int smfb_erasecols(void *, int, int, int, long); int smfb_eraserows(void *, int, int, long); int smfb_wait(struct smfb *); void smfb_wait_panel_vsync(struct smfb *, int); uint8_t smfb_vgats_read(struct smfb *, uint); void smfb_vgats_write(struct smfb *, uint, uint8_t); void smfb_attach_common(struct smfb_softc *, int, bus_space_tag_t, bus_space_handle_t, bus_space_tag_t, bus_space_handle_t); static struct smfb smfbcn; const struct pci_matchid smfb_devices[] = { { PCI_VENDOR_SMI, PCI_PRODUCT_SMI_SM712 } }; int smfb_pci_match(struct device *parent, void *vcf, void *aux) { struct pci_attach_args *pa = (struct pci_attach_args *)aux; return pci_matchbyid(pa, smfb_devices, nitems(smfb_devices)); } int smfb_voyager_match(struct device *parent, void *vcf, void *aux) { struct voyager_attach_args *vaa = (struct voyager_attach_args *)aux; struct cfdata *cf = (struct cfdata *)vcf; return strcmp(vaa->vaa_name, cf->cf_driver->cd_name) == 0; } void smfb_pci_attach(struct device *parent, struct device *self, void *aux) { struct smfb_softc *sc = (struct smfb_softc *)self; struct pci_attach_args *pa = (struct pci_attach_args *)aux; bus_space_tag_t memt; bus_space_handle_t memh; if (pci_mapreg_map(pa, PCI_MAPREG_START, PCI_MAPREG_TYPE_MEM, BUS_SPACE_MAP_LINEAR, &memt, &memh, NULL, NULL, 0) != 0) { printf(": can't map frame buffer\n"); return; } smfb_attach_common(sc, 0, memt, memh, memt, memh); } void smfb_voyager_attach(struct device *parent, struct device *self, void *aux) { struct smfb_softc *sc = (struct smfb_softc *)self; struct voyager_attach_args *vaa = (struct voyager_attach_args *)aux; smfb_attach_common(sc, 1, vaa->vaa_fbt, vaa->vaa_fbh, vaa->vaa_mmiot, vaa->vaa_mmioh); } void smfb_attach_common(struct smfb_softc *sc, int is5xx, bus_space_tag_t memt, bus_space_handle_t memh, bus_space_tag_t mmiot, bus_space_handle_t mmioh) { struct wsemuldisplaydev_attach_args waa; int console; console = smfbcn.ri.ri_hw != NULL; if (console) { sc->sc_fb = &smfbcn; sc->sc_fb->sc = sc; } else { sc->sc_fb = &sc->sc_fb_store; sc->sc_fb->is5xx = is5xx; if (smfb_setup(sc->sc_fb, memt, memh, mmiot, mmioh) != 0) { printf(": can't setup frame buffer\n"); return; } } /* XXX print resolution */ printf("\n"); sc->sc_scrlist[0] = &sc->sc_fb->wsd; sc->sc_wsl.nscreens = 1; sc->sc_wsl.screens = (const struct wsscreen_descr **)sc->sc_scrlist; waa.console = console; waa.scrdata = &sc->sc_wsl; waa.accessops = &smfb_accessops; waa.accesscookie = sc; waa.defaultscreens = 0; config_found((struct device *)sc, &waa, wsemuldisplaydevprint); } /* * wsdisplay accesops */ int smfb_alloc_screen(void *v, const struct wsscreen_descr *type, void **cookiep, int *curxp, int *curyp, long *attrp) { struct smfb_softc *sc = (struct smfb_softc *)v; struct rasops_info *ri = &sc->sc_fb->ri; if (sc->sc_nscr > 0) return ENOMEM; *cookiep = ri; *curxp = *curyp = 0; ri->ri_ops.alloc_attr(ri, 0, 0, 0, attrp); sc->sc_nscr++; return 0; } void smfb_free_screen(void *v, void *cookie) { struct smfb_softc *sc = (struct smfb_softc *)v; sc->sc_nscr--; } int smfb_ioctl(void *v, u_long cmd, caddr_t data, int flags, struct proc *p) { struct smfb_softc *sc = (struct smfb_softc *)v; struct rasops_info *ri = &sc->sc_fb->ri; struct wsdisplay_fbinfo *wdf; switch (cmd) { case WSDISPLAYIO_GTYPE: *(uint *)data = WSDISPLAY_TYPE_SMFB; break; case WSDISPLAYIO_GINFO: wdf = (struct wsdisplay_fbinfo *)data; wdf->width = ri->ri_width; wdf->height = ri->ri_height; wdf->depth = ri->ri_depth; wdf->cmsize = 0; break; case WSDISPLAYIO_LINEBYTES: *(uint *)data = ri->ri_stride; break; default: return -1; } return 0; } int smfb_show_screen(void *v, void *cookie, int waitok, void (*cb)(void *, int, int), void *cbarg) { return 0; } paddr_t smfb_mmap(void *v, off_t offset, int prot) { struct smfb_softc *sc = (struct smfb_softc *)v; struct rasops_info *ri = &sc->sc_fb->ri; if ((offset & PAGE_MASK) != 0) return -1; if (offset < 0 || offset >= ri->ri_stride * ri->ri_height) return -1; return XKPHYS_TO_PHYS((paddr_t)ri->ri_bits) + offset; } int smfb_load_font(void *v, void *emulcookie, struct wsdisplay_font *font) { struct smfb_softc *sc = (struct smfb_softc *)v; struct rasops_info *ri = &sc->sc_fb->ri; return rasops_load_font(ri, emulcookie, font); } int smfb_list_font(void *v, struct wsdisplay_font *font) { struct smfb_softc *sc = (struct smfb_softc *)v; struct rasops_info *ri = &sc->sc_fb->ri; return rasops_list_font(ri, font); } void smfb_burner(void *v, uint on, uint flg) { struct smfb_softc *sc = (struct smfb_softc *)v; struct smfb *fb = sc->sc_fb; if (fb->is5xx) { if (on) { /* * Wait for a few cycles after restoring power, * to prevent white flickering. */ DCR_WRITE(fb, DCR_PANEL_DISPLAY_CONTROL, DCR_READ(fb, DCR_PANEL_DISPLAY_CONTROL) | PDC_VDD); smfb_wait_panel_vsync(fb, 4); DCR_WRITE(fb, DCR_PANEL_DISPLAY_CONTROL, DCR_READ(fb, DCR_PANEL_DISPLAY_CONTROL) | PDC_DATA); smfb_wait_panel_vsync(fb, 4); DCR_WRITE(fb, DCR_PANEL_DISPLAY_CONTROL, DCR_READ(fb, DCR_PANEL_DISPLAY_CONTROL) | (PDC_BIAS | PDC_EN)); } else DCR_WRITE(fb, DCR_PANEL_DISPLAY_CONTROL, DCR_READ(fb, DCR_PANEL_DISPLAY_CONTROL) & ~(PDC_EN | PDC_BIAS | PDC_DATA | PDC_VDD)); } else { if (on) { smfb_vgats_write(fb, 0x31, smfb_vgats_read(fb, 0x31) | 0x01); } else { smfb_vgats_write(fb, 0x21, smfb_vgats_read(fb, 0x21) | 0x30); smfb_vgats_write(fb, 0x31, smfb_vgats_read(fb, 0x31) & ~0x01); } } } /* * Frame buffer initialization. */ int smfb_setup(struct smfb *fb, bus_space_tag_t memt, bus_space_handle_t memh, bus_space_tag_t mmiot, bus_space_handle_t mmioh) { struct rasops_info *ri; int accel = 0; int rc; ri = &fb->ri; switch (sys_platform->system_type) { case LOONGSON_EBT700: ri->ri_width = 800; ri->ri_height = 480; break; default: case LOONGSON_GDIUM: case LOONGSON_LYNLOONG: case LOONGSON_YEELOONG: ri->ri_width = 1024; ri->ri_height = 600; break; } ri->ri_depth = 16; ri->ri_stride = (ri->ri_width * ri->ri_depth) / 8; ri->ri_flg = RI_CENTER | RI_CLEAR | RI_FULLCLEAR; ri->ri_bits = (void *)bus_space_vaddr(memt, memh); ri->ri_hw = fb; #ifdef __MIPSEL__ /* swap B and R */ ri->ri_rnum = 5; ri->ri_rpos = 11; ri->ri_gnum = 6; ri->ri_gpos = 5; ri->ri_bnum = 5; ri->ri_bpos = 0; #endif rasops_init(ri, 160, 160); strlcpy(fb->wsd.name, "std", sizeof(fb->wsd.name)); fb->wsd.ncols = ri->ri_cols; fb->wsd.nrows = ri->ri_rows; fb->wsd.textops = &ri->ri_ops; fb->wsd.fontwidth = ri->ri_font->fontwidth; fb->wsd.fontheight = ri->ri_font->fontheight; fb->wsd.capabilities = ri->ri_caps; if (fb->is5xx) { fb->dcrt = mmiot; if ((rc = bus_space_subregion(mmiot, mmioh, SM5XX_DCR_BASE, SM5XX_DCR_SIZE, &fb->dcrh)) != 0) return rc; fb->dprt = mmiot; if ((rc = bus_space_subregion(mmiot, mmioh, SM5XX_DPR_BASE, SMXXX_DPR_SIZE, &fb->dprh)) != 0) return rc; fb->mmiot = mmiot; if ((rc = bus_space_subregion(mmiot, mmioh, SM5XX_MMIO_BASE, SM5XX_MMIO_SIZE, &fb->mmioh)) != 0) return rc; accel = 1; } else { fb->dprt = memt; if ((rc = bus_space_subregion(memt, memh, SM7XX_DPR_BASE, SMXXX_DPR_SIZE, &fb->dprh)) != 0) return rc; fb->mmiot = memt; if ((rc = bus_space_subregion(memt, memh, SM7XX_MMIO_BASE, SM7XX_MMIO_SIZE, &fb->mmioh)) != 0) return rc; accel = 1; } /* * Setup 2D acceleration whenever possible */ if (accel) { if (smfb_wait(fb) != 0) accel = 0; } if (accel) { DPR_WRITE(fb, DPR_CROP_TOPLEFT_COORDS, DPR_COORDS(0, 0)); /* use of width both times is intentional */ DPR_WRITE(fb, DPR_PITCH, DPR_COORDS(ri->ri_width, ri->ri_width)); DPR_WRITE(fb, DPR_SRC_WINDOW, DPR_COORDS(ri->ri_width, ri->ri_width)); DPR_WRITE(fb, DPR_BYTE_BIT_MASK, 0xffffffff); DPR_WRITE(fb, DPR_COLOR_COMPARE_MASK, 0); DPR_WRITE(fb, DPR_COLOR_COMPARE, 0); DPR_WRITE(fb, DPR_SRC_BASE, 0); DPR_WRITE(fb, DPR_DST_BASE, 0); DPR_READ(fb, DPR_DST_BASE); ri->ri_ops.copycols = smfb_copycols; ri->ri_ops.copyrows = smfb_copyrows; ri->ri_ops.erasecols = smfb_erasecols; ri->ri_ops.eraserows = smfb_eraserows; } return 0; } void smfb_copyrect(struct smfb *fb, int sx, int sy, int dx, int dy, int w, int h) { uint32_t dir; /* Compute rop direction */ if (sy < dy || (sy == dy && sx <= dx)) { sx += w - 1; dx += w - 1; sy += h - 1; dy += h - 1; dir = DE_CTRL_RTOL; } else dir = 0; DPR_WRITE(fb, DPR_SRC_COORDS, DPR_COORDS(sx, sy)); DPR_WRITE(fb, DPR_DST_COORDS, DPR_COORDS(dx, dy)); DPR_WRITE(fb, DPR_SPAN_COORDS, DPR_COORDS(w, h)); DPR_WRITE(fb, DPR_DE_CTRL, DE_CTRL_START | DE_CTRL_ROP_ENABLE | dir | (DE_CTRL_COMMAND_BITBLT << DE_CTRL_COMMAND_SHIFT) | (DE_CTRL_ROP_SRC << DE_CTRL_ROP_SHIFT)); DPR_READ(fb, DPR_DE_CTRL); smfb_wait(fb); } void smfb_fillrect(struct smfb *fb, int x, int y, int w, int h, int bg) { struct rasops_info *ri; ri = &fb->ri; DPR_WRITE(fb, DPR_FG_COLOR, ri->ri_devcmap[bg]); DPR_WRITE(fb, DPR_DST_COORDS, DPR_COORDS(x, y)); DPR_WRITE(fb, DPR_SPAN_COORDS, DPR_COORDS(w, h)); DPR_WRITE(fb, DPR_DE_CTRL, DE_CTRL_START | DE_CTRL_ROP_ENABLE | (DE_CTRL_COMMAND_SOLIDFILL << DE_CTRL_COMMAND_SHIFT) | (DE_CTRL_ROP_SRC << DE_CTRL_ROP_SHIFT)); DPR_READ(fb, DPR_DE_CTRL); smfb_wait(fb); } int smfb_copyrows(void *cookie, int src, int dst, int num) { struct rasops_info *ri = cookie; struct smfb *fb = ri->ri_hw; struct wsdisplay_font *f = ri->ri_font; num *= f->fontheight; src *= f->fontheight; dst *= f->fontheight; smfb_copyrect(fb, ri->ri_xorigin, ri->ri_yorigin + src, ri->ri_xorigin, ri->ri_yorigin + dst, ri->ri_emuwidth, num); return 0; } int smfb_copycols(void *cookie, int row, int src, int dst, int num) { struct rasops_info *ri = cookie; struct smfb *fb = ri->ri_hw; struct wsdisplay_font *f = ri->ri_font; num *= f->fontwidth; src *= f->fontwidth; dst *= f->fontwidth; row *= f->fontheight; smfb_copyrect(fb, ri->ri_xorigin + src, ri->ri_yorigin + row, ri->ri_xorigin + dst, ri->ri_yorigin + row, num, f->fontheight); return 0; } int smfb_erasecols(void *cookie, int row, int col, int num, long attr) { struct rasops_info *ri = cookie; struct smfb *fb = ri->ri_hw; struct wsdisplay_font *f = ri->ri_font; int bg, fg; ri->ri_ops.unpack_attr(cookie, attr, &fg, &bg, NULL); row *= f->fontheight; col *= f->fontwidth; num *= f->fontwidth; smfb_fillrect(fb, ri->ri_xorigin + col, ri->ri_yorigin + row, num, f->fontheight, bg); return 0; } int smfb_eraserows(void *cookie, int row, int num, long attr) { struct rasops_info *ri = cookie; struct smfb *fb = ri->ri_hw; struct wsdisplay_font *f = ri->ri_font; int bg, fg; int x, y, w; ri->ri_ops.unpack_attr(cookie, attr, &fg, &bg, NULL); if ((num == ri->ri_rows) && ISSET(ri->ri_flg, RI_FULLCLEAR)) { num = ri->ri_height; x = y = 0; w = ri->ri_width; } else { num *= f->fontheight; x = ri->ri_xorigin; y = ri->ri_yorigin + row * f->fontheight; w = ri->ri_emuwidth; } smfb_fillrect(fb, x, y, w, num, bg); return 0; } int smfb_wait(struct smfb *fb) { uint32_t reg; int i; i = 10000; while (i-- != 0) { if (fb->is5xx) { reg = bus_space_read_4(fb->mmiot, fb->mmioh, VOYAGER_SYSTEM_CONTROL); if ((reg & (VSC_FIFO_EMPTY | VSC_2DENGINE_BUSY)) == VSC_FIFO_EMPTY) return 0; } else { reg = smfb_vgats_read(fb, 0x16); if ((reg & 0x18) == 0x10) return 0; } delay(1); } return EBUSY; } /* * wait for a few panel vertical retrace cycles (5xx only) */ void smfb_wait_panel_vsync(struct smfb *fb, int ncycles) { while (ncycles-- != 0) { /* wait for end of retrace-in-progress */ while (ISSET(bus_space_read_4(fb->mmiot, fb->mmioh, VOYAGER_COMMANDLIST_STATUS), VCS_SP)) delay(10); /* wait for start of retrace */ while (!ISSET(bus_space_read_4(fb->mmiot, fb->mmioh, VOYAGER_COMMANDLIST_STATUS), VCS_SP)) delay(10); } } /* * vga sequencer access through mmio space (non-5xx only) */ uint8_t smfb_vgats_read(struct smfb *fb, uint regno) { bus_space_write_1(fb->mmiot, fb->mmioh, IO_VGA + VGA_TS_INDEX, regno); return bus_space_read_1(fb->mmiot, fb->mmioh, IO_VGA + VGA_TS_DATA); } void smfb_vgats_write(struct smfb *fb, uint regno, uint8_t value) { bus_space_write_1(fb->mmiot, fb->mmioh, IO_VGA + VGA_TS_INDEX, regno); bus_space_write_1(fb->mmiot, fb->mmioh, IO_VGA + VGA_TS_DATA, value); } /* * Early console code */ int smfb_cnattach(bus_space_tag_t, bus_space_tag_t, pcitag_t, pcireg_t); int smfb_cnattach(bus_space_tag_t memt, bus_space_tag_t iot, pcitag_t tag, pcireg_t id) { long defattr; struct rasops_info *ri; bus_space_handle_t fbh, mmioh; pcireg_t bar; int rc, is5xx; /* filter out unrecognized devices */ switch (id) { default: return ENODEV; case PCI_ID_CODE(PCI_VENDOR_SMI, PCI_PRODUCT_SMI_SM712): is5xx = 0; break; case PCI_ID_CODE(PCI_VENDOR_SMI, PCI_PRODUCT_SMI_SM501): is5xx = 1; break; } smfbcn.is5xx = is5xx; bar = pci_conf_read_early(tag, PCI_MAPREG_START); if (PCI_MAPREG_TYPE(bar) != PCI_MAPREG_TYPE_MEM) return EINVAL; rc = bus_space_map(memt, PCI_MAPREG_MEM_ADDR(bar), 1 /* XXX */, BUS_SPACE_MAP_LINEAR, &fbh); if (rc != 0) return rc; if (smfbcn.is5xx) { bar = pci_conf_read_early(tag, PCI_MAPREG_START + 0x04); if (PCI_MAPREG_TYPE(bar) != PCI_MAPREG_TYPE_MEM) return EINVAL; rc = bus_space_map(memt, PCI_MAPREG_MEM_ADDR(bar), 1 /* XXX */, BUS_SPACE_MAP_LINEAR, &mmioh); if (rc != 0) return rc; } else { mmioh = fbh; } rc = smfb_setup(&smfbcn, memt, fbh, memt, mmioh); if (rc != 0) return rc; ri = &smfbcn.ri; ri->ri_ops.alloc_attr(ri, 0, 0, 0, &defattr); wsdisplay_cnattach(&smfbcn.wsd, ri, 0, 0, defattr); return 0; } int smfb_activate(struct device *self, int act) { struct smfb_softc *sc = (struct smfb_softc *)self; switch (act) { case DVACT_SUSPEND: smfb_burner(sc, 0, 0); break; case DVACT_RESUME: smfb_burner(sc, 1, 0); break; } return 0; }