Linux-libre 5.3.12-gnu
[librecmc/linux-libre.git] / drivers / video / fbdev / nuc900fb.c
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  *
4  * Copyright (c) 2009 Nuvoton technology corporation
5  * All rights reserved.
6  *
7  *  Description:
8  *    Nuvoton LCD Controller Driver
9  *  Author:
10  *    Wang Qiang (rurality.linux@gmail.com) 2009/12/11
11  */
12 #include <linux/module.h>
13 #include <linux/kernel.h>
14 #include <linux/err.h>
15 #include <linux/errno.h>
16 #include <linux/string.h>
17 #include <linux/mm.h>
18 #include <linux/tty.h>
19 #include <linux/slab.h>
20 #include <linux/delay.h>
21 #include <linux/fb.h>
22 #include <linux/init.h>
23 #include <linux/dma-mapping.h>
24 #include <linux/interrupt.h>
25 #include <linux/workqueue.h>
26 #include <linux/wait.h>
27 #include <linux/platform_device.h>
28 #include <linux/clk.h>
29 #include <linux/cpufreq.h>
30 #include <linux/io.h>
31 #include <linux/pm.h>
32 #include <linux/device.h>
33
34 #include <mach/map.h>
35 #include <mach/regs-clock.h>
36 #include <mach/regs-ldm.h>
37 #include <linux/platform_data/video-nuc900fb.h>
38
39 #include "nuc900fb.h"
40
41
42 /*
43  *  Initialize the nuc900 video (dual) buffer address
44  */
45 static void nuc900fb_set_lcdaddr(struct fb_info *info)
46 {
47         struct nuc900fb_info *fbi = info->par;
48         void __iomem *regs = fbi->io;
49         unsigned long vbaddr1, vbaddr2;
50
51         vbaddr1  = info->fix.smem_start;
52         vbaddr2  = info->fix.smem_start;
53         vbaddr2 += info->fix.line_length * info->var.yres;
54
55         /* set frambuffer start phy addr*/
56         writel(vbaddr1, regs + REG_LCM_VA_BADDR0);
57         writel(vbaddr2, regs + REG_LCM_VA_BADDR1);
58
59         writel(fbi->regs.lcd_va_fbctrl, regs + REG_LCM_VA_FBCTRL);
60         writel(fbi->regs.lcd_va_scale, regs + REG_LCM_VA_SCALE);
61 }
62
63 /*
64  *      calculate divider for lcd div
65  */
66 static unsigned int nuc900fb_calc_pixclk(struct nuc900fb_info *fbi,
67                                          unsigned long pixclk)
68 {
69         unsigned long clk = fbi->clk_rate;
70         unsigned long long div;
71
72         /* pixclk is in picseconds. our clock is in Hz*/
73         /* div = (clk * pixclk)/10^12 */
74         div = (unsigned long long)clk * pixclk;
75         div >>= 12;
76         do_div(div, 625 * 625UL * 625);
77
78         dev_dbg(fbi->dev, "pixclk %ld, divisor is %lld\n", pixclk, div);
79
80         return div;
81 }
82
83 /*
84  *      Check the video params of 'var'.
85  */
86 static int nuc900fb_check_var(struct fb_var_screeninfo *var,
87                                struct fb_info *info)
88 {
89         struct nuc900fb_info *fbi = info->par;
90         struct nuc900fb_mach_info *mach_info = dev_get_platdata(fbi->dev);
91         struct nuc900fb_display *display = NULL;
92         struct nuc900fb_display *default_display = mach_info->displays +
93                                                    mach_info->default_display;
94         int i;
95
96         dev_dbg(fbi->dev, "check_var(var=%p, info=%p)\n", var, info);
97
98         /* validate x/y resolution */
99         /* choose default mode if possible */
100         if (var->xres == default_display->xres &&
101             var->yres == default_display->yres &&
102             var->bits_per_pixel == default_display->bpp)
103                 display = default_display;
104         else
105                 for (i = 0; i < mach_info->num_displays; i++)
106                         if (var->xres == mach_info->displays[i].xres &&
107                             var->yres == mach_info->displays[i].yres &&
108                             var->bits_per_pixel == mach_info->displays[i].bpp) {
109                                 display = mach_info->displays + i;
110                                 break;
111                         }
112
113         if (display == NULL) {
114                 printk(KERN_ERR "wrong resolution or depth %dx%d at %d bit per pixel\n",
115                         var->xres, var->yres, var->bits_per_pixel);
116                 return -EINVAL;
117         }
118
119         /* it should be the same size as the display */
120         var->xres_virtual       = display->xres;
121         var->yres_virtual       = display->yres;
122         var->height             = display->height;
123         var->width              = display->width;
124
125         /* copy lcd settings */
126         var->pixclock           = display->pixclock;
127         var->left_margin        = display->left_margin;
128         var->right_margin       = display->right_margin;
129         var->upper_margin       = display->upper_margin;
130         var->lower_margin       = display->lower_margin;
131         var->vsync_len          = display->vsync_len;
132         var->hsync_len          = display->hsync_len;
133
134         var->transp.offset      = 0;
135         var->transp.length      = 0;
136
137         fbi->regs.lcd_dccs = display->dccs;
138         fbi->regs.lcd_device_ctrl = display->devctl;
139         fbi->regs.lcd_va_fbctrl = display->fbctrl;
140         fbi->regs.lcd_va_scale = display->scale;
141
142         /* set R/G/B possions */
143         switch (var->bits_per_pixel) {
144         case 1:
145         case 2:
146         case 4:
147         case 8:
148         default:
149                 var->red.offset         = 0;
150                 var->red.length         = var->bits_per_pixel;
151                 var->green              = var->red;
152                 var->blue               = var->red;
153                 break;
154         case 12:
155                 var->red.length         = 4;
156                 var->green.length       = 4;
157                 var->blue.length        = 4;
158                 var->red.offset         = 8;
159                 var->green.offset       = 4;
160                 var->blue.offset        = 0;
161                 break;
162         case 16:
163                 var->red.length         = 5;
164                 var->green.length       = 6;
165                 var->blue.length        = 5;
166                 var->red.offset         = 11;
167                 var->green.offset       = 5;
168                 var->blue.offset        = 0;
169                 break;
170         case 18:
171                 var->red.length         = 6;
172                 var->green.length       = 6;
173                 var->blue.length        = 6;
174                 var->red.offset         = 12;
175                 var->green.offset       = 6;
176                 var->blue.offset        = 0;
177                 break;
178         case 32:
179                 var->red.length         = 8;
180                 var->green.length       = 8;
181                 var->blue.length        = 8;
182                 var->red.offset         = 16;
183                 var->green.offset       = 8;
184                 var->blue.offset        = 0;
185                 break;
186         }
187
188         return 0;
189 }
190
191 /*
192  *      Calculate lcd register values from var setting & save into hw
193  */
194 static void nuc900fb_calculate_lcd_regs(const struct fb_info *info,
195                                         struct nuc900fb_hw *regs)
196 {
197         const struct fb_var_screeninfo *var = &info->var;
198         int vtt = var->height + var->upper_margin + var->lower_margin;
199         int htt = var->width + var->left_margin + var->right_margin;
200         int hsync = var->width + var->right_margin;
201         int vsync = var->height + var->lower_margin;
202
203         regs->lcd_crtc_size = LCM_CRTC_SIZE_VTTVAL(vtt) |
204                               LCM_CRTC_SIZE_HTTVAL(htt);
205         regs->lcd_crtc_dend = LCM_CRTC_DEND_VDENDVAL(var->height) |
206                               LCM_CRTC_DEND_HDENDVAL(var->width);
207         regs->lcd_crtc_hr = LCM_CRTC_HR_EVAL(var->width + 5) |
208                             LCM_CRTC_HR_SVAL(var->width + 1);
209         regs->lcd_crtc_hsync = LCM_CRTC_HSYNC_EVAL(hsync + var->hsync_len) |
210                                LCM_CRTC_HSYNC_SVAL(hsync);
211         regs->lcd_crtc_vr = LCM_CRTC_VR_EVAL(vsync + var->vsync_len) |
212                             LCM_CRTC_VR_SVAL(vsync);
213
214 }
215
216 /*
217  *      Activate (set) the controller from the given framebuffer
218  *      information
219  */
220 static void nuc900fb_activate_var(struct fb_info *info)
221 {
222         struct nuc900fb_info *fbi = info->par;
223         void __iomem *regs = fbi->io;
224         struct fb_var_screeninfo *var = &info->var;
225         int clkdiv;
226
227         clkdiv = nuc900fb_calc_pixclk(fbi, var->pixclock) - 1;
228         if (clkdiv < 0)
229                 clkdiv = 0;
230
231         nuc900fb_calculate_lcd_regs(info, &fbi->regs);
232
233         /* set the new lcd registers*/
234
235         dev_dbg(fbi->dev, "new lcd register set:\n");
236         dev_dbg(fbi->dev, "dccs       = 0x%08x\n", fbi->regs.lcd_dccs);
237         dev_dbg(fbi->dev, "dev_ctl    = 0x%08x\n", fbi->regs.lcd_device_ctrl);
238         dev_dbg(fbi->dev, "crtc_size  = 0x%08x\n", fbi->regs.lcd_crtc_size);
239         dev_dbg(fbi->dev, "crtc_dend  = 0x%08x\n", fbi->regs.lcd_crtc_dend);
240         dev_dbg(fbi->dev, "crtc_hr    = 0x%08x\n", fbi->regs.lcd_crtc_hr);
241         dev_dbg(fbi->dev, "crtc_hsync = 0x%08x\n", fbi->regs.lcd_crtc_hsync);
242         dev_dbg(fbi->dev, "crtc_vr    = 0x%08x\n", fbi->regs.lcd_crtc_vr);
243
244         writel(fbi->regs.lcd_device_ctrl, regs + REG_LCM_DEV_CTRL);
245         writel(fbi->regs.lcd_crtc_size, regs + REG_LCM_CRTC_SIZE);
246         writel(fbi->regs.lcd_crtc_dend, regs + REG_LCM_CRTC_DEND);
247         writel(fbi->regs.lcd_crtc_hr, regs + REG_LCM_CRTC_HR);
248         writel(fbi->regs.lcd_crtc_hsync, regs + REG_LCM_CRTC_HSYNC);
249         writel(fbi->regs.lcd_crtc_vr, regs + REG_LCM_CRTC_VR);
250
251         /* set lcd address pointers */
252         nuc900fb_set_lcdaddr(info);
253
254         writel(fbi->regs.lcd_dccs, regs + REG_LCM_DCCS);
255 }
256
257 /*
258  *      Alters the hardware state.
259  *
260  */
261 static int nuc900fb_set_par(struct fb_info *info)
262 {
263         struct fb_var_screeninfo *var = &info->var;
264
265         switch (var->bits_per_pixel) {
266         case 32:
267         case 24:
268         case 18:
269         case 16:
270         case 12:
271                 info->fix.visual = FB_VISUAL_TRUECOLOR;
272                 break;
273         case 1:
274                 info->fix.visual = FB_VISUAL_MONO01;
275                 break;
276         default:
277                 info->fix.visual = FB_VISUAL_PSEUDOCOLOR;
278                 break;
279         }
280
281         info->fix.line_length = (var->xres_virtual * var->bits_per_pixel) / 8;
282
283         /* activate this new configuration */
284         nuc900fb_activate_var(info);
285         return 0;
286 }
287
288 static inline unsigned int chan_to_field(unsigned int chan,
289                                          struct fb_bitfield *bf)
290 {
291         chan &= 0xffff;
292         chan >>= 16 - bf->length;
293         return chan << bf->offset;
294 }
295
296 static int nuc900fb_setcolreg(unsigned regno,
297                                unsigned red, unsigned green, unsigned blue,
298                                unsigned transp, struct fb_info *info)
299 {
300         unsigned int val;
301
302         switch (info->fix.visual) {
303         case FB_VISUAL_TRUECOLOR:
304                 /* true-colour, use pseuo-palette */
305                 if (regno < 16) {
306                         u32 *pal = info->pseudo_palette;
307
308                         val  = chan_to_field(red, &info->var.red);
309                         val |= chan_to_field(green, &info->var.green);
310                         val |= chan_to_field(blue, &info->var.blue);
311                         pal[regno] = val;
312                 }
313                 break;
314
315         default:
316                 return 1;   /* unknown type */
317         }
318         return 0;
319 }
320
321 /**
322  *      nuc900fb_blank
323  *
324  */
325 static int nuc900fb_blank(int blank_mode, struct fb_info *info)
326 {
327
328         return 0;
329 }
330
331 static struct fb_ops nuc900fb_ops = {
332         .owner                  = THIS_MODULE,
333         .fb_check_var           = nuc900fb_check_var,
334         .fb_set_par             = nuc900fb_set_par,
335         .fb_blank               = nuc900fb_blank,
336         .fb_setcolreg           = nuc900fb_setcolreg,
337         .fb_fillrect            = cfb_fillrect,
338         .fb_copyarea            = cfb_copyarea,
339         .fb_imageblit           = cfb_imageblit,
340 };
341
342
343 static inline void modify_gpio(void __iomem *reg,
344                                unsigned long set, unsigned long mask)
345 {
346         unsigned long tmp;
347         tmp = readl(reg) & ~mask;
348         writel(tmp | set, reg);
349 }
350
351 /*
352  * Initialise LCD-related registers
353  */
354 static int nuc900fb_init_registers(struct fb_info *info)
355 {
356         struct nuc900fb_info *fbi = info->par;
357         struct nuc900fb_mach_info *mach_info = dev_get_platdata(fbi->dev);
358         void __iomem *regs = fbi->io;
359
360         /*reset the display engine*/
361         writel(0, regs + REG_LCM_DCCS);
362         writel(readl(regs + REG_LCM_DCCS) | LCM_DCCS_ENG_RST,
363                regs + REG_LCM_DCCS);
364         ndelay(100);
365         writel(readl(regs + REG_LCM_DCCS) & (~LCM_DCCS_ENG_RST),
366                regs + REG_LCM_DCCS);
367         ndelay(100);
368
369         writel(0, regs + REG_LCM_DEV_CTRL);
370
371         /* config gpio output */
372         modify_gpio(W90X900_VA_GPIO + 0x54, mach_info->gpio_dir,
373                     mach_info->gpio_dir_mask);
374         modify_gpio(W90X900_VA_GPIO + 0x58, mach_info->gpio_data,
375                     mach_info->gpio_data_mask);
376
377         return 0;
378 }
379
380
381 /*
382  *    Alloc the SDRAM region of NUC900 for the frame buffer.
383  *    The buffer should be a non-cached, non-buffered, memory region
384  *    to allow palette and pixel writes without flushing the cache.
385  */
386 static int nuc900fb_map_video_memory(struct fb_info *info)
387 {
388         struct nuc900fb_info *fbi = info->par;
389         dma_addr_t map_dma;
390         unsigned long map_size = PAGE_ALIGN(info->fix.smem_len);
391
392         dev_dbg(fbi->dev, "nuc900fb_map_video_memory(fbi=%p) map_size %lu\n",
393                 fbi, map_size);
394
395         info->screen_base = dma_alloc_wc(fbi->dev, map_size, &map_dma,
396                                          GFP_KERNEL);
397
398         if (!info->screen_base)
399                 return -ENOMEM;
400
401         memset(info->screen_base, 0x00, map_size);
402         info->fix.smem_start = map_dma;
403
404         return 0;
405 }
406
407 static inline void nuc900fb_unmap_video_memory(struct fb_info *info)
408 {
409         struct nuc900fb_info *fbi = info->par;
410         dma_free_wc(fbi->dev, PAGE_ALIGN(info->fix.smem_len),
411                     info->screen_base, info->fix.smem_start);
412 }
413
414 static irqreturn_t nuc900fb_irqhandler(int irq, void *dev_id)
415 {
416         struct nuc900fb_info *fbi = dev_id;
417         void __iomem *regs = fbi->io;
418         void __iomem *irq_base = fbi->irq_base;
419         unsigned long lcdirq = readl(regs + REG_LCM_INT_CS);
420
421         if (lcdirq & LCM_INT_CS_DISP_F_STATUS) {
422                 writel(readl(irq_base) | 1<<30, irq_base);
423
424                 /* wait VA_EN low */
425                 if ((readl(regs + REG_LCM_DCCS) &
426                     LCM_DCCS_SINGLE) == LCM_DCCS_SINGLE)
427                         while ((readl(regs + REG_LCM_DCCS) &
428                                LCM_DCCS_VA_EN) == LCM_DCCS_VA_EN)
429                                 ;
430                 /* display_out-enable */
431                 writel(readl(regs + REG_LCM_DCCS) | LCM_DCCS_DISP_OUT_EN,
432                         regs + REG_LCM_DCCS);
433                 /* va-enable*/
434                 writel(readl(regs + REG_LCM_DCCS) | LCM_DCCS_VA_EN,
435                         regs + REG_LCM_DCCS);
436         } else if (lcdirq & LCM_INT_CS_UNDERRUN_INT) {
437                 writel(readl(irq_base) | LCM_INT_CS_UNDERRUN_INT, irq_base);
438         } else if (lcdirq & LCM_INT_CS_BUS_ERROR_INT) {
439                 writel(readl(irq_base) | LCM_INT_CS_BUS_ERROR_INT, irq_base);
440         }
441
442         return IRQ_HANDLED;
443 }
444
445 #ifdef CONFIG_CPU_FREQ
446
447 static int nuc900fb_cpufreq_transition(struct notifier_block *nb,
448                                        unsigned long val, void *data)
449 {
450         struct nuc900fb_info *info;
451         struct fb_info *fbinfo;
452         long delta_f;
453         info = container_of(nb, struct nuc900fb_info, freq_transition);
454         fbinfo = dev_get_drvdata(info->dev);
455
456         delta_f = info->clk_rate - clk_get_rate(info->clk);
457
458         if ((val == CPUFREQ_POSTCHANGE && delta_f > 0) ||
459            (val == CPUFREQ_PRECHANGE && delta_f < 0)) {
460                 info->clk_rate = clk_get_rate(info->clk);
461                 nuc900fb_activate_var(fbinfo);
462         }
463
464         return 0;
465 }
466
467 static inline int nuc900fb_cpufreq_register(struct nuc900fb_info *fbi)
468 {
469         fbi->freq_transition.notifier_call = nuc900fb_cpufreq_transition;
470         return cpufreq_register_notifier(&fbi->freq_transition,
471                                   CPUFREQ_TRANSITION_NOTIFIER);
472 }
473
474 static inline void nuc900fb_cpufreq_deregister(struct nuc900fb_info *fbi)
475 {
476         cpufreq_unregister_notifier(&fbi->freq_transition,
477                                     CPUFREQ_TRANSITION_NOTIFIER);
478 }
479 #else
480 static inline int nuc900fb_cpufreq_transition(struct notifier_block *nb,
481                                        unsigned long val, void *data)
482 {
483         return 0;
484 }
485
486 static inline int nuc900fb_cpufreq_register(struct nuc900fb_info *fbi)
487 {
488         return 0;
489 }
490
491 static inline void nuc900fb_cpufreq_deregister(struct nuc900fb_info *info)
492 {
493 }
494 #endif
495
496 static char driver_name[] = "nuc900fb";
497
498 static int nuc900fb_probe(struct platform_device *pdev)
499 {
500         struct nuc900fb_info *fbi;
501         struct nuc900fb_display *display;
502         struct fb_info     *fbinfo;
503         struct nuc900fb_mach_info *mach_info;
504         struct resource *res;
505         int ret;
506         int irq;
507         int i;
508         int size;
509
510         dev_dbg(&pdev->dev, "devinit\n");
511         mach_info = dev_get_platdata(&pdev->dev);
512         if (mach_info == NULL) {
513                 dev_err(&pdev->dev,
514                         "no platform data for lcd, cannot attach\n");
515                 return -EINVAL;
516         }
517
518         if (mach_info->default_display > mach_info->num_displays) {
519                 dev_err(&pdev->dev,
520                         "default display No. is %d but only %d displays \n",
521                         mach_info->default_display, mach_info->num_displays);
522                 return -EINVAL;
523         }
524
525
526         display = mach_info->displays + mach_info->default_display;
527
528         irq = platform_get_irq(pdev, 0);
529         if (irq < 0) {
530                 dev_err(&pdev->dev, "no irq for device\n");
531                 return -ENOENT;
532         }
533
534         fbinfo = framebuffer_alloc(sizeof(struct nuc900fb_info), &pdev->dev);
535         if (!fbinfo)
536                 return -ENOMEM;
537
538         platform_set_drvdata(pdev, fbinfo);
539
540         fbi = fbinfo->par;
541         fbi->dev = &pdev->dev;
542
543 #ifdef CONFIG_CPU_NUC950
544         fbi->drv_type = LCDDRV_NUC950;
545 #endif
546
547         res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
548
549         size = resource_size(res);
550         fbi->mem = request_mem_region(res->start, size, pdev->name);
551         if (fbi->mem == NULL) {
552                 dev_err(&pdev->dev, "failed to alloc memory region\n");
553                 ret = -ENOENT;
554                 goto free_fb;
555         }
556
557         fbi->io = ioremap(res->start, size);
558         if (fbi->io == NULL) {
559                 dev_err(&pdev->dev, "ioremap() of lcd registers failed\n");
560                 ret = -ENXIO;
561                 goto release_mem_region;
562         }
563
564         fbi->irq_base = fbi->io + REG_LCM_INT_CS;
565
566
567         /* Stop the LCD */
568         writel(0, fbi->io + REG_LCM_DCCS);
569
570         /* fill the fbinfo*/
571         strcpy(fbinfo->fix.id, driver_name);
572         fbinfo->fix.type                = FB_TYPE_PACKED_PIXELS;
573         fbinfo->fix.type_aux            = 0;
574         fbinfo->fix.xpanstep            = 0;
575         fbinfo->fix.ypanstep            = 0;
576         fbinfo->fix.ywrapstep           = 0;
577         fbinfo->fix.accel               = FB_ACCEL_NONE;
578         fbinfo->var.nonstd              = 0;
579         fbinfo->var.activate            = FB_ACTIVATE_NOW;
580         fbinfo->var.accel_flags         = 0;
581         fbinfo->var.vmode               = FB_VMODE_NONINTERLACED;
582         fbinfo->fbops                   = &nuc900fb_ops;
583         fbinfo->flags                   = FBINFO_FLAG_DEFAULT;
584         fbinfo->pseudo_palette          = &fbi->pseudo_pal;
585
586         ret = request_irq(irq, nuc900fb_irqhandler, 0, pdev->name, fbi);
587         if (ret) {
588                 dev_err(&pdev->dev, "cannot register irq handler %d -err %d\n",
589                         irq, ret);
590                 ret = -EBUSY;
591                 goto release_regs;
592         }
593
594         fbi->clk = clk_get(&pdev->dev, NULL);
595         if (IS_ERR(fbi->clk)) {
596                 printk(KERN_ERR "nuc900-lcd:failed to get lcd clock source\n");
597                 ret = PTR_ERR(fbi->clk);
598                 goto release_irq;
599         }
600
601         clk_enable(fbi->clk);
602         dev_dbg(&pdev->dev, "got and enabled clock\n");
603
604         fbi->clk_rate = clk_get_rate(fbi->clk);
605
606         /* calutate the video buffer size */
607         for (i = 0; i < mach_info->num_displays; i++) {
608                 unsigned long smem_len = mach_info->displays[i].xres;
609                 smem_len *= mach_info->displays[i].yres;
610                 smem_len *= mach_info->displays[i].bpp;
611                 smem_len >>= 3;
612                 if (fbinfo->fix.smem_len < smem_len)
613                         fbinfo->fix.smem_len = smem_len;
614         }
615
616         /* Initialize Video Memory */
617         ret = nuc900fb_map_video_memory(fbinfo);
618         if (ret) {
619                 printk(KERN_ERR "Failed to allocate video RAM: %x\n", ret);
620                 goto release_clock;
621         }
622
623         dev_dbg(&pdev->dev, "got video memory\n");
624
625         fbinfo->var.xres = display->xres;
626         fbinfo->var.yres = display->yres;
627         fbinfo->var.bits_per_pixel = display->bpp;
628
629         nuc900fb_init_registers(fbinfo);
630
631         nuc900fb_check_var(&fbinfo->var, fbinfo);
632
633         ret = nuc900fb_cpufreq_register(fbi);
634         if (ret < 0) {
635                 dev_err(&pdev->dev, "Failed to register cpufreq\n");
636                 goto free_video_memory;
637         }
638
639         ret = register_framebuffer(fbinfo);
640         if (ret) {
641                 printk(KERN_ERR "failed to register framebuffer device: %d\n",
642                         ret);
643                 goto free_cpufreq;
644         }
645
646         fb_info(fbinfo, "%s frame buffer device\n", fbinfo->fix.id);
647
648         return 0;
649
650 free_cpufreq:
651         nuc900fb_cpufreq_deregister(fbi);
652 free_video_memory:
653         nuc900fb_unmap_video_memory(fbinfo);
654 release_clock:
655         clk_disable(fbi->clk);
656         clk_put(fbi->clk);
657 release_irq:
658         free_irq(irq, fbi);
659 release_regs:
660         iounmap(fbi->io);
661 release_mem_region:
662         release_mem_region(res->start, size);
663 free_fb:
664         framebuffer_release(fbinfo);
665         return ret;
666 }
667
668 /*
669  * shutdown the lcd controller
670  */
671 static void nuc900fb_stop_lcd(struct fb_info *info)
672 {
673         struct nuc900fb_info *fbi = info->par;
674         void __iomem *regs = fbi->io;
675
676         writel((~LCM_DCCS_DISP_INT_EN) | (~LCM_DCCS_VA_EN) | (~LCM_DCCS_OSD_EN),
677                 regs + REG_LCM_DCCS);
678 }
679
680 /*
681  *  Cleanup
682  */
683 static int nuc900fb_remove(struct platform_device *pdev)
684 {
685         struct fb_info *fbinfo = platform_get_drvdata(pdev);
686         struct nuc900fb_info *fbi = fbinfo->par;
687         int irq;
688
689         nuc900fb_stop_lcd(fbinfo);
690         msleep(1);
691
692         unregister_framebuffer(fbinfo);
693         nuc900fb_cpufreq_deregister(fbi);
694         nuc900fb_unmap_video_memory(fbinfo);
695
696         iounmap(fbi->io);
697
698         irq = platform_get_irq(pdev, 0);
699         free_irq(irq, fbi);
700
701         release_resource(fbi->mem);
702         kfree(fbi->mem);
703
704         framebuffer_release(fbinfo);
705
706         return 0;
707 }
708
709 #ifdef CONFIG_PM
710
711 /*
712  *      suspend and resume support for the lcd controller
713  */
714
715 static int nuc900fb_suspend(struct platform_device *dev, pm_message_t state)
716 {
717         struct fb_info     *fbinfo = platform_get_drvdata(dev);
718         struct nuc900fb_info *info = fbinfo->par;
719
720         nuc900fb_stop_lcd(fbinfo);
721         msleep(1);
722         clk_disable(info->clk);
723         return 0;
724 }
725
726 static int nuc900fb_resume(struct platform_device *dev)
727 {
728         struct fb_info     *fbinfo = platform_get_drvdata(dev);
729         struct nuc900fb_info *fbi = fbinfo->par;
730
731         printk(KERN_INFO "nuc900fb resume\n");
732
733         clk_enable(fbi->clk);
734         msleep(1);
735
736         nuc900fb_init_registers(fbinfo);
737         nuc900fb_activate_var(fbinfo);
738
739         return 0;
740 }
741
742 #else
743 #define nuc900fb_suspend NULL
744 #define nuc900fb_resume  NULL
745 #endif
746
747 static struct platform_driver nuc900fb_driver = {
748         .probe          = nuc900fb_probe,
749         .remove         = nuc900fb_remove,
750         .suspend        = nuc900fb_suspend,
751         .resume         = nuc900fb_resume,
752         .driver         = {
753                 .name   = "nuc900-lcd",
754         },
755 };
756
757 module_platform_driver(nuc900fb_driver);
758
759 MODULE_DESCRIPTION("Framebuffer driver for the NUC900");
760 MODULE_LICENSE("GPL");