au1100fb.c 20.2 KB
Newer Older
L
Linus Torvalds 已提交
1 2 3 4
/*
 * BRIEF MODULE DESCRIPTION
 *	Au1100 LCD Driver.
 *
P
Pete Popov 已提交
5 6 7 8 9
 * Rewritten for 2.6 by Embedded Alley Solutions
 * 	<source@embeddedalley.com>, based on submissions by
 *  	Karl Lessard <klessard@sunrisetelecom.com>
 *  	<c.pellegrin@exadron.com>
 *
10
 * PM support added by Rodolfo Giometti <giometti@linux.it>
11
 * Cursor enable/disable by Rodolfo Giometti <giometti@linux.it>
12
 *
L
Linus Torvalds 已提交
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
 * Copyright 2002 MontaVista Software
 * Author: MontaVista Software, Inc.
 *		ppopov@mvista.com or source@mvista.com
 *
 * Copyright 2002 Alchemy Semiconductor
 * Author: Alchemy Semiconductor
 *
 * Based on:
 * linux/drivers/video/skeletonfb.c -- Skeleton for a frame buffer device
 *  Created 28 Dec 1997 by Geert Uytterhoeven
 *
 *  This program is free software; you can redistribute	 it and/or modify it
 *  under  the terms of	 the GNU General  Public License as published by the
 *  Free Software Foundation;  either version 2 of the	License, or (at your
 *  option) any later version.
 *
 *  THIS  SOFTWARE  IS PROVIDED	  ``AS	IS'' AND   ANY	EXPRESS OR IMPLIED
 *  WARRANTIES,	  INCLUDING, BUT NOT  LIMITED  TO, THE IMPLIED WARRANTIES OF
 *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
 *  NO	EVENT  SHALL   THE AUTHOR  BE	 LIABLE FOR ANY	  DIRECT, INDIRECT,
 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 *  NOT LIMITED	  TO, PROCUREMENT OF  SUBSTITUTE GOODS	OR SERVICES; LOSS OF
 *  USE, DATA,	OR PROFITS; OR	BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 *  ANY THEORY OF LIABILITY, WHETHER IN	 CONTRACT, STRICT LIABILITY, OR TORT
 *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 *  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *  You should have received a copy of the  GNU General Public License along
 *  with this program; if not, write  to the Free Software Foundation, Inc.,
 *  675 Mass Ave, Cambridge, MA 02139, USA.
 */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/fb.h>
#include <linux/init.h>
P
Pete Popov 已提交
51 52 53
#include <linux/interrupt.h>
#include <linux/ctype.h>
#include <linux/dma-mapping.h>
54
#include <linux/platform_device.h>
L
Linus Torvalds 已提交
55

P
Pete Popov 已提交
56
#include <asm/mach-au1x00/au1000.h>
L
Linus Torvalds 已提交
57

P
Pete Popov 已提交
58 59 60
#define DEBUG 0

#include "au1100fb.h"
L
Linus Torvalds 已提交
61 62 63 64 65

/*
 * Sanity check. If this is a new Au1100 based board, search for
 * the PB1100 ifdefs to make sure you modify the code accordingly.
 */
P
Pete Popov 已提交
66 67 68 69
#if defined(CONFIG_MIPS_PB1100)
  #include <asm/mach-pb1x00/pb1100.h>
#elif defined(CONFIG_MIPS_DB1100)
  #include <asm/mach-db1x00/db1x00.h>
L
Linus Torvalds 已提交
70
#else
P
Pete Popov 已提交
71
  #error "Unknown Au1100 board, Au1100 FB driver not supported"
L
Linus Torvalds 已提交
72 73
#endif

P
Pete Popov 已提交
74 75
#define DRIVER_NAME "au1100fb"
#define DRIVER_DESC "LCD controller driver for AU1100 processors"
L
Linus Torvalds 已提交
76

P
Pete Popov 已提交
77 78
#define to_au1100fb_device(_info) \
	  (_info ? container_of(_info, struct au1100fb_device, info) : NULL);
L
Linus Torvalds 已提交
79

P
Pete Popov 已提交
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
/* Bitfields format supported by the controller. Note that the order of formats
 * SHOULD be the same as in the LCD_CONTROL_SBPPF field, so we can retrieve the
 * right pixel format by doing rgb_bitfields[LCD_CONTROL_SBPPF_XXX >> LCD_CONTROL_SBPPF]
 */
struct fb_bitfield rgb_bitfields[][4] =
{
  	/*     Red, 	   Green, 	 Blue, 	     Transp   */
	{ { 10, 6, 0 }, { 5, 5, 0 }, { 0, 5, 0 }, { 0, 0, 0 } },
	{ { 11, 5, 0 }, { 5, 6, 0 }, { 0, 5, 0 }, { 0, 0, 0 } },
	{ { 11, 5, 0 }, { 6, 5, 0 }, { 0, 6, 0 }, { 0, 0, 0 } },
	{ { 10, 5, 0 }, { 5, 5, 0 }, { 0, 5, 0 }, { 15, 1, 0 } },
	{ { 11, 5, 0 }, { 6, 5, 0 }, { 1, 5, 0 }, { 0, 1, 0 } },

	/* The last is used to describe 12bpp format */
	{ { 8, 4, 0 },  { 4, 4, 0 }, { 0, 4, 0 }, { 0, 0, 0 } },
L
Linus Torvalds 已提交
95 96
};

P
Pete Popov 已提交
97 98 99 100 101 102
static struct fb_fix_screeninfo au1100fb_fix __initdata = {
	.id		= "AU1100 FB",
	.xpanstep 	= 1,
	.ypanstep 	= 1,
	.type		= FB_TYPE_PACKED_PIXELS,
	.accel		= FB_ACCEL_NONE,
L
Linus Torvalds 已提交
103 104
};

P
Pete Popov 已提交
105 106 107 108 109
static struct fb_var_screeninfo au1100fb_var __initdata = {
	.activate	= FB_ACTIVATE_NOW,
	.height		= -1,
	.width		= -1,
	.vmode		= FB_VMODE_NONINTERLACED,
L
Linus Torvalds 已提交
110 111
};

P
Pete Popov 已提交
112
static struct au1100fb_drv_info drv_info;
L
Linus Torvalds 已提交
113

114 115 116 117
static int nocursor = 0;
module_param(nocursor, int, 0644);
MODULE_PARM_DESC(nocursor, "cursor enable/disable");

118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
/* fb_blank
 * Blank the screen. Depending on the mode, the screen will be
 * activated with the backlight color, or desactivated
 */
static int au1100fb_fb_blank(int blank_mode, struct fb_info *fbi)
{
	struct au1100fb_device *fbdev = to_au1100fb_device(fbi);

	print_dbg("fb_blank %d %p", blank_mode, fbi);

	switch (blank_mode) {

	case VESA_NO_BLANKING:
			/* Turn on panel */
			fbdev->regs->lcd_control |= LCD_CONTROL_GO;
#ifdef CONFIG_MIPS_PB1100
			if (drv_info.panel_idx == 1) {
				au_writew(au_readw(PB1100_G_CONTROL)
					  | (PB1100_G_CONTROL_BL | PB1100_G_CONTROL_VDD),
			PB1100_G_CONTROL);
			}
#endif
		au_sync();
		break;

	case VESA_VSYNC_SUSPEND:
	case VESA_HSYNC_SUSPEND:
	case VESA_POWERDOWN:
			/* Turn off panel */
			fbdev->regs->lcd_control &= ~LCD_CONTROL_GO;
#ifdef CONFIG_MIPS_PB1100
			if (drv_info.panel_idx == 1) {
				au_writew(au_readw(PB1100_G_CONTROL)
				  	  & ~(PB1100_G_CONTROL_BL | PB1100_G_CONTROL_VDD),
			PB1100_G_CONTROL);
			}
#endif
		au_sync();
		break;
	default:
		break;

	}
	return 0;
}

P
Pete Popov 已提交
164 165 166
/*
 * Set hardware with var settings. This will enable the controller with a specific
 * mode, normally validated with the fb_check_var method
L
Linus Torvalds 已提交
167
	 */
P
Pete Popov 已提交
168
int au1100fb_setmode(struct au1100fb_device *fbdev)
L
Linus Torvalds 已提交
169
{
P
Pete Popov 已提交
170 171 172
	struct fb_info *info = &fbdev->info;
	u32 words;
	int index;
L
Linus Torvalds 已提交
173

P
Pete Popov 已提交
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
	if (!fbdev)
		return -EINVAL;

	/* Update var-dependent FB info */
	if (panel_is_active(fbdev->panel) || panel_is_color(fbdev->panel)) {
		if (info->var.bits_per_pixel <= 8) {
			/* palettized */
			info->var.red.offset    = 0;
			info->var.red.length    = info->var.bits_per_pixel;
			info->var.red.msb_right = 0;

			info->var.green.offset  = 0;
			info->var.green.length  = info->var.bits_per_pixel;
			info->var.green.msb_right = 0;

			info->var.blue.offset   = 0;
			info->var.blue.length   = info->var.bits_per_pixel;
			info->var.blue.msb_right = 0;

			info->var.transp.offset = 0;
			info->var.transp.length = 0;
			info->var.transp.msb_right = 0;

			info->fix.visual = FB_VISUAL_PSEUDOCOLOR;
			info->fix.line_length = info->var.xres_virtual /
							(8/info->var.bits_per_pixel);
		} else {
			/* non-palettized */
			index = (fbdev->panel->control_base & LCD_CONTROL_SBPPF_MASK) >> LCD_CONTROL_SBPPF_BIT;
			info->var.red = rgb_bitfields[index][0];
			info->var.green = rgb_bitfields[index][1];
			info->var.blue = rgb_bitfields[index][2];
			info->var.transp = rgb_bitfields[index][3];

			info->fix.visual = FB_VISUAL_TRUECOLOR;
			info->fix.line_length = info->var.xres_virtual << 1; /* depth=16 */
210
		}
P
Pete Popov 已提交
211 212 213 214
	} else {
		/* mono */
		info->fix.visual = FB_VISUAL_MONO10;
		info->fix.line_length = info->var.xres_virtual / 8;
L
Linus Torvalds 已提交
215 216
	}

P
Pete Popov 已提交
217
	info->screen_size = info->fix.line_length * info->var.yres_virtual;
218 219
	info->var.rotate = ((fbdev->panel->control_base&LCD_CONTROL_SM_MASK) \
				>> LCD_CONTROL_SM_BIT) * 90;
L
Linus Torvalds 已提交
220

P
Pete Popov 已提交
221
	/* Determine BPP mode and format */
222
	fbdev->regs->lcd_control = fbdev->panel->control_base;
P
Pete Popov 已提交
223 224 225
	fbdev->regs->lcd_horztiming = fbdev->panel->horztiming;
	fbdev->regs->lcd_verttiming = fbdev->panel->verttiming;
	fbdev->regs->lcd_clkcontrol = fbdev->panel->clkcontrol_base;
226 227
	fbdev->regs->lcd_intenable = 0;
	fbdev->regs->lcd_intstatus = 0;
P
Pete Popov 已提交
228 229 230 231 232 233 234 235 236 237 238 239
	fbdev->regs->lcd_dmaaddr0 = LCD_DMA_SA_N(fbdev->fb_phys);

	if (panel_is_dual(fbdev->panel)) {
		/* Second panel display seconf half of screen if possible,
		 * otherwise display the same as the first panel */
		if (info->var.yres_virtual >= (info->var.yres << 1)) {
			fbdev->regs->lcd_dmaaddr1 = LCD_DMA_SA_N(fbdev->fb_phys +
							  (info->fix.line_length *
						          (info->var.yres_virtual >> 1)));
		} else {
			fbdev->regs->lcd_dmaaddr1 = LCD_DMA_SA_N(fbdev->fb_phys);
		}
L
Linus Torvalds 已提交
240 241
	}

P
Pete Popov 已提交
242 243 244 245 246 247 248 249
	words = info->fix.line_length / sizeof(u32);
	if (!info->var.rotate || (info->var.rotate == 180)) {
		words *= info->var.yres_virtual;
		if (info->var.rotate /* 180 */) {
			words -= (words % 8); /* should be divisable by 8 */
		}
	}
	fbdev->regs->lcd_words = LCD_WRD_WRDS_N(words);
L
Linus Torvalds 已提交
250

P
Pete Popov 已提交
251 252
	fbdev->regs->lcd_pwmdiv = 0;
	fbdev->regs->lcd_pwmhi = 0;
L
Linus Torvalds 已提交
253

P
Pete Popov 已提交
254 255
	/* Resume controller */
	fbdev->regs->lcd_control |= LCD_CONTROL_GO;
256 257
	mdelay(10);
	au1100fb_fb_blank(VESA_NO_BLANKING, info);
L
Linus Torvalds 已提交
258

P
Pete Popov 已提交
259
	return 0;
L
Linus Torvalds 已提交
260 261
}

P
Pete Popov 已提交
262 263 264 265
/* fb_setcolreg
 * Set color in LCD palette.
 */
int au1100fb_fb_setcolreg(unsigned regno, unsigned red, unsigned green, unsigned blue, unsigned transp, struct fb_info *fbi)
L
Linus Torvalds 已提交
266
{
267 268
	struct au1100fb_device *fbdev;
	u32 *palette;
P
Pete Popov 已提交
269
	u32 value;
L
Linus Torvalds 已提交
270

271 272 273
	fbdev = to_au1100fb_device(fbi);
	palette = fbdev->regs->lcd_pallettebase;

P
Pete Popov 已提交
274 275
	if (regno > (AU1100_LCD_NBR_PALETTE_ENTRIES - 1))
		return -EINVAL;
L
Linus Torvalds 已提交
276

P
Pete Popov 已提交
277 278 279 280 281
	if (fbi->var.grayscale) {
		/* Convert color to grayscale */
		red = green = blue =
			(19595 * red + 38470 * green + 7471 * blue) >> 16;
	}
L
Linus Torvalds 已提交
282

P
Pete Popov 已提交
283 284 285 286
	if (fbi->fix.visual == FB_VISUAL_TRUECOLOR) {
		/* Place color in the pseudopalette */
		if (regno > 16)
			return -EINVAL;
L
Linus Torvalds 已提交
287

P
Pete Popov 已提交
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313
		palette = (u32*)fbi->pseudo_palette;

		red   >>= (16 - fbi->var.red.length);
		green >>= (16 - fbi->var.green.length);
		blue  >>= (16 - fbi->var.blue.length);

		value = (red   << fbi->var.red.offset) 	|
			(green << fbi->var.green.offset)|
			(blue  << fbi->var.blue.offset);
		value &= 0xFFFF;

	} else if (panel_is_active(fbdev->panel)) {
		/* COLOR TFT PALLETTIZED (use RGB 565) */
		value = (red & 0xF800)|((green >> 5) & 0x07E0)|((blue >> 11) & 0x001F);
		value &= 0xFFFF;

	} else if (panel_is_color(fbdev->panel)) {
		/* COLOR STN MODE */
		value = (((panel_swap_rgb(fbdev->panel) ? blue : red) >> 12) & 0x000F) |
			((green >> 8) & 0x00F0) |
			(((panel_swap_rgb(fbdev->panel) ? red : blue) >> 4) & 0x0F00);
		value &= 0xFFF;
	} else {
		/* MONOCHROME MODE */
		value = (green >> 12) & 0x000F;
		value &= 0xF;
L
Linus Torvalds 已提交
314 315
	}

P
Pete Popov 已提交
316 317
	palette[regno] = value;

L
Linus Torvalds 已提交
318 319 320
	return 0;
}

P
Pete Popov 已提交
321 322 323 324
/* fb_pan_display
 * Pan display in x and/or y as specified
 */
int au1100fb_fb_pan_display(struct fb_var_screeninfo *var, struct fb_info *fbi)
L
Linus Torvalds 已提交
325
{
326
	struct au1100fb_device *fbdev;
P
Pete Popov 已提交
327 328
	int dy;

329 330
	fbdev = to_au1100fb_device(fbi);

P
Pete Popov 已提交
331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384
	print_dbg("fb_pan_display %p %p", var, fbi);

	if (!var || !fbdev) {
		return -EINVAL;
	}

	if (var->xoffset - fbi->var.xoffset) {
		/* No support for X panning for now! */
		return -EINVAL;
	}

	print_dbg("fb_pan_display 2 %p %p", var, fbi);
	dy = var->yoffset - fbi->var.yoffset;
	if (dy) {

		u32 dmaaddr;

		print_dbg("Panning screen of %d lines", dy);

		dmaaddr = fbdev->regs->lcd_dmaaddr0;
		dmaaddr += (fbi->fix.line_length * dy);

		/* TODO: Wait for current frame to finished */
		fbdev->regs->lcd_dmaaddr0 = LCD_DMA_SA_N(dmaaddr);

		if (panel_is_dual(fbdev->panel)) {
			dmaaddr = fbdev->regs->lcd_dmaaddr1;
			dmaaddr += (fbi->fix.line_length * dy);
			fbdev->regs->lcd_dmaaddr0 = LCD_DMA_SA_N(dmaaddr);
	}
	}
	print_dbg("fb_pan_display 3 %p %p", var, fbi);

	return 0;
}

/* fb_rotate
 * Rotate the display of this angle. This doesn't seems to be used by the core,
 * but as our hardware supports it, so why not implementing it...
 */
void au1100fb_fb_rotate(struct fb_info *fbi, int angle)
{
	struct au1100fb_device *fbdev = to_au1100fb_device(fbi);

	print_dbg("fb_rotate %p %d", fbi, angle);

	if (fbdev && (angle > 0) && !(angle % 90)) {

		fbdev->regs->lcd_control &= ~LCD_CONTROL_GO;

		fbdev->regs->lcd_control &= ~(LCD_CONTROL_SM_MASK);
		fbdev->regs->lcd_control |= ((angle/90) << LCD_CONTROL_SM_BIT);

		fbdev->regs->lcd_control |= LCD_CONTROL_GO;
L
Linus Torvalds 已提交
385 386 387
	}
}

P
Pete Popov 已提交
388 389 390 391
/* fb_mmap
 * Map video memory in user space. We don't use the generic fb_mmap method mainly
 * to allow the use of the TLB streaming flag (CCA=6)
 */
392
int au1100fb_fb_mmap(struct fb_info *fbi, struct vm_area_struct *vma)
L
Linus Torvalds 已提交
393
{
394
	struct au1100fb_device *fbdev;
L
Linus Torvalds 已提交
395 396 397
	unsigned int len;
	unsigned long start=0, off;

398 399
	fbdev = to_au1100fb_device(fbi);

L
Linus Torvalds 已提交
400 401 402 403
	if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT)) {
		return -EINVAL;
	}

P
Pete Popov 已提交
404 405
	start = fbdev->fb_phys & PAGE_MASK;
	len = PAGE_ALIGN((start & ~PAGE_MASK) + fbdev->fb_len);
L
Linus Torvalds 已提交
406 407 408 409 410 411 412 413 414 415

	off = vma->vm_pgoff << PAGE_SHIFT;

	if ((vma->vm_end - vma->vm_start + off) > len) {
		return -EINVAL;
	}

	off += start;
	vma->vm_pgoff = off >> PAGE_SHIFT;

P
Pete Popov 已提交
416
	vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
L
Linus Torvalds 已提交
417 418 419 420
	pgprot_val(vma->vm_page_prot) |= (6 << 9); //CCA=6

	vma->vm_flags |= VM_IO;

421
	if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT,
L
Linus Torvalds 已提交
422 423 424 425 426 427 428 429
				vma->vm_end - vma->vm_start,
				vma->vm_page_prot)) {
		return -EAGAIN;
	}

	return 0;
}

430 431 432 433 434 435 436 437 438 439 440
/* fb_cursor
 * Used to disable cursor drawing...
 */
int au1100fb_fb_cursor(struct fb_info *info, struct fb_cursor *cursor)
{
	if (nocursor)
		return 0;
	else
		return -EINVAL;	/* just to force soft_cursor() call */
}

P
Pete Popov 已提交
441
static struct fb_ops au1100fb_ops =
L
Linus Torvalds 已提交
442
{
P
Pete Popov 已提交
443 444 445 446 447 448 449 450 451
	.owner			= THIS_MODULE,
	.fb_setcolreg		= au1100fb_fb_setcolreg,
	.fb_blank		= au1100fb_fb_blank,
	.fb_pan_display		= au1100fb_fb_pan_display,
	.fb_fillrect		= cfb_fillrect,
	.fb_copyarea		= cfb_copyarea,
	.fb_imageblit		= cfb_imageblit,
	.fb_rotate		= au1100fb_fb_rotate,
	.fb_mmap		= au1100fb_fb_mmap,
452
	.fb_cursor		= au1100fb_fb_cursor,
P
Pete Popov 已提交
453
};
L
Linus Torvalds 已提交
454 455


P
Pete Popov 已提交
456
/*-------------------------------------------------------------------------*/
L
Linus Torvalds 已提交
457

P
Pete Popov 已提交
458
/* AU1100 LCD controller device driver */
L
Linus Torvalds 已提交
459

R
Ralf Baechle 已提交
460
static int __init au1100fb_drv_probe(struct device *dev)
L
Linus Torvalds 已提交
461
{
P
Pete Popov 已提交
462 463 464 465 466 467
	struct au1100fb_device *fbdev = NULL;
	struct resource *regs_res;
	unsigned long page;
	u32 sys_clksrc;

	if (!dev)
L
Linus Torvalds 已提交
468
			return -EINVAL;
P
Pete Popov 已提交
469 470

	/* Allocate new device private */
J
Jiri Slaby 已提交
471
	if (!(fbdev = kzalloc(sizeof(struct au1100fb_device), GFP_KERNEL))) {
P
Pete Popov 已提交
472 473
		print_err("fail to allocate device private record");
		return -ENOMEM;
L
Linus Torvalds 已提交
474 475
	}

P
Pete Popov 已提交
476
	fbdev->panel = &known_lcd_panels[drv_info.panel_idx];
L
Linus Torvalds 已提交
477

P
Pete Popov 已提交
478
	dev_set_drvdata(dev, (void*)fbdev);
L
Linus Torvalds 已提交
479

P
Pete Popov 已提交
480 481 482 483 484 485
	/* Allocate region for our registers and map them */
	if (!(regs_res = platform_get_resource(to_platform_device(dev),
					IORESOURCE_MEM, 0))) {
		print_err("fail to retrieve registers resource");
		return -EFAULT;
	}
L
Linus Torvalds 已提交
486

P
Pete Popov 已提交
487 488
	au1100fb_fix.mmio_start = regs_res->start;
	au1100fb_fix.mmio_len = regs_res->end - regs_res->start + 1;
L
Linus Torvalds 已提交
489

P
Pete Popov 已提交
490 491
	if (!request_mem_region(au1100fb_fix.mmio_start, au1100fb_fix.mmio_len,
				DRIVER_NAME)) {
492
		print_err("fail to lock memory region at 0x%08lx",
P
Pete Popov 已提交
493 494 495
				au1100fb_fix.mmio_start);
		return -EBUSY;
	}
L
Linus Torvalds 已提交
496

P
Pete Popov 已提交
497
	fbdev->regs = (struct au1100fb_regs*)KSEG1ADDR(au1100fb_fix.mmio_start);
L
Linus Torvalds 已提交
498

P
Pete Popov 已提交
499 500
	print_dbg("Register memory map at %p", fbdev->regs);
	print_dbg("phys=0x%08x, size=%d", fbdev->regs_phys, fbdev->regs_len);
L
Linus Torvalds 已提交
501 502 503



P
Pete Popov 已提交
504 505 506 507 508 509 510 511 512
	/* Allocate the framebuffer to the maximum screen size * nbr of video buffers */
	fbdev->fb_len = fbdev->panel->xres * fbdev->panel->yres *
		  	(fbdev->panel->bpp >> 3) * AU1100FB_NBR_VIDEO_BUFFERS;

	fbdev->fb_mem = dma_alloc_coherent(dev, PAGE_ALIGN(fbdev->fb_len),
					&fbdev->fb_phys, GFP_KERNEL);
	if (!fbdev->fb_mem) {
		print_err("fail to allocate frambuffer (size: %dK))",
			  fbdev->fb_len / 1024);
L
Linus Torvalds 已提交
513 514
		return -ENOMEM;
	}
P
Pete Popov 已提交
515 516 517

	au1100fb_fix.smem_start = fbdev->fb_phys;
	au1100fb_fix.smem_len = fbdev->fb_len;
L
Linus Torvalds 已提交
518 519 520 521 522

	/*
	 * Set page reserved so that mmap will work. This is necessary
	 * since we'll be remapping normal memory.
	 */
P
Pete Popov 已提交
523 524
	for (page = (unsigned long)fbdev->fb_mem;
	     page < PAGE_ALIGN((unsigned long)fbdev->fb_mem + fbdev->fb_len);
L
Linus Torvalds 已提交
525
	     page += PAGE_SIZE) {
P
Pete Popov 已提交
526 527 528
#if CONFIG_DMA_NONCOHERENT
		SetPageReserved(virt_to_page(CAC_ADDR(page)));
#else
L
Linus Torvalds 已提交
529
		SetPageReserved(virt_to_page(page));
P
Pete Popov 已提交
530
#endif
L
Linus Torvalds 已提交
531 532
	}

P
Pete Popov 已提交
533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550
	print_dbg("Framebuffer memory map at %p", fbdev->fb_mem);
	print_dbg("phys=0x%08x, size=%dK", fbdev->fb_phys, fbdev->fb_len / 1024);

	/* Setup LCD clock to AUX (48 MHz) */
	sys_clksrc = au_readl(SYS_CLKSRC) & ~(SYS_CS_ML_MASK | SYS_CS_DL | SYS_CS_CL);
	au_writel((sys_clksrc | (1 << SYS_CS_ML_BIT)), SYS_CLKSRC);

	/* load the panel info into the var struct */
	au1100fb_var.bits_per_pixel = fbdev->panel->bpp;
	au1100fb_var.xres = fbdev->panel->xres;
	au1100fb_var.xres_virtual = au1100fb_var.xres;
	au1100fb_var.yres = fbdev->panel->yres;
	au1100fb_var.yres_virtual = au1100fb_var.yres;

	fbdev->info.screen_base = fbdev->fb_mem;
	fbdev->info.fbops = &au1100fb_ops;
	fbdev->info.fix = au1100fb_fix;

J
Jiri Slaby 已提交
551
	if (!(fbdev->info.pseudo_palette = kzalloc(sizeof(u32) * 16, GFP_KERNEL))) {
P
Pete Popov 已提交
552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586
		return -ENOMEM;
	}

	if (fb_alloc_cmap(&fbdev->info.cmap, AU1100_LCD_NBR_PALETTE_ENTRIES, 0) < 0) {
		print_err("Fail to allocate colormap (%d entries)",
			   AU1100_LCD_NBR_PALETTE_ENTRIES);
		kfree(fbdev->info.pseudo_palette);
		return -EFAULT;
	}

	fbdev->info.var = au1100fb_var;

	/* Set h/w registers */
	au1100fb_setmode(fbdev);

	/* Register new framebuffer */
	if (register_framebuffer(&fbdev->info) < 0) {
		print_err("cannot register new framebuffer");
		goto failed;
	}

	return 0;

failed:
	if (fbdev->regs) {
		release_mem_region(fbdev->regs_phys, fbdev->regs_len);
	}
	if (fbdev->fb_mem) {
		dma_free_noncoherent(dev, fbdev->fb_len, fbdev->fb_mem, fbdev->fb_phys);
	}
	if (fbdev->info.cmap.len != 0) {
		fb_dealloc_cmap(&fbdev->info.cmap);
	}
	kfree(fbdev);
	dev_set_drvdata(dev, NULL);
L
Linus Torvalds 已提交
587 588 589 590

	return 0;
}

P
Pete Popov 已提交
591 592 593 594 595 596 597 598 599 600 601 602 603
int au1100fb_drv_remove(struct device *dev)
{
	struct au1100fb_device *fbdev = NULL;

	if (!dev)
		return -ENODEV;

	fbdev = (struct au1100fb_device*) dev_get_drvdata(dev);

#if !defined(CONFIG_FRAMEBUFFER_CONSOLE) && defined(CONFIG_LOGO)
	au1100fb_fb_blank(VESA_POWERDOWN, &fbdev->info);
#endif
	fbdev->regs->lcd_control &= ~LCD_CONTROL_GO;
L
Linus Torvalds 已提交
604

P
Pete Popov 已提交
605 606 607 608 609 610 611 612 613 614 615 616 617 618
	/* Clean up all probe data */
	unregister_framebuffer(&fbdev->info);

	release_mem_region(fbdev->regs_phys, fbdev->regs_len);

	dma_free_coherent(dev, PAGE_ALIGN(fbdev->fb_len), fbdev->fb_mem, fbdev->fb_phys);

	fb_dealloc_cmap(&fbdev->info.cmap);
	kfree(fbdev->info.pseudo_palette);
	kfree((void*)fbdev);

	return 0;
}

619 620 621 622
#ifdef CONFIG_PM
static u32 sys_clksrc;
static struct au1100fb_regs fbregs;

623
int au1100fb_drv_suspend(struct device *dev, pm_message_t state)
P
Pete Popov 已提交
624
{
625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640
	struct au1100fb_device *fbdev = dev_get_drvdata(dev);

	if (!fbdev)
		return 0;

	/* Save the clock source state */
	sys_clksrc = au_readl(SYS_CLKSRC);

	/* Blank the LCD */
	au1100fb_fb_blank(VESA_POWERDOWN, &fbdev->info);

	/* Stop LCD clocking */
	au_writel(sys_clksrc & ~SYS_CS_ML_MASK, SYS_CLKSRC);

	memcpy(&fbregs, fbdev->regs, sizeof(struct au1100fb_regs));

P
Pete Popov 已提交
641 642 643
	return 0;
}

644
int au1100fb_drv_resume(struct device *dev)
L
Linus Torvalds 已提交
645
{
646 647 648 649 650 651 652 653 654 655 656 657 658
	struct au1100fb_device *fbdev = dev_get_drvdata(dev);

	if (!fbdev)
		return 0;

	memcpy(fbdev->regs, &fbregs, sizeof(struct au1100fb_regs));

	/* Restart LCD clocking */
	au_writel(sys_clksrc, SYS_CLKSRC);

	/* Unblank the LCD */
	au1100fb_fb_blank(VESA_NO_BLANKING, &fbdev->info);

P
Pete Popov 已提交
659
	return 0;
L
Linus Torvalds 已提交
660
}
661 662 663 664
#else
#define au1100fb_drv_suspend NULL
#define au1100fb_drv_resume NULL
#endif
L
Linus Torvalds 已提交
665

P
Pete Popov 已提交
666 667 668
static struct device_driver au1100fb_driver = {
	.name		= "au1100-lcd",
	.bus		= &platform_bus_type,
L
Linus Torvalds 已提交
669

P
Pete Popov 已提交
670 671 672 673 674 675 676 677 678 679 680
	.probe		= au1100fb_drv_probe,
        .remove		= au1100fb_drv_remove,
	.suspend	= au1100fb_drv_suspend,
        .resume		= au1100fb_drv_resume,
};

/*-------------------------------------------------------------------------*/

/* Kernel driver */

int au1100fb_setup(char *options)
L
Linus Torvalds 已提交
681 682
{
	char* this_opt;
P
Pete Popov 已提交
683 684 685
	int num_panels = ARRAY_SIZE(known_lcd_panels);
	char* mode = NULL;
	int panel_idx = 0;
L
Linus Torvalds 已提交
686

P
Pete Popov 已提交
687 688 689 690
	if (num_panels <= 0) {
		print_err("No LCD panels supported by driver!");
		return -EFAULT;
			}
L
Linus Torvalds 已提交
691

P
Pete Popov 已提交
692 693 694
	if (options) {
		while ((this_opt = strsep(&options,",")) != NULL) {
			/* Panel option */
695
			if (!strncmp(this_opt, "panel:", 6)) {
P
Pete Popov 已提交
696 697 698 699 700
				int i;
				this_opt += 6;
				for (i = 0; i < num_panels; i++) {
					if (!strncmp(this_opt,
					      	     known_lcd_panels[i].name,
L
Linus Torvalds 已提交
701
							strlen(this_opt))) {
P
Pete Popov 已提交
702
						panel_idx = i;
703 704
						break;
					}
L
Linus Torvalds 已提交
705
				}
P
Pete Popov 已提交
706 707 708 709
				if (i >= num_panels) {
 					print_warn("Panel %s not supported!", this_opt);
				}
			}
710 711 712 713 714
			if (!strncmp(this_opt, "nocursor", 8)) {
				this_opt += 8;
				nocursor = 1;
				print_info("Cursor disabled");
			}
P
Pete Popov 已提交
715 716 717 718 719 720 721 722
			/* Mode option (only option that start with digit) */
			else if (isdigit(this_opt[0])) {
				mode = kmalloc(strlen(this_opt) + 1, GFP_KERNEL);
				strncpy(mode, this_opt, strlen(this_opt) + 1);
			}
			/* Unsupported option */
			else {
				print_warn("Unsupported option \"%s\"", this_opt);
723
			}
L
Linus Torvalds 已提交
724 725 726
		}
	}

P
Pete Popov 已提交
727 728
	drv_info.panel_idx = panel_idx;
	drv_info.opt_mode = mode;
L
Linus Torvalds 已提交
729

P
Pete Popov 已提交
730 731 732
	print_info("Panel=%s Mode=%s",
			known_lcd_panels[drv_info.panel_idx].name,
		      	drv_info.opt_mode ? drv_info.opt_mode : "default");
L
Linus Torvalds 已提交
733

P
Pete Popov 已提交
734 735
	return 0;
}
L
Linus Torvalds 已提交
736

P
Pete Popov 已提交
737
int __init au1100fb_init(void)
L
Linus Torvalds 已提交
738
{
P
Pete Popov 已提交
739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756
	char* options;
	int ret;

	print_info("" DRIVER_DESC "");

	memset(&drv_info, 0, sizeof(drv_info));

	if (fb_get_options(DRIVER_NAME, &options))
		return -ENODEV;

	/* Setup driver with options */
	ret = au1100fb_setup(options);
	if (ret < 0) {
		print_err("Fail to setup driver");
		return ret;
	}

	return driver_register(&au1100fb_driver);
L
Linus Torvalds 已提交
757 758
}

P
Pete Popov 已提交
759
void __exit au1100fb_cleanup(void)
L
Linus Torvalds 已提交
760
{
P
Pete Popov 已提交
761 762
	driver_unregister(&au1100fb_driver);

763
	kfree(drv_info.opt_mode);
L
Linus Torvalds 已提交
764 765
}

P
Pete Popov 已提交
766 767 768 769 770
module_init(au1100fb_init);
module_exit(au1100fb_cleanup);

MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE("GPL");