aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/video/s3c2410fb.c
diff options
context:
space:
mode:
authorArnaud Patard <arnaud.patard@rtp-net.org>2005-09-09 16:10:07 -0400
committerLinus Torvalds <torvalds@g5.osdl.org>2005-09-09 17:03:42 -0400
commit20fd5767689124a920c1deb9c380304e082f026c (patch)
treefe779116d39a1612c80f414f0add8ed2893041d9 /drivers/video/s3c2410fb.c
parent3b4abffbadf728996fb9243b4af1df48dd771e86 (diff)
[PATCH] s3c2410fb: ARM S3C2410 framebuffer driver
This set of two patches add support for the framebuffer of the Samsung S3C2410 ARM SoC. This driver was started about one year ago and is now used on iPAQ h1930/h1940, Acer n30 and probably other s3c2410-based machines I'm not aware of. I've also heard yesterday that it's working also on iPAQ rx3715/rx3115 (s3c2440-based machines). Signed-Off-By: Arnaud Patard <arnaud.patard@rtp-net.org> Signed-off-by: Antonino Daplas <adaplas@pol.net> Signed-off-by: Ben Dooks <ben@trinity.fluff.org> Cc: Russell King <rmk@arm.linux.org.uk> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Diffstat (limited to 'drivers/video/s3c2410fb.c')
-rw-r--r--drivers/video/s3c2410fb.c915
1 files changed, 915 insertions, 0 deletions
diff --git a/drivers/video/s3c2410fb.c b/drivers/video/s3c2410fb.c
new file mode 100644
index 00000000000..00c0223a352
--- /dev/null
+++ b/drivers/video/s3c2410fb.c
@@ -0,0 +1,915 @@
1/*
2 * linux/drivers/video/s3c2410fb.c
3 * Copyright (c) Arnaud Patard, Ben Dooks
4 *
5 * This file is subject to the terms and conditions of the GNU General Public
6 * License. See the file COPYING in the main directory of this archive for
7 * more details.
8 *
9 * S3C2410 LCD Controller Frame Buffer Driver
10 * based on skeletonfb.c, sa1100fb.c and others
11 *
12 * ChangeLog
13 * 2005-04-07: Arnaud Patard <arnaud.patard@rtp-net.org>
14 * - u32 state -> pm_message_t state
15 * - S3C2410_{VA,SZ}_LCD -> S3C24XX
16 *
17 * 2005-03-15: Arnaud Patard <arnaud.patard@rtp-net.org>
18 * - Removed the ioctl
19 * - use readl/writel instead of __raw_writel/__raw_readl
20 *
21 * 2004-12-04: Arnaud Patard <arnaud.patard@rtp-net.org>
22 * - Added the possibility to set on or off the
23 * debugging mesaages
24 * - Replaced 0 and 1 by on or off when reading the
25 * /sys files
26 *
27 * 2005-03-23: Ben Dooks <ben-linux@fluff.org>
28 * - added non 16bpp modes
29 * - updated platform information for range of x/y/bpp
30 * - add code to ensure palette is written correctly
31 * - add pixel clock divisor control
32 *
33 * 2004-11-11: Arnaud Patard <arnaud.patard@rtp-net.org>
34 * - Removed the use of currcon as it no more exist
35 * - Added LCD power sysfs interface
36 *
37 * 2004-11-03: Ben Dooks <ben-linux@fluff.org>
38 * - minor cleanups
39 * - add suspend/resume support
40 * - s3c2410fb_setcolreg() not valid in >8bpp modes
41 * - removed last CONFIG_FB_S3C2410_FIXED
42 * - ensure lcd controller stopped before cleanup
43 * - added sysfs interface for backlight power
44 * - added mask for gpio configuration
45 * - ensured IRQs disabled during GPIO configuration
46 * - disable TPAL before enabling video
47 *
48 * 2004-09-20: Arnaud Patard <arnaud.patard@rtp-net.org>
49 * - Suppress command line options
50 *
51 * 2004-09-15: Arnaud Patard <arnaud.patard@rtp-net.org>
52 * - code cleanup
53 *
54 * 2004-09-07: Arnaud Patard <arnaud.patard@rtp-net.org>
55 * - Renamed from h1940fb.c to s3c2410fb.c
56 * - Add support for different devices
57 * - Backlight support
58 *
59 * 2004-09-05: Herbert Pötzl <herbert@13thfloor.at>
60 * - added clock (de-)allocation code
61 * - added fixem fbmem option
62 *
63 * 2004-07-27: Arnaud Patard <arnaud.patard@rtp-net.org>
64 * - code cleanup
65 * - added a forgotten return in h1940fb_init
66 *
67 * 2004-07-19: Herbert Pötzl <herbert@13thfloor.at>
68 * - code cleanup and extended debugging
69 *
70 * 2004-07-15: Arnaud Patard <arnaud.patard@rtp-net.org>
71 * - First version
72 */
73
74#include <linux/module.h>
75#include <linux/kernel.h>
76#include <linux/errno.h>
77#include <linux/string.h>
78#include <linux/mm.h>
79#include <linux/tty.h>
80#include <linux/slab.h>
81#include <linux/delay.h>
82#include <linux/fb.h>
83#include <linux/init.h>
84#include <linux/dma-mapping.h>
85#include <linux/string.h>
86#include <linux/interrupt.h>
87#include <linux/workqueue.h>
88#include <linux/wait.h>
89
90#include <asm/io.h>
91#include <asm/uaccess.h>
92#include <asm/div64.h>
93
94#include <asm/mach/map.h>
95#include <asm/arch/regs-lcd.h>
96#include <asm/arch/regs-gpio.h>
97#include <asm/arch/fb.h>
98#include <asm/hardware/clock.h>
99
100#ifdef CONFIG_PM
101#include <linux/pm.h>
102#endif
103
104#include "s3c2410fb.h"
105
106
107static struct s3c2410fb_mach_info *mach_info;
108
109/* Debugging stuff */
110#ifdef CONFIG_FB_S3C2410_DEBUG
111static int debug = 1;
112#else
113static int debug = 0;
114#endif
115
116#define dprintk(msg...) if (debug) { printk(KERN_DEBUG "s3c2410fb: " msg); }
117
118/* useful functions */
119
120/* s3c2410fb_set_lcdaddr
121 *
122 * initialise lcd controller address pointers
123*/
124
125static void s3c2410fb_set_lcdaddr(struct s3c2410fb_info *fbi)
126{
127 struct fb_var_screeninfo *var = &fbi->fb->var;
128 unsigned long saddr1, saddr2, saddr3;
129
130 saddr1 = fbi->fb->fix.smem_start >> 1;
131 saddr2 = fbi->fb->fix.smem_start;
132 saddr2 += (var->xres * var->yres * var->bits_per_pixel)/8;
133 saddr2>>= 1;
134
135 saddr3 = S3C2410_OFFSIZE(0) | S3C2410_PAGEWIDTH(var->xres);
136
137 dprintk("LCDSADDR1 = 0x%08lx\n", saddr1);
138 dprintk("LCDSADDR2 = 0x%08lx\n", saddr2);
139 dprintk("LCDSADDR3 = 0x%08lx\n", saddr3);
140
141 writel(saddr1, S3C2410_LCDSADDR1);
142 writel(saddr2, S3C2410_LCDSADDR2);
143 writel(saddr3, S3C2410_LCDSADDR3);
144}
145
146/* s3c2410fb_calc_pixclk()
147 *
148 * calculate divisor for clk->pixclk
149*/
150
151static unsigned int s3c2410fb_calc_pixclk(struct s3c2410fb_info *fbi,
152 unsigned long pixclk)
153{
154 unsigned long clk = clk_get_rate(fbi->clk);
155 unsigned long long div;
156
157 /* pixclk is in picoseoncds, our clock is in Hz
158 *
159 * Hz -> picoseconds is / 10^-12
160 */
161
162 div = (unsigned long long)clk * pixclk;
163 do_div(div,1000000UL);
164 do_div(div,1000000UL);
165
166 dprintk("pixclk %ld, divisor is %ld\n", pixclk, (long)div);
167 return div;
168}
169
170/*
171 * s3c2410fb_check_var():
172 * Get the video params out of 'var'. If a value doesn't fit, round it up,
173 * if it's too big, return -EINVAL.
174 *
175 */
176static int s3c2410fb_check_var(struct fb_var_screeninfo *var,
177 struct fb_info *info)
178{
179 struct s3c2410fb_info *fbi = info->par;
180
181 dprintk("check_var(var=%p, info=%p)\n", var, info);
182
183 /* validate x/y resolution */
184
185 if (var->yres > fbi->mach_info->yres.max)
186 var->yres = fbi->mach_info->yres.max;
187 else if (var->yres < fbi->mach_info->yres.min)
188 var->yres = fbi->mach_info->yres.min;
189
190 if (var->xres > fbi->mach_info->xres.max)
191 var->yres = fbi->mach_info->xres.max;
192 else if (var->xres < fbi->mach_info->xres.min)
193 var->xres = fbi->mach_info->xres.min;
194
195 /* validate bpp */
196
197 if (var->bits_per_pixel > fbi->mach_info->bpp.max)
198 var->bits_per_pixel = fbi->mach_info->bpp.max;
199 else if (var->bits_per_pixel < fbi->mach_info->bpp.min)
200 var->bits_per_pixel = fbi->mach_info->bpp.min;
201
202 /* set r/g/b positions */
203
204 if (var->bits_per_pixel == 16) {
205 var->red.offset = 11;
206 var->green.offset = 5;
207 var->blue.offset = 0;
208 var->red.length = 5;
209 var->green.length = 6;
210 var->blue.length = 5;
211 var->transp.length = 0;
212 } else {
213 var->red.length = var->bits_per_pixel;
214 var->red.offset = 0;
215 var->green.length = var->bits_per_pixel;
216 var->green.offset = 0;
217 var->blue.length = var->bits_per_pixel;
218 var->blue.offset = 0;
219 var->transp.length = 0;
220 }
221
222 return 0;
223}
224
225/* s3c2410fb_activate_var
226 *
227 * activate (set) the controller from the given framebuffer
228 * information
229*/
230
231static int s3c2410fb_activate_var(struct s3c2410fb_info *fbi,
232 struct fb_var_screeninfo *var)
233{
234 fbi->regs.lcdcon1 &= ~S3C2410_LCDCON1_MODEMASK;
235
236 dprintk("%s: var->xres = %d\n", __FUNCTION__, var->xres);
237 dprintk("%s: var->yres = %d\n", __FUNCTION__, var->yres);
238 dprintk("%s: var->bpp = %d\n", __FUNCTION__, var->bits_per_pixel);
239
240 switch (var->bits_per_pixel) {
241 case 1:
242 fbi->regs.lcdcon1 |= S3C2410_LCDCON1_TFT1BPP;
243 break;
244 case 2:
245 fbi->regs.lcdcon1 |= S3C2410_LCDCON1_TFT2BPP;
246 break;
247 case 4:
248 fbi->regs.lcdcon1 |= S3C2410_LCDCON1_TFT4BPP;
249 break;
250 case 8:
251 fbi->regs.lcdcon1 |= S3C2410_LCDCON1_TFT8BPP;
252 break;
253 case 16:
254 fbi->regs.lcdcon1 |= S3C2410_LCDCON1_TFT16BPP;
255 break;
256 }
257
258 /* check to see if we need to update sync/borders */
259
260 if (!fbi->mach_info->fixed_syncs) {
261 dprintk("setting vert: up=%d, low=%d, sync=%d\n",
262 var->upper_margin, var->lower_margin,
263 var->vsync_len);
264
265 dprintk("setting horz: lft=%d, rt=%d, sync=%d\n",
266 var->left_margin, var->right_margin,
267 var->hsync_len);
268
269 fbi->regs.lcdcon2 =
270 S3C2410_LCDCON2_VBPD(var->upper_margin - 1) |
271 S3C2410_LCDCON2_VFPD(var->lower_margin - 1) |
272 S3C2410_LCDCON2_VSPW(var->vsync_len - 1);
273
274 fbi->regs.lcdcon3 =
275 S3C2410_LCDCON3_HBPD(var->right_margin - 1) |
276 S3C2410_LCDCON3_HFPD(var->left_margin - 1);
277
278 fbi->regs.lcdcon4 &= ~S3C2410_LCDCON4_HSPW(0xff);
279 fbi->regs.lcdcon4 |= S3C2410_LCDCON4_HSPW(var->hsync_len - 1);
280 }
281
282 /* update X/Y info */
283
284 fbi->regs.lcdcon2 &= ~S3C2410_LCDCON2_LINEVAL(0x3ff);
285 fbi->regs.lcdcon2 |= S3C2410_LCDCON2_LINEVAL(var->yres - 1);
286
287 fbi->regs.lcdcon3 &= ~S3C2410_LCDCON3_HOZVAL(0x7ff);
288 fbi->regs.lcdcon3 |= S3C2410_LCDCON3_HOZVAL(var->xres - 1);
289
290 if (var->pixclock > 0) {
291 int clkdiv = s3c2410fb_calc_pixclk(fbi, var->pixclock);
292
293 clkdiv = (clkdiv / 2) -1;
294 if (clkdiv < 0)
295 clkdiv = 0;
296
297 fbi->regs.lcdcon1 &= ~S3C2410_LCDCON1_CLKVAL(0x3ff);
298 fbi->regs.lcdcon1 |= S3C2410_LCDCON1_CLKVAL(clkdiv);
299 }
300
301 /* write new registers */
302
303 dprintk("new register set:\n");
304 dprintk("lcdcon[1] = 0x%08lx\n", fbi->regs.lcdcon1);
305 dprintk("lcdcon[2] = 0x%08lx\n", fbi->regs.lcdcon2);
306 dprintk("lcdcon[3] = 0x%08lx\n", fbi->regs.lcdcon3);
307 dprintk("lcdcon[4] = 0x%08lx\n", fbi->regs.lcdcon4);
308 dprintk("lcdcon[5] = 0x%08lx\n", fbi->regs.lcdcon5);
309
310 writel(fbi->regs.lcdcon1 & ~S3C2410_LCDCON1_ENVID, S3C2410_LCDCON1);
311 writel(fbi->regs.lcdcon2, S3C2410_LCDCON2);
312 writel(fbi->regs.lcdcon3, S3C2410_LCDCON3);
313 writel(fbi->regs.lcdcon4, S3C2410_LCDCON4);
314 writel(fbi->regs.lcdcon5, S3C2410_LCDCON5);
315
316 /* set lcd address pointers */
317 s3c2410fb_set_lcdaddr(fbi);
318
319 writel(fbi->regs.lcdcon1, S3C2410_LCDCON1);
320}
321
322
323/*
324 * s3c2410fb_set_par - Optional function. Alters the hardware state.
325 * @info: frame buffer structure that represents a single frame buffer
326 *
327 */
328static int s3c2410fb_set_par(struct fb_info *info)
329{
330 struct s3c2410fb_info *fbi = info->par;
331 struct fb_var_screeninfo *var = &info->var;
332
333 if (var->bits_per_pixel == 16)
334 fbi->fb->fix.visual = FB_VISUAL_TRUECOLOR;
335 else
336 fbi->fb->fix.visual = FB_VISUAL_PSEUDOCOLOR;
337
338 fbi->fb->fix.line_length = (var->width*var->bits_per_pixel)/8;
339
340 /* activate this new configuration */
341
342 s3c2410fb_activate_var(fbi, var);
343 return 0;
344}
345
346static void schedule_palette_update(struct s3c2410fb_info *fbi,
347 unsigned int regno, unsigned int val)
348{
349 unsigned long flags;
350 unsigned long irqen;
351
352 local_irq_save(flags);
353
354 fbi->palette_buffer[regno] = val;
355
356 if (!fbi->palette_ready) {
357 fbi->palette_ready = 1;
358
359 /* enable IRQ */
360 irqen = readl(S3C2410_LCDINTMSK);
361 irqen &= ~S3C2410_LCDINT_FRSYNC;
362 writel(irqen, S3C2410_LCDINTMSK);
363 }
364
365 local_irq_restore(flags);
366}
367
368/* from pxafb.c */
369static inline unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf)
370{
371 chan &= 0xffff;
372 chan >>= 16 - bf->length;
373 return chan << bf->offset;
374}
375
376static int s3c2410fb_setcolreg(unsigned regno,
377 unsigned red, unsigned green, unsigned blue,
378 unsigned transp, struct fb_info *info)
379{
380 struct s3c2410fb_info *fbi = info->par;
381 unsigned int val;
382
383 /* dprintk("setcol: regno=%d, rgb=%d,%d,%d\n", regno, red, green, blue); */
384
385 switch (fbi->fb->fix.visual) {
386 case FB_VISUAL_TRUECOLOR:
387 /* true-colour, use pseuo-palette */
388
389 if (regno < 16) {
390 u32 *pal = fbi->fb->pseudo_palette;
391
392 val = chan_to_field(red, &fbi->fb->var.red);
393 val |= chan_to_field(green, &fbi->fb->var.green);
394 val |= chan_to_field(blue, &fbi->fb->var.blue);
395
396 pal[regno] = val;
397 }
398 break;
399
400 case FB_VISUAL_PSEUDOCOLOR:
401 if (regno < 256) {
402 /* currently assume RGB 5-6-5 mode */
403
404 val = ((red >> 0) & 0xf800);
405 val |= ((green >> 5) & 0x07e0);
406 val |= ((blue >> 11) & 0x001f);
407
408 writel(val, S3C2410_TFTPAL(regno));
409 schedule_palette_update(fbi, regno, val);
410 }
411
412 break;
413
414 default:
415 return 1; /* unknown type */
416 }
417
418 return 0;
419}
420
421
422/**
423 * s3c2410fb_blank
424 * @blank_mode: the blank mode we want.
425 * @info: frame buffer structure that represents a single frame buffer
426 *
427 * Blank the screen if blank_mode != 0, else unblank. Return 0 if
428 * blanking succeeded, != 0 if un-/blanking failed due to e.g. a
429 * video mode which doesn't support it. Implements VESA suspend
430 * and powerdown modes on hardware that supports disabling hsync/vsync:
431 * blank_mode == 2: suspend vsync
432 * blank_mode == 3: suspend hsync
433 * blank_mode == 4: powerdown
434 *
435 * Returns negative errno on error, or zero on success.
436 *
437 */
438static int s3c2410fb_blank(int blank_mode, struct fb_info *info)
439{
440 dprintk("blank(mode=%d, info=%p)\n", blank_mode, info);
441
442 if (mach_info == NULL)
443 return -EINVAL;
444
445 if (blank_mode == FB_BLANK_UNBLANK)
446 writel(0x0, S3C2410_TPAL);
447 else {
448 dprintk("setting TPAL to output 0x000000\n");
449 writel(S3C2410_TPAL_EN, S3C2410_TPAL);
450 }
451
452 return 0;
453}
454
455static int s3c2410fb_debug_show(struct device *dev, struct device_attribute *attr, char *buf)
456{
457 return snprintf(buf, PAGE_SIZE, "%s\n", debug ? "on" : "off");
458}
459static int s3c2410fb_debug_store(struct device *dev, struct device_attribute *attr,
460 const char *buf, size_t len)
461{
462 if (mach_info == NULL)
463 return -EINVAL;
464
465 if (len < 1)
466 return -EINVAL;
467
468 if (strnicmp(buf, "on", 2) == 0 ||
469 strnicmp(buf, "1", 1) == 0) {
470 debug = 1;
471 printk(KERN_DEBUG "s3c2410fb: Debug On");
472 } else if (strnicmp(buf, "off", 3) == 0 ||
473 strnicmp(buf, "0", 1) == 0) {
474 debug = 0;
475 printk(KERN_DEBUG "s3c2410fb: Debug Off");
476 } else {
477 return -EINVAL;
478 }
479
480 return len;
481}
482
483
484static DEVICE_ATTR(debug, 0666,
485 s3c2410fb_debug_show,
486 s3c2410fb_debug_store);
487
488static struct fb_ops s3c2410fb_ops = {
489 .owner = THIS_MODULE,
490 .fb_check_var = s3c2410fb_check_var,
491 .fb_set_par = s3c2410fb_set_par,
492 .fb_blank = s3c2410fb_blank,
493 .fb_setcolreg = s3c2410fb_setcolreg,
494 .fb_fillrect = cfb_fillrect,
495 .fb_copyarea = cfb_copyarea,
496 .fb_imageblit = cfb_imageblit,
497 .fb_cursor = soft_cursor,
498};
499
500
501/*
502 * s3c2410fb_map_video_memory():
503 * Allocates the DRAM memory for the frame buffer. This buffer is
504 * remapped into a non-cached, non-buffered, memory region to
505 * allow palette and pixel writes to occur without flushing the
506 * cache. Once this area is remapped, all virtual memory
507 * access to the video memory should occur at the new region.
508 */
509static int __init s3c2410fb_map_video_memory(struct s3c2410fb_info *fbi)
510{
511 dprintk("map_video_memory(fbi=%p)\n", fbi);
512
513 fbi->map_size = PAGE_ALIGN(fbi->fb->fix.smem_len + PAGE_SIZE);
514 fbi->map_cpu = dma_alloc_writecombine(fbi->dev, fbi->map_size,
515 &fbi->map_dma, GFP_KERNEL);
516
517 fbi->map_size = fbi->fb->fix.smem_len;
518
519 if (fbi->map_cpu) {
520 /* prevent initial garbage on screen */
521 dprintk("map_video_memory: clear %p:%08x\n",
522 fbi->map_cpu, fbi->map_size);
523 memset(fbi->map_cpu, 0xf0, fbi->map_size);
524
525 fbi->screen_dma = fbi->map_dma;
526 fbi->fb->screen_base = fbi->map_cpu;
527 fbi->fb->fix.smem_start = fbi->screen_dma;
528
529 dprintk("map_video_memory: dma=%08x cpu=%p size=%08x\n",
530 fbi->map_dma, fbi->map_cpu, fbi->fb->fix.smem_len);
531 }
532
533 return fbi->map_cpu ? 0 : -ENOMEM;
534}
535
536static inline void s3c2410fb_unmap_video_memory(struct s3c2410fb_info *fbi)
537{
538 dma_free_writecombine(fbi->dev,fbi->map_size,fbi->map_cpu, fbi->map_dma);
539}
540
541static inline void modify_gpio(void __iomem *reg,
542 unsigned long set, unsigned long mask)
543{
544 unsigned long tmp;
545
546 tmp = readl(reg) & ~mask;
547 writel(tmp | set, reg);
548}
549
550
551/*
552 * s3c2410fb_init_registers - Initialise all LCD-related registers
553 */
554
555int s3c2410fb_init_registers(struct s3c2410fb_info *fbi)
556{
557 unsigned long flags;
558
559 /* Initialise LCD with values from haret */
560
561 local_irq_save(flags);
562
563 /* modify the gpio(s) with interrupts set (bjd) */
564
565 modify_gpio(S3C2410_GPCUP, mach_info->gpcup, mach_info->gpcup_mask);
566 modify_gpio(S3C2410_GPCCON, mach_info->gpccon, mach_info->gpccon_mask);
567 modify_gpio(S3C2410_GPDUP, mach_info->gpdup, mach_info->gpdup_mask);
568 modify_gpio(S3C2410_GPDCON, mach_info->gpdcon, mach_info->gpdcon_mask);
569
570 local_irq_restore(flags);
571
572 writel(fbi->regs.lcdcon1, S3C2410_LCDCON1);
573 writel(fbi->regs.lcdcon2, S3C2410_LCDCON2);
574 writel(fbi->regs.lcdcon3, S3C2410_LCDCON3);
575 writel(fbi->regs.lcdcon4, S3C2410_LCDCON4);
576 writel(fbi->regs.lcdcon5, S3C2410_LCDCON5);
577
578 s3c2410fb_set_lcdaddr(fbi);
579
580 dprintk("LPCSEL = 0x%08lx\n", mach_info->lpcsel);
581 writel(mach_info->lpcsel, S3C2410_LPCSEL);
582
583 dprintk("replacing TPAL %08x\n", readl(S3C2410_TPAL));
584
585 /* ensure temporary palette disabled */
586 writel(0x00, S3C2410_TPAL);
587
588 /* Enable video by setting the ENVID bit to 1 */
589 fbi->regs.lcdcon1 |= S3C2410_LCDCON1_ENVID;
590 writel(fbi->regs.lcdcon1, S3C2410_LCDCON1);
591 return 0;
592}
593
594static void s3c2410fb_write_palette(struct s3c2410fb_info *fbi)
595{
596 unsigned int i;
597 unsigned long ent;
598
599 fbi->palette_ready = 0;
600
601 for (i = 0; i < 256; i++) {
602 if ((ent = fbi->palette_buffer[i]) == PALETTE_BUFF_CLEAR)
603 continue;
604
605 writel(ent, S3C2410_TFTPAL(i));
606
607 /* it seems the only way to know exactly
608 * if the palette wrote ok, is to check
609 * to see if the value verifies ok
610 */
611
612 if (readw(S3C2410_TFTPAL(i)) == ent)
613 fbi->palette_buffer[i] = PALETTE_BUFF_CLEAR;
614 else
615 fbi->palette_ready = 1; /* retry */
616 }
617}
618
619static irqreturn_t s3c2410fb_irq(int irq, void *dev_id, struct pt_regs *r)
620{
621 struct s3c2410fb_info *fbi = dev_id;
622 unsigned long lcdirq = readl(S3C2410_LCDINTPND);
623
624 if (lcdirq & S3C2410_LCDINT_FRSYNC) {
625 if (fbi->palette_ready)
626 s3c2410fb_write_palette(fbi);
627
628 writel(S3C2410_LCDINT_FRSYNC, S3C2410_LCDINTPND);
629 writel(S3C2410_LCDINT_FRSYNC, S3C2410_LCDSRCPND);
630 }
631
632 return IRQ_HANDLED;
633}
634
635static char driver_name[]="s3c2410fb";
636
637int __init s3c2410fb_probe(struct device *dev)
638{
639 struct s3c2410fb_info *info;
640 struct fb_info *fbinfo;
641 struct platform_device *pdev = to_platform_device(dev);
642 struct s3c2410fb_hw *mregs;
643 int ret;
644 int irq;
645 int i;
646
647 mach_info = dev->platform_data;
648 if (mach_info == NULL) {
649 dev_err(dev,"no platform data for lcd, cannot attach\n");
650 return -EINVAL;
651 }
652
653 mregs = &mach_info->regs;
654
655 irq = platform_get_irq(pdev, 0);
656 if (irq < 0) {
657 dev_err(dev, "no irq for device\n");
658 return -ENOENT;
659 }
660
661 fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), dev);
662 if (!fbinfo) {
663 return -ENOMEM;
664 }
665
666
667 info = fbinfo->par;
668 info->fb = fbinfo;
669 dev_set_drvdata(dev, fbinfo);
670
671 s3c2410fb_init_registers(info);
672
673 dprintk("devinit\n");
674
675 strcpy(fbinfo->fix.id, driver_name);
676
677 memcpy(&info->regs, &mach_info->regs, sizeof(info->regs));
678
679 info->mach_info = dev->platform_data;
680
681 fbinfo->fix.type = FB_TYPE_PACKED_PIXELS;
682 fbinfo->fix.type_aux = 0;
683 fbinfo->fix.xpanstep = 0;
684 fbinfo->fix.ypanstep = 0;
685 fbinfo->fix.ywrapstep = 0;
686 fbinfo->fix.accel = FB_ACCEL_NONE;
687
688 fbinfo->var.nonstd = 0;
689 fbinfo->var.activate = FB_ACTIVATE_NOW;
690 fbinfo->var.height = mach_info->height;
691 fbinfo->var.width = mach_info->width;
692 fbinfo->var.accel_flags = 0;
693 fbinfo->var.vmode = FB_VMODE_NONINTERLACED;
694
695 fbinfo->fbops = &s3c2410fb_ops;
696 fbinfo->flags = FBINFO_FLAG_DEFAULT;
697 fbinfo->pseudo_palette = &info->pseudo_pal;
698
699 fbinfo->var.xres = mach_info->xres.defval;
700 fbinfo->var.xres_virtual = mach_info->xres.defval;
701 fbinfo->var.yres = mach_info->yres.defval;
702 fbinfo->var.yres_virtual = mach_info->yres.defval;
703 fbinfo->var.bits_per_pixel = mach_info->bpp.defval;
704
705 fbinfo->var.upper_margin = S3C2410_LCDCON2_GET_VBPD(mregs->lcdcon2) +1;
706 fbinfo->var.lower_margin = S3C2410_LCDCON2_GET_VFPD(mregs->lcdcon2) +1;
707 fbinfo->var.vsync_len = S3C2410_LCDCON2_GET_VSPW(mregs->lcdcon2) + 1;
708
709 fbinfo->var.left_margin = S3C2410_LCDCON3_GET_HFPD(mregs->lcdcon3) + 1;
710 fbinfo->var.right_margin = S3C2410_LCDCON3_GET_HBPD(mregs->lcdcon3) + 1;
711 fbinfo->var.hsync_len = S3C2410_LCDCON4_GET_HSPW(mregs->lcdcon4) + 1;
712
713 fbinfo->var.red.offset = 11;
714 fbinfo->var.green.offset = 5;
715 fbinfo->var.blue.offset = 0;
716 fbinfo->var.transp.offset = 0;
717 fbinfo->var.red.length = 5;
718 fbinfo->var.green.length = 6;
719 fbinfo->var.blue.length = 5;
720 fbinfo->var.transp.length = 0;
721 fbinfo->fix.smem_len = mach_info->xres.max *
722 mach_info->yres.max *
723 mach_info->bpp.max / 8;
724
725 for (i = 0; i < 256; i++)
726 info->palette_buffer[i] = PALETTE_BUFF_CLEAR;
727
728 if (!request_mem_region((unsigned long)S3C24XX_VA_LCD, SZ_1M, "s3c2410-lcd")) {
729 ret = -EBUSY;
730 goto dealloc_fb;
731 }
732
733
734 dprintk("got LCD region\n");
735
736 ret = request_irq(irq, s3c2410fb_irq, SA_INTERRUPT, pdev->name, info);
737 if (ret) {
738 dev_err(dev, "cannot get irq %d - err %d\n", irq, ret);
739 ret = -EBUSY;
740 goto release_mem;
741 }
742
743 info->clk = clk_get(NULL, "lcd");
744 if (!info->clk || IS_ERR(info->clk)) {
745 printk(KERN_ERR "failed to get lcd clock source\n");
746 ret = -ENOENT;
747 goto release_irq;
748 }
749
750 clk_use(info->clk);
751 clk_enable(info->clk);
752 dprintk("got and enabled clock\n");
753
754 msleep(1);
755
756 /* Initialize video memory */
757 ret = s3c2410fb_map_video_memory(info);
758 if (ret) {
759 printk( KERN_ERR "Failed to allocate video RAM: %d\n", ret);
760 ret = -ENOMEM;
761 goto release_clock;
762 }
763 dprintk("got video memory\n");
764
765 ret = s3c2410fb_init_registers(info);
766
767 ret = s3c2410fb_check_var(&fbinfo->var, fbinfo);
768
769 ret = register_framebuffer(fbinfo);
770 if (ret < 0) {
771 printk(KERN_ERR "Failed to register framebuffer device: %d\n", ret);
772 goto free_video_memory;
773 }
774
775 /* create device files */
776 device_create_file(dev, &dev_attr_debug);
777
778 printk(KERN_INFO "fb%d: %s frame buffer device\n",
779 fbinfo->node, fbinfo->fix.id);
780
781 return 0;
782
783free_video_memory:
784 s3c2410fb_unmap_video_memory(info);
785release_clock:
786 clk_disable(info->clk);
787 clk_unuse(info->clk);
788 clk_put(info->clk);
789release_irq:
790 free_irq(irq,info);
791release_mem:
792 release_mem_region((unsigned long)S3C24XX_VA_LCD, S3C24XX_SZ_LCD);
793dealloc_fb:
794 framebuffer_release(fbinfo);
795 return ret;
796}
797
798/* s3c2410fb_stop_lcd
799 *
800 * shutdown the lcd controller
801*/
802
803static void s3c2410fb_stop_lcd(void)
804{
805 unsigned long flags;
806 unsigned long tmp;
807
808 local_irq_save(flags);
809
810 tmp = readl(S3C2410_LCDCON1);
811 writel(tmp & ~S3C2410_LCDCON1_ENVID, S3C2410_LCDCON1);
812
813 local_irq_restore(flags);
814}
815
816/*
817 * Cleanup
818 */
819static int s3c2410fb_remove(struct device *dev)
820{
821 struct platform_device *pdev = to_platform_device(dev);
822 struct fb_info *fbinfo = dev_get_drvdata(dev);
823 struct s3c2410fb_info *info = fbinfo->par;
824 int irq;
825
826 s3c2410fb_stop_lcd();
827 msleep(1);
828
829 s3c2410fb_unmap_video_memory(info);
830
831 if (info->clk) {
832 clk_disable(info->clk);
833 clk_unuse(info->clk);
834 clk_put(info->clk);
835 info->clk = NULL;
836 }
837
838 irq = platform_get_irq(pdev, 0);
839 free_irq(irq,info);
840 release_mem_region((unsigned long)S3C24XX_VA_LCD, S3C24XX_SZ_LCD);
841 unregister_framebuffer(fbinfo);
842
843 return 0;
844}
845
846#ifdef CONFIG_PM
847
848/* suspend and resume support for the lcd controller */
849
850static int s3c2410fb_suspend(struct device *dev, pm_message_t state, u32 level)
851{
852 struct fb_info *fbinfo = dev_get_drvdata(dev);
853 struct s3c2410fb_info *info = fbinfo->par;
854
855 if (level == SUSPEND_DISABLE || level == SUSPEND_POWER_DOWN) {
856 s3c2410fb_stop_lcd();
857
858 /* sleep before disabling the clock, we need to ensure
859 * the LCD DMA engine is not going to get back on the bus
860 * before the clock goes off again (bjd) */
861
862 msleep(1);
863 clk_disable(info->clk);
864 }
865
866 return 0;
867}
868
869static int s3c2410fb_resume(struct device *dev, u32 level)
870{
871 struct fb_info *fbinfo = dev_get_drvdata(dev);
872 struct s3c2410fb_info *info = fbinfo->par;
873
874 if (level == RESUME_ENABLE) {
875 clk_enable(info->clk);
876 msleep(1);
877
878 s3c2410fb_init_registers(info);
879
880 }
881
882 return 0;
883}
884
885#else
886#define s3c2410fb_suspend NULL
887#define s3c2410fb_resume NULL
888#endif
889
890static struct device_driver s3c2410fb_driver = {
891 .name = "s3c2410-lcd",
892 .bus = &platform_bus_type,
893 .probe = s3c2410fb_probe,
894 .suspend = s3c2410fb_suspend,
895 .resume = s3c2410fb_resume,
896 .remove = s3c2410fb_remove
897};
898
899int __devinit s3c2410fb_init(void)
900{
901 return driver_register(&s3c2410fb_driver);
902}
903
904static void __exit s3c2410fb_cleanup(void)
905{
906 driver_unregister(&s3c2410fb_driver);
907}
908
909
910module_init(s3c2410fb_init);
911module_exit(s3c2410fb_cleanup);
912
913MODULE_AUTHOR("Arnaud Patard <arnaud.patard@rtp-net.org>, Ben Dooks <ben-linux@fluff.org>");
914MODULE_DESCRIPTION("Framebuffer driver for the s3c2410");
915MODULE_LICENSE("GPL");