162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * linux/drivers/video/ep93xx-fb.c
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Framebuffer support for the EP93xx series.
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Copyright (C) 2007 Bluewater Systems Ltd
862306a36Sopenharmony_ci * Author: Ryan Mallon
962306a36Sopenharmony_ci *
1062306a36Sopenharmony_ci * Copyright (c) 2009 H Hartley Sweeten <hsweeten@visionengravers.com>
1162306a36Sopenharmony_ci *
1262306a36Sopenharmony_ci * Based on the Cirrus Logic ep93xxfb driver, and various other ep93xxfb
1362306a36Sopenharmony_ci * drivers.
1462306a36Sopenharmony_ci */
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#include <linux/platform_device.h>
1762306a36Sopenharmony_ci#include <linux/module.h>
1862306a36Sopenharmony_ci#include <linux/dma-mapping.h>
1962306a36Sopenharmony_ci#include <linux/slab.h>
2062306a36Sopenharmony_ci#include <linux/clk.h>
2162306a36Sopenharmony_ci#include <linux/fb.h>
2262306a36Sopenharmony_ci#include <linux/io.h>
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci#include <linux/platform_data/video-ep93xx.h>
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci/* Vertical Frame Timing Registers */
2762306a36Sopenharmony_ci#define EP93XXFB_VLINES_TOTAL			0x0000	/* SW locked */
2862306a36Sopenharmony_ci#define EP93XXFB_VSYNC				0x0004	/* SW locked */
2962306a36Sopenharmony_ci#define EP93XXFB_VACTIVE			0x0008	/* SW locked */
3062306a36Sopenharmony_ci#define EP93XXFB_VBLANK				0x0228	/* SW locked */
3162306a36Sopenharmony_ci#define EP93XXFB_VCLK				0x000c	/* SW locked */
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci/* Horizontal Frame Timing Registers */
3462306a36Sopenharmony_ci#define EP93XXFB_HCLKS_TOTAL			0x0010	/* SW locked */
3562306a36Sopenharmony_ci#define EP93XXFB_HSYNC				0x0014	/* SW locked */
3662306a36Sopenharmony_ci#define EP93XXFB_HACTIVE			0x0018	/* SW locked */
3762306a36Sopenharmony_ci#define EP93XXFB_HBLANK				0x022c	/* SW locked */
3862306a36Sopenharmony_ci#define EP93XXFB_HCLK				0x001c	/* SW locked */
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci/* Frame Buffer Memory Configuration Registers */
4162306a36Sopenharmony_ci#define EP93XXFB_SCREEN_PAGE			0x0028
4262306a36Sopenharmony_ci#define EP93XXFB_SCREEN_HPAGE			0x002c
4362306a36Sopenharmony_ci#define EP93XXFB_SCREEN_LINES			0x0030
4462306a36Sopenharmony_ci#define EP93XXFB_LINE_LENGTH			0x0034
4562306a36Sopenharmony_ci#define EP93XXFB_VLINE_STEP			0x0038
4662306a36Sopenharmony_ci#define EP93XXFB_LINE_CARRY			0x003c	/* SW locked */
4762306a36Sopenharmony_ci#define EP93XXFB_EOL_OFFSET			0x0230
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci/* Other Video Registers */
5062306a36Sopenharmony_ci#define EP93XXFB_BRIGHTNESS			0x0020
5162306a36Sopenharmony_ci#define EP93XXFB_ATTRIBS			0x0024	/* SW locked */
5262306a36Sopenharmony_ci#define EP93XXFB_SWLOCK				0x007c	/* SW locked */
5362306a36Sopenharmony_ci#define EP93XXFB_AC_RATE			0x0214
5462306a36Sopenharmony_ci#define EP93XXFB_FIFO_LEVEL			0x0234
5562306a36Sopenharmony_ci#define EP93XXFB_PIXELMODE			0x0054
5662306a36Sopenharmony_ci#define EP93XXFB_PIXELMODE_32BPP		(0x7 << 0)
5762306a36Sopenharmony_ci#define EP93XXFB_PIXELMODE_24BPP		(0x6 << 0)
5862306a36Sopenharmony_ci#define EP93XXFB_PIXELMODE_16BPP		(0x4 << 0)
5962306a36Sopenharmony_ci#define EP93XXFB_PIXELMODE_8BPP			(0x2 << 0)
6062306a36Sopenharmony_ci#define EP93XXFB_PIXELMODE_SHIFT_1P_24B		(0x0 << 3)
6162306a36Sopenharmony_ci#define EP93XXFB_PIXELMODE_SHIFT_1P_18B		(0x1 << 3)
6262306a36Sopenharmony_ci#define EP93XXFB_PIXELMODE_COLOR_LUT		(0x0 << 10)
6362306a36Sopenharmony_ci#define EP93XXFB_PIXELMODE_COLOR_888		(0x4 << 10)
6462306a36Sopenharmony_ci#define EP93XXFB_PIXELMODE_COLOR_555		(0x5 << 10)
6562306a36Sopenharmony_ci#define EP93XXFB_PARL_IF_OUT			0x0058
6662306a36Sopenharmony_ci#define EP93XXFB_PARL_IF_IN			0x005c
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci/* Blink Control Registers */
6962306a36Sopenharmony_ci#define EP93XXFB_BLINK_RATE			0x0040
7062306a36Sopenharmony_ci#define EP93XXFB_BLINK_MASK			0x0044
7162306a36Sopenharmony_ci#define EP93XXFB_BLINK_PATTRN			0x0048
7262306a36Sopenharmony_ci#define EP93XXFB_PATTRN_MASK			0x004c
7362306a36Sopenharmony_ci#define EP93XXFB_BKGRND_OFFSET			0x0050
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci/* Hardware Cursor Registers */
7662306a36Sopenharmony_ci#define EP93XXFB_CURSOR_ADR_START		0x0060
7762306a36Sopenharmony_ci#define EP93XXFB_CURSOR_ADR_RESET		0x0064
7862306a36Sopenharmony_ci#define EP93XXFB_CURSOR_SIZE			0x0068
7962306a36Sopenharmony_ci#define EP93XXFB_CURSOR_COLOR1			0x006c
8062306a36Sopenharmony_ci#define EP93XXFB_CURSOR_COLOR2			0x0070
8162306a36Sopenharmony_ci#define EP93XXFB_CURSOR_BLINK_COLOR1		0x021c
8262306a36Sopenharmony_ci#define EP93XXFB_CURSOR_BLINK_COLOR2		0x0220
8362306a36Sopenharmony_ci#define EP93XXFB_CURSOR_XY_LOC			0x0074
8462306a36Sopenharmony_ci#define EP93XXFB_CURSOR_DSCAN_HY_LOC		0x0078
8562306a36Sopenharmony_ci#define EP93XXFB_CURSOR_BLINK_RATE_CTRL		0x0224
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci/* LUT Registers */
8862306a36Sopenharmony_ci#define EP93XXFB_GRY_SCL_LUTR			0x0080
8962306a36Sopenharmony_ci#define EP93XXFB_GRY_SCL_LUTG			0x0280
9062306a36Sopenharmony_ci#define EP93XXFB_GRY_SCL_LUTB			0x0300
9162306a36Sopenharmony_ci#define EP93XXFB_LUT_SW_CONTROL			0x0218
9262306a36Sopenharmony_ci#define EP93XXFB_LUT_SW_CONTROL_SWTCH		(1 << 0)
9362306a36Sopenharmony_ci#define EP93XXFB_LUT_SW_CONTROL_SSTAT		(1 << 1)
9462306a36Sopenharmony_ci#define EP93XXFB_COLOR_LUT			0x0400
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci/* Video Signature Registers */
9762306a36Sopenharmony_ci#define EP93XXFB_VID_SIG_RSLT_VAL		0x0200
9862306a36Sopenharmony_ci#define EP93XXFB_VID_SIG_CTRL			0x0204
9962306a36Sopenharmony_ci#define EP93XXFB_VSIG				0x0208
10062306a36Sopenharmony_ci#define EP93XXFB_HSIG				0x020c
10162306a36Sopenharmony_ci#define EP93XXFB_SIG_CLR_STR			0x0210
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci/* Minimum / Maximum resolutions supported */
10462306a36Sopenharmony_ci#define EP93XXFB_MIN_XRES			64
10562306a36Sopenharmony_ci#define EP93XXFB_MIN_YRES			64
10662306a36Sopenharmony_ci#define EP93XXFB_MAX_XRES			1024
10762306a36Sopenharmony_ci#define EP93XXFB_MAX_YRES			768
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_cistruct ep93xx_fbi {
11062306a36Sopenharmony_ci	struct ep93xxfb_mach_info	*mach_info;
11162306a36Sopenharmony_ci	struct clk			*clk;
11262306a36Sopenharmony_ci	struct resource			*res;
11362306a36Sopenharmony_ci	void __iomem			*mmio_base;
11462306a36Sopenharmony_ci	unsigned int			pseudo_palette[256];
11562306a36Sopenharmony_ci};
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_cistatic int check_screenpage_bug = 1;
11862306a36Sopenharmony_cimodule_param(check_screenpage_bug, int, 0644);
11962306a36Sopenharmony_ciMODULE_PARM_DESC(check_screenpage_bug,
12062306a36Sopenharmony_ci		 "Check for bit 27 screen page bug. Default = 1");
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_cistatic inline unsigned int ep93xxfb_readl(struct ep93xx_fbi *fbi,
12362306a36Sopenharmony_ci					  unsigned int off)
12462306a36Sopenharmony_ci{
12562306a36Sopenharmony_ci	return __raw_readl(fbi->mmio_base + off);
12662306a36Sopenharmony_ci}
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_cistatic inline void ep93xxfb_writel(struct ep93xx_fbi *fbi,
12962306a36Sopenharmony_ci				   unsigned int val, unsigned int off)
13062306a36Sopenharmony_ci{
13162306a36Sopenharmony_ci	__raw_writel(val, fbi->mmio_base + off);
13262306a36Sopenharmony_ci}
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci/*
13562306a36Sopenharmony_ci * Write to one of the locked raster registers.
13662306a36Sopenharmony_ci */
13762306a36Sopenharmony_cistatic inline void ep93xxfb_out_locked(struct ep93xx_fbi *fbi,
13862306a36Sopenharmony_ci				       unsigned int val, unsigned int reg)
13962306a36Sopenharmony_ci{
14062306a36Sopenharmony_ci	/*
14162306a36Sopenharmony_ci	 * We don't need a lock or delay here since the raster register
14262306a36Sopenharmony_ci	 * block will remain unlocked until the next access.
14362306a36Sopenharmony_ci	 */
14462306a36Sopenharmony_ci	ep93xxfb_writel(fbi, 0xaa, EP93XXFB_SWLOCK);
14562306a36Sopenharmony_ci	ep93xxfb_writel(fbi, val, reg);
14662306a36Sopenharmony_ci}
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_cistatic void ep93xxfb_set_video_attribs(struct fb_info *info)
14962306a36Sopenharmony_ci{
15062306a36Sopenharmony_ci	struct ep93xx_fbi *fbi = info->par;
15162306a36Sopenharmony_ci	unsigned int attribs;
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci	attribs = EP93XXFB_ENABLE;
15462306a36Sopenharmony_ci	attribs |= fbi->mach_info->flags;
15562306a36Sopenharmony_ci	ep93xxfb_out_locked(fbi, attribs, EP93XXFB_ATTRIBS);
15662306a36Sopenharmony_ci}
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_cistatic int ep93xxfb_set_pixelmode(struct fb_info *info)
15962306a36Sopenharmony_ci{
16062306a36Sopenharmony_ci	struct ep93xx_fbi *fbi = info->par;
16162306a36Sopenharmony_ci	unsigned int val;
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci	info->var.transp.offset = 0;
16462306a36Sopenharmony_ci	info->var.transp.length = 0;
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	switch (info->var.bits_per_pixel) {
16762306a36Sopenharmony_ci	case 8:
16862306a36Sopenharmony_ci		val = EP93XXFB_PIXELMODE_8BPP | EP93XXFB_PIXELMODE_COLOR_LUT |
16962306a36Sopenharmony_ci			EP93XXFB_PIXELMODE_SHIFT_1P_18B;
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci		info->var.red.offset	= 0;
17262306a36Sopenharmony_ci		info->var.red.length	= 8;
17362306a36Sopenharmony_ci		info->var.green.offset	= 0;
17462306a36Sopenharmony_ci		info->var.green.length	= 8;
17562306a36Sopenharmony_ci		info->var.blue.offset	= 0;
17662306a36Sopenharmony_ci		info->var.blue.length	= 8;
17762306a36Sopenharmony_ci		info->fix.visual 	= FB_VISUAL_PSEUDOCOLOR;
17862306a36Sopenharmony_ci		break;
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	case 16:
18162306a36Sopenharmony_ci		val = EP93XXFB_PIXELMODE_16BPP | EP93XXFB_PIXELMODE_COLOR_555 |
18262306a36Sopenharmony_ci			EP93XXFB_PIXELMODE_SHIFT_1P_18B;
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci		info->var.red.offset	= 11;
18562306a36Sopenharmony_ci		info->var.red.length	= 5;
18662306a36Sopenharmony_ci		info->var.green.offset	= 5;
18762306a36Sopenharmony_ci		info->var.green.length	= 6;
18862306a36Sopenharmony_ci		info->var.blue.offset	= 0;
18962306a36Sopenharmony_ci		info->var.blue.length	= 5;
19062306a36Sopenharmony_ci		info->fix.visual 	= FB_VISUAL_TRUECOLOR;
19162306a36Sopenharmony_ci		break;
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	case 24:
19462306a36Sopenharmony_ci		val = EP93XXFB_PIXELMODE_24BPP | EP93XXFB_PIXELMODE_COLOR_888 |
19562306a36Sopenharmony_ci			EP93XXFB_PIXELMODE_SHIFT_1P_24B;
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci		info->var.red.offset	= 16;
19862306a36Sopenharmony_ci		info->var.red.length	= 8;
19962306a36Sopenharmony_ci		info->var.green.offset	= 8;
20062306a36Sopenharmony_ci		info->var.green.length	= 8;
20162306a36Sopenharmony_ci		info->var.blue.offset	= 0;
20262306a36Sopenharmony_ci		info->var.blue.length	= 8;
20362306a36Sopenharmony_ci		info->fix.visual 	= FB_VISUAL_TRUECOLOR;
20462306a36Sopenharmony_ci		break;
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	case 32:
20762306a36Sopenharmony_ci		val = EP93XXFB_PIXELMODE_32BPP | EP93XXFB_PIXELMODE_COLOR_888 |
20862306a36Sopenharmony_ci			EP93XXFB_PIXELMODE_SHIFT_1P_24B;
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci		info->var.red.offset	= 16;
21162306a36Sopenharmony_ci		info->var.red.length	= 8;
21262306a36Sopenharmony_ci		info->var.green.offset	= 8;
21362306a36Sopenharmony_ci		info->var.green.length	= 8;
21462306a36Sopenharmony_ci		info->var.blue.offset	= 0;
21562306a36Sopenharmony_ci		info->var.blue.length	= 8;
21662306a36Sopenharmony_ci		info->fix.visual 	= FB_VISUAL_TRUECOLOR;
21762306a36Sopenharmony_ci		break;
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci	default:
22062306a36Sopenharmony_ci		return -EINVAL;
22162306a36Sopenharmony_ci	}
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci	ep93xxfb_writel(fbi, val, EP93XXFB_PIXELMODE);
22462306a36Sopenharmony_ci	return 0;
22562306a36Sopenharmony_ci}
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_cistatic void ep93xxfb_set_timing(struct fb_info *info)
22862306a36Sopenharmony_ci{
22962306a36Sopenharmony_ci	struct ep93xx_fbi *fbi = info->par;
23062306a36Sopenharmony_ci	unsigned int vlines_total, hclks_total, start, stop;
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci	vlines_total = info->var.yres + info->var.upper_margin +
23362306a36Sopenharmony_ci		info->var.lower_margin + info->var.vsync_len - 1;
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci	hclks_total = info->var.xres + info->var.left_margin +
23662306a36Sopenharmony_ci		info->var.right_margin + info->var.hsync_len - 1;
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci	ep93xxfb_out_locked(fbi, vlines_total, EP93XXFB_VLINES_TOTAL);
23962306a36Sopenharmony_ci	ep93xxfb_out_locked(fbi, hclks_total, EP93XXFB_HCLKS_TOTAL);
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ci	start = vlines_total;
24262306a36Sopenharmony_ci	stop = vlines_total - info->var.vsync_len;
24362306a36Sopenharmony_ci	ep93xxfb_out_locked(fbi, start | (stop << 16), EP93XXFB_VSYNC);
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	start = vlines_total - info->var.vsync_len - info->var.upper_margin;
24662306a36Sopenharmony_ci	stop = info->var.lower_margin - 1;
24762306a36Sopenharmony_ci	ep93xxfb_out_locked(fbi, start | (stop << 16), EP93XXFB_VBLANK);
24862306a36Sopenharmony_ci	ep93xxfb_out_locked(fbi, start | (stop << 16), EP93XXFB_VACTIVE);
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ci	start = vlines_total;
25162306a36Sopenharmony_ci	stop = vlines_total + 1;
25262306a36Sopenharmony_ci	ep93xxfb_out_locked(fbi, start | (stop << 16), EP93XXFB_VCLK);
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_ci	start = hclks_total;
25562306a36Sopenharmony_ci	stop = hclks_total - info->var.hsync_len;
25662306a36Sopenharmony_ci	ep93xxfb_out_locked(fbi, start | (stop << 16), EP93XXFB_HSYNC);
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_ci	start = hclks_total - info->var.hsync_len - info->var.left_margin;
25962306a36Sopenharmony_ci	stop = info->var.right_margin - 1;
26062306a36Sopenharmony_ci	ep93xxfb_out_locked(fbi, start | (stop << 16), EP93XXFB_HBLANK);
26162306a36Sopenharmony_ci	ep93xxfb_out_locked(fbi, start | (stop << 16), EP93XXFB_HACTIVE);
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_ci	start = hclks_total;
26462306a36Sopenharmony_ci	stop = hclks_total;
26562306a36Sopenharmony_ci	ep93xxfb_out_locked(fbi, start | (stop << 16), EP93XXFB_HCLK);
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_ci	ep93xxfb_out_locked(fbi, 0x0, EP93XXFB_LINE_CARRY);
26862306a36Sopenharmony_ci}
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_cistatic int ep93xxfb_set_par(struct fb_info *info)
27162306a36Sopenharmony_ci{
27262306a36Sopenharmony_ci	struct ep93xx_fbi *fbi = info->par;
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_ci	clk_set_rate(fbi->clk, 1000 * PICOS2KHZ(info->var.pixclock));
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_ci	ep93xxfb_set_timing(info);
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_ci	info->fix.line_length = info->var.xres_virtual *
27962306a36Sopenharmony_ci		info->var.bits_per_pixel / 8;
28062306a36Sopenharmony_ci
28162306a36Sopenharmony_ci	ep93xxfb_writel(fbi, info->fix.smem_start, EP93XXFB_SCREEN_PAGE);
28262306a36Sopenharmony_ci	ep93xxfb_writel(fbi, info->var.yres - 1, EP93XXFB_SCREEN_LINES);
28362306a36Sopenharmony_ci	ep93xxfb_writel(fbi, ((info->var.xres * info->var.bits_per_pixel)
28462306a36Sopenharmony_ci			      / 32) - 1, EP93XXFB_LINE_LENGTH);
28562306a36Sopenharmony_ci	ep93xxfb_writel(fbi, info->fix.line_length / 4, EP93XXFB_VLINE_STEP);
28662306a36Sopenharmony_ci	ep93xxfb_set_video_attribs(info);
28762306a36Sopenharmony_ci	return 0;
28862306a36Sopenharmony_ci}
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_cistatic int ep93xxfb_check_var(struct fb_var_screeninfo *var,
29162306a36Sopenharmony_ci			      struct fb_info *info)
29262306a36Sopenharmony_ci{
29362306a36Sopenharmony_ci	int err;
29462306a36Sopenharmony_ci
29562306a36Sopenharmony_ci	err = ep93xxfb_set_pixelmode(info);
29662306a36Sopenharmony_ci	if (err)
29762306a36Sopenharmony_ci		return err;
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ci	var->xres = max_t(unsigned int, var->xres, EP93XXFB_MIN_XRES);
30062306a36Sopenharmony_ci	var->xres = min_t(unsigned int, var->xres, EP93XXFB_MAX_XRES);
30162306a36Sopenharmony_ci	var->xres_virtual = max(var->xres_virtual, var->xres);
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_ci	var->yres = max_t(unsigned int, var->yres, EP93XXFB_MIN_YRES);
30462306a36Sopenharmony_ci	var->yres = min_t(unsigned int, var->yres, EP93XXFB_MAX_YRES);
30562306a36Sopenharmony_ci	var->yres_virtual = max(var->yres_virtual, var->yres);
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_ci	return 0;
30862306a36Sopenharmony_ci}
30962306a36Sopenharmony_ci
31062306a36Sopenharmony_cistatic int ep93xxfb_mmap(struct fb_info *info, struct vm_area_struct *vma)
31162306a36Sopenharmony_ci{
31262306a36Sopenharmony_ci	unsigned int offset = vma->vm_pgoff << PAGE_SHIFT;
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_ci	if (offset < info->fix.smem_len) {
31562306a36Sopenharmony_ci		return dma_mmap_wc(info->device, vma, info->screen_base,
31662306a36Sopenharmony_ci				   info->fix.smem_start, info->fix.smem_len);
31762306a36Sopenharmony_ci	}
31862306a36Sopenharmony_ci
31962306a36Sopenharmony_ci	return -EINVAL;
32062306a36Sopenharmony_ci}
32162306a36Sopenharmony_ci
32262306a36Sopenharmony_cistatic int ep93xxfb_blank(int blank_mode, struct fb_info *info)
32362306a36Sopenharmony_ci{
32462306a36Sopenharmony_ci	struct ep93xx_fbi *fbi = info->par;
32562306a36Sopenharmony_ci	unsigned int attribs = ep93xxfb_readl(fbi, EP93XXFB_ATTRIBS);
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_ci	if (blank_mode) {
32862306a36Sopenharmony_ci		if (fbi->mach_info->blank)
32962306a36Sopenharmony_ci			fbi->mach_info->blank(blank_mode, info);
33062306a36Sopenharmony_ci		ep93xxfb_out_locked(fbi, attribs & ~EP93XXFB_ENABLE,
33162306a36Sopenharmony_ci				    EP93XXFB_ATTRIBS);
33262306a36Sopenharmony_ci		clk_disable(fbi->clk);
33362306a36Sopenharmony_ci	} else {
33462306a36Sopenharmony_ci		clk_enable(fbi->clk);
33562306a36Sopenharmony_ci		ep93xxfb_out_locked(fbi, attribs | EP93XXFB_ENABLE,
33662306a36Sopenharmony_ci				    EP93XXFB_ATTRIBS);
33762306a36Sopenharmony_ci		if (fbi->mach_info->blank)
33862306a36Sopenharmony_ci			fbi->mach_info->blank(blank_mode, info);
33962306a36Sopenharmony_ci	}
34062306a36Sopenharmony_ci
34162306a36Sopenharmony_ci	return 0;
34262306a36Sopenharmony_ci}
34362306a36Sopenharmony_ci
34462306a36Sopenharmony_cistatic inline int ep93xxfb_convert_color(int val, int width)
34562306a36Sopenharmony_ci{
34662306a36Sopenharmony_ci	return ((val << width) + 0x7fff - val) >> 16;
34762306a36Sopenharmony_ci}
34862306a36Sopenharmony_ci
34962306a36Sopenharmony_cistatic int ep93xxfb_setcolreg(unsigned int regno, unsigned int red,
35062306a36Sopenharmony_ci			      unsigned int green, unsigned int blue,
35162306a36Sopenharmony_ci			      unsigned int transp, struct fb_info *info)
35262306a36Sopenharmony_ci{
35362306a36Sopenharmony_ci	struct ep93xx_fbi *fbi = info->par;
35462306a36Sopenharmony_ci	unsigned int *pal = info->pseudo_palette;
35562306a36Sopenharmony_ci	unsigned int ctrl, i, rgb, lut_current, lut_stat;
35662306a36Sopenharmony_ci
35762306a36Sopenharmony_ci	switch (info->fix.visual) {
35862306a36Sopenharmony_ci	case FB_VISUAL_PSEUDOCOLOR:
35962306a36Sopenharmony_ci		if (regno > 255)
36062306a36Sopenharmony_ci			return 1;
36162306a36Sopenharmony_ci		rgb = ((red & 0xff00) << 8) | (green & 0xff00) |
36262306a36Sopenharmony_ci			((blue & 0xff00) >> 8);
36362306a36Sopenharmony_ci
36462306a36Sopenharmony_ci		pal[regno] = rgb;
36562306a36Sopenharmony_ci		ep93xxfb_writel(fbi, rgb, (EP93XXFB_COLOR_LUT + (regno << 2)));
36662306a36Sopenharmony_ci		ctrl = ep93xxfb_readl(fbi, EP93XXFB_LUT_SW_CONTROL);
36762306a36Sopenharmony_ci		lut_stat = !!(ctrl & EP93XXFB_LUT_SW_CONTROL_SSTAT);
36862306a36Sopenharmony_ci		lut_current = !!(ctrl & EP93XXFB_LUT_SW_CONTROL_SWTCH);
36962306a36Sopenharmony_ci
37062306a36Sopenharmony_ci		if (lut_stat == lut_current) {
37162306a36Sopenharmony_ci			for (i = 0; i < 256; i++) {
37262306a36Sopenharmony_ci				ep93xxfb_writel(fbi, pal[i],
37362306a36Sopenharmony_ci					EP93XXFB_COLOR_LUT + (i << 2));
37462306a36Sopenharmony_ci			}
37562306a36Sopenharmony_ci
37662306a36Sopenharmony_ci			ep93xxfb_writel(fbi,
37762306a36Sopenharmony_ci					ctrl ^ EP93XXFB_LUT_SW_CONTROL_SWTCH,
37862306a36Sopenharmony_ci					EP93XXFB_LUT_SW_CONTROL);
37962306a36Sopenharmony_ci		}
38062306a36Sopenharmony_ci		break;
38162306a36Sopenharmony_ci
38262306a36Sopenharmony_ci	case FB_VISUAL_TRUECOLOR:
38362306a36Sopenharmony_ci		if (regno > 16)
38462306a36Sopenharmony_ci			return 1;
38562306a36Sopenharmony_ci
38662306a36Sopenharmony_ci		red = ep93xxfb_convert_color(red, info->var.red.length);
38762306a36Sopenharmony_ci		green = ep93xxfb_convert_color(green, info->var.green.length);
38862306a36Sopenharmony_ci		blue = ep93xxfb_convert_color(blue, info->var.blue.length);
38962306a36Sopenharmony_ci		transp = ep93xxfb_convert_color(transp,
39062306a36Sopenharmony_ci						info->var.transp.length);
39162306a36Sopenharmony_ci
39262306a36Sopenharmony_ci		pal[regno] = (red << info->var.red.offset) |
39362306a36Sopenharmony_ci			(green << info->var.green.offset) |
39462306a36Sopenharmony_ci			(blue << info->var.blue.offset) |
39562306a36Sopenharmony_ci			(transp << info->var.transp.offset);
39662306a36Sopenharmony_ci		break;
39762306a36Sopenharmony_ci
39862306a36Sopenharmony_ci	default:
39962306a36Sopenharmony_ci		return 1;
40062306a36Sopenharmony_ci	}
40162306a36Sopenharmony_ci
40262306a36Sopenharmony_ci	return 0;
40362306a36Sopenharmony_ci}
40462306a36Sopenharmony_ci
40562306a36Sopenharmony_cistatic const struct fb_ops ep93xxfb_ops = {
40662306a36Sopenharmony_ci	.owner		= THIS_MODULE,
40762306a36Sopenharmony_ci	.fb_check_var	= ep93xxfb_check_var,
40862306a36Sopenharmony_ci	.fb_set_par	= ep93xxfb_set_par,
40962306a36Sopenharmony_ci	.fb_blank	= ep93xxfb_blank,
41062306a36Sopenharmony_ci	.fb_fillrect	= cfb_fillrect,
41162306a36Sopenharmony_ci	.fb_copyarea	= cfb_copyarea,
41262306a36Sopenharmony_ci	.fb_imageblit	= cfb_imageblit,
41362306a36Sopenharmony_ci	.fb_setcolreg	= ep93xxfb_setcolreg,
41462306a36Sopenharmony_ci	.fb_mmap	= ep93xxfb_mmap,
41562306a36Sopenharmony_ci};
41662306a36Sopenharmony_ci
41762306a36Sopenharmony_cistatic int ep93xxfb_alloc_videomem(struct fb_info *info)
41862306a36Sopenharmony_ci{
41962306a36Sopenharmony_ci	char __iomem *virt_addr;
42062306a36Sopenharmony_ci	dma_addr_t phys_addr;
42162306a36Sopenharmony_ci	unsigned int fb_size;
42262306a36Sopenharmony_ci
42362306a36Sopenharmony_ci	/* Maximum 16bpp -> used memory is maximum x*y*2 bytes */
42462306a36Sopenharmony_ci	fb_size = EP93XXFB_MAX_XRES * EP93XXFB_MAX_YRES * 2;
42562306a36Sopenharmony_ci
42662306a36Sopenharmony_ci	virt_addr = dma_alloc_wc(info->device, fb_size, &phys_addr, GFP_KERNEL);
42762306a36Sopenharmony_ci	if (!virt_addr)
42862306a36Sopenharmony_ci		return -ENOMEM;
42962306a36Sopenharmony_ci
43062306a36Sopenharmony_ci	/*
43162306a36Sopenharmony_ci	 * There is a bug in the ep93xx framebuffer which causes problems
43262306a36Sopenharmony_ci	 * if bit 27 of the physical address is set.
43362306a36Sopenharmony_ci	 * See: https://marc.info/?l=linux-arm-kernel&m=110061245502000&w=2
43462306a36Sopenharmony_ci	 * There does not seem to be any official errata for this, but I
43562306a36Sopenharmony_ci	 * have confirmed the problem exists on my hardware (ep9315) at
43662306a36Sopenharmony_ci	 * least.
43762306a36Sopenharmony_ci	 */
43862306a36Sopenharmony_ci	if (check_screenpage_bug && phys_addr & (1 << 27)) {
43962306a36Sopenharmony_ci		fb_err(info, "ep93xx framebuffer bug. phys addr (0x%x) "
44062306a36Sopenharmony_ci		       "has bit 27 set: cannot init framebuffer\n",
44162306a36Sopenharmony_ci		       phys_addr);
44262306a36Sopenharmony_ci
44362306a36Sopenharmony_ci		dma_free_coherent(info->device, fb_size, virt_addr, phys_addr);
44462306a36Sopenharmony_ci		return -ENOMEM;
44562306a36Sopenharmony_ci	}
44662306a36Sopenharmony_ci
44762306a36Sopenharmony_ci	info->fix.smem_start = phys_addr;
44862306a36Sopenharmony_ci	info->fix.smem_len = fb_size;
44962306a36Sopenharmony_ci	info->screen_base = virt_addr;
45062306a36Sopenharmony_ci
45162306a36Sopenharmony_ci	return 0;
45262306a36Sopenharmony_ci}
45362306a36Sopenharmony_ci
45462306a36Sopenharmony_cistatic void ep93xxfb_dealloc_videomem(struct fb_info *info)
45562306a36Sopenharmony_ci{
45662306a36Sopenharmony_ci	if (info->screen_base)
45762306a36Sopenharmony_ci		dma_free_coherent(info->device, info->fix.smem_len,
45862306a36Sopenharmony_ci				  info->screen_base, info->fix.smem_start);
45962306a36Sopenharmony_ci}
46062306a36Sopenharmony_ci
46162306a36Sopenharmony_cistatic int ep93xxfb_probe(struct platform_device *pdev)
46262306a36Sopenharmony_ci{
46362306a36Sopenharmony_ci	struct ep93xxfb_mach_info *mach_info = dev_get_platdata(&pdev->dev);
46462306a36Sopenharmony_ci	struct fb_info *info;
46562306a36Sopenharmony_ci	struct ep93xx_fbi *fbi;
46662306a36Sopenharmony_ci	struct resource *res;
46762306a36Sopenharmony_ci	char *video_mode;
46862306a36Sopenharmony_ci	int err;
46962306a36Sopenharmony_ci
47062306a36Sopenharmony_ci	if (!mach_info)
47162306a36Sopenharmony_ci		return -EINVAL;
47262306a36Sopenharmony_ci
47362306a36Sopenharmony_ci	info = framebuffer_alloc(sizeof(struct ep93xx_fbi), &pdev->dev);
47462306a36Sopenharmony_ci	if (!info)
47562306a36Sopenharmony_ci		return -ENOMEM;
47662306a36Sopenharmony_ci
47762306a36Sopenharmony_ci	platform_set_drvdata(pdev, info);
47862306a36Sopenharmony_ci	fbi = info->par;
47962306a36Sopenharmony_ci	fbi->mach_info = mach_info;
48062306a36Sopenharmony_ci
48162306a36Sopenharmony_ci	err = fb_alloc_cmap(&info->cmap, 256, 0);
48262306a36Sopenharmony_ci	if (err)
48362306a36Sopenharmony_ci		goto failed_cmap;
48462306a36Sopenharmony_ci
48562306a36Sopenharmony_ci	err = ep93xxfb_alloc_videomem(info);
48662306a36Sopenharmony_ci	if (err)
48762306a36Sopenharmony_ci		goto failed_videomem;
48862306a36Sopenharmony_ci
48962306a36Sopenharmony_ci	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
49062306a36Sopenharmony_ci	if (!res) {
49162306a36Sopenharmony_ci		err = -ENXIO;
49262306a36Sopenharmony_ci		goto failed_resource;
49362306a36Sopenharmony_ci	}
49462306a36Sopenharmony_ci
49562306a36Sopenharmony_ci	/*
49662306a36Sopenharmony_ci	 * FIXME - We don't do a request_mem_region here because we are
49762306a36Sopenharmony_ci	 * sharing the register space with the backlight driver (see
49862306a36Sopenharmony_ci	 * drivers/video/backlight/ep93xx_bl.c) and doing so will cause
49962306a36Sopenharmony_ci	 * the second loaded driver to return -EBUSY.
50062306a36Sopenharmony_ci	 *
50162306a36Sopenharmony_ci	 * NOTE: No locking is required; the backlight does not touch
50262306a36Sopenharmony_ci	 * any of the framebuffer registers.
50362306a36Sopenharmony_ci	 */
50462306a36Sopenharmony_ci	fbi->res = res;
50562306a36Sopenharmony_ci	fbi->mmio_base = devm_ioremap(&pdev->dev, res->start,
50662306a36Sopenharmony_ci				      resource_size(res));
50762306a36Sopenharmony_ci	if (!fbi->mmio_base) {
50862306a36Sopenharmony_ci		err = -ENXIO;
50962306a36Sopenharmony_ci		goto failed_resource;
51062306a36Sopenharmony_ci	}
51162306a36Sopenharmony_ci
51262306a36Sopenharmony_ci	strcpy(info->fix.id, pdev->name);
51362306a36Sopenharmony_ci	info->fbops		= &ep93xxfb_ops;
51462306a36Sopenharmony_ci	info->fix.type		= FB_TYPE_PACKED_PIXELS;
51562306a36Sopenharmony_ci	info->fix.accel		= FB_ACCEL_NONE;
51662306a36Sopenharmony_ci	info->var.activate	= FB_ACTIVATE_NOW;
51762306a36Sopenharmony_ci	info->var.vmode		= FB_VMODE_NONINTERLACED;
51862306a36Sopenharmony_ci	info->node		= -1;
51962306a36Sopenharmony_ci	info->state		= FBINFO_STATE_RUNNING;
52062306a36Sopenharmony_ci	info->pseudo_palette	= &fbi->pseudo_palette;
52162306a36Sopenharmony_ci
52262306a36Sopenharmony_ci	fb_get_options("ep93xx-fb", &video_mode);
52362306a36Sopenharmony_ci	err = fb_find_mode(&info->var, info, video_mode,
52462306a36Sopenharmony_ci			   NULL, 0, NULL, 16);
52562306a36Sopenharmony_ci	if (err == 0) {
52662306a36Sopenharmony_ci		fb_err(info, "No suitable video mode found\n");
52762306a36Sopenharmony_ci		err = -EINVAL;
52862306a36Sopenharmony_ci		goto failed_resource;
52962306a36Sopenharmony_ci	}
53062306a36Sopenharmony_ci
53162306a36Sopenharmony_ci	if (mach_info->setup) {
53262306a36Sopenharmony_ci		err = mach_info->setup(pdev);
53362306a36Sopenharmony_ci		if (err)
53462306a36Sopenharmony_ci			goto failed_resource;
53562306a36Sopenharmony_ci	}
53662306a36Sopenharmony_ci
53762306a36Sopenharmony_ci	err = ep93xxfb_check_var(&info->var, info);
53862306a36Sopenharmony_ci	if (err)
53962306a36Sopenharmony_ci		goto failed_check;
54062306a36Sopenharmony_ci
54162306a36Sopenharmony_ci	fbi->clk = devm_clk_get(&pdev->dev, NULL);
54262306a36Sopenharmony_ci	if (IS_ERR(fbi->clk)) {
54362306a36Sopenharmony_ci		err = PTR_ERR(fbi->clk);
54462306a36Sopenharmony_ci		fbi->clk = NULL;
54562306a36Sopenharmony_ci		goto failed_check;
54662306a36Sopenharmony_ci	}
54762306a36Sopenharmony_ci
54862306a36Sopenharmony_ci	ep93xxfb_set_par(info);
54962306a36Sopenharmony_ci	err = clk_prepare_enable(fbi->clk);
55062306a36Sopenharmony_ci	if (err)
55162306a36Sopenharmony_ci		goto failed_check;
55262306a36Sopenharmony_ci
55362306a36Sopenharmony_ci	err = register_framebuffer(info);
55462306a36Sopenharmony_ci	if (err)
55562306a36Sopenharmony_ci		goto failed_framebuffer;
55662306a36Sopenharmony_ci
55762306a36Sopenharmony_ci	fb_info(info, "registered. Mode = %dx%d-%d\n",
55862306a36Sopenharmony_ci		info->var.xres, info->var.yres, info->var.bits_per_pixel);
55962306a36Sopenharmony_ci	return 0;
56062306a36Sopenharmony_ci
56162306a36Sopenharmony_cifailed_framebuffer:
56262306a36Sopenharmony_ci	clk_disable_unprepare(fbi->clk);
56362306a36Sopenharmony_cifailed_check:
56462306a36Sopenharmony_ci	if (fbi->mach_info->teardown)
56562306a36Sopenharmony_ci		fbi->mach_info->teardown(pdev);
56662306a36Sopenharmony_cifailed_resource:
56762306a36Sopenharmony_ci	ep93xxfb_dealloc_videomem(info);
56862306a36Sopenharmony_cifailed_videomem:
56962306a36Sopenharmony_ci	fb_dealloc_cmap(&info->cmap);
57062306a36Sopenharmony_cifailed_cmap:
57162306a36Sopenharmony_ci	kfree(info);
57262306a36Sopenharmony_ci
57362306a36Sopenharmony_ci	return err;
57462306a36Sopenharmony_ci}
57562306a36Sopenharmony_ci
57662306a36Sopenharmony_cistatic void ep93xxfb_remove(struct platform_device *pdev)
57762306a36Sopenharmony_ci{
57862306a36Sopenharmony_ci	struct fb_info *info = platform_get_drvdata(pdev);
57962306a36Sopenharmony_ci	struct ep93xx_fbi *fbi = info->par;
58062306a36Sopenharmony_ci
58162306a36Sopenharmony_ci	unregister_framebuffer(info);
58262306a36Sopenharmony_ci	clk_disable_unprepare(fbi->clk);
58362306a36Sopenharmony_ci	ep93xxfb_dealloc_videomem(info);
58462306a36Sopenharmony_ci	fb_dealloc_cmap(&info->cmap);
58562306a36Sopenharmony_ci
58662306a36Sopenharmony_ci	if (fbi->mach_info->teardown)
58762306a36Sopenharmony_ci		fbi->mach_info->teardown(pdev);
58862306a36Sopenharmony_ci
58962306a36Sopenharmony_ci	kfree(info);
59062306a36Sopenharmony_ci}
59162306a36Sopenharmony_ci
59262306a36Sopenharmony_cistatic struct platform_driver ep93xxfb_driver = {
59362306a36Sopenharmony_ci	.probe		= ep93xxfb_probe,
59462306a36Sopenharmony_ci	.remove_new	= ep93xxfb_remove,
59562306a36Sopenharmony_ci	.driver = {
59662306a36Sopenharmony_ci		.name	= "ep93xx-fb",
59762306a36Sopenharmony_ci	},
59862306a36Sopenharmony_ci};
59962306a36Sopenharmony_cimodule_platform_driver(ep93xxfb_driver);
60062306a36Sopenharmony_ci
60162306a36Sopenharmony_ciMODULE_DESCRIPTION("EP93XX Framebuffer Driver");
60262306a36Sopenharmony_ciMODULE_ALIAS("platform:ep93xx-fb");
60362306a36Sopenharmony_ciMODULE_AUTHOR("Ryan Mallon, "
60462306a36Sopenharmony_ci	      "H Hartley Sweeten <hsweeten@visionengravers.com");
60562306a36Sopenharmony_ciMODULE_LICENSE("GPL");
606