diff options
Diffstat (limited to 'drivers/video/fbdev/fsl-diu-fb.c')
-rw-r--r-- | drivers/video/fbdev/fsl-diu-fb.c | 1994 |
1 files changed, 1994 insertions, 0 deletions
diff --git a/drivers/video/fbdev/fsl-diu-fb.c b/drivers/video/fbdev/fsl-diu-fb.c new file mode 100644 index 000000000000..e8758b9c3bcc --- /dev/null +++ b/drivers/video/fbdev/fsl-diu-fb.c | |||
@@ -0,0 +1,1994 @@ | |||
1 | /* | ||
2 | * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. | ||
3 | * | ||
4 | * Freescale DIU Frame Buffer device driver | ||
5 | * | ||
6 | * Authors: Hongjun Chen <hong-jun.chen@freescale.com> | ||
7 | * Paul Widmer <paul.widmer@freescale.com> | ||
8 | * Srikanth Srinivasan <srikanth.srinivasan@freescale.com> | ||
9 | * York Sun <yorksun@freescale.com> | ||
10 | * | ||
11 | * Based on imxfb.c Copyright (C) 2004 S.Hauer, Pengutronix | ||
12 | * | ||
13 | * This program is free software; you can redistribute it and/or modify it | ||
14 | * under the terms of the GNU General Public License as published by the | ||
15 | * Free Software Foundation; either version 2 of the License, or (at your | ||
16 | * option) any later version. | ||
17 | * | ||
18 | */ | ||
19 | |||
20 | #include <linux/module.h> | ||
21 | #include <linux/kernel.h> | ||
22 | #include <linux/errno.h> | ||
23 | #include <linux/string.h> | ||
24 | #include <linux/slab.h> | ||
25 | #include <linux/fb.h> | ||
26 | #include <linux/init.h> | ||
27 | #include <linux/dma-mapping.h> | ||
28 | #include <linux/platform_device.h> | ||
29 | #include <linux/interrupt.h> | ||
30 | #include <linux/clk.h> | ||
31 | #include <linux/uaccess.h> | ||
32 | #include <linux/vmalloc.h> | ||
33 | #include <linux/spinlock.h> | ||
34 | #include <linux/of_address.h> | ||
35 | #include <linux/of_irq.h> | ||
36 | |||
37 | #include <sysdev/fsl_soc.h> | ||
38 | #include <linux/fsl-diu-fb.h> | ||
39 | #include "edid.h" | ||
40 | |||
41 | #define NUM_AOIS 5 /* 1 for plane 0, 2 for planes 1 & 2 each */ | ||
42 | |||
43 | /* HW cursor parameters */ | ||
44 | #define MAX_CURS 32 | ||
45 | |||
46 | /* INT_STATUS/INT_MASK field descriptions */ | ||
47 | #define INT_VSYNC 0x01 /* Vsync interrupt */ | ||
48 | #define INT_VSYNC_WB 0x02 /* Vsync interrupt for write back operation */ | ||
49 | #define INT_UNDRUN 0x04 /* Under run exception interrupt */ | ||
50 | #define INT_PARERR 0x08 /* Display parameters error interrupt */ | ||
51 | #define INT_LS_BF_VS 0x10 /* Lines before vsync. interrupt */ | ||
52 | |||
53 | /* | ||
54 | * List of supported video modes | ||
55 | * | ||
56 | * The first entry is the default video mode. The remain entries are in | ||
57 | * order if increasing resolution and frequency. The 320x240-60 mode is | ||
58 | * the initial AOI for the second and third planes. | ||
59 | */ | ||
60 | static struct fb_videomode fsl_diu_mode_db[] = { | ||
61 | { | ||
62 | .refresh = 60, | ||
63 | .xres = 1024, | ||
64 | .yres = 768, | ||
65 | .pixclock = 15385, | ||
66 | .left_margin = 160, | ||
67 | .right_margin = 24, | ||
68 | .upper_margin = 29, | ||
69 | .lower_margin = 3, | ||
70 | .hsync_len = 136, | ||
71 | .vsync_len = 6, | ||
72 | .sync = FB_SYNC_COMP_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
73 | .vmode = FB_VMODE_NONINTERLACED | ||
74 | }, | ||
75 | { | ||
76 | .refresh = 60, | ||
77 | .xres = 320, | ||
78 | .yres = 240, | ||
79 | .pixclock = 79440, | ||
80 | .left_margin = 16, | ||
81 | .right_margin = 16, | ||
82 | .upper_margin = 16, | ||
83 | .lower_margin = 5, | ||
84 | .hsync_len = 48, | ||
85 | .vsync_len = 1, | ||
86 | .sync = FB_SYNC_COMP_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
87 | .vmode = FB_VMODE_NONINTERLACED | ||
88 | }, | ||
89 | { | ||
90 | .refresh = 60, | ||
91 | .xres = 640, | ||
92 | .yres = 480, | ||
93 | .pixclock = 39722, | ||
94 | .left_margin = 48, | ||
95 | .right_margin = 16, | ||
96 | .upper_margin = 33, | ||
97 | .lower_margin = 10, | ||
98 | .hsync_len = 96, | ||
99 | .vsync_len = 2, | ||
100 | .sync = FB_SYNC_COMP_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
101 | .vmode = FB_VMODE_NONINTERLACED | ||
102 | }, | ||
103 | { | ||
104 | .refresh = 72, | ||
105 | .xres = 640, | ||
106 | .yres = 480, | ||
107 | .pixclock = 32052, | ||
108 | .left_margin = 128, | ||
109 | .right_margin = 24, | ||
110 | .upper_margin = 28, | ||
111 | .lower_margin = 9, | ||
112 | .hsync_len = 40, | ||
113 | .vsync_len = 3, | ||
114 | .sync = FB_SYNC_COMP_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
115 | .vmode = FB_VMODE_NONINTERLACED | ||
116 | }, | ||
117 | { | ||
118 | .refresh = 75, | ||
119 | .xres = 640, | ||
120 | .yres = 480, | ||
121 | .pixclock = 31747, | ||
122 | .left_margin = 120, | ||
123 | .right_margin = 16, | ||
124 | .upper_margin = 16, | ||
125 | .lower_margin = 1, | ||
126 | .hsync_len = 64, | ||
127 | .vsync_len = 3, | ||
128 | .sync = FB_SYNC_COMP_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
129 | .vmode = FB_VMODE_NONINTERLACED | ||
130 | }, | ||
131 | { | ||
132 | .refresh = 90, | ||
133 | .xres = 640, | ||
134 | .yres = 480, | ||
135 | .pixclock = 25057, | ||
136 | .left_margin = 120, | ||
137 | .right_margin = 32, | ||
138 | .upper_margin = 14, | ||
139 | .lower_margin = 25, | ||
140 | .hsync_len = 40, | ||
141 | .vsync_len = 14, | ||
142 | .sync = FB_SYNC_COMP_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
143 | .vmode = FB_VMODE_NONINTERLACED | ||
144 | }, | ||
145 | { | ||
146 | .refresh = 100, | ||
147 | .xres = 640, | ||
148 | .yres = 480, | ||
149 | .pixclock = 22272, | ||
150 | .left_margin = 48, | ||
151 | .right_margin = 32, | ||
152 | .upper_margin = 17, | ||
153 | .lower_margin = 22, | ||
154 | .hsync_len = 128, | ||
155 | .vsync_len = 12, | ||
156 | .sync = FB_SYNC_COMP_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
157 | .vmode = FB_VMODE_NONINTERLACED | ||
158 | }, | ||
159 | { | ||
160 | .refresh = 60, | ||
161 | .xres = 800, | ||
162 | .yres = 480, | ||
163 | .pixclock = 33805, | ||
164 | .left_margin = 96, | ||
165 | .right_margin = 24, | ||
166 | .upper_margin = 10, | ||
167 | .lower_margin = 3, | ||
168 | .hsync_len = 72, | ||
169 | .vsync_len = 7, | ||
170 | .sync = FB_SYNC_COMP_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
171 | .vmode = FB_VMODE_NONINTERLACED | ||
172 | }, | ||
173 | { | ||
174 | .refresh = 60, | ||
175 | .xres = 800, | ||
176 | .yres = 600, | ||
177 | .pixclock = 25000, | ||
178 | .left_margin = 88, | ||
179 | .right_margin = 40, | ||
180 | .upper_margin = 23, | ||
181 | .lower_margin = 1, | ||
182 | .hsync_len = 128, | ||
183 | .vsync_len = 4, | ||
184 | .sync = FB_SYNC_COMP_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
185 | .vmode = FB_VMODE_NONINTERLACED | ||
186 | }, | ||
187 | { | ||
188 | .refresh = 60, | ||
189 | .xres = 854, | ||
190 | .yres = 480, | ||
191 | .pixclock = 31518, | ||
192 | .left_margin = 104, | ||
193 | .right_margin = 16, | ||
194 | .upper_margin = 13, | ||
195 | .lower_margin = 1, | ||
196 | .hsync_len = 88, | ||
197 | .vsync_len = 3, | ||
198 | .sync = FB_SYNC_COMP_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
199 | .vmode = FB_VMODE_NONINTERLACED | ||
200 | }, | ||
201 | { | ||
202 | .refresh = 70, | ||
203 | .xres = 1024, | ||
204 | .yres = 768, | ||
205 | .pixclock = 16886, | ||
206 | .left_margin = 3, | ||
207 | .right_margin = 3, | ||
208 | .upper_margin = 2, | ||
209 | .lower_margin = 2, | ||
210 | .hsync_len = 40, | ||
211 | .vsync_len = 18, | ||
212 | .sync = FB_SYNC_COMP_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
213 | .vmode = FB_VMODE_NONINTERLACED | ||
214 | }, | ||
215 | { | ||
216 | .refresh = 75, | ||
217 | .xres = 1024, | ||
218 | .yres = 768, | ||
219 | .pixclock = 15009, | ||
220 | .left_margin = 3, | ||
221 | .right_margin = 3, | ||
222 | .upper_margin = 2, | ||
223 | .lower_margin = 2, | ||
224 | .hsync_len = 80, | ||
225 | .vsync_len = 32, | ||
226 | .sync = FB_SYNC_COMP_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
227 | .vmode = FB_VMODE_NONINTERLACED | ||
228 | }, | ||
229 | { | ||
230 | .refresh = 60, | ||
231 | .xres = 1280, | ||
232 | .yres = 480, | ||
233 | .pixclock = 18939, | ||
234 | .left_margin = 353, | ||
235 | .right_margin = 47, | ||
236 | .upper_margin = 39, | ||
237 | .lower_margin = 4, | ||
238 | .hsync_len = 8, | ||
239 | .vsync_len = 2, | ||
240 | .sync = FB_SYNC_COMP_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
241 | .vmode = FB_VMODE_NONINTERLACED | ||
242 | }, | ||
243 | { | ||
244 | .refresh = 60, | ||
245 | .xres = 1280, | ||
246 | .yres = 720, | ||
247 | .pixclock = 13426, | ||
248 | .left_margin = 192, | ||
249 | .right_margin = 64, | ||
250 | .upper_margin = 22, | ||
251 | .lower_margin = 1, | ||
252 | .hsync_len = 136, | ||
253 | .vsync_len = 3, | ||
254 | .sync = FB_SYNC_COMP_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
255 | .vmode = FB_VMODE_NONINTERLACED | ||
256 | }, | ||
257 | { | ||
258 | .refresh = 60, | ||
259 | .xres = 1280, | ||
260 | .yres = 1024, | ||
261 | .pixclock = 9375, | ||
262 | .left_margin = 38, | ||
263 | .right_margin = 128, | ||
264 | .upper_margin = 2, | ||
265 | .lower_margin = 7, | ||
266 | .hsync_len = 216, | ||
267 | .vsync_len = 37, | ||
268 | .sync = FB_SYNC_COMP_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
269 | .vmode = FB_VMODE_NONINTERLACED | ||
270 | }, | ||
271 | { | ||
272 | .refresh = 70, | ||
273 | .xres = 1280, | ||
274 | .yres = 1024, | ||
275 | .pixclock = 9380, | ||
276 | .left_margin = 6, | ||
277 | .right_margin = 6, | ||
278 | .upper_margin = 4, | ||
279 | .lower_margin = 4, | ||
280 | .hsync_len = 60, | ||
281 | .vsync_len = 94, | ||
282 | .sync = FB_SYNC_COMP_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
283 | .vmode = FB_VMODE_NONINTERLACED | ||
284 | }, | ||
285 | { | ||
286 | .refresh = 75, | ||
287 | .xres = 1280, | ||
288 | .yres = 1024, | ||
289 | .pixclock = 9380, | ||
290 | .left_margin = 6, | ||
291 | .right_margin = 6, | ||
292 | .upper_margin = 4, | ||
293 | .lower_margin = 4, | ||
294 | .hsync_len = 60, | ||
295 | .vsync_len = 15, | ||
296 | .sync = FB_SYNC_COMP_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
297 | .vmode = FB_VMODE_NONINTERLACED | ||
298 | }, | ||
299 | { | ||
300 | .refresh = 60, | ||
301 | .xres = 1920, | ||
302 | .yres = 1080, | ||
303 | .pixclock = 5787, | ||
304 | .left_margin = 328, | ||
305 | .right_margin = 120, | ||
306 | .upper_margin = 34, | ||
307 | .lower_margin = 1, | ||
308 | .hsync_len = 208, | ||
309 | .vsync_len = 3, | ||
310 | .sync = FB_SYNC_COMP_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, | ||
311 | .vmode = FB_VMODE_NONINTERLACED | ||
312 | }, | ||
313 | }; | ||
314 | |||
315 | static char *fb_mode; | ||
316 | static unsigned long default_bpp = 32; | ||
317 | static enum fsl_diu_monitor_port monitor_port; | ||
318 | static char *monitor_string; | ||
319 | |||
320 | #if defined(CONFIG_NOT_COHERENT_CACHE) | ||
321 | static u8 *coherence_data; | ||
322 | static size_t coherence_data_size; | ||
323 | static unsigned int d_cache_line_size; | ||
324 | #endif | ||
325 | |||
326 | static DEFINE_SPINLOCK(diu_lock); | ||
327 | |||
328 | enum mfb_index { | ||
329 | PLANE0 = 0, /* Plane 0, only one AOI that fills the screen */ | ||
330 | PLANE1_AOI0, /* Plane 1, first AOI */ | ||
331 | PLANE1_AOI1, /* Plane 1, second AOI */ | ||
332 | PLANE2_AOI0, /* Plane 2, first AOI */ | ||
333 | PLANE2_AOI1, /* Plane 2, second AOI */ | ||
334 | }; | ||
335 | |||
336 | struct mfb_info { | ||
337 | enum mfb_index index; | ||
338 | char *id; | ||
339 | int registered; | ||
340 | unsigned long pseudo_palette[16]; | ||
341 | struct diu_ad *ad; | ||
342 | unsigned char g_alpha; | ||
343 | unsigned int count; | ||
344 | int x_aoi_d; /* aoi display x offset to physical screen */ | ||
345 | int y_aoi_d; /* aoi display y offset to physical screen */ | ||
346 | struct fsl_diu_data *parent; | ||
347 | }; | ||
348 | |||
349 | /** | ||
350 | * struct fsl_diu_data - per-DIU data structure | ||
351 | * @dma_addr: DMA address of this structure | ||
352 | * @fsl_diu_info: fb_info objects, one per AOI | ||
353 | * @dev_attr: sysfs structure | ||
354 | * @irq: IRQ | ||
355 | * @monitor_port: the monitor port this DIU is connected to | ||
356 | * @diu_reg: pointer to the DIU hardware registers | ||
357 | * @reg_lock: spinlock for register access | ||
358 | * @dummy_aoi: video buffer for the 4x4 32-bit dummy AOI | ||
359 | * dummy_ad: DIU Area Descriptor for the dummy AOI | ||
360 | * @ad[]: Area Descriptors for each real AOI | ||
361 | * @gamma: gamma color table | ||
362 | * @cursor: hardware cursor data | ||
363 | * | ||
364 | * This data structure must be allocated with 32-byte alignment, so that the | ||
365 | * internal fields can be aligned properly. | ||
366 | */ | ||
367 | struct fsl_diu_data { | ||
368 | dma_addr_t dma_addr; | ||
369 | struct fb_info fsl_diu_info[NUM_AOIS]; | ||
370 | struct mfb_info mfb[NUM_AOIS]; | ||
371 | struct device_attribute dev_attr; | ||
372 | unsigned int irq; | ||
373 | enum fsl_diu_monitor_port monitor_port; | ||
374 | struct diu __iomem *diu_reg; | ||
375 | spinlock_t reg_lock; | ||
376 | u8 dummy_aoi[4 * 4 * 4]; | ||
377 | struct diu_ad dummy_ad __aligned(8); | ||
378 | struct diu_ad ad[NUM_AOIS] __aligned(8); | ||
379 | u8 gamma[256 * 3] __aligned(32); | ||
380 | /* It's easier to parse the cursor data as little-endian */ | ||
381 | __le16 cursor[MAX_CURS * MAX_CURS] __aligned(32); | ||
382 | /* Blank cursor data -- used to hide the cursor */ | ||
383 | __le16 blank_cursor[MAX_CURS * MAX_CURS] __aligned(32); | ||
384 | uint8_t edid_data[EDID_LENGTH]; | ||
385 | bool has_edid; | ||
386 | } __aligned(32); | ||
387 | |||
388 | /* Determine the DMA address of a member of the fsl_diu_data structure */ | ||
389 | #define DMA_ADDR(p, f) ((p)->dma_addr + offsetof(struct fsl_diu_data, f)) | ||
390 | |||
391 | static struct mfb_info mfb_template[] = { | ||
392 | { | ||
393 | .index = PLANE0, | ||
394 | .id = "Panel0", | ||
395 | .registered = 0, | ||
396 | .count = 0, | ||
397 | .x_aoi_d = 0, | ||
398 | .y_aoi_d = 0, | ||
399 | }, | ||
400 | { | ||
401 | .index = PLANE1_AOI0, | ||
402 | .id = "Panel1 AOI0", | ||
403 | .registered = 0, | ||
404 | .g_alpha = 0xff, | ||
405 | .count = 0, | ||
406 | .x_aoi_d = 0, | ||
407 | .y_aoi_d = 0, | ||
408 | }, | ||
409 | { | ||
410 | .index = PLANE1_AOI1, | ||
411 | .id = "Panel1 AOI1", | ||
412 | .registered = 0, | ||
413 | .g_alpha = 0xff, | ||
414 | .count = 0, | ||
415 | .x_aoi_d = 0, | ||
416 | .y_aoi_d = 480, | ||
417 | }, | ||
418 | { | ||
419 | .index = PLANE2_AOI0, | ||
420 | .id = "Panel2 AOI0", | ||
421 | .registered = 0, | ||
422 | .g_alpha = 0xff, | ||
423 | .count = 0, | ||
424 | .x_aoi_d = 640, | ||
425 | .y_aoi_d = 0, | ||
426 | }, | ||
427 | { | ||
428 | .index = PLANE2_AOI1, | ||
429 | .id = "Panel2 AOI1", | ||
430 | .registered = 0, | ||
431 | .g_alpha = 0xff, | ||
432 | .count = 0, | ||
433 | .x_aoi_d = 640, | ||
434 | .y_aoi_d = 480, | ||
435 | }, | ||
436 | }; | ||
437 | |||
438 | #ifdef DEBUG | ||
439 | static void __attribute__ ((unused)) fsl_diu_dump(struct diu __iomem *hw) | ||
440 | { | ||
441 | mb(); | ||
442 | pr_debug("DIU: desc=%08x,%08x,%08x, gamma=%08x pallete=%08x " | ||
443 | "cursor=%08x curs_pos=%08x diu_mode=%08x bgnd=%08x " | ||
444 | "disp_size=%08x hsyn_para=%08x vsyn_para=%08x syn_pol=%08x " | ||
445 | "thresholds=%08x int_mask=%08x plut=%08x\n", | ||
446 | hw->desc[0], hw->desc[1], hw->desc[2], hw->gamma, | ||
447 | hw->pallete, hw->cursor, hw->curs_pos, hw->diu_mode, | ||
448 | hw->bgnd, hw->disp_size, hw->hsyn_para, hw->vsyn_para, | ||
449 | hw->syn_pol, hw->thresholds, hw->int_mask, hw->plut); | ||
450 | rmb(); | ||
451 | } | ||
452 | #endif | ||
453 | |||
454 | /** | ||
455 | * fsl_diu_name_to_port - convert a port name to a monitor port enum | ||
456 | * | ||
457 | * Takes the name of a monitor port ("dvi", "lvds", or "dlvds") and returns | ||
458 | * the enum fsl_diu_monitor_port that corresponds to that string. | ||
459 | * | ||
460 | * For compatibility with older versions, a number ("0", "1", or "2") is also | ||
461 | * supported. | ||
462 | * | ||
463 | * If the string is unknown, DVI is assumed. | ||
464 | * | ||
465 | * If the particular port is not supported by the platform, another port | ||
466 | * (platform-specific) is chosen instead. | ||
467 | */ | ||
468 | static enum fsl_diu_monitor_port fsl_diu_name_to_port(const char *s) | ||
469 | { | ||
470 | enum fsl_diu_monitor_port port = FSL_DIU_PORT_DVI; | ||
471 | unsigned long val; | ||
472 | |||
473 | if (s) { | ||
474 | if (!kstrtoul(s, 10, &val) && (val <= 2)) | ||
475 | port = (enum fsl_diu_monitor_port) val; | ||
476 | else if (strncmp(s, "lvds", 4) == 0) | ||
477 | port = FSL_DIU_PORT_LVDS; | ||
478 | else if (strncmp(s, "dlvds", 5) == 0) | ||
479 | port = FSL_DIU_PORT_DLVDS; | ||
480 | } | ||
481 | |||
482 | return diu_ops.valid_monitor_port(port); | ||
483 | } | ||
484 | |||
485 | /* | ||
486 | * Workaround for failed writing desc register of planes. | ||
487 | * Needed with MPC5121 DIU rev 2.0 silicon. | ||
488 | */ | ||
489 | void wr_reg_wa(u32 *reg, u32 val) | ||
490 | { | ||
491 | do { | ||
492 | out_be32(reg, val); | ||
493 | } while (in_be32(reg) != val); | ||
494 | } | ||
495 | |||
496 | static void fsl_diu_enable_panel(struct fb_info *info) | ||
497 | { | ||
498 | struct mfb_info *pmfbi, *cmfbi, *mfbi = info->par; | ||
499 | struct diu_ad *ad = mfbi->ad; | ||
500 | struct fsl_diu_data *data = mfbi->parent; | ||
501 | struct diu __iomem *hw = data->diu_reg; | ||
502 | |||
503 | switch (mfbi->index) { | ||
504 | case PLANE0: | ||
505 | wr_reg_wa(&hw->desc[0], ad->paddr); | ||
506 | break; | ||
507 | case PLANE1_AOI0: | ||
508 | cmfbi = &data->mfb[2]; | ||
509 | if (hw->desc[1] != ad->paddr) { /* AOI0 closed */ | ||
510 | if (cmfbi->count > 0) /* AOI1 open */ | ||
511 | ad->next_ad = | ||
512 | cpu_to_le32(cmfbi->ad->paddr); | ||
513 | else | ||
514 | ad->next_ad = 0; | ||
515 | wr_reg_wa(&hw->desc[1], ad->paddr); | ||
516 | } | ||
517 | break; | ||
518 | case PLANE2_AOI0: | ||
519 | cmfbi = &data->mfb[4]; | ||
520 | if (hw->desc[2] != ad->paddr) { /* AOI0 closed */ | ||
521 | if (cmfbi->count > 0) /* AOI1 open */ | ||
522 | ad->next_ad = | ||
523 | cpu_to_le32(cmfbi->ad->paddr); | ||
524 | else | ||
525 | ad->next_ad = 0; | ||
526 | wr_reg_wa(&hw->desc[2], ad->paddr); | ||
527 | } | ||
528 | break; | ||
529 | case PLANE1_AOI1: | ||
530 | pmfbi = &data->mfb[1]; | ||
531 | ad->next_ad = 0; | ||
532 | if (hw->desc[1] == data->dummy_ad.paddr) | ||
533 | wr_reg_wa(&hw->desc[1], ad->paddr); | ||
534 | else /* AOI0 open */ | ||
535 | pmfbi->ad->next_ad = cpu_to_le32(ad->paddr); | ||
536 | break; | ||
537 | case PLANE2_AOI1: | ||
538 | pmfbi = &data->mfb[3]; | ||
539 | ad->next_ad = 0; | ||
540 | if (hw->desc[2] == data->dummy_ad.paddr) | ||
541 | wr_reg_wa(&hw->desc[2], ad->paddr); | ||
542 | else /* AOI0 was open */ | ||
543 | pmfbi->ad->next_ad = cpu_to_le32(ad->paddr); | ||
544 | break; | ||
545 | } | ||
546 | } | ||
547 | |||
548 | static void fsl_diu_disable_panel(struct fb_info *info) | ||
549 | { | ||
550 | struct mfb_info *pmfbi, *cmfbi, *mfbi = info->par; | ||
551 | struct diu_ad *ad = mfbi->ad; | ||
552 | struct fsl_diu_data *data = mfbi->parent; | ||
553 | struct diu __iomem *hw = data->diu_reg; | ||
554 | |||
555 | switch (mfbi->index) { | ||
556 | case PLANE0: | ||
557 | wr_reg_wa(&hw->desc[0], 0); | ||
558 | break; | ||
559 | case PLANE1_AOI0: | ||
560 | cmfbi = &data->mfb[2]; | ||
561 | if (cmfbi->count > 0) /* AOI1 is open */ | ||
562 | wr_reg_wa(&hw->desc[1], cmfbi->ad->paddr); | ||
563 | /* move AOI1 to the first */ | ||
564 | else /* AOI1 was closed */ | ||
565 | wr_reg_wa(&hw->desc[1], data->dummy_ad.paddr); | ||
566 | /* close AOI 0 */ | ||
567 | break; | ||
568 | case PLANE2_AOI0: | ||
569 | cmfbi = &data->mfb[4]; | ||
570 | if (cmfbi->count > 0) /* AOI1 is open */ | ||
571 | wr_reg_wa(&hw->desc[2], cmfbi->ad->paddr); | ||
572 | /* move AOI1 to the first */ | ||
573 | else /* AOI1 was closed */ | ||
574 | wr_reg_wa(&hw->desc[2], data->dummy_ad.paddr); | ||
575 | /* close AOI 0 */ | ||
576 | break; | ||
577 | case PLANE1_AOI1: | ||
578 | pmfbi = &data->mfb[1]; | ||
579 | if (hw->desc[1] != ad->paddr) { | ||
580 | /* AOI1 is not the first in the chain */ | ||
581 | if (pmfbi->count > 0) | ||
582 | /* AOI0 is open, must be the first */ | ||
583 | pmfbi->ad->next_ad = 0; | ||
584 | } else /* AOI1 is the first in the chain */ | ||
585 | wr_reg_wa(&hw->desc[1], data->dummy_ad.paddr); | ||
586 | /* close AOI 1 */ | ||
587 | break; | ||
588 | case PLANE2_AOI1: | ||
589 | pmfbi = &data->mfb[3]; | ||
590 | if (hw->desc[2] != ad->paddr) { | ||
591 | /* AOI1 is not the first in the chain */ | ||
592 | if (pmfbi->count > 0) | ||
593 | /* AOI0 is open, must be the first */ | ||
594 | pmfbi->ad->next_ad = 0; | ||
595 | } else /* AOI1 is the first in the chain */ | ||
596 | wr_reg_wa(&hw->desc[2], data->dummy_ad.paddr); | ||
597 | /* close AOI 1 */ | ||
598 | break; | ||
599 | } | ||
600 | } | ||
601 | |||
602 | static void enable_lcdc(struct fb_info *info) | ||
603 | { | ||
604 | struct mfb_info *mfbi = info->par; | ||
605 | struct fsl_diu_data *data = mfbi->parent; | ||
606 | struct diu __iomem *hw = data->diu_reg; | ||
607 | |||
608 | out_be32(&hw->diu_mode, MFB_MODE1); | ||
609 | } | ||
610 | |||
611 | static void disable_lcdc(struct fb_info *info) | ||
612 | { | ||
613 | struct mfb_info *mfbi = info->par; | ||
614 | struct fsl_diu_data *data = mfbi->parent; | ||
615 | struct diu __iomem *hw = data->diu_reg; | ||
616 | |||
617 | out_be32(&hw->diu_mode, 0); | ||
618 | } | ||
619 | |||
620 | static void adjust_aoi_size_position(struct fb_var_screeninfo *var, | ||
621 | struct fb_info *info) | ||
622 | { | ||
623 | struct mfb_info *lower_aoi_mfbi, *upper_aoi_mfbi, *mfbi = info->par; | ||
624 | struct fsl_diu_data *data = mfbi->parent; | ||
625 | int available_height, upper_aoi_bottom; | ||
626 | enum mfb_index index = mfbi->index; | ||
627 | int lower_aoi_is_open, upper_aoi_is_open; | ||
628 | __u32 base_plane_width, base_plane_height, upper_aoi_height; | ||
629 | |||
630 | base_plane_width = data->fsl_diu_info[0].var.xres; | ||
631 | base_plane_height = data->fsl_diu_info[0].var.yres; | ||
632 | |||
633 | if (mfbi->x_aoi_d < 0) | ||
634 | mfbi->x_aoi_d = 0; | ||
635 | if (mfbi->y_aoi_d < 0) | ||
636 | mfbi->y_aoi_d = 0; | ||
637 | switch (index) { | ||
638 | case PLANE0: | ||
639 | if (mfbi->x_aoi_d != 0) | ||
640 | mfbi->x_aoi_d = 0; | ||
641 | if (mfbi->y_aoi_d != 0) | ||
642 | mfbi->y_aoi_d = 0; | ||
643 | break; | ||
644 | case PLANE1_AOI0: | ||
645 | case PLANE2_AOI0: | ||
646 | lower_aoi_mfbi = data->fsl_diu_info[index+1].par; | ||
647 | lower_aoi_is_open = lower_aoi_mfbi->count > 0 ? 1 : 0; | ||
648 | if (var->xres > base_plane_width) | ||
649 | var->xres = base_plane_width; | ||
650 | if ((mfbi->x_aoi_d + var->xres) > base_plane_width) | ||
651 | mfbi->x_aoi_d = base_plane_width - var->xres; | ||
652 | |||
653 | if (lower_aoi_is_open) | ||
654 | available_height = lower_aoi_mfbi->y_aoi_d; | ||
655 | else | ||
656 | available_height = base_plane_height; | ||
657 | if (var->yres > available_height) | ||
658 | var->yres = available_height; | ||
659 | if ((mfbi->y_aoi_d + var->yres) > available_height) | ||
660 | mfbi->y_aoi_d = available_height - var->yres; | ||
661 | break; | ||
662 | case PLANE1_AOI1: | ||
663 | case PLANE2_AOI1: | ||
664 | upper_aoi_mfbi = data->fsl_diu_info[index-1].par; | ||
665 | upper_aoi_height = data->fsl_diu_info[index-1].var.yres; | ||
666 | upper_aoi_bottom = upper_aoi_mfbi->y_aoi_d + upper_aoi_height; | ||
667 | upper_aoi_is_open = upper_aoi_mfbi->count > 0 ? 1 : 0; | ||
668 | if (var->xres > base_plane_width) | ||
669 | var->xres = base_plane_width; | ||
670 | if ((mfbi->x_aoi_d + var->xres) > base_plane_width) | ||
671 | mfbi->x_aoi_d = base_plane_width - var->xres; | ||
672 | if (mfbi->y_aoi_d < 0) | ||
673 | mfbi->y_aoi_d = 0; | ||
674 | if (upper_aoi_is_open) { | ||
675 | if (mfbi->y_aoi_d < upper_aoi_bottom) | ||
676 | mfbi->y_aoi_d = upper_aoi_bottom; | ||
677 | available_height = base_plane_height | ||
678 | - upper_aoi_bottom; | ||
679 | } else | ||
680 | available_height = base_plane_height; | ||
681 | if (var->yres > available_height) | ||
682 | var->yres = available_height; | ||
683 | if ((mfbi->y_aoi_d + var->yres) > base_plane_height) | ||
684 | mfbi->y_aoi_d = base_plane_height - var->yres; | ||
685 | break; | ||
686 | } | ||
687 | } | ||
688 | /* | ||
689 | * Checks to see if the hardware supports the state requested by var passed | ||
690 | * in. This function does not alter the hardware state! If the var passed in | ||
691 | * is slightly off by what the hardware can support then we alter the var | ||
692 | * PASSED in to what we can do. If the hardware doesn't support mode change | ||
693 | * a -EINVAL will be returned by the upper layers. | ||
694 | */ | ||
695 | static int fsl_diu_check_var(struct fb_var_screeninfo *var, | ||
696 | struct fb_info *info) | ||
697 | { | ||
698 | if (var->xres_virtual < var->xres) | ||
699 | var->xres_virtual = var->xres; | ||
700 | if (var->yres_virtual < var->yres) | ||
701 | var->yres_virtual = var->yres; | ||
702 | |||
703 | if (var->xoffset < 0) | ||
704 | var->xoffset = 0; | ||
705 | |||
706 | if (var->yoffset < 0) | ||
707 | var->yoffset = 0; | ||
708 | |||
709 | if (var->xoffset + info->var.xres > info->var.xres_virtual) | ||
710 | var->xoffset = info->var.xres_virtual - info->var.xres; | ||
711 | |||
712 | if (var->yoffset + info->var.yres > info->var.yres_virtual) | ||
713 | var->yoffset = info->var.yres_virtual - info->var.yres; | ||
714 | |||
715 | if ((var->bits_per_pixel != 32) && (var->bits_per_pixel != 24) && | ||
716 | (var->bits_per_pixel != 16)) | ||
717 | var->bits_per_pixel = default_bpp; | ||
718 | |||
719 | switch (var->bits_per_pixel) { | ||
720 | case 16: | ||
721 | var->red.length = 5; | ||
722 | var->red.offset = 11; | ||
723 | var->red.msb_right = 0; | ||
724 | |||
725 | var->green.length = 6; | ||
726 | var->green.offset = 5; | ||
727 | var->green.msb_right = 0; | ||
728 | |||
729 | var->blue.length = 5; | ||
730 | var->blue.offset = 0; | ||
731 | var->blue.msb_right = 0; | ||
732 | |||
733 | var->transp.length = 0; | ||
734 | var->transp.offset = 0; | ||
735 | var->transp.msb_right = 0; | ||
736 | break; | ||
737 | case 24: | ||
738 | var->red.length = 8; | ||
739 | var->red.offset = 0; | ||
740 | var->red.msb_right = 0; | ||
741 | |||
742 | var->green.length = 8; | ||
743 | var->green.offset = 8; | ||
744 | var->green.msb_right = 0; | ||
745 | |||
746 | var->blue.length = 8; | ||
747 | var->blue.offset = 16; | ||
748 | var->blue.msb_right = 0; | ||
749 | |||
750 | var->transp.length = 0; | ||
751 | var->transp.offset = 0; | ||
752 | var->transp.msb_right = 0; | ||
753 | break; | ||
754 | case 32: | ||
755 | var->red.length = 8; | ||
756 | var->red.offset = 16; | ||
757 | var->red.msb_right = 0; | ||
758 | |||
759 | var->green.length = 8; | ||
760 | var->green.offset = 8; | ||
761 | var->green.msb_right = 0; | ||
762 | |||
763 | var->blue.length = 8; | ||
764 | var->blue.offset = 0; | ||
765 | var->blue.msb_right = 0; | ||
766 | |||
767 | var->transp.length = 8; | ||
768 | var->transp.offset = 24; | ||
769 | var->transp.msb_right = 0; | ||
770 | |||
771 | break; | ||
772 | } | ||
773 | |||
774 | var->height = -1; | ||
775 | var->width = -1; | ||
776 | var->grayscale = 0; | ||
777 | |||
778 | /* Copy nonstd field to/from sync for fbset usage */ | ||
779 | var->sync |= var->nonstd; | ||
780 | var->nonstd |= var->sync; | ||
781 | |||
782 | adjust_aoi_size_position(var, info); | ||
783 | return 0; | ||
784 | } | ||
785 | |||
786 | static void set_fix(struct fb_info *info) | ||
787 | { | ||
788 | struct fb_fix_screeninfo *fix = &info->fix; | ||
789 | struct fb_var_screeninfo *var = &info->var; | ||
790 | struct mfb_info *mfbi = info->par; | ||
791 | |||
792 | strncpy(fix->id, mfbi->id, sizeof(fix->id)); | ||
793 | fix->line_length = var->xres_virtual * var->bits_per_pixel / 8; | ||
794 | fix->type = FB_TYPE_PACKED_PIXELS; | ||
795 | fix->accel = FB_ACCEL_NONE; | ||
796 | fix->visual = FB_VISUAL_TRUECOLOR; | ||
797 | fix->xpanstep = 1; | ||
798 | fix->ypanstep = 1; | ||
799 | } | ||
800 | |||
801 | static void update_lcdc(struct fb_info *info) | ||
802 | { | ||
803 | struct fb_var_screeninfo *var = &info->var; | ||
804 | struct mfb_info *mfbi = info->par; | ||
805 | struct fsl_diu_data *data = mfbi->parent; | ||
806 | struct diu __iomem *hw; | ||
807 | int i, j; | ||
808 | u8 *gamma_table_base; | ||
809 | |||
810 | u32 temp; | ||
811 | |||
812 | hw = data->diu_reg; | ||
813 | |||
814 | if (diu_ops.set_monitor_port) | ||
815 | diu_ops.set_monitor_port(data->monitor_port); | ||
816 | gamma_table_base = data->gamma; | ||
817 | |||
818 | /* Prep for DIU init - gamma table, cursor table */ | ||
819 | |||
820 | for (i = 0; i <= 2; i++) | ||
821 | for (j = 0; j <= 255; j++) | ||
822 | *gamma_table_base++ = j; | ||
823 | |||
824 | if (diu_ops.set_gamma_table) | ||
825 | diu_ops.set_gamma_table(data->monitor_port, data->gamma); | ||
826 | |||
827 | disable_lcdc(info); | ||
828 | |||
829 | /* Program DIU registers */ | ||
830 | |||
831 | out_be32(&hw->gamma, DMA_ADDR(data, gamma)); | ||
832 | |||
833 | out_be32(&hw->bgnd, 0x007F7F7F); /* Set background to grey */ | ||
834 | out_be32(&hw->disp_size, (var->yres << 16) | var->xres); | ||
835 | |||
836 | /* Horizontal and vertical configuration register */ | ||
837 | temp = var->left_margin << 22 | /* BP_H */ | ||
838 | var->hsync_len << 11 | /* PW_H */ | ||
839 | var->right_margin; /* FP_H */ | ||
840 | |||
841 | out_be32(&hw->hsyn_para, temp); | ||
842 | |||
843 | temp = var->upper_margin << 22 | /* BP_V */ | ||
844 | var->vsync_len << 11 | /* PW_V */ | ||
845 | var->lower_margin; /* FP_V */ | ||
846 | |||
847 | out_be32(&hw->vsyn_para, temp); | ||
848 | |||
849 | diu_ops.set_pixel_clock(var->pixclock); | ||
850 | |||
851 | #ifndef CONFIG_PPC_MPC512x | ||
852 | /* | ||
853 | * The PLUT register is defined differently on the MPC5121 than it | ||
854 | * is on other SOCs. Unfortunately, there's no documentation that | ||
855 | * explains how it's supposed to be programmed, so for now, we leave | ||
856 | * it at the default value on the MPC5121. | ||
857 | * | ||
858 | * For other SOCs, program it for the highest priority, which will | ||
859 | * reduce the chance of underrun. Technically, we should scale the | ||
860 | * priority to match the screen resolution, but doing that properly | ||
861 | * requires delicate fine-tuning for each use-case. | ||
862 | */ | ||
863 | out_be32(&hw->plut, 0x01F5F666); | ||
864 | #endif | ||
865 | |||
866 | /* Enable the DIU */ | ||
867 | enable_lcdc(info); | ||
868 | } | ||
869 | |||
870 | static int map_video_memory(struct fb_info *info) | ||
871 | { | ||
872 | u32 smem_len = info->fix.line_length * info->var.yres_virtual; | ||
873 | void *p; | ||
874 | |||
875 | p = alloc_pages_exact(smem_len, GFP_DMA | __GFP_ZERO); | ||
876 | if (!p) { | ||
877 | dev_err(info->dev, "unable to allocate fb memory\n"); | ||
878 | return -ENOMEM; | ||
879 | } | ||
880 | mutex_lock(&info->mm_lock); | ||
881 | info->screen_base = p; | ||
882 | info->fix.smem_start = virt_to_phys(info->screen_base); | ||
883 | info->fix.smem_len = smem_len; | ||
884 | mutex_unlock(&info->mm_lock); | ||
885 | info->screen_size = info->fix.smem_len; | ||
886 | |||
887 | return 0; | ||
888 | } | ||
889 | |||
890 | static void unmap_video_memory(struct fb_info *info) | ||
891 | { | ||
892 | void *p = info->screen_base; | ||
893 | size_t l = info->fix.smem_len; | ||
894 | |||
895 | mutex_lock(&info->mm_lock); | ||
896 | info->screen_base = NULL; | ||
897 | info->fix.smem_start = 0; | ||
898 | info->fix.smem_len = 0; | ||
899 | mutex_unlock(&info->mm_lock); | ||
900 | |||
901 | if (p) | ||
902 | free_pages_exact(p, l); | ||
903 | } | ||
904 | |||
905 | /* | ||
906 | * Using the fb_var_screeninfo in fb_info we set the aoi of this | ||
907 | * particular framebuffer. It is a light version of fsl_diu_set_par. | ||
908 | */ | ||
909 | static int fsl_diu_set_aoi(struct fb_info *info) | ||
910 | { | ||
911 | struct fb_var_screeninfo *var = &info->var; | ||
912 | struct mfb_info *mfbi = info->par; | ||
913 | struct diu_ad *ad = mfbi->ad; | ||
914 | |||
915 | /* AOI should not be greater than display size */ | ||
916 | ad->offset_xyi = cpu_to_le32((var->yoffset << 16) | var->xoffset); | ||
917 | ad->offset_xyd = cpu_to_le32((mfbi->y_aoi_d << 16) | mfbi->x_aoi_d); | ||
918 | return 0; | ||
919 | } | ||
920 | |||
921 | /** | ||
922 | * fsl_diu_get_pixel_format: return the pixel format for a given color depth | ||
923 | * | ||
924 | * The pixel format is a 32-bit value that determine which bits in each | ||
925 | * pixel are to be used for each color. This is the default function used | ||
926 | * if the platform does not define its own version. | ||
927 | */ | ||
928 | static u32 fsl_diu_get_pixel_format(unsigned int bits_per_pixel) | ||
929 | { | ||
930 | #define PF_BYTE_F 0x10000000 | ||
931 | #define PF_ALPHA_C_MASK 0x0E000000 | ||
932 | #define PF_ALPHA_C_SHIFT 25 | ||
933 | #define PF_BLUE_C_MASK 0x01800000 | ||
934 | #define PF_BLUE_C_SHIFT 23 | ||
935 | #define PF_GREEN_C_MASK 0x00600000 | ||
936 | #define PF_GREEN_C_SHIFT 21 | ||
937 | #define PF_RED_C_MASK 0x00180000 | ||
938 | #define PF_RED_C_SHIFT 19 | ||
939 | #define PF_PALETTE 0x00040000 | ||
940 | #define PF_PIXEL_S_MASK 0x00030000 | ||
941 | #define PF_PIXEL_S_SHIFT 16 | ||
942 | #define PF_COMP_3_MASK 0x0000F000 | ||
943 | #define PF_COMP_3_SHIFT 12 | ||
944 | #define PF_COMP_2_MASK 0x00000F00 | ||
945 | #define PF_COMP_2_SHIFT 8 | ||
946 | #define PF_COMP_1_MASK 0x000000F0 | ||
947 | #define PF_COMP_1_SHIFT 4 | ||
948 | #define PF_COMP_0_MASK 0x0000000F | ||
949 | #define PF_COMP_0_SHIFT 0 | ||
950 | |||
951 | #define MAKE_PF(alpha, red, green, blue, size, c0, c1, c2, c3) \ | ||
952 | cpu_to_le32(PF_BYTE_F | (alpha << PF_ALPHA_C_SHIFT) | \ | ||
953 | (blue << PF_BLUE_C_SHIFT) | (green << PF_GREEN_C_SHIFT) | \ | ||
954 | (red << PF_RED_C_SHIFT) | (c3 << PF_COMP_3_SHIFT) | \ | ||
955 | (c2 << PF_COMP_2_SHIFT) | (c1 << PF_COMP_1_SHIFT) | \ | ||
956 | (c0 << PF_COMP_0_SHIFT) | (size << PF_PIXEL_S_SHIFT)) | ||
957 | |||
958 | switch (bits_per_pixel) { | ||
959 | case 32: | ||
960 | /* 0x88883316 */ | ||
961 | return MAKE_PF(3, 2, 1, 0, 3, 8, 8, 8, 8); | ||
962 | case 24: | ||
963 | /* 0x88082219 */ | ||
964 | return MAKE_PF(4, 0, 1, 2, 2, 8, 8, 8, 0); | ||
965 | case 16: | ||
966 | /* 0x65053118 */ | ||
967 | return MAKE_PF(4, 2, 1, 0, 1, 5, 6, 5, 0); | ||
968 | default: | ||
969 | pr_err("fsl-diu: unsupported color depth %u\n", bits_per_pixel); | ||
970 | return 0; | ||
971 | } | ||
972 | } | ||
973 | |||
974 | /* | ||
975 | * Copies a cursor image from user space to the proper place in driver | ||
976 | * memory so that the hardware can display the cursor image. | ||
977 | * | ||
978 | * Cursor data is represented as a sequence of 'width' bits packed into bytes. | ||
979 | * That is, the first 8 bits are in the first byte, the second 8 bits in the | ||
980 | * second byte, and so on. Therefore, the each row of the cursor is (width + | ||
981 | * 7) / 8 bytes of 'data' | ||
982 | * | ||
983 | * The DIU only supports cursors up to 32x32 (MAX_CURS). We reject cursors | ||
984 | * larger than this, so we already know that 'width' <= 32. Therefore, we can | ||
985 | * simplify our code by using a 32-bit big-endian integer ("line") to read in | ||
986 | * a single line of pixels, and only look at the top 'width' bits of that | ||
987 | * integer. | ||
988 | * | ||
989 | * This could result in an unaligned 32-bit read. For example, if the cursor | ||
990 | * is 24x24, then the first three bytes of 'image' contain the pixel data for | ||
991 | * the top line of the cursor. We do a 32-bit read of 'image', but we look | ||
992 | * only at the top 24 bits. Then we increment 'image' by 3 bytes. The next | ||
993 | * read is unaligned. The only problem is that we might read past the end of | ||
994 | * 'image' by 1-3 bytes, but that should not cause any problems. | ||
995 | */ | ||
996 | static void fsl_diu_load_cursor_image(struct fb_info *info, | ||
997 | const void *image, uint16_t bg, uint16_t fg, | ||
998 | unsigned int width, unsigned int height) | ||
999 | { | ||
1000 | struct mfb_info *mfbi = info->par; | ||
1001 | struct fsl_diu_data *data = mfbi->parent; | ||
1002 | __le16 *cursor = data->cursor; | ||
1003 | __le16 _fg = cpu_to_le16(fg); | ||
1004 | __le16 _bg = cpu_to_le16(bg); | ||
1005 | unsigned int h, w; | ||
1006 | |||
1007 | for (h = 0; h < height; h++) { | ||
1008 | uint32_t mask = 1 << 31; | ||
1009 | uint32_t line = be32_to_cpup(image); | ||
1010 | |||
1011 | for (w = 0; w < width; w++) { | ||
1012 | cursor[w] = (line & mask) ? _fg : _bg; | ||
1013 | mask >>= 1; | ||
1014 | } | ||
1015 | |||
1016 | cursor += MAX_CURS; | ||
1017 | image += DIV_ROUND_UP(width, 8); | ||
1018 | } | ||
1019 | } | ||
1020 | |||
1021 | /* | ||
1022 | * Set a hardware cursor. The image data for the cursor is passed via the | ||
1023 | * fb_cursor object. | ||
1024 | */ | ||
1025 | static int fsl_diu_cursor(struct fb_info *info, struct fb_cursor *cursor) | ||
1026 | { | ||
1027 | struct mfb_info *mfbi = info->par; | ||
1028 | struct fsl_diu_data *data = mfbi->parent; | ||
1029 | struct diu __iomem *hw = data->diu_reg; | ||
1030 | |||
1031 | if (cursor->image.width > MAX_CURS || cursor->image.height > MAX_CURS) | ||
1032 | return -EINVAL; | ||
1033 | |||
1034 | /* The cursor size has changed */ | ||
1035 | if (cursor->set & FB_CUR_SETSIZE) { | ||
1036 | /* | ||
1037 | * The DIU cursor is a fixed size, so when we get this | ||
1038 | * message, instead of resizing the cursor, we just clear | ||
1039 | * all the image data, in expectation of new data. However, | ||
1040 | * in tests this control does not appear to be normally | ||
1041 | * called. | ||
1042 | */ | ||
1043 | memset(data->cursor, 0, sizeof(data->cursor)); | ||
1044 | } | ||
1045 | |||
1046 | /* The cursor position has changed (cursor->image.dx|dy) */ | ||
1047 | if (cursor->set & FB_CUR_SETPOS) { | ||
1048 | uint32_t xx, yy; | ||
1049 | |||
1050 | yy = (cursor->image.dy - info->var.yoffset) & 0x7ff; | ||
1051 | xx = (cursor->image.dx - info->var.xoffset) & 0x7ff; | ||
1052 | |||
1053 | out_be32(&hw->curs_pos, yy << 16 | xx); | ||
1054 | } | ||
1055 | |||
1056 | /* | ||
1057 | * FB_CUR_SETIMAGE - the cursor image has changed | ||
1058 | * FB_CUR_SETCMAP - the cursor colors has changed | ||
1059 | * FB_CUR_SETSHAPE - the cursor bitmask has changed | ||
1060 | */ | ||
1061 | if (cursor->set & (FB_CUR_SETSHAPE | FB_CUR_SETCMAP | FB_CUR_SETIMAGE)) { | ||
1062 | unsigned int image_size = | ||
1063 | DIV_ROUND_UP(cursor->image.width, 8) * cursor->image.height; | ||
1064 | unsigned int image_words = | ||
1065 | DIV_ROUND_UP(image_size, sizeof(uint32_t)); | ||
1066 | unsigned int bg_idx = cursor->image.bg_color; | ||
1067 | unsigned int fg_idx = cursor->image.fg_color; | ||
1068 | uint8_t buffer[image_size]; | ||
1069 | uint32_t *image, *source, *mask; | ||
1070 | uint16_t fg, bg; | ||
1071 | unsigned int i; | ||
1072 | |||
1073 | if (info->state != FBINFO_STATE_RUNNING) | ||
1074 | return 0; | ||
1075 | |||
1076 | /* | ||
1077 | * Determine the size of the cursor image data. Normally, | ||
1078 | * it's 8x16. | ||
1079 | */ | ||
1080 | image_size = DIV_ROUND_UP(cursor->image.width, 8) * | ||
1081 | cursor->image.height; | ||
1082 | |||
1083 | bg = ((info->cmap.red[bg_idx] & 0xf8) << 7) | | ||
1084 | ((info->cmap.green[bg_idx] & 0xf8) << 2) | | ||
1085 | ((info->cmap.blue[bg_idx] & 0xf8) >> 3) | | ||
1086 | 1 << 15; | ||
1087 | |||
1088 | fg = ((info->cmap.red[fg_idx] & 0xf8) << 7) | | ||
1089 | ((info->cmap.green[fg_idx] & 0xf8) << 2) | | ||
1090 | ((info->cmap.blue[fg_idx] & 0xf8) >> 3) | | ||
1091 | 1 << 15; | ||
1092 | |||
1093 | /* Use 32-bit operations on the data to improve performance */ | ||
1094 | image = (uint32_t *)buffer; | ||
1095 | source = (uint32_t *)cursor->image.data; | ||
1096 | mask = (uint32_t *)cursor->mask; | ||
1097 | |||
1098 | if (cursor->rop == ROP_XOR) | ||
1099 | for (i = 0; i < image_words; i++) | ||
1100 | image[i] = source[i] ^ mask[i]; | ||
1101 | else | ||
1102 | for (i = 0; i < image_words; i++) | ||
1103 | image[i] = source[i] & mask[i]; | ||
1104 | |||
1105 | fsl_diu_load_cursor_image(info, image, bg, fg, | ||
1106 | cursor->image.width, cursor->image.height); | ||
1107 | } | ||
1108 | |||
1109 | /* | ||
1110 | * Show or hide the cursor. The cursor data is always stored in the | ||
1111 | * 'cursor' memory block, and the actual cursor position is always in | ||
1112 | * the DIU's CURS_POS register. To hide the cursor, we redirect the | ||
1113 | * CURSOR register to a blank cursor. The show the cursor, we | ||
1114 | * redirect the CURSOR register to the real cursor data. | ||
1115 | */ | ||
1116 | if (cursor->enable) | ||
1117 | out_be32(&hw->cursor, DMA_ADDR(data, cursor)); | ||
1118 | else | ||
1119 | out_be32(&hw->cursor, DMA_ADDR(data, blank_cursor)); | ||
1120 | |||
1121 | return 0; | ||
1122 | } | ||
1123 | |||
1124 | /* | ||
1125 | * Using the fb_var_screeninfo in fb_info we set the resolution of this | ||
1126 | * particular framebuffer. This function alters the fb_fix_screeninfo stored | ||
1127 | * in fb_info. It does not alter var in fb_info since we are using that | ||
1128 | * data. This means we depend on the data in var inside fb_info to be | ||
1129 | * supported by the hardware. fsl_diu_check_var is always called before | ||
1130 | * fsl_diu_set_par to ensure this. | ||
1131 | */ | ||
1132 | static int fsl_diu_set_par(struct fb_info *info) | ||
1133 | { | ||
1134 | unsigned long len; | ||
1135 | struct fb_var_screeninfo *var = &info->var; | ||
1136 | struct mfb_info *mfbi = info->par; | ||
1137 | struct fsl_diu_data *data = mfbi->parent; | ||
1138 | struct diu_ad *ad = mfbi->ad; | ||
1139 | struct diu __iomem *hw; | ||
1140 | |||
1141 | hw = data->diu_reg; | ||
1142 | |||
1143 | set_fix(info); | ||
1144 | |||
1145 | len = info->var.yres_virtual * info->fix.line_length; | ||
1146 | /* Alloc & dealloc each time resolution/bpp change */ | ||
1147 | if (len != info->fix.smem_len) { | ||
1148 | if (info->fix.smem_start) | ||
1149 | unmap_video_memory(info); | ||
1150 | |||
1151 | /* Memory allocation for framebuffer */ | ||
1152 | if (map_video_memory(info)) { | ||
1153 | dev_err(info->dev, "unable to allocate fb memory 1\n"); | ||
1154 | return -ENOMEM; | ||
1155 | } | ||
1156 | } | ||
1157 | |||
1158 | if (diu_ops.get_pixel_format) | ||
1159 | ad->pix_fmt = diu_ops.get_pixel_format(data->monitor_port, | ||
1160 | var->bits_per_pixel); | ||
1161 | else | ||
1162 | ad->pix_fmt = fsl_diu_get_pixel_format(var->bits_per_pixel); | ||
1163 | |||
1164 | ad->addr = cpu_to_le32(info->fix.smem_start); | ||
1165 | ad->src_size_g_alpha = cpu_to_le32((var->yres_virtual << 12) | | ||
1166 | var->xres_virtual) | mfbi->g_alpha; | ||
1167 | /* AOI should not be greater than display size */ | ||
1168 | ad->aoi_size = cpu_to_le32((var->yres << 16) | var->xres); | ||
1169 | ad->offset_xyi = cpu_to_le32((var->yoffset << 16) | var->xoffset); | ||
1170 | ad->offset_xyd = cpu_to_le32((mfbi->y_aoi_d << 16) | mfbi->x_aoi_d); | ||
1171 | |||
1172 | /* Disable chroma keying function */ | ||
1173 | ad->ckmax_r = 0; | ||
1174 | ad->ckmax_g = 0; | ||
1175 | ad->ckmax_b = 0; | ||
1176 | |||
1177 | ad->ckmin_r = 255; | ||
1178 | ad->ckmin_g = 255; | ||
1179 | ad->ckmin_b = 255; | ||
1180 | |||
1181 | if (mfbi->index == PLANE0) | ||
1182 | update_lcdc(info); | ||
1183 | return 0; | ||
1184 | } | ||
1185 | |||
1186 | static inline __u32 CNVT_TOHW(__u32 val, __u32 width) | ||
1187 | { | ||
1188 | return ((val << width) + 0x7FFF - val) >> 16; | ||
1189 | } | ||
1190 | |||
1191 | /* | ||
1192 | * Set a single color register. The values supplied have a 16 bit magnitude | ||
1193 | * which needs to be scaled in this function for the hardware. Things to take | ||
1194 | * into consideration are how many color registers, if any, are supported with | ||
1195 | * the current color visual. With truecolor mode no color palettes are | ||
1196 | * supported. Here a pseudo palette is created which we store the value in | ||
1197 | * pseudo_palette in struct fb_info. For pseudocolor mode we have a limited | ||
1198 | * color palette. | ||
1199 | */ | ||
1200 | static int fsl_diu_setcolreg(unsigned int regno, unsigned int red, | ||
1201 | unsigned int green, unsigned int blue, | ||
1202 | unsigned int transp, struct fb_info *info) | ||
1203 | { | ||
1204 | int ret = 1; | ||
1205 | |||
1206 | /* | ||
1207 | * If greyscale is true, then we convert the RGB value | ||
1208 | * to greyscale no matter what visual we are using. | ||
1209 | */ | ||
1210 | if (info->var.grayscale) | ||
1211 | red = green = blue = (19595 * red + 38470 * green + | ||
1212 | 7471 * blue) >> 16; | ||
1213 | switch (info->fix.visual) { | ||
1214 | case FB_VISUAL_TRUECOLOR: | ||
1215 | /* | ||
1216 | * 16-bit True Colour. We encode the RGB value | ||
1217 | * according to the RGB bitfield information. | ||
1218 | */ | ||
1219 | if (regno < 16) { | ||
1220 | u32 *pal = info->pseudo_palette; | ||
1221 | u32 v; | ||
1222 | |||
1223 | red = CNVT_TOHW(red, info->var.red.length); | ||
1224 | green = CNVT_TOHW(green, info->var.green.length); | ||
1225 | blue = CNVT_TOHW(blue, info->var.blue.length); | ||
1226 | transp = CNVT_TOHW(transp, info->var.transp.length); | ||
1227 | |||
1228 | v = (red << info->var.red.offset) | | ||
1229 | (green << info->var.green.offset) | | ||
1230 | (blue << info->var.blue.offset) | | ||
1231 | (transp << info->var.transp.offset); | ||
1232 | |||
1233 | pal[regno] = v; | ||
1234 | ret = 0; | ||
1235 | } | ||
1236 | break; | ||
1237 | } | ||
1238 | |||
1239 | return ret; | ||
1240 | } | ||
1241 | |||
1242 | /* | ||
1243 | * Pan (or wrap, depending on the `vmode' field) the display using the | ||
1244 | * 'xoffset' and 'yoffset' fields of the 'var' structure. If the values | ||
1245 | * don't fit, return -EINVAL. | ||
1246 | */ | ||
1247 | static int fsl_diu_pan_display(struct fb_var_screeninfo *var, | ||
1248 | struct fb_info *info) | ||
1249 | { | ||
1250 | if ((info->var.xoffset == var->xoffset) && | ||
1251 | (info->var.yoffset == var->yoffset)) | ||
1252 | return 0; /* No change, do nothing */ | ||
1253 | |||
1254 | if (var->xoffset < 0 || var->yoffset < 0 | ||
1255 | || var->xoffset + info->var.xres > info->var.xres_virtual | ||
1256 | || var->yoffset + info->var.yres > info->var.yres_virtual) | ||
1257 | return -EINVAL; | ||
1258 | |||
1259 | info->var.xoffset = var->xoffset; | ||
1260 | info->var.yoffset = var->yoffset; | ||
1261 | |||
1262 | if (var->vmode & FB_VMODE_YWRAP) | ||
1263 | info->var.vmode |= FB_VMODE_YWRAP; | ||
1264 | else | ||
1265 | info->var.vmode &= ~FB_VMODE_YWRAP; | ||
1266 | |||
1267 | fsl_diu_set_aoi(info); | ||
1268 | |||
1269 | return 0; | ||
1270 | } | ||
1271 | |||
1272 | static int fsl_diu_ioctl(struct fb_info *info, unsigned int cmd, | ||
1273 | unsigned long arg) | ||
1274 | { | ||
1275 | struct mfb_info *mfbi = info->par; | ||
1276 | struct diu_ad *ad = mfbi->ad; | ||
1277 | struct mfb_chroma_key ck; | ||
1278 | unsigned char global_alpha; | ||
1279 | struct aoi_display_offset aoi_d; | ||
1280 | __u32 pix_fmt; | ||
1281 | void __user *buf = (void __user *)arg; | ||
1282 | |||
1283 | if (!arg) | ||
1284 | return -EINVAL; | ||
1285 | |||
1286 | dev_dbg(info->dev, "ioctl %08x (dir=%s%s type=%u nr=%u size=%u)\n", cmd, | ||
1287 | _IOC_DIR(cmd) & _IOC_READ ? "R" : "", | ||
1288 | _IOC_DIR(cmd) & _IOC_WRITE ? "W" : "", | ||
1289 | _IOC_TYPE(cmd), _IOC_NR(cmd), _IOC_SIZE(cmd)); | ||
1290 | |||
1291 | switch (cmd) { | ||
1292 | case MFB_SET_PIXFMT_OLD: | ||
1293 | dev_warn(info->dev, | ||
1294 | "MFB_SET_PIXFMT value of 0x%08x is deprecated.\n", | ||
1295 | MFB_SET_PIXFMT_OLD); | ||
1296 | case MFB_SET_PIXFMT: | ||
1297 | if (copy_from_user(&pix_fmt, buf, sizeof(pix_fmt))) | ||
1298 | return -EFAULT; | ||
1299 | ad->pix_fmt = pix_fmt; | ||
1300 | break; | ||
1301 | case MFB_GET_PIXFMT_OLD: | ||
1302 | dev_warn(info->dev, | ||
1303 | "MFB_GET_PIXFMT value of 0x%08x is deprecated.\n", | ||
1304 | MFB_GET_PIXFMT_OLD); | ||
1305 | case MFB_GET_PIXFMT: | ||
1306 | pix_fmt = ad->pix_fmt; | ||
1307 | if (copy_to_user(buf, &pix_fmt, sizeof(pix_fmt))) | ||
1308 | return -EFAULT; | ||
1309 | break; | ||
1310 | case MFB_SET_AOID: | ||
1311 | if (copy_from_user(&aoi_d, buf, sizeof(aoi_d))) | ||
1312 | return -EFAULT; | ||
1313 | mfbi->x_aoi_d = aoi_d.x_aoi_d; | ||
1314 | mfbi->y_aoi_d = aoi_d.y_aoi_d; | ||
1315 | fsl_diu_check_var(&info->var, info); | ||
1316 | fsl_diu_set_aoi(info); | ||
1317 | break; | ||
1318 | case MFB_GET_AOID: | ||
1319 | aoi_d.x_aoi_d = mfbi->x_aoi_d; | ||
1320 | aoi_d.y_aoi_d = mfbi->y_aoi_d; | ||
1321 | if (copy_to_user(buf, &aoi_d, sizeof(aoi_d))) | ||
1322 | return -EFAULT; | ||
1323 | break; | ||
1324 | case MFB_GET_ALPHA: | ||
1325 | global_alpha = mfbi->g_alpha; | ||
1326 | if (copy_to_user(buf, &global_alpha, sizeof(global_alpha))) | ||
1327 | return -EFAULT; | ||
1328 | break; | ||
1329 | case MFB_SET_ALPHA: | ||
1330 | /* set panel information */ | ||
1331 | if (copy_from_user(&global_alpha, buf, sizeof(global_alpha))) | ||
1332 | return -EFAULT; | ||
1333 | ad->src_size_g_alpha = (ad->src_size_g_alpha & (~0xff)) | | ||
1334 | (global_alpha & 0xff); | ||
1335 | mfbi->g_alpha = global_alpha; | ||
1336 | break; | ||
1337 | case MFB_SET_CHROMA_KEY: | ||
1338 | /* set panel winformation */ | ||
1339 | if (copy_from_user(&ck, buf, sizeof(ck))) | ||
1340 | return -EFAULT; | ||
1341 | |||
1342 | if (ck.enable && | ||
1343 | (ck.red_max < ck.red_min || | ||
1344 | ck.green_max < ck.green_min || | ||
1345 | ck.blue_max < ck.blue_min)) | ||
1346 | return -EINVAL; | ||
1347 | |||
1348 | if (!ck.enable) { | ||
1349 | ad->ckmax_r = 0; | ||
1350 | ad->ckmax_g = 0; | ||
1351 | ad->ckmax_b = 0; | ||
1352 | ad->ckmin_r = 255; | ||
1353 | ad->ckmin_g = 255; | ||
1354 | ad->ckmin_b = 255; | ||
1355 | } else { | ||
1356 | ad->ckmax_r = ck.red_max; | ||
1357 | ad->ckmax_g = ck.green_max; | ||
1358 | ad->ckmax_b = ck.blue_max; | ||
1359 | ad->ckmin_r = ck.red_min; | ||
1360 | ad->ckmin_g = ck.green_min; | ||
1361 | ad->ckmin_b = ck.blue_min; | ||
1362 | } | ||
1363 | break; | ||
1364 | #ifdef CONFIG_PPC_MPC512x | ||
1365 | case MFB_SET_GAMMA: { | ||
1366 | struct fsl_diu_data *data = mfbi->parent; | ||
1367 | |||
1368 | if (copy_from_user(data->gamma, buf, sizeof(data->gamma))) | ||
1369 | return -EFAULT; | ||
1370 | setbits32(&data->diu_reg->gamma, 0); /* Force table reload */ | ||
1371 | break; | ||
1372 | } | ||
1373 | case MFB_GET_GAMMA: { | ||
1374 | struct fsl_diu_data *data = mfbi->parent; | ||
1375 | |||
1376 | if (copy_to_user(buf, data->gamma, sizeof(data->gamma))) | ||
1377 | return -EFAULT; | ||
1378 | break; | ||
1379 | } | ||
1380 | #endif | ||
1381 | default: | ||
1382 | dev_err(info->dev, "unknown ioctl command (0x%08X)\n", cmd); | ||
1383 | return -ENOIOCTLCMD; | ||
1384 | } | ||
1385 | |||
1386 | return 0; | ||
1387 | } | ||
1388 | |||
1389 | static inline void fsl_diu_enable_interrupts(struct fsl_diu_data *data) | ||
1390 | { | ||
1391 | u32 int_mask = INT_UNDRUN; /* enable underrun detection */ | ||
1392 | |||
1393 | if (IS_ENABLED(CONFIG_NOT_COHERENT_CACHE)) | ||
1394 | int_mask |= INT_VSYNC; /* enable vertical sync */ | ||
1395 | |||
1396 | clrbits32(&data->diu_reg->int_mask, int_mask); | ||
1397 | } | ||
1398 | |||
1399 | /* turn on fb if count == 1 | ||
1400 | */ | ||
1401 | static int fsl_diu_open(struct fb_info *info, int user) | ||
1402 | { | ||
1403 | struct mfb_info *mfbi = info->par; | ||
1404 | int res = 0; | ||
1405 | |||
1406 | /* free boot splash memory on first /dev/fb0 open */ | ||
1407 | if ((mfbi->index == PLANE0) && diu_ops.release_bootmem) | ||
1408 | diu_ops.release_bootmem(); | ||
1409 | |||
1410 | spin_lock(&diu_lock); | ||
1411 | mfbi->count++; | ||
1412 | if (mfbi->count == 1) { | ||
1413 | fsl_diu_check_var(&info->var, info); | ||
1414 | res = fsl_diu_set_par(info); | ||
1415 | if (res < 0) | ||
1416 | mfbi->count--; | ||
1417 | else { | ||
1418 | fsl_diu_enable_interrupts(mfbi->parent); | ||
1419 | fsl_diu_enable_panel(info); | ||
1420 | } | ||
1421 | } | ||
1422 | |||
1423 | spin_unlock(&diu_lock); | ||
1424 | return res; | ||
1425 | } | ||
1426 | |||
1427 | /* turn off fb if count == 0 | ||
1428 | */ | ||
1429 | static int fsl_diu_release(struct fb_info *info, int user) | ||
1430 | { | ||
1431 | struct mfb_info *mfbi = info->par; | ||
1432 | int res = 0; | ||
1433 | |||
1434 | spin_lock(&diu_lock); | ||
1435 | mfbi->count--; | ||
1436 | if (mfbi->count == 0) { | ||
1437 | struct fsl_diu_data *data = mfbi->parent; | ||
1438 | bool disable = true; | ||
1439 | int i; | ||
1440 | |||
1441 | /* Disable interrupts only if all AOIs are closed */ | ||
1442 | for (i = 0; i < NUM_AOIS; i++) { | ||
1443 | struct mfb_info *mi = data->fsl_diu_info[i].par; | ||
1444 | |||
1445 | if (mi->count) | ||
1446 | disable = false; | ||
1447 | } | ||
1448 | if (disable) | ||
1449 | out_be32(&data->diu_reg->int_mask, 0xffffffff); | ||
1450 | fsl_diu_disable_panel(info); | ||
1451 | } | ||
1452 | |||
1453 | spin_unlock(&diu_lock); | ||
1454 | return res; | ||
1455 | } | ||
1456 | |||
1457 | static struct fb_ops fsl_diu_ops = { | ||
1458 | .owner = THIS_MODULE, | ||
1459 | .fb_check_var = fsl_diu_check_var, | ||
1460 | .fb_set_par = fsl_diu_set_par, | ||
1461 | .fb_setcolreg = fsl_diu_setcolreg, | ||
1462 | .fb_pan_display = fsl_diu_pan_display, | ||
1463 | .fb_fillrect = cfb_fillrect, | ||
1464 | .fb_copyarea = cfb_copyarea, | ||
1465 | .fb_imageblit = cfb_imageblit, | ||
1466 | .fb_ioctl = fsl_diu_ioctl, | ||
1467 | .fb_open = fsl_diu_open, | ||
1468 | .fb_release = fsl_diu_release, | ||
1469 | .fb_cursor = fsl_diu_cursor, | ||
1470 | }; | ||
1471 | |||
1472 | static int install_fb(struct fb_info *info) | ||
1473 | { | ||
1474 | int rc; | ||
1475 | struct mfb_info *mfbi = info->par; | ||
1476 | struct fsl_diu_data *data = mfbi->parent; | ||
1477 | const char *aoi_mode, *init_aoi_mode = "320x240"; | ||
1478 | struct fb_videomode *db = fsl_diu_mode_db; | ||
1479 | unsigned int dbsize = ARRAY_SIZE(fsl_diu_mode_db); | ||
1480 | int has_default_mode = 1; | ||
1481 | |||
1482 | info->var.activate = FB_ACTIVATE_NOW; | ||
1483 | info->fbops = &fsl_diu_ops; | ||
1484 | info->flags = FBINFO_DEFAULT | FBINFO_VIRTFB | FBINFO_PARTIAL_PAN_OK | | ||
1485 | FBINFO_READS_FAST; | ||
1486 | info->pseudo_palette = mfbi->pseudo_palette; | ||
1487 | |||
1488 | rc = fb_alloc_cmap(&info->cmap, 16, 0); | ||
1489 | if (rc) | ||
1490 | return rc; | ||
1491 | |||
1492 | if (mfbi->index == PLANE0) { | ||
1493 | if (data->has_edid) { | ||
1494 | /* Now build modedb from EDID */ | ||
1495 | fb_edid_to_monspecs(data->edid_data, &info->monspecs); | ||
1496 | fb_videomode_to_modelist(info->monspecs.modedb, | ||
1497 | info->monspecs.modedb_len, | ||
1498 | &info->modelist); | ||
1499 | db = info->monspecs.modedb; | ||
1500 | dbsize = info->monspecs.modedb_len; | ||
1501 | } | ||
1502 | aoi_mode = fb_mode; | ||
1503 | } else { | ||
1504 | aoi_mode = init_aoi_mode; | ||
1505 | } | ||
1506 | rc = fb_find_mode(&info->var, info, aoi_mode, db, dbsize, NULL, | ||
1507 | default_bpp); | ||
1508 | if (!rc) { | ||
1509 | /* | ||
1510 | * For plane 0 we continue and look into | ||
1511 | * driver's internal modedb. | ||
1512 | */ | ||
1513 | if ((mfbi->index == PLANE0) && data->has_edid) | ||
1514 | has_default_mode = 0; | ||
1515 | else | ||
1516 | return -EINVAL; | ||
1517 | } | ||
1518 | |||
1519 | if (!has_default_mode) { | ||
1520 | rc = fb_find_mode(&info->var, info, aoi_mode, fsl_diu_mode_db, | ||
1521 | ARRAY_SIZE(fsl_diu_mode_db), NULL, default_bpp); | ||
1522 | if (rc) | ||
1523 | has_default_mode = 1; | ||
1524 | } | ||
1525 | |||
1526 | /* Still not found, use preferred mode from database if any */ | ||
1527 | if (!has_default_mode && info->monspecs.modedb) { | ||
1528 | struct fb_monspecs *specs = &info->monspecs; | ||
1529 | struct fb_videomode *modedb = &specs->modedb[0]; | ||
1530 | |||
1531 | /* | ||
1532 | * Get preferred timing. If not found, | ||
1533 | * first mode in database will be used. | ||
1534 | */ | ||
1535 | if (specs->misc & FB_MISC_1ST_DETAIL) { | ||
1536 | int i; | ||
1537 | |||
1538 | for (i = 0; i < specs->modedb_len; i++) { | ||
1539 | if (specs->modedb[i].flag & FB_MODE_IS_FIRST) { | ||
1540 | modedb = &specs->modedb[i]; | ||
1541 | break; | ||
1542 | } | ||
1543 | } | ||
1544 | } | ||
1545 | |||
1546 | info->var.bits_per_pixel = default_bpp; | ||
1547 | fb_videomode_to_var(&info->var, modedb); | ||
1548 | } | ||
1549 | |||
1550 | if (fsl_diu_check_var(&info->var, info)) { | ||
1551 | dev_err(info->dev, "fsl_diu_check_var failed\n"); | ||
1552 | unmap_video_memory(info); | ||
1553 | fb_dealloc_cmap(&info->cmap); | ||
1554 | return -EINVAL; | ||
1555 | } | ||
1556 | |||
1557 | if (register_framebuffer(info) < 0) { | ||
1558 | dev_err(info->dev, "register_framebuffer failed\n"); | ||
1559 | unmap_video_memory(info); | ||
1560 | fb_dealloc_cmap(&info->cmap); | ||
1561 | return -EINVAL; | ||
1562 | } | ||
1563 | |||
1564 | mfbi->registered = 1; | ||
1565 | dev_info(info->dev, "%s registered successfully\n", mfbi->id); | ||
1566 | |||
1567 | return 0; | ||
1568 | } | ||
1569 | |||
1570 | static void uninstall_fb(struct fb_info *info) | ||
1571 | { | ||
1572 | struct mfb_info *mfbi = info->par; | ||
1573 | |||
1574 | if (!mfbi->registered) | ||
1575 | return; | ||
1576 | |||
1577 | unregister_framebuffer(info); | ||
1578 | unmap_video_memory(info); | ||
1579 | if (&info->cmap) | ||
1580 | fb_dealloc_cmap(&info->cmap); | ||
1581 | |||
1582 | mfbi->registered = 0; | ||
1583 | } | ||
1584 | |||
1585 | static irqreturn_t fsl_diu_isr(int irq, void *dev_id) | ||
1586 | { | ||
1587 | struct diu __iomem *hw = dev_id; | ||
1588 | uint32_t status = in_be32(&hw->int_status); | ||
1589 | |||
1590 | if (status) { | ||
1591 | /* This is the workaround for underrun */ | ||
1592 | if (status & INT_UNDRUN) { | ||
1593 | out_be32(&hw->diu_mode, 0); | ||
1594 | udelay(1); | ||
1595 | out_be32(&hw->diu_mode, 1); | ||
1596 | } | ||
1597 | #if defined(CONFIG_NOT_COHERENT_CACHE) | ||
1598 | else if (status & INT_VSYNC) { | ||
1599 | unsigned int i; | ||
1600 | |||
1601 | for (i = 0; i < coherence_data_size; | ||
1602 | i += d_cache_line_size) | ||
1603 | __asm__ __volatile__ ( | ||
1604 | "dcbz 0, %[input]" | ||
1605 | ::[input]"r"(&coherence_data[i])); | ||
1606 | } | ||
1607 | #endif | ||
1608 | return IRQ_HANDLED; | ||
1609 | } | ||
1610 | return IRQ_NONE; | ||
1611 | } | ||
1612 | |||
1613 | #ifdef CONFIG_PM | ||
1614 | /* | ||
1615 | * Power management hooks. Note that we won't be called from IRQ context, | ||
1616 | * unlike the blank functions above, so we may sleep. | ||
1617 | */ | ||
1618 | static int fsl_diu_suspend(struct platform_device *ofdev, pm_message_t state) | ||
1619 | { | ||
1620 | struct fsl_diu_data *data; | ||
1621 | |||
1622 | data = dev_get_drvdata(&ofdev->dev); | ||
1623 | disable_lcdc(data->fsl_diu_info); | ||
1624 | |||
1625 | return 0; | ||
1626 | } | ||
1627 | |||
1628 | static int fsl_diu_resume(struct platform_device *ofdev) | ||
1629 | { | ||
1630 | struct fsl_diu_data *data; | ||
1631 | |||
1632 | data = dev_get_drvdata(&ofdev->dev); | ||
1633 | enable_lcdc(data->fsl_diu_info); | ||
1634 | |||
1635 | return 0; | ||
1636 | } | ||
1637 | |||
1638 | #else | ||
1639 | #define fsl_diu_suspend NULL | ||
1640 | #define fsl_diu_resume NULL | ||
1641 | #endif /* CONFIG_PM */ | ||
1642 | |||
1643 | static ssize_t store_monitor(struct device *device, | ||
1644 | struct device_attribute *attr, const char *buf, size_t count) | ||
1645 | { | ||
1646 | enum fsl_diu_monitor_port old_monitor_port; | ||
1647 | struct fsl_diu_data *data = | ||
1648 | container_of(attr, struct fsl_diu_data, dev_attr); | ||
1649 | |||
1650 | old_monitor_port = data->monitor_port; | ||
1651 | data->monitor_port = fsl_diu_name_to_port(buf); | ||
1652 | |||
1653 | if (old_monitor_port != data->monitor_port) { | ||
1654 | /* All AOIs need adjust pixel format | ||
1655 | * fsl_diu_set_par only change the pixsel format here | ||
1656 | * unlikely to fail. */ | ||
1657 | unsigned int i; | ||
1658 | |||
1659 | for (i=0; i < NUM_AOIS; i++) | ||
1660 | fsl_diu_set_par(&data->fsl_diu_info[i]); | ||
1661 | } | ||
1662 | return count; | ||
1663 | } | ||
1664 | |||
1665 | static ssize_t show_monitor(struct device *device, | ||
1666 | struct device_attribute *attr, char *buf) | ||
1667 | { | ||
1668 | struct fsl_diu_data *data = | ||
1669 | container_of(attr, struct fsl_diu_data, dev_attr); | ||
1670 | |||
1671 | switch (data->monitor_port) { | ||
1672 | case FSL_DIU_PORT_DVI: | ||
1673 | return sprintf(buf, "DVI\n"); | ||
1674 | case FSL_DIU_PORT_LVDS: | ||
1675 | return sprintf(buf, "Single-link LVDS\n"); | ||
1676 | case FSL_DIU_PORT_DLVDS: | ||
1677 | return sprintf(buf, "Dual-link LVDS\n"); | ||
1678 | } | ||
1679 | |||
1680 | return 0; | ||
1681 | } | ||
1682 | |||
1683 | static int fsl_diu_probe(struct platform_device *pdev) | ||
1684 | { | ||
1685 | struct device_node *np = pdev->dev.of_node; | ||
1686 | struct mfb_info *mfbi; | ||
1687 | struct fsl_diu_data *data; | ||
1688 | dma_addr_t dma_addr; /* DMA addr of fsl_diu_data struct */ | ||
1689 | const void *prop; | ||
1690 | unsigned int i; | ||
1691 | int ret; | ||
1692 | |||
1693 | data = dmam_alloc_coherent(&pdev->dev, sizeof(struct fsl_diu_data), | ||
1694 | &dma_addr, GFP_DMA | __GFP_ZERO); | ||
1695 | if (!data) | ||
1696 | return -ENOMEM; | ||
1697 | data->dma_addr = dma_addr; | ||
1698 | |||
1699 | /* | ||
1700 | * dma_alloc_coherent() uses a page allocator, so the address is | ||
1701 | * always page-aligned. We need the memory to be 32-byte aligned, | ||
1702 | * so that's good. However, if one day the allocator changes, we | ||
1703 | * need to catch that. It's not worth the effort to handle unaligned | ||
1704 | * alloctions now because it's highly unlikely to ever be a problem. | ||
1705 | */ | ||
1706 | if ((unsigned long)data & 31) { | ||
1707 | dev_err(&pdev->dev, "misaligned allocation"); | ||
1708 | ret = -ENOMEM; | ||
1709 | goto error; | ||
1710 | } | ||
1711 | |||
1712 | spin_lock_init(&data->reg_lock); | ||
1713 | |||
1714 | for (i = 0; i < NUM_AOIS; i++) { | ||
1715 | struct fb_info *info = &data->fsl_diu_info[i]; | ||
1716 | |||
1717 | info->device = &pdev->dev; | ||
1718 | info->par = &data->mfb[i]; | ||
1719 | |||
1720 | /* | ||
1721 | * We store the physical address of the AD in the reserved | ||
1722 | * 'paddr' field of the AD itself. | ||
1723 | */ | ||
1724 | data->ad[i].paddr = DMA_ADDR(data, ad[i]); | ||
1725 | |||
1726 | info->fix.smem_start = 0; | ||
1727 | |||
1728 | /* Initialize the AOI data structure */ | ||
1729 | mfbi = info->par; | ||
1730 | memcpy(mfbi, &mfb_template[i], sizeof(struct mfb_info)); | ||
1731 | mfbi->parent = data; | ||
1732 | mfbi->ad = &data->ad[i]; | ||
1733 | } | ||
1734 | |||
1735 | /* Get the EDID data from the device tree, if present */ | ||
1736 | prop = of_get_property(np, "edid", &ret); | ||
1737 | if (prop && ret == EDID_LENGTH) { | ||
1738 | memcpy(data->edid_data, prop, EDID_LENGTH); | ||
1739 | data->has_edid = true; | ||
1740 | } | ||
1741 | |||
1742 | data->diu_reg = of_iomap(np, 0); | ||
1743 | if (!data->diu_reg) { | ||
1744 | dev_err(&pdev->dev, "cannot map DIU registers\n"); | ||
1745 | ret = -EFAULT; | ||
1746 | goto error; | ||
1747 | } | ||
1748 | |||
1749 | /* Get the IRQ of the DIU */ | ||
1750 | data->irq = irq_of_parse_and_map(np, 0); | ||
1751 | |||
1752 | if (!data->irq) { | ||
1753 | dev_err(&pdev->dev, "could not get DIU IRQ\n"); | ||
1754 | ret = -EINVAL; | ||
1755 | goto error; | ||
1756 | } | ||
1757 | data->monitor_port = monitor_port; | ||
1758 | |||
1759 | /* Initialize the dummy Area Descriptor */ | ||
1760 | data->dummy_ad.addr = cpu_to_le32(DMA_ADDR(data, dummy_aoi)); | ||
1761 | data->dummy_ad.pix_fmt = 0x88882317; | ||
1762 | data->dummy_ad.src_size_g_alpha = cpu_to_le32((4 << 12) | 4); | ||
1763 | data->dummy_ad.aoi_size = cpu_to_le32((4 << 16) | 2); | ||
1764 | data->dummy_ad.offset_xyi = 0; | ||
1765 | data->dummy_ad.offset_xyd = 0; | ||
1766 | data->dummy_ad.next_ad = 0; | ||
1767 | data->dummy_ad.paddr = DMA_ADDR(data, dummy_ad); | ||
1768 | |||
1769 | /* | ||
1770 | * Let DIU continue to display splash screen if it was pre-initialized | ||
1771 | * by the bootloader; otherwise, clear the display. | ||
1772 | */ | ||
1773 | if (in_be32(&data->diu_reg->diu_mode) == MFB_MODE0) | ||
1774 | out_be32(&data->diu_reg->desc[0], 0); | ||
1775 | |||
1776 | out_be32(&data->diu_reg->desc[1], data->dummy_ad.paddr); | ||
1777 | out_be32(&data->diu_reg->desc[2], data->dummy_ad.paddr); | ||
1778 | |||
1779 | /* | ||
1780 | * Older versions of U-Boot leave interrupts enabled, so disable | ||
1781 | * all of them and clear the status register. | ||
1782 | */ | ||
1783 | out_be32(&data->diu_reg->int_mask, 0xffffffff); | ||
1784 | in_be32(&data->diu_reg->int_status); | ||
1785 | |||
1786 | ret = request_irq(data->irq, fsl_diu_isr, 0, "fsl-diu-fb", | ||
1787 | data->diu_reg); | ||
1788 | if (ret) { | ||
1789 | dev_err(&pdev->dev, "could not claim irq\n"); | ||
1790 | goto error; | ||
1791 | } | ||
1792 | |||
1793 | for (i = 0; i < NUM_AOIS; i++) { | ||
1794 | ret = install_fb(&data->fsl_diu_info[i]); | ||
1795 | if (ret) { | ||
1796 | dev_err(&pdev->dev, "could not register fb %d\n", i); | ||
1797 | free_irq(data->irq, data->diu_reg); | ||
1798 | goto error; | ||
1799 | } | ||
1800 | } | ||
1801 | |||
1802 | sysfs_attr_init(&data->dev_attr.attr); | ||
1803 | data->dev_attr.attr.name = "monitor"; | ||
1804 | data->dev_attr.attr.mode = S_IRUGO|S_IWUSR; | ||
1805 | data->dev_attr.show = show_monitor; | ||
1806 | data->dev_attr.store = store_monitor; | ||
1807 | ret = device_create_file(&pdev->dev, &data->dev_attr); | ||
1808 | if (ret) { | ||
1809 | dev_err(&pdev->dev, "could not create sysfs file %s\n", | ||
1810 | data->dev_attr.attr.name); | ||
1811 | } | ||
1812 | |||
1813 | dev_set_drvdata(&pdev->dev, data); | ||
1814 | return 0; | ||
1815 | |||
1816 | error: | ||
1817 | for (i = 0; i < NUM_AOIS; i++) | ||
1818 | uninstall_fb(&data->fsl_diu_info[i]); | ||
1819 | |||
1820 | iounmap(data->diu_reg); | ||
1821 | |||
1822 | return ret; | ||
1823 | } | ||
1824 | |||
1825 | static int fsl_diu_remove(struct platform_device *pdev) | ||
1826 | { | ||
1827 | struct fsl_diu_data *data; | ||
1828 | int i; | ||
1829 | |||
1830 | data = dev_get_drvdata(&pdev->dev); | ||
1831 | disable_lcdc(&data->fsl_diu_info[0]); | ||
1832 | |||
1833 | free_irq(data->irq, data->diu_reg); | ||
1834 | |||
1835 | for (i = 0; i < NUM_AOIS; i++) | ||
1836 | uninstall_fb(&data->fsl_diu_info[i]); | ||
1837 | |||
1838 | iounmap(data->diu_reg); | ||
1839 | |||
1840 | return 0; | ||
1841 | } | ||
1842 | |||
1843 | #ifndef MODULE | ||
1844 | static int __init fsl_diu_setup(char *options) | ||
1845 | { | ||
1846 | char *opt; | ||
1847 | unsigned long val; | ||
1848 | |||
1849 | if (!options || !*options) | ||
1850 | return 0; | ||
1851 | |||
1852 | while ((opt = strsep(&options, ",")) != NULL) { | ||
1853 | if (!*opt) | ||
1854 | continue; | ||
1855 | if (!strncmp(opt, "monitor=", 8)) { | ||
1856 | monitor_port = fsl_diu_name_to_port(opt + 8); | ||
1857 | } else if (!strncmp(opt, "bpp=", 4)) { | ||
1858 | if (!kstrtoul(opt + 4, 10, &val)) | ||
1859 | default_bpp = val; | ||
1860 | } else | ||
1861 | fb_mode = opt; | ||
1862 | } | ||
1863 | |||
1864 | return 0; | ||
1865 | } | ||
1866 | #endif | ||
1867 | |||
1868 | static struct of_device_id fsl_diu_match[] = { | ||
1869 | #ifdef CONFIG_PPC_MPC512x | ||
1870 | { | ||
1871 | .compatible = "fsl,mpc5121-diu", | ||
1872 | }, | ||
1873 | #endif | ||
1874 | { | ||
1875 | .compatible = "fsl,diu", | ||
1876 | }, | ||
1877 | {} | ||
1878 | }; | ||
1879 | MODULE_DEVICE_TABLE(of, fsl_diu_match); | ||
1880 | |||
1881 | static struct platform_driver fsl_diu_driver = { | ||
1882 | .driver = { | ||
1883 | .name = "fsl-diu-fb", | ||
1884 | .owner = THIS_MODULE, | ||
1885 | .of_match_table = fsl_diu_match, | ||
1886 | }, | ||
1887 | .probe = fsl_diu_probe, | ||
1888 | .remove = fsl_diu_remove, | ||
1889 | .suspend = fsl_diu_suspend, | ||
1890 | .resume = fsl_diu_resume, | ||
1891 | }; | ||
1892 | |||
1893 | static int __init fsl_diu_init(void) | ||
1894 | { | ||
1895 | #ifdef CONFIG_NOT_COHERENT_CACHE | ||
1896 | struct device_node *np; | ||
1897 | const u32 *prop; | ||
1898 | #endif | ||
1899 | int ret; | ||
1900 | #ifndef MODULE | ||
1901 | char *option; | ||
1902 | |||
1903 | /* | ||
1904 | * For kernel boot options (in 'video=xxxfb:<options>' format) | ||
1905 | */ | ||
1906 | if (fb_get_options("fslfb", &option)) | ||
1907 | return -ENODEV; | ||
1908 | fsl_diu_setup(option); | ||
1909 | #else | ||
1910 | monitor_port = fsl_diu_name_to_port(monitor_string); | ||
1911 | #endif | ||
1912 | pr_info("Freescale Display Interface Unit (DIU) framebuffer driver\n"); | ||
1913 | |||
1914 | #ifdef CONFIG_NOT_COHERENT_CACHE | ||
1915 | np = of_find_node_by_type(NULL, "cpu"); | ||
1916 | if (!np) { | ||
1917 | pr_err("fsl-diu-fb: can't find 'cpu' device node\n"); | ||
1918 | return -ENODEV; | ||
1919 | } | ||
1920 | |||
1921 | prop = of_get_property(np, "d-cache-size", NULL); | ||
1922 | if (prop == NULL) { | ||
1923 | pr_err("fsl-diu-fb: missing 'd-cache-size' property' " | ||
1924 | "in 'cpu' node\n"); | ||
1925 | of_node_put(np); | ||
1926 | return -ENODEV; | ||
1927 | } | ||
1928 | |||
1929 | /* | ||
1930 | * Freescale PLRU requires 13/8 times the cache size to do a proper | ||
1931 | * displacement flush | ||
1932 | */ | ||
1933 | coherence_data_size = be32_to_cpup(prop) * 13; | ||
1934 | coherence_data_size /= 8; | ||
1935 | |||
1936 | pr_debug("fsl-diu-fb: coherence data size is %zu bytes\n", | ||
1937 | coherence_data_size); | ||
1938 | |||
1939 | prop = of_get_property(np, "d-cache-line-size", NULL); | ||
1940 | if (prop == NULL) { | ||
1941 | pr_err("fsl-diu-fb: missing 'd-cache-line-size' property' " | ||
1942 | "in 'cpu' node\n"); | ||
1943 | of_node_put(np); | ||
1944 | return -ENODEV; | ||
1945 | } | ||
1946 | d_cache_line_size = be32_to_cpup(prop); | ||
1947 | |||
1948 | pr_debug("fsl-diu-fb: cache lines size is %u bytes\n", | ||
1949 | d_cache_line_size); | ||
1950 | |||
1951 | of_node_put(np); | ||
1952 | coherence_data = vmalloc(coherence_data_size); | ||
1953 | if (!coherence_data) { | ||
1954 | pr_err("fsl-diu-fb: could not allocate coherence data " | ||
1955 | "(size=%zu)\n", coherence_data_size); | ||
1956 | return -ENOMEM; | ||
1957 | } | ||
1958 | |||
1959 | #endif | ||
1960 | |||
1961 | ret = platform_driver_register(&fsl_diu_driver); | ||
1962 | if (ret) { | ||
1963 | pr_err("fsl-diu-fb: failed to register platform driver\n"); | ||
1964 | #if defined(CONFIG_NOT_COHERENT_CACHE) | ||
1965 | vfree(coherence_data); | ||
1966 | #endif | ||
1967 | } | ||
1968 | return ret; | ||
1969 | } | ||
1970 | |||
1971 | static void __exit fsl_diu_exit(void) | ||
1972 | { | ||
1973 | platform_driver_unregister(&fsl_diu_driver); | ||
1974 | #if defined(CONFIG_NOT_COHERENT_CACHE) | ||
1975 | vfree(coherence_data); | ||
1976 | #endif | ||
1977 | } | ||
1978 | |||
1979 | module_init(fsl_diu_init); | ||
1980 | module_exit(fsl_diu_exit); | ||
1981 | |||
1982 | MODULE_AUTHOR("York Sun <yorksun@freescale.com>"); | ||
1983 | MODULE_DESCRIPTION("Freescale DIU framebuffer driver"); | ||
1984 | MODULE_LICENSE("GPL"); | ||
1985 | |||
1986 | module_param_named(mode, fb_mode, charp, 0); | ||
1987 | MODULE_PARM_DESC(mode, | ||
1988 | "Specify resolution as \"<xres>x<yres>[-<bpp>][@<refresh>]\" "); | ||
1989 | module_param_named(bpp, default_bpp, ulong, 0); | ||
1990 | MODULE_PARM_DESC(bpp, "Specify bit-per-pixel if not specified in 'mode'"); | ||
1991 | module_param_named(monitor, monitor_string, charp, 0); | ||
1992 | MODULE_PARM_DESC(monitor, "Specify the monitor port " | ||
1993 | "(\"dvi\", \"lvds\", or \"dlvds\") if supported by the platform"); | ||
1994 | |||