/*
* Copyright (c) 2013 Michael Lorenz
* 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.
*/
/*
* A console driver for nvidia geforce graphics controllers
* tested on macppc only so far, should work on other hardware as long as
* something sets up a usable graphics mode and sets the right device properties
* This driver should work with all NV1x hardware but so far it's been tested
* only on NV11 / GeForce2 MX. Needs testing with more hardware and if
* successful, PCI IDs need to be added to gffb_match()
*/
if (PCI_CLASS(pa->pa_class) != PCI_CLASS_DISPLAY)
return 0;
if (PCI_VENDOR(pa->pa_id) != PCI_VENDOR_NVIDIA)
return 0;
/* only card tested on so far - likely need a list */
if (PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_NVIDIA_GEFORCE2MX)
return 100;
if (PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_NVIDIA_GEFORCE_6800U)
return 100;
if (PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_NVIDIA_GF_FXGO5200)
return 100;
return (0);
}
/* first, see what kind of chip we've got */
reg = pci_conf_read(sc->sc_pc, sc->sc_pcitag, PCI_ID_REG);
sc->sc_accel = PCI_PRODUCT(reg) == PCI_PRODUCT_NVIDIA_GEFORCE2MX;
pci_aprint_devinfo(pa, NULL);
/* fill in parameters from properties */
dict = device_properties(self);
if (!prop_dictionary_get_uint32(dict, "width", &sc->sc_width)) {
aprint_error("%s: no width property\n", device_xname(self));
return;
}
if (!prop_dictionary_get_uint32(dict, "height", &sc->sc_height)) {
aprint_error("%s: no height property\n", device_xname(self));
return;
}
#ifdef GLYPHCACHE_DEBUG
/* leave some visible VRAM unused so we can see the glyph cache */
sc->sc_height -= 300;
#endif
if (!prop_dictionary_get_uint32(dict, "depth", &sc->sc_depth)) {
aprint_error("%s: no depth property\n", device_xname(self));
return;
}
if (!prop_dictionary_get_uint32(dict, "linebytes", &sc->sc_stride)) {
aprint_error("%s: no linebytes property\n",
device_xname(self));
return;
}
/*
* on !2MX we need to use the firmware's offset - for some reason
* register writes to anything other than the DACs go wrong
*/
sc->sc_fboffset = 0;
if (prop_dictionary_get_uint32(dict, "address", &addr)) {
sc->sc_fboffset = addr & 0x000fffff; /* XXX */
}
/* don't map more VRAM than we actually have */
if (pci_mapreg_info(sc->sc_pc, sc->sc_pcitag,
0x14, PCI_MAPREG_TYPE_MEM, &sc->sc_fb, &sc->sc_fbsize, &f)) {
aprint_error("%s: can't find the framebuffer?!\n",
device_xname(sc->sc_dev));
}
if (sc->sc_vramsize == 0) sc->sc_vramsize = sc->sc_fbsize;
/* don't map (much) more than we actually need */
if (bus_space_map(sc->sc_memt, sc->sc_fb, 0x1000000,
BUS_SPACE_MAP_PREFETCHABLE | BUS_SPACE_MAP_LINEAR,
&sc->sc_fbh)) {
aprint_error("%s: failed to map the framebuffer.\n",
device_xname(sc->sc_dev));
}
sc->sc_fbaddr = bus_space_vaddr(tag, sc->sc_fbh);
aprint_normal("%s: %d MB aperture at 0x%08x\n", device_xname(self),
(int)(sc->sc_fbsize >> 20), (uint32_t)sc->sc_fb);
aprint_normal_dev(sc->sc_dev, "%d MB video memory\n",
(int)(sc->sc_vramsize >> 20));
/*
* we don't have hardware synchronization so we need a lock to serialize
* access to the DMA buffer between normal and kernel output
* actually it might be enough to use atomic ops on sc_current, sc_free
* etc. but for now we'll play it safe
* XXX we will probably deadlock if we take an interrupt while sc_lock
* is held and then try to printf()
*/
mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_NONE);
wsdisplay_cnattach(&sc->sc_defaultscreen_descr, ri, 0, 0,
defattr);
vcons_replay_msgbuf(&sc->sc_console_screen);
} else {
/*
* since we're not the console we can postpone the rest
* until someone actually allocates a screen for us
*/
if (sc->sc_console_screen.scr_ri.ri_rows == 0) {
/* do some minimal setup to avoid weirdnesses later */
vcons_init_screen(&sc->vd, &sc->sc_console_screen, 1,
&defattr);
} else
(*ri->ri_ops.allocattr)(ri, 0, 0, 0, &defattr);
/* no suspend/resume support yet */
if (!pmf_device_register(sc->sc_dev, NULL, NULL))
aprint_error_dev(sc->sc_dev,
"couldn't establish power handler\n");
/*
* restrict all other mappings to processes with superuser privileges
* or the kernel itself
*/
if (kauth_authorize_machdep(kauth_cred_get(),
KAUTH_MACHDEP_UNMANAGEDMEM,
NULL, NULL, NULL, NULL) != 0) {
aprint_normal("%s: mmap() rejected.\n",
device_xname(sc->sc_dev));
return -1;
}
/*
* from xf86_video_nv/nv_xaa.c:
* There is a HW race condition with videoram command buffers.
* You can't jump to the location of your put offset. We write put
* at the jump offset + SKIPS dwords with noop padding in between
* to solve this problem
*/
#define SKIPS 8
static void
gffb_make_room(struct gffb_softc *sc, int size)
{
uint32_t get;
size = (size + 1) << 2; /* slots -> offset */
while (sc->sc_free < size) {
get = GFFB_READ_4(GFFB_FIFO_GET);
if (sc->sc_put >= get) {
sc->sc_free = 0x2000 - sc->sc_current;
if (sc->sc_free < size) {
gffb_dmanext(sc, 0x20000000);
if(get <= (SKIPS << 2)) {
if (sc->sc_put <= (SKIPS << 2)) {
/* corner case - will be idle */
GFFB_WRITE_4(GFFB_FIFO_PUT,
(SKIPS + 1) << 2);
}
do {
get =GFFB_READ_4(GFFB_FIFO_GET);
} while (get <= (SKIPS << 2));
}
GFFB_WRITE_4(GFFB_FIFO_PUT, SKIPS << 2);
sc->sc_current = sc->sc_put = (SKIPS << 2);
sc->sc_free = get - ((SKIPS + 1) << 2);
}
} else
sc->sc_free = get - sc->sc_current - 4;
}
}
static void
gffb_sync(struct gffb_softc *sc)
{
int bail;
int i;
/*
* if there are commands in the buffer make sure the chip is actually
* trying to run them
*/
gffb_dma_kickoff(sc);
/* now wait for the command buffer to drain... */
bail = 100000000;
while ((GFFB_READ_4(GFFB_FIFO_GET) != sc->sc_put) && (bail > 0)) {
bail--;
}
if (bail == 0) goto crap;
/* ... and for the engine to go idle */
bail = 100000000;
while((GFFB_READ_4(GFFB_BUSY) != 0) && (bail > 0)) {
bail--;
}
if (bail == 0) goto crap;
return;
crap:
/* if we time out fill the buffer with NOPs and cross fingers */
sc->sc_put = 0;
sc->sc_current = 0;
for (i = 0; i < 0x2000; i += 4)
bus_space_write_stream_4(sc->sc_memt, sc->sc_fbh, i, 0);
aprint_error_dev(sc->sc_dev, "DMA lockup\n");
}
static void
gffb_init(struct gffb_softc *sc)
{
int i;
uint32_t foo;
static void
gffb_rectfill(struct gffb_softc *sc, int x, int y, int wi, int he,
uint32_t colour)
{
if (!sc->sc_accel) return;
mutex_enter(&sc->sc_lock);
gffb_rop(sc, 0xcc);
static void
gffb_cursor(void *cookie, int on, int row, int col)
{
struct rasops_info *ri = cookie;
struct vcons_screen *scr = ri->ri_hw;
struct gffb_softc *sc = scr->scr_cookie;
int x, y, wi, he;
wi = ri->ri_font->fontwidth;
he = ri->ri_font->fontheight;
if (sc->sc_mode == WSDISPLAYIO_MODE_EMUL) {
x = ri->ri_ccol * wi + ri->ri_xorigin;
y = ri->ri_crow * he + ri->ri_yorigin;
if (ri->ri_flg & RI_CURSOR) {
gffb_bitblt(sc, x, y, x, y, wi, he, 0x33);
ri->ri_flg &= ~RI_CURSOR;
}
ri->ri_crow = row;
ri->ri_ccol = col;
if (on) {
x = ri->ri_ccol * wi + ri->ri_xorigin;
y = ri->ri_crow * he + ri->ri_yorigin;
gffb_bitblt(sc, x, y, x, y, wi, he, 0x33);
ri->ri_flg |= RI_CURSOR;
}
} else {
scr->scr_ri.ri_crow = row;
scr->scr_ri.ri_ccol = col;
scr->scr_ri.ri_flg &= ~RI_CURSOR;
}
}
static void
gffb_putchar(void *cookie, int row, int col, u_int c, long attr)
{
struct rasops_info *ri = cookie;
struct wsdisplay_font *font = PICK_FONT(ri, c);
struct vcons_screen *scr = ri->ri_hw;
struct gffb_softc *sc = scr->scr_cookie;
int x, y, wi, he, rv = GC_NOPE;
uint32_t bg;
if (sc->sc_mode != WSDISPLAYIO_MODE_EMUL)
return;
if (!CHAR_IN_FONT(c, font))
return;
wi = font->fontwidth;
he = font->fontheight;
x = ri->ri_xorigin + col * wi;
y = ri->ri_yorigin + row * he;
bg = ri->ri_devcmap[(attr >> 16) & 0xf];
if (c == 0x20) {
gffb_rectfill(sc, x, y, wi, he, bg);
return;
}
rv = glyphcache_try(&sc->sc_gc, c, x, y, attr);
if (rv == GC_OK)
return;
/*
* Use gffb_sync to wait for the engine to become idle before
* we start scribbling into VRAM -- we wouldn't want to stomp on
* a scroll in progress or a prior glyphcache_add that hasn't
* completed yet on the GPU.
*/
mutex_enter(&sc->sc_lock);
gffb_sync(sc);
sc->sc_putchar(cookie, row, col, c, attr);
mutex_exit(&sc->sc_lock);
/*
* If glyphcache_try asked us to, cache the newly written
* character. This will issue a gffb_bitblt which will wait
* for our CPU writes to the framebuffer in VRAM to complete
* before triggering GPU reads from the framebuffer in VRAM.
*/
if (rv == GC_ADD) {
glyphcache_add(&sc->sc_gc, c, x, y);
}
}
static void
gffb_copycols(void *cookie, int row, int srccol, int dstcol, int ncols)
{
struct rasops_info *ri = cookie;
struct vcons_screen *scr = ri->ri_hw;
struct gffb_softc *sc = scr->scr_cookie;
int32_t xs, xd, y, width, height;