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
71 enum 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
78 struct 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
92 struct 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
102 static 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
134 static void
aml8726_fb_cfg_output(struct aml8726_fb_softc * sc)135 aml8726_fb_cfg_output(struct aml8726_fb_softc *sc)
136 {
137 /* XXX */
138 }
139
140 static void
aml8726_fb_cfg_video(struct aml8726_fb_softc * sc)141 aml8726_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
232 static void
aml8726_fb_cfg_canvas(struct aml8726_fb_softc * sc)233 aml8726_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
274 static void
aml8726_fb_intr(void * arg)275 aml8726_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
284 static int
aml8726_fb_probe(device_t dev)285 aml8726_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
299 static int
aml8726_fb_attach(device_t dev)300 aml8726_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
412 fail:
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
425 static int
aml8726_fb_detach(device_t dev)426 aml8726_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
443 static struct fb_info *
aml8726_fb_getinfo(device_t dev)444 aml8726_fb_getinfo(device_t dev)
445 {
446 struct aml8726_fb_softc *sc = device_get_softc(dev);
447
448 return (&sc->info);
449 }
450
451 static 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
463 static driver_t aml8726_fb_driver = {
464 "fb",
465 aml8726_fb_methods,
466 sizeof(struct aml8726_fb_softc),
467 };
468
469 static devclass_t aml8726_fb_devclass;
470
471 DRIVER_MODULE(fb, simplebus, aml8726_fb_driver, aml8726_fb_devclass, 0, 0);
472