1/*-
2 * Copyright 2013-2014 John Wehle <john@feith.com>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27/*
28 * Amlogic aml8726 frame buffer driver.
29 *
30 * The current implementation has limited flexibility.
31 * For example only progressive scan is supported when
32 * using HDMI and the resolution / frame rate is not
33 * negotiated.
34 */
35
36#include <sys/cdefs.h>
37__FBSDID("$FreeBSD$");
38
39#include <sys/param.h>
40#include <sys/systm.h>
41#include <sys/conf.h>
42#include <sys/bus.h>
43#include <sys/kernel.h>
44#include <sys/module.h>
45#include <sys/lock.h>
46#include <sys/mutex.h>
47#include <sys/resource.h>
48#include <sys/rman.h>
49
50#include <sys/fbio.h>
51
52#include <vm/vm.h>
53#include <vm/pmap.h>
54
55#include <machine/bus.h>
56#include <machine/cpu.h>
57#include <machine/fdt.h>
58
59#include <dev/fdt/fdt_common.h>
60#include <dev/ofw/ofw_bus.h>
61#include <dev/ofw/ofw_bus_subr.h>
62
63#include <dev/fb/fbreg.h>
64#include <dev/vt/vt.h>
65
66#include <arm/amlogic/aml8726/aml8726_fb.h>
67
68#include "fb_if.h"
69
70
71enum aml8726_fb_output {
72	aml8726_unknown_fb_output,
73	aml8726_cvbs_fb_output,
74	aml8726_hdmi_fb_output,
75	aml8726_lcd_fb_output
76};
77
78struct aml8726_fb_clk {
79	uint32_t	freq;
80	uint32_t	video_pre;
81	uint32_t	video_post;
82	uint32_t	video_x;
83	uint32_t	hdmi_tx;
84	uint32_t	encp;
85	uint32_t	enci;
86	uint32_t	enct;
87	uint32_t	encl;
88	uint32_t	vdac0;
89	uint32_t	vdac1;
90};
91
92struct aml8726_fb_softc {
93	device_t		dev;
94	struct resource		*res[4];
95	struct mtx		mtx;
96	void			*ih_cookie;
97	struct fb_info		info;
98	enum aml8726_fb_output	output;
99	struct aml8726_fb_clk	clk;
100};
101
102static struct resource_spec aml8726_fb_spec[] = {
103	{ SYS_RES_MEMORY,	0,	RF_ACTIVE },	/* CANVAS */
104	{ SYS_RES_MEMORY,	1,	RF_ACTIVE },	/* VIU */
105	{ SYS_RES_MEMORY,	2,	RF_ACTIVE },	/* VPP */
106	{ SYS_RES_IRQ,		1,	RF_ACTIVE },	/* INT_VIU_VSYNC */
107	{ -1, 0 }
108};
109
110#define	AML_FB_LOCK(sc)			mtx_lock(&(sc)->mtx)
111#define	AML_FB_UNLOCK(sc)		mtx_unlock(&(sc)->mtx)
112#define	AML_FB_LOCK_INIT(sc)		\
113    mtx_init(&(sc)->mtx, device_get_nameunit((sc)->dev),	\
114    "fb", MTX_DEF)
115#define	AML_FB_LOCK_DESTROY(sc)		mtx_destroy(&(sc)->mtx);
116
117#define	CAV_WRITE_4(sc, reg, val)	bus_write_4((sc)->res[0], reg, (val))
118#define	CAV_READ_4(sc, reg)		bus_read_4((sc)->res[0], reg)
119#define	CAV_BARRIER(sc, reg)		bus_barrier((sc)->res[0], reg, 4, \
120    (BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE))
121
122#define	VIU_WRITE_4(sc, reg, val)	bus_write_4((sc)->res[1], reg, (val))
123#define	VIU_READ_4(sc, reg)		bus_read_4((sc)->res[1], reg)
124
125#define	VPP_WRITE_4(sc, reg, val)	bus_write_4((sc)->res[2], reg, (val))
126#define	VPP_READ_4(sc, reg)		bus_read_4((sc)->res[2], reg)
127
128#define	CLK_WRITE_4(sc, reg, val)	bus_write_4((sc)->res[X], reg, (val))
129#define	CLK_READ_4(sc, reg)		bus_read_4((sc)->res[X], reg)
130
131#define	AML_FB_CLK_FREQ_SD		1080
132#define	AML_FB_CLK_FREQ_HD		1488
133
134static void
135aml8726_fb_cfg_output(struct aml8726_fb_softc *sc)
136{
137	/* XXX */
138}
139
140static void
141aml8726_fb_cfg_video(struct aml8726_fb_softc *sc)
142{
143	uint32_t value;
144
145	/*
146	 * basic initialization
147	 *
148	 * The fifo depth is in units of 8 so programming 32
149	 * sets the depth to 256.
150	 */
151
152	value = (32 << AML_VIU_OSD_FIFO_CTRL_DEPTH_SHIFT);
153	value |= AML_VIU_OSD_FIFO_CTRL_BURST_LEN_64;
154	value |= (4 << AML_VIU_OSD_FIFO_CTRL_HOLD_LINES_SHIFT);
155
156	VIU_WRITE_4(sc, AML_VIU_OSD1_FIFO_CTRL_REG, value);
157	VIU_WRITE_4(sc, AML_VIU_OSD2_FIFO_CTRL_REG, value);
158
159	value = VPP_READ_4(sc, AML_VPP_MISC_REG);
160
161	value &= ~AML_VPP_MISC_PREBLEND_EN;
162	value |= AML_VPP_MISC_POSTBLEND_EN;
163	value &= ~(AML_VPP_MISC_OSD1_POSTBLEND | AML_VPP_MISC_OSD2_POSTBLEND
164	    | AML_VPP_MISC_VD1_POSTBLEND | AML_VPP_MISC_VD2_POSTBLEND);
165
166	VPP_WRITE_4(sc, AML_VPP_MISC_REG, value);
167
168	value = AML_VIU_OSD_CTRL_OSD_EN;
169	value |= (0xff << AML_VIU_OSD_CTRL_GLOBAL_ALPHA_SHIFT);
170
171	VIU_WRITE_4(sc, AML_VIU_OSD1_CTRL_REG, value);
172	VIU_WRITE_4(sc, AML_VIU_OSD2_CTRL_REG, value);
173
174	/* color mode for OSD1 block 0 */
175
176	value = (AML_CAV_OSD1_INDEX << AML_VIU_OSD_BLK_CFG_W0_INDEX_SHIFT)
177	    | AML_VIU_OSD_BLK_CFG_W0_LITTLE_ENDIAN
178	    | AML_VIU_OSD_BLK_CFG_W0_BLKMODE_24
179	    | AML_VIU_OSD_BLK_CFG_W0_RGB_EN
180	    | AML_VIU_OSD_BLK_CFG_W0_CMATRIX_RGB;
181
182	VIU_WRITE_4(sc, AML_VIU_OSD1_BLK0_CFG_W0_REG, value);
183
184	/* geometry / scaling for OSD1 block 0 */
185
186	value = ((sc->info.fb_width - 1) << AML_VIU_OSD_BLK_CFG_W1_X_END_SHIFT)
187	    & AML_VIU_OSD_BLK_CFG_W1_X_END_MASK;
188	value |= (0 << AML_VIU_OSD_BLK_CFG_W1_X_START_SHIFT)
189	    & AML_VIU_OSD_BLK_CFG_W1_X_START_MASK;
190
191	VIU_WRITE_4(sc, AML_VIU_OSD1_BLK0_CFG_W1_REG, value);
192
193	value = ((sc->info.fb_height - 1) << AML_VIU_OSD_BLK_CFG_W2_Y_END_SHIFT)
194	    & AML_VIU_OSD_BLK_CFG_W2_Y_END_MASK;
195	value |= (0 << AML_VIU_OSD_BLK_CFG_W2_Y_START_SHIFT)
196	    & AML_VIU_OSD_BLK_CFG_W2_Y_START_MASK;
197
198	VIU_WRITE_4(sc, AML_VIU_OSD1_BLK0_CFG_W2_REG, value);
199
200	value = ((sc->info.fb_width - 1) << AML_VIU_OSD_BLK_CFG_W3_H_END_SHIFT)
201	    & AML_VIU_OSD_BLK_CFG_W3_H_END_MASK;
202	value |= (0 << AML_VIU_OSD_BLK_CFG_W3_H_START_SHIFT)
203	    & AML_VIU_OSD_BLK_CFG_W3_H_START_MASK;
204
205	VIU_WRITE_4(sc, AML_VIU_OSD1_BLK0_CFG_W3_REG, value);
206
207	value = ((sc->info.fb_height - 1) << AML_VIU_OSD_BLK_CFG_W4_V_END_SHIFT)
208	    & AML_VIU_OSD_BLK_CFG_W4_V_END_MASK;
209	value |= (0 << AML_VIU_OSD_BLK_CFG_W4_V_START_SHIFT)
210	    & AML_VIU_OSD_BLK_CFG_W4_V_START_MASK;
211
212	VIU_WRITE_4(sc, AML_VIU_OSD1_BLK0_CFG_W4_REG, value);
213
214	/* Enable the OSD block now that it's fully configured */
215
216	value = VIU_READ_4(sc, AML_VIU_OSD1_CTRL_REG);
217
218	value &= ~AML_VIU_OSD_CTRL_OSD_BLK_EN_MASK;
219	value |= 1 << AML_VIU_OSD_CTRL_OSD_BLK_EN_SHIFT;
220
221	VIU_WRITE_4(sc, AML_VIU_OSD1_CTRL_REG, value);
222
223	/* enable video processing of OSD1 */
224
225	value = VPP_READ_4(sc, AML_VPP_MISC_REG);
226
227	value |= AML_VPP_MISC_OSD1_POSTBLEND;
228
229	VPP_WRITE_4(sc, AML_VPP_MISC_REG, value);
230}
231
232static void
233aml8726_fb_cfg_canvas(struct aml8726_fb_softc *sc)
234{
235	uint32_t value;
236	uint32_t width;
237
238	/*
239	 * The frame buffer address and width are programmed in units of 8
240	 * (meaning they need to be aligned and the actual values divided
241	 * by 8 prior to programming the hardware).
242	 */
243
244	width = (uint32_t)sc->info.fb_stride / 8;
245
246	/* lower bits of the width */
247	value = (width << AML_CAV_LUT_DATAL_WIDTH_SHIFT) &
248	    AML_CAV_LUT_DATAL_WIDTH_MASK;
249
250	/* physical address */
251	value |= (uint32_t)sc->info.fb_pbase / 8;
252
253	CAV_WRITE_4(sc, AML_CAV_LUT_DATAL_REG, value);
254
255	/* upper bits of the width */
256	value = ((width >> AML_CAV_LUT_DATAL_WIDTH_WIDTH) <<
257	    AML_CAV_LUT_DATAH_WIDTH_SHIFT) & AML_CAV_LUT_DATAH_WIDTH_MASK;
258
259	/* height */
260	value |= ((uint32_t)sc->info.fb_height <<
261	    AML_CAV_LUT_DATAH_HEIGHT_SHIFT) & AML_CAV_LUT_DATAH_HEIGHT_MASK;
262
263	/* mode */
264	value |= AML_CAV_LUT_DATAH_BLKMODE_LINEAR;
265
266	CAV_WRITE_4(sc, AML_CAV_LUT_DATAH_REG, value);
267
268	CAV_WRITE_4(sc, AML_CAV_LUT_ADDR_REG, (AML_CAV_LUT_ADDR_WR_EN |
269	    (AML_CAV_OSD1_INDEX << AML_CAV_LUT_ADDR_INDEX_SHIFT)));
270
271	CAV_BARRIER(sc, AML_CAV_LUT_ADDR_REG);
272}
273
274static void
275aml8726_fb_intr(void *arg)
276{
277	struct aml8726_fb_softc *sc = (struct aml8726_fb_softc *)arg;
278
279	AML_FB_LOCK(sc);
280
281	AML_FB_UNLOCK(sc);
282}
283
284static int
285aml8726_fb_probe(device_t dev)
286{
287
288	if (!ofw_bus_status_okay(dev))
289		return (ENXIO);
290
291	if (!ofw_bus_is_compatible(dev, "amlogic,aml8726-fb"))
292		return (ENXIO);
293
294	device_set_desc(dev, "Amlogic aml8726 FB");
295
296	return (BUS_PROBE_DEFAULT);
297}
298
299static int
300aml8726_fb_attach(device_t dev)
301{
302	struct aml8726_fb_softc *sc = device_get_softc(dev);
303	int error;
304	device_t child;
305	pcell_t prop;
306	phandle_t node;
307
308	sc->dev = dev;
309
310	sc->info.fb_name = device_get_nameunit(sc->dev);
311
312	node = ofw_bus_get_node(dev);
313
314	if (OF_getencprop(node, "width", &prop, sizeof(prop)) <= 0) {
315		device_printf(dev, "missing width attribute in FDT\n");
316		return (ENXIO);
317	}
318	if ((prop % 8) != 0) {
319		device_printf(dev,
320		    "width attribute in FDT must be a multiple of 8\n");
321		return (ENXIO);
322	}
323	sc->info.fb_width = prop;
324
325	if (OF_getencprop(node, "height", &prop, sizeof(prop)) <= 0) {
326		device_printf(dev, "missing height attribute in FDT\n");
327		return (ENXIO);
328	}
329	sc->info.fb_height = prop;
330
331	if (OF_getencprop(node, "depth", &prop, sizeof(prop)) <= 0) {
332		device_printf(dev, "missing depth attribute in FDT\n");
333		return (ENXIO);
334	}
335	if (prop != 24) {
336		device_printf(dev,
337		    "depth attribute in FDT is an unsupported value\n");
338		return (ENXIO);
339	}
340	sc->info.fb_depth = prop;
341	sc->info.fb_bpp = prop;
342
343	if (OF_getencprop(node, "linebytes", &prop, sizeof(prop)) <= 0) {
344		device_printf(dev, "missing linebytes attribute in FDT\n");
345		return (ENXIO);
346	}
347	if ((prop % 8) != 0) {
348		device_printf(dev,
349		    "linebytes attribute in FDT must be a multiple of 8\n");
350		return (ENXIO);
351	}
352	if (prop < (sc->info.fb_width * 3)) {
353		device_printf(dev,
354		    "linebytes attribute in FDT is too small\n");
355		return (ENXIO);
356	}
357	sc->info.fb_stride = prop;
358
359	if (OF_getencprop(node, "address", &prop, sizeof(prop)) <= 0) {
360		device_printf(dev, "missing address attribute in FDT\n");
361		return (ENXIO);
362	}
363	if ((prop % 8) != 0) {
364		device_printf(dev,
365		    "address attribute in FDT must be a multiple of 8\n");
366		return (ENXIO);
367	}
368	sc->info.fb_pbase = prop;
369	sc->info.fb_size = sc->info.fb_height * sc->info.fb_stride;
370	sc->info.fb_vbase = (intptr_t)pmap_mapdev(sc->info.fb_pbase,
371	    sc->info.fb_size);
372
373	if (bus_alloc_resources(dev, aml8726_fb_spec, sc->res)) {
374		device_printf(dev, "could not allocate resources for device\n");
375		pmap_unmapdev(sc->info.fb_vbase, sc->info.fb_size);
376		return (ENXIO);
377	}
378
379	aml8726_fb_cfg_output(sc);
380
381	aml8726_fb_cfg_video(sc);
382
383	aml8726_fb_cfg_canvas(sc);
384
385	AML_FB_LOCK_INIT(sc);
386
387	error = bus_setup_intr(dev, sc->res[3], INTR_TYPE_MISC | INTR_MPSAFE,
388	    NULL, aml8726_fb_intr, sc, &sc->ih_cookie);
389
390	if (error) {
391		device_printf(dev, "could not setup interrupt handler\n");
392		goto fail;
393	}
394
395	child = device_add_child(dev, "fbd", device_get_unit(dev));
396
397	if (!child) {
398		device_printf(dev, "could not add fbd\n");
399		error = ENXIO;
400		goto fail;
401	}
402
403	error = device_probe_and_attach(child);
404
405	if (error) {
406		device_printf(dev, "could not attach fbd\n");
407		goto fail;
408	}
409
410	return (0);
411
412fail:
413	if (sc->ih_cookie)
414		bus_teardown_intr(dev, sc->res[3], sc->ih_cookie);
415
416	AML_FB_LOCK_DESTROY(sc);
417
418	bus_release_resources(dev, aml8726_fb_spec, sc->res);
419
420	pmap_unmapdev(sc->info.fb_vbase, sc->info.fb_size);
421
422	return (error);
423}
424
425static int
426aml8726_fb_detach(device_t dev)
427{
428	struct aml8726_fb_softc *sc = device_get_softc(dev);
429
430	bus_generic_detach(dev);
431
432	bus_teardown_intr(dev, sc->res[3], sc->ih_cookie);
433
434	AML_FB_LOCK_DESTROY(sc);
435
436	bus_release_resources(dev, aml8726_fb_spec, sc->res);
437
438	pmap_unmapdev(sc->info.fb_vbase, sc->info.fb_size);
439
440	return (0);
441}
442
443static struct fb_info *
444aml8726_fb_getinfo(device_t dev)
445{
446	struct aml8726_fb_softc *sc = device_get_softc(dev);
447
448	return (&sc->info);
449}
450
451static device_method_t aml8726_fb_methods[] = {
452	/* Device interface */
453	DEVMETHOD(device_probe,		aml8726_fb_probe),
454	DEVMETHOD(device_attach,	aml8726_fb_attach),
455	DEVMETHOD(device_detach,	aml8726_fb_detach),
456
457	/* FB interface */
458	DEVMETHOD(fb_getinfo,		aml8726_fb_getinfo),
459
460	DEVMETHOD_END
461};
462
463static driver_t aml8726_fb_driver = {
464	"fb",
465	aml8726_fb_methods,
466	sizeof(struct aml8726_fb_softc),
467};
468
469static devclass_t aml8726_fb_devclass;
470
471DRIVER_MODULE(fb, simplebus, aml8726_fb_driver, aml8726_fb_devclass, 0, 0);
472