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$");
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
52281092Sandrew#include <vm/vm.h>
53281092Sandrew#include <vm/pmap.h>
54281092Sandrew
55280905Sganbold#include <machine/bus.h>
56280905Sganbold#include <machine/cpu.h>
57280905Sganbold#include <machine/fdt.h>
58280905Sganbold
59280905Sganbold#include <dev/fdt/fdt_common.h>
60280905Sganbold#include <dev/ofw/ofw_bus.h>
61280905Sganbold#include <dev/ofw/ofw_bus_subr.h>
62280905Sganbold
63280905Sganbold#include <dev/fb/fbreg.h>
64280905Sganbold#include <dev/vt/vt.h>
65280905Sganbold
66280905Sganbold#include <arm/amlogic/aml8726/aml8726_fb.h>
67280905Sganbold
68280905Sganbold#include "fb_if.h"
69280905Sganbold
70280905Sganbold
71280905Sganboldenum aml8726_fb_output {
72280905Sganbold	aml8726_unknown_fb_output,
73280905Sganbold	aml8726_cvbs_fb_output,
74280905Sganbold	aml8726_hdmi_fb_output,
75280905Sganbold	aml8726_lcd_fb_output
76280905Sganbold};
77280905Sganbold
78280905Sganboldstruct aml8726_fb_clk {
79280905Sganbold	uint32_t	freq;
80280905Sganbold	uint32_t	video_pre;
81280905Sganbold	uint32_t	video_post;
82280905Sganbold	uint32_t	video_x;
83280905Sganbold	uint32_t	hdmi_tx;
84280905Sganbold	uint32_t	encp;
85280905Sganbold	uint32_t	enci;
86280905Sganbold	uint32_t	enct;
87280905Sganbold	uint32_t	encl;
88280905Sganbold	uint32_t	vdac0;
89280905Sganbold	uint32_t	vdac1;
90280905Sganbold};
91280905Sganbold
92280905Sganboldstruct aml8726_fb_softc {
93280905Sganbold	device_t		dev;
94280905Sganbold	struct resource		*res[4];
95280905Sganbold	struct mtx		mtx;
96280905Sganbold	void			*ih_cookie;
97280905Sganbold	struct fb_info		info;
98280905Sganbold	enum aml8726_fb_output	output;
99280905Sganbold	struct aml8726_fb_clk	clk;
100280905Sganbold};
101280905Sganbold
102280905Sganboldstatic struct resource_spec aml8726_fb_spec[] = {
103280905Sganbold	{ SYS_RES_MEMORY,	0,	RF_ACTIVE },	/* CANVAS */
104280905Sganbold	{ SYS_RES_MEMORY,	1,	RF_ACTIVE },	/* VIU */
105280905Sganbold	{ SYS_RES_MEMORY,	2,	RF_ACTIVE },	/* VPP */
106280905Sganbold	{ SYS_RES_IRQ,		1,	RF_ACTIVE },	/* INT_VIU_VSYNC */
107280905Sganbold	{ -1, 0 }
108280905Sganbold};
109280905Sganbold
110280905Sganbold#define	AML_FB_LOCK(sc)			mtx_lock(&(sc)->mtx)
111280905Sganbold#define	AML_FB_UNLOCK(sc)		mtx_unlock(&(sc)->mtx)
112280905Sganbold#define	AML_FB_LOCK_INIT(sc)		\
113280905Sganbold    mtx_init(&(sc)->mtx, device_get_nameunit((sc)->dev),	\
114280905Sganbold    "fb", MTX_DEF)
115280905Sganbold#define	AML_FB_LOCK_DESTROY(sc)		mtx_destroy(&(sc)->mtx);
116280905Sganbold
117280905Sganbold#define	CAV_WRITE_4(sc, reg, val)	bus_write_4((sc)->res[0], reg, (val))
118280905Sganbold#define	CAV_READ_4(sc, reg)		bus_read_4((sc)->res[0], reg)
119280905Sganbold#define	CAV_BARRIER(sc, reg)		bus_barrier((sc)->res[0], reg, 4, \
120280905Sganbold    (BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE))
121280905Sganbold
122280905Sganbold#define	VIU_WRITE_4(sc, reg, val)	bus_write_4((sc)->res[1], reg, (val))
123280905Sganbold#define	VIU_READ_4(sc, reg)		bus_read_4((sc)->res[1], reg)
124280905Sganbold
125280905Sganbold#define	VPP_WRITE_4(sc, reg, val)	bus_write_4((sc)->res[2], reg, (val))
126280905Sganbold#define	VPP_READ_4(sc, reg)		bus_read_4((sc)->res[2], reg)
127280905Sganbold
128280905Sganbold#define	CLK_WRITE_4(sc, reg, val)	bus_write_4((sc)->res[X], reg, (val))
129280905Sganbold#define	CLK_READ_4(sc, reg)		bus_read_4((sc)->res[X], reg)
130280905Sganbold
131280905Sganbold#define	AML_FB_CLK_FREQ_SD		1080
132280905Sganbold#define	AML_FB_CLK_FREQ_HD		1488
133280905Sganbold
134280905Sganboldstatic void
135280905Sganboldaml8726_fb_cfg_output(struct aml8726_fb_softc *sc)
136280905Sganbold{
137280905Sganbold	/* XXX */
138280905Sganbold}
139280905Sganbold
140280905Sganboldstatic void
141280905Sganboldaml8726_fb_cfg_video(struct aml8726_fb_softc *sc)
142280905Sganbold{
143280905Sganbold	uint32_t value;
144280905Sganbold
145280905Sganbold	/*
146280905Sganbold	 * basic initialization
147280905Sganbold	 *
148280905Sganbold	 * The fifo depth is in units of 8 so programming 32
149280905Sganbold	 * sets the depth to 256.
150280905Sganbold	 */
151280905Sganbold
152280905Sganbold	value = (32 << AML_VIU_OSD_FIFO_CTRL_DEPTH_SHIFT);
153280905Sganbold	value |= AML_VIU_OSD_FIFO_CTRL_BURST_LEN_64;
154280905Sganbold	value |= (4 << AML_VIU_OSD_FIFO_CTRL_HOLD_LINES_SHIFT);
155280905Sganbold
156280905Sganbold	VIU_WRITE_4(sc, AML_VIU_OSD1_FIFO_CTRL_REG, value);
157280905Sganbold	VIU_WRITE_4(sc, AML_VIU_OSD2_FIFO_CTRL_REG, value);
158280905Sganbold
159280905Sganbold	value = VPP_READ_4(sc, AML_VPP_MISC_REG);
160280905Sganbold
161280905Sganbold	value &= ~AML_VPP_MISC_PREBLEND_EN;
162280905Sganbold	value |= AML_VPP_MISC_POSTBLEND_EN;
163280905Sganbold	value &= ~(AML_VPP_MISC_OSD1_POSTBLEND | AML_VPP_MISC_OSD2_POSTBLEND
164280905Sganbold	    | AML_VPP_MISC_VD1_POSTBLEND | AML_VPP_MISC_VD2_POSTBLEND);
165280905Sganbold
166280905Sganbold	VPP_WRITE_4(sc, AML_VPP_MISC_REG, value);
167280905Sganbold
168280905Sganbold	value = AML_VIU_OSD_CTRL_OSD_EN;
169280905Sganbold	value |= (0xff << AML_VIU_OSD_CTRL_GLOBAL_ALPHA_SHIFT);
170280905Sganbold
171280905Sganbold	VIU_WRITE_4(sc, AML_VIU_OSD1_CTRL_REG, value);
172280905Sganbold	VIU_WRITE_4(sc, AML_VIU_OSD2_CTRL_REG, value);
173280905Sganbold
174280905Sganbold	/* color mode for OSD1 block 0 */
175280905Sganbold
176280905Sganbold	value = (AML_CAV_OSD1_INDEX << AML_VIU_OSD_BLK_CFG_W0_INDEX_SHIFT)
177280905Sganbold	    | AML_VIU_OSD_BLK_CFG_W0_LITTLE_ENDIAN
178280905Sganbold	    | AML_VIU_OSD_BLK_CFG_W0_BLKMODE_24
179280905Sganbold	    | AML_VIU_OSD_BLK_CFG_W0_RGB_EN
180280905Sganbold	    | AML_VIU_OSD_BLK_CFG_W0_CMATRIX_RGB;
181280905Sganbold
182280905Sganbold	VIU_WRITE_4(sc, AML_VIU_OSD1_BLK0_CFG_W0_REG, value);
183280905Sganbold
184280905Sganbold	/* geometry / scaling for OSD1 block 0 */
185280905Sganbold
186280905Sganbold	value = ((sc->info.fb_width - 1) << AML_VIU_OSD_BLK_CFG_W1_X_END_SHIFT)
187280905Sganbold	    & AML_VIU_OSD_BLK_CFG_W1_X_END_MASK;
188280905Sganbold	value |= (0 << AML_VIU_OSD_BLK_CFG_W1_X_START_SHIFT)
189280905Sganbold	    & AML_VIU_OSD_BLK_CFG_W1_X_START_MASK;
190280905Sganbold
191280905Sganbold	VIU_WRITE_4(sc, AML_VIU_OSD1_BLK0_CFG_W1_REG, value);
192280905Sganbold
193280905Sganbold	value = ((sc->info.fb_height - 1) << AML_VIU_OSD_BLK_CFG_W2_Y_END_SHIFT)
194280905Sganbold	    & AML_VIU_OSD_BLK_CFG_W2_Y_END_MASK;
195280905Sganbold	value |= (0 << AML_VIU_OSD_BLK_CFG_W2_Y_START_SHIFT)
196280905Sganbold	    & AML_VIU_OSD_BLK_CFG_W2_Y_START_MASK;
197280905Sganbold
198280905Sganbold	VIU_WRITE_4(sc, AML_VIU_OSD1_BLK0_CFG_W2_REG, value);
199280905Sganbold
200280905Sganbold	value = ((sc->info.fb_width - 1) << AML_VIU_OSD_BLK_CFG_W3_H_END_SHIFT)
201280905Sganbold	    & AML_VIU_OSD_BLK_CFG_W3_H_END_MASK;
202280905Sganbold	value |= (0 << AML_VIU_OSD_BLK_CFG_W3_H_START_SHIFT)
203280905Sganbold	    & AML_VIU_OSD_BLK_CFG_W3_H_START_MASK;
204280905Sganbold
205280905Sganbold	VIU_WRITE_4(sc, AML_VIU_OSD1_BLK0_CFG_W3_REG, value);
206280905Sganbold
207280905Sganbold	value = ((sc->info.fb_height - 1) << AML_VIU_OSD_BLK_CFG_W4_V_END_SHIFT)
208280905Sganbold	    & AML_VIU_OSD_BLK_CFG_W4_V_END_MASK;
209280905Sganbold	value |= (0 << AML_VIU_OSD_BLK_CFG_W4_V_START_SHIFT)
210280905Sganbold	    & AML_VIU_OSD_BLK_CFG_W4_V_START_MASK;
211280905Sganbold
212280905Sganbold	VIU_WRITE_4(sc, AML_VIU_OSD1_BLK0_CFG_W4_REG, value);
213280905Sganbold
214280905Sganbold	/* Enable the OSD block now that it's fully configured */
215280905Sganbold
216280905Sganbold	value = VIU_READ_4(sc, AML_VIU_OSD1_CTRL_REG);
217280905Sganbold
218280905Sganbold	value &= ~AML_VIU_OSD_CTRL_OSD_BLK_EN_MASK;
219280905Sganbold	value |= 1 << AML_VIU_OSD_CTRL_OSD_BLK_EN_SHIFT;
220280905Sganbold
221280905Sganbold	VIU_WRITE_4(sc, AML_VIU_OSD1_CTRL_REG, value);
222280905Sganbold
223280905Sganbold	/* enable video processing of OSD1 */
224280905Sganbold
225280905Sganbold	value = VPP_READ_4(sc, AML_VPP_MISC_REG);
226280905Sganbold
227280905Sganbold	value |= AML_VPP_MISC_OSD1_POSTBLEND;
228280905Sganbold
229280905Sganbold	VPP_WRITE_4(sc, AML_VPP_MISC_REG, value);
230280905Sganbold}
231280905Sganbold
232280905Sganboldstatic void
233280905Sganboldaml8726_fb_cfg_canvas(struct aml8726_fb_softc *sc)
234280905Sganbold{
235280905Sganbold	uint32_t value;
236280905Sganbold	uint32_t width;
237280905Sganbold
238280905Sganbold	/*
239280905Sganbold	 * The frame buffer address and width are programmed in units of 8
240280905Sganbold	 * (meaning they need to be aligned and the actual values divided
241280905Sganbold	 * by 8 prior to programming the hardware).
242280905Sganbold	 */
243280905Sganbold
244280905Sganbold	width = (uint32_t)sc->info.fb_stride / 8;
245280905Sganbold
246280905Sganbold	/* lower bits of the width */
247280905Sganbold	value = (width << AML_CAV_LUT_DATAL_WIDTH_SHIFT) &
248280905Sganbold	    AML_CAV_LUT_DATAL_WIDTH_MASK;
249280905Sganbold
250280905Sganbold	/* physical address */
251280905Sganbold	value |= (uint32_t)sc->info.fb_pbase / 8;
252280905Sganbold
253280905Sganbold	CAV_WRITE_4(sc, AML_CAV_LUT_DATAL_REG, value);
254280905Sganbold
255280905Sganbold	/* upper bits of the width */
256280905Sganbold	value = ((width >> AML_CAV_LUT_DATAL_WIDTH_WIDTH) <<
257280905Sganbold	    AML_CAV_LUT_DATAH_WIDTH_SHIFT) & AML_CAV_LUT_DATAH_WIDTH_MASK;
258280905Sganbold
259280905Sganbold	/* height */
260280905Sganbold	value |= ((uint32_t)sc->info.fb_height <<
261280905Sganbold	    AML_CAV_LUT_DATAH_HEIGHT_SHIFT) & AML_CAV_LUT_DATAH_HEIGHT_MASK;
262280905Sganbold
263280905Sganbold	/* mode */
264280905Sganbold	value |= AML_CAV_LUT_DATAH_BLKMODE_LINEAR;
265280905Sganbold
266280905Sganbold	CAV_WRITE_4(sc, AML_CAV_LUT_DATAH_REG, value);
267280905Sganbold
268280905Sganbold	CAV_WRITE_4(sc, AML_CAV_LUT_ADDR_REG, (AML_CAV_LUT_ADDR_WR_EN |
269280905Sganbold	    (AML_CAV_OSD1_INDEX << AML_CAV_LUT_ADDR_INDEX_SHIFT)));
270280905Sganbold
271280905Sganbold	CAV_BARRIER(sc, AML_CAV_LUT_ADDR_REG);
272280905Sganbold}
273280905Sganbold
274280905Sganboldstatic void
275280905Sganboldaml8726_fb_intr(void *arg)
276280905Sganbold{
277280905Sganbold	struct aml8726_fb_softc *sc = (struct aml8726_fb_softc *)arg;
278280905Sganbold
279280905Sganbold	AML_FB_LOCK(sc);
280280905Sganbold
281280905Sganbold	AML_FB_UNLOCK(sc);
282280905Sganbold}
283280905Sganbold
284280905Sganboldstatic int
285280905Sganboldaml8726_fb_probe(device_t dev)
286280905Sganbold{
287280905Sganbold
288280905Sganbold	if (!ofw_bus_status_okay(dev))
289280905Sganbold		return (ENXIO);
290280905Sganbold
291280905Sganbold	if (!ofw_bus_is_compatible(dev, "amlogic,aml8726-fb"))
292280905Sganbold		return (ENXIO);
293280905Sganbold
294280905Sganbold	device_set_desc(dev, "Amlogic aml8726 FB");
295280905Sganbold
296280905Sganbold	return (BUS_PROBE_DEFAULT);
297280905Sganbold}
298280905Sganbold
299280905Sganboldstatic int
300280905Sganboldaml8726_fb_attach(device_t dev)
301280905Sganbold{
302280905Sganbold	struct aml8726_fb_softc *sc = device_get_softc(dev);
303280905Sganbold	int error;
304280905Sganbold	device_t child;
305280905Sganbold	pcell_t prop;
306280905Sganbold	phandle_t node;
307280905Sganbold
308280905Sganbold	sc->dev = dev;
309280905Sganbold
310280905Sganbold	sc->info.fb_name = device_get_nameunit(sc->dev);
311280905Sganbold
312280905Sganbold	node = ofw_bus_get_node(dev);
313280905Sganbold
314280905Sganbold	if (OF_getencprop(node, "width", &prop, sizeof(prop)) <= 0) {
315280905Sganbold		device_printf(dev, "missing width attribute in FDT\n");
316280905Sganbold		return (ENXIO);
317280905Sganbold	}
318280905Sganbold	if ((prop % 8) != 0) {
319280905Sganbold		device_printf(dev,
320280905Sganbold		    "width attribute in FDT must be a multiple of 8\n");
321280905Sganbold		return (ENXIO);
322280905Sganbold	}
323280905Sganbold	sc->info.fb_width = prop;
324280905Sganbold
325280905Sganbold	if (OF_getencprop(node, "height", &prop, sizeof(prop)) <= 0) {
326280905Sganbold		device_printf(dev, "missing height attribute in FDT\n");
327280905Sganbold		return (ENXIO);
328280905Sganbold	}
329280905Sganbold	sc->info.fb_height = prop;
330280905Sganbold
331280905Sganbold	if (OF_getencprop(node, "depth", &prop, sizeof(prop)) <= 0) {
332280905Sganbold		device_printf(dev, "missing depth attribute in FDT\n");
333280905Sganbold		return (ENXIO);
334280905Sganbold	}
335280905Sganbold	if (prop != 24) {
336280905Sganbold		device_printf(dev,
337280905Sganbold		    "depth attribute in FDT is an unsupported value\n");
338280905Sganbold		return (ENXIO);
339280905Sganbold	}
340280905Sganbold	sc->info.fb_depth = prop;
341280905Sganbold	sc->info.fb_bpp = prop;
342280905Sganbold
343280905Sganbold	if (OF_getencprop(node, "linebytes", &prop, sizeof(prop)) <= 0) {
344280905Sganbold		device_printf(dev, "missing linebytes attribute in FDT\n");
345280905Sganbold		return (ENXIO);
346280905Sganbold	}
347280905Sganbold	if ((prop % 8) != 0) {
348280905Sganbold		device_printf(dev,
349280905Sganbold		    "linebytes attribute in FDT must be a multiple of 8\n");
350280905Sganbold		return (ENXIO);
351280905Sganbold	}
352280905Sganbold	if (prop < (sc->info.fb_width * 3)) {
353280905Sganbold		device_printf(dev,
354280905Sganbold		    "linebytes attribute in FDT is too small\n");
355280905Sganbold		return (ENXIO);
356280905Sganbold	}
357280905Sganbold	sc->info.fb_stride = prop;
358280905Sganbold
359280905Sganbold	if (OF_getencprop(node, "address", &prop, sizeof(prop)) <= 0) {
360280905Sganbold		device_printf(dev, "missing address attribute in FDT\n");
361280905Sganbold		return (ENXIO);
362280905Sganbold	}
363280905Sganbold	if ((prop % 8) != 0) {
364280905Sganbold		device_printf(dev,
365280905Sganbold		    "address attribute in FDT must be a multiple of 8\n");
366280905Sganbold		return (ENXIO);
367280905Sganbold	}
368280905Sganbold	sc->info.fb_pbase = prop;
369280905Sganbold	sc->info.fb_size = sc->info.fb_height * sc->info.fb_stride;
370280905Sganbold	sc->info.fb_vbase = (intptr_t)pmap_mapdev(sc->info.fb_pbase,
371280905Sganbold	    sc->info.fb_size);
372280905Sganbold
373280905Sganbold	if (bus_alloc_resources(dev, aml8726_fb_spec, sc->res)) {
374280905Sganbold		device_printf(dev, "could not allocate resources for device\n");
375280905Sganbold		pmap_unmapdev(sc->info.fb_vbase, sc->info.fb_size);
376280905Sganbold		return (ENXIO);
377280905Sganbold	}
378280905Sganbold
379280905Sganbold	aml8726_fb_cfg_output(sc);
380280905Sganbold
381280905Sganbold	aml8726_fb_cfg_video(sc);
382280905Sganbold
383280905Sganbold	aml8726_fb_cfg_canvas(sc);
384280905Sganbold
385280905Sganbold	AML_FB_LOCK_INIT(sc);
386280905Sganbold
387280905Sganbold	error = bus_setup_intr(dev, sc->res[3], INTR_TYPE_MISC | INTR_MPSAFE,
388280905Sganbold	    NULL, aml8726_fb_intr, sc, &sc->ih_cookie);
389280905Sganbold
390280905Sganbold	if (error) {
391280905Sganbold		device_printf(dev, "could not setup interrupt handler\n");
392280905Sganbold		goto fail;
393280905Sganbold	}
394280905Sganbold
395280905Sganbold	child = device_add_child(dev, "fbd", device_get_unit(dev));
396280905Sganbold
397280905Sganbold	if (!child) {
398280905Sganbold		device_printf(dev, "could not add fbd\n");
399280905Sganbold		error = ENXIO;
400280905Sganbold		goto fail;
401280905Sganbold	}
402280905Sganbold
403280905Sganbold	error = device_probe_and_attach(child);
404280905Sganbold
405280905Sganbold	if (error) {
406280905Sganbold		device_printf(dev, "could not attach fbd\n");
407280905Sganbold		goto fail;
408280905Sganbold	}
409280905Sganbold
410280905Sganbold	return (0);
411280905Sganbold
412280905Sganboldfail:
413280905Sganbold	if (sc->ih_cookie)
414280905Sganbold		bus_teardown_intr(dev, sc->res[3], sc->ih_cookie);
415280905Sganbold
416280905Sganbold	AML_FB_LOCK_DESTROY(sc);
417280905Sganbold
418280905Sganbold	bus_release_resources(dev, aml8726_fb_spec, sc->res);
419280905Sganbold
420280905Sganbold	pmap_unmapdev(sc->info.fb_vbase, sc->info.fb_size);
421280905Sganbold
422280905Sganbold	return (error);
423280905Sganbold}
424280905Sganbold
425280905Sganboldstatic int
426280905Sganboldaml8726_fb_detach(device_t dev)
427280905Sganbold{
428280905Sganbold	struct aml8726_fb_softc *sc = device_get_softc(dev);
429280905Sganbold
430280905Sganbold	bus_generic_detach(dev);
431280905Sganbold
432280905Sganbold	bus_teardown_intr(dev, sc->res[3], sc->ih_cookie);
433280905Sganbold
434280905Sganbold	AML_FB_LOCK_DESTROY(sc);
435280905Sganbold
436280905Sganbold	bus_release_resources(dev, aml8726_fb_spec, sc->res);
437280905Sganbold
438280905Sganbold	pmap_unmapdev(sc->info.fb_vbase, sc->info.fb_size);
439280905Sganbold
440280905Sganbold	return (0);
441280905Sganbold}
442280905Sganbold
443280905Sganboldstatic struct fb_info *
444280905Sganboldaml8726_fb_getinfo(device_t dev)
445280905Sganbold{
446280905Sganbold	struct aml8726_fb_softc *sc = device_get_softc(dev);
447280905Sganbold
448280905Sganbold	return (&sc->info);
449280905Sganbold}
450280905Sganbold
451280905Sganboldstatic device_method_t aml8726_fb_methods[] = {
452280905Sganbold	/* Device interface */
453280905Sganbold	DEVMETHOD(device_probe,		aml8726_fb_probe),
454280905Sganbold	DEVMETHOD(device_attach,	aml8726_fb_attach),
455280905Sganbold	DEVMETHOD(device_detach,	aml8726_fb_detach),
456280905Sganbold
457280905Sganbold	/* FB interface */
458280905Sganbold	DEVMETHOD(fb_getinfo,		aml8726_fb_getinfo),
459280905Sganbold
460280905Sganbold	DEVMETHOD_END
461280905Sganbold};
462280905Sganbold
463280905Sganboldstatic driver_t aml8726_fb_driver = {
464280905Sganbold	"fb",
465280905Sganbold	aml8726_fb_methods,
466280905Sganbold	sizeof(struct aml8726_fb_softc),
467280905Sganbold};
468280905Sganbold
469280905Sganboldstatic devclass_t aml8726_fb_devclass;
470280905Sganbold
471280905SganboldDRIVER_MODULE(fb, simplebus, aml8726_fb_driver, aml8726_fb_devclass, 0, 0);
472