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