162306a36Sopenharmony_ci/*
262306a36Sopenharmony_ci *  linux/drivers/video/sa1100fb.c
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci *  Copyright (C) 1999 Eric A. Thomas
562306a36Sopenharmony_ci *   Based on acornfb.c Copyright (C) Russell King.
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * This file is subject to the terms and conditions of the GNU General Public
862306a36Sopenharmony_ci * License.  See the file COPYING in the main directory of this archive for
962306a36Sopenharmony_ci * more details.
1062306a36Sopenharmony_ci *
1162306a36Sopenharmony_ci *	        StrongARM 1100 LCD Controller Frame Buffer Driver
1262306a36Sopenharmony_ci *
1362306a36Sopenharmony_ci * Please direct your questions and comments on this driver to the following
1462306a36Sopenharmony_ci * email address:
1562306a36Sopenharmony_ci *
1662306a36Sopenharmony_ci *	linux-arm-kernel@lists.arm.linux.org.uk
1762306a36Sopenharmony_ci *
1862306a36Sopenharmony_ci * Clean patches should be sent to the ARM Linux Patch System.  Please see the
1962306a36Sopenharmony_ci * following web page for more information:
2062306a36Sopenharmony_ci *
2162306a36Sopenharmony_ci *	https://www.arm.linux.org.uk/developer/patches/info.shtml
2262306a36Sopenharmony_ci *
2362306a36Sopenharmony_ci * Thank you.
2462306a36Sopenharmony_ci *
2562306a36Sopenharmony_ci * Known problems:
2662306a36Sopenharmony_ci *	- With the Neponset plugged into an Assabet, LCD powerdown
2762306a36Sopenharmony_ci *	  doesn't work (LCD stays powered up).  Therefore we shouldn't
2862306a36Sopenharmony_ci *	  blank the screen.
2962306a36Sopenharmony_ci *	- We don't limit the CPU clock rate nor the mode selection
3062306a36Sopenharmony_ci *	  according to the available SDRAM bandwidth.
3162306a36Sopenharmony_ci *
3262306a36Sopenharmony_ci * Other notes:
3362306a36Sopenharmony_ci *	- Linear grayscale palettes and the kernel.
3462306a36Sopenharmony_ci *	  Such code does not belong in the kernel.  The kernel frame buffer
3562306a36Sopenharmony_ci *	  drivers do not expect a linear colourmap, but a colourmap based on
3662306a36Sopenharmony_ci *	  the VT100 standard mapping.
3762306a36Sopenharmony_ci *
3862306a36Sopenharmony_ci *	  If your _userspace_ requires a linear colourmap, then the setup of
3962306a36Sopenharmony_ci *	  such a colourmap belongs _in userspace_, not in the kernel.  Code
4062306a36Sopenharmony_ci *	  to set the colourmap correctly from user space has been sent to
4162306a36Sopenharmony_ci *	  David Neuer.  It's around 8 lines of C code, plus another 4 to
4262306a36Sopenharmony_ci *	  detect if we are using grayscale.
4362306a36Sopenharmony_ci *
4462306a36Sopenharmony_ci *	- The following must never be specified in a panel definition:
4562306a36Sopenharmony_ci *	     LCCR0_LtlEnd, LCCR3_PixClkDiv, LCCR3_VrtSnchL, LCCR3_HorSnchL
4662306a36Sopenharmony_ci *
4762306a36Sopenharmony_ci *	- The following should be specified:
4862306a36Sopenharmony_ci *	     either LCCR0_Color or LCCR0_Mono
4962306a36Sopenharmony_ci *	     either LCCR0_Sngl or LCCR0_Dual
5062306a36Sopenharmony_ci *	     either LCCR0_Act or LCCR0_Pas
5162306a36Sopenharmony_ci *	     either LCCR3_OutEnH or LCCD3_OutEnL
5262306a36Sopenharmony_ci *	     either LCCR3_PixRsEdg or LCCR3_PixFlEdg
5362306a36Sopenharmony_ci *	     either LCCR3_ACBsDiv or LCCR3_ACBsCntOff
5462306a36Sopenharmony_ci *
5562306a36Sopenharmony_ci * Code Status:
5662306a36Sopenharmony_ci * 1999/04/01:
5762306a36Sopenharmony_ci *	- Driver appears to be working for Brutus 320x200x8bpp mode.  Other
5862306a36Sopenharmony_ci *	  resolutions are working, but only the 8bpp mode is supported.
5962306a36Sopenharmony_ci *	  Changes need to be made to the palette encode and decode routines
6062306a36Sopenharmony_ci *	  to support 4 and 16 bpp modes.
6162306a36Sopenharmony_ci *	  Driver is not designed to be a module.  The FrameBuffer is statically
6262306a36Sopenharmony_ci *	  allocated since dynamic allocation of a 300k buffer cannot be
6362306a36Sopenharmony_ci *	  guaranteed.
6462306a36Sopenharmony_ci *
6562306a36Sopenharmony_ci * 1999/06/17:
6662306a36Sopenharmony_ci *	- FrameBuffer memory is now allocated at run-time when the
6762306a36Sopenharmony_ci *	  driver is initialized.
6862306a36Sopenharmony_ci *
6962306a36Sopenharmony_ci * 2000/04/10: Nicolas Pitre <nico@fluxnic.net>
7062306a36Sopenharmony_ci *	- Big cleanup for dynamic selection of machine type at run time.
7162306a36Sopenharmony_ci *
7262306a36Sopenharmony_ci * 2000/07/19: Jamey Hicks <jamey@crl.dec.com>
7362306a36Sopenharmony_ci *	- Support for Bitsy aka Compaq iPAQ H3600 added.
7462306a36Sopenharmony_ci *
7562306a36Sopenharmony_ci * 2000/08/07: Tak-Shing Chan <tchan.rd@idthk.com>
7662306a36Sopenharmony_ci *	       Jeff Sutherland <jsutherland@accelent.com>
7762306a36Sopenharmony_ci *	- Resolved an issue caused by a change made to the Assabet's PLD
7862306a36Sopenharmony_ci *	  earlier this year which broke the framebuffer driver for newer
7962306a36Sopenharmony_ci *	  Phase 4 Assabets.  Some other parameters were changed to optimize
8062306a36Sopenharmony_ci *	  for the Sharp display.
8162306a36Sopenharmony_ci *
8262306a36Sopenharmony_ci * 2000/08/09: Kunihiko IMAI <imai@vasara.co.jp>
8362306a36Sopenharmony_ci *	- XP860 support added
8462306a36Sopenharmony_ci *
8562306a36Sopenharmony_ci * 2000/08/19: Mark Huang <mhuang@livetoy.com>
8662306a36Sopenharmony_ci *	- Allows standard options to be passed on the kernel command line
8762306a36Sopenharmony_ci *	  for most common passive displays.
8862306a36Sopenharmony_ci *
8962306a36Sopenharmony_ci * 2000/08/29:
9062306a36Sopenharmony_ci *	- s/save_flags_cli/local_irq_save/
9162306a36Sopenharmony_ci *	- remove unneeded extra save_flags_cli in sa1100fb_enable_lcd_controller
9262306a36Sopenharmony_ci *
9362306a36Sopenharmony_ci * 2000/10/10: Erik Mouw <J.A.K.Mouw@its.tudelft.nl>
9462306a36Sopenharmony_ci *	- Updated LART stuff. Fixed some minor bugs.
9562306a36Sopenharmony_ci *
9662306a36Sopenharmony_ci * 2000/10/30: Murphy Chen <murphy@mail.dialogue.com.tw>
9762306a36Sopenharmony_ci *	- Pangolin support added
9862306a36Sopenharmony_ci *
9962306a36Sopenharmony_ci * 2000/10/31: Roman Jordan <jor@hoeft-wessel.de>
10062306a36Sopenharmony_ci *	- Huw Webpanel support added
10162306a36Sopenharmony_ci *
10262306a36Sopenharmony_ci * 2000/11/23: Eric Peng <ericpeng@coventive.com>
10362306a36Sopenharmony_ci *	- Freebird add
10462306a36Sopenharmony_ci *
10562306a36Sopenharmony_ci * 2001/02/07: Jamey Hicks <jamey.hicks@compaq.com>
10662306a36Sopenharmony_ci *	       Cliff Brake <cbrake@accelent.com>
10762306a36Sopenharmony_ci *	- Added PM callback
10862306a36Sopenharmony_ci *
10962306a36Sopenharmony_ci * 2001/05/26: <rmk@arm.linux.org.uk>
11062306a36Sopenharmony_ci *	- Fix 16bpp so that (a) we use the right colours rather than some
11162306a36Sopenharmony_ci *	  totally random colour depending on what was in page 0, and (b)
11262306a36Sopenharmony_ci *	  we don't de-reference a NULL pointer.
11362306a36Sopenharmony_ci *	- remove duplicated implementation of consistent_alloc()
11462306a36Sopenharmony_ci *	- convert dma address types to dma_addr_t
11562306a36Sopenharmony_ci *	- remove unused 'montype' stuff
11662306a36Sopenharmony_ci *	- remove redundant zero inits of init_var after the initial
11762306a36Sopenharmony_ci *	  memset.
11862306a36Sopenharmony_ci *	- remove allow_modeset (acornfb idea does not belong here)
11962306a36Sopenharmony_ci *
12062306a36Sopenharmony_ci * 2001/05/28: <rmk@arm.linux.org.uk>
12162306a36Sopenharmony_ci *	- massive cleanup - move machine dependent data into structures
12262306a36Sopenharmony_ci *	- I've left various #warnings in - if you see one, and know
12362306a36Sopenharmony_ci *	  the hardware concerned, please get in contact with me.
12462306a36Sopenharmony_ci *
12562306a36Sopenharmony_ci * 2001/05/31: <rmk@arm.linux.org.uk>
12662306a36Sopenharmony_ci *	- Fix LCCR1 HSW value, fix all machine type specifications to
12762306a36Sopenharmony_ci *	  keep values in line.  (Please check your machine type specs)
12862306a36Sopenharmony_ci *
12962306a36Sopenharmony_ci * 2001/06/10: <rmk@arm.linux.org.uk>
13062306a36Sopenharmony_ci *	- Fiddle with the LCD controller from task context only; mainly
13162306a36Sopenharmony_ci *	  so that we can run with interrupts on, and sleep.
13262306a36Sopenharmony_ci *	- Convert #warnings into #errors.  No pain, no gain. ;)
13362306a36Sopenharmony_ci *
13462306a36Sopenharmony_ci * 2001/06/14: <rmk@arm.linux.org.uk>
13562306a36Sopenharmony_ci *	- Make the palette BPS value for 12bpp come out correctly.
13662306a36Sopenharmony_ci *	- Take notice of "greyscale" on any colour depth.
13762306a36Sopenharmony_ci *	- Make truecolor visuals use the RGB channel encoding information.
13862306a36Sopenharmony_ci *
13962306a36Sopenharmony_ci * 2001/07/02: <rmk@arm.linux.org.uk>
14062306a36Sopenharmony_ci *	- Fix colourmap problems.
14162306a36Sopenharmony_ci *
14262306a36Sopenharmony_ci * 2001/07/13: <abraham@2d3d.co.za>
14362306a36Sopenharmony_ci *	- Added support for the ICP LCD-Kit01 on LART. This LCD is
14462306a36Sopenharmony_ci *	  manufactured by Prime View, model no V16C6448AB
14562306a36Sopenharmony_ci *
14662306a36Sopenharmony_ci * 2001/07/23: <rmk@arm.linux.org.uk>
14762306a36Sopenharmony_ci *	- Hand merge version from handhelds.org CVS tree.  See patch
14862306a36Sopenharmony_ci *	  notes for 595/1 for more information.
14962306a36Sopenharmony_ci *	- Drop 12bpp (it's 16bpp with different colour register mappings).
15062306a36Sopenharmony_ci *	- This hardware can not do direct colour.  Therefore we don't
15162306a36Sopenharmony_ci *	  support it.
15262306a36Sopenharmony_ci *
15362306a36Sopenharmony_ci * 2001/07/27: <rmk@arm.linux.org.uk>
15462306a36Sopenharmony_ci *	- Halve YRES on dual scan LCDs.
15562306a36Sopenharmony_ci *
15662306a36Sopenharmony_ci * 2001/08/22: <rmk@arm.linux.org.uk>
15762306a36Sopenharmony_ci *	- Add b/w iPAQ pixclock value.
15862306a36Sopenharmony_ci *
15962306a36Sopenharmony_ci * 2001/10/12: <rmk@arm.linux.org.uk>
16062306a36Sopenharmony_ci *	- Add patch 681/1 and clean up stork definitions.
16162306a36Sopenharmony_ci */
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci#include <linux/module.h>
16462306a36Sopenharmony_ci#include <linux/kernel.h>
16562306a36Sopenharmony_ci#include <linux/sched.h>
16662306a36Sopenharmony_ci#include <linux/errno.h>
16762306a36Sopenharmony_ci#include <linux/string.h>
16862306a36Sopenharmony_ci#include <linux/interrupt.h>
16962306a36Sopenharmony_ci#include <linux/slab.h>
17062306a36Sopenharmony_ci#include <linux/mm.h>
17162306a36Sopenharmony_ci#include <linux/fb.h>
17262306a36Sopenharmony_ci#include <linux/delay.h>
17362306a36Sopenharmony_ci#include <linux/init.h>
17462306a36Sopenharmony_ci#include <linux/ioport.h>
17562306a36Sopenharmony_ci#include <linux/cpufreq.h>
17662306a36Sopenharmony_ci#include <linux/gpio/consumer.h>
17762306a36Sopenharmony_ci#include <linux/platform_device.h>
17862306a36Sopenharmony_ci#include <linux/dma-mapping.h>
17962306a36Sopenharmony_ci#include <linux/mutex.h>
18062306a36Sopenharmony_ci#include <linux/io.h>
18162306a36Sopenharmony_ci#include <linux/clk.h>
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci#include <video/sa1100fb.h>
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci#include <mach/hardware.h>
18662306a36Sopenharmony_ci#include <asm/mach-types.h>
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci/*
18962306a36Sopenharmony_ci * Complain if VAR is out of range.
19062306a36Sopenharmony_ci */
19162306a36Sopenharmony_ci#define DEBUG_VAR 1
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci#include "sa1100fb.h"
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_cistatic const struct sa1100fb_rgb rgb_4 = {
19662306a36Sopenharmony_ci	.red	= { .offset = 0,  .length = 4, },
19762306a36Sopenharmony_ci	.green	= { .offset = 0,  .length = 4, },
19862306a36Sopenharmony_ci	.blue	= { .offset = 0,  .length = 4, },
19962306a36Sopenharmony_ci	.transp	= { .offset = 0,  .length = 0, },
20062306a36Sopenharmony_ci};
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_cistatic const struct sa1100fb_rgb rgb_8 = {
20362306a36Sopenharmony_ci	.red	= { .offset = 0,  .length = 8, },
20462306a36Sopenharmony_ci	.green	= { .offset = 0,  .length = 8, },
20562306a36Sopenharmony_ci	.blue	= { .offset = 0,  .length = 8, },
20662306a36Sopenharmony_ci	.transp	= { .offset = 0,  .length = 0, },
20762306a36Sopenharmony_ci};
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_cistatic const struct sa1100fb_rgb def_rgb_16 = {
21062306a36Sopenharmony_ci	.red	= { .offset = 11, .length = 5, },
21162306a36Sopenharmony_ci	.green	= { .offset = 5,  .length = 6, },
21262306a36Sopenharmony_ci	.blue	= { .offset = 0,  .length = 5, },
21362306a36Sopenharmony_ci	.transp	= { .offset = 0,  .length = 0, },
21462306a36Sopenharmony_ci};
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_cistatic int sa1100fb_activate_var(struct fb_var_screeninfo *var, struct sa1100fb_info *);
21962306a36Sopenharmony_cistatic void set_ctrlr_state(struct sa1100fb_info *fbi, u_int state);
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_cistatic inline void sa1100fb_schedule_work(struct sa1100fb_info *fbi, u_int state)
22262306a36Sopenharmony_ci{
22362306a36Sopenharmony_ci	unsigned long flags;
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci	local_irq_save(flags);
22662306a36Sopenharmony_ci	/*
22762306a36Sopenharmony_ci	 * We need to handle two requests being made at the same time.
22862306a36Sopenharmony_ci	 * There are two important cases:
22962306a36Sopenharmony_ci	 *  1. When we are changing VT (C_REENABLE) while unblanking (C_ENABLE)
23062306a36Sopenharmony_ci	 *     We must perform the unblanking, which will do our REENABLE for us.
23162306a36Sopenharmony_ci	 *  2. When we are blanking, but immediately unblank before we have
23262306a36Sopenharmony_ci	 *     blanked.  We do the "REENABLE" thing here as well, just to be sure.
23362306a36Sopenharmony_ci	 */
23462306a36Sopenharmony_ci	if (fbi->task_state == C_ENABLE && state == C_REENABLE)
23562306a36Sopenharmony_ci		state = (u_int) -1;
23662306a36Sopenharmony_ci	if (fbi->task_state == C_DISABLE && state == C_ENABLE)
23762306a36Sopenharmony_ci		state = C_REENABLE;
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci	if (state != (u_int)-1) {
24062306a36Sopenharmony_ci		fbi->task_state = state;
24162306a36Sopenharmony_ci		schedule_work(&fbi->task);
24262306a36Sopenharmony_ci	}
24362306a36Sopenharmony_ci	local_irq_restore(flags);
24462306a36Sopenharmony_ci}
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_cistatic inline u_int chan_to_field(u_int chan, struct fb_bitfield *bf)
24762306a36Sopenharmony_ci{
24862306a36Sopenharmony_ci	chan &= 0xffff;
24962306a36Sopenharmony_ci	chan >>= 16 - bf->length;
25062306a36Sopenharmony_ci	return chan << bf->offset;
25162306a36Sopenharmony_ci}
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci/*
25462306a36Sopenharmony_ci * Convert bits-per-pixel to a hardware palette PBS value.
25562306a36Sopenharmony_ci */
25662306a36Sopenharmony_cistatic inline u_int palette_pbs(struct fb_var_screeninfo *var)
25762306a36Sopenharmony_ci{
25862306a36Sopenharmony_ci	int ret = 0;
25962306a36Sopenharmony_ci	switch (var->bits_per_pixel) {
26062306a36Sopenharmony_ci	case 4:  ret = 0 << 12;	break;
26162306a36Sopenharmony_ci	case 8:  ret = 1 << 12; break;
26262306a36Sopenharmony_ci	case 16: ret = 2 << 12; break;
26362306a36Sopenharmony_ci	}
26462306a36Sopenharmony_ci	return ret;
26562306a36Sopenharmony_ci}
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_cistatic int
26862306a36Sopenharmony_cisa1100fb_setpalettereg(u_int regno, u_int red, u_int green, u_int blue,
26962306a36Sopenharmony_ci		       u_int trans, struct fb_info *info)
27062306a36Sopenharmony_ci{
27162306a36Sopenharmony_ci	struct sa1100fb_info *fbi =
27262306a36Sopenharmony_ci		container_of(info, struct sa1100fb_info, fb);
27362306a36Sopenharmony_ci	u_int val, ret = 1;
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_ci	if (regno < fbi->palette_size) {
27662306a36Sopenharmony_ci		val = ((red >> 4) & 0xf00);
27762306a36Sopenharmony_ci		val |= ((green >> 8) & 0x0f0);
27862306a36Sopenharmony_ci		val |= ((blue >> 12) & 0x00f);
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci		if (regno == 0)
28162306a36Sopenharmony_ci			val |= palette_pbs(&fbi->fb.var);
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci		fbi->palette_cpu[regno] = val;
28462306a36Sopenharmony_ci		ret = 0;
28562306a36Sopenharmony_ci	}
28662306a36Sopenharmony_ci	return ret;
28762306a36Sopenharmony_ci}
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_cistatic int
29062306a36Sopenharmony_cisa1100fb_setcolreg(u_int regno, u_int red, u_int green, u_int blue,
29162306a36Sopenharmony_ci		   u_int trans, struct fb_info *info)
29262306a36Sopenharmony_ci{
29362306a36Sopenharmony_ci	struct sa1100fb_info *fbi =
29462306a36Sopenharmony_ci		container_of(info, struct sa1100fb_info, fb);
29562306a36Sopenharmony_ci	unsigned int val;
29662306a36Sopenharmony_ci	int ret = 1;
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_ci	/*
29962306a36Sopenharmony_ci	 * If inverse mode was selected, invert all the colours
30062306a36Sopenharmony_ci	 * rather than the register number.  The register number
30162306a36Sopenharmony_ci	 * is what you poke into the framebuffer to produce the
30262306a36Sopenharmony_ci	 * colour you requested.
30362306a36Sopenharmony_ci	 */
30462306a36Sopenharmony_ci	if (fbi->inf->cmap_inverse) {
30562306a36Sopenharmony_ci		red   = 0xffff - red;
30662306a36Sopenharmony_ci		green = 0xffff - green;
30762306a36Sopenharmony_ci		blue  = 0xffff - blue;
30862306a36Sopenharmony_ci	}
30962306a36Sopenharmony_ci
31062306a36Sopenharmony_ci	/*
31162306a36Sopenharmony_ci	 * If greyscale is true, then we convert the RGB value
31262306a36Sopenharmony_ci	 * to greyscale no mater what visual we are using.
31362306a36Sopenharmony_ci	 */
31462306a36Sopenharmony_ci	if (fbi->fb.var.grayscale)
31562306a36Sopenharmony_ci		red = green = blue = (19595 * red + 38470 * green +
31662306a36Sopenharmony_ci					7471 * blue) >> 16;
31762306a36Sopenharmony_ci
31862306a36Sopenharmony_ci	switch (fbi->fb.fix.visual) {
31962306a36Sopenharmony_ci	case FB_VISUAL_TRUECOLOR:
32062306a36Sopenharmony_ci		/*
32162306a36Sopenharmony_ci		 * 12 or 16-bit True Colour.  We encode the RGB value
32262306a36Sopenharmony_ci		 * according to the RGB bitfield information.
32362306a36Sopenharmony_ci		 */
32462306a36Sopenharmony_ci		if (regno < 16) {
32562306a36Sopenharmony_ci			val  = chan_to_field(red, &fbi->fb.var.red);
32662306a36Sopenharmony_ci			val |= chan_to_field(green, &fbi->fb.var.green);
32762306a36Sopenharmony_ci			val |= chan_to_field(blue, &fbi->fb.var.blue);
32862306a36Sopenharmony_ci
32962306a36Sopenharmony_ci			fbi->pseudo_palette[regno] = val;
33062306a36Sopenharmony_ci			ret = 0;
33162306a36Sopenharmony_ci		}
33262306a36Sopenharmony_ci		break;
33362306a36Sopenharmony_ci
33462306a36Sopenharmony_ci	case FB_VISUAL_STATIC_PSEUDOCOLOR:
33562306a36Sopenharmony_ci	case FB_VISUAL_PSEUDOCOLOR:
33662306a36Sopenharmony_ci		ret = sa1100fb_setpalettereg(regno, red, green, blue, trans, info);
33762306a36Sopenharmony_ci		break;
33862306a36Sopenharmony_ci	}
33962306a36Sopenharmony_ci
34062306a36Sopenharmony_ci	return ret;
34162306a36Sopenharmony_ci}
34262306a36Sopenharmony_ci
34362306a36Sopenharmony_ci#ifdef CONFIG_CPU_FREQ
34462306a36Sopenharmony_ci/*
34562306a36Sopenharmony_ci *  sa1100fb_display_dma_period()
34662306a36Sopenharmony_ci *    Calculate the minimum period (in picoseconds) between two DMA
34762306a36Sopenharmony_ci *    requests for the LCD controller.  If we hit this, it means we're
34862306a36Sopenharmony_ci *    doing nothing but LCD DMA.
34962306a36Sopenharmony_ci */
35062306a36Sopenharmony_cistatic inline unsigned int sa1100fb_display_dma_period(struct fb_var_screeninfo *var)
35162306a36Sopenharmony_ci{
35262306a36Sopenharmony_ci	/*
35362306a36Sopenharmony_ci	 * Period = pixclock * bits_per_byte * bytes_per_transfer
35462306a36Sopenharmony_ci	 *		/ memory_bits_per_pixel;
35562306a36Sopenharmony_ci	 */
35662306a36Sopenharmony_ci	return var->pixclock * 8 * 16 / var->bits_per_pixel;
35762306a36Sopenharmony_ci}
35862306a36Sopenharmony_ci#endif
35962306a36Sopenharmony_ci
36062306a36Sopenharmony_ci/*
36162306a36Sopenharmony_ci *  sa1100fb_check_var():
36262306a36Sopenharmony_ci *    Round up in the following order: bits_per_pixel, xres,
36362306a36Sopenharmony_ci *    yres, xres_virtual, yres_virtual, xoffset, yoffset, grayscale,
36462306a36Sopenharmony_ci *    bitfields, horizontal timing, vertical timing.
36562306a36Sopenharmony_ci */
36662306a36Sopenharmony_cistatic int
36762306a36Sopenharmony_cisa1100fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
36862306a36Sopenharmony_ci{
36962306a36Sopenharmony_ci	struct sa1100fb_info *fbi =
37062306a36Sopenharmony_ci		container_of(info, struct sa1100fb_info, fb);
37162306a36Sopenharmony_ci	int rgbidx;
37262306a36Sopenharmony_ci
37362306a36Sopenharmony_ci	if (var->xres < MIN_XRES)
37462306a36Sopenharmony_ci		var->xres = MIN_XRES;
37562306a36Sopenharmony_ci	if (var->yres < MIN_YRES)
37662306a36Sopenharmony_ci		var->yres = MIN_YRES;
37762306a36Sopenharmony_ci	if (var->xres > fbi->inf->xres)
37862306a36Sopenharmony_ci		var->xres = fbi->inf->xres;
37962306a36Sopenharmony_ci	if (var->yres > fbi->inf->yres)
38062306a36Sopenharmony_ci		var->yres = fbi->inf->yres;
38162306a36Sopenharmony_ci	var->xres_virtual = max(var->xres_virtual, var->xres);
38262306a36Sopenharmony_ci	var->yres_virtual = max(var->yres_virtual, var->yres);
38362306a36Sopenharmony_ci
38462306a36Sopenharmony_ci	dev_dbg(fbi->dev, "var->bits_per_pixel=%d\n", var->bits_per_pixel);
38562306a36Sopenharmony_ci	switch (var->bits_per_pixel) {
38662306a36Sopenharmony_ci	case 4:
38762306a36Sopenharmony_ci		rgbidx = RGB_4;
38862306a36Sopenharmony_ci		break;
38962306a36Sopenharmony_ci	case 8:
39062306a36Sopenharmony_ci		rgbidx = RGB_8;
39162306a36Sopenharmony_ci		break;
39262306a36Sopenharmony_ci	case 16:
39362306a36Sopenharmony_ci		rgbidx = RGB_16;
39462306a36Sopenharmony_ci		break;
39562306a36Sopenharmony_ci	default:
39662306a36Sopenharmony_ci		return -EINVAL;
39762306a36Sopenharmony_ci	}
39862306a36Sopenharmony_ci
39962306a36Sopenharmony_ci	/*
40062306a36Sopenharmony_ci	 * Copy the RGB parameters for this display
40162306a36Sopenharmony_ci	 * from the machine specific parameters.
40262306a36Sopenharmony_ci	 */
40362306a36Sopenharmony_ci	var->red    = fbi->rgb[rgbidx]->red;
40462306a36Sopenharmony_ci	var->green  = fbi->rgb[rgbidx]->green;
40562306a36Sopenharmony_ci	var->blue   = fbi->rgb[rgbidx]->blue;
40662306a36Sopenharmony_ci	var->transp = fbi->rgb[rgbidx]->transp;
40762306a36Sopenharmony_ci
40862306a36Sopenharmony_ci	dev_dbg(fbi->dev, "RGBT length = %d:%d:%d:%d\n",
40962306a36Sopenharmony_ci		var->red.length, var->green.length, var->blue.length,
41062306a36Sopenharmony_ci		var->transp.length);
41162306a36Sopenharmony_ci
41262306a36Sopenharmony_ci	dev_dbg(fbi->dev, "RGBT offset = %d:%d:%d:%d\n",
41362306a36Sopenharmony_ci		var->red.offset, var->green.offset, var->blue.offset,
41462306a36Sopenharmony_ci		var->transp.offset);
41562306a36Sopenharmony_ci
41662306a36Sopenharmony_ci#ifdef CONFIG_CPU_FREQ
41762306a36Sopenharmony_ci	dev_dbg(fbi->dev, "dma period = %d ps, clock = %ld kHz\n",
41862306a36Sopenharmony_ci		sa1100fb_display_dma_period(var),
41962306a36Sopenharmony_ci		clk_get_rate(fbi->clk) / 1000);
42062306a36Sopenharmony_ci#endif
42162306a36Sopenharmony_ci
42262306a36Sopenharmony_ci	return 0;
42362306a36Sopenharmony_ci}
42462306a36Sopenharmony_ci
42562306a36Sopenharmony_cistatic void sa1100fb_set_visual(struct sa1100fb_info *fbi, u32 visual)
42662306a36Sopenharmony_ci{
42762306a36Sopenharmony_ci	if (fbi->inf->set_visual)
42862306a36Sopenharmony_ci		fbi->inf->set_visual(visual);
42962306a36Sopenharmony_ci}
43062306a36Sopenharmony_ci
43162306a36Sopenharmony_ci/*
43262306a36Sopenharmony_ci * sa1100fb_set_par():
43362306a36Sopenharmony_ci *	Set the user defined part of the display for the specified console
43462306a36Sopenharmony_ci */
43562306a36Sopenharmony_cistatic int sa1100fb_set_par(struct fb_info *info)
43662306a36Sopenharmony_ci{
43762306a36Sopenharmony_ci	struct sa1100fb_info *fbi =
43862306a36Sopenharmony_ci		container_of(info, struct sa1100fb_info, fb);
43962306a36Sopenharmony_ci	struct fb_var_screeninfo *var = &info->var;
44062306a36Sopenharmony_ci	unsigned long palette_mem_size;
44162306a36Sopenharmony_ci
44262306a36Sopenharmony_ci	dev_dbg(fbi->dev, "set_par\n");
44362306a36Sopenharmony_ci
44462306a36Sopenharmony_ci	if (var->bits_per_pixel == 16)
44562306a36Sopenharmony_ci		fbi->fb.fix.visual = FB_VISUAL_TRUECOLOR;
44662306a36Sopenharmony_ci	else if (!fbi->inf->cmap_static)
44762306a36Sopenharmony_ci		fbi->fb.fix.visual = FB_VISUAL_PSEUDOCOLOR;
44862306a36Sopenharmony_ci	else {
44962306a36Sopenharmony_ci		/*
45062306a36Sopenharmony_ci		 * Some people have weird ideas about wanting static
45162306a36Sopenharmony_ci		 * pseudocolor maps.  I suspect their user space
45262306a36Sopenharmony_ci		 * applications are broken.
45362306a36Sopenharmony_ci		 */
45462306a36Sopenharmony_ci		fbi->fb.fix.visual = FB_VISUAL_STATIC_PSEUDOCOLOR;
45562306a36Sopenharmony_ci	}
45662306a36Sopenharmony_ci
45762306a36Sopenharmony_ci	fbi->fb.fix.line_length = var->xres_virtual *
45862306a36Sopenharmony_ci				  var->bits_per_pixel / 8;
45962306a36Sopenharmony_ci	fbi->palette_size = var->bits_per_pixel == 8 ? 256 : 16;
46062306a36Sopenharmony_ci
46162306a36Sopenharmony_ci	palette_mem_size = fbi->palette_size * sizeof(u16);
46262306a36Sopenharmony_ci
46362306a36Sopenharmony_ci	dev_dbg(fbi->dev, "palette_mem_size = 0x%08lx\n", palette_mem_size);
46462306a36Sopenharmony_ci
46562306a36Sopenharmony_ci	fbi->palette_cpu = (u16 *)(fbi->map_cpu + PAGE_SIZE - palette_mem_size);
46662306a36Sopenharmony_ci	fbi->palette_dma = fbi->map_dma + PAGE_SIZE - palette_mem_size;
46762306a36Sopenharmony_ci
46862306a36Sopenharmony_ci	/*
46962306a36Sopenharmony_ci	 * Set (any) board control register to handle new color depth
47062306a36Sopenharmony_ci	 */
47162306a36Sopenharmony_ci	sa1100fb_set_visual(fbi, fbi->fb.fix.visual);
47262306a36Sopenharmony_ci	sa1100fb_activate_var(var, fbi);
47362306a36Sopenharmony_ci
47462306a36Sopenharmony_ci	return 0;
47562306a36Sopenharmony_ci}
47662306a36Sopenharmony_ci
47762306a36Sopenharmony_ci#if 0
47862306a36Sopenharmony_cistatic int
47962306a36Sopenharmony_cisa1100fb_set_cmap(struct fb_cmap *cmap, int kspc, int con,
48062306a36Sopenharmony_ci		  struct fb_info *info)
48162306a36Sopenharmony_ci{
48262306a36Sopenharmony_ci	struct sa1100fb_info *fbi = (struct sa1100fb_info *)info;
48362306a36Sopenharmony_ci
48462306a36Sopenharmony_ci	/*
48562306a36Sopenharmony_ci	 * Make sure the user isn't doing something stupid.
48662306a36Sopenharmony_ci	 */
48762306a36Sopenharmony_ci	if (!kspc && (fbi->fb.var.bits_per_pixel == 16 || fbi->inf->cmap_static))
48862306a36Sopenharmony_ci		return -EINVAL;
48962306a36Sopenharmony_ci
49062306a36Sopenharmony_ci	return gen_set_cmap(cmap, kspc, con, info);
49162306a36Sopenharmony_ci}
49262306a36Sopenharmony_ci#endif
49362306a36Sopenharmony_ci
49462306a36Sopenharmony_ci/*
49562306a36Sopenharmony_ci * Formal definition of the VESA spec:
49662306a36Sopenharmony_ci *  On
49762306a36Sopenharmony_ci *  	This refers to the state of the display when it is in full operation
49862306a36Sopenharmony_ci *  Stand-By
49962306a36Sopenharmony_ci *  	This defines an optional operating state of minimal power reduction with
50062306a36Sopenharmony_ci *  	the shortest recovery time
50162306a36Sopenharmony_ci *  Suspend
50262306a36Sopenharmony_ci *  	This refers to a level of power management in which substantial power
50362306a36Sopenharmony_ci *  	reduction is achieved by the display.  The display can have a longer
50462306a36Sopenharmony_ci *  	recovery time from this state than from the Stand-by state
50562306a36Sopenharmony_ci *  Off
50662306a36Sopenharmony_ci *  	This indicates that the display is consuming the lowest level of power
50762306a36Sopenharmony_ci *  	and is non-operational. Recovery from this state may optionally require
50862306a36Sopenharmony_ci *  	the user to manually power on the monitor
50962306a36Sopenharmony_ci *
51062306a36Sopenharmony_ci *  Now, the fbdev driver adds an additional state, (blank), where they
51162306a36Sopenharmony_ci *  turn off the video (maybe by colormap tricks), but don't mess with the
51262306a36Sopenharmony_ci *  video itself: think of it semantically between on and Stand-By.
51362306a36Sopenharmony_ci *
51462306a36Sopenharmony_ci *  So here's what we should do in our fbdev blank routine:
51562306a36Sopenharmony_ci *
51662306a36Sopenharmony_ci *  	VESA_NO_BLANKING (mode 0)	Video on,  front/back light on
51762306a36Sopenharmony_ci *  	VESA_VSYNC_SUSPEND (mode 1)  	Video on,  front/back light off
51862306a36Sopenharmony_ci *  	VESA_HSYNC_SUSPEND (mode 2)  	Video on,  front/back light off
51962306a36Sopenharmony_ci *  	VESA_POWERDOWN (mode 3)		Video off, front/back light off
52062306a36Sopenharmony_ci *
52162306a36Sopenharmony_ci *  This will match the matrox implementation.
52262306a36Sopenharmony_ci */
52362306a36Sopenharmony_ci/*
52462306a36Sopenharmony_ci * sa1100fb_blank():
52562306a36Sopenharmony_ci *	Blank the display by setting all palette values to zero.  Note, the
52662306a36Sopenharmony_ci * 	12 and 16 bpp modes don't really use the palette, so this will not
52762306a36Sopenharmony_ci *      blank the display in all modes.
52862306a36Sopenharmony_ci */
52962306a36Sopenharmony_cistatic int sa1100fb_blank(int blank, struct fb_info *info)
53062306a36Sopenharmony_ci{
53162306a36Sopenharmony_ci	struct sa1100fb_info *fbi =
53262306a36Sopenharmony_ci		container_of(info, struct sa1100fb_info, fb);
53362306a36Sopenharmony_ci	int i;
53462306a36Sopenharmony_ci
53562306a36Sopenharmony_ci	dev_dbg(fbi->dev, "sa1100fb_blank: blank=%d\n", blank);
53662306a36Sopenharmony_ci
53762306a36Sopenharmony_ci	switch (blank) {
53862306a36Sopenharmony_ci	case FB_BLANK_POWERDOWN:
53962306a36Sopenharmony_ci	case FB_BLANK_VSYNC_SUSPEND:
54062306a36Sopenharmony_ci	case FB_BLANK_HSYNC_SUSPEND:
54162306a36Sopenharmony_ci	case FB_BLANK_NORMAL:
54262306a36Sopenharmony_ci		if (fbi->fb.fix.visual == FB_VISUAL_PSEUDOCOLOR ||
54362306a36Sopenharmony_ci		    fbi->fb.fix.visual == FB_VISUAL_STATIC_PSEUDOCOLOR)
54462306a36Sopenharmony_ci			for (i = 0; i < fbi->palette_size; i++)
54562306a36Sopenharmony_ci				sa1100fb_setpalettereg(i, 0, 0, 0, 0, info);
54662306a36Sopenharmony_ci		sa1100fb_schedule_work(fbi, C_DISABLE);
54762306a36Sopenharmony_ci		break;
54862306a36Sopenharmony_ci
54962306a36Sopenharmony_ci	case FB_BLANK_UNBLANK:
55062306a36Sopenharmony_ci		if (fbi->fb.fix.visual == FB_VISUAL_PSEUDOCOLOR ||
55162306a36Sopenharmony_ci		    fbi->fb.fix.visual == FB_VISUAL_STATIC_PSEUDOCOLOR)
55262306a36Sopenharmony_ci			fb_set_cmap(&fbi->fb.cmap, info);
55362306a36Sopenharmony_ci		sa1100fb_schedule_work(fbi, C_ENABLE);
55462306a36Sopenharmony_ci	}
55562306a36Sopenharmony_ci	return 0;
55662306a36Sopenharmony_ci}
55762306a36Sopenharmony_ci
55862306a36Sopenharmony_cistatic int sa1100fb_mmap(struct fb_info *info,
55962306a36Sopenharmony_ci			 struct vm_area_struct *vma)
56062306a36Sopenharmony_ci{
56162306a36Sopenharmony_ci	struct sa1100fb_info *fbi =
56262306a36Sopenharmony_ci		container_of(info, struct sa1100fb_info, fb);
56362306a36Sopenharmony_ci	unsigned long off = vma->vm_pgoff << PAGE_SHIFT;
56462306a36Sopenharmony_ci
56562306a36Sopenharmony_ci	if (off < info->fix.smem_len) {
56662306a36Sopenharmony_ci		vma->vm_pgoff += 1; /* skip over the palette */
56762306a36Sopenharmony_ci		return dma_mmap_wc(fbi->dev, vma, fbi->map_cpu, fbi->map_dma,
56862306a36Sopenharmony_ci				   fbi->map_size);
56962306a36Sopenharmony_ci	}
57062306a36Sopenharmony_ci
57162306a36Sopenharmony_ci	vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
57262306a36Sopenharmony_ci
57362306a36Sopenharmony_ci	return vm_iomap_memory(vma, info->fix.mmio_start, info->fix.mmio_len);
57462306a36Sopenharmony_ci}
57562306a36Sopenharmony_ci
57662306a36Sopenharmony_cistatic const struct fb_ops sa1100fb_ops = {
57762306a36Sopenharmony_ci	.owner		= THIS_MODULE,
57862306a36Sopenharmony_ci	.fb_check_var	= sa1100fb_check_var,
57962306a36Sopenharmony_ci	.fb_set_par	= sa1100fb_set_par,
58062306a36Sopenharmony_ci//	.fb_set_cmap	= sa1100fb_set_cmap,
58162306a36Sopenharmony_ci	.fb_setcolreg	= sa1100fb_setcolreg,
58262306a36Sopenharmony_ci	.fb_fillrect	= cfb_fillrect,
58362306a36Sopenharmony_ci	.fb_copyarea	= cfb_copyarea,
58462306a36Sopenharmony_ci	.fb_imageblit	= cfb_imageblit,
58562306a36Sopenharmony_ci	.fb_blank	= sa1100fb_blank,
58662306a36Sopenharmony_ci	.fb_mmap	= sa1100fb_mmap,
58762306a36Sopenharmony_ci};
58862306a36Sopenharmony_ci
58962306a36Sopenharmony_ci/*
59062306a36Sopenharmony_ci * Calculate the PCD value from the clock rate (in picoseconds).
59162306a36Sopenharmony_ci * We take account of the PPCR clock setting.
59262306a36Sopenharmony_ci */
59362306a36Sopenharmony_cistatic inline unsigned int get_pcd(struct sa1100fb_info *fbi,
59462306a36Sopenharmony_ci		unsigned int pixclock)
59562306a36Sopenharmony_ci{
59662306a36Sopenharmony_ci	unsigned int pcd = clk_get_rate(fbi->clk) / 100 / 1000;
59762306a36Sopenharmony_ci
59862306a36Sopenharmony_ci	pcd *= pixclock;
59962306a36Sopenharmony_ci	pcd /= 10000000;
60062306a36Sopenharmony_ci
60162306a36Sopenharmony_ci	return pcd + 1;	/* make up for integer math truncations */
60262306a36Sopenharmony_ci}
60362306a36Sopenharmony_ci
60462306a36Sopenharmony_ci/*
60562306a36Sopenharmony_ci * sa1100fb_activate_var():
60662306a36Sopenharmony_ci *	Configures LCD Controller based on entries in var parameter.  Settings are
60762306a36Sopenharmony_ci *	only written to the controller if changes were made.
60862306a36Sopenharmony_ci */
60962306a36Sopenharmony_cistatic int sa1100fb_activate_var(struct fb_var_screeninfo *var, struct sa1100fb_info *fbi)
61062306a36Sopenharmony_ci{
61162306a36Sopenharmony_ci	struct sa1100fb_lcd_reg new_regs;
61262306a36Sopenharmony_ci	u_int half_screen_size, yres, pcd;
61362306a36Sopenharmony_ci	u_long flags;
61462306a36Sopenharmony_ci
61562306a36Sopenharmony_ci	dev_dbg(fbi->dev, "Configuring SA1100 LCD\n");
61662306a36Sopenharmony_ci
61762306a36Sopenharmony_ci	dev_dbg(fbi->dev, "var: xres=%d hslen=%d lm=%d rm=%d\n",
61862306a36Sopenharmony_ci		var->xres, var->hsync_len,
61962306a36Sopenharmony_ci		var->left_margin, var->right_margin);
62062306a36Sopenharmony_ci	dev_dbg(fbi->dev, "var: yres=%d vslen=%d um=%d bm=%d\n",
62162306a36Sopenharmony_ci		var->yres, var->vsync_len,
62262306a36Sopenharmony_ci		var->upper_margin, var->lower_margin);
62362306a36Sopenharmony_ci
62462306a36Sopenharmony_ci#if DEBUG_VAR
62562306a36Sopenharmony_ci	if (var->xres < 16        || var->xres > 1024)
62662306a36Sopenharmony_ci		dev_err(fbi->dev, "%s: invalid xres %d\n",
62762306a36Sopenharmony_ci			fbi->fb.fix.id, var->xres);
62862306a36Sopenharmony_ci	if (var->hsync_len < 1    || var->hsync_len > 64)
62962306a36Sopenharmony_ci		dev_err(fbi->dev, "%s: invalid hsync_len %d\n",
63062306a36Sopenharmony_ci			fbi->fb.fix.id, var->hsync_len);
63162306a36Sopenharmony_ci	if (var->left_margin < 1  || var->left_margin > 255)
63262306a36Sopenharmony_ci		dev_err(fbi->dev, "%s: invalid left_margin %d\n",
63362306a36Sopenharmony_ci			fbi->fb.fix.id, var->left_margin);
63462306a36Sopenharmony_ci	if (var->right_margin < 1 || var->right_margin > 255)
63562306a36Sopenharmony_ci		dev_err(fbi->dev, "%s: invalid right_margin %d\n",
63662306a36Sopenharmony_ci			fbi->fb.fix.id, var->right_margin);
63762306a36Sopenharmony_ci	if (var->yres < 1         || var->yres > 1024)
63862306a36Sopenharmony_ci		dev_err(fbi->dev, "%s: invalid yres %d\n",
63962306a36Sopenharmony_ci			fbi->fb.fix.id, var->yres);
64062306a36Sopenharmony_ci	if (var->vsync_len < 1    || var->vsync_len > 64)
64162306a36Sopenharmony_ci		dev_err(fbi->dev, "%s: invalid vsync_len %d\n",
64262306a36Sopenharmony_ci			fbi->fb.fix.id, var->vsync_len);
64362306a36Sopenharmony_ci	if (var->upper_margin < 0 || var->upper_margin > 255)
64462306a36Sopenharmony_ci		dev_err(fbi->dev, "%s: invalid upper_margin %d\n",
64562306a36Sopenharmony_ci			fbi->fb.fix.id, var->upper_margin);
64662306a36Sopenharmony_ci	if (var->lower_margin < 0 || var->lower_margin > 255)
64762306a36Sopenharmony_ci		dev_err(fbi->dev, "%s: invalid lower_margin %d\n",
64862306a36Sopenharmony_ci			fbi->fb.fix.id, var->lower_margin);
64962306a36Sopenharmony_ci#endif
65062306a36Sopenharmony_ci
65162306a36Sopenharmony_ci	new_regs.lccr0 = fbi->inf->lccr0 |
65262306a36Sopenharmony_ci		LCCR0_LEN | LCCR0_LDM | LCCR0_BAM |
65362306a36Sopenharmony_ci		LCCR0_ERM | LCCR0_LtlEnd | LCCR0_DMADel(0);
65462306a36Sopenharmony_ci
65562306a36Sopenharmony_ci	new_regs.lccr1 =
65662306a36Sopenharmony_ci		LCCR1_DisWdth(var->xres) +
65762306a36Sopenharmony_ci		LCCR1_HorSnchWdth(var->hsync_len) +
65862306a36Sopenharmony_ci		LCCR1_BegLnDel(var->left_margin) +
65962306a36Sopenharmony_ci		LCCR1_EndLnDel(var->right_margin);
66062306a36Sopenharmony_ci
66162306a36Sopenharmony_ci	/*
66262306a36Sopenharmony_ci	 * If we have a dual scan LCD, then we need to halve
66362306a36Sopenharmony_ci	 * the YRES parameter.
66462306a36Sopenharmony_ci	 */
66562306a36Sopenharmony_ci	yres = var->yres;
66662306a36Sopenharmony_ci	if (fbi->inf->lccr0 & LCCR0_Dual)
66762306a36Sopenharmony_ci		yres /= 2;
66862306a36Sopenharmony_ci
66962306a36Sopenharmony_ci	new_regs.lccr2 =
67062306a36Sopenharmony_ci		LCCR2_DisHght(yres) +
67162306a36Sopenharmony_ci		LCCR2_VrtSnchWdth(var->vsync_len) +
67262306a36Sopenharmony_ci		LCCR2_BegFrmDel(var->upper_margin) +
67362306a36Sopenharmony_ci		LCCR2_EndFrmDel(var->lower_margin);
67462306a36Sopenharmony_ci
67562306a36Sopenharmony_ci	pcd = get_pcd(fbi, var->pixclock);
67662306a36Sopenharmony_ci	new_regs.lccr3 = LCCR3_PixClkDiv(pcd) | fbi->inf->lccr3 |
67762306a36Sopenharmony_ci		(var->sync & FB_SYNC_HOR_HIGH_ACT ? LCCR3_HorSnchH : LCCR3_HorSnchL) |
67862306a36Sopenharmony_ci		(var->sync & FB_SYNC_VERT_HIGH_ACT ? LCCR3_VrtSnchH : LCCR3_VrtSnchL);
67962306a36Sopenharmony_ci
68062306a36Sopenharmony_ci	dev_dbg(fbi->dev, "nlccr0 = 0x%08lx\n", new_regs.lccr0);
68162306a36Sopenharmony_ci	dev_dbg(fbi->dev, "nlccr1 = 0x%08lx\n", new_regs.lccr1);
68262306a36Sopenharmony_ci	dev_dbg(fbi->dev, "nlccr2 = 0x%08lx\n", new_regs.lccr2);
68362306a36Sopenharmony_ci	dev_dbg(fbi->dev, "nlccr3 = 0x%08lx\n", new_regs.lccr3);
68462306a36Sopenharmony_ci
68562306a36Sopenharmony_ci	half_screen_size = var->bits_per_pixel;
68662306a36Sopenharmony_ci	half_screen_size = half_screen_size * var->xres * var->yres / 16;
68762306a36Sopenharmony_ci
68862306a36Sopenharmony_ci	/* Update shadow copy atomically */
68962306a36Sopenharmony_ci	local_irq_save(flags);
69062306a36Sopenharmony_ci	fbi->dbar1 = fbi->palette_dma;
69162306a36Sopenharmony_ci	fbi->dbar2 = fbi->screen_dma + half_screen_size;
69262306a36Sopenharmony_ci
69362306a36Sopenharmony_ci	fbi->reg_lccr0 = new_regs.lccr0;
69462306a36Sopenharmony_ci	fbi->reg_lccr1 = new_regs.lccr1;
69562306a36Sopenharmony_ci	fbi->reg_lccr2 = new_regs.lccr2;
69662306a36Sopenharmony_ci	fbi->reg_lccr3 = new_regs.lccr3;
69762306a36Sopenharmony_ci	local_irq_restore(flags);
69862306a36Sopenharmony_ci
69962306a36Sopenharmony_ci	/*
70062306a36Sopenharmony_ci	 * Only update the registers if the controller is enabled
70162306a36Sopenharmony_ci	 * and something has changed.
70262306a36Sopenharmony_ci	 */
70362306a36Sopenharmony_ci	if (readl_relaxed(fbi->base + LCCR0) != fbi->reg_lccr0 ||
70462306a36Sopenharmony_ci	    readl_relaxed(fbi->base + LCCR1) != fbi->reg_lccr1 ||
70562306a36Sopenharmony_ci	    readl_relaxed(fbi->base + LCCR2) != fbi->reg_lccr2 ||
70662306a36Sopenharmony_ci	    readl_relaxed(fbi->base + LCCR3) != fbi->reg_lccr3 ||
70762306a36Sopenharmony_ci	    readl_relaxed(fbi->base + DBAR1) != fbi->dbar1 ||
70862306a36Sopenharmony_ci	    readl_relaxed(fbi->base + DBAR2) != fbi->dbar2)
70962306a36Sopenharmony_ci		sa1100fb_schedule_work(fbi, C_REENABLE);
71062306a36Sopenharmony_ci
71162306a36Sopenharmony_ci	return 0;
71262306a36Sopenharmony_ci}
71362306a36Sopenharmony_ci
71462306a36Sopenharmony_ci/*
71562306a36Sopenharmony_ci * NOTE!  The following functions are purely helpers for set_ctrlr_state.
71662306a36Sopenharmony_ci * Do not call them directly; set_ctrlr_state does the correct serialisation
71762306a36Sopenharmony_ci * to ensure that things happen in the right way 100% of time time.
71862306a36Sopenharmony_ci *	-- rmk
71962306a36Sopenharmony_ci */
72062306a36Sopenharmony_cistatic inline void __sa1100fb_backlight_power(struct sa1100fb_info *fbi, int on)
72162306a36Sopenharmony_ci{
72262306a36Sopenharmony_ci	dev_dbg(fbi->dev, "backlight o%s\n", on ? "n" : "ff");
72362306a36Sopenharmony_ci
72462306a36Sopenharmony_ci	if (fbi->inf->backlight_power)
72562306a36Sopenharmony_ci		fbi->inf->backlight_power(on);
72662306a36Sopenharmony_ci}
72762306a36Sopenharmony_ci
72862306a36Sopenharmony_cistatic inline void __sa1100fb_lcd_power(struct sa1100fb_info *fbi, int on)
72962306a36Sopenharmony_ci{
73062306a36Sopenharmony_ci	dev_dbg(fbi->dev, "LCD power o%s\n", on ? "n" : "ff");
73162306a36Sopenharmony_ci
73262306a36Sopenharmony_ci	if (fbi->inf->lcd_power)
73362306a36Sopenharmony_ci		fbi->inf->lcd_power(on);
73462306a36Sopenharmony_ci}
73562306a36Sopenharmony_ci
73662306a36Sopenharmony_cistatic void sa1100fb_setup_gpio(struct sa1100fb_info *fbi)
73762306a36Sopenharmony_ci{
73862306a36Sopenharmony_ci	u_int mask = 0;
73962306a36Sopenharmony_ci
74062306a36Sopenharmony_ci	/*
74162306a36Sopenharmony_ci	 * Enable GPIO<9:2> for LCD use if:
74262306a36Sopenharmony_ci	 *  1. Active display, or
74362306a36Sopenharmony_ci	 *  2. Color Dual Passive display
74462306a36Sopenharmony_ci	 *
74562306a36Sopenharmony_ci	 * see table 11.8 on page 11-27 in the SA1100 manual
74662306a36Sopenharmony_ci	 *   -- Erik.
74762306a36Sopenharmony_ci	 *
74862306a36Sopenharmony_ci	 * SA1110 spec update nr. 25 says we can and should
74962306a36Sopenharmony_ci	 * clear LDD15 to 12 for 4 or 8bpp modes with active
75062306a36Sopenharmony_ci	 * panels.
75162306a36Sopenharmony_ci	 */
75262306a36Sopenharmony_ci	if ((fbi->reg_lccr0 & LCCR0_CMS) == LCCR0_Color &&
75362306a36Sopenharmony_ci	    (fbi->reg_lccr0 & (LCCR0_Dual|LCCR0_Act)) != 0) {
75462306a36Sopenharmony_ci		mask = GPIO_LDD11 | GPIO_LDD10 | GPIO_LDD9  | GPIO_LDD8;
75562306a36Sopenharmony_ci
75662306a36Sopenharmony_ci		if (fbi->fb.var.bits_per_pixel > 8 ||
75762306a36Sopenharmony_ci		    (fbi->reg_lccr0 & (LCCR0_Dual|LCCR0_Act)) == LCCR0_Dual)
75862306a36Sopenharmony_ci			mask |= GPIO_LDD15 | GPIO_LDD14 | GPIO_LDD13 | GPIO_LDD12;
75962306a36Sopenharmony_ci
76062306a36Sopenharmony_ci	}
76162306a36Sopenharmony_ci
76262306a36Sopenharmony_ci	if (mask) {
76362306a36Sopenharmony_ci		unsigned long flags;
76462306a36Sopenharmony_ci
76562306a36Sopenharmony_ci		/*
76662306a36Sopenharmony_ci		 * SA-1100 requires the GPIO direction register set
76762306a36Sopenharmony_ci		 * appropriately for the alternate function.  Hence
76862306a36Sopenharmony_ci		 * we set it here via bitmask rather than excessive
76962306a36Sopenharmony_ci		 * fiddling via the GPIO subsystem - and even then
77062306a36Sopenharmony_ci		 * we'll still have to deal with GAFR.
77162306a36Sopenharmony_ci		 */
77262306a36Sopenharmony_ci		local_irq_save(flags);
77362306a36Sopenharmony_ci		GPDR |= mask;
77462306a36Sopenharmony_ci		GAFR |= mask;
77562306a36Sopenharmony_ci		local_irq_restore(flags);
77662306a36Sopenharmony_ci	}
77762306a36Sopenharmony_ci}
77862306a36Sopenharmony_ci
77962306a36Sopenharmony_cistatic void sa1100fb_enable_controller(struct sa1100fb_info *fbi)
78062306a36Sopenharmony_ci{
78162306a36Sopenharmony_ci	dev_dbg(fbi->dev, "Enabling LCD controller\n");
78262306a36Sopenharmony_ci
78362306a36Sopenharmony_ci	/*
78462306a36Sopenharmony_ci	 * Make sure the mode bits are present in the first palette entry
78562306a36Sopenharmony_ci	 */
78662306a36Sopenharmony_ci	fbi->palette_cpu[0] &= 0xcfff;
78762306a36Sopenharmony_ci	fbi->palette_cpu[0] |= palette_pbs(&fbi->fb.var);
78862306a36Sopenharmony_ci
78962306a36Sopenharmony_ci	/* enable LCD controller clock */
79062306a36Sopenharmony_ci	clk_prepare_enable(fbi->clk);
79162306a36Sopenharmony_ci
79262306a36Sopenharmony_ci	/* Sequence from 11.7.10 */
79362306a36Sopenharmony_ci	writel_relaxed(fbi->reg_lccr3, fbi->base + LCCR3);
79462306a36Sopenharmony_ci	writel_relaxed(fbi->reg_lccr2, fbi->base + LCCR2);
79562306a36Sopenharmony_ci	writel_relaxed(fbi->reg_lccr1, fbi->base + LCCR1);
79662306a36Sopenharmony_ci	writel_relaxed(fbi->reg_lccr0 & ~LCCR0_LEN, fbi->base + LCCR0);
79762306a36Sopenharmony_ci	writel_relaxed(fbi->dbar1, fbi->base + DBAR1);
79862306a36Sopenharmony_ci	writel_relaxed(fbi->dbar2, fbi->base + DBAR2);
79962306a36Sopenharmony_ci	writel_relaxed(fbi->reg_lccr0 | LCCR0_LEN, fbi->base + LCCR0);
80062306a36Sopenharmony_ci
80162306a36Sopenharmony_ci	if (fbi->shannon_lcden)
80262306a36Sopenharmony_ci		gpiod_set_value(fbi->shannon_lcden, 1);
80362306a36Sopenharmony_ci
80462306a36Sopenharmony_ci	dev_dbg(fbi->dev, "DBAR1: 0x%08x\n", readl_relaxed(fbi->base + DBAR1));
80562306a36Sopenharmony_ci	dev_dbg(fbi->dev, "DBAR2: 0x%08x\n", readl_relaxed(fbi->base + DBAR2));
80662306a36Sopenharmony_ci	dev_dbg(fbi->dev, "LCCR0: 0x%08x\n", readl_relaxed(fbi->base + LCCR0));
80762306a36Sopenharmony_ci	dev_dbg(fbi->dev, "LCCR1: 0x%08x\n", readl_relaxed(fbi->base + LCCR1));
80862306a36Sopenharmony_ci	dev_dbg(fbi->dev, "LCCR2: 0x%08x\n", readl_relaxed(fbi->base + LCCR2));
80962306a36Sopenharmony_ci	dev_dbg(fbi->dev, "LCCR3: 0x%08x\n", readl_relaxed(fbi->base + LCCR3));
81062306a36Sopenharmony_ci}
81162306a36Sopenharmony_ci
81262306a36Sopenharmony_cistatic void sa1100fb_disable_controller(struct sa1100fb_info *fbi)
81362306a36Sopenharmony_ci{
81462306a36Sopenharmony_ci	DECLARE_WAITQUEUE(wait, current);
81562306a36Sopenharmony_ci	u32 lccr0;
81662306a36Sopenharmony_ci
81762306a36Sopenharmony_ci	dev_dbg(fbi->dev, "Disabling LCD controller\n");
81862306a36Sopenharmony_ci
81962306a36Sopenharmony_ci	if (fbi->shannon_lcden)
82062306a36Sopenharmony_ci		gpiod_set_value(fbi->shannon_lcden, 0);
82162306a36Sopenharmony_ci
82262306a36Sopenharmony_ci	set_current_state(TASK_UNINTERRUPTIBLE);
82362306a36Sopenharmony_ci	add_wait_queue(&fbi->ctrlr_wait, &wait);
82462306a36Sopenharmony_ci
82562306a36Sopenharmony_ci	/* Clear LCD Status Register */
82662306a36Sopenharmony_ci	writel_relaxed(~0, fbi->base + LCSR);
82762306a36Sopenharmony_ci
82862306a36Sopenharmony_ci	lccr0 = readl_relaxed(fbi->base + LCCR0);
82962306a36Sopenharmony_ci	lccr0 &= ~LCCR0_LDM;	/* Enable LCD Disable Done Interrupt */
83062306a36Sopenharmony_ci	writel_relaxed(lccr0, fbi->base + LCCR0);
83162306a36Sopenharmony_ci	lccr0 &= ~LCCR0_LEN;	/* Disable LCD Controller */
83262306a36Sopenharmony_ci	writel_relaxed(lccr0, fbi->base + LCCR0);
83362306a36Sopenharmony_ci
83462306a36Sopenharmony_ci	schedule_timeout(20 * HZ / 1000);
83562306a36Sopenharmony_ci	remove_wait_queue(&fbi->ctrlr_wait, &wait);
83662306a36Sopenharmony_ci
83762306a36Sopenharmony_ci	/* disable LCD controller clock */
83862306a36Sopenharmony_ci	clk_disable_unprepare(fbi->clk);
83962306a36Sopenharmony_ci}
84062306a36Sopenharmony_ci
84162306a36Sopenharmony_ci/*
84262306a36Sopenharmony_ci *  sa1100fb_handle_irq: Handle 'LCD DONE' interrupts.
84362306a36Sopenharmony_ci */
84462306a36Sopenharmony_cistatic irqreturn_t sa1100fb_handle_irq(int irq, void *dev_id)
84562306a36Sopenharmony_ci{
84662306a36Sopenharmony_ci	struct sa1100fb_info *fbi = dev_id;
84762306a36Sopenharmony_ci	unsigned int lcsr = readl_relaxed(fbi->base + LCSR);
84862306a36Sopenharmony_ci
84962306a36Sopenharmony_ci	if (lcsr & LCSR_LDD) {
85062306a36Sopenharmony_ci		u32 lccr0 = readl_relaxed(fbi->base + LCCR0) | LCCR0_LDM;
85162306a36Sopenharmony_ci		writel_relaxed(lccr0, fbi->base + LCCR0);
85262306a36Sopenharmony_ci		wake_up(&fbi->ctrlr_wait);
85362306a36Sopenharmony_ci	}
85462306a36Sopenharmony_ci
85562306a36Sopenharmony_ci	writel_relaxed(lcsr, fbi->base + LCSR);
85662306a36Sopenharmony_ci	return IRQ_HANDLED;
85762306a36Sopenharmony_ci}
85862306a36Sopenharmony_ci
85962306a36Sopenharmony_ci/*
86062306a36Sopenharmony_ci * This function must be called from task context only, since it will
86162306a36Sopenharmony_ci * sleep when disabling the LCD controller, or if we get two contending
86262306a36Sopenharmony_ci * processes trying to alter state.
86362306a36Sopenharmony_ci */
86462306a36Sopenharmony_cistatic void set_ctrlr_state(struct sa1100fb_info *fbi, u_int state)
86562306a36Sopenharmony_ci{
86662306a36Sopenharmony_ci	u_int old_state;
86762306a36Sopenharmony_ci
86862306a36Sopenharmony_ci	mutex_lock(&fbi->ctrlr_lock);
86962306a36Sopenharmony_ci
87062306a36Sopenharmony_ci	old_state = fbi->state;
87162306a36Sopenharmony_ci
87262306a36Sopenharmony_ci	/*
87362306a36Sopenharmony_ci	 * Hack around fbcon initialisation.
87462306a36Sopenharmony_ci	 */
87562306a36Sopenharmony_ci	if (old_state == C_STARTUP && state == C_REENABLE)
87662306a36Sopenharmony_ci		state = C_ENABLE;
87762306a36Sopenharmony_ci
87862306a36Sopenharmony_ci	switch (state) {
87962306a36Sopenharmony_ci	case C_DISABLE_CLKCHANGE:
88062306a36Sopenharmony_ci		/*
88162306a36Sopenharmony_ci		 * Disable controller for clock change.  If the
88262306a36Sopenharmony_ci		 * controller is already disabled, then do nothing.
88362306a36Sopenharmony_ci		 */
88462306a36Sopenharmony_ci		if (old_state != C_DISABLE && old_state != C_DISABLE_PM) {
88562306a36Sopenharmony_ci			fbi->state = state;
88662306a36Sopenharmony_ci			sa1100fb_disable_controller(fbi);
88762306a36Sopenharmony_ci		}
88862306a36Sopenharmony_ci		break;
88962306a36Sopenharmony_ci
89062306a36Sopenharmony_ci	case C_DISABLE_PM:
89162306a36Sopenharmony_ci	case C_DISABLE:
89262306a36Sopenharmony_ci		/*
89362306a36Sopenharmony_ci		 * Disable controller
89462306a36Sopenharmony_ci		 */
89562306a36Sopenharmony_ci		if (old_state != C_DISABLE) {
89662306a36Sopenharmony_ci			fbi->state = state;
89762306a36Sopenharmony_ci
89862306a36Sopenharmony_ci			__sa1100fb_backlight_power(fbi, 0);
89962306a36Sopenharmony_ci			if (old_state != C_DISABLE_CLKCHANGE)
90062306a36Sopenharmony_ci				sa1100fb_disable_controller(fbi);
90162306a36Sopenharmony_ci			__sa1100fb_lcd_power(fbi, 0);
90262306a36Sopenharmony_ci		}
90362306a36Sopenharmony_ci		break;
90462306a36Sopenharmony_ci
90562306a36Sopenharmony_ci	case C_ENABLE_CLKCHANGE:
90662306a36Sopenharmony_ci		/*
90762306a36Sopenharmony_ci		 * Enable the controller after clock change.  Only
90862306a36Sopenharmony_ci		 * do this if we were disabled for the clock change.
90962306a36Sopenharmony_ci		 */
91062306a36Sopenharmony_ci		if (old_state == C_DISABLE_CLKCHANGE) {
91162306a36Sopenharmony_ci			fbi->state = C_ENABLE;
91262306a36Sopenharmony_ci			sa1100fb_enable_controller(fbi);
91362306a36Sopenharmony_ci		}
91462306a36Sopenharmony_ci		break;
91562306a36Sopenharmony_ci
91662306a36Sopenharmony_ci	case C_REENABLE:
91762306a36Sopenharmony_ci		/*
91862306a36Sopenharmony_ci		 * Re-enable the controller only if it was already
91962306a36Sopenharmony_ci		 * enabled.  This is so we reprogram the control
92062306a36Sopenharmony_ci		 * registers.
92162306a36Sopenharmony_ci		 */
92262306a36Sopenharmony_ci		if (old_state == C_ENABLE) {
92362306a36Sopenharmony_ci			sa1100fb_disable_controller(fbi);
92462306a36Sopenharmony_ci			sa1100fb_setup_gpio(fbi);
92562306a36Sopenharmony_ci			sa1100fb_enable_controller(fbi);
92662306a36Sopenharmony_ci		}
92762306a36Sopenharmony_ci		break;
92862306a36Sopenharmony_ci
92962306a36Sopenharmony_ci	case C_ENABLE_PM:
93062306a36Sopenharmony_ci		/*
93162306a36Sopenharmony_ci		 * Re-enable the controller after PM.  This is not
93262306a36Sopenharmony_ci		 * perfect - think about the case where we were doing
93362306a36Sopenharmony_ci		 * a clock change, and we suspended half-way through.
93462306a36Sopenharmony_ci		 */
93562306a36Sopenharmony_ci		if (old_state != C_DISABLE_PM)
93662306a36Sopenharmony_ci			break;
93762306a36Sopenharmony_ci		fallthrough;
93862306a36Sopenharmony_ci
93962306a36Sopenharmony_ci	case C_ENABLE:
94062306a36Sopenharmony_ci		/*
94162306a36Sopenharmony_ci		 * Power up the LCD screen, enable controller, and
94262306a36Sopenharmony_ci		 * turn on the backlight.
94362306a36Sopenharmony_ci		 */
94462306a36Sopenharmony_ci		if (old_state != C_ENABLE) {
94562306a36Sopenharmony_ci			fbi->state = C_ENABLE;
94662306a36Sopenharmony_ci			sa1100fb_setup_gpio(fbi);
94762306a36Sopenharmony_ci			__sa1100fb_lcd_power(fbi, 1);
94862306a36Sopenharmony_ci			sa1100fb_enable_controller(fbi);
94962306a36Sopenharmony_ci			__sa1100fb_backlight_power(fbi, 1);
95062306a36Sopenharmony_ci		}
95162306a36Sopenharmony_ci		break;
95262306a36Sopenharmony_ci	}
95362306a36Sopenharmony_ci	mutex_unlock(&fbi->ctrlr_lock);
95462306a36Sopenharmony_ci}
95562306a36Sopenharmony_ci
95662306a36Sopenharmony_ci/*
95762306a36Sopenharmony_ci * Our LCD controller task (which is called when we blank or unblank)
95862306a36Sopenharmony_ci * via keventd.
95962306a36Sopenharmony_ci */
96062306a36Sopenharmony_cistatic void sa1100fb_task(struct work_struct *w)
96162306a36Sopenharmony_ci{
96262306a36Sopenharmony_ci	struct sa1100fb_info *fbi = container_of(w, struct sa1100fb_info, task);
96362306a36Sopenharmony_ci	u_int state = xchg(&fbi->task_state, -1);
96462306a36Sopenharmony_ci
96562306a36Sopenharmony_ci	set_ctrlr_state(fbi, state);
96662306a36Sopenharmony_ci}
96762306a36Sopenharmony_ci
96862306a36Sopenharmony_ci#ifdef CONFIG_CPU_FREQ
96962306a36Sopenharmony_ci/*
97062306a36Sopenharmony_ci * CPU clock speed change handler.  We need to adjust the LCD timing
97162306a36Sopenharmony_ci * parameters when the CPU clock is adjusted by the power management
97262306a36Sopenharmony_ci * subsystem.
97362306a36Sopenharmony_ci */
97462306a36Sopenharmony_cistatic int
97562306a36Sopenharmony_cisa1100fb_freq_transition(struct notifier_block *nb, unsigned long val,
97662306a36Sopenharmony_ci			 void *data)
97762306a36Sopenharmony_ci{
97862306a36Sopenharmony_ci	struct sa1100fb_info *fbi = TO_INF(nb, freq_transition);
97962306a36Sopenharmony_ci	u_int pcd;
98062306a36Sopenharmony_ci
98162306a36Sopenharmony_ci	switch (val) {
98262306a36Sopenharmony_ci	case CPUFREQ_PRECHANGE:
98362306a36Sopenharmony_ci		set_ctrlr_state(fbi, C_DISABLE_CLKCHANGE);
98462306a36Sopenharmony_ci		break;
98562306a36Sopenharmony_ci
98662306a36Sopenharmony_ci	case CPUFREQ_POSTCHANGE:
98762306a36Sopenharmony_ci		pcd = get_pcd(fbi, fbi->fb.var.pixclock);
98862306a36Sopenharmony_ci		fbi->reg_lccr3 = (fbi->reg_lccr3 & ~0xff) | LCCR3_PixClkDiv(pcd);
98962306a36Sopenharmony_ci		set_ctrlr_state(fbi, C_ENABLE_CLKCHANGE);
99062306a36Sopenharmony_ci		break;
99162306a36Sopenharmony_ci	}
99262306a36Sopenharmony_ci	return 0;
99362306a36Sopenharmony_ci}
99462306a36Sopenharmony_ci#endif
99562306a36Sopenharmony_ci
99662306a36Sopenharmony_ci#ifdef CONFIG_PM
99762306a36Sopenharmony_ci/*
99862306a36Sopenharmony_ci * Power management hooks.  Note that we won't be called from IRQ context,
99962306a36Sopenharmony_ci * unlike the blank functions above, so we may sleep.
100062306a36Sopenharmony_ci */
100162306a36Sopenharmony_cistatic int sa1100fb_suspend(struct platform_device *dev, pm_message_t state)
100262306a36Sopenharmony_ci{
100362306a36Sopenharmony_ci	struct sa1100fb_info *fbi = platform_get_drvdata(dev);
100462306a36Sopenharmony_ci
100562306a36Sopenharmony_ci	set_ctrlr_state(fbi, C_DISABLE_PM);
100662306a36Sopenharmony_ci	return 0;
100762306a36Sopenharmony_ci}
100862306a36Sopenharmony_ci
100962306a36Sopenharmony_cistatic int sa1100fb_resume(struct platform_device *dev)
101062306a36Sopenharmony_ci{
101162306a36Sopenharmony_ci	struct sa1100fb_info *fbi = platform_get_drvdata(dev);
101262306a36Sopenharmony_ci
101362306a36Sopenharmony_ci	set_ctrlr_state(fbi, C_ENABLE_PM);
101462306a36Sopenharmony_ci	return 0;
101562306a36Sopenharmony_ci}
101662306a36Sopenharmony_ci#else
101762306a36Sopenharmony_ci#define sa1100fb_suspend	NULL
101862306a36Sopenharmony_ci#define sa1100fb_resume		NULL
101962306a36Sopenharmony_ci#endif
102062306a36Sopenharmony_ci
102162306a36Sopenharmony_ci/*
102262306a36Sopenharmony_ci * sa1100fb_map_video_memory():
102362306a36Sopenharmony_ci *      Allocates the DRAM memory for the frame buffer.  This buffer is
102462306a36Sopenharmony_ci *	remapped into a non-cached, non-buffered, memory region to
102562306a36Sopenharmony_ci *      allow palette and pixel writes to occur without flushing the
102662306a36Sopenharmony_ci *      cache.  Once this area is remapped, all virtual memory
102762306a36Sopenharmony_ci *      access to the video memory should occur at the new region.
102862306a36Sopenharmony_ci */
102962306a36Sopenharmony_cistatic int sa1100fb_map_video_memory(struct sa1100fb_info *fbi)
103062306a36Sopenharmony_ci{
103162306a36Sopenharmony_ci	/*
103262306a36Sopenharmony_ci	 * We reserve one page for the palette, plus the size
103362306a36Sopenharmony_ci	 * of the framebuffer.
103462306a36Sopenharmony_ci	 */
103562306a36Sopenharmony_ci	fbi->map_size = PAGE_ALIGN(fbi->fb.fix.smem_len + PAGE_SIZE);
103662306a36Sopenharmony_ci	fbi->map_cpu = dma_alloc_wc(fbi->dev, fbi->map_size, &fbi->map_dma,
103762306a36Sopenharmony_ci				    GFP_KERNEL);
103862306a36Sopenharmony_ci
103962306a36Sopenharmony_ci	if (fbi->map_cpu) {
104062306a36Sopenharmony_ci		fbi->fb.screen_base = fbi->map_cpu + PAGE_SIZE;
104162306a36Sopenharmony_ci		fbi->screen_dma = fbi->map_dma + PAGE_SIZE;
104262306a36Sopenharmony_ci		/*
104362306a36Sopenharmony_ci		 * FIXME: this is actually the wrong thing to place in
104462306a36Sopenharmony_ci		 * smem_start.  But fbdev suffers from the problem that
104562306a36Sopenharmony_ci		 * it needs an API which doesn't exist (in this case,
104662306a36Sopenharmony_ci		 * dma_writecombine_mmap)
104762306a36Sopenharmony_ci		 */
104862306a36Sopenharmony_ci		fbi->fb.fix.smem_start = fbi->screen_dma;
104962306a36Sopenharmony_ci	}
105062306a36Sopenharmony_ci
105162306a36Sopenharmony_ci	return fbi->map_cpu ? 0 : -ENOMEM;
105262306a36Sopenharmony_ci}
105362306a36Sopenharmony_ci
105462306a36Sopenharmony_ci/* Fake monspecs to fill in fbinfo structure */
105562306a36Sopenharmony_cistatic const struct fb_monspecs monspecs = {
105662306a36Sopenharmony_ci	.hfmin	= 30000,
105762306a36Sopenharmony_ci	.hfmax	= 70000,
105862306a36Sopenharmony_ci	.vfmin	= 50,
105962306a36Sopenharmony_ci	.vfmax	= 65,
106062306a36Sopenharmony_ci};
106162306a36Sopenharmony_ci
106262306a36Sopenharmony_ci
106362306a36Sopenharmony_cistatic struct sa1100fb_info *sa1100fb_init_fbinfo(struct device *dev)
106462306a36Sopenharmony_ci{
106562306a36Sopenharmony_ci	struct sa1100fb_mach_info *inf = dev_get_platdata(dev);
106662306a36Sopenharmony_ci	struct sa1100fb_info *fbi;
106762306a36Sopenharmony_ci	unsigned i;
106862306a36Sopenharmony_ci
106962306a36Sopenharmony_ci	fbi = devm_kzalloc(dev, sizeof(struct sa1100fb_info), GFP_KERNEL);
107062306a36Sopenharmony_ci	if (!fbi)
107162306a36Sopenharmony_ci		return NULL;
107262306a36Sopenharmony_ci
107362306a36Sopenharmony_ci	fbi->dev = dev;
107462306a36Sopenharmony_ci
107562306a36Sopenharmony_ci	strcpy(fbi->fb.fix.id, SA1100_NAME);
107662306a36Sopenharmony_ci
107762306a36Sopenharmony_ci	fbi->fb.fix.type	= FB_TYPE_PACKED_PIXELS;
107862306a36Sopenharmony_ci	fbi->fb.fix.type_aux	= 0;
107962306a36Sopenharmony_ci	fbi->fb.fix.xpanstep	= 0;
108062306a36Sopenharmony_ci	fbi->fb.fix.ypanstep	= 0;
108162306a36Sopenharmony_ci	fbi->fb.fix.ywrapstep	= 0;
108262306a36Sopenharmony_ci	fbi->fb.fix.accel	= FB_ACCEL_NONE;
108362306a36Sopenharmony_ci
108462306a36Sopenharmony_ci	fbi->fb.var.nonstd	= 0;
108562306a36Sopenharmony_ci	fbi->fb.var.activate	= FB_ACTIVATE_NOW;
108662306a36Sopenharmony_ci	fbi->fb.var.height	= -1;
108762306a36Sopenharmony_ci	fbi->fb.var.width	= -1;
108862306a36Sopenharmony_ci	fbi->fb.var.accel_flags	= 0;
108962306a36Sopenharmony_ci	fbi->fb.var.vmode	= FB_VMODE_NONINTERLACED;
109062306a36Sopenharmony_ci
109162306a36Sopenharmony_ci	fbi->fb.fbops		= &sa1100fb_ops;
109262306a36Sopenharmony_ci	fbi->fb.monspecs	= monspecs;
109362306a36Sopenharmony_ci	fbi->fb.pseudo_palette	= fbi->pseudo_palette;
109462306a36Sopenharmony_ci
109562306a36Sopenharmony_ci	fbi->rgb[RGB_4]		= &rgb_4;
109662306a36Sopenharmony_ci	fbi->rgb[RGB_8]		= &rgb_8;
109762306a36Sopenharmony_ci	fbi->rgb[RGB_16]	= &def_rgb_16;
109862306a36Sopenharmony_ci
109962306a36Sopenharmony_ci	/*
110062306a36Sopenharmony_ci	 * People just don't seem to get this.  We don't support
110162306a36Sopenharmony_ci	 * anything but correct entries now, so panic if someone
110262306a36Sopenharmony_ci	 * does something stupid.
110362306a36Sopenharmony_ci	 */
110462306a36Sopenharmony_ci	if (inf->lccr3 & (LCCR3_VrtSnchL|LCCR3_HorSnchL|0xff) ||
110562306a36Sopenharmony_ci	    inf->pixclock == 0)
110662306a36Sopenharmony_ci		panic("sa1100fb error: invalid LCCR3 fields set or zero "
110762306a36Sopenharmony_ci			"pixclock.");
110862306a36Sopenharmony_ci
110962306a36Sopenharmony_ci	fbi->fb.var.xres		= inf->xres;
111062306a36Sopenharmony_ci	fbi->fb.var.xres_virtual	= inf->xres;
111162306a36Sopenharmony_ci	fbi->fb.var.yres		= inf->yres;
111262306a36Sopenharmony_ci	fbi->fb.var.yres_virtual	= inf->yres;
111362306a36Sopenharmony_ci	fbi->fb.var.bits_per_pixel	= inf->bpp;
111462306a36Sopenharmony_ci	fbi->fb.var.pixclock		= inf->pixclock;
111562306a36Sopenharmony_ci	fbi->fb.var.hsync_len		= inf->hsync_len;
111662306a36Sopenharmony_ci	fbi->fb.var.left_margin		= inf->left_margin;
111762306a36Sopenharmony_ci	fbi->fb.var.right_margin	= inf->right_margin;
111862306a36Sopenharmony_ci	fbi->fb.var.vsync_len		= inf->vsync_len;
111962306a36Sopenharmony_ci	fbi->fb.var.upper_margin	= inf->upper_margin;
112062306a36Sopenharmony_ci	fbi->fb.var.lower_margin	= inf->lower_margin;
112162306a36Sopenharmony_ci	fbi->fb.var.sync		= inf->sync;
112262306a36Sopenharmony_ci	fbi->fb.var.grayscale		= inf->cmap_greyscale;
112362306a36Sopenharmony_ci	fbi->state			= C_STARTUP;
112462306a36Sopenharmony_ci	fbi->task_state			= (u_char)-1;
112562306a36Sopenharmony_ci	fbi->fb.fix.smem_len		= inf->xres * inf->yres *
112662306a36Sopenharmony_ci					  inf->bpp / 8;
112762306a36Sopenharmony_ci	fbi->inf			= inf;
112862306a36Sopenharmony_ci
112962306a36Sopenharmony_ci	/* Copy the RGB bitfield overrides */
113062306a36Sopenharmony_ci	for (i = 0; i < NR_RGB; i++)
113162306a36Sopenharmony_ci		if (inf->rgb[i])
113262306a36Sopenharmony_ci			fbi->rgb[i] = inf->rgb[i];
113362306a36Sopenharmony_ci
113462306a36Sopenharmony_ci	init_waitqueue_head(&fbi->ctrlr_wait);
113562306a36Sopenharmony_ci	INIT_WORK(&fbi->task, sa1100fb_task);
113662306a36Sopenharmony_ci	mutex_init(&fbi->ctrlr_lock);
113762306a36Sopenharmony_ci
113862306a36Sopenharmony_ci	return fbi;
113962306a36Sopenharmony_ci}
114062306a36Sopenharmony_ci
114162306a36Sopenharmony_cistatic int sa1100fb_probe(struct platform_device *pdev)
114262306a36Sopenharmony_ci{
114362306a36Sopenharmony_ci	struct sa1100fb_info *fbi;
114462306a36Sopenharmony_ci	int ret, irq;
114562306a36Sopenharmony_ci
114662306a36Sopenharmony_ci	if (!dev_get_platdata(&pdev->dev)) {
114762306a36Sopenharmony_ci		dev_err(&pdev->dev, "no platform LCD data\n");
114862306a36Sopenharmony_ci		return -EINVAL;
114962306a36Sopenharmony_ci	}
115062306a36Sopenharmony_ci
115162306a36Sopenharmony_ci	irq = platform_get_irq(pdev, 0);
115262306a36Sopenharmony_ci	if (irq < 0)
115362306a36Sopenharmony_ci		return -EINVAL;
115462306a36Sopenharmony_ci
115562306a36Sopenharmony_ci	fbi = sa1100fb_init_fbinfo(&pdev->dev);
115662306a36Sopenharmony_ci	if (!fbi)
115762306a36Sopenharmony_ci		return -ENOMEM;
115862306a36Sopenharmony_ci
115962306a36Sopenharmony_ci	fbi->base = devm_platform_ioremap_resource(pdev, 0);
116062306a36Sopenharmony_ci	if (IS_ERR(fbi->base))
116162306a36Sopenharmony_ci		return PTR_ERR(fbi->base);
116262306a36Sopenharmony_ci
116362306a36Sopenharmony_ci	fbi->clk = devm_clk_get(&pdev->dev, NULL);
116462306a36Sopenharmony_ci	if (IS_ERR(fbi->clk))
116562306a36Sopenharmony_ci		return PTR_ERR(fbi->clk);
116662306a36Sopenharmony_ci
116762306a36Sopenharmony_ci	ret = devm_request_irq(&pdev->dev, irq, sa1100fb_handle_irq, 0,
116862306a36Sopenharmony_ci			       "LCD", fbi);
116962306a36Sopenharmony_ci	if (ret) {
117062306a36Sopenharmony_ci		dev_err(&pdev->dev, "request_irq failed: %d\n", ret);
117162306a36Sopenharmony_ci		return ret;
117262306a36Sopenharmony_ci	}
117362306a36Sopenharmony_ci
117462306a36Sopenharmony_ci	fbi->shannon_lcden = gpiod_get_optional(&pdev->dev, "shannon-lcden",
117562306a36Sopenharmony_ci						GPIOD_OUT_LOW);
117662306a36Sopenharmony_ci	if (IS_ERR(fbi->shannon_lcden))
117762306a36Sopenharmony_ci		return PTR_ERR(fbi->shannon_lcden);
117862306a36Sopenharmony_ci
117962306a36Sopenharmony_ci	/* Initialize video memory */
118062306a36Sopenharmony_ci	ret = sa1100fb_map_video_memory(fbi);
118162306a36Sopenharmony_ci	if (ret)
118262306a36Sopenharmony_ci		return ret;
118362306a36Sopenharmony_ci
118462306a36Sopenharmony_ci	/*
118562306a36Sopenharmony_ci	 * This makes sure that our colour bitfield
118662306a36Sopenharmony_ci	 * descriptors are correctly initialised.
118762306a36Sopenharmony_ci	 */
118862306a36Sopenharmony_ci	sa1100fb_check_var(&fbi->fb.var, &fbi->fb);
118962306a36Sopenharmony_ci
119062306a36Sopenharmony_ci	platform_set_drvdata(pdev, fbi);
119162306a36Sopenharmony_ci
119262306a36Sopenharmony_ci	ret = register_framebuffer(&fbi->fb);
119362306a36Sopenharmony_ci	if (ret < 0) {
119462306a36Sopenharmony_ci		dma_free_wc(fbi->dev, fbi->map_size, fbi->map_cpu,
119562306a36Sopenharmony_ci			    fbi->map_dma);
119662306a36Sopenharmony_ci		return ret;
119762306a36Sopenharmony_ci	}
119862306a36Sopenharmony_ci
119962306a36Sopenharmony_ci#ifdef CONFIG_CPU_FREQ
120062306a36Sopenharmony_ci	fbi->freq_transition.notifier_call = sa1100fb_freq_transition;
120162306a36Sopenharmony_ci	cpufreq_register_notifier(&fbi->freq_transition, CPUFREQ_TRANSITION_NOTIFIER);
120262306a36Sopenharmony_ci#endif
120362306a36Sopenharmony_ci
120462306a36Sopenharmony_ci	/* This driver cannot be unloaded at the moment */
120562306a36Sopenharmony_ci	return 0;
120662306a36Sopenharmony_ci}
120762306a36Sopenharmony_ci
120862306a36Sopenharmony_cistatic struct platform_driver sa1100fb_driver = {
120962306a36Sopenharmony_ci	.probe		= sa1100fb_probe,
121062306a36Sopenharmony_ci	.suspend	= sa1100fb_suspend,
121162306a36Sopenharmony_ci	.resume		= sa1100fb_resume,
121262306a36Sopenharmony_ci	.driver		= {
121362306a36Sopenharmony_ci		.name	= "sa11x0-fb",
121462306a36Sopenharmony_ci	},
121562306a36Sopenharmony_ci};
121662306a36Sopenharmony_ci
121762306a36Sopenharmony_cistatic int __init sa1100fb_init(void)
121862306a36Sopenharmony_ci{
121962306a36Sopenharmony_ci	if (fb_get_options("sa1100fb", NULL))
122062306a36Sopenharmony_ci		return -ENODEV;
122162306a36Sopenharmony_ci
122262306a36Sopenharmony_ci	return platform_driver_register(&sa1100fb_driver);
122362306a36Sopenharmony_ci}
122462306a36Sopenharmony_ci
122562306a36Sopenharmony_cimodule_init(sa1100fb_init);
122662306a36Sopenharmony_ciMODULE_DESCRIPTION("StrongARM-1100/1110 framebuffer driver");
122762306a36Sopenharmony_ciMODULE_LICENSE("GPL");
1228