diff options
Diffstat (limited to 'drivers/video/vt8500lcdfb.c')
-rw-r--r-- | drivers/video/vt8500lcdfb.c | 447 |
1 files changed, 447 insertions, 0 deletions
diff --git a/drivers/video/vt8500lcdfb.c b/drivers/video/vt8500lcdfb.c new file mode 100644 index 000000000000..7617f12e4fd7 --- /dev/null +++ b/drivers/video/vt8500lcdfb.c | |||
@@ -0,0 +1,447 @@ | |||
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/module.h> | ||
19 | #include <linux/kernel.h> | ||
20 | #include <linux/errno.h> | ||
21 | #include <linux/string.h> | ||
22 | #include <linux/mm.h> | ||
23 | #include <linux/slab.h> | ||
24 | #include <linux/delay.h> | ||
25 | #include <linux/fb.h> | ||
26 | #include <linux/init.h> | ||
27 | #include <linux/interrupt.h> | ||
28 | #include <linux/io.h> | ||
29 | #include <linux/dma-mapping.h> | ||
30 | #include <linux/platform_device.h> | ||
31 | #include <linux/wait.h> | ||
32 | |||
33 | #include <mach/vt8500fb.h> | ||
34 | |||
35 | #include "vt8500lcdfb.h" | ||
36 | #include "wmt_ge_rops.h" | ||
37 | |||
38 | #define to_vt8500lcd_info(__info) container_of(__info, \ | ||
39 | struct vt8500lcd_info, fb) | ||
40 | |||
41 | static int vt8500lcd_set_par(struct fb_info *info) | ||
42 | { | ||
43 | struct vt8500lcd_info *fbi = to_vt8500lcd_info(info); | ||
44 | int reg_bpp = 5; /* 16bpp */ | ||
45 | int i; | ||
46 | unsigned long control0; | ||
47 | |||
48 | if (!fbi) | ||
49 | return -EINVAL; | ||
50 | |||
51 | if (info->var.bits_per_pixel <= 8) { | ||
52 | /* palettized */ | ||
53 | info->var.red.offset = 0; | ||
54 | info->var.red.length = info->var.bits_per_pixel; | ||
55 | info->var.red.msb_right = 0; | ||
56 | |||
57 | info->var.green.offset = 0; | ||
58 | info->var.green.length = info->var.bits_per_pixel; | ||
59 | info->var.green.msb_right = 0; | ||
60 | |||
61 | info->var.blue.offset = 0; | ||
62 | info->var.blue.length = info->var.bits_per_pixel; | ||
63 | info->var.blue.msb_right = 0; | ||
64 | |||
65 | info->var.transp.offset = 0; | ||
66 | info->var.transp.length = 0; | ||
67 | info->var.transp.msb_right = 0; | ||
68 | |||
69 | info->fix.visual = FB_VISUAL_PSEUDOCOLOR; | ||
70 | info->fix.line_length = info->var.xres_virtual / | ||
71 | (8/info->var.bits_per_pixel); | ||
72 | } else { | ||
73 | /* non-palettized */ | ||
74 | info->var.transp.offset = 0; | ||
75 | info->var.transp.length = 0; | ||
76 | info->var.transp.msb_right = 0; | ||
77 | |||
78 | if (info->var.bits_per_pixel == 16) { | ||
79 | /* RGB565 */ | ||
80 | info->var.red.offset = 11; | ||
81 | info->var.red.length = 5; | ||
82 | info->var.red.msb_right = 0; | ||
83 | info->var.green.offset = 5; | ||
84 | info->var.green.length = 6; | ||
85 | info->var.green.msb_right = 0; | ||
86 | info->var.blue.offset = 0; | ||
87 | info->var.blue.length = 5; | ||
88 | info->var.blue.msb_right = 0; | ||
89 | } else { | ||
90 | /* Equal depths per channel */ | ||
91 | info->var.red.offset = info->var.bits_per_pixel | ||
92 | * 2 / 3; | ||
93 | info->var.red.length = info->var.bits_per_pixel / 3; | ||
94 | info->var.red.msb_right = 0; | ||
95 | info->var.green.offset = info->var.bits_per_pixel / 3; | ||
96 | info->var.green.length = info->var.bits_per_pixel / 3; | ||
97 | info->var.green.msb_right = 0; | ||
98 | info->var.blue.offset = 0; | ||
99 | info->var.blue.length = info->var.bits_per_pixel / 3; | ||
100 | info->var.blue.msb_right = 0; | ||
101 | } | ||
102 | |||
103 | info->fix.visual = FB_VISUAL_TRUECOLOR; | ||
104 | info->fix.line_length = info->var.bits_per_pixel > 16 ? | ||
105 | info->var.xres_virtual << 2 : | ||
106 | info->var.xres_virtual << 1; | ||
107 | } | ||
108 | |||
109 | for (i = 0; i < 8; i++) { | ||
110 | if (bpp_values[i] == info->var.bits_per_pixel) { | ||
111 | reg_bpp = i; | ||
112 | continue; | ||
113 | } | ||
114 | } | ||
115 | |||
116 | control0 = readl(fbi->regbase) & ~0xf; | ||
117 | writel(0, fbi->regbase); | ||
118 | while (readl(fbi->regbase + 0x38) & 0x10) | ||
119 | /* wait */; | ||
120 | writel((((info->var.hsync_len - 1) & 0x3f) << 26) | ||
121 | | ((info->var.left_margin & 0xff) << 18) | ||
122 | | (((info->var.xres - 1) & 0x3ff) << 8) | ||
123 | | (info->var.right_margin & 0xff), fbi->regbase + 0x4); | ||
124 | writel((((info->var.vsync_len - 1) & 0x3f) << 26) | ||
125 | | ((info->var.upper_margin & 0xff) << 18) | ||
126 | | (((info->var.yres - 1) & 0x3ff) << 8) | ||
127 | | (info->var.lower_margin & 0xff), fbi->regbase + 0x8); | ||
128 | writel((((info->var.yres - 1) & 0x400) << 2) | ||
129 | | ((info->var.xres - 1) & 0x400), fbi->regbase + 0x10); | ||
130 | writel(0x80000000, fbi->regbase + 0x20); | ||
131 | writel(control0 | (reg_bpp << 1) | 0x100, fbi->regbase); | ||
132 | |||
133 | return 0; | ||
134 | } | ||
135 | |||
136 | static inline u_int chan_to_field(u_int chan, struct fb_bitfield *bf) | ||
137 | { | ||
138 | chan &= 0xffff; | ||
139 | chan >>= 16 - bf->length; | ||
140 | return chan << bf->offset; | ||
141 | } | ||
142 | |||
143 | static int vt8500lcd_setcolreg(unsigned regno, unsigned red, unsigned green, | ||
144 | unsigned blue, unsigned transp, | ||
145 | struct fb_info *info) { | ||
146 | struct vt8500lcd_info *fbi = to_vt8500lcd_info(info); | ||
147 | int ret = 1; | ||
148 | unsigned int val; | ||
149 | if (regno >= 256) | ||
150 | return -EINVAL; | ||
151 | |||
152 | if (info->var.grayscale) | ||
153 | red = green = blue = | ||
154 | (19595 * red + 38470 * green + 7471 * blue) >> 16; | ||
155 | |||
156 | switch (fbi->fb.fix.visual) { | ||
157 | case FB_VISUAL_TRUECOLOR: | ||
158 | if (regno < 16) { | ||
159 | u32 *pal = fbi->fb.pseudo_palette; | ||
160 | |||
161 | val = chan_to_field(red, &fbi->fb.var.red); | ||
162 | val |= chan_to_field(green, &fbi->fb.var.green); | ||
163 | val |= chan_to_field(blue, &fbi->fb.var.blue); | ||
164 | |||
165 | pal[regno] = val; | ||
166 | ret = 0; | ||
167 | } | ||
168 | break; | ||
169 | |||
170 | case FB_VISUAL_STATIC_PSEUDOCOLOR: | ||
171 | case FB_VISUAL_PSEUDOCOLOR: | ||
172 | writew((red & 0xf800) | ||
173 | | ((green >> 5) & 0x7e0) | ||
174 | | ((blue >> 11) & 0x1f), | ||
175 | fbi->palette_cpu + sizeof(u16) * regno); | ||
176 | break; | ||
177 | } | ||
178 | |||
179 | return ret; | ||
180 | } | ||
181 | |||
182 | static int vt8500lcd_ioctl(struct fb_info *info, unsigned int cmd, | ||
183 | unsigned long arg) | ||
184 | { | ||
185 | int ret = 0; | ||
186 | struct vt8500lcd_info *fbi = to_vt8500lcd_info(info); | ||
187 | |||
188 | if (cmd == FBIO_WAITFORVSYNC) { | ||
189 | /* Unmask End of Frame interrupt */ | ||
190 | writel(0xffffffff ^ (1 << 3), fbi->regbase + 0x3c); | ||
191 | ret = wait_event_interruptible_timeout(fbi->wait, | ||
192 | readl(fbi->regbase + 0x38) & (1 << 3), HZ / 10); | ||
193 | /* Mask back to reduce unwanted interrupt traffic */ | ||
194 | writel(0xffffffff, fbi->regbase + 0x3c); | ||
195 | if (ret < 0) | ||
196 | return ret; | ||
197 | if (ret == 0) | ||
198 | return -ETIMEDOUT; | ||
199 | } | ||
200 | |||
201 | return ret; | ||
202 | } | ||
203 | |||
204 | static int vt8500lcd_pan_display(struct fb_var_screeninfo *var, | ||
205 | struct fb_info *info) | ||
206 | { | ||
207 | unsigned pixlen = info->fix.line_length / info->var.xres_virtual; | ||
208 | unsigned off = pixlen * var->xoffset | ||
209 | + info->fix.line_length * var->yoffset; | ||
210 | struct vt8500lcd_info *fbi = to_vt8500lcd_info(info); | ||
211 | |||
212 | writel((1 << 31) | ||
213 | | (((var->xres_virtual - var->xres) * pixlen / 4) << 20) | ||
214 | | (off >> 2), fbi->regbase + 0x20); | ||
215 | return 0; | ||
216 | } | ||
217 | |||
218 | static struct fb_ops vt8500lcd_ops = { | ||
219 | .owner = THIS_MODULE, | ||
220 | .fb_set_par = vt8500lcd_set_par, | ||
221 | .fb_setcolreg = vt8500lcd_setcolreg, | ||
222 | .fb_fillrect = wmt_ge_fillrect, | ||
223 | .fb_copyarea = wmt_ge_copyarea, | ||
224 | .fb_imageblit = sys_imageblit, | ||
225 | .fb_sync = wmt_ge_sync, | ||
226 | .fb_ioctl = vt8500lcd_ioctl, | ||
227 | .fb_pan_display = vt8500lcd_pan_display, | ||
228 | }; | ||
229 | |||
230 | static irqreturn_t vt8500lcd_handle_irq(int irq, void *dev_id) | ||
231 | { | ||
232 | struct vt8500lcd_info *fbi = dev_id; | ||
233 | |||
234 | if (readl(fbi->regbase + 0x38) & (1 << 3)) | ||
235 | wake_up_interruptible(&fbi->wait); | ||
236 | |||
237 | writel(0xffffffff, fbi->regbase + 0x38); | ||
238 | return IRQ_HANDLED; | ||
239 | } | ||
240 | |||
241 | static int __devinit vt8500lcd_probe(struct platform_device *pdev) | ||
242 | { | ||
243 | struct vt8500lcd_info *fbi; | ||
244 | struct resource *res; | ||
245 | struct vt8500fb_platform_data *pdata = pdev->dev.platform_data; | ||
246 | void *addr; | ||
247 | int irq, ret; | ||
248 | |||
249 | ret = -ENOMEM; | ||
250 | fbi = NULL; | ||
251 | |||
252 | fbi = kzalloc(sizeof(struct vt8500lcd_info) + sizeof(u32) * 16, | ||
253 | GFP_KERNEL); | ||
254 | if (!fbi) { | ||
255 | dev_err(&pdev->dev, "Failed to initialize framebuffer device\n"); | ||
256 | ret = -ENOMEM; | ||
257 | goto failed; | ||
258 | } | ||
259 | |||
260 | strcpy(fbi->fb.fix.id, "VT8500 LCD"); | ||
261 | |||
262 | fbi->fb.fix.type = FB_TYPE_PACKED_PIXELS; | ||
263 | fbi->fb.fix.xpanstep = 0; | ||
264 | fbi->fb.fix.ypanstep = 1; | ||
265 | fbi->fb.fix.ywrapstep = 0; | ||
266 | fbi->fb.fix.accel = FB_ACCEL_NONE; | ||
267 | |||
268 | fbi->fb.var.nonstd = 0; | ||
269 | fbi->fb.var.activate = FB_ACTIVATE_NOW; | ||
270 | fbi->fb.var.height = -1; | ||
271 | fbi->fb.var.width = -1; | ||
272 | fbi->fb.var.vmode = FB_VMODE_NONINTERLACED; | ||
273 | |||
274 | fbi->fb.fbops = &vt8500lcd_ops; | ||
275 | fbi->fb.flags = FBINFO_DEFAULT | ||
276 | | FBINFO_HWACCEL_COPYAREA | ||
277 | | FBINFO_HWACCEL_FILLRECT | ||
278 | | FBINFO_HWACCEL_YPAN | ||
279 | | FBINFO_VIRTFB | ||
280 | | FBINFO_PARTIAL_PAN_OK; | ||
281 | fbi->fb.node = -1; | ||
282 | |||
283 | addr = fbi; | ||
284 | addr = addr + sizeof(struct vt8500lcd_info); | ||
285 | fbi->fb.pseudo_palette = addr; | ||
286 | |||
287 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
288 | if (res == NULL) { | ||
289 | dev_err(&pdev->dev, "no I/O memory resource defined\n"); | ||
290 | ret = -ENODEV; | ||
291 | goto failed_fbi; | ||
292 | } | ||
293 | |||
294 | res = request_mem_region(res->start, resource_size(res), "vt8500lcd"); | ||
295 | if (res == NULL) { | ||
296 | dev_err(&pdev->dev, "failed to request I/O memory\n"); | ||
297 | ret = -EBUSY; | ||
298 | goto failed_fbi; | ||
299 | } | ||
300 | |||
301 | fbi->regbase = ioremap(res->start, resource_size(res)); | ||
302 | if (fbi->regbase == NULL) { | ||
303 | dev_err(&pdev->dev, "failed to map I/O memory\n"); | ||
304 | ret = -EBUSY; | ||
305 | goto failed_free_res; | ||
306 | } | ||
307 | |||
308 | fbi->fb.fix.smem_start = pdata->video_mem_phys; | ||
309 | fbi->fb.fix.smem_len = pdata->video_mem_len; | ||
310 | fbi->fb.screen_base = pdata->video_mem_virt; | ||
311 | |||
312 | fbi->palette_size = PAGE_ALIGN(512); | ||
313 | fbi->palette_cpu = dma_alloc_coherent(&pdev->dev, | ||
314 | fbi->palette_size, | ||
315 | &fbi->palette_phys, | ||
316 | GFP_KERNEL); | ||
317 | if (fbi->palette_cpu == NULL) { | ||
318 | dev_err(&pdev->dev, "Failed to allocate palette buffer\n"); | ||
319 | ret = -ENOMEM; | ||
320 | goto failed_free_io; | ||
321 | } | ||
322 | |||
323 | irq = platform_get_irq(pdev, 0); | ||
324 | if (irq < 0) { | ||
325 | dev_err(&pdev->dev, "no IRQ defined\n"); | ||
326 | ret = -ENODEV; | ||
327 | goto failed_free_palette; | ||
328 | } | ||
329 | |||
330 | ret = request_irq(irq, vt8500lcd_handle_irq, IRQF_DISABLED, "LCD", fbi); | ||
331 | if (ret) { | ||
332 | dev_err(&pdev->dev, "request_irq failed: %d\n", ret); | ||
333 | ret = -EBUSY; | ||
334 | goto failed_free_palette; | ||
335 | } | ||
336 | |||
337 | init_waitqueue_head(&fbi->wait); | ||
338 | |||
339 | if (fb_alloc_cmap(&fbi->fb.cmap, 256, 0) < 0) { | ||
340 | dev_err(&pdev->dev, "Failed to allocate color map\n"); | ||
341 | ret = -ENOMEM; | ||
342 | goto failed_free_irq; | ||
343 | } | ||
344 | |||
345 | fb_videomode_to_var(&fbi->fb.var, &pdata->mode); | ||
346 | fbi->fb.var.bits_per_pixel = pdata->bpp; | ||
347 | fbi->fb.var.xres_virtual = pdata->xres_virtual; | ||
348 | fbi->fb.var.yres_virtual = pdata->yres_virtual; | ||
349 | |||
350 | ret = vt8500lcd_set_par(&fbi->fb); | ||
351 | if (ret) { | ||
352 | dev_err(&pdev->dev, "Failed to set parameters\n"); | ||
353 | goto failed_free_cmap; | ||
354 | } | ||
355 | |||
356 | writel(fbi->fb.fix.smem_start >> 22, fbi->regbase + 0x1c); | ||
357 | writel((fbi->palette_phys & 0xfffffe00) | 1, fbi->regbase + 0x18); | ||
358 | |||
359 | platform_set_drvdata(pdev, fbi); | ||
360 | |||
361 | ret = register_framebuffer(&fbi->fb); | ||
362 | if (ret < 0) { | ||
363 | dev_err(&pdev->dev, | ||
364 | "Failed to register framebuffer device: %d\n", ret); | ||
365 | goto failed_free_cmap; | ||
366 | } | ||
367 | |||
368 | /* | ||
369 | * Ok, now enable the LCD controller | ||
370 | */ | ||
371 | writel(readl(fbi->regbase) | 1, fbi->regbase); | ||
372 | |||
373 | return 0; | ||
374 | |||
375 | failed_free_cmap: | ||
376 | if (fbi->fb.cmap.len) | ||
377 | fb_dealloc_cmap(&fbi->fb.cmap); | ||
378 | failed_free_irq: | ||
379 | free_irq(irq, fbi); | ||
380 | failed_free_palette: | ||
381 | dma_free_coherent(&pdev->dev, fbi->palette_size, | ||
382 | fbi->palette_cpu, fbi->palette_phys); | ||
383 | failed_free_io: | ||
384 | iounmap(fbi->regbase); | ||
385 | failed_free_res: | ||
386 | release_mem_region(res->start, resource_size(res)); | ||
387 | failed_fbi: | ||
388 | platform_set_drvdata(pdev, NULL); | ||
389 | kfree(fbi); | ||
390 | failed: | ||
391 | return ret; | ||
392 | } | ||
393 | |||
394 | static int __devexit vt8500lcd_remove(struct platform_device *pdev) | ||
395 | { | ||
396 | struct vt8500lcd_info *fbi = platform_get_drvdata(pdev); | ||
397 | struct resource *res; | ||
398 | int irq; | ||
399 | |||
400 | unregister_framebuffer(&fbi->fb); | ||
401 | |||
402 | writel(0, fbi->regbase); | ||
403 | |||
404 | if (fbi->fb.cmap.len) | ||
405 | fb_dealloc_cmap(&fbi->fb.cmap); | ||
406 | |||
407 | irq = platform_get_irq(pdev, 0); | ||
408 | free_irq(irq, fbi); | ||
409 | |||
410 | dma_free_coherent(&pdev->dev, fbi->palette_size, | ||
411 | fbi->palette_cpu, fbi->palette_phys); | ||
412 | |||
413 | iounmap(fbi->regbase); | ||
414 | |||
415 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
416 | release_mem_region(res->start, resource_size(res)); | ||
417 | |||
418 | kfree(fbi); | ||
419 | |||
420 | return 0; | ||
421 | } | ||
422 | |||
423 | static struct platform_driver vt8500lcd_driver = { | ||
424 | .probe = vt8500lcd_probe, | ||
425 | .remove = __devexit_p(vt8500lcd_remove), | ||
426 | .driver = { | ||
427 | .owner = THIS_MODULE, | ||
428 | .name = "vt8500-lcd", | ||
429 | }, | ||
430 | }; | ||
431 | |||
432 | static int __init vt8500lcd_init(void) | ||
433 | { | ||
434 | return platform_driver_register(&vt8500lcd_driver); | ||
435 | } | ||
436 | |||
437 | static void __exit vt8500lcd_exit(void) | ||
438 | { | ||
439 | platform_driver_unregister(&vt8500lcd_driver); | ||
440 | } | ||
441 | |||
442 | module_init(vt8500lcd_init); | ||
443 | module_exit(vt8500lcd_exit); | ||
444 | |||
445 | MODULE_AUTHOR("Alexey Charkov <alchark@gmail.com>"); | ||
446 | MODULE_DESCRIPTION("LCD controller driver for VIA VT8500"); | ||
447 | MODULE_LICENSE("GPL"); | ||