diff options
Diffstat (limited to 'drivers/video/fbdev/vt8500lcdfb.c')
-rw-r--r-- | drivers/video/fbdev/vt8500lcdfb.c | 502 |
1 files changed, 502 insertions, 0 deletions
diff --git a/drivers/video/fbdev/vt8500lcdfb.c b/drivers/video/fbdev/vt8500lcdfb.c new file mode 100644 index 000000000000..a8f2b280f796 --- /dev/null +++ b/drivers/video/fbdev/vt8500lcdfb.c | |||
@@ -0,0 +1,502 @@ | |||
1 | /* | ||
2 | * linux/drivers/video/vt8500lcdfb.c | ||
3 | * | ||
4 | * Copyright (C) 2010 Alexey Charkov <alchark@gmail.com> | ||
5 | * | ||
6 | * Based on skeletonfb.c and pxafb.c | ||
7 | * | ||
8 | * This software is licensed under the terms of the GNU General Public | ||
9 | * License version 2, as published by the Free Software Foundation, and | ||
10 | * may be copied, distributed, and modified under those terms. | ||
11 | * | ||
12 | * This program is distributed in the hope that it will be useful, | ||
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
15 | * GNU General Public License for more details. | ||
16 | */ | ||
17 | |||
18 | #include <linux/delay.h> | ||
19 | #include <linux/dma-mapping.h> | ||
20 | #include <linux/errno.h> | ||
21 | #include <linux/fb.h> | ||
22 | #include <linux/init.h> | ||
23 | #include <linux/interrupt.h> | ||
24 | #include <linux/io.h> | ||
25 | #include <linux/kernel.h> | ||
26 | #include <linux/mm.h> | ||
27 | #include <linux/module.h> | ||
28 | #include <linux/platform_device.h> | ||
29 | #include <linux/slab.h> | ||
30 | #include <linux/string.h> | ||
31 | #include <linux/wait.h> | ||
32 | #include <video/of_display_timing.h> | ||
33 | |||
34 | #include "vt8500lcdfb.h" | ||
35 | #include "wmt_ge_rops.h" | ||
36 | |||
37 | #ifdef CONFIG_OF | ||
38 | #include <linux/of.h> | ||
39 | #include <linux/of_fdt.h> | ||
40 | #include <linux/memblock.h> | ||
41 | #endif | ||
42 | |||
43 | |||
44 | #define to_vt8500lcd_info(__info) container_of(__info, \ | ||
45 | struct vt8500lcd_info, fb) | ||
46 | |||
47 | static int vt8500lcd_set_par(struct fb_info *info) | ||
48 | { | ||
49 | struct vt8500lcd_info *fbi = to_vt8500lcd_info(info); | ||
50 | int reg_bpp = 5; /* 16bpp */ | ||
51 | int i; | ||
52 | unsigned long control0; | ||
53 | |||
54 | if (!fbi) | ||
55 | return -EINVAL; | ||
56 | |||
57 | if (info->var.bits_per_pixel <= 8) { | ||
58 | /* palettized */ | ||
59 | info->var.red.offset = 0; | ||
60 | info->var.red.length = info->var.bits_per_pixel; | ||
61 | info->var.red.msb_right = 0; | ||
62 | |||
63 | info->var.green.offset = 0; | ||
64 | info->var.green.length = info->var.bits_per_pixel; | ||
65 | info->var.green.msb_right = 0; | ||
66 | |||
67 | info->var.blue.offset = 0; | ||
68 | info->var.blue.length = info->var.bits_per_pixel; | ||
69 | info->var.blue.msb_right = 0; | ||
70 | |||
71 | info->var.transp.offset = 0; | ||
72 | info->var.transp.length = 0; | ||
73 | info->var.transp.msb_right = 0; | ||
74 | |||
75 | info->fix.visual = FB_VISUAL_PSEUDOCOLOR; | ||
76 | info->fix.line_length = info->var.xres_virtual / | ||
77 | (8/info->var.bits_per_pixel); | ||
78 | } else { | ||
79 | /* non-palettized */ | ||
80 | info->var.transp.offset = 0; | ||
81 | info->var.transp.length = 0; | ||
82 | info->var.transp.msb_right = 0; | ||
83 | |||
84 | if (info->var.bits_per_pixel == 16) { | ||
85 | /* RGB565 */ | ||
86 | info->var.red.offset = 11; | ||
87 | info->var.red.length = 5; | ||
88 | info->var.red.msb_right = 0; | ||
89 | info->var.green.offset = 5; | ||
90 | info->var.green.length = 6; | ||
91 | info->var.green.msb_right = 0; | ||
92 | info->var.blue.offset = 0; | ||
93 | info->var.blue.length = 5; | ||
94 | info->var.blue.msb_right = 0; | ||
95 | } else { | ||
96 | /* Equal depths per channel */ | ||
97 | info->var.red.offset = info->var.bits_per_pixel | ||
98 | * 2 / 3; | ||
99 | info->var.red.length = info->var.bits_per_pixel / 3; | ||
100 | info->var.red.msb_right = 0; | ||
101 | info->var.green.offset = info->var.bits_per_pixel / 3; | ||
102 | info->var.green.length = info->var.bits_per_pixel / 3; | ||
103 | info->var.green.msb_right = 0; | ||
104 | info->var.blue.offset = 0; | ||
105 | info->var.blue.length = info->var.bits_per_pixel / 3; | ||
106 | info->var.blue.msb_right = 0; | ||
107 | } | ||
108 | |||
109 | info->fix.visual = FB_VISUAL_TRUECOLOR; | ||
110 | info->fix.line_length = info->var.bits_per_pixel > 16 ? | ||
111 | info->var.xres_virtual << 2 : | ||
112 | info->var.xres_virtual << 1; | ||
113 | } | ||
114 | |||
115 | for (i = 0; i < 8; i++) { | ||
116 | if (bpp_values[i] == info->var.bits_per_pixel) { | ||
117 | reg_bpp = i; | ||
118 | continue; | ||
119 | } | ||
120 | } | ||
121 | |||
122 | control0 = readl(fbi->regbase) & ~0xf; | ||
123 | writel(0, fbi->regbase); | ||
124 | while (readl(fbi->regbase + 0x38) & 0x10) | ||
125 | /* wait */; | ||
126 | writel((((info->var.hsync_len - 1) & 0x3f) << 26) | ||
127 | | ((info->var.left_margin & 0xff) << 18) | ||
128 | | (((info->var.xres - 1) & 0x3ff) << 8) | ||
129 | | (info->var.right_margin & 0xff), fbi->regbase + 0x4); | ||
130 | writel((((info->var.vsync_len - 1) & 0x3f) << 26) | ||
131 | | ((info->var.upper_margin & 0xff) << 18) | ||
132 | | (((info->var.yres - 1) & 0x3ff) << 8) | ||
133 | | (info->var.lower_margin & 0xff), fbi->regbase + 0x8); | ||
134 | writel((((info->var.yres - 1) & 0x400) << 2) | ||
135 | | ((info->var.xres - 1) & 0x400), fbi->regbase + 0x10); | ||
136 | writel(0x80000000, fbi->regbase + 0x20); | ||
137 | writel(control0 | (reg_bpp << 1) | 0x100, fbi->regbase); | ||
138 | |||
139 | return 0; | ||
140 | } | ||
141 | |||
142 | static inline u_int chan_to_field(u_int chan, struct fb_bitfield *bf) | ||
143 | { | ||
144 | chan &= 0xffff; | ||
145 | chan >>= 16 - bf->length; | ||
146 | return chan << bf->offset; | ||
147 | } | ||
148 | |||
149 | static int vt8500lcd_setcolreg(unsigned regno, unsigned red, unsigned green, | ||
150 | unsigned blue, unsigned transp, | ||
151 | struct fb_info *info) { | ||
152 | struct vt8500lcd_info *fbi = to_vt8500lcd_info(info); | ||
153 | int ret = 1; | ||
154 | unsigned int val; | ||
155 | if (regno >= 256) | ||
156 | return -EINVAL; | ||
157 | |||
158 | if (info->var.grayscale) | ||
159 | red = green = blue = | ||
160 | (19595 * red + 38470 * green + 7471 * blue) >> 16; | ||
161 | |||
162 | switch (fbi->fb.fix.visual) { | ||
163 | case FB_VISUAL_TRUECOLOR: | ||
164 | if (regno < 16) { | ||
165 | u32 *pal = fbi->fb.pseudo_palette; | ||
166 | |||
167 | val = chan_to_field(red, &fbi->fb.var.red); | ||
168 | val |= chan_to_field(green, &fbi->fb.var.green); | ||
169 | val |= chan_to_field(blue, &fbi->fb.var.blue); | ||
170 | |||
171 | pal[regno] = val; | ||
172 | ret = 0; | ||
173 | } | ||
174 | break; | ||
175 | |||
176 | case FB_VISUAL_STATIC_PSEUDOCOLOR: | ||
177 | case FB_VISUAL_PSEUDOCOLOR: | ||
178 | writew((red & 0xf800) | ||
179 | | ((green >> 5) & 0x7e0) | ||
180 | | ((blue >> 11) & 0x1f), | ||
181 | fbi->palette_cpu + sizeof(u16) * regno); | ||
182 | break; | ||
183 | } | ||
184 | |||
185 | return ret; | ||
186 | } | ||
187 | |||
188 | static int vt8500lcd_ioctl(struct fb_info *info, unsigned int cmd, | ||
189 | unsigned long arg) | ||
190 | { | ||
191 | int ret = 0; | ||
192 | struct vt8500lcd_info *fbi = to_vt8500lcd_info(info); | ||
193 | |||
194 | if (cmd == FBIO_WAITFORVSYNC) { | ||
195 | /* Unmask End of Frame interrupt */ | ||
196 | writel(0xffffffff ^ (1 << 3), fbi->regbase + 0x3c); | ||
197 | ret = wait_event_interruptible_timeout(fbi->wait, | ||
198 | readl(fbi->regbase + 0x38) & (1 << 3), HZ / 10); | ||
199 | /* Mask back to reduce unwanted interrupt traffic */ | ||
200 | writel(0xffffffff, fbi->regbase + 0x3c); | ||
201 | if (ret < 0) | ||
202 | return ret; | ||
203 | if (ret == 0) | ||
204 | return -ETIMEDOUT; | ||
205 | } | ||
206 | |||
207 | return ret; | ||
208 | } | ||
209 | |||
210 | static int vt8500lcd_pan_display(struct fb_var_screeninfo *var, | ||
211 | struct fb_info *info) | ||
212 | { | ||
213 | unsigned pixlen = info->fix.line_length / info->var.xres_virtual; | ||
214 | unsigned off = pixlen * var->xoffset | ||
215 | + info->fix.line_length * var->yoffset; | ||
216 | struct vt8500lcd_info *fbi = to_vt8500lcd_info(info); | ||
217 | |||
218 | writel((1 << 31) | ||
219 | | (((info->var.xres_virtual - info->var.xres) * pixlen / 4) << 20) | ||
220 | | (off >> 2), fbi->regbase + 0x20); | ||
221 | return 0; | ||
222 | } | ||
223 | |||
224 | /* | ||
225 | * vt8500lcd_blank(): | ||
226 | * Blank the display by setting all palette values to zero. Note, | ||
227 | * True Color modes do not really use the palette, so this will not | ||
228 | * blank the display in all modes. | ||
229 | */ | ||
230 | static int vt8500lcd_blank(int blank, struct fb_info *info) | ||
231 | { | ||
232 | int i; | ||
233 | |||
234 | switch (blank) { | ||
235 | case FB_BLANK_POWERDOWN: | ||
236 | case FB_BLANK_VSYNC_SUSPEND: | ||
237 | case FB_BLANK_HSYNC_SUSPEND: | ||
238 | case FB_BLANK_NORMAL: | ||
239 | if (info->fix.visual == FB_VISUAL_PSEUDOCOLOR || | ||
240 | info->fix.visual == FB_VISUAL_STATIC_PSEUDOCOLOR) | ||
241 | for (i = 0; i < 256; i++) | ||
242 | vt8500lcd_setcolreg(i, 0, 0, 0, 0, info); | ||
243 | case FB_BLANK_UNBLANK: | ||
244 | if (info->fix.visual == FB_VISUAL_PSEUDOCOLOR || | ||
245 | info->fix.visual == FB_VISUAL_STATIC_PSEUDOCOLOR) | ||
246 | fb_set_cmap(&info->cmap, info); | ||
247 | } | ||
248 | return 0; | ||
249 | } | ||
250 | |||
251 | static struct fb_ops vt8500lcd_ops = { | ||
252 | .owner = THIS_MODULE, | ||
253 | .fb_set_par = vt8500lcd_set_par, | ||
254 | .fb_setcolreg = vt8500lcd_setcolreg, | ||
255 | .fb_fillrect = wmt_ge_fillrect, | ||
256 | .fb_copyarea = wmt_ge_copyarea, | ||
257 | .fb_imageblit = sys_imageblit, | ||
258 | .fb_sync = wmt_ge_sync, | ||
259 | .fb_ioctl = vt8500lcd_ioctl, | ||
260 | .fb_pan_display = vt8500lcd_pan_display, | ||
261 | .fb_blank = vt8500lcd_blank, | ||
262 | }; | ||
263 | |||
264 | static irqreturn_t vt8500lcd_handle_irq(int irq, void *dev_id) | ||
265 | { | ||
266 | struct vt8500lcd_info *fbi = dev_id; | ||
267 | |||
268 | if (readl(fbi->regbase + 0x38) & (1 << 3)) | ||
269 | wake_up_interruptible(&fbi->wait); | ||
270 | |||
271 | writel(0xffffffff, fbi->regbase + 0x38); | ||
272 | return IRQ_HANDLED; | ||
273 | } | ||
274 | |||
275 | static int vt8500lcd_probe(struct platform_device *pdev) | ||
276 | { | ||
277 | struct vt8500lcd_info *fbi; | ||
278 | struct resource *res; | ||
279 | struct display_timings *disp_timing; | ||
280 | void *addr; | ||
281 | int irq, ret; | ||
282 | |||
283 | struct fb_videomode of_mode; | ||
284 | u32 bpp; | ||
285 | dma_addr_t fb_mem_phys; | ||
286 | unsigned long fb_mem_len; | ||
287 | void *fb_mem_virt; | ||
288 | |||
289 | ret = -ENOMEM; | ||
290 | fbi = NULL; | ||
291 | |||
292 | fbi = devm_kzalloc(&pdev->dev, sizeof(struct vt8500lcd_info) | ||
293 | + sizeof(u32) * 16, GFP_KERNEL); | ||
294 | if (!fbi) { | ||
295 | dev_err(&pdev->dev, "Failed to initialize framebuffer device\n"); | ||
296 | return -ENOMEM; | ||
297 | } | ||
298 | |||
299 | strcpy(fbi->fb.fix.id, "VT8500 LCD"); | ||
300 | |||
301 | fbi->fb.fix.type = FB_TYPE_PACKED_PIXELS; | ||
302 | fbi->fb.fix.xpanstep = 0; | ||
303 | fbi->fb.fix.ypanstep = 1; | ||
304 | fbi->fb.fix.ywrapstep = 0; | ||
305 | fbi->fb.fix.accel = FB_ACCEL_NONE; | ||
306 | |||
307 | fbi->fb.var.nonstd = 0; | ||
308 | fbi->fb.var.activate = FB_ACTIVATE_NOW; | ||
309 | fbi->fb.var.height = -1; | ||
310 | fbi->fb.var.width = -1; | ||
311 | fbi->fb.var.vmode = FB_VMODE_NONINTERLACED; | ||
312 | |||
313 | fbi->fb.fbops = &vt8500lcd_ops; | ||
314 | fbi->fb.flags = FBINFO_DEFAULT | ||
315 | | FBINFO_HWACCEL_COPYAREA | ||
316 | | FBINFO_HWACCEL_FILLRECT | ||
317 | | FBINFO_HWACCEL_YPAN | ||
318 | | FBINFO_VIRTFB | ||
319 | | FBINFO_PARTIAL_PAN_OK; | ||
320 | fbi->fb.node = -1; | ||
321 | |||
322 | addr = fbi; | ||
323 | addr = addr + sizeof(struct vt8500lcd_info); | ||
324 | fbi->fb.pseudo_palette = addr; | ||
325 | |||
326 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
327 | if (res == NULL) { | ||
328 | dev_err(&pdev->dev, "no I/O memory resource defined\n"); | ||
329 | return -ENODEV; | ||
330 | } | ||
331 | |||
332 | res = request_mem_region(res->start, resource_size(res), "vt8500lcd"); | ||
333 | if (res == NULL) { | ||
334 | dev_err(&pdev->dev, "failed to request I/O memory\n"); | ||
335 | return -EBUSY; | ||
336 | } | ||
337 | |||
338 | fbi->regbase = ioremap(res->start, resource_size(res)); | ||
339 | if (fbi->regbase == NULL) { | ||
340 | dev_err(&pdev->dev, "failed to map I/O memory\n"); | ||
341 | ret = -EBUSY; | ||
342 | goto failed_free_res; | ||
343 | } | ||
344 | |||
345 | disp_timing = of_get_display_timings(pdev->dev.of_node); | ||
346 | if (!disp_timing) { | ||
347 | ret = -EINVAL; | ||
348 | goto failed_free_io; | ||
349 | } | ||
350 | |||
351 | ret = of_get_fb_videomode(pdev->dev.of_node, &of_mode, | ||
352 | OF_USE_NATIVE_MODE); | ||
353 | if (ret) | ||
354 | goto failed_free_io; | ||
355 | |||
356 | ret = of_property_read_u32(pdev->dev.of_node, "bits-per-pixel", &bpp); | ||
357 | if (ret) | ||
358 | goto failed_free_io; | ||
359 | |||
360 | /* try allocating the framebuffer */ | ||
361 | fb_mem_len = of_mode.xres * of_mode.yres * 2 * (bpp / 8); | ||
362 | fb_mem_virt = dma_alloc_coherent(&pdev->dev, fb_mem_len, &fb_mem_phys, | ||
363 | GFP_KERNEL); | ||
364 | if (!fb_mem_virt) { | ||
365 | pr_err("%s: Failed to allocate framebuffer\n", __func__); | ||
366 | ret = -ENOMEM; | ||
367 | goto failed_free_io; | ||
368 | } | ||
369 | |||
370 | fbi->fb.fix.smem_start = fb_mem_phys; | ||
371 | fbi->fb.fix.smem_len = fb_mem_len; | ||
372 | fbi->fb.screen_base = fb_mem_virt; | ||
373 | |||
374 | fbi->palette_size = PAGE_ALIGN(512); | ||
375 | fbi->palette_cpu = dma_alloc_coherent(&pdev->dev, | ||
376 | fbi->palette_size, | ||
377 | &fbi->palette_phys, | ||
378 | GFP_KERNEL); | ||
379 | if (fbi->palette_cpu == NULL) { | ||
380 | dev_err(&pdev->dev, "Failed to allocate palette buffer\n"); | ||
381 | ret = -ENOMEM; | ||
382 | goto failed_free_io; | ||
383 | } | ||
384 | |||
385 | irq = platform_get_irq(pdev, 0); | ||
386 | if (irq < 0) { | ||
387 | dev_err(&pdev->dev, "no IRQ defined\n"); | ||
388 | ret = -ENODEV; | ||
389 | goto failed_free_palette; | ||
390 | } | ||
391 | |||
392 | ret = request_irq(irq, vt8500lcd_handle_irq, 0, "LCD", fbi); | ||
393 | if (ret) { | ||
394 | dev_err(&pdev->dev, "request_irq failed: %d\n", ret); | ||
395 | ret = -EBUSY; | ||
396 | goto failed_free_palette; | ||
397 | } | ||
398 | |||
399 | init_waitqueue_head(&fbi->wait); | ||
400 | |||
401 | if (fb_alloc_cmap(&fbi->fb.cmap, 256, 0) < 0) { | ||
402 | dev_err(&pdev->dev, "Failed to allocate color map\n"); | ||
403 | ret = -ENOMEM; | ||
404 | goto failed_free_irq; | ||
405 | } | ||
406 | |||
407 | fb_videomode_to_var(&fbi->fb.var, &of_mode); | ||
408 | |||
409 | fbi->fb.var.xres_virtual = of_mode.xres; | ||
410 | fbi->fb.var.yres_virtual = of_mode.yres * 2; | ||
411 | fbi->fb.var.bits_per_pixel = bpp; | ||
412 | |||
413 | ret = vt8500lcd_set_par(&fbi->fb); | ||
414 | if (ret) { | ||
415 | dev_err(&pdev->dev, "Failed to set parameters\n"); | ||
416 | goto failed_free_cmap; | ||
417 | } | ||
418 | |||
419 | writel(fbi->fb.fix.smem_start >> 22, fbi->regbase + 0x1c); | ||
420 | writel((fbi->palette_phys & 0xfffffe00) | 1, fbi->regbase + 0x18); | ||
421 | |||
422 | platform_set_drvdata(pdev, fbi); | ||
423 | |||
424 | ret = register_framebuffer(&fbi->fb); | ||
425 | if (ret < 0) { | ||
426 | dev_err(&pdev->dev, | ||
427 | "Failed to register framebuffer device: %d\n", ret); | ||
428 | goto failed_free_cmap; | ||
429 | } | ||
430 | |||
431 | /* | ||
432 | * Ok, now enable the LCD controller | ||
433 | */ | ||
434 | writel(readl(fbi->regbase) | 1, fbi->regbase); | ||
435 | |||
436 | return 0; | ||
437 | |||
438 | failed_free_cmap: | ||
439 | if (fbi->fb.cmap.len) | ||
440 | fb_dealloc_cmap(&fbi->fb.cmap); | ||
441 | failed_free_irq: | ||
442 | free_irq(irq, fbi); | ||
443 | failed_free_palette: | ||
444 | dma_free_coherent(&pdev->dev, fbi->palette_size, | ||
445 | fbi->palette_cpu, fbi->palette_phys); | ||
446 | failed_free_io: | ||
447 | iounmap(fbi->regbase); | ||
448 | failed_free_res: | ||
449 | release_mem_region(res->start, resource_size(res)); | ||
450 | return ret; | ||
451 | } | ||
452 | |||
453 | static int vt8500lcd_remove(struct platform_device *pdev) | ||
454 | { | ||
455 | struct vt8500lcd_info *fbi = platform_get_drvdata(pdev); | ||
456 | struct resource *res; | ||
457 | int irq; | ||
458 | |||
459 | unregister_framebuffer(&fbi->fb); | ||
460 | |||
461 | writel(0, fbi->regbase); | ||
462 | |||
463 | if (fbi->fb.cmap.len) | ||
464 | fb_dealloc_cmap(&fbi->fb.cmap); | ||
465 | |||
466 | irq = platform_get_irq(pdev, 0); | ||
467 | free_irq(irq, fbi); | ||
468 | |||
469 | dma_free_coherent(&pdev->dev, fbi->palette_size, | ||
470 | fbi->palette_cpu, fbi->palette_phys); | ||
471 | |||
472 | iounmap(fbi->regbase); | ||
473 | |||
474 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
475 | release_mem_region(res->start, resource_size(res)); | ||
476 | |||
477 | kfree(fbi); | ||
478 | |||
479 | return 0; | ||
480 | } | ||
481 | |||
482 | static const struct of_device_id via_dt_ids[] = { | ||
483 | { .compatible = "via,vt8500-fb", }, | ||
484 | {} | ||
485 | }; | ||
486 | |||
487 | static struct platform_driver vt8500lcd_driver = { | ||
488 | .probe = vt8500lcd_probe, | ||
489 | .remove = vt8500lcd_remove, | ||
490 | .driver = { | ||
491 | .owner = THIS_MODULE, | ||
492 | .name = "vt8500-lcd", | ||
493 | .of_match_table = of_match_ptr(via_dt_ids), | ||
494 | }, | ||
495 | }; | ||
496 | |||
497 | module_platform_driver(vt8500lcd_driver); | ||
498 | |||
499 | MODULE_AUTHOR("Alexey Charkov <alchark@gmail.com>"); | ||
500 | MODULE_DESCRIPTION("LCD controller driver for VIA VT8500"); | ||
501 | MODULE_LICENSE("GPL v2"); | ||
502 | MODULE_DEVICE_TABLE(of, via_dt_ids); | ||