/*-
 * 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: releng/12.0/sys/arm/allwinner/a10_fb.c 338318 2018-08-25 19:38:08Z alc $
 */

/*
 * Allwinner A10/A20 Framebuffer
 */

#include <sys/cdefs.h>
__FBSDID("$FreeBSD: releng/12.0/sys/arm/allwinner/a10_fb.c 338318 2018-08-25 19:38:08Z alc $");

#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(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(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);
