1 /*
2  *  Cobalt/SEAD3 LCD frame buffer driver.
3  *
4  *  Copyright (C) 2008  Yoichi Yuasa <yuasa@linux-mips.org>
5  *  Copyright (C) 2012  MIPS Technologies, Inc.
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation; either version 2 of the License, or
10  *  (at your option) any later version.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program; if not, write to the Free Software
19  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20  */
21 #include <linux/delay.h>
22 #include <linux/fb.h>
23 #include <linux/init.h>
24 #include <linux/io.h>
25 #include <linux/ioport.h>
26 #include <linux/uaccess.h>
27 #include <linux/platform_device.h>
28 #include <linux/module.h>
29 
30 /*
31  * Cursor position address
32  * \X  0    1    2  ...  14   15
33  * Y+----+----+----+---+----+----+
34  * 0|0x00|0x01|0x02|...|0x0e|0x0f|
35  *  +----+----+----+---+----+----+
36  * 1|0x40|0x41|0x42|...|0x4e|0x4f|
37  *  +----+----+----+---+----+----+
38  */
39 #define LCD_DATA_REG_OFFSET	0x10
40 #define LCD_XRES_MAX		16
41 #define LCD_YRES_MAX		2
42 #define LCD_CHARS_MAX		32
43 
44 #define LCD_CLEAR		0x01
45 #define LCD_CURSOR_MOVE_HOME	0x02
46 #define LCD_RESET		0x06
47 #define LCD_OFF			0x08
48 #define LCD_CURSOR_OFF		0x0c
49 #define LCD_CURSOR_BLINK_OFF	0x0e
50 #define LCD_CURSOR_ON		0x0f
51 #define LCD_ON			LCD_CURSOR_ON
52 #define LCD_CURSOR_MOVE_LEFT	0x10
53 #define LCD_CURSOR_MOVE_RIGHT	0x14
54 #define LCD_DISPLAY_LEFT	0x18
55 #define LCD_DISPLAY_RIGHT	0x1c
56 #define LCD_PRERESET		0x3f	/* execute 4 times continuously */
57 #define LCD_BUSY		0x80
58 
59 #define LCD_GRAPHIC_MODE	0x40
60 #define LCD_TEXT_MODE		0x80
61 #define LCD_CUR_POS_MASK	0x7f
62 
63 #define LCD_CUR_POS(x)		((x) & LCD_CUR_POS_MASK)
64 #define LCD_TEXT_POS(x)		((x) | LCD_TEXT_MODE)
65 
66 #ifdef CONFIG_MIPS_COBALT
67 static inline void lcd_write_control(struct fb_info *info, u8 control)
68 {
69 	writel((u32)control << 24, info->screen_base);
70 }
71 
72 static inline u8 lcd_read_control(struct fb_info *info)
73 {
74 	return readl(info->screen_base) >> 24;
75 }
76 
77 static inline void lcd_write_data(struct fb_info *info, u8 data)
78 {
79 	writel((u32)data << 24, info->screen_base + LCD_DATA_REG_OFFSET);
80 }
81 
82 static inline u8 lcd_read_data(struct fb_info *info)
83 {
84 	return readl(info->screen_base + LCD_DATA_REG_OFFSET) >> 24;
85 }
86 #else
87 
88 #define LCD_CTL			0x00
89 #define LCD_DATA		0x08
90 #define CPLD_STATUS		0x10
91 #define CPLD_DATA		0x18
92 
93 static inline void cpld_wait(struct fb_info *info)
94 {
95 	do {
96 	} while (readl(info->screen_base + CPLD_STATUS) & 1);
97 }
98 
99 static inline void lcd_write_control(struct fb_info *info, u8 control)
100 {
101 	cpld_wait(info);
102 	writel(control, info->screen_base + LCD_CTL);
103 }
104 
105 static inline u8 lcd_read_control(struct fb_info *info)
106 {
107 	cpld_wait(info);
108 	readl(info->screen_base + LCD_CTL);
109 	cpld_wait(info);
110 	return readl(info->screen_base + CPLD_DATA) & 0xff;
111 }
112 
113 static inline void lcd_write_data(struct fb_info *info, u8 data)
114 {
115 	cpld_wait(info);
116 	writel(data, info->screen_base + LCD_DATA);
117 }
118 
119 static inline u8 lcd_read_data(struct fb_info *info)
120 {
121 	cpld_wait(info);
122 	readl(info->screen_base + LCD_DATA);
123 	cpld_wait(info);
124 	return readl(info->screen_base + CPLD_DATA) & 0xff;
125 }
126 #endif
127 
128 static int lcd_busy_wait(struct fb_info *info)
129 {
130 	u8 val = 0;
131 	int timeout = 10, retval = 0;
132 
133 	do {
134 		val = lcd_read_control(info);
135 		val &= LCD_BUSY;
136 		if (val != LCD_BUSY)
137 			break;
138 
139 		if (msleep_interruptible(1))
140 			return -EINTR;
141 
142 		timeout--;
143 	} while (timeout);
144 
145 	if (val == LCD_BUSY)
146 		retval = -EBUSY;
147 
148 	return retval;
149 }
150 
151 static void lcd_clear(struct fb_info *info)
152 {
153 	int i;
154 
155 	for (i = 0; i < 4; i++) {
156 		udelay(150);
157 
158 		lcd_write_control(info, LCD_PRERESET);
159 	}
160 
161 	udelay(150);
162 
163 	lcd_write_control(info, LCD_CLEAR);
164 
165 	udelay(150);
166 
167 	lcd_write_control(info, LCD_RESET);
168 }
169 
170 static struct fb_fix_screeninfo cobalt_lcdfb_fix = {
171 	.id		= "cobalt-lcd",
172 	.type		= FB_TYPE_TEXT,
173 	.type_aux	= FB_AUX_TEXT_MDA,
174 	.visual		= FB_VISUAL_MONO01,
175 	.line_length	= LCD_XRES_MAX,
176 	.accel		= FB_ACCEL_NONE,
177 };
178 
179 static ssize_t cobalt_lcdfb_read(struct fb_info *info, char __user *buf,
180 				 size_t count, loff_t *ppos)
181 {
182 	char src[LCD_CHARS_MAX];
183 	unsigned long pos;
184 	int len, retval = 0;
185 
186 	pos = *ppos;
187 	if (pos >= LCD_CHARS_MAX || count == 0)
188 		return 0;
189 
190 	if (count > LCD_CHARS_MAX)
191 		count = LCD_CHARS_MAX;
192 
193 	if (pos + count > LCD_CHARS_MAX)
194 		count = LCD_CHARS_MAX - pos;
195 
196 	for (len = 0; len < count; len++) {
197 		retval = lcd_busy_wait(info);
198 		if (retval < 0)
199 			break;
200 
201 		lcd_write_control(info, LCD_TEXT_POS(pos));
202 
203 		retval = lcd_busy_wait(info);
204 		if (retval < 0)
205 			break;
206 
207 		src[len] = lcd_read_data(info);
208 		if (pos == 0x0f)
209 			pos = 0x40;
210 		else
211 			pos++;
212 	}
213 
214 	if (retval < 0 && signal_pending(current))
215 		return -ERESTARTSYS;
216 
217 	if (copy_to_user(buf, src, len))
218 		return -EFAULT;
219 
220 	*ppos += len;
221 
222 	return len;
223 }
224 
225 static ssize_t cobalt_lcdfb_write(struct fb_info *info, const char __user *buf,
226 				  size_t count, loff_t *ppos)
227 {
228 	char dst[LCD_CHARS_MAX];
229 	unsigned long pos;
230 	int len, retval = 0;
231 
232 	pos = *ppos;
233 	if (pos >= LCD_CHARS_MAX || count == 0)
234 		return 0;
235 
236 	if (count > LCD_CHARS_MAX)
237 		count = LCD_CHARS_MAX;
238 
239 	if (pos + count > LCD_CHARS_MAX)
240 		count = LCD_CHARS_MAX - pos;
241 
242 	if (copy_from_user(dst, buf, count))
243 		return -EFAULT;
244 
245 	for (len = 0; len < count; len++) {
246 		retval = lcd_busy_wait(info);
247 		if (retval < 0)
248 			break;
249 
250 		lcd_write_control(info, LCD_TEXT_POS(pos));
251 
252 		retval = lcd_busy_wait(info);
253 		if (retval < 0)
254 			break;
255 
256 		lcd_write_data(info, dst[len]);
257 		if (pos == 0x0f)
258 			pos = 0x40;
259 		else
260 			pos++;
261 	}
262 
263 	if (retval < 0 && signal_pending(current))
264 		return -ERESTARTSYS;
265 
266 	*ppos += len;
267 
268 	return len;
269 }
270 
271 static int cobalt_lcdfb_blank(int blank_mode, struct fb_info *info)
272 {
273 	int retval;
274 
275 	retval = lcd_busy_wait(info);
276 	if (retval < 0)
277 		return retval;
278 
279 	switch (blank_mode) {
280 	case FB_BLANK_UNBLANK:
281 		lcd_write_control(info, LCD_ON);
282 		break;
283 	default:
284 		lcd_write_control(info, LCD_OFF);
285 		break;
286 	}
287 
288 	return 0;
289 }
290 
291 static int cobalt_lcdfb_cursor(struct fb_info *info, struct fb_cursor *cursor)
292 {
293 	u32 x, y;
294 	int retval;
295 
296 	switch (cursor->set) {
297 	case FB_CUR_SETPOS:
298 		x = cursor->image.dx;
299 		y = cursor->image.dy;
300 		if (x >= LCD_XRES_MAX || y >= LCD_YRES_MAX)
301 			return -EINVAL;
302 
303 		retval = lcd_busy_wait(info);
304 		if (retval < 0)
305 			return retval;
306 
307 		lcd_write_control(info,
308 				  LCD_TEXT_POS(info->fix.line_length * y + x));
309 		break;
310 	default:
311 		return -EINVAL;
312 	}
313 
314 	retval = lcd_busy_wait(info);
315 	if (retval < 0)
316 		return retval;
317 
318 	if (cursor->enable)
319 		lcd_write_control(info, LCD_CURSOR_ON);
320 	else
321 		lcd_write_control(info, LCD_CURSOR_OFF);
322 
323 	return 0;
324 }
325 
326 static struct fb_ops cobalt_lcd_fbops = {
327 	.owner		= THIS_MODULE,
328 	.fb_read	= cobalt_lcdfb_read,
329 	.fb_write	= cobalt_lcdfb_write,
330 	.fb_blank	= cobalt_lcdfb_blank,
331 	.fb_cursor	= cobalt_lcdfb_cursor,
332 };
333 
334 static int cobalt_lcdfb_probe(struct platform_device *dev)
335 {
336 	struct fb_info *info;
337 	struct resource *res;
338 	int retval;
339 
340 	info = framebuffer_alloc(0, &dev->dev);
341 	if (!info)
342 		return -ENOMEM;
343 
344 	res = platform_get_resource(dev, IORESOURCE_MEM, 0);
345 	if (!res) {
346 		framebuffer_release(info);
347 		return -EBUSY;
348 	}
349 
350 	info->screen_size = resource_size(res);
351 	info->screen_base = devm_ioremap(&dev->dev, res->start,
352 					 info->screen_size);
353 	info->fbops = &cobalt_lcd_fbops;
354 	info->fix = cobalt_lcdfb_fix;
355 	info->fix.smem_start = res->start;
356 	info->fix.smem_len = info->screen_size;
357 	info->pseudo_palette = NULL;
358 	info->par = NULL;
359 	info->flags = FBINFO_DEFAULT;
360 
361 	retval = register_framebuffer(info);
362 	if (retval < 0) {
363 		framebuffer_release(info);
364 		return retval;
365 	}
366 
367 	platform_set_drvdata(dev, info);
368 
369 	lcd_clear(info);
370 
371 	fb_info(info, "Cobalt server LCD frame buffer device\n");
372 
373 	return 0;
374 }
375 
376 static int cobalt_lcdfb_remove(struct platform_device *dev)
377 {
378 	struct fb_info *info;
379 
380 	info = platform_get_drvdata(dev);
381 	if (info) {
382 		unregister_framebuffer(info);
383 		framebuffer_release(info);
384 	}
385 
386 	return 0;
387 }
388 
389 static struct platform_driver cobalt_lcdfb_driver = {
390 	.probe	= cobalt_lcdfb_probe,
391 	.remove	= cobalt_lcdfb_remove,
392 	.driver	= {
393 		.name	= "cobalt-lcd",
394 	},
395 };
396 module_platform_driver(cobalt_lcdfb_driver);
397 
398 MODULE_LICENSE("GPL v2");
399 MODULE_AUTHOR("Yoichi Yuasa");
400 MODULE_DESCRIPTION("Cobalt server LCD frame buffer driver");
401