root/sys/dev/pci/radeonfb.c
/*      $NetBSD: radeonfb.c,v 1.124 2026/07/12 11:36:25 rkujawa Exp $ */

/*-
 * Copyright (c) 2006 Itronix Inc.
 * All rights reserved.
 *
 * Written by Garrett D'Amore for Itronix Inc.
 *
 * 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.
 * 3. The name of Itronix Inc. may not be used to endorse
 *    or promote products derived from this software without specific
 *    prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY ITRONIX INC. ``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 ITRONIX INC. 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.
 */ 

/*
 * ATI Technologies Inc. ("ATI") has not assisted in the creation of, and
 * does not endorse, this software.  ATI will not be responsible or liable
 * for any actual or alleged damage or loss caused by or in connection with
 * the use of or reliance on this software.
 */

/*
 * Portions of this code were taken from XFree86's Radeon driver, which bears
 * this notice:
 *
 * Copyright 2000 ATI Technologies Inc., Markham, Ontario, and
 *                VA Linux Systems Inc., Fremont, California.
 *
 * All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation on the rights to use, copy, modify, merge,
 * publish, distribute, sublicense, and/or sell copies of the Software,
 * and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the
 * next paragraph) shall be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NON-INFRINGEMENT.  IN NO EVENT SHALL ATI, VA LINUX SYSTEMS AND/OR
 * THEIR SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: radeonfb.c,v 1.124 2026/07/12 11:36:25 rkujawa Exp $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/malloc.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/lwp.h>
#include <sys/kauth.h>
#include <sys/kmem.h>

#include <dev/wscons/wsdisplayvar.h>
#include <dev/wscons/wsconsio.h>
#include <dev/wsfont/wsfont.h>
#include <dev/rasops/rasops.h>
#include <dev/videomode/videomode.h>
#include <dev/videomode/edidvar.h>
#include <dev/wscons/wsdisplay_vconsvar.h>
#include <dev/pci/wsdisplay_pci.h>
#include <dev/wscons/wsdisplay_glyphcachevar.h>

#include <dev/pci/pcidevs.h>
#include <dev/pci/pcireg.h>
#include <dev/pci/pcivar.h>
#include <dev/pci/pciio.h>
#include <dev/pci/radeonfbreg.h>
#include <dev/pci/radeonfbvar.h>
#include "opt_radeonfb.h"
#include "opt_vcons.h"

#ifdef RADEONFB_DEPTH_32
#define RADEONFB_DEFAULT_DEPTH 32
#else
#define RADEONFB_DEFAULT_DEPTH 8
#endif

static int radeonfb_match(device_t, cfdata_t, void *);
static void radeonfb_attach(device_t, device_t, void *);
static int radeonfb_ioctl(void *, void *, unsigned long, void *, int,
    struct lwp *);
static paddr_t radeonfb_mmap(void *, void *, off_t, int);
static int radeonfb_scratch_test(struct radeonfb_softc *, int, uint32_t);
static void radeonfb_loadbios(struct radeonfb_softc *,
    const struct pci_attach_args *);

static uintmax_t radeonfb_getprop_num(struct radeonfb_softc *, const char *,
    uintmax_t);
static int radeonfb_getclocks(struct radeonfb_softc *);
static int radeonfb_gettmds(struct radeonfb_softc *);
static int radeonfb_calc_dividers(struct radeonfb_softc *, uint32_t,
    uint32_t *, uint32_t *, int);
/* flags for radeonfb_calc_dividers */
#define NO_ODD_FBDIV    1
    
static int radeonfb_getconnectors(struct radeonfb_softc *);
static const struct videomode *radeonfb_modelookup(const char *);
static void radeonfb_init_screen(void *, struct vcons_screen *, int, long *);
static void radeonfb_pllwriteupdate(struct radeonfb_softc *, int);
static void radeonfb_pllwaitatomicread(struct radeonfb_softc *, int);
static void radeonfb_program_vclk(struct radeonfb_softc *, int, int, int);
static void radeonfb_modeswitch(struct radeonfb_display *);
static void radeonfb_setcrtc(struct radeonfb_display *, int);
static void radeonfb_init_misc(struct radeonfb_softc *);
static void radeonfb_set_fbloc(struct radeonfb_softc *);
static void radeonfb_init_palette(struct radeonfb_display *);
static void radeonfb_r300cg_workaround(struct radeonfb_softc *);

static int radeonfb_isblank(struct radeonfb_display *);
static void radeonfb_blank(struct radeonfb_display *, int);
static int radeonfb_set_cursor(struct radeonfb_display *,
    struct wsdisplay_cursor *);
static int radeonfb_set_curpos(struct radeonfb_display *,
    struct wsdisplay_curpos *);
static void radeonfb_putpal(struct radeonfb_display *, int, int, int, int);
static int radeonfb_putcmap(struct radeonfb_display *, struct wsdisplay_cmap *);
static int radeonfb_getcmap(struct radeonfb_display *, struct wsdisplay_cmap *);

/* acceleration support */
static void  radeonfb_rectfill(struct radeonfb_display *, int dstx, int dsty,
    int width, int height, uint32_t color);
static void  radeonfb_rectfill_a(void *, int, int, int, int, long);
static void radeonfb_bitblt(void *, int srcx, int srcy,
    int dstx, int dsty, int width, int height, int rop);

/* hw cursor support */
static void radeonfb_cursor_cmap(struct radeonfb_display *);
static void radeonfb_cursor_shape(struct radeonfb_display *);
static void radeonfb_cursor_position(struct radeonfb_display *);
static void radeonfb_cursor_visible(struct radeonfb_display *);
static void radeonfb_cursor_update(struct radeonfb_display *, unsigned);

static inline void radeonfb_wait_fifo(struct radeonfb_softc *, int);
static void radeonfb_engine_idle(struct radeonfb_softc *);
static void radeonfb_engine_flush(struct radeonfb_softc *);
static void radeonfb_engine_reset(struct radeonfb_softc *);
static void radeonfb_engine_init(struct radeonfb_display *);
static inline void radeonfb_unclip(struct radeonfb_softc *) __unused;

static void radeonfb_eraserows(void *, int, int, long);
static void radeonfb_erasecols(void *, int, int, int, long);
static void radeonfb_copyrows(void *, int, int, int);
static void radeonfb_copycols(void *, int, int, int, int);
static void radeonfb_cursor(void *, int, int, int);
static void radeonfb_putchar(void *, int, int, unsigned, long);
static void radeonfb_putchar_aa32(void *, int, int, unsigned, long);
static void radeonfb_putchar_aa8(void *, int, int, unsigned, long);
#ifndef RADEONFB_ALWAYS_ACCEL_PUTCHAR
static void radeonfb_putchar_wrapper(void *, int, int, unsigned, long);
#endif
static int radeonfb_allocattr(void *, int, int, int, long *);

static int radeonfb_set_backlight(struct radeonfb_display *, int);
static int radeonfb_get_backlight(struct radeonfb_display *);
static void radeonfb_switch_backlight(struct radeonfb_display *, int);
static void radeonfb_lvds_callout(void *);

static void radeonfb_brightness_up(device_t);
static void radeonfb_brightness_down(device_t);

static struct videomode *radeonfb_best_refresh(struct videomode *,
    struct videomode *);
static void radeonfb_pickres(struct radeonfb_display *, uint16_t *,
    uint16_t *, int);
static const struct videomode *radeonfb_port_mode(struct radeonfb_softc *, 
    struct radeonfb_port *, int, int);

static int radeonfb_drm_print(void *, const char *);

#ifdef  RADEONFB_DEBUG
int     radeon_debug = 1;
#define DPRINTF(x)      \
        if (radeon_debug) printf x
#define PRINTREG(r)     DPRINTF((#r " = %08x\n", GET32(sc, r)))
#define PRINTPLL(r)     DPRINTF((#r " = %08x\n", GETPLL(sc, r)))
#else
#define DPRINTF(x)
#define PRINTREG(r)
#define PRINTPLL(r)
#endif

#define ROUNDUP(x,y)    (((x) + ((y) - 1)) & ~((y) - 1))

#ifndef RADEON_DEFAULT_MODE
/* any reasonably modern display should handle this */
#define RADEON_DEFAULT_MODE     "1024x768x60"
#endif

extern const u_char rasops_cmap[768];

const char      *radeonfb_default_mode = RADEON_DEFAULT_MODE;

static struct {
        int             size;           /* minimum memory size (MB) */
        int             maxx;           /* maximum x dimension */
        int             maxy;           /* maximum y dimension */
        int             maxbpp;         /* maximum bpp */
        int             maxdisp;        /* maximum logical display count */
} radeonfb_limits[] = {
        { 32,   2048, 1536, 32, 2 },
        { 16,   1600, 1200, 32, 2 },
        { 8,    1600, 1200, 32, 1 },
        { 0,    0, 0, 0, 0 },
};

static struct wsscreen_descr radeonfb_stdscreen = {
        "fb",           /* name */
        0, 0,           /* ncols, nrows */
        NULL,           /* textops */
        8, 16,          /* fontwidth, fontheight */
        WSSCREEN_WSCOLORS | WSSCREEN_UNDERLINE | WSSCREEN_RESIZE, /* capabilities */
        0,              /* modecookie */
};

struct wsdisplay_accessops radeonfb_accessops = {
        radeonfb_ioctl,
        radeonfb_mmap,
        NULL,           /* vcons_alloc_screen */
        NULL,           /* vcons_free_screen */
        NULL,           /* vcons_show_screen */
        NULL,           /* load_font */
        NULL,           /* pollc */
        NULL,           /* scroll */
};

static struct {
        uint16_t        devid;
        uint16_t        family;
        uint16_t        flags;
} radeonfb_devices[] = 
{
        /* R100 family */
        { PCI_PRODUCT_ATI_RADEON_R100_QD,       RADEON_R100, 0 },
        { PCI_PRODUCT_ATI_RADEON_R100_QE,       RADEON_R100, 0 },
        { PCI_PRODUCT_ATI_RADEON_R100_QF,       RADEON_R100, 0 },
        { PCI_PRODUCT_ATI_RADEON_R100_QG,       RADEON_R100, 0 },

        /* RV100 family */
        { PCI_PRODUCT_ATI_RADEON_RV100_LY,      RADEON_RV100, RFB_MOB },
        { PCI_PRODUCT_ATI_RADEON_RV100_LZ,      RADEON_RV100, RFB_MOB },
        { PCI_PRODUCT_ATI_RADEON_RV100_QY,      RADEON_RV100, 0 },
        { PCI_PRODUCT_ATI_RADEON_RV100_QZ,      RADEON_RV100, 0 },

        /* RS100 family */
        { PCI_PRODUCT_ATI_RADEON_RS100_4136,    RADEON_RS100, 0 },
        { PCI_PRODUCT_ATI_RADEON_RS100_4336,    RADEON_RS100, RFB_MOB },

        /* RS200/RS250 family */
        { PCI_PRODUCT_ATI_RADEON_RS200_4337,    RADEON_RS200, RFB_MOB },
        { PCI_PRODUCT_ATI_RADEON_RS200_A7,      RADEON_RS200, 0 },
        { PCI_PRODUCT_ATI_RADEON_RS250_B7,      RADEON_RS200, RFB_MOB },
        { PCI_PRODUCT_ATI_RADEON_RS250_D7,      RADEON_RS200, 0 },

        /* R200 family */
        /* add more R200 products? , 5148 */
        { PCI_PRODUCT_ATI_RADEON_R200_BB,       RADEON_R200, 0 },
        { PCI_PRODUCT_ATI_RADEON_R200_BC,       RADEON_R200, 0 },
        { PCI_PRODUCT_ATI_RADEON_R200_QH,       RADEON_R200, 0 },
        { PCI_PRODUCT_ATI_RADEON_R200_QL,       RADEON_R200, 0 },
        { PCI_PRODUCT_ATI_RADEON_R200_QM,       RADEON_R200, 0 },

        /* RV200 family */
        { PCI_PRODUCT_ATI_RADEON_RV200_LW,      RADEON_RV200, RFB_MOB },
        { PCI_PRODUCT_ATI_RADEON_RV200_LX,      RADEON_RV200, RFB_MOB },
        { PCI_PRODUCT_ATI_RADEON_RV200_QW,      RADEON_RV200, 0 },
        { PCI_PRODUCT_ATI_RADEON_RV200_QX,      RADEON_RV200, 0 },

        /* RV250 family */
        { PCI_PRODUCT_ATI_RADEON_RV250_4966,    RADEON_RV250, 0 },
        { PCI_PRODUCT_ATI_RADEON_RV250_4967,    RADEON_RV250, 0 },
        { PCI_PRODUCT_ATI_RADEON_RV250_4C64,    RADEON_RV250, RFB_MOB },
        { PCI_PRODUCT_ATI_RADEON_RV250_4C66,    RADEON_RV250, RFB_MOB },
        { PCI_PRODUCT_ATI_RADEON_RV250_4C67,    RADEON_RV250, RFB_MOB },

        /* RS300 family */
        { PCI_PRODUCT_ATI_RADEON_RS300_X5,      RADEON_RS300, 0 },
        { PCI_PRODUCT_ATI_RADEON_RS300_X4,      RADEON_RS300, 0 },
        { PCI_PRODUCT_ATI_RADEON_RS300_7834,    RADEON_RS300, 0 },
        { PCI_PRODUCT_ATI_RADEON_RS300_7835,    RADEON_RS300, RFB_MOB },

        /* RV280 family */
        { PCI_PRODUCT_ATI_RADEON_RV280_5960,    RADEON_RV280, 0 },
        { PCI_PRODUCT_ATI_RADEON_RV280_5961,    RADEON_RV280, 0 },
        { PCI_PRODUCT_ATI_RADEON_RV280_5962,    RADEON_RV280, 0 },
        { PCI_PRODUCT_ATI_RADEON_RV280_5963,    RADEON_RV280, 0 },
        { PCI_PRODUCT_ATI_RADEON_RV280_5964,    RADEON_RV280, 0 },
        { PCI_PRODUCT_ATI_RADEON_RV280_5C61,    RADEON_RV280, RFB_MOB },
        { PCI_PRODUCT_ATI_RADEON_RV280_5C63,    RADEON_RV280, RFB_MOB },

        /* R300 family */
        { PCI_PRODUCT_ATI_RADEON_R300_AD,       RADEON_R300, 0 },
        { PCI_PRODUCT_ATI_RADEON_R300_AE,       RADEON_R300, 0 },
        { PCI_PRODUCT_ATI_RADEON_R300_AF,       RADEON_R300, 0 },
        { PCI_PRODUCT_ATI_RADEON_R300_AG,       RADEON_R300, 0 },
        { PCI_PRODUCT_ATI_RADEON_R300_ND,       RADEON_R300, 0 },
        { PCI_PRODUCT_ATI_RADEON_R300_NE,       RADEON_R300, 0 },
        { PCI_PRODUCT_ATI_RADEON_R300_NF,       RADEON_R300, 0 },
        { PCI_PRODUCT_ATI_RADEON_R300_NG,       RADEON_R300, 0 },

        /* RV350/RV360 family */
        { PCI_PRODUCT_ATI_RADEON_RV350_AP,      RADEON_RV350, 0 },
        { PCI_PRODUCT_ATI_RADEON_RV350_AQ,      RADEON_RV350, 0 },
        { PCI_PRODUCT_ATI_RADEON_RV360_AR,      RADEON_RV350, 0 },
        { PCI_PRODUCT_ATI_RADEON_RV350_AS,      RADEON_RV350, 0 },
        { PCI_PRODUCT_ATI_RADEON_RV350_AT,      RADEON_RV350, 0 },
        { PCI_PRODUCT_ATI_RADEON_RV350_AV,      RADEON_RV350, 0 },
        { PCI_PRODUCT_ATI_RADEON_RV350_NP,      RADEON_RV350, RFB_MOB },
        { PCI_PRODUCT_ATI_RADEON_RV350_NQ,      RADEON_RV350, RFB_MOB },
        { PCI_PRODUCT_ATI_RADEON_RV350_NR,      RADEON_RV350, RFB_MOB },
        { PCI_PRODUCT_ATI_RADEON_RV350_NS,      RADEON_RV350, RFB_MOB },
        { PCI_PRODUCT_ATI_RADEON_RV350_NT,      RADEON_RV350, RFB_MOB },
        { PCI_PRODUCT_ATI_RADEON_RV350_NV,      RADEON_RV350, RFB_MOB },

        /* R350/R360 family */
        { PCI_PRODUCT_ATI_RADEON_R350_AH,       RADEON_R350, 0 },
        { PCI_PRODUCT_ATI_RADEON_R350_AI,       RADEON_R350, 0 },
        { PCI_PRODUCT_ATI_RADEON_R350_AJ,       RADEON_R350, 0 },
        { PCI_PRODUCT_ATI_RADEON_R350_AK,       RADEON_R350, 0 },
        { PCI_PRODUCT_ATI_RADEON_R350_NH,       RADEON_R350, 0 },
        { PCI_PRODUCT_ATI_RADEON_R350_NI,       RADEON_R350, 0 },
        { PCI_PRODUCT_ATI_RADEON_R350_NK,       RADEON_R350, 0 },
        { PCI_PRODUCT_ATI_RADEON_R360_NJ,       RADEON_R350, 0 },

        /* RV380/RV370 family */
        { PCI_PRODUCT_ATI_RADEON_RV380_3150,    RADEON_RV380, RFB_MOB },
        { PCI_PRODUCT_ATI_RADEON_RV380_3154,    RADEON_RV380, RFB_MOB },
        { PCI_PRODUCT_ATI_RADEON_RV380_3E50,    RADEON_RV380, 0 },
        { PCI_PRODUCT_ATI_RADEON_RV380_3E54,    RADEON_RV380, 0 },
        { PCI_PRODUCT_ATI_RADEON_RV370_5460,    RADEON_RV380, RFB_MOB },
        { PCI_PRODUCT_ATI_RADEON_RV370_5464,    RADEON_RV380, RFB_MOB },
        { PCI_PRODUCT_ATI_RADEON_RV370_5B60,    RADEON_RV380, 0 },
        { PCI_PRODUCT_ATI_RADEON_RV370_5B63,    RADEON_RV380, 0 },
        { PCI_PRODUCT_ATI_RADEON_RV370_5B64,    RADEON_RV380, 0 },
        { PCI_PRODUCT_ATI_RADEON_RV370_5B65,    RADEON_RV380, 0 },

#if notyet
        /* R420/R423 family */
        { PCI_PRODUCT_ATI_RADEON_R420_JH,       RADEON_R420, 0 },
        { PCI_PRODUCT_ATI_RADEON_R420_JI,       RADEON_R420, 0 },
        { PCI_PRODUCT_ATI_RADEON_R420_JJ,       RADEON_R420, 0 },
        { PCI_PRODUCT_ATI_RADEON_R420_JK,       RADEON_R420, 0 },
        { PCI_PRODUCT_ATI_RADEON_R420_JL,       RADEON_R420, 0 },
        { PCI_PRODUCT_ATI_RADEON_R420_JM,       RADEON_R420, 0 },
        { PCI_PRODUCT_ATI_RADEON_R420_JN,       RADEON_R420, RFB_MOB },
        { PCI_PRODUCT_ATI_RADEON_R420_JP,       RADEON_R420, 0 },
        { PCI_PRODUCT_ATI_RADEON_R423_UH,       RADEON_R420, 0 },
        { PCI_PRODUCT_ATI_RADEON_R423_UI,       RADEON_R420, 0 },
        { PCI_PRODUCT_ATI_RADEON_R423_UJ,       RADEON_R420, 0 },
        { PCI_PRODUCT_ATI_RADEON_R423_UK,       RADEON_R420, 0 },
        { PCI_PRODUCT_ATI_RADEON_R423_UQ,       RADEON_R420, 0 },
        { PCI_PRODUCT_ATI_RADEON_R423_UR,       RADEON_R420, 0 },
        { PCI_PRODUCT_ATI_RADEON_R423_UT,       RADEON_R420, 0 },
        { PCI_PRODUCT_ATI_RADEON_R423_5D57,     RADEON_R420, 0 },
        { PCI_PRODUCT_ATI_RADEON_R430_554F,     RADEON_R420, 0 },
#endif

        /* R5xx family */
        { 0x7240,       RADEON_R580, RFB_IS_AVIVO },    
        { 0, 0, 0 }
};

static struct {
        int divider;
        int mask;
} radeonfb_dividers[] = {
        { 16, 5 },
        { 12, 7 },
        {  8, 3 },
        {  6, 6 },
        {  4, 2 },
        {  3, 4 },
        {  2, 1 },
        {  1, 0 },
        {  0, 0 }
};

/*
 * This table taken from X11.
 */
static const struct {
        int                     family;
        struct radeon_tmds_pll  plls[4];
} radeonfb_tmds_pll[] = {
        { RADEON_R100,  {{12000, 0xa1b}, {-1, 0xa3f}}},
        { RADEON_RV100, {{12000, 0xa1b}, {-1, 0xa3f}}},
        { RADEON_RS100, {{0, 0}}},
        { RADEON_RV200, {{15000, 0xa1b}, {-1, 0xa3f}}},
        { RADEON_RS200, {{15000, 0xa1b}, {-1, 0xa3f}}},
        { RADEON_R200,  {{15000, 0xa1b}, {-1, 0xa3f}}},
        { RADEON_RV250, {{15500, 0x81b}, {-1, 0x83f}}},
        { RADEON_RS300, {{0, 0}}},
        { RADEON_RV280, {{13000, 0x400f4}, {15000, 0x400f7}, {-1, 0x40111}}},
        { RADEON_R300,  {{-1, 0xb01cb}}},
        { RADEON_R350,  {{-1, 0xb01cb}}},
        { RADEON_RV350, {{15000, 0xb0155}, {-1, 0xb01cb}}},
        { RADEON_RV380, {{15000, 0xb0155}, {-1, 0xb01cb}}},
        { RADEON_R420,  {{-1, 0xb01cb}}},
        { RADEON_R580,  {{-1, 0xb01cb}}}, /* XXX likely bogus */
};

#define RADEONFB_BACKLIGHT_MAX    255  /* Maximum backlight level. */


CFATTACH_DECL_NEW(radeonfb, sizeof (struct radeonfb_softc),
    radeonfb_match, radeonfb_attach, NULL, NULL);

static int
radeonfb_match(device_t parent, cfdata_t match, void *aux)
{
        const struct pci_attach_args    *pa = aux;
        int                     i;

        if (PCI_VENDOR(pa->pa_id) != PCI_VENDOR_ATI)
                return 0;

        for (i = 0; radeonfb_devices[i].devid; i++) {
                if (PCI_PRODUCT(pa->pa_id) == radeonfb_devices[i].devid)
                        return 100;     /* high to defeat VGA/VESA */
        }

        return 0;
}

static void
radeonfb_attach(device_t parent, device_t dev, void *aux)
{
        struct radeonfb_softc   *sc = device_private(dev);
        const struct pci_attach_args    *pa = aux;
        const char              *mptr;
        bus_size_t              bsz;
        pcireg_t                screg;
        int                     i, j, fg, bg, ul, flags;
        uint32_t                v;
        char                    memstr[16] = "";

        sc->sc_dev = dev;
        sc->sc_id = pa->pa_id;
        for (i = 0; radeonfb_devices[i].devid; i++) {
                if (PCI_PRODUCT(sc->sc_id) == radeonfb_devices[i].devid)
                        break;
        }

        pci_aprint_devinfo(pa, NULL);

        DPRINTF(("%s", prop_dictionary_externalize(device_properties(dev))));

        KASSERT(radeonfb_devices[i].devid != 0);
        sc->sc_pt = pa->pa_tag;
        sc->sc_iot = pa->pa_iot;
        sc->sc_pc = pa->pa_pc;
        sc->sc_family = radeonfb_devices[i].family;
        sc->sc_flags = radeonfb_devices[i].flags;
        sc->sc_bios = NULL;
        sc->sc_biossz = 0;

        /* enable memory and IO access */
        screg = pci_conf_read(sc->sc_pc, sc->sc_pt, PCI_COMMAND_STATUS_REG);
        screg |= PCI_COMMAND_IO_ENABLE | PCI_COMMAND_MEM_ENABLE;
        pci_conf_write(sc->sc_pc, sc->sc_pt, PCI_COMMAND_STATUS_REG, screg);

        /*
         * Some flags are general to entire chip families, and rather
         * than clutter up the table with them, we go ahead and set
         * them here.
         */
        switch (sc->sc_family) {
        case RADEON_RS100:
        case RADEON_RS200:
                sc->sc_flags |= RFB_IGP | RFB_RV100;
                break;

        case RADEON_RV100:
        case RADEON_RV200:
        case RADEON_RV250:
        case RADEON_RV280:
                sc->sc_flags |= RFB_RV100;
                break;

        case RADEON_RS300:
                sc->sc_flags |= RFB_SDAC | RFB_IGP | RFB_RV100;
                break;

        case RADEON_R300:
        case RADEON_RV350:
        case RADEON_R350:
        case RADEON_RV380:
        case RADEON_R420:
        case RADEON_R580:
                /* newer chips */
                sc->sc_flags |= RFB_R300;
                break;

        case RADEON_R100:
                sc->sc_flags |= RFB_NCRTC2;
                break;
        }

        if ((sc->sc_family == RADEON_RV200) ||
            (sc->sc_family == RADEON_RV250) ||
            (sc->sc_family == RADEON_RV280) ||
            (sc->sc_family == RADEON_RV350)) {
                bool inverted = 0;
                /* backlight level is linear */
                DPRINTF(("found RV* chip, backlight is supposedly linear\n"));
                prop_dictionary_get_bool(device_properties(sc->sc_dev),
                    "backlight_level_reverted", &inverted);
                if (inverted) {
                        DPRINTF(("nope, it's inverted\n"));
                        sc->sc_flags |= RFB_INV_BLIGHT;
                }
        } else
                sc->sc_flags |= RFB_INV_BLIGHT;

        /*
         * XXX: to support true multihead, this must change.
         */
        sc->sc_ndisplays = 1;

        /* XXX: */
        if (!HAS_CRTC2(sc)) {
                sc->sc_ndisplays = 1;
        }

        if (pci_mapreg_map(pa, RADEON_MAPREG_MMIO, PCI_MAPREG_TYPE_MEM, 0,
                &sc->sc_regt, &sc->sc_regh, &sc->sc_regaddr,
                &sc->sc_regsz) != 0) {
                aprint_error("%s: unable to map registers!\n", XNAME(sc));
                goto error;
        }

        if (pci_mapreg_info(sc->sc_pc, sc->sc_pt, PCI_MAPREG_ROM,
             PCI_MAPREG_TYPE_ROM, &sc->sc_romaddr, &sc->sc_romsz, &flags) != 0)
        {
                aprint_error("%s: unable to find ROM!\n", XNAME(sc));
                goto error;
        }
        sc->sc_romt = sc->sc_memt;

        sc->sc_mapped = TRUE;

        /* scratch register test... */
        if (radeonfb_scratch_test(sc, RADEON_BIOS_0_SCRATCH, 0x55555555) ||
            radeonfb_scratch_test(sc, RADEON_BIOS_0_SCRATCH, 0xaaaaaaaa)) {
                aprint_error("%s: scratch register test failed!\n", XNAME(sc));
                goto error;
        }

        PRINTREG(RADEON_CRTC_EXT_CNTL);
        PRINTREG(RADEON_CRTC_GEN_CNTL);
        PRINTREG(RADEON_CRTC2_GEN_CNTL);
        PRINTREG(RADEON_DISP_OUTPUT_CNTL);
        PRINTREG(RADEON_DAC_CNTL2);
        PRINTREG(RADEON_BIOS_4_SCRATCH);
        PRINTREG(RADEON_FP_GEN_CNTL);
        sc->sc_fp_gen_cntl = GET32(sc, RADEON_FP_GEN_CNTL);
        PRINTREG(RADEON_FP2_GEN_CNTL);
        PRINTREG(RADEON_TMDS_CNTL);
        PRINTREG(RADEON_TMDS_TRANSMITTER_CNTL);
        PRINTREG(RADEON_TMDS_PLL_CNTL);
        PRINTREG(RADEON_LVDS_GEN_CNTL);
        PRINTREG(RADEON_DISP_HW_DEBUG);
        if (!IS_AVIVO(sc)) {
                /*
                XXX: We can't print this, as it's not correctly aligned
                PRINTREG(RADEON_PIXCLKS_CNTL);
                */
                PRINTREG(RADEON_CRTC_H_SYNC_STRT_WID);
                PRINTREG(RADEON_FP_H_SYNC_STRT_WID);
                PRINTREG(RADEON_CRTC2_H_SYNC_STRT_WID);
                PRINTREG(RADEON_FP_H2_SYNC_STRT_WID);
        }
        radeonfb_i2c_init(sc);

        radeonfb_loadbios(sc, pa);

#ifdef  RADEONFB_BIOS_INIT
        if (radeonfb_bios_init(sc)) {
                aprint_error("%s: BIOS initialization failed\n", XNAME(sc));
        }
#endif

/*
 * XXX
 * This was if (IS_RV100()), which is set for all pre-R3xx chips.
 * I suspect this only makes sense on Sun XVR-100 with firmware that doesn't
 * support DVI, so for now let's restrict it to only actual RV100
 */
        if (sc->sc_family == RADEON_RV100)
                PUT32(sc, RADEON_TMDS_PLL_CNTL, 0xa27);

        /* XXX
         * according to xf86-video-radeon R3xx has this bit backwards
         *
         * this must happen after radeonfb_bios_init(): the BIOS init
         * tables rewrite TMDS_TRANSMITTER_CNTL and would disable the
         * transmitter PLL again
         */
        if (IS_R300(sc)) {
                PATCH32(sc, RADEON_TMDS_TRANSMITTER_CNTL,
                    0,
                    ~(RADEON_TMDS_TRANSMITTER_PLLEN | RADEON_TMDS_TRANSMITTER_PLLRST));
        } else {
                PATCH32(sc, RADEON_TMDS_TRANSMITTER_CNTL,
                    RADEON_TMDS_TRANSMITTER_PLLEN,
                    ~(RADEON_TMDS_TRANSMITTER_PLLEN | RADEON_TMDS_TRANSMITTER_PLLRST));
        }

        if (radeonfb_getclocks(sc)) {
                aprint_error("%s: Unable to get reference clocks from BIOS\n",
                    XNAME(sc));
                goto error;
        }

        if (radeonfb_gettmds(sc)) {
                aprint_error("%s: Unable to identify TMDS PLL settings\n",
                    XNAME(sc));
                goto error;
        }

        aprint_verbose("%s: refclk = %d.%03d MHz, refdiv = %d "
            "minpll = %d, maxpll = %d\n", XNAME(sc),
            (int)sc->sc_refclk / 1000, (int)sc->sc_refclk % 1000,
            (int)sc->sc_refdiv, (int)sc->sc_minpll, (int)sc->sc_maxpll);

        radeonfb_getconnectors(sc);

        radeonfb_set_fbloc(sc);

        /* 64 MB should be enough -- more just wastes map entries */
        if (sc->sc_memsz > (64 << 20)) {
                snprintf(memstr, 16, " %d MB,", (int)sc->sc_memsz >> 20);
                sc->sc_memsz = (64 << 20);
        }

        for (i = 0; radeonfb_limits[i].size; i++) {
                if (sc->sc_memsz >= radeonfb_limits[i].size) {
                        sc->sc_maxx = radeonfb_limits[i].maxx;
                        sc->sc_maxy = radeonfb_limits[i].maxy;
                        sc->sc_maxbpp = radeonfb_limits[i].maxbpp;
                        /* framebuffer offset, start at a 4K page */
                        sc->sc_fboffset = sc->sc_memsz /
                            radeonfb_limits[i].maxdisp;
                        /*
                         * we use the fbsize to figure out where we can store
                         * things like cursor data.
                         */
                        sc->sc_fbsize =
                            ROUNDUP(ROUNDUP(sc->sc_maxx * sc->sc_maxbpp / 8 ,
                                        RADEON_STRIDEALIGN) * sc->sc_maxy,
                                4096);
                        break;
                }
        }


        radeonfb_init_misc(sc);

        /* program the DAC wirings */
        for (i = 0; i < (HAS_CRTC2(sc) ? 2 : 1); i++) {
                switch (sc->sc_ports[i].rp_dac_type) {
                case RADEON_DAC_PRIMARY:
                        PATCH32(sc, RADEON_DAC_CNTL2,
                            i ? RADEON_DAC2_DAC_CLK_SEL : 0,
                            ~RADEON_DAC2_DAC_CLK_SEL);
                        break;
                case RADEON_DAC_TVDAC:
                        /* we always use the TVDAC to drive a secondary analog
                         * CRT for now.  if we ever support TV-out this will
                         * have to change.
                         */
                        SET32(sc, RADEON_DAC_CNTL2,
                            RADEON_DAC2_DAC2_CLK_SEL);
                        PATCH32(sc, RADEON_DISP_HW_DEBUG,
                            i ? 0 : RADEON_CRT2_DISP1_SEL,
                            ~RADEON_CRT2_DISP1_SEL);
                        /* we're using CRTC2 for the 2nd port */
                        if (sc->sc_ports[i].rp_number == 1) {
                                PATCH32(sc, RADEON_DISP_OUTPUT_CNTL,
                                    RADEON_DISP_DAC2_SOURCE_CRTC2,
                                    ~RADEON_DISP_DAC2_SOURCE_MASK);
                        }
                        break;
                }
                DPRINTF(("%s: port %d tmds type %d\n", __func__, i,
                    sc->sc_ports[i].rp_tmds_type));
                switch (sc->sc_ports[i].rp_tmds_type) {
                case RADEON_TMDS_INT:
                        /* point FP0 at the CRTC this port uses */
                        DPRINTF(("%s: plugging internal TMDS into CRTC %d\n",
                            __func__, sc->sc_ports[i].rp_number));
                        if (IS_R300(sc)) {
                                PATCH32(sc, RADEON_FP_GEN_CNTL,
                                    sc->sc_ports[i].rp_number ?
                                      R200_FP_SOURCE_SEL_CRTC2 :
                                      R200_FP_SOURCE_SEL_CRTC1,
                                    ~R200_FP_SOURCE_SEL_MASK);
                        } else {
                                PATCH32(sc, RADEON_FP_GEN_CNTL,
                                    sc->sc_ports[i].rp_number ?
                                      RADEON_FP_SEL_CRTC2 :
                                      RADEON_FP_SEL_CRTC1,
                                    ~RADEON_FP_SEL_MASK);
                        }
                        break;
                case RADEON_TMDS_EXT:
                        /* point FP2 at the CRTC this port uses */
                        DPRINTF(("%s: plugging external TMDS into CRTC %d\n",
                            __func__, sc->sc_ports[i].rp_number));
                        if (IS_R300(sc)) {
                                PATCH32(sc, RADEON_FP2_GEN_CNTL,
                                    sc->sc_ports[i].rp_number ?
                                      R200_FP2_SOURCE_SEL_CRTC2 :
                                      R200_FP2_SOURCE_SEL_CRTC1,
                                    ~R200_FP2_SOURCE_SEL_CRTC2);
                        } else {
                                PATCH32(sc, RADEON_FP2_GEN_CNTL,
                                    sc->sc_ports[i].rp_number ?
                                      RADEON_FP2_SRC_SEL_CRTC2 :
                                      RADEON_FP2_SRC_SEL_CRTC1,
                                    ~RADEON_FP2_SRC_SEL_CRTC2);
                        }
                        break;
                }
        }
        PRINTREG(RADEON_DAC_CNTL2);
        PRINTREG(RADEON_DISP_HW_DEBUG);

        PRINTREG(RADEON_DAC_CNTL);
        /* other DAC programming */
        v = GET32(sc, RADEON_DAC_CNTL);
        v &= (RADEON_DAC_RANGE_CNTL_MASK | RADEON_DAC_BLANKING);
        v |= RADEON_DAC_MASK_ALL | RADEON_DAC_8BIT_EN;
        PUT32(sc, RADEON_DAC_CNTL, v);
        PRINTREG(RADEON_DAC_CNTL);

        /* XXX: this may need more investigation */
        PUT32(sc, RADEON_TV_DAC_CNTL, 0x00280203);
        PRINTREG(RADEON_TV_DAC_CNTL);

        /* enable TMDS */
        SET32(sc, RADEON_FP_GEN_CNTL,
            RADEON_FP_TMDS_EN |
                RADEON_FP_CRTC_DONT_SHADOW_VPAR |
                RADEON_FP_CRTC_DONT_SHADOW_HEND);
        /*
         * XXX
         * no idea why this is necessary - if I do not clear this bit on my
         * iBook G4 the screen remains black, even though it's already clear.
         * It needs to be set on my Sun XVR-100 for the DVI port to work
         * TODO:
         * see if this is still necessary now that CRTCs, DACs and outputs are
         * getting wired up in a halfway sane way
         *
         * Only do this when we have no BIOS connector table to go by (Mac
         * and Sun firmware-initialized cards). On cards never POSTed by
         * firmware sc_fp_gen_cntl holds meaningless reset defaults, duh.
         */
        if (sc->sc_biossz == 0) {
                if (sc->sc_fp_gen_cntl & RADEON_FP_SEL_CRTC2) {
                        SET32(sc, RADEON_FP_GEN_CNTL, RADEON_FP_SEL_CRTC2);
                } else {
                        CLR32(sc, RADEON_FP_GEN_CNTL, RADEON_FP_SEL_CRTC2);
                }
        }

        /*
         * we use bus_space_map instead of pci_mapreg, because we don't
         * need the full aperature space.  no point in wasting virtual
         * address space we don't intend to use, right?
         */
        if ((sc->sc_memsz < (4096 * 1024)) ||
            (pci_mapreg_info(sc->sc_pc, sc->sc_pt, RADEON_MAPREG_VRAM,
                PCI_MAPREG_TYPE_MEM, &sc->sc_memaddr, &bsz, NULL) != 0) ||
            (bsz < sc->sc_memsz)) {
                sc->sc_memsz = 0;
                aprint_error("%s: Bad frame buffer configuration\n",
                    XNAME(sc));
                goto error;
        }

        sc->sc_memt = pa->pa_memt;
        if (bus_space_map(sc->sc_memt, sc->sc_memaddr, sc->sc_memsz,
                BUS_SPACE_MAP_LINEAR | BUS_SPACE_MAP_PREFETCHABLE,
                &sc->sc_memh) != 0) {
                sc->sc_memsz = 0;
                aprint_error("%s: Unable to map frame buffer\n", XNAME(sc));
                goto error;
        }

        aprint_normal("%s:%s %d MB aperture at 0x%08x, "
            "%d KB registers at 0x%08x\n", XNAME(sc), memstr,
            (int)sc->sc_memsz >> 20, (unsigned)sc->sc_memaddr,
            (int)sc->sc_regsz >> 10, (unsigned)sc->sc_regaddr);

        /* setup default video mode from devprop (allows PROM override) */
        sc->sc_defaultmode = radeonfb_default_mode;
        if (prop_dictionary_get_string(device_properties(sc->sc_dev),
            "videomode", &mptr)) {

                strncpy(sc->sc_modebuf, mptr, sizeof(sc->sc_modebuf));
                sc->sc_defaultmode = sc->sc_modebuf;
        }

        /* initialize some basic display parameters */
        for (i = 0; i < sc->sc_ndisplays; i++) {
                struct radeonfb_display *dp = &sc->sc_displays[i];
                struct rasops_info *ri;
                long defattr;
                struct wsemuldisplaydev_attach_args aa;

                /*
                 * Figure out how many "displays" (desktops) we are going to
                 * support.  If more than one, then each CRTC gets its own
                 * programming.
                 *
                 * XXX: this code needs to change to support mergedfb.
                 * XXX: would be nice to allow this to be overridden
                 */
                if (HAS_CRTC2(sc) && (sc->sc_ndisplays == 1)) {
                        DPRINTF(("dual crtcs!\n"));
                        /*
                         * Only wire up CRTCs for ports that have a physical
                         * connector.
                         */
                        dp->rd_ncrtcs = 0;
                        for (j = 0; j < 2; j++) {
                                if (sc->sc_ports[j].rp_conn_type ==
                                    RADEON_CONN_NONE)
                                        continue;
                                dp->rd_crtcs[dp->rd_ncrtcs].rc_port =
                                    &sc->sc_ports[j];
                                dp->rd_crtcs[dp->rd_ncrtcs].rc_number =
                                    sc->sc_ports[j].rp_number;
                                dp->rd_ncrtcs++;
                        }
                        if (dp->rd_ncrtcs == 0) {
                                /* no connector info at all, be safe */
                                dp->rd_ncrtcs = 1;
                                dp->rd_crtcs[0].rc_port = &sc->sc_ports[0];
                                dp->rd_crtcs[0].rc_number =
                                    sc->sc_ports[0].rp_number;
                        }
                } else {
                        dp->rd_ncrtcs = 1;
                        dp->rd_crtcs[0].rc_port =
                            &sc->sc_ports[i];
                        dp->rd_crtcs[0].rc_number = sc->sc_ports[i].rp_number;
                }

                dp->rd_softc = sc;
                dp->rd_wsmode = WSDISPLAYIO_MODE_EMUL;
                dp->rd_bpp = RADEONFB_DEFAULT_DEPTH;    /* XXX */

                /* for text mode, we pick a resolution that won't
                 * require panning */
                radeonfb_pickres(dp, &dp->rd_virtx, &dp->rd_virty, 0);

                aprint_normal("%s: display %d: "
                    "initial virtual resolution %dx%d at %d bpp\n",
                    XNAME(sc), i, dp->rd_virtx, dp->rd_virty, dp->rd_bpp);
                aprint_normal_dev(sc->sc_dev, "using %d MB per display\n",
                    sc->sc_fboffset >> 20);
                /* now select the *video mode* that we will use */
                for (j = 0; j < dp->rd_ncrtcs; j++) {
                        const struct videomode *vmp;
                        vmp = radeonfb_port_mode(sc, dp->rd_crtcs[j].rc_port,
                            dp->rd_virtx, dp->rd_virty);

                        /*
                         * virtual resolution should be at least as high as
                         * physical
                         */
                        if (dp->rd_virtx < vmp->hdisplay ||
                            dp->rd_virty < vmp->vdisplay) {
                                dp->rd_virtx = vmp->hdisplay;
                                dp->rd_virty = vmp->vdisplay;
                        }

                        dp->rd_crtcs[j].rc_videomode = *vmp;
                        printf("%s: port %d: physical %dx%d %dHz\n",
                            XNAME(sc), j, vmp->hdisplay, vmp->vdisplay,
                            DIVIDE(DIVIDE(vmp->dot_clock * 1000,
                                       vmp->htotal), vmp->vtotal));
                }

                /* N.B.: radeon wants 64-byte aligned stride */
                dp->rd_stride = dp->rd_virtx * dp->rd_bpp / 8;
                dp->rd_stride = ROUNDUP(dp->rd_stride, RADEON_STRIDEALIGN);

                dp->rd_offset = sc->sc_fboffset * i;
                dp->rd_fbptr = (vaddr_t)bus_space_vaddr(sc->sc_memt,
                    sc->sc_memh) + dp->rd_offset;
                dp->rd_curoff = sc->sc_fboffset - 16384; /* 16KB cursor space */
                dp->rd_curptr = dp->rd_fbptr + dp->rd_curoff;

                DPRINTF(("fpbtr = %p\n", (void *)dp->rd_fbptr));

                switch (dp->rd_bpp) {
                case 8:
                        dp->rd_format = 2;
                        break;
                case 32:
                        dp->rd_format = 6;
                        break;
                default:
                        aprint_error("%s: bad depth %d\n", XNAME(sc),
                            dp->rd_bpp);
                        goto error;
                }
                DPRINTF(("init engine\n"));
                /* XXX: this seems suspicious - per display engine
                   initialization? */

                radeonfb_modeswitch(dp);
                radeonfb_engine_init(dp);

                /* copy the template into place */
                dp->rd_wsscreens_storage[0] = radeonfb_stdscreen;
                dp->rd_wsscreens = dp->rd_wsscreens_storage;

                /* and make up the list */
                dp->rd_wsscreenlist.nscreens = 1;
                dp->rd_wsscreenlist.screens = (void *)&dp->rd_wsscreens;

                vcons_init(&dp->rd_vd, dp, dp->rd_wsscreens,
                    &radeonfb_accessops);

                dp->rd_vd.init_screen = radeonfb_init_screen;

                dp->rd_console = 0;
                prop_dictionary_get_bool(device_properties(sc->sc_dev),
                    "is_console", &dp->rd_console);

                dp->rd_vscreen.scr_flags |= VCONS_SCREEN_IS_STATIC;


                vcons_init_screen(&dp->rd_vd, &dp->rd_vscreen,
                    dp->rd_console, &defattr);

                ri = &dp->rd_vscreen.scr_ri;

                /* clear the screen */
                rasops_unpack_attr(defattr, &fg, &bg, &ul);
                dp->rd_bg = ri->ri_devcmap[bg & 0xf];
                radeonfb_rectfill(dp, 0, 0, ri->ri_width, ri->ri_height,
                    dp->rd_bg);

                dp->rd_wsscreens->textops = &ri->ri_ops;
                dp->rd_wsscreens->capabilities = ri->ri_caps;
                dp->rd_wsscreens->nrows = ri->ri_rows;
                dp->rd_wsscreens->ncols = ri->ri_cols;

#ifdef SPLASHSCREEN
                dp->rd_splash.si_depth = ri->ri_depth;
                dp->rd_splash.si_bits = ri->ri_bits;
                dp->rd_splash.si_hwbits = ri->ri_hwbits;
                dp->rd_splash.si_width = ri->ri_width;
                dp->rd_splash.si_height = ri->ri_height;
                dp->rd_splash.si_stride = ri->ri_stride;
                dp->rd_splash.si_fillrect = NULL;
#endif
                dp->rd_gc.gc_bitblt = radeonfb_bitblt;
                dp->rd_gc.gc_rectfill = radeonfb_rectfill_a;
                dp->rd_gc.gc_rop = RADEON_ROP3_S;
                dp->rd_gc.gc_blitcookie = dp;
                /*
                 * use memory between framebuffer and cursor area as glyph
                 * cache, cap at 4096 lines
                 */
                glyphcache_init(&dp->rd_gc, dp->rd_virty + 4,
                    uimin(4096, 
                        (dp->rd_curoff / dp->rd_stride) - (dp->rd_virty + 4)),
                    dp->rd_virtx,
                    ri->ri_font->fontwidth,
                    ri->ri_font->fontheight,
                    defattr);
                dp->rd_vd.show_screen_cookie = &dp->rd_gc;
                dp->rd_vd.show_screen_cb = glyphcache_adapt;

                if (dp->rd_console) {

                        wsdisplay_cnattach(dp->rd_wsscreens, ri, 0, 0,
                            defattr);
#ifdef SPLASHSCREEN
                        if (splash_render(&dp->rd_splash,
                            SPLASH_F_CENTER|SPLASH_F_FILL) == 0)
                                SCREEN_DISABLE_DRAWING(&dp->rd_vscreen);
                        else
#endif
                                vcons_replay_msgbuf(&dp->rd_vscreen);
                } else {

                        /*
                         * since we're not the console we can postpone
                         * the rest until someone actually allocates a
                         * screen for us.  but we do clear the screen
                         * at least.
                         */
                        memset(ri->ri_bits, 0, 1024);

#ifdef SPLASHSCREEN
                        if (splash_render(&dp->rd_splash,
                            SPLASH_F_CENTER|SPLASH_F_FILL) == 0)
                                SCREEN_DISABLE_DRAWING(&dp->rd_vscreen);
#endif
                }

                aa.console = dp->rd_console;
                aa.scrdata = &dp->rd_wsscreenlist;
                aa.accessops = &radeonfb_accessops;
                aa.accesscookie = &dp->rd_vd;

                config_found(sc->sc_dev, &aa, wsemuldisplaydevprint,
                    CFARGS(.iattr = "wsemuldisplaydev"));

                radeonfb_blank(dp, 0);

                /* Initialise delayed lvds operations for backlight. */
                callout_init(&dp->rd_bl_lvds_co, 0);
                callout_setfunc(&dp->rd_bl_lvds_co,
                                radeonfb_lvds_callout, dp);

                dp->rd_bl_on = 1;
                if (sc->sc_flags & RFB_MOB) {
                        dp->rd_bl_level = radeonfb_get_backlight(dp);
                } else
                        dp->rd_bl_level = 128;

                radeonfb_set_backlight(dp, dp->rd_bl_level);
        }
        for (i = 0; i < RADEON_NDISPLAYS; i++)
                radeonfb_init_palette(&sc->sc_displays[i]);

        if (HAS_CRTC2(sc) && !IS_AVIVO(sc)) {
                CLR32(sc, RADEON_CRTC2_GEN_CNTL, RADEON_CRTC2_DISP_DIS);
        }

        if (!IS_AVIVO(sc)) {
                CLR32(sc, RADEON_CRTC_EXT_CNTL, RADEON_CRTC_DISPLAY_DIS);
                SET32(sc, RADEON_FP_GEN_CNTL, RADEON_FP_FPON);
        }

        pmf_event_register(dev, PMFE_DISPLAY_BRIGHTNESS_UP,
            radeonfb_brightness_up, TRUE);
        pmf_event_register(dev, PMFE_DISPLAY_BRIGHTNESS_DOWN,
            radeonfb_brightness_down, TRUE);

        /*
         * if we attach a DRM we need to unmap registers in
         * WSDISPLAYIO_MODE_MAPPED, since this keeps us from doing things like
         * screen blanking we only do it if needed
         */
        sc->sc_needs_unmap = 
            (config_found(dev, aux, radeonfb_drm_print,
                          CFARGS(.iattr = "drm")) != NULL);
        DPRINTF(("needs_unmap: %d\n", sc->sc_needs_unmap));

        if (!IS_AVIVO(sc)) {
                PRINTREG(RADEON_CRTC_EXT_CNTL);
                PRINTREG(RADEON_CRTC_GEN_CNTL);
                PRINTREG(RADEON_CRTC2_GEN_CNTL);
                PRINTREG(RADEON_DISP_OUTPUT_CNTL);
                PRINTREG(RADEON_DAC_CNTL2);
                PRINTREG(RADEON_FP_GEN_CNTL);
                PRINTREG(RADEON_FP2_GEN_CNTL);
                PRINTREG(RADEON_TMDS_CNTL);
                PRINTREG(RADEON_TMDS_TRANSMITTER_CNTL);
                PRINTREG(RADEON_TMDS_PLL_CNTL);
                /*
                XXX: We can't print this, as it's not correctly aligned
                PRINTREG(RADEON_PIXCLKS_CNTL);
                */
        }
        return;

error:
        if (sc->sc_biossz)
                free(sc->sc_bios, M_DEVBUF);

        if (sc->sc_regsz)
                bus_space_unmap(sc->sc_regt, sc->sc_regh, sc->sc_regsz);

        if (sc->sc_memsz)
                bus_space_unmap(sc->sc_memt, sc->sc_memh, sc->sc_memsz);
}

static void
radeonfb_map(struct radeonfb_softc *sc)
{
        if (!sc->sc_mapped) {
                if (bus_space_map(sc->sc_regt, sc->sc_regaddr, sc->sc_regsz, 0,
                    &sc->sc_regh) != 0) {
                        aprint_error_dev(sc->sc_dev, 
                            "unable to map registers!\n");
                        return;
                }
                if (bus_space_map(sc->sc_memt, sc->sc_memaddr, sc->sc_memsz,
                    BUS_SPACE_MAP_LINEAR | BUS_SPACE_MAP_PREFETCHABLE,
                    &sc->sc_memh) != 0) {
                        sc->sc_memsz = 0;
                        aprint_error_dev(sc->sc_dev,
                            "Unable to map frame buffer\n");
                        return;
                }
                sc->sc_mapped = TRUE;
        }
}

static void
radeonfb_unmap(struct radeonfb_softc *sc)
{
        if (!sc->sc_needs_unmap)
                return;

        if (sc->sc_mapped) {
                bus_space_unmap(sc->sc_regt, sc->sc_regh, sc->sc_regsz);
                bus_space_unmap(sc->sc_memt, sc->sc_memh, sc->sc_memsz);
                sc->sc_mapped = FALSE;
        }
}

static int
radeonfb_drm_print(void *aux, const char *pnp)
{
        if (pnp)
                aprint_normal("drm at %s", pnp);
        return (UNCONF);
}

int
radeonfb_ioctl(void *v, void *vs,
    unsigned long cmd, void *d, int flag, struct lwp *l)
{
        struct vcons_data       *vd;
        struct radeonfb_display *dp;
        struct radeonfb_softc   *sc;
        struct wsdisplay_param  *param;
        struct vcons_screen     *ms;

        vd = (struct vcons_data *)v;
        ms = vd->active;
        dp = (struct radeonfb_display *)vd->cookie;
        sc = dp->rd_softc;

        /* can't do these without registers being mapped */
        if (!sc->sc_mapped) {
                switch (cmd) {
                        case WSDISPLAYIO_GVIDEO:
                        case WSDISPLAYIO_SVIDEO:
                        case WSDISPLAYIO_GETCMAP:
                        case WSDISPLAYIO_PUTCMAP:
                        case WSDISPLAYIO_SCURSOR:
                        case WSDISPLAYIO_GCURPOS:
                        case WSDISPLAYIO_SCURPOS:
                        case WSDISPLAYIO_SETPARAM:
                                return EINVAL;
                }
        }

        switch (cmd) {
        case WSDISPLAYIO_GTYPE:
                *(unsigned *)d = WSDISPLAY_TYPE_PCIMISC;
                return 0;

        case WSDISPLAYIO_GINFO:
                if (vd->active != NULL) {
                        struct wsdisplay_fbinfo *fb;
                        fb = (struct wsdisplay_fbinfo *)d;
                        fb->width = dp->rd_virtx; 
                        fb->height = dp->rd_virty; 
                        fb->depth = dp->rd_bpp;
                        fb->cmsize = 256;
                        return 0;
                } else
                        return ENODEV;
        case WSDISPLAYIO_GVIDEO:
                if (radeonfb_isblank(dp))
                        *(unsigned *)d = WSDISPLAYIO_VIDEO_OFF;
                else
                        *(unsigned *)d = WSDISPLAYIO_VIDEO_ON;
                return 0;

        case WSDISPLAYIO_SVIDEO:
                if (dp->rd_wsmode != WSDISPLAYIO_MODE_MAPPED) {
                        radeonfb_blank(dp,
                            (*(unsigned int *)d == WSDISPLAYIO_VIDEO_OFF));
                        radeonfb_switch_backlight(dp,
                            (*(unsigned int *)d == WSDISPLAYIO_VIDEO_ON));
                }
                pmf_event_inject(NULL, 
                    (*(unsigned int *)d == WSDISPLAYIO_VIDEO_ON) ?
                     PMFE_DISPLAY_ON : PMFE_DISPLAY_OFF);
                return 0;

        case WSDISPLAYIO_GETCMAP:
                if (dp->rd_bpp == 8)
                        return radeonfb_getcmap(dp,
                            (struct wsdisplay_cmap *)d);
                return EINVAL;
                
        case WSDISPLAYIO_PUTCMAP:
                if (dp->rd_bpp == 8)
                        return radeonfb_putcmap(dp,
                            (struct wsdisplay_cmap *)d);
                return EINVAL;
                
        case WSDISPLAYIO_LINEBYTES:
                *(unsigned *)d = dp->rd_stride;
                return 0;

        case WSDISPLAYIO_SMODE:
                if (*(int *)d != dp->rd_wsmode) {
                        dp->rd_wsmode = *(int *)d;
                        if ((dp->rd_wsmode == WSDISPLAYIO_MODE_EMUL) ||
                            (dp->rd_wsmode == WSDISPLAYIO_MODE_DUMBFB))
                                radeonfb_map(sc);
                        
                        if ((dp->rd_wsmode == WSDISPLAYIO_MODE_EMUL) &&
                            (dp->rd_vd.active)) {
                                radeonfb_engine_init(dp);
                                glyphcache_wipe(&dp->rd_gc);
                                radeonfb_init_palette(dp);
                                radeonfb_modeswitch(dp);
                                radeonfb_rectfill(dp, 0, 0, dp->rd_virtx,
                                    dp->rd_virty, dp->rd_bg);
                                vcons_redraw_screen(dp->rd_vd.active);
                        }
                        if (dp->rd_wsmode == WSDISPLAYIO_MODE_MAPPED)
                                radeonfb_unmap(sc);
                }
                return 0;

        case WSDISPLAYIO_GCURMAX:
                ((struct wsdisplay_curpos *)d)->x = RADEON_CURSORMAXX;
                ((struct wsdisplay_curpos *)d)->y = RADEON_CURSORMAXY;
                return 0;

        case WSDISPLAYIO_SCURSOR:
                return radeonfb_set_cursor(dp, (struct wsdisplay_cursor *)d);

        case WSDISPLAYIO_GCURSOR:
                return EPASSTHROUGH;

        case WSDISPLAYIO_GCURPOS:
                ((struct wsdisplay_curpos *)d)->x = dp->rd_cursor.rc_pos.x;
                ((struct wsdisplay_curpos *)d)->y = dp->rd_cursor.rc_pos.y;
                return 0;

        case WSDISPLAYIO_SCURPOS:
                return radeonfb_set_curpos(dp, (struct wsdisplay_curpos *)d);

        case WSDISPLAYIO_SSPLASH:
#if defined(SPLASHSCREEN)
                if (*(int *)d == 1) {
                        SCREEN_DISABLE_DRAWING(&dp->rd_vscreen);
                        splash_render(&dp->rd_splash,
                            SPLASH_F_CENTER|SPLASH_F_FILL);
                } else
                        SCREEN_ENABLE_DRAWING(&dp->rd_vscreen);
                return 0;
#else
                return ENODEV;
#endif
        case WSDISPLAYIO_GETPARAM:
                param = (struct wsdisplay_param *)d;
                switch (param->param) {
                case WSDISPLAYIO_PARAM_BRIGHTNESS:
                        param->min = 0;
                        param->max = 255;
                        param->curval = dp->rd_bl_level;
                        return 0;
                case WSDISPLAYIO_PARAM_BACKLIGHT:
                        param->min = 0;
                        param->max = RADEONFB_BACKLIGHT_MAX;
                        param->curval = dp->rd_bl_on;
                        return 0;
                }
                return EPASSTHROUGH;

        case WSDISPLAYIO_SETPARAM:
                param = (struct wsdisplay_param *)d;
                switch (param->param) {
                case WSDISPLAYIO_PARAM_BRIGHTNESS:
                        radeonfb_set_backlight(dp, param->curval);
                        return 0;
                case WSDISPLAYIO_PARAM_BACKLIGHT:
                        radeonfb_switch_backlight(dp,  param->curval);
                        return 0;
                }
                return EPASSTHROUGH;

        /* PCI config read/write passthrough. */
        case PCI_IOC_CFGREAD:
        case PCI_IOC_CFGWRITE:
                return pci_devioctl(sc->sc_pc, sc->sc_pt, cmd, d, flag, l);

        case WSDISPLAYIO_GET_BUSID:
                return wsdisplayio_busid_pci(sc->sc_dev, sc->sc_pc,
                    sc->sc_pt, d);

        case WSDISPLAYIO_GET_EDID: {
                struct wsdisplayio_edid_info *ei = d;
                return wsdisplayio_get_edid(sc->sc_dev, ei);
        }

        case WSDISPLAYIO_GET_FBINFO: {
                struct wsdisplayio_fbinfo *fbi = d;
                return wsdisplayio_get_fbinfo(&ms->scr_ri, fbi);
        }

        default:
                return EPASSTHROUGH;
        }
}

paddr_t
radeonfb_mmap(void *v, void *vs, off_t offset, int prot)
{
        struct vcons_data       *vd;
        struct radeonfb_display *dp;
        struct radeonfb_softc   *sc;
        paddr_t                 pa;

        vd = (struct vcons_data *)v;
        dp = (struct radeonfb_display *)vd->cookie;
        sc = dp->rd_softc;

        if ((offset >= 0) && (offset < (dp->rd_virty * dp->rd_stride))) {
                pa = bus_space_mmap(sc->sc_memt,
                    sc->sc_memaddr, dp->rd_offset + offset,
                    prot, BUS_SPACE_MAP_LINEAR | BUS_SPACE_MAP_PREFETCHABLE);
                return pa;
        }

        /*
         * 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_error_dev(sc->sc_dev, "mmap() rejected.\n");
                return -1;
        }

        if ((offset >= sc->sc_regaddr) && 
            (offset < sc->sc_regaddr + sc->sc_regsz)) {
                return bus_space_mmap(sc->sc_regt, offset, 0, prot, 
                    BUS_SPACE_MAP_LINEAR);
        }

        if ((offset >= sc->sc_memaddr) && 
            (offset < sc->sc_memaddr + sc->sc_memsz)) {
                return bus_space_mmap(sc->sc_memt, offset, 0, prot, 
                    BUS_SPACE_MAP_LINEAR);
        }

        if ((offset >= sc->sc_romaddr) && 
            (offset < sc->sc_romaddr + sc->sc_romsz)) {
                return bus_space_mmap(sc->sc_memt, offset, 0, prot, 
                    BUS_SPACE_MAP_LINEAR);
        }

#ifdef PCI_MAGIC_IO_RANGE
        /* allow mapping of IO space */
        if ((offset >= PCI_MAGIC_IO_RANGE) &&
            (offset < PCI_MAGIC_IO_RANGE + 0x10000)) {
                pa = bus_space_mmap(sc->sc_iot, offset - PCI_MAGIC_IO_RANGE,
                    0, prot, 0);        
                return pa;
        }       
#endif /* PCI_MAGIC_IO_RANGE */

        return -1;
}

static void
radeonfb_loadbios(struct radeonfb_softc *sc, const struct pci_attach_args *pa)
{
        bus_space_tag_t         romt;
        bus_space_handle_t      romh, biosh;
        bus_size_t              romsz;
        bus_addr_t              ptr;
        uint32_t                busctl, crtcg, crtc2g = 0, viphctl, seprom, extc;
        int                     bios_voodoo = 0;

        if (pci_mapreg_map(pa, PCI_MAPREG_ROM, PCI_MAPREG_TYPE_ROM,
                BUS_SPACE_MAP_PREFETCHABLE, &romt, &romh, NULL, &romsz) != 0) {
                aprint_verbose("%s: unable to map BIOS!\n", XNAME(sc));
                return;
        }

        pci_find_rom(pa, romt, romh, romsz, PCI_ROM_CODE_TYPE_X86, &biosh,
            &sc->sc_biossz);
        if (sc->sc_biossz != 0) goto foundit;

        aprint_verbose("trying to read disabled BIOS...\n");

        bios_voodoo = 1;
        seprom = radeonfb_get32(sc, RADEON_SEPROM_CNTL1);
        radeonfb_put32(sc, RADEON_SEPROM_CNTL1,
            (seprom & ~RADEON_SCK_PRESCALE_MASK) |
            (0xc << RADEON_SCK_PRESCALE_SHIFT));
        viphctl = radeonfb_get32(sc, RADEON_VIPH_CONTROL);
        radeonfb_put32(sc, RADEON_VIPH_CONTROL, viphctl & ~RADEON_VIPH_EN);
        busctl = radeonfb_get32(sc, RADEON_BUS_CNTL);
        radeonfb_put32(sc, RADEON_BUS_CNTL, busctl & ~RADEON_BUS_BIOS_DIS_ROM);
        crtcg = radeonfb_get32(sc, RADEON_CRTC_GEN_CNTL);
        radeonfb_put32(sc, RADEON_CRTC_GEN_CNTL, ((crtcg & ~RADEON_CRTC_EN) |
                                      (RADEON_CRTC_DISP_REQ_EN_B |
                                       RADEON_CRTC_EXT_DISP_EN)));
        if (HAS_CRTC2(sc)) {    
                crtc2g = radeonfb_get32(sc, RADEON_CRTC2_GEN_CNTL);
                radeonfb_put32(sc, RADEON_CRTC2_GEN_CNTL, 
                    (crtc2g & ~RADEON_CRTC2_EN) |
                    RADEON_CRTC2_DISP_REQ_EN_B);
        }
        extc = radeonfb_get32(sc, RADEON_CRTC_EXT_CNTL);
        radeonfb_put32(sc, RADEON_CRTC_EXT_CNTL, (extc & ~RADEON_CRTC_CRT_ON) |
                                      (RADEON_CRTC_SYNC_TRISTAT |
                                       RADEON_CRTC_DISPLAY_DIS));
        pci_find_rom(pa, romt, romh, romsz, PCI_ROM_CODE_TYPE_X86, &biosh,
            &sc->sc_biossz);

foundit:
        if (sc->sc_biossz > 0) {
                sc->sc_bios = malloc(sc->sc_biossz, M_DEVBUF, M_WAITOK);
                bus_space_read_region_1(romt, biosh, 0, sc->sc_bios,
                    sc->sc_biossz);
        }

        if (bios_voodoo != 0) {
                radeonfb_put32(sc, RADEON_CRTC_EXT_CNTL, extc);
                if (HAS_CRTC2(sc)) {    
                        radeonfb_put32(sc, RADEON_CRTC2_GEN_CNTL, crtc2g);
                }
                radeonfb_put32(sc, RADEON_CRTC_GEN_CNTL, crtcg);
                radeonfb_put32(sc, RADEON_BUS_CNTL, busctl);
                radeonfb_put32(sc, RADEON_VIPH_CONTROL, viphctl);
                radeonfb_put32(sc, RADEON_SEPROM_CNTL1, seprom);
        }

        /* unmap the PCI expansion rom */
        bus_space_unmap(romt, romh, romsz);
        
        /* turn off rom decoder now */
        pci_conf_write(pa->pa_pc, pa->pa_tag, PCI_MAPREG_ROM,
            pci_conf_read(pa->pa_pc, pa->pa_tag, PCI_MAPREG_ROM) &
            ~PCI_MAPREG_ROM_ENABLE);

        if (sc->sc_biossz > 0) {
                ptr = GETBIOS16(sc, 0x48);
                if ((GETBIOS32(sc, ptr + 4) == 0x41544f4d /* "ATOM" */) ||
                    (GETBIOS32(sc, ptr + 4) == 0x4d4f5441 /* "MOTA" */)) {
                        sc->sc_flags |= RFB_ATOM;
                }

                aprint_verbose("%s: Found %d KB %s BIOS\n", XNAME(sc),
                    (unsigned)sc->sc_biossz >> 10,
                    IS_ATOM(sc) ? "ATOM" : "Legacy");
        }
}


uint32_t
radeonfb_get32(struct radeonfb_softc *sc, uint32_t reg)
{

        return bus_space_read_4(sc->sc_regt, sc->sc_regh, reg);
}

void
radeonfb_put32(struct radeonfb_softc *sc, uint32_t reg, uint32_t val)
{

        bus_space_write_4(sc->sc_regt, sc->sc_regh, reg, val);
}

void
radeonfb_put32s(struct radeonfb_softc *sc, uint32_t reg, uint32_t val)
{

        bus_space_write_stream_4(sc->sc_regt, sc->sc_regh, reg, val);
}

void
radeonfb_mask32(struct radeonfb_softc *sc, uint32_t reg,
    uint32_t andmask, uint32_t ormask)
{
        int             s;
        uint32_t        val;

        s = splhigh();
        val = radeonfb_get32(sc, reg);
        val = (val & andmask) | ormask;
        radeonfb_put32(sc, reg, val);
        splx(s);
}

uint32_t
radeonfb_getindex(struct radeonfb_softc *sc, uint32_t idx)
{
        int             s;
        uint32_t        val;

        s = splhigh();
        radeonfb_put32(sc, RADEON_MM_INDEX, idx);
        val = radeonfb_get32(sc, RADEON_MM_DATA);
        splx(s);

        return (val);
}

void
radeonfb_putindex(struct radeonfb_softc *sc, uint32_t idx, uint32_t val)
{
        int     s;

        s = splhigh();
        radeonfb_put32(sc, RADEON_MM_INDEX, idx);
        radeonfb_put32(sc, RADEON_MM_DATA, val);
        splx(s);
}

void
radeonfb_maskindex(struct radeonfb_softc *sc, uint32_t idx,
    uint32_t andmask, uint32_t ormask)
{
        int             s;
        uint32_t        val;

        s = splhigh();
        radeonfb_put32(sc, RADEON_MM_INDEX, idx);
        val = radeonfb_get32(sc, RADEON_MM_DATA);
        val = (val & andmask) | ormask;
        radeonfb_put32(sc, RADEON_MM_DATA, val);
        splx(s);
}

uint32_t
radeonfb_getpll(struct radeonfb_softc *sc, uint32_t idx)
{
        int             s;
        uint32_t        val;

        s = splhigh();
        radeonfb_put32(sc, RADEON_CLOCK_CNTL_INDEX, (idx & 0x3f));
        val = radeonfb_get32(sc, RADEON_CLOCK_CNTL_DATA);
        if (HAS_R300CG(sc))
                radeonfb_r300cg_workaround(sc);
        splx(s);

        return (val);
}

void
radeonfb_putpll(struct radeonfb_softc *sc, uint32_t idx, uint32_t val)
{
        int     s;

        s = splhigh();
        radeonfb_put32(sc, RADEON_CLOCK_CNTL_INDEX, (idx & 0x3f) |
            RADEON_PLL_WR_EN);
        radeonfb_put32(sc, RADEON_CLOCK_CNTL_DATA, val);
        radeonfb_put32(sc, RADEON_CLOCK_CNTL_INDEX, 0);
        splx(s);
}

void
radeonfb_maskpll(struct radeonfb_softc *sc, uint32_t idx,
    uint32_t andmask, uint32_t ormask)
{
        int             s;
        uint32_t        val;

        s = splhigh();
        radeonfb_put32(sc, RADEON_CLOCK_CNTL_INDEX, (idx & 0x3f) |
                RADEON_PLL_WR_EN);
        val = radeonfb_get32(sc, RADEON_CLOCK_CNTL_DATA);
        val = (val & andmask) | ormask;
        radeonfb_put32(sc, RADEON_CLOCK_CNTL_DATA, val);
        radeonfb_put32(sc, RADEON_CLOCK_CNTL_INDEX, 0);
        splx(s);
}

int
radeonfb_scratch_test(struct radeonfb_softc *sc, int reg, uint32_t v)
{
        uint32_t        saved;

        saved = GET32(sc, reg);
        PUT32(sc, reg, v);
        if (GET32(sc, reg) != v) {
                return -1;
        }
        PUT32(sc, reg, saved);
        return 0;
}

uintmax_t
radeonfb_getprop_num(struct radeonfb_softc *sc, const char *name,
    uintmax_t defval)
{
        prop_number_t   pn;
        pn = prop_dictionary_get(device_properties(sc->sc_dev), name);
        if (pn == NULL) {
                return defval;
        }
        KASSERT(prop_object_type(pn) == PROP_TYPE_NUMBER);
        return prop_number_unsigned_value(pn);
}

int
radeonfb_getclocks(struct radeonfb_softc *sc)
{
        bus_addr_t      ptr;
        int             refclk = 0;
        int             refdiv = 0;
        int             minpll = 0;
        int             maxpll = 0;

        /* load initial property values if port/board provides them */
        refclk = radeonfb_getprop_num(sc, "refclk", 0) & 0xffff;
        refdiv = radeonfb_getprop_num(sc, "refdiv", 0) & 0xffff;
        minpll = radeonfb_getprop_num(sc, "minpll", 0) & 0xffffffffU;
        maxpll = radeonfb_getprop_num(sc, "maxpll", 0) & 0xffffffffU;

        PRINTPLL(RADEON_PPLL_REF_DIV);
        PRINTPLL(RADEON_PPLL_DIV_0);
        PRINTPLL(RADEON_PPLL_DIV_1);
        PRINTPLL(RADEON_PPLL_DIV_2);
        PRINTPLL(RADEON_PPLL_DIV_3);
        PRINTREG(RADEON_CLOCK_CNTL_INDEX);
        PRINTPLL(RADEON_P2PLL_REF_DIV);
        PRINTPLL(RADEON_P2PLL_DIV_0);

        if (refclk && refdiv && minpll && maxpll)
                goto dontprobe;

        if (!sc->sc_biossz) {
                /* no BIOS */
                aprint_verbose("%s: No video BIOS, using default clocks\n",
                    XNAME(sc));
                if (IS_IGP(sc))
                        refclk = refclk ? refclk : 1432;
                else
                        refclk = refclk ? refclk : 2700;
                refdiv = refdiv ? refdiv : 12;
                minpll = minpll ? minpll : 12500;
                /* XXX
                 * Need to check if the firmware or something programmed a
                 * higher value than this, and if so, bump it.
                 * The RV280 in my iBook is unhappy if the PLL input is less
                 * than 360MHz
                 */
                maxpll = maxpll ? maxpll : 40000/*35000*/;
        } else if (IS_ATOM(sc)) {
                /* ATOM BIOS */
                ptr = GETBIOS16(sc, 0x48);
                ptr = GETBIOS16(sc, ptr + 32);  /* aka MasterDataStart */
                ptr = GETBIOS16(sc, ptr + 12);  /* pll info block */
                refclk = refclk ? refclk : GETBIOS16(sc, ptr + 82);
                minpll = minpll ? minpll : GETBIOS16(sc, ptr + 78);
                maxpll = maxpll ? maxpll : GETBIOS16(sc, ptr + 32);
                /*
                 * ATOM BIOS doesn't supply a reference divider, so we
                 * have to probe for it.
                 */
                if (refdiv < 2)
                        refdiv = GETPLL(sc, RADEON_PPLL_REF_DIV) &
                            RADEON_PPLL_REF_DIV_MASK;
                /*
                 * if probe is zero, just assume one that should work
                 * for most parts
                 */
                if (refdiv < 2)
                        refdiv = 12;
                
        } else {
                uint32_t tmp = GETPLL(sc, RADEON_PPLL_REF_DIV);
                /* Legacy BIOS */
                ptr = GETBIOS16(sc, 0x48);
                ptr = GETBIOS16(sc, ptr + 0x30);
                if (IS_R300(sc)) {
                        refdiv = refdiv ? refdiv :
                            (tmp & R300_PPLL_REF_DIV_ACC_MASK) >>
                            R300_PPLL_REF_DIV_ACC_SHIFT;
                } else {
                        refdiv = refdiv ? refdiv :
                            tmp & RADEON_PPLL_REF_DIV_MASK;
                }
                refclk = refclk ? refclk : GETBIOS16(sc, ptr + 0x0E);
                refdiv = refdiv ? refdiv : GETBIOS16(sc, ptr + 0x10);
                minpll = minpll ? minpll : GETBIOS32(sc, ptr + 0x12);
                maxpll = maxpll ? maxpll : GETBIOS32(sc, ptr + 0x16);
        }


dontprobe:
        sc->sc_refclk = refclk * 10;
        sc->sc_refdiv = refdiv;
        sc->sc_minpll = minpll * 10;
        sc->sc_maxpll = maxpll * 10;
        return 0;
}

int
radeonfb_calc_dividers(struct radeonfb_softc *sc, uint32_t dotclock,
    uint32_t *postdivbit, uint32_t *feedbackdiv, int flags)
{
        int             i;
        uint32_t        outfreq;
        int             div;

        DPRINTF(("dot clock: %u\n", dotclock));
        for (i = 0; (div = radeonfb_dividers[i].divider) != 0; i++) {

                if ((flags & NO_ODD_FBDIV) && ((div & 1) != 0))
                        continue;

                /*
                 * XXX
                 * the rv350 in my last generation 14" iBook G4 produces
                 * garbage with dividers > 4. No idea if this is a hardware
                 * limitation or an error in the divider table.
                 */
                if ((sc->sc_family == RADEON_RV350) && (div > 4))
                        continue;

                outfreq = div * dotclock;
                if ((outfreq >= sc->sc_minpll) &&
                    (outfreq <= sc->sc_maxpll)) {
                        DPRINTF(("outfreq: %u\n", outfreq));
                        *postdivbit =
                            ((uint32_t)radeonfb_dividers[i].mask << 16);
                        DPRINTF(("post divider: %d (mask %x)\n", div,
                                    *postdivbit));
                        break;
                }
        }

        if (div == 0)
                return 1;

        *feedbackdiv = DIVIDE(sc->sc_refdiv * outfreq, sc->sc_refclk);
        DPRINTF(("feedback divider: %d\n", *feedbackdiv));
        return 0;
}

#if 0
#ifdef RADEONFB_DEBUG
static void
dump_buffer(const char *pfx, void *buffer, unsigned int size)
{
        char            asc[17];
        unsigned        ptr = (unsigned)buffer;
        char            *start = (char *)(ptr & ~0xf);
        char            *end = (char *)(ptr + size);

        end = (char *)(((unsigned)end + 0xf) & ~0xf);

        if (pfx == NULL) {
                pfx = "";
        }

        while (start < end) {
                unsigned offset = (unsigned)start & 0xf;
                if (offset == 0) {
                        printf("%s%x: ", pfx, (unsigned)start);
                }
                if (((unsigned)start < ptr) ||
                    ((unsigned)start >= (ptr + size))) {
                        printf("  ");
                        asc[offset] = ' ';
                } else {
                        printf("%02x", *(unsigned char *)start);
                        if ((*start >= ' ') && (*start <= '~')) {
                                asc[offset] = *start;
                        } else {
                                asc[offset] = '.';
                        }
                }
                asc[offset + 1] = 0;
                if (offset % 2) {
                        printf(" ");
                }
                if (offset == 15) {
                        printf(" %s\n", asc);
                }
                start++;
        }
}
#endif
#endif

int
radeonfb_getconnectors(struct radeonfb_softc *sc)
{
        int     i;
        int     found = 0;

        for (i = 0; i < 2; i++) {
                sc->sc_ports[i].rp_mon_type = RADEON_MT_UNKNOWN;
                sc->sc_ports[i].rp_ddc_type = RADEON_DDC_NONE;
                sc->sc_ports[i].rp_dac_type = RADEON_DAC_UNKNOWN;
                sc->sc_ports[i].rp_conn_type = RADEON_CONN_NONE;
                sc->sc_ports[i].rp_tmds_type = RADEON_TMDS_UNKNOWN;
        }

        /*
         * This logic is borrowed from Xorg's radeon driver.
         */
        if (!sc->sc_biossz)
                goto nobios;

        if (IS_ATOM(sc)) {
                /* not done yet */
        } else {
                uint16_t        ptr;
                int             port = 0;

                ptr = GETBIOS16(sc, 0x48);
                ptr = GETBIOS16(sc, ptr + 0x50);
                for (i = 1; i < 4; i++) {
                        uint16_t        entry;
                        uint8_t         conn, ddc, dac, tmds;

                        /*
                         * Parse the connector table.  From reading the code,
                         * it appears to made up of 16-bit entries for each
                         * connector.  The 16-bits are defined as:
                         *
                         * bits 12-15   - connector type (0 == end of table)
                         * bits 8-11    - DDC type
                         * bits 5-7     - ???
                         * bit 4        - TMDS type (1 = EXT, 0 = INT)
                         * bits 1-3     - ???
                         * bit 0        - DAC, 1 = TVDAC, 0 = primary
                         */
                        if (!GETBIOS8(sc, ptr + i * 2) && i > 1)
                                break;
                        entry = GETBIOS16(sc, ptr + i * 2);

                        conn = (entry >> 12) & 0xf;
                        ddc = (entry >> 8) & 0xf;
                        dac = (entry & 0x1) ? RADEON_DAC_TVDAC :
                            RADEON_DAC_PRIMARY;
                        tmds = ((entry >> 4) & 0x1) ? RADEON_TMDS_EXT :
                            RADEON_TMDS_INT;

                        if (conn == RADEON_CONN_NONE)
                                continue;       /* no connector */

                        /* 
                         * XXX
                         * both Mac Mini variants have both outputs wired to 
                         * the same connector and share the DDC lines
                         */ 
                        if ((found > 0) &&
                            (sc->sc_ports[port].rp_ddc_type == ddc)) {
                                /* duplicate entry for same connector */
                                continue;
                        }

                        /* internal DDC_DVI port gets priority */
                        if ((ddc == RADEON_DDC_DVI) || (port == 1))
                                port = 0;
                        else
                                port = 1;

                        sc->sc_ports[port].rp_ddc_type =
                            ddc > RADEON_DDC_CRT2 ? RADEON_DDC_NONE : ddc;
                        sc->sc_ports[port].rp_dac_type = dac;
                        sc->sc_ports[port].rp_conn_type =
                            uimin(conn, RADEON_CONN_UNSUPPORTED) ;

                        sc->sc_ports[port].rp_tmds_type = tmds;

                        if ((conn != RADEON_CONN_DVI_I) &&
                            (conn != RADEON_CONN_DVI_D) &&
                            (tmds == RADEON_TMDS_INT))
                                sc->sc_ports[port].rp_tmds_type =
                                    RADEON_TMDS_UNKNOWN;
                        sc->sc_ports[port].rp_number = i - 1;

                        found += (port + 1);
                }
        }

nobios:
        if (!found) {
                bool dvi_ext = FALSE, dvi_int = FALSE;
                DPRINTF(("No connector info in BIOS!\n"));
                prop_dictionary_get_bool(device_properties(sc->sc_dev),
                    "dvi-internal", &dvi_int);
                prop_dictionary_get_bool(device_properties(sc->sc_dev),
                    "dvi-external", &dvi_ext);
                if (dvi_ext) {
                        sc->sc_ports[0].rp_mon_type = RADEON_MT_UNKNOWN;
                        sc->sc_ports[0].rp_ddc_type = RADEON_DDC_CRT2;
                        sc->sc_ports[0].rp_dac_type = RADEON_DAC_PRIMARY;
                        sc->sc_ports[0].rp_conn_type = RADEON_CONN_DVI_I;
                        sc->sc_ports[0].rp_tmds_type = RADEON_TMDS_EXT; /* output to fp2 */
                        sc->sc_ports[0].rp_number = 0;
                        sc->sc_ports[1].rp_mon_type = RADEON_MT_UNKNOWN;
                        sc->sc_ports[1].rp_ddc_type = RADEON_DDC_NONE;
                        sc->sc_ports[1].rp_dac_type = RADEON_DAC_UNKNOWN;
                        sc->sc_ports[1].rp_conn_type = RADEON_CONN_NONE;
                        sc->sc_ports[1].rp_tmds_type = RADEON_TMDS_UNKNOWN;
                        sc->sc_ports[1].rp_number = 1;
                } else  if (dvi_int) {
                        sc->sc_ports[0].rp_mon_type = RADEON_MT_UNKNOWN;
                        sc->sc_ports[0].rp_ddc_type = RADEON_DDC_CRT2;
                        sc->sc_ports[0].rp_dac_type = RADEON_DAC_PRIMARY;
                        sc->sc_ports[0].rp_conn_type = RADEON_CONN_DVI_I;
                        sc->sc_ports[0].rp_tmds_type = RADEON_TMDS_INT;
                        sc->sc_ports[0].rp_number = 0;
                        sc->sc_ports[1].rp_mon_type = RADEON_MT_UNKNOWN;
                        sc->sc_ports[1].rp_ddc_type = RADEON_DDC_NONE;
                        sc->sc_ports[1].rp_dac_type = RADEON_DAC_UNKNOWN;
                        sc->sc_ports[1].rp_conn_type = RADEON_CONN_NONE;
                        sc->sc_ports[1].rp_tmds_type = RADEON_TMDS_UNKNOWN;
                        sc->sc_ports[1].rp_number = 1;
                } else if IS_MOBILITY(sc) {
                        /* default, port 0 = internal TMDS, port 1 = CRT */
                        sc->sc_ports[0].rp_mon_type = RADEON_MT_UNKNOWN;
                        sc->sc_ports[0].rp_ddc_type = RADEON_DDC_DVI;
                        sc->sc_ports[0].rp_dac_type = RADEON_DAC_TVDAC;
                        sc->sc_ports[0].rp_conn_type = RADEON_CONN_DVI_D;
                        sc->sc_ports[0].rp_tmds_type = RADEON_TMDS_INT;
                        sc->sc_ports[0].rp_number = 0;

                        sc->sc_ports[1].rp_mon_type = RADEON_MT_UNKNOWN;
                        sc->sc_ports[1].rp_ddc_type = RADEON_DDC_VGA;
                        sc->sc_ports[1].rp_dac_type = RADEON_DAC_PRIMARY;
                        sc->sc_ports[1].rp_conn_type = RADEON_CONN_CRT;
                        sc->sc_ports[1].rp_tmds_type = RADEON_TMDS_EXT;
                        sc->sc_ports[1].rp_number = 1;
                } else {
                        /* default, port 0 = DVI, port 1 = CRT */
                        sc->sc_ports[0].rp_mon_type = RADEON_MT_UNKNOWN;
                        sc->sc_ports[0].rp_ddc_type = RADEON_DDC_DVI;
                        sc->sc_ports[0].rp_dac_type = RADEON_DAC_TVDAC;
                        sc->sc_ports[0].rp_conn_type = RADEON_CONN_DVI_D;
                        sc->sc_ports[0].rp_tmds_type = RADEON_TMDS_INT;
                        sc->sc_ports[0].rp_number = 0;

                        sc->sc_ports[1].rp_mon_type = RADEON_MT_UNKNOWN;
                        sc->sc_ports[1].rp_ddc_type = RADEON_DDC_VGA;
                        sc->sc_ports[1].rp_dac_type = RADEON_DAC_PRIMARY;
                        sc->sc_ports[1].rp_conn_type = RADEON_CONN_CRT;
                        sc->sc_ports[1].rp_tmds_type = RADEON_TMDS_UNKNOWN;
                        sc->sc_ports[1].rp_number = 1;
                }
        }

        /*
         * Fixup for RS300/RS350/RS400 chips, that lack a primary DAC.
         * these chips should use TVDAC for the VGA port.
         */
        if (HAS_SDAC(sc)) {
                if (sc->sc_ports[0].rp_conn_type == RADEON_CONN_CRT) {
                        sc->sc_ports[0].rp_dac_type = RADEON_DAC_TVDAC;
                        sc->sc_ports[1].rp_dac_type = RADEON_DAC_PRIMARY;
                } else {
                        sc->sc_ports[1].rp_dac_type = RADEON_DAC_TVDAC;
                        sc->sc_ports[0].rp_dac_type = RADEON_DAC_PRIMARY;
                }
        } else if (!HAS_CRTC2(sc)) {
                sc->sc_ports[0].rp_dac_type = RADEON_DAC_PRIMARY;
        }

        for (i = 0; i < 2; i++) {
                char    edid[128], edid_port_str[7] = "EDID:";
                uint8_t ddc;
                struct edid_info *eip = &sc->sc_ports[i].rp_edid;
                prop_data_t edid_data;

                DPRINTF(("Port #%d:\n", i));
                DPRINTF(("   conn = %d\n", sc->sc_ports[i].rp_conn_type));
                DPRINTF(("    ddc = %d\n", sc->sc_ports[i].rp_ddc_type));
                DPRINTF(("    dac = %d\n", sc->sc_ports[i].rp_dac_type));
                DPRINTF(("   tmds = %d\n", sc->sc_ports[i].rp_tmds_type));
                DPRINTF(("   crtc = %d\n", sc->sc_ports[i].rp_number));

                sc->sc_ports[i].rp_edid_valid = 0;
                /*
                 * First look for static EDID data
                 * Try "EDID:port" then "EDID"
                 */
                snprintf(&edid_port_str[5], 2, "%d", i);
                edid_data = prop_dictionary_get(device_properties(
                    sc->sc_dev), edid_port_str);
                if (edid_data == NULL)
                        edid_data = prop_dictionary_get(device_properties(
                              sc->sc_dev), "EDID");
                if (edid_data != NULL) {
                        aprint_debug_dev(sc->sc_dev, "using static EDID\n");
                        memcpy(edid, prop_data_value(edid_data), 128);
                        if (edid_parse(edid, eip) == 0) {

                                sc->sc_ports[i].rp_edid_valid = 1;
#ifdef RADEONFB_DEBUG
                                        edid_print(eip);
#endif
                        }
                }
                /* if we didn't find any we'll try to talk to the monitor */
                if (sc->sc_ports[i].rp_edid_valid != 1) {

                        ddc = sc->sc_ports[i].rp_ddc_type;
                        if (ddc != RADEON_DDC_NONE) {
                                if ((radeonfb_i2c_read_edid(sc, ddc, edid)
                                    == 0) && (edid_parse(edid, eip) == 0)) {

                                        sc->sc_ports[i].rp_edid_valid = 1;
#ifdef RADEONFB_DEBUG
                                        edid_print(eip);
#endif
                                }
                        }
                }
        }

        return found;
}

int
radeonfb_gettmds(struct radeonfb_softc *sc)
{
        int     i;

        if (!sc->sc_biossz) {
                goto nobios;
        }

        if (IS_ATOM(sc)) {
                /* XXX: not done yet */
        } else {
                uint16_t        ptr;
                int             n;

                ptr = GETBIOS16(sc, 0x48);
                ptr = GETBIOS16(sc, ptr + 0x34);
                DPRINTF(("DFP table revision %d\n", GETBIOS8(sc, ptr)));
                if (GETBIOS8(sc, ptr) == 3) {   
                        /* revision three table */
                        n = GETBIOS8(sc, ptr + 5) + 1;
                        n = uimin(n, 4);

                        memset(sc->sc_tmds_pll, 0, sizeof (sc->sc_tmds_pll));
                        for (i = 0; i < n; i++) {
                                sc->sc_tmds_pll[i].rtp_pll = GETBIOS32(sc,
                                    ptr + i * 10 + 8);
                                sc->sc_tmds_pll[i].rtp_freq = GETBIOS16(sc,
                                    ptr + i * 10 + 0x10);
                                DPRINTF(("TMDS_PLL dot clock %d pll %x\n",
                                            sc->sc_tmds_pll[i].rtp_freq,
                                            sc->sc_tmds_pll[i].rtp_pll));
                        }
                        return 0;
                }
        }

nobios:
        DPRINTF(("no suitable DFP table present\n"));
        for (i = 0;
             i < sizeof (radeonfb_tmds_pll) / sizeof (radeonfb_tmds_pll[0]);
             i++) {
                int     j;

                if (radeonfb_tmds_pll[i].family != sc->sc_family)
                        continue;

                for (j = 0; j < 4; j++) {
                        sc->sc_tmds_pll[j] = radeonfb_tmds_pll[i].plls[j];
                        DPRINTF(("TMDS_PLL dot clock %d pll %x\n",
                                    sc->sc_tmds_pll[j].rtp_freq,
                                    sc->sc_tmds_pll[j].rtp_pll));
                }
                return 0;
        }

        return -1;
}

const struct videomode *
radeonfb_modelookup(const char *name)
{
        int     i;
        /* Use a default mode in case we don't find a matching mode */
        const char *vm = "1024x768x60";
        const struct videomode *vmp = NULL;

        for (i = 0; i < videomode_count; i++) {
                if (!strcmp(name, videomode_list[i].name))
                        return &videomode_list[i];
                if (!strcmp(vm, videomode_list[i].name))
                        vmp = &videomode_list[i];
        }
        return vmp;
}

void
radeonfb_pllwriteupdate(struct radeonfb_softc *sc, int crtc)
{
        if (crtc) {
                while (GETPLL(sc, RADEON_P2PLL_REF_DIV) &
                    RADEON_P2PLL_ATOMIC_UPDATE_R);
                SETPLL(sc, RADEON_P2PLL_REF_DIV, RADEON_P2PLL_ATOMIC_UPDATE_W);
        } else {
                while (GETPLL(sc, RADEON_PPLL_REF_DIV) &
                    RADEON_PPLL_ATOMIC_UPDATE_R);
                SETPLL(sc, RADEON_PPLL_REF_DIV, RADEON_PPLL_ATOMIC_UPDATE_W);
        }
}

void
radeonfb_pllwaitatomicread(struct radeonfb_softc *sc, int crtc)
{
        int     i;

        for (i = 10000; i; i--) {
                if (crtc) {
                        if (GETPLL(sc, RADEON_P2PLL_REF_DIV) &
                            RADEON_P2PLL_ATOMIC_UPDATE_R)
                                break;
                } else {
                        if (GETPLL(sc, RADEON_PPLL_REF_DIV) &
                            RADEON_PPLL_ATOMIC_UPDATE_R)
                                break;
                }
        }
}

void
radeonfb_program_vclk(struct radeonfb_softc *sc, int dotclock, int crtc, int flags)
{
        uint32_t        pbit = 0;
        uint32_t        feed = 0;
        uint32_t        data, refdiv, div0, r2xxref;

        radeonfb_calc_dividers(sc, dotclock, &pbit, &feed, flags);

        if (crtc == 0) {

                refdiv = GETPLL(sc, RADEON_PPLL_REF_DIV);
                
                /*
                 * XXX
                 * the RV350 in my last generation iBook G4 behaves like an
                 * r2xx here - try to detect that and not screw up the reference
                 * divider.
                 * xf86-video-radeon just skips PLL programming altogether
                 * on iBooks, probably for this reason.
                 */
                r2xxref = (refdiv & ~RADEON_PPLL_REF_DIV_MASK) | sc->sc_refdiv;
                if (IS_R300(sc) && (r2xxref != refdiv)) {
                        refdiv = (refdiv & ~R300_PPLL_REF_DIV_ACC_MASK) |
                            (sc->sc_refdiv << R300_PPLL_REF_DIV_ACC_SHIFT);
                } else {
                        refdiv = (refdiv & ~RADEON_PPLL_REF_DIV_MASK) |
                            sc->sc_refdiv;
                }
                DPRINTF(("refdiv %08x\n", refdiv));
                div0 = GETPLL(sc, RADEON_PPLL_DIV_0);
                DPRINTF(("div0 %08x\n", div0));
                div0 &= ~(RADEON_PPLL_FB3_DIV_MASK |
                    RADEON_PPLL_POST3_DIV_MASK);
                div0 |= pbit;
                div0 |= (feed & RADEON_PPLL_FB3_DIV_MASK);
                DPRINTF(("div0 %08x\n", div0));

                if ((refdiv == GETPLL(sc, RADEON_PPLL_REF_DIV)) &&
                    (div0 == GETPLL(sc, RADEON_PPLL_DIV_0))) {
                        /* 
                         * nothing to do here, the PLL is already where we
                         * want it
                         */
                        PATCH32(sc, RADEON_CLOCK_CNTL_INDEX, 0,
                            ~RADEON_PLL_DIV_SEL);
                        aprint_debug_dev(sc->sc_dev, "no need to touch the PLL\n");
                        return;
                }

                /* alright, we do need to reprogram stuff */     
                PATCHPLL(sc, RADEON_VCLK_ECP_CNTL,
                    RADEON_VCLK_SRC_SEL_CPUCLK,
                    ~RADEON_VCLK_SRC_SEL_MASK);
            
                /* put vclk into reset, use atomic updates */
                SETPLL(sc, RADEON_PPLL_CNTL,
                    RADEON_PPLL_REFCLK_SEL |
                    RADEON_PPLL_FBCLK_SEL |
                    RADEON_PPLL_RESET |
                    RADEON_PPLL_ATOMIC_UPDATE_EN |
                    RADEON_PPLL_VGA_ATOMIC_UPDATE_EN);

                /* select clock 0 */
                PATCH32(sc, RADEON_CLOCK_CNTL_INDEX, 0,
                    ~RADEON_PLL_DIV_SEL);
                
                PUTPLL(sc, RADEON_PPLL_REF_DIV, refdiv);

                /* xf86-video-radeon does this, not sure why */
                PUTPLL(sc, RADEON_PPLL_DIV_0, div0);
                PUTPLL(sc, RADEON_PPLL_DIV_0, div0);

                /* use the atomic update */
                radeonfb_pllwriteupdate(sc, crtc);

                /* and wait for it to complete */
                radeonfb_pllwaitatomicread(sc, crtc);

                /* program HTOTAL (why?) */
                PUTPLL(sc, RADEON_HTOTAL_CNTL, 0);

                /* drop reset */
                CLRPLL(sc, RADEON_PPLL_CNTL,
                    RADEON_PPLL_RESET | RADEON_PPLL_SLEEP |
                    RADEON_PPLL_ATOMIC_UPDATE_EN |
                    RADEON_PPLL_VGA_ATOMIC_UPDATE_EN);

                PRINTPLL(RADEON_PPLL_CNTL);
                PRINTPLL(RADEON_PPLL_REF_DIV);
                PRINTPLL(RADEON_PPLL_DIV_3);

                /* give clock time to lock */
                delay(50000);

                PATCHPLL(sc, RADEON_VCLK_ECP_CNTL,
                    RADEON_VCLK_SRC_SEL_PPLLCLK,
                    ~RADEON_VCLK_SRC_SEL_MASK);

                /*
                 * Ungate the CRTC1 pixel clocks
                 */
                SETPLL(sc, RADEON_VCLK_ECP_CNTL,
                    RADEON_PIXCLK_ALWAYS_ONb |
                    RADEON_PIXCLK_DAC_ALWAYS_ONb);
                PRINTPLL(RADEON_VCLK_ECP_CNTL);

        } else {

                PATCHPLL(sc, RADEON_PIXCLKS_CNTL,
                    RADEON_PIX2CLK_SRC_SEL_CPUCLK,
                    ~RADEON_PIX2CLK_SRC_SEL_MASK);

                /* put vclk into reset, use atomic updates */
                SETPLL(sc, RADEON_P2PLL_CNTL,
                    RADEON_P2PLL_RESET |
                    RADEON_P2PLL_ATOMIC_UPDATE_EN |
                    RADEON_P2PLL_VGA_ATOMIC_UPDATE_EN);

                /* program reference divider */
                PATCHPLL(sc, RADEON_P2PLL_REF_DIV, sc->sc_refdiv,
                    ~RADEON_P2PLL_REF_DIV_MASK);

                /* program feedback and post dividers */
                data = GETPLL(sc, RADEON_P2PLL_DIV_0);
                data &= ~(RADEON_P2PLL_FB0_DIV_MASK |
                    RADEON_P2PLL_POST0_DIV_MASK);
                data |= pbit;
                data |= (feed & RADEON_P2PLL_FB0_DIV_MASK);
                PUTPLL(sc, RADEON_P2PLL_DIV_0, data);
                PUTPLL(sc, RADEON_P2PLL_DIV_0, data);

                PRINTPLL(RADEON_P2PLL_REF_DIV);
                PRINTPLL(RADEON_P2PLL_DIV_0);

                /* use the atomic update */
                radeonfb_pllwriteupdate(sc, crtc);

                /* and wait for it to complete */
                radeonfb_pllwaitatomicread(sc, crtc);

                /* program HTOTAL (why?) */
                PUTPLL(sc, RADEON_HTOTAL2_CNTL, 0);

                /* drop reset */
                CLRPLL(sc, RADEON_P2PLL_CNTL,
                    RADEON_P2PLL_RESET | RADEON_P2PLL_SLEEP |
                    RADEON_P2PLL_ATOMIC_UPDATE_EN |
                    RADEON_P2PLL_VGA_ATOMIC_UPDATE_EN);

                /* allow time for clock to lock */
                delay(50000);

                PATCHPLL(sc, RADEON_PIXCLKS_CNTL,
                    RADEON_PIX2CLK_SRC_SEL_P2PLLCLK,
                    ~RADEON_PIX2CLK_SRC_SEL_MASK);

                /*
                 * Ungate the CRTC2 and digital-output pixel clocks
                 */
                SETPLL(sc, RADEON_PIXCLKS_CNTL,
                    RADEON_PIX2CLK_ALWAYS_ONb |
                    RADEON_PIX2CLK_DAC_ALWAYS_ONb |
                    RADEON_PIXCLK_BLEND_ALWAYS_ONb |
                    RADEON_PIXCLK_GV_ALWAYS_ONb |
                    RADEON_PIXCLK_DIG_TMDS_ALWAYS_ONb |
                    RADEON_PIXCLK_TMDS_ALWAYS_ONb);
                PRINTPLL(RADEON_PIXCLKS_CNTL);
        }
        PRINTREG(RADEON_CRTC_MORE_CNTL);
}

void
radeonfb_modeswitch(struct radeonfb_display *dp)
{
        struct radeonfb_softc   *sc = dp->rd_softc;
        int                     i;

        if (IS_AVIVO(sc)) {
                /*
                 * no actual mode setting yet, we just make sure the CRTCs
                 * point at the right memory ranges and use the same pitch
                 * for the drawing engine
                 */
                if (GET32(sc, AVIVO_D1CRTC_CONTROL) & AVIVO_CRTC_EN) {
                        CLR32(sc, AVIVO_D1GRPH_CONTROL, AVIVO_D1GRPH_MACRO_ADDRESS_MODE);
                        dp->rd_stride = GET32(sc, AVIVO_D1GRPH_PITCH);
                        PUT32(sc, AVIVO_D1GRPH_PRIMARY_SURFACE_ADDRESS, 0);
                }
                if (GET32(sc, AVIVO_D2CRTC_CONTROL) & AVIVO_CRTC_EN) {
                        CLR32(sc, AVIVO_D2GRPH_CONTROL, AVIVO_D1GRPH_MACRO_ADDRESS_MODE);
                        dp->rd_stride = GET32(sc, AVIVO_D2GRPH_PITCH);
                        PUT32(sc, AVIVO_D2GRPH_PRIMARY_SURFACE_ADDRESS, 0);
                }
                return;
        }

        /* blank the display while we switch modes */
        //radeonfb_blank(dp, 1);

#if 0
        SET32(sc, RADEON_CRTC_EXT_CNTL,
            RADEON_CRTC_VSYNC_DIS | RADEON_CRTC_HSYNC_DIS |
            RADEON_CRTC_DISPLAY_DIS /* | RADEON_CRTC_DISP_REQ_EN_B */);
#endif

        /* these registers might get in the way... */
        PUT32(sc, RADEON_OVR_CLR, 0);
        PUT32(sc, RADEON_OVR_WID_LEFT_RIGHT, 0);
        PUT32(sc, RADEON_OVR_WID_TOP_BOTTOM, 0);
        PUT32(sc, RADEON_OV0_SCALE_CNTL, 0);
        PUT32(sc, RADEON_SUBPIC_CNTL, 0);
        PUT32(sc, RADEON_VIPH_CONTROL, 0);
        PUT32(sc, RADEON_I2C_CNTL_1, 0);
        PUT32(sc, RADEON_GEN_INT_CNTL, 0);
        PUT32(sc, RADEON_CAP0_TRIG_CNTL, 0);
        PUT32(sc, RADEON_CAP1_TRIG_CNTL, 0);

        for (i = 0; i < dp->rd_ncrtcs; i++)
                radeonfb_setcrtc(dp, i);

#if 0
        /*
         * DVO chip voodoo from xf86-video-radeon
         * apparently this is needed for some powerbooks with DVI outputs
         */

        uint8_t data[5][2] = {{0x8, 0x030}, {0x9, 0}, {0xa, 0x90}, {0xc, 0x89}, {0x8, 0x3b}};
        int n = 0;
        iic_acquire_bus(&sc->sc_i2c[0].ric_controller, 0);
        for (i = 0; i < 5; i++)
                n += iic_exec(&sc->sc_i2c[0].ric_controller, I2C_OP_WRITE, 0x38, data[i], 2, NULL, 0, 0);
        iic_release_bus(&sc->sc_i2c[0].ric_controller, 0);
        printf("n = %d\n", n);
#endif

        /* activate the display */
        radeonfb_blank(dp, 0);
}

void
radeonfb_setcrtc(struct radeonfb_display *dp, int index)
{
        int                     crtc, flags = 0;
        struct videomode        *mode;
        struct radeonfb_softc   *sc;
        struct radeonfb_crtc    *cp;
        uint32_t                v, hd, vd;
        uint32_t                gencntl;
        uint32_t                htotaldisp;
        uint32_t                hsyncstrt;
        uint32_t                vtotaldisp;
        uint32_t                vsyncstrt;
        uint32_t                fphsyncstrt;
        uint32_t                fpvsyncstrt;
        uint32_t                fphtotaldisp;
        uint32_t                fpvtotaldisp;
        uint32_t                pitch;

        sc = dp->rd_softc;

        if ((sc->sc_ports[index].rp_tmds_type == RADEON_TMDS_INT) ||
            (sc->sc_ports[index].rp_tmds_type == RADEON_TMDS_EXT)) {
                flags |= NO_ODD_FBDIV;
        }

        cp = &dp->rd_crtcs[index];
        crtc = cp->rc_number;
        mode = &cp->rc_videomode;

#if 1
        pitch = dp->rd_stride / dp->rd_bpp;
#else
        pitch = (((sc->sc_maxx * sc->sc_maxbpp) + ((sc->sc_maxbpp * 8) - 1)) /
            (sc->sc_maxbpp * 8));
#endif
        switch (crtc) {
        case 0:
                gencntl = RADEON_CRTC_GEN_CNTL;
                htotaldisp = RADEON_CRTC_H_TOTAL_DISP;
                hsyncstrt = RADEON_CRTC_H_SYNC_STRT_WID;
                vtotaldisp = RADEON_CRTC_V_TOTAL_DISP;
                vsyncstrt = RADEON_CRTC_V_SYNC_STRT_WID;
                /* should probably leave those alone on non-LVDS */
                fpvsyncstrt = RADEON_FP_V_SYNC_STRT_WID;
                fphsyncstrt = RADEON_FP_H_SYNC_STRT_WID;
                fpvtotaldisp = RADEON_FP_CRTC_V_TOTAL_DISP;
                fphtotaldisp = RADEON_FP_CRTC_H_TOTAL_DISP;
                break;
        case 1:
                gencntl = RADEON_CRTC2_GEN_CNTL;
                htotaldisp = RADEON_CRTC2_H_TOTAL_DISP;
                hsyncstrt = RADEON_CRTC2_H_SYNC_STRT_WID;
                vtotaldisp = RADEON_CRTC2_V_TOTAL_DISP;
                vsyncstrt = RADEON_CRTC2_V_SYNC_STRT_WID;
                fpvsyncstrt = RADEON_FP_V2_SYNC_STRT_WID;
                fphsyncstrt = RADEON_FP_H2_SYNC_STRT_WID;
                /* XXX these registers don't seem to exist */
                fpvtotaldisp = 0;//RADEON_FP_CRTC2_V_TOTAL_DISP;
                fphtotaldisp = 0;//RADEON_FP_CRTC2_H_TOTAL_DISP;
                break;
        default:
                panic("Bad CRTC!");
                break;
        }

        /*
         * CRTC_GEN_CNTL - depth, accelerator mode, etc.
         */
        /* only bother with 32bpp and 8bpp */
        v = dp->rd_format << RADEON_CRTC_PIX_WIDTH_SHIFT;

        if (crtc == 1) {
                v |= RADEON_CRTC2_CRT2_ON | RADEON_CRTC2_EN;
        } else {
                v |= RADEON_CRTC_EXT_DISP_EN | RADEON_CRTC_EN;
        }

        if (mode->flags & VID_DBLSCAN)
                v |= RADEON_CRTC2_DBL_SCAN_EN;

        if (mode->flags & VID_INTERLACE)
                v |= RADEON_CRTC2_INTERLACE_EN;

        if (mode->flags & VID_CSYNC) {
                v |= RADEON_CRTC2_CSYNC_EN;
                if (crtc == 1)
                        v |= RADEON_CRTC2_VSYNC_TRISTAT;
        }
        
        PUT32(sc, gencntl, v);
        DPRINTF(("CRTC%s_GEN_CNTL = %08x\n", crtc ? "2" : "", v));

        /*
         * CRTC_EXT_CNTL - set ATI linear and EXT_CNT. Do NOT preserve
         * HSYNC_DIS/VSYNC_DIS: on cards not POSTed by system firmware
         * (RADEONFB_BIOS_INIT) the BIOS init tables may leave the syncs
         * disabled, and nothing else along the modeswitch path would
         * re-enable them... (Would result in black screen)
         */
        v = GET32(sc, RADEON_CRTC_EXT_CNTL);
        if (crtc == 0) {
                v &= RADEON_CRTC_DISPLAY_DIS;
                v |= RADEON_XCRT_CNT_EN | RADEON_VGA_ATI_LINEAR;
                if (mode->flags & VID_CSYNC)
                        v |= RADEON_CRTC_VSYNC_TRISTAT;
        }
        /* unconditional turn on CRT, in case first CRTC is DFP */
        v |= RADEON_CRTC_CRT_ON;
        PUT32(sc, RADEON_CRTC_EXT_CNTL, v);
        PRINTREG(RADEON_CRTC_EXT_CNTL);

        hd = ((GET32(sc, htotaldisp) >> 16) + 1) * 8;
        vd = (GET32(sc, vtotaldisp) >> 16) + 1;
        DPRINTF(("res %d x %d\n", hd, vd));

        if ((hd != mode->hdisplay) || (vd != mode->vdisplay)) {

                /*
                 * H_TOTAL_DISP
                 */
                v = ((mode->hdisplay / 8) - 1) << 16;
                v |= (mode->htotal / 8) - 1;
                PRINTREG(RADEON_CRTC_H_TOTAL_DISP);
                PUT32(sc, htotaldisp, v);
                DPRINTF(("CRTC%s_H_TOTAL_DISP = %08x\n", crtc ? "2" : "", v));
                if (fphtotaldisp) {
                        PRINTREG(RADEON_FP_CRTC_H_TOTAL_DISP);
                        PUT32(sc, fphtotaldisp, v);
                        DPRINTF(("FP_H%s_TOTAL_DISP = %08x\n", crtc ? "2" : "", v));
                }
                /*
                 * H_SYNC_STRT_WID
                 */
                v = (((mode->hsync_end - mode->hsync_start) / 8) << 16);
                v |= (mode->hsync_start - 8);   /* match xf86-video-radeon */
                if (mode->flags & VID_NHSYNC)
                        v |= RADEON_CRTC_H_SYNC_POL;
                PUT32(sc, hsyncstrt, v);
                DPRINTF(("CRTC%s_H_SYNC_STRT_WID = %08x\n", crtc ? "2" : "", v));
                if (fphsyncstrt) {
                        PUT32(sc, fphsyncstrt, v);
                        DPRINTF(("FP_H%s_SYNC_STRT_WID = %08x\n", crtc ? "2" : "", v));
                }

                /*
                 * V_TOTAL_DISP
                 */
                v = ((mode->vdisplay - 1) << 16);
                v |= (mode->vtotal - 1);
                PUT32(sc, vtotaldisp, v);
                DPRINTF(("CRTC%s_V_TOTAL_DISP = %08x\n", crtc ? "2" : "", v));
                if (fpvtotaldisp) {
                        PUT32(sc, fpvtotaldisp, v);
                        DPRINTF(("FP_V%s_TOTAL_DISP = %08x\n", crtc ? "2" : "", v));
                }

                /*
                 * V_SYNC_STRT_WID
                 */
                v = ((mode->vsync_end - mode->vsync_start) << 16);
                v |= (mode->vsync_start - 1);
                if (mode->flags & VID_NVSYNC)
                        v |= RADEON_CRTC_V_SYNC_POL;
                PUT32(sc, vsyncstrt, v);
                DPRINTF(("CRTC%s_V_SYNC_STRT_WID = %08x\n", crtc ? "2" : "", v));
                if (fpvsyncstrt) {
                        PUT32(sc, fpvsyncstrt, v);
                        DPRINTF(("FP_V%s_SYNC_STRT_WID = %08x\n", crtc ? "2" : "", v));
                }
        }
        radeonfb_program_vclk(sc, mode->dot_clock, crtc, flags);

        switch (crtc) {
        case 0:
                PUT32(sc, RADEON_CRTC_OFFSET, 0);
                PUT32(sc, RADEON_CRTC_OFFSET_CNTL, 0);
                PUT32(sc, RADEON_CRTC_PITCH, pitch);
                CLR32(sc, RADEON_DISP_MERGE_CNTL, RADEON_DISP_RGB_OFFSET_EN);

                CLR32(sc, RADEON_CRTC_EXT_CNTL,
                    RADEON_CRTC_VSYNC_DIS | RADEON_CRTC_HSYNC_DIS |
                    RADEON_CRTC_DISPLAY_DIS /* | RADEON_CRTC_DISP_REQ_EN_B */);
                CLR32(sc, RADEON_CRTC_GEN_CNTL, RADEON_CRTC_DISP_REQ_EN_B);
                PRINTREG(RADEON_CRTC_EXT_CNTL);
                PRINTREG(RADEON_CRTC_GEN_CNTL);
                PRINTREG(RADEON_CLOCK_CNTL_INDEX);
                break;

        case 1:
                PUT32(sc, RADEON_CRTC2_OFFSET, 0);
                PUT32(sc, RADEON_CRTC2_OFFSET_CNTL, 0);
                PUT32(sc, RADEON_CRTC2_PITCH, pitch);
                CLR32(sc, RADEON_DISP2_MERGE_CNTL, RADEON_DISP2_RGB_OFFSET_EN);
                CLR32(sc, RADEON_CRTC2_GEN_CNTL,
                    RADEON_CRTC2_VSYNC_DIS |
                    RADEON_CRTC2_HSYNC_DIS |
                    RADEON_CRTC2_DISP_DIS | RADEON_CRTC2_DISP_REQ_EN_B);
                PRINTREG(RADEON_CRTC2_GEN_CNTL);
                break;
        }

        /*
         * Program the TMDS PLL for this dot clock - handle cards that 
         * never POSTed.
         */
        if (sc->sc_ports[index].rp_tmds_type == RADEON_TMDS_INT) {
                int     i;

                for (i = 0; i < 4; i++) {
                        if ((uint32_t)(mode->dot_clock / 10) <=
                            sc->sc_tmds_pll[i].rtp_freq) {
                                uint32_t tpll = sc->sc_tmds_pll[i].rtp_pll;

                                /*
                                 * RV280 wants bit 22 set here, the bit
                                 * reads back inverted, DON'T trust the
                                 * readback!
                                 */
                                if (sc->sc_family == RADEON_RV280)
                                        tpll |= (1 << 22);
                                PUT32(sc, RADEON_TMDS_PLL_CNTL, tpll);
                                PRINTREG(RADEON_TMDS_PLL_CNTL);
                                break;
                        }
                }
        }
}

int
radeonfb_isblank(struct radeonfb_display *dp)
{
        struct radeonfb_softc   *sc = dp->rd_softc;
        uint32_t        reg, mask;

        if(!dp->rd_softc->sc_mapped)
                return 1;

        if (IS_AVIVO(sc)) {
                reg = GET32(sc, AVIVO_D1CRTC_CONTROL);
                return ((reg & AVIVO_CRTC_EN) == 0);
        }

        if (dp->rd_crtcs[0].rc_number) {
                reg = RADEON_CRTC2_GEN_CNTL;
                mask = RADEON_CRTC2_DISP_DIS;
        } else {
                reg = RADEON_CRTC_EXT_CNTL;
                mask = RADEON_CRTC_DISPLAY_DIS;
        }
        return ((GET32(dp->rd_softc, reg) & mask) ? 1 : 0);
}

void
radeonfb_blank(struct radeonfb_display *dp, int blank)
{
        struct radeonfb_softc   *sc = dp->rd_softc;
        uint32_t                reg, mask;
        uint32_t                fpreg, fpval;
        int                     i;


        if (!sc->sc_mapped)
                return;

        if(IS_AVIVO(sc)) {

                /*
                 * XXX
                 * I don't know how to turn the sunc outputs off for DPMS
                 * power control, so for now just turn the entire CRTC off
                 */
                if (blank) {
                        CLR32(sc, AVIVO_D1CRTC_CONTROL, AVIVO_CRTC_EN);
                        CLR32(sc, AVIVO_D2CRTC_CONTROL, AVIVO_CRTC_EN);
                } else {
                        SET32(sc, AVIVO_D1CRTC_CONTROL, AVIVO_CRTC_EN);
                        SET32(sc, AVIVO_D2CRTC_CONTROL, AVIVO_CRTC_EN);
                }
                return;
        }
        /* non-AVIVO case */
        for (i = 0; i < dp->rd_ncrtcs; i++) {

                if (dp->rd_crtcs[i].rc_number) {
                        reg = RADEON_CRTC2_GEN_CNTL;
                        mask = RADEON_CRTC2_DISP_DIS;
                        fpreg = RADEON_FP2_GEN_CNTL;
                        fpval = RADEON_FP2_ON;
                } else {
                        reg = RADEON_CRTC_EXT_CNTL;
                        mask = RADEON_CRTC_DISPLAY_DIS;
                        fpreg = RADEON_FP_GEN_CNTL;
                        fpval = RADEON_FP_FPON;
                }

                if (blank) {
                        SET32(sc, reg, mask);
                        CLR32(sc, fpreg, fpval);
                } else {
                        if (dp->rd_crtcs[i].rc_number == 0)
                                mask |= RADEON_CRTC_HSYNC_DIS |
                                    RADEON_CRTC_VSYNC_DIS;
                        CLR32(sc, reg, mask);
                        SET32(sc, fpreg, fpval);
                }
        }
        PRINTREG(RADEON_FP_GEN_CNTL);
        PRINTREG(RADEON_FP2_GEN_CNTL);
}

void
radeonfb_init_screen(void *cookie, struct vcons_screen *scr, int existing,
    long *defattr)
{
        struct radeonfb_display *dp = cookie;
        struct rasops_info *ri = &scr->scr_ri;

        /* initialize font subsystem */
        wsfont_init();

        scr->scr_flags |= VCONS_LOADFONT;

        DPRINTF(("init screen called, existing %d\n", existing));

        ri->ri_depth = dp->rd_bpp;
        ri->ri_width = dp->rd_virtx;
        ri->ri_height = dp->rd_virty;
        ri->ri_stride = dp->rd_stride;
        ri->ri_flg = RI_CENTER | RI_FULLCLEAR;
        switch (ri->ri_depth) {
                case 8:
                        ri->ri_flg |= RI_ENABLE_ALPHA | RI_8BIT_IS_RGB | RI_PREFER_ALPHA;
                        break;
                case 32:
                        ri->ri_flg |= RI_ENABLE_ALPHA | RI_PREFER_ALPHA;
                        /* we run radeons in RGB even on SPARC hardware */
                        ri->ri_rnum = 8;
                        ri->ri_gnum = 8;
                        ri->ri_bnum = 8;
                        ri->ri_rpos = 16;
                        ri->ri_gpos = 8;
                        ri->ri_bpos = 0;
                        break;
        }

        ri->ri_bits = (void *)dp->rd_fbptr;

#ifdef VCONS_DRAW_INTR
        scr->scr_flags |= VCONS_DONT_READ;
#endif

        if (existing) {
                ri->ri_flg |= RI_CLEAR;

                /* start a modeswitch now */
                //radeonfb_modeswitch(dp);
        }

        /*
         * XXX: font selection should be based on properties, with some
         * normal/reasonable default.
         */

        /* initialize and look for an initial font */
        rasops_init(ri, 0, 0);
        ri->ri_caps = WSSCREEN_UNDERLINE | WSSCREEN_HILIT |
                    WSSCREEN_WSCOLORS | WSSCREEN_REVERSE | WSSCREEN_RESIZE;

        rasops_reconfig(ri, dp->rd_virty / ri->ri_font->fontheight,
                    dp->rd_virtx / ri->ri_font->fontwidth);

        /* enable acceleration */
        dp->rd_putchar = ri->ri_ops.putchar;
        ri->ri_ops.copyrows = radeonfb_copyrows;
        ri->ri_ops.copycols = radeonfb_copycols;
        ri->ri_ops.eraserows = radeonfb_eraserows;
        ri->ri_ops.erasecols = radeonfb_erasecols;
        ri->ri_ops.allocattr = radeonfb_allocattr;
        /* pick a putchar method based on font and Radeon model */
        if (ri->ri_font->stride < ri->ri_font->fontwidth) {
                /* got a bitmap font */
#if !defined(RADEONFB_ALWAYS_ACCEL_PUTCHAR)
                if (IS_R300(dp->rd_softc) && 0) {
                        /*
                         * radeonfb_putchar() doesn't work right on some R3xx
                         * so we use software drawing here, the wrapper just
                         *  makes sure the engine is idle before scribbling
                         * into vram
                         */
                        ri->ri_ops.putchar = radeonfb_putchar_wrapper;
                } else
#endif
                        ri->ri_ops.putchar = radeonfb_putchar;
        } else {
                /* got an alpha font */
                switch(ri->ri_depth) {
                        case 32:
                                ri->ri_ops.putchar = radeonfb_putchar_aa32;
                                break;
                        case 8:
                                ri->ri_ops.putchar = radeonfb_putchar_aa8;
                                break;
                        default:
                                /* XXX this should never happen */
                                panic("%s: depth is not 8 or 32 but we got an" \
                                         " alpha font?!", __func__);
                }
        }
        ri->ri_ops.cursor = radeonfb_cursor;
}

static uint32_t
radeonfb_avivo_INMC(struct radeonfb_softc *sc, uint32_t addr)
{
        uint32_t data;

        PUT32(sc, AVIVO_MC_INDEX, (addr & 0xff) | 0x7f0000);
        (void)GET32(sc, AVIVO_MC_INDEX);
        data = GET32(sc, AVIVO_MC_DATA);
        PUT32(sc, AVIVO_MC_INDEX, 0);
        (void)GET32(sc, AVIVO_MC_INDEX);
        return data;
}

static void
radeonfb_avivo_OUTMC(struct radeonfb_softc *sc, uint32_t addr, uint32_t data)
{

        PUT32(sc, AVIVO_MC_INDEX, (addr & 0xff) | 0x7f0000);
        (void)GET32(sc, AVIVO_MC_INDEX);
        PUT32(sc, AVIVO_MC_DATA, data);
        PUT32(sc, AVIVO_MC_INDEX, 0);
        (void)GET32(sc, AVIVO_MC_INDEX);
}

void
radeonfb_set_fbloc(struct radeonfb_softc *sc)
{
        uint32_t        gen = 0, ext = 0, gen2 = 0;
        uint32_t        agploc, aperbase, apersize, mcfbloc;


        if (IS_AVIVO(sc)) {
                agploc = radeonfb_avivo_INMC(sc, R520_MC_AGP_LOCATION);
        } else {
                gen = GET32(sc, RADEON_CRTC_GEN_CNTL);
                /* XXX */
                ext = GET32(sc, RADEON_CRTC_EXT_CNTL) & ~RADEON_CRTC_DISPLAY_DIS;
                agploc = GET32(sc, RADEON_MC_AGP_LOCATION);
                PUT32(sc, RADEON_CRTC_GEN_CNTL, gen | RADEON_CRTC_DISP_REQ_EN_B);
                PUT32(sc, RADEON_CRTC_EXT_CNTL, ext | RADEON_CRTC_DISPLAY_DIS);
#if 0
                PUT32(sc, RADEON_CRTC_GEN_CNTL, gen | RADEON_CRTC_DISPLAY_DIS);
                PUT32(sc, RADEON_CRTC_EXT_CNTL, ext | RADEON_CRTC_DISP_REQ_EN_B);
#endif

                if (HAS_CRTC2(sc)) {
                        gen2 = GET32(sc, RADEON_CRTC2_GEN_CNTL);
                        PUT32(sc, RADEON_CRTC2_GEN_CNTL, 
                            gen2 | RADEON_CRTC2_DISP_REQ_EN_B);
                }

                delay(100000);
        }

        aperbase = GET32(sc, RADEON_CONFIG_APER_0_BASE);
        apersize = GET32(sc, RADEON_CONFIG_APER_SIZE);


        mcfbloc = (aperbase >> 16) |
            ((aperbase + (apersize - 1)) & 0xffff0000);

        sc->sc_aperbase = (mcfbloc & 0xffff) << 16;
        sc->sc_memsz = apersize;
        DPRINTF(("aperbase = %08x\n", aperbase));

        if (((agploc & 0xffff) << 16) !=
            ((mcfbloc & 0xffff0000U) + 0x10000)) {
                agploc = mcfbloc & 0xffff0000U;
                agploc |= ((agploc + 0x10000) >> 16);
        }

        PUT32(sc, RADEON_HOST_PATH_CNTL, 0);

        if (IS_AVIVO(sc)) {
                radeonfb_avivo_OUTMC(sc, R520_MC_FB_LOCATION, mcfbloc);
                radeonfb_avivo_OUTMC(sc, R520_MC_AGP_LOCATION, agploc);
                PRINTREG(AVIVO_HDP_FB_LOCATION);
                DPRINTF((" FB loc %08x\n", radeonfb_avivo_INMC(sc, R520_MC_FB_LOCATION)));
                DPRINTF(("AGP loc %08x\n", radeonfb_avivo_INMC(sc, R520_MC_AGP_LOCATION)));
        } else {
                PUT32(sc, RADEON_MC_FB_LOCATION, mcfbloc);
                PUT32(sc, RADEON_MC_AGP_LOCATION, agploc);
                PRINTREG(RADEON_MC_FB_LOCATION);
                PRINTREG(RADEON_MC_AGP_LOCATION);

                PUT32(sc, RADEON_DISPLAY_BASE_ADDR, sc->sc_aperbase);

                if (HAS_CRTC2(sc))
                        PUT32(sc, RADEON_DISPLAY2_BASE_ADDR, sc->sc_aperbase);

                PUT32(sc, RADEON_OV0_BASE_ADDR, sc->sc_aperbase);
                delay(100000);

                PUT32(sc, RADEON_CRTC_GEN_CNTL, gen);
                PUT32(sc, RADEON_CRTC_EXT_CNTL, ext);

                if (HAS_CRTC2(sc))
                        PUT32(sc, RADEON_CRTC2_GEN_CNTL, gen2);
        }
#if 0
        /* XXX: what is this AGP garbage? :-) */
        PUT32(sc, RADEON_AGP_CNTL, 0x00100000);
#endif
}

void
radeonfb_init_misc(struct radeonfb_softc *sc)
{
        PUT32(sc, RADEON_BUS_CNTL,
            RADEON_BUS_MASTER_DIS |
            RADEON_BUS_PREFETCH_MODE_ACT |
            RADEON_BUS_PCI_READ_RETRY_EN |
            RADEON_BUS_PCI_WRT_RETRY_EN |
            (3 << RADEON_BUS_RETRY_WS_SHIFT) |
            RADEON_BUS_MSTR_RD_MULT |
            RADEON_BUS_MSTR_RD_LINE |
            RADEON_BUS_RD_DISCARD_EN |
            RADEON_BUS_MSTR_DISCONNECT_EN |
            RADEON_BUS_READ_BURST);

        PUT32(sc, RADEON_BUS_CNTL1, 0xf0);
        /* PUT32(sc, RADEON_SEPROM_CNTL1, 0x09ff0000); */
        PUT32(sc, RADEON_FCP_CNTL, RADEON_FCP0_SRC_GND);
        PUT32(sc, RADEON_RBBM_CNTL,
            (3 << RADEON_RB_SETTLE_SHIFT) |
            (4 << RADEON_ABORTCLKS_HI_SHIFT) |
            (4 << RADEON_ABORTCLKS_CP_SHIFT) |
            (4 << RADEON_ABORTCLKS_CFIFO_SHIFT));

        /* XXX: figure out what these mean! */
        PUT32(sc, RADEON_AGP_CNTL, 0x00100000);
        PUT32(sc, RADEON_HOST_PATH_CNTL, 0);
#if 0
        PUT32(sc, RADEON_DISP_MISC_CNTL, 0x5bb00400);
#endif

        PUT32(sc, RADEON_GEN_INT_CNTL, 0);
        PUT32(sc, RADEON_GEN_INT_STATUS, GET32(sc, RADEON_GEN_INT_STATUS));
}

static void
radeonfb_putpal(struct radeonfb_display *dp, int idx, int r, int g, int b)
{
        struct radeonfb_softc *sc = dp->rd_softc;
        int             crtc, cc;
        uint32_t        vclk;

        if (IS_AVIVO(sc)) {
                for (cc = 0; cc < dp->rd_ncrtcs; cc++) {
                        crtc = dp->rd_crtcs[cc].rc_number;

                        if (crtc)
                                PUT32(sc, AVIVO_DC_LUT_RW_SELECT, 1);
                        else
                                PUT32(sc, AVIVO_DC_LUT_RW_SELECT, 0);

                        PUT32(sc, AVIVO_DC_LUT_RW_INDEX, idx);
                        PUT32(sc, AVIVO_DC_LUT_30_COLOR,
                            (r << 22) | (g << 12) | (b << 2));
                }
        } else {
                vclk = GETPLL(sc, RADEON_VCLK_ECP_CNTL);
                PUTPLL(sc, RADEON_VCLK_ECP_CNTL,
                    vclk & ~RADEON_PIXCLK_DAC_ALWAYS_ONb);

                /* init the palette for every CRTC used by this display */
                for (cc = 0; cc < dp->rd_ncrtcs; cc++) {
                        crtc = dp->rd_crtcs[cc].rc_number;

                        if (crtc)
                                SET32(sc, RADEON_DAC_CNTL2,
                                    RADEON_DAC2_PALETTE_ACC_CTL);
                        else
                                CLR32(sc, RADEON_DAC_CNTL2,
                                    RADEON_DAC2_PALETTE_ACC_CTL);

                        PUT32(sc, RADEON_PALETTE_INDEX, idx);
                        PUT32(sc, RADEON_PALETTE_30_DATA,
                            (r << 22) | (g << 12) | (b << 2));
                }

                PUTPLL(sc, RADEON_VCLK_ECP_CNTL, vclk);
        }
}

/*
 * This loads a linear color map for true color.
 */
void
radeonfb_init_palette(struct radeonfb_display *dp)
{
        int             i;

#define DAC_WIDTH ((1 << 10) - 1)
#define CLUT_WIDTH ((1 << 8) - 1)
#define CLUT_COLOR(i)      ((i * DAC_WIDTH * 2 / CLUT_WIDTH + 1) / 2)

        if (dp->rd_bpp == 8) {

                /* R3G3B2 palette */
                uint32_t tmp, r, g, b;
 
                for (i = 0; i <= CLUT_WIDTH; ++i) {
                        tmp = i & 0xe0;

                        /*
                         * replicate bits so 0xe0 maps to a red value of 0xff
                         * in order to make white look actually white
                         */
                        tmp |= (tmp >> 3) | (tmp >> 6);
                        r = tmp;

                        tmp = (i & 0x1c) << 3;
                        tmp |= (tmp >> 3) | (tmp >> 6);
                        g = tmp;

                        tmp = (i & 0x03) << 6;
                        tmp |= tmp >> 2;
                        tmp |= tmp >> 4;
                        b = tmp;

                        dp->rd_cmap_red[i] = r;
                        dp->rd_cmap_green[i] = g;
                        dp->rd_cmap_blue[i] = b;
                        radeonfb_putpal(dp, i, r, g, b);
                }
        } else {
                /* linear ramp */
                for (i = 0; i <= CLUT_WIDTH; ++i) {
                        radeonfb_putpal(dp, i, i, i, i);
                }
        }
}

static int
radeonfb_putcmap(struct radeonfb_display *dp, struct wsdisplay_cmap *cm)
{
        u_char *r, *g, *b;
        u_int index = cm->index;
        u_int count = cm->count;
        int i, error;
        u_char rbuf[256], gbuf[256], bbuf[256];

#ifdef GENFB_DEBUG
        aprint_debug("putcmap: %d %d\n",index, count);
#endif
        if (index >= 256 || count > 256 - index)
                return EINVAL;
        error = copyin(cm->red, &rbuf[index], count);
        if (error)
                return error;
        error = copyin(cm->green, &gbuf[index], count);
        if (error)
                return error;
        error = copyin(cm->blue, &bbuf[index], count);
        if (error)
                return error;

        memcpy(&dp->rd_cmap_red[index], &rbuf[index], count);
        memcpy(&dp->rd_cmap_green[index], &gbuf[index], count);
        memcpy(&dp->rd_cmap_blue[index], &bbuf[index], count);

        r = &dp->rd_cmap_red[index];
        g = &dp->rd_cmap_green[index];
        b = &dp->rd_cmap_blue[index];

        for (i = 0; i < count; i++) {
                radeonfb_putpal(dp, index, *r, *g, *b);
                index++;
                r++, g++, b++;
        }
        return 0;
}

static int
radeonfb_getcmap(struct radeonfb_display *dp, struct wsdisplay_cmap *cm)
{
        u_int index = cm->index;
        u_int count = cm->count;
        int error;

        if (index >= 256 || count > 256 - index)
                return EINVAL;

        error = copyout(&dp->rd_cmap_red[index],   cm->red,   count);
        if (error)
                return error;
        error = copyout(&dp->rd_cmap_green[index], cm->green, count);
        if (error)
                return error;
        error = copyout(&dp->rd_cmap_blue[index],  cm->blue,  count);
        if (error)
                return error;

        return 0;
}

/*
 * Bugs in some R300 hardware requires this when accessing CLOCK_CNTL_INDEX.
 */
void
radeonfb_r300cg_workaround(struct radeonfb_softc *sc)
{
        uint32_t        tmp, save;

        save = GET32(sc, RADEON_CLOCK_CNTL_INDEX);
        tmp = save & ~(0x3f | RADEON_PLL_WR_EN);
        PUT32(sc, RADEON_CLOCK_CNTL_INDEX, tmp);
        tmp = GET32(sc, RADEON_CLOCK_CNTL_DATA);
        PUT32(sc, RADEON_CLOCK_CNTL_INDEX, save);
}

/*
 * Acceleration entry points.
 */

/* this one draws characters using bitmap fonts */
static void
radeonfb_putchar(void *cookie, int row, int col, u_int c, long attr)
{
        struct rasops_info      *ri = cookie;
        struct vcons_screen     *scr = ri->ri_hw;
        struct radeonfb_display *dp = scr->scr_cookie;
        struct radeonfb_softc   *sc = dp->rd_softc;
        struct wsdisplay_font   *font = PICK_FONT(ri, c);
        uint32_t                w, h;
        int                     xd, yd, offset, i;
        uint32_t                bg, fg, gmc;
        uint32_t                reg;
        uint8_t                 *data8;
        uint16_t                *data16;
        uint32_t                *data32;
        void                    *data;

        if (dp->rd_wsmode != WSDISPLAYIO_MODE_EMUL)
                return;

        if (!CHAR_IN_FONT(c, font))
                return;

        w = font->fontwidth;
        h = font->fontheight;

        bg = ri->ri_devcmap[(attr >> 16) & 0xf];
        fg = ri->ri_devcmap[(attr >> 24) & 0xf];

        xd = ri->ri_xorigin + col * w;
        yd = ri->ri_yorigin + row * h;

        if (c == 0x20) {
                radeonfb_rectfill(dp, xd, yd, w, h, bg);
                if (attr & WSATTR_UNDERLINE)
                        radeonfb_rectfill(dp, xd, yd + h - 2, w, 1, fg);
                return;
        }
        data = WSFONT_GLYPH(c, font);

        gmc = dp->rd_format << RADEON_GMC_DST_DATATYPE_SHIFT;

        radeonfb_wait_fifo(sc, 9);

        PUT32(sc, RADEON_DP_GUI_MASTER_CNTL,
            RADEON_GMC_BRUSH_NONE |
            RADEON_GMC_SRC_DATATYPE_MONO_FG_BG |
            RADEON_GMC_DST_CLIPPING |
            RADEON_ROP3_S |
            RADEON_DP_SRC_SOURCE_HOST_DATA |
            RADEON_GMC_CLR_CMP_CNTL_DIS |
            RADEON_GMC_WR_MSK_DIS |
            gmc);

        PUT32(sc, RADEON_SC_LEFT, xd);
        PUT32(sc, RADEON_SC_RIGHT, xd + w);
        PUT32(sc, RADEON_DP_SRC_FRGD_CLR, fg);
        PUT32(sc, RADEON_DP_SRC_BKGD_CLR, bg);
        PUT32(sc, RADEON_DP_CNTL,
            RADEON_DST_X_LEFT_TO_RIGHT |
            RADEON_DST_Y_TOP_TO_BOTTOM);

        PUT32(sc, RADEON_SRC_X_Y, 0);
        offset = 32 - (font->stride << 3);
        PUT32(sc, RADEON_DST_X_Y, ((xd - offset) << 16) | yd);
        PUT32(sc, RADEON_DST_WIDTH_HEIGHT, (32 << 16) | h);

        radeonfb_wait_fifo(sc, h);
        if (attr & WSATTR_HILIT) {
                switch (font->stride) {
                        case 1: {
                                data8 = data;
                                for (i = 0; i < h; i++) {
                                        reg = *data8;
                                        reg |= reg >> 1;
#if BYTE_ORDER == LITTLE_ENDIAN
                                        reg = reg << 24;
#endif
                                        bus_space_write_stream_4(sc->sc_regt, 
                                            sc->sc_regh, RADEON_HOST_DATA0, reg);
                                        data8++;
                                }
                                break;
                        }
                        case 2: {
                                data16 = data;
                                for (i = 0; i < h; i++) {
                                        reg = *data16;
                                        reg |= reg >> 1;
#if BYTE_ORDER == LITTLE_ENDIAN
                                        reg = reg << 16;
#endif
                                        bus_space_write_stream_4(sc->sc_regt, 
                                            sc->sc_regh, RADEON_HOST_DATA0, reg);
                                        data16++;
                                }
                                break;
                        }
                        case 4: {
                                data32 = data;
                                for (i = 0; i < h; i++) {
                                        reg = *data32;
                                        reg |= reg >> 1;
                                        bus_space_write_stream_4(sc->sc_regt, 
                                            sc->sc_regh, RADEON_HOST_DATA0, reg);
                                        data32++;
                                }
                                break;
                        }
                }
        } else {
                switch (font->stride) {
                        case 1: {
                                data8 = data;
                                for (i = 0; i < h; i++) {
                                        reg = *data8;
#if BYTE_ORDER == LITTLE_ENDIAN
                                        reg = reg << 24;
#endif
                                        bus_space_write_stream_4(sc->sc_regt, 
                                            sc->sc_regh, RADEON_HOST_DATA0, reg);
                                        data8++;
                                }
                                break;
                        }
                        case 2: {
                                data16 = data;
                                for (i = 0; i < h; i++) {
                                        reg = *data16;
#if BYTE_ORDER == LITTLE_ENDIAN
                                        reg = reg << 16;
#endif
                                        bus_space_write_stream_4(sc->sc_regt, 
                                            sc->sc_regh, RADEON_HOST_DATA0, reg);
                                        data16++;
                                }
                                break;
                        }
                        case 4: {
                                data32 = data;
                                for (i = 0; i < h; i++) {
                                        reg = *data32;
                                        bus_space_write_stream_4(sc->sc_regt, 
                                            sc->sc_regh, RADEON_HOST_DATA0, reg);
                                        data32++;
                                }
                                break;
                        }
                }
        }
        if (attr & WSATTR_UNDERLINE)
                radeonfb_rectfill(dp, xd, yd + h - 2, w, 1, fg);
}

/* ... while this one is for anti-aliased ones */
static void
radeonfb_putchar_aa32(void *cookie, int row, int col, u_int c, long attr)
{
        struct rasops_info      *ri = cookie;
        struct vcons_screen     *scr = ri->ri_hw;
        struct radeonfb_display *dp = scr->scr_cookie;
        struct radeonfb_softc   *sc = dp->rd_softc;
        struct wsdisplay_font   *font = PICK_FONT(ri, c);
        uint32_t                bg, fg, gmc;
        uint8_t                 *data;
        int                     w, h, xd, yd;
        int                     i, r, g, b, aval;
        int                     rf, gf, bf, rb, gb, bb;
        uint32_t                pixel;
        int rv;

        if (dp->rd_wsmode != WSDISPLAYIO_MODE_EMUL)
                return;

        if (!CHAR_IN_FONT(c, font))
                return;

        w = font->fontwidth;
        h = font->fontheight;

        bg = ri->ri_devcmap[(attr >> 16) & 0xf];
        fg = ri->ri_devcmap[(attr >> 24) & 0xf];

        xd = ri->ri_xorigin + col * w;
        yd = ri->ri_yorigin + row * h;

        if (c == 0x20) {
                radeonfb_rectfill(dp, xd, yd, w, h, bg);
                if (attr & WSATTR_UNDERLINE)
                        radeonfb_rectfill(dp, xd, yd + h - 2, w, 1, fg);
                return;
        }
        rv = glyphcache_try(&dp->rd_gc, c, xd, yd, attr);
        if (rv == GC_OK)
                return;

        data = WSFONT_GLYPH(c, font);

        gmc = dp->rd_format << RADEON_GMC_DST_DATATYPE_SHIFT;

        radeonfb_wait_fifo(sc, 5);

        PUT32(sc, RADEON_DP_GUI_MASTER_CNTL,
            RADEON_GMC_BRUSH_NONE |
            RADEON_GMC_SRC_DATATYPE_COLOR |
            RADEON_ROP3_S |
            RADEON_DP_SRC_SOURCE_HOST_DATA |
            RADEON_GMC_CLR_CMP_CNTL_DIS |
            RADEON_GMC_WR_MSK_DIS |
            gmc);

        PUT32(sc, RADEON_DP_CNTL,
            RADEON_DST_X_LEFT_TO_RIGHT |
            RADEON_DST_Y_TOP_TO_BOTTOM);

        PUT32(sc, RADEON_SRC_X_Y, 0);
        PUT32(sc, RADEON_DST_X_Y, (xd << 16) | yd);
        PUT32(sc, RADEON_DST_WIDTH_HEIGHT, (w << 16) | h);

        rf = (fg >> 16) & 0xff;
        rb = (bg >> 16) & 0xff;
        gf = (fg >> 8) & 0xff;
        gb = (bg >> 8) & 0xff;
        bf =  fg & 0xff;
        bb =  bg & 0xff;

        /*
         * I doubt we can upload data faster than even the slowest Radeon
         * could process them, especially when doing the alpha blending stuff
         * along the way, so just make sure there's some room in the FIFO and
         * then hammer away
         * As it turns out we can, so make periodic stops to let the FIFO
         * drain.
         */
        radeonfb_wait_fifo(sc, 20);
        for (i = 0; i < ri->ri_fontscale; i++) {
                aval = *data;
                data++;
                if (aval == 0) {
                        pixel = bg;
                } else if (aval == 255) {
                        pixel = fg;
                } else {
                        r = aval * rf + (255 - aval) * rb;
                        g = aval * gf + (255 - aval) * gb;
                        b = aval * bf + (255 - aval) * bb;
                        pixel = (r & 0xff00) << 8 |
                                (g & 0xff00) |
                                (b & 0xff00) >> 8;
                }
                if (i & 16)
                        radeonfb_wait_fifo(sc, 20);
                PUT32(sc, RADEON_HOST_DATA0, pixel);
        }
        if (rv == GC_ADD) {
                glyphcache_add(&dp->rd_gc, c, xd, yd);
        } else if (attr & WSATTR_UNDERLINE)
                radeonfb_rectfill(dp, xd, yd + h - 2, w, 1, fg);
}

static void
radeonfb_putchar_aa8(void *cookie, int row, int col, u_int c, long attr)
{
        struct rasops_info      *ri = cookie;
        struct vcons_screen     *scr = ri->ri_hw;
        struct radeonfb_display *dp = scr->scr_cookie;
        struct radeonfb_softc   *sc = dp->rd_softc;
        struct wsdisplay_font   *font = PICK_FONT(ri, c);
        uint32_t bg, fg, latch = 0, bg8, fg8, pixel, gmc;
        int i, x, y, wi, he, r, g, b, aval;
        int r1, g1, b1, r0, g0, b0, fgo, bgo;
        uint8_t *data8;
        int rv, cnt;

        if (dp->rd_wsmode != WSDISPLAYIO_MODE_EMUL)
                return;

        if (!CHAR_IN_FONT(c, font))
                return;

        wi = font->fontwidth;
        he = font->fontheight;

        bg = ri->ri_devcmap[(attr >> 16) & 0xf];
        fg = ri->ri_devcmap[(attr >> 24) & 0xf];

        x = ri->ri_xorigin + col * wi;
        y = ri->ri_yorigin + row * he;

        if (c == 0x20) {
                radeonfb_rectfill(dp, x, y, wi, he, bg);
                if (attr & WSATTR_UNDERLINE)
                        radeonfb_rectfill(dp, x, y + he - 2, wi, 1, fg);
                return;
        }
        rv = glyphcache_try(&dp->rd_gc, c, x, y, attr);
        if (rv == GC_OK)
                return;

        data8 = WSFONT_GLYPH(c, font);

        gmc = dp->rd_format << RADEON_GMC_DST_DATATYPE_SHIFT;

        radeonfb_wait_fifo(sc, 5);

        PUT32(sc, RADEON_DP_GUI_MASTER_CNTL,
            RADEON_GMC_BRUSH_NONE |
            RADEON_GMC_SRC_DATATYPE_COLOR |
            RADEON_ROP3_S |
            RADEON_DP_SRC_SOURCE_HOST_DATA |
            RADEON_GMC_CLR_CMP_CNTL_DIS |
            RADEON_GMC_WR_MSK_DIS |
            gmc);

        PUT32(sc, RADEON_DP_CNTL,
            RADEON_DST_X_LEFT_TO_RIGHT |
            RADEON_DST_Y_TOP_TO_BOTTOM);

        PUT32(sc, RADEON_SRC_X_Y, 0);
        PUT32(sc, RADEON_DST_X_Y, (x << 16) | y);
        PUT32(sc, RADEON_DST_WIDTH_HEIGHT, (wi << 16) | he);

        /*
         * we need the RGB colours here, so get offsets into rasops_cmap
         */
        fgo = ((attr >> 24) & 0xf) * 3;
        bgo = ((attr >> 16) & 0xf) * 3;

        r0 = rasops_cmap[bgo];
        r1 = rasops_cmap[fgo];
        g0 = rasops_cmap[bgo + 1];
        g1 = rasops_cmap[fgo + 1];
        b0 = rasops_cmap[bgo + 2];
        b1 = rasops_cmap[fgo + 2];
#define R3G3B2(r, g, b) ((r & 0xe0) | ((g >> 3) & 0x1c) | (b >> 6))
        bg8 = R3G3B2(r0, g0, b0);
        fg8 = R3G3B2(r1, g1, b1);

        radeonfb_wait_fifo(sc, 20);
        cnt = 0;
        for (i = 0; i < ri->ri_fontscale; i++) {
                aval = *data8;
                if (aval == 0) {
                        pixel = bg8;
                } else if (aval == 255) {
                        pixel = fg8;
                } else {
                        r = aval * r1 + (255 - aval) * r0;
                        g = aval * g1 + (255 - aval) * g0;
                        b = aval * b1 + (255 - aval) * b0;
                        pixel = ((r & 0xe000) >> 8) |
                                ((g & 0xe000) >> 11) |
                                ((b & 0xc000) >> 14);
                }
                latch |= pixel << (8 * (i & 3));
                /* write in 32bit chunks */
                if ((i & 3) == 3) {
                        PUT32(sc, RADEON_HOST_DATA0, latch);
                        /*
                         * not strictly necessary, old data should be shifted 
                         * out 
                         */
                        latch = 0;
                        cnt++;
                        if (cnt > 16) {
                                cnt = 0;
                                radeonfb_wait_fifo(sc, 20);
                        }
                }
                data8++;
        }
        /* if we have pixels left in latch write them out */
        if ((i & 3) != 0) {
                /*
                 * radeon is weird - apparently leftover pixels are written
                 * from the middle, not from the left as everything else
                 */
                PUT32(sc, RADEON_HOST_DATA0, latch);
        }

        if (rv == GC_ADD) {
                glyphcache_add(&dp->rd_gc, c, x, y);
        } else
                if (attr & WSATTR_UNDERLINE)
                        radeonfb_rectfill(dp, x, y + he - 2, wi, 1, fg);
}

/*
 * wrapper for software character drawing
 * just sync the engine and call rasops*_putchar()
 */

#ifndef RADEONFB_ALWAYS_ACCEL_PUTCHAR
static void
radeonfb_putchar_wrapper(void *cookie, int row, int col, u_int c, long attr)
{
        struct rasops_info      *ri = cookie;
        struct vcons_screen     *scr = ri->ri_hw;
        struct radeonfb_display *dp = scr->scr_cookie;

        radeonfb_engine_idle(dp->rd_softc);
        dp->rd_putchar(ri, row, col, c, attr);
}
#endif

static int
radeonfb_allocattr(void *cookie, int fg0, int bg0, int flg, long *attr)
{
        struct rasops_info *ri = cookie;
        int fg = fg0, bg = bg0;

        if ((flg & WSATTR_BLINK) != 0)
                return EINVAL;

        if ((flg & WSATTR_REVERSE) != 0) {
                fg = bg0;
                bg = fg0;
        }

        if (FONT_IS_ALPHA(ri->ri_font) && ((flg & WSATTR_HILIT) != 0)) {
                fg = fg0 < 8 ? fg0 + 8 : fg0;
        }

        *attr = (bg << 16) | (fg << 24) | flg;
        return 0;
}

static void
radeonfb_eraserows(void *cookie, int row, int nrows, long fillattr)
{
        struct rasops_info      *ri = cookie;
        struct vcons_screen     *scr = ri->ri_hw;
        struct radeonfb_display *dp = scr->scr_cookie;
        uint32_t                x, y, w, h, fg, bg, ul;

        /* XXX: check for full emulation mode? */
        if (dp->rd_wsmode == WSDISPLAYIO_MODE_EMUL) {
                if (row == 0 && nrows == ri->ri_rows) {
                        x = y = 0;
                        w = dp->rd_virtx;
                        h = dp->rd_virty;
                } else {
                        x = ri->ri_xorigin;
                        y = ri->ri_yorigin + ri->ri_font->fontheight * row;
                        w = ri->ri_emuwidth;
                        h = ri->ri_font->fontheight * nrows;
                }

                rasops_unpack_attr(fillattr, &fg, &bg, &ul);
                radeonfb_rectfill(dp, x, y, w, h, ri->ri_devcmap[bg & 0xf]);
        }
}

static void
radeonfb_copyrows(void *cookie, int srcrow, int dstrow, int nrows)
{
        struct rasops_info      *ri = cookie;
        struct vcons_screen     *scr = ri->ri_hw;
        struct radeonfb_display *dp = scr->scr_cookie;
        uint32_t                x, ys, yd, w, h;

        if (dp->rd_wsmode == WSDISPLAYIO_MODE_EMUL) {
                x = ri->ri_xorigin;
                ys = ri->ri_yorigin + ri->ri_font->fontheight * srcrow;
                yd = ri->ri_yorigin + ri->ri_font->fontheight * dstrow;
                w = ri->ri_emuwidth;
                h = ri->ri_font->fontheight * nrows;
                radeonfb_bitblt(dp, x, ys, x, yd, w, h,
                    RADEON_ROP3_S);
        }
}

static void
radeonfb_copycols(void *cookie, int row, int srccol, int dstcol, int ncols)
{
        struct rasops_info      *ri = cookie;
        struct vcons_screen     *scr = ri->ri_hw;
        struct radeonfb_display *dp = scr->scr_cookie;
        uint32_t                xs, xd, y, w, h;

        if (dp->rd_wsmode == WSDISPLAYIO_MODE_EMUL) {
                xs = ri->ri_xorigin + ri->ri_font->fontwidth * srccol;
                xd = ri->ri_xorigin + ri->ri_font->fontwidth * dstcol;
                y = ri->ri_yorigin + ri->ri_font->fontheight * row;
                w = ri->ri_font->fontwidth * ncols;
                h = ri->ri_font->fontheight;
                radeonfb_bitblt(dp, xs, y, xd, y, w, h,
                    RADEON_ROP3_S);
        }
}

static void
radeonfb_erasecols(void *cookie, int row, int startcol, int ncols,
    long fillattr)
{
        struct rasops_info      *ri = cookie;
        struct vcons_screen     *scr = ri->ri_hw;
        struct radeonfb_display *dp = scr->scr_cookie;
        uint32_t                x, y, w, h, fg, bg, ul;

        if (dp->rd_wsmode == WSDISPLAYIO_MODE_EMUL) {
                x = ri->ri_xorigin + ri->ri_font->fontwidth * startcol;
                y = ri->ri_yorigin + ri->ri_font->fontheight * row;
                w = ri->ri_font->fontwidth * ncols;
                h = ri->ri_font->fontheight;

                rasops_unpack_attr(fillattr, &fg, &bg, &ul);
                radeonfb_rectfill(dp, x, y, w, h, ri->ri_devcmap[bg & 0xf]);
        }
}

static void
radeonfb_cursor(void *cookie, int on, int row, int col)
{
        struct rasops_info *ri = cookie;
        struct vcons_screen *scr = ri->ri_hw;
        struct radeonfb_display *dp = scr->scr_cookie;
        int x, y, wi, he;

        wi = ri->ri_font->fontwidth;
        he = ri->ri_font->fontheight;

        if (dp->rd_wsmode == WSDISPLAYIO_MODE_EMUL) {
                x = ri->ri_ccol * wi + ri->ri_xorigin;
                y = ri->ri_crow * he + ri->ri_yorigin;
                /* first turn off the old cursor */
                if (ri->ri_flg & RI_CURSOR) {
                        radeonfb_bitblt(dp, x, y, x, y, wi, he,
                            RADEON_ROP3_Dn);
                        ri->ri_flg &= ~RI_CURSOR;
                }
                ri->ri_crow = row;
                ri->ri_ccol = col;
                /* then (possibly) turn on the new one */
                if (on) {
                        x = ri->ri_ccol * wi + ri->ri_xorigin;
                        y = ri->ri_crow * he + ri->ri_yorigin;
                        radeonfb_bitblt(dp, x, y, x, y, wi, he,
                            RADEON_ROP3_Dn);
                        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;
        }
}

/*
 * Underlying acceleration support.
 */

static void
radeonfb_rectfill(struct radeonfb_display *dp, int dstx, int dsty,
    int width, int height, uint32_t color)
{
        struct radeonfb_softc   *sc = dp->rd_softc;
        uint32_t                gmc;

        gmc = dp->rd_format << RADEON_GMC_DST_DATATYPE_SHIFT;

        radeonfb_wait_fifo(sc, 6);

        PUT32(sc, RADEON_DP_GUI_MASTER_CNTL,
            RADEON_GMC_BRUSH_SOLID_COLOR |
            RADEON_GMC_SRC_DATATYPE_COLOR |
            RADEON_GMC_CLR_CMP_CNTL_DIS |
            RADEON_ROP3_P | gmc);

        PUT32(sc, RADEON_DP_BRUSH_FRGD_CLR, color);
        PUT32(sc, RADEON_DP_WRITE_MASK, 0xffffffff);
        PUT32(sc, RADEON_DP_CNTL,
            RADEON_DST_X_LEFT_TO_RIGHT |
            RADEON_DST_Y_TOP_TO_BOTTOM);
        PUT32(sc, RADEON_DST_Y_X, (dsty << 16) | dstx);
        PUT32(sc, RADEON_DST_WIDTH_HEIGHT, (width << 16) | (height));
}

static void
radeonfb_rectfill_a(void *cookie, int dstx, int dsty,
    int width, int height, long attr)
{
        struct radeonfb_display *dp = cookie;

        radeonfb_rectfill(dp, dstx, dsty, width, height,
            dp->rd_vscreen.scr_ri.ri_devcmap[(attr >> 24 & 0xf)]);
}

static void
radeonfb_bitblt(void *cookie, int srcx, int srcy,
    int dstx, int dsty, int width, int height, int rop)
{
        struct radeonfb_display *dp = cookie;
        struct radeonfb_softc   *sc = dp->rd_softc;
        uint32_t                gmc;
        uint32_t                dir;

        if (dsty < srcy) {
                dir = RADEON_DST_Y_TOP_TO_BOTTOM;
        } else {
                srcy += height - 1;
                dsty += height - 1;
                dir = 0;
        }
        if (dstx < srcx) {
                dir |= RADEON_DST_X_LEFT_TO_RIGHT;
        } else {
                srcx += width - 1;
                dstx += width - 1;
        }

        gmc = dp->rd_format << RADEON_GMC_DST_DATATYPE_SHIFT;
        
        radeonfb_wait_fifo(sc, 6);

        PUT32(sc, RADEON_DP_GUI_MASTER_CNTL,
            RADEON_GMC_BRUSH_NONE |
            RADEON_GMC_SRC_DATATYPE_COLOR |
            RADEON_GMC_CLR_CMP_CNTL_DIS |
            RADEON_DP_SRC_SOURCE_MEMORY |
            rop | gmc);

        PUT32(sc, RADEON_DP_WRITE_MASK, 0xffffffff);
        PUT32(sc, RADEON_DP_CNTL, dir);
        PUT32(sc, RADEON_SRC_Y_X, (srcy << 16) | srcx);
        PUT32(sc, RADEON_DST_Y_X, (dsty << 16) | dstx);
        PUT32(sc, RADEON_DST_WIDTH_HEIGHT, (width << 16) | (height));
}

static void
radeonfb_engine_idle(struct radeonfb_softc *sc)
{

        radeonfb_wait_fifo(sc, 64);
        while ((GET32(sc, RADEON_RBBM_STATUS) &
                        RADEON_RBBM_ACTIVE) != 0);
        radeonfb_engine_flush(sc);
}

static inline void
radeonfb_wait_fifo(struct radeonfb_softc *sc, int n)
{
        int     i;

        for (i = RADEON_TIMEOUT; i; i--) {
                if ((GET32(sc, RADEON_RBBM_STATUS) &
                        RADEON_RBBM_FIFOCNT_MASK) >= n)
                        return;
        }
#ifdef  RADEONFB_DEBUG
        if (!i)
                printf("%s: timed out waiting for fifo (%x)\n",
                    XNAME(sc), GET32(sc, RADEON_RBBM_STATUS));
#endif
}

static void
radeonfb_engine_flush(struct radeonfb_softc *sc)
{
        int     i = 0;

        if (IS_R300(sc) || IS_AVIVO(sc)) {
                SET32(sc, R300_DSTCACHE_CTLSTAT, R300_RB2D_DC_FLUSH_ALL);
                while (GET32(sc, R300_DSTCACHE_CTLSTAT) & R300_RB2D_DC_BUSY) {
                        i++;
                }
        } else {
                SET32(sc, RADEON_RB2D_DSTCACHE_CTLSTAT, 
                    RADEON_RB2D_DC_FLUSH_ALL);
                while (GET32(sc, RADEON_RB2D_DSTCACHE_CTLSTAT) &
                        RADEON_RB2D_DC_BUSY) {
                        i++;
                }
        }
#ifdef DIAGNOSTIC
        if (i > RADEON_TIMEOUT)
                printf("%s: engine flush timed out!\n", XNAME(sc));
#endif
}

static inline void
radeonfb_unclip(struct radeonfb_softc *sc)
{

        radeonfb_wait_fifo(sc, 2);
        PUT32(sc, RADEON_SC_TOP_LEFT, 0);
        PUT32(sc, RADEON_SC_BOTTOM_RIGHT, 0x1fff1fff);
}

static void
radeonfb_engine_init(struct radeonfb_display *dp)
{
        struct radeonfb_softc   *sc = dp->rd_softc;
        uint32_t                pitch;

        /* no 3D */
        PUT32(sc, RADEON_RB3D_CNTL, 0);

        if (IS_AVIVO(sc)) {

#if 0
                /* XXX the xf86-video-radeon does this, causes lockups here */
                psel = GET32(sc, R400_GB_PIPE_SELECT);
                PRINTREG(R400_GB_PIPE_SELECT);
                DPRINTF(("PLL %08x %08x\n", GETPLL(sc, R500_DYN_SCLK_PWMEM_PIPE),
                    (1 | ((psel >> 8) & 0xf) << 4)));
                PUTPLL(sc, R500_DYN_SCLK_PWMEM_PIPE, (1 | ((psel >> 8) & 0xf) << 4));
#endif
                SET32(sc, RADEON_WAIT_UNTIL, RADEON_WAIT_2D_IDLECLEAN | RADEON_WAIT_3D_IDLECLEAN);
                SET32(sc, R300_DST_PIPE_CONFIG, R300_PIPE_AUTO_CONFIG);
                SET32(sc, R300_RB2D_DSTCACHE_MODE, R300_DC_AUTOFLUSH_ENABLE |
                                             R300_DC_DC_DISABLE_IGNORE_PE);
        }

        PRINTREG(RADEON_RB3D_CNTL);
        PRINTREG(RADEON_DP_GUI_MASTER_CNTL);
        PRINTREG(RADEON_RBBM_STATUS);

        radeonfb_engine_reset(sc);
        PRINTREG(RADEON_RBBM_STATUS);

        /*
         * Apple OF hands us some radeons with tiling enabled - explicitly
         * disable it here
         */
        PUT32(sc, RADEON_SURFACE_CNTL, RADEON_SURF_TRANSLATION_DIS);

        radeonfb_wait_fifo(sc, 1);
        if (!IS_R300(sc) && !IS_AVIVO(sc))
                PUT32(sc, RADEON_RB2D_DSTCACHE_MODE, 0);

        radeonfb_wait_fifo(sc, 3);

        /*
         * XXX
         * I strongly suspect this works mostly by accident on !AVIVO
         * AVIVO uses all 22 bits for the framebuffer offset, so it can
         * address up to 4GB. Older chips probably use bits 20-22 for other
         * things and we just so happen to set the right ones by having our
         * PCI/AGP space above 0x80000000.
         * Either way, r5xx does not work if we set these bits, while older
         * chips don't work without.
         */
        pitch = (dp->rd_stride + 0x3f) >> 6;
        if (IS_AVIVO(sc)) {
                pitch = pitch << 22;
        } else
                pitch = (pitch << 22) | (sc->sc_aperbase >> 10);

        PUT32(sc, RADEON_DEFAULT_PITCH_OFFSET, pitch);
        PUT32(sc, RADEON_DST_PITCH_OFFSET, pitch);
        PUT32(sc, RADEON_SRC_PITCH_OFFSET, pitch);

        (void)GET32(sc, RADEON_DP_DATATYPE);

        /* default scissors -- no clipping */
        radeonfb_wait_fifo(sc, 1);
        PUT32(sc, RADEON_DEFAULT_SC_BOTTOM_RIGHT,
            RADEON_DEFAULT_SC_RIGHT_MAX | RADEON_DEFAULT_SC_BOTTOM_MAX);

        radeonfb_wait_fifo(sc, 1);
        PUT32(sc, RADEON_DP_GUI_MASTER_CNTL,
            (dp->rd_format << RADEON_GMC_DST_DATATYPE_SHIFT) |
            RADEON_GMC_CLR_CMP_CNTL_DIS |
            RADEON_GMC_BRUSH_SOLID_COLOR |
            RADEON_GMC_SRC_DATATYPE_COLOR);

        radeonfb_wait_fifo(sc, 10);
        PUT32(sc, RADEON_DST_LINE_START, 0);
        PUT32(sc, RADEON_DST_LINE_END, 0);
        PUT32(sc, RADEON_DP_BRUSH_FRGD_CLR, 0xffffffff);
        PUT32(sc, RADEON_DP_BRUSH_BKGD_CLR, 0);
        PUT32(sc, RADEON_DP_SRC_FRGD_CLR, 0xffffffff);
        PUT32(sc, RADEON_DP_SRC_BKGD_CLR, 0);
        PUT32(sc, RADEON_DP_WRITE_MASK, 0xffffffff);
        PUT32(sc, RADEON_SC_TOP_LEFT, 0);
        PUT32(sc, RADEON_SC_BOTTOM_RIGHT, 0x1fff1fff);
        PUT32(sc, RADEON_AUX_SC_CNTL, 0);
        radeonfb_engine_idle(sc);
}

static void
radeonfb_engine_reset(struct radeonfb_softc *sc)
{
        uint32_t        hpc, rbbm, mclkcntl, clkindex;

        radeonfb_engine_flush(sc);

        clkindex = GET32(sc, RADEON_CLOCK_CNTL_INDEX);
        if (HAS_R300CG(sc))
                radeonfb_r300cg_workaround(sc);
        mclkcntl = GETPLL(sc, RADEON_MCLK_CNTL);

        /*
         * According to comments in XFree code, resetting the HDP via
         * the RBBM_SOFT_RESET can cause bad behavior on some systems.
         * So we use HOST_PATH_CNTL instead.
         */

        hpc = GET32(sc, RADEON_HOST_PATH_CNTL);
        rbbm = GET32(sc, RADEON_RBBM_SOFT_RESET);
        if (IS_R300(sc) || IS_AVIVO(sc)) {
                PUT32(sc, RADEON_RBBM_SOFT_RESET, rbbm |
                    RADEON_SOFT_RESET_CP |
                    RADEON_SOFT_RESET_HI |
                    RADEON_SOFT_RESET_E2);
                GET32(sc, RADEON_RBBM_SOFT_RESET);
                PUT32(sc, RADEON_RBBM_SOFT_RESET, 0);
                /*
                 * XXX: this bit is not defined in any ATI docs I have,
                 * nor in the XFree code, but XFree does it.  Why?
                 */
                SET32(sc, RADEON_RB2D_DSTCACHE_MODE, R300_DC_DC_DISABLE_IGNORE_PE);
        } else {
                PUT32(sc, RADEON_RBBM_SOFT_RESET, rbbm |
                    RADEON_SOFT_RESET_CP |
                    RADEON_SOFT_RESET_SE |
                    RADEON_SOFT_RESET_RE |
                    RADEON_SOFT_RESET_PP |
                    RADEON_SOFT_RESET_E2 |
                    RADEON_SOFT_RESET_RB);
                GET32(sc, RADEON_RBBM_SOFT_RESET);
                PUT32(sc, RADEON_RBBM_SOFT_RESET, rbbm &
                    ~(RADEON_SOFT_RESET_CP |
                        RADEON_SOFT_RESET_SE |
                        RADEON_SOFT_RESET_RE |
                        RADEON_SOFT_RESET_PP |
                        RADEON_SOFT_RESET_E2 |
                        RADEON_SOFT_RESET_RB));
                GET32(sc, RADEON_RBBM_SOFT_RESET);
        }

        PUT32(sc, RADEON_HOST_PATH_CNTL, hpc | RADEON_HDP_SOFT_RESET);
        GET32(sc, RADEON_HOST_PATH_CNTL);
        PUT32(sc, RADEON_HOST_PATH_CNTL, hpc);

        if (!IS_R300(sc) && !IS_AVIVO(sc))
                PUT32(sc, RADEON_RBBM_SOFT_RESET, rbbm);

        PUT32(sc, RADEON_CLOCK_CNTL_INDEX, clkindex);
        PRINTREG(RADEON_CLOCK_CNTL_INDEX);
        PUTPLL(sc, RADEON_MCLK_CNTL, mclkcntl);

        if (HAS_R300CG(sc))
                radeonfb_r300cg_workaround(sc);
}

static int
radeonfb_set_curpos(struct radeonfb_display *dp, struct wsdisplay_curpos *pos)
{
        int             x, y;

        x = pos->x;
        y = pos->y;

        /*
         * This doesn't let a cursor move off the screen.  I'm not
         * sure if this will have negative effects for e.g. Xinerama.
         * I'd guess Xinerama handles it by changing the cursor shape,
         * but that needs verification.
         */
        if (x >= dp->rd_virtx)
                x = dp->rd_virtx - 1;
        if (x < 0)
                x = 0;
        if (y >= dp->rd_virty)
                y = dp->rd_virty - 1;
        if (y < 0)
                y = 0;

        dp->rd_cursor.rc_pos.x = x;
        dp->rd_cursor.rc_pos.y = y;

        radeonfb_cursor_position(dp);
        return 0;
}

static int
radeonfb_set_cursor(struct radeonfb_display *dp, struct wsdisplay_cursor *wc)
{
        unsigned        flags;

        uint8_t         r[2], g[2], b[2];
        unsigned        index, count;
        int             i, err;
        int             pitch, size;
        struct radeonfb_cursor  *nc = &dp->rd_tempcursor;

        flags = wc->which;

        /* copy old values */
        memcpy(nc, &dp->rd_cursor, sizeof(struct radeonfb_cursor));

        if (flags & WSDISPLAY_CURSOR_DOCMAP) {
                index = wc->cmap.index;
                count = wc->cmap.count;

                if (index >= 2 || count > 2 - index)
                        return EINVAL;

                err = copyin(wc->cmap.red, &r[index], count);
                if (err)
                        return err;
                err = copyin(wc->cmap.green, &g[index], count);
                if (err)
                        return err;
                err = copyin(wc->cmap.blue, &b[index], count);
                if (err)
                        return err;

                for (i = index; i < index + count; i++) {
                        nc->rc_cmap[i] =
                            (r[i] << 16) + (g[i] << 8) + (b[i] << 0);
                }
        }

        if (flags & WSDISPLAY_CURSOR_DOSHAPE) {
                if ((wc->size.x > RADEON_CURSORMAXX) ||
                    (wc->size.y > RADEON_CURSORMAXY))
                        return EINVAL;

                /* figure bytes per line */
                pitch = (wc->size.x + 7) / 8;
                size = pitch * wc->size.y;

                /* clear the old cursor and mask */
                memset(nc->rc_image, 0, 512);
                memset(nc->rc_mask, 0, 512);

                nc->rc_size = wc->size;

                if ((err = copyin(wc->image, nc->rc_image, size)) != 0)
                        return err;

                if ((err = copyin(wc->mask, nc->rc_mask, size)) != 0)
                        return err;
        }

        if (flags & WSDISPLAY_CURSOR_DOHOT) {
                nc->rc_hot = wc->hot;
                if (nc->rc_hot.x >= nc->rc_size.x)
                        nc->rc_hot.x = nc->rc_size.x - 1;
                if (nc->rc_hot.y >= nc->rc_size.y)
                        nc->rc_hot.y = nc->rc_size.y - 1;
        }

        if (flags & WSDISPLAY_CURSOR_DOPOS) {
                nc->rc_pos = wc->pos;
                if (nc->rc_pos.x >= dp->rd_virtx)
                        nc->rc_pos.x = dp->rd_virtx - 1;
#if 0
                if (nc->rc_pos.x < 0)
                        nc->rc_pos.x = 0;
#endif
                if (nc->rc_pos.y >= dp->rd_virty)
                        nc->rc_pos.y = dp->rd_virty - 1;
#if 0
                if (nc->rc_pos.y < 0)
                        nc->rc_pos.y = 0;
#endif
        }
        if (flags & WSDISPLAY_CURSOR_DOCUR) {
                nc->rc_visible = wc->enable;
        }

        memcpy(&dp->rd_cursor, nc, sizeof(struct radeonfb_cursor));
        radeonfb_cursor_update(dp, wc->which);

        return 0;
}

static uint8_t
radeonfb_backwards(uint8_t d)
{
        uint8_t l;

        l = d << 7;
        l |= ((d & 0x02) << 5); 
        l |= ((d & 0x04) << 3); 
        l |= ((d & 0x08) << 1);
        l |= ((d & 0x10) >> 1); 
        l |= ((d & 0x20) >> 3); 
        l |= ((d & 0x40) >> 5); 
        l |= ((d & 0x80) >> 7);
        return l;
} 
 
/*
 * Change the cursor shape.  Call this with the cursor locked to avoid
 * flickering/tearing.
 */
static void
radeonfb_cursor_shape(struct radeonfb_display *dp)
{
        uint8_t and[512], xor[512];
        int     i, j, src, dst /* , pitch */;
        const uint8_t   *msk = dp->rd_cursor.rc_mask;
        const uint8_t   *img = dp->rd_cursor.rc_image;

        /*
         * Radeon cursor data interleaves one line of AND data followed
         * by a line of XOR data.  (Each line corresponds to a whole hardware
         * pitch - i.e. 64 pixels or 8 bytes.)
         *
         * The cursor is displayed using the following table:
         *
         * AND  XOR     Result
         * ----------------------
         *  0    0      Cursor color 0
         *  0    1      Cursor color 1
         *  1    0      Transparent
         *  1    1      Complement of background
         *
         * Our masks are therefore different from what we were passed.
         * Passed in, I'm assuming the data represents either color 0 or 1,
         * and a mask, so the passed in table looks like:
         *
         * IMG  Mask    Result
         * -----------------------
         *  0    0      Transparent
         *  0    1      Cursor color 0
         *  1    0      Transparent
         *  1    1      Cursor color 1
         *
         * IF mask bit == 1, AND = 0, XOR = color.
         * IF mask bit == 0, AND = 1, XOR = 0.
         *
         * hence:       AND = ~(mask);  XOR = color & ~(mask);
         */

        /* pitch = ((dp->rd_cursor.rc_size.x + 7) / 8); */

        /* start by assuming all bits are transparent */
        memset(and, 0xff, 512);
        memset(xor, 0x00, 512);

        src = 0;
        dst = 0;
        for (i = 0; i < 64; i++) {
                for (j = 0; j < 64; j += 8) {
                        if ((i < dp->rd_cursor.rc_size.y) &&
                            (j < dp->rd_cursor.rc_size.x)) {

                                /* take care to leave odd bits alone */
                                and[dst] &= ~(msk[src]);
                                xor[dst] = img[src] & msk[src];
                                src++;
                        }
                        dst++;
                }
        }

        for (i = 0; i < 512; i++) {
                and[i] = radeonfb_backwards(and[i]);
                xor[i] = radeonfb_backwards(xor[i]);
        }

        /* copy the image into place */
        for (i = 0; i < 64; i++) {
                memcpy((uint8_t *)dp->rd_curptr + (i * 16),
                    &and[i * 8], 8);
                memcpy((uint8_t *)dp->rd_curptr + (i * 16) + 8,
                    &xor[i * 8], 8);
        }
}

/*
 * We use the cursor in 24bit mode on avivo, much simpler than the above.
 * Should probably do the same on older radeons
 */
static void
radeonfb_avivo_cursor_shape(struct radeonfb_display *dp)
{
        const uint8_t   *msk = dp->rd_cursor.rc_mask;
        const uint8_t   *img = dp->rd_cursor.rc_image;
        uint32_t        *out = (uint32_t *)dp->rd_curptr;
        uint8_t         bit;
        int             i, j, px;

        for (i = 0; i < 64 * 8; i++) {
                bit = 0x01;
                for (j = 0; j < 8; j++) {
                        px = ((*msk & bit) ? 2 : 0) | ((*img & bit) ? 1 : 0);
                        switch (px) {
                                case 0:
                                case 1:
                                        *out = htole32(0x00000000);
                                        break;
                                case 2:
                                        *out = htole32(0xff000000 |
                                                  dp->rd_cursor.rc_cmap[0]);
                                        break;
                                case 3:
                                        *out = htole32(0xff000000 |
                                                  dp->rd_cursor.rc_cmap[1]);
                                        break;
                        }               
                        out++;
                        bit = bit << 1;
                }
                msk++;
                img++;
        }
}

static void
radeonfb_cursor_position(struct radeonfb_display *dp)
{
        struct radeonfb_softc   *sc = dp->rd_softc;
        uint32_t                offset, hvoff, hvpos;   /* registers */
        uint32_t                coff;                   /* cursor offset */
        int                     i, x, y, xoff, yoff, crtcoff, lock;

        /*
         * XXX: this also needs to handle pan/scan
         */
        for (i = 0; i < dp->rd_ncrtcs; i++) {

                struct radeonfb_crtc    *rcp = &dp->rd_crtcs[i];

                SET32(sc, AVIVO_D1CUR_UPDATE, AVIVO_D1CURSOR_UPDATE_LOCK);
                PUT32(sc, AVIVO_D1CUR_SIZE, 0x003f003f);
                if (IS_AVIVO(sc)) {
                        if (rcp->rc_number) {
                                offset = AVIVO_D2CUR_SURFACE_ADDRESS;
                                hvoff = AVIVO_D2CUR_HOT_SPOT;
                                hvpos = AVIVO_D2CUR_POSITION;
                                crtcoff = 0/*RADEON_CRTC_OFFSET*/;
                        } else {
                                offset = AVIVO_D1CUR_SURFACE_ADDRESS;
                                hvoff = AVIVO_D1CUR_HOT_SPOT;
                                hvpos = AVIVO_D1CUR_POSITION;
                                crtcoff = 0/*RADEON_CRTC_OFFSET*/;
                        }
                        lock = 0;
                } else {
                        if (rcp->rc_number) {
                                offset = RADEON_CUR2_OFFSET;
                                hvoff = RADEON_CUR2_HORZ_VERT_OFF;
                                hvpos = RADEON_CUR2_HORZ_VERT_POSN;
                                crtcoff = RADEON_CRTC2_OFFSET;
                        } else {
                                offset = RADEON_CUR_OFFSET;
                                hvoff = RADEON_CUR_HORZ_VERT_OFF;
                                hvpos = RADEON_CUR_HORZ_VERT_POSN;
                                crtcoff = RADEON_CRTC_OFFSET;
                        }
                        lock = RADEON_CUR_LOCK;
                }

                x = dp->rd_cursor.rc_pos.x;
                y = dp->rd_cursor.rc_pos.y;

                while (y < rcp->rc_yoffset) {
                        rcp->rc_yoffset -= RADEON_PANINCREMENT;
                }
                while (y >= (rcp->rc_yoffset + rcp->rc_videomode.vdisplay)) {
                        rcp->rc_yoffset += RADEON_PANINCREMENT;
                }
                while (x < rcp->rc_xoffset) {
                        rcp->rc_xoffset -= RADEON_PANINCREMENT;
                }
                while (x >= (rcp->rc_xoffset + rcp->rc_videomode.hdisplay)) {
                        rcp->rc_xoffset += RADEON_PANINCREMENT;
                }

                /* adjust for the cursor's hotspot */
                x -= dp->rd_cursor.rc_hot.x;
                y -= dp->rd_cursor.rc_hot.y;
                xoff = yoff = 0;

                if (x >= dp->rd_virtx)
                        x = dp->rd_virtx - 1;
                if (y >= dp->rd_virty)
                        y = dp->rd_virty - 1;

                /* now adjust cursor so it is relative to viewport */
                x -= rcp->rc_xoffset;
                y -= rcp->rc_yoffset;

                /*
                 * no need to check for fall off, because we should
                 * never move off the screen entirely!
                 */
                coff = 0;
                if (x < 0) {
                        xoff = -x;
                        x = 0;
                }
                if (y < 0) {
                        yoff = -y;
                        y = 0;
                        coff = (yoff * 2) * 8;
                }

                /* pan the display */
                if (crtcoff != 0)
                        PUT32(sc, crtcoff, (rcp->rc_yoffset * dp->rd_stride) +
                            rcp->rc_xoffset);

                PUT32(sc, offset, (dp->rd_curoff + coff) | lock);
                PUT32(sc, hvoff, (xoff << 16) | (yoff) | lock);
                /* NB: this unlocks the cursor */
                PUT32(sc, hvpos, (x << 16) | y);
                if (IS_AVIVO(sc))
                        CLR32(sc, AVIVO_D1CUR_UPDATE, AVIVO_D1CURSOR_UPDATE_LOCK);
        }
}

static void
radeonfb_cursor_visible(struct radeonfb_display *dp)
{
        struct radeonfb_softc   *sc = dp->rd_softc;
        int             i;
        uint32_t        gencntl, bit;

        for (i = 0; i < dp->rd_ncrtcs; i++) {
                if (IS_AVIVO(sc)) {
                        SET32(sc, AVIVO_D1CUR_UPDATE, AVIVO_D1CURSOR_UPDATE_LOCK);
                        if (dp->rd_crtcs[i].rc_number) {
                                gencntl = AVIVO_D2CUR_CONTROL;
                                bit = AVIVO_D1CURSOR_EN | (2 << 8);
                        } else {
                                gencntl = AVIVO_D1CUR_CONTROL;
                                bit = AVIVO_D1CURSOR_EN | (2 << 8);
                        }
                } else {
                        if (dp->rd_crtcs[i].rc_number) {
                                gencntl = RADEON_CRTC2_GEN_CNTL;
                                bit = RADEON_CRTC2_CUR_EN;
                        } else {
                                gencntl = RADEON_CRTC_GEN_CNTL;
                                bit = RADEON_CRTC_CUR_EN;
                        }
                }
                if (dp->rd_cursor.rc_visible)
                        SET32(dp->rd_softc, gencntl, bit);
                else
                        CLR32(dp->rd_softc, gencntl, bit);
                if (IS_AVIVO(sc))
                        CLR32(sc, AVIVO_D1CUR_UPDATE, AVIVO_D1CURSOR_UPDATE_LOCK);
                
        }
}

static void
radeonfb_cursor_cmap(struct radeonfb_display *dp)
{
        int             i;
        uint32_t        c0reg, c1reg;
        struct radeonfb_softc   *sc = dp->rd_softc;

        for (i = 0; i < dp->rd_ncrtcs; i++) {
                if (dp->rd_crtcs[i].rc_number) {
                        c0reg = RADEON_CUR2_CLR0;
                        c1reg = RADEON_CUR2_CLR1;
                } else {
                        c0reg = RADEON_CUR_CLR0;
                        c1reg = RADEON_CUR_CLR1;
                }

                PUT32(sc, c0reg, dp->rd_cursor.rc_cmap[0]);
                PUT32(sc, c1reg, dp->rd_cursor.rc_cmap[1]);
        }
}

static void
radeonfb_cursor_update(struct radeonfb_display *dp, unsigned which)
{
        struct radeonfb_softc   *sc;
        int             i;

        sc = dp->rd_softc;
        
        for (i = 0; i < dp->rd_ncrtcs; i++) {
                if (dp->rd_crtcs[i].rc_number) {
                        if (IS_AVIVO(sc)) {
                                SET32(sc, AVIVO_D2CUR_UPDATE, AVIVO_D1CURSOR_UPDATE_LOCK);
                        } else
                                SET32(sc, RADEON_CUR2_OFFSET, RADEON_CUR_LOCK);
                } else {
                        if (IS_AVIVO(sc)) {
                                SET32(sc, AVIVO_D1CUR_UPDATE, AVIVO_D1CURSOR_UPDATE_LOCK);
                        } else
                                SET32(sc, RADEON_CUR_OFFSET,RADEON_CUR_LOCK);
                }
        }

        if (which & WSDISPLAY_CURSOR_DOCMAP) {
                if (IS_AVIVO(sc)) {
                        /*
                         * we use an ARGB cursor here, so we need to rebuild
                         * the cursor image every time the palette changes
                         */
                        radeonfb_avivo_cursor_shape(dp);
                } else
                        radeonfb_cursor_cmap(dp);
        }

        if (which & WSDISPLAY_CURSOR_DOSHAPE) {
                if (IS_AVIVO(sc)) {
                        radeonfb_avivo_cursor_shape(dp);
                } else
                        radeonfb_cursor_shape(dp);
        }
        
        if (which & WSDISPLAY_CURSOR_DOCUR)
                radeonfb_cursor_visible(dp);

        /* this one is unconditional, because it updates other stuff */
        radeonfb_cursor_position(dp);
}

static struct videomode *
radeonfb_best_refresh(struct videomode *m1, struct videomode *m2)
{
        int     r1, r2;

        /* otherwise pick the higher refresh rate */
        r1 = DIVIDE(DIVIDE(m1->dot_clock, m1->htotal), m1->vtotal);
        r2 = DIVIDE(DIVIDE(m2->dot_clock, m2->htotal), m2->vtotal);

        return (r1 < r2 ? m2 : m1);
}

static const struct videomode *
radeonfb_port_mode(struct radeonfb_softc *sc, struct radeonfb_port *rp,
    int x, int y)
{
        struct edid_info        *ep = &rp->rp_edid;
        struct videomode        *vmp = NULL;
        int                     i;

        if (!rp->rp_edid_valid) {
                /* fallback to safe mode */
                return radeonfb_modelookup(sc->sc_defaultmode);
        }
        
        /* always choose the preferred mode first! */
        if (ep->edid_preferred_mode) {

                /* XXX: add auto-stretching support for native mode */

                /* this may want panning to occur, btw */
                if ((ep->edid_preferred_mode->hdisplay <= x) &&
                    (ep->edid_preferred_mode->vdisplay <= y))
                        return ep->edid_preferred_mode;
        }

        for (i = 0; i < ep->edid_nmodes; i++) {
                /*
                 * We elect to pick a resolution that is too large for
                 * the monitor than one that is too small.  This means
                 * that we will prefer to pan rather than to try to
                 * center a smaller display on a larger screen.  In
                 * practice, this shouldn't matter because if a
                 * monitor can support a larger resolution, it can
                 * probably also support the smaller.  A specific
                 * exception is fixed format panels, but hopefully
                 * they are properly dealt with by the "autostretch"
                 * logic above.
                 */
                if ((ep->edid_modes[i].hdisplay > x) ||
                    (ep->edid_modes[i].vdisplay > y)) {
                        continue;
                }

                /*
                 * at this point, the display mode is no larger than
                 * what we've requested.
                 */
                if (vmp == NULL)
                        vmp = &ep->edid_modes[i];

                /* eliminate smaller modes */
                if ((vmp->hdisplay >= ep->edid_modes[i].hdisplay) ||
                    (vmp->vdisplay >= ep->edid_modes[i].vdisplay))
                        continue;

                if ((vmp->hdisplay < ep->edid_modes[i].hdisplay) ||
                    (vmp->vdisplay < ep->edid_modes[i].vdisplay)) {
                        vmp = &ep->edid_modes[i];
                        continue;
                }

                KASSERT(vmp->hdisplay == ep->edid_modes[i].hdisplay);
                KASSERT(vmp->vdisplay == ep->edid_modes[i].vdisplay);

                vmp = radeonfb_best_refresh(vmp, &ep->edid_modes[i]);
        }

        return (vmp ? vmp : radeonfb_modelookup(sc->sc_defaultmode));
}

static int
radeonfb_hasres(struct videomode *list, int nlist, int x, int y)
{
        int     i;

        for (i = 0; i < nlist; i++) {
                if ((x == list[i].hdisplay) &&
                    (y == list[i].vdisplay)) {
                        return 1;
                }
        }
        return 0;
}

static void
radeonfb_pickres(struct radeonfb_display *dp, uint16_t *x, uint16_t *y,
    int pan)
{
        struct radeonfb_port    *rp;
        struct edid_info        *ep;
        int                     i, j;

        *x = 0;
        *y = 0;

        if (pan) {
                for (i = 0; i < dp->rd_ncrtcs; i++) {
                        rp = dp->rd_crtcs[i].rc_port;
                        ep = &rp->rp_edid;
                        if (!rp->rp_edid_valid) {
                                /* monitor not present */
                                continue;
                        }

                        /*
                         * For now we are ignoring "conflict" that
                         * could occur when mixing some modes like
                         * 1280x1024 and 1400x800.  It isn't clear
                         * which is better, so the first one wins.
                         */
                        for (j = 0; j < ep->edid_nmodes; j++) {
                                /*
                                 * ignore resolutions that are too big for
                                 * the radeon
                                 */
                                if (ep->edid_modes[j].hdisplay >
                                    dp->rd_softc->sc_maxx)
                                        continue;
                                if (ep->edid_modes[j].vdisplay >
                                    dp->rd_softc->sc_maxy)
                                        continue;

                                /*
                                 * pick largest resolution, the
                                 * smaller monitor will pan
                                 */
                                if ((ep->edid_modes[j].hdisplay >= *x) &&
                                    (ep->edid_modes[j].vdisplay >= *y)) {
                                        *x = ep->edid_modes[j].hdisplay;
                                        *y = ep->edid_modes[j].vdisplay;
                                }
                        }
                }

        } else {
                struct videomode        *modes;
                size_t                  smodes;
                int                     nmodes = 0;
                int                     valid = 0;

                smodes = sizeof(struct videomode) * 64;
                modes = kmem_alloc(smodes, KM_SLEEP);

                for (i = 0; i < dp->rd_ncrtcs; i++) {
                        /*
                         * pick the largest resolution in common.
                         */
                        rp = dp->rd_crtcs[i].rc_port;
                        ep = &rp->rp_edid;

                        if (!rp->rp_edid_valid)
                                continue;

                        if (!valid) {
                                /*
                                 * Pick the preferred mode for this port
                                 * if available.
                                 */
                                if (ep->edid_preferred_mode) {
                                        struct videomode *vmp = 
                                                ep->edid_preferred_mode;

                                        if ((vmp->hdisplay <= 
                                             dp->rd_softc->sc_maxx) && 
                                            (vmp->vdisplay <= 
                                             dp->rd_softc->sc_maxy))
                                                modes[nmodes++] = *vmp;
                                } else {

                                        /* initialize starting list */
                                        for (j = 0; j < ep->edid_nmodes; j++) {
                                                /*
                                                 * ignore resolutions that are
                                                 * too big for the radeon
                                                 */
                                                if (ep->edid_modes[j].hdisplay >
                                                    dp->rd_softc->sc_maxx)
                                                        continue;
                                                if (ep->edid_modes[j].vdisplay >
                                                    dp->rd_softc->sc_maxy)
                                                        continue;

                                                modes[nmodes] = 
                                                        ep->edid_modes[j];
                                                nmodes++;
                                        }
                                }
                                valid = 1;
                        } else {
                                /* merge into preexisting list */
                                for (j = 0; j < nmodes; j++) {
                                        if (!radeonfb_hasres(ep->edid_modes,
                                                ep->edid_nmodes,
                                                modes[j].hdisplay,
                                                modes[j].vdisplay)) {
                                                modes[j] = modes[nmodes];
                                                j--;
                                                nmodes--;
                                        }
                                }
                        }
                }

                /* now we have to pick from the merged list */
                for (i = 0; i < nmodes; i++) {
                        if ((modes[i].hdisplay >= *x) &&
                            (modes[i].vdisplay >= *y)) {
                                *x = modes[i].hdisplay;
                                *y = modes[i].vdisplay;
                        }
                }
                kmem_free(modes, smodes);

        }

        if ((*x == 0) || (*y == 0)) {
                /* fallback to safe mode */
                *x = 640;
                *y = 480;
        }
}

/*
 * backlight levels are linear on:
 * - RV200, RV250, RV280, RV350
 * - but NOT on PowerBook4,3 6,3 6,5
 * according to Linux' radeonfb
 */

/* Get the current backlight level for the display.  */

static int 
radeonfb_get_backlight(struct radeonfb_display *dp)
{
        int s;
        uint32_t level;

        s = spltty();

        level = radeonfb_get32(dp->rd_softc, RADEON_LVDS_GEN_CNTL);
        level &= RADEON_LVDS_BL_MOD_LEV_MASK;
        level >>= RADEON_LVDS_BL_MOD_LEV_SHIFT;

        /* 
         * On some chips, we should negate the backlight level. 
         * XXX Find out on which chips. 
         */
        if (dp->rd_softc->sc_flags & RFB_INV_BLIGHT)
        level = RADEONFB_BACKLIGHT_MAX - level;

        splx(s);

        return level;
}       

/* Set the backlight to the given level for the display.  */
static void 
radeonfb_switch_backlight(struct radeonfb_display *dp, int on)
{
        if (dp->rd_bl_on == on)
                return;
        dp->rd_bl_on = on;
        radeonfb_set_backlight(dp, dp->rd_bl_level);
}

static int 
radeonfb_set_backlight(struct radeonfb_display *dp, int level)
{
        struct radeonfb_softc *sc = dp->rd_softc;
        int rlevel, s;
        uint32_t lvds;

        if(!sc->sc_mapped)
                return 0;

        s = spltty();

        dp->rd_bl_level = level;
        if (dp->rd_bl_on == 0)
                level = 0;

        if (level < 0)
                level = 0;
        else if (level >= RADEONFB_BACKLIGHT_MAX)
                level = RADEONFB_BACKLIGHT_MAX;

        /* On some chips, we should negate the backlight level. */
        if (dp->rd_softc->sc_flags & RFB_INV_BLIGHT) {
                rlevel = RADEONFB_BACKLIGHT_MAX - level;
        } else
        rlevel = level;

        callout_stop(&dp->rd_bl_lvds_co);
        //radeonfb_engine_idle(sc);

        /* 
         * Turn off the display if the backlight is set to 0, since the
         * display is useless without backlight anyway. 
         */
        if (level == 0)
                radeonfb_blank(dp, 1);
        else if (radeonfb_get_backlight(dp) == 0)
                radeonfb_blank(dp, 0);
        
        lvds = radeonfb_get32(sc, RADEON_LVDS_GEN_CNTL);
        lvds &= ~RADEON_LVDS_DISPLAY_DIS;
        if (!(lvds & RADEON_LVDS_BLON) || !(lvds & RADEON_LVDS_ON)) {
                lvds |= dp->rd_bl_lvds_val & RADEON_LVDS_DIGON;
                lvds |= RADEON_LVDS_BLON | RADEON_LVDS_EN;
                radeonfb_put32(sc, RADEON_LVDS_GEN_CNTL, lvds);
                lvds &= ~RADEON_LVDS_BL_MOD_LEV_MASK;
                lvds |= rlevel << RADEON_LVDS_BL_MOD_LEV_SHIFT;
                lvds |= RADEON_LVDS_ON;
                lvds |= dp->rd_bl_lvds_val & RADEON_LVDS_BL_MOD_EN;
        } else {
                lvds &= ~RADEON_LVDS_BL_MOD_LEV_MASK;
                lvds |= rlevel << RADEON_LVDS_BL_MOD_LEV_SHIFT;
                radeonfb_put32(sc, RADEON_LVDS_GEN_CNTL, lvds);
        }
        
        dp->rd_bl_lvds_val &= ~RADEON_LVDS_STATE_MASK;
        dp->rd_bl_lvds_val |= lvds & RADEON_LVDS_STATE_MASK;
        /* XXX What is the correct delay? */
        callout_schedule(&dp->rd_bl_lvds_co, 200 * hz); 

        splx(s);

        return 0;
}

/* 
 * Callout function for delayed operations on the LVDS_GEN_CNTL register. 
 * Set the delayed bits in the register, and clear the stored delayed
 * value.
 */

static void radeonfb_lvds_callout(void *arg)
{
        struct radeonfb_display *dp = arg;
        int s;

        s = splhigh();

        radeonfb_mask32(dp->rd_softc, RADEON_LVDS_GEN_CNTL, ~0, 
                        dp->rd_bl_lvds_val);
        dp->rd_bl_lvds_val = 0;

        splx(s);
}

static void
radeonfb_brightness_up(device_t dev)
{
        struct radeonfb_softc *sc = device_private(dev);
        struct radeonfb_display *dp = &sc->sc_displays[0];
        int level;

        /* we assume the main display is the first one - need a better way */
        if (sc->sc_ndisplays < 1) return;
        /* make sure pushing the hotkeys always has an effect */
        dp->rd_bl_on = 1;
        level = dp->rd_bl_level;
        level = uimin(RADEONFB_BACKLIGHT_MAX, level + 5);
        radeonfb_set_backlight(dp, level);
}

static void
radeonfb_brightness_down(device_t dev)
{
        struct radeonfb_softc *sc = device_private(dev);
        struct radeonfb_display *dp = &sc->sc_displays[0];
        int level;

        /* we assume the main display is the first one - need a better way */
        if (sc->sc_ndisplays < 1) return;
        /* make sure pushing the hotkeys always has an effect */
        dp->rd_bl_on = 1;
        level = dp->rd_bl_level;
        level = uimax(0, level - 5);
        radeonfb_set_backlight(dp, level);
}