162306a36Sopenharmony_ci/*
262306a36Sopenharmony_ci * Xilinx TFT frame buffer driver
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci * Author: MontaVista Software, Inc.
562306a36Sopenharmony_ci *         source@mvista.com
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * 2002-2007 (c) MontaVista Software, Inc.
862306a36Sopenharmony_ci * 2007 (c) Secret Lab Technologies, Ltd.
962306a36Sopenharmony_ci * 2009 (c) Xilinx Inc.
1062306a36Sopenharmony_ci *
1162306a36Sopenharmony_ci * This file is licensed under the terms of the GNU General Public License
1262306a36Sopenharmony_ci * version 2.  This program is licensed "as is" without any warranty of any
1362306a36Sopenharmony_ci * kind, whether express or implied.
1462306a36Sopenharmony_ci */
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci/*
1762306a36Sopenharmony_ci * This driver was based on au1100fb.c by MontaVista rewritten for 2.6
1862306a36Sopenharmony_ci * by Embedded Alley Solutions <source@embeddedalley.com>, which in turn
1962306a36Sopenharmony_ci * was based on skeletonfb.c, Skeleton for a frame buffer device by
2062306a36Sopenharmony_ci * Geert Uytterhoeven.
2162306a36Sopenharmony_ci */
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci#include <linux/device.h>
2462306a36Sopenharmony_ci#include <linux/module.h>
2562306a36Sopenharmony_ci#include <linux/kernel.h>
2662306a36Sopenharmony_ci#include <linux/errno.h>
2762306a36Sopenharmony_ci#include <linux/platform_device.h>
2862306a36Sopenharmony_ci#include <linux/string.h>
2962306a36Sopenharmony_ci#include <linux/mm.h>
3062306a36Sopenharmony_ci#include <linux/fb.h>
3162306a36Sopenharmony_ci#include <linux/init.h>
3262306a36Sopenharmony_ci#include <linux/dma-mapping.h>
3362306a36Sopenharmony_ci#include <linux/of.h>
3462306a36Sopenharmony_ci#include <linux/io.h>
3562306a36Sopenharmony_ci#include <linux/slab.h>
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci#ifdef CONFIG_PPC_DCR
3862306a36Sopenharmony_ci#include <asm/dcr.h>
3962306a36Sopenharmony_ci#endif
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci#define DRIVER_NAME		"xilinxfb"
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci/*
4462306a36Sopenharmony_ci * Xilinx calls it "TFT LCD Controller" though it can also be used for
4562306a36Sopenharmony_ci * the VGA port on the Xilinx ML40x board. This is a hardware display
4662306a36Sopenharmony_ci * controller for a 640x480 resolution TFT or VGA screen.
4762306a36Sopenharmony_ci *
4862306a36Sopenharmony_ci * The interface to the framebuffer is nice and simple.  There are two
4962306a36Sopenharmony_ci * control registers.  The first tells the LCD interface where in memory
5062306a36Sopenharmony_ci * the frame buffer is (only the 11 most significant bits are used, so
5162306a36Sopenharmony_ci * don't start thinking about scrolling).  The second allows the LCD to
5262306a36Sopenharmony_ci * be turned on or off as well as rotated 180 degrees.
5362306a36Sopenharmony_ci *
5462306a36Sopenharmony_ci * In case of direct BUS access the second control register will be at
5562306a36Sopenharmony_ci * an offset of 4 as compared to the DCR access where the offset is 1
5662306a36Sopenharmony_ci * i.e. REG_CTRL. So this is taken care in the function
5762306a36Sopenharmony_ci * xilinx_fb_out32 where it left shifts the offset 2 times in case of
5862306a36Sopenharmony_ci * direct BUS access.
5962306a36Sopenharmony_ci */
6062306a36Sopenharmony_ci#define NUM_REGS	2
6162306a36Sopenharmony_ci#define REG_FB_ADDR	0
6262306a36Sopenharmony_ci#define REG_CTRL	1
6362306a36Sopenharmony_ci#define REG_CTRL_ENABLE	 0x0001
6462306a36Sopenharmony_ci#define REG_CTRL_ROTATE	 0x0002
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci/*
6762306a36Sopenharmony_ci * The hardware only handles a single mode: 640x480 24 bit true
6862306a36Sopenharmony_ci * color. Each pixel gets a word (32 bits) of memory.  Within each word,
6962306a36Sopenharmony_ci * the 8 most significant bits are ignored, the next 8 bits are the red
7062306a36Sopenharmony_ci * level, the next 8 bits are the green level and the 8 least
7162306a36Sopenharmony_ci * significant bits are the blue level.  Each row of the LCD uses 1024
7262306a36Sopenharmony_ci * words, but only the first 640 pixels are displayed with the other 384
7362306a36Sopenharmony_ci * words being ignored.  There are 480 rows.
7462306a36Sopenharmony_ci */
7562306a36Sopenharmony_ci#define BYTES_PER_PIXEL	4
7662306a36Sopenharmony_ci#define BITS_PER_PIXEL	(BYTES_PER_PIXEL * 8)
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci#define RED_SHIFT	16
7962306a36Sopenharmony_ci#define GREEN_SHIFT	8
8062306a36Sopenharmony_ci#define BLUE_SHIFT	0
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci#define PALETTE_ENTRIES_NO	16	/* passed to fb_alloc_cmap() */
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci/* ML300/403 reference design framebuffer driver platform data struct */
8562306a36Sopenharmony_cistruct xilinxfb_platform_data {
8662306a36Sopenharmony_ci	u32 rotate_screen;      /* Flag to rotate display 180 degrees */
8762306a36Sopenharmony_ci	u32 screen_height_mm;   /* Physical dimensions of screen in mm */
8862306a36Sopenharmony_ci	u32 screen_width_mm;
8962306a36Sopenharmony_ci	u32 xres, yres;         /* resolution of screen in pixels */
9062306a36Sopenharmony_ci	u32 xvirt, yvirt;       /* resolution of memory buffer */
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	/* Physical address of framebuffer memory; If non-zero, driver
9362306a36Sopenharmony_ci	 * will use provided memory address instead of allocating one from
9462306a36Sopenharmony_ci	 * the consistent pool.
9562306a36Sopenharmony_ci	 */
9662306a36Sopenharmony_ci	u32 fb_phys;
9762306a36Sopenharmony_ci};
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci/*
10062306a36Sopenharmony_ci * Default xilinxfb configuration
10162306a36Sopenharmony_ci */
10262306a36Sopenharmony_cistatic const struct xilinxfb_platform_data xilinx_fb_default_pdata = {
10362306a36Sopenharmony_ci	.xres = 640,
10462306a36Sopenharmony_ci	.yres = 480,
10562306a36Sopenharmony_ci	.xvirt = 1024,
10662306a36Sopenharmony_ci	.yvirt = 480,
10762306a36Sopenharmony_ci};
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci/*
11062306a36Sopenharmony_ci * Here are the default fb_fix_screeninfo and fb_var_screeninfo structures
11162306a36Sopenharmony_ci */
11262306a36Sopenharmony_cistatic const struct fb_fix_screeninfo xilinx_fb_fix = {
11362306a36Sopenharmony_ci	.id =		"Xilinx",
11462306a36Sopenharmony_ci	.type =		FB_TYPE_PACKED_PIXELS,
11562306a36Sopenharmony_ci	.visual =	FB_VISUAL_TRUECOLOR,
11662306a36Sopenharmony_ci	.accel =	FB_ACCEL_NONE
11762306a36Sopenharmony_ci};
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_cistatic const struct fb_var_screeninfo xilinx_fb_var = {
12062306a36Sopenharmony_ci	.bits_per_pixel =	BITS_PER_PIXEL,
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	.red =		{ RED_SHIFT, 8, 0 },
12362306a36Sopenharmony_ci	.green =	{ GREEN_SHIFT, 8, 0 },
12462306a36Sopenharmony_ci	.blue =		{ BLUE_SHIFT, 8, 0 },
12562306a36Sopenharmony_ci	.transp =	{ 0, 0, 0 },
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	.activate =	FB_ACTIVATE_NOW
12862306a36Sopenharmony_ci};
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci#define BUS_ACCESS_FLAG		0x1 /* 1 = BUS, 0 = DCR */
13162306a36Sopenharmony_ci#define LITTLE_ENDIAN_ACCESS	0x2 /* LITTLE ENDIAN IO functions */
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_cistruct xilinxfb_drvdata {
13462306a36Sopenharmony_ci	struct fb_info	info;		/* FB driver info record */
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	phys_addr_t	regs_phys;	/* phys. address of the control
13762306a36Sopenharmony_ci					 * registers
13862306a36Sopenharmony_ci					 */
13962306a36Sopenharmony_ci	void __iomem	*regs;		/* virt. address of the control
14062306a36Sopenharmony_ci					 * registers
14162306a36Sopenharmony_ci					 */
14262306a36Sopenharmony_ci#ifdef CONFIG_PPC_DCR
14362306a36Sopenharmony_ci	dcr_host_t      dcr_host;
14462306a36Sopenharmony_ci	unsigned int    dcr_len;
14562306a36Sopenharmony_ci#endif
14662306a36Sopenharmony_ci	void		*fb_virt;	/* virt. address of the frame buffer */
14762306a36Sopenharmony_ci	dma_addr_t	fb_phys;	/* phys. address of the frame buffer */
14862306a36Sopenharmony_ci	int		fb_alloced;	/* Flag, was the fb memory alloced? */
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	u8		flags;		/* features of the driver */
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	u32		reg_ctrl_default;
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	u32		pseudo_palette[PALETTE_ENTRIES_NO];
15562306a36Sopenharmony_ci					/* Fake palette of 16 colors */
15662306a36Sopenharmony_ci};
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci#define to_xilinxfb_drvdata(_info) \
15962306a36Sopenharmony_ci	container_of(_info, struct xilinxfb_drvdata, info)
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci/*
16262306a36Sopenharmony_ci * The XPS TFT Controller can be accessed through BUS or DCR interface.
16362306a36Sopenharmony_ci * To perform the read/write on the registers we need to check on
16462306a36Sopenharmony_ci * which bus its connected and call the appropriate write API.
16562306a36Sopenharmony_ci */
16662306a36Sopenharmony_cistatic void xilinx_fb_out32(struct xilinxfb_drvdata *drvdata, u32 offset,
16762306a36Sopenharmony_ci			    u32 val)
16862306a36Sopenharmony_ci{
16962306a36Sopenharmony_ci	if (drvdata->flags & BUS_ACCESS_FLAG) {
17062306a36Sopenharmony_ci		if (drvdata->flags & LITTLE_ENDIAN_ACCESS)
17162306a36Sopenharmony_ci			iowrite32(val, drvdata->regs + (offset << 2));
17262306a36Sopenharmony_ci		else
17362306a36Sopenharmony_ci			iowrite32be(val, drvdata->regs + (offset << 2));
17462306a36Sopenharmony_ci	}
17562306a36Sopenharmony_ci#ifdef CONFIG_PPC_DCR
17662306a36Sopenharmony_ci	else
17762306a36Sopenharmony_ci		dcr_write(drvdata->dcr_host, offset, val);
17862306a36Sopenharmony_ci#endif
17962306a36Sopenharmony_ci}
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_cistatic u32 xilinx_fb_in32(struct xilinxfb_drvdata *drvdata, u32 offset)
18262306a36Sopenharmony_ci{
18362306a36Sopenharmony_ci	if (drvdata->flags & BUS_ACCESS_FLAG) {
18462306a36Sopenharmony_ci		if (drvdata->flags & LITTLE_ENDIAN_ACCESS)
18562306a36Sopenharmony_ci			return ioread32(drvdata->regs + (offset << 2));
18662306a36Sopenharmony_ci		else
18762306a36Sopenharmony_ci			return ioread32be(drvdata->regs + (offset << 2));
18862306a36Sopenharmony_ci	}
18962306a36Sopenharmony_ci#ifdef CONFIG_PPC_DCR
19062306a36Sopenharmony_ci	else
19162306a36Sopenharmony_ci		return dcr_read(drvdata->dcr_host, offset);
19262306a36Sopenharmony_ci#endif
19362306a36Sopenharmony_ci	return 0;
19462306a36Sopenharmony_ci}
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_cistatic int
19762306a36Sopenharmony_cixilinx_fb_setcolreg(unsigned int regno, unsigned int red, unsigned int green,
19862306a36Sopenharmony_ci		    unsigned int blue, unsigned int transp, struct fb_info *fbi)
19962306a36Sopenharmony_ci{
20062306a36Sopenharmony_ci	u32 *palette = fbi->pseudo_palette;
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ci	if (regno >= PALETTE_ENTRIES_NO)
20362306a36Sopenharmony_ci		return -EINVAL;
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci	if (fbi->var.grayscale) {
20662306a36Sopenharmony_ci		/* Convert color to grayscale.
20762306a36Sopenharmony_ci		 * grayscale = 0.30*R + 0.59*G + 0.11*B
20862306a36Sopenharmony_ci		 */
20962306a36Sopenharmony_ci		blue = (red * 77 + green * 151 + blue * 28 + 127) >> 8;
21062306a36Sopenharmony_ci		green = blue;
21162306a36Sopenharmony_ci		red = green;
21262306a36Sopenharmony_ci	}
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci	/* fbi->fix.visual is always FB_VISUAL_TRUECOLOR */
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	/* We only handle 8 bits of each color. */
21762306a36Sopenharmony_ci	red >>= 8;
21862306a36Sopenharmony_ci	green >>= 8;
21962306a36Sopenharmony_ci	blue >>= 8;
22062306a36Sopenharmony_ci	palette[regno] = (red << RED_SHIFT) | (green << GREEN_SHIFT) |
22162306a36Sopenharmony_ci			 (blue << BLUE_SHIFT);
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci	return 0;
22462306a36Sopenharmony_ci}
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_cistatic int
22762306a36Sopenharmony_cixilinx_fb_blank(int blank_mode, struct fb_info *fbi)
22862306a36Sopenharmony_ci{
22962306a36Sopenharmony_ci	struct xilinxfb_drvdata *drvdata = to_xilinxfb_drvdata(fbi);
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ci	switch (blank_mode) {
23262306a36Sopenharmony_ci	case FB_BLANK_UNBLANK:
23362306a36Sopenharmony_ci		/* turn on panel */
23462306a36Sopenharmony_ci		xilinx_fb_out32(drvdata, REG_CTRL, drvdata->reg_ctrl_default);
23562306a36Sopenharmony_ci		break;
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_ci	case FB_BLANK_NORMAL:
23862306a36Sopenharmony_ci	case FB_BLANK_VSYNC_SUSPEND:
23962306a36Sopenharmony_ci	case FB_BLANK_HSYNC_SUSPEND:
24062306a36Sopenharmony_ci	case FB_BLANK_POWERDOWN:
24162306a36Sopenharmony_ci		/* turn off panel */
24262306a36Sopenharmony_ci		xilinx_fb_out32(drvdata, REG_CTRL, 0);
24362306a36Sopenharmony_ci		break;
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	default:
24662306a36Sopenharmony_ci		break;
24762306a36Sopenharmony_ci	}
24862306a36Sopenharmony_ci	return 0; /* success */
24962306a36Sopenharmony_ci}
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_cistatic const struct fb_ops xilinxfb_ops = {
25262306a36Sopenharmony_ci	.owner			= THIS_MODULE,
25362306a36Sopenharmony_ci	FB_DEFAULT_IOMEM_OPS,
25462306a36Sopenharmony_ci	.fb_setcolreg		= xilinx_fb_setcolreg,
25562306a36Sopenharmony_ci	.fb_blank		= xilinx_fb_blank,
25662306a36Sopenharmony_ci};
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_ci/* ---------------------------------------------------------------------
25962306a36Sopenharmony_ci * Bus independent setup/teardown
26062306a36Sopenharmony_ci */
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_cistatic int xilinxfb_assign(struct platform_device *pdev,
26362306a36Sopenharmony_ci			   struct xilinxfb_drvdata *drvdata,
26462306a36Sopenharmony_ci			   struct xilinxfb_platform_data *pdata)
26562306a36Sopenharmony_ci{
26662306a36Sopenharmony_ci	int rc;
26762306a36Sopenharmony_ci	struct device *dev = &pdev->dev;
26862306a36Sopenharmony_ci	int fbsize = pdata->xvirt * pdata->yvirt * BYTES_PER_PIXEL;
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ci	if (drvdata->flags & BUS_ACCESS_FLAG) {
27162306a36Sopenharmony_ci		struct resource *res;
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci		drvdata->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
27462306a36Sopenharmony_ci		if (IS_ERR(drvdata->regs))
27562306a36Sopenharmony_ci			return PTR_ERR(drvdata->regs);
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci		drvdata->regs_phys = res->start;
27862306a36Sopenharmony_ci	}
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci	/* Allocate the framebuffer memory */
28162306a36Sopenharmony_ci	if (pdata->fb_phys) {
28262306a36Sopenharmony_ci		drvdata->fb_phys = pdata->fb_phys;
28362306a36Sopenharmony_ci		drvdata->fb_virt = ioremap(pdata->fb_phys, fbsize);
28462306a36Sopenharmony_ci	} else {
28562306a36Sopenharmony_ci		drvdata->fb_alloced = 1;
28662306a36Sopenharmony_ci		drvdata->fb_virt = dma_alloc_coherent(dev, PAGE_ALIGN(fbsize),
28762306a36Sopenharmony_ci						      &drvdata->fb_phys,
28862306a36Sopenharmony_ci						      GFP_KERNEL);
28962306a36Sopenharmony_ci	}
29062306a36Sopenharmony_ci
29162306a36Sopenharmony_ci	if (!drvdata->fb_virt) {
29262306a36Sopenharmony_ci		dev_err(dev, "Could not allocate frame buffer memory\n");
29362306a36Sopenharmony_ci		return -ENOMEM;
29462306a36Sopenharmony_ci	}
29562306a36Sopenharmony_ci
29662306a36Sopenharmony_ci	/* Clear (turn to black) the framebuffer */
29762306a36Sopenharmony_ci	memset_io((void __iomem *)drvdata->fb_virt, 0, fbsize);
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ci	/* Tell the hardware where the frame buffer is */
30062306a36Sopenharmony_ci	xilinx_fb_out32(drvdata, REG_FB_ADDR, drvdata->fb_phys);
30162306a36Sopenharmony_ci	rc = xilinx_fb_in32(drvdata, REG_FB_ADDR);
30262306a36Sopenharmony_ci	/* Endianness detection */
30362306a36Sopenharmony_ci	if (rc != drvdata->fb_phys) {
30462306a36Sopenharmony_ci		drvdata->flags |= LITTLE_ENDIAN_ACCESS;
30562306a36Sopenharmony_ci		xilinx_fb_out32(drvdata, REG_FB_ADDR, drvdata->fb_phys);
30662306a36Sopenharmony_ci	}
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_ci	/* Turn on the display */
30962306a36Sopenharmony_ci	drvdata->reg_ctrl_default = REG_CTRL_ENABLE;
31062306a36Sopenharmony_ci	if (pdata->rotate_screen)
31162306a36Sopenharmony_ci		drvdata->reg_ctrl_default |= REG_CTRL_ROTATE;
31262306a36Sopenharmony_ci	xilinx_fb_out32(drvdata, REG_CTRL, drvdata->reg_ctrl_default);
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_ci	/* Fill struct fb_info */
31562306a36Sopenharmony_ci	drvdata->info.device = dev;
31662306a36Sopenharmony_ci	drvdata->info.screen_base = (void __iomem *)drvdata->fb_virt;
31762306a36Sopenharmony_ci	drvdata->info.fbops = &xilinxfb_ops;
31862306a36Sopenharmony_ci	drvdata->info.fix = xilinx_fb_fix;
31962306a36Sopenharmony_ci	drvdata->info.fix.smem_start = drvdata->fb_phys;
32062306a36Sopenharmony_ci	drvdata->info.fix.smem_len = fbsize;
32162306a36Sopenharmony_ci	drvdata->info.fix.line_length = pdata->xvirt * BYTES_PER_PIXEL;
32262306a36Sopenharmony_ci
32362306a36Sopenharmony_ci	drvdata->info.pseudo_palette = drvdata->pseudo_palette;
32462306a36Sopenharmony_ci	drvdata->info.var = xilinx_fb_var;
32562306a36Sopenharmony_ci	drvdata->info.var.height = pdata->screen_height_mm;
32662306a36Sopenharmony_ci	drvdata->info.var.width = pdata->screen_width_mm;
32762306a36Sopenharmony_ci	drvdata->info.var.xres = pdata->xres;
32862306a36Sopenharmony_ci	drvdata->info.var.yres = pdata->yres;
32962306a36Sopenharmony_ci	drvdata->info.var.xres_virtual = pdata->xvirt;
33062306a36Sopenharmony_ci	drvdata->info.var.yres_virtual = pdata->yvirt;
33162306a36Sopenharmony_ci
33262306a36Sopenharmony_ci	/* Allocate a colour map */
33362306a36Sopenharmony_ci	rc = fb_alloc_cmap(&drvdata->info.cmap, PALETTE_ENTRIES_NO, 0);
33462306a36Sopenharmony_ci	if (rc) {
33562306a36Sopenharmony_ci		dev_err(dev, "Fail to allocate colormap (%d entries)\n",
33662306a36Sopenharmony_ci			PALETTE_ENTRIES_NO);
33762306a36Sopenharmony_ci		goto err_cmap;
33862306a36Sopenharmony_ci	}
33962306a36Sopenharmony_ci
34062306a36Sopenharmony_ci	/* Register new frame buffer */
34162306a36Sopenharmony_ci	rc = register_framebuffer(&drvdata->info);
34262306a36Sopenharmony_ci	if (rc) {
34362306a36Sopenharmony_ci		dev_err(dev, "Could not register frame buffer\n");
34462306a36Sopenharmony_ci		goto err_regfb;
34562306a36Sopenharmony_ci	}
34662306a36Sopenharmony_ci
34762306a36Sopenharmony_ci	if (drvdata->flags & BUS_ACCESS_FLAG) {
34862306a36Sopenharmony_ci		/* Put a banner in the log (for DEBUG) */
34962306a36Sopenharmony_ci		dev_dbg(dev, "regs: phys=%pa, virt=%p\n",
35062306a36Sopenharmony_ci			&drvdata->regs_phys, drvdata->regs);
35162306a36Sopenharmony_ci	}
35262306a36Sopenharmony_ci	/* Put a banner in the log (for DEBUG) */
35362306a36Sopenharmony_ci	dev_dbg(dev, "fb: phys=%llx, virt=%p, size=%x\n",
35462306a36Sopenharmony_ci		(unsigned long long)drvdata->fb_phys, drvdata->fb_virt, fbsize);
35562306a36Sopenharmony_ci
35662306a36Sopenharmony_ci	return 0;	/* success */
35762306a36Sopenharmony_ci
35862306a36Sopenharmony_cierr_regfb:
35962306a36Sopenharmony_ci	fb_dealloc_cmap(&drvdata->info.cmap);
36062306a36Sopenharmony_ci
36162306a36Sopenharmony_cierr_cmap:
36262306a36Sopenharmony_ci	if (drvdata->fb_alloced)
36362306a36Sopenharmony_ci		dma_free_coherent(dev, PAGE_ALIGN(fbsize), drvdata->fb_virt,
36462306a36Sopenharmony_ci				  drvdata->fb_phys);
36562306a36Sopenharmony_ci	else
36662306a36Sopenharmony_ci		iounmap(drvdata->fb_virt);
36762306a36Sopenharmony_ci
36862306a36Sopenharmony_ci	/* Turn off the display */
36962306a36Sopenharmony_ci	xilinx_fb_out32(drvdata, REG_CTRL, 0);
37062306a36Sopenharmony_ci
37162306a36Sopenharmony_ci	return rc;
37262306a36Sopenharmony_ci}
37362306a36Sopenharmony_ci
37462306a36Sopenharmony_cistatic void xilinxfb_release(struct device *dev)
37562306a36Sopenharmony_ci{
37662306a36Sopenharmony_ci	struct xilinxfb_drvdata *drvdata = dev_get_drvdata(dev);
37762306a36Sopenharmony_ci
37862306a36Sopenharmony_ci#if !defined(CONFIG_FRAMEBUFFER_CONSOLE) && defined(CONFIG_LOGO)
37962306a36Sopenharmony_ci	xilinx_fb_blank(VESA_POWERDOWN, &drvdata->info);
38062306a36Sopenharmony_ci#endif
38162306a36Sopenharmony_ci
38262306a36Sopenharmony_ci	unregister_framebuffer(&drvdata->info);
38362306a36Sopenharmony_ci
38462306a36Sopenharmony_ci	fb_dealloc_cmap(&drvdata->info.cmap);
38562306a36Sopenharmony_ci
38662306a36Sopenharmony_ci	if (drvdata->fb_alloced)
38762306a36Sopenharmony_ci		dma_free_coherent(dev, PAGE_ALIGN(drvdata->info.fix.smem_len),
38862306a36Sopenharmony_ci				  drvdata->fb_virt, drvdata->fb_phys);
38962306a36Sopenharmony_ci	else
39062306a36Sopenharmony_ci		iounmap(drvdata->fb_virt);
39162306a36Sopenharmony_ci
39262306a36Sopenharmony_ci	/* Turn off the display */
39362306a36Sopenharmony_ci	xilinx_fb_out32(drvdata, REG_CTRL, 0);
39462306a36Sopenharmony_ci
39562306a36Sopenharmony_ci#ifdef CONFIG_PPC_DCR
39662306a36Sopenharmony_ci	/* Release the resources, as allocated based on interface */
39762306a36Sopenharmony_ci	if (!(drvdata->flags & BUS_ACCESS_FLAG))
39862306a36Sopenharmony_ci		dcr_unmap(drvdata->dcr_host, drvdata->dcr_len);
39962306a36Sopenharmony_ci#endif
40062306a36Sopenharmony_ci}
40162306a36Sopenharmony_ci
40262306a36Sopenharmony_ci/* ---------------------------------------------------------------------
40362306a36Sopenharmony_ci * OF bus binding
40462306a36Sopenharmony_ci */
40562306a36Sopenharmony_ci
40662306a36Sopenharmony_cistatic int xilinxfb_of_probe(struct platform_device *pdev)
40762306a36Sopenharmony_ci{
40862306a36Sopenharmony_ci	const u32 *prop;
40962306a36Sopenharmony_ci	u32 tft_access = 0;
41062306a36Sopenharmony_ci	struct xilinxfb_platform_data pdata;
41162306a36Sopenharmony_ci	int size;
41262306a36Sopenharmony_ci	struct xilinxfb_drvdata *drvdata;
41362306a36Sopenharmony_ci
41462306a36Sopenharmony_ci	/* Copy with the default pdata (not a ptr reference!) */
41562306a36Sopenharmony_ci	pdata = xilinx_fb_default_pdata;
41662306a36Sopenharmony_ci
41762306a36Sopenharmony_ci	/* Allocate the driver data region */
41862306a36Sopenharmony_ci	drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL);
41962306a36Sopenharmony_ci	if (!drvdata)
42062306a36Sopenharmony_ci		return -ENOMEM;
42162306a36Sopenharmony_ci
42262306a36Sopenharmony_ci	/*
42362306a36Sopenharmony_ci	 * To check whether the core is connected directly to DCR or BUS
42462306a36Sopenharmony_ci	 * interface and initialize the tft_access accordingly.
42562306a36Sopenharmony_ci	 */
42662306a36Sopenharmony_ci	of_property_read_u32(pdev->dev.of_node, "xlnx,dcr-splb-slave-if",
42762306a36Sopenharmony_ci			     &tft_access);
42862306a36Sopenharmony_ci
42962306a36Sopenharmony_ci	/*
43062306a36Sopenharmony_ci	 * Fill the resource structure if its direct BUS interface
43162306a36Sopenharmony_ci	 * otherwise fill the dcr_host structure.
43262306a36Sopenharmony_ci	 */
43362306a36Sopenharmony_ci	if (tft_access)
43462306a36Sopenharmony_ci		drvdata->flags |= BUS_ACCESS_FLAG;
43562306a36Sopenharmony_ci#ifdef CONFIG_PPC_DCR
43662306a36Sopenharmony_ci	else {
43762306a36Sopenharmony_ci		int start;
43862306a36Sopenharmony_ci
43962306a36Sopenharmony_ci		start = dcr_resource_start(pdev->dev.of_node, 0);
44062306a36Sopenharmony_ci		drvdata->dcr_len = dcr_resource_len(pdev->dev.of_node, 0);
44162306a36Sopenharmony_ci		drvdata->dcr_host = dcr_map(pdev->dev.of_node, start, drvdata->dcr_len);
44262306a36Sopenharmony_ci		if (!DCR_MAP_OK(drvdata->dcr_host)) {
44362306a36Sopenharmony_ci			dev_err(&pdev->dev, "invalid DCR address\n");
44462306a36Sopenharmony_ci			return -ENODEV;
44562306a36Sopenharmony_ci		}
44662306a36Sopenharmony_ci	}
44762306a36Sopenharmony_ci#endif
44862306a36Sopenharmony_ci
44962306a36Sopenharmony_ci	prop = of_get_property(pdev->dev.of_node, "phys-size", &size);
45062306a36Sopenharmony_ci	if ((prop) && (size >= sizeof(u32) * 2)) {
45162306a36Sopenharmony_ci		pdata.screen_width_mm = prop[0];
45262306a36Sopenharmony_ci		pdata.screen_height_mm = prop[1];
45362306a36Sopenharmony_ci	}
45462306a36Sopenharmony_ci
45562306a36Sopenharmony_ci	prop = of_get_property(pdev->dev.of_node, "resolution", &size);
45662306a36Sopenharmony_ci	if ((prop) && (size >= sizeof(u32) * 2)) {
45762306a36Sopenharmony_ci		pdata.xres = prop[0];
45862306a36Sopenharmony_ci		pdata.yres = prop[1];
45962306a36Sopenharmony_ci	}
46062306a36Sopenharmony_ci
46162306a36Sopenharmony_ci	prop = of_get_property(pdev->dev.of_node, "virtual-resolution", &size);
46262306a36Sopenharmony_ci	if ((prop) && (size >= sizeof(u32) * 2)) {
46362306a36Sopenharmony_ci		pdata.xvirt = prop[0];
46462306a36Sopenharmony_ci		pdata.yvirt = prop[1];
46562306a36Sopenharmony_ci	}
46662306a36Sopenharmony_ci
46762306a36Sopenharmony_ci	pdata.rotate_screen = of_property_read_bool(pdev->dev.of_node, "rotate-display");
46862306a36Sopenharmony_ci
46962306a36Sopenharmony_ci	platform_set_drvdata(pdev, drvdata);
47062306a36Sopenharmony_ci	return xilinxfb_assign(pdev, drvdata, &pdata);
47162306a36Sopenharmony_ci}
47262306a36Sopenharmony_ci
47362306a36Sopenharmony_cistatic void xilinxfb_of_remove(struct platform_device *op)
47462306a36Sopenharmony_ci{
47562306a36Sopenharmony_ci	xilinxfb_release(&op->dev);
47662306a36Sopenharmony_ci}
47762306a36Sopenharmony_ci
47862306a36Sopenharmony_ci/* Match table for of_platform binding */
47962306a36Sopenharmony_cistatic const struct of_device_id xilinxfb_of_match[] = {
48062306a36Sopenharmony_ci	{ .compatible = "xlnx,xps-tft-1.00.a", },
48162306a36Sopenharmony_ci	{ .compatible = "xlnx,xps-tft-2.00.a", },
48262306a36Sopenharmony_ci	{ .compatible = "xlnx,xps-tft-2.01.a", },
48362306a36Sopenharmony_ci	{ .compatible = "xlnx,plb-tft-cntlr-ref-1.00.a", },
48462306a36Sopenharmony_ci	{ .compatible = "xlnx,plb-dvi-cntlr-ref-1.00.c", },
48562306a36Sopenharmony_ci	{},
48662306a36Sopenharmony_ci};
48762306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, xilinxfb_of_match);
48862306a36Sopenharmony_ci
48962306a36Sopenharmony_cistatic struct platform_driver xilinxfb_of_driver = {
49062306a36Sopenharmony_ci	.probe = xilinxfb_of_probe,
49162306a36Sopenharmony_ci	.remove_new = xilinxfb_of_remove,
49262306a36Sopenharmony_ci	.driver = {
49362306a36Sopenharmony_ci		.name = DRIVER_NAME,
49462306a36Sopenharmony_ci		.of_match_table = xilinxfb_of_match,
49562306a36Sopenharmony_ci	},
49662306a36Sopenharmony_ci};
49762306a36Sopenharmony_ci
49862306a36Sopenharmony_cimodule_platform_driver(xilinxfb_of_driver);
49962306a36Sopenharmony_ci
50062306a36Sopenharmony_ciMODULE_AUTHOR("MontaVista Software, Inc. <source@mvista.com>");
50162306a36Sopenharmony_ciMODULE_DESCRIPTION("Xilinx TFT frame buffer driver");
50262306a36Sopenharmony_ciMODULE_LICENSE("GPL");
503