xref: /openbmc/linux/drivers/video/fbdev/ep93xx-fb.c (revision c900529f3d9161bfde5cca0754f83b4d3c3e0220)
1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2f7018c21STomi Valkeinen /*
3f7018c21STomi Valkeinen  * linux/drivers/video/ep93xx-fb.c
4f7018c21STomi Valkeinen  *
5f7018c21STomi Valkeinen  * Framebuffer support for the EP93xx series.
6f7018c21STomi Valkeinen  *
7f7018c21STomi Valkeinen  * Copyright (C) 2007 Bluewater Systems Ltd
8f7018c21STomi Valkeinen  * Author: Ryan Mallon
9f7018c21STomi Valkeinen  *
10f7018c21STomi Valkeinen  * Copyright (c) 2009 H Hartley Sweeten <hsweeten@visionengravers.com>
11f7018c21STomi Valkeinen  *
12f7018c21STomi Valkeinen  * Based on the Cirrus Logic ep93xxfb driver, and various other ep93xxfb
13f7018c21STomi Valkeinen  * drivers.
14f7018c21STomi Valkeinen  */
15f7018c21STomi Valkeinen 
16f7018c21STomi Valkeinen #include <linux/platform_device.h>
17f7018c21STomi Valkeinen #include <linux/module.h>
18f7018c21STomi Valkeinen #include <linux/dma-mapping.h>
19f7018c21STomi Valkeinen #include <linux/slab.h>
20f7018c21STomi Valkeinen #include <linux/clk.h>
21f7018c21STomi Valkeinen #include <linux/fb.h>
22f7018c21STomi Valkeinen #include <linux/io.h>
23f7018c21STomi Valkeinen 
24f7018c21STomi Valkeinen #include <linux/platform_data/video-ep93xx.h>
25f7018c21STomi Valkeinen 
26f7018c21STomi Valkeinen /* Vertical Frame Timing Registers */
27f7018c21STomi Valkeinen #define EP93XXFB_VLINES_TOTAL			0x0000	/* SW locked */
28f7018c21STomi Valkeinen #define EP93XXFB_VSYNC				0x0004	/* SW locked */
29f7018c21STomi Valkeinen #define EP93XXFB_VACTIVE			0x0008	/* SW locked */
30f7018c21STomi Valkeinen #define EP93XXFB_VBLANK				0x0228	/* SW locked */
31f7018c21STomi Valkeinen #define EP93XXFB_VCLK				0x000c	/* SW locked */
32f7018c21STomi Valkeinen 
33f7018c21STomi Valkeinen /* Horizontal Frame Timing Registers */
34f7018c21STomi Valkeinen #define EP93XXFB_HCLKS_TOTAL			0x0010	/* SW locked */
35f7018c21STomi Valkeinen #define EP93XXFB_HSYNC				0x0014	/* SW locked */
36f7018c21STomi Valkeinen #define EP93XXFB_HACTIVE			0x0018	/* SW locked */
37f7018c21STomi Valkeinen #define EP93XXFB_HBLANK				0x022c	/* SW locked */
38f7018c21STomi Valkeinen #define EP93XXFB_HCLK				0x001c	/* SW locked */
39f7018c21STomi Valkeinen 
40f7018c21STomi Valkeinen /* Frame Buffer Memory Configuration Registers */
41f7018c21STomi Valkeinen #define EP93XXFB_SCREEN_PAGE			0x0028
42f7018c21STomi Valkeinen #define EP93XXFB_SCREEN_HPAGE			0x002c
43f7018c21STomi Valkeinen #define EP93XXFB_SCREEN_LINES			0x0030
44f7018c21STomi Valkeinen #define EP93XXFB_LINE_LENGTH			0x0034
45f7018c21STomi Valkeinen #define EP93XXFB_VLINE_STEP			0x0038
46f7018c21STomi Valkeinen #define EP93XXFB_LINE_CARRY			0x003c	/* SW locked */
47f7018c21STomi Valkeinen #define EP93XXFB_EOL_OFFSET			0x0230
48f7018c21STomi Valkeinen 
49f7018c21STomi Valkeinen /* Other Video Registers */
50f7018c21STomi Valkeinen #define EP93XXFB_BRIGHTNESS			0x0020
51f7018c21STomi Valkeinen #define EP93XXFB_ATTRIBS			0x0024	/* SW locked */
52f7018c21STomi Valkeinen #define EP93XXFB_SWLOCK				0x007c	/* SW locked */
53f7018c21STomi Valkeinen #define EP93XXFB_AC_RATE			0x0214
54f7018c21STomi Valkeinen #define EP93XXFB_FIFO_LEVEL			0x0234
55f7018c21STomi Valkeinen #define EP93XXFB_PIXELMODE			0x0054
56f7018c21STomi Valkeinen #define EP93XXFB_PIXELMODE_32BPP		(0x7 << 0)
57f7018c21STomi Valkeinen #define EP93XXFB_PIXELMODE_24BPP		(0x6 << 0)
58f7018c21STomi Valkeinen #define EP93XXFB_PIXELMODE_16BPP		(0x4 << 0)
59f7018c21STomi Valkeinen #define EP93XXFB_PIXELMODE_8BPP			(0x2 << 0)
60f7018c21STomi Valkeinen #define EP93XXFB_PIXELMODE_SHIFT_1P_24B		(0x0 << 3)
61f7018c21STomi Valkeinen #define EP93XXFB_PIXELMODE_SHIFT_1P_18B		(0x1 << 3)
62f7018c21STomi Valkeinen #define EP93XXFB_PIXELMODE_COLOR_LUT		(0x0 << 10)
63f7018c21STomi Valkeinen #define EP93XXFB_PIXELMODE_COLOR_888		(0x4 << 10)
64f7018c21STomi Valkeinen #define EP93XXFB_PIXELMODE_COLOR_555		(0x5 << 10)
65f7018c21STomi Valkeinen #define EP93XXFB_PARL_IF_OUT			0x0058
66f7018c21STomi Valkeinen #define EP93XXFB_PARL_IF_IN			0x005c
67f7018c21STomi Valkeinen 
68f7018c21STomi Valkeinen /* Blink Control Registers */
69f7018c21STomi Valkeinen #define EP93XXFB_BLINK_RATE			0x0040
70f7018c21STomi Valkeinen #define EP93XXFB_BLINK_MASK			0x0044
71f7018c21STomi Valkeinen #define EP93XXFB_BLINK_PATTRN			0x0048
72f7018c21STomi Valkeinen #define EP93XXFB_PATTRN_MASK			0x004c
73f7018c21STomi Valkeinen #define EP93XXFB_BKGRND_OFFSET			0x0050
74f7018c21STomi Valkeinen 
75f7018c21STomi Valkeinen /* Hardware Cursor Registers */
76f7018c21STomi Valkeinen #define EP93XXFB_CURSOR_ADR_START		0x0060
77f7018c21STomi Valkeinen #define EP93XXFB_CURSOR_ADR_RESET		0x0064
78f7018c21STomi Valkeinen #define EP93XXFB_CURSOR_SIZE			0x0068
79f7018c21STomi Valkeinen #define EP93XXFB_CURSOR_COLOR1			0x006c
80f7018c21STomi Valkeinen #define EP93XXFB_CURSOR_COLOR2			0x0070
81f7018c21STomi Valkeinen #define EP93XXFB_CURSOR_BLINK_COLOR1		0x021c
82f7018c21STomi Valkeinen #define EP93XXFB_CURSOR_BLINK_COLOR2		0x0220
83f7018c21STomi Valkeinen #define EP93XXFB_CURSOR_XY_LOC			0x0074
84f7018c21STomi Valkeinen #define EP93XXFB_CURSOR_DSCAN_HY_LOC		0x0078
85f7018c21STomi Valkeinen #define EP93XXFB_CURSOR_BLINK_RATE_CTRL		0x0224
86f7018c21STomi Valkeinen 
87f7018c21STomi Valkeinen /* LUT Registers */
88f7018c21STomi Valkeinen #define EP93XXFB_GRY_SCL_LUTR			0x0080
89f7018c21STomi Valkeinen #define EP93XXFB_GRY_SCL_LUTG			0x0280
90f7018c21STomi Valkeinen #define EP93XXFB_GRY_SCL_LUTB			0x0300
91f7018c21STomi Valkeinen #define EP93XXFB_LUT_SW_CONTROL			0x0218
92f7018c21STomi Valkeinen #define EP93XXFB_LUT_SW_CONTROL_SWTCH		(1 << 0)
93f7018c21STomi Valkeinen #define EP93XXFB_LUT_SW_CONTROL_SSTAT		(1 << 1)
94f7018c21STomi Valkeinen #define EP93XXFB_COLOR_LUT			0x0400
95f7018c21STomi Valkeinen 
96f7018c21STomi Valkeinen /* Video Signature Registers */
97f7018c21STomi Valkeinen #define EP93XXFB_VID_SIG_RSLT_VAL		0x0200
98f7018c21STomi Valkeinen #define EP93XXFB_VID_SIG_CTRL			0x0204
99f7018c21STomi Valkeinen #define EP93XXFB_VSIG				0x0208
100f7018c21STomi Valkeinen #define EP93XXFB_HSIG				0x020c
101f7018c21STomi Valkeinen #define EP93XXFB_SIG_CLR_STR			0x0210
102f7018c21STomi Valkeinen 
103f7018c21STomi Valkeinen /* Minimum / Maximum resolutions supported */
104f7018c21STomi Valkeinen #define EP93XXFB_MIN_XRES			64
105f7018c21STomi Valkeinen #define EP93XXFB_MIN_YRES			64
106f7018c21STomi Valkeinen #define EP93XXFB_MAX_XRES			1024
107f7018c21STomi Valkeinen #define EP93XXFB_MAX_YRES			768
108f7018c21STomi Valkeinen 
109f7018c21STomi Valkeinen struct ep93xx_fbi {
110f7018c21STomi Valkeinen 	struct ep93xxfb_mach_info	*mach_info;
111f7018c21STomi Valkeinen 	struct clk			*clk;
112f7018c21STomi Valkeinen 	struct resource			*res;
113f7018c21STomi Valkeinen 	void __iomem			*mmio_base;
114f7018c21STomi Valkeinen 	unsigned int			pseudo_palette[256];
115f7018c21STomi Valkeinen };
116f7018c21STomi Valkeinen 
117f7018c21STomi Valkeinen static int check_screenpage_bug = 1;
118f7018c21STomi Valkeinen module_param(check_screenpage_bug, int, 0644);
119f7018c21STomi Valkeinen MODULE_PARM_DESC(check_screenpage_bug,
120f7018c21STomi Valkeinen 		 "Check for bit 27 screen page bug. Default = 1");
121f7018c21STomi Valkeinen 
ep93xxfb_readl(struct ep93xx_fbi * fbi,unsigned int off)122f7018c21STomi Valkeinen static inline unsigned int ep93xxfb_readl(struct ep93xx_fbi *fbi,
123f7018c21STomi Valkeinen 					  unsigned int off)
124f7018c21STomi Valkeinen {
125f7018c21STomi Valkeinen 	return __raw_readl(fbi->mmio_base + off);
126f7018c21STomi Valkeinen }
127f7018c21STomi Valkeinen 
ep93xxfb_writel(struct ep93xx_fbi * fbi,unsigned int val,unsigned int off)128f7018c21STomi Valkeinen static inline void ep93xxfb_writel(struct ep93xx_fbi *fbi,
129f7018c21STomi Valkeinen 				   unsigned int val, unsigned int off)
130f7018c21STomi Valkeinen {
131f7018c21STomi Valkeinen 	__raw_writel(val, fbi->mmio_base + off);
132f7018c21STomi Valkeinen }
133f7018c21STomi Valkeinen 
134f7018c21STomi Valkeinen /*
135f7018c21STomi Valkeinen  * Write to one of the locked raster registers.
136f7018c21STomi Valkeinen  */
ep93xxfb_out_locked(struct ep93xx_fbi * fbi,unsigned int val,unsigned int reg)137f7018c21STomi Valkeinen static inline void ep93xxfb_out_locked(struct ep93xx_fbi *fbi,
138f7018c21STomi Valkeinen 				       unsigned int val, unsigned int reg)
139f7018c21STomi Valkeinen {
140f7018c21STomi Valkeinen 	/*
141f7018c21STomi Valkeinen 	 * We don't need a lock or delay here since the raster register
142f7018c21STomi Valkeinen 	 * block will remain unlocked until the next access.
143f7018c21STomi Valkeinen 	 */
144f7018c21STomi Valkeinen 	ep93xxfb_writel(fbi, 0xaa, EP93XXFB_SWLOCK);
145f7018c21STomi Valkeinen 	ep93xxfb_writel(fbi, val, reg);
146f7018c21STomi Valkeinen }
147f7018c21STomi Valkeinen 
ep93xxfb_set_video_attribs(struct fb_info * info)148f7018c21STomi Valkeinen static void ep93xxfb_set_video_attribs(struct fb_info *info)
149f7018c21STomi Valkeinen {
150f7018c21STomi Valkeinen 	struct ep93xx_fbi *fbi = info->par;
151f7018c21STomi Valkeinen 	unsigned int attribs;
152f7018c21STomi Valkeinen 
153f7018c21STomi Valkeinen 	attribs = EP93XXFB_ENABLE;
154f7018c21STomi Valkeinen 	attribs |= fbi->mach_info->flags;
155f7018c21STomi Valkeinen 	ep93xxfb_out_locked(fbi, attribs, EP93XXFB_ATTRIBS);
156f7018c21STomi Valkeinen }
157f7018c21STomi Valkeinen 
ep93xxfb_set_pixelmode(struct fb_info * info)158f7018c21STomi Valkeinen static int ep93xxfb_set_pixelmode(struct fb_info *info)
159f7018c21STomi Valkeinen {
160f7018c21STomi Valkeinen 	struct ep93xx_fbi *fbi = info->par;
161f7018c21STomi Valkeinen 	unsigned int val;
162f7018c21STomi Valkeinen 
163f7018c21STomi Valkeinen 	info->var.transp.offset = 0;
164f7018c21STomi Valkeinen 	info->var.transp.length = 0;
165f7018c21STomi Valkeinen 
166f7018c21STomi Valkeinen 	switch (info->var.bits_per_pixel) {
167f7018c21STomi Valkeinen 	case 8:
168f7018c21STomi Valkeinen 		val = EP93XXFB_PIXELMODE_8BPP | EP93XXFB_PIXELMODE_COLOR_LUT |
169f7018c21STomi Valkeinen 			EP93XXFB_PIXELMODE_SHIFT_1P_18B;
170f7018c21STomi Valkeinen 
171f7018c21STomi Valkeinen 		info->var.red.offset	= 0;
172f7018c21STomi Valkeinen 		info->var.red.length	= 8;
173f7018c21STomi Valkeinen 		info->var.green.offset	= 0;
174f7018c21STomi Valkeinen 		info->var.green.length	= 8;
175f7018c21STomi Valkeinen 		info->var.blue.offset	= 0;
176f7018c21STomi Valkeinen 		info->var.blue.length	= 8;
177f7018c21STomi Valkeinen 		info->fix.visual 	= FB_VISUAL_PSEUDOCOLOR;
178f7018c21STomi Valkeinen 		break;
179f7018c21STomi Valkeinen 
180f7018c21STomi Valkeinen 	case 16:
181f7018c21STomi Valkeinen 		val = EP93XXFB_PIXELMODE_16BPP | EP93XXFB_PIXELMODE_COLOR_555 |
182f7018c21STomi Valkeinen 			EP93XXFB_PIXELMODE_SHIFT_1P_18B;
183f7018c21STomi Valkeinen 
184f7018c21STomi Valkeinen 		info->var.red.offset	= 11;
185f7018c21STomi Valkeinen 		info->var.red.length	= 5;
186f7018c21STomi Valkeinen 		info->var.green.offset	= 5;
187f7018c21STomi Valkeinen 		info->var.green.length	= 6;
188f7018c21STomi Valkeinen 		info->var.blue.offset	= 0;
189f7018c21STomi Valkeinen 		info->var.blue.length	= 5;
190f7018c21STomi Valkeinen 		info->fix.visual 	= FB_VISUAL_TRUECOLOR;
191f7018c21STomi Valkeinen 		break;
192f7018c21STomi Valkeinen 
193f7018c21STomi Valkeinen 	case 24:
194f7018c21STomi Valkeinen 		val = EP93XXFB_PIXELMODE_24BPP | EP93XXFB_PIXELMODE_COLOR_888 |
195f7018c21STomi Valkeinen 			EP93XXFB_PIXELMODE_SHIFT_1P_24B;
196f7018c21STomi Valkeinen 
197f7018c21STomi Valkeinen 		info->var.red.offset	= 16;
198f7018c21STomi Valkeinen 		info->var.red.length	= 8;
199f7018c21STomi Valkeinen 		info->var.green.offset	= 8;
200f7018c21STomi Valkeinen 		info->var.green.length	= 8;
201f7018c21STomi Valkeinen 		info->var.blue.offset	= 0;
202f7018c21STomi Valkeinen 		info->var.blue.length	= 8;
203f7018c21STomi Valkeinen 		info->fix.visual 	= FB_VISUAL_TRUECOLOR;
204f7018c21STomi Valkeinen 		break;
205f7018c21STomi Valkeinen 
206f7018c21STomi Valkeinen 	case 32:
207f7018c21STomi Valkeinen 		val = EP93XXFB_PIXELMODE_32BPP | EP93XXFB_PIXELMODE_COLOR_888 |
208f7018c21STomi Valkeinen 			EP93XXFB_PIXELMODE_SHIFT_1P_24B;
209f7018c21STomi Valkeinen 
210f7018c21STomi Valkeinen 		info->var.red.offset	= 16;
211f7018c21STomi Valkeinen 		info->var.red.length	= 8;
212f7018c21STomi Valkeinen 		info->var.green.offset	= 8;
213f7018c21STomi Valkeinen 		info->var.green.length	= 8;
214f7018c21STomi Valkeinen 		info->var.blue.offset	= 0;
215f7018c21STomi Valkeinen 		info->var.blue.length	= 8;
216f7018c21STomi Valkeinen 		info->fix.visual 	= FB_VISUAL_TRUECOLOR;
217f7018c21STomi Valkeinen 		break;
218f7018c21STomi Valkeinen 
219f7018c21STomi Valkeinen 	default:
220f7018c21STomi Valkeinen 		return -EINVAL;
221f7018c21STomi Valkeinen 	}
222f7018c21STomi Valkeinen 
223f7018c21STomi Valkeinen 	ep93xxfb_writel(fbi, val, EP93XXFB_PIXELMODE);
224f7018c21STomi Valkeinen 	return 0;
225f7018c21STomi Valkeinen }
226f7018c21STomi Valkeinen 
ep93xxfb_set_timing(struct fb_info * info)227f7018c21STomi Valkeinen static void ep93xxfb_set_timing(struct fb_info *info)
228f7018c21STomi Valkeinen {
229f7018c21STomi Valkeinen 	struct ep93xx_fbi *fbi = info->par;
230f7018c21STomi Valkeinen 	unsigned int vlines_total, hclks_total, start, stop;
231f7018c21STomi Valkeinen 
232f7018c21STomi Valkeinen 	vlines_total = info->var.yres + info->var.upper_margin +
233f7018c21STomi Valkeinen 		info->var.lower_margin + info->var.vsync_len - 1;
234f7018c21STomi Valkeinen 
235f7018c21STomi Valkeinen 	hclks_total = info->var.xres + info->var.left_margin +
236f7018c21STomi Valkeinen 		info->var.right_margin + info->var.hsync_len - 1;
237f7018c21STomi Valkeinen 
238f7018c21STomi Valkeinen 	ep93xxfb_out_locked(fbi, vlines_total, EP93XXFB_VLINES_TOTAL);
239f7018c21STomi Valkeinen 	ep93xxfb_out_locked(fbi, hclks_total, EP93XXFB_HCLKS_TOTAL);
240f7018c21STomi Valkeinen 
241f7018c21STomi Valkeinen 	start = vlines_total;
242f7018c21STomi Valkeinen 	stop = vlines_total - info->var.vsync_len;
243f7018c21STomi Valkeinen 	ep93xxfb_out_locked(fbi, start | (stop << 16), EP93XXFB_VSYNC);
244f7018c21STomi Valkeinen 
245f7018c21STomi Valkeinen 	start = vlines_total - info->var.vsync_len - info->var.upper_margin;
246f7018c21STomi Valkeinen 	stop = info->var.lower_margin - 1;
247f7018c21STomi Valkeinen 	ep93xxfb_out_locked(fbi, start | (stop << 16), EP93XXFB_VBLANK);
248f7018c21STomi Valkeinen 	ep93xxfb_out_locked(fbi, start | (stop << 16), EP93XXFB_VACTIVE);
249f7018c21STomi Valkeinen 
250f7018c21STomi Valkeinen 	start = vlines_total;
251f7018c21STomi Valkeinen 	stop = vlines_total + 1;
252f7018c21STomi Valkeinen 	ep93xxfb_out_locked(fbi, start | (stop << 16), EP93XXFB_VCLK);
253f7018c21STomi Valkeinen 
254f7018c21STomi Valkeinen 	start = hclks_total;
255f7018c21STomi Valkeinen 	stop = hclks_total - info->var.hsync_len;
256f7018c21STomi Valkeinen 	ep93xxfb_out_locked(fbi, start | (stop << 16), EP93XXFB_HSYNC);
257f7018c21STomi Valkeinen 
258f7018c21STomi Valkeinen 	start = hclks_total - info->var.hsync_len - info->var.left_margin;
259f7018c21STomi Valkeinen 	stop = info->var.right_margin - 1;
260f7018c21STomi Valkeinen 	ep93xxfb_out_locked(fbi, start | (stop << 16), EP93XXFB_HBLANK);
261f7018c21STomi Valkeinen 	ep93xxfb_out_locked(fbi, start | (stop << 16), EP93XXFB_HACTIVE);
262f7018c21STomi Valkeinen 
263f7018c21STomi Valkeinen 	start = hclks_total;
264f7018c21STomi Valkeinen 	stop = hclks_total;
265f7018c21STomi Valkeinen 	ep93xxfb_out_locked(fbi, start | (stop << 16), EP93XXFB_HCLK);
266f7018c21STomi Valkeinen 
267f7018c21STomi Valkeinen 	ep93xxfb_out_locked(fbi, 0x0, EP93XXFB_LINE_CARRY);
268f7018c21STomi Valkeinen }
269f7018c21STomi Valkeinen 
ep93xxfb_set_par(struct fb_info * info)270f7018c21STomi Valkeinen static int ep93xxfb_set_par(struct fb_info *info)
271f7018c21STomi Valkeinen {
272f7018c21STomi Valkeinen 	struct ep93xx_fbi *fbi = info->par;
273f7018c21STomi Valkeinen 
274f7018c21STomi Valkeinen 	clk_set_rate(fbi->clk, 1000 * PICOS2KHZ(info->var.pixclock));
275f7018c21STomi Valkeinen 
276f7018c21STomi Valkeinen 	ep93xxfb_set_timing(info);
277f7018c21STomi Valkeinen 
278f7018c21STomi Valkeinen 	info->fix.line_length = info->var.xres_virtual *
279f7018c21STomi Valkeinen 		info->var.bits_per_pixel / 8;
280f7018c21STomi Valkeinen 
281f7018c21STomi Valkeinen 	ep93xxfb_writel(fbi, info->fix.smem_start, EP93XXFB_SCREEN_PAGE);
282f7018c21STomi Valkeinen 	ep93xxfb_writel(fbi, info->var.yres - 1, EP93XXFB_SCREEN_LINES);
283f7018c21STomi Valkeinen 	ep93xxfb_writel(fbi, ((info->var.xres * info->var.bits_per_pixel)
284f7018c21STomi Valkeinen 			      / 32) - 1, EP93XXFB_LINE_LENGTH);
285f7018c21STomi Valkeinen 	ep93xxfb_writel(fbi, info->fix.line_length / 4, EP93XXFB_VLINE_STEP);
286f7018c21STomi Valkeinen 	ep93xxfb_set_video_attribs(info);
287f7018c21STomi Valkeinen 	return 0;
288f7018c21STomi Valkeinen }
289f7018c21STomi Valkeinen 
ep93xxfb_check_var(struct fb_var_screeninfo * var,struct fb_info * info)290f7018c21STomi Valkeinen static int ep93xxfb_check_var(struct fb_var_screeninfo *var,
291f7018c21STomi Valkeinen 			      struct fb_info *info)
292f7018c21STomi Valkeinen {
293f7018c21STomi Valkeinen 	int err;
294f7018c21STomi Valkeinen 
295f7018c21STomi Valkeinen 	err = ep93xxfb_set_pixelmode(info);
296f7018c21STomi Valkeinen 	if (err)
297f7018c21STomi Valkeinen 		return err;
298f7018c21STomi Valkeinen 
299f7018c21STomi Valkeinen 	var->xres = max_t(unsigned int, var->xres, EP93XXFB_MIN_XRES);
300f7018c21STomi Valkeinen 	var->xres = min_t(unsigned int, var->xres, EP93XXFB_MAX_XRES);
301f7018c21STomi Valkeinen 	var->xres_virtual = max(var->xres_virtual, var->xres);
302f7018c21STomi Valkeinen 
303f7018c21STomi Valkeinen 	var->yres = max_t(unsigned int, var->yres, EP93XXFB_MIN_YRES);
304f7018c21STomi Valkeinen 	var->yres = min_t(unsigned int, var->yres, EP93XXFB_MAX_YRES);
305f7018c21STomi Valkeinen 	var->yres_virtual = max(var->yres_virtual, var->yres);
306f7018c21STomi Valkeinen 
307f7018c21STomi Valkeinen 	return 0;
308f7018c21STomi Valkeinen }
309f7018c21STomi Valkeinen 
ep93xxfb_mmap(struct fb_info * info,struct vm_area_struct * vma)310f7018c21STomi Valkeinen static int ep93xxfb_mmap(struct fb_info *info, struct vm_area_struct *vma)
311f7018c21STomi Valkeinen {
312f7018c21STomi Valkeinen 	unsigned int offset = vma->vm_pgoff << PAGE_SHIFT;
313f7018c21STomi Valkeinen 
314f7018c21STomi Valkeinen 	if (offset < info->fix.smem_len) {
3159aee7f04SThomas Zimmermann 		return dma_mmap_wc(info->device, vma, info->screen_base,
316f6e45661SLuis R. Rodriguez 				   info->fix.smem_start, info->fix.smem_len);
317f7018c21STomi Valkeinen 	}
318f7018c21STomi Valkeinen 
319f7018c21STomi Valkeinen 	return -EINVAL;
320f7018c21STomi Valkeinen }
321f7018c21STomi Valkeinen 
ep93xxfb_blank(int blank_mode,struct fb_info * info)322f7018c21STomi Valkeinen static int ep93xxfb_blank(int blank_mode, struct fb_info *info)
323f7018c21STomi Valkeinen {
324f7018c21STomi Valkeinen 	struct ep93xx_fbi *fbi = info->par;
325f7018c21STomi Valkeinen 	unsigned int attribs = ep93xxfb_readl(fbi, EP93XXFB_ATTRIBS);
326f7018c21STomi Valkeinen 
327f7018c21STomi Valkeinen 	if (blank_mode) {
328f7018c21STomi Valkeinen 		if (fbi->mach_info->blank)
329f7018c21STomi Valkeinen 			fbi->mach_info->blank(blank_mode, info);
330f7018c21STomi Valkeinen 		ep93xxfb_out_locked(fbi, attribs & ~EP93XXFB_ENABLE,
331f7018c21STomi Valkeinen 				    EP93XXFB_ATTRIBS);
332f7018c21STomi Valkeinen 		clk_disable(fbi->clk);
333f7018c21STomi Valkeinen 	} else {
334f7018c21STomi Valkeinen 		clk_enable(fbi->clk);
335f7018c21STomi Valkeinen 		ep93xxfb_out_locked(fbi, attribs | EP93XXFB_ENABLE,
336f7018c21STomi Valkeinen 				    EP93XXFB_ATTRIBS);
337f7018c21STomi Valkeinen 		if (fbi->mach_info->blank)
338f7018c21STomi Valkeinen 			fbi->mach_info->blank(blank_mode, info);
339f7018c21STomi Valkeinen 	}
340f7018c21STomi Valkeinen 
341f7018c21STomi Valkeinen 	return 0;
342f7018c21STomi Valkeinen }
343f7018c21STomi Valkeinen 
ep93xxfb_convert_color(int val,int width)344f7018c21STomi Valkeinen static inline int ep93xxfb_convert_color(int val, int width)
345f7018c21STomi Valkeinen {
346f7018c21STomi Valkeinen 	return ((val << width) + 0x7fff - val) >> 16;
347f7018c21STomi Valkeinen }
348f7018c21STomi Valkeinen 
ep93xxfb_setcolreg(unsigned int regno,unsigned int red,unsigned int green,unsigned int blue,unsigned int transp,struct fb_info * info)349f7018c21STomi Valkeinen static int ep93xxfb_setcolreg(unsigned int regno, unsigned int red,
350f7018c21STomi Valkeinen 			      unsigned int green, unsigned int blue,
351f7018c21STomi Valkeinen 			      unsigned int transp, struct fb_info *info)
352f7018c21STomi Valkeinen {
353f7018c21STomi Valkeinen 	struct ep93xx_fbi *fbi = info->par;
354f7018c21STomi Valkeinen 	unsigned int *pal = info->pseudo_palette;
355f7018c21STomi Valkeinen 	unsigned int ctrl, i, rgb, lut_current, lut_stat;
356f7018c21STomi Valkeinen 
357f7018c21STomi Valkeinen 	switch (info->fix.visual) {
358f7018c21STomi Valkeinen 	case FB_VISUAL_PSEUDOCOLOR:
359f7018c21STomi Valkeinen 		if (regno > 255)
360f7018c21STomi Valkeinen 			return 1;
361f7018c21STomi Valkeinen 		rgb = ((red & 0xff00) << 8) | (green & 0xff00) |
362f7018c21STomi Valkeinen 			((blue & 0xff00) >> 8);
363f7018c21STomi Valkeinen 
364f7018c21STomi Valkeinen 		pal[regno] = rgb;
365f7018c21STomi Valkeinen 		ep93xxfb_writel(fbi, rgb, (EP93XXFB_COLOR_LUT + (regno << 2)));
366f7018c21STomi Valkeinen 		ctrl = ep93xxfb_readl(fbi, EP93XXFB_LUT_SW_CONTROL);
367f7018c21STomi Valkeinen 		lut_stat = !!(ctrl & EP93XXFB_LUT_SW_CONTROL_SSTAT);
368f7018c21STomi Valkeinen 		lut_current = !!(ctrl & EP93XXFB_LUT_SW_CONTROL_SWTCH);
369f7018c21STomi Valkeinen 
370f7018c21STomi Valkeinen 		if (lut_stat == lut_current) {
371f7018c21STomi Valkeinen 			for (i = 0; i < 256; i++) {
372f7018c21STomi Valkeinen 				ep93xxfb_writel(fbi, pal[i],
373f7018c21STomi Valkeinen 					EP93XXFB_COLOR_LUT + (i << 2));
374f7018c21STomi Valkeinen 			}
375f7018c21STomi Valkeinen 
376f7018c21STomi Valkeinen 			ep93xxfb_writel(fbi,
377f7018c21STomi Valkeinen 					ctrl ^ EP93XXFB_LUT_SW_CONTROL_SWTCH,
378f7018c21STomi Valkeinen 					EP93XXFB_LUT_SW_CONTROL);
379f7018c21STomi Valkeinen 		}
380f7018c21STomi Valkeinen 		break;
381f7018c21STomi Valkeinen 
382f7018c21STomi Valkeinen 	case FB_VISUAL_TRUECOLOR:
383f7018c21STomi Valkeinen 		if (regno > 16)
384f7018c21STomi Valkeinen 			return 1;
385f7018c21STomi Valkeinen 
386f7018c21STomi Valkeinen 		red = ep93xxfb_convert_color(red, info->var.red.length);
387f7018c21STomi Valkeinen 		green = ep93xxfb_convert_color(green, info->var.green.length);
388f7018c21STomi Valkeinen 		blue = ep93xxfb_convert_color(blue, info->var.blue.length);
389f7018c21STomi Valkeinen 		transp = ep93xxfb_convert_color(transp,
390f7018c21STomi Valkeinen 						info->var.transp.length);
391f7018c21STomi Valkeinen 
392f7018c21STomi Valkeinen 		pal[regno] = (red << info->var.red.offset) |
393f7018c21STomi Valkeinen 			(green << info->var.green.offset) |
394f7018c21STomi Valkeinen 			(blue << info->var.blue.offset) |
395f7018c21STomi Valkeinen 			(transp << info->var.transp.offset);
396f7018c21STomi Valkeinen 		break;
397f7018c21STomi Valkeinen 
398f7018c21STomi Valkeinen 	default:
399f7018c21STomi Valkeinen 		return 1;
400f7018c21STomi Valkeinen 	}
401f7018c21STomi Valkeinen 
402f7018c21STomi Valkeinen 	return 0;
403f7018c21STomi Valkeinen }
404f7018c21STomi Valkeinen 
4058a48ac33SJani Nikula static const struct fb_ops ep93xxfb_ops = {
406f7018c21STomi Valkeinen 	.owner		= THIS_MODULE,
407f7018c21STomi Valkeinen 	.fb_check_var	= ep93xxfb_check_var,
408f7018c21STomi Valkeinen 	.fb_set_par	= ep93xxfb_set_par,
409f7018c21STomi Valkeinen 	.fb_blank	= ep93xxfb_blank,
410f7018c21STomi Valkeinen 	.fb_fillrect	= cfb_fillrect,
411f7018c21STomi Valkeinen 	.fb_copyarea	= cfb_copyarea,
412f7018c21STomi Valkeinen 	.fb_imageblit	= cfb_imageblit,
413f7018c21STomi Valkeinen 	.fb_setcolreg	= ep93xxfb_setcolreg,
414f7018c21STomi Valkeinen 	.fb_mmap	= ep93xxfb_mmap,
415f7018c21STomi Valkeinen };
416f7018c21STomi Valkeinen 
ep93xxfb_alloc_videomem(struct fb_info * info)417f7018c21STomi Valkeinen static int ep93xxfb_alloc_videomem(struct fb_info *info)
418f7018c21STomi Valkeinen {
419f7018c21STomi Valkeinen 	char __iomem *virt_addr;
420f7018c21STomi Valkeinen 	dma_addr_t phys_addr;
421f7018c21STomi Valkeinen 	unsigned int fb_size;
422f7018c21STomi Valkeinen 
42316478b61SLinus Walleij 	/* Maximum 16bpp -> used memory is maximum x*y*2 bytes */
42416478b61SLinus Walleij 	fb_size = EP93XXFB_MAX_XRES * EP93XXFB_MAX_YRES * 2;
42516478b61SLinus Walleij 
4269aee7f04SThomas Zimmermann 	virt_addr = dma_alloc_wc(info->device, fb_size, &phys_addr, GFP_KERNEL);
427f7018c21STomi Valkeinen 	if (!virt_addr)
428f7018c21STomi Valkeinen 		return -ENOMEM;
429f7018c21STomi Valkeinen 
430f7018c21STomi Valkeinen 	/*
431f7018c21STomi Valkeinen 	 * There is a bug in the ep93xx framebuffer which causes problems
432f7018c21STomi Valkeinen 	 * if bit 27 of the physical address is set.
4337c7b2a35SAlexander A. Klimov 	 * See: https://marc.info/?l=linux-arm-kernel&m=110061245502000&w=2
434f7018c21STomi Valkeinen 	 * There does not seem to be any official errata for this, but I
435f7018c21STomi Valkeinen 	 * have confirmed the problem exists on my hardware (ep9315) at
436f7018c21STomi Valkeinen 	 * least.
437f7018c21STomi Valkeinen 	 */
438f7018c21STomi Valkeinen 	if (check_screenpage_bug && phys_addr & (1 << 27)) {
439be05e207SThomas Zimmermann 		fb_err(info, "ep93xx framebuffer bug. phys addr (0x%x) "
440f7018c21STomi Valkeinen 		       "has bit 27 set: cannot init framebuffer\n",
441f7018c21STomi Valkeinen 		       phys_addr);
442f7018c21STomi Valkeinen 
4439aee7f04SThomas Zimmermann 		dma_free_coherent(info->device, fb_size, virt_addr, phys_addr);
444f7018c21STomi Valkeinen 		return -ENOMEM;
445f7018c21STomi Valkeinen 	}
446f7018c21STomi Valkeinen 
447f7018c21STomi Valkeinen 	info->fix.smem_start = phys_addr;
448f7018c21STomi Valkeinen 	info->fix.smem_len = fb_size;
449f7018c21STomi Valkeinen 	info->screen_base = virt_addr;
450f7018c21STomi Valkeinen 
451f7018c21STomi Valkeinen 	return 0;
452f7018c21STomi Valkeinen }
453f7018c21STomi Valkeinen 
ep93xxfb_dealloc_videomem(struct fb_info * info)454f7018c21STomi Valkeinen static void ep93xxfb_dealloc_videomem(struct fb_info *info)
455f7018c21STomi Valkeinen {
456f7018c21STomi Valkeinen 	if (info->screen_base)
4579aee7f04SThomas Zimmermann 		dma_free_coherent(info->device, info->fix.smem_len,
458f7018c21STomi Valkeinen 				  info->screen_base, info->fix.smem_start);
459f7018c21STomi Valkeinen }
460f7018c21STomi Valkeinen 
ep93xxfb_probe(struct platform_device * pdev)461f7018c21STomi Valkeinen static int ep93xxfb_probe(struct platform_device *pdev)
462f7018c21STomi Valkeinen {
463f7018c21STomi Valkeinen 	struct ep93xxfb_mach_info *mach_info = dev_get_platdata(&pdev->dev);
464f7018c21STomi Valkeinen 	struct fb_info *info;
465f7018c21STomi Valkeinen 	struct ep93xx_fbi *fbi;
466f7018c21STomi Valkeinen 	struct resource *res;
467f7018c21STomi Valkeinen 	char *video_mode;
468f7018c21STomi Valkeinen 	int err;
469f7018c21STomi Valkeinen 
470f7018c21STomi Valkeinen 	if (!mach_info)
471f7018c21STomi Valkeinen 		return -EINVAL;
472f7018c21STomi Valkeinen 
473f7018c21STomi Valkeinen 	info = framebuffer_alloc(sizeof(struct ep93xx_fbi), &pdev->dev);
474f7018c21STomi Valkeinen 	if (!info)
475f7018c21STomi Valkeinen 		return -ENOMEM;
476f7018c21STomi Valkeinen 
477f7018c21STomi Valkeinen 	platform_set_drvdata(pdev, info);
478f7018c21STomi Valkeinen 	fbi = info->par;
479f7018c21STomi Valkeinen 	fbi->mach_info = mach_info;
480f7018c21STomi Valkeinen 
481f7018c21STomi Valkeinen 	err = fb_alloc_cmap(&info->cmap, 256, 0);
482f7018c21STomi Valkeinen 	if (err)
483f7018c21STomi Valkeinen 		goto failed_cmap;
484f7018c21STomi Valkeinen 
485f7018c21STomi Valkeinen 	err = ep93xxfb_alloc_videomem(info);
486f7018c21STomi Valkeinen 	if (err)
487f7018c21STomi Valkeinen 		goto failed_videomem;
488f7018c21STomi Valkeinen 
489f7018c21STomi Valkeinen 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
490f7018c21STomi Valkeinen 	if (!res) {
491f7018c21STomi Valkeinen 		err = -ENXIO;
492f7018c21STomi Valkeinen 		goto failed_resource;
493f7018c21STomi Valkeinen 	}
494f7018c21STomi Valkeinen 
495f7018c21STomi Valkeinen 	/*
496f7018c21STomi Valkeinen 	 * FIXME - We don't do a request_mem_region here because we are
497f7018c21STomi Valkeinen 	 * sharing the register space with the backlight driver (see
498f7018c21STomi Valkeinen 	 * drivers/video/backlight/ep93xx_bl.c) and doing so will cause
499f7018c21STomi Valkeinen 	 * the second loaded driver to return -EBUSY.
500f7018c21STomi Valkeinen 	 *
501f7018c21STomi Valkeinen 	 * NOTE: No locking is required; the backlight does not touch
502f7018c21STomi Valkeinen 	 * any of the framebuffer registers.
503f7018c21STomi Valkeinen 	 */
504f7018c21STomi Valkeinen 	fbi->res = res;
505f7018c21STomi Valkeinen 	fbi->mmio_base = devm_ioremap(&pdev->dev, res->start,
506f7018c21STomi Valkeinen 				      resource_size(res));
507f7018c21STomi Valkeinen 	if (!fbi->mmio_base) {
508f7018c21STomi Valkeinen 		err = -ENXIO;
509f7018c21STomi Valkeinen 		goto failed_resource;
510f7018c21STomi Valkeinen 	}
511f7018c21STomi Valkeinen 
512f7018c21STomi Valkeinen 	strcpy(info->fix.id, pdev->name);
513f7018c21STomi Valkeinen 	info->fbops		= &ep93xxfb_ops;
514f7018c21STomi Valkeinen 	info->fix.type		= FB_TYPE_PACKED_PIXELS;
515f7018c21STomi Valkeinen 	info->fix.accel		= FB_ACCEL_NONE;
516f7018c21STomi Valkeinen 	info->var.activate	= FB_ACTIVATE_NOW;
517f7018c21STomi Valkeinen 	info->var.vmode		= FB_VMODE_NONINTERLACED;
518f7018c21STomi Valkeinen 	info->node		= -1;
519f7018c21STomi Valkeinen 	info->state		= FBINFO_STATE_RUNNING;
520f7018c21STomi Valkeinen 	info->pseudo_palette	= &fbi->pseudo_palette;
521f7018c21STomi Valkeinen 
522f7018c21STomi Valkeinen 	fb_get_options("ep93xx-fb", &video_mode);
523f7018c21STomi Valkeinen 	err = fb_find_mode(&info->var, info, video_mode,
52416478b61SLinus Walleij 			   NULL, 0, NULL, 16);
525f7018c21STomi Valkeinen 	if (err == 0) {
526be05e207SThomas Zimmermann 		fb_err(info, "No suitable video mode found\n");
527f7018c21STomi Valkeinen 		err = -EINVAL;
528f7018c21STomi Valkeinen 		goto failed_resource;
529f7018c21STomi Valkeinen 	}
530f7018c21STomi Valkeinen 
531f7018c21STomi Valkeinen 	if (mach_info->setup) {
532f7018c21STomi Valkeinen 		err = mach_info->setup(pdev);
533f7018c21STomi Valkeinen 		if (err)
534f7018c21STomi Valkeinen 			goto failed_resource;
535f7018c21STomi Valkeinen 	}
536f7018c21STomi Valkeinen 
537f7018c21STomi Valkeinen 	err = ep93xxfb_check_var(&info->var, info);
538f7018c21STomi Valkeinen 	if (err)
539f7018c21STomi Valkeinen 		goto failed_check;
540f7018c21STomi Valkeinen 
541f7018c21STomi Valkeinen 	fbi->clk = devm_clk_get(&pdev->dev, NULL);
542f7018c21STomi Valkeinen 	if (IS_ERR(fbi->clk)) {
543f7018c21STomi Valkeinen 		err = PTR_ERR(fbi->clk);
544f7018c21STomi Valkeinen 		fbi->clk = NULL;
545f7018c21STomi Valkeinen 		goto failed_check;
546f7018c21STomi Valkeinen 	}
547f7018c21STomi Valkeinen 
548f7018c21STomi Valkeinen 	ep93xxfb_set_par(info);
549*9d5651adSYuanjun Gong 	err = clk_prepare_enable(fbi->clk);
550*9d5651adSYuanjun Gong 	if (err)
551*9d5651adSYuanjun Gong 		goto failed_check;
552f7018c21STomi Valkeinen 
553f7018c21STomi Valkeinen 	err = register_framebuffer(info);
554f7018c21STomi Valkeinen 	if (err)
555c84bf485SGaosheng Cui 		goto failed_framebuffer;
556f7018c21STomi Valkeinen 
557be05e207SThomas Zimmermann 	fb_info(info, "registered. Mode = %dx%d-%d\n",
558f7018c21STomi Valkeinen 		info->var.xres, info->var.yres, info->var.bits_per_pixel);
559f7018c21STomi Valkeinen 	return 0;
560f7018c21STomi Valkeinen 
561c84bf485SGaosheng Cui failed_framebuffer:
562c84bf485SGaosheng Cui 	clk_disable_unprepare(fbi->clk);
563f7018c21STomi Valkeinen failed_check:
564f7018c21STomi Valkeinen 	if (fbi->mach_info->teardown)
565f7018c21STomi Valkeinen 		fbi->mach_info->teardown(pdev);
566f7018c21STomi Valkeinen failed_resource:
567f7018c21STomi Valkeinen 	ep93xxfb_dealloc_videomem(info);
568f7018c21STomi Valkeinen failed_videomem:
569f7018c21STomi Valkeinen 	fb_dealloc_cmap(&info->cmap);
570f7018c21STomi Valkeinen failed_cmap:
571f7018c21STomi Valkeinen 	kfree(info);
572f7018c21STomi Valkeinen 
573f7018c21STomi Valkeinen 	return err;
574f7018c21STomi Valkeinen }
575f7018c21STomi Valkeinen 
ep93xxfb_remove(struct platform_device * pdev)57681431a9eSUwe Kleine-König static void ep93xxfb_remove(struct platform_device *pdev)
577f7018c21STomi Valkeinen {
578f7018c21STomi Valkeinen 	struct fb_info *info = platform_get_drvdata(pdev);
579f7018c21STomi Valkeinen 	struct ep93xx_fbi *fbi = info->par;
580f7018c21STomi Valkeinen 
581f7018c21STomi Valkeinen 	unregister_framebuffer(info);
5820937a7b3SAlexander Sverdlin 	clk_disable_unprepare(fbi->clk);
583f7018c21STomi Valkeinen 	ep93xxfb_dealloc_videomem(info);
584f7018c21STomi Valkeinen 	fb_dealloc_cmap(&info->cmap);
585f7018c21STomi Valkeinen 
586f7018c21STomi Valkeinen 	if (fbi->mach_info->teardown)
587f7018c21STomi Valkeinen 		fbi->mach_info->teardown(pdev);
588f7018c21STomi Valkeinen 
589f7018c21STomi Valkeinen 	kfree(info);
590f7018c21STomi Valkeinen }
591f7018c21STomi Valkeinen 
592f7018c21STomi Valkeinen static struct platform_driver ep93xxfb_driver = {
593f7018c21STomi Valkeinen 	.probe		= ep93xxfb_probe,
59481431a9eSUwe Kleine-König 	.remove_new	= ep93xxfb_remove,
595f7018c21STomi Valkeinen 	.driver = {
596f7018c21STomi Valkeinen 		.name	= "ep93xx-fb",
597f7018c21STomi Valkeinen 	},
598f7018c21STomi Valkeinen };
599f7018c21STomi Valkeinen module_platform_driver(ep93xxfb_driver);
600f7018c21STomi Valkeinen 
601f7018c21STomi Valkeinen MODULE_DESCRIPTION("EP93XX Framebuffer Driver");
602f7018c21STomi Valkeinen MODULE_ALIAS("platform:ep93xx-fb");
603f7018c21STomi Valkeinen MODULE_AUTHOR("Ryan Mallon, "
604f7018c21STomi Valkeinen 	      "H Hartley Sweeten <hsweeten@visionengravers.com");
605f7018c21STomi Valkeinen MODULE_LICENSE("GPL");
606