s3c2410fb: fix missing registers offset
[safe/jmp/linux-2.6] / drivers / video / s3c2410fb.c
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/slab.h>
80 #include <linux/delay.h>
81 #include <linux/fb.h>
82 #include <linux/init.h>
83 #include <linux/dma-mapping.h>
84 #include <linux/interrupt.h>
85 #include <linux/workqueue.h>
86 #include <linux/wait.h>
87 #include <linux/platform_device.h>
88 #include <linux/clk.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
99 #ifdef CONFIG_PM
100 #include <linux/pm.h>
101 #endif
102
103 #include "s3c2410fb.h"
104
105 static struct s3c2410fb_mach_info *mach_info;
106
107 /* Debugging stuff */
108 #ifdef CONFIG_FB_S3C2410_DEBUG
109 static int debug        = 1;
110 #else
111 static int debug        = 0;
112 #endif
113
114 #define dprintk(msg...) if (debug) { printk(KERN_DEBUG "s3c2410fb: " msg); }
115
116 /* useful functions */
117
118 /* s3c2410fb_set_lcdaddr
119  *
120  * initialise lcd controller address pointers
121  */
122 static void s3c2410fb_set_lcdaddr(struct fb_info *info)
123 {
124         unsigned long saddr1, saddr2, saddr3;
125         int line_length = info->var.xres * info->var.bits_per_pixel;
126         struct s3c2410fb_info *fbi = info->par;
127         void __iomem *regs = fbi->io;
128
129         saddr1  = info->fix.smem_start >> 1;
130         saddr2  = info->fix.smem_start;
131         saddr2 += (line_length * info->var.yres) / 8;
132         saddr2 >>= 1;
133
134         saddr3 = S3C2410_OFFSIZE(0) |
135                  S3C2410_PAGEWIDTH((line_length / 16) & 0x3ff);
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, regs + S3C2410_LCDSADDR1);
142         writel(saddr2, regs + S3C2410_LCDSADDR2);
143         writel(saddr3, regs + S3C2410_LCDSADDR3);
144 }
145
146 /* s3c2410fb_calc_pixclk()
147  *
148  * calculate divisor for clk->pixclk
149  */
150 static unsigned int s3c2410fb_calc_pixclk(struct s3c2410fb_info *fbi,
151                                           unsigned long pixclk)
152 {
153         unsigned long clk = clk_get_rate(fbi->clk);
154         unsigned long long div;
155
156         /* pixclk is in picoseoncds, our clock is in Hz
157          *
158          * Hz -> picoseconds is / 10^-12
159          */
160
161         div = (unsigned long long)clk * pixclk;
162         do_div(div, 1000000UL);
163         do_div(div, 1000000UL);
164
165         dprintk("pixclk %ld, divisor is %ld\n", pixclk, (long)div);
166         return div;
167 }
168
169 /*
170  *      s3c2410fb_check_var():
171  *      Get the video params out of 'var'. If a value doesn't fit, round it up,
172  *      if it's too big, return -EINVAL.
173  *
174  */
175 static int s3c2410fb_check_var(struct fb_var_screeninfo *var,
176                                struct fb_info *info)
177 {
178         struct s3c2410fb_info *fbi = info->par;
179         struct s3c2410fb_mach_info *mach_info = fbi->mach_info;
180         struct s3c2410fb_display *display = NULL;
181         unsigned i;
182
183         dprintk("check_var(var=%p, info=%p)\n", var, info);
184
185         /* validate x/y resolution */
186
187         for (i = 0; i < mach_info->num_displays; i++)
188                 if (var->yres == mach_info->displays[i].yres &&
189                     var->xres == mach_info->displays[i].xres &&
190                     var->bits_per_pixel == mach_info->displays[i].bpp) {
191                         display = mach_info->displays + i;
192                         fbi->current_display = i;
193                         break;
194                 }
195
196         if (!display) {
197                 dprintk("wrong resolution or depth %dx%d at %d bpp\n",
198                         var->xres, var->yres, var->bits_per_pixel);
199                 return -EINVAL;
200         }
201
202         /* it is always the size as the display */
203         var->xres_virtual = display->xres;
204         var->yres_virtual = display->yres;
205
206         /* copy lcd settings */
207         var->left_margin = display->left_margin;
208         var->right_margin = display->right_margin;
209
210         var->transp.offset = 0;
211         var->transp.length = 0;
212         /* set r/g/b positions */
213         switch (var->bits_per_pixel) {
214         case 1:
215         case 2:
216         case 4:
217                 var->red.offset = 0;
218                 var->red.length = var->bits_per_pixel;
219                 var->green      = var->red;
220                 var->blue       = var->red;
221                 break;
222         case 8:
223                 if (display->type != S3C2410_LCDCON1_TFT) {
224                         /* 8 bpp 332 */
225                         var->red.length         = 3;
226                         var->red.offset         = 5;
227                         var->green.length       = 3;
228                         var->green.offset       = 2;
229                         var->blue.length        = 2;
230                         var->blue.offset        = 0;
231                 } else {
232                         var->red.offset         = 0;
233                         var->red.length         = 8;
234                         var->green              = var->red;
235                         var->blue               = var->red;
236                 }
237                 break;
238         case 12:
239                 /* 12 bpp 444 */
240                 var->red.length         = 4;
241                 var->red.offset         = 8;
242                 var->green.length       = 4;
243                 var->green.offset       = 4;
244                 var->blue.length        = 4;
245                 var->blue.offset        = 0;
246                 break;
247
248         default:
249         case 16:
250                 if (display->lcdcon5 & S3C2410_LCDCON5_FRM565) {
251                         /* 16 bpp, 565 format */
252                         var->red.offset         = 11;
253                         var->green.offset       = 5;
254                         var->blue.offset        = 0;
255                         var->red.length         = 5;
256                         var->green.length       = 6;
257                         var->blue.length        = 5;
258                 } else {
259                         /* 16 bpp, 5551 format */
260                         var->red.offset         = 11;
261                         var->green.offset       = 6;
262                         var->blue.offset        = 1;
263                         var->red.length         = 5;
264                         var->green.length       = 5;
265                         var->blue.length        = 5;
266                 }
267                 break;
268         case 24:
269                 /* 24 bpp 888 */
270                 var->red.length         = 8;
271                 var->red.offset         = 16;
272                 var->green.length       = 8;
273                 var->green.offset       = 8;
274                 var->blue.length        = 8;
275                 var->blue.offset        = 0;
276                 break;
277
278
279         }
280         return 0;
281 }
282
283 /* s3c2410fb_calculate_stn_lcd_regs
284  *
285  * calculate register values from var settings
286  */
287 static void s3c2410fb_calculate_stn_lcd_regs(const struct fb_info *info,
288                                              struct s3c2410fb_hw *regs)
289 {
290         const struct s3c2410fb_info *fbi = info->par;
291         const struct fb_var_screeninfo *var = &info->var;
292         int type = regs->lcdcon1 & ~S3C2410_LCDCON1_TFT;
293         int hs = var->xres >> 2;
294         unsigned wdly = (var->left_margin >> 4) - 1;
295         unsigned wlh = (var->hsync_len >> 4) - 1;
296
297         dprintk("%s: var->xres  = %d\n", __FUNCTION__, var->xres);
298         dprintk("%s: var->yres  = %d\n", __FUNCTION__, var->yres);
299         dprintk("%s: var->bpp   = %d\n", __FUNCTION__, var->bits_per_pixel);
300
301         if (type != S3C2410_LCDCON1_STN4)
302                 hs >>= 1;
303
304         regs->lcdcon1 &= ~S3C2410_LCDCON1_MODEMASK;
305
306         switch (var->bits_per_pixel) {
307         case 1:
308                 regs->lcdcon1 |= S3C2410_LCDCON1_STN1BPP;
309                 break;
310         case 2:
311                 regs->lcdcon1 |= S3C2410_LCDCON1_STN2GREY;
312                 break;
313         case 4:
314                 regs->lcdcon1 |= S3C2410_LCDCON1_STN4GREY;
315                 break;
316         case 8:
317                 regs->lcdcon1 |= S3C2410_LCDCON1_STN8BPP;
318                 hs *= 3;
319                 break;
320         case 12:
321                 regs->lcdcon1 |= S3C2410_LCDCON1_STN12BPP;
322                 hs *= 3;
323                 break;
324
325         default:
326                 /* invalid pixel depth */
327                 dev_err(fbi->dev, "invalid bpp %d\n",
328                         var->bits_per_pixel);
329         }
330         /* update X/Y info */
331         dprintk("setting horz: lft=%d, rt=%d, sync=%d\n",
332                 var->left_margin, var->right_margin, var->hsync_len);
333
334         regs->lcdcon2 = S3C2410_LCDCON2_LINEVAL(var->yres - 1);
335
336         if (wdly > 3)
337                 wdly = 3;
338
339         if (wlh > 3)
340                 wlh = 3;
341
342         regs->lcdcon3 = S3C2410_LCDCON3_WDLY(wdly) |
343                         S3C2410_LCDCON3_LINEBLANK(var->right_margin / 8) |
344                         S3C2410_LCDCON3_HOZVAL(hs - 1);
345
346         regs->lcdcon4 = S3C2410_LCDCON4_WLH(wlh);
347 }
348
349 /* s3c2410fb_calculate_tft_lcd_regs
350  *
351  * calculate register values from var settings
352  */
353 static void s3c2410fb_calculate_tft_lcd_regs(const struct fb_info *info,
354                                              struct s3c2410fb_hw *regs)
355 {
356         const struct s3c2410fb_info *fbi = info->par;
357         const struct fb_var_screeninfo *var = &info->var;
358
359         dprintk("%s: var->xres  = %d\n", __FUNCTION__, var->xres);
360         dprintk("%s: var->yres  = %d\n", __FUNCTION__, var->yres);
361         dprintk("%s: var->bpp   = %d\n", __FUNCTION__, var->bits_per_pixel);
362
363         regs->lcdcon1 &= ~S3C2410_LCDCON1_MODEMASK;
364
365         switch (var->bits_per_pixel) {
366         case 1:
367                 regs->lcdcon1 |= S3C2410_LCDCON1_TFT1BPP;
368                 break;
369         case 2:
370                 regs->lcdcon1 |= S3C2410_LCDCON1_TFT2BPP;
371                 break;
372         case 4:
373                 regs->lcdcon1 |= S3C2410_LCDCON1_TFT4BPP;
374                 break;
375         case 8:
376                 regs->lcdcon1 |= S3C2410_LCDCON1_TFT8BPP;
377                 break;
378         case 16:
379                 regs->lcdcon1 |= S3C2410_LCDCON1_TFT16BPP;
380                 break;
381
382         default:
383                 /* invalid pixel depth */
384                 dev_err(fbi->dev, "invalid bpp %d\n",
385                         var->bits_per_pixel);
386         }
387         /* update X/Y info */
388         dprintk("setting vert: up=%d, low=%d, sync=%d\n",
389                 var->upper_margin, var->lower_margin, var->vsync_len);
390
391         dprintk("setting horz: lft=%d, rt=%d, sync=%d\n",
392                 var->left_margin, var->right_margin, var->hsync_len);
393
394         regs->lcdcon2 = S3C2410_LCDCON2_LINEVAL(var->yres - 1) |
395                         S3C2410_LCDCON2_VBPD(var->upper_margin - 1) |
396                         S3C2410_LCDCON2_VFPD(var->lower_margin - 1) |
397                         S3C2410_LCDCON2_VSPW(var->vsync_len - 1);
398
399         regs->lcdcon3 = S3C2410_LCDCON3_HBPD(var->right_margin - 1) |
400                         S3C2410_LCDCON3_HFPD(var->left_margin - 1) |
401                         S3C2410_LCDCON3_HOZVAL(var->xres - 1);
402
403         regs->lcdcon4 = S3C2410_LCDCON4_HSPW(var->hsync_len - 1);
404 }
405
406 /* s3c2410fb_activate_var
407  *
408  * activate (set) the controller from the given framebuffer
409  * information
410  */
411 static void s3c2410fb_activate_var(struct fb_info *info)
412 {
413         struct s3c2410fb_info *fbi = info->par;
414         void __iomem *regs = fbi->io;
415         struct fb_var_screeninfo *var = &info->var;
416         struct s3c2410fb_mach_info *mach_info = fbi->mach_info;
417         struct s3c2410fb_display *display = mach_info->displays +
418                                             fbi->current_display;
419
420         /* set display type */
421         fbi->regs.lcdcon1 &= ~S3C2410_LCDCON1_TFT;
422         fbi->regs.lcdcon1 |= display->type;
423
424         if (var->pixclock > 0) {
425                 int clkdiv = s3c2410fb_calc_pixclk(fbi, var->pixclock);
426
427                 if (display->type == S3C2410_LCDCON1_TFT) {
428                         clkdiv = (clkdiv / 2) - 1;
429                         if (clkdiv < 0)
430                                 clkdiv = 0;
431                 } else {
432                         clkdiv = (clkdiv / 2);
433                         if (clkdiv < 2)
434                                 clkdiv = 2;
435                 }
436
437                 fbi->regs.lcdcon1 &= ~S3C2410_LCDCON1_CLKVAL(0x3ff);
438                 fbi->regs.lcdcon1 |=  S3C2410_LCDCON1_CLKVAL(clkdiv);
439         }
440
441         if (display->type == S3C2410_LCDCON1_TFT)
442                 s3c2410fb_calculate_tft_lcd_regs(info, &fbi->regs);
443         else
444                 s3c2410fb_calculate_stn_lcd_regs(info, &fbi->regs);
445
446         /* write new registers */
447
448         dprintk("new register set:\n");
449         dprintk("lcdcon[1] = 0x%08lx\n", fbi->regs.lcdcon1);
450         dprintk("lcdcon[2] = 0x%08lx\n", fbi->regs.lcdcon2);
451         dprintk("lcdcon[3] = 0x%08lx\n", fbi->regs.lcdcon3);
452         dprintk("lcdcon[4] = 0x%08lx\n", fbi->regs.lcdcon4);
453         dprintk("lcdcon[5] = 0x%08lx\n", fbi->regs.lcdcon5);
454
455         writel(fbi->regs.lcdcon1 & ~S3C2410_LCDCON1_ENVID,
456                 regs + S3C2410_LCDCON1);
457         writel(fbi->regs.lcdcon2, regs + S3C2410_LCDCON2);
458         writel(fbi->regs.lcdcon3, regs + S3C2410_LCDCON3);
459         writel(fbi->regs.lcdcon4, regs + S3C2410_LCDCON4);
460         writel(fbi->regs.lcdcon5, regs + S3C2410_LCDCON5);
461
462         /* set lcd address pointers */
463         s3c2410fb_set_lcdaddr(info);
464
465         writel(fbi->regs.lcdcon1, regs + S3C2410_LCDCON1);
466 }
467
468 /*
469  *      s3c2410fb_set_par - Alters the hardware state.
470  *      @info: frame buffer structure that represents a single frame buffer
471  *
472  */
473 static int s3c2410fb_set_par(struct fb_info *info)
474 {
475         struct fb_var_screeninfo *var = &info->var;
476
477         switch (var->bits_per_pixel) {
478         case 16:
479                 info->fix.visual = FB_VISUAL_TRUECOLOR;
480                 break;
481         case 1:
482                 info->fix.visual = FB_VISUAL_MONO01;
483                 break;
484         default:
485                 info->fix.visual = FB_VISUAL_PSEUDOCOLOR;
486                 break;
487         }
488
489         info->fix.line_length = (var->width * var->bits_per_pixel) / 8;
490
491         /* activate this new configuration */
492
493         s3c2410fb_activate_var(info);
494         return 0;
495 }
496
497 static void schedule_palette_update(struct s3c2410fb_info *fbi,
498                                     unsigned int regno, unsigned int val)
499 {
500         unsigned long flags;
501         unsigned long irqen;
502         void __iomem *regs = fbi->io;
503
504         local_irq_save(flags);
505
506         fbi->palette_buffer[regno] = val;
507
508         if (!fbi->palette_ready) {
509                 fbi->palette_ready = 1;
510
511                 /* enable IRQ */
512                 irqen = readl(regs + S3C2410_LCDINTMSK);
513                 irqen &= ~S3C2410_LCDINT_FRSYNC;
514                 writel(irqen, regs + S3C2410_LCDINTMSK);
515         }
516
517         local_irq_restore(flags);
518 }
519
520 /* from pxafb.c */
521 static inline unsigned int chan_to_field(unsigned int chan,
522                                          struct fb_bitfield *bf)
523 {
524         chan &= 0xffff;
525         chan >>= 16 - bf->length;
526         return chan << bf->offset;
527 }
528
529 static int s3c2410fb_setcolreg(unsigned regno,
530                                unsigned red, unsigned green, unsigned blue,
531                                unsigned transp, struct fb_info *info)
532 {
533         struct s3c2410fb_info *fbi = info->par;
534         void __iomem *regs = fbi->io;
535         unsigned int val;
536
537         /* dprintk("setcol: regno=%d, rgb=%d,%d,%d\n",
538                    regno, red, green, blue); */
539
540         switch (info->fix.visual) {
541         case FB_VISUAL_TRUECOLOR:
542                 /* true-colour, use pseudo-palette */
543
544                 if (regno < 16) {
545                         u32 *pal = info->pseudo_palette;
546
547                         val  = chan_to_field(red,   &info->var.red);
548                         val |= chan_to_field(green, &info->var.green);
549                         val |= chan_to_field(blue,  &info->var.blue);
550
551                         pal[regno] = val;
552                 }
553                 break;
554
555         case FB_VISUAL_PSEUDOCOLOR:
556                 if (regno < 256) {
557                         /* currently assume RGB 5-6-5 mode */
558
559                         val  = ((red   >>  0) & 0xf800);
560                         val |= ((green >>  5) & 0x07e0);
561                         val |= ((blue  >> 11) & 0x001f);
562
563                         writel(val, regs + S3C2410_TFTPAL(regno));
564                         schedule_palette_update(fbi, regno, val);
565                 }
566
567                 break;
568
569         default:
570                 return 1;       /* unknown type */
571         }
572
573         return 0;
574 }
575
576 /*
577  *      s3c2410fb_blank
578  *      @blank_mode: the blank mode we want.
579  *      @info: frame buffer structure that represents a single frame buffer
580  *
581  *      Blank the screen if blank_mode != 0, else unblank. Return 0 if
582  *      blanking succeeded, != 0 if un-/blanking failed due to e.g. a
583  *      video mode which doesn't support it. Implements VESA suspend
584  *      and powerdown modes on hardware that supports disabling hsync/vsync:
585  *      blank_mode == 2: suspend vsync
586  *      blank_mode == 3: suspend hsync
587  *      blank_mode == 4: powerdown
588  *
589  *      Returns negative errno on error, or zero on success.
590  *
591  */
592 static int s3c2410fb_blank(int blank_mode, struct fb_info *info)
593 {
594         struct s3c2410fb_info *fbi = info->par;
595         void __iomem *regs = fbi->io;
596
597         dprintk("blank(mode=%d, info=%p)\n", blank_mode, info);
598
599         if (mach_info == NULL)
600                 return -EINVAL;
601
602         if (blank_mode == FB_BLANK_UNBLANK)
603                 writel(0x0, regs + S3C2410_TPAL);
604         else {
605                 dprintk("setting TPAL to output 0x000000\n");
606                 writel(S3C2410_TPAL_EN, regs + S3C2410_TPAL);
607         }
608
609         return 0;
610 }
611
612 static int s3c2410fb_debug_show(struct device *dev,
613                                 struct device_attribute *attr, char *buf)
614 {
615         return snprintf(buf, PAGE_SIZE, "%s\n", debug ? "on" : "off");
616 }
617 static int s3c2410fb_debug_store(struct device *dev,
618                                  struct device_attribute *attr,
619                                  const char *buf, size_t len)
620 {
621         if (mach_info == NULL)
622                 return -EINVAL;
623
624         if (len < 1)
625                 return -EINVAL;
626
627         if (strnicmp(buf, "on", 2) == 0 ||
628             strnicmp(buf, "1", 1) == 0) {
629                 debug = 1;
630                 printk(KERN_DEBUG "s3c2410fb: Debug On");
631         } else if (strnicmp(buf, "off", 3) == 0 ||
632                    strnicmp(buf, "0", 1) == 0) {
633                 debug = 0;
634                 printk(KERN_DEBUG "s3c2410fb: Debug Off");
635         } else {
636                 return -EINVAL;
637         }
638
639         return len;
640 }
641
642 static DEVICE_ATTR(debug, 0666, s3c2410fb_debug_show, s3c2410fb_debug_store);
643
644 static struct fb_ops s3c2410fb_ops = {
645         .owner          = THIS_MODULE,
646         .fb_check_var   = s3c2410fb_check_var,
647         .fb_set_par     = s3c2410fb_set_par,
648         .fb_blank       = s3c2410fb_blank,
649         .fb_setcolreg   = s3c2410fb_setcolreg,
650         .fb_fillrect    = cfb_fillrect,
651         .fb_copyarea    = cfb_copyarea,
652         .fb_imageblit   = cfb_imageblit,
653 };
654
655 /*
656  * s3c2410fb_map_video_memory():
657  *      Allocates the DRAM memory for the frame buffer.  This buffer is
658  *      remapped into a non-cached, non-buffered, memory region to
659  *      allow palette and pixel writes to occur without flushing the
660  *      cache.  Once this area is remapped, all virtual memory
661  *      access to the video memory should occur at the new region.
662  */
663 static int __init s3c2410fb_map_video_memory(struct fb_info *info)
664 {
665         struct s3c2410fb_info *fbi = info->par;
666
667         dprintk("map_video_memory(fbi=%p)\n", fbi);
668
669         fbi->map_size = PAGE_ALIGN(info->fix.smem_len + PAGE_SIZE);
670         fbi->map_cpu  = dma_alloc_writecombine(fbi->dev, fbi->map_size,
671                                                &fbi->map_dma, GFP_KERNEL);
672
673         fbi->map_size = info->fix.smem_len;
674
675         if (fbi->map_cpu) {
676                 /* prevent initial garbage on screen */
677                 dprintk("map_video_memory: clear %p:%08x\n",
678                         fbi->map_cpu, fbi->map_size);
679                 memset(fbi->map_cpu, 0xf0, fbi->map_size);
680
681                 fbi->screen_dma         = fbi->map_dma;
682                 info->screen_base       = fbi->map_cpu;
683                 info->fix.smem_start    = fbi->screen_dma;
684
685                 dprintk("map_video_memory: dma=%08x cpu=%p size=%08x\n",
686                         fbi->map_dma, fbi->map_cpu, info->fix.smem_len);
687         }
688
689         return fbi->map_cpu ? 0 : -ENOMEM;
690 }
691
692 static inline void s3c2410fb_unmap_video_memory(struct s3c2410fb_info *fbi)
693 {
694         dma_free_writecombine(fbi->dev, fbi->map_size, fbi->map_cpu,
695                               fbi->map_dma);
696 }
697
698 static inline void modify_gpio(void __iomem *reg,
699                                unsigned long set, unsigned long mask)
700 {
701         unsigned long tmp;
702
703         tmp = readl(reg) & ~mask;
704         writel(tmp | set, reg);
705 }
706
707 /*
708  * s3c2410fb_init_registers - Initialise all LCD-related registers
709  */
710 static int s3c2410fb_init_registers(struct fb_info *info)
711 {
712         struct s3c2410fb_info *fbi = info->par;
713         unsigned long flags;
714         void __iomem *regs = fbi->io;
715
716         /* Initialise LCD with values from haret */
717
718         local_irq_save(flags);
719
720         /* modify the gpio(s) with interrupts set (bjd) */
721
722         modify_gpio(S3C2410_GPCUP,  mach_info->gpcup,  mach_info->gpcup_mask);
723         modify_gpio(S3C2410_GPCCON, mach_info->gpccon, mach_info->gpccon_mask);
724         modify_gpio(S3C2410_GPDUP,  mach_info->gpdup,  mach_info->gpdup_mask);
725         modify_gpio(S3C2410_GPDCON, mach_info->gpdcon, mach_info->gpdcon_mask);
726
727         local_irq_restore(flags);
728
729         writel(fbi->regs.lcdcon1, regs + S3C2410_LCDCON1);
730         writel(fbi->regs.lcdcon2, regs + S3C2410_LCDCON2);
731         writel(fbi->regs.lcdcon3, regs + S3C2410_LCDCON3);
732         writel(fbi->regs.lcdcon4, regs + S3C2410_LCDCON4);
733         writel(fbi->regs.lcdcon5, regs + S3C2410_LCDCON5);
734
735         s3c2410fb_set_lcdaddr(info);
736
737         dprintk("LPCSEL    = 0x%08lx\n", mach_info->lpcsel);
738         writel(mach_info->lpcsel, regs + S3C2410_LPCSEL);
739
740         dprintk("replacing TPAL %08x\n", readl(regs + S3C2410_TPAL));
741
742         /* ensure temporary palette disabled */
743         writel(0x00, regs + S3C2410_TPAL);
744
745         /* Enable video by setting the ENVID bit to 1 */
746         fbi->regs.lcdcon1 |= S3C2410_LCDCON1_ENVID;
747         writel(fbi->regs.lcdcon1, regs + S3C2410_LCDCON1);
748         return 0;
749 }
750
751 static void s3c2410fb_write_palette(struct s3c2410fb_info *fbi)
752 {
753         unsigned int i;
754         void __iomem *regs = fbi->io;
755
756         fbi->palette_ready = 0;
757
758         for (i = 0; i < 256; i++) {
759                 unsigned long ent = fbi->palette_buffer[i];
760                 if (ent == PALETTE_BUFF_CLEAR)
761                         continue;
762
763                 writel(ent, regs + S3C2410_TFTPAL(i));
764
765                 /* it seems the only way to know exactly
766                  * if the palette wrote ok, is to check
767                  * to see if the value verifies ok
768                  */
769
770                 if (readw(regs + S3C2410_TFTPAL(i)) == ent)
771                         fbi->palette_buffer[i] = PALETTE_BUFF_CLEAR;
772                 else
773                         fbi->palette_ready = 1;   /* retry */
774         }
775 }
776
777 static irqreturn_t s3c2410fb_irq(int irq, void *dev_id)
778 {
779         struct s3c2410fb_info *fbi = dev_id;
780         void __iomem *regs = fbi->io;
781         unsigned long lcdirq = readl(regs + S3C2410_LCDINTPND);
782
783         if (lcdirq & S3C2410_LCDINT_FRSYNC) {
784                 if (fbi->palette_ready)
785                         s3c2410fb_write_palette(fbi);
786
787                 writel(S3C2410_LCDINT_FRSYNC, regs + S3C2410_LCDINTPND);
788                 writel(S3C2410_LCDINT_FRSYNC, regs + S3C2410_LCDSRCPND);
789         }
790
791         return IRQ_HANDLED;
792 }
793
794 static char driver_name[] = "s3c2410fb";
795
796 static int __init s3c2410fb_probe(struct platform_device *pdev)
797 {
798         struct s3c2410fb_info *info;
799         struct s3c2410fb_display *display;
800         struct fb_info *fbinfo;
801         struct resource *res;
802         int ret;
803         int irq;
804         int i;
805         int size;
806         u32 lcdcon1;
807
808         mach_info = pdev->dev.platform_data;
809         if (mach_info == NULL) {
810                 dev_err(&pdev->dev,
811                         "no platform data for lcd, cannot attach\n");
812                 return -EINVAL;
813         }
814
815         display = mach_info->displays + mach_info->default_display;
816
817         irq = platform_get_irq(pdev, 0);
818         if (irq < 0) {
819                 dev_err(&pdev->dev, "no irq for device\n");
820                 return -ENOENT;
821         }
822
823         fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);
824         if (!fbinfo)
825                 return -ENOMEM;
826
827         info = fbinfo->par;
828         info->dev = &pdev->dev;
829
830         res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
831         if (res == NULL) {
832                 dev_err(&pdev->dev, "failed to get memory registers\n");
833                 ret = -ENXIO;
834                 goto dealloc_fb;
835         }
836
837         size = (res->end - res->start) + 1;
838         info->mem = request_mem_region(res->start, size, pdev->name);
839         if (info->mem == NULL) {
840                 dev_err(&pdev->dev, "failed to get memory region\n");
841                 ret = -ENOENT;
842                 goto dealloc_fb;
843         }
844
845         info->io = ioremap(res->start, size);
846         if (info->io == NULL) {
847                 dev_err(&pdev->dev, "ioremap() of registers failed\n");
848                 ret = -ENXIO;
849                 goto release_mem;
850         }
851
852         platform_set_drvdata(pdev, fbinfo);
853
854         dprintk("devinit\n");
855
856         strcpy(fbinfo->fix.id, driver_name);
857
858         info->regs.lcdcon1 = display->lcdcon1;
859         info->regs.lcdcon5 = display->lcdcon5;
860
861         /* Stop the video and unset ENVID if set */
862         info->regs.lcdcon1 &= ~S3C2410_LCDCON1_ENVID;
863         lcdcon1 = readl(info->io + S3C2410_LCDCON1);
864         writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, info->io + S3C2410_LCDCON1);
865
866         info->mach_info             = pdev->dev.platform_data;
867         info->current_display       = mach_info->default_display;
868
869         fbinfo->fix.type            = FB_TYPE_PACKED_PIXELS;
870         fbinfo->fix.type_aux        = 0;
871         fbinfo->fix.xpanstep        = 0;
872         fbinfo->fix.ypanstep        = 0;
873         fbinfo->fix.ywrapstep       = 0;
874         fbinfo->fix.accel           = FB_ACCEL_NONE;
875
876         fbinfo->var.nonstd          = 0;
877         fbinfo->var.activate        = FB_ACTIVATE_NOW;
878         fbinfo->var.height          = display->height;
879         fbinfo->var.width           = display->width;
880         fbinfo->var.accel_flags     = 0;
881         fbinfo->var.vmode           = FB_VMODE_NONINTERLACED;
882
883         fbinfo->fbops               = &s3c2410fb_ops;
884         fbinfo->flags               = FBINFO_FLAG_DEFAULT;
885         fbinfo->pseudo_palette      = &info->pseudo_pal;
886
887         fbinfo->var.xres            = display->xres;
888         fbinfo->var.xres_virtual    = display->xres;
889         fbinfo->var.yres            = display->yres;
890         fbinfo->var.yres_virtual    = display->yres;
891         fbinfo->var.bits_per_pixel  = display->bpp;
892         fbinfo->var.left_margin     = display->left_margin;
893         fbinfo->var.right_margin    = display->right_margin;
894
895         fbinfo->var.upper_margin    = display->upper_margin;
896         fbinfo->var.lower_margin    = display->lower_margin;
897         fbinfo->var.vsync_len       = display->vsync_len;
898         fbinfo->var.hsync_len       = display->hsync_len;
899
900         fbinfo->var.red.offset      = 11;
901         fbinfo->var.green.offset    = 5;
902         fbinfo->var.blue.offset     = 0;
903         fbinfo->var.transp.offset   = 0;
904         fbinfo->var.red.length      = 5;
905         fbinfo->var.green.length    = 6;
906         fbinfo->var.blue.length     = 5;
907         fbinfo->var.transp.length   = 0;
908
909         /* find maximum required memory size for display */
910         for (i = 0; i < mach_info->num_displays; i++) {
911                 unsigned long smem_len = mach_info->displays[i].xres;
912
913                 smem_len *= mach_info->displays[i].yres;
914                 smem_len *= mach_info->displays[i].bpp;
915                 smem_len >>= 3;
916                 if (fbinfo->fix.smem_len < smem_len)
917                         fbinfo->fix.smem_len = smem_len;
918         }
919
920         for (i = 0; i < 256; i++)
921                 info->palette_buffer[i] = PALETTE_BUFF_CLEAR;
922
923         ret = request_irq(irq, s3c2410fb_irq, IRQF_DISABLED, pdev->name, info);
924         if (ret) {
925                 dev_err(&pdev->dev, "cannot get irq %d - err %d\n", irq, ret);
926                 ret = -EBUSY;
927                 goto release_regs;
928         }
929
930         info->clk = clk_get(NULL, "lcd");
931         if (!info->clk || IS_ERR(info->clk)) {
932                 printk(KERN_ERR "failed to get lcd clock source\n");
933                 ret = -ENOENT;
934                 goto release_irq;
935         }
936
937         clk_enable(info->clk);
938         dprintk("got and enabled clock\n");
939
940         msleep(1);
941
942         /* Initialize video memory */
943         ret = s3c2410fb_map_video_memory(fbinfo);
944         if (ret) {
945                 printk(KERN_ERR "Failed to allocate video RAM: %d\n", ret);
946                 ret = -ENOMEM;
947                 goto release_clock;
948         }
949
950         dprintk("got video memory\n");
951
952         s3c2410fb_init_registers(fbinfo);
953
954         s3c2410fb_check_var(&fbinfo->var, fbinfo);
955
956         ret = register_framebuffer(fbinfo);
957         if (ret < 0) {
958                 printk(KERN_ERR "Failed to register framebuffer device: %d\n",
959                         ret);
960                 goto free_video_memory;
961         }
962
963         /* create device files */
964         device_create_file(&pdev->dev, &dev_attr_debug);
965
966         printk(KERN_INFO "fb%d: %s frame buffer device\n",
967                 fbinfo->node, fbinfo->fix.id);
968
969         return 0;
970
971 free_video_memory:
972         s3c2410fb_unmap_video_memory(info);
973 release_clock:
974         clk_disable(info->clk);
975         clk_put(info->clk);
976 release_irq:
977         free_irq(irq, info);
978 release_regs:
979         iounmap(info->io);
980 release_mem:
981         release_resource(info->mem);
982         kfree(info->mem);
983 dealloc_fb:
984         framebuffer_release(fbinfo);
985         return ret;
986 }
987
988 /* s3c2410fb_stop_lcd
989  *
990  * shutdown the lcd controller
991  */
992 static void s3c2410fb_stop_lcd(struct s3c2410fb_info *fbi)
993 {
994         unsigned long flags;
995
996         local_irq_save(flags);
997
998         fbi->regs.lcdcon1 &= ~S3C2410_LCDCON1_ENVID;
999         writel(fbi->regs.lcdcon1, fbi->io + S3C2410_LCDCON1);
1000
1001         local_irq_restore(flags);
1002 }
1003
1004 /*
1005  *  Cleanup
1006  */
1007 static int s3c2410fb_remove(struct platform_device *pdev)
1008 {
1009         struct fb_info *fbinfo = platform_get_drvdata(pdev);
1010         struct s3c2410fb_info *info = fbinfo->par;
1011         int irq;
1012
1013         s3c2410fb_stop_lcd(info);
1014         msleep(1);
1015
1016         s3c2410fb_unmap_video_memory(info);
1017
1018         if (info->clk) {
1019                 clk_disable(info->clk);
1020                 clk_put(info->clk);
1021                 info->clk = NULL;
1022         }
1023
1024         irq = platform_get_irq(pdev, 0);
1025         free_irq(irq, info);
1026
1027         release_resource(info->mem);
1028         kfree(info->mem);
1029         iounmap(info->io);
1030         unregister_framebuffer(fbinfo);
1031
1032         return 0;
1033 }
1034
1035 #ifdef CONFIG_PM
1036
1037 /* suspend and resume support for the lcd controller */
1038 static int s3c2410fb_suspend(struct platform_device *dev, pm_message_t state)
1039 {
1040         struct fb_info     *fbinfo = platform_get_drvdata(dev);
1041         struct s3c2410fb_info *info = fbinfo->par;
1042
1043         s3c2410fb_stop_lcd(info);
1044
1045         /* sleep before disabling the clock, we need to ensure
1046          * the LCD DMA engine is not going to get back on the bus
1047          * before the clock goes off again (bjd) */
1048
1049         msleep(1);
1050         clk_disable(info->clk);
1051
1052         return 0;
1053 }
1054
1055 static int s3c2410fb_resume(struct platform_device *dev)
1056 {
1057         struct fb_info     *fbinfo = platform_get_drvdata(dev);
1058         struct s3c2410fb_info *info = fbinfo->par;
1059
1060         clk_enable(info->clk);
1061         msleep(1);
1062
1063         s3c2410fb_init_registers(info);
1064
1065         return 0;
1066 }
1067
1068 #else
1069 #define s3c2410fb_suspend NULL
1070 #define s3c2410fb_resume  NULL
1071 #endif
1072
1073 static struct platform_driver s3c2410fb_driver = {
1074         .probe          = s3c2410fb_probe,
1075         .remove         = s3c2410fb_remove,
1076         .suspend        = s3c2410fb_suspend,
1077         .resume         = s3c2410fb_resume,
1078         .driver         = {
1079                 .name   = "s3c2410-lcd",
1080                 .owner  = THIS_MODULE,
1081         },
1082 };
1083
1084 int __devinit s3c2410fb_init(void)
1085 {
1086         return platform_driver_register(&s3c2410fb_driver);
1087 }
1088
1089 static void __exit s3c2410fb_cleanup(void)
1090 {
1091         platform_driver_unregister(&s3c2410fb_driver);
1092 }
1093
1094 module_init(s3c2410fb_init);
1095 module_exit(s3c2410fb_cleanup);
1096
1097 MODULE_AUTHOR("Arnaud Patard <arnaud.patard@rtp-net.org>, "
1098               "Ben Dooks <ben-linux@fluff.org>");
1099 MODULE_DESCRIPTION("Framebuffer driver for the s3c2410");
1100 MODULE_LICENSE("GPL");