aml8726_fb.c revision 280905
1280905Sganbold/*-
2280905Sganbold * Copyright 2013-2014 John Wehle <john@feith.com>
3280905Sganbold * All rights reserved.
4280905Sganbold *
5280905Sganbold * Redistribution and use in source and binary forms, with or without
6280905Sganbold * modification, are permitted provided that the following conditions
7280905Sganbold * are met:
8280905Sganbold * 1. Redistributions of source code must retain the above copyright
9280905Sganbold *    notice, this list of conditions and the following disclaimer.
10280905Sganbold * 2. Redistributions in binary form must reproduce the above copyright
11280905Sganbold *    notice, this list of conditions and the following disclaimer in the
12280905Sganbold *    documentation and/or other materials provided with the distribution.
13280905Sganbold *
14280905Sganbold * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15280905Sganbold * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16280905Sganbold * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17280905Sganbold * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18280905Sganbold * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19280905Sganbold * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20280905Sganbold * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21280905Sganbold * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22280905Sganbold * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23280905Sganbold * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24280905Sganbold * SUCH DAMAGE.
25280905Sganbold */
26280905Sganbold
27280905Sganbold/*
28280905Sganbold * Amlogic aml8726 frame buffer driver.
29280905Sganbold *
30280905Sganbold * The current implementation has limited flexibility.
31280905Sganbold * For example only progressive scan is supported when
32280905Sganbold * using HDMI and the resolution / frame rate is not
33280905Sganbold * negotiated.
34280905Sganbold */
35280905Sganbold
36280905Sganbold#include <sys/cdefs.h>
37280905Sganbold__FBSDID("$FreeBSD: head/sys/arm/amlogic/aml8726/aml8726_fb.c 280905 2015-03-31 11:50:46Z ganbold $");
38280905Sganbold
39280905Sganbold#include <sys/param.h>
40280905Sganbold#include <sys/systm.h>
41280905Sganbold#include <sys/conf.h>
42280905Sganbold#include <sys/bus.h>
43280905Sganbold#include <sys/kernel.h>
44280905Sganbold#include <sys/module.h>
45280905Sganbold#include <sys/lock.h>
46280905Sganbold#include <sys/mutex.h>
47280905Sganbold#include <sys/resource.h>
48280905Sganbold#include <sys/rman.h>
49280905Sganbold
50280905Sganbold#include <sys/fbio.h>
51280905Sganbold
52280905Sganbold#include <machine/bus.h>
53280905Sganbold#include <machine/cpu.h>
54280905Sganbold#include <machine/fdt.h>
55280905Sganbold
56280905Sganbold#include <dev/fdt/fdt_common.h>
57280905Sganbold#include <dev/ofw/ofw_bus.h>
58280905Sganbold#include <dev/ofw/ofw_bus_subr.h>
59280905Sganbold
60280905Sganbold#include <dev/fb/fbreg.h>
61280905Sganbold#include <dev/vt/vt.h>
62280905Sganbold
63280905Sganbold#include <arm/amlogic/aml8726/aml8726_fb.h>
64280905Sganbold
65280905Sganbold#include "fb_if.h"
66280905Sganbold
67280905Sganbold
68280905Sganboldenum aml8726_fb_output {
69280905Sganbold	aml8726_unknown_fb_output,
70280905Sganbold	aml8726_cvbs_fb_output,
71280905Sganbold	aml8726_hdmi_fb_output,
72280905Sganbold	aml8726_lcd_fb_output
73280905Sganbold};
74280905Sganbold
75280905Sganboldstruct aml8726_fb_clk {
76280905Sganbold	uint32_t	freq;
77280905Sganbold	uint32_t	video_pre;
78280905Sganbold	uint32_t	video_post;
79280905Sganbold	uint32_t	video_x;
80280905Sganbold	uint32_t	hdmi_tx;
81280905Sganbold	uint32_t	encp;
82280905Sganbold	uint32_t	enci;
83280905Sganbold	uint32_t	enct;
84280905Sganbold	uint32_t	encl;
85280905Sganbold	uint32_t	vdac0;
86280905Sganbold	uint32_t	vdac1;
87280905Sganbold};
88280905Sganbold
89280905Sganboldstruct aml8726_fb_softc {
90280905Sganbold	device_t		dev;
91280905Sganbold	struct resource		*res[4];
92280905Sganbold	struct mtx		mtx;
93280905Sganbold	void			*ih_cookie;
94280905Sganbold	struct fb_info		info;
95280905Sganbold	enum aml8726_fb_output	output;
96280905Sganbold	struct aml8726_fb_clk	clk;
97280905Sganbold};
98280905Sganbold
99280905Sganboldstatic struct resource_spec aml8726_fb_spec[] = {
100280905Sganbold	{ SYS_RES_MEMORY,	0,	RF_ACTIVE },	/* CANVAS */
101280905Sganbold	{ SYS_RES_MEMORY,	1,	RF_ACTIVE },	/* VIU */
102280905Sganbold	{ SYS_RES_MEMORY,	2,	RF_ACTIVE },	/* VPP */
103280905Sganbold	{ SYS_RES_IRQ,		1,	RF_ACTIVE },	/* INT_VIU_VSYNC */
104280905Sganbold	{ -1, 0 }
105280905Sganbold};
106280905Sganbold
107280905Sganbold#define	AML_FB_LOCK(sc)			mtx_lock(&(sc)->mtx)
108280905Sganbold#define	AML_FB_UNLOCK(sc)		mtx_unlock(&(sc)->mtx)
109280905Sganbold#define	AML_FB_LOCK_INIT(sc)		\
110280905Sganbold    mtx_init(&(sc)->mtx, device_get_nameunit((sc)->dev),	\
111280905Sganbold    "fb", MTX_DEF)
112280905Sganbold#define	AML_FB_LOCK_DESTROY(sc)		mtx_destroy(&(sc)->mtx);
113280905Sganbold
114280905Sganbold#define	CAV_WRITE_4(sc, reg, val)	bus_write_4((sc)->res[0], reg, (val))
115280905Sganbold#define	CAV_READ_4(sc, reg)		bus_read_4((sc)->res[0], reg)
116280905Sganbold#define	CAV_BARRIER(sc, reg)		bus_barrier((sc)->res[0], reg, 4, \
117280905Sganbold    (BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE))
118280905Sganbold
119280905Sganbold#define	VIU_WRITE_4(sc, reg, val)	bus_write_4((sc)->res[1], reg, (val))
120280905Sganbold#define	VIU_READ_4(sc, reg)		bus_read_4((sc)->res[1], reg)
121280905Sganbold
122280905Sganbold#define	VPP_WRITE_4(sc, reg, val)	bus_write_4((sc)->res[2], reg, (val))
123280905Sganbold#define	VPP_READ_4(sc, reg)		bus_read_4((sc)->res[2], reg)
124280905Sganbold
125280905Sganbold#define	CLK_WRITE_4(sc, reg, val)	bus_write_4((sc)->res[X], reg, (val))
126280905Sganbold#define	CLK_READ_4(sc, reg)		bus_read_4((sc)->res[X], reg)
127280905Sganbold
128280905Sganbold#define	AML_FB_CLK_FREQ_SD		1080
129280905Sganbold#define	AML_FB_CLK_FREQ_HD		1488
130280905Sganbold
131280905Sganboldstatic void
132280905Sganboldaml8726_fb_cfg_output(struct aml8726_fb_softc *sc)
133280905Sganbold{
134280905Sganbold	/* XXX */
135280905Sganbold}
136280905Sganbold
137280905Sganboldstatic void
138280905Sganboldaml8726_fb_cfg_video(struct aml8726_fb_softc *sc)
139280905Sganbold{
140280905Sganbold	uint32_t value;
141280905Sganbold
142280905Sganbold	/*
143280905Sganbold	 * basic initialization
144280905Sganbold	 *
145280905Sganbold	 * The fifo depth is in units of 8 so programming 32
146280905Sganbold	 * sets the depth to 256.
147280905Sganbold	 */
148280905Sganbold
149280905Sganbold	value = (32 << AML_VIU_OSD_FIFO_CTRL_DEPTH_SHIFT);
150280905Sganbold	value |= AML_VIU_OSD_FIFO_CTRL_BURST_LEN_64;
151280905Sganbold	value |= (4 << AML_VIU_OSD_FIFO_CTRL_HOLD_LINES_SHIFT);
152280905Sganbold
153280905Sganbold	VIU_WRITE_4(sc, AML_VIU_OSD1_FIFO_CTRL_REG, value);
154280905Sganbold	VIU_WRITE_4(sc, AML_VIU_OSD2_FIFO_CTRL_REG, value);
155280905Sganbold
156280905Sganbold	value = VPP_READ_4(sc, AML_VPP_MISC_REG);
157280905Sganbold
158280905Sganbold	value &= ~AML_VPP_MISC_PREBLEND_EN;
159280905Sganbold	value |= AML_VPP_MISC_POSTBLEND_EN;
160280905Sganbold	value &= ~(AML_VPP_MISC_OSD1_POSTBLEND | AML_VPP_MISC_OSD2_POSTBLEND
161280905Sganbold	    | AML_VPP_MISC_VD1_POSTBLEND | AML_VPP_MISC_VD2_POSTBLEND);
162280905Sganbold
163280905Sganbold	VPP_WRITE_4(sc, AML_VPP_MISC_REG, value);
164280905Sganbold
165280905Sganbold	value = AML_VIU_OSD_CTRL_OSD_EN;
166280905Sganbold	value |= (0xff << AML_VIU_OSD_CTRL_GLOBAL_ALPHA_SHIFT);
167280905Sganbold
168280905Sganbold	VIU_WRITE_4(sc, AML_VIU_OSD1_CTRL_REG, value);
169280905Sganbold	VIU_WRITE_4(sc, AML_VIU_OSD2_CTRL_REG, value);
170280905Sganbold
171280905Sganbold	/* color mode for OSD1 block 0 */
172280905Sganbold
173280905Sganbold	value = (AML_CAV_OSD1_INDEX << AML_VIU_OSD_BLK_CFG_W0_INDEX_SHIFT)
174280905Sganbold	    | AML_VIU_OSD_BLK_CFG_W0_LITTLE_ENDIAN
175280905Sganbold	    | AML_VIU_OSD_BLK_CFG_W0_BLKMODE_24
176280905Sganbold	    | AML_VIU_OSD_BLK_CFG_W0_RGB_EN
177280905Sganbold	    | AML_VIU_OSD_BLK_CFG_W0_CMATRIX_RGB;
178280905Sganbold
179280905Sganbold	VIU_WRITE_4(sc, AML_VIU_OSD1_BLK0_CFG_W0_REG, value);
180280905Sganbold
181280905Sganbold	/* geometry / scaling for OSD1 block 0 */
182280905Sganbold
183280905Sganbold	value = ((sc->info.fb_width - 1) << AML_VIU_OSD_BLK_CFG_W1_X_END_SHIFT)
184280905Sganbold	    & AML_VIU_OSD_BLK_CFG_W1_X_END_MASK;
185280905Sganbold	value |= (0 << AML_VIU_OSD_BLK_CFG_W1_X_START_SHIFT)
186280905Sganbold	    & AML_VIU_OSD_BLK_CFG_W1_X_START_MASK;
187280905Sganbold
188280905Sganbold	VIU_WRITE_4(sc, AML_VIU_OSD1_BLK0_CFG_W1_REG, value);
189280905Sganbold
190280905Sganbold	value = ((sc->info.fb_height - 1) << AML_VIU_OSD_BLK_CFG_W2_Y_END_SHIFT)
191280905Sganbold	    & AML_VIU_OSD_BLK_CFG_W2_Y_END_MASK;
192280905Sganbold	value |= (0 << AML_VIU_OSD_BLK_CFG_W2_Y_START_SHIFT)
193280905Sganbold	    & AML_VIU_OSD_BLK_CFG_W2_Y_START_MASK;
194280905Sganbold
195280905Sganbold	VIU_WRITE_4(sc, AML_VIU_OSD1_BLK0_CFG_W2_REG, value);
196280905Sganbold
197280905Sganbold	value = ((sc->info.fb_width - 1) << AML_VIU_OSD_BLK_CFG_W3_H_END_SHIFT)
198280905Sganbold	    & AML_VIU_OSD_BLK_CFG_W3_H_END_MASK;
199280905Sganbold	value |= (0 << AML_VIU_OSD_BLK_CFG_W3_H_START_SHIFT)
200280905Sganbold	    & AML_VIU_OSD_BLK_CFG_W3_H_START_MASK;
201280905Sganbold
202280905Sganbold	VIU_WRITE_4(sc, AML_VIU_OSD1_BLK0_CFG_W3_REG, value);
203280905Sganbold
204280905Sganbold	value = ((sc->info.fb_height - 1) << AML_VIU_OSD_BLK_CFG_W4_V_END_SHIFT)
205280905Sganbold	    & AML_VIU_OSD_BLK_CFG_W4_V_END_MASK;
206280905Sganbold	value |= (0 << AML_VIU_OSD_BLK_CFG_W4_V_START_SHIFT)
207280905Sganbold	    & AML_VIU_OSD_BLK_CFG_W4_V_START_MASK;
208280905Sganbold
209280905Sganbold	VIU_WRITE_4(sc, AML_VIU_OSD1_BLK0_CFG_W4_REG, value);
210280905Sganbold
211280905Sganbold	/* Enable the OSD block now that it's fully configured */
212280905Sganbold
213280905Sganbold	value = VIU_READ_4(sc, AML_VIU_OSD1_CTRL_REG);
214280905Sganbold
215280905Sganbold	value &= ~AML_VIU_OSD_CTRL_OSD_BLK_EN_MASK;
216280905Sganbold	value |= 1 << AML_VIU_OSD_CTRL_OSD_BLK_EN_SHIFT;
217280905Sganbold
218280905Sganbold	VIU_WRITE_4(sc, AML_VIU_OSD1_CTRL_REG, value);
219280905Sganbold
220280905Sganbold	/* enable video processing of OSD1 */
221280905Sganbold
222280905Sganbold	value = VPP_READ_4(sc, AML_VPP_MISC_REG);
223280905Sganbold
224280905Sganbold	value |= AML_VPP_MISC_OSD1_POSTBLEND;
225280905Sganbold
226280905Sganbold	VPP_WRITE_4(sc, AML_VPP_MISC_REG, value);
227280905Sganbold}
228280905Sganbold
229280905Sganboldstatic void
230280905Sganboldaml8726_fb_cfg_canvas(struct aml8726_fb_softc *sc)
231280905Sganbold{
232280905Sganbold	uint32_t value;
233280905Sganbold	uint32_t width;
234280905Sganbold
235280905Sganbold	/*
236280905Sganbold	 * The frame buffer address and width are programmed in units of 8
237280905Sganbold	 * (meaning they need to be aligned and the actual values divided
238280905Sganbold	 * by 8 prior to programming the hardware).
239280905Sganbold	 */
240280905Sganbold
241280905Sganbold	width = (uint32_t)sc->info.fb_stride / 8;
242280905Sganbold
243280905Sganbold	/* lower bits of the width */
244280905Sganbold	value = (width << AML_CAV_LUT_DATAL_WIDTH_SHIFT) &
245280905Sganbold	    AML_CAV_LUT_DATAL_WIDTH_MASK;
246280905Sganbold
247280905Sganbold	/* physical address */
248280905Sganbold	value |= (uint32_t)sc->info.fb_pbase / 8;
249280905Sganbold
250280905Sganbold	CAV_WRITE_4(sc, AML_CAV_LUT_DATAL_REG, value);
251280905Sganbold
252280905Sganbold	/* upper bits of the width */
253280905Sganbold	value = ((width >> AML_CAV_LUT_DATAL_WIDTH_WIDTH) <<
254280905Sganbold	    AML_CAV_LUT_DATAH_WIDTH_SHIFT) & AML_CAV_LUT_DATAH_WIDTH_MASK;
255280905Sganbold
256280905Sganbold	/* height */
257280905Sganbold	value |= ((uint32_t)sc->info.fb_height <<
258280905Sganbold	    AML_CAV_LUT_DATAH_HEIGHT_SHIFT) & AML_CAV_LUT_DATAH_HEIGHT_MASK;
259280905Sganbold
260280905Sganbold	/* mode */
261280905Sganbold	value |= AML_CAV_LUT_DATAH_BLKMODE_LINEAR;
262280905Sganbold
263280905Sganbold	CAV_WRITE_4(sc, AML_CAV_LUT_DATAH_REG, value);
264280905Sganbold
265280905Sganbold	CAV_WRITE_4(sc, AML_CAV_LUT_ADDR_REG, (AML_CAV_LUT_ADDR_WR_EN |
266280905Sganbold	    (AML_CAV_OSD1_INDEX << AML_CAV_LUT_ADDR_INDEX_SHIFT)));
267280905Sganbold
268280905Sganbold	CAV_BARRIER(sc, AML_CAV_LUT_ADDR_REG);
269280905Sganbold}
270280905Sganbold
271280905Sganboldstatic void
272280905Sganboldaml8726_fb_intr(void *arg)
273280905Sganbold{
274280905Sganbold	struct aml8726_fb_softc *sc = (struct aml8726_fb_softc *)arg;
275280905Sganbold
276280905Sganbold	AML_FB_LOCK(sc);
277280905Sganbold
278280905Sganbold	AML_FB_UNLOCK(sc);
279280905Sganbold}
280280905Sganbold
281280905Sganboldstatic int
282280905Sganboldaml8726_fb_probe(device_t dev)
283280905Sganbold{
284280905Sganbold
285280905Sganbold	if (!ofw_bus_status_okay(dev))
286280905Sganbold		return (ENXIO);
287280905Sganbold
288280905Sganbold	if (!ofw_bus_is_compatible(dev, "amlogic,aml8726-fb"))
289280905Sganbold		return (ENXIO);
290280905Sganbold
291280905Sganbold	device_set_desc(dev, "Amlogic aml8726 FB");
292280905Sganbold
293280905Sganbold	return (BUS_PROBE_DEFAULT);
294280905Sganbold}
295280905Sganbold
296280905Sganboldstatic int
297280905Sganboldaml8726_fb_attach(device_t dev)
298280905Sganbold{
299280905Sganbold	struct aml8726_fb_softc *sc = device_get_softc(dev);
300280905Sganbold	int error;
301280905Sganbold	device_t child;
302280905Sganbold	pcell_t prop;
303280905Sganbold	phandle_t node;
304280905Sganbold
305280905Sganbold	sc->dev = dev;
306280905Sganbold
307280905Sganbold	sc->info.fb_name = device_get_nameunit(sc->dev);
308280905Sganbold
309280905Sganbold	node = ofw_bus_get_node(dev);
310280905Sganbold
311280905Sganbold	if (OF_getencprop(node, "width", &prop, sizeof(prop)) <= 0) {
312280905Sganbold		device_printf(dev, "missing width attribute in FDT\n");
313280905Sganbold		return (ENXIO);
314280905Sganbold	}
315280905Sganbold	if ((prop % 8) != 0) {
316280905Sganbold		device_printf(dev,
317280905Sganbold		    "width attribute in FDT must be a multiple of 8\n");
318280905Sganbold		return (ENXIO);
319280905Sganbold	}
320280905Sganbold	sc->info.fb_width = prop;
321280905Sganbold
322280905Sganbold	if (OF_getencprop(node, "height", &prop, sizeof(prop)) <= 0) {
323280905Sganbold		device_printf(dev, "missing height attribute in FDT\n");
324280905Sganbold		return (ENXIO);
325280905Sganbold	}
326280905Sganbold	sc->info.fb_height = prop;
327280905Sganbold
328280905Sganbold	if (OF_getencprop(node, "depth", &prop, sizeof(prop)) <= 0) {
329280905Sganbold		device_printf(dev, "missing depth attribute in FDT\n");
330280905Sganbold		return (ENXIO);
331280905Sganbold	}
332280905Sganbold	if (prop != 24) {
333280905Sganbold		device_printf(dev,
334280905Sganbold		    "depth attribute in FDT is an unsupported value\n");
335280905Sganbold		return (ENXIO);
336280905Sganbold	}
337280905Sganbold	sc->info.fb_depth = prop;
338280905Sganbold	sc->info.fb_bpp = prop;
339280905Sganbold
340280905Sganbold	if (OF_getencprop(node, "linebytes", &prop, sizeof(prop)) <= 0) {
341280905Sganbold		device_printf(dev, "missing linebytes attribute in FDT\n");
342280905Sganbold		return (ENXIO);
343280905Sganbold	}
344280905Sganbold	if ((prop % 8) != 0) {
345280905Sganbold		device_printf(dev,
346280905Sganbold		    "linebytes attribute in FDT must be a multiple of 8\n");
347280905Sganbold		return (ENXIO);
348280905Sganbold	}
349280905Sganbold	if (prop < (sc->info.fb_width * 3)) {
350280905Sganbold		device_printf(dev,
351280905Sganbold		    "linebytes attribute in FDT is too small\n");
352280905Sganbold		return (ENXIO);
353280905Sganbold	}
354280905Sganbold	sc->info.fb_stride = prop;
355280905Sganbold
356280905Sganbold	if (OF_getencprop(node, "address", &prop, sizeof(prop)) <= 0) {
357280905Sganbold		device_printf(dev, "missing address attribute in FDT\n");
358280905Sganbold		return (ENXIO);
359280905Sganbold	}
360280905Sganbold	if ((prop % 8) != 0) {
361280905Sganbold		device_printf(dev,
362280905Sganbold		    "address attribute in FDT must be a multiple of 8\n");
363280905Sganbold		return (ENXIO);
364280905Sganbold	}
365280905Sganbold	sc->info.fb_pbase = prop;
366280905Sganbold	sc->info.fb_size = sc->info.fb_height * sc->info.fb_stride;
367280905Sganbold	sc->info.fb_vbase = (intptr_t)pmap_mapdev(sc->info.fb_pbase,
368280905Sganbold	    sc->info.fb_size);
369280905Sganbold
370280905Sganbold	if (bus_alloc_resources(dev, aml8726_fb_spec, sc->res)) {
371280905Sganbold		device_printf(dev, "could not allocate resources for device\n");
372280905Sganbold		pmap_unmapdev(sc->info.fb_vbase, sc->info.fb_size);
373280905Sganbold		return (ENXIO);
374280905Sganbold	}
375280905Sganbold
376280905Sganbold	aml8726_fb_cfg_output(sc);
377280905Sganbold
378280905Sganbold	aml8726_fb_cfg_video(sc);
379280905Sganbold
380280905Sganbold	aml8726_fb_cfg_canvas(sc);
381280905Sganbold
382280905Sganbold	AML_FB_LOCK_INIT(sc);
383280905Sganbold
384280905Sganbold	error = bus_setup_intr(dev, sc->res[3], INTR_TYPE_MISC | INTR_MPSAFE,
385280905Sganbold	    NULL, aml8726_fb_intr, sc, &sc->ih_cookie);
386280905Sganbold
387280905Sganbold	if (error) {
388280905Sganbold		device_printf(dev, "could not setup interrupt handler\n");
389280905Sganbold		goto fail;
390280905Sganbold	}
391280905Sganbold
392280905Sganbold	child = device_add_child(dev, "fbd", device_get_unit(dev));
393280905Sganbold
394280905Sganbold	if (!child) {
395280905Sganbold		device_printf(dev, "could not add fbd\n");
396280905Sganbold		error = ENXIO;
397280905Sganbold		goto fail;
398280905Sganbold	}
399280905Sganbold
400280905Sganbold	error = device_probe_and_attach(child);
401280905Sganbold
402280905Sganbold	if (error) {
403280905Sganbold		device_printf(dev, "could not attach fbd\n");
404280905Sganbold		goto fail;
405280905Sganbold	}
406280905Sganbold
407280905Sganbold	return (0);
408280905Sganbold
409280905Sganboldfail:
410280905Sganbold	if (sc->ih_cookie)
411280905Sganbold		bus_teardown_intr(dev, sc->res[3], sc->ih_cookie);
412280905Sganbold
413280905Sganbold	AML_FB_LOCK_DESTROY(sc);
414280905Sganbold
415280905Sganbold	bus_release_resources(dev, aml8726_fb_spec, sc->res);
416280905Sganbold
417280905Sganbold	pmap_unmapdev(sc->info.fb_vbase, sc->info.fb_size);
418280905Sganbold
419280905Sganbold	return (error);
420280905Sganbold}
421280905Sganbold
422280905Sganboldstatic int
423280905Sganboldaml8726_fb_detach(device_t dev)
424280905Sganbold{
425280905Sganbold	struct aml8726_fb_softc *sc = device_get_softc(dev);
426280905Sganbold
427280905Sganbold	bus_generic_detach(dev);
428280905Sganbold
429280905Sganbold	bus_teardown_intr(dev, sc->res[3], sc->ih_cookie);
430280905Sganbold
431280905Sganbold	AML_FB_LOCK_DESTROY(sc);
432280905Sganbold
433280905Sganbold	bus_release_resources(dev, aml8726_fb_spec, sc->res);
434280905Sganbold
435280905Sganbold	pmap_unmapdev(sc->info.fb_vbase, sc->info.fb_size);
436280905Sganbold
437280905Sganbold	return (0);
438280905Sganbold}
439280905Sganbold
440280905Sganboldstatic struct fb_info *
441280905Sganboldaml8726_fb_getinfo(device_t dev)
442280905Sganbold{
443280905Sganbold	struct aml8726_fb_softc *sc = device_get_softc(dev);
444280905Sganbold
445280905Sganbold	return (&sc->info);
446280905Sganbold}
447280905Sganbold
448280905Sganboldstatic device_method_t aml8726_fb_methods[] = {
449280905Sganbold	/* Device interface */
450280905Sganbold	DEVMETHOD(device_probe,		aml8726_fb_probe),
451280905Sganbold	DEVMETHOD(device_attach,	aml8726_fb_attach),
452280905Sganbold	DEVMETHOD(device_detach,	aml8726_fb_detach),
453280905Sganbold
454280905Sganbold	/* FB interface */
455280905Sganbold	DEVMETHOD(fb_getinfo,		aml8726_fb_getinfo),
456280905Sganbold
457280905Sganbold	DEVMETHOD_END
458280905Sganbold};
459280905Sganbold
460280905Sganboldstatic driver_t aml8726_fb_driver = {
461280905Sganbold	"fb",
462280905Sganbold	aml8726_fb_methods,
463280905Sganbold	sizeof(struct aml8726_fb_softc),
464280905Sganbold};
465280905Sganbold
466280905Sganboldstatic devclass_t aml8726_fb_devclass;
467280905Sganbold
468280905SganboldDRIVER_MODULE(fb, simplebus, aml8726_fb_driver, aml8726_fb_devclass, 0, 0);
469