freebsd-dev/sys/arm/allwinner/a10_fb.c
Michal Meloun dac935533b EXTRES: Add OF node as argument to all <foo>_get_by_ofw_<bar>() functions.
In some cases, the driver must handle given properties located in
specific OF subnode. Instead of creating duplicate set of function, add
'node' as argument to existing functions, defaulting it to device OF node.

MFC after: 3 weeks
2016-07-10 18:28:15 +00:00

663 lines
17 KiB
C

/*-
* Copyright (c) 2016 Jared McNeill <jmcneill@invisible.ca>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
/*
* Allwinner A10/A20 Framebuffer
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/rman.h>
#include <sys/condvar.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/fbio.h>
#include <vm/vm.h>
#include <vm/vm_extern.h>
#include <vm/vm_kern.h>
#include <vm/pmap.h>
#include <machine/bus.h>
#include <dev/ofw/ofw_bus.h>
#include <dev/ofw/ofw_bus_subr.h>
#include <dev/videomode/videomode.h>
#include <dev/videomode/edidvar.h>
#include <dev/extres/clk/clk.h>
#include <dev/extres/hwreset/hwreset.h>
#include "fb_if.h"
#include "hdmi_if.h"
#define FB_DEFAULT_W 800
#define FB_DEFAULT_H 600
#define FB_DEFAULT_REF 60
#define FB_BPP 32
#define FB_ALIGN 0x1000
#define HDMI_ENABLE_DELAY 20000
#define DEBE_FREQ 300000000
#define DOT_CLOCK_TO_HZ(c) ((c) * 1000)
/* Display backend */
#define DEBE_REG_START 0x800
#define DEBE_REG_END 0x1000
#define DEBE_REG_WIDTH 4
#define DEBE_MODCTL 0x800
#define MODCTL_ITLMOD_EN (1 << 28)
#define MODCTL_OUT_SEL_MASK (0x7 << 20)
#define MODCTL_OUT_SEL(sel) ((sel) << 20)
#define OUT_SEL_LCD 0
#define MODCTL_LAY0_EN (1 << 8)
#define MODCTL_START_CTL (1 << 1)
#define MODCTL_EN (1 << 0)
#define DEBE_DISSIZE 0x808
#define DIS_HEIGHT(h) (((h) - 1) << 16)
#define DIS_WIDTH(w) (((w) - 1) << 0)
#define DEBE_LAYSIZE0 0x810
#define LAY_HEIGHT(h) (((h) - 1) << 16)
#define LAY_WIDTH(w) (((w) - 1) << 0)
#define DEBE_LAYCOOR0 0x820
#define LAY_XCOOR(x) ((x) << 16)
#define LAY_YCOOR(y) ((y) << 0)
#define DEBE_LAYLINEWIDTH0 0x840
#define DEBE_LAYFB_L32ADD0 0x850
#define LAYFB_L32ADD(pa) ((pa) << 3)
#define DEBE_LAYFB_H4ADD 0x860
#define LAY0FB_H4ADD(pa) ((pa) >> 29)
#define DEBE_REGBUFFCTL 0x870
#define REGBUFFCTL_LOAD (1 << 0)
#define DEBE_ATTCTL1 0x8a0
#define ATTCTL1_FBFMT(fmt) ((fmt) << 8)
#define FBFMT_XRGB8888 9
#define ATTCTL1_FBPS(ps) ((ps) << 0)
#define FBPS_32BPP_ARGB 0
/* Timing controller */
#define TCON_GCTL 0x000
#define GCTL_TCON_EN (1 << 31)
#define GCTL_IO_MAP_SEL_TCON1 (1 << 0)
#define TCON_GINT1 0x008
#define GINT1_TCON1_LINENO(n) (((n) + 2) << 0)
#define TCON0_DCLK 0x044
#define DCLK_EN 0xf0000000
#define TCON1_CTL 0x090
#define TCON1_EN (1 << 31)
#define INTERLACE_EN (1 << 20)
#define TCON1_SRC_SEL(src) ((src) << 0)
#define TCON1_SRC_CH1 0
#define TCON1_SRC_CH2 1
#define TCON1_SRC_BLUE 2
#define TCON1_START_DELAY(sd) ((sd) << 4)
#define TCON1_BASIC0 0x094
#define TCON1_BASIC1 0x098
#define TCON1_BASIC2 0x09c
#define TCON1_BASIC3 0x0a0
#define TCON1_BASIC4 0x0a4
#define TCON1_BASIC5 0x0a8
#define BASIC_X(x) (((x) - 1) << 16)
#define BASIC_Y(y) (((y) - 1) << 0)
#define BASIC3_HT(ht) (((ht) - 1) << 16)
#define BASIC3_HBP(hbp) (((hbp) - 1) << 0)
#define BASIC4_VT(vt) ((vt) << 16)
#define BASIC4_VBP(vbp) (((vbp) - 1) << 0)
#define BASIC5_HSPW(hspw) (((hspw) - 1) << 16)
#define BASIC5_VSPW(vspw) (((vspw) - 1) << 0)
#define TCON1_IO_POL 0x0f0
#define IO_POL_IO2_INV (1 << 26)
#define IO_POL_PHSYNC (1 << 25)
#define IO_POL_PVSYNC (1 << 24)
#define TCON1_IO_TRI 0x0f4
#define IO0_OUTPUT_TRI_EN (1 << 24)
#define IO1_OUTPUT_TRI_EN (1 << 25)
#define IO_TRI_MASK 0xffffffff
#define START_DELAY(vbl) (MIN(32, (vbl)) - 2)
#define VBLANK_LEN(vt, vd, i) ((((vt) << (i)) >> 1) - (vd) - 2)
#define VTOTAL(vt) ((vt) * 2)
#define DIVIDE(x, y) (((x) + ((y) / 2)) / (y))
struct a10fb_softc {
device_t dev;
device_t fbdev;
struct resource *res[2];
/* Framebuffer */
struct fb_info info;
size_t fbsize;
bus_addr_t paddr;
vm_offset_t vaddr;
/* HDMI */
eventhandler_tag hdmi_evh;
};
static struct resource_spec a10fb_spec[] = {
{ SYS_RES_MEMORY, 0, RF_ACTIVE }, /* DEBE */
{ SYS_RES_MEMORY, 1, RF_ACTIVE }, /* TCON */
{ -1, 0 }
};
#define DEBE_READ(sc, reg) bus_read_4((sc)->res[0], (reg))
#define DEBE_WRITE(sc, reg, val) bus_write_4((sc)->res[0], (reg), (val))
#define TCON_READ(sc, reg) bus_read_4((sc)->res[1], (reg))
#define TCON_WRITE(sc, reg, val) bus_write_4((sc)->res[1], (reg), (val))
static int
a10fb_allocfb(struct a10fb_softc *sc)
{
sc->vaddr = kmem_alloc_contig(kernel_arena, sc->fbsize,
M_NOWAIT | M_ZERO, 0, ~0, FB_ALIGN, 0, VM_MEMATTR_WRITE_COMBINING);
if (sc->vaddr == 0) {
device_printf(sc->dev, "failed to allocate FB memory\n");
return (ENOMEM);
}
sc->paddr = pmap_kextract(sc->vaddr);
return (0);
}
static void
a10fb_freefb(struct a10fb_softc *sc)
{
kmem_free(kernel_arena, sc->vaddr, sc->fbsize);
}
static int
a10fb_setup_debe(struct a10fb_softc *sc, const struct videomode *mode)
{
int width, height, interlace, reg;
clk_t clk_ahb, clk_dram, clk_debe;
hwreset_t rst;
uint32_t val;
int error;
interlace = !!(mode->flags & VID_INTERLACE);
width = mode->hdisplay;
height = mode->vdisplay << interlace;
/* Leave reset */
error = hwreset_get_by_ofw_name(sc->dev, 0, "de_be", &rst);
if (error != 0) {
device_printf(sc->dev, "cannot find reset 'de_be'\n");
return (error);
}
error = hwreset_deassert(rst);
if (error != 0) {
device_printf(sc->dev, "couldn't de-assert reset 'de_be'\n");
return (error);
}
/* Gating AHB clock for BE */
error = clk_get_by_ofw_name(sc->dev, 0, "ahb_de_be", &clk_ahb);
if (error != 0) {
device_printf(sc->dev, "cannot find clk 'ahb_de_be'\n");
return (error);
}
error = clk_enable(clk_ahb);
if (error != 0) {
device_printf(sc->dev, "cannot enable clk 'ahb_de_be'\n");
return (error);
}
/* Enable DRAM clock to BE */
error = clk_get_by_ofw_name(sc->dev, 0, "dram_de_be", &clk_dram);
if (error != 0) {
device_printf(sc->dev, "cannot find clk 'dram_de_be'\n");
return (error);
}
error = clk_enable(clk_dram);
if (error != 0) {
device_printf(sc->dev, "cannot enable clk 'dram_de_be'\n");
return (error);
}
/* Set BE clock to 300MHz and enable */
error = clk_get_by_ofw_name(sc->dev, 0, "de_be", &clk_debe);
if (error != 0) {
device_printf(sc->dev, "cannot find clk 'de_be'\n");
return (error);
}
error = clk_set_freq(clk_debe, DEBE_FREQ, CLK_SET_ROUND_DOWN);
if (error != 0) {
device_printf(sc->dev, "cannot set 'de_be' frequency\n");
return (error);
}
error = clk_enable(clk_debe);
if (error != 0) {
device_printf(sc->dev, "cannot enable clk 'de_be'\n");
return (error);
}
/* Initialize all registers to 0 */
for (reg = DEBE_REG_START; reg < DEBE_REG_END; reg += DEBE_REG_WIDTH)
DEBE_WRITE(sc, reg, 0);
/* Enable display backend */
DEBE_WRITE(sc, DEBE_MODCTL, MODCTL_EN);
/* Set display size */
DEBE_WRITE(sc, DEBE_DISSIZE, DIS_HEIGHT(height) | DIS_WIDTH(width));
/* Set layer 0 size, position, and stride */
DEBE_WRITE(sc, DEBE_LAYSIZE0, LAY_HEIGHT(height) | LAY_WIDTH(width));
DEBE_WRITE(sc, DEBE_LAYCOOR0, LAY_XCOOR(0) | LAY_YCOOR(0));
DEBE_WRITE(sc, DEBE_LAYLINEWIDTH0, width * FB_BPP);
/* Point layer 0 to FB memory */
DEBE_WRITE(sc, DEBE_LAYFB_L32ADD0, LAYFB_L32ADD(sc->paddr));
DEBE_WRITE(sc, DEBE_LAYFB_H4ADD, LAY0FB_H4ADD(sc->paddr));
/* Set backend format and pixel sequence */
DEBE_WRITE(sc, DEBE_ATTCTL1, ATTCTL1_FBFMT(FBFMT_XRGB8888) |
ATTCTL1_FBPS(FBPS_32BPP_ARGB));
/* Enable layer 0, output to LCD, setup interlace */
val = DEBE_READ(sc, DEBE_MODCTL);
val |= MODCTL_LAY0_EN;
val &= ~MODCTL_OUT_SEL_MASK;
val |= MODCTL_OUT_SEL(OUT_SEL_LCD);
if (interlace)
val |= MODCTL_ITLMOD_EN;
else
val &= ~MODCTL_ITLMOD_EN;
DEBE_WRITE(sc, DEBE_MODCTL, val);
/* Commit settings */
DEBE_WRITE(sc, DEBE_REGBUFFCTL, REGBUFFCTL_LOAD);
/* Start DEBE */
val = DEBE_READ(sc, DEBE_MODCTL);
val |= MODCTL_START_CTL;
DEBE_WRITE(sc, DEBE_MODCTL, val);
return (0);
}
static int
a10fb_setup_pll(struct a10fb_softc *sc, uint64_t freq)
{
clk_t clk_sclk1, clk_sclk2;
int error;
error = clk_get_by_ofw_name(sc->dev, 0, "lcd_ch1_sclk1", &clk_sclk1);
if (error != 0) {
device_printf(sc->dev, "cannot find clk 'lcd_ch1_sclk1'\n");
return (error);
}
error = clk_get_by_ofw_name(sc->dev, 0, "lcd_ch1_sclk2", &clk_sclk2);
if (error != 0) {
device_printf(sc->dev, "cannot find clk 'lcd_ch1_sclk2'\n");
return (error);
}
error = clk_set_freq(clk_sclk2, freq, 0);
if (error != 0) {
device_printf(sc->dev, "cannot set lcd ch1 frequency\n");
return (error);
}
error = clk_enable(clk_sclk2);
if (error != 0) {
device_printf(sc->dev, "cannot enable lcd ch1 sclk2\n");
return (error);
}
error = clk_enable(clk_sclk1);
if (error != 0) {
device_printf(sc->dev, "cannot enable lcd ch1 sclk1\n");
return (error);
}
return (0);
}
static int
a10fb_setup_tcon(struct a10fb_softc *sc, const struct videomode *mode)
{
u_int interlace, hspw, hbp, vspw, vbp, vbl, width, height, start_delay;
u_int vtotal, framerate, clk;
clk_t clk_ahb;
hwreset_t rst;
uint32_t val;
int error;
interlace = !!(mode->flags & VID_INTERLACE);
width = mode->hdisplay;
height = mode->vdisplay;
hspw = mode->hsync_end - mode->hsync_start;
hbp = mode->htotal - mode->hsync_start;
vspw = mode->vsync_end - mode->vsync_start;
vbp = mode->vtotal - mode->vsync_start;
vbl = VBLANK_LEN(mode->vtotal, mode->vdisplay, interlace);
start_delay = START_DELAY(vbl);
/* Leave reset */
error = hwreset_get_by_ofw_name(sc->dev, 0, "lcd", &rst);
if (error != 0) {
device_printf(sc->dev, "cannot find reset 'lcd'\n");
return (error);
}
error = hwreset_deassert(rst);
if (error != 0) {
device_printf(sc->dev, "couldn't de-assert reset 'lcd'\n");
return (error);
}
/* Gating AHB clock for LCD */
error = clk_get_by_ofw_name(sc->dev, 0, "ahb_lcd", &clk_ahb);
if (error != 0) {
device_printf(sc->dev, "cannot find clk 'ahb_lcd'\n");
return (error);
}
error = clk_enable(clk_ahb);
if (error != 0) {
device_printf(sc->dev, "cannot enable clk 'ahb_lcd'\n");
return (error);
}
/* Disable TCON and TCON1 */
TCON_WRITE(sc, TCON_GCTL, 0);
TCON_WRITE(sc, TCON1_CTL, 0);
/* Enable clocks */
TCON_WRITE(sc, TCON0_DCLK, DCLK_EN);
/* Disable IO and data output ports */
TCON_WRITE(sc, TCON1_IO_TRI, IO_TRI_MASK);
/* Disable TCON and select TCON1 */
TCON_WRITE(sc, TCON_GCTL, GCTL_IO_MAP_SEL_TCON1);
/* Source width and height */
TCON_WRITE(sc, TCON1_BASIC0, BASIC_X(width) | BASIC_Y(height));
/* Scaler width and height */
TCON_WRITE(sc, TCON1_BASIC1, BASIC_X(width) | BASIC_Y(height));
/* Output width and height */
TCON_WRITE(sc, TCON1_BASIC2, BASIC_X(width) | BASIC_Y(height));
/* Horizontal total and back porch */
TCON_WRITE(sc, TCON1_BASIC3, BASIC3_HT(mode->htotal) | BASIC3_HBP(hbp));
/* Vertical total and back porch */
vtotal = VTOTAL(mode->vtotal);
if (interlace) {
framerate = DIVIDE(DIVIDE(DOT_CLOCK_TO_HZ(mode->dot_clock),
mode->htotal), mode->vtotal);
clk = mode->htotal * (VTOTAL(mode->vtotal) + 1) * framerate;
if ((clk / 2) == DOT_CLOCK_TO_HZ(mode->dot_clock))
vtotal += 1;
}
TCON_WRITE(sc, TCON1_BASIC4, BASIC4_VT(vtotal) | BASIC4_VBP(vbp));
/* Horizontal and vertical sync */
TCON_WRITE(sc, TCON1_BASIC5, BASIC5_HSPW(hspw) | BASIC5_VSPW(vspw));
/* Polarity */
val = IO_POL_IO2_INV;
if (mode->flags & VID_PHSYNC)
val |= IO_POL_PHSYNC;
if (mode->flags & VID_PVSYNC)
val |= IO_POL_PVSYNC;
TCON_WRITE(sc, TCON1_IO_POL, val);
/* Set scan line for TCON1 line trigger */
TCON_WRITE(sc, TCON_GINT1, GINT1_TCON1_LINENO(start_delay));
/* Enable TCON1 */
val = TCON1_EN;
if (interlace)
val |= INTERLACE_EN;
val |= TCON1_START_DELAY(start_delay);
val |= TCON1_SRC_SEL(TCON1_SRC_CH1);
TCON_WRITE(sc, TCON1_CTL, val);
/* Setup PLL */
return (a10fb_setup_pll(sc, DOT_CLOCK_TO_HZ(mode->dot_clock)));
}
static void
a10fb_enable_tcon(struct a10fb_softc *sc, int onoff)
{
uint32_t val;
/* Enable TCON */
val = TCON_READ(sc, TCON_GCTL);
if (onoff)
val |= GCTL_TCON_EN;
else
val &= ~GCTL_TCON_EN;
TCON_WRITE(sc, TCON_GCTL, val);
/* Enable TCON1 IO0/IO1 outputs */
val = TCON_READ(sc, TCON1_IO_TRI);
if (onoff)
val &= ~(IO0_OUTPUT_TRI_EN | IO1_OUTPUT_TRI_EN);
else
val |= (IO0_OUTPUT_TRI_EN | IO1_OUTPUT_TRI_EN);
TCON_WRITE(sc, TCON1_IO_TRI, val);
}
static int
a10fb_configure(struct a10fb_softc *sc, const struct videomode *mode)
{
size_t fbsize;
int error;
fbsize = round_page(mode->hdisplay * mode->vdisplay * (FB_BPP / NBBY));
/* Detach the old FB device */
if (sc->fbdev != NULL) {
device_delete_child(sc->dev, sc->fbdev);
sc->fbdev = NULL;
}
/* If the FB size has changed, free the old FB memory */
if (sc->fbsize > 0 && sc->fbsize != fbsize) {
a10fb_freefb(sc);
sc->vaddr = 0;
}
/* Allocate the FB if necessary */
sc->fbsize = fbsize;
if (sc->vaddr == 0) {
error = a10fb_allocfb(sc);
if (error != 0) {
device_printf(sc->dev, "failed to allocate FB memory\n");
return (ENXIO);
}
}
/* Setup display backend */
error = a10fb_setup_debe(sc, mode);
if (error != 0)
return (error);
/* Setup display timing controller */
error = a10fb_setup_tcon(sc, mode);
if (error != 0)
return (error);
/* Attach framebuffer device */
sc->info.fb_name = device_get_nameunit(sc->dev);
sc->info.fb_vbase = (intptr_t)sc->vaddr;
sc->info.fb_pbase = sc->paddr;
sc->info.fb_size = sc->fbsize;
sc->info.fb_bpp = sc->info.fb_depth = FB_BPP;
sc->info.fb_stride = mode->hdisplay * (FB_BPP / NBBY);
sc->info.fb_width = mode->hdisplay;
sc->info.fb_height = mode->vdisplay;
sc->fbdev = device_add_child(sc->dev, "fbd", device_get_unit(sc->dev));
if (sc->fbdev == NULL) {
device_printf(sc->dev, "failed to add fbd child\n");
return (ENOENT);
}
error = device_probe_and_attach(sc->fbdev);
if (error != 0) {
device_printf(sc->dev, "failed to attach fbd device\n");
return (error);
}
return (0);
}
static void
a10fb_hdmi_event(void *arg, device_t hdmi_dev)
{
const struct videomode *mode;
struct videomode hdmi_mode;
struct a10fb_softc *sc;
struct edid_info ei;
uint8_t *edid;
uint32_t edid_len;
int error;
sc = arg;
edid = NULL;
edid_len = 0;
mode = NULL;
error = HDMI_GET_EDID(hdmi_dev, &edid, &edid_len);
if (error != 0) {
device_printf(sc->dev, "failed to get EDID: %d\n", error);
} else {
error = edid_parse(edid, &ei);
if (error != 0) {
device_printf(sc->dev, "failed to parse EDID: %d\n",
error);
} else {
if (bootverbose)
edid_print(&ei);
mode = ei.edid_preferred_mode;
}
}
/* If the preferred mode could not be determined, use the default */
if (mode == NULL)
mode = pick_mode_by_ref(FB_DEFAULT_W, FB_DEFAULT_H,
FB_DEFAULT_REF);
if (mode == NULL) {
device_printf(sc->dev, "failed to find usable video mode\n");
return;
}
if (bootverbose)
device_printf(sc->dev, "using %dx%d\n",
mode->hdisplay, mode->vdisplay);
/* Disable HDMI */
HDMI_ENABLE(hdmi_dev, 0);
/* Disable timing controller */
a10fb_enable_tcon(sc, 0);
/* Configure DEBE and TCON */
error = a10fb_configure(sc, mode);
if (error != 0) {
device_printf(sc->dev, "failed to configure FB: %d\n", error);
return;
}
hdmi_mode = *mode;
hdmi_mode.hskew = mode->hsync_end - mode->hsync_start;
hdmi_mode.flags |= VID_HSKEW;
HDMI_SET_VIDEOMODE(hdmi_dev, &hdmi_mode);
/* Enable timing controller */
a10fb_enable_tcon(sc, 1);
DELAY(HDMI_ENABLE_DELAY);
/* Enable HDMI */
HDMI_ENABLE(hdmi_dev, 1);
}
static int
a10fb_probe(device_t dev)
{
if (!ofw_bus_status_okay(dev))
return (ENXIO);
if (!ofw_bus_is_compatible(dev, "allwinner,sun7i-a20-fb"))
return (ENXIO);
device_set_desc(dev, "Allwinner Framebuffer");
return (BUS_PROBE_DEFAULT);
}
static int
a10fb_attach(device_t dev)
{
struct a10fb_softc *sc;
sc = device_get_softc(dev);
sc->dev = dev;
if (bus_alloc_resources(dev, a10fb_spec, sc->res)) {
device_printf(dev, "cannot allocate resources for device\n");
return (ENXIO);
}
sc->hdmi_evh = EVENTHANDLER_REGISTER(hdmi_event,
a10fb_hdmi_event, sc, 0);
return (0);
}
static struct fb_info *
a10fb_fb_getinfo(device_t dev)
{
struct a10fb_softc *sc;
sc = device_get_softc(dev);
return (&sc->info);
}
static device_method_t a10fb_methods[] = {
/* Device interface */
DEVMETHOD(device_probe, a10fb_probe),
DEVMETHOD(device_attach, a10fb_attach),
/* FB interface */
DEVMETHOD(fb_getinfo, a10fb_fb_getinfo),
DEVMETHOD_END
};
static driver_t a10fb_driver = {
"fb",
a10fb_methods,
sizeof(struct a10fb_softc),
};
static devclass_t a10fb_devclass;
DRIVER_MODULE(fb, simplebus, a10fb_driver, a10fb_devclass, 0, 0);